chatroom-cli 1.65.2 → 1.65.4

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,7 +36595,6 @@ 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;
@@ -36678,10 +36678,6 @@ var init_opencode_sdk_agent_service = __esm(() => {
36678
36678
  if (childProcess.stderr) {
36679
36679
  const stderrBuffer = new StderrLineBuffer((line) => {
36680
36680
  emitLogLine(line);
36681
- const activeForwarder = this.forwarders.get(pid);
36682
- if (activeForwarder && isNonRetryableHarnessFailureText(line)) {
36683
- activeForwarder.abortTerminalProviderError();
36684
- }
36685
36681
  });
36686
36682
  childProcess.stderr.on("data", (chunk2) => {
36687
36683
  entry.lastOutputAt = Date.now();
@@ -36694,6 +36690,18 @@ var init_opencode_sdk_agent_service = __esm(() => {
36694
36690
  });
36695
36691
  }
36696
36692
  }
36693
+ createSessionForwarder(client4, sessionId, context5, emitLogLine, emitAssistantText, outputCallbacks) {
36694
+ return startSessionEventForwarder(client4, {
36695
+ sessionId,
36696
+ role: context5.role,
36697
+ onLogLine: emitLogLine,
36698
+ onAssistantText: emitAssistantText,
36699
+ onActivity: () => {
36700
+ for (const cb of outputCallbacks)
36701
+ cb();
36702
+ }
36703
+ });
36704
+ }
36697
36705
  registerRunningSession(args2) {
36698
36706
  const {
36699
36707
  childProcess,
@@ -36884,16 +36892,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
36884
36892
  if (!sessionInfo.data?.id) {
36885
36893
  throw new Error(`OpenCode session ${sessionId} not found (sessions may not survive serve restart)`);
36886
36894
  }
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
- });
36895
+ forwarder = this.createSessionForwarder(client4, sessionId, context5, emitLogLine, emitAssistantText, outputCallbacks);
36897
36896
  const availableAgents = await this.listAvailableAgents(client4);
36898
36897
  const agentDef = availableAgents.find((a) => a.name === agentName);
36899
36898
  const composedSystem = composeSystemPrompt(agentDef?.prompt, systemPrompt);
@@ -36941,16 +36940,7 @@ var init_opencode_sdk_agent_service = __esm(() => {
36941
36940
  throw new Error("Failed to create session");
36942
36941
  }
36943
36942
  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
- });
36943
+ forwarder = this.createSessionForwarder(client4, sessionId, context5, emitLogLine, emitAssistantText, outputCallbacks);
36954
36944
  const availableAgents = await this.listAvailableAgents(client4);
36955
36945
  const selected = selectAgent(availableAgents);
36956
36946
  agentName = selected.name;
@@ -37772,10 +37762,17 @@ var init_detection = __esm(() => {
37772
37762
  var MACHINE_CONFIG_VERSION = "1";
37773
37763
 
37774
37764
  // src/infrastructure/machine/storage.ts
37775
- import { randomUUID as randomUUID3 } from "node:crypto";
37765
+ import { randomBytes, randomUUID as randomUUID3 } from "node:crypto";
37776
37766
  import * as fs4 from "node:fs/promises";
37777
37767
  import { homedir as homedir3, hostname as hostname2 } from "node:os";
37778
37768
  import { join as join11 } from "node:path";
37769
+ function enqueueMachineConfigSave(task) {
37770
+ const run3 = saveChain.then(task, task);
37771
+ saveChain = run3.catch(() => {
37772
+ return;
37773
+ });
37774
+ return run3;
37775
+ }
37779
37776
  function chatroomConfigDir() {
37780
37777
  return join11(homedir3(), ".chatroom");
37781
37778
  }
@@ -37801,13 +37798,20 @@ async function loadConfigFile() {
37801
37798
  return null;
37802
37799
  }
37803
37800
  }
37804
- async function saveConfigFile(configFile) {
37801
+ async function writeConfigFileAtomically(configFile) {
37805
37802
  await ensureConfigDir2();
37806
37803
  const configPath = getMachineConfigPath();
37807
- const tempPath = `${configPath}.tmp`;
37804
+ const tempPath = `${configPath}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
37808
37805
  const content = JSON.stringify(configFile, null, 2);
37809
37806
  await fs4.writeFile(tempPath, content, { mode: 384 });
37810
- await fs4.rename(tempPath, configPath);
37807
+ try {
37808
+ await fs4.rename(tempPath, configPath);
37809
+ } catch (error) {
37810
+ await fs4.unlink(tempPath).catch(() => {
37811
+ return;
37812
+ });
37813
+ throw error;
37814
+ }
37811
37815
  }
37812
37816
  async function loadMachineConfig() {
37813
37817
  const configFile = await loadConfigFile();
@@ -37817,13 +37821,15 @@ async function loadMachineConfig() {
37817
37821
  return configFile.machines[convexUrl] ?? null;
37818
37822
  }
37819
37823
  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);
37824
+ await enqueueMachineConfigSave(async () => {
37825
+ const configFile = await loadConfigFile() ?? {
37826
+ version: MACHINE_CONFIG_VERSION,
37827
+ machines: {}
37828
+ };
37829
+ const convexUrl = getConvexUrl();
37830
+ configFile.machines[convexUrl] = config2;
37831
+ await writeConfigFileAtomically(configFile);
37832
+ });
37827
37833
  }
37828
37834
  async function createNewEndpointConfig() {
37829
37835
  const now = new Date().toISOString();
@@ -37867,10 +37873,11 @@ async function getMachineId() {
37867
37873
  const config2 = await loadMachineConfig();
37868
37874
  return config2?.machineId ?? null;
37869
37875
  }
37870
- var MACHINE_FILE = "machine.json";
37876
+ var MACHINE_FILE = "machine.json", saveChain;
37871
37877
  var init_storage2 = __esm(() => {
37872
37878
  init_detection();
37873
37879
  init_client2();
37880
+ saveChain = Promise.resolve();
37874
37881
  });
37875
37882
 
37876
37883
  // src/infrastructure/machine/daemon-state.ts
@@ -80480,6 +80487,8 @@ ${getRoleGuidanceDisclosureBlock(roleGuidanceContext)}
80480
80487
  <!-- Demonstrate adherence to:
80481
80488
  - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
80482
80489
  - 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.
80490
+ - No Revisit: implemented in a way so the user does not have to revisit this implementation again.
80491
+ - Leave It Better: leave the code in a slightly better state than before when touching files.
80483
80492
  -->
80484
80493
  <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
80485
80494
 
@@ -80641,6 +80650,8 @@ ${getRoleGuidanceDisclosureBlock(roleGuidanceContext)}
80641
80650
  <!-- Demonstrate adherence to:
80642
80651
  - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
80643
80652
  - 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.
80653
+ - No Revisit: implemented in a way so the user does not have to revisit this implementation again.
80654
+ - Leave It Better: leave the code in a slightly better state than before when touching files.
80644
80655
  -->
80645
80656
  <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
80646
80657
 
@@ -80741,6 +80752,8 @@ ${getRoleGuidanceDisclosureBlock(roleGuidanceContext)}
80741
80752
  <!-- Demonstrate adherence to:
80742
80753
  - Organization & Maintainability: a small change in requirements should result in a small change in code in a small number of files and folders.
80743
80754
  - 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.
80755
+ - No Revisit: implemented in a way so the user does not have to revisit this implementation again.
80756
+ - Leave It Better: leave the code in a slightly better state than before when touching files.
80744
80757
  -->
80745
80758
  <how this work follows the principles above — localized changes, readable structure, correctness provable from source then tests>
80746
80759
 
@@ -84686,6 +84699,7 @@ var DaemonMachineService, DaemonMachineServiceLive = (ops) => exports_Layer.succ
84686
84699
  recover: () => exports_Effect.promise(() => mgr.recover()),
84687
84700
  getSlot: (chatroomId, role) => mgr.getSlot(chatroomId, role),
84688
84701
  listActive: () => mgr.listActive(),
84702
+ clearStuckStoppingSlot: (chatroomId, role) => exports_Effect.promise(() => mgr.clearStuckStoppingSlot(chatroomId, role)),
84689
84703
  whenTurnEndsIdle: () => exports_Effect.promise(() => mgr.whenTurnEndsIdle()),
84690
84704
  resumeTurnForSlot: (args2) => exports_Effect.promise(() => mgr.resumeTurnForSlot(args2)),
84691
84705
  setLastInFlightTask: (chatroomId, role, taskId) => exports_Effect.sync(() => mgr.setLastInFlightTask(chatroomId, role, taskId))
@@ -86028,9 +86042,369 @@ var init_pid = __esm(() => {
86028
86042
  CHATROOM_DIR4 = join17(homedir6(), ".chatroom");
86029
86043
  });
86030
86044
 
86031
- // src/infrastructure/harnesses/cursor-sdk/cursor-session.ts
86045
+ // src/infrastructure/harnesses/claude-sdk/claude-session.ts
86032
86046
  import { randomUUID as randomUUID4 } from "node:crypto";
86033
86047
 
86048
+ class ClaudeSdkSession {
86049
+ opencodeSessionId;
86050
+ _sessionTitle;
86051
+ get sessionTitle() {
86052
+ return this._sessionTitle;
86053
+ }
86054
+ cwd;
86055
+ executablePath;
86056
+ query;
86057
+ defaultModel;
86058
+ storedSystemPrompt;
86059
+ onClose;
86060
+ listeners = new Set;
86061
+ closed = false;
86062
+ providerSessionId;
86063
+ resumeOnFirstQuery;
86064
+ isFirstQuery = true;
86065
+ activeQuery;
86066
+ sawTextDelta = false;
86067
+ sawThinkingDelta = false;
86068
+ constructor(options) {
86069
+ this.cwd = options.cwd;
86070
+ this.executablePath = options.executablePath;
86071
+ this.query = options.query;
86072
+ this.opencodeSessionId = options.opencodeSessionId;
86073
+ this._sessionTitle = options.sessionTitle;
86074
+ this.defaultModel = options.defaultModel;
86075
+ this.storedSystemPrompt = options.systemPrompt;
86076
+ this.providerSessionId = options.providerSessionId;
86077
+ this.resumeOnFirstQuery = options.resumeOnFirstQuery ?? false;
86078
+ this.onClose = options.onClose;
86079
+ }
86080
+ setTitle(title) {
86081
+ this._sessionTitle = title;
86082
+ }
86083
+ async prompt(input) {
86084
+ if (this.closed)
86085
+ throw new Error("Session is closed");
86086
+ const text = input.parts.map((p) => p.text).join(`
86087
+ `);
86088
+ const messageId = randomUUID4();
86089
+ const model = resolveClaudeModel(this.defaultModel, input.model);
86090
+ const useResume = Boolean(this.providerSessionId) && (!this.isFirstQuery || this.resumeOnFirstQuery);
86091
+ this.sawTextDelta = false;
86092
+ this.sawThinkingDelta = false;
86093
+ const queryInstance = this.query({
86094
+ prompt: text,
86095
+ options: {
86096
+ cwd: this.cwd,
86097
+ model,
86098
+ maxTurns: DEFAULT_MAX_TURNS3,
86099
+ pathToClaudeCodeExecutable: this.executablePath,
86100
+ includePartialMessages: true,
86101
+ systemPrompt: this.isFirstQuery && !this.resumeOnFirstQuery ? this.storedSystemPrompt : undefined,
86102
+ resume: useResume ? this.providerSessionId : undefined,
86103
+ settingSources: [],
86104
+ permissionMode: "bypassPermissions",
86105
+ allowDangerouslySkipPermissions: true,
86106
+ effort: DEFAULT_EFFORT2,
86107
+ canUseTool: async (_toolName, toolInput) => ({
86108
+ behavior: "allow",
86109
+ updatedInput: toolInput
86110
+ })
86111
+ }
86112
+ });
86113
+ this.activeQuery = queryInstance;
86114
+ if (this.resumeOnFirstQuery) {
86115
+ this.resumeOnFirstQuery = false;
86116
+ }
86117
+ this.isFirstQuery = false;
86118
+ try {
86119
+ await withTimeout((async () => {
86120
+ for await (const message of queryInstance) {
86121
+ if (this.closed)
86122
+ break;
86123
+ this.captureProviderSessionId(message);
86124
+ this.emitFromSdkMessage(message, messageId);
86125
+ if (message.type === "result") {
86126
+ if (message.is_error) {
86127
+ const errors2 = "errors" in message && Array.isArray(message.errors) ? message.errors.join("; ") : "turn failed";
86128
+ throw new Error(errors2);
86129
+ }
86130
+ break;
86131
+ }
86132
+ }
86133
+ })(), QUERY_TIMEOUT_MS, "query");
86134
+ } finally {
86135
+ this.activeQuery = undefined;
86136
+ }
86137
+ this.emit({ type: "session.idle", payload: {}, timestamp: Date.now() });
86138
+ }
86139
+ onEvent(listener) {
86140
+ this.listeners.add(listener);
86141
+ return () => {
86142
+ this.listeners.delete(listener);
86143
+ };
86144
+ }
86145
+ async close() {
86146
+ if (this.closed)
86147
+ return;
86148
+ this.closed = true;
86149
+ try {
86150
+ await this.activeQuery?.interrupt();
86151
+ } catch {}
86152
+ this.activeQuery = undefined;
86153
+ this.onClose?.(this.opencodeSessionId);
86154
+ this.listeners.clear();
86155
+ }
86156
+ emit(event) {
86157
+ for (const listener of this.listeners) {
86158
+ listener(event);
86159
+ }
86160
+ }
86161
+ emitDelta(messageId, delta, partType) {
86162
+ if (!delta)
86163
+ return;
86164
+ this.emit({
86165
+ type: "message.part.delta",
86166
+ payload: { messageID: messageId, delta, partType },
86167
+ timestamp: Date.now()
86168
+ });
86169
+ }
86170
+ captureProviderSessionId(message) {
86171
+ if (!("session_id" in message) || typeof message.session_id !== "string") {
86172
+ return;
86173
+ }
86174
+ const sessionId = message.session_id;
86175
+ if (this.providerSessionId === sessionId) {
86176
+ return;
86177
+ }
86178
+ const isFirstAllocation = !this.providerSessionId;
86179
+ this.providerSessionId = sessionId;
86180
+ if (isFirstAllocation) {
86181
+ this.emit({
86182
+ type: "session.provider_id",
86183
+ payload: { sessionId },
86184
+ timestamp: Date.now()
86185
+ });
86186
+ }
86187
+ }
86188
+ emitFromSdkMessage(message, messageId) {
86189
+ switch (message.type) {
86190
+ case "stream_event": {
86191
+ const event = message.event;
86192
+ if (event.type === "content_block_delta") {
86193
+ const delta = event.delta;
86194
+ if (delta.type === "text_delta") {
86195
+ this.sawTextDelta = true;
86196
+ this.emitDelta(messageId, delta.text, "text");
86197
+ } else if (delta.type === "thinking_delta") {
86198
+ this.sawThinkingDelta = true;
86199
+ this.emitDelta(messageId, delta.thinking, "reasoning");
86200
+ }
86201
+ }
86202
+ break;
86203
+ }
86204
+ case "assistant":
86205
+ for (const block of message.message.content) {
86206
+ if (block.type === "text" && block.text) {
86207
+ if (!this.sawTextDelta) {
86208
+ this.emitDelta(messageId, block.text, "text");
86209
+ }
86210
+ } else if (block.type === "thinking" && block.thinking) {
86211
+ if (!this.sawThinkingDelta) {
86212
+ this.emitDelta(messageId, block.thinking, "reasoning");
86213
+ }
86214
+ }
86215
+ }
86216
+ break;
86217
+ default:
86218
+ break;
86219
+ }
86220
+ }
86221
+ }
86222
+ function resolveClaudeModel(defaultModel, promptModel) {
86223
+ if (promptModel) {
86224
+ if (promptModel.providerID === "anthropic")
86225
+ return promptModel.modelID;
86226
+ return promptModel.modelID;
86227
+ }
86228
+ if (defaultModel) {
86229
+ const slash = defaultModel.indexOf("/");
86230
+ if (slash === -1)
86231
+ return defaultModel;
86232
+ const provider = defaultModel.slice(0, slash);
86233
+ const modelId = defaultModel.slice(slash + 1);
86234
+ if (provider === "anthropic")
86235
+ return modelId;
86236
+ return modelId;
86237
+ }
86238
+ return "sonnet";
86239
+ }
86240
+ var DEFAULT_MAX_TURNS3 = 200, DEFAULT_EFFORT2 = "medium", QUERY_TIMEOUT_MS = 3600000;
86241
+ var init_claude_session = () => {};
86242
+
86243
+ // src/infrastructure/harnesses/claude-sdk/claude-harness.ts
86244
+ import { randomUUID as randomUUID5 } from "node:crypto";
86245
+ async function loadSdk4() {
86246
+ if (_sdkCache4)
86247
+ return _sdkCache4;
86248
+ if (_sdkLoadError4)
86249
+ throw _sdkLoadError4;
86250
+ try {
86251
+ _sdkCache4 = await importBundledClaudeSdk();
86252
+ return _sdkCache4;
86253
+ } catch (err) {
86254
+ _sdkLoadError4 = err;
86255
+ throw err;
86256
+ }
86257
+ }
86258
+ async function loadExecutablePath() {
86259
+ if (_executablePathCache)
86260
+ return _executablePathCache;
86261
+ _executablePathCache = await resolvePathToClaudeCodeExecutable();
86262
+ return _executablePathCache;
86263
+ }
86264
+
86265
+ class ClaudeSdkHarness {
86266
+ type = "claude-sdk";
86267
+ displayName = "Claude (SDK)";
86268
+ cwd;
86269
+ query;
86270
+ executablePath;
86271
+ closed = false;
86272
+ sessions = new Map;
86273
+ constructor(cwd, sdk, executablePath) {
86274
+ this.cwd = cwd;
86275
+ this.query = sdk.query;
86276
+ this.executablePath = executablePath;
86277
+ }
86278
+ async models() {
86279
+ const providers = await this.listProviders();
86280
+ const models = [];
86281
+ for (const provider of providers) {
86282
+ for (const model of provider.models) {
86283
+ models.push({
86284
+ id: `${provider.providerID}/${model.modelID}`,
86285
+ name: model.name,
86286
+ provider: provider.name
86287
+ });
86288
+ }
86289
+ }
86290
+ return models;
86291
+ }
86292
+ async listAgents() {
86293
+ return [{ name: "builder", mode: "primary" }];
86294
+ }
86295
+ async listProviders() {
86296
+ const dynamic = await fetchClaudeModels();
86297
+ const modelIds = dynamic ?? [...CLAUDE_FALLBACK_MODELS];
86298
+ return [
86299
+ {
86300
+ providerID: "anthropic",
86301
+ name: "Anthropic",
86302
+ models: modelIds.map((modelID) => ({ modelID, name: modelID }))
86303
+ }
86304
+ ];
86305
+ }
86306
+ async newSession(config3) {
86307
+ if (this.closed)
86308
+ throw new Error("Harness is closed");
86309
+ const opencodeSessionId = randomUUID5();
86310
+ const session2 = new ClaudeSdkSession({
86311
+ cwd: this.cwd,
86312
+ executablePath: this.executablePath,
86313
+ query: this.query,
86314
+ opencodeSessionId,
86315
+ sessionTitle: config3.title ?? "",
86316
+ defaultModel: config3.model ?? DEFAULT_MODEL2,
86317
+ systemPrompt: config3.systemPrompt,
86318
+ onClose: (id3) => this.sessions.delete(id3)
86319
+ });
86320
+ this.sessions.set(opencodeSessionId, session2);
86321
+ return session2;
86322
+ }
86323
+ async resumeSession(sessionId, _options) {
86324
+ if (this.closed)
86325
+ throw new Error("Harness is closed");
86326
+ const existing = this.sessions.get(sessionId);
86327
+ if (existing)
86328
+ return existing;
86329
+ const session2 = new ClaudeSdkSession({
86330
+ cwd: this.cwd,
86331
+ executablePath: this.executablePath,
86332
+ query: this.query,
86333
+ opencodeSessionId: sessionId,
86334
+ sessionTitle: "",
86335
+ providerSessionId: sessionId,
86336
+ resumeOnFirstQuery: true,
86337
+ onClose: (id3) => this.sessions.delete(id3)
86338
+ });
86339
+ this.sessions.set(sessionId, session2);
86340
+ return session2;
86341
+ }
86342
+ async fetchSessionTitle(_opencodeSessionId) {
86343
+ return;
86344
+ }
86345
+ isAlive() {
86346
+ return !this.closed;
86347
+ }
86348
+ async close() {
86349
+ if (this.closed)
86350
+ return;
86351
+ this.closed = true;
86352
+ const closing = [...this.sessions.values()].map((s) => s.close().catch(() => {}));
86353
+ await Promise.all(closing);
86354
+ this.sessions.clear();
86355
+ }
86356
+ }
86357
+ var DEFAULT_MODEL2 = "anthropic/sonnet", _sdkCache4, _sdkLoadError4, _executablePathCache, startClaudeSdkHarness = async (config3) => {
86358
+ try {
86359
+ const sdk = await loadSdk4();
86360
+ const executablePath = await loadExecutablePath();
86361
+ return new ClaudeSdkHarness(config3.workingDir, sdk, executablePath);
86362
+ } catch (err) {
86363
+ throw new Error(`claude-sdk unavailable: ${formatClaudeSdkLoadError(err)}`);
86364
+ }
86365
+ };
86366
+ var init_claude_harness = __esm(() => {
86367
+ init_claude_session();
86368
+ init_claude_models();
86369
+ init_claude_sdk_package();
86370
+ });
86371
+
86372
+ // src/infrastructure/harnesses/shared-chunk-extractor.ts
86373
+ function createStandardSdkChunkExtractor() {
86374
+ return function extract(event) {
86375
+ if (event.type !== "message.part.delta")
86376
+ return null;
86377
+ const payload = event.payload;
86378
+ const delta = payload?.delta;
86379
+ if (!delta || delta.length === 0)
86380
+ return null;
86381
+ const messageId = payload?.messageID;
86382
+ if (!messageId)
86383
+ return null;
86384
+ return {
86385
+ content: delta,
86386
+ messageId,
86387
+ partType: payload?.partType ?? "text"
86388
+ };
86389
+ };
86390
+ }
86391
+
86392
+ // src/infrastructure/harnesses/claude-sdk/event-extractor.ts
86393
+ function createClaudeSdkChunkExtractor() {
86394
+ return createStandardSdkChunkExtractor();
86395
+ }
86396
+ var init_event_extractor = () => {};
86397
+
86398
+ // src/infrastructure/harnesses/claude-sdk/index.ts
86399
+ var init_claude_sdk2 = __esm(() => {
86400
+ init_claude_harness();
86401
+ init_claude_session();
86402
+ init_event_extractor();
86403
+ });
86404
+
86405
+ // src/infrastructure/harnesses/cursor-sdk/cursor-session.ts
86406
+ import { randomUUID as randomUUID6 } from "node:crypto";
86407
+
86034
86408
  class CursorSdkSession {
86035
86409
  opencodeSessionId;
86036
86410
  _sessionTitle;
@@ -86056,13 +86430,13 @@ class CursorSdkSession {
86056
86430
  throw new Error("Session is closed");
86057
86431
  const text = input.parts.map((p) => p.text).join(`
86058
86432
  `);
86059
- const messageId = randomUUID4();
86433
+ const messageId = randomUUID6();
86060
86434
  const isFirstTurn = this.turnCount === 0;
86061
86435
  this.turnCount += 1;
86062
86436
  const modelId = input.model ? resolveModelFromPrompt(input.model.providerID, input.model.modelID) : undefined;
86063
86437
  const run3 = await withTimeout(this.agent.send(text, {
86064
86438
  local: { force: isFirstTurn },
86065
- idempotencyKey: randomUUID4(),
86439
+ idempotencyKey: randomUUID6(),
86066
86440
  ...modelId ? { model: { id: modelId } } : {}
86067
86441
  }), SEND_TIMEOUT_MS2, "agent.send");
86068
86442
  for await (const message of run3.stream()) {
@@ -86135,16 +86509,16 @@ var SEND_TIMEOUT_MS2 = 60000, RUN_WAIT_TIMEOUT_MS2 = 3600000;
86135
86509
  var init_cursor_session = () => {};
86136
86510
 
86137
86511
  // src/infrastructure/harnesses/cursor-sdk/cursor-harness.ts
86138
- async function loadSdk4() {
86139
- if (_sdkCache4)
86140
- return _sdkCache4;
86141
- if (_sdkLoadError4)
86142
- throw _sdkLoadError4;
86512
+ async function loadSdk5() {
86513
+ if (_sdkCache5)
86514
+ return _sdkCache5;
86515
+ if (_sdkLoadError5)
86516
+ throw _sdkLoadError5;
86143
86517
  try {
86144
- _sdkCache4 = await importBundledCursorSdk();
86145
- return _sdkCache4;
86518
+ _sdkCache5 = await importBundledCursorSdk();
86519
+ return _sdkCache5;
86146
86520
  } catch (err) {
86147
- _sdkLoadError4 = err;
86521
+ _sdkLoadError5 = err;
86148
86522
  throw err;
86149
86523
  }
86150
86524
  }
@@ -86179,7 +86553,7 @@ class CursorSdkHarness {
86179
86553
  const apiKey = process.env.CURSOR_API_KEY?.trim();
86180
86554
  if (!apiKey)
86181
86555
  return [];
86182
- const { Cursor } = await loadSdk4();
86556
+ const { Cursor } = await loadSdk5();
86183
86557
  const listed = await withTimeout(Cursor.models.list({ apiKey }), MODELS_LIST_TIMEOUT_MS2, "Cursor.models.list");
86184
86558
  const modelIds = normalizeCursorSdkListedModels(listed.map((m) => m.id).filter((id3) => id3.length > 0));
86185
86559
  return [
@@ -86196,8 +86570,8 @@ class CursorSdkHarness {
86196
86570
  const apiKey = process.env.CURSOR_API_KEY?.trim();
86197
86571
  if (!apiKey)
86198
86572
  throw new Error("CURSOR_API_KEY is not set");
86199
- const modelId = resolveCursorSdkModel(config3.model ?? DEFAULT_MODEL2);
86200
- const { Agent } = await loadSdk4();
86573
+ const modelId = resolveCursorSdkModel(config3.model ?? DEFAULT_MODEL3);
86574
+ const { Agent } = await loadSdk5();
86201
86575
  const agent = await withTimeout(Agent.create({
86202
86576
  apiKey,
86203
86577
  model: { id: modelId },
@@ -86221,10 +86595,10 @@ class CursorSdkHarness {
86221
86595
  const apiKey = process.env.CURSOR_API_KEY?.trim();
86222
86596
  if (!apiKey)
86223
86597
  throw new Error("CURSOR_API_KEY is not set");
86224
- const { Agent } = await loadSdk4();
86598
+ const { Agent } = await loadSdk5();
86225
86599
  const agent = await withTimeout(Agent.resume(sessionId, {
86226
86600
  apiKey,
86227
- model: { id: DEFAULT_MODEL2 },
86601
+ model: { id: DEFAULT_MODEL3 },
86228
86602
  local: { cwd: this.cwd, settingSources: [] }
86229
86603
  }), AGENT_CREATE_TIMEOUT_MS2, "Agent.resume");
86230
86604
  const session2 = new CursorSdkSession({
@@ -86251,9 +86625,9 @@ class CursorSdkHarness {
86251
86625
  this.sessions.clear();
86252
86626
  }
86253
86627
  }
86254
- var DEFAULT_MODEL2 = "composer-2.5", MODELS_LIST_TIMEOUT_MS2 = 60000, AGENT_CREATE_TIMEOUT_MS2 = 60000, _sdkCache4, _sdkLoadError4, startCursorSdkHarness = async (config3) => {
86628
+ var DEFAULT_MODEL3 = "composer-2.5", MODELS_LIST_TIMEOUT_MS2 = 60000, AGENT_CREATE_TIMEOUT_MS2 = 60000, _sdkCache5, _sdkLoadError5, startCursorSdkHarness = async (config3) => {
86255
86629
  try {
86256
- await loadSdk4();
86630
+ await loadSdk5();
86257
86631
  } catch (err) {
86258
86632
  throw new Error(`cursor-sdk unavailable: ${formatCursorSdkLoadError(err)}`);
86259
86633
  }
@@ -86267,37 +86641,17 @@ var init_cursor_harness = __esm(() => {
86267
86641
  init_cursor_sdk_package();
86268
86642
  });
86269
86643
 
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
86644
  // src/infrastructure/harnesses/cursor-sdk/event-extractor.ts
86291
86645
  function createCursorSdkChunkExtractor() {
86292
86646
  return createStandardSdkChunkExtractor();
86293
86647
  }
86294
- var init_event_extractor = () => {};
86648
+ var init_event_extractor2 = () => {};
86295
86649
 
86296
86650
  // src/infrastructure/harnesses/cursor-sdk/index.ts
86297
86651
  var init_cursor_sdk2 = __esm(() => {
86298
86652
  init_cursor_harness();
86299
86653
  init_cursor_session();
86300
- init_event_extractor();
86654
+ init_event_extractor2();
86301
86655
  });
86302
86656
 
86303
86657
  // src/infrastructure/harnesses/opencode-sdk/event-extractor.ts
@@ -86831,7 +87185,7 @@ var init_opencode_harness = __esm(() => {
86831
87185
  });
86832
87186
 
86833
87187
  // src/infrastructure/harnesses/pi-sdk/pi-session.ts
86834
- import { randomUUID as randomUUID5 } from "node:crypto";
87188
+ import { randomUUID as randomUUID7 } from "node:crypto";
86835
87189
 
86836
87190
  class PiSdkSession {
86837
87191
  opencodeSessionId;
@@ -86858,7 +87212,7 @@ class PiSdkSession {
86858
87212
  throw new Error("Session is closed");
86859
87213
  const text = input.parts.map((p) => p.text).join(`
86860
87214
  `);
86861
- const messageId = randomUUID5();
87215
+ const messageId = randomUUID7();
86862
87216
  const onSessionEvent = (event) => {
86863
87217
  if (this.closed)
86864
87218
  return;
@@ -86918,16 +87272,16 @@ var PROMPT_TIMEOUT_MS2 = 3600000;
86918
87272
  var init_pi_session = () => {};
86919
87273
 
86920
87274
  // src/infrastructure/harnesses/pi-sdk/pi-harness.ts
86921
- async function loadSdk5() {
86922
- if (_sdkCache5)
86923
- return _sdkCache5;
86924
- if (_sdkLoadError5)
86925
- throw _sdkLoadError5;
87275
+ async function loadSdk6() {
87276
+ if (_sdkCache6)
87277
+ return _sdkCache6;
87278
+ if (_sdkLoadError6)
87279
+ throw _sdkLoadError6;
86926
87280
  try {
86927
- _sdkCache5 = await importBundledPiSdk();
86928
- return _sdkCache5;
87281
+ _sdkCache6 = await importBundledPiSdk();
87282
+ return _sdkCache6;
86929
87283
  } catch (err) {
86930
- _sdkLoadError5 = err;
87284
+ _sdkLoadError6 = err;
86931
87285
  throw err;
86932
87286
  }
86933
87287
  }
@@ -86999,7 +87353,7 @@ class PiSdkHarness {
86999
87353
  const existing = this.sessions.get(sessionId);
87000
87354
  if (existing)
87001
87355
  return existing;
87002
- const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk5();
87356
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk6();
87003
87357
  const sessions = await SessionManager.list(this.cwd, getPiSessionDir(this.cwd));
87004
87358
  const match17 = sessions.find((s) => s.id === sessionId);
87005
87359
  if (!match17) {
@@ -87047,8 +87401,8 @@ class PiSdkHarness {
87047
87401
  this.sessions.clear();
87048
87402
  }
87049
87403
  async createAgentSession(systemPrompt, model) {
87050
- const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk5();
87051
- const resolvedModel = resolveModel2(this.modelRegistry, model ?? DEFAULT_MODEL3);
87404
+ const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = await loadSdk6();
87405
+ const resolvedModel = resolveModel2(this.modelRegistry, model ?? DEFAULT_MODEL4);
87052
87406
  if (!resolvedModel) {
87053
87407
  throw new Error("No Pi model available — configure provider credentials in ~/.pi/agent/auth.json");
87054
87408
  }
@@ -87069,9 +87423,9 @@ class PiSdkHarness {
87069
87423
  return session2;
87070
87424
  }
87071
87425
  }
87072
- var SESSION_CREATE_TIMEOUT_MS3 = 60000, DEFAULT_MODEL3 = "opencode/big-pickle", _sdkCache5, _sdkLoadError5, startPiSdkHarness = async (config3) => {
87426
+ var SESSION_CREATE_TIMEOUT_MS3 = 60000, DEFAULT_MODEL4 = "opencode/big-pickle", _sdkCache6, _sdkLoadError6, startPiSdkHarness = async (config3) => {
87073
87427
  try {
87074
- const { AuthStorage, ModelRegistry } = await loadSdk5();
87428
+ const { AuthStorage, ModelRegistry } = await loadSdk6();
87075
87429
  const authStorage = AuthStorage.create();
87076
87430
  const modelRegistry = ModelRegistry.create(authStorage);
87077
87431
  if (modelRegistry.getAvailable().length === 0) {
@@ -87092,13 +87446,13 @@ var init_pi_harness = __esm(() => {
87092
87446
  function createPiSdkChunkExtractor() {
87093
87447
  return createStandardSdkChunkExtractor();
87094
87448
  }
87095
- var init_event_extractor2 = () => {};
87449
+ var init_event_extractor3 = () => {};
87096
87450
 
87097
87451
  // src/infrastructure/harnesses/pi-sdk/index.ts
87098
87452
  var init_pi_sdk2 = __esm(() => {
87099
87453
  init_pi_harness();
87100
87454
  init_pi_session();
87101
- init_event_extractor2();
87455
+ init_event_extractor3();
87102
87456
  });
87103
87457
 
87104
87458
  // src/infrastructure/harnesses/registry.ts
@@ -87111,6 +87465,8 @@ async function startBoundHarness(config3) {
87111
87465
  return startCursorSdkHarness(config3);
87112
87466
  case "pi-sdk":
87113
87467
  return startPiSdkHarness(config3);
87468
+ case "claude-sdk":
87469
+ return startClaudeSdkHarness(config3);
87114
87470
  default: {
87115
87471
  const _exhaustive = config3.harnessName;
87116
87472
  throw new Error(`Unsupported direct harness: ${String(_exhaustive)}`);
@@ -87125,6 +87481,8 @@ function createChunkExtractor(harnessName) {
87125
87481
  return createCursorSdkChunkExtractor();
87126
87482
  case "pi-sdk":
87127
87483
  return createPiSdkChunkExtractor();
87484
+ case "claude-sdk":
87485
+ return createClaudeSdkChunkExtractor();
87128
87486
  default:
87129
87487
  return createStandardSdkChunkExtractor();
87130
87488
  }
@@ -87159,6 +87517,16 @@ async function isPiSdkInstalled() {
87159
87517
  return false;
87160
87518
  }
87161
87519
  }
87520
+ async function isClaudeSdkInstalled() {
87521
+ try {
87522
+ const { importBundledClaudeSdk: importBundledClaudeSdk2, resolvePathToClaudeCodeExecutable: resolvePathToClaudeCodeExecutable2 } = await Promise.resolve().then(() => (init_claude_sdk_package(), exports_claude_sdk_package));
87523
+ await importBundledClaudeSdk2();
87524
+ await resolvePathToClaudeCodeExecutable2();
87525
+ return true;
87526
+ } catch {
87527
+ return false;
87528
+ }
87529
+ }
87162
87530
  async function listInstalledNativeDirectHarnesses() {
87163
87531
  const installed2 = [];
87164
87532
  if (opencodeOnPath())
@@ -87167,9 +87535,12 @@ async function listInstalledNativeDirectHarnesses() {
87167
87535
  installed2.push("cursor-sdk");
87168
87536
  if (await isPiSdkInstalled())
87169
87537
  installed2.push("pi-sdk");
87538
+ if (await isClaudeSdkInstalled())
87539
+ installed2.push("claude-sdk");
87170
87540
  return installed2;
87171
87541
  }
87172
87542
  var init_registry4 = __esm(() => {
87543
+ init_claude_sdk2();
87173
87544
  init_cursor_sdk2();
87174
87545
  init_opencode_harness();
87175
87546
  init_pi_sdk2();
@@ -87808,6 +88179,14 @@ function recordLiveSessionChunk(event, handle, journal, extractChunk, deps) {
87808
88179
  deps.sessionRepository.bindTurnMessageId(handle.currentTurn.turnId, chunk2.messageId).catch((err) => console.warn("[direct-harness] bindTurnMessageId error:", err));
87809
88180
  }
87810
88181
  }
88182
+ function handleLiveSessionProviderId(event, deps, rowId, handle, liveSession) {
88183
+ if (event.type !== "session.provider_id")
88184
+ return;
88185
+ const sessionId = event.payload.sessionId;
88186
+ if (!sessionId || sessionId === handle.opencodeSessionId)
88187
+ return;
88188
+ deps.sessionRepository.associateOpenCodeSessionId(rowId, sessionId, liveSession.sessionTitle ?? "").catch((err) => console.warn("[direct-harness] associateOpenCodeSessionId (provider id) error:", err));
88189
+ }
87811
88190
  function handleLiveSessionTitleUpdate(event, deps, rowId, liveSession) {
87812
88191
  if (event.type !== "session.updated") {
87813
88192
  return;
@@ -87827,6 +88206,7 @@ function handleLiveSessionEvent(event, ctx) {
87827
88206
  handleSessionIdle(handle, journal, idleConfig, deps.sessionRepository).catch((err) => console.warn("[direct-harness] idle handler error:", err));
87828
88207
  }
87829
88208
  handleLiveSessionTitleUpdate(event, deps, rowId, liveSession);
88209
+ handleLiveSessionProviderId(event, deps, rowId, handle, liveSession);
87830
88210
  }
87831
88211
  async function processOne(daemonSession, deps, session2) {
87832
88212
  const rowId = session2._id;
@@ -90962,7 +91342,7 @@ function diffPathIndexes(previous, next4) {
90962
91342
  }
90963
91343
 
90964
91344
  // src/infrastructure/services/workspace/workspace-sync-state.ts
90965
- import { createHash as createHash6, randomUUID as randomUUID6 } from "node:crypto";
91345
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
90966
91346
  import * as fs11 from "node:fs/promises";
90967
91347
  import { homedir as homedir7 } from "node:os";
90968
91348
  import { join as join20 } from "node:path";
@@ -91024,7 +91404,7 @@ function createManifestFromTree(args2) {
91024
91404
  version: SYNC_STATE_VERSION,
91025
91405
  machineId: args2.machineId,
91026
91406
  workingDir: normalizeWorkingDirForLookup(args2.workingDir),
91027
- syncGeneration: randomUUID6(),
91407
+ syncGeneration: randomUUID8(),
91028
91408
  completedAt: Date.now(),
91029
91409
  scanner: args2.scanner,
91030
91410
  dataHash: args2.dataHash,
@@ -91046,7 +91426,7 @@ var init_workspace_sync_state = __esm(() => {
91046
91426
  });
91047
91427
 
91048
91428
  // src/infrastructure/services/workspace/workspace-file-tree-coordinator.ts
91049
- import { randomUUID as randomUUID7 } from "node:crypto";
91429
+ import { randomUUID as randomUUID9 } from "node:crypto";
91050
91430
  import path6 from "node:path";
91051
91431
  function deltaEntry(paths, pathValue) {
91052
91432
  const type = paths[pathValue];
@@ -91073,7 +91453,7 @@ function buildPendingDeltas(previous, next4) {
91073
91453
  const result = [];
91074
91454
  for (let offset = 0;offset < operations.length; offset += MAX_DELTA_OPERATIONS) {
91075
91455
  const delta = {
91076
- operationId: randomUUID7(),
91456
+ operationId: randomUUID9(),
91077
91457
  added: [],
91078
91458
  removed: [],
91079
91459
  typeChanged: [],
@@ -91297,7 +91677,7 @@ var init_workspace_file_tree_coordinator = __esm(() => {
91297
91677
  });
91298
91678
 
91299
91679
  // src/commands/machine/daemon-start/file-tree-subscription.ts
91300
- import { randomUUID as randomUUID8 } from "node:crypto";
91680
+ import { randomUUID as randomUUID10 } from "node:crypto";
91301
91681
  import { gzipSync as gzipSync4 } from "node:zlib";
91302
91682
  function logSubscriptionWarn(label, err) {
91303
91683
  console.warn(`[${formatTimestamp()}] ⚠️ ${label}: ${getErrorMessage2(err)}`);
@@ -91339,7 +91719,7 @@ function toDeltaOperations(delta) {
91339
91719
  }
91340
91720
  async function publishCheckpoint(session2, normalizedWorkingDir, tree, revision) {
91341
91721
  const dataHash = computeFileTreeDataHash(tree);
91342
- const syncGeneration = randomUUID8();
91722
+ const syncGeneration = randomUUID10();
91343
91723
  const snapshot = await syncScannedFileTree(session2, normalizedWorkingDir, tree, dataHash, syncGeneration);
91344
91724
  let checkpointRevision = revision;
91345
91725
  let result = await session2.backend.mutation(api.workspaceFiles.publishFileTreeCheckpoint, {
@@ -91868,6 +92248,8 @@ async function pushAvailableGitState(ctx, workingDir, stateKey, branchResult) {
91868
92248
  ...pipeline2.toMutationArgs(values3, false)
91869
92249
  });
91870
92250
  ctx.lastPushedGitState.set(stateKey, stateHash);
92251
+ lastPushedBranch.set(stateKey, branch);
92252
+ lastFullPushMs.set(stateKey, Date.now());
91871
92253
  console.log(`[${formatTimestamp()}] \uD83D\uDD00 Git state pushed: ${workingDir} (${branch}${values3.get("isDirty") ? ", dirty" : ", clean"})`);
91872
92254
  }
91873
92255
  if (ctx.lastPushedGitState.get(commitsKey) !== commitsHash) {
@@ -91919,6 +92301,7 @@ function pushObservedBranchErrorEffect(session2, lastPushedGitState, stateKey, w
91919
92301
  function pushObservedFullGitStateEffect(session2, lastPushedGitState, stateKey, workingDir, branch, reason) {
91920
92302
  return exports_Effect.gen(function* () {
91921
92303
  yield* exports_Effect.promise(() => pushSingleWorkspaceGitStateImpl(buildGitStateDeps(session2, lastPushedGitState), workingDir));
92304
+ lastPushedBranch.set(stateKey, branch);
91922
92305
  lastFullPushMs.set(stateKey, Date.now());
91923
92306
  console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Observed full git state pushed: ${workingDir} (${branch})${reason === "refresh" ? " [refresh]" : ""}`);
91924
92307
  });
@@ -91948,7 +92331,7 @@ function pushObservedSlimGitSummaryEffect(session2, lastPushedGitState, stateKey
91948
92331
  console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Observed git summary pushed: ${workingDir} (${branch}${values3.get("isDirty") ? ", dirty" : ", clean"})${reason === "refresh" ? " [refresh]" : ""}`);
91949
92332
  });
91950
92333
  }
91951
- var lastFullPushMs, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummaryForObservedEffect = (workingDir, reason = "safety-poll") => exports_Effect.gen(function* pushObservedGitSummaryForObserved() {
92334
+ var lastFullPushMs, lastPushedBranch, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummaryForObservedEffect = (workingDir, reason = "safety-poll") => exports_Effect.gen(function* pushObservedGitSummaryForObserved() {
91952
92335
  const session2 = yield* DaemonSessionService;
91953
92336
  const mutable2 = yield* DaemonMutableStateService;
91954
92337
  const lastPushedGitState = yield* exports_Ref.get(mutable2.lastPushedGitState);
@@ -91969,7 +92352,10 @@ var lastFullPushMs, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummary
91969
92352
  const branch = branchResult.branch;
91970
92353
  const now = Date.now();
91971
92354
  const lastFull = lastFullPushMs.get(stateKey) ?? 0;
91972
- if (now - lastFull >= OBSERVED_FULL_PUSH_INTERVAL_MS) {
92355
+ const previousBranch = lastPushedBranch.get(stateKey);
92356
+ const branchChanged = previousBranch !== undefined && previousBranch !== branch;
92357
+ const needsFullPush = reason === "refresh" || branchChanged || now - lastFull >= OBSERVED_FULL_PUSH_INTERVAL_MS;
92358
+ if (needsFullPush) {
91973
92359
  yield* pushObservedFullGitStateEffect(session2, lastPushedGitState, stateKey, workingDir, branch, reason);
91974
92360
  return;
91975
92361
  }
@@ -91990,6 +92376,7 @@ var init_git_heartbeat = __esm(() => {
91990
92376
  init_git_state_pipeline();
91991
92377
  init_convex_error();
91992
92378
  lastFullPushMs = new Map;
92379
+ lastPushedBranch = new Map;
91993
92380
  branchField = {
91994
92381
  key: "branch",
91995
92382
  includeInSlim: true,
@@ -93171,7 +93558,8 @@ var init_decide_resume_path = __esm(() => {
93171
93558
  "platform.team_switch",
93172
93559
  "platform.resume_storm",
93173
93560
  "daemon.shutdown",
93174
- "daemon.respawn"
93561
+ "daemon.respawn",
93562
+ "daemon.stop_timeout"
93175
93563
  ]);
93176
93564
  });
93177
93565
 
@@ -93338,21 +93726,14 @@ function classifyResumeStormReason(logLines) {
93338
93726
  }
93339
93727
  return "unknown";
93340
93728
  }
93341
- function isPermanentHarnessFailure(logLines) {
93342
- if (isTerminalProviderFailureInLogs(logLines)) {
93343
- return true;
93344
- }
93345
- return PERMANENT_FAILURE_REASONS.has(classifyResumeStormReason(logLines));
93346
- }
93347
93729
  function formatPermanentHarnessFailureMessage(logLines) {
93348
93730
  const reason = classifyResumeStormReason(logLines);
93349
93731
  const blob = logLines.join(`
93350
93732
  `).trim();
93351
- return blob ? `Permanent harness error (${reason}): ${blob.slice(-500)}` : `Permanent harness error (${reason})`;
93733
+ return blob ? `Crash recovery limit (${reason}): ${blob.slice(-500)}` : `Crash recovery limit (${reason})`;
93352
93734
  }
93353
93735
  var CLASSIFICATION_RULES, PERMANENT_FAILURE_REASONS;
93354
93736
  var init_classify_resume_storm_reason = __esm(() => {
93355
- init_terminal_provider_error();
93356
93737
  CLASSIFICATION_RULES = [
93357
93738
  {
93358
93739
  reason: "rate_limit",
@@ -93456,27 +93837,11 @@ async function handleTurnCompleted(deps, input, slot) {
93456
93837
  if (await tryAbortResumeStorm(deps, input, slot)) {
93457
93838
  return { outcome: "storm_aborted" };
93458
93839
  }
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
93840
  deps.killProcess(input.pid);
93475
93841
  return { outcome: "killed" };
93476
93842
  }
93477
93843
  var init_handle_turn_completed = __esm(() => {
93478
93844
  init_abort_resume_storm();
93479
- init_terminal_provider_error();
93480
93845
  });
93481
93846
 
93482
93847
  // src/infrastructure/deps/process.ts
@@ -93894,19 +94259,30 @@ class AgentProcessManager {
93894
94259
  whenTurnEndsIdle() {
93895
94260
  return this.turnEndQueue.whenIdle();
93896
94261
  }
94262
+ clearSlotRuntimeState(slot) {
94263
+ slot.state = "idle";
94264
+ slot.pid = undefined;
94265
+ slot.harness = undefined;
94266
+ slot.harnessSessionId = undefined;
94267
+ slot.resumableHarnessSessionId = undefined;
94268
+ slot.model = undefined;
94269
+ slot.workingDir = undefined;
94270
+ slot.startedAt = undefined;
94271
+ slot.pendingOperation = undefined;
94272
+ slot.stoppingSince = undefined;
94273
+ }
94274
+ bumpStopGeneration(slot) {
94275
+ slot.stopGeneration = (slot.stopGeneration ?? 0) + 1;
94276
+ return slot.stopGeneration;
94277
+ }
93897
94278
  async ensureRunning(opts) {
93898
94279
  const key = agentKey3(opts.chatroomId, opts.role);
93899
94280
  const slot = this.getOrCreateSlot(key);
93900
94281
  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;
94282
+ this.clearSlotRuntimeState(slot);
94283
+ }
94284
+ if (slot.state === "stopping" && (slot.stoppingSince === undefined || this.deps.clock.now() - slot.stoppingSince >= STOPPING_TIMEOUT_MS)) {
94285
+ await this.forceClearStuckStoppingSlot(key, slot, opts.chatroomId, opts.role, "daemon.stop_timeout");
93910
94286
  }
93911
94287
  if (slot.pendingOperation) {
93912
94288
  if (slot.state === "stopping") {
@@ -93970,7 +94346,9 @@ class AgentProcessManager {
93970
94346
  return { success: true };
93971
94347
  }
93972
94348
  slot.state = "stopping";
93973
- const operation = this.doStop(key, slot, pid, opts);
94349
+ const stopGeneration = this.bumpStopGeneration(slot);
94350
+ slot.stoppingSince = this.deps.clock.now();
94351
+ const operation = this.doStop(key, slot, pid, opts, stopGeneration);
93974
94352
  slot.pendingOperation = operation;
93975
94353
  return null;
93976
94354
  }
@@ -94053,24 +94431,6 @@ class AgentProcessManager {
94053
94431
  console.log(`[AgentProcessManager] ✅ Handled rapid resume storm for ${opts.role}`);
94054
94432
  return;
94055
94433
  }
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
94434
  try {
94075
94435
  const result = await this.deps.backend.mutation(api.participants.handleNativeAgentEnd, {
94076
94436
  sessionId: this.deps.sessionId,
@@ -94217,16 +94577,12 @@ class AgentProcessManager {
94217
94577
  console.log(`[AgentProcessManager] ⚠️ Cannot restart — missing harness or workingDir ` + `(role: ${opts.role}, harness: ${harness ?? "none"}, workingDir: ${workingDir ?? "none"})`);
94218
94578
  return;
94219
94579
  }
94220
- if (isPermanentHarnessFailure(logs)) {
94221
- this.handlePermanentFailureForRestart(opts, recentLogLines, ctx.terminalProviderFailureHandled);
94222
- return;
94223
- }
94224
94580
  const hadRunError = this.hasCursorSdkRunErrorInContext(logs);
94225
94581
  if (harness === "cursor-sdk") {
94226
94582
  this.retryCursorSdkSessionReopen(opts, ctx, hadRunError);
94227
94583
  return;
94228
94584
  }
94229
- this.ensureRunning({
94585
+ this.attemptCrashRecoveryRestart(opts, ctx, {
94230
94586
  chatroomId: opts.chatroomId,
94231
94587
  role: opts.role,
94232
94588
  agentHarness: harness,
@@ -94234,15 +94590,54 @@ class AgentProcessManager {
94234
94590
  workingDir,
94235
94591
  reason: "platform.crash_recovery",
94236
94592
  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
94593
  });
94245
94594
  }
94595
+ async attemptCrashRecoveryRestart(exitOpts, ctx, ensureOpts) {
94596
+ try {
94597
+ const result = await this.ensureRunning(ensureOpts);
94598
+ if (result.success)
94599
+ return;
94600
+ if (result.error === "backoff") {
94601
+ await this.retryCrashRecoveryAfterBackoff(exitOpts, ctx, ensureOpts, result.retryAfterMs);
94602
+ return;
94603
+ }
94604
+ if (result.error === "crash_loop") {
94605
+ this.handleCrashLoopLimitReached(exitOpts, ctx.recentLogLines);
94606
+ return;
94607
+ }
94608
+ console.log(`[AgentProcessManager] ⚠️ Agent restart did not complete for ${exitOpts.role}: ${result.error ?? "unknown"}`);
94609
+ } catch (err) {
94610
+ const message = err instanceof Error ? err.message : String(err);
94611
+ console.log(` ⚠️ Failed to restart agent: ${message}`);
94612
+ this.emitStartFailedEvent(exitOpts.role, exitOpts.chatroomId, message);
94613
+ }
94614
+ }
94615
+ async retryCrashRecoveryAfterBackoff(exitOpts, ctx, ensureOpts, retryAfterMs) {
94616
+ if (retryAfterMs === undefined || retryAfterMs <= 0) {
94617
+ return;
94618
+ }
94619
+ const classified = classifyResumeStormReason(ctx.recentLogLines ?? []);
94620
+ console.log(`[AgentProcessManager] ⏳ Crash recovery backoff for ${exitOpts.role} (${classified}): waiting ${retryAfterMs}ms`);
94621
+ await this.deps.clock.delay(retryAfterMs);
94622
+ const retry4 = await this.ensureRunning(ensureOpts);
94623
+ if (retry4.success)
94624
+ return;
94625
+ if (retry4.error === "crash_loop") {
94626
+ this.handleCrashLoopLimitReached(exitOpts, ctx.recentLogLines);
94627
+ return;
94628
+ }
94629
+ if (!retry4.success) {
94630
+ console.log(`[AgentProcessManager] ⚠️ Agent restart did not complete for ${exitOpts.role}: ${retry4.error ?? "unknown"}`);
94631
+ }
94632
+ }
94633
+ handleCrashLoopLimitReached(opts, recentLogLines) {
94634
+ const error = formatPermanentHarnessFailureMessage(recentLogLines ?? []);
94635
+ console.log(`[AgentProcessManager] ⛔ Crash recovery limit reached — ${error}`);
94636
+ this.deps.crashLoop.clear(opts.chatroomId, opts.role);
94637
+ const key = agentKey3(opts.chatroomId, opts.role);
94638
+ this.clearLastHarnessSession(key);
94639
+ this.emitStartFailedEvent(opts.role, opts.chatroomId, error);
94640
+ }
94246
94641
  clearHarnessSessionAfterResumePhaseFailure(key, opts) {
94247
94642
  const stored = this.lastHarnessSessions.get(key);
94248
94643
  this.clearLastHarnessSession(key);
@@ -94310,25 +94705,6 @@ class AgentProcessManager {
94310
94705
  this.sessionReopenRetryInFlight.delete(key);
94311
94706
  }
94312
94707
  }
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
94708
  emitStartFailedEvent(role, chatroomId, error) {
94333
94709
  this.deps.backend.mutation(api.machines.emitAgentStartFailed, {
94334
94710
  sessionId: this.deps.sessionId,
@@ -94355,6 +94731,37 @@ class AgentProcessManager {
94355
94731
  }
94356
94732
  return result;
94357
94733
  }
94734
+ listAllSlots() {
94735
+ const result = [];
94736
+ for (const [key, slot] of this.slots) {
94737
+ const [chatroomId, role] = key.split(":");
94738
+ result.push({ chatroomId, role, slot });
94739
+ }
94740
+ return result;
94741
+ }
94742
+ async clearStuckStoppingSlot(chatroomId, role) {
94743
+ const key = agentKey3(chatroomId, role);
94744
+ const slot = this.slots.get(key);
94745
+ if (!slot || slot.state !== "stopping") {
94746
+ return false;
94747
+ }
94748
+ const elapsed3 = slot.stoppingSince ? this.deps.clock.now() - slot.stoppingSince : STOPPING_TIMEOUT_MS;
94749
+ if (elapsed3 < STOPPING_TIMEOUT_MS) {
94750
+ return false;
94751
+ }
94752
+ await this.forceClearStuckStoppingSlot(key, slot, chatroomId, role, "daemon.stop_timeout");
94753
+ console.warn(`[AgentProcessManager] ⚠️ Cleared stuck stopping slot for ${role}@${chatroomId}`);
94754
+ return true;
94755
+ }
94756
+ async clearAllStuckStoppingSlots() {
94757
+ let cleared = 0;
94758
+ for (const { chatroomId, role } of this.listAllSlots()) {
94759
+ if (await this.clearStuckStoppingSlot(chatroomId, role)) {
94760
+ cleared++;
94761
+ }
94762
+ }
94763
+ return cleared;
94764
+ }
94358
94765
  async recover() {
94359
94766
  let entries2 = [];
94360
94767
  try {
@@ -94387,6 +94794,10 @@ class AgentProcessManager {
94387
94794
  }
94388
94795
  }
94389
94796
  console.log(`[AgentProcessManager] Recovery: ${killed} killed, ${cleaned} cleaned up`);
94797
+ const clearedCount = await this.clearAllStuckStoppingSlots();
94798
+ if (clearedCount > 0) {
94799
+ console.log(`[AgentProcessManager] Recovery: cleared ${clearedCount} stuck stopping slot(s)`);
94800
+ }
94390
94801
  }
94391
94802
  getOrCreateSlot(key) {
94392
94803
  let slot = this.slots.get(key);
@@ -94423,7 +94834,9 @@ class AgentProcessManager {
94423
94834
  if (slot?.pid && isProcessAlive(this.deps.processes.kill, slot.pid) && (slot.state === "running" || slot.state === "spawning")) {
94424
94835
  const pid = slot.pid;
94425
94836
  slot.state = "stopping";
94426
- await this.doStop(key, slot, pid, { chatroomId, role, reason: "daemon.respawn" });
94837
+ const stopGeneration = this.bumpStopGeneration(slot);
94838
+ slot.stoppingSince = this.deps.clock.now();
94839
+ await this.doStop(key, slot, pid, { chatroomId, role, reason: "daemon.respawn" }, stopGeneration);
94427
94840
  }
94428
94841
  }
94429
94842
  async killPersistedProcessIfAlive(chatroomId, role) {
@@ -94693,7 +95106,7 @@ class AgentProcessManager {
94693
95106
  if (loopCheck.waitMs !== undefined && loopCheck.waitMs > 0) {
94694
95107
  console.log(` ⏳ Agent restart backoff: waiting ${loopCheck.waitMs}ms before retry`);
94695
95108
  this.resetSlotIdle(slot);
94696
- return { success: false, error: "backoff" };
95109
+ return { success: false, error: "backoff", retryAfterMs: loopCheck.waitMs };
94697
95110
  }
94698
95111
  this.deps.backend.mutation(api.machines.emitRestartLimitReached, {
94699
95112
  sessionId: this.deps.sessionId,
@@ -95018,6 +95431,42 @@ class AgentProcessManager {
95018
95431
  slot.pid = undefined;
95019
95432
  slot.startedAt = undefined;
95020
95433
  slot.pendingOperation = undefined;
95434
+ slot.stoppingSince = undefined;
95435
+ }
95436
+ async forceClearStuckStoppingSlot(key, slot, chatroomId, role, reason) {
95437
+ const pid = slot.pid;
95438
+ const harness = slot.harness;
95439
+ const durationMs = slot.stoppingSince ? this.deps.clock.now() - slot.stoppingSince : STOPPING_TIMEOUT_MS;
95440
+ this.bumpStopGeneration(slot);
95441
+ this.clearSlotRuntimeState(slot);
95442
+ this.deps.backend.mutation(api.machines.emitAgentStopTimeout, {
95443
+ sessionId: this.deps.sessionId,
95444
+ machineId: this.deps.machineId,
95445
+ chatroomId,
95446
+ role,
95447
+ pid,
95448
+ durationMs
95449
+ }).catch((err) => {
95450
+ console.log(` ⚠️ Failed to emit agent.stopTimeout event: ${err.message}`);
95451
+ });
95452
+ if (pid) {
95453
+ try {
95454
+ this.deps.processes.kill(-pid, "SIGKILL");
95455
+ } catch {}
95456
+ const exitArgs = {
95457
+ sessionId: this.deps.sessionId,
95458
+ machineId: this.deps.machineId,
95459
+ chatroomId,
95460
+ role,
95461
+ pid,
95462
+ stopReason: reason,
95463
+ exitCode: undefined,
95464
+ signal: "SIGKILL",
95465
+ agentHarness: harness
95466
+ };
95467
+ this.recordAgentExitedOrQueueRetry(role, exitArgs, "Failed to record stop-timeout exit");
95468
+ }
95469
+ await this.clearAgentPidQuietly(chatroomId, role);
95021
95470
  }
95022
95471
  recordStopExit(slot, pid, opts) {
95023
95472
  const exitArgs3 = {
@@ -95036,7 +95485,7 @@ class AgentProcessManager {
95036
95485
  this.queueExitRetry({ role: opts.role, args: exitArgs3 });
95037
95486
  });
95038
95487
  }
95039
- async doStop(key, slot, pid, opts) {
95488
+ async doStop(key, slot, pid, opts, stopGeneration) {
95040
95489
  try {
95041
95490
  const harness = slot.harness;
95042
95491
  const service3 = harness ? this.deps.agentServices.get(harness) : undefined;
@@ -95048,6 +95497,9 @@ class AgentProcessManager {
95048
95497
  await this.killProcessWithFallback(pid);
95049
95498
  }
95050
95499
  } catch {}
95500
+ if (slot.stopGeneration !== stopGeneration) {
95501
+ return { success: true };
95502
+ }
95051
95503
  this.resetSlotAfterStop(slot);
95052
95504
  this.recordStopExit(slot, pid, opts);
95053
95505
  try {
@@ -95056,7 +95508,7 @@ class AgentProcessManager {
95056
95508
  return { success: true };
95057
95509
  }
95058
95510
  }
95059
- var AGENT_EXIT_RETRY_INTERVAL_MS = 1e4;
95511
+ var AGENT_EXIT_RETRY_INTERVAL_MS = 1e4, STOPPING_TIMEOUT_MS = 30000;
95060
95512
  var init_agent_process_manager = __esm(() => {
95061
95513
  init_types();
95062
95514
  init_participant();
@@ -95069,7 +95521,6 @@ var init_agent_process_manager = __esm(() => {
95069
95521
  init_agent_lifecycle();
95070
95522
  init_abort_resume_storm();
95071
95523
  init_classify_resume_storm_reason();
95072
- init_terminal_provider_error();
95073
95524
  init_handle_turn_completed();
95074
95525
  init_spawn_policy();
95075
95526
  init_agent_lifecycle_runtime();
@@ -108577,10 +109028,19 @@ function isPendingAliveRunningTask(task) {
108577
109028
  const { agentConfig, status: status3 } = task;
108578
109029
  return status3 === "pending" && agentConfig.spawnedAgentPid != null && agentConfig.desiredState === "running";
108579
109030
  }
108580
- function isSlotUnavailableForPid(slot, pid, isPidAlive) {
108581
- if (!slot || slot.state === "idle" || slot.state === "stopping") {
109031
+ function isSlotUnavailableForPid(slot, pid, isPidAlive, now = Date.now()) {
109032
+ if (!slot) {
109033
+ return true;
109034
+ }
109035
+ if (slot.state === "idle") {
108582
109036
  return true;
108583
109037
  }
109038
+ if (slot.state === "stopping") {
109039
+ if (!slot.stoppingSince || now - slot.stoppingSince >= STOPPING_TIMEOUT_MS) {
109040
+ return true;
109041
+ }
109042
+ return false;
109043
+ }
108584
109044
  if (slot.pid !== pid) {
108585
109045
  return true;
108586
109046
  }
@@ -108596,7 +109056,7 @@ function isNativeRevivableTaskStatus(task) {
108596
109056
  }
108597
109057
  return false;
108598
109058
  }
108599
- function isNativeAgentSlotDown(task, health) {
109059
+ function isNativeAgentSlotDown(task, health, now = Date.now()) {
108600
109060
  const slot = health.getSlot(task.chatroomId, task.agentConfig.role);
108601
109061
  if (slot?.state === "spawning")
108602
109062
  return false;
@@ -108604,20 +109064,20 @@ function isNativeAgentSlotDown(task, health) {
108604
109064
  if (pid == null) {
108605
109065
  return slot?.state !== "running";
108606
109066
  }
108607
- return isSlotUnavailableForPid(slot, pid, health.isPidAlive);
109067
+ return isSlotUnavailableForPid(slot, pid, health.isPidAlive, now);
108608
109068
  }
108609
- function isNativeActiveTaskAgentDown(task, health) {
109069
+ function isNativeActiveTaskAgentDown(task, health, now) {
108610
109070
  if (!isNativeHarness(task.agentConfig.agentHarness))
108611
109071
  return false;
108612
109072
  if (task.agentConfig.desiredState !== "running")
108613
109073
  return false;
108614
109074
  if (!isNativeRevivableTaskStatus(task))
108615
109075
  return false;
108616
- return isNativeAgentSlotDown(task, health);
109076
+ return isNativeAgentSlotDown(task, health, now);
108617
109077
  }
108618
109078
  function listNativeTasksNeedingRevive(tasks, health, now, cooldown) {
108619
109079
  return tasks.filter((task) => {
108620
- if (!isNativeActiveTaskAgentDown(task, health))
109080
+ if (!isNativeActiveTaskAgentDown(task, health, now))
108621
109081
  return false;
108622
109082
  const { chatroomId, agentConfig } = task;
108623
109083
  if (!agentConfig.workingDir)
@@ -108677,6 +109137,7 @@ var PENDING_IDLE_NUDGE_MS = 15000, NUDGE_COOLDOWN_MS = 60000;
108677
109137
  var init_task_monitor_logic = __esm(() => {
108678
109138
  init_native_task_injector_logic();
108679
109139
  init_predicates();
109140
+ init_agent_process_manager();
108680
109141
  });
108681
109142
  // ../../services/backend/src/domain/usecase/machine/assigned-task-monitor-row.ts
108682
109143
  function applyAssignedTaskSignal(existing, signal) {
@@ -109319,8 +109780,15 @@ function runCliNudgeEffect(task, runtime4, effectContext2, agentMgr, sessionDeps
109319
109780
  console.log(buildCliNudgeLogLine(task));
109320
109781
  executeCliNudge(task, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
109321
109782
  }
109783
+ async function clearStuckStoppingSlotIfNeeded(agentMgr, chatroomId, role) {
109784
+ const cleared = await agentMgr.clearStuckStoppingSlot(chatroomId, role);
109785
+ if (cleared) {
109786
+ console.log(`[TaskMonitor] cleared stuck stopping slot for ${role}@${chatroomId}`);
109787
+ }
109788
+ }
109322
109789
  async function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
109323
109790
  for (const row of listTasksReadyForNudge(tasks, now, cooldown)) {
109791
+ await clearStuckStoppingSlotIfNeeded(agentMgr, row.chatroomId, row.agentConfig.role);
109324
109792
  const full = await fetchTaskForAction(sessionDeps, machineId, row);
109325
109793
  if (!full)
109326
109794
  continue;
@@ -109329,6 +109797,7 @@ async function nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, a
109329
109797
  }
109330
109798
  async function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId) {
109331
109799
  for (const row of listNativeTasksNeedingRevive(tasks, localHealth, now, cooldown)) {
109800
+ await clearStuckStoppingSlotIfNeeded(agentMgr, row.chatroomId, row.agentConfig.role);
109332
109801
  const full = await fetchTaskForAction(sessionDeps, machineId, row);
109333
109802
  if (!full)
109334
109803
  continue;
@@ -109579,7 +110048,7 @@ function handleGitRefreshCommandEffect(event, tracker) {
109579
110048
  const lastPushedGitState = yield* exports_Ref.get(mutable2.lastPushedGitState);
109580
110049
  lastPushedGitState.delete(makeGitStateKey(session2.machineId, typedEvent.workingDir));
109581
110050
  console.log(`[${formatTimestamp()}] \uD83D\uDD04 Git refresh requested for ${typedEvent.workingDir}`);
109582
- yield* pushGitStateEffect;
110051
+ yield* pushSingleWorkspaceGitStateEffect(typedEvent.workingDir);
109583
110052
  tracker.gitRefreshIds.set(eventId, Date.now());
109584
110053
  });
109585
110054
  }
@@ -110988,4 +111457,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
110988
111457
  });
110989
111458
  program2.parse();
110990
111459
 
110991
- //# debugId=D9A06559C6AC810C64756E2164756E21
111460
+ //# debugId=B0C2AFBAB3CD49E664756E2164756E21