chatroom-cli 1.57.2 → 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)."
@@ -76191,7 +76208,7 @@ var claudeCapabilities;
76191
76208
  var init_claude_config = __esm(() => {
76192
76209
  claudeCapabilities = {
76193
76210
  runtimeKind: "cli",
76194
- supportsSessionResume: false,
76211
+ supportsDaemonMemoryResume: false,
76195
76212
  supportsNativeIntegration: false,
76196
76213
  lifecycle: {
76197
76214
  turnCompleted: false,
@@ -76207,7 +76224,7 @@ var commandcodeCapabilities;
76207
76224
  var init_commandcode_config = __esm(() => {
76208
76225
  commandcodeCapabilities = {
76209
76226
  runtimeKind: "cli",
76210
- supportsSessionResume: false,
76227
+ supportsDaemonMemoryResume: false,
76211
76228
  supportsNativeIntegration: false,
76212
76229
  lifecycle: {
76213
76230
  turnCompleted: true,
@@ -76223,7 +76240,7 @@ var copilotCapabilities;
76223
76240
  var init_copilot_config = __esm(() => {
76224
76241
  copilotCapabilities = {
76225
76242
  runtimeKind: "cli",
76226
- supportsSessionResume: false,
76243
+ supportsDaemonMemoryResume: false,
76227
76244
  supportsNativeIntegration: false,
76228
76245
  lifecycle: {
76229
76246
  turnCompleted: true,
@@ -76239,7 +76256,7 @@ var cursorSdkCapabilities;
76239
76256
  var init_cursor_sdk_config = __esm(() => {
76240
76257
  cursorSdkCapabilities = {
76241
76258
  runtimeKind: "sdk",
76242
- supportsSessionResume: false,
76259
+ supportsDaemonMemoryResume: true,
76243
76260
  supportsNativeIntegration: true,
76244
76261
  lifecycle: {
76245
76262
  turnCompleted: true,
@@ -76255,7 +76272,7 @@ var cursorCapabilities;
76255
76272
  var init_cursor_config = __esm(() => {
76256
76273
  cursorCapabilities = {
76257
76274
  runtimeKind: "cli",
76258
- supportsSessionResume: false,
76275
+ supportsDaemonMemoryResume: false,
76259
76276
  supportsNativeIntegration: false,
76260
76277
  lifecycle: {
76261
76278
  turnCompleted: true,
@@ -76271,7 +76288,7 @@ var opencodeSdkCapabilities;
76271
76288
  var init_opencode_sdk_config = __esm(() => {
76272
76289
  opencodeSdkCapabilities = {
76273
76290
  runtimeKind: "sdk",
76274
- supportsSessionResume: false,
76291
+ supportsDaemonMemoryResume: true,
76275
76292
  supportsNativeIntegration: true,
76276
76293
  lifecycle: {
76277
76294
  turnCompleted: true,
@@ -76287,7 +76304,7 @@ var opencodeCapabilities;
76287
76304
  var init_opencode_config = __esm(() => {
76288
76305
  opencodeCapabilities = {
76289
76306
  runtimeKind: "cli",
76290
- supportsSessionResume: false,
76307
+ supportsDaemonMemoryResume: false,
76291
76308
  supportsNativeIntegration: false,
76292
76309
  lifecycle: {
76293
76310
  turnCompleted: false,
@@ -76303,7 +76320,7 @@ var piSdkCapabilities;
76303
76320
  var init_pi_sdk_config = __esm(() => {
76304
76321
  piSdkCapabilities = {
76305
76322
  runtimeKind: "sdk",
76306
- supportsSessionResume: false,
76323
+ supportsDaemonMemoryResume: false,
76307
76324
  supportsNativeIntegration: true,
76308
76325
  lifecycle: {
76309
76326
  turnCompleted: true,
@@ -76319,7 +76336,7 @@ var piCapabilities;
76319
76336
  var init_pi_config = __esm(() => {
76320
76337
  piCapabilities = {
76321
76338
  runtimeKind: "cli",
76322
- supportsSessionResume: false,
76339
+ supportsDaemonMemoryResume: false,
76323
76340
  supportsNativeIntegration: false,
76324
76341
  lifecycle: {
76325
76342
  turnCompleted: true,
@@ -76372,43 +76389,6 @@ var init_types = __esm(() => {
76372
76389
  });
76373
76390
  // ../../services/backend/prompts/review-guidelines/index.ts
76374
76391
  var init_review_guidelines = () => {};
76375
- // ../../services/backend/prompts/cli/resume-session/message.ts
76376
- function composeResumeMessage(params) {
76377
- const { chatroomId, role, convexUrl, supportsNativeIntegration } = params;
76378
- const cliEnvPrefix = getCliEnvPrefix(convexUrl);
76379
- const contextRead = `${cliEnvPrefix}chatroom context read --chatroom-id="${chatroomId}" --role="${role}"`;
76380
- if (supportsNativeIntegration) {
76381
- return [
76382
- "Your previous turn has ended. Your session stays active.",
76383
- "The next chatroom task will be injected automatically when ready.",
76384
- "",
76385
- "If you need context on what you were doing, run:",
76386
- ` ${contextRead}`
76387
- ].join(`
76388
- `);
76389
- }
76390
- const getNextTask2 = getNextTaskCommand({
76391
- chatroomId,
76392
- role,
76393
- cliEnvPrefix
76394
- });
76395
- return [
76396
- "Your previous turn has ended. A pending chatroom task may already be waiting.",
76397
- "You MUST immediately run this command as a foreground blocking bash tool call (not in background):",
76398
- ` ${getNextTask2}`,
76399
- "",
76400
- "Do not reply with text only — run the command first. The harness delivers tasks only while get-next-task blocks.",
76401
- "",
76402
- "If you need context on what you were doing, run:",
76403
- ` ${contextRead}`
76404
- ].join(`
76405
- `);
76406
- }
76407
- var init_message = __esm(() => {
76408
- init_env();
76409
- init_command();
76410
- });
76411
-
76412
76392
  // ../../services/backend/prompts/generator.ts
76413
76393
  function generateHandoffOutput(params) {
76414
76394
  const { role, nextRole, chatroomId, convexUrl, supportsNativeIntegration } = params;
@@ -76431,7 +76411,6 @@ var init_generator = __esm(() => {
76431
76411
  init_command();
76432
76412
  init_reminder();
76433
76413
  init_fromContext();
76434
- init_handoff_templates_preview();
76435
76414
  init_classification_guide();
76436
76415
  init_commands_reference();
76437
76416
  init_current_classification();
@@ -76447,7 +76426,6 @@ var init_generator = __esm(() => {
76447
76426
  init_utils3();
76448
76427
  init_types();
76449
76428
  init_review_guidelines();
76450
- init_message();
76451
76429
  });
76452
76430
 
76453
76431
  // src/commands/handoff/index.ts
@@ -77439,6 +77417,52 @@ function readFileContent(filePath, optionName) {
77439
77417
  }
77440
77418
  var init_file_content = () => {};
77441
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
+
77442
77466
  // src/commands/task/read/render.ts
77443
77467
  function detectBacklogDivergence(contextContent, attachedIds) {
77444
77468
  if (!contextContent || attachedIds.length === 0) {
@@ -77447,19 +77471,10 @@ function detectBacklogDivergence(contextContent, attachedIds) {
77447
77471
  return attachedIds.filter((id3) => !contextContent.includes(id3));
77448
77472
  }
77449
77473
  function renderAttachments(input, chatroomId, role) {
77450
- if (!input.attachedBacklogItems || input.attachedBacklogItems.length === 0)
77451
- return [];
77452
- const lines = [""];
77453
- lines.push("<attachments>");
77454
- for (const item of input.attachedBacklogItems) {
77455
- lines.push(` <attachment type="backlog-item">`);
77456
- lines.push(` - [${item.status.toUpperCase()}] ${item.content}`);
77457
- lines.push(` ID: ${item._id}`);
77458
- 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>`);
77459
- lines.push(` </attachment>`);
77460
- }
77461
- lines.push("</attachments>");
77462
- return lines;
77474
+ return renderDeliveryAttachmentsBlock({
77475
+ attachedBacklogItems: input.attachedBacklogItems,
77476
+ attachedSnippets: input.attachedSnippets
77477
+ }, { chatroomId, role, mode: "task-read" });
77463
77478
  }
77464
77479
  function renderDivergenceWarnings(input) {
77465
77480
  const ctx = input.context;
@@ -77514,6 +77529,9 @@ ${content}`);
77514
77529
  return lines.join(`
77515
77530
  `);
77516
77531
  }
77532
+ var init_render = __esm(() => {
77533
+ init_render_delivery_attachments();
77534
+ });
77517
77535
 
77518
77536
  // src/commands/task/read/index.ts
77519
77537
  var exports_read = {};
@@ -77541,6 +77559,18 @@ function validateTaskId(taskId) {
77541
77559
  }
77542
77560
  return exports_Effect.void;
77543
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
+ }
77544
77574
  function handleTaskReadError(err) {
77545
77575
  return exports_Effect.sync(() => {
77546
77576
  const handler = taskReadErrorHandlers[err._tag];
@@ -77573,19 +77603,12 @@ var taskReadEffect = (chatroomId, options) => exports_Effect.gen(function* () {
77573
77603
  taskId
77574
77604
  }).pipe(exports_Effect.mapError((cause3) => ({ _tag: "MutationFailed", cause: cause3 })));
77575
77605
  yield* exports_Effect.sync(() => {
77576
- console.log(renderTaskPrompt({
77577
- taskId: result.taskId,
77578
- status: result.status,
77579
- content: result.content,
77580
- chatroomId,
77581
- role,
77582
- context: result.context && typeof result.context === "object" ? result.context : undefined,
77583
- attachedBacklogItems: Array.isArray(result.attachedBacklogItems) ? result.attachedBacklogItems : undefined
77584
- }));
77606
+ console.log(buildTaskPromptFromReadResult(result, chatroomId, role));
77585
77607
  });
77586
77608
  }), taskReadErrorHandlers;
77587
77609
  var init_read = __esm(() => {
77588
77610
  init_esm();
77611
+ init_render();
77589
77612
  init_api3();
77590
77613
  init_storage();
77591
77614
  init_client2();
@@ -82717,161 +82740,311 @@ var init_commit_detail_sync = __esm(() => {
82717
82740
  seenShas = new Map;
82718
82741
  });
82719
82742
 
82720
- // src/domain/direct-harness/usecases/update-capabilities.ts
82721
- async function updateCapabilities(deps, input) {
82722
- const { publisher, machineId } = deps;
82723
- const { harness, workspace } = input;
82724
- const [agents, providers] = await Promise.all([
82725
- harness.listAgents(),
82726
- harness.listProviders()
82727
- ]);
82728
- await publisher.publish({
82729
- machineId,
82730
- lastSeenAt: Date.now(),
82731
- workspaces: [
82732
- {
82733
- workspaceId: workspace.workspaceId,
82734
- cwd: workspace.cwd,
82735
- name: workspace.name,
82736
- harnesses: [
82737
- {
82738
- name: harness.type,
82739
- displayName: harness.displayName,
82740
- agents: [...agents],
82741
- providers: [...providers]
82742
- }
82743
- ]
82744
- }
82745
- ]
82746
- });
82747
- }
82743
+ // src/infrastructure/harnesses/cursor-sdk/cursor-session.ts
82744
+ import { randomUUID as randomUUID3 } from "node:crypto";
82748
82745
 
82749
- // src/commands/machine/daemon-start/direct-harness/command-subscriber.ts
82750
- function startCommandSubscriber(session2, wsClient2, deps) {
82751
- const processed = new Set;
82752
- let processing = false;
82753
- let pendingDrain = false;
82754
- const runDrain = () => {
82755
- if (processing) {
82756
- 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)
82757
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);
82758
82811
  }
82759
- processing = true;
82760
- pendingDrain = false;
82761
- drain(session2, deps, processed).finally(() => {
82762
- processing = false;
82763
- if (pendingDrain)
82764
- 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()
82765
82820
  });
82766
- };
82767
- const unsub = wsClient2.onUpdate(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId }, () => runDrain(), (err) => {
82768
- console.warn("[direct-harness] Command subscription error:", err instanceof Error ? err.message : String(err));
82769
- });
82770
- return { stop: unsub };
82771
- }
82772
- async function dispatchPendingCommand(session2, deps, cmd) {
82773
- switch (cmd.type) {
82774
- case "refreshCapabilities":
82775
- await handleRefreshCapabilities(session2, deps, cmd);
82776
- break;
82777
- case "refreshSessionTitle":
82778
- await handleRefreshSessionTitle(session2, deps, cmd);
82779
- break;
82780
- default:
82781
- await markFailed(session2, cmd._id, `Unknown command type: ${cmd.type}`);
82782
82821
  }
82783
- }
82784
- async function processPendingCommand(session2, deps, cmd, now) {
82785
- if (now - cmd.createdAt > DIRECT_HARNESS_COMMAND_TTL_MS) {
82786
- console.log(`[direct-harness] Discarding stale command ${cmd._id} (type=${cmd.type}, age=${now - cmd.createdAt}ms)`);
82787
- await markFailed(session2, cmd._id, "Command expired (TTL)");
82788
- 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
+ }
82789
82839
  }
82790
- await dispatchPendingCommand(session2, deps, cmd);
82791
82840
  }
82792
- async function drain(session2, deps, processed) {
82793
- const pending3 = await session2.backend.query(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId });
82794
- if (!pending3 || pending3.length === 0)
82795
- return;
82796
- const now = Date.now();
82797
- for (const cmd of pending3) {
82798
- if (processed.has(cmd._id))
82799
- continue;
82800
- processed.add(cmd._id);
82801
- try {
82802
- await processPendingCommand(session2, deps, cmd, now);
82803
- } catch (err) {
82804
- const message = err instanceof Error ? err.message : String(err);
82805
- console.warn(`[direct-harness] Command ${cmd._id} failed: ${message}`);
82806
- await markFailed(session2, cmd._id, message).catch(() => {});
82807
- }
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;
82808
82861
  }
82809
82862
  }
82810
- async function handleRefreshCapabilities(session2, deps, cmd) {
82811
- const payload = cmd.refreshCapabilities;
82812
- console.log(`[direct-harness] Processing refreshCapabilities for workspace=${cmd.workspaceId}` + (payload ? ` (initiatedBy=${payload.initiatedBy})` : ""));
82813
- const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId);
82814
- await updateCapabilities({ publisher: deps.publisher, machineId: session2.machineId }, {
82815
- harness,
82816
- workspace: {
82817
- workspaceId: cmd.workspaceId,
82818
- cwd: harness.cwd,
82819
- 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
+ }
82820
82884
  }
82821
- });
82822
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82823
- sessionId: session2.sessionId,
82824
- commandId: cmd._id,
82825
- status: "done"
82826
- });
82827
- console.log(`[direct-harness] Capabilities refreshed for workspace=${cmd.workspaceId}`);
82828
- }
82829
- async function handleRefreshSessionTitle(session2, deps, cmd) {
82830
- const { harnessSessionId } = cmd.refreshSessionTitle ?? {};
82831
- if (!harnessSessionId) {
82832
- await markFailed(session2, cmd._id, "refreshSessionTitle: missing harnessSessionId");
82833
- return;
82885
+ return models;
82834
82886
  }
82835
- const sessionRow = await session2.backend.query(api.daemon.directHarness.sessions.getSession, {
82836
- harnessSessionId
82837
- });
82838
- if (!sessionRow?.opencodeSessionId) {
82839
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82840
- sessionId: session2.sessionId,
82841
- commandId: cmd._id,
82842
- 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)
82843
82923
  });
82844
- return;
82924
+ this.sessions.set(agent.agentId, session2);
82925
+ return session2;
82845
82926
  }
82846
- const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId);
82847
- const newTitle = await harness.fetchSessionTitle(sessionRow.opencodeSessionId);
82848
- if (newTitle) {
82849
- await session2.backend.mutation(api.daemon.directHarness.sessions.updateSessionTitle, {
82850
- sessionId: session2.sessionId,
82851
- harnessSessionId,
82852
- 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)
82853
82947
  });
82854
- 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();
82855
82964
  }
82856
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82857
- sessionId: session2.sessionId,
82858
- commandId: cmd._id,
82859
- status: "done"
82860
- });
82861
82965
  }
82862
- async function markFailed(session2, commandId, errorMessage) {
82863
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82864
- sessionId: session2.sessionId,
82865
- commandId,
82866
- status: "failed",
82867
- errorMessage
82868
- });
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
+ };
82869
83000
  }
82870
- var DIRECT_HARNESS_COMMAND_TTL_MS = 60000;
82871
- var init_command_subscriber = __esm(() => {
82872
- 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();
82873
83013
  });
82874
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
+
82875
83048
  // src/infrastructure/harnesses/opencode-sdk/sse-event-buffer.ts
82876
83049
  var SseEventBuffer;
82877
83050
  var init_sse_event_buffer = __esm(() => {
@@ -83369,44 +83542,560 @@ var init_opencode_harness = __esm(() => {
83369
83542
  init_spawn_env();
83370
83543
  });
83371
83544
 
83372
- // src/infrastructure/harnesses/opencode-sdk/event-extractor.ts
83373
- function createOpencodeSdkChunkExtractor() {
83374
- const partMap = new Map;
83375
- return function extract(event) {
83376
- if (event.type === "message.part.updated") {
83377
- const payload = event.payload;
83378
- const part = payload?.part;
83379
- if (part?.id && part?.messageID) {
83380
- const partType = part.type === "reasoning" ? "reasoning" : "text";
83381
- partMap.set(part.id, { messageId: part.messageID, partType });
83382
- const delta = payload?.delta;
83383
- if (delta && delta.length > 0) {
83384
- return { content: delta, messageId: part.messageID, partType };
83385
- }
83386
- }
83387
- 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);
83388
83607
  }
83389
- if (event.type === "message.part.delta") {
83390
- const payload = event.payload;
83391
- const delta = payload?.delta;
83392
- if (!delta || delta.length === 0)
83393
- return null;
83394
- const messageId = payload?.messageID;
83395
- if (!messageId)
83396
- return null;
83397
- const partID = payload?.partID;
83398
- const partType = (partID ? partMap.get(partID)?.partType : undefined) ?? "text";
83399
- 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");
83400
83626
  }
83401
- return null;
83402
- };
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];
83403
83657
  }
83404
83658
 
83405
- // src/infrastructure/harnesses/opencode-sdk/index.ts
83406
- var init_opencode_sdk2 = __esm(() => {
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();
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
+ });
83815
+
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();
83407
83886
  init_opencode_harness();
83887
+ init_pi_sdk2();
83408
83888
  });
83409
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();
84086
+ });
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
+
83410
84099
  // src/commands/machine/daemon-start/direct-harness/harness-lifecycle-manager.ts
83411
84100
  class HarnessLifecycleManager {
83412
84101
  harnesses;
@@ -83421,10 +84110,15 @@ class HarnessLifecycleManager {
83421
84110
  this.resolveWorkspace = resolveWorkspace;
83422
84111
  this.resolvedConvexUrl = resolvedConvexUrl;
83423
84112
  }
83424
- getOrStartHarnessEffect(workspaceId) {
83425
- const existing = this.harnesses.get(workspaceId);
83426
- if (existing)
84113
+ getOrStartHarnessEffect(workspaceId, harnessName) {
84114
+ const key = makeHarnessKey(workspaceId, harnessName);
84115
+ const existing = this.harnesses.get(key);
84116
+ if (existing?.isAlive())
83427
84117
  return exports_Effect.succeed(existing);
84118
+ if (existing) {
84119
+ existing.close().catch(() => {});
84120
+ this.harnesses.delete(key);
84121
+ }
83428
84122
  return exports_Effect.gen(this, function* () {
83429
84123
  const workspace = yield* exports_Effect.tryPromise({
83430
84124
  try: () => this.resolveWorkspace(workspaceId),
@@ -83433,23 +84127,23 @@ class HarnessLifecycleManager {
83433
84127
  if (!workspace) {
83434
84128
  return yield* exports_Effect.fail(new Error(`Workspace ${workspaceId} not found — cannot start harness`));
83435
84129
  }
83436
- 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})`);
83437
84131
  const harness = yield* exports_Effect.tryPromise({
83438
- try: () => startOpencodeSdkHarness({
83439
- type: "opencode",
84132
+ try: () => startBoundHarness({
84133
+ harnessName,
83440
84134
  workingDir: workspace.workingDir,
83441
84135
  workspaceId,
83442
84136
  resolvedConvexUrl: this.resolvedConvexUrl
83443
84137
  }),
83444
84138
  catch: (e) => e instanceof Error ? e : new Error(String(e))
83445
84139
  });
83446
- this.harnesses.set(workspaceId, harness);
83447
- this.idleSince.set(workspaceId, Date.now());
84140
+ this.harnesses.set(key, harness);
84141
+ this.idleSince.set(key, Date.now());
83448
84142
  return harness;
83449
84143
  });
83450
84144
  }
83451
- async getOrStart(workspaceId) {
83452
- return exports_Effect.runPromise(this.getOrStartHarnessEffect(workspaceId));
84145
+ async getOrStart(workspaceId, harnessName) {
84146
+ return exports_Effect.runPromise(this.getOrStartHarnessEffect(workspaceId, harnessName));
83453
84147
  }
83454
84148
  startMonitoring() {
83455
84149
  if (this.monitorTimer)
@@ -83465,28 +84159,30 @@ class HarnessLifecycleManager {
83465
84159
  }
83466
84160
  checkInactivity() {
83467
84161
  const now = Date.now();
83468
- for (const workspaceId of this.harnesses.keys()) {
83469
- 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);
83470
84165
  if (hasActiveSessions) {
83471
- this.idleSince.delete(workspaceId);
84166
+ this.idleSince.delete(key);
83472
84167
  } else {
83473
- const idleStart = this.idleSince.get(workspaceId);
84168
+ const idleStart = this.idleSince.get(key);
83474
84169
  if (idleStart === undefined) {
83475
- this.idleSince.set(workspaceId, now);
84170
+ this.idleSince.set(key, now);
83476
84171
  } else if (now - idleStart >= INACTIVITY_TTL_MS) {
83477
84172
  const idleMinutes = Math.round((now - idleStart) / 60000);
83478
- console.log(`[${formatTimestamp()}] \uD83D\uDD2A Killing idle harness for workspace=${workspaceId} (idle ${idleMinutes}min)`);
83479
- this.harnesses.get(workspaceId)?.close().catch(() => {});
83480
- this.harnesses.delete(workspaceId);
83481
- 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);
83482
84177
  }
83483
84178
  }
83484
84179
  }
83485
84180
  }
83486
- hasSessionsForWorkspace(workspaceId) {
84181
+ hasSessionsForHarnessKey(workspaceId, harnessName) {
83487
84182
  for (const handle of this.activeSessions.values()) {
83488
- if (handle.workspaceId === workspaceId)
84183
+ if (handle.workspaceId === workspaceId && handle.harnessName === harnessName) {
83489
84184
  return true;
84185
+ }
83490
84186
  }
83491
84187
  return false;
83492
84188
  }
@@ -83494,7 +84190,7 @@ class HarnessLifecycleManager {
83494
84190
  var INACTIVITY_TTL_MS, CHECK_INTERVAL_MS;
83495
84191
  var init_harness_lifecycle_manager = __esm(() => {
83496
84192
  init_esm();
83497
- init_opencode_sdk2();
84193
+ init_registry4();
83498
84194
  INACTIVITY_TTL_MS = 15 * 60 * 1000;
83499
84195
  CHECK_INTERVAL_MS = 60 * 1000;
83500
84196
  });
@@ -83589,6 +84285,7 @@ async function resumeSession(deps, input) {
83589
84285
  let closed = false;
83590
84286
  const handle = {
83591
84287
  harnessSessionId,
84288
+ harnessName: input.harnessName,
83592
84289
  opencodeSessionId,
83593
84290
  workspaceId: input.workspaceId ?? "",
83594
84291
  session: session2,
@@ -83653,14 +84350,16 @@ async function resumeSessionHandle(session2, deps, rowId, info) {
83653
84350
  return null;
83654
84351
  }
83655
84352
  const workspaceId = info.workspaceId;
84353
+ const harnessName = info.harnessName;
83656
84354
  if (!workspaceId) {
83657
84355
  console.warn(`[direct-harness] Cannot resume session ${rowId}: no workspace info`);
83658
84356
  return null;
83659
84357
  }
83660
- let harness = deps.harnesses.get(workspaceId);
84358
+ const key = makeHarnessKey(workspaceId, harnessName);
84359
+ let harness = deps.harnesses.get(key);
83661
84360
  if (harness && !harness.isAlive()) {
83662
84361
  harness.close().catch(() => {});
83663
- deps.harnesses.delete(workspaceId);
84362
+ deps.harnesses.delete(key);
83664
84363
  harness = undefined;
83665
84364
  }
83666
84365
  if (!harness) {
@@ -83672,20 +84371,20 @@ async function resumeSessionHandle(session2, deps, rowId, info) {
83672
84371
  console.warn(`[direct-harness] Cannot resume session ${rowId}: workspace not found`);
83673
84372
  return null;
83674
84373
  }
83675
- harness = await startOpencodeSdkHarness({
83676
- type: "opencode",
84374
+ harness = await startBoundHarness({
84375
+ harnessName,
83677
84376
  workingDir: workspace.workingDir,
83678
84377
  workspaceId,
83679
84378
  resolvedConvexUrl: session2.convexUrl
83680
84379
  });
83681
- deps.harnesses.set(workspaceId, harness);
84380
+ deps.harnesses.set(key, harness);
83682
84381
  }
83683
84382
  try {
83684
84383
  return await resumeSession({
83685
84384
  harness,
83686
84385
  journalFactory: deps.journalFactory,
83687
- chunkExtractor: createOpencodeSdkChunkExtractor()
83688
- }, { harnessSessionId: rowId, opencodeSessionId, workspaceId });
84386
+ chunkExtractor: createChunkExtractor(harness.type)
84387
+ }, { harnessSessionId: rowId, opencodeSessionId, workspaceId, harnessName });
83689
84388
  } catch (err) {
83690
84389
  const message = err instanceof Error ? err.message : String(err);
83691
84390
  console.warn(`[direct-harness] Cannot resume session ${rowId}: ${message}`);
@@ -83731,8 +84430,9 @@ async function deliverPendingMessages(handle, deps, rowId, messages, info) {
83731
84430
  console.warn(`[direct-harness] Prompt failed for session ${rowId} seq=${msg.seq}: ${message}`);
83732
84431
  await deps.sessionRepository.markIdle(rowId).catch(() => {});
83733
84432
  deps.activeSessions.delete(rowId);
83734
- if (info?.workspaceId)
83735
- deps.harnesses.delete(info.workspaceId);
84433
+ if (info?.workspaceId && info.harnessName) {
84434
+ deps.harnesses.delete(makeHarnessKey(info.workspaceId, info.harnessName));
84435
+ }
83736
84436
  return;
83737
84437
  }
83738
84438
  }
@@ -83759,7 +84459,7 @@ var init_prompt_subscriber = __esm(() => {
83759
84459
  init_idle_handler();
83760
84460
  init_api3();
83761
84461
  init_resume_session();
83762
- init_opencode_sdk2();
84462
+ init_registry4();
83763
84463
  });
83764
84464
 
83765
84465
  // src/commands/machine/daemon-start/direct-harness/session-subscriber.ts
@@ -83784,21 +84484,23 @@ function startSessionSubscriber(daemonSession, wsClient2, deps) {
83784
84484
  return { stop: unsub };
83785
84485
  }
83786
84486
  async function getOrCreateHarness(daemonSession, deps, session2, workspace) {
83787
- 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);
83788
84490
  if (harness && !harness.isAlive()) {
83789
- 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`);
83790
84492
  harness.close().catch(() => {});
83791
- deps.harnesses.delete(session2.workspaceId);
84493
+ deps.harnesses.delete(key);
83792
84494
  harness = undefined;
83793
84495
  }
83794
84496
  if (!harness) {
83795
- harness = await startOpencodeSdkHarness({
83796
- type: "opencode",
84497
+ harness = await startBoundHarness({
84498
+ harnessName,
83797
84499
  workingDir: workspace.workingDir,
83798
84500
  workspaceId: session2.workspaceId,
83799
84501
  resolvedConvexUrl: daemonSession.convexUrl
83800
84502
  });
83801
- deps.harnesses.set(session2.workspaceId, harness);
84503
+ deps.harnesses.set(key, harness);
83802
84504
  }
83803
84505
  return harness;
83804
84506
  }
@@ -83850,13 +84552,17 @@ async function processOne(daemonSession, deps, session2) {
83850
84552
  await deps.sessionRepository.markFailed(rowId);
83851
84553
  return;
83852
84554
  }
84555
+ const harnessName = session2.opencode?.harnessName ?? "opencode-sdk";
83853
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;
83854
84559
  const liveSession = await harness.newSession({
83855
84560
  agent: session2.opencode?.lastUsedConfig.agent ?? "build",
84561
+ model,
83856
84562
  harnessSessionId: rowId
83857
84563
  });
83858
84564
  const journal = deps.journalFactory.create(rowId);
83859
- const extractChunk = createOpencodeSdkChunkExtractor();
84565
+ const extractChunk = createChunkExtractor(harness.type);
83860
84566
  const idleConfig = {
83861
84567
  agent: session2.opencode?.lastUsedConfig.agent ?? "build",
83862
84568
  model: session2.opencode?.lastUsedConfig.model
@@ -83871,9 +84577,11 @@ async function processOne(daemonSession, deps, session2) {
83871
84577
  await journal.commit();
83872
84578
  await liveSession.close();
83873
84579
  await deps.sessionRepository.markClosed(rowId);
84580
+ deps.activeSessions.delete(rowId);
83874
84581
  };
83875
84582
  const handle = {
83876
84583
  harnessSessionId: rowId,
84584
+ harnessName,
83877
84585
  opencodeSessionId: liveSession.opencodeSessionId,
83878
84586
  workspaceId: session2.workspaceId,
83879
84587
  session: liveSession,
@@ -83911,7 +84619,61 @@ async function processOne(daemonSession, deps, session2) {
83911
84619
  var init_session_subscriber = __esm(() => {
83912
84620
  init_idle_handler();
83913
84621
  init_api3();
83914
- 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();
83915
84677
  });
83916
84678
 
83917
84679
  // src/infrastructure/repos/convex-capabilities-publisher.ts
@@ -84202,13 +84964,20 @@ function startDirectHarnessSubscriptions(session2, wsClient2, activeSessions, ha
84202
84964
  publisher: new ConvexCapabilitiesPublisher({
84203
84965
  backend: session2.backend,
84204
84966
  sessionId: session2.sessionId
84205
- })
84967
+ }),
84968
+ activeSessions,
84969
+ sessionRepository
84206
84970
  });
84207
84971
  return {
84208
84972
  pendingPromptSubscriptionHandle,
84209
84973
  pendingHarnessSessionSubscriptionHandle,
84210
84974
  commandSubscriptionHandle,
84211
- lifecycleManager
84975
+ lifecycleManager,
84976
+ closeSessionsOnShutdown: () => closeAllMachineHarnessSessionsOnShutdown(session2, {
84977
+ lifecycleManager,
84978
+ activeSessions,
84979
+ sessionRepository
84980
+ })
84212
84981
  };
84213
84982
  }
84214
84983
  var init_start_subscriptions = __esm(() => {
@@ -84216,6 +84985,7 @@ var init_start_subscriptions = __esm(() => {
84216
84985
  init_harness_lifecycle_manager();
84217
84986
  init_prompt_subscriber();
84218
84987
  init_session_subscriber();
84988
+ init_shutdown_sessions();
84219
84989
  init_api3();
84220
84990
  init_convex_capabilities_publisher();
84221
84991
  init_convex_output_repository();
@@ -84935,16 +85705,6 @@ function createTurnCompletedBackend(deps) {
84935
85705
  machineId: deps.machineId,
84936
85706
  ...args2
84937
85707
  }),
84938
- emitSessionResumed: (args2) => deps.backend.mutation(api.machines.emitSessionResumed, {
84939
- sessionId: deps.sessionId,
84940
- machineId: deps.machineId,
84941
- ...args2
84942
- }),
84943
- emitSessionResumeFailed: (args2) => deps.backend.mutation(api.machines.emitSessionResumeFailed, {
84944
- sessionId: deps.sessionId,
84945
- machineId: deps.machineId,
84946
- ...args2
84947
- }),
84948
85708
  emitAgentStartFailed: (args2) => deps.backend.mutation(api.machines.emitAgentStartFailed, {
84949
85709
  sessionId: deps.sessionId,
84950
85710
  machineId: deps.machineId,
@@ -85239,12 +85999,13 @@ class NativeTaskDeliveryCoordinator {
85239
85999
  }
85240
86000
  }
85241
86001
  reconcileAssignedTasks(params) {
85242
- const { tasks, runtime: runtime4, effectContext: effectContext2, agentMgr, sessionDeps } = params;
85243
- for (const task of tasks) {
85244
- 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);
85245
86006
  const harnessSessionId = slot?.harnessSessionId;
85246
- if (!shouldDeliverNativeTask(task, {
85247
- ledger: this.ledger,
86007
+ if (!shouldDeliverNativeTask(row, {
86008
+ ledger,
85248
86009
  harnessSessionId
85249
86010
  })) {
85250
86011
  continue;
@@ -85252,14 +86013,24 @@ class NativeTaskDeliveryCoordinator {
85252
86013
  if (!harnessSessionId) {
85253
86014
  continue;
85254
86015
  }
85255
- exports_Runtime.runFork(runtime4)(runNativeInjectionEffect(task, harnessSessionId, {
85256
- sessionId: sessionDeps.sessionId,
85257
- backend: sessionDeps.backend,
85258
- agentMgr: {
85259
- resumeTurnForSlot: (args2) => exports_Effect.runPromise(agentMgr.resumeTurnForSlot(args2))
85260
- },
85261
- convexUrl: sessionDeps.convexUrl
85262
- }, 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)}`)))));
85263
86034
  }
85264
86035
  }
85265
86036
  }
@@ -85275,6 +86046,7 @@ var init_native_task_delivery_coordinator = __esm(() => {
85275
86046
  init_esm();
85276
86047
  init_native_task_injector_logic();
85277
86048
  init_native_task_injector();
86049
+ init_api3();
85278
86050
  init_convex_error();
85279
86051
  });
85280
86052
 
@@ -85570,9 +86342,6 @@ async function tryAbortResumeStorm(deps, input, slot) {
85570
86342
  return false;
85571
86343
  }
85572
86344
  deps.resumeStormTracker.reset(input.chatroomId, input.role);
85573
- if (slot) {
85574
- slot.resumeInFlight = false;
85575
- }
85576
86345
  try {
85577
86346
  await deps.backend.emitResumeStormAborted({
85578
86347
  chatroomId: input.chatroomId,
@@ -85608,11 +86377,11 @@ function appendRecentLogLine(slot, line) {
85608
86377
  }
85609
86378
  var RECENT_LOG_LINE_CAP2 = 100;
85610
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
+
85611
86383
  // src/domain/agent-lifecycle/use-cases/handle-turn-completed.ts
85612
86384
  async function handleTurnCompleted(deps, input, slot) {
85613
- if (slot?.resumeInFlight) {
85614
- return { outcome: "skipped_duplicate" };
85615
- }
85616
86385
  if (await tryAbortResumeStorm(deps, input, slot)) {
85617
86386
  return { outcome: "storm_aborted" };
85618
86387
  }
@@ -85631,35 +86400,6 @@ async function handleTurnCompleted(deps, input, slot) {
85631
86400
  deps.killProcess(input.pid);
85632
86401
  return { outcome: "killed_terminal_provider_error" };
85633
86402
  }
85634
- if (input.supportsSessionResume && input.wantResume) {
85635
- if (slot) {
85636
- slot.resumeInFlight = true;
85637
- }
85638
- try {
85639
- await deps.resumeTurn(input.pid, deps.composeResumePrompt({ chatroomId: input.chatroomId, role: input.role }));
85640
- try {
85641
- await deps.backend.emitSessionResumed({
85642
- chatroomId: input.chatroomId,
85643
- role: input.role,
85644
- ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
85645
- });
85646
- } catch {}
85647
- return { outcome: "resumed" };
85648
- } catch (err) {
85649
- try {
85650
- await deps.backend.emitSessionResumeFailed({
85651
- chatroomId: input.chatroomId,
85652
- role: input.role,
85653
- reason: err.message,
85654
- ...slot?.harnessSessionId ? { harnessSessionId: slot.harnessSessionId } : {}
85655
- });
85656
- } catch {}
85657
- } finally {
85658
- if (slot) {
85659
- slot.resumeInFlight = false;
85660
- }
85661
- }
85662
- }
85663
86403
  deps.killProcess(input.pid);
85664
86404
  return { outcome: "killed" };
85665
86405
  }
@@ -86039,6 +86779,7 @@ class AgentProcessManager {
86039
86779
  deps;
86040
86780
  slots = new Map;
86041
86781
  lastHarnessSessions = new Map;
86782
+ sessionReopenRetryInFlight = new Set;
86042
86783
  exitRetryQueue = [];
86043
86784
  exitRetryTimer = null;
86044
86785
  turnEndQueue = new TurnEndQueue;
@@ -86070,7 +86811,6 @@ class AgentProcessManager {
86070
86811
  model: slot.model,
86071
86812
  workingDir: slot.workingDir,
86072
86813
  startedAt: slot.startedAt,
86073
- resumeInFlight: slot.resumeInFlight,
86074
86814
  recentLogLines: slot.recentLogLines,
86075
86815
  wantResume: slot.wantResume
86076
86816
  });
@@ -86176,9 +86916,7 @@ class AgentProcessManager {
86176
86916
  }
86177
86917
  async runHandleAgentEnd(opts) {
86178
86918
  const slot = this.slots.get(agentKey3(opts.chatroomId, opts.role));
86179
- const service3 = this.deps.agentServices.get(opts.harness);
86180
86919
  const capabilities = getHarnessCapabilities(opts.harness);
86181
- const supportsSessionResume = capabilities.supportsSessionResume && typeof service3?.resumeTurn === "function";
86182
86920
  this.updateSlotsMirror(opts.chatroomId, opts.role, {
86183
86921
  state: slot?.state ?? "idle",
86184
86922
  pid: slot?.pid,
@@ -86187,11 +86925,10 @@ class AgentProcessManager {
86187
86925
  model: slot?.model,
86188
86926
  workingDir: slot?.workingDir,
86189
86927
  startedAt: slot?.startedAt,
86190
- resumeInFlight: slot?.resumeInFlight,
86191
86928
  recentLogLines: slot?.recentLogLines,
86192
86929
  wantResume: slot?.wantResume
86193
86930
  });
86194
- console.log(`[AgentProcessManager] lifecycle.turn.completed: role=${opts.role} pid=${opts.pid} harness=${opts.harness} supportsResume=${supportsSessionResume}`);
86931
+ console.log(`[AgentProcessManager] lifecycle.turn.completed: role=${opts.role} pid=${opts.pid} harness=${opts.harness}`);
86195
86932
  if (capabilities.supportsNativeIntegration) {
86196
86933
  await this.runHandleNativeTurnEnd(opts, slot);
86197
86934
  return;
@@ -86200,18 +86937,6 @@ class AgentProcessManager {
86200
86937
  resumeStormTracker: this.deps.resumeStormTracker,
86201
86938
  backend: createTurnCompletedBackend(this.deps),
86202
86939
  now: () => this.deps.clock.now(),
86203
- composeResumePrompt: ({ chatroomId, role }) => composeResumeMessage({
86204
- chatroomId,
86205
- role,
86206
- convexUrl: this.deps.convexUrl,
86207
- supportsNativeIntegration: capabilities.supportsNativeIntegration
86208
- }),
86209
- resumeTurn: async (pid, prompt) => {
86210
- if (!service3?.resumeTurn) {
86211
- throw new Error("Harness does not support resumeTurn");
86212
- }
86213
- await service3.resumeTurn(pid, prompt);
86214
- },
86215
86940
  killProcess: (pid) => {
86216
86941
  try {
86217
86942
  this.deps.processes.kill(-pid, "SIGTERM");
@@ -86221,27 +86946,17 @@ class AgentProcessManager {
86221
86946
  }, {
86222
86947
  chatroomId: opts.chatroomId,
86223
86948
  role: opts.role,
86224
- pid: opts.pid,
86225
- supportsSessionResume,
86226
- wantResume: slot?.wantResume ?? true
86949
+ pid: opts.pid
86227
86950
  }, slot);
86228
- if (result.outcome === "skipped_duplicate") {
86229
- console.log(`[AgentProcessManager] lifecycle.turn.completed: skipping duplicate resume for ${opts.role} (resume already in flight)`);
86230
- } else if (result.outcome === "storm_aborted") {
86951
+ if (result.outcome === "storm_aborted") {
86231
86952
  console.log(`[AgentProcessManager] ✅ Handled rapid resume storm for ${opts.role}`);
86232
- } else if (result.outcome === "resumed") {
86233
- console.log(`[AgentProcessManager] ✅ Emitted agent.sessionResumed for ${opts.role}`);
86234
86953
  } else if (result.outcome === "killed") {
86235
- console.log(`[AgentProcessManager] lifecycle.turn.completed: killed process for ${opts.role} (resume disabled or failed)`);
86954
+ console.log(`[AgentProcessManager] lifecycle.turn.completed: killed process for ${opts.role}`);
86236
86955
  } else if (result.outcome === "killed_terminal_provider_error") {
86237
86956
  console.log(`[AgentProcessManager] ⛔ Terminal provider error for ${opts.role} — emitted agent.startFailed`);
86238
86957
  }
86239
86958
  }
86240
86959
  async runHandleNativeTurnEnd(opts, slot) {
86241
- if (slot?.resumeInFlight) {
86242
- console.log(`[AgentProcessManager] lifecycle.turn.completed: skipping duplicate native turn-end for ${opts.role}`);
86243
- return;
86244
- }
86245
86960
  if (await tryAbortResumeStorm({
86246
86961
  resumeStormTracker: this.deps.resumeStormTracker,
86247
86962
  backend: createTurnCompletedBackend(this.deps),
@@ -86250,9 +86965,7 @@ class AgentProcessManager {
86250
86965
  }, {
86251
86966
  chatroomId: opts.chatroomId,
86252
86967
  role: opts.role,
86253
- pid: opts.pid,
86254
- supportsSessionResume: false,
86255
- wantResume: false
86968
+ pid: opts.pid
86256
86969
  }, slot)) {
86257
86970
  console.log(`[AgentProcessManager] ✅ Handled rapid resume storm for ${opts.role}`);
86258
86971
  return;
@@ -86416,6 +87129,10 @@ class AgentProcessManager {
86416
87129
  return;
86417
87130
  }
86418
87131
  const coldRestartAfterRunError = this.clearHarnessSessionAfterRunError(key, logs);
87132
+ if (harness === "cursor-sdk") {
87133
+ this.retryCursorSdkSessionReopen(opts, ctx, coldRestartAfterRunError);
87134
+ return;
87135
+ }
86419
87136
  this.ensureRunning({
86420
87137
  chatroomId: opts.chatroomId,
86421
87138
  role: opts.role,
@@ -86433,6 +87150,49 @@ class AgentProcessManager {
86433
87150
  this.emitStartFailedEvent(opts.role, opts.chatroomId, err.message);
86434
87151
  });
86435
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
+ }
86436
87196
  handlePermanentFailureForRestart(opts, recentLogLines, startFailedAlreadyEmitted) {
86437
87197
  const error = isTerminalProviderFailureInLogs(recentLogLines ?? []) ? formatTerminalProviderFailureMessage(recentLogLines ?? []) : formatPermanentHarnessFailureMessage(recentLogLines ?? []);
86438
87198
  console.log(`[AgentProcessManager] ⛔ Skipping restart — ${error}`);
@@ -86652,12 +87412,12 @@ class AgentProcessManager {
86652
87412
  if (!stored) {
86653
87413
  return null;
86654
87414
  }
86655
- const resumeFromDaemonMemory = opts.service.resumeFromDaemonMemory;
86656
- if (!resumeFromDaemonMemory) {
87415
+ if (!opts.service.resumeFromDaemonMemory) {
86657
87416
  return null;
86658
87417
  }
86659
87418
  try {
86660
- const spawnResult = await resumeFromDaemonMemory({
87419
+ await this.emitSessionResumeRequested(opts.chatroomId, opts.role, opts.agentHarness, stored.harnessSessionId);
87420
+ const spawnResult = await opts.service.resumeFromDaemonMemory({
86661
87421
  workingDir: stored.workingDir,
86662
87422
  prompt: createSpawnPrompt(opts.initPrompt),
86663
87423
  systemPrompt: opts.systemPrompt,
@@ -86704,6 +87464,21 @@ class AgentProcessManager {
86704
87464
  }
86705
87465
  return null;
86706
87466
  }
87467
+ async emitSessionResumeRequested(chatroomId, role, agentHarness, harnessSessionId) {
87468
+ try {
87469
+ await this.deps.backend.mutation(api.machines.emitSessionResumeRequested, {
87470
+ sessionId: this.deps.sessionId,
87471
+ machineId: this.deps.machineId,
87472
+ chatroomId,
87473
+ role,
87474
+ agentHarness,
87475
+ ...harnessSessionId ? { harnessSessionId } : {}
87476
+ });
87477
+ console.log(`[AgentProcessManager] ✅ Emitted agent.sessionResumeRequested for ${role}`);
87478
+ } catch (err) {
87479
+ console.log(` ⚠️ Failed to emit sessionResumeRequested event: ${err.message}`);
87480
+ }
87481
+ }
86707
87482
  async emitSessionResumed(chatroomId, role, harnessSessionId) {
86708
87483
  try {
86709
87484
  await this.deps.backend.mutation(api.machines.emitSessionResumed, {
@@ -86733,6 +87508,23 @@ class AgentProcessManager {
86733
87508
  console.log(` ⚠️ Failed to emit sessionResumeFailed event: ${err.message}`);
86734
87509
  }
86735
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
+ }
86736
87528
  resetSlotIdle(slot) {
86737
87529
  slot.state = "idle";
86738
87530
  slot.pendingOperation = undefined;
@@ -87110,7 +87902,6 @@ class AgentProcessManager {
87110
87902
  }
87111
87903
  var AGENT_EXIT_RETRY_INTERVAL_MS = 1e4;
87112
87904
  var init_agent_process_manager = __esm(() => {
87113
- init_generator();
87114
87905
  init_types();
87115
87906
  init_participant();
87116
87907
  init_esm();
@@ -87136,7 +87927,7 @@ class SpawnRateLimiter {
87136
87927
  this.config = { ...DEFAULT_CONFIG2, ...config3 };
87137
87928
  }
87138
87929
  tryConsume(chatroomId, reason) {
87139
- 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") {
87140
87931
  return { allowed: true };
87141
87932
  }
87142
87933
  const bucket = this._getOrCreateBucket(chatroomId);
@@ -87163,13 +87954,16 @@ class SpawnRateLimiter {
87163
87954
  };
87164
87955
  }
87165
87956
  _getOrCreateBucket(chatroomId) {
87166
- if (!this.buckets.has(chatroomId)) {
87167
- this.buckets.set(chatroomId, {
87168
- tokens: this.config.initialTokens,
87169
- lastRefillAt: Date.now()
87170
- });
87957
+ const existing = this.buckets.get(chatroomId);
87958
+ if (existing) {
87959
+ return existing;
87171
87960
  }
87172
- 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;
87173
87967
  }
87174
87968
  _refill(bucket) {
87175
87969
  const now = Date.now();
@@ -88056,6 +88850,432 @@ var init_task_monitor_logic = __esm(() => {
88056
88850
  init_predicates();
88057
88851
  });
88058
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
+
88059
89279
  // src/commands/machine/daemon-start/task-monitor.ts
88060
89280
  function resolveTaskWantResume(task) {
88061
89281
  return compressContextToWantResume(parseCompressContext(task.taskContent ?? ""));
@@ -88068,7 +89288,7 @@ function buildCliNudgeLogLine(task) {
88068
89288
  const wantResume = resolveTaskWantResume(task);
88069
89289
  return `[TaskMonitor] nudging ${role}@${chatroomId} — pending task ${task.taskId}, lastSeenAction=${lastSeenAction}, compress_context=${compressMode}, wantResume=${wantResume}`;
88070
89290
  }
88071
- function resolveTaskRunnerContext(task) {
89291
+ function resolveTaskRunnerContextFromFull(task) {
88072
89292
  const { chatroomId, agentConfig } = task;
88073
89293
  const { role } = agentConfig;
88074
89294
  const workingDir = agentConfig.workingDir;
@@ -88083,7 +89303,7 @@ function resolveTaskRunnerContext(task) {
88083
89303
  };
88084
89304
  }
88085
89305
  function executeCliNudge(task, runtime4, effectContext2, agentMgr) {
88086
- const ctx = resolveTaskRunnerContext(task);
89306
+ const ctx = resolveTaskRunnerContextFromFull(task);
88087
89307
  if (!ctx)
88088
89308
  return;
88089
89309
  const { chatroomId, agentConfig, role, workingDir, wantResume } = ctx;
@@ -88101,7 +89321,7 @@ function executeCliNudge(task, runtime4, effectContext2, agentMgr) {
88101
89321
  }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[TaskMonitor] nudge failed for ${role}@${chatroomId}: ${getErrorMessage(err)}`)))));
88102
89322
  }
88103
89323
  function runNativeReviveEffect(task, runtime4, effectContext2, agentMgr) {
88104
- const ctx = resolveTaskRunnerContext(task);
89324
+ const ctx = resolveTaskRunnerContextFromFull(task);
88105
89325
  if (!ctx)
88106
89326
  return;
88107
89327
  const { chatroomId, agentConfig, role, workingDir, wantResume } = ctx;
@@ -88118,63 +89338,124 @@ function runNativeReviveEffect(task, runtime4, effectContext2, agentMgr) {
88118
89338
  });
88119
89339
  }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[TaskMonitor] native revive failed for ${role}@${chatroomId}: ${getErrorMessage(err)}`)))));
88120
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
+ }
88121
89350
  function runCliNudgeEffect(task, runtime4, effectContext2, agentMgr) {
88122
89351
  console.log(buildCliNudgeLogLine(task));
88123
89352
  executeCliNudge(task, runtime4, effectContext2, agentMgr);
88124
89353
  }
88125
- function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr) {
88126
- for (const task of listTasksReadyForNudge(tasks, now, cooldown)) {
88127
- 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);
88128
89360
  }
88129
89361
  }
88130
- function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr) {
88131
- for (const task of listNativeTasksNeedingRevive(tasks, localHealth, now, cooldown)) {
88132
- 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);
88133
89368
  }
88134
89369
  }
88135
- 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;
88136
89373
  const now = Date.now();
88137
89374
  const localHealth = {
88138
89375
  getSlot: (chatroomId, role) => agentMgr.getSlot(chatroomId, role),
88139
89376
  isPidAlive: (pid) => isProcessAlive((p) => process.kill(p, 0), pid)
88140
89377
  };
88141
- reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr);
89378
+ await reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
88142
89379
  getNativeTaskDeliveryCoordinator().reconcileAssignedTasks({
88143
89380
  tasks,
88144
89381
  runtime: runtime4,
88145
89382
  effectContext: effectContext2,
88146
89383
  agentMgr,
88147
- sessionDeps
89384
+ sessionDeps,
89385
+ machineId
88148
89386
  });
88149
- nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr);
89387
+ if (pass === "reconcile") {
89388
+ await nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
89389
+ }
88150
89390
  }
88151
- var startTaskMonitorSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
89391
+ var startTaskMonitorEffect = (wsClient2) => exports_Effect.gen(function* () {
88152
89392
  const session2 = yield* DaemonSessionService;
88153
89393
  const agentMgr = yield* DaemonAgentProcessManagerService;
88154
89394
  const effectContext2 = yield* exports_Effect.context();
88155
89395
  const runtime4 = yield* exports_Effect.runtime();
88156
- console.log(`[${formatTimestamp()}] \uD83D\uDCCB Starting task-monitor subscription (reactive)`);
89396
+ console.log(`[${formatTimestamp()}] \uD83D\uDCCB Starting task-monitor (incremental subscribe)`);
88157
89397
  const cooldown = new NudgeCooldown;
89398
+ const snapshot = createTaskMonitorSnapshot();
88158
89399
  let stopped = false;
89400
+ let monitorPassInFlight = false;
88159
89401
  const sessionDeps = {
88160
89402
  sessionId: session2.sessionId,
88161
89403
  convexUrl: session2.convexUrl,
89404
+ machineId: session2.machineId,
88162
89405
  backend: {
88163
89406
  mutation: (fn2, args2) => session2.backend.mutation(fn2, args2),
88164
89407
  query: (fn2, args2) => session2.backend.query(fn2, args2)
88165
89408
  }
88166
89409
  };
88167
- const onTasksUpdate = (result) => {
88168
- if (stopped || !result?.tasks?.length)
89410
+ const runMonitorPass = (tasks, pass) => {
89411
+ if (stopped || monitorPassInFlight || tasks.length === 0)
88169
89412
  return;
88170
- 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
+ });
88171
89417
  };
88172
- 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
+ });
88173
89454
  return {
88174
89455
  stop() {
88175
89456
  stopped = true;
88176
- unsubscribe();
88177
- console.log(`[${formatTimestamp()}] \uD83D\uDCCB Task-monitor subscription stopped`);
89457
+ exports_Effect.runPromise(feedHandle.stop());
89458
+ console.log(`[${formatTimestamp()}] \uD83D\uDCCB Task-monitor stopped`);
88178
89459
  }
88179
89460
  };
88180
89461
  });
@@ -88184,7 +89465,10 @@ var init_task_monitor = __esm(() => {
88184
89465
  init_daemon_services();
88185
89466
  init_native_task_delivery_coordinator();
88186
89467
  init_task_monitor_logic();
89468
+ init_task_monitor_snapshot();
88187
89469
  init_api3();
89470
+ init_dual_channel_feed();
89471
+ init_assigned_task_signals();
88188
89472
  init_convex_error();
88189
89473
  });
88190
89474
 
@@ -88387,6 +89671,11 @@ function handleRefreshCapabilitiesEffect(event, tracker) {
88387
89671
  })));
88388
89672
  });
88389
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
+ }
88390
89679
  var GIT_PUSH_ACTIONS, commandEventHandlers, dispatchCommandEventEffect = (event, tracker) => {
88391
89680
  const factory2 = commandEventHandlers[event.type];
88392
89681
  return factory2 != null ? factory2(event, tracker) : exports_Effect.void;
@@ -88445,9 +89734,7 @@ var init_command_loop = __esm(() => {
88445
89734
  heartbeatCount++;
88446
89735
  console.log(`[${formatTimestamp()}] \uD83D\uDC93 Daemon heartbeat #${heartbeatCount} OK`);
88447
89736
  if (!observedSyncEnabled) {
88448
- exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
88449
- concurrency: "unbounded"
88450
- }).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");
88451
89738
  }
88452
89739
  }).catch((err) => {
88453
89740
  console.warn(`[${formatTimestamp()}] ⚠️ Daemon heartbeat failed: ${getErrorMessage(err)}`);
@@ -88464,14 +89751,13 @@ var init_command_loop = __esm(() => {
88464
89751
  let pendingHarnessSessionSubscriptionHandle = null;
88465
89752
  let commandSubscriptionHandle = null;
88466
89753
  let lifecycleManager = null;
89754
+ let closeDirectHarnessSessionsOnShutdown = null;
88467
89755
  const activeSessions = new Map;
88468
89756
  const harnesses = new Map;
88469
89757
  if (observedSyncEnabled) {
88470
89758
  console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Observed-sync enabled, skipping immediate push`);
88471
89759
  } else {
88472
- exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
88473
- concurrency: "unbounded"
88474
- }).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");
88475
89761
  }
88476
89762
  const PROCESS_KILL_TIMEOUT_MS = 6000;
88477
89763
  const CLOSE_TIMEOUT_MS = 3000;
@@ -88539,8 +89825,12 @@ var init_command_loop = __esm(() => {
88539
89825
  await withTimeout2(exports_Effect.runPromise(onDaemonShutdownEffect.pipe(exports_Effect.provide(effectContext2))), PROCESS_KILL_TIMEOUT_MS);
88540
89826
  };
88541
89827
  const closeAllSessionsAndHarnesses = async () => {
88542
- for (const handle of activeSessions.values()) {
88543
- 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
+ }
88544
89834
  }
88545
89835
  for (const harness of harnesses.values()) {
88546
89836
  await withTimeout2(harness.close(), CLOSE_TIMEOUT_MS);
@@ -88570,7 +89860,7 @@ var init_command_loop = __esm(() => {
88570
89860
  if (observedSyncEnabled) {
88571
89861
  observedSyncSubscriptionHandle = yield* startObservedSyncSubscriptionEffect(wsClient2);
88572
89862
  }
88573
- const taskMonitorHandle = yield* startTaskMonitorSubscriptionEffect(wsClient2);
89863
+ const taskMonitorHandle = yield* startTaskMonitorEffect(wsClient2);
88574
89864
  logObserverSubscriptionHandle = startLogObserverSubscription({ sessionId: session2.sessionId, machineId: session2.machineId }, wsClient2);
88575
89865
  if (featureFlags.directHarnessWorkers) {
88576
89866
  const handles = startDirectHarnessSubscriptions({
@@ -88583,6 +89873,7 @@ var init_command_loop = __esm(() => {
88583
89873
  pendingHarnessSessionSubscriptionHandle = handles.pendingHarnessSessionSubscriptionHandle;
88584
89874
  commandSubscriptionHandle = handles.commandSubscriptionHandle;
88585
89875
  lifecycleManager = handles.lifecycleManager;
89876
+ closeDirectHarnessSessionsOnShutdown = handles.closeSessionsOnShutdown;
88586
89877
  }
88587
89878
  console.log(`
88588
89879
  Listening for commands...`);
@@ -88769,8 +90060,8 @@ import * as os2 from "os";
88769
90060
  import * as path4 from "path";
88770
90061
  async function isChatroomInstalledDefault() {
88771
90062
  try {
88772
- const { execSync: execSync2 } = await import("child_process");
88773
- execSync2("chatroom --version", { stdio: "pipe" });
90063
+ const { execSync: execSync3 } = await import("child_process");
90064
+ execSync3("chatroom --version", { stdio: "pipe" });
88774
90065
  return true;
88775
90066
  } catch {
88776
90067
  return false;
@@ -89684,4 +90975,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
89684
90975
  });
89685
90976
  program2.parse();
89686
90977
 
89687
- //# debugId=A1C383F59B3688B364756E2164756E21
90978
+ //# debugId=00A3989CD8BAD93D64756E2164756E21