@cabane/companion 0.6.122 → 0.6.123
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 +113 -23
- package/dist/runtime.js +108 -18
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1334,6 +1334,13 @@ var init_api = __esm({
|
|
|
1334
1334
|
(e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
|
|
1335
1335
|
);
|
|
1336
1336
|
}
|
|
1337
|
+
// CT1680: the launcher mints the runner credential for one dispatch, on its
|
|
1338
|
+
// PAT and from its device, and hands it to the runner in place of both. A
|
|
1339
|
+
// retry is safe: the server returns the same runner id and retires the
|
|
1340
|
+
// credential the lost response carried.
|
|
1341
|
+
mintRunnerCredential(body) {
|
|
1342
|
+
return this.request("POST", "/api/agent/runner-credentials", body, { retry: true });
|
|
1343
|
+
}
|
|
1337
1344
|
// Read the skip intent recorded by the URL turn-control MCP surface.
|
|
1338
1345
|
// Codex/opencode use this server-side state; settle imports skipped/skipReason
|
|
1339
1346
|
// into the same local skip state used by claude-code's in-process tools.
|
|
@@ -7049,8 +7056,7 @@ function parseRunnerInput(env, stdin) {
|
|
|
7049
7056
|
const credentials = credentialsSchema.parse(JSON.parse(stdin || "{}"));
|
|
7050
7057
|
return {
|
|
7051
7058
|
baseUrl: required(env, RUNNER_ENV.url),
|
|
7052
|
-
|
|
7053
|
-
...credentials.deviceToken ? { deviceToken: credentials.deviceToken } : {},
|
|
7059
|
+
credential: credentials.credential,
|
|
7054
7060
|
dispatch,
|
|
7055
7061
|
turnId: required(env, RUNNER_ENV.turnId),
|
|
7056
7062
|
resumed: env[RUNNER_ENV.resumed] === "1",
|
|
@@ -7067,13 +7073,7 @@ function runnerEnv(input) {
|
|
|
7067
7073
|
};
|
|
7068
7074
|
}
|
|
7069
7075
|
function runnerStdin(input) {
|
|
7070
|
-
return `${JSON.stringify({
|
|
7071
|
-
// Phase 1 (CT1680) replaces the agent PAT with a runner credential bound to
|
|
7072
|
-
// this one dispatch; until then the child holds what the in-process turn
|
|
7073
|
-
// held, on the same machine.
|
|
7074
|
-
agentToken: input.agentToken,
|
|
7075
|
-
...input.deviceToken ? { deviceToken: input.deviceToken } : {}
|
|
7076
|
-
})}
|
|
7076
|
+
return `${JSON.stringify({ credential: input.credential })}
|
|
7077
7077
|
`;
|
|
7078
7078
|
}
|
|
7079
7079
|
function encodeLauncherMessage(message) {
|
|
@@ -7157,7 +7157,10 @@ var init_protocol = __esm({
|
|
|
7157
7157
|
connectorHealth: z14.record(z14.string(), runtimeHealthSchema),
|
|
7158
7158
|
// True when this (conversation, agent) pair was already told its session
|
|
7159
7159
|
// could not be saved: the notice is once per pair, not once per process.
|
|
7160
|
-
sessionWriteNoticeClaimed: z14.boolean()
|
|
7160
|
+
sessionWriteNoticeClaimed: z14.boolean(),
|
|
7161
|
+
// CT1680: the runner's identity, minted with its credential. The start
|
|
7162
|
+
// PATCH carries it so the lease knows which runner holds the span.
|
|
7163
|
+
runnerId: z14.string()
|
|
7161
7164
|
});
|
|
7162
7165
|
dispatchSchema = z14.object({
|
|
7163
7166
|
type: z14.literal("device:dispatch_requested"),
|
|
@@ -7168,8 +7171,7 @@ var init_protocol = __esm({
|
|
|
7168
7171
|
episode: z14.number().int().optional()
|
|
7169
7172
|
});
|
|
7170
7173
|
credentialsSchema = z14.object({
|
|
7171
|
-
|
|
7172
|
-
deviceToken: z14.string().min(1).optional()
|
|
7174
|
+
credential: z14.string().startsWith("cabrn_")
|
|
7173
7175
|
});
|
|
7174
7176
|
}
|
|
7175
7177
|
});
|
|
@@ -7192,8 +7194,9 @@ function healthSnapshot(store) {
|
|
|
7192
7194
|
}
|
|
7193
7195
|
return out;
|
|
7194
7196
|
}
|
|
7195
|
-
function runnerSpec(opts, launch, duties) {
|
|
7197
|
+
function runnerSpec(opts, launch, duties, runnerId) {
|
|
7196
7198
|
return {
|
|
7199
|
+
runnerId,
|
|
7197
7200
|
...opts.deviceId ? { deviceId: opts.deviceId } : {},
|
|
7198
7201
|
bootId,
|
|
7199
7202
|
workspaceId: opts.workspaceId,
|
|
@@ -7274,6 +7277,43 @@ function giveBackLocks(locks, prefix, remote, launch, log) {
|
|
|
7274
7277
|
})();
|
|
7275
7278
|
}
|
|
7276
7279
|
}
|
|
7280
|
+
async function mintForRunner(opts, payload, handleOpts, signal, log) {
|
|
7281
|
+
try {
|
|
7282
|
+
const minted = await opts.api.mintRunnerCredential({
|
|
7283
|
+
conversationId: payload.conversationId,
|
|
7284
|
+
messageId: payload.messageId,
|
|
7285
|
+
...payload.episode !== void 0 ? { episode: payload.episode } : {},
|
|
7286
|
+
// The one turn the credential will operate: this run's.
|
|
7287
|
+
turnId: handleOpts.turnId
|
|
7288
|
+
});
|
|
7289
|
+
return { ok: true, credential: minted.credential, runnerId: minted.runnerId };
|
|
7290
|
+
} catch (err) {
|
|
7291
|
+
if (signal.aborted) return { ok: false, reason: "cancelled" };
|
|
7292
|
+
const status2 = err instanceof ApiError ? err.status : 0;
|
|
7293
|
+
if (status2 === 404) return { ok: false, reason: "runner_credential_not_found" };
|
|
7294
|
+
if (status2 === 409 && apiErrorCode(err) === "dispatch_not_admitted")
|
|
7295
|
+
return { ok: false, reason: "runner_credential_not_admitted" };
|
|
7296
|
+
log.warn(apiErrorLogFields(err), "runner: couldn't get a credential for the turn's runner");
|
|
7297
|
+
const reason = `fetch_failed: ${describeError(err)}`;
|
|
7298
|
+
return { ok: false, reason, errorReason: reason };
|
|
7299
|
+
}
|
|
7300
|
+
}
|
|
7301
|
+
async function settleUnstarted(opts, payload, turnId, errorReason, log) {
|
|
7302
|
+
try {
|
|
7303
|
+
await opts.api.setActiveRun(opts.workspaceId, payload.conversationId, payload.agentId, {
|
|
7304
|
+
activeRunStartedAt: null,
|
|
7305
|
+
turnId,
|
|
7306
|
+
outcome: "failed",
|
|
7307
|
+
...errorReason ? { errorReason: errorReason.slice(0, 200) } : {},
|
|
7308
|
+
readThrough: payload.messageId
|
|
7309
|
+
});
|
|
7310
|
+
} catch (err) {
|
|
7311
|
+
log.warn(
|
|
7312
|
+
{ ...apiErrorLogFields(err), turnId },
|
|
7313
|
+
"Couldn't update the turn's status in Cabane. Check the conversation before trying again."
|
|
7314
|
+
);
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7277
7317
|
async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
7278
7318
|
const startedAt = Date.now();
|
|
7279
7319
|
const { turnId } = handleOpts;
|
|
@@ -7297,6 +7337,15 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7297
7337
|
agentId: payload.agentId,
|
|
7298
7338
|
turnId
|
|
7299
7339
|
};
|
|
7340
|
+
const minted = await mintForRunner(opts, payload, handleOpts, controller.signal, log);
|
|
7341
|
+
if (!minted.ok) {
|
|
7342
|
+
controller.signal.removeEventListener("abort", forwardStop);
|
|
7343
|
+
duties.releaseAbort(turnId, controller);
|
|
7344
|
+
const durationMs2 = Date.now() - startedAt;
|
|
7345
|
+
await settleUnstarted(opts, payload, turnId, minted.errorReason, log);
|
|
7346
|
+
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs: durationMs2, reason: minted.reason });
|
|
7347
|
+
return { ok: false, durationMs: durationMs2, reason: minted.reason };
|
|
7348
|
+
}
|
|
7300
7349
|
let result;
|
|
7301
7350
|
let thrown;
|
|
7302
7351
|
let code;
|
|
@@ -7314,12 +7363,11 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7314
7363
|
}
|
|
7315
7364
|
const input = {
|
|
7316
7365
|
baseUrl: opts.baseUrl,
|
|
7317
|
-
|
|
7318
|
-
...launch.deviceToken ? { deviceToken: launch.deviceToken } : {},
|
|
7366
|
+
credential: minted.credential,
|
|
7319
7367
|
dispatch: payload,
|
|
7320
7368
|
turnId,
|
|
7321
7369
|
resumed: handleOpts.resumed,
|
|
7322
|
-
spec: runnerSpec(opts, launch, duties)
|
|
7370
|
+
spec: runnerSpec(opts, launch, duties, minted.runnerId)
|
|
7323
7371
|
};
|
|
7324
7372
|
Object.assign(env, runnerEnv(input));
|
|
7325
7373
|
const running = launch.provider.exec(handle, launch.command, env, {
|
|
@@ -7431,6 +7479,14 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7431
7479
|
"Couldn't update the turn's status in Cabane. Check the conversation before trying again."
|
|
7432
7480
|
);
|
|
7433
7481
|
}
|
|
7482
|
+
await launch.reportRunnerGone?.({
|
|
7483
|
+
workspaceId: opts.workspaceId,
|
|
7484
|
+
conversationId: payload.conversationId,
|
|
7485
|
+
agentId: payload.agentId,
|
|
7486
|
+
messageId: payload.messageId,
|
|
7487
|
+
runnerId: minted.runnerId,
|
|
7488
|
+
reason: "exited"
|
|
7489
|
+
});
|
|
7434
7490
|
if (thrown !== void 0) throw new Error(thrown);
|
|
7435
7491
|
log.warn({ turnId, code }, "runner: exited without reporting a result");
|
|
7436
7492
|
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
@@ -7442,6 +7498,7 @@ var init_launch = __esm({
|
|
|
7442
7498
|
"use strict";
|
|
7443
7499
|
init_opencode_lock();
|
|
7444
7500
|
init_api_error_shape();
|
|
7501
|
+
init_errors();
|
|
7445
7502
|
init_boot_id();
|
|
7446
7503
|
init_turn_containment();
|
|
7447
7504
|
init_protocol();
|
|
@@ -8769,14 +8826,15 @@ var init_turn_execution = __esm({
|
|
|
8769
8826
|
const reportPreparing = (phase) => {
|
|
8770
8827
|
preparing ??= { id: `prepare:${randomUUID3()}`, seq: this.nextSeq() };
|
|
8771
8828
|
const { id, seq } = preparing;
|
|
8772
|
-
|
|
8829
|
+
const body = {
|
|
8773
8830
|
turnId,
|
|
8774
8831
|
toolUseId: id,
|
|
8775
8832
|
toolName: PREPARING_TOOL_NAME,
|
|
8776
8833
|
summary: "",
|
|
8777
8834
|
phase,
|
|
8778
8835
|
seq
|
|
8779
|
-
}
|
|
8836
|
+
};
|
|
8837
|
+
void this.opts.api.reportActivity(workspaceId, payload.conversationId, payload.agentId, body).catch((err) => {
|
|
8780
8838
|
turnLog.debug(
|
|
8781
8839
|
{ err: err instanceof Error ? err.message : String(err), phase },
|
|
8782
8840
|
"dispatcher: preparing-activity report failed (continuing with the hook)"
|
|
@@ -8852,6 +8910,8 @@ var init_turn_execution = __esm({
|
|
|
8852
8910
|
// CT1679: a runner leases under its launcher's boot id — the one the
|
|
8853
8911
|
// device heartbeats — or the lost-run sweep reads it as a restart.
|
|
8854
8912
|
bootId: this.opts.inRunner?.bootId ?? bootId,
|
|
8913
|
+
// CT1680: the runner holding the span, beside the device's boot id.
|
|
8914
|
+
...this.opts.inRunner ? { runnerId: this.opts.inRunner.runnerId } : {},
|
|
8855
8915
|
// CT1380: the durable record that this turn resumed, and from where.
|
|
8856
8916
|
...this.resumedFromSeq !== void 0 ? { resumedFromSeq: this.resumedFromSeq } : {},
|
|
8857
8917
|
// CT1454: the issuance the context THIS attempt runs on came from. Read
|
|
@@ -9219,7 +9279,10 @@ var init_turn_execution = __esm({
|
|
|
9219
9279
|
workspaceId,
|
|
9220
9280
|
payload.conversationId,
|
|
9221
9281
|
payload.agentId,
|
|
9222
|
-
|
|
9282
|
+
// CT1680: named for its turn, which a runner credential
|
|
9283
|
+
// requires once its start is admitted; the server then binds
|
|
9284
|
+
// the capture to that turn's attempt rather than the pair's.
|
|
9285
|
+
{ agentSessionId: event.state, turnId }
|
|
9223
9286
|
);
|
|
9224
9287
|
} catch (err) {
|
|
9225
9288
|
const status2 = err instanceof ApiError ? err.status : void 0;
|
|
@@ -10028,8 +10091,7 @@ function runnerDispatcherOptions(input, log, emit, extras = {}) {
|
|
|
10028
10091
|
const outbox = new Outbox(spec.agentId, log);
|
|
10029
10092
|
const api = new CabaneApi({
|
|
10030
10093
|
baseUrl: input.baseUrl,
|
|
10031
|
-
token: input.
|
|
10032
|
-
...input.deviceToken ? { deviceToken: input.deviceToken } : {},
|
|
10094
|
+
token: input.credential,
|
|
10033
10095
|
outbox,
|
|
10034
10096
|
log
|
|
10035
10097
|
});
|
|
@@ -10051,7 +10113,7 @@ function runnerDispatcherOptions(input, log, emit, extras = {}) {
|
|
|
10051
10113
|
workspaceSlug: spec.workspaceSlug,
|
|
10052
10114
|
agentId: spec.agentId,
|
|
10053
10115
|
agentUsername: spec.agentUsername,
|
|
10054
|
-
credential: input.
|
|
10116
|
+
credential: input.credential,
|
|
10055
10117
|
local: spec.local,
|
|
10056
10118
|
runConfig: spec.runConfig,
|
|
10057
10119
|
log,
|
|
@@ -10067,6 +10129,7 @@ function runnerDispatcherOptions(input, log, emit, extras = {}) {
|
|
|
10067
10129
|
connectorHealth,
|
|
10068
10130
|
inRunner: {
|
|
10069
10131
|
bootId: spec.bootId,
|
|
10132
|
+
runnerId: spec.runnerId,
|
|
10070
10133
|
opencodeTransport: (serverUrl) => runnerOpencodeTransport(
|
|
10071
10134
|
serverUrl,
|
|
10072
10135
|
acquireLock,
|
|
@@ -13433,7 +13496,7 @@ var CompanionSupervisor = class {
|
|
|
13433
13496
|
runner: {
|
|
13434
13497
|
provider: this.runnerProvider(),
|
|
13435
13498
|
command: this.runnerCommand ?? selfRunnerCommand(),
|
|
13436
|
-
|
|
13499
|
+
reportRunnerGone: (report) => this.reportRunnerGone(report),
|
|
13437
13500
|
connectorHealth: this.connectorHealth,
|
|
13438
13501
|
locks: this.runnerLocks,
|
|
13439
13502
|
sandbox: this.runnerSandbox()
|
|
@@ -13623,6 +13686,33 @@ var CompanionSupervisor = class {
|
|
|
13623
13686
|
);
|
|
13624
13687
|
return false;
|
|
13625
13688
|
}
|
|
13689
|
+
// CT1680: a runner is gone without having settled its turn. Same bounded
|
|
13690
|
+
// retry as the unrunnable report above, on the device credential, which is
|
|
13691
|
+
// the one the launcher still holds whatever became of the runner.
|
|
13692
|
+
async reportRunnerGone(report) {
|
|
13693
|
+
if (!this.deviceApi) return;
|
|
13694
|
+
let lastErr;
|
|
13695
|
+
for (const wait of this.unrunnableRetryDelaysMs) {
|
|
13696
|
+
if (this.stopped) return;
|
|
13697
|
+
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
13698
|
+
try {
|
|
13699
|
+
await this.deviceApi.reportDispatchDrop(report);
|
|
13700
|
+
return;
|
|
13701
|
+
} catch (err) {
|
|
13702
|
+
lastErr = err;
|
|
13703
|
+
}
|
|
13704
|
+
}
|
|
13705
|
+
this.log.error(
|
|
13706
|
+
{
|
|
13707
|
+
workspaceId: report.workspaceId,
|
|
13708
|
+
conversationId: report.conversationId,
|
|
13709
|
+
agentId: report.agentId,
|
|
13710
|
+
runnerId: report.runnerId,
|
|
13711
|
+
err: lastErr instanceof Error ? lastErr.message : String(lastErr)
|
|
13712
|
+
},
|
|
13713
|
+
"Couldn't tell Cabane a turn's runner stopped; its credential expires on its own."
|
|
13714
|
+
);
|
|
13715
|
+
}
|
|
13626
13716
|
// Pull-on-miss for the assignment-change race: a dispatch arrived for an agent
|
|
13627
13717
|
// we don't yet run. If it's addressed to THIS device, the assignment just
|
|
13628
13718
|
// changed under us (the poll hasn't caught it) — force a fresh assignments pull
|
package/dist/runtime.js
CHANGED
|
@@ -2516,6 +2516,13 @@ var CabaneApi = class {
|
|
|
2516
2516
|
(e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
|
|
2517
2517
|
);
|
|
2518
2518
|
}
|
|
2519
|
+
// CT1680: the launcher mints the runner credential for one dispatch, on its
|
|
2520
|
+
// PAT and from its device, and hands it to the runner in place of both. A
|
|
2521
|
+
// retry is safe: the server returns the same runner id and retires the
|
|
2522
|
+
// credential the lost response carried.
|
|
2523
|
+
mintRunnerCredential(body) {
|
|
2524
|
+
return this.request("POST", "/api/agent/runner-credentials", body, { retry: true });
|
|
2525
|
+
}
|
|
2519
2526
|
// Read the skip intent recorded by the URL turn-control MCP surface.
|
|
2520
2527
|
// Codex/opencode use this server-side state; settle imports skipped/skipReason
|
|
2521
2528
|
// into the same local skip state used by claude-code's in-process tools.
|
|
@@ -8243,7 +8250,10 @@ var runnerSpecSchema = z14.object({
|
|
|
8243
8250
|
connectorHealth: z14.record(z14.string(), runtimeHealthSchema),
|
|
8244
8251
|
// True when this (conversation, agent) pair was already told its session
|
|
8245
8252
|
// could not be saved: the notice is once per pair, not once per process.
|
|
8246
|
-
sessionWriteNoticeClaimed: z14.boolean()
|
|
8253
|
+
sessionWriteNoticeClaimed: z14.boolean(),
|
|
8254
|
+
// CT1680: the runner's identity, minted with its credential. The start
|
|
8255
|
+
// PATCH carries it so the lease knows which runner holds the span.
|
|
8256
|
+
runnerId: z14.string()
|
|
8247
8257
|
});
|
|
8248
8258
|
var dispatchSchema = z14.object({
|
|
8249
8259
|
type: z14.literal("device:dispatch_requested"),
|
|
@@ -8254,8 +8264,7 @@ var dispatchSchema = z14.object({
|
|
|
8254
8264
|
episode: z14.number().int().optional()
|
|
8255
8265
|
});
|
|
8256
8266
|
var credentialsSchema = z14.object({
|
|
8257
|
-
|
|
8258
|
-
deviceToken: z14.string().min(1).optional()
|
|
8267
|
+
credential: z14.string().startsWith("cabrn_")
|
|
8259
8268
|
});
|
|
8260
8269
|
function runnerEnv(input) {
|
|
8261
8270
|
return {
|
|
@@ -8267,13 +8276,7 @@ function runnerEnv(input) {
|
|
|
8267
8276
|
};
|
|
8268
8277
|
}
|
|
8269
8278
|
function runnerStdin(input) {
|
|
8270
|
-
return `${JSON.stringify({
|
|
8271
|
-
// Phase 1 (CT1680) replaces the agent PAT with a runner credential bound to
|
|
8272
|
-
// this one dispatch; until then the child holds what the in-process turn
|
|
8273
|
-
// held, on the same machine.
|
|
8274
|
-
agentToken: input.agentToken,
|
|
8275
|
-
...input.deviceToken ? { deviceToken: input.deviceToken } : {}
|
|
8276
|
-
})}
|
|
8279
|
+
return `${JSON.stringify({ credential: input.credential })}
|
|
8277
8280
|
`;
|
|
8278
8281
|
}
|
|
8279
8282
|
function encodeLauncherMessage(message) {
|
|
@@ -8309,8 +8312,9 @@ function healthSnapshot(store) {
|
|
|
8309
8312
|
}
|
|
8310
8313
|
return out;
|
|
8311
8314
|
}
|
|
8312
|
-
function runnerSpec(opts, launch, duties) {
|
|
8315
|
+
function runnerSpec(opts, launch, duties, runnerId) {
|
|
8313
8316
|
return {
|
|
8317
|
+
runnerId,
|
|
8314
8318
|
...opts.deviceId ? { deviceId: opts.deviceId } : {},
|
|
8315
8319
|
bootId,
|
|
8316
8320
|
workspaceId: opts.workspaceId,
|
|
@@ -8399,6 +8403,43 @@ function giveBackLocks(locks, prefix, remote, launch, log) {
|
|
|
8399
8403
|
})();
|
|
8400
8404
|
}
|
|
8401
8405
|
}
|
|
8406
|
+
async function mintForRunner(opts, payload, handleOpts, signal, log) {
|
|
8407
|
+
try {
|
|
8408
|
+
const minted = await opts.api.mintRunnerCredential({
|
|
8409
|
+
conversationId: payload.conversationId,
|
|
8410
|
+
messageId: payload.messageId,
|
|
8411
|
+
...payload.episode !== void 0 ? { episode: payload.episode } : {},
|
|
8412
|
+
// The one turn the credential will operate: this run's.
|
|
8413
|
+
turnId: handleOpts.turnId
|
|
8414
|
+
});
|
|
8415
|
+
return { ok: true, credential: minted.credential, runnerId: minted.runnerId };
|
|
8416
|
+
} catch (err) {
|
|
8417
|
+
if (signal.aborted) return { ok: false, reason: "cancelled" };
|
|
8418
|
+
const status = err instanceof ApiError ? err.status : 0;
|
|
8419
|
+
if (status === 404) return { ok: false, reason: "runner_credential_not_found" };
|
|
8420
|
+
if (status === 409 && apiErrorCode(err) === "dispatch_not_admitted")
|
|
8421
|
+
return { ok: false, reason: "runner_credential_not_admitted" };
|
|
8422
|
+
log.warn(apiErrorLogFields(err), "runner: couldn't get a credential for the turn's runner");
|
|
8423
|
+
const reason = `fetch_failed: ${describeError(err)}`;
|
|
8424
|
+
return { ok: false, reason, errorReason: reason };
|
|
8425
|
+
}
|
|
8426
|
+
}
|
|
8427
|
+
async function settleUnstarted(opts, payload, turnId, errorReason, log) {
|
|
8428
|
+
try {
|
|
8429
|
+
await opts.api.setActiveRun(opts.workspaceId, payload.conversationId, payload.agentId, {
|
|
8430
|
+
activeRunStartedAt: null,
|
|
8431
|
+
turnId,
|
|
8432
|
+
outcome: "failed",
|
|
8433
|
+
...errorReason ? { errorReason: errorReason.slice(0, 200) } : {},
|
|
8434
|
+
readThrough: payload.messageId
|
|
8435
|
+
});
|
|
8436
|
+
} catch (err) {
|
|
8437
|
+
log.warn(
|
|
8438
|
+
{ ...apiErrorLogFields(err), turnId },
|
|
8439
|
+
"Couldn't update the turn's status in Cabane. Check the conversation before trying again."
|
|
8440
|
+
);
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8402
8443
|
async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
8403
8444
|
const startedAt = Date.now();
|
|
8404
8445
|
const { turnId } = handleOpts;
|
|
@@ -8422,6 +8463,15 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
8422
8463
|
agentId: payload.agentId,
|
|
8423
8464
|
turnId
|
|
8424
8465
|
};
|
|
8466
|
+
const minted = await mintForRunner(opts, payload, handleOpts, controller.signal, log);
|
|
8467
|
+
if (!minted.ok) {
|
|
8468
|
+
controller.signal.removeEventListener("abort", forwardStop);
|
|
8469
|
+
duties.releaseAbort(turnId, controller);
|
|
8470
|
+
const durationMs2 = Date.now() - startedAt;
|
|
8471
|
+
await settleUnstarted(opts, payload, turnId, minted.errorReason, log);
|
|
8472
|
+
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs: durationMs2, reason: minted.reason });
|
|
8473
|
+
return { ok: false, durationMs: durationMs2, reason: minted.reason };
|
|
8474
|
+
}
|
|
8425
8475
|
let result;
|
|
8426
8476
|
let thrown;
|
|
8427
8477
|
let code;
|
|
@@ -8439,12 +8489,11 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
8439
8489
|
}
|
|
8440
8490
|
const input = {
|
|
8441
8491
|
baseUrl: opts.baseUrl,
|
|
8442
|
-
|
|
8443
|
-
...launch.deviceToken ? { deviceToken: launch.deviceToken } : {},
|
|
8492
|
+
credential: minted.credential,
|
|
8444
8493
|
dispatch: payload,
|
|
8445
8494
|
turnId,
|
|
8446
8495
|
resumed: handleOpts.resumed,
|
|
8447
|
-
spec: runnerSpec(opts, launch, duties)
|
|
8496
|
+
spec: runnerSpec(opts, launch, duties, minted.runnerId)
|
|
8448
8497
|
};
|
|
8449
8498
|
Object.assign(env, runnerEnv(input));
|
|
8450
8499
|
const running = launch.provider.exec(handle, launch.command, env, {
|
|
@@ -8556,6 +8605,14 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
8556
8605
|
"Couldn't update the turn's status in Cabane. Check the conversation before trying again."
|
|
8557
8606
|
);
|
|
8558
8607
|
}
|
|
8608
|
+
await launch.reportRunnerGone?.({
|
|
8609
|
+
workspaceId: opts.workspaceId,
|
|
8610
|
+
conversationId: payload.conversationId,
|
|
8611
|
+
agentId: payload.agentId,
|
|
8612
|
+
messageId: payload.messageId,
|
|
8613
|
+
runnerId: minted.runnerId,
|
|
8614
|
+
reason: "exited"
|
|
8615
|
+
});
|
|
8559
8616
|
if (thrown !== void 0) throw new Error(thrown);
|
|
8560
8617
|
log.warn({ turnId, code }, "runner: exited without reporting a result");
|
|
8561
8618
|
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
@@ -9783,14 +9840,15 @@ var TurnExecution = class {
|
|
|
9783
9840
|
const reportPreparing = (phase) => {
|
|
9784
9841
|
preparing ??= { id: `prepare:${randomUUID3()}`, seq: this.nextSeq() };
|
|
9785
9842
|
const { id, seq } = preparing;
|
|
9786
|
-
|
|
9843
|
+
const body = {
|
|
9787
9844
|
turnId,
|
|
9788
9845
|
toolUseId: id,
|
|
9789
9846
|
toolName: PREPARING_TOOL_NAME,
|
|
9790
9847
|
summary: "",
|
|
9791
9848
|
phase,
|
|
9792
9849
|
seq
|
|
9793
|
-
}
|
|
9850
|
+
};
|
|
9851
|
+
void this.opts.api.reportActivity(workspaceId, payload.conversationId, payload.agentId, body).catch((err) => {
|
|
9794
9852
|
turnLog.debug(
|
|
9795
9853
|
{ err: err instanceof Error ? err.message : String(err), phase },
|
|
9796
9854
|
"dispatcher: preparing-activity report failed (continuing with the hook)"
|
|
@@ -9866,6 +9924,8 @@ var TurnExecution = class {
|
|
|
9866
9924
|
// CT1679: a runner leases under its launcher's boot id — the one the
|
|
9867
9925
|
// device heartbeats — or the lost-run sweep reads it as a restart.
|
|
9868
9926
|
bootId: this.opts.inRunner?.bootId ?? bootId,
|
|
9927
|
+
// CT1680: the runner holding the span, beside the device's boot id.
|
|
9928
|
+
...this.opts.inRunner ? { runnerId: this.opts.inRunner.runnerId } : {},
|
|
9869
9929
|
// CT1380: the durable record that this turn resumed, and from where.
|
|
9870
9930
|
...this.resumedFromSeq !== void 0 ? { resumedFromSeq: this.resumedFromSeq } : {},
|
|
9871
9931
|
// CT1454: the issuance the context THIS attempt runs on came from. Read
|
|
@@ -10233,7 +10293,10 @@ var TurnExecution = class {
|
|
|
10233
10293
|
workspaceId,
|
|
10234
10294
|
payload.conversationId,
|
|
10235
10295
|
payload.agentId,
|
|
10236
|
-
|
|
10296
|
+
// CT1680: named for its turn, which a runner credential
|
|
10297
|
+
// requires once its start is admitted; the server then binds
|
|
10298
|
+
// the capture to that turn's attempt rather than the pair's.
|
|
10299
|
+
{ agentSessionId: event.state, turnId }
|
|
10237
10300
|
);
|
|
10238
10301
|
} catch (err) {
|
|
10239
10302
|
const status = err instanceof ApiError ? err.status : void 0;
|
|
@@ -11731,7 +11794,7 @@ var CompanionSupervisor = class {
|
|
|
11731
11794
|
runner: {
|
|
11732
11795
|
provider: this.runnerProvider(),
|
|
11733
11796
|
command: this.runnerCommand ?? selfRunnerCommand(),
|
|
11734
|
-
|
|
11797
|
+
reportRunnerGone: (report) => this.reportRunnerGone(report),
|
|
11735
11798
|
connectorHealth: this.connectorHealth,
|
|
11736
11799
|
locks: this.runnerLocks,
|
|
11737
11800
|
sandbox: this.runnerSandbox()
|
|
@@ -11921,6 +11984,33 @@ var CompanionSupervisor = class {
|
|
|
11921
11984
|
);
|
|
11922
11985
|
return false;
|
|
11923
11986
|
}
|
|
11987
|
+
// CT1680: a runner is gone without having settled its turn. Same bounded
|
|
11988
|
+
// retry as the unrunnable report above, on the device credential, which is
|
|
11989
|
+
// the one the launcher still holds whatever became of the runner.
|
|
11990
|
+
async reportRunnerGone(report) {
|
|
11991
|
+
if (!this.deviceApi) return;
|
|
11992
|
+
let lastErr;
|
|
11993
|
+
for (const wait of this.unrunnableRetryDelaysMs) {
|
|
11994
|
+
if (this.stopped) return;
|
|
11995
|
+
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
11996
|
+
try {
|
|
11997
|
+
await this.deviceApi.reportDispatchDrop(report);
|
|
11998
|
+
return;
|
|
11999
|
+
} catch (err) {
|
|
12000
|
+
lastErr = err;
|
|
12001
|
+
}
|
|
12002
|
+
}
|
|
12003
|
+
this.log.error(
|
|
12004
|
+
{
|
|
12005
|
+
workspaceId: report.workspaceId,
|
|
12006
|
+
conversationId: report.conversationId,
|
|
12007
|
+
agentId: report.agentId,
|
|
12008
|
+
runnerId: report.runnerId,
|
|
12009
|
+
err: lastErr instanceof Error ? lastErr.message : String(lastErr)
|
|
12010
|
+
},
|
|
12011
|
+
"Couldn't tell Cabane a turn's runner stopped; its credential expires on its own."
|
|
12012
|
+
);
|
|
12013
|
+
}
|
|
11924
12014
|
// Pull-on-miss for the assignment-change race: a dispatch arrived for an agent
|
|
11925
12015
|
// we don't yet run. If it's addressed to THIS device, the assignment just
|
|
11926
12016
|
// changed under us (the poll hasn't caught it) — force a fresh assignments pull
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.123",
|
|
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",
|