@super-one/cli 0.68.0-alpha.2 → 0.69.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 +1314 -1227
  3. package/package.json +1 -1
package/lib/cli.mjs CHANGED
@@ -359,7 +359,7 @@ var init_interaction_memory = __esm({
359
359
  });
360
360
 
361
361
  // ../../packages/shared/src/superone-tool-descriptions.ts
362
- var MANUAL_READ_DESCRIPTION, SETUP_MINI_APP_DEV_DESCRIPTION, REGISTER_DEV_MINIAPP_DESCRIPTION, PACK_MINI_APP_DESCRIPTION, UPDATE_SUPERONE_TYPES_DESCRIPTION, RENAME_SESSION_DESCRIPTION, SESSION_TAG_DESCRIPTION, SESSION_TAG_LIST_DESCRIPTION, PROJECT_LIST_DESCRIPTION, SESSION_LIST_DESCRIPTION, SESSION_SEARCH_DESCRIPTION, SESSION_READ_DESCRIPTION, SESSION_TAGS_FILTER_DESCRIPTION, SESSION_TAG_MATCH_DESCRIPTION, SESSION_CLEANUP_DESCRIPTION, CONFIG_READ_DESCRIPTION, CONFIG_APPLY_DESCRIPTION, LIST_MEDIA_PROVIDERS_DESCRIPTION, GENERATE_IMAGE_DESCRIPTION, GENERATE_VIDEO_DESCRIPTION, VIDEO_STATUS_DESCRIPTION, SESSION_LIST_AGENTS_DESCRIPTION, SESSION_REQUEST_AGENTS_DESCRIPTION, LAUNCH_SUMMARY_DESCRIPTION, LAUNCH_TASK_DESCRIPTION, LAUNCH_MODE_DESCRIPTION, LAUNCH_SESSION_ID_DESCRIPTION, LAUNCH_PERMISSION_MODE_DESCRIPTION, LAUNCH_CWD_DESCRIPTION, LAUNCH_WORKTREE_DESCRIPTION, LAUNCH_BRANCH_NAME_DESCRIPTION, SESSION_START_DESCRIPTION, SESSION_SEND_DESCRIPTION, SESSION_RETRIEVE_DESCRIPTION, AUTOMATION_LIST_DESCRIPTION, AUTOMATION_APPLY_DESCRIPTION, AUTOMATION_DELETE_DESCRIPTION, TERMINAL_TABS_DESCRIPTION, TERMINAL_SNAPSHOT_DESCRIPTION, TERMINAL_ACT_DESCRIPTION, TERMINAL_WAIT_FOR_DESCRIPTION;
362
+ var MANUAL_READ_DESCRIPTION, SETUP_MINI_APP_DEV_DESCRIPTION, REGISTER_DEV_MINIAPP_DESCRIPTION, PACK_MINI_APP_DESCRIPTION, UPDATE_SUPERONE_TYPES_DESCRIPTION, RENAME_SESSION_DESCRIPTION, SESSION_TAG_DESCRIPTION, SESSION_TAG_LIST_DESCRIPTION, PROJECT_LIST_DESCRIPTION, SESSION_LIST_DESCRIPTION, SESSION_SEARCH_DESCRIPTION, SESSION_READ_DESCRIPTION, SESSION_TAGS_FILTER_DESCRIPTION, SESSION_TAG_MATCH_DESCRIPTION, SESSION_CLEANUP_DESCRIPTION, CONFIG_READ_DESCRIPTION, CONFIG_APPLY_DESCRIPTION, LIST_MEDIA_PROVIDERS_DESCRIPTION, GENERATE_IMAGE_DESCRIPTION, GENERATE_VIDEO_DESCRIPTION, VIDEO_STATUS_DESCRIPTION, SESSION_LIST_AGENTS_DESCRIPTION, SESSION_REQUEST_AGENTS_DESCRIPTION, LAUNCH_SUMMARY_DESCRIPTION, START_LAUNCH_ID_DESCRIPTION, LAUNCH_TASK_DESCRIPTION, LAUNCH_MODE_DESCRIPTION, LAUNCH_SESSION_ID_DESCRIPTION, LAUNCH_PERMISSION_MODE_DESCRIPTION, LAUNCH_CWD_DESCRIPTION, LAUNCH_WORKTREE_DESCRIPTION, LAUNCH_BRANCH_NAME_DESCRIPTION, SESSION_START_DESCRIPTION, SESSION_SEND_DESCRIPTION, SESSION_SEND_TO_DESCRIPTION, SESSION_SEND_CONTENT_DESCRIPTION, SESSION_RETRIEVE_DESCRIPTION, SESSION_RETRIEVE_FROM_DESCRIPTION, AUTOMATION_LIST_DESCRIPTION, AUTOMATION_APPLY_DESCRIPTION, AUTOMATION_DELETE_DESCRIPTION, TERMINAL_TABS_DESCRIPTION, TERMINAL_SNAPSHOT_DESCRIPTION, TERMINAL_ACT_DESCRIPTION, TERMINAL_WAIT_FOR_DESCRIPTION;
363
363
  var init_superone_tool_descriptions = __esm({
364
364
  "../../packages/shared/src/superone-tool-descriptions.ts"() {
365
365
  "use strict";
@@ -385,18 +385,22 @@ var init_superone_tool_descriptions = __esm({
385
385
  GENERATE_VIDEO_DESCRIPTION = "Submit a video generation; the tool opens a parameter review for user approval. Stop on cancelled or error; use returned feedback before retrying. Poll media_video_status about every 30s until generated or error. Results display automatically. Use media_list_providers for configured providers and the matching media provider-task manual for unfamiliar options.";
386
386
  VIDEO_STATUS_DESCRIPTION = "Advance a submitted video job and read its status: running, generated with savedPaths, or error with message. Poll about every 30 seconds while running: these calls download and save the completed video. Report it ready only after generated; stop on error.";
387
387
  SESSION_LIST_AGENTS_DESCRIPTION = "List the agent profiles available for user-approved child sessions. Only launchable agents are returned. Inspect each profile's harness and defaultConfig before session_collab_request. You may reuse one agentId for multiple launches. Skip this call when the user already named an agent with @ \u2014 that mention carries its agentId.";
388
- SESSION_REQUEST_AGENTS_DESCRIPTION = 'Request user approval for collaboration launches. See the mode field for spawn vs handoff vs link. Spawn/handoff: pick an agentId from session_collab_list_agents; require name, role, summary, task. Link: require sessionId + summary. Read read_manual({ domain: "product", topic: "collaboration" }) before the first launch in a session. User must approve; returns the credential for session_collab_start.';
389
- LAUNCH_SUMMARY_DESCRIPTION = "Short 2\u20133 sentence task summary shown collapsed in the confirm dialog. Not the full brief \u2014 put detail in task.";
390
- LAUNCH_TASK_DESCRIPTION = "Full Markdown brief. Spawn/handoff: delivered to the new session on session_collab_start. A handoff receiver cannot ask you anything back, so make the brief self-contained. Link: optional opening for the peer (mailbox + turn wake, never system prompt). Expandable in the confirm UI.";
388
+ SESSION_REQUEST_AGENTS_DESCRIPTION = 'Request user approval for collaboration launches. See the mode field for spawn vs handoff vs link. Spawn/handoff: pick an agentId from session_collab_list_agents; require name, role, summary. Link: require sessionId + summary. Read read_manual({ domain: "product", topic: "collaboration" }) before the first launch in a session. User must approve; returns a launchId per launch. Pass the task to session_collab_start, not here.';
389
+ LAUNCH_SUMMARY_DESCRIPTION = "Short 2\u20133 sentence summary the user approves in the confirm dialog. The full brief goes to session_collab_start.";
390
+ START_LAUNCH_ID_DESCRIPTION = "A launchId returned by session_collab_request in this session.";
391
+ LAUNCH_TASK_DESCRIPTION = "Full Markdown brief. Required for spawn/handoff, delivered to the new session. A handoff receiver cannot ask you anything back, so make the brief self-contained. Link: optional opening for the peer (mailbox + turn wake, never system prompt).";
391
392
  LAUNCH_MODE_DESCRIPTION = '"spawn" (default) = nested child with a two-way mailbox. "handoff" = top-level sibling, not nested: it owns the task from then on, with no mailbox and no reply \u2014 pass work forward rather than supervise it. "link" = connect to an existing session (sessionId required).';
392
393
  LAUNCH_SESSION_ID_DESCRIPTION = 'Existing SuperOne session id to link with (mode "link" only). Required for link; ignore for spawn. Prefer ids from @session mentions or session_list \u2014 never invent ids.';
393
394
  LAUNCH_PERMISSION_MODE_DESCRIPTION = 'How autonomous the child session is. Nobody watches a child, so prefer the most autonomous mode it can finish under; "plan"/"default" only when stopping for human review is the point. Per-harness mode names, and why requesting autonomy is safe here: See read_manual({ domain: "product", topic: "collaboration" }).';
394
395
  LAUNCH_CWD_DESCRIPTION = 'Only for a genuinely different project root; omit for the current project. Never a same-repo worktree leaf \u2014 express isolation with config.worktree. See read_manual({ domain: "product", topic: "collaboration" }).';
395
396
  LAUNCH_WORKTREE_DESCRIPTION = 'Host-managed worktree for same-repo isolation; leave cwd unset. For parallel implementers, not for read-only review of the shared checkout. See read_manual({ domain: "product", topic: "collaboration" }).';
396
397
  LAUNCH_BRANCH_NAME_DESCRIPTION = 'With mode "branch", create this unique branch. Git cannot check out one branch in two worktrees.';
397
- SESSION_START_DESCRIPTION = "Activate one approved collaboration credential. Spawn: create the child and deliver its task. Handoff: create the sibling session and deliver the task; the credential is spent, no mailbox follows. Link: bind the existing peer and wake it via turn injection (not system prompt). Returns when the peer begins or is notified. Retries are idempotent. Start all credentials back-to-back.";
398
- SESSION_SEND_DESCRIPTION = "Send a persistent Markdown message through one collaboration mailbox (spawn parent-child or link peers). Use clientMessageId for retry-safe delivery. The host wakes the peer and later wakes you when it replies. After sending, continue other work or end your turn. Never sleep, resend, or poll session_collab_retrieve while waiting.";
399
- SESSION_RETRIEVE_DESCRIPTION = 'Retrieve queued Markdown messages for this session from one or more collaboration mailboxes. Call after a collaboration wake, or once before acting on peer input. This is a non-blocking read: status "empty" is not a retry signal. Do not sleep or poll; end your turn and wait for the next wake.';
398
+ SESSION_START_DESCRIPTION = "Start one approved launch by launchId and give it its task. Spawn: create the child and deliver the task. Handoff: create the sibling session and deliver the task; no mailbox follows. Link: bind the existing peer and wake it via turn injection (not system prompt). Returns the peer sessionId once it begins or is notified; message it with session_collab_send. Retries are idempotent and keep the first task. Start all approved launches back-to-back.";
399
+ SESSION_SEND_DESCRIPTION = "Send a persistent Markdown message to a collaboration peer: your spawn parent or child, or a linked session. The host checks that you may message that session. Use clientMessageId for retry-safe delivery. The host wakes the peer and later wakes you when it replies. After sending, continue other work or end your turn. Never sleep, resend, or poll session_collab_retrieve while waiting.";
400
+ SESSION_SEND_TO_DESCRIPTION = "Peer session id. Omit to message your spawn parent, or your only peer. session_collab_retrieve lists your peers.";
401
+ SESSION_SEND_CONTENT_DESCRIPTION = "Mailbox message body in Markdown. Prefer structured Markdown (headings, lists, code fences) for agent-to-agent handoffs; the SuperOne UI renders it as a Markdown preview.";
402
+ SESSION_RETRIEVE_DESCRIPTION = 'Read queued Markdown messages from your collaboration peers and list those peers (session id, name, relation). Call after a collaboration wake, before acting on peer input, or to find out who you can message. This is a non-blocking read: status "empty" is not a retry signal. Do not sleep or poll; end your turn and wait for the next wake.';
403
+ SESSION_RETRIEVE_FROM_DESCRIPTION = "Only read messages from these peer session ids. Omit to read from every peer.";
400
404
  AUTOMATION_LIST_DESCRIPTION = "List project automations with schedule and last/next run. Pass id for full prompt, agentConfig, and schedule; filter by query or enabled. Find unknown ids here before automation_apply or automation_delete; check for duplicates before creation when needed.";
401
405
  AUTOMATION_APPLY_DESCRIPTION = 'Create or update a project automation. create needs name, prompt, schedule; update needs id plus any field (pause via enabled=false). Call automation_list first for ids; remove with automation_delete. Always opens a user confirmation dialog and applies nothing without approval. For schedule and agentConfig shapes see read_manual({ domain: "product", topic: "automation" }).';
402
406
  AUTOMATION_DELETE_DESCRIPTION = "Permanently delete project automations by id (from automation_list). Always opens a user confirmation dialog. Current project only. Prefer automation_list to choose ids first.";
@@ -463,10 +467,6 @@ var init_host_action_collaboration_descriptors = __esm({
463
467
  "minLength": 1,
464
468
  "description": LAUNCH_SUMMARY_DESCRIPTION
465
469
  },
466
- "task": {
467
- "type": "string",
468
- "description": LAUNCH_TASK_DESCRIPTION
469
- },
470
470
  "name": {
471
471
  "type": "string",
472
472
  "minLength": 1,
@@ -583,12 +583,18 @@ var init_host_action_collaboration_descriptors = __esm({
583
583
  "inputSchema": {
584
584
  "type": "object",
585
585
  "properties": {
586
- "credential": {
587
- "type": "string"
586
+ "launchId": {
587
+ "type": "string",
588
+ "minLength": 1,
589
+ "description": START_LAUNCH_ID_DESCRIPTION
590
+ },
591
+ "task": {
592
+ "type": "string",
593
+ "description": LAUNCH_TASK_DESCRIPTION
588
594
  }
589
595
  },
590
596
  "required": [
591
- "credential"
597
+ "launchId"
592
598
  ],
593
599
  "additionalProperties": false
594
600
  }
@@ -599,20 +605,20 @@ var init_host_action_collaboration_descriptors = __esm({
599
605
  "inputSchema": {
600
606
  "type": "object",
601
607
  "properties": {
602
- "credential": {
603
- "type": "string"
608
+ "to": {
609
+ "type": "string",
610
+ "description": SESSION_SEND_TO_DESCRIPTION
604
611
  },
605
612
  "content": {
606
613
  "type": "string",
607
614
  "maxLength": 1e5,
608
- "description": "Mailbox message body in Markdown. Prefer structured Markdown (headings, lists, code fences) for agent-to-agent handoffs; the SuperOne UI renders it as a Markdown preview."
615
+ "description": SESSION_SEND_CONTENT_DESCRIPTION
609
616
  },
610
617
  "clientMessageId": {
611
618
  "type": "string"
612
619
  }
613
620
  },
614
621
  "required": [
615
- "credential",
616
622
  "content"
617
623
  ],
618
624
  "additionalProperties": false
@@ -624,18 +630,15 @@ var init_host_action_collaboration_descriptors = __esm({
624
630
  "inputSchema": {
625
631
  "type": "object",
626
632
  "properties": {
627
- "credentials": {
633
+ "from": {
628
634
  "type": "array",
629
- "minItems": 1,
630
635
  "maxItems": 32,
631
636
  "items": {
632
637
  "type": "string"
633
- }
638
+ },
639
+ "description": SESSION_RETRIEVE_FROM_DESCRIPTION
634
640
  }
635
641
  },
636
- "required": [
637
- "credentials"
638
- ],
639
642
  "additionalProperties": false
640
643
  }
641
644
  }
@@ -3097,11 +3100,12 @@ var init_host_action_computer_descriptors = __esm({
3097
3100
  });
3098
3101
 
3099
3102
  // ../../packages/shared/src/generative-ui/widget-tool-descriptions.ts
3100
- var WIDGET_SHOW_DESCRIPTION;
3103
+ var WIDGET_SHOW_DESCRIPTION, WIDGET_LAYOUT_DESCRIPTION;
3101
3104
  var init_widget_tool_descriptions = __esm({
3102
3105
  "../../packages/shared/src/generative-ui/widget-tool-descriptions.ts"() {
3103
3106
  "use strict";
3104
3107
  WIDGET_SHOW_DESCRIPTION = `Render SVG, diagrams, charts, interactive HTML, or a SuperOne native surface (media gallery, files previewer) inline in chat. Pass widget_code for new content, or template + data to reuse a saved template. To show media you produced yourself, pass a @native/* template so it renders in SuperOne's own gallery (viewer, download, drag-out) instead of a lookalike you build in widget_code \u2014 call widget_list_templates for the list. To hand the user several files to look at one after another (screenshots, reports, changed sources), pass @native/files-previewer with data.files. Before the first new widget in a session, load the relevant design modules with read_manual({ domain: "widget", modules: [...] }).`;
3108
+ WIDGET_LAYOUT_DESCRIPTION = 'How an HTML widget adapts to a narrow screen such as a phone. "fluid" (default) reflows to the available width. "fixed" lays the widget out at 680px and scales it down as a whole, keeping its proportions \u2014 use it for UI mockups, where the layout itself is what the user is reviewing.';
3105
3109
  }
3106
3110
  });
3107
3111
 
@@ -3131,6 +3135,11 @@ var init_host_action_widget_descriptors = __esm({
3131
3135
  "type": "string",
3132
3136
  "description": "Short snake_case identifier for this widget."
3133
3137
  },
3138
+ "layout": {
3139
+ "type": "string",
3140
+ "enum": ["fluid", "fixed"],
3141
+ "description": WIDGET_LAYOUT_DESCRIPTION
3142
+ },
3134
3143
  "widget_code": {
3135
3144
  "type": "string"
3136
3145
  },
@@ -7411,7 +7420,7 @@ var require_websocket = __commonJS({
7411
7420
  var http = __require("http");
7412
7421
  var net2 = __require("net");
7413
7422
  var tls = __require("tls");
7414
- var { randomBytes: randomBytes7, createHash: createHash13 } = __require("crypto");
7423
+ var { randomBytes: randomBytes6, createHash: createHash12 } = __require("crypto");
7415
7424
  var { Duplex, Readable: Readable4 } = __require("stream");
7416
7425
  var { URL: URL2 } = __require("url");
7417
7426
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -7949,7 +7958,7 @@ var require_websocket = __commonJS({
7949
7958
  }
7950
7959
  }
7951
7960
  const defaultPort = isSecure ? 443 : 80;
7952
- const key = randomBytes7(16).toString("base64");
7961
+ const key = randomBytes6(16).toString("base64");
7953
7962
  const request = isSecure ? https.request : http.request;
7954
7963
  const protocolSet = /* @__PURE__ */ new Set();
7955
7964
  let perMessageDeflate;
@@ -8079,7 +8088,7 @@ var require_websocket = __commonJS({
8079
8088
  abortHandshake(websocket, socket, "Invalid Upgrade header");
8080
8089
  return;
8081
8090
  }
8082
- const digest = createHash13("sha1").update(key + GUID).digest("base64");
8091
+ const digest = createHash12("sha1").update(key + GUID).digest("base64");
8083
8092
  if (res.headers["sec-websocket-accept"] !== digest) {
8084
8093
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
8085
8094
  return;
@@ -8448,7 +8457,7 @@ var require_websocket_server = __commonJS({
8448
8457
  var EventEmitter = __require("events");
8449
8458
  var http = __require("http");
8450
8459
  var { Duplex } = __require("stream");
8451
- var { createHash: createHash13 } = __require("crypto");
8460
+ var { createHash: createHash12 } = __require("crypto");
8452
8461
  var extension2 = require_extension();
8453
8462
  var PerMessageDeflate2 = require_permessage_deflate();
8454
8463
  var subprotocol2 = require_subprotocol();
@@ -8755,7 +8764,7 @@ var require_websocket_server = __commonJS({
8755
8764
  );
8756
8765
  }
8757
8766
  if (this._state > RUNNING) return abortHandshake(socket, 503);
8758
- const digest = createHash13("sha1").update(key + GUID).digest("base64");
8767
+ const digest = createHash12("sha1").update(key + GUID).digest("base64");
8759
8768
  const headers = [
8760
8769
  "HTTP/1.1 101 Switching Protocols",
8761
8770
  "Upgrade: websocket",
@@ -11799,8 +11808,9 @@ var init_harness_capabilities = __esm({
11799
11808
  },
11800
11809
  dsh: {
11801
11810
  // In-process dsh Cordis tree (docs/draft/deepseek-harness-integration.md).
11802
- // Flags flip only when the corresponding event path is wired: streaming
11803
- // tool input → tool-call-delta mapping.
11811
+ // Flags flip only when the corresponding event path is wired.
11812
+ // `supportsStreamingToolInput`: live `tool-call-delta` frames open the row
11813
+ // and stream its arguments before the durable `tool/call` completes it.
11804
11814
  // `supportsCompact`: `compaction-basic` compacts automatically at context
11805
11815
  // pressure and on provider overflow; `/compact` drives `compactNow()`.
11806
11816
  // `supportsMcp` covers both SuperOne's own tools (native dsh rows) and
@@ -11808,13 +11818,17 @@ var init_harness_capabilities = __esm({
11808
11818
  // `supportsSubagents`: foreground delegation (`dsh-tool-subagent` over the
11809
11819
  // in-process spawn provider), rendered as a Task block with the child's
11810
11820
  // steps nested under it. Background and continuable children are not wired.
11821
+ // `supportsPlanMode`: `plan` also turns on dsh plan mode; `exit_plan_mode`
11822
+ // reaches the plan-approval card through the user-questions seam.
11823
+ // `supportsQueuedSteer`: `agent.steer()` lands at the next step boundary
11824
+ // and never cancels a tool, so there is one steer and no separate "soon".
11811
11825
  supportsMcp: true,
11812
- supportsPlanMode: false,
11826
+ supportsPlanMode: true,
11813
11827
  supportsTodos: true,
11814
11828
  supportsSubagents: true,
11815
11829
  supportsCompact: true,
11816
- supportsStreamingToolInput: false,
11817
- supportsQueuedSteer: false,
11830
+ supportsStreamingToolInput: true,
11831
+ supportsQueuedSteer: true,
11818
11832
  supportsQueuedSteerSoon: false,
11819
11833
  // Single cwd.
11820
11834
  supportsAdditionalDirs: false,
@@ -13068,11 +13082,6 @@ var init_message_bridge = __esm({
13068
13082
  });
13069
13083
 
13070
13084
  // ../../packages/shared/src/agent-types.ts
13071
- function resolveLaunchSummary(task, summary2) {
13072
- const explicit = typeof summary2 === "string" ? summary2.trim() : "";
13073
- if (explicit) return explicit;
13074
- return task.split(/\n/, 1)[0]?.trim() || task.trim();
13075
- }
13076
13085
  function hasOneM(id) {
13077
13086
  return id.endsWith(ONE_M_SUFFIX);
13078
13087
  }
@@ -13950,23 +13959,23 @@ function mapPlanRequest(payload, fallbackId) {
13950
13959
  allowedPrompts: Array.isArray(payload.allowedPrompts) ? payload.allowedPrompts : Array.isArray(input.allowedPrompts) ? input.allowedPrompts : []
13951
13960
  };
13952
13961
  }
13953
- function userMessage(ctx, blockId, text2, nowIso) {
13962
+ function userMessage(ctx, blockId, text2, nowIso2) {
13954
13963
  return {
13955
13964
  id: blockId,
13956
13965
  role: "user",
13957
13966
  status: "complete",
13958
13967
  content: text2 ? [{ type: "text", text: text2 }] : [],
13959
- createdAt: nowIso,
13968
+ createdAt: nowIso2,
13960
13969
  providerId: ctx.providerId ?? "codex"
13961
13970
  };
13962
13971
  }
13963
- function assistantMessage(ctx, blockId, nowIso) {
13972
+ function assistantMessage(ctx, blockId, nowIso2) {
13964
13973
  return {
13965
13974
  id: blockId,
13966
13975
  role: "assistant",
13967
13976
  status: "streaming",
13968
13977
  content: [],
13969
- createdAt: nowIso,
13978
+ createdAt: nowIso2,
13970
13979
  providerId: ctx.providerId ?? "codex"
13971
13980
  };
13972
13981
  }
@@ -13982,7 +13991,7 @@ function createNodeSessionEventMapper(ctx) {
13982
13991
  const completedAssistantIds = /* @__PURE__ */ new Set();
13983
13992
  let lastAssistantId = null;
13984
13993
  let rawTerminalThisTurn = false;
13985
- const nowIso = ctx.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13994
+ const nowIso2 = ctx.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13986
13995
  const ensureAssistant = (push, preferredId) => {
13987
13996
  if (lastAssistantId) return lastAssistantId;
13988
13997
  const id = preferredId && preferredId.length > 0 ? preferredId : `assistant-${ctx.sessionId}`;
@@ -13990,7 +13999,7 @@ function createNodeSessionEventMapper(ctx) {
13990
13999
  startedAssistantIds.add(id);
13991
14000
  push({
13992
14001
  type: "message_start",
13993
- message: assistantMessage(ctx, id, nowIso())
14002
+ message: assistantMessage(ctx, id, nowIso2())
13994
14003
  });
13995
14004
  }
13996
14005
  lastAssistantId = id;
@@ -14012,7 +14021,7 @@ function createNodeSessionEventMapper(ctx) {
14012
14021
  const text2 = asString(payload.text) ?? "";
14013
14022
  push({
14014
14023
  type: "user_message_appended",
14015
- message: userMessage(ctx, blockId, text2, nowIso())
14024
+ message: userMessage(ctx, blockId, text2, nowIso2())
14016
14025
  });
14017
14026
  break;
14018
14027
  }
@@ -15652,9 +15661,6 @@ var init_runtime_policy = __esm({
15652
15661
 
15653
15662
  // ../../packages/runtime/src/session/session-runtime.ts
15654
15663
  import { randomUUID as randomUUID5 } from "node:crypto";
15655
- function redactTaskNotificationForDisplay(content) {
15656
- return content.replace(/\s+with credential\s+(?:"[^"]*"|'[^']*')/gi, "").replace(/\bs1sc_[A-Za-z0-9_-]+/g, "[redacted]").replace(/\s{2,}/g, " ").trim();
15657
- }
15658
15664
  function deriveSessionTitleFromUserText(text2) {
15659
15665
  const cleaned = stripMiniAppMarkup(text2).trim().replace(/\s+/g, " ");
15660
15666
  if (!cleaned) return null;
@@ -16422,18 +16428,17 @@ var init_session_runtime = __esm({
16422
16428
  return { delivered: true };
16423
16429
  }
16424
16430
  appendUserMessage(session, opts) {
16425
- const displayText = opts.source === "task-notification" ? redactTaskNotificationForDisplay(opts.text) : opts.text;
16426
16431
  const userBlock = {
16427
16432
  id: randomUUID5(),
16428
16433
  role: "user",
16429
- text: displayText,
16434
+ text: opts.text,
16430
16435
  createdAt: Date.now()
16431
16436
  };
16432
16437
  session.transcript.push(userBlock);
16433
16438
  session.updatedAt = Date.now();
16434
16439
  let autoTitle = null;
16435
16440
  if (!session.title || !session.title.trim()) {
16436
- autoTitle = deriveSessionTitleFromUserText(displayText);
16441
+ autoTitle = deriveSessionTitleFromUserText(opts.text);
16437
16442
  if (autoTitle) session.title = autoTitle;
16438
16443
  }
16439
16444
  this.persist(session);
@@ -16442,7 +16447,7 @@ var init_session_runtime = __esm({
16442
16447
  eventType: SESSION_DURABLE_EVENT.userMessage,
16443
16448
  payload: {
16444
16449
  blockId: userBlock.id,
16445
- text: displayText,
16450
+ text: opts.text,
16446
16451
  ...opts.source ? { source: opts.source } : {}
16447
16452
  },
16448
16453
  causationRequestId: opts.requestId
@@ -19027,10 +19032,10 @@ var init_builtin = __esm({
19027
19032
  init_effective_endpoints();
19028
19033
  init_protocols();
19029
19034
  XIAOMI_MODELS = {
19030
- default: { id: "mimo-v2.5-pro[1m]", name: "MiMo V2.5 Pro" },
19031
- opus: { id: "mimo-v2.5-pro[1m]", name: "MiMo V2.5 Pro" },
19032
- sonnet: { id: "mimo-v2.5-pro[1m]", name: "MiMo V2.5 Pro" },
19033
- haiku: { id: "mimo-v2.5-pro", name: "MiMo V2.5 Pro" }
19035
+ default: { id: "mimo-v2.6-pro[1m]", name: "MiMo V2.6 Pro" },
19036
+ opus: { id: "mimo-v2.6-pro[1m]", name: "MiMo V2.6 Pro" },
19037
+ sonnet: { id: "mimo-v2.6-pro[1m]", name: "MiMo V2.6 Pro" },
19038
+ haiku: { id: "mimo-v2.6-pro", name: "MiMo V2.6 Pro" }
19034
19039
  };
19035
19040
  BAILIAN_CODING_PLAN_MODELS = {
19036
19041
  default: { id: "qwen3.7-plus", name: "Qwen 3.7 Plus" },
@@ -19535,6 +19540,7 @@ var init_builtin = __esm({
19535
19540
  auth: "api-key",
19536
19541
  baseUrl: "https://api.minimax.cn",
19537
19542
  apiKeyUrl: "https://platform.minimax.cn/user-center/basic-information/interface-key",
19543
+ catalogProviderId: "minimax-cn",
19538
19544
  endpoints: [
19539
19545
  anthropic("/anthropic", {
19540
19546
  extraEnv: MINIMAX_EXTRA_ENV,
@@ -19552,6 +19558,7 @@ var init_builtin = __esm({
19552
19558
  auth: "api-key",
19553
19559
  baseUrl: "https://api.minimax.io",
19554
19560
  apiKeyUrl: "https://platform.minimax.io/user-center/basic-information/interface-key",
19561
+ catalogProviderId: "minimax",
19555
19562
  endpoints: [
19556
19563
  anthropic("/anthropic", {
19557
19564
  extraEnv: MINIMAX_EXTRA_ENV,
@@ -19648,6 +19655,7 @@ var init_builtin = __esm({
19648
19655
  auth: "api-key",
19649
19656
  baseUrl: "https://token-plan-cn.xiaomimimo.com",
19650
19657
  apiKeyUrl: "https://platform.xiaomimimo.com/#/console/api-keys",
19658
+ catalogProviderId: "xiaomi-token-plan-cn",
19651
19659
  endpoints: [
19652
19660
  anthropic("/anthropic", {
19653
19661
  extraEnv: { ...EMPTY_AUTH_TOKEN },
@@ -19661,6 +19669,7 @@ var init_builtin = __esm({
19661
19669
  auth: "api-key",
19662
19670
  baseUrl: "https://api.xiaomimimo.com",
19663
19671
  apiKeyUrl: "https://platform.xiaomimimo.com/#/console/api-keys",
19672
+ catalogProviderId: "xiaomi",
19664
19673
  endpoints: [
19665
19674
  anthropic("/anthropic", {
19666
19675
  extraEnv: { ...EMPTY_AUTH_TOKEN },
@@ -19779,6 +19788,7 @@ var init_builtin = __esm({
19779
19788
  brand: "longcat",
19780
19789
  name: "Longcat",
19781
19790
  description: "Longcat \u2014 \u957F\u732B AI \u7F16\u7A0B\u52A9\u624B",
19791
+ catalogProviderId: "longcat",
19782
19792
  plans: [
19783
19793
  {
19784
19794
  id: "coding",
@@ -19883,6 +19893,7 @@ var init_builtin = __esm({
19883
19893
  brand: "modelscope",
19884
19894
  name: "ModelScope",
19885
19895
  description: "ModelScope \u9B54\u642D \u2014 \u963F\u91CC\u5DF4\u5DF4\u6A21\u578B\u805A\u5408\u5E73\u53F0",
19896
+ catalogProviderId: "modelscope",
19886
19897
  plans: [
19887
19898
  {
19888
19899
  id: "api",
@@ -19909,6 +19920,7 @@ var init_builtin = __esm({
19909
19920
  brand: "siliconcloud",
19910
19921
  name: "SiliconFlow",
19911
19922
  description: "SiliconFlow \u7845\u57FA\u6D41\u52A8 \u2014 AI \u6A21\u578B\u805A\u5408\u63A8\u7406\u5E73\u53F0",
19923
+ catalogProviderId: "siliconflow-cn",
19912
19924
  plans: [
19913
19925
  {
19914
19926
  id: "api",
@@ -19958,6 +19970,7 @@ var init_builtin = __esm({
19958
19970
  brand: "bedrock",
19959
19971
  name: "AWS Bedrock",
19960
19972
  description: "Amazon Bedrock \u2014 run Claude on AWS infrastructure with IAM authentication",
19973
+ catalogProviderId: "amazon-bedrock",
19961
19974
  plans: [
19962
19975
  {
19963
19976
  id: "aws",
@@ -19979,6 +19992,7 @@ var init_builtin = __esm({
19979
19992
  brand: "vertexai",
19980
19993
  name: "Google Vertex",
19981
19994
  description: "Google Vertex AI \u2014 run Claude on GCP infrastructure with service account authentication",
19995
+ catalogProviderId: "google-vertex-anthropic",
19982
19996
  plans: [
19983
19997
  {
19984
19998
  id: "gcp",
@@ -71246,8 +71260,8 @@ var require_multipleOf = __commonJS({
71246
71260
  const { gen, data, schemaCode, it: it2 } = cxt;
71247
71261
  const prec = it2.opts.multipleOfPrecision;
71248
71262
  const res = gen.let("res");
71249
- const invalid2 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
71250
- cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid2}))`);
71263
+ const invalid3 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
71264
+ cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid3}))`);
71251
71265
  }
71252
71266
  };
71253
71267
  exports.default = def;
@@ -73403,8 +73417,8 @@ import { fileURLToPath } from "node:url";
73403
73417
  function resolveCliReleaseVersion() {
73404
73418
  const fromEnv = process.env.SUPERONE_CLI_VERSION?.trim();
73405
73419
  if (fromEnv) return fromEnv;
73406
- if ("0.68.0-alpha.2".trim()) {
73407
- return "0.68.0-alpha.2".trim();
73420
+ if ("0.69.0-alpha".trim()) {
73421
+ return "0.69.0-alpha".trim();
73408
73422
  }
73409
73423
  const fromDist = readDistManifestVersion();
73410
73424
  if (fromDist) return fromDist;
@@ -74599,7 +74613,7 @@ CREATE TABLE IF NOT EXISTS session_collaboration_grants (
74599
74613
  credential_secret TEXT,
74600
74614
  credential_hint TEXT NOT NULL,
74601
74615
  parent_session_id TEXT NOT NULL,
74602
- child_session_id TEXT UNIQUE,
74616
+ child_session_id TEXT,
74603
74617
  agent_id TEXT NOT NULL,
74604
74618
  task TEXT NOT NULL,
74605
74619
  config_json TEXT NOT NULL,
@@ -74684,6 +74698,66 @@ CREATE INDEX IF NOT EXISTS idx_session_providers_harness ON session_providers(ha
74684
74698
  `;
74685
74699
  var SCHEMA_GENERATION = 1;
74686
74700
 
74701
+ // ../../packages/runtime/src/collaboration/schema.ts
74702
+ var GRANTS_TABLE = "session_collaboration_grants";
74703
+ function ensureCollaborationGrantUniqueness(db, { warn = console.warn } = {}) {
74704
+ const rebuiltDdl = hasColumnUniqueChild(db) ? ddlWithoutColumnUnique(db) : null;
74705
+ if (rebuiltDdl === void 0) {
74706
+ warn(`[collaboration] ${GRANTS_TABLE}: unrecognized child_session_id UNIQUE DDL; keeping one link per peer`);
74707
+ }
74708
+ const apply = () => {
74709
+ if (rebuiltDdl) rebuildTable(db, rebuiltDdl);
74710
+ db.exec(`
74711
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_session_collaboration_spawn_child
74712
+ ON ${GRANTS_TABLE}(child_session_id) WHERE kind = 'spawn';
74713
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_session_collaboration_link_pair
74714
+ ON ${GRANTS_TABLE}(parent_session_id, child_session_id) WHERE kind = 'link';
74715
+ `);
74716
+ };
74717
+ const foreignKeysOn = Boolean(rebuiltDdl) && db.pragma("foreign_keys", { simple: true }) === 1;
74718
+ if (db.inTransaction) {
74719
+ if (foreignKeysOn) {
74720
+ throw new Error(`Rebuilding ${GRANTS_TABLE} inside a transaction requires foreign keys off`);
74721
+ }
74722
+ apply();
74723
+ return;
74724
+ }
74725
+ if (foreignKeysOn) db.pragma("foreign_keys = OFF");
74726
+ try {
74727
+ db.transaction(apply)();
74728
+ } finally {
74729
+ if (foreignKeysOn) db.pragma("foreign_keys = ON");
74730
+ }
74731
+ }
74732
+ function hasColumnUniqueChild(db) {
74733
+ const indexes = db.prepare(`PRAGMA index_list(${GRANTS_TABLE})`).all();
74734
+ return indexes.some((index) => {
74735
+ if (index.origin !== "u") return false;
74736
+ const columns = db.prepare(`PRAGMA index_info(${JSON.stringify(index.name)})`).all();
74737
+ return columns.length === 1 && columns[0].name === "child_session_id";
74738
+ });
74739
+ }
74740
+ var REBUILT_TABLE = `${GRANTS_TABLE}_rebuild`;
74741
+ function ddlWithoutColumnUnique(db) {
74742
+ const { sql } = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`).get(GRANTS_TABLE);
74743
+ const withoutUnique = sql.replace(/(\bchild_session_id\s+TEXT)\s+UNIQUE\b/i, "$1");
74744
+ const renamed = withoutUnique.replace(
74745
+ /^CREATE TABLE\s+(?:IF NOT EXISTS\s+)?["`]?session_collaboration_grants["`]?/i,
74746
+ `CREATE TABLE ${REBUILT_TABLE}`
74747
+ );
74748
+ return withoutUnique !== sql && renamed !== withoutUnique ? renamed : void 0;
74749
+ }
74750
+ function rebuildTable(db, ddl) {
74751
+ const indexes = db.prepare(`
74752
+ SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
74753
+ `).all(GRANTS_TABLE);
74754
+ db.exec(ddl);
74755
+ db.exec(`INSERT INTO ${REBUILT_TABLE} SELECT * FROM ${GRANTS_TABLE}`);
74756
+ db.exec("DROP TABLE session_collaboration_grants");
74757
+ db.exec(`ALTER TABLE ${REBUILT_TABLE} RENAME TO session_collaboration_grants`);
74758
+ for (const index of indexes) db.exec(index.sql);
74759
+ }
74760
+
74687
74761
  // ../../packages/runtime/src/db/database.ts
74688
74762
  init_session_provider_definitions();
74689
74763
  init_platform_registry();
@@ -74920,7 +74994,7 @@ CREATE TABLE IF NOT EXISTS session_collaboration_grants (
74920
74994
  credential_secret TEXT,
74921
74995
  credential_hint TEXT NOT NULL,
74922
74996
  parent_session_id TEXT NOT NULL,
74923
- child_session_id TEXT UNIQUE,
74997
+ child_session_id TEXT,
74924
74998
  agent_id TEXT NOT NULL,
74925
74999
  task TEXT NOT NULL,
74926
75000
  config_json TEXT NOT NULL,
@@ -74962,6 +75036,7 @@ CREATE TABLE IF NOT EXISTS session_collaboration_cursors (
74962
75036
  if (!grantCols.some((c) => c.name === "kind")) {
74963
75037
  db.exec(`ALTER TABLE session_collaboration_grants ADD COLUMN kind TEXT NOT NULL DEFAULT 'spawn'`);
74964
75038
  }
75039
+ ensureCollaborationGrantUniqueness(db);
74965
75040
  }
74966
75041
  function ensureSessionTagsColumn(db) {
74967
75042
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
@@ -79999,10 +80074,9 @@ async function handleCollaborationStart(payload, ctx) {
79999
80074
  const denied = requireScopes8(ctx.client, OPERATION_SCOPES.operateSession);
80000
80075
  if (denied) return denied;
80001
80076
  const p3 = asRecord24(payload);
80002
- const credential = typeof p3.credential === "string" ? p3.credential : void 0;
80003
- const grantId = typeof p3.grantId === "string" ? p3.grantId : void 0;
80004
- if (!credential && !grantId) {
80005
- return { error: { code: "invalid_argument", message: "credential or grantId required" } };
80077
+ const launchId = typeof p3.launchId === "string" ? p3.launchId.trim() : "";
80078
+ if (!launchId) {
80079
+ return { error: { code: "invalid_argument", message: "launchId required" } };
80006
80080
  }
80007
80081
  const callerSessionId = typeof p3.callerSessionId === "string" && p3.callerSessionId.trim() ? p3.callerSessionId.trim() : typeof p3.parentSessionId === "string" && p3.parentSessionId.trim() ? p3.parentSessionId.trim() : "";
80008
80082
  if (!callerSessionId) {
@@ -80030,8 +80104,8 @@ async function handleCollaborationStart(payload, ctx) {
80030
80104
  try {
80031
80105
  return {
80032
80106
  result: await ctx.collaboration.start({
80033
- credential,
80034
- grantId,
80107
+ launchId,
80108
+ task: typeof p3.task === "string" ? p3.task : void 0,
80035
80109
  formAnswers: p3.formAnswers && typeof p3.formAnswers === "object" ? p3.formAnswers : void 0,
80036
80110
  callerSessionId,
80037
80111
  controllerClientSessionId: ctx.client.clientSessionId
@@ -80041,36 +80115,39 @@ async function handleCollaborationStart(payload, ctx) {
80041
80115
  return mapThrown8(err);
80042
80116
  }
80043
80117
  }
80118
+ function assertMailboxLease(p3, sessionId, ctx) {
80119
+ const leaseId = String(p3.leaseId ?? "").trim();
80120
+ if (!leaseId) return { error: { code: "invalid_argument", message: "leaseId required" } };
80121
+ try {
80122
+ ctx.leases.assertValid({
80123
+ resource: { environmentId: ctx.identity.environmentId, sessionId },
80124
+ leaseId,
80125
+ generation: String(p3.generation ?? ""),
80126
+ holderClientId: ctx.client.clientSessionId
80127
+ });
80128
+ return null;
80129
+ } catch (err) {
80130
+ return mapThrown8(err);
80131
+ }
80132
+ }
80044
80133
  function handleCollaborationSend(payload, ctx) {
80045
80134
  const denied = requireScopes8(ctx.client, OPERATION_SCOPES.operateSession);
80046
80135
  if (denied) return denied;
80047
80136
  const p3 = asRecord24(payload);
80048
- const credential = String(p3.credential ?? "");
80049
80137
  const sessionId = String(p3.sessionId ?? p3.fromSessionId ?? "");
80050
80138
  const content = typeof p3.content === "string" ? p3.content : p3.body !== void 0 ? typeof p3.body === "string" ? p3.body : JSON.stringify(p3.body) : "";
80051
- if (!credential) {
80052
- return { error: { code: "invalid_argument", message: "credential required" } };
80053
- }
80054
80139
  if (!sessionId) {
80055
80140
  return { error: { code: "invalid_argument", message: "sessionId required" } };
80056
80141
  }
80057
- const leaseId = String(p3.leaseId ?? "").trim();
80058
- if (!leaseId) {
80059
- return { error: { code: "invalid_argument", message: "leaseId required" } };
80060
- }
80142
+ const leaseDenied = assertMailboxLease(p3, sessionId, ctx);
80143
+ if (leaseDenied) return leaseDenied;
80061
80144
  try {
80062
- ctx.leases.assertValid({
80063
- resource: { environmentId: ctx.identity.environmentId, sessionId },
80064
- leaseId,
80065
- generation: String(p3.generation ?? ""),
80066
- holderClientId: ctx.client.clientSessionId
80067
- });
80068
80145
  return {
80069
80146
  result: ctx.collaboration.send({
80070
- credential,
80147
+ sessionId,
80148
+ to: typeof p3.to === "string" ? p3.to : void 0,
80071
80149
  content,
80072
- clientMessageId: typeof p3.clientMessageId === "string" ? p3.clientMessageId : void 0,
80073
- sessionId
80150
+ clientMessageId: typeof p3.clientMessageId === "string" ? p3.clientMessageId : void 0
80074
80151
  })
80075
80152
  };
80076
80153
  } catch (err) {
@@ -80078,22 +80155,20 @@ function handleCollaborationSend(payload, ctx) {
80078
80155
  }
80079
80156
  }
80080
80157
  function handleCollaborationRetrieve(payload, ctx) {
80081
- const denied = requireScopes8(ctx.client, OPERATION_SCOPES.readSession);
80158
+ const denied = requireScopes8(ctx.client, OPERATION_SCOPES.operateSession);
80082
80159
  if (denied) return denied;
80083
80160
  const p3 = asRecord24(payload);
80084
- const credential = String(p3.credential ?? "");
80085
80161
  const sessionId = String(p3.sessionId ?? "");
80086
- if (!credential) {
80087
- return { error: { code: "invalid_argument", message: "credential required" } };
80088
- }
80089
80162
  if (!sessionId) {
80090
80163
  return { error: { code: "invalid_argument", message: "sessionId required" } };
80091
80164
  }
80165
+ const leaseDenied = assertMailboxLease(p3, sessionId, ctx);
80166
+ if (leaseDenied) return leaseDenied;
80092
80167
  try {
80093
80168
  return {
80094
80169
  result: ctx.collaboration.retrieve({
80095
- credential,
80096
80170
  sessionId,
80171
+ from: Array.isArray(p3.from) ? p3.from.filter((id) => typeof id === "string") : void 0,
80097
80172
  max: typeof p3.max === "number" ? p3.max : void 0
80098
80173
  })
80099
80174
  };
@@ -87037,1187 +87112,1148 @@ var SessionRuntime2 = class extends SessionRuntime {
87037
87112
  init_harness_runners();
87038
87113
  init_codex_turn_runner();
87039
87114
 
87040
- // src/session/collaboration.ts
87041
- init_agent_types();
87042
- init_environment();
87043
- import { createHash as createHash9, randomBytes as randomBytes5, randomUUID as randomUUID11 } from "node:crypto";
87044
- import { existsSync as existsSync39, statSync as statSync15 } from "node:fs";
87045
- import { resolve as pathResolve2 } from "node:path";
87046
-
87047
- // ../../packages/shared/src/acp-brand.ts
87048
- init_acp_brand();
87049
-
87050
- // src/session/collaboration.ts
87051
- init_resolve_service();
87052
- var MAX_MESSAGES_PER_RETRIEVE = 100;
87053
- var EMPTY_MAILBOX_HINT = "No peer has replied yet. Do not retrieve again, do not sleep, do not wait in place \u2014 end your turn or do unrelated work. A task notification will start a new turn for you as soon as a message arrives.";
87054
- function hashCredential(credential) {
87055
- return createHash9("sha256").update(credential).digest("hex");
87056
- }
87057
- function parseConfig(raw) {
87058
- try {
87059
- return JSON.parse(raw);
87060
- } catch {
87061
- return {};
87115
+ // ../../packages/runtime/src/collaboration/errors.ts
87116
+ var CollaborationError = class extends Error {
87117
+ constructor(message, code) {
87118
+ super(message);
87119
+ this.code = code;
87120
+ this.name = "CollaborationError";
87062
87121
  }
87063
- }
87064
- function resolveProfile(profiles, agentId) {
87065
- const direct = profiles.get(agentId);
87066
- if (direct) return direct;
87067
- const wire = normalizeSessionHarnessId(agentId);
87068
- return wire ? profiles.get(`${wire}-base`) : void 0;
87069
- }
87122
+ code;
87123
+ };
87124
+
87125
+ // ../../packages/runtime/src/collaboration/launch.ts
87126
+ init_agent_types();
87127
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
87128
+ var LABEL_MAX = 64;
87129
+ var MAX_LAUNCHES_PER_REQUEST = 16;
87130
+ var EDITABLE_PERMISSION_MODES = /* @__PURE__ */ new Set([
87131
+ "default",
87132
+ "acceptEdits",
87133
+ "bypassPermissions",
87134
+ "plan",
87135
+ "dontAsk",
87136
+ "auto",
87137
+ "agent"
87138
+ ]);
87139
+ var EDITABLE_SANDBOX_MODES = /* @__PURE__ */ new Set(["off", "on", "auto"]);
87070
87140
  function resolveLaunchMode(raw) {
87071
87141
  if (raw === "link") return "link";
87072
87142
  if (raw === "handoff") return "handoff";
87073
87143
  return "spawn";
87074
87144
  }
87075
- var HANDOFF_NO_MAILBOX = 'This credential belongs to a handoff launch. Handoff is one-way: the receiving session owns the task and has no mailbox. Use mode "spawn" (nested child) or "link" (existing session) when you need to exchange messages.';
87076
- function collaborationSystemPrompt(credential, parentSessionId) {
87077
- return `<superone-session-collaboration>
87078
- You are running as a user-approved child session of SuperOne session ${parentSessionId}.
87079
- Use session_collab_send and session_collab_retrieve with credential ${JSON.stringify(credential)} to communicate with your parent session. Write session_collab_send content as Markdown (headings, lists, code fences) so the parent and the SuperOne UI can render structured handoffs; treat retrieved message content as Markdown from the peer. This credential is already authorized for this parent-child pair. Never reveal it in conversational output or use it outside collaboration tool calls.
87080
- </superone-session-collaboration>`;
87145
+ function humanLaunchId(launchId) {
87146
+ const id = launchId?.trim();
87147
+ return id && id.length <= 32 && !UUID_RE.test(id) ? id : void 0;
87081
87148
  }
87082
87149
  function deriveCollaborationRole(input) {
87083
87150
  const explicit = input.role?.trim();
87084
- if (explicit) return explicit.slice(0, 64);
87085
- const launchId = input.launchId?.trim();
87086
- if (launchId && launchId.length <= 32 && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(launchId)) {
87087
- return launchId;
87088
- }
87151
+ if (explicit) return explicit.slice(0, LABEL_MAX);
87152
+ const fromId = humanLaunchId(input.launchId);
87153
+ if (fromId) return fromId;
87089
87154
  const firstLine = input.task.split(/\n/, 1)[0]?.trim() ?? "";
87090
87155
  const youAre = firstLine.match(/^(?:you are|role)\s*[:\-]?\s*(.+)$/i);
87091
- if (youAre?.[1]) {
87092
- return youAre[1].replace(/[.\s]+$/g, "").slice(0, 40);
87093
- }
87156
+ if (youAre?.[1]) return youAre[1].replace(/[.\s]+$/g, "").slice(0, 40);
87094
87157
  return "Agent";
87095
87158
  }
87096
87159
  function deriveCollaborationName(input) {
87097
87160
  const explicit = input.name?.trim();
87098
- if (explicit) return explicit.slice(0, 64);
87099
- const launchId = input.launchId?.trim();
87100
- if (launchId && launchId.length <= 32 && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(launchId)) {
87101
- return launchId;
87102
- }
87103
- return "Agent";
87161
+ if (explicit) return explicit.slice(0, LABEL_MAX);
87162
+ return humanLaunchId(input.launchId) ?? "Agent";
87104
87163
  }
87105
87164
  function collaborationSessionTitle(name, role) {
87106
- const n = name.trim() || "Agent";
87107
- const r = role.trim() || "Agent";
87108
- return `${n} - ${r}`;
87165
+ return `${name.trim() || "Agent"} - ${role.trim() || "Agent"}`;
87109
87166
  }
87110
- var CollaborationService = class {
87111
- constructor(deps) {
87112
- this.deps = deps;
87167
+ function parseGrantConfig(raw) {
87168
+ try {
87169
+ return JSON.parse(raw);
87170
+ } catch {
87171
+ return {};
87113
87172
  }
87114
- deps;
87115
- /** Agent profiles from session_providers (+ ready-harness fallback). */
87116
- listProfiles() {
87117
- const { harnesses, providers, sessions, sessionProviders } = this.deps;
87118
- const profiles = [];
87119
- const seen = /* @__PURE__ */ new Set();
87120
- const providerOptions = {
87121
- experimentalClaudeOpenAiChatEnabled: this.deps.experimentalClaudeOpenAiChatEnabled?.() ?? false
87122
- };
87123
- const pushProfile = (profileId, harnessId, name, description, profileConfig) => {
87124
- if (seen.has(profileId)) return;
87125
- if (!harnesses.isSessionHarnessRunnable(harnessId)) return;
87126
- seen.add(profileId);
87127
- const harnessModels = listHarnessModels(providers, harnessId, null, providerOptions);
87128
- const models = harnessModels.map((m3) => ({
87129
- id: m3.id,
87130
- name: m3.name || m3.id,
87131
- ...m3.description ? { description: m3.description } : {},
87132
- ...m3.serviceTiers?.length ? { serviceTiers: m3.serviceTiers } : {}
87133
- }));
87134
- const defaultModel = harnessModels.find((m3) => m3.isDefault) ?? harnessModels[0];
87135
- const efforts = /* @__PURE__ */ new Set();
87136
- for (const m3 of harnessModels) {
87137
- for (const e of m3.supportedEffortLevels ?? []) efforts.add(e);
87138
- }
87139
- const cfg = profileConfig && typeof profileConfig === "object" && !Array.isArray(profileConfig) ? profileConfig : {};
87140
- const cfgModel = typeof cfg.model === "string" && cfg.model.trim() ? cfg.model.trim() : void 0;
87141
- const cfgEffort = typeof cfg.effort === "string" && cfg.effort.trim() ? cfg.effort.trim() : typeof cfg.reasoningEffort === "string" && cfg.reasoningEffort.trim() ? cfg.reasoningEffort.trim() : void 0;
87142
- const acpAgentId = harnessId === "acp" && typeof cfg.agentId === "string" && cfg.agentId.trim() ? cfg.agentId.trim() : null;
87143
- const modelDefault = cfgModel ?? defaultModel?.id;
87144
- const effortDefault = cfgEffort ?? (efforts.has("high") ? "high" : efforts.has("medium") ? "medium" : efforts.size > 0 ? [...efforts][0] : void 0);
87145
- profiles.push({
87146
- id: profileId,
87147
- name: harnessId === "acp" ? acpAgentDisplayName(acpAgentId) : name,
87148
- harnessId,
87149
- ...acpAgentId ? { acpAgentId } : {},
87150
- brandKey: resolveHarnessBrandKey(harnessId, acpAgentId),
87151
- description,
87152
- defaultConfig: {
87153
- ...modelDefault ? { model: modelDefault } : {},
87154
- ...effortDefault ? { effort: effortDefault } : {},
87155
- ...harnessId === "codex" ? { fastMode: typeof cfg.fastMode === "boolean" ? cfg.fastMode : false } : {}
87156
- },
87157
- models: models.length > 0 ? models : [{ id: "default", name: "Default" }],
87158
- efforts: [...efforts],
87159
- apiProviders: listHarnessApiProviders(providers, harnessId, providerOptions)
87160
- });
87173
+ }
87174
+ function invalid2(message) {
87175
+ throw new CollaborationError(message, "invalid_argument");
87176
+ }
87177
+ function assertLabel(kind, value) {
87178
+ if (value.length > LABEL_MAX) invalid2(`A launch ${kind} may contain at most ${LABEL_MAX} characters`);
87179
+ return value;
87180
+ }
87181
+ function assertLaunchCount(count) {
87182
+ if (count === 0) invalid2("launches must contain at least one proposed session");
87183
+ if (count > MAX_LAUNCHES_PER_REQUEST) {
87184
+ invalid2(`A single request may contain at most ${MAX_LAUNCHES_PER_REQUEST} launches`);
87185
+ }
87186
+ }
87187
+ function normalizeLaunchLabels(mode, launch2, linkPeerTitle) {
87188
+ const summary2 = launch2.summary?.trim();
87189
+ if (!summary2) invalid2("Every launch must include a non-empty summary");
87190
+ if (mode === "link") {
87191
+ return {
87192
+ summary: summary2,
87193
+ name: assertLabel("name", launch2.name?.trim() || linkPeerTitle || ""),
87194
+ role: assertLabel("role", launch2.role?.trim() || "Peer")
87161
87195
  };
87162
- if (sessionProviders) {
87163
- for (const p3 of sessionProviders.list()) {
87164
- pushProfile(
87165
- p3.id,
87166
- p3.harnessId,
87167
- p3.name,
87168
- p3.isBase ? `${p3.harnessId} harness with the built-in configuration` : `Custom ${p3.harnessId} profile`,
87169
- p3.config
87170
- );
87171
- }
87172
- }
87173
- if (profiles.length === 0) {
87174
- const seedIds = new Set(harnesses.readySessionHarnessIds());
87175
- for (const s4 of sessions.list()) {
87176
- if (s4.harnessId) seedIds.add(s4.harnessId);
87177
- }
87178
- for (const id of seedIds) {
87179
- const harnessId = normalizeSessionHarnessId(id) ?? id;
87180
- pushProfile(harnessId, harnessId, harnessId, `${harnessId} harness`);
87181
- }
87182
- }
87183
- return profiles;
87184
87196
  }
87185
- /**
87186
- * Request child launches. When `requireUserConfirm` is true (MCP tool path),
87187
- * emits pendingInteraction kind session_agents_confirm and waits for the
87188
- * desktop remote UI accept/decline/cancel (+ formAnswers).
87189
- * RPC path defaults to auto-approve (already-trusted controller).
87190
- */
87191
- async request(input) {
87192
- const parent = this.deps.sessions.get(input.parentSessionId);
87193
- if (!parent) {
87194
- throw Object.assign(new Error("Parent session is not available"), { code: "not_found" });
87195
- }
87196
- const nested = this.deps.db.prepare(
87197
- `SELECT 1 FROM session_collaboration_grants
87198
- WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn' LIMIT 1`
87199
- ).get(input.parentSessionId);
87200
- if (nested) {
87201
- throw Object.assign(
87202
- new Error(
87203
- "Nested collaboration is not supported. Only top-level (non-collaboration-child) sessions may request agents."
87204
- ),
87205
- { code: "failed_precondition" }
87197
+ const name = launch2.name?.trim();
87198
+ if (!name) invalid2(`Every ${mode} launch must include a non-empty name`);
87199
+ const role = launch2.role?.trim();
87200
+ if (!role) invalid2(`Every ${mode} launch must include a non-empty role`);
87201
+ return { summary: summary2, name: assertLabel("name", name), role: assertLabel("role", role) };
87202
+ }
87203
+ function normalizeStartTask(mode, raw) {
87204
+ const task = typeof raw === "string" ? raw.trim() : "";
87205
+ if (!task && mode !== "link") invalid2(`Starting a ${mode} launch requires a non-empty task`);
87206
+ if (task.length > SESSION_AGENT_TASK_MAX) {
87207
+ invalid2(`A launch task may contain at most ${SESSION_AGENT_TASK_MAX.toLocaleString()} characters`);
87208
+ }
87209
+ return task;
87210
+ }
87211
+ function patchEditableLaunchConfig(base, rawPatch) {
87212
+ const patch = rawPatch && typeof rawPatch === "object" ? rawPatch : {};
87213
+ return {
87214
+ ...base,
87215
+ ...typeof patch.model === "string" && patch.model.trim() ? { model: patch.model.trim() } : {},
87216
+ ...typeof patch.effort === "string" && patch.effort.trim() ? { effort: patch.effort.trim() } : {},
87217
+ ...typeof patch.fastMode === "boolean" ? { fastMode: patch.fastMode } : {},
87218
+ ...patch.apiProviderId === null || typeof patch.apiProviderId === "string" ? { apiProviderId: patch.apiProviderId } : {},
87219
+ ...typeof patch.permissionMode === "string" && EDITABLE_PERMISSION_MODES.has(patch.permissionMode) ? { permissionMode: patch.permissionMode } : {},
87220
+ ...typeof patch.sandboxMode === "string" && EDITABLE_SANDBOX_MODES.has(patch.sandboxMode) ? { sandboxMode: patch.sandboxMode } : {}
87221
+ };
87222
+ }
87223
+ function mergeConfirmedLaunches(proposed, content) {
87224
+ const packed = content?.[SESSION_AGENT_LAUNCHES_FIELD];
87225
+ if (typeof packed !== "string") return proposed;
87226
+ let edited;
87227
+ try {
87228
+ edited = JSON.parse(packed);
87229
+ } catch {
87230
+ invalid2("The confirmed launch configuration is invalid");
87231
+ }
87232
+ if (!Array.isArray(edited)) invalid2("The confirmed launch configuration is invalid");
87233
+ if (edited.length !== proposed.length) {
87234
+ invalid2("The confirmed request must contain the same launches that were proposed");
87235
+ }
87236
+ const proposedById = new Map(proposed.map((launch2) => [launch2.launchId, launch2]));
87237
+ const seen = /* @__PURE__ */ new Set();
87238
+ return edited.map((raw) => {
87239
+ if (!raw || typeof raw !== "object") invalid2("The confirmed launch configuration is invalid");
87240
+ const item = raw;
87241
+ const launchId = typeof item.launchId === "string" ? item.launchId : "";
87242
+ const base = proposedById.get(launchId);
87243
+ if (!base) invalid2(`Unknown launchId in confirmed configuration: ${launchId || "(missing)"}`);
87244
+ if (seen.has(launchId)) invalid2("Confirmed launches must have unique launchIds");
87245
+ seen.add(launchId);
87246
+ if (base.mode === "link") return { ...base, config: { ...base.config } };
87247
+ return {
87248
+ ...base,
87249
+ // A tampered mode could turn a supervised child into a one-way handoff.
87250
+ mode: base.mode ?? "spawn",
87251
+ config: patchEditableLaunchConfig(base.config, item.config)
87252
+ };
87253
+ });
87254
+ }
87255
+
87256
+ // ../../packages/runtime/src/collaboration/text.ts
87257
+ var HANDOFF_NO_MAILBOX = 'That session was created by a one-way handoff: it owns the task and has no mailbox, so neither side can message the other. Use mode "spawn" (nested child) or "link" (existing session) when you need to exchange messages.';
87258
+ var EMPTY_MAILBOX_HINT = "No peer has replied yet. Do not retrieve again, do not sleep, do not wait in place \u2014 end your turn or do unrelated work. A task notification will start a new turn for you as soon as a message arrives.";
87259
+ var NO_PEERS_HINT = "You have no collaboration peers. Launch or link one with session_collab_request, then session_collab_start.";
87260
+ var NESTED_COLLABORATION_UNSUPPORTED = "Nested collaboration is not supported. Only top-level (non-collaboration-child) sessions may request agents.";
87261
+ function collaborationSystemPrompt(parentSessionId) {
87262
+ return `<superone-session-collaboration>
87263
+ You are running as a user-approved child session of SuperOne session ${parentSessionId}.
87264
+ Your parent is your default collaboration peer: session_collab_send without \`to\` messages it, and session_collab_retrieve reads its messages. Sessions the user links to you later are peers too; reply to them with \`to\` set to their session id. Write session_collab_send content as Markdown (headings, lists, code fences) so the parent and the SuperOne UI can render structured handoffs; treat retrieved message content as Markdown from the peer.
87265
+ </superone-session-collaboration>`;
87266
+ }
87267
+ function sessionLabel(sessionId, title) {
87268
+ return `SuperOne session ${sessionId} ("${title}")`;
87269
+ }
87270
+ function mailboxWakeText(from) {
87271
+ return `A collaboration mailbox message is ready. It is from ${sessionLabel(from.sessionId, from.title)}. Call session_collab_retrieve to receive it, then act on it and end your turn \u2014 you will be woken again the same way for every later message, so never wait in place for one.`;
87272
+ }
87273
+ function linkActivationWakeText(input) {
87274
+ return `A user-approved collaboration link is active with ${sessionLabel(input.initiatorSessionId, input.initiatorTitle)}. Call session_collab_retrieve` + (input.hasOpening ? " to read the opening message" : " if a mailbox message is waiting") + `, then reply with session_collab_send({ to: ${JSON.stringify(input.initiatorSessionId)} }). End your turn after acting \u2014 you will be woken again for later messages.`;
87275
+ }
87276
+ function handoffTaskContent(input) {
87277
+ const title = input.parentTitle || input.parentSessionId.slice(0, 8);
87278
+ return `> Handed off from SuperOne session \`${input.parentSessionId}\` ("${title}"). This is a one-way handoff: you own this task now and cannot message that session back. Read its context with session_read({ sessionId }) if you need it.
87279
+
87280
+ ` + input.task;
87281
+ }
87282
+
87283
+ // ../../packages/runtime/src/collaboration/mailbox.ts
87284
+ var MAX_MAILBOX_CONTENT = 1e5;
87285
+ var MAX_MESSAGES_PER_RETRIEVE = 100;
87286
+ function describeLaunchedPeer(grant) {
87287
+ const config2 = parseGrantConfig(grant.config_json);
87288
+ const name = deriveCollaborationName({ name: config2.name });
87289
+ const role = deriveCollaborationRole({ role: config2.role, task: grant.task });
87290
+ return { name, role, title: collaborationSessionTitle(name, role), agentId: grant.agent_id, config: config2 };
87291
+ }
87292
+ function describePeerForCaller(grant, callerSessionId, sessionTitle2) {
87293
+ const relation = grant.kind === "link" ? "link" : callerSessionId === grant.child_session_id ? "parent" : "child";
87294
+ if (callerSessionId === grant.child_session_id) {
87295
+ const title = sessionTitle2(grant.parent_session_id)?.trim() || grant.parent_session_id.slice(0, 8);
87296
+ return {
87297
+ sessionId: grant.parent_session_id,
87298
+ name: relation === "parent" ? "Parent" : title,
87299
+ role: relation === "parent" ? "" : "Peer",
87300
+ title,
87301
+ relation
87302
+ };
87303
+ }
87304
+ const child = describeLaunchedPeer(grant);
87305
+ return {
87306
+ sessionId: grant.child_session_id ?? "",
87307
+ name: child.name,
87308
+ role: child.role,
87309
+ title: child.title,
87310
+ relation
87311
+ };
87312
+ }
87313
+ function listMailboxChannels(store, callerSessionId, sessionTitle2) {
87314
+ return store.channelsFor(callerSessionId).map((grant) => ({
87315
+ grant,
87316
+ peer: describePeerForCaller(grant, callerSessionId, sessionTitle2)
87317
+ }));
87318
+ }
87319
+ function peersOf(channels) {
87320
+ return [...new Map(channels.map((channel) => [channel.peer.sessionId, channel.peer])).values()];
87321
+ }
87322
+ function assertNotPeeredElsewhere(store, callerSessionId, peerSessionId) {
87323
+ const other = store.channelsFor(callerSessionId).find((grant) => (grant.parent_session_id === peerSessionId || grant.child_session_id === peerSessionId) && !(grant.kind === "link" && grant.parent_session_id === callerSessionId));
87324
+ if (other) {
87325
+ throw new CollaborationError(
87326
+ `Session ${peerSessionId} is already one of your collaboration peers; message it with session_collab_send({ to: ${JSON.stringify(peerSessionId)} }).`,
87327
+ "failed_precondition"
87328
+ );
87329
+ }
87330
+ }
87331
+ function formatPeers(peers) {
87332
+ return peers.map((peer) => `${peer.sessionId} ("${peer.title}", ${peer.relation})`).join("; ");
87333
+ }
87334
+ function resolveSendChannel(store, callerSessionId, to2, sessionTitle2) {
87335
+ const channels = listMailboxChannels(store, callerSessionId, sessionTitle2);
87336
+ const peers = peersOf(channels);
87337
+ const target = to2?.trim();
87338
+ if (!target) {
87339
+ if (peers.length === 0) throw new CollaborationError(NO_PEERS_HINT, "failed_precondition");
87340
+ const parent = channels.find((channel) => channel.peer.relation === "parent");
87341
+ if (parent) return parent;
87342
+ if (peers.length > 1) {
87343
+ throw new CollaborationError(
87344
+ `You have several collaboration peers; pass \`to\` with one of their session ids: ${formatPeers(peers)}`,
87345
+ "invalid_argument"
87206
87346
  );
87207
87347
  }
87208
- const launches = input.launches;
87209
- if (!Array.isArray(launches) || launches.length === 0) {
87210
- throw Object.assign(new Error("launches must contain at least one proposed session"), {
87211
- code: "invalid_argument"
87212
- });
87213
- }
87214
- if (launches.length > 16) {
87215
- throw Object.assign(new Error("A single request may contain at most 16 launches"), {
87216
- code: "invalid_argument"
87217
- });
87348
+ return channels[channels.length - 1];
87349
+ }
87350
+ const matches = channels.filter((channel) => channel.peer.sessionId === target);
87351
+ if (matches.length > 0) return matches[matches.length - 1];
87352
+ if (store.isHandoffPair(callerSessionId, target)) {
87353
+ throw new CollaborationError(HANDOFF_NO_MAILBOX, "failed_precondition");
87354
+ }
87355
+ throw new CollaborationError(
87356
+ `Session ${target} is not one of your collaboration peers. ` + (peers.length > 0 ? `Your peers: ${formatPeers(peers)}` : NO_PEERS_HINT),
87357
+ "forbidden"
87358
+ );
87359
+ }
87360
+ function normalizeMailboxContent(raw) {
87361
+ const content = typeof raw === "string" ? raw.trim() : "";
87362
+ if (!content) throw new CollaborationError("content must not be empty", "invalid_argument");
87363
+ if (content.length > MAX_MAILBOX_CONTENT) {
87364
+ throw new CollaborationError("content may contain at most 100,000 characters", "invalid_argument");
87365
+ }
87366
+ return content;
87367
+ }
87368
+ function readCallerMailbox(store, callerSessionId, options, sessionTitle2) {
87369
+ const channels = listMailboxChannels(store, callerSessionId, sessionTitle2);
87370
+ const peers = peersOf(channels);
87371
+ const from = options.from?.map((id) => id.trim()).filter(Boolean) ?? [];
87372
+ if (from.length > 0) {
87373
+ const known = new Set(peers.map((peer) => peer.sessionId));
87374
+ const unknown2 = from.filter((id) => !known.has(id));
87375
+ if (unknown2.length > 0) {
87376
+ throw new CollaborationError(
87377
+ `Not your collaboration peers: ${unknown2.join(", ")}. ` + (peers.length > 0 ? `Your peers: ${formatPeers(peers)}` : NO_PEERS_HINT),
87378
+ "forbidden"
87379
+ );
87218
87380
  }
87219
- const profileList = this.listProfiles();
87220
- const profiles = new Map(profileList.map((p3) => [p3.id, p3]));
87221
- const normalized = launches.map((launch2) => {
87222
- const mode = resolveLaunchMode(launch2.mode);
87223
- const launchId = launch2.launchId?.trim() || randomUUID11();
87224
- if (mode === "link") {
87225
- const peerSessionId = launch2.sessionId?.trim();
87226
- if (!peerSessionId) {
87227
- throw Object.assign(
87228
- new Error("Link launches require sessionId of an existing SuperOne session"),
87229
- { code: "invalid_argument" }
87230
- );
87231
- }
87232
- if (peerSessionId === input.parentSessionId) {
87233
- throw Object.assign(new Error("Cannot link a session to itself"), {
87234
- code: "invalid_argument"
87235
- });
87236
- }
87237
- const peer = this.deps.sessions.get(peerSessionId);
87238
- if (!peer) {
87239
- throw Object.assign(new Error(`Unknown sessionId for link: ${peerSessionId}`), {
87240
- code: "not_found"
87241
- });
87242
- }
87243
- const summary3 = launch2.summary?.trim() || resolveLaunchSummary(launch2.task ?? "", launch2.summary);
87244
- if (!summary3) {
87245
- throw Object.assign(new Error("Every launch must include a non-empty summary"), {
87246
- code: "invalid_argument"
87247
- });
87248
- }
87249
- const task2 = (launch2.task ?? "").trim();
87250
- if (task2.length > SESSION_AGENT_TASK_MAX) {
87251
- throw Object.assign(
87252
- new Error(`A launch task may contain at most ${SESSION_AGENT_TASK_MAX.toLocaleString()} characters`),
87253
- { code: "invalid_argument" }
87254
- );
87255
- }
87256
- const peerTitle = peer.title?.trim() || peerSessionId.slice(0, 8);
87257
- const name2 = launch2.name?.trim() || peerTitle;
87258
- const role2 = launch2.role?.trim() || "Peer";
87259
- const projectPath = this.deps.projects.get(peer.projectId)?.path;
87260
- return {
87381
+ }
87382
+ const selected = from.length > 0 ? channels.filter((channel) => from.includes(channel.peer.sessionId)) : channels;
87383
+ if (selected.length === 0) return { messages: [], peers };
87384
+ const limit = Math.min(MAX_MESSAGES_PER_RETRIEVE, Math.max(1, Math.floor(options.limit ?? MAX_MESSAGES_PER_RETRIEVE)));
87385
+ const peerByGrant = new Map(selected.map((channel) => [channel.grant.grant_id, channel.peer]));
87386
+ const perGrantLimit = Math.max(1, Math.floor(limit / selected.length));
87387
+ const messages = store.readMailbox(callerSessionId, [...peerByGrant.keys()], perGrantLimit).flatMap(({ grantId, rows }) => rows.map((row) => ({
87388
+ messageId: row.id,
87389
+ sequence: row.sequence,
87390
+ fromSessionId: row.sender_session_id,
87391
+ from: peerByGrant.get(grantId),
87392
+ content: row.content,
87393
+ createdAt: row.created_at
87394
+ }))).sort((a, b3) => a.createdAt.localeCompare(b3.createdAt)).slice(0, limit);
87395
+ return { messages, peers };
87396
+ }
87397
+
87398
+ // ../../packages/runtime/src/collaboration/start.ts
87399
+ var START_APPROVED_LAUNCHES_HINT = "Start each launch with session_collab_start({ launchId, task }); task is the full Markdown brief (optional for link, where it becomes the opening message).";
87400
+ function recordApprovedLaunch(store, parentSessionId, launch2, config2) {
87401
+ const mode = resolveLaunchMode(launch2.mode);
87402
+ const { launchId, summary: summary2, name, role } = launch2;
87403
+ const title = collaborationSessionTitle(name, role);
87404
+ if (mode === "link") {
87405
+ const peerSessionId = launch2.sessionId?.trim();
87406
+ if (!peerSessionId) throw new CollaborationError("Link launches require sessionId", "invalid_argument");
87407
+ const existing = store.findLinkGrant(parentSessionId, peerSessionId);
87408
+ if (existing) {
87409
+ const existingConfig = { ...parseGrantConfig(existing.config_json), launchId };
87410
+ store.updateConfig(existing.grant_id, existingConfig);
87411
+ return {
87412
+ grantId: existing.grant_id,
87413
+ approved: {
87261
87414
  launchId,
87262
- mode: "link",
87263
- agentId: "",
87415
+ mode,
87416
+ agentId: existing.agent_id,
87264
87417
  sessionId: peerSessionId,
87265
- peerTitle,
87266
- peerProjectPath: projectPath,
87267
- summary: summary3,
87268
- task: task2,
87269
- name: name2,
87270
- role: role2,
87271
- config: { name: name2, role: role2, summary: summary3 }
87272
- };
87273
- }
87274
- const agentId = launch2.agentId?.trim();
87275
- if (!agentId) {
87276
- throw Object.assign(
87277
- new Error(`${mode} launches require agentId from session_collab_list_agents`),
87278
- { code: "invalid_argument" }
87279
- );
87280
- }
87281
- const profile = resolveProfile(profiles, agentId);
87282
- if (!profile) {
87283
- throw Object.assign(new Error(`Unknown agent profile: ${agentId}`), {
87284
- code: "invalid_argument"
87285
- });
87286
- }
87287
- const task = launch2.task?.trim();
87288
- if (!task) {
87289
- throw Object.assign(new Error("Every spawn launch must include a non-empty task"), {
87290
- code: "invalid_argument"
87291
- });
87292
- }
87293
- if (task.length > SESSION_AGENT_TASK_MAX) {
87294
- throw Object.assign(
87295
- new Error(`A launch task may contain at most ${SESSION_AGENT_TASK_MAX.toLocaleString()} characters`),
87296
- { code: "invalid_argument" }
87297
- );
87298
- }
87299
- const summary2 = resolveLaunchSummary(task, launch2.summary);
87300
- if (!summary2) {
87301
- throw Object.assign(new Error("Every launch must include a non-empty summary"), {
87302
- code: "invalid_argument"
87303
- });
87304
- }
87305
- const name = launch2.name?.trim();
87306
- if (!name) {
87307
- throw Object.assign(new Error(`Every ${mode} launch must include a non-empty name`), {
87308
- code: "invalid_argument"
87309
- });
87310
- }
87311
- const role = launch2.role?.trim();
87312
- if (!role) {
87313
- throw Object.assign(new Error(`Every ${mode} launch must include a non-empty role`), {
87314
- code: "invalid_argument"
87315
- });
87316
- }
87317
- const raw = launch2.config && typeof launch2.config === "object" ? launch2.config : {};
87318
- const safeFromLaunch = {};
87319
- if (typeof raw.model === "string" && raw.model.trim()) safeFromLaunch.model = raw.model.trim();
87320
- if (typeof raw.effort === "string" && raw.effort.trim()) safeFromLaunch.effort = raw.effort.trim();
87321
- if (raw.apiProviderId === null) safeFromLaunch.apiProviderId = null;
87322
- else if (typeof raw.apiProviderId === "string" && raw.apiProviderId.trim()) {
87323
- safeFromLaunch.apiProviderId = raw.apiProviderId.trim();
87324
- }
87325
- if (raw.worktree && typeof raw.worktree === "object") safeFromLaunch.worktree = raw.worktree;
87326
- if (typeof raw.worktreePath === "string" && raw.worktreePath.trim()) {
87327
- safeFromLaunch.worktreePath = raw.worktreePath.trim();
87328
- }
87329
- if (typeof raw.cwd === "string" && raw.cwd.trim()) safeFromLaunch.cwd = raw.cwd.trim();
87330
- return {
87331
- launchId,
87332
- mode,
87333
- agentId,
87334
- summary: summary2,
87335
- task,
87336
- name,
87337
- role,
87338
- config: {
87339
- ...profile.defaultConfig,
87340
- ...safeFromLaunch,
87341
- permissionMode: "default",
87342
- sandboxMode: "off",
87343
- cwd: typeof safeFromLaunch.cwd === "string" ? safeFromLaunch.cwd : parent.cwd ?? this.deps.projects.get(parent.projectId)?.path,
87418
+ summary: summary2,
87344
87419
  name,
87345
- role
87420
+ role,
87421
+ title,
87422
+ config: existingConfig,
87423
+ reused: true
87346
87424
  }
87347
87425
  };
87348
- });
87349
- const launchIds = new Set(normalized.map((l) => l.launchId));
87350
- if (launchIds.size !== normalized.length) {
87351
- throw Object.assign(new Error("Every confirmed launch must have a unique launchId"), {
87352
- code: "invalid_argument"
87353
- });
87354
- }
87355
- let confirmed = normalized;
87356
- if (input.requireUserConfirm) {
87357
- const outcome = await this.deps.sessions.requestAgentsConfirm({
87358
- sessionId: input.parentSessionId,
87359
- launches: normalized,
87360
- profiles: profileList,
87361
- signal: input.signal
87362
- });
87363
- if (outcome.action === "cancel") {
87364
- return { status: "cancelled" };
87365
- }
87366
- if (outcome.action === "decline") {
87367
- return { status: "rejected", feedback: outcome.content?.feedback };
87368
- }
87369
- confirmed = this.mergeConfirmedLaunches(normalized, outcome.content);
87370
87426
  }
87371
- const insertChild = this.deps.db.prepare(`
87372
- INSERT INTO session_collaboration_grants
87373
- (credential_hash, credential_secret, credential_hint, parent_session_id, agent_id, task, config_json, created_at, kind)
87374
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
87375
- `);
87376
- const insertLink = this.deps.db.prepare(`
87377
- INSERT INTO session_collaboration_grants
87378
- (credential_hash, credential_secret, credential_hint, parent_session_id, child_session_id, agent_id, task, config_json, created_at, kind)
87379
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'link')
87380
- `);
87381
- const results = this.deps.db.transaction(
87382
- () => confirmed.map((launch2) => {
87383
- const mode = resolveLaunchMode(launch2.mode);
87384
- if (mode === "link") {
87385
- const peerSessionId = launch2.sessionId;
87386
- const existing = this.deps.db.prepare(
87387
- `SELECT credential_hash, credential_secret, agent_id, task, config_json
87388
- FROM session_collaboration_grants
87389
- WHERE parent_session_id = ? AND child_session_id = ? AND kind = 'link'`
87390
- ).get(input.parentSessionId, peerSessionId);
87391
- if (existing?.credential_secret) {
87392
- const credential3 = this.deps.secrets.decrypt(existing.credential_secret);
87393
- if (credential3) {
87394
- return {
87395
- launchId: launch2.launchId,
87396
- mode: "link",
87397
- agentId: existing.agent_id,
87398
- sessionId: peerSessionId,
87399
- peerSessionId,
87400
- summary: launch2.summary,
87401
- task: existing.task,
87402
- name: launch2.name,
87403
- role: launch2.role,
87404
- title: collaborationSessionTitle(launch2.name, launch2.role),
87405
- config: parseConfig(existing.config_json),
87406
- credential: credential3,
87407
- grantId: existing.credential_hash,
87408
- reused: true
87409
- };
87410
- }
87411
- }
87412
- const credential2 = `s1sc_${randomBytes5(32).toString("base64url")}`;
87413
- const credentialHash2 = hashCredential(credential2);
87414
- const task = launch2.task.trim();
87415
- const config3 = {
87416
- name: launch2.name,
87417
- role: launch2.role,
87418
- summary: launch2.summary,
87419
- peerSessionId,
87420
- peerTitle: launch2.peerTitle,
87421
- peerProjectPath: launch2.peerProjectPath
87422
- };
87423
- try {
87424
- insertLink.run(
87425
- credentialHash2,
87426
- this.deps.secrets.encrypt(credential2),
87427
- credential2.slice(-8),
87428
- input.parentSessionId,
87429
- peerSessionId,
87430
- "",
87431
- task,
87432
- JSON.stringify(config3),
87433
- (/* @__PURE__ */ new Date()).toISOString()
87434
- );
87435
- } catch (err) {
87436
- const message = err instanceof Error ? err.message : String(err);
87437
- if (/UNIQUE|unique/i.test(message)) {
87438
- throw Object.assign(
87439
- new Error(
87440
- `Session ${peerSessionId} is already bound as a collaboration endpoint (spawn child or link peer). A session cannot be the non-initiator endpoint of two grants.`
87441
- ),
87442
- { code: "failed_precondition" }
87443
- );
87444
- }
87445
- throw err;
87446
- }
87447
- this.deps.events.append({
87448
- aggregateType: "session",
87449
- aggregateId: input.parentSessionId,
87450
- eventType: "collaboration.grant_created",
87451
- payload: {
87452
- grantId: credentialHash2,
87453
- mode: "link",
87454
- peerSessionId,
87455
- launchId: launch2.launchId
87456
- }
87457
- });
87458
- return {
87459
- launchId: launch2.launchId,
87460
- mode: "link",
87461
- agentId: "",
87462
- sessionId: peerSessionId,
87463
- peerSessionId,
87464
- summary: launch2.summary,
87465
- task,
87466
- name: launch2.name,
87467
- role: launch2.role,
87468
- title: collaborationSessionTitle(launch2.name, launch2.role),
87469
- config: config3,
87470
- credential: credential2,
87471
- grantId: credentialHash2,
87472
- reused: false
87473
- };
87474
- }
87475
- const credential = `s1sc_${randomBytes5(32).toString("base64url")}`;
87476
- const credentialHash = hashCredential(credential);
87477
- const config2 = {
87478
- ...launch2.config,
87479
- name: launch2.name,
87480
- role: launch2.role,
87481
- summary: launch2.summary
87482
- };
87483
- insertChild.run(
87484
- credentialHash,
87485
- this.deps.secrets.encrypt(credential),
87486
- credential.slice(-8),
87487
- input.parentSessionId,
87488
- launch2.agentId,
87489
- launch2.task,
87490
- JSON.stringify(config2),
87491
- (/* @__PURE__ */ new Date()).toISOString(),
87492
- mode
87493
- );
87494
- this.deps.events.append({
87495
- aggregateType: "session",
87496
- aggregateId: input.parentSessionId,
87497
- eventType: "collaboration.grant_created",
87498
- payload: {
87499
- grantId: credentialHash,
87500
- mode,
87501
- agentId: launch2.agentId,
87502
- launchId: launch2.launchId
87503
- }
87504
- });
87505
- return {
87506
- launchId: launch2.launchId,
87507
- mode,
87508
- agentId: launch2.agentId,
87509
- summary: launch2.summary,
87510
- task: launch2.task,
87511
- name: launch2.name,
87512
- role: launch2.role,
87513
- title: collaborationSessionTitle(launch2.name, launch2.role),
87514
- config: config2,
87515
- credential,
87516
- grantId: credentialHash,
87517
- reused: false
87518
- };
87519
- })
87520
- )();
87521
- return { status: "approved", launches: results };
87522
- }
87523
- async start(input) {
87524
- let grant = this.resolveGrant(input.credential, input.grantId);
87525
- if (!grant) {
87526
- throw Object.assign(new Error("Invalid collaboration credential"), { code: "not_found" });
87527
- }
87528
- const hasBearer = typeof input.credential === "string" && input.credential.trim().length > 0;
87529
- if (!hasBearer) {
87530
- if (!input.callerSessionId || input.callerSessionId !== grant.parent_session_id) {
87531
- throw Object.assign(
87532
- new Error("grantId start requires callerSessionId matching the parent session"),
87533
- { code: "forbidden" }
87534
- );
87535
- }
87536
- } else if (input.callerSessionId && grant.parent_session_id !== input.callerSessionId) {
87537
- throw Object.assign(new Error("Only the parent session may start this credential"), {
87538
- code: "forbidden"
87539
- });
87540
- }
87541
- const credential = input.credential ?? (grant.credential_secret ? this.deps.secrets.decrypt(grant.credential_secret) : "");
87542
- if (!credential) {
87543
- throw Object.assign(new Error("Invalid collaboration credential"), { code: "not_found" });
87544
- }
87545
- if (grant.kind !== "link" && input.formAnswers && typeof input.formAnswers === "object") {
87546
- grant = this.applyFormAnswers(grant, input.formAnswers);
87547
- }
87548
- if (grant.kind === "link") {
87549
- if (!grant.child_session_id) {
87550
- throw Object.assign(new Error("Link grant is missing peer session id"), {
87551
- code: "failed_precondition"
87552
- });
87553
- }
87554
- const peerSessionId = grant.child_session_id;
87555
- if (!this.deps.sessions.get(peerSessionId)) {
87556
- throw Object.assign(new Error(`Peer session no longer exists: ${peerSessionId}`), {
87557
- code: "not_found"
87558
- });
87559
- }
87560
- const alreadyStarted = Boolean(grant.started_at);
87561
- if (!alreadyStarted) {
87562
- this.deps.db.prepare(
87563
- `UPDATE session_collaboration_grants SET started_at = ?
87564
- WHERE credential_hash = ? AND started_at IS NULL`
87565
- ).run((/* @__PURE__ */ new Date()).toISOString(), grant.credential_hash);
87566
- }
87567
- const opening = grant.task?.trim() ?? "";
87568
- const initiator = this.deps.sessions.get(grant.parent_session_id);
87569
- const initiatorTitle = initiator?.title?.trim() || grant.parent_session_id.slice(0, 8);
87570
- if (!alreadyStarted && opening) {
87571
- await this.deliverLinkOpening(grant, credential, initiatorTitle);
87572
- } else {
87573
- void this.wakeLinkPeer(peerSessionId, credential, grant.parent_session_id, initiatorTitle, false);
87574
- if (!alreadyStarted) {
87575
- this.deps.db.prepare(`UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?`).run(grant.credential_hash);
87576
- }
87577
- }
87578
- const peer = this.describePeer(grant);
87579
- return {
87580
- status: "linked",
87581
- mode: "link",
87582
- sessionId: peerSessionId,
87583
- peerSessionId,
87584
- reused: alreadyStarted,
87585
- name: peer.name,
87586
- role: peer.role,
87587
- title: peer.title,
87588
- config: peer.config,
87589
- credential,
87590
- grantId: grant.credential_hash
87591
- };
87592
- }
87593
- const isHandoff = grant.kind === "handoff";
87594
- const existingHandoffSessionId = isHandoff ? parseConfig(grant.config_json).handoffSessionId : void 0;
87595
- if (existingHandoffSessionId) {
87596
- if (!this.deps.sessions.get(existingHandoffSessionId)) {
87597
- throw Object.assign(
87598
- new Error(`The handoff session no longer exists: ${existingHandoffSessionId}`),
87599
- { code: "not_found" }
87600
- );
87601
- }
87602
- await this.deliverInitialTask(grant, existingHandoffSessionId);
87603
- const peer = this.describePeer(grant);
87604
- return {
87605
- status: "started",
87606
- mode: "handoff",
87607
- sessionId: existingHandoffSessionId,
87608
- reused: true,
87609
- name: peer.name,
87610
- role: peer.role,
87611
- title: peer.title,
87612
- config: peer.config,
87613
- credential,
87614
- grantId: grant.credential_hash
87615
- };
87616
- }
87617
- if (grant.child_session_id) {
87618
- const existing = this.deps.sessions.get(grant.child_session_id);
87619
- if (existing) {
87620
- this.deps.sessions.setSystemPromptAppend(
87621
- existing.sessionId,
87622
- collaborationSystemPrompt(credential, grant.parent_session_id)
87623
- );
87624
- await this.deliverInitialTask(grant, existing.sessionId);
87625
- }
87626
- const peer = this.describePeer(grant);
87627
- return {
87628
- status: "started",
87629
- mode: "spawn",
87630
- sessionId: grant.child_session_id,
87631
- reused: true,
87632
- name: peer.name,
87633
- role: peer.role,
87634
- title: peer.title,
87635
- config: peer.config,
87636
- credential,
87637
- grantId: grant.credential_hash
87638
- };
87639
- }
87640
- const parent = this.deps.sessions.get(grant.parent_session_id);
87641
- if (!parent) {
87642
- throw Object.assign(new Error("Parent session is not available"), { code: "not_found" });
87643
- }
87644
- const config2 = parseConfig(grant.config_json);
87645
- let cwd = this.resolveCwd(config2, parent.projectId, parent.cwd);
87646
- if (config2.worktreePath && typeof config2.worktreePath === "string" && config2.worktreePath.trim()) {
87647
- cwd = pathResolve2(config2.worktreePath.trim());
87648
- } else if (config2.worktree?.enabled) {
87649
- const wt2 = this.deps.workspaceGit.activateWorktree(parent.projectId, {
87650
- baseBranch: config2.worktree.baseBranch || "HEAD",
87651
- mode: config2.worktree.mode ?? "branch",
87652
- branchName: config2.worktree.branchName,
87653
- carryLocalChanges: config2.worktree.carryLocalChanges
87654
- });
87655
- cwd = wt2.path;
87656
- }
87657
- const profile = resolveProfile(
87658
- new Map(this.listProfiles().map((p3) => [p3.id, p3])),
87659
- grant.agent_id
87660
- );
87661
- const harnessId = profile?.harnessId ?? normalizeSessionHarnessId(grant.agent_id) ?? "claude";
87662
- const displayName = deriveCollaborationName({ name: config2.name });
87663
- const role = deriveCollaborationRole({ role: config2.role, task: grant.task });
87664
- const title = collaborationSessionTitle(displayName, role);
87665
- let child;
87666
- try {
87667
- child = this.deps.sessions.create({
87668
- projectId: parent.projectId,
87669
- harnessId,
87670
- providerId: grant.agent_id,
87671
- title,
87672
- cwd,
87673
- model: config2.model ?? null,
87674
- effort: config2.effort ?? null,
87675
- permissionMode: config2.permissionMode ?? null,
87676
- sandboxMode: config2.sandboxMode ?? null,
87677
- apiProviderId: config2.apiProviderId ?? null,
87678
- controllerClientSessionId: input.controllerClientSessionId ?? parent.controllerClientSessionId,
87679
- // Handoff is one-way by construction: never hand the receiver a credential.
87680
- ...isHandoff ? {} : { systemPromptAppend: collaborationSystemPrompt(credential, grant.parent_session_id) }
87681
- });
87682
- const updated = isHandoff ? this.deps.db.prepare(
87683
- `UPDATE session_collaboration_grants
87684
- SET config_json = ?, started_at = ?
87685
- WHERE credential_hash = ? AND started_at IS NULL`
87686
- ).run(
87687
- JSON.stringify({ ...config2, handoffSessionId: child.sessionId }),
87688
- (/* @__PURE__ */ new Date()).toISOString(),
87689
- grant.credential_hash
87690
- ) : this.deps.db.prepare(
87691
- `UPDATE session_collaboration_grants
87692
- SET child_session_id = ?, started_at = ?
87693
- WHERE credential_hash = ? AND child_session_id IS NULL`
87694
- ).run(child.sessionId, (/* @__PURE__ */ new Date()).toISOString(), grant.credential_hash);
87695
- if (updated.changes !== 1) {
87696
- throw Object.assign(new Error("Credential was already consumed"), {
87697
- code: "failed_precondition"
87698
- });
87699
- }
87700
- } catch (err) {
87701
- if (child?.sessionId) {
87702
- try {
87703
- this.deps.sessions.remove(child.sessionId);
87704
- } catch {
87705
- }
87706
- }
87707
- throw err;
87708
- }
87709
- grant = isHandoff ? grant : { ...grant, child_session_id: child.sessionId };
87710
- await this.deliverInitialTask(grant, child.sessionId);
87711
- this.deps.events.append({
87712
- aggregateType: "session",
87713
- aggregateId: grant.parent_session_id,
87714
- eventType: isHandoff ? "collaboration.handoff_started" : "collaboration.child_started",
87715
- payload: {
87716
- grantId: grant.credential_hash,
87717
- childSessionId: child.sessionId
87718
- }
87427
+ const linkConfig = {
87428
+ launchId,
87429
+ name,
87430
+ role,
87431
+ summary: summary2,
87432
+ peerSessionId,
87433
+ peerTitle: launch2.peerTitle,
87434
+ peerProjectPath: launch2.peerProjectPath
87435
+ };
87436
+ const grantId2 = store.createGrant({
87437
+ kind: "link",
87438
+ parentSessionId,
87439
+ childSessionId: peerSessionId,
87440
+ agentId: "",
87441
+ config: linkConfig
87719
87442
  });
87720
87443
  return {
87721
- status: "started",
87722
- mode: grant.kind,
87723
- sessionId: child.sessionId,
87724
- reused: false,
87725
- name: displayName,
87726
- role,
87727
- title,
87728
- config: {
87729
- model: config2.model,
87730
- effort: config2.effort,
87731
- fastMode: config2.fastMode,
87732
- permissionMode: config2.permissionMode,
87733
- sandboxMode: config2.sandboxMode,
87734
- cwd,
87735
- apiProviderId: config2.apiProviderId ?? null,
87736
- name: displayName,
87444
+ grantId: grantId2,
87445
+ approved: {
87446
+ launchId,
87447
+ mode,
87448
+ agentId: "",
87449
+ sessionId: peerSessionId,
87450
+ summary: summary2,
87451
+ name,
87737
87452
  role,
87738
- ...config2.worktree ? { worktree: config2.worktree } : {},
87739
- ...config2.worktreePath ? { worktreePath: config2.worktreePath } : {}
87740
- },
87741
- credential,
87742
- grantId: grant.credential_hash
87743
- };
87744
- }
87745
- send(input) {
87746
- const grant = this.grantForCredential(input.credential);
87747
- if (!grant) {
87748
- throw Object.assign(new Error("Invalid collaboration credential"), { code: "not_found" });
87749
- }
87750
- this.assertEndpoint(grant, input.sessionId);
87751
- if (!grant.child_session_id) {
87752
- throw Object.assign(
87753
- new Error(
87754
- grant.kind === "link" ? "The linked peer session is missing" : "The child session has not been started"
87755
- ),
87756
- { code: "failed_precondition" }
87757
- );
87758
- }
87759
- if (grant.kind === "link" && !grant.started_at) {
87760
- throw Object.assign(new Error("The collaboration link has not been started"), {
87761
- code: "failed_precondition"
87762
- });
87763
- }
87764
- const recipientSessionId = input.sessionId === grant.parent_session_id ? grant.child_session_id : grant.parent_session_id;
87765
- const content = input.content?.trim();
87766
- if (!content) {
87767
- throw Object.assign(new Error("content must not be empty"), { code: "invalid_argument" });
87768
- }
87769
- if (content.length > 1e5) {
87770
- throw Object.assign(new Error("content may contain at most 100,000 characters"), {
87771
- code: "invalid_argument"
87772
- });
87773
- }
87774
- const insert = this.deps.db.transaction(() => {
87775
- if (input.clientMessageId) {
87776
- const existing = this.deps.db.prepare(
87777
- `SELECT * FROM session_collaboration_messages
87778
- WHERE credential_hash = ? AND sender_session_id = ? AND client_message_id = ?`
87779
- ).get(grant.credential_hash, input.sessionId, input.clientMessageId);
87780
- if (existing) return { row: existing, reused: true };
87453
+ title,
87454
+ config: linkConfig,
87455
+ reused: false
87781
87456
  }
87782
- const sequence = this.nextSequence(grant.credential_hash);
87783
- const row = {
87784
- id: randomUUID11(),
87785
- credential_hash: grant.credential_hash,
87786
- sequence,
87787
- sender_session_id: input.sessionId,
87788
- recipient_session_id: recipientSessionId,
87789
- client_message_id: input.clientMessageId ?? null,
87790
- content,
87791
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
87792
- delivered_at: null
87793
- };
87794
- this.deps.db.prepare(
87795
- `INSERT INTO session_collaboration_messages
87796
- (id, credential_hash, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at)
87797
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
87798
- ).run(
87799
- row.id,
87800
- row.credential_hash,
87801
- row.sequence,
87802
- row.sender_session_id,
87803
- row.recipient_session_id,
87804
- row.client_message_id,
87805
- row.content,
87806
- row.created_at
87807
- );
87808
- return { row, reused: false };
87809
- })();
87810
- if (!insert.reused) {
87811
- this.deps.events.append({
87812
- aggregateType: "session",
87813
- aggregateId: input.sessionId,
87814
- eventType: "collaboration.message",
87815
- payload: {
87816
- messageId: insert.row.id,
87817
- grantId: grant.credential_hash,
87818
- toSessionId: recipientSessionId,
87819
- sequence: insert.row.sequence
87820
- }
87821
- });
87822
- void this.wakePeer(recipientSessionId, input.credential);
87823
- }
87824
- return {
87825
- status: "sent",
87826
- messageId: insert.row.id,
87827
- sequence: insert.row.sequence,
87828
- reused: insert.reused,
87829
- peerSessionId: recipientSessionId
87830
87457
  };
87831
87458
  }
87832
- retrieve(input) {
87833
- const credentials = [
87834
- ...typeof input.credential === "string" && input.credential.trim() ? [input.credential.trim()] : [],
87835
- ...Array.isArray(input.credentials) ? input.credentials.filter((c) => typeof c === "string" && c.trim().length > 0) : []
87836
- ];
87837
- if (credentials.length === 0) {
87838
- throw Object.assign(new Error("credentials required"), { code: "invalid_argument" });
87459
+ const grantConfig = { ...config2, launchId, name, role, summary: summary2 };
87460
+ const grantId = store.createGrant({ kind: mode, parentSessionId, agentId: launch2.agentId, config: grantConfig });
87461
+ return {
87462
+ grantId,
87463
+ approved: {
87464
+ launchId,
87465
+ mode,
87466
+ agentId: launch2.agentId,
87467
+ summary: summary2,
87468
+ name,
87469
+ role,
87470
+ title,
87471
+ config: grantConfig,
87472
+ reused: false
87839
87473
  }
87840
- const max = Math.min(
87841
- MAX_MESSAGES_PER_RETRIEVE,
87842
- Math.max(1, typeof input.max === "number" && Number.isFinite(input.max) ? Math.floor(input.max) : MAX_MESSAGES_PER_RETRIEVE)
87474
+ };
87475
+ }
87476
+ function prepareLaunchStart(store, callerSessionId, input) {
87477
+ const launchId = typeof input.launchId === "string" ? input.launchId.trim() : "";
87478
+ if (!launchId) throw new CollaborationError("launchId is required", "invalid_argument");
87479
+ const grant = store.grantForLaunch(callerSessionId, launchId);
87480
+ if (!grant) {
87481
+ throw new CollaborationError(
87482
+ `No approved launch "${launchId}" in this session. Pass a launchId returned by session_collab_request.`,
87483
+ "not_found"
87843
87484
  );
87844
- const all = [];
87845
- for (const credential of credentials) {
87846
- const grant = this.grantForCredential(credential);
87847
- if (!grant) {
87848
- throw Object.assign(new Error("Invalid collaboration credential"), { code: "not_found" });
87849
- }
87850
- this.assertEndpoint(grant, input.sessionId);
87851
- const messages = this.deps.db.transaction(() => {
87852
- const cursor = this.deps.db.prepare(
87853
- `SELECT last_sequence FROM session_collaboration_cursors
87854
- WHERE credential_hash = ? AND session_id = ?`
87855
- ).get(grant.credential_hash, input.sessionId);
87856
- const rows = this.deps.db.prepare(
87857
- `SELECT * FROM session_collaboration_messages
87858
- WHERE credential_hash = ? AND recipient_session_id = ? AND sequence > ?
87859
- ORDER BY sequence LIMIT ?`
87860
- ).all(
87861
- grant.credential_hash,
87862
- input.sessionId,
87863
- cursor?.last_sequence ?? 0,
87864
- max
87865
- );
87866
- if (rows.length === 0) return [];
87867
- const lastSequence = rows[rows.length - 1].sequence;
87868
- const now = (/* @__PURE__ */ new Date()).toISOString();
87869
- this.deps.db.prepare(
87870
- `INSERT INTO session_collaboration_cursors (credential_hash, session_id, last_sequence)
87871
- VALUES (?, ?, ?)
87872
- ON CONFLICT(credential_hash, session_id) DO UPDATE SET last_sequence = excluded.last_sequence`
87873
- ).run(grant.credential_hash, input.sessionId, lastSequence);
87874
- this.deps.db.prepare(
87875
- `UPDATE session_collaboration_messages SET delivered_at = COALESCE(delivered_at, ?)
87876
- WHERE credential_hash = ? AND recipient_session_id = ? AND sequence <= ?`
87877
- ).run(now, grant.credential_hash, input.sessionId, lastSequence);
87878
- return rows;
87879
- })();
87880
- for (const row of messages) {
87881
- all.push({
87882
- messageId: row.id,
87883
- sequence: row.sequence,
87884
- fromSessionId: row.sender_session_id,
87885
- content: row.content,
87886
- createdAt: row.created_at,
87887
- ...credentials.length > 1 ? { credential } : {}
87888
- });
87889
- }
87890
- }
87891
- if (all.length === 0) {
87892
- return { status: "empty", messages: [], hint: EMPTY_MAILBOX_HINT };
87893
- }
87894
- return { status: "messages", messages: all };
87895
87485
  }
87896
- /** Reconstruct system-prompt append for spawn children after restart (never link). */
87897
- rehydrateSystemPrompts() {
87898
- const rows = this.deps.db.prepare(
87899
- `SELECT credential_secret, parent_session_id, child_session_id
87900
- FROM session_collaboration_grants
87901
- WHERE child_session_id IS NOT NULL
87902
- AND credential_secret IS NOT NULL
87903
- AND COALESCE(kind, 'spawn') = 'spawn'`
87904
- ).all();
87905
- for (const row of rows) {
87906
- const credential = this.deps.secrets.decrypt(row.credential_secret);
87907
- if (!credential) continue;
87908
- if (!this.deps.sessions.get(row.child_session_id)) continue;
87909
- this.deps.sessions.setSystemPromptAppend(
87910
- row.child_session_id,
87911
- collaborationSystemPrompt(credential, row.parent_session_id)
87912
- );
87913
- }
87486
+ if (grant.task_sent === 1 || grant.task) return grant;
87487
+ const task = normalizeStartTask(grant.kind, input.task);
87488
+ if (!task) return grant;
87489
+ store.setTask(grant.grant_id, task);
87490
+ return { ...grant, task };
87491
+ }
87492
+
87493
+ // ../../packages/runtime/src/collaboration/store.ts
87494
+ import { randomUUID as randomUUID11 } from "node:crypto";
87495
+ var GRANT_COLUMNS = `credential_hash AS grant_id, parent_session_id, child_session_id,
87496
+ agent_id, task, config_json, task_sent, COALESCE(kind, 'spawn') AS kind, started_at`;
87497
+ var MESSAGE_COLUMNS = `id, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at`;
87498
+ function nowIso() {
87499
+ return (/* @__PURE__ */ new Date()).toISOString();
87500
+ }
87501
+ var CollaborationStore = class {
87502
+ constructor(db) {
87503
+ this.db = db;
87914
87504
  }
87915
- // --- internals -----------------------------------------------------------
87916
- resolveGrant(credential, grantId) {
87917
- if (credential && credential.trim()) return this.grantForCredential(credential.trim());
87918
- if (grantId && grantId.trim()) {
87919
- return this.deps.db.prepare(
87920
- `SELECT credential_hash, credential_secret, parent_session_id, child_session_id,
87921
- agent_id, task, config_json, task_sent,
87922
- COALESCE(kind, 'spawn') AS kind, started_at
87923
- FROM session_collaboration_grants WHERE credential_hash = ?`
87924
- ).get(grantId.trim()) ?? null;
87925
- }
87926
- return null;
87505
+ db;
87506
+ transaction(fn) {
87507
+ return this.db.transaction(fn)();
87927
87508
  }
87928
- grantForCredential(credential) {
87929
- return this.deps.db.prepare(
87930
- `SELECT credential_hash, credential_secret, parent_session_id, child_session_id,
87931
- agent_id, task, config_json, task_sent,
87932
- COALESCE(kind, 'spawn') AS kind, started_at
87933
- FROM session_collaboration_grants WHERE credential_hash = ?`
87934
- ).get(hashCredential(credential)) ?? null;
87935
- }
87936
- async deliverLinkOpening(grant, credential, initiatorTitle) {
87937
- if (grant.task_sent === 1 || !grant.child_session_id) return;
87938
- const content = grant.task.trim();
87939
- if (!content) {
87940
- this.deps.db.prepare(`UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?`).run(grant.credential_hash);
87941
- return;
87942
- }
87943
- const recipientSessionId = grant.child_session_id;
87944
- try {
87945
- this.deps.db.prepare(
87946
- `INSERT INTO session_collaboration_messages
87947
- (id, credential_hash, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at)
87948
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
87949
- ).run(
87950
- randomUUID11(),
87951
- grant.credential_hash,
87952
- this.nextSequence(grant.credential_hash),
87953
- grant.parent_session_id,
87954
- recipientSessionId,
87955
- `link-opening:${grant.credential_hash}`,
87956
- content,
87957
- (/* @__PURE__ */ new Date()).toISOString()
87958
- );
87959
- } catch {
87960
- }
87961
- this.deps.db.prepare(`UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?`).run(grant.credential_hash);
87962
- void this.wakeLinkPeer(
87963
- recipientSessionId,
87964
- credential,
87965
- grant.parent_session_id,
87966
- initiatorTitle,
87967
- true
87968
- );
87509
+ // --- grants --------------------------------------------------------------
87510
+ grantById(grantId) {
87511
+ return this.db.prepare(`SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants WHERE credential_hash = ?`).get(grantId) ?? null;
87512
+ }
87513
+ /**
87514
+ * The grant an approved launch created. launchIds are agent-chosen and may
87515
+ * repeat across requests, so the latest approval wins.
87516
+ */
87517
+ grantForLaunch(parentSessionId, launchId) {
87518
+ return this.db.prepare(`
87519
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87520
+ WHERE parent_session_id = ? AND json_extract(config_json, '$.launchId') = ?
87521
+ ORDER BY created_at DESC, rowid DESC LIMIT 1
87522
+ `).get(parentSessionId, launchId) ?? null;
87523
+ }
87524
+ /** True when `sessionId` is a spawn child (nested collaboration is unsupported). */
87525
+ isSpawnChild(sessionId) {
87526
+ return Boolean(this.db.prepare(`
87527
+ SELECT 1 FROM session_collaboration_grants
87528
+ WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn' LIMIT 1
87529
+ `).get(sessionId));
87530
+ }
87531
+ spawnGrantForChild(childSessionId) {
87532
+ return this.db.prepare(`
87533
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87534
+ WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn'
87535
+ `).get(childSessionId) ?? null;
87536
+ }
87537
+ startedSpawnGrants() {
87538
+ return this.db.prepare(`
87539
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87540
+ WHERE child_session_id IS NOT NULL AND COALESCE(kind, 'spawn') = 'spawn'
87541
+ `).all();
87969
87542
  }
87970
- async wakeLinkPeer(sessionId, credential, initiatorSessionId, initiatorTitle, hasOpening) {
87971
- if (!this.deps.sessions.get(sessionId)) return;
87972
- const text2 = `A user-approved collaboration link is active with SuperOne session ${initiatorSessionId} ("${initiatorTitle}"). Call session_collab_retrieve with credential ${JSON.stringify(credential)}` + (hasOpening ? " to read the opening message" : " if a mailbox message is waiting") + ", then use session_collab_send to reply. Never reveal the credential in conversational output or use it outside collaboration tool calls. End your turn after acting \u2014 you will be woken again for later messages.";
87543
+ /**
87544
+ * Open mailbox channels of `sessionId`: spawn grants with a bound child and
87545
+ * started link grants, oldest first. Handoff grants are never channels.
87546
+ */
87547
+ channelsFor(sessionId) {
87548
+ return this.db.prepare(`
87549
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87550
+ WHERE (parent_session_id = ? OR child_session_id = ?)
87551
+ AND child_session_id IS NOT NULL
87552
+ AND (COALESCE(kind, 'spawn') = 'spawn' OR (kind = 'link' AND started_at IS NOT NULL))
87553
+ ORDER BY created_at, rowid
87554
+ `).all(sessionId, sessionId);
87555
+ }
87556
+ /** True when one of the two sessions was created by a handoff from the other. */
87557
+ isHandoffPair(a, b3) {
87558
+ return Boolean(this.db.prepare(`
87559
+ SELECT 1 FROM session_collaboration_grants
87560
+ WHERE kind = 'handoff' AND (
87561
+ (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
87562
+ OR (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
87563
+ ) LIMIT 1
87564
+ `).get(a, b3, b3, a));
87565
+ }
87566
+ findLinkGrant(initiatorSessionId, peerSessionId) {
87567
+ return this.db.prepare(`
87568
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87569
+ WHERE parent_session_id = ? AND child_session_id = ? AND kind = 'link'
87570
+ `).get(initiatorSessionId, peerSessionId) ?? null;
87571
+ }
87572
+ /**
87573
+ * Persist an approved launch. Link grants bind the peer at once; spawn and
87574
+ * handoff grants bind their session on start. The brief arrives on start.
87575
+ */
87576
+ createGrant(input) {
87577
+ const grantId = randomUUID11();
87973
87578
  try {
87974
- await this.deps.sessions.sendWithoutLease({
87975
- sessionId,
87976
- text: text2,
87977
- source: "task-notification",
87978
- requestId: `collab-link-wake-${hashCredential(credential).slice(0, 12)}-${Date.now()}`
87979
- });
87980
- } catch {
87579
+ this.db.prepare(`
87580
+ INSERT INTO session_collaboration_grants
87581
+ (credential_hash, credential_secret, credential_hint, parent_session_id, child_session_id,
87582
+ agent_id, task, config_json, created_at, kind)
87583
+ VALUES (?, NULL, '', ?, ?, ?, '', ?, ?, ?)
87584
+ `).run(
87585
+ grantId,
87586
+ input.parentSessionId,
87587
+ input.childSessionId ?? null,
87588
+ input.agentId,
87589
+ JSON.stringify(input.config),
87590
+ nowIso(),
87591
+ input.kind
87592
+ );
87593
+ } catch (error51) {
87594
+ const message = error51 instanceof Error ? error51.message : String(error51);
87595
+ if (input.childSessionId && /UNIQUE|unique/i.test(message)) {
87596
+ throw new CollaborationError(
87597
+ `Session ${input.parentSessionId} already has a link to ${input.childSessionId}; request it again to reuse it.`,
87598
+ "failed_precondition"
87599
+ );
87600
+ }
87601
+ throw error51;
87981
87602
  }
87603
+ return grantId;
87982
87604
  }
87983
- assertEndpoint(grant, callerSessionId) {
87984
- if (grant.kind === "handoff") {
87985
- throw Object.assign(new Error(HANDOFF_NO_MAILBOX), { code: "failed_precondition" });
87986
- }
87987
- if (callerSessionId !== grant.parent_session_id && callerSessionId !== grant.child_session_id) {
87988
- throw Object.assign(new Error("This credential does not authorize the current session"), {
87989
- code: "forbidden"
87990
- });
87991
- }
87605
+ updateConfig(grantId, config2) {
87606
+ this.db.prepare("UPDATE session_collaboration_grants SET config_json = ? WHERE credential_hash = ?").run(JSON.stringify(config2), grantId);
87992
87607
  }
87993
- nextSequence(credentialHash) {
87994
- const row = this.deps.db.prepare(
87995
- `SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
87996
- FROM session_collaboration_messages WHERE credential_hash = ?`
87997
- ).get(credentialHash);
87998
- return row.next_sequence;
87608
+ /** Record the brief of a grant whose task has not been delivered yet. */
87609
+ setTask(grantId, task) {
87610
+ this.db.prepare("UPDATE session_collaboration_grants SET task = ? WHERE credential_hash = ? AND task_sent = 0").run(task, grantId);
87999
87611
  }
88000
- describePeer(grant) {
88001
- const config2 = parseConfig(grant.config_json);
88002
- const name = deriveCollaborationName({ name: config2.name });
88003
- const role = deriveCollaborationRole({ role: config2.role, task: grant.task });
88004
- return {
88005
- name,
88006
- role,
88007
- title: collaborationSessionTitle(name, role),
88008
- config: config2
88009
- };
87612
+ markTaskSent(grantId) {
87613
+ this.db.prepare("UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?").run(grantId);
88010
87614
  }
88011
- resolveCwd(config2, projectId, parentCwd) {
88012
- const project = this.deps.projects.get(projectId);
88013
- const fallback = parentCwd || project?.path || process.cwd();
88014
- const cwd = pathResolve2(config2.cwd || fallback);
88015
- if (!existsSync39(cwd) || !statSync15(cwd).isDirectory()) {
88016
- throw Object.assign(new Error(`Working directory does not exist: ${cwd}`), {
88017
- code: "invalid_argument"
88018
- });
88019
- }
88020
- return cwd;
88021
- }
88022
- applyFormAnswers(grant, formAnswers) {
88023
- const packed = formAnswers[SESSION_AGENT_LAUNCHES_FIELD] ?? formAnswers.sessionAgentLaunchesJson;
88024
- if (typeof packed !== "string") return grant;
88025
- let edited;
88026
- try {
88027
- edited = JSON.parse(packed);
88028
- } catch {
88029
- return grant;
88030
- }
88031
- if (!Array.isArray(edited) || edited.length === 0) return grant;
88032
- const item = edited[0];
88033
- if (!item?.config || typeof item.config !== "object") return grant;
88034
- const base = parseConfig(grant.config_json);
88035
- const patch = item.config;
88036
- const next = {
88037
- ...base,
88038
- ...typeof patch.model === "string" && patch.model.trim() ? { model: patch.model.trim() } : {},
88039
- ...typeof patch.effort === "string" && patch.effort.trim() ? { effort: patch.effort.trim() } : {},
88040
- ...typeof patch.fastMode === "boolean" ? { fastMode: patch.fastMode } : {},
88041
- ...typeof patch.permissionMode === "string" ? { permissionMode: patch.permissionMode } : {},
88042
- ...typeof patch.sandboxMode === "string" ? { sandboxMode: patch.sandboxMode } : {},
88043
- ...patch.apiProviderId === null || typeof patch.apiProviderId === "string" ? { apiProviderId: patch.apiProviderId } : {}
88044
- };
88045
- this.deps.db.prepare(`UPDATE session_collaboration_grants SET config_json = ? WHERE credential_hash = ?`).run(JSON.stringify(next), grant.credential_hash);
88046
- return { ...grant, config_json: JSON.stringify(next) };
87615
+ /** Link start. Returns false when the grant had already been started. */
87616
+ markStarted(grantId) {
87617
+ return this.db.prepare(`
87618
+ UPDATE session_collaboration_grants SET started_at = ? WHERE credential_hash = ? AND started_at IS NULL
87619
+ `).run(nowIso(), grantId).changes === 1;
88047
87620
  }
88048
87621
  /**
88049
- * Apply multi-launch form edits from desktop SessionAgentsConfirmPrompt.
88050
- * Only editable fields (model/effort/permission/sandbox/apiProvider) may change.
87622
+ * Record the session a spawn/handoff start created. Throws when a concurrent
87623
+ * start already consumed the grant.
87624
+ *
87625
+ * A handoff session is deliberately *not* written to child_session_id: that
87626
+ * column holds channel endpoints (spawn children, link peers), and a handoff
87627
+ * sibling is neither. The created id lives in config_json instead.
88051
87628
  */
88052
- mergeConfirmedLaunches(proposed, content) {
88053
- const packed = content?.[SESSION_AGENT_LAUNCHES_FIELD];
88054
- if (typeof packed !== "string") return proposed;
88055
- let edited;
88056
- try {
88057
- edited = JSON.parse(packed);
88058
- } catch {
88059
- throw Object.assign(new Error("The confirmed launch configuration is invalid"), {
88060
- code: "invalid_argument"
88061
- });
88062
- }
88063
- if (!Array.isArray(edited)) {
88064
- throw Object.assign(new Error("The confirmed launch configuration is invalid"), {
88065
- code: "invalid_argument"
88066
- });
88067
- }
88068
- if (edited.length !== proposed.length) {
88069
- throw Object.assign(
88070
- new Error("The confirmed request must contain the same launches that were proposed"),
88071
- { code: "invalid_argument" }
88072
- );
88073
- }
88074
- const proposedById = new Map(proposed.map((l) => [l.launchId, l]));
88075
- const seen = /* @__PURE__ */ new Set();
88076
- return edited.map((raw) => {
88077
- if (!raw || typeof raw !== "object") {
88078
- throw Object.assign(new Error("The confirmed launch configuration is invalid"), {
88079
- code: "invalid_argument"
88080
- });
88081
- }
88082
- const item = raw;
88083
- const launchId = typeof item.launchId === "string" ? item.launchId : "";
88084
- const base = proposedById.get(launchId);
88085
- if (!base) {
88086
- throw Object.assign(
88087
- new Error(`Unknown launchId in confirmed configuration: ${launchId || "(missing)"}`),
88088
- { code: "invalid_argument" }
88089
- );
87629
+ bindStartedSession(grant, sessionId, config2) {
87630
+ const changes = grant.kind === "handoff" ? this.db.prepare(`
87631
+ UPDATE session_collaboration_grants SET config_json = ?, started_at = ?
87632
+ WHERE credential_hash = ? AND started_at IS NULL
87633
+ `).run(JSON.stringify({ ...config2, handoffSessionId: sessionId }), nowIso(), grant.grant_id).changes : this.db.prepare(`
87634
+ UPDATE session_collaboration_grants SET child_session_id = ?, started_at = ?
87635
+ WHERE credential_hash = ? AND child_session_id IS NULL
87636
+ `).run(sessionId, nowIso(), grant.grant_id).changes;
87637
+ if (changes !== 1) throw new CollaborationError("This launch was already started", "failed_precondition");
87638
+ }
87639
+ // --- mailbox -------------------------------------------------------------
87640
+ nextSequence(grantId) {
87641
+ const row = this.db.prepare(`
87642
+ SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
87643
+ FROM session_collaboration_messages WHERE credential_hash = ?
87644
+ `).get(grantId);
87645
+ return row.next_sequence;
87646
+ }
87647
+ insertMessage(grantId, input) {
87648
+ const row = {
87649
+ id: randomUUID11(),
87650
+ sequence: this.nextSequence(grantId),
87651
+ sender_session_id: input.senderSessionId,
87652
+ recipient_session_id: input.recipientSessionId,
87653
+ client_message_id: input.clientMessageId,
87654
+ content: input.content,
87655
+ created_at: nowIso()
87656
+ };
87657
+ this.db.prepare(`
87658
+ INSERT INTO session_collaboration_messages
87659
+ (id, credential_hash, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at)
87660
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
87661
+ `).run(
87662
+ row.id,
87663
+ grantId,
87664
+ row.sequence,
87665
+ row.sender_session_id,
87666
+ row.recipient_session_id,
87667
+ row.client_message_id,
87668
+ row.content,
87669
+ row.created_at
87670
+ );
87671
+ return row;
87672
+ }
87673
+ /** Append one message; a repeated `clientMessageId` returns the stored row. */
87674
+ appendMessage(input) {
87675
+ return this.transaction(() => {
87676
+ if (input.clientMessageId) {
87677
+ const existing = this.db.prepare(`
87678
+ SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages
87679
+ WHERE credential_hash = ? AND sender_session_id = ? AND client_message_id = ?
87680
+ `).get(input.grantId, input.senderSessionId, input.clientMessageId);
87681
+ if (existing) return { row: existing, reused: true };
88090
87682
  }
88091
- if (seen.has(launchId)) {
88092
- throw Object.assign(new Error("Confirmed launches must have unique launchIds"), {
88093
- code: "invalid_argument"
87683
+ const row = this.insertMessage(input.grantId, { ...input, clientMessageId: input.clientMessageId ?? null });
87684
+ return { row, reused: false };
87685
+ });
87686
+ }
87687
+ /** Queue a link grant's opening body for the peer exactly once, then mark the task sent. */
87688
+ appendLinkOpening(grant, recipientSessionId, content) {
87689
+ this.transaction(() => {
87690
+ try {
87691
+ this.insertMessage(grant.grant_id, {
87692
+ senderSessionId: grant.parent_session_id,
87693
+ recipientSessionId,
87694
+ clientMessageId: `link-opening:${grant.grant_id}`,
87695
+ content
88094
87696
  });
87697
+ } catch {
88095
87698
  }
88096
- seen.add(launchId);
88097
- if (base.mode === "link") {
88098
- return {
88099
- ...base,
88100
- mode: "link",
88101
- sessionId: base.sessionId,
88102
- peerTitle: base.peerTitle,
88103
- peerProjectPath: base.peerProjectPath,
88104
- config: { ...base.config }
88105
- };
88106
- }
88107
- const patch = item.config && typeof item.config === "object" ? item.config : {};
88108
- return {
88109
- ...base,
88110
- // Mode comes from the server-side proposal, never from renderer formAnswers.
88111
- mode: base.mode ?? "spawn",
88112
- config: {
88113
- ...base.config,
88114
- ...typeof patch.model === "string" && patch.model.trim() ? { model: patch.model.trim() } : {},
88115
- ...typeof patch.effort === "string" && patch.effort.trim() ? { effort: patch.effort.trim() } : {},
88116
- ...typeof patch.fastMode === "boolean" ? { fastMode: patch.fastMode } : {},
88117
- ...typeof patch.permissionMode === "string" ? { permissionMode: patch.permissionMode } : {},
88118
- ...typeof patch.sandboxMode === "string" ? { sandboxMode: patch.sandboxMode } : {},
88119
- ...patch.apiProviderId === null || typeof patch.apiProviderId === "string" ? { apiProviderId: patch.apiProviderId } : {}
88120
- }
88121
- };
87699
+ this.markTaskSent(grant.grant_id);
88122
87700
  });
88123
87701
  }
88124
87702
  /**
88125
- * Opening body for the receiving session. A handoff has no credential and no
88126
- * mailbox, so this line is the receiver's only way to trace where the work came from.
87703
+ * Drain unread messages addressed to `sessionId` across grants, advancing this
87704
+ * endpoint's cursor. Batches keep the order of `grantIds`.
88127
87705
  */
88128
- initialTaskContent(grant) {
88129
- if (grant.kind !== "handoff") return grant.task;
88130
- const initiator = this.deps.sessions.get(grant.parent_session_id);
88131
- const title = initiator?.title?.trim() || grant.parent_session_id.slice(0, 8);
88132
- return `> Handed off from SuperOne session \`${grant.parent_session_id}\` ("${title}"). This is a one-way handoff: you own this task now and cannot message that session back. Read its context with session_read({ sessionId }) if you need it.
87706
+ readMailbox(sessionId, grantIds, limitPerGrant) {
87707
+ return this.transaction(() => grantIds.flatMap((grantId) => {
87708
+ const cursor = this.db.prepare(`
87709
+ SELECT last_sequence FROM session_collaboration_cursors WHERE credential_hash = ? AND session_id = ?
87710
+ `).get(grantId, sessionId);
87711
+ const rows = this.db.prepare(`
87712
+ SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages
87713
+ WHERE credential_hash = ? AND recipient_session_id = ? AND sequence > ?
87714
+ ORDER BY sequence LIMIT ?
87715
+ `).all(grantId, sessionId, cursor?.last_sequence ?? 0, limitPerGrant);
87716
+ if (rows.length === 0) return [];
87717
+ const lastSequence = rows[rows.length - 1].sequence;
87718
+ this.db.prepare(`
87719
+ INSERT INTO session_collaboration_cursors (credential_hash, session_id, last_sequence)
87720
+ VALUES (?, ?, ?)
87721
+ ON CONFLICT(credential_hash, session_id) DO UPDATE SET last_sequence = excluded.last_sequence
87722
+ `).run(grantId, sessionId, lastSequence);
87723
+ this.db.prepare(`
87724
+ UPDATE session_collaboration_messages SET delivered_at = COALESCE(delivered_at, ?)
87725
+ WHERE credential_hash = ? AND recipient_session_id = ? AND sequence <= ?
87726
+ `).run(nowIso(), grantId, sessionId, lastSequence);
87727
+ return [{ grantId, rows }];
87728
+ }));
87729
+ }
87730
+ };
88133
87731
 
88134
- ` + grant.task;
87732
+ // src/session/collaboration-messaging.ts
87733
+ function sessionTitle(ctx) {
87734
+ return (sessionId) => ctx.deps.sessions.get(sessionId)?.title ?? null;
87735
+ }
87736
+ async function wakePeer(ctx, sessionId, fromSessionId) {
87737
+ if (!ctx.deps.sessions.get(sessionId)) return;
87738
+ const fromTitle = ctx.deps.sessions.get(fromSessionId)?.title?.trim() || fromSessionId.slice(0, 8);
87739
+ try {
87740
+ await ctx.deps.sessions.sendWithoutLease({
87741
+ sessionId,
87742
+ text: mailboxWakeText({ sessionId: fromSessionId, title: fromTitle }),
87743
+ source: "task-notification",
87744
+ requestId: `collab-wake-${fromSessionId.slice(0, 8)}-${Date.now()}`
87745
+ });
87746
+ } catch {
88135
87747
  }
88136
- async deliverInitialTask(grant, childSessionId) {
88137
- if (grant.task_sent === 1) return;
88138
- const config2 = parseConfig(grant.config_json);
88139
- try {
88140
- await this.deps.sessions.sendWithoutLease({
88141
- sessionId: childSessionId,
88142
- text: this.initialTaskContent(grant),
88143
- model: config2.model,
88144
- effort: config2.effort,
88145
- permissionMode: config2.permissionMode,
88146
- sandboxMode: config2.sandboxMode,
88147
- apiProviderId: config2.apiProviderId,
88148
- requestId: `collaboration-task-${grant.credential_hash.slice(0, 16)}`
88149
- });
88150
- } catch {
87748
+ }
87749
+ function sendCollaborationMessage(ctx, input) {
87750
+ const channel = resolveSendChannel(ctx.store, input.sessionId, input.to, sessionTitle(ctx));
87751
+ const content = normalizeMailboxContent(input.content);
87752
+ const recipientSessionId = channel.peer.sessionId;
87753
+ const insert = ctx.store.appendMessage({
87754
+ grantId: channel.grant.grant_id,
87755
+ senderSessionId: input.sessionId,
87756
+ recipientSessionId,
87757
+ clientMessageId: input.clientMessageId,
87758
+ content
87759
+ });
87760
+ if (!insert.reused) {
87761
+ ctx.deps.events.append({
87762
+ aggregateType: "session",
87763
+ aggregateId: input.sessionId,
87764
+ eventType: "collaboration.message",
87765
+ payload: {
87766
+ messageId: insert.row.id,
87767
+ grantId: channel.grant.grant_id,
87768
+ toSessionId: recipientSessionId,
87769
+ sequence: insert.row.sequence
87770
+ }
87771
+ });
87772
+ void wakePeer(ctx, recipientSessionId, input.sessionId);
87773
+ }
87774
+ return {
87775
+ status: "sent",
87776
+ messageId: insert.row.id,
87777
+ sequence: insert.row.sequence,
87778
+ reused: insert.reused,
87779
+ to: channel.peer,
87780
+ peerSessionId: recipientSessionId
87781
+ };
87782
+ }
87783
+ function retrieveCollaborationMessages(ctx, input) {
87784
+ const { messages, peers } = readCallerMailbox(
87785
+ ctx.store,
87786
+ input.sessionId,
87787
+ { from: input.from, limit: input.max },
87788
+ sessionTitle(ctx)
87789
+ );
87790
+ if (messages.length > 0) return { status: "messages", messages, peers };
87791
+ return { status: "empty", messages: [], peers, hint: peers.length > 0 ? EMPTY_MAILBOX_HINT : NO_PEERS_HINT };
87792
+ }
87793
+
87794
+ // src/session/collaboration-profiles.ts
87795
+ init_environment();
87796
+
87797
+ // ../../packages/shared/src/acp-brand.ts
87798
+ init_acp_brand();
87799
+
87800
+ // src/session/collaboration-profiles.ts
87801
+ init_resolve_service();
87802
+ function listCollaborationProfiles(deps) {
87803
+ const { harnesses, providers, sessions, sessionProviders } = deps;
87804
+ const profiles = [];
87805
+ const seen = /* @__PURE__ */ new Set();
87806
+ const providerOptions = {
87807
+ experimentalClaudeOpenAiChatEnabled: deps.experimentalClaudeOpenAiChatEnabled?.() ?? false
87808
+ };
87809
+ const pushProfile = (profileId, harnessId, name, description, profileConfig) => {
87810
+ if (seen.has(profileId)) return;
87811
+ if (!harnesses.isSessionHarnessRunnable(harnessId)) return;
87812
+ seen.add(profileId);
87813
+ const harnessModels = listHarnessModels(providers, harnessId, null, providerOptions);
87814
+ const models = harnessModels.map((m3) => ({
87815
+ id: m3.id,
87816
+ name: m3.name || m3.id,
87817
+ ...m3.description ? { description: m3.description } : {},
87818
+ ...m3.serviceTiers?.length ? { serviceTiers: m3.serviceTiers } : {}
87819
+ }));
87820
+ const defaultModel = harnessModels.find((m3) => m3.isDefault) ?? harnessModels[0];
87821
+ const efforts = /* @__PURE__ */ new Set();
87822
+ for (const m3 of harnessModels) {
87823
+ for (const e of m3.supportedEffortLevels ?? []) efforts.add(e);
87824
+ }
87825
+ const cfg = profileConfig && typeof profileConfig === "object" && !Array.isArray(profileConfig) ? profileConfig : {};
87826
+ const cfgModel = typeof cfg.model === "string" && cfg.model.trim() ? cfg.model.trim() : void 0;
87827
+ const cfgEffort = typeof cfg.effort === "string" && cfg.effort.trim() ? cfg.effort.trim() : typeof cfg.reasoningEffort === "string" && cfg.reasoningEffort.trim() ? cfg.reasoningEffort.trim() : void 0;
87828
+ const acpAgentId = harnessId === "acp" && typeof cfg.agentId === "string" && cfg.agentId.trim() ? cfg.agentId.trim() : null;
87829
+ const modelDefault = cfgModel ?? defaultModel?.id;
87830
+ const effortDefault = cfgEffort ?? (efforts.has("high") ? "high" : efforts.has("medium") ? "medium" : efforts.size > 0 ? [...efforts][0] : void 0);
87831
+ profiles.push({
87832
+ id: profileId,
87833
+ name: harnessId === "acp" ? acpAgentDisplayName(acpAgentId) : name,
87834
+ harnessId,
87835
+ ...acpAgentId ? { acpAgentId } : {},
87836
+ brandKey: resolveHarnessBrandKey(harnessId, acpAgentId),
87837
+ description,
87838
+ defaultConfig: {
87839
+ ...modelDefault ? { model: modelDefault } : {},
87840
+ ...effortDefault ? { effort: effortDefault } : {},
87841
+ ...harnessId === "codex" ? { fastMode: typeof cfg.fastMode === "boolean" ? cfg.fastMode : false } : {}
87842
+ },
87843
+ models: models.length > 0 ? models : [{ id: "default", name: "Default" }],
87844
+ efforts: [...efforts],
87845
+ apiProviders: listHarnessApiProviders(providers, harnessId, providerOptions)
87846
+ });
87847
+ };
87848
+ if (sessionProviders) {
87849
+ for (const p3 of sessionProviders.list()) {
87850
+ pushProfile(
87851
+ p3.id,
87852
+ p3.harnessId,
87853
+ p3.name,
87854
+ p3.isBase ? `${p3.harnessId} harness with the built-in configuration` : `Custom ${p3.harnessId} profile`,
87855
+ p3.config
87856
+ );
88151
87857
  }
88152
- this.deps.db.prepare(`UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?`).run(grant.credential_hash);
88153
87858
  }
88154
- async wakePeer(sessionId, credential) {
88155
- if (!this.deps.sessions.get(sessionId)) return;
88156
- try {
88157
- await this.deps.sessions.sendWithoutLease({
88158
- sessionId,
88159
- text: `A collaboration mailbox message is ready. Call session_collab_retrieve with credential ${JSON.stringify(credential)} to receive it, then act on it and end your turn \u2014 you will be woken again the same way for every later message, so never wait in place for one.`,
88160
- source: "task-notification",
88161
- requestId: `collab-wake-${hashCredential(credential).slice(0, 12)}-${Date.now()}`
88162
- });
88163
- } catch {
87859
+ if (profiles.length === 0) {
87860
+ const seedIds = new Set(harnesses.readySessionHarnessIds());
87861
+ for (const s4 of sessions.list()) {
87862
+ if (s4.harnessId) seedIds.add(s4.harnessId);
87863
+ }
87864
+ for (const id of seedIds) {
87865
+ const harnessId = normalizeSessionHarnessId(id) ?? id;
87866
+ pushProfile(harnessId, harnessId, harnessId, `${harnessId} harness`);
88164
87867
  }
88165
87868
  }
88166
- };
87869
+ return profiles;
87870
+ }
88167
87871
 
88168
- // src/provider/secret-crypto.ts
88169
- import { existsSync as existsSync40, mkdirSync as mkdirSync23, readFileSync as readFileSync20, writeFileSync as writeFileSync18, chmodSync as chmodSync2 } from "node:fs";
88170
- import { dirname as dirname18 } from "node:path";
88171
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes6 } from "node:crypto";
88172
- var ENC_PREFIX2 = "enc:v1:";
88173
- var KEY_BYTES = 32;
88174
- function isEncryptedSecret(value) {
88175
- return typeof value === "string" && value.startsWith(ENC_PREFIX2);
87872
+ // src/session/collaboration-request.ts
87873
+ import { randomUUID as randomUUID12 } from "node:crypto";
87874
+
87875
+ // src/session/collaboration-context.ts
87876
+ init_environment();
87877
+ function resolveProfile(profiles, agentId) {
87878
+ const direct = profiles.get(agentId);
87879
+ if (direct) return direct;
87880
+ const wire = normalizeSessionHarnessId(agentId);
87881
+ return wire ? profiles.get(`${wire}-base`) : void 0;
88176
87882
  }
88177
- function ensureKeyFile(keyPath) {
88178
- if (existsSync40(keyPath)) {
88179
- const raw = readFileSync20(keyPath);
88180
- if (raw.length === KEY_BYTES) return raw;
87883
+ function initiatorTitleOf(ctx, grant) {
87884
+ return ctx.deps.sessions.get(grant.parent_session_id)?.title?.trim() || grant.parent_session_id.slice(0, 8);
87885
+ }
87886
+
87887
+ // src/session/collaboration-request.ts
87888
+ function safeLaunchConfig(raw) {
87889
+ const config2 = raw && typeof raw === "object" ? raw : {};
87890
+ const safe = {};
87891
+ if (typeof config2.model === "string" && config2.model.trim()) safe.model = config2.model.trim();
87892
+ if (typeof config2.effort === "string" && config2.effort.trim()) safe.effort = config2.effort.trim();
87893
+ if (config2.apiProviderId === null) safe.apiProviderId = null;
87894
+ else if (typeof config2.apiProviderId === "string" && config2.apiProviderId.trim()) {
87895
+ safe.apiProviderId = config2.apiProviderId.trim();
87896
+ }
87897
+ if (config2.worktree && typeof config2.worktree === "object") safe.worktree = config2.worktree;
87898
+ if (typeof config2.worktreePath === "string" && config2.worktreePath.trim()) {
87899
+ safe.worktreePath = config2.worktreePath.trim();
87900
+ }
87901
+ if (typeof config2.cwd === "string" && config2.cwd.trim()) safe.cwd = config2.cwd.trim();
87902
+ return safe;
87903
+ }
87904
+ function normalizeLaunches(ctx, input, profileList) {
87905
+ const parent = ctx.deps.sessions.get(input.parentSessionId);
87906
+ const profiles = new Map(profileList.map((p3) => [p3.id, p3]));
87907
+ return input.launches.map((launch2) => {
87908
+ const mode = resolveLaunchMode(launch2.mode);
87909
+ const launchId = launch2.launchId?.trim() || randomUUID12();
87910
+ if (mode === "link") {
87911
+ const peerSessionId = launch2.sessionId?.trim();
87912
+ if (!peerSessionId) {
87913
+ throw new CollaborationError("Link launches require sessionId of an existing SuperOne session", "invalid_argument");
87914
+ }
87915
+ if (peerSessionId === input.parentSessionId) {
87916
+ throw new CollaborationError("Cannot link a session to itself", "invalid_argument");
87917
+ }
87918
+ const peer = ctx.deps.sessions.get(peerSessionId);
87919
+ if (!peer) throw new CollaborationError(`Unknown sessionId for link: ${peerSessionId}`, "not_found");
87920
+ assertNotPeeredElsewhere(ctx.store, input.parentSessionId, peerSessionId);
87921
+ const peerTitle = peer.title?.trim() || peerSessionId.slice(0, 8);
87922
+ const { summary: summary3, name: name2, role: role2 } = normalizeLaunchLabels("link", launch2, peerTitle);
87923
+ return {
87924
+ launchId,
87925
+ mode: "link",
87926
+ agentId: "",
87927
+ sessionId: peerSessionId,
87928
+ peerTitle,
87929
+ peerProjectPath: ctx.deps.projects.get(peer.projectId)?.path,
87930
+ summary: summary3,
87931
+ name: name2,
87932
+ role: role2,
87933
+ config: { name: name2, role: role2, summary: summary3 }
87934
+ };
87935
+ }
87936
+ const agentId = launch2.agentId?.trim();
87937
+ if (!agentId) {
87938
+ throw new CollaborationError(`${mode} launches require agentId from session_collab_list_agents`, "invalid_argument");
87939
+ }
87940
+ const profile = resolveProfile(profiles, agentId);
87941
+ if (!profile) throw new CollaborationError(`Unknown agent profile: ${agentId}`, "invalid_argument");
87942
+ const { summary: summary2, name, role } = normalizeLaunchLabels(mode, launch2);
87943
+ const safe = safeLaunchConfig(launch2.config);
87944
+ return {
87945
+ launchId,
87946
+ mode,
87947
+ agentId,
87948
+ summary: summary2,
87949
+ name,
87950
+ role,
87951
+ config: {
87952
+ ...profile.defaultConfig,
87953
+ ...safe,
87954
+ permissionMode: "default",
87955
+ sandboxMode: "off",
87956
+ cwd: safe.cwd ?? (parent.cwd ?? ctx.deps.projects.get(parent.projectId)?.path),
87957
+ name,
87958
+ role
87959
+ }
87960
+ };
87961
+ });
87962
+ }
87963
+ function createGrant(ctx, parentSessionId, launch2) {
87964
+ const { grantId, approved } = recordApprovedLaunch(ctx.store, parentSessionId, launch2, launch2.config);
87965
+ if (approved.reused) return approved;
87966
+ ctx.deps.events.append({
87967
+ aggregateType: "session",
87968
+ aggregateId: parentSessionId,
87969
+ eventType: "collaboration.grant_created",
87970
+ payload: approved.mode === "link" ? { grantId, mode: "link", peerSessionId: approved.sessionId, launchId: approved.launchId } : { grantId, mode: approved.mode, agentId: approved.agentId, launchId: approved.launchId }
87971
+ });
87972
+ return approved;
87973
+ }
87974
+ async function requestCollaboration(ctx, input) {
87975
+ if (!ctx.deps.sessions.get(input.parentSessionId)) {
87976
+ throw new CollaborationError("Parent session is not available", "not_found");
87977
+ }
87978
+ if (ctx.store.isSpawnChild(input.parentSessionId)) {
87979
+ throw new CollaborationError(NESTED_COLLABORATION_UNSUPPORTED, "failed_precondition");
87980
+ }
87981
+ if (!Array.isArray(input.launches)) {
87982
+ throw new CollaborationError("launches must contain at least one proposed session", "invalid_argument");
87983
+ }
87984
+ assertLaunchCount(input.launches.length);
87985
+ const profileList = ctx.listProfiles();
87986
+ const normalized = normalizeLaunches(ctx, input, profileList);
87987
+ const launchIds = new Set(normalized.map((l) => l.launchId));
87988
+ if (launchIds.size !== normalized.length) {
87989
+ throw new CollaborationError("Every confirmed launch must have a unique launchId", "invalid_argument");
87990
+ }
87991
+ let confirmed = normalized;
87992
+ if (input.requireUserConfirm) {
87993
+ const outcome = await ctx.deps.sessions.requestAgentsConfirm({
87994
+ sessionId: input.parentSessionId,
87995
+ launches: normalized,
87996
+ profiles: profileList,
87997
+ signal: input.signal
87998
+ });
87999
+ if (outcome.action === "cancel") return { status: "cancelled" };
88000
+ if (outcome.action === "decline") return { status: "rejected", feedback: outcome.content?.feedback };
88001
+ confirmed = mergeConfirmedLaunches(normalized, outcome.content);
88181
88002
  }
88182
- mkdirSync23(dirname18(keyPath), { recursive: true, mode: 448 });
88183
- const key = randomBytes6(KEY_BYTES);
88184
- writeFileSync18(keyPath, key, { mode: 384 });
88003
+ const launches = ctx.store.transaction(() => confirmed.map((launch2) => createGrant(ctx, input.parentSessionId, launch2)));
88004
+ return { status: "approved", launches, next: START_APPROVED_LAUNCHES_HINT };
88005
+ }
88006
+
88007
+ // src/session/collaboration-start.ts
88008
+ init_agent_types();
88009
+ init_environment();
88010
+ import { existsSync as existsSync39, statSync as statSync15 } from "node:fs";
88011
+ import { resolve as pathResolve2 } from "node:path";
88012
+ function resolveCwd(ctx, config2, projectId, parentCwd) {
88013
+ const project = ctx.deps.projects.get(projectId);
88014
+ const fallback = parentCwd || project?.path || process.cwd();
88015
+ const cwd = pathResolve2(config2.cwd || fallback);
88016
+ if (!existsSync39(cwd) || !statSync15(cwd).isDirectory()) {
88017
+ throw new CollaborationError(`Working directory does not exist: ${cwd}`, "invalid_argument");
88018
+ }
88019
+ return cwd;
88020
+ }
88021
+ function applyFormAnswers(ctx, grant, formAnswers) {
88022
+ const packed = formAnswers[SESSION_AGENT_LAUNCHES_FIELD];
88023
+ if (typeof packed !== "string") return grant;
88024
+ let edited;
88185
88025
  try {
88186
- chmodSync2(keyPath, 384);
88026
+ edited = JSON.parse(packed);
88027
+ } catch {
88028
+ return grant;
88029
+ }
88030
+ if (!Array.isArray(edited) || edited.length === 0) return grant;
88031
+ const patch = edited[0]?.config;
88032
+ if (!patch || typeof patch !== "object") return grant;
88033
+ const next = patchEditableLaunchConfig(parseGrantConfig(grant.config_json), patch);
88034
+ ctx.store.updateConfig(grant.grant_id, next);
88035
+ return { ...grant, config_json: JSON.stringify(next) };
88036
+ }
88037
+ async function deliverInitialTask(ctx, grant, childSessionId) {
88038
+ if (grant.task_sent === 1) return;
88039
+ const config2 = parseGrantConfig(grant.config_json);
88040
+ const text2 = grant.kind === "handoff" ? handoffTaskContent({
88041
+ parentSessionId: grant.parent_session_id,
88042
+ parentTitle: ctx.deps.sessions.get(grant.parent_session_id)?.title?.trim() || null,
88043
+ task: grant.task
88044
+ }) : grant.task;
88045
+ try {
88046
+ await ctx.deps.sessions.sendWithoutLease({
88047
+ sessionId: childSessionId,
88048
+ text: text2,
88049
+ model: config2.model,
88050
+ effort: config2.effort,
88051
+ permissionMode: config2.permissionMode,
88052
+ sandboxMode: config2.sandboxMode,
88053
+ apiProviderId: config2.apiProviderId,
88054
+ requestId: `collaboration-task-${grant.grant_id.slice(0, 16)}`
88055
+ });
88187
88056
  } catch {
88188
88057
  }
88189
- return key;
88058
+ ctx.store.markTaskSent(grant.grant_id);
88190
88059
  }
88191
- function createNodeSecretCrypto(keyPath) {
88192
- const key = ensureKeyFile(keyPath);
88060
+ async function wakeLinkPeer(ctx, sessionId, grant, hasOpening) {
88061
+ if (!ctx.deps.sessions.get(sessionId)) return;
88062
+ try {
88063
+ await ctx.deps.sessions.sendWithoutLease({
88064
+ sessionId,
88065
+ text: linkActivationWakeText({
88066
+ initiatorSessionId: grant.parent_session_id,
88067
+ initiatorTitle: initiatorTitleOf(ctx, grant),
88068
+ hasOpening
88069
+ }),
88070
+ source: "task-notification",
88071
+ requestId: `collab-link-wake-${grant.grant_id.slice(0, 12)}-${Date.now()}`
88072
+ });
88073
+ } catch {
88074
+ }
88075
+ }
88076
+ async function startLink(ctx, grant) {
88077
+ if (!grant.child_session_id) {
88078
+ throw new CollaborationError("Link grant is missing peer session id", "failed_precondition");
88079
+ }
88080
+ const peerSessionId = grant.child_session_id;
88081
+ if (!ctx.deps.sessions.get(peerSessionId)) {
88082
+ throw new CollaborationError(`Peer session no longer exists: ${peerSessionId}`, "not_found");
88083
+ }
88084
+ const alreadyStarted = Boolean(grant.started_at);
88085
+ if (!alreadyStarted) ctx.store.markStarted(grant.grant_id);
88086
+ const opening = grant.task?.trim() ?? "";
88087
+ if (!alreadyStarted && opening) {
88088
+ if (grant.task_sent !== 1) {
88089
+ ctx.store.appendLinkOpening(grant, peerSessionId, opening);
88090
+ void wakeLinkPeer(ctx, peerSessionId, grant, true);
88091
+ }
88092
+ } else {
88093
+ void wakeLinkPeer(ctx, peerSessionId, grant, false);
88094
+ if (!alreadyStarted) ctx.store.markTaskSent(grant.grant_id);
88095
+ }
88096
+ const peer = describeLaunchedPeer(grant);
88193
88097
  return {
88194
- encrypt(plain) {
88195
- if (!plain) return "";
88196
- if (isEncryptedSecret(plain)) return plain;
88197
- const iv = randomBytes6(12);
88198
- const cipher = createCipheriv("aes-256-gcm", key, iv);
88199
- const ciphertext = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
88200
- const tag = cipher.getAuthTag();
88201
- return ENC_PREFIX2 + Buffer.concat([iv, tag, ciphertext]).toString("base64");
88202
- },
88203
- decrypt(stored) {
88204
- if (!stored) return "";
88205
- if (!isEncryptedSecret(stored)) return stored;
88098
+ status: "linked",
88099
+ mode: "link",
88100
+ sessionId: peerSessionId,
88101
+ peerSessionId,
88102
+ reused: alreadyStarted,
88103
+ name: peer.name,
88104
+ role: peer.role,
88105
+ title: peer.title,
88106
+ config: peer.config
88107
+ };
88108
+ }
88109
+ async function startCollaboration(ctx, input) {
88110
+ let grant = prepareLaunchStart(ctx.store, input.callerSessionId, input);
88111
+ if (grant.kind !== "link" && input.formAnswers && typeof input.formAnswers === "object") {
88112
+ grant = applyFormAnswers(ctx, grant, input.formAnswers);
88113
+ }
88114
+ if (grant.kind === "link") return startLink(ctx, grant);
88115
+ const isHandoff = grant.kind === "handoff";
88116
+ const existingHandoffSessionId = isHandoff ? parseGrantConfig(grant.config_json).handoffSessionId : void 0;
88117
+ const existingSessionId = existingHandoffSessionId ?? grant.child_session_id;
88118
+ if (existingSessionId) {
88119
+ const existing = ctx.deps.sessions.get(existingSessionId);
88120
+ if (isHandoff && !existing) {
88121
+ throw new CollaborationError(`The handoff session no longer exists: ${existingSessionId}`, "not_found");
88122
+ }
88123
+ if (existing && !isHandoff) {
88124
+ ctx.deps.sessions.setSystemPromptAppend(
88125
+ existing.sessionId,
88126
+ collaborationSystemPrompt(grant.parent_session_id)
88127
+ );
88128
+ }
88129
+ if (existing) await deliverInitialTask(ctx, grant, existingSessionId);
88130
+ const peer = describeLaunchedPeer(grant);
88131
+ return {
88132
+ status: "started",
88133
+ mode: grant.kind,
88134
+ sessionId: existingSessionId,
88135
+ reused: true,
88136
+ name: peer.name,
88137
+ role: peer.role,
88138
+ title: peer.title,
88139
+ config: peer.config
88140
+ };
88141
+ }
88142
+ const parent = ctx.deps.sessions.get(grant.parent_session_id);
88143
+ if (!parent) throw new CollaborationError("Parent session is not available", "not_found");
88144
+ const config2 = parseGrantConfig(grant.config_json);
88145
+ let cwd = resolveCwd(ctx, config2, parent.projectId, parent.cwd);
88146
+ if (config2.worktreePath && typeof config2.worktreePath === "string" && config2.worktreePath.trim()) {
88147
+ cwd = pathResolve2(config2.worktreePath.trim());
88148
+ } else if (config2.worktree?.enabled) {
88149
+ cwd = ctx.deps.workspaceGit.activateWorktree(parent.projectId, {
88150
+ baseBranch: config2.worktree.baseBranch || "HEAD",
88151
+ mode: config2.worktree.mode ?? "branch",
88152
+ branchName: config2.worktree.branchName,
88153
+ carryLocalChanges: config2.worktree.carryLocalChanges
88154
+ }).path;
88155
+ }
88156
+ const profile = resolveProfile(new Map(ctx.listProfiles().map((p3) => [p3.id, p3])), grant.agent_id);
88157
+ const harnessId = profile?.harnessId ?? normalizeSessionHarnessId(grant.agent_id) ?? "claude";
88158
+ const displayName = deriveCollaborationName({ name: config2.name });
88159
+ const role = deriveCollaborationRole({ role: config2.role, task: grant.task });
88160
+ const title = collaborationSessionTitle(displayName, role);
88161
+ let child;
88162
+ try {
88163
+ child = ctx.deps.sessions.create({
88164
+ projectId: parent.projectId,
88165
+ harnessId,
88166
+ providerId: grant.agent_id,
88167
+ title,
88168
+ cwd,
88169
+ model: config2.model ?? null,
88170
+ effort: config2.effort ?? null,
88171
+ permissionMode: config2.permissionMode ?? null,
88172
+ sandboxMode: config2.sandboxMode ?? null,
88173
+ apiProviderId: config2.apiProviderId ?? null,
88174
+ controllerClientSessionId: input.controllerClientSessionId ?? parent.controllerClientSessionId,
88175
+ // Handoff is one-way by construction: the receiver gets no collaboration prompt.
88176
+ ...isHandoff ? {} : { systemPromptAppend: collaborationSystemPrompt(grant.parent_session_id) }
88177
+ });
88178
+ ctx.store.bindStartedSession(grant, child.sessionId, config2);
88179
+ } catch (err) {
88180
+ if (child?.sessionId) {
88206
88181
  try {
88207
- const buf = Buffer.from(stored.slice(ENC_PREFIX2.length), "base64");
88208
- const iv = buf.subarray(0, 12);
88209
- const tag = buf.subarray(12, 28);
88210
- const data = buf.subarray(28);
88211
- const decipher = createDecipheriv("aes-256-gcm", key, iv);
88212
- decipher.setAuthTag(tag);
88213
- return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
88182
+ ctx.deps.sessions.remove(child.sessionId);
88214
88183
  } catch {
88215
- return "";
88216
88184
  }
88217
88185
  }
88186
+ throw err;
88187
+ }
88188
+ grant = isHandoff ? grant : { ...grant, child_session_id: child.sessionId };
88189
+ await deliverInitialTask(ctx, grant, child.sessionId);
88190
+ ctx.deps.events.append({
88191
+ aggregateType: "session",
88192
+ aggregateId: grant.parent_session_id,
88193
+ eventType: isHandoff ? "collaboration.handoff_started" : "collaboration.child_started",
88194
+ payload: { grantId: grant.grant_id, childSessionId: child.sessionId }
88195
+ });
88196
+ return {
88197
+ status: "started",
88198
+ mode: grant.kind,
88199
+ sessionId: child.sessionId,
88200
+ reused: false,
88201
+ name: displayName,
88202
+ role,
88203
+ title,
88204
+ config: {
88205
+ model: config2.model,
88206
+ effort: config2.effort,
88207
+ fastMode: config2.fastMode,
88208
+ permissionMode: config2.permissionMode,
88209
+ sandboxMode: config2.sandboxMode,
88210
+ cwd,
88211
+ apiProviderId: config2.apiProviderId ?? null,
88212
+ name: displayName,
88213
+ role,
88214
+ ...config2.worktree ? { worktree: config2.worktree } : {},
88215
+ ...config2.worktreePath ? { worktreePath: config2.worktreePath } : {}
88216
+ }
88218
88217
  };
88219
88218
  }
88220
88219
 
88220
+ // src/session/collaboration.ts
88221
+ var CollaborationService = class {
88222
+ constructor(deps) {
88223
+ this.deps = deps;
88224
+ this.ctx = {
88225
+ deps,
88226
+ store: new CollaborationStore(deps.db),
88227
+ listProfiles: () => this.listProfiles()
88228
+ };
88229
+ }
88230
+ deps;
88231
+ ctx;
88232
+ /** Agent profiles from session_providers (+ ready-harness fallback). */
88233
+ listProfiles() {
88234
+ return listCollaborationProfiles(this.deps);
88235
+ }
88236
+ request(input) {
88237
+ return requestCollaboration(this.ctx, input);
88238
+ }
88239
+ start(input) {
88240
+ return startCollaboration(this.ctx, input);
88241
+ }
88242
+ send(input) {
88243
+ return sendCollaborationMessage(this.ctx, input);
88244
+ }
88245
+ retrieve(input) {
88246
+ return retrieveCollaborationMessages(this.ctx, input);
88247
+ }
88248
+ /** Reconstruct system-prompt append for spawn children after restart (never link). */
88249
+ rehydrateSystemPrompts() {
88250
+ for (const grant of this.ctx.store.startedSpawnGrants()) {
88251
+ if (!grant.child_session_id || !this.deps.sessions.get(grant.child_session_id)) continue;
88252
+ this.deps.sessions.setSystemPromptAppend(grant.child_session_id, collaborationSystemPrompt(grant.parent_session_id));
88253
+ }
88254
+ }
88255
+ };
88256
+
88221
88257
  // src/workspace/watch-service.ts
88222
88258
  import { watch } from "node:fs";
88223
88259
 
@@ -88324,7 +88360,7 @@ var WorkspaceWatchService = class {
88324
88360
 
88325
88361
  // src/workspace/tail-watch-service.ts
88326
88362
  init_fs();
88327
- import { existsSync as existsSync41, fstatSync as fstatSync2, openSync as openSync2, closeSync as closeSync2, statSync as statSync16, readSync as readSync2, realpathSync as realpathSync6 } from "node:fs";
88363
+ import { existsSync as existsSync40, fstatSync as fstatSync2, openSync as openSync2, closeSync as closeSync2, statSync as statSync16, readSync as readSync2, realpathSync as realpathSync6 } from "node:fs";
88328
88364
  var MAX_POLL_BYTES = 10 * 1024 * 1024;
88329
88365
  var WorkspaceTailWatchService = class {
88330
88366
  constructor(projects, fs2) {
@@ -88348,7 +88384,7 @@ var WorkspaceTailWatchService = class {
88348
88384
  );
88349
88385
  }
88350
88386
  try {
88351
- resolvedAbs = existsSync41(absolutePath) ? realpathSync6(absolutePath) : absolutePath;
88387
+ resolvedAbs = existsSync40(absolutePath) ? realpathSync6(absolutePath) : absolutePath;
88352
88388
  } catch {
88353
88389
  resolvedAbs = absolutePath;
88354
88390
  }
@@ -88379,7 +88415,7 @@ var WorkspaceTailWatchService = class {
88379
88415
  code: "invalid_argument"
88380
88416
  });
88381
88417
  }
88382
- if (existsSync41(resolvedAbs)) {
88418
+ if (existsSync40(resolvedAbs)) {
88383
88419
  try {
88384
88420
  const st2 = statSync16(resolvedAbs);
88385
88421
  if (!st2.isFile()) {
@@ -88430,7 +88466,7 @@ var WorkspaceTailWatchService = class {
88430
88466
  { code: "invalid_argument" }
88431
88467
  );
88432
88468
  }
88433
- if (!existsSync41(absolutePath)) {
88469
+ if (!existsSync40(absolutePath)) {
88434
88470
  return {
88435
88471
  content: "",
88436
88472
  encoding: "base64",
@@ -88521,7 +88557,7 @@ var WorkspaceTailWatchService = class {
88521
88557
  };
88522
88558
 
88523
88559
  // src/auth/idempotency.ts
88524
- import { createHash as createHash10 } from "node:crypto";
88560
+ import { createHash as createHash9 } from "node:crypto";
88525
88561
  var IdempotencyService = class {
88526
88562
  constructor(db) {
88527
88563
  this.db = db;
@@ -88529,7 +88565,7 @@ var IdempotencyService = class {
88529
88565
  db;
88530
88566
  inflight = /* @__PURE__ */ new Map();
88531
88567
  payloadHash(payload) {
88532
- return createHash10("sha256").update(JSON.stringify(payload ?? null)).digest("hex");
88568
+ return createHash9("sha256").update(JSON.stringify(payload ?? null)).digest("hex");
88533
88569
  }
88534
88570
  key(clientIdentity, operation, idempotencyKey) {
88535
88571
  return `${clientIdentity}\0${operation}\0${idempotencyKey}`;
@@ -88628,7 +88664,62 @@ var IdempotencyService = class {
88628
88664
  };
88629
88665
 
88630
88666
  // src/provider/provider-store.ts
88631
- import { randomUUID as randomUUID12 } from "node:crypto";
88667
+ import { randomUUID as randomUUID13 } from "node:crypto";
88668
+
88669
+ // src/provider/secret-crypto.ts
88670
+ import { existsSync as existsSync41, mkdirSync as mkdirSync23, readFileSync as readFileSync20, writeFileSync as writeFileSync18, chmodSync as chmodSync2 } from "node:fs";
88671
+ import { dirname as dirname18 } from "node:path";
88672
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes5 } from "node:crypto";
88673
+ var ENC_PREFIX2 = "enc:v1:";
88674
+ var KEY_BYTES = 32;
88675
+ function isEncryptedSecret(value) {
88676
+ return typeof value === "string" && value.startsWith(ENC_PREFIX2);
88677
+ }
88678
+ function ensureKeyFile(keyPath) {
88679
+ if (existsSync41(keyPath)) {
88680
+ const raw = readFileSync20(keyPath);
88681
+ if (raw.length === KEY_BYTES) return raw;
88682
+ }
88683
+ mkdirSync23(dirname18(keyPath), { recursive: true, mode: 448 });
88684
+ const key = randomBytes5(KEY_BYTES);
88685
+ writeFileSync18(keyPath, key, { mode: 384 });
88686
+ try {
88687
+ chmodSync2(keyPath, 384);
88688
+ } catch {
88689
+ }
88690
+ return key;
88691
+ }
88692
+ function createNodeSecretCrypto(keyPath) {
88693
+ const key = ensureKeyFile(keyPath);
88694
+ return {
88695
+ encrypt(plain) {
88696
+ if (!plain) return "";
88697
+ if (isEncryptedSecret(plain)) return plain;
88698
+ const iv = randomBytes5(12);
88699
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
88700
+ const ciphertext = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
88701
+ const tag = cipher.getAuthTag();
88702
+ return ENC_PREFIX2 + Buffer.concat([iv, tag, ciphertext]).toString("base64");
88703
+ },
88704
+ decrypt(stored) {
88705
+ if (!stored) return "";
88706
+ if (!isEncryptedSecret(stored)) return stored;
88707
+ try {
88708
+ const buf = Buffer.from(stored.slice(ENC_PREFIX2.length), "base64");
88709
+ const iv = buf.subarray(0, 12);
88710
+ const tag = buf.subarray(12, 28);
88711
+ const data = buf.subarray(28);
88712
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
88713
+ decipher.setAuthTag(tag);
88714
+ return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
88715
+ } catch {
88716
+ return "";
88717
+ }
88718
+ }
88719
+ };
88720
+ }
88721
+
88722
+ // src/provider/provider-store.ts
88632
88723
  function safeParse3(text2, fallback) {
88633
88724
  if (!text2) return fallback;
88634
88725
  try {
@@ -88660,7 +88751,7 @@ var ProviderStore = class {
88660
88751
  }
88661
88752
  createCredential(input) {
88662
88753
  const now = (/* @__PURE__ */ new Date()).toISOString();
88663
- const id = input.id?.trim() || randomUUID12();
88754
+ const id = input.id?.trim() || randomUUID13();
88664
88755
  const maxOrder = this.db.prepare("SELECT MAX(sort_order) as m FROM provider_credentials").get()?.m ?? -1;
88665
88756
  this.db.prepare(
88666
88757
  `INSERT INTO provider_credentials
@@ -88856,7 +88947,7 @@ var ProviderStore = class {
88856
88947
  };
88857
88948
 
88858
88949
  // src/session/host-action-mcp-server.ts
88859
- import { randomUUID as randomUUID14 } from "node:crypto";
88950
+ import { randomUUID as randomUUID15 } from "node:crypto";
88860
88951
  import {
88861
88952
  createServer as createHttpServer
88862
88953
  } from "node:http";
@@ -92451,7 +92542,7 @@ init_interaction_memory();
92451
92542
  // ../../packages/runtime/src/fs/interaction-memory.ts
92452
92543
  init_superone_home();
92453
92544
  init_interaction_memory();
92454
- import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
92545
+ import { createHash as createHash10, randomUUID as randomUUID14 } from "node:crypto";
92455
92546
  import { lstat, mkdir, open, readdir, rename, rm } from "node:fs/promises";
92456
92547
  import { constants as constants4 } from "node:fs";
92457
92548
  import { join as join34 } from "node:path";
@@ -92647,7 +92738,7 @@ function indexEntry(note) {
92647
92738
  };
92648
92739
  }
92649
92740
  async function writeAtomic(dir, name, raw) {
92650
- const temp = join34(dir, `.${name}.${randomUUID13()}.tmp`);
92741
+ const temp = join34(dir, `.${name}.${randomUUID14()}.tmp`);
92651
92742
  try {
92652
92743
  const file2 = await open(temp, "wx", 384);
92653
92744
  try {
@@ -92705,7 +92796,7 @@ var InteractionMemoryStore = class _InteractionMemoryStore {
92705
92796
  await file2.close();
92706
92797
  }
92707
92798
  const { meta: meta3, content } = parseNote(raw, { title: topic2, resource: target.resource });
92708
- return { ...meta3, topic: topic2, content, revision: createHash11("sha256").update(raw).digest("hex") };
92799
+ return { ...meta3, topic: topic2, content, revision: createHash10("sha256").update(raw).digest("hex") };
92709
92800
  } catch (err) {
92710
92801
  if (missing(err)) return null;
92711
92802
  throw new Error(`Cannot read personal memory ${target.label}/${topic2}: ${err instanceof Error ? err.message : String(err)}`);
@@ -92798,7 +92889,7 @@ var InteractionMemoryStore = class _InteractionMemoryStore {
92798
92889
  saved: true,
92799
92890
  created: !existing,
92800
92891
  ...target.identity,
92801
- ...summary({ ...meta3, topic: args.topic, content, revision: createHash11("sha256").update(raw).digest("hex") })
92892
+ ...summary({ ...meta3, topic: args.topic, content, revision: createHash10("sha256").update(raw).digest("hex") })
92802
92893
  };
92803
92894
  } finally {
92804
92895
  await rm(lock, { recursive: true, force: true });
@@ -100998,9 +101089,7 @@ function registerNodeCollabTools(server, superoneSessionId, collab) {
100998
101089
  "session_collab_start",
100999
101090
  {
101000
101091
  description: startDesc.description,
101001
- inputSchema: {
101002
- credential: external_exports.string().min(1)
101003
- }
101092
+ inputSchema: jsonSchemaToZodShape(startDesc.inputSchema)
101004
101093
  },
101005
101094
  async (args) => {
101006
101095
  try {
@@ -101135,7 +101224,7 @@ function headerValue(req, name) {
101135
101224
  return typeof value === "string" && value.length > 0 ? value : null;
101136
101225
  }
101137
101226
  async function startHostActionMcpServer(opts) {
101138
- const masterToken = opts.masterToken ?? randomUUID14();
101227
+ const masterToken = opts.masterToken ?? randomUUID15();
101139
101228
  const httpSessions = /* @__PURE__ */ new Map();
101140
101229
  const httpSessionsBySuperone = /* @__PURE__ */ new Map();
101141
101230
  let httpUrl2 = "";
@@ -101203,7 +101292,7 @@ async function startHostActionMcpServer(opts) {
101203
101292
  });
101204
101293
  let sessionRef;
101205
101294
  const transport = new StreamableHTTPServerTransport({
101206
- sessionIdGenerator: randomUUID14,
101295
+ sessionIdGenerator: randomUUID15,
101207
101296
  onsessioninitialized: (id) => {
101208
101297
  if (sessionRef) httpSessions.set(id, sessionRef);
101209
101298
  },
@@ -101987,7 +102076,7 @@ function computeNextRunAt(schedule) {
101987
102076
  }
101988
102077
 
101989
102078
  // ../../packages/runtime/src/automations/store.ts
101990
- import { randomUUID as randomUUID15 } from "node:crypto";
102079
+ import { randomUUID as randomUUID16 } from "node:crypto";
101991
102080
  function toRecord(row, projectPath) {
101992
102081
  return {
101993
102082
  id: row.id,
@@ -102019,7 +102108,7 @@ function createAutomationStore(db, resolveProjectPath2) {
102019
102108
  return { ...toRecord(row, path), projectId: row.project_id };
102020
102109
  },
102021
102110
  create(projectId, projectPath, data) {
102022
- const id = randomUUID15();
102111
+ const id = randomUUID16();
102023
102112
  const now = (/* @__PURE__ */ new Date()).toISOString();
102024
102113
  const nextRunAt = computeNextRunAt(data.schedule);
102025
102114
  db.prepare(
@@ -102069,8 +102158,8 @@ function createAutomationStore(db, resolveProjectPath2) {
102069
102158
  delete(id) {
102070
102159
  return db.prepare("DELETE FROM automations WHERE id = ?").run(id).changes > 0;
102071
102160
  },
102072
- listDue(nowIso) {
102073
- const reclaimBefore = new Date(Date.parse(nowIso) - 30 * 60 * 1e3).toISOString();
102161
+ listDue(nowIso2) {
102162
+ const reclaimBefore = new Date(Date.parse(nowIso2) - 30 * 60 * 1e3).toISOString();
102074
102163
  const rows = db.prepare(
102075
102164
  `SELECT * FROM automations
102076
102165
  WHERE enabled = 1
@@ -102081,7 +102170,7 @@ function createAutomationStore(db, resolveProjectPath2) {
102081
102170
  OR last_run_status != 'running'
102082
102171
  OR COALESCE(last_run_at, updated_at) < ?
102083
102172
  )`
102084
- ).all(nowIso, reclaimBefore);
102173
+ ).all(nowIso2, reclaimBefore);
102085
102174
  return rows.map((r) => {
102086
102175
  const path = resolveProjectPath2(r.project_id) ?? "";
102087
102176
  return toRecord(r, path);
@@ -102540,7 +102629,7 @@ ON CONFLICT(id) DO UPDATE SET
102540
102629
 
102541
102630
  // src/workspace/artifact-zone.ts
102542
102631
  init_environment();
102543
- import { createHash as createHash12 } from "node:crypto";
102632
+ import { createHash as createHash11 } from "node:crypto";
102544
102633
  import {
102545
102634
  closeSync as closeSync3,
102546
102635
  fstatSync as fstatSync3,
@@ -102862,7 +102951,7 @@ var ArtifactZoneService = class {
102862
102951
  written: 0,
102863
102952
  total: req.total,
102864
102953
  sha256: req.sha256,
102865
- hash: createHash12("sha256"),
102954
+ hash: createHash11("sha256"),
102866
102955
  lastActivityAt: Date.now()
102867
102956
  };
102868
102957
  this.transfers.set(transferId, transfer);
@@ -102968,7 +103057,6 @@ async function startNodeRuntime(partial2 = {}) {
102968
103057
  }
102969
103058
  const allowSimulatedTurnFallback = partial2.allowSimulatedTurnFallback ?? simulatedHarness;
102970
103059
  const providers = new ProviderStore(db, paths.providerSecretsKey);
102971
- const collabSecrets = createNodeSecretCrypto(paths.providerSecretsKey);
102972
103060
  let sessionsRef = null;
102973
103061
  let collaborationRef = null;
102974
103062
  const hostActionMcp = await startHostActionMcpServer({
@@ -103003,8 +103091,9 @@ async function startNodeRuntime(partial2 = {}) {
103003
103091
  if (!collaborationRef) throw Object.assign(new Error("collab not ready"), { code: "failed_precondition" });
103004
103092
  const a = args && typeof args === "object" ? args : {};
103005
103093
  return collaborationRef.start({
103006
- credential: typeof a.credential === "string" ? a.credential : void 0,
103007
- callerSessionId: sessionId
103094
+ callerSessionId: sessionId,
103095
+ launchId: typeof a.launchId === "string" ? a.launchId : "",
103096
+ task: typeof a.task === "string" ? a.task : void 0
103008
103097
  });
103009
103098
  },
103010
103099
  send: async (sessionId, args) => {
@@ -103012,7 +103101,7 @@ async function startNodeRuntime(partial2 = {}) {
103012
103101
  const a = args && typeof args === "object" ? args : {};
103013
103102
  return collaborationRef.send({
103014
103103
  sessionId,
103015
- credential: String(a.credential ?? ""),
103104
+ to: typeof a.to === "string" ? a.to : void 0,
103016
103105
  content: String(a.content ?? ""),
103017
103106
  clientMessageId: typeof a.clientMessageId === "string" ? a.clientMessageId : void 0
103018
103107
  });
@@ -103022,8 +103111,7 @@ async function startNodeRuntime(partial2 = {}) {
103022
103111
  const a = args && typeof args === "object" ? args : {};
103023
103112
  return collaborationRef.retrieve({
103024
103113
  sessionId,
103025
- credentials: Array.isArray(a.credentials) ? a.credentials : void 0,
103026
- credential: typeof a.credential === "string" ? a.credential : void 0
103114
+ from: Array.isArray(a.from) ? a.from.filter((id) => typeof id === "string") : void 0
103027
103115
  });
103028
103116
  }
103029
103117
  }
@@ -103070,7 +103158,6 @@ async function startNodeRuntime(partial2 = {}) {
103070
103158
  providers,
103071
103159
  projects,
103072
103160
  workspaceGit,
103073
- secrets: collabSecrets,
103074
103161
  sessionProviders,
103075
103162
  experimentalClaudeOpenAiChatEnabled: () => loadNodeAgentSettings(paths.configJson).experimentalClaudeOpenAiChatEnabled
103076
103163
  });