@cabane/companion 0.6.13 → 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 +97 -18
- package/dist/runtime.js +97 -18
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6278,7 +6278,7 @@ var ConnectorHealthStore = class {
|
|
|
6278
6278
|
|
|
6279
6279
|
// src/dispatcher.ts
|
|
6280
6280
|
import { randomUUID } from "crypto";
|
|
6281
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync10 } from "fs";
|
|
6281
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync10, statSync as statSync2 } from "fs";
|
|
6282
6282
|
import { join as join13 } from "path";
|
|
6283
6283
|
|
|
6284
6284
|
// src/summon.ts
|
|
@@ -6620,7 +6620,7 @@ async function writeCodexInstructionsFile(contents) {
|
|
|
6620
6620
|
}
|
|
6621
6621
|
|
|
6622
6622
|
// src/prepared.ts
|
|
6623
|
-
import { mkdirSync as mkdirSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
|
|
6623
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
|
|
6624
6624
|
import { join as join10 } from "path";
|
|
6625
6625
|
function dirFor(workspaceId) {
|
|
6626
6626
|
return join10(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
@@ -6655,6 +6655,9 @@ function writePrepared(workspaceId, conversationId, agentId, result) {
|
|
|
6655
6655
|
"utf8"
|
|
6656
6656
|
);
|
|
6657
6657
|
}
|
|
6658
|
+
function clearPrepared(workspaceId, conversationId, agentId) {
|
|
6659
|
+
rmSync4(pathFor3(workspaceId, conversationId, agentId), { force: true });
|
|
6660
|
+
}
|
|
6658
6661
|
|
|
6659
6662
|
// src/secrets.ts
|
|
6660
6663
|
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
@@ -6748,7 +6751,7 @@ function resolveMcpSecrets(mcpServers, store) {
|
|
|
6748
6751
|
}
|
|
6749
6752
|
|
|
6750
6753
|
// src/transcript-writer.ts
|
|
6751
|
-
import { appendFileSync, chmodSync as chmodSync3, mkdirSync as mkdirSync9, readdirSync, rmSync as
|
|
6754
|
+
import { appendFileSync, chmodSync as chmodSync3, mkdirSync as mkdirSync9, readdirSync, rmSync as rmSync5 } from "fs";
|
|
6752
6755
|
import { join as join12 } from "path";
|
|
6753
6756
|
function transcriptsDir() {
|
|
6754
6757
|
return join12(cabaneDir(), "transcripts");
|
|
@@ -6819,7 +6822,7 @@ function pruneOld(dir2, retain) {
|
|
|
6819
6822
|
const drop = files.sort().slice(0, files.length - retain);
|
|
6820
6823
|
for (const f of drop) {
|
|
6821
6824
|
try {
|
|
6822
|
-
|
|
6825
|
+
rmSync5(join12(dir2, f), { force: true });
|
|
6823
6826
|
} catch {
|
|
6824
6827
|
}
|
|
6825
6828
|
}
|
|
@@ -7117,6 +7120,21 @@ var SKIPPED_MARKER_BODY = "(skipped)";
|
|
|
7117
7120
|
var DEFAULT_PREPARING_ROW_DELAY_MS = 1500;
|
|
7118
7121
|
var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
7119
7122
|
var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
7123
|
+
function checkoutState(cwd) {
|
|
7124
|
+
if (!cwd) return { ok: false, reason: "no checkout was resolved for this turn" };
|
|
7125
|
+
if (!existsSync9(cwd)) return { ok: false, reason: `the checkout directory is gone (${cwd})` };
|
|
7126
|
+
const gitPath = join13(cwd, ".git");
|
|
7127
|
+
if (!existsSync9(gitPath)) return { ok: false, reason: `${cwd} holds no git metadata` };
|
|
7128
|
+
let stat;
|
|
7129
|
+
try {
|
|
7130
|
+
stat = statSync2(gitPath);
|
|
7131
|
+
} catch (error) {
|
|
7132
|
+
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
7133
|
+
}
|
|
7134
|
+
if (stat.isDirectory() && !existsSync9(join13(gitPath, "HEAD")))
|
|
7135
|
+
return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
|
|
7136
|
+
return { ok: true, reason: "usable" };
|
|
7137
|
+
}
|
|
7120
7138
|
function runKey(conversationId, agentId) {
|
|
7121
7139
|
return `${conversationId}|${agentId}`;
|
|
7122
7140
|
}
|
|
@@ -7286,7 +7304,15 @@ var Dispatcher = class {
|
|
|
7286
7304
|
const triggerIsPrepareFailure = message.body.startsWith(PREPARE_FAILED_PREFIX);
|
|
7287
7305
|
const prepareFailureDispatch = turnContext.dispatchedByAgentId && !triggerIsPrepareFailure ? { dispatch: turnContext.dispatchedByAgentId } : {};
|
|
7288
7306
|
if (prepareHook) {
|
|
7289
|
-
|
|
7307
|
+
let cached2 = readPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
7308
|
+
if (cached2 && !checkoutState(cached2.cwd).ok) {
|
|
7309
|
+
turnLog.warn(
|
|
7310
|
+
{ cwd: cached2.cwd, reason: checkoutState(cached2.cwd).reason },
|
|
7311
|
+
"dispatcher: the prepared checkout is no longer usable \u2014 re-running the prepare hook to restore it"
|
|
7312
|
+
);
|
|
7313
|
+
clearPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
7314
|
+
cached2 = null;
|
|
7315
|
+
}
|
|
7290
7316
|
if (cached2) {
|
|
7291
7317
|
effectiveCwd = cached2.cwd;
|
|
7292
7318
|
hookEnv = cached2.env;
|
|
@@ -7542,21 +7568,49 @@ ${reason}`,
|
|
|
7542
7568
|
`runtime_unavailable:${err.runtime}`
|
|
7543
7569
|
);
|
|
7544
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
|
+
};
|
|
7545
7597
|
if (prepareHook && hookEnv?.CABANE_TASK_ID) {
|
|
7546
7598
|
const proof = await proveWorkspaceTools(request, adapter.name, {
|
|
7547
7599
|
...this.opts.workspaceProofFetch ? { fetchImpl: this.opts.workspaceProofFetch } : {},
|
|
7548
7600
|
harnessFingerprint: turnContext.runtime
|
|
7549
7601
|
});
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7602
|
+
const checkout = checkoutState(effectiveCwd);
|
|
7603
|
+
turnLog[proof.ok && checkout.ok ? "info" : "error"](
|
|
7604
|
+
{ workspaceProof: proof, checkout: effectiveCwd ?? null, checkoutState: checkout },
|
|
7605
|
+
`dispatcher: workspace tool proof ${proof.ok ? "passed" : "failed"}, checkout ${checkout.ok ? "usable" : checkout.reason}`
|
|
7553
7606
|
);
|
|
7554
|
-
if (effectiveCwd) {
|
|
7607
|
+
if (effectiveCwd && checkout.ok) {
|
|
7555
7608
|
try {
|
|
7556
7609
|
const diagnosticDir = join13(effectiveCwd, ".git", "cabane");
|
|
7557
7610
|
mkdirSync10(diagnosticDir, { recursive: true });
|
|
7611
|
+
const receiptPath = join13(diagnosticDir, "readiness.jsonl");
|
|
7558
7612
|
appendFileSync2(
|
|
7559
|
-
|
|
7613
|
+
receiptPath,
|
|
7560
7614
|
`${JSON.stringify({
|
|
7561
7615
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7562
7616
|
taskId: hookEnv.CABANE_TASK_ID,
|
|
@@ -7564,11 +7618,18 @@ ${reason}`,
|
|
|
7564
7618
|
checkout: effectiveCwd,
|
|
7565
7619
|
classification: proof.ok ? "ready" : "workspace_tools_missing",
|
|
7566
7620
|
failedCapability: proof.failedCapability,
|
|
7567
|
-
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
|
|
7568
7628
|
})}
|
|
7569
7629
|
`,
|
|
7570
7630
|
{ mode: 384 }
|
|
7571
7631
|
);
|
|
7632
|
+
turnReceiptPath = receiptPath;
|
|
7572
7633
|
} catch (error) {
|
|
7573
7634
|
turnLog.warn(
|
|
7574
7635
|
{ err: error instanceof Error ? error.message : String(error) },
|
|
@@ -7576,8 +7637,26 @@ ${reason}`,
|
|
|
7576
7637
|
);
|
|
7577
7638
|
}
|
|
7578
7639
|
}
|
|
7640
|
+
if (!checkout.ok) {
|
|
7641
|
+
const reason = `checkout_missing: ${checkout.reason}; task=${hookEnv.CABANE_TASK_ID}; recovery=re-dispatch this conversation (the prepare hook re-provisions the environment)`;
|
|
7642
|
+
try {
|
|
7643
|
+
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
7644
|
+
body: `**Couldn't prepare your environment.** ${reason}`,
|
|
7645
|
+
kind: "final",
|
|
7646
|
+
turnId,
|
|
7647
|
+
parentMessageId: payload.messageId
|
|
7648
|
+
});
|
|
7649
|
+
} catch (postErr) {
|
|
7650
|
+
turnLog.warn(
|
|
7651
|
+
{ err: postErr instanceof Error ? postErr.message : String(postErr) },
|
|
7652
|
+
"dispatcher: checkout-missing notice post failed"
|
|
7653
|
+
);
|
|
7654
|
+
}
|
|
7655
|
+
return this.concludeBeforeRun(payload, turnLog, startedAt, reason);
|
|
7656
|
+
}
|
|
7579
7657
|
if (!proof.ok) {
|
|
7580
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);
|
|
7581
7660
|
try {
|
|
7582
7661
|
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
7583
7662
|
body: `**Couldn't prepare your environment.** ${reason}`,
|
|
@@ -7689,7 +7768,6 @@ ${reason}`,
|
|
|
7689
7768
|
}
|
|
7690
7769
|
};
|
|
7691
7770
|
const idleTimeoutMs = this.opts.idleTimeoutMs ?? DEFAULT_AGENT_IDLE_TIMEOUT_MS;
|
|
7692
|
-
const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
|
|
7693
7771
|
const fireTimeout = (reason) => {
|
|
7694
7772
|
if (abortController.signal.aborted) return;
|
|
7695
7773
|
timeoutReason = reason;
|
|
@@ -7791,6 +7869,7 @@ ${reason}`,
|
|
|
7791
7869
|
} finally {
|
|
7792
7870
|
if (idleTimer) clearTimeout(idleTimer);
|
|
7793
7871
|
clearTimeout(totalTimer);
|
|
7872
|
+
closeTurnReceipt(okResult, resultReason ?? null);
|
|
7794
7873
|
const userCancelled = abortController.signal.aborted && timeoutReason === null;
|
|
7795
7874
|
if (timeoutReason !== null) {
|
|
7796
7875
|
resultReason = timeoutReason;
|
|
@@ -8140,7 +8219,7 @@ import {
|
|
|
8140
8219
|
readdirSync as readdirSync2,
|
|
8141
8220
|
readFileSync as readFileSync8,
|
|
8142
8221
|
renameSync as renameSync3,
|
|
8143
|
-
rmSync as
|
|
8222
|
+
rmSync as rmSync6,
|
|
8144
8223
|
writeFileSync as writeFileSync7
|
|
8145
8224
|
} from "fs";
|
|
8146
8225
|
import { join as join14 } from "path";
|
|
@@ -8174,7 +8253,7 @@ var Outbox = class {
|
|
|
8174
8253
|
renameSync3(tmp, target);
|
|
8175
8254
|
} catch (err) {
|
|
8176
8255
|
try {
|
|
8177
|
-
|
|
8256
|
+
rmSync6(tmp, { force: true });
|
|
8178
8257
|
} catch {
|
|
8179
8258
|
}
|
|
8180
8259
|
this.log?.warn(
|
|
@@ -8221,7 +8300,7 @@ var Outbox = class {
|
|
|
8221
8300
|
// Remove a delivered (or terminally-discarded) entry. No-op if already gone.
|
|
8222
8301
|
remove(turnId, seq) {
|
|
8223
8302
|
try {
|
|
8224
|
-
|
|
8303
|
+
rmSync6(this.fileFor(turnId, seq), { force: true });
|
|
8225
8304
|
} catch {
|
|
8226
8305
|
}
|
|
8227
8306
|
}
|
|
@@ -8240,7 +8319,7 @@ var Outbox = class {
|
|
|
8240
8319
|
"companion outbox: dropping unreadable entry"
|
|
8241
8320
|
);
|
|
8242
8321
|
try {
|
|
8243
|
-
|
|
8322
|
+
rmSync6(full, { force: true });
|
|
8244
8323
|
} catch {
|
|
8245
8324
|
}
|
|
8246
8325
|
}
|
|
@@ -9307,7 +9386,7 @@ function handleUncaught(log, err, origin) {
|
|
|
9307
9386
|
}
|
|
9308
9387
|
|
|
9309
9388
|
// src/crash-marker.ts
|
|
9310
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync12, readFileSync as readFileSync9, rmSync as
|
|
9389
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync12, readFileSync as readFileSync9, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
9311
9390
|
import { join as join15 } from "path";
|
|
9312
9391
|
function crashMarkerPath() {
|
|
9313
9392
|
return join15(cabaneDir(), "last-error.json");
|
|
@@ -9322,7 +9401,7 @@ function recordCrash(rec2) {
|
|
|
9322
9401
|
function clearCrash() {
|
|
9323
9402
|
try {
|
|
9324
9403
|
const path3 = crashMarkerPath();
|
|
9325
|
-
if (existsSync11(path3))
|
|
9404
|
+
if (existsSync11(path3)) rmSync7(path3, { force: true });
|
|
9326
9405
|
} catch {
|
|
9327
9406
|
}
|
|
9328
9407
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -5931,7 +5931,7 @@ var ConnectorHealthStore = class {
|
|
|
5931
5931
|
|
|
5932
5932
|
// src/dispatcher.ts
|
|
5933
5933
|
import { randomUUID } from "crypto";
|
|
5934
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync9 } from "fs";
|
|
5934
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, statSync as statSync2 } from "fs";
|
|
5935
5935
|
import { join as join13 } from "path";
|
|
5936
5936
|
|
|
5937
5937
|
// src/summon.ts
|
|
@@ -6273,7 +6273,7 @@ async function writeCodexInstructionsFile(contents) {
|
|
|
6273
6273
|
}
|
|
6274
6274
|
|
|
6275
6275
|
// src/prepared.ts
|
|
6276
|
-
import { mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
|
|
6276
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
|
|
6277
6277
|
import { join as join10 } from "path";
|
|
6278
6278
|
function dirFor(workspaceId) {
|
|
6279
6279
|
return join10(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
@@ -6308,6 +6308,9 @@ function writePrepared(workspaceId, conversationId, agentId, result) {
|
|
|
6308
6308
|
"utf8"
|
|
6309
6309
|
);
|
|
6310
6310
|
}
|
|
6311
|
+
function clearPrepared(workspaceId, conversationId, agentId) {
|
|
6312
|
+
rmSync4(pathFor3(workspaceId, conversationId, agentId), { force: true });
|
|
6313
|
+
}
|
|
6311
6314
|
|
|
6312
6315
|
// src/secrets.ts
|
|
6313
6316
|
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
@@ -6401,7 +6404,7 @@ function resolveMcpSecrets(mcpServers, store) {
|
|
|
6401
6404
|
}
|
|
6402
6405
|
|
|
6403
6406
|
// src/transcript-writer.ts
|
|
6404
|
-
import { appendFileSync, chmodSync as chmodSync3, mkdirSync as mkdirSync8, readdirSync, rmSync as
|
|
6407
|
+
import { appendFileSync, chmodSync as chmodSync3, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync5 } from "fs";
|
|
6405
6408
|
import { join as join12 } from "path";
|
|
6406
6409
|
function transcriptsDir() {
|
|
6407
6410
|
return join12(cabaneDir(), "transcripts");
|
|
@@ -6472,7 +6475,7 @@ function pruneOld(dir2, retain) {
|
|
|
6472
6475
|
const drop = files.sort().slice(0, files.length - retain);
|
|
6473
6476
|
for (const f of drop) {
|
|
6474
6477
|
try {
|
|
6475
|
-
|
|
6478
|
+
rmSync5(join12(dir2, f), { force: true });
|
|
6476
6479
|
} catch {
|
|
6477
6480
|
}
|
|
6478
6481
|
}
|
|
@@ -6770,6 +6773,21 @@ var SKIPPED_MARKER_BODY = "(skipped)";
|
|
|
6770
6773
|
var DEFAULT_PREPARING_ROW_DELAY_MS = 1500;
|
|
6771
6774
|
var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
6772
6775
|
var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
6776
|
+
function checkoutState(cwd) {
|
|
6777
|
+
if (!cwd) return { ok: false, reason: "no checkout was resolved for this turn" };
|
|
6778
|
+
if (!existsSync9(cwd)) return { ok: false, reason: `the checkout directory is gone (${cwd})` };
|
|
6779
|
+
const gitPath = join13(cwd, ".git");
|
|
6780
|
+
if (!existsSync9(gitPath)) return { ok: false, reason: `${cwd} holds no git metadata` };
|
|
6781
|
+
let stat;
|
|
6782
|
+
try {
|
|
6783
|
+
stat = statSync2(gitPath);
|
|
6784
|
+
} catch (error) {
|
|
6785
|
+
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
6786
|
+
}
|
|
6787
|
+
if (stat.isDirectory() && !existsSync9(join13(gitPath, "HEAD")))
|
|
6788
|
+
return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
|
|
6789
|
+
return { ok: true, reason: "usable" };
|
|
6790
|
+
}
|
|
6773
6791
|
function runKey(conversationId, agentId) {
|
|
6774
6792
|
return `${conversationId}|${agentId}`;
|
|
6775
6793
|
}
|
|
@@ -6939,7 +6957,15 @@ var Dispatcher = class {
|
|
|
6939
6957
|
const triggerIsPrepareFailure = message.body.startsWith(PREPARE_FAILED_PREFIX);
|
|
6940
6958
|
const prepareFailureDispatch = turnContext.dispatchedByAgentId && !triggerIsPrepareFailure ? { dispatch: turnContext.dispatchedByAgentId } : {};
|
|
6941
6959
|
if (prepareHook) {
|
|
6942
|
-
|
|
6960
|
+
let cached2 = readPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
6961
|
+
if (cached2 && !checkoutState(cached2.cwd).ok) {
|
|
6962
|
+
turnLog.warn(
|
|
6963
|
+
{ cwd: cached2.cwd, reason: checkoutState(cached2.cwd).reason },
|
|
6964
|
+
"dispatcher: the prepared checkout is no longer usable \u2014 re-running the prepare hook to restore it"
|
|
6965
|
+
);
|
|
6966
|
+
clearPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
6967
|
+
cached2 = null;
|
|
6968
|
+
}
|
|
6943
6969
|
if (cached2) {
|
|
6944
6970
|
effectiveCwd = cached2.cwd;
|
|
6945
6971
|
hookEnv = cached2.env;
|
|
@@ -7195,21 +7221,49 @@ ${reason}`,
|
|
|
7195
7221
|
`runtime_unavailable:${err.runtime}`
|
|
7196
7222
|
);
|
|
7197
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
|
+
};
|
|
7198
7250
|
if (prepareHook && hookEnv?.CABANE_TASK_ID) {
|
|
7199
7251
|
const proof = await proveWorkspaceTools(request, adapter.name, {
|
|
7200
7252
|
...this.opts.workspaceProofFetch ? { fetchImpl: this.opts.workspaceProofFetch } : {},
|
|
7201
7253
|
harnessFingerprint: turnContext.runtime
|
|
7202
7254
|
});
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7255
|
+
const checkout = checkoutState(effectiveCwd);
|
|
7256
|
+
turnLog[proof.ok && checkout.ok ? "info" : "error"](
|
|
7257
|
+
{ workspaceProof: proof, checkout: effectiveCwd ?? null, checkoutState: checkout },
|
|
7258
|
+
`dispatcher: workspace tool proof ${proof.ok ? "passed" : "failed"}, checkout ${checkout.ok ? "usable" : checkout.reason}`
|
|
7206
7259
|
);
|
|
7207
|
-
if (effectiveCwd) {
|
|
7260
|
+
if (effectiveCwd && checkout.ok) {
|
|
7208
7261
|
try {
|
|
7209
7262
|
const diagnosticDir = join13(effectiveCwd, ".git", "cabane");
|
|
7210
7263
|
mkdirSync9(diagnosticDir, { recursive: true });
|
|
7264
|
+
const receiptPath = join13(diagnosticDir, "readiness.jsonl");
|
|
7211
7265
|
appendFileSync2(
|
|
7212
|
-
|
|
7266
|
+
receiptPath,
|
|
7213
7267
|
`${JSON.stringify({
|
|
7214
7268
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7215
7269
|
taskId: hookEnv.CABANE_TASK_ID,
|
|
@@ -7217,11 +7271,18 @@ ${reason}`,
|
|
|
7217
7271
|
checkout: effectiveCwd,
|
|
7218
7272
|
classification: proof.ok ? "ready" : "workspace_tools_missing",
|
|
7219
7273
|
failedCapability: proof.failedCapability,
|
|
7220
|
-
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
|
|
7221
7281
|
})}
|
|
7222
7282
|
`,
|
|
7223
7283
|
{ mode: 384 }
|
|
7224
7284
|
);
|
|
7285
|
+
turnReceiptPath = receiptPath;
|
|
7225
7286
|
} catch (error) {
|
|
7226
7287
|
turnLog.warn(
|
|
7227
7288
|
{ err: error instanceof Error ? error.message : String(error) },
|
|
@@ -7229,8 +7290,26 @@ ${reason}`,
|
|
|
7229
7290
|
);
|
|
7230
7291
|
}
|
|
7231
7292
|
}
|
|
7293
|
+
if (!checkout.ok) {
|
|
7294
|
+
const reason = `checkout_missing: ${checkout.reason}; task=${hookEnv.CABANE_TASK_ID}; recovery=re-dispatch this conversation (the prepare hook re-provisions the environment)`;
|
|
7295
|
+
try {
|
|
7296
|
+
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
7297
|
+
body: `**Couldn't prepare your environment.** ${reason}`,
|
|
7298
|
+
kind: "final",
|
|
7299
|
+
turnId,
|
|
7300
|
+
parentMessageId: payload.messageId
|
|
7301
|
+
});
|
|
7302
|
+
} catch (postErr) {
|
|
7303
|
+
turnLog.warn(
|
|
7304
|
+
{ err: postErr instanceof Error ? postErr.message : String(postErr) },
|
|
7305
|
+
"dispatcher: checkout-missing notice post failed"
|
|
7306
|
+
);
|
|
7307
|
+
}
|
|
7308
|
+
return this.concludeBeforeRun(payload, turnLog, startedAt, reason);
|
|
7309
|
+
}
|
|
7232
7310
|
if (!proof.ok) {
|
|
7233
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);
|
|
7234
7313
|
try {
|
|
7235
7314
|
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
7236
7315
|
body: `**Couldn't prepare your environment.** ${reason}`,
|
|
@@ -7342,7 +7421,6 @@ ${reason}`,
|
|
|
7342
7421
|
}
|
|
7343
7422
|
};
|
|
7344
7423
|
const idleTimeoutMs = this.opts.idleTimeoutMs ?? DEFAULT_AGENT_IDLE_TIMEOUT_MS;
|
|
7345
|
-
const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
|
|
7346
7424
|
const fireTimeout = (reason) => {
|
|
7347
7425
|
if (abortController.signal.aborted) return;
|
|
7348
7426
|
timeoutReason = reason;
|
|
@@ -7444,6 +7522,7 @@ ${reason}`,
|
|
|
7444
7522
|
} finally {
|
|
7445
7523
|
if (idleTimer) clearTimeout(idleTimer);
|
|
7446
7524
|
clearTimeout(totalTimer);
|
|
7525
|
+
closeTurnReceipt(okResult, resultReason ?? null);
|
|
7447
7526
|
const userCancelled = abortController.signal.aborted && timeoutReason === null;
|
|
7448
7527
|
if (timeoutReason !== null) {
|
|
7449
7528
|
resultReason = timeoutReason;
|
|
@@ -7793,7 +7872,7 @@ import {
|
|
|
7793
7872
|
readdirSync as readdirSync2,
|
|
7794
7873
|
readFileSync as readFileSync8,
|
|
7795
7874
|
renameSync as renameSync3,
|
|
7796
|
-
rmSync as
|
|
7875
|
+
rmSync as rmSync6,
|
|
7797
7876
|
writeFileSync as writeFileSync7
|
|
7798
7877
|
} from "fs";
|
|
7799
7878
|
import { join as join14 } from "path";
|
|
@@ -7827,7 +7906,7 @@ var Outbox = class {
|
|
|
7827
7906
|
renameSync3(tmp, target);
|
|
7828
7907
|
} catch (err) {
|
|
7829
7908
|
try {
|
|
7830
|
-
|
|
7909
|
+
rmSync6(tmp, { force: true });
|
|
7831
7910
|
} catch {
|
|
7832
7911
|
}
|
|
7833
7912
|
this.log?.warn(
|
|
@@ -7874,7 +7953,7 @@ var Outbox = class {
|
|
|
7874
7953
|
// Remove a delivered (or terminally-discarded) entry. No-op if already gone.
|
|
7875
7954
|
remove(turnId, seq) {
|
|
7876
7955
|
try {
|
|
7877
|
-
|
|
7956
|
+
rmSync6(this.fileFor(turnId, seq), { force: true });
|
|
7878
7957
|
} catch {
|
|
7879
7958
|
}
|
|
7880
7959
|
}
|
|
@@ -7893,7 +7972,7 @@ var Outbox = class {
|
|
|
7893
7972
|
"companion outbox: dropping unreadable entry"
|
|
7894
7973
|
);
|
|
7895
7974
|
try {
|
|
7896
|
-
|
|
7975
|
+
rmSync6(full, { force: true });
|
|
7897
7976
|
} catch {
|
|
7898
7977
|
}
|
|
7899
7978
|
}
|
|
@@ -8960,7 +9039,7 @@ function handleUncaught(log, err, origin) {
|
|
|
8960
9039
|
}
|
|
8961
9040
|
|
|
8962
9041
|
// src/crash-marker.ts
|
|
8963
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync9, rmSync as
|
|
9042
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync9, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
8964
9043
|
import { join as join15 } from "path";
|
|
8965
9044
|
function crashMarkerPath() {
|
|
8966
9045
|
return join15(cabaneDir(), "last-error.json");
|
|
@@ -8975,7 +9054,7 @@ function recordCrash(rec) {
|
|
|
8975
9054
|
function clearCrash() {
|
|
8976
9055
|
try {
|
|
8977
9056
|
const path3 = crashMarkerPath();
|
|
8978
|
-
if (existsSync11(path3))
|
|
9057
|
+
if (existsSync11(path3)) rmSync7(path3, { force: true });
|
|
8979
9058
|
} catch {
|
|
8980
9059
|
}
|
|
8981
9060
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
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",
|