@cabane/companion 0.6.211 → 0.6.213
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 +57 -12
- package/dist/runtime.js +28 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9292,6 +9292,7 @@ var init_turn_execution = __esm({
|
|
|
9292
9292
|
this.supervisor = supervisor;
|
|
9293
9293
|
this.payload = payload;
|
|
9294
9294
|
this.resumed = handleOpts.resumed === true;
|
|
9295
|
+
this.dispatchTiming = handleOpts.dispatchTiming ?? (() => void 0);
|
|
9295
9296
|
this.dispatchId = payload.messageId;
|
|
9296
9297
|
this.workspaceId = opts.workspaceId;
|
|
9297
9298
|
this.turnLog = opts.log.child({
|
|
@@ -9323,6 +9324,8 @@ var init_turn_execution = __esm({
|
|
|
9323
9324
|
// is set on every dispatch. `resumedFromSeq` is what it seeded from.
|
|
9324
9325
|
resumed;
|
|
9325
9326
|
resumedFromSeq;
|
|
9327
|
+
// The dispatch's time on the device before this process started, as the launcher said it.
|
|
9328
|
+
dispatchTiming;
|
|
9326
9329
|
// Whether the span this resumed run continues already has first words —
|
|
9327
9330
|
// from the server (`turnSpoke`) or from speech still queued in the outbox.
|
|
9328
9331
|
resumedSpoke = false;
|
|
@@ -9774,6 +9777,16 @@ var init_turn_execution = __esm({
|
|
|
9774
9777
|
this.hookEnv = hookEnv;
|
|
9775
9778
|
this.turnEnv = turnEnv;
|
|
9776
9779
|
}
|
|
9780
|
+
leaseTiming() {
|
|
9781
|
+
const timing = this.dispatchTiming();
|
|
9782
|
+
if (this.turnContext.dispatchTimingVersion !== 1 || !timing) return void 0;
|
|
9783
|
+
return {
|
|
9784
|
+
dispatchTiming: {
|
|
9785
|
+
queueMs: Math.round(timing.queueMs),
|
|
9786
|
+
setupMs: Math.round(timing.beforeRunnerMs + performance.now())
|
|
9787
|
+
}
|
|
9788
|
+
};
|
|
9789
|
+
}
|
|
9777
9790
|
async acquireLease() {
|
|
9778
9791
|
const { payload, workspaceId, turnId, turnLog } = this;
|
|
9779
9792
|
const abortController = this.abortController;
|
|
@@ -9785,6 +9798,11 @@ var init_turn_execution = __esm({
|
|
|
9785
9798
|
responsivenessVersion: 1,
|
|
9786
9799
|
responsivenessSessionMode: this.request.session ? "resumed" : "fresh"
|
|
9787
9800
|
} : {},
|
|
9801
|
+
// The dispatch's time on this device before this request: its queue, and its setup,
|
|
9802
|
+
// which is what passed before this process started plus this process's uptime
|
|
9803
|
+
// (`performance.now()` counts from the process's start). Only to an API that takes the
|
|
9804
|
+
// field, and only once the launcher has said; never a guess in its place.
|
|
9805
|
+
...this.leaseTiming() ?? {},
|
|
9788
9806
|
// A runner leases under its launcher's boot id, the one the device
|
|
9789
9807
|
// heartbeats, or the lost-run sweep reads it as a restart.
|
|
9790
9808
|
bootId: this.opts.inRunner?.bootId ?? bootId,
|
|
@@ -10299,7 +10317,7 @@ var init_turn_execution = __esm({
|
|
|
10299
10317
|
if (!o.okResult && o.resultReason && o.resultReason !== "cancelled" && !userCancelled && !o.leaseLost && !o.runtimeIncomplete && !this.skipState.skipped) {
|
|
10300
10318
|
body.errorReason = o.resultReason.slice(0, ERROR_REASON_MAX);
|
|
10301
10319
|
}
|
|
10302
|
-
body.outcome = o.okResult ? committer.spoke ? "settled" : "passed" : body.errorReason || o.runtimeIncomplete ? "failed" : "interrupted";
|
|
10320
|
+
body.outcome = o.okResult ? committer.spoke && !this.skipState.skipped ? "settled" : "passed" : body.errorReason || o.runtimeIncomplete ? "failed" : "interrupted";
|
|
10303
10321
|
if (body.outcome === "settled") body.lastMessageSeq = committer.lastMessageSeq;
|
|
10304
10322
|
if (o.sessionDegraded) {
|
|
10305
10323
|
body.degraded = true;
|
|
@@ -10631,6 +10649,7 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
|
|
|
10631
10649
|
let gotFirst = false;
|
|
10632
10650
|
let stopRequested = false;
|
|
10633
10651
|
let gzipBodies;
|
|
10652
|
+
let dispatchTiming;
|
|
10634
10653
|
let gone = false;
|
|
10635
10654
|
const causeWaiters = /* @__PURE__ */ new Set();
|
|
10636
10655
|
const first = new Promise((resolve2) => resolveFirst = resolve2);
|
|
@@ -10645,6 +10664,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
|
|
|
10645
10664
|
if (message2?.cabaneLauncher === "granted") waiting.get(message2.id)?.();
|
|
10646
10665
|
if (message2?.cabaneLauncher === "stop") stopRequested = true;
|
|
10647
10666
|
if (message2?.cabaneLauncher === "gzip_bodies") gzipBodies = message2.on;
|
|
10667
|
+
if (message2?.cabaneLauncher === "dispatch_timing") {
|
|
10668
|
+
dispatchTiming = { queueMs: message2.queueMs, beforeRunnerMs: message2.beforeRunnerMs };
|
|
10669
|
+
}
|
|
10648
10670
|
if (message2?.cabaneLauncher === "cause") {
|
|
10649
10671
|
for (const resolve2 of causeWaiters) resolve2(message2.cause);
|
|
10650
10672
|
}
|
|
@@ -10663,6 +10685,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
|
|
|
10663
10685
|
get gzipBodies() {
|
|
10664
10686
|
return gzipBodies;
|
|
10665
10687
|
},
|
|
10688
|
+
get dispatchTiming() {
|
|
10689
|
+
return dispatchTiming;
|
|
10690
|
+
},
|
|
10666
10691
|
terminationCause(timeoutMs = CAUSE_TIMEOUT_MS) {
|
|
10667
10692
|
if (gone) return Promise.resolve(null);
|
|
10668
10693
|
return new Promise((resolve2) => {
|
|
@@ -10874,7 +10899,7 @@ async function runTurn(env, deps) {
|
|
|
10874
10899
|
for (const controller of controllers) controller.abort(RUNNER_SIGTERM);
|
|
10875
10900
|
};
|
|
10876
10901
|
deps.signals.on("SIGTERM", onTerm);
|
|
10877
|
-
emit({ cabaneRunner: "ready" });
|
|
10902
|
+
emit({ cabaneRunner: "ready", uptimeMs: performance.now() });
|
|
10878
10903
|
try {
|
|
10879
10904
|
const input = parseRunnerInput(env, await channel.first);
|
|
10880
10905
|
for (const name of Object.values(RUNNER_ENV)) delete env[name];
|
|
@@ -10913,7 +10938,8 @@ async function runTurn(env, deps) {
|
|
|
10913
10938
|
});
|
|
10914
10939
|
const result = await new TurnExecution(opts, supervisor, input.dispatch, {
|
|
10915
10940
|
turnId: input.turnId,
|
|
10916
|
-
resumed: input.resumed
|
|
10941
|
+
resumed: input.resumed,
|
|
10942
|
+
dispatchTiming: () => channel.dispatchTiming
|
|
10917
10943
|
}).run().finally(() => shims.dispose());
|
|
10918
10944
|
if (outbox) await drainBeforeExit(outbox.outbox, outbox.api, deps.outboxGraceMs, log2);
|
|
10919
10945
|
emit({ cabaneRunner: "result", result });
|
|
@@ -13244,6 +13270,16 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
13244
13270
|
case "ready":
|
|
13245
13271
|
ready = true;
|
|
13246
13272
|
if (controller.signal.aborted) stopRunner.abort();
|
|
13273
|
+
if (handleOpts.clock && event.uptimeMs !== void 0 && running.stdin.writable) {
|
|
13274
|
+
const elapsed = performance.now() - handleOpts.clock.begunAt;
|
|
13275
|
+
running.stdin.write(
|
|
13276
|
+
encodeLauncherMessage({
|
|
13277
|
+
cabaneLauncher: "dispatch_timing",
|
|
13278
|
+
queueMs: handleOpts.clock.queueMs,
|
|
13279
|
+
beforeRunnerMs: Math.max(0, elapsed - event.uptimeMs)
|
|
13280
|
+
})
|
|
13281
|
+
);
|
|
13282
|
+
}
|
|
13247
13283
|
return;
|
|
13248
13284
|
case "start":
|
|
13249
13285
|
duties.notifyStart({
|
|
@@ -13424,7 +13460,11 @@ var Dispatcher = class {
|
|
|
13424
13460
|
notifyEnd: (info) => this.notifyEnd(info)
|
|
13425
13461
|
},
|
|
13426
13462
|
payload,
|
|
13427
|
-
{
|
|
13463
|
+
{
|
|
13464
|
+
turnId: opts.turnId ?? randomUUID5(),
|
|
13465
|
+
resumed: opts.resumed === true,
|
|
13466
|
+
...opts.clock ? { clock: opts.clock } : {}
|
|
13467
|
+
}
|
|
13428
13468
|
);
|
|
13429
13469
|
}
|
|
13430
13470
|
// Cancel the named turn. An older server omits turnId and cancels the pair.
|
|
@@ -14642,7 +14682,9 @@ var CompanionSupervisor = class {
|
|
|
14642
14682
|
this.detectTimer = setInterval(() => void this.detect(), DETECT_INTERVAL_MS);
|
|
14643
14683
|
this.detectTimer.unref?.();
|
|
14644
14684
|
if (!this.deviceApi || !this.config.deviceToken) {
|
|
14645
|
-
this.log.warn(
|
|
14685
|
+
this.log.warn(
|
|
14686
|
+
"This device isn't paired. Get a setup line from a workspace's Settings \u2192 Devices."
|
|
14687
|
+
);
|
|
14646
14688
|
return;
|
|
14647
14689
|
}
|
|
14648
14690
|
this.deviceSub = new SseSubscriber({
|
|
@@ -15236,6 +15278,7 @@ var CompanionSupervisor = class {
|
|
|
15236
15278
|
// it replays what is still offered on every connect instead of resuming from
|
|
15237
15279
|
// a `Last-Event-ID`.
|
|
15238
15280
|
async handleDeviceMessage(ev) {
|
|
15281
|
+
const receivedAt = performance.now();
|
|
15239
15282
|
const wire = this.parseFrame(ev);
|
|
15240
15283
|
if (!wire) return;
|
|
15241
15284
|
let wr = this.workspaces.get(wire.workspaceId);
|
|
@@ -15257,7 +15300,7 @@ var CompanionSupervisor = class {
|
|
|
15257
15300
|
}
|
|
15258
15301
|
}
|
|
15259
15302
|
this.hub.recordEvent(wr.workspaceId);
|
|
15260
|
-
await this.deliverFrame(wr, wire);
|
|
15303
|
+
await this.deliverFrame(wr, wire, receivedAt);
|
|
15261
15304
|
}
|
|
15262
15305
|
parseFrame(ev) {
|
|
15263
15306
|
try {
|
|
@@ -15276,19 +15319,19 @@ var CompanionSupervisor = class {
|
|
|
15276
15319
|
//
|
|
15277
15320
|
// A cancel needs no gate: `dispatcher.cancel` on an already-aborted run is a
|
|
15278
15321
|
// no-op that reports `false`, so a second copy is inert.
|
|
15279
|
-
async deliverFrame(wr, wire) {
|
|
15322
|
+
async deliverFrame(wr, wire, receivedAt) {
|
|
15280
15323
|
const claim = wire.type === "device:dispatch_requested" && wire.id ? this.claims.claim(wire.id) : null;
|
|
15281
15324
|
if (claim && !claim.first) {
|
|
15282
15325
|
await claim.done;
|
|
15283
15326
|
return;
|
|
15284
15327
|
}
|
|
15285
15328
|
try {
|
|
15286
|
-
await this.routeFrame(wr, wire);
|
|
15329
|
+
await this.routeFrame(wr, wire, receivedAt);
|
|
15287
15330
|
} finally {
|
|
15288
15331
|
if (claim) this.claims.release(wire.id);
|
|
15289
15332
|
}
|
|
15290
15333
|
}
|
|
15291
|
-
async routeFrame(wr, wire) {
|
|
15334
|
+
async routeFrame(wr, wire, receivedAt) {
|
|
15292
15335
|
if (wire.type === "device:cancel_requested") {
|
|
15293
15336
|
const payload2 = {
|
|
15294
15337
|
type: "device:cancel_requested",
|
|
@@ -15342,7 +15385,7 @@ var CompanionSupervisor = class {
|
|
|
15342
15385
|
}
|
|
15343
15386
|
const chainKey = `${payload.conversationId}|${payload.agentId}`;
|
|
15344
15387
|
const prev = wr.chains.get(chainKey) ?? Promise.resolve();
|
|
15345
|
-
const tail = prev.then(() => this.runDispatch(wr, wire.id, payload, agent)).catch((err) => {
|
|
15388
|
+
const tail = prev.then(() => this.runDispatch(wr, wire.id, payload, agent, void 0, receivedAt)).catch((err) => {
|
|
15346
15389
|
this.log.warn(
|
|
15347
15390
|
{
|
|
15348
15391
|
workspaceId: wr.workspaceId,
|
|
@@ -15539,7 +15582,7 @@ var CompanionSupervisor = class {
|
|
|
15539
15582
|
}
|
|
15540
15583
|
}
|
|
15541
15584
|
// One turn: the skip-vs-resume dedupe, the dispatch, then the console summary.
|
|
15542
|
-
async runDispatch(wr, dispatchId, payload, agent, recoveredTurnId) {
|
|
15585
|
+
async runDispatch(wr, dispatchId, payload, agent, recoveredTurnId, receivedAt) {
|
|
15543
15586
|
await this.initialHarnessReady;
|
|
15544
15587
|
if (this.stopped) return;
|
|
15545
15588
|
const workspaceId = wr.workspaceId;
|
|
@@ -15602,9 +15645,11 @@ var CompanionSupervisor = class {
|
|
|
15602
15645
|
workspaceId
|
|
15603
15646
|
};
|
|
15604
15647
|
this.log.info(turnBindings, "Turn started");
|
|
15648
|
+
const begunAt = performance.now();
|
|
15605
15649
|
const result = await agent.dispatcher.handle(payload, {
|
|
15606
15650
|
turnId,
|
|
15607
|
-
resumed: resumedTurnId !== null
|
|
15651
|
+
resumed: resumedTurnId !== null,
|
|
15652
|
+
...receivedAt !== void 0 ? { clock: { queueMs: begunAt - receivedAt, begunAt } } : {}
|
|
15608
15653
|
});
|
|
15609
15654
|
if (dispatchId) markCompleted(workspaceId, dispatchId);
|
|
15610
15655
|
const durationS = (result.durationMs / 1e3).toFixed(1);
|
package/dist/runtime.js
CHANGED
|
@@ -8101,6 +8101,16 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
8101
8101
|
case "ready":
|
|
8102
8102
|
ready = true;
|
|
8103
8103
|
if (controller.signal.aborted) stopRunner.abort();
|
|
8104
|
+
if (handleOpts.clock && event.uptimeMs !== void 0 && running.stdin.writable) {
|
|
8105
|
+
const elapsed = performance.now() - handleOpts.clock.begunAt;
|
|
8106
|
+
running.stdin.write(
|
|
8107
|
+
encodeLauncherMessage({
|
|
8108
|
+
cabaneLauncher: "dispatch_timing",
|
|
8109
|
+
queueMs: handleOpts.clock.queueMs,
|
|
8110
|
+
beforeRunnerMs: Math.max(0, elapsed - event.uptimeMs)
|
|
8111
|
+
})
|
|
8112
|
+
);
|
|
8113
|
+
}
|
|
8104
8114
|
return;
|
|
8105
8115
|
case "start":
|
|
8106
8116
|
duties.notifyStart({
|
|
@@ -8591,7 +8601,11 @@ var Dispatcher = class {
|
|
|
8591
8601
|
notifyEnd: (info) => this.notifyEnd(info)
|
|
8592
8602
|
},
|
|
8593
8603
|
payload,
|
|
8594
|
-
{
|
|
8604
|
+
{
|
|
8605
|
+
turnId: opts.turnId ?? randomUUID5(),
|
|
8606
|
+
resumed: opts.resumed === true,
|
|
8607
|
+
...opts.clock ? { clock: opts.clock } : {}
|
|
8608
|
+
}
|
|
8595
8609
|
);
|
|
8596
8610
|
}
|
|
8597
8611
|
// Cancel the named turn. An older server omits turnId and cancels the pair.
|
|
@@ -10187,7 +10201,9 @@ var CompanionSupervisor = class {
|
|
|
10187
10201
|
this.detectTimer = setInterval(() => void this.detect(), DETECT_INTERVAL_MS);
|
|
10188
10202
|
this.detectTimer.unref?.();
|
|
10189
10203
|
if (!this.deviceApi || !this.config.deviceToken) {
|
|
10190
|
-
this.log.warn(
|
|
10204
|
+
this.log.warn(
|
|
10205
|
+
"This device isn't paired. Get a setup line from a workspace's Settings \u2192 Devices."
|
|
10206
|
+
);
|
|
10191
10207
|
return;
|
|
10192
10208
|
}
|
|
10193
10209
|
this.deviceSub = new SseSubscriber({
|
|
@@ -10781,6 +10797,7 @@ var CompanionSupervisor = class {
|
|
|
10781
10797
|
// it replays what is still offered on every connect instead of resuming from
|
|
10782
10798
|
// a `Last-Event-ID`.
|
|
10783
10799
|
async handleDeviceMessage(ev) {
|
|
10800
|
+
const receivedAt = performance.now();
|
|
10784
10801
|
const wire = this.parseFrame(ev);
|
|
10785
10802
|
if (!wire) return;
|
|
10786
10803
|
let wr = this.workspaces.get(wire.workspaceId);
|
|
@@ -10802,7 +10819,7 @@ var CompanionSupervisor = class {
|
|
|
10802
10819
|
}
|
|
10803
10820
|
}
|
|
10804
10821
|
this.hub.recordEvent(wr.workspaceId);
|
|
10805
|
-
await this.deliverFrame(wr, wire);
|
|
10822
|
+
await this.deliverFrame(wr, wire, receivedAt);
|
|
10806
10823
|
}
|
|
10807
10824
|
parseFrame(ev) {
|
|
10808
10825
|
try {
|
|
@@ -10821,19 +10838,19 @@ var CompanionSupervisor = class {
|
|
|
10821
10838
|
//
|
|
10822
10839
|
// A cancel needs no gate: `dispatcher.cancel` on an already-aborted run is a
|
|
10823
10840
|
// no-op that reports `false`, so a second copy is inert.
|
|
10824
|
-
async deliverFrame(wr, wire) {
|
|
10841
|
+
async deliverFrame(wr, wire, receivedAt) {
|
|
10825
10842
|
const claim = wire.type === "device:dispatch_requested" && wire.id ? this.claims.claim(wire.id) : null;
|
|
10826
10843
|
if (claim && !claim.first) {
|
|
10827
10844
|
await claim.done;
|
|
10828
10845
|
return;
|
|
10829
10846
|
}
|
|
10830
10847
|
try {
|
|
10831
|
-
await this.routeFrame(wr, wire);
|
|
10848
|
+
await this.routeFrame(wr, wire, receivedAt);
|
|
10832
10849
|
} finally {
|
|
10833
10850
|
if (claim) this.claims.release(wire.id);
|
|
10834
10851
|
}
|
|
10835
10852
|
}
|
|
10836
|
-
async routeFrame(wr, wire) {
|
|
10853
|
+
async routeFrame(wr, wire, receivedAt) {
|
|
10837
10854
|
if (wire.type === "device:cancel_requested") {
|
|
10838
10855
|
const payload2 = {
|
|
10839
10856
|
type: "device:cancel_requested",
|
|
@@ -10887,7 +10904,7 @@ var CompanionSupervisor = class {
|
|
|
10887
10904
|
}
|
|
10888
10905
|
const chainKey = `${payload.conversationId}|${payload.agentId}`;
|
|
10889
10906
|
const prev = wr.chains.get(chainKey) ?? Promise.resolve();
|
|
10890
|
-
const tail = prev.then(() => this.runDispatch(wr, wire.id, payload, agent)).catch((err) => {
|
|
10907
|
+
const tail = prev.then(() => this.runDispatch(wr, wire.id, payload, agent, void 0, receivedAt)).catch((err) => {
|
|
10891
10908
|
this.log.warn(
|
|
10892
10909
|
{
|
|
10893
10910
|
workspaceId: wr.workspaceId,
|
|
@@ -11084,7 +11101,7 @@ var CompanionSupervisor = class {
|
|
|
11084
11101
|
}
|
|
11085
11102
|
}
|
|
11086
11103
|
// One turn: the skip-vs-resume dedupe, the dispatch, then the console summary.
|
|
11087
|
-
async runDispatch(wr, dispatchId, payload, agent, recoveredTurnId) {
|
|
11104
|
+
async runDispatch(wr, dispatchId, payload, agent, recoveredTurnId, receivedAt) {
|
|
11088
11105
|
await this.initialHarnessReady;
|
|
11089
11106
|
if (this.stopped) return;
|
|
11090
11107
|
const workspaceId = wr.workspaceId;
|
|
@@ -11147,9 +11164,11 @@ var CompanionSupervisor = class {
|
|
|
11147
11164
|
workspaceId
|
|
11148
11165
|
};
|
|
11149
11166
|
this.log.info(turnBindings, "Turn started");
|
|
11167
|
+
const begunAt = performance.now();
|
|
11150
11168
|
const result = await agent.dispatcher.handle(payload, {
|
|
11151
11169
|
turnId,
|
|
11152
|
-
resumed: resumedTurnId !== null
|
|
11170
|
+
resumed: resumedTurnId !== null,
|
|
11171
|
+
...receivedAt !== void 0 ? { clock: { queueMs: begunAt - receivedAt, begunAt } } : {}
|
|
11153
11172
|
});
|
|
11154
11173
|
if (dispatchId) markCompleted(workspaceId, dispatchId);
|
|
11155
11174
|
const durationS = (result.durationMs / 1e3).toFixed(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.213",
|
|
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",
|