@cabane/companion 0.6.210 → 0.6.212

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/cli.js CHANGED
@@ -1801,7 +1801,12 @@ var init_api = __esm({
1801
1801
  this.opts.outbox?.remove(activeRunOutboxKey(conversationId, agentId), ACTIVE_RUN_OUTBOX_SEQ);
1802
1802
  return res;
1803
1803
  }
1804
- return this.request("PATCH", path, body);
1804
+ return this.request(
1805
+ "PATCH",
1806
+ path,
1807
+ body,
1808
+ body.responsiveness ? { signal: AbortSignal.timeout(5e3) } : {}
1809
+ );
1805
1810
  }
1806
1811
  // Send-or-enqueue for an active-run flag write, last-writer-wins per
1807
1812
  // (conversation, agent) pair. Like `durableCommit`, except:
@@ -8020,6 +8025,59 @@ var init_protocol = __esm({
8020
8025
  }
8021
8026
  });
8022
8027
 
8028
+ // src/turn-responsiveness.ts
8029
+ import { randomUUID as randomUUID2 } from "crypto";
8030
+ function createTurnResponsiveness(send, source, now = () => performance.now()) {
8031
+ let invocationId = "";
8032
+ let started = 0;
8033
+ let sessionMode = "fresh";
8034
+ let firstEvent = false;
8035
+ let firstText = false;
8036
+ let invocations = 0;
8037
+ const report = (phase, eventKind) => {
8038
+ if (invocations > 8) return;
8039
+ const value = {
8040
+ executionSourceId: source,
8041
+ invocationId,
8042
+ phase,
8043
+ sessionMode,
8044
+ elapsedMs: Math.min(864e5, Math.max(0, Math.round(now() - started))),
8045
+ ...eventKind ? { eventKind } : {}
8046
+ };
8047
+ try {
8048
+ void send(value).catch(() => {
8049
+ });
8050
+ } catch {
8051
+ }
8052
+ };
8053
+ return {
8054
+ start(mode) {
8055
+ invocationId = randomUUID2();
8056
+ started = now();
8057
+ sessionMode = mode;
8058
+ firstEvent = false;
8059
+ firstText = false;
8060
+ invocations += 1;
8061
+ report("invocation");
8062
+ },
8063
+ event(event) {
8064
+ if (!firstEvent) {
8065
+ firstEvent = true;
8066
+ report("first_event", event.type);
8067
+ }
8068
+ if (!firstText && event.type === "text" && event.body.trim()) {
8069
+ firstText = true;
8070
+ report("first_text", "text");
8071
+ }
8072
+ }
8073
+ };
8074
+ }
8075
+ var init_turn_responsiveness = __esm({
8076
+ "src/turn-responsiveness.ts"() {
8077
+ "use strict";
8078
+ }
8079
+ });
8080
+
8023
8081
  // src/grants.ts
8024
8082
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
8025
8083
  import { join as join14 } from "path";
@@ -8138,7 +8196,7 @@ var init_work_dir = __esm({
8138
8196
  });
8139
8197
 
8140
8198
  // src/turn-control-tools.ts
8141
- import { randomUUID as randomUUID2 } from "crypto";
8199
+ import { randomUUID as randomUUID3 } from "crypto";
8142
8200
  import { z as z16 } from "zod";
