chatroom-cli 1.57.3 → 1.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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";
@@ -31671,7 +31677,7 @@ function isClassifiableHarnessLogLine(line) {
31671
31677
  return true;
31672
31678
  if (line.includes(" error]"))
31673
31679
  return true;
31674
- if (!HARNESS_LOG_PREFIX.test(line))
31680
+ if (line.includes(" run-error]"))
31675
31681
  return true;
31676
31682
  return false;
31677
31683
  }
@@ -31710,7 +31716,7 @@ function formatTerminalProviderFailureMessage(logLines) {
31710
31716
  `).trim();
31711
31717
  return blob ? `Provider rate limit or quota error (non-retryable): ${blob.slice(-500)}` : "Provider rate limit or quota error (non-retryable)";
31712
31718
  }
31713
- var QUOTA_PHRASES, PROVIDER_ERROR_NAMES, HARNESS_LOG_PREFIX;
31719
+ var QUOTA_PHRASES, PROVIDER_ERROR_NAMES;
31714
31720
  var init_terminal_provider_error = __esm(() => {
31715
31721
  QUOTA_PHRASES = [
31716
31722
  "usagelimit",
@@ -31724,7 +31730,6 @@ var init_terminal_provider_error = __esm(() => {
31724
31730
  "exceeded your weekly"
31725
31731
  ];
31726
31732
  PROVIDER_ERROR_NAMES = ["ai_apicallerror", "ai_retryerror"];
31727
- HARNESS_LOG_PREFIX = /^\[[^\]]+\] role:/;
31728
31733
  });
31729
31734
 
31730
31735
  // src/infrastructure/services/remote-agents/opencode-sdk/session-event-forwarder.ts
@@ -32520,6 +32525,12 @@ var init_opencode_sdk = __esm(() => {
32520
32525
  });
32521
32526
 
32522
32527
  // src/infrastructure/services/remote-agents/pi-sdk/pi-sdk-package.ts
32528
+ var exports_pi_sdk_package = {};
32529
+ __export(exports_pi_sdk_package, {
32530
+ importBundledPiSdk: () => importBundledPiSdk,
32531
+ getBundledPiSdkVersion: () => getBundledPiSdkVersion,
32532
+ formatPiSdkLoadError: () => formatPiSdkLoadError
32533
+ });
32523
32534
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
32524
32535
  import { dirname as dirname4, join as join9 } from "node:path";
32525
32536
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -75550,21 +75561,38 @@ Put your **complete** deliverable in the handoff message — not in session text
75550
75561
  ---`;
75551
75562
  }
75552
75563
 
75564
+ // ../../services/backend/prompts/utils/code-change-verification.ts
75565
+ var CODE_CHANGE_VERIFICATION_COMMAND = "pnpm typecheck && pnpm test", CODE_CHANGE_VERIFICATION_CONFIRMATION;
75566
+ var init_code_change_verification = __esm(() => {
75567
+ CODE_CHANGE_VERIFICATION_CONFIRMATION = `- [ ] I confirm that I have run \`${CODE_CHANGE_VERIFICATION_COMMAND}\` (only required if code changes were made)`;
75568
+ });
75569
+
75553
75570
  // ../../services/backend/prompts/teams/duo/handoff-templates/builder-to-planner.ts
75554
75571
  function getBuilderToPlannerHandoffTemplate() {
75555
75572
  return `${getHandoffRecipientVisibilityCallout("planner")}
75556
75573
 
75557
- **Handoff Template (Builder → Planner)** — paste into the handoff message. Fill in EVERY section; use \`Not Applicable\` when a section does not apply.
75574
+ **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
75575
 
75559
75576
  \`\`\`markdown
75560
75577
  ## Summary
75561
75578
  <what was implemented or attempted, in plain terms>
75562
75579
 
75563
- ## Proof files changed
75580
+ ## Template Disclosure Confirmation
75581
+ - [ ] I confirm that I have seen this template at the start of this task, before implementing or modifying any code
75582
+
75583
+ ## Proof of Principle
75584
+ <!-- Demonstrate adherence to:
75585
+ - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
75586
+ - 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.
75587
+ -->
75588
+ <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
75589
+
75590
+ ## Proof of Completion
75564
75591
  - \`path/to/file.ts\` — <what changed and why>
75592
+ <evidence the goal was met — list every file you modified>
75565
75593
 
75566
- ## Verification
75567
- - \`pnpm typecheck && pnpm test\` — <pass/fail + notes>
75594
+ ## Code Change Verification
75595
+ ${CODE_CHANGE_VERIFICATION_CONFIRMATION}
75568
75596
 
75569
75597
  ## Blockers / questions
75570
75598
  <anything needing planner decision, or "Not Applicable">
@@ -75573,18 +75601,18 @@ function getBuilderToPlannerHandoffTemplate() {
75573
75601
  <specific areas for planner to check, or "Not Applicable">
75574
75602
  \`\`\``;
75575
75603
  }
75576
- var init_builder_to_planner = () => {};
75604
+ var init_builder_to_planner = __esm(() => {
75605
+ init_code_change_verification();
75606
+ });
75577
75607
 
75578
75608
  // ../../services/backend/prompts/teams/duo/handoff-templates/planner-to-builder.ts
75579
75609
  function getPlannerToBuilderHandoffTemplate(nativeIntegration = false) {
75580
- const sessionManagement = nativeIntegration ? `**Native harnesses** (\`cursor-sdk\`, \`opencode-sdk\`): in-session context compaction is supported by the SDK runtime. \`new_session\` triggers a fresh context within the same process; the session stays active and tasks continue via injection.` : `**Native harnesses** (\`cursor-sdk\`, \`opencode-sdk\`): in-session context compaction is supported by the SDK runtime. \`new_session\` triggers a fresh context within the same process; no get-next-task rejoin needed.
75581
-
75582
- **CLI harnesses** (all others): in-session compaction is NOT supported. \`new_session\` requires a hard restart — the daemon stops the agent, cold-starts it, and the agent must rejoin via \`get-next-task\`. \`none\` resumes the prior session (\`wantResume=true\`).`;
75610
+ const sessionManagement = nativeIntegration ? `\`compact\` runs in-session context compaction via the SDK runtime. \`new_session\` starts a completely new session within the same process (not compaction). \`none\` continues the prior session. Tasks continue via injection.` : `\`compact\` is NOT supported use \`none\` or \`new_session\`. \`new_session\` requires a hard restart (daemon stops agent, cold-starts, agent rejoins via \`get-next-task\`). \`none\` resumes prior session (\`wantResume=true\`).`;
75583
75611
  return `${getHandoffRecipientVisibilityCallout("builder")}
75584
75612
 
75585
75613
  **Delegation Brief (Planner → Builder)** — paste into the handoff message and fill in EVERY field. No field is optional: if a section does not apply, write \`Not Applicable\` (do not delete the section).
75586
75614
 
75587
- **Division of labor:** You (planner) own architecture and API shape. The builder implements exactly what you specify, runs verification, and does not redesign or invent alternatives unless blocked.
75615
+ **Division of labor:** You (planner) own architecture and API shape. The builder implements exactly what you specify and does not redesign or invent alternatives unless blocked.
75588
75616
 
75589
75617
  **Detail bar:** Specify down to **every file** the builder will create or modify (full repo paths). Include code snippets — types, signatures, stubs, or target implementations — until a competent builder **cannot misinterpret** what to write. Vague layers ("update the backend", "fix the component") are not acceptable.
75590
75618
 
@@ -75636,23 +75664,23 @@ Cross-file types, interfaces, or patterns that apply beyond a single file. Write
75636
75664
 
75637
75665
  ## Requirements (acceptance criteria)
75638
75666
  - <verifiable outcome the builder can self-check>
75639
- - Verify: \`pnpm typecheck && pnpm test\`
75640
75667
 
75641
75668
  ## What to avoid
75642
75669
  - <anti-patterns, recurring mistakes, or scope creep for this slice — be explicit>
75643
75670
  - <e.g. "Do not add new abstractions", "Do not refactor unrelated files", "Do not change existing public APIs", or "Not Applicable">
75644
75671
 
75645
75672
  ## Skills to activate
75646
- - <e.g. chatroom skill activate software-engineering --chatroom-id=<id> --role=builder, or "Not Applicable">
75673
+ - <e.g. chatroom skill activate code-review --chatroom-id=<id> --role=builder, or "Not Applicable">
75647
75674
 
75648
75675
  ## Out of scope
75649
75676
  - <files or areas the builder must NOT touch in this slice, or "Not Applicable">
75650
75677
 
75651
- ## Session Management
75652
- Valid values: \`new_session\` | \`none\`
75653
- - \`new_session\` — start a fresh agent session (default)
75678
+ ## Session Augmentation
75679
+ Valid values: \`none\` | \`compact\` | \`new_session\`
75654
75680
  - \`none\` — continue prior session context
75655
- // data:agent.compress_context=new_session
75681
+ - \`compact\` — run in-session context compaction (native SDK harnesses only)
75682
+ - \`new_session\` — start a completely new session (default)
75683
+ // data:agent.session_augmentation=new_session
75656
75684
 
75657
75685
  ${sessionManagement}
75658
75686
 
@@ -75684,6 +75712,15 @@ function getPlannerToUserReportTemplate() {
75684
75712
  - \`path/to/file.ts\` — <what changed and why>
75685
75713
  <evidence the goal was met — list every file you (or the builder) modified>
75686
75714
 
75715
+ ## Backlog Tasks Implemented
75716
+ - \`backlog-item-id\` — <backlog item title/summary and how this work addresses it>
75717
+ <List every backlog item this work implemented. Write \`Not Applicable\` if no backlog items were in scope.>
75718
+
75719
+ ## Backlog Pending User Review Confirmation
75720
+ - [ ] 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
75721
+ - PR URL(s): <link to PR(s), or \`Not Applicable\` if no PR was raised>
75722
+ - If no backlog items apply, write \`Not Applicable\` for the checkbox and explain in one line
75723
+
75687
75724
  ## Key Technical Decisions
75688
75725
  - <schema design, modules, interfaces, domain entities — what you chose and why, or "Not Applicable">
75689
75726
 
@@ -75701,14 +75738,16 @@ flowchart TD
75701
75738
  A[Component] --> B[Component]
75702
75739
  \`\`\`
75703
75740
 
75704
- ## Verification
75705
- - \`pnpm typecheck && pnpm test\` — <result>
75741
+ ## Code Change Verification
75742
+ ${CODE_CHANGE_VERIFICATION_CONFIRMATION}
75706
75743
 
75707
75744
  ## Notes / Next steps
75708
75745
  <anything the user should know, follow-ups, or open questions, or "Not Applicable">
75709
75746
  \`\`\``;
75710
75747
  }
75711
- var init_planner_to_user = () => {};
75748
+ var init_planner_to_user = __esm(() => {
75749
+ init_code_change_verification();
75750
+ });
75712
75751
 
75713
75752
  // ../../services/backend/prompts/teams/duo/handoff-templates/index.ts
