@super-one/cli 0.74.0-alpha.2 → 0.75.0-alpha

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.
Files changed (3) hide show
  1. package/MANIFEST.json +2 -2
  2. package/lib/cli.mjs +734 -543
  3. package/package.json +1 -1
package/lib/cli.mjs CHANGED
@@ -137,8 +137,8 @@ import { fileURLToPath } from "node:url";
137
137
  function resolveCliReleaseVersion() {
138
138
  const fromEnv = process.env.SUPERONE_CLI_VERSION?.trim();
139
139
  if (fromEnv) return fromEnv;
140
- if ("0.74.0-alpha.2".trim()) {
141
- return "0.74.0-alpha.2".trim();
140
+ if ("0.75.0-alpha".trim()) {
141
+ return "0.75.0-alpha".trim();
142
142
  }
143
143
  const fromDist = readDistManifestVersion();
144
144
  if (fromDist) return fromDist;
@@ -510,7 +510,7 @@ var init_superone_tool_descriptions = __esm({
510
510
  ENVIRONMENT_GET_INFO_INCLUDE_DESCRIPTION = 'Detail groups to add. Default ["hardware"]; [] returns IDs, labels and state only.';
511
511
  ARCHIVE_ENVIRONMENT_DESCRIPTION = "Target environment ID from environment_get_info. Omit or use localhost for the calling session host.";
512
512
  LAYOUT_SNAPSHOT_DESCRIPTION = "Read the SuperOne desktop layout around your session: whether the user can see your chat, the sidebar, your activity panel (open, side, maximized), its tab groups (index and px box, so you can tell top/bottom from side by side) and tabs (id, kind, title, active, group, openedBy), your bottom terminal, the split view, chatMode and chatPosition, and sizes: px widths of the window, sidebar, panel and chat, with the min/max each column can take. Call it for tab and group ids before layout_act, for sizes before a resize, or to check what the user is looking at.";
513
- LAYOUT_ACT_DESCRIPTION = "Arrange the SuperOne desktop for the user: open a file as a tab; focus or close tabs, move one beside or into a group or reorder it (move_tab), resize a group; show, hide, maximize, move or resize your activity panel; show or hide your terminal; show, hide or resize the sidebar (widen a column to narrow the chat); dock, float or iconify the chat; put a session you launched beside yours. Open browser and terminal tabs with browser_tabs / terminal_tabs. You can close only tabs and panes you opened. It never moves the user: in the background your own layout changes wait for them (status queued) and window-wide ones are skipped. `visible` says whether the user sees the result now.";
513
+ LAYOUT_ACT_DESCRIPTION = "Arrange the SuperOne desktop for the user: open a file as a tab; focus or close tabs, move one beside or into a group or reorder it (move_tab), resize a group; show, hide, maximize, move or resize your activity panel; show or hide your terminal; show, hide or resize the sidebar (widen a column to narrow the chat); dock, float or iconify the chat; put any other session beside yours. Open browser and terminal tabs with browser_tabs / terminal_tabs. It never moves the user: in the background your own layout changes wait for them (status queued) and window-wide ones are skipped. `visible` says whether the user sees the result now.";
514
514
  LAYOUT_ACT_PATH_DESCRIPTION = "open_file: the file, absolute or relative to your working folder.";
515
515
  LAYOUT_ACT_LINE_DESCRIPTION = "open_file: 1-based line to reveal.";
516
516
  LAYOUT_ACT_TAB_DESCRIPTION = "Tab id from layout_snapshot (browser_tabs / terminal_tabs ids work too). focus_tab, close_tab, move_tab; maximize_panel defaults to the active tab.";
@@ -521,7 +521,7 @@ var init_superone_tool_descriptions = __esm({
521
521
  LAYOUT_ACT_HEIGHT_DESCRIPTION = "resize_group: target height in px.";
522
522
  LAYOUT_ACT_MODE_DESCRIPTION = "chat_mode: docked (a column beside the panel), floating (a movable window over the panel, which then fills the window) or icon (that window collapsed).";
523
523
  LAYOUT_ACT_POSITION_DESCRIPTION = "chat_mode: where the floating chat or its icon snaps. Alone it moves the chat without changing mode; while docked it is kept for the next float.";
524
- LAYOUT_ACT_SESSION_DESCRIPTION = "split_session / close_split: a session you launched with session_collab_*.";
524
+ LAYOUT_ACT_SESSION_DESCRIPTION = "split_session / close_split: a session id from session_list, session_search or session_collab_*.";
525
525
  }
526
526
  });
527
527
 
@@ -15634,6 +15634,23 @@ var init_session_runtime = __esm({
15634
15634
  }
15635
15635
  this.systemPromptAppends.set(sessionId, prompt.trim());
15636
15636
  }
15637
+ /**
15638
+ * `to` continues `from`, a collaboration child of a session on another
15639
+ * machine: `to` becomes that child, with its parent and prompt, and `from`
15640
+ * a plain session. No-op when `from` has no such parent.
15641
+ */
15642
+ moveExternalParent(from, to2) {
15643
+ const source = this.live.get(from);
15644
+ const target = this.live.get(to2);
15645
+ const parent = source?.externalParent;
15646
+ if (!source || !target || !parent) return;
15647
+ target.externalParent = { sessionId: parent.sessionId };
15648
+ source.externalParent = null;
15649
+ this.systemPromptAppends.set(to2, collaborationSystemPrompt(parent.sessionId));
15650
+ this.systemPromptAppends.delete(from);
15651
+ this.persist(target);
15652
+ this.persist(source);
15653
+ }
15637
15654
  /** Set agent cwd (project root or worktree). Null clears to project default. */
15638
15655
  setCwd(sessionId, cwd) {
15639
15656
  const session = this.live.get(sessionId);
@@ -20939,11 +20956,246 @@ var init_sqlite_session_store = __esm({
20939
20956
  }
20940
20957
  });
20941
20958
 
20959
+ // ../../packages/shared/src/harness/acp-brand.ts
20960
+ function isGrokAcpAgent(agentId) {
20961
+ if (!agentId) return false;
20962
+ const id = agentId.toLowerCase();
20963
+ return id.includes("grok");
20964
+ }
20965
+ function isOpenCodeAcpAgent(agentId) {
20966
+ if (!agentId) return false;
20967
+ return agentId.toLowerCase().includes("opencode");
20968
+ }
20969
+ function resolveHarnessBrandKey(harnessId, acpAgentId) {
20970
+ if (!harnessId) return "claude";
20971
+ if (harnessId !== "acp") return harnessId;
20972
+ if (isGrokAcpAgent(acpAgentId)) return "acp-grok";
20973
+ if (isOpenCodeAcpAgent(acpAgentId)) return "acp-opencode";
20974
+ if (acpAgentId?.trim()) {
20975
+ const short = acpAgentId.trim().toLowerCase().replace(/-build$/, "").replace(/[^a-z0-9]+/g, "-");
20976
+ return short ? `acp-${short}` : "acp";
20977
+ }
20978
+ return "acp";
20979
+ }
20980
+ function acpAgentDisplayName(agentId, catalogName) {
20981
+ if (catalogName?.trim()) {
20982
+ return catalogName.trim().replace(/\s+Build$/i, "");
20983
+ }
20984
+ if (isGrokAcpAgent(agentId)) return "Grok";
20985
+ if (isOpenCodeAcpAgent(agentId)) return "OpenCode";
20986
+ if (agentId?.trim()) {
20987
+ return agentId.trim().replace(/-build$/i, "").split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
20988
+ }
20989
+ return "ACP";
20990
+ }
20991
+ var init_acp_brand = __esm({
20992
+ "../../packages/shared/src/harness/acp-brand.ts"() {
20993
+ "use strict";
20994
+ }
20995
+ });
20996
+
20997
+ // ../../packages/shared/src/harness/harness-capabilities.ts
20998
+ function supportsShortWidgetResult(harnessId) {
20999
+ return !!harnessId && Object.hasOwn(HARNESS_CAPABILITIES, harnessId) && HARNESS_CAPABILITIES[harnessId].supportsShortWidgetResult;
21000
+ }
21001
+ var HARNESS_CAPABILITIES, ALL_GOAL_LIFECYCLE_ARGS;
21002
+ var init_harness_capabilities = __esm({
21003
+ "../../packages/shared/src/harness/harness-capabilities.ts"() {
21004
+ "use strict";
21005
+ init_acp_brand();
21006
+ HARNESS_CAPABILITIES = {
21007
+ claude: {
21008
+ supportsMcp: true,
21009
+ supportsPlanMode: true,
21010
+ supportsTodos: true,
21011
+ supportsSubagents: true,
21012
+ supportsCompact: true,
21013
+ supportsStreamingToolInput: true,
21014
+ supportsQueuedSteer: true,
21015
+ supportsQueuedSteerSoon: true,
21016
+ // SDK `additionalDirectories`.
21017
+ supportsAdditionalDirs: true,
21018
+ // SDK `forkSession()` copies + remaps the transcript jsonl.
21019
+ supportsFork: true,
21020
+ supportsContextUsage: true,
21021
+ softCancel: false,
21022
+ // Built-in `/goal <condition>`: a Stop hook re-checks the condition after
21023
+ // each turn and keeps going until it is met, so there is no paused state —
21024
+ // the goal is either live or cleared.
21025
+ goal: { lifecycleArgs: ["clear"], canPause: false, transport: "slash", semantics: "condition" },
21026
+ terminalCommandApproval: "harness",
21027
+ modUi: true,
21028
+ supportsShortWidgetResult: true,
21029
+ // `ultrathink` adds a deeper-reasoning reminder to the turn (effort is
21030
+ // untouched); `ultracode` opts it into the Workflow tool, on messages stamped
21031
+ // human — see `claudeMessageOrigin`.
21032
+ promptKeywords: ["ultrathink", "ultracode"],
21033
+ displayName: "Claude"
21034
+ },
21035
+ codex: {
21036
+ supportsRealtimeVoice: true,
21037
+ supportsMcp: true,
21038
+ supportsPlanMode: true,
21039
+ supportsTodos: false,
21040
+ supportsSubagents: false,
21041
+ supportsCompact: true,
21042
+ supportsStreamingToolInput: false,
21043
+ supportsQueuedSteer: true,
21044
+ // Core's queue item either interrupts the turn or waits it out.
21045
+ supportsQueuedSteerSoon: false,
21046
+ // sandbox_workspace_write.writable_roots, re-sent every turn.
21047
+ supportsAdditionalDirs: true,
21048
+ // app-server thread fork, truncatable at a turn id.
21049
+ supportsFork: true,
21050
+ // Token usage arrives on the session snapshot only.
21051
+ supportsContextUsage: false,
21052
+ softCancel: false,
21053
+ // `thread/goal/{get,set,clear}` over the app server; SuperOne drives the
21054
+ // follow-up turns itself, so every transition is an explicit RPC.
21055
+ goal: { lifecycleArgs: [], canPause: true, transport: "rpc", semantics: "objective" },
21056
+ terminalCommandApproval: "harness",
21057
+ modUi: false,
21058
+ supportsShortWidgetResult: true,
21059
+ promptKeywords: [],
21060
+ displayName: "Codex"
21061
+ },
21062
+ acp: {
21063
+ // Host injects SuperOne MCP on every ACP session; user MCP attach is a separate gap.
21064
+ supportsMcp: true,
21065
+ // Agent-driven plan + x.ai/exit_plan_mode approval is shipped (not host enter-plan).
21066
+ supportsPlanMode: true,
21067
+ // ACP session/update plan entries map to todo_write-style UI events.
21068
+ supportsTodos: true,
21069
+ // Subagent_* notifications already map to task rows, but there is no nested
21070
+ // child chrome for ACP. Keep false until Task UI can host those sessions.
21071
+ supportsSubagents: false,
21072
+ // Host intercepts `/compact` → `x.ai/compact_conversation`.
21073
+ supportsCompact: true,
21074
+ supportsStreamingToolInput: false,
21075
+ // Mid-turn send queues. Steer (`now`) is a new `session/prompt` with
21076
+ // `_meta.sendNow`, which cancels the live turn and runs the message next.
21077
+ // Steer soon is `x.ai/interject`: next safe point, the tool in flight stays.
21078
+ supportsQueuedSteer: true,
21079
+ supportsQueuedSteerSoon: true,
21080
+ // session/new additionalDirectories, gated per agent capability.
21081
+ supportsAdditionalDirs: true,
21082
+ // Cold `x.ai/session/fork` copies Grok session files; SuperOne then resumes the child.
21083
+ supportsFork: true,
21084
+ supportsContextUsage: true,
21085
+ softCancel: true,
21086
+ // Grok only — see `resolveGoalCapability`. The agent owns the loop and
21087
+ // reports back over `goal_updated`; the host just posts `/goal …` lines.
21088
+ goal: {
21089
+ lifecycleArgs: ["status", "pause", "resume", "clear"],
21090
+ canPause: true,
21091
+ transport: "slash",
21092
+ semantics: "objective"
21093
+ },
21094
+ terminalCommandApproval: "harness",
21095
+ modUi: false,
21096
+ supportsShortWidgetResult: false,
21097
+ promptKeywords: [],
21098
+ displayName: "Others"
21099
+ },
21100
+ opencode: {
21101
+ supportsMcp: true,
21102
+ supportsPlanMode: true,
21103
+ supportsTodos: true,
21104
+ supportsSubagents: true,
21105
+ supportsCompact: true,
21106
+ supportsStreamingToolInput: false,
21107
+ supportsQueuedSteer: false,
21108
+ supportsQueuedSteerSoon: false,
21109
+ // Single `directory` only.
21110
+ supportsAdditionalDirs: false,
21111
+ // Server-side `forkSession(id, anchor)` + `moveSession`.
21112
+ supportsFork: true,
21113
+ supportsContextUsage: true,
21114
+ softCancel: true,
21115
+ goal: null,
21116
+ terminalCommandApproval: "harness",
21117
+ modUi: false,
21118
+ supportsShortWidgetResult: false,
21119
+ promptKeywords: [],
21120
+ displayName: "OpenCode"
21121
+ },
21122
+ cursor: {
21123
+ supportsMcp: true,
21124
+ supportsPlanMode: true,
21125
+ // updateTodos / task tool deltas when event map lands (PR5+)
21126
+ supportsTodos: true,
21127
+ supportsSubagents: true,
21128
+ supportsCompact: false,
21129
+ supportsStreamingToolInput: true,
21130
+ supportsQueuedSteer: false,
21131
+ supportsQueuedSteerSoon: false,
21132
+ // Single cwd; multi-root parked in the harness design doc.
21133
+ supportsAdditionalDirs: false,
21134
+ // SDK has no transcript-fork API; the adapter creates a blank agent.
21135
+ supportsFork: false,
21136
+ supportsContextUsage: true,
21137
+ softCancel: false,
21138
+ goal: null,
21139
+ // The @cursor/sdk local executor auto-approves custom tools with no host hook.
21140
+ terminalCommandApproval: "executor",
21141
+ modUi: false,
21142
+ supportsShortWidgetResult: false,
21143
+ promptKeywords: [],
21144
+ displayName: "Cursor"
21145
+ },
21146
+ dsh: {
21147
+ // In-process dsh Cordis tree (docs/harness/dsh/deepseek-harness-integration.md).
21148
+ // Flags flip only when the corresponding event path is wired.
21149
+ // `supportsStreamingToolInput`: live `tool-call-delta` frames open the row
21150
+ // and stream its arguments before the durable `tool/call` completes it.
21151
+ // `supportsCompact`: `compaction-basic` compacts automatically at context
21152
+ // pressure and on provider overflow; `/compact` drives `compactNow()`.
21153
+ // `supportsMcp` covers both SuperOne's own tools (native dsh rows) and
21154
+ // third-party servers read from dsh's own profile patch layer.
21155
+ // `supportsSubagents`: foreground delegation (`dsh-tool-subagent` over the
21156
+ // in-process spawn provider), rendered as a Task block with the child's
21157
+ // steps nested under it. Background and continuable children are not wired.
21158
+ // `supportsPlanMode`: `plan` also turns on dsh plan mode; `exit_plan_mode`
21159
+ // reaches the plan-approval card through the user-questions seam.
21160
+ // `supportsQueuedSteer`: `agent.steer()` lands at the next step boundary
21161
+ // and never cancels a tool, so there is one steer and no separate "soon".
21162
+ supportsMcp: true,
21163
+ supportsPlanMode: true,
21164
+ supportsTodos: true,
21165
+ supportsSubagents: true,
21166
+ supportsCompact: true,
21167
+ supportsStreamingToolInput: true,
21168
+ supportsQueuedSteer: true,
21169
+ supportsQueuedSteerSoon: false,
21170
+ // Single cwd.
21171
+ supportsAdditionalDirs: false,
21172
+ // `runtime.forkSession` copies the log prefix up to an event seq.
21173
+ supportsFork: true,
21174
+ supportsContextUsage: false,
21175
+ softCancel: false,
21176
+ goal: null,
21177
+ terminalCommandApproval: "harness",
21178
+ modUi: false,
21179
+ supportsShortWidgetResult: false,
21180
+ promptKeywords: [],
21181
+ displayName: "DeepSeek"
21182
+ }
21183
+ };
21184
+ ALL_GOAL_LIFECYCLE_ARGS = [...new Set(
21185
+ Object.values(HARNESS_CAPABILITIES).flatMap((caps) => caps.goal?.lifecycleArgs ?? [])
21186
+ )];
21187
+ }
21188
+ });
21189
+
20942
21190
  // ../../packages/shared/src/session-provider-definitions.ts
21191
+ function sessionProviderDisplayName(row2) {
21192
+ return row2.isBase ? HARNESS_CAPABILITIES[row2.harnessId].displayName : row2.name;
21193
+ }
20943
21194
  var BASE_SESSION_PROVIDERS, BASE_SESSION_PROVIDER_DEFINITIONS;
20944
21195
  var init_session_provider_definitions = __esm({
20945
21196
  "../../packages/shared/src/session-provider-definitions.ts"() {
20946
21197
  "use strict";
21198
+ init_harness_capabilities();
20947
21199
  BASE_SESSION_PROVIDERS = {
20948
21200
  claude: { id: "claude-base", name: "Claude (Base)", config: {} },
20949
21201
  codex: { id: "codex-base", name: "Codex (Base)", config: {} },
@@ -36579,237 +36831,6 @@ var init_slash_commands = __esm({
36579
36831
  }
36580
36832
  });
36581
36833
 
36582
- // ../../packages/shared/src/harness/acp-brand.ts
36583
- function isGrokAcpAgent(agentId) {
36584
- if (!agentId) return false;
36585
- const id = agentId.toLowerCase();
36586
- return id.includes("grok");
36587
- }
36588
- function isOpenCodeAcpAgent(agentId) {
36589
- if (!agentId) return false;
36590
- return agentId.toLowerCase().includes("opencode");
36591
- }
36592
- function resolveHarnessBrandKey(harnessId, acpAgentId) {
36593
- if (!harnessId) return "claude";
36594
- if (harnessId !== "acp") return harnessId;
36595
- if (isGrokAcpAgent(acpAgentId)) return "acp-grok";
36596
- if (isOpenCodeAcpAgent(acpAgentId)) return "acp-opencode";
36597
- if (acpAgentId?.trim()) {
36598
- const short = acpAgentId.trim().toLowerCase().replace(/-build$/, "").replace(/[^a-z0-9]+/g, "-");
36599
- return short ? `acp-${short}` : "acp";
36600
- }
36601
- return "acp";
36602
- }
36603
- function acpAgentDisplayName(agentId, catalogName) {
36604
- if (catalogName?.trim()) {
36605
- return catalogName.trim().replace(/\s+Build$/i, "");
36606
- }
36607
- if (isGrokAcpAgent(agentId)) return "Grok";
36608
- if (isOpenCodeAcpAgent(agentId)) return "OpenCode";
36609
- if (agentId?.trim()) {
36610
- return agentId.trim().replace(/-build$/i, "").split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
36611
- }
36612
- return "ACP";
36613
- }
36614
- var init_acp_brand = __esm({
36615
- "../../packages/shared/src/harness/acp-brand.ts"() {
36616
- "use strict";
36617
- }
36618
- });
36619
-
36620
- // ../../packages/shared/src/harness/harness-capabilities.ts
36621
- function supportsShortWidgetResult(harnessId) {
36622
- return !!harnessId && Object.hasOwn(HARNESS_CAPABILITIES, harnessId) && HARNESS_CAPABILITIES[harnessId].supportsShortWidgetResult;
36623
- }
36624
- var HARNESS_CAPABILITIES, ALL_GOAL_LIFECYCLE_ARGS;
36625
- var init_harness_capabilities = __esm({
36626
- "../../packages/shared/src/harness/harness-capabilities.ts"() {
36627
- "use strict";
36628
- init_acp_brand();
36629
- HARNESS_CAPABILITIES = {
36630
- claude: {
36631
- supportsMcp: true,
36632
- supportsPlanMode: true,
36633
- supportsTodos: true,
36634
- supportsSubagents: true,
36635
- supportsCompact: true,
36636
- supportsStreamingToolInput: true,
36637
- supportsQueuedSteer: true,
36638
- supportsQueuedSteerSoon: true,
36639
- // SDK `additionalDirectories`.
36640
- supportsAdditionalDirs: true,
36641
- // SDK `forkSession()` copies + remaps the transcript jsonl.
36642
- supportsFork: true,
36643
- supportsContextUsage: true,
36644
- softCancel: false,
36645
- // Built-in `/goal <condition>`: a Stop hook re-checks the condition after
36646
- // each turn and keeps going until it is met, so there is no paused state —
36647
- // the goal is either live or cleared.
36648
- goal: { lifecycleArgs: ["clear"], canPause: false, transport: "slash", semantics: "condition" },
36649
- terminalCommandApproval: "harness",
36650
- modUi: true,
36651
- supportsShortWidgetResult: true,
36652
- // `ultrathink` adds a deeper-reasoning reminder to the turn (effort is
36653
- // untouched); `ultracode` opts it into the Workflow tool, on messages stamped
36654
- // human — see `claudeMessageOrigin`.
36655
- promptKeywords: ["ultrathink", "ultracode"],
36656
- displayName: "Claude"
36657
- },
36658
- codex: {
36659
- supportsRealtimeVoice: true,
36660
- supportsMcp: true,
36661
- supportsPlanMode: true,
36662
- supportsTodos: false,
36663
- supportsSubagents: false,
36664
- supportsCompact: true,
36665
- supportsStreamingToolInput: false,
36666
- supportsQueuedSteer: true,
36667
- // Core's queue item either interrupts the turn or waits it out.
36668
- supportsQueuedSteerSoon: false,
36669
- // sandbox_workspace_write.writable_roots, re-sent every turn.
36670
- supportsAdditionalDirs: true,
36671
- // app-server thread fork, truncatable at a turn id.
36672
- supportsFork: true,
36673
- // Token usage arrives on the session snapshot only.
36674
- supportsContextUsage: false,
36675
- softCancel: false,
36676
- // `thread/goal/{get,set,clear}` over the app server; SuperOne drives the
36677
- // follow-up turns itself, so every transition is an explicit RPC.
36678
- goal: { lifecycleArgs: [], canPause: true, transport: "rpc", semantics: "objective" },
36679
- terminalCommandApproval: "harness",
36680
- modUi: false,
36681
- supportsShortWidgetResult: true,
36682
- promptKeywords: [],
36683
- displayName: "Codex"
36684
- },
36685
- acp: {
36686
- // Host injects SuperOne MCP on every ACP session; user MCP attach is a separate gap.
36687
- supportsMcp: true,
36688
- // Agent-driven plan + x.ai/exit_plan_mode approval is shipped (not host enter-plan).
36689
- supportsPlanMode: true,
36690
- // ACP session/update plan entries map to todo_write-style UI events.
36691
- supportsTodos: true,
36692
- // Subagent_* notifications already map to task rows, but there is no nested
36693
- // child chrome for ACP. Keep false until Task UI can host those sessions.
36694
- supportsSubagents: false,
36695
- // Host intercepts `/compact` → `x.ai/compact_conversation`.
36696
- supportsCompact: true,
36697
- supportsStreamingToolInput: false,
36698
- // Mid-turn send queues. Steer (`now`) is a new `session/prompt` with
36699
- // `_meta.sendNow`, which cancels the live turn and runs the message next.
36700
- // Steer soon is `x.ai/interject`: next safe point, the tool in flight stays.
36701
- supportsQueuedSteer: true,
36702
- supportsQueuedSteerSoon: true,
36703
- // session/new additionalDirectories, gated per agent capability.
36704
- supportsAdditionalDirs: true,
36705
- // Cold `x.ai/session/fork` copies Grok session files; SuperOne then resumes the child.
36706
- supportsFork: true,
36707
- supportsContextUsage: true,
36708
- softCancel: true,
36709
- // Grok only — see `resolveGoalCapability`. The agent owns the loop and
36710
- // reports back over `goal_updated`; the host just posts `/goal …` lines.
36711
- goal: {
36712
- lifecycleArgs: ["status", "pause", "resume", "clear"],
36713
- canPause: true,
36714
- transport: "slash",
36715
- semantics: "objective"
36716
- },
36717
- terminalCommandApproval: "harness",
36718
- modUi: false,
36719
- supportsShortWidgetResult: false,
36720
- promptKeywords: [],
36721
- displayName: "Others"
36722
- },
36723
- opencode: {
36724
- supportsMcp: true,
36725
- supportsPlanMode: true,
36726
- supportsTodos: true,
36727
- supportsSubagents: true,
36728
- supportsCompact: true,
36729
- supportsStreamingToolInput: false,
36730
- supportsQueuedSteer: false,
36731
- supportsQueuedSteerSoon: false,
36732
- // Single `directory` only.
36733
- supportsAdditionalDirs: false,
36734
- // Server-side `forkSession(id, anchor)` + `moveSession`.
36735
- supportsFork: true,
36736
- supportsContextUsage: true,
36737
- softCancel: true,
36738
- goal: null,
36739
- terminalCommandApproval: "harness",
36740
- modUi: false,
36741
- supportsShortWidgetResult: false,
36742
- promptKeywords: [],
36743
- displayName: "OpenCode"
36744
- },
36745
- cursor: {
36746
- supportsMcp: true,
36747
- supportsPlanMode: true,
36748
- // updateTodos / task tool deltas when event map lands (PR5+)
36749
- supportsTodos: true,
36750
- supportsSubagents: true,
36751
- supportsCompact: false,
36752
- supportsStreamingToolInput: true,
36753
- supportsQueuedSteer: false,
36754
- supportsQueuedSteerSoon: false,
36755
- // Single cwd; multi-root parked in the harness design doc.
36756
- supportsAdditionalDirs: false,
36757
- // SDK has no transcript-fork API; the adapter creates a blank agent.
36758
- supportsFork: false,
36759
- supportsContextUsage: true,
36760
- softCancel: false,
36761
- goal: null,
36762
- // The @cursor/sdk local executor auto-approves custom tools with no host hook.
36763
- terminalCommandApproval: "executor",
36764
- modUi: false,
36765
- supportsShortWidgetResult: false,
36766
- promptKeywords: [],
36767
- displayName: "Cursor"
36768
- },
36769
- dsh: {
36770
- // In-process dsh Cordis tree (docs/harness/dsh/deepseek-harness-integration.md).
36771
- // Flags flip only when the corresponding event path is wired.
36772
- // `supportsStreamingToolInput`: live `tool-call-delta` frames open the row
36773
- // and stream its arguments before the durable `tool/call` completes it.
36774
- // `supportsCompact`: `compaction-basic` compacts automatically at context
36775
- // pressure and on provider overflow; `/compact` drives `compactNow()`.
36776
- // `supportsMcp` covers both SuperOne's own tools (native dsh rows) and
36777
- // third-party servers read from dsh's own profile patch layer.
36778
- // `supportsSubagents`: foreground delegation (`dsh-tool-subagent` over the
36779
- // in-process spawn provider), rendered as a Task block with the child's
36780
- // steps nested under it. Background and continuable children are not wired.
36781
- // `supportsPlanMode`: `plan` also turns on dsh plan mode; `exit_plan_mode`
36782
- // reaches the plan-approval card through the user-questions seam.
36783
- // `supportsQueuedSteer`: `agent.steer()` lands at the next step boundary
36784
- // and never cancels a tool, so there is one steer and no separate "soon".
36785
- supportsMcp: true,
36786
- supportsPlanMode: true,
36787
- supportsTodos: true,
36788
- supportsSubagents: true,
36789
- supportsCompact: true,
36790
- supportsStreamingToolInput: true,
36791
- supportsQueuedSteer: true,
36792
- supportsQueuedSteerSoon: false,
36793
- // Single cwd.
36794
- supportsAdditionalDirs: false,
36795
- // `runtime.forkSession` copies the log prefix up to an event seq.
36796
- supportsFork: true,
36797
- supportsContextUsage: false,
36798
- softCancel: false,
36799
- goal: null,
36800
- terminalCommandApproval: "harness",
36801
- modUi: false,
36802
- supportsShortWidgetResult: false,
36803
- promptKeywords: [],
36804
- displayName: "DeepSeek"
36805
- }
36806
- };
36807
- ALL_GOAL_LIFECYCLE_ARGS = [...new Set(
36808
- Object.values(HARNESS_CAPABILITIES).flatMap((caps) => caps.goal?.lifecycleArgs ?? [])
36809
- )];
36810
- }
36811
- });
36812
-
36813
36834
  // ../../packages/shared/src/session-goal.ts
36814
36835
  function sessionGoalFromClaudeActive(raw) {
36815
36836
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
@@ -36911,6 +36932,22 @@ var init_plugin_notice_wire = __esm({
36911
36932
  }
36912
36933
  });
36913
36934
 
36935
+ // ../../packages/claude/src/artifact-result.ts
36936
+ function artifactResultTitle(toolName, toolUseResult) {
36937
+ if (toolName !== "Artifact" || !toolUseResult || typeof toolUseResult !== "object") return void 0;
36938
+ const result3 = toolUseResult;
36939
+ const read5 = result3.read && typeof result3.read === "object" ? result3.read : null;
36940
+ return nonEmpty2(result3.title) ?? nonEmpty2(read5?.title);
36941
+ }
36942
+ function nonEmpty2(value) {
36943
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
36944
+ }
36945
+ var init_artifact_result = __esm({
36946
+ "../../packages/claude/src/artifact-result.ts"() {
36947
+ "use strict";
36948
+ }
36949
+ });
36950
+
36914
36951
  // ../../packages/claude/src/agent-event-mapper.ts
36915
36952
  function emptyResult(sessionId) {
36916
36953
  return {
@@ -37001,6 +37038,8 @@ function buildClaudeResultMetadata(result3, startedAt, pausedMs, lastAssistantUs
37001
37038
  cacheReadInputTokens: usage2.cache_read_input_tokens ?? 0,
37002
37039
  cacheCreationInputTokens: usage2.cache_creation_input_tokens ?? 0
37003
37040
  };
37041
+ const cacheTtl = writtenCacheTtl(usage2.cache_creation);
37042
+ if (cacheTtl) metadata.usage.cacheTtl = cacheTtl;
37004
37043
  }
37005
37044
  if (result3.modelUsage) {
37006
37045
  metadata.modelUsage = {};
@@ -37022,6 +37061,11 @@ function buildClaudeResultMetadata(result3, startedAt, pausedMs, lastAssistantUs
37022
37061
  }
37023
37062
  return metadata;
37024
37063
  }
37064
+ function writtenCacheTtl(cacheCreation) {
37065
+ if ((cacheCreation?.ephemeral_1h_input_tokens ?? 0) > 0) return "1h";
37066
+ if ((cacheCreation?.ephemeral_5m_input_tokens ?? 0) > 0) return "5m";
37067
+ return void 0;
37068
+ }
37025
37069
  function createClaudeAgentEventMapper(options) {
37026
37070
  const now = options.now ?? Date.now;
37027
37071
  const startedAt = options.startedAt ?? now();
@@ -37064,6 +37108,7 @@ function createClaudeAgentEventMapper(options) {
37064
37108
  const outputPath = isBash ? extractBashOutputPath(text2) : void 0;
37065
37109
  const isTimedOut = isBash ? extractBashKilled(toolUseResult) : void 0;
37066
37110
  const taskCreateTodo = extractTaskCreateTodo(toolName, toolUseResult, text2);
37111
+ const resultTitle = artifactResultTitle(toolName, toolUseResult);
37067
37112
  const isError = isClaudeToolLayerError(toolName, sdkIsError, text2);
37068
37113
  const app = options.toolApps?.toolResult(toolUseId, toolUseResult, sdkIsError, content);
37069
37114
  emit({
@@ -37077,6 +37122,7 @@ function createClaudeAgentEventMapper(options) {
37077
37122
  ...isTimedOut ? { isTimedOut } : {},
37078
37123
  ...isError ? { isError: true } : {},
37079
37124
  ...taskCreateTodo ?? {},
37125
+ ...resultTitle ? { resultTitle } : {},
37080
37126
  parentToolUseId,
37081
37127
  ...app ? { app } : {}
37082
37128
  },
@@ -37638,6 +37684,7 @@ var init_agent_event_mapper2 = __esm({
37638
37684
  init_session_goal();
37639
37685
  init_result_failure();
37640
37686
  init_plugin_notice_wire();
37687
+ init_artifact_result();
37641
37688
  }
37642
37689
  });
37643
37690
 
@@ -98425,7 +98472,10 @@ var BUILTIN_TOOL_INPUT_FIELDS = {
98425
98472
  TaskList: ["status"],
98426
98473
  TodoList: ["total", "completed"],
98427
98474
  UpdateGoal: ["message", "blocked_reason"],
98428
- Artifact: ["action", "scope", "title"],
98475
+ Artifact: ["action", "scope", "title", "file_path", "url"],
98476
+ ArtifactComments: ["action"],
98477
+ ArtifactData: ["action", "collection", "doc_id"],
98478
+ ArtifactCheck: ["action"],
98429
98479
  SandboxNetworkAccess: ["host"],
98430
98480
  ToolSearch: ["query"],
98431
98481
  SearchTools: ["query"],
@@ -102062,6 +102112,10 @@ async function handleSessionCreate(payload, ctx) {
102062
102112
  if (p3.externalParent != null && !externalParentSessionId) {
102063
102113
  return { error: { code: "invalid_argument", message: "externalParent.sessionId is required" } };
102064
102114
  }
102115
+ if (p3.continuesFrom != null && typeof p3.continuesFrom !== "string") {
102116
+ return { error: { code: "invalid_argument", message: "continuesFrom must be a string" } };
102117
+ }
102118
+ const continuesFrom = typeof p3.continuesFrom === "string" && p3.continuesFrom.trim() ? p3.continuesFrom.trim() : null;
102065
102119
  try {
102066
102120
  const agentSettings = loadNodeAgentSettings(ctx.settingsConfigPath);
102067
102121
  const defaults2 = resolveAgentTurnDefaults(agentSettings, harnessId);
@@ -102122,7 +102176,18 @@ async function handleSessionCreate(payload, ctx) {
102122
102176
  ctx.drafts.assertControl(selections.draftId, ctx.client.clientSessionId, selections.draftLeaseId);
102123
102177
  };
102124
102178
  assertCreate();
102125
- let session = ctx.sessions.createOnHost ? await ctx.sessions.createOnHost({ ...createInput, ...selections }, { settings: initialSettings, assertCreate }) : ctx.sessions.create(createInput);
102179
+ const continuation = continuesFrom && !ctx.sessions.createOnHost ? ctx.collaboration : void 0;
102180
+ if (continuesFrom && !ctx.sessions.createOnHost) {
102181
+ if (!continuation?.assertContinuable || !continuation.continueSession) {
102182
+ throw Object.assign(new Error("This host cannot continue a session in a new one"), { code: "unsupported" });
102183
+ }
102184
+ continuation.assertContinuable(continuesFrom);
102185
+ }
102186
+ let session = ctx.sessions.createOnHost ? await ctx.sessions.createOnHost(
102187
+ { ...createInput, ...selections, ...continuesFrom ? { continuesFrom } : {} },
102188
+ { settings: initialSettings, assertCreate }
102189
+ ) : ctx.sessions.create(createInput);
102190
+ if (continuesFrom && continuation) continuation.continueSession(continuesFrom, session.sessionId);
102126
102191
  if (!ctx.sessions.createOnHost && (model || effort || permissionMode || sandboxMode || Object.keys(extras).length)) {
102127
102192
  session = await ctx.sessions.patchSettings(session.sessionId, {
102128
102193
  ...extras,
@@ -109019,6 +109084,364 @@ import Database5 from "better-sqlite3";
109019
109084
  import { mkdirSync as mkdirSync24 } from "node:fs";
109020
109085
  import { dirname as dirname20 } from "node:path";
109021
109086
 
109087
+ // ../../packages/runtime/src/collaboration/store.ts
109088
+ import { randomUUID as randomUUID17 } from "node:crypto";
109089
+
109090
+ // ../../packages/runtime/src/collaboration/errors.ts
109091
+ var CollaborationError = class extends Error {
109092
+ constructor(message, code) {
109093
+ super(message);
109094
+ this.code = code;
109095
+ this.name = "CollaborationError";
109096
+ }
109097
+ code;
109098
+ };
109099
+
109100
+ // ../../packages/runtime/src/collaboration/store.ts
109101
+ var SESSION_CONTINUATION_TABLE_DDL = `
109102
+ CREATE TABLE IF NOT EXISTS session_continuations (
109103
+ from_session_id TEXT PRIMARY KEY NOT NULL,
109104
+ to_session_id TEXT NOT NULL UNIQUE,
109105
+ created_at TEXT NOT NULL
109106
+ );
109107
+ `;
109108
+ var MAX_CONTINUATION_HOPS = 64;
109109
+ var GRANT_COLUMNS = `credential_hash AS grant_id, parent_session_id, child_session_id,
109110
+ agent_id, task, config_json, task_sent, COALESCE(kind, 'spawn') AS kind, started_at`;
109111
+ var MESSAGE_COLUMNS = `id, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at`;
109112
+ function nowIso() {
109113
+ return (/* @__PURE__ */ new Date()).toISOString();
109114
+ }
109115
+ var CollaborationStore = class {
109116
+ constructor(db) {
109117
+ this.db = db;
109118
+ }
109119
+ db;
109120
+ transaction(fn) {
109121
+ return this.db.transaction(fn)();
109122
+ }
109123
+ // --- grants --------------------------------------------------------------
109124
+ grantById(grantId) {
109125
+ return this.db.prepare(`SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants WHERE credential_hash = ?`).get(grantId) ?? null;
109126
+ }
109127
+ /**
109128
+ * The grant an approved launch created. launchIds are agent-chosen and may
109129
+ * repeat across requests, so the latest approval wins.
109130
+ */
109131
+ grantForLaunch(parentSessionId, launchId) {
109132
+ return this.db.prepare(`
109133
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
109134
+ WHERE parent_session_id = ? AND json_extract(config_json, '$.launchId') = ?
109135
+ ORDER BY created_at DESC, rowid DESC LIMIT 1
109136
+ `).get(parentSessionId, launchId) ?? null;
109137
+ }
109138
+ /** True when `sessionId` is a spawn child (nested collaboration is unsupported). */
109139
+ isSpawnChild(sessionId) {
109140
+ return Boolean(this.db.prepare(`
109141
+ SELECT 1 FROM session_collaboration_grants
109142
+ WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn' LIMIT 1
109143
+ `).get(sessionId));
109144
+ }
109145
+ spawnGrantForChild(childSessionId) {
109146
+ return this.db.prepare(`
109147
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
109148
+ WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn'
109149
+ `).get(childSessionId) ?? null;
109150
+ }
109151
+ startedSpawnGrants() {
109152
+ return this.db.prepare(`
109153
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
109154
+ WHERE child_session_id IS NOT NULL AND COALESCE(kind, 'spawn') = 'spawn'
109155
+ `).all();
109156
+ }
109157
+ /** Started handoff grants; the session each created is `config_json.handoffSessionId`. */
109158
+ startedHandoffGrants() {
109159
+ return this.db.prepare(`
109160
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
109161
+ WHERE kind = 'handoff' AND started_at IS NOT NULL
109162
+ `).all();
109163
+ }
109164
+ /**
109165
+ * Open mailbox channels of `sessionId`: spawn grants with a bound child and
109166
+ * started link grants, oldest first. Handoff grants are never channels.
109167
+ */
109168
+ channelsFor(sessionId) {
109169
+ return this.db.prepare(`
109170
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
109171
+ WHERE (parent_session_id = ? OR child_session_id = ?)
109172
+ AND child_session_id IS NOT NULL
109173
+ AND (COALESCE(kind, 'spawn') = 'spawn' OR (kind = 'link' AND started_at IS NOT NULL))
109174
+ ORDER BY created_at, rowid
109175
+ `).all(sessionId, sessionId);
109176
+ }
109177
+ /** True when one of the two sessions was created by a handoff from the other. */
109178
+ isHandoffPair(a, b3) {
109179
+ return Boolean(this.db.prepare(`
109180
+ SELECT 1 FROM session_collaboration_grants
109181
+ WHERE kind = 'handoff' AND (
109182
+ (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
109183
+ OR (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
109184
+ ) LIMIT 1
109185
+ `).get(a, b3, b3, a));
109186
+ }
109187
+ findLinkGrant(initiatorSessionId, peerSessionId) {
109188
+ return this.db.prepare(`
109189
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
109190
+ WHERE parent_session_id = ? AND child_session_id = ? AND kind = 'link'
109191
+ `).get(initiatorSessionId, peerSessionId) ?? null;
109192
+ }
109193
+ /**
109194
+ * Persist an approved launch. Link grants bind the peer at once; spawn and
109195
+ * handoff grants bind their session on start. The brief arrives on start.
109196
+ */
109197
+ createGrant(input) {
109198
+ const grantId = randomUUID17();
109199
+ try {
109200
+ this.db.prepare(`
109201
+ INSERT INTO session_collaboration_grants
109202
+ (credential_hash, credential_secret, credential_hint, parent_session_id, child_session_id,
109203
+ agent_id, task, config_json, created_at, kind)
109204
+ VALUES (?, NULL, '', ?, ?, ?, '', ?, ?, ?)
109205
+ `).run(
109206
+ grantId,
109207
+ input.parentSessionId,
109208
+ input.childSessionId ?? null,
109209
+ input.agentId,
109210
+ JSON.stringify(input.config),
109211
+ nowIso(),
109212
+ input.kind
109213
+ );
109214
+ } catch (error51) {
109215
+ const message = error51 instanceof Error ? error51.message : String(error51);
109216
+ if (input.childSessionId && /UNIQUE|unique/i.test(message)) {
109217
+ throw new CollaborationError(
109218
+ `Session ${input.parentSessionId} already has a link to ${input.childSessionId}; request it again to reuse it.`,
109219
+ "failed_precondition"
109220
+ );
109221
+ }
109222
+ throw error51;
109223
+ }
109224
+ return grantId;
109225
+ }
109226
+ updateConfig(grantId, config2) {
109227
+ this.db.prepare("UPDATE session_collaboration_grants SET config_json = ? WHERE credential_hash = ?").run(JSON.stringify(config2), grantId);
109228
+ }
109229
+ /** Record the brief of a grant whose task has not been delivered yet. */
109230
+ setTask(grantId, task) {
109231
+ this.db.prepare("UPDATE session_collaboration_grants SET task = ? WHERE credential_hash = ? AND task_sent = 0").run(task, grantId);
109232
+ }
109233
+ markTaskSent(grantId) {
109234
+ this.db.prepare("UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?").run(grantId);
109235
+ }
109236
+ /** Link start. Returns false when the grant had already been started. */
109237
+ markStarted(grantId) {
109238
+ return this.db.prepare(`
109239
+ UPDATE session_collaboration_grants SET started_at = ? WHERE credential_hash = ? AND started_at IS NULL
109240
+ `).run(nowIso(), grantId).changes === 1;
109241
+ }
109242
+ /**
109243
+ * Record the session a spawn/handoff start created. Throws when a concurrent
109244
+ * start already consumed the grant.
109245
+ *
109246
+ * A handoff session is deliberately *not* written to child_session_id: that
109247
+ * column holds channel endpoints (spawn children, link peers), and a handoff
109248
+ * sibling is neither. The created id lives in config_json instead.
109249
+ */
109250
+ bindStartedSession(grant, sessionId, config2) {
109251
+ const changes = grant.kind === "handoff" ? this.db.prepare(`
109252
+ UPDATE session_collaboration_grants SET config_json = ?, started_at = ?
109253
+ WHERE credential_hash = ? AND started_at IS NULL
109254
+ `).run(JSON.stringify({ ...config2, handoffSessionId: sessionId }), nowIso(), grant.grant_id).changes : this.db.prepare(`
109255
+ UPDATE session_collaboration_grants SET child_session_id = ?, started_at = ?
109256
+ WHERE credential_hash = ? AND child_session_id IS NULL
109257
+ `).run(sessionId, nowIso(), grant.grant_id).changes;
109258
+ if (changes !== 1) throw new CollaborationError("This launch was already started", "failed_precondition");
109259
+ }
109260
+ // --- continuation --------------------------------------------------------
109261
+ /** The session `sessionId` was continued in; null when it was not. */
109262
+ successorOf(sessionId) {
109263
+ const row2 = this.db.prepare("SELECT to_session_id FROM session_continuations WHERE from_session_id = ?").get(sessionId);
109264
+ return row2?.to_session_id ?? null;
109265
+ }
109266
+ /** The session `sessionId` continues; null when it continues none. */
109267
+ predecessorOf(sessionId) {
109268
+ const row2 = this.db.prepare("SELECT from_session_id FROM session_continuations WHERE to_session_id = ?").get(sessionId);
109269
+ return row2?.from_session_id ?? null;
109270
+ }
109271
+ /** The newest session in `sessionId`'s continuation chain (itself when never continued). */
109272
+ currentSessionFor(sessionId) {
109273
+ let current = sessionId;
109274
+ for (let hop = 0; hop < MAX_CONTINUATION_HOPS; hop++) {
109275
+ const next = this.successorOf(current);
109276
+ if (!next) return current;
109277
+ current = next;
109278
+ }
109279
+ return current;
109280
+ }
109281
+ /**
109282
+ * Record that `to` continues `from`, and move `from`'s mailbox channels
109283
+ * (spawn grants with a bound child, started link grants) with their
109284
+ * messages and read cursors to `to`. Handoff and not-yet-started grants
109285
+ * stay: their launch ids live in `from`'s transcript.
109286
+ *
109287
+ * Messages move on both sides so idempotency keys and report history follow
109288
+ * the session, and deleting `from` no longer cascades them away. Returns the
109289
+ * moved grants as they are now.
109290
+ */
109291
+ continueSession(from, to2) {
109292
+ return this.transaction(() => {
109293
+ const successor = this.successorOf(from);
109294
+ if (successor) {
109295
+ throw new CollaborationError(`Session ${from} was already continued in ${successor}`, "failed_precondition");
109296
+ }
109297
+ this.db.prepare("INSERT INTO session_continuations (from_session_id, to_session_id, created_at) VALUES (?, ?, ?)").run(from, to2, nowIso());
109298
+ const grantIds = this.channelsFor(from).map((grant) => grant.grant_id);
109299
+ for (const grantId of grantIds) {
109300
+ for (const column of ["parent_session_id", "child_session_id"]) {
109301
+ this.db.prepare(`UPDATE session_collaboration_grants SET ${column} = ? WHERE credential_hash = ? AND ${column} = ?`).run(to2, grantId, from);
109302
+ }
109303
+ for (const column of ["sender_session_id", "recipient_session_id"]) {
109304
+ this.db.prepare(`UPDATE session_collaboration_messages SET ${column} = ? WHERE credential_hash = ? AND ${column} = ?`).run(to2, grantId, from);
109305
+ }
109306
+ this.db.prepare("UPDATE session_collaboration_cursors SET session_id = ? WHERE credential_hash = ? AND session_id = ?").run(to2, grantId, from);
109307
+ }
109308
+ return grantIds.map((grantId) => this.grantById(grantId));
109309
+ });
109310
+ }
109311
+ // --- mailbox -------------------------------------------------------------
109312
+ nextSequence(grantId) {
109313
+ const row2 = this.db.prepare(`
109314
+ SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
109315
+ FROM session_collaboration_messages WHERE credential_hash = ?
109316
+ `).get(grantId);
109317
+ return row2.next_sequence;
109318
+ }
109319
+ insertMessage(grantId, input) {
109320
+ const row2 = {
109321
+ id: randomUUID17(),
109322
+ sequence: this.nextSequence(grantId),
109323
+ sender_session_id: input.senderSessionId,
109324
+ recipient_session_id: input.recipientSessionId,
109325
+ client_message_id: input.clientMessageId,
109326
+ content: input.content,
109327
+ created_at: nowIso()
109328
+ };
109329
+ this.db.prepare(`
109330
+ INSERT INTO session_collaboration_messages
109331
+ (id, credential_hash, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at)
109332
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
109333
+ `).run(
109334
+ row2.id,
109335
+ grantId,
109336
+ row2.sequence,
109337
+ row2.sender_session_id,
109338
+ row2.recipient_session_id,
109339
+ row2.client_message_id,
109340
+ row2.content,
109341
+ row2.created_at
109342
+ );
109343
+ return row2;
109344
+ }
109345
+ /** Append one message; a repeated `clientMessageId` returns the stored row. */
109346
+ appendMessage(input) {
109347
+ return this.transaction(() => {
109348
+ if (input.clientMessageId) {
109349
+ const existing = this.db.prepare(`
109350
+ SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages
109351
+ WHERE credential_hash = ? AND sender_session_id = ? AND client_message_id = ?
109352
+ `).get(input.grantId, input.senderSessionId, input.clientMessageId);
109353
+ if (existing) return { row: existing, reused: true };
109354
+ }
109355
+ const row2 = this.insertMessage(input.grantId, { ...input, clientMessageId: input.clientMessageId ?? null });
109356
+ return { row: row2, reused: false };
109357
+ });
109358
+ }
109359
+ /** When `sessionId` last sent and last received a mailbox message (ISO), across all its channels. */
109360
+ lastMessageTimes(sessionId) {
109361
+ return this.db.prepare(`
109362
+ SELECT
109363
+ (SELECT MAX(created_at) FROM session_collaboration_messages WHERE sender_session_id = ?) AS sentAt,
109364
+ (SELECT MAX(created_at) FROM session_collaboration_messages WHERE recipient_session_id = ?) AS receivedAt
109365
+ `).get(sessionId, sessionId);
109366
+ }
109367
+ /** Queue a link grant's opening body for the peer exactly once, then mark the task sent. */
109368
+ appendLinkOpening(grant, recipientSessionId, content) {
109369
+ this.transaction(() => {
109370
+ try {
109371
+ this.insertMessage(grant.grant_id, {
109372
+ senderSessionId: grant.parent_session_id,
109373
+ recipientSessionId,
109374
+ clientMessageId: `link-opening:${grant.grant_id}`,
109375
+ content
109376
+ });
109377
+ } catch {
109378
+ }
109379
+ this.markTaskSent(grant.grant_id);
109380
+ });
109381
+ }
109382
+ /**
109383
+ * Up to `limit` unread messages addressed to `sessionId` across grants,
109384
+ * without marking them read: one per grant in turn, grants with the oldest
109385
+ * unread message first, so successive reads reach every grant even when
109386
+ * there are more grants than `limit`. Each batch is a prefix of its grant's
109387
+ * unread messages. Selection reads only the mailbox index; bodies load for
109388
+ * the selected messages alone.
109389
+ */
109390
+ peekMailbox(sessionId, grantIds, limit2) {
109391
+ const unread = this.db.prepare(`
109392
+ SELECT rowid AS rid FROM session_collaboration_messages
109393
+ WHERE credential_hash = ? AND recipient_session_id = ? AND sequence > ?
109394
+ ORDER BY sequence LIMIT ?
109395
+ `);
109396
+ const createdAt = this.db.prepare("SELECT created_at FROM session_collaboration_messages WHERE rowid = ?");
109397
+ const queues2 = grantIds.flatMap((grantId) => {
109398
+ const cursor = this.db.prepare(`
109399
+ SELECT last_sequence FROM session_collaboration_cursors WHERE credential_hash = ? AND session_id = ?
109400
+ `).get(grantId, sessionId);
109401
+ const rids = unread.all(grantId, sessionId, cursor?.last_sequence ?? 0, limit2).map((row2) => row2.rid);
109402
+ if (rids.length === 0) return [];
109403
+ const oldest = createdAt.get(rids[0]).created_at;
109404
+ return [{ grantId, rids, oldest, taken: 0 }];
109405
+ }).sort((a, b3) => a.oldest.localeCompare(b3.oldest));
109406
+ let budget = limit2;
109407
+ while (budget > 0) {
109408
+ let took = false;
109409
+ for (const queue of queues2) {
109410
+ if (budget === 0) break;
109411
+ if (queue.taken === queue.rids.length) continue;
109412
+ queue.taken++;
109413
+ budget--;
109414
+ took = true;
109415
+ }
109416
+ if (!took) break;
109417
+ }
109418
+ const load = this.db.prepare(`SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages WHERE rowid = ?`);
109419
+ return queues2.filter((queue) => queue.taken > 0).map(({ grantId, rids, taken }) => ({
109420
+ grantId,
109421
+ rows: rids.slice(0, taken).map((rid) => load.get(rid))
109422
+ }));
109423
+ }
109424
+ /**
109425
+ * Mark messages read up to each grant's `lastSequence`. A cursor never
109426
+ * moves back, so acknowledgements may arrive in any order.
109427
+ */
109428
+ ackMailbox(sessionId, acks) {
109429
+ this.transaction(() => {
109430
+ for (const { grantId, lastSequence } of acks) {
109431
+ this.db.prepare(`
109432
+ INSERT INTO session_collaboration_cursors (credential_hash, session_id, last_sequence)
109433
+ VALUES (?, ?, ?)
109434
+ ON CONFLICT(credential_hash, session_id) DO UPDATE SET last_sequence = MAX(last_sequence, excluded.last_sequence)
109435
+ `).run(grantId, sessionId, lastSequence);
109436
+ this.db.prepare(`
109437
+ UPDATE session_collaboration_messages SET delivered_at = COALESCE(delivered_at, ?)
109438
+ WHERE credential_hash = ? AND recipient_session_id = ? AND sequence <= ?
109439
+ `).run(nowIso(), grantId, sessionId, lastSequence);
109440
+ }
109441
+ });
109442
+ }
109443
+ };
109444
+
109022
109445
  // ../../packages/shared/src/session-interruption.ts
109023
109446
  function isNodeRestartInterruption(reason) {
109024
109447
  return reason === "node_restart" || reason === "node_restart_non_reattachable";
@@ -109105,6 +109528,26 @@ var CollaborationWakeQueue = class {
109105
109528
  this.start();
109106
109529
  }
109107
109530
  }
109531
+ /** True while a wake for `sessionId` is being prepared or handed to its harness. */
109532
+ hasInFlight(sessionId) {
109533
+ if (this.delivering.has(sessionId)) return true;
109534
+ return Boolean(this.db.prepare(`SELECT 1 FROM collaboration_wakes
109535
+ WHERE session_id = ? AND state IN ('preparing', 'dispatching') LIMIT 1`).get(sessionId));
109536
+ }
109537
+ /**
109538
+ * Hand `from`'s wakes to `to`, the session that continues it. Settled rows
109539
+ * move too, so deduplication by notification id keeps holding for the new
109540
+ * recipient. `to` starts paused: its own first output (see {@link observe})
109541
+ * releases the moved wakes, so none runs ahead of the turn the user starts.
109542
+ * Throws while a wake for `from` is in flight.
109543
+ */
109544
+ migrate(from, to2) {
109545
+ if (this.hasInFlight(from)) throw new Error(`A collaboration wake for ${from} is being delivered`);
109546
+ this.db.prepare("UPDATE collaboration_wakes SET session_id = ? WHERE session_id = ?").run(to2, from);
109547
+ this.db.prepare("DELETE FROM collaboration_wake_recipients WHERE session_id = ?").run(from);
109548
+ this.paused.delete(from);
109549
+ this.pause(to2);
109550
+ }
109108
109551
  pause(sessionId) {
109109
109552
  if (this.paused.has(sessionId)) return;
109110
109553
  this.db.prepare(`INSERT INTO collaboration_wake_recipients (session_id, paused)
@@ -109204,6 +109647,7 @@ var CollaborationWakeQueue = class {
109204
109647
  // ../../packages/runtime/src/db/schema.ts
109205
109648
  var SCHEMA_SQL = `
109206
109649
  ${COLLABORATION_WAKE_TABLE_DDL}
109650
+ ${SESSION_CONTINUATION_TABLE_DDL}
109207
109651
  PRAGMA foreign_keys = ON;
109208
109652
 
109209
109653
  CREATE TABLE IF NOT EXISTS meta (
@@ -111381,7 +111825,7 @@ function dispatchAutomationRpc(method, payload, ctx) {
111381
111825
  // ../../packages/runtime/src/server/rpc-codex.ts
111382
111826
  init_environment();
111383
111827
  init_oauth_loopback();
111384
- import { randomUUID as randomUUID17 } from "node:crypto";
111828
+ import { randomUUID as randomUUID18 } from "node:crypto";
111385
111829
  function invalid9(message) {
111386
111830
  throw Object.assign(new Error(message), { code: "invalid_argument" });
111387
111831
  }
@@ -111408,7 +111852,7 @@ async function startMcpOauth(client3, login) {
111408
111852
  const result3 = login((url2) => opened(url2));
111409
111853
  const first = await Promise.race([authorization, result3]);
111410
111854
  if (typeof first !== "string") return first;
111411
- const loginId = randomUUID17();
111855
+ const loginId = randomUUID18();
111412
111856
  pendingMcpOauth.set(loginId, { clientSessionId: client3.clientSessionId, authorizationUrl: first, result: result3 });
111413
111857
  const forget = () => {
111414
111858
  setTimeout(() => pendingMcpOauth.delete(loginId), MCP_OAUTH_COLLECT_MS).unref?.();
@@ -112353,7 +112797,7 @@ var NodeTerminalManager = class {
112353
112797
  init_session();
112354
112798
 
112355
112799
  // ../../packages/runtime/src/lease/control-lease.ts
112356
- import { randomUUID as randomUUID18 } from "node:crypto";
112800
+ import { randomUUID as randomUUID19 } from "node:crypto";
112357
112801
  var DEFAULT_TTL_MS = 3e4;
112358
112802
  var MAX_TTL_MS = 15 * 60 * 1e3;
112359
112803
  var YIELD_LEVEL = { none: 0, holder: 1, any: 2 };
@@ -112381,7 +112825,7 @@ function clampTtlMs(ttlMs) {
112381
112825
  var ControlLeaseService = class {
112382
112826
  constructor(db) {
112383
112827
  this.db = db;
112384
- this.leaseEpoch = randomUUID18();
112828
+ this.leaseEpoch = randomUUID19();
112385
112829
  }
112386
112830
  db;
112387
112831
  leaseEpoch;
@@ -112468,7 +112912,7 @@ var ControlLeaseService = class {
112468
112912
  code: "failed_precondition"
112469
112913
  });
112470
112914
  }
112471
- const leaseId = randomUUID18();
112915
+ const leaseId = randomUUID19();
112472
112916
  const generation = existing && existing.epoch === this.leaseEpoch ? String(Number(existing.generation || "0") + 1) : "1";
112473
112917
  const expiresAt = now + ttl;
112474
112918
  this.db.prepare(
@@ -112589,7 +113033,7 @@ import { dirname as dirname21, join as join51 } from "node:path";
112589
113033
  // ../../packages/runtime/src/mcp-apps/resource-store.ts
112590
113034
  init_mcp_apps();
112591
113035
  init_mcp_app_resource();
112592
- import { createHash as createHash21, randomUUID as randomUUID19 } from "node:crypto";
113036
+ import { createHash as createHash21, randomUUID as randomUUID20 } from "node:crypto";
112593
113037
  import { existsSync as existsSync50, mkdirSync as mkdirSync25, readFileSync as readFileSync26, readdirSync as readdirSync16, renameSync as renameSync9, rmSync as rmSync11, statSync as statSync17, utimesSync, writeFileSync as writeFileSync20 } from "node:fs";
112594
113038
  import { join as join50 } from "node:path";
112595
113039
  function createMcpAppResourceStore(directory) {
@@ -112605,7 +113049,7 @@ function createMcpAppResourceStore(directory) {
112605
113049
  const hash2 = digest(snapshot.html), file2 = path(hash2);
112606
113050
  if (!existsSync50(file2) || statSync17(file2).size > MCP_APP_HTML_MAX_BYTES || digest(readFileSync26(file2, "utf8")) !== hash2) {
112607
113051
  mkdirSync25(directory, { recursive: true });
112608
- const temporary = join50(directory, `${hash2}.${randomUUID19()}.tmp`);
113052
+ const temporary = join50(directory, `${hash2}.${randomUUID20()}.tmp`);
112609
113053
  try {
112610
113054
  writeFileSync20(temporary, snapshot.html, { encoding: "utf8", mode: 384, flag: "wx" });
112611
113055
  renameSync9(temporary, file2);
@@ -112685,15 +113129,23 @@ init_harness();
112685
113129
  // src/session/collaboration.ts
112686
113130
  init_environment();
112687
113131
 
112688
- // ../../packages/runtime/src/collaboration/errors.ts
112689
- var CollaborationError = class extends Error {
112690
- constructor(message, code) {
112691
- super(message);
112692
- this.code = code;
112693
- this.name = "CollaborationError";
112694
- }
112695
- code;
112696
- };
113132
+ // ../../packages/runtime/src/collaboration/continuation.ts
113133
+ function assertContinuable(store, wakes, from, state) {
113134
+ if (!state) throw new CollaborationError(`Session ${from} no longer exists`, "not_found");
113135
+ const successor = store.successorOf(from);
113136
+ if (successor) throw new CollaborationError(`Session ${from} was already continued in ${successor}`, "failed_precondition");
113137
+ if (state.busy || wakes.hasInFlight(from)) {
113138
+ throw new CollaborationError(`Session ${from} is working; continue it once it is idle`, "failed_precondition");
113139
+ }
113140
+ }
113141
+ function continueCollaboration(store, wakes, from, to2, state) {
113142
+ return store.transaction(() => {
113143
+ assertContinuable(store, wakes, from, state);
113144
+ const moved = store.continueSession(from, to2);
113145
+ wakes.migrate(from, to2);
113146
+ return moved;
113147
+ });
113148
+ }
112697
113149
 
112698
113150
  // ../../packages/runtime/src/collaboration/launch.ts
112699
113151
  init_agent_types();
@@ -112881,7 +113333,7 @@ function formatPeers(peers) {
112881
113333
  function resolveSendChannel(store, callerSessionId, to2, sessionTitle2) {
112882
113334
  const channels = listMailboxChannels(store, callerSessionId, sessionTitle2);
112883
113335
  const peers = peersOf(channels);
112884
- const target = to2?.trim();
113336
+ const target = to2?.trim() ? store.currentSessionFor(to2.trim()) : void 0;
112885
113337
  if (!target) {
112886
113338
  if (peers.length === 0) throw new CollaborationError(NO_PEERS_HINT, "failed_precondition");
112887
113339
  const parent = channels.find((channel) => channel.peer.relation === "parent");
@@ -112915,7 +113367,7 @@ function normalizeMailboxContent(raw) {
112915
113367
  function readCallerMailbox(store, callerSessionId, options, sessionTitle2) {
112916
113368
  const channels = listMailboxChannels(store, callerSessionId, sessionTitle2);
112917
113369
  const peers = peersOf(channels);
112918
- const from = options.from?.map((id) => id.trim()).filter(Boolean) ?? [];
113370
+ const from = options.from?.map((id) => id.trim()).filter(Boolean).map((id) => store.currentSessionFor(id)) ?? [];
112919
113371
  if (from.length > 0) {
112920
113372
  const known = new Set(peers.map((peer) => peer.sessionId));
112921
113373
  const unknown2 = from.filter((id) => !known.has(id));
@@ -113039,293 +113491,6 @@ function prepareLaunchStart(store, callerSessionId, input) {
113039
113491
  return { ...grant, task };
113040
113492
  }
113041
113493
 
113042
- // ../../packages/runtime/src/collaboration/store.ts
113043
- import { randomUUID as randomUUID20 } from "node:crypto";
113044
- var GRANT_COLUMNS = `credential_hash AS grant_id, parent_session_id, child_session_id,
113045
- agent_id, task, config_json, task_sent, COALESCE(kind, 'spawn') AS kind, started_at`;
113046
- var MESSAGE_COLUMNS = `id, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at`;
113047
- function nowIso() {
113048
- return (/* @__PURE__ */ new Date()).toISOString();
113049
- }
113050
- var CollaborationStore = class {
113051
- constructor(db) {
113052
- this.db = db;
113053
- }
113054
- db;
113055
- transaction(fn) {
113056
- return this.db.transaction(fn)();
113057
- }
113058
- // --- grants --------------------------------------------------------------
113059
- grantById(grantId) {
113060
- return this.db.prepare(`SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants WHERE credential_hash = ?`).get(grantId) ?? null;
113061
- }
113062
- /**
113063
- * The grant an approved launch created. launchIds are agent-chosen and may
113064
- * repeat across requests, so the latest approval wins.
113065
- */
113066
- grantForLaunch(parentSessionId, launchId) {
113067
- return this.db.prepare(`
113068
- SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
113069
- WHERE parent_session_id = ? AND json_extract(config_json, '$.launchId') = ?
113070
- ORDER BY created_at DESC, rowid DESC LIMIT 1
113071
- `).get(parentSessionId, launchId) ?? null;
113072
- }
113073
- /** True when `sessionId` is a spawn child (nested collaboration is unsupported). */
113074
- isSpawnChild(sessionId) {
113075
- return Boolean(this.db.prepare(`
113076
- SELECT 1 FROM session_collaboration_grants
113077
- WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn' LIMIT 1
113078
- `).get(sessionId));
113079
- }
113080
- spawnGrantForChild(childSessionId) {
113081
- return this.db.prepare(`
113082
- SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
113083
- WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn'
113084
- `).get(childSessionId) ?? null;
113085
- }
113086
- startedSpawnGrants() {
113087
- return this.db.prepare(`
113088
- SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
113089
- WHERE child_session_id IS NOT NULL AND COALESCE(kind, 'spawn') = 'spawn'
113090
- `).all();
113091
- }
113092
- /** Started handoff grants; the session each created is `config_json.handoffSessionId`. */
113093
- startedHandoffGrants() {
113094
- return this.db.prepare(`
113095
- SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
113096
- WHERE kind = 'handoff' AND started_at IS NOT NULL
113097
- `).all();
113098
- }
113099
- /**
113100
- * Open mailbox channels of `sessionId`: spawn grants with a bound child and
113101
- * started link grants, oldest first. Handoff grants are never channels.
113102
- */
113103
- channelsFor(sessionId) {
113104
- return this.db.prepare(`
113105
- SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
113106
- WHERE (parent_session_id = ? OR child_session_id = ?)
113107
- AND child_session_id IS NOT NULL
113108
- AND (COALESCE(kind, 'spawn') = 'spawn' OR (kind = 'link' AND started_at IS NOT NULL))
113109
- ORDER BY created_at, rowid
113110
- `).all(sessionId, sessionId);
113111
- }
113112
- /** True when one of the two sessions was created by a handoff from the other. */
113113
- isHandoffPair(a, b3) {
113114
- return Boolean(this.db.prepare(`
113115
- SELECT 1 FROM session_collaboration_grants
113116
- WHERE kind = 'handoff' AND (
113117
- (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
113118
- OR (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
113119
- ) LIMIT 1
113120
- `).get(a, b3, b3, a));
113121
- }
113122
- findLinkGrant(initiatorSessionId, peerSessionId) {
113123
- return this.db.prepare(`
113124
- SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
113125
- WHERE parent_session_id = ? AND child_session_id = ? AND kind = 'link'
113126
- `).get(initiatorSessionId, peerSessionId) ?? null;
113127
- }
113128
- /**
113129
- * Persist an approved launch. Link grants bind the peer at once; spawn and
113130
- * handoff grants bind their session on start. The brief arrives on start.
113131
- */
113132
- createGrant(input) {
113133
- const grantId = randomUUID20();
113134
- try {
113135
- this.db.prepare(`
113136
- INSERT INTO session_collaboration_grants
113137
- (credential_hash, credential_secret, credential_hint, parent_session_id, child_session_id,
113138
- agent_id, task, config_json, created_at, kind)
113139
- VALUES (?, NULL, '', ?, ?, ?, '', ?, ?, ?)
113140
- `).run(
113141
- grantId,
113142
- input.parentSessionId,
113143
- input.childSessionId ?? null,
113144
- input.agentId,
113145
- JSON.stringify(input.config),
113146
- nowIso(),
113147
- input.kind
113148
- );
113149
- } catch (error51) {
113150
- const message = error51 instanceof Error ? error51.message : String(error51);
113151
- if (input.childSessionId && /UNIQUE|unique/i.test(message)) {
113152
- throw new CollaborationError(
113153
- `Session ${input.parentSessionId} already has a link to ${input.childSessionId}; request it again to reuse it.`,
113154
- "failed_precondition"
113155
- );
113156
- }
113157
- throw error51;
113158
- }
113159
- return grantId;
113160
- }
113161
- updateConfig(grantId, config2) {
113162
- this.db.prepare("UPDATE session_collaboration_grants SET config_json = ? WHERE credential_hash = ?").run(JSON.stringify(config2), grantId);
113163
- }
113164
- /** Record the brief of a grant whose task has not been delivered yet. */
113165
- setTask(grantId, task) {
113166
- this.db.prepare("UPDATE session_collaboration_grants SET task = ? WHERE credential_hash = ? AND task_sent = 0").run(task, grantId);
113167
- }
113168
- markTaskSent(grantId) {
113169
- this.db.prepare("UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?").run(grantId);
113170
- }
113171
- /** Link start. Returns false when the grant had already been started. */
113172
- markStarted(grantId) {
113173
- return this.db.prepare(`
113174
- UPDATE session_collaboration_grants SET started_at = ? WHERE credential_hash = ? AND started_at IS NULL
113175
- `).run(nowIso(), grantId).changes === 1;
113176
- }
113177
- /**
113178
- * Record the session a spawn/handoff start created. Throws when a concurrent
113179
- * start already consumed the grant.
113180
- *
113181
- * A handoff session is deliberately *not* written to child_session_id: that
113182
- * column holds channel endpoints (spawn children, link peers), and a handoff
113183
- * sibling is neither. The created id lives in config_json instead.
113184
- */
113185
- bindStartedSession(grant, sessionId, config2) {
113186
- const changes = grant.kind === "handoff" ? this.db.prepare(`
113187
- UPDATE session_collaboration_grants SET config_json = ?, started_at = ?
113188
- WHERE credential_hash = ? AND started_at IS NULL
113189
- `).run(JSON.stringify({ ...config2, handoffSessionId: sessionId }), nowIso(), grant.grant_id).changes : this.db.prepare(`
113190
- UPDATE session_collaboration_grants SET child_session_id = ?, started_at = ?
113191
- WHERE credential_hash = ? AND child_session_id IS NULL
113192
- `).run(sessionId, nowIso(), grant.grant_id).changes;
113193
- if (changes !== 1) throw new CollaborationError("This launch was already started", "failed_precondition");
113194
- }
113195
- // --- mailbox -------------------------------------------------------------
113196
- nextSequence(grantId) {
113197
- const row2 = this.db.prepare(`
113198
- SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
113199
- FROM session_collaboration_messages WHERE credential_hash = ?
113200
- `).get(grantId);
113201
- return row2.next_sequence;
113202
- }
113203
- insertMessage(grantId, input) {
113204
- const row2 = {
113205
- id: randomUUID20(),
113206
- sequence: this.nextSequence(grantId),
113207
- sender_session_id: input.senderSessionId,
113208
- recipient_session_id: input.recipientSessionId,
113209
- client_message_id: input.clientMessageId,
113210
- content: input.content,
113211
- created_at: nowIso()
113212
- };
113213
- this.db.prepare(`
113214
- INSERT INTO session_collaboration_messages
113215
- (id, credential_hash, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at)
113216
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
113217
- `).run(
113218
- row2.id,
113219
- grantId,
113220
- row2.sequence,
113221
- row2.sender_session_id,
113222
- row2.recipient_session_id,
113223
- row2.client_message_id,
113224
- row2.content,
113225
- row2.created_at
113226
- );
113227
- return row2;
113228
- }
113229
- /** Append one message; a repeated `clientMessageId` returns the stored row. */
113230
- appendMessage(input) {
113231
- return this.transaction(() => {
113232
- if (input.clientMessageId) {
113233
- const existing = this.db.prepare(`
113234
- SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages
113235
- WHERE credential_hash = ? AND sender_session_id = ? AND client_message_id = ?
113236
- `).get(input.grantId, input.senderSessionId, input.clientMessageId);
113237
- if (existing) return { row: existing, reused: true };
113238
- }
113239
- const row2 = this.insertMessage(input.grantId, { ...input, clientMessageId: input.clientMessageId ?? null });
113240
- return { row: row2, reused: false };
113241
- });
113242
- }
113243
- /** When `sessionId` last sent and last received a mailbox message (ISO), across all its channels. */
113244
- lastMessageTimes(sessionId) {
113245
- return this.db.prepare(`
113246
- SELECT
113247
- (SELECT MAX(created_at) FROM session_collaboration_messages WHERE sender_session_id = ?) AS sentAt,
113248
- (SELECT MAX(created_at) FROM session_collaboration_messages WHERE recipient_session_id = ?) AS receivedAt
113249
- `).get(sessionId, sessionId);
113250
- }
113251
- /** Queue a link grant's opening body for the peer exactly once, then mark the task sent. */
113252
- appendLinkOpening(grant, recipientSessionId, content) {
113253
- this.transaction(() => {
113254
- try {
113255
- this.insertMessage(grant.grant_id, {
113256
- senderSessionId: grant.parent_session_id,
113257
- recipientSessionId,
113258
- clientMessageId: `link-opening:${grant.grant_id}`,
113259
- content
113260
- });
113261
- } catch {
113262
- }
113263
- this.markTaskSent(grant.grant_id);
113264
- });
113265
- }
113266
- /**
113267
- * Up to `limit` unread messages addressed to `sessionId` across grants,
113268
- * without marking them read: one per grant in turn, grants with the oldest
113269
- * unread message first, so successive reads reach every grant even when
113270
- * there are more grants than `limit`. Each batch is a prefix of its grant's
113271
- * unread messages. Selection reads only the mailbox index; bodies load for
113272
- * the selected messages alone.
113273
- */
113274
- peekMailbox(sessionId, grantIds, limit2) {
113275
- const unread = this.db.prepare(`
113276
- SELECT rowid AS rid FROM session_collaboration_messages
113277
- WHERE credential_hash = ? AND recipient_session_id = ? AND sequence > ?
113278
- ORDER BY sequence LIMIT ?
113279
- `);
113280
- const createdAt = this.db.prepare("SELECT created_at FROM session_collaboration_messages WHERE rowid = ?");
113281
- const queues2 = grantIds.flatMap((grantId) => {
113282
- const cursor = this.db.prepare(`
113283
- SELECT last_sequence FROM session_collaboration_cursors WHERE credential_hash = ? AND session_id = ?
113284
- `).get(grantId, sessionId);
113285
- const rids = unread.all(grantId, sessionId, cursor?.last_sequence ?? 0, limit2).map((row2) => row2.rid);
113286
- if (rids.length === 0) return [];
113287
- const oldest = createdAt.get(rids[0]).created_at;
113288
- return [{ grantId, rids, oldest, taken: 0 }];
113289
- }).sort((a, b3) => a.oldest.localeCompare(b3.oldest));
113290
- let budget = limit2;
113291
- while (budget > 0) {
113292
- let took = false;
113293
- for (const queue of queues2) {
113294
- if (budget === 0) break;
113295
- if (queue.taken === queue.rids.length) continue;
113296
- queue.taken++;
113297
- budget--;
113298
- took = true;
113299
- }
113300
- if (!took) break;
113301
- }
113302
- const load = this.db.prepare(`SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages WHERE rowid = ?`);
113303
- return queues2.filter((queue) => queue.taken > 0).map(({ grantId, rids, taken }) => ({
113304
- grantId,
113305
- rows: rids.slice(0, taken).map((rid) => load.get(rid))
113306
- }));
113307
- }
113308
- /**
113309
- * Mark messages read up to each grant's `lastSequence`. A cursor never
113310
- * moves back, so acknowledgements may arrive in any order.
113311
- */
113312
- ackMailbox(sessionId, acks) {
113313
- this.transaction(() => {
113314
- for (const { grantId, lastSequence } of acks) {
113315
- this.db.prepare(`
113316
- INSERT INTO session_collaboration_cursors (credential_hash, session_id, last_sequence)
113317
- VALUES (?, ?, ?)
113318
- ON CONFLICT(credential_hash, session_id) DO UPDATE SET last_sequence = MAX(last_sequence, excluded.last_sequence)
113319
- `).run(grantId, sessionId, lastSequence);
113320
- this.db.prepare(`
113321
- UPDATE session_collaboration_messages SET delivered_at = COALESCE(delivered_at, ?)
113322
- WHERE credential_hash = ? AND recipient_session_id = ? AND sequence <= ?
113323
- `).run(nowIso(), grantId, sessionId, lastSequence);
113324
- }
113325
- });
113326
- }
113327
- };
113328
-
113329
113494
  // ../../packages/runtime/src/collaboration/index.ts
113330
113495
  init_text();
113331
113496
 
@@ -113393,6 +113558,7 @@ function retrieveCollaborationMessages(ctx, input) {
113393
113558
  init_agent_profile();
113394
113559
  init_environment();
113395
113560
  init_harness_capabilities();
113561
+ init_session_provider_definitions();
113396
113562
 
113397
113563
  // ../../packages/shared/src/acp-brand.ts
113398
113564
  init_acp_brand();
@@ -113468,7 +113634,7 @@ function listCollaborationProfiles(deps) {
113468
113634
  pushProfile(
113469
113635
  p3.id,
113470
113636
  p3.harnessId,
113471
- p3.name,
113637
+ sessionProviderDisplayName(p3),
113472
113638
  p3.isBase ? `${p3.harnessId} harness with the built-in configuration` : `Custom ${p3.harnessId} profile`,
113473
113639
  p3.config
113474
113640
  );
@@ -113912,6 +114078,31 @@ var CollaborationService = class {
113912
114078
  retrieve(input) {
113913
114079
  return retrieveCollaborationMessages(this.ctx, input);
113914
114080
  }
114081
+ /** The session `sessionId` was continued in; null when it was not. */
114082
+ successorOf(sessionId) {
114083
+ return this.ctx.store.successorOf(sessionId);
114084
+ }
114085
+ /** Throws when `from` cannot be continued in a new session now. */
114086
+ assertContinuable(from) {
114087
+ assertContinuable(this.ctx.store, this.ctx.wakes, from, this.continuedState(from));
114088
+ }
114089
+ /**
114090
+ * `to` was just created to continue `from`: it takes over `from`'s channels
114091
+ * and wakes, and the child prompt moves with a spawn grant.
114092
+ */
114093
+ continueSession(from, to2) {
114094
+ continueCollaboration(this.ctx.store, this.ctx.wakes, from, to2, this.continuedState(from));
114095
+ this.deps.sessions.moveExternalParent(from, to2);
114096
+ const grant = this.ctx.store.spawnGrantForChild(to2);
114097
+ if (grant) {
114098
+ this.deps.sessions.setSystemPromptAppend(to2, launchedSessionPromptAppend(this.deps, grant));
114099
+ this.deps.sessions.setSystemPromptAppend(from, null);
114100
+ }
114101
+ }
114102
+ continuedState(sessionId) {
114103
+ const session = this.deps.sessions.get(sessionId);
114104
+ return session ? { busy: session.status === "streaming" || session.pendingInteraction != null } : null;
114105
+ }
113915
114106
  /** Reconstruct the system-prompt append of launched sessions after restart (never link). */
113916
114107
  rehydrateSystemPrompts() {
113917
114108
  const launched = [