chatroom-cli 1.36.1 → 1.37.2

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
@@ -13426,6 +13426,10 @@ function startSessionEventForwarder(client4, options) {
13426
13426
  const errorTarget = options.errorTarget ?? process.stderr;
13427
13427
  let cancelled = false;
13428
13428
  let doneResolve;
13429
+ let sessionStarted = false;
13430
+ const seenToolStates = new Map;
13431
+ let lastStatus;
13432
+ const agentEndCallbacks = [];
13429
13433
  const donePromise = new Promise((resolve) => {
13430
13434
  doneResolve = resolve;
13431
13435
  });
@@ -13445,31 +13449,76 @@ function startSessionEventForwarder(client4, options) {
13445
13449
  const eventSession = eventSessionId(event);
13446
13450
  if (eventSession && eventSession !== options.sessionId)
13447
13451
  continue;
13448
- if (eventSession === undefined && event.type !== "session.idle" && event.type !== "session.compacted" && event.type !== "session.error" && event.type !== "session.status" && event.type !== "file.edited")
13452
+ if (eventSession === undefined && event.type !== "message.part.updated" && event.type !== "session.idle" && event.type !== "session.compacted" && event.type !== "session.error" && event.type !== "session.status" && event.type !== "file.edited")
13449
13453
  continue;
13454
+ if (!sessionStarted) {
13455
+ sessionStarted = true;
13456
+ target.write(formatLogLine(options, "session] Started", `role: ${options.role}`) + `
13457
+ `);
13458
+ }
13450
13459
  switch (event.type) {
13451
13460
  case "message.part.updated": {
13452
13461
  const props = event.properties;
13453
13462
  const part = props?.part;
13454
- if (part?.type === "text" && part.delta) {
13455
- target.write(formatLogLine(options, "text", part.delta) + `
13463
+ if (part?.type === "text") {
13464
+ const chunk = props?.delta !== undefined && props.delta !== "" ? props.delta : part.text;
13465
+ if (chunk) {
13466
+ target.write(formatLogLine(options, "text", chunk) + `
13456
13467
  `);
13468
+ }
13469
+ } else if (part?.type === "reasoning") {
13470
+ const chunk = props?.delta !== undefined && props.delta !== "" ? props.delta : part.text;
13471
+ if (chunk) {
13472
+ target.write(formatLogLine(options, "thinking", chunk) + `
13473
+ `);
13474
+ }
13457
13475
  } else if (part?.type === "tool" && part.tool) {
13458
- const state = props?.state ?? "started";
13459
- target.write(formatLogLine(options, "tool: " + part.tool, state) + `
13476
+ let appendInput = function(base, input, tool) {
13477
+ if (!input || typeof input === "object" && Object.keys(input).length === 0) {
13478
+ return base;
13479
+ }
13480
+ const inp = input;
13481
+ if (tool === "bash" && typeof inp.command === "string") {
13482
+ return `${base}: ${inp.command}`;
13483
+ }
13484
+ const inputStr = typeof inp === "string" ? inp : JSON.stringify(inp);
13485
+ return `${base}: ${inputStr}`;
13486
+ };
13487
+ const state = typeof props?.state === "string" ? props.state : typeof part.state?.status === "string" ? part.state.status : "started";
13488
+ let payload = state;
13489
+ if (part.state?.input) {
13490
+ payload = appendInput(payload, part.state.input, part.tool);
13491
+ }
13492
+ if (state === "completed" && part.state?.time?.start !== undefined && part.state?.time?.end !== undefined) {
13493
+ const duration = ((part.state.time.end - part.state.time.start) / 1000).toFixed(1);
13494
+ payload = appendInput(`${state} (${duration}s)`, part.state.input, part.tool);
13495
+ }
13496
+ const callID = part.callID ?? "unknown";
13497
+ const seenKey = `${callID}:${state}`;
13498
+ if (!seenToolStates.has(seenKey)) {
13499
+ seenToolStates.set(seenKey, payload);
13500
+ target.write(formatLogLine(options, "tool: " + part.tool, payload) + `
13460
13501
  `);
13502
+ }
13503
+ if (state === "completed" || state === "error") {
13504
+ seenToolStates.delete(seenKey);
13505
+ }
13461
13506
  }
13462
13507
  break;
13463
13508
  }
13464
13509
  case "file.edited": {
13465
13510
  const props = event.properties;
13466
- target.write(formatLogLine(options, "file", props?.file) + `
13511
+ const kind = props?.action ?? props?.kind;
13512
+ const filePayload = kind ? `${props?.file} (${kind})` : props?.file;
13513
+ target.write(formatLogLine(options, "file", filePayload) + `
13467
13514
  `);
13468
13515
  break;
13469
13516
  }
13470
13517
  case "session.idle": {
13471
13518
  target.write(formatLogLine(options, "agent_end") + `
13472
13519
  `);
13520
+ for (const cb of agentEndCallbacks)
13521
+ cb();
13473
13522
  break;
13474
13523
  }
13475
13524
  case "session.compacted": {
@@ -13479,15 +13528,25 @@ function startSessionEventForwarder(client4, options) {
13479
13528
  }
13480
13529
  case "session.status": {
13481
13530
  const props = event.properties;
13482
- target.write(formatLogLine(options, "status", props?.status?.type) + `
13531
+ const currentStatus = props?.status?.type;
13532
+ if (currentStatus !== lastStatus) {
13533
+ lastStatus = currentStatus;
13534
+ target.write(formatLogLine(options, "status", currentStatus) + `
13483
13535
  `);
13536
+ }
13484
13537
  break;
13485
13538
  }
13486
13539
  case "session.error": {
13487
13540
  const props = event.properties;
13488
13541
  const err = props?.error;
13489
13542
  const errMsg = err?.name ? `${err.name}${err?.data?.message ? ": " + err.data.message : ""}` : String(err ?? "unknown");
13490
- errorTarget.write(formatLogLine(options, "error", errMsg) + `
13543
+ let payload = errMsg;
13544
+ if (props?.tool) {
13545
+ payload += ` [tool: ${props.tool}]`;
13546
+ } else if (props?.command) {
13547
+ payload += ` [command: ${props.command}]`;
13548
+ }
13549
+ errorTarget.write(formatLogLine(options, "error", payload) + `
13491
13550
  `);
13492
13551
  break;
13493
13552
  }
@@ -13508,7 +13567,10 @@ function startSessionEventForwarder(client4, options) {
13508
13567
  stop: () => {
13509
13568
  cancelled = true;
13510
13569
  },
13511
- done: donePromise
13570
+ done: donePromise,
13571
+ onAgentEnd: (cb) => {
13572
+ agentEndCallbacks.push(cb);
13573
+ }
13512
13574
  };
13513
13575
  }
13514
13576
 
@@ -13629,7 +13691,12 @@ var init_opencode_sdk_agent_service = __esm(() => {
13629
13691
  agent: selected.name,
13630
13692
  ...composedSystem ? { system: composedSystem } : {},
13631
13693
  parts: [{ type: "text", text: prompt }],
13632
- ...modelParts ? { model: modelParts } : {}
13694
+ ...modelParts ? { model: modelParts } : {},
13695
+ tools: {
13696
+ task: false,
13697
+ question: false,
13698
+ external_directory: false
13699
+ }
13633
13700
  }
13634
13701
  }), PROMPT_ASYNC_TIMEOUT_MS, "session.promptAsync");
13635
13702
  } catch (err) {
@@ -13688,6 +13755,9 @@ var init_opencode_sdk_agent_service = __esm(() => {
13688
13755
  },
13689
13756
  onOutput: (cb) => {
13690
13757
  outputCallbacks.push(cb);
13758
+ },
13759
+ onAgentEnd: (cb) => {
13760
+ forwarder?.onAgentEnd(cb);
13691
13761
  }
13692
13762
  };
13693
13763
  }
@@ -58879,6 +58949,13 @@ function handlePing() {
58879
58949
  // src/commands/machine/daemon-start/handlers/command-runner.ts
58880
58950
  import { spawn as spawn3 } from "node:child_process";
58881
58951
  import { access as access3 } from "node:fs/promises";
58952
+ function evictStalePendingStops() {
58953
+ const evictBefore = Date.now() - PENDING_STOP_TTL_MS;
58954
+ for (const [runId, ts] of pendingStops) {
58955
+ if (ts < evictBefore)
58956
+ pendingStops.delete(runId);
58957
+ }
58958
+ }
58882
58959
  async function reportRunFailed(ctx, runId, reason) {
58883
58960
  try {
58884
58961
  await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
@@ -58923,6 +59000,21 @@ async function onCommandRun(ctx, event) {
58923
59000
  console.log(`[${formatTimestamp()}] ⚠️ Command already running: ${runIdStr}`);
58924
59001
  return;
58925
59002
  }
59003
+ if (pendingStops.has(runIdStr)) {
59004
+ pendingStops.delete(runIdStr);
59005
+ console.log(`[${formatTimestamp()}] ⏭️ Skipping command run due to pending stop: ${commandName} (${runIdStr})`);
59006
+ try {
59007
+ await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
59008
+ sessionId: ctx.sessionId,
59009
+ machineId: ctx.machineId,
59010
+ runId,
59011
+ status: "stopped"
59012
+ });
59013
+ } catch (err) {
59014
+ console.warn(`[${formatTimestamp()}] ⚠️ Failed to update status to stopped for pending-stop skip: ${getErrorMessage(err)}`);
59015
+ }
59016
+ return;
59017
+ }
58926
59018
  console.log(`[${formatTimestamp()}] \uD83D\uDE80 Running command: ${commandName} → ${script}`);
58927
59019
  if (!workingDir.startsWith("/")) {
58928
59020
  console.error(`[${formatTimestamp()}] ❌ Rejected command: workingDir is not absolute: ${workingDir}`);
@@ -58942,6 +59034,34 @@ async function onCommandRun(ctx, event) {
58942
59034
  stdio: ["ignore", "pipe", "pipe"],
58943
59035
  detached: true
58944
59036
  });
59037
+ const timeoutTimer = setTimeout(() => {
59038
+ console.log(`[${formatTimestamp()}] ⏰ Command timed out after ${DEFAULT_COMMAND_TIMEOUT_MS / 60000} minutes: ${commandName} (runId: ${runIdStr})`);
59039
+ const pid = child.pid;
59040
+ if (pid) {
59041
+ try {
59042
+ process.kill(-pid, "SIGTERM");
59043
+ } catch {
59044
+ child.kill("SIGTERM");
59045
+ }
59046
+ } else {
59047
+ child.kill("SIGTERM");
59048
+ }
59049
+ setTimeout(() => {
59050
+ if (!runningProcesses.has(runIdStr))
59051
+ return;
59052
+ console.log(`[${formatTimestamp()}] \uD83D\uDD2A Force-killing timed-out process: ${runIdStr}`);
59053
+ if (pid) {
59054
+ try {
59055
+ process.kill(-pid, "SIGKILL");
59056
+ } catch {
59057
+ child.kill("SIGKILL");
59058
+ }
59059
+ } else {
59060
+ child.kill("SIGKILL");
59061
+ }
59062
+ }, SIGTERM_GRACE_PERIOD_MS);
59063
+ }, DEFAULT_COMMAND_TIMEOUT_MS);
59064
+ timeoutTimer.unref?.();
58945
59065
  const tracked = {
58946
59066
  process: child,
58947
59067
  runId: runIdStr,
@@ -58949,7 +59069,8 @@ async function onCommandRun(ctx, event) {
58949
59069
  chunkIndex: 0,
58950
59070
  flushTimer: setInterval(() => {
58951
59071
  flushOutput(ctx, tracked).catch(() => {});
58952
- }, OUTPUT_FLUSH_INTERVAL_MS)
59072
+ }, OUTPUT_FLUSH_INTERVAL_MS),
59073
+ timeoutTimer
58953
59074
  };
58954
59075
  tracked.flushTimer.unref?.();
58955
59076
  runningProcesses.set(runIdStr, tracked);
@@ -58974,6 +59095,8 @@ async function onCommandRun(ctx, event) {
58974
59095
  console.log(`[${formatTimestamp()}] \uD83C\uDFC1 Command exited: ${commandName} (code=${code2}, signal=${signal})`);
58975
59096
  await flushOutput(ctx, tracked).catch(() => {});
58976
59097
  clearInterval(tracked.flushTimer);
59098
+ if (tracked.timeoutTimer)
59099
+ clearTimeout(tracked.timeoutTimer);
58977
59100
  runningProcesses.delete(runIdStr);
58978
59101
  const status = code2 === 0 ? "completed" : signal ? "stopped" : "failed";
58979
59102
  try {
@@ -58991,6 +59114,8 @@ async function onCommandRun(ctx, event) {
58991
59114
  child.on("error", async (err) => {
58992
59115
  console.error(`[${formatTimestamp()}] ❌ Command spawn failed: ${commandName}: ${err.message}`);
58993
59116
  clearInterval(tracked.flushTimer);
59117
+ if (tracked.timeoutTimer)
59118
+ clearTimeout(tracked.timeoutTimer);
58994
59119
  runningProcesses.delete(runIdStr);
58995
59120
  try {
58996
59121
  await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
@@ -59004,11 +59129,42 @@ async function onCommandRun(ctx, event) {
59004
59129
  }
59005
59130
  });
59006
59131
  }
59132
+ function killProcess(child, signal) {
59133
+ const pid = child.pid;
59134
+ if (pid) {
59135
+ try {
59136
+ process.kill(-pid, signal);
59137
+ return;
59138
+ } catch {}
59139
+ }
59140
+ try {
59141
+ child.kill(signal);
59142
+ } catch {}
59143
+ }
59144
+ function waitForExit(runIdStr, ms) {
59145
+ return new Promise((resolve5) => {
59146
+ const interval = 100;
59147
+ let elapsed = 0;
59148
+ const timer = setInterval(() => {
59149
+ if (!runningProcesses.has(runIdStr)) {
59150
+ clearInterval(timer);
59151
+ resolve5(true);
59152
+ return;
59153
+ }
59154
+ elapsed += interval;
59155
+ if (elapsed >= ms) {
59156
+ clearInterval(timer);
59157
+ resolve5(false);
59158
+ }
59159
+ }, interval);
59160
+ });
59161
+ }
59007
59162
  async function onCommandStop(ctx, event) {
59008
59163
  const runIdStr = event.runId.toString();
59009
59164
  const tracked = runningProcesses.get(runIdStr);
59010
59165
  if (!tracked) {
59011
- console.log(`[${formatTimestamp()}] ⚠️ No running process found for run: ${runIdStr}`);
59166
+ console.log(`[${formatTimestamp()}] ⚠️ No running process found for run: ${runIdStr} — marking as stopped`);
59167
+ pendingStops.set(runIdStr, Date.now());
59012
59168
  try {
59013
59169
  await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
59014
59170
  sessionId: ctx.sessionId,
@@ -59016,44 +59172,85 @@ async function onCommandStop(ctx, event) {
59016
59172
  runId: event.runId,
59017
59173
  status: "stopped"
59018
59174
  });
59019
- console.log(`[${formatTimestamp()}] \uD83D\uDCDD Marked orphaned run as stopped: ${runIdStr}`);
59020
59175
  } catch (err) {
59021
- console.warn(`[${formatTimestamp()}] ⚠️ Failed to mark orphaned run as stopped: ${getErrorMessage(err)}`);
59176
+ console.warn(`[${formatTimestamp()}] ⚠️ Failed to mark run as stopped (will retry): ${getErrorMessage(err)}`);
59177
+ throw err;
59022
59178
  }
59023
59179
  return;
59024
59180
  }
59025
59181
  console.log(`[${formatTimestamp()}] \uD83D\uDED1 Stopping command run: ${runIdStr}`);
59026
- const pid = tracked.process.pid;
59027
- if (pid) {
59028
- try {
59029
- process.kill(-pid, "SIGTERM");
59030
- } catch {
59031
- tracked.process.kill("SIGTERM");
59032
- }
59033
- } else {
59034
- tracked.process.kill("SIGTERM");
59182
+ if (tracked.timeoutTimer) {
59183
+ clearTimeout(tracked.timeoutTimer);
59184
+ tracked.timeoutTimer = null;
59035
59185
  }
59036
- setTimeout(() => {
59037
- if (tracked.process.killed)
59038
- return;
59186
+ killProcess(tracked.process, "SIGTERM");
59187
+ const exitedAfterSigterm = await waitForExit(runIdStr, SIGTERM_GRACE_PERIOD_MS);
59188
+ if (!exitedAfterSigterm) {
59039
59189
  console.log(`[${formatTimestamp()}] \uD83D\uDD2A Force-killing process: ${runIdStr}`);
59190
+ killProcess(tracked.process, "SIGKILL");
59191
+ const exitedAfterSigkill = await waitForExit(runIdStr, 1000);
59192
+ if (!exitedAfterSigkill) {
59193
+ console.error(`[${formatTimestamp()}] ❌ Failed to stop process for run: ${runIdStr} — process did not exit after SIGKILL`);
59194
+ }
59195
+ }
59196
+ try {
59197
+ await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
59198
+ sessionId: ctx.sessionId,
59199
+ machineId: ctx.machineId,
59200
+ runId: event.runId,
59201
+ status: "stopped"
59202
+ });
59203
+ } catch (err) {
59204
+ console.warn(`[${formatTimestamp()}] ⚠️ Failed to mark run as stopped in backend: ${getErrorMessage(err)}`);
59205
+ }
59206
+ }
59207
+ async function shutdownAllCommands(ctx) {
59208
+ if (runningProcesses.size === 0)
59209
+ return;
59210
+ console.log(`[${formatTimestamp()}] Shutting down ${runningProcesses.size} running command(s)...`);
59211
+ for (const [, tracked] of runningProcesses) {
59212
+ clearInterval(tracked.flushTimer);
59213
+ if (tracked.timeoutTimer)
59214
+ clearTimeout(tracked.timeoutTimer);
59215
+ await flushOutput(ctx, tracked).catch(() => {});
59216
+ const pid = tracked.process.pid;
59040
59217
  if (pid) {
59041
59218
  try {
59042
- process.kill(-pid, "SIGKILL");
59219
+ process.kill(-pid, "SIGTERM");
59043
59220
  } catch {
59044
- tracked.process.kill("SIGKILL");
59221
+ tracked.process.kill("SIGTERM");
59045
59222
  }
59046
59223
  } else {
59047
- tracked.process.kill("SIGKILL");
59224
+ tracked.process.kill("SIGTERM");
59225
+ }
59226
+ }
59227
+ await new Promise((resolve5) => {
59228
+ const t = setTimeout(resolve5, 3000);
59229
+ t.unref?.();
59230
+ });
59231
+ for (const [, tracked] of runningProcesses) {
59232
+ const pid = tracked.process.pid;
59233
+ if (pid) {
59234
+ try {
59235
+ process.kill(-pid, "SIGKILL");
59236
+ } catch {}
59237
+ } else {
59238
+ try {
59239
+ tracked.process.kill("SIGKILL");
59240
+ } catch {}
59048
59241
  }
59049
- }, 5000);
59242
+ }
59243
+ runningProcesses.clear();
59244
+ console.log(`[${formatTimestamp()}] All commands stopped`);
59050
59245
  }
59051
- var runningProcesses, OUTPUT_FLUSH_INTERVAL_MS = 3000, MAX_BUFFER_SIZE;
59246
+ var runningProcesses, pendingStops, PENDING_STOP_TTL_MS = 60000, OUTPUT_FLUSH_INTERVAL_MS = 3000, MAX_BUFFER_SIZE, DEFAULT_COMMAND_TIMEOUT_MS, SIGTERM_GRACE_PERIOD_MS = 5000;
59052
59247
  var init_command_runner = __esm(() => {
59053
59248
  init_api3();
59054
59249
  init_convex_error();
59055
59250
  runningProcesses = new Map;
59251
+ pendingStops = new Map;
59056
59252
  MAX_BUFFER_SIZE = 100 * 1024;
59253
+ DEFAULT_COMMAND_TIMEOUT_MS = 30 * 60 * 1000;
59057
59254
  });
59058
59255
 
59059
59256
  // src/commands/machine/daemon-start/handlers/state-recovery.ts
@@ -59097,6 +59294,14 @@ var init_state_recovery = __esm(() => {
59097
59294
  init_api3();
59098
59295
  });
59099
59296
 
59297
+ // src/commands/machine/daemon-start/capabilities-snapshot.ts
59298
+ function harnessCapabilitiesFingerprint(harnesses, versions) {
59299
+ const h = [...harnesses].sort().join("\x01");
59300
+ const keys = Object.keys(versions).sort();
59301
+ const v3 = keys.map((k) => `${k}:${JSON.stringify(versions[k] ?? null)}`).join("\x02");
59302
+ return `${h}::${v3}`;
59303
+ }
59304
+
59100
59305
  // src/infrastructure/local-api/routes/identity.ts
59101
59306
  async function handleIdentity(_req, ctx) {
59102
59307
  const identity = {
@@ -60294,6 +60499,17 @@ async function recoverState(ctx) {
60294
60499
  } catch (e) {
60295
60500
  console.log(` ⚠️ Failed to clear stale PIDs: ${getErrorMessage(e)}`);
60296
60501
  }
60502
+ try {
60503
+ const runResult = await ctx.deps.backend.mutation(api.commands.clearStaleCommandRuns, {
60504
+ sessionId: ctx.sessionId,
60505
+ machineId: ctx.machineId
60506
+ });
60507
+ if (runResult.clearedCount > 0) {
60508
+ console.log(` \uD83E\uDDF9 Cleared ${runResult.clearedCount} stale command run(s) from backend`);
60509
+ }
60510
+ } catch (e) {
60511
+ console.log(` ⚠️ Failed to clear stale command runs: ${getErrorMessage(e)}`);
60512
+ }
60297
60513
  }
60298
60514
  async function initDaemon() {
60299
60515
  if (!acquireLock()) {
@@ -60344,6 +60560,7 @@ async function initDaemon() {
60344
60560
  agentServices,
60345
60561
  lastPushedGitState: new Map,
60346
60562
  lastPushedModels: availableModels,
60563
+ lastPushedHarnessFingerprint: harnessCapabilitiesFingerprint(config3.availableHarnesses, config3.harnessVersions),
60347
60564
  observedSyncEnabled: featureFlags.observedSyncEnabled ?? false
60348
60565
  };
60349
60566
  registerEventListeners(ctx);
@@ -60392,6 +60609,7 @@ var init_init2 = __esm(() => {
60392
60609
 
60393
60610
  // src/events/lifecycle/on-daemon-shutdown.ts
60394
60611
  async function onDaemonShutdown(ctx) {
60612
+ await shutdownAllCommands(ctx);
60395
60613
  const activeAgents = ctx.deps.agentProcessManager.listActive();
60396
60614
  if (activeAgents.length > 0) {
60397
60615
  console.log(`[${formatTimestamp()}] Stopping ${activeAgents.length} agent(s)...`);
@@ -60420,6 +60638,7 @@ async function onDaemonShutdown(ctx) {
60420
60638
  }
60421
60639
  var init_on_daemon_shutdown = __esm(() => {
60422
60640
  init_api3();
60641
+ init_command_runner();
60423
60642
  });
60424
60643
 
60425
60644
  // src/infrastructure/git/git-writer.ts
@@ -60611,15 +60830,18 @@ function formatModelMap(map) {
60611
60830
  return Object.entries(map).map(([harness, models]) => `${harness}: ${models.join(", ")}`).join("; ");
60612
60831
  }
60613
60832
  async function refreshModels(ctx) {
60614
- if (!ctx.config)
60615
- return;
60833
+ if (!ctx.config) {
60834
+ return { kind: "noop" };
60835
+ }
60616
60836
  const models = await discoverModels(ctx.agentServices);
60617
60837
  const freshConfig = ensureMachineRegistered();
60618
60838
  ctx.config.availableHarnesses = freshConfig.availableHarnesses;
60619
60839
  ctx.config.harnessVersions = freshConfig.harnessVersions;
60620
- const diff = diffModels(ctx.lastPushedModels, models);
60621
- if (!diff.hasChanges) {
60622
- return;
60840
+ const modelDiff = diffModels(ctx.lastPushedModels, models);
60841
+ const nextHarnessFingerprint = harnessCapabilitiesFingerprint(ctx.config.availableHarnesses, ctx.config.harnessVersions);
60842
+ const harnessFingerprintChanged = ctx.lastPushedHarnessFingerprint !== null && nextHarnessFingerprint !== ctx.lastPushedHarnessFingerprint;
60843
+ if (!modelDiff.hasChanges && !harnessFingerprintChanged) {
60844
+ return { kind: "skipped_no_changes" };
60623
60845
  }
60624
60846
  const totalCount = Object.values(models).flat().length;
60625
60847
  try {
@@ -60631,15 +60853,19 @@ async function refreshModels(ctx) {
60631
60853
  availableModels: models
60632
60854
  });
60633
60855
  ctx.lastPushedModels = models;
60634
- if (Object.keys(diff.added).length > 0) {
60635
- console.log(`[${formatTimestamp()}] New models detected — ${formatModelMap(diff.added)}`);
60856
+ ctx.lastPushedHarnessFingerprint = nextHarnessFingerprint;
60857
+ if (Object.keys(modelDiff.added).length > 0) {
60858
+ console.log(`[${formatTimestamp()}] ➕ New models detected — ${formatModelMap(modelDiff.added)}`);
60636
60859
  }
60637
- if (Object.keys(diff.removed).length > 0) {
60638
- console.log(`[${formatTimestamp()}] ➖ Models no longer available — ${formatModelMap(diff.removed)}`);
60860
+ if (Object.keys(modelDiff.removed).length > 0) {
60861
+ console.log(`[${formatTimestamp()}] ➖ Models no longer available — ${formatModelMap(modelDiff.removed)}`);
60639
60862
  }
60640
60863
  console.log(`[${formatTimestamp()}] \uD83D\uDD04 Model refresh pushed: ${totalCount > 0 ? `${totalCount} models` : "none discovered"}`);
60864
+ return { kind: "pushed" };
60641
60865
  } catch (error) {
60642
- console.warn(`[${formatTimestamp()}] ⚠️ Model refresh failed: ${getErrorMessage(error)}`);
60866
+ const message = getErrorMessage(error);
60867
+ console.warn(`[${formatTimestamp()}] ⚠️ Model refresh failed: ${message}`);
60868
+ return { kind: "failed", message };
60643
60869
  }
60644
60870
  }
60645
60871
  function evictStaleDedupEntries(tracker) {
@@ -60656,6 +60882,10 @@ function evictStaleDedupEntries(tracker) {
60656
60882
  if (ts2 < evictBefore)
60657
60883
  tracker.gitRefreshIds.delete(id);
60658
60884
  }
60885
+ for (const [id, ts2] of tracker.capabilitiesRefreshIds) {
60886
+ if (ts2 < evictBefore)
60887
+ tracker.capabilitiesRefreshIds.delete(id);
60888
+ }
60659
60889
  for (const [id, ts2] of tracker.localActionIds) {
60660
60890
  if (ts2 < evictBefore)
60661
60891
  tracker.localActionIds.delete(id);
@@ -60668,6 +60898,7 @@ function evictStaleDedupEntries(tracker) {
60668
60898
  if (ts2 < evictBefore)
60669
60899
  tracker.commandStopIds.delete(id);
60670
60900
  }
60901
+ evictStalePendingStops();
60671
60902
  }
60672
60903
  async function dispatchCommandEvent(ctx, event, tracker) {
60673
60904
  const eventId = event._id.toString();
@@ -60675,40 +60906,40 @@ async function dispatchCommandEvent(ctx, event, tracker) {
60675
60906
  if (event.type === "agent.requestStart") {
60676
60907
  if (tracker.commandIds.has(eventId))
60677
60908
  return;
60678
- tracker.commandIds.set(eventId, Date.now());
60679
60909
  await onRequestStartAgent(ctx, event);
60910
+ tracker.commandIds.set(eventId, Date.now());
60680
60911
  } else if (event.type === "agent.requestStop") {
60681
60912
  if (tracker.commandIds.has(eventId))
60682
60913
  return;
60683
- tracker.commandIds.set(eventId, Date.now());
60684
60914
  await onRequestStopAgent(ctx, event);
60915
+ tracker.commandIds.set(eventId, Date.now());
60685
60916
  } else if (event.type === "daemon.ping") {
60686
60917
  if (tracker.pingIds.has(eventId))
60687
60918
  return;
60688
- tracker.pingIds.set(eventId, Date.now());
60689
60919
  handlePing();
60690
60920
  await ctx.deps.backend.mutation(api.machines.ackPing, {
60691
60921
  sessionId: ctx.sessionId,
60692
60922
  machineId: ctx.machineId,
60693
60923
  pingEventId: event._id
60694
60924
  });
60925
+ tracker.pingIds.set(eventId, Date.now());
60695
60926
  } else if (event.type === "daemon.gitRefresh") {
60696
60927
  if (tracker.gitRefreshIds.has(eventId))
60697
60928
  return;
60698
- tracker.gitRefreshIds.set(eventId, Date.now());
60699
60929
  const stateKey = makeGitStateKey(ctx.machineId, event.workingDir);
60700
60930
  ctx.lastPushedGitState.delete(stateKey);
60701
60931
  console.log(`[${formatTimestamp()}] \uD83D\uDD04 Git refresh requested for ${event.workingDir}`);
60702
60932
  await pushGitState(ctx);
60933
+ tracker.gitRefreshIds.set(eventId, Date.now());
60703
60934
  } else if (event.type === "daemon.localAction") {
60704
60935
  if (tracker.localActionIds.has(eventId))
60705
60936
  return;
60706
- tracker.localActionIds.set(eventId, Date.now());
60707
60937
  console.log(`[${formatTimestamp()}] \uD83D\uDDA5️ Local action: ${event.action} → ${event.workingDir}`);
60708
60938
  const result = await executeLocalAction(event.action, event.workingDir);
60709
60939
  if (!result.success) {
60710
60940
  console.warn(`[${formatTimestamp()}] ⚠️ Local action failed: ${result.error}`);
60711
60941
  }
60942
+ tracker.localActionIds.set(eventId, Date.now());
60712
60943
  } else if (eventType === "command.run") {
60713
60944
  if (tracker.commandRunIds.has(eventId))
60714
60945
  return;
@@ -60717,8 +60948,42 @@ async function dispatchCommandEvent(ctx, event, tracker) {
60717
60948
  } else if (eventType === "command.stop") {
60718
60949
  if (tracker.commandStopIds.has(eventId))
60719
60950
  return;
60720
- tracker.commandStopIds.set(eventId, Date.now());
60721
60951
  await onCommandStop(ctx, event);
60952
+ tracker.commandStopIds.set(eventId, Date.now());
60953
+ } else if (event.type === "daemon.refreshCapabilities") {
60954
+ if (tracker.capabilitiesRefreshIds.has(eventId))
60955
+ return;
60956
+ console.log(`[${formatTimestamp()}] \uD83D\uDD04 Manual capabilities refresh requested`);
60957
+ const outcome = await refreshModels(ctx);
60958
+ tracker.capabilitiesRefreshIds.set(eventId, Date.now());
60959
+ const batchId = "batchId" in event && event.batchId !== undefined ? event.batchId : undefined;
60960
+ if (!batchId) {
60961
+ return;
60962
+ }
60963
+ let status;
60964
+ let errorMessage;
60965
+ if (outcome.kind === "pushed") {
60966
+ status = "completed";
60967
+ } else if (outcome.kind === "skipped_no_changes") {
60968
+ status = "skipped_no_changes";
60969
+ } else if (outcome.kind === "failed") {
60970
+ status = "failed";
60971
+ errorMessage = outcome.message;
60972
+ } else {
60973
+ status = "failed";
60974
+ errorMessage = "Daemon configuration unavailable";
60975
+ }
60976
+ try {
60977
+ await ctx.deps.backend.mutation(api.machines.reportCapabilitiesRefreshResult, {
60978
+ sessionId: ctx.sessionId,
60979
+ batchId,
60980
+ machineId: ctx.machineId,
60981
+ status,
60982
+ errorMessage
60983
+ });
60984
+ } catch (error) {
60985
+ console.warn(`[${formatTimestamp()}] ⚠️ Capabilities refresh report failed: ${getErrorMessage(error)}`);
60986
+ }
60722
60987
  }
60723
60988
  }
60724
60989
  async function startCommandLoop(ctx) {
@@ -60790,6 +61055,7 @@ Listening for commands...`);
60790
61055
  commandIds: new Map,
60791
61056
  pingIds: new Map,
60792
61057
  gitRefreshIds: new Map,
61058
+ capabilitiesRefreshIds: new Map,
60793
61059
  localActionIds: new Map,
60794
61060
  commandRunIds: new Map,
60795
61061
  commandStopIds: new Map
@@ -60810,15 +61076,8 @@ Listening for commands...`);
60810
61076
  }
60811
61077
  }
60812
61078
  });
60813
- const modelRefreshTimer = setInterval(() => {
60814
- refreshModels(ctx).catch((err) => {
60815
- console.warn(`[${formatTimestamp()}] ⚠️ Model refresh error: ${getErrorMessage(err)}`);
60816
- });
60817
- }, MODEL_REFRESH_INTERVAL_MS);
60818
- modelRefreshTimer.unref();
60819
61079
  return await new Promise(() => {});
60820
61080
  }
60821
- var MODEL_REFRESH_INTERVAL_MS;
60822
61081
  var init_command_loop = __esm(() => {
60823
61082
  init_on_request_start_agent();
60824
61083
  init_on_request_stop_agent();
@@ -60837,7 +61096,6 @@ var init_command_loop = __esm(() => {
60837
61096
  init_local_actions();
60838
61097
  init_machine();
60839
61098
  init_convex_error();
60840
- MODEL_REFRESH_INTERVAL_MS = 10 * 1000;
60841
61099
  });
60842
61100
 
60843
61101
  // src/commands/machine/daemon-start/index.ts
@@ -61934,5 +62192,5 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
61934
62192
  });
61935
62193
  program2.parse();
61936
62194
 
61937
- //# debugId=C6BB51F249BBA12864756E2164756E21
62195
+ //# debugId=967EF2E82583BAE364756E2164756E21
61938
62196
  //# sourceMappingURL=index.js.map