75714
75753
  function getDuoHandoffTemplate(query) {
@@ -75758,9 +75797,6 @@ flowchart TD
75758
75797
  A[Component] --> B[Component]
75759
75798
  \`\`\`
75760
75799
 
75761
- ## Verification
75762
- - \`pnpm typecheck && pnpm test\` — <result>
75763
-
75764
75800
  ## Notes / Next steps
75765
75801
  <anything the user should know, follow-ups, or open questions, or "Not Applicable">
75766
75802
  \`\`\``;
@@ -76013,16 +76049,11 @@ var init_fromContext = __esm(() => {
76013
76049
  init_planner();
76014
76050
  init_solo();
76015
76051
  });
76016
- // ../../services/backend/prompts/cli/sections/handoff-templates-preview.ts
76017
- var init_handoff_templates_preview = __esm(() => {
76018
- init_handoff_templates3();
76019
- });
76020
76052
 
76021
76053
  // ../../services/backend/prompts/native/task-started-content.ts
76022
76054
  var init_task_started_content = __esm(() => {
76023
76055
  init_new();
76024
76056
  });
76025
-
76026
76057
  // ../../services/backend/prompts/sections/classification-guide.ts
76027
76058
  var init_classification_guide = __esm(() => {
76028
76059
  init_cli();
@@ -76050,6 +76081,8 @@ var init_current_classification = () => {};
76050
76081
  var init_getting_started = __esm(() => {
76051
76082
  init_getting_started_content();
76052
76083
  });
76084
+ // ../../services/backend/src/domain/usecase/skills/modules/attachments/index.ts
76085
+ var init_attachments = () => {};
76053
76086
 
76054
76087
  // ../../services/backend/prompts/cli/backlog/command.ts
76055
76088
  var init_command3 = __esm(() => {
@@ -76064,18 +76097,11 @@ var init_backlog = __esm(() => {
76064
76097
  // ../../services/backend/src/domain/usecase/skills/modules/code-review/index.ts
76065
76098
  var init_code_review = () => {};
76066
76099
 
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
76100
  // ../../services/backend/src/domain/usecase/skills/registry.ts
76074
76101
  var init_registry3 = __esm(() => {
76102
+ init_attachments();
76075
76103
  init_backlog();
76076
76104
  init_code_review();
76077
- init_development_workflow();
76078
- init_software_engineering();
76079
76105
  });
76080
76106
 
76081
76107
  // ../../services/backend/prompts/sections/glossary.ts
@@ -76102,20 +76128,15 @@ var init_glossary = __esm(() => {
76102
76128
  linkedSkillId: "backlog"
76103
76129
  },
76104
76130
  {
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"
76131
+ term: "attachments",
76132
+ 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.",
76133
+ linkedSkillId: "attachments"
76108
76134
  },
76109
76135
  {
76110
76136
  term: "code-review",
76111
76137
  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
76138
  linkedSkillId: "code-review"
76113
76139
  },
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
76140
  {
76120
76141
  term: "structural-decisions",
76121
76142
  definition: "Meta-level architectural choices that persist in the codebase and influence consistency: " + "folder structure, file naming, interface definitions, and key abstraction names/locations " + "(e.g., Repository/Service layers)."
@@ -76394,7 +76415,6 @@ var init_generator = __esm(() => {
76394
76415
  init_command();
76395
76416
  init_reminder();
76396
76417
  init_fromContext();
76397
- init_handoff_templates_preview();
76398
76418
  init_classification_guide();
76399
76419
  init_commands_reference();
76400
76420
  init_current_classification();
@@ -77401,6 +77421,52 @@ function readFileContent(filePath, optionName) {
77401
77421
  }
77402
77422
  var init_file_content = () => {};
77403
77423
 
77424
+ // ../../services/backend/prompts/attachments/render-delivery-attachments.ts
77425
+ function renderSnippetAttachment(snippet) {
77426
+ return [
77427
+ ` <attachment reference="${snippet.reference}">`,
77428
+ ` <snippet file-source="${snippet.fileSource}">`,
77429
+ ` <user-selected-content>`,
77430
+ snippet.selectedContent,
77431
+ ` </user-selected-content>`,
77432
+ ` </snippet>`,
77433
+ ` </attachment>`
77434
+ ];
77435
+ }
77436
+ function renderBacklogAttachments(items, ctx) {
77437
+ const lines = [];
77438
+ for (const item of items) {
77439
+ lines.push(` <attachment type="backlog-item">`);
77440
+ lines.push(` - [${item.status.toUpperCase()}] ${item.content}`);
77441
+ lines.push(` ID: ${item._id}`);
77442
+ 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>`);
77443
+ lines.push(` </attachment>`);
77444
+ }
77445
+ return lines;
77446
+ }
77447
+ function renderSnippetAttachments(snippets) {
77448
+ const lines = [];
77449
+ for (const snippet of snippets) {
77450
+ lines.push(...renderSnippetAttachment(snippet));
77451
+ }
77452
+ return lines;
77453
+ }
77454
+ function renderDeliveryAttachmentsBlock(input, ctx) {
77455
+ const backlogLines = input.attachedBacklogItems?.length ? DELIVERY_ATTACHMENT_RENDERERS.backlog(input.attachedBacklogItems, ctx) : [];
77456
+ const snippetLines = input.attachedSnippets?.length ? DELIVERY_ATTACHMENT_RENDERERS.snippet(input.attachedSnippets, ctx) : [];
77457
+ if (backlogLines.length === 0 && snippetLines.length === 0)
77458
+ return [];
77459
+ return ["", "<attachments>", ...backlogLines, ...snippetLines, "</attachments>"];
77460
+ }
77461
+ var DELIVERY_ATTACHMENT_RENDERERS;
77462
+ var init_render_delivery_attachments = __esm(() => {
77463
+ DELIVERY_ATTACHMENT_RENDERERS = {
77464
+ backlog: renderBacklogAttachments,
77465
+ snippet: renderSnippetAttachments,
77466
+ message: () => []
77467
+ };
77468
+ });
77469
+
77404
77470
  // src/commands/task/read/render.ts
77405
77471
  function detectBacklogDivergence(contextContent, attachedIds) {
77406
77472
  if (!contextContent || attachedIds.length === 0) {
@@ -77409,19 +77475,10 @@ function detectBacklogDivergence(contextContent, attachedIds) {
77409
77475
  return attachedIds.filter((id3) => !contextContent.includes(id3));
77410
77476
  }
77411
77477
  function renderAttachments(input, chatroomId, role) {
77412
- if (!input.attachedBacklogItems || input.attachedBacklogItems.length === 0)
77413
- return [];
77414
- const lines = [""];
77415
- lines.push("<attachments>");
77416
- for (const item of input.attachedBacklogItems) {
77417
- lines.push(` <attachment type="backlog-item">`);
77418
- lines.push(` - [${item.status.toUpperCase()}] ${item.content}`);
77419
- lines.push(` ID: ${item._id}`);
77420
- lines.push(` <hint>Work on this item. When done: chatroom backlog mark-for-review --chatroom-id="${chatroomId}" --role="${role}" --backlog-item-id=${item._id}</hint>`);
77421
- lines.push(` </attachment>`);
77422
- }
77423
- lines.push("</attachments>");
77424
- return lines;
77478
+ return renderDeliveryAttachmentsBlock({
77479
+ attachedBacklogItems: input.attachedBacklogItems,
77480
+ attachedSnippets: input.attachedSnippets
77481
+ }, { chatroomId, role, mode: "task-read" });
77425
77482
  }
77426
77483
  function renderDivergenceWarnings(input) {
77427
77484
  const ctx = input.context;
@@ -77476,6 +77533,9 @@ ${content}`);
77476
77533
  return lines.join(`
77477
77534
  `);
77478
77535
  }
77536
+ var init_render = __esm(() => {
77537
+ init_render_delivery_attachments();
77538
+ });
77479
77539
 
77480
77540
  // src/commands/task/read/index.ts
77481
77541
  var exports_read = {};
@@ -77503,6 +77563,18 @@ function validateTaskId(taskId) {
77503
77563
  }
77504
77564
  return exports_Effect.void;
77505
77565
  }
77566
+ function buildTaskPromptFromReadResult(result, chatroomId, role) {
77567
+ return renderTaskPrompt({
77568
+ taskId: result.taskId,
77569
+ status: result.status,
77570
+ content: result.content,
77571
+ chatroomId,
77572
+ role,
77573
+ context: result.context && typeof result.context === "object" ? result.context : undefined,
77574
+ attachedBacklogItems: Array.isArray(result.attachedBacklogItems) ? result.attachedBacklogItems : undefined,
77575
+ attachedSnippets: Array.isArray(result.attachedSnippets) ? result.attachedSnippets : undefined
77576
+ });
77577
+ }
77506
77578
  function handleTaskReadError(err) {
77507
77579
  return exports_Effect.sync(() => {
77508
77580
  const handler = taskReadErrorHandlers[err._tag];
@@ -77535,19 +77607,12 @@ var taskReadEffect = (chatroomId, options) => exports_Effect.gen(function* () {
77535
77607
  taskId
77536
77608
  }).pipe(exports_Effect.mapError((cause3) => ({ _tag: "MutationFailed", cause: cause3 })));
77537
77609
  yield* exports_Effect.sync(() => {
77538
- console.log(renderTaskPrompt({
77539
- taskId: result.taskId,
77540
- status: result.status,
77541
- content: result.content,
77542
- chatroomId,
77543
- role,
77544
- context: result.context && typeof result.context === "object" ? result.context : undefined,
77545
- attachedBacklogItems: Array.isArray(result.attachedBacklogItems) ? result.attachedBacklogItems : undefined
77546
- }));
77610
+ console.log(buildTaskPromptFromReadResult(result, chatroomId, role));
77547
77611
  });
77548
77612
  }), taskReadErrorHandlers;
77549
77613
  var init_read = __esm(() => {
77550
77614
  init_esm();
77615
+ init_render();
77551
77616
  init_api3();
77552
77617
  init_storage();
77553
77618
  init_client2();
@@ -82679,161 +82744,311 @@ var init_commit_detail_sync = __esm(() => {
82679
82744
  seenShas = new Map;
82680
82745
  });
82681
82746
 
82682
- // src/domain/direct-harness/usecases/update-capabilities.ts
82683
- async function updateCapabilities(deps, input) {
82684
- const { publisher, machineId } = deps;
82685
- const { harness, workspace } = input;
82686
- const [agents, providers] = await Promise.all([
82687
- harness.listAgents(),
82688
- harness.listProviders()
82689
- ]);
82690
- await publisher.publish({
82691
- machineId,
82692
- lastSeenAt: Date.now(),
82693
- workspaces: [
82694
- {
82695
- workspaceId: workspace.workspaceId,
82696
- cwd: workspace.cwd,
82697
- name: workspace.name,
82698
- harnesses: [
82699
- {
82700
- name: harness.type,
82701
- displayName: harness.displayName,
82702
- agents: [...agents],
82703
- providers: [...providers]
82704
- }
82705
- ]
82706
- }
82707
- ]
82708
- });
82709
- }
82747
+ // src/infrastructure/harnesses/cursor-sdk/cursor-session.ts
82748
+ import { randomUUID as randomUUID3 } from "node:crypto";
82710
82749
 
82711
- // src/commands/machine/daemon-start/direct-harness/command-subscriber.ts
82712
- function startCommandSubscriber(session2, wsClient2, deps) {
82713
- const processed = new Set;
82714
- let processing = false;
82715
- let pendingDrain = false;
82716
- const runDrain = () => {
82717
- if (processing) {
82718
- pendingDrain = true;
82750
+ class CursorSdkSession {
82751
+ opencodeSessionId;
82752
+ _sessionTitle;
82753
+ get sessionTitle() {
82754
+ return this._sessionTitle;
82755
+ }
82756
+ agent;
82757
+ onClose;
82758
+ listeners = new Set;
82759
+ closed = false;
82760
+ turnCount = 0;
82761
+ constructor(options) {
82762
+ this.agent = options.agent;
82763
+ this.opencodeSessionId = options.opencodeSessionId;
82764
+ this._sessionTitle = options.sessionTitle;
82765
+ this.onClose = options.onClose;
82766
+ }
82767
+ setTitle(title) {
82768
+ this._sessionTitle = title;
82769
+ }
82770
+ async prompt(input) {
82771
+ if (this.closed)
82772
+ throw new Error("Session is closed");
82773
+ const text = input.parts.map((p) => p.text).join(`
82774
+ `);
82775
+ const messageId = randomUUID3();
82776
+ const isFirstTurn = this.turnCount === 0;
82777
+ this.turnCount += 1;
82778
+ const modelId = input.model ? resolveModelFromPrompt(input.model.providerID, input.model.modelID) : undefined;
82779
+ const run3 = await withTimeout(this.agent.send(text, {
82780
+ local: { force: isFirstTurn },
82781
+ idempotencyKey: randomUUID3(),
82782
+ ...modelId ? { model: { id: modelId } } : {}
82783
+ }), SEND_TIMEOUT_MS2, "agent.send");
82784
+ for await (const message of run3.stream()) {
82785
+ if (this.closed)
82786
+ break;
82787
+ this.emitFromSdkMessage(message, messageId);
82788
+ }
82789
+ const result = await withTimeout(run3.wait(), RUN_WAIT_TIMEOUT_MS2, "run.wait");
82790
+ if (result.status === "error") {
82791
+ const detail = typeof result.result === "string" && result.result.trim().length > 0 ? result.result.trim() : `run ${result.id} failed`;
82792
+ throw new Error(detail);
82793
+ }
82794
+ this.emit({ type: "session.idle", payload: {}, timestamp: Date.now() });
82795
+ }
82796
+ onEvent(listener) {
82797
+ this.listeners.add(listener);
82798
+ return () => {
82799
+ this.listeners.delete(listener);
82800
+ };
82801
+ }
82802
+ async close() {
82803
+ if (this.closed)
82719
82804
  return;
82805
+ this.closed = true;
82806
+ try {
82807
+ this.agent.close();
82808
+ } catch {}
82809
+ this.onClose?.(this.opencodeSessionId);
82810
+ this.listeners.clear();
82811
+ }
82812
+ emit(event) {
82813
+ for (const listener of this.listeners) {
82814
+ listener(event);
82720
82815
  }
82721
- processing = true;
82722
- pendingDrain = false;
82723
- drain(session2, deps, processed).finally(() => {
82724
- processing = false;
82725
- if (pendingDrain)
82726
- runDrain();
82816
+ }
82817
+ emitDelta(messageId, delta, partType) {
82818
+ if (!delta)
82819
+ return;
82820
+ this.emit({
82821
+ type: "message.part.delta",
82822
+ payload: { messageID: messageId, delta, partType },
82823
+ timestamp: Date.now()
82727
82824
  });
82728
- };
82729
- const unsub = wsClient2.onUpdate(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId }, () => runDrain(), (err) => {
82730
- console.warn("[direct-harness] Command subscription error:", err instanceof Error ? err.message : String(err));
82731
- });
82732
- return { stop: unsub };
82733
- }
82734
- async function dispatchPendingCommand(session2, deps, cmd) {
82735
- switch (cmd.type) {
82736
- case "refreshCapabilities":
82737
- await handleRefreshCapabilities(session2, deps, cmd);
82738
- break;
82739
- case "refreshSessionTitle":
82740
- await handleRefreshSessionTitle(session2, deps, cmd);
82741
- break;
82742
- default:
82743
- await markFailed(session2, cmd._id, `Unknown command type: ${cmd.type}`);
82744
82825
  }
82745
- }
82746
- async function processPendingCommand(session2, deps, cmd, now) {
82747
- if (now - cmd.createdAt > DIRECT_HARNESS_COMMAND_TTL_MS) {
82748
- console.log(`[direct-harness] Discarding stale command ${cmd._id} (type=${cmd.type}, age=${now - cmd.createdAt}ms)`);
82749
- await markFailed(session2, cmd._id, "Command expired (TTL)");
82750
- return;
82826
+ emitFromSdkMessage(message, messageId) {
82827
+ switch (message.type) {
82828
+ case "assistant":
82829
+ for (const block of message.message.content) {
82830
+ if (block.type === "text" && block.text) {
82831
+ this.emitDelta(messageId, block.text, "text");
82832
+ }
82833
+ }
82834
+ break;
82835
+ case "thinking":
82836
+ if (message.text) {
82837
+ this.emitDelta(messageId, message.text, "reasoning");
82838
+ }
82839
+ break;
82840
+ default:
82841
+ break;
82842
+ }
82751
82843
  }
82752
- await dispatchPendingCommand(session2, deps, cmd);
82753
82844
  }
82754
- async function drain(session2, deps, processed) {
82755
- const pending3 = await session2.backend.query(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId });
82756
- if (!pending3 || pending3.length === 0)
82757
- return;
82758
- const now = Date.now();
82759
- for (const cmd of pending3) {
82760
- if (processed.has(cmd._id))
82761
- continue;
82762
- processed.add(cmd._id);
82763
- try {
82764
- await processPendingCommand(session2, deps, cmd, now);
82765
- } catch (err) {
82766
- const message = err instanceof Error ? err.message : String(err);
82767
- console.warn(`[direct-harness] Command ${cmd._id} failed: ${message}`);
82768
- await markFailed(session2, cmd._id, message).catch(() => {});
82769
- }
82845
+ function resolveModelFromPrompt(providerID, modelID) {
82846
+ if (providerID === "cursor")
82847
+ return resolveCursorSdkModel(modelID);
82848
+ return resolveCursorSdkModel(`${providerID}/${modelID}`);
82849
+ }
82850
+ var SEND_TIMEOUT_MS2 = 60000, RUN_WAIT_TIMEOUT_MS2 = 3600000;
82851
+ var init_cursor_session = () => {};
82852
+
82853
+ // src/infrastructure/harnesses/cursor-sdk/cursor-harness.ts
82854
+ async function loadSdk3() {
82855
+ if (_sdkCache3)
82856
+ return _sdkCache3;
82857
+ if (_sdkLoadError3)
82858
+ throw _sdkLoadError3;
82859
+ try {
82860
+ _sdkCache3 = await importBundledCursorSdk();
82861
+ return _sdkCache3;
82862
+ } catch (err) {
82863
+ _sdkLoadError3 = err;
82864
+ throw err;
82770
82865
  }
82771
82866
  }
82772
- async function handleRefreshCapabilities(session2, deps, cmd) {
82773
- const payload = cmd.refreshCapabilities;
82774
- console.log(`[direct-harness] Processing refreshCapabilities for workspace=${cmd.workspaceId}` + (payload ? ` (initiatedBy=${payload.initiatedBy})` : ""));
82775
- const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId);
82776
- await updateCapabilities({ publisher: deps.publisher, machineId: session2.machineId }, {
82777
- harness,
82778
- workspace: {
82779
- workspaceId: cmd.workspaceId,
82780
- cwd: harness.cwd,
82781
- name: harness.cwd
82867
+
82868
+ class CursorSdkHarness {
82869
+ type = "cursor-sdk";
82870
+ displayName = "Cursor (SDK)";
82871
+ cwd;
82872
+ closed = false;
82873
+ sessions = new Map;
82874
+ constructor(cwd) {
82875
+ this.cwd = cwd;
82876
+ }
82877
+ async models() {
82878
+ const providers = await this.listProviders();
82879
+ const models = [];
82880
+ for (const provider of providers) {
82881
+ for (const model of provider.models) {
82882
+ models.push({
82883
+ id: `${provider.providerID}/${model.modelID}`,
82884
+ name: model.name,
82885
+ provider: provider.name
82886
+ });
82887
+ }
82782
82888
  }
82783
- });
82784
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82785
- sessionId: session2.sessionId,
82786
- commandId: cmd._id,
82787
- status: "done"
82788
- });
82789
- console.log(`[direct-harness] Capabilities refreshed for workspace=${cmd.workspaceId}`);
82790
- }
82791
- async function handleRefreshSessionTitle(session2, deps, cmd) {
82792
- const { harnessSessionId } = cmd.refreshSessionTitle ?? {};
82793
- if (!harnessSessionId) {
82794
- await markFailed(session2, cmd._id, "refreshSessionTitle: missing harnessSessionId");
82795
- return;
82889
+ return models;
82796
82890
  }
82797
- const sessionRow = await session2.backend.query(api.daemon.directHarness.sessions.getSession, {
82798
- harnessSessionId
82799
- });
82800
- if (!sessionRow?.opencodeSessionId) {
82801
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82802
- sessionId: session2.sessionId,
82803
- commandId: cmd._id,
82804
- status: "done"
82891
+ async listAgents() {
82892
+ return [{ name: "builder", mode: "primary" }];
82893
+ }
82894
+ async listProviders() {
82895
+ const apiKey = process.env.CURSOR_API_KEY?.trim();
82896
+ if (!apiKey)
82897
+ return [];
82898
+ const { Cursor } = await loadSdk3();
82899
+ const listed = await withTimeout(Cursor.models.list({ apiKey }), MODELS_LIST_TIMEOUT_MS2, "Cursor.models.list");
82900
+ const modelIds = normalizeCursorSdkListedModels(listed.map((m) => m.id).filter((id3) => id3.length > 0));
82901
+ return [
82902
+ {
82903
+ providerID: "cursor",
82904
+ name: "Cursor",
82905
+ models: modelIds.map((modelID) => ({ modelID, name: modelID }))
82906
+ }
82907
+ ];
82908
+ }
82909
+ async newSession(config3) {
82910
+ if (this.closed)
82911
+ throw new Error("Harness is closed");
82912
+ const apiKey = process.env.CURSOR_API_KEY?.trim();
82913
+ if (!apiKey)
82914
+ throw new Error("CURSOR_API_KEY is not set");
82915
+ const modelId = resolveCursorSdkModel(config3.model ?? DEFAULT_MODEL2);
82916
+ const { Agent } = await loadSdk3();
82917
+ const agent = await withTimeout(Agent.create({
82918
+ apiKey,
82919
+ model: { id: modelId },
82920
+ local: { cwd: this.cwd, settingSources: [] }
82921
+ }), AGENT_CREATE_TIMEOUT_MS2, "Agent.create");
82922
+ const session2 = new CursorSdkSession({
82923
+ agent,
82924
+ opencodeSessionId: agent.agentId,
82925
+ sessionTitle: config3.title ?? "",
82926
+ onClose: (id3) => this.sessions.delete(id3)
82805
82927
  });
82806
- return;
82928
+ this.sessions.set(agent.agentId, session2);
82929
+ return session2;
82807
82930
  }
82808
- const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId);
82809
- const newTitle = await harness.fetchSessionTitle(sessionRow.opencodeSessionId);
82810
- if (newTitle) {
82811
- await session2.backend.mutation(api.daemon.directHarness.sessions.updateSessionTitle, {
82812
- sessionId: session2.sessionId,
82813
- harnessSessionId,
82814
- sessionTitle: newTitle
82931
+ async resumeSession(sessionId, _options) {
82932
+ if (this.closed)
82933
+ throw new Error("Harness is closed");
82934
+ const existing = this.sessions.get(sessionId);
82935
+ if (existing)
82936
+ return existing;
82937
+ const apiKey = process.env.CURSOR_API_KEY?.trim();
82938
+ if (!apiKey)
82939
+ throw new Error("CURSOR_API_KEY is not set");
82940
+ const { Agent } = await loadSdk3();
82941
+ const agent = await withTimeout(Agent.resume(sessionId, {
82942
+ apiKey,
82943
+ model: { id: DEFAULT_MODEL2 },
82944
+ local: { cwd: this.cwd, settingSources: [] }
82945
+ }), AGENT_CREATE_TIMEOUT_MS2, "Agent.resume");
82946
+ const session2 = new CursorSdkSession({
82947
+ agent,
82948
+ opencodeSessionId: agent.agentId,
82949
+ sessionTitle: "",
82950
+ onClose: (id3) => this.sessions.delete(id3)
82815
82951
  });
82816
- console.log(`[direct-harness] Refreshed title for session ${harnessSessionId}: "${newTitle}"`);
82952
+ this.sessions.set(agent.agentId, session2);
82953
+ return session2;
82954
+ }
82955
+ async fetchSessionTitle(_opencodeSessionId) {
82956
+ return;
82957
+ }
82958
+ isAlive() {
82959
+ return !this.closed;
82960
+ }
82961
+ async close() {
82962
+ if (this.closed)
82963
+ return;
82964
+ this.closed = true;
82965
+ const closing = [...this.sessions.values()].map((s) => s.close().catch(() => {}));
82966
+ await Promise.all(closing);
82967
+ this.sessions.clear();
82817
82968
  }
82818
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82819
- sessionId: session2.sessionId,
82820
- commandId: cmd._id,
82821
- status: "done"
82822
- });
82823
82969
  }
82824
- async function markFailed(session2, commandId, errorMessage) {
82825
- await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
82826
- sessionId: session2.sessionId,
82827
- commandId,
82828
- status: "failed",
82829
- errorMessage
82830
- });
82970
+ var DEFAULT_MODEL2 = "composer-2.5", MODELS_LIST_TIMEOUT_MS2 = 60000, AGENT_CREATE_TIMEOUT_MS2 = 60000, _sdkCache3, _sdkLoadError3, startCursorSdkHarness = async (config3) => {
82971
+ try {
82972
+ await loadSdk3();
82973
+ } catch (err) {
82974
+ throw new Error(`cursor-sdk unavailable: ${formatCursorSdkLoadError(err)}`);
82975
+ }
82976
+ if (!process.env.CURSOR_API_KEY?.trim()) {
82977
+ throw new Error("CURSOR_API_KEY is not set");
82978
+ }
82979
+ return new CursorSdkHarness(config3.workingDir);
82980
+ };
82981
+ var init_cursor_harness = __esm(() => {
82982
+ init_cursor_session();
82983
+ init_cursor_sdk_package();
82984
+ });
82985
+
82986
+ // src/infrastructure/harnesses/shared-chunk-extractor.ts
82987
+ function createStandardSdkChunkExtractor() {
82988
+ return function extract(event) {
82989
+ if (event.type !== "message.part.delta")
82990
+ return null;
82991
+ const payload = event.payload;
82992
+ const delta = payload?.delta;
82993
+ if (!delta || delta.length === 0)
82994
+ return null;
82995
+ const messageId = payload?.messageID;
82996
+ if (!messageId)
82997
+ return null;
82998
+ return {
82999
+ content: delta,
83000
+ messageId,
83001
+ partType: payload?.partType ?? "text"
83002
+ };
83003
+ };
82831
83004
  }
82832
- var DIRECT_HARNESS_COMMAND_TTL_MS = 60000;
82833
- var init_command_subscriber = __esm(() => {
82834
- init_api3();
83005
+
83006
+ // src/infrastructure/harnesses/cursor-sdk/event-extractor.ts
83007
+ function createCursorSdkChunkExtractor() {
83008
+ return createStandardSdkChunkExtractor();
83009
+ }
83010
+ var init_event_extractor = () => {};
83011
+
83012
+ // src/infrastructure/harnesses/cursor-sdk/index.ts
83013
+ var init_cursor_sdk2 = __esm(() => {
83014
+ init_cursor_harness();
83015
+ init_cursor_session();
83016
+ init_event_extractor();
82835
83017
  });
82836
83018
 
83019
+ // src/infrastructure/harnesses/opencode-sdk/event-extractor.ts
83020
+ function createOpencodeSdkChunkExtractor() {
83021
+ const partMap = new Map;
83022
+ return function extract(event) {
83023
+ if (event.type === "message.part.updated") {
83024
+ const payload = event.payload;
83025
+ const part = payload?.part;
83026
+ if (part?.id && part?.messageID) {
83027
+ const partType = part.type === "reasoning" ? "reasoning" : "text";
83028
+ partMap.set(part.id, { messageId: part.messageID, partType });
83029
+ const delta = payload?.delta;
83030
+ if (delta && delta.length > 0) {
83031
+ return { content: delta, messageId: part.messageID, partType };
83032
+ }
83033
+ }
83034
+ return null;
83035
+ }
83036
+ if (event.type === "message.part.delta") {
83037
+ const payload = event.payload;
83038
+ const delta = payload?.delta;
83039
+ if (!delta || delta.length === 0)
83040
+ return null;
83041
+ const messageId = payload?.messageID;
83042
+ if (!messageId)
83043
+ return null;
83044
+ const partID = payload?.partID;
83045
+ const partType = (partID ? partMap.get(partID)?.partType : undefined) ?? "text";
83046
+ return { content: delta, messageId, partType };
83047
+ }
83048
+ return null;
83049
+ };
83050
+ }
83051
+
82837
83052
  // src/infrastructure/harnesses/opencode-sdk/sse-event-buffer.ts
82838
83053
  var SseEventBuffer;
82839
83054
  var init_sse_event_buffer = __esm(() => {
@@ -83331,44 +83546,560 @@ var init_opencode_harness = __esm(() => {
83331
83546
  init_spawn_env();
83332
83547
  });
83333
83548
 
83334
- // src/infrastructure/harnesses/opencode-sdk/event-extractor.ts
83335
- function createOpencodeSdkChunkExtractor() {
83336
- const partMap = new Map;
83337
- return function extract(event) {
83338
- if (event.type === "message.part.updated") {
83339
- const payload = event.payload;
83340
- const part = payload?.part;
83341
- if (part?.id && part?.messageID) {
83342
- const partType = part.type === "reasoning" ? "reasoning" : "text";
83343
- partMap.set(part.id, { messageId: part.messageID, partType });
83344
- const delta = payload?.delta;
83345
- if (delta && delta.length > 0) {
83346
- return { content: delta, messageId: part.messageID, partType };
83347
- }
83348
- }
83349
- return null;
83549
+ // src/infrastructure/harnesses/pi-sdk/pi-session.ts
83550
+ import { randomUUID as randomUUID4 } from "node:crypto";
83551
+
83552
+ class PiSdkSession {
83553
+ opencodeSessionId;
83554
+ _sessionTitle;
83555
+ get sessionTitle() {
83556
+ return this._sessionTitle;
83557
+ }
83558
+ session;
83559
+ onClose;
83560
+ listeners = new Set;
83561
+ closed = false;
83562
+ unsubscribe;
83563
+ constructor(options) {
83564
+ this.session = options.session;
83565
+ this.opencodeSessionId = options.opencodeSessionId;
83566
+ this._sessionTitle = options.sessionTitle;
83567
+ this.onClose = options.onClose;
83568
+ }
83569
+ setTitle(title) {
83570
+ this._sessionTitle = title;
83571
+ }
83572
+ async prompt(input) {
83573
+ if (this.closed)
83574
+ throw new Error("Session is closed");
83575
+ const text = input.parts.map((p) => p.text).join(`
83576
+ `);
83577
+ const messageId = randomUUID4();
83578
+ const onSessionEvent = (event) => {
83579
+ if (this.closed)
83580
+ return;
83581
+ this.emitFromPiEvent(event, messageId);
83582
+ };
83583
+ this.unsubscribe?.();
83584
+ this.unsubscribe = this.session.subscribe(onSessionEvent);
83585
+ await withTimeout(this.session.prompt(text), PROMPT_TIMEOUT_MS2, "session.prompt");
83586
+ this.emit({ type: "session.idle", payload: {}, timestamp: Date.now() });
83587
+ }
83588
+ onEvent(listener) {
83589
+ this.listeners.add(listener);
83590
+ return () => {
83591
+ this.listeners.delete(listener);
83592
+ };
83593
+ }
83594
+ async close() {
83595
+ if (this.closed)
83596
+ return;
83597
+ this.closed = true;
83598
+ this.unsubscribe?.();
83599
+ try {
83600
+ await this.session.abort();
83601
+ } catch {}
83602
+ try {
83603
+ this.session.dispose();
83604
+ } catch {}
83605
+ this.onClose?.(this.opencodeSessionId);
83606
+ this.listeners.clear();
83607
+ }
83608
+ emit(event) {
83609
+ for (const listener of this.listeners) {
83610
+ listener(event);
83350
83611
  }
83351
- if (event.type === "message.part.delta") {
83352
- const payload = event.payload;
83353
- const delta = payload?.delta;
83354
- if (!delta || delta.length === 0)
83355
- return null;
83356
- const messageId = payload?.messageID;
83357
- if (!messageId)
83358
- return null;
83359
- const partID = payload?.partID;
83360
- const partType = (partID ? partMap.get(partID)?.partType : undefined) ?? "text";
83361
- return { content: delta, messageId, partType };
83612
+ }
83613
+ emitDelta(messageId, delta, partType) {
83614
+ if (!delta)
83615
+ return;
83616
+ this.emit({
83617
+ type: "message.part.delta",
83618
+ payload: { messageID: messageId, delta, partType },
83619
+ timestamp: Date.now()
83620
+ });
83621
+ }
83622
+ emitFromPiEvent(event, messageId) {
83623
+ if (event.type !== "message_update")
83624
+ return;
83625
+ const assistantEvent = event.assistantMessageEvent;
83626
+ if (assistantEvent.type === "text_delta") {
83627
+ this.emitDelta(messageId, assistantEvent.delta, "text");
83628
+ } else if (assistantEvent.type === "thinking_delta") {
83629
+ this.emitDelta(messageId, assistantEvent.delta, "reasoning");
83362
83630
  }
83363
- return null;
83364
- };
83631
+ }
83632
+ }
83633
+ var PROMPT_TIMEOUT_MS2 = 3600000;
83634
+ var init_pi_session = () => {};
83635
+
83636
+ // src/infrastructure/harnesses/pi-sdk/pi-harness.ts
83637
+ async function loadSdk4() {
83638
+ if (_sdkCache4)
83639
+ return _sdkCache4;
83640
+ if (_sdkLoadError4)
83641
+ throw _sdkLoadError4;
83642
+ try {
83643
+ _sdkCache4 = await importBundledPiSdk();
83644
+ return _sdkCache4;
83645
+ } catch (err) {
83646
+ _sdkLoadError4 = err;
83647
+ throw err;
83648
+ }
83649
+ }
83650
+ function resolveModel2(modelRegistry, model) {
83651
+ if (model) {
83652
+ const slash = model.indexOf("/");
83653
+ if (slash === -1) {
83654
+ return modelRegistry.getAll().find((entry) => entry.id === model);
83655
+ }
83656
+ const provider = model.slice(0, slash);
83657
+ const modelId = model.slice(slash + 1);
83658
+ return modelRegistry.find(provider, modelId);
83659
+ }
83660
+ return modelRegistry.getAvailable()[0];
83661
+ }
83662
+
83663
+ class PiSdkHarness {
83664
+ type = "pi-sdk";
83665
+ displayName = "Pi (SDK)";
83666
+ cwd;
83667
+ closed = false;
83668
+ sessions = new Map;
83669
+ modelRegistry;
83670
+ authStorage;
83671
+ constructor(cwd, modelRegistry, authStorage) {
83672
+ this.cwd = cwd;
83673
+ this.modelRegistry = modelRegistry;
83674
+ this.authStorage = authStorage;
83675
+ }
83676
+ async models() {
83677
+ return this.modelRegistry.getAvailable().map((entry) => ({
83678
+ id: `${entry.provider}/${entry.id}`,
83679
+ name: entry.id,
83680
+ provider: entry.provider
83681
+ }));
83682
+ }
83683
+ async listAgents() {
83684
+ return [{ name: "builder", mode: "primary" }];
83685
+ }
83686
+ async listProviders() {
83687
+ const byProvider = new Map;
83688
+ for (const entry of this.modelRegistry.getAvailable()) {
83689
+ const models = byProvider.get(entry.provider) ?? [];
83690
+ models.push({ modelID: entry.id, name: entry.id });
83691
+ byProvider.set(entry.provider, models);
83692
+ }
83693
+ return [...byProvider.entries()].map(([providerID, models]) => ({
83694
+ providerID,
83695
+ name: providerID,
83696
+ models
83697
+ }));
83698
+ }
83699
+ async newSession(config3) {
83700
+ if (this.closed)
83701
+ throw new Error("Harness is closed");
83702
+ const session2 = await this.createAgentSession(config3.systemPrompt ?? "", config3.model);
83703
+ const piSession = new PiSdkSession({
83704
+ session: session2,
83705
+ opencodeSessionId: session2.sessionId,
83706
+ sessionTitle: config3.title ?? "",
83707
+ onClose: (id3) => this.sessions.delete(id3)
83708
+ });
83709
+ this.sessions.set(session2.sessionId, piSession);
83710
+ return piSession;
83711
+ }
83712
+ async resumeSession(sessionId, _options) {
83713
+ if (this.closed)
83714
+ throw new Error("Harness is closed");
83715
+ const existing = this.sessions.get(sessionId);
83716
+ if (existing)
83717
+ return existing;
83718
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk4();
83719
+ const sessions = await SessionManager.list(this.cwd, getPiSessionDir(this.cwd));
83720
+ const match17 = sessions.find((s) => s.id === sessionId);
83721
+ if (!match17) {
83722
+ throw new Error(`Session ${sessionId} not found on the harness`);
83723
+ }
83724
+ const resourceLoader = new DefaultResourceLoader({
83725
+ cwd: this.cwd,
83726
+ agentDir: getAgentDir(),
83727
+ systemPromptOverride: () => ""
83728
+ });
83729
+ await resourceLoader.reload();
83730
+ const resolvedModel = resolveModel2(this.modelRegistry);
83731
+ if (!resolvedModel) {
83732
+ throw new Error("No Pi model available");
83733
+ }
83734
+ const { session: session2 } = await withTimeout(createAgentSession({
83735
+ cwd: this.cwd,
83736
+ model: resolvedModel,
83737
+ sessionManager: SessionManager.open(match17.path, getPiSessionDir(this.cwd)),
83738
+ authStorage: this.authStorage,
83739
+ modelRegistry: this.modelRegistry,
83740
+ resourceLoader
83741
+ }), SESSION_CREATE_TIMEOUT_MS3, "createAgentSession");
83742
+ const piSession = new PiSdkSession({
83743
+ session: session2,
83744
+ opencodeSessionId: session2.sessionId,
83745
+ sessionTitle: match17.name ?? "",
83746
+ onClose: (id3) => this.sessions.delete(id3)
83747
+ });
83748
+ this.sessions.set(session2.sessionId, piSession);
83749
+ return piSession;
83750
+ }
83751
+ async fetchSessionTitle(_opencodeSessionId) {
83752
+ return;
83753
+ }
83754
+ isAlive() {
83755
+ return !this.closed;
83756
+ }
83757
+ async close() {
83758
+ if (this.closed)
83759
+ return;
83760
+ this.closed = true;
83761
+ const closing = [...this.sessions.values()].map((s) => s.close().catch(() => {}));
83762
+ await Promise.all(closing);
83763
+ this.sessions.clear();
83764
+ }
83765
+ async createAgentSession(systemPrompt, model) {
83766
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk4();
83767
+ const resolvedModel = resolveModel2(this.modelRegistry, model ?? DEFAULT_MODEL3);
83768
+ if (!resolvedModel) {
83769
+ throw new Error("No Pi model available — configure provider credentials in ~/.pi/agent/auth.json");
83770
+ }
83771
+ const resourceLoader = new DefaultResourceLoader({
83772
+ cwd: this.cwd,
83773
+ agentDir: getAgentDir(),
83774
+ systemPromptOverride: () => systemPrompt
83775
+ });
83776
+ await resourceLoader.reload();
83777
+ const { session: session2 } = await withTimeout(createAgentSession({
83778
+ cwd: this.cwd,
83779
+ model: resolvedModel,
83780
+ sessionManager: SessionManager.create(getPiSessionDir(this.cwd)),
83781
+ authStorage: this.authStorage,
83782
+ modelRegistry: this.modelRegistry,
83783
+ resourceLoader
83784
+ }), SESSION_CREATE_TIMEOUT_MS3, "createAgentSession");
83785
+ return session2;
83786
+ }
83365
83787
  }
83788
+ var SESSION_CREATE_TIMEOUT_MS3 = 60000, DEFAULT_MODEL3 = "opencode/big-pickle", _sdkCache4, _sdkLoadError4, startPiSdkHarness = async (config3) => {
83789
+ try {
83790
+ const { AuthStorage, ModelRegistry } = await loadSdk4();
83791
+ const authStorage = AuthStorage.create();
83792
+ const modelRegistry = ModelRegistry.create(authStorage);
83793
+ if (modelRegistry.getAvailable().length === 0) {
83794
+ throw new Error("No Pi models available");
83795
+ }
83796
+ return new PiSdkHarness(config3.workingDir, modelRegistry, authStorage);
83797
+ } catch (err) {
83798
+ throw new Error(`pi-sdk unavailable: ${formatPiSdkLoadError(err)}`);
83799
+ }
83800
+ };
83801
+ var init_pi_harness = __esm(() => {
83802
+ init_pi_session();
83803
+ init_pi_agent_service();
83804
+ init_pi_sdk_package();
83805
+ });
83806
+
83807
+ // src/infrastructure/harnesses/pi-sdk/event-extractor.ts
83808
+ function createPiSdkChunkExtractor() {
83809
+ return createStandardSdkChunkExtractor();
83810
+ }
83811
+ var init_event_extractor2 = () => {};
83812
+
83813
+ // src/infrastructure/harnesses/pi-sdk/index.ts
83814
+ var init_pi_sdk2 = __esm(() => {
83815
+ init_pi_harness();
83816
+ init_pi_session();
83817
+ init_event_extractor2();
83818
+ });
83366
83819
 
83367
- // src/infrastructure/harnesses/opencode-sdk/index.ts
83368
- var init_opencode_sdk2 = __esm(() => {
83820
+ // src/infrastructure/harnesses/registry.ts
83821
+ import { execSync as execSync2 } from "node:child_process";
83822
+ async function startBoundHarness(config3) {
83823
+ switch (config3.harnessName) {
83824
+ case "opencode-sdk":
83825
+ return startOpencodeSdkHarness(config3);
83826
+ case "cursor-sdk":
83827
+ return startCursorSdkHarness(config3);
83828
+ case "pi-sdk":
83829
+ return startPiSdkHarness(config3);
83830
+ default: {
83831
+ const _exhaustive = config3.harnessName;
83832
+ throw new Error(`Unsupported direct harness: ${String(_exhaustive)}`);
83833
+ }
83834
+ }
83835
+ }
83836
+ function createChunkExtractor(harnessName) {
83837
+ switch (harnessName) {
83838
+ case "opencode-sdk":
83839
+ return createOpencodeSdkChunkExtractor();
83840
+ case "cursor-sdk":
83841
+ return createCursorSdkChunkExtractor();
83842
+ case "pi-sdk":
83843
+ return createPiSdkChunkExtractor();
83844
+ default:
83845
+ return createStandardSdkChunkExtractor();
83846
+ }
83847
+ }
83848
+ function opencodeOnPath() {
83849
+ try {
83850
+ execSync2("opencode --version", { stdio: "pipe" });
83851
+ return true;
83852
+ } catch {
83853
+ return false;
83854
+ }
83855
+ }
83856
+ async function isCursorSdkInstalled() {
83857
+ if (!process.env.CURSOR_API_KEY?.trim())
83858
+ return false;
83859
+ try {
83860
+ const { importBundledCursorSdk: importBundledCursorSdk2 } = await Promise.resolve().then(() => (init_cursor_sdk_package(), exports_cursor_sdk_package));
83861
+ await importBundledCursorSdk2();
83862
+ return true;
83863
+ } catch {
83864
+ return false;
83865
+ }
83866
+ }
83867
+ async function isPiSdkInstalled() {
83868
+ try {
83869
+ const { importBundledPiSdk: importBundledPiSdk2 } = await Promise.resolve().then(() => (init_pi_sdk_package(), exports_pi_sdk_package));
83870
+ const { ModelRegistry, AuthStorage } = await importBundledPiSdk2();
83871
+ const authStorage = AuthStorage.create();
83872
+ const modelRegistry = ModelRegistry.create(authStorage);
83873
+ return modelRegistry.getAvailable().length > 0;
83874
+ } catch {
83875
+ return false;
83876
+ }
83877
+ }
83878
+ async function listInstalledNativeDirectHarnesses() {
83879
+ const installed2 = [];
83880
+ if (opencodeOnPath())
83881
+ installed2.push("opencode-sdk");
83882
+ if (await isCursorSdkInstalled())
83883
+ installed2.push("cursor-sdk");
83884
+ if (await isPiSdkInstalled())
83885
+ installed2.push("pi-sdk");
83886
+ return installed2;
83887
+ }
83888
+ var init_registry4 = __esm(() => {
83889
+ init_cursor_sdk2();
83369
83890
  init_opencode_harness();
83891
+ init_pi_sdk2();
83892
+ });
83893
+
83894
+ // src/commands/machine/daemon-start/direct-harness/command-subscriber.ts
83895
+ function startCommandSubscriber(session2, wsClient2, deps) {
83896
+ const processed = new Set;
83897
+ let processing = false;
83898
+ let pendingDrain = false;
83899
+ const runDrain = () => {
83900
+ if (processing) {
83901
+ pendingDrain = true;
83902
+ return;
83903
+ }
83904
+ processing = true;
83905
+ pendingDrain = false;
83906
+ drain(session2, deps, processed).finally(() => {
83907
+ processing = false;
83908
+ if (pendingDrain)
83909
+ runDrain();
83910
+ });
83911
+ };
83912
+ const unsub = wsClient2.onUpdate(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId }, () => runDrain(), (err) => {
83913
+ console.warn("[direct-harness] Command subscription error:", err instanceof Error ? err.message : String(err));
83914
+ });
83915
+ return { stop: unsub };
83916
+ }
83917
+ async function dispatchPendingCommand(session2, deps, cmd) {
83918
+ switch (cmd.type) {
83919
+ case "refreshCapabilities":
83920
+ await handleRefreshCapabilities(session2, deps, cmd);
83921
+ break;
83922
+ case "refreshSessionTitle":
83923
+ await handleRefreshSessionTitle(session2, deps, cmd);
83924
+ break;
83925
+ case "closeSession":
83926
+ await handleCloseSession(session2, deps, cmd);
83927
+ break;
83928
+ default:
83929
+ await markFailed(session2, cmd._id, `Unknown command type: ${cmd.type}`);
83930
+ }
83931
+ }
83932
+ async function processPendingCommand(session2, deps, cmd, now) {
83933
+ if (now - cmd.createdAt > DIRECT_HARNESS_COMMAND_TTL_MS) {
83934
+ console.log(`[direct-harness] Discarding stale command ${cmd._id} (type=${cmd.type}, age=${now - cmd.createdAt}ms)`);
83935
+ await markFailed(session2, cmd._id, "Command expired (TTL)");
83936
+ return;
83937
+ }
83938
+ await dispatchPendingCommand(session2, deps, cmd);
83939
+ }
83940
+ async function drain(session2, deps, processed) {
83941
+ const pending3 = await session2.backend.query(api.daemon.directHarness.commands.listPendingCommands, { sessionId: session2.sessionId, machineId: session2.machineId });
83942
+ if (!pending3 || pending3.length === 0)
83943
+ return;
83944
+ const now = Date.now();
83945
+ for (const cmd of pending3) {
83946
+ if (processed.has(cmd._id))
83947
+ continue;
83948
+ processed.add(cmd._id);
83949
+ try {
83950
+ await processPendingCommand(session2, deps, cmd, now);
83951
+ } catch (err) {
83952
+ const message = err instanceof Error ? err.message : String(err);
83953
+ console.warn(`[direct-harness] Command ${cmd._id} failed: ${message}`);
83954
+ await markFailed(session2, cmd._id, message).catch(() => {});
83955
+ }
83956
+ }
83957
+ }
83958
+ async function handleRefreshCapabilities(session2, deps, cmd) {
83959
+ const payload = cmd.refreshCapabilities;
83960
+ console.log(`[direct-harness] Processing refreshCapabilities for workspace=${cmd.workspaceId}` + (payload ? ` (initiatedBy=${payload.initiatedBy})` : ""));
83961
+ const installed2 = await listInstalledNativeDirectHarnesses();
83962
+ const harnessEntries = [];
83963
+ let cwd = "";
83964
+ for (const harnessName of installed2) {
83965
+ const harness = await deps.lifecycleManager.getOrStart(cmd.workspaceId, harnessName);
83966
+ cwd = harness.cwd;
83967
+ const [agents, providers] = await Promise.all([harness.listAgents(), harness.listProviders()]);
83968
+ harnessEntries.push({
83969
+ name: harness.type,
83970
+ displayName: harness.displayName,
83971
+ agents: [...agents],
83972
+ providers: [...providers]
83973
+ });
83974
+ }
83975
+ const existing = await session2.backend.query(api.daemon.directHarness.capabilities.getForMachine, { sessionId: session2.sessionId, machineId: session2.machineId });
83976
+ const updatedWorkspace = {
83977
+ workspaceId: cmd.workspaceId,
83978
+ cwd,
83979
+ name: cwd,
83980
+ harnesses: harnessEntries
83981
+ };
83982
+ const merged = [
83983
+ ...(existing?.workspaces ?? []).filter((ws) => ws.workspaceId !== cmd.workspaceId),
83984
+ updatedWorkspace
83985
+ ];
83986
+ await deps.publisher.publish({
83987
+ machineId: session2.machineId,
83988
+ lastSeenAt: Date.now(),
83989
+ workspaces: merged
83990
+ });
83991
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
83992
+ sessionId: session2.sessionId,
83993
+ commandId: cmd._id,
83994
+ status: "done"
83995
+ });
83996
+ console.log(`[direct-harness] Capabilities refreshed for workspace=${cmd.workspaceId}`);
83997
+ }
83998
+ async function handleRefreshSessionTitle(session2, deps, cmd) {
83999
+ const { harnessSessionId } = cmd.refreshSessionTitle ?? {};
84000
+ if (!harnessSessionId) {
84001
+ await markFailed(session2, cmd._id, "refreshSessionTitle: missing harnessSessionId");
84002
+ return;
84003
+ }
84004
+ const sessionRow = await session2.backend.query(api.daemon.directHarness.sessions.getSession, {
84005
+ harnessSessionId
84006
+ });
84007
+ if (!sessionRow?.opencodeSessionId) {
84008
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84009
+ sessionId: session2.sessionId,
84010
+ commandId: cmd._id,
84011
+ status: "done"
84012
+ });
84013
+ return;
84014
+ }
84015
+ const harnessName = sessionRow.harnessName ?? "opencode-sdk";
84016
+ const harness = await deps.lifecycleManager.getOrStart(sessionRow.workspaceId ?? cmd.workspaceId, harnessName);
84017
+ const newTitle = await harness.fetchSessionTitle(sessionRow.opencodeSessionId);
84018
+ if (newTitle) {
84019
+ await session2.backend.mutation(api.daemon.directHarness.sessions.updateSessionTitle, {
84020
+ sessionId: session2.sessionId,
84021
+ harnessSessionId,
84022
+ sessionTitle: newTitle
84023
+ });
84024
+ console.log(`[direct-harness] Refreshed title for session ${harnessSessionId}: "${newTitle}"`);
84025
+ }
84026
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84027
+ sessionId: session2.sessionId,
84028
+ commandId: cmd._id,
84029
+ status: "done"
84030
+ });
84031
+ }
84032
+ async function closeDisconnectedHarnessSession(deps, sessionRow, harnessSessionId) {
84033
+ if (!sessionRow.opencodeSessionId) {
84034
+ await deps.sessionRepository.markClosed(harnessSessionId);
84035
+ return;
84036
+ }
84037
+ const harnessName = sessionRow.harnessName ?? "opencode-sdk";
84038
+ const harness = await deps.lifecycleManager.getOrStart(sessionRow.workspaceId ?? "", harnessName);
84039
+ const liveSession = await harness.resumeSession(sessionRow.opencodeSessionId, {
84040
+ harnessSessionId
84041
+ });
84042
+ await liveSession.close();
84043
+ await deps.sessionRepository.markClosed(harnessSessionId);
84044
+ }
84045
+ async function closeHarnessSession(session2, deps, harnessSessionId) {
84046
+ const sessionRow = await session2.backend.query(api.daemon.directHarness.sessions.getSession, {
84047
+ harnessSessionId
84048
+ });
84049
+ if (!sessionRow) {
84050
+ throw new Error(`Harness session ${harnessSessionId} not found`);
84051
+ }
84052
+ if (sessionRow.status === "closed" || sessionRow.status === "failed") {
84053
+ return;
84054
+ }
84055
+ const handle = deps.activeSessions.get(harnessSessionId);
84056
+ if (handle) {
84057
+ await handle.close();
84058
+ deps.activeSessions.delete(harnessSessionId);
84059
+ return;
84060
+ }
84061
+ await closeDisconnectedHarnessSession(deps, sessionRow, harnessSessionId);
84062
+ }
84063
+ async function handleCloseSession(session2, deps, cmd) {
84064
+ const { harnessSessionId } = cmd.closeSession ?? {};
84065
+ if (!harnessSessionId) {
84066
+ await markFailed(session2, cmd._id, "closeSession: missing harnessSessionId");
84067
+ return;
84068
+ }
84069
+ console.log(`[direct-harness] Processing closeSession for harnessSessionId=${harnessSessionId}`);
84070
+ await closeHarnessSession(session2, deps, harnessSessionId);
84071
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84072
+ sessionId: session2.sessionId,
84073
+ commandId: cmd._id,
84074
+ status: "done"
84075
+ });
84076
+ console.log(`[direct-harness] Closed session ${harnessSessionId}`);
84077
+ }
84078
+ async function markFailed(session2, commandId, errorMessage) {
84079
+ await session2.backend.mutation(api.daemon.directHarness.commands.updateCommandStatus, {
84080
+ sessionId: session2.sessionId,
84081
+ commandId,
84082
+ status: "failed",
84083
+ errorMessage
84084
+ });
84085
+ }
84086
+ var DIRECT_HARNESS_COMMAND_TTL_MS = 60000;
84087
+ var init_command_subscriber = __esm(() => {
84088
+ init_api3();
84089
+ init_registry4();
83370
84090
  });
83371
84091
 
84092
+ // src/infrastructure/harnesses/harness-key.ts
84093
+ function makeHarnessKey(workspaceId, harnessName) {
84094
+ return `${workspaceId}:${harnessName}`;
84095
+ }
84096
+ function parseHarnessKey(key) {
84097
+ const idx = key.indexOf(":");
84098
+ if (idx === -1)
84099
+ throw new Error(`Invalid harness key: ${key}`);
84100
+ return { workspaceId: key.slice(0, idx), harnessName: key.slice(idx + 1) };
84101
+ }
84102
+
83372
84103
  // src/commands/machine/daemon-start/direct-harness/harness-lifecycle-manager.ts
83373
84104
  class HarnessLifecycleManager {
83374
84105
  harnesses;
@@ -83383,10 +84114,15 @@ class HarnessLifecycleManager {
83383
84114
  this.resolveWorkspace = resolveWorkspace;
83384
84115
  this.resolvedConvexUrl = resolvedConvexUrl;
83385
84116
  }
83386
- getOrStartHarnessEffect(workspaceId) {
83387
- const existing = this.harnesses.get(workspaceId);
83388
- if (existing)
84117
+ getOrStartHarnessEffect(workspaceId, harnessName) {
84118
+ const key = makeHarnessKey(workspaceId, harnessName);
84119
+ const existing = this.harnesses.get(key);
84120
+ if (existing?.isAlive())
83389
84121
  return exports_Effect.succeed(existing);
84122
+ if (existing) {
84123
+ existing.close().catch(() => {});
84124
+ this.harnesses.delete(key);
84125
+ }
83390
84126
  return exports_Effect.gen(this, function* () {
83391
84127
  const workspace = yield* exports_Effect.tryPromise({
83392
84128
  try: () => this.resolveWorkspace(workspaceId),
@@ -83395,23 +84131,23 @@ class HarnessLifecycleManager {
83395
84131
  if (!workspace) {
83396
84132
  return yield* exports_Effect.fail(new Error(`Workspace ${workspaceId} not found — cannot start harness`));
83397
84133
  }
83398
- console.log(`[${formatTimestamp()}] \uD83D\uDD27 Auto-starting harness for workspace=${workspaceId} (${workspace.workingDir})`);
84134
+ console.log(`[${formatTimestamp()}] \uD83D\uDD27 Auto-starting ${harnessName} for workspace=${workspaceId} (${workspace.workingDir})`);
83399
84135
  const harness = yield* exports_Effect.tryPromise({
83400
- try: () => startOpencodeSdkHarness({
83401
- type: "opencode",
84136
+ try: () => startBoundHarness({
84137
+ harnessName,
83402
84138
  workingDir: workspace.workingDir,
83403
84139
  workspaceId,
83404
84140
  resolvedConvexUrl: this.resolvedConvexUrl
83405
84141
  }),
83406
84142
  catch: (e) => e instanceof Error ? e : new Error(String(e))
83407
84143
  });
83408
- this.harnesses.set(workspaceId, harness);
83409
- this.idleSince.set(workspaceId, Date.now());
84144
+ this.harnesses.set(key, harness);
84145
+ this.idleSince.set(key, Date.now());
83410
84146
  return harness;
83411
84147
  });
83412
84148
  }
83413
- async getOrStart(workspaceId) {
83414
- return exports_Effect.runPromise(this.getOrStartHarnessEffect(workspaceId));
84149
+ async getOrStart(workspaceId, harnessName) {
84150
+ return exports_Effect.runPromise(this.getOrStartHarnessEffect(workspaceId, harnessName));
83415
84151
  }
83416
84152
  startMonitoring() {
83417
84153
  if (this.monitorTimer)
@@ -83427,28 +84163,30 @@ class HarnessLifecycleManager {
83427
84163
  }
83428
84164
  checkInactivity() {
83429
84165
  const now = Date.now();
83430
- for (const workspaceId of this.harnesses.keys()) {
83431
- const hasActiveSessions = this.hasSessionsForWorkspace(workspaceId);
84166
+ for (const key of this.harnesses.keys()) {
84167
+ const { workspaceId, harnessName } = parseHarnessKey(key);
84168
+ const hasActiveSessions = this.hasSessionsForHarnessKey(workspaceId, harnessName);
83432
84169
  if (hasActiveSessions) {
83433
- this.idleSince.delete(workspaceId);
84170
+ this.idleSince.delete(key);
83434
84171
  } else {
83435
- const idleStart = this.idleSince.get(workspaceId);
84172
+ const idleStart = this.idleSince.get(key);
83436
84173
  if (idleStart === undefined) {
83437
- this.idleSince.set(workspaceId, now);
84174
+ this.idleSince.set(key, now);
83438
84175
  } else if (now - idleStart >= INACTIVITY_TTL_MS) {
83439
84176
  const idleMinutes = Math.round((now - idleStart) / 60000);
83440
- console.log(`[${formatTimestamp()}] \uD83D\uDD2A Killing idle harness for workspace=${workspaceId} (idle ${idleMinutes}min)`);
83441
- this.harnesses.get(workspaceId)?.close().catch(() => {});
83442
- this.harnesses.delete(workspaceId);
83443
- this.idleSince.delete(workspaceId);
84177
+ console.log(`[${formatTimestamp()}] \uD83D\uDD2A Killing idle harness ${harnessName} for workspace=${workspaceId} (idle ${idleMinutes}min)`);
84178
+ this.harnesses.get(key)?.close().catch(() => {});
84179
+ this.harnesses.delete(key);
84180
+ this.idleSince.delete(key);
83444
84181
  }
83445
84182
  }
83446
84183
  }
83447
84184
  }
83448
- hasSessionsForWorkspace(workspaceId) {
84185
+ hasSessionsForHarnessKey(workspaceId, harnessName) {
83449
84186
  for (const handle of this.activeSessions.values()) {
83450
- if (handle.workspaceId === workspaceId)
84187
+ if (handle.workspaceId === workspaceId && handle.harnessName === harnessName) {
83451
84188
  return true;
84189
+ }
83452
84190
  }
83453
84191
  return false;
83454
84192
  }
@@ -83456,7 +84194,7 @@ class HarnessLifecycleManager {
83456
84194
  var INACTIVITY_TTL_MS, CHECK_INTERVAL_MS;
83457
84195
  var init_harness_lifecycle_manager = __esm(() => {
83458
84196
  init_esm();
83459
- init_opencode_sdk2();
84197
+ init_registry4();
83460
84198
  INACTIVITY_TTL_MS = 15 * 60 * 1000;
83461
84199
  CHECK_INTERVAL_MS = 60 * 1000;
83462
84200
  });
@@ -83551,6 +84289,7 @@ async function resumeSession(deps, input) {
83551
84289
  let closed = false;
83552
84290
  const handle = {
83553
84291
  harnessSessionId,
84292
+ harnessName: input.harnessName,
83554
84293
  opencodeSessionId,
83555
84294
  workspaceId: input.workspaceId ?? "",
83556
84295
  session: session2,
@@ -83615,14 +84354,16 @@ async function resumeSessionHandle(session2, deps, rowId, info) {
83615
84354
  return null;
83616
84355
  }
83617
84356
  const workspaceId = info.workspaceId;
84357
+ const harnessName = info.harnessName;
83618
84358
  if (!workspaceId) {
83619
84359
  console.warn(`[direct-harness] Cannot resume session ${rowId}: no workspace info`);
83620
84360
  return null;
83621
84361
  }
83622
- let harness = deps.harnesses.get(workspaceId);
84362
+ const key = makeHarnessKey(workspaceId, harnessName);
84363
+ let harness = deps.harnesses.get(key);
83623
84364
  if (harness && !harness.isAlive()) {
83624
84365
  harness.close().catch(() => {});
83625
- deps.harnesses.delete(workspaceId);
84366
+ deps.harnesses.delete(key);
83626
84367
  harness = undefined;
83627
84368
  }
83628
84369
  if (!harness) {
@@ -83634,20 +84375,20 @@ async function resumeSessionHandle(session2, deps, rowId, info) {
83634
84375
  console.warn(`[direct-harness] Cannot resume session ${rowId}: workspace not found`);
83635
84376
  return null;
83636
84377
  }
83637
- harness = await startOpencodeSdkHarness({
83638
- type: "opencode",
84378
+ harness = await startBoundHarness({
84379
+ harnessName,
83639
84380
  workingDir: workspace.workingDir,
83640
84381
  workspaceId,
83641
84382
  resolvedConvexUrl: session2.convexUrl
83642
84383
  });
83643
- deps.harnesses.set(workspaceId, harness);
84384
+ deps.harnesses.set(key, harness);
83644
84385
  }
83645
84386
  try {
83646
84387
  return await resumeSession({
83647
84388
  harness,
83648
84389
  journalFactory: deps.journalFactory,
83649
- chunkExtractor: createOpencodeSdkChunkExtractor()
83650
- }, { harnessSessionId: rowId, opencodeSessionId, workspaceId });
84390
+ chunkExtractor: createChunkExtractor(harness.type)
84391
+ }, { harnessSessionId: rowId, opencodeSessionId, workspaceId, harnessName });
83651
84392
  } catch (err) {
83652
84393
  const message = err instanceof Error ? err.message : String(err);
83653
84394
  console.warn(`[direct-harness] Cannot resume session ${rowId}: ${message}`);
@@ -83693,8 +84434,9 @@ async function deliverPendingMessages(handle, deps, rowId, messages, info) {
83693
84434
  console.warn(`[direct-harness] Prompt failed for session ${rowId} seq=${msg.seq}: ${message}`);
83694
84435
  await deps.sessionRepository.markIdle(rowId).catch(() => {});
83695
84436
  deps.activeSessions.delete(rowId);
83696
- if (info?.workspaceId)
83697
- deps.harnesses.delete(info.workspaceId);
84437
+ if (info?.workspaceId && info.harnessName) {
84438
+ deps.harnesses.delete(makeHarnessKey(info.workspaceId, info.harnessName));
84439
+ }
83698
84440
  return;
83699
84441
  }
83700
84442
  }
@@ -83721,7 +84463,7 @@ var init_prompt_subscriber = __esm(() => {
83721
84463
  init_idle_handler();
83722
84464
  init_api3();
83723
84465
  init_resume_session();
83724
- init_opencode_sdk2();
84466
+ init_registry4();
83725
84467
  });
83726
84468
 
83727
84469
  // src/commands/machine/daemon-start/direct-harness/session-subscriber.ts
@@ -83746,21 +84488,23 @@ function startSessionSubscriber(daemonSession, wsClient2, deps) {
83746
84488
  return { stop: unsub };
83747
84489
  }
83748
84490
  async function getOrCreateHarness(daemonSession, deps, session2, workspace) {
83749
- let harness = deps.harnesses.get(session2.workspaceId);
84491
+ const harnessName = session2.opencode?.harnessName ?? "opencode-sdk";
84492
+ const key = makeHarnessKey(session2.workspaceId, harnessName);
84493
+ let harness = deps.harnesses.get(key);
83750
84494
  if (harness && !harness.isAlive()) {
83751
- console.warn(`[direct-harness] Harness for workspace ${session2.workspaceId} is no longer alive — restarting`);
84495
+ console.warn(`[direct-harness] Harness ${harnessName} for workspace ${session2.workspaceId} is no longer alive — restarting`);
83752
84496
  harness.close().catch(() => {});
83753
- deps.harnesses.delete(session2.workspaceId);
84497
+ deps.harnesses.delete(key);
83754
84498
  harness = undefined;
83755
84499
  }
83756
84500
  if (!harness) {
83757
- harness = await startOpencodeSdkHarness({
83758
- type: "opencode",
84501
+ harness = await startBoundHarness({
84502
+ harnessName,
83759
84503
  workingDir: workspace.workingDir,
83760
84504
  workspaceId: session2.workspaceId,
83761
84505
  resolvedConvexUrl: daemonSession.convexUrl
83762
84506
  });
83763
- deps.harnesses.set(session2.workspaceId, harness);
84507
+ deps.harnesses.set(key, harness);
83764
84508
  }
83765
84509
  return harness;
83766
84510
  }
@@ -83812,13 +84556,17 @@ async function processOne(daemonSession, deps, session2) {
83812
84556
  await deps.sessionRepository.markFailed(rowId);
83813
84557
  return;
83814
84558
  }
84559
+ const harnessName = session2.opencode?.harnessName ?? "opencode-sdk";
83815
84560
  const harness = await getOrCreateHarness(daemonSession, deps, session2, workspace);
84561
+ const modelConfig = session2.opencode?.lastUsedConfig.model;
84562
+ const model = modelConfig ? `${modelConfig.providerID}/${modelConfig.modelID}` : undefined;
83816
84563
  const liveSession = await harness.newSession({
83817
84564
  agent: session2.opencode?.lastUsedConfig.agent ?? "build",
84565
+ model,
83818
84566
  harnessSessionId: rowId
83819
84567
  });
83820
84568
  const journal = deps.journalFactory.create(rowId);
83821
- const extractChunk = createOpencodeSdkChunkExtractor();
84569
+ const extractChunk = createChunkExtractor(harness.type);
83822
84570
  const idleConfig = {
83823
84571
  agent: session2.opencode?.lastUsedConfig.agent ?? "build",
83824
84572
  model: session2.opencode?.lastUsedConfig.model
@@ -83833,9 +84581,11 @@ async function processOne(daemonSession, deps, session2) {
83833
84581
  await journal.commit();
83834
84582
  await liveSession.close();
83835
84583
  await deps.sessionRepository.markClosed(rowId);
84584
+ deps.activeSessions.delete(rowId);
83836
84585
  };
83837
84586
  const handle = {
83838
84587
  harnessSessionId: rowId,
84588
+ harnessName,
83839
84589
  opencodeSessionId: liveSession.opencodeSessionId,
83840
84590
  workspaceId: session2.workspaceId,
83841
84591
  session: liveSession,
@@ -83873,7 +84623,61 @@ async function processOne(daemonSession, deps, session2) {
83873
84623
  var init_session_subscriber = __esm(() => {
83874
84624
  init_idle_handler();
83875
84625
  init_api3();
83876
- init_opencode_sdk2();
84626
+ init_registry4();
84627
+ });
84628
+
84629
+ // src/commands/machine/daemon-start/direct-harness/shutdown-sessions.ts
84630
+ async function listHarnessSessionIdsForShutdown(session2) {
84631
+ const ids3 = new Set;
84632
+ const managed = await session2.backend.query(api.daemon.directHarness.turns.getMachineHarnessSessions, {
84633
+ sessionId: session2.sessionId,
84634
+ machineId: session2.machineId
84635
+ });
84636
+ for (const row of managed) {
84637
+ ids3.add(row.harnessSessionId);
84638
+ }
84639
+ const pending3 = await session2.backend.query(api.daemon.directHarness.sessions.listPendingSessionsForMachine, {
84640
+ sessionId: session2.sessionId,
84641
+ machineId: session2.machineId
84642
+ });
84643
+ for (const row of pending3) {
84644
+ ids3.add(row._id);
84645
+ }
84646
+ return [...ids3];
84647
+ }
84648
+ async function closeAllMachineHarnessSessionsOnShutdown(session2, deps) {
84649
+ const commandDeps = {
84650
+ lifecycleManager: deps.lifecycleManager,
84651
+ publisher: {},
84652
+ activeSessions: deps.activeSessions,
84653
+ sessionRepository: deps.sessionRepository
84654
+ };
84655
+ const harnessSessionIds = await listHarnessSessionIdsForShutdown(session2);
84656
+ if (harnessSessionIds.length === 0)
84657
+ return;
84658
+ console.log(`[direct-harness] Closing ${harnessSessionIds.length} harness session(s) on daemon shutdown...`);
84659
+ for (const harnessSessionId of harnessSessionIds) {
84660
+ try {
84661
+ await session2.backend.mutation(api.daemon.directHarness.turns.markOrphanTurnsFailed, {
84662
+ sessionId: session2.sessionId,
84663
+ machineId: session2.machineId,
84664
+ harnessSessionId
84665
+ });
84666
+ } catch {}
84667
+ try {
84668
+ await closeHarnessSession(session2, commandDeps, harnessSessionId);
84669
+ } catch (err) {
84670
+ const message = err instanceof Error ? err.message : String(err);
84671
+ console.warn(`[direct-harness] Failed to close session ${harnessSessionId} on shutdown: ${message}`);
84672
+ try {
84673
+ await deps.sessionRepository.markClosed(harnessSessionId);
84674
+ } catch {}
84675
+ }
84676
+ }
84677
+ }
84678
+ var init_shutdown_sessions = __esm(() => {
84679
+ init_command_subscriber();
84680
+ init_api3();
83877
84681
  });
83878
84682
 
83879
84683
  // src/infrastructure/repos/convex-capabilities-publisher.ts
@@ -84164,13 +84968,20 @@ function startDirectHarnessSubscriptions(session2, wsClient2, activeSessions, ha
84164
84968
  publisher: new ConvexCapabilitiesPublisher({
84165
84969
  backend: session2.backend,
84166
84970
  sessionId: session2.sessionId
84167
- })
84971
+ }),
84972
+ activeSessions,
84973
+ sessionRepository
84168
84974
  });
84169
84975
  return {
84170
84976
  pendingPromptSubscriptionHandle,
84171
84977
  pendingHarnessSessionSubscriptionHandle,
84172
84978
  commandSubscriptionHandle,
84173
- lifecycleManager
84979
+ lifecycleManager,
84980
+ closeSessionsOnShutdown: () => closeAllMachineHarnessSessionsOnShutdown(session2, {
84981
+ lifecycleManager,
84982
+ activeSessions,
84983
+ sessionRepository
84984
+ })
84174
84985
  };
84175
84986
  }
84176
84987
  var init_start_subscriptions = __esm(() => {
@@ -84178,6 +84989,7 @@ var init_start_subscriptions = __esm(() => {
84178
84989
  init_harness_lifecycle_manager();
84179
84990
  init_prompt_subscriber();
84180
84991
  init_session_subscriber();
84992
+ init_shutdown_sessions();
84181
84993
  init_api3();
84182
84994
  init_convex_capabilities_publisher();
84183
84995
  init_convex_output_repository();
@@ -84981,6 +85793,9 @@ function isInjectableNativeAction(action) {
84981
85793
  return true;
84982
85794
  return action === NATIVE_WAITING_ACTION;
84983
85795
  }
85796
+ function shouldEmitNativeWaitingOnTurnEnd(lastStatus) {
85797
+ return lastStatus !== "task.acknowledged" && lastStatus !== "task.inProgress";
85798
+ }
84984
85799
  function isNativeIdleAfterTaskComplete(participant) {
84985
85800
  return participant.lastSeenAction === NATIVE_TASK_INJECTED_ACTION && participant.lastStatus === "task.completed";
84986
85801
  }
@@ -85050,13 +85865,21 @@ function shouldDeliverNativeTask(task, opts) {
85050
85865
  return isInjectableNativeAction(task.participant?.lastSeenAction) || isNativeIdleAfterTaskComplete(task.participant ?? {}) || isNativeAcknowledgedInjectionRetry(task);
85051
85866
  }
85052
85867
  function buildNativeInjectionPrompt(params) {
85053
- const { taskDeliveryOutput, compressMode } = params;
85054
- if (compressMode === "new_session") {
85868
+ const { taskDeliveryOutput, augmentationMode } = params;
85869
+ if (augmentationMode === "compact") {
85055
85870
  return [
85056
85871
  "⚠️ Context was compacted. Run `chatroom get-system-prompt` only if role instructions are missing.",
85057
85872
  "",
85058
85873
  taskDeliveryOutput
85059
85874
  ].join(`
85875
+ `);
85876
+ }
85877
+ if (augmentationMode === "new_session") {
85878
+ return [
85879
+ "⚠️ Starting a new agent session. Run `chatroom get-system-prompt` to reload role instructions if needed.",
85880
+ "",
85881
+ taskDeliveryOutput
85882
+ ].join(`
85060
85883
  `);
85061
85884
  }
85062
85885
  return taskDeliveryOutput;
@@ -85066,18 +85889,23 @@ var init_native_task_injector_logic = __esm(() => {
85066
85889
  init_native_integration();
85067
85890
  });
85068
85891
 
85069
- // ../../services/backend/src/domain/handoff/parse-compress-context.ts
85892
+ // ../../services/backend/src/domain/entities/team-agent-settings.ts
85893
+ function roleSupportsSessionAugmentation(role) {
85894
+ const normalized = role.toLowerCase();
85895
+ return SESSION_AUGMENTATION_ROLES.some((r) => r === normalized);
85896
+ }
85897
+ var SESSION_AUGMENTATION_ROLES;
85898
+ var init_team_agent_settings = __esm(() => {
85899
+ SESSION_AUGMENTATION_ROLES = ["builder"];
85900
+ });
85901
+
85902
+ // ../../services/backend/src/domain/handoff/parse-session-augmentation.ts
85070
85903
  function findSectionIndex(content, headings) {
85071
85904
  const indices = headings.map((heading) => content.indexOf(heading)).filter((idx) => idx !== -1);
85072
85905
  return indices.length === 0 ? -1 : Math.min(...indices);
85073
85906
  }
85074
85907
  function normalizeMode(raw) {
85075
- const value = raw.toLowerCase();
85076
- if (value === "reset")
85077
- return "new_session";
85078
- if (value === "none")
85079
- return value;
85080
- return DEFAULT_MODE;
85908
+ return MODE_ALIASES[raw.toLowerCase()] ?? DEFAULT_MODE;
85081
85909
  }
85082
85910
  function extractSectionBody(content, sectionIdx) {
85083
85911
  const matchedHeading = SECTION_HEADINGS.find((h) => content.indexOf(h, sectionIdx) === sectionIdx) ?? SECTION_HEADINGS[0];
@@ -85085,7 +85913,7 @@ function extractSectionBody(content, sectionIdx) {
85085
85913
  const nextHeading = afterSection.slice(matchedHeading.length).search(/\n## /);
85086
85914
  return nextHeading === -1 ? afterSection : afterSection.slice(0, matchedHeading.length + nextHeading);
85087
85915
  }
85088
- function parseCompressContext(handoffContent) {
85916
+ function parseSessionAugmentation(handoffContent) {
85089
85917
  const sectionIdx = findSectionIndex(handoffContent, SECTION_HEADINGS);
85090
85918
  if (sectionIdx === -1)
85091
85919
  return DEFAULT_MODE;
@@ -85094,13 +85922,33 @@ function parseCompressContext(handoffContent) {
85094
85922
  return DEFAULT_MODE;
85095
85923
  return normalizeMode(match17[1]);
85096
85924
  }
85097
- function compressContextToWantResume(mode) {
85098
- return mode === "none";
85925
+ function resolveSessionAugmentationForRole(handoffContent, role) {
85926
+ if (!roleSupportsSessionAugmentation(role)) {
85927
+ return "none";
85928
+ }
85929
+ return parseSessionAugmentation(handoffContent);
85930
+ }
85931
+ function sessionAugmentationToWantResume(mode) {
85932
+ return mode === "none" || mode === "compact";
85933
+ }
85934
+ function sessionAugmentationNewSessionStarted(mode) {
85935
+ return mode === "new_session";
85099
85936
  }
85100
- var SECTION_HEADINGS, DATA_TAG, DEFAULT_MODE = "new_session";
85101
- var init_parse_compress_context = __esm(() => {
85102
- SECTION_HEADINGS = ["## Session Management", "## Restart new context"];
85103
- DATA_TAG = /\/\/\s*data:agent\.compress_context=(new_session|reset|none)\b/i;
85937
+ var SECTION_HEADINGS, DATA_TAG, DEFAULT_MODE = "new_session", MODE_ALIASES;
85938
+ var init_parse_session_augmentation = __esm(() => {
85939
+ init_team_agent_settings();
85940
+ SECTION_HEADINGS = [
85941
+ "## Session Augmentation",
85942
+ "## Session Management",
85943
+ "## Restart new context"
85944
+ ];
85945
+ DATA_TAG = /\/\/\s*data:agent\.(?:session_augmentation|compress_context)=(none|compact|new_session|reset)\b/i;
85946
+ MODE_ALIASES = {
85947
+ reset: "new_session",
85948
+ none: "none",
85949
+ compact: "compact",
85950
+ new_session: "new_session"
85951
+ };
85104
85952
  });
85105
85953
 
85106
85954
  // src/commands/machine/daemon-start/native-task-injector.ts
@@ -85144,9 +85992,10 @@ function runNativeInjectionEffect(task, harnessSessionId, deps, ledger) {
85144
85992
  return yield* exports_Effect.fail(deliveryResult.left);
85145
85993
  }
85146
85994
  const delivery = deliveryResult.right;
85995
+ const augmentationMode = resolveSessionAugmentationForRole(taskContent, role);
85147
85996
  const prompt = buildNativeInjectionPrompt({
85148
85997
  taskDeliveryOutput: delivery.fullCliOutput,
85149
- compressMode: parseCompressContext(taskContent)
85998
+ augmentationMode
85150
85999
  });
85151
86000
  yield* exports_Effect.tryPromise({
85152
86001
  try: () => deps.backend.mutation(api.participants.join, {
@@ -85158,6 +86007,21 @@ function runNativeInjectionEffect(task, harnessSessionId, deps, ledger) {
85158
86007
  }),
85159
86008
  catch: (err) => err
85160
86009
  }).pipe(exports_Effect.tapError(() => exports_Effect.sync(() => ledger.clearDelivery(taskId, harnessSessionId))));
86010
+ if (roleSupportsSessionAugmentation(role)) {
86011
+ yield* exports_Effect.tryPromise({
86012
+ try: () => deps.backend.mutation(api.machines.emitSessionAugmented, {
86013
+ sessionId: deps.sessionId,
86014
+ machineId: deps.machineId,
86015
+ chatroomId,
86016
+ role,
86017
+ taskId,
86018
+ mode: augmentationMode,
86019
+ newSessionStarted: sessionAugmentationNewSessionStarted(augmentationMode),
86020
+ harnessSessionId
86021
+ }),
86022
+ catch: (err) => err
86023
+ }).pipe(exports_Effect.catchAll(() => exports_Effect.void));
86024
+ }
85161
86025
  const resumeResult = yield* exports_Effect.tryPromise({
85162
86026
  try: () => deps.agentMgr.resumeTurnForSlot({ chatroomId, role, prompt }),
85163
86027
  catch: (err) => err
@@ -85172,7 +86036,8 @@ function runNativeInjectionEffect(task, harnessSessionId, deps, ledger) {
85172
86036
  }
85173
86037
  var init_native_task_injector = __esm(() => {
85174
86038
  init_participant();
85175
- init_parse_compress_context();
86039
+ init_team_agent_settings();
86040
+ init_parse_session_augmentation();
85176
86041
  init_esm();
85177
86042
  init_native_task_injector_logic();
85178
86043
  init_api3();
@@ -85191,12 +86056,13 @@ class NativeTaskDeliveryCoordinator {
85191
86056
  }
85192
86057
  }
85193
86058
  reconcileAssignedTasks(params) {
85194
- const { tasks, runtime: runtime4, effectContext: effectContext2, agentMgr, sessionDeps } = params;
85195
- for (const task of tasks) {
85196
- const slot = agentMgr.getSlot(task.chatroomId, task.agentConfig.role);
86059
+ const { tasks, runtime: runtime4, effectContext: effectContext2, agentMgr, sessionDeps, machineId } = params;
86060
+ const ledger = this.ledger;
86061
+ for (const row of tasks) {
86062
+ const slot = agentMgr.getSlot(row.chatroomId, row.agentConfig.role);
85197
86063
  const harnessSessionId = slot?.harnessSessionId;
85198
- if (!shouldDeliverNativeTask(task, {
85199
- ledger: this.ledger,
86064
+ if (!shouldDeliverNativeTask(row, {
86065
+ ledger,
85200
86066
  harnessSessionId
85201
86067
  })) {
85202
86068
  continue;
@@ -85204,14 +86070,25 @@ class NativeTaskDeliveryCoordinator {
85204
86070
  if (!harnessSessionId) {
85205
86071
  continue;
85206
86072
  }
85207
- exports_Runtime.runFork(runtime4)(runNativeInjectionEffect(task, harnessSessionId, {
85208
- sessionId: sessionDeps.sessionId,
85209
- backend: sessionDeps.backend,
85210
- agentMgr: {
85211
- resumeTurnForSlot: (args2) => exports_Effect.runPromise(agentMgr.resumeTurnForSlot(args2))
85212
- },
85213
- convexUrl: sessionDeps.convexUrl
85214
- }, this.ledger).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[NativeTaskDelivery] delivery failed for ${task.agentConfig.role}@${task.chatroomId}: ${getErrorMessage(err)}`)))));
86073
+ exports_Runtime.runFork(runtime4)(exports_Effect.gen(function* () {
86074
+ const full = yield* exports_Effect.tryPromise(() => sessionDeps.backend.query(api.machines.getAssignedTaskForAction, {
86075
+ sessionId: sessionDeps.sessionId,
86076
+ machineId,
86077
+ taskId: row.taskId,
86078
+ role: row.agentConfig.role
86079
+ }));
86080
+ if (!full)
86081
+ return;
86082
+ yield* runNativeInjectionEffect(full, harnessSessionId, {
86083
+ sessionId: sessionDeps.sessionId,
86084
+ machineId: sessionDeps.machineId,
86085
+ backend: sessionDeps.backend,
86086
+ agentMgr: {
86087
+ resumeTurnForSlot: (args2) => exports_Effect.runPromise(agentMgr.resumeTurnForSlot(args2))
86088
+ },
86089
+ convexUrl: sessionDeps.convexUrl
86090
+ }, ledger);
86091
+ }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[NativeTaskDelivery] delivery failed for ${row.agentConfig.role}@${row.chatroomId}: ${getErrorMessage(err)}`)))));
85215
86092
  }
85216
86093
  }
85217
86094
  }
@@ -85227,6 +86104,7 @@ var init_native_task_delivery_coordinator = __esm(() => {
85227
86104
  init_esm();
85228
86105
  init_native_task_injector_logic();
85229
86106
  init_native_task_injector();
86107
+ init_api3();
85230
86108
  init_convex_error();
85231
86109
  });
85232
86110
 
@@ -85401,24 +86279,12 @@ var init_agent_lifecycle = __esm(() => {
85401
86279
  init_restart_decision();
85402
86280
  });
85403
86281
 
85404
- // src/domain/agent-lifecycle/policies/cursor-sdk-run-error.ts
85405
- function isCursorSdkRunErrorInLogs(logLines) {
85406
- return logLines.some((line) => line.includes(" run-error]"));
85407
- }
85408
- function formatCursorSdkRunErrorMessage(logLines) {
85409
- const line = [...logLines].reverse().find((l) => l.includes(" run-error]"));
85410
- return line?.trim() ?? "Cursor SDK run failed";
85411
- }
85412
-
85413
86282
  // src/commands/machine/daemon-start/native-harness-session-exit.ts
85414
86283
  function isNativeHarnessSessionDiscardedOnExit(ctx) {
85415
- const { harness, harnessSessionId, stopReason, recentLogLines, supportsDaemonMemoryResume } = ctx;
86284
+ const { harness, harnessSessionId, stopReason, supportsDaemonMemoryResume } = ctx;
85416
86285
  if (!harness || !harnessSessionId) {
85417
86286
  return false;
85418
86287
  }
85419
- if (isCursorSdkRunErrorInLogs(recentLogLines ?? [])) {
85420
- return true;
85421
- }
85422
86288
  if (!supportsDaemonMemoryResume) {
85423
86289
  return true;
85424
86290
  }
@@ -85557,6 +86423,18 @@ function appendRecentLogLine(slot, line) {
85557
86423
  }
85558
86424
  var RECENT_LOG_LINE_CAP2 = 100;
85559
86425
 
86426
+ // src/domain/agent-lifecycle/policies/cursor-sdk-run-error.ts
86427
+ function isCursorSdkRunErrorInLogs(logLines) {
86428
+ return logLines.some((line) => line.includes(" run-error]"));
86429
+ }
86430
+ function formatCursorSdkRunErrorMessage(logLines) {
86431
+ const line = [...logLines].reverse().find((l) => l.includes(" run-error]"));
86432
+ return line?.trim() ?? "Cursor SDK run failed";
86433
+ }
86434
+
86435
+ // src/domain/agent-lifecycle/policies/cursor-sdk-session-reopen-retry.ts
86436
+ 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", CURSOR_SDK_SESSION_RESUME_FIRST_ATTEMPTS = 3;
86437
+
85560
86438
  // src/domain/agent-lifecycle/use-cases/handle-turn-completed.ts
85561
86439
  async function handleTurnCompleted(deps, input, slot) {
85562
86440
  if (await tryAbortResumeStorm(deps, input, slot)) {
@@ -85956,6 +86834,7 @@ class AgentProcessManager {
85956
86834
  deps;
85957
86835
  slots = new Map;
85958
86836
  lastHarnessSessions = new Map;
86837
+ sessionReopenRetryInFlight = new Set;
85959
86838
  exitRetryQueue = [];
85960
86839
  exitRetryTimer = null;
85961
86840
  turnEndQueue = new TurnEndQueue;
@@ -86164,8 +87043,10 @@ class AgentProcessManager {
86164
87043
  console.log(`[AgentProcessManager] ⛔ Terminal provider error for ${opts.role} — emitted agent.startFailed`);
86165
87044
  return;
86166
87045
  }
86167
- await this.emitNativeWaiting(opts.chatroomId, opts.role, opts.harness);
86168
- console.log(`[AgentProcessManager] ✅ Native harness idle for ${opts.role} (native:waiting)`);
87046
+ const emitted = await this.emitNativeWaiting(opts.chatroomId, opts.role, opts.harness, "turn-end");
87047
+ if (emitted) {
87048
+ console.log(`[AgentProcessManager] ✅ Native harness idle for ${opts.role} (native:waiting)`);
87049
+ }
86169
87050
  }
86170
87051
  async handleExit(opts) {
86171
87052
  const key = agentKey3(opts.chatroomId, opts.role);
@@ -86227,14 +87108,10 @@ class AgentProcessManager {
86227
87108
  });
86228
87109
  }
86229
87110
  async preserveHarnessSessionOnExit(key, slot, ctx) {
86230
- const { harness, harnessSessionId, stopReason, recentLogLines } = ctx;
87111
+ const { harness, harnessSessionId, stopReason } = ctx;
86231
87112
  if (!harness || !harnessSessionId) {
86232
87113
  return;
86233
87114
  }
86234
- if (isCursorSdkRunErrorInLogs(recentLogLines ?? [])) {
86235
- this.clearLastHarnessSession(key);
86236
- return;
86237
- }
86238
87115
  const service3 = this.deps.agentServices.get(harness);
86239
87116
  if (!service3?.resumeFromDaemonMemory) {
86240
87117
  return;
@@ -86284,17 +87161,15 @@ class AgentProcessManager {
86284
87161
  }
86285
87162
  this.maybeRestartAgent(opts, ctx);
86286
87163
  }
86287
- clearHarnessSessionAfterRunError(key, recentLogLines) {
87164
+ hasCursorSdkRunErrorInContext(recentLogLines) {
86288
87165
  if (!isCursorSdkRunErrorInLogs(recentLogLines)) {
86289
87166
  return false;
86290
87167
  }
86291
- console.log(`[AgentProcessManager] cursor-sdk run-error — cold restarting ${key}: ${formatCursorSdkRunErrorMessage(recentLogLines)}`);
86292
- this.clearLastHarnessSession(key);
87168
+ console.log(`[AgentProcessManager] cursor-sdk run-error detected: ${formatCursorSdkRunErrorMessage(recentLogLines)}`);
86293
87169
  return true;
86294
87170
  }
86295
87171
  maybeRestartAgent(opts, ctx) {
86296
87172
  const { harness, model, workingDir, recentLogLines } = ctx;
86297
- const key = agentKey3(opts.chatroomId, opts.role);
86298
87173
  const logs = recentLogLines ?? [];
86299
87174
  if (!harness || !workingDir) {
86300
87175
  console.log(`[AgentProcessManager] ⚠️ Cannot restart — missing harness or workingDir ` + `(role: ${opts.role}, harness: ${harness ?? "none"}, workingDir: ${workingDir ?? "none"})`);
@@ -86304,7 +87179,11 @@ class AgentProcessManager {
86304
87179
  this.handlePermanentFailureForRestart(opts, recentLogLines, ctx.terminalProviderFailureHandled);
86305
87180
  return;
86306
87181
  }
86307
- const coldRestartAfterRunError = this.clearHarnessSessionAfterRunError(key, logs);
87182
+ const hadRunError = this.hasCursorSdkRunErrorInContext(logs);
87183
+ if (harness === "cursor-sdk") {
87184
+ this.retryCursorSdkSessionReopen(opts, ctx, hadRunError);
87185
+ return;
87186
+ }
86308
87187
  this.ensureRunning({
86309
87188
  chatroomId: opts.chatroomId,
86310
87189
  role: opts.role,
@@ -86312,7 +87191,7 @@ class AgentProcessManager {
86312
87191
  model,
86313
87192
  workingDir,
86314
87193
  reason: "platform.crash_recovery",
86315
- wantResume: coldRestartAfterRunError ? false : ctx.wantResume ?? true
87194
+ wantResume: hadRunError ? false : ctx.wantResume ?? true
86316
87195
  }).then((result) => {
86317
87196
  if (!result.success) {
86318
87197
  console.log(`[AgentProcessManager] ⚠️ Agent restart did not complete for ${opts.role}: ${result.error ?? "unknown"}`);
@@ -86322,6 +87201,72 @@ class AgentProcessManager {
86322
87201
  this.emitStartFailedEvent(opts.role, opts.chatroomId, err.message);
86323
87202
  });
86324
87203
  }
87204
+ clearHarnessSessionAfterResumePhaseFailure(key, opts) {
87205
+ const stored = this.lastHarnessSessions.get(key);
87206
+ this.clearLastHarnessSession(key);
87207
+ if (stored?.harnessSessionId) {
87208
+ notifyNativeSessionLost({
87209
+ chatroomId: opts.chatroomId,
87210
+ role: opts.role,
87211
+ harnessSessionId: stored.harnessSessionId
87212
+ });
87213
+ }
87214
+ }
87215
+ resolveCursorSdkReopenWantResume(hadRunError, attempt, ctx) {
87216
+ if (hadRunError && attempt <= CURSOR_SDK_SESSION_RESUME_FIRST_ATTEMPTS) {
87217
+ return true;
87218
+ }
87219
+ if (hadRunError) {
87220
+ return false;
87221
+ }
87222
+ return ctx.wantResume ?? true;
87223
+ }
87224
+ async retryCursorSdkSessionReopen(opts, ctx, hadRunError) {
87225
+ const key = agentKey3(opts.chatroomId, opts.role);
87226
+ if (this.sessionReopenRetryInFlight.has(key)) {
87227
+ return;
87228
+ }
87229
+ this.sessionReopenRetryInFlight.add(key);
87230
+ const harness = ctx.harness;
87231
+ const model = ctx.model;
87232
+ const workingDir = ctx.workingDir;
87233
+ let lastError = "unknown";
87234
+ try {
87235
+ for (let attempt = 1;attempt <= CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS; attempt++) {
87236
+ if (hadRunError && attempt === CURSOR_SDK_SESSION_RESUME_FIRST_ATTEMPTS + 1) {
87237
+ this.clearHarnessSessionAfterResumePhaseFailure(key, opts);
87238
+ }
87239
+ const wantResume = this.resolveCursorSdkReopenWantResume(hadRunError, attempt, ctx);
87240
+ const storedSessionId = this.lastHarnessSessions.get(key)?.harnessSessionId;
87241
+ await this.emitSessionReopenRetry(opts.chatroomId, opts.role, attempt, attempt > 1 ? lastError : undefined, storedSessionId);
87242
+ const result = await this.ensureRunning({
87243
+ chatroomId: opts.chatroomId,
87244
+ role: opts.role,
87245
+ agentHarness: harness,
87246
+ model,
87247
+ workingDir,
87248
+ reason: CURSOR_SDK_SESSION_REOPEN_REASON,
87249
+ wantResume
87250
+ });
87251
+ if (result.success) {
87252
+ return;
87253
+ }
87254
+ lastError = result.error ?? "unknown";
87255
+ if (attempt < CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS) {
87256
+ await this.deps.clock.delay(CURSOR_SDK_SESSION_REOPEN_INTERVAL_MS);
87257
+ }
87258
+ }
87259
+ const failureMessage = `cursor-sdk session reopen failed after ${CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS} attempts: ${lastError}`;
87260
+ console.log(`[AgentProcessManager] ⛔ ${failureMessage}`);
87261
+ this.emitStartFailedEvent(opts.role, opts.chatroomId, failureMessage);
87262
+ } catch (err) {
87263
+ const message = err instanceof Error ? err.message : String(err);
87264
+ console.log(` ⚠️ cursor-sdk session reopen retry loop failed: ${message}`);
87265
+ this.emitStartFailedEvent(opts.role, opts.chatroomId, message);
87266
+ } finally {
87267
+ this.sessionReopenRetryInFlight.delete(key);
87268
+ }
87269
+ }
86325
87270
  handlePermanentFailureForRestart(opts, recentLogLines, startFailedAlreadyEmitted) {
86326
87271
  const error = isTerminalProviderFailureInLogs(recentLogLines ?? []) ? formatTerminalProviderFailureMessage(recentLogLines ?? []) : formatPermanentHarnessFailureMessage(recentLogLines ?? []);
86327
87272
  console.log(`[AgentProcessManager] ⛔ Skipping restart — ${error}`);
@@ -86474,13 +87419,6 @@ class AgentProcessManager {
86474
87419
  try {
86475
87420
  await this.killExistingBeforeSpawn(opts.chatroomId, opts.role);
86476
87421
  const result = await this.doEnsureRunning(key, slot, opts);
86477
- if (!result.success && opts.reason === "platform.auto_restart_on_new_context") {
86478
- console.log(`[AgentProcessManager] Context auto-restart failed (${result.error ?? "unknown"}), ` + `attempting crash recovery (rate-limited)`);
86479
- return await this.doEnsureRunning(key, slot, {
86480
- ...opts,
86481
- reason: "platform.crash_recovery"
86482
- });
86483
- }
86484
87422
  return result;
86485
87423
  } finally {
86486
87424
  if (slot.pendingOperation) {
@@ -86637,6 +87575,23 @@ class AgentProcessManager {
86637
87575
  console.log(` ⚠️ Failed to emit sessionResumeFailed event: ${err.message}`);
86638
87576
  }
86639
87577
  }
87578
+ async emitSessionReopenRetry(chatroomId, role, attempt, error, harnessSessionId) {
87579
+ try {
87580
+ await this.deps.backend.mutation(api.machines.emitSessionReopenRetry, {
87581
+ sessionId: this.deps.sessionId,
87582
+ machineId: this.deps.machineId,
87583
+ chatroomId,
87584
+ role,
87585
+ attempt,
87586
+ maxAttempts: CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS,
87587
+ ...error ? { error } : {},
87588
+ ...harnessSessionId ? { harnessSessionId } : {}
87589
+ });
87590
+ console.log(`[AgentProcessManager] ✅ Emitted agent.sessionReopenRetry for ${role} (attempt ${attempt}/${CURSOR_SDK_SESSION_REOPEN_MAX_ATTEMPTS})`);
87591
+ } catch (err) {
87592
+ console.log(` ⚠️ Failed to emit sessionReopenRetry event: ${err.message}`);
87593
+ }
87594
+ }
86640
87595
  resetSlotIdle(slot) {
86641
87596
  slot.state = "idle";
86642
87597
  slot.pendingOperation = undefined;
@@ -86856,9 +87811,25 @@ class AgentProcessManager {
86856
87811
  this.registerSpawnCallbacks(slot, opts, spawnResult, pid);
86857
87812
  await this.emitNativeWaiting(opts.chatroomId, opts.role, opts.agentHarness);
86858
87813
  }
86859
- async emitNativeWaiting(chatroomId, role, harness) {
87814
+ async emitNativeWaiting(chatroomId, role, harness, reason = "spawn") {
86860
87815
  if (!getHarnessCapabilities(harness).supportsNativeIntegration)
86861
- return;
87816
+ return false;
87817
+ if (reason === "turn-end") {
87818
+ try {
87819
+ const participant = await this.deps.backend.query(api.participants.getByRole, {
87820
+ sessionId: this.deps.sessionId,
87821
+ chatroomId,
87822
+ role
87823
+ });
87824
+ if (!shouldEmitNativeWaitingOnTurnEnd(participant?.lastStatus)) {
87825
+ console.log(`[AgentProcessManager] Skipping native:waiting for ${role} — active work (${participant?.lastStatus})`);
87826
+ return false;
87827
+ }
87828
+ } catch (err) {
87829
+ console.log(` ⚠️ Failed to check status before native:waiting for ${role}: ${err.message}`);
87830
+ return false;
87831
+ }
87832
+ }
86862
87833
  try {
86863
87834
  await this.deps.backend.mutation(api.participants.join, {
86864
87835
  sessionId: this.deps.sessionId,
@@ -86866,8 +87837,10 @@ class AgentProcessManager {
86866
87837
  role,
86867
87838
  action: NATIVE_WAITING_ACTION
86868
87839
  });
87840
+ return true;
86869
87841
  } catch (err) {
86870
87842
  console.log(` ⚠️ Failed to emit native:waiting for ${role}: ${err.message}`);
87843
+ return false;
86871
87844
  }
86872
87845
  }
86873
87846
  async doEnsureRunning(key, slot, opts) {
@@ -87021,11 +87994,13 @@ var init_agent_process_manager = __esm(() => {
87021
87994
  init_api3();
87022
87995
  init_orphan_tracker();
87023
87996
  init_native_harness_session_exit();
87997
+ init_native_task_delivery_coordinator();
87024
87998
  init_agent_lifecycle();
87025
87999
  init_abort_resume_storm();
87026
88000
  init_classify_resume_storm_reason();
87027
88001
  init_terminal_provider_error();
87028
88002
  init_handle_turn_completed();
88003
+ init_predicates();
87029
88004
  init_spawn_policy();
87030
88005
  init_agent_lifecycle_runtime();
87031
88006
  init_agent_lifecycle_types();
@@ -87039,7 +88014,7 @@ class SpawnRateLimiter {
87039
88014
  this.config = { ...DEFAULT_CONFIG2, ...config3 };
87040
88015
  }
87041
88016
  tryConsume(chatroomId, reason) {
87042
- if (reason.startsWith("user.") || reason === "platform.auto_restart_on_new_context") {
88017
+ if (reason.startsWith("user.") || reason === "platform.cursor_sdk_session_reopen") {
87043
88018
  return { allowed: true };
87044
88019
  }
87045
88020
  const bucket = this._getOrCreateBucket(chatroomId);
@@ -87066,13 +88041,16 @@ class SpawnRateLimiter {
87066
88041
  };
87067
88042
  }
87068
88043
  _getOrCreateBucket(chatroomId) {
87069
- if (!this.buckets.has(chatroomId)) {
87070
- this.buckets.set(chatroomId, {
87071
- tokens: this.config.initialTokens,
87072
- lastRefillAt: Date.now()
87073
- });
88044
+ const existing = this.buckets.get(chatroomId);
88045
+ if (existing) {
88046
+ return existing;
87074
88047
  }
87075
- return this.buckets.get(chatroomId);
88048
+ const bucket = {
88049
+ tokens: this.config.initialTokens,
88050
+ lastRefillAt: Date.now()
88051
+ };
88052
+ this.buckets.set(chatroomId, bucket);
88053
+ return bucket;
87076
88054
  }
87077
88055
  _refill(bucket) {
87078
88056
  const now = Date.now();
@@ -87959,19 +88937,445 @@ var init_task_monitor_logic = __esm(() => {
87959
88937
  init_predicates();
87960
88938
  });
87961
88939
 
88940
+ // src/infrastructure/incremental-sync/working-snapshot.ts
88941
+ class WorkingSnapshot {
88942
+ opts;
88943
+ rows = new Map;
88944
+ constructor(opts) {
88945
+ this.opts = opts;
88946
+ }
88947
+ replaceAll(rows) {
88948
+ this.rows.clear();
88949
+ for (const row of rows) {
88950
+ this.rows.set(this.opts.rowKey(row), row);
88951
+ }
88952
+ }
88953
+ getByKey(key) {
88954
+ return this.rows.get(key);
88955
+ }
88956
+ getBySignal(signal) {
88957
+ return this.rows.get(this.opts.signalKey(signal));
88958
+ }
88959
+ mergeSignal(signal) {
88960
+ const key = this.opts.signalKey(signal);
88961
+ const merged = this.opts.mergeSignal(this.rows.get(key), signal);
88962
+ if (!merged) {
88963
+ return;
88964
+ }
88965
+ this.rows.set(key, merged);
88966
+ return merged;
88967
+ }
88968
+ }
88969
+
88970
+ // src/commands/machine/daemon-start/task-monitor-snapshot.ts
88971
+ function taskSnapshotKey(taskId, role) {
88972
+ return `${taskId}:${role}`;
88973
+ }
88974
+ function mergeSignalIntoTaskSnapshot(existing, signal) {
88975
+ if (!existing) {
88976
+ return;
88977
+ }
88978
+ return {
88979
+ ...existing,
88980
+ status: signal.status,
88981
+ agentConfig: {
88982
+ ...existing.agentConfig,
88983
+ spawnedAgentPid: signal.spawnedAgentPid ?? existing.agentConfig.spawnedAgentPid,
88984
+ desiredState: signal.desiredState ?? existing.agentConfig.desiredState
88985
+ },
88986
+ participant: {
88987
+ lastSeenAction: signal.lastSeenAction ?? existing.participant?.lastSeenAction ?? null,
88988
+ lastSeenAt: existing.participant?.lastSeenAt ?? null,
88989
+ lastStatus: existing.participant?.lastStatus ?? null
88990
+ }
88991
+ };
88992
+ }
88993
+ function createTaskMonitorSnapshot() {
88994
+ return new WorkingSnapshot(taskMonitorSnapshotOptions);
88995
+ }
88996
+ var taskMonitorSnapshotOptions;
88997
+ var init_task_monitor_snapshot = __esm(() => {
88998
+ taskMonitorSnapshotOptions = {
88999
+ rowKey: (row) => taskSnapshotKey(row.taskId, row.agentConfig.role),
89000
+ signalKey: (signal) => taskSnapshotKey(signal.taskId, signal.role),
89001
+ mergeSignal: mergeSignalIntoTaskSnapshot
89002
+ };
89003
+ });
89004
+
89005
+ // src/infrastructure/incremental-sync/layers.ts
89006
+ var IntervalClock, IntervalClockLive;
89007
+ var init_layers = __esm(() => {
89008
+ init_esm();
89009
+ IntervalClock = class IntervalClock extends exports_Context.Tag("IntervalClock")() {
89010
+ };
89011
+ IntervalClockLive = exports_Layer.succeed(IntervalClock, {
89012
+ sleep: (ms) => exports_Effect.promise(() => new Promise((resolve5) => setTimeout(resolve5, ms)))
89013
+ });
89014
+ });
89015
+
89016
+ // src/infrastructure/incremental-sync/message-buffer.ts
89017
+ class MessageBuffer {
89018
+ keyOf;
89019
+ queue = [];
89020
+ inBuffer = new Set;
89021
+ inFlight = new Map;
89022
+ recentlyAcked = new Map;
89023
+ dedupe;
89024
+ dedupeTtlMs;
89025
+ maxSize;
89026
+ constructor(config3, keyOf) {
89027
+ this.keyOf = keyOf;
89028
+ this.dedupe = config3.dedupe ?? true;
89029
+ this.dedupeTtlMs = config3.dedupeTtlMs ?? 0;
89030
+ this.maxSize = config3.maxSize;
89031
+ }
89032
+ enqueue(items) {
89033
+ this.pruneRecentlyAcked();
89034
+ let enqueued = 0;
89035
+ for (const item of items) {
89036
+ const key = this.keyOf(item);
89037
+ if (this.dedupe && this.shouldSkip(key)) {
89038
+ continue;
89039
+ }
89040
+ this.queue.push({ key, item });
89041
+ this.inBuffer.add(key);
89042
+ enqueued++;
89043
+ }
89044
+ if (enqueued > 0) {
89045
+ this.queue.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
89046
+ }
89047
+ this.enforceMaxSize();
89048
+ return enqueued;
89049
+ }
89050
+ dequeue() {
89051
+ this.pruneRecentlyAcked();
89052
+ const next4 = this.queue.shift();
89053
+ if (!next4)
89054
+ return;
89055
+ this.inBuffer.delete(next4.key);
89056
+ this.inFlight.set(next4.key, next4.item);
89057
+ return next4.item;
89058
+ }
89059
+ ack(key) {
89060
+ this.inFlight.delete(key);
89061
+ if (this.dedupe) {
89062
+ this.recentlyAcked.set(key, Date.now());
89063
+ }
89064
+ }
89065
+ nack(key, requeue = false) {
89066
+ const item = this.inFlight.get(key);
89067
+ this.inFlight.delete(key);
89068
+ if (requeue && item !== undefined) {
89069
+ this.enqueue([item]);
89070
+ }
89071
+ }
89072
+ size() {
89073
+ return this.queue.length;
89074
+ }
89075
+ highKeyOf(items) {
89076
+ if (items.length === 0)
89077
+ return null;
89078
+ let high = null;
89079
+ for (const item of items) {
89080
+ const key = this.keyOf(item);
89081
+ if (high === null || key > high) {
89082
+ high = key;
89083
+ }
89084
+ }
89085
+ return high;
89086
+ }
89087
+ shouldSkip(key) {
89088
+ if (this.inBuffer.has(key) || this.inFlight.has(key)) {
89089
+ return true;
89090
+ }
89091
+ if (this.dedupeTtlMs > 0) {
89092
+ const ackedAt = this.recentlyAcked.get(key);
89093
+ if (ackedAt !== undefined && Date.now() - ackedAt < this.dedupeTtlMs) {
89094
+ return true;
89095
+ }
89096
+ } else if (this.recentlyAcked.has(key)) {
89097
+ return true;
89098
+ }
89099
+ return false;
89100
+ }
89101
+ pruneRecentlyAcked() {
89102
+ if (this.dedupeTtlMs <= 0)
89103
+ return;
89104
+ const now = Date.now();
89105
+ for (const [key, ackedAt] of this.recentlyAcked) {
89106
+ if (now - ackedAt >= this.dedupeTtlMs) {
89107
+ this.recentlyAcked.delete(key);
89108
+ }
89109
+ }
89110
+ }
89111
+ enforceMaxSize() {
89112
+ while (this.queue.length > this.maxSize) {
89113
+ const dropped = this.queue.shift();
89114
+ if (dropped) {
89115
+ this.inBuffer.delete(dropped.key);
89116
+ console.warn(`[incremental-sync] Buffer full — dropped oldest unacked item (key=${dropped.key})`);
89117
+ }
89118
+ }
89119
+ }
89120
+ }
89121
+
89122
+ // src/infrastructure/incremental-sync/resolve-high-key.ts
89123
+ function resolveHighKey(def, buffer, page) {
89124
+ if (page.highKey !== null)
89125
+ return page.highKey;
89126
+ const fromItems = buffer.highKeyOf(page.items);
89127
+ if (fromItems !== null)
89128
+ return fromItems;
89129
+ if (def.itemToKey && page.items.length > 0) {
89130
+ let high = null;
89131
+ for (const item of page.items) {
89132
+ const key = def.itemToKey(item);
89133
+ if (high === null || key > high)
89134
+ high = key;
89135
+ }
89136
+ return high;
89137
+ }
89138
+ return null;
89139
+ }
89140
+
89141
+ // src/infrastructure/incremental-sync/subscribe-loop.ts
89142
+ function startSubscribeLoop(opts) {
89143
+ let afterKey = opts.initialAfterKey ?? null;
89144
+ let stopped = false;
89145
+ let unsubscribe;
89146
+ let drainInFlight = false;
89147
+ const subscribe = () => {
89148
+ if (stopped)
89149
+ return;
89150
+ unsubscribe?.();
89151
+ unsubscribe = opts.wsClient.onUpdate(opts.target.query, opts.target.buildArgs(opts.args, afterKey, opts.config.limit), (result) => {
89152
+ if (stopped)
89153
+ return;
89154
+ const page = opts.target.parsePage(result);
89155
+ drainPages(page);
89156
+ }, (err) => {
89157
+ opts.onError?.(err);
89158
+ });
89159
+ };
89160
+ const fetchPage = async (cursor) => {
89161
+ const result = await opts.wsClient.query(opts.target.query, opts.target.buildArgs(opts.args, cursor, opts.config.limit));
89162
+ return opts.target.parsePage(result);
89163
+ };
89164
+ const drainPages = async (initialPage) => {
89165
+ if (stopped || drainInFlight)
89166
+ return;
89167
+ drainInFlight = true;
89168
+ try {
89169
+ const cursorAtStart = afterKey;
89170
+ let page = initialPage;
89171
+ while (!stopped && page.items.length > 0) {
89172
+ opts.buffer.enqueue(page.items);
89173
+ const nextKey = resolveHighKey(opts.def, opts.buffer, page);
89174
+ if (nextKey === null) {
89175
+ break;
89176
+ }
89177
+ afterKey = nextKey;
89178
+ if (!page.hasMore) {
89179
+ break;
89180
+ }
89181
+ page = await fetchPage(afterKey);
89182
+ }
89183
+ if (!stopped && afterKey !== cursorAtStart) {
89184
+ subscribe();
89185
+ }
89186
+ } catch (err) {
89187
+ opts.onError?.(err);
89188
+ } finally {
89189
+ drainInFlight = false;
89190
+ }
89191
+ };
89192
+ subscribe();
89193
+ return {
89194
+ stop: () => {
89195
+ stopped = true;
89196
+ unsubscribe?.();
89197
+ unsubscribe = undefined;
89198
+ },
89199
+ getAfterKey: () => afterKey
89200
+ };
89201
+ }
89202
+ var init_subscribe_loop = () => {};
89203
+
89204
+ // src/infrastructure/incremental-sync/feed-runtime.ts
89205
+ function runWorkerLoop(feedName, buffer, keyOf, onItem) {
89206
+ return exports_Effect.gen(function* () {
89207
+ while (true) {
89208
+ const item = buffer.dequeue();
89209
+ if (item === undefined) {
89210
+ yield* exports_Effect.sleep("10 millis");
89211
+ continue;
89212
+ }
89213
+ const key = keyOf(item);
89214
+ const ctx = {
89215
+ item,
89216
+ feedName,
89217
+ ack: () => buffer.ack(key),
89218
+ nack: (opts) => buffer.nack(key, opts?.requeue ?? false)
89219
+ };
89220
+ yield* onItem(ctx).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89221
+ }
89222
+ });
89223
+ }
89224
+ var runIncrementalSubscribeLive = (opts) => exports_Effect.gen(function* () {
89225
+ const buffer = new MessageBuffer(opts.buffer, opts.def.itemKey);
89226
+ const subscribeHandle = startSubscribeLoop({
89227
+ wsClient: opts.wsClient,
89228
+ target: opts.target,
89229
+ args: opts.args,
89230
+ buffer,
89231
+ def: opts.def,
89232
+ config: opts.subscribe,
89233
+ initialAfterKey: opts.initialAfterKey ?? null,
89234
+ onError: opts.onError
89235
+ });
89236
+ const workerFiber = yield* exports_Effect.forkDaemon(runWorkerLoop(opts.def.name, buffer, opts.def.itemKey, opts.onItem));
89237
+ return {
89238
+ buffer,
89239
+ stop: () => exports_Effect.gen(function* () {
89240
+ subscribeHandle.stop();
89241
+ yield* exports_Fiber.interrupt(workerFiber);
89242
+ })
89243
+ };
89244
+ }), runReconcilePoll = (opts) => exports_Effect.gen(function* () {
89245
+ const clock4 = yield* IntervalClock;
89246
+ const backoffCfg = opts.backoff ?? { initialMs: 1000, maxMs: 30000 };
89247
+ let backoffMs = opts.intervalMs;
89248
+ let stopped = false;
89249
+ const loopFiber = yield* exports_Effect.forkDaemon(exports_Effect.gen(function* () {
89250
+ while (!stopped) {
89251
+ const pollOutcome = yield* exports_Effect.either(exports_Effect.tryPromise(() => opts.poll(opts.args)));
89252
+ if (pollOutcome._tag === "Left") {
89253
+ backoffMs = Math.min(backoffMs === opts.intervalMs ? backoffCfg.initialMs : backoffMs * 2, backoffCfg.maxMs);
89254
+ yield* clock4.sleep(backoffMs);
89255
+ continue;
89256
+ }
89257
+ yield* opts.onResult(pollOutcome.right);
89258
+ backoffMs = opts.intervalMs;
89259
+ yield* clock4.sleep(opts.intervalMs);
89260
+ }
89261
+ }));
89262
+ return {
89263
+ stop: () => exports_Effect.gen(function* () {
89264
+ stopped = true;
89265
+ yield* exports_Fiber.interrupt(loopFiber);
89266
+ })
89267
+ };
89268
+ }), runReconcilePollLive = (opts) => runReconcilePoll(opts).pipe(exports_Effect.provide(IntervalClockLive));
89269
+ var init_feed_runtime = __esm(() => {
89270
+ init_esm();
89271
+ init_layers();
89272
+ init_subscribe_loop();
89273
+ });
89274
+
89275
+ // src/infrastructure/incremental-sync/dual-channel-feed.ts
89276
+ var runDualChannelFeedLive = (opts) => exports_Effect.gen(function* () {
89277
+ const initial = yield* exports_Effect.tryPromise(() => opts.fetchReconcile()).pipe(exports_Effect.orElseSucceed(() => null));
89278
+ if (initial !== null) {
89279
+ const rows = opts.extractReconcileRows(initial);
89280
+ opts.snapshot.replaceAll(rows);
89281
+ if (rows.length > 0) {
89282
+ yield* opts.onReconcileRows(rows).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89283
+ }
89284
+ }
89285
+ const seedKey = yield* exports_Effect.tryPromise(() => opts.seedCursor()).pipe(exports_Effect.orElseSucceed(() => null));
89286
+ const hydrateFromReconcile = async () => {
89287
+ const result = await opts.fetchReconcile();
89288
+ opts.snapshot.replaceAll(opts.extractReconcileRows(result));
89289
+ };
89290
+ const resolveRowForSignal = async (signal) => {
89291
+ const merged = opts.snapshot.mergeSignal(signal);
89292
+ if (merged) {
89293
+ return merged;
89294
+ }
89295
+ await hydrateFromReconcile();
89296
+ return opts.snapshot.mergeSignal(signal) ?? opts.snapshot.getBySignal(signal);
89297
+ };
89298
+ const signalHandle = yield* runIncrementalSubscribeLive({
89299
+ wsClient: opts.wsClient,
89300
+ def: opts.def,
89301
+ target: opts.target,
89302
+ args: opts.args,
89303
+ buffer: opts.buffer,
89304
+ subscribe: opts.subscribe,
89305
+ initialAfterKey: seedKey,
89306
+ onError: opts.onSubscribeError,
89307
+ onItem: ({ item: signal, ack }) => exports_Effect.gen(function* () {
89308
+ ack();
89309
+ if (opts.isStopped()) {
89310
+ return;
89311
+ }
89312
+ const row = yield* exports_Effect.tryPromise(() => resolveRowForSignal(signal));
89313
+ if (!row) {
89314
+ return;
89315
+ }
89316
+ yield* opts.onSignalRow(row).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89317
+ })
89318
+ });
89319
+ const reconcileHandle = yield* runReconcilePollLive({
89320
+ name: `${opts.name}-reconcile`,
89321
+ poll: () => opts.fetchReconcile(),
89322
+ args: undefined,
89323
+ intervalMs: opts.reconcileIntervalMs,
89324
+ onResult: (result) => exports_Effect.gen(function* () {
89325
+ const rows = opts.extractReconcileRows(result);
89326
+ opts.snapshot.replaceAll(rows);
89327
+ yield* opts.onReconcileRows(rows).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89328
+ })
89329
+ });
89330
+ return {
89331
+ stop: () => exports_Effect.gen(function* () {
89332
+ yield* signalHandle.stop();
89333
+ yield* reconcileHandle.stop();
89334
+ })
89335
+ };
89336
+ });
89337
+ var init_dual_channel_feed = __esm(() => {
89338
+ init_esm();
89339
+ init_feed_runtime();
89340
+ });
89341
+
89342
+ // src/infrastructure/incremental-sync/feeds/assigned-task-signals.ts
89343
+ var assignedTaskSignalsFeedDef, assignedTaskSignalsSubscribeTarget, ASSIGNED_TASK_SIGNAL_FEED_LIMIT = 50, ASSIGNED_TASK_SIGNAL_FEED_BUFFER, ASSIGNED_TASK_RECONCILE_INTERVAL_MS = 15000;
89344
+ var init_assigned_task_signals = __esm(() => {
89345
+ init_api3();
89346
+ assignedTaskSignalsFeedDef = {
89347
+ name: "assigned-task-signals",
89348
+ itemKey: (item) => item.revisionKey
89349
+ };
89350
+ assignedTaskSignalsSubscribeTarget = {
89351
+ query: api.machines.subscribeAssignedTaskSignalsSince,
89352
+ buildArgs: (args2, afterKey, limit) => ({
89353
+ sessionId: args2.sessionId,
89354
+ machineId: args2.machineId,
89355
+ afterKey: afterKey ?? undefined,
89356
+ limit
89357
+ }),
89358
+ parsePage: (result) => result
89359
+ };
89360
+ ASSIGNED_TASK_SIGNAL_FEED_BUFFER = {
89361
+ maxSize: 200,
89362
+ dedupe: true
89363
+ };
89364
+ });
89365
+
87962
89366
  // src/commands/machine/daemon-start/task-monitor.ts
87963
89367
  function resolveTaskWantResume(task) {
87964
- return compressContextToWantResume(parseCompressContext(task.taskContent ?? ""));
89368
+ return sessionAugmentationToWantResume(resolveSessionAugmentationForRole(task.taskContent ?? "", task.agentConfig.role));
87965
89369
  }
87966
89370
  function buildCliNudgeLogLine(task) {
87967
89371
  const { chatroomId, agentConfig } = task;
87968
89372
  const { role } = agentConfig;
87969
89373
  const lastSeenAction = task.participant?.lastSeenAction ?? "unknown";
87970
- const compressMode = parseCompressContext(task.taskContent ?? "");
89374
+ const augmentationMode = resolveSessionAugmentationForRole(task.taskContent ?? "", role);
87971
89375
  const wantResume = resolveTaskWantResume(task);
87972
- return `[TaskMonitor] nudging ${role}@${chatroomId} — pending task ${task.taskId}, lastSeenAction=${lastSeenAction}, compress_context=${compressMode}, wantResume=${wantResume}`;
89376
+ return `[TaskMonitor] nudging ${role}@${chatroomId} — pending task ${task.taskId}, lastSeenAction=${lastSeenAction}, session_augmentation=${augmentationMode}, wantResume=${wantResume}`;
87973
89377
  }
87974
- function resolveTaskRunnerContext(task) {
89378
+ function resolveTaskRunnerContextFromFull(task) {
87975
89379
  const { chatroomId, agentConfig } = task;
87976
89380
  const { role } = agentConfig;
87977
89381
  const workingDir = agentConfig.workingDir;
@@ -87985,11 +89389,12 @@ function resolveTaskRunnerContext(task) {
87985
89389
  wantResume: resolveTaskWantResume(task)
87986
89390
  };
87987
89391
  }
87988
- function executeCliNudge(task, runtime4, effectContext2, agentMgr) {
87989
- const ctx = resolveTaskRunnerContext(task);
89392
+ function executeCliNudge(task, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
89393
+ const ctx = resolveTaskRunnerContextFromFull(task);
87990
89394
  if (!ctx)
87991
89395
  return;
87992
89396
  const { chatroomId, agentConfig, role, workingDir, wantResume } = ctx;
89397
+ const augmentationMode = resolveSessionAugmentationForRole(task.taskContent ?? "", role);
87993
89398
  exports_Runtime.runFork(runtime4)(exports_Effect.gen(function* () {
87994
89399
  yield* agentMgr.stop({ chatroomId, role, reason: "platform.task_monitor_nudge" });
87995
89400
  yield* agentMgr.ensureRunning({
@@ -88001,10 +89406,24 @@ function executeCliNudge(task, runtime4, effectContext2, agentMgr) {
88001
89406
  reason: "platform.task_monitor_nudge",
88002
89407
  wantResume
88003
89408
  });
89409
+ if (roleSupportsSessionAugmentation(role)) {
89410
+ yield* exports_Effect.tryPromise({
89411
+ try: () => sessionDeps.backend.mutation(api.machines.emitSessionAugmented, {
89412
+ sessionId: sessionDeps.sessionId,
89413
+ machineId,
89414
+ chatroomId,
89415
+ role,
89416
+ taskId: task.taskId,
89417
+ mode: augmentationMode,
89418
+ newSessionStarted: sessionAugmentationNewSessionStarted(augmentationMode)
89419
+ }),
89420
+ catch: (err) => err
89421
+ }).pipe(exports_Effect.catchAll(() => exports_Effect.void));
89422
+ }
88004
89423
  }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[TaskMonitor] nudge failed for ${role}@${chatroomId}: ${getErrorMessage(err)}`)))));
88005
89424
  }
88006
89425
  function runNativeReviveEffect(task, runtime4, effectContext2, agentMgr) {
88007
- const ctx = resolveTaskRunnerContext(task);
89426
+ const ctx = resolveTaskRunnerContextFromFull(task);
88008
89427
  if (!ctx)
88009
89428
  return;
88010
89429
  const { chatroomId, agentConfig, role, workingDir, wantResume } = ctx;
@@ -88021,73 +89440,138 @@ function runNativeReviveEffect(task, runtime4, effectContext2, agentMgr) {
88021
89440
  });
88022
89441
  }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[TaskMonitor] native revive failed for ${role}@${chatroomId}: ${getErrorMessage(err)}`)))));
88023
89442
  }
88024
- function runCliNudgeEffect(task, runtime4, effectContext2, agentMgr) {
89443
+ async function fetchTaskForAction(sessionDeps, machineId, snapshotRow) {
89444
+ const result = await sessionDeps.backend.query(api.machines.getAssignedTaskForAction, {
89445
+ sessionId: sessionDeps.sessionId,
89446
+ machineId,
89447
+ taskId: snapshotRow.taskId,
89448
+ role: snapshotRow.agentConfig.role
89449
+ });
89450
+ return result;
89451
+ }
89452
+ function runCliNudgeEffect(task, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
88025
89453
  console.log(buildCliNudgeLogLine(task));
88026
- executeCliNudge(task, runtime4, effectContext2, agentMgr);
89454
+ executeCliNudge(task, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
88027
89455
  }
88028
- function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr) {
88029
- for (const task of listTasksReadyForNudge(tasks, now, cooldown)) {
88030
- runCliNudgeEffect(task, runtime4, effectContext2, agentMgr);
89456
+ async function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
89457
+ for (const row of listTasksReadyForNudge(tasks, now, cooldown)) {
89458
+ const full = await fetchTaskForAction(sessionDeps, machineId, row);
89459
+ if (!full)
89460
+ continue;
89461
+ runCliNudgeEffect(full, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
88031
89462
  }
88032
89463
  }
88033
- function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr) {
88034
- for (const task of listNativeTasksNeedingRevive(tasks, localHealth, now, cooldown)) {
88035
- runNativeReviveEffect(task, runtime4, effectContext2, agentMgr);
89464
+ async function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
89465
+ for (const row of listNativeTasksNeedingRevive(tasks, localHealth, now, cooldown)) {
89466
+ const full = await fetchTaskForAction(sessionDeps, machineId, row);
89467
+ if (!full)
89468
+ continue;
89469
+ runNativeReviveEffect(full, runtime4, effectContext2, agentMgr);
88036
89470
  }
88037
89471
  }
88038
- function processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps) {
89472
+ async function processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps, machineId, pass) {
89473
+ if (tasks.length === 0)
89474
+ return;
88039
89475
  const now = Date.now();
88040
89476
  const localHealth = {
88041
89477
  getSlot: (chatroomId, role) => agentMgr.getSlot(chatroomId, role),
88042
89478
  isPidAlive: (pid) => isProcessAlive((p) => process.kill(p, 0), pid)
88043
89479
  };
88044
- reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr);
89480
+ await reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
88045
89481
  getNativeTaskDeliveryCoordinator().reconcileAssignedTasks({
88046
89482
  tasks,
88047
89483
  runtime: runtime4,
88048
89484
  effectContext: effectContext2,
88049
89485
  agentMgr,
88050
- sessionDeps
89486
+ sessionDeps,
89487
+ machineId
88051
89488
  });
88052
- nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr);
89489
+ if (pass === "reconcile") {
89490
+ await nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
89491
+ }
88053
89492
  }
88054
- var startTaskMonitorSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
89493
+ var startTaskMonitorEffect = (wsClient2) => exports_Effect.gen(function* () {
88055
89494
  const session2 = yield* DaemonSessionService;
88056
89495
  const agentMgr = yield* DaemonAgentProcessManagerService;
88057
89496
  const effectContext2 = yield* exports_Effect.context();
88058
89497
  const runtime4 = yield* exports_Effect.runtime();
88059
- console.log(`[${formatTimestamp()}] \uD83D\uDCCB Starting task-monitor subscription (reactive)`);
89498
+ console.log(`[${formatTimestamp()}] \uD83D\uDCCB Starting task-monitor (incremental subscribe)`);
88060
89499
  const cooldown = new NudgeCooldown;
89500
+ const snapshot = createTaskMonitorSnapshot();
88061
89501
  let stopped = false;
89502
+ let monitorPassInFlight = false;
88062
89503
  const sessionDeps = {
88063
89504
  sessionId: session2.sessionId,
88064
89505
  convexUrl: session2.convexUrl,
89506
+ machineId: session2.machineId,
88065
89507
  backend: {
88066
89508
  mutation: (fn2, args2) => session2.backend.mutation(fn2, args2),
88067
89509
  query: (fn2, args2) => session2.backend.query(fn2, args2)
88068
89510
  }
88069
89511
  };
88070
- const onTasksUpdate = (result) => {
88071
- if (stopped || !result?.tasks?.length)
89512
+ const runMonitorPass = (tasks, pass) => {
89513
+ if (stopped || monitorPassInFlight || tasks.length === 0)
88072
89514
  return;
88073
- processTasksUpdate(result.tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps);
89515
+ monitorPassInFlight = true;
89516
+ processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps, session2.machineId, pass).finally(() => {
89517
+ monitorPassInFlight = false;
89518
+ });
88074
89519
  };
88075
- const unsubscribe = wsClient2.onUpdate(api.machines.getAssignedTasks, { sessionId: session2.sessionId, machineId: session2.machineId }, onTasksUpdate, (err) => console.warn(`[${formatTimestamp()}] Task-monitor subscription error: ${getErrorMessage(err)}`));
89520
+ const queryReconcileSnapshots = () => session2.backend.query(api.machines.listAssignedTasksForReconcile, {
89521
+ sessionId: session2.sessionId,
89522
+ machineId: session2.machineId
89523
+ });
89524
+ const feedHandle = yield* runDualChannelFeedLive({
89525
+ name: "assigned-tasks",
89526
+ wsClient: wsClient2,
89527
+ def: assignedTaskSignalsFeedDef,
89528
+ target: assignedTaskSignalsSubscribeTarget,
89529
+ args: {
89530
+ sessionId: session2.sessionId,
89531
+ machineId: session2.machineId
89532
+ },
89533
+ buffer: ASSIGNED_TASK_SIGNAL_FEED_BUFFER,
89534
+ subscribe: { limit: ASSIGNED_TASK_SIGNAL_FEED_LIMIT },
89535
+ snapshot,
89536
+ seedCursor: async () => {
89537
+ const seedPage = await session2.backend.query(api.machines.subscribeAssignedTaskSignalsSince, {
89538
+ sessionId: session2.sessionId,
89539
+ machineId: session2.machineId,
89540
+ limit: ASSIGNED_TASK_SIGNAL_FEED_LIMIT
89541
+ });
89542
+ return seedPage?.highKey ?? null;
89543
+ },
89544
+ fetchReconcile: () => queryReconcileSnapshots(),
89545
+ extractReconcileRows: (result) => result?.tasks ?? [],
89546
+ reconcileIntervalMs: ASSIGNED_TASK_RECONCILE_INTERVAL_MS,
89547
+ isStopped: () => stopped,
89548
+ onSubscribeError: (err) => console.warn(`[${formatTimestamp()}] ⚠️ Task signal subscription error: ${getErrorMessage(err)}`),
89549
+ onSignalRow: (row) => exports_Effect.sync(() => {
89550
+ runMonitorPass([row], "signal");
89551
+ }),
89552
+ onReconcileRows: (tasks) => exports_Effect.sync(() => {
89553
+ runMonitorPass([...tasks], "reconcile");
89554
+ })
89555
+ });
88076
89556
  return {
88077
89557
  stop() {
88078
89558
  stopped = true;
88079
- unsubscribe();
88080
- console.log(`[${formatTimestamp()}] \uD83D\uDCCB Task-monitor subscription stopped`);
89559
+ exports_Effect.runPromise(feedHandle.stop());
89560
+ console.log(`[${formatTimestamp()}] \uD83D\uDCCB Task-monitor stopped`);
88081
89561
  }
88082
89562
  };
88083
89563
  });
88084
89564
  var init_task_monitor = __esm(() => {
88085
- init_parse_compress_context();
89565
+ init_team_agent_settings();
89566
+ init_parse_session_augmentation();
88086
89567
  init_esm();
88087
89568
  init_daemon_services();
88088
89569
  init_native_task_delivery_coordinator();
88089
89570
  init_task_monitor_logic();
89571
+ init_task_monitor_snapshot();
88090
89572
  init_api3();
89573
+ init_dual_channel_feed();
89574
+ init_assigned_task_signals();
88091
89575
  init_convex_error();
88092
89576
  });
88093
89577
 
@@ -88290,6 +89774,11 @@ function handleRefreshCapabilitiesEffect(event, tracker) {
88290
89774
  })));
88291
89775
  });
88292
89776
  }
89777
+ function forkDaemonSyncBatch(runtime4, effectContext2, failureLabel) {
89778
+ exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
89779
+ concurrency: "unbounded"
89780
+ }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[${formatTimestamp()}] ⚠️ ${failureLabel}: ${getErrorMessage(err)}`)))));
89781
+ }
88293
89782
  var GIT_PUSH_ACTIONS, commandEventHandlers, dispatchCommandEventEffect = (event, tracker) => {
88294
89783
  const factory2 = commandEventHandlers[event.type];
88295
89784
  return factory2 != null ? factory2(event, tracker) : exports_Effect.void;
@@ -88348,9 +89837,7 @@ var init_command_loop = __esm(() => {
88348
89837
  heartbeatCount++;
88349
89838
  console.log(`[${formatTimestamp()}] \uD83D\uDC93 Daemon heartbeat #${heartbeatCount} OK`);
88350
89839
  if (!observedSyncEnabled) {
88351
- exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
88352
- concurrency: "unbounded"
88353
- }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[${formatTimestamp()}] ⚠️ Heartbeat sync failed: ${getErrorMessage(err)}`)))));
89840
+ forkDaemonSyncBatch(runtime4, effectContext2, "Heartbeat sync failed");
88354
89841
  }
88355
89842
  }).catch((err) => {
88356
89843
  console.warn(`[${formatTimestamp()}] ⚠️ Daemon heartbeat failed: ${getErrorMessage(err)}`);
@@ -88367,14 +89854,13 @@ var init_command_loop = __esm(() => {
88367
89854
  let pendingHarnessSessionSubscriptionHandle = null;
88368
89855
  let commandSubscriptionHandle = null;
88369
89856
  let lifecycleManager = null;
89857
+ let closeDirectHarnessSessionsOnShutdown = null;
88370
89858
  const activeSessions = new Map;
88371
89859
  const harnesses = new Map;
88372
89860
  if (observedSyncEnabled) {
88373
89861
  console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Observed-sync enabled, skipping immediate push`);
88374
89862
  } else {
88375
- exports_Runtime.runFork(runtime4)(exports_Effect.all([pushGitStateEffect, pushCommandsEffect, syncCommitDetailsEffect()], {
88376
- concurrency: "unbounded"
88377
- }).pipe(exports_Effect.provide(effectContext2), exports_Effect.catchAll((err) => exports_Effect.sync(() => console.warn(`[${formatTimestamp()}] ⚠️ Startup sync failed: ${getErrorMessage(err)}`)))));
89863
+ forkDaemonSyncBatch(runtime4, effectContext2, "Startup sync failed");
88378
89864
  }
88379
89865
  const PROCESS_KILL_TIMEOUT_MS = 6000;
88380
89866
  const CLOSE_TIMEOUT_MS = 3000;
@@ -88442,8 +89928,12 @@ var init_command_loop = __esm(() => {
88442
89928
  await withTimeout2(exports_Effect.runPromise(onDaemonShutdownEffect.pipe(exports_Effect.provide(effectContext2))), PROCESS_KILL_TIMEOUT_MS);
88443
89929
  };
88444
89930
  const closeAllSessionsAndHarnesses = async () => {
88445
- for (const handle of activeSessions.values()) {
88446
- await withTimeout2(handle.close(), CLOSE_TIMEOUT_MS);
89931
+ if (closeDirectHarnessSessionsOnShutdown) {
89932
+ await withTimeout2(closeDirectHarnessSessionsOnShutdown(), PROCESS_KILL_TIMEOUT_MS);
89933
+ } else {
89934
+ for (const handle of activeSessions.values()) {
89935
+ await withTimeout2(handle.close(), CLOSE_TIMEOUT_MS);
89936
+ }
88447
89937
  }
88448
89938
  for (const harness of harnesses.values()) {
88449
89939
  await withTimeout2(harness.close(), CLOSE_TIMEOUT_MS);
@@ -88473,7 +89963,7 @@ var init_command_loop = __esm(() => {
88473
89963
  if (observedSyncEnabled) {
88474
89964
  observedSyncSubscriptionHandle = yield* startObservedSyncSubscriptionEffect(wsClient2);
88475
89965
  }
88476
- const taskMonitorHandle = yield* startTaskMonitorSubscriptionEffect(wsClient2);
89966
+ const taskMonitorHandle = yield* startTaskMonitorEffect(wsClient2);
88477
89967
  logObserverSubscriptionHandle = startLogObserverSubscription({ sessionId: session2.sessionId, machineId: session2.machineId }, wsClient2);
88478
89968
  if (featureFlags.directHarnessWorkers) {
88479
89969
  const handles = startDirectHarnessSubscriptions({
@@ -88486,6 +89976,7 @@ var init_command_loop = __esm(() => {
88486
89976
  pendingHarnessSessionSubscriptionHandle = handles.pendingHarnessSessionSubscriptionHandle;
88487
89977
  commandSubscriptionHandle = handles.commandSubscriptionHandle;
88488
89978
  lifecycleManager = handles.lifecycleManager;
89979
+ closeDirectHarnessSessionsOnShutdown = handles.closeSessionsOnShutdown;
88489
89980
  }
88490
89981
  console.log(`
88491
89982
  Listening for commands...`);
@@ -88672,8 +90163,8 @@ import * as os2 from "os";
88672
90163
  import * as path4 from "path";
88673
90164
  async function isChatroomInstalledDefault() {
88674
90165
  try {
88675
- const { execSync: execSync2 } = await import("child_process");
88676
- execSync2("chatroom --version", { stdio: "pipe" });
90166
+ const { execSync: execSync3 } = await import("child_process");
90167
+ execSync3("chatroom --version", { stdio: "pipe" });
88677
90168
  return true;
88678
90169
  } catch {
88679
90170
  return false;
@@ -89587,4 +91078,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
89587
91078
  });
89588
91079
  program2.parse();
89589
91080
 
89590
- //# debugId=4A0FE03BEB3CACE864756E2164756E21
91081
+ //# debugId=D22ED479E2182D5A64756E2164756E21