8143
8201
  function createSkipState() {
8144
8202
  return { skipped: false, reason: null };
@@ -8237,7 +8295,7 @@ function createTurnControlMcpServer(acts, skipState) {
8237
8295
  }
8238
8296
  return performAct(acts, {
8239
8297
  kind: "ask",
8240
- callId: randomUUID2(),
8298
+ callId: randomUUID3(),
8241
8299
  // The swap rides the ask, so the server commits both halves
8242
8300
  // in one transaction — the person never sees neither or both.
8243
8301
  ...args.replaces ? { replaces: args.replaces } : {},
@@ -8279,7 +8337,7 @@ function createTurnControlMcpServer(acts, skipState) {
8279
8337
  }
8280
8338
  return performAct(acts, {
8281
8339
  kind: "wake",
8282
- callId: randomUUID2(),
8340
+ callId: randomUUID3(),
8283
8341
  wake: {
8284
8342
  ...hasAfter ? { afterSeconds: args.afterSeconds } : {},
8285
8343
  ...hasAt ? { at: args.at } : {},
@@ -8303,7 +8361,7 @@ function createTurnControlMcpServer(acts, skipState) {
8303
8361
  },
8304
8362
  async (args) => performAct(acts, {
8305
8363
  kind: "withdraw_ask",
8306
- callId: randomUUID2(),
8364
+ callId: randomUUID3(),
8307
8365
  withdraw: { askId: args.askId, reason: args.reason }
8308
8366
  }),
8309
8367
  { annotations: ACT_TOOL, alwaysLoad: true }
@@ -8312,7 +8370,7 @@ function createTurnControlMcpServer(acts, skipState) {
8312
8370
  ANSWER_TOOL2,
8313
8371
  TURN_CONTROL_TOOL_DESCRIPTIONS.answer,
8314
8372
  {},
8315
- async () => performAct(acts, { kind: "answer", callId: randomUUID2() }),
8373
+ async () => performAct(acts, { kind: "answer", callId: randomUUID3() }),
8316
8374
  { annotations: ACT_TOOL, alwaysLoad: true }
8317
8375
  ),
8318
8376
  // The inverse. No arguments — it can only ever stand down the
@@ -8321,7 +8379,7 @@ function createTurnControlMcpServer(acts, skipState) {
8321
8379
  CANCEL_WAKE_TOOL,
8322
8380
  TURN_CONTROL_TOOL_DESCRIPTIONS.cancel_wake,
8323
8381
  {},
8324
- async () => performAct(acts, { kind: "cancel_wake", callId: randomUUID2() }),
8382
+ async () => performAct(acts, { kind: "cancel_wake", callId: randomUUID3() }),
8325
8383
  { annotations: CANCEL_TOOL, alwaysLoad: true }
8326
8384
  )
8327
8385
  ]
@@ -9143,7 +9201,7 @@ var init_turn_runtime_integrity = __esm({
9143
9201
  });
9144
9202
 
9145
9203
  // src/turn-execution.ts
9146
- import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
9204
+ import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
9147
9205
  import { existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
9148
9206
  function resolveIdleTimeoutMs(override) {
9149
9207
  return override ?? DEFAULT_AGENT_IDLE_TIMEOUT_MS;
@@ -9185,6 +9243,7 @@ var PREPARING_TOOL_NAME, HARNESS_UNAVAILABLE_NOTICE, NO_TURN_TOKEN_REASON, UNEXP
9185
9243
  var init_turn_execution = __esm({
9186
9244
  "src/turn-execution.ts"() {
9187
9245
  "use strict";
9246
+ init_turn_responsiveness();
9188
9247
  init_term();
9189
9248
  init_cabane_command();
9190
9249
  init_boot_id();
@@ -9241,7 +9300,7 @@ var init_turn_execution = __esm({
9241
9300
  agentId: payload.agentId,
9242
9301
  messageId: payload.messageId
9243
9302
  });
9244
- this.turnId = handleOpts.turnId ?? randomUUID3();
9303
+ this.turnId = handleOpts.turnId ?? randomUUID4();
9245
9304
  }
9246
9305
  opts;
9247
9306
  supervisor;
@@ -9651,7 +9710,7 @@ var init_turn_execution = __esm({
9651
9710
  let preparing = null;
9652
9711
  const executionSourceId = this.executionSourceId;
9653
9712
  const reportPreparing = (phase) => {
9654
- preparing ??= { id: `prepare:${randomUUID3()}`, seq: this.nextSeq() };
9713
+ preparing ??= { id: `prepare:${randomUUID4()}`, seq: this.nextSeq() };
9655
9714
  const { id, seq } = preparing;
9656
9715
  const body = {
9657
9716
  turnId,
@@ -9722,6 +9781,10 @@ var init_turn_execution = __esm({
9722
9781
  try {
9723
9782
  await this.opts.api.setActiveRun(workspaceId, payload.conversationId, payload.agentId, {
9724
9783
  activeRunStartedAt: (/* @__PURE__ */ new Date()).toISOString(),
9784
+ ...this.turnContext.responsivenessVersion === 1 ? {
9785
+ responsivenessVersion: 1,
9786
+ responsivenessSessionMode: this.request.session ? "resumed" : "fresh"
9787
+ } : {},
9725
9788
  // A runner leases under its launcher's boot id, the one the device
9726
9789
  // heartbeats, or the lost-run sweep reads it as a restart.
9727
9790
  bootId: this.opts.inRunner?.bootId ?? bootId,
@@ -10066,10 +10129,23 @@ var init_turn_execution = __esm({
10066
10129
  clearTimeout(totalTimer);
10067
10130
  clearInterval(leaseTimer);
10068
10131
  };
10132
+ const responsiveness = this.turnContext.responsivenessVersion === 1 && this.executionSourceId ? createTurnResponsiveness(
10133
+ (report) => this.opts.api.setActiveRun(workspaceId, payload.conversationId, payload.agentId, {
10134
+ turnId,
10135
+ responsiveness: report
10136
+ }),
10137
+ this.executionSourceId
10138
+ ) : null;
10139
+ responsiveness?.start(this.request.session ? "resumed" : "fresh");
10069
10140
  try {
10070
10141
  for await (const event of this.adapter.runTurn(this.request, abortController.signal, {
10071
- fresh: (reason) => this.freshRequest(reason)
10142
+ fresh: async (reason) => {
10143
+ const fresh = await this.freshRequest(reason);
10144
+ if (fresh) responsiveness?.start("fallback");
10145
+ return fresh;
10146
+ }
10072
10147
  })) {
10148
+ responsiveness?.event(event);
10073
10149
  transcript2?.write(event);
10074
10150
  o.eventCounts[event.type] += 1;
10075
10151
  if (isContentBearingEvent(event)) o.contentBearingEvents += 1;
@@ -10223,7 +10299,7 @@ var init_turn_execution = __esm({
10223
10299
  if (!o.okResult && o.resultReason && o.resultReason !== "cancelled" && !userCancelled && !o.leaseLost && !o.runtimeIncomplete && !this.skipState.skipped) {
10224
10300
  body.errorReason = o.resultReason.slice(0, ERROR_REASON_MAX);
10225
10301
  }
10226
- body.outcome = o.okResult ? committer.spoke ? "settled" : "passed" : body.errorReason || o.runtimeIncomplete ? "failed" : "interrupted";
10302
+ body.outcome = o.okResult ? committer.spoke && !this.skipState.skipped ? "settled" : "passed" : body.errorReason || o.runtimeIncomplete ? "failed" : "interrupted";
10227
10303
  if (body.outcome === "settled") body.lastMessageSeq = committer.lastMessageSeq;
10228
10304
  if (o.sessionDegraded) {
10229
10305
  body.degraded = true;
@@ -11901,7 +11977,7 @@ function isDevicePaired() {
11901
11977
  // src/runtime.ts
11902
11978
  init_config();
11903
11979
  init_cabane_command();
11904
- import { randomUUID as randomUUID6 } from "crypto";
11980
+ import { randomUUID as randomUUID7 } from "crypto";
11905
11981
  import { fileURLToPath as fileURLToPath6 } from "url";
11906
11982
 
11907
11983
  // src/dashboard/server.ts
@@ -12475,7 +12551,7 @@ function turnFailureCopy(reason, runtime) {
12475
12551
  init_api_error_shape();
12476
12552
  init_boot_id();
12477
12553
  init_api();
12478
- import { randomUUID as randomUUID5 } from "crypto";
12554
+ import { randomUUID as randomUUID6 } from "crypto";
12479
12555
 
12480
12556
  // src/device-api.ts
12481
12557
  init_errors();
@@ -12858,7 +12934,7 @@ var ConnectorHealthStore = class {
12858
12934
  };
12859
12935
 
12860
12936
  // src/dispatcher.ts
12861
- import { randomUUID as randomUUID4 } from "crypto";
12937
+ import { randomUUID as randomUUID5 } from "crypto";
12862
12938
 
12863
12939
  // src/runner/launch.ts
12864
12940
  init_src();
@@ -13348,7 +13424,7 @@ var Dispatcher = class {
13348
13424
  notifyEnd: (info) => this.notifyEnd(info)
13349
13425
  },
13350
13426
  payload,
13351
- { turnId: opts.turnId ?? randomUUID4(), resumed: opts.resumed === true }
13427
+ { turnId: opts.turnId ?? randomUUID5(), resumed: opts.resumed === true }
13352
13428
  );
13353
13429
  }
13354
13430
  // Cancel the named turn. An older server omits turnId and cancels the pair.
@@ -15489,7 +15565,7 @@ var CompanionSupervisor = class {
15489
15565
  workspaceId,
15490
15566
  agent,
15491
15567
  payload,
15492
- turnIdForEvent(workspaceId, dispatchId) ?? randomUUID5(),
15568
+ turnIdForEvent(workspaceId, dispatchId) ?? randomUUID6(),
15493
15569
  attempt,
15494
15570
  dispatchId
15495
15571
  );
@@ -15502,7 +15578,7 @@ var CompanionSupervisor = class {
15502
15578
  );
15503
15579
  }
15504
15580
  const resumedTurnId = recoveredTurnId ?? (dispatchId ? turnIdForEvent(workspaceId, dispatchId) : null);
15505
- const turnId = resumedTurnId ?? randomUUID5();
15581
+ const turnId = resumedTurnId ?? randomUUID6();
15506
15582
  if (dispatchId) {
15507
15583
  const liveTurnIds = rememberTurnId(workspaceId, dispatchId, turnId);
15508
15584
  if (liveTurnIds > TURN_ID_OVERFLOW_WARN) {
@@ -16020,7 +16096,7 @@ async function createCompanionRuntime(opts = {}) {
16020
16096
  throw err;
16021
16097
  }
16022
16098
  configureLogger(log2, cfg);
16023
- const instanceId = randomUUID6();
16099
+ const instanceId = randomUUID7();
16024
16100
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
16025
16101
  const pre = readLiveRuntimeState();
16026
16102
  if (pre && await verifyRuntime(pre) === "stale") clearRuntimeState();
package/dist/runtime.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/runtime.ts
2
- import { randomUUID as randomUUID6 } from "crypto";
2
+ import { randomUUID as randomUUID7 } from "crypto";
3
3
  import { fileURLToPath as fileURLToPath6 } from "url";
4
4
 
5
5
  // src/cabane-command.ts
@@ -1864,7 +1864,7 @@ import { randomUUID } from "crypto";
1864
1864
  var bootId = randomUUID();
1865
1865
 
1866
1866
  // src/supervisor.ts
1867
- import { randomUUID as randomUUID5 } from "crypto";
1867
+ import { randomUUID as randomUUID6 } from "crypto";
1868
1868
 
1869
1869
  // src/api.ts
1870
1870
  import { promisify } from "util";
@@ -2226,7 +2226,12 @@ var CabaneApi = class {
2226
2226
  this.opts.outbox?.remove(activeRunOutboxKey(conversationId, agentId), ACTIVE_RUN_OUTBOX_SEQ);
2227
2227
  return res;
2228
2228
  }
2229
- return this.request("PATCH", path, body);
2229
+ return this.request(
2230
+ "PATCH",
2231
+ path,
2232
+ body,
2233
+ body.responsiveness ? { signal: AbortSignal.timeout(5e3) } : {}
2234
+ );
2230
2235
  }
2231
2236
  // Send-or-enqueue for an active-run flag write, last-writer-wins per
2232
2237
  // (conversation, agent) pair. Like `durableCommit`, except:
@@ -7552,7 +7557,7 @@ var ConnectorHealthStore = class {
7552
7557
  };
7553
7558
 
7554
7559
  // src/dispatcher.ts
7555
- import { randomUUID as randomUUID4 } from "crypto";
7560
+ import { randomUUID as randomUUID5 } from "crypto";
7556
7561
 
7557
7562
  // src/runner/launch.ts
7558
7563
  import { createInterface } from "readline";
@@ -8216,8 +8221,11 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8216
8221
  return { ok: false, durationMs, reason };
8217
8222
  }
8218
8223
 
8224
+ // src/turn-responsiveness.ts
8225
+ import { randomUUID as randomUUID2 } from "crypto";
8226
+
8219
8227
  // src/turn-execution.ts
8220
- import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
8228
+ import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
8221
8229
  import { existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
8222
8230
 
8223
8231
  // src/grants.ts
@@ -8284,7 +8292,7 @@ import { statSync as statSync3 } from "fs";
8284
8292
  import { join as join14 } from "path";
8285
8293
 
8286
8294
  // src/turn-control-tools.ts
8287
- import { randomUUID as randomUUID2 } from "crypto";
8295
+ import { randomUUID as randomUUID3 } from "crypto";
8288
8296
  import { z as z16 } from "zod";
8289
8297
  var COMPANION_LOCAL_MCP_SERVER = TURN_CONTROL_MCP_SERVER;
8290
8298
  var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
@@ -8583,7 +8591,7 @@ var Dispatcher = class {
8583
8591
  notifyEnd: (info) => this.notifyEnd(info)
8584
8592
  },
8585
8593
  payload,
8586
- { turnId: opts.turnId ?? randomUUID4(), resumed: opts.resumed === true }
8594
+ { turnId: opts.turnId ?? randomUUID5(), resumed: opts.resumed === true }
8587
8595
  );
8588
8596
  }
8589
8597
  // Cancel the named turn. An older server omits turnId and cancels the pair.
@@ -11102,7 +11110,7 @@ var CompanionSupervisor = class {
11102
11110
  workspaceId,
11103
11111
  agent,
11104
11112
  payload,
11105
- turnIdForEvent(workspaceId, dispatchId) ?? randomUUID5(),
11113
+ turnIdForEvent(workspaceId, dispatchId) ?? randomUUID6(),
11106
11114
  attempt,
11107
11115
  dispatchId
11108
11116
  );
@@ -11115,7 +11123,7 @@ var CompanionSupervisor = class {
11115
11123
  );
11116
11124
  }
11117
11125
  const resumedTurnId = recoveredTurnId ?? (dispatchId ? turnIdForEvent(workspaceId, dispatchId) : null);
11118
- const turnId = resumedTurnId ?? randomUUID5();
11126
+ const turnId = resumedTurnId ?? randomUUID6();
11119
11127
  if (dispatchId) {
11120
11128
  const liveTurnIds = rememberTurnId(workspaceId, dispatchId, turnId);
11121
11129
  if (liveTurnIds > TURN_ID_OVERFLOW_WARN) {
@@ -11631,7 +11639,7 @@ async function createCompanionRuntime(opts = {}) {
11631
11639
  throw err;
11632
11640
  }
11633
11641
  configureLogger(log, cfg);
11634
- const instanceId = randomUUID6();
11642
+ const instanceId = randomUUID7();
11635
11643
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
11636
11644
  const pre = readLiveRuntimeState();
11637
11645
  if (pre && await verifyRuntime(pre) === "stale") clearRuntimeState();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.210",
3
+ "version": "0.6.212",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect an agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",