feat: simplify listen command - remove binding flags, add auto-genera… (#1111)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
1
bun.lock
1
bun.lock
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@letta-ai/letta-code",
|
||||
|
||||
@@ -6451,23 +6451,17 @@ export default function App({
|
||||
);
|
||||
|
||||
let name: string | undefined;
|
||||
let listenAgentId: string | undefined;
|
||||
let _listenAgentId: string | undefined;
|
||||
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
const nextPart = parts[i + 1];
|
||||
if (part === "--name" && nextPart) {
|
||||
if (part === "--env-name" && nextPart) {
|
||||
name = nextPart;
|
||||
i++;
|
||||
} else if (part === "--agent" && nextPart) {
|
||||
listenAgentId = nextPart;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to current agent if not specified
|
||||
const targetAgentId = listenAgentId || agentId;
|
||||
|
||||
const cmd = commandRunner.start(msg, "Starting listener...");
|
||||
const { handleListen, setActiveCommandId: setActiveListenCommandId } =
|
||||
await import("./commands/listen");
|
||||
@@ -6478,9 +6472,11 @@ export default function App({
|
||||
buffersRef,
|
||||
refreshDerived,
|
||||
setCommandRunning,
|
||||
agentId,
|
||||
conversationId: conversationIdRef.current,
|
||||
},
|
||||
msg,
|
||||
{ name, agentId: targetAgentId },
|
||||
{ envName: name },
|
||||
);
|
||||
} finally {
|
||||
setActiveListenCommandId(null);
|
||||
|
||||
@@ -28,6 +28,8 @@ export interface ListenCommandContext {
|
||||
buffersRef: { current: Buffers };
|
||||
refreshDerived: () => void;
|
||||
setCommandRunning: (running: boolean) => void;
|
||||
agentId: string | null;
|
||||
conversationId: string | null;
|
||||
}
|
||||
|
||||
// Helper to add a command result to buffers
|
||||
@@ -85,33 +87,64 @@ function updateCommandResult(
|
||||
}
|
||||
|
||||
interface ListenOptions {
|
||||
name?: string;
|
||||
agentId?: string;
|
||||
envName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle /listen command
|
||||
* Usage: /listen --name "george" [--agent agent-xyz]
|
||||
* Usage: /listen [--env-name "work-laptop"]
|
||||
* /listen off
|
||||
*/
|
||||
export async function handleListen(
|
||||
ctx: ListenCommandContext,
|
||||
msg: string,
|
||||
opts: ListenOptions = {},
|
||||
): Promise<void> {
|
||||
// Handle /listen off - stop the listener
|
||||
if (msg.trim() === "/listen off") {
|
||||
const { stopListenerClient, isListenerActive } = await import(
|
||||
"../../websocket/listen-client"
|
||||
);
|
||||
|
||||
if (!isListenerActive()) {
|
||||
addCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
msg,
|
||||
"Listen mode is not active.",
|
||||
false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
stopListenerClient();
|
||||
addCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
msg,
|
||||
"✓ Listen mode stopped\n\nListener disconnected from Letta Cloud.",
|
||||
true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show usage if needed
|
||||
if (msg.includes("--help") || msg.includes("-h")) {
|
||||
addCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
msg,
|
||||
"Usage: /listen --name <connection-name> [--agent <agent-id>]\n\n" +
|
||||
"Usage: /listen [--env-name <name>]\n" +
|
||||
" /listen off\n\n" +
|
||||
"Register this letta-code instance to receive messages from Letta Cloud.\n\n" +
|
||||
"Options:\n" +
|
||||
" --name <name> Friendly name for this connection (required)\n" +
|
||||
" --agent <id> Bind connection to specific agent (defaults to current agent)\n\n" +
|
||||
" --env-name <name> Friendly name for this environment (uses hostname if not provided)\n" +
|
||||
" off Stop the active listener connection\n" +
|
||||
" -h, --help Show this help message\n\n" +
|
||||
"Examples:\n" +
|
||||
' /listen --name "george" # Uses current agent\n' +
|
||||
' /listen --name "laptop-work" --agent agent-abc123\n\n' +
|
||||
" /listen # Start listener with hostname\n" +
|
||||
' /listen --env-name "work-laptop" # Start with custom name\n' +
|
||||
" /listen off # Stop listening\n\n" +
|
||||
"Once connected, this instance will listen for incoming messages from cloud agents.\n" +
|
||||
"Messages will be executed locally using your letta-code environment.",
|
||||
true,
|
||||
@@ -119,37 +152,37 @@ export async function handleListen(
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
const connectionName = opts.name;
|
||||
const agentId = opts.agentId;
|
||||
// Determine connection name
|
||||
let connectionName: string;
|
||||
|
||||
if (!connectionName) {
|
||||
addCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
msg,
|
||||
"Error: --name is required\n\n" +
|
||||
'Usage: /listen --name "george"\n\n' +
|
||||
"Provide a friendly name to identify this connection (e.g., your name, device name).",
|
||||
false,
|
||||
);
|
||||
return;
|
||||
if (opts.envName) {
|
||||
// Explicitly provided - use it and save to local project settings
|
||||
connectionName = opts.envName;
|
||||
settingsManager.setListenerEnvName(connectionName);
|
||||
} else {
|
||||
// Not provided - check saved local project settings
|
||||
const savedName = settingsManager.getListenerEnvName();
|
||||
|
||||
if (savedName) {
|
||||
// Reuse saved name
|
||||
connectionName = savedName;
|
||||
} else {
|
||||
// No saved name - use hostname and save it
|
||||
connectionName = hostname();
|
||||
settingsManager.setListenerEnvName(connectionName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!agentId) {
|
||||
addCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
msg,
|
||||
"Error: No agent specified\n\n" +
|
||||
"This connection needs a default agent to execute messages.\n" +
|
||||
"If you're seeing this, it means no agent is active in this conversation.\n\n" +
|
||||
"Please start a conversation with an agent first, or specify one explicitly:\n" +
|
||||
' /listen --name "george" --agent agent-abc123',
|
||||
false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Helper to build ADE connection URL
|
||||
const buildConnectionUrl = (connId: string): string => {
|
||||
if (!ctx.agentId) return "";
|
||||
|
||||
let url = `https://app.letta.com/agents/${ctx.agentId}?deviceId=${connId}`;
|
||||
if (ctx.conversationId) {
|
||||
url += `&conversationId=${ctx.conversationId}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
// Start listen flow
|
||||
ctx.setCommandRunning(true);
|
||||
@@ -200,7 +233,6 @@ export async function handleListen(
|
||||
body: JSON.stringify({
|
||||
deviceId,
|
||||
connectionName,
|
||||
agentId: opts.agentId,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -214,10 +246,6 @@ export async function handleListen(
|
||||
wsUrl: string;
|
||||
};
|
||||
|
||||
// Build agent info message
|
||||
const adeUrl = `https://app.letta.com/agents/${agentId}`;
|
||||
const agentInfo = `Agent: ${agentId}\n→ ${adeUrl}\n\n`;
|
||||
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
@@ -225,8 +253,7 @@ export async function handleListen(
|
||||
msg,
|
||||
`✓ Registered successfully!\n\n` +
|
||||
`Connection ID: ${connectionId}\n` +
|
||||
`Name: "${connectionName}"\n` +
|
||||
agentInfo +
|
||||
`Environment: "${connectionName}"\n` +
|
||||
`WebSocket: ${wsUrl}\n\n` +
|
||||
`Starting WebSocket connection...`,
|
||||
true,
|
||||
@@ -243,9 +270,7 @@ export async function handleListen(
|
||||
wsUrl,
|
||||
deviceId,
|
||||
connectionName,
|
||||
agentId,
|
||||
onStatusChange: (status, connId) => {
|
||||
const adeUrl = `https://app.letta.com/agents/${agentId}?deviceId=${connId}`;
|
||||
const statusText =
|
||||
status === "receiving"
|
||||
? "Receiving message"
|
||||
@@ -253,43 +278,45 @@ export async function handleListen(
|
||||
? "Processing message"
|
||||
: "Awaiting instructions";
|
||||
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
cmdId,
|
||||
msg,
|
||||
`Connected to Letta Cloud\n` +
|
||||
`${statusText}\n\n` +
|
||||
`View in ADE → ${adeUrl}`,
|
||||
true,
|
||||
"finished",
|
||||
);
|
||||
},
|
||||
onRetrying: (attempt, _maxAttempts, nextRetryIn) => {
|
||||
const adeUrl = `https://app.letta.com/agents/${agentId}?deviceId=${connectionId}`;
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
cmdId,
|
||||
msg,
|
||||
`Reconnecting to Letta Cloud...\n` +
|
||||
`Attempt ${attempt}, retrying in ${Math.round(nextRetryIn / 1000)}s\n\n` +
|
||||
`View in ADE → ${adeUrl}`,
|
||||
true,
|
||||
"running",
|
||||
);
|
||||
},
|
||||
onConnected: () => {
|
||||
const adeUrl = `https://app.letta.com/agents/${agentId}?deviceId=${connectionId}`;
|
||||
const url = buildConnectionUrl(connId);
|
||||
const urlText = url ? `\n\nConnect to this environment:\n${url}` : "";
|
||||
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
cmdId,
|
||||
msg,
|
||||
`Connected to Letta Cloud\n` +
|
||||
`Awaiting instructions\n\n` +
|
||||
`View in ADE → ${adeUrl}`,
|
||||
`Environment initialized: ${connectionName}\n${statusText}${urlText}`,
|
||||
true,
|
||||
"finished",
|
||||
);
|
||||
},
|
||||
onRetrying: (attempt, _maxAttempts, nextRetryIn, connId) => {
|
||||
const url = buildConnectionUrl(connId);
|
||||
const urlText = url ? `\n\nConnect to this environment:\n${url}` : "";
|
||||
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
cmdId,
|
||||
msg,
|
||||
`Environment initialized: ${connectionName}\n` +
|
||||
`Reconnecting to Letta Cloud...\n` +
|
||||
`Attempt ${attempt}, retrying in ${Math.round(nextRetryIn / 1000)}s${urlText}`,
|
||||
true,
|
||||
"running",
|
||||
);
|
||||
},
|
||||
onConnected: (connId) => {
|
||||
const url = buildConnectionUrl(connId);
|
||||
const urlText = url ? `\n\nConnect to this environment:\n${url}` : "";
|
||||
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
cmdId,
|
||||
msg,
|
||||
`Environment initialized: ${connectionName}\nAwaiting instructions${urlText}`,
|
||||
true,
|
||||
"finished",
|
||||
);
|
||||
|
||||
@@ -118,6 +118,15 @@ export const commands: Record<string, Command> = {
|
||||
return "Opening provider connection...";
|
||||
},
|
||||
},
|
||||
// "/listen": {
|
||||
// desc: "Connect to Letta Cloud (device connect mode)",
|
||||
// args: "[--env-name <name>]",
|
||||
// order: 17.5,
|
||||
// handler: () => {
|
||||
// // Handled specially in App.tsx
|
||||
// return "Starting listener...";
|
||||
// },
|
||||
// },
|
||||
"/clear": {
|
||||
desc: "Clear in-context messages",
|
||||
order: 18,
|
||||
|
||||
@@ -3,9 +3,8 @@ import Spinner from "ink-spinner";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface ListenerStatusUIProps {
|
||||
agentId: string;
|
||||
connectionId: string;
|
||||
conversationId?: string;
|
||||
envName: string;
|
||||
onReady: (callbacks: {
|
||||
updateStatus: (status: "idle" | "receiving" | "processing") => void;
|
||||
updateRetryStatus: (attempt: number, nextRetryIn: number) => void;
|
||||
@@ -14,7 +13,7 @@ interface ListenerStatusUIProps {
|
||||
}
|
||||
|
||||
export function ListenerStatusUI(props: ListenerStatusUIProps) {
|
||||
const { agentId, connectionId, conversationId, onReady } = props;
|
||||
const { connectionId, envName, onReady } = props;
|
||||
const [status, setStatus] = useState<"idle" | "receiving" | "processing">(
|
||||
"idle",
|
||||
);
|
||||
@@ -35,8 +34,6 @@ export function ListenerStatusUI(props: ListenerStatusUIProps) {
|
||||
});
|
||||
}, [onReady]);
|
||||
|
||||
const adeUrl = `https://app.letta.com/agents/${agentId}?deviceId=${connectionId}${conversationId ? `&conversationId=${conversationId}` : ""}`;
|
||||
|
||||
const statusText = retryInfo
|
||||
? `Reconnecting (attempt ${retryInfo.attempt}, retry in ${Math.round(retryInfo.nextRetryIn / 1000)}s)`
|
||||
: status === "receiving"
|
||||
@@ -51,7 +48,7 @@ export function ListenerStatusUI(props: ListenerStatusUIProps) {
|
||||
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
||||
<Box marginBottom={1}>
|
||||
<Text bold color="green">
|
||||
Connected to Letta Cloud
|
||||
The name of your environment is: {envName}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -68,9 +65,10 @@ export function ListenerStatusUI(props: ListenerStatusUIProps) {
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text dimColor>View in ADE → </Text>
|
||||
<Text color="blue" underline>
|
||||
{adeUrl}
|
||||
<Text dimColor>
|
||||
Connect to this environment by visiting any agent and clicking the
|
||||
"cloud" button at the bottom left of the messenger input and swapping
|
||||
your environment to {envName}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,27 +1,47 @@
|
||||
/**
|
||||
* CLI subcommand: letta listen --name "george"
|
||||
* CLI subcommand: letta listen --name \"george\"
|
||||
* Register letta-code as a listener to receive messages from Letta Cloud
|
||||
*/
|
||||
|
||||
import { hostname } from "node:os";
|
||||
import { parseArgs } from "node:util";
|
||||
import { render } from "ink";
|
||||
import { Box, render, Text } from "ink";
|
||||
import TextInput from "ink-text-input";
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { getServerUrl } from "../../agent/client";
|
||||
import { settingsManager } from "../../settings-manager";
|
||||
import { ListenerStatusUI } from "../components/ListenerStatusUI";
|
||||
|
||||
export async function runListenSubcommand(argv: string[]): Promise<number> {
|
||||
// Preprocess args to support --conv as alias for --conversation
|
||||
const processedArgv = argv.map((arg) =>
|
||||
arg === "--conv" ? "--conversation" : arg,
|
||||
);
|
||||
/**
|
||||
* Interactive prompt for environment name
|
||||
*/
|
||||
function PromptEnvName(props: {
|
||||
onSubmit: (envName: string) => void;
|
||||
}): React.ReactElement {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>Enter environment name (or press Enter for hostname): </Text>
|
||||
<TextInput
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
onSubmit={(input) => {
|
||||
const finalName = input.trim() || hostname();
|
||||
props.onSubmit(finalName);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export async function runListenSubcommand(argv: string[]): Promise<number> {
|
||||
// Parse arguments
|
||||
const { values } = parseArgs({
|
||||
args: processedArgv,
|
||||
args: argv,
|
||||
options: {
|
||||
name: { type: "string" },
|
||||
agent: { type: "string" },
|
||||
conversation: { type: "string", short: "C" },
|
||||
envName: { type: "string" },
|
||||
help: { type: "boolean", short: "h" },
|
||||
},
|
||||
allowPositionals: false,
|
||||
@@ -29,30 +49,20 @@ export async function runListenSubcommand(argv: string[]): Promise<number> {
|
||||
|
||||
// Show help
|
||||
if (values.help) {
|
||||
console.log(
|
||||
"Usage: letta listen --name <connection-name> [--agent <agent-id>] [--conversation <id>]\n",
|
||||
);
|
||||
console.log("Usage: letta listen [--env-name <name>]\n");
|
||||
console.log(
|
||||
"Register this letta-code instance to receive messages from Letta Cloud.\n",
|
||||
);
|
||||
console.log("Options:");
|
||||
console.log(
|
||||
" --name <name> Friendly name for this connection (required)",
|
||||
);
|
||||
console.log(
|
||||
" --agent <id> Bind connection to specific agent (required for CLI usage)",
|
||||
);
|
||||
console.log(" --conversation <id>, --conv <id>, -C <id>");
|
||||
console.log(
|
||||
" Route messages to a specific conversation",
|
||||
" --env-name <name> Friendly name for this environment (uses hostname if not provided)",
|
||||
);
|
||||
console.log(" -h, --help Show this help message\n");
|
||||
console.log("Examples:");
|
||||
console.log(' letta listen --name "george" --agent agent-abc123');
|
||||
console.log(' letta listen --name "laptop-work" --agent agent-xyz789');
|
||||
console.log(
|
||||
' letta listen --name "daily-cron" --agent agent-abc123 --conv conv-xyz789\n',
|
||||
" letta listen # Uses hostname as default",
|
||||
);
|
||||
console.log(' letta listen --env-name "work-laptop"\n');
|
||||
console.log(
|
||||
"Once connected, this instance will listen for incoming messages from cloud agents.",
|
||||
);
|
||||
@@ -62,29 +72,39 @@ export async function runListenSubcommand(argv: string[]): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const connectionName = values.name;
|
||||
const agentId = values.agent;
|
||||
const conversationId = values.conversation as string | undefined;
|
||||
// Load local project settings to access saved environment name
|
||||
await settingsManager.loadLocalProjectSettings();
|
||||
|
||||
if (!connectionName) {
|
||||
console.error("Error: --name is required\n");
|
||||
console.error('Usage: letta listen --name "george" --agent agent-abc123\n');
|
||||
console.error(
|
||||
"Provide a friendly name to identify this connection (e.g., your name, device name).",
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
// Determine connection name
|
||||
let connectionName: string;
|
||||
|
||||
if (!agentId) {
|
||||
console.error("Error: --agent is required\n");
|
||||
console.error('Usage: letta listen --name "george" --agent agent-abc123\n');
|
||||
console.error(
|
||||
"A listener connection needs a default agent to execute messages.",
|
||||
);
|
||||
console.error(
|
||||
"Specify which agent should receive messages from this connection.",
|
||||
);
|
||||
return 1;
|
||||
if (values.envName) {
|
||||
// Explicitly provided - use it and save to local project settings
|
||||
connectionName = values.envName;
|
||||
settingsManager.setListenerEnvName(connectionName);
|
||||
} else {
|
||||
// Not provided - check saved local project settings
|
||||
const savedName = settingsManager.getListenerEnvName();
|
||||
|
||||
if (savedName) {
|
||||
// Reuse saved name
|
||||
connectionName = savedName;
|
||||
} else {
|
||||
// No saved name - prompt user
|
||||
connectionName = await new Promise<string>((resolve) => {
|
||||
const { unmount } = render(
|
||||
<PromptEnvName
|
||||
onSubmit={(name) => {
|
||||
unmount();
|
||||
resolve(name);
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
// Save to local project settings for future runs
|
||||
settingsManager.setListenerEnvName(connectionName);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -115,8 +135,6 @@ export async function runListenSubcommand(argv: string[]): Promise<number> {
|
||||
body: JSON.stringify({
|
||||
deviceId,
|
||||
connectionName,
|
||||
agentId,
|
||||
...(conversationId && { conversationId }),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -144,9 +162,8 @@ export async function runListenSubcommand(argv: string[]): Promise<number> {
|
||||
|
||||
const { unmount } = render(
|
||||
<ListenerStatusUI
|
||||
agentId={agentId}
|
||||
connectionId={connectionId}
|
||||
conversationId={conversationId}
|
||||
envName={connectionName}
|
||||
onReady={(callbacks) => {
|
||||
updateStatusCallback = callbacks.updateStatus;
|
||||
updateRetryStatusCallback = callbacks.updateRetryStatus;
|
||||
@@ -165,8 +182,6 @@ export async function runListenSubcommand(argv: string[]): Promise<number> {
|
||||
wsUrl,
|
||||
deviceId,
|
||||
connectionName,
|
||||
agentId,
|
||||
defaultConversationId: conversationId,
|
||||
onStatusChange: (status) => {
|
||||
clearRetryStatusCallback?.();
|
||||
updateStatusCallback?.(status);
|
||||
|
||||
@@ -120,6 +120,7 @@ export interface LocalProjectSettings {
|
||||
// Server-indexed settings (agent IDs are server-specific)
|
||||
sessionsByServer?: Record<string, SessionRef>; // key = normalized base URL
|
||||
pinnedAgentsByServer?: Record<string, string[]>; // key = normalized base URL
|
||||
listenerEnvName?: string; // Saved environment name for listener connections (project-specific)
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: Settings = {
|
||||
@@ -1349,6 +1350,54 @@ class SettingsManager {
|
||||
console.warn("unpinProfile is deprecated, use unpinLocal(agentId) instead");
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Listener Environment Name Helpers
|
||||
// =====================================================================
|
||||
|
||||
/**
|
||||
* Get saved listener environment name from local project settings (if any).
|
||||
* Returns undefined if not set or settings not loaded.
|
||||
*/
|
||||
getListenerEnvName(
|
||||
workingDirectory: string = process.cwd(),
|
||||
): string | undefined {
|
||||
try {
|
||||
const localSettings = this.getLocalProjectSettings(workingDirectory);
|
||||
return localSettings.listenerEnvName;
|
||||
} catch {
|
||||
// Settings not loaded yet
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save listener environment name to local project settings.
|
||||
* Loads settings if not already loaded.
|
||||
*/
|
||||
setListenerEnvName(
|
||||
envName: string,
|
||||
workingDirectory: string = process.cwd(),
|
||||
): void {
|
||||
try {
|
||||
this.updateLocalProjectSettings(
|
||||
{ listenerEnvName: envName },
|
||||
workingDirectory,
|
||||
);
|
||||
} catch {
|
||||
// Settings not loaded yet - load and retry
|
||||
this.loadLocalProjectSettings(workingDirectory)
|
||||
.then(() => {
|
||||
this.updateLocalProjectSettings(
|
||||
{ listenerEnvName: envName },
|
||||
workingDirectory,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to save listener environment name:", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Agent Settings (unified agents array) Helpers
|
||||
// =====================================================================
|
||||
|
||||
@@ -33,9 +33,7 @@ interface StartListenerOptions {
|
||||
wsUrl: string;
|
||||
deviceId: string;
|
||||
connectionName: string;
|
||||
agentId?: string;
|
||||
defaultConversationId?: string;
|
||||
onConnected: () => void;
|
||||
onConnected: (connectionId: string) => void;
|
||||
onDisconnected: () => void;
|
||||
onError: (error: Error) => void;
|
||||
onStatusChange?: (
|
||||
@@ -46,6 +44,7 @@ interface StartListenerOptions {
|
||||
attempt: number,
|
||||
maxAttempts: number,
|
||||
nextRetryIn: number,
|
||||
connectionId: string,
|
||||
) => void;
|
||||
}
|
||||
|
||||
@@ -353,7 +352,7 @@ async function connectWithRetry(
|
||||
Math.log2(MAX_RETRY_DURATION_MS / INITIAL_RETRY_DELAY_MS),
|
||||
);
|
||||
|
||||
opts.onRetrying?.(attempt, maxAttempts, delay);
|
||||
opts.onRetrying?.(attempt, maxAttempts, delay, opts.connectionId);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
runtime.reconnectTimeout = setTimeout(resolve, delay);
|
||||
@@ -381,9 +380,6 @@ async function connectWithRetry(
|
||||
const url = new URL(opts.wsUrl);
|
||||
url.searchParams.set("deviceId", opts.deviceId);
|
||||
url.searchParams.set("connectionName", opts.connectionName);
|
||||
if (opts.agentId) {
|
||||
url.searchParams.set("agentId", opts.agentId);
|
||||
}
|
||||
|
||||
const socket = new WebSocket(url.toString(), {
|
||||
headers: {
|
||||
@@ -399,7 +395,7 @@ async function connectWithRetry(
|
||||
}
|
||||
|
||||
runtime.hasSuccessfulConnection = true;
|
||||
opts.onConnected();
|
||||
opts.onConnected(opts.connectionId);
|
||||
|
||||
// Send current mode state to cloud for UI sync
|
||||
sendClientMessage(socket, {
|
||||
@@ -439,7 +435,6 @@ async function connectWithRetry(
|
||||
socket,
|
||||
opts.onStatusChange,
|
||||
opts.connectionId,
|
||||
opts.defaultConversationId,
|
||||
);
|
||||
opts.onStatusChange?.("idle", opts.connectionId);
|
||||
})
|
||||
@@ -504,7 +499,6 @@ async function handleIncomingMessage(
|
||||
connectionId: string,
|
||||
) => void,
|
||||
connectionId?: string,
|
||||
defaultConversationId?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const agentId = msg.agentId;
|
||||
@@ -515,8 +509,7 @@ async function handleIncomingMessage(
|
||||
const requestedConversationId = msg.conversationId || undefined;
|
||||
|
||||
// For sendMessageStream: "default" means use agent endpoint, else use conversations endpoint
|
||||
const conversationId =
|
||||
requestedConversationId ?? defaultConversationId ?? "default";
|
||||
const conversationId = requestedConversationId ?? "default";
|
||||
|
||||
if (!agentId) {
|
||||
return;
|
||||
@@ -746,6 +739,13 @@ async function handleIncomingMessage(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if listener is currently active.
|
||||
*/
|
||||
export function isListenerActive(): boolean {
|
||||
return activeRuntime !== null && activeRuntime.socket !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the active listener connection.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user