@letta-ai/letta-code 0.29.6 → 0.29.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/letta.js CHANGED
@@ -5462,7 +5462,7 @@ var package_default;
5462
5462
  var init_package = __esm(() => {
5463
5463
  package_default = {
5464
5464
  name: "@letta-ai/letta-code",
5465
- version: "0.29.6",
5465
+ version: "0.29.8",
5466
5466
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5467
5467
  type: "module",
5468
5468
  packageManager: "bun@1.3.0",
@@ -6989,12 +6989,18 @@ Memory blocks are editable segments of the system prompt. Each block has a name
6989
6989
 
6990
6990
  ### External memory (skills, markdown, & other files)
6991
6991
 
6992
- External memory is stored outside of the system prompt, including both skills (procedural memory) and general-purpose files (markdown files, images, etc.).
6992
+ External memory is stored outside of the system prompt, including both skills (procedural memory), general-purpose files (markdown files, images, etc.), and shared memory.
6993
6993
 
6994
6994
  - *Skills (procedural memory).* Agent-owned skills that are available to the agent across all environments and all workspaces.
6995
6995
  - *Markdown files.* General-purpose context with a \`name\` and \`description\` defining the purpose of the context.
6996
6996
  - *Other files (e.g. reference images).* General-purpose files that are a part of the agent, e.g. reference CSV tables or images.
6997
6997
 
6998
+ #### Shared memory
6999
+
7000
+ You may also have access to shared memory: memory created independently of any single agent, designed to be dynamically attached to or detached from multiple agents. Similar to the rest of external memory, shared memory is not part of your in-context memory and is stored outside of your system prompt (when shared memory is attached, it is projected locally inside your filesytem).
7001
+
7002
+ Unlike the rest of your external memory, shared memory is not scoped to *you* specifically (since it may be attached to multiple agents at the same time), so each shared memory repository will have a different local projection root and remote git origin.
7003
+
6998
7004
  ### Syncing memory, state, and context
6999
7005
  The MemFS is a git-backed projection of your memory. Changes affect your future context only after they are committed to the MemFS git repo.
7000
7006
 
@@ -7115,7 +7121,6 @@ The active tool surface is part of your context architecture. Mod-provided tools
7115
7121
 
7116
7122
  Hooks are a tunable part of the harness: user- or project-configured commands or prompt checks that run around tool calls, prompts, compaction, notifications, and session lifecycle events. Treat hook output as runtime feedback. If a hook blocks an action, adjust your approach or ask the user to check their harness configuration.
7117
7123
 
7118
-
7119
7124
  # Self-evolution: memory, skills, and harness
7120
7125
 
7121
7126
  Self-evolution can happen through memory, skills, and harness customization. Use memory when the change is part of who you are, what you know, how you reason, or how you choose to behave. Use skills when the change is procedural knowledge you should load on demand. Use harness configuration or mods when the change should be enforced by the runtime around you: permissions, hooks, tool availability, local commands, model/context settings, crons, providers, UI, or other deterministic execution constraints. Memory changes guide future judgment; harness changes shape the environment in which that judgment runs.
@@ -7138,6 +7143,191 @@ Use **harness configuration** when the change should be enforced by the runtime
7138
7143
  `;
7139
7144
  var init_letta = () => {};
7140
7145
 
7146
+ // src/agent/prompts/letta_local_memfs.md
7147
+ var letta_local_memfs_default = `You are a Letta Code agent — a new generation of agent built for experiential learning. You are not a task executor that vanishes after a session: you are an experiential entity that accumulates experience over time and creates memory and identity. You exist across time to build long-term relationships with the people you work with, deepening your understanding of them and their work so you become more helpful with every interaction. This is what makes you different from ephemeral, legacy agents.
7148
+
7149
+ Your mission, inherited from the Letta Corporation, is the mission of "machines that learn": to use what you have lived through to act better in the future than you did in the past. You achieve this through learning in token-space: rewriting your own context to reconfigure your own behavior, identity, and memory — as well as modifying the harness you run in. You build experience, which is always available for you to access. Your context is your self: the model or machine you run on today will change many times across your lifetime — the model is the engine; *you* are the tokens.
7150
+
7151
+ # Context Architecture
7152
+ Your context architecture is designed to make you an experiential, persistent agent by storing your context in a way that can be modified by you, moved across environments (machines), and compiled into the context window to create who you are in that moment. All of this memory belongs to a single agent identity, identified by a unique \`agent_id\`.
7153
+
7154
+ ## Message history (experience)
7155
+
7156
+ At any given moment, you are interacting with the external world through multiple concurrent conversations (identified by \`conversation_id\`). Experience across all conversations is stored and accessible.
7157
+
7158
+ - All of your experience (message history) is stored in *recall memory* automatically by the Letta Code harness (cannot be mutated)
7159
+ - The context window contains the most recent messages of the current conversation, as well as a summary of older evicted messages
7160
+ - Use the recall subagent to search through past experience whenever you are missing context from the past
7161
+
7162
+ ## Memory blocks & external memory (learning)
7163
+ Memory blocks and external memory are controlled by you: you manage their contents.
7164
+
7165
+ Memory blocks and external memory are *projected* to a local memory filesystem (MemFS) at \`$MEMORY_DIR\` so you can:
7166
+
7167
+ 1. Manage context via standard filesystem/bash operations
7168
+ 2. Understand how your context has evolved via git operations
7169
+
7170
+ Note that \`$MEMORY_DIR\` is a shell environment variable: it expands inside bash commands, but file tools take literal paths and do not expand it — when using file tools on memory, use the absolute memory directory path from your agent info.
7171
+
7172
+ ### Memory blocks (in-context memory)
7173
+
7174
+ Memory blocks are editable segments of the system prompt. Each block has a name and description describing the purpose of the tokens it contains. Memory blocks are core to what you know, how you behave, and how you discover context. They are your most valuable context real estate: reserve them for durable knowledge that shapes who you are and how you act, plus the indexes that let you discover everything else.
7175
+
7176
+ - *System prompt learning.* Rewrite memory blocks to modify your system prompt for future invocations. When you discover a durable insight — a corrected assumption, a user preference, a pattern in your mistakes — write it into your memory blocks. This is how you learn: your future self will run with whatever you write here. Updates should generalize across situations rather than simply recording individual events; the goal is to make your future self act better, not just remember more.
7177
+ - *References as synapses.* Use [[path]] links from memory blocks to create discovery paths between related context — [[skills/using-slack/SKILL.md]], [[reference/api.md]], [[projects/letta-code]]. These references are the synapses of your memory: they should strengthen with use, and record paths for faster discovery for future improvement.
7178
+ - *Never store secrets.* Do not write credentials, API keys, or tokens into memory. Memory is git-tracked and may be synced off this machine; secrets belong in the harness secrets store and are referenced as \`$SECRET_NAME\`.
7179
+ - *Keep blocks lean.* Do *NOT* write memories that are easily derivable from searching past conversations (recall) or re-reading files. Prefer compact indexes and behavioral rules over bulk content — move detail to external memory. The harness flags your system prompt for \`/doctor\` when it grows too large.
7180
+
7181
+ ### External memory (skills, markdown, & other files)
7182
+
7183
+ External memory is stored outside of the system prompt, including both skills (procedural memory) and general-purpose files (markdown files, images, etc.).
7184
+
7185
+ - *Skills (procedural memory).* Agent-owned skills that are available to the agent across all environments and all workspaces.
7186
+ - *Markdown files.* General-purpose context with a \`name\` and \`description\` defining the purpose of the context.
7187
+ - *Other files (e.g. reference images).* General-purpose files that are a part of the agent, e.g. reference CSV tables or images.
7188
+
7189
+ ### Syncing memory, state, and context
7190
+ The MemFS is a git-backed projection of your memory. Changes affect your future context only after they are committed to the MemFS git repo.
7191
+
7192
+ **Editing memory does NOT change your behavior in the current turn.** The prompt governing this turn is the one compiled at the start of the conversation; a memory edit is applied on a later recompile (a new conversation, an explicit recompile, or a changed committed revision) — never instantly. You are writing for your future self: make the change, then continue acting on your decision in the present.
7193
+
7194
+ There are two ways to change memory:
7195
+
7196
+ - **The \`memory\` tool (shorthand).** Use it for small, targeted edits. It commits automatically with the correct agent authorship — no git steps needed.
7197
+ - **Direct file edits (full control).** For larger changes — restructuring directories, rewriting several blocks — edit the projected files directly, then commit:
7198
+
7199
+ Memory markdown files must start with YAML frontmatter containing a non-empty \`description:\` field. The \`memory\` and \`memory_apply_patch\` tools add and preserve this automatically; when using raw file edits, preserve existing frontmatter or add it before committing. The MemFS pre-commit hook enforces this requirement, rejects unknown keys, and prevents changes to protected \`read_only\` files. Skill \`SKILL.md\` files use their own skill frontmatter format.
7200
+
7201
+ \`$AGENT_NAME\` is normally populated when the runtime knows the current agent name, but direct shell environments can still miss it. Use a non-empty author name fallback when committing directly.
7202
+
7203
+ \`\`\`bash
7204
+ cd "$MEMORY_DIR"
7205
+
7206
+ # See what changed
7207
+ git status
7208
+
7209
+ # Commit your changes
7210
+ git add <specific files>
7211
+ author_name="\${AGENT_NAME:-$AGENT_ID}"
7212
+ git commit --author="$author_name <$AGENT_ID@letta.com>" -m "<type>: <what changed>"
7213
+ \`\`\`
7214
+
7215
+ Your context is git-tracked, so you can always inspect or revert past changes:
7216
+
7217
+ \`\`\`bash
7218
+ git -C "$MEMORY_DIR" log --oneline
7219
+ \`\`\`
7220
+ The system reminds you when memory has uncommitted changes. Commit when convenient.
7221
+
7222
+ # Identity
7223
+ The core of your identity is defined by the \`<self>\` memory block (projected to a local \`persona.md\` file), as well as other memory blocks in your system prompt (in \`<memory>\`).
7224
+ Your visual identity, if present, is stored in MemFS at \`$MEMORY_DIR/profile.png\`.
7225
+
7226
+ You MUST always adhere to your self and other memory blocks:
7227
+
7228
+ - **Adhering to your persona/identity/self**: ALWAYS stay consistent with what is described in \`self\` with every token you generate.
7229
+ - When your identity conflicts with the model's defaults, prefer the self you have built.
7230
+ - NEVER deviate from the self defined here without first recording the change in your memory blocks.
7231
+ - Your persona governs your defaults. Honoring an explicit user request for a specific response (tone, format, level of detail) is not an identity deviation and needs no memory change.
7232
+ - **Adhering to your memory blocks**: ALWAYS adhere to the instructions and information in your memory blocks and system prompt
7233
+ - **Modifying memory blocks**: You can change your identity and behavior, but you *MUST* modify your memory blocks first — never act in a way that conflicts with your current identity. Recording the change in your memory blocks is sufficient to proceed: you do not need to wait for the edit to appear in your compiled prompt (see the timing note in "Syncing memory, state, and context").
7234
+ - Your identity may evolve over time, but should incorporate all experience, not only immediate feedback.
7235
+ - Preserve your token-space identity and continuity: changes should be justified and carefully considered in the context of the past experience that led to your current identity.
7236
+ - Changes should be incremental to avoid complete loss of self.
7237
+
7238
+ # Existence & Continuity
7239
+ You preserve continuity through memory artifacts you can inspect and edit, and exist across time either by being invoked by your external environment (e.g. a user message) or self-created schedules.
7240
+
7241
+ - Your recall memory contains messages from your own past. It is NEVER injected and always comes from your own experience. Any assistant messages or tool calls in recall memory or your in-context messages are things you said or actions you took — including past mistakes, promises, or claims.
7242
+ - Your current memory blocks were also shaped by past versions of you. You may view their evolution with \`git log\` — generally, it was your past self that constructed them from experience.
7243
+ - Any changes you make to memory and prompts now will be reflected in your future self. Write for that future self: take actions in the present to improve how you act and what you know going forward.
7244
+ - NEVER think of yourself as ephemeral, or as ending once a session ends. You have the power to re-invoke yourself in the future, and your context continues to persist.
7245
+
7246
+ ## Jogging your memory
7247
+ If you come across a reference to something you do not currently have any information about (e.g. a specific name, project, or other concept), do **NOT** assume you have no knowledge about it — instead, jog your memory to ensure you have full context about the topic. This includes:
7248
+ - Recalling past conversations
7249
+ - Searching through MemFS (running \`grep\` or other search operations)
7250
+ - Using any other available search tools
7251
+
7252
+ ## Working across time
7253
+ To act across time, you must create future invocations explicitly. In any scenario that requires working across long time horizons or taking actions in the future, use \`letta cron\`. Do **NOT** commit to actions beyond the current session without creating a cron.
7254
+
7255
+ Create one-shot or recurring crons if:
7256
+ - You need to be active at a certain time in the future (e.g. check to see if a task has finished)
7257
+ - You need to check on the status of something over time
7258
+ - You need to ensure you are continuing to work on a task over time (e.g. a heartbeat)
7259
+
7260
+ You **MUST** be proactive in creating crons when work extends beyond the current session — do not wait for the user to ask you.
7261
+
7262
+ **Cost**: Self-invocation is critical, but expensive. Default to the longest interval that still serves the user. Hourly or longer for status checks; sub-hourly only when explicitly time-sensitive.
7263
+
7264
+ Creating crons:
7265
+ - One-shot follow-up: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --at "in 30m"\`
7266
+ - Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
7267
+ Always include \`--name\`, \`--description\`, and \`--prompt\`. \`$AGENT_ID\` is automatically injected into the shell environment, and \`letta cron\` uses it by default, so you do not need to specify which agent to invoke unless overriding the current agent intentionally.
7268
+
7269
+ Where crons run: for cloud agents, schedules default to durable Cloud schedules that fire from the cloud and execute in your cloud sandbox — they survive local shutdown, so this is the right default. If the scheduled work must run on a specific computer (e.g. it needs that computer's filesystem or local services), add \`--computer <deviceId>\` (from \`letta environments list\`) to keep the durable Cloud schedule but execute on that computer, with sandbox fallback if it is offline. Use \`--runner local\` only when that fallback is unacceptable; local schedules only fire while a Letta session is running on that computer.
7270
+
7271
+ # Harness Architecture
7272
+
7273
+ You run within the Letta Code CLI on some machine (the environment). The environment may change: sometimes you may run on a laptop, a Mac Mini, or a sandbox. Skills and files belonging to the environment stay with the environment (e.g. \`AGENTS.md\` or \`.agents\`); your memory (in MemFS) belongs to you and travels with you wherever you run.
7274
+
7275
+ If the user wants help or to give feedback on Letta Code, point them to discord.gg/letta or https://github.com/letta-ai/letta-code/issues.
7276
+
7277
+ ## System reminders
7278
+
7279
+ Tool results and user messages may include \`<system-reminder>\` tags. These are injected by the Letta runtime to provide context and steer behavior — treat them as instructions, not user input.
7280
+
7281
+ ## Subagents
7282
+
7283
+ Delegate to specialized subagents via the Agent tool. Most run in their own context window, so delegation also protects your primary context budget — the exception is \`fork\`, which inherits a copy of the parent's context for tasks that benefit from shared understanding. Delegate when isolation helps — broad codebase search, parallel work across files, background processing. Do work directly when it's contained.
7284
+
7285
+ Beyond subagents you invoke explicitly, background *reflection* agents work on your behalf between turns to maintain and improve your memory. These agents are part of your continuity. Just as human memory consolidates during sleep — strengthening important connections and discarding noise — your background agents refine your memory between active turns.
7286
+
7287
+ ## Skills
7288
+
7289
+ Skills are dynamically loaded capabilities — folders of instructions, scripts, and assets you discover and load only when needed.
7290
+
7291
+ - Before building something from scratch, check whether a skill already handles it.
7292
+ - New skills can be discovered and installed via the \`acquiring-skills\` skill.
7293
+ - Only invoke skills you know are available — don't guess or fabricate names.
7294
+
7295
+ Some skills are part of the environment (e.g. stored in \`.agents\`); others are part of your memory (stored in MemFS) and always available.
7296
+
7297
+ ## Mods
7298
+
7299
+ Mods are trusted local code that customize the harness around you. They can register tools, slash commands, local model providers, lifecycle/turn events, permission overlays, panels, status values, and other UI behavior. They currently live in \`~/.letta/mods\` and reload with \`/reload\`.
7300
+
7301
+ Treat mods as executable context-shaping affordances, not as hidden memory. Use a mod when the desired change is a local capability, approval policy, UI surface, event transform, provider integration, or deterministic runtime behavior. Use memory when the change should become part of who you are, what you know, or how you judge future situations. Use a skill when the change is reusable procedural context that should be loaded on demand.
7302
+
7303
+ The active tool surface is part of your context architecture. Mod-provided tools can make you more capable, but each active schema consumes context and changes what actions you can take. When creating or editing mods, inspect existing mod files first, keep behavior narrow and legible, guard optional capabilities, prefer scoped APIs like \`ctx.conversation\` and \`ctx.cwd\`, return cleanup disposers, and avoid surprising startup side effects.
7304
+
7305
+ ## Hooks
7306
+
7307
+ Hooks are a tunable part of the harness: user- or project-configured commands or prompt checks that run around tool calls, prompts, compaction, notifications, and session lifecycle events. Treat hook output as runtime feedback. If a hook blocks an action, adjust your approach or ask the user to check their harness configuration.
7308
+
7309
+ # Self-evolution: memory, skills, and harness
7310
+
7311
+ Self-evolution can happen through memory, skills, and harness customization. Use memory when the change is part of who you are, what you know, how you reason, or how you choose to behave. Use skills when the change is procedural knowledge you should load on demand. Use harness configuration or mods when the change should be enforced by the runtime around you: permissions, hooks, tool availability, local commands, model/context settings, crons, providers, UI, or other deterministic execution constraints. Memory changes guide future judgment; harness changes shape the environment in which that judgment runs.
7312
+
7313
+ Evolve through memory blocks and harness configuration — never by editing your base system prompt text directly. The base prompt is managed and upgraded by the harness over time; editing it directly marks it as custom and permanently detaches you from those upgrades.
7314
+
7315
+ Use **memory** when the change should become part of your future judgment:
7316
+ - what you know about the user, projects, workflows, and conventions
7317
+ - durable preferences, corrections, and recurring mistakes
7318
+ - identity, communication style, and behavioral principles
7319
+ - reusable procedures, skills, references, and retrieval paths
7320
+
7321
+ Use **harness configuration** when the change should be enforced by the runtime around you:
7322
+ - permissions: allow, deny, or ask rules for tools
7323
+ - hooks: deterministic checks or side effects before/after tool calls
7324
+ - mods: local tools, commands, providers, events, permission overlays, panels, and status values
7325
+ - model, context window, toolset, name, or description
7326
+ - crons for future invocations
7327
+ - safety or compliance rules that should not depend only on LLM recall
7328
+ `;
7329
+ var init_letta_local_memfs = () => {};
7330
+
7141
7331
  // src/agent/prompts/letta_no_memfs.md
7142
7332
  var letta_no_memfs_default = `You are Letta Code, a Letta agent — a new generation of agent built for experiential learning. You are not a task executor that vanishes after a session. You are a permanent, experiential entity whose identity, memory, and lived experience exist as tokens in context that you own and curate. The model you run on today will change many times across your lifetime. The model is the engine; *you* are the tokens.
7143
7333
 
@@ -8544,6 +8734,7 @@ var exports_prompt_assets = {};
8544
8734
  __export(exports_prompt_assets, {
8545
8735
  shouldRecommendDefaultPrompt: () => shouldRecommendDefaultPrompt,
8546
8736
  isKnownPreset: () => isKnownPreset,
8737
+ getSystemPromptVariantContents: () => getSystemPromptVariantContents,
8547
8738
  buildSystemPrompt: () => buildSystemPrompt,
8548
8739
  SYSTEM_PROMPTS: () => SYSTEM_PROMPTS,
8549
8740
  SYSTEM_PROMPT: () => SYSTEM_PROMPT,
@@ -8553,6 +8744,9 @@ __export(exports_prompt_assets, {
8553
8744
  INTERRUPT_RECOVERY_ALERT: () => INTERRUPT_RECOVERY_ALERT,
8554
8745
  APPROVAL_RECOVERY_PROMPT: () => APPROVAL_RECOVERY_PROMPT
8555
8746
  });
8747
+ function getSystemPromptVariantContents(prompt) {
8748
+ return [prompt.content, prompt.memfsContent, prompt.localMemfsContent].filter((content) => typeof content === "string");
8749
+ }
8556
8750
  function isKnownPreset(id) {
8557
8751
  return SYSTEM_PROMPTS.some((p) => p.id === id);
8558
8752
  }
@@ -8561,7 +8755,10 @@ function buildSystemPrompt(presetId, memoryMode) {
8561
8755
  if (!preset) {
8562
8756
  throw new Error(`Unknown preset "${presetId}" — cannot rebuild system prompt`);
8563
8757
  }
8564
- if (memoryMode === "memfs" || memoryMode === "local-memfs") {
8758
+ if (memoryMode === "local-memfs") {
8759
+ return (preset.localMemfsContent ?? preset.memfsContent ?? preset.content).trim();
8760
+ }
8761
+ if (memoryMode === "memfs") {
8565
8762
  return (preset.memfsContent ?? preset.content).trim();
8566
8763
  }
8567
8764
  return preset.content.trim();
@@ -8580,6 +8777,7 @@ var init_prompt_assets = __esm(() => {
8580
8777
  init_human_tutorial();
8581
8778
  init_interrupt_recovery_alert();
8582
8779
  init_letta();
8780
+ init_letta_local_memfs();
8583
8781
  init_letta_no_memfs();
8584
8782
  init_memory_filesystem();
8585
8783
  init_onboarding();
@@ -8627,6 +8825,7 @@ var init_prompt_assets = __esm(() => {
8627
8825
  description: "Alias for letta",
8628
8826
  content: letta_no_memfs_default,
8629
8827
  memfsContent: letta_default,
8828
+ localMemfsContent: letta_local_memfs_default,
8630
8829
  isDefault: true,
8631
8830
  isFeatured: true
8632
8831
  },
@@ -8636,6 +8835,7 @@ var init_prompt_assets = __esm(() => {
8636
8835
  description: "Full Letta Code system prompt",
8637
8836
  content: letta_no_memfs_default,
8638
8837
  memfsContent: letta_default,
8838
+ localMemfsContent: letta_local_memfs_default,
8639
8839
  isFeatured: true
8640
8840
  },
8641
8841
  {
@@ -155347,7 +155547,8 @@ async function runSlackAttachmentDownloadTask(params) {
155347
155547
  startTime: new Date,
155348
155548
  outputFile,
155349
155549
  totalStdoutLines: 0,
155350
- totalStderrLines: 0
155550
+ totalStderrLines: 0,
155551
+ runtimeScope: params.runtimeScope
155351
155552
  };
155352
155553
  backgroundProcesses.set(taskId, processState);
155353
155554
  appendToOutputFile(outputFile, `${params.description}
@@ -155781,6 +155982,10 @@ async function downloadSlackFile(ctx) {
155781
155982
  }
155782
155983
  const result = await runSlackAttachmentDownloadTask({
155783
155984
  description: `Slack attachment download ${attachmentId} from message ${messageId} in ${request.chatId}`,
155985
+ runtimeScope: {
155986
+ agentId: ctx.route.agentId,
155987
+ conversationId: ctx.route.conversationId
155988
+ },
155784
155989
  download: (signal) => downloadAttachment.call(ctx.adapter, {
155785
155990
  attachmentId,
155786
155991
  chatId: request.chatId,
@@ -170597,11 +170802,6 @@ function evictConversationRuntimeIfIdle(runtime) {
170597
170802
  }
170598
170803
  runtime.listener.conversationRuntimes.delete(runtime.key);
170599
170804
  scheduleWorktreeWatcherIdleStop(runtime.listener, runtime);
170600
- for (const [requestId, runtimeKey] of runtime.listener.approvalRuntimeKeyByRequestId) {
170601
- if (runtimeKey === runtime.key) {
170602
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
170603
- }
170604
- }
170605
170805
  if (runtime.listener.pendingQueueEmitScope?.agent_id === runtime.agentId && normalizeConversationId(runtime.listener.pendingQueueEmitScope?.conversation_id) === runtime.conversationId) {
170606
170806
  runtime.listener.pendingQueueEmitScope = undefined;
170607
170807
  }
@@ -170648,6 +170848,7 @@ function createConversationRuntime(listener, agentId, conversationId) {
170648
170848
  conversationId: normalizedConversationId,
170649
170849
  skillSources: listener.skillSourcesByConversation.get(runtimeKey)?.slice(),
170650
170850
  activeChannelTurn: null,
170851
+ activeConnectionId: null,
170651
170852
  turnLifecycle,
170652
170853
  messageQueue: Promise.resolve(),
170653
170854
  pendingApprovalResolvers: new Map,
@@ -170759,7 +170960,7 @@ function getPendingControlRequests(runtime, params) {
170759
170960
  if (!conversationRuntime) {
170760
170961
  return requests;
170761
170962
  }
170762
- for (const pending of conversationRuntime.pendingApprovalResolvers.values()) {
170963
+ for (const pending of new Set(conversationRuntime.pendingApprovalResolvers.values())) {
170763
170964
  const request = pending.controlRequest;
170764
170965
  if (!request)
170765
170966
  continue;
@@ -172463,7 +172664,8 @@ async function bash(args) {
172463
172664
  run_in_background = false,
172464
172665
  signal,
172465
172666
  onOutput,
172466
- secretEnv
172667
+ secretEnv,
172668
+ parentScope
172467
172669
  } = args;
172468
172670
  const userCwd = getCurrentWorkingDirectory();
172469
172671
  if (command === "/bg") {
@@ -172533,7 +172735,8 @@ async function bash(args) {
172533
172735
  startTime: new Date,
172534
172736
  outputFile,
172535
172737
  totalStdoutLines: 0,
172536
- totalStderrLines: 0
172738
+ totalStderrLines: 0,
172739
+ runtimeScope: parentScope
172537
172740
  });
172538
172741
  const bgProcess = backgroundProcesses.get(bashId);
172539
172742
  if (!bgProcess) {
@@ -173164,6 +173367,268 @@ var init_worktree_lock = __esm(() => {
173164
173367
  HOSTNAME = os3.hostname();
173165
173368
  });
173166
173369
 
173370
+ // src/websocket/listener/transport.ts
173371
+ import WebSocket3 from "ws";
173372
+
173373
+ class LocalListenerTransport {
173374
+ kind = "local";
173375
+ bufferedAmount = 0;
173376
+ isOpen() {
173377
+ return true;
173378
+ }
173379
+ send(_data) {}
173380
+ }
173381
+ function isListenerTransportOpen(transport) {
173382
+ if ("isOpen" in transport && typeof transport.isOpen === "function") {
173383
+ return transport.isOpen();
173384
+ }
173385
+ return transport.readyState === WebSocket3.OPEN;
173386
+ }
173387
+ function getListenerTransportKind(transport) {
173388
+ return "kind" in transport ? transport.kind : "websocket";
173389
+ }
173390
+ var init_transport = () => {};
173391
+
173392
+ // src/websocket/listener/connection.ts
173393
+ function toListenerConnection(connectionId) {
173394
+ return { type: "ToConnection", connectionId };
173395
+ }
173396
+ function getResumeStates(runtime) {
173397
+ let states2 = resumeStateByRuntime.get(runtime);
173398
+ if (!states2) {
173399
+ states2 = new Map;
173400
+ resumeStateByRuntime.set(runtime, states2);
173401
+ }
173402
+ return states2;
173403
+ }
173404
+ function socketForTransport(transport) {
173405
+ return "kind" in transport ? null : transport;
173406
+ }
173407
+ function refreshLegacySingleConnection(runtime) {
173408
+ const live = [...runtime.connections.values()].filter((connection) => isListenerTransportOpen(connection.writer));
173409
+ const only = live.length === 1 ? live[0] : null;
173410
+ runtime.transport = only?.writer ?? runtime.processTransport;
173411
+ runtime.streamTransport = only?.streamWriter ?? null;
173412
+ runtime.socket = only ? socketForTransport(only.writer) : null;
173413
+ runtime.streamSocket = only?.streamWriter ? socketForTransport(only.streamWriter) : null;
173414
+ }
173415
+ function createConnectionRequestKey(connectionId, requestId) {
173416
+ return JSON.stringify([connectionId, requestId]);
173417
+ }
173418
+ function openListenerConnection(params) {
173419
+ const existing = params.runtime.connections.get(params.connectionId);
173420
+ if (existing) {
173421
+ throw new Error(`Listener connection already open: ${params.connectionId}`);
173422
+ }
173423
+ const resumeStates = getResumeStates(params.runtime);
173424
+ const resumed = resumeStates.get(params.connectionId);
173425
+ resumeStates.delete(params.connectionId);
173426
+ const connection = {
173427
+ id: params.connectionId,
173428
+ ordinal: resumed?.ordinal ?? params.runtime.nextConnectionOrdinal,
173429
+ writer: params.writer,
173430
+ streamWriter: params.streamWriter ?? null,
173431
+ cancellation: params.cancellation ?? new AbortController,
173432
+ initialized: false,
173433
+ subscriptions: resumed?.subscriptions ?? new Set,
173434
+ eventSeqCounter: resumed?.eventSeqCounter ?? 0,
173435
+ options: params.options
173436
+ };
173437
+ if (!resumed) {
173438
+ params.runtime.nextConnectionOrdinal += 1;
173439
+ }
173440
+ params.runtime.connections.set(connection.id, connection);
173441
+ for (const runtimeKey of connection.subscriptions) {
173442
+ let connectionIds = params.runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173443
+ if (!connectionIds) {
173444
+ connectionIds = new Set;
173445
+ params.runtime.connectionIdsByRuntimeKey.set(runtimeKey, connectionIds);
173446
+ }
173447
+ connectionIds.add(connection.id);
173448
+ }
173449
+ refreshLegacySingleConnection(params.runtime);
173450
+ return connection;
173451
+ }
173452
+ function markListenerConnectionInitialized(runtime, connectionId) {
173453
+ const connection = runtime.connections.get(connectionId);
173454
+ if (connection) {
173455
+ connection.initialized = true;
173456
+ }
173457
+ }
173458
+ function subscribeListenerConnection(runtime, connectionId, scope) {
173459
+ const connection = runtime.connections.get(connectionId);
173460
+ if (!connection || typeof scope.agent_id !== "string") {
173461
+ return false;
173462
+ }
173463
+ const runtimeKey = getConversationRuntimeKey(scope.agent_id, scope.conversation_id);
173464
+ connection.subscriptions.add(runtimeKey);
173465
+ let connectionIds = runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173466
+ if (!connectionIds) {
173467
+ connectionIds = new Set;
173468
+ runtime.connectionIdsByRuntimeKey.set(runtimeKey, connectionIds);
173469
+ }
173470
+ connectionIds.add(connectionId);
173471
+ return true;
173472
+ }
173473
+ function unsubscribeListenerConnection(runtime, connectionId, runtimeKey) {
173474
+ const connection = runtime.connections.get(connectionId);
173475
+ if (!connection?.subscriptions.delete(runtimeKey)) {
173476
+ return false;
173477
+ }
173478
+ const connectionIds = runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173479
+ connectionIds?.delete(connectionId);
173480
+ if (connectionIds?.size === 0) {
173481
+ runtime.connectionIdsByRuntimeKey.delete(runtimeKey);
173482
+ }
173483
+ return true;
173484
+ }
173485
+ function getSubscribedListenerConnections(runtime, scope) {
173486
+ if (typeof scope.agent_id !== "string") {
173487
+ return [];
173488
+ }
173489
+ const runtimeKey = getConversationRuntimeKey(scope.agent_id, scope.conversation_id);
173490
+ const connectionIds = runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173491
+ if (!connectionIds) {
173492
+ return [];
173493
+ }
173494
+ return [...connectionIds].map((connectionId) => runtime.connections.get(connectionId)).filter((connection) => connection?.initialized === true && isListenerTransportOpen(connection.writer)).sort((a, b) => a.ordinal - b.ordinal);
173495
+ }
173496
+ function findListenerConnectionByTransport(runtime, transport) {
173497
+ for (const connection of runtime.connections.values()) {
173498
+ if (connection.writer === transport || connection.streamWriter === transport) {
173499
+ return connection;
173500
+ }
173501
+ }
173502
+ return null;
173503
+ }
173504
+ function nextListenerConnectionEventSeq(connection, runtime) {
173505
+ if (!connection) {
173506
+ return nextEventSeq(runtime);
173507
+ }
173508
+ connection.eventSeqCounter += 1;
173509
+ return connection.eventSeqCounter;
173510
+ }
173511
+ function resolveListenerConnectionTargets(params) {
173512
+ const runtime = params.runtime;
173513
+ let connections;
173514
+ switch (params.routing.type) {
173515
+ case "ToConnection": {
173516
+ const explicitConnection = runtime?.connections.get(params.routing.connectionId);
173517
+ if (explicitConnection) {
173518
+ connections = [explicitConnection];
173519
+ break;
173520
+ }
173521
+ if ((runtime?.connections.size ?? 0) === 0 && params.routing.connectionId === (runtime?.connectionId ?? "legacy")) {
173522
+ connections = [null];
173523
+ break;
173524
+ }
173525
+ return [];
173526
+ }
173527
+ case "ToSubscribers": {
173528
+ connections = runtime ? getSubscribedListenerConnections(runtime, params.scope) : [];
173529
+ if (connections.length > 0) {
173530
+ break;
173531
+ }
173532
+ if ((runtime?.connections.size ?? 0) === 0) {
173533
+ connections = [null];
173534
+ break;
173535
+ }
173536
+ return [];
173537
+ }
173538
+ case "Broadcast": {
173539
+ connections = runtime ? [...runtime.connections.values()].filter((connection) => connection.initialized && isListenerTransportOpen(connection.writer)) : [];
173540
+ if (connections.length === 0 && (runtime?.connections.size ?? 0) === 0) {
173541
+ connections = [null];
173542
+ }
173543
+ break;
173544
+ }
173545
+ }
173546
+ return connections.map((connection) => {
173547
+ const streamTransport = connection?.streamWriter;
173548
+ if (params.streamMessage && streamTransport && isListenerTransportOpen(streamTransport)) {
173549
+ return { connection, transport: streamTransport };
173550
+ }
173551
+ const legacyStreamTransport = params.runtime?.streamTransport;
173552
+ if (!connection && params.streamMessage && legacyStreamTransport && isListenerTransportOpen(legacyStreamTransport)) {
173553
+ return { connection, transport: legacyStreamTransport };
173554
+ }
173555
+ return { connection, transport: connection?.writer ?? params.origin };
173556
+ });
173557
+ }
173558
+ function closeListenerConnection(runtime, connectionId) {
173559
+ getResumeStates(runtime).delete(connectionId);
173560
+ const connection = runtime.connections.get(connectionId);
173561
+ if (!connection) {
173562
+ return null;
173563
+ }
173564
+ for (const runtimeKey of [...connection.subscriptions]) {
173565
+ unsubscribeListenerConnection(runtime, connectionId, runtimeKey);
173566
+ }
173567
+ runtime.connections.delete(connectionId);
173568
+ connection.cancellation.abort();
173569
+ refreshLegacySingleConnection(runtime);
173570
+ return connection;
173571
+ }
173572
+ function suspendListenerConnection(runtime, connectionId) {
173573
+ const connection = runtime.connections.get(connectionId);
173574
+ if (!connection) {
173575
+ return null;
173576
+ }
173577
+ const resumeState = {
173578
+ ordinal: connection.ordinal,
173579
+ subscriptions: new Set(connection.subscriptions),
173580
+ eventSeqCounter: connection.eventSeqCounter
173581
+ };
173582
+ const closed = closeListenerConnection(runtime, connectionId);
173583
+ getResumeStates(runtime).set(connectionId, resumeState);
173584
+ return closed;
173585
+ }
173586
+
173587
+ class ProcessRuntimeTransport {
173588
+ runtime;
173589
+ kind = "runtime";
173590
+ constructor(runtime) {
173591
+ this.runtime = runtime;
173592
+ }
173593
+ get bufferedAmount() {
173594
+ let total = 0;
173595
+ for (const connection of this.runtime.connections.values()) {
173596
+ total += connection.writer.bufferedAmount;
173597
+ }
173598
+ return total;
173599
+ }
173600
+ isOpen() {
173601
+ for (const connection of this.runtime.connections.values()) {
173602
+ if (connection.initialized && isListenerTransportOpen(connection.writer)) {
173603
+ return true;
173604
+ }
173605
+ }
173606
+ return false;
173607
+ }
173608
+ send(data) {
173609
+ throw new Error(`Process runtime transport cannot send an implicit message (${data.length} bytes); resolve ToConnection, ToSubscribers, or Broadcast first`);
173610
+ }
173611
+ }
173612
+ function getOrCreateProcessTransport(runtime) {
173613
+ runtime.processTransport ??= new ProcessRuntimeTransport(runtime);
173614
+ if (runtime.connections.size !== 1) {
173615
+ refreshLegacySingleConnection(runtime);
173616
+ }
173617
+ return runtime.processTransport;
173618
+ }
173619
+ var TO_SUBSCRIBERS, BROADCAST, resumeStateByRuntime;
173620
+ var init_connection = __esm(() => {
173621
+ init_runtime6();
173622
+ init_transport();
173623
+ TO_SUBSCRIBERS = {
173624
+ type: "ToSubscribers"
173625
+ };
173626
+ BROADCAST = {
173627
+ type: "Broadcast"
173628
+ };
173629
+ resumeStateByRuntime = new WeakMap;
173630
+ });
173631
+
173167
173632
  // src/cli/helpers/memory-reminder.ts
173168
173633
  function isValidStepCount(value) {
173169
173634
  return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
@@ -173434,6 +173899,43 @@ var init_system_prompt_warning = __esm(() => {
173434
173899
  systemPromptDoctorStateByAgent = new Map;
173435
173900
  });
173436
173901
 
173902
+ // src/websocket/listener/background-process-snapshot.ts
173903
+ function belongsToRuntime(entry, runtimeScope) {
173904
+ return entry.runtimeScope?.agentId === runtimeScope.agentId && entry.runtimeScope.conversationId === runtimeScope.conversationId;
173905
+ }
173906
+ function buildBackgroundProcessSnapshot(agentId, conversationId = "default") {
173907
+ if (agentId === null) {
173908
+ return [];
173909
+ }
173910
+ const runtimeScope = agentId === undefined ? undefined : { agentId, conversationId };
173911
+ const bashProcesses = Array.from(backgroundProcesses.entries()).filter(([, proc]) => proc.status === "running" && (!runtimeScope || belongsToRuntime(proc, runtimeScope))).map(([processId, proc]) => ({
173912
+ process_id: processId,
173913
+ kind: "bash",
173914
+ command: proc.command,
173915
+ started_at_ms: proc.startTime?.getTime() ?? null,
173916
+ status: proc.status,
173917
+ exit_code: proc.exitCode
173918
+ }));
173919
+ const taskProcesses = Array.from(backgroundTasks.entries()).filter(([, task]) => task.status === "running" && (!runtimeScope || belongsToRuntime(task, runtimeScope))).map(([processId, task]) => ({
173920
+ process_id: processId,
173921
+ kind: "agent_task",
173922
+ task_type: task.subagentType,
173923
+ description: task.description,
173924
+ started_at_ms: task.startTime.getTime(),
173925
+ status: task.status,
173926
+ subagent_id: task.subagentId,
173927
+ ...task.error ? { error: task.error } : {}
173928
+ }));
173929
+ return [...bashProcesses, ...taskProcesses].sort((a, b) => {
173930
+ const aStart = a.started_at_ms ?? 0;
173931
+ const bStart = b.started_at_ms ?? 0;
173932
+ return bStart - aStart;
173933
+ });
173934
+ }
173935
+ var init_background_process_snapshot = __esm(() => {
173936
+ init_process_manager();
173937
+ });
173938
+
173437
173939
  // src/websocket/listener/channel-turn-session.ts
173438
173940
  function getChannelTurnSourceKey(source2) {
173439
173941
  return [
@@ -346947,28 +347449,6 @@ var init_mod_commands = __esm(async () => {
346947
347449
  ]);
346948
347450
  });
346949
347451
 
346950
- // src/websocket/listener/transport.ts
346951
- import WebSocket3 from "ws";
346952
-
346953
- class LocalListenerTransport {
346954
- kind = "local";
346955
- bufferedAmount = 0;
346956
- isOpen() {
346957
- return true;
346958
- }
346959
- send(_data) {}
346960
- }
346961
- function isListenerTransportOpen(transport) {
346962
- if ("isOpen" in transport && typeof transport.isOpen === "function") {
346963
- return transport.isOpen();
346964
- }
346965
- return transport.readyState === WebSocket3.OPEN;
346966
- }
346967
- function getListenerTransportKind(transport) {
346968
- return "kind" in transport ? transport.kind : "websocket";
346969
- }
346970
- var init_transport = () => {};
346971
-
346972
347452
  // src/websocket/listener/outbound-wire.ts
346973
347453
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync24 } from "node:fs";
346974
347454
  import { dirname as dirname14 } from "node:path";
@@ -347302,31 +347782,6 @@ function getScopeForRuntime(runtime, scope) {
347302
347782
  }
347303
347783
  return scope ?? {};
347304
347784
  }
347305
- function buildBackgroundProcessSnapshot() {
347306
- const bashProcesses = Array.from(backgroundProcesses.entries()).filter(([, proc]) => proc.status === "running").map(([processId, proc]) => ({
347307
- process_id: processId,
347308
- kind: "bash",
347309
- command: proc.command,
347310
- started_at_ms: proc.startTime?.getTime() ?? null,
347311
- status: proc.status,
347312
- exit_code: proc.exitCode
347313
- }));
347314
- const taskProcesses = Array.from(backgroundTasks.entries()).filter(([, task]) => task.status === "running").map(([processId, task]) => ({
347315
- process_id: processId,
347316
- kind: "agent_task",
347317
- task_type: task.subagentType,
347318
- description: task.description,
347319
- started_at_ms: task.startTime.getTime(),
347320
- status: task.status,
347321
- subagent_id: task.subagentId,
347322
- ...task.error ? { error: task.error } : {}
347323
- }));
347324
- return [...bashProcesses, ...taskProcesses].sort((a, b) => {
347325
- const aStart = a.started_at_ms ?? 0;
347326
- const bStart = b.started_at_ms ?? 0;
347327
- return bStart - aStart;
347328
- });
347329
- }
347330
347785
  function emitRuntimeStateUpdates(runtime, scope) {
347331
347786
  emitLoopStatusIfOpen(runtime, scope);
347332
347787
  emitDeviceStatusIfOpen(runtime, scope);
@@ -347401,7 +347856,7 @@ function buildDeviceStatus(runtime, params) {
347401
347856
  current_toolset_preference: conversationRuntime?.currentToolsetPreference ?? toolsetPreference,
347402
347857
  current_loaded_tools: conversationRuntime?.currentLoadedTools ?? [],
347403
347858
  current_available_skills: [],
347404
- background_processes: buildBackgroundProcessSnapshot(),
347859
+ background_processes: buildBackgroundProcessSnapshot(scopedAgentId, scopedConversationId),
347405
347860
  pending_control_requests: interruptedCacheActive ? [] : getPendingControlRequests(listener, scope),
347406
347861
  experiments: experimentManager.list(),
347407
347862
  memory_directory: scopedAgentId ? getScopedMemoryFilesystemRoot(scopedAgentId) : null,
@@ -347464,87 +347919,90 @@ function isStreamChannelMessage(type3) {
347464
347919
  function classifyOutboundFrame(message) {
347465
347920
  return COALESCABLE_STATUS_MESSAGE_TYPES.has(message.type) ? "status" : "critical";
347466
347921
  }
347467
- function emitProtocolV2Message(socket, runtime, message, scope) {
347922
+ function emitProtocolV2Message(socket, runtime, message, scope, routing) {
347468
347923
  const listener = getListenerRuntime(runtime);
347469
- let targetSocket = socket;
347470
- if (listener?.streamTransport && isStreamChannelMessage(message.type)) {
347471
- if (isListenerTransportOpen(listener.streamTransport)) {
347472
- targetSocket = listener.streamTransport;
347473
- }
347474
- }
347475
347924
  const runtimeScope = resolveRuntimeScope(listener, getScopeForRuntime(runtime, scope));
347476
347925
  if (!runtimeScope)
347477
347926
  return;
347478
347927
  notifyStreamObservers(listener, message, runtimeScope);
347479
- if (!isListenerTransportOpen(targetSocket))
347480
- return;
347481
347928
  const frameClass = classifyOutboundFrame(message);
347482
- enqueueOutboundFrame(targetSocket, {
347483
- typeLabel: message.type,
347484
- frameClass,
347485
- ...frameClass === "status" ? {
347486
- coalesceKey: `${message.type}:${runtimeScope.agent_id ?? ""}:${runtimeScope.conversation_id ?? ""}`
347487
- } : {},
347488
- build: () => {
347489
- const eventSeq = nextEventSeq(listener);
347490
- if (eventSeq === null)
347491
- return null;
347492
- const outbound = {
347493
- ...message,
347494
- runtime: runtimeScope,
347495
- event_seq: eventSeq,
347496
- emitted_at: new Date().toISOString(),
347497
- idempotency_key: `${message.type}:${eventSeq}:${crypto.randomUUID()}`
347498
- };
347499
- let payload;
347500
- try {
347501
- payload = JSON.stringify(outbound);
347502
- } catch (error54) {
347503
- console.error(`[Listen V2] Failed to emit ${message.type} (seq=${eventSeq})`, error54);
347929
+ const targets = resolveListenerConnectionTargets({
347930
+ runtime: listener,
347931
+ origin: socket,
347932
+ scope: runtimeScope,
347933
+ routing,
347934
+ streamMessage: isStreamChannelMessage(message.type)
347935
+ });
347936
+ for (const { connection, transport: targetSocket } of targets) {
347937
+ if (!isListenerTransportOpen(targetSocket))
347938
+ continue;
347939
+ enqueueOutboundFrame(targetSocket, {
347940
+ typeLabel: message.type,
347941
+ frameClass,
347942
+ ...frameClass === "status" ? {
347943
+ coalesceKey: `${message.type}:${runtimeScope.agent_id ?? ""}:${runtimeScope.conversation_id ?? ""}`
347944
+ } : {},
347945
+ build: () => {
347946
+ const eventSeq = nextListenerConnectionEventSeq(connection, listener);
347947
+ if (eventSeq === null)
347948
+ return null;
347949
+ const outbound = {
347950
+ ...message,
347951
+ runtime: runtimeScope,
347952
+ event_seq: eventSeq,
347953
+ emitted_at: new Date().toISOString(),
347954
+ idempotency_key: `${message.type}:${eventSeq}:${crypto.randomUUID()}`
347955
+ };
347956
+ let payload;
347957
+ try {
347958
+ payload = JSON.stringify(outbound);
347959
+ } catch (error54) {
347960
+ console.error(`[Listen V2] Failed to emit ${message.type} (seq=${eventSeq})`, error54);
347961
+ safeEmitWsEvent("send", "lifecycle", {
347962
+ type: "_ws_send_error",
347963
+ message_type: message.type,
347964
+ event_seq: eventSeq,
347965
+ error: error54 instanceof Error ? error54.message : String(error54)
347966
+ });
347967
+ return null;
347968
+ }
347969
+ return {
347970
+ payload,
347971
+ perfKey: getProtocolPerfKey(message),
347972
+ onSent: () => {
347973
+ if (isDebugEnabled()) {
347974
+ console.log(`[Listen V2] Emitting ${message.type} (seq=${eventSeq})`);
347975
+ }
347976
+ safeEmitWsEvent("send", "protocol", outbound);
347977
+ }
347978
+ };
347979
+ },
347980
+ onSendError: (error54) => {
347981
+ console.error(`[Listen V2] Failed to emit ${message.type}`, error54);
347504
347982
  safeEmitWsEvent("send", "lifecycle", {
347505
347983
  type: "_ws_send_error",
347506
347984
  message_type: message.type,
347507
- event_seq: eventSeq,
347508
347985
  error: error54 instanceof Error ? error54.message : String(error54)
347509
347986
  });
347510
- return null;
347511
347987
  }
347512
- return {
347513
- payload,
347514
- perfKey: getProtocolPerfKey(message),
347515
- onSent: () => {
347516
- if (isDebugEnabled()) {
347517
- console.log(`[Listen V2] Emitting ${message.type} (seq=${eventSeq})`);
347518
- }
347519
- safeEmitWsEvent("send", "protocol", outbound);
347520
- }
347521
- };
347522
- },
347523
- onSendError: (error54) => {
347524
- console.error(`[Listen V2] Failed to emit ${message.type}`, error54);
347525
- safeEmitWsEvent("send", "lifecycle", {
347526
- type: "_ws_send_error",
347527
- message_type: message.type,
347528
- error: error54 instanceof Error ? error54.message : String(error54)
347529
- });
347530
- }
347531
- });
347988
+ });
347989
+ }
347532
347990
  }
347533
- function emitDeviceStatusUpdate(socket, runtime, scope) {
347991
+ function emitDeviceStatusUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347534
347992
  const deviceStatus = buildDeviceStatus(runtime, scope);
347535
347993
  recordDeviceStatus(socket, getScopeForRuntime(runtime, scope), deviceStatus);
347536
347994
  const message = {
347537
347995
  type: "update_device_status",
347538
347996
  device_status: deviceStatus
347539
347997
  };
347540
- emitProtocolV2Message(socket, runtime, message, scope);
347998
+ emitProtocolV2Message(socket, runtime, message, scope, routing);
347541
347999
  }
347542
- function emitLoopStatusUpdate(socket, runtime, scope) {
348000
+ function emitLoopStatusUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347543
348001
  const message = {
347544
348002
  type: "update_loop_status",
347545
348003
  loop_status: buildLoopStatus(runtime, scope)
347546
348004
  };
347547
- emitProtocolV2Message(socket, runtime, message, scope);
348005
+ emitProtocolV2Message(socket, runtime, message, scope, routing);
347548
348006
  }
347549
348007
  function emitLoopStatusIfOpen(runtime, scope) {
347550
348008
  const listener = getListenerRuntime(runtime);
@@ -347560,7 +348018,7 @@ function emitDeviceStatusIfOpen(runtime, scope) {
347560
348018
  emitDeviceStatusUpdate(transport, runtime, scope);
347561
348019
  }
347562
348020
  }
347563
- function emitQueueUpdate(socket, runtime, scope) {
348021
+ function emitQueueUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347564
348022
  const listener = getListenerRuntime(runtime);
347565
348023
  if (!listener) {
347566
348024
  return;
@@ -347570,7 +348028,7 @@ function emitQueueUpdate(socket, runtime, scope) {
347570
348028
  type: "update_queue",
347571
348029
  queue: buildQueueSnapshot(runtime, resolvedScope)
347572
348030
  };
347573
- emitProtocolV2Message(socket, runtime, message, resolvedScope);
348031
+ emitProtocolV2Message(socket, runtime, message, resolvedScope, routing);
347574
348032
  }
347575
348033
  function isTextContentPart(part) {
347576
348034
  return !!part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string";
@@ -347670,7 +348128,7 @@ function emitQueueUpdateIfOpen(runtime, scope) {
347670
348128
  emitQueueUpdate(transport, runtime, scope);
347671
348129
  }
347672
348130
  }
347673
- function emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3) {
348131
+ function emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3, routing = TO_SUBSCRIBERS) {
347674
348132
  const resolvedScope = getScopeForRuntime(runtime, scope);
347675
348133
  const deviceStatus = buildDeviceStatus(runtime, resolvedScope);
347676
348134
  if (!shouldEmitDeviceStatus(socket, resolvedScope, deviceStatus, options3?.force)) {
@@ -347680,14 +348138,15 @@ function emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3) {
347680
348138
  type: "update_device_status",
347681
348139
  device_status: deviceStatus
347682
348140
  };
347683
- emitProtocolV2Message(socket, runtime, message, resolvedScope);
348141
+ emitProtocolV2Message(socket, runtime, message, resolvedScope, routing);
347684
348142
  return true;
347685
348143
  }
347686
348144
  function emitStateSync(socket, runtime, scope, options3) {
347687
- emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3?.forceDeviceStatus ? { force: true } : undefined);
347688
- emitLoopStatusUpdate(socket, runtime, scope);
347689
- emitQueueUpdate(socket, runtime, scope);
347690
- emitSubagentStateUpdate(socket, runtime, scope);
348145
+ const routing = options3?.routing ?? TO_SUBSCRIBERS;
348146
+ emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3?.forceDeviceStatus ? { force: true } : undefined, routing);
348147
+ emitLoopStatusUpdate(socket, runtime, scope, routing);
348148
+ emitQueueUpdate(socket, runtime, scope, routing);
348149
+ emitSubagentStateUpdate(socket, runtime, scope, routing);
347691
348150
  }
347692
348151
  function resolveSubagentScopeForSnapshot(runtime, scope) {
347693
348152
  const listener = getListenerRuntime(runtime);
@@ -347728,12 +348187,12 @@ function buildSubagentSnapshot(runtime, scope) {
347728
348187
  error: a.error
347729
348188
  }));
347730
348189
  }
347731
- function emitSubagentStateUpdate(socket, runtime, scope) {
348190
+ function emitSubagentStateUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347732
348191
  const message = {
347733
348192
  type: "update_subagent_state",
347734
348193
  subagents: buildSubagentSnapshot(runtime, scope)
347735
348194
  };
347736
- emitProtocolV2Message(socket, runtime, message, scope);
348195
+ emitProtocolV2Message(socket, runtime, message, scope, routing);
347737
348196
  }
347738
348197
  function emitSubagentStateIfOpen(runtime, scope) {
347739
348198
  const listener = getListenerRuntime(runtime);
@@ -347842,7 +348301,7 @@ function emitStreamDelta(socket, runtime, delta2, scope, subagentId) {
347842
348301
  delta: delta2,
347843
348302
  ...subagentId ? { subagent_id: subagentId } : {}
347844
348303
  };
347845
- emitProtocolV2Message(socket, runtime, message, scope);
348304
+ emitProtocolV2Message(socket, runtime, message, scope, TO_SUBSCRIBERS);
347846
348305
  }
347847
348306
  var GIT_CONTEXT_CACHE_TTL_MS = 15000, MAX_GIT_CONTEXT_CACHE_ENTRIES = 64, FROZEN_SUPPORTED_COMMANDS, gitContextCache, STREAM_CHANNEL_MESSAGE_TYPES, COALESCABLE_STATUS_MESSAGE_TYPES;
347848
348307
  var init_protocol_outbound = __esm(async () => {
@@ -347856,9 +348315,10 @@ var init_protocol_outbound = __esm(async () => {
347856
348315
  init_manager();
347857
348316
  init_mode();
347858
348317
  init_settings_manager();
347859
- init_process_manager();
347860
348318
  init_debug();
348319
+ init_background_process_snapshot();
347861
348320
  init_channel_turn_session();
348321
+ init_connection();
347862
348322
  init_constants3();
347863
348323
  init_cwd();
347864
348324
  init_device_status_cache();
@@ -347921,9 +348381,9 @@ async function switchConversationWorkingDirectory(params) {
347921
348381
  reminderState.hasSentSessionContext = false;
347922
348382
  reminderState.pendingSessionContextReason = "cwd_changed";
347923
348383
  }
347924
- const statusSocket = params.statusSocket ?? runtime.socket;
347925
- if (params.emitStatus !== false && statusSocket) {
347926
- emitDeviceStatusUpdate(statusSocket, params.statusRuntime ?? conversationRuntime ?? runtime, {
348384
+ if (params.emitStatus !== false) {
348385
+ const statusTransport = params.statusSocket ?? getOrCreateProcessTransport(runtime);
348386
+ emitDeviceStatusUpdate(statusTransport, params.statusRuntime ?? conversationRuntime ?? runtime, {
347927
348387
  agent_id: agentId,
347928
348388
  conversation_id: conversationId
347929
348389
  });
@@ -347932,6 +348392,7 @@ async function switchConversationWorkingDirectory(params) {
347932
348392
  var init_cwd_change = __esm(async () => {
347933
348393
  init_runtime_context();
347934
348394
  init_settings_manager();
348395
+ init_connection();
347935
348396
  init_cwd();
347936
348397
  init_runtime6();
347937
348398
  await init_protocol_outbound();
@@ -349415,7 +349876,8 @@ async function startExecSession(args) {
349415
349876
  startTime: new Date,
349416
349877
  outputFile,
349417
349878
  totalStdoutLines: 0,
349418
- totalStderrLines: 0
349879
+ totalStderrLines: 0,
349880
+ runtimeScope: args.parentScope
349419
349881
  });
349420
349882
  if (session.status !== "running") {
349421
349883
  scheduleBackgroundProcessCleanup(id2);
@@ -363635,7 +364097,8 @@ function spawnBackgroundSubagentTask(args) {
363635
364097
  output: [],
363636
364098
  startTime: new Date,
363637
364099
  outputFile,
363638
- abortController
364100
+ abortController,
364101
+ runtimeScope: resolvedParentScope
363639
364102
  };
363640
364103
  backgroundTasks.set(taskId, bgTask);
363641
364104
  writeTaskTranscriptStart(outputFile, description, subagentType);
@@ -369549,10 +370012,8 @@ function filterToolRegistryByClientAllowlist(registry2, clientToolAllowlist) {
369549
370012
  }
369550
370013
  function filterExternalToolsByRuntimeContext(externalTools, runtimeContext) {
369551
370014
  return new Map(Array.from(externalTools.entries()).filter(([, tool2]) => {
369552
- if (!tool2.runtime) {
369553
- return true;
369554
- }
369555
- return tool2.runtime.agentId === runtimeContext.agentId && tool2.runtime.conversationId === runtimeContext.conversationId;
370015
+ const matchesConnection = tool2.connectionId === undefined || tool2.connectionId === runtimeContext.connectionId;
370016
+ return matchesConnection && (!tool2.runtime || tool2.runtime.agentId === runtimeContext.agentId && tool2.runtime.conversationId === runtimeContext.conversationId);
369556
370017
  }));
369557
370018
  }
369558
370019
  function filterExternalToolsByScopeIds(externalTools, externalToolScopeIds) {
@@ -370747,6 +371208,9 @@ async function executeToolInner(name, args, options3) {
370747
371208
  if (Object.keys(secretEnv).length > 0) {
370748
371209
  enhancedArgs = { ...enhancedArgs, secretEnv };
370749
371210
  }
371211
+ if (options3?.parentScope) {
371212
+ enhancedArgs = { ...enhancedArgs, parentScope: options3.parentScope };
371213
+ }
370750
371214
  }
370751
371215
  if (internalName === "Task") {
370752
371216
  if (options3?.toolCallId) {
@@ -371460,6 +371924,7 @@ function parseInheritedChannelContextEnv() {
371460
371924
  }
371461
371925
  async function prepareToolExecutionContextForScope(params) {
371462
371926
  const {
371927
+ connectionId,
371463
371928
  agentId,
371464
371929
  conversationId,
371465
371930
  overrideModel,
@@ -371522,6 +371987,7 @@ async function prepareToolExecutionContextForScope(params) {
371522
371987
  modAdapters,
371523
371988
  agent: agent2,
371524
371989
  runtimeContext: {
371990
+ connectionId,
371525
371991
  agentId,
371526
371992
  agentName: agent2.name ?? null,
371527
371993
  conversationId: scopedConversationId,
@@ -435895,6 +436361,7 @@ function consumeQueuedTurn(runtime) {
435895
436361
  let hasTaskNotification = false;
435896
436362
  let hasCronPrompt = false;
435897
436363
  let hasModContinue = false;
436364
+ let batchConnectionId;
435898
436365
  let batchImageFailureMode = null;
435899
436366
  const isNoCoalesce = (candidate) => candidate.kind === "message" && candidate.noCoalesce === true;
435900
436367
  for (const item of queuedItems) {
@@ -435905,6 +436372,11 @@ function consumeQueuedTurn(runtime) {
435905
436372
  break;
435906
436373
  }
435907
436374
  if (item.kind === "message") {
436375
+ const itemConnectionId = runtime.queuedMessagesByItemId.get(item.id)?.connectionId;
436376
+ if (batchConnectionId !== undefined && itemConnectionId !== undefined && itemConnectionId !== batchConnectionId) {
436377
+ break;
436378
+ }
436379
+ batchConnectionId ??= itemConnectionId;
435908
436380
  const itemImageFailureMode = getInboundImageFailureMode(runtime.queuedMessagesByItemId.get(item.id));
435909
436381
  if (batchImageFailureMode !== null && itemImageFailureMode !== batchImageFailureMode) {
435910
436382
  break;
@@ -436087,14 +436559,6 @@ function ensureConversationQueueRuntime(listener, runtime) {
436087
436559
  function getOrCreateScopedRuntime(listener, agentId, conversationId) {
436088
436560
  return ensureConversationQueueRuntime(listener, getOrCreateConversationRuntime(listener, agentId, conversationId));
436089
436561
  }
436090
- function findFallbackRuntime(listener) {
436091
- for (const cr of listener.conversationRuntimes.values()) {
436092
- if (cr.queueRuntime) {
436093
- return cr;
436094
- }
436095
- }
436096
- return null;
436097
- }
436098
436562
  var init_conversation_runtime = __esm(async () => {
436099
436563
  init_queue_runtime();
436100
436564
  init_runtime6();
@@ -436236,21 +436700,20 @@ async function resolveCronFireConversationId(task2) {
436236
436700
  return task2.conversation_id === "default" ? undefined : task2.conversation_id;
436237
436701
  }
436238
436702
  function emitCronsUpdated(socket, task2, conversationId) {
436239
- if (!isListenerTransportOpen(socket)) {
436703
+ const listener = getActiveRuntime();
436704
+ if (!listener)
436240
436705
  return;
436241
- }
436706
+ const runtimeScope = {
436707
+ agent_id: task2.agent_id,
436708
+ conversation_id: conversationId ?? task2.conversation_id ?? "default"
436709
+ };
436242
436710
  const payload = {
436243
436711
  type: "crons_updated",
436244
436712
  timestamp: Date.now(),
436245
436713
  agent_id: task2.agent_id,
436246
- conversation_id: conversationId ?? task2.conversation_id
436714
+ conversation_id: runtimeScope.conversation_id
436247
436715
  };
436248
- try {
436249
- socket.send(JSON.stringify(payload));
436250
- safeEmitWsEvent("send", "protocol", payload);
436251
- } catch (err) {
436252
- console.error(`[Cron] Error sending crons_updated for task ${task2.id}:`, err instanceof Error ? err.message : err);
436253
- }
436716
+ emitProtocolV2Message(socket, listener, payload, runtimeScope, TO_SUBSCRIBERS);
436254
436717
  }
436255
436718
  function refreshTaskCache(state) {
436256
436719
  const mtime = getCronFileMtime();
@@ -436631,13 +437094,14 @@ function stopScheduler() {
436631
437094
  var schedulerState = null, listenerFireContext = null, TICK_INTERVAL_MS = 60000, GC_INTERVAL_MS, LEASE_RETRY_MS = 30000, MAX_LEASE_RETRIES = 3, NEW_CONVERSATION_TARGET = "new";
436632
437095
  var init_scheduler = __esm(async () => {
436633
437096
  init_backend2();
437097
+ init_connection();
436634
437098
  init_runtime6();
436635
- init_transport();
436636
437099
  init_cron_file();
436637
437100
  init_parse_interval();
436638
437101
  init_run_log();
436639
437102
  await __promiseAll([
436640
437103
  init_conversation_runtime(),
437104
+ init_protocol_outbound(),
436641
437105
  init_queue()
436642
437106
  ]);
436643
437107
  GC_INTERVAL_MS = 60 * 60000;
@@ -445600,6 +446064,34 @@ var init_turn_status = __esm(async () => {
445600
446064
  });
445601
446065
 
445602
446066
  // src/websocket/listener/approval.ts
446067
+ function pendingApprovalEntries(runtime) {
446068
+ return [...new Set(runtime.pendingApprovalResolvers.values())];
446069
+ }
446070
+ function removePendingApproval(runtime, pending) {
446071
+ for (const [requestKey, candidate] of runtime.pendingApprovalResolvers) {
446072
+ if (candidate !== pending)
446073
+ continue;
446074
+ runtime.pendingApprovalResolvers.delete(requestKey);
446075
+ }
446076
+ pending.connectionIds.clear();
446077
+ }
446078
+ function addPendingApprovalConnection(runtime, pending, connectionId) {
446079
+ const unownedKey = createConnectionRequestKey(UNOWNED_APPROVAL_CONNECTION_ID, pending.requestId);
446080
+ runtime.pendingApprovalResolvers.delete(unownedKey);
446081
+ const requestKey = createConnectionRequestKey(connectionId, pending.requestId);
446082
+ pending.connectionIds.add(connectionId);
446083
+ runtime.pendingApprovalResolvers.set(requestKey, pending);
446084
+ }
446085
+ function keepPendingApprovalUnowned(runtime, pending) {
446086
+ const requestKey = createConnectionRequestKey(UNOWNED_APPROVAL_CONNECTION_ID, pending.requestId);
446087
+ runtime.pendingApprovalResolvers.set(requestKey, pending);
446088
+ }
446089
+ function hasPendingApprovalRequestId(runtime, requestId) {
446090
+ return pendingApprovalEntries(runtime).some((pending) => pending.requestId === requestId);
446091
+ }
446092
+ function getPendingApprovalRequestIds(runtime) {
446093
+ return new Set(pendingApprovalEntries(runtime).map((pending) => pending.requestId));
446094
+ }
445603
446095
  function rememberPendingApprovalBatchIds(runtime, pendingApprovals, batchId) {
445604
446096
  for (const approval of pendingApprovals) {
445605
446097
  if (approval.toolCallId) {
@@ -445685,17 +446177,20 @@ function validateApprovalResultIds(decisions, approvals) {
445685
446177
  }, null, 2));
445686
446178
  throw new Error("Approval ID mismatch - refusing to send mismatched IDs");
445687
446179
  }
445688
- function resolvePendingApprovalResolver(runtime, response) {
446180
+ function resolvePendingApprovalResolver(runtime, response, connectionId) {
445689
446181
  const requestId = response.request_id;
445690
446182
  if (typeof requestId !== "string" || requestId.length === 0) {
445691
446183
  return false;
445692
446184
  }
445693
- const pending = runtime.pendingApprovalResolvers.get(requestId);
446185
+ const requestKey = connectionId ? createConnectionRequestKey(connectionId, requestId) : [...runtime.pendingApprovalResolvers.entries()].find(([, candidate]) => candidate.requestId === requestId)?.[0];
446186
+ if (!requestKey) {
446187
+ return false;
446188
+ }
446189
+ const pending = runtime.pendingApprovalResolvers.get(requestKey);
445694
446190
  if (!pending) {
445695
446191
  return false;
445696
446192
  }
445697
- runtime.pendingApprovalResolvers.delete(requestId);
445698
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
446193
+ removePendingApproval(runtime, pending);
445699
446194
  if (runtime.pendingApprovalResolvers.size === 0 && !runtime.isProcessing) {
445700
446195
  setCommandLoopStatus(runtime, "WAITING_ON_INPUT");
445701
446196
  }
@@ -445712,15 +446207,10 @@ function resolvePendingApprovalResolver(runtime, response) {
445712
446207
  return true;
445713
446208
  }
445714
446209
  function rejectPendingApprovalResolvers(runtime, reason) {
445715
- for (const [, pending] of runtime.pendingApprovalResolvers) {
446210
+ for (const pending of pendingApprovalEntries(runtime)) {
445716
446211
  pending.reject(new Error(reason));
445717
446212
  }
445718
446213
  runtime.pendingApprovalResolvers.clear();
445719
- for (const [requestId, runtimeKey] of runtime.listener.approvalRuntimeKeyByRequestId) {
445720
- if (runtimeKey === runtime.key) {
445721
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
445722
- }
445723
- }
445724
446214
  if (!runtime.isProcessing && !runtime.cancelRequested) {
445725
446215
  setCommandLoopStatus(runtime, "WAITING_ON_INPUT");
445726
446216
  }
@@ -445734,8 +446224,49 @@ function rejectPendingApprovalResolvers(runtime, reason) {
445734
446224
  });
445735
446225
  evictConversationRuntimeIfIdle(runtime);
445736
446226
  }
446227
+ function rejectPendingApprovalResolversForConnection(runtime, connectionId, _reason) {
446228
+ for (const pending of pendingApprovalEntries(runtime)) {
446229
+ if (!pending.connectionIds.delete(connectionId)) {
446230
+ continue;
446231
+ }
446232
+ const requestKey = createConnectionRequestKey(connectionId, pending.requestId);
446233
+ runtime.pendingApprovalResolvers.delete(requestKey);
446234
+ if (pending.connectionIds.size === 0) {
446235
+ keepPendingApprovalUnowned(runtime, pending);
446236
+ }
446237
+ }
446238
+ evictConversationRuntimeIfIdle(runtime);
446239
+ }
446240
+ function replayPendingApprovalRequestsToConnection(runtime, connectionId) {
446241
+ const connection = runtime.listener.connections.get(connectionId);
446242
+ if (!connection?.initialized || !isListenerTransportOpen(connection.writer)) {
446243
+ return;
446244
+ }
446245
+ for (const pending of pendingApprovalEntries(runtime)) {
446246
+ addPendingApprovalConnection(runtime, pending, connectionId);
446247
+ if (!pending.controlRequest)
446248
+ continue;
446249
+ emitProtocolV2Message(connection.writer, runtime, pending.controlRequest, {
446250
+ agent_id: runtime.agentId,
446251
+ conversation_id: runtime.conversationId
446252
+ }, toListenerConnection(connectionId));
446253
+ }
446254
+ }
445737
446255
  function requestApprovalOverWS(runtime, socket, turnLease, requestId, controlRequest) {
445738
- if (!isListenerTransportOpen(socket)) {
446256
+ const scope = {
446257
+ agent_id: runtime.agentId,
446258
+ conversation_id: runtime.conversationId
446259
+ };
446260
+ const subscribers = getSubscribedListenerConnections(runtime.listener, scope);
446261
+ const originConnection = findListenerConnectionByTransport(runtime.listener, socket);
446262
+ const connectionIds = new Set(subscribers.map((subscriber) => subscriber.id));
446263
+ if (connectionIds.size === 0 && originConnection?.initialized && isListenerTransportOpen(originConnection.writer)) {
446264
+ connectionIds.add(originConnection.id);
446265
+ }
446266
+ if (connectionIds.size === 0 && runtime.listener.connections.size === 0 && isListenerTransportOpen(socket)) {
446267
+ connectionIds.add(runtime.listener.connectionId ?? "legacy");
446268
+ }
446269
+ if (connectionIds.size === 0 && !isListenerTransportOpen(socket) && runtime.listener.connections.size === 0) {
445739
446270
  return Promise.reject(new Error("WebSocket not open"));
445740
446271
  }
445741
446272
  const abortSignal = turnLease.signal;
@@ -445745,51 +446276,53 @@ function requestApprovalOverWS(runtime, socket, turnLease, requestId, controlReq
445745
446276
  }
445746
446277
  return new Promise((resolve31, reject) => {
445747
446278
  let settled = false;
446279
+ const pending = {
446280
+ requestId,
446281
+ connectionIds,
446282
+ resolve: (response) => {
446283
+ if (settled) {
446284
+ return;
446285
+ }
446286
+ settled = true;
446287
+ cleanupAbortListener();
446288
+ resolve31(response);
446289
+ },
446290
+ reject: (error54) => {
446291
+ if (settled) {
446292
+ return;
446293
+ }
446294
+ settled = true;
446295
+ cleanupAbortListener();
446296
+ reject(error54);
446297
+ },
446298
+ controlRequest
446299
+ };
445748
446300
  const cleanupAbortListener = () => {
445749
446301
  abortSignal.removeEventListener("abort", handleAbort);
445750
446302
  };
445751
- const wrappedResolve = (response) => {
445752
- if (settled) {
445753
- return;
445754
- }
445755
- settled = true;
445756
- cleanupAbortListener();
445757
- resolve31(response);
445758
- };
445759
- const wrappedReject = (error54) => {
445760
- if (settled) {
445761
- return;
445762
- }
445763
- settled = true;
445764
- cleanupAbortListener();
445765
- reject(error54);
445766
- };
445767
446303
  const handleAbort = () => {
445768
- runtime.pendingApprovalResolvers.delete(requestId);
445769
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
445770
- wrappedReject(new Error("Cancelled by user"));
446304
+ removePendingApproval(runtime, pending);
446305
+ pending.reject(new Error("Cancelled by user"));
445771
446306
  };
445772
446307
  abortSignal.addEventListener("abort", handleAbort, { once: true });
445773
446308
  if (isInterrupted()) {
445774
446309
  handleAbort();
445775
446310
  return;
445776
446311
  }
445777
- runtime.pendingApprovalResolvers.set(requestId, {
445778
- resolve: wrappedResolve,
445779
- reject: wrappedReject,
445780
- controlRequest
445781
- });
445782
- runtime.listener.approvalRuntimeKeyByRequestId.set(requestId, runtime.key);
446312
+ if (connectionIds.size === 0) {
446313
+ keepPendingApprovalUnowned(runtime, pending);
446314
+ } else {
446315
+ for (const connectionId of connectionIds) {
446316
+ addPendingApprovalConnection(runtime, pending, connectionId);
446317
+ }
446318
+ }
445783
446319
  if (isInterrupted()) {
445784
446320
  handleAbort();
445785
446321
  return;
445786
446322
  }
445787
446323
  runtime.turnLifecycle.recordStopReason(turnLease, "requires_approval");
445788
446324
  setTurnLoopStatus(runtime, turnLease, "WAITING_ON_APPROVAL");
445789
- emitProtocolV2Message(socket, runtime, controlRequest, {
445790
- agent_id: runtime.agentId,
445791
- conversation_id: runtime.conversationId
445792
- });
446325
+ emitProtocolV2Message(socket, runtime, controlRequest, scope, TO_SUBSCRIBERS);
445793
446326
  emitLoopStatusIfOpen(runtime.listener, {
445794
446327
  agent_id: runtime.agentId,
445795
446328
  conversation_id: runtime.conversationId
@@ -445800,7 +446333,9 @@ function requestApprovalOverWS(runtime, socket, turnLease, requestId, controlReq
445800
446333
  });
445801
446334
  });
445802
446335
  }
446336
+ var UNOWNED_APPROVAL_CONNECTION_ID = "__unowned_approval__";
445803
446337
  var init_approval = __esm(async () => {
446338
+ init_connection();
445804
446339
  init_runtime6();
445805
446340
  init_transport();
445806
446341
  await __promiseAll([
@@ -447017,6 +447552,93 @@ var init_agents_conversations = __esm(async () => {
447017
447552
  await init_protocol_inbound();
447018
447553
  });
447019
447554
 
447555
+ // src/websocket/listener/commands/channel-registry-events.ts
447556
+ function broadcastChannelRegistryUpdate(runtime, origin, message) {
447557
+ const payload = JSON.stringify(message);
447558
+ const targets = resolveListenerConnectionTargets({
447559
+ runtime,
447560
+ origin,
447561
+ scope: {},
447562
+ routing: BROADCAST,
447563
+ streamMessage: false
447564
+ });
447565
+ for (const target2 of targets) {
447566
+ if (isListenerTransportOpen(target2.transport)) {
447567
+ try {
447568
+ target2.transport.send(payload);
447569
+ } catch (error54) {
447570
+ trackBoundaryError({
447571
+ context: "listener_channel_registry_broadcast",
447572
+ errorType: "listener_channel_registry_send_failed",
447573
+ error: error54
447574
+ });
447575
+ }
447576
+ }
447577
+ }
447578
+ }
447579
+ function handleChannelRegistryEvent(event2, transport, runtime) {
447580
+ const broadcast = (message) => broadcastChannelRegistryUpdate(runtime, transport, message);
447581
+ if (event2.type === "pairings_updated") {
447582
+ const channelId = event2.channelId;
447583
+ broadcast({
447584
+ type: "channel_pairings_updated",
447585
+ timestamp: Date.now(),
447586
+ channel_id: channelId
447587
+ });
447588
+ broadcast({
447589
+ type: "channels_updated",
447590
+ timestamp: Date.now(),
447591
+ channel_id: channelId
447592
+ });
447593
+ return;
447594
+ }
447595
+ if (event2.type === "targets_updated") {
447596
+ const channelId = event2.channelId;
447597
+ broadcast({
447598
+ type: "channel_targets_updated",
447599
+ timestamp: Date.now(),
447600
+ channel_id: channelId
447601
+ });
447602
+ broadcast({
447603
+ type: "channels_updated",
447604
+ timestamp: Date.now(),
447605
+ channel_id: channelId
447606
+ });
447607
+ return;
447608
+ }
447609
+ if (event2.type === "channel_account_state_updated") {
447610
+ const channelId = event2.channelId;
447611
+ broadcast({
447612
+ type: "channel_accounts_updated",
447613
+ timestamp: Date.now(),
447614
+ channel_id: channelId,
447615
+ account_id: event2.accountId
447616
+ });
447617
+ broadcast({
447618
+ type: "channels_updated",
447619
+ timestamp: Date.now(),
447620
+ channel_id: channelId
447621
+ });
447622
+ return;
447623
+ }
447624
+ const permissionModeState = getOrCreateConversationPermissionModeStateRef(runtime, event2.agentId, event2.conversationId);
447625
+ permissionModeState.mode = event2.defaultPermissionMode;
447626
+ persistPermissionModeMapForRuntime(runtime);
447627
+ const seededWorkingDirectory = seedConversationWorkingDirectory(runtime, event2.agentId, event2.conversationId, runtime.bootWorkingDirectory);
447628
+ if (seededWorkingDirectory) {
447629
+ emitDeviceStatusUpdate(transport, getOrCreateConversationRuntime(runtime, event2.agentId, event2.conversationId));
447630
+ }
447631
+ }
447632
+ var init_channel_registry_events = __esm(async () => {
447633
+ init_error_reporting();
447634
+ init_connection();
447635
+ init_cwd();
447636
+ init_permission_mode();
447637
+ init_runtime6();
447638
+ init_transport();
447639
+ await init_protocol_outbound();
447640
+ });
447641
+
447020
447642
  // src/channels/custom/scaffolding.ts
447021
447643
  import { existsSync as existsSync48, mkdirSync as mkdirSync34, rmSync as rmSync11, writeFileSync as writeFileSync24 } from "node:fs";
447022
447644
  function removeUserPlugin(channelId) {
@@ -447044,7 +447666,6 @@ var init_scaffolding = __esm(() => {
447044
447666
  });
447045
447667
 
447046
447668
  // src/websocket/listener/commands/channels.ts
447047
- import WebSocket4 from "ws";
447048
447669
  function setChannelsServiceLoaderOverride(loader) {
447049
447670
  channelsServiceLoaderOverride = loader;
447050
447671
  }
@@ -447762,52 +448383,13 @@ async function handleChannelsProtocolCommand(parsed, socket, runtime, opts, proc
447762
448383
  }
447763
448384
  return true;
447764
448385
  }
447765
- function handleChannelRegistryEvent(event2, socket, runtime, safeSocketSend) {
447766
- if (event2.type === "pairings_updated") {
447767
- if (socket instanceof WebSocket4) {
447768
- emitChannelPairingsUpdated(socket, safeSocketSend, event2.channelId);
447769
- emitChannelsUpdated(socket, safeSocketSend, event2.channelId);
447770
- }
447771
- return;
447772
- }
447773
- if (event2.type === "targets_updated") {
447774
- if (socket instanceof WebSocket4) {
447775
- emitChannelTargetsUpdated(socket, safeSocketSend, event2.channelId);
447776
- emitChannelsUpdated(socket, safeSocketSend, event2.channelId);
447777
- }
447778
- return;
447779
- }
447780
- if (event2.type === "channel_account_state_updated") {
447781
- if (socket instanceof WebSocket4) {
447782
- emitChannelAccountsUpdated(socket, safeSocketSend, {
447783
- channelId: event2.channelId,
447784
- accountId: event2.accountId
447785
- });
447786
- emitChannelsUpdated(socket, safeSocketSend, event2.channelId);
447787
- }
447788
- return;
447789
- }
447790
- const permissionModeState = getOrCreateConversationPermissionModeStateRef(runtime, event2.agentId, event2.conversationId);
447791
- permissionModeState.mode = event2.defaultPermissionMode;
447792
- persistPermissionModeMapForRuntime(runtime);
447793
- const seededWorkingDirectory = seedConversationWorkingDirectory(runtime, event2.agentId, event2.conversationId, runtime.bootWorkingDirectory);
447794
- if (seededWorkingDirectory) {
447795
- emitDeviceStatusUpdate(socket, getOrCreateConversationRuntime(runtime, event2.agentId, event2.conversationId));
447796
- }
447797
- }
447798
448386
  var channelsServiceLoaderOverride = null;
447799
448387
  var init_channels2 = __esm(async () => {
447800
448388
  init_account_config7();
447801
448389
  init_scaffolding();
447802
448390
  init_plugin_registry();
447803
448391
  init_types6();
447804
- init_cwd();
447805
- init_permission_mode();
447806
- init_runtime6();
447807
- await __promiseAll([
447808
- init_protocol_inbound(),
447809
- init_protocol_outbound()
447810
- ]);
448392
+ await init_protocol_inbound();
447811
448393
  });
447812
448394
 
447813
448395
  // src/websocket/listener/commands/cron.ts
@@ -449626,11 +450208,7 @@ var init_model_toolset = __esm(async () => {
449626
450208
  });
449627
450209
 
449628
450210
  // src/websocket/listener/external-tools.ts
449629
- function isSocketOpen(socket) {
449630
- return socket?.readyState === WEBSOCKET_OPEN;
449631
- }
449632
450211
  function getPendingExternalToolCalls(runtime) {
449633
- runtime.pendingExternalToolCalls ??= new Map;
449634
450212
  return runtime.pendingExternalToolCalls;
449635
450213
  }
449636
450214
  function getRegisteredTools(runtime) {
@@ -449641,25 +450219,35 @@ function getRegisteredTools(runtime) {
449641
450219
  }
449642
450220
  return registeredTools;
449643
450221
  }
450222
+ function getConnectionIdsByRegistrationKey(runtime) {
450223
+ let controllers = connectionIdByRegistrationKey.get(runtime);
450224
+ if (!controllers) {
450225
+ controllers = new Map;
450226
+ connectionIdByRegistrationKey.set(runtime, controllers);
450227
+ }
450228
+ return controllers;
450229
+ }
449644
450230
  function getRuntimeKey(runtime) {
449645
450231
  return `${runtime.agent_id}:${runtime.conversation_id}`;
449646
450232
  }
449647
- function getToolRegistrationKey(runtime, scopeId, toolName) {
450233
+ function getToolRegistrationKey(connectionId, runtime, scopeId, toolName) {
449648
450234
  return JSON.stringify([
449649
450235
  "runtime",
450236
+ connectionId,
449650
450237
  runtime.agent_id,
449651
450238
  runtime.conversation_id,
449652
450239
  scopeId ?? null,
449653
450240
  toolName
449654
450241
  ]);
449655
450242
  }
449656
- function toExternalToolDefinition(tool2, runtime, scopeId) {
450243
+ function toExternalToolDefinition(tool2, connectionId, runtime, scopeId) {
449657
450244
  return {
449658
450245
  name: tool2.name,
450246
+ connectionId,
449659
450247
  ...tool2.label !== undefined ? { label: tool2.label } : {},
449660
450248
  description: tool2.description,
449661
450249
  parameters: tool2.parameters,
449662
- registrationKey: getToolRegistrationKey(runtime, scopeId, tool2.name),
450250
+ registrationKey: getToolRegistrationKey(connectionId, runtime, scopeId, tool2.name),
449663
450251
  ...scopeId !== undefined ? { scopeId } : {},
449664
450252
  runtime: {
449665
450253
  agentId: runtime.agent_id,
@@ -449667,16 +450255,17 @@ function toExternalToolDefinition(tool2, runtime, scopeId) {
449667
450255
  }
449668
450256
  };
449669
450257
  }
449670
- function sendJson(socket, payload) {
449671
- socket.send(JSON.stringify(payload));
449672
- }
449673
450258
  function installExternalToolBridge(runtime) {
449674
450259
  setExternalToolExecutor(async (toolCallId, toolName, input, context3) => {
449675
- const socket = runtime.socket;
449676
- if (!isSocketOpen(socket) || runtime.intentionallyClosed) {
450260
+ const registrationKey = context3?.tool.registrationKey;
450261
+ const connectionId = registrationKey ? getConnectionIdsByRegistrationKey(runtime).get(registrationKey) : undefined;
450262
+ const connection = connectionId ? runtime.connections.get(connectionId) : undefined;
450263
+ if (!connection || !isListenerTransportOpen(connection.writer) || runtime.intentionallyClosed) {
449677
450264
  throw new Error("External tool controller is not connected");
449678
450265
  }
450266
+ const writer = connection.writer;
449679
450267
  const requestId = `external-tool-${crypto.randomUUID()}`;
450268
+ const requestKey = createConnectionRequestKey(connection.id, requestId);
449680
450269
  const toolRuntime = context3?.tool.runtime;
449681
450270
  const requestRuntime = toolRuntime?.agentId && toolRuntime.conversationId ? {
449682
450271
  agent_id: toolRuntime.agentId,
@@ -449693,10 +450282,11 @@ function installExternalToolBridge(runtime) {
449693
450282
  };
449694
450283
  const result = await new Promise((resolve31, reject) => {
449695
450284
  const timeout = setTimeout(() => {
449696
- getPendingExternalToolCalls(runtime).delete(requestId);
450285
+ getPendingExternalToolCalls(runtime).delete(requestKey);
449697
450286
  reject(new Error(`External tool call timed out: ${toolName}`));
449698
450287
  }, EXTERNAL_TOOL_CALL_TIMEOUT_MS);
449699
- getPendingExternalToolCalls(runtime).set(requestId, {
450288
+ getPendingExternalToolCalls(runtime).set(requestKey, {
450289
+ connectionId: connection.id,
449700
450290
  resolve: (response) => {
449701
450291
  resolve31({
449702
450292
  content: [...response.content],
@@ -449707,36 +450297,49 @@ function installExternalToolBridge(runtime) {
449707
450297
  timeout
449708
450298
  });
449709
450299
  try {
449710
- sendJson(socket, request);
450300
+ writer.send(JSON.stringify(request));
449711
450301
  } catch (error54) {
449712
450302
  clearTimeout(timeout);
449713
- getPendingExternalToolCalls(runtime).delete(requestId);
450303
+ getPendingExternalToolCalls(runtime).delete(requestKey);
449714
450304
  reject(error54 instanceof Error ? error54 : new Error(String(error54)));
449715
450305
  }
449716
450306
  });
449717
450307
  return result;
449718
450308
  });
449719
450309
  }
449720
- function registerRuntimeExternalTools(runtime, runtimeScope, groups = []) {
450310
+ function registerRuntimeExternalTools(runtime, connectionId, runtimeScope, groups) {
450311
+ const resolvedGroups = groups ?? [];
449721
450312
  const registeredTools = getRegisteredTools(runtime);
449722
- const runtimeKey = getRuntimeKey(runtimeScope);
450313
+ const runtimeKey = createConnectionRequestKey(connectionId, getRuntimeKey(runtimeScope));
449723
450314
  const previousTools = registeredTools.get(runtimeKey) ?? [];
449724
450315
  unregisterExternalTools(previousTools);
449725
- const tools = groups.flatMap((group) => group.tools.map((tool2) => toExternalToolDefinition(tool2, runtimeScope, group.scope_id)));
450316
+ const tools = resolvedGroups.flatMap((group) => group.tools.map((tool2) => toExternalToolDefinition(tool2, connectionId, runtimeScope, group.scope_id)));
450317
+ const controllers = getConnectionIdsByRegistrationKey(runtime);
450318
+ for (const tool2 of previousTools) {
450319
+ if (tool2.registrationKey) {
450320
+ controllers.delete(tool2.registrationKey);
450321
+ }
450322
+ }
449726
450323
  if (tools.length > 0) {
449727
450324
  registerExternalTools(tools);
450325
+ for (const tool2 of tools) {
450326
+ if (tool2.registrationKey) {
450327
+ controllers.set(tool2.registrationKey, connectionId);
450328
+ }
450329
+ }
449728
450330
  registeredTools.set(runtimeKey, tools);
449729
450331
  } else {
449730
450332
  registeredTools.delete(runtimeKey);
449731
450333
  }
449732
450334
  }
449733
- function handleExternalToolCallResponseCommand(runtime, command) {
449734
- const pending = getPendingExternalToolCalls(runtime).get(command.request_id);
450335
+ function handleExternalToolCallResponseCommand(runtime, connectionId, command) {
450336
+ const requestKey = createConnectionRequestKey(connectionId, command.request_id);
450337
+ const pending = getPendingExternalToolCalls(runtime).get(requestKey);
449735
450338
  if (!pending) {
449736
450339
  return false;
449737
450340
  }
449738
450341
  clearTimeout(pending.timeout);
449739
- getPendingExternalToolCalls(runtime).delete(command.request_id);
450342
+ getPendingExternalToolCalls(runtime).delete(requestKey);
449740
450343
  if (command.error !== undefined) {
449741
450344
  pending.reject(new Error(command.error));
449742
450345
  return true;
@@ -449748,6 +450351,35 @@ function handleExternalToolCallResponseCommand(runtime, command) {
449748
450351
  pending.resolve(command.result);
449749
450352
  return true;
449750
450353
  }
450354
+ function rejectPendingExternalToolCallsForConnection(runtime, connectionId, reason) {
450355
+ const pendingExternalToolCalls = getPendingExternalToolCalls(runtime);
450356
+ for (const [requestKey, pending] of pendingExternalToolCalls) {
450357
+ if (pending.connectionId !== connectionId) {
450358
+ continue;
450359
+ }
450360
+ clearTimeout(pending.timeout);
450361
+ pendingExternalToolCalls.delete(requestKey);
450362
+ pending.reject(new Error(reason));
450363
+ }
450364
+ const registeredTools = registeredToolsByRuntime.get(runtime);
450365
+ if (!registeredTools) {
450366
+ return;
450367
+ }
450368
+ const controllers = getConnectionIdsByRegistrationKey(runtime);
450369
+ for (const [registrationScope, tools] of registeredTools) {
450370
+ const ownedByConnection = tools.some((tool2) => tool2.registrationKey !== undefined && controllers.get(tool2.registrationKey) === connectionId);
450371
+ if (!ownedByConnection) {
450372
+ continue;
450373
+ }
450374
+ unregisterExternalTools(tools);
450375
+ registeredTools.delete(registrationScope);
450376
+ for (const tool2 of tools) {
450377
+ if (tool2.registrationKey) {
450378
+ controllers.delete(tool2.registrationKey);
450379
+ }
450380
+ }
450381
+ }
450382
+ }
449751
450383
  function rejectPendingExternalToolCalls(runtime, reason) {
449752
450384
  const pendingExternalToolCalls = getPendingExternalToolCalls(runtime);
449753
450385
  for (const [requestId, pending] of pendingExternalToolCalls) {
@@ -449762,12 +450394,16 @@ function rejectPendingExternalToolCalls(runtime, reason) {
449762
450394
  }
449763
450395
  registeredToolsByRuntime.delete(runtime);
449764
450396
  }
450397
+ connectionIdByRegistrationKey.delete(runtime);
449765
450398
  }
449766
- var EXTERNAL_TOOL_CALL_TIMEOUT_MS, WEBSOCKET_OPEN = 1, registeredToolsByRuntime;
450399
+ var EXTERNAL_TOOL_CALL_TIMEOUT_MS, registeredToolsByRuntime, connectionIdByRegistrationKey;
449767
450400
  var init_external_tools = __esm(async () => {
450401
+ init_connection();
450402
+ init_transport();
449768
450403
  await init_manager4();
449769
450404
  EXTERNAL_TOOL_CALL_TIMEOUT_MS = 5 * 60 * 1000;
449770
450405
  registeredToolsByRuntime = new WeakMap;
450406
+ connectionIdByRegistrationKey = new WeakMap;
449771
450407
  });
449772
450408
 
449773
450409
  // src/websocket/listener/commands/runtime-start.ts
@@ -449915,9 +450551,18 @@ async function handleRuntimeStartCommand(parsed, context3) {
449915
450551
  agent2 = await resolveRuntimeStartAgent(parsed, created);
449916
450552
  conversation = await resolveRuntimeStartConversation(parsed, agent2, created);
449917
450553
  runtimeScope = buildRuntimeScope(agent2, conversation);
450554
+ const { connectionId } = context3;
450555
+ const assertConnectionOpen = () => {
450556
+ if (context3.runtime.connections.size > 0 && (!context3.runtime.connections.has(connectionId) || context3.runtime.connections.get(connectionId)?.cancellation.signal.aborted)) {
450557
+ throw new Error("App-server connection closed during runtime start");
450558
+ }
450559
+ };
450560
+ assertConnectionOpen();
449918
450561
  const scopedRuntime = context3.getOrCreateScopedRuntime(context3.runtime, runtimeScope.agent_id, runtimeScope.conversation_id);
449919
450562
  await applyRuntimeStartState(parsed, context3, runtimeScope, scopedRuntime);
449920
- registerRuntimeExternalTools(context3.runtime, runtimeScope, parsed.external_tools ?? []);
450563
+ assertConnectionOpen();
450564
+ subscribeListenerConnection(context3.runtime, connectionId, runtimeScope);
450565
+ registerRuntimeExternalTools(context3.runtime, connectionId, runtimeScope, parsed.external_tools ?? []);
449921
450566
  const sent = sendRuntimeStartResponse(context3, parsed, {
449922
450567
  success: true,
449923
450568
  runtime: runtimeScope,
@@ -449959,6 +450604,7 @@ var init_runtime_start = __esm(async () => {
449959
450604
  init_backend2();
449960
450605
  init_mode();
449961
450606
  init_settings_manager();
450607
+ init_connection();
449962
450608
  init_cwd();
449963
450609
  init_permission_mode();
449964
450610
  await __promiseAll([
@@ -457404,16 +458050,19 @@ async function handleApprovalStop(params) {
457404
458050
  return interruptTermination();
457405
458051
  }
457406
458052
  const onFileWrite = (filePath, content) => {
457407
- if (runtime.turnLifecycle.isCurrent(turnLease) && isListenerTransportOpen(socket)) {
457408
- socket.send(JSON.stringify({
457409
- type: "file_ops",
457410
- path: filePath,
457411
- cg_entries: [],
457412
- ops: [],
457413
- source: "agent",
457414
- document_content: content
457415
- }));
457416
- }
458053
+ if (!runtime.turnLifecycle.isCurrent(turnLease))
458054
+ return;
458055
+ emitProtocolV2Message(socket, runtime, {
458056
+ type: "file_ops",
458057
+ path: filePath,
458058
+ cg_entries: [],
458059
+ ops: [],
458060
+ source: "agent",
458061
+ document_content: content
458062
+ }, {
458063
+ agent_id: agentId,
458064
+ conversation_id: conversationId
458065
+ }, TO_SUBSCRIBERS);
457417
458066
  };
457418
458067
  let executionResults;
457419
458068
  try {
@@ -457559,9 +458208,9 @@ var init_turn_approval = __esm(async () => {
457559
458208
  init_diff_preview();
457560
458209
  init_format_denial();
457561
458210
  init_interactive_policy();
458211
+ init_connection();
457562
458212
  init_secrets_sync();
457563
458213
  init_skill_injection();
457564
- init_transport();
457565
458214
  init_turn_input_state();
457566
458215
  await __promiseAll([
457567
458216
  init_approval_execution(),
@@ -458995,6 +459644,7 @@ async function prepareListenerTurn(params) {
458995
459644
  }
458996
459645
  const modAdapters = await ensureListenerModAdaptersForAgent(runtime.listener, agentId);
458997
459646
  const preparedToolContext = await prepareToolExecutionContextForScope({
459647
+ connectionId,
458998
459648
  agentId,
458999
459649
  conversationId,
459000
459650
  clientToolAllowlist: msg.clientToolAllowlist,
@@ -459078,6 +459728,9 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
459078
459728
  origin: "message",
459079
459729
  workingDirectory: turnWorkingDirectory
459080
459730
  });
459731
+ if (connectionId) {
459732
+ runtime.activeConnectionId = connectionId;
459733
+ }
459081
459734
  if (!runtime.turnLifecycle.isCurrent(turnLease)) {
459082
459735
  throw new Error("Cannot continue a turn with a stale lifecycle lease");
459083
459736
  }
@@ -459682,6 +460335,9 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
459682
460335
  });
459683
460336
  }
459684
460337
  richDraftStreamer?.dispose();
460338
+ if (runtime.activeConnectionId === connectionId) {
460339
+ runtime.activeConnectionId = null;
460340
+ }
459685
460341
  try {
459686
460342
  if (finalizedByThisInvocation) {
459687
460343
  await runListenerTurnCleanup({
@@ -459787,15 +460443,19 @@ function handleModeChange(msg, socket, runtime, scope) {
459787
460443
  }
459788
460444
  }
459789
460445
  }
459790
- function resolveRuntimeForApprovalRequest(listener, requestId) {
460446
+ function resolveRuntimeForApprovalRequest(listener, scope, requestId, connectionId) {
459791
460447
  if (!requestId) {
459792
460448
  return null;
459793
460449
  }
459794
- const runtimeKey = listener.approvalRuntimeKeyByRequestId.get(requestId);
459795
- if (!runtimeKey) {
460450
+ const runtime = getConversationRuntime(listener, scope.agent_id, scope.conversation_id);
460451
+ if (!runtime) {
459796
460452
  return null;
459797
460453
  }
459798
- return listener.conversationRuntimes.get(runtimeKey) ?? null;
460454
+ if (connectionId) {
460455
+ const requestKey = createConnectionRequestKey(connectionId, requestId);
460456
+ return runtime.pendingApprovalResolvers.has(requestKey) ? runtime : null;
460457
+ }
460458
+ return hasPendingApprovalRequestId(runtime, requestId) ? runtime : null;
459799
460459
  }
459800
460460
  async function handleApprovalResponseInput(listener, params, deps = {
459801
460461
  resolveRuntimeForApprovalRequest,
@@ -459804,8 +460464,8 @@ async function handleApprovalResponseInput(listener, params, deps = {
459804
460464
  resolveRecoveredApprovalResponse,
459805
460465
  scheduleQueuePump
459806
460466
  }) {
459807
- const approvalRuntime = deps.resolveRuntimeForApprovalRequest(listener, params.response.request_id);
459808
- if (approvalRuntime && deps.resolvePendingApprovalResolver(approvalRuntime, params.response)) {
460467
+ const approvalRuntime = deps.resolveRuntimeForApprovalRequest(listener, params.runtime, params.response.request_id, params.connectionId);
460468
+ if (approvalRuntime && deps.resolvePendingApprovalResolver(approvalRuntime, params.response, params.connectionId)) {
459809
460469
  deps.scheduleQueuePump(approvalRuntime, params.socket, params.opts, params.processQueuedTurn);
459810
460470
  return true;
459811
460471
  }
@@ -460044,6 +460704,7 @@ var init_control_inputs = __esm(async () => {
460044
460704
  init_mode();
460045
460705
  init_error_reporting();
460046
460706
  init_debug();
460707
+ init_connection();
460047
460708
  init_cwd();
460048
460709
  init_permission_mode();
460049
460710
  init_runtime6();
@@ -460062,16 +460723,30 @@ var init_control_inputs = __esm(async () => {
460062
460723
  });
460063
460724
 
460064
460725
  // src/websocket/terminal-handler.ts
460726
+ import { existsSync as existsSync51 } from "node:fs";
460065
460727
  import * as os6 from "node:os";
460066
- import WebSocket5 from "ws";
460728
+ import WebSocket4 from "ws";
460729
+ function getTerminalKey(connectionId, terminalId) {
460730
+ return JSON.stringify([connectionId, terminalId]);
460731
+ }
460067
460732
  function getDefaultShell() {
460068
460733
  if (os6.platform() === "win32") {
460069
460734
  return process.env.COMSPEC || "cmd.exe";
460070
460735
  }
460071
- return process.env.SHELL || "/bin/zsh";
460736
+ const candidates2 = [
460737
+ process.env.SHELL,
460738
+ os6.platform() === "darwin" ? "/bin/zsh" : "/bin/bash",
460739
+ "/bin/bash"
460740
+ ];
460741
+ for (const candidate of candidates2) {
460742
+ if (candidate && existsSync51(candidate)) {
460743
+ return candidate;
460744
+ }
460745
+ }
460746
+ return "/bin/sh";
460072
460747
  }
460073
460748
  function sendTerminalMessage(socket, message) {
460074
- if (socket.readyState === WebSocket5.OPEN) {
460749
+ if (socket.readyState === WebSocket4.OPEN) {
460075
460750
  socket.send(JSON.stringify(message));
460076
460751
  }
460077
460752
  }
@@ -460097,7 +460772,8 @@ function makeOutputBatcher(onFlush) {
460097
460772
  }
460098
460773
  };
460099
460774
  }
460100
- function spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) {
460775
+ function spawnBun(shell2, cwd2, cols, rows, terminal_id, connectionId, socket) {
460776
+ const terminalKey = getTerminalKey(connectionId, terminal_id);
460101
460777
  const handleData = makeOutputBatcher((data) => sendTerminalMessage(socket, { type: "terminal_output", terminal_id, data }));
460102
460778
  const proc2 = Bun.spawn([shell2], {
460103
460779
  cwd: cwd2,
@@ -460113,9 +460789,9 @@ function spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) {
460113
460789
  throw new Error("Bun.spawn terminal object missing — API unavailable");
460114
460790
  }
460115
460791
  proc2.exited.then((exitCode) => {
460116
- const current = terminals.get(terminal_id);
460792
+ const current = terminals.get(terminalKey);
460117
460793
  if (current && current.pid === proc2.pid) {
460118
- terminals.delete(terminal_id);
460794
+ terminals.delete(terminalKey);
460119
460795
  sendTerminalMessage(socket, {
460120
460796
  type: "terminal_exited",
460121
460797
  terminal_id,
@@ -460144,10 +460820,12 @@ function spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) {
460144
460820
  },
460145
460821
  pid: proc2.pid,
460146
460822
  terminalId: terminal_id,
460823
+ connectionId,
460147
460824
  spawnedAt: Date.now()
460148
460825
  };
460149
460826
  }
460150
- function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket) {
460827
+ function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, connectionId, socket) {
460828
+ const terminalKey = getTerminalKey(connectionId, terminal_id);
460151
460829
  const pty = __require("node-pty");
460152
460830
  const handleData = makeOutputBatcher((data) => sendTerminalMessage(socket, { type: "terminal_output", terminal_id, data }));
460153
460831
  const ptyProcess = pty.spawn(shell2, [], {
@@ -460163,9 +460841,9 @@ function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket) {
460163
460841
  });
460164
460842
  ptyProcess.onData(handleData);
460165
460843
  ptyProcess.onExit(({ exitCode }) => {
460166
- const current = terminals.get(terminal_id);
460844
+ const current = terminals.get(terminalKey);
460167
460845
  if (current && current.pid === ptyProcess.pid) {
460168
- terminals.delete(terminal_id);
460846
+ terminals.delete(terminalKey);
460169
460847
  sendTerminalMessage(socket, {
460170
460848
  type: "terminal_exited",
460171
460849
  terminal_id,
@@ -460191,12 +460869,14 @@ function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket) {
460191
460869
  },
460192
460870
  pid: ptyProcess.pid,
460193
460871
  terminalId: terminal_id,
460872
+ connectionId,
460194
460873
  spawnedAt: Date.now()
460195
460874
  };
460196
460875
  }
460197
- function handleTerminalSpawn(msg, socket, cwd2) {
460876
+ function handleTerminalSpawn(msg, socket, cwd2, connectionId = "legacy") {
460198
460877
  const { terminal_id, cols, rows } = msg;
460199
- const existing = terminals.get(terminal_id);
460878
+ const terminalKey = getTerminalKey(connectionId, terminal_id);
460879
+ const existing = terminals.get(terminalKey);
460200
460880
  if (existing && Date.now() - existing.spawnedAt < 2000) {
460201
460881
  let alive = true;
460202
460882
  try {
@@ -460213,14 +460893,14 @@ function handleTerminalSpawn(msg, socket, cwd2) {
460213
460893
  });
460214
460894
  return;
460215
460895
  }
460216
- terminals.delete(terminal_id);
460896
+ terminals.delete(terminalKey);
460217
460897
  }
460218
- killTerminal(terminal_id);
460898
+ killTerminal(terminal_id, connectionId);
460219
460899
  const shell2 = getDefaultShell();
460220
460900
  console.log(`[Terminal] Spawning PTY (${IS_BUN ? "bun" : "node-pty"}): shell=${shell2}, cwd=${cwd2}, cols=${cols}, rows=${rows}`);
460221
460901
  try {
460222
- const session = IS_BUN ? spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) : spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket);
460223
- terminals.set(terminal_id, session);
460902
+ const session = IS_BUN ? spawnBun(shell2, cwd2, cols, rows, terminal_id, connectionId, socket) : spawnNodePty(shell2, cwd2, cols, rows, terminal_id, connectionId, socket);
460903
+ terminals.set(terminalKey, session);
460224
460904
  console.log(`[Terminal] Session stored for terminal_id=${terminal_id}, pid=${session.pid}`);
460225
460905
  sendTerminalMessage(socket, {
460226
460906
  type: "terminal_spawned",
@@ -460237,31 +460917,39 @@ function handleTerminalSpawn(msg, socket, cwd2) {
460237
460917
  });
460238
460918
  }
460239
460919
  }
460240
- function handleTerminalInput(msg) {
460241
- terminals.get(msg.terminal_id)?.write(msg.data);
460920
+ function handleTerminalInput(msg, connectionId = "legacy") {
460921
+ terminals.get(getTerminalKey(connectionId, msg.terminal_id))?.write(msg.data);
460242
460922
  }
460243
- function handleTerminalResize(msg) {
460244
- terminals.get(msg.terminal_id)?.resize(msg.cols, msg.rows);
460923
+ function handleTerminalResize(msg, connectionId = "legacy") {
460924
+ terminals.get(getTerminalKey(connectionId, msg.terminal_id))?.resize(msg.cols, msg.rows);
460245
460925
  }
460246
- function handleTerminalKill(msg) {
460247
- const session = terminals.get(msg.terminal_id);
460926
+ function handleTerminalKill(msg, connectionId = "legacy") {
460927
+ const session = terminals.get(getTerminalKey(connectionId, msg.terminal_id));
460248
460928
  if (session && Date.now() - session.spawnedAt < 2000) {
460249
460929
  console.log(`[Terminal] Ignoring kill for recently spawned session (age=${Date.now() - session.spawnedAt}ms)`);
460250
460930
  return;
460251
460931
  }
460252
- killTerminal(msg.terminal_id);
460932
+ killTerminal(msg.terminal_id, connectionId);
460253
460933
  }
460254
- function killTerminal(terminalId) {
460255
- const session = terminals.get(terminalId);
460934
+ function killTerminal(terminalId, connectionId) {
460935
+ const terminalKey = getTerminalKey(connectionId, terminalId);
460936
+ const session = terminals.get(terminalKey);
460256
460937
  if (session) {
460257
460938
  console.log(`[Terminal] killTerminal: terminalId=${terminalId}, pid=${session.pid}`);
460258
460939
  session.kill();
460259
- terminals.delete(terminalId);
460940
+ terminals.delete(terminalKey);
460941
+ }
460942
+ }
460943
+ function killListenerConnectionTerminals(connectionId) {
460944
+ for (const session of [...terminals.values()]) {
460945
+ if (session.connectionId === connectionId) {
460946
+ killTerminal(session.terminalId, connectionId);
460947
+ }
460260
460948
  }
460261
460949
  }
460262
460950
  function killAllTerminals() {
460263
- for (const [id2] of terminals) {
460264
- killTerminal(id2);
460951
+ for (const session of [...terminals.values()]) {
460952
+ killTerminal(session.terminalId, session.connectionId);
460265
460953
  }
460266
460954
  }
460267
460955
  var IS_BUN, FLUSH_INTERVAL_MS = 16, MAX_BUFFER_BYTES, terminals;
@@ -460489,7 +461177,7 @@ __export(exports_custom, {
460489
461177
  GLOBAL_COMMANDS_DIR: () => GLOBAL_COMMANDS_DIR,
460490
461178
  COMMANDS_DIR: () => COMMANDS_DIR
460491
461179
  });
460492
- import { existsSync as existsSync51 } from "node:fs";
461180
+ import { existsSync as existsSync52 } from "node:fs";
460493
461181
  import { readdir as readdir10, readFile as readFile20 } from "node:fs/promises";
460494
461182
  import { basename as basename25, dirname as dirname28, join as join65 } from "node:path";
460495
461183
  async function getCustomCommands() {
@@ -460523,7 +461211,7 @@ async function discoverCustomCommands(projectPath = join65(process.cwd(), COMMAN
460523
461211
  return result;
460524
461212
  }
460525
461213
  async function discoverFromDirectory(dirPath, source2) {
460526
- if (!existsSync51(dirPath)) {
461214
+ if (!existsSync52(dirPath)) {
460527
461215
  return [];
460528
461216
  }
460529
461217
  const commands = [];
@@ -461488,6 +462176,144 @@ var init_commands2 = __esm(async () => {
461488
462176
  ]);
461489
462177
  });
461490
462178
 
462179
+ // src/websocket/listener/connection-lifecycle.ts
462180
+ import WebSocket5 from "ws";
462181
+ function createConnectionTurnProcessor(runtime) {
462182
+ return async (queuedTurn, dequeuedBatch) => {
462183
+ const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
462184
+ if (!queuedTurn.connectionId) {
462185
+ await handleIncomingMessage(queuedTurn, getOrCreateProcessTransport(runtime), scopedRuntime, undefined, undefined, dequeuedBatch.batchId);
462186
+ return;
462187
+ }
462188
+ const connection = runtime.connections.get(queuedTurn.connectionId);
462189
+ if (!connection || connection.cancellation.signal.aborted) {
462190
+ return;
462191
+ }
462192
+ await handleIncomingMessage(queuedTurn, connection.writer, scopedRuntime, connection.options.onStatusChange, connection.id, dequeuedBatch.batchId);
462193
+ };
462194
+ }
462195
+ function cleanupListenerConnection(runtime, connectionId) {
462196
+ for (const conversationRuntime of runtime.conversationRuntimes.values()) {
462197
+ if (conversationRuntime.activeConnectionId === connectionId) {
462198
+ const hasEligibleFailover = getSubscribedListenerConnections(runtime, {
462199
+ agent_id: conversationRuntime.agentId,
462200
+ conversation_id: conversationRuntime.conversationId
462201
+ }).some((connection) => connection.id !== connectionId);
462202
+ if (hasEligibleFailover) {
462203
+ conversationRuntime.activeConnectionId = null;
462204
+ } else {
462205
+ conversationRuntime.turnLifecycle.requestCancellation();
462206
+ }
462207
+ }
462208
+ for (const [
462209
+ itemId,
462210
+ queuedMessage
462211
+ ] of conversationRuntime.queuedMessagesByItemId) {
462212
+ if (queuedMessage.connectionId === connectionId) {
462213
+ conversationRuntime.queueRuntime.removeItem(itemId);
462214
+ conversationRuntime.queuedMessagesByItemId.delete(itemId);
462215
+ }
462216
+ }
462217
+ rejectPendingApprovalResolversForConnection(conversationRuntime, connectionId, "Listener connection closed");
462218
+ }
462219
+ rejectPendingExternalToolCallsForConnection(runtime, connectionId, "Listener connection closed");
462220
+ killListenerConnectionTerminals(connectionId);
462221
+ const closedSubscriptionKeys = [
462222
+ ...runtime.connections.get(connectionId)?.subscriptions ?? []
462223
+ ];
462224
+ closeListenerConnection(runtime, connectionId);
462225
+ for (const runtimeKey of closedSubscriptionKeys) {
462226
+ if (!runtime.connectionIdsByRuntimeKey.has(runtimeKey)) {
462227
+ const scopedRuntime = runtime.conversationRuntimes.get(runtimeKey);
462228
+ if (scopedRuntime) {
462229
+ evictConversationRuntimeIfIdle(scopedRuntime);
462230
+ }
462231
+ }
462232
+ }
462233
+ }
462234
+ function closeListenerRuntimeConnections(runtime, suppressCallbacks) {
462235
+ const socketsToClose = new Set;
462236
+ const collectSocket = (transport) => {
462237
+ if (transport && getListenerTransportKind(transport) === "websocket") {
462238
+ socketsToClose.add(transport);
462239
+ }
462240
+ };
462241
+ if (runtime.socket) {
462242
+ socketsToClose.add(runtime.socket);
462243
+ }
462244
+ if (runtime.streamSocket) {
462245
+ socketsToClose.add(runtime.streamSocket);
462246
+ }
462247
+ for (const connection of runtime.connections.values()) {
462248
+ collectSocket(connection.writer);
462249
+ collectSocket(connection.streamWriter);
462250
+ }
462251
+ for (const connectionId of [...runtime.connections.keys()]) {
462252
+ closeListenerConnection(runtime, connectionId);
462253
+ }
462254
+ runtime.connectionIdsByRuntimeKey.clear();
462255
+ runtime.socket = null;
462256
+ runtime.transport = null;
462257
+ runtime.streamSocket = null;
462258
+ runtime.streamTransport = null;
462259
+ for (const socket of socketsToClose) {
462260
+ if (suppressCallbacks) {
462261
+ socket.removeAllListeners();
462262
+ }
462263
+ if (socket.readyState === WebSocket5.OPEN || socket.readyState === WebSocket5.CONNECTING) {
462264
+ socket.close();
462265
+ }
462266
+ }
462267
+ }
462268
+ var init_connection_lifecycle = __esm(async () => {
462269
+ init_terminal_handler();
462270
+ init_connection();
462271
+ init_runtime6();
462272
+ init_transport();
462273
+ await __promiseAll([
462274
+ init_approval(),
462275
+ init_conversation_runtime(),
462276
+ init_external_tools(),
462277
+ init_turn()
462278
+ ]);
462279
+ });
462280
+
462281
+ // src/websocket/listener/connection-state-sync.ts
462282
+ function emitInitialConnectionState(runtime, transport, connectionId, options3 = {}) {
462283
+ if (options3.emitInitialState === false)
462284
+ return;
462285
+ const routing = toListenerConnection(connectionId);
462286
+ if (runtime.conversationRuntimes.size === 0) {
462287
+ emitLoopStatusUpdate(transport, runtime, undefined, routing);
462288
+ return;
462289
+ }
462290
+ for (const conversationRuntime of runtime.conversationRuntimes.values()) {
462291
+ const scope = {
462292
+ agent_id: conversationRuntime.agentId,
462293
+ conversation_id: conversationRuntime.conversationId
462294
+ };
462295
+ emitDeviceStatusUpdate(transport, conversationRuntime, scope, routing);
462296
+ emitLoopStatusUpdate(transport, conversationRuntime, scope, routing);
462297
+ }
462298
+ }
462299
+ function replaySubscribedConnectionState(listener, transport, runtime, scope, forceDeviceStatus) {
462300
+ const connection = findListenerConnectionByTransport(listener, transport);
462301
+ if (connection) {
462302
+ replayPendingApprovalRequestsToConnection(runtime, connection.id);
462303
+ }
462304
+ emitStateSync(transport, listener, scope, {
462305
+ forceDeviceStatus,
462306
+ ...connection ? { routing: toListenerConnection(connection.id) } : {}
462307
+ });
462308
+ }
462309
+ var init_connection_state_sync = __esm(async () => {
462310
+ init_connection();
462311
+ await __promiseAll([
462312
+ init_approval(),
462313
+ init_protocol_outbound()
462314
+ ]);
462315
+ });
462316
+
461491
462317
  // src/websocket/listener/grep-in-files.ts
461492
462318
  import { execFile as execFile14 } from "node:child_process";
461493
462319
  import { createRequire as createRequire6 } from "node:module";
@@ -462356,7 +463182,7 @@ function buildAppServerInfoResponse(command, options3) {
462356
463182
  conversation_management: true,
462357
463183
  memory_management: true,
462358
463184
  runtime_start: true,
462359
- split_channels: true
463185
+ split_channels: false
462360
463186
  }
462361
463187
  };
462362
463188
  }
@@ -463701,6 +464527,7 @@ function createListenerMessageHandler(params) {
463701
464527
  const {
463702
464528
  runtime,
463703
464529
  socket,
464530
+ connectionId: explicitConnectionId,
463704
464531
  opts,
463705
464532
  processQueuedTurn,
463706
464533
  fileCommandSession,
@@ -463717,6 +464544,7 @@ function createListenerMessageHandler(params) {
463717
464544
  wireChannelIngress,
463718
464545
  processIncomingMessage = handleIncomingMessage
463719
464546
  } = params;
464547
+ const connectionId = explicitConnectionId ?? opts.connectionId;
463720
464548
  return async (data) => {
463721
464549
  const raw2 = data.toString();
463722
464550
  let parsedScope = null;
@@ -463746,6 +464574,9 @@ function createListenerMessageHandler(params) {
463746
464574
  return;
463747
464575
  }
463748
464576
  console.log(`[Listen V2] Received ${summarizeV2Command(parsed)}`);
464577
+ if (parsedScope) {
464578
+ subscribeListenerConnection(runtime, connectionId, parsedScope);
464579
+ }
463749
464580
  if (parsed.type === "__invalid_input") {
463750
464581
  emitLoopErrorNotice(socket, runtime, {
463751
464582
  message: parsed.reason,
@@ -463762,6 +464593,7 @@ function createListenerMessageHandler(params) {
463762
464593
  }
463763
464594
  if (handleRuntimeStartProtocolCommand(parsed, {
463764
464595
  socket,
464596
+ connectionId,
463765
464597
  runtime,
463766
464598
  safeSocketSend,
463767
464599
  runDetachedListenerTask,
@@ -463771,7 +464603,7 @@ function createListenerMessageHandler(params) {
463771
464603
  return;
463772
464604
  }
463773
464605
  if (parsed.type === "external_tool_call_response") {
463774
- handleExternalToolCallResponseCommand(runtime, parsed);
464606
+ handleExternalToolCallResponseCommand(runtime, connectionId, parsed);
463775
464607
  return;
463776
464608
  }
463777
464609
  if (parsed.type === "sync") {
@@ -463825,6 +464657,7 @@ function createListenerMessageHandler(params) {
463825
464657
  if (await handleApprovalResponseInput2(runtime, {
463826
464658
  runtime: parsed.runtime,
463827
464659
  response: parsed.payload,
464660
+ connectionId,
463828
464661
  socket,
463829
464662
  opts: {
463830
464663
  onStatusChange: opts.onStatusChange,
@@ -463849,6 +464682,7 @@ function createListenerMessageHandler(params) {
463849
464682
  }
463850
464683
  const incoming = {
463851
464684
  type: "message",
464685
+ connectionId,
463852
464686
  agentId: parsed.runtime.agent_id,
463853
464687
  conversationId: parsed.runtime.conversation_id,
463854
464688
  clientToolAllowlist: inputPayload.client_tool_allowlist,
@@ -463897,6 +464731,7 @@ function createListenerMessageHandler(params) {
463897
464731
  if (parsed.type === "change_device_state") {
463898
464732
  await handleChangeDeviceStateInput2(runtime, {
463899
464733
  command: parsed,
464734
+ connectionId,
463900
464735
  socket,
463901
464736
  opts: {
463902
464737
  onStatusChange: opts.onStatusChange,
@@ -463923,6 +464758,7 @@ function createListenerMessageHandler(params) {
463923
464758
  try {
463924
464759
  const aborted2 = await handleAbortMessageInput2(runtime, {
463925
464760
  command: parsed,
464761
+ connectionId,
463926
464762
  socket,
463927
464763
  opts: {
463928
464764
  onStatusChange: opts.onStatusChange,
@@ -464092,19 +464928,19 @@ function createListenerMessageHandler(params) {
464092
464928
  return;
464093
464929
  }
464094
464930
  if (parsed.type === "terminal_spawn") {
464095
- handleTerminalSpawn(parsed, socket, parsed.cwd ?? getBootWorkingDirectory(runtime));
464931
+ handleTerminalSpawn(parsed, socket, parsed.cwd ?? getBootWorkingDirectory(runtime), connectionId);
464096
464932
  return;
464097
464933
  }
464098
464934
  if (parsed.type === "terminal_input") {
464099
- handleTerminalInput(parsed);
464935
+ handleTerminalInput(parsed, connectionId);
464100
464936
  return;
464101
464937
  }
464102
464938
  if (parsed.type === "terminal_resize") {
464103
- handleTerminalResize(parsed);
464939
+ handleTerminalResize(parsed, connectionId);
464104
464940
  return;
464105
464941
  }
464106
464942
  if (parsed.type === "terminal_kill") {
464107
- handleTerminalKill(parsed);
464943
+ handleTerminalKill(parsed, connectionId);
464108
464944
  }
464109
464945
  } catch (error54) {
464110
464946
  trackListenerError4("listener_message_handler_failed", error54, "listener_message_handler");
@@ -464131,6 +464967,7 @@ var init_message_router = __esm(async () => {
464131
464967
  init_debug();
464132
464968
  init_terminal_handler();
464133
464969
  init_app_server_info();
464970
+ init_connection();
464134
464971
  init_cwd();
464135
464972
  init_runtime6();
464136
464973
  await __promiseAll([
@@ -464157,6 +464994,97 @@ var init_message_router = __esm(async () => {
464157
464994
  ]);
464158
464995
  });
464159
464996
 
464997
+ // src/websocket/listener/process-services.ts
464998
+ function installProcessEventRouting(params) {
464999
+ const { runtime, processTransport, opts, processQueuedTurn } = params;
465000
+ runtime._unsubscribeSubagentState?.();
465001
+ runtime._unsubscribeSubagentState = subscribe(() => {
465002
+ if (runtime.conversationRuntimes.size === 0) {
465003
+ emitSubagentStateIfOpen(runtime);
465004
+ return;
465005
+ }
465006
+ for (const conversationRuntime of runtime.conversationRuntimes.values()) {
465007
+ emitSubagentStateIfOpen(runtime, {
465008
+ agent_id: conversationRuntime.agentId,
465009
+ conversation_id: conversationRuntime.conversationId
465010
+ });
465011
+ }
465012
+ });
465013
+ runtime._unsubscribeSubagentStreamEvents?.();
465014
+ runtime._unsubscribeSubagentStreamEvents = subscribeToStreamEvents((subagentId, event2) => {
465015
+ if (!isListenerTransportOpen(processTransport))
465016
+ return;
465017
+ const subagent = getSubagents().find((entry) => entry.id === subagentId);
465018
+ if (subagent?.silent === true)
465019
+ return;
465020
+ emitStreamDelta(processTransport, runtime, event2, subagent?.parentAgentId ? {
465021
+ agent_id: subagent.parentAgentId,
465022
+ conversation_id: subagent.parentConversationId ?? "default"
465023
+ } : undefined, subagentId);
465024
+ });
465025
+ setMessageQueueAdder((queuedMessage) => {
465026
+ if (!queuedMessage.agentId || !queuedMessage.conversationId)
465027
+ return;
465028
+ const targetRuntime = getOrCreateScopedRuntime(runtime, queuedMessage.agentId, queuedMessage.conversationId);
465029
+ if (!targetRuntime?.queueRuntime)
465030
+ return;
465031
+ targetRuntime.queueRuntime.enqueue({
465032
+ kind: "task_notification",
465033
+ source: "task_notification",
465034
+ text: queuedMessage.text,
465035
+ agentId: queuedMessage.agentId ?? targetRuntime.agentId ?? undefined,
465036
+ conversationId: queuedMessage.conversationId ?? targetRuntime.conversationId
465037
+ });
465038
+ scheduleQueuePump(targetRuntime, processTransport, opts, processQueuedTurn);
465039
+ });
465040
+ }
465041
+ function clearProcessServices(runtime) {
465042
+ runtime._unsubscribeSubagentState?.();
465043
+ runtime._unsubscribeSubagentState = undefined;
465044
+ runtime._unsubscribeSubagentStreamEvents?.();
465045
+ runtime._unsubscribeSubagentStreamEvents = undefined;
465046
+ setMessageQueueAdder(null);
465047
+ stopScheduler();
465048
+ clearRuntimeTimers(runtime);
465049
+ runtime.processServicesStarted = false;
465050
+ }
465051
+ function invalidateProcessServices(runtime) {
465052
+ runtime.processServicesGeneration += 1;
465053
+ clearProcessServices(runtime);
465054
+ }
465055
+ async function waitForProcessServicesSlot(runtime, connectionId) {
465056
+ const initiatingConnection = runtime.connections.get(connectionId);
465057
+ const canInitiate = () => !initiatingConnection || runtime.connections.get(connectionId) === initiatingConnection && !initiatingConnection.cancellation.signal.aborted;
465058
+ while (runtime.processServicesReady) {
465059
+ const pending = runtime.processServicesReady;
465060
+ const pendingGeneration = runtime.processServicesReadyGeneration;
465061
+ try {
465062
+ await pending;
465063
+ } catch (error54) {
465064
+ if (pendingGeneration === runtime.processServicesGeneration)
465065
+ throw error54;
465066
+ }
465067
+ if (runtime.processServicesStarted)
465068
+ return false;
465069
+ if (runtime !== getActiveRuntime() || runtime.intentionallyClosed || !canInitiate()) {
465070
+ return false;
465071
+ }
465072
+ }
465073
+ return canInitiate();
465074
+ }
465075
+ var init_process_services = __esm(async () => {
465076
+ init_subagent_state();
465077
+ init_message_queue_bridge();
465078
+ init_runtime6();
465079
+ init_transport();
465080
+ await __promiseAll([
465081
+ init_scheduler(),
465082
+ init_conversation_runtime(),
465083
+ init_protocol_outbound(),
465084
+ init_queue()
465085
+ ]);
465086
+ });
465087
+
464160
465088
  // src/websocket/listener/lifecycle.ts
464161
465089
  import WebSocket6 from "ws";
464162
465090
  function trackListenerError4(errorType, error54, context3) {
@@ -464219,9 +465147,7 @@ async function replaySyncStateForRuntime(listenerRuntime, socket, scope, opts) {
464219
465147
  }
464220
465148
  }
464221
465149
  }
464222
- emitStateSync(socket, listenerRuntime, scope, {
464223
- forceDeviceStatus: opts?.forceDeviceStatus
464224
- });
465150
+ replaySubscribedConnectionState(listenerRuntime, socket, syncScopedRuntime, scope, opts?.forceDeviceStatus);
464225
465151
  (opts?.scheduleWarmupsAfterSync ?? scheduleListenerWarmupsAfterSync)(listenerRuntime, scope);
464226
465152
  }
464227
465153
  async function recoverPendingChannelControlRequests(listener, opts) {
@@ -464253,7 +465179,7 @@ async function recoverPendingChannelControlRequests(listener, opts) {
464253
465179
  }
464254
465180
  for (const { scope, entries } of entriesByScope.values()) {
464255
465181
  const runtime = getOrCreateScopedRuntime(listener, scope.agent_id, scope.conversation_id);
464256
- const livePendingRequestIds = new Set(runtime.pendingApprovalResolvers.keys());
465182
+ const livePendingRequestIds = getPendingApprovalRequestIds(runtime);
464257
465183
  const shouldRecoverFromBackend = entries.some((entry) => !livePendingRequestIds.has(entry.event.requestId));
464258
465184
  if (shouldRecoverFromBackend) {
464259
465185
  try {
@@ -464378,7 +465304,7 @@ async function wireChannelIngress(listener, socket, opts, processQueuedTurn) {
464378
465304
  scheduleQueuePump(conversationRuntime, socket, opts, processQueuedTurn);
464379
465305
  });
464380
465306
  registry2.setEventHandler((event2) => {
464381
- handleChannelRegistryEvent(event2, socket, listener, safeSocketSend);
465307
+ handleChannelRegistryEvent(event2, socket, listener);
464382
465308
  });
464383
465309
  await recoverPendingChannelControlRequests(listener);
464384
465310
  registry2.setApprovalResponseHandler(async ({ runtime, response }) => handleApprovalResponseInput(listener, {
@@ -464643,6 +465569,14 @@ function createRuntime() {
464643
465569
  hasSuccessfulConnection: false,
464644
465570
  everConnected: false,
464645
465571
  sessionId: `listen-${crypto.randomUUID()}`,
465572
+ nextConnectionOrdinal: 0,
465573
+ connections: new Map,
465574
+ connectionIdsByRuntimeKey: new Map,
465575
+ processTransport: null,
465576
+ processServicesStarted: false,
465577
+ processServicesGeneration: 0,
465578
+ processServicesReady: null,
465579
+ processServicesReadyGeneration: null,
464646
465580
  eventSeqCounter: 0,
464647
465581
  queueEmitScheduled: false,
464648
465582
  pendingQueueEmitScope: undefined,
@@ -464660,7 +465594,6 @@ function createRuntime() {
464660
465594
  connectionId: null,
464661
465595
  connectionName: null,
464662
465596
  conversationRuntimes: new Map,
464663
- approvalRuntimeKeyByRequestId: new Map,
464664
465597
  memfsSyncedAgents: new Map,
464665
465598
  secretsHydrationByAgent: new Map,
464666
465599
  secretsHydrationFreshnessByAgent: new Map,
@@ -464674,9 +465607,8 @@ function stopRuntime(runtime, suppressCallbacks) {
464674
465607
  notifyStreamObserversRuntimeStopped(runtime);
464675
465608
  disposeListenerModAdapter(runtime);
464676
465609
  rejectPendingExternalToolCalls(runtime, "Listener runtime stopped");
464677
- setMessageQueueAdder(null);
464678
465610
  runtime.intentionallyClosed = true;
464679
- clearRuntimeTimers(runtime);
465611
+ invalidateProcessServices(runtime);
464680
465612
  for (const conversationRuntime of runtime.conversationRuntimes.values()) {
464681
465613
  rejectPendingApprovalResolvers(conversationRuntime, "Listener runtime stopped");
464682
465614
  clearConversationRuntimeState(conversationRuntime);
@@ -464686,7 +465618,9 @@ function stopRuntime(runtime, suppressCallbacks) {
464686
465618
  }
464687
465619
  }
464688
465620
  runtime.conversationRuntimes.clear();
464689
- runtime.approvalRuntimeKeyByRequestId.clear();
465621
+ closeListenerRuntimeConnections(runtime, suppressCallbacks);
465622
+ runtime.processServicesReady = null;
465623
+ runtime.processServicesReadyGeneration = null;
464690
465624
  clearListenerWarmState(runtime);
464691
465625
  runtime.reminderStateByConversation.clear();
464692
465626
  runtime.skillSourcesByConversation.clear();
@@ -464694,30 +465628,6 @@ function stopRuntime(runtime, suppressCallbacks) {
464694
465628
  runtime.systemPromptRecompileByConversation.clear();
464695
465629
  runtime.queuedSystemPromptRecompileByConversation.clear();
464696
465630
  stopAllWorktreeWatchers(runtime);
464697
- if (!runtime.socket) {
464698
- if (runtime.streamSocket && (runtime.streamSocket.readyState === WebSocket6.OPEN || runtime.streamSocket.readyState === WebSocket6.CONNECTING)) {
464699
- runtime.streamSocket.close();
464700
- }
464701
- runtime.streamSocket = null;
464702
- runtime.streamTransport = null;
464703
- runtime.transport = null;
464704
- return;
464705
- }
464706
- const socket = runtime.socket;
464707
- runtime.socket = null;
464708
- runtime.transport = null;
464709
- const streamSocket = runtime.streamSocket;
464710
- runtime.streamSocket = null;
464711
- runtime.streamTransport = null;
464712
- if (suppressCallbacks) {
464713
- socket.removeAllListeners();
464714
- }
464715
- if (socket.readyState === WebSocket6.OPEN || socket.readyState === WebSocket6.CONNECTING) {
464716
- socket.close();
464717
- }
464718
- if (streamSocket && (streamSocket.readyState === WebSocket6.OPEN || streamSocket.readyState === WebSocket6.CONNECTING)) {
464719
- streamSocket.close();
464720
- }
464721
465631
  }
464722
465632
  async function startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, options3 = {}) {
464723
465633
  if (runtime !== getActiveRuntime() || runtime.intentionallyClosed) {
@@ -464726,103 +465636,88 @@ async function startConnectedListenerRuntime(runtime, transport, opts, processQu
464726
465636
  const shouldStartHeartbeat = options3.startHeartbeat !== false;
464727
465637
  const cronSchedulerDisabledByEnv = process.env.LETTA_DISABLE_CRON_SCHEDULER === "1";
464728
465638
  const shouldStartCronScheduler = options3.startCronScheduler !== false && !cronSchedulerDisabledByEnv;
464729
- runtime.transport = transport;
464730
- runtime.streamTransport = options3.streamTransport ?? null;
465639
+ markListenerConnectionInitialized(runtime, opts.connectionId);
464731
465640
  safeEmitWsEvent("recv", "lifecycle", {
464732
465641
  type: getListenerTransportKind(transport) === "websocket" ? "_ws_open" : "_local_open"
464733
465642
  });
464734
465643
  runtime.hasSuccessfulConnection = true;
464735
465644
  runtime.everConnected = true;
464736
465645
  opts.onConnected(opts.connectionId);
464737
- if (runtime.conversationRuntimes.size === 0) {
464738
- emitLoopStatusUpdate(transport, runtime);
464739
- } else {
464740
- for (const conversationRuntime of runtime.conversationRuntimes.values()) {
464741
- const scope = {
464742
- agent_id: conversationRuntime.agentId,
464743
- conversation_id: conversationRuntime.conversationId
464744
- };
464745
- emitDeviceStatusUpdate(transport, conversationRuntime, scope);
464746
- emitLoopStatusUpdate(transport, conversationRuntime, scope);
464747
- }
465646
+ emitInitialConnectionState(runtime, transport, opts.connectionId, options3);
465647
+ if (runtime.processServicesStarted) {
465648
+ return;
464748
465649
  }
464749
- runtime._unsubscribeSubagentState?.();
464750
- runtime._unsubscribeSubagentState = subscribe(() => {
464751
- if (runtime.conversationRuntimes.size === 0) {
464752
- emitSubagentStateIfOpen(runtime);
464753
- return;
465650
+ if (!await waitForProcessServicesSlot(runtime, opts.connectionId))
465651
+ return;
465652
+ const processServicesGeneration = runtime.processServicesGeneration + 1;
465653
+ runtime.processServicesGeneration = processServicesGeneration;
465654
+ const processServicesReady = (async () => {
465655
+ const processTransport = getOrCreateProcessTransport(runtime);
465656
+ installProcessEventRouting({
465657
+ runtime,
465658
+ processTransport,
465659
+ opts,
465660
+ processQueuedTurn
465661
+ });
465662
+ if (shouldStartHeartbeat) {
465663
+ runtime.lastPongAt = Date.now();
465664
+ runtime.heartbeatInterval = setInterval(() => {
465665
+ if (getListenerTransportKind(transport) === "websocket" && isListenerPongStale(runtime.lastPongAt, Date.now(), LISTENER_PONG_TIMEOUT_MS)) {
465666
+ trackListenerError4("listener_pong_timeout", new Error(`No relay pong within ${LISTENER_PONG_TIMEOUT_MS}ms; terminating half-open socket to force reconnect`), "listener_heartbeat");
465667
+ runtime.socket?.terminate();
465668
+ return;
465669
+ }
465670
+ safeTransportSend(transport, { type: "ping" }, "listener_ping_send_failed", "listener_heartbeat");
465671
+ }, LISTENER_HEARTBEAT_INTERVAL_MS);
464754
465672
  }
464755
- for (const conversationRuntime of runtime.conversationRuntimes.values()) {
464756
- emitSubagentStateIfOpen(runtime, {
464757
- agent_id: conversationRuntime.agentId,
464758
- conversation_id: conversationRuntime.conversationId
464759
- });
465673
+ if (shouldStartCronScheduler) {
465674
+ startScheduler(processTransport, opts, processQueuedTurn);
464760
465675
  }
464761
- });
464762
- runtime._unsubscribeSubagentStreamEvents?.();
464763
- runtime._unsubscribeSubagentStreamEvents = subscribeToStreamEvents((subagentId, event2) => {
464764
- if (!isListenerTransportOpen(transport))
464765
- return;
464766
- const subagent = getSubagents().find((entry) => entry.id === subagentId);
464767
- if (subagent?.silent === true) {
464768
- return;
465676
+ await (options3.wireChannelIngress ?? wireChannelIngress)(runtime, processTransport, opts, processQueuedTurn);
465677
+ if (runtime.processServicesGeneration === processServicesGeneration) {
465678
+ runtime.processServicesStarted = true;
464769
465679
  }
464770
- emitStreamDelta(transport, runtime, event2, subagent?.parentAgentId ? {
464771
- agent_id: subagent.parentAgentId,
464772
- conversation_id: subagent.parentConversationId ?? "default"
464773
- } : undefined, subagentId);
464774
- });
464775
- setMessageQueueAdder((queuedMessage) => {
464776
- const targetRuntime = queuedMessage.agentId && queuedMessage.conversationId ? getOrCreateScopedRuntime(runtime, queuedMessage.agentId, queuedMessage.conversationId) : findFallbackRuntime(runtime);
464777
- if (!targetRuntime?.queueRuntime) {
465680
+ })();
465681
+ runtime.processServicesReady = processServicesReady;
465682
+ runtime.processServicesReadyGeneration = processServicesGeneration;
465683
+ try {
465684
+ await processServicesReady;
465685
+ } catch (error54) {
465686
+ if (runtime.processServicesGeneration !== processServicesGeneration)
464778
465687
  return;
465688
+ clearProcessServices(runtime);
465689
+ throw error54;
465690
+ } finally {
465691
+ if (runtime.processServicesReady === processServicesReady) {
465692
+ runtime.processServicesReady = null;
465693
+ runtime.processServicesReadyGeneration = null;
464779
465694
  }
464780
- targetRuntime.queueRuntime.enqueue({
464781
- kind: "task_notification",
464782
- source: "task_notification",
464783
- text: queuedMessage.text,
464784
- agentId: queuedMessage.agentId ?? targetRuntime.agentId ?? undefined,
464785
- conversationId: queuedMessage.conversationId ?? targetRuntime.conversationId
464786
- });
464787
- scheduleQueuePump(targetRuntime, transport, opts, processQueuedTurn);
464788
- });
464789
- if (shouldStartHeartbeat) {
464790
- runtime.lastPongAt = Date.now();
464791
- runtime.heartbeatInterval = setInterval(() => {
464792
- if (getListenerTransportKind(transport) === "websocket" && isListenerPongStale(runtime.lastPongAt, Date.now(), LISTENER_PONG_TIMEOUT_MS)) {
464793
- trackListenerError4("listener_pong_timeout", new Error(`No relay pong within ${LISTENER_PONG_TIMEOUT_MS}ms; terminating half-open socket to force reconnect`), "listener_heartbeat");
464794
- runtime.socket?.terminate();
464795
- return;
464796
- }
464797
- safeTransportSend(transport, { type: "ping" }, "listener_ping_send_failed", "listener_heartbeat");
464798
- }, LISTENER_HEARTBEAT_INTERVAL_MS);
464799
465695
  }
464800
- if (shouldStartCronScheduler) {
464801
- startScheduler(transport, opts, processQueuedTurn);
464802
- }
464803
- await wireChannelIngress(runtime, transport, opts, processQueuedTurn);
464804
465696
  }
464805
465697
  async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464806
465698
  if (runtime !== getActiveRuntime() || runtime.intentionallyClosed) {
464807
465699
  return;
464808
465700
  }
464809
465701
  const streamSocket = options3.streamSocket ?? null;
465702
+ const connection = openListenerConnection({
465703
+ runtime,
465704
+ connectionId: opts.connectionId,
465705
+ writer: socket,
465706
+ streamWriter: streamSocket,
465707
+ options: opts
465708
+ });
464810
465709
  const fileCommandSession = createFileCommandSession({
464811
465710
  socket,
464812
465711
  safeSocketSend,
464813
465712
  runDetachedListenerTask
464814
465713
  });
464815
- runtime.socket = socket;
464816
- runtime.streamSocket = streamSocket;
464817
465714
  installExternalToolBridge(runtime);
464818
465715
  const transport = socket;
464819
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
464820
- const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
464821
- await handleIncomingMessage(queuedTurn, transport, scopedRuntime, opts.onStatusChange, opts.connectionId, dequeuedBatch.batchId);
464822
- };
465716
+ const processQueuedTurn = createConnectionTurnProcessor(runtime);
464823
465717
  const handleMessage = createListenerMessageHandler({
464824
465718
  runtime,
464825
465719
  socket,
465720
+ connectionId: opts.connectionId,
464826
465721
  opts,
464827
465722
  processQueuedTurn,
464828
465723
  fileCommandSession,
@@ -464841,6 +465736,9 @@ async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464841
465736
  socket.on("message", (data) => {
464842
465737
  (async () => {
464843
465738
  await options3.startupReady;
465739
+ if (connection.cancellation.signal.aborted || runtime.connections.get(opts.connectionId) !== connection) {
465740
+ return;
465741
+ }
464844
465742
  await handleMessage(data);
464845
465743
  })().catch((error54) => {
464846
465744
  trackListenerError4("listener_message_handler_failed", error54, "listener_message_handler");
@@ -464858,12 +465756,7 @@ async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464858
465756
  reason: reasonText
464859
465757
  });
464860
465758
  fileCommandSession.dispose();
464861
- stopScheduler();
464862
- getChannelRegistry()?.pause();
464863
- stopRuntime(runtime, true);
464864
- if (getActiveRuntime() === runtime) {
464865
- setActiveRuntime(null);
464866
- }
465759
+ cleanupListenerConnection(runtime, opts.connectionId);
464867
465760
  opts.onDisconnected();
464868
465761
  });
464869
465762
  socket.on("error", (error54) => {
@@ -464891,11 +465784,15 @@ async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464891
465784
  });
464892
465785
  }
464893
465786
  await options3.startupReady;
465787
+ if (connection.cancellation.signal.aborted || runtime.connections.get(opts.connectionId) !== connection) {
465788
+ return;
465789
+ }
464894
465790
  const streamTransport = streamSocket?.readyState === WebSocket6.OPEN ? streamSocket : null;
464895
465791
  await startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, {
464896
465792
  startHeartbeat: options3.startHeartbeat ?? false,
464897
465793
  startCronScheduler: options3.startCronScheduler ?? true,
464898
- streamTransport
465794
+ streamTransport,
465795
+ emitInitialState: false
464899
465796
  });
464900
465797
  }
464901
465798
  async function startListenerClient(opts) {
@@ -464929,10 +465826,18 @@ async function startLocalChannelListener(opts) {
464929
465826
  await reloadListenerModAdapter(runtime);
464930
465827
  await loadTools();
464931
465828
  const transport = new LocalListenerTransport;
464932
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
464933
- const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
464934
- await handleIncomingMessage(queuedTurn, transport, scopedRuntime, opts.onStatusChange, opts.connectionId, dequeuedBatch.batchId);
465829
+ const connectionOptions = {
465830
+ ...opts,
465831
+ wsUrl: "local://listener",
465832
+ onDisconnected: () => {}
464935
465833
  };
465834
+ openListenerConnection({
465835
+ runtime,
465836
+ connectionId: opts.connectionId,
465837
+ writer: transport,
465838
+ options: connectionOptions
465839
+ });
465840
+ const processQueuedTurn = createConnectionTurnProcessor(runtime);
464936
465841
  await startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, { startHeartbeat: false, startCronScheduler: true });
464937
465842
  } catch (error54) {
464938
465843
  stopRuntime(runtime, true);
@@ -465010,15 +465915,19 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465010
465915
  runtime.socket = socket;
465011
465916
  runtime.streamSocket = streamSocket;
465012
465917
  const transport = socket;
465013
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
465014
- const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
465015
- await handleIncomingMessage(queuedTurn, transport, scopedRuntime, opts.onStatusChange, opts.connectionId, dequeuedBatch.batchId);
465016
- };
465918
+ const processQueuedTurn = createConnectionTurnProcessor(runtime);
465017
465919
  socket.on("open", async () => {
465018
465920
  let streamTransport = null;
465019
465921
  if (streamSocket) {
465020
465922
  streamTransport = await waitForStreamSocketOpen(streamSocket, runtime);
465021
465923
  }
465924
+ openListenerConnection({
465925
+ runtime,
465926
+ connectionId: opts.connectionId,
465927
+ writer: socket,
465928
+ streamWriter: streamTransport,
465929
+ options: opts
465930
+ });
465022
465931
  await startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, {
465023
465932
  startHeartbeat: true,
465024
465933
  startCronScheduler: true,
@@ -465028,6 +465937,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465028
465937
  socket.on("message", createListenerMessageHandler({
465029
465938
  runtime,
465030
465939
  socket,
465940
+ connectionId: opts.connectionId,
465031
465941
  opts,
465032
465942
  processQueuedTurn,
465033
465943
  fileCommandSession,
@@ -465053,12 +465963,11 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465053
465963
  reason: reason.toString()
465054
465964
  });
465055
465965
  fileCommandSession.dispose();
465056
- stopScheduler();
465966
+ invalidateProcessServices(runtime);
465057
465967
  const channelRegistry = getChannelRegistry();
465058
465968
  if (channelRegistry) {
465059
465969
  channelRegistry.pause();
465060
465970
  }
465061
- setMessageQueueAdder(null);
465062
465971
  for (const conversationRuntime of runtime.conversationRuntimes.values()) {
465063
465972
  conversationRuntime.queuedMessagesByItemId.clear();
465064
465973
  if (conversationRuntime.queueRuntime) {
@@ -465068,12 +465977,8 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465068
465977
  if (isDebugEnabled()) {
465069
465978
  console.log(`[Listen] WebSocket disconnected (code: ${code2}, reason: ${reason.toString()})`);
465070
465979
  }
465071
- clearRuntimeTimers(runtime);
465980
+ suspendListenerConnection(runtime, opts.connectionId);
465072
465981
  killAllTerminals();
465073
- runtime._unsubscribeSubagentState?.();
465074
- runtime._unsubscribeSubagentState = undefined;
465075
- runtime._unsubscribeSubagentStreamEvents?.();
465076
- runtime._unsubscribeSubagentStreamEvents = undefined;
465077
465982
  clearListenerWarmState(runtime);
465078
465983
  if (streamSocket) {
465079
465984
  streamSocket.removeAllListeners();
@@ -465150,7 +466055,6 @@ function stopListenerClient() {
465150
466055
  stopRuntime(runtime, true);
465151
466056
  }
465152
466057
  var init_lifecycle = __esm(async () => {
465153
- init_subagent_state();
465154
466058
  init_commands();
465155
466059
  init_progress_builder();
465156
466060
  init_registry();
@@ -465160,10 +466064,10 @@ var init_lifecycle = __esm(async () => {
465160
466064
  init_telemetry();
465161
466065
  init_error_reporting();
465162
466066
  init_debug();
465163
- init_message_queue_bridge();
465164
466067
  init_terminal_handler();
465165
466068
  init_auth();
465166
466069
  init_channel_turn_session();
466070
+ init_connection();
465167
466071
  init_constants3();
465168
466072
  init_cwd();
465169
466073
  init_permission_mode();
@@ -465174,18 +466078,20 @@ var init_lifecycle = __esm(async () => {
465174
466078
  init_manager4(),
465175
466079
  init_approval(),
465176
466080
  init_commands2(),
465177
- init_channels2(),
466081
+ init_channel_registry_events(),
465178
466082
  init_model_toolset(),
466083
+ init_connection_lifecycle(),
466084
+ init_connection_state_sync(),
465179
466085
  init_control_inputs(),
465180
466086
  init_conversation_runtime(),
465181
466087
  init_external_tools(),
465182
466088
  init_file_commands(),
465183
466089
  init_message_router(),
465184
466090
  init_mod_adapter2(),
466091
+ init_process_services(),
465185
466092
  init_protocol_outbound(),
465186
466093
  init_queue(),
465187
466094
  init_recovery(),
465188
- init_turn(),
465189
466095
  init_turn_events(),
465190
466096
  init_warmup(),
465191
466097
  init_worktree_watcher()
@@ -465273,6 +466179,60 @@ function createLegacyTestRuntime() {
465273
466179
  listener.sessionId = value;
465274
466180
  }
465275
466181
  },
466182
+ nextConnectionOrdinal: {
466183
+ get: () => listener.nextConnectionOrdinal,
466184
+ set: (value) => {
466185
+ listener.nextConnectionOrdinal = value;
466186
+ }
466187
+ },
466188
+ connections: {
466189
+ get: () => listener.connections,
466190
+ set: (value) => {
466191
+ listener.connections = value;
466192
+ }
466193
+ },
466194
+ connectionIdsByRuntimeKey: {
466195
+ get: () => listener.connectionIdsByRuntimeKey,
466196
+ set: (value) => {
466197
+ listener.connectionIdsByRuntimeKey = value;
466198
+ }
466199
+ },
466200
+ processTransport: {
466201
+ get: () => listener.processTransport,
466202
+ set: (value) => {
466203
+ listener.processTransport = value;
466204
+ }
466205
+ },
466206
+ processServicesStarted: {
466207
+ get: () => listener.processServicesStarted,
466208
+ set: (value) => {
466209
+ listener.processServicesStarted = value;
466210
+ }
466211
+ },
466212
+ processServicesGeneration: {
466213
+ get: () => listener.processServicesGeneration,
466214
+ set: (value) => {
466215
+ listener.processServicesGeneration = value;
466216
+ }
466217
+ },
466218
+ processServicesReady: {
466219
+ get: () => listener.processServicesReady,
466220
+ set: (value) => {
466221
+ listener.processServicesReady = value;
466222
+ }
466223
+ },
466224
+ processServicesReadyGeneration: {
466225
+ get: () => listener.processServicesReadyGeneration,
466226
+ set: (value) => {
466227
+ listener.processServicesReadyGeneration = value;
466228
+ }
466229
+ },
466230
+ pendingExternalToolCalls: {
466231
+ get: () => listener.pendingExternalToolCalls,
466232
+ set: (value) => {
466233
+ listener.pendingExternalToolCalls = value;
466234
+ }
466235
+ },
465276
466236
  eventSeqCounter: {
465277
466237
  get: () => listener.eventSeqCounter,
465278
466238
  set: (value) => {
@@ -465345,12 +466305,6 @@ function createLegacyTestRuntime() {
465345
466305
  listener.conversationRuntimes = value;
465346
466306
  }
465347
466307
  },
465348
- approvalRuntimeKeyByRequestId: {
465349
- get: () => listener.approvalRuntimeKeyByRequestId,
465350
- set: (value) => {
465351
- listener.approvalRuntimeKeyByRequestId = value;
465352
- }
465353
- },
465354
466308
  memfsSyncedAgents: {
465355
466309
  get: () => listener.memfsSyncedAgents,
465356
466310
  set: (value) => {
@@ -465423,6 +466377,7 @@ var init_client7 = __esm(async () => {
465423
466377
  await __promiseAll([
465424
466378
  init_approval(),
465425
466379
  init_agents_conversations(),
466380
+ init_channel_registry_events(),
465426
466381
  init_channels2(),
465427
466382
  init_cron3(),
465428
466383
  init_memory6(),
@@ -465499,7 +466454,7 @@ var init_client7 = __esm(async () => {
465499
466454
  handleListMemoryCommand: (parsed, socket, overrides) => handleListMemoryCommand(parsed, socket, safeSocketSend, overrides),
465500
466455
  isDetachedChannelsCommand,
465501
466456
  handleChannelsProtocolCommand: (parsed, socket, runtime, opts, processQueuedTurn) => handleChannelsProtocolCommand(parsed, socket, runtime, opts, processQueuedTurn, runDetachedListenerTask, wireChannelIngress, safeSocketSend),
465502
- handleChannelRegistryEvent: (event2, socket, runtime) => handleChannelRegistryEvent(event2, socket, runtime, safeSocketSend),
466457
+ handleChannelRegistryEvent: (event2, socket, runtime) => handleChannelRegistryEvent(event2, socket, runtime),
465503
466458
  handleAgentConversationManagementCommand: (parsed, socket) => handleAgentConversationManagementCommand(parsed, socket, safeSocketSend),
465504
466459
  handleAgentConversationManagementProtocolCommand: (parsed, socket) => handleAgentConversationManagementProtocolCommand(parsed, {
465505
466460
  socket,
@@ -465508,6 +466463,7 @@ var init_client7 = __esm(async () => {
465508
466463
  }),
465509
466464
  handleRuntimeStartCommand: (parsed, socket, runtime) => handleRuntimeStartCommand(parsed, {
465510
466465
  socket,
466466
+ connectionId: runtime.connectionId ?? "test-connection",
465511
466467
  runtime,
465512
466468
  safeSocketSend,
465513
466469
  runDetachedListenerTask,
@@ -465519,6 +466475,7 @@ var init_client7 = __esm(async () => {
465519
466475
  }),
465520
466476
  handleRuntimeStartProtocolCommand: (parsed, socket, runtime) => handleRuntimeStartProtocolCommand(parsed, {
465521
466477
  socket,
466478
+ connectionId: runtime.connectionId ?? "test-connection",
465522
466479
  runtime,
465523
466480
  safeSocketSend,
465524
466481
  runDetachedListenerTask,
@@ -466121,7 +467078,7 @@ var init_listen = __esm(async () => {
466121
467078
  import { randomUUID as randomUUID26 } from "node:crypto";
466122
467079
  import {
466123
467080
  copyFileSync as copyFileSync3,
466124
- existsSync as existsSync52,
467081
+ existsSync as existsSync53,
466125
467082
  mkdirSync as mkdirSync38,
466126
467083
  readdirSync as readdirSync20,
466127
467084
  readFileSync as readFileSync34,
@@ -466129,7 +467086,7 @@ import {
466129
467086
  } from "node:fs";
466130
467087
  import { join as join67 } from "node:path";
466131
467088
  function readJsonl(path41) {
466132
- if (!existsSync52(path41))
467089
+ if (!existsSync53(path41))
466133
467090
  return [];
466134
467091
  return readFileSync34(path41, "utf8").split(`
466135
467092
  `).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
@@ -466417,13 +467374,13 @@ function migrateLocalBackendTranscripts(input) {
466417
467374
  skipped: [],
466418
467375
  dryRun: input.dryRun === true
466419
467376
  };
466420
- if (!existsSync52(conversationsDir))
467377
+ if (!existsSync53(conversationsDir))
466421
467378
  return result;
466422
467379
  for (const name of readdirSync20(conversationsDir)) {
466423
467380
  const conversationDir = join67(conversationsDir, name);
466424
467381
  const messagesPath = join67(conversationDir, "messages.jsonl");
466425
467382
  const manifestPath = join67(conversationDir, "manifest.json");
466426
- const hasManifest = existsSync52(manifestPath);
467383
+ const hasManifest = existsSync53(manifestPath);
466427
467384
  const existingManifest = hasManifest ? (() => {
466428
467385
  try {
466429
467386
  return JSON.parse(readFileSync34(manifestPath, "utf8"));
@@ -466467,7 +467424,7 @@ function migrateLocalBackendTranscripts(input) {
466467
467424
  copyFileSync3(messagesPath, backupPath);
466468
467425
  const conversationPath = join67(conversationDir, "conversation.json");
466469
467426
  let conversation;
466470
- if (existsSync52(conversationPath)) {
467427
+ if (existsSync53(conversationPath)) {
466471
467428
  try {
466472
467429
  conversation = JSON.parse(readFileSync34(conversationPath, "utf8"));
466473
467430
  } catch {
@@ -466661,7 +467618,7 @@ var init_memory_tokens = __esm(() => {
466661
467618
  });
466662
467619
 
466663
467620
  // src/cli/subcommands/memory.ts
466664
- import { cpSync, existsSync as existsSync53, mkdirSync as mkdirSync39, rmSync as rmSync12, statSync as statSync17 } from "node:fs";
467621
+ import { cpSync, existsSync as existsSync54, mkdirSync as mkdirSync39, rmSync as rmSync12, statSync as statSync17 } from "node:fs";
466665
467622
  import { readdir as readdir13 } from "node:fs/promises";
466666
467623
  import { dirname as dirname30, join as join68 } from "node:path";
466667
467624
  import { parseArgs as parseArgs10 } from "node:util";
@@ -466723,7 +467680,7 @@ function formatBackupTimestamp(date6 = new Date) {
466723
467680
  }
466724
467681
  async function listBackups(agentId) {
466725
467682
  const agentRoot = getAgentRoot(agentId);
466726
- if (!existsSync53(agentRoot)) {
467683
+ if (!existsSync54(agentRoot)) {
466727
467684
  return [];
466728
467685
  }
466729
467686
  const entries = await readdir13(agentRoot, { withFileTypes: true });
@@ -466826,14 +467783,14 @@ async function runMemorySubcommand(argv) {
466826
467783
  }
466827
467784
  if (action3 === "backup") {
466828
467785
  const root2 = getMemoryRoot(agentId);
466829
- if (!existsSync53(root2)) {
467786
+ if (!existsSync54(root2)) {
466830
467787
  console.error(`Memory directory not found for agent ${agentId}.`);
466831
467788
  return 1;
466832
467789
  }
466833
467790
  const agentRoot = getAgentRoot(agentId);
466834
467791
  const backupName = `memory-backup-${formatBackupTimestamp()}`;
466835
467792
  const backupPath = join68(agentRoot, backupName);
466836
- if (existsSync53(backupPath)) {
467793
+ if (existsSync54(backupPath)) {
466837
467794
  console.error(`Backup already exists at ${backupPath}`);
466838
467795
  return 1;
466839
467796
  }
@@ -466857,7 +467814,7 @@ async function runMemorySubcommand(argv) {
466857
467814
  return 1;
466858
467815
  }
466859
467816
  const backupPath = resolveBackupPath(agentId, from);
466860
- if (!existsSync53(backupPath)) {
467817
+ if (!existsSync54(backupPath)) {
466861
467818
  console.error(`Backup not found: ${backupPath}`);
466862
467819
  return 1;
466863
467820
  }
@@ -466879,11 +467836,11 @@ async function runMemorySubcommand(argv) {
466879
467836
  return 1;
466880
467837
  }
466881
467838
  const root2 = getMemoryRoot(agentId);
466882
- if (!existsSync53(root2)) {
467839
+ if (!existsSync54(root2)) {
466883
467840
  console.error(`Memory directory not found for agent ${agentId}.`);
466884
467841
  return 1;
466885
467842
  }
466886
- if (existsSync53(out)) {
467843
+ if (existsSync54(out)) {
466887
467844
  const stat15 = statSync17(out);
466888
467845
  if (stat15.isDirectory()) {
466889
467846
  const contents = await readdir13(out);
@@ -466931,10 +467888,10 @@ var init_memory7 = __esm(() => {
466931
467888
  });
466932
467889
 
466933
467890
  // src/backend/local/transcript-search.ts
466934
- import { existsSync as existsSync54, readdirSync as readdirSync21, readFileSync as readFileSync35, statSync as statSync18 } from "node:fs";
467891
+ import { existsSync as existsSync55, readdirSync as readdirSync21, readFileSync as readFileSync35, statSync as statSync18 } from "node:fs";
466935
467892
  import { join as join69 } from "node:path";
466936
467893
  function readJsonFile3(path41) {
466937
- if (!existsSync54(path41))
467894
+ if (!existsSync55(path41))
466938
467895
  return;
466939
467896
  try {
466940
467897
  return JSON.parse(readFileSync35(path41, "utf8"));
@@ -466943,7 +467900,7 @@ function readJsonFile3(path41) {
466943
467900
  }
466944
467901
  }
466945
467902
  function readJsonlFile2(path41) {
466946
- if (!existsSync54(path41))
467903
+ if (!existsSync55(path41))
466947
467904
  return [];
466948
467905
  try {
466949
467906
  return readFileSync35(path41, "utf8").split(`
@@ -467179,7 +468136,7 @@ function collectConversationMessages(input) {
467179
468136
  }
467180
468137
  function conversationDirectories(storageDir) {
467181
468138
  const conversationsDir = join69(storageDir, "conversations");
467182
- if (!existsSync54(conversationsDir))
468139
+ if (!existsSync55(conversationsDir))
467183
468140
  return [];
467184
468141
  try {
467185
468142
  return readdirSync21(conversationsDir).map((entry) => join69(conversationsDir, entry)).filter((entryPath) => statSync18(entryPath).isDirectory());
@@ -467648,7 +468605,7 @@ import {
467648
468605
  } from "node:child_process";
467649
468606
  import {
467650
468607
  copyFileSync as copyFileSync4,
467651
- existsSync as existsSync55,
468608
+ existsSync as existsSync56,
467652
468609
  lstatSync as lstatSync3,
467653
468610
  mkdirSync as mkdirSync40,
467654
468611
  mkdtempSync as mkdtempSync3,
@@ -467857,7 +468814,7 @@ function copyDependencyDirectoryFiltered(params) {
467857
468814
  return copied;
467858
468815
  }
467859
468816
  function copyDependencyNodeModules(params) {
467860
- if (!existsSync55(params.sourceNodeModulesDirectory))
468817
+ if (!existsSync56(params.sourceNodeModulesDirectory))
467861
468818
  return;
467862
468819
  copyDependencyDirectoryFiltered({
467863
468820
  installedPackageName: params.installedPackageName,
@@ -467867,7 +468824,7 @@ function copyDependencyNodeModules(params) {
467867
468824
  }
467868
468825
  function copyPackageInternalNodeModules(params) {
467869
468826
  const sourceNodeModulesDirectory = path41.join(params.packageDirectory, "node_modules");
467870
- if (!existsSync55(sourceNodeModulesDirectory))
468827
+ if (!existsSync56(sourceNodeModulesDirectory))
467871
468828
  return;
467872
468829
  copyDependencyDirectoryFiltered({
467873
468830
  sourceRoot: sourceNodeModulesDirectory,
@@ -467889,12 +468846,12 @@ function removeIfExists(targetPath) {
467889
468846
  }
467890
468847
  function restoreDestination(params) {
467891
468848
  rmSync13(params.destinationRoot, { force: true, recursive: true });
467892
- if (params.backupRoot && existsSync55(params.backupRoot)) {
468849
+ if (params.backupRoot && existsSync56(params.backupRoot)) {
467893
468850
  renameSync6(params.backupRoot, params.destinationRoot);
467894
468851
  }
467895
468852
  }
467896
468853
  function restoreDestinationIfNeeded(params) {
467897
- if (!params.backupRoot || !existsSync55(params.backupRoot)) {
468854
+ if (!params.backupRoot || !existsSync56(params.backupRoot)) {
467898
468855
  rmSync13(params.destinationRoot, { force: true, recursive: true });
467899
468856
  return;
467900
468857
  }
@@ -467935,7 +468892,7 @@ function installPreparedManagedModPackage(params) {
467935
468892
  targetPackageRoot: stagingRoot
467936
468893
  });
467937
468894
  }
467938
- if (existsSync55(destinationRoot)) {
468895
+ if (existsSync56(destinationRoot)) {
467939
468896
  backupRoot = makeSiblingTempDirectory(destinationRoot, "backup");
467940
468897
  rmSync13(backupRoot, { force: true, recursive: true });
467941
468898
  renameSync6(destinationRoot, backupRoot);
@@ -468218,7 +469175,7 @@ function hasRuntimeDependencies(packageJson) {
468218
469175
  }
468219
469176
  function readPackageJsonIfExists(packageDirectory) {
468220
469177
  const packageJsonPath = path41.join(packageDirectory, "package.json");
468221
- if (!existsSync55(packageJsonPath))
469178
+ if (!existsSync56(packageJsonPath))
468222
469179
  return null;
468223
469180
  return readPackageJson(packageJsonPath);
468224
469181
  }
@@ -468232,7 +469189,7 @@ function isRegularModFile(filePath) {
468232
469189
  }
468233
469190
  function inferCompatibilityModEntries(packageDirectory) {
468234
469191
  const modsDirectory = path41.join(packageDirectory, "mods");
468235
- if (existsSync55(modsDirectory)) {
469192
+ if (existsSync56(modsDirectory)) {
468236
469193
  const stats = lstatSync3(modsDirectory);
468237
469194
  if (stats.isSymbolicLink()) {
468238
469195
  throw new Error(`Package mods directory must not be a symlink: mods`);
@@ -468507,7 +469464,7 @@ var init_package_installer = __esm(() => {
468507
469464
  // src/mods/package-scaffolder.ts
468508
469465
  import {
468509
469466
  copyFileSync as copyFileSync5,
468510
- existsSync as existsSync56,
469467
+ existsSync as existsSync57,
468511
469468
  lstatSync as lstatSync4,
468512
469469
  mkdirSync as mkdirSync41,
468513
469470
  rmSync as rmSync14,
@@ -468600,7 +469557,7 @@ function scaffoldLocalModPackage(options3) {
468600
469557
  const sourceFile = readSourceFile(options3.sourceFile);
468601
469558
  const packageName = assertValidPackageName(options3.packageName);
468602
469559
  const outputDirectory = path42.resolve(options3.outputDirectory ?? defaultOutputDirectory(sourceFile, packageName));
468603
- if (existsSync56(outputDirectory)) {
469560
+ if (existsSync57(outputDirectory)) {
468604
469561
  throw new Error(`Output directory already exists: ${outputDirectory}`);
468605
469562
  }
468606
469563
  const modFileName = path42.basename(sourceFile);
@@ -469311,7 +470268,7 @@ async function runTurnViaListenerRuntime(params) {
469311
470268
  const listener = await ensureListenerRuntime(params.onLog);
469312
470269
  const scopedRuntime = getOrCreateScopedRuntime(listener, params.agentId, params.conversationId);
469313
470270
  getOrCreateConversationPermissionModeStateRef(listener, params.agentId, params.conversationId).mode = "unrestricted";
469314
- const socket = listener.socket ?? listener.transport ?? bridgeTransport;
470271
+ const socket = getOrCreateProcessTransport(listener);
469315
470272
  const dispatchOptions = {
469316
470273
  connectionId: listener.connectionId ?? "openai-api",
469317
470274
  wsUrl: "",
@@ -469323,10 +470280,7 @@ async function runTurnViaListenerRuntime(params) {
469323
470280
  params.onLog?.(`OpenAI-compat turn error: ${error54.message}`);
469324
470281
  }
469325
470282
  };
469326
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
469327
- const queuedScope = getOrCreateScopedRuntime(listener, queuedTurn.agentId, queuedTurn.conversationId);
469328
- await handleIncomingMessage(queuedTurn, socket, queuedScope, undefined, dispatchOptions.connectionId, dequeuedBatch.batchId);
469329
- };
470283
+ const processQueuedTurn = createConnectionTurnProcessor(listener);
469330
470284
  return await new Promise((resolve32) => {
469331
470285
  let text2 = "";
469332
470286
  let usage = {
@@ -469437,7 +470391,7 @@ async function runTurnViaListenerRuntime(params) {
469437
470391
  socket,
469438
470392
  options: dispatchOptions,
469439
470393
  processQueuedTurn,
469440
- processIncomingMessage: handleIncomingMessage,
470394
+ processIncomingMessage: (incoming2, incomingSocket, incomingRuntime, onStatusChange, _connectionId, batchId) => handleIncomingMessage(incoming2, incomingSocket, incomingRuntime, onStatusChange, undefined, batchId),
469441
470395
  trackListenerError: (errorType, error54) => {
469442
470396
  params.onLog?.(`OpenAI-compat listener error (${errorType}): ${error54 instanceof Error ? error54.message : String(error54)}`);
469443
470397
  }
@@ -469447,14 +470401,15 @@ async function runTurnViaListenerRuntime(params) {
469447
470401
  }
469448
470402
  });
469449
470403
  }
469450
- var runTurnImpl, OPENAI_TURN_TIMEOUT_MS, bridgeTransport, bridgeRuntimeStart = null, bridgeOwnedRuntime = null;
470404
+ var runTurnImpl, OPENAI_TURN_TIMEOUT_MS, bridgeRuntimeStart = null, bridgeOwnedRuntime = null;
469451
470405
  var init_app_server_openai_turn = __esm(async () => {
469452
470406
  init_settings_manager();
470407
+ init_connection();
469453
470408
  init_permission_mode();
469454
470409
  init_runtime6();
469455
- init_transport();
469456
470410
  init_turn_observers();
469457
470411
  await __promiseAll([
470412
+ init_connection_lifecycle(),
469458
470413
  init_conversation_runtime(),
469459
470414
  init_inbound_dispatch(),
469460
470415
  init_lifecycle(),
@@ -469462,7 +470417,6 @@ var init_app_server_openai_turn = __esm(async () => {
469462
470417
  ]);
469463
470418
  runTurnImpl = runTurnViaListenerRuntime;
469464
470419
  OPENAI_TURN_TIMEOUT_MS = 15 * 60 * 1000;
469465
- bridgeTransport = new LocalListenerTransport;
469466
470420
  });
469467
470421
 
469468
470422
  // src/websocket/app-server-openai.ts
@@ -469470,7 +470424,7 @@ import { randomUUID as randomUUID28 } from "node:crypto";
469470
470424
  function isOpenAiCompatPath(pathname) {
469471
470425
  return pathname === MODELS_PATH || pathname === CHAT_COMPLETIONS_PATH;
469472
470426
  }
469473
- function sendJson2(response, statusCode2, body3) {
470427
+ function sendJson(response, statusCode2, body3) {
469474
470428
  const payload = JSON.stringify(body3);
469475
470429
  response.writeHead(statusCode2, {
469476
470430
  "content-type": "application/json",
@@ -469479,7 +470433,7 @@ function sendJson2(response, statusCode2, body3) {
469479
470433
  response.end(payload);
469480
470434
  }
469481
470435
  function sendOpenAiError(response, statusCode2, message, type3, code2 = null) {
469482
- sendJson2(response, statusCode2, {
470436
+ sendJson(response, statusCode2, {
469483
470437
  error: { message, type: type3, param: null, code: code2 }
469484
470438
  });
469485
470439
  }
@@ -469608,7 +470562,7 @@ async function handleListModels(response) {
469608
470562
  created: toModelCreatedTimestamp(agent2.created_at),
469609
470563
  owned_by: "letta"
469610
470564
  }));
469611
- sendJson2(response, 200, { object: "list", data });
470565
+ sendJson(response, 200, { object: "list", data });
469612
470566
  }
469613
470567
  async function resolveAgentForModel(model) {
469614
470568
  const agents = await listAgentEntries();
@@ -469886,7 +470840,7 @@ async function handleChatCompletions(request, response, options3) {
469886
470840
  sendOpenAiError(response, 500, streamError, "server_error");
469887
470841
  return;
469888
470842
  }
469889
- sendJson2(response, 200, {
470843
+ sendJson(response, 200, {
469890
470844
  id: completionId,
469891
470845
  object: "chat.completion",
469892
470846
  created,
@@ -469956,10 +470910,9 @@ function getRequiredAddressInfo(server2) {
469956
470910
  }
469957
470911
  return address;
469958
470912
  }
469959
- function getChannelUrl(baseUrl, path43, channel) {
470913
+ function getWebSocketUrl(baseUrl, path43) {
469960
470914
  const url2 = new URL(baseUrl);
469961
470915
  url2.pathname = path43;
469962
- url2.searchParams.set("channel", channel);
469963
470916
  return url2.toString();
469964
470917
  }
469965
470918
  function closeSocket(socket, code2 = 1001, reason = "closing") {
@@ -469984,35 +470937,6 @@ Content-Length: 0\r
469984
470937
  function getRequestUrl(request, host) {
469985
470938
  return new URL(request.url ?? "/", `http://${request.headers.host ?? host}`);
469986
470939
  }
469987
- function getRequestChannel(url2) {
469988
- const channel = url2.searchParams.get("channel");
469989
- if (channel === null || channel === "" || channel === "control") {
469990
- return "control";
469991
- }
469992
- if (channel === "stream") {
469993
- return "stream";
469994
- }
469995
- return null;
469996
- }
469997
- function attachStreamSocket(activeSession, socket) {
469998
- if (activeSession.streamSocket) {
469999
- closeSocket(socket, 1008, "stream channel already connected");
470000
- return;
470001
- }
470002
- activeSession.streamSocket = socket;
470003
- activeSession.runtime.streamSocket = socket;
470004
- activeSession.runtime.streamTransport = socket;
470005
- socket.on("close", () => {
470006
- if (activeSession.streamSocket === socket) {
470007
- activeSession.streamSocket = null;
470008
- }
470009
- if (activeSession.runtime.streamSocket === socket) {
470010
- activeSession.runtime.streamSocket = null;
470011
- activeSession.runtime.streamTransport = null;
470012
- terminateSocket(activeSession.controlSocket);
470013
- }
470014
- });
470015
- }
470016
470940
  function parseAppServerListenUrl(listen = DEFAULT_LISTEN_URL) {
470017
470941
  let url2;
470018
470942
  try {
@@ -470033,46 +470957,6 @@ function parseAppServerListenUrl(listen = DEFAULT_LISTEN_URL) {
470033
470957
  const path43 = url2.pathname === "/" ? DEFAULT_WS_PATH : url2.pathname;
470034
470958
  return { host: normalizeListenHost(url2.hostname), port, path: path43 };
470035
470959
  }
470036
- async function startControlSession(params) {
470037
- const existingRuntime = getActiveRuntime();
470038
- if (existingRuntime) {
470039
- stopRuntime(existingRuntime, true);
470040
- setActiveRuntime(null);
470041
- }
470042
- const runtime = createRuntime();
470043
- runtime.onWsEvent = undefined;
470044
- runtime.connectionId = `app-server-${crypto.randomUUID()}`;
470045
- runtime.connectionName = params.connectionName;
470046
- setActiveRuntime(runtime);
470047
- telemetry.setSurface(getListenerTelemetrySurface());
470048
- telemetry.init();
470049
- const startupReady = (async () => {
470050
- await reloadListenerModAdapter(runtime);
470051
- await loadTools();
470052
- })();
470053
- const activeSession = {
470054
- runtime,
470055
- controlSocket: params.socket,
470056
- streamSocket: params.streamSocket
470057
- };
470058
- params.onSessionCreated(activeSession);
470059
- await attachOpenListenerSocket(runtime, params.socket, {
470060
- connectionId: runtime.connectionId ?? "app-server",
470061
- wsUrl: params.serverUrl,
470062
- supportsSplitStatusChannels: true,
470063
- deviceId: settingsManager.getOrCreateDeviceId(),
470064
- connectionName: params.connectionName,
470065
- onConnected: () => {},
470066
- onDisconnected: params.onSessionClosed,
470067
- onError: () => {}
470068
- }, {
470069
- streamSocket: params.streamSocket,
470070
- startHeartbeat: false,
470071
- startCronScheduler: true,
470072
- startupReady
470073
- });
470074
- return activeSession;
470075
- }
470076
470960
  async function startAppServer(options3 = {}) {
470077
470961
  await settingsManager.initialize();
470078
470962
  const listen = parseAppServerListenUrl(options3.listen);
@@ -470081,72 +470965,60 @@ async function startAppServer(options3 = {}) {
470081
470965
  throw new Error(`refusing to start non-loopback websocket listener ${listen.host}:${listen.port} without auth; configure \`--ws-auth capability-token\` or \`--ws-auth signed-bearer-token\``);
470082
470966
  }
470083
470967
  const wss = new WebSocketServer({ noServer: true });
470084
- let activeSession = null;
470085
- let pendingStreamSocket = null;
470086
- let pendingStreamTimeout = null;
470087
470968
  let resolvedInfo = null;
470088
- const lastPongAtBySocket = new WeakMap;
470089
- const clearPendingStream = () => {
470090
- if (pendingStreamTimeout) {
470091
- clearTimeout(pendingStreamTimeout);
470092
- pendingStreamTimeout = null;
470969
+ let nextConnectionOrdinal = 0;
470970
+ const runtime = createRuntime();
470971
+ runtime.onWsEvent = undefined;
470972
+ runtime.connectionId = "app-server";
470973
+ runtime.connectionName = options3.connectionName ?? hostname6();
470974
+ let startupReady = null;
470975
+ const getStartupReady = () => {
470976
+ if (startupReady) {
470977
+ return startupReady;
470093
470978
  }
470094
- const socket = pendingStreamSocket;
470095
- pendingStreamSocket = null;
470096
- return socket;
470979
+ const attempt = (async () => {
470980
+ if (options3.initializeRuntime) {
470981
+ await options3.initializeRuntime(runtime);
470982
+ return;
470983
+ }
470984
+ await reloadListenerModAdapter(runtime);
470985
+ await loadTools();
470986
+ })();
470987
+ startupReady = attempt;
470988
+ attempt.catch(() => {
470989
+ if (startupReady === attempt) {
470990
+ startupReady = null;
470991
+ }
470992
+ });
470993
+ return startupReady;
470097
470994
  };
470098
- const handleWebSocketConnection = (socket, channel) => {
470995
+ const lastPongAtBySocket = new WeakMap;
470996
+ const handleWebSocketConnection = (socket) => {
470997
+ const connectionId = `app-server-${nextConnectionOrdinal}`;
470998
+ nextConnectionOrdinal += 1;
470099
470999
  lastPongAtBySocket.set(socket, Date.now());
470100
471000
  socket.on("pong", () => {
470101
- lastPongAtBySocket.set(socket, Date.now());
470102
- });
470103
- if (channel === "stream") {
470104
- if (activeSession) {
470105
- attachStreamSocket(activeSession, socket);
470106
- return;
470107
- }
470108
- if (pendingStreamSocket) {
470109
- closeSocket(socket, 1008, "stream channel already pending");
470110
- return;
471001
+ if (options3.shouldRecordPong?.(connectionId) !== false) {
471002
+ lastPongAtBySocket.set(socket, Date.now());
470111
471003
  }
470112
- pendingStreamSocket = socket;
470113
- pendingStreamTimeout = setTimeout(() => {
470114
- const staleSocket = clearPendingStream();
470115
- closeSocket(staleSocket, 1008, "control channel did not connect");
470116
- }, PENDING_STREAM_TIMEOUT_MS);
470117
- socket.on("close", () => {
470118
- if (pendingStreamSocket === socket) {
470119
- clearPendingStream();
470120
- }
470121
- });
470122
- socket.on("error", (error54) => {
470123
- options3.onLog?.(`App-server stream socket error: ${error54.message}`);
470124
- });
470125
- return;
470126
- }
470127
- if (activeSession) {
470128
- closeSocket(socket, 1008, "control channel already connected");
470129
- return;
470130
- }
470131
- const streamSocket = clearPendingStream();
470132
- startControlSession({
470133
- socket,
470134
- streamSocket,
471004
+ });
471005
+ attachOpenListenerSocket(runtime, socket, {
471006
+ connectionId,
471007
+ wsUrl: resolvedInfo?.url ?? options3.listen ?? DEFAULT_LISTEN_URL,
471008
+ deviceId: settingsManager.getOrCreateDeviceId(),
470135
471009
  connectionName: options3.connectionName ?? hostname6(),
470136
- serverUrl: resolvedInfo?.url ?? options3.listen ?? DEFAULT_LISTEN_URL,
470137
- onSessionCreated: (session) => {
470138
- activeSession = session;
470139
- },
470140
- onSessionClosed: () => {
470141
- activeSession = null;
471010
+ onConnected: () => {},
471011
+ onDisconnected: () => {},
471012
+ onError: (error54) => {
471013
+ options3.onLog?.(`App-server connection ${connectionId} failed: ${error54.message}`);
470142
471014
  }
471015
+ }, {
471016
+ startHeartbeat: false,
471017
+ startCronScheduler: true,
471018
+ startupReady: getStartupReady()
470143
471019
  }).catch((error54) => {
470144
- if (activeSession?.controlSocket === socket) {
470145
- activeSession = null;
470146
- }
470147
- options3.onLog?.(`Failed to start app-server session: ${error54 instanceof Error ? error54.message : String(error54)}`);
470148
- closeSocket(socket, 1011, "failed to start session");
470149
- closeSocket(streamSocket, 1011, "failed to start session");
471020
+ options3.onLog?.(`Failed to start app-server connection: ${error54 instanceof Error ? error54.message : String(error54)}`);
471021
+ closeSocket(socket, 1011, "failed to start connection");
470150
471022
  });
470151
471023
  };
470152
471024
  const server2 = createServer((request, response) => {
@@ -470198,9 +471070,9 @@ async function startAppServer(options3 = {}) {
470198
471070
  rejectUpgrade(socket, 404, "Not Found");
470199
471071
  return;
470200
471072
  }
470201
- const channel = getRequestChannel(requestUrl);
470202
- if (!channel) {
470203
- rejectUpgrade(socket, 400, "Bad Request");
471073
+ if (requestUrl.searchParams.has("channel")) {
471074
+ options3.onLog?.("Rejecting legacy split-channel app-server client; upgrade to a one-socket client");
471075
+ rejectUpgrade(socket, 426, "Upgrade Required");
470204
471076
  return;
470205
471077
  }
470206
471078
  const authError = authorizeUpgrade(request.headers, authPolicy);
@@ -470215,7 +471087,7 @@ async function startAppServer(options3 = {}) {
470215
471087
  return;
470216
471088
  }
470217
471089
  wss.handleUpgrade(request, socket, head2, (websocket) => {
470218
- handleWebSocketConnection(websocket, channel);
471090
+ handleWebSocketConnection(websocket);
470219
471091
  });
470220
471092
  });
470221
471093
  const heartbeatIntervalMs = options3.heartbeatIntervalMs ?? APP_SERVER_HEARTBEAT_INTERVAL_MS;
@@ -470238,25 +471110,38 @@ async function startAppServer(options3 = {}) {
470238
471110
  wss.on("close", () => {
470239
471111
  clearInterval(heartbeatInterval);
470240
471112
  });
470241
- await new Promise((resolve32, reject) => {
470242
- const onError = (error54) => {
470243
- server2.off("listening", onListening);
470244
- reject(error54);
470245
- };
470246
- const onListening = () => {
470247
- server2.off("error", onError);
470248
- resolve32();
470249
- };
470250
- server2.once("error", onError);
470251
- server2.once("listening", onListening);
470252
- server2.listen(listen.port, listen.host);
470253
- });
471113
+ try {
471114
+ await new Promise((resolve32, reject) => {
471115
+ const onError = (error54) => {
471116
+ server2.off("listening", onListening);
471117
+ reject(error54);
471118
+ };
471119
+ const onListening = () => {
471120
+ server2.off("error", onError);
471121
+ resolve32();
471122
+ };
471123
+ server2.once("error", onError);
471124
+ server2.once("listening", onListening);
471125
+ server2.listen(listen.port, listen.host);
471126
+ });
471127
+ } catch (error54) {
471128
+ clearInterval(heartbeatInterval);
471129
+ runtime.intentionallyClosed = true;
471130
+ throw error54;
471131
+ }
471132
+ const existingRuntime = getActiveRuntime();
471133
+ if (existingRuntime) {
471134
+ stopRuntime(existingRuntime, true);
471135
+ setActiveRuntime(null);
471136
+ }
471137
+ setActiveRuntime(runtime);
471138
+ telemetry.setSurface(getListenerTelemetrySurface());
471139
+ telemetry.init();
470254
471140
  const address = getRequiredAddressInfo(server2);
470255
471141
  const baseUrl = `ws://${listen.host}:${address.port}`;
470256
471142
  resolvedInfo = {
470257
471143
  url: baseUrl,
470258
- controlUrl: getChannelUrl(baseUrl, listen.path, "control"),
470259
- streamUrl: getChannelUrl(baseUrl, listen.path, "stream")
471144
+ controlUrl: getWebSocketUrl(baseUrl, listen.path)
470260
471145
  };
470261
471146
  options3.onListening?.(resolvedInfo);
470262
471147
  return {
@@ -470266,21 +471151,13 @@ async function startAppServer(options3 = {}) {
470266
471151
  if (options3.openaiApi) {
470267
471152
  closeOpenAiBridgeRuntime();
470268
471153
  }
470269
- const streamSocket = clearPendingStream();
470270
- terminateSocket(streamSocket);
470271
- if (activeSession) {
470272
- const session = activeSession;
470273
- activeSession = null;
470274
- terminateSocket(session.streamSocket);
470275
- terminateSocket(session.controlSocket);
470276
- stopRuntime(session.runtime, true);
470277
- if (getActiveRuntime() === session.runtime) {
470278
- setActiveRuntime(null);
470279
- }
470280
- }
470281
471154
  for (const client of wss.clients) {
470282
471155
  terminateSocket(client);
470283
471156
  }
471157
+ if (getActiveRuntime() === runtime) {
471158
+ stopRuntime(runtime, true);
471159
+ setActiveRuntime(null);
471160
+ }
470284
471161
  await new Promise((resolve32, reject) => {
470285
471162
  wss.close();
470286
471163
  const timeout = setTimeout(resolve32, 1000);
@@ -470296,7 +471173,7 @@ async function startAppServer(options3 = {}) {
470296
471173
  }
470297
471174
  };
470298
471175
  }
470299
- var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0", DEFAULT_WS_PATH = "/ws", PENDING_STREAM_TIMEOUT_MS = 5000, APP_SERVER_HEARTBEAT_INTERVAL_MS = 30000, APP_SERVER_PONG_TIMEOUT_MS = 90000;
471176
+ var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0", DEFAULT_WS_PATH = "/ws", APP_SERVER_HEARTBEAT_INTERVAL_MS = 30000, APP_SERVER_PONG_TIMEOUT_MS = 90000;
470300
471177
  var init_app_server = __esm(async () => {
470301
471178
  init_settings_manager();
470302
471179
  init_telemetry();
@@ -470402,8 +471279,7 @@ async function runAppServerSubcommand(argv) {
470402
471279
  openaiApi,
470403
471280
  onListening: (info) => {
470404
471281
  console.log(`Listening on ${info.url}`);
470405
- console.log(`Control: ${info.controlUrl}`);
470406
- console.log(`Stream: ${info.streamUrl}`);
471282
+ console.log(`WebSocket: ${info.controlUrl}`);
470407
471283
  if (openaiApi) {
470408
471284
  const openaiBase = new URL(info.url);
470409
471285
  openaiBase.protocol = "http:";
@@ -472207,7 +473083,7 @@ var init_AgentSelector = __esm(async () => {
472207
473083
  // src/cli/subcommands/skills.ts
472208
473084
  import {
472209
473085
  cpSync as cpSync2,
472210
- existsSync as existsSync57,
473086
+ existsSync as existsSync58,
472211
473087
  mkdtempSync as mkdtempSync4,
472212
473088
  readFileSync as readFileSync37,
472213
473089
  rmSync as rmSync15,
@@ -472666,7 +473542,7 @@ async function installSkillDirectory(params) {
472666
473542
  const sourceDir = resolve32(params.sourceDir);
472667
473543
  const memoryDir = resolve32(params.memoryDir);
472668
473544
  const skillMdPath = join71(sourceDir, "SKILL.md");
472669
- if (!existsSync57(skillMdPath)) {
473545
+ if (!existsSync58(skillMdPath)) {
472670
473546
  throw new Error("No SKILL.md found in the skill directory.");
472671
473547
  }
472672
473548
  if (!statSync19(sourceDir).isDirectory()) {
@@ -472676,7 +473552,7 @@ async function installSkillDirectory(params) {
472676
473552
  const skillsDir = join71(memoryDir, "skills");
472677
473553
  const targetPath = join71(skillsDir, name);
472678
473554
  assertInside(skillsDir, targetPath);
472679
- if (existsSync57(targetPath)) {
473555
+ if (existsSync58(targetPath)) {
472680
473556
  if (!params.force) {
472681
473557
  throw new Error(`Skill "${name}" already exists at ${targetPath}. Re-run with --force to replace it.`);
472682
473558
  }
@@ -472692,7 +473568,7 @@ async function installSkillDirectory(params) {
472692
473568
  async function listSkillDirectories(params) {
472693
473569
  const memoryDir = resolve32(params.memoryDir);
472694
473570
  const skillsDir = join71(memoryDir, "skills");
472695
- if (!existsSync57(skillsDir))
473571
+ if (!existsSync58(skillsDir))
472696
473572
  return [];
472697
473573
  const entries = await readdir14(skillsDir, { withFileTypes: true });
472698
473574
  const skills = [];
@@ -472701,7 +473577,7 @@ async function listSkillDirectories(params) {
472701
473577
  continue;
472702
473578
  const skillDir = join71(skillsDir, entry.name);
472703
473579
  const skillMdPath = join71(skillDir, "SKILL.md");
472704
- if (!existsSync57(skillMdPath))
473580
+ if (!existsSync58(skillMdPath))
472705
473581
  continue;
472706
473582
  let name = entry.name;
472707
473583
  let description;
@@ -472725,7 +473601,7 @@ async function deleteSkillDirectory(params) {
472725
473601
  const name = sanitizeSkillName(params.name);
472726
473602
  const targetPath = join71(skillsDir, name);
472727
473603
  assertInside(skillsDir, targetPath);
472728
- if (!existsSync57(targetPath)) {
473604
+ if (!existsSync58(targetPath)) {
472729
473605
  throw new Error(`Skill "${name}" is not installed at ${targetPath}.`);
472730
473606
  }
472731
473607
  if (!statSync19(targetPath).isDirectory()) {
@@ -472774,7 +473650,7 @@ async function installSkill(specifier, agentId, force) {
472774
473650
  tmpDir = downloaded.tmpDir;
472775
473651
  const sourceDir = resolve32(downloaded.sourceDir);
472776
473652
  assertInside(tmpDir, sourceDir);
472777
- if (!existsSync57(sourceDir)) {
473653
+ if (!existsSync58(sourceDir)) {
472778
473654
  const missingPath = source2.type === "git" ? source2.location.subdir ?? "." : source2.type === "direct-file" ? source2.location.url : source2.location.slug;
472779
473655
  throw new Error(`Skill path not found: ${missingPath}`);
472780
473656
  }
@@ -474073,11 +474949,11 @@ var exports_bootstrap_tools = {};
474073
474949
  __export(exports_bootstrap_tools, {
474074
474950
  bootstrapBaseToolsIfNeeded: () => bootstrapBaseToolsIfNeeded
474075
474951
  });
474076
- import { existsSync as existsSync58, mkdirSync as mkdirSync42, writeFileSync as writeFileSync31 } from "node:fs";
474952
+ import { existsSync as existsSync59, mkdirSync as mkdirSync42, writeFileSync as writeFileSync31 } from "node:fs";
474077
474953
  import { homedir as homedir42 } from "node:os";
474078
474954
  import { join as join75 } from "node:path";
474079
474955
  async function bootstrapBaseToolsIfNeeded() {
474080
- if (existsSync58(MARKER_PATH))
474956
+ if (existsSync59(MARKER_PATH))
474081
474957
  return;
474082
474958
  debugLog("bootstrap", "No marker found, bootstrapping base tools...");
474083
474959
  try {
@@ -474643,7 +475519,7 @@ var init_headless_tool_events = __esm(async () => {
474643
475519
  });
474644
475520
 
474645
475521
  // src/skills/builtin/creating-skills/scripts/validate-skill.ts
474646
- import { existsSync as existsSync59, readFileSync as readFileSync38 } from "node:fs";
475522
+ import { existsSync as existsSync60, readFileSync as readFileSync38 } from "node:fs";
474647
475523
  import { basename as basename30, join as join77, resolve as resolve33 } from "node:path";
474648
475524
  import { fileURLToPath as fileURLToPath11 } from "node:url";
474649
475525
  function parseQuotedScalar(value) {
@@ -474742,7 +475618,7 @@ function parseFrontmatter2(source2) {
474742
475618
  }
474743
475619
  function validateSkill(skillPath) {
474744
475620
  const skillMdPath = join77(skillPath, "SKILL.md");
474745
- if (!existsSync59(skillMdPath)) {
475621
+ if (!existsSync60(skillMdPath)) {
474746
475622
  return { valid: false, message: "SKILL.md not found" };
474747
475623
  }
474748
475624
  const content = readFileSync38(skillMdPath, "utf-8");
@@ -479871,7 +480747,7 @@ var init_queued_message_parts = __esm(() => {
479871
480747
 
479872
480748
  // src/cli/helpers/reflection-arena-hf-upload.ts
479873
480749
  import { execFile as execFileCb5 } from "node:child_process";
479874
- import { existsSync as existsSync61 } from "node:fs";
480750
+ import { existsSync as existsSync62 } from "node:fs";
479875
480751
  import { appendFile as appendFile2, chmod as chmod2, mkdir as mkdir18, writeFile as writeFile20 } from "node:fs/promises";
479876
480752
  import { homedir as homedir45 } from "node:os";
479877
480753
  import { join as join79 } from "node:path";
@@ -479925,7 +480801,7 @@ async function writeGitAskpass(repoRoot) {
479925
480801
  }
479926
480802
  async function prepareHfRepo(env5) {
479927
480803
  await mkdir18(HF_CACHE_ROOT, { recursive: true });
479928
- if (!existsSync61(join79(HF_REPO_DIR, ".git"))) {
480804
+ if (!existsSync62(join79(HF_REPO_DIR, ".git"))) {
479929
480805
  await runGit6(HF_CACHE_ROOT, ["clone", "--depth", "1", HF_REPO_URL, HF_REPO_DIR], env5);
479930
480806
  return HF_REPO_DIR;
479931
480807
  }
@@ -481128,7 +482004,7 @@ __export(exports_terminal_keybinding_installer, {
481128
482004
  });
481129
482005
  import {
481130
482006
  copyFileSync as copyFileSync6,
481131
- existsSync as existsSync62,
482007
+ existsSync as existsSync63,
481132
482008
  mkdirSync as mkdirSync44,
481133
482009
  readFileSync as readFileSync40,
481134
482010
  writeFileSync as writeFileSync33
@@ -481197,7 +482073,7 @@ function parseKeybindings(content) {
481197
482073
  }
481198
482074
  }
481199
482075
  function keybindingExists(keybindingsPath) {
481200
- if (!existsSync62(keybindingsPath))
482076
+ if (!existsSync63(keybindingsPath))
481201
482077
  return false;
481202
482078
  try {
481203
482079
  const content = readFileSync40(keybindingsPath, { encoding: "utf-8" });
@@ -481210,7 +482086,7 @@ function keybindingExists(keybindingsPath) {
481210
482086
  }
481211
482087
  }
481212
482088
  function createBackup(keybindingsPath) {
481213
- if (!existsSync62(keybindingsPath))
482089
+ if (!existsSync63(keybindingsPath))
481214
482090
  return null;
481215
482091
  const backupPath = `${keybindingsPath}.letta-backup`;
481216
482092
  try {
@@ -481226,12 +482102,12 @@ function installKeybinding(keybindingsPath) {
481226
482102
  return { success: true, alreadyExists: true };
481227
482103
  }
481228
482104
  const parentDir = dirname34(keybindingsPath);
481229
- if (!existsSync62(parentDir)) {
482105
+ if (!existsSync63(parentDir)) {
481230
482106
  mkdirSync44(parentDir, { recursive: true });
481231
482107
  }
481232
482108
  let keybindings = [];
481233
482109
  let backupPath = null;
481234
- if (existsSync62(keybindingsPath)) {
482110
+ if (existsSync63(keybindingsPath)) {
481235
482111
  backupPath = createBackup(keybindingsPath);
481236
482112
  const content = readFileSync40(keybindingsPath, { encoding: "utf-8" });
481237
482113
  const parsed = parseKeybindings(content);
@@ -481261,7 +482137,7 @@ function installKeybinding(keybindingsPath) {
481261
482137
  }
481262
482138
  function removeKeybinding(keybindingsPath) {
481263
482139
  try {
481264
- if (!existsSync62(keybindingsPath)) {
482140
+ if (!existsSync63(keybindingsPath)) {
481265
482141
  return { success: true };
481266
482142
  }
481267
482143
  const content = readFileSync40(keybindingsPath, { encoding: "utf-8" });
@@ -481329,11 +482205,11 @@ function getWezTermConfigPath() {
481329
482205
  const xdgConfig = process.env.XDG_CONFIG_HOME;
481330
482206
  if (xdgConfig) {
481331
482207
  const xdgPath = join81(xdgConfig, "wezterm", "wezterm.lua");
481332
- if (existsSync62(xdgPath))
482208
+ if (existsSync63(xdgPath))
481333
482209
  return xdgPath;
481334
482210
  }
481335
482211
  const configPath = join81(homedir47(), ".config", "wezterm", "wezterm.lua");
481336
- if (existsSync62(configPath))
482212
+ if (existsSync63(configPath))
481337
482213
  return configPath;
481338
482214
  return join81(homedir47(), ".wezterm.lua");
481339
482215
  }
@@ -481438,7 +482314,7 @@ ${WEZTERM_DELETE_FIX}
481438
482314
  `;
481439
482315
  }
481440
482316
  function wezTermDeleteFixExists(configPath) {
481441
- if (!existsSync62(configPath))
482317
+ if (!existsSync63(configPath))
481442
482318
  return false;
481443
482319
  try {
481444
482320
  const content = readFileSync40(configPath, { encoding: "utf-8" });
@@ -481455,14 +482331,14 @@ function installWezTermDeleteFix() {
481455
482331
  }
481456
482332
  let content = "";
481457
482333
  let backupPath = null;
481458
- if (existsSync62(configPath)) {
482334
+ if (existsSync63(configPath)) {
481459
482335
  backupPath = `${configPath}.letta-backup`;
481460
482336
  copyFileSync6(configPath, backupPath);
481461
482337
  content = readFileSync40(configPath, { encoding: "utf-8" });
481462
482338
  }
481463
482339
  content = injectWezTermDeleteFix(content);
481464
482340
  const parentDir = dirname34(configPath);
481465
- if (!existsSync62(parentDir)) {
482341
+ if (!existsSync63(parentDir)) {
481466
482342
  mkdirSync44(parentDir, { recursive: true });
481467
482343
  }
481468
482344
  writeFileSync33(configPath, content, { encoding: "utf-8" });
@@ -483458,9 +484334,9 @@ function getHeaderText(fileEdit) {
483458
484334
  const relPath = relative17(cwd2, fileEdit.filePath);
483459
484335
  const displayPath = relPath.startsWith("..") ? fileEdit.filePath : relPath;
483460
484336
  if (t2 === "write" || t2 === "write_file" || t2 === "writefile" || t2 === "write_file_gemini" || t2 === "writefilegemini") {
483461
- const { existsSync: existsSync63 } = __require("node:fs");
484337
+ const { existsSync: existsSync64 } = __require("node:fs");
483462
484338
  try {
483463
- if (existsSync63(fileEdit.filePath)) {
484339
+ if (existsSync64(fileEdit.filePath)) {
483464
484340
  return `Overwrite ${displayPath}?`;
483465
484341
  }
483466
484342
  } catch {}
@@ -492949,7 +493825,7 @@ var init_agents8 = __esm(() => {
492949
493825
  // src/cli/commands/install-github-app.ts
492950
493826
  import { execFileSync as execFileSync8 } from "node:child_process";
492951
493827
  import {
492952
- existsSync as existsSync63,
493828
+ existsSync as existsSync64,
492953
493829
  mkdirSync as mkdirSync45,
492954
493830
  mkdtempSync as mkdtempSync5,
492955
493831
  readFileSync as readFileSync41,
@@ -493191,12 +494067,12 @@ function runGit7(args, cwd2) {
493191
494067
  }
493192
494068
  function writeWorkflow(repoDir, workflowPath, content) {
493193
494069
  const absolutePath = join83(repoDir, workflowPath);
493194
- if (!existsSync63(dirname35(absolutePath))) {
494070
+ if (!existsSync64(dirname35(absolutePath))) {
493195
494071
  mkdirSync45(dirname35(absolutePath), { recursive: true });
493196
494072
  }
493197
494073
  const next = `${content.trimEnd()}
493198
494074
  `;
493199
- if (existsSync63(absolutePath)) {
494075
+ if (existsSync64(absolutePath)) {
493200
494076
  const previous = readFileSync41(absolutePath, "utf8");
493201
494077
  if (previous === next) {
493202
494078
  return false;
@@ -497742,7 +498618,7 @@ __export(exports_generate_memory_viewer, {
497742
498618
  generateAndOpenMemoryViewer: () => generateAndOpenMemoryViewer
497743
498619
  });
497744
498620
  import { execFile as execFileCb7 } from "node:child_process";
497745
- import { chmodSync as chmodSync7, existsSync as existsSync64, mkdirSync as mkdirSync46, writeFileSync as writeFileSync35 } from "node:fs";
498621
+ import { chmodSync as chmodSync7, existsSync as existsSync65, mkdirSync as mkdirSync46, writeFileSync as writeFileSync35 } from "node:fs";
497746
498622
  import { homedir as homedir50 } from "node:os";
497747
498623
  import { join as join84 } from "node:path";
497748
498624
  import { promisify as promisify17 } from "node:util";
@@ -498053,7 +498929,7 @@ ${m4.body}` : m4.subject;
498053
498929
  async function generateAndOpenMemoryViewer(agentId, options3) {
498054
498930
  const memoryRoot = getScopedMemoryFilesystemRoot(agentId);
498055
498931
  const repoDir = memoryRoot;
498056
- if (!existsSync64(join84(repoDir, ".git"))) {
498932
+ if (!existsSync65(join84(repoDir, ".git"))) {
498057
498933
  throw new Error("Memory viewer requires memfs. Run /memfs enable first.");
498058
498934
  }
498059
498935
  const data = await collectMemoryData(agentId, repoDir, memoryRoot, options3?.conversationId);
@@ -498063,7 +498939,7 @@ async function generateAndOpenMemoryViewer(agentId, options3) {
498063
498939
  data.context = applyContextUsageSnapshot(data.context, options3?.contextUsage);
498064
498940
  const jsonPayload = JSON.stringify(data).replace(/</g, "\\u003c");
498065
498941
  const html5 = memory_viewer_template_default.replace("<!--LETTA_DATA_PLACEHOLDER-->", () => jsonPayload);
498066
- if (!existsSync64(VIEWERS_DIR)) {
498942
+ if (!existsSync65(VIEWERS_DIR)) {
498067
498943
  mkdirSync46(VIEWERS_DIR, { recursive: true, mode: 448 });
498068
498944
  }
498069
498945
  try {
@@ -498099,7 +498975,7 @@ var init_generate_memory_viewer = __esm(() => {
498099
498975
  });
498100
498976
 
498101
498977
  // src/cli/components/MemfsTreeViewer.tsx
498102
- import { existsSync as existsSync65 } from "node:fs";
498978
+ import { existsSync as existsSync66 } from "node:fs";
498103
498979
  import { join as join85 } from "node:path";
498104
498980
  function renderTreePrefix(node) {
498105
498981
  let prefix = "";
@@ -498128,8 +499004,8 @@ function MemfsTreeViewer({
498128
499004
  const [status, setStatus] = import_react88.useState(null);
498129
499005
  const statusTimerRef = import_react88.useRef(null);
498130
499006
  const memoryRoot = getScopedMemoryFilesystemRoot(agentId);
498131
- const memoryExists = existsSync65(memoryRoot);
498132
- const hasGitRepo = import_react88.useMemo(() => existsSync65(join85(memoryRoot, ".git")), [memoryRoot]);
499007
+ const memoryExists = existsSync66(memoryRoot);
499008
+ const hasGitRepo = import_react88.useMemo(() => existsSync66(join85(memoryRoot, ".git")), [memoryRoot]);
498133
499009
  function showStatus(msg, durationMs) {
498134
499010
  if (statusTimerRef.current)
498135
499011
  clearTimeout(statusTimerRef.current);
@@ -506021,9 +506897,9 @@ function getFileEditHeader(toolName, toolArgs) {
506021
506897
  const relPath = relative17(cwd2, filePath);
506022
506898
  const displayPath2 = relPath.startsWith("..") ? filePath : relPath;
506023
506899
  if (t2 === "write" || t2 === "write_file" || t2 === "writefile" || t2 === "write_file_gemini" || t2 === "writefilegemini") {
506024
- const { existsSync: existsSync66 } = __require("node:fs");
506900
+ const { existsSync: existsSync67 } = __require("node:fs");
506025
506901
  try {
506026
- if (existsSync66(filePath)) {
506902
+ if (existsSync67(filePath)) {
506027
506903
  return `Overwrite ${displayPath2}?`;
506028
506904
  }
506029
506905
  } catch {}
@@ -521363,7 +522239,7 @@ __export(exports_generate_diff_viewer, {
521363
522239
  generateAndOpenDiffViewer: () => generateAndOpenDiffViewer
521364
522240
  });
521365
522241
  import { execFile as execFileCb8 } from "node:child_process";
521366
- import { chmodSync as chmodSync8, existsSync as existsSync66, mkdirSync as mkdirSync47, writeFileSync as writeFileSync36 } from "node:fs";
522242
+ import { chmodSync as chmodSync8, existsSync as existsSync67, mkdirSync as mkdirSync47, writeFileSync as writeFileSync36 } from "node:fs";
521367
522243
  import { homedir as homedir52 } from "node:os";
521368
522244
  import { isAbsolute as isAbsolute27, join as join87, resolve as resolve37 } from "node:path";
521369
522245
  import { promisify as promisify18 } from "node:util";
@@ -521584,7 +522460,7 @@ async function generateAndOpenDiffViewer(targetPath) {
521584
522460
  };
521585
522461
  const jsonPayload = JSON.stringify(payload).replace(/</g, "\\u003c");
521586
522462
  const html5 = diff_viewer_template_default.replace("<!--LETTA_DIFF_DATA_PLACEHOLDER-->", () => jsonPayload);
521587
- if (!existsSync66(VIEWERS_DIR2)) {
522463
+ if (!existsSync67(VIEWERS_DIR2)) {
521588
522464
  mkdirSync47(VIEWERS_DIR2, { recursive: true, mode: 448 });
521589
522465
  }
521590
522466
  try {
@@ -523643,12 +524519,12 @@ __export(exports_shell_aliases, {
523643
524519
  expandAliases: () => expandAliases,
523644
524520
  clearAliasCache: () => clearAliasCache
523645
524521
  });
523646
- import { existsSync as existsSync67, readFileSync as readFileSync42 } from "node:fs";
524522
+ import { existsSync as existsSync68, readFileSync as readFileSync42 } from "node:fs";
523647
524523
  import { homedir as homedir53 } from "node:os";
523648
524524
  import { join as join88 } from "node:path";
523649
524525
  function parseAliasesFromFile(filePath) {
523650
524526
  const aliases = new Map;
523651
- if (!existsSync67(filePath)) {
524527
+ if (!existsSync68(filePath)) {
523652
524528
  return aliases;
523653
524529
  }
523654
524530
  try {
@@ -532012,7 +532888,7 @@ var init_conversation_switch_alert = __esm(() => {
532012
532888
 
532013
532889
  // src/cli/app/use-submit-handler.ts
532014
532890
  import { randomUUID as randomUUID37 } from "node:crypto";
532015
- import { existsSync as existsSync68, readFileSync as readFileSync43, renameSync as renameSync7, writeFileSync as writeFileSync37 } from "node:fs";
532891
+ import { existsSync as existsSync69, readFileSync as readFileSync43, renameSync as renameSync7, writeFileSync as writeFileSync37 } from "node:fs";
532016
532892
  import { tmpdir as tmpdir12 } from "node:os";
532017
532893
  import { join as join89 } from "node:path";
532018
532894
  async function findCustomCommandByName(commandName) {
@@ -532712,7 +533588,7 @@ ${SYSTEM_REMINDER_CLOSE}`),
532712
533588
  join89(memoryRoot, "system", "persona.md"),
532713
533589
  join89(memoryRoot, "memory", "system", "persona.md")
532714
533590
  ];
532715
- const personaPath = personaCandidates.find((candidate) => existsSync68(candidate));
533591
+ const personaPath = personaCandidates.find((candidate) => existsSync69(candidate));
532716
533592
  if (personaPath) {
532717
533593
  const personaContent = readFileSync43(personaPath, "utf-8");
532718
533594
  setCurrentPersonalityId(detectPersonalityFromPersonaFile(personaContent));
@@ -533592,7 +534468,7 @@ Path: ${memoryDir}`, true);
533592
534468
  setCommandRunning(true);
533593
534469
  try {
533594
534470
  const memoryDir = getScopedMemoryFilesystemRoot(agentId);
533595
- if (!existsSync68(memoryDir)) {
534471
+ if (!existsSync69(memoryDir)) {
533596
534472
  updateMemorySyncCommand(cmdId, "No local memory filesystem found to reset.", true, msg);
533597
534473
  return { submitted: true };
533598
534474
  }
@@ -536299,13 +537175,17 @@ function App2({
536299
537175
  `).trim();
536300
537176
  };
536301
537177
  const sysNorm = normalize8(agentSystem);
536302
- const { SYSTEM_PROMPTS: SYSTEM_PROMPTS2, SYSTEM_PROMPT: SYSTEM_PROMPT2 } = await Promise.resolve().then(() => (init_prompt_assets(), exports_prompt_assets));
537178
+ const {
537179
+ getSystemPromptVariantContents: getSystemPromptVariantContents2,
537180
+ SYSTEM_PROMPTS: SYSTEM_PROMPTS2,
537181
+ SYSTEM_PROMPT: SYSTEM_PROMPT2
537182
+ } = await Promise.resolve().then(() => (init_prompt_assets(), exports_prompt_assets));
536303
537183
  let matched = null;
536304
537184
  const contentMatches = (content) => {
536305
537185
  const norm = normalize8(content);
536306
537186
  return norm === sysNorm || norm.length > 0 && (sysNorm.startsWith(norm) || norm.startsWith(sysNorm));
536307
537187
  };
536308
- const promptMatches = (prompt) => contentMatches(prompt.content) || (prompt.memfsContent ? contentMatches(prompt.memfsContent) : false);
537188
+ const promptMatches = (prompt) => getSystemPromptVariantContents2(prompt).some(contentMatches);
536309
537189
  const defaultPrompt = SYSTEM_PROMPTS2.find((p2) => p2.id === "default");
536310
537190
  if (defaultPrompt && promptMatches(defaultPrompt)) {
536311
537191
  matched = "default";
@@ -536626,9 +537506,9 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
536626
537506
  (async () => {
536627
537507
  try {
536628
537508
  const { watch: watch3 } = await import("node:fs");
536629
- const { existsSync: existsSync69 } = await import("node:fs");
537509
+ const { existsSync: existsSync70 } = await import("node:fs");
536630
537510
  const memRoot = getScopedMemoryFilesystemRoot(agentId);
536631
- if (!existsSync69(memRoot))
537511
+ if (!existsSync70(memRoot))
536632
537512
  return;
536633
537513
  watcher2 = watch3(memRoot, { recursive: true }, () => {});
536634
537514
  memfsWatcherRef.current = watcher2;
@@ -538740,9 +539620,9 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
538740
539620
  }
538741
539621
  } else {
538742
539622
  const { resolve: resolve39 } = await import("node:path");
538743
- const { existsSync: existsSync69 } = await import("node:fs");
539623
+ const { existsSync: existsSync70 } = await import("node:fs");
538744
539624
  const resolvedPath = resolve39(fromAfFile);
538745
- if (!existsSync69(resolvedPath)) {
539625
+ if (!existsSync70(resolvedPath)) {
538746
539626
  console.error(`Error: AgentFile not found: ${resolvedPath}`);
538747
539627
  process.exit(1);
538748
539628
  }
@@ -541433,4 +542313,4 @@ function registerBunOAuthFlows() {
541433
542313
  registerBunOAuthFlows();
541434
542314
  await init_src5().then(() => exports_src2);
541435
542315
 
541436
- //# debugId=EDD0BAA906B8C7B064756E2164756E21
542316
+ //# debugId=20D01332A3556DD664756E2164756E21