@super-one/cli 0.68.0 → 0.69.0-alpha.1

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 +1446 -1388
  3. package/package.json +1 -1
package/lib/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- process.env.SUPERONE_VARIANT = "stable";
1
+ process.env.SUPERONE_VARIANT = "alpha";
2
2
  import { createRequire as __superoneCreateRequire } from 'node:module';
3
3
  import { fileURLToPath as __superoneFileURLToPath } from 'node:url';
4
4
  import { dirname as __superoneDirname } from 'node:path';
@@ -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",
@@ -10230,7 +10239,7 @@ async function openCodexAppServer(opts) {
10230
10239
  stderrBuf = (stderrBuf + chunk).slice(-MAX_STDERR_CHARS);
10231
10240
  });
10232
10241
  const rl = createInterface({ input: child.stdout });
10233
- const pending = /* @__PURE__ */ new Map();
10242
+ const pending2 = /* @__PURE__ */ new Map();
10234
10243
  const notificationWaiters = [];
10235
10244
  const notificationQueue = [];
10236
10245
  let nextId = 1;
@@ -10250,11 +10259,11 @@ async function openCodexAppServer(opts) {
10250
10259
  }
10251
10260
  };
10252
10261
  const failAll = (err) => {
10253
- for (const [, p3] of pending) {
10262
+ for (const [, p3] of pending2) {
10254
10263
  clearTimeout(p3.timer);
10255
10264
  p3.reject(err);
10256
10265
  }
10257
- pending.clear();
10266
+ pending2.clear();
10258
10267
  while (notificationWaiters.length) {
10259
10268
  const w3 = notificationWaiters.shift();
10260
10269
  clearTimeout(w3.timer);
@@ -10354,9 +10363,9 @@ async function openCodexAppServer(opts) {
10354
10363
  }
10355
10364
  if (rec.id != null && (typeof rec.id === "number" || typeof rec.id === "string")) {
10356
10365
  const id = typeof rec.id === "number" ? rec.id : Number(rec.id);
10357
- const waiter = pending.get(id);
10366
+ const waiter = pending2.get(id);
10358
10367
  if (!waiter) continue;
10359
- pending.delete(id);
10368
+ pending2.delete(id);
10360
10369
  clearTimeout(waiter.timer);
10361
10370
  if (rec.error) {
10362
10371
  const errObj = rec.error;
@@ -10394,14 +10403,14 @@ async function openCodexAppServer(opts) {
10394
10403
  }
10395
10404
  const id = nextId++;
10396
10405
  const timer = setTimeout(() => {
10397
- pending.delete(id);
10406
+ pending2.delete(id);
10398
10407
  reject(new Error(`Codex app-server ${method} timed out after ${timeoutMs}ms`));
10399
10408
  }, timeoutMs);
10400
- pending.set(id, { resolve: resolve17, reject, timer });
10409
+ pending2.set(id, { resolve: resolve17, reject, timer });
10401
10410
  try {
10402
10411
  writeLine({ jsonrpc: "2.0", id, method, params: params ?? {} });
10403
10412
  } catch (err) {
10404
- pending.delete(id);
10413
+ pending2.delete(id);
10405
10414
  clearTimeout(timer);
10406
10415
  reject(safePublicError("Codex app-server write failed", err));
10407
10416
  }
@@ -11208,9 +11217,9 @@ var init_account_manager = __esm({
11208
11217
  type,
11209
11218
  ...type === "chatgpt" ? { useHostedLoginSuccessPage: true, appBrand: "chatgpt" } : {}
11210
11219
  }));
11211
- const pending = { accountId: id, connection };
11212
- this.pending.set(result.loginId, pending);
11213
- void this.wait(result.loginId, pending);
11220
+ const pending2 = { accountId: id, connection };
11221
+ this.pending.set(result.loginId, pending2);
11222
+ void this.wait(result.loginId, pending2);
11214
11223
  return { ...result, accountId: id };
11215
11224
  } catch (error51) {
11216
11225
  this.failed.add(id);
@@ -11221,53 +11230,53 @@ var init_account_manager = __esm({
11221
11230
  this.starting = false;
11222
11231
  }
11223
11232
  }
11224
- async wait(loginId, pending) {
11233
+ async wait(loginId, pending2) {
11225
11234
  const deadline = Date.now() + 15 * 6e4;
11226
11235
  const timeout = setTimeout(() => {
11227
11236
  void this.cancel(loginId).catch(() => {
11228
11237
  });
11229
- this.failed.add(pending.accountId);
11238
+ this.failed.add(pending2.accountId);
11230
11239
  }, 15 * 6e4);
11231
11240
  timeout.unref?.();
11232
11241
  try {
11233
- while (this.pending.get(loginId) === pending && Date.now() < deadline) {
11234
- const notification = await pending.connection.nextNotification();
11242
+ while (this.pending.get(loginId) === pending2 && Date.now() < deadline) {
11243
+ const notification = await pending2.connection.nextNotification();
11235
11244
  if (!notification) throw new Error("Login connection closed");
11236
11245
  if (notification?.method !== "account/login/completed" || notification.params.loginId !== loginId) continue;
11237
11246
  if (notification.params.success !== true) throw new Error("Sign-in failed");
11238
- const status = parseAccountStatus(await pending.connection.request("account/read", { refreshToken: false }));
11247
+ const status = parseAccountStatus(await pending2.connection.request("account/read", { refreshToken: false }));
11239
11248
  if (!status.signedIn) throw new Error("Sign-in did not complete");
11240
- if (this.pending.get(loginId) !== pending) return;
11241
- this.store.update(pending.accountId, status);
11242
- this.store.invalidateCredentials(pending.accountId);
11243
- this.changed(codexAccountProviderId(pending.accountId));
11249
+ if (this.pending.get(loginId) !== pending2) return;
11250
+ this.store.update(pending2.accountId, status);
11251
+ this.store.invalidateCredentials(pending2.accountId);
11252
+ this.changed(codexAccountProviderId(pending2.accountId));
11244
11253
  return;
11245
11254
  }
11246
- if (this.pending.get(loginId) === pending) this.failed.add(pending.accountId);
11255
+ if (this.pending.get(loginId) === pending2) this.failed.add(pending2.accountId);
11247
11256
  } catch {
11248
- this.failed.add(pending.accountId);
11257
+ this.failed.add(pending2.accountId);
11249
11258
  } finally {
11250
11259
  clearTimeout(timeout);
11251
- if (this.pending.get(loginId) === pending) this.pending.delete(loginId);
11252
- await pending.connection.close().catch(() => {
11260
+ if (this.pending.get(loginId) === pending2) this.pending.delete(loginId);
11261
+ await pending2.connection.close().catch(() => {
11253
11262
  });
11254
11263
  }
11255
11264
  }
11256
11265
  async cancel(loginId) {
11257
- const pending = this.pending.get(loginId);
11258
- if (!pending) return;
11266
+ const pending2 = this.pending.get(loginId);
11267
+ if (!pending2) return;
11259
11268
  this.pending.delete(loginId);
11260
11269
  try {
11261
- await pending.connection.request("account/login/cancel", { loginId });
11270
+ await pending2.connection.request("account/login/cancel", { loginId });
11262
11271
  } finally {
11263
- await pending.connection.close();
11272
+ await pending2.connection.close();
11264
11273
  }
11265
11274
  }
11266
11275
  async logout(providerId) {
11267
11276
  const id = codexAccountId(providerId) ?? codexAccountId(this.store.defaultProviderId()) ?? CODEX_CLI_ACCOUNT_ID;
11268
11277
  this.revisions.set(id, (this.revisions.get(id) ?? 0) + 1);
11269
- for (const [loginId, pending] of this.pending) {
11270
- if (pending.accountId === id) await this.cancel(loginId);
11278
+ for (const [loginId, pending2] of this.pending) {
11279
+ if (pending2.accountId === id) await this.cancel(loginId);
11271
11280
  }
11272
11281
  const selected = codexAccountProviderId(id);
11273
11282
  const connection = await this.open(selected);
@@ -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
  }
@@ -13587,7 +13596,7 @@ async function fetchClaudeModels(opts) {
13587
13596
  };
13588
13597
  let q2;
13589
13598
  try {
13590
- q2 = (opts.queryFn ?? sdkQuery3)({ prompt: "hi", options });
13599
+ q2 = (opts.queryFn ?? sdkQuery3)({ prompt: CLAUDE_METADATA_PROBE_PROMPT, options });
13591
13600
  const models = await withTimeout(
13592
13601
  (async () => {
13593
13602
  await q2.initializationResult();
@@ -13620,12 +13629,13 @@ function withTimeout(promise2, ms2) {
13620
13629
  );
13621
13630
  });
13622
13631
  }
13623
- var MODEL_NAME_RE, DEFAULT_PROBE_TIMEOUT_MS;
13632
+ var CLAUDE_METADATA_PROBE_PROMPT, MODEL_NAME_RE, DEFAULT_PROBE_TIMEOUT_MS;
13624
13633
  var init_fetch_models = __esm({
13625
13634
  "../../packages/claude/src/fetch-models.ts"() {
13626
13635
  "use strict";
13627
13636
  init_resolve_sdk_binary();
13628
13637
  init_root_permission_guard();
13638
+ CLAUDE_METADATA_PROBE_PROMPT = "/help";
13629
13639
  MODEL_NAME_RE = /^(\w+ [\d.]+)(?:\s+with\s+(\w+)\s+context)?/;
13630
13640
  DEFAULT_PROBE_TIMEOUT_MS = 2e4;
13631
13641
  }
@@ -13950,23 +13960,23 @@ function mapPlanRequest(payload, fallbackId) {
13950
13960
  allowedPrompts: Array.isArray(payload.allowedPrompts) ? payload.allowedPrompts : Array.isArray(input.allowedPrompts) ? input.allowedPrompts : []
13951
13961
  };
13952
13962
  }
13953
- function userMessage(ctx, blockId, text2, nowIso) {
13963
+ function userMessage(ctx, blockId, text2, nowIso2) {
13954
13964
  return {
13955
13965
  id: blockId,
13956
13966
  role: "user",
13957
13967
  status: "complete",
13958
13968
  content: text2 ? [{ type: "text", text: text2 }] : [],
13959
- createdAt: nowIso,
13969
+ createdAt: nowIso2,
13960
13970
  providerId: ctx.providerId ?? "codex"
13961
13971
  };
13962
13972
  }
13963
- function assistantMessage(ctx, blockId, nowIso) {
13973
+ function assistantMessage(ctx, blockId, nowIso2) {
13964
13974
  return {
13965
13975
  id: blockId,
13966
13976
  role: "assistant",
13967
13977
  status: "streaming",
13968
13978
  content: [],
13969
- createdAt: nowIso,
13979
+ createdAt: nowIso2,
13970
13980
  providerId: ctx.providerId ?? "codex"
13971
13981
  };
13972
13982
  }
@@ -13982,7 +13992,7 @@ function createNodeSessionEventMapper(ctx) {
13982
13992
  const completedAssistantIds = /* @__PURE__ */ new Set();
13983
13993
  let lastAssistantId = null;
13984
13994
  let rawTerminalThisTurn = false;
13985
- const nowIso = ctx.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13995
+ const nowIso2 = ctx.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13986
13996
  const ensureAssistant = (push, preferredId) => {
13987
13997
  if (lastAssistantId) return lastAssistantId;
13988
13998
  const id = preferredId && preferredId.length > 0 ? preferredId : `assistant-${ctx.sessionId}`;
@@ -13990,7 +14000,7 @@ function createNodeSessionEventMapper(ctx) {
13990
14000
  startedAssistantIds.add(id);
13991
14001
  push({
13992
14002
  type: "message_start",
13993
- message: assistantMessage(ctx, id, nowIso())
14003
+ message: assistantMessage(ctx, id, nowIso2())
13994
14004
  });
13995
14005
  }
13996
14006
  lastAssistantId = id;
@@ -14012,7 +14022,7 @@ function createNodeSessionEventMapper(ctx) {
14012
14022
  const text2 = asString(payload.text) ?? "";
14013
14023
  push({
14014
14024
  type: "user_message_appended",
14015
- message: userMessage(ctx, blockId, text2, nowIso())
14025
+ message: userMessage(ctx, blockId, text2, nowIso2())
14016
14026
  });
14017
14027
  break;
14018
14028
  }
@@ -14906,6 +14916,20 @@ var init_session_title = __esm({
14906
14916
  }
14907
14917
  });
14908
14918
 
14919
+ // ../../packages/shared/src/host-wake.ts
14920
+ function isModelOnlyHostWake(text2) {
14921
+ return MODEL_ONLY_WAKE.test(text2.trimStart());
14922
+ }
14923
+ var MODEL_ONLY_WAKE;
14924
+ var init_host_wake = __esm({
14925
+ "../../packages/shared/src/host-wake.ts"() {
14926
+ "use strict";
14927
+ MODEL_ONLY_WAKE = new RegExp(
14928
+ '^(?:<task_notification source="(?:browser_download|artifact_sync)"|A collaboration mailbox message is ready\\.|A user-approved collaboration link is active with SuperOne session )'
14929
+ );
14930
+ }
14931
+ });
14932
+
14909
14933
  // ../../packages/runtime/src/session/host-action-store.ts
14910
14934
  import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
14911
14935
  function hashToken(token) {
@@ -15624,15 +15648,6 @@ var init_host_action_store = __esm({
15624
15648
  }
15625
15649
  });
15626
15650
 
15627
- // ../../packages/runtime/src/session/types.ts
15628
- var DEFAULT_PERMISSION_TIMEOUT_MS;
15629
- var init_types = __esm({
15630
- "../../packages/runtime/src/session/types.ts"() {
15631
- "use strict";
15632
- DEFAULT_PERMISSION_TIMEOUT_MS = 6e4;
15633
- }
15634
- });
15635
-
15636
15651
  // ../../packages/runtime/src/session/runtime-policy.ts
15637
15652
  function getRuntimeIdleTimeoutMs(activeRuntimeCount) {
15638
15653
  if (activeRuntimeCount <= 4) return TWENTY_MINUTES_MS;
@@ -15652,9 +15667,6 @@ var init_runtime_policy = __esm({
15652
15667
 
15653
15668
  // ../../packages/runtime/src/session/session-runtime.ts
15654
15669
  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
15670
  function deriveSessionTitleFromUserText(text2) {
15659
15671
  const cleaned = stripMiniAppMarkup(text2).trim().replace(/\s+/g, " ");
15660
15672
  if (!cleaned) return null;
@@ -15758,10 +15770,9 @@ var init_session_runtime = __esm({
15758
15770
  init_message_catalog();
15759
15771
  init_miniapp_prompt_tags();
15760
15772
  init_session_title();
15773
+ init_host_wake();
15761
15774
  init_host_action_store();
15762
- init_types();
15763
15775
  init_runtime_policy();
15764
- init_types();
15765
15776
  DEFAULT_AGENTS_CONFIRM_TIMEOUT_MS = 10 * 6e4;
15766
15777
  SessionRuntime = class {
15767
15778
  constructor(store, events, leases, environmentId, turnRunner, opts) {
@@ -15771,7 +15782,6 @@ var init_session_runtime = __esm({
15771
15782
  this.environmentId = environmentId;
15772
15783
  this.turnRunner = turnRunner;
15773
15784
  this.defaultApiProviderId = opts?.defaultApiProviderId;
15774
- this.permissionTimeoutMs = opts?.permissionTimeoutMs ?? DEFAULT_PERMISSION_TIMEOUT_MS;
15775
15785
  this.agentsConfirmTimeoutMs = opts?.agentsConfirmTimeoutMs ?? DEFAULT_AGENTS_CONFIRM_TIMEOUT_MS;
15776
15786
  this.hostActions = opts?.hostActions ?? null;
15777
15787
  this.hydrateFromStore();
@@ -15806,14 +15816,13 @@ var init_session_runtime = __esm({
15806
15816
  disposing = false;
15807
15817
  /**
15808
15818
  * Active permission waiters keyed by interactionId.
15809
- * respondPermission resolves these; timeout/abort/close deny them.
15819
+ * respondPermission resolves these; abort/close deny them.
15810
15820
  */
15811
15821
  permissionWaiters = /* @__PURE__ */ new Map();
15812
15822
  questionWaiters = /* @__PURE__ */ new Map();
15813
15823
  planWaiters = /* @__PURE__ */ new Map();
15814
15824
  agentsConfirmWaiters = /* @__PURE__ */ new Map();
15815
15825
  defaultApiProviderId;
15816
- permissionTimeoutMs;
15817
15826
  agentsConfirmTimeoutMs;
15818
15827
  hostActions;
15819
15828
  /** Live waiters for requestHostAction terminal settlement. */
@@ -16422,18 +16431,18 @@ var init_session_runtime = __esm({
16422
16431
  return { delivered: true };
16423
16432
  }
16424
16433
  appendUserMessage(session, opts) {
16425
- const displayText = opts.source === "task-notification" ? redactTaskNotificationForDisplay(opts.text) : opts.text;
16434
+ if (opts.source === "task-notification" && isModelOnlyHostWake(opts.text)) return;
16426
16435
  const userBlock = {
16427
16436
  id: randomUUID5(),
16428
16437
  role: "user",
16429
- text: displayText,
16438
+ text: opts.text,
16430
16439
  createdAt: Date.now()
16431
16440
  };
16432
16441
  session.transcript.push(userBlock);
16433
16442
  session.updatedAt = Date.now();
16434
16443
  let autoTitle = null;
16435
16444
  if (!session.title || !session.title.trim()) {
16436
- autoTitle = deriveSessionTitleFromUserText(displayText);
16445
+ autoTitle = deriveSessionTitleFromUserText(opts.text);
16437
16446
  if (autoTitle) session.title = autoTitle;
16438
16447
  }
16439
16448
  this.persist(session);
@@ -16442,7 +16451,7 @@ var init_session_runtime = __esm({
16442
16451
  eventType: SESSION_DURABLE_EVENT.userMessage,
16443
16452
  payload: {
16444
16453
  blockId: userBlock.id,
16445
- text: displayText,
16454
+ text: opts.text,
16446
16455
  ...opts.source ? { source: opts.source } : {}
16447
16456
  },
16448
16457
  causationRequestId: opts.requestId
@@ -16651,7 +16660,7 @@ var init_session_runtime = __esm({
16651
16660
  this.aborts.delete(sessionId);
16652
16661
  session.closed = true;
16653
16662
  session.status = "ended";
16654
- this.rejectPendingPermission(session, "aborted");
16663
+ this.rejectPendingPermission(session);
16655
16664
  this.cancelHostActionsForSession(sessionId, "session_closed");
16656
16665
  session.updatedAt = Date.now();
16657
16666
  this.persist(session);
@@ -17079,11 +17088,11 @@ var init_session_runtime = __esm({
17079
17088
  generation: input.generation,
17080
17089
  holderClientId: input.client.clientSessionId
17081
17090
  });
17082
- const pending = session.pendingInteraction;
17083
- if (!pending || pending.interactionId !== input.interactionId) {
17091
+ const pending2 = session.pendingInteraction;
17092
+ if (!pending2 || pending2.interactionId !== input.interactionId) {
17084
17093
  throw Object.assign(new Error("no matching pending permission"), { code: "failed_precondition" });
17085
17094
  }
17086
- if (pending.kind === "session_agents_confirm") {
17095
+ if (pending2.kind === "session_agents_confirm") {
17087
17096
  const waiter2 = this.agentsConfirmWaiters.get(input.interactionId);
17088
17097
  if (!waiter2 || waiter2.sessionId !== input.sessionId) {
17089
17098
  throw Object.assign(new Error("no matching pending agents confirm"), {
@@ -17101,7 +17110,7 @@ var init_session_runtime = __esm({
17101
17110
  });
17102
17111
  return;
17103
17112
  }
17104
- if (pending.kind && pending.kind !== "permission") {
17113
+ if (pending2.kind && pending2.kind !== "permission") {
17105
17114
  throw Object.assign(new Error("no matching pending permission"), { code: "failed_precondition" });
17106
17115
  }
17107
17116
  const waiter = this.permissionWaiters.get(input.interactionId);
@@ -17138,7 +17147,7 @@ var init_session_runtime = __esm({
17138
17147
  );
17139
17148
  }
17140
17149
  if (session.pendingInteraction) {
17141
- this.rejectPendingPermission(session, "aborted");
17150
+ this.rejectPendingPermission(session);
17142
17151
  }
17143
17152
  const interaction = {
17144
17153
  interactionId: `sessionagents_${Date.now()}_${randomUUID5().slice(0, 8)}`,
@@ -17264,11 +17273,11 @@ var init_session_runtime = __esm({
17264
17273
  }
17265
17274
  /**
17266
17275
  * Block the turn until the control-lease holder responds, or until
17267
- * timeout / abort / session close. Always emits a durable permission event.
17276
+ * abort / session close. Always emits a durable permission event.
17268
17277
  */
17269
17278
  waitForPermissionDecision(session, interaction, signal, requestId) {
17270
17279
  if (session.pendingInteraction) {
17271
- this.rejectPendingPermission(session, "aborted");
17280
+ this.rejectPendingPermission(session);
17272
17281
  }
17273
17282
  return new Promise((resolve17) => {
17274
17283
  let settled = false;
@@ -17284,11 +17293,7 @@ var init_session_runtime = __esm({
17284
17293
  const settle = (result) => {
17285
17294
  if (settled) return;
17286
17295
  settled = true;
17287
- const waiter = this.permissionWaiters.get(interaction.interactionId);
17288
- if (waiter) {
17289
- clearTimeout(waiter.timer);
17290
- this.permissionWaiters.delete(interaction.interactionId);
17291
- }
17296
+ this.permissionWaiters.delete(interaction.interactionId);
17292
17297
  if (session.pendingInteraction?.interactionId === interaction.interactionId) {
17293
17298
  session.pendingInteraction = null;
17294
17299
  session.updatedAt = Date.now();
@@ -17303,12 +17308,6 @@ var init_session_runtime = __esm({
17303
17308
  decision: result.clientDecision ?? result.decision
17304
17309
  }
17305
17310
  });
17306
- } else if (result.reason === "timeout") {
17307
- this.events.appendSession({
17308
- sessionId: session.sessionId,
17309
- eventType: SESSION_DURABLE_EVENT.permissionTimeout,
17310
- payload: { interactionId: interaction.interactionId, decision: "deny" }
17311
- });
17312
17311
  } else {
17313
17312
  this.events.appendSession({
17314
17313
  sessionId: session.sessionId,
@@ -17318,13 +17317,9 @@ var init_session_runtime = __esm({
17318
17317
  }
17319
17318
  resolve17(result.decision);
17320
17319
  };
17321
- const timer = setTimeout(() => {
17322
- settle({ decision: "deny", reason: "timeout" });
17323
- }, this.permissionTimeoutMs);
17324
17320
  this.permissionWaiters.set(interaction.interactionId, {
17325
17321
  sessionId: session.sessionId,
17326
- settle,
17327
- timer
17322
+ settle
17328
17323
  });
17329
17324
  if (signal.aborted) {
17330
17325
  settle({ decision: "deny", reason: "aborted" });
@@ -17340,59 +17335,55 @@ var init_session_runtime = __esm({
17340
17335
  });
17341
17336
  }
17342
17337
  /** Deny and clear any active permission waiter for this session. */
17343
- rejectPendingPermission(session, reason) {
17344
- const pending = session.pendingInteraction;
17345
- if (!pending) return;
17346
- if (pending.kind === "question") {
17347
- const qw = this.questionWaiters.get(pending.interactionId);
17348
- if (qw) qw.settle({ answers: {}, reason });
17338
+ rejectPendingPermission(session) {
17339
+ const pending2 = session.pendingInteraction;
17340
+ if (!pending2) return;
17341
+ if (pending2.kind === "question") {
17342
+ const qw = this.questionWaiters.get(pending2.interactionId);
17343
+ if (qw) qw.settle({ answers: {}, reason: "aborted" });
17349
17344
  else session.pendingInteraction = null;
17350
17345
  return;
17351
17346
  }
17352
- if (pending.kind === "plan") {
17353
- const pw = this.planWaiters.get(pending.interactionId);
17354
- if (pw) pw.settle({ decision: "reject", reason });
17347
+ if (pending2.kind === "plan") {
17348
+ const pw = this.planWaiters.get(pending2.interactionId);
17349
+ if (pw) pw.settle({ decision: "reject", reason: "aborted" });
17355
17350
  else session.pendingInteraction = null;
17356
17351
  return;
17357
17352
  }
17358
- if (pending.kind === "session_agents_confirm") {
17359
- const aw = this.agentsConfirmWaiters.get(pending.interactionId);
17360
- if (aw) aw.settle({ action: "cancel", reason });
17353
+ if (pending2.kind === "session_agents_confirm") {
17354
+ const aw = this.agentsConfirmWaiters.get(pending2.interactionId);
17355
+ if (aw) aw.settle({ action: "cancel", reason: "aborted" });
17361
17356
  else session.pendingInteraction = null;
17362
17357
  return;
17363
17358
  }
17364
- const waiter = this.permissionWaiters.get(pending.interactionId);
17359
+ const waiter = this.permissionWaiters.get(pending2.interactionId);
17365
17360
  if (waiter) {
17366
- waiter.settle({ decision: "deny", reason });
17361
+ waiter.settle({ decision: "deny", reason: "aborted" });
17367
17362
  } else {
17368
17363
  session.pendingInteraction = null;
17369
17364
  }
17370
17365
  }
17371
17366
  waitForQuestionDecision(session, interaction, signal, requestId) {
17372
17367
  if (session.pendingInteraction) {
17373
- this.rejectPendingPermission(session, "aborted");
17368
+ this.rejectPendingPermission(session);
17374
17369
  }
17375
- const pending = { ...interaction, kind: "question" };
17370
+ const pending2 = { ...interaction, kind: "question" };
17376
17371
  return new Promise((resolve17) => {
17377
17372
  let settled = false;
17378
- session.pendingInteraction = pending;
17373
+ session.pendingInteraction = pending2;
17379
17374
  session.updatedAt = Date.now();
17380
17375
  this.persist(session);
17381
17376
  this.events.appendSession({
17382
17377
  sessionId: session.sessionId,
17383
17378
  eventType: SESSION_DURABLE_EVENT.questionRequested,
17384
- payload: pending,
17379
+ payload: pending2,
17385
17380
  causationRequestId: requestId
17386
17381
  });
17387
17382
  const settle = (result) => {
17388
17383
  if (settled) return;
17389
17384
  settled = true;
17390
- const waiter = this.questionWaiters.get(pending.interactionId);
17391
- if (waiter) {
17392
- clearTimeout(waiter.timer);
17393
- this.questionWaiters.delete(pending.interactionId);
17394
- }
17395
- if (session.pendingInteraction?.interactionId === pending.interactionId) {
17385
+ this.questionWaiters.delete(pending2.interactionId);
17386
+ if (session.pendingInteraction?.interactionId === pending2.interactionId) {
17396
17387
  session.pendingInteraction = null;
17397
17388
  session.updatedAt = Date.now();
17398
17389
  this.persist(session);
@@ -17401,30 +17392,20 @@ var init_session_runtime = __esm({
17401
17392
  this.events.appendSession({
17402
17393
  sessionId: session.sessionId,
17403
17394
  eventType: SESSION_DURABLE_EVENT.questionResponded,
17404
- payload: { interactionId: pending.interactionId, answers: result.answers }
17405
- });
17406
- } else if (result.reason === "timeout") {
17407
- this.events.appendSession({
17408
- sessionId: session.sessionId,
17409
- eventType: SESSION_DURABLE_EVENT.questionTimeout,
17410
- payload: { interactionId: pending.interactionId }
17395
+ payload: { interactionId: pending2.interactionId, answers: result.answers }
17411
17396
  });
17412
17397
  } else {
17413
17398
  this.events.appendSession({
17414
17399
  sessionId: session.sessionId,
17415
17400
  eventType: SESSION_DURABLE_EVENT.questionAborted,
17416
- payload: { interactionId: pending.interactionId }
17401
+ payload: { interactionId: pending2.interactionId }
17417
17402
  });
17418
17403
  }
17419
17404
  resolve17(result.answers);
17420
17405
  };
17421
- const timer = setTimeout(() => {
17422
- settle({ answers: {}, reason: "timeout" });
17423
- }, this.permissionTimeoutMs);
17424
- this.questionWaiters.set(pending.interactionId, {
17406
+ this.questionWaiters.set(pending2.interactionId, {
17425
17407
  sessionId: session.sessionId,
17426
- settle,
17427
- timer
17408
+ settle
17428
17409
  });
17429
17410
  if (signal.aborted) {
17430
17411
  settle({ answers: {}, reason: "aborted" });
@@ -17441,29 +17422,25 @@ var init_session_runtime = __esm({
17441
17422
  }
17442
17423
  waitForPlanDecision(session, interaction, signal, requestId) {
17443
17424
  if (session.pendingInteraction) {
17444
- this.rejectPendingPermission(session, "aborted");
17425
+ this.rejectPendingPermission(session);
17445
17426
  }
17446
- const pending = { ...interaction, kind: "plan" };
17427
+ const pending2 = { ...interaction, kind: "plan" };
17447
17428
  return new Promise((resolve17) => {
17448
17429
  let settled = false;
17449
- session.pendingInteraction = pending;
17430
+ session.pendingInteraction = pending2;
17450
17431
  session.updatedAt = Date.now();
17451
17432
  this.persist(session);
17452
17433
  this.events.appendSession({
17453
17434
  sessionId: session.sessionId,
17454
17435
  eventType: SESSION_DURABLE_EVENT.planRequested,
17455
- payload: pending,
17436
+ payload: pending2,
17456
17437
  causationRequestId: requestId
17457
17438
  });
17458
17439
  const settle = (result) => {
17459
17440
  if (settled) return;
17460
17441
  settled = true;
17461
- const waiter = this.planWaiters.get(pending.interactionId);
17462
- if (waiter) {
17463
- clearTimeout(waiter.timer);
17464
- this.planWaiters.delete(pending.interactionId);
17465
- }
17466
- if (session.pendingInteraction?.interactionId === pending.interactionId) {
17442
+ this.planWaiters.delete(pending2.interactionId);
17443
+ if (session.pendingInteraction?.interactionId === pending2.interactionId) {
17467
17444
  session.pendingInteraction = null;
17468
17445
  session.updatedAt = Date.now();
17469
17446
  this.persist(session);
@@ -17473,33 +17450,23 @@ var init_session_runtime = __esm({
17473
17450
  sessionId: session.sessionId,
17474
17451
  eventType: SESSION_DURABLE_EVENT.planResponded,
17475
17452
  payload: {
17476
- interactionId: pending.interactionId,
17453
+ interactionId: pending2.interactionId,
17477
17454
  decision: result.decision,
17478
17455
  options: result.options
17479
17456
  }
17480
17457
  });
17481
- } else if (result.reason === "timeout") {
17482
- this.events.appendSession({
17483
- sessionId: session.sessionId,
17484
- eventType: SESSION_DURABLE_EVENT.planTimeout,
17485
- payload: { interactionId: pending.interactionId, decision: "reject" }
17486
- });
17487
17458
  } else {
17488
17459
  this.events.appendSession({
17489
17460
  sessionId: session.sessionId,
17490
17461
  eventType: SESSION_DURABLE_EVENT.planAborted,
17491
- payload: { interactionId: pending.interactionId, decision: "reject" }
17462
+ payload: { interactionId: pending2.interactionId, decision: "reject" }
17492
17463
  });
17493
17464
  }
17494
17465
  resolve17({ decision: result.decision, options: result.options });
17495
17466
  };
17496
- const timer = setTimeout(() => {
17497
- settle({ decision: "reject", reason: "timeout" });
17498
- }, this.permissionTimeoutMs);
17499
- this.planWaiters.set(pending.interactionId, {
17467
+ this.planWaiters.set(pending2.interactionId, {
17500
17468
  sessionId: session.sessionId,
17501
- settle,
17502
- timer
17469
+ settle
17503
17470
  });
17504
17471
  if (signal.aborted) {
17505
17472
  settle({ decision: "reject", reason: "aborted" });
@@ -18696,7 +18663,7 @@ var init_relay_identify = __esm({
18696
18663
 
18697
18664
  // ../../packages/shared/src/platform-registry/types.ts
18698
18665
  var CONSUMER_TASK;
18699
- var init_types2 = __esm({
18666
+ var init_types = __esm({
18700
18667
  "../../packages/shared/src/platform-registry/types.ts"() {
18701
18668
  "use strict";
18702
18669
  CONSUMER_TASK = {
@@ -19027,10 +18994,10 @@ var init_builtin = __esm({
19027
18994
  init_effective_endpoints();
19028
18995
  init_protocols();
19029
18996
  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" }
18997
+ default: { id: "mimo-v2.6-pro[1m]", name: "MiMo V2.6 Pro" },
18998
+ opus: { id: "mimo-v2.6-pro[1m]", name: "MiMo V2.6 Pro" },
18999
+ sonnet: { id: "mimo-v2.6-pro[1m]", name: "MiMo V2.6 Pro" },
19000
+ haiku: { id: "mimo-v2.6-pro", name: "MiMo V2.6 Pro" }
19034
19001
  };
19035
19002
  BAILIAN_CODING_PLAN_MODELS = {
19036
19003
  default: { id: "qwen3.7-plus", name: "Qwen 3.7 Plus" },
@@ -19535,6 +19502,7 @@ var init_builtin = __esm({
19535
19502
  auth: "api-key",
19536
19503
  baseUrl: "https://api.minimax.cn",
19537
19504
  apiKeyUrl: "https://platform.minimax.cn/user-center/basic-information/interface-key",
19505
+ catalogProviderId: "minimax-cn",
19538
19506
  endpoints: [
19539
19507
  anthropic("/anthropic", {
19540
19508
  extraEnv: MINIMAX_EXTRA_ENV,
@@ -19552,6 +19520,7 @@ var init_builtin = __esm({
19552
19520
  auth: "api-key",
19553
19521
  baseUrl: "https://api.minimax.io",
19554
19522
  apiKeyUrl: "https://platform.minimax.io/user-center/basic-information/interface-key",
19523
+ catalogProviderId: "minimax",
19555
19524
  endpoints: [
19556
19525
  anthropic("/anthropic", {
19557
19526
  extraEnv: MINIMAX_EXTRA_ENV,
@@ -19648,6 +19617,7 @@ var init_builtin = __esm({
19648
19617
  auth: "api-key",
19649
19618
  baseUrl: "https://token-plan-cn.xiaomimimo.com",
19650
19619
  apiKeyUrl: "https://platform.xiaomimimo.com/#/console/api-keys",
19620
+ catalogProviderId: "xiaomi-token-plan-cn",
19651
19621
  endpoints: [
19652
19622
  anthropic("/anthropic", {
19653
19623
  extraEnv: { ...EMPTY_AUTH_TOKEN },
@@ -19661,6 +19631,7 @@ var init_builtin = __esm({
19661
19631
  auth: "api-key",
19662
19632
  baseUrl: "https://api.xiaomimimo.com",
19663
19633
  apiKeyUrl: "https://platform.xiaomimimo.com/#/console/api-keys",
19634
+ catalogProviderId: "xiaomi",
19664
19635
  endpoints: [
19665
19636
  anthropic("/anthropic", {
19666
19637
  extraEnv: { ...EMPTY_AUTH_TOKEN },
@@ -19779,6 +19750,7 @@ var init_builtin = __esm({
19779
19750
  brand: "longcat",
19780
19751
  name: "Longcat",
19781
19752
  description: "Longcat \u2014 \u957F\u732B AI \u7F16\u7A0B\u52A9\u624B",
19753
+ catalogProviderId: "longcat",
19782
19754
  plans: [
19783
19755
  {
19784
19756
  id: "coding",
@@ -19883,6 +19855,7 @@ var init_builtin = __esm({
19883
19855
  brand: "modelscope",
19884
19856
  name: "ModelScope",
19885
19857
  description: "ModelScope \u9B54\u642D \u2014 \u963F\u91CC\u5DF4\u5DF4\u6A21\u578B\u805A\u5408\u5E73\u53F0",
19858
+ catalogProviderId: "modelscope",
19886
19859
  plans: [
19887
19860
  {
19888
19861
  id: "api",
@@ -19909,6 +19882,7 @@ var init_builtin = __esm({
19909
19882
  brand: "siliconcloud",
19910
19883
  name: "SiliconFlow",
19911
19884
  description: "SiliconFlow \u7845\u57FA\u6D41\u52A8 \u2014 AI \u6A21\u578B\u805A\u5408\u63A8\u7406\u5E73\u53F0",
19885
+ catalogProviderId: "siliconflow-cn",
19912
19886
  plans: [
19913
19887
  {
19914
19888
  id: "api",
@@ -19958,6 +19932,7 @@ var init_builtin = __esm({
19958
19932
  brand: "bedrock",
19959
19933
  name: "AWS Bedrock",
19960
19934
  description: "Amazon Bedrock \u2014 run Claude on AWS infrastructure with IAM authentication",
19935
+ catalogProviderId: "amazon-bedrock",
19961
19936
  plans: [
19962
19937
  {
19963
19938
  id: "aws",
@@ -19979,6 +19954,7 @@ var init_builtin = __esm({
19979
19954
  brand: "vertexai",
19980
19955
  name: "Google Vertex",
19981
19956
  description: "Google Vertex AI \u2014 run Claude on GCP infrastructure with service account authentication",
19957
+ catalogProviderId: "google-vertex-anthropic",
19982
19958
  plans: [
19983
19959
  {
19984
19960
  id: "gcp",
@@ -20118,9 +20094,9 @@ var init_platform_registry = __esm({
20118
20094
  "use strict";
20119
20095
  init_relay_identify();
20120
20096
  init_protocols();
20121
- init_types2();
20097
+ init_types();
20122
20098
  init_protocols();
20123
- init_types2();
20099
+ init_types();
20124
20100
  init_capabilities2();
20125
20101
  init_claude_env();
20126
20102
  init_merge();
@@ -64627,6 +64603,23 @@ var init_cursor_store = __esm({
64627
64603
  }
64628
64604
  });
64629
64605
 
64606
+ // ../../packages/cursor/src/cursor-sdk.ts
64607
+ function loadCursorSdk() {
64608
+ pending ??= import("@cursor/sdk").then((sdk) => loaded = sdk);
64609
+ return pending;
64610
+ }
64611
+ function isCursorSdkError(error51, name) {
64612
+ return loaded !== null && error51 instanceof loaded[name];
64613
+ }
64614
+ var pending, loaded;
64615
+ var init_cursor_sdk = __esm({
64616
+ "../../packages/cursor/src/cursor-sdk.ts"() {
64617
+ "use strict";
64618
+ pending = null;
64619
+ loaded = null;
64620
+ }
64621
+ });
64622
+
64630
64623
  // ../../packages/shared/src/model-option-params.ts
64631
64624
  function parseCatalogContextWindow(raw) {
64632
64625
  if (!raw) return null;
@@ -64677,33 +64670,30 @@ var init_cursor_model_selection = __esm({
64677
64670
  });
64678
64671
 
64679
64672
  // ../../packages/cursor/src/cursor-client.ts
64680
- import { Agent as Agent2, Cursor } from "@cursor/sdk";
64681
64673
  var init_cursor_client = __esm({
64682
64674
  "../../packages/cursor/src/cursor-client.ts"() {
64683
64675
  "use strict";
64676
+ init_cursor_sdk();
64684
64677
  init_cursor_config();
64685
64678
  init_cursor_model_selection();
64686
64679
  }
64687
64680
  });
64688
64681
 
64689
64682
  // ../../packages/cursor/src/cursor-cloud.ts
64690
- import {
64691
- Agent as Agent3,
64692
- Cursor as Cursor2
64693
- } from "@cursor/sdk";
64694
64683
  var init_cursor_cloud = __esm({
64695
64684
  "../../packages/cursor/src/cursor-cloud.ts"() {
64696
64685
  "use strict";
64686
+ init_cursor_sdk();
64697
64687
  init_cursor_config();
64698
64688
  init_cursor_store();
64699
64689
  }
64700
64690
  });
64701
64691
 
64702
64692
  // ../../packages/cursor/src/cursor-sdk-auth.ts
64703
- import { Cursor as Cursor3 } from "@cursor/sdk";
64704
64693
  var init_cursor_sdk_auth = __esm({
64705
64694
  "../../packages/cursor/src/cursor-sdk-auth.ts"() {
64706
64695
  "use strict";
64696
+ init_cursor_sdk();
64707
64697
  }
64708
64698
  });
64709
64699
 
@@ -64742,9 +64732,7 @@ var init_cursor_local_options = __esm({
64742
64732
  });
64743
64733
 
64744
64734
  // ../../packages/cursor/src/cursor-network-retry.ts
64745
- import { NetworkError } from "@cursor/sdk";
64746
64735
  function isCursorRetryableNetworkError(error51) {
64747
- if (error51 instanceof NetworkError) return error51.isRetryable;
64748
64736
  if (!error51 || typeof error51 !== "object") return false;
64749
64737
  const name = "name" in error51 ? String(error51.name) : "";
64750
64738
  if (name !== "NetworkError") return false;
@@ -64828,14 +64816,8 @@ var init_cursor_sdk_trace = __esm({
64828
64816
  });
64829
64817
 
64830
64818
  // ../../packages/cursor/src/cursor-runtime.ts
64831
- import {
64832
- Agent as Agent4,
64833
- AgentBusyError,
64834
- Cursor as Cursor4,
64835
- IntegrationNotConnectedError
64836
- } from "@cursor/sdk";
64837
64819
  function formatCursorError(error51) {
64838
- if (error51 instanceof IntegrationNotConnectedError) {
64820
+ if (isCursorSdkError(error51, "IntegrationNotConnectedError")) {
64839
64821
  return new CursorIntegrationError(
64840
64822
  `${error51.message} Connect ${error51.provider} at ${error51.helpUrl} then retry.`,
64841
64823
  { provider: error51.provider, helpUrl: error51.helpUrl }
@@ -64863,9 +64845,10 @@ async function createCursorRuntime(opts) {
64863
64845
  "Cursor User API Key missing. Create one at https://cursor.com/dashboard/api, set it on the Cursor provider, or export CURSOR_API_KEY."
64864
64846
  );
64865
64847
  }
64848
+ const { Agent: Agent2, Cursor } = await loadCursorSdk();
64866
64849
  if (plan.config.useHttp1ForAgent != null) {
64867
64850
  try {
64868
- Cursor4.configure({ local: { useHttp1ForAgent: plan.config.useHttp1ForAgent } });
64851
+ Cursor.configure({ local: { useHttp1ForAgent: plan.config.useHttp1ForAgent } });
64869
64852
  } catch (error51) {
64870
64853
  log2.debug("[CursorRuntime] Cursor.configure useHttp1ForAgent failed:", error51);
64871
64854
  }
@@ -64917,7 +64900,7 @@ async function createCursorRuntime(opts) {
64917
64900
  }, opts.sessionId);
64918
64901
  const openAgent = (sandbox) => withCursorPlatformLookup(async () => {
64919
64902
  if (opts.providerSessionId) {
64920
- return Agent4.resume(opts.providerSessionId, {
64903
+ return Agent2.resume(opts.providerSessionId, {
64921
64904
  apiKey,
64922
64905
  ...model ? { model } : {},
64923
64906
  ...agentName ? { name: agentName } : {},
@@ -64940,7 +64923,7 @@ async function createCursorRuntime(opts) {
64940
64923
  });
64941
64924
  }
64942
64925
  if (isCloud) {
64943
- return Agent4.create({
64926
+ return Agent2.create({
64944
64927
  apiKey,
64945
64928
  ...model ? { model } : {},
64946
64929
  ...agentName ? { name: agentName } : {},
@@ -64949,7 +64932,7 @@ async function createCursorRuntime(opts) {
64949
64932
  cloud: buildCloudOptions(config2)
64950
64933
  });
64951
64934
  }
64952
- return Agent4.create({
64935
+ return Agent2.create({
64953
64936
  apiKey,
64954
64937
  model,
64955
64938
  ...agentName ? { name: agentName } : {},
@@ -65132,7 +65115,7 @@ ${text2}` : text2;
65132
65115
  try {
65133
65116
  run = await agent.send(userMessage2, sendOptions);
65134
65117
  } catch (error51) {
65135
- if (error51 instanceof AgentBusyError && !sendOpts?.force && !isCloud) {
65118
+ if (isCursorSdkError(error51, "AgentBusyError") && !sendOpts?.force && !isCloud) {
65136
65119
  log2.warn("[CursorRuntime] AgentBusyError \u2014 retrying with local.force");
65137
65120
  tracer.runtime("agent_busy_retry", {
65138
65121
  message: error51 instanceof Error ? error51.message : String(error51)
@@ -65303,6 +65286,7 @@ var init_cursor_runtime = __esm({
65303
65286
  "../../packages/cursor/src/cursor-runtime.ts"() {
65304
65287
  "use strict";
65305
65288
  init_superone_system_prompt();
65289
+ init_cursor_sdk();
65306
65290
  init_cursor_config();
65307
65291
  init_cursor_local_options();
65308
65292
  init_cursor_network_retry();
@@ -65335,10 +65319,10 @@ var init_cursor_runtime = __esm({
65335
65319
  });
65336
65320
 
65337
65321
  // ../../packages/cursor/src/cursor-workspace-prewarm.ts
65338
- import { createAgentPlatform } from "@cursor/sdk";
65339
65322
  var init_cursor_workspace_prewarm = __esm({
65340
65323
  "../../packages/cursor/src/cursor-workspace-prewarm.ts"() {
65341
65324
  "use strict";
65325
+ init_cursor_sdk();
65342
65326
  init_cursor_sdk_trace();
65343
65327
  init_cursor_local_options();
65344
65328
  init_cursor_platform_binaries();
@@ -65486,6 +65470,7 @@ var init_src5 = __esm({
65486
65470
  init_cursor_event_map();
65487
65471
  init_cursor_store();
65488
65472
  init_cursor_client();
65473
+ init_cursor_sdk();
65489
65474
  init_cursor_model_selection();
65490
65475
  init_cursor_cloud();
65491
65476
  init_cursor_sdk_auth();
@@ -71246,8 +71231,8 @@ var require_multipleOf = __commonJS({
71246
71231
  const { gen, data, schemaCode, it: it2 } = cxt;
71247
71232
  const prec = it2.opts.multipleOfPrecision;
71248
71233
  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}))`);
71234
+ const invalid3 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
71235
+ cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid3}))`);
71251
71236
  }
71252
71237
  };
71253
71238
  exports.default = def;
@@ -73403,8 +73388,8 @@ import { fileURLToPath } from "node:url";
73403
73388
  function resolveCliReleaseVersion() {
73404
73389
  const fromEnv = process.env.SUPERONE_CLI_VERSION?.trim();
73405
73390
  if (fromEnv) return fromEnv;
73406
- if ("0.68.0".trim()) {
73407
- return "0.68.0".trim();
73391
+ if ("0.69.0-alpha.1".trim()) {
73392
+ return "0.69.0-alpha.1".trim();
73408
73393
  }
73409
73394
  const fromDist = readDistManifestVersion();
73410
73395
  if (fromDist) return fromDist;
@@ -74599,7 +74584,7 @@ CREATE TABLE IF NOT EXISTS session_collaboration_grants (
74599
74584
  credential_secret TEXT,
74600
74585
  credential_hint TEXT NOT NULL,
74601
74586
  parent_session_id TEXT NOT NULL,
74602
- child_session_id TEXT UNIQUE,
74587
+ child_session_id TEXT,
74603
74588
  agent_id TEXT NOT NULL,
74604
74589
  task TEXT NOT NULL,
74605
74590
  config_json TEXT NOT NULL,
@@ -74684,6 +74669,66 @@ CREATE INDEX IF NOT EXISTS idx_session_providers_harness ON session_providers(ha
74684
74669
  `;
74685
74670
  var SCHEMA_GENERATION = 1;
74686
74671
 
74672
+ // ../../packages/runtime/src/collaboration/schema.ts
74673
+ var GRANTS_TABLE = "session_collaboration_grants";
74674
+ function ensureCollaborationGrantUniqueness(db, { warn = console.warn } = {}) {
74675
+ const rebuiltDdl = hasColumnUniqueChild(db) ? ddlWithoutColumnUnique(db) : null;
74676
+ if (rebuiltDdl === void 0) {
74677
+ warn(`[collaboration] ${GRANTS_TABLE}: unrecognized child_session_id UNIQUE DDL; keeping one link per peer`);
74678
+ }
74679
+ const apply = () => {
74680
+ if (rebuiltDdl) rebuildTable(db, rebuiltDdl);
74681
+ db.exec(`
74682
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_session_collaboration_spawn_child
74683
+ ON ${GRANTS_TABLE}(child_session_id) WHERE kind = 'spawn';
74684
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_session_collaboration_link_pair
74685
+ ON ${GRANTS_TABLE}(parent_session_id, child_session_id) WHERE kind = 'link';
74686
+ `);
74687
+ };
74688
+ const foreignKeysOn = Boolean(rebuiltDdl) && db.pragma("foreign_keys", { simple: true }) === 1;
74689
+ if (db.inTransaction) {
74690
+ if (foreignKeysOn) {
74691
+ throw new Error(`Rebuilding ${GRANTS_TABLE} inside a transaction requires foreign keys off`);
74692
+ }
74693
+ apply();
74694
+ return;
74695
+ }
74696
+ if (foreignKeysOn) db.pragma("foreign_keys = OFF");
74697
+ try {
74698
+ db.transaction(apply)();
74699
+ } finally {
74700
+ if (foreignKeysOn) db.pragma("foreign_keys = ON");
74701
+ }
74702
+ }
74703
+ function hasColumnUniqueChild(db) {
74704
+ const indexes = db.prepare(`PRAGMA index_list(${GRANTS_TABLE})`).all();
74705
+ return indexes.some((index) => {
74706
+ if (index.origin !== "u") return false;
74707
+ const columns = db.prepare(`PRAGMA index_info(${JSON.stringify(index.name)})`).all();
74708
+ return columns.length === 1 && columns[0].name === "child_session_id";
74709
+ });
74710
+ }
74711
+ var REBUILT_TABLE = `${GRANTS_TABLE}_rebuild`;
74712
+ function ddlWithoutColumnUnique(db) {
74713
+ const { sql } = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`).get(GRANTS_TABLE);
74714
+ const withoutUnique = sql.replace(/(\bchild_session_id\s+TEXT)\s+UNIQUE\b/i, "$1");
74715
+ const renamed = withoutUnique.replace(
74716
+ /^CREATE TABLE\s+(?:IF NOT EXISTS\s+)?["`]?session_collaboration_grants["`]?/i,
74717
+ `CREATE TABLE ${REBUILT_TABLE}`
74718
+ );
74719
+ return withoutUnique !== sql && renamed !== withoutUnique ? renamed : void 0;
74720
+ }
74721
+ function rebuildTable(db, ddl) {
74722
+ const indexes = db.prepare(`
74723
+ SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
74724
+ `).all(GRANTS_TABLE);
74725
+ db.exec(ddl);
74726
+ db.exec(`INSERT INTO ${REBUILT_TABLE} SELECT * FROM ${GRANTS_TABLE}`);
74727
+ db.exec("DROP TABLE session_collaboration_grants");
74728
+ db.exec(`ALTER TABLE ${REBUILT_TABLE} RENAME TO session_collaboration_grants`);
74729
+ for (const index of indexes) db.exec(index.sql);
74730
+ }
74731
+
74687
74732
  // ../../packages/runtime/src/db/database.ts
74688
74733
  init_session_provider_definitions();
74689
74734
  init_platform_registry();
@@ -74920,7 +74965,7 @@ CREATE TABLE IF NOT EXISTS session_collaboration_grants (
74920
74965
  credential_secret TEXT,
74921
74966
  credential_hint TEXT NOT NULL,
74922
74967
  parent_session_id TEXT NOT NULL,
74923
- child_session_id TEXT UNIQUE,
74968
+ child_session_id TEXT,
74924
74969
  agent_id TEXT NOT NULL,
74925
74970
  task TEXT NOT NULL,
74926
74971
  config_json TEXT NOT NULL,
@@ -74962,6 +75007,7 @@ CREATE TABLE IF NOT EXISTS session_collaboration_cursors (
74962
75007
  if (!grantCols.some((c) => c.name === "kind")) {
74963
75008
  db.exec(`ALTER TABLE session_collaboration_grants ADD COLUMN kind TEXT NOT NULL DEFAULT 'spawn'`);
74964
75009
  }
75010
+ ensureCollaborationGrantUniqueness(db);
74965
75011
  }
74966
75012
  function ensureSessionTagsColumn(db) {
74967
75013
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
@@ -79999,10 +80045,9 @@ async function handleCollaborationStart(payload, ctx) {
79999
80045
  const denied = requireScopes8(ctx.client, OPERATION_SCOPES.operateSession);
80000
80046
  if (denied) return denied;
80001
80047
  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" } };
80048
+ const launchId = typeof p3.launchId === "string" ? p3.launchId.trim() : "";
80049
+ if (!launchId) {
80050
+ return { error: { code: "invalid_argument", message: "launchId required" } };
80006
80051
  }
80007
80052
  const callerSessionId = typeof p3.callerSessionId === "string" && p3.callerSessionId.trim() ? p3.callerSessionId.trim() : typeof p3.parentSessionId === "string" && p3.parentSessionId.trim() ? p3.parentSessionId.trim() : "";
80008
80053
  if (!callerSessionId) {
@@ -80030,8 +80075,8 @@ async function handleCollaborationStart(payload, ctx) {
80030
80075
  try {
80031
80076
  return {
80032
80077
  result: await ctx.collaboration.start({
80033
- credential,
80034
- grantId,
80078
+ launchId,
80079
+ task: typeof p3.task === "string" ? p3.task : void 0,
80035
80080
  formAnswers: p3.formAnswers && typeof p3.formAnswers === "object" ? p3.formAnswers : void 0,
80036
80081
  callerSessionId,
80037
80082
  controllerClientSessionId: ctx.client.clientSessionId
@@ -80041,36 +80086,39 @@ async function handleCollaborationStart(payload, ctx) {
80041
80086
  return mapThrown8(err);
80042
80087
  }
80043
80088
  }
80089
+ function assertMailboxLease(p3, sessionId, ctx) {
80090
+ const leaseId = String(p3.leaseId ?? "").trim();
80091
+ if (!leaseId) return { error: { code: "invalid_argument", message: "leaseId required" } };
80092
+ try {
80093
+ ctx.leases.assertValid({
80094
+ resource: { environmentId: ctx.identity.environmentId, sessionId },
80095
+ leaseId,
80096
+ generation: String(p3.generation ?? ""),
80097
+ holderClientId: ctx.client.clientSessionId
80098
+ });
80099
+ return null;
80100
+ } catch (err) {
80101
+ return mapThrown8(err);
80102
+ }
80103
+ }
80044
80104
  function handleCollaborationSend(payload, ctx) {
80045
80105
  const denied = requireScopes8(ctx.client, OPERATION_SCOPES.operateSession);
80046
80106
  if (denied) return denied;
80047
80107
  const p3 = asRecord24(payload);
80048
- const credential = String(p3.credential ?? "");
80049
80108
  const sessionId = String(p3.sessionId ?? p3.fromSessionId ?? "");
80050
80109
  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
80110
  if (!sessionId) {
80055
80111
  return { error: { code: "invalid_argument", message: "sessionId required" } };
80056
80112
  }
80057
- const leaseId = String(p3.leaseId ?? "").trim();
80058
- if (!leaseId) {
80059
- return { error: { code: "invalid_argument", message: "leaseId required" } };
80060
- }
80113
+ const leaseDenied = assertMailboxLease(p3, sessionId, ctx);
80114
+ if (leaseDenied) return leaseDenied;
80061
80115
  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
80116
  return {
80069
80117
  result: ctx.collaboration.send({
80070
- credential,
80118
+ sessionId,
80119
+ to: typeof p3.to === "string" ? p3.to : void 0,
80071
80120
  content,
80072
- clientMessageId: typeof p3.clientMessageId === "string" ? p3.clientMessageId : void 0,
80073
- sessionId
80121
+ clientMessageId: typeof p3.clientMessageId === "string" ? p3.clientMessageId : void 0
80074
80122
  })
80075
80123
  };
80076
80124
  } catch (err) {
@@ -80078,22 +80126,20 @@ function handleCollaborationSend(payload, ctx) {
80078
80126
  }
80079
80127
  }
80080
80128
  function handleCollaborationRetrieve(payload, ctx) {
80081
- const denied = requireScopes8(ctx.client, OPERATION_SCOPES.readSession);
80129
+ const denied = requireScopes8(ctx.client, OPERATION_SCOPES.operateSession);
80082
80130
  if (denied) return denied;
80083
80131
  const p3 = asRecord24(payload);
80084
- const credential = String(p3.credential ?? "");
80085
80132
  const sessionId = String(p3.sessionId ?? "");
80086
- if (!credential) {
80087
- return { error: { code: "invalid_argument", message: "credential required" } };
80088
- }
80089
80133
  if (!sessionId) {
80090
80134
  return { error: { code: "invalid_argument", message: "sessionId required" } };
80091
80135
  }
80136
+ const leaseDenied = assertMailboxLease(p3, sessionId, ctx);
80137
+ if (leaseDenied) return leaseDenied;
80092
80138
  try {
80093
80139
  return {
80094
80140
  result: ctx.collaboration.retrieve({
80095
- credential,
80096
80141
  sessionId,
80142
+ from: Array.isArray(p3.from) ? p3.from.filter((id) => typeof id === "string") : void 0,
80097
80143
  max: typeof p3.max === "number" ? p3.max : void 0
80098
80144
  })
80099
80145
  };
@@ -87037,1187 +87083,1148 @@ var SessionRuntime2 = class extends SessionRuntime {
87037
87083
  init_harness_runners();
87038
87084
  init_codex_turn_runner();
87039
87085
 
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 {};
87086
+ // ../../packages/runtime/src/collaboration/errors.ts
87087
+ var CollaborationError = class extends Error {
87088
+ constructor(message, code) {
87089
+ super(message);
87090
+ this.code = code;
87091
+ this.name = "CollaborationError";
87062
87092
  }
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
- }
87093
+ code;
87094
+ };
87095
+
87096
+ // ../../packages/runtime/src/collaboration/launch.ts
87097
+ init_agent_types();
87098
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
87099
+ var LABEL_MAX = 64;
87100
+ var MAX_LAUNCHES_PER_REQUEST = 16;
87101
+ var EDITABLE_PERMISSION_MODES = /* @__PURE__ */ new Set([
87102
+ "default",
87103
+ "acceptEdits",
87104
+ "bypassPermissions",
87105
+ "plan",
87106
+ "dontAsk",
87107
+ "auto",
87108
+ "agent"
87109
+ ]);
87110
+ var EDITABLE_SANDBOX_MODES = /* @__PURE__ */ new Set(["off", "on", "auto"]);
87070
87111
  function resolveLaunchMode(raw) {
87071
87112
  if (raw === "link") return "link";
87072
87113
  if (raw === "handoff") return "handoff";
87073
87114
  return "spawn";
87074
87115
  }
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>`;
87116
+ function humanLaunchId(launchId) {
87117
+ const id = launchId?.trim();
87118
+ return id && id.length <= 32 && !UUID_RE.test(id) ? id : void 0;
87081
87119
  }
87082
87120
  function deriveCollaborationRole(input) {
87083
87121
  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
- }
87122
+ if (explicit) return explicit.slice(0, LABEL_MAX);
87123
+ const fromId = humanLaunchId(input.launchId);
87124
+ if (fromId) return fromId;
87089
87125
  const firstLine = input.task.split(/\n/, 1)[0]?.trim() ?? "";
87090
87126
  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
- }
87127
+ if (youAre?.[1]) return youAre[1].replace(/[.\s]+$/g, "").slice(0, 40);
87094
87128
  return "Agent";
87095
87129
  }
87096
87130
  function deriveCollaborationName(input) {
87097
87131
  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";
87132
+ if (explicit) return explicit.slice(0, LABEL_MAX);
87133
+ return humanLaunchId(input.launchId) ?? "Agent";
87104
87134
  }
87105
87135
  function collaborationSessionTitle(name, role) {
87106
- const n = name.trim() || "Agent";
87107
- const r = role.trim() || "Agent";
87108
- return `${n} - ${r}`;
87136
+ return `${name.trim() || "Agent"} - ${role.trim() || "Agent"}`;
87109
87137
  }
87110
- var CollaborationService = class {
87111
- constructor(deps) {
87112
- this.deps = deps;
87138
+ function parseGrantConfig(raw) {
87139
+ try {
87140
+ return JSON.parse(raw);
87141
+ } catch {
87142
+ return {};
87113
87143
  }
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
- });
87144
+ }
87145
+ function invalid2(message) {
87146
+ throw new CollaborationError(message, "invalid_argument");
87147
+ }
87148
+ function assertLabel(kind, value) {
87149
+ if (value.length > LABEL_MAX) invalid2(`A launch ${kind} may contain at most ${LABEL_MAX} characters`);
87150
+ return value;
87151
+ }
87152
+ function assertLaunchCount(count) {
87153
+ if (count === 0) invalid2("launches must contain at least one proposed session");
87154
+ if (count > MAX_LAUNCHES_PER_REQUEST) {
87155
+ invalid2(`A single request may contain at most ${MAX_LAUNCHES_PER_REQUEST} launches`);
87156
+ }
87157
+ }
87158
+ function normalizeLaunchLabels(mode, launch2, linkPeerTitle) {
87159
+ const summary2 = launch2.summary?.trim();
87160
+ if (!summary2) invalid2("Every launch must include a non-empty summary");
87161
+ if (mode === "link") {
87162
+ return {
87163
+ summary: summary2,
87164
+ name: assertLabel("name", launch2.name?.trim() || linkPeerTitle || ""),
87165
+ role: assertLabel("role", launch2.role?.trim() || "Peer")
87161
87166
  };
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
87167
  }
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" }
87168
+ const name = launch2.name?.trim();
87169
+ if (!name) invalid2(`Every ${mode} launch must include a non-empty name`);
87170
+ const role = launch2.role?.trim();
87171
+ if (!role) invalid2(`Every ${mode} launch must include a non-empty role`);
87172
+ return { summary: summary2, name: assertLabel("name", name), role: assertLabel("role", role) };
87173
+ }
87174
+ function normalizeStartTask(mode, raw) {
87175
+ const task = typeof raw === "string" ? raw.trim() : "";
87176
+ if (!task && mode !== "link") invalid2(`Starting a ${mode} launch requires a non-empty task`);
87177
+ if (task.length > SESSION_AGENT_TASK_MAX) {
87178
+ invalid2(`A launch task may contain at most ${SESSION_AGENT_TASK_MAX.toLocaleString()} characters`);
87179
+ }
87180
+ return task;
87181
+ }
87182
+ function patchEditableLaunchConfig(base, rawPatch) {
87183
+ const patch = rawPatch && typeof rawPatch === "object" ? rawPatch : {};
87184
+ return {
87185
+ ...base,
87186
+ ...typeof patch.model === "string" && patch.model.trim() ? { model: patch.model.trim() } : {},
87187
+ ...typeof patch.effort === "string" && patch.effort.trim() ? { effort: patch.effort.trim() } : {},
87188
+ ...typeof patch.fastMode === "boolean" ? { fastMode: patch.fastMode } : {},
87189
+ ...patch.apiProviderId === null || typeof patch.apiProviderId === "string" ? { apiProviderId: patch.apiProviderId } : {},
87190
+ ...typeof patch.permissionMode === "string" && EDITABLE_PERMISSION_MODES.has(patch.permissionMode) ? { permissionMode: patch.permissionMode } : {},
87191
+ ...typeof patch.sandboxMode === "string" && EDITABLE_SANDBOX_MODES.has(patch.sandboxMode) ? { sandboxMode: patch.sandboxMode } : {}
87192
+ };
87193
+ }
87194
+ function mergeConfirmedLaunches(proposed, content) {
87195
+ const packed = content?.[SESSION_AGENT_LAUNCHES_FIELD];
87196
+ if (typeof packed !== "string") return proposed;
87197
+ let edited;
87198
+ try {
87199
+ edited = JSON.parse(packed);
87200
+ } catch {
87201
+ invalid2("The confirmed launch configuration is invalid");
87202
+ }
87203
+ if (!Array.isArray(edited)) invalid2("The confirmed launch configuration is invalid");
87204
+ if (edited.length !== proposed.length) {
87205
+ invalid2("The confirmed request must contain the same launches that were proposed");
87206
+ }
87207
+ const proposedById = new Map(proposed.map((launch2) => [launch2.launchId, launch2]));
87208
+ const seen = /* @__PURE__ */ new Set();
87209
+ return edited.map((raw) => {
87210
+ if (!raw || typeof raw !== "object") invalid2("The confirmed launch configuration is invalid");
87211
+ const item = raw;
87212
+ const launchId = typeof item.launchId === "string" ? item.launchId : "";
87213
+ const base = proposedById.get(launchId);
87214
+ if (!base) invalid2(`Unknown launchId in confirmed configuration: ${launchId || "(missing)"}`);
87215
+ if (seen.has(launchId)) invalid2("Confirmed launches must have unique launchIds");
87216
+ seen.add(launchId);
87217
+ if (base.mode === "link") return { ...base, config: { ...base.config } };
87218
+ return {
87219
+ ...base,
87220
+ // A tampered mode could turn a supervised child into a one-way handoff.
87221
+ mode: base.mode ?? "spawn",
87222
+ config: patchEditableLaunchConfig(base.config, item.config)
87223
+ };
87224
+ });
87225
+ }
87226
+
87227
+ // ../../packages/runtime/src/collaboration/text.ts
87228
+ 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.';
87229
+ 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.";
87230
+ var NO_PEERS_HINT = "You have no collaboration peers. Launch or link one with session_collab_request, then session_collab_start.";
87231
+ var NESTED_COLLABORATION_UNSUPPORTED = "Nested collaboration is not supported. Only top-level (non-collaboration-child) sessions may request agents.";
87232
+ function collaborationSystemPrompt(parentSessionId) {
87233
+ return `<superone-session-collaboration>
87234
+ You are running as a user-approved child session of SuperOne session ${parentSessionId}.
87235
+ 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.
87236
+ </superone-session-collaboration>`;
87237
+ }
87238
+ function sessionLabel(sessionId, title) {
87239
+ return `SuperOne session ${sessionId} ("${title}")`;
87240
+ }
87241
+ function mailboxWakeText(from) {
87242
+ 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.`;
87243
+ }
87244
+ function linkActivationWakeText(input) {
87245
+ 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.`;
87246
+ }
87247
+ function handoffTaskContent(input) {
87248
+ const title = input.parentTitle || input.parentSessionId.slice(0, 8);
87249
+ 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.
87250
+
87251
+ ` + input.task;
87252
+ }
87253
+
87254
+ // ../../packages/runtime/src/collaboration/mailbox.ts
87255
+ var MAX_MAILBOX_CONTENT = 1e5;
87256
+ var MAX_MESSAGES_PER_RETRIEVE = 100;
87257
+ function describeLaunchedPeer(grant) {
87258
+ const config2 = parseGrantConfig(grant.config_json);
87259
+ const name = deriveCollaborationName({ name: config2.name });
87260
+ const role = deriveCollaborationRole({ role: config2.role, task: grant.task });
87261
+ return { name, role, title: collaborationSessionTitle(name, role), agentId: grant.agent_id, config: config2 };
87262
+ }
87263
+ function describePeerForCaller(grant, callerSessionId, sessionTitle2) {
87264
+ const relation = grant.kind === "link" ? "link" : callerSessionId === grant.child_session_id ? "parent" : "child";
87265
+ if (callerSessionId === grant.child_session_id) {
87266
+ const title = sessionTitle2(grant.parent_session_id)?.trim() || grant.parent_session_id.slice(0, 8);
87267
+ return {
87268
+ sessionId: grant.parent_session_id,
87269
+ name: relation === "parent" ? "Parent" : title,
87270
+ role: relation === "parent" ? "" : "Peer",
87271
+ title,
87272
+ relation
87273
+ };
87274
+ }
87275
+ const child = describeLaunchedPeer(grant);
87276
+ return {
87277
+ sessionId: grant.child_session_id ?? "",
87278
+ name: child.name,
87279
+ role: child.role,
87280
+ title: child.title,
87281
+ relation
87282
+ };
87283
+ }
87284
+ function listMailboxChannels(store, callerSessionId, sessionTitle2) {
87285
+ return store.channelsFor(callerSessionId).map((grant) => ({
87286
+ grant,
87287
+ peer: describePeerForCaller(grant, callerSessionId, sessionTitle2)
87288
+ }));
87289
+ }
87290
+ function peersOf(channels) {
87291
+ return [...new Map(channels.map((channel) => [channel.peer.sessionId, channel.peer])).values()];
87292
+ }
87293
+ function assertNotPeeredElsewhere(store, callerSessionId, peerSessionId) {
87294
+ 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));
87295
+ if (other) {
87296
+ throw new CollaborationError(
87297
+ `Session ${peerSessionId} is already one of your collaboration peers; message it with session_collab_send({ to: ${JSON.stringify(peerSessionId)} }).`,
87298
+ "failed_precondition"
87299
+ );
87300
+ }
87301
+ }
87302
+ function formatPeers(peers) {
87303
+ return peers.map((peer) => `${peer.sessionId} ("${peer.title}", ${peer.relation})`).join("; ");
87304
+ }
87305
+ function resolveSendChannel(store, callerSessionId, to2, sessionTitle2) {
87306
+ const channels = listMailboxChannels(store, callerSessionId, sessionTitle2);
87307
+ const peers = peersOf(channels);
87308
+ const target = to2?.trim();
87309
+ if (!target) {
87310
+ if (peers.length === 0) throw new CollaborationError(NO_PEERS_HINT, "failed_precondition");
87311
+ const parent = channels.find((channel) => channel.peer.relation === "parent");
87312
+ if (parent) return parent;
87313
+ if (peers.length > 1) {
87314
+ throw new CollaborationError(
87315
+ `You have several collaboration peers; pass \`to\` with one of their session ids: ${formatPeers(peers)}`,
87316
+ "invalid_argument"
87206
87317
  );
87207
87318
  }
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
- });
87319
+ return channels[channels.length - 1];
87320
+ }
87321
+ const matches = channels.filter((channel) => channel.peer.sessionId === target);
87322
+ if (matches.length > 0) return matches[matches.length - 1];
87323
+ if (store.isHandoffPair(callerSessionId, target)) {
87324
+ throw new CollaborationError(HANDOFF_NO_MAILBOX, "failed_precondition");
87325
+ }
87326
+ throw new CollaborationError(
87327
+ `Session ${target} is not one of your collaboration peers. ` + (peers.length > 0 ? `Your peers: ${formatPeers(peers)}` : NO_PEERS_HINT),
87328
+ "forbidden"
87329
+ );
87330
+ }
87331
+ function normalizeMailboxContent(raw) {
87332
+ const content = typeof raw === "string" ? raw.trim() : "";
87333
+ if (!content) throw new CollaborationError("content must not be empty", "invalid_argument");
87334
+ if (content.length > MAX_MAILBOX_CONTENT) {
87335
+ throw new CollaborationError("content may contain at most 100,000 characters", "invalid_argument");
87336
+ }
87337
+ return content;
87338
+ }
87339
+ function readCallerMailbox(store, callerSessionId, options, sessionTitle2) {
87340
+ const channels = listMailboxChannels(store, callerSessionId, sessionTitle2);
87341
+ const peers = peersOf(channels);
87342
+ const from = options.from?.map((id) => id.trim()).filter(Boolean) ?? [];
87343
+ if (from.length > 0) {
87344
+ const known = new Set(peers.map((peer) => peer.sessionId));
87345
+ const unknown2 = from.filter((id) => !known.has(id));
87346
+ if (unknown2.length > 0) {
87347
+ throw new CollaborationError(
87348
+ `Not your collaboration peers: ${unknown2.join(", ")}. ` + (peers.length > 0 ? `Your peers: ${formatPeers(peers)}` : NO_PEERS_HINT),
87349
+ "forbidden"
87350
+ );
87218
87351
  }
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 {
87352
+ }
87353
+ const selected = from.length > 0 ? channels.filter((channel) => from.includes(channel.peer.sessionId)) : channels;
87354
+ if (selected.length === 0) return { messages: [], peers };
87355
+ const limit = Math.min(MAX_MESSAGES_PER_RETRIEVE, Math.max(1, Math.floor(options.limit ?? MAX_MESSAGES_PER_RETRIEVE)));
87356
+ const peerByGrant = new Map(selected.map((channel) => [channel.grant.grant_id, channel.peer]));
87357
+ const perGrantLimit = Math.max(1, Math.floor(limit / selected.length));
87358
+ const messages = store.readMailbox(callerSessionId, [...peerByGrant.keys()], perGrantLimit).flatMap(({ grantId, rows }) => rows.map((row) => ({
87359
+ messageId: row.id,
87360
+ sequence: row.sequence,
87361
+ fromSessionId: row.sender_session_id,
87362
+ from: peerByGrant.get(grantId),
87363
+ content: row.content,
87364
+ createdAt: row.created_at
87365
+ }))).sort((a, b3) => a.createdAt.localeCompare(b3.createdAt)).slice(0, limit);
87366
+ return { messages, peers };
87367
+ }
87368
+
87369
+ // ../../packages/runtime/src/collaboration/start.ts
87370
+ 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).";
87371
+ function recordApprovedLaunch(store, parentSessionId, launch2, config2) {
87372
+ const mode = resolveLaunchMode(launch2.mode);
87373
+ const { launchId, summary: summary2, name, role } = launch2;
87374
+ const title = collaborationSessionTitle(name, role);
87375
+ if (mode === "link") {
87376
+ const peerSessionId = launch2.sessionId?.trim();
87377
+ if (!peerSessionId) throw new CollaborationError("Link launches require sessionId", "invalid_argument");
87378
+ const existing = store.findLinkGrant(parentSessionId, peerSessionId);
87379
+ if (existing) {
87380
+ const existingConfig = { ...parseGrantConfig(existing.config_json), launchId };
87381
+ store.updateConfig(existing.grant_id, existingConfig);
87382
+ return {
87383
+ grantId: existing.grant_id,
87384
+ approved: {
87261
87385
  launchId,
87262
- mode: "link",
87263
- agentId: "",
87386
+ mode,
87387
+ agentId: existing.agent_id,
87264
87388
  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,
87389
+ summary: summary2,
87344
87390
  name,
87345
- role
87346
- }
87347
- };
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
- }
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);
87391
+ role,
87392
+ title,
87393
+ config: existingConfig,
87394
+ reused: true
87576
87395
  }
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
87396
  };
87616
87397
  }
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
- }
87398
+ const linkConfig = {
87399
+ launchId,
87400
+ name,
87401
+ role,
87402
+ summary: summary2,
87403
+ peerSessionId,
87404
+ peerTitle: launch2.peerTitle,
87405
+ peerProjectPath: launch2.peerProjectPath
87406
+ };
87407
+ const grantId2 = store.createGrant({
87408
+ kind: "link",
87409
+ parentSessionId,
87410
+ childSessionId: peerSessionId,
87411
+ agentId: "",
87412
+ config: linkConfig
87719
87413
  });
87720
87414
  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,
87415
+ grantId: grantId2,
87416
+ approved: {
87417
+ launchId,
87418
+ mode,
87419
+ agentId: "",
87420
+ sessionId: peerSessionId,
87421
+ summary: summary2,
87422
+ name,
87737
87423
  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 };
87424
+ title,
87425
+ config: linkConfig,
87426
+ reused: false
87781
87427
  }
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
87428
  };
87831
87429
  }
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" });
87430
+ const grantConfig = { ...config2, launchId, name, role, summary: summary2 };
87431
+ const grantId = store.createGrant({ kind: mode, parentSessionId, agentId: launch2.agentId, config: grantConfig });
87432
+ return {
87433
+ grantId,
87434
+ approved: {
87435
+ launchId,
87436
+ mode,
87437
+ agentId: launch2.agentId,
87438
+ summary: summary2,
87439
+ name,
87440
+ role,
87441
+ title,
87442
+ config: grantConfig,
87443
+ reused: false
87839
87444
  }
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)
87445
+ };
87446
+ }
87447
+ function prepareLaunchStart(store, callerSessionId, input) {
87448
+ const launchId = typeof input.launchId === "string" ? input.launchId.trim() : "";
87449
+ if (!launchId) throw new CollaborationError("launchId is required", "invalid_argument");
87450
+ const grant = store.grantForLaunch(callerSessionId, launchId);
87451
+ if (!grant) {
87452
+ throw new CollaborationError(
87453
+ `No approved launch "${launchId}" in this session. Pass a launchId returned by session_collab_request.`,
87454
+ "not_found"
87843
87455
  );
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
87456
  }
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
- }
87457
+ if (grant.task_sent === 1 || grant.task) return grant;
87458
+ const task = normalizeStartTask(grant.kind, input.task);
87459
+ if (!task) return grant;
87460
+ store.setTask(grant.grant_id, task);
87461
+ return { ...grant, task };
87462
+ }
87463
+
87464
+ // ../../packages/runtime/src/collaboration/store.ts
87465
+ import { randomUUID as randomUUID11 } from "node:crypto";
87466
+ var GRANT_COLUMNS = `credential_hash AS grant_id, parent_session_id, child_session_id,
87467
+ agent_id, task, config_json, task_sent, COALESCE(kind, 'spawn') AS kind, started_at`;
87468
+ var MESSAGE_COLUMNS = `id, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at`;
87469
+ function nowIso() {
87470
+ return (/* @__PURE__ */ new Date()).toISOString();
87471
+ }
87472
+ var CollaborationStore = class {
87473
+ constructor(db) {
87474
+ this.db = db;
87914
87475
  }
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;
87476
+ db;
87477
+ transaction(fn) {
87478
+ return this.db.transaction(fn)();
87927
87479
  }
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
- );
87480
+ // --- grants --------------------------------------------------------------
87481
+ grantById(grantId) {
87482
+ return this.db.prepare(`SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants WHERE credential_hash = ?`).get(grantId) ?? null;
87969
87483
  }
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.";
87973
- 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 {
87981
- }
87484
+ /**
87485
+ * The grant an approved launch created. launchIds are agent-chosen and may
87486
+ * repeat across requests, so the latest approval wins.
87487
+ */
87488
+ grantForLaunch(parentSessionId, launchId) {
87489
+ return this.db.prepare(`
87490
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87491
+ WHERE parent_session_id = ? AND json_extract(config_json, '$.launchId') = ?
87492
+ ORDER BY created_at DESC, rowid DESC LIMIT 1
87493
+ `).get(parentSessionId, launchId) ?? null;
87494
+ }
87495
+ /** True when `sessionId` is a spawn child (nested collaboration is unsupported). */
87496
+ isSpawnChild(sessionId) {
87497
+ return Boolean(this.db.prepare(`
87498
+ SELECT 1 FROM session_collaboration_grants
87499
+ WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn' LIMIT 1
87500
+ `).get(sessionId));
87501
+ }
87502
+ spawnGrantForChild(childSessionId) {
87503
+ return this.db.prepare(`
87504
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87505
+ WHERE child_session_id = ? AND COALESCE(kind, 'spawn') = 'spawn'
87506
+ `).get(childSessionId) ?? null;
87507
+ }
87508
+ startedSpawnGrants() {
87509
+ return this.db.prepare(`
87510
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87511
+ WHERE child_session_id IS NOT NULL AND COALESCE(kind, 'spawn') = 'spawn'
87512
+ `).all();
87982
87513
  }
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
- });
87514
+ /**
87515
+ * Open mailbox channels of `sessionId`: spawn grants with a bound child and
87516
+ * started link grants, oldest first. Handoff grants are never channels.
87517
+ */
87518
+ channelsFor(sessionId) {
87519
+ return this.db.prepare(`
87520
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87521
+ WHERE (parent_session_id = ? OR child_session_id = ?)
87522
+ AND child_session_id IS NOT NULL
87523
+ AND (COALESCE(kind, 'spawn') = 'spawn' OR (kind = 'link' AND started_at IS NOT NULL))
87524
+ ORDER BY created_at, rowid
87525
+ `).all(sessionId, sessionId);
87526
+ }
87527
+ /** True when one of the two sessions was created by a handoff from the other. */
87528
+ isHandoffPair(a, b3) {
87529
+ return Boolean(this.db.prepare(`
87530
+ SELECT 1 FROM session_collaboration_grants
87531
+ WHERE kind = 'handoff' AND (
87532
+ (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
87533
+ OR (parent_session_id = ? AND json_extract(config_json, '$.handoffSessionId') = ?)
87534
+ ) LIMIT 1
87535
+ `).get(a, b3, b3, a));
87536
+ }
87537
+ findLinkGrant(initiatorSessionId, peerSessionId) {
87538
+ return this.db.prepare(`
87539
+ SELECT ${GRANT_COLUMNS} FROM session_collaboration_grants
87540
+ WHERE parent_session_id = ? AND child_session_id = ? AND kind = 'link'
87541
+ `).get(initiatorSessionId, peerSessionId) ?? null;
87542
+ }
87543
+ /**
87544
+ * Persist an approved launch. Link grants bind the peer at once; spawn and
87545
+ * handoff grants bind their session on start. The brief arrives on start.
87546
+ */
87547
+ createGrant(input) {
87548
+ const grantId = randomUUID11();
87549
+ try {
87550
+ this.db.prepare(`
87551
+ INSERT INTO session_collaboration_grants
87552
+ (credential_hash, credential_secret, credential_hint, parent_session_id, child_session_id,
87553
+ agent_id, task, config_json, created_at, kind)
87554
+ VALUES (?, NULL, '', ?, ?, ?, '', ?, ?, ?)
87555
+ `).run(
87556
+ grantId,
87557
+ input.parentSessionId,
87558
+ input.childSessionId ?? null,
87559
+ input.agentId,
87560
+ JSON.stringify(input.config),
87561
+ nowIso(),
87562
+ input.kind
87563
+ );
87564
+ } catch (error51) {
87565
+ const message = error51 instanceof Error ? error51.message : String(error51);
87566
+ if (input.childSessionId && /UNIQUE|unique/i.test(message)) {
87567
+ throw new CollaborationError(
87568
+ `Session ${input.parentSessionId} already has a link to ${input.childSessionId}; request it again to reuse it.`,
87569
+ "failed_precondition"
87570
+ );
87571
+ }
87572
+ throw error51;
87991
87573
  }
87574
+ return grantId;
87992
87575
  }
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;
87576
+ updateConfig(grantId, config2) {
87577
+ this.db.prepare("UPDATE session_collaboration_grants SET config_json = ? WHERE credential_hash = ?").run(JSON.stringify(config2), grantId);
87999
87578
  }
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
- };
87579
+ /** Record the brief of a grant whose task has not been delivered yet. */
87580
+ setTask(grantId, task) {
87581
+ this.db.prepare("UPDATE session_collaboration_grants SET task = ? WHERE credential_hash = ? AND task_sent = 0").run(task, grantId);
88010
87582
  }
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;
87583
+ markTaskSent(grantId) {
87584
+ this.db.prepare("UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?").run(grantId);
88021
87585
  }
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) };
87586
+ /** Link start. Returns false when the grant had already been started. */
87587
+ markStarted(grantId) {
87588
+ return this.db.prepare(`
87589
+ UPDATE session_collaboration_grants SET started_at = ? WHERE credential_hash = ? AND started_at IS NULL
87590
+ `).run(nowIso(), grantId).changes === 1;
88047
87591
  }
88048
87592
  /**
88049
- * Apply multi-launch form edits from desktop SessionAgentsConfirmPrompt.
88050
- * Only editable fields (model/effort/permission/sandbox/apiProvider) may change.
87593
+ * Record the session a spawn/handoff start created. Throws when a concurrent
87594
+ * start already consumed the grant.
87595
+ *
87596
+ * A handoff session is deliberately *not* written to child_session_id: that
87597
+ * column holds channel endpoints (spawn children, link peers), and a handoff
87598
+ * sibling is neither. The created id lives in config_json instead.
88051
87599
  */
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
- );
87600
+ bindStartedSession(grant, sessionId, config2) {
87601
+ const changes = grant.kind === "handoff" ? this.db.prepare(`
87602
+ UPDATE session_collaboration_grants SET config_json = ?, started_at = ?
87603
+ WHERE credential_hash = ? AND started_at IS NULL
87604
+ `).run(JSON.stringify({ ...config2, handoffSessionId: sessionId }), nowIso(), grant.grant_id).changes : this.db.prepare(`
87605
+ UPDATE session_collaboration_grants SET child_session_id = ?, started_at = ?
87606
+ WHERE credential_hash = ? AND child_session_id IS NULL
87607
+ `).run(sessionId, nowIso(), grant.grant_id).changes;
87608
+ if (changes !== 1) throw new CollaborationError("This launch was already started", "failed_precondition");
87609
+ }
87610
+ // --- mailbox -------------------------------------------------------------
87611
+ nextSequence(grantId) {
87612
+ const row = this.db.prepare(`
87613
+ SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
87614
+ FROM session_collaboration_messages WHERE credential_hash = ?
87615
+ `).get(grantId);
87616
+ return row.next_sequence;
87617
+ }
87618
+ insertMessage(grantId, input) {
87619
+ const row = {
87620
+ id: randomUUID11(),
87621
+ sequence: this.nextSequence(grantId),
87622
+ sender_session_id: input.senderSessionId,
87623
+ recipient_session_id: input.recipientSessionId,
87624
+ client_message_id: input.clientMessageId,
87625
+ content: input.content,
87626
+ created_at: nowIso()
87627
+ };
87628
+ this.db.prepare(`
87629
+ INSERT INTO session_collaboration_messages
87630
+ (id, credential_hash, sequence, sender_session_id, recipient_session_id, client_message_id, content, created_at)
87631
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
87632
+ `).run(
87633
+ row.id,
87634
+ grantId,
87635
+ row.sequence,
87636
+ row.sender_session_id,
87637
+ row.recipient_session_id,
87638
+ row.client_message_id,
87639
+ row.content,
87640
+ row.created_at
87641
+ );
87642
+ return row;
87643
+ }
87644
+ /** Append one message; a repeated `clientMessageId` returns the stored row. */
87645
+ appendMessage(input) {
87646
+ return this.transaction(() => {
87647
+ if (input.clientMessageId) {
87648
+ const existing = this.db.prepare(`
87649
+ SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages
87650
+ WHERE credential_hash = ? AND sender_session_id = ? AND client_message_id = ?
87651
+ `).get(input.grantId, input.senderSessionId, input.clientMessageId);
87652
+ if (existing) return { row: existing, reused: true };
88090
87653
  }
88091
- if (seen.has(launchId)) {
88092
- throw Object.assign(new Error("Confirmed launches must have unique launchIds"), {
88093
- code: "invalid_argument"
87654
+ const row = this.insertMessage(input.grantId, { ...input, clientMessageId: input.clientMessageId ?? null });
87655
+ return { row, reused: false };
87656
+ });
87657
+ }
87658
+ /** Queue a link grant's opening body for the peer exactly once, then mark the task sent. */
87659
+ appendLinkOpening(grant, recipientSessionId, content) {
87660
+ this.transaction(() => {
87661
+ try {
87662
+ this.insertMessage(grant.grant_id, {
87663
+ senderSessionId: grant.parent_session_id,
87664
+ recipientSessionId,
87665
+ clientMessageId: `link-opening:${grant.grant_id}`,
87666
+ content
88094
87667
  });
87668
+ } catch {
88095
87669
  }
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
- };
87670
+ this.markTaskSent(grant.grant_id);
88122
87671
  });
88123
87672
  }
88124
87673
  /**
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.
87674
+ * Drain unread messages addressed to `sessionId` across grants, advancing this
87675
+ * endpoint's cursor. Batches keep the order of `grantIds`.
88127
87676
  */
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.
87677
+ readMailbox(sessionId, grantIds, limitPerGrant) {
87678
+ return this.transaction(() => grantIds.flatMap((grantId) => {
87679
+ const cursor = this.db.prepare(`
87680
+ SELECT last_sequence FROM session_collaboration_cursors WHERE credential_hash = ? AND session_id = ?
87681
+ `).get(grantId, sessionId);
87682
+ const rows = this.db.prepare(`
87683
+ SELECT ${MESSAGE_COLUMNS} FROM session_collaboration_messages
87684
+ WHERE credential_hash = ? AND recipient_session_id = ? AND sequence > ?
87685
+ ORDER BY sequence LIMIT ?
87686
+ `).all(grantId, sessionId, cursor?.last_sequence ?? 0, limitPerGrant);
87687
+ if (rows.length === 0) return [];
87688
+ const lastSequence = rows[rows.length - 1].sequence;
87689
+ this.db.prepare(`
87690
+ INSERT INTO session_collaboration_cursors (credential_hash, session_id, last_sequence)
87691
+ VALUES (?, ?, ?)
87692
+ ON CONFLICT(credential_hash, session_id) DO UPDATE SET last_sequence = excluded.last_sequence
87693
+ `).run(grantId, sessionId, lastSequence);
87694
+ this.db.prepare(`
87695
+ UPDATE session_collaboration_messages SET delivered_at = COALESCE(delivered_at, ?)
87696
+ WHERE credential_hash = ? AND recipient_session_id = ? AND sequence <= ?
87697
+ `).run(nowIso(), grantId, sessionId, lastSequence);
87698
+ return [{ grantId, rows }];
87699
+ }));
87700
+ }
87701
+ };
88133
87702
 
88134
- ` + grant.task;
87703
+ // src/session/collaboration-messaging.ts
87704
+ function sessionTitle(ctx) {
87705
+ return (sessionId) => ctx.deps.sessions.get(sessionId)?.title ?? null;
87706
+ }
87707
+ async function wakePeer(ctx, sessionId, fromSessionId) {
87708
+ if (!ctx.deps.sessions.get(sessionId)) return;
87709
+ const fromTitle = ctx.deps.sessions.get(fromSessionId)?.title?.trim() || fromSessionId.slice(0, 8);
87710
+ try {
87711
+ await ctx.deps.sessions.sendWithoutLease({
87712
+ sessionId,
87713
+ text: mailboxWakeText({ sessionId: fromSessionId, title: fromTitle }),
87714
+ source: "task-notification",
87715
+ requestId: `collab-wake-${fromSessionId.slice(0, 8)}-${Date.now()}`
87716
+ });
87717
+ } catch {
88135
87718
  }
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 {
87719
+ }
87720
+ function sendCollaborationMessage(ctx, input) {
87721
+ const channel = resolveSendChannel(ctx.store, input.sessionId, input.to, sessionTitle(ctx));
87722
+ const content = normalizeMailboxContent(input.content);
87723
+ const recipientSessionId = channel.peer.sessionId;
87724
+ const insert = ctx.store.appendMessage({
87725
+ grantId: channel.grant.grant_id,
87726
+ senderSessionId: input.sessionId,
87727
+ recipientSessionId,
87728
+ clientMessageId: input.clientMessageId,
87729
+ content
87730
+ });
87731
+ if (!insert.reused) {
87732
+ ctx.deps.events.append({
87733
+ aggregateType: "session",
87734
+ aggregateId: input.sessionId,
87735
+ eventType: "collaboration.message",
87736
+ payload: {
87737
+ messageId: insert.row.id,
87738
+ grantId: channel.grant.grant_id,
87739
+ toSessionId: recipientSessionId,
87740
+ sequence: insert.row.sequence
87741
+ }
87742
+ });
87743
+ void wakePeer(ctx, recipientSessionId, input.sessionId);
87744
+ }
87745
+ return {
87746
+ status: "sent",
87747
+ messageId: insert.row.id,
87748
+ sequence: insert.row.sequence,
87749
+ reused: insert.reused,
87750
+ to: channel.peer,
87751
+ peerSessionId: recipientSessionId
87752
+ };
87753
+ }
87754
+ function retrieveCollaborationMessages(ctx, input) {
87755
+ const { messages, peers } = readCallerMailbox(
87756
+ ctx.store,
87757
+ input.sessionId,
87758
+ { from: input.from, limit: input.max },
87759
+ sessionTitle(ctx)
87760
+ );
87761
+ if (messages.length > 0) return { status: "messages", messages, peers };
87762
+ return { status: "empty", messages: [], peers, hint: peers.length > 0 ? EMPTY_MAILBOX_HINT : NO_PEERS_HINT };
87763
+ }
87764
+
87765
+ // src/session/collaboration-profiles.ts
87766
+ init_environment();
87767
+
87768
+ // ../../packages/shared/src/acp-brand.ts
87769
+ init_acp_brand();
87770
+
87771
+ // src/session/collaboration-profiles.ts
87772
+ init_resolve_service();
87773
+ function listCollaborationProfiles(deps) {
87774
+ const { harnesses, providers, sessions, sessionProviders } = deps;
87775
+ const profiles = [];
87776
+ const seen = /* @__PURE__ */ new Set();
87777
+ const providerOptions = {
87778
+ experimentalClaudeOpenAiChatEnabled: deps.experimentalClaudeOpenAiChatEnabled?.() ?? false
87779
+ };
87780
+ const pushProfile = (profileId, harnessId, name, description, profileConfig) => {
87781
+ if (seen.has(profileId)) return;
87782
+ if (!harnesses.isSessionHarnessRunnable(harnessId)) return;
87783
+ seen.add(profileId);
87784
+ const harnessModels = listHarnessModels(providers, harnessId, null, providerOptions);
87785
+ const models = harnessModels.map((m3) => ({
87786
+ id: m3.id,
87787
+ name: m3.name || m3.id,
87788
+ ...m3.description ? { description: m3.description } : {},
87789
+ ...m3.serviceTiers?.length ? { serviceTiers: m3.serviceTiers } : {}
87790
+ }));
87791
+ const defaultModel = harnessModels.find((m3) => m3.isDefault) ?? harnessModels[0];
87792
+ const efforts = /* @__PURE__ */ new Set();
87793
+ for (const m3 of harnessModels) {
87794
+ for (const e of m3.supportedEffortLevels ?? []) efforts.add(e);
87795
+ }
87796
+ const cfg = profileConfig && typeof profileConfig === "object" && !Array.isArray(profileConfig) ? profileConfig : {};
87797
+ const cfgModel = typeof cfg.model === "string" && cfg.model.trim() ? cfg.model.trim() : void 0;
87798
+ const cfgEffort = typeof cfg.effort === "string" && cfg.effort.trim() ? cfg.effort.trim() : typeof cfg.reasoningEffort === "string" && cfg.reasoningEffort.trim() ? cfg.reasoningEffort.trim() : void 0;
87799
+ const acpAgentId = harnessId === "acp" && typeof cfg.agentId === "string" && cfg.agentId.trim() ? cfg.agentId.trim() : null;
87800
+ const modelDefault = cfgModel ?? defaultModel?.id;
87801
+ const effortDefault = cfgEffort ?? (efforts.has("high") ? "high" : efforts.has("medium") ? "medium" : efforts.size > 0 ? [...efforts][0] : void 0);
87802
+ profiles.push({
87803
+ id: profileId,
87804
+ name: harnessId === "acp" ? acpAgentDisplayName(acpAgentId) : name,
87805
+ harnessId,
87806
+ ...acpAgentId ? { acpAgentId } : {},
87807
+ brandKey: resolveHarnessBrandKey(harnessId, acpAgentId),
87808
+ description,
87809
+ defaultConfig: {
87810
+ ...modelDefault ? { model: modelDefault } : {},
87811
+ ...effortDefault ? { effort: effortDefault } : {},
87812
+ ...harnessId === "codex" ? { fastMode: typeof cfg.fastMode === "boolean" ? cfg.fastMode : false } : {}
87813
+ },
87814
+ models: models.length > 0 ? models : [{ id: "default", name: "Default" }],
87815
+ efforts: [...efforts],
87816
+ apiProviders: listHarnessApiProviders(providers, harnessId, providerOptions)
87817
+ });
87818
+ };
87819
+ if (sessionProviders) {
87820
+ for (const p3 of sessionProviders.list()) {
87821
+ pushProfile(
87822
+ p3.id,
87823
+ p3.harnessId,
87824
+ p3.name,
87825
+ p3.isBase ? `${p3.harnessId} harness with the built-in configuration` : `Custom ${p3.harnessId} profile`,
87826
+ p3.config
87827
+ );
88151
87828
  }
88152
- this.deps.db.prepare(`UPDATE session_collaboration_grants SET task_sent = 1 WHERE credential_hash = ?`).run(grant.credential_hash);
88153
87829
  }
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 {
87830
+ if (profiles.length === 0) {
87831
+ const seedIds = new Set(harnesses.readySessionHarnessIds());
87832
+ for (const s4 of sessions.list()) {
87833
+ if (s4.harnessId) seedIds.add(s4.harnessId);
87834
+ }
87835
+ for (const id of seedIds) {
87836
+ const harnessId = normalizeSessionHarnessId(id) ?? id;
87837
+ pushProfile(harnessId, harnessId, harnessId, `${harnessId} harness`);
88164
87838
  }
88165
87839
  }
88166
- };
87840
+ return profiles;
87841
+ }
88167
87842
 
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);
87843
+ // src/session/collaboration-request.ts
87844
+ import { randomUUID as randomUUID12 } from "node:crypto";
87845
+
87846
+ // src/session/collaboration-context.ts
87847
+ init_environment();
87848
+ function resolveProfile(profiles, agentId) {
87849
+ const direct = profiles.get(agentId);
87850
+ if (direct) return direct;
87851
+ const wire = normalizeSessionHarnessId(agentId);
87852
+ return wire ? profiles.get(`${wire}-base`) : void 0;
88176
87853
  }
88177
- function ensureKeyFile(keyPath) {
88178
- if (existsSync40(keyPath)) {
88179
- const raw = readFileSync20(keyPath);
88180
- if (raw.length === KEY_BYTES) return raw;
87854
+ function initiatorTitleOf(ctx, grant) {
87855
+ return ctx.deps.sessions.get(grant.parent_session_id)?.title?.trim() || grant.parent_session_id.slice(0, 8);
87856
+ }
87857
+
87858
+ // src/session/collaboration-request.ts
87859
+ function safeLaunchConfig(raw) {
87860
+ const config2 = raw && typeof raw === "object" ? raw : {};
87861
+ const safe = {};
87862
+ if (typeof config2.model === "string" && config2.model.trim()) safe.model = config2.model.trim();
87863
+ if (typeof config2.effort === "string" && config2.effort.trim()) safe.effort = config2.effort.trim();
87864
+ if (config2.apiProviderId === null) safe.apiProviderId = null;
87865
+ else if (typeof config2.apiProviderId === "string" && config2.apiProviderId.trim()) {
87866
+ safe.apiProviderId = config2.apiProviderId.trim();
87867
+ }
87868
+ if (config2.worktree && typeof config2.worktree === "object") safe.worktree = config2.worktree;
87869
+ if (typeof config2.worktreePath === "string" && config2.worktreePath.trim()) {
87870
+ safe.worktreePath = config2.worktreePath.trim();
87871
+ }
87872
+ if (typeof config2.cwd === "string" && config2.cwd.trim()) safe.cwd = config2.cwd.trim();
87873
+ return safe;
87874
+ }
87875
+ function normalizeLaunches(ctx, input, profileList) {
87876
+ const parent = ctx.deps.sessions.get(input.parentSessionId);
87877
+ const profiles = new Map(profileList.map((p3) => [p3.id, p3]));
87878
+ return input.launches.map((launch2) => {
87879
+ const mode = resolveLaunchMode(launch2.mode);
87880
+ const launchId = launch2.launchId?.trim() || randomUUID12();
87881
+ if (mode === "link") {
87882
+ const peerSessionId = launch2.sessionId?.trim();
87883
+ if (!peerSessionId) {
87884
+ throw new CollaborationError("Link launches require sessionId of an existing SuperOne session", "invalid_argument");
87885
+ }
87886
+ if (peerSessionId === input.parentSessionId) {
87887
+ throw new CollaborationError("Cannot link a session to itself", "invalid_argument");
87888
+ }
87889
+ const peer = ctx.deps.sessions.get(peerSessionId);
87890
+ if (!peer) throw new CollaborationError(`Unknown sessionId for link: ${peerSessionId}`, "not_found");
87891
+ assertNotPeeredElsewhere(ctx.store, input.parentSessionId, peerSessionId);
87892
+ const peerTitle = peer.title?.trim() || peerSessionId.slice(0, 8);
87893
+ const { summary: summary3, name: name2, role: role2 } = normalizeLaunchLabels("link", launch2, peerTitle);
87894
+ return {
87895
+ launchId,
87896
+ mode: "link",
87897
+ agentId: "",
87898
+ sessionId: peerSessionId,
87899
+ peerTitle,
87900
+ peerProjectPath: ctx.deps.projects.get(peer.projectId)?.path,
87901
+ summary: summary3,
87902
+ name: name2,
87903
+ role: role2,
87904
+ config: { name: name2, role: role2, summary: summary3 }
87905
+ };
87906
+ }
87907
+ const agentId = launch2.agentId?.trim();
87908
+ if (!agentId) {
87909
+ throw new CollaborationError(`${mode} launches require agentId from session_collab_list_agents`, "invalid_argument");
87910
+ }
87911
+ const profile = resolveProfile(profiles, agentId);
87912
+ if (!profile) throw new CollaborationError(`Unknown agent profile: ${agentId}`, "invalid_argument");
87913
+ const { summary: summary2, name, role } = normalizeLaunchLabels(mode, launch2);
87914
+ const safe = safeLaunchConfig(launch2.config);
87915
+ return {
87916
+ launchId,
87917
+ mode,
87918
+ agentId,
87919
+ summary: summary2,
87920
+ name,
87921
+ role,
87922
+ config: {
87923
+ ...profile.defaultConfig,
87924
+ ...safe,
87925
+ permissionMode: "default",
87926
+ sandboxMode: "off",
87927
+ cwd: safe.cwd ?? (parent.cwd ?? ctx.deps.projects.get(parent.projectId)?.path),
87928
+ name,
87929
+ role
87930
+ }
87931
+ };
87932
+ });
87933
+ }
87934
+ function createGrant(ctx, parentSessionId, launch2) {
87935
+ const { grantId, approved } = recordApprovedLaunch(ctx.store, parentSessionId, launch2, launch2.config);
87936
+ if (approved.reused) return approved;
87937
+ ctx.deps.events.append({
87938
+ aggregateType: "session",
87939
+ aggregateId: parentSessionId,
87940
+ eventType: "collaboration.grant_created",
87941
+ payload: approved.mode === "link" ? { grantId, mode: "link", peerSessionId: approved.sessionId, launchId: approved.launchId } : { grantId, mode: approved.mode, agentId: approved.agentId, launchId: approved.launchId }
87942
+ });
87943
+ return approved;
87944
+ }
87945
+ async function requestCollaboration(ctx, input) {
87946
+ if (!ctx.deps.sessions.get(input.parentSessionId)) {
87947
+ throw new CollaborationError("Parent session is not available", "not_found");
87948
+ }
87949
+ if (ctx.store.isSpawnChild(input.parentSessionId)) {
87950
+ throw new CollaborationError(NESTED_COLLABORATION_UNSUPPORTED, "failed_precondition");
87951
+ }
87952
+ if (!Array.isArray(input.launches)) {
87953
+ throw new CollaborationError("launches must contain at least one proposed session", "invalid_argument");
87954
+ }
87955
+ assertLaunchCount(input.launches.length);
87956
+ const profileList = ctx.listProfiles();
87957
+ const normalized = normalizeLaunches(ctx, input, profileList);
87958
+ const launchIds = new Set(normalized.map((l) => l.launchId));
87959
+ if (launchIds.size !== normalized.length) {
87960
+ throw new CollaborationError("Every confirmed launch must have a unique launchId", "invalid_argument");
87961
+ }
87962
+ let confirmed = normalized;
87963
+ if (input.requireUserConfirm) {
87964
+ const outcome = await ctx.deps.sessions.requestAgentsConfirm({
87965
+ sessionId: input.parentSessionId,
87966
+ launches: normalized,
87967
+ profiles: profileList,
87968
+ signal: input.signal
87969
+ });
87970
+ if (outcome.action === "cancel") return { status: "cancelled" };
87971
+ if (outcome.action === "decline") return { status: "rejected", feedback: outcome.content?.feedback };
87972
+ confirmed = mergeConfirmedLaunches(normalized, outcome.content);
88181
87973
  }
88182
- mkdirSync23(dirname18(keyPath), { recursive: true, mode: 448 });
88183
- const key = randomBytes6(KEY_BYTES);
88184
- writeFileSync18(keyPath, key, { mode: 384 });
87974
+ const launches = ctx.store.transaction(() => confirmed.map((launch2) => createGrant(ctx, input.parentSessionId, launch2)));
87975
+ return { status: "approved", launches, next: START_APPROVED_LAUNCHES_HINT };
87976
+ }
87977
+
87978
+ // src/session/collaboration-start.ts
87979
+ init_agent_types();
87980
+ init_environment();
87981
+ import { existsSync as existsSync39, statSync as statSync15 } from "node:fs";
87982
+ import { resolve as pathResolve2 } from "node:path";
87983
+ function resolveCwd(ctx, config2, projectId, parentCwd) {
87984
+ const project = ctx.deps.projects.get(projectId);
87985
+ const fallback = parentCwd || project?.path || process.cwd();
87986
+ const cwd = pathResolve2(config2.cwd || fallback);
87987
+ if (!existsSync39(cwd) || !statSync15(cwd).isDirectory()) {
87988
+ throw new CollaborationError(`Working directory does not exist: ${cwd}`, "invalid_argument");
87989
+ }
87990
+ return cwd;
87991
+ }
87992
+ function applyFormAnswers(ctx, grant, formAnswers) {
87993
+ const packed = formAnswers[SESSION_AGENT_LAUNCHES_FIELD];
87994
+ if (typeof packed !== "string") return grant;
87995
+ let edited;
88185
87996
  try {
88186
- chmodSync2(keyPath, 384);
87997
+ edited = JSON.parse(packed);
87998
+ } catch {
87999
+ return grant;
88000
+ }
88001
+ if (!Array.isArray(edited) || edited.length === 0) return grant;
88002
+ const patch = edited[0]?.config;
88003
+ if (!patch || typeof patch !== "object") return grant;
88004
+ const next = patchEditableLaunchConfig(parseGrantConfig(grant.config_json), patch);
88005
+ ctx.store.updateConfig(grant.grant_id, next);
88006
+ return { ...grant, config_json: JSON.stringify(next) };
88007
+ }
88008
+ async function deliverInitialTask(ctx, grant, childSessionId) {
88009
+ if (grant.task_sent === 1) return;
88010
+ const config2 = parseGrantConfig(grant.config_json);
88011
+ const text2 = grant.kind === "handoff" ? handoffTaskContent({
88012
+ parentSessionId: grant.parent_session_id,
88013
+ parentTitle: ctx.deps.sessions.get(grant.parent_session_id)?.title?.trim() || null,
88014
+ task: grant.task
88015
+ }) : grant.task;
88016
+ try {
88017
+ await ctx.deps.sessions.sendWithoutLease({
88018
+ sessionId: childSessionId,
88019
+ text: text2,
88020
+ model: config2.model,
88021
+ effort: config2.effort,
88022
+ permissionMode: config2.permissionMode,
88023
+ sandboxMode: config2.sandboxMode,
88024
+ apiProviderId: config2.apiProviderId,
88025
+ requestId: `collaboration-task-${grant.grant_id.slice(0, 16)}`
88026
+ });
88187
88027
  } catch {
88188
88028
  }
88189
- return key;
88029
+ ctx.store.markTaskSent(grant.grant_id);
88190
88030
  }
88191
- function createNodeSecretCrypto(keyPath) {
88192
- const key = ensureKeyFile(keyPath);
88031
+ async function wakeLinkPeer(ctx, sessionId, grant, hasOpening) {
88032
+ if (!ctx.deps.sessions.get(sessionId)) return;
88033
+ try {
88034
+ await ctx.deps.sessions.sendWithoutLease({
88035
+ sessionId,
88036
+ text: linkActivationWakeText({
88037
+ initiatorSessionId: grant.parent_session_id,
88038
+ initiatorTitle: initiatorTitleOf(ctx, grant),
88039
+ hasOpening
88040
+ }),
88041
+ source: "task-notification",
88042
+ requestId: `collab-link-wake-${grant.grant_id.slice(0, 12)}-${Date.now()}`
88043
+ });
88044
+ } catch {
88045
+ }
88046
+ }
88047
+ async function startLink(ctx, grant) {
88048
+ if (!grant.child_session_id) {
88049
+ throw new CollaborationError("Link grant is missing peer session id", "failed_precondition");
88050
+ }
88051
+ const peerSessionId = grant.child_session_id;
88052
+ if (!ctx.deps.sessions.get(peerSessionId)) {
88053
+ throw new CollaborationError(`Peer session no longer exists: ${peerSessionId}`, "not_found");
88054
+ }
88055
+ const alreadyStarted = Boolean(grant.started_at);
88056
+ if (!alreadyStarted) ctx.store.markStarted(grant.grant_id);
88057
+ const opening = grant.task?.trim() ?? "";
88058
+ if (!alreadyStarted && opening) {
88059
+ if (grant.task_sent !== 1) {
88060
+ ctx.store.appendLinkOpening(grant, peerSessionId, opening);
88061
+ void wakeLinkPeer(ctx, peerSessionId, grant, true);
88062
+ }
88063
+ } else {
88064
+ void wakeLinkPeer(ctx, peerSessionId, grant, false);
88065
+ if (!alreadyStarted) ctx.store.markTaskSent(grant.grant_id);
88066
+ }
88067
+ const peer = describeLaunchedPeer(grant);
88193
88068
  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;
88069
+ status: "linked",
88070
+ mode: "link",
88071
+ sessionId: peerSessionId,
88072
+ peerSessionId,
88073
+ reused: alreadyStarted,
88074
+ name: peer.name,
88075
+ role: peer.role,
88076
+ title: peer.title,
88077
+ config: peer.config
88078
+ };
88079
+ }
88080
+ async function startCollaboration(ctx, input) {
88081
+ let grant = prepareLaunchStart(ctx.store, input.callerSessionId, input);
88082
+ if (grant.kind !== "link" && input.formAnswers && typeof input.formAnswers === "object") {
88083
+ grant = applyFormAnswers(ctx, grant, input.formAnswers);
88084
+ }
88085
+ if (grant.kind === "link") return startLink(ctx, grant);
88086
+ const isHandoff = grant.kind === "handoff";
88087
+ const existingHandoffSessionId = isHandoff ? parseGrantConfig(grant.config_json).handoffSessionId : void 0;
88088
+ const existingSessionId = existingHandoffSessionId ?? grant.child_session_id;
88089
+ if (existingSessionId) {
88090
+ const existing = ctx.deps.sessions.get(existingSessionId);
88091
+ if (isHandoff && !existing) {
88092
+ throw new CollaborationError(`The handoff session no longer exists: ${existingSessionId}`, "not_found");
88093
+ }
88094
+ if (existing && !isHandoff) {
88095
+ ctx.deps.sessions.setSystemPromptAppend(
88096
+ existing.sessionId,
88097
+ collaborationSystemPrompt(grant.parent_session_id)
88098
+ );
88099
+ }
88100
+ if (existing) await deliverInitialTask(ctx, grant, existingSessionId);
88101
+ const peer = describeLaunchedPeer(grant);
88102
+ return {
88103
+ status: "started",
88104
+ mode: grant.kind,
88105
+ sessionId: existingSessionId,
88106
+ reused: true,
88107
+ name: peer.name,
88108
+ role: peer.role,
88109
+ title: peer.title,
88110
+ config: peer.config
88111
+ };
88112
+ }
88113
+ const parent = ctx.deps.sessions.get(grant.parent_session_id);
88114
+ if (!parent) throw new CollaborationError("Parent session is not available", "not_found");
88115
+ const config2 = parseGrantConfig(grant.config_json);
88116
+ let cwd = resolveCwd(ctx, config2, parent.projectId, parent.cwd);
88117
+ if (config2.worktreePath && typeof config2.worktreePath === "string" && config2.worktreePath.trim()) {
88118
+ cwd = pathResolve2(config2.worktreePath.trim());
88119
+ } else if (config2.worktree?.enabled) {
88120
+ cwd = ctx.deps.workspaceGit.activateWorktree(parent.projectId, {
88121
+ baseBranch: config2.worktree.baseBranch || "HEAD",
88122
+ mode: config2.worktree.mode ?? "branch",
88123
+ branchName: config2.worktree.branchName,
88124
+ carryLocalChanges: config2.worktree.carryLocalChanges
88125
+ }).path;
88126
+ }
88127
+ const profile = resolveProfile(new Map(ctx.listProfiles().map((p3) => [p3.id, p3])), grant.agent_id);
88128
+ const harnessId = profile?.harnessId ?? normalizeSessionHarnessId(grant.agent_id) ?? "claude";
88129
+ const displayName = deriveCollaborationName({ name: config2.name });
88130
+ const role = deriveCollaborationRole({ role: config2.role, task: grant.task });
88131
+ const title = collaborationSessionTitle(displayName, role);
88132
+ let child;
88133
+ try {
88134
+ child = ctx.deps.sessions.create({
88135
+ projectId: parent.projectId,
88136
+ harnessId,
88137
+ providerId: grant.agent_id,
88138
+ title,
88139
+ cwd,
88140
+ model: config2.model ?? null,
88141
+ effort: config2.effort ?? null,
88142
+ permissionMode: config2.permissionMode ?? null,
88143
+ sandboxMode: config2.sandboxMode ?? null,
88144
+ apiProviderId: config2.apiProviderId ?? null,
88145
+ controllerClientSessionId: input.controllerClientSessionId ?? parent.controllerClientSessionId,
88146
+ // Handoff is one-way by construction: the receiver gets no collaboration prompt.
88147
+ ...isHandoff ? {} : { systemPromptAppend: collaborationSystemPrompt(grant.parent_session_id) }
88148
+ });
88149
+ ctx.store.bindStartedSession(grant, child.sessionId, config2);
88150
+ } catch (err) {
88151
+ if (child?.sessionId) {
88206
88152
  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");
88153
+ ctx.deps.sessions.remove(child.sessionId);
88214
88154
  } catch {
88215
- return "";
88216
88155
  }
88217
88156
  }
88157
+ throw err;
88158
+ }
88159
+ grant = isHandoff ? grant : { ...grant, child_session_id: child.sessionId };
88160
+ await deliverInitialTask(ctx, grant, child.sessionId);
88161
+ ctx.deps.events.append({
88162
+ aggregateType: "session",
88163
+ aggregateId: grant.parent_session_id,
88164
+ eventType: isHandoff ? "collaboration.handoff_started" : "collaboration.child_started",
88165
+ payload: { grantId: grant.grant_id, childSessionId: child.sessionId }
88166
+ });
88167
+ return {
88168
+ status: "started",
88169
+ mode: grant.kind,
88170
+ sessionId: child.sessionId,
88171
+ reused: false,
88172
+ name: displayName,
88173
+ role,
88174
+ title,
88175
+ config: {
88176
+ model: config2.model,
88177
+ effort: config2.effort,
88178
+ fastMode: config2.fastMode,
88179
+ permissionMode: config2.permissionMode,
88180
+ sandboxMode: config2.sandboxMode,
88181
+ cwd,
88182
+ apiProviderId: config2.apiProviderId ?? null,
88183
+ name: displayName,
88184
+ role,
88185
+ ...config2.worktree ? { worktree: config2.worktree } : {},
88186
+ ...config2.worktreePath ? { worktreePath: config2.worktreePath } : {}
88187
+ }
88218
88188
  };
88219
88189
  }
88220
88190
 
88191
+ // src/session/collaboration.ts
88192
+ var CollaborationService = class {
88193
+ constructor(deps) {
88194
+ this.deps = deps;
88195
+ this.ctx = {
88196
+ deps,
88197
+ store: new CollaborationStore(deps.db),
88198
+ listProfiles: () => this.listProfiles()
88199
+ };
88200
+ }
88201
+ deps;
88202
+ ctx;
88203
+ /** Agent profiles from session_providers (+ ready-harness fallback). */
88204
+ listProfiles() {
88205
+ return listCollaborationProfiles(this.deps);
88206
+ }
88207
+ request(input) {
88208
+ return requestCollaboration(this.ctx, input);
88209
+ }
88210
+ start(input) {
88211
+ return startCollaboration(this.ctx, input);
88212
+ }
88213
+ send(input) {
88214
+ return sendCollaborationMessage(this.ctx, input);
88215
+ }
88216
+ retrieve(input) {
88217
+ return retrieveCollaborationMessages(this.ctx, input);
88218
+ }
88219
+ /** Reconstruct system-prompt append for spawn children after restart (never link). */
88220
+ rehydrateSystemPrompts() {
88221
+ for (const grant of this.ctx.store.startedSpawnGrants()) {
88222
+ if (!grant.child_session_id || !this.deps.sessions.get(grant.child_session_id)) continue;
88223
+ this.deps.sessions.setSystemPromptAppend(grant.child_session_id, collaborationSystemPrompt(grant.parent_session_id));
88224
+ }
88225
+ }
88226
+ };
88227
+
88221
88228
  // src/workspace/watch-service.ts
88222
88229
  import { watch } from "node:fs";
88223
88230
 
@@ -88324,7 +88331,7 @@ var WorkspaceWatchService = class {
88324
88331
 
88325
88332
  // src/workspace/tail-watch-service.ts
88326
88333
  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";
88334
+ 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
88335
  var MAX_POLL_BYTES = 10 * 1024 * 1024;
88329
88336
  var WorkspaceTailWatchService = class {
88330
88337
  constructor(projects, fs2) {
@@ -88348,7 +88355,7 @@ var WorkspaceTailWatchService = class {
88348
88355
  );
88349
88356
  }
88350
88357
  try {
88351
- resolvedAbs = existsSync41(absolutePath) ? realpathSync6(absolutePath) : absolutePath;
88358
+ resolvedAbs = existsSync40(absolutePath) ? realpathSync6(absolutePath) : absolutePath;
88352
88359
  } catch {
88353
88360
  resolvedAbs = absolutePath;
88354
88361
  }
@@ -88379,7 +88386,7 @@ var WorkspaceTailWatchService = class {
88379
88386
  code: "invalid_argument"
88380
88387
  });
88381
88388
  }
88382
- if (existsSync41(resolvedAbs)) {
88389
+ if (existsSync40(resolvedAbs)) {
88383
88390
  try {
88384
88391
  const st2 = statSync16(resolvedAbs);
88385
88392
  if (!st2.isFile()) {
@@ -88430,7 +88437,7 @@ var WorkspaceTailWatchService = class {
88430
88437
  { code: "invalid_argument" }
88431
88438
  );
88432
88439
  }
88433
- if (!existsSync41(absolutePath)) {
88440
+ if (!existsSync40(absolutePath)) {
88434
88441
  return {
88435
88442
  content: "",
88436
88443
  encoding: "base64",
@@ -88521,7 +88528,7 @@ var WorkspaceTailWatchService = class {
88521
88528
  };
88522
88529
 
88523
88530
  // src/auth/idempotency.ts
88524
- import { createHash as createHash10 } from "node:crypto";
88531
+ import { createHash as createHash9 } from "node:crypto";
88525
88532
  var IdempotencyService = class {
88526
88533
  constructor(db) {
88527
88534
  this.db = db;
@@ -88529,7 +88536,7 @@ var IdempotencyService = class {
88529
88536
  db;
88530
88537
  inflight = /* @__PURE__ */ new Map();
88531
88538
  payloadHash(payload) {
88532
- return createHash10("sha256").update(JSON.stringify(payload ?? null)).digest("hex");
88539
+ return createHash9("sha256").update(JSON.stringify(payload ?? null)).digest("hex");
88533
88540
  }
88534
88541
  key(clientIdentity, operation, idempotencyKey) {
88535
88542
  return `${clientIdentity}\0${operation}\0${idempotencyKey}`;
@@ -88628,7 +88635,62 @@ var IdempotencyService = class {
88628
88635
  };
88629
88636
 
88630
88637
  // src/provider/provider-store.ts
88631
- import { randomUUID as randomUUID12 } from "node:crypto";
88638
+ import { randomUUID as randomUUID13 } from "node:crypto";
88639
+
88640
+ // src/provider/secret-crypto.ts
88641
+ import { existsSync as existsSync41, mkdirSync as mkdirSync23, readFileSync as readFileSync20, writeFileSync as writeFileSync18, chmodSync as chmodSync2 } from "node:fs";
88642
+ import { dirname as dirname18 } from "node:path";
88643
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes5 } from "node:crypto";
88644
+ var ENC_PREFIX2 = "enc:v1:";
88645
+ var KEY_BYTES = 32;
88646
+ function isEncryptedSecret(value) {
88647
+ return typeof value === "string" && value.startsWith(ENC_PREFIX2);
88648
+ }
88649
+ function ensureKeyFile(keyPath) {
88650
+ if (existsSync41(keyPath)) {
88651
+ const raw = readFileSync20(keyPath);
88652
+ if (raw.length === KEY_BYTES) return raw;
88653
+ }
88654
+ mkdirSync23(dirname18(keyPath), { recursive: true, mode: 448 });
88655
+ const key = randomBytes5(KEY_BYTES);
88656
+ writeFileSync18(keyPath, key, { mode: 384 });
88657
+ try {
88658
+ chmodSync2(keyPath, 384);
88659
+ } catch {
88660
+ }
88661
+ return key;
88662
+ }
88663
+ function createNodeSecretCrypto(keyPath) {
88664
+ const key = ensureKeyFile(keyPath);
88665
+ return {
88666
+ encrypt(plain) {
88667
+ if (!plain) return "";
88668
+ if (isEncryptedSecret(plain)) return plain;
88669
+ const iv = randomBytes5(12);
88670
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
88671
+ const ciphertext = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
88672
+ const tag = cipher.getAuthTag();
88673
+ return ENC_PREFIX2 + Buffer.concat([iv, tag, ciphertext]).toString("base64");
88674
+ },
88675
+ decrypt(stored) {
88676
+ if (!stored) return "";
88677
+ if (!isEncryptedSecret(stored)) return stored;
88678
+ try {
88679
+ const buf = Buffer.from(stored.slice(ENC_PREFIX2.length), "base64");
88680
+ const iv = buf.subarray(0, 12);
88681
+ const tag = buf.subarray(12, 28);
88682
+ const data = buf.subarray(28);
88683
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
88684
+ decipher.setAuthTag(tag);
88685
+ return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
88686
+ } catch {
88687
+ return "";
88688
+ }
88689
+ }
88690
+ };
88691
+ }
88692
+
88693
+ // src/provider/provider-store.ts
88632
88694
  function safeParse3(text2, fallback) {
88633
88695
  if (!text2) return fallback;
88634
88696
  try {
@@ -88660,7 +88722,7 @@ var ProviderStore = class {
88660
88722
  }
88661
88723
  createCredential(input) {
88662
88724
  const now = (/* @__PURE__ */ new Date()).toISOString();
88663
- const id = input.id?.trim() || randomUUID12();
88725
+ const id = input.id?.trim() || randomUUID13();
88664
88726
  const maxOrder = this.db.prepare("SELECT MAX(sort_order) as m FROM provider_credentials").get()?.m ?? -1;
88665
88727
  this.db.prepare(
88666
88728
  `INSERT INTO provider_credentials
@@ -88856,7 +88918,7 @@ var ProviderStore = class {
88856
88918
  };
88857
88919
 
88858
88920
  // src/session/host-action-mcp-server.ts
88859
- import { randomUUID as randomUUID14 } from "node:crypto";
88921
+ import { randomUUID as randomUUID15 } from "node:crypto";
88860
88922
  import {
88861
88923
  createServer as createHttpServer
88862
88924
  } from "node:http";
@@ -92451,7 +92513,7 @@ init_interaction_memory();
92451
92513
  // ../../packages/runtime/src/fs/interaction-memory.ts
92452
92514
  init_superone_home();
92453
92515
  init_interaction_memory();
92454
- import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
92516
+ import { createHash as createHash10, randomUUID as randomUUID14 } from "node:crypto";
92455
92517
  import { lstat, mkdir, open, readdir, rename, rm } from "node:fs/promises";
92456
92518
  import { constants as constants4 } from "node:fs";
92457
92519
  import { join as join34 } from "node:path";
@@ -92647,7 +92709,7 @@ function indexEntry(note) {
92647
92709
  };
92648
92710
  }
92649
92711
  async function writeAtomic(dir, name, raw) {
92650
- const temp = join34(dir, `.${name}.${randomUUID13()}.tmp`);
92712
+ const temp = join34(dir, `.${name}.${randomUUID14()}.tmp`);
92651
92713
  try {
92652
92714
  const file2 = await open(temp, "wx", 384);
92653
92715
  try {
@@ -92705,7 +92767,7 @@ var InteractionMemoryStore = class _InteractionMemoryStore {
92705
92767
  await file2.close();
92706
92768
  }
92707
92769
  const { meta: meta3, content } = parseNote(raw, { title: topic2, resource: target.resource });
92708
- return { ...meta3, topic: topic2, content, revision: createHash11("sha256").update(raw).digest("hex") };
92770
+ return { ...meta3, topic: topic2, content, revision: createHash10("sha256").update(raw).digest("hex") };
92709
92771
  } catch (err) {
92710
92772
  if (missing(err)) return null;
92711
92773
  throw new Error(`Cannot read personal memory ${target.label}/${topic2}: ${err instanceof Error ? err.message : String(err)}`);
@@ -92798,7 +92860,7 @@ var InteractionMemoryStore = class _InteractionMemoryStore {
92798
92860
  saved: true,
92799
92861
  created: !existing,
92800
92862
  ...target.identity,
92801
- ...summary({ ...meta3, topic: args.topic, content, revision: createHash11("sha256").update(raw).digest("hex") })
92863
+ ...summary({ ...meta3, topic: args.topic, content, revision: createHash10("sha256").update(raw).digest("hex") })
92802
92864
  };
92803
92865
  } finally {
92804
92866
  await rm(lock, { recursive: true, force: true });
@@ -100998,9 +101060,7 @@ function registerNodeCollabTools(server, superoneSessionId, collab) {
100998
101060
  "session_collab_start",
100999
101061
  {
101000
101062
  description: startDesc.description,
101001
- inputSchema: {
101002
- credential: external_exports.string().min(1)
101003
- }
101063
+ inputSchema: jsonSchemaToZodShape(startDesc.inputSchema)
101004
101064
  },
101005
101065
  async (args) => {
101006
101066
  try {
@@ -101135,7 +101195,7 @@ function headerValue(req, name) {
101135
101195
  return typeof value === "string" && value.length > 0 ? value : null;
101136
101196
  }
101137
101197
  async function startHostActionMcpServer(opts) {
101138
- const masterToken = opts.masterToken ?? randomUUID14();
101198
+ const masterToken = opts.masterToken ?? randomUUID15();
101139
101199
  const httpSessions = /* @__PURE__ */ new Map();
101140
101200
  const httpSessionsBySuperone = /* @__PURE__ */ new Map();
101141
101201
  let httpUrl2 = "";
@@ -101203,7 +101263,7 @@ async function startHostActionMcpServer(opts) {
101203
101263
  });
101204
101264
  let sessionRef;
101205
101265
  const transport = new StreamableHTTPServerTransport({
101206
- sessionIdGenerator: randomUUID14,
101266
+ sessionIdGenerator: randomUUID15,
101207
101267
  onsessioninitialized: (id) => {
101208
101268
  if (sessionRef) httpSessions.set(id, sessionRef);
101209
101269
  },
@@ -101987,7 +102047,7 @@ function computeNextRunAt(schedule) {
101987
102047
  }
101988
102048
 
101989
102049
  // ../../packages/runtime/src/automations/store.ts
101990
- import { randomUUID as randomUUID15 } from "node:crypto";
102050
+ import { randomUUID as randomUUID16 } from "node:crypto";
101991
102051
  function toRecord(row, projectPath) {
101992
102052
  return {
101993
102053
  id: row.id,
@@ -102019,7 +102079,7 @@ function createAutomationStore(db, resolveProjectPath2) {
102019
102079
  return { ...toRecord(row, path), projectId: row.project_id };
102020
102080
  },
102021
102081
  create(projectId, projectPath, data) {
102022
- const id = randomUUID15();
102082
+ const id = randomUUID16();
102023
102083
  const now = (/* @__PURE__ */ new Date()).toISOString();
102024
102084
  const nextRunAt = computeNextRunAt(data.schedule);
102025
102085
  db.prepare(
@@ -102069,8 +102129,8 @@ function createAutomationStore(db, resolveProjectPath2) {
102069
102129
  delete(id) {
102070
102130
  return db.prepare("DELETE FROM automations WHERE id = ?").run(id).changes > 0;
102071
102131
  },
102072
- listDue(nowIso) {
102073
- const reclaimBefore = new Date(Date.parse(nowIso) - 30 * 60 * 1e3).toISOString();
102132
+ listDue(nowIso2) {
102133
+ const reclaimBefore = new Date(Date.parse(nowIso2) - 30 * 60 * 1e3).toISOString();
102074
102134
  const rows = db.prepare(
102075
102135
  `SELECT * FROM automations
102076
102136
  WHERE enabled = 1
@@ -102081,7 +102141,7 @@ function createAutomationStore(db, resolveProjectPath2) {
102081
102141
  OR last_run_status != 'running'
102082
102142
  OR COALESCE(last_run_at, updated_at) < ?
102083
102143
  )`
102084
- ).all(nowIso, reclaimBefore);
102144
+ ).all(nowIso2, reclaimBefore);
102085
102145
  return rows.map((r) => {
102086
102146
  const path = resolveProjectPath2(r.project_id) ?? "";
102087
102147
  return toRecord(r, path);
@@ -102324,10 +102384,10 @@ init_environment();
102324
102384
  // ../../packages/shared/src/environment/draft-content.ts
102325
102385
  function hasPersistableDraftContent(draft) {
102326
102386
  if (draft.text.trim() || draft.attachments?.length) return true;
102327
- const pending = [draft.docJson];
102387
+ const pending2 = [draft.docJson];
102328
102388
  const seen = /* @__PURE__ */ new Set();
102329
- while (pending.length) {
102330
- const node = pending.pop();
102389
+ while (pending2.length) {
102390
+ const node = pending2.pop();
102331
102391
  if (!node || typeof node !== "object") continue;
102332
102392
  if (seen.has(node)) continue;
102333
102393
  seen.add(node);
@@ -102335,7 +102395,7 @@ function hasPersistableDraftContent(draft) {
102335
102395
  if (type === "text" && typeof text2 === "string" && text2.trim()) return true;
102336
102396
  const value = type === "pasteChip" ? attrs?.text : type === "mention" ? attrs?.value : null;
102337
102397
  if (typeof value === "string" && value.trim()) return true;
102338
- if (Array.isArray(content)) pending.push(...content);
102398
+ if (Array.isArray(content)) pending2.push(...content);
102339
102399
  }
102340
102400
  return false;
102341
102401
  }
@@ -102540,7 +102600,7 @@ ON CONFLICT(id) DO UPDATE SET
102540
102600
 
102541
102601
  // src/workspace/artifact-zone.ts
102542
102602
  init_environment();
102543
- import { createHash as createHash12 } from "node:crypto";
102603
+ import { createHash as createHash11 } from "node:crypto";
102544
102604
  import {
102545
102605
  closeSync as closeSync3,
102546
102606
  fstatSync as fstatSync3,
@@ -102862,7 +102922,7 @@ var ArtifactZoneService = class {
102862
102922
  written: 0,
102863
102923
  total: req.total,
102864
102924
  sha256: req.sha256,
102865
- hash: createHash12("sha256"),
102925
+ hash: createHash11("sha256"),
102866
102926
  lastActivityAt: Date.now()
102867
102927
  };
102868
102928
  this.transfers.set(transferId, transfer);
@@ -102968,7 +103028,6 @@ async function startNodeRuntime(partial2 = {}) {
102968
103028
  }
102969
103029
  const allowSimulatedTurnFallback = partial2.allowSimulatedTurnFallback ?? simulatedHarness;
102970
103030
  const providers = new ProviderStore(db, paths.providerSecretsKey);
102971
- const collabSecrets = createNodeSecretCrypto(paths.providerSecretsKey);
102972
103031
  let sessionsRef = null;
102973
103032
  let collaborationRef = null;
102974
103033
  const hostActionMcp = await startHostActionMcpServer({
@@ -103003,8 +103062,9 @@ async function startNodeRuntime(partial2 = {}) {
103003
103062
  if (!collaborationRef) throw Object.assign(new Error("collab not ready"), { code: "failed_precondition" });
103004
103063
  const a = args && typeof args === "object" ? args : {};
103005
103064
  return collaborationRef.start({
103006
- credential: typeof a.credential === "string" ? a.credential : void 0,
103007
- callerSessionId: sessionId
103065
+ callerSessionId: sessionId,
103066
+ launchId: typeof a.launchId === "string" ? a.launchId : "",
103067
+ task: typeof a.task === "string" ? a.task : void 0
103008
103068
  });
103009
103069
  },
103010
103070
  send: async (sessionId, args) => {
@@ -103012,7 +103072,7 @@ async function startNodeRuntime(partial2 = {}) {
103012
103072
  const a = args && typeof args === "object" ? args : {};
103013
103073
  return collaborationRef.send({
103014
103074
  sessionId,
103015
- credential: String(a.credential ?? ""),
103075
+ to: typeof a.to === "string" ? a.to : void 0,
103016
103076
  content: String(a.content ?? ""),
103017
103077
  clientMessageId: typeof a.clientMessageId === "string" ? a.clientMessageId : void 0
103018
103078
  });
@@ -103022,8 +103082,7 @@ async function startNodeRuntime(partial2 = {}) {
103022
103082
  const a = args && typeof args === "object" ? args : {};
103023
103083
  return collaborationRef.retrieve({
103024
103084
  sessionId,
103025
- credentials: Array.isArray(a.credentials) ? a.credentials : void 0,
103026
- credential: typeof a.credential === "string" ? a.credential : void 0
103085
+ from: Array.isArray(a.from) ? a.from.filter((id) => typeof id === "string") : void 0
103027
103086
  });
103028
103087
  }
103029
103088
  }
@@ -103070,7 +103129,6 @@ async function startNodeRuntime(partial2 = {}) {
103070
103129
  providers,
103071
103130
  projects,
103072
103131
  workspaceGit,
103073
- secrets: collabSecrets,
103074
103132
  sessionProviders,
103075
103133
  experimentalClaudeOpenAiChatEnabled: () => loadNodeAgentSettings(paths.configJson).experimentalClaudeOpenAiChatEnabled
103076
103134
  });