@coreplane/switchboard 1.220.0 → 1.221.0
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/assets/deploy/cloudflare-resident/worker.ts +87 -22
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/runEvents.ts +13 -0
- package/dist/assets/src/execution/residentReuse.ts +98 -0
- package/dist/assets/src/execution/residentSteps.ts +1 -0
- package/dist/assets/web/dist/.vite/manifest.json +18 -18
- package/dist/assets/web/dist/assets/{ResidentDetailPage-DvCt96Dk.js → ResidentDetailPage-iqyj3Kpt.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-BZaYja1-.js → ResidentsIndexPage-BiECMcX5.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-DI4vzCUu.js → RunRoutePage-YCWYYZkj.js} +4 -4
- package/dist/assets/web/dist/assets/{RunsIndexPage-Ch9uP4p4.js → RunsIndexPage-BX6231bA.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-DAkAHMCH.js → ScheduledPage-ibRdZaDS.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-CfBonGcV.js → StatusDot-W2x3d782.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-CBD36E3W.js → Tooltip-BLRVgG37.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-DQ1U73LF.js → dist-C3C34ZMu.js} +1 -1
- package/dist/assets/web/dist/assets/{main-CdLnG-yl.js → main-B9dN-Wec.js} +2 -2
- package/dist/cli.js +904 -321
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -408,8 +408,11 @@ var init_store = __esm({
|
|
|
408
408
|
const res = await this.fetchImpl(signed);
|
|
409
409
|
if (res.status === 404) return null;
|
|
410
410
|
if (!res.ok) throw new Error(`artifact store: HEAD ${key} answered HTTP ${res.status}`);
|
|
411
|
-
const
|
|
412
|
-
|
|
411
|
+
const length = res.headers.get("content-length");
|
|
412
|
+
const size = length === null ? Number.NaN : Number(length);
|
|
413
|
+
if (!Number.isFinite(size) || size < 0) {
|
|
414
|
+
throw new Error(`artifact store: HEAD ${key} answered HTTP ${res.status} without a length`);
|
|
415
|
+
}
|
|
413
416
|
return { size, contentType: res.headers.get("content-type") ?? "application/octet-stream" };
|
|
414
417
|
}
|
|
415
418
|
async get(key) {
|
|
@@ -4136,6 +4139,7 @@ var init_residentSteps = __esm({
|
|
|
4136
4139
|
"ops-dir": "preparing a directory",
|
|
4137
4140
|
"stage-dir": "preparing a directory",
|
|
4138
4141
|
stat: "checking a file",
|
|
4142
|
+
"boot-id": "reading the container's identity",
|
|
4139
4143
|
touch: "touching a marker",
|
|
4140
4144
|
nproc: "counting CPUs",
|
|
4141
4145
|
mutex_wait: "waiting for the workspace",
|
|
@@ -7993,6 +7997,9 @@ function parseExitPrefix(output) {
|
|
|
7993
7997
|
if (!m) return { failed: false, exitCode: 0 };
|
|
7994
7998
|
return /^\d+$/.test(m[1]) ? { failed: true, exitCode: Number(m[1]) } : { failed: true };
|
|
7995
7999
|
}
|
|
8000
|
+
function toolTextFailed(output) {
|
|
8001
|
+
return /^\s*error:/i.test(stripAnsi(output));
|
|
8002
|
+
}
|
|
7996
8003
|
function capClean(clean) {
|
|
7997
8004
|
const text = clean.trim();
|
|
7998
8005
|
if (text.length <= TOOL_OUTPUT_CAP) return text;
|
|
@@ -9413,7 +9420,7 @@ async function parseResidentBody(res) {
|
|
|
9413
9420
|
}
|
|
9414
9421
|
return sanitizeResidentBody(data);
|
|
9415
9422
|
}
|
|
9416
|
-
var DETACH_TIMEOUT_MS, ResidentNeedsRefError, ResidentOperations, ResidentExecutor;
|
|
9423
|
+
var DETACH_TIMEOUT_MS, ResidentNeedsRefError, ResidentReuseRefusedError, ResidentOperations, ResidentExecutor;
|
|
9417
9424
|
var init_resident = __esm({
|
|
9418
9425
|
"../../src/execution/resident.ts"() {
|
|
9419
9426
|
"use strict";
|
|
@@ -9445,6 +9452,16 @@ var init_resident = __esm({
|
|
|
9445
9452
|
defaultRef;
|
|
9446
9453
|
needs = "ref";
|
|
9447
9454
|
};
|
|
9455
|
+
ResidentReuseRefusedError = class extends Error {
|
|
9456
|
+
constructor(resource, why) {
|
|
9457
|
+
super(`resident attach: ${resource} cannot reuse this thread's worktree (${why})`);
|
|
9458
|
+
this.resource = resource;
|
|
9459
|
+
this.why = why;
|
|
9460
|
+
this.name = "ResidentReuseRefusedError";
|
|
9461
|
+
}
|
|
9462
|
+
resource;
|
|
9463
|
+
why;
|
|
9464
|
+
};
|
|
9448
9465
|
ResidentOperations = class {
|
|
9449
9466
|
constructor(opts) {
|
|
9450
9467
|
this.opts = opts;
|
|
@@ -9602,6 +9619,7 @@ var init_resident = __esm({
|
|
|
9602
9619
|
if (this.opts.refHint) body.refHint = this.opts.refHint;
|
|
9603
9620
|
if (this.opts.readonly) body.readonly = true;
|
|
9604
9621
|
if (this.opts.sha) body.sha = this.opts.sha;
|
|
9622
|
+
if (this.opts.reuse) body.reuse = true;
|
|
9605
9623
|
const answered = await this.call("/attach", body, timeoutMs, void 0, span);
|
|
9606
9624
|
const data = answered.data;
|
|
9607
9625
|
const status3 = answered.status === 200 && typeof data.error === "string" && typeof data.status === "number" ? data.status : answered.status;
|
|
@@ -9615,6 +9633,7 @@ var init_resident = __esm({
|
|
|
9615
9633
|
sha: data.sha,
|
|
9616
9634
|
...typeof data.workspace === "string" && data.workspace ? { workspace: data.workspace } : {},
|
|
9617
9635
|
...typeof data.user === "string" && data.user ? { user: data.user } : {},
|
|
9636
|
+
...typeof data.container === "string" && data.container ? { container: data.container } : {},
|
|
9618
9637
|
...trace.length > 0 ? { trace } : {},
|
|
9619
9638
|
...typeof data.attachMs === "number" && Number.isFinite(data.attachMs) ? { attachMs: data.attachMs } : {}
|
|
9620
9639
|
};
|
|
@@ -9634,6 +9653,8 @@ var init_resident = __esm({
|
|
|
9634
9653
|
const defaultRef = typeof data.defaultRef === "string" && data.defaultRef ? data.defaultRef : void 0;
|
|
9635
9654
|
return traced(new ResidentNeedsRefError(this.opts.resource, defaultRef));
|
|
9636
9655
|
}
|
|
9656
|
+
if (status3 === 409 && data.needs === "recreate")
|
|
9657
|
+
return traced(new ResidentReuseRefusedError(this.opts.resource, err2));
|
|
9637
9658
|
if (status3 === 404) return traced(new Error(`resident attach: ${this.opts.resource} is not onboarded (${err2})`));
|
|
9638
9659
|
return traced(new Error(`resident attach failed for ${this.opts.resource}: ${err2}`));
|
|
9639
9660
|
}
|
|
@@ -9988,11 +10009,33 @@ var init_githubApp = __esm({
|
|
|
9988
10009
|
// ../../src/execution/factory.ts
|
|
9989
10010
|
import { resolve as resolve7 } from "node:path";
|
|
9990
10011
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
10012
|
+
function workspaceBindingOf(value) {
|
|
10013
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
10014
|
+
const v = value;
|
|
10015
|
+
if (typeof v.backend !== "string" || !BACKENDS.includes(v.backend)) return void 0;
|
|
10016
|
+
return {
|
|
10017
|
+
backend: v.backend,
|
|
10018
|
+
...typeof v.workspace === "string" && v.workspace ? { workspace: v.workspace } : {},
|
|
10019
|
+
...typeof v.user === "string" && v.user ? { user: v.user } : {},
|
|
10020
|
+
...typeof v.container === "string" && v.container ? { container: v.container } : {}
|
|
10021
|
+
};
|
|
10022
|
+
}
|
|
10023
|
+
function workspaceBindingFor(selection, machine = "repo-resident") {
|
|
10024
|
+
if (machine === "none" || selection.backend === void 0) return void 0;
|
|
10025
|
+
const b = selection.binding;
|
|
10026
|
+
return {
|
|
10027
|
+
backend: selection.backend,
|
|
10028
|
+
...b?.workspace !== void 0 ? { workspace: b.workspace } : {},
|
|
10029
|
+
...b?.user !== void 0 ? { user: b.user } : {},
|
|
10030
|
+
...b?.container !== void 0 ? { container: b.container } : {}
|
|
10031
|
+
};
|
|
10032
|
+
}
|
|
9991
10033
|
async function makeExecutor(opts, ctx, span) {
|
|
9992
10034
|
const machine = ctx.profile.machine;
|
|
9993
10035
|
if (machine === "none") {
|
|
9994
10036
|
return { executor: new NullExecutor(ctx.agent.name), backend: "local" };
|
|
9995
10037
|
}
|
|
10038
|
+
if (ctx.reattach !== void 0) return reattachWorkspace(opts, ctx, ctx.reattach, span);
|
|
9996
10039
|
if (machine === "blank") {
|
|
9997
10040
|
return {
|
|
9998
10041
|
executor: await makePerThreadExecutor(opts, { threadKey: ctx.threadKey, resolveEnvs: async () => ({}) }),
|
|
@@ -10050,6 +10093,58 @@ async function makeExecutor(opts, ctx, span) {
|
|
|
10050
10093
|
...failedAttach ? { trace: failedAttach.steps } : {}
|
|
10051
10094
|
};
|
|
10052
10095
|
}
|
|
10096
|
+
async function reattachWorkspace(opts, ctx, recorded, span) {
|
|
10097
|
+
const refuse3 = (why) => new WorkspaceReattachRefusedError(recorded, oneLine(why));
|
|
10098
|
+
if (recorded.backend !== "resident") {
|
|
10099
|
+
const input = ctx.profile.machine === "blank" ? { threadKey: ctx.threadKey, resolveEnvs: async () => ({}) } : perThreadCheckout(ctx);
|
|
10100
|
+
return { executor: await makePerThreadExecutor(opts, input), backend: perThreadBackend(opts) };
|
|
10101
|
+
}
|
|
10102
|
+
const resident = opts.execution?.resident;
|
|
10103
|
+
if (!resident) throw refuse3("no resident backend is configured in this process");
|
|
10104
|
+
if (!ctx.repo) throw refuse3("the run's row names no repository to re-attach on");
|
|
10105
|
+
const tokenEnv = resident.tokenEnv ?? "RESIDENT_OPERATOR_TOKEN";
|
|
10106
|
+
const token2 = processSecrets.named(tokenEnv);
|
|
10107
|
+
if (!token2) throw new Error(`execution.resident is configured but ${tokenEnv} is not set`);
|
|
10108
|
+
const resource = repoResourceId(ctx.repo);
|
|
10109
|
+
const probe = await probeResident(resident, token2, resource, span);
|
|
10110
|
+
if (probe.kind === "unreachable") throw refuse3(`resident unreachable (${probe.error})`);
|
|
10111
|
+
if (!isServiceable(probe.state, probe.reason))
|
|
10112
|
+
throw refuse3(`resident ${probe.state}${probe.reason ? ` (${probe.reason})` : ""}`);
|
|
10113
|
+
const nonWarm = probe.state === "warm" ? void 0 : oneLine(`${probe.state}${probe.reason ? ` (${probe.reason})` : ""}`);
|
|
10114
|
+
const executor = new ResidentExecutor({
|
|
10115
|
+
baseUrl: resident.baseUrl,
|
|
10116
|
+
token: token2.reveal(),
|
|
10117
|
+
resource,
|
|
10118
|
+
threadKey: ctx.threadKey,
|
|
10119
|
+
refHint: ctx.ref,
|
|
10120
|
+
readonly: ctx.profile.identity === "read" ? true : void 0,
|
|
10121
|
+
sha: ctx.headSha,
|
|
10122
|
+
reuse: true
|
|
10123
|
+
});
|
|
10124
|
+
let binding;
|
|
10125
|
+
try {
|
|
10126
|
+
binding = await executor.attach(span);
|
|
10127
|
+
} catch (err2) {
|
|
10128
|
+
throw refuse3(err2 instanceof Error ? err2.message : String(err2));
|
|
10129
|
+
}
|
|
10130
|
+
const same = (recordedValue, answered) => recordedValue === void 0 || answered === void 0 || recordedValue === answered;
|
|
10131
|
+
if (!same(recorded.workspace, binding.workspace) || !same(recorded.user, binding.user)) {
|
|
10132
|
+
const name = (workspace, user) => `${workspace ?? "(unnamed)"} as ${user ?? "(unnamed)"}`;
|
|
10133
|
+
throw refuse3(
|
|
10134
|
+
`the resident's worktree for this thread is ${name(binding.workspace, binding.user)}, not the run's recorded ${name(recorded.workspace, recorded.user)}`
|
|
10135
|
+
);
|
|
10136
|
+
}
|
|
10137
|
+
const where = `${resource.replace(/^repo:/, "")} \xB7 ${binding.ref}@${binding.sha.slice(0, 7)}`;
|
|
10138
|
+
return {
|
|
10139
|
+
executor,
|
|
10140
|
+
resident: true,
|
|
10141
|
+
backend: "resident",
|
|
10142
|
+
...binding.trace ? { trace: binding.trace } : {},
|
|
10143
|
+
...binding.attachMs !== void 0 ? { attachMs: binding.attachMs } : {},
|
|
10144
|
+
binding,
|
|
10145
|
+
note: nonWarm ? `resident ${nonWarm} \xB7 ${where} \xB7 re-attached to the run's worktree` : `resident \xB7 ${where} \xB7 re-attached to the run's worktree`
|
|
10146
|
+
};
|
|
10147
|
+
}
|
|
10053
10148
|
async function openResident(opts, nonWarm, span) {
|
|
10054
10149
|
let executor = new ResidentExecutor(opts);
|
|
10055
10150
|
let binding;
|
|
@@ -10200,7 +10295,7 @@ function perThreadBackend(opts) {
|
|
|
10200
10295
|
const type = opts.execution?.type ?? "local";
|
|
10201
10296
|
return type === "e2b" ? "e2b" : type === "cloudflare" ? "sandbox" : "local";
|
|
10202
10297
|
}
|
|
10203
|
-
var PROBE_OUTAGE_WINDOW_MS, probeOutage, NullExecutor;
|
|
10298
|
+
var BACKENDS, WorkspaceReattachRefusedError, PROBE_OUTAGE_WINDOW_MS, probeOutage, NullExecutor;
|
|
10204
10299
|
var init_factory = __esm({
|
|
10205
10300
|
"../../src/execution/factory.ts"() {
|
|
10206
10301
|
"use strict";
|
|
@@ -10215,6 +10310,17 @@ var init_factory = __esm({
|
|
|
10215
10310
|
init_residentState();
|
|
10216
10311
|
init_clock();
|
|
10217
10312
|
init_secrets2();
|
|
10313
|
+
BACKENDS = ["local", "resident", "sandbox", "e2b"];
|
|
10314
|
+
WorkspaceReattachRefusedError = class extends Error {
|
|
10315
|
+
constructor(recorded, why) {
|
|
10316
|
+
super(`the run's workspace on the ${recorded.backend} backend could not be re-attached: ${why}`);
|
|
10317
|
+
this.recorded = recorded;
|
|
10318
|
+
this.why = why;
|
|
10319
|
+
this.name = "WorkspaceReattachRefusedError";
|
|
10320
|
+
}
|
|
10321
|
+
recorded;
|
|
10322
|
+
why;
|
|
10323
|
+
};
|
|
10218
10324
|
PROBE_OUTAGE_WINDOW_MS = 3e4;
|
|
10219
10325
|
NullExecutor = class {
|
|
10220
10326
|
constructor(agentName) {
|
|
@@ -20502,7 +20608,7 @@ function createRunsService(deps) {
|
|
|
20502
20608
|
const analyze = deps.analyze ?? ((events, opts) => analyzeRunFriction(events, opts));
|
|
20503
20609
|
const clock3 = deps.clock ?? systemClock;
|
|
20504
20610
|
const warn = deps.warn ?? ((m) => console.warn(m));
|
|
20505
|
-
const
|
|
20611
|
+
const describe12 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
20506
20612
|
let listing;
|
|
20507
20613
|
const liveRows = () => {
|
|
20508
20614
|
if (!ledger) return null;
|
|
@@ -20522,7 +20628,7 @@ function createRunsService(deps) {
|
|
|
20522
20628
|
try {
|
|
20523
20629
|
rows = await pending2;
|
|
20524
20630
|
} catch (err2) {
|
|
20525
|
-
warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${
|
|
20631
|
+
warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${describe12(err2)}`);
|
|
20526
20632
|
return [];
|
|
20527
20633
|
}
|
|
20528
20634
|
const foreign = rows.filter((row) => !registry.getById(row.runId));
|
|
@@ -20532,7 +20638,7 @@ function createRunsService(deps) {
|
|
|
20532
20638
|
try {
|
|
20533
20639
|
events = await ledger.readEvents(row.runId);
|
|
20534
20640
|
} catch (err2) {
|
|
20535
|
-
warn(`[runs] run ledger events read failed for ${row.runId}: ${
|
|
20641
|
+
warn(`[runs] run ledger events read failed for ${row.runId}: ${describe12(err2)}`);
|
|
20536
20642
|
}
|
|
20537
20643
|
return ledgerView(row, events);
|
|
20538
20644
|
})
|
|
@@ -20546,7 +20652,7 @@ function createRunsService(deps) {
|
|
|
20546
20652
|
if (!row) return null;
|
|
20547
20653
|
return { row, events: await ledger.readEvents(id) };
|
|
20548
20654
|
} catch (err2) {
|
|
20549
|
-
warn(`[runs] run ledger read failed for ${id}: ${
|
|
20655
|
+
warn(`[runs] run ledger read failed for ${id}: ${describe12(err2)}`);
|
|
20550
20656
|
return null;
|
|
20551
20657
|
}
|
|
20552
20658
|
};
|
|
@@ -20564,7 +20670,7 @@ function createRunsService(deps) {
|
|
|
20564
20670
|
row = await storeSummary(id);
|
|
20565
20671
|
} catch (err2) {
|
|
20566
20672
|
warn(
|
|
20567
|
-
`[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${
|
|
20673
|
+
`[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${describe12(err2)}`
|
|
20568
20674
|
);
|
|
20569
20675
|
return {};
|
|
20570
20676
|
}
|
|
@@ -20735,7 +20841,7 @@ function createRunsService(deps) {
|
|
|
20735
20841
|
const r = await ledger.requestStop(id, mode);
|
|
20736
20842
|
if (r.ok) return { ok: true, value: { id, mode, state: "stopping" } };
|
|
20737
20843
|
} catch (err2) {
|
|
20738
|
-
warn(`[runs] run ledger stop failed for ${id}: ${
|
|
20844
|
+
warn(`[runs] run ledger stop failed for ${id}: ${describe12(err2)}`);
|
|
20739
20845
|
}
|
|
20740
20846
|
}
|
|
20741
20847
|
return await storeSummary(id) ? conflict : notFound;
|
|
@@ -25933,7 +26039,11 @@ function fileNameOf2(path) {
|
|
|
25933
26039
|
return segments2[segments2.length - 1] ?? path;
|
|
25934
26040
|
}
|
|
25935
26041
|
function putCommandFor(path, contentType, url) {
|
|
25936
|
-
return `curl -fsS -T ${shellQuote(path)} -H ${shellQuote(`Content-Type: ${contentType}`)} ${shellQuote(url)}`;
|
|
26042
|
+
return `curl -fsS -T ${shellQuote(path)} -H ${shellQuote(`Content-Type: ${contentType}`)} -o /dev/null -w ${shellQuote(PUT_REPORT_FORMAT)} ${shellQuote(url)}`;
|
|
26043
|
+
}
|
|
26044
|
+
function parsePutReport(output) {
|
|
26045
|
+
const m = /(\d{3}) (\d{1,15})\s*$/.exec(output);
|
|
26046
|
+
return m ? { httpCode: Number(m[1]), sentBytes: Number(m[2]) } : null;
|
|
25937
26047
|
}
|
|
25938
26048
|
function postCommandFor(path, url) {
|
|
25939
26049
|
return `curl -fsS --upload-file ${shellQuote(path)} -X POST ${shellQuote(url)}`;
|
|
@@ -25968,11 +26078,14 @@ async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
|
25968
26078
|
if (parseExitPrefix(putOut).failed) {
|
|
25969
26079
|
return `error: the upload of ${name} to the artifact store failed: ${putOut.trim()}`;
|
|
25970
26080
|
}
|
|
26081
|
+
const report2 = parsePutReport(putOut);
|
|
26082
|
+
const curlSaid = report2 ? `; curl reported HTTP ${report2.httpCode} after sending ${report2.sentBytes} bytes` : "";
|
|
25971
26083
|
const head = await artifacts.store.head(key);
|
|
25972
|
-
if (!head)
|
|
25973
|
-
return `error: the artifact store holds nothing under ${key} after the upload of ${name} \u2014 nothing was posted`;
|
|
26084
|
+
if (!head) {
|
|
26085
|
+
return `error: the artifact store holds nothing under ${key} after the upload of ${name}${curlSaid} \u2014 nothing was posted`;
|
|
26086
|
+
}
|
|
25974
26087
|
if (head.size !== size) {
|
|
25975
|
-
return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured \u2014 nothing was posted`;
|
|
26088
|
+
return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured${curlSaid} \u2014 nothing was posted`;
|
|
25976
26089
|
}
|
|
25977
26090
|
ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType });
|
|
25978
26091
|
if (!ctx.uploadTicket) {
|
|
@@ -26000,7 +26113,7 @@ async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
|
26000
26113
|
}
|
|
26001
26114
|
return `attached ${name} (${size} bytes) to the conversation and the run page`;
|
|
26002
26115
|
}
|
|
26003
|
-
var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe2, secs, attachFileTool;
|
|
26116
|
+
var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe2, secs, PUT_REPORT_FORMAT, attachFileTool;
|
|
26004
26117
|
var init_attach = __esm({
|
|
26005
26118
|
"../../src/tools/attach.ts"() {
|
|
26006
26119
|
"use strict";
|
|
@@ -26016,8 +26129,10 @@ var init_attach = __esm({
|
|
|
26016
26129
|
STAT_TIMEOUT_MS = 3e4;
|
|
26017
26130
|
describe2 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
26018
26131
|
secs = (ms) => Math.max(0, Math.round(ms / 1e3));
|
|
26132
|
+
PUT_REPORT_FORMAT = "\\n%{http_code} %{size_upload}";
|
|
26019
26133
|
attachFileTool = {
|
|
26020
26134
|
name: "attach_file",
|
|
26135
|
+
failsInText: true,
|
|
26021
26136
|
description: `Post a file from the workspace into the conversation so the person sees it inline \u2014 a screenshot (e.g. from \`playwright screenshot\`), a rendered PDF, a recording, a log. Use it whenever you produce an image worth showing: a link to a file is not a picture. Whole files only: up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) where the artifact store is configured, ${MAX_READ_BYTES} bytes otherwise \u2014 the result says which.`,
|
|
26022
26137
|
inputSchema: {
|
|
26023
26138
|
type: "object",
|
|
@@ -26557,6 +26672,7 @@ var init_runs2 = __esm({
|
|
|
26557
26672
|
CHILDREN_PAGES_MAX = 25;
|
|
26558
26673
|
spawnRunTool = {
|
|
26559
26674
|
name: "spawn_run",
|
|
26675
|
+
failsInText: true,
|
|
26560
26676
|
description: `Start a child run as the person who asked you: an ordinary Switchboard run of the named preset, in a thread of its own in this channel, under their permissions \u2014 what they could start by hand with \`agent:<preset>\`. The child is a reader of this conversation: it starts from this conversation's text so far (every user and assistant turn, never your tool calls or their results) plus \`prompt\`, its one new turn \u2014 so say what it should do. Presets a child can run: ${CHILD_PRESETS().join(", ")}; a preset that writes (${WRITE_PRESETS().join(", ")}) is refused \`spawn_identity\`. \`repo\` (owner/name) for a preset that works in a repository (${REPO_PRESETS().join(", ")}); \`budget\` narrows the child's wall clock in whole minutes (at least 2; it is also capped by what is left of yours). Returns the child's run id, its thread and a link, or a refusal by name: a preset or repository the requester may not use, a boundary the child's profile exceeds, the fan-out cap, or a channel that cannot open a thread. A child cannot spawn children.`,
|
|
26561
26677
|
inputSchema: {
|
|
26562
26678
|
type: "object",
|
|
@@ -26589,9 +26705,10 @@ var init_runs2 = __esm({
|
|
|
26589
26705
|
request.budget = budget;
|
|
26590
26706
|
}
|
|
26591
26707
|
const spawn7 = ctx.spawn ?? nullSpawnCapability;
|
|
26708
|
+
const conversation = ctx.conversation ? await ctx.conversation() : void 0;
|
|
26592
26709
|
const out = await spawn7.spawn(request, {
|
|
26593
26710
|
remainingMs: ctx.remainingMs?.() ?? Number.POSITIVE_INFINITY,
|
|
26594
|
-
...
|
|
26711
|
+
...conversation ? { conversation } : {}
|
|
26595
26712
|
});
|
|
26596
26713
|
if (out.kind === "refused") return `spawn refused (${out.reason}): ${out.message}`;
|
|
26597
26714
|
return `spawned a ${preset} run: ${out.runId} in thread ${out.threadKey}${out.url ? ` (${out.url})` : ""} \u2014 it runs on its own; await_runs waits for it and returns its reply; get_run_status ${out.runId} answers its progress meanwhile.`;
|
|
@@ -26599,6 +26716,7 @@ var init_runs2 = __esm({
|
|
|
26599
26716
|
};
|
|
26600
26717
|
sendToRunTool = {
|
|
26601
26718
|
name: "send_to_run",
|
|
26719
|
+
failsInText: true,
|
|
26602
26720
|
description: "Steer one of your live children: `text` reaches it as a follow-up at its next step, the way a reply in its thread would \u2014 sent as the person who asked you, and recorded on the child's run page as coming from this run. Use it when the request changed or a child is heading the wrong way. Refused by name: `not_found` (an unknown id, or a run the requester may not read), `not_child` (a run you did not spawn), `not_live` (it already ended \u2014 its status is given, nothing is sent; get_run_status has its reply), or the child's agent's allowlist. A child that finishes before its next step never reads it.",
|
|
26603
26721
|
inputSchema: {
|
|
26604
26722
|
type: "object",
|
|
@@ -26638,6 +26756,7 @@ var init_runs2 = __esm({
|
|
|
26638
26756
|
AWAIT_IDS_MAX = 50;
|
|
26639
26757
|
awaitRunsTool = {
|
|
26640
26758
|
name: "await_runs",
|
|
26759
|
+
failsInText: true,
|
|
26641
26760
|
description: "Wait for runs \u2014 normally your children \u2014 to end, and get each one's end as data: its terminal status (completed, failed, refused, stopped_soft, stopped_hard, interrupted) with its final reply wrapped as untrusted content; `running` for one still live when the wait was cut; `not_found` for an unknown id or one the requester may not read. The wait ends at the first of: every named run ended; `timeoutMinutes` (optional, whole minutes); the edge of your own budget (a minute before your clock runs out \u2014 write up what came back and name what is still running, which keeps running); a stop; a follow-up landing in this thread (it rides your next turn). `ended` says which. An interrupted child is reported, never restarted. A child is its thread: when a person's reply in a child's thread started a later run there, the child's row follows that run \u2014 `continuedBy` names it, and `status` and `finalReply` are its \u2014 never the child's first reply alone.",
|
|
26642
26761
|
inputSchema: {
|
|
26643
26762
|
type: "object",
|
|
@@ -26768,6 +26887,252 @@ var init_runs2 = __esm({
|
|
|
26768
26887
|
}
|
|
26769
26888
|
});
|
|
26770
26889
|
|
|
26890
|
+
// ../../src/core/dispatch/staging.ts
|
|
26891
|
+
function stagedBasename(index, name) {
|
|
26892
|
+
return `${index}-${safeBasename(name)}`;
|
|
26893
|
+
}
|
|
26894
|
+
function pullCommandFor(url, basename4) {
|
|
26895
|
+
return `mkdir -p ${ATTACHMENTS_DIR} && curl -fsS -o ${shellQuote(`${ATTACHMENTS_DIR}/${basename4}`)} ${shellQuote(url)}`;
|
|
26896
|
+
}
|
|
26897
|
+
function excludeCommand() {
|
|
26898
|
+
const entry = shellQuote(`${ATTACHMENTS_DIR}/`);
|
|
26899
|
+
return `mkdir -p .git/info && { grep -qxF ${entry} .git/info/exclude 2>/dev/null || echo ${entry} >> .git/info/exclude; }`;
|
|
26900
|
+
}
|
|
26901
|
+
function formatSize(bytes) {
|
|
26902
|
+
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
|
|
26903
|
+
if (bytes >= 1e6) return `${Math.round(bytes / 1e6)} MB`;
|
|
26904
|
+
if (bytes >= 1e3) return `${Math.round(bytes / 1e3)} KB`;
|
|
26905
|
+
return `${bytes} B`;
|
|
26906
|
+
}
|
|
26907
|
+
function attachmentsLine(outcomes) {
|
|
26908
|
+
const landed = outcomes.filter((o) => o.error === void 0);
|
|
26909
|
+
const failed = outcomes.filter((o) => o.error !== void 0);
|
|
26910
|
+
const parts = [];
|
|
26911
|
+
if (landed.length > 0) {
|
|
26912
|
+
parts.push(
|
|
26913
|
+
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map(
|
|
26914
|
+
(o) => `${o.basename} (${formatSize(o.file.size)}, ${o.file.type}${o.earlier ? ", from earlier in the thread" : ""})`
|
|
26915
|
+
).join(", ")}`
|
|
26916
|
+
);
|
|
26917
|
+
}
|
|
26918
|
+
for (const o of failed) parts.push(`${o.file.name} could not be staged: ${o.error}`);
|
|
26919
|
+
return parts.join(". ");
|
|
26920
|
+
}
|
|
26921
|
+
function noWorkspaceLine(files) {
|
|
26922
|
+
return files.map(
|
|
26923
|
+
(f) => `this agent has no workspace for ${f.name} (${formatSize(f.size)}, ${f.type}); ask \`agent:coding\` to work with it`
|
|
26924
|
+
).join(". ");
|
|
26925
|
+
}
|
|
26926
|
+
function stagingIndex() {
|
|
26927
|
+
let n2 = 0;
|
|
26928
|
+
return () => ++n2;
|
|
26929
|
+
}
|
|
26930
|
+
async function copyStaged(files, deps) {
|
|
26931
|
+
return Promise.all(
|
|
26932
|
+
files.map(async (file) => {
|
|
26933
|
+
const index = deps.nextIndex();
|
|
26934
|
+
const basename4 = stagedBasename(index, file.name);
|
|
26935
|
+
const key = inboundKey(deps.threadKey, file.messageId, index, file.name);
|
|
26936
|
+
try {
|
|
26937
|
+
await deps.store.copyFromUrl({ url: file.url, size: file.size, key });
|
|
26938
|
+
deps.publish?.({
|
|
26939
|
+
type: "artifact",
|
|
26940
|
+
direction: "in",
|
|
26941
|
+
key,
|
|
26942
|
+
name: file.name,
|
|
26943
|
+
size: file.size,
|
|
26944
|
+
contentType: file.type
|
|
26945
|
+
});
|
|
26946
|
+
return { file, basename: basename4, key };
|
|
26947
|
+
} catch (err2) {
|
|
26948
|
+
return { file, basename: basename4, key, error: `the copy into the store failed: ${describe4(err2)}` };
|
|
26949
|
+
}
|
|
26950
|
+
})
|
|
26951
|
+
);
|
|
26952
|
+
}
|
|
26953
|
+
async function pullStaged(outcomes, deps) {
|
|
26954
|
+
const landed = outcomes.filter((o) => o.error === void 0);
|
|
26955
|
+
if (landed.length === 0) return [...outcomes];
|
|
26956
|
+
const timeoutMs = deps.timeoutMs ?? BASH_TIMEOUT_MAX_MS;
|
|
26957
|
+
const opts = { timeoutMs, ...deps.signal ? { signal: deps.signal } : {} };
|
|
26958
|
+
if (deps.resident) await deps.executor.exec(excludeCommand(), opts);
|
|
26959
|
+
const pulled = /* @__PURE__ */ new Map();
|
|
26960
|
+
for (const o of landed) {
|
|
26961
|
+
const url = await deps.store.presignGet(o.key);
|
|
26962
|
+
const out = await deps.executor.exec(pullCommandFor(url, o.basename), opts);
|
|
26963
|
+
pulled.set(o.key, parseExitPrefix(out).failed ? `the pull into the workspace failed: ${out.trim()}` : void 0);
|
|
26964
|
+
}
|
|
26965
|
+
return outcomes.map((o) => {
|
|
26966
|
+
const error = o.error ?? pulled.get(o.key);
|
|
26967
|
+
if (error !== void 0) return { ...o, error };
|
|
26968
|
+
const { error: _dropped, ...landedOutcome } = o;
|
|
26969
|
+
return landedOutcome;
|
|
26970
|
+
});
|
|
26971
|
+
}
|
|
26972
|
+
function stageThreadArtifacts(artifacts, deps) {
|
|
26973
|
+
const outcomes = [];
|
|
26974
|
+
for (const a of artifacts) {
|
|
26975
|
+
const file = { name: a.name, size: a.size, type: a.contentType };
|
|
26976
|
+
if (a.held === void 0) {
|
|
26977
|
+
outcomes.push({
|
|
26978
|
+
file,
|
|
26979
|
+
basename: safeBasename(a.name),
|
|
26980
|
+
key: a.key,
|
|
26981
|
+
earlier: true,
|
|
26982
|
+
error: "the store could not be asked whether it still holds it"
|
|
26983
|
+
});
|
|
26984
|
+
continue;
|
|
26985
|
+
}
|
|
26986
|
+
if (!a.held) {
|
|
26987
|
+
outcomes.push({
|
|
26988
|
+
file,
|
|
26989
|
+
basename: safeBasename(a.name),
|
|
26990
|
+
key: a.key,
|
|
26991
|
+
earlier: true,
|
|
26992
|
+
error: "the store no longer holds it (its retention passed)"
|
|
26993
|
+
});
|
|
26994
|
+
continue;
|
|
26995
|
+
}
|
|
26996
|
+
const basename4 = stagedBasename(deps.nextIndex(), a.name);
|
|
26997
|
+
deps.publish?.({
|
|
26998
|
+
type: "artifact",
|
|
26999
|
+
direction: "in",
|
|
27000
|
+
key: a.key,
|
|
27001
|
+
name: a.name,
|
|
27002
|
+
size: a.size,
|
|
27003
|
+
contentType: a.contentType
|
|
27004
|
+
});
|
|
27005
|
+
outcomes.push({ file, basename: basename4, key: a.key, earlier: true });
|
|
27006
|
+
}
|
|
27007
|
+
return outcomes;
|
|
27008
|
+
}
|
|
27009
|
+
async function stageIntoWorkspace(files, deps) {
|
|
27010
|
+
const outcomes = await pullStaged(await copyStaged(files, deps), deps);
|
|
27011
|
+
return { line: attachmentsLine(outcomes), outcomes };
|
|
27012
|
+
}
|
|
27013
|
+
var ATTACHMENTS_DIR, WorkspaceFiles, describe4;
|
|
27014
|
+
var init_staging = __esm({
|
|
27015
|
+
"../../src/core/dispatch/staging.ts"() {
|
|
27016
|
+
"use strict";
|
|
27017
|
+
init_keys();
|
|
27018
|
+
init_bashTimeout();
|
|
27019
|
+
init_shellQuote();
|
|
27020
|
+
init_runEvents();
|
|
27021
|
+
ATTACHMENTS_DIR = "attachments";
|
|
27022
|
+
WorkspaceFiles = class {
|
|
27023
|
+
paths = /* @__PURE__ */ new Map();
|
|
27024
|
+
/** Every outcome that landed: its key now names a workspace path. */
|
|
27025
|
+
record(outcomes) {
|
|
27026
|
+
for (const o of outcomes) if (o.error === void 0) this.paths.set(o.key, `${ATTACHMENTS_DIR}/${o.basename}`);
|
|
27027
|
+
}
|
|
27028
|
+
pathOf(key) {
|
|
27029
|
+
return this.paths.get(key);
|
|
27030
|
+
}
|
|
27031
|
+
};
|
|
27032
|
+
describe4 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
27033
|
+
}
|
|
27034
|
+
});
|
|
27035
|
+
|
|
27036
|
+
// ../../src/core/dispatch/threadAssets.ts
|
|
27037
|
+
function threadAssetsOf(runsOldestFirst) {
|
|
27038
|
+
const seen = /* @__PURE__ */ new Set();
|
|
27039
|
+
const out = [];
|
|
27040
|
+
for (const run2 of runsOldestFirst) {
|
|
27041
|
+
for (const e of run2.events) {
|
|
27042
|
+
if (e.type !== "artifact" || seen.has(e.key)) continue;
|
|
27043
|
+
seen.add(e.key);
|
|
27044
|
+
out.push({
|
|
27045
|
+
key: e.key,
|
|
27046
|
+
name: e.name,
|
|
27047
|
+
size: e.size,
|
|
27048
|
+
contentType: e.contentType,
|
|
27049
|
+
direction: e.direction,
|
|
27050
|
+
runId: run2.id,
|
|
27051
|
+
...e.seq !== void 0 ? { seq: e.seq } : {}
|
|
27052
|
+
});
|
|
27053
|
+
}
|
|
27054
|
+
}
|
|
27055
|
+
return out;
|
|
27056
|
+
}
|
|
27057
|
+
async function readThreadAssets(deps, threadKey, warn = (line3) => console.warn(line3)) {
|
|
27058
|
+
const runs = [];
|
|
27059
|
+
let before;
|
|
27060
|
+
try {
|
|
27061
|
+
for (; ; ) {
|
|
27062
|
+
const page2 = await deps.runs.listRuns({
|
|
27063
|
+
status: "all",
|
|
27064
|
+
visibleTo: { kind: "all" },
|
|
27065
|
+
threadKey,
|
|
27066
|
+
limit: THREAD_ASSETS_PAGE,
|
|
27067
|
+
...before !== void 0 ? { before: before.finishedAt, beforeId: before.id } : {}
|
|
27068
|
+
});
|
|
27069
|
+
runs.push(...page2.runs);
|
|
27070
|
+
if (page2.storeUnavailable) {
|
|
27071
|
+
warn(
|
|
27072
|
+
`[thread] ${threadKey}: listing its runs for their files answered live rows only after ${runs.length} run(s) \u2014 the run store was unavailable`
|
|
27073
|
+
);
|
|
27074
|
+
break;
|
|
27075
|
+
}
|
|
27076
|
+
if (page2.nextBefore === void 0) break;
|
|
27077
|
+
before = page2.nextBefore;
|
|
27078
|
+
}
|
|
27079
|
+
} catch (err2) {
|
|
27080
|
+
warn(
|
|
27081
|
+
`[thread] ${threadKey}: listing its runs for their files stopped after ${runs.length} run(s) \u2014 ${describe5(err2)}`
|
|
27082
|
+
);
|
|
27083
|
+
}
|
|
27084
|
+
const perRun = [];
|
|
27085
|
+
for (const run2 of runs.reverse()) {
|
|
27086
|
+
const events = [];
|
|
27087
|
+
let afterSeq;
|
|
27088
|
+
try {
|
|
27089
|
+
for (; ; ) {
|
|
27090
|
+
const r = await deps.runs.getRunEvents(run2.id, afterSeq === void 0 ? {} : { afterSeq });
|
|
27091
|
+
if (!r.ok) {
|
|
27092
|
+
warn(`[thread] ${run2.id}: reading its files stopped after ${events.length} event(s) \u2014 ${r.error}`);
|
|
27093
|
+
break;
|
|
27094
|
+
}
|
|
27095
|
+
events.push(...r.value.events);
|
|
27096
|
+
if (r.value.nextAfterSeq === void 0) break;
|
|
27097
|
+
afterSeq = r.value.nextAfterSeq;
|
|
27098
|
+
}
|
|
27099
|
+
} catch (err2) {
|
|
27100
|
+
warn(`[thread] ${run2.id}: reading its files failed after ${events.length} event(s) \u2014 ${describe5(err2)}`);
|
|
27101
|
+
}
|
|
27102
|
+
perRun.push({ id: run2.id, events });
|
|
27103
|
+
}
|
|
27104
|
+
return Promise.all(
|
|
27105
|
+
threadAssetsOf(perRun).map(async (asset) => {
|
|
27106
|
+
try {
|
|
27107
|
+
return { ...asset, held: await deps.store.head(asset.key) !== null };
|
|
27108
|
+
} catch (err2) {
|
|
27109
|
+
warn(`[thread] ${asset.key}: the store could not be asked whether it holds it \u2014 ${describe5(err2)}`);
|
|
27110
|
+
return asset;
|
|
27111
|
+
}
|
|
27112
|
+
})
|
|
27113
|
+
);
|
|
27114
|
+
}
|
|
27115
|
+
function describeAsset(asset, path) {
|
|
27116
|
+
const what = `${asset.name} (${formatSize(asset.size)}, ${asset.contentType})`;
|
|
27117
|
+
const origin = asset.direction === "in" ? "received on this thread" : `produced by run ${asset.runId}`;
|
|
27118
|
+
return `${what} \u2014 ${origin}; ${whereIs(asset, path)}`;
|
|
27119
|
+
}
|
|
27120
|
+
function whereIs(asset, path) {
|
|
27121
|
+
if (path !== void 0) return `in this workspace at ./${path}`;
|
|
27122
|
+
if (asset.held === false) return "no longer in the store (its retention passed)";
|
|
27123
|
+
if (asset.held === void 0) return "the store could not be asked whether it still holds it";
|
|
27124
|
+
return asset.direction === "out" ? "on that run's page, not in this workspace" : "in the store, not in this workspace";
|
|
27125
|
+
}
|
|
27126
|
+
var THREAD_ASSETS_PAGE, describe5;
|
|
27127
|
+
var init_threadAssets = __esm({
|
|
27128
|
+
"../../src/core/dispatch/threadAssets.ts"() {
|
|
27129
|
+
"use strict";
|
|
27130
|
+
init_staging();
|
|
27131
|
+
THREAD_ASSETS_PAGE = 50;
|
|
27132
|
+
describe5 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
27133
|
+
}
|
|
27134
|
+
});
|
|
27135
|
+
|
|
26771
27136
|
// ../../src/core/runLedger/sessionLog.ts
|
|
26772
27137
|
function sessionKey(threadKey, agent) {
|
|
26773
27138
|
return `${threadKey}:${agent ?? "-"}`;
|
|
@@ -26789,31 +27154,50 @@ var init_sessionLog = __esm({
|
|
|
26789
27154
|
});
|
|
26790
27155
|
|
|
26791
27156
|
// ../../src/tools/session.ts
|
|
26792
|
-
function sessionCapabilityFor(run2, ledger) {
|
|
27157
|
+
function sessionCapabilityFor(run2, ledger, assets) {
|
|
26793
27158
|
const session = run2?.session;
|
|
26794
27159
|
if (!session) return void 0;
|
|
26795
27160
|
return {
|
|
26796
27161
|
session,
|
|
26797
27162
|
search: (query, limit) => ledger.searchSession(session.key, query, limit),
|
|
26798
27163
|
readTurn: async (idx) => (await ledger.readSession(session.key, idx, idx)).messages[0],
|
|
27164
|
+
readConversation: async () => (await ledger.readSession(session.key, session.seedFrom)).messages,
|
|
26799
27165
|
readNotepad: () => ledger.readNotepad(session.key),
|
|
26800
|
-
writeNotepad: (text) => ledger.writeNotepad(session.key, text)
|
|
27166
|
+
writeNotepad: (text) => ledger.writeNotepad(session.key, text),
|
|
27167
|
+
...assets ? { assets: assets.read, workspacePathOf: assets.pathOf } : {}
|
|
27168
|
+
};
|
|
27169
|
+
}
|
|
27170
|
+
function assetView(asset, path) {
|
|
27171
|
+
return {
|
|
27172
|
+
name: asset.name,
|
|
27173
|
+
size: asset.size,
|
|
27174
|
+
type: asset.contentType,
|
|
27175
|
+
[asset.direction === "in" ? "received" : "produced"]: `run ${asset.runId}`,
|
|
27176
|
+
key: asset.key,
|
|
27177
|
+
where: whereIs(asset, path)
|
|
26801
27178
|
};
|
|
26802
27179
|
}
|
|
27180
|
+
function filesNamedIn(text, catalogue) {
|
|
27181
|
+
return catalogue.filter((a) => [a.name, safeBasename(a.name)].some((n2) => nameAt2(n2).test(text)));
|
|
27182
|
+
}
|
|
26803
27183
|
async function mayRead(ctx) {
|
|
26804
27184
|
if (!ctx.runs?.runId) return false;
|
|
26805
27185
|
const res = await ctx.runs.service.getRun(ctx.runs.runId);
|
|
26806
27186
|
return res.ok && authorize(ctx.runs.actor, "runs:read", runResource(res.value)).allow;
|
|
26807
27187
|
}
|
|
26808
|
-
var UNAVAILABLE3, DEFAULT_HITS, MAX_HITS, SNIPPET_CHARS, snippetOf, recallTool, notesTool, SESSION_TOOLS;
|
|
27188
|
+
var FILE_BEARING, nameAt2, UNAVAILABLE3, DEFAULT_HITS, MAX_HITS, SNIPPET_CHARS, snippetOf, recallTool, notesTool, SESSION_TOOLS;
|
|
26809
27189
|
var init_session = __esm({
|
|
26810
27190
|
"../../src/tools/session.ts"() {
|
|
26811
27191
|
"use strict";
|
|
27192
|
+
init_keys();
|
|
26812
27193
|
init_authorize();
|
|
27194
|
+
init_threadAssets();
|
|
26813
27195
|
init_runEvents();
|
|
26814
27196
|
init_sessionLog();
|
|
26815
27197
|
init_runRecord();
|
|
26816
27198
|
init_runsService();
|
|
27199
|
+
FILE_BEARING = /attachments\/|attach_file|attached files/i;
|
|
27200
|
+
nameAt2 = (name) => new RegExp(`(?<![A-Za-z0-9._])${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9._-])`);
|
|
26817
27201
|
UNAVAILABLE3 = "the session tools are not available in this context: this run has no session log.";
|
|
26818
27202
|
DEFAULT_HITS = 5;
|
|
26819
27203
|
MAX_HITS = 50;
|
|
@@ -26824,7 +27208,7 @@ var init_session = __esm({
|
|
|
26824
27208
|
};
|
|
26825
27209
|
recallTool = {
|
|
26826
27210
|
name: "recall",
|
|
26827
|
-
description: "Search this conversation's whole log \u2014 every turn of this thread on this agent, earlier runs' included and everything a compaction removed from your window \u2014 for words, and get the matching turns in relevance order with their turn numbers and a snippet; or pass `turn` to read one turn whole (a tool result, a command, a message). Use it when you need something said or seen earlier that is not in front of you: a failing test's name, a command's output, a decision. `limit` defaults to 5 (at most 50).",
|
|
27211
|
+
description: "Search this conversation's whole log \u2014 every turn of this thread on this agent, earlier runs' included and everything a compaction removed from your window \u2014 for words, and get the matching turns in relevance order with their turn numbers and a snippet; or pass `turn` to read one turn whole (a tool result, a command, a message). Use it when you need something said or seen earlier that is not in front of you: a failing test's name, a command's output, a decision. `limit` defaults to 5 (at most 50). A hit that names a file of this thread carries the file's key and where it is for this run; `assets: true` lists every file the thread received or its runs produced instead of searching.",
|
|
26828
27212
|
inputSchema: {
|
|
26829
27213
|
type: "object",
|
|
26830
27214
|
properties: {
|
|
@@ -26833,6 +27217,10 @@ var init_session = __esm({
|
|
|
26833
27217
|
turn: {
|
|
26834
27218
|
type: "integer",
|
|
26835
27219
|
description: "A turn number from an earlier recall: returns that turn whole instead of searching"
|
|
27220
|
+
},
|
|
27221
|
+
assets: {
|
|
27222
|
+
type: "boolean",
|
|
27223
|
+
description: "List the thread's files \u2014 received on its messages or produced by its runs \u2014 with their keys and where each is for this run, instead of searching"
|
|
26836
27224
|
}
|
|
26837
27225
|
}
|
|
26838
27226
|
},
|
|
@@ -26841,13 +27229,30 @@ var init_session = __esm({
|
|
|
26841
27229
|
if (!ctx.session) return UNAVAILABLE3;
|
|
26842
27230
|
const turn = typeof input.turn === "number" ? Math.trunc(input.turn) : void 0;
|
|
26843
27231
|
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
26844
|
-
|
|
26845
|
-
if (
|
|
27232
|
+
const listAssets = input.assets === true;
|
|
27233
|
+
if (turn === void 0 && query.length === 0 && !listAssets)
|
|
27234
|
+
return "recall needs a query, a turn number or assets: true.";
|
|
27235
|
+
if (!await mayRead(ctx)) {
|
|
27236
|
+
return JSON.stringify(turn !== void 0 ? { turn, content: null } : listAssets ? { assets: [] } : { hits: [] });
|
|
27237
|
+
}
|
|
26846
27238
|
if (turn !== void 0) {
|
|
26847
27239
|
const message3 = turn >= 0 ? await ctx.session.readTurn(turn) : void 0;
|
|
26848
27240
|
if (!message3) return `no turn ${turn} in this session's log.`;
|
|
26849
27241
|
return JSON.stringify({ turn, role: message3.role, content: message3.content });
|
|
26850
27242
|
}
|
|
27243
|
+
if (listAssets) {
|
|
27244
|
+
if (!ctx.session.assets) {
|
|
27245
|
+
return JSON.stringify({
|
|
27246
|
+
assets: [],
|
|
27247
|
+
note: "this deployment has no artifact store: the thread's files are not catalogued"
|
|
27248
|
+
});
|
|
27249
|
+
}
|
|
27250
|
+
const assets = await ctx.session.assets();
|
|
27251
|
+
return JSON.stringify({
|
|
27252
|
+
assets: assets.map((a) => assetView(a, ctx.session.workspacePathOf?.(a.key))),
|
|
27253
|
+
...assets.length === 0 ? { note: "no run of this thread has received or produced a file" } : {}
|
|
27254
|
+
});
|
|
27255
|
+
}
|
|
26851
27256
|
const limit = Math.min(
|
|
26852
27257
|
MAX_HITS,
|
|
26853
27258
|
Math.max(1, typeof input.limit === "number" ? Math.trunc(input.limit) : DEFAULT_HITS)
|
|
@@ -26857,12 +27262,17 @@ var init_session = __esm({
|
|
|
26857
27262
|
if (hits.length === 0)
|
|
26858
27263
|
notes.push("no turn of this session's log matches; your notes (notes {}) may hold what you look for");
|
|
26859
27264
|
if (ctx.session.session.seedFrom === 0) notes.push("this is the first run of its session: the log begins with it");
|
|
27265
|
+
const catalogue = ctx.session.assets && hits.some((h) => FILE_BEARING.test(h.text)) ? await ctx.session.assets() : [];
|
|
26860
27266
|
return JSON.stringify({
|
|
26861
|
-
hits: hits.map((h) =>
|
|
26862
|
-
|
|
26863
|
-
|
|
26864
|
-
|
|
26865
|
-
|
|
27267
|
+
hits: hits.map((h) => {
|
|
27268
|
+
const files = filesNamedIn(h.text, catalogue);
|
|
27269
|
+
return {
|
|
27270
|
+
turn: h.idx,
|
|
27271
|
+
...h.role !== void 0 ? { role: h.role } : {},
|
|
27272
|
+
snippet: snippetOf(h.text),
|
|
27273
|
+
...files.length > 0 ? { files: files.map((a) => assetView(a, ctx.session.workspacePathOf?.(a.key))) } : {}
|
|
27274
|
+
};
|
|
27275
|
+
}),
|
|
26866
27276
|
...gaps.length ? {
|
|
26867
27277
|
gaps: `the log has a gap at ${gaps.map((g) => `turn ${g}`).join(", ")}: a run before it detached from the ledger, so turns it saw after that point are not here`
|
|
26868
27278
|
} : {},
|
|
@@ -27037,6 +27447,7 @@ ${raw.trim()}`;
|
|
|
27037
27447
|
};
|
|
27038
27448
|
submitVerdictTool = {
|
|
27039
27449
|
name: "submit_verdict",
|
|
27450
|
+
failsInText: true,
|
|
27040
27451
|
description: "Record your review verdict. REQUIRED before your final message when reviewing a PR: `approve` when there are no blocking issues (nits alone are not blocking), `request_changes` otherwise. Switchboard writes the verdict as the first line of the GitHub comment itself (`LGTM:` only for approve); a review with no submitted verdict is posted as NOT approving. Call it once, after your analysis; a later call replaces the earlier one. `head` is the commit you reviewed \u2014 run `git rev-parse HEAD` in the checkout you read and tested and pass its output; Switchboard posts to the PR only if that commit IS the PR's head, so a review of the wrong branch can never land on a PR. Enumerate EVERY issue you report in `findings` with STABLE ids assigned in order (F1, F2, \u2026) \u2014 a fix round references findings by these ids, so never renumber them. Severity is exactly one of blocking|major|minor|nit; the entry carries the file (plus line when it points at one) and a one-line title, while the full explanation stays in your review text keyed by the same ids. An `approve` carrying a `blocking` finding is downgraded to `request_changes` \u2014 approve only when nothing blocking remains.",
|
|
27041
27452
|
inputSchema: {
|
|
27042
27453
|
type: "object",
|
|
@@ -27087,6 +27498,7 @@ ${raw.trim()}`;
|
|
|
27087
27498
|
};
|
|
27088
27499
|
submitDispositionsTool = {
|
|
27089
27500
|
name: "submit_dispositions",
|
|
27501
|
+
failsInText: true,
|
|
27090
27502
|
description: "Record one disposition per review finding after addressing them: `fixed` (the finding is addressed in your pushed code) or `declined` (deliberately not doing it \u2014 the note says why). `findingId` is the finding's stable id from the review (F1, F2, \u2026) \u2014 use exactly those ids; an id the review never issued answers nothing and is dropped when the plan runner reads your record. Every finding gets exactly one entry, every severity included (nits too). Call it once with the complete set after your last push; a later call replaces the earlier one. The set rides this run's record, where the plan runner reads it for the re-review.",
|
|
27091
27503
|
inputSchema: {
|
|
27092
27504
|
type: "object",
|
|
@@ -27118,6 +27530,7 @@ ${raw.trim()}`;
|
|
|
27118
27530
|
};
|
|
27119
27531
|
submitPrDescriptionTool = {
|
|
27120
27532
|
name: "submit_pr_description",
|
|
27533
|
+
failsInText: true,
|
|
27121
27534
|
description: "Submit the PR description as a typed object. REQUIRED after pushing your branch: Switchboard renders the GitHub PR body from this object at the pushed head and opens (or updates) the pull request itself \u2014 never open a PR yourself. Fields map 1:1 to the rendered sections; `title` becomes the PR's title; tour anchors are (path, from, to) line ranges at your pushed head. Call it after your last push; if you push again afterwards, call it again \u2014 the last valid call wins. An invalid object returns an error naming the field to fix; correct it and resubmit.",
|
|
27122
27535
|
inputSchema: {
|
|
27123
27536
|
type: "object",
|
|
@@ -27211,6 +27624,7 @@ ${raw.trim()}`;
|
|
|
27211
27624
|
});
|
|
27212
27625
|
submitHandoffTool = {
|
|
27213
27626
|
name: "submit_handoff",
|
|
27627
|
+
failsInText: true,
|
|
27214
27628
|
description: "Submit the unit handoff as a typed object \u2014 REQUIRED when your first user turn carries a `## Contract` block: once, after submit_pr_description and before your final message. `deviations`: where you departed from the unit as written (from, to, why); `followUps`: what you found and did not do, and where it belongs (what, where); `unproven`: which of the unit's test scenarios or criteria you could not prove, and why (criterion, why). Switchboard records it on the run and posts it to the unit's board issue, where a person decides each row. Submit empty lists when there is nothing to say \u2014 never skip it. A later call replaces the earlier one; an invalid object returns an error naming the field to fix.",
|
|
27215
27629
|
inputSchema: {
|
|
27216
27630
|
type: "object",
|
|
@@ -27523,7 +27937,11 @@ function imperativeRule(writers) {
|
|
|
27523
27937
|
}
|
|
27524
27938
|
function attachRule(attachers) {
|
|
27525
27939
|
const names = attachers.map((a) => `\`${a}\``).join(" or ");
|
|
27526
|
-
return
|
|
27940
|
+
return [
|
|
27941
|
+
`Only ${names} can attach or post a file into the thread (the \`attach_file\` tool).`,
|
|
27942
|
+
`A request that names attach_file routes to ${names}, whatever it asks the tool to do \u2014 a probe, a test or a diagnostic of the tool is still a call to it.`,
|
|
27943
|
+
"A request that asks for a file, a screenshot, a recording or an attachment to be posted, attached or sent back routes there too, however read-only the rest of it sounds; a read-only preset can describe a file but never post one."
|
|
27944
|
+
].join(" ");
|
|
27527
27945
|
}
|
|
27528
27946
|
function tidyReason(reason) {
|
|
27529
27947
|
const line3 = oneLine(redactAndCap(reason, ROUTE_REASON_CAP));
|
|
@@ -27994,17 +28412,17 @@ ${stagedLine}` : prompt }];
|
|
|
27994
28412
|
try {
|
|
27995
28413
|
const output = await untilHardStop(tool.run(tu.input ?? {}, ctx));
|
|
27996
28414
|
const text2 = toolResultText(output);
|
|
27997
|
-
const exit = tu.name === "bash" ? parseExitPrefix(text2) :
|
|
28415
|
+
const exit = tu.name === "bash" ? parseExitPrefix(text2) : { failed: tool.failsInText === true && toolTextFailed(text2) };
|
|
27998
28416
|
emit({
|
|
27999
28417
|
type: "tool_result",
|
|
28000
28418
|
tool: tu.name,
|
|
28001
|
-
ok: !exit
|
|
28419
|
+
ok: !exit.failed,
|
|
28002
28420
|
callId: tu.id,
|
|
28003
|
-
...exit
|
|
28421
|
+
...exit.exitCode !== void 0 ? { exitCode: exit.exitCode } : {},
|
|
28004
28422
|
...prepareToolResult(text2),
|
|
28005
28423
|
...spanId
|
|
28006
28424
|
});
|
|
28007
|
-
settle2(!exit
|
|
28425
|
+
settle2(!exit.failed, exit.exitCode !== void 0 ? { exitCode: exit.exitCode } : {});
|
|
28008
28426
|
return { type: "tool_result", toolUseId: tu.id, content: capToolResultContent(output) };
|
|
28009
28427
|
} catch (err2) {
|
|
28010
28428
|
if (err2 instanceof HardStopError) throw err2;
|
|
@@ -28591,7 +29009,8 @@ async function attachRoundWorkspace(input) {
|
|
|
28591
29009
|
profile,
|
|
28592
29010
|
repo: input.round.repo,
|
|
28593
29011
|
ref: input.round.ref,
|
|
28594
|
-
headSha: input.round.headSha
|
|
29012
|
+
headSha: input.round.headSha,
|
|
29013
|
+
...input.round.reattach !== void 0 ? { reattach: input.round.reattach } : {}
|
|
28595
29014
|
},
|
|
28596
29015
|
input.span
|
|
28597
29016
|
);
|
|
@@ -29218,157 +29637,6 @@ var init_messages = __esm({
|
|
|
29218
29637
|
}
|
|
29219
29638
|
});
|
|
29220
29639
|
|
|
29221
|
-
// ../../src/core/dispatch/staging.ts
|
|
29222
|
-
function stagedBasename(index, name) {
|
|
29223
|
-
return `${index}-${safeBasename(name)}`;
|
|
29224
|
-
}
|
|
29225
|
-
function pullCommandFor(url, basename4) {
|
|
29226
|
-
return `mkdir -p ${ATTACHMENTS_DIR} && curl -fsS -o ${shellQuote(`${ATTACHMENTS_DIR}/${basename4}`)} ${shellQuote(url)}`;
|
|
29227
|
-
}
|
|
29228
|
-
function excludeCommand() {
|
|
29229
|
-
const entry = shellQuote(`${ATTACHMENTS_DIR}/`);
|
|
29230
|
-
return `mkdir -p .git/info && { grep -qxF ${entry} .git/info/exclude 2>/dev/null || echo ${entry} >> .git/info/exclude; }`;
|
|
29231
|
-
}
|
|
29232
|
-
function formatSize(bytes) {
|
|
29233
|
-
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
|
|
29234
|
-
if (bytes >= 1e6) return `${Math.round(bytes / 1e6)} MB`;
|
|
29235
|
-
if (bytes >= 1e3) return `${Math.round(bytes / 1e3)} KB`;
|
|
29236
|
-
return `${bytes} B`;
|
|
29237
|
-
}
|
|
29238
|
-
function attachmentsLine(outcomes) {
|
|
29239
|
-
const landed = outcomes.filter((o) => o.error === void 0);
|
|
29240
|
-
const failed = outcomes.filter((o) => o.error !== void 0);
|
|
29241
|
-
const parts = [];
|
|
29242
|
-
if (landed.length > 0) {
|
|
29243
|
-
parts.push(
|
|
29244
|
-
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map(
|
|
29245
|
-
(o) => `${o.basename} (${formatSize(o.file.size)}, ${o.file.type}${o.earlier ? ", from earlier in the thread" : ""})`
|
|
29246
|
-
).join(", ")}`
|
|
29247
|
-
);
|
|
29248
|
-
}
|
|
29249
|
-
for (const o of failed) parts.push(`${o.file.name} could not be staged: ${o.error}`);
|
|
29250
|
-
return parts.join(". ");
|
|
29251
|
-
}
|
|
29252
|
-
function noWorkspaceLine(files) {
|
|
29253
|
-
return files.map(
|
|
29254
|
-
(f) => `this agent has no workspace for ${f.name} (${formatSize(f.size)}, ${f.type}); ask \`agent:coding\` to work with it`
|
|
29255
|
-
).join(". ");
|
|
29256
|
-
}
|
|
29257
|
-
function stagingIndex() {
|
|
29258
|
-
let n2 = 0;
|
|
29259
|
-
return () => ++n2;
|
|
29260
|
-
}
|
|
29261
|
-
async function copyStaged(files, deps) {
|
|
29262
|
-
return Promise.all(
|
|
29263
|
-
files.map(async (file) => {
|
|
29264
|
-
const index = deps.nextIndex();
|
|
29265
|
-
const basename4 = stagedBasename(index, file.name);
|
|
29266
|
-
const key = inboundKey(deps.threadKey, file.messageId, index, file.name);
|
|
29267
|
-
try {
|
|
29268
|
-
await deps.store.copyFromUrl({ url: file.url, size: file.size, key });
|
|
29269
|
-
deps.publish?.({
|
|
29270
|
-
type: "artifact",
|
|
29271
|
-
direction: "in",
|
|
29272
|
-
key,
|
|
29273
|
-
name: file.name,
|
|
29274
|
-
size: file.size,
|
|
29275
|
-
contentType: file.type
|
|
29276
|
-
});
|
|
29277
|
-
return { file, basename: basename4, key };
|
|
29278
|
-
} catch (err2) {
|
|
29279
|
-
return { file, basename: basename4, key, error: `the copy into the store failed: ${describe4(err2)}` };
|
|
29280
|
-
}
|
|
29281
|
-
})
|
|
29282
|
-
);
|
|
29283
|
-
}
|
|
29284
|
-
async function pullStaged(outcomes, deps) {
|
|
29285
|
-
const landed = outcomes.filter((o) => o.error === void 0);
|
|
29286
|
-
if (landed.length === 0) return [...outcomes];
|
|
29287
|
-
const timeoutMs = deps.timeoutMs ?? BASH_TIMEOUT_MAX_MS;
|
|
29288
|
-
const opts = { timeoutMs, ...deps.signal ? { signal: deps.signal } : {} };
|
|
29289
|
-
if (deps.resident) await deps.executor.exec(excludeCommand(), opts);
|
|
29290
|
-
const pulled = /* @__PURE__ */ new Map();
|
|
29291
|
-
for (const o of landed) {
|
|
29292
|
-
const url = await deps.store.presignGet(o.key);
|
|
29293
|
-
const out = await deps.executor.exec(pullCommandFor(url, o.basename), opts);
|
|
29294
|
-
pulled.set(o.key, parseExitPrefix(out).failed ? `the pull into the workspace failed: ${out.trim()}` : void 0);
|
|
29295
|
-
}
|
|
29296
|
-
return outcomes.map((o) => {
|
|
29297
|
-
const error = o.error ?? pulled.get(o.key);
|
|
29298
|
-
if (error !== void 0) return { ...o, error };
|
|
29299
|
-
const { error: _dropped, ...landedOutcome } = o;
|
|
29300
|
-
return landedOutcome;
|
|
29301
|
-
});
|
|
29302
|
-
}
|
|
29303
|
-
function threadInboundArtifacts(runsOldestFirst) {
|
|
29304
|
-
const seen = /* @__PURE__ */ new Set();
|
|
29305
|
-
const out = [];
|
|
29306
|
-
for (const events of runsOldestFirst) {
|
|
29307
|
-
for (const e of events) {
|
|
29308
|
-
if (e.type !== "artifact" || e.direction !== "in" || seen.has(e.key)) continue;
|
|
29309
|
-
seen.add(e.key);
|
|
29310
|
-
out.push({ key: e.key, name: e.name, size: e.size, contentType: e.contentType });
|
|
29311
|
-
}
|
|
29312
|
-
}
|
|
29313
|
-
return out;
|
|
29314
|
-
}
|
|
29315
|
-
async function stageThreadArtifacts(artifacts, deps) {
|
|
29316
|
-
const outcomes = [];
|
|
29317
|
-
for (const a of artifacts) {
|
|
29318
|
-
const file = { name: a.name, size: a.size, type: a.contentType };
|
|
29319
|
-
let held;
|
|
29320
|
-
try {
|
|
29321
|
-
held = await deps.store.head(a.key) !== null;
|
|
29322
|
-
} catch (err2) {
|
|
29323
|
-
outcomes.push({
|
|
29324
|
-
file,
|
|
29325
|
-
basename: safeBasename(a.name),
|
|
29326
|
-
key: a.key,
|
|
29327
|
-
earlier: true,
|
|
29328
|
-
error: `the store could not be asked: ${describe4(err2)}`
|
|
29329
|
-
});
|
|
29330
|
-
continue;
|
|
29331
|
-
}
|
|
29332
|
-
if (!held) {
|
|
29333
|
-
outcomes.push({
|
|
29334
|
-
file,
|
|
29335
|
-
basename: safeBasename(a.name),
|
|
29336
|
-
key: a.key,
|
|
29337
|
-
earlier: true,
|
|
29338
|
-
error: "the store no longer holds it (its retention passed)"
|
|
29339
|
-
});
|
|
29340
|
-
continue;
|
|
29341
|
-
}
|
|
29342
|
-
const basename4 = stagedBasename(deps.nextIndex(), a.name);
|
|
29343
|
-
deps.publish?.({
|
|
29344
|
-
type: "artifact",
|
|
29345
|
-
direction: "in",
|
|
29346
|
-
key: a.key,
|
|
29347
|
-
name: a.name,
|
|
29348
|
-
size: a.size,
|
|
29349
|
-
contentType: a.contentType
|
|
29350
|
-
});
|
|
29351
|
-
outcomes.push({ file, basename: basename4, key: a.key, earlier: true });
|
|
29352
|
-
}
|
|
29353
|
-
return outcomes;
|
|
29354
|
-
}
|
|
29355
|
-
async function stageIntoWorkspace(files, deps) {
|
|
29356
|
-
const outcomes = await pullStaged(await copyStaged(files, deps), deps);
|
|
29357
|
-
return { line: attachmentsLine(outcomes), outcomes };
|
|
29358
|
-
}
|
|
29359
|
-
var ATTACHMENTS_DIR, describe4;
|
|
29360
|
-
var init_staging = __esm({
|
|
29361
|
-
"../../src/core/dispatch/staging.ts"() {
|
|
29362
|
-
"use strict";
|
|
29363
|
-
init_keys();
|
|
29364
|
-
init_bashTimeout();
|
|
29365
|
-
init_shellQuote();
|
|
29366
|
-
init_runEvents();
|
|
29367
|
-
ATTACHMENTS_DIR = "attachments";
|
|
29368
|
-
describe4 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
29369
|
-
}
|
|
29370
|
-
});
|
|
29371
|
-
|
|
29372
29640
|
// ../../src/core/modelProxy/runBearers.ts
|
|
29373
29641
|
import { createHash as createHash4, randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
29374
29642
|
function token(runId3, secret2) {
|
|
@@ -30071,7 +30339,7 @@ async function reserveRun(deps, ctx) {
|
|
|
30071
30339
|
return void 0;
|
|
30072
30340
|
}
|
|
30073
30341
|
async function attachWorkspace(deps, ctx) {
|
|
30074
|
-
const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock3, agent, profile, repoCtx, root } = ctx;
|
|
30342
|
+
const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock3, agent, profile, repoCtx, root, reattach } = ctx;
|
|
30075
30343
|
let round2;
|
|
30076
30344
|
try {
|
|
30077
30345
|
round2 = await root.span("dispatch.workspace.attach", async (span) => {
|
|
@@ -30096,7 +30364,8 @@ async function attachWorkspace(deps, ctx) {
|
|
|
30096
30364
|
profile,
|
|
30097
30365
|
repo: repoCtx.repo,
|
|
30098
30366
|
ref: repoCtx.ref,
|
|
30099
|
-
headSha: repoCtx.headSha
|
|
30367
|
+
headSha: repoCtx.headSha,
|
|
30368
|
+
...reattach !== void 0 ? { reattach } : {}
|
|
30100
30369
|
},
|
|
30101
30370
|
logKey: msg.threadKey,
|
|
30102
30371
|
span
|
|
@@ -30123,6 +30392,7 @@ async function attachWorkspace(deps, ctx) {
|
|
|
30123
30392
|
});
|
|
30124
30393
|
return { kind: "refused", reason: "which_branch" };
|
|
30125
30394
|
}
|
|
30395
|
+
if (err2 instanceof WorkspaceReattachRefusedError) return { kind: "reattach_refused", why: err2.why };
|
|
30126
30396
|
throw err2;
|
|
30127
30397
|
}
|
|
30128
30398
|
return { kind: "attached", round: round2 };
|
|
@@ -30159,6 +30429,10 @@ function sessionNotesBlock(session) {
|
|
|
30159
30429
|
parts.push(
|
|
30160
30430
|
"SUMMARY OF THE EARLIER CONVERSATION (written when the context was compacted; every earlier turn is still reachable with `recall`):\n" + summary
|
|
30161
30431
|
);
|
|
30432
|
+
if (session.files && session.files.length > 0)
|
|
30433
|
+
parts.push(
|
|
30434
|
+
"FILES OF THIS THREAD (received on its messages or produced by its runs, as their records name them; `recall {assets: true}` lists them with their keys):\n" + session.files.map((line3) => `- ${line3}`).join("\n")
|
|
30435
|
+
);
|
|
30162
30436
|
return parts.length ? parts.join("\n\n") : void 0;
|
|
30163
30437
|
}
|
|
30164
30438
|
async function composePrompt(deps, ctx) {
|
|
@@ -30258,6 +30532,7 @@ var init_provision = __esm({
|
|
|
30258
30532
|
init_contract3();
|
|
30259
30533
|
init_registry();
|
|
30260
30534
|
init_profile();
|
|
30535
|
+
init_factory();
|
|
30261
30536
|
init_residentTrace();
|
|
30262
30537
|
init_resident();
|
|
30263
30538
|
init_memory();
|
|
@@ -30311,6 +30586,7 @@ async function claimRun(deps, ctx) {
|
|
|
30311
30586
|
} = ctx;
|
|
30312
30587
|
const { resident, binding } = selection;
|
|
30313
30588
|
let ledgerRun = ctx.ledgerRun;
|
|
30589
|
+
const workspaceBinding = workspaceBindingFor(selection, profile.machine);
|
|
30314
30590
|
if (!resume && reserved) {
|
|
30315
30591
|
const ledger = deps.runLedger;
|
|
30316
30592
|
const opened = await root.span(
|
|
@@ -30344,6 +30620,7 @@ async function claimRun(deps, ctx) {
|
|
|
30344
30620
|
...requestRow !== void 0 ? { request: requestRow } : {}
|
|
30345
30621
|
},
|
|
30346
30622
|
card: card.handle ?? null,
|
|
30623
|
+
...workspaceBinding !== void 0 ? { state: { binding: workspaceBinding } } : {},
|
|
30347
30624
|
// The row reserved before the attach (item 42), promoted in place;
|
|
30348
30625
|
// its hooks (a stop, a fence) were wired at the reservation and stay.
|
|
30349
30626
|
reservation: reserved,
|
|
@@ -30400,6 +30677,7 @@ var init_run2 = __esm({
|
|
|
30400
30677
|
init_workspace();
|
|
30401
30678
|
init_web();
|
|
30402
30679
|
init_githubApi();
|
|
30680
|
+
init_factory();
|
|
30403
30681
|
init_runRegistry();
|
|
30404
30682
|
init_secrets2();
|
|
30405
30683
|
webCapability = () => sharedWeb ??= makeWebCapability(processSecrets);
|
|
@@ -30432,6 +30710,17 @@ const URL_ENV = "SWITCHBOARD_HARNESS_URL";
|
|
|
30432
30710
|
/** How long the tool_call hook keeps asking an unreachable bot before it blocks. */
|
|
30433
30711
|
const AUTHORIZE_WAIT_MS = 90_000;
|
|
30434
30712
|
const AUTHORIZE_RETRY_MS = 2_000;
|
|
30713
|
+
/** How long one request for a relayed tool's result may take before it is asked
|
|
30714
|
+
* again: over the bot's window (30 s, after which it answers that the call is
|
|
30715
|
+
* still running), under this runtime's own five-minute header timeout on a
|
|
30716
|
+
* fetch, so a hung connection costs a minute of the wait, not five. */
|
|
30717
|
+
const TOOL_REQUEST_TIMEOUT_MS = 60_000;
|
|
30718
|
+
/** How long a relayed call keeps asking a bot it cannot reach before it fails; the cadence is the hook's. */
|
|
30719
|
+
const TOOL_WAIT_MS = 90_000;
|
|
30720
|
+
/** A bot that says a call is still running is asked again after the window it
|
|
30721
|
+
* held the request for; one that answered sooner is asked again no sooner
|
|
30722
|
+
* than this, so a misbehaving bot is never asked in a hot loop. */
|
|
30723
|
+
const TOOL_REASK_FLOOR_MS = 1_000;
|
|
30435
30724
|
|
|
30436
30725
|
function settings() {
|
|
30437
30726
|
const url = process.env[URL_ENV];
|
|
@@ -30505,6 +30794,49 @@ async function authorize(event) {
|
|
|
30505
30794
|
}
|
|
30506
30795
|
}
|
|
30507
30796
|
|
|
30797
|
+
/** pi's abort signal for the call, with this request's own timeout beside it
|
|
30798
|
+
* where the runtime can combine the two; pi's alone where it cannot. */
|
|
30799
|
+
function requestSignal(signal) {
|
|
30800
|
+
const timeout = typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(TOOL_REQUEST_TIMEOUT_MS) : undefined;
|
|
30801
|
+
if (!timeout) return signal;
|
|
30802
|
+
if (!signal) return timeout;
|
|
30803
|
+
return typeof AbortSignal.any === "function" ? AbortSignal.any([signal, timeout]) : signal;
|
|
30804
|
+
}
|
|
30805
|
+
|
|
30806
|
+
/** A relayed tool's result. The call is posted with its id; a bot that answers
|
|
30807
|
+
* that the call is still running (a wait on other runs can take minutes) is
|
|
30808
|
+
* asked again with the same id until it answers: the bot runs the call once
|
|
30809
|
+
* and every later ask joins it. A bot that cannot be reached or is failing (a
|
|
30810
|
+
* network error, a timed-out request, a 5xx) is asked again every two seconds
|
|
30811
|
+
* until the wait is up, then the call fails naming why; a refusal at the door
|
|
30812
|
+
* (a 4xx) fails it at once; an abort from pi ends the asking. */
|
|
30813
|
+
async function relay(toolCallId, tool, input, signal) {
|
|
30814
|
+
let lastAnswered = Date.now();
|
|
30815
|
+
let lastError = "";
|
|
30816
|
+
for (;;) {
|
|
30817
|
+
if (signal && signal.aborted) throw new Error("switchboard harness: " + tool + " was aborted before the bot answered");
|
|
30818
|
+
const asked = Date.now();
|
|
30819
|
+
let answer;
|
|
30820
|
+
try {
|
|
30821
|
+
answer = await call("POST", "/harness/tool", { toolCallId, tool, input }, requestSignal(signal));
|
|
30822
|
+
} catch (err) {
|
|
30823
|
+
if (signal && signal.aborted) throw err;
|
|
30824
|
+
const status = err instanceof HarnessAnswerError ? err.status : undefined;
|
|
30825
|
+
if (status !== undefined && status >= 400 && status < 500) throw err;
|
|
30826
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
30827
|
+
if (Date.now() - lastAnswered >= TOOL_WAIT_MS) {
|
|
30828
|
+
throw new Error("switchboard harness: the bot did not answer " + tool + " for 90 s (" + lastError + ")");
|
|
30829
|
+
}
|
|
30830
|
+
await sleep(AUTHORIZE_RETRY_MS);
|
|
30831
|
+
continue;
|
|
30832
|
+
}
|
|
30833
|
+
lastAnswered = Date.now();
|
|
30834
|
+
if (!answer.pending) return answer;
|
|
30835
|
+
const held = Date.now() - asked;
|
|
30836
|
+
if (held < TOOL_REASK_FLOOR_MS) await sleep(TOOL_REASK_FLOOR_MS - held);
|
|
30837
|
+
}
|
|
30838
|
+
}
|
|
30839
|
+
|
|
30508
30840
|
function relayTool(def) {
|
|
30509
30841
|
return {
|
|
30510
30842
|
name: def.name,
|
|
@@ -30512,7 +30844,7 @@ function relayTool(def) {
|
|
|
30512
30844
|
description: def.description,
|
|
30513
30845
|
parameters: def.inputSchema,
|
|
30514
30846
|
async execute(toolCallId, params, signal) {
|
|
30515
|
-
const answer = await
|
|
30847
|
+
const answer = await relay(toolCallId, def.name, params, signal);
|
|
30516
30848
|
if (answer.isError) throw new Error(answer.content.map((c) => (c.type === "text" ? c.text : "")).join("\\n"));
|
|
30517
30849
|
return { content: answer.content, details: {} };
|
|
30518
30850
|
},
|
|
@@ -30711,6 +31043,9 @@ function readLogScript(path, offset, maxBytes) {
|
|
|
30711
31043
|
function aliveScript(pid) {
|
|
30712
31044
|
return `kill -0 ${pid} 2>/dev/null && echo alive || echo dead`;
|
|
30713
31045
|
}
|
|
31046
|
+
function identityScript() {
|
|
31047
|
+
return "cat /proc/sys/kernel/random/boot_id 2>/dev/null || true";
|
|
31048
|
+
}
|
|
30714
31049
|
function killScript(pid) {
|
|
30715
31050
|
return `kill -TERM -- -${pid} 2>/dev/null; kill -TERM ${pid} 2>/dev/null; sleep 1; kill -KILL -- -${pid} 2>/dev/null; kill -KILL ${pid} 2>/dev/null; true`;
|
|
30716
31051
|
}
|
|
@@ -30727,7 +31062,7 @@ function stdoutOf(operation, out) {
|
|
|
30727
31062
|
const stdout = cut >= 0 ? out.slice(0, cut) : out;
|
|
30728
31063
|
return stdout === "(no output)" ? "" : stdout;
|
|
30729
31064
|
}
|
|
30730
|
-
var PiContainerError, WRITE_CHUNK_CHARS, INLINE_LINE_CHARS, LOG_READ_BYTES, MESSAGE_UPDATE_MARK, LOG_FILTER, STDERR_MARK, OP_TIMEOUT_MS, ExecPiContainer;
|
|
31065
|
+
var PiContainerError, WRITE_CHUNK_CHARS, INLINE_LINE_CHARS, LOG_READ_BYTES, MESSAGE_UPDATE_MARK, LOG_FILTER, IDENTITY_WORD, STDERR_MARK, OP_TIMEOUT_MS, ExecPiContainer;
|
|
30731
31066
|
var init_container = __esm({
|
|
30732
31067
|
"../../src/core/harness/pi/container.ts"() {
|
|
30733
31068
|
"use strict";
|
|
@@ -30747,6 +31082,7 @@ var init_container = __esm({
|
|
|
30747
31082
|
LOG_READ_BYTES = 48 * 1024;
|
|
30748
31083
|
MESSAGE_UPDATE_MARK = '"type":"message_update"';
|
|
30749
31084
|
LOG_FILTER = `grep --line-buffered -v ${shellQuote(MESSAGE_UPDATE_MARK)}`;
|
|
31085
|
+
IDENTITY_WORD = /^[A-Za-z0-9-]{1,64}$/;
|
|
30750
31086
|
STDERR_MARK = "\n--- stderr ---\n";
|
|
30751
31087
|
OP_TIMEOUT_MS = 6e4;
|
|
30752
31088
|
ExecPiContainer = class {
|
|
@@ -30760,6 +31096,12 @@ var init_container = __esm({
|
|
|
30760
31096
|
async makeRoot(runId3) {
|
|
30761
31097
|
return piRunPaths(runId3);
|
|
30762
31098
|
}
|
|
31099
|
+
/** The start script never changes directory, so pi runs where the executor
|
|
31100
|
+
* runs every command: the thread's checkout, which the harness names (a
|
|
31101
|
+
* resident's `/exec` runs in the thread's worktree). No command runs here. */
|
|
31102
|
+
cwd(_paths, checkout) {
|
|
31103
|
+
return checkout;
|
|
31104
|
+
}
|
|
30763
31105
|
async writeFile(path, content) {
|
|
30764
31106
|
for (const script of writeFileScripts(path, content)) stdoutOf("write", await this.exec(script));
|
|
30765
31107
|
}
|
|
@@ -30785,6 +31127,16 @@ var init_container = __esm({
|
|
|
30785
31127
|
async alive(pid) {
|
|
30786
31128
|
return stdoutOf("alive", await this.exec(aliveScript(pid))).trim() === "alive";
|
|
30787
31129
|
}
|
|
31130
|
+
/** One word or nothing: an empty answer, a malformed one or a command the
|
|
31131
|
+
* executor could not run is no identity: a judgement never rests on a guess. */
|
|
31132
|
+
async identity() {
|
|
31133
|
+
try {
|
|
31134
|
+
const word = stdoutOf("identity", await this.exec(identityScript())).trim();
|
|
31135
|
+
return IDENTITY_WORD.test(word) ? word : void 0;
|
|
31136
|
+
} catch {
|
|
31137
|
+
return void 0;
|
|
31138
|
+
}
|
|
31139
|
+
}
|
|
30788
31140
|
async kill(pid) {
|
|
30789
31141
|
stdoutOf("kill", await this.exec(killScript(pid)));
|
|
30790
31142
|
}
|
|
@@ -30916,6 +31268,14 @@ var init_botHostContainer = __esm({
|
|
|
30916
31268
|
throw new PiContainerError("root", message2(err2));
|
|
30917
31269
|
}
|
|
30918
31270
|
}
|
|
31271
|
+
/** The run's own root: `start` spawns pi in it, so that is the directory a
|
|
31272
|
+
* session written for this root names, present on this host in this
|
|
31273
|
+
* generation. The checkout the harness names is a container path this host
|
|
31274
|
+
* does not have (a `none` run has no workspace), and the previous
|
|
31275
|
+
* generation's root is gone with its pi: neither is where pi runs. */
|
|
31276
|
+
cwd(paths, _checkout) {
|
|
31277
|
+
return paths.dir;
|
|
31278
|
+
}
|
|
30919
31279
|
/** Parents at 700, the file at 600, exact bytes, created and never
|
|
30920
31280
|
* replaced: under a root this container made nothing is there before the
|
|
30921
31281
|
* harness writes it, so a path already present is a named failure, never
|
|
@@ -31036,6 +31396,12 @@ var init_botHostContainer = __esm({
|
|
|
31036
31396
|
const child = this.byPid.get(pid);
|
|
31037
31397
|
return child !== void 0 && !child.exited;
|
|
31038
31398
|
}
|
|
31399
|
+
/** The bot host names no container: its pi is a child of one bot generation
|
|
31400
|
+
* and never outlives it, so a resume judges it by its pid alone (`alive`
|
|
31401
|
+
* answers no for a pid this container did not start). */
|
|
31402
|
+
async identity() {
|
|
31403
|
+
return void 0;
|
|
31404
|
+
}
|
|
31039
31405
|
/** The group first (pi leads it), then pi itself: TERM, the grace, KILL;
|
|
31040
31406
|
* a pid this container never started, or one already ended, is nothing to end. */
|
|
31041
31407
|
async kill(pid) {
|
|
@@ -31289,7 +31655,7 @@ var init_bridge2 = __esm({
|
|
|
31289
31655
|
const tool = open2?.tool ?? str5(event.toolName);
|
|
31290
31656
|
const isError = event.isError === true;
|
|
31291
31657
|
const text = piResultText(event.result);
|
|
31292
|
-
const exit = tool === "bash" ? piBashExit(text, isError) : { failed: isError
|
|
31658
|
+
const exit = tool === "bash" ? piBashExit(text, isError) : { failed: isError || this.deps.textFailing?.has(tool) === true && toolTextFailed(text) };
|
|
31293
31659
|
const ok2 = !exit.failed;
|
|
31294
31660
|
this.emit({
|
|
31295
31661
|
type: "tool_result",
|
|
@@ -31361,6 +31727,11 @@ var init_bridge2 = __esm({
|
|
|
31361
31727
|
openSpan(callId) {
|
|
31362
31728
|
return this.openTools.get(callId)?.span;
|
|
31363
31729
|
}
|
|
31730
|
+
/** Whether the bridge has read the call's start off the log and not yet its
|
|
31731
|
+
* end: what a relayed request that arrived ahead of the poll waits for. */
|
|
31732
|
+
callOpen(callId) {
|
|
31733
|
+
return this.openTools.has(callId);
|
|
31734
|
+
}
|
|
31364
31735
|
/** End whatever tool spans a stopped pi left open, so no span outlives the run. */
|
|
31365
31736
|
closeOpenSpans(reason) {
|
|
31366
31737
|
for (const [callId, open2] of this.openTools) {
|
|
@@ -31682,7 +32053,8 @@ function piHarnessFactsOf(value) {
|
|
|
31682
32053
|
logOffset: v.logOffset,
|
|
31683
32054
|
...typeof v.sessionFile === "string" ? { sessionFile: v.sessionFile } : {},
|
|
31684
32055
|
...typeof v.bearerHash === "string" ? { bearerHash: v.bearerHash } : {},
|
|
31685
|
-
...typeof v.root === "string" ? { root: v.root } : {}
|
|
32056
|
+
...typeof v.root === "string" ? { root: v.root } : {},
|
|
32057
|
+
...typeof v.container === "string" ? { container: v.container } : {}
|
|
31686
32058
|
};
|
|
31687
32059
|
}
|
|
31688
32060
|
function relayedTools(tools) {
|
|
@@ -31739,12 +32111,36 @@ async function runPiHarness(deps, run2) {
|
|
|
31739
32111
|
run2.onProgress?.(summary);
|
|
31740
32112
|
emit({ type: "run_note", kind, summary, ...mode ? { mode } : {} });
|
|
31741
32113
|
};
|
|
31742
|
-
const bridge = new PiBridge({
|
|
32114
|
+
const bridge = new PiBridge({
|
|
32115
|
+
emit,
|
|
32116
|
+
onProgress: run2.onProgress,
|
|
32117
|
+
agentSpan,
|
|
32118
|
+
clock: clock3,
|
|
32119
|
+
textFailing: new Set(run2.tools.filter((t) => t.failsInText).map((t) => t.name))
|
|
32120
|
+
});
|
|
31743
32121
|
let paths;
|
|
31744
32122
|
const remainingMs = run2.resume?.remainingMs ?? run2.agent.maxMinutes * 6e4;
|
|
31745
32123
|
const deadline = now() + remainingMs;
|
|
31746
32124
|
const warnAt = deadline - Math.min(3 * 6e4, run2.agent.maxMinutes * 15e3);
|
|
31747
32125
|
run2.toolContext.remainingMs = () => deadline - now();
|
|
32126
|
+
if (run2.conversation) {
|
|
32127
|
+
const readConversation = run2.conversation;
|
|
32128
|
+
run2.toolContext.conversation = async () => {
|
|
32129
|
+
try {
|
|
32130
|
+
return await readConversation();
|
|
32131
|
+
} catch (err2) {
|
|
32132
|
+
emit({
|
|
32133
|
+
type: "run_note",
|
|
32134
|
+
kind: "seed",
|
|
32135
|
+
summary: redactAndCap(
|
|
32136
|
+
`the conversation could not be read from the session log for a child's seed (${err2 instanceof Error ? err2.message : String(err2)}); a child spawned now starts from its own thread`,
|
|
32137
|
+
300
|
|
32138
|
+
)
|
|
32139
|
+
});
|
|
32140
|
+
return void 0;
|
|
32141
|
+
}
|
|
32142
|
+
};
|
|
32143
|
+
}
|
|
31748
32144
|
bridge.turns = run2.resume?.turn ?? 0;
|
|
31749
32145
|
const mirror = new PiMirror({
|
|
31750
32146
|
onStep: run2.onStep,
|
|
@@ -31774,7 +32170,11 @@ async function runPiHarness(deps, run2) {
|
|
|
31774
32170
|
emit,
|
|
31775
32171
|
toolSpan: (callId) => bridge.openSpan(callId),
|
|
31776
32172
|
gateSaw: (callId) => bridge.gateSaw(callId),
|
|
31777
|
-
toolsBlocked
|
|
32173
|
+
toolsBlocked,
|
|
32174
|
+
callSeen: async (callId) => {
|
|
32175
|
+
for (let waited = 0; !bridge.callOpen(callId) && waited < CALL_SEEN_WAIT_MS; waited += CALL_SEEN_TICK_MS)
|
|
32176
|
+
await deps.sleep(CALL_SEEN_TICK_MS);
|
|
32177
|
+
}
|
|
31778
32178
|
};
|
|
31779
32179
|
const forget = deps.registry.register(live);
|
|
31780
32180
|
let pid;
|
|
@@ -31791,21 +32191,27 @@ async function runPiHarness(deps, run2) {
|
|
|
31791
32191
|
const recorded = run2.resume?.facts;
|
|
31792
32192
|
let reattached = false;
|
|
31793
32193
|
let ended;
|
|
32194
|
+
let elsewhere;
|
|
32195
|
+
const here = await container.identity();
|
|
31794
32196
|
const honoured = (hash) => hash !== void 0 && (deps.bearers === void 0 || deps.bearers.adopt(run2.runId, hash));
|
|
31795
32197
|
if (recorded !== void 0) {
|
|
31796
|
-
|
|
31797
|
-
|
|
31798
|
-
|
|
31799
|
-
|
|
31800
|
-
|
|
31801
|
-
|
|
31802
|
-
|
|
31803
|
-
})
|
|
32198
|
+
if (recorded.container !== void 0 && here !== void 0 && recorded.container !== here) {
|
|
32199
|
+
elsewhere = `pi is elsewhere: the row's pi (pid ${recorded.pid}) ran in container ${recorded.container}, not the one this run was handed (${here}), so it was neither probed nor ended here`;
|
|
32200
|
+
} else {
|
|
32201
|
+
const alive = await container.alive(recorded.pid);
|
|
32202
|
+
if (alive && recorded.root !== void 0 && honoured(recorded.bearerHash)) {
|
|
32203
|
+
reattached = true;
|
|
32204
|
+
paths = piRunPathsAt(recorded.root);
|
|
32205
|
+
} else if (alive) {
|
|
32206
|
+
ended = recorded.root === void 0 ? "named no directory for its pi" : "carried no bearer this generation could honour for its pi";
|
|
32207
|
+
await container.kill(recorded.pid).catch(() => {
|
|
32208
|
+
});
|
|
32209
|
+
}
|
|
31804
32210
|
}
|
|
31805
32211
|
}
|
|
31806
32212
|
if (reattached && recorded && paths !== void 0) {
|
|
31807
32213
|
pid = recorded.pid;
|
|
31808
|
-
facts2 = { ...recorded };
|
|
32214
|
+
facts2 = { ...recorded, ...recorded.container === void 0 && here !== void 0 ? { container: here } : {} };
|
|
31809
32215
|
transport = new PiRpcTransport({
|
|
31810
32216
|
container,
|
|
31811
32217
|
paths,
|
|
@@ -31821,7 +32227,7 @@ async function runPiHarness(deps, run2) {
|
|
|
31821
32227
|
);
|
|
31822
32228
|
} else {
|
|
31823
32229
|
paths = await container.makeRoot(run2.runId);
|
|
31824
|
-
if (recorded?.root !== void 0 && recorded.root !== paths.dir)
|
|
32230
|
+
if (recorded?.root !== void 0 && recorded.root !== paths.dir && elsewhere === void 0)
|
|
31825
32231
|
await container.remove(piRunPathsAt(recorded.root)).catch(() => {
|
|
31826
32232
|
});
|
|
31827
32233
|
const spec = {
|
|
@@ -31854,7 +32260,7 @@ async function runPiHarness(deps, run2) {
|
|
|
31854
32260
|
piSessionFile(
|
|
31855
32261
|
session.messages,
|
|
31856
32262
|
{
|
|
31857
|
-
cwd: run2.rules.checkout,
|
|
32263
|
+
cwd: container.cwd(paths, run2.rules.checkout),
|
|
31858
32264
|
model: {
|
|
31859
32265
|
provider: run2.model.provider,
|
|
31860
32266
|
id: run2.model.id,
|
|
@@ -31868,7 +32274,7 @@ async function runPiHarness(deps, run2) {
|
|
|
31868
32274
|
}
|
|
31869
32275
|
if (run2.resume) {
|
|
31870
32276
|
const lost = run2.resume.settlements.length;
|
|
31871
|
-
const how = ended !== void 0 && recorded !== void 0 ? `the row ${ended} (pid ${recorded.pid}), so it was ended and pi restarted` : "pi restarted";
|
|
32277
|
+
const how = elsewhere !== void 0 ? `${elsewhere}, and pi restarted` : ended !== void 0 && recorded !== void 0 ? `the row ${ended} (pid ${recorded.pid}), so it was ended and pi restarted` : "pi restarted";
|
|
31872
32278
|
note(
|
|
31873
32279
|
"resumed",
|
|
31874
32280
|
`resumed after a restart: ${how} on the mirrored transcript \u2014 ${lost} call(s) were in flight, each answered with a restart note; ${Math.round(remainingMs / 6e4)} min of budget left`
|
|
@@ -31878,7 +32284,13 @@ async function runPiHarness(deps, run2) {
|
|
|
31878
32284
|
for (const file of piLaunchFiles(launch)) await container.writeFile(file.path, file.content);
|
|
31879
32285
|
({ pid } = await container.start({ paths, args: piLaunchArgs(launch), env: piLaunchEnv(launch, deps.bearer) }));
|
|
31880
32286
|
const bearerHash = bearerHashOf(deps.bearer);
|
|
31881
|
-
facts2 = {
|
|
32287
|
+
facts2 = {
|
|
32288
|
+
pid,
|
|
32289
|
+
logOffset: 0,
|
|
32290
|
+
root: paths.dir,
|
|
32291
|
+
...bearerHash !== void 0 ? { bearerHash } : {},
|
|
32292
|
+
...here !== void 0 ? { container: here } : {}
|
|
32293
|
+
};
|
|
31882
32294
|
save();
|
|
31883
32295
|
transport = new PiRpcTransport({ container, paths, pid, pollMs: deps.pollMs ?? 750, sleep: deps.sleep });
|
|
31884
32296
|
}
|
|
@@ -32073,7 +32485,7 @@ ${stagedLine}` : prompt,
|
|
|
32073
32485
|
agentSpan?.end(hardStopped || bypass ? "error" : "ok");
|
|
32074
32486
|
}
|
|
32075
32487
|
}
|
|
32076
|
-
var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CONTINUE_PROMPT, PromptRefused, GateBypassed;
|
|
32488
|
+
var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CALL_SEEN_WAIT_MS, CALL_SEEN_TICK_MS, CONTINUE_PROMPT, PromptRefused, GateBypassed;
|
|
32077
32489
|
var init_harness = __esm({
|
|
32078
32490
|
"../../src/core/harness/pi/harness.ts"() {
|
|
32079
32491
|
"use strict";
|
|
@@ -32088,6 +32500,8 @@ var init_harness = __esm({
|
|
|
32088
32500
|
init_transport();
|
|
32089
32501
|
NATIVE_WORKSPACE_TOOLS = /* @__PURE__ */ new Set(["bash", "read_file", "write_file"]);
|
|
32090
32502
|
FINALE_TIMEOUT_MS2 = 3 * 6e4;
|
|
32503
|
+
CALL_SEEN_WAIT_MS = 3e3;
|
|
32504
|
+
CALL_SEEN_TICK_MS = 50;
|
|
32091
32505
|
CONTINUE_PROMPT = "Continue where you left off: the bot restarted mid-run, so re-check the effects of your last command before relying on them.";
|
|
32092
32506
|
PromptRefused = class extends Error {
|
|
32093
32507
|
constructor(reason) {
|
|
@@ -32942,6 +33356,7 @@ async function runLoop2(deps, ctx) {
|
|
|
32942
33356
|
executor,
|
|
32943
33357
|
resident: round2.selection.resident !== void 0
|
|
32944
33358
|
});
|
|
33359
|
+
ctx.workspaceFiles?.record(staged.outcomes);
|
|
32945
33360
|
return staged.line;
|
|
32946
33361
|
} : void 0;
|
|
32947
33362
|
let artifactSeq = 0;
|
|
@@ -32990,10 +33405,19 @@ async function runLoop2(deps, ctx) {
|
|
|
32990
33405
|
});
|
|
32991
33406
|
if (harness === "pi" && piFacts) {
|
|
32992
33407
|
const container = deps.harness?.containerFor?.(executor, profile.machine) ?? piContainerFor(executor, profile.machine);
|
|
32993
|
-
await container.
|
|
32994
|
-
|
|
32995
|
-
|
|
32996
|
-
|
|
33408
|
+
const here = await container.identity();
|
|
33409
|
+
if (piFacts.container === void 0 || here === void 0 || piFacts.container === here) {
|
|
33410
|
+
await container.kill(piFacts.pid).catch(() => {
|
|
33411
|
+
});
|
|
33412
|
+
if (piFacts.root !== void 0) await container.remove(piRunPathsAt(piFacts.root)).catch(() => {
|
|
33413
|
+
});
|
|
33414
|
+
} else {
|
|
33415
|
+
onEvent({
|
|
33416
|
+
type: "run_note",
|
|
33417
|
+
kind: "resumed",
|
|
33418
|
+
summary: `the run's pi (pid ${piFacts.pid}) ran in container ${piFacts.container}, not the one this run was handed (${here}): it was not ended here`
|
|
33419
|
+
});
|
|
33420
|
+
}
|
|
32997
33421
|
}
|
|
32998
33422
|
answer = answerUnderEnding(finish2.answer, loopEnding, agent.maxMinutes);
|
|
32999
33423
|
} else if (harness === "pi") {
|
|
@@ -33038,7 +33462,7 @@ async function runLoop2(deps, ctx) {
|
|
|
33038
33462
|
messages,
|
|
33039
33463
|
tools: relayedTools(mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools)),
|
|
33040
33464
|
toolContext,
|
|
33041
|
-
...session ? { notepad: () => session.readNotepad() } : {},
|
|
33465
|
+
...session ? { notepad: () => session.readNotepad(), conversation: () => session.readConversation() } : {},
|
|
33042
33466
|
rules: {
|
|
33043
33467
|
checkout: binding?.workspace ?? "/workspace",
|
|
33044
33468
|
...ownBranch !== void 0 ? { branch: ownBranch } : {},
|
|
@@ -33856,7 +34280,7 @@ async function plan(deps, input) {
|
|
|
33856
34280
|
try {
|
|
33857
34281
|
planId = planIdOf(request.planPath);
|
|
33858
34282
|
} catch (err2) {
|
|
33859
|
-
return { ok: false, reply: `\u{1F6AB} ${
|
|
34283
|
+
return { ok: false, reply: `\u{1F6AB} ${describe6(err2)}` };
|
|
33860
34284
|
}
|
|
33861
34285
|
let text;
|
|
33862
34286
|
try {
|
|
@@ -33864,7 +34288,7 @@ async function plan(deps, input) {
|
|
|
33864
34288
|
} catch (err2) {
|
|
33865
34289
|
return {
|
|
33866
34290
|
ok: false,
|
|
33867
|
-
reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${
|
|
34291
|
+
reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${describe6(err2)}`
|
|
33868
34292
|
};
|
|
33869
34293
|
}
|
|
33870
34294
|
const graph = parsePlanGraph(text, planId);
|
|
@@ -33877,7 +34301,7 @@ async function plan(deps, input) {
|
|
|
33877
34301
|
try {
|
|
33878
34302
|
selected = openPlanCursor(graph, request.units).order;
|
|
33879
34303
|
} catch (err2) {
|
|
33880
|
-
return { ok: false, reply: `\u{1F6AB} ${
|
|
34304
|
+
return { ok: false, reply: `\u{1F6AB} ${describe6(err2)}` };
|
|
33881
34305
|
}
|
|
33882
34306
|
return { ok: true, planned: { kind: "plan", planId, path: request.planPath, base, graph, selected, identity: identity2 } };
|
|
33883
34307
|
}
|
|
@@ -33993,7 +34417,7 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
33993
34417
|
try {
|
|
33994
34418
|
answer = await deps.create(instance.id);
|
|
33995
34419
|
} catch (err2) {
|
|
33996
|
-
answer = { kind: "unanswered", reason:
|
|
34420
|
+
answer = { kind: "unanswered", reason: describe6(err2) };
|
|
33997
34421
|
}
|
|
33998
34422
|
switch (answer.kind) {
|
|
33999
34423
|
case "created": {
|
|
@@ -34015,7 +34439,7 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
34015
34439
|
);
|
|
34016
34440
|
}
|
|
34017
34441
|
}
|
|
34018
|
-
var refused3,
|
|
34442
|
+
var refused3, describe6, RUNNING, ENDED;
|
|
34019
34443
|
var init_handOff = __esm({
|
|
34020
34444
|
"../../src/core/coordinator/handOff.ts"() {
|
|
34021
34445
|
"use strict";
|
|
@@ -34023,7 +34447,7 @@ var init_handOff = __esm({
|
|
|
34023
34447
|
init_coordinator();
|
|
34024
34448
|
init_briefs();
|
|
34025
34449
|
refused3 = (reply) => ({ status: "aborted", reply });
|
|
34026
|
-
|
|
34450
|
+
describe6 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
34027
34451
|
RUNNING = /* @__PURE__ */ new Set(["queued", "running", "paused", "waiting", "waitingForPause"]);
|
|
34028
34452
|
ENDED = /* @__PURE__ */ new Set(["complete", "errored", "terminated"]);
|
|
34029
34453
|
}
|
|
@@ -34716,6 +35140,77 @@ var init_settle = __esm({
|
|
|
34716
35140
|
}
|
|
34717
35141
|
});
|
|
34718
35142
|
|
|
35143
|
+
// ../../src/core/dispatch/reattach.ts
|
|
35144
|
+
function carriedWorkspaceBinding(row) {
|
|
35145
|
+
const recorded = workspaceBindingOf(row.state.binding);
|
|
35146
|
+
if (recorded) return recorded;
|
|
35147
|
+
if (row.meta.selection === "resident")
|
|
35148
|
+
return { backend: "resident", ...row.meta.workspace ? { workspace: row.meta.workspace } : {} };
|
|
35149
|
+
if (row.meta.selection === "sandbox" || row.meta.selection === "local") return { backend: row.meta.selection };
|
|
35150
|
+
return void 0;
|
|
35151
|
+
}
|
|
35152
|
+
function lostWorkspaceNote(why, restarts) {
|
|
35153
|
+
const lost = `resumed after a restart: the run's workspace could not be re-attached (${why})`;
|
|
35154
|
+
return restarts ? `${lost}; the run restarts from its request as a new run in this thread` : `${lost}, and the row's request cannot be read, so the run ends here; re-send it to run it again`;
|
|
35155
|
+
}
|
|
35156
|
+
async function abandonLostWorkspace(ctx) {
|
|
35157
|
+
const { refuse: refuse3, card, shell, closeLines, clock: clock3, run: run2, registry, resume, ledgerRun, why } = ctx;
|
|
35158
|
+
const restored = messageFromInbox(resume.row.meta.request ?? {}, resume.row.startedAt);
|
|
35159
|
+
const summary = lostWorkspaceNote(why, restored !== void 0);
|
|
35160
|
+
const note = { type: "run_note", kind: "resumed", summary, at: clock3() };
|
|
35161
|
+
await refuse3("workspace_lost", async () => {
|
|
35162
|
+
registry.publish(run2.id, note);
|
|
35163
|
+
await card.done(
|
|
35164
|
+
shell.close({
|
|
35165
|
+
kind: "not_started",
|
|
35166
|
+
icon: "\u{1F501}",
|
|
35167
|
+
reason: restored ? "workspace lost across the restart; restarting from the request" : "workspace lost across the restart; re-send to run again",
|
|
35168
|
+
...closeLines(clock3(), false)
|
|
35169
|
+
})
|
|
35170
|
+
);
|
|
35171
|
+
if (ledgerRun)
|
|
35172
|
+
await closeResumedRow(
|
|
35173
|
+
ledgerRun,
|
|
35174
|
+
{ ...resume, events: [...resume.events, { ...note, seq: resume.lastSeq + 1 }] },
|
|
35175
|
+
"the run's workspace could not be re-attached"
|
|
35176
|
+
);
|
|
35177
|
+
});
|
|
35178
|
+
console.log(
|
|
35179
|
+
`[resume] ${resume.row.threadKey} run ${resume.row.runId} ${restored ? "restarts from its request" : "ends"}: ${why}`
|
|
35180
|
+
);
|
|
35181
|
+
return restored?.msg;
|
|
35182
|
+
}
|
|
35183
|
+
function prepareRestartTurn(deps, ctx) {
|
|
35184
|
+
const { request, pending: pending2, clock: clock3 } = ctx;
|
|
35185
|
+
const merged = mergeFollowUps(pending2);
|
|
35186
|
+
const receivedAt = clock3();
|
|
35187
|
+
const msg = {
|
|
35188
|
+
...request,
|
|
35189
|
+
...merged ? {
|
|
35190
|
+
text: `${request.text}
|
|
35191
|
+
|
|
35192
|
+
${merged.text}`,
|
|
35193
|
+
...merged.images || request.images ? { images: [...request.images ?? [], ...merged.images ?? []] } : {},
|
|
35194
|
+
...merged.documents || request.documents ? { documents: [...request.documents ?? [], ...merged.documents ?? []] } : {},
|
|
35195
|
+
...merged.staged || request.staged ? { staged: [...request.staged ?? [], ...merged.staged ?? []] } : {}
|
|
35196
|
+
} : {},
|
|
35197
|
+
receivedAt,
|
|
35198
|
+
originAt: void 0
|
|
35199
|
+
};
|
|
35200
|
+
const trace = startRequestRoot(deps, { channel: channelOf(msg.channelId), receivedAt });
|
|
35201
|
+
return { msg, opts: { trace } };
|
|
35202
|
+
}
|
|
35203
|
+
var init_reattach = __esm({
|
|
35204
|
+
"../../src/core/dispatch/reattach.ts"() {
|
|
35205
|
+
"use strict";
|
|
35206
|
+
init_factory();
|
|
35207
|
+
init_inboxMessage();
|
|
35208
|
+
init_requestTrace();
|
|
35209
|
+
init_threadAdmission();
|
|
35210
|
+
init_admission();
|
|
35211
|
+
}
|
|
35212
|
+
});
|
|
35213
|
+
|
|
34719
35214
|
// ../../src/core/dispatch/lineage.ts
|
|
34720
35215
|
function lineageOf(newest) {
|
|
34721
35216
|
if (newest === void 0 || newest.parentRunId === void 0) return void 0;
|
|
@@ -34775,31 +35270,6 @@ var init_lineage = __esm({
|
|
|
34775
35270
|
});
|
|
34776
35271
|
|
|
34777
35272
|
// ../../src/core/dispatch/thread.ts
|
|
34778
|
-
async function readThreadArtifacts(service, thread, warn = (line3) => console.warn(line3)) {
|
|
34779
|
-
const perRun = [];
|
|
34780
|
-
for (const run2 of [...thread].reverse()) {
|
|
34781
|
-
const events = [];
|
|
34782
|
-
let afterSeq;
|
|
34783
|
-
try {
|
|
34784
|
-
for (; ; ) {
|
|
34785
|
-
const r = await service.getRunEvents(run2.id, afterSeq === void 0 ? {} : { afterSeq });
|
|
34786
|
-
if (!r.ok) {
|
|
34787
|
-
warn(`[thread] ${run2.id}: reading its received files stopped after ${events.length} event(s) \u2014 ${r.error}`);
|
|
34788
|
-
break;
|
|
34789
|
-
}
|
|
34790
|
-
events.push(...r.value.events);
|
|
34791
|
-
if (r.value.nextAfterSeq === void 0) break;
|
|
34792
|
-
afterSeq = r.value.nextAfterSeq;
|
|
34793
|
-
}
|
|
34794
|
-
} catch (err2) {
|
|
34795
|
-
warn(
|
|
34796
|
-
`[thread] ${run2.id}: reading its received files failed after ${events.length} event(s) \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
34797
|
-
);
|
|
34798
|
-
}
|
|
34799
|
-
perRun.push(events);
|
|
34800
|
-
}
|
|
34801
|
-
return threadInboundArtifacts(perRun);
|
|
34802
|
-
}
|
|
34803
35273
|
async function readThread(service, threadKey) {
|
|
34804
35274
|
try {
|
|
34805
35275
|
const page2 = await service.listRuns({
|
|
@@ -34841,7 +35311,6 @@ var THREAD_READ_LIMIT;
|
|
|
34841
35311
|
var init_thread = __esm({
|
|
34842
35312
|
"../../src/core/dispatch/thread.ts"() {
|
|
34843
35313
|
"use strict";
|
|
34844
|
-
init_staging();
|
|
34845
35314
|
THREAD_READ_LIMIT = 8;
|
|
34846
35315
|
}
|
|
34847
35316
|
});
|
|
@@ -34961,22 +35430,22 @@ function createRunEnding(deps) {
|
|
|
34961
35430
|
try {
|
|
34962
35431
|
deps.registry.seal(id, replyOk === void 0 ? {} : { replyOk });
|
|
34963
35432
|
} catch (err2) {
|
|
34964
|
-
log(`[ending] seal ${id}: ${
|
|
35433
|
+
log(`[ending] seal ${id}: ${describe7(err2)}`);
|
|
34965
35434
|
}
|
|
34966
35435
|
try {
|
|
34967
35436
|
afterSeal?.();
|
|
34968
35437
|
} catch (err2) {
|
|
34969
|
-
log(`[ending] after seal ${id}: ${
|
|
35438
|
+
log(`[ending] after seal ${id}: ${describe7(err2)}`);
|
|
34970
35439
|
}
|
|
34971
35440
|
}
|
|
34972
35441
|
for (const entry of pending2.splice(0)) {
|
|
34973
35442
|
try {
|
|
34974
35443
|
const result = deps.registry.seal(entry.runId);
|
|
34975
35444
|
void Promise.resolve(entry.write(result, entry.failedAfterFinish)).catch(
|
|
34976
|
-
(err2) => log(`[ending] record ${entry.runId}: ${
|
|
35445
|
+
(err2) => log(`[ending] record ${entry.runId}: ${describe7(err2)}`)
|
|
34977
35446
|
);
|
|
34978
35447
|
} catch (err2) {
|
|
34979
|
-
log(`[ending] record ${entry.runId}: ${
|
|
35448
|
+
log(`[ending] record ${entry.runId}: ${describe7(err2)}`);
|
|
34980
35449
|
}
|
|
34981
35450
|
}
|
|
34982
35451
|
};
|
|
@@ -34986,7 +35455,7 @@ function createRunEnding(deps) {
|
|
|
34986
35455
|
try {
|
|
34987
35456
|
deps.onFinished?.(runId3);
|
|
34988
35457
|
} catch (err2) {
|
|
34989
|
-
log(`[ending] finished ${runId3}: ${
|
|
35458
|
+
log(`[ending] finished ${runId3}: ${describe7(err2)}`);
|
|
34990
35459
|
}
|
|
34991
35460
|
},
|
|
34992
35461
|
register(entry) {
|
|
@@ -35014,7 +35483,7 @@ function createRunEnding(deps) {
|
|
|
35014
35483
|
}
|
|
35015
35484
|
};
|
|
35016
35485
|
}
|
|
35017
|
-
function
|
|
35486
|
+
function describe7(err2) {
|
|
35018
35487
|
return err2 instanceof Error ? err2.message : String(err2);
|
|
35019
35488
|
}
|
|
35020
35489
|
var init_runEnding = __esm({
|
|
@@ -35076,6 +35545,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35076
35545
|
let reserved;
|
|
35077
35546
|
let requestRow;
|
|
35078
35547
|
let fencedWhileAttaching = false;
|
|
35548
|
+
let resumeRowClosed = false;
|
|
35549
|
+
let restartRequest;
|
|
35079
35550
|
const reservationHooks = {
|
|
35080
35551
|
onStop: (mode) => void registered?.control.requestStop(mode),
|
|
35081
35552
|
onFenced: () => {
|
|
@@ -35316,18 +35787,21 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35316
35787
|
await tellLineage({ kind: "started", runId: runId3 });
|
|
35317
35788
|
const staged = !resume && deps.artifacts && msg.staged && msg.staged.length > 0 ? msg.staged : [];
|
|
35318
35789
|
const nextStagedIndex = stagingIndex();
|
|
35790
|
+
const workspaceFiles = new WorkspaceFiles();
|
|
35319
35791
|
const stagedCopies = staged.length > 0 && agent.machine !== "none" ? copyStaged(staged, {
|
|
35320
35792
|
store: deps.artifacts,
|
|
35321
35793
|
threadKey: msg.threadKey,
|
|
35322
35794
|
nextIndex: nextStagedIndex,
|
|
35323
35795
|
publish: (e) => registry.publish(runId3, e)
|
|
35324
35796
|
}) : void 0;
|
|
35325
|
-
const
|
|
35326
|
-
(
|
|
35327
|
-
|
|
35328
|
-
|
|
35329
|
-
|
|
35330
|
-
|
|
35797
|
+
const threadAssets = !resume && deps.artifacts && thread && thread.length > 0 ? readThreadAssets({ runs: runsService, store: deps.artifacts }, msg.threadKey).then(
|
|
35798
|
+
(assets) => assets.filter((a) => a.runId !== runId3)
|
|
35799
|
+
) : void 0;
|
|
35800
|
+
const earlierStaged = threadAssets && agent.machine !== "none" ? threadAssets.then(
|
|
35801
|
+
(assets) => stageThreadArtifacts(
|
|
35802
|
+
assets.filter((a) => a.direction === "in"),
|
|
35803
|
+
{ nextIndex: nextStagedIndex, publish: (e) => registry.publish(runId3, e) }
|
|
35804
|
+
)
|
|
35331
35805
|
) : void 0;
|
|
35332
35806
|
const reservation = await reserveRun(deps, {
|
|
35333
35807
|
msg,
|
|
@@ -35354,6 +35828,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35354
35828
|
reserved = reservation.reserved;
|
|
35355
35829
|
requestRow = reservation.requestRow;
|
|
35356
35830
|
}
|
|
35831
|
+
const reattach = resume ? carriedWorkspaceBinding(resume.row) : void 0;
|
|
35357
35832
|
const attach = await attachWorkspace(deps, {
|
|
35358
35833
|
msg,
|
|
35359
35834
|
io,
|
|
@@ -35365,10 +35840,35 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35365
35840
|
agent,
|
|
35366
35841
|
profile,
|
|
35367
35842
|
repoCtx,
|
|
35368
|
-
root
|
|
35843
|
+
root,
|
|
35844
|
+
...reattach !== void 0 ? { reattach } : {}
|
|
35369
35845
|
});
|
|
35370
35846
|
if (attach.kind === "refused") return ended;
|
|
35847
|
+
if (attach.kind === "reattach_refused") {
|
|
35848
|
+
if (resume) {
|
|
35849
|
+
restartRequest = await abandonLostWorkspace({
|
|
35850
|
+
msg,
|
|
35851
|
+
io,
|
|
35852
|
+
refuse: refuse3,
|
|
35853
|
+
card,
|
|
35854
|
+
shell,
|
|
35855
|
+
closeLines,
|
|
35856
|
+
clock: clock3,
|
|
35857
|
+
run: run2,
|
|
35858
|
+
registry,
|
|
35859
|
+
resume,
|
|
35860
|
+
ledgerRun,
|
|
35861
|
+
why: attach.why
|
|
35862
|
+
});
|
|
35863
|
+
resumeRowClosed = true;
|
|
35864
|
+
}
|
|
35865
|
+
return ended;
|
|
35866
|
+
}
|
|
35371
35867
|
const { round: round2 } = attach;
|
|
35868
|
+
if (resume && ledgerRun) {
|
|
35869
|
+
const rebound = workspaceBindingFor(round2.selection, profile.machine);
|
|
35870
|
+
if (rebound !== void 0) ledgerRun.setState({ binding: rebound });
|
|
35871
|
+
}
|
|
35372
35872
|
const { executor, note, resident } = round2.selection;
|
|
35373
35873
|
if (fencedWhileAttaching) {
|
|
35374
35874
|
console.log(
|
|
@@ -35380,13 +35880,16 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35380
35880
|
}
|
|
35381
35881
|
const earlier2 = earlierStaged ? await earlierStaged : [];
|
|
35382
35882
|
if (staged.length > 0 || earlier2.length > 0) {
|
|
35383
|
-
|
|
35384
|
-
|
|
35883
|
+
let line3;
|
|
35884
|
+
if (stagedCopies || earlier2.length > 0) {
|
|
35885
|
+
const pulled = await pullStaged([...stagedCopies ? await stagedCopies : [], ...earlier2], {
|
|
35385
35886
|
store: deps.artifacts,
|
|
35386
35887
|
executor,
|
|
35387
35888
|
resident: resident !== void 0
|
|
35388
|
-
})
|
|
35389
|
-
|
|
35889
|
+
});
|
|
35890
|
+
workspaceFiles.record(pulled);
|
|
35891
|
+
line3 = attachmentsLine(pulled);
|
|
35892
|
+
} else line3 = noWorkspaceLine(staged);
|
|
35390
35893
|
const request = messages[messages.length - 1];
|
|
35391
35894
|
if (request && request.role === "user" && line3) {
|
|
35392
35895
|
request.content = Array.isArray(request.content) ? [...request.content, { type: "text", text: line3 }] : [
|
|
@@ -35416,6 +35919,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35416
35919
|
if (headGate.headAdopted) publishMeta(repoCtx);
|
|
35417
35920
|
const isPrReview = agent.name === "review" && repoCtx.repo !== void 0 && repoCtx.pr !== void 0;
|
|
35418
35921
|
const isCodingPrRun = agent.toolset === "full";
|
|
35922
|
+
const threadFiles = threadAssets ? (await threadAssets).map((a) => describeAsset(a, workspaceFiles.pathOf(a.key))) : [];
|
|
35419
35923
|
const prompt = await composePrompt(deps, {
|
|
35420
35924
|
msg,
|
|
35421
35925
|
agent,
|
|
@@ -35432,11 +35936,13 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35432
35936
|
root,
|
|
35433
35937
|
...contractBlock !== void 0 && agent.name === "review" ? { contract: contractBlock } : {},
|
|
35434
35938
|
// What the session already knows (session-log item 10): the notepad and
|
|
35435
|
-
// the newest compaction's summary the seed brought back,
|
|
35436
|
-
|
|
35939
|
+
// the newest compaction's summary the seed brought back, and the thread's
|
|
35940
|
+
// files (record 0033) with where each is for this run, for the prompt.
|
|
35941
|
+
...session?.notepad !== void 0 || session?.summary !== void 0 || threadFiles.length > 0 ? {
|
|
35437
35942
|
session: {
|
|
35438
|
-
...session
|
|
35439
|
-
...session
|
|
35943
|
+
...session?.notepad !== void 0 ? { notepad: session.notepad } : {},
|
|
35944
|
+
...session?.summary !== void 0 ? { summary: session.summary } : {},
|
|
35945
|
+
...threadFiles.length > 0 ? { files: threadFiles } : {}
|
|
35440
35946
|
}
|
|
35441
35947
|
} : {}
|
|
35442
35948
|
});
|
|
@@ -35496,7 +36002,14 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35496
36002
|
seed,
|
|
35497
36003
|
...session ? { seedLog: session.log } : {}
|
|
35498
36004
|
});
|
|
35499
|
-
const sessionTools = sessionCapabilityFor(
|
|
36005
|
+
const sessionTools = sessionCapabilityFor(
|
|
36006
|
+
ledgerRun,
|
|
36007
|
+
deps.runLedger,
|
|
36008
|
+
deps.artifacts ? {
|
|
36009
|
+
read: () => readThreadAssets({ runs: runsService, store: deps.artifacts }, msg.threadKey),
|
|
36010
|
+
pathOf: (key) => workspaceFiles.pathOf(key)
|
|
36011
|
+
} : void 0
|
|
36012
|
+
);
|
|
35500
36013
|
const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
|
|
35501
36014
|
{ core: deps, dispatch, registry, clock: clock3 },
|
|
35502
36015
|
{
|
|
@@ -35516,6 +36029,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35516
36029
|
profile,
|
|
35517
36030
|
resolved,
|
|
35518
36031
|
stagingIndex: nextStagedIndex,
|
|
36032
|
+
workspaceFiles,
|
|
35519
36033
|
provider,
|
|
35520
36034
|
model,
|
|
35521
36035
|
messages,
|
|
@@ -35618,7 +36132,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35618
36132
|
clearInterval(setupHeartbeat);
|
|
35619
36133
|
ending.drain(void 0);
|
|
35620
36134
|
if (registered) deps.runBearers?.revoke(registered.id);
|
|
35621
|
-
if (resume && ledgerRun && !runLoopStarted) {
|
|
36135
|
+
if (resume && ledgerRun && !runLoopStarted && !resumeRowClosed) {
|
|
35622
36136
|
const adopted = ledgerRun;
|
|
35623
36137
|
await root.span(
|
|
35624
36138
|
"post.history_write",
|
|
@@ -35633,7 +36147,15 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
35633
36147
|
const stopMode = settled.kind === "handed-on" ? void 0 : settled.stopMode;
|
|
35634
36148
|
ended.status = caught ? "failed" : refused5 ? "refused" : stopMode ? "stopped" : "completed";
|
|
35635
36149
|
root.end(caught ? "error" : "ok", { status: ended.status });
|
|
35636
|
-
if (
|
|
36150
|
+
if (restartRequest) {
|
|
36151
|
+
const pending2 = settled.kind === "handed-on" ? settled.pending : [];
|
|
36152
|
+
const restart2 = prepareRestartTurn(deps, { request: restartRequest, pending: pending2, clock: clock3 });
|
|
36153
|
+
await dispatch(deps, restart2.msg, io, restart2.opts).catch(
|
|
36154
|
+
(err2) => console.error(
|
|
36155
|
+
`[dispatch] ${msg.threadKey} restart after a lost workspace failed: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
36156
|
+
)
|
|
36157
|
+
);
|
|
36158
|
+
} else if (settled.kind === "handed-on") {
|
|
35637
36159
|
const fresh = prepareFreshTurn(deps, { agent: settled.agent, pending: settled.pending, clock: clock3 });
|
|
35638
36160
|
await dispatch(deps, fresh.msg, fresh.io, fresh.opts).catch(
|
|
35639
36161
|
(err2) => console.error(
|
|
@@ -35676,10 +36198,13 @@ var init_dispatcher = __esm({
|
|
|
35676
36198
|
init_contract();
|
|
35677
36199
|
init_codingChild();
|
|
35678
36200
|
init_settle();
|
|
36201
|
+
init_reattach();
|
|
36202
|
+
init_factory();
|
|
35679
36203
|
init_lineage();
|
|
35680
36204
|
init_seed();
|
|
35681
36205
|
init_session();
|
|
35682
36206
|
init_thread();
|
|
36207
|
+
init_threadAssets();
|
|
35683
36208
|
init_select();
|
|
35684
36209
|
init_spawn();
|
|
35685
36210
|
init_runsService();
|
|
@@ -35690,7 +36215,7 @@ var init_dispatcher = __esm({
|
|
|
35690
36215
|
});
|
|
35691
36216
|
|
|
35692
36217
|
// ../../src/core/runHistoryWriter.ts
|
|
35693
|
-
function
|
|
36218
|
+
function describe8(err2) {
|
|
35694
36219
|
return err2 instanceof Error ? err2.message : String(err2);
|
|
35695
36220
|
}
|
|
35696
36221
|
function createRunHistoryWriter(opts) {
|
|
@@ -35707,7 +36232,7 @@ function createRunHistoryWriter(opts) {
|
|
|
35707
36232
|
try {
|
|
35708
36233
|
opts.onPersisted?.(id);
|
|
35709
36234
|
} catch (err2) {
|
|
35710
|
-
opts.warn(`[run-history] onPersisted hook failed for ${id}: ${
|
|
36235
|
+
opts.warn(`[run-history] onPersisted hook failed for ${id}: ${describe8(err2)}`);
|
|
35711
36236
|
}
|
|
35712
36237
|
};
|
|
35713
36238
|
const attemptAll = async (record2, flag2, sink, span) => {
|
|
@@ -35730,12 +36255,12 @@ function createRunHistoryWriter(opts) {
|
|
|
35730
36255
|
}
|
|
35731
36256
|
if (err2 instanceof PermanentStoreError) {
|
|
35732
36257
|
failures++;
|
|
35733
|
-
opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${
|
|
36258
|
+
opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${describe8(err2)}`);
|
|
35734
36259
|
return;
|
|
35735
36260
|
}
|
|
35736
36261
|
if (attempt >= attempts) {
|
|
35737
36262
|
failures++;
|
|
35738
|
-
opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${
|
|
36263
|
+
opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${describe8(err2)}`);
|
|
35739
36264
|
return;
|
|
35740
36265
|
}
|
|
35741
36266
|
await sleep2(jittered(RUN_HISTORY_RETRY_DELAYS_MS[attempt - 1]));
|
|
@@ -35755,7 +36280,7 @@ function createRunHistoryWriter(opts) {
|
|
|
35755
36280
|
}
|
|
35756
36281
|
const p = attemptAll(record2, flag2, writeOpts?.via ?? opts.store, writeOpts?.span).catch((err2) => {
|
|
35757
36282
|
failures++;
|
|
35758
|
-
opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${
|
|
36283
|
+
opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${describe8(err2)}`);
|
|
35759
36284
|
}).finally(() => {
|
|
35760
36285
|
if (flag2) provisionalFlags.delete(flag2);
|
|
35761
36286
|
void inFlight.delete(p);
|
|
@@ -36172,7 +36697,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36172
36697
|
return { outcome: "untracked" };
|
|
36173
36698
|
}
|
|
36174
36699
|
if (err2 instanceof PermanentStoreError || attempt >= claimAttempts) {
|
|
36175
|
-
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${
|
|
36700
|
+
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${describe9(err2)}`);
|
|
36176
36701
|
return { outcome: "untracked" };
|
|
36177
36702
|
}
|
|
36178
36703
|
await sleep2(RETRY_MS[Math.min(attempt, RETRY_MS.length) - 1]);
|
|
@@ -36334,7 +36859,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36334
36859
|
if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
|
|
36335
36860
|
else this.seeded = true;
|
|
36336
36861
|
} catch (err2) {
|
|
36337
|
-
this.detach(`seed failed: ${
|
|
36862
|
+
this.detach(`seed failed: ${describe9(err2)}`);
|
|
36338
36863
|
}
|
|
36339
36864
|
}
|
|
36340
36865
|
async step(report2) {
|
|
@@ -36361,7 +36886,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36361
36886
|
return;
|
|
36362
36887
|
} catch (err2) {
|
|
36363
36888
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
36364
|
-
this.detach(`step ${record2.step} failed: ${
|
|
36889
|
+
this.detach(`step ${record2.step} failed: ${describe9(err2)}`);
|
|
36365
36890
|
return;
|
|
36366
36891
|
}
|
|
36367
36892
|
await sleep2(RETRY_MS[0]);
|
|
@@ -36394,7 +36919,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36394
36919
|
} catch (err2) {
|
|
36395
36920
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
36396
36921
|
this.stateDirty = true;
|
|
36397
|
-
warn(`[ledger] ${this.threadKey} state not written: ${
|
|
36922
|
+
warn(`[ledger] ${this.threadKey} state not written: ${describe9(err2)}`);
|
|
36398
36923
|
return;
|
|
36399
36924
|
}
|
|
36400
36925
|
await sleep2(RETRY_MS[0]);
|
|
@@ -36412,7 +36937,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36412
36937
|
this.detach(`finishing refused (fenced)`);
|
|
36413
36938
|
return "fenced";
|
|
36414
36939
|
} catch (err2) {
|
|
36415
|
-
warn(`[ledger] ${this.threadKey} finishing failed: ${
|
|
36940
|
+
warn(`[ledger] ${this.threadKey} finishing failed: ${describe9(err2)}`);
|
|
36416
36941
|
return "unavailable";
|
|
36417
36942
|
}
|
|
36418
36943
|
}
|
|
@@ -36427,7 +36952,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36427
36952
|
warn(`[ledger] ${this.threadKey} abandon refused (fenced) \u2014 the row is another generation's`);
|
|
36428
36953
|
} catch (err2) {
|
|
36429
36954
|
warn(
|
|
36430
|
-
`[ledger] ${this.threadKey} abandon failed: ${
|
|
36955
|
+
`[ledger] ${this.threadKey} abandon failed: ${describe9(err2)} \u2014 the sweep will take the row once its lease lapses`
|
|
36431
36956
|
);
|
|
36432
36957
|
}
|
|
36433
36958
|
}
|
|
@@ -36448,7 +36973,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36448
36973
|
this.onStop(result.stop);
|
|
36449
36974
|
}
|
|
36450
36975
|
} catch (err2) {
|
|
36451
|
-
warn(`[ledger] ${this.threadKey} heartbeat failed: ${
|
|
36976
|
+
warn(`[ledger] ${this.threadKey} heartbeat failed: ${describe9(err2)}`);
|
|
36452
36977
|
}
|
|
36453
36978
|
}
|
|
36454
36979
|
stopHeartbeat() {
|
|
@@ -36526,7 +37051,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36526
37051
|
warn(`[ledger] inbox push refused for run ${runId3}: no live row \u2014 the follow-up rides in memory only`);
|
|
36527
37052
|
return void 0;
|
|
36528
37053
|
} catch (err2) {
|
|
36529
|
-
warn(`[ledger] inbox push failed for run ${runId3}: ${
|
|
37054
|
+
warn(`[ledger] inbox push failed for run ${runId3}: ${describe9(err2)} \u2014 the follow-up rides in memory only`);
|
|
36530
37055
|
return void 0;
|
|
36531
37056
|
}
|
|
36532
37057
|
},
|
|
@@ -36535,7 +37060,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
36535
37060
|
return await ledger.readInbox(runId3, afterSeq);
|
|
36536
37061
|
} catch (err2) {
|
|
36537
37062
|
warn(
|
|
36538
|
-
err2 instanceof RouteMissingError ? `[ledger] state Worker has no inbox read route \u2014 run ${runId3} resumes with the reclaim's inbox snapshot only` : `[ledger] inbox read failed for run ${runId3}: ${
|
|
37063
|
+
err2 instanceof RouteMissingError ? `[ledger] state Worker has no inbox read route \u2014 run ${runId3} resumes with the reclaim's inbox snapshot only` : `[ledger] inbox read failed for run ${runId3}: ${describe9(err2)} \u2014 resuming with the reclaim's snapshot only`
|
|
36539
37064
|
);
|
|
36540
37065
|
return [];
|
|
36541
37066
|
}
|
|
@@ -36557,12 +37082,12 @@ function createLedgerWriteThrough(opts) {
|
|
|
36557
37082
|
for (const r of candidates) if (markedSet.has(r.runId)) r.markHandedOff();
|
|
36558
37083
|
return { marked };
|
|
36559
37084
|
} catch (err2) {
|
|
36560
|
-
return { marked: [], failed:
|
|
37085
|
+
return { marked: [], failed: describe9(err2) };
|
|
36561
37086
|
}
|
|
36562
37087
|
}
|
|
36563
37088
|
};
|
|
36564
37089
|
}
|
|
36565
|
-
var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS,
|
|
37090
|
+
var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS, describe9;
|
|
36566
37091
|
var init_writeThrough = __esm({
|
|
36567
37092
|
"../../src/core/runLedger/writeThrough.ts"() {
|
|
36568
37093
|
"use strict";
|
|
@@ -36642,7 +37167,7 @@ var init_writeThrough = __esm({
|
|
|
36642
37167
|
}
|
|
36643
37168
|
};
|
|
36644
37169
|
RETRY_MS = [200, 800];
|
|
36645
|
-
|
|
37170
|
+
describe9 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
36646
37171
|
}
|
|
36647
37172
|
});
|
|
36648
37173
|
|
|
@@ -41288,7 +41813,7 @@ async function reclaimRuns(opts) {
|
|
|
41288
41813
|
reclaimed = await ledger.reclaim(gen, now(), LEASE_MS);
|
|
41289
41814
|
} catch (err2) {
|
|
41290
41815
|
warn(
|
|
41291
|
-
err2 instanceof RouteMissingError ? "[reclaim] state Worker has no run-ledger routes \u2014 nothing to take over" : `[reclaim] reclaim failed: ${
|
|
41816
|
+
err2 instanceof RouteMissingError ? "[reclaim] state Worker has no run-ledger routes \u2014 nothing to take over" : `[reclaim] reclaim failed: ${describe10(err2)} \u2014 runs the last generation left stay on the ledger until the next boot`
|
|
41292
41817
|
);
|
|
41293
41818
|
return outcome;
|
|
41294
41819
|
}
|
|
@@ -41356,8 +41881,8 @@ async function reclaimRuns(opts) {
|
|
|
41356
41881
|
`[reclaim] ${row.runId} ${row.threadKey} closed ${status3} (from ${run2.reclaimedFrom}; ${why}; ${events.length} event(s))`
|
|
41357
41882
|
);
|
|
41358
41883
|
} catch (err2) {
|
|
41359
|
-
outcome.failed.push({ runId: row.runId, error:
|
|
41360
|
-
warn(`[reclaim] ${row.runId} ${row.threadKey}: ${
|
|
41884
|
+
outcome.failed.push({ runId: row.runId, error: describe10(err2) });
|
|
41885
|
+
warn(`[reclaim] ${row.runId} ${row.threadKey}: ${describe10(err2)} \u2014 left on the ledger for the next boot`);
|
|
41361
41886
|
}
|
|
41362
41887
|
}
|
|
41363
41888
|
try {
|
|
@@ -41373,7 +41898,7 @@ async function reclaimRuns(opts) {
|
|
|
41373
41898
|
});
|
|
41374
41899
|
}
|
|
41375
41900
|
} catch (err2) {
|
|
41376
|
-
warn(`[reclaim] listing live runs failed: ${
|
|
41901
|
+
warn(`[reclaim] listing live runs failed: ${describe10(err2)} \u2014 the card sweep runs without the ledger's guard`);
|
|
41377
41902
|
}
|
|
41378
41903
|
if (reclaimed.length > 0 || outcome.liveElsewhere.length > 0) {
|
|
41379
41904
|
log(
|
|
@@ -41395,7 +41920,7 @@ function startReclaimSweep(opts) {
|
|
|
41395
41920
|
await opts.onOutcome(outcome);
|
|
41396
41921
|
}
|
|
41397
41922
|
} catch (err2) {
|
|
41398
|
-
warn(`[reclaim] sweep failed: ${
|
|
41923
|
+
warn(`[reclaim] sweep failed: ${describe10(err2)}`);
|
|
41399
41924
|
} finally {
|
|
41400
41925
|
running = false;
|
|
41401
41926
|
}
|
|
@@ -41421,7 +41946,7 @@ async function completenessVerdict(ledger, row, lastStep) {
|
|
|
41421
41946
|
const source2 = transcriptSource(row.meta);
|
|
41422
41947
|
transcript = source2.kind === "session" ? await ledger.readSession(source2.key, source2.from) : await ledger.readTranscript(row.runId);
|
|
41423
41948
|
} catch (err2) {
|
|
41424
|
-
return { resumable: false, why: `transcript unreadable (${
|
|
41949
|
+
return { resumable: false, why: `transcript unreadable (${describe10(err2)})` };
|
|
41425
41950
|
}
|
|
41426
41951
|
if (!transcript.complete) return { resumable: false, why: `transcript incomplete: ${transcript.gap}` };
|
|
41427
41952
|
const verdict = transcriptCompleteness({
|
|
@@ -41442,7 +41967,7 @@ async function completenessVerdict(ledger, row, lastStep) {
|
|
|
41442
41967
|
return { resumable: false, why: verdict.why };
|
|
41443
41968
|
}
|
|
41444
41969
|
}
|
|
41445
|
-
var TERMINAL,
|
|
41970
|
+
var TERMINAL, describe10;
|
|
41446
41971
|
var init_boot = __esm({
|
|
41447
41972
|
"../../src/core/boot.ts"() {
|
|
41448
41973
|
"use strict";
|
|
@@ -41459,7 +41984,7 @@ var init_boot = __esm({
|
|
|
41459
41984
|
"failed",
|
|
41460
41985
|
"interrupted"
|
|
41461
41986
|
]);
|
|
41462
|
-
|
|
41987
|
+
describe10 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
41463
41988
|
}
|
|
41464
41989
|
});
|
|
41465
41990
|
|
|
@@ -42308,6 +42833,17 @@ var init_toolRules = __esm({
|
|
|
42308
42833
|
});
|
|
42309
42834
|
|
|
42310
42835
|
// ../../src/core/harness/pi/relay.ts
|
|
42836
|
+
async function relayToolCall(harness, calls, ask, opts = {}) {
|
|
42837
|
+
const answer = calls.join(ask.toolCallId, () => runRelayedTool(harness, ask, { signal: calls.signal }));
|
|
42838
|
+
const window = new AbortController();
|
|
42839
|
+
const sleep2 = opts.sleep ?? sleepUnlessAborted;
|
|
42840
|
+
const progress = await Promise.race([
|
|
42841
|
+
answer.then((a) => ({ done: true, answer: a })),
|
|
42842
|
+
sleep2(opts.windowMs ?? RELAY_POLL_WINDOW_MS, window.signal).then(() => ({ done: false }))
|
|
42843
|
+
]);
|
|
42844
|
+
window.abort();
|
|
42845
|
+
return progress;
|
|
42846
|
+
}
|
|
42311
42847
|
function relayedToolDefinitions(harness) {
|
|
42312
42848
|
return harness.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
42313
42849
|
}
|
|
@@ -42328,18 +42864,23 @@ function authorizeToolCall(harness, ask) {
|
|
|
42328
42864
|
if (verdict.verdict === "allowed") return { allow: true };
|
|
42329
42865
|
return refuse3(verdict.reason);
|
|
42330
42866
|
}
|
|
42331
|
-
async function runRelayedTool(harness, ask) {
|
|
42867
|
+
async function runRelayedTool(harness, ask, opts = {}) {
|
|
42332
42868
|
const tool = harness.tools.find((t) => t.name === ask.tool);
|
|
42333
42869
|
if (!tool) return { content: [{ type: "text", text: `Unknown tool: ${ask.tool}` }], isError: true };
|
|
42334
42870
|
const span = harness.toolSpan(ask.toolCallId);
|
|
42335
42871
|
const base = harness.toolContext;
|
|
42336
|
-
const
|
|
42337
|
-
|
|
42338
|
-
span
|
|
42339
|
-
|
|
42340
|
-
|
|
42341
|
-
|
|
42342
|
-
|
|
42872
|
+
const readConversation = base.conversation;
|
|
42873
|
+
const ctx = {
|
|
42874
|
+
...span ? {
|
|
42875
|
+
...base,
|
|
42876
|
+
span,
|
|
42877
|
+
executor: new TracingExecutor(base.executor, span, harness.backend),
|
|
42878
|
+
publish: (e) => harness.emit(withSpanId2(e, span.id)),
|
|
42879
|
+
...base.github?.api.withSpan ? { github: { ...base.github, api: base.github.api.withSpan(span) } } : {}
|
|
42880
|
+
} : { ...base, publish: (e) => harness.emit(e) },
|
|
42881
|
+
...opts.signal && !base.signal ? { signal: opts.signal } : {},
|
|
42882
|
+
...readConversation && harness.callSeen ? { conversation: () => harness.callSeen(ask.toolCallId).then(() => readConversation()) } : {}
|
|
42883
|
+
};
|
|
42343
42884
|
try {
|
|
42344
42885
|
const input = typeof ask.input === "object" && ask.input !== null ? ask.input : {};
|
|
42345
42886
|
const output = await tool.run(input, ctx);
|
|
@@ -42367,30 +42908,64 @@ function withSpanId2(e, spanId) {
|
|
|
42367
42908
|
return e;
|
|
42368
42909
|
}
|
|
42369
42910
|
}
|
|
42370
|
-
var HarnessRegistry;
|
|
42911
|
+
var HarnessRegistry, RELAY_POLL_WINDOW_MS, RelayedCalls;
|
|
42371
42912
|
var init_relay = __esm({
|
|
42372
42913
|
"../../src/core/harness/pi/relay.ts"() {
|
|
42373
42914
|
"use strict";
|
|
42374
42915
|
init_tracingExecutor();
|
|
42375
42916
|
init_types3();
|
|
42917
|
+
init_awaitChildren();
|
|
42376
42918
|
init_runEvents();
|
|
42377
42919
|
init_toolRules();
|
|
42378
42920
|
HarnessRegistry = class {
|
|
42379
42921
|
live = /* @__PURE__ */ new Map();
|
|
42380
|
-
/** The run drives a pi from here
|
|
42922
|
+
/** The run drives a pi from here, its relayed calls kept beside it; the
|
|
42923
|
+
* returned function forgets it and ends whatever call still runs. */
|
|
42381
42924
|
register(harness) {
|
|
42382
|
-
|
|
42925
|
+
const entry = { harness, calls: new RelayedCalls() };
|
|
42926
|
+
this.live.set(harness.runId, entry);
|
|
42383
42927
|
return () => {
|
|
42384
|
-
if (this.live.get(harness.runId)
|
|
42928
|
+
if (this.live.get(harness.runId) !== entry) return;
|
|
42929
|
+
this.live.delete(harness.runId);
|
|
42930
|
+
entry.calls.end();
|
|
42385
42931
|
};
|
|
42386
42932
|
}
|
|
42387
42933
|
get(runId3) {
|
|
42388
|
-
return this.live.get(runId3);
|
|
42934
|
+
return this.live.get(runId3)?.harness;
|
|
42935
|
+
}
|
|
42936
|
+
/** The run's relayed calls, for as long as it is registered. */
|
|
42937
|
+
calls(runId3) {
|
|
42938
|
+
return this.live.get(runId3)?.calls;
|
|
42389
42939
|
}
|
|
42390
42940
|
size() {
|
|
42391
42941
|
return this.live.size;
|
|
42392
42942
|
}
|
|
42393
42943
|
};
|
|
42944
|
+
RELAY_POLL_WINDOW_MS = 3e4;
|
|
42945
|
+
RelayedCalls = class {
|
|
42946
|
+
calls = /* @__PURE__ */ new Map();
|
|
42947
|
+
ending = new AbortController();
|
|
42948
|
+
get size() {
|
|
42949
|
+
return this.calls.size;
|
|
42950
|
+
}
|
|
42951
|
+
/** Aborted when the run ends: the signal every call here runs under. */
|
|
42952
|
+
get signal() {
|
|
42953
|
+
return this.ending.signal;
|
|
42954
|
+
}
|
|
42955
|
+
/** The call's answer as a promise: started by `start` on the first ask, the same promise after. */
|
|
42956
|
+
join(callId, start3) {
|
|
42957
|
+
let answer = this.calls.get(callId);
|
|
42958
|
+
if (!answer) {
|
|
42959
|
+
answer = start3();
|
|
42960
|
+
this.calls.set(callId, answer);
|
|
42961
|
+
}
|
|
42962
|
+
return answer;
|
|
42963
|
+
}
|
|
42964
|
+
end() {
|
|
42965
|
+
this.ending.abort();
|
|
42966
|
+
this.calls.clear();
|
|
42967
|
+
}
|
|
42968
|
+
};
|
|
42394
42969
|
}
|
|
42395
42970
|
});
|
|
42396
42971
|
|
|
@@ -42439,7 +43014,15 @@ async function handleHarnessRequest(deps, req) {
|
|
|
42439
43014
|
deps.log?.(`[harness] run=${door.runId} authorize ${ask.tool} \u2192 ${answer2.allow ? "allow" : "refuse"}`);
|
|
42440
43015
|
return { status: 200, body: { ...answer2 } };
|
|
42441
43016
|
}
|
|
42442
|
-
const
|
|
43017
|
+
const progress = await relayToolCall(harness, deps.harnesses.calls(door.runId), ask, {
|
|
43018
|
+
...deps.relayWindowMs !== void 0 ? { windowMs: deps.relayWindowMs } : {},
|
|
43019
|
+
...deps.sleep ? { sleep: deps.sleep } : {}
|
|
43020
|
+
});
|
|
43021
|
+
if (!progress.done) {
|
|
43022
|
+
deps.log?.(`[harness] run=${door.runId} tool ${ask.tool} \u2192 pending`);
|
|
43023
|
+
return { status: 202, body: { pending: true, toolCallId: ask.toolCallId } };
|
|
43024
|
+
}
|
|
43025
|
+
const { answer } = progress;
|
|
42443
43026
|
deps.log?.(`[harness] run=${door.runId} tool ${ask.tool} \u2192 ${answer.isError ? "error" : "ok"}`);
|
|
42444
43027
|
return { status: 200, body: { ...answer } };
|
|
42445
43028
|
}
|
|
@@ -42775,7 +43358,7 @@ async function openThreadFromRequester(deps, instance, lead, at) {
|
|
|
42775
43358
|
}
|
|
42776
43359
|
};
|
|
42777
43360
|
} catch (err2) {
|
|
42778
|
-
return { ok: false, response: json(502, { ok: false, error: "thread_failed", message:
|
|
43361
|
+
return { ok: false, response: json(502, { ok: false, error: "thread_failed", message: describe11(err2), at }) };
|
|
42779
43362
|
}
|
|
42780
43363
|
}
|
|
42781
43364
|
async function ensureReviewThread(deps, instance, row, at) {
|
|
@@ -42901,8 +43484,8 @@ async function spawn6(body, deps) {
|
|
|
42901
43484
|
try {
|
|
42902
43485
|
turn = await composeChild(req.brief, instance, row, briefReaders(deps, instance, io));
|
|
42903
43486
|
} catch (err2) {
|
|
42904
|
-
log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${
|
|
42905
|
-
return json(502, { ok: false, error: "brief_failed", message:
|
|
43487
|
+
log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe11(err2)}`);
|
|
43488
|
+
return json(502, { ok: false, error: "brief_failed", message: describe11(err2), at });
|
|
42906
43489
|
}
|
|
42907
43490
|
} else {
|
|
42908
43491
|
turn = { prompt: req.prompt };
|
|
@@ -42952,7 +43535,7 @@ async function spawn6(body, deps) {
|
|
|
42952
43535
|
);
|
|
42953
43536
|
void settled.then((end) => {
|
|
42954
43537
|
if (end.kind === "threw")
|
|
42955
|
-
log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${
|
|
43538
|
+
log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${describe11(end.err)}`);
|
|
42956
43539
|
});
|
|
42957
43540
|
const first = await Promise.race([started.then((id) => ({ kind: "started", id })), settled]);
|
|
42958
43541
|
if (first.kind === "started" || startedId !== void 0) {
|
|
@@ -42960,7 +43543,7 @@ async function spawn6(body, deps) {
|
|
|
42960
43543
|
log(`[coordinator] ${instance.id} ${req.step}: spawned ${req.preset} run ${runId3} in ${threadKey}`);
|
|
42961
43544
|
return json(200, { ok: true, runId: runId3, threadKey, at });
|
|
42962
43545
|
}
|
|
42963
|
-
if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message:
|
|
43546
|
+
if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message: describe11(first.err), at });
|
|
42964
43547
|
const refusal = first.outcome.refusal;
|
|
42965
43548
|
if (refusal === "coordinator_thread_live") {
|
|
42966
43549
|
const now = await liveOnThread(deps.runs, instance, threadKey);
|
|
@@ -43113,7 +43696,7 @@ async function prCheck(body, deps) {
|
|
|
43113
43696
|
at
|
|
43114
43697
|
});
|
|
43115
43698
|
} catch (err2) {
|
|
43116
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
43699
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe11(err2), at });
|
|
43117
43700
|
}
|
|
43118
43701
|
}
|
|
43119
43702
|
async function plan2(body, deps) {
|
|
@@ -43222,7 +43805,7 @@ async function branch(body, deps) {
|
|
|
43222
43805
|
await deps.createBranchRef(instance.repo, name, base);
|
|
43223
43806
|
return json(200, { ok: true, branch: name, base, at });
|
|
43224
43807
|
} catch (err2) {
|
|
43225
|
-
return json(200, { ok: false, reason:
|
|
43808
|
+
return json(200, { ok: false, reason: describe11(err2), at });
|
|
43226
43809
|
}
|
|
43227
43810
|
}
|
|
43228
43811
|
function unitLines(units) {
|
|
@@ -43274,7 +43857,7 @@ async function round(body, deps) {
|
|
|
43274
43857
|
instance,
|
|
43275
43858
|
units.map((u) => u.unit === updated.unit ? updated : u)
|
|
43276
43859
|
).catch(
|
|
43277
|
-
(err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${
|
|
43860
|
+
(err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${describe11(err2)}`)
|
|
43278
43861
|
);
|
|
43279
43862
|
return json(200, { ok: true, at });
|
|
43280
43863
|
}
|
|
@@ -43308,7 +43891,7 @@ async function unitEnd(body, deps) {
|
|
|
43308
43891
|
told = true;
|
|
43309
43892
|
} catch (err2) {
|
|
43310
43893
|
(deps.log ?? console.warn)(
|
|
43311
|
-
`[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${
|
|
43894
|
+
`[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${describe11(err2)}`
|
|
43312
43895
|
);
|
|
43313
43896
|
}
|
|
43314
43897
|
}
|
|
@@ -43322,7 +43905,7 @@ ${ending.report}` + (rendered !== void 0 ? `
|
|
|
43322
43905
|
${rendered}` : "");
|
|
43323
43906
|
await deps.github.commentIssue(instance.repo, row.issue, comment).catch(
|
|
43324
43907
|
(err2) => (deps.log ?? console.warn)(
|
|
43325
|
-
`[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${
|
|
43908
|
+
`[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${describe11(err2)}`
|
|
43326
43909
|
)
|
|
43327
43910
|
);
|
|
43328
43911
|
}
|
|
@@ -43377,7 +43960,7 @@ async function merge(body, deps, subject2) {
|
|
|
43377
43960
|
try {
|
|
43378
43961
|
facts2 = await deps.fetchPrFacts(pr);
|
|
43379
43962
|
} catch (err2) {
|
|
43380
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
43963
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe11(err2), at });
|
|
43381
43964
|
}
|
|
43382
43965
|
if (facts2 === void 0)
|
|
43383
43966
|
return json(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
|
|
@@ -43429,7 +44012,7 @@ async function merge(body, deps, subject2) {
|
|
|
43429
44012
|
title: facts2.title ?? `Merge pull request #${pr.number}`
|
|
43430
44013
|
});
|
|
43431
44014
|
} catch (err2) {
|
|
43432
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
44015
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe11(err2), at });
|
|
43433
44016
|
}
|
|
43434
44017
|
if (!merged.ok) {
|
|
43435
44018
|
log(
|
|
@@ -43604,13 +44187,13 @@ function createAdminCoordinatorHandler(deps) {
|
|
|
43604
44187
|
}
|
|
43605
44188
|
write(res, await answerCoordinatorStep(door, read.body, deps));
|
|
43606
44189
|
} catch (err2) {
|
|
43607
|
-
(deps.log ?? console.error)(`[coordinator] ${
|
|
44190
|
+
(deps.log ?? console.error)(`[coordinator] ${describe11(err2)}`);
|
|
43608
44191
|
write(res, json(500, { ok: false, error: "internal error" }));
|
|
43609
44192
|
}
|
|
43610
44193
|
})();
|
|
43611
44194
|
};
|
|
43612
44195
|
}
|
|
43613
|
-
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json,
|
|
44196
|
+
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json, describe11, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
43614
44197
|
var init_adminCoordinator = __esm({
|
|
43615
44198
|
"../../src/channels/adminCoordinator.ts"() {
|
|
43616
44199
|
"use strict";
|
|
@@ -43643,7 +44226,7 @@ var init_adminCoordinator = __esm({
|
|
|
43643
44226
|
};
|
|
43644
44227
|
invalid = (error) => ({ ok: false, error });
|
|
43645
44228
|
json = (status3, body) => ({ status: status3, body });
|
|
43646
|
-
|
|
44229
|
+
describe11 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
43647
44230
|
EVERY_RUN = { kind: "all" };
|
|
43648
44231
|
REVIEW_POSTED_CHECKS = 3;
|
|
43649
44232
|
REVIEW_POSTED_RECHECK_MS = 2e3;
|