chatroom-cli 1.65.2 → 1.65.5

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
@@ -32368,6 +32368,13 @@ async function withTimeout(p, ms, label) {
32368
32368
  }
32369
32369
 
32370
32370
  // src/infrastructure/services/remote-agents/claude-sdk/claude-sdk-package.ts
32371
+ var exports_claude_sdk_package = {};
32372
+ __export(exports_claude_sdk_package, {
32373
+ resolvePathToClaudeCodeExecutable: () => resolvePathToClaudeCodeExecutable,
32374
+ importBundledClaudeSdk: () => importBundledClaudeSdk,
32375
+ getBundledClaudeSdkVersion: () => getBundledClaudeSdkVersion,
32376
+ formatClaudeSdkLoadError: () => formatClaudeSdkLoadError
32377
+ });
32371
32378
  import { existsSync, readFileSync as readFileSync3 } from "node:fs";
32372
32379
  import { createRequire as createRequire3 } from "node:module";
32373
32380
  import { dirname as dirname2, join as join7 } from "node:path";
@@ -33522,6 +33529,8 @@ var init_cursor_sdk_stream_adapter = __esm(() => {
33522
33529
  case "task":
33523
33530
  this.writeLine(formatAgentLogLine(this.logPrefix, "task", [message.status, message.text].filter(Boolean).join(": ")));
33524
33531
  break;
33532
+ case "usage":
33533
+ break;
33525
33534
  default: {
33526
33535
  const unknown = message;
33527
33536
  if (unknown.type) {
@@ -36100,9 +36109,6 @@ function selectAgent(agents) {
36100
36109
  }
36101
36110
 
36102
36111
  // src/domain/agent-lifecycle/policies/terminal-provider-error.ts
36103
- function isProviderRateLimitHarnessMarker(line) {
36104
- return PROVIDER_RATE_LIMIT_AGENT_END_MARKER.test(line);
36105
- }
36106
36112
  function matchesQuotaPhrase(blob) {
36107
36113
  const text = blob.toLowerCase();
36108
36114
  return QUOTA_PHRASES.some((phrase) => text.includes(phrase));
@@ -36137,30 +36143,6 @@ function isTerminalProviderError(error) {
36137
36143
  const message = extractMessage(e);
36138
36144
  return matchesQuotaPhrase(message) || matchesQuotaPhrase(`${name} ${message}`) || matchesFatalHarnessErrorText(message) || matchesFatalHarnessErrorText(`${name} ${message}`);
36139
36145
  }
36140
- function isTerminalProviderFailureInLogs(logLines) {
36141
- for (const line of logLines) {
36142
- if (!isClassifiableHarnessLogLine(line))
36143
- continue;
36144
- if (isProviderRateLimitHarnessMarker(line))
36145
- return true;
36146
- if (isNonRetryableHarnessFailureText(line))
36147
- return true;
36148
- }
36149
- return false;
36150
- }
36151
- function isClassifiableHarnessLogLine(line) {
36152
- if (/\b(?:text|thinking)\]/.test(line))
36153
- return false;
36154
- if (line.includes("agent_end]"))
36155
- return true;
36156
- if (line.includes("spawn-error]"))
36157
- return true;
36158
- if (line.includes(" error]"))
36159
- return true;
36160
- if (line.includes(" run-error]"))
36161
- return true;
36162
- return false;
36163
- }
36164
36146
  function extractName(e) {
36165
36147
  const name = e.name;
36166
36148
  if (typeof name === "string")
@@ -36191,12 +36173,7 @@ function messageFromData(data) {
36191
36173
  const msg = data.message;
36192
36174
  return typeof msg === "string" ? msg.toLowerCase() : undefined;
36193
36175
  }
36194
- function formatTerminalProviderFailureMessage(logLines) {
36195
- const blob = logLines.join(`
36196
- `).trim();
36197
- return blob ? `Fatal harness error (non-retryable): ${blob.slice(-500)}` : "Fatal harness error (non-retryable)";
36198
- }
36199
- var FATAL_HARNESS_PHRASES, QUOTA_PHRASES, PROVIDER_ERROR_NAMES, PROVIDER_RATE_LIMIT_AGENT_END_MARKER;
36176
+ var FATAL_HARNESS_PHRASES, QUOTA_PHRASES, PROVIDER_ERROR_NAMES;
36200
36177
  var init_terminal_provider_error = __esm(() => {
36201
36178
  FATAL_HARNESS_PHRASES = [
36202
36179
  "failed to load model",
@@ -36217,7 +36194,6 @@ var init_terminal_provider_error = __esm(() => {
36217
36194
  "exceeded your weekly"
36218
36195
  ];
36219
36196
  PROVIDER_ERROR_NAMES = ["ai_apicallerror", "ai_retryerror"];
36220
- PROVIDER_RATE_LIMIT_AGENT_END_MARKER = /\bagent_end]\s*reason:\s*provider_rate_limit\b/;
36221
36197
  });
36222
36198
 
36223
36199
  // src/infrastructure/services/remote-agents/opencode-sdk/session-event-forwarder.ts
@@ -36269,10 +36245,12 @@ function startSessionEventForwarder(client4, options) {
36269
36245
  let doneResolve;
36270
36246
  let sessionStarted = false;
36271
36247
  let terminalAbortRequested = false;
36248
+ let agentEndEmitted = false;
36272
36249
  const seenToolStates = new Map;
36273
36250
  let lastStatus;
36274
36251
  const agentEndCallbacks = [];
36275
36252
  const recentLogLines = [];
36253
+ let retryIdleTimeout;
36276
36254
  const donePromise = new Promise((resolve2) => {
36277
36255
  doneResolve = resolve2;
36278
36256
  });
@@ -36295,7 +36273,26 @@ function startSessionEventForwarder(client4, options) {
36295
36273
  options.onActivity?.();
36296
36274
  }
36297
36275
  }
36276
+ function clearRetryIdleTimeout() {
36277
+ if (retryIdleTimeout !== undefined) {
36278
+ clearTimeout(retryIdleTimeout);
36279
+ retryIdleTimeout = undefined;
36280
+ }
36281
+ }
36282
+ function scheduleRetryIdleTimeout() {
36283
+ clearRetryIdleTimeout();
36284
+ const timeoutMs = options.sessionRetryIdleTimeoutMs ?? DEFAULT_SESSION_RETRY_IDLE_TIMEOUT_MS;
36285
+ retryIdleTimeout = setTimeout(() => {
36286
+ if (cancelled || terminalAbortRequested)
36287
+ return;
36288
+ logLine(target, "status", "retry_timeout");
36289
+ emitAgentEnd();
36290
+ }, timeoutMs);
36291
+ }
36298
36292
  function emitAgentEnd(reason) {
36293
+ if (agentEndEmitted)
36294
+ return;
36295
+ agentEndEmitted = true;
36299
36296
  logLine(target, "agent_end", reason ? `reason: ${reason}` : undefined);
36300
36297
  for (const cb of agentEndCallbacks)
36301
36298
  cb();
@@ -36338,11 +36335,6 @@ function startSessionEventForwarder(client4, options) {
36338
36335
  if (chunk2)
36339
36336
  logLine(target, "thinking", chunk2);
36340
36337
  }
36341
- async function handleToolPartUpdate(part, props, toolStates) {
36342
- if (!part.tool)
36343
- return;
36344
- await handleToolPart(part, props, toolStates);
36345
- }
36346
36338
  async function handlePartUpdated(props) {
36347
36339
  const part = props.part;
36348
36340
  const partType = part?.type;
@@ -36351,7 +36343,7 @@ function startSessionEventForwarder(client4, options) {
36351
36343
  const dispatch = {
36352
36344
  text: () => handleTextPartUpdate(props, part),
36353
36345
  reasoning: () => handleReasoningPartUpdate(props, part),
36354
- tool: () => handleToolPartUpdate(part, props, seenToolStates)
36346
+ tool: () => part.tool ? handleToolPart(part, props, seenToolStates) : Promise.resolve()
36355
36347
  };
36356
36348
  await dispatch[partType]?.();
36357
36349
  }
@@ -36385,6 +36377,7 @@ function startSessionEventForwarder(client4, options) {
36385
36377
  logLine(target, "file", formatFilePayload(props));
36386
36378
  }
36387
36379
  async function handleSessionIdle() {
36380
+ clearRetryIdleTimeout();
36388
36381
  if (terminalAbortRequested)
36389
36382
  return;
36390
36383
  emitAgentEnd();
@@ -36397,9 +36390,15 @@ function startSessionEventForwarder(client4, options) {
36397
36390
  if (currentStatus !== lastStatus) {
36398
36391
  lastStatus = currentStatus;
36399
36392
  logLine(target, "status", currentStatus);
36400
- if (currentStatus === "retry" && (terminalAbortRequested || isTerminalProviderFailureInLogs(recentLogLines))) {
36401
- abortTerminalProviderError();
36393
+ if (currentStatus === "retry") {
36394
+ if (terminalAbortRequested) {
36395
+ abortTerminalProviderError();
36396
+ return;
36397
+ }
36398
+ scheduleRetryIdleTimeout();
36399
+ return;
36402
36400
  }
36401
+ clearRetryIdleTimeout();
36403
36402
  }
36404
36403
  }
36405
36404
  async function handleSessionError(props) {
@@ -36484,6 +36483,7 @@ function startSessionEventForwarder(client4, options) {
36484
36483
  return {
36485
36484
  stop: () => {
36486
36485
  cancelled = true;
36486
+ clearRetryIdleTimeout();
36487
36487
  },
36488
36488
  done: donePromise,
36489
36489
  onAgentEnd: (cb) => {
@@ -36492,9 +36492,10 @@ function startSessionEventForwarder(client4, options) {
36492
36492
  abortTerminalProviderError
36493
36493
  };
36494
36494
  }
36495
- var RECENT_LOG_LINE_CAP = 20;
36495
+ var RECENT_LOG_LINE_CAP = 20, DEFAULT_SESSION_RETRY_IDLE_TIMEOUT_MS;
36496
36496
  var init_session_event_forwarder = __esm(() => {
36497
36497
  init_terminal_provider_error();
36498
+ DEFAULT_SESSION_RETRY_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
36498
36499
  });
36499
36500
 
36500
36501
  // src/infrastructure/services/remote-agents/opencode-sdk/session-metadata-store.ts
@@ -36594,10 +36595,10 @@ var init_opencode_sdk_agent_service = __esm(() => {
36594
36595
  init_parse_listening_url();
36595
36596
  init_session_event_forwarder();
36596
36597
  init_session_metadata_store();
36597
- init_terminal_provider_error();
36598
36598
  OpenCodeSdkAgentService = class OpenCodeSdkAgentService extends OpenCodeBinaryAgentService {
36599
36599
  agentEndCallbacksByPid = new Map;
36600
36600
  assistantTextCallbacksByPid = new Map;
36601
+ outputCallbacksByPid = new Map;
36601
36602
  id = "opencode-sdk";
36602
36603
  displayName = "OpenCode (SDK)";
36603
36604
  listModelsHarnessId = "opencode-sdk";
@@ -36638,7 +36639,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
36638
36639
  await super.stop(pid);
36639
36640
  }
36640
36641
  spawnServeProcess(workingDir, resolvedConvexUrl) {
36641
- const childProcess = this.deps.spawn(OPENCODE_COMMAND2, ["serve", "--print-logs"], {
36642
+ const childProcess = this.deps.spawn(OPENCODE_COMMAND2, ["serve", "--print-logs", "--log-level", "WARN"], {
36642
36643
  cwd: workingDir,
36643
36644
  stdio: ["pipe", "pipe", "pipe"],
36644
36645
  shell: false,
@@ -36677,11 +36678,8 @@ var init_opencode_sdk_agent_service = __esm(() => {
36677
36678
  }
36678
36679
  if (childProcess.stderr) {
36679
36680
  const stderrBuffer = new StderrLineBuffer((line) => {
36680
- emitLogLine(line);
36681
- const activeForwarder = this.forwarders.get(pid);
36682
- if (activeForwarder && isNonRetryableHarnessFailureText(line)) {
36683
- activeForwarder.abortTerminalProviderError();
36684
- }
36681
+ if (!isInfoLine(line))
36682
+ emitLogLine(line);
36685
36683
  });
36686
36684
  childProcess.stderr.on("data", (chunk2) => {
36687
36685
  entry.lastOutputAt = Date.now();
@@ -36694,6 +36692,18 @@ var init_opencode_sdk_agent_service = __esm(() => {
36694
36692
  });
36695
36693
  }
36696
36694
  }
36695
+ createSessionForwarder(client4, sessionId, context5, emitLogLine, emitAssistantText, outputCallbacks) {
36696
+ return startSessionEventForwarder(client4, {
36697
+ sessionId,
36698
+ role: context5.role,
36699
+ onLogLine: emitLogLine,
36700
+ onAssistantText: emitAssistantText,
36701
+ onActivity: () => {
36702
+ for (const cb of outputCallbacks)
36703
+ cb();
36704
+ }
36705
+ });
36706
+ }
36697
36707
  registerRunningSession(args2) {
36698
36708
  const {
36699
36709
  childProcess,
@@ -36726,6 +36736,9 @@ var init_opencode_sdk_agent_service = __esm(() => {
36726
36736
  if (args2.assistantTextCallbacks) {
36727
36737
  this.assistantTextCallbacksByPid.set(pid, args2.assistantTextCallbacks);
36728
36738
  }
36739
+ if (args2.outputCallbacks) {
36740
+ this.outputCallbacksByPid.set(pid, args2.outputCallbacks);
36741
+ }
36729
36742
  const outputCallbacks = args2.outputCallbacks ?? [];
36730
36743
  this.wireChildOutput(childProcess, pid, entry, emitLogLine, outputCallbacks);
36731
36744
  return {
@@ -36745,6 +36758,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
36745
36758
  this.sessionStore.remove(sessionId);
36746
36759
  this.agentEndCallbacksByPid.delete(pid);
36747
36760
  this.assistantTextCallbacksByPid.delete(pid);
36761
+ this.outputCallbacksByPid.delete(pid);
36748
36762
  this.deleteProcess(pid);
36749
36763
  cb({ code: code2, signal, context: context5 });
36750
36764
  });
@@ -36834,13 +36848,20 @@ var init_opencode_sdk_agent_service = __esm(() => {
36834
36848
  throw new Error("Failed to create session during resume fallback");
36835
36849
  }
36836
36850
  const newSessionId2 = sessionCreateResult.data.id;
36851
+ const { emitLogLine } = this.createLogLineEmitter();
36852
+ const pidOutputCallbacks = this.outputCallbacksByPid.get(args2.pid) ?? [];
36837
36853
  const forwarder = startSessionEventForwarder(client4, {
36838
36854
  sessionId: newSessionId2,
36839
36855
  role: args2.context.role,
36856
+ onLogLine: emitLogLine,
36840
36857
  onAssistantText: (text) => {
36841
36858
  const callbacks2 = this.assistantTextCallbacksByPid.get(args2.pid) ?? [];
36842
36859
  for (const cb of callbacks2)
36843
36860
  cb(text);
36861
+ },
36862
+ onActivity: () => {
36863
+ for (const cb of pidOutputCallbacks)
36864
+ cb();
36844
36865
  }
36845
36866
  });
36846
36867
  const callbacks = this.agentEndCallbacksByPid.get(args2.pid) ?? [];
@@ -36884,16 +36905,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
36884
36905
  if (!sessionInfo.data?.id) {
36885
36906
  throw new Error(`OpenCode session ${sessionId} not found (sessions may not survive serve restart)`);
36886
36907
  }
36887
- forwarder = startSessionEventForwarder(client4, {
36888
- sessionId,
36889
- role: context5.role,
36890
- onLogLine: emitLogLine,
36891
- onAssistantText: emitAssistantText,
36892
- onActivity: () => {
36893
- for (const cb of outputCallbacks)
36894
- cb();
36895
- }
36896
- });
36908
+ forwarder = this.createSessionForwarder(client4, sessionId, context5, emitLogLine, emitAssistantText, outputCallbacks);
36897
36909
  const availableAgents = await this.listAvailableAgents(client4);
36898
36910
  const agentDef = availableAgents.find((a) => a.name === agentName);
36899
36911
  const composedSystem = composeSystemPrompt(agentDef?.prompt, systemPrompt);
@@ -36941,16 +36953,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
36941
36953
  throw new Error("Failed to create session");
36942
36954
  }
36943
36955
  sessionId = sessionCreateResult.data.id;
36944
- forwarder = startSessionEventForwarder(client4, {
36945
- sessionId,
36946
- role: context5.role,
36947
- onLogLine: emitLogLine,
36948
- onAssistantText: emitAssistantText,
36949
- onActivity: () => {
36950
- for (const cb of outputCallbacks)
36951
- cb();
36952
- }
36953
- });
36956
+ forwarder = this.createSessionForwarder(client4, sessionId, context5, emitLogLine, emitAssistantText, outputCallbacks);
36954
36957
  const availableAgents = await this.listAvailableAgents(client4);
36955
36958
  const selected = selectAgent(availableAgents);
36956
36959
  agentName = selected.name;
@@ -37772,10 +37775,17 @@ var init_detection = __esm(() => {
37772
37775
  var MACHINE_CONFIG_VERSION = "1";
37773
37776
 
37774
37777
  // src/infrastructure/machine/storage.ts
37775
- import { randomUUID as randomUUID3 } from "node:crypto";
37778
+ import { randomBytes, randomUUID as randomUUID3 } from "node:crypto";
37776
37779
  import * as fs4 from "node:fs/promises";
37777
37780
  import { homedir as homedir3, hostname as hostname2 } from "node:os";
37778
37781
  import { join as join11 } from "node:path";
37782
+ function enqueueMachineConfigSave(task) {
37783
+ const run3 = saveChain.then(task, task);
37784
+ saveChain = run3.catch(() => {
37785
+ return;
37786
+ });
37787
+ return run3;
37788
+ }
37779
37789
  function chatroomConfigDir() {
37780
37790
  return join11(homedir3(), ".chatroom");
37781
37791
  }
@@ -37801,13 +37811,20 @@ async function loadConfigFile() {
37801
37811
  return null;
37802
37812
  }
37803
37813
  }
37804
- async function saveConfigFile(configFile) {
37814
+ async function writeConfigFileAtomically(configFile) {
37805
37815
  await ensureConfigDir2();
37806
37816
  const configPath = getMachineConfigPath();
37807
- const tempPath = `${configPath}.tmp`;
37817
+ const tempPath = `${configPath}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
37808
37818
  const content = JSON.stringify(configFile, null, 2);
37809
37819
  await fs4.writeFile(tempPath, content, { mode: 384 });
37810
- await fs4.rename(tempPath, configPath);
37820
+ try {
37821
+ await fs4.rename(tempPath, configPath);
37822
+ } catch (error) {
37823
+ await fs4.unlink(tempPath).catch(() => {
37824
+ return;
37825
+ });
37826
+ throw error;
37827
+ }
37811
37828
  }
37812
37829
  async function loadMachineConfig() {
37813
37830
  const configFile = await loadConfigFile();
@@ -37817,13 +37834,15 @@ async function loadMachineConfig() {
37817
37834
  return configFile.machines[convexUrl] ?? null;
37818
37835
  }
37819
37836
  async function saveMachineConfig(config2) {
37820
- const configFile = await loadConfigFile() ?? {
37821
- version: MACHINE_CONFIG_VERSION,
37822
- machines: {}
37823
- };
37824
- const convexUrl = getConvexUrl();
37825
- configFile.machines[convexUrl] = config2;
37826
- await saveConfigFile(configFile);
37837
+ await enqueueMachineConfigSave(async () => {
37838
+ const configFile = await loadConfigFile() ?? {
37839
+ version: MACHINE_CONFIG_VERSION,
37840
+ machines: {}
37841
+ };
37842
+ const convexUrl = getConvexUrl();
37843
+ configFile.machines[convexUrl] = config2;
37844
+ await writeConfigFileAtomically(configFile);
37845
+ });
37827
37846
  }
37828
37847
  async function createNewEndpointConfig() {
37829
37848
  const now = new Date().toISOString();
@@ -37867,10 +37886,11 @@ async function getMachineId() {
37867
37886
  const config2 = await loadMachineConfig();
37868
37887
  return config2?.machineId ?? null;
37869
37888
  }
37870
- var MACHINE_FILE = "machine.json";
37889
+ var MACHINE_FILE = "machine.json", saveChain;
37871
37890
  var init_storage2 = __esm(() => {
37872
37891
  init_detection();
37873
37892
  init_client2();
37893
+ saveChain = Promise.resolve();
37874
37894
  });
37875
37895
 
37876
37896
  // src/infrastructure/machine/daemon-state.ts
@@ -80480,6 +80500,8 @@ ${getRoleGuidanceDisclosureBlock(roleGuidanceContext)}
80480
80500
  <!-- Demonstrate adherence to:
80481
80501
  - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
80482
80502
  - 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.
80503
+ - No Revisit: implemented in a way so the user does not have to revisit this implementation again.
80504
+ - Leave It Better: leave the code in a slightly better state than before when touching files.
80483
80505
  -->
80484
80506
  <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
80485
80507
 
@@ -80641,6 +80663,8 @@ ${getRoleGuidanceDisclosureBlock(roleGuidanceContext)}
80641
80663
  <!-- Demonstrate adherence to:
80642
80664
  - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
80643
80665
  - 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.
80666
+ - No Revisit: implemented in a way so the user does not have to revisit this implementation again.
80667
+ - Leave It Better: leave the code in a slightly better state than before when touching files.
80644
80668
  -->
80645
80669
  <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
80646
80670
 
@@ -80741,6 +80765,8 @@ ${getRoleGuidanceDisclosureBlock(roleGuidanceContext)}
80741
80765
  <!-- Demonstrate adherence to:
80742
80766
  - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
80743
80767
  - 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.
80768
+ - No Revisit: implemented in a way so the user does not have to revisit this implementation again.
80769
+ - Leave It Better: leave the code in a slightly better state than before when touching files.
80744
80770
  -->
80745
80771
  <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
80746
80772
 
@@ -84686,6 +84712,7 @@ var DaemonMachineService, DaemonMachineServiceLive = (ops) => exports_Layer.succ
84686
84712
  recover: () => exports_Effect.promise(() => mgr.recover()),
84687
84713
  getSlot: (chatroomId, role) => mgr.getSlot(chatroomId, role),
84688
84714
  listActive: () => mgr.listActive(),
84715
+ clearStuckStoppingSlot: (chatroomId, role) => exports_Effect.promise(() => mgr.clearStuckStoppingSlot(chatroomId, role)),
84689
84716
  whenTurnEndsIdle: () => exports_Effect.promise(() => mgr.whenTurnEndsIdle()),
84690
84717
  resumeTurnForSlot: (args2) => exports_Effect.promise(() => mgr.resumeTurnForSlot(args2)),
84691
84718
  setLastInFlightTask: (chatroomId, role, taskId) => exports_Effect.sync(() => mgr.setLastInFlightTask(chatroomId, role, taskId))
@@ -86028,9 +86055,369 @@ var init_pid = __esm(() => {
86028
86055
  CHATROOM_DIR4 = join17(homedir6(), ".chatroom");
86029
86056
  });
86030
86057
 
86031
- // src/infrastructure/harnesses/cursor-sdk/cursor-session.ts
86058
+ // src/infrastructure/harnesses/claude-sdk/claude-session.ts
86032
86059
  import { randomUUID as randomUUID4 } from "node:crypto";
86033
86060
 
86061
+ class ClaudeSdkSession {
86062
+ opencodeSessionId;
86063
+ _sessionTitle;
86064
+ get sessionTitle() {
86065
+ return this._sessionTitle;
86066
+ }
86067
+ cwd;
86068
+ executablePath;
86069
+ query;
86070
+ defaultModel;
86071
+ storedSystemPrompt;
86072
+ onClose;
86073
+ listeners = new Set;
86074
+ closed = false;
86075
+ providerSessionId;
86076
+ resumeOnFirstQuery;
86077
+ isFirstQuery = true;
86078
+ activeQuery;
86079
+ sawTextDelta = false;
86080
+ sawThinkingDelta = false;
86081
+ constructor(options) {
86082
+ this.cwd = options.cwd;
86083
+ this.executablePath = options.executablePath;
86084
+ this.query = options.query;
86085
+ this.opencodeSessionId = options.opencodeSessionId;
86086
+ this._sessionTitle = options.sessionTitle;
86087
+ this.defaultModel = options.defaultModel;
86088
+ this.storedSystemPrompt = options.systemPrompt;
86089
+ this.providerSessionId = options.providerSessionId;
86090
+ this.resumeOnFirstQuery = options.resumeOnFirstQuery ?? false;
86091
+ this.onClose = options.onClose;
86092
+ }
86093
+ setTitle(title) {
86094
+ this._sessionTitle = title;
86095
+ }
86096
+ async prompt(input) {
86097
+ if (this.closed)
86098
+ throw new Error("Session is closed");
86099
+ const text = input.parts.map((p) => p.text).join(`
86100
+ `);
86101
+ const messageId = randomUUID4();
86102
+ const model = resolveClaudeModel(this.defaultModel, input.model);
86103
+ const useResume = Boolean(this.providerSessionId) && (!this.isFirstQuery || this.resumeOnFirstQuery);
86104
+ this.sawTextDelta = false;
86105
+ this.sawThinkingDelta = false;
86106
+ const queryInstance = this.query({
86107
+ prompt: text,
86108
+ options: {
86109
+ cwd: this.cwd,
86110
+ model,
86111
+ maxTurns: DEFAULT_MAX_TURNS3,
86112
+ pathToClaudeCodeExecutable: this.executablePath,
86113
+ includePartialMessages: true,
86114
+ systemPrompt: this.isFirstQuery && !this.resumeOnFirstQuery ? this.storedSystemPrompt : undefined,
86115
+ resume: useResume ? this.providerSessionId : undefined,
86116
+ settingSources: [],
86117
+ permissionMode: "bypassPermissions",
86118
+ allowDangerouslySkipPermissions: true,
86119
+ effort: DEFAULT_EFFORT2,
86120
+ canUseTool: async (_toolName, toolInput) => ({
86121
+ behavior: "allow",
86122
+ updatedInput: toolInput
86123
+ })
86124
+ }
86125
+ });
86126
+ this.activeQuery = queryInstance;
86127
+ if (this.resumeOnFirstQuery) {
86128
+ this.resumeOnFirstQuery = false;
86129
+ }
86130
+ this.isFirstQuery = false;
86131
+ try {
86132
+ await withTimeout((async () => {
86133
+ for await (const message of queryInstance) {
86134
+ if (this.closed)
86135
+ break;
86136
+ this.captureProviderSessionId(message);
86137
+ this.emitFromSdkMessage(message, messageId);
86138
+ if (message.type === "result") {
86139
+ if (message.is_error) {
86140
+ const errors2 = "errors" in message && Array.isArray(message.errors) ? message.errors.join("; ") : "turn failed";
86141
+ throw new Error(errors2);
86142
+ }
86143
+ break;
86144
+ }
86145
+ }
86146
+ })(), QUERY_TIMEOUT_MS, "query");
86147
+ } finally {
86148
+ this.activeQuery = undefined;
86149
+ }
86150
+ this.emit({ type: "session.idle", payload: {}, timestamp: Date.now() });
86151
+ }
86152
+ onEvent(listener) {
86153
+ this.listeners.add(listener);
86154
+ return () => {
86155
+ this.listeners.delete(listener);
86156
+ };
86157
+ }
86158
+ async close() {
86159
+ if (this.closed)
86160
+ return;
86161
+ this.closed = true;
86162
+ try {
86163
+ await this.activeQuery?.interrupt();
86164
+ } catch {}
86165
+ this.activeQuery = undefined;
86166
+ this.onClose?.(this.opencodeSessionId);
86167
+ this.listeners.clear();
86168
+ }
86169
+ emit(event) {
86170
+ for (const listener of this.listeners) {
86171
+ listener(event);
86172
+ }
86173
+ }
86174
+ emitDelta(messageId, delta, partType) {
86175
+ if (!delta)
86176
+ return;
86177
+ this.emit({
86178
+ type: "message.part.delta",
86179
+ payload: { messageID: messageId, delta, partType },
86180
+ timestamp: Date.now()
86181
+ });
86182
+ }
86183
+ captureProviderSessionId(message) {
86184
+ if (!("session_id" in message) || typeof message.session_id !== "string") {
86185
+ return;
86186
+ }
86187
+ const sessionId = message.session_id;
86188
+ if (this.providerSessionId === sessionId) {
86189
+ return;
86190
+ }
86191
+ const isFirstAllocation = !this.providerSessionId;
86192
+ this.providerSessionId = sessionId;
86193
+ if (isFirstAllocation) {
86194
+ this.emit({
86195
+ type: "session.provider_id",
86196
+ payload: { sessionId },
86197
+ timestamp: Date.now()
86198
+ });
86199
+ }
86200
+ }
86201
+ emitFromSdkMessage(message, messageId) {
86202
+ switch (message.type) {
86203
+ case "stream_event": {
86204
+ const event = message.event;
86205
+ if (event.type === "content_block_delta") {
86206
+ const delta = event.delta;
86207
+ if (delta.type === "text_delta") {
86208
+ this.sawTextDelta = true;
86209
+ this.emitDelta(messageId, delta.text, "text");
86210
+ } else if (delta.type === "thinking_delta") {
86211
+ this.sawThinkingDelta = true;
86212
+ this.emitDelta(messageId, delta.thinking, "reasoning");
86213
+ }
86214
+ }
86215
+ break;
86216
+ }
86217
+ case "assistant":
86218
+ for (const block of message.message.content) {
86219
+ if (block.type === "text" && block.text) {
86220
+ if (!this.sawTextDelta) {
86221
+ this.emitDelta(messageId, block.text, "text");
86222
+ }
86223
+ } else if (block.type === "thinking" && block.thinking) {
86224
+ if (!this.sawThinkingDelta) {
86225
+ this.emitDelta(messageId, block.thinking, "reasoning");
86226
+ }
86227
+ }
86228
+ }
86229
+ break;
86230
+ default:
86231
+ break;
86232
+ }
86233
+ }
86234
+ }
86235
+ function resolveClaudeModel(defaultModel, promptModel) {
86236
+ if (promptModel) {
86237
+ if (promptModel.providerID === "anthropic")
86238
+ return promptModel.modelID;
86239
+ return promptModel.modelID;
86240
+ }
86241
+ if (defaultModel) {
86242
+ const slash = defaultModel.indexOf("/");
86243
+ if (slash === -1)
86244
+ return defaultModel;
86245
+ const provider = defaultModel.slice(0, slash);
86246
+ const modelId = defaultModel.slice(slash + 1);
86247
+ if (provider === "anthropic")
86248
+ return modelId;
86249
+ return modelId;
86250
+ }
86251
+ return "sonnet";
86252
+ }
86253
+ var DEFAULT_MAX_TURNS3 = 200, DEFAULT_EFFORT2 = "medium", QUERY_TIMEOUT_MS = 3600000;
86254
+ var init_claude_session = () => {};
86255
+
86256
+ // src/infrastructure/harnesses/claude-sdk/claude-harness.ts
86257
+ import { randomUUID as randomUUID5 } from "node:crypto";
86258
+ async function loadSdk4() {
86259
+ if (_sdkCache4)
86260
+ return _sdkCache4;
86261
+ if (_sdkLoadError4)
86262
+ throw _sdkLoadError4;
86263
+ try {
86264
+ _sdkCache4 = await importBundledClaudeSdk();
86265
+ return _sdkCache4;
86266
+ } catch (err) {
86267
+ _sdkLoadError4 = err;
86268
+ throw err;
86269
+ }
86270
+ }
86271
+ async function loadExecutablePath() {
86272
+ if (_executablePathCache)
86273
+ return _executablePathCache;
86274
+ _executablePathCache = await resolvePathToClaudeCodeExecutable();
86275
+ return _executablePathCache;
86276
+ }
86277
+
86278
+ class ClaudeSdkHarness {
86279
+ type = "claude-sdk";
86280
+ displayName = "Claude (SDK)";
86281
+ cwd;
86282
+ query;
86283
+ executablePath;
86284
+ closed = false;
86285
+ sessions = new Map;
86286
+ constructor(cwd, sdk, executablePath) {
86287
+ this.cwd = cwd;
86288
+ this.query = sdk.query;
86289
+ this.executablePath = executablePath;
86290
+ }
86291
+ async models() {
86292
+ const providers = await this.listProviders();
86293
+ const models = [];
86294
+ for (const provider of providers) {
86295
+ for (const model of provider.models) {
86296
+ models.push({
86297
+ id: `${provider.providerID}/${model.modelID}`,
86298
+ name: model.name,
86299
+ provider: provider.name
86300
+ });
86301
+ }
86302
+ }
86303
+ return models;
86304
+ }
86305
+ async listAgents() {
86306
+ return [{ name: "builder", mode: "primary" }];
86307
+ }
86308
+ async listProviders() {
86309
+ const dynamic = await fetchClaudeModels();
86310
+ const modelIds = dynamic ?? [...CLAUDE_FALLBACK_MODELS];
86311
+ return [
86312
+ {
86313
+ providerID: "anthropic",
86314
+ name: "Anthropic",
86315
+ models: modelIds.map((modelID) => ({ modelID, name: modelID }))
86316
+ }
86317
+ ];
86318
+ }
86319
+ async newSession(config3) {
86320
+ if (this.closed)
86321
+ throw new Error("Harness is closed");
86322
+ const opencodeSessionId = randomUUID5();
86323
+ const session2 = new ClaudeSdkSession({
86324
+ cwd: this.cwd,
86325
+ executablePath: this.executablePath,
86326
+ query: this.query,
86327
+ opencodeSessionId,
86328
+ sessionTitle: config3.title ?? "",
86329
+ defaultModel: config3.model ?? DEFAULT_MODEL2,
86330
+ systemPrompt: config3.systemPrompt,
86331
+ onClose: (id3) => this.sessions.delete(id3)
86332
+ });
86333
+ this.sessions.set(opencodeSessionId, session2);
86334
+ return session2;
86335
+ }
86336
+ async resumeSession(sessionId, _options) {
86337
+ if (this.closed)
86338
+ throw new Error("Harness is closed");
86339
+ const existing = this.sessions.get(sessionId);
86340
+ if (existing)
86341
+ return existing;
86342
+ const session2 = new ClaudeSdkSession({
86343
+ cwd: this.cwd,
86344
+ executablePath: this.executablePath,
86345
+ query: this.query,
86346
+ opencodeSessionId: sessionId,
86347
+ sessionTitle: "",
86348
+ providerSessionId: sessionId,
86349
+ resumeOnFirstQuery: true,
86350
+ onClose: (id3) => this.sessions.delete(id3)
86351
+ });
86352
+ this.sessions.set(sessionId, session2);
86353
+ return session2;
86354
+ }
86355
+ async fetchSessionTitle(_opencodeSessionId) {
86356
+ return;
86357
+ }
86358
+ isAlive() {
86359
+ return !this.closed;
86360
+ }
86361
+ async close() {
86362
+ if (this.closed)
86363
+ return;
86364
+ this.closed = true;
86365
+ const closing = [...this.sessions.values()].map((s) => s.close().catch(() => {}));
86366
+ await Promise.all(closing);
86367
+ this.sessions.clear();
86368
+ }
86369
+ }
86370
+ var DEFAULT_MODEL2 = "anthropic/sonnet", _sdkCache4, _sdkLoadError4, _executablePathCache, startClaudeSdkHarness = async (config3) => {
86371
+ try {
86372
+ const sdk = await loadSdk4();
86373
+ const executablePath = await loadExecutablePath();
86374
+ return new ClaudeSdkHarness(config3.workingDir, sdk, executablePath);
86375
+ } catch (err) {
86376
+ throw new Error(`claude-sdk unavailable: ${formatClaudeSdkLoadError(err)}`);
86377
+ }
86378
+ };
86379
+ var init_claude_harness = __esm(() => {
86380
+ init_claude_session();
86381
+ init_claude_models();
86382
+ init_claude_sdk_package();
86383
+ });
86384
+
86385
+ // src/infrastructure/harnesses/shared-chunk-extractor.ts
86386
+ function createStandardSdkChunkExtractor() {
86387
+ return function extract(event) {
86388
+ if (event.type !== "message.part.delta")
86389
+ return null;
86390
+ const payload = event.payload;
86391
+ const delta = payload?.delta;
86392
+ if (!delta || delta.length === 0)
86393
+ return null;
86394
+ const messageId = payload?.messageID;
86395
+ if (!messageId)
86396
+ return null;
86397
+ return {
86398
+ content: delta,
86399
+ messageId,
86400
+ partType: payload?.partType ?? "text"
86401
+ };
86402
+ };
86403
+ }
86404
+
86405
+ // src/infrastructure/harnesses/claude-sdk/event-extractor.ts
86406
+ function createClaudeSdkChunkExtractor() {
86407
+ return createStandardSdkChunkExtractor();
86408
+ }
86409
+ var init_event_extractor = () => {};
86410
+
86411
+ // src/infrastructure/harnesses/claude-sdk/index.ts
86412
+ var init_claude_sdk2 = __esm(() => {
86413
+ init_claude_harness();
86414
+ init_claude_session();
86415
+ init_event_extractor();
86416
+ });
86417
+
86418
+ // src/infrastructure/harnesses/cursor-sdk/cursor-session.ts
86419
+ import { randomUUID as randomUUID6 } from "node:crypto";
86420
+
86034
86421
  class CursorSdkSession {
86035
86422
  opencodeSessionId;
86036
86423
  _sessionTitle;
@@ -86056,13 +86443,13 @@ class CursorSdkSession {
86056
86443
  throw new Error("Session is closed");
86057
86444
  const text = input.parts.map((p) => p.text).join(`
86058
86445
  `);
86059
- const messageId = randomUUID4();
86446
+ const messageId = randomUUID6();
86060
86447
  const isFirstTurn = this.turnCount === 0;
86061
86448
  this.turnCount += 1;
86062
86449
  const modelId = input.model ? resolveModelFromPrompt(input.model.providerID, input.model.modelID) : undefined;
86063
86450
  const run3 = await withTimeout(this.agent.send(text, {
86064
86451
  local: { force: isFirstTurn },
86065
- idempotencyKey: randomUUID4(),
86452
+ idempotencyKey: randomUUID6(),
86066
86453
  ...modelId ? { model: { id: modelId } } : {}
86067
86454
  }), SEND_TIMEOUT_MS2, "agent.send");
86068
86455
  for await (const message of run3.stream()) {
@@ -86135,16 +86522,16 @@ var SEND_TIMEOUT_MS2 = 60000, RUN_WAIT_TIMEOUT_MS2 = 3600000;
86135
86522
  var init_cursor_session = () => {};
86136
86523
 
86137
86524
  // src/infrastructure/harnesses/cursor-sdk/cursor-harness.ts
86138
- async function loadSdk4() {
86139
- if (_sdkCache4)
86140
- return _sdkCache4;
86141
- if (_sdkLoadError4)
86142
- throw _sdkLoadError4;
86525
+ async function loadSdk5() {
86526
+ if (_sdkCache5)
86527
+ return _sdkCache5;
86528
+ if (_sdkLoadError5)
86529
+ throw _sdkLoadError5;
86143
86530
  try {
86144
- _sdkCache4 = await importBundledCursorSdk();
86145
- return _sdkCache4;
86531
+ _sdkCache5 = await importBundledCursorSdk();
86532
+ return _sdkCache5;
86146
86533
  } catch (err) {
86147
- _sdkLoadError4 = err;
86534
+ _sdkLoadError5 = err;
86148
86535
  throw err;
86149
86536
  }
86150
86537
  }
@@ -86179,7 +86566,7 @@ class CursorSdkHarness {
86179
86566
  const apiKey = process.env.CURSOR_API_KEY?.trim();
86180
86567
  if (!apiKey)
86181
86568
  return [];
86182
- const { Cursor } = await loadSdk4();
86569
+ const { Cursor } = await loadSdk5();
86183
86570
  const listed = await withTimeout(Cursor.models.list({ apiKey }), MODELS_LIST_TIMEOUT_MS2, "Cursor.models.list");
86184
86571
  const modelIds = normalizeCursorSdkListedModels(listed.map((m) => m.id).filter((id3) => id3.length > 0));
86185
86572
  return [
@@ -86196,8 +86583,8 @@ class CursorSdkHarness {
86196
86583
  const apiKey = process.env.CURSOR_API_KEY?.trim();
86197
86584
  if (!apiKey)
86198
86585
  throw new Error("CURSOR_API_KEY is not set");
86199
- const modelId = resolveCursorSdkModel(config3.model ?? DEFAULT_MODEL2);
86200
- const { Agent } = await loadSdk4();
86586
+ const modelId = resolveCursorSdkModel(config3.model ?? DEFAULT_MODEL3);
86587
+ const { Agent } = await loadSdk5();
86201
86588
  const agent = await withTimeout(Agent.create({
86202
86589
  apiKey,
86203
86590
  model: { id: modelId },
@@ -86221,10 +86608,10 @@ class CursorSdkHarness {
86221
86608
  const apiKey = process.env.CURSOR_API_KEY?.trim();
86222
86609
  if (!apiKey)
86223
86610
  throw new Error("CURSOR_API_KEY is not set");
86224
- const { Agent } = await loadSdk4();
86611
+ const { Agent } = await loadSdk5();
86225
86612
  const agent = await withTimeout(Agent.resume(sessionId, {
86226
86613
  apiKey,
86227
- model: { id: DEFAULT_MODEL2 },
86614
+ model: { id: DEFAULT_MODEL3 },
86228
86615
  local: { cwd: this.cwd, settingSources: [] }
86229
86616
  }), AGENT_CREATE_TIMEOUT_MS2, "Agent.resume");
86230
86617
  const session2 = new CursorSdkSession({
@@ -86251,9 +86638,9 @@ class CursorSdkHarness {
86251
86638
  this.sessions.clear();
86252
86639
  }
86253
86640
  }
86254
- var DEFAULT_MODEL2 = "composer-2.5", MODELS_LIST_TIMEOUT_MS2 = 60000, AGENT_CREATE_TIMEOUT_MS2 = 60000, _sdkCache4, _sdkLoadError4, startCursorSdkHarness = async (config3) => {
86641
+ var DEFAULT_MODEL3 = "composer-2.5", MODELS_LIST_TIMEOUT_MS2 = 60000, AGENT_CREATE_TIMEOUT_MS2 = 60000, _sdkCache5, _sdkLoadError5, startCursorSdkHarness = async (config3) => {
86255
86642
  try {
86256
- await loadSdk4();
86643
+ await loadSdk5();
86257
86644
  } catch (err) {
86258
86645
  throw new Error(`cursor-sdk unavailable: ${formatCursorSdkLoadError(err)}`);
86259
86646
  }
@@ -86267,37 +86654,17 @@ var init_cursor_harness = __esm(() => {
86267
86654
  init_cursor_sdk_package();
86268
86655
  });
86269
86656
 
86270
- // src/infrastructure/harnesses/shared-chunk-extractor.ts
86271
- function createStandardSdkChunkExtractor() {
86272
- return function extract(event) {
86273
- if (event.type !== "message.part.delta")
86274
- return null;
86275
- const payload = event.payload;
86276
- const delta = payload?.delta;
86277
- if (!delta || delta.length === 0)
86278
- return null;
86279
- const messageId = payload?.messageID;
86280
- if (!messageId)
86281
- return null;
86282
- return {
86283
- content: delta,
86284
- messageId,
86285
- partType: payload?.partType ?? "text"
86286
- };
86287
- };
86288
- }
86289
-
86290
86657
  // src/infrastructure/harnesses/cursor-sdk/event-extractor.ts
86291
86658
  function createCursorSdkChunkExtractor() {
86292
86659
  return createStandardSdkChunkExtractor();
86293
86660
  }
86294
- var init_event_extractor = () => {};
86661
+ var init_event_extractor2 = () => {};
86295
86662
 
86296
86663
  // src/infrastructure/harnesses/cursor-sdk/index.ts
86297
86664
  var init_cursor_sdk2 = __esm(() => {
86298
86665
  init_cursor_harness();
86299
86666
  init_cursor_session();
86300
- init_event_extractor();
86667
+ init_event_extractor2();
86301
86668
  });
86302
86669
 
86303
86670
  // src/infrastructure/harnesses/opencode-sdk/event-extractor.ts
@@ -86797,7 +87164,7 @@ class OpencodeSdkHarness {
86797
87164
  }
86798
87165
  }
86799
87166
  var OPENCODE_COMMAND3 = "opencode", SERVE_STARTUP_TIMEOUT_MS2 = 1e4, startOpencodeSdkHarness = async (config3) => {
86800
- const childProcess = spawn5(OPENCODE_COMMAND3, ["serve", "--print-logs"], {
87167
+ const childProcess = spawn5(OPENCODE_COMMAND3, ["serve", "--print-logs", "--log-level", "WARN"], {
86801
87168
  cwd: config3.workingDir,
86802
87169
  stdio: ["pipe", "pipe", "pipe"],
86803
87170
  shell: false,
@@ -86831,7 +87198,7 @@ var init_opencode_harness = __esm(() => {
86831
87198
  });
86832
87199
 
86833
87200
  // src/infrastructure/harnesses/pi-sdk/pi-session.ts
86834
- import { randomUUID as randomUUID5 } from "node:crypto";
87201
+ import { randomUUID as randomUUID7 } from "node:crypto";
86835
87202
 
86836
87203
  class PiSdkSession {
86837
87204
  opencodeSessionId;
@@ -86858,7 +87225,7 @@ class PiSdkSession {
86858
87225
  throw new Error("Session is closed");
86859
87226
  const text = input.parts.map((p) => p.text).join(`
86860
87227
  `);
86861
- const messageId = randomUUID5();
87228
+ const messageId = randomUUID7();
86862
87229
  const onSessionEvent = (event) => {
86863
87230
  if (this.closed)
86864
87231
  return;
@@ -86918,16 +87285,16 @@ var PROMPT_TIMEOUT_MS2 = 3600000;
86918
87285
  var init_pi_session = () => {};
86919
87286
 
86920
87287
  // src/infrastructure/harnesses/pi-sdk/pi-harness.ts
86921
- async function loadSdk5() {
86922
- if (_sdkCache5)
86923
- return _sdkCache5;
86924
- if (_sdkLoadError5)
86925
- throw _sdkLoadError5;
87288
+ async function loadSdk6() {
87289
+ if (_sdkCache6)
87290
+ return _sdkCache6;
87291
+ if (_sdkLoadError6)
87292
+ throw _sdkLoadError6;
86926
87293
  try {
86927
- _sdkCache5 = await importBundledPiSdk();
86928
- return _sdkCache5;
87294
+ _sdkCache6 = await importBundledPiSdk();
87295
+ return _sdkCache6;
86929
87296
  } catch (err) {
86930
- _sdkLoadError5 = err;
87297
+ _sdkLoadError6 = err;
86931
87298
  throw err;
86932
87299
  }
86933
87300
  }
@@ -86999,7 +87366,7 @@ class PiSdkHarness {
86999
87366
  const existing = this.sessions.get(sessionId);
87000
87367
  if (existing)
87001
87368
  return existing;
87002
- const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk5();
87369
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk6();
87003
87370
  const sessions = await SessionManager.list(this.cwd, getPiSessionDir(this.cwd));
87004
87371
  const match17 = sessions.find((s) => s.id === sessionId);
87005
87372
  if (!match17) {
@@ -87047,8 +87414,8 @@ class PiSdkHarness {
87047
87414
  this.sessions.clear();
87048
87415
  }
87049
87416
  async createAgentSession(systemPrompt, model) {
87050
- const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk5();
87051
- const resolvedModel = resolveModel2(this.modelRegistry, model ?? DEFAULT_MODEL3);
87417
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk6();
87418
+ const resolvedModel = resolveModel2(this.modelRegistry, model ?? DEFAULT_MODEL4);
87052
87419
  if (!resolvedModel) {
87053
87420
  throw new Error("No Pi model available — configure provider credentials in ~/.pi/agent/auth.json");
87054
87421
  }
@@ -87069,9 +87436,9 @@ class PiSdkHarness {
87069
87436
  return session2;
87070
87437
  }
87071
87438
  }
87072
- var SESSION_CREATE_TIMEOUT_MS3 = 60000, DEFAULT_MODEL3 = "opencode/big-pickle", _sdkCache5, _sdkLoadError5, startPiSdkHarness = async (config3) => {
87439
+ var SESSION_CREATE_TIMEOUT_MS3 = 60000, DEFAULT_MODEL4 = "opencode/big-pickle", _sdkCache6, _sdkLoadError6, startPiSdkHarness = async (config3) => {
87073
87440
  try {
87074
- const { AuthStorage, ModelRegistry } = await loadSdk5();
87441
+ const { AuthStorage, ModelRegistry } = await loadSdk6();
87075
87442
  const authStorage = AuthStorage.create();
87076
87443
  const modelRegistry = ModelRegistry.create(authStorage);
87077
87444
  if (modelRegistry.getAvailable().length === 0) {
@@ -87092,13 +87459,13 @@ var init_pi_harness = __esm(() => {
87092
87459
  function createPiSdkChunkExtractor() {
87093
87460
  return createStandardSdkChunkExtractor();
87094
87461
  }
87095
- var init_event_extractor2 = () => {};
87462
+ var init_event_extractor3 = () => {};
87096
87463
 
87097
87464
  // src/infrastructure/harnesses/pi-sdk/index.ts
87098
87465
  var init_pi_sdk2 = __esm(() => {
87099
87466
  init_pi_harness();
87100
87467
  init_pi_session();
87101
- init_event_extractor2();
87468
+ init_event_extractor3();
87102
87469
  });
87103
87470
 
87104
87471
  // src/infrastructure/harnesses/registry.ts
@@ -87111,6 +87478,8 @@ async function startBoundHarness(config3) {
87111
87478
  return startCursorSdkHarness(config3);
87112
87479
  case "pi-sdk":
87113
87480
  return startPiSdkHarness(config3);
87481
+ case "claude-sdk":
87482
+ return startClaudeSdkHarness(config3);
87114
87483
  default: {
87115
87484
  const _exhaustive = config3.harnessName;
87116
87485
  throw new Error(`Unsupported direct harness: ${String(_exhaustive)}`);
@@ -87125,6 +87494,8 @@ function createChunkExtractor(harnessName) {
87125
87494
  return createCursorSdkChunkExtractor();
87126
87495
  case "pi-sdk":
87127
87496
  return createPiSdkChunkExtractor();
87497
+ case "claude-sdk":
87498
+ return createClaudeSdkChunkExtractor();
87128
87499
  default:
87129
87500
  return createStandardSdkChunkExtractor();
87130
87501
  }
@@ -87159,6 +87530,16 @@ async function isPiSdkInstalled() {
87159
87530
  return false;
87160
87531
  }
87161
87532
  }
87533
+ async function isClaudeSdkInstalled() {
87534
+ try {
87535
+ const { importBundledClaudeSdk: importBundledClaudeSdk2, resolvePathToClaudeCodeExecutable: resolvePathToClaudeCodeExecutable2 } = await Promise.resolve().then(() => (init_claude_sdk_package(), exports_claude_sdk_package));
87536
+ await importBundledClaudeSdk2();
87537
+ await resolvePathToClaudeCodeExecutable2();
87538
+ return true;
87539
+ } catch {
87540
+ return false;
87541
+ }
87542
+ }
87162
87543
  async function listInstalledNativeDirectHarnesses() {
87163
87544
  const installed2 = [];
87164
87545
  if (opencodeOnPath())
@@ -87167,9 +87548,12 @@ async function listInstalledNativeDirectHarnesses() {
87167
87548
  installed2.push("cursor-sdk");
87168
87549
  if (await isPiSdkInstalled())
87169
87550
  installed2.push("pi-sdk");
87551
+ if (await isClaudeSdkInstalled())
87552
+ installed2.push("claude-sdk");
87170
87553
  return installed2;
87171
87554
  }
87172
87555
  var init_registry4 = __esm(() => {
87556
+ init_claude_sdk2();
87173
87557
  init_cursor_sdk2();
87174
87558
  init_opencode_harness();
87175
87559
  init_pi_sdk2();
@@ -87808,6 +88192,14 @@ function recordLiveSessionChunk(event, handle, journal, extractChunk, deps) {
87808
88192
  deps.sessionRepository.bindTurnMessageId(handle.currentTurn.turnId, chunk2.messageId).catch((err) => console.warn("[direct-harness] bindTurnMessageId error:", err));
87809
88193
  }
87810
88194
  }
88195
+ function handleLiveSessionProviderId(event, deps, rowId, handle, liveSession) {
88196
+ if (event.type !== "session.provider_id")
88197
+ return;
88198
+ const sessionId = event.payload.sessionId;
88199
+ if (!sessionId || sessionId === handle.opencodeSessionId)
88200
+ return;
88201
+ deps.sessionRepository.associateOpenCodeSessionId(rowId, sessionId, liveSession.sessionTitle ?? "").catch((err) => console.warn("[direct-harness] associateOpenCodeSessionId (provider id) error:", err));
88202
+ }
87811
88203
  function handleLiveSessionTitleUpdate(event, deps, rowId, liveSession) {
87812
88204
  if (event.type !== "session.updated") {
87813
88205
  return;
@@ -87827,6 +88219,7 @@ function handleLiveSessionEvent(event, ctx) {
87827
88219
  handleSessionIdle(handle, journal, idleConfig, deps.sessionRepository).catch((err) => console.warn("[direct-harness] idle handler error:", err));
87828
88220
  }
87829
88221
  handleLiveSessionTitleUpdate(event, deps, rowId, liveSession);
88222
+ handleLiveSessionProviderId(event, deps, rowId, handle, liveSession);
87830
88223
  }
87831
88224
  async function processOne(daemonSession, deps, session2) {
87832
88225
  const rowId = session2._id;
@@ -90962,7 +91355,7 @@ function diffPathIndexes(previous, next4) {
90962
91355
  }
90963
91356
 
90964
91357
  // src/infrastructure/services/workspace/workspace-sync-state.ts
90965
- import { createHash as createHash6, randomUUID as randomUUID6 } from "node:crypto";
91358
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
90966
91359
  import * as fs11 from "node:fs/promises";
90967
91360
  import { homedir as homedir7 } from "node:os";
90968
91361
  import { join as join20 } from "node:path";
@@ -91024,7 +91417,7 @@ function createManifestFromTree(args2) {
91024
91417
  version: SYNC_STATE_VERSION,
91025
91418
  machineId: args2.machineId,
91026
91419
  workingDir: normalizeWorkingDirForLookup(args2.workingDir),
91027
- syncGeneration: randomUUID6(),
91420
+ syncGeneration: randomUUID8(),
91028
91421
  completedAt: Date.now(),
91029
91422
  scanner: args2.scanner,
91030
91423
  dataHash: args2.dataHash,
@@ -91046,7 +91439,7 @@ var init_workspace_sync_state = __esm(() => {
91046
91439
  });
91047
91440
 
91048
91441
  // src/infrastructure/services/workspace/workspace-file-tree-coordinator.ts
91049
- import { randomUUID as randomUUID7 } from "node:crypto";
91442
+ import { randomUUID as randomUUID9 } from "node:crypto";
91050
91443
  import path6 from "node:path";
91051
91444
  function deltaEntry(paths, pathValue) {
91052
91445
  const type = paths[pathValue];
@@ -91073,7 +91466,7 @@ function buildPendingDeltas(previous, next4) {
91073
91466
  const result = [];
91074
91467
  for (let offset = 0;offset < operations.length; offset += MAX_DELTA_OPERATIONS) {
91075
91468
  const delta = {
91076
- operationId: randomUUID7(),
91469
+ operationId: randomUUID9(),
91077
91470
  added: [],
91078
91471
  removed: [],
91079
91472
  typeChanged: [],
@@ -91297,7 +91690,7 @@ var init_workspace_file_tree_coordinator = __esm(() => {
91297
91690
  });
91298
91691
 
91299
91692
  // src/commands/machine/daemon-start/file-tree-subscription.ts
91300
- import { randomUUID as randomUUID8 } from "node:crypto";
91693
+ import { randomUUID as randomUUID10 } from "node:crypto";
91301
91694
  import { gzipSync as gzipSync4 } from "node:zlib";
91302
91695
  function logSubscriptionWarn(label, err) {
91303
91696
  console.warn(`[${formatTimestamp()}] ⚠️ ${label}: ${getErrorMessage2(err)}`);
@@ -91339,7 +91732,7 @@ function toDeltaOperations(delta) {
91339
91732
  }
91340
91733
  async function publishCheckpoint(session2, normalizedWorkingDir, tree, revision) {
91341
91734
  const dataHash = computeFileTreeDataHash(tree);
91342
- const syncGeneration = randomUUID8();
91735
+ const syncGeneration = randomUUID10();
91343
91736
  const snapshot = await syncScannedFileTree(session2, normalizedWorkingDir, tree, dataHash, syncGeneration);
91344
91737
  let checkpointRevision = revision;
91345
91738
  let result = await session2.backend.mutation(api.workspaceFiles.publishFileTreeCheckpoint, {
@@ -91868,6 +92261,8 @@ async function pushAvailableGitState(ctx, workingDir, stateKey, branchResult) {
91868
92261
  ...pipeline2.toMutationArgs(values3, false)
91869
92262
  });
91870
92263
  ctx.lastPushedGitState.set(stateKey, stateHash);
92264
+ lastPushedBranch.set(stateKey, branch);
92265
+ lastFullPushMs.set(stateKey, Date.now());
91871
92266
  console.log(`[${formatTimestamp()}] \uD83D\uDD00 Git state pushed: ${workingDir} (${branch}${values3.get("isDirty") ? ", dirty" : ", clean"})`);
91872
92267
  }
91873
92268
  if (ctx.lastPushedGitState.get(commitsKey) !== commitsHash) {
@@ -91919,6 +92314,7 @@ function pushObservedBranchErrorEffect(session2, lastPushedGitState, stateKey, w
91919
92314
  function pushObservedFullGitStateEffect(session2, lastPushedGitState, stateKey, workingDir, branch, reason) {
91920
92315
  return exports_Effect.gen(function* () {
91921
92316
  yield* exports_Effect.promise(() => pushSingleWorkspaceGitStateImpl(buildGitStateDeps(session2, lastPushedGitState), workingDir));
92317
+ lastPushedBranch.set(stateKey, branch);
91922
92318
  lastFullPushMs.set(stateKey, Date.now());
91923
92319
  console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Observed full git state pushed: ${workingDir} (${branch})${reason === "refresh" ? " [refresh]" : ""}`);
91924
92320
  });
@@ -91948,7 +92344,7 @@ function pushObservedSlimGitSummaryEffect(session2, lastPushedGitState, stateKey
91948
92344
  console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Observed git summary pushed: ${workingDir} (${branch}${values3.get("isDirty") ? ", dirty" : ", clean"})${reason === "refresh" ? " [refresh]" : ""}`);
91949
92345
  });
91950
92346
  }
91951
- var lastFullPushMs, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummaryForObservedEffect = (workingDir, reason = "safety-poll") => exports_Effect.gen(function* pushObservedGitSummaryForObserved() {
92347
+ var lastFullPushMs, lastPushedBranch, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummaryForObservedEffect = (workingDir, reason = "safety-poll") => exports_Effect.gen(function* pushObservedGitSummaryForObserved() {
91952
92348
  const session2 = yield* DaemonSessionService;
91953
92349
  const mutable2 = yield* DaemonMutableStateService;
91954
92350
  const lastPushedGitState = yield* exports_Ref.get(mutable2.lastPushedGitState);
@@ -91969,7 +92365,10 @@ var lastFullPushMs, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummary
91969
92365
  const branch = branchResult.branch;
91970
92366
  const now = Date.now();
91971
92367
  const lastFull = lastFullPushMs.get(stateKey) ?? 0;
91972
- if (now - lastFull >= OBSERVED_FULL_PUSH_INTERVAL_MS) {
92368
+ const previousBranch = lastPushedBranch.get(stateKey);
92369
+ const branchChanged = previousBranch !== undefined && previousBranch !== branch;
92370
+ const needsFullPush = reason === "refresh" || branchChanged || now - lastFull >= OBSERVED_FULL_PUSH_INTERVAL_MS;
92371
+ if (needsFullPush) {
91973
92372
  yield* pushObservedFullGitStateEffect(session2, lastPushedGitState, stateKey, workingDir, branch, reason);
91974
92373
  return;
91975
92374
  }
@@ -91990,6 +92389,7 @@ var init_git_heartbeat = __esm(() => {
91990
92389
  init_git_state_pipeline();
91991
92390
  init_convex_error();
91992
92391
  lastFullPushMs = new Map;
92392
+ lastPushedBranch = new Map;
91993
92393
  branchField = {
91994
92394
  key: "branch",
91995
92395
  includeInSlim: true,
@@ -92705,7 +93105,8 @@ var init_participant = __esm(() => {
92705
93105
  "task.acknowledged",
92706
93106
  "task.inProgress",
92707
93107
  "task.completed",
92708
- "agent.requestStop"
93108
+ "agent.requestStop",
93109
+ "agent.awaitingHandoff"
92709
93110
  ]);
92710
93111
  });
92711
93112
 
@@ -93171,7 +93572,8 @@ var init_decide_resume_path = __esm(() => {
93171
93572
  "platform.team_switch",
93172
93573
  "platform.resume_storm",
93173
93574
  "daemon.shutdown",
93174
- "daemon.respawn"
93575
+ "daemon.respawn",
93576
+ "daemon.stop_timeout"
93175
93577
  ]);
93176
93578
  });
93177
93579
 
@@ -93338,21 +93740,14 @@ function classifyResumeStormReason(logLines) {
93338
93740
  }
93339
93741
  return "unknown";
93340
93742
  }
93341
- function isPermanentHarnessFailure(logLines) {
93342
- if (isTerminalProviderFailureInLogs(logLines)) {
93343
- return true;
93344
- }
93345
- return PERMANENT_FAILURE_REASONS.has(classifyResumeStormReason(logLines));
93346
- }
93347
93743
  function formatPermanentHarnessFailureMessage(logLines) {
93348
93744
  const reason = classifyResumeStormReason(logLines);
93349
93745
  const blob = logLines.join(`
93350
93746
  `).trim();
93351
- return blob ? `Permanent harness error (${reason}): ${blob.slice(-500)}` : `Permanent harness error (${reason})`;
93747
+ return blob ? `Crash recovery limit (${reason}): ${blob.slice(-500)}` : `Crash recovery limit (${reason})`;
93352
93748
  }
93353
93749
  var CLASSIFICATION_RULES, PERMANENT_FAILURE_REASONS;
93354
93750
  var init_classify_resume_storm_reason = __esm(() => {
93355
- init_terminal_provider_error();
93356
93751
  CLASSIFICATION_RULES = [
93357
93752
  {
93358
93753
  reason: "rate_limit",
@@ -93456,27 +93851,11 @@ async function handleTurnCompleted(deps, input, slot) {
93456
93851
  if (await tryAbortResumeStorm(deps, input, slot)) {
93457
93852
  return { outcome: "storm_aborted" };
93458
93853
  }
93459
- if (isTerminalProviderFailureInLogs(slot?.recentLogLines ?? [])) {
93460
- if (slot) {
93461
- slot.terminalProviderFailureHandled = true;
93462
- }
93463
- const error = formatTerminalProviderFailureMessage(slot?.recentLogLines ?? []);
93464
- try {
93465
- await deps.backend.emitAgentStartFailed({
93466
- chatroomId: input.chatroomId,
93467
- role: input.role,
93468
- error
93469
- });
93470
- } catch {}
93471
- deps.killProcess(input.pid);
93472
- return { outcome: "killed_terminal_provider_error" };
93473
- }
93474
93854
  deps.killProcess(input.pid);
93475
93855
  return { outcome: "killed" };
93476
93856
  }
93477
93857
  var init_handle_turn_completed = __esm(() => {
93478
93858
  init_abort_resume_storm();
93479
- init_terminal_provider_error();
93480
93859
  });
93481
93860
 
93482
93861
  // src/infrastructure/deps/process.ts
@@ -93894,19 +94273,30 @@ class AgentProcessManager {
93894
94273
  whenTurnEndsIdle() {
93895
94274
  return this.turnEndQueue.whenIdle();
93896
94275
  }
94276
+ clearSlotRuntimeState(slot) {
94277
+ slot.state = "idle";
94278
+ slot.pid = undefined;
94279
+ slot.harness = undefined;
94280
+ slot.harnessSessionId = undefined;
94281
+ slot.resumableHarnessSessionId = undefined;
94282
+ slot.model = undefined;
94283
+ slot.workingDir = undefined;
94284
+ slot.startedAt = undefined;
94285
+ slot.pendingOperation = undefined;
94286
+ slot.stoppingSince = undefined;
94287
+ }
94288
+ bumpStopGeneration(slot) {
94289
+ slot.stopGeneration = (slot.stopGeneration ?? 0) + 1;
94290
+ return slot.stopGeneration;
94291
+ }
93897
94292
  async ensureRunning(opts) {
93898
94293
  const key = agentKey3(opts.chatroomId, opts.role);
93899
94294
  const slot = this.getOrCreateSlot(key);
93900
94295
  if (slot.state === "running" && slot.pid && !isProcessAlive(this.deps.processes.kill, slot.pid)) {
93901
- slot.state = "idle";
93902
- slot.pid = undefined;
93903
- slot.harness = undefined;
93904
- slot.harnessSessionId = undefined;
93905
- slot.resumableHarnessSessionId = undefined;
93906
- slot.model = undefined;
93907
- slot.workingDir = undefined;
93908
- slot.startedAt = undefined;
93909
- slot.pendingOperation = undefined;
94296
+ this.clearSlotRuntimeState(slot);
94297
+ }
94298
+ if (slot.state === "stopping" && (slot.stoppingSince === undefined || this.deps.clock.now() - slot.stoppingSince >= STOPPING_TIMEOUT_MS)) {
94299
+ await this.forceClearStuckStoppingSlot(key, slot, opts.chatroomId, opts.role, "daemon.stop_timeout");
93910
94300
  }
93911
94301
  if (slot.pendingOperation) {
93912
94302
  if (slot.state === "stopping") {
@@ -93970,7 +94360,9 @@ class AgentProcessManager {
93970
94360
  return { success: true };
93971
94361
  }
93972
94362
  slot.state = "stopping";
93973
- const operation = this.doStop(key, slot, pid, opts);
94363
+ const stopGeneration = this.bumpStopGeneration(slot);
94364
+ slot.stoppingSince = this.deps.clock.now();
94365
+ const operation = this.doStop(key, slot, pid, opts, stopGeneration);
93974
94366
  slot.pendingOperation = operation;
93975
94367
  return null;
93976
94368
  }
@@ -94053,24 +94445,6 @@ class AgentProcessManager {
94053
94445
  console.log(`[AgentProcessManager] ✅ Handled rapid resume storm for ${opts.role}`);
94054
94446
  return;
94055
94447
  }
94056
- if (isTerminalProviderFailureInLogs(slot?.recentLogLines ?? [])) {
94057
- if (slot) {
94058
- slot.terminalProviderFailureHandled = true;
94059
- }
94060
- const error = formatTerminalProviderFailureMessage(slot?.recentLogLines ?? []);
94061
- try {
94062
- await createTurnCompletedBackend(this.deps).emitAgentStartFailed({
94063
- chatroomId: opts.chatroomId,
94064
- role: opts.role,
94065
- error
94066
- });
94067
- } catch {}
94068
- try {
94069
- this.deps.processes.kill(-opts.pid, "SIGTERM");
94070
- } catch {}
94071
- console.log(`[AgentProcessManager] ⛔ Terminal provider error for ${opts.role} — emitted agent.startFailed`);
94072
- return;
94073
- }
94074
94448
  try {
94075
94449
  const result = await this.deps.backend.mutation(api.participants.handleNativeAgentEnd, {
94076
94450
  sessionId: this.deps.sessionId,
@@ -94217,16 +94591,12 @@ class AgentProcessManager {
94217
94591
  console.log(`[AgentProcessManager] ⚠️ Cannot restart — missing harness or workingDir ` + `(role: ${opts.role}, harness: ${harness ?? "none"}, workingDir: ${workingDir ?? "none"})`);
94218
94592
  return;
94219
94593
  }
94220
- if (isPermanentHarnessFailure(logs)) {
94221
- this.handlePermanentFailureForRestart(opts, recentLogLines, ctx.terminalProviderFailureHandled);
94222
- return;
94223
- }
94224
94594
  const hadRunError = this.hasCursorSdkRunErrorInContext(logs);
94225
94595
  if (harness === "cursor-sdk") {
94226
94596
  this.retryCursorSdkSessionReopen(opts, ctx, hadRunError);
94227
94597
  return;
94228
94598
  }
94229
- this.ensureRunning({
94599
+ this.attemptCrashRecoveryRestart(opts, ctx, {
94230
94600
  chatroomId: opts.chatroomId,
94231
94601
  role: opts.role,
94232
94602
  agentHarness: harness,
@@ -94234,15 +94604,54 @@ class AgentProcessManager {
94234
94604
  workingDir,
94235
94605
  reason: "platform.crash_recovery",
94236
94606
  wantResume: hadRunError ? false : ctx.wantResume ?? true
94237
- }).then((result) => {
94238
- if (!result.success) {
94239
- console.log(`[AgentProcessManager] ⚠️ Agent restart did not complete for ${opts.role}: ${result.error ?? "unknown"}`);
94240
- }
94241
- }).catch((err) => {
94242
- console.log(` ⚠️ Failed to restart agent: ${err.message}`);
94243
- this.emitStartFailedEvent(opts.role, opts.chatroomId, err.message);
94244
94607
  });
94245
94608
  }
94609
+ async attemptCrashRecoveryRestart(exitOpts, ctx, ensureOpts) {
94610
+ try {
94611
+ const result = await this.ensureRunning(ensureOpts);
94612
+ if (result.success)
94613
+ return;
94614
+ if (result.error === "backoff") {
94615
+ await this.retryCrashRecoveryAfterBackoff(exitOpts, ctx, ensureOpts, result.retryAfterMs);
94616
+ return;
94617
+ }
94618
+ if (result.error === "crash_loop") {
94619
+ this.handleCrashLoopLimitReached(exitOpts, ctx.recentLogLines);
94620
+ return;
94621
+ }
94622
+ console.log(`[AgentProcessManager] ⚠️ Agent restart did not complete for ${exitOpts.role}: ${result.error ?? "unknown"}`);
94623
+ } catch (err) {
94624
+ const message = err instanceof Error ? err.message : String(err);
94625
+ console.log(` ⚠️ Failed to restart agent: ${message}`);
94626
+ this.emitStartFailedEvent(exitOpts.role, exitOpts.chatroomId, message);
94627
+ }
94628
+ }
94629
+ async retryCrashRecoveryAfterBackoff(exitOpts, ctx, ensureOpts, retryAfterMs) {
94630
+ if (retryAfterMs === undefined || retryAfterMs <= 0) {
94631
+ return;
94632
+ }
94633
+ const classified = classifyResumeStormReason(ctx.recentLogLines ?? []);
94634
+ console.log(`[AgentProcessManager] ⏳ Crash recovery backoff for ${exitOpts.role} (${classified}): waiting ${retryAfterMs}ms`);
94635
+ await this.deps.clock.delay(retryAfterMs);
94636
+ const retry4 = await this.ensureRunning(ensureOpts);
94637
+ if (retry4.success)
94638
+ return;
94639
+ if (retry4.error === "crash_loop") {
94640
+ this.handleCrashLoopLimitReached(exitOpts, ctx.recentLogLines);
94641
+ return;
94642
+ }
94643
+ if (!retry4.success) {
94644
+ console.log(`[AgentProcessManager] ⚠️ Agent restart did not complete for ${exitOpts.role}: ${retry4.error ?? "unknown"}`);
94645
+ }
94646
+ }
94647
+ handleCrashLoopLimitReached(opts, recentLogLines) {
94648
+ const error = formatPermanentHarnessFailureMessage(recentLogLines ?? []);
94649
+ console.log(`[AgentProcessManager] ⛔ Crash recovery limit reached — ${error}`);
94650
+ this.deps.crashLoop.clear(opts.chatroomId, opts.role);
94651
+ const key = agentKey3(opts.chatroomId, opts.role);
94652
+ this.clearLastHarnessSession(key);
94653
+ this.emitStartFailedEvent(opts.role, opts.chatroomId, error);
94654
+ }
94246
94655
  clearHarnessSessionAfterResumePhaseFailure(key, opts) {
94247
94656
  const stored = this.lastHarnessSessions.get(key);
94248
94657
  this.clearLastHarnessSession(key);
@@ -94310,25 +94719,6 @@ class AgentProcessManager {
94310
94719
  this.sessionReopenRetryInFlight.delete(key);
94311
94720
  }
94312
94721
  }
94313
- handlePermanentFailureForRestart(opts, recentLogLines, startFailedAlreadyEmitted) {
94314
- const error = isTerminalProviderFailureInLogs(recentLogLines ?? []) ? formatTerminalProviderFailureMessage(recentLogLines ?? []) : formatPermanentHarnessFailureMessage(recentLogLines ?? []);
94315
- console.log(`[AgentProcessManager] ⛔ Skipping restart — ${error}`);
94316
- this.deps.crashLoop.clear(opts.chatroomId, opts.role);
94317
- const key = agentKey3(opts.chatroomId, opts.role);
94318
- this.clearLastHarnessSession(key);
94319
- if (startFailedAlreadyEmitted) {
94320
- return;
94321
- }
94322
- this.deps.backend.mutation(api.machines.emitAgentStartFailed, {
94323
- sessionId: this.deps.sessionId,
94324
- machineId: this.deps.machineId,
94325
- chatroomId: opts.chatroomId,
94326
- role: opts.role,
94327
- error
94328
- }).catch((emitErr) => {
94329
- console.log(` ⚠️ Failed to emit startFailed event: ${emitErr.message}`);
94330
- });
94331
- }
94332
94722
  emitStartFailedEvent(role, chatroomId, error) {
94333
94723
  this.deps.backend.mutation(api.machines.emitAgentStartFailed, {
94334
94724
  sessionId: this.deps.sessionId,
@@ -94355,6 +94745,37 @@ class AgentProcessManager {
94355
94745
  }
94356
94746
  return result;
94357
94747
  }
94748
+ listAllSlots() {
94749
+ const result = [];
94750
+ for (const [key, slot] of this.slots) {
94751
+ const [chatroomId, role] = key.split(":");
94752
+ result.push({ chatroomId, role, slot });
94753
+ }
94754
+ return result;
94755
+ }
94756
+ async clearStuckStoppingSlot(chatroomId, role) {
94757
+ const key = agentKey3(chatroomId, role);
94758
+ const slot = this.slots.get(key);
94759
+ if (!slot || slot.state !== "stopping") {
94760
+ return false;
94761
+ }
94762
+ const elapsed3 = slot.stoppingSince ? this.deps.clock.now() - slot.stoppingSince : STOPPING_TIMEOUT_MS;
94763
+ if (elapsed3 < STOPPING_TIMEOUT_MS) {
94764
+ return false;
94765
+ }
94766
+ await this.forceClearStuckStoppingSlot(key, slot, chatroomId, role, "daemon.stop_timeout");
94767
+ console.warn(`[AgentProcessManager] ⚠️ Cleared stuck stopping slot for ${role}@${chatroomId}`);
94768
+ return true;
94769
+ }
94770
+ async clearAllStuckStoppingSlots() {
94771
+ let cleared = 0;
94772
+ for (const { chatroomId, role } of this.listAllSlots()) {
94773
+ if (await this.clearStuckStoppingSlot(chatroomId, role)) {
94774
+ cleared++;
94775
+ }
94776
+ }
94777
+ return cleared;
94778
+ }
94358
94779
  async recover() {
94359
94780
  let entries2 = [];
94360
94781
  try {
@@ -94387,6 +94808,10 @@ class AgentProcessManager {
94387
94808
  }
94388
94809
  }
94389
94810
  console.log(`[AgentProcessManager] Recovery: ${killed} killed, ${cleaned} cleaned up`);
94811
+ const clearedCount = await this.clearAllStuckStoppingSlots();
94812
+ if (clearedCount > 0) {
94813
+ console.log(`[AgentProcessManager] Recovery: cleared ${clearedCount} stuck stopping slot(s)`);
94814
+ }
94390
94815
  }
94391
94816
  getOrCreateSlot(key) {
94392
94817
  let slot = this.slots.get(key);
@@ -94423,7 +94848,9 @@ class AgentProcessManager {
94423
94848
  if (slot?.pid && isProcessAlive(this.deps.processes.kill, slot.pid) && (slot.state === "running" || slot.state === "spawning")) {
94424
94849
  const pid = slot.pid;
94425
94850
  slot.state = "stopping";
94426
- await this.doStop(key, slot, pid, { chatroomId, role, reason: "daemon.respawn" });
94851
+ const stopGeneration = this.bumpStopGeneration(slot);
94852
+ slot.stoppingSince = this.deps.clock.now();
94853
+ await this.doStop(key, slot, pid, { chatroomId, role, reason: "daemon.respawn" }, stopGeneration);
94427
94854
  }
94428
94855
  }
94429
94856
  async killPersistedProcessIfAlive(chatroomId, role) {
@@ -94693,7 +95120,7 @@ class AgentProcessManager {
94693
95120
  if (loopCheck.waitMs !== undefined && loopCheck.waitMs > 0) {
94694
95121
  console.log(` ⏳ Agent restart backoff: waiting ${loopCheck.waitMs}ms before retry`);
94695
95122
  this.resetSlotIdle(slot);
94696
- return { success: false, error: "backoff" };
95123
+ return { success: false, error: "backoff", retryAfterMs: loopCheck.waitMs };
94697
95124
  }
94698
95125
  this.deps.backend.mutation(api.machines.emitRestartLimitReached, {
94699
95126
  sessionId: this.deps.sessionId,
@@ -95018,6 +95445,42 @@ class AgentProcessManager {
95018
95445
  slot.pid = undefined;
95019
95446
  slot.startedAt = undefined;
95020
95447
  slot.pendingOperation = undefined;
95448
+ slot.stoppingSince = undefined;
95449
+ }
95450
+ async forceClearStuckStoppingSlot(key, slot, chatroomId, role, reason) {
95451
+ const pid = slot.pid;
95452
+ const harness = slot.harness;
95453
+ const durationMs = slot.stoppingSince ? this.deps.clock.now() - slot.stoppingSince : STOPPING_TIMEOUT_MS;
95454
+ this.bumpStopGeneration(slot);
95455
+ this.clearSlotRuntimeState(slot);
95456
+ this.deps.backend.mutation(api.machines.emitAgentStopTimeout, {
95457
+ sessionId: this.deps.sessionId,
95458
+ machineId: this.deps.machineId,
95459
+ chatroomId,
95460
+ role,
95461
+ pid,
95462
+ durationMs
95463
+ }).catch((err) => {
95464
+ console.log(` ⚠️ Failed to emit agent.stopTimeout event: ${err.message}`);
95465
+ });
95466
+ if (pid) {
95467
+ try {
95468
+ this.deps.processes.kill(-pid, "SIGKILL");
95469
+ } catch {}
95470
+ const exitArgs = {
95471
+ sessionId: this.deps.sessionId,
95472
+ machineId: this.deps.machineId,
95473
+ chatroomId,
95474
+ role,
95475
+ pid,
95476
+ stopReason: reason,
95477
+ exitCode: undefined,
95478
+ signal: "SIGKILL",
95479
+ agentHarness: harness
95480
+ };
95481
+ this.recordAgentExitedOrQueueRetry(role, exitArgs, "Failed to record stop-timeout exit");
95482
+ }
95483
+ await this.clearAgentPidQuietly(chatroomId, role);
95021
95484
  }
95022
95485
  recordStopExit(slot, pid, opts) {
95023
95486
  const exitArgs3 = {
@@ -95036,7 +95499,7 @@ class AgentProcessManager {
95036
95499
  this.queueExitRetry({ role: opts.role, args: exitArgs3 });
95037
95500
  });
95038
95501
  }
95039
- async doStop(key, slot, pid, opts) {
95502
+ async doStop(key, slot, pid, opts, stopGeneration) {
95040
95503
  try {
95041
95504
  const harness = slot.harness;
95042
95505
  const service3 = harness ? this.deps.agentServices.get(harness) : undefined;
@@ -95048,6 +95511,9 @@ class AgentProcessManager {
95048
95511
  await this.killProcessWithFallback(pid);
95049
95512
  }
95050
95513
  } catch {}
95514
+ if (slot.stopGeneration !== stopGeneration) {
95515
+ return { success: true };
95516
+ }
95051
95517
  this.resetSlotAfterStop(slot);
95052
95518
  this.recordStopExit(slot, pid, opts);
95053
95519
  try {
@@ -95056,7 +95522,7 @@ class AgentProcessManager {
95056
95522
  return { success: true };
95057
95523
  }
95058
95524
  }
95059
- var AGENT_EXIT_RETRY_INTERVAL_MS = 1e4;
95525
+ var AGENT_EXIT_RETRY_INTERVAL_MS = 1e4, STOPPING_TIMEOUT_MS = 30000;
95060
95526
  var init_agent_process_manager = __esm(() => {
95061
95527
  init_types();
95062
95528
  init_participant();
@@ -95069,7 +95535,6 @@ var init_agent_process_manager = __esm(() => {
95069
95535
  init_agent_lifecycle();
95070
95536
  init_abort_resume_storm();
95071
95537
  init_classify_resume_storm_reason();
95072
- init_terminal_provider_error();
95073
95538
  init_handle_turn_completed();
95074
95539
  init_spawn_policy();
95075
95540
  init_agent_lifecycle_runtime();
@@ -108577,10 +109042,19 @@ function isPendingAliveRunningTask(task) {
108577
109042
  const { agentConfig, status: status3 } = task;
108578
109043
  return status3 === "pending" && agentConfig.spawnedAgentPid != null && agentConfig.desiredState === "running";
108579
109044
  }
108580
- function isSlotUnavailableForPid(slot, pid, isPidAlive) {
108581
- if (!slot || slot.state === "idle" || slot.state === "stopping") {
109045
+ function isSlotUnavailableForPid(slot, pid, isPidAlive, now = Date.now()) {
109046
+ if (!slot) {
109047
+ return true;
109048
+ }
109049
+ if (slot.state === "idle") {
108582
109050
  return true;
108583
109051
  }
109052
+ if (slot.state === "stopping") {
109053
+ if (!slot.stoppingSince || now - slot.stoppingSince >= STOPPING_TIMEOUT_MS) {
109054
+ return true;
109055
+ }
109056
+ return false;
109057
+ }
108584
109058
  if (slot.pid !== pid) {
108585
109059
  return true;
108586
109060
  }
@@ -108596,7 +109070,7 @@ function isNativeRevivableTaskStatus(task) {
108596
109070
  }
108597
109071
  return false;
108598
109072
  }
108599
- function isNativeAgentSlotDown(task, health) {
109073
+ function isNativeAgentSlotDown(task, health, now = Date.now()) {
108600
109074
  const slot = health.getSlot(task.chatroomId, task.agentConfig.role);
108601
109075
  if (slot?.state === "spawning")
108602
109076
  return false;
@@ -108604,20 +109078,20 @@ function isNativeAgentSlotDown(task, health) {
108604
109078
  if (pid == null) {
108605
109079
  return slot?.state !== "running";
108606
109080
  }
108607
- return isSlotUnavailableForPid(slot, pid, health.isPidAlive);
109081
+ return isSlotUnavailableForPid(slot, pid, health.isPidAlive, now);
108608
109082
  }
108609
- function isNativeActiveTaskAgentDown(task, health) {
109083
+ function isNativeActiveTaskAgentDown(task, health, now) {
108610
109084
  if (!isNativeHarness(task.agentConfig.agentHarness))
108611
109085
  return false;
108612
109086
  if (task.agentConfig.desiredState !== "running")
108613
109087
  return false;
108614
109088
  if (!isNativeRevivableTaskStatus(task))
108615
109089
  return false;
108616
- return isNativeAgentSlotDown(task, health);
109090
+ return isNativeAgentSlotDown(task, health, now);
108617
109091
  }
108618
109092
  function listNativeTasksNeedingRevive(tasks, health, now, cooldown) {
108619
109093
  return tasks.filter((task) => {
108620
- if (!isNativeActiveTaskAgentDown(task, health))
109094
+ if (!isNativeActiveTaskAgentDown(task, health, now))
108621
109095
  return false;
108622
109096
  const { chatroomId, agentConfig } = task;
108623
109097
  if (!agentConfig.workingDir)
@@ -108677,6 +109151,7 @@ var PENDING_IDLE_NUDGE_MS = 15000, NUDGE_COOLDOWN_MS = 60000;
108677
109151
  var init_task_monitor_logic = __esm(() => {
108678
109152
  init_native_task_injector_logic();
108679
109153
  init_predicates();
109154
+ init_agent_process_manager();
108680
109155
  });
108681
109156
  // ../../services/backend/src/domain/usecase/machine/assigned-task-monitor-row.ts
108682
109157
  function applyAssignedTaskSignal(existing, signal) {
@@ -109319,8 +109794,15 @@ function runCliNudgeEffect(task, runtime4, effectContext2, agentMgr, sessionDeps
109319
109794
  console.log(buildCliNudgeLogLine(task));
109320
109795
  executeCliNudge(task, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
109321
109796
  }
109797
+ async function clearStuckStoppingSlotIfNeeded(agentMgr, chatroomId, role) {
109798
+ const cleared = await agentMgr.clearStuckStoppingSlot(chatroomId, role);
109799
+ if (cleared) {
109800
+ console.log(`[TaskMonitor] cleared stuck stopping slot for ${role}@${chatroomId}`);
109801
+ }
109802
+ }
109322
109803
  async function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
109323
109804
  for (const row of listTasksReadyForNudge(tasks, now, cooldown)) {
109805
+ await clearStuckStoppingSlotIfNeeded(agentMgr, row.chatroomId, row.agentConfig.role);
109324
109806
  const full = await fetchTaskForAction(sessionDeps, machineId, row);
109325
109807
  if (!full)
109326
109808
  continue;
@@ -109329,6 +109811,7 @@ async function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, a
109329
109811
  }
109330
109812
  async function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
109331
109813
  for (const row of listNativeTasksNeedingRevive(tasks, localHealth, now, cooldown)) {
109814
+ await clearStuckStoppingSlotIfNeeded(agentMgr, row.chatroomId, row.agentConfig.role);
109332
109815
  const full = await fetchTaskForAction(sessionDeps, machineId, row);
109333
109816
  if (!full)
109334
109817
  continue;
@@ -109579,7 +110062,7 @@ function handleGitRefreshCommandEffect(event, tracker) {
109579
110062
  const lastPushedGitState = yield* exports_Ref.get(mutable2.lastPushedGitState);
109580
110063
  lastPushedGitState.delete(makeGitStateKey(session2.machineId, typedEvent.workingDir));
109581
110064
  console.log(`[${formatTimestamp()}] \uD83D\uDD04 Git refresh requested for ${typedEvent.workingDir}`);
109582
- yield* pushGitStateEffect;
110065
+ yield* pushSingleWorkspaceGitStateEffect(typedEvent.workingDir);
109583
110066
  tracker.gitRefreshIds.set(eventId, Date.now());
109584
110067
  });
109585
110068
  }
@@ -110988,4 +111471,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
110988
111471
  });
110989
111472
  program2.parse();
110990
111473
 
110991
- //# debugId=D9A06559C6AC810C64756E2164756E21
111474
+ //# debugId=8E7C236B7CEE28C564756E2164756E21