@cabane/companion 0.6.14 → 0.6.15

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
@@ -7568,6 +7568,32 @@ ${reason}`,
7568
7568
  `runtime_unavailable:${err.runtime}`
7569
7569
  );
7570
7570
  }
7571
+ let turnReceiptPath = null;
7572
+ const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
7573
+ const closeTurnReceipt = (ok, reason) => {
7574
+ if (!turnReceiptPath) return;
7575
+ const target = turnReceiptPath;
7576
+ turnReceiptPath = null;
7577
+ try {
7578
+ appendFileSync2(
7579
+ target,
7580
+ `${JSON.stringify({
7581
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7582
+ event: "settled",
7583
+ turnId,
7584
+ ok,
7585
+ reason
7586
+ })}
7587
+ `,
7588
+ { mode: 384 }
7589
+ );
7590
+ } catch (error) {
7591
+ turnLog.warn(
7592
+ { err: error instanceof Error ? error.message : String(error) },
7593
+ "dispatcher: turn-settled diagnostic write failed"
7594
+ );
7595
+ }
7596
+ };
7571
7597
  if (prepareHook && hookEnv?.CABANE_TASK_ID) {
7572
7598
  const proof = await proveWorkspaceTools(request, adapter.name, {
7573
7599
  ...this.opts.workspaceProofFetch ? { fetchImpl: this.opts.workspaceProofFetch } : {},
@@ -7582,8 +7608,9 @@ ${reason}`,
7582
7608
  try {
7583
7609
  const diagnosticDir = join13(effectiveCwd, ".git", "cabane");
7584
7610
  mkdirSync10(diagnosticDir, { recursive: true });
7611
+ const receiptPath = join13(diagnosticDir, "readiness.jsonl");
7585
7612
  appendFileSync2(
7586
- join13(diagnosticDir, "readiness.jsonl"),
7613
+ receiptPath,
7587
7614
  `${JSON.stringify({
7588
7615
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7589
7616
  taskId: hookEnv.CABANE_TASK_ID,
@@ -7591,11 +7618,18 @@ ${reason}`,
7591
7618
  checkout: effectiveCwd,
7592
7619
  classification: proof.ok ? "ready" : "workspace_tools_missing",
7593
7620
  failedCapability: proof.failedCapability,
7594
- workspaceTools: proof
7621
+ workspaceTools: proof,
7622
+ // CT1022: the two fields the environment reaper reads — which turn
7623
+ // this is (so its settle can be matched among interleaved agents)
7624
+ // and how long it may legitimately run (so an unclosed receipt
7625
+ // expires on this turn's real deadline, not the reaper's guess).
7626
+ turnId,
7627
+ totalTimeoutMs
7595
7628
  })}
7596
7629
  `,
7597
7630
  { mode: 384 }
7598
7631
  );
7632
+ turnReceiptPath = receiptPath;
7599
7633
  } catch (error) {
7600
7634
  turnLog.warn(
7601
7635
  { err: error instanceof Error ? error.message : String(error) },
@@ -7622,6 +7656,7 @@ ${reason}`,
7622
7656
  }
