chatroom-cli 1.57.3 → 1.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -28903,6 +28903,12 @@ function normalizeCursorSdkListedModels(listedModelIds) {
28903
28903
  var CURSOR_PROVIDER2 = "cursor", DEFAULT_AUTO_MODEL_ID = "default", UI_AUTO_MODEL_ID = "auto";
28904
28904
 
28905
28905
  // src/infrastructure/services/remote-agents/cursor-sdk/cursor-sdk-package.ts
28906
+ var exports_cursor_sdk_package = {};
28907
+ __export(exports_cursor_sdk_package, {
28908
+ importBundledCursorSdk: () => importBundledCursorSdk,
28909
+ getBundledCursorSdkVersion: () => getBundledCursorSdkVersion,
28910
+ formatCursorSdkLoadError: () => formatCursorSdkLoadError
28911
+ });
28906
28912
  import { existsSync, readFileSync as readFileSync2 } from "node:fs";
28907
28913
  import { createRequire as createRequire3 } from "node:module";
28908
28914
  import { dirname as dirname2, join as join7 } from "node:path";
@@ -32520,6 +32526,12 @@ var init_opencode_sdk = __esm(() => {
32520
32526
  });
32521
32527
 
32522
32528
  // src/infrastructure/services/remote-agents/pi-sdk/pi-sdk-package.ts
32529
+ var exports_pi_sdk_package = {};
32530
+ __export(exports_pi_sdk_package, {
32531
+ importBundledPiSdk: () => importBundledPiSdk,
32532
+ getBundledPiSdkVersion: () => getBundledPiSdkVersion,
32533
+ formatPiSdkLoadError: () => formatPiSdkLoadError
32534
+ });
32523
32535
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
32524
32536
  import { dirname as dirname4, join as join9 } from "node:path";
32525
32537
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -75554,14 +75566,25 @@ Put your **complete** deliverable in the handoff message — not in session text
75554
75566
  function getBuilderToPlannerHandoffTemplate() {
75555
75567
  return `${getHandoffRecipientVisibilityCallout("planner")}
75556
75568
 
75557
- **Handoff Template (Builder → Planner)** — paste into the handoff message. Fill in EVERY section; use \`Not Applicable\` when a section does not apply.
75569
+ **Handoff Template (Builder → Planner)** — paste into the handoff message. Fill in EVERY section below. If a section does not apply, write \`Not Applicable\` (do not delete the section):
75558
75570
 
75559
75571
  \`\`\`markdown
75560
75572
  ## Summary
75561
75573
  <what was implemented or attempted, in plain terms>
75562
75574
 
75563
- ## Proof files changed
75575
+ ## Template Disclosure Confirmation
75576
+ - [ ] I confirm that I have seen this template at the start of this task, before implementing or modifying any code
75577
+
75578
+ ## Proof of Principle
75579
+ <!-- Demonstrate adherence to:
75580
+ - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
75581
+ - Static Evaluability and Provability: the system's behavior should be provably correct by looking at the source code, then automated tests, then manual tests, in this order.
75582
+ -->
75583
+ <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
75584
+
75585
+ ## Proof of Completion
75564
75586
  - \`path/to/file.ts\` — <what changed and why>
75587
+ <evidence the goal was met — list every file you modified>
75565
75588
 
75566
75589
  ## Verification
75567
75590
  - \`pnpm typecheck && pnpm test\` — <pass/fail + notes>
@@ -75643,7 +75666,7 @@ Cross-file types, interfaces, or patterns that apply beyond a single file. Write
75643
75666
  - <e.g. "Do not add new abstractions", "Do not refactor unrelated files", "Do not change existing public APIs", or "Not Applicable">
75644
75667
 
75645
75668
  ## Skills to activate
75646
- - <e.g. chatroom skill activate software-engineering --chatroom-id=<id> --role=builder, or "Not Applicable">
75669
+ - <e.g. chatroom skill activate code-review --chatroom-id=<id> --role=builder, or "Not Applicable">
75647
75670
 
75648
75671
  ## Out of scope
75649
75672
  - <files or areas the builder must NOT touch in this slice, or "Not Applicable">
@@ -75684,6 +75707,15 @@ function getPlannerToUserReportTemplate() {
75684
75707
  - \`path/to/file.ts\` — <what changed and why>
75685
75708
  <evidence the goal was met — list every file you (or the builder) modified>
75686
75709
 
75710
+ ## Backlog Tasks Implemented
75711
+ - \`backlog-item-id\` — <backlog item title/summary and how this work addresses it>
75712
+ <List every backlog item this work implemented. Write \`Not Applicable\` if no backlog items were in scope.>
75713
+
75714
+ ## Backlog Pending User Review Confirmation
75715
+ - [ ] I confirm that every backlog item implemented in this work has been moved to \`pending_user_review\` via \`chatroom backlog mark-for-review\` because a PR has been raised for user review
75716
+ - PR URL(s): <link to PR(s), or \`Not Applicable\` if no PR was raised>
75717
+ - If no backlog items apply, write \`Not Applicable\` for the checkbox and explain in one line
75718
+
75687
75719
  ## Key Technical Decisions
75688
75720
  - <schema design, modules, interfaces, domain entities — what you chose and why, or "Not Applicable">
75689
75721
 
@@ -76013,16 +76045,11 @@ var init_fromContext = __esm(() => {
76013
76045
  init_planner();
76014
76046
  init_solo();
76015
76047
  });
76016
- // ../../services/backend/prompts/cli/sections/handoff-templates-preview.ts
76017
- var init_handoff_templates_preview = __esm(() => {
76018
- init_handoff_templates3();
76019
- });
76020
76048
 
76021
76049
  // ../../services/backend/prompts/native/task-started-content.ts
76022
76050
  var init_task_started_content = __esm(() => {
76023
76051
  init_new();
76024
76052
  });
76025
-
76026
76053
  // ../../services/backend/prompts/sections/classification-guide.ts
76027
76054
  var init_classification_guide = __esm(() => {
76028
76055
  init_cli();
@@ -76050,6 +76077,8 @@ var init_current_classification = () => {};
76050
76077
  var init_getting_started = __esm(() => {
76051
76078
  init_getting_started_content();
76052
76079
  });
76080
+ // ../../services/backend/src/domain/usecase/skills/modules/attachments/index.ts
76081
+ var init_attachments = () => {};
76053
76082
 
76054
76083
  // ../../services/backend/prompts/cli/backlog/command.ts
76055
76084
  var init_command3 = __esm(() => {
@@ -76064,18 +76093,11 @@ var init_backlog = __esm(() => {
76064
76093
  // ../../services/backend/src/domain/usecase/skills/modules/code-review/index.ts
76065
76094
  var init_code_review = () => {};
76066
76095
 
76067
- // ../../services/backend/src/domain/usecase/skills/modules/development-workflow/index.ts
76068
- var init_development_workflow = () => {};
76069
-
76070
- // ../../services/backend/src/domain/usecase/skills/modules/software-engineering/index.ts
76071
- var init_software_engineering = () => {};
76072
-
76073
76096
  // ../../services/backend/src/domain/usecase/skills/registry.ts
76074
76097
  var init_registry3 = __esm(() => {
76098
+ init_attachments();
76075
76099
  init_backlog();
76076
76100
  init_code_review();
76077
- init_development_workflow();
76078
- init_software_engineering();
76079
76101
  });
76080
76102
 
76081
76103
  // ../../services/backend/prompts/sections/glossary.ts
@@ -76102,20 +76124,15 @@ var init_glossary = __esm(() => {
76102
76124
  linkedSkillId: "backlog"
76103
76125
  },
76104
76126
  {
76105
- term: "software-engineering",
76106
- definition: "Universal software engineering standards: build from the application core outward, SOLID principles, and naming conventions.",
76107
- linkedSkillId: "software-engineering"
76127
+ term: "attachments",
76128
+ definition: "Message attachment types (task, backlog, message, snippet) and their compose, delivery, and task-read paths. " + "Use when adding or changing attachment UI, delivery XML, or agent-facing attachment formats.",
76129
+ linkedSkillId: "attachments"
76108
76130
  },
76109
76131
  {
76110
76132
  term: "code-review",
76111
76133
  definition: "Eight-pillar code review framework: simplification, type drift, duplication, design patterns, security, test quality, ownership/observability, and dead code elimination. " + "Covers AI-generated code review with focus on maintainability and tech debt prevention.",
76112
76134
  linkedSkillId: "code-review"
76113
76135
  },
76114
- {
76115
- term: "development-workflow",
76116
- definition: "Manages the development and release flow: creating release branches, updating versions, raising PRs, and managing feature branches. " + "Use this skill for coordinating complex release and development processes.",
76117
- linkedSkillId: "development-workflow"
76118
- },
76119
76136
  {
76120
76137
  term: "structural-decisions",
76121
76138
  definition: "Meta-level architectural choices that persist in the codebase and influence consistency: " + "folder structure, file naming, interface definitions, and key abstraction names/locations " + "(e.g., Repository/Service layers)."
@@ -76394,7 +76411,6 @@ var init_generator = __esm(() => {
76394
76411
  init_command();
76395
76412
  init_reminder();
76396
76413
  init_fromContext();
76397
- init_handoff_templates_preview();
76398
76414
  init_classification_guide();
76399
76415
  init_commands_reference();
76400
76416
  init_current_classification();
@@ -77401,6 +77417,52 @@ function readFileContent(filePath, optionName) {
77401
77417
  }
77402
77418
  var init_file_content = () => {};
77403
77419
 
77420
+ // ../../services/backend/prompts/attachments/render-delivery-attachments.ts
77421
+ function renderSnippetAttachment(snippet) {
77422
+ return [
77423
+ ` <attachment reference="${snippet.reference}">`,
77424
+ ` <snippet file-source="${snippet.fileSource}">`,
77425
+ ` <user-selected-content>`,
77426
+ snippet.selectedContent,
77427
+ ` </user-selected-content>`,
77428
+ ` </snippet>`,
77429
+ ` </attachment>`
77430
+ ];
77431
+ }
77432
+ function renderBacklogAttachments(items, ctx) {
77433
+ const lines = [];
77434
+ for (const item of items) {
77435
+ lines.push(` <attachment type="backlog-item">`);
77436
+ lines.push(` - [${item.status.toUpperCase()}] ${item.content}`);
77437
+ lines.push(` ID: ${item._id}`);
77438
+ lines.push(` <hint>Work on this item. When done: chatroom backlog mark-for-review --chatroom-id="${ctx.chatroomId}" --role="${ctx.role}" --backlog-item-id=${item._id}</hint>`);
77439
+ lines.push(` </attachment>`);
77440
+ }
77441
+ return lines;
77442
+ }
77443
+ function renderSnippetAttachments(snippets) {
77444
+ const lines = [];
77445
+ for (const snippet of snippets) {
77446
+ lines.push(...renderSnippetAttachment(snippet));
77447
+ }
77448
+ return lines;
77449
+ }
77450
+ function renderDeliveryAttachmentsBlock(input, ctx) {
77451
+ const backlogLines = input.attachedBacklogItems?.length ? DELIVERY_ATTACHMENT_RENDERERS.backlog(input.attachedBacklogItems, ctx) : [];
77452
+ const snippetLines = input.attachedSnippets?.length ? DELIVERY_ATTACHMENT_RENDERERS.snippet(input.attachedSnippets, ctx) : [];
77453
+ if (backlogLines.length === 0 && snippetLines.length === 0)
77454
+ return [];
77455
+ return ["", "<attachments>", ...backlogLines, ...snippetLines, "</attachments>"];
77456
+ }
77457
+ var DELIVERY_ATTACHMENT_RENDERERS;
77458
+ var init_render_delivery_attachments = __esm(() => {
77459
+ DELIVERY_ATTACHMENT_RENDERERS = {
77460
+ backlog: renderBacklogAttachments,
77461
+ snippet: renderSnippetAttachments,
77462
+ message: () => []
77463
+ };
77464
+ });
77465
+
77404
77466
  // src/commands/task/read/render.ts
77405
77467
  function detectBacklogDivergence(contextContent, attachedIds) {
77406
77468
  if (!contextContent || attachedIds.length === 0) {
@@ -77409,19 +77471,10 @@ function detectBacklogDivergence(contextContent, attachedIds) {
77409
77471
  return attachedIds.filter((id3) => !contextContent.includes(id3));
77410
77472
  }
77411
77473
  function renderAttachments(input, chatroomId, role) {
77412
- if (!input.attachedBacklogItems || input.attachedBacklogItems.length === 0)
77413
- return [];
77414
- const lines = [""];
77415
- lines.push("<attachments>");
77416
- for (const item of input.attachedBacklogItems) {
77417
- lines.push(` <attachment type="backlog-item">`);
77418
- lines.push(` - [${item.status.toUpperCase()}] ${item.content}`);
77419
- lines.push(` ID: ${item._id}`);
77420
- lines.push(` <hint>Work on this item. When done: chatroom backlog mark-for-review --chatroom-id="${chatroomId}" --role="${role}" --backlog-item-id=${item._id}</hint>`);
77421
- lines.push(` </attachment>`);
77422
- }
77423
- lines.push("</attachments>");
77424
- return lines;
77474
+ return renderDeliveryAttachmentsBlock({
77475
+ attachedBacklogItems: input.attachedBacklogItems,
77476
+ attachedSnippets: input.attachedSnippets
77477
+ }, { chatroomId, role, mode: "task-read" });
77425
77478
  }
77426
77479
  function renderDivergenceWarnings(input) {
77427
77480
  const ctx = input.context;
@@ -77476,6 +77529,9 @@ ${content}`);
77476
77529
  return lines.join(`
77477
77530
  `);
77478
77531
  }
77532
+ var init_render = __esm(() => {
77533
+ init_render_delivery_attachments();
77534
+ });
77479
77535
 
77480
77536
  // src/commands/task/read/index.ts
77481
77537
  var exports_read = {};
@@ -77503,6 +77559,18 @@ function validateTaskId(taskId) {
77503
77559
  }
77504
77560
  return exports_Effect.void;
77505
77561
  }
