@codeagentswarm/cas-cloud 0.0.15 → 0.0.17

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/dist/cas.js CHANGED
@@ -101,12 +101,11 @@ var require_provider_login = __commonJS({
101
101
  verified: "2026-08-10 \xB7 opencode 1.18.11"
102
102
  },
103
103
  kimi: {
104
- mode: LOGIN_MODES.MANUAL,
104
+ mode: LOGIN_MODES.CLI,
105
105
  label: "Kimi Code",
106
106
  command: ["kimi", "login"],
107
- terminalHint: "/login",
108
- reason: "Kimi's sign-in has not been observed outside its own interface, so CodeAgentSwarm will not pretend to drive it.",
109
- verified: null
107
+ acceptsCode: false,
108
+ verified: "2026-09-04 \xB7 kimi 0.36.1; device flow captured on 0.38.0"
110
109
  },
111
110
  antigravity: {
112
111
  mode: LOGIN_MODES.UNAVAILABLE,
@@ -128,6 +127,16 @@ var require_provider_login = __commonJS({
128
127
  function canLoginFromChat(agent) {
129
128
  return loginStrategyForAgent(agent).mode === LOGIN_MODES.CLI;
130
129
  }
130
+ function loginStrategyForRemoteAgent(agent) {
131
+ const strategy = loginStrategyForAgent(agent);
132
+ if (agent === "codex") return { ...strategy, command: ["codex", "login", "--device-auth"] };
133
+ if (strategy.mode !== LOGIN_MODES.CLI) return {
134
+ ...strategy,
135
+ terminalHint: null,
136
+ reason: `${strategy.reason || ""} Complete sign-in on the remote computer.`
137
+ };
138
+ return strategy;
139
+ }
131
140
  function canSwitchAccount(agent) {
132
141
  const strategy = loginStrategyForAgent(agent);
133
142
  return strategy.mode === LOGIN_MODES.CLI && Array.isArray(strategy.logoutCommand) && strategy.logoutCommand.length > 0 && Array.isArray(strategy.statusCommand) && strategy.statusCommand.length > 0;
@@ -143,6 +152,7 @@ var require_provider_login = __commonJS({
143
152
  LOGIN_MODES,
144
153
  PROVIDER_LOGIN,
145
154
  loginStrategyForAgent,
155
+ loginStrategyForRemoteAgent,
146
156
  canLoginFromChat,
147
157
  canSwitchAccount,
148
158
  loginCommandLine,
@@ -616,6 +626,7 @@ var require_provider_login_manager = __commonJS({
616
626
  "src/infrastructure/agent-drivers/provider-login-manager.js"(exports2, module2) {
617
627
  var EventEmitter = require("events");
618
628
  var { spawn } = require("child_process");
629
+ var { randomUUID } = require("crypto");
619
630
  var { LOGIN_MODES, loginStrategyForAgent } = require_provider_login();
620
631
  var { isNativeExe, quoteForCmd } = require_windows_direct_spawn();
621
632
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -640,7 +651,6 @@ var require_provider_login_manager = __commonJS({
640
651
  this._resolveBinary = resolveBinary || (() => null);
641
652
  this._resolveStrategy = resolveStrategy || loginStrategyForAgent;
642
653
  this._timeoutMs = timeoutMs || DEFAULT_TIMEOUT_MS;
643
- this._nextId = 0;
644
654
  this._flows = /* @__PURE__ */ new Map();
645
655
  this._registry = spawnFn ? null : safeRequireRegistry();
646
656
  }
@@ -665,7 +675,7 @@ var require_provider_login_manager = __commonJS({
665
675
  if (replaceAccount) await this._logout(agent, strategy, env);
666
676
  const [defaultBinary, ...args] = strategy.command;
667
677
  const binary = await this._resolveBinary({ agent }) || defaultBinary;
668
- const loginId = `login-${++this._nextId}`;
678
+ const loginId = `login-${randomUUID()}`;
669
679
  const child = this._spawnCommand(binary, args, {
670
680
  env,
671
681
  stdio: ["pipe", "pipe", "pipe"]
@@ -13947,8 +13957,7 @@ var require_headless_provider_service = __commonJS({
13947
13957
  var { ProviderLoginManager } = require_provider_login_manager();
13948
13958
  var {
13949
13959
  canSwitchAccount,
13950
- loginStrategyForAgent,
13951
- terminalLoginHint
13960
+ loginStrategyForRemoteAgent: headlessLoginStrategy
13952
13961
  } = require_provider_login();
13953
13962
  var { initializeCliAgentRegistry } = require_cli_agent_registry_bootstrap();
13954
13963
  var AGENT_IDS = Object.freeze(["claude", "codex", "antigravity", "opencode", "kimi", "grok", "cursor"]);
@@ -13989,16 +13998,12 @@ ${stderr || ""}`.trim() });
13989
13998
  acceptsCode: strategy.acceptsCode === true,
13990
13999
  codeHint: String(strategy.codeHint || "").slice(0, 200),
13991
14000
  command: Array.isArray(strategy.command) ? strategy.command.join(" ").slice(0, 300) : null,
13992
- terminalHint: terminalLoginHint(agent) || null,
14001
+ terminalHint: strategy.terminalHint || null,
13993
14002
  reason: String(strategy.reason || "").slice(0, 500),
13994
14003
  canSwitchAccount: false,
13995
14004
  status
13996
14005
  };
13997
14006
  }
13998
- function headlessLoginStrategy(agent) {
13999
- const strategy = loginStrategyForAgent(agent);
14000
- return agent === "codex" ? { ...strategy, command: ["codex", "login", "--device-auth"] } : strategy;
14001
- }
14002
14007
  var HeadlessProviderService = class extends EventEmitter {
14003
14008
  constructor({ registry, env = process.env, fsImpl = fs, execFileImpl = execFile, loginManager } = {}) {
14004
14009
  super();
@@ -14790,33 +14795,30 @@ CodeAgentSwarm shows two different things per agent:
14790
14795
  the context stays clear at a glance \u2014 name the feature, NOT a low-level step or file,
14791
14796
  and NOT a work PHASE (testing, e2e, validating, reviewing, deploying). Running the e2e
14792
14797
  suite for a settings redesign is titled "Settings Redesign", never "Settings E2E Tests".
14793
- Examples: "Promo Video for Twitter", "Minimize Agents". You set it ONCE at the start
14794
- and it almost never changes \u2014 the ONLY reasons to set it again are refining it when the
14795
- goal evolves, or REPLACING it when the agent pivots to a completely different topic.
14798
+ Derive it from the **first user request** that established this conversation's
14799
+ functionality and set it ONCE per conversation. It stays stable across follow-ups,
14800
+ new turns, tasks, reviews, tests, validation, finishing, and context compaction.
14796
14801
  2. **Current activity** = the product-focused step you are doing RIGHT NOW. Example:
14797
14802
  "Implementing the analytics tracking backend". It is shown only when the user hovers
14798
14803
  the agent, and it accumulates as the activity log behind the clock button.
14799
14804
 
14800
14805
  ## \u{1F4E2} THE RULE
14801
14806
 
14802
- **\u{1F6A6} FIRST ACTION when you start working in an agent:** set the GENERAL title right away,
14803
- before anything else, so the agent is NEVER without info. It is ALWAYS safe to call \u2014 if
14804
- the user manually renamed the agent the app keeps their name (a manual rename always wins
14805
- over an MCP title). Never leave an agent with no meaningful title.
14807
+ **\u{1F6A6} FIRST ACTION in a new conversation:** set the GENERAL title from the first user
14808
+ request. On later turns, do NOT call \`set_terminal_title\`; update activity instead.
14806
14809
 
14807
14810
  **\u{1F4CD} ALWAYS leave a CURRENT SUMMARY before acting.** Right after the title, call
14808
14811
  \`update_terminal_activity\` with your first product-focused step, BEFORE running any other
14809
14812
  tool. If an agent has no activity yet, set one before you do anything else \u2014 so at every
14810
14813
  moment the user can see what each agent is doing. Never start working with no activity set.
14811
14814
 
14812
- **At the START of work \u2192 set the GENERAL title ONCE. As you work \u2192 update the ACTIVITY often.**
14815
+ **First request \u2192 set the title ONCE. Every turn \u2192 update the ACTIVITY.**
14813
14816
 
14814
- - \u2705 Set the general title once so the user always knows, at a glance, what this agent
14815
- is for. Set it again only to REFINE it when the overall goal changes (e.g. the user
14816
- expands the feature), or to REPLACE it when the topic changes RADICALLY.
14817
+ - \u2705 Keep the title from the first user request. Refining, extending, fixing, reviewing,
14818
+ validating, or finishing that request changes the activity, not the title.
14817
14819
  - \u2705 RADICAL topic change \u2192 NEW general title, MANDATORY. When the user switches this
14818
14820
  agent to a completely different topic (e.g. from "Minimize Agents" to preparing
14819
- a release), call \`set_terminal_title\` again with the new topic. Updating only the
14821
+ a release), call \`set_terminal_title\` again with \`replace_existing=true\`. Updating only the
14820
14822
  activity and leaving the old title is WRONG: the tab would keep describing work that
14821
14823
  is no longer happening.
14822
14824
  - \u2705 Update the activity every time your focus moves to a new step. This is what the user
@@ -14830,7 +14832,7 @@ the app's own static UI stays in English; these agent-written labels follow the
14830
14832
 
14831
14833
  **\u{1F527} THE TWO TOOLS:**
14832
14834
  \`\`\`
14833
- # 1) ONCE at the start - the sticky, product-level tab title
14835
+ # 1) ONCE on the conversation's first request - the sticky tab title
14834
14836
  mcp__codeagentswarm-tasks__set_terminal_title(
14835
14837
  title="Promo Video",
14836
14838
  long_title="Promotional video for the Twitter launch"
@@ -14851,9 +14853,8 @@ mcp__codeagentswarm-tasks__update_terminal_activity(
14851
14853
  in the tab. \`long_title\` is the **GOAL**: one sentence on what this agent is FOR \u2014 the
14852
14854
  outcome the work aims at, not the step you are on. The user reads it in the hover under
14853
14855
  the title, labelled GOAL, and it is the ONLY place that answers "why does this agent
14854
- exist"; six words cannot carry that. Sticky: set once, refine rarely \u2014 but ALWAYS set a
14855
- fresh one when the agent pivots to a completely different topic. Never describe a
14856
- single step here.
14856
+ exist"; six words cannot carry that. Repeated calls preserve the first title. Pass
14857
+ \`replace_existing=true\` only after the user pivots to completely different functionality.
14857
14858
  \`\`\`
14858
14859
  \u2705 title="Orden notificaci\xF3n"
14859
14860
  long_title="Que las notificaciones no salgan antes de que cambie el status del agente"
@@ -14879,7 +14880,7 @@ mcp__codeagentswarm-tasks__update_terminal_activity(
14879
14880
  1. find_related_active_tasks(...) \u2190 Check first
14880
14881
  2. create_task(...) \u2190 If needed
14881
14882
  3. start_task(task_id=123)
14882
- 4. set_terminal_title( \u2190 ONCE, AFTER start_task (captures task_id)
14883
+ 4. set_terminal_title( \u2190 only if this conversation has no title
14883
14884
  title="Minimize Agents",
14884
14885
  long_title="Add the ability to minimize and restore agents"
14885
14886
  )
@@ -14888,7 +14889,7 @@ mcp__codeagentswarm-tasks__update_terminal_activity(
14888
14889
  7. ...keep calling update_terminal_activity as your focus moves to new steps...
14889
14890
  \`\`\`
14890
14891
 
14891
- **\u{1F6A8} CRITICAL:** call set_terminal_title AFTER start_task so the task_id is captured.
14892
+ **\u{1F6A8} CRITICAL:** starting another task in the same conversation does not justify retitling it.
14892
14893
 
14893
14894
  ### \u2705 WITHOUT A TASK (research/investigation):
14894
14895
  \`\`\`
@@ -14907,13 +14908,10 @@ update_terminal_activity(activity="Implementing restore-from-minimized")
14907
14908
  update_terminal_activity(activity="Persisting minimized state across restarts")
14908
14909
  \`\`\`
14909
14910
 
14910
- ### \u2705 THE GOAL ITSELF CHANGES \u2192 refine the GENERAL title (rare):
14911
+ ### \u2705 THE ORIGINAL GOAL EXPANDS \u2192 keep the title and update ACTIVITY:
14911
14912
  \`\`\`
14912
- # The user expanded the feature, so the product goal genuinely changed:
14913
- set_terminal_title(
14914
- title="Minimize & Restore",
14915
- long_title="Minimize agents and restore them later from a tray"
14916
- )
14913
+ # The user expanded the same feature; the first title remains "Minimize Agents":
14914
+ update_terminal_activity(activity="Adding restore from the tray")
14917
14915
  \`\`\`
14918
14916
 
14919
14917
  ### \u2705 RADICAL TOPIC CHANGE \u2192 set a NEW general title (mandatory):
@@ -14922,7 +14920,8 @@ set_terminal_title(
14922
14920
  User: "leave that, now let's prepare the release notes"
14923
14921
  set_terminal_title(
14924
14922
  title="Release Notes",
14925
- long_title="Preparing the release notes for the next version"
14923
+ long_title="Preparing the release notes for the next version",
14924
+ replace_existing=true
14926
14925
  )
14927
14926
  update_terminal_activity(activity="Drafting the release notes")
14928
14927
  # \u274C WRONG: calling only update_terminal_activity here \u2014 the tab would still
@@ -14931,9 +14930,8 @@ update_terminal_activity(activity="Drafting the release notes")
14931
14930
 
14932
14931
  ## \u26A0\uFE0F WHEN TO USE WHICH
14933
14932
 
14934
- - **General title (set_terminal_title):** once at the start; again when the overall
14935
- goal/feature changes or the topic changes radically. Aim for very few calls per
14936
- session \u2014 but NEVER leave a stale title on an agent that moved to another topic.
14933
+ - **General title (set_terminal_title):** once per conversation from the first request.
14934
+ Replace only after a radical functionality pivot, with \`replace_existing=true\`.
14937
14935
  - **Current activity (update_terminal_activity):** every time you move to a new product
14938
14936
  step. Call it freely - it never disturbs the tab.
14939
14937
 
@@ -15011,14 +15009,14 @@ update_terminal_activity(activity="Investigating the output handler cost") \u
15011
15009
  **Bad 3 - Never setting a general title:**
15012
15010
  \`\`\`
15013
15011
  # Only calling update_terminal_activity, so the tab never gets a product label.
15014
- \u2705 Always set_terminal_title ONCE at the start so the tab is meaningful.
15012
+ \u2705 Set the title ONCE from the first user request so the tab is meaningful.
15015
15013
  \`\`\`
15016
15014
 
15017
15015
  **Bad 4 - Radical topic change but the title never changes:**
15018
15016
  \`\`\`
15019
15017
  # Title: "Promo Video". The user then says: "now let's debug the login crash"
15020
15018
  update_terminal_activity(activity="Debugging the login crash") \u2190 \u274C tab still says "Promo Video"
15021
- \u2705 set_terminal_title(title="Login Crash", long_title="Debugging the crash when logging in")
15019
+ \u2705 set_terminal_title(title="Login Crash", long_title="Debugging the crash when logging in", replace_existing=true)
15022
15020
  \u2705 update_terminal_activity(activity="Reproducing the login crash")
15023
15021
  \`\`\`
15024
15022
 
@@ -15059,22 +15057,21 @@ Rules:
15059
15057
 
15060
15058
  ` : ""}## \u{1F6A8} FINAL REMINDER
15061
15059
 
15062
- - Set the **general title AND the GOAL once** at the start (\`set_terminal_title(title,
15063
- long_title)\` \u2014 always both); set them again when the goal changes or the topic pivots
15064
- radically \u2014 never leave a stale title.
15060
+ - Set the **general title AND GOAL once per conversation**, from the first user request.
15061
+ Follow-ups never retitle; a radical functionality pivot uses \`replace_existing=true\`.
15065
15062
  - Keep the **current activity** flowing with \`update_terminal_activity\` (product-level),
15066
15063
  one short sentence per step.
15067
15064
  ${includeStatus ? `- Keep the **status badge** honest with \`set_terminal_status\` at every phase change.
15068
15065
  ` : ""}- The tab should stay stable; the hover + clock-button log carry the detail.
15069
15066
 
15070
15067
  **MEMORIZE:**
15071
- ${includeStatus ? `1. **Start work \u2192 \`set_terminal_title(title, long_title)\` (ONCE, the tab label AND the GOAL sentence) + \`set_terminal_status(status="working")\`**
15068
+ ${includeStatus ? `1. **First request \u2192 \`set_terminal_title(title, long_title)\` once + \`set_terminal_status(status="working")\`**
15072
15069
  2. **New step \u2192 \`update_terminal_activity\` (product step)**
15073
15070
  3. **Phase changed (waiting / testing / done) \u2192 \`set_terminal_status\`**
15074
- 4. **Goal changed or RADICAL topic change \u2192 \`set_terminal_title\` again (refined or NEW title)**
15075
- 5. **\u{1F3C1} Finishing \u2192 final \`update_terminal_activity\` summary + \`set_terminal_status\` \u2192 \`complete_task\`**` : `1. **Start work \u2192 \`set_terminal_title(title, long_title)\` (ONCE, the tab label AND the GOAL sentence)**
15071
+ 4. **Radical functionality pivot \u2192 \`set_terminal_title(..., replace_existing=true)\`**
15072
+ 5. **\u{1F3C1} Finishing \u2192 final \`update_terminal_activity\` summary + \`set_terminal_status\` \u2192 \`complete_task\`**` : `1. **First request \u2192 \`set_terminal_title(title, long_title)\` once**
15076
15073
  2. **New step \u2192 \`update_terminal_activity\` (product step)**
15077
- 3. **Goal changed or RADICAL topic change \u2192 \`set_terminal_title\` again (refined or NEW title)**
15074
+ 3. **Radical functionality pivot \u2192 \`set_terminal_title(..., replace_existing=true)\`**
15078
15075
  4. **\u{1F3C1} Finishing \u2192 final \`update_terminal_activity\` summary \u2192 \`complete_task\`**`}
15079
15076
 
15080
15077
  ### \u{1F4DD} Task Plan Management
@@ -15322,8 +15319,9 @@ Avoid token limits:
15322
15319
  - Creating new? \u2192 YES
15323
15320
 
15324
15321
  **Agent Title?**
15325
- - ALWAYS set it immediately when starting work
15326
- - Radical topic change \u2192 set a NEW title (don't leave a stale one)
15322
+ - Set it from the first user request, once per conversation
15323
+ - Follow-up work changes activity, never the title
15324
+ - Radical functionality pivot \u2192 replace with \`replace_existing=true\`
15327
15325
 
15328
15326
  **Task Continuation?**
15329
15327
  - Same work <24h \u2192 Continue
@@ -15364,33 +15362,30 @@ CodeAgentSwarm shows two different things per agent:
15364
15362
  the context stays clear at a glance \u2014 name the feature, NOT a low-level step or file,
15365
15363
  and NOT a work PHASE (testing, e2e, validating, reviewing, deploying). Running the e2e
15366
15364
  suite for a settings redesign is titled "Settings Redesign", never "Settings E2E Tests".
15367
- Examples: "Promo Video for Twitter", "Minimize Agents". You set it ONCE at the start
15368
- and it almost never changes \u2014 the ONLY reasons to set it again are refining it when the
15369
- goal evolves, or REPLACING it when the agent pivots to a completely different topic.
15365
+ Derive it from the **first user request** that established this conversation's
15366
+ functionality and set it ONCE per conversation. It stays stable across follow-ups,
15367
+ new turns, tasks, reviews, tests, validation, finishing, and context compaction.
15370
15368
  2. **Current activity** = the product-focused step you are doing RIGHT NOW. Example:
15371
15369
  "Implementing the analytics tracking backend". It is shown only when the user hovers
15372
15370
  the agent, and it accumulates as the activity log behind the clock button.
15373
15371
 
15374
15372
  ## \u{1F4E2} THE RULE
15375
15373
 
15376
- **\u{1F6A6} FIRST ACTION when you start working in an agent:** set the GENERAL title right away,
15377
- before anything else, so the agent is NEVER without info. It is ALWAYS safe to call \u2014 if
15378
- the user manually renamed the agent the app keeps their name (a manual rename always wins
15379
- over an MCP title). Never leave an agent with no meaningful title.
15374
+ **\u{1F6A6} FIRST ACTION in a new conversation:** set the GENERAL title from the first user
15375
+ request. On later turns, do NOT call \`set_terminal_title\`; update activity instead.
15380
15376
 
15381
15377
  **\u{1F4CD} ALWAYS leave a CURRENT SUMMARY before acting.** Right after the title, call
15382
15378
  \`update_terminal_activity\` with your first product-focused step, BEFORE running any other
15383
15379
  tool. If an agent has no activity yet, set one before you do anything else \u2014 so at every
15384
15380
  moment the user can see what each agent is doing. Never start working with no activity set.
15385
15381
 
15386
- **At the START of work \u2192 set the GENERAL title ONCE. As you work \u2192 update the ACTIVITY often.**
15382
+ **First request \u2192 set the title ONCE. Every turn \u2192 update the ACTIVITY.**
15387
15383
 
15388
- - \u2705 Set the general title once so the user always knows, at a glance, what this agent
15389
- is for. Set it again only to REFINE it when the overall goal changes (e.g. the user
15390
- expands the feature), or to REPLACE it when the topic changes RADICALLY.
15384
+ - \u2705 Keep the title from the first user request. Refining, extending, fixing, reviewing,
15385
+ validating, or finishing that request changes the activity, not the title.
15391
15386
  - \u2705 RADICAL topic change \u2192 NEW general title, MANDATORY. When the user switches this
15392
15387
  agent to a completely different topic (e.g. from "Minimize Agents" to preparing
15393
- a release), call \`set_terminal_title\` again with the new topic. Updating only the
15388
+ a release), call \`set_terminal_title\` again with \`replace_existing=true\`. Updating only the
15394
15389
  activity and leaving the old title is WRONG: the tab would keep describing work that
15395
15390
  is no longer happening.
15396
15391
  - \u2705 Update the activity every time your focus moves to a new step. This is what the user
@@ -15404,7 +15399,7 @@ the app's own static UI stays in English; these agent-written labels follow the
15404
15399
 
15405
15400
  **\u{1F527} THE TWO TOOLS:**
15406
15401
  \`\`\`
15407
- # 1) ONCE at the start - the sticky, product-level tab title
15402
+ # 1) ONCE on the conversation's first request - the sticky tab title
15408
15403
  mcp__codeagentswarm-tasks__set_terminal_title(
15409
15404
  title="Promo Video",
15410
15405
  long_title="Promotional video for the Twitter launch"
@@ -15425,9 +15420,8 @@ mcp__codeagentswarm-tasks__update_terminal_activity(
15425
15420
  in the tab. \`long_title\` is the **GOAL**: one sentence on what this agent is FOR \u2014 the
15426
15421
  outcome the work aims at, not the step you are on. The user reads it in the hover under
15427
15422
  the title, labelled GOAL, and it is the ONLY place that answers "why does this agent
15428
- exist"; six words cannot carry that. Sticky: set once, refine rarely \u2014 but ALWAYS set a
15429
- fresh one when the agent pivots to a completely different topic. Never describe a
15430
- single step here.
15423
+ exist"; six words cannot carry that. Repeated calls preserve the first title. Pass
15424
+ \`replace_existing=true\` only after the user pivots to completely different functionality.
15431
15425
  \`\`\`
15432
15426
  \u2705 title="Orden notificaci\xF3n"
15433
15427
  long_title="Que las notificaciones no salgan antes de que cambie el status del agente"
@@ -15446,9 +15440,9 @@ mcp__codeagentswarm-tasks__update_terminal_activity(
15446
15440
 
15447
15441
  ## \u{1F3AF} WORKFLOW
15448
15442
 
15449
- ### \u2705 WHEN STARTING WORK:
15443
+ ### \u2705 ON THE CONVERSATION'S FIRST REQUEST:
15450
15444
  \`\`\`
15451
- 1. set_terminal_title( \u2190 ONCE, product goal
15445
+ 1. set_terminal_title( \u2190 once per conversation
15452
15446
  title="Minimize Agents",
15453
15447
  long_title="Add the ability to minimize and restore agents"
15454
15448
  )
@@ -15463,13 +15457,10 @@ update_terminal_activity(activity="Implementing restore-from-minimized")
15463
15457
  update_terminal_activity(activity="Persisting minimized state across restarts")
15464
15458
  \`\`\`
15465
15459
 
15466
- ### \u2705 THE GOAL ITSELF CHANGES \u2192 refine the GENERAL title (rare):
15460
+ ### \u2705 THE ORIGINAL GOAL EXPANDS \u2192 keep the title and update ACTIVITY:
15467
15461
  \`\`\`
15468
- # The user expanded the feature, so the product goal genuinely changed:
15469
- set_terminal_title(
15470
- title="Minimize & Restore",
15471
- long_title="Minimize agents and restore them later from a tray"
15472
- )
15462
+ # The user expanded the same feature; the first title remains "Minimize Agents":
15463
+ update_terminal_activity(activity="Adding restore from the tray")
15473
15464
  \`\`\`
15474
15465
 
15475
15466
  ### \u2705 RADICAL TOPIC CHANGE \u2192 set a NEW general title (mandatory):
@@ -15478,7 +15469,8 @@ set_terminal_title(
15478
15469
  User: "leave that, now let's prepare the release notes"
15479
15470
  set_terminal_title(
15480
15471
  title="Release Notes",
15481
- long_title="Preparing the release notes for the next version"
15472
+ long_title="Preparing the release notes for the next version",
15473
+ replace_existing=true
15482
15474
  )
15483
15475
  update_terminal_activity(activity="Drafting the release notes")
15484
15476
  # \u274C WRONG: calling only update_terminal_activity here \u2014 the tab would still
@@ -15487,9 +15479,8 @@ update_terminal_activity(activity="Drafting the release notes")
15487
15479
 
15488
15480
  ## \u26A0\uFE0F WHEN TO USE WHICH
15489
15481
 
15490
- - **General title (set_terminal_title):** once at the start; again when the overall
15491
- goal/feature changes or the topic changes radically. Aim for very few calls per
15492
- session \u2014 but NEVER leave a stale title on an agent that moved to another topic.
15482
+ - **General title (set_terminal_title):** once per conversation from the first request.
15483
+ Replace only after a radical functionality pivot, with \`replace_existing=true\`.
15493
15484
  - **Current activity (update_terminal_activity):** every time you move to a new product
15494
15485
  step. Call it freely - it never disturbs the tab.
15495
15486
 
@@ -15565,14 +15556,14 @@ update_terminal_activity(activity="Investigating the output handler cost") \u
15565
15556
  **Bad 3 - Never setting a general title:**
15566
15557
  \`\`\`
15567
15558
  # Only calling update_terminal_activity, so the tab never gets a product label.
15568
- \u2705 Always set_terminal_title ONCE at the start so the tab is meaningful.
15559
+ \u2705 Set the title ONCE from the first user request so the tab is meaningful.
15569
15560
  \`\`\`
15570
15561
 
15571
15562
  **Bad 4 - Radical topic change but the title never changes:**
15572
15563
  \`\`\`
15573
15564
  # Title: "Promo Video". The user then says: "now let's debug the login crash"
15574
15565
  update_terminal_activity(activity="Debugging the login crash") \u2190 \u274C tab still says "Promo Video"
15575
- \u2705 set_terminal_title(title="Login Crash", long_title="Debugging the crash when logging in")
15566
+ \u2705 set_terminal_title(title="Login Crash", long_title="Debugging the crash when logging in", replace_existing=true)
15576
15567
  \u2705 update_terminal_activity(activity="Reproducing the login crash")
15577
15568
  \`\`\`
15578
15569
 
@@ -15587,26 +15578,25 @@ set_terminal_title(title="Settings E2E Tests") \u2190 \u274C testing is a STEP
15587
15578
 
15588
15579
  ## \u{1F6A8} FINAL REMINDER
15589
15580
 
15590
- - Set the **general title AND the GOAL once** at the start (\`set_terminal_title(title,
15591
- long_title)\` \u2014 always both); set them again when the goal changes or the topic pivots
15592
- radically \u2014 never leave a stale title.
15581
+ - Set the **general title AND GOAL once per conversation**, from the first user request.
15582
+ Follow-ups never retitle; a radical functionality pivot uses \`replace_existing=true\`.
15593
15583
  - Keep the **current activity** flowing with \`update_terminal_activity\` (product-level),
15594
15584
  one short sentence per step.
15595
15585
  - The tab should stay stable; the hover + clock-button log carry the detail.
15596
15586
  - Do NOT create or manage tasks \u2014 automatic task management is disabled.
15597
15587
 
15598
15588
  **MEMORIZE:**
15599
- 1. **Start work \u2192 \`set_terminal_title(title, long_title)\` (ONCE, the tab label AND the GOAL sentence)**
15589
+ 1. **First request \u2192 \`set_terminal_title(title, long_title)\` once**
15600
15590
  2. **New step \u2192 \`update_terminal_activity\` (product step)**
15601
- 3. **Goal changed or RADICAL topic change \u2192 \`set_terminal_title\` again (refined or NEW title)**
15591
+ 3. **Radical functionality pivot \u2192 \`set_terminal_title(..., replace_existing=true)\`**
15602
15592
  4. **\u{1F3C1} Finishing \u2192 final \`update_terminal_activity\` summary**
15603
15593
 
15604
15594
  ## Quick Decision Guide
15605
15595
 
15606
15596
  **Agent Title?**
15607
- - ALWAYS set it immediately when starting work
15608
- - Refine when the overall goal changes
15609
- - Radical topic change \u2192 set a NEW title (don't leave a stale one)
15597
+ - Set it from the first user request, once per conversation
15598
+ - Follow-up work changes activity, never the title
15599
+ - Radical functionality pivot \u2192 replace with \`replace_existing=true\`
15610
15600
 
15611
15601
  **Tasks?**
15612
15602
  - Automatic task management is DISABLED \u2014 never create or manage tasks
@@ -18774,7 +18764,8 @@ var require_session_communication_env = __commonJS({
18774
18764
  // src/infrastructure/agent-drivers/provider-auth.js
18775
18765
  var require_provider_auth = __commonJS({
18776
18766
  "src/infrastructure/agent-drivers/provider-auth.js"(exports2, module2) {
18777
- var { canLoginFromChat } = require_provider_login();
18767
+ var { canLoginFromChat, canSwitchAccount } = require_provider_login();
18768
+ var { isModelUsageLimitError } = require_provider_error_presentation();
18778
18769
  var PROVIDER_AUTH = Object.freeze({
18779
18770
  claude: {
18780
18771
  label: "Claude Code",
@@ -18807,6 +18798,7 @@ var require_provider_auth = __commonJS({
18807
18798
  });
18808
18799
  var AUTH_ERROR_PATTERNS = Object.freeze([
18809
18800
  /\bnot logged in\b/i,
18801
+ /\bnot signed in\b/i,
18810
18802
  /\bnot authenticated\b/i,
18811
18803
  /\bunauthenticated\b/i,
18812
18804
  /\bauthentication required\b/i,
@@ -18909,6 +18901,16 @@ var require_provider_auth = __commonJS({
18909
18901
  }
18910
18902
  return createProviderUnavailableStatus(agent);
18911
18903
  }
18904
+ function providerErrorLoginAction(agent, message) {
18905
+ const auth = classifyProviderAuthError(agent, message);
18906
+ const usageLimit = isModelUsageLimitError({ provider: agent, payload: { message } });
18907
+ if (!auth && !usageLimit) return null;
18908
+ const replaceAccount = (auth == null ? void 0 : auth.switchAccount) === true || usageLimit && canSwitchAccount(agent);
18909
+ return {
18910
+ action: replaceAccount ? "switch-account" : "start",
18911
+ label: replaceAccount ? "Change account" : canLoginFromChat(agent) ? "Sign in" : "Sign-in options"
18912
+ };
18913
+ }
18912
18914
  function serializeProviderError(agent, error) {
18913
18915
  const message = String(error && error.message ? error.message : error || "Provider error");
18914
18916
  const providerStatus = classifyProviderStartupError(agent, error);
@@ -18930,6 +18932,7 @@ var require_provider_auth = __commonJS({
18930
18932
  createProviderUnavailableStatus,
18931
18933
  createUnauthenticatedStatus,
18932
18934
  providerAuthMetadata,
18935
+ providerErrorLoginAction,
18933
18936
  serializeProviderError
18934
18937
  };
18935
18938
  }
@@ -19169,7 +19172,6 @@ var require_codex_app_server_driver = __commonJS({
19169
19172
  var DEFAULT_SERVICE_TIER_ID = "default";
19170
19173
  var EPHEMERAL_CODEX_HOME_PREFIX = "codeagentswarm-codex-home-";
19171
19174
  var EPHEMERAL_CODEX_HOME_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
19172
- var REQUIRED_TASK_MCP_TOOL = "check_active";
19173
19175
  var TEXT_ONLY_INSTRUCTIONS = "Answer with plain text only. Do not call tools, commands, apps, skills, web search, MCP servers, or subagents.";
19174
19176
  var IMAGE_ONLY_INSTRUCTIONS = "Generate exactly one image with the built-in image generation capability. Do not call commands, apps, skills, web search, MCP servers, subagents, or any other tool. Do not return prose.";
19175
19177
  var TEXT_ONLY_CONFIG_OVERRIDES = Object.freeze([
@@ -19326,10 +19328,6 @@ var require_codex_app_server_driver = __commonJS({
19326
19328
  * @param {{name: string, title: string, version: string}} [options.clientInfo]
19327
19329
  * @param {number} [options.requestTimeoutMs=30000] Per-request timeout.
19328
19330
  * @param {Function} [options.spawnFn] Injectable spawn, for tests.
19329
- * @param {string} [options.requiredMcpServer] MCP server whose activation
19330
- * check is repaired before the first turn when possible.
19331
- * @param {Function} [options.repairMcpConfig] Repairs the on-disk MCP config
19332
- * before the running app-server reloads it.
19333
19331
  * @param {{register: Function, unregister: Function}} [options.processRegistry]
19334
19332
  * PID registry the child is tracked in; injectable for tests.
19335
19333
  */
@@ -19339,9 +19337,7 @@ var require_codex_app_server_driver = __commonJS({
19339
19337
  clientInfo,
19340
19338
  requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
19341
19339
  spawnFn,
19342
- processRegistry,
19343
- requiredMcpServer,
19344
- repairMcpConfig
19340
+ processRegistry
19345
19341
  } = {}) {
19346
19342
  super();
19347
19343
  this._binaryPath = binaryPath;
@@ -19350,10 +19346,11 @@ var require_codex_app_server_driver = __commonJS({
19350
19346
  this._requestTimeoutMs = requestTimeoutMs;
19351
19347
  this._spawnFn = spawnFn || spawn;
19352
19348
  this._processRegistry = processRegistry || require_spawned_process_registry();
19353
- this._requiredMcpServer = requiredMcpServer || null;
19354
- this._repairMcpConfig = repairMcpConfig || null;
19355
19349
  this._child = null;
19356
19350
  this._pendingRequests = /* @__PURE__ */ new Map();
19351
+ this._taskMcpStartup = null;
19352
+ this._taskMcpWaiters = /* @__PURE__ */ new Set();
19353
+ this._inputInterruptVersion = 0;
19357
19354
  this._pendingApprovals = /* @__PURE__ */ new Map();
19358
19355
  this._pendingQuestions = /* @__PURE__ */ new Map();
19359
19356
  this._nextRequestId = 1;
@@ -19488,6 +19485,10 @@ var require_codex_app_server_driver = __commonJS({
19488
19485
  });
19489
19486
  return { turnId: activeTurnId, steered: true };
19490
19487
  }
19488
+ const interruptVersion = this._inputInterruptVersion;
19489
+ await this._waitForTaskMcp();
19490
+ if (interruptVersion !== this._inputInterruptVersion) throw new Error("CodeAgentSwarm message cancelled while tools were starting.");
19491
+ if (this._stopping || this._exited) throw new Error("Codex session stopped");
19491
19492
  const result = await this._request("turn/start", {
19492
19493
  threadId: this._threadId,
19493
19494
  input,
@@ -19496,6 +19497,35 @@ var require_codex_app_server_driver = __commonJS({
19496
19497
  this._activeTurnId = (_a = result == null ? void 0 : result.turn) == null ? void 0 : _a.id;
19497
19498
  return { turnId: this._activeTurnId };
19498
19499
  }
19500
+ async _waitForTaskMcp() {
19501
+ var _a;
19502
+ if (!this._taskMcpStartup) {
19503
+ const { config } = await this._request("config/read", { cwd: this._sessionCwd, includeLayers: false });
19504
+ const server = (_a = config == null ? void 0 : config.mcp_servers) == null ? void 0 : _a["codeagentswarm-tasks"];
19505
+ this._taskMcpStartup ||= {
19506
+ threadId: this._threadId,
19507
+ status: server && server.enabled !== false ? "starting" : "ready"
19508
+ };
19509
+ }
19510
+ if (this._taskMcpStartup.threadId !== this._threadId) return;
19511
+ if (this._taskMcpStartup.status === "starting") {
19512
+ await new Promise((resolve, reject) => {
19513
+ const finish = (error) => {
19514
+ clearTimeout(timer);
19515
+ this._taskMcpWaiters.delete(finish);
19516
+ if (error) reject(error);
19517
+ else resolve();
19518
+ };
19519
+ const timer = setTimeout(() => finish(new Error(
19520
+ "CodeAgentSwarm tools are still starting. Retry sending your message."
19521
+ )), this._requestTimeoutMs);
19522
+ this._taskMcpWaiters.add(finish);
19523
+ });
19524
+ }
19525
+ if (this._taskMcpStartup.status !== "ready") {
19526
+ throw new Error("CodeAgentSwarm tools could not start. Reopen this conversation to retry.");
19527
+ }
19528
+ }
19499
19529
  async _flushQueuedTurnInputs() {
19500
19530
  if (this._flushingQueuedTurnInputs || this._activeTurnId) return;
19501
19531
  this._flushingQueuedTurnInputs = true;
@@ -19520,6 +19550,8 @@ var require_codex_app_server_driver = __commonJS({
19520
19550
  * @returns {Promise<void>}
19521
19551
  */
19522
19552
  async interruptTurn() {
19553
+ this._inputInterruptVersion += 1;
19554
+ for (const finish of this._taskMcpWaiters) finish(new Error("CodeAgentSwarm message cancelled while tools were starting."));
19523
19555
  if (!this._activeTurnId) return void 0;
19524
19556
  await this._request("turn/interrupt", {
19525
19557
  threadId: this._threadId,
@@ -19898,7 +19930,7 @@ var require_codex_app_server_driver = __commonJS({
19898
19930
  initialTurnsPage: {
19899
19931
  limit: INITIAL_HISTORY_TURN_PAGE_SIZE,
19900
19932
  sortDirection: "desc",
19901
- itemsView: "summary"
19933
+ itemsView: "full"
19902
19934
  },
19903
19935
  ...threadParams
19904
19936
  });
@@ -19924,13 +19956,6 @@ var require_codex_app_server_driver = __commonJS({
19924
19956
  }
19925
19957
  }
19926
19958
  this._threadId = ((_a = result == null ? void 0 : result.thread) == null ? void 0 : _a.id) || (result == null ? void 0 : result.threadId) || (resumeSessionId || null);
19927
- if (!toolsDisabled && !imageGenerationOnly) {
19928
- try {
19929
- await this._ensureRequiredMcpServer(this._threadId);
19930
- } catch (error) {
19931
- console.warn(`[CodexAppServerDriver] Continuing without verified task MCP tools: ${error.message}`);
19932
- }
19933
- }
19934
19959
  this._sessionCwd = (result == null ? void 0 : result.cwd) || ((_b = result == null ? void 0 : result.thread) == null ? void 0 : _b.cwd) || cwd;
19935
19960
  let historyPage = result == null ? void 0 : result.initialTurnsPage;
19936
19961
  const returnedTurns = Array.isArray((_c = result == null ? void 0 : result.thread) == null ? void 0 : _c.turns) ? result.thread.turns : [];
@@ -19940,7 +19965,7 @@ var require_codex_app_server_driver = __commonJS({
19940
19965
  threadId: this._threadId,
19941
19966
  limit: INITIAL_HISTORY_TURN_PAGE_SIZE,
19942
19967
  sortDirection: "desc",
19943
- itemsView: "summary"
19968
+ itemsView: "full"
19944
19969
  });
19945
19970
  } catch (_) {
19946
19971
  }
@@ -19989,15 +20014,6 @@ var require_codex_app_server_driver = __commonJS({
19989
20014
  } while (cursor);
19990
20015
  return servers;
19991
20016
  }
19992
- async _ensureRequiredMcpServer(threadId) {
19993
- if (!this._requiredMcpServer) return;
19994
- const connected = (servers) => servers.some((server) => (server == null ? void 0 : server.name) === this._requiredMcpServer && Object.values(server.tools || {}).some((tool) => (tool == null ? void 0 : tool.name) === REQUIRED_TASK_MCP_TOOL));
19995
- if (connected(await this._listMcpServers(threadId))) return;
19996
- if (this._repairMcpConfig && await this._repairMcpConfig() === false) {
19997
- throw new Error("CodeAgentSwarm could not repair the Codex MCP configuration. Restart CodeAgentSwarm and retry Chat.");
19998
- }
19999
- await this._request("config/mcpServer/reload", null);
20000
- }
20001
20017
  /**
20002
20018
  * Build a resumed thread's past items as historical `item.completed` events.
20003
20019
  *
@@ -20224,6 +20240,7 @@ var require_codex_app_server_driver = __commonJS({
20224
20240
  * @param {Error} error
20225
20241
  */
20226
20242
  _rejectAllPending(error) {
20243
+ for (const finish of this._taskMcpWaiters) finish(error);
20227
20244
  for (const pending of this._pendingRequests.values()) {
20228
20245
  clearTimeout(pending.timer);
20229
20246
  pending.reject(error);
@@ -20305,6 +20322,15 @@ var require_codex_app_server_driver = __commonJS({
20305
20322
  */
20306
20323
  _handleNotification({ method, params }) {
20307
20324
  const notifThreadId = params && (params.threadId || params.thread && params.thread.id);
20325
+ if (method === "mcpServer/startupStatus/updated") {
20326
+ if ((params == null ? void 0 : params.name) === "codeagentswarm-tasks" && notifThreadId && (!this._threadId || notifThreadId === this._threadId)) {
20327
+ this._taskMcpStartup = params;
20328
+ if (params.status !== "starting") {
20329
+ for (const finish of this._taskMcpWaiters) finish();
20330
+ }
20331
+ }
20332
+ return;
20333
+ }
20308
20334
  if (notifThreadId && this._threadId && notifThreadId !== this._threadId) {
20309
20335
  this._handleChildNotification(notifThreadId, method, params);
20310
20336
  return;
@@ -27641,7 +27667,7 @@ var require_database_mcp_standalone = __commonJS({
27641
27667
  { status_key: "needs_testing", label: "Needs testing", color: "#3b82f6", icon: "flask-conical", sort_order: 2, is_default: 1, agent_settable: 1, prompt: "Set it when you finish the implementation and the work is pending the user testing it manually. Do not set it if there are still things left to implement." },
27642
27668
  { status_key: "working", label: "Working", color: "#fbbf24", icon: "hammer", sort_order: 3, is_default: 1, agent_settable: 1, prompt: "Set it when you start working on any request and while you are implementing, investigating or fixing something." },
27643
27669
  { status_key: "done", label: "Done", color: "#22c55e", icon: "circle-check", sort_order: 4, is_default: 1, agent_settable: 1, prompt: "Set it when the work is completely finished: implemented, validated and with its commit/push done when applicable. It is the final state." },
27644
- { status_key: "idle", label: "Idle", color: "#6b7280", icon: "circle-dashed", sort_order: 5, is_default: 1, agent_settable: 0, prompt: "Set by the app on an agent that has just been opened and has not been given any work yet. Agents cannot set this status; it clears itself as soon as you send the agent something." }
27670
+ { status_key: "idle", label: "Idle", color: "#6b7280", icon: "circle-dashed", sort_order: 5, is_default: 1, agent_settable: 0, prompt: "Set by the app when an agent is resting without an explicit work status, including after a reply finishes. It does not mean the work is complete or that user input is required. Agents cannot set this status." }
27645
27671
  ];
27646
27672
  var IDLE_TERMINAL_STATUS_KEY = "idle";
27647
27673
  var IDLE_STATUS_SEEDED_SETTING = "terminal_status_idle_seeded";
@@ -27653,7 +27679,8 @@ var require_database_mcp_standalone = __commonJS({
27653
27679
  blocked: 5,
27654
27680
  done: 6
27655
27681
  };
27656
- var LEGACY_SPANISH_DEFAULT_PROMPTS = {
27682
+ var LEGACY_DEFAULT_PROMPTS = {
27683
+ idle: "Set by the app on an agent that has just been opened and has not been given any work yet. Agents cannot set this status; it clears itself as soon as you send the agent something.",
27657
27684
  working: "Ponlo al empezar a trabajar en cualquier petici\xF3n y mientras est\xE9s implementando, investigando o arreglando algo.",
27658
27685
  needs_input: "Ponlo cuando pares porque necesites una respuesta o decisi\xF3n del usuario para continuar (una pregunta, una elecci\xF3n de dise\xF1o, un permiso).",
27659
27686
  needs_testing: "Ponlo cuando termines la implementaci\xF3n y el trabajo quede pendiente de que el usuario lo pruebe a mano. No lo pongas si a\xFAn quedan cosas por implementar.",
@@ -27667,14 +27694,14 @@ var require_database_mcp_standalone = __commonJS({
27667
27694
  label: "Blocked",
27668
27695
  color: "#ef4444",
27669
27696
  prompt_en: "Set it when you cannot make progress due to something external that does not depend on you or the user: broken CI, a dependency that is down, failing permissions, a third-party bug.",
27670
- prompt_es: LEGACY_SPANISH_DEFAULT_PROMPTS.blocked
27697
+ prompt_es: LEGACY_DEFAULT_PROMPTS.blocked
27671
27698
  },
27672
27699
  {
27673
27700
  status_key: "pending_commit",
27674
27701
  label: "Pending commit/push",
27675
27702
  color: "#a78bfa",
27676
27703
  prompt_en: "Set it when the user has already validated the work and only the commit or push remains.",
27677
- prompt_es: LEGACY_SPANISH_DEFAULT_PROMPTS.pending_commit
27704
+ prompt_es: LEGACY_DEFAULT_PROMPTS.pending_commit
27678
27705
  }
27679
27706
  ];
27680
27707
  function findSqlite3Path() {
@@ -28011,13 +28038,13 @@ SELECT COUNT(*) AS count FROM terminal_statuses`);
28011
28038
  console.error("[MCP Database] Error migrating idle terminal status:", error.message);
28012
28039
  }
28013
28040
  }
28014
- // Migration: upgrade the legacy SPANISH factory prompts to the English ones.
28041
+ // Migration: upgrade the legacy factory prompts to the English ones.
28015
28042
  // Only default statuses whose prompt is still the legacy string VERBATIM (never
28016
28043
  // edited by the user) are touched. Idempotent. Mirrors database.js.
28017
28044
  migrateLegacyTerminalStatusPrompts() {
28018
28045
  try {
28019
28046
  for (const status of DEFAULT_TERMINAL_STATUSES) {
28020
- const legacy = LEGACY_SPANISH_DEFAULT_PROMPTS[status.status_key];
28047
+ const legacy = LEGACY_DEFAULT_PROMPTS[status.status_key];
28021
28048
  if (!legacy) continue;
28022
28049
  this.execSQL(
28023
28050
  `UPDATE terminal_statuses SET prompt = ?
@@ -30375,6 +30402,18 @@ var require_mcp_stdio_server = __commonJS({
30375
30402
  } catch (e) {
30376
30403
  }
30377
30404
  }
30405
+ _hasNudgeMarker(kind) {
30406
+ try {
30407
+ const os2 = require("os");
30408
+ const fs2 = require("fs");
30409
+ const stateDir = path.join(os2.homedir(), ".codeagentswarm", "terminal-nudge-state");
30410
+ const rawKey = process.env.CODEAGENTSWARM_TERMINAL_ID || process.env.CODEAGENTSWARM_CURRENT_QUADRANT;
30411
+ const key = String(rawKey || "").replace(/[^A-Za-z0-9_-]/g, "_");
30412
+ return !!key && fs2.existsSync(path.join(stateDir, `${key}.${kind}`));
30413
+ } catch (e) {
30414
+ return false;
30415
+ }
30416
+ }
30378
30417
  // Resolve the current terminal id (1-based) from the environment.
30379
30418
  _getCurrentTerminalId() {
30380
30419
  const terminalId = process.env.CODEAGENTSWARM_CURRENT_QUADRANT;
@@ -30393,20 +30432,30 @@ var require_mcp_stdio_server = __commonJS({
30393
30432
  }
30394
30433
  // Set the GENERAL terminal title: the sticky tab label that represents the
30395
30434
  // product-level goal of what this terminal is working on. Set it ONCE at the
30396
- // start of work; call it again only to refine when the overall functionality
30397
- // changes, or to replace it when the terminal pivots to a radically different
30398
- // topic. It does NOT represent the current step — use update_terminal_activity
30399
- // for that.
30435
+ // start of the conversation. Later calls preserve it unless the caller explicitly
30436
+ // declares a radical topic change. It does NOT represent the current step — use
30437
+ // update_terminal_activity for that.
30400
30438
  async setTerminalTitle(params) {
30401
- const { title, long_title } = params;
30439
+ const { title, long_title, replace_existing } = params;
30402
30440
  if (!title) {
30403
30441
  throw new Error("title is required");
30404
30442
  }
30405
30443
  if (isPlaceholderTitle(title)) {
30406
30444
  throw new Error(`"${title}" looks like example text copied from the tool documentation, not a real title. Call the tool again with a title that describes the ACTUAL feature or goal you are working on in this agent (max 6 words).`);
30407
30445
  }
30408
- const shortTitle = this.generateShortTitle(title);
30446
+ if (replace_existing !== void 0 && typeof replace_existing !== "boolean") {
30447
+ throw new Error("replace_existing must be a boolean");
30448
+ }
30409
30449
  const terminalId = this._getCurrentTerminalId();
30450
+ if (replace_existing !== true && this._hasNudgeMarker("title")) {
30451
+ return {
30452
+ terminal_id: terminalId,
30453
+ updated: false,
30454
+ preserved: true,
30455
+ message: "The existing conversation title was preserved. Use update_terminal_activity for follow-up work; set replace_existing=true only after the user pivots to a completely different functionality."
30456
+ };
30457
+ }
30458
+ const shortTitle = this.generateShortTitle(title);
30410
30459
  const currentTask = this._getCurrentTask(terminalId);
30411
30460
  const taskId = currentTask ? currentTask.id : null;
30412
30461
  let longTitle = long_title;
@@ -31304,7 +31353,7 @@ Use 'clear' to remove the status.`;
31304
31353
  },
31305
31354
  {
31306
31355
  name: "set_terminal_title",
31307
- description: 'Set the GENERAL agent title AND the agent GOAL \u2014 pass BOTH arguments, because the user sees them together. `title` is the sticky Agent tab label at the FEATURE level (e.g. "Promo video for Twitter", "Minimize agents"); `long_title` is one sentence on what this agent is FOR, shown in the hover under the title. Keep the title high-level: name the feature, not a low-level step or work phase. Set it once at the start, then only refine it when the overall goal changes. Use update_terminal_activity for the current step. LANGUAGE: write BOTH in the SAME language the user is speaking.',
31356
+ description: "Set the GENERAL agent title AND the agent GOAL from the first user request that established this conversation's topic. Pass BOTH text arguments because the user sees them together. This is once per conversation, NOT once per user message, turn, task, review, validation, or resumed session. Follow-up work belongs in update_terminal_activity and repeated calls preserve the original title. Only a user pivot to a completely different functionality may replace it, by passing replace_existing=true. LANGUAGE: write both text fields in the SAME language the user is speaking.",
31308
31357
  inputSchema: {
31309
31358
  type: "object",
31310
31359
  properties: {
@@ -31315,6 +31364,10 @@ Use 'clear' to remove the status.`;
31315
31364
  long_title: {
31316
31365
  type: "string",
31317
31366
  description: `The agent GOAL: one sentence saying what this agent is FOR \u2014 the outcome the work is aiming at, not the current step. ALWAYS provide it: the user reads it in the Agent hover, labelled GOAL, under the short title. Write the real goal in the user's language. Do NOT restate the title or prefix it with "Working on:".`
31367
+ },
31368
+ replace_existing: {
31369
+ type: "boolean",
31370
+ description: "Leave false or omit for normal follow-up work. Set true ONLY when the user explicitly pivots this existing conversation to a completely different functionality; reviews, tests, fixes, implementation phases, and refinements of the original request are activities and must not replace the title."
31318
31371
  }
31319
31372
  },
31320
31373
  // long_title is NOT in `required` on purpose: some agent clients
@@ -31768,7 +31821,8 @@ Use 'clear' to remove the status.`;
31768
31821
  case "set_terminal_title":
31769
31822
  result = await this.setTerminalTitle({
31770
31823
  title: args.title,
31771
- long_title: args.long_title
31824
+ long_title: args.long_title,
31825
+ replace_existing: args.replace_existing
31772
31826
  });
31773
31827
  break;
31774
31828
  case "update_terminal_activity":
@@ -33593,18 +33647,13 @@ var require_driver_chat_manager = __commonJS({
33593
33647
  });
33594
33648
  });
33595
33649
  }
33596
- function defaultCreateDriver({ agent, env, binaryPath, requiredMcpServer, repairMcpConfig }) {
33650
+ function defaultCreateDriver({ agent, env, binaryPath }) {
33597
33651
  if (agent === "claude") return new ClaudeAgentSdkDriver({ env, binaryPath });
33598
33652
  if (agent === "antigravity") return new AntigravityPrintDriver({ env, binaryPath });
33599
33653
  if (agent === "opencode" || agent === "kimi" || agent === "grok" || agent === "cursor") {
33600
33654
  return new AcpAgentDriver({ provider: agent, env, binaryPath });
33601
33655
  }
33602
- return new CodexAppServerDriver({
33603
- env,
33604
- binaryPath,
33605
- requiredMcpServer,
33606
- repairMcpConfig
33607
- });
33656
+ return new CodexAppServerDriver({ env, binaryPath });
33608
33657
  }
33609
33658
  var DriverChatManager = class extends EventEmitter {
33610
33659
  /**
@@ -33731,12 +33780,11 @@ var require_driver_chat_manager = __commonJS({
33731
33780
  useWorktree = false,
33732
33781
  worktreeTitle,
33733
33782
  clientRequestId
33734
- } = {}, { signal } = {}) {
33783
+ } = {}, { signal, sessionId = crypto.randomUUID() } = {}) {
33735
33784
  var _a, _b, _c;
33736
33785
  if (!SUPPORTED_AGENTS.includes(agent)) {
33737
33786
  throw new Error(`Unsupported driver chat agent: ${agent}`);
33738
33787
  }
33739
- const sessionId = crypto.randomUUID();
33740
33788
  if (clientRequestId) {
33741
33789
  this.emit(SESSION_STARTING, {
33742
33790
  sessionId,
@@ -33871,6 +33919,7 @@ var require_driver_chat_manager = __commonJS({
33871
33919
  // main-process placeholder, so return that same UUID for persistence
33872
33920
  // and instance-safe MCP notification routing.
33873
33921
  terminalUuid: env.CODEAGENTSWARM_TERMINAL_ID || null,
33922
+ communicationEnabled: env.CODEAGENTSWARM_SESSION_COMMUNICATION_ENABLED === "1",
33874
33923
  model: session.model,
33875
33924
  effort: session.effort || resolvedEffort,
33876
33925
  serviceTier: session.serviceTier ?? (rejected.has("serviceTier") ? null : (_a = selections.find((selection) => (selection == null ? void 0 : selection.id) === "serviceTier")) == null ? void 0 : _a.value) ?? null,
@@ -34210,6 +34259,11 @@ var require_driver_chat_manager = __commonJS({
34210
34259
  await this._teardownSession(sessionId);
34211
34260
  return { stopped: true };
34212
34261
  }
34262
+ /** Replace the process while keeping the host-owned conversation identity. */
34263
+ async restartSession(sessionId, options) {
34264
+ await this._teardownSession(sessionId);
34265
+ return this.startSession(options, { sessionId });
34266
+ }
34213
34267
  /**
34214
34268
  * Stops every live session (app quit).
34215
34269
  *
@@ -34629,11 +34683,13 @@ var require_chat_history_pagination = __commonJS({
34629
34683
  return Date.parse(value);
34630
34684
  }
34631
34685
  function normalizeConversationMessages(messages) {
34686
+ let internalTurn = false;
34632
34687
  return (Array.isArray(messages) ? messages : []).flatMap((message) => {
34633
34688
  const role = (message == null ? void 0 : message.role) === "assistant" ? "assistant_message" : (message == null ? void 0 : message.role) === "user" ? "user_message" : null;
34634
34689
  const text = [message == null ? void 0 : message.content, message == null ? void 0 : message.text, message == null ? void 0 : message.displayText].find((value) => typeof value === "string" && value.trim());
34635
34690
  const attachments = Array.isArray(message == null ? void 0 : message.attachments) ? message.attachments : [];
34636
- return role && (text || attachments.length) && !(role === "user_message" && text && parseSessionCoordinationPrompt(text)) ? [{ role, text: text || "", attachments, timestamp: message.timestamp }] : [];
34691
+ if (role === "user_message") internalTurn = Boolean(text && parseSessionCoordinationPrompt(text));
34692
+ return role && (text || attachments.length) && !internalTurn ? [{ role, text: text || "", attachments, timestamp: message.timestamp }] : [];
34637
34693
  });
34638
34694
  }
34639
34695
  function boundedConversationMessages(messages, { limit = 30, maxMessageChars = 8e3, maxBytes = 22e4 } = {}) {
@@ -34684,12 +34740,14 @@ var require_chat_history_pagination = __commonJS({
34684
34740
  before,
34685
34741
  anchor,
34686
34742
  knownCount = 0,
34743
+ hydratedCount = 0,
34687
34744
  limit = DEFAULT_HISTORY_PAGE_SIZE
34688
34745
  } = {}) {
34689
34746
  const normalized = normalizeConversationMessages(messages);
34690
34747
  const safeLimit = Number.isSafeInteger(limit) && limit > 0 ? limit : DEFAULT_HISTORY_PAGE_SIZE;
34691
34748
  const anchorIndex = findConversationAnchor(normalized, anchor);
34692
- const end = Number.isSafeInteger(before) ? Math.max(0, Math.min(before, normalized.length)) : anchorIndex ?? Math.max(0, normalized.length - Math.max(0, Number(knownCount) || 0));
34749
+ const hydratedEnd = Math.max(0, normalized.length - Math.max(0, Number(hydratedCount) || 0));
34750
+ const end = Number.isSafeInteger(before) ? Math.max(0, Math.min(before, normalized.length)) : Math.min(hydratedEnd, anchorIndex ?? Math.max(0, normalized.length - Math.max(0, Number(knownCount) || 0)));
34693
34751
  const start = Math.max(0, end - safeLimit);
34694
34752
  return {
34695
34753
  messages: normalized.slice(start, end).map((message, offset) => ({
@@ -34950,10 +35008,12 @@ var require_mobile_runtime = __commonJS({
34950
35008
  var { parseSessionCoordinationPrompt } = require_session_coordination_message();
34951
35009
  var {
34952
35010
  conversationMessagesToEvents,
35011
+ normalizeConversationMessages,
34953
35012
  pageConversationMessages
34954
35013
  } = require_chat_history_pagination();
34955
35014
  var PROTOCOL_VERSION = 2;
34956
35015
  var SESSION_SUBSCRIPTIONS_FEATURE = "session-subscriptions";
35016
+ var SNAPSHOT_PROJECT_ICONS_FEATURE = "snapshot-project-icons";
34957
35017
  var SUBSCRIPTION_ONLY_EVENT_TYPES = /* @__PURE__ */ new Set([
34958
35018
  "session.config.updated",
34959
35019
  "session.commands.updated",
@@ -35255,6 +35315,7 @@ var require_mobile_runtime = __commonJS({
35255
35315
  listProjectDirectories = null,
35256
35316
  createProject = null,
35257
35317
  updateProject = null,
35318
+ gitAvailability = null,
35258
35319
  projectIconAvailability = null,
35259
35320
  generateProjectIcon = null,
35260
35321
  registerProject = null,
@@ -35325,6 +35386,7 @@ var require_mobile_runtime = __commonJS({
35325
35386
  this.listProjectDirectories = listProjectDirectories;
35326
35387
  this.createProject = createProject;
35327
35388
  this.updateProject = updateProject;
35389
+ this.gitAvailability = gitAvailability;
35328
35390
  this.projectIconAvailability = projectIconAvailability;
35329
35391
  this.generateProjectIcon = generateProjectIcon;
35330
35392
  this.registerProject = registerProject;
@@ -35470,6 +35532,7 @@ var require_mobile_runtime = __commonJS({
35470
35532
  accountLabel: session.accountLabel,
35471
35533
  threadId: session.threadId,
35472
35534
  terminalUuid: session.terminalUuid,
35535
+ communicationEnabled: session.communicationEnabled === true,
35473
35536
  terminalOrder: session.terminalOrder,
35474
35537
  cwd: session.cwd,
35475
35538
  model: session.model,
@@ -35533,7 +35596,8 @@ var require_mobile_runtime = __commonJS({
35533
35596
  compact.items = selected.sort((left, right) => left.sourceIndex - right.sourceIndex).map(({ item }) => item);
35534
35597
  return compact;
35535
35598
  }
35536
- snapshot() {
35599
+ snapshot({ projectIcons = false } = {}) {
35600
+ var _a;
35537
35601
  const allProjects = this._projects();
35538
35602
  const projects = allProjects.slice(0, 100);
35539
35603
  const snapshot = {
@@ -35552,6 +35616,17 @@ var require_mobile_runtime = __commonJS({
35552
35616
  terminalStatuses: compactTerminalStatuses(this.getTerminalStatuses()),
35553
35617
  sessions: Array.from(this.sessions.values(), (session) => this._snapshotSession(session))
35554
35618
  };
35619
+ const referencedProjectIcons = /* @__PURE__ */ new Set();
35620
+ if (projectIcons) {
35621
+ for (const session of snapshot.sessions) {
35622
+ if (!((_a = session.project) == null ? void 0 : _a.iconDataUrl)) continue;
35623
+ const projectIconIndex = projects.findIndex((project2) => project2.path === session.project.path && project2.iconDataUrl === session.project.iconDataUrl);
35624
+ if (projectIconIndex < 0) continue;
35625
+ const { iconDataUrl, ...project } = session.project;
35626
+ session.project = { ...project, projectIconIndex };
35627
+ referencedProjectIcons.add(projectIconIndex);
35628
+ }
35629
+ }
35555
35630
  if (jsonBytes(snapshot) <= MAX_SNAPSHOT_BYTES) return snapshot;
35556
35631
  const compact = {
35557
35632
  computerName: snapshot.computerName,
@@ -35572,10 +35647,11 @@ var require_mobile_runtime = __commonJS({
35572
35647
  truncated: true
35573
35648
  };
35574
35649
  let iconChars = compact.projects.reduce((total, project) => {
35575
- var _a;
35576
- return total + (((_a = project.iconDataUrl) == null ? void 0 : _a.length) || 0);
35650
+ var _a2;
35651
+ return total + (((_a2 = project.iconDataUrl) == null ? void 0 : _a2.length) || 0);
35577
35652
  }, 0);
35578
35653
  for (let index = compact.projects.length - 1; iconChars > MAX_SNAPSHOT_PROJECT_ICON_CHARS && index >= 0; index -= 1) {
35654
+ if (referencedProjectIcons.has(index)) continue;
35579
35655
  const icon = compact.projects[index].iconDataUrl;
35580
35656
  if (!icon) continue;
35581
35657
  delete compact.projects[index].iconDataUrl;
@@ -35583,6 +35659,7 @@ var require_mobile_runtime = __commonJS({
35583
35659
  }
35584
35660
  let used = jsonBytes(compact);
35585
35661
  for (let index = compact.projects.length - 1; used > MAX_SNAPSHOT_BYTES && index >= 0; index -= 1) {
35662
+ if (referencedProjectIcons.has(index)) continue;
35586
35663
  if (!compact.projects[index].iconDataUrl) continue;
35587
35664
  delete compact.projects[index].iconDataUrl;
35588
35665
  used = jsonBytes(compact);
@@ -35777,13 +35854,16 @@ var require_mobile_runtime = __commonJS({
35777
35854
  });
35778
35855
  }
35779
35856
  publishProjectIconEvent(event = {}) {
35857
+ const iconDataUrl = typeof event.iconDataUrl === "string" && event.iconDataUrl.length <= 1e5 ? event.iconDataUrl : null;
35780
35858
  return this._publish("project.icon.generated", {
35781
35859
  jobId: cleanText(event.jobId, 100),
35782
35860
  projectId: cleanText(event.projectId, 128),
35783
35861
  success: event.success === true,
35784
35862
  applied: event.applied === true,
35785
35863
  unavailable: event.unavailable === true,
35786
- ...cleanText(event.error, 500) ? { error: cleanText(event.error, 500) } : {}
35864
+ ...cleanText(event.error, 500) ? { error: cleanText(event.error, 500) } : {},
35865
+ ...cleanText(event.icon, 300) ? { icon: cleanText(event.icon, 300) } : {},
35866
+ ...iconDataUrl ? { iconDataUrl } : {}
35787
35867
  });
35788
35868
  }
35789
35869
  publishProviderLoginEvent(event = {}) {
@@ -35831,7 +35911,9 @@ var require_mobile_runtime = __commonJS({
35831
35911
  });
35832
35912
  }
35833
35913
  _registerSession(started) {
35914
+ var _a;
35834
35915
  if (!started || typeof started.sessionId !== "string") return;
35916
+ const restarting = ((_a = this.sessions.get(started.sessionId)) == null ? void 0 : _a.restarting) === true;
35835
35917
  const clientRequestId = cleanText(started.clientRequestId, 128);
35836
35918
  this._session(started.sessionId, {
35837
35919
  clientRequestId,
@@ -35841,6 +35923,7 @@ var require_mobile_runtime = __commonJS({
35841
35923
  accountLabel: started.accountLabel || null,
35842
35924
  threadId: started.threadId,
35843
35925
  terminalUuid: started.terminalUuid,
35926
+ communicationEnabled: started.communicationEnabled === true,
35844
35927
  terminalOrder: Number.isSafeInteger(started.terminalId) && started.terminalId > 0 ? started.terminalId : null,
35845
35928
  desktopTerminalId: Number.isSafeInteger(started.terminalId) && started.terminalId > 0 ? started.terminalId : null,
35846
35929
  cwd: started.cwd,
@@ -35866,6 +35949,7 @@ var require_mobile_runtime = __commonJS({
35866
35949
  accountLabel: started.accountLabel || null,
35867
35950
  threadId: started.threadId,
35868
35951
  terminalUuid: started.terminalUuid,
35952
+ communicationEnabled: started.communicationEnabled === true,
35869
35953
  terminalOrder: Number.isSafeInteger(started.terminalId) && started.terminalId > 0 ? started.terminalId : null,
35870
35954
  cwd: started.cwd,
35871
35955
  model: started.model,
@@ -35882,7 +35966,7 @@ var require_mobile_runtime = __commonJS({
35882
35966
  hasEarlierHistory: started.resumed === true
35883
35967
  }
35884
35968
  });
35885
- for (const event of started.historyEvents || []) {
35969
+ for (const event of restarting ? [] : started.historyEvents || []) {
35886
35970
  this._publishProviderEvent(started.sessionId, event);
35887
35971
  }
35888
35972
  this._sessionsChanged();
@@ -35921,11 +36005,11 @@ var require_mobile_runtime = __commonJS({
35921
36005
  return;
35922
36006
  }
35923
36007
  if (!Array.isArray(messages) || !messages.length) return;
35924
- const recent = messages.slice(-200);
36008
+ const recent = normalizeConversationMessages(messages).slice(-200);
35925
36009
  recent.forEach((message, index) => {
35926
36010
  var _a2, _b2;
35927
- const role = (message == null ? void 0 : message.role) === "assistant" ? "assistant_message" : (message == null ? void 0 : message.role) === "user" ? "user_message" : null;
35928
- const text = cleanText((message == null ? void 0 : message.content) ?? (message == null ? void 0 : message.text) ?? (message == null ? void 0 : message.displayText), MAX_INITIAL_PROMPT_CHARS);
36011
+ const role = message.role;
36012
+ const text = cleanText(message.text, MAX_INITIAL_PROMPT_CHARS);
35929
36013
  const attachments = Array.isArray(message == null ? void 0 : message.attachments) ? message.attachments : [];
35930
36014
  if (!role || !text && !attachments.length) return;
35931
36015
  const key = `${role}\0${text}`;
@@ -35961,6 +36045,7 @@ var require_mobile_runtime = __commonJS({
35961
36045
  var _a, _b, _c, _d, _e, _f;
35962
36046
  if (!this.sessions.has(sessionId)) return;
35963
36047
  if (!event || !isProviderEventType(event.type)) return;
36048
+ if (this.sessions.get(sessionId).restarting && ["item.started", "item.updated", "item.completed", "content.delta", "turn.started", "turn.completed"].includes(event.type)) return;
35964
36049
  if (event.visibility === "internal" && ![
35965
36050
  "request.opened",
35966
36051
  "request.updated",
@@ -36217,6 +36302,7 @@ var require_mobile_runtime = __commonJS({
36217
36302
  accountLabel: null,
36218
36303
  threadId: null,
36219
36304
  terminalUuid: null,
36305
+ communicationEnabled: false,
36220
36306
  terminalOrder: null,
36221
36307
  desktopTerminalId: null,
36222
36308
  cwd: null,
@@ -36363,6 +36449,7 @@ var require_mobile_runtime = __commonJS({
36363
36449
  return;
36364
36450
  }
36365
36451
  client.selective = Array.isArray(message.features) && message.features.slice(0, 20).includes(SESSION_SUBSCRIPTIONS_FEATURE);
36452
+ const projectIcons = Array.isArray(message.features) && message.features.slice(0, 20).includes(SNAPSHOT_PROJECT_ICONS_FEATURE);
36366
36453
  client.subscriptions = new Set(client.selective && Array.isArray(message.subscriptions) ? message.subscriptions.slice(0, 20).filter((sessionId) => typeof sessionId === "string" && sessionId.length > 0 && sessionId.length <= 128) : []);
36367
36454
  client.skippedSeq = 0;
36368
36455
  this.streamMetrics.helloMessages += 1;
@@ -36376,10 +36463,13 @@ var require_mobile_runtime = __commonJS({
36376
36463
  runtimeId: this.runtimeId,
36377
36464
  latestSeq: this.sequence,
36378
36465
  reset: !replayable,
36379
- features: client.selective ? [SESSION_SUBSCRIPTIONS_FEATURE] : [],
36466
+ features: [
36467
+ ...client.selective ? [SESSION_SUBSCRIPTIONS_FEATURE] : [],
36468
+ ...projectIcons ? [SNAPSHOT_PROJECT_ICONS_FEATURE] : []
36469
+ ],
36380
36470
  desktop: this.getClientMetadata(),
36381
36471
  capabilities: (this.getCapabilities() || []).slice(0, 50).flatMap((capability) => typeof capability === "string" && capability.length <= 100 ? [capability] : []),
36382
- ...!replayable ? { snapshot: this.snapshot() } : {}
36472
+ ...!replayable ? { snapshot: this.snapshot({ projectIcons }) } : {}
36383
36473
  };
36384
36474
  this.streamMetrics[replayable ? "replayWelcomes" : "resetWelcomes"] += 1;
36385
36475
  welcome.builtMs = Math.max(0, Math.round(Date.now() - helloReceivedAt));
@@ -36481,7 +36571,7 @@ var require_mobile_runtime = __commonJS({
36481
36571
  }
36482
36572
  }
36483
36573
  async _executeCommand(command = {}, context = {}) {
36484
- var _a, _b, _c, _d, _e, _f;
36574
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
36485
36575
  this._pruneAttachmentUploads();
36486
36576
  const sessionId = command.sessionId;
36487
36577
  const payload = command.payload || {};
@@ -36591,10 +36681,15 @@ var require_mobile_runtime = __commonJS({
36591
36681
  this.publishProjects();
36592
36682
  return result;
36593
36683
  }
36684
+ if (command.type === "project.git.availability") {
36685
+ if (typeof this.gitAvailability !== "function") return { available: false };
36686
+ exactPayload(["rootId"]);
36687
+ return this.gitAvailability({ rootId: cleanText(payload.rootId, 128) });
36688
+ }
36594
36689
  if (command.type === "project.icon.availability") {
36595
36690
  if (typeof this.projectIconAvailability !== "function") return { available: false };
36596
- exactPayload([]);
36597
- return this.projectIconAvailability();
36691
+ exactPayload(["projectId"]);
36692
+ return this.projectIconAvailability({ projectId: cleanText(payload.projectId, 128) });
36598
36693
  }
36599
36694
  if (command.type === "project.icon.generate") {
36600
36695
  if (typeof this.generateProjectIcon !== "function") throw new Error("Codex icon generation is unavailable");
@@ -36608,7 +36703,9 @@ var require_mobile_runtime = __commonJS({
36608
36703
  if (command.type === "project.register") {
36609
36704
  if (typeof this.registerProject !== "function") throw new Error("Remote project registration is unavailable");
36610
36705
  exactPayload(["rootId", "relativePath", "requestId"]);
36611
- return this.registerProject({ rootId: payload.rootId, relativePath: payload.relativePath, requestId: mutationRequestId() });
36706
+ const result = await this.registerProject({ rootId: payload.rootId, relativePath: payload.relativePath, requestId: mutationRequestId() });
36707
+ this.publishProjects();
36708
+ return result;
36612
36709
  }
36613
36710
  if (command.type === "project.clone") {
36614
36711
  if (typeof this.cloneProject !== "function") throw new Error("Remote project cloning is unavailable");
@@ -36720,7 +36817,9 @@ var require_mobile_runtime = __commonJS({
36720
36817
  if (typeof this.describeProviderLogin !== "function") throw new Error("Remote provider sign-in is unavailable");
36721
36818
  exactPayload(["agent"]);
36722
36819
  if (!MOBILE_AGENTS.has(payload.agent)) throw new Error("Choose a supported provider");
36723
- return this.describeProviderLogin(payload.agent);
36820
+ const session = sessionId ? this.sessions.get(sessionId) : null;
36821
+ if (sessionId && (!session || session.agent !== payload.agent)) throw new Error("Remote sign-in session is invalid");
36822
+ return this.describeProviderLogin(payload.agent, { accountId: (session == null ? void 0 : session.accountId) || "current" });
36724
36823
  }
36725
36824
  if (command.type === "provider.login.start") {
36726
36825
  if (typeof this.startProviderLogin !== "function") throw new Error("Remote provider sign-in is unavailable");
@@ -36728,7 +36827,10 @@ var require_mobile_runtime = __commonJS({
36728
36827
  if (!MOBILE_AGENTS.has(payload.agent) || payload.replaceAccount !== void 0 && typeof payload.replaceAccount !== "boolean") {
36729
36828
  throw new Error("Remote provider sign-in is invalid");
36730
36829
  }
36731
- return this.startProviderLogin({ agent: payload.agent, replaceAccount: payload.replaceAccount === true });
36830
+ const session = sessionId ? this.sessions.get(sessionId) : null;
36831
+ if (sessionId && (!session || session.agent !== payload.agent)) throw new Error("Remote sign-in session is invalid");
36832
+ if (((_b = session == null ? void 0 : session.currentTurn) == null ? void 0 : _b.state) === "running") throw new Error("Wait for the current response to finish");
36833
+ return this.startProviderLogin({ agent: payload.agent, accountId: (session == null ? void 0 : session.accountId) || "current", replaceAccount: payload.replaceAccount === true });
36732
36834
  }
36733
36835
  if (command.type === "provider.login.submit") {
36734
36836
  if (typeof this.submitProviderLogin !== "function") throw new Error("Remote provider sign-in is unavailable");
@@ -36855,7 +36957,7 @@ var require_mobile_runtime = __commonJS({
36855
36957
  if (payload.useWorktree !== void 0 && typeof payload.useWorktree !== "boolean") {
36856
36958
  throw new Error("The worktree preference is invalid");
36857
36959
  }
36858
- const clientRequestId = ((_b = payload.clientRequestId) == null ? void 0 : _b.trim()) || null;
36960
+ const clientRequestId = ((_c = payload.clientRequestId) == null ? void 0 : _c.trim()) || null;
36859
36961
  try {
36860
36962
  const result = await this.createSession({
36861
36963
  agent: payload.agent,
@@ -36894,6 +36996,7 @@ var require_mobile_runtime = __commonJS({
36894
36996
  agent: session.agent
36895
36997
  });
36896
36998
  const knownCount = Array.from(session.items.values()).filter(isConversationItem).length;
36999
+ const hydratedCount = ((_e = (_d = context.client) == null ? void 0 : _d.subscriptions) == null ? void 0 : _e.has(sessionId)) ? this._subscriptionSnapshot(session).items.filter(isConversationItem).length : 0;
36897
37000
  const page = pageConversationMessages(messages, {
36898
37001
  before: Number.isSafeInteger(payload.before) ? payload.before : void 0,
36899
37002
  anchor: payload.anchor && typeof payload.anchor === "object" ? {
@@ -36902,6 +37005,7 @@ var require_mobile_runtime = __commonJS({
36902
37005
  timestamp: cleanText(payload.anchor.timestamp, 64)
36903
37006
  } : null,
36904
37007
  knownCount: Number.isSafeInteger(payload.knownCount) ? payload.knownCount : knownCount,
37008
+ hydratedCount,
36905
37009
  limit: 30
36906
37010
  });
36907
37011
  const events = conversationMessagesToEvents(page.messages, {
@@ -36958,6 +37062,41 @@ var require_mobile_runtime = __commonJS({
36958
37062
  ...retained.thumbnailDataUrl ? { thumbnailDataUrl: retained.thumbnailDataUrl } : {}
36959
37063
  };
36960
37064
  }
37065
+ case "session.restart": {
37066
+ exactPayload([]);
37067
+ const session = this.sessions.get(sessionId);
37068
+ if (!session || !session.cwd) throw new Error("The remote conversation is unavailable");
37069
+ if (((_f = session.currentTurn) == null ? void 0 : _f.state) === "running" || session.state === "starting") {
37070
+ throw new Error("Wait for the current response to finish");
37071
+ }
37072
+ if (session.threadId && session.supportsResume === false) {
37073
+ throw new Error("This provider cannot resume this conversation after sign-in");
37074
+ }
37075
+ session.state = "starting";
37076
+ session.restarting = true;
37077
+ try {
37078
+ await this.manager.restartSession(sessionId, {
37079
+ agent: session.agent,
37080
+ accountId: session.accountId || "current",
37081
+ cwd: session.cwd,
37082
+ resumeSessionId: session.threadId || void 0,
37083
+ model: session.model,
37084
+ effort: session.effort,
37085
+ permissionMode: session.permissionMode,
37086
+ interactionMode: session.interactionMode,
37087
+ terminalId: session.desktopTerminalId,
37088
+ terminalUuid: session.terminalUuid,
37089
+ clientRequestId: session.clientRequestId,
37090
+ providerOptions: session.serviceTier ? [{ id: "serviceTier", value: session.serviceTier }] : []
37091
+ });
37092
+ return { restarted: true };
37093
+ } catch (error) {
37094
+ session.state = "error";
37095
+ throw error;
37096
+ } finally {
37097
+ delete session.restarting;
37098
+ }
37099
+ }
36961
37100
  case "session.models":
36962
37101
  return {
36963
37102
  models: compactModels(await this.manager.listModels(sessionId)),
@@ -36973,7 +37112,7 @@ var require_mobile_runtime = __commonJS({
36973
37112
  const value = typeof payload.value === "boolean" && REASONING_CONFIG_IDS.has(configId) ? payload.value : cleanText(payload.value, 200);
36974
37113
  if (value === null || value === "") throw new Error("Choose a configuration value");
36975
37114
  const session = this._session(sessionId);
36976
- if (((_c = session.currentTurn) == null ? void 0 : _c.state) === "running") {
37115
+ if (((_g = session.currentTurn) == null ? void 0 : _g.state) === "running") {
36977
37116
  throw new Error("Wait for the current response to finish");
36978
37117
  }
36979
37118
  return this.manager.setConfigOption(sessionId, configId, value);
@@ -37119,11 +37258,11 @@ var require_mobile_runtime = __commonJS({
37119
37258
  for (const uploadId of uploadIds) this.attachmentUploads.delete(uploadId);
37120
37259
  return { accepted: true, ...(delivered == null ? void 0 : delivered.turnId) ? { turnId: delivered.turnId } : {} };
37121
37260
  } catch (error) {
37122
- const localEcho = (_d = this.sessions.get(sessionId)) == null ? void 0 : _d.items.get(localEchoId);
37261
+ const localEcho = (_h = this.sessions.get(sessionId)) == null ? void 0 : _h.items.get(localEchoId);
37123
37262
  if (localEcho == null ? void 0 : localEcho.providerItemId) return { accepted: true };
37124
37263
  this._publishProviderEvent(sessionId, {
37125
37264
  eventId: crypto.randomUUID(),
37126
- provider: (_e = this.sessions.get(sessionId)) == null ? void 0 : _e.provider,
37265
+ provider: (_i = this.sessions.get(sessionId)) == null ? void 0 : _i.provider,
37127
37266
  type: "item.completed",
37128
37267
  executionOrigin: "main",
37129
37268
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -37133,7 +37272,7 @@ var require_mobile_runtime = __commonJS({
37133
37272
  status: "failed",
37134
37273
  localEcho: true,
37135
37274
  remoteCommand: true,
37136
- data: { text, attachments: ((_f = localEcho == null ? void 0 : localEcho.data) == null ? void 0 : _f.attachments) || [] }
37275
+ data: { text, attachments: ((_j = localEcho == null ? void 0 : localEcho.data) == null ? void 0 : _j.attachments) || [] }
37137
37276
  }
37138
37277
  });
37139
37278
  this.remoteTurnSessions.delete(sessionId);
@@ -38507,6 +38646,7 @@ var require_remote_runtime_client = __commonJS({
38507
38646
  } = require_mobile_crypto();
38508
38647
  var PROTOCOL_VERSION = 2;
38509
38648
  var SESSION_SUBSCRIPTIONS_FEATURE = "session-subscriptions";
38649
+ var SNAPSHOT_PROJECT_ICONS_FEATURE = "snapshot-project-icons";
38510
38650
  var MAX_PAIRING_INPUT_LENGTH = 8192;
38511
38651
  var MAX_RUNTIME_MESSAGE_BYTES = 1024 * 1024;
38512
38652
  var MAX_RESET_SNAPSHOT_BYTES = 256 * 1024;
@@ -38571,6 +38711,7 @@ var require_remote_runtime_client = __commonJS({
38571
38711
  "turn.send",
38572
38712
  "turn.interrupt",
38573
38713
  "session.stop",
38714
+ "session.restart",
38574
38715
  "session.models",
38575
38716
  "session.configure",
38576
38717
  "session.subscribe",
@@ -38589,6 +38730,9 @@ var require_remote_runtime_client = __commonJS({
38589
38730
  "project.register",
38590
38731
  "project.clone",
38591
38732
  "project.clone.cancel",
38733
+ "project.git.availability",
38734
+ "project.icon.availability",
38735
+ "project.icon.generate",
38592
38736
  "project.unregister",
38593
38737
  "tasks.list",
38594
38738
  "task.create",
@@ -39022,6 +39166,7 @@ var require_remote_runtime_client = __commonJS({
39022
39166
  this.reconnectAttempt = 0;
39023
39167
  this.reconnectTimer = null;
39024
39168
  this.openTimer = null;
39169
+ this.runtimeWaitTimer = null;
39025
39170
  this.heartbeatTimer = null;
39026
39171
  this.pongTimer = null;
39027
39172
  this.refreshTimer = null;
@@ -39358,6 +39503,7 @@ var require_remote_runtime_client = __commonJS({
39358
39503
  if (message.kind === "pair.challenge") return this._handlePairChallenge(message);
39359
39504
  if (message.kind === "pair.completed") return this._handlePairCompleted(socket, message);
39360
39505
  if (message.kind === "hello.accepted") {
39506
+ if (this.connection && !this.pairing) this._waitForRuntime();
39361
39507
  if (this.connectTrace) this.connectTrace.authenticatedAt = this.now();
39362
39508
  this._diagnostic("remote.relay_authenticated", {
39363
39509
  mode: this.pairing ? "pairing" : "runtime",
@@ -39368,9 +39514,8 @@ var require_remote_runtime_client = __commonJS({
39368
39514
  return;
39369
39515
  }
39370
39516
  if (message.kind === "credential.renewed") {
39371
- if (!this.connection || Number(message.accessExpiresAt) !== this.connection.accessExpiresAt) {
39372
- return this._protocolFailure(socket);
39373
- }
39517
+ const accessExpiresAt = Number(message.accessExpiresAt);
39518
+ if (!this.connection || !Number.isFinite(accessExpiresAt) || Math.abs(accessExpiresAt - this.connection.accessExpiresAt) > 2e3) return;
39374
39519
  if (this.renewTimer) clearTimeout(this.renewTimer);
39375
39520
  this.renewTimer = null;
39376
39521
  this._diagnostic("remote.credential_renewed");
@@ -39386,6 +39531,8 @@ var require_remote_runtime_client = __commonJS({
39386
39531
  return;
39387
39532
  }
39388
39533
  if (message.kind === "runtime.offline") {
39534
+ clearTimeout(this.runtimeWaitTimer);
39535
+ this.runtimeWaitTimer = null;
39389
39536
  this.runtimeOnline = false;
39390
39537
  this.resyncPending = false;
39391
39538
  this.subscriptionsSupported = false;
@@ -39496,6 +39643,7 @@ var require_remote_runtime_client = __commonJS({
39496
39643
  var _a, _b, _c, _d, _e;
39497
39644
  let safe;
39498
39645
  let bytes;
39646
+ let snapshotBytes;
39499
39647
  try {
39500
39648
  bytes = Buffer.byteLength(JSON.stringify(envelope));
39501
39649
  if (!envelope || typeof envelope !== "object" || bytes > MAX_RUNTIME_MESSAGE_BYTES) {
@@ -39503,15 +39651,35 @@ var require_remote_runtime_client = __commonJS({
39503
39651
  }
39504
39652
  assertPublicPayload(envelope);
39505
39653
  if (envelope.kind === "welcome" && envelope.reset === true) {
39506
- if (!envelope.snapshot || typeof envelope.snapshot !== "object" || Buffer.byteLength(JSON.stringify(envelope.snapshot)) > MAX_RESET_SNAPSHOT_BYTES) {
39654
+ snapshotBytes = envelope.snapshot ? Buffer.byteLength(JSON.stringify(envelope.snapshot)) : 0;
39655
+ if (!envelope.snapshot || typeof envelope.snapshot !== "object" || snapshotBytes > MAX_RESET_SNAPSHOT_BYTES) {
39507
39656
  throw new Error("Invalid runtime snapshot");
39508
39657
  }
39658
+ if (Array.isArray(envelope.features) && envelope.features.includes(SNAPSHOT_PROJECT_ICONS_FEATURE)) {
39659
+ const projects = envelope.snapshot.projects;
39660
+ let expandedBytes = bytes;
39661
+ envelope = { ...envelope, snapshot: {
39662
+ ...envelope.snapshot,
39663
+ sessions: (envelope.snapshot.sessions || []).map((session) => {
39664
+ if (!Object.hasOwn(session.project || {}, "projectIconIndex")) return session;
39665
+ const { projectIconIndex, ...project } = session.project;
39666
+ const source = Array.isArray(projects) && Number.isSafeInteger(projectIconIndex) && projectIconIndex >= 0 ? projects == null ? void 0 : projects[projectIconIndex] : null;
39667
+ if (!source || typeof source.path !== "string" || source.path !== project.path || typeof source.iconDataUrl !== "string") {
39668
+ throw new Error("Invalid runtime project icon reference");
39669
+ }
39670
+ const restored = { ...project, iconDataUrl: source.iconDataUrl };
39671
+ expandedBytes += Buffer.byteLength(JSON.stringify(restored)) - Buffer.byteLength(JSON.stringify(session.project));
39672
+ if (expandedBytes > MAX_RUNTIME_MESSAGE_BYTES) throw new Error("Expanded runtime snapshot is too large");
39673
+ return { ...session, project: restored };
39674
+ })
39675
+ } };
39676
+ }
39509
39677
  }
39510
39678
  const commandType = envelope.kind === "command.result" ? (_c = (_b = (_a = this.pendingCommands.get(envelope.commandId)) == null ? void 0 : _a.message) == null ? void 0 : _b.command) == null ? void 0 : _c.type : null;
39511
39679
  safe = stripPathFields(envelope, 0, commandType === "project.directories.list");
39512
39680
  } catch {
39513
39681
  const kind = typeof (envelope == null ? void 0 : envelope.kind) === "string" && /^[a-z][a-z0-9.]{0,63}$/.test(envelope.kind) ? envelope.kind : void 0;
39514
- this._diagnostic("remote.runtime_rejected", { reason: "invalid_envelope", kind });
39682
+ this._diagnostic("remote.runtime_rejected", { reason: "invalid_envelope", kind, bytes, snapshotBytes });
39515
39683
  return this._protocolFailure(this.socket);
39516
39684
  }
39517
39685
  if (safe.kind === "command.accepted") {
@@ -39558,6 +39726,8 @@ var require_remote_runtime_client = __commonJS({
39558
39726
  this._setState({ ...this.state, phase: "online", lastEnvelope: safe, error: null });
39559
39727
  }
39560
39728
  this.reconnectAttempt = 0;
39729
+ clearTimeout(this.runtimeWaitTimer);
39730
+ this.runtimeWaitTimer = null;
39561
39731
  this._diagnostic("remote.runtime_synced", {
39562
39732
  totalMs: this.connectTrace ? this.now() - this.connectTrace.startedAt : void 0,
39563
39733
  reset: safe.reset === true,
@@ -39614,11 +39784,12 @@ var require_remote_runtime_client = __commonJS({
39614
39784
  _sendRuntimeHello(withCursor = true) {
39615
39785
  const cursor = withCursor ? this.state.cursor : null;
39616
39786
  this._setState({ ...this.state, phase: "syncing" });
39787
+ this._waitForRuntime();
39617
39788
  const sent = this._sendRuntime({
39618
39789
  kind: "hello",
39619
39790
  protocolVersion: PROTOCOL_VERSION,
39620
39791
  accepts: ["deflate"],
39621
- features: [SESSION_SUBSCRIPTIONS_FEATURE],
39792
+ features: [SESSION_SUBSCRIPTIONS_FEATURE, SNAPSHOT_PROJECT_ICONS_FEATURE],
39622
39793
  subscriptions: [...this.subscriptions],
39623
39794
  ...cursor ? { cursor } : {}
39624
39795
  });
@@ -39631,6 +39802,15 @@ var require_remote_runtime_client = __commonJS({
39631
39802
  this.resyncPending = true;
39632
39803
  this._sendRuntimeHello(false);
39633
39804
  }
39805
+ _waitForRuntime() {
39806
+ if (this.runtimeWaitTimer) return;
39807
+ this.runtimeWaitTimer = setTimeout(() => {
39808
+ this.runtimeWaitTimer = null;
39809
+ if (!["connecting", "syncing"].includes(this.state.phase)) return;
39810
+ this._diagnostic("remote.runtime_wait_timeout");
39811
+ this._setState({ ...this.state, phase: "offline", error: "The remote computer did not respond" });
39812
+ }, this.timeouts.open);
39813
+ }
39634
39814
  _sendRuntime(payload) {
39635
39815
  if (!this.connection) return false;
39636
39816
  return this._sendRelay({
@@ -39877,6 +40057,8 @@ var require_remote_runtime_client = __commonJS({
39877
40057
  if (pairingFailed && this.connection) this._scheduleReconnect();
39878
40058
  }
39879
40059
  _closeSocket(close = true) {
40060
+ clearTimeout(this.runtimeWaitTimer);
40061
+ this.runtimeWaitTimer = null;
39880
40062
  if (this.openTimer) clearTimeout(this.openTimer);
39881
40063
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
39882
40064
  if (this.pongTimer) clearTimeout(this.pongTimer);
@@ -40695,7 +40877,7 @@ var require_headless_session_bridge = __commonJS({
40695
40877
  };
40696
40878
  }
40697
40879
  var HeadlessSessionBridge = class {
40698
- constructor({ runtime, remoteClient, peerRuntimeNetwork = null, dataPath, deliverMessage, createPairingLink = null, randomBytes = crypto.randomBytes } = {}) {
40880
+ constructor({ runtime, remoteClient, peerRuntimeNetwork = null, dataPath, deliverMessage, listLocalSessions = null, readLocalTranscript = null, createPairingLink = null, randomBytes = crypto.randomBytes } = {}) {
40699
40881
  if (!runtime || !remoteClient || typeof deliverMessage !== "function" || !path.isAbsolute(dataPath || "")) {
40700
40882
  throw new Error("CAS Cloud session bridge configuration is invalid");
40701
40883
  }
@@ -40704,6 +40886,8 @@ var require_headless_session_bridge = __commonJS({
40704
40886
  this.peerRuntimeNetwork = peerRuntimeNetwork;
40705
40887
  this.dataPath = dataPath;
40706
40888
  this.deliverMessage = deliverMessage;
40889
+ this.listLocalSessions = typeof listLocalSessions === "function" ? listLocalSessions : null;
40890
+ this.readLocalTranscript = typeof readLocalTranscript === "function" ? readLocalTranscript : null;
40707
40891
  this.createPairingLink = createPairingLink;
40708
40892
  this.adminToken = randomBytes(32).toString("hex");
40709
40893
  this.sessionSecret = randomBytes(32);
@@ -40882,7 +41066,7 @@ var require_headless_session_bridge = __commonJS({
40882
41066
  });
40883
41067
  }
40884
41068
  async _handle(request, response) {
40885
- var _a, _b, _c;
41069
+ var _a, _b, _c, _d, _e, _f, _g, _h;
40886
41070
  const url = new URL(request.url, "http://127.0.0.1");
40887
41071
  if (url.pathname.startsWith("/admin/")) {
40888
41072
  if (!safeBearer(request, this.adminToken)) return sendJson(response, 401, { error: "Unauthorized" });
@@ -40934,11 +41118,29 @@ var require_headless_session_bridge = __commonJS({
40934
41118
  payload: {}
40935
41119
  })
40936
41120
  })));
40937
- const sessions = results.flatMap((entry) => entry.status === "fulfilled" ? (({ state, result }) => (Array.isArray(result == null ? void 0 : result.sessions) ? result.sessions : []).slice(0, 100).flatMap((session) => session && typeof session.id === "string" && session.id.length <= 128 ? [{
41121
+ const localSessions = (((_b = (_a = this.listLocalSessions) == null ? void 0 : _a.call(this)) == null ? void 0 : _b.sessions) || []).flatMap((session) => session && typeof session.id === "string" && session.id.length <= 128 ? [{
41122
+ id: session.id,
41123
+ name: clip(session.name, 120),
41124
+ agent: clip(session.agent, 60),
41125
+ project: clip(session.project, 160),
41126
+ agent_id: clip(session.agent_id, 60),
41127
+ project_icon: clip(session.project_icon, 140),
41128
+ project_color: clip(session.project_color, 32),
41129
+ goal: clip(session.goal, 1200),
41130
+ activity: clip(session.activity, 500),
41131
+ status: clip(session.status, 80),
41132
+ surface: session.surface === "terminal" ? "terminal" : "chat",
41133
+ state: ["working", "needs_input"].includes(session.state) ? session.state : "idle",
41134
+ is_current: session.id === sourceSessionId
41135
+ }] : []);
41136
+ const remoteSessions = results.flatMap((entry) => entry.status === "fulfilled" ? (({ state, result }) => (Array.isArray(result == null ? void 0 : result.sessions) ? result.sessions : []).slice(0, 100).flatMap((session) => session && typeof session.id === "string" && session.id.length <= 128 ? [{
40938
41137
  id: encodeRemoteSessionId(state.runtime.id, session.id),
40939
41138
  name: clip(session.name, 120),
40940
41139
  agent: clip(session.agent, 60),
40941
41140
  project: clip(session.project, 160),
41141
+ agent_id: clip(session.agent_id, 60),
41142
+ project_icon: clip(session.project_icon, 140),
41143
+ project_color: clip(session.project_color, 32),
40942
41144
  goal: clip(session.goal, 1200),
40943
41145
  activity: clip(session.activity, 500),
40944
41146
  status: clip(session.status, 80),
@@ -40947,7 +41149,7 @@ var require_headless_session_bridge = __commonJS({
40947
41149
  host: state.runtime.name || "Paired host",
40948
41150
  is_current: false
40949
41151
  }] : []))(entry.value) : []);
40950
- return sendJson(response, 200, { sessions });
41152
+ return sendJson(response, 200, { sessions: [...localSessions, ...remoteSessions] });
40951
41153
  } catch (_) {
40952
41154
  return sendJson(response, 503, { error: "The paired Mac is offline" });
40953
41155
  }
@@ -40958,6 +41160,25 @@ var require_headless_session_bridge = __commonJS({
40958
41160
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 60) {
40959
41161
  return sendJson(response, 400, { error: "Conversation limit must be between 1 and 60" });
40960
41162
  }
41163
+ if (this._sourceSession(body.target_session_id)) {
41164
+ if (!this.readLocalTranscript) return sendJson(response, 503, { error: "Conversation reading is unavailable" });
41165
+ try {
41166
+ const result = await this.readLocalTranscript({ targetSessionId: body.target_session_id, limit });
41167
+ const snapshot = boundedConversationMessages(result == null ? void 0 : result.messages, { limit });
41168
+ return sendJson(response, 200, {
41169
+ session: {
41170
+ id: body.target_session_id,
41171
+ name: clip((_c = result == null ? void 0 : result.session) == null ? void 0 : _c.name, 120),
41172
+ agent: clip((_d = result == null ? void 0 : result.session) == null ? void 0 : _d.agent, 60),
41173
+ project: clip((_e = result == null ? void 0 : result.session) == null ? void 0 : _e.project, 160)
41174
+ },
41175
+ messages: snapshot.messages,
41176
+ truncated: (result == null ? void 0 : result.truncated) === true || snapshot.truncated
41177
+ });
41178
+ } catch (_) {
41179
+ return sendJson(response, 503, { error: "The conversation could not be read" });
41180
+ }
41181
+ }
40961
41182
  let target;
40962
41183
  try {
40963
41184
  target = decodeRemoteSessionId(body.target_session_id);
@@ -40976,9 +41197,9 @@ var require_headless_session_bridge = __commonJS({
40976
41197
  return sendJson(response, 200, {
40977
41198
  session: {
40978
41199
  id: body.target_session_id,
40979
- name: clip((_a = result == null ? void 0 : result.session) == null ? void 0 : _a.name, 120),
40980
- agent: clip((_b = result == null ? void 0 : result.session) == null ? void 0 : _b.agent, 60),
40981
- project: clip((_c = result == null ? void 0 : result.session) == null ? void 0 : _c.project, 160),
41200
+ name: clip((_f = result == null ? void 0 : result.session) == null ? void 0 : _f.name, 120),
41201
+ agent: clip((_g = result == null ? void 0 : result.session) == null ? void 0 : _g.agent, 60),
41202
+ project: clip((_h = result == null ? void 0 : result.session) == null ? void 0 : _h.project, 160),
40982
41203
  host: state.runtime.name || "Paired Mac"
40983
41204
  },
40984
41205
  messages: snapshot.messages,
@@ -40998,10 +41219,30 @@ var require_headless_session_bridge = __commonJS({
40998
41219
  this._pruneReplies();
40999
41220
  if (messageType === "response") {
41000
41221
  const pending = this.pendingReplies.get(body.reply_to_request_id);
41001
- if (!pending || pending.direction !== "incoming" || pending.sourceSessionId !== body.target_session_id || pending.targetSessionId !== sourceSessionId) {
41222
+ if (!pending || !["incoming", "local"].includes(pending.direction) || pending.sourceSessionId !== body.target_session_id || pending.targetSessionId !== sourceSessionId) {
41002
41223
  return sendJson(response, 409, { error: "The response does not match an active session request" });
41003
41224
  }
41004
41225
  const source2 = this._sourceSession(sourceSessionId);
41226
+ if (pending.direction === "local") {
41227
+ try {
41228
+ const result = await this.deliverMessage({
41229
+ targetSessionId: pending.sourceSessionId,
41230
+ sourceSessionId,
41231
+ sourceName: (source2 == null ? void 0 : source2.title) || `${(source2 == null ? void 0 : source2.agent) || "CAS Cloud"} session`,
41232
+ sourceAgent: (source2 == null ? void 0 : source2.agent) || (source2 == null ? void 0 : source2.provider) || "agent",
41233
+ message: body.message.trim(),
41234
+ messageType: "response",
41235
+ communicationRequestId: body.reply_to_request_id
41236
+ });
41237
+ this.pendingReplies.delete(body.reply_to_request_id);
41238
+ return sendJson(response, 200, {
41239
+ success: true,
41240
+ status: (result == null ? void 0 : result.status) === "queued" ? "queued" : "delivered"
41241
+ });
41242
+ } catch (_) {
41243
+ return sendJson(response, 503, { error: "The session could not receive the response" });
41244
+ }
41245
+ }
41005
41246
  if (!pending.reply({
41006
41247
  sourceSessionId: pending.replyTargetSessionId,
41007
41248
  targetSessionId: pending.remoteSourceSessionId,
@@ -41014,6 +41255,37 @@ var require_headless_session_bridge = __commonJS({
41014
41255
  this.pendingReplies.delete(body.reply_to_request_id);
41015
41256
  return sendJson(response, 200, { success: true, status: "delivered" });
41016
41257
  }
41258
+ if (body.target_session_id === sourceSessionId) {
41259
+ return sendJson(response, 400, { error: "A session cannot message itself" });
41260
+ }
41261
+ const source = this._sourceSession(sourceSessionId);
41262
+ if (this._sourceSession(body.target_session_id)) {
41263
+ const communicationRequestId2 = crypto.randomUUID();
41264
+ this._rememberReply(communicationRequestId2, {
41265
+ direction: "local",
41266
+ sourceSessionId,
41267
+ targetSessionId: body.target_session_id
41268
+ });
41269
+ try {
41270
+ const result = await this.deliverMessage({
41271
+ targetSessionId: body.target_session_id,
41272
+ sourceSessionId,
41273
+ sourceName: (source == null ? void 0 : source.title) || `${(source == null ? void 0 : source.agent) || "CAS Cloud"} session`,
41274
+ sourceAgent: (source == null ? void 0 : source.agent) || (source == null ? void 0 : source.provider) || "agent",
41275
+ message: body.message.trim(),
41276
+ messageType: "request",
41277
+ communicationRequestId: communicationRequestId2
41278
+ });
41279
+ return sendJson(response, 200, {
41280
+ success: true,
41281
+ status: (result == null ? void 0 : result.status) === "queued" ? "queued" : "delivered",
41282
+ request_id: communicationRequestId2
41283
+ });
41284
+ } catch (_) {
41285
+ this.pendingReplies.delete(communicationRequestId2);
41286
+ return sendJson(response, 503, { error: "The session could not receive the request" });
41287
+ }
41288
+ }
41017
41289
  let target;
41018
41290
  try {
41019
41291
  target = decodeRemoteSessionId(body.target_session_id);
@@ -41021,7 +41293,6 @@ var require_headless_session_bridge = __commonJS({
41021
41293
  return sendJson(response, 400, { error: error.message });
41022
41294
  }
41023
41295
  if (!this._remoteClient(target.runtimeId)) return sendJson(response, 404, { error: "The remote session is unavailable" });
41024
- const source = this._sourceSession(sourceSessionId);
41025
41296
  const communicationRequestId = crypto.randomUUID();
41026
41297
  this._rememberReply(communicationRequestId, {
41027
41298
  direction: "outgoing",
@@ -41361,7 +41632,8 @@ var require_headless_project_registry = __commonJS({
41361
41632
  onOperation = () => {
41362
41633
  },
41363
41634
  maxConcurrentClones = MAX_CLONES,
41364
- stopTimeoutMs = 2e3
41635
+ stopTimeoutMs = 2e3,
41636
+ platform = process.platform
41365
41637
  }) {
41366
41638
  if (!(database == null ? void 0 : database.db)) throw new Error("HeadlessProjectRegistry requires a SQLite database");
41367
41639
  this.database = database;
@@ -41372,6 +41644,7 @@ var require_headless_project_registry = __commonJS({
41372
41644
  this.onOperation = onOperation;
41373
41645
  this.maxConcurrentClones = maxConcurrentClones;
41374
41646
  this.stopTimeoutMs = stopTimeoutMs;
41647
+ this.platform = platform;
41375
41648
  this.stopping = false;
41376
41649
  this.running = /* @__PURE__ */ new Map();
41377
41650
  this.queue = [];
@@ -41564,7 +41837,7 @@ var require_headless_project_registry = __commonJS({
41564
41837
  throw runtimeError("clone_root_insecure", "The configured clone root is not secure");
41565
41838
  }
41566
41839
  const effectiveUid = typeof process.geteuid === "function" ? process.geteuid() : null;
41567
- if (!stat.isDirectory() || effectiveUid !== null && stat.uid !== effectiveUid || (stat.mode & 18) !== 0) {
41840
+ if (!stat.isDirectory() || this.platform !== "win32" && (effectiveUid !== null && stat.uid !== effectiveUid || (stat.mode & 18) !== 0)) {
41568
41841
  throw runtimeError("clone_root_insecure", "The configured clone root is not secure");
41569
41842
  }
41570
41843
  return stat;
@@ -41611,6 +41884,46 @@ var require_headless_project_registry = __commonJS({
41611
41884
  getRoots() {
41612
41885
  return this.db.prepare("SELECT root_id, name FROM runtime_project_roots ORDER BY created_at, root_id").all().map((root) => ({ rootId: root.root_id, name: String(root.name).slice(0, 200) }));
41613
41886
  }
41887
+ gitAvailability() {
41888
+ return new Promise((resolve) => {
41889
+ var _a, _b;
41890
+ let child;
41891
+ let output = "";
41892
+ let settled = false;
41893
+ const finish = (result) => {
41894
+ if (settled) return;
41895
+ settled = true;
41896
+ clearTimeout(timer);
41897
+ resolve(result);
41898
+ };
41899
+ const timer = setTimeout(() => {
41900
+ try {
41901
+ child == null ? void 0 : child.kill();
41902
+ } catch (_) {
41903
+ }
41904
+ finish({ available: false });
41905
+ }, 2e3);
41906
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
41907
+ try {
41908
+ child = this.spawnImpl("git", ["--version"], {
41909
+ shell: false,
41910
+ stdio: ["ignore", "pipe", "ignore"],
41911
+ windowsHide: true
41912
+ });
41913
+ (_b = child.stdout) == null ? void 0 : _b.on("data", (chunk) => {
41914
+ output = `${output}${String(chunk)}`.slice(0, 200);
41915
+ });
41916
+ child.once("error", () => finish({ available: false }));
41917
+ child.once("close", (code) => {
41918
+ var _a2;
41919
+ const version = (_a2 = /^git version\s+([^\s]+)/i.exec(output.trim())) == null ? void 0 : _a2[1];
41920
+ finish(code === 0 ? { available: true, ...version ? { version } : {} } : { available: false });
41921
+ });
41922
+ } catch (_) {
41923
+ finish({ available: false });
41924
+ }
41925
+ });
41926
+ }
41614
41927
  listDirectories({ rootId, relativePath = "." } = {}) {
41615
41928
  const root = this._root(rootId);
41616
41929
  const currentPath = validateRelativePath(relativePath || ".");
@@ -42030,6 +42343,7 @@ var require_headless_project_registry = __commonJS({
42030
42343
  contained,
42031
42344
  runtimeError,
42032
42345
  validateGitUrl,
42346
+ cloneDirectoryName,
42033
42347
  validateCloneChildName,
42034
42348
  validateRelativePath
42035
42349
  };
@@ -45172,7 +45486,7 @@ var require_database = __commonJS({
45172
45486
  { status_key: "needs_testing", label: "Needs testing", color: "#3b82f6", icon: "flask-conical", sort_order: 2, is_default: 1, agent_settable: 1, prompt: "Set it when you finish the implementation and the work is pending the user testing it manually. Do not set it if there are still things left to implement." },
45173
45487
  { status_key: "working", label: "Working", color: "#fbbf24", icon: "hammer", sort_order: 3, is_default: 1, agent_settable: 1, prompt: "Set it when you start working on any request and while you are implementing, investigating or fixing something." },
45174
45488
  { status_key: "done", label: "Done", color: "#22c55e", icon: "circle-check", sort_order: 4, is_default: 1, agent_settable: 1, prompt: "Set it when the work is completely finished: implemented, validated and with its commit/push done when applicable. It is the final state." },
45175
- { status_key: "idle", label: "Idle", color: "#6b7280", icon: "circle-dashed", sort_order: 5, is_default: 1, agent_settable: 0, prompt: "Set by the app on an agent that has just been opened and has not been given any work yet. Agents cannot set this status; it clears itself as soon as you send the agent something." }
45489
+ { status_key: "idle", label: "Idle", color: "#6b7280", icon: "circle-dashed", sort_order: 5, is_default: 1, agent_settable: 0, prompt: "Set by the app when an agent is resting without an explicit work status, including after a reply finishes. It does not mean the work is complete or that user input is required. Agents cannot set this status." }
45176
45490
  ];
45177
45491
  var IDLE_TERMINAL_STATUS_KEY = "idle";
45178
45492
  var IDLE_STATUS_SEEDED_SETTING = "terminal_status_idle_seeded";
@@ -45184,7 +45498,8 @@ var require_database = __commonJS({
45184
45498
  blocked: 5,
45185
45499
  done: 6
45186
45500
  };
45187
- var LEGACY_SPANISH_DEFAULT_PROMPTS = {
45501
+ var LEGACY_DEFAULT_PROMPTS = {
45502
+ idle: "Set by the app on an agent that has just been opened and has not been given any work yet. Agents cannot set this status; it clears itself as soon as you send the agent something.",
45188
45503
  working: "Ponlo al empezar a trabajar en cualquier petici\xF3n y mientras est\xE9s implementando, investigando o arreglando algo.",
45189
45504
  needs_input: "Ponlo cuando pares porque necesites una respuesta o decisi\xF3n del usuario para continuar (una pregunta, una elecci\xF3n de dise\xF1o, un permiso).",
45190
45505
  needs_testing: "Ponlo cuando termines la implementaci\xF3n y el trabajo quede pendiente de que el usuario lo pruebe a mano. No lo pongas si a\xFAn quedan cosas por implementar.",
@@ -45198,14 +45513,14 @@ var require_database = __commonJS({
45198
45513
  label: "Blocked",
45199
45514
  color: "#ef4444",
45200
45515
  prompt_en: "Set it when you cannot make progress due to something external that does not depend on you or the user: broken CI, a dependency that is down, failing permissions, a third-party bug.",
45201
- prompt_es: LEGACY_SPANISH_DEFAULT_PROMPTS.blocked
45516
+ prompt_es: LEGACY_DEFAULT_PROMPTS.blocked
45202
45517
  },
45203
45518
  {
45204
45519
  status_key: "pending_commit",
45205
45520
  label: "Pending commit/push",
45206
45521
  color: "#a78bfa",
45207
45522
  prompt_en: "Set it when the user has already validated the work and only the commit or push remains.",
45208
- prompt_es: LEGACY_SPANISH_DEFAULT_PROMPTS.pending_commit
45523
+ prompt_es: LEGACY_DEFAULT_PROMPTS.pending_commit
45209
45524
  }
45210
45525
  ];
45211
45526
  var app;
@@ -46083,7 +46398,7 @@ var require_database = __commonJS({
46083
46398
  console.error("Error migrating idle terminal status:", error);
46084
46399
  }
46085
46400
  }
46086
- // Migration: upgrade the legacy SPANISH factory prompts to the English ones.
46401
+ // Migration: upgrade the legacy factory prompts to the English ones.
46087
46402
  // Only touches default statuses whose prompt is still the legacy string
46088
46403
  // VERBATIM (i.e. the user never edited it), so user wording always wins.
46089
46404
  // Idempotent: once upgraded, no row matches any more.
@@ -46096,7 +46411,7 @@ var require_database = __commonJS({
46096
46411
  `);
46097
46412
  const migrate = this.db.transaction(() => {
46098
46413
  for (const status of DEFAULT_TERMINAL_STATUSES) {
46099
- const legacy = LEGACY_SPANISH_DEFAULT_PROMPTS[status.status_key];
46414
+ const legacy = LEGACY_DEFAULT_PROMPTS[status.status_key];
46100
46415
  if (!legacy) continue;
46101
46416
  update.run({
46102
46417
  status_key: status.status_key,
@@ -49271,6 +49586,7 @@ var require_headless_runtime = __commonJS({
49271
49586
  "project.register",
49272
49587
  "project.clone",
49273
49588
  "project.clone.cancel",
49589
+ "project.git.availability",
49274
49590
  "project.unregister",
49275
49591
  "shortcuts.manage",
49276
49592
  "session.action",
@@ -49282,6 +49598,7 @@ var require_headless_runtime = __commonJS({
49282
49598
  "providers.list",
49283
49599
  "provider.install",
49284
49600
  "provider.login.describe",
49601
+ "session.restart",
49285
49602
  "provider.login.start",
49286
49603
  "provider.login.submit",
49287
49604
  "provider.login.cancel",
@@ -49754,17 +50071,20 @@ var require_headless_runtime = __commonJS({
49754
50071
  };
49755
50072
  const listCoordinatedSessions = () => ({
49756
50073
  sessions: Array.from(runtime.sessions.values()).flatMap((session) => {
49757
- var _a, _b;
50074
+ var _a, _b, _c, _d;
49758
50075
  return isCoordinatedSessionEligible(session) ? [{
49759
50076
  id: session.terminalUuid,
49760
50077
  name: session.title || `${session.agent || "Agent"} session`,
49761
50078
  agent: session.agent || session.provider || "",
50079
+ agent_id: session.agent || session.provider || "",
49762
50080
  project: ((_a = session.project) == null ? void 0 : _a.name) || "",
50081
+ project_icon: ((_b = session.project) == null ? void 0 : _b.icon) || "",
50082
+ project_color: ((_c = session.project) == null ? void 0 : _c.color) || "",
49763
50083
  goal: session.goal || "",
49764
50084
  activity: session.activity || "",
49765
50085
  status: session.workStatus || "",
49766
50086
  surface: "chat",
49767
- state: session.workStatus === "needs_input" ? "needs_input" : ((_b = session.currentTurn) == null ? void 0 : _b.state) === "running" ? "working" : "idle"
50087
+ state: session.workStatus === "needs_input" ? "needs_input" : ((_d = session.currentTurn) == null ? void 0 : _d.state) === "running" ? "working" : "idle"
49768
50088
  }] : [];
49769
50089
  }).slice(0, 100)
49770
50090
  });
@@ -50038,6 +50358,7 @@ ${message}`;
50038
50358
  registerProject: (payload) => registry.register(payload),
50039
50359
  cloneProject: (payload) => registry.clone(payload),
50040
50360
  cancelProjectClone: (payload) => registry.cancelClone(payload),
50361
+ gitAvailability: () => registry.gitAvailability(),
50041
50362
  unregisterProject: (payload) => {
50042
50363
  const busy = startingProjects.has(payload.projectId) || Array.from(runtime.sessions.values()).some((session) => {
50043
50364
  var _a;
@@ -50114,6 +50435,8 @@ ${message}`;
50114
50435
  peerRuntimeNetwork,
50115
50436
  dataPath: resolvedDataPath,
50116
50437
  deliverMessage: deliverCoordinatedMessage,
50438
+ listLocalSessions: listCoordinatedSessions,
50439
+ readLocalTranscript: readCoordinatedTranscript,
50117
50440
  createPairingLink: async () => {
50118
50441
  const pairing = await relay.createPairing();
50119
50442
  return { url: desktopConnectionLink(pairing), expiresAt: pairing.expiresAt };
@@ -50720,7 +51043,7 @@ var require_cas = __commonJS({
50720
51043
  loadIdentity,
50721
51044
  resolveProject
50722
51045
  } = require_headless_runtime();
50723
- var version = true ? "0.0.15" : JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8")).version;
51046
+ var version = true ? "0.0.17" : JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8")).version;
50724
51047
  function help() {
50725
51048
  return `CAS CLI ${version}
50726
51049