7623
7657
  if (!proof.ok) {
7624
7658
  const reason = `workspace_tools_missing: ${proof.failedCapability}; checkout=${effectiveCwd ?? "unknown"}; runtime=${adapter.name}; recovery=restart the connector after restoring the Cabane workspace tool mount`;
7659
+ closeTurnReceipt(false, reason);
7625
7660
  try {
7626
7661
  await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
7627
7662
  body: `**Couldn't prepare your environment.** ${reason}`,
@@ -7733,7 +7768,6 @@ ${reason}`,
7733
7768
  }
7734
7769
  };
7735
7770
  const idleTimeoutMs = this.opts.idleTimeoutMs ?? DEFAULT_AGENT_IDLE_TIMEOUT_MS;
7736
- const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
7737
7771
  const fireTimeout = (reason) => {
7738
7772
  if (abortController.signal.aborted) return;
7739
7773
  timeoutReason = reason;
@@ -7835,6 +7869,7 @@ ${reason}`,
7835
7869
  } finally {
7836
7870
  if (idleTimer) clearTimeout(idleTimer);
7837
7871
  clearTimeout(totalTimer);
7872
+ closeTurnReceipt(okResult, resultReason ?? null);
7838
7873
  const userCancelled = abortController.signal.aborted && timeoutReason === null;
7839
7874
  if (timeoutReason !== null) {
7840
7875
  resultReason = timeoutReason;
package/dist/runtime.js CHANGED
@@ -7221,6 +7221,32 @@ ${reason}`,
7221
7221
  `runtime_unavailable:${err.runtime}`
7222
7222
  );
7223
7223
  }
7224
+ let turnReceiptPath = null;
7225
+ const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
7226
+ const closeTurnReceipt = (ok, reason) => {
7227
+ if (!turnReceiptPath) return;
7228
+ const target = turnReceiptPath;
7229
+ turnReceiptPath = null;
7230
+ try {
7231
+ appendFileSync2(
7232
+ target,
7233
+ `${JSON.stringify({
7234
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7235
+ event: "settled",
7236
+ turnId,
7237
+ ok,
7238
+ reason
7239
+ })}
7240
+ `,
7241
+ { mode: 384 }
7242
+ );
7243
+ } catch (error) {
7244
+ turnLog.warn(
7245
+ { err: error instanceof Error ? error.message : String(error) },
7246
+ "dispatcher: turn-settled diagnostic write failed"
7247
+ );
7248
+ }
7249
+ };
7224
7250
  if (prepareHook && hookEnv?.CABANE_TASK_ID) {
7225
7251
  const proof = await proveWorkspaceTools(request, adapter.name, {
7226
7252
  ...this.opts.workspaceProofFetch ? { fetchImpl: this.opts.workspaceProofFetch } : {},
@@ -7235,8 +7261,9 @@ ${reason}`,
7235
7261
  try {
7236
7262
  const diagnosticDir = join13(effectiveCwd, ".git", "cabane");
7237
7263
  mkdirSync9(diagnosticDir, { recursive: true });
7264
+ const receiptPath = join13(diagnosticDir, "readiness.jsonl");
7238
7265
  appendFileSync2(
7239
- join13(diagnosticDir, "readiness.jsonl"),
7266
+ receiptPath,
7240
7267
  `${JSON.stringify({
7241
7268
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7242
7269
  taskId: hookEnv.CABANE_TASK_ID,
@@ -7244,11 +7271,18 @@ ${reason}`,
7244
7271
  checkout: effectiveCwd,
7245
7272
  classification: proof.ok ? "ready" : "workspace_tools_missing",
7246
7273
  failedCapability: proof.failedCapability,
7247
- workspaceTools: proof
7274
+ workspaceTools: proof,
7275
+ // CT1022: the two fields the environment reaper reads — which turn
7276
+ // this is (so its settle can be matched among interleaved agents)
7277
+ // and how long it may legitimately run (so an unclosed receipt
7278
+ // expires on this turn's real deadline, not the reaper's guess).
7279
+ turnId,
7280
+ totalTimeoutMs
7248
7281
  })}
7249
7282
  `,
7250
7283
  { mode: 384 }
7251
7284
  );
7285
+ turnReceiptPath = receiptPath;
7252
7286
  } catch (error) {
7253
7287
  turnLog.warn(
7254
7288
  { err: error instanceof Error ? error.message : String(error) },
@@ -7275,6 +7309,7 @@ ${reason}`,
7275
7309
  }
7276
7310
  if (!proof.ok) {
7277
7311
  const reason = `workspace_tools_missing: ${proof.failedCapability}; checkout=${effectiveCwd ?? "unknown"}; runtime=${adapter.name}; recovery=restart the connector after restoring the Cabane workspace tool mount`;
7312
+ closeTurnReceipt(false, reason);
7278
7313
  try {
7279
7314
  await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
7280
7315
  body: `**Couldn't prepare your environment.** ${reason}`,
@@ -7386,7 +7421,6 @@ ${reason}`,
7386
7421
  }
7387
7422
  };
7388
7423
  const idleTimeoutMs = this.opts.idleTimeoutMs ?? DEFAULT_AGENT_IDLE_TIMEOUT_MS;
7389
- const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
7390
7424
  const fireTimeout = (reason) => {
7391
7425
  if (abortController.signal.aborted) return;
7392
7426
  timeoutReason = reason;
@@ -7488,6 +7522,7 @@ ${reason}`,
7488
7522
  } finally {
7489
7523
  if (idleTimer) clearTimeout(idleTimer);
7490
7524
  clearTimeout(totalTimer);
7525
+ closeTurnReceipt(okResult, resultReason ?? null);
7491
7526
  const userCancelled = abortController.signal.aborted && timeoutReason === null;
7492
7527
  if (timeoutReason !== null) {
7493
7528
  resultReason = timeoutReason;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.14",
3
+ "version": "0.6.15",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect a coding 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",