77562
+ function buildTaskPromptFromReadResult(result, chatroomId, role) {
77563
+ return renderTaskPrompt({
77564
+ taskId: result.taskId,
77565
+ status: result.status,
77566
+ content: result.content,
77567
+ chatroomId,
77568
+ role,
77569
+ context: result.context && typeof result.context === "object" ? result.context : undefined,
77570
+ attachedBacklogItems: Array.isArray(result.attachedBacklogItems) ? result.attachedBacklogItems : undefined,
77571
+ attachedSnippets: Array.isArray(result.attachedSnippets) ? result.attachedSnippets : undefined
77572
+ });
77573
+ }
77506
77574
  function handleTaskReadError(err) {
77507
77575
  return exports_Effect.sync(() => {
77508
77576
  const handler = taskReadErrorHandlers[err._tag];
@@ -77535,19 +77603,12 @@ var taskReadEffect = (chatroomId, options) => exports_Effect.gen(function* () {
77535
77603
  taskId
77536
77604
  }).pipe(exports_Effect.mapError((cause3) => ({ _tag: "MutationFailed", cause: cause3 })));
77537
77605
  yield* exports_Effect.sync(() => {
77538
- console.log(renderTaskPrompt({
77539
- taskId: result.taskId,
77540
- status: result.status,
77541
- content: result.content,
77542
- chatroomId,
77543
- role,
77544
- context: result.context && typeof result.context === "object" ? result.context : undefined,
77545
- attachedBacklogItems: Array.isArray(result.attachedBacklogItems) ? result.attachedBacklogItems : undefined
77546
- }));
77606
+ console.log(buildTaskPromptFromReadResult(result, chatroomId, role));
77547
77607
  });
77548
77608
  }), taskReadErrorHandlers;
77549
77609
  var init_read = __esm(() => {
77550
77610
  init_esm();
77611
+ init_render();
77551
77612
  init_api3();
77552
77613
  init_storage();
77553
77614
  init_client2();
@@ -82679,161 +82740,311 @@ var init_commit_detail_sync = __esm(() => {
82679
82740
  seenShas = new Map;
82680
82741
  });
82681
82742
 
82682
- // src/domain/direct-harness/usecases/update-capabilities.ts
82683
- async function updateCapabilities(deps, input) {
82684
- const { publisher, machineId } = deps;
82685
- const { harness, workspace } = input;
82686
- const [agents, providers] = await Promise.all([
82687
- harness.listAgents(),
82688
- harness.listProviders()
82689
- ]);
82690
- await publisher.publish({
82691
- machineId,
82692
- lastSeenAt: Date.now(),
82693
- workspaces: [
82694
- {
82695
- workspaceId: workspace.workspaceId,
82696
- cwd: workspace.cwd,
82697
- name: workspace.name,
82698
- harnesses: [
82699
- {
82700
- name: harness.type,
82701
- displayName: harness.displayName,
82702
- agents: [...agents],
82703
- providers: [...providers]
82704
- }
82705
- ]
82706
- }
82707
- ]
82708
- });
82709
- }
82743
+ // src/infrastructure/harnesses/cursor-sdk/cursor-session.ts
82744
+ import { randomUUID as randomUUID3 } from "node:crypto";
82710
82745
 
82711
- // src/commands/machine/daemon-start/direct-harness/command-subscriber.ts
82712
- function startCommandSubscriber(session2, wsClient2, deps) {
82713
- const processed = new Set;
82714
- let processing = false;
82715
- let pendingDrain = false;
82716
- const runDrain = () => {
82717
- if (processing) {
82718
- pendingDrain = true;
82746
+ class CursorSdkSession {
82747
+ opencodeSessionId;
82748
+ _sessionTitle;
82749
+ get sessionTitle() {
82750
+ return this._sessionTitle;
82751
+ }
82752
+ agent;
82753
+ onClose;
82754
+ listeners = new Set;
82755
+ closed = false;
82756
+ turnCount = 0;
82757
+ constructor(options) {
82758
+ this.agent = options.agent;
82759
+ this.opencodeSessionId = options.opencodeSessionId;
82760
+ this._sessionTitle = options.sessionTitle;
82761
+ this.onClose = options.onClose;
82762
+ }
82763
+ setTitle(title) {
82764
+ this._sessionTitle = title;
82765
+ }
82766
+ async prompt(input) {
82767
+ if (this.closed)
82768
+ throw new Error("Session is closed");
82769
+ const text = input.parts.map((p) => p.text).join(`
82770
+ `);
82771
+ const messageId = randomUUID3();
82772
+ const isFirstTurn = this.turnCount === 0;
82773
+ this.turnCount += 1;
82774
+ const modelId = input.model ? resolveModelFromPrompt(input.model.providerID, input.model.modelID) : undefined;
82775
+ const run3 = await withTimeout(this.agent.send(text, {
82776
+ local: { force: isFirstTurn },
82777
+ idempotencyKey: randomUUID3(),
82778
+ ...modelId ? { model: { id: modelId } } : {}
82779
+ }), SEND_TIMEOUT_MS2, "agent.send");
82780
+ for await (const message of run3.stream()) {
82781
+ if (this.closed)
82782
+ break;
82783
+ this.emitFromSdkMessage(message, messageId);
82784
+ }
82785
+ const result = await withTimeout(run3.wait(), RUN_WAIT_TIMEOUT_MS2, "run.wait");
82786
+ if (result.status === "error") {
82787
+ const detail = typeof result.result === "string" && result.result.trim().length > 0 ? result.result.trim() : `run ${result.id} failed`;
82788
+ throw new Error(detail);
82789
+ }
82790
+ this.emit({ type: "session.idle", payload: {}, timestamp: Date.now() });
82791
+ }
82792
+ onEvent(listener) {
82793
+ this.listeners.add(listener);
82794
+ return () => {
82795
+ this.listeners.delete(listener);
82796
+ };
82797
+ }
82798
+ async close() {
82799
+ if (this.closed)
82719
82800
  return;
82801
+ this.closed = true;
82802
+ try {
82803
+ this.agent.close();
82804
+ } catch {}
82805
+ this.onClose?.(this.opencodeSessionId);
82806
+ this.listeners.clear();
82807
+ }
82808
+ emit(event) {
82809
+ for (const listener of this.listeners) {
82810
+ listener(event);
82720
82811
  }
82721
- processing = true;
82722
- pendingDrain = false;
82723
- drain(session2, deps, processed).finally(() => {
82724
- processing = false;
82725
- if (pendingDrain)
82726
- runDrain();
82812
+ }
82813
+ emitDelta(messageId, delta, partType) {
82814
+ if (!delta)
82815
+ return;
82816
+ this.emit({
82817
+ type: "message.part.delta",
82818
+ payload: { messageID: messageId, delta, partType },
82819
+ timestamp: Date.now()
82727
82820
  });
82728
- };
82729
- const unsub = wsClient2.onUpdate(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId }, () => runDrain(), (err) => {
82730
- console.warn("[direct-harness] Command subscription error:", err instanceof Error ? err.message : String(err));
82731
- });
82732
- return { stop: unsub };
82733
- }
82734
- async function dispatchPendingCommand(session2, deps, cmd) {
82735
- switch (cmd.type) {
82736
- case "refreshCapabilities":
82737
- await handleRefreshCapabilities(session2, deps, cmd);
82738
- break;
82739
- case "refreshSessionTitle":
82740
- await handleRefreshSessionTitle(session2, deps, cmd);
82741
- break;
82742
- default:
82743
- await markFailed(session2, cmd._id, `Unknown command type: ${cmd.type}`);
82744
82821
  }
82745
- }
82746
- async function processPendingCommand(session2, deps, cmd, now) {
82747
- if (now - cmd.createdAt > DIRECT_HARNESS_COMMAND_TTL_MS) {
82748
- console.log(`[direct-harness] Discarding stale command ${cmd._id} (type=${cmd.type}, age=${now - cmd.createdAt}ms)`);
82749
- await markFailed(session2, cmd._id, "Command expired (TTL)");
82750
- return;
82822
+ emitFromSdkMessage(message, messageId) {
82823
+ switch (message.type) {
82824
+ case "assistant":
82825
+ for (const block of message.message.content) {
82826
+ if (block.type === "text" && block.text) {
82827
+ this.emitDelta(messageId, block.text, "text");
82828
+ }
82829
+ }
82830
+ break;
82831
+ case "thinking":
82832
+ if (message.text) {
82833
+ this.emitDelta(messageId, message.text, "reasoning");
82834
+ }
82835
+ break;
82836
+ default:
82837
+ break;
82838
+ }
82751
82839
  }
82752
- await dispatchPendingCommand(session2, deps, cmd);
82753
82840
  }
82754
- async function drain(session2, deps, processed) {
82755
- const pending3 = await session2.backend.query(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId });
82756
- if (!pending3 || pending3.length === 0)
82757
- return;
82758
- const now = Date.now();
82759
- for (const cmd of pending3) {
82760
- if (processed.has(cmd._id))
82761
- continue;
82762
- processed.add(cmd._id);
82763
- try {
82764
- await processPendingCommand(session2, deps, cmd, now);
82765
- } catch (err) {
82766
- const message = err instanceof Error ? err.message : String(err);
82767
- console.warn(`[direct-harness] Command ${cmd._id} failed: ${message}`);
82768
- await markFailed(session2, cmd._id, message).catch(() => {});
82769
- }
82841
+ function resolveModelFromPrompt(providerID, modelID) {
82842
+ if (providerID === "cursor")
82843
+ return resolveCursorSdkModel(modelID);
82844
+ return resolveCursorSdkModel(`${providerID}/${modelID}`);
82845
+ }
82846
+ var SEND_TIMEOUT_MS2 = 60000, RUN_WAIT_TIMEOUT_MS2 = 3600000;
82847
+ var init_cursor_session = () => {};
82848
+
82849
+ // src/infrastructure/harnesses/cursor-sdk/cursor-harness.ts
82850
+ async function loadSdk3() {
82851
+ if (_sdkCache3)
82852
+ return _sdkCache3;
82853
+ if (_sdkLoadError3)
82854
+ throw _sdkLoadError3;
82855
+ try {
82856
+ _sdkCache3 = await importBundledCursorSdk();
82857
+ return _sdkCache3;
82858
+ } catch (err) {
82859
+ _sdkLoadError3 = err;
82860
+ throw err;
82770
82861
  }
82771
82862
  }
82772
- async function handleRefreshCapabilities(session2, deps, cmd) {
82773
- const payload = cmd.refreshCapabilities;
82774
- console.log(`[direct-harness] Processing refreshCapabilities for workspace=${cmd.workspaceId}` + (payload ? ` (initiatedBy=${payload.initiatedBy})` : ""));
82775
- const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId);
82776
- await updateCapabilities({ publisher: deps.publisher, machineId: session2.machineId }, {
82777
- harness,
82778
- workspace: {
82779
- workspaceId: cmd.workspaceId,
82780
- cwd: harness.cwd,
82781
- name: harness.cwd
82863
+
82864
+ class CursorSdkHarness {
82865
+ type = "cursor-sdk";
82866
+ displayName = "Cursor (SDK)";
82867
+ cwd;
82868
+ closed = false;
82869
+ sessions = new Map;
82870
+ constructor(cwd) {
82871
+ this.cwd = cwd;
82872
+ }
82873
+ async models() {
82874
+ const providers = await this.listProviders();
82875
+ const models = [];
82876
+ for (const provider of providers) {
82877
+ for (const model of provider.models) {
82878
+ models.push({
82879
+ id: `${provider.providerID}/${model.modelID}`,
82880
+ name: model.name,
82881
+ provider: provider.name
82882
+ });
82883
+ }
82782
82884
  }
82783
- });
82784
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82785
- sessionId: session2.sessionId,
82786
- commandId: cmd._id,
82787
- status: "done"
82788
- });
82789
- console.log(`[direct-harness] Capabilities refreshed for workspace=${cmd.workspaceId}`);
82790
- }
82791
- async function handleRefreshSessionTitle(session2, deps, cmd) {
82792
- const { harnessSessionId } = cmd.refreshSessionTitle ?? {};
82793
- if (!harnessSessionId) {
82794
- await markFailed(session2, cmd._id, "refreshSessionTitle: missing harnessSessionId");
82795
- return;
82885
+ return models;
82796
82886
  }
82797
- const sessionRow = await session2.backend.query(api.daemon.directHarness.sessions.getSession, {
82798
- harnessSessionId
82799
- });
82800
- if (!sessionRow?.opencodeSessionId) {
82801
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82802
- sessionId: session2.sessionId,
82803
- commandId: cmd._id,
82804
- status: "done"
82887
+ async listAgents() {
82888
+ return [{ name: "builder", mode: "primary" }];
82889
+ }
82890
+ async listProviders() {
82891
+ const apiKey = process.env.CURSOR_API_KEY?.trim();
82892
+ if (!apiKey)
82893
+ return [];
82894
+ const { Cursor } = await loadSdk3();
82895
+ const listed = await withTimeout(Cursor.models.list({ apiKey }), MODELS_LIST_TIMEOUT_MS2, "Cursor.models.list");
82896
+ const modelIds = normalizeCursorSdkListedModels(listed.map((m) => m.id).filter((id3) => id3.length > 0));
82897
+ return [
82898
+ {
82899
+ providerID: "cursor",
82900
+ name: "Cursor",
82901
+ models: modelIds.map((modelID) => ({ modelID, name: modelID }))
82902
+ }
82903
+ ];
82904
+ }
82905
+ async newSession(config3) {
82906
+ if (this.closed)
82907
+ throw new Error("Harness is closed");
82908
+ const apiKey = process.env.CURSOR_API_KEY?.trim();
82909
+ if (!apiKey)
82910
+ throw new Error("CURSOR_API_KEY is not set");
82911
+ const modelId = resolveCursorSdkModel(config3.model ?? DEFAULT_MODEL2);
82912
+ const { Agent } = await loadSdk3();
82913
+ const agent = await withTimeout(Agent.create({
82914
+ apiKey,
82915
+ model: { id: modelId },
82916
+ local: { cwd: this.cwd, settingSources: [] }
82917
+ }), AGENT_CREATE_TIMEOUT_MS2, "Agent.create");
82918
+ const session2 = new CursorSdkSession({
82919
+ agent,
82920
+ opencodeSessionId: agent.agentId,
82921
+ sessionTitle: config3.title ?? "",
82922
+ onClose: (id3) => this.sessions.delete(id3)
82805
82923
  });
82806
- return;
82924
+ this.sessions.set(agent.agentId, session2);
82925
+ return session2;
82807
82926
  }
82808
- const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId);
82809
- const newTitle = await harness.fetchSessionTitle(sessionRow.opencodeSessionId);
82810
- if (newTitle) {
82811
- await session2.backend.mutation(api.daemon.directHarness.sessions.updateSessionTitle, {
82812
- sessionId: session2.sessionId,
82813
- harnessSessionId,
82814
- sessionTitle: newTitle
82927
+ async resumeSession(sessionId, _options) {
82928
+ if (this.closed)
82929
+ throw new Error("Harness is closed");
82930
+ const existing = this.sessions.get(sessionId);
82931
+ if (existing)
82932
+ return existing;
82933
+ const apiKey = process.env.CURSOR_API_KEY?.trim();
82934
+ if (!apiKey)
82935
+ throw new Error("CURSOR_API_KEY is not set");
82936
+ const { Agent } = await loadSdk3();
82937
+ const agent = await withTimeout(Agent.resume(sessionId, {
82938
+ apiKey,
82939
+ model: { id: DEFAULT_MODEL2 },
82940
+ local: { cwd: this.cwd, settingSources: [] }
82941
+ }), AGENT_CREATE_TIMEOUT_MS2, "Agent.resume");
82942
+ const session2 = new CursorSdkSession({
82943
+ agent,
82944
+ opencodeSessionId: agent.agentId,
82945
+ sessionTitle: "",
82946
+ onClose: (id3) => this.sessions.delete(id3)
82815
82947
  });
82816
- console.log(`[direct-harness] Refreshed title for session ${harnessSessionId}: "${newTitle}"`);
82948
+ this.sessions.set(agent.agentId, session2);
82949
+ return session2;
82950
+ }
82951
+ async fetchSessionTitle(_opencodeSessionId) {
82952
+ return;
82953
+ }
82954
+ isAlive() {
82955
+ return !this.closed;
82956
+ }
82957
+ async close() {
82958
+ if (this.closed)
82959
+ return;
82960
+ this.closed = true;
82961
+ const closing = [...this.sessions.values()].map((s) => s.close().catch(() => {}));
82962
+ await Promise.all(closing);
82963
+ this.sessions.clear();
82817
82964
  }
82818
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82819
- sessionId: session2.sessionId,
82820
- commandId: cmd._id,
82821
- status: "done"
82822
- });
82823
82965
  }
82824
- async function markFailed(session2, commandId, errorMessage) {
82825
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82826
- sessionId: session2.sessionId,
82827
- commandId,
82828
- status: "failed",
82829
- errorMessage
82830
- });
82966
+ var DEFAULT_MODEL2 = "composer-2.5", MODELS_LIST_TIMEOUT_MS2 = 60000, AGENT_CREATE_TIMEOUT_MS2 = 60000, _sdkCache3, _sdkLoadError3, startCursorSdkHarness = async (config3) => {
82967
+ try {
82968
+ await loadSdk3();
82969
+ } catch (err) {
82970
+ throw new Error(`cursor-sdk unavailable: ${formatCursorSdkLoadError(err)}`);
82971
+ }
82972
+ if (!process.env.CURSOR_API_KEY?.trim()) {
82973
+ throw new Error("CURSOR_API_KEY is not set");
82974
+ }
82975
+ return new CursorSdkHarness(config3.workingDir);
82976
+ };
82977
+ var init_cursor_harness = __esm(() => {
82978
+ init_cursor_session();
82979
+ init_cursor_sdk_package();
82980
+ });
82981
+
82982
+ // src/infrastructure/harnesses/shared-chunk-extractor.ts
82983
+ function createStandardSdkChunkExtractor() {
82984
+ return function extract(event) {
82985
+ if (event.type !== "message.part.delta")
82986
+ return null;
82987
+ const payload = event.payload;
82988
+ const delta = payload?.delta;
82989
+ if (!delta || delta.length === 0)
82990
+ return null;
82991
+ const messageId = payload?.messageID;
82992
+ if (!messageId)
82993
+ return null;
82994
+ return {
82995
+ content: delta,
82996
+ messageId,
82997
+ partType: payload?.partType ?? "text"
82998
+ };
82999
+ };
82831
83000
  }
82832
- var DIRECT_HARNESS_COMMAND_TTL_MS = 60000;
82833
- var init_command_subscriber = __esm(() => {
82834
- init_api3();
83001
+
83002
+ // src/infrastructure/harnesses/cursor-sdk/event-extractor.ts
83003
+ function createCursorSdkChunkExtractor() {
83004
+ return createStandardSdkChunkExtractor();
83005
+ }
83006
+ var init_event_extractor = () => {};
83007
+
83008
+ // src/infrastructure/harnesses/cursor-sdk/index.ts
83009
+ var init_cursor_sdk2 = __esm(() => {
83010
+ init_cursor_harness();
83011
+ init_cursor_session();
83012
+ init_event_extractor();
82835
83013
  });
82836
83014
 
83015
+ // src/infrastructure/harnesses/opencode-sdk/event-extractor.ts
83016
+ function createOpencodeSdkChunkExtractor() {
83017
+ const partMap = new Map;
83018
+ return function extract(event) {
83019
+ if (event.type === "message.part.updated") {
83020
+ const payload = event.payload;
83021
+ const part = payload?.part;
83022
+ if (part?.id && part?.messageID) {
83023
+ const partType = part.type === "reasoning" ? "reasoning" : "text";
83024
+ partMap.set(part.id, { messageId: part.messageID, partType });
83025
+ const delta = payload?.delta;
83026
+ if (delta && delta.length > 0) {
83027
+ return { content: delta, messageId: part.messageID, partType };
83028
+ }
83029
+ }
83030
+ return null;
83031
+ }
83032
+ if (event.type === "message.part.delta") {
83033
+ const payload = event.payload;
83034
+ const delta = payload?.delta;
83035
+ if (!delta || delta.length === 0)
83036
+ return null;
83037
+ const messageId = payload?.messageID;
83038
+ if (!messageId)
83039
+ return null;
83040
+ const partID = payload?.partID;
83041
+ const partType = (partID ? partMap.get(partID)?.partType : undefined) ?? "text";
83042
+ return { content: delta, messageId, partType };
83043
+ }
83044
+ return null;
83045
+ };
83046
+ }
83047
+
82837
83048
  // src/infrastructure/harnesses/opencode-sdk/sse-event-buffer.ts
82838
83049
  var SseEventBuffer;
82839
83050
  var init_sse_event_buffer = __esm(() => {
@@ -83331,44 +83542,560 @@ var init_opencode_harness = __esm(() => {
83331
83542
  init_spawn_env();
83332
83543
  });
83333
83544
 
83334
- // src/infrastructure/harnesses/opencode-sdk/event-extractor.ts
83335
- function createOpencodeSdkChunkExtractor() {
83336
- const partMap = new Map;
83337
- return function extract(event) {
83338
- if (event.type === "message.part.updated") {
83339
- const payload = event.payload;
83340
- const part = payload?.part;
83341
- if (part?.id && part?.messageID) {
83342
- const partType = part.type === "reasoning" ? "reasoning" : "text";
83343
- partMap.set(part.id, { messageId: part.messageID, partType });
83344
- const delta = payload?.delta;
83345
- if (delta && delta.length > 0) {
83346
- return { content: delta, messageId: part.messageID, partType };
83347
- }
83348
- }
83349
- return null;
83545
+ // src/infrastructure/harnesses/pi-sdk/pi-session.ts
83546
+ import { randomUUID as randomUUID4 } from "node:crypto";
83547
+
83548
+ class PiSdkSession {
83549
+ opencodeSessionId;
83550
+ _sessionTitle;
83551
+ get sessionTitle() {
83552
+ return this._sessionTitle;
83553
+ }
83554
+ session;
83555
+ onClose;
83556
+ listeners = new Set;
83557
+ closed = false;
83558
+ unsubscribe;
83559
+ constructor(options) {
83560
+ this.session = options.session;
83561
+ this.opencodeSessionId = options.opencodeSessionId;
83562
+ this._sessionTitle = options.sessionTitle;
83563
+ this.onClose = options.onClose;
83564
+ }
83565
+ setTitle(title) {
83566
+ this._sessionTitle = title;
83567
+ }
83568
+ async prompt(input) {
83569
+ if (this.closed)
83570
+ throw new Error("Session is closed");
83571
+ const text = input.parts.map((p) => p.text).join(`
83572
+ `);
83573
+ const messageId = randomUUID4();
83574
+ const onSessionEvent = (event) => {
83575
+ if (this.closed)
83576
+ return;
83577
+ this.emitFromPiEvent(event, messageId);
83578
+ };
83579
+ this.unsubscribe?.();
83580
+ this.unsubscribe = this.session.subscribe(onSessionEvent);
83581
+ await withTimeout(this.session.prompt(text), PROMPT_TIMEOUT_MS2, "session.prompt");
83582
+ this.emit({ type: "session.idle", payload: {}, timestamp: Date.now() });
83583
+ }
83584
+ onEvent(listener) {
83585
+ this.listeners.add(listener);
83586
+ return () => {
83587
+ this.listeners.delete(listener);
83588
+ };
83589
+ }
83590
+ async close() {
83591
+ if (this.closed)
83592
+ return;
83593
+ this.closed = true;
83594
+ this.unsubscribe?.();
83595
+ try {
83596
+ await this.session.abort();
83597
+ } catch {}
83598
+ try {
83599
+ this.session.dispose();
83600
+ } catch {}
83601
+ this.onClose?.(this.opencodeSessionId);
83602
+ this.listeners.clear();
83603
+ }
83604
+ emit(event) {
83605
+ for (const listener of this.listeners) {
83606
+ listener(event);
83350
83607
  }
83351
- if (event.type === "message.part.delta") {
83352
- const payload = event.payload;
83353
- const delta = payload?.delta;
83354
- if (!delta || delta.length === 0)
83355
- return null;
83356
- const messageId = payload?.messageID;
83357
- if (!messageId)
83358
- return null;
83359
- const partID = payload?.partID;
83360
- const partType = (partID ? partMap.get(partID)?.partType : undefined) ?? "text";
83361
- return { content: delta, messageId, partType };
83608
+ }
83609
+ emitDelta(messageId, delta, partType) {
83610
+ if (!delta)
83611
+ return;
83612
+ this.emit({
83613
+ type: "message.part.delta",
83614
+ payload: { messageID: messageId, delta, partType },
83615
+ timestamp: Date.now()
83616
+ });
83617
+ }
83618
+ emitFromPiEvent(event, messageId) {
83619
+ if (event.type !== "message_update")
83620
+ return;
83621
+ const assistantEvent = event.assistantMessageEvent;
83622
+ if (assistantEvent.type === "text_delta") {
83623
+ this.emitDelta(messageId, assistantEvent.delta, "text");
83624
+ } else if (assistantEvent.type === "thinking_delta") {
83625
+ this.emitDelta(messageId, assistantEvent.delta, "reasoning");
83362
83626
  }
83363
- return null;
83364
- };
83627
+ }
83628
+ }
83629
+ var PROMPT_TIMEOUT_MS2 = 3600000;
83630
+ var init_pi_session = () => {};
83631
+
83632
+ // src/infrastructure/harnesses/pi-sdk/pi-harness.ts
83633
+ async function loadSdk4() {
83634
+ if (_sdkCache4)
83635
+ return _sdkCache4;
83636
+ if (_sdkLoadError4)
83637
+ throw _sdkLoadError4;
83638
+ try {
83639
+ _sdkCache4 = await importBundledPiSdk();
83640
+ return _sdkCache4;
83641
+ } catch (err) {
83642
+ _sdkLoadError4 = err;
83643
+ throw err;
83644
+ }
83645
+ }
83646
+ function resolveModel2(modelRegistry, model) {
83647
+ if (model) {
83648
+ const slash = model.indexOf("/");
83649
+ if (slash === -1) {
83650
+ return modelRegistry.getAll().find((entry) => entry.id === model);
83651
+ }
83652
+ const provider = model.slice(0, slash);
83653
+ const modelId = model.slice(slash + 1);
83654
+ return modelRegistry.find(provider, modelId);
83655
+ }
83656
+ return modelRegistry.getAvailable()[0];
83657
+ }
83658
+
83659
+ class PiSdkHarness {
83660
+ type = "pi-sdk";
83661
+ displayName = "Pi (SDK)";
83662
+ cwd;
83663
+ closed = false;
83664
+ sessions = new Map;
83665
+ modelRegistry;
83666
+ authStorage;
83667
+ constructor(cwd, modelRegistry, authStorage) {
83668
+ this.cwd = cwd;
83669
+ this.modelRegistry = modelRegistry;
83670
+ this.authStorage = authStorage;
83671
+ }
83672
+ async models() {
83673
+ return this.modelRegistry.getAvailable().map((entry) => ({
83674
+ id: `${entry.provider}/${entry.id}`,
83675
+ name: entry.id,
83676
+ provider: entry.provider
83677
+ }));
83678
+ }
83679
+ async listAgents() {
83680
+ return [{ name: "builder", mode: "primary" }];
83681
+ }
83682
+ async listProviders() {
83683
+ const byProvider = new Map;
83684
+ for (const entry of this.modelRegistry.getAvailable()) {
83685
+ const models = byProvider.get(entry.provider) ?? [];
83686
+ models.push({ modelID: entry.id, name: entry.id });
83687
+ byProvider.set(entry.provider, models);
83688
+ }
83689
+ return [...byProvider.entries()].map(([providerID, models]) => ({
83690
+ providerID,
83691
+ name: providerID,
83692
+ models
83693
+ }));
83694
+ }
83695
+ async newSession(config3) {
83696
+ if (this.closed)
83697
+ throw new Error("Harness is closed");
83698
+ const session2 = await this.createAgentSession(config3.systemPrompt ?? "", config3.model);
83699
+ const piSession = new PiSdkSession({
83700
+ session: session2,
83701
+ opencodeSessionId: session2.sessionId,
83702
+ sessionTitle: config3.title ?? "",
83703
+ onClose: (id3) => this.sessions.delete(id3)
83704
+ });
83705
+ this.sessions.set(session2.sessionId, piSession);
83706
+ return piSession;
83707
+ }
83708
+ async resumeSession(sessionId, _options) {
83709
+ if (this.closed)
83710
+ throw new Error("Harness is closed");
83711
+ const existing = this.sessions.get(sessionId);
83712
+ if (existing)
83713
+ return existing;
83714
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk4();
83715
+ const sessions = await SessionManager.list(this.cwd, getPiSessionDir(this.cwd));
83716
+ const match17 = sessions.find((s) => s.id === sessionId);
83717
+ if (!match17) {
83718
+ throw new Error(`Session ${sessionId} not found on the harness`);
83719
+ }
83720
+ const resourceLoader = new DefaultResourceLoader({
83721
+ cwd: this.cwd,
83722
+ agentDir: getAgentDir(),
83723
+ systemPromptOverride: () => ""
83724
+ });
83725
+ await resourceLoader.reload();
83726
+ const resolvedModel = resolveModel2(this.modelRegistry);
83727
+ if (!resolvedModel) {
83728
+ throw new Error("No Pi model available");
83729
+ }
83730
+ const { session: session2 } = await withTimeout(createAgentSession({
83731
+ cwd: this.cwd,
83732
+ model: resolvedModel,
83733
+ sessionManager: SessionManager.open(match17.path, getPiSessionDir(this.cwd)),
83734
+ authStorage: this.authStorage,
83735
+ modelRegistry: this.modelRegistry,
83736
+ resourceLoader
83737
+ }), SESSION_CREATE_TIMEOUT_MS3, "createAgentSession");
83738
+ const piSession = new PiSdkSession({
83739
+ session: session2,
83740
+ opencodeSessionId: session2.sessionId,
83741
+ sessionTitle: match17.name ?? "",
83742
+ onClose: (id3) => this.sessions.delete(id3)
83743
+ });
83744
+ this.sessions.set(session2.sessionId, piSession);
83745
+ return piSession;
83746
+ }
83747
+ async fetchSessionTitle(_opencodeSessionId) {
83748
+ return;
83749
+ }
83750
+ isAlive() {
83751
+ return !this.closed;
83752
+ }
83753
+ async close() {
83754
+ if (this.closed)
83755
+ return;
83756
+ this.closed = true;
83757
+ const closing = [...this.sessions.values()].map((s) => s.close().catch(() => {}));
83758
+ await Promise.all(closing);
83759
+ this.sessions.clear();
83760
+ }
83761
+ async createAgentSession(systemPrompt, model) {
83762
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk4();
83763
+ const resolvedModel = resolveModel2(this.modelRegistry, model ?? DEFAULT_MODEL3);
83764
+ if (!resolvedModel) {
83765
+ throw new Error("No Pi model available — configure provider credentials in ~/.pi/agent/auth.json");
83766
+ }
83767
+ const resourceLoader = new DefaultResourceLoader({
83768
+ cwd: this.cwd,
83769
+ agentDir: getAgentDir(),
83770
+ systemPromptOverride: () => systemPrompt
83771
+ });
83772
+ await resourceLoader.reload();
83773
+ const { session: session2 } = await withTimeout(createAgentSession({
83774
+ cwd: this.cwd,
83775
+ model: resolvedModel,
83776
+ sessionManager: SessionManager.create(getPiSessionDir(this.cwd)),
83777
+ authStorage: this.authStorage,
83778
+ modelRegistry: this.modelRegistry,
83779
+ resourceLoader
83780
+ }), SESSION_CREATE_TIMEOUT_MS3, "createAgentSession");
83781
+ return session2;
83782
+ }
83783
+ }
83784
+ var SESSION_CREATE_TIMEOUT_MS3 = 60000, DEFAULT_MODEL3 = "opencode/big-pickle", _sdkCache4, _sdkLoadError4, startPiSdkHarness = async (config3) => {
83785
+ try {
83786
+ const { AuthStorage, ModelRegistry } = await loadSdk4();
83787
+ const authStorage = AuthStorage.create();
83788
+ const modelRegistry = ModelRegistry.create(authStorage);
83789
+ if (modelRegistry.getAvailable().length === 0) {
83790
+ throw new Error("No Pi models available");
83791
+ }
83792
+ return new PiSdkHarness(config3.workingDir, modelRegistry, authStorage);
83793
+ } catch (err) {
83794
+ throw new Error(`pi-sdk unavailable: ${formatPiSdkLoadError(err)}`);
83795
+ }
83796
+ };
83797
+ var init_pi_harness = __esm(() => {
83798
+ init_pi_session();
83799
+ init_pi_agent_service();
83800
+ init_pi_sdk_package();
83801
+ });
83802
+
83803
+ // src/infrastructure/harnesses/pi-sdk/event-extractor.ts
83804
+ function createPiSdkChunkExtractor() {
83805
+ return createStandardSdkChunkExtractor();
83365
83806
  }
83807
+ var init_event_extractor2 = () => {};
83808
+
83809
+ // src/infrastructure/harnesses/pi-sdk/index.ts
83810
+ var init_pi_sdk2 = __esm(() => {
83811
+ init_pi_harness();
83812
+ init_pi_session();
83813
+ init_event_extractor2();
83814
+ });
83366
83815
 
83367
- // src/infrastructure/harnesses/opencode-sdk/index.ts
83368
- var init_opencode_sdk2 = __esm(() => {
83816
+ // src/infrastructure/harnesses/registry.ts
83817
+ import { execSync as execSync2 } from "node:child_process";
83818
+ async function startBoundHarness(config3) {
83819
+ switch (config3.harnessName) {
83820
+ case "opencode-sdk":
83821
+ return startOpencodeSdkHarness(config3);
83822
+ case "cursor-sdk":
83823
+ return startCursorSdkHarness(config3);
83824
+ case "pi-sdk":
83825
+ return startPiSdkHarness(config3);
83826
+ default: {
83827
+ const _exhaustive = config3.harnessName;
83828
+ throw new Error(`Unsupported direct harness: ${String(_exhaustive)}`);
83829
+ }
83830
+ }
83831
+ }
83832
+ function createChunkExtractor(harnessName) {
83833
+ switch (harnessName) {
83834
+ case "opencode-sdk":
83835
+ return createOpencodeSdkChunkExtractor();
83836
+ case "cursor-sdk":
83837
+ return createCursorSdkChunkExtractor();
83838
+ case "pi-sdk":
83839
+ return createPiSdkChunkExtractor();
83840
+ default:
83841
+ return createStandardSdkChunkExtractor();
83842
+ }
83843
+ }
83844
+ function opencodeOnPath() {
83845
+ try {
83846
+ execSync2("opencode --version", { stdio: "pipe" });
83847
+ return true;
83848
+ } catch {
83849
+ return false;
83850
+ }
83851
+ }
83852
+ async function isCursorSdkInstalled() {
83853
+ if (!process.env.CURSOR_API_KEY?.trim())
83854
+ return false;
83855
+ try {
83856
+ const { importBundledCursorSdk: importBundledCursorSdk2 } = await Promise.resolve().then(() => (init_cursor_sdk_package(), exports_cursor_sdk_package));
83857
+ await importBundledCursorSdk2();
83858
+ return true;
83859
+ } catch {
83860
+ return false;
83861
+ }
83862
+ }
83863
+ async function isPiSdkInstalled() {
83864
+ try {
83865
+ const { importBundledPiSdk: importBundledPiSdk2 } = await Promise.resolve().then(() => (init_pi_sdk_package(), exports_pi_sdk_package));
83866
+ const { ModelRegistry, AuthStorage } = await importBundledPiSdk2();
83867
+ const authStorage = AuthStorage.create();
83868
+ const modelRegistry = ModelRegistry.create(authStorage);
83869
+ return modelRegistry.getAvailable().length > 0;
83870
+ } catch {
83871
+ return false;
83872
+ }
83873
+ }
83874
+ async function listInstalledNativeDirectHarnesses() {
83875
+ const installed2 = [];
83876
+ if (opencodeOnPath())
83877
+ installed2.push("opencode-sdk");
83878
+ if (await isCursorSdkInstalled())
83879
+ installed2.push("cursor-sdk");
83880
+ if (await isPiSdkInstalled())
83881
+ installed2.push("pi-sdk");
83882
+ return installed2;
83883
+ }
83884
+ var init_registry4 = __esm(() => {
83885
+ init_cursor_sdk2();
83369
83886
  init_opencode_harness();
83887
+ init_pi_sdk2();
83888
+ });
83889
+
83890
+ // src/commands/machine/daemon-start/direct-harness/command-subscriber.ts
83891
+ function startCommandSubscriber(session2, wsClient2, deps) {
83892
+ const processed = new Set;
83893
+ let processing = false;
83894
+ let pendingDrain = false;
83895
+ const runDrain = () => {
83896
+ if (processing) {
83897
+ pendingDrain = true;
83898
+ return;
83899
+ }
83900
+ processing = true;
83901
+ pendingDrain = false;
83902
+ drain(session2, deps, processed).finally(() => {
83903
+ processing = false;
83904
+ if (pendingDrain)
83905
+ runDrain();
83906
+ });
83907
+ };
83908
+ const unsub = wsClient2.onUpdate(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId }, () => runDrain(), (err) => {
83909
+ console.warn("[direct-harness] Command subscription error:", err instanceof Error ? err.message : String(err));
83910
+ });
83911
+ return { stop: unsub };
83912
+ }
83913
+ async function dispatchPendingCommand(session2, deps, cmd) {
83914
+ switch (cmd.type) {
83915
+ case "refreshCapabilities":
83916
+ await handleRefreshCapabilities(session2, deps, cmd);
83917
+ break;
83918
+ case "refreshSessionTitle":
83919
+ await handleRefreshSessionTitle(session2, deps, cmd);
83920
+ break;
83921
+ case "closeSession":
83922
+ await handleCloseSession(session2, deps, cmd);
83923
+ break;
83924
+ default:
83925
+ await markFailed(session2, cmd._id, `Unknown command type: ${cmd.type}`);
83926
+ }
83927
+ }
83928
+ async function processPendingCommand(session2, deps, cmd, now) {
83929
+ if (now - cmd.createdAt > DIRECT_HARNESS_COMMAND_TTL_MS) {
83930
+ console.log(`[direct-harness] Discarding stale command ${cmd._id} (type=${cmd.type}, age=${now - cmd.createdAt}ms)`);
83931
+ await markFailed(session2, cmd._id, "Command expired (TTL)");
83932
+ return;
83933
+ }
83934
+ await dispatchPendingCommand(session2, deps, cmd);
83935
+ }
83936
+ async function drain(session2, deps, processed) {
83937
+ const pending3 = await session2.backend.query(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId });
83938
+ if (!pending3 || pending3.length === 0)
83939
+ return;
83940
+ const now = Date.now();
83941
+ for (const cmd of pending3) {
83942
+ if (processed.has(cmd._id))
83943
+ continue;
83944
+ processed.add(cmd._id);
83945
+ try {
83946
+ await processPendingCommand(session2, deps, cmd, now);
83947
+ } catch (err) {
83948
+ const message = err instanceof Error ? err.message : String(err);
83949
+ console.warn(`[direct-harness] Command ${cmd._id} failed: ${message}`);
83950
+ await markFailed(session2, cmd._id, message).catch(() => {});
83951
+ }
83952
+ }
83953
+ }
83954
+ async function handleRefreshCapabilities(session2, deps, cmd) {
83955
+ const payload = cmd.refreshCapabilities;
83956
+ console.log(`[direct-harness] Processing refreshCapabilities for workspace=${cmd.workspaceId}` + (payload ? ` (initiatedBy=${payload.initiatedBy})` : ""));
83957
+ const installed2 = await listInstalledNativeDirectHarnesses();
83958
+ const harnessEntries = [];
83959
+ let cwd = "";
83960
+ for (const harnessName of installed2) {
83961
+ const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId, harnessName);
83962
+ cwd = harness.cwd;
83963
+ const [agents, providers] = await Promise.all([harness.listAgents(), harness.listProviders()]);
83964
+ harnessEntries.push({
83965
+ name: harness.type,
83966
+ displayName: harness.displayName,
83967
+ agents: [...agents],
83968
+ providers: [...providers]
83969
+ });
83970
+ }
83971
+ const existing = await session2.backend.query(api.daemon.directHarness.capabilities.getForMachine, { sessionId: session2.sessionId, machineId: session2.machineId });
83972
+ const updatedWorkspace = {
83973
+ workspaceId: cmd.workspaceId,
83974
+ cwd,
83975
+ name: cwd,
83976
+ harnesses: harnessEntries
83977
+ };
83978
+ const merged = [
83979
+ ...(existing?.workspaces ?? []).filter((ws) => ws.workspaceId !== cmd.workspaceId),
83980
+ updatedWorkspace
83981
+ ];
83982
+ await deps.publisher.publish({
83983
+ machineId: session2.machineId,
83984
+ lastSeenAt: Date.now(),
83985
+ workspaces: merged
83986
+ });
83987
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
83988
+ sessionId: session2.sessionId,
83989
+ commandId: cmd._id,
83990
+ status: "done"
83991
+ });
83992
+ console.log(`[direct-harness] Capabilities refreshed for workspace=${cmd.workspaceId}`);
83993
+ }
83994
+ async function handleRefreshSessionTitle(session2, deps, cmd) {
83995
+ const { harnessSessionId } = cmd.refreshSessionTitle ?? {};
83996
+ if (!harnessSessionId) {
83997
+ await markFailed(session2, cmd._id, "refreshSessionTitle: missing harnessSessionId");
83998
+ return;
83999
+ }
84000
+ const sessionRow = await session2.backend.query(api.daemon.directHarness.sessions.getSession, {
84001
+ harnessSessionId
84002
+ });
84003
+ if (!sessionRow?.opencodeSessionId) {
84004
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84005
+ sessionId: session2.sessionId,
84006
+ commandId: cmd._id,
84007
+ status: "done"
84008
+ });
84009
+ return;
84010
+ }
84011
+ const harnessName = sessionRow.harnessName ?? "opencode-sdk";
84012
+ const harness = await deps.lifecycleManager.getOrStart(sessionRow.workspaceId ?? cmd.workspaceId, harnessName);
84013
+ const newTitle = await harness.fetchSessionTitle(sessionRow.opencodeSessionId);
84014
+ if (newTitle) {
84015
+ await session2.backend.mutation(api.daemon.directHarness.sessions.updateSessionTitle, {
84016
+ sessionId: session2.sessionId,
84017
+ harnessSessionId,
84018
+ sessionTitle: newTitle
84019
+ });
84020
+ console.log(`[direct-harness] Refreshed title for session ${harnessSessionId}: "${newTitle}"`);
84021
+ }
84022
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84023
+ sessionId: session2.sessionId,
84024
+ commandId: cmd._id,
84025
+ status: "done"
84026
+ });
84027
+ }
84028
+ async function closeDisconnectedHarnessSession(deps, sessionRow, harnessSessionId) {
84029
+ if (!sessionRow.opencodeSessionId) {
84030
+ await deps.sessionRepository.markClosed(harnessSessionId);
84031
+ return;
84032
+ }
84033
+ const harnessName = sessionRow.harnessName ?? "opencode-sdk";
84034
+ const harness = await deps.lifecycleManager.getOrStart(sessionRow.workspaceId ?? "", harnessName);
84035
+ const liveSession = await harness.resumeSession(sessionRow.opencodeSessionId, {
84036
+ harnessSessionId
84037
+ });
84038
+ await liveSession.close();
84039
+ await deps.sessionRepository.markClosed(harnessSessionId);
84040
+ }
84041
+ async function closeHarnessSession(session2, deps, harnessSessionId) {
84042
+ const sessionRow = await session2.backend.query(api.daemon.directHarness.sessions.getSession, {
84043
+ harnessSessionId
84044
+ });
84045
+ if (!sessionRow) {
84046
+ throw new Error(`Harness session ${harnessSessionId} not found`);
84047
+ }
84048
+ if (sessionRow.status === "closed" || sessionRow.status === "failed") {
84049
+ return;
84050
+ }
84051
+ const handle = deps.activeSessions.get(harnessSessionId);
84052
+ if (handle) {
84053
+ await handle.close();
84054
+ deps.activeSessions.delete(harnessSessionId);
84055
+ return;
84056
+ }
84057
+ await closeDisconnectedHarnessSession(deps, sessionRow, harnessSessionId);
84058
+ }
84059
+ async function handleCloseSession(session2, deps, cmd) {
84060
+ const { harnessSessionId } = cmd.closeSession ?? {};
84061
+ if (!harnessSessionId) {
84062
+ await markFailed(session2, cmd._id, "closeSession: missing harnessSessionId");
84063
+ return;
84064
+ }
84065
+ console.log(`[direct-harness] Processing closeSession for harnessSessionId=${harnessSessionId}`);
84066
+ await closeHarnessSession(session2, deps, harnessSessionId);
84067
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84068
+ sessionId: session2.sessionId,
84069
+ commandId: cmd._id,
84070
+ status: "done"
84071
+ });
84072
+ console.log(`[direct-harness] Closed session ${harnessSessionId}`);
84073
+ }
84074
+ async function markFailed(session2, commandId, errorMessage) {
84075
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84076
+ sessionId: session2.sessionId,
84077
+ commandId,
84078
+ status: "failed",
84079
+ errorMessage
84080
+ });
84081
+ }
84082
+ var DIRECT_HARNESS_COMMAND_TTL_MS = 60000;
84083
+ var init_command_subscriber = __esm(() => {
84084
+ init_api3();
84085
+ init_registry4();
83370
84086
  });
83371
84087
 
84088
+ // src/infrastructure/harnesses/harness-key.ts
84089
+ function makeHarnessKey(workspaceId, harnessName) {
84090
+ return `${workspaceId}:${harnessName}`;
84091
+ }
84092
+ function parseHarnessKey(key) {
84093
+ const idx = key.indexOf(":");
84094
+ if (idx === -1)
84095
+ throw new Error(`Invalid harness key: ${key}`);
84096
+ return { workspaceId: key.slice(0, idx), harnessName: key.slice(idx + 1) };
84097
+ }
84098
+
83372
84099
  // src/commands/machine/daemon-start/direct-harness/harness-lifecycle-manager.ts
83373
84100
  class HarnessLifecycleManager {
83374
84101
  harnesses;
@@ -83383,10 +84110,15 @@ class HarnessLifecycleManager {
83383
84110
  this.resolveWorkspace = resolveWorkspace;
83384
84111
  this.resolvedConvexUrl = resolvedConvexUrl;
83385
84112
  }
83386
- getOrStartHarnessEffect(workspaceId) {
83387
- const existing = this.harnesses.get(workspaceId);
83388
- if (existing)
84113
+ getOrStartHarnessEffect(workspaceId, harnessName) {
84114
+ const key = makeHarnessKey(workspaceId, harnessName);
84115
+ const existing = this.harnesses.get(key);
84116
+ if (existing?.isAlive())
83389
84117
  return exports_Effect.succeed(existing);
84118
+ if (existing) {
84119
+ existing.close().catch(() => {});
84120
+ this.harnesses.delete(key);
84121
+ }
83390
84122
  return exports_Effect.gen(this, function* () {
83391
84123
  const workspace = yield* exports_Effect.tryPromise({
83392
84124
  try: () => this.resolveWorkspace(workspaceId),
@@ -83395,23 +84127,23 @@ class HarnessLifecycleManager {
83395
84127
  if (!workspace) {
83396
84128
  return yield* exports_Effect.fail(new Error(`Workspace ${workspaceId} not found — cannot start harness`));
83397
84129
  }
83398
- console.log(`[${formatTimestamp()}] \uD83D\uDD27 Auto-starting harness for workspace=${workspaceId} (${workspace.workingDir})`);
84130
+ console.log(`[${formatTimestamp()}] \uD83D\uDD27 Auto-starting ${harnessName} for workspace=${workspaceId} (${workspace.workingDir})`);
83399
84131
  const harness = yield* exports_Effect.tryPromise({
83400
- try: () => startOpencodeSdkHarness({
83401
- type: "opencode",
84132
+ try: () => startBoundHarness({
84133
+ harnessName,
83402
84134
  workingDir: workspace.workingDir,
83403
84135
  workspaceId,
83404
84136
  resolvedConvexUrl: this.resolvedConvexUrl
83405
84137
  }),
83406
84138
  catch: (e) => e instanceof Error ? e : new Error(String(e))
83407
84139
  });
83408
- this.harnesses.set(workspaceId, harness);
83409
- this.idleSince.set(workspaceId, Date.now());
84140
+ this.harnesses.set(key, harness);
84141
+ this.idleSince.set(key, Date.now());
83410
84142
  return harness;
83411
84143
  });
83412
84144
  }
83413
- async getOrStart(workspaceId) {
83414
- return exports_Effect.runPromise(this.getOrStartHarnessEffect(workspaceId));
84145
+ async getOrStart(workspaceId, harnessName) {
84146
+ return exports_Effect.runPromise(this.getOrStartHarnessEffect(workspaceId, harnessName));
83415
84147
  }
83416
84148
  startMonitoring() {
83417
84149
  if (this.monitorTimer)
@@ -83427,28 +84159,30 @@ class HarnessLifecycleManager {
83427
84159
  }
83428
84160
  checkInactivity() {
83429
84161
  const now = Date.now();
83430
- for (const workspaceId of this.harnesses.keys()) {
83431
- const hasActiveSessions = this.hasSessionsForWorkspace(workspaceId);
84162
+ for (const key of this.harnesses.keys()) {
84163
+ const { workspaceId, harnessName } = parseHarnessKey(key);
84164
+ const hasActiveSessions = this.hasSessionsForHarnessKey(workspaceId, harnessName);
83432
84165
  if (hasActiveSessions) {
83433
- this.idleSince.delete(workspaceId);
84166
+ this.idleSince.delete(key);
83434
84167
  } else {
83435
- const idleStart = this.idleSince.get(workspaceId);
84168
+ const idleStart = this.idleSince.get(key);
83436
84169
  if (idleStart === undefined) {
83437
- this.idleSince.set(workspaceId, now);
84170
+ this.idleSince.set(key, now);
83438
84171
  } else if (now - idleStart >= INACTIVITY_TTL_MS) {
83439
84172
  const idleMinutes = Math.round((now - idleStart) / 60000);
83440
- console.log(`[${formatTimestamp()}] \uD83D\uDD2A Killing idle harness for workspace=${workspaceId} (idle ${idleMinutes}min)`);
83441
- this.harnesses.get(workspaceId)?.close().catch(() => {});
83442
- this.harnesses.delete(workspaceId);
83443
- this.idleSince.delete(workspaceId);
84173
+ console.log(`[${formatTimestamp()}] \uD83D\uDD2A Killing idle harness ${harnessName} for workspace=${workspaceId} (idle ${idleMinutes}min)`);
84174
+ this.harnesses.get(key)?.close().catch(() => {});
84175
+ this.harnesses.delete(key);
84176
+ this.idleSince.delete(key);
83444
84177
  }
83445
84178
  }
83446
84179
  }
83447
84180
  }
83448
- hasSessionsForWorkspace(workspaceId) {
84181
+ hasSessionsForHarnessKey(workspaceId, harnessName) {
83449
84182
  for (const handle of this.activeSessions.values()) {
83450
- if (handle.workspaceId === workspaceId)
84183
+ if (handle.workspaceId === workspaceId && handle.harnessName === harnessName) {
83451
84184
  return true;
84185
+ }
83452
84186
  }
83453
84187
  return false;
83454
84188
  }
@@ -83456,7 +84190,7 @@ class HarnessLifecycleManager {
83456
84190
  var INACTIVITY_TTL_MS, CHECK_INTERVAL_MS;
83457
84191
  var init_harness_lifecycle_manager = __esm(() => {
83458
84192
  init_esm();
83459
- init_opencode_sdk2();
84193
+ init_registry4();
83460
84194
  INACTIVITY_TTL_MS = 15 * 60 * 1000;
83461
84195
  CHECK_INTERVAL_MS = 60 * 1000;
83462
84196
  });
@@ -83551,6 +84285,7 @@ async function resumeSession(deps, input) {
83551
84285
  let closed = false;
83552
84286
  const handle = {
83553
84287
  harnessSessionId,
84288
+ harnessName: input.harnessName,
83554
84289
  opencodeSessionId,
83555
84290
  workspaceId: input.workspaceId ?? "",
83556
84291
  session: session2,
@@ -83615,14 +84350,16 @@ async function resumeSessionHandle(session2, deps, rowId, info) {
83615
84350
  return null;
83616
84351
  }
83617
84352
  const workspaceId = info.workspaceId;
84353
+ const harnessName = info.harnessName;
83618
84354
  if (!workspaceId) {
83619
84355
  console.warn(`[direct-harness] Cannot resume session ${rowId}: no workspace info`);
83620
84356
  return null;
83621
84357
  }
83622
- let harness = deps.harnesses.get(workspaceId);
84358
+ const key = makeHarnessKey(workspaceId, harnessName);
84359
+ let harness = deps.harnesses.get(key);
83623
84360
  if (harness && !harness.isAlive()) {
83624
84361
  harness.close().catch(() => {});
83625
- deps.harnesses.delete(workspaceId);
84362
+ deps.harnesses.delete(key);
83626
84363
  harness = undefined;
83627
84364
  }
83628
84365
  if (!harness) {
@@ -83634,20 +84371,20 @@ async function resumeSessionHandle(session2, deps, rowId, info) {
83634
84371
  console.warn(`[direct-harness] Cannot resume session ${rowId}: workspace not found`);
83635
84372
  return null;
83636
84373
  }
83637
- harness = await startOpencodeSdkHarness({
83638
- type: "opencode",
84374
+ harness = await startBoundHarness({
84375
+ harnessName,
83639
84376
  workingDir: workspace.workingDir,
83640
84377
  workspaceId,
83641
84378
  resolvedConvexUrl: session2.convexUrl
83642
84379
  });
83643
- deps.harnesses.set(workspaceId, harness);
84380
+ deps.harnesses.set(key, harness);
83644
84381
  }
83645
84382
  try {
83646
84383
  return await resumeSession({
83647
84384
  harness,
83648
84385
  journalFactory: deps.journalFactory,
83649
- chunkExtractor: createOpencodeSdkChunkExtractor()
83650
- }, { harnessSessionId: rowId, opencodeSessionId, workspaceId });
84386
+ chunkExtractor: createChunkExtractor(harness.type)
84387
+ }, { harnessSessionId: rowId, opencodeSessionId, workspaceId, harnessName });
83651
84388
  } catch (err) {
83652
84389
  const message = err instanceof Error ? err.message : String(err);
83653
84390
  console.warn(`[direct-harness] Cannot resume session ${rowId}: ${message}`);
@@ -83693,8 +84430,9 @@ async function deliverPendingMessages(handle, deps, rowId, messages, info) {
83693
84430
  console.warn(`[direct-harness] Prompt failed for session ${rowId} seq=${msg.seq}: ${message}`);
83694
84431
  await deps.sessionRepository.markIdle(rowId).catch(() => {});
83695
84432
  deps.activeSessions.delete(rowId);
83696
- if (info?.workspaceId)
83697
- deps.harnesses.delete(info.workspaceId);
84433
+ if (info?.workspaceId && info.harnessName) {
84434
+ deps.harnesses.delete(makeHarnessKey(info.workspaceId, info.harnessName));
84435
+ }
83698
84436
  return;
83699
84437
  }
83700
84438
  }
@@ -83721,7 +84459,7 @@ var init_prompt_subscriber = __esm(() => {
83721
84459
  init_idle_handler();
83722
84460
  init_api3();
83723
84461
  init_resume_session();
83724
- init_opencode_sdk2();
84462
+ init_registry4();
83725
84463
  });
83726
84464
 
83727
84465
  // src/commands/machine/daemon-start/direct-harness/session-subscriber.ts
@@ -83746,21 +84484,23 @@ function startSessionSubscriber(daemonSession, wsClient2, deps) {
83746
84484
  return { stop: unsub };
83747
84485
  }
83748
84486
  async function getOrCreateHarness(daemonSession, deps, session2, workspace) {
83749
- let harness = deps.harnesses.get(session2.workspaceId);
84487
+ const harnessName = session2.opencode?.harnessName ?? "opencode-sdk";
84488
+ const key = makeHarnessKey(session2.workspaceId, harnessName);
84489
+ let harness = deps.harnesses.get(key);
83750
84490
  if (harness && !harness.isAlive()) {
83751
- console.warn(`[direct-harness] Harness for workspace ${session2.workspaceId} is no longer alive — restarting`);
84491
+ console.warn(`[direct-harness] Harness ${harnessName} for workspace ${session2.workspaceId} is no longer alive — restarting`);
83752
84492
  harness.close().catch(() => {});
83753
- deps.harnesses.delete(session2.workspaceId);
84493
+ deps.harnesses.delete(key);
83754
84494
  harness = undefined;
83755
84495
  }
83756
84496
  if (!harness) {
83757
- harness = await startOpencodeSdkHarness({
83758
- type: "opencode",
84497
+ harness = await startBoundHarness({
84498
+ harnessName,
83759
84499
  workingDir: workspace.workingDir,
83760
84500
  workspaceId: session2.workspaceId,
83761
84501
  resolvedConvexUrl: daemonSession.convexUrl
83762
84502
  });
83763
- deps.harnesses.set(session2.workspaceId, harness);
84503
+ deps.harnesses.set(key, harness);
83764
84504
  }
83765
84505
  return harness;
83766
84506
  }
@@ -83812,13 +84552,17 @@ async function processOne(daemonSession, deps, session2) {
83812
84552
  await deps.sessionRepository.markFailed(rowId);
83813
84553
  return;
83814
84554
  }
84555
+ const harnessName = session2.opencode?.harnessName ?? "opencode-sdk";
83815
84556
  const harness = await getOrCreateHarness(daemonSession, deps, session2, workspace);
84557
+ const modelConfig = session2.opencode?.lastUsedConfig.model;
84558
+ const model = modelConfig ? `${modelConfig.providerID}/${modelConfig.modelID}` : undefined;
83816
84559
  const liveSession = await harness.newSession({
83817
84560
  agent: session2.opencode?.lastUsedConfig.agent ?? "build",
84561
+ model,
83818
84562
  harnessSessionId: rowId
83819
84563
  });
83820
84564
  const journal = deps.journalFactory.create(rowId);
83821
- const extractChunk = createOpencodeSdkChunkExtractor();
84565
+ const extractChunk = createChunkExtractor(harness.type);
83822
84566
  const idleConfig = {
83823
84567
  agent: session2.opencode?.lastUsedConfig.agent ?? "build",
83824
84568
  model: session2.opencode?.lastUsedConfig.model
@@ -83833,9 +84577,11 @@ async function processOne(daemonSession, deps, session2) {
83833
84577
  await journal.commit();
83834
84578
  await liveSession.close();
83835
84579
  await deps.sessionRepository.markClosed(rowId);
84580
+ deps.activeSessions.delete(rowId);
83836
84581
  };
83837
84582
  const handle = {
83838
84583
  harnessSessionId: rowId,
84584
+ harnessName,
83839
84585
  opencodeSessionId: liveSession.opencodeSessionId,
83840
84586
  workspaceId: session2.workspaceId,
83841
84587
  session: liveSession,
@@ -83873,7 +84619,61 @@ async function processOne(daemonSession, deps, session2) {
83873
84619
  var init_session_subscriber = __esm(() => {
83874
84620
  init_idle_handler();
83875
84621
  init_api3();
83876
- init_opencode_sdk2();
84622
+ init_registry4();
84623
+ });
84624
+
84625
+ // src/commands/machine/daemon-start/direct-harness/shutdown-sessions.ts
84626
+ async function listHarnessSessionIdsForShutdown(session2) {
84627
+ const ids3 = new Set;
84628
+ const managed = await session2.backend.query(api.daemon.directHarness.turns.getMachineHarnessSessions, {
84629
+ sessionId: session2.sessionId,
84630
+ machineId: session2.machineId
84631
+ });
84632
+ for (const row of managed) {
84633
+ ids3.add(row.harnessSessionId);
84634
+ }
84635
+ const pending3 = await session2.backend.query(api.daemon.directHarness.sessions.listPendingSessionsForMachine, {
84636
+ sessionId: session2.sessionId,
84637
+ machineId: session2.machineId
84638
+ });
84639
+ for (const row of pending3) {
84640
+ ids3.add(row._id);
84641
+ }
84642
+ return [...ids3];
84643
+ }
84644
+ async function closeAllMachineHarnessSessionsOnShutdown(session2, deps) {
84645
+ const commandDeps = {
84646
+ lifecycleManager: deps.lifecycleManager,
84647
+ publisher: {},
84648
+ activeSessions: deps.activeSessions,
84649
+ sessionRepository: deps.sessionRepository
84650
+ };
84651
+ const harnessSessionIds = await listHarnessSessionIdsForShutdown(session2);
84652
+ if (harnessSessionIds.length === 0)
84653
+ return;
84654
+ console.log(`[direct-harness] Closing ${harnessSessionIds.length} harness session(s) on daemon shutdown...`);
84655
+ for (const harnessSessionId of harnessSessionIds) {
84656
+ try {
84657
+ await session2.backend.mutation(api.daemon.directHarness.turns.markOrphanTurnsFailed, {
84658
+ sessionId: session2.sessionId,
84659
+ machineId: session2.machineId,
84660
+ harnessSessionId
84661
+ });
84662
+ } catch {}
84663
+ try {
84664
+ await closeHarnessSession(session2, commandDeps, harnessSessionId);
84665
+ } catch (err) {
84666
+ const message = err instanceof Error ? err.message : String(err);
84667
+ console.warn(`[direct-harness] Failed to close session ${harnessSessionId} on shutdown: ${message}`);
84668
+ try {
84669
+ await deps.sessionRepository.markClosed(harnessSessionId);
84670
+ } catch {}
84671
+ }
84672
+ }
84673
+ }
84674
+ var init_shutdown_sessions = __esm(() => {
84675
+ init_command_subscriber();
84676
+ init_api3();
83877
84677
  });
83878
84678
 
83879
84679
  // src/infrastructure/repos/convex-capabilities-publisher.ts
@@ -84164,13 +84964,20 @@ function startDirectHarnessSubscriptions(session2, wsClient2, activeSessions, ha
84164
84964
  publisher: new ConvexCapabilitiesPublisher({
84165
84965
  backend: session2.backend,
84166
84966
  sessionId: session2.sessionId
84167
- })
84967
+ }),
84968
+ activeSessions,
84969
+ sessionRepository
84168
84970
  });
84169
84971
  return {
84170
84972
  pendingPromptSubscriptionHandle,
84171
84973
  pendingHarnessSessionSubscriptionHandle,
84172
84974
  commandSubscriptionHandle,
84173
- lifecycleManager
84975
+ lifecycleManager,
84976
+ closeSessionsOnShutdown: () => closeAllMachineHarnessSessionsOnShutdown(session2, {
84977
+ lifecycleManager,
84978
+ activeSessions,
84979
+ sessionRepository
84980
+ })
84174
84981
  };
84175
84982
  }
84176
84983
  var init_start_subscriptions = __esm(() => {
@@ -84178,6 +84985,7 @@ var init_start_subscriptions = __esm(() => {
84178
84985
  init_harness_lifecycle_manager();
84179
84986
  init_prompt_subscriber();
84180
84987
  init_session_subscriber();
84988
+ init_shutdown_sessions();
84181
84989
  init_api3();
84182
84990
  init_convex_capabilities_publisher();
84183
84991
  init_convex_output_repository();
@@ -85191,12 +85999,13 @@ class NativeTaskDeliveryCoordinator {
85191
85999
  }
85192
86000
  }
85193
86001
  reconcileAssignedTasks(params) {
85194
- const { tasks, runtime: runtime4, effectContext: effectContext2, agentMgr, sessionDeps } = params;
85195
- for (const task of tasks) {
85196
- const slot = agentMgr.getSlot(task.chatroomId, task.agentConfig.role);
86002
+ const { tasks, runtime: runtime4, effectContext: effectContext2, agentMgr, sessionDeps, machineId } = params;
86003
+ const ledger = this.ledger;
86004
+ for (const row of tasks) {
86005
+ const slot = agentMgr.getSlot(row.chatroomId, row.agentConfig.role);
85197
86006
  const harnessSessionId = slot?.harnessSessionId;
85198
- if (!shouldDeliverNativeTask(task, {
85199
- ledger: this.ledger,
86007
+ if (!shouldDeliverNativeTask(row, {
86008
+ ledger,
85200
86009
  harnessSessionId
85201
86010
  })) {
85202
86011
  continue;
@@ -85204,14 +86013,24 @@ class NativeTaskDeliveryCoordinator {
85204
86013
  if (!harnessSessionId) {
85205
86014
  continue;
85206
86015
  }
85207
- exports_Runtime.runFork(runtime4)(runNativeInjectionEffect(task, harnessSessionId, {
85208
- sessionId: sessionDeps.sessionId,
85209
- backend: sessionDeps.backend,
85210
- agentMgr: {
85211
- resumeTurnForSlot: (args2) => exports_Effect.runPromise(agentMgr.resumeTurnForSlot(args2))
85212
- },
85213
- convexUrl: sessionDeps.convexUrl
85214
- }, this.ledger).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[NativeTaskDelivery] delivery failed for ${task.agentConfig.role}@${task.chatroomId}: ${getErrorMessage(err)}`)))));
86016
+ exports_Runtime.runFork(runtime4)(exports_Effect.gen(function* () {
86017
+ const full = yield* exports_Effect.tryPromise(() => sessionDeps.backend.query(api.machines.getAssignedTaskForAction, {
86018
+ sessionId: sessionDeps.sessionId,
86019
+ machineId,
86020
+ taskId: row.taskId,
86021
+ role: row.agentConfig.role
86022
+ }));
86023
+ if (!full)
86024
+ return;
86025
+ yield* runNativeInjectionEffect(full, harnessSessionId, {
86026
+ sessionId: sessionDeps.sessionId,
86027
+ backend: sessionDeps.backend,
86028
+ agentMgr: {
86029
+ resumeTurnForSlot: (args2) => exports_Effect.runPromise(agentMgr.resumeTurnForSlot(args2))
86030
+ },
86031
+ convexUrl: sessionDeps.convexUrl
86032
+ }, ledger);
86033
+ }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[NativeTaskDelivery] delivery failed for ${row.agentConfig.role}@${row.chatroomId}: ${getErrorMessage(err)}`)))));
85215
86034
  }
85216
86035
  }
85217
86036
  }
@@ -85227,6 +86046,7 @@ var init_native_task_delivery_coordinator = __esm(() => {
85227
86046
  init_esm();
85228
86047
  init_native_task_injector_logic();
85229
86048
  init_native_task_injector();
86049
+ init_api3();
85230
86050
  init_convex_error();
85231
86051
  });
85232
86052
 
@@ -85557,6 +86377,9 @@ function appendRecentLogLine(slot, line) {
85557
86377
  }
85558
86378
  var RECENT_LOG_LINE_CAP2 = 100;
85559
86379
 
86380
+ // src/domain/agent-lifecycle/policies/cursor-sdk-session-reopen-retry.ts
86381
+ var CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS = 20, CURSOR_SDK_SESSION_REOPEN_INTERVAL_MS = 5000, CURSOR_SDK_SESSION_REOPEN_REASON = "platform.cursor_sdk_session_reopen";
86382
+
85560
86383
  // src/domain/agent-lifecycle/use-cases/handle-turn-completed.ts
85561
86384
  async function handleTurnCompleted(deps, input, slot) {
85562
86385
  if (await tryAbortResumeStorm(deps, input, slot)) {
@@ -85956,6 +86779,7 @@ class AgentProcessManager {
85956
86779
  deps;
85957
86780
  slots = new Map;
85958
86781
  lastHarnessSessions = new Map;
86782
+ sessionReopenRetryInFlight = new Set;
85959
86783
  exitRetryQueue = [];
85960
86784
  exitRetryTimer = null;
85961
86785
  turnEndQueue = new TurnEndQueue;
@@ -86305,6 +87129,10 @@ class AgentProcessManager {
86305
87129
  return;
86306
87130
  }
86307
87131
  const coldRestartAfterRunError = this.clearHarnessSessionAfterRunError(key, logs);
87132
+ if (harness === "cursor-sdk") {
87133
+ this.retryCursorSdkSessionReopen(opts, ctx, coldRestartAfterRunError);
87134
+ return;
87135
+ }
86308
87136
  this.ensureRunning({
86309
87137
  chatroomId: opts.chatroomId,
86310
87138
  role: opts.role,
@@ -86322,6 +87150,49 @@ class AgentProcessManager {
86322
87150
  this.emitStartFailedEvent(opts.role, opts.chatroomId, err.message);
86323
87151
  });
86324
87152
  }
87153
+ async retryCursorSdkSessionReopen(opts, ctx, coldRestartAfterRunError) {
87154
+ const key = agentKey3(opts.chatroomId, opts.role);
87155
+ if (this.sessionReopenRetryInFlight.has(key)) {
87156
+ return;
87157
+ }
87158
+ this.sessionReopenRetryInFlight.add(key);
87159
+ const harness = ctx.harness;
87160
+ const model = ctx.model;
87161
+ const workingDir = ctx.workingDir;
87162
+ const wantResume = coldRestartAfterRunError ? false : ctx.wantResume ?? true;
87163
+ const storedSessionId = this.lastHarnessSessions.get(key)?.harnessSessionId;
87164
+ let lastError = "unknown";
87165
+ try {
87166
+ for (let attempt = 1;attempt <= CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS; attempt++) {
87167
+ await this.emitSessionReopenRetry(opts.chatroomId, opts.role, attempt, attempt > 1 ? lastError : undefined, storedSessionId);
87168
+ const result = await this.ensureRunning({
87169
+ chatroomId: opts.chatroomId,
87170
+ role: opts.role,
87171
+ agentHarness: harness,
87172
+ model,
87173
+ workingDir,
87174
+ reason: CURSOR_SDK_SESSION_REOPEN_REASON,
87175
+ wantResume
87176
+ });
87177
+ if (result.success) {
87178
+ return;
87179
+ }
87180
+ lastError = result.error ?? "unknown";
87181
+ if (attempt < CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS) {
87182
+ await this.deps.clock.delay(CURSOR_SDK_SESSION_REOPEN_INTERVAL_MS);
87183
+ }
87184
+ }
87185
+ const failureMessage = `cursor-sdk session reopen failed after ${CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS} attempts: ${lastError}`;
87186
+ console.log(`[AgentProcessManager] ⛔ ${failureMessage}`);
87187
+ this.emitStartFailedEvent(opts.role, opts.chatroomId, failureMessage);
87188
+ } catch (err) {
87189
+ const message = err instanceof Error ? err.message : String(err);
87190
+ console.log(` ⚠️ cursor-sdk session reopen retry loop failed: ${message}`);
87191
+ this.emitStartFailedEvent(opts.role, opts.chatroomId, message);
87192
+ } finally {
87193
+ this.sessionReopenRetryInFlight.delete(key);
87194
+ }
87195
+ }
86325
87196
  handlePermanentFailureForRestart(opts, recentLogLines, startFailedAlreadyEmitted) {
86326
87197
  const error = isTerminalProviderFailureInLogs(recentLogLines ?? []) ? formatTerminalProviderFailureMessage(recentLogLines ?? []) : formatPermanentHarnessFailureMessage(recentLogLines ?? []);
86327
87198
  console.log(`[AgentProcessManager] ⛔ Skipping restart — ${error}`);
@@ -86637,6 +87508,23 @@ class AgentProcessManager {
86637
87508
  console.log(` ⚠️ Failed to emit sessionResumeFailed event: ${err.message}`);
86638
87509
  }
86639
87510
  }
87511
+ async emitSessionReopenRetry(chatroomId, role, attempt, error, harnessSessionId) {
87512
+ try {
87513
+ await this.deps.backend.mutation(api.machines.emitSessionReopenRetry, {
87514
+ sessionId: this.deps.sessionId,
87515
+ machineId: this.deps.machineId,
87516
+ chatroomId,
87517
+ role,
87518
+ attempt,
87519
+ maxAttempts: CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS,
87520
+ ...error ? { error } : {},
87521
+ ...harnessSessionId ? { harnessSessionId } : {}
87522
+ });
87523
+ console.log(`[AgentProcessManager] ✅ Emitted agent.sessionReopenRetry for ${role} (attempt ${attempt}/${CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS})`);
87524
+ } catch (err) {
87525
+ console.log(` ⚠️ Failed to emit sessionReopenRetry event: ${err.message}`);
87526
+ }
87527
+ }
86640
87528
  resetSlotIdle(slot) {
86641
87529
  slot.state = "idle";
86642
87530
  slot.pendingOperation = undefined;
@@ -87039,7 +87927,7 @@ class SpawnRateLimiter {
87039
87927
  this.config = { ...DEFAULT_CONFIG2, ...config3 };
87040
87928
  }
87041
87929
  tryConsume(chatroomId, reason) {
87042
- if (reason.startsWith("user.") || reason === "platform.auto_restart_on_new_context") {
87930
+ if (reason.startsWith("user.") || reason === "platform.auto_restart_on_new_context" || reason === "platform.cursor_sdk_session_reopen") {
87043
87931
  return { allowed: true };
87044
87932
  }
87045
87933
  const bucket = this._getOrCreateBucket(chatroomId);
@@ -87066,13 +87954,16 @@ class SpawnRateLimiter {
87066
87954
  };
87067
87955
  }
87068
87956
  _getOrCreateBucket(chatroomId) {
87069
- if (!this.buckets.has(chatroomId)) {
87070
- this.buckets.set(chatroomId, {
87071
- tokens: this.config.initialTokens,
87072
- lastRefillAt: Date.now()
87073
- });
87957
+ const existing = this.buckets.get(chatroomId);
87958
+ if (existing) {
87959
+ return existing;
87074
87960
  }
87075
- return this.buckets.get(chatroomId);
87961
+ const bucket = {
87962
+ tokens: this.config.initialTokens,
87963
+ lastRefillAt: Date.now()
87964
+ };
87965
+ this.buckets.set(chatroomId, bucket);
87966
+ return bucket;
87076
87967
  }
87077
87968
  _refill(bucket) {
87078
87969
  const now = Date.now();
@@ -87959,6 +88850,432 @@ var init_task_monitor_logic = __esm(() => {
87959
88850
  init_predicates();
87960
88851
  });
87961
88852
 
88853
+ // src/infrastructure/incremental-sync/working-snapshot.ts
88854
+ class WorkingSnapshot {
88855
+ opts;
88856
+ rows = new Map;
88857
+ constructor(opts) {
88858
+ this.opts = opts;
88859
+ }
88860
+ replaceAll(rows) {
88861
+ this.rows.clear();
88862
+ for (const row of rows) {
88863
+ this.rows.set(this.opts.rowKey(row), row);
88864
+ }
88865
+ }
88866
+ getByKey(key) {
88867
+ return this.rows.get(key);
88868
+ }
88869
+ getBySignal(signal) {
88870
+ return this.rows.get(this.opts.signalKey(signal));
88871
+ }
88872
+ mergeSignal(signal) {
88873
+ const key = this.opts.signalKey(signal);
88874
+ const merged = this.opts.mergeSignal(this.rows.get(key), signal);
88875
+ if (!merged) {
88876
+ return;
88877
+ }
88878
+ this.rows.set(key, merged);
88879
+ return merged;
88880
+ }
88881
+ }
88882
+
88883
+ // src/commands/machine/daemon-start/task-monitor-snapshot.ts
88884
+ function taskSnapshotKey(taskId, role) {
88885
+ return `${taskId}:${role}`;
88886
+ }
88887
+ function mergeSignalIntoTaskSnapshot(existing, signal) {
88888
+ if (!existing) {
88889
+ return;
88890
+ }
88891
+ return {
88892
+ ...existing,
88893
+ status: signal.status,
88894
+ agentConfig: {
88895
+ ...existing.agentConfig,
88896
+ spawnedAgentPid: signal.spawnedAgentPid ?? existing.agentConfig.spawnedAgentPid,
88897
+ desiredState: signal.desiredState ?? existing.agentConfig.desiredState
88898
+ },
88899
+ participant: {
88900
+ lastSeenAction: signal.lastSeenAction ?? existing.participant?.lastSeenAction ?? null,
88901
+ lastSeenAt: existing.participant?.lastSeenAt ?? null,
88902
+ lastStatus: existing.participant?.lastStatus ?? null
88903
+ }
88904
+ };
88905
+ }
88906
+ function createTaskMonitorSnapshot() {
88907
+ return new WorkingSnapshot(taskMonitorSnapshotOptions);
88908
+ }
88909
+ var taskMonitorSnapshotOptions;
88910
+ var init_task_monitor_snapshot = __esm(() => {
88911
+ taskMonitorSnapshotOptions = {
88912
+ rowKey: (row) => taskSnapshotKey(row.taskId, row.agentConfig.role),
88913
+ signalKey: (signal) => taskSnapshotKey(signal.taskId, signal.role),
88914
+ mergeSignal: mergeSignalIntoTaskSnapshot
88915
+ };
88916
+ });
88917
+
88918
+ // src/infrastructure/incremental-sync/layers.ts
88919
+ var IntervalClock, IntervalClockLive;
88920
+ var init_layers = __esm(() => {
88921
+ init_esm();
88922
+ IntervalClock = class IntervalClock extends exports_Context.Tag("IntervalClock")() {
88923
+ };
88924
+ IntervalClockLive = exports_Layer.succeed(IntervalClock, {
88925
+ sleep: (ms) => exports_Effect.promise(() => new Promise((resolve5) => setTimeout(resolve5, ms)))
88926
+ });
88927
+ });
88928
+
88929
+ // src/infrastructure/incremental-sync/message-buffer.ts
88930
+ class MessageBuffer {
88931
+ keyOf;
88932
+ queue = [];
88933
+ inBuffer = new Set;
88934
+ inFlight = new Map;
88935
+ recentlyAcked = new Map;
88936
+ dedupe;
88937
+ dedupeTtlMs;
88938
+ maxSize;
88939
+ constructor(config3, keyOf) {
88940
+ this.keyOf = keyOf;
88941
+ this.dedupe = config3.dedupe ?? true;
88942
+ this.dedupeTtlMs = config3.dedupeTtlMs ?? 0;
88943
+ this.maxSize = config3.maxSize;
88944
+ }
88945
+ enqueue(items) {
88946
+ this.pruneRecentlyAcked();
88947
+ let enqueued = 0;
88948
+ for (const item of items) {
88949
+ const key = this.keyOf(item);
88950
+ if (this.dedupe && this.shouldSkip(key)) {
88951
+ continue;
88952
+ }
88953
+ this.queue.push({ key, item });
88954
+ this.inBuffer.add(key);
88955
+ enqueued++;
88956
+ }
88957
+ if (enqueued > 0) {
88958
+ this.queue.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
88959
+ }
88960
+ this.enforceMaxSize();
88961
+ return enqueued;
88962
+ }
88963
+ dequeue() {
88964
+ this.pruneRecentlyAcked();
88965
+ const next4 = this.queue.shift();
88966
+ if (!next4)
88967
+ return;
88968
+ this.inBuffer.delete(next4.key);
88969
+ this.inFlight.set(next4.key, next4.item);
88970
+ return next4.item;
88971
+ }
88972
+ ack(key) {
88973
+ this.inFlight.delete(key);
88974
+ if (this.dedupe) {
88975
+ this.recentlyAcked.set(key, Date.now());
88976
+ }
88977
+ }
88978
+ nack(key, requeue = false) {
88979
+ const item = this.inFlight.get(key);
88980
+ this.inFlight.delete(key);
88981
+ if (requeue && item !== undefined) {
88982
+ this.enqueue([item]);
88983
+ }
88984
+ }
88985
+ size() {
88986
+ return this.queue.length;
88987
+ }
88988
+ highKeyOf(items) {
88989
+ if (items.length === 0)
88990
+ return null;
88991
+ let high = null;
88992
+ for (const item of items) {
88993
+ const key = this.keyOf(item);
88994
+ if (high === null || key > high) {
88995
+ high = key;
88996
+ }
88997
+ }
88998
+ return high;
88999
+ }
89000
+ shouldSkip(key) {
89001
+ if (this.inBuffer.has(key) || this.inFlight.has(key)) {
89002
+ return true;
89003
+ }
89004
+ if (this.dedupeTtlMs > 0) {
89005
+ const ackedAt = this.recentlyAcked.get(key);
89006
+ if (ackedAt !== undefined && Date.now() - ackedAt < this.dedupeTtlMs) {
89007
+ return true;
89008
+ }
89009
+ } else if (this.recentlyAcked.has(key)) {
89010
+ return true;
89011
+ }
89012
+ return false;
89013
+ }
89014
+ pruneRecentlyAcked() {
89015
+ if (this.dedupeTtlMs <= 0)
89016
+ return;
89017
+ const now = Date.now();
89018
+ for (const [key, ackedAt] of this.recentlyAcked) {
89019
+ if (now - ackedAt >= this.dedupeTtlMs) {
89020
+ this.recentlyAcked.delete(key);
89021
+ }
89022
+ }
89023
+ }
89024
+ enforceMaxSize() {
89025
+ while (this.queue.length > this.maxSize) {
89026
+ const dropped = this.queue.shift();
89027
+ if (dropped) {
89028
+ this.inBuffer.delete(dropped.key);
89029
+ console.warn(`[incremental-sync] Buffer full — dropped oldest unacked item (key=${dropped.key})`);
89030
+ }
89031
+ }
89032
+ }
89033
+ }
89034
+
89035
+ // src/infrastructure/incremental-sync/resolve-high-key.ts
89036
+ function resolveHighKey(def, buffer, page) {
89037
+ if (page.highKey !== null)
89038
+ return page.highKey;
89039
+ const fromItems = buffer.highKeyOf(page.items);
89040
+ if (fromItems !== null)
89041
+ return fromItems;
89042
+ if (def.itemToKey && page.items.length > 0) {
89043
+ let high = null;
89044
+ for (const item of page.items) {
89045
+ const key = def.itemToKey(item);
89046
+ if (high === null || key > high)
89047
+ high = key;
89048
+ }
89049
+ return high;
89050
+ }
89051
+ return null;
89052
+ }
89053
+
89054
+ // src/infrastructure/incremental-sync/subscribe-loop.ts
89055
+ function startSubscribeLoop(opts) {
89056
+ let afterKey = opts.initialAfterKey ?? null;
89057
+ let stopped = false;
89058
+ let unsubscribe;
89059
+ let drainInFlight = false;
89060
+ const subscribe = () => {
89061
+ if (stopped)
89062
+ return;
89063
+ unsubscribe?.();
89064
+ unsubscribe = opts.wsClient.onUpdate(opts.target.query, opts.target.buildArgs(opts.args, afterKey, opts.config.limit), (result) => {
89065
+ if (stopped)
89066
+ return;
89067
+ const page = opts.target.parsePage(result);
89068
+ drainPages(page);
89069
+ }, (err) => {
89070
+ opts.onError?.(err);
89071
+ });
89072
+ };
89073
+ const fetchPage = async (cursor) => {
89074
+ const result = await opts.wsClient.query(opts.target.query, opts.target.buildArgs(opts.args, cursor, opts.config.limit));
89075
+ return opts.target.parsePage(result);
89076
+ };
89077
+ const drainPages = async (initialPage) => {
89078
+ if (stopped || drainInFlight)
89079
+ return;
89080
+ drainInFlight = true;
89081
+ try {
89082
+ const cursorAtStart = afterKey;
89083
+ let page = initialPage;
89084
+ while (!stopped && page.items.length > 0) {
89085
+ opts.buffer.enqueue(page.items);
89086
+ const nextKey = resolveHighKey(opts.def, opts.buffer, page);
89087
+ if (nextKey === null) {
89088
+ break;
89089
+ }
89090
+ afterKey = nextKey;
89091
+ if (!page.hasMore) {
89092
+ break;
89093
+ }
89094
+ page = await fetchPage(afterKey);
89095
+ }
89096
+ if (!stopped && afterKey !== cursorAtStart) {
89097
+ subscribe();
89098
+ }
89099
+ } catch (err) {
89100
+ opts.onError?.(err);
89101
+ } finally {
89102
+ drainInFlight = false;
89103
+ }
89104
+ };
89105
+ subscribe();
89106
+ return {
89107
+ stop: () => {
89108
+ stopped = true;
89109
+ unsubscribe?.();
89110
+ unsubscribe = undefined;
89111
+ },
89112
+ getAfterKey: () => afterKey
89113
+ };
89114
+ }
89115
+ var init_subscribe_loop = () => {};
89116
+
89117
+ // src/infrastructure/incremental-sync/feed-runtime.ts
89118
+ function runWorkerLoop(feedName, buffer, keyOf, onItem) {
89119
+ return exports_Effect.gen(function* () {
89120
+ while (true) {
89121
+ const item = buffer.dequeue();
89122
+ if (item === undefined) {
89123
+ yield* exports_Effect.sleep("10 millis");
89124
+ continue;
89125
+ }
89126
+ const key = keyOf(item);
89127
+ const ctx = {
89128
+ item,
89129
+ feedName,
89130
+ ack: () => buffer.ack(key),
89131
+ nack: (opts) => buffer.nack(key, opts?.requeue ?? false)
89132
+ };
89133
+ yield* onItem(ctx).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89134
+ }
89135
+ });
89136
+ }
89137
+ var runIncrementalSubscribeLive = (opts) => exports_Effect.gen(function* () {
89138
+ const buffer = new MessageBuffer(opts.buffer, opts.def.itemKey);
89139
+ const subscribeHandle = startSubscribeLoop({
89140
+ wsClient: opts.wsClient,
89141
+ target: opts.target,
89142
+ args: opts.args,
89143
+ buffer,
89144
+ def: opts.def,
89145
+ config: opts.subscribe,
89146
+ initialAfterKey: opts.initialAfterKey ?? null,
89147
+ onError: opts.onError
89148
+ });
89149
+ const workerFiber = yield* exports_Effect.forkDaemon(runWorkerLoop(opts.def.name, buffer, opts.def.itemKey, opts.onItem));
89150
+ return {
89151
+ buffer,
89152
+ stop: () => exports_Effect.gen(function* () {
89153
+ subscribeHandle.stop();
89154
+ yield* exports_Fiber.interrupt(workerFiber);
89155
+ })
89156
+ };
89157
+ }), runReconcilePoll = (opts) => exports_Effect.gen(function* () {
89158
+ const clock4 = yield* IntervalClock;
89159
+ const backoffCfg = opts.backoff ?? { initialMs: 1000, maxMs: 30000 };
89160
+ let backoffMs = opts.intervalMs;
89161
+ let stopped = false;
89162
+ const loopFiber = yield* exports_Effect.forkDaemon(exports_Effect.gen(function* () {
89163
+ while (!stopped) {
89164
+ const pollOutcome = yield* exports_Effect.either(exports_Effect.tryPromise(() => opts.poll(opts.args)));
89165
+ if (pollOutcome._tag === "Left") {
89166
+ backoffMs = Math.min(backoffMs === opts.intervalMs ? backoffCfg.initialMs : backoffMs * 2, backoffCfg.maxMs);
89167
+ yield* clock4.sleep(backoffMs);
89168
+ continue;
89169
+ }
89170
+ yield* opts.onResult(pollOutcome.right);
89171
+ backoffMs = opts.intervalMs;
89172
+ yield* clock4.sleep(opts.intervalMs);
89173
+ }
89174
+ }));
89175
+ return {
89176
+ stop: () => exports_Effect.gen(function* () {
89177
+ stopped = true;
89178
+ yield* exports_Fiber.interrupt(loopFiber);
89179
+ })
89180
+ };
89181
+ }), runReconcilePollLive = (opts) => runReconcilePoll(opts).pipe(exports_Effect.provide(IntervalClockLive));
89182
+ var init_feed_runtime = __esm(() => {
89183
+ init_esm();
89184
+ init_layers();
89185
+ init_subscribe_loop();
89186
+ });
89187
+
89188
+ // src/infrastructure/incremental-sync/dual-channel-feed.ts
89189
+ var runDualChannelFeedLive = (opts) => exports_Effect.gen(function* () {
89190
+ const initial = yield* exports_Effect.tryPromise(() => opts.fetchReconcile()).pipe(exports_Effect.orElseSucceed(() => null));
89191
+ if (initial !== null) {
89192
+ const rows = opts.extractReconcileRows(initial);
89193
+ opts.snapshot.replaceAll(rows);
89194
+ if (rows.length > 0) {
89195
+ yield* opts.onReconcileRows(rows).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89196
+ }
89197
+ }
89198
+ const seedKey = yield* exports_Effect.tryPromise(() => opts.seedCursor()).pipe(exports_Effect.orElseSucceed(() => null));
89199
+ const hydrateFromReconcile = async () => {
89200
+ const result = await opts.fetchReconcile();
89201
+ opts.snapshot.replaceAll(opts.extractReconcileRows(result));
89202
+ };
89203
+ const resolveRowForSignal = async (signal) => {
89204
+ const merged = opts.snapshot.mergeSignal(signal);
89205
+ if (merged) {
89206
+ return merged;
89207
+ }
89208
+ await hydrateFromReconcile();
89209
+ return opts.snapshot.mergeSignal(signal) ?? opts.snapshot.getBySignal(signal);
89210
+ };
89211
+ const signalHandle = yield* runIncrementalSubscribeLive({
89212
+ wsClient: opts.wsClient,
89213
+ def: opts.def,
89214
+ target: opts.target,
89215
+ args: opts.args,
89216
+ buffer: opts.buffer,
89217
+ subscribe: opts.subscribe,
89218
+ initialAfterKey: seedKey,
89219
+ onError: opts.onSubscribeError,
89220
+ onItem: ({ item: signal, ack }) => exports_Effect.gen(function* () {
89221
+ ack();
89222
+ if (opts.isStopped()) {
89223
+ return;
89224
+ }
89225
+ const row = yield* exports_Effect.tryPromise(() => resolveRowForSignal(signal));
89226
+ if (!row) {
89227
+ return;
89228
+ }
89229
+ yield* opts.onSignalRow(row).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89230
+ })
89231
+ });
89232
+ const reconcileHandle = yield* runReconcilePollLive({
89233
+ name: `${opts.name}-reconcile`,
89234
+ poll: () => opts.fetchReconcile(),
89235
+ args: undefined,
89236
+ intervalMs: opts.reconcileIntervalMs,
89237
+ onResult: (result) => exports_Effect.gen(function* () {
89238
+ const rows = opts.extractReconcileRows(result);
89239
+ opts.snapshot.replaceAll(rows);
89240
+ yield* opts.onReconcileRows(rows).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89241
+ })
89242
+ });
89243
+ return {
89244
+ stop: () => exports_Effect.gen(function* () {
89245
+ yield* signalHandle.stop();
89246
+ yield* reconcileHandle.stop();
89247
+ })
89248
+ };
89249
+ });
89250
+ var init_dual_channel_feed = __esm(() => {
89251
+ init_esm();
89252
+ init_feed_runtime();
89253
+ });
89254
+
89255
+ // src/infrastructure/incremental-sync/feeds/assigned-task-signals.ts
89256
+ var assignedTaskSignalsFeedDef, assignedTaskSignalsSubscribeTarget, ASSIGNED_TASK_SIGNAL_FEED_LIMIT = 50, ASSIGNED_TASK_SIGNAL_FEED_BUFFER, ASSIGNED_TASK_RECONCILE_INTERVAL_MS = 15000;
89257
+ var init_assigned_task_signals = __esm(() => {
89258
+ init_api3();
89259
+ assignedTaskSignalsFeedDef = {
89260
+ name: "assigned-task-signals",
89261
+ itemKey: (item) => item.revisionKey
89262
+ };
89263
+ assignedTaskSignalsSubscribeTarget = {
89264
+ query: api.machines.subscribeAssignedTaskSignalsSince,
89265
+ buildArgs: (args2, afterKey, limit) => ({
89266
+ sessionId: args2.sessionId,
89267
+ machineId: args2.machineId,
89268
+ afterKey: afterKey ?? undefined,
89269
+ limit
89270
+ }),
89271
+ parsePage: (result) => result
89272
+ };
89273
+ ASSIGNED_TASK_SIGNAL_FEED_BUFFER = {
89274
+ maxSize: 200,
89275
+ dedupe: true
89276
+ };
89277
+ });
89278
+
87962
89279
  // src/commands/machine/daemon-start/task-monitor.ts
87963
89280
  function resolveTaskWantResume(task) {
87964
89281
  return compressContextToWantResume(parseCompressContext(task.taskContent ?? ""));
@@ -87971,7 +89288,7 @@ function buildCliNudgeLogLine(task) {
87971
89288
  const wantResume = resolveTaskWantResume(task);
87972
89289
  return `[TaskMonitor] nudging ${role}@${chatroomId} — pending task ${task.taskId}, lastSeenAction=${lastSeenAction}, compress_context=${compressMode}, wantResume=${wantResume}`;
87973
89290
  }
87974
- function resolveTaskRunnerContext(task) {
89291
+ function resolveTaskRunnerContextFromFull(task) {
87975
89292
  const { chatroomId, agentConfig } = task;
87976
89293
  const { role } = agentConfig;
87977
89294
  const workingDir = agentConfig.workingDir;
@@ -87986,7 +89303,7 @@ function resolveTaskRunnerContext(task) {
87986
89303
  };
87987
89304
  }
87988
89305
  function executeCliNudge(task, runtime4, effectContext2, agentMgr) {
87989
- const ctx = resolveTaskRunnerContext(task);
89306
+ const ctx = resolveTaskRunnerContextFromFull(task);
87990
89307
  if (!ctx)
87991
89308
  return;
87992
89309
  const { chatroomId, agentConfig, role, workingDir, wantResume } = ctx;
@@ -88004,7 +89321,7 @@ function executeCliNudge(task, runtime4, effectContext2, agentMgr) {
88004
89321
  }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[TaskMonitor] nudge failed for ${role}@${chatroomId}: ${getErrorMessage(err)}`)))));
88005
89322
  }
88006
89323
  function runNativeReviveEffect(task, runtime4, effectContext2, agentMgr) {
88007
- const ctx = resolveTaskRunnerContext(task);
89324
+ const ctx = resolveTaskRunnerContextFromFull(task);
88008
89325
  if (!ctx)
88009
89326
  return;
88010
89327
  const { chatroomId, agentConfig, role, workingDir, wantResume } = ctx;
@@ -88021,63 +89338,124 @@ function runNativeReviveEffect(task, runtime4, effectContext2, agentMgr) {
88021
89338
  });
88022
89339
  }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[TaskMonitor] native revive failed for ${role}@${chatroomId}: ${getErrorMessage(err)}`)))));
88023
89340
  }
89341
+ async function fetchTaskForAction(sessionDeps, machineId, snapshotRow) {
89342
+ const result = await sessionDeps.backend.query(api.machines.getAssignedTaskForAction, {
89343
+ sessionId: sessionDeps.sessionId,
89344
+ machineId,
89345
+ taskId: snapshotRow.taskId,
89346
+ role: snapshotRow.agentConfig.role
89347
+ });
89348
+ return result;
89349
+ }
88024
89350
  function runCliNudgeEffect(task, runtime4, effectContext2, agentMgr) {
88025
89351
  console.log(buildCliNudgeLogLine(task));
88026
89352
  executeCliNudge(task, runtime4, effectContext2, agentMgr);
88027
89353
  }
88028
- function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr) {
88029
- for (const task of listTasksReadyForNudge(tasks, now, cooldown)) {
88030
- runCliNudgeEffect(task, runtime4, effectContext2, agentMgr);
89354
+ async function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
89355
+ for (const row of listTasksReadyForNudge(tasks, now, cooldown)) {
89356
+ const full = await fetchTaskForAction(sessionDeps, machineId, row);
89357
+ if (!full)
89358
+ continue;
89359
+ runCliNudgeEffect(full, runtime4, effectContext2, agentMgr);
88031
89360
  }
88032
89361
  }
88033
- function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr) {
88034
- for (const task of listNativeTasksNeedingRevive(tasks, localHealth, now, cooldown)) {
88035
- runNativeReviveEffect(task, runtime4, effectContext2, agentMgr);
89362
+ async function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
89363
+ for (const row of listNativeTasksNeedingRevive(tasks, localHealth, now, cooldown)) {
89364
+ const full = await fetchTaskForAction(sessionDeps, machineId, row);
89365
+ if (!full)
89366
+ continue;
89367
+ runNativeReviveEffect(full, runtime4, effectContext2, agentMgr);
88036
89368
  }
88037
89369
  }
88038
- function processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps) {
89370
+ async function processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps, machineId, pass) {
89371
+ if (tasks.length === 0)
89372
+ return;
88039
89373
  const now = Date.now();
88040
89374
  const localHealth = {
88041
89375
  getSlot: (chatroomId, role) => agentMgr.getSlot(chatroomId, role),
88042
89376
  isPidAlive: (pid) => isProcessAlive((p) => process.kill(p, 0), pid)
88043
89377
  };
88044
- reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr);
89378
+ await reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
88045
89379
  getNativeTaskDeliveryCoordinator().reconcileAssignedTasks({
88046
89380
  tasks,
88047
89381
  runtime: runtime4,
88048
89382
  effectContext: effectContext2,
88049
89383
  agentMgr,
88050
- sessionDeps
89384
+ sessionDeps,
89385
+ machineId
88051
89386
  });
88052
- nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr);
89387
+ if (pass === "reconcile") {
89388
+ await nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
89389
+ }
88053
89390
  }
88054
- var startTaskMonitorSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
89391
+ var startTaskMonitorEffect = (wsClient2) => exports_Effect.gen(function* () {
88055
89392
  const session2 = yield* DaemonSessionService;
88056
89393
  const agentMgr = yield* DaemonAgentProcessManagerService;
88057
89394
  const effectContext2 = yield* exports_Effect.context();
88058
89395
  const runtime4 = yield* exports_Effect.runtime();
88059
- console.log(`[${formatTimestamp()}] \uD83D\uDCCB Starting task-monitor subscription (reactive)`);
89396
+ console.log(`[${formatTimestamp()}] \uD83D\uDCCB Starting task-monitor (incremental subscribe)`);
88060
89397
  const cooldown = new NudgeCooldown;
89398
+ const snapshot = createTaskMonitorSnapshot();
88061
89399
  let stopped = false;
89400
+ let monitorPassInFlight = false;
88062
89401
  const sessionDeps = {
88063
89402
  sessionId: session2.sessionId,
88064
89403
  convexUrl: session2.convexUrl,
89404
+ machineId: session2.machineId,
88065
89405
  backend: {
88066
89406
  mutation: (fn2, args2) => session2.backend.mutation(fn2, args2),
88067
89407
  query: (fn2, args2) => session2.backend.query(fn2, args2)
88068
89408
  }
88069
89409
  };
88070
- const onTasksUpdate = (result) => {
88071
- if (stopped || !result?.tasks?.length)
89410
+ const runMonitorPass = (tasks, pass) => {
89411
+ if (stopped || monitorPassInFlight || tasks.length === 0)
88072
89412
  return;
88073
- processTasksUpdate(result.tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps);
89413
+ monitorPassInFlight = true;
89414
+ processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps, session2.machineId, pass).finally(() => {
89415
+ monitorPassInFlight = false;
89416
+ });
88074
89417
  };
88075
- const unsubscribe = wsClient2.onUpdate(api.machines.getAssignedTasks, { sessionId: session2.sessionId, machineId: session2.machineId }, onTasksUpdate, (err) => console.warn(`[${formatTimestamp()}] Task-monitor subscription error: ${getErrorMessage(err)}`));
89418
+ const queryReconcileSnapshots = () => session2.backend.query(api.machines.listAssignedTasksForReconcile, {
89419
+ sessionId: session2.sessionId,
89420
+ machineId: session2.machineId
89421
+ });
89422
+ const feedHandle = yield* runDualChannelFeedLive({
89423
+ name: "assigned-tasks",
89424
+ wsClient: wsClient2,
89425
+ def: assignedTaskSignalsFeedDef,
89426
+ target: assignedTaskSignalsSubscribeTarget,
89427
+ args: {
89428
+ sessionId: session2.sessionId,
89429
+ machineId: session2.machineId
89430
+ },
89431
+ buffer: ASSIGNED_TASK_SIGNAL_FEED_BUFFER,
89432
+ subscribe: { limit: ASSIGNED_TASK_SIGNAL_FEED_LIMIT },
89433
+ snapshot,
89434
+ seedCursor: async () => {
89435
+ const seedPage = await session2.backend.query(api.machines.subscribeAssignedTaskSignalsSince, {
89436
+ sessionId: session2.sessionId,
89437
+ machineId: session2.machineId,
89438
+ limit: ASSIGNED_TASK_SIGNAL_FEED_LIMIT
89439
+ });
89440
+ return seedPage?.highKey ?? null;
89441
+ },
89442
+ fetchReconcile: () => queryReconcileSnapshots(),
89443
+ extractReconcileRows: (result) => result?.tasks ?? [],
89444
+ reconcileIntervalMs: ASSIGNED_TASK_RECONCILE_INTERVAL_MS,
89445
+ isStopped: () => stopped,
89446
+ onSubscribeError: (err) => console.warn(`[${formatTimestamp()}] ⚠️ Task signal subscription error: ${getErrorMessage(err)}`),
89447
+ onSignalRow: (row) => exports_Effect.sync(() => {
89448
+ runMonitorPass([row], "signal");
89449
+ }),
89450
+ onReconcileRows: (tasks) => exports_Effect.sync(() => {
89451
+ runMonitorPass([...tasks], "reconcile");
89452
+ })
89453
+ });
88076
89454
  return {
88077
89455
  stop() {
88078
89456
  stopped = true;
88079
- unsubscribe();
88080
- console.log(`[${formatTimestamp()}] \uD83D\uDCCB Task-monitor subscription stopped`);
89457
+ exports_Effect.runPromise(feedHandle.stop());
89458
+ console.log(`[${formatTimestamp()}] \uD83D\uDCCB Task-monitor stopped`);
88081
89459
  }
88082
89460
  };
88083
89461
  });
@@ -88087,7 +89465,10 @@ var init_task_monitor = __esm(() => {
88087
89465
  init_daemon_services();
88088
89466
  init_native_task_delivery_coordinator();
88089
89467
  init_task_monitor_logic();
89468
+ init_task_monitor_snapshot();
88090
89469
  init_api3();
89470
+ init_dual_channel_feed();
89471
+ init_assigned_task_signals();
88091
89472
  init_convex_error();
88092
89473
  });
88093
89474
 
@@ -88290,6 +89671,11 @@ function handleRefreshCapabilitiesEffect(event, tracker) {
88290
89671
  })));
88291
89672
  });
88292
89673
  }
89674
+ function forkDaemonSyncBatch(runtime4, effectContext2, failureLabel) {
89675
+ exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
89676
+ concurrency: "unbounded"
89677
+ }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[${formatTimestamp()}] ⚠️ ${failureLabel}: ${getErrorMessage(err)}`)))));
89678
+ }
88293
89679
  var GIT_PUSH_ACTIONS, commandEventHandlers, dispatchCommandEventEffect = (event, tracker) => {
88294
89680
  const factory2 = commandEventHandlers[event.type];
88295
89681
  return factory2 != null ? factory2(event, tracker) : exports_Effect.void;
@@ -88348,9 +89734,7 @@ var init_command_loop = __esm(() => {
88348
89734
  heartbeatCount++;
88349
89735
  console.log(`[${formatTimestamp()}] \uD83D\uDC93 Daemon heartbeat #${heartbeatCount} OK`);
88350
89736
  if (!observedSyncEnabled) {
88351
- exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
88352
- concurrency: "unbounded"
88353
- }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[${formatTimestamp()}] ⚠️ Heartbeat sync failed: ${getErrorMessage(err)}`)))));
89737
+ forkDaemonSyncBatch(runtime4, effectContext2, "Heartbeat sync failed");
88354
89738
  }
88355
89739
  }).catch((err) => {
88356
89740
  console.warn(`[${formatTimestamp()}] ⚠️ Daemon heartbeat failed: ${getErrorMessage(err)}`);
@@ -88367,14 +89751,13 @@ var init_command_loop = __esm(() => {
88367
89751
  let pendingHarnessSessionSubscriptionHandle = null;
88368
89752
  let commandSubscriptionHandle = null;
88369
89753
  let lifecycleManager = null;
89754
+ let closeDirectHarnessSessionsOnShutdown = null;
88370
89755
  const activeSessions = new Map;
88371
89756
  const harnesses = new Map;
88372
89757
  if (observedSyncEnabled) {
88373
89758
  console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Observed-sync enabled, skipping immediate push`);
88374
89759
  } else {
88375
- exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
88376
- concurrency: "unbounded"
88377
- }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[${formatTimestamp()}] ⚠️ Startup sync failed: ${getErrorMessage(err)}`)))));
89760
+ forkDaemonSyncBatch(runtime4, effectContext2, "Startup sync failed");
88378
89761
  }
88379
89762
  const PROCESS_KILL_TIMEOUT_MS = 6000;
88380
89763
  const CLOSE_TIMEOUT_MS = 3000;
@@ -88442,8 +89825,12 @@ var init_command_loop = __esm(() => {
88442
89825
  await withTimeout2(exports_Effect.runPromise(onDaemonShutdownEffect.pipe(exports_Effect.provide(effectContext2))), PROCESS_KILL_TIMEOUT_MS);
88443
89826
  };
88444
89827
  const closeAllSessionsAndHarnesses = async () => {
88445
- for (const handle of activeSessions.values()) {
88446
- await withTimeout2(handle.close(), CLOSE_TIMEOUT_MS);
89828
+ if (closeDirectHarnessSessionsOnShutdown) {
89829
+ await withTimeout2(closeDirectHarnessSessionsOnShutdown(), PROCESS_KILL_TIMEOUT_MS);
89830
+ } else {
89831
+ for (const handle of activeSessions.values()) {
89832
+ await withTimeout2(handle.close(), CLOSE_TIMEOUT_MS);
89833
+ }
88447
89834
  }
88448
89835
  for (const harness of harnesses.values()) {
88449
89836
  await withTimeout2(harness.close(), CLOSE_TIMEOUT_MS);
@@ -88473,7 +89860,7 @@ var init_command_loop = __esm(() => {
88473
89860
  if (observedSyncEnabled) {
88474
89861
  observedSyncSubscriptionHandle = yield* startObservedSyncSubscriptionEffect(wsClient2);
88475
89862
  }
88476
- const taskMonitorHandle = yield* startTaskMonitorSubscriptionEffect(wsClient2);
89863
+ const taskMonitorHandle = yield* startTaskMonitorEffect(wsClient2);
88477
89864
  logObserverSubscriptionHandle = startLogObserverSubscription({ sessionId: session2.sessionId, machineId: session2.machineId }, wsClient2);
88478
89865
  if (featureFlags.directHarnessWorkers) {
88479
89866
  const handles = startDirectHarnessSubscriptions({
@@ -88486,6 +89873,7 @@ var init_command_loop = __esm(() => {
88486
89873
  pendingHarnessSessionSubscriptionHandle = handles.pendingHarnessSessionSubscriptionHandle;
88487
89874
  commandSubscriptionHandle = handles.commandSubscriptionHandle;
88488
89875
  lifecycleManager = handles.lifecycleManager;
89876
+ closeDirectHarnessSessionsOnShutdown = handles.closeSessionsOnShutdown;
88489
89877
  }
88490
89878
  console.log(`
88491
89879
  Listening for commands...`);
@@ -88672,8 +90060,8 @@ import * as os2 from "os";
88672
90060
  import * as path4 from "path";
88673
90061
  async function isChatroomInstalledDefault() {
88674
90062
  try {
88675
- const { execSync: execSync2 } = await import("child_process");
88676
- execSync2("chatroom --version", { stdio: "pipe" });
90063
+ const { execSync: execSync3 } = await import("child_process");
90064
+ execSync3("chatroom --version", { stdio: "pipe" });
88677
90065
  return true;
88678
90066
  } catch {
88679
90067
  return false;
@@ -89587,4 +90975,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
89587
90975
  });
89588
90976
  program2.parse();
89589
90977
 
89590
- //# debugId=4A0FE03BEB3CACE864756E2164756E21
90978
+ //# debugId=00A3989CD8BAD93D64756E2164756E21