@cabane/companion 0.6.130 → 0.6.132

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
@@ -1383,6 +1383,21 @@ var init_api = __esm({
1383
1383
  (e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
1384
1384
  );
1385
1385
  }
1386
+ // CT1677: ADOPT the issuance a turn context carried as the execution source of
1387
+ // the work about to run on it. Made once per context, before preparation emits
1388
+ // or queues anything, so every row that work commits can name a source the
1389
+ // server has durably recorded and will never reap. Retried like any write: the
1390
+ // server's adoption is idempotent. A 409 `execution_source_unavailable` is a
1391
+ // refusal — nothing to adopt, the reaper got there first — and the caller
1392
+ // fetches a fresh context rather than working on this one.
1393
+ adoptExecutionSource(issuanceId, body, signal) {
1394
+ return this.request(
1395
+ "POST",
1396
+ `/api/agent/execution-sources/${encodeURIComponent(issuanceId)}/adoption`,
1397
+ body,
1398
+ signal ? { retry: true, signal } : { retry: true }
1399
+ );
1400
+ }
1386
1401
  // CT1680: the launcher mints the runner credential for one dispatch, on its
1387
1402
  // PAT and from its device, and hands it to the runner in place of both. A
1388
1403
  // retry is safe: the server returns the same runner id and retires the
@@ -8535,6 +8550,7 @@ var init_turn_committer = __esm({
8535
8550
  TurnCommitter = class {
8536
8551
  constructor(deps) {
8537
8552
  this.deps = deps;
8553
+ const source = deps.executionSourceId !== void 0 ? { executionSourceId: deps.executionSourceId } : {};
8538
8554
  this.onError = (err, hook) => {
8539
8555
  deps.log.warn(
8540
8556
  { ...apiErrorLogFields(err), hook },
@@ -8552,7 +8568,8 @@ var init_turn_committer = __esm({
8552
8568
  kind,
8553
8569
  turnId: deps.turnId,
8554
8570
  seq,
8555
- parentMessageId: deps.parentMessageId
8571
+ parentMessageId: deps.parentMessageId,
8572
+ ...source
8556
8573
  },
8557
8574
  deps.signal
8558
8575
  );
@@ -8580,7 +8597,8 @@ var init_turn_committer = __esm({
8580
8597
  result,
8581
8598
  mcpServer,
8582
8599
  phase,
8583
- seq
8600
+ seq,
8601
+ ...source
8584
8602
  },
8585
8603
  deps.signal
8586
8604
  );
@@ -8590,7 +8608,7 @@ var init_turn_committer = __esm({
8590
8608
  deps.workspaceId,
8591
8609
  deps.conversationId,
8592
8610
  deps.agentId,
8593
- { turnId: deps.turnId, text, seq },
8611
+ { turnId: deps.turnId, text, seq, ...source },
8594
8612
  deps.signal
8595
8613
  );
8596
8614
  },
@@ -8599,7 +8617,7 @@ var init_turn_committer = __esm({
8599
8617
  deps.workspaceId,
8600
8618
  deps.conversationId,
8601
8619
  deps.agentId,
8602
- { turnId: deps.turnId, text, seq },
8620
+ { turnId: deps.turnId, text, seq, ...source },
8603
8621
  deps.signal
8604
8622
  );
8605
8623
  }
@@ -8925,6 +8943,14 @@ var init_turn_execution = __esm({
8925
8943
  admitted = false;
8926
8944
  // Rooms: one recompose per turn when the lease refuses a stale context.
8927
8945
  recomposedForLease = false;
8946
+ // CT1677: the execution source the CURRENT context was adopted as. Reassigned
8947
+ // when a `dispatch_context_stale` recompose adopts a replacement, so a piece of
8948
+ // work — preparation, the committer, a notice — captures it when that work
8949
+ // begins and never reads it again: earlier work keeps its source. Undefined
8950
+ // only before the first adoption; no work runs until it is set.
8951
+ executionSourceId;
8952
+ // CT1677: one fresh fetch per turn when adoption is refused.
8953
+ refetchedForAdoption = false;
8928
8954
  concluded(reason, errorReason) {
8929
8955
  return new TurnConcluded(reason, errorReason);
8930
8956
  }
@@ -9075,6 +9101,15 @@ var init_turn_execution = __esm({
9075
9101
  turnLog.warn({ turnId }, "dispatcher: turn context carried no turn token");
9076
9102
  throw this.concluded(NO_TURN_TOKEN_REASON, NO_TURN_TOKEN_REASON);
9077
9103
  }
9104
+ if (await this.adoptSource(turnContext) === "refused") {
9105
+ if (this.refetchedForAdoption) {
9106
+ const reason = "execution_source_unavailable: adoption refused twice";
9107
+ throw this.concluded(reason, reason);
9108
+ }
9109
+ this.refetchedForAdoption = true;
9110
+ turnLog.info({ turnId }, "dispatcher: context could not be adopted; fetching a fresh one");
9111
+ return this.fetchContext();
9112
+ }
9078
9113
  this.turnContext = turnContext;
9079
9114
  if (this.resumed) {
9080
9115
  const sources = { serverFloor: turnContext.turnSeqFloor, outboxFloor };
@@ -9090,6 +9125,34 @@ var init_turn_execution = __esm({
9090
9125
  throw this.concluded(reason, reason);
9091
9126
  }
9092
9127
  }
9128
+ // CT1677: register this context's issuance as the execution source of the
9129
+ // work about to run on it. A context with no issuance has nothing to adopt,
9130
+ // and that ends the turn exactly as a failed adoption does: this companion
9131
+ // never commits a row whose provenance it cannot name. Only a refusal —
9132
+ // nothing to adopt any more — earns a fresh fetch.
9133
+ async adoptSource(context) {
9134
+ const { payload, turnId, turnLog } = this;
9135
+ if (!context.issuanceId) {
9136
+ turnLog.warn({ turnId }, "dispatcher: the turn context carried no issuance to adopt");
9137
+ const reason = "execution_source_unavailable: the context carried no issuance";
9138
+ throw this.concluded(reason, reason);
9139
+ }
9140
+ try {
9141
+ const adopted = await this.opts.api.adoptExecutionSource(
9142
+ context.issuanceId,
9143
+ { conversationId: payload.conversationId, turnId },
9144
+ this.abortController.signal
9145
+ );
9146
+ this.executionSourceId = adopted.executionSourceId;
9147
+ return "adopted";
9148
+ } catch (err) {
9149
+ if (this.abortController.signal.aborted) throw this.concluded("cancelled");
9150
+ if (apiErrorCode(err) === "execution_source_unavailable") return "refused";
9151
+ turnLog.warn(apiErrorLogFields(err), "dispatcher: failed to adopt the turn context");
9152
+ const reason = `execution_source_unavailable: ${describeError(err)}`;
9153
+ throw this.concluded(reason, reason);
9154
+ }
9155
+ }
9093
9156
  gateTrigger() {
9094
9157
  const { payload, turnLog } = this;
9095
9158
  const message = this.turnContext.message;
@@ -9186,6 +9249,7 @@ var init_turn_execution = __esm({
9186
9249
  const delayMs = this.opts.preparingRowDelayMs ?? DEFAULT_PREPARING_ROW_DELAY_MS;
9187
9250
  let preparingStarted = false;
9188
9251
  let preparing = null;
9252
+ const executionSourceId = this.executionSourceId;
9189
9253
  const reportPreparing = (phase) => {
9190
9254
  preparing ??= { id: `prepare:${randomUUID3()}`, seq: this.nextSeq() };
9191
9255
  const { id, seq } = preparing;
@@ -9195,7 +9259,8 @@ var init_turn_execution = __esm({
9195
9259
  toolName: PREPARING_TOOL_NAME,
9196
9260
  summary: "",
9197
9261
  phase,
9198
- seq
9262
+ seq,
9263
+ ...executionSourceId !== void 0 ? { executionSourceId } : {}
9199
9264
  };
9200
9265
  void this.opts.api.reportActivity(workspaceId, payload.conversationId, payload.agentId, body).catch((err) => {
9201
9266
  turnLog.debug(
@@ -9363,6 +9428,7 @@ var init_turn_execution = __esm({
9363
9428
  });
9364
9429
  }
9365
9430
  integrityContext() {
9431
+ const source = this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {};
9366
9432
  return {
9367
9433
  runtime: this.turnContext.runtime,
9368
9434
  workspaceId: this.workspaceId,
@@ -9370,7 +9436,7 @@ var init_turn_execution = __esm({
9370
9436
  turnId: this.turnId,
9371
9437
  parentMessageId: this.payload.messageId,
9372
9438
  log: this.turnLog,
9373
- postTurnMessage: (workspaceId, conversationId, body) => this.opts.api.postTurnMessage(workspaceId, conversationId, body),
9439
+ postTurnMessage: (workspaceId, conversationId, body) => this.opts.api.postTurnMessage(workspaceId, conversationId, { ...body, ...source }),
9374
9440
  ...this.opts.resolveBundled ? { resolveBundled: this.opts.resolveBundled } : {}
9375
9441
  };
9376
9442
  }
@@ -9459,7 +9525,9 @@ var init_turn_execution = __esm({
9459
9525
  body: `${RUNTIME_UNAVAILABLE_PREFIX} ${err.message}`,
9460
9526
  kind: "runtime_notice",
9461
9527
  turnId,
9462
- parentMessageId: payload.messageId
9528
+ parentMessageId: payload.messageId,
9529
+ // CT1677: the notice is written by this attempt's source, like its speech.
9530
+ ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {}
9463
9531
  });
9464
9532
  } catch (postErr) {
9465
9533
  turnLog.warn(
@@ -9553,6 +9621,8 @@ var init_turn_execution = __esm({
9553
9621
  log: turnLog,
9554
9622
  nextSeq: this.nextSeq,
9555
9623
  spoke: this.resumedSpoke,
9624
+ // CT1677: admitted by now, so this is the source the model's work runs on.
9625
+ ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {},
9556
9626
  // CT1292: a commit that didn't land may mean this turn's lease is gone.
9557
9627
  // `noteCommitFailed` is a field slot the lease watchdog fills when it
9558
9628
  // arms (it starts as a no-op) — the committer only fires it from inside
@@ -13964,7 +14034,9 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13964
14034
  function containerName(attempt) {
13965
14035
  return `cabane-runner-${attempt.conversationId}-${attempt.agentId}`;
13966
14036
  }
13967
- var networkName = containerName;
14037
+ function networkName(attempt) {
14038
+ return containerName(attempt);
14039
+ }
13968
14040
  function networkCreateError(stderr) {
13969
14041
  const said = stderr.trim();
13970
14042
  if (/address pool|fully subnetted/i.test(said))
package/dist/runtime.js CHANGED
@@ -2566,6 +2566,21 @@ var CabaneApi = class {
2566
2566
  (e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
2567
2567
  );
2568
2568
  }
2569
+ // CT1677: ADOPT the issuance a turn context carried as the execution source of
2570
+ // the work about to run on it. Made once per context, before preparation emits
2571
+ // or queues anything, so every row that work commits can name a source the
2572
+ // server has durably recorded and will never reap. Retried like any write: the
2573
+ // server's adoption is idempotent. A 409 `execution_source_unavailable` is a
2574
+ // refusal — nothing to adopt, the reaper got there first — and the caller
2575
+ // fetches a fresh context rather than working on this one.
2576
+ adoptExecutionSource(issuanceId, body, signal) {
2577
+ return this.request(
2578
+ "POST",
2579
+ `/api/agent/execution-sources/${encodeURIComponent(issuanceId)}/adoption`,
2580
+ body,
2581
+ signal ? { retry: true, signal } : { retry: true }
2582
+ );
2583
+ }
2569
2584
  // CT1680: the launcher mints the runner credential for one dispatch, on its
2570
2585
  // PAT and from its device, and hands it to the runner in place of both. A
2571
2586
  // retry is safe: the server returns the same runner id and retires the
@@ -9568,6 +9583,7 @@ function pruneOld(dir2, retain) {
9568
9583
  var TurnCommitter = class {
9569
9584
  constructor(deps) {
9570
9585
  this.deps = deps;
9586
+ const source = deps.executionSourceId !== void 0 ? { executionSourceId: deps.executionSourceId } : {};
9571
9587
  this.onError = (err, hook) => {
9572
9588
  deps.log.warn(
9573
9589
  { ...apiErrorLogFields(err), hook },
@@ -9585,7 +9601,8 @@ var TurnCommitter = class {
9585
9601
  kind,
9586
9602
  turnId: deps.turnId,
9587
9603
  seq,
9588
- parentMessageId: deps.parentMessageId
9604
+ parentMessageId: deps.parentMessageId,
9605
+ ...source
9589
9606
  },
9590
9607
  deps.signal
9591
9608
  );
@@ -9613,7 +9630,8 @@ var TurnCommitter = class {
9613
9630
  result,
9614
9631
  mcpServer,
9615
9632
  phase,
9616
- seq
9633
+ seq,
9634
+ ...source
9617
9635
  },
9618
9636
  deps.signal
9619
9637
  );
@@ -9623,7 +9641,7 @@ var TurnCommitter = class {
9623
9641
  deps.workspaceId,
9624
9642
  deps.conversationId,
9625
9643
  deps.agentId,
9626
- { turnId: deps.turnId, text, seq },
9644
+ { turnId: deps.turnId, text, seq, ...source },
9627
9645
  deps.signal
9628
9646
  );
9629
9647
  },
@@ -9632,7 +9650,7 @@ var TurnCommitter = class {
9632
9650
  deps.workspaceId,
9633
9651
  deps.conversationId,
9634
9652
  deps.agentId,
9635
- { turnId: deps.turnId, text, seq },
9653
+ { turnId: deps.turnId, text, seq, ...source },
9636
9654
  deps.signal
9637
9655
  );
9638
9656
  }
@@ -9911,6 +9929,14 @@ var TurnExecution = class {
9911
9929
  admitted = false;
9912
9930
  // Rooms: one recompose per turn when the lease refuses a stale context.
9913
9931
  recomposedForLease = false;
9932
+ // CT1677: the execution source the CURRENT context was adopted as. Reassigned
9933
+ // when a `dispatch_context_stale` recompose adopts a replacement, so a piece of
9934
+ // work — preparation, the committer, a notice — captures it when that work
9935
+ // begins and never reads it again: earlier work keeps its source. Undefined
9936
+ // only before the first adoption; no work runs until it is set.
9937
+ executionSourceId;
9938
+ // CT1677: one fresh fetch per turn when adoption is refused.
9939
+ refetchedForAdoption = false;
9914
9940
  concluded(reason, errorReason) {
9915
9941
  return new TurnConcluded(reason, errorReason);
9916
9942
  }
@@ -10061,6 +10087,15 @@ var TurnExecution = class {
10061
10087
  turnLog.warn({ turnId }, "dispatcher: turn context carried no turn token");
10062
10088
  throw this.concluded(NO_TURN_TOKEN_REASON, NO_TURN_TOKEN_REASON);
10063
10089
  }
10090
+ if (await this.adoptSource(turnContext) === "refused") {
10091
+ if (this.refetchedForAdoption) {
10092
+ const reason = "execution_source_unavailable: adoption refused twice";
10093
+ throw this.concluded(reason, reason);
10094
+ }
10095
+ this.refetchedForAdoption = true;
10096
+ turnLog.info({ turnId }, "dispatcher: context could not be adopted; fetching a fresh one");
10097
+ return this.fetchContext();
10098
+ }
10064
10099
  this.turnContext = turnContext;
10065
10100
  if (this.resumed) {
10066
10101
  const sources = { serverFloor: turnContext.turnSeqFloor, outboxFloor };
@@ -10076,6 +10111,34 @@ var TurnExecution = class {
10076
10111
  throw this.concluded(reason, reason);
10077
10112
  }
10078
10113
  }
10114
+ // CT1677: register this context's issuance as the execution source of the
10115
+ // work about to run on it. A context with no issuance has nothing to adopt,
10116
+ // and that ends the turn exactly as a failed adoption does: this companion
10117
+ // never commits a row whose provenance it cannot name. Only a refusal —
10118
+ // nothing to adopt any more — earns a fresh fetch.
10119
+ async adoptSource(context) {
10120
+ const { payload, turnId, turnLog } = this;
10121
+ if (!context.issuanceId) {
10122
+ turnLog.warn({ turnId }, "dispatcher: the turn context carried no issuance to adopt");
10123
+ const reason = "execution_source_unavailable: the context carried no issuance";
10124
+ throw this.concluded(reason, reason);
10125
+ }
10126
+ try {
10127
+ const adopted = await this.opts.api.adoptExecutionSource(
10128
+ context.issuanceId,
10129
+ { conversationId: payload.conversationId, turnId },
10130
+ this.abortController.signal
10131
+ );
10132
+ this.executionSourceId = adopted.executionSourceId;
10133
+ return "adopted";
10134
+ } catch (err) {
10135
+ if (this.abortController.signal.aborted) throw this.concluded("cancelled");
10136
+ if (apiErrorCode(err) === "execution_source_unavailable") return "refused";
10137
+ turnLog.warn(apiErrorLogFields(err), "dispatcher: failed to adopt the turn context");
10138
+ const reason = `execution_source_unavailable: ${describeError(err)}`;
10139
+ throw this.concluded(reason, reason);
10140
+ }
10141
+ }
10079
10142
  gateTrigger() {
10080
10143
  const { payload, turnLog } = this;
10081
10144
  const message = this.turnContext.message;
@@ -10172,6 +10235,7 @@ var TurnExecution = class {
10172
10235
  const delayMs = this.opts.preparingRowDelayMs ?? DEFAULT_PREPARING_ROW_DELAY_MS;
10173
10236
  let preparingStarted = false;
10174
10237
  let preparing = null;
10238
+ const executionSourceId = this.executionSourceId;
10175
10239
  const reportPreparing = (phase) => {
10176
10240
  preparing ??= { id: `prepare:${randomUUID3()}`, seq: this.nextSeq() };
10177
10241
  const { id, seq } = preparing;
@@ -10181,7 +10245,8 @@ var TurnExecution = class {
10181
10245
  toolName: PREPARING_TOOL_NAME,
10182
10246
  summary: "",
10183
10247
  phase,
10184
- seq
10248
+ seq,
10249
+ ...executionSourceId !== void 0 ? { executionSourceId } : {}
10185
10250
  };
10186
10251
  void this.opts.api.reportActivity(workspaceId, payload.conversationId, payload.agentId, body).catch((err) => {
10187
10252
  turnLog.debug(
@@ -10349,6 +10414,7 @@ var TurnExecution = class {
10349
10414
  });
10350
10415
  }
10351
10416
  integrityContext() {
10417
+ const source = this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {};
10352
10418
  return {
10353
10419
  runtime: this.turnContext.runtime,
10354
10420
  workspaceId: this.workspaceId,
@@ -10356,7 +10422,7 @@ var TurnExecution = class {
10356
10422
  turnId: this.turnId,
10357
10423
  parentMessageId: this.payload.messageId,
10358
10424
  log: this.turnLog,
10359
- postTurnMessage: (workspaceId, conversationId, body) => this.opts.api.postTurnMessage(workspaceId, conversationId, body),
10425
+ postTurnMessage: (workspaceId, conversationId, body) => this.opts.api.postTurnMessage(workspaceId, conversationId, { ...body, ...source }),
10360
10426
  ...this.opts.resolveBundled ? { resolveBundled: this.opts.resolveBundled } : {}
10361
10427
  };
10362
10428
  }
@@ -10445,7 +10511,9 @@ var TurnExecution = class {
10445
10511
  body: `${RUNTIME_UNAVAILABLE_PREFIX} ${err.message}`,
10446
10512
  kind: "runtime_notice",
10447
10513
  turnId,
10448
- parentMessageId: payload.messageId
10514
+ parentMessageId: payload.messageId,
10515
+ // CT1677: the notice is written by this attempt's source, like its speech.
10516
+ ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {}
10449
10517
  });
10450
10518
  } catch (postErr) {
10451
10519
  turnLog.warn(
@@ -10539,6 +10607,8 @@ var TurnExecution = class {
10539
10607
  log: turnLog,
10540
10608
  nextSeq: this.nextSeq,
10541
10609
  spoke: this.resumedSpoke,
10610
+ // CT1677: admitted by now, so this is the source the model's work runs on.
10611
+ ...this.executionSourceId !== void 0 ? { executionSourceId: this.executionSourceId } : {},
10542
10612
  // CT1292: a commit that didn't land may mean this turn's lease is gone.
10543
10613
  // `noteCommitFailed` is a field slot the lease watchdog fills when it
10544
10614
  // arms (it starts as a no-op) — the committer only fires it from inside
@@ -11560,7 +11630,9 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11560
11630
  function containerName(attempt) {
11561
11631
  return `cabane-runner-${attempt.conversationId}-${attempt.agentId}`;
11562
11632
  }
11563
- var networkName = containerName;
11633
+ function networkName(attempt) {
11634
+ return containerName(attempt);
11635
+ }
11564
11636
  function networkCreateError(stderr) {
11565
11637
  const said = stderr.trim();
11566
11638
  if (/address pool|fully subnetted/i.test(said))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.130",
3
+ "version": "0.6.132",
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",