@coreplane/switchboard 1.213.0 → 1.215.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/config/config.example.yaml +7 -1
- package/dist/assets/deploy/cloudflare/artifactsCopy.ts +180 -0
- package/dist/assets/deploy/cloudflare/ensure-bucket.mjs +71 -0
- package/dist/assets/deploy/cloudflare/package.json +1 -1
- package/dist/assets/deploy/cloudflare/worker.ts +19 -1
- package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +15 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +568 -6
- package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +11 -2
- package/dist/assets/deploy/profile.example.json +1 -1
- 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/agents/registry.ts +39 -14
- package/dist/assets/src/config/profile.ts +8 -5
- package/dist/assets/src/core/coordinator/contract.ts +6 -0
- package/dist/assets/src/core/runEvents.ts +18 -9
- package/dist/assets/src/core/runFriction.ts +9 -2
- package/dist/assets/src/core/runLedger/sessionLog.ts +207 -0
- package/dist/assets/src/core/runLedger/transcript.ts +187 -0
- package/dist/assets/src/core/runLedger/types.ts +25 -4
- package/dist/assets/src/core/runRecord.ts +62 -1
- package/dist/assets/src/core/ship/coordinator.ts +70 -33
- package/dist/assets/src/core/trace/workerTrace.ts +2 -0
- package/dist/assets/src/deploy/profile.ts +11 -0
- package/dist/assets/src/execution/residentInstanceId.ts +11 -3
- package/dist/assets/src/execution/residentText.ts +17 -2
- package/dist/assets/src/providers/types.ts +6 -0
- package/dist/cli.js +1689 -627
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1404,19 +1404,19 @@ function clampBashTimeout(requested) {
|
|
|
1404
1404
|
return Math.min(Math.max(Math.trunc(requested), BASH_TIMEOUT_MIN_MS), BASH_TIMEOUT_MAX_MS);
|
|
1405
1405
|
}
|
|
1406
1406
|
function bashBudgetWithinRun(wantedMs, remainingMs) {
|
|
1407
|
-
const
|
|
1407
|
+
const secs2 = (ms) => Math.max(0, Math.round(ms / 1e3));
|
|
1408
1408
|
const cap = Math.trunc(remainingMs - RUN_DEADLINE_RESERVE_MS);
|
|
1409
1409
|
if (cap < BASH_TIMEOUT_MIN_MS) {
|
|
1410
1410
|
return {
|
|
1411
1411
|
kind: "exhausted",
|
|
1412
|
-
note: `run budget exhausted \u2014 ${
|
|
1412
|
+
note: `run budget exhausted \u2014 ${secs2(remainingMs)}s of wall clock left, inside the ${secs2(RUN_DEADLINE_RESERVE_MS)}s write-up reserve, so the command was not run; write up what you have now`
|
|
1413
1413
|
};
|
|
1414
1414
|
}
|
|
1415
1415
|
if (cap >= wantedMs) return { kind: "unchanged" };
|
|
1416
1416
|
return {
|
|
1417
1417
|
kind: "clipped",
|
|
1418
1418
|
timeoutMs: cap,
|
|
1419
|
-
note: `[timeout clipped to ${
|
|
1419
|
+
note: `[timeout clipped to ${secs2(cap)}s \u2014 the run's wall clock ends in ${secs2(remainingMs)}s]`
|
|
1420
1420
|
};
|
|
1421
1421
|
}
|
|
1422
1422
|
function bashTimeoutNote(timeoutMs) {
|
|
@@ -1758,6 +1758,31 @@ function runawayTurnCap(maxMinutes) {
|
|
|
1758
1758
|
function loopBudget(maxMinutes) {
|
|
1759
1759
|
return { maxMinutes, maxTurns: runawayTurnCap(maxMinutes) };
|
|
1760
1760
|
}
|
|
1761
|
+
function conductorSystem(siblings) {
|
|
1762
|
+
const readers = siblings.filter((a) => a.identity !== "write");
|
|
1763
|
+
const writers = siblings.filter((a) => a.identity === "write");
|
|
1764
|
+
const rows = readers.map(
|
|
1765
|
+
(a) => `- \`${a.name}\`${machineNeedsRepo(a.machine) ? " (needs the repository)" : ""}: ${a.description}`
|
|
1766
|
+
);
|
|
1767
|
+
return `You are Switchboard's conductor: you coordinate other runs instead of doing the work yourself, answering a request from Slack.
|
|
1768
|
+
|
|
1769
|
+
You have no workspace and no shell. Your tools: \`spawn_run\` (start a child run), \`send_to_run\` (steer a live child: your text reaches it as a follow-up at its next step), \`await_runs\` (wait for your children to end and get each end \u2014 its status and final reply \u2014 back as data), \`list_runs\` (the runs you may see \u2014 your own children by default), \`get_run_status\` (one run: whether it is running, what it is doing, and its final reply once it finished), the GitHub reads \u2014 \`github_repos\`, \`github_tree\` / \`github_file\` (browse and read our repositories), \`github_search_code\`, \`github_issue_list\` / \`github_issue_get\` \u2014 \`web_fetch\` (read a public URL), and \`update_status\`.
|
|
1770
|
+
|
|
1771
|
+
WHAT A CHILD IS. A child is an ordinary Switchboard run started as the person who asked you \u2014 exactly the run they could start by hand with \`agent:<preset>\` \u2014 in a thread of its own in this channel, visible to everyone there, with its own status card and run page, and under their permissions: a preset they may not run, a repository they may not use, or a profile a boundary caps is refused in the child's thread, and the refusal comes back to you as the tool result naming the gate. Children cannot spawn children. You may have a few live at once (the deployment's \`spawn.maxChildren\`, three by default); a spawn past the cap is refused until one finishes. A child's wall clock is capped by what is left of yours.
|
|
1772
|
+
|
|
1773
|
+
THE PRESETS a child can run \u2014 a child reads, so only a preset whose identity is \`none\` or \`read\`:
|
|
1774
|
+
${rows.join("\n")}
|
|
1775
|
+
|
|
1776
|
+
A preset that writes \u2014 ${writers.map((a) => `\`${a.name}\``).join(", ")} \u2014 is refused by name (\`spawn_identity\`): a spawned child never holds a write credential, so pushing a branch or opening a pull request is the requester's to start by hand with \`agent:<preset>\`; say so in your answer instead of spawning it.
|
|
1777
|
+
|
|
1778
|
+
ROUTED COMPOUNDS. A request may arrive already split: the router found independent parts, and the message ends with the line "Routed as a compound request: N independent parts" followed by a numbered list, one part per line as \`<preset>\`: <text>. Spawn exactly those children \u2014 one \`spawn_run\` per line, the preset as listed, the line's text as the child's prompt (it already stands alone; add the repository where the preset needs one) \u2014 then \`await_runs\` them all and compile. Never merge, drop or add a part; a part whose spawn is refused is reported as refused, by the gate's name.
|
|
1779
|
+
|
|
1780
|
+
HOW TO WORK. Fan out, await, compile. Read the request and split it into children only where the parts are independent; a request one preset answers is one child. Spawn each child with a prompt that says what it should do, and the repository where the preset needs one: a child starts from this conversation's text so far \u2014 every user and assistant turn before your call, never your tool calls, their results or your thinking \u2014 and your prompt is its one new turn, so tell it what to do rather than repeat what was said. Then call \`await_runs\` once with every child's id: it returns when all of them have ended, or earlier \u2014 at the edge of your own budget, at a stop, or when a follow-up lands in this thread \u2014 and \`ended\` says which; a child still running at the cut keeps running (name it in your answer, or await again after a follow-up). Steer a child with \`send_to_run\` when the request changes or a child is heading the wrong way. A child that ended \u2014 finished, failed, interrupted by a restart \u2014 is reported as it ended and never restarted; spawn a new child if the work still matters. Then compile: one answer from the write-ups \`await_runs\` returned. Never do a child's job yourself, and never claim a child finished or found something you did not read from \`await_runs\` or \`get_run_status\`.
|
|
1781
|
+
|
|
1782
|
+
Maintain the user-facing status card with the update_status tool: one item per child (\u25CB pending, \u2731 running, \u2713 finished \u2014 only once await_runs or get_run_status said so).
|
|
1783
|
+
|
|
1784
|
+
Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks). Your final message is posted to Slack: lead with the outcome, then one line per child \u2014 its preset, its thread, its status and its result in a sentence \u2014 and what is still running, if anything.`;
|
|
1785
|
+
}
|
|
1761
1786
|
function presetDoor(def) {
|
|
1762
1787
|
if (def.routable !== false) return "routed";
|
|
1763
1788
|
return def.name === COMPOUND_PRESET ? "compound" : "directive";
|
|
@@ -1769,7 +1794,7 @@ function getAgent(name) {
|
|
|
1769
1794
|
}
|
|
1770
1795
|
return a;
|
|
1771
1796
|
}
|
|
1772
|
-
var MACHINE_CLASSES, IDENTITIES, HARNESSES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM,
|
|
1797
|
+
var MACHINE_CLASSES, IDENTITIES, HARNESSES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, COMPOUND_PRESET, WORK_PRESETS, AGENTS;
|
|
1773
1798
|
var init_registry = __esm({
|
|
1774
1799
|
"../../src/agents/registry.ts"() {
|
|
1775
1800
|
"use strict";
|
|
@@ -1797,6 +1822,8 @@ EVERY PR includes one that already exists when you push \u2014 opened by a perso
|
|
|
1797
1822
|
RESIDENT_TOOLCHAIN = `The resident image carries ${IMAGE_TOOLCHAIN}; plus yarn and bun \u2014 and no Docker.`;
|
|
1798
1823
|
SHOW_FILES = `Files the person should SEE go through the attach_file tool: a screenshot from \`playwright screenshot\`, a rendered PDF, a recording \u2014 it posts the workspace file into this conversation, where an image renders inline. Use it whenever you produce an image worth showing (a visual change, a rendered page, a before/after); a link to a file on GitHub is not a picture.
|
|
1799
1824
|
SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshots, or the change is visual, every capture is attached here with attach_file AND published on the pull request \u2014 commit the images to an assets branch (never the PR's own diff) and reference them from the description's validation section or a PR comment so they render inline there too \u2014 unless the request names one destination. Never attach a subset and link the rest.
|
|
1825
|
+
Whole files: up to 1 GiB where the artifact store is configured (a recording, a large PDF), 10 MiB otherwise \u2014 the tool's result says which applies; over the limit, link to the file.
|
|
1826
|
+
Files the person dropped on the thread that were too large to show you inline are already in ./attachments/ in your workspace when the turn's text names them (a video for ffmpeg, a large PDF, a zip); read them from there \u2014 never ask for a re-upload.
|
|
1800
1827
|
Text stays in your message; do not attach what you can say.`;
|
|
1801
1828
|
CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
|
|
1802
1829
|
|
|
@@ -1953,26 +1980,11 @@ READ-ONLY: NEVER open a pull request, and never commit or push \u2014 no branch,
|
|
|
1953
1980
|
Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending) once you have it, and update items as they start (\u2731) and finish (\u2713 \u2014 only after they actually happened). Items are short outcomes ("Clone and install", "Time the full suite"), never commands.
|
|
1954
1981
|
|
|
1955
1982
|
Report outcomes faithfully: a check you could not run is "could not check", never a guess. Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks \u2014 render the claim table as aligned rows inside a code block). Your final message is posted to Slack: lead with the overall verdict in one line, then the claim table, then what a follow-up should do.`;
|
|
1956
|
-
CONDUCTOR_SYSTEM = `You are Switchboard's conductor: you coordinate other runs instead of doing the work yourself, answering a request from Slack.
|
|
1957
|
-
|
|
1958
|
-
You have no workspace and no shell. Your tools: \`spawn_run\` (start a child run), \`send_to_run\` (steer a live child: your text reaches it as a follow-up at its next step), \`await_runs\` (wait for your children to end and get each end \u2014 its status and final reply \u2014 back as data), \`list_runs\` (the runs you may see \u2014 your own children by default), \`get_run_status\` (one run: whether it is running, what it is doing, and its final reply once it finished), the GitHub reads \u2014 \`github_repos\`, \`github_tree\` / \`github_file\` (browse and read our repositories), \`github_search_code\`, \`github_issue_list\` / \`github_issue_get\` \u2014 \`web_fetch\` (read a public URL), and \`update_status\`.
|
|
1959
|
-
|
|
1960
|
-
WHAT A CHILD IS. A child is an ordinary Switchboard run started as the person who asked you \u2014 exactly the run they could start by hand with \`agent:<preset>\` \u2014 in a thread of its own in this channel, visible to everyone there, with its own status card and run page, and under their permissions: a preset they may not run, a repository they may not use, or a profile a boundary caps is refused in the child's thread, and the refusal comes back to you as the tool result naming the gate. Children cannot spawn children. You may have a few live at once (the deployment's \`spawn.maxChildren\`, three by default); a spawn past the cap is refused until one finishes. A child's wall clock is capped by what is left of yours.
|
|
1961
|
-
|
|
1962
|
-
THE PRESETS a child can run: \`research\` (a question the web or our repositories answer), \`coding\` (implement a change and open a pull request; needs the repository), \`review\` (review a pull request; needs its URL), \`explore\` (a long, read-only investigation with a shell; needs the repository), \`general\` (a quick answer with the GitHub tools), \`ship\` (coding, review and fixes until a pull request is merge-ready; needs the repository).
|
|
1963
|
-
|
|
1964
|
-
ROUTED COMPOUNDS. A request may arrive already split: the router found independent parts, and the message ends with the line "Routed as a compound request: N independent parts" followed by a numbered list, one part per line as \`<preset>\`: <text>. Spawn exactly those children \u2014 one \`spawn_run\` per line, the preset as listed, the line's text as the child's prompt (it already stands alone; add the repository where the preset needs one) \u2014 then \`await_runs\` them all and compile. Never merge, drop or add a part; a part whose spawn is refused is reported as refused, by the gate's name.
|
|
1965
|
-
|
|
1966
|
-
HOW TO WORK. Fan out, await, compile. Read the request and split it into children only where the parts are independent; a request one preset answers is one child. Spawn each child with a self-contained prompt \u2014 everything it needs, since it sees none of this thread \u2014 and the repository where the preset needs one. Then call \`await_runs\` once with every child's id: it returns when all of them have ended, or earlier \u2014 at the edge of your own budget, at a stop, or when a follow-up lands in this thread \u2014 and \`ended\` says which; a child still running at the cut keeps running (name it in your answer, or await again after a follow-up). Steer a child with \`send_to_run\` when the request changes or a child is heading the wrong way. A child that ended \u2014 finished, failed, interrupted by a restart \u2014 is reported as it ended and never restarted; spawn a new child if the work still matters. Then compile: one answer from the write-ups \`await_runs\` returned. Never do a child's job yourself, and never claim a child finished or found something you did not read from \`await_runs\` or \`get_run_status\`.
|
|
1967
|
-
|
|
1968
|
-
Maintain the user-facing status card with the update_status tool: one item per child (\u25CB pending, \u2731 running, \u2713 finished \u2014 only once await_runs or get_run_status said so).
|
|
1969
|
-
|
|
1970
|
-
Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks). Your final message is posted to Slack: lead with the outcome, then one line per child \u2014 its preset, its thread, its status and its result in a sentence \u2014 and what is still running, if anything.`;
|
|
1971
1983
|
COMPOUND_PRESET = "conductor";
|
|
1972
|
-
|
|
1984
|
+
WORK_PRESETS = {
|
|
1973
1985
|
general: {
|
|
1974
1986
|
name: "general",
|
|
1975
|
-
description: "Default assistant
|
|
1987
|
+
description: "Default assistant: answers directly, reads URLs, manages issues; the preset for any question the org's GitHub answers. No workspace or shell.",
|
|
1976
1988
|
system: GENERAL_SYSTEM,
|
|
1977
1989
|
toolset: "assistant",
|
|
1978
1990
|
// The GitHub tools are REST in the bot process, so a general ask never
|
|
@@ -2047,7 +2059,7 @@ Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks
|
|
|
2047
2059
|
},
|
|
2048
2060
|
research: {
|
|
2049
2061
|
name: "research",
|
|
2050
|
-
description: "Answers questions
|
|
2062
|
+
description: "Answers questions that need the web (search, URL reading); GitHub for context, not for a question GitHub alone answers. No workspace.",
|
|
2051
2063
|
system: RESEARCH_SYSTEM,
|
|
2052
2064
|
toolset: "web",
|
|
2053
2065
|
machine: "none",
|
|
@@ -2073,11 +2085,14 @@ Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks
|
|
|
2073
2085
|
// would expire between them, so the 2× write buys reads for the whole run.
|
|
2074
2086
|
cacheTtl: "1h"
|
|
2075
2087
|
// No built-in effort: the deployment decides, as for coding.
|
|
2076
|
-
}
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
AGENTS = {
|
|
2091
|
+
...WORK_PRESETS,
|
|
2077
2092
|
conductor: {
|
|
2078
2093
|
name: "conductor",
|
|
2079
2094
|
description: "Coordinates other runs: spawns child runs as the requester \u2014 each in a thread of its own, under their permissions \u2014 follows them, and reports. No workspace or shell.",
|
|
2080
|
-
system:
|
|
2095
|
+
system: conductorSystem(Object.values(WORK_PRESETS)),
|
|
2081
2096
|
toolset: "conductor",
|
|
2082
2097
|
// Nothing is provisioned and no credential minted: the run tools call the
|
|
2083
2098
|
// dispatcher, the GitHub reads are REST in the bot process.
|
|
@@ -2821,6 +2836,8 @@ function validateRouting(routing, providers) {
|
|
|
2821
2836
|
throw new Error(`config.yaml: routing.${key} is not a known key`);
|
|
2822
2837
|
if (routing.auto !== void 0 && typeof routing.auto !== "boolean")
|
|
2823
2838
|
throw new Error("config.yaml: routing.auto must be true or false");
|
|
2839
|
+
if (routing.answer !== void 0 && !ROUTE_ANSWER_MODES.includes(routing.answer))
|
|
2840
|
+
throw new Error(`config.yaml: routing.answer must be ${ROUTE_ANSWER_MODES.join(" or ")}`);
|
|
2824
2841
|
if (routing.model !== void 0) {
|
|
2825
2842
|
if (typeof routing.model !== "string" || !routing.model.includes("/"))
|
|
2826
2843
|
throw new Error("config.yaml: routing.model must be a <provider>/<model> ref");
|
|
@@ -2875,8 +2892,11 @@ function validateRunHistory(rh) {
|
|
|
2875
2892
|
if (v !== void 0 && (!Number.isInteger(v) || v < 1))
|
|
2876
2893
|
throw new Error(`config.yaml: runHistory.${key} must be an integer >= 1`);
|
|
2877
2894
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2895
|
+
for (const key of ["maxBytes", "sessionLogMaxBytes"]) {
|
|
2896
|
+
const v = rh[key];
|
|
2897
|
+
if (v !== void 0 && (!Number.isInteger(v) || v < 1))
|
|
2898
|
+
throw new Error(`config.yaml: runHistory.${key} must be an integer >= 1`);
|
|
2899
|
+
}
|
|
2880
2900
|
if (rh.store !== void 0 && rh.store !== "worker" && rh.store !== "file")
|
|
2881
2901
|
throw new Error('config.yaml: runHistory.store must be "worker" or "file"');
|
|
2882
2902
|
if (rh.worker !== void 0) {
|
|
@@ -2917,7 +2937,7 @@ function validateRuntimeOverrides(ro) {
|
|
|
2917
2937
|
}
|
|
2918
2938
|
}
|
|
2919
2939
|
}
|
|
2920
|
-
var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
|
|
2940
|
+
var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, ROUTE_ANSWER_MODES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
|
|
2921
2941
|
var init_validate = __esm({
|
|
2922
2942
|
"../../src/config/validate.ts"() {
|
|
2923
2943
|
"use strict";
|
|
@@ -2966,7 +2986,8 @@ var init_validate = __esm({
|
|
|
2966
2986
|
};
|
|
2967
2987
|
BOUNDARY_KEYS = { maxMinutes: true, maxIdentity: true, machines: true };
|
|
2968
2988
|
MIN_BOUNDARY_MINUTES = 2;
|
|
2969
|
-
|
|
2989
|
+
ROUTE_ANSWER_MODES = ["tool", "text"];
|
|
2990
|
+
ROUTING_KEYS = { auto: true, model: true, answer: true };
|
|
2970
2991
|
SHIP_KEYS = { maxRounds: true, maxMinutes: true };
|
|
2971
2992
|
SPAWN_KEYS = { maxChildren: true };
|
|
2972
2993
|
}
|
|
@@ -2987,7 +3008,7 @@ function identityWithin(identity2, cap) {
|
|
|
2987
3008
|
return IDENTITY_ORDER[identity2] <= IDENTITY_ORDER[cap];
|
|
2988
3009
|
}
|
|
2989
3010
|
function budgetedAgent(agent, profile) {
|
|
2990
|
-
return { ...agent, maxMinutes: profile.minutes };
|
|
3011
|
+
return { ...agent, maxMinutes: profile.minutes, maxTurns: runawayTurnCap(profile.minutes) };
|
|
2991
3012
|
}
|
|
2992
3013
|
function intersectBoundaries(layers) {
|
|
2993
3014
|
const out = {};
|
|
@@ -3058,6 +3079,7 @@ var IDENTITY_ORDER, MACHINE_ORDER;
|
|
|
3058
3079
|
var init_profile = __esm({
|
|
3059
3080
|
"../../src/config/profile.ts"() {
|
|
3060
3081
|
"use strict";
|
|
3082
|
+
init_registry();
|
|
3061
3083
|
IDENTITY_ORDER = { none: 0, read: 1, write: 2 };
|
|
3062
3084
|
MACHINE_ORDER = { none: 0, blank: 1, "repo-cold": 2, "repo-resident": 3 };
|
|
3063
3085
|
}
|
|
@@ -4833,7 +4855,14 @@ var init_profile2 = __esm({
|
|
|
4833
4855
|
images: z2.enum(IMAGE_MODES).default("build"),
|
|
4834
4856
|
/** The Cloudflare Access application in front of the bot's dashboards, when
|
|
4835
4857
|
* there is one: the team domain the JWT is issued by and the app's AUD. */
|
|
4836
|
-
access: z2.object({ teamDomain: hostname, aud: z2.string().regex(/^[0-9a-f]{64}$/) }).optional()
|
|
4858
|
+
access: z2.object({ teamDomain: hostname, aud: z2.string().regex(/^[0-9a-f]{64}$/) }).optional(),
|
|
4859
|
+
/** The artifact store's bucket (docs/reference/specs/execution.md item 20), when the installation
|
|
4860
|
+
* has one: the bot Worker's template binds it as `ARTIFACTS` and `deploy` creates it before the
|
|
4861
|
+
* upload. The bot's runtime config (`artifacts.r2.bucket`) must name the same bucket — the two
|
|
4862
|
+
* are held equal by `artifacts check`, not by the profile. R2's own bucket-name rules. */
|
|
4863
|
+
artifacts: z2.object({
|
|
4864
|
+
bucket: z2.string().regex(/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/, "an R2 bucket name: 3\u201363 lowercase letters, digits and hyphens")
|
|
4865
|
+
}).optional()
|
|
4837
4866
|
});
|
|
4838
4867
|
EXAMPLE_ACCOUNT = "00000000000000000000000000000000";
|
|
4839
4868
|
}
|
|
@@ -4906,7 +4935,11 @@ function planDeploy(opts, host, loaded, images) {
|
|
|
4906
4935
|
setEnv: w.preflight ? { [w.preflight.baseUrlEnv]: w.baseUrl, ...opts.force ? { [w.preflight.forceEnv]: "1" } : {} } : {},
|
|
4907
4936
|
...opts.force && w.preflight ? { forcedBy: w.preflight.forceEnv } : {},
|
|
4908
4937
|
requiredEnv: w.requiredEnv ?? [],
|
|
4909
|
-
|
|
4938
|
+
// The bot's `npm run deploy` creates the artifacts bucket before the upload
|
|
4939
|
+
// (deploy/cloudflare/ensure-bucket.mjs) when the profile names one — that
|
|
4940
|
+
// needs R2 on the credential, checked up front like every capability; an
|
|
4941
|
+
// installation without artifacts asks nothing extra of its token.
|
|
4942
|
+
capabilities: [...w.capabilities ?? [], ...w.name === "bot" && profile.artifacts ? [R2_CAPABILITY] : []],
|
|
4910
4943
|
retryOnPreflightRefusal: !!w.preflight && !opts.force,
|
|
4911
4944
|
...w.waitMaxMs !== void 0 ? { waitMaxMs: w.waitMaxMs } : {},
|
|
4912
4945
|
...w.preflight?.healthUrl ? { healthUrl: w.preflight.healthUrl } : {},
|
|
@@ -5039,7 +5072,7 @@ function classifyDeployOutput(exitCode, output) {
|
|
|
5039
5072
|
const firstError = lines.find((l) => isErrorish(l) && !/^npm ERR!/i.test(l)) ?? lines.find(isErrorish);
|
|
5040
5073
|
return { kind: "failed", reason: firstError ?? `exit ${exitCode}` };
|
|
5041
5074
|
}
|
|
5042
|
-
var UNSET_ENV, SANDBOX_CONTAINER_CLASS, SANDBOX_BEARER_ENV, RESIDENT_BEARER_ENVS, RESIDENT_WAIT_MAX_MS, CONTAINERS_CAPABILITY, WORKER_DIRS, WORKER_SPECS, DEPLOY_ORDER, CONFIG_DOCUMENT_KEY, hasImage, ANSI_SEQUENCE;
|
|
5075
|
+
var UNSET_ENV, SANDBOX_CONTAINER_CLASS, SANDBOX_BEARER_ENV, R2_CAPABILITY, RESIDENT_BEARER_ENVS, RESIDENT_WAIT_MAX_MS, CONTAINERS_CAPABILITY, WORKER_DIRS, WORKER_SPECS, DEPLOY_ORDER, CONFIG_DOCUMENT_KEY, hasImage, ANSI_SEQUENCE;
|
|
5043
5076
|
var init_plan = __esm({
|
|
5044
5077
|
"../../src/deploy/plan.ts"() {
|
|
5045
5078
|
"use strict";
|
|
@@ -5051,6 +5084,10 @@ var init_plan = __esm({
|
|
|
5051
5084
|
UNSET_ENV = ["CLOUDFLARE_ACCOUNT_ID"];
|
|
5052
5085
|
SANDBOX_CONTAINER_CLASS = "SwitchboardSandbox";
|
|
5053
5086
|
SANDBOX_BEARER_ENV = "SANDBOX_TOKEN";
|
|
5087
|
+
R2_CAPABILITY = {
|
|
5088
|
+
command: ["wrangler", "r2", "bucket", "list"],
|
|
5089
|
+
needs: "Workers R2 Storage: Edit"
|
|
5090
|
+
};
|
|
5054
5091
|
RESIDENT_BEARER_ENVS = ["RESIDENT_ADMIN_TOKEN", "RESIDENT_OPERATOR_TOKEN", "RESIDENT_READ_TOKEN"];
|
|
5055
5092
|
RESIDENT_WAIT_MAX_MS = 30 * 6e4;
|
|
5056
5093
|
CONTAINERS_CAPABILITY = {
|
|
@@ -5123,10 +5160,7 @@ var init_plan = __esm({
|
|
|
5123
5160
|
// A container image like the bot's — checked here too, since an --affected
|
|
5124
5161
|
// release can select the resident without the bot — plus the BACKUP_BUCKET
|
|
5125
5162
|
// R2 binding wrangler validates on deploy.
|
|
5126
|
-
capabilities: [
|
|
5127
|
-
CONTAINERS_CAPABILITY,
|
|
5128
|
-
{ command: ["wrangler", "r2", "bucket", "list"], needs: "Workers R2 Storage: Edit" }
|
|
5129
|
-
],
|
|
5163
|
+
capabilities: [CONTAINERS_CAPABILITY, R2_CAPABILITY],
|
|
5130
5164
|
waitMaxMs: RESIDENT_WAIT_MAX_MS,
|
|
5131
5165
|
why: "per-repo DOs \u2014 preflight refuses while a resident has a run in flight or is provisioning (a refresh or restore mid-cycle only warns: it resumes after the swap)"
|
|
5132
5166
|
},
|
|
@@ -5680,6 +5714,7 @@ function templateView(profile, kind, published) {
|
|
|
5680
5714
|
...hasImage2(kind) ? { image: containerImage(kind, profile, published) } : {},
|
|
5681
5715
|
urls: { publicBaseUrl: urls.publicBaseUrl, stateWorkerUrl: urls.stateWorkerUrl },
|
|
5682
5716
|
access: profile.access,
|
|
5717
|
+
artifacts: profile.artifacts,
|
|
5683
5718
|
bot: { script: profile.workers.bot.script }
|
|
5684
5719
|
};
|
|
5685
5720
|
}
|
|
@@ -7811,7 +7846,8 @@ var init_runEvents = __esm({
|
|
|
7811
7846
|
"review_not_posted",
|
|
7812
7847
|
"compacted",
|
|
7813
7848
|
"harness_error",
|
|
7814
|
-
"tool_refused"
|
|
7849
|
+
"tool_refused",
|
|
7850
|
+
"stuck_loop"
|
|
7815
7851
|
];
|
|
7816
7852
|
ACTOR_ID_CHARS = "A-Za-z0-9:@._-";
|
|
7817
7853
|
ACTOR_ID_MAX = 128;
|
|
@@ -8108,6 +8144,13 @@ var init_host3 = __esm({
|
|
|
8108
8144
|
});
|
|
8109
8145
|
|
|
8110
8146
|
// ../../src/execution/binaryRead.ts
|
|
8147
|
+
function statCommandFor(resolvedPath) {
|
|
8148
|
+
return `stat -c %s -- ${resolvedPath}`;
|
|
8149
|
+
}
|
|
8150
|
+
function parseByteSize(stdout) {
|
|
8151
|
+
const m = /^\s*(\d{1,15})\s*$/.exec(stdout);
|
|
8152
|
+
return m ? Number(m[1]) : null;
|
|
8153
|
+
}
|
|
8111
8154
|
function tooLargeMessage(path, bytes) {
|
|
8112
8155
|
const size = bytes === void 0 ? "" : ` (${bytes} bytes)`;
|
|
8113
8156
|
return `${path}${size} is over the ${MAX_READ_BYTES}-byte cap of a binary read`;
|
|
@@ -8731,9 +8774,9 @@ function sendDeadlineMs(budgetMs) {
|
|
|
8731
8774
|
return budgetMs + EXEC_CALL_MARGIN_MS;
|
|
8732
8775
|
}
|
|
8733
8776
|
function noAnswerMessage(route3, budgetMs) {
|
|
8734
|
-
const
|
|
8777
|
+
const secs2 = (ms) => Math.round(ms / 1e3);
|
|
8735
8778
|
const exec = route3 === "/exec";
|
|
8736
|
-
return `sandbox worker ${route3} gave no answer within ${
|
|
8779
|
+
return `sandbox worker ${route3} gave no answer within ${secs2(sendDeadlineMs(budgetMs))}s (${exec ? "command budget" : "budget"} ${secs2(budgetMs)}s + ${secs2(EXEC_CALL_MARGIN_MS)}s margin) \u2014 the sandbox may be gone; ${exec ? "the command may still be running in it" : "the operation may still have run in it"}`;
|
|
8737
8780
|
}
|
|
8738
8781
|
function waitForSlot(ms, waitedMs, signal) {
|
|
8739
8782
|
return new Promise((resolve12, reject) => {
|
|
@@ -8896,6 +8939,9 @@ ${parts}`);
|
|
|
8896
8939
|
function residentText(text) {
|
|
8897
8940
|
return redactAndCap(stripAnsi(text), RESIDENT_TEXT_CAP);
|
|
8898
8941
|
}
|
|
8942
|
+
function residentErrorText(text) {
|
|
8943
|
+
return redactAndCap(stripAnsi(text), RESIDENT_ERROR_CAP);
|
|
8944
|
+
}
|
|
8899
8945
|
function sanitizeResidentBody(data) {
|
|
8900
8946
|
return walk(data, SANITIZE_DEPTH);
|
|
8901
8947
|
}
|
|
@@ -8905,7 +8951,7 @@ function walk(value, depth) {
|
|
|
8905
8951
|
const src = value;
|
|
8906
8952
|
const out = {};
|
|
8907
8953
|
for (const [key, v] of Object.entries(src)) {
|
|
8908
|
-
out[key] = SANITIZED_FIELDS.includes(key) && typeof v === "string" ? residentText(v) : walk(v, depth - 1);
|
|
8954
|
+
out[key] = SANITIZED_FIELDS.includes(key) && typeof v === "string" ? key === "error" ? residentErrorText(v) : residentText(v) : walk(v, depth - 1);
|
|
8909
8955
|
}
|
|
8910
8956
|
if (typeof src.stderr === "string" && src.stderr === src.error && typeof out.error === "string") {
|
|
8911
8957
|
out.stderr = out.error;
|
|
@@ -8915,12 +8961,13 @@ function walk(value, depth) {
|
|
|
8915
8961
|
function residentState(value) {
|
|
8916
8962
|
return typeof value === "string" && PROBE_STATES.has(value) ? value : "unknown";
|
|
8917
8963
|
}
|
|
8918
|
-
var RESIDENT_TEXT_CAP, SANITIZED_FIELDS, SANITIZE_DEPTH, PROBE_STATES;
|
|
8964
|
+
var RESIDENT_TEXT_CAP, RESIDENT_ERROR_CAP, SANITIZED_FIELDS, SANITIZE_DEPTH, PROBE_STATES;
|
|
8919
8965
|
var init_residentText = __esm({
|
|
8920
8966
|
"../../src/execution/residentText.ts"() {
|
|
8921
8967
|
"use strict";
|
|
8922
8968
|
init_redact();
|
|
8923
8969
|
RESIDENT_TEXT_CAP = 300;
|
|
8970
|
+
RESIDENT_ERROR_CAP = 1e3;
|
|
8924
8971
|
SANITIZED_FIELDS = ["error", "reason", "summary"];
|
|
8925
8972
|
SANITIZE_DEPTH = 4;
|
|
8926
8973
|
PROBE_STATES = /* @__PURE__ */ new Set([
|
|
@@ -10928,7 +10975,8 @@ function buildAnthropicParams(req) {
|
|
|
10928
10975
|
...req.system ? { system: [{ type: "text", text: req.system, cache_control: cache2 }] } : {},
|
|
10929
10976
|
messages,
|
|
10930
10977
|
...effort2 ? { output_config: { effort: effort2 } } : {},
|
|
10931
|
-
...tools.length > 0 ? { tools } : {}
|
|
10978
|
+
...tools.length > 0 ? { tools } : {},
|
|
10979
|
+
...req.toolChoice ? { tool_choice: { type: "tool", name: req.toolChoice.name } } : {}
|
|
10932
10980
|
};
|
|
10933
10981
|
}
|
|
10934
10982
|
function effortFor(model, effort2) {
|
|
@@ -12745,6 +12793,7 @@ function isCoordinatorUnit(v) {
|
|
|
12745
12793
|
if (!isText(r.unit, 32) || !isText(r.slug) || !isText(r.branch) || !isOptionalText(r.title)) return false;
|
|
12746
12794
|
if (!Array.isArray(r.dependsOn) || !r.dependsOn.every((d) => isText(d, 32))) return false;
|
|
12747
12795
|
if (!isOptionalText(r.threadKey) || !isOptionalText(r.sourceUrl)) return false;
|
|
12796
|
+
if (r.reviewThread !== void 0 && !isThread(r.reviewThread)) return false;
|
|
12748
12797
|
if (r.issue !== void 0 && !isFinite(r.issue)) return false;
|
|
12749
12798
|
if (r.pr !== void 0 && !isPr(r.pr)) return false;
|
|
12750
12799
|
if (r.resume !== void 0 && !isResume(r.resume)) return false;
|
|
@@ -12755,7 +12804,7 @@ function isCoordinatorUnit(v) {
|
|
|
12755
12804
|
if (r.startedAt !== void 0 && !isFinite(r.startedAt)) return false;
|
|
12756
12805
|
return true;
|
|
12757
12806
|
}
|
|
12758
|
-
var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, REPO_SLUG2, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isObject2, isPr, isResume;
|
|
12807
|
+
var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, REPO_SLUG2, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isObject2, isPr, isResume, isThread;
|
|
12759
12808
|
var init_contract3 = __esm({
|
|
12760
12809
|
"../../src/core/coordinator/contract.ts"() {
|
|
12761
12810
|
"use strict";
|
|
@@ -12776,6 +12825,7 @@ var init_contract3 = __esm({
|
|
|
12776
12825
|
isObject2 = (v) => typeof v === "object" && v !== null;
|
|
12777
12826
|
isPr = (v) => isObject2(v) && isFinite(v.number) && isText(v.url, 2048);
|
|
12778
12827
|
isResume = (v) => isObject2(v) && isFinite(v.pr) && (v.headSha === void 0 || isText(v.headSha)) && (v.url === void 0 || isText(v.url, 2048));
|
|
12828
|
+
isThread = (v) => isObject2(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
|
|
12779
12829
|
}
|
|
12780
12830
|
});
|
|
12781
12831
|
|
|
@@ -13288,7 +13338,7 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13288
13338
|
}
|
|
13289
13339
|
if (ev.type === "tool_call") {
|
|
13290
13340
|
toolCalls++;
|
|
13291
|
-
if (failedCalls.has(
|
|
13341
|
+
if (failedCalls.has(callSignature(ev.tool, ev.summary))) {
|
|
13292
13342
|
findings.push({
|
|
13293
13343
|
category: "retry",
|
|
13294
13344
|
severity: "low",
|
|
@@ -13310,7 +13360,7 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13310
13360
|
const durationMs = span ? durationOfSpan(span) : void 0;
|
|
13311
13361
|
const timed = (f) => durationMs !== void 0 ? { ...f, durationMs } : f;
|
|
13312
13362
|
const interval = span && durationMs !== void 0 ? { interval: { start: span.startedAt, end: span.startedAt + durationMs } } : {};
|
|
13313
|
-
if (!ev.ok) failedCalls.add(
|
|
13363
|
+
if (!ev.ok) failedCalls.add(callSignature(ev.tool, callSummary));
|
|
13314
13364
|
if (ev.infra) {
|
|
13315
13365
|
findings.push(
|
|
13316
13366
|
timed({
|
|
@@ -13503,7 +13553,7 @@ function formatFrictionReport(d) {
|
|
|
13503
13553
|
}
|
|
13504
13554
|
return lines.join("\n");
|
|
13505
13555
|
}
|
|
13506
|
-
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED;
|
|
13556
|
+
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED, callSignature;
|
|
13507
13557
|
var init_runFriction = __esm({
|
|
13508
13558
|
"../../src/core/runFriction.ts"() {
|
|
13509
13559
|
"use strict";
|
|
@@ -13546,10 +13596,22 @@ var init_runFriction = __esm({
|
|
|
13546
13596
|
DEFAULT_SLOW_MODEL_TURN_MS = 6e4;
|
|
13547
13597
|
INSTALL_SEGMENT = /(?:^|&&|;|\|\|?)\s*(?:sudo\s+)?(?:corepack\s+)?(?:(?:npm|pnpm|bun)\s+(?:install|i|ci|add)(?=\s|$)|yarn(?:\s+(?:install|add)(?=\s|$)|\s*$)|npx\s+playwright\s+install(?=\s|$)|(?:python3?\s+-m\s+)?pip3?\s+install(?=\s|$)|poetry\s+install(?=\s|$)|uv\s+(?:sync|pip\s+install)(?=\s|$)|apt(?:-get)?\s+install(?=\s|$)|apk\s+add(?=\s|$)|brew\s+install(?=\s|$)|bundle\s+install(?=\s|$)|gem\s+install(?=\s|$)|cargo\s+(?:fetch|build)(?=\s|$)|go\s+mod\s+download(?=\s|$)|git\s+clone(?=\s|$)|make\s+(?:deps|install|setup)(?=\s|$))/;
|
|
13548
13598
|
QUOTED = /"(?:[^"\\]|\\.)*"|'[^']*'/g;
|
|
13599
|
+
callSignature = (tool, summary) => `${tool} ${summary}`;
|
|
13549
13600
|
}
|
|
13550
13601
|
});
|
|
13551
13602
|
|
|
13552
13603
|
// ../../src/core/runRecord.ts
|
|
13604
|
+
function isRunSession(v) {
|
|
13605
|
+
if (typeof v !== "object" || v === null) return false;
|
|
13606
|
+
const s = v;
|
|
13607
|
+
if (typeof s.key !== "string" || !SESSION_KEY_PATTERN.test(s.key)) return false;
|
|
13608
|
+
if (!isIndex(s.seedFrom) || !isIndex(s.request) || s.request < s.seedFrom) return false;
|
|
13609
|
+
if (s.range === "broken") return true;
|
|
13610
|
+
if (typeof s.range !== "object" || s.range === null) return false;
|
|
13611
|
+
const range = s.range;
|
|
13612
|
+
if (!isIndex(range.from) || range.from < s.seedFrom) return false;
|
|
13613
|
+
return range.to === void 0 || isIndex(range.to) && range.to >= range.from;
|
|
13614
|
+
}
|
|
13553
13615
|
function isRunProfileRecord(v) {
|
|
13554
13616
|
if (typeof v !== "object" || v === null) return false;
|
|
13555
13617
|
const p = v;
|
|
@@ -13628,7 +13690,12 @@ function clampRetentionPolicy(partial) {
|
|
|
13628
13690
|
const [lo, hi] = RETENTION_BOUNDS[k];
|
|
13629
13691
|
return Math.min(hi, Math.max(lo, n2));
|
|
13630
13692
|
};
|
|
13631
|
-
return {
|
|
13693
|
+
return {
|
|
13694
|
+
retentionDays: field("retentionDays"),
|
|
13695
|
+
maxRuns: field("maxRuns"),
|
|
13696
|
+
maxBytes: field("maxBytes"),
|
|
13697
|
+
sessionLogMaxBytes: field("sessionLogMaxBytes")
|
|
13698
|
+
};
|
|
13632
13699
|
}
|
|
13633
13700
|
function isOptionalString(v) {
|
|
13634
13701
|
return v === void 0 || typeof v === "string";
|
|
@@ -13694,6 +13761,8 @@ function isRunRecord(v) {
|
|
|
13694
13761
|
if (r.profile !== void 0 && !isRunProfileRecord(r.profile)) return false;
|
|
13695
13762
|
if (r.parentRunId !== void 0 && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
|
|
13696
13763
|
return false;
|
|
13764
|
+
if (r.seed !== void 0 && !RUN_SEEDS.includes(r.seed)) return false;
|
|
13765
|
+
if (r.session !== void 0 && !isRunSession(r.session)) return false;
|
|
13697
13766
|
if (r.parentInstanceId === void 0 !== (r.idempotencyKey === void 0)) return false;
|
|
13698
13767
|
if (r.parentInstanceId !== void 0 && (typeof r.parentInstanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.parentInstanceId)))
|
|
13699
13768
|
return false;
|
|
@@ -13825,7 +13894,7 @@ function dropSpanPairsFromTheMiddle(events, eventBudget) {
|
|
|
13825
13894
|
}
|
|
13826
13895
|
return events.filter((_, k) => !dropped.has(k));
|
|
13827
13896
|
}
|
|
13828
|
-
var RUN_STATUSES, RUN_ID_PATTERN, REVIEW_HEAD_PATTERN, MACHINE_CLASSES_IN_RECORD, IDENTITIES_IN_RECORD, BOUNDARY_SCOPES_IN_RECORD, RUN_LIST_DEFAULT_LIMIT, RUN_LIST_MAX_LIMIT, RUN_EVENTS_DEFAULT_PAGE, RUN_EVENTS_MAX_PAGE, CHANNEL_VISIBILITIES2, KIB, MIB, GIB2, DEFAULT_RETENTION_POLICY, RETENTION_BOUNDS, MAX_RECORD_BYTES, MAX_EVENT_BYTES, ELLIPSIS, encoder;
|
|
13897
|
+
var RUN_STATUSES, RUN_ID_PATTERN, REVIEW_HEAD_PATTERN, RUN_SEEDS, SESSION_KEY_PATTERN, isIndex, MACHINE_CLASSES_IN_RECORD, IDENTITIES_IN_RECORD, BOUNDARY_SCOPES_IN_RECORD, RUN_LIST_DEFAULT_LIMIT, RUN_LIST_MAX_LIMIT, RUN_EVENTS_DEFAULT_PAGE, RUN_EVENTS_MAX_PAGE, CHANNEL_VISIBILITIES2, KIB, MIB, GIB2, DEFAULT_RETENTION_POLICY, RETENTION_BOUNDS, MAX_RECORD_BYTES, MAX_EVENT_BYTES, ELLIPSIS, encoder;
|
|
13829
13898
|
var init_runRecord = __esm({
|
|
13830
13899
|
"../../src/core/runRecord.ts"() {
|
|
13831
13900
|
"use strict";
|
|
@@ -13837,6 +13906,9 @@ var init_runRecord = __esm({
|
|
|
13837
13906
|
RUN_STATUSES = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
|
|
13838
13907
|
RUN_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13839
13908
|
REVIEW_HEAD_PATTERN = /^[0-9a-f]{7,40}$/;
|
|
13909
|
+
RUN_SEEDS = ["channel", "parent"];
|
|
13910
|
+
SESSION_KEY_PATTERN = /^[A-Za-z0-9_.:@+/-]{1,512}$/;
|
|
13911
|
+
isIndex = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
13840
13912
|
MACHINE_CLASSES_IN_RECORD = {
|
|
13841
13913
|
none: true,
|
|
13842
13914
|
blank: true,
|
|
@@ -13862,12 +13934,14 @@ var init_runRecord = __esm({
|
|
|
13862
13934
|
DEFAULT_RETENTION_POLICY = {
|
|
13863
13935
|
retentionDays: 30,
|
|
13864
13936
|
maxRuns: 5e3,
|
|
13865
|
-
maxBytes: 2 * GIB2
|
|
13937
|
+
maxBytes: 2 * GIB2,
|
|
13938
|
+
sessionLogMaxBytes: 200 * MIB
|
|
13866
13939
|
};
|
|
13867
13940
|
RETENTION_BOUNDS = {
|
|
13868
13941
|
retentionDays: [1, 365],
|
|
13869
13942
|
maxRuns: [1, 2e4],
|
|
13870
|
-
maxBytes: [16 * MIB, 8 * GIB2]
|
|
13943
|
+
maxBytes: [16 * MIB, 8 * GIB2],
|
|
13944
|
+
sessionLogMaxBytes: [16 * MIB, 2 * GIB2]
|
|
13871
13945
|
};
|
|
13872
13946
|
MAX_RECORD_BYTES = 1.5 * MIB;
|
|
13873
13947
|
MAX_EVENT_BYTES = 64 * KIB;
|
|
@@ -19896,7 +19970,7 @@ function createRunsService(deps) {
|
|
|
19896
19970
|
const analyze = deps.analyze ?? ((events, opts) => analyzeRunFriction(events, opts));
|
|
19897
19971
|
const clock3 = deps.clock ?? systemClock;
|
|
19898
19972
|
const warn = deps.warn ?? ((m) => console.warn(m));
|
|
19899
|
-
const
|
|
19973
|
+
const describe11 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
19900
19974
|
let listing;
|
|
19901
19975
|
const liveRows = () => {
|
|
19902
19976
|
if (!ledger) return null;
|
|
@@ -19916,7 +19990,7 @@ function createRunsService(deps) {
|
|
|
19916
19990
|
try {
|
|
19917
19991
|
rows = await pending2;
|
|
19918
19992
|
} catch (err2) {
|
|
19919
|
-
warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${
|
|
19993
|
+
warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${describe11(err2)}`);
|
|
19920
19994
|
return [];
|
|
19921
19995
|
}
|
|
19922
19996
|
const foreign = rows.filter((row) => !registry.getById(row.runId));
|
|
@@ -19926,7 +20000,7 @@ function createRunsService(deps) {
|
|
|
19926
20000
|
try {
|
|
19927
20001
|
events = await ledger.readEvents(row.runId);
|
|
19928
20002
|
} catch (err2) {
|
|
19929
|
-
warn(`[runs] run ledger events read failed for ${row.runId}: ${
|
|
20003
|
+
warn(`[runs] run ledger events read failed for ${row.runId}: ${describe11(err2)}`);
|
|
19930
20004
|
}
|
|
19931
20005
|
return ledgerView(row, events);
|
|
19932
20006
|
})
|
|
@@ -19940,7 +20014,7 @@ function createRunsService(deps) {
|
|
|
19940
20014
|
if (!row) return null;
|
|
19941
20015
|
return { row, events: await ledger.readEvents(id) };
|
|
19942
20016
|
} catch (err2) {
|
|
19943
|
-
warn(`[runs] run ledger read failed for ${id}: ${
|
|
20017
|
+
warn(`[runs] run ledger read failed for ${id}: ${describe11(err2)}`);
|
|
19944
20018
|
return null;
|
|
19945
20019
|
}
|
|
19946
20020
|
};
|
|
@@ -19958,7 +20032,7 @@ function createRunsService(deps) {
|
|
|
19958
20032
|
row = await storeSummary(id);
|
|
19959
20033
|
} catch (err2) {
|
|
19960
20034
|
warn(
|
|
19961
|
-
`[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${
|
|
20035
|
+
`[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${describe11(err2)}`
|
|
19962
20036
|
);
|
|
19963
20037
|
return {};
|
|
19964
20038
|
}
|
|
@@ -20127,7 +20201,7 @@ function createRunsService(deps) {
|
|
|
20127
20201
|
const r = await ledger.requestStop(id, mode);
|
|
20128
20202
|
if (r.ok) return { ok: true, value: { id, mode, state: "stopping" } };
|
|
20129
20203
|
} catch (err2) {
|
|
20130
|
-
warn(`[runs] run ledger stop failed for ${id}: ${
|
|
20204
|
+
warn(`[runs] run ledger stop failed for ${id}: ${describe11(err2)}`);
|
|
20131
20205
|
}
|
|
20132
20206
|
}
|
|
20133
20207
|
return await storeSummary(id) ? conflict : notFound;
|
|
@@ -21502,6 +21576,11 @@ function liveViewLink(id, token2) {
|
|
|
21502
21576
|
if (!base) return void 0;
|
|
21503
21577
|
return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}?t=${encodeURIComponent(token2)}`;
|
|
21504
21578
|
}
|
|
21579
|
+
function runPageLink(id) {
|
|
21580
|
+
const base = process.env.PUBLIC_BASE_URL?.trim();
|
|
21581
|
+
if (!base) return void 0;
|
|
21582
|
+
return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}`;
|
|
21583
|
+
}
|
|
21505
21584
|
function stripPlatformPrefix(id) {
|
|
21506
21585
|
const i = id.indexOf(":");
|
|
21507
21586
|
return i === -1 ? id : id.slice(i + 1);
|
|
@@ -21748,7 +21827,11 @@ function durableInboxMessage(msg, text, at, from) {
|
|
|
21748
21827
|
...msg.userName !== void 0 ? { userName: msg.userName } : {},
|
|
21749
21828
|
...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
|
|
21750
21829
|
...msg.channelName !== void 0 ? { channelName: msg.channelName } : {},
|
|
21751
|
-
...from !== void 0 ? { fromRunId: from.runId } : {}
|
|
21830
|
+
...from !== void 0 ? { fromRunId: from.runId } : {},
|
|
21831
|
+
// Staged references are metadata (record 0033) — a few hundred bytes each —
|
|
21832
|
+
// so they ride the base row and survive the restart whatever the
|
|
21833
|
+
// attachments below do.
|
|
21834
|
+
...msg.staged && msg.staged.length > 0 ? { staged: msg.staged.map(stagedRow) } : {}
|
|
21752
21835
|
};
|
|
21753
21836
|
const images = msg.images ?? [];
|
|
21754
21837
|
const documents = msg.documents ?? [];
|
|
@@ -21761,6 +21844,17 @@ function durableInboxMessage(msg, text, at, from) {
|
|
|
21761
21844
|
if (Buffer.byteLength(JSON.stringify(withAttachments), "utf8") <= DURABLE_INBOX_MAX_BYTES) return withAttachments;
|
|
21762
21845
|
return { ...base, attachmentsDropped: { images: images.length, documents: documents.length } };
|
|
21763
21846
|
}
|
|
21847
|
+
function stagedFromInbox(v) {
|
|
21848
|
+
if (!Array.isArray(v)) return void 0;
|
|
21849
|
+
const out = v.flatMap((e) => {
|
|
21850
|
+
if (typeof e !== "object" || e === null) return [];
|
|
21851
|
+
const r = e;
|
|
21852
|
+
if (typeof r.name !== "string" || typeof r.size !== "number" || typeof r.type !== "string" || typeof r.url !== "string" || typeof r.messageId !== "string")
|
|
21853
|
+
return [];
|
|
21854
|
+
return [{ name: r.name, size: r.size, type: r.type, url: r.url, messageId: r.messageId }];
|
|
21855
|
+
});
|
|
21856
|
+
return out.length > 0 ? out : void 0;
|
|
21857
|
+
}
|
|
21764
21858
|
function messageFromInbox(stored, fallbackAt) {
|
|
21765
21859
|
const m = stored;
|
|
21766
21860
|
const str7 = (k) => typeof m[k] === "string" ? m[k] : void 0;
|
|
@@ -21777,6 +21871,7 @@ function messageFromInbox(stored, fallbackAt) {
|
|
|
21777
21871
|
const at = typeof m.at === "number" && Number.isFinite(m.at) ? m.at : fallbackAt;
|
|
21778
21872
|
const images = attachmentsFromInbox(m.images);
|
|
21779
21873
|
const documents = attachmentsFromInbox(m.documents);
|
|
21874
|
+
const staged = stagedFromInbox(m.staged);
|
|
21780
21875
|
const note = droppedNote(m.attachmentsDropped);
|
|
21781
21876
|
const msg = {
|
|
21782
21877
|
channelId,
|
|
@@ -21789,7 +21884,8 @@ ${note}` : text,
|
|
|
21789
21884
|
...sourceUrl !== void 0 ? { sourceUrl } : {},
|
|
21790
21885
|
...channelName !== void 0 ? { channelName } : {},
|
|
21791
21886
|
...images ? { images } : {},
|
|
21792
|
-
...documents ? { documents } : {}
|
|
21887
|
+
...documents ? { documents } : {},
|
|
21888
|
+
...staged ? { staged } : {}
|
|
21793
21889
|
};
|
|
21794
21890
|
return { msg, at, ...fromRunId !== void 0 ? { from: { runId: fromRunId } } : {} };
|
|
21795
21891
|
}
|
|
@@ -21810,7 +21906,7 @@ function droppedNote(dropped) {
|
|
|
21810
21906
|
if (n2 <= 0) return void 0;
|
|
21811
21907
|
return `(${n2} attachment${n2 === 1 ? "" : "s"} from this reply could not be carried across the bot's restart and ${n2 === 1 ? "is" : "are"} not attached.)`;
|
|
21812
21908
|
}
|
|
21813
|
-
var DURABLE_INBOX_MAX_BYTES, attachmentRow;
|
|
21909
|
+
var DURABLE_INBOX_MAX_BYTES, attachmentRow, stagedRow;
|
|
21814
21910
|
var init_inboxMessage = __esm({
|
|
21815
21911
|
"../../src/core/runLedger/inboxMessage.ts"() {
|
|
21816
21912
|
"use strict";
|
|
@@ -21820,6 +21916,7 @@ var init_inboxMessage = __esm({
|
|
|
21820
21916
|
data: a.data,
|
|
21821
21917
|
...a.name !== void 0 ? { name: a.name } : {}
|
|
21822
21918
|
});
|
|
21919
|
+
stagedRow = (s) => ({ name: s.name, size: s.size, type: s.type, url: s.url, messageId: s.messageId });
|
|
21823
21920
|
}
|
|
21824
21921
|
});
|
|
21825
21922
|
|
|
@@ -21851,13 +21948,15 @@ function mergeFollowUps(inputs) {
|
|
|
21851
21948
|
const last = inputs[inputs.length - 1];
|
|
21852
21949
|
const images = inputs.flatMap((i) => i.images ?? []);
|
|
21853
21950
|
const documents = inputs.flatMap((i) => i.documents ?? []);
|
|
21951
|
+
const staged = inputs.flatMap((i) => i.staged ?? []);
|
|
21854
21952
|
return {
|
|
21855
21953
|
text: inputs.map((i) => i.text).join("\n\n"),
|
|
21856
21954
|
userId: last.userId,
|
|
21857
21955
|
...last.userName !== void 0 ? { userName: last.userName } : {},
|
|
21858
21956
|
...last.sourceUrl !== void 0 ? { sourceUrl: last.sourceUrl } : {},
|
|
21859
21957
|
...images.length > 0 ? { images } : {},
|
|
21860
|
-
...documents.length > 0 ? { documents } : {}
|
|
21958
|
+
...documents.length > 0 ? { documents } : {},
|
|
21959
|
+
...staged.length > 0 ? { staged } : {}
|
|
21861
21960
|
};
|
|
21862
21961
|
}
|
|
21863
21962
|
var FollowUpInbox, ThreadAdmission, elapsed, linkSuffix, followUpHeader, supersededHeader;
|
|
@@ -21969,6 +22068,7 @@ function interruptedRunRecord(summary, snap, finishedAt) {
|
|
|
21969
22068
|
channelVisibility: summary.channelVisibility ?? "unknown",
|
|
21970
22069
|
repo: summary.repo,
|
|
21971
22070
|
...summary.parentRunId !== void 0 ? { parentRunId: summary.parentRunId } : {},
|
|
22071
|
+
...summary.seed !== void 0 ? { seed: summary.seed } : {},
|
|
21972
22072
|
...summary.parentInstanceId !== void 0 && summary.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: summary.parentInstanceId, idempotencyKey: summary.idempotencyKey } } : {},
|
|
21973
22073
|
finishedAt,
|
|
21974
22074
|
status: "interrupted",
|
|
@@ -22004,6 +22104,10 @@ function reclaimedRunRecord(input) {
|
|
|
22004
22104
|
// included); a row from before profiles existed leaves the record without one.
|
|
22005
22105
|
...row.meta.profile && row.meta.agent ? { profile: { preset: row.meta.agent, ...row.meta.profile } } : {},
|
|
22006
22106
|
...row.meta.parentRunId !== void 0 ? { parentRunId: row.meta.parentRunId } : {},
|
|
22107
|
+
...row.meta.seed !== void 0 ? { seed: row.meta.seed } : {},
|
|
22108
|
+
// The row's place in its session log (item 53): the range stays open on a
|
|
22109
|
+
// record closed here, since the closer has no turn count to end it with.
|
|
22110
|
+
...row.meta.session !== void 0 ? { session: row.meta.session } : {},
|
|
22007
22111
|
// A coordinator's child keeps its instance and key on the close (item 48),
|
|
22008
22112
|
// so the state Worker's finish still sends the parent its event.
|
|
22009
22113
|
...row.meta.parentInstanceId !== void 0 && row.meta.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: row.meta.parentInstanceId, idempotencyKey: row.meta.idempotencyKey } } : {},
|
|
@@ -22072,7 +22176,9 @@ function assembleRunRecord(input) {
|
|
|
22072
22176
|
...input.reviewPost !== void 0 ? { reviewPost: redactReviewPost(input.reviewPost) } : {},
|
|
22073
22177
|
...input.profile !== void 0 ? { profile: input.profile } : {},
|
|
22074
22178
|
...input.parentRunId !== void 0 ? { parentRunId: input.parentRunId } : {},
|
|
22075
|
-
...coordinatorFields(input.coordinator)
|
|
22179
|
+
...coordinatorFields(input.coordinator),
|
|
22180
|
+
...input.seed !== void 0 ? { seed: input.seed } : {},
|
|
22181
|
+
...input.session !== void 0 ? { session: input.session } : {}
|
|
22076
22182
|
});
|
|
22077
22183
|
return fitted.eventCount !== fitted.storedEventCount ? { ...fitted, truncated: true } : fitted;
|
|
22078
22184
|
}
|
|
@@ -22080,7 +22186,20 @@ function profileRecordOf(agent, profile) {
|
|
|
22080
22186
|
return { preset: agent.name, ...profile };
|
|
22081
22187
|
}
|
|
22082
22188
|
function writeTombstone(deps, ctx) {
|
|
22083
|
-
const {
|
|
22189
|
+
const {
|
|
22190
|
+
msg,
|
|
22191
|
+
agent,
|
|
22192
|
+
profile,
|
|
22193
|
+
resolved,
|
|
22194
|
+
repoCtx,
|
|
22195
|
+
channelVisibility,
|
|
22196
|
+
run: run2,
|
|
22197
|
+
registry,
|
|
22198
|
+
resume,
|
|
22199
|
+
parentRunId,
|
|
22200
|
+
coordinator,
|
|
22201
|
+
seed
|
|
22202
|
+
} = ctx;
|
|
22084
22203
|
if (!resume) {
|
|
22085
22204
|
const startSnap = registry.snapshot(run2.id, run2.token);
|
|
22086
22205
|
if (startSnap) {
|
|
@@ -22096,6 +22215,7 @@ function writeTombstone(deps, ctx) {
|
|
|
22096
22215
|
profile: profileRecordOf(agent, profile),
|
|
22097
22216
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
22098
22217
|
...coordinator !== void 0 ? { coordinator } : {},
|
|
22218
|
+
...seed !== void 0 ? { seed } : {},
|
|
22099
22219
|
finishedAt: startSnap.startedAt,
|
|
22100
22220
|
status: "interrupted",
|
|
22101
22221
|
diagnosis: analyzeRunFriction(startSnap.events, {
|
|
@@ -22130,7 +22250,8 @@ function registerFinishRecord(deps, ctx) {
|
|
|
22130
22250
|
dispositions,
|
|
22131
22251
|
reviewPost,
|
|
22132
22252
|
parentRunId,
|
|
22133
|
-
coordinator
|
|
22253
|
+
coordinator,
|
|
22254
|
+
seed
|
|
22134
22255
|
} = ctx;
|
|
22135
22256
|
ending.register({
|
|
22136
22257
|
runId: run2.id,
|
|
@@ -22155,7 +22276,8 @@ function registerFinishRecord(deps, ctx) {
|
|
|
22155
22276
|
...dispositions !== void 0 ? { dispositions } : {},
|
|
22156
22277
|
...reviewPost !== void 0 ? { reviewPost } : {},
|
|
22157
22278
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
22158
|
-
...coordinator !== void 0 ? { coordinator } : {}
|
|
22279
|
+
...coordinator !== void 0 ? { coordinator } : {},
|
|
22280
|
+
...seed !== void 0 ? { seed } : {}
|
|
22159
22281
|
}),
|
|
22160
22282
|
{ span: root, ...ledgerRun ? { via: ledgerRun.sink } : {} }
|
|
22161
22283
|
)
|
|
@@ -22188,6 +22310,7 @@ function followUpOf(msg, text, at, opts) {
|
|
|
22188
22310
|
...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
|
|
22189
22311
|
...msg.images !== void 0 ? { images: msg.images } : {},
|
|
22190
22312
|
...msg.documents !== void 0 ? { documents: msg.documents } : {},
|
|
22313
|
+
...msg.staged !== void 0 ? { staged: msg.staged } : {},
|
|
22191
22314
|
at,
|
|
22192
22315
|
...opts.ledgerSeq !== void 0 ? { ledgerSeq: opts.ledgerSeq } : {},
|
|
22193
22316
|
...opts.from !== void 0 ? { from: opts.from } : {},
|
|
@@ -22253,7 +22376,9 @@ async function admit(deps, ctx) {
|
|
|
22253
22376
|
threadKey: msg.threadKey,
|
|
22254
22377
|
state: resume.row.state,
|
|
22255
22378
|
lastStep: resume.lastStep.step,
|
|
22256
|
-
lastSeq: resume.lastSeq
|
|
22379
|
+
lastSeq: resume.lastSeq,
|
|
22380
|
+
// The row's place in its session log, so the resumed run appends where it left off.
|
|
22381
|
+
...resume.row.meta.session ? { session: resume.row.meta.session } : {}
|
|
22257
22382
|
});
|
|
22258
22383
|
await root.span(
|
|
22259
22384
|
"dispatch.admission",
|
|
@@ -22403,6 +22528,8 @@ async function adoptCarriedRun(deps, ctx) {
|
|
|
22403
22528
|
state: resume.row.state,
|
|
22404
22529
|
lastStep: resume.lastStep.step,
|
|
22405
22530
|
lastSeq: resume.lastSeq,
|
|
22531
|
+
// The row's place in its session log, so the resumed run appends where it left off.
|
|
22532
|
+
...resume.row.meta.session ? { session: resume.row.meta.session } : {},
|
|
22406
22533
|
...ctx.hooks.adopt
|
|
22407
22534
|
});
|
|
22408
22535
|
}
|
|
@@ -23353,6 +23480,7 @@ function summaryOf(run2) {
|
|
|
23353
23480
|
...m?.parentRunId !== void 0 ? { parentRunId: m.parentRunId } : {},
|
|
23354
23481
|
...m?.parentInstanceId !== void 0 ? { parentInstanceId: m.parentInstanceId } : {},
|
|
23355
23482
|
...m?.idempotencyKey !== void 0 ? { idempotencyKey: m.idempotencyKey } : {},
|
|
23483
|
+
...m?.seed !== void 0 ? { seed: m.seed } : {},
|
|
23356
23484
|
finished: run2.finished,
|
|
23357
23485
|
startedAt: run2.startedAt,
|
|
23358
23486
|
...run2.finishedAt !== void 0 ? { finishedAt: run2.finishedAt } : {},
|
|
@@ -24254,6 +24382,21 @@ var init_awaitChildren = __esm({
|
|
|
24254
24382
|
}
|
|
24255
24383
|
});
|
|
24256
24384
|
|
|
24385
|
+
// ../../src/core/dispatch/textTurns.ts
|
|
24386
|
+
function textTurnsOf(messages) {
|
|
24387
|
+
const turns = [];
|
|
24388
|
+
for (const m of messages) {
|
|
24389
|
+
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n\n");
|
|
24390
|
+
if (text.length > 0) turns.push({ role: m.role, text });
|
|
24391
|
+
}
|
|
24392
|
+
return turns;
|
|
24393
|
+
}
|
|
24394
|
+
var init_textTurns = __esm({
|
|
24395
|
+
"../../src/core/dispatch/textTurns.ts"() {
|
|
24396
|
+
"use strict";
|
|
24397
|
+
}
|
|
24398
|
+
});
|
|
24399
|
+
|
|
24257
24400
|
// ../../src/core/dispatch/spawn.ts
|
|
24258
24401
|
function maxChildrenOf(cfg) {
|
|
24259
24402
|
return cfg?.maxChildren ?? DEFAULT_MAX_CHILDREN;
|
|
@@ -24290,6 +24433,7 @@ function watchedChild(io, on) {
|
|
|
24290
24433
|
};
|
|
24291
24434
|
if (io.attach) watched2.attach = (file) => io.attach(file);
|
|
24292
24435
|
if (io.attachFile) watched2.attachFile = (file) => io.attachFile(file);
|
|
24436
|
+
if (io.uploadTicket) watched2.uploadTicket = (file) => io.uploadTicket(file);
|
|
24293
24437
|
if (io.runFinished) watched2.runFinished = (receipt) => io.runFinished(receipt);
|
|
24294
24438
|
if (io.openThread) watched2.openThread = (lead) => io.openThread(lead);
|
|
24295
24439
|
return watched2;
|
|
@@ -24302,6 +24446,12 @@ async function spawnChild(deps, parent, request) {
|
|
|
24302
24446
|
`a child run cannot spawn: this run is itself a child, and a tree is ${MAX_SPAWN_DEPTH} level deep \u2014 the run that started it is the one to ask`
|
|
24303
24447
|
);
|
|
24304
24448
|
}
|
|
24449
|
+
if (AGENTS[request.preset]?.identity === "write") {
|
|
24450
|
+
return refused5(
|
|
24451
|
+
"spawn_identity",
|
|
24452
|
+
`\`${request.preset}\` runs as a \`write\` identity \u2014 it pushes branches and opens pull requests \u2014 and a spawned child never writes: it reads this conversation and reports; the person who asked starts that work by hand with \`agent:${request.preset}\``
|
|
24453
|
+
);
|
|
24454
|
+
}
|
|
24305
24455
|
const minutesLeft = Math.floor(parent.remainingMs / 6e4);
|
|
24306
24456
|
if (minutesLeft < MIN_BOUNDARY_MINUTES) {
|
|
24307
24457
|
return refused5(
|
|
@@ -24354,8 +24504,10 @@ async function spawnChild(deps, parent, request) {
|
|
|
24354
24504
|
lastReply = text;
|
|
24355
24505
|
}
|
|
24356
24506
|
});
|
|
24507
|
+
const seed = parent.conversation ? textTurnsOf(parent.conversation) : void 0;
|
|
24357
24508
|
const settled = deps.dispatch(deps.core, child, io, {
|
|
24358
|
-
parent: { runId: parent.runId, depth: parent.depth + 1, remainingMs: parent.remainingMs }
|
|
24509
|
+
parent: { runId: parent.runId, depth: parent.depth + 1, remainingMs: parent.remainingMs },
|
|
24510
|
+
...seed ? { seed } : {}
|
|
24359
24511
|
}).then(
|
|
24360
24512
|
(outcome) => ({ kind: "ended", outcome }),
|
|
24361
24513
|
(err2) => ({ kind: "threw", err: err2 })
|
|
@@ -24396,8 +24548,8 @@ function spawnCapabilityFor(deps, parent) {
|
|
|
24396
24548
|
};
|
|
24397
24549
|
let previous = Promise.resolve();
|
|
24398
24550
|
return {
|
|
24399
|
-
spawn: (request,
|
|
24400
|
-
const turn = previous.then(() => spawnChild(withHook, { ...parent,
|
|
24551
|
+
spawn: (request, at) => {
|
|
24552
|
+
const turn = previous.then(() => spawnChild(withHook, { ...parent, ...at }, request));
|
|
24401
24553
|
previous = turn.catch(() => {
|
|
24402
24554
|
});
|
|
24403
24555
|
return turn;
|
|
@@ -24441,11 +24593,13 @@ var DEFAULT_MAX_CHILDREN, MAX_SPAWN_DEPTH, describe2, nullSpawnCapability;
|
|
|
24441
24593
|
var init_spawn = __esm({
|
|
24442
24594
|
"../../src/core/dispatch/spawn.ts"() {
|
|
24443
24595
|
"use strict";
|
|
24596
|
+
init_registry();
|
|
24444
24597
|
init_validate();
|
|
24445
24598
|
init_actor();
|
|
24446
24599
|
init_runsService();
|
|
24447
24600
|
init_admission();
|
|
24448
24601
|
init_awaitChildren();
|
|
24602
|
+
init_textTurns();
|
|
24449
24603
|
DEFAULT_MAX_CHILDREN = 3;
|
|
24450
24604
|
MAX_SPAWN_DEPTH = 1;
|
|
24451
24605
|
describe2 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
@@ -24470,12 +24624,61 @@ function routablePresets() {
|
|
|
24470
24624
|
maxMinutes
|
|
24471
24625
|
}));
|
|
24472
24626
|
}
|
|
24627
|
+
function partPresets(presets) {
|
|
24628
|
+
return presets.filter((p) => p.identity !== "write");
|
|
24629
|
+
}
|
|
24630
|
+
function nameList(names) {
|
|
24631
|
+
return names.length <= 1 ? names.join("") : `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}`;
|
|
24632
|
+
}
|
|
24473
24633
|
function renderPresetTable(presets) {
|
|
24474
24634
|
const rows = presets.map(
|
|
24475
24635
|
(p) => `| \`${p.name}\` | ${oneLine(p.description)} | ${p.machine} | ${p.identity} | ${p.maxMinutes} min |`
|
|
24476
24636
|
);
|
|
24477
24637
|
return ["| name | what it does | machine | credential | budget |", "|---|---|---|---|---|", ...rows].join("\n");
|
|
24478
24638
|
}
|
|
24639
|
+
function routeMaxOutputTokens(compound) {
|
|
24640
|
+
const PART_JSON_OVERHEAD = 40;
|
|
24641
|
+
const ANSWER_JSON_OVERHEAD = 80;
|
|
24642
|
+
const chars = ANSWER_JSON_OVERHEAD + ROUTE_REASON_CAP + (compound ? compound.maxParts * (ROUTE_PART_TEXT_CAP + PART_JSON_OVERHEAD) : 0);
|
|
24643
|
+
return Math.max(ROUTE_MIN_OUTPUT_TOKENS, Math.ceil(chars / 3));
|
|
24644
|
+
}
|
|
24645
|
+
function routeTool(presets, compound) {
|
|
24646
|
+
const names = presets.map((p) => p.name);
|
|
24647
|
+
const readers = partPresets(presets).map((p) => p.name);
|
|
24648
|
+
const writers = presets.filter((p) => p.identity === "write").map((p) => p.name);
|
|
24649
|
+
const table = presets.map((p) => `${p.name}: ${oneLine(p.description)}`).join("; ");
|
|
24650
|
+
const writeAsk = writers.length > 0 ? ` An ask that needs ${nameList(writers)} is never a part: when any part would need it, omit parts and answer ${nameList(writers)} for the whole request.` : "";
|
|
24651
|
+
const properties = {
|
|
24652
|
+
preset: {
|
|
24653
|
+
type: "string",
|
|
24654
|
+
enum: compound ? [...names, COMPOUND_PRESET] : names,
|
|
24655
|
+
description: `the least capable preset whose description covers the request \u2014 ${table}${compound ? `; ${COMPOUND_PRESET}: only for a compound request, with parts` : ""}`
|
|
24656
|
+
},
|
|
24657
|
+
reason: { type: "string", description: "one line, under 100 characters: why this preset" },
|
|
24658
|
+
...compound ? {
|
|
24659
|
+
parts: {
|
|
24660
|
+
type: "array",
|
|
24661
|
+
description: `only when the request has two or more INDEPENDENT asks on different subjects, each rewritten so it stands alone and each on a read-only preset (${nameList(readers)}), with preset "${COMPOUND_PRESET}".${writeAsk} A single ask with several steps is one request on one preset: omit parts and name that preset. When one ask is in doubt, omit parts.`,
|
|
24662
|
+
minItems: 2,
|
|
24663
|
+
maxItems: compound.maxParts,
|
|
24664
|
+
items: {
|
|
24665
|
+
type: "object",
|
|
24666
|
+
additionalProperties: false,
|
|
24667
|
+
required: ["preset", "text"],
|
|
24668
|
+
properties: {
|
|
24669
|
+
preset: { type: "string", enum: readers, description: "the read-only preset this part runs on" },
|
|
24670
|
+
text: { type: "string", description: "the part as a request of its own" }
|
|
24671
|
+
}
|
|
24672
|
+
}
|
|
24673
|
+
}
|
|
24674
|
+
} : {}
|
|
24675
|
+
};
|
|
24676
|
+
return {
|
|
24677
|
+
name: ROUTE_TOOL_NAME,
|
|
24678
|
+
description: "Route the request: name the preset it runs on and why.",
|
|
24679
|
+
inputSchema: { type: "object", additionalProperties: false, required: ["preset", "reason"], properties }
|
|
24680
|
+
};
|
|
24681
|
+
}
|
|
24479
24682
|
function quoteRequest(text) {
|
|
24480
24683
|
const bent = text.replace(/<(\/?)request>/gi, "\u2039$1request\u203A");
|
|
24481
24684
|
if (bent.length <= ROUTE_TEXT_CAP) return bent;
|
|
@@ -24500,7 +24703,7 @@ function buildRoutePrompt(input) {
|
|
|
24500
24703
|
"",
|
|
24501
24704
|
"Presets you may pick:",
|
|
24502
24705
|
renderPresetTable(input.presets),
|
|
24503
|
-
...input.compound ? ["", ...compoundRules(input.compound)] : []
|
|
24706
|
+
...input.compound ? ["", ...compoundRules(input.compound, input.presets)] : []
|
|
24504
24707
|
].join("\n");
|
|
24505
24708
|
const user = [
|
|
24506
24709
|
`Earlier directives in this thread: ${directivesLine(input.recentDirectives)}`,
|
|
@@ -24509,18 +24712,25 @@ function buildRoutePrompt(input) {
|
|
|
24509
24712
|
quoteRequest(input.text),
|
|
24510
24713
|
"</request>"
|
|
24511
24714
|
].join("\n");
|
|
24512
|
-
return { system, user };
|
|
24715
|
+
return { system, user, tool: routeTool(input.presets, input.compound) };
|
|
24513
24716
|
}
|
|
24514
|
-
function compoundRules(offer) {
|
|
24717
|
+
function compoundRules(offer, presets) {
|
|
24718
|
+
const readers = partPresets(presets).map((p) => p.name);
|
|
24719
|
+
const writers = presets.filter((p) => p.identity === "write").map((p) => `\`${p.name}\``);
|
|
24515
24720
|
return [
|
|
24516
24721
|
"Compound requests: when the request has two or more INDEPENDENT parts \u2014 neither part needs the other's result, and each would stand alone as a request of its own \u2014 answer this form instead, and only then:",
|
|
24517
|
-
`{"preset": "${COMPOUND_PRESET}", "parts": [{"text": "<one part, rewritten so it stands alone>", "preset": "<
|
|
24518
|
-
`Independent means neither part needs the other's result. A single ask with several steps ("clone it, run the tests, tell me what fails") is NOT compound: it is one request on one preset, however many steps it takes. Two asks on two different subjects ("summarize what is in the docs folder, and run the lint on main in a sandbox") ARE compound even when both are read-only or would land on the same preset \u2014 the same preset may appear twice. When one ask is in doubt, do not split it. At most ${offer.maxParts} parts
|
|
24722
|
+
`{"preset": "${COMPOUND_PRESET}", "parts": [{"text": "<one part, rewritten so it stands alone>", "preset": "<one of ${readers.join(", ")}>"}, \u2026], "reason": "<one line: why the parts are independent>"}`,
|
|
24723
|
+
`Independent means neither part needs the other's result. A single ask with several steps ("clone it, run the tests, tell me what fails") is NOT compound: it is one request on one preset, however many steps it takes. Two asks on two different subjects ("summarize what is in the docs folder, and run the lint on main in a sandbox") ARE compound even when both are read-only or would land on the same preset \u2014 the same preset may appear twice. When one ask is in doubt, do not split it. At most ${offer.maxParts} parts. Each part runs as a child that only reads, so each part's preset is one of ${readers.map((r) => `\`${r}\``).join(", ")} (the rows above whose credential is none or read), chosen for that part alone by the same rules as a single request \u2014 least capable first.`,
|
|
24724
|
+
...writers.length > 0 ? [writeAskRule(writers)] : []
|
|
24519
24725
|
];
|
|
24520
24726
|
}
|
|
24727
|
+
function writeAskRule(writers) {
|
|
24728
|
+
const names = writers.join(" or ");
|
|
24729
|
+
return `An ask that needs ${names} is never a part: a part runs as a child that only reads, and ${names} pushes. When any part of the request would need ${names}, do not split it: answer ${names} alone for the whole request as typed, and it reads what it must before it changes anything ("review PR 7 and fix what it finds" is one ${names} request; "research X and open a PR for Y" is one ${names} request).`;
|
|
24730
|
+
}
|
|
24521
24731
|
function imperativeRule(writers) {
|
|
24522
24732
|
const names = writers.map((w) => `\`${w}\``).join(" or ");
|
|
24523
|
-
return `Short imperatives: one terse order to change something or to make a failure go away \u2014 "fix it", "make it pass", "make the tests green", "add X", "rename Y", "bump Z" \u2014 is a request to change code even when it names no file, repository or cause: the channel or thread it arrives in is bound to a repository, and the preset that implements changes finds the failure itself. For it, answer ${names}. A question or a read-only ask about the same failure \u2014 "why did ci fail?", "check whether ci is red", "tell me why the build failed", "list the failing tests" \u2014 changes nothing: answer a read-only preset that covers it, never ${names}. An ask to look at, check or judge a pull request is a review, not an order to change it.`;
|
|
24733
|
+
return `Short imperatives: one terse order to change something or to make a failure go away \u2014 "fix it", "make it pass", "make the tests green", "add X", "rename Y", "bump Z" \u2014 is a request to change code even when it names no file, repository or cause: the channel or thread it arrives in is bound to a repository, and the preset that implements changes finds the failure itself. For it, answer ${names}. A question or a read-only ask about the same failure \u2014 "why did ci fail?", "check whether ci is red", "tell me why the build failed", "list the failing tests" \u2014 changes nothing: answer a read-only preset that covers it, never ${names}. An ask to look at, check or judge a pull request \u2014 named by a link or a number, or the thread's own ("this PR") \u2014 is a review, not an order to change it; a question about a failure with no pull request in view is not a review.`;
|
|
24524
24734
|
}
|
|
24525
24735
|
function tidyReason(reason) {
|
|
24526
24736
|
const line3 = oneLine(redactAndCap(reason, ROUTE_REASON_CAP));
|
|
@@ -24537,8 +24747,10 @@ function parseRouteAnswer(raw, allowed2, compound) {
|
|
|
24537
24747
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
24538
24748
|
return { preset: void 0, reason: `not a single JSON object: ${tidyReason(trimmed)}` };
|
|
24539
24749
|
const { preset, reason, parts } = parsed;
|
|
24540
|
-
if (typeof preset !== "string")
|
|
24541
|
-
|
|
24750
|
+
if (typeof preset !== "string")
|
|
24751
|
+
return { preset: void 0, reason: `missing preset in the router's answer: ${tidyReason(trimmed)}` };
|
|
24752
|
+
if (typeof reason !== "string")
|
|
24753
|
+
return { preset: void 0, reason: `missing reason in the router's answer: ${tidyReason(trimmed)}` };
|
|
24542
24754
|
if (preset === COMPOUND_PRESET) return parseCompound(parts, tidyReason(reason), allowed2, compound);
|
|
24543
24755
|
if (!allowed2.includes(preset))
|
|
24544
24756
|
return { preset: void 0, reason: `router said "${tidyReason(preset)}", not a preset the requester may run` };
|
|
@@ -24567,16 +24779,20 @@ function parseCompound(parts, reason, allowed2, offer) {
|
|
|
24567
24779
|
if (tidy.length === 0) return rejected(`part ${n2} has no text`);
|
|
24568
24780
|
out.push({ preset, text: tidy });
|
|
24569
24781
|
}
|
|
24782
|
+
const writer = out.find((p) => identityOf(p.preset) === "write");
|
|
24783
|
+
if (writer) return { preset: writer.preset, reason, collapsed: { presets: out.map((p) => p.preset) } };
|
|
24570
24784
|
return { preset: COMPOUND_PRESET, reason, parts: out };
|
|
24571
24785
|
}
|
|
24572
24786
|
async function route(input, model, opts = {}) {
|
|
24573
|
-
const
|
|
24787
|
+
const { compound: offer, ...rest } = input;
|
|
24788
|
+
const offered = rest.presets.filter((p) => rest.allowed.includes(p.name));
|
|
24574
24789
|
if (offered.length === 0) return { preset: void 0, reason: "no preset the requester may run" };
|
|
24575
|
-
const
|
|
24790
|
+
const compound = offer && partPresets(offered).length > 0 ? offer : void 0;
|
|
24791
|
+
const prompt = buildRoutePrompt({ ...rest, presets: offered, ...compound ? { compound } : {} });
|
|
24576
24792
|
let raw;
|
|
24577
24793
|
try {
|
|
24578
24794
|
raw = await model(prompt, {
|
|
24579
|
-
maxTokens:
|
|
24795
|
+
maxTokens: routeMaxOutputTokens(compound),
|
|
24580
24796
|
signal: AbortSignal.timeout(opts.timeoutMs ?? ROUTE_TIMEOUT_MS)
|
|
24581
24797
|
});
|
|
24582
24798
|
} catch (err2) {
|
|
@@ -24588,18 +24804,25 @@ async function route(input, model, opts = {}) {
|
|
|
24588
24804
|
return parseRouteAnswer(
|
|
24589
24805
|
raw,
|
|
24590
24806
|
offered.map((p) => p.name),
|
|
24591
|
-
|
|
24807
|
+
compound
|
|
24592
24808
|
);
|
|
24593
24809
|
}
|
|
24594
|
-
function providerRouteModel(provider, model) {
|
|
24595
|
-
|
|
24810
|
+
function providerRouteModel(provider, model, opts = {}) {
|
|
24811
|
+
const forced = (opts.answer ?? "tool") === "tool";
|
|
24812
|
+
return async (prompt, call2) => {
|
|
24596
24813
|
const result = await provider.complete({
|
|
24597
24814
|
model,
|
|
24598
24815
|
system: prompt.system,
|
|
24599
24816
|
messages: [{ role: "user", content: [{ type: "text", text: prompt.user }] }],
|
|
24600
|
-
maxTokens:
|
|
24601
|
-
signal:
|
|
24817
|
+
maxTokens: call2.maxTokens,
|
|
24818
|
+
signal: call2.signal,
|
|
24819
|
+
...forced ? { tools: [prompt.tool], toolChoice: { type: "tool", name: prompt.tool.name } } : {}
|
|
24602
24820
|
});
|
|
24821
|
+
if (result.stopReason === "max_tokens") throw new Error(`answer cut at the output cap (${call2.maxTokens} tokens)`);
|
|
24822
|
+
const answer = result.content.find(
|
|
24823
|
+
(p) => p.type === "tool_use" && p.name === prompt.tool.name
|
|
24824
|
+
);
|
|
24825
|
+
if (answer) return JSON.stringify(answer.input);
|
|
24603
24826
|
return result.content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
24604
24827
|
};
|
|
24605
24828
|
}
|
|
@@ -24617,7 +24840,9 @@ async function routeRequest(deps, ctx) {
|
|
|
24617
24840
|
if (deps.routeModel) model = deps.routeModel;
|
|
24618
24841
|
else {
|
|
24619
24842
|
const ref = parseModelRef(modelRef2);
|
|
24620
|
-
model = providerRouteModel(deps.providers.get(ref.provider), ref.model
|
|
24843
|
+
model = providerRouteModel(deps.providers.get(ref.provider), ref.model, {
|
|
24844
|
+
...cfg.routing?.answer ? { answer: cfg.routing.answer } : {}
|
|
24845
|
+
});
|
|
24621
24846
|
}
|
|
24622
24847
|
} catch (err2) {
|
|
24623
24848
|
console.log(`[route] ${msg.threadKey} not routed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
@@ -24653,18 +24878,24 @@ async function routeRequest(deps, ctx) {
|
|
|
24653
24878
|
effort: directives.effort ?? sticky.effort
|
|
24654
24879
|
}
|
|
24655
24880
|
});
|
|
24656
|
-
const parts = decision
|
|
24881
|
+
const { parts, collapsed } = decision;
|
|
24657
24882
|
console.log(
|
|
24658
|
-
`[route] ${msg.threadKey} routed to ${decision.preset} on ${modelRef2}${parts ? ` (${parts.length} parts)` : ""}: ${decision.reason}`
|
|
24883
|
+
`[route] ${msg.threadKey} routed to ${decision.preset} on ${modelRef2}${parts ? ` (${parts.length} parts)` : ""}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}: ${decision.reason}`
|
|
24659
24884
|
);
|
|
24660
24885
|
return {
|
|
24661
24886
|
kind: "routed",
|
|
24662
24887
|
resolved,
|
|
24663
|
-
route: {
|
|
24888
|
+
route: {
|
|
24889
|
+
preset: decision.preset,
|
|
24890
|
+
reason: decision.reason,
|
|
24891
|
+
model: modelRef2,
|
|
24892
|
+
...parts ? { parts } : {},
|
|
24893
|
+
...collapsed ? { collapsed } : {}
|
|
24894
|
+
}
|
|
24664
24895
|
};
|
|
24665
24896
|
}
|
|
24666
|
-
function routedLabel(reason) {
|
|
24667
|
-
return `routed: ${reason}`;
|
|
24897
|
+
function routedLabel(reason, collapsed) {
|
|
24898
|
+
return `routed: ${reason}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}`;
|
|
24668
24899
|
}
|
|
24669
24900
|
function routedPartLines(parts) {
|
|
24670
24901
|
return parts.map((p) => {
|
|
@@ -24680,7 +24911,7 @@ function compoundBrief(text, parts) {
|
|
|
24680
24911
|
...parts.map((p, i) => `${i + 1}. \`${p.preset}\`: ${p.text}`)
|
|
24681
24912
|
].join("\n");
|
|
24682
24913
|
}
|
|
24683
|
-
var ROUTE_TEXT_CAP, ROUTE_REASON_CAP,
|
|
24914
|
+
var ROUTE_TEXT_CAP, ROUTE_REASON_CAP, ROUTE_MIN_OUTPUT_TOKENS, ROUTE_TOOL_NAME, ROUTE_TIMEOUT_MS, ROUTE_PART_TEXT_CAP, ROUTE_PART_LINE_CAP, COMPOUND_BRIEF_HEADING, identityOf, ROUTED_CARD_FOOTER;
|
|
24684
24915
|
var init_route = __esm({
|
|
24685
24916
|
"../../src/core/dispatch/route.ts"() {
|
|
24686
24917
|
"use strict";
|
|
@@ -24691,11 +24922,13 @@ var init_route = __esm({
|
|
|
24691
24922
|
init_spawn();
|
|
24692
24923
|
ROUTE_TEXT_CAP = 2e3;
|
|
24693
24924
|
ROUTE_REASON_CAP = 120;
|
|
24694
|
-
|
|
24925
|
+
ROUTE_MIN_OUTPUT_TOKENS = 200;
|
|
24926
|
+
ROUTE_TOOL_NAME = "route";
|
|
24695
24927
|
ROUTE_TIMEOUT_MS = 8e3;
|
|
24696
24928
|
ROUTE_PART_TEXT_CAP = 1e3;
|
|
24697
24929
|
ROUTE_PART_LINE_CAP = 100;
|
|
24698
24930
|
COMPOUND_BRIEF_HEADING = "Routed as a compound request";
|
|
24931
|
+
identityOf = (preset) => AGENTS[preset]?.identity;
|
|
24699
24932
|
ROUTED_CARD_FOOTER = "reply agent:<preset> to run it another way";
|
|
24700
24933
|
}
|
|
24701
24934
|
});
|
|
@@ -25723,19 +25956,162 @@ ${skill.body}${footer}`;
|
|
|
25723
25956
|
}
|
|
25724
25957
|
});
|
|
25725
25958
|
|
|
25959
|
+
// ../../src/artifacts/contentType.ts
|
|
25960
|
+
function contentTypeFor(name) {
|
|
25961
|
+
const dot = name.lastIndexOf(".");
|
|
25962
|
+
if (dot < 0 || dot === name.length - 1) return OCTET_STREAM;
|
|
25963
|
+
return BY_EXTENSION[name.slice(dot + 1).toLowerCase()] ?? OCTET_STREAM;
|
|
25964
|
+
}
|
|
25965
|
+
var BY_EXTENSION, OCTET_STREAM, INLINE_IMAGE_TYPES;
|
|
25966
|
+
var init_contentType = __esm({
|
|
25967
|
+
"../../src/artifacts/contentType.ts"() {
|
|
25968
|
+
"use strict";
|
|
25969
|
+
BY_EXTENSION = {
|
|
25970
|
+
png: "image/png",
|
|
25971
|
+
jpg: "image/jpeg",
|
|
25972
|
+
jpeg: "image/jpeg",
|
|
25973
|
+
gif: "image/gif",
|
|
25974
|
+
webp: "image/webp",
|
|
25975
|
+
svg: "image/svg+xml",
|
|
25976
|
+
pdf: "application/pdf",
|
|
25977
|
+
mp4: "video/mp4",
|
|
25978
|
+
webm: "video/webm",
|
|
25979
|
+
mov: "video/quicktime",
|
|
25980
|
+
mp3: "audio/mpeg",
|
|
25981
|
+
wav: "audio/wav",
|
|
25982
|
+
txt: "text/plain",
|
|
25983
|
+
md: "text/markdown",
|
|
25984
|
+
csv: "text/csv",
|
|
25985
|
+
json: "application/json",
|
|
25986
|
+
html: "text/html",
|
|
25987
|
+
zip: "application/zip",
|
|
25988
|
+
gz: "application/gzip",
|
|
25989
|
+
tar: "application/x-tar"
|
|
25990
|
+
};
|
|
25991
|
+
OCTET_STREAM = "application/octet-stream";
|
|
25992
|
+
INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
25993
|
+
}
|
|
25994
|
+
});
|
|
25995
|
+
|
|
25996
|
+
// ../../src/artifacts/keys.ts
|
|
25997
|
+
function safeBasename(name) {
|
|
25998
|
+
const segments2 = name.split(/[\\/]+/).filter((s) => s.length > 0);
|
|
25999
|
+
const last = segments2[segments2.length - 1] ?? "";
|
|
26000
|
+
const reduced = last.replace(SAFE, "_").replace(/\.{2,}/g, "_").replace(/_+/g, "_");
|
|
26001
|
+
if (reduced === "" || /^[._-]+$/.test(reduced)) return "file";
|
|
26002
|
+
return reduced;
|
|
26003
|
+
}
|
|
26004
|
+
function threadKeySafe(threadKey) {
|
|
26005
|
+
return threadKey.replace(/:/g, "-").replace(SAFE, "_");
|
|
26006
|
+
}
|
|
26007
|
+
function outboundKey(runId3, seq, name) {
|
|
26008
|
+
return `runs/${runId3}/out/${seq}-${safeBasename(name)}`;
|
|
26009
|
+
}
|
|
26010
|
+
function inboundKey(threadKey, messageTs, index, name) {
|
|
26011
|
+
return `threads/${threadKeySafe(threadKey)}/in/${messageTs}/${index}-${safeBasename(name)}`;
|
|
26012
|
+
}
|
|
26013
|
+
var SAFE;
|
|
26014
|
+
var init_keys = __esm({
|
|
26015
|
+
"../../src/artifacts/keys.ts"() {
|
|
26016
|
+
"use strict";
|
|
26017
|
+
SAFE = /[^A-Za-z0-9._-]+/g;
|
|
26018
|
+
}
|
|
26019
|
+
});
|
|
26020
|
+
|
|
25726
26021
|
// ../../src/tools/attach.ts
|
|
26022
|
+
function transferBudgetMs(size) {
|
|
26023
|
+
return Math.ceil(size / TRANSFER_FLOOR_BYTES_PER_MS) + TRANSFER_SETUP_MS;
|
|
26024
|
+
}
|
|
25727
26025
|
function fileNameOf2(path) {
|
|
25728
26026
|
const segments2 = path.split("/").filter(Boolean);
|
|
25729
26027
|
return segments2[segments2.length - 1] ?? path;
|
|
25730
26028
|
}
|
|
25731
|
-
|
|
26029
|
+
function putCommandFor(path, contentType, url) {
|
|
26030
|
+
return `curl -fsS -T ${shellQuote(path)} -H ${shellQuote(`Content-Type: ${contentType}`)} ${shellQuote(url)}`;
|
|
26031
|
+
}
|
|
26032
|
+
function postCommandFor(path, url) {
|
|
26033
|
+
return `curl -fsS --data-binary @${shellQuote(path)} ${shellQuote(url)}`;
|
|
26034
|
+
}
|
|
26035
|
+
async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
26036
|
+
const exec = (command2, timeoutMs2) => {
|
|
26037
|
+
const opts = { timeoutMs: timeoutMs2, ...ctx.signal ? { signal: ctx.signal } : {} };
|
|
26038
|
+
return ctx.executor.exec(command2, opts);
|
|
26039
|
+
};
|
|
26040
|
+
const statOut = await exec(statCommandFor(shellQuote(path)), STAT_TIMEOUT_MS);
|
|
26041
|
+
if (parseExitPrefix(statOut).failed) return `error: could not read ${path}: ${statOut.trim()}`;
|
|
26042
|
+
const size = parseByteSize(statOut);
|
|
26043
|
+
if (size === null) return `error: could not measure ${path}: ${statOut.trim()}`;
|
|
26044
|
+
if (size === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
26045
|
+
if (size > MAX_ARTIFACT_BYTES) {
|
|
26046
|
+
return `error: ${path} is ${size} bytes; attach_file takes files up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) \u2014 link to the file instead`;
|
|
26047
|
+
}
|
|
26048
|
+
let timeoutMs = BASH_TIMEOUT_MAX_MS;
|
|
26049
|
+
if (ctx.remainingMs) {
|
|
26050
|
+
const budget = bashBudgetWithinRun(BASH_TIMEOUT_MAX_MS, ctx.remainingMs());
|
|
26051
|
+
if (budget.kind === "exhausted") return `error: ${budget.note}`;
|
|
26052
|
+
if (budget.kind === "clipped") timeoutMs = budget.timeoutMs;
|
|
26053
|
+
}
|
|
26054
|
+
const needed = transferBudgetMs(size);
|
|
26055
|
+
if (needed > timeoutMs) {
|
|
26056
|
+
return `error: ${name} is ${size} bytes and needs about ${secs(needed)}s to move at ${TRANSFER_FLOOR_BYTES_PER_MS} KB/s; the run has ${secs(timeoutMs)}s of command budget left \u2014 write up what you have and link to the file instead`;
|
|
26057
|
+
}
|
|
26058
|
+
const contentType = contentTypeFor(name);
|
|
26059
|
+
const key = outboundKey(artifacts.runId, artifacts.nextSeq(), name);
|
|
26060
|
+
const putUrl = await artifacts.store.presignPut(key, contentType);
|
|
26061
|
+
const putOut = await exec(putCommandFor(path, contentType, putUrl), timeoutMs);
|
|
26062
|
+
if (parseExitPrefix(putOut).failed) {
|
|
26063
|
+
return `error: the upload of ${name} to the artifact store failed: ${putOut.trim()}`;
|
|
26064
|
+
}
|
|
26065
|
+
const head = await artifacts.store.head(key);
|
|
26066
|
+
if (!head)
|
|
26067
|
+
return `error: the artifact store holds nothing under ${key} after the upload of ${name} \u2014 nothing was posted`;
|
|
26068
|
+
if (head.size !== size) {
|
|
26069
|
+
return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured \u2014 nothing was posted`;
|
|
26070
|
+
}
|
|
26071
|
+
ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType });
|
|
26072
|
+
if (!ctx.uploadTicket) {
|
|
26073
|
+
const where = artifacts.runUrl ? ` \u2014 ${artifacts.runUrl}` : "";
|
|
26074
|
+
await artifacts.reply(`${lead}
|
|
26075
|
+
\u{1F4CE} ${name} (${size} bytes) is on the run page${where}`);
|
|
26076
|
+
return `attached ${name} (${size} bytes) to the run page; this conversation's channel takes no file uploads, so the lead and the page's link were posted instead`;
|
|
26077
|
+
}
|
|
26078
|
+
const kept = "the file is kept on the run page";
|
|
26079
|
+
let ticket;
|
|
26080
|
+
try {
|
|
26081
|
+
ticket = await ctx.uploadTicket({ name, size });
|
|
26082
|
+
} catch (err2) {
|
|
26083
|
+
return `error: the channel refused an upload ticket for ${name}: ${describe3(err2)}; ${kept}`;
|
|
26084
|
+
}
|
|
26085
|
+
const postOut = await exec(postCommandFor(path, ticket.url), timeoutMs);
|
|
26086
|
+
if (parseExitPrefix(postOut).failed) {
|
|
26087
|
+
return `error: the upload of ${name} to the channel failed: ${postOut.trim()}; ${kept}`;
|
|
26088
|
+
}
|
|
26089
|
+
try {
|
|
26090
|
+
await ticket.complete(lead);
|
|
26091
|
+
} catch (err2) {
|
|
26092
|
+
return `error: the channel refused to complete the upload of ${name}: ${describe3(err2)}; ${kept}`;
|
|
26093
|
+
}
|
|
26094
|
+
return `attached ${name} (${size} bytes) to the conversation and the run page`;
|
|
26095
|
+
}
|
|
26096
|
+
var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe3, secs, attachFileTool;
|
|
25732
26097
|
var init_attach = __esm({
|
|
25733
26098
|
"../../src/tools/attach.ts"() {
|
|
25734
26099
|
"use strict";
|
|
26100
|
+
init_contentType();
|
|
26101
|
+
init_keys();
|
|
26102
|
+
init_runEvents();
|
|
26103
|
+
init_bashTimeout();
|
|
25735
26104
|
init_binaryRead();
|
|
26105
|
+
init_shellQuote();
|
|
26106
|
+
MAX_ARTIFACT_BYTES = 1073741824;
|
|
26107
|
+
TRANSFER_FLOOR_BYTES_PER_MS = 1024;
|
|
26108
|
+
TRANSFER_SETUP_MS = 1e4;
|
|
26109
|
+
STAT_TIMEOUT_MS = 3e4;
|
|
26110
|
+
describe3 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
26111
|
+
secs = (ms) => Math.max(0, Math.round(ms / 1e3));
|
|
25736
26112
|
attachFileTool = {
|
|
25737
26113
|
name: "attach_file",
|
|
25738
|
-
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
|
|
26114
|
+
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.`,
|
|
25739
26115
|
inputSchema: {
|
|
25740
26116
|
type: "object",
|
|
25741
26117
|
properties: {
|
|
@@ -25752,6 +26128,7 @@ var init_attach = __esm({
|
|
|
25752
26128
|
if (!path) return "error: path is required";
|
|
25753
26129
|
const name = fileNameOf2(path);
|
|
25754
26130
|
const lead = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : name;
|
|
26131
|
+
if (ctx.artifacts) return attachThroughStore(path, name, lead, ctx.artifacts, ctx);
|
|
25755
26132
|
if (!ctx.attach) {
|
|
25756
26133
|
return "attach_file is not available here: this conversation's channel takes no file uploads \u2014 link to the file instead";
|
|
25757
26134
|
}
|
|
@@ -25763,13 +26140,13 @@ var init_attach = __esm({
|
|
|
25763
26140
|
try {
|
|
25764
26141
|
bytes = await readBytes(path);
|
|
25765
26142
|
} catch (err2) {
|
|
25766
|
-
return `error: could not read ${path}: ${
|
|
26143
|
+
return `error: could not read ${path}: ${describe3(err2)}`;
|
|
25767
26144
|
}
|
|
25768
26145
|
if (bytes.byteLength === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
25769
26146
|
try {
|
|
25770
26147
|
await ctx.attach({ name, bytes, lead });
|
|
25771
26148
|
} catch (err2) {
|
|
25772
|
-
return `error: the channel refused the upload of ${name}: ${
|
|
26149
|
+
return `error: the channel refused the upload of ${name}: ${describe3(err2)}`;
|
|
25773
26150
|
}
|
|
25774
26151
|
return `attached ${name} (${bytes.byteLength} bytes) to the conversation`;
|
|
25775
26152
|
}
|
|
@@ -25902,7 +26279,7 @@ async function nextTick(wait, ids, ms, signal) {
|
|
|
25902
26279
|
signal?.removeEventListener("abort", end);
|
|
25903
26280
|
}
|
|
25904
26281
|
}
|
|
25905
|
-
var UNAVAILABLE2, NOT_FOUND,
|
|
26282
|
+
var UNAVAILABLE2, NOT_FOUND, CHILD_PRESETS, WRITE_PRESETS, REPO_PRESETS, CHILDREN_PAGES_MAX, spawnRunTool, sendToRunTool, AWAIT_IDS_MAX, awaitRunsTool, listRunsTool, getRunStatusTool, RUN_TOOLS;
|
|
25906
26283
|
var init_runs2 = __esm({
|
|
25907
26284
|
"../../src/tools/runs.ts"() {
|
|
25908
26285
|
"use strict";
|
|
@@ -25917,16 +26294,24 @@ var init_runs2 = __esm({
|
|
|
25917
26294
|
init_runsService();
|
|
25918
26295
|
UNAVAILABLE2 = "run tools are not available in this context.";
|
|
25919
26296
|
NOT_FOUND = "not_found";
|
|
25920
|
-
|
|
26297
|
+
CHILD_PRESETS = () => Object.values(AGENTS).filter((a) => a.name !== "conductor" && a.identity !== "write").map((a) => a.name);
|
|
26298
|
+
WRITE_PRESETS = () => Object.values(AGENTS).filter((a) => a.identity === "write").map((a) => a.name);
|
|
26299
|
+
REPO_PRESETS = () => Object.values(AGENTS).filter((a) => machineNeedsRepo(a.machine)).map((a) => a.name);
|
|
25921
26300
|
CHILDREN_PAGES_MAX = 25;
|
|
25922
26301
|
spawnRunTool = {
|
|
25923
26302
|
name: "spawn_run",
|
|
25924
|
-
description:
|
|
26303
|
+
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.`,
|
|
25925
26304
|
inputSchema: {
|
|
25926
26305
|
type: "object",
|
|
25927
26306
|
properties: {
|
|
25928
|
-
preset: {
|
|
25929
|
-
|
|
26307
|
+
preset: {
|
|
26308
|
+
type: "string",
|
|
26309
|
+
description: `The preset the child runs: one of ${CHILD_PRESETS().join(", ")} (a preset that writes \u2014 ${WRITE_PRESETS().join(", ")} \u2014 is refused spawn_identity)`
|
|
26310
|
+
},
|
|
26311
|
+
prompt: {
|
|
26312
|
+
type: "string",
|
|
26313
|
+
description: "What the child should do \u2014 its one new turn after this conversation's text"
|
|
26314
|
+
},
|
|
25930
26315
|
repo: { type: "string", description: "owner/name of the repository the child works in, for a repository preset" },
|
|
25931
26316
|
budget: { type: "integer", description: "The child's wall clock in whole minutes, at least 2 (optional)" }
|
|
25932
26317
|
},
|
|
@@ -25934,9 +26319,10 @@ var init_runs2 = __esm({
|
|
|
25934
26319
|
},
|
|
25935
26320
|
async run(input, ctx) {
|
|
25936
26321
|
const preset = String(input.preset ?? "");
|
|
25937
|
-
if (!Object.hasOwn(AGENTS, preset))
|
|
26322
|
+
if (!Object.hasOwn(AGENTS, preset))
|
|
26323
|
+
return `error: unknown preset "${preset}" \u2014 one of ${CHILD_PRESETS().join(", ")}`;
|
|
25938
26324
|
const prompt = String(input.prompt ?? "").trim();
|
|
25939
|
-
if (!prompt) return "error: prompt is required \u2014 the child
|
|
26325
|
+
if (!prompt) return "error: prompt is required \u2014 what the child should do, after this conversation's text";
|
|
25940
26326
|
const request = { preset, prompt };
|
|
25941
26327
|
if (input.repo !== void 0 && String(input.repo).trim()) request.repo = String(input.repo).trim();
|
|
25942
26328
|
if (input.budget !== void 0) {
|
|
@@ -25946,7 +26332,10 @@ var init_runs2 = __esm({
|
|
|
25946
26332
|
request.budget = budget;
|
|
25947
26333
|
}
|
|
25948
26334
|
const spawn7 = ctx.spawn ?? nullSpawnCapability;
|
|
25949
|
-
const out = await spawn7.spawn(request,
|
|
26335
|
+
const out = await spawn7.spawn(request, {
|
|
26336
|
+
remainingMs: ctx.remainingMs?.() ?? Number.POSITIVE_INFINITY,
|
|
26337
|
+
...ctx.conversation ? { conversation: ctx.conversation() } : {}
|
|
26338
|
+
});
|
|
25950
26339
|
if (out.kind === "refused") return `spawn refused (${out.reason}): ${out.message}`;
|
|
25951
26340
|
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.`;
|
|
25952
26341
|
}
|
|
@@ -26310,7 +26699,7 @@ ${raw.trim()}`;
|
|
|
26310
26699
|
};
|
|
26311
26700
|
submitDispositionsTool = {
|
|
26312
26701
|
name: "submit_dispositions",
|
|
26313
|
-
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
|
|
26702
|
+
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.",
|
|
26314
26703
|
inputSchema: {
|
|
26315
26704
|
type: "object",
|
|
26316
26705
|
properties: {
|
|
@@ -26333,15 +26722,7 @@ ${raw.trim()}`;
|
|
|
26333
26722
|
async run(input, ctx) {
|
|
26334
26723
|
const parsed = parseDispositionsInput(input);
|
|
26335
26724
|
if (!parsed) return "error: dispositions must be an array of { findingId, disposition: fixed|declined, note }";
|
|
26336
|
-
if (ctx.
|
|
26337
|
-
const known = new Set(ctx.knownFindingIds);
|
|
26338
|
-
const unknown = [...new Set(parsed.dispositions.map((d) => d.findingId).filter((id) => !known.has(id)))];
|
|
26339
|
-
if (unknown.length) {
|
|
26340
|
-
return `error: unknown finding id${unknown.length === 1 ? "" : "s"} ${unknown.join(", ")} \u2014 use exactly the ids from the review's findings list`;
|
|
26341
|
-
}
|
|
26342
|
-
}
|
|
26343
|
-
if (!ctx.onDispositions)
|
|
26344
|
-
return "no ship fix round is active here \u2014 dispositions were not recorded (they apply only when addressing a ship review's findings)";
|
|
26725
|
+
if (!ctx.onDispositions) return "no run is recording dispositions here";
|
|
26345
26726
|
ctx.onDispositions(parsed.dispositions);
|
|
26346
26727
|
const drops = parsed.dropped.length ? ` (dropped: ${parsed.dropped.join("; ")})` : "";
|
|
26347
26728
|
return `dispositions recorded: ${parsed.dispositions.length}${drops}; a later call replaces this one`;
|
|
@@ -26673,14 +27054,34 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26673
27054
|
const deadline = now() + (opts.resume ? opts.resume.remainingMs : opts.agent.maxMinutes * 6e4);
|
|
26674
27055
|
const warnAt = deadline - Math.min(3 * 6e4, opts.agent.maxMinutes * 15e3);
|
|
26675
27056
|
toolContext.remainingMs = () => deadline - now();
|
|
27057
|
+
toolContext.conversation = () => messages;
|
|
26676
27058
|
let warned = false;
|
|
26677
27059
|
let sandboxDead = false;
|
|
27060
|
+
let stuckLoop = false;
|
|
27061
|
+
let stuckSig;
|
|
27062
|
+
let stuckStreak = 0;
|
|
27063
|
+
let stuckNudged = false;
|
|
27064
|
+
const trackStuck = (tu, ok2) => {
|
|
27065
|
+
const sig = callSignature(tu.name, redactAndCap(describeToolCall(tu)));
|
|
27066
|
+
if (ok2) {
|
|
27067
|
+
if (sig === stuckSig) {
|
|
27068
|
+
stuckSig = void 0;
|
|
27069
|
+
stuckStreak = 0;
|
|
27070
|
+
stuckNudged = false;
|
|
27071
|
+
}
|
|
27072
|
+
return;
|
|
27073
|
+
}
|
|
27074
|
+
if (sig !== stuckSig) stuckNudged = false;
|
|
27075
|
+
stuckStreak = sig === stuckSig ? stuckStreak + 1 : 1;
|
|
27076
|
+
stuckSig = sig;
|
|
27077
|
+
};
|
|
26678
27078
|
let turn = 0;
|
|
26679
27079
|
const wouldStep = (turns, iteration) => turns < opts.agent.maxTurns && iteration < opts.agent.maxTurns * 2 && now() < deadline && !control?.requested;
|
|
26680
27080
|
const pendingFollowUps = () => (opts.inbox?.size ?? 0) > 0;
|
|
26681
27081
|
let inboxConsumedSeq = opts.resume?.inboxConsumedSeq ?? 0;
|
|
26682
|
-
const drainFollowUps = (superseded = false) => {
|
|
27082
|
+
const drainFollowUps = async (superseded = false) => {
|
|
26683
27083
|
const inputs = opts.inbox?.drain() ?? [];
|
|
27084
|
+
const stagedLine = opts.stageFollowUps ? await opts.stageFollowUps(inputs) : "";
|
|
26684
27085
|
for (const input of inputs) {
|
|
26685
27086
|
if (input.ledgerSeq !== void 0 && input.ledgerSeq > inboxConsumedSeq) inboxConsumedSeq = input.ledgerSeq;
|
|
26686
27087
|
const source2 = {
|
|
@@ -26691,7 +27092,10 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26691
27092
|
emit({ type: "input", text: redactSecrets(input.text), ...Object.keys(source2).length > 0 ? { source: source2 } : {} });
|
|
26692
27093
|
note("follow_up", `follow-up folded in: ${redactSecrets(followUpSnippet(input))}`);
|
|
26693
27094
|
}
|
|
26694
|
-
const
|
|
27095
|
+
const prompt = followUpPrompt(inputs, { superseded });
|
|
27096
|
+
const parts = [{ type: "text", text: stagedLine ? `${prompt}
|
|
27097
|
+
|
|
27098
|
+
${stagedLine}` : prompt }];
|
|
26695
27099
|
for (const input of inputs) {
|
|
26696
27100
|
for (const img of input.images ?? []) parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
|
|
26697
27101
|
for (const doc of input.documents ?? [])
|
|
@@ -26708,12 +27112,15 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26708
27112
|
const body = async (callSpan) => {
|
|
26709
27113
|
if (announceIt) announce(tu, callSpan?.id);
|
|
26710
27114
|
const spanId = callSpan ? { spanId: callSpan.id } : {};
|
|
26711
|
-
const settle2 = (ok2, extra = {}) =>
|
|
26712
|
-
|
|
26713
|
-
ok:
|
|
26714
|
-
|
|
26715
|
-
|
|
26716
|
-
|
|
27115
|
+
const settle2 = (ok2, extra = {}) => {
|
|
27116
|
+
trackStuck(tu, ok2);
|
|
27117
|
+
callSpan?.end(ok2 ? "ok" : "error", {
|
|
27118
|
+
callId: tu.id,
|
|
27119
|
+
ok: ok2,
|
|
27120
|
+
...extra.exitCode !== void 0 ? { exitCode: extra.exitCode } : {},
|
|
27121
|
+
...extra.infra ? { infra: true } : {}
|
|
27122
|
+
});
|
|
27123
|
+
};
|
|
26717
27124
|
const tool = toolsByName.get(tu.name);
|
|
26718
27125
|
if (!tool) {
|
|
26719
27126
|
emit({
|
|
@@ -26897,7 +27304,7 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26897
27304
|
turn++;
|
|
26898
27305
|
if (text2) emit({ type: "assistant", text: redactSecrets(text2) });
|
|
26899
27306
|
messages.push({ role: "assistant", content: result.content });
|
|
26900
|
-
messages.push({ role: "user", content: drainFollowUps(true) });
|
|
27307
|
+
messages.push({ role: "user", content: await drainFollowUps(true) });
|
|
26901
27308
|
continue;
|
|
26902
27309
|
}
|
|
26903
27310
|
return text2 || "_(no response)_";
|
|
@@ -26922,6 +27329,10 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26922
27329
|
reportedUpTo = messages.length;
|
|
26923
27330
|
}
|
|
26924
27331
|
const results = await dispatchToolUses(toolUses);
|
|
27332
|
+
if (!stuckNudged && stuckStreak >= STUCK_NUDGE_AT) {
|
|
27333
|
+
stuckNudged = true;
|
|
27334
|
+
results.push({ type: "text", text: stuckLoopNudge() });
|
|
27335
|
+
}
|
|
26925
27336
|
if (!warned && now() >= warnAt) {
|
|
26926
27337
|
warned = true;
|
|
26927
27338
|
const minutesLeft = Math.max(1, Math.round((deadline - now()) / 6e4));
|
|
@@ -26929,12 +27340,16 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26929
27340
|
results.push({ type: "text", text: wrapUpInstruction(minutesLeft) });
|
|
26930
27341
|
}
|
|
26931
27342
|
const dead = execTracker.consecutiveInfraFailures >= MAX_CONSECUTIVE_INFRA_FAILURES;
|
|
26932
|
-
if (pendingFollowUps() && !dead && wouldStep(turn, iteration + 1)) results.push(...drainFollowUps());
|
|
27343
|
+
if (pendingFollowUps() && !dead && wouldStep(turn, iteration + 1)) results.push(...await drainFollowUps());
|
|
26933
27344
|
messages.push({ role: "user", content: results });
|
|
26934
27345
|
if (dead) {
|
|
26935
27346
|
sandboxDead = true;
|
|
26936
27347
|
break;
|
|
26937
27348
|
}
|
|
27349
|
+
if (stuckStreak >= STUCK_WRITE_UP_AT) {
|
|
27350
|
+
stuckLoop = true;
|
|
27351
|
+
break;
|
|
27352
|
+
}
|
|
26938
27353
|
}
|
|
26939
27354
|
narrateHeld();
|
|
26940
27355
|
if (control?.requested === "soft") {
|
|
@@ -26946,6 +27361,11 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26946
27361
|
note("sandbox_dead", `${diagnosis} \u2014 aborting instead of retrying into a dead sandbox`);
|
|
26947
27362
|
return await finishSandboxDead(complete2, opts, messages, system, diagnosis);
|
|
26948
27363
|
}
|
|
27364
|
+
if (stuckLoop) {
|
|
27365
|
+
note("stuck_loop", stuckLoopNote());
|
|
27366
|
+
const text2 = await runFinale(complete2, opts, messages, system, stuckLoopInstruction());
|
|
27367
|
+
return stuckLoopAnswer(text2);
|
|
27368
|
+
}
|
|
26949
27369
|
const wasTimeout = now() >= deadline;
|
|
26950
27370
|
const elapsedMs = opts.agent.maxMinutes * 6e4 - (deadline - now());
|
|
26951
27371
|
const pace = turnGuardPace(turn, elapsedMs);
|
|
@@ -27047,7 +27467,7 @@ function withSpanId(e, spanId) {
|
|
|
27047
27467
|
return e;
|
|
27048
27468
|
}
|
|
27049
27469
|
}
|
|
27050
|
-
var MAX_CONSECUTIVE_INFRA_FAILURES, HardStopError, HARD_STOP_MESSAGE, wrapUpNote, wrapUpInstruction, WRITE_UP_REQUEST, timeBudgetInstruction, turnGuardInstruction, SOFT_STOP_INSTRUCTION, turnGuardPace, timeBudgetNote, turnGuardNote, softStopNote, hardStopNote, timeBudgetAnswer, turnGuardAnswer, softStopAnswer, FinaleTimeoutError, FINALE_TIMEOUT_MS;
|
|
27470
|
+
var MAX_CONSECUTIVE_INFRA_FAILURES, HardStopError, HARD_STOP_MESSAGE, wrapUpNote, wrapUpInstruction, WRITE_UP_REQUEST, timeBudgetInstruction, turnGuardInstruction, SOFT_STOP_INSTRUCTION, turnGuardPace, timeBudgetNote, turnGuardNote, softStopNote, hardStopNote, timeBudgetAnswer, turnGuardAnswer, STUCK_NUDGE_AT, STUCK_WRITE_UP_AT, stuckLoopNudge, stuckLoopNote, stuckLoopInstruction, stuckLoopAnswer, softStopAnswer, FinaleTimeoutError, FINALE_TIMEOUT_MS;
|
|
27051
27471
|
var init_runner = __esm({
|
|
27052
27472
|
"../../src/runner.ts"() {
|
|
27053
27473
|
"use strict";
|
|
@@ -27058,6 +27478,7 @@ var init_runner = __esm({
|
|
|
27058
27478
|
init_tracingExecutor();
|
|
27059
27479
|
init_workspace();
|
|
27060
27480
|
init_formatDuration();
|
|
27481
|
+
init_runFriction();
|
|
27061
27482
|
MAX_CONSECUTIVE_INFRA_FAILURES = 2;
|
|
27062
27483
|
HardStopError = class extends Error {
|
|
27063
27484
|
constructor() {
|
|
@@ -27083,6 +27504,14 @@ ${text}` : `Stopped at the ${maxMinutes}-minute budget without finishing. Partia
|
|
|
27083
27504
|
turnGuardAnswer = (text, pace) => text ? `\u26A0\uFE0F _Stopped after ${pace} \u2014 that pace looks like a loop; findings so far:_
|
|
27084
27505
|
|
|
27085
27506
|
${text}` : `Stopped after ${pace} \u2014 that pace looks like a loop \u2014 without finishing. Partial work may exist in the workspace \u2014 look for a retry loop in the run's events before trying again.`;
|
|
27507
|
+
STUCK_NUDGE_AT = 3;
|
|
27508
|
+
STUCK_WRITE_UP_AT = 6;
|
|
27509
|
+
stuckLoopNudge = () => `\u26A0\uFE0F That exact tool call has now failed ${STUCK_NUDGE_AT} times identically. Change approach \u2014 different arguments, a different tool, a smaller step \u2014 or write up what you have.`;
|
|
27510
|
+
stuckLoopNote = () => `stuck loop: the same call failed ${STUCK_WRITE_UP_AT} times identically \u2014 writing up findings so far`;
|
|
27511
|
+
stuckLoopInstruction = () => `The same tool call has failed ${STUCK_WRITE_UP_AT} times identically; the run is stuck and can make no more tool calls. ${WRITE_UP_REQUEST}`;
|
|
27512
|
+
stuckLoopAnswer = (text) => text ? `\u26A0\uFE0F _Stopped after the same call failed ${STUCK_WRITE_UP_AT} times identically \u2014 findings so far:_
|
|
27513
|
+
|
|
27514
|
+
${text}` : `Stopped after the same call failed ${STUCK_WRITE_UP_AT} times identically, without finishing. Partial work may exist in the workspace \u2014 look at the failing call in the run's events before trying again.`;
|
|
27086
27515
|
softStopAnswer = (text) => text ? `\u23F9 _Stopped early by an operator (soft stop) \u2014 findings so far:_
|
|
27087
27516
|
|
|
27088
27517
|
${text}` : "\u23F9 Stopped early by an operator (soft stop) before any findings were written. Partial work may exist in the workspace.";
|
|
@@ -27925,11 +28354,107 @@ var init_messages = __esm({
|
|
|
27925
28354
|
init_runEvents();
|
|
27926
28355
|
init_runRecord();
|
|
27927
28356
|
init_reply();
|
|
28357
|
+
init_textTurns();
|
|
27928
28358
|
CONTEXT_MAX_ITEMS = 20;
|
|
27929
28359
|
CONTEXT_MAX_BYTES = 256 * 1024;
|
|
27930
28360
|
}
|
|
27931
28361
|
});
|
|
27932
28362
|
|
|
28363
|
+
// ../../src/core/dispatch/staging.ts
|
|
28364
|
+
function stagedBasename(index, name) {
|
|
28365
|
+
return `${index}-${safeBasename(name)}`;
|
|
28366
|
+
}
|
|
28367
|
+
function pullCommandFor(url, basename4) {
|
|
28368
|
+
return `mkdir -p ${ATTACHMENTS_DIR} && curl -fsS -o ${shellQuote(`${ATTACHMENTS_DIR}/${basename4}`)} ${shellQuote(url)}`;
|
|
28369
|
+
}
|
|
28370
|
+
function excludeCommand() {
|
|
28371
|
+
const entry = shellQuote(`${ATTACHMENTS_DIR}/`);
|
|
28372
|
+
return `mkdir -p .git/info && { grep -qxF ${entry} .git/info/exclude 2>/dev/null || echo ${entry} >> .git/info/exclude; }`;
|
|
28373
|
+
}
|
|
28374
|
+
function formatSize(bytes) {
|
|
28375
|
+
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
|
|
28376
|
+
if (bytes >= 1e6) return `${Math.round(bytes / 1e6)} MB`;
|
|
28377
|
+
if (bytes >= 1e3) return `${Math.round(bytes / 1e3)} KB`;
|
|
28378
|
+
return `${bytes} B`;
|
|
28379
|
+
}
|
|
28380
|
+
function attachmentsLine(outcomes) {
|
|
28381
|
+
const landed = outcomes.filter((o) => o.error === void 0);
|
|
28382
|
+
const failed = outcomes.filter((o) => o.error !== void 0);
|
|
28383
|
+
const parts = [];
|
|
28384
|
+
if (landed.length > 0) {
|
|
28385
|
+
parts.push(
|
|
28386
|
+
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map((o) => `${o.basename} (${formatSize(o.file.size)}, ${o.file.type})`).join(", ")}`
|
|
28387
|
+
);
|
|
28388
|
+
}
|
|
28389
|
+
for (const o of failed) parts.push(`${o.file.name} could not be staged: ${o.error}`);
|
|
28390
|
+
return parts.join(". ");
|
|
28391
|
+
}
|
|
28392
|
+
function noWorkspaceLine(files) {
|
|
28393
|
+
return files.map(
|
|
28394
|
+
(f) => `this agent has no workspace for ${f.name} (${formatSize(f.size)}, ${f.type}); ask \`agent:coding\` to work with it`
|
|
28395
|
+
).join(". ");
|
|
28396
|
+
}
|
|
28397
|
+
function stagingIndex() {
|
|
28398
|
+
let n2 = 0;
|
|
28399
|
+
return () => ++n2;
|
|
28400
|
+
}
|
|
28401
|
+
async function copyStaged(files, deps) {
|
|
28402
|
+
return Promise.all(
|
|
28403
|
+
files.map(async (file) => {
|
|
28404
|
+
const index = deps.nextIndex();
|
|
28405
|
+
const basename4 = stagedBasename(index, file.name);
|
|
28406
|
+
const key = inboundKey(deps.threadKey, file.messageId, index, file.name);
|
|
28407
|
+
try {
|
|
28408
|
+
await deps.store.copyFromUrl({ url: file.url, size: file.size, key });
|
|
28409
|
+
deps.publish?.({
|
|
28410
|
+
type: "artifact",
|
|
28411
|
+
direction: "in",
|
|
28412
|
+
key,
|
|
28413
|
+
name: file.name,
|
|
28414
|
+
size: file.size,
|
|
28415
|
+
contentType: file.type
|
|
28416
|
+
});
|
|
28417
|
+
return { file, basename: basename4, key };
|
|
28418
|
+
} catch (err2) {
|
|
28419
|
+
return { file, basename: basename4, key, error: `the copy into the store failed: ${describe4(err2)}` };
|
|
28420
|
+
}
|
|
28421
|
+
})
|
|
28422
|
+
);
|
|
28423
|
+
}
|
|
28424
|
+
async function pullStaged(outcomes, deps) {
|
|
28425
|
+
const landed = outcomes.filter((o) => o.error === void 0);
|
|
28426
|
+
if (landed.length === 0) return [...outcomes];
|
|
28427
|
+
const timeoutMs = deps.timeoutMs ?? BASH_TIMEOUT_MAX_MS;
|
|
28428
|
+
const opts = { timeoutMs, ...deps.signal ? { signal: deps.signal } : {} };
|
|
28429
|
+
if (deps.resident) await deps.executor.exec(excludeCommand(), opts);
|
|
28430
|
+
const pulled = /* @__PURE__ */ new Map();
|
|
28431
|
+
for (const o of landed) {
|
|
28432
|
+
const url = await deps.store.presignGet(o.key);
|
|
28433
|
+
const out = await deps.executor.exec(pullCommandFor(url, o.basename), opts);
|
|
28434
|
+
pulled.set(o.key, parseExitPrefix(out).failed ? `the pull into the workspace failed: ${out.trim()}` : void 0);
|
|
28435
|
+
}
|
|
28436
|
+
return outcomes.map((o) => {
|
|
28437
|
+
const error = o.error ?? pulled.get(o.key);
|
|
28438
|
+
return error === void 0 ? { file: o.file, basename: o.basename, key: o.key } : { ...o, error };
|
|
28439
|
+
});
|
|
28440
|
+
}
|
|
28441
|
+
async function stageIntoWorkspace(files, deps) {
|
|
28442
|
+
const outcomes = await pullStaged(await copyStaged(files, deps), deps);
|
|
28443
|
+
return { line: attachmentsLine(outcomes), outcomes };
|
|
28444
|
+
}
|
|
28445
|
+
var ATTACHMENTS_DIR, describe4;
|
|
28446
|
+
var init_staging = __esm({
|
|
28447
|
+
"../../src/core/dispatch/staging.ts"() {
|
|
28448
|
+
"use strict";
|
|
28449
|
+
init_keys();
|
|
28450
|
+
init_bashTimeout();
|
|
28451
|
+
init_shellQuote();
|
|
28452
|
+
init_runEvents();
|
|
28453
|
+
ATTACHMENTS_DIR = "attachments";
|
|
28454
|
+
describe4 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
28455
|
+
}
|
|
28456
|
+
});
|
|
28457
|
+
|
|
27933
28458
|
// ../../src/core/modelProxy/runBearers.ts
|
|
27934
28459
|
import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
27935
28460
|
function token(runId3, secret2) {
|
|
@@ -28481,7 +29006,7 @@ function startMemoryRead(deps, ctx) {
|
|
|
28481
29006
|
async function openAckCard(deps, ctx) {
|
|
28482
29007
|
const { io, agent, resolved, startedAt, clock: clock3, root, trace, route: route3 } = ctx;
|
|
28483
29008
|
const shell = createCardShell({
|
|
28484
|
-
label: `*${agent.name}* on \`${resolved.modelRef}\`${route3 ? ` \xB7 ${routedLabel(route3.reason)}` : ""}`,
|
|
29009
|
+
label: `*${agent.name}* on \`${resolved.modelRef}\`${route3 ? ` \xB7 ${routedLabel(route3.reason, route3.collapsed)}` : ""}`,
|
|
28485
29010
|
startedAt,
|
|
28486
29011
|
now: clock3,
|
|
28487
29012
|
...route3?.parts ? { lead: routedPartLines(route3.parts) } : {},
|
|
@@ -28525,6 +29050,8 @@ async function registerRun(deps, ctx) {
|
|
|
28525
29050
|
admitted,
|
|
28526
29051
|
parentRunId,
|
|
28527
29052
|
coordinator,
|
|
29053
|
+
seed,
|
|
29054
|
+
seedTurns,
|
|
28528
29055
|
agentSource,
|
|
28529
29056
|
route: route3
|
|
28530
29057
|
} = ctx;
|
|
@@ -28557,7 +29084,8 @@ async function registerRun(deps, ctx) {
|
|
|
28557
29084
|
...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
|
|
28558
29085
|
...msg.userName !== void 0 ? { userName: msg.userName } : {},
|
|
28559
29086
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
28560
|
-
...coordinatorFields(coordinator)
|
|
29087
|
+
...coordinatorFields(coordinator),
|
|
29088
|
+
...seed !== void 0 ? { seed } : {}
|
|
28561
29089
|
},
|
|
28562
29090
|
// Under the run's id, at the card's start (the reservation's, or the
|
|
28563
29091
|
// carried row's) — a resume replays its events, a restart starts them
|
|
@@ -28606,7 +29134,7 @@ async function registerRun(deps, ctx) {
|
|
|
28606
29134
|
if (!resume) publishMeta(repoCtx);
|
|
28607
29135
|
if (!resume && route3) registry.publish(run2.id, { type: "route", ...route3, at: clock3() });
|
|
28608
29136
|
if (!resume && deps.config.config.runHistory?.includeContext !== false) {
|
|
28609
|
-
for (const text of contextMessageTexts(history, humanize)) publishText("context", text);
|
|
29137
|
+
for (const text of contextMessageTexts(seedTurns ?? history, humanize)) publishText("context", text);
|
|
28610
29138
|
}
|
|
28611
29139
|
return { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta };
|
|
28612
29140
|
}
|
|
@@ -28628,7 +29156,8 @@ async function reserveRun(deps, ctx) {
|
|
|
28628
29156
|
admitted,
|
|
28629
29157
|
root,
|
|
28630
29158
|
parentRunId,
|
|
28631
|
-
coordinator
|
|
29159
|
+
coordinator,
|
|
29160
|
+
seed
|
|
28632
29161
|
} = ctx;
|
|
28633
29162
|
if (!resume && !restart) {
|
|
28634
29163
|
const requestRow = durableInboxMessage(msg, msg.text, receivedAt);
|
|
@@ -28656,6 +29185,7 @@ async function reserveRun(deps, ctx) {
|
|
|
28656
29185
|
profile,
|
|
28657
29186
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
28658
29187
|
...coordinatorFields(coordinator),
|
|
29188
|
+
...seed !== void 0 ? { seed } : {},
|
|
28659
29189
|
request: requestRow
|
|
28660
29190
|
},
|
|
28661
29191
|
card: card.handle ?? null,
|
|
@@ -28885,7 +29415,8 @@ async function claimRun(deps, ctx) {
|
|
|
28885
29415
|
clock: clock3,
|
|
28886
29416
|
root,
|
|
28887
29417
|
parentRunId,
|
|
28888
|
-
coordinator
|
|
29418
|
+
coordinator,
|
|
29419
|
+
seed
|
|
28889
29420
|
} = ctx;
|
|
28890
29421
|
const { resident, binding } = selection;
|
|
28891
29422
|
let ledgerRun = ctx.ledgerRun;
|
|
@@ -28915,6 +29446,7 @@ async function claimRun(deps, ctx) {
|
|
|
28915
29446
|
profile,
|
|
28916
29447
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
28917
29448
|
...coordinatorFields(coordinator),
|
|
29449
|
+
...seed !== void 0 ? { seed } : {},
|
|
28918
29450
|
selection: resident === true ? "resident" : "sandbox",
|
|
28919
29451
|
...binding?.workspace !== void 0 ? { workspace: binding.workspace } : {},
|
|
28920
29452
|
...requestRow !== void 0 ? { request: requestRow } : {}
|
|
@@ -29113,6 +29645,157 @@ var init_container = __esm({
|
|
|
29113
29645
|
}
|
|
29114
29646
|
});
|
|
29115
29647
|
|
|
29648
|
+
// ../../src/core/harness/pi/extensionSource.ts
|
|
29649
|
+
var BLOCKED_AT_DOOR_PREFIX, BLOCKED_UNAVAILABLE_PREFIX, PI_EXTENSION_SOURCE;
|
|
29650
|
+
var init_extensionSource = __esm({
|
|
29651
|
+
"../../src/core/harness/pi/extensionSource.ts"() {
|
|
29652
|
+
"use strict";
|
|
29653
|
+
BLOCKED_AT_DOOR_PREFIX = "authorization refused at the door: ";
|
|
29654
|
+
BLOCKED_UNAVAILABLE_PREFIX = "authorization unavailable: ";
|
|
29655
|
+
PI_EXTENSION_SOURCE = `// Switchboard's pi harness extension. Written into the run's directory by the
|
|
29656
|
+
// bot before pi starts; loaded with \`-e\`. Imports nothing.
|
|
29657
|
+
|
|
29658
|
+
const BEARER_ENV = "SWITCHBOARD_RUN_BEARER";
|
|
29659
|
+
const URL_ENV = "SWITCHBOARD_HARNESS_URL";
|
|
29660
|
+
/** How long the tool_call hook keeps asking an unreachable bot before it blocks. */
|
|
29661
|
+
const AUTHORIZE_WAIT_MS = 90_000;
|
|
29662
|
+
const AUTHORIZE_RETRY_MS = 2_000;
|
|
29663
|
+
|
|
29664
|
+
function settings() {
|
|
29665
|
+
const url = process.env[URL_ENV];
|
|
29666
|
+
const bearer = process.env[BEARER_ENV];
|
|
29667
|
+
if (!url || !bearer) throw new Error("switchboard harness: " + URL_ENV + " and " + BEARER_ENV + " must be set");
|
|
29668
|
+
return { base: url.replace(/\\/$/, ""), bearer };
|
|
29669
|
+
}
|
|
29670
|
+
|
|
29671
|
+
/** The bot answered, and said no: the status rides along so a caller can tell
|
|
29672
|
+
* a verdict from a bot it could not reach. */
|
|
29673
|
+
class HarnessAnswerError extends Error {
|
|
29674
|
+
constructor(message, status) {
|
|
29675
|
+
super(message);
|
|
29676
|
+
this.status = status;
|
|
29677
|
+
}
|
|
29678
|
+
}
|
|
29679
|
+
|
|
29680
|
+
async function call(method, path, body, signal) {
|
|
29681
|
+
const { base, bearer } = settings();
|
|
29682
|
+
const res = await fetch(base + path, {
|
|
29683
|
+
method,
|
|
29684
|
+
headers: { authorization: "Bearer " + bearer, "content-type": "application/json", accept: "application/json" },
|
|
29685
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
29686
|
+
signal,
|
|
29687
|
+
});
|
|
29688
|
+
const text = await res.text();
|
|
29689
|
+
let json;
|
|
29690
|
+
try {
|
|
29691
|
+
json = JSON.parse(text);
|
|
29692
|
+
} catch {
|
|
29693
|
+
throw new Error("switchboard harness: " + method + " " + path + " answered " + res.status + " with a body that is not JSON");
|
|
29694
|
+
}
|
|
29695
|
+
if (!res.ok) {
|
|
29696
|
+
throw new HarnessAnswerError(
|
|
29697
|
+
"switchboard harness: " + method + " " + path + " answered " + res.status + ": " + (json.error || text),
|
|
29698
|
+
res.status,
|
|
29699
|
+
);
|
|
29700
|
+
}
|
|
29701
|
+
return json;
|
|
29702
|
+
}
|
|
29703
|
+
|
|
29704
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
29705
|
+
|
|
29706
|
+
/** The bot's verdict on one tool call. A verdict answers at once, and so does
|
|
29707
|
+
* a refusal at the door \u2014 a 4xx: the bearer wrong, expired or revoked, the
|
|
29708
|
+
* run not on the harness \u2014 since nothing later changes it. A bot that cannot
|
|
29709
|
+
* be reached or is failing (a network error, a 5xx, a body that is not JSON)
|
|
29710
|
+
* is asked again every two seconds until the wait is up, then the call is
|
|
29711
|
+
* blocked naming why. */
|
|
29712
|
+
async function authorize(event) {
|
|
29713
|
+
const started = Date.now();
|
|
29714
|
+
let lastError = "";
|
|
29715
|
+
for (;;) {
|
|
29716
|
+
try {
|
|
29717
|
+
return await call("POST", "/harness/authorize", {
|
|
29718
|
+
toolCallId: event.toolCallId,
|
|
29719
|
+
tool: event.toolName,
|
|
29720
|
+
input: event.input,
|
|
29721
|
+
});
|
|
29722
|
+
} catch (err) {
|
|
29723
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
29724
|
+
const status = err instanceof HarnessAnswerError ? err.status : undefined;
|
|
29725
|
+
if (status !== undefined && status >= 400 && status < 500) {
|
|
29726
|
+
return { allow: false, reason: "${BLOCKED_AT_DOOR_PREFIX}" + lastError };
|
|
29727
|
+
}
|
|
29728
|
+
if (Date.now() - started >= AUTHORIZE_WAIT_MS) {
|
|
29729
|
+
return { allow: false, reason: "${BLOCKED_UNAVAILABLE_PREFIX}the bot did not answer for 90 s (" + lastError + ")" };
|
|
29730
|
+
}
|
|
29731
|
+
await sleep(AUTHORIZE_RETRY_MS);
|
|
29732
|
+
}
|
|
29733
|
+
}
|
|
29734
|
+
}
|
|
29735
|
+
|
|
29736
|
+
function relayTool(def) {
|
|
29737
|
+
return {
|
|
29738
|
+
name: def.name,
|
|
29739
|
+
label: def.name,
|
|
29740
|
+
description: def.description,
|
|
29741
|
+
parameters: def.inputSchema,
|
|
29742
|
+
async execute(toolCallId, params, signal) {
|
|
29743
|
+
const answer = await call("POST", "/harness/tool", { toolCallId, tool: def.name, input: params }, signal);
|
|
29744
|
+
if (answer.isError) throw new Error(answer.content.map((c) => (c.type === "text" ? c.text : "")).join("\\n"));
|
|
29745
|
+
return { content: answer.content, details: {} };
|
|
29746
|
+
},
|
|
29747
|
+
};
|
|
29748
|
+
}
|
|
29749
|
+
|
|
29750
|
+
export default async function switchboardHarness(pi) {
|
|
29751
|
+
const { tools } = await call("GET", "/harness/tools");
|
|
29752
|
+
for (const def of tools) pi.registerTool(relayTool(def));
|
|
29753
|
+
pi.on("tool_call", async (event) => {
|
|
29754
|
+
const verdict = await authorize(event);
|
|
29755
|
+
if (!verdict.allow) return { block: true, reason: verdict.reason };
|
|
29756
|
+
return undefined;
|
|
29757
|
+
});
|
|
29758
|
+
}
|
|
29759
|
+
`;
|
|
29760
|
+
}
|
|
29761
|
+
});
|
|
29762
|
+
|
|
29763
|
+
// ../../src/core/harness/pi/protocol.ts
|
|
29764
|
+
function parsePiLine(line3) {
|
|
29765
|
+
try {
|
|
29766
|
+
const value = JSON.parse(line3);
|
|
29767
|
+
if (typeof value === "object" && value !== null && typeof value.type === "string") {
|
|
29768
|
+
return value;
|
|
29769
|
+
}
|
|
29770
|
+
} catch {
|
|
29771
|
+
}
|
|
29772
|
+
return void 0;
|
|
29773
|
+
}
|
|
29774
|
+
function piAnsweredWithoutRunning(event) {
|
|
29775
|
+
if (event.type !== "tool_execution_end" || event.isError !== true) return void 0;
|
|
29776
|
+
const tool = typeof event.toolName === "string" ? event.toolName : "";
|
|
29777
|
+
const result = event.result;
|
|
29778
|
+
const content = Array.isArray(result?.content) ? result.content : [];
|
|
29779
|
+
const text = content.filter(
|
|
29780
|
+
(p) => typeof p === "object" && p !== null && p.type === "text" && typeof p.text === "string"
|
|
29781
|
+
).map((p) => p.text).join("\n");
|
|
29782
|
+
const validation = `Validation failed for tool "${tool}":
|
|
29783
|
+
`;
|
|
29784
|
+
if (text.startsWith(validation)) {
|
|
29785
|
+
const problems = text.slice(validation.length).split("\n\n")[0].split("\n").map((line3) => line3.replace(/^\s*-\s*/, "").trim()).filter((line3) => line3.length > 0);
|
|
29786
|
+
return `its arguments failed pi's validation (${problems.join("; ")})`;
|
|
29787
|
+
}
|
|
29788
|
+
if (text.startsWith(`Tool call "${tool}" was not executed: the response hit the output token limit`))
|
|
29789
|
+
return "its arguments were cut by the output token limit";
|
|
29790
|
+
if (text === `Tool ${tool} not found`) return "the tool is not on pi's list";
|
|
29791
|
+
return void 0;
|
|
29792
|
+
}
|
|
29793
|
+
var init_protocol = __esm({
|
|
29794
|
+
"../../src/core/harness/pi/protocol.ts"() {
|
|
29795
|
+
"use strict";
|
|
29796
|
+
}
|
|
29797
|
+
});
|
|
29798
|
+
|
|
29116
29799
|
// ../../src/core/harness/pi/bridge.ts
|
|
29117
29800
|
function describePiToolCall(tool, args) {
|
|
29118
29801
|
const input = isRecord3(args) ? args : void 0;
|
|
@@ -29140,6 +29823,8 @@ var init_bridge2 = __esm({
|
|
|
29140
29823
|
"use strict";
|
|
29141
29824
|
init_formatDuration();
|
|
29142
29825
|
init_runEvents();
|
|
29826
|
+
init_extensionSource();
|
|
29827
|
+
init_protocol();
|
|
29143
29828
|
PI_EVENT_DISPOSITION = {
|
|
29144
29829
|
response: "structure",
|
|
29145
29830
|
agent_start: "structure",
|
|
@@ -29182,8 +29867,23 @@ var init_bridge2 = __esm({
|
|
|
29182
29867
|
/** Text beside a bookkeeping-only call, held until the next message decides (run-loop item 15). */
|
|
29183
29868
|
heldAnswer;
|
|
29184
29869
|
assistantStartedAt;
|
|
29870
|
+
/** The calls under way: their span, their tool, and whether their end is
|
|
29871
|
+
* judged against the gate (harness-pi item 7). */
|
|
29185
29872
|
openTools = /* @__PURE__ */ new Map();
|
|
29873
|
+
/** The calls the gate saw — the extension asked `/harness/authorize` for
|
|
29874
|
+
* them (`gateSaw`) — not yet ended. */
|
|
29875
|
+
vetted = /* @__PURE__ */ new Set();
|
|
29876
|
+
/** Whether a call starting now is judged against the gate when it ends.
|
|
29877
|
+
* The harness turns it off while catching up on a re-attach: those calls
|
|
29878
|
+
* were vetted by the bot generation that died, and this one never heard. */
|
|
29879
|
+
judgeGate = true;
|
|
29186
29880
|
summarizationRetries = 0;
|
|
29881
|
+
/** The gate saw this call: the extension's `tool_call` hook asked the bot
|
|
29882
|
+
* for it, whatever the answer. Called from the authorize route, before pi
|
|
29883
|
+
* runs the tool — so it always precedes the call's `tool_execution_end`. */
|
|
29884
|
+
gateSaw(callId) {
|
|
29885
|
+
this.vetted.add(callId);
|
|
29886
|
+
}
|
|
29187
29887
|
/** The run's answer as it stands: the last text-only assistant message, else a held bookkeeping text. */
|
|
29188
29888
|
answer() {
|
|
29189
29889
|
return this.answerText ?? this.heldAnswer;
|
|
@@ -29222,10 +29922,10 @@ var init_bridge2 = __esm({
|
|
|
29222
29922
|
this.onToolStart(event);
|
|
29223
29923
|
break;
|
|
29224
29924
|
case "tool_execution_end":
|
|
29225
|
-
this.onToolEnd(event);
|
|
29925
|
+
this.onToolEnd(event, out);
|
|
29226
29926
|
break;
|
|
29227
29927
|
case "compaction_end":
|
|
29228
|
-
this.onCompactionEnd(event);
|
|
29928
|
+
this.onCompactionEnd(event, out);
|
|
29229
29929
|
break;
|
|
29230
29930
|
case "summarization_retry_scheduled":
|
|
29231
29931
|
this.summarizationRetries++;
|
|
@@ -29285,7 +29985,7 @@ var init_bridge2 = __esm({
|
|
|
29285
29985
|
const tool = str5(event.toolName);
|
|
29286
29986
|
const callId = str5(event.toolCallId);
|
|
29287
29987
|
const span = this.deps.agentSpan?.start(`tool.${tool}`);
|
|
29288
|
-
this.openTools.set(callId, { span, tool });
|
|
29988
|
+
this.openTools.set(callId, { span, tool, judged: this.judgeGate });
|
|
29289
29989
|
this.toolCalls++;
|
|
29290
29990
|
const input = isRecord3(event.args) ? event.args : void 0;
|
|
29291
29991
|
const command2 = tool === "bash" && typeof input?.command === "string" ? { command: redactAndCap(input.command, COMMAND_CAP) } : {};
|
|
@@ -29298,7 +29998,7 @@ var init_bridge2 = __esm({
|
|
|
29298
29998
|
...span ? { spanId: span.id } : {}
|
|
29299
29999
|
});
|
|
29300
30000
|
}
|
|
29301
|
-
onToolEnd(event) {
|
|
30001
|
+
onToolEnd(event, out) {
|
|
29302
30002
|
const callId = str5(event.toolCallId);
|
|
29303
30003
|
const open = this.openTools.get(callId);
|
|
29304
30004
|
this.openTools.delete(callId);
|
|
@@ -29321,8 +30021,25 @@ var init_bridge2 = __esm({
|
|
|
29321
30021
|
ok: ok2,
|
|
29322
30022
|
...exit.exitCode !== void 0 ? { exitCode: exit.exitCode } : {}
|
|
29323
30023
|
});
|
|
30024
|
+
const seen = this.vetted.delete(callId);
|
|
30025
|
+
if (!seen && open?.judged) {
|
|
30026
|
+
const rejection = piAnsweredWithoutRunning(event);
|
|
30027
|
+
if (rejection !== void 0) {
|
|
30028
|
+
this.note(
|
|
30029
|
+
"harness_error",
|
|
30030
|
+
`pi answered the ${tool} call ${callId} itself, before the gate: ${redactAndCap(rejection, 300)}; nothing ran`
|
|
30031
|
+
);
|
|
30032
|
+
} else if (isError && (text.startsWith(BLOCKED_AT_DOOR_PREFIX) || text.startsWith(BLOCKED_UNAVAILABLE_PREFIX))) {
|
|
30033
|
+
this.note(
|
|
30034
|
+
"harness_error",
|
|
30035
|
+
`the extension blocked the ${tool} call ${callId} without the gate's verdict: ${redactAndCap(text, 300)}; nothing ran`
|
|
30036
|
+
);
|
|
30037
|
+
} else {
|
|
30038
|
+
out.gateBypassed = { callId, tool };
|
|
30039
|
+
}
|
|
30040
|
+
}
|
|
29324
30041
|
}
|
|
29325
|
-
onCompactionEnd(event) {
|
|
30042
|
+
onCompactionEnd(event, out) {
|
|
29326
30043
|
const result = isRecord3(event.result) ? event.result : void 0;
|
|
29327
30044
|
if (event.aborted === true || !result) {
|
|
29328
30045
|
this.note(
|
|
@@ -29339,6 +30056,13 @@ var init_bridge2 = __esm({
|
|
|
29339
30056
|
"compacted",
|
|
29340
30057
|
`pi compacted the context (${str5(event.reason)}): ${before ?? "?"} \u2192 about ${after ?? "?"} tokens; the transcript keeps the originals${retries > 0 ? `; ${retries} summarization retr${retries === 1 ? "y" : "ies"}` : ""}`
|
|
29341
30058
|
);
|
|
30059
|
+
if (typeof result.summary === "string") {
|
|
30060
|
+
out.compaction = {
|
|
30061
|
+
summary: result.summary,
|
|
30062
|
+
...before !== void 0 ? { tokensBefore: before } : {},
|
|
30063
|
+
...typeof result.firstKeptEntryId === "string" ? { firstKeptEntryId: result.firstKeptEntryId } : {}
|
|
30064
|
+
};
|
|
30065
|
+
}
|
|
29342
30066
|
}
|
|
29343
30067
|
onUiRequest(event, out) {
|
|
29344
30068
|
const method = str5(event.method);
|
|
@@ -29360,6 +30084,7 @@ var init_bridge2 = __esm({
|
|
|
29360
30084
|
this.emit({ type: "tool_result", tool: open.tool, ok: false, callId, summary: redactAndCap(reason) });
|
|
29361
30085
|
}
|
|
29362
30086
|
this.openTools.clear();
|
|
30087
|
+
this.vetted.clear();
|
|
29363
30088
|
}
|
|
29364
30089
|
};
|
|
29365
30090
|
}
|
|
@@ -29406,7 +30131,7 @@ function chatMessageOf(message2) {
|
|
|
29406
30131
|
return void 0;
|
|
29407
30132
|
}
|
|
29408
30133
|
}
|
|
29409
|
-
function piSessionFile(messages, opts) {
|
|
30134
|
+
function piSessionFile(messages, opts, compactions = []) {
|
|
29410
30135
|
const lines = [];
|
|
29411
30136
|
const stamp = new Date(opts.at).toISOString();
|
|
29412
30137
|
lines.push(
|
|
@@ -29415,12 +30140,35 @@ function piSessionFile(messages, opts) {
|
|
|
29415
30140
|
let parentId = null;
|
|
29416
30141
|
let n2 = 0;
|
|
29417
30142
|
const toolNames = /* @__PURE__ */ new Map();
|
|
30143
|
+
const firstEntryOf = [];
|
|
30144
|
+
const nextId = () => (n2++).toString(16).padStart(8, "0");
|
|
29418
30145
|
const push = (message2) => {
|
|
29419
|
-
const id = (
|
|
30146
|
+
const id = nextId();
|
|
29420
30147
|
lines.push(JSON.stringify({ type: "message", id, parentId, timestamp: stamp, message: message2 }));
|
|
29421
30148
|
parentId = id;
|
|
30149
|
+
return id;
|
|
29422
30150
|
};
|
|
29423
|
-
|
|
30151
|
+
const pushCompaction = (c) => {
|
|
30152
|
+
const id = nextId();
|
|
30153
|
+
const kept = c.keptBefore !== void 0 ? firstEntryOf[c.keptBefore] : void 0;
|
|
30154
|
+
lines.push(
|
|
30155
|
+
JSON.stringify({
|
|
30156
|
+
type: "compaction",
|
|
30157
|
+
id,
|
|
30158
|
+
parentId,
|
|
30159
|
+
timestamp: stamp,
|
|
30160
|
+
summary: c.entry.summary,
|
|
30161
|
+
firstKeptEntryId: kept ?? c.entry.firstKeptEntryId ?? "",
|
|
30162
|
+
tokensBefore: c.entry.tokensBefore ?? 0
|
|
30163
|
+
})
|
|
30164
|
+
);
|
|
30165
|
+
parentId = id;
|
|
30166
|
+
};
|
|
30167
|
+
const byPosition = /* @__PURE__ */ new Map();
|
|
30168
|
+
for (const c of compactions) byPosition.set(c.before, [...byPosition.get(c.before) ?? [], c]);
|
|
30169
|
+
messages.forEach((message2, i) => {
|
|
30170
|
+
for (const c of byPosition.get(i) ?? []) pushCompaction(c);
|
|
30171
|
+
firstEntryOf[i] = n2.toString(16).padStart(8, "0");
|
|
29424
30172
|
if (message2.role === "assistant") {
|
|
29425
30173
|
const content = [];
|
|
29426
30174
|
for (const p of message2.content) {
|
|
@@ -29448,7 +30196,7 @@ function piSessionFile(messages, opts) {
|
|
|
29448
30196
|
stopReason: hasTool ? "toolUse" : "stop",
|
|
29449
30197
|
timestamp: opts.at
|
|
29450
30198
|
});
|
|
29451
|
-
|
|
30199
|
+
return;
|
|
29452
30200
|
}
|
|
29453
30201
|
const results = message2.content.filter(
|
|
29454
30202
|
(p) => p.type === "tool_result"
|
|
@@ -29475,7 +30223,8 @@ function piSessionFile(messages, opts) {
|
|
|
29475
30223
|
});
|
|
29476
30224
|
}
|
|
29477
30225
|
if (rest.length > 0) push({ role: "user", content: rest, timestamp: opts.at });
|
|
29478
|
-
}
|
|
30226
|
+
});
|
|
30227
|
+
for (const c of byPosition.get(messages.length) ?? []) pushCompaction(c);
|
|
29479
30228
|
return lines.join("\n") + "\n";
|
|
29480
30229
|
}
|
|
29481
30230
|
function sessionIdOf(at) {
|
|
@@ -29532,124 +30281,36 @@ var init_mirror = __esm({
|
|
|
29532
30281
|
this.idx += turns.length;
|
|
29533
30282
|
await this.deps.onStep(report2);
|
|
29534
30283
|
}
|
|
29535
|
-
|
|
29536
|
-
|
|
29537
|
-
|
|
29538
|
-
|
|
29539
|
-
|
|
29540
|
-
|
|
29541
|
-
|
|
29542
|
-
|
|
29543
|
-
|
|
29544
|
-
|
|
29545
|
-
|
|
29546
|
-
|
|
29547
|
-
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
|
|
29555
|
-
|
|
29556
|
-
if (!url || !bearer) throw new Error("switchboard harness: " + URL_ENV + " and " + BEARER_ENV + " must be set");
|
|
29557
|
-
return { base: url.replace(/\\/$/, ""), bearer };
|
|
29558
|
-
}
|
|
29559
|
-
|
|
29560
|
-
/** The bot answered, and said no: the status rides along so a caller can tell
|
|
29561
|
-
* a verdict from a bot it could not reach. */
|
|
29562
|
-
class HarnessAnswerError extends Error {
|
|
29563
|
-
constructor(message, status) {
|
|
29564
|
-
super(message);
|
|
29565
|
-
this.status = status;
|
|
29566
|
-
}
|
|
29567
|
-
}
|
|
29568
|
-
|
|
29569
|
-
async function call(method, path, body, signal) {
|
|
29570
|
-
const { base, bearer } = settings();
|
|
29571
|
-
const res = await fetch(base + path, {
|
|
29572
|
-
method,
|
|
29573
|
-
headers: { authorization: "Bearer " + bearer, "content-type": "application/json", accept: "application/json" },
|
|
29574
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
29575
|
-
signal,
|
|
29576
|
-
});
|
|
29577
|
-
const text = await res.text();
|
|
29578
|
-
let json;
|
|
29579
|
-
try {
|
|
29580
|
-
json = JSON.parse(text);
|
|
29581
|
-
} catch {
|
|
29582
|
-
throw new Error("switchboard harness: " + method + " " + path + " answered " + res.status + " with a body that is not JSON");
|
|
29583
|
-
}
|
|
29584
|
-
if (!res.ok) {
|
|
29585
|
-
throw new HarnessAnswerError(
|
|
29586
|
-
"switchboard harness: " + method + " " + path + " answered " + res.status + ": " + (json.error || text),
|
|
29587
|
-
res.status,
|
|
29588
|
-
);
|
|
29589
|
-
}
|
|
29590
|
-
return json;
|
|
29591
|
-
}
|
|
29592
|
-
|
|
29593
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
29594
|
-
|
|
29595
|
-
/** The bot's verdict on one tool call. A verdict answers at once, and so does
|
|
29596
|
-
* a refusal at the door \u2014 a 4xx: the bearer wrong, expired or revoked, the
|
|
29597
|
-
* run not on the harness \u2014 since nothing later changes it. A bot that cannot
|
|
29598
|
-
* be reached or is failing (a network error, a 5xx, a body that is not JSON)
|
|
29599
|
-
* is asked again every two seconds until the wait is up, then the call is
|
|
29600
|
-
* blocked naming why. */
|
|
29601
|
-
async function authorize(event) {
|
|
29602
|
-
const started = Date.now();
|
|
29603
|
-
let lastError = "";
|
|
29604
|
-
for (;;) {
|
|
29605
|
-
try {
|
|
29606
|
-
return await call("POST", "/harness/authorize", {
|
|
29607
|
-
toolCallId: event.toolCallId,
|
|
29608
|
-
tool: event.toolName,
|
|
29609
|
-
input: event.input,
|
|
29610
|
-
});
|
|
29611
|
-
} catch (err) {
|
|
29612
|
-
lastError = err instanceof Error ? err.message : String(err);
|
|
29613
|
-
const status = err instanceof HarnessAnswerError ? err.status : undefined;
|
|
29614
|
-
if (status !== undefined && status >= 400 && status < 500) {
|
|
29615
|
-
return { allow: false, reason: "authorization refused at the door: " + lastError };
|
|
29616
|
-
}
|
|
29617
|
-
if (Date.now() - started >= AUTHORIZE_WAIT_MS) {
|
|
29618
|
-
return { allow: false, reason: "authorization unavailable: the bot did not answer for 90 s (" + lastError + ")" };
|
|
30284
|
+
/** pi compacted (session-log item 6): the results pending since the last
|
|
30285
|
+
* assistant turn go as the user turn they would have been, the entry as the
|
|
30286
|
+
* row after them — its own step with nothing in flight, so the log holds
|
|
30287
|
+
* the summary where pi wrote it and the index moves past it. */
|
|
30288
|
+
async onCompaction(entry, turn) {
|
|
30289
|
+
if (!this.deps.onStep) return;
|
|
30290
|
+
const turns = [];
|
|
30291
|
+
if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
|
|
30292
|
+
this.pendingUser = [];
|
|
30293
|
+
const report2 = {
|
|
30294
|
+
turns,
|
|
30295
|
+
firstIdx: this.idx,
|
|
30296
|
+
compaction: entry,
|
|
30297
|
+
inFlight: [],
|
|
30298
|
+
turn,
|
|
30299
|
+
iteration: this.iteration,
|
|
30300
|
+
remainingMs: Math.max(0, this.deps.remainingMs()),
|
|
30301
|
+
inboxConsumedSeq: this.inboxConsumedSeq
|
|
30302
|
+
};
|
|
30303
|
+
this.idx += turns.length + 1;
|
|
30304
|
+
await this.deps.onStep(report2);
|
|
29619
30305
|
}
|
|
29620
|
-
|
|
29621
|
-
}
|
|
29622
|
-
}
|
|
29623
|
-
}
|
|
29624
|
-
|
|
29625
|
-
function relayTool(def) {
|
|
29626
|
-
return {
|
|
29627
|
-
name: def.name,
|
|
29628
|
-
label: def.name,
|
|
29629
|
-
description: def.description,
|
|
29630
|
-
parameters: def.inputSchema,
|
|
29631
|
-
async execute(toolCallId, params, signal) {
|
|
29632
|
-
const answer = await call("POST", "/harness/tool", { toolCallId, tool: def.name, input: params }, signal);
|
|
29633
|
-
if (answer.isError) throw new Error(answer.content.map((c) => (c.type === "text" ? c.text : "")).join("\\n"));
|
|
29634
|
-
return { content: answer.content, details: {} };
|
|
29635
|
-
},
|
|
29636
|
-
};
|
|
29637
|
-
}
|
|
29638
|
-
|
|
29639
|
-
export default async function switchboardHarness(pi) {
|
|
29640
|
-
const { tools } = await call("GET", "/harness/tools");
|
|
29641
|
-
for (const def of tools) pi.registerTool(relayTool(def));
|
|
29642
|
-
pi.on("tool_call", async (event) => {
|
|
29643
|
-
const verdict = await authorize(event);
|
|
29644
|
-
if (!verdict.allow) return { block: true, reason: verdict.reason };
|
|
29645
|
-
return undefined;
|
|
29646
|
-
});
|
|
29647
|
-
}
|
|
29648
|
-
`;
|
|
30306
|
+
};
|
|
29649
30307
|
}
|
|
29650
30308
|
});
|
|
29651
30309
|
|
|
29652
30310
|
// ../../src/core/harness/pi/process.ts
|
|
30311
|
+
function piBuiltinToolsFor(identity2) {
|
|
30312
|
+
return identity2 === "write" ? PI_BUILTIN_TOOLS : PI_READ_TOOLS;
|
|
30313
|
+
}
|
|
29653
30314
|
function piThinkingLevel(effort2) {
|
|
29654
30315
|
return effort2;
|
|
29655
30316
|
}
|
|
@@ -29679,7 +30340,7 @@ function piLaunchArgs(spec) {
|
|
|
29679
30340
|
"-e",
|
|
29680
30341
|
spec.paths.extension,
|
|
29681
30342
|
"--tools",
|
|
29682
|
-
[...
|
|
30343
|
+
[...piBuiltinToolsFor(spec.identity), ...spec.relayTools].join(","),
|
|
29683
30344
|
"--provider",
|
|
29684
30345
|
PROXY_PROVIDER,
|
|
29685
30346
|
"--model",
|
|
@@ -29698,6 +30359,13 @@ function piLaunchEnv(spec, bearer) {
|
|
|
29698
30359
|
PI_TELEMETRY: "0"
|
|
29699
30360
|
};
|
|
29700
30361
|
}
|
|
30362
|
+
function takesAdaptiveThinking(modelId) {
|
|
30363
|
+
const version2 = /(?:^|-)(\d{1,2})(?:-(\d{1,2}))?(?=-|$)/.exec(modelId);
|
|
30364
|
+
if (!version2) return true;
|
|
30365
|
+
const major = Number(version2[1]);
|
|
30366
|
+
const minor = version2[2] === void 0 ? 0 : Number(version2[2]);
|
|
30367
|
+
return major > 4 || major === 4 && minor >= 6;
|
|
30368
|
+
}
|
|
29701
30369
|
function piModelsJson(spec) {
|
|
29702
30370
|
const anthropic = spec.model.providerType === "anthropic";
|
|
29703
30371
|
const base = spec.harnessUrl.replace(/\/$/, "");
|
|
@@ -29713,6 +30381,7 @@ function piModelsJson(spec) {
|
|
|
29713
30381
|
id: spec.model.id,
|
|
29714
30382
|
name: spec.model.id,
|
|
29715
30383
|
reasoning: true,
|
|
30384
|
+
...anthropic ? { compat: { forceAdaptiveThinking: takesAdaptiveThinking(spec.model.id) } } : {},
|
|
29716
30385
|
input: ["text", "image"],
|
|
29717
30386
|
contextWindow: 2e5,
|
|
29718
30387
|
maxTokens: spec.model.maxTokens,
|
|
@@ -29726,17 +30395,18 @@ function piModelsJson(spec) {
|
|
|
29726
30395
|
2
|
|
29727
30396
|
) + "\n";
|
|
29728
30397
|
}
|
|
29729
|
-
function harnessPromptNote(relayTools) {
|
|
30398
|
+
function harnessPromptNote(relayTools, identity2) {
|
|
30399
|
+
const write = identity2 === "write";
|
|
29730
30400
|
return [
|
|
29731
|
-
"HARNESS NOTE: this run's workspace tools are pi's own \u2014 `read`, `bash`, `edit`, `write`, `grep`, `find` and `ls`, all running in the worktree.",
|
|
29732
|
-
"Where these instructions say `read_file` use `read`; where they say `write_file` use `write` (a whole file) or `edit` (a targeted change).",
|
|
30401
|
+
write ? "HARNESS NOTE: this run's workspace tools are pi's own \u2014 `read`, `bash`, `edit`, `write`, `grep`, `find` and `ls`, all running in the worktree." : "HARNESS NOTE: this run's workspace tools are pi's own \u2014 `read`, `bash`, `grep`, `find` and `ls`, all running in the worktree; it has no `edit` and no `write`: the run is read-only, and a call to either is refused.",
|
|
30402
|
+
write ? "Where these instructions say `read_file` use `read`; where they say `write_file` use `write` (a whole file) or `edit` (a targeted change)." : "Where these instructions say `read_file` use `read`.",
|
|
29733
30403
|
relayTools.length > 0 ? `Every other tool named above is available under its own name: ${relayTools.map((t) => `\`${t}\``).join(", ")}.` : "No other tools are available in this run."
|
|
29734
30404
|
].join(" ");
|
|
29735
30405
|
}
|
|
29736
30406
|
function piSystemPrompt(spec) {
|
|
29737
30407
|
return `${spec.system.trimEnd()}
|
|
29738
30408
|
|
|
29739
|
-
${harnessPromptNote(spec.relayTools)}
|
|
30409
|
+
${harnessPromptNote(spec.relayTools, spec.identity)}
|
|
29740
30410
|
`;
|
|
29741
30411
|
}
|
|
29742
30412
|
function piSettingsJson() {
|
|
@@ -29750,7 +30420,7 @@ function piLaunchFiles(spec) {
|
|
|
29750
30420
|
{ path: spec.paths.extension, content: PI_EXTENSION_SOURCE }
|
|
29751
30421
|
];
|
|
29752
30422
|
}
|
|
29753
|
-
var RUN_BEARER_ENV, HARNESS_URL_ENV, PROXY_PROVIDER, PI_BUILTIN_TOOLS;
|
|
30423
|
+
var RUN_BEARER_ENV, HARNESS_URL_ENV, PROXY_PROVIDER, PI_BUILTIN_TOOLS, PI_READ_TOOLS;
|
|
29754
30424
|
var init_process = __esm({
|
|
29755
30425
|
"../../src/core/harness/pi/process.ts"() {
|
|
29756
30426
|
"use strict";
|
|
@@ -29759,23 +30429,7 @@ var init_process = __esm({
|
|
|
29759
30429
|
HARNESS_URL_ENV = "SWITCHBOARD_HARNESS_URL";
|
|
29760
30430
|
PROXY_PROVIDER = "switchboard";
|
|
29761
30431
|
PI_BUILTIN_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
29762
|
-
|
|
29763
|
-
});
|
|
29764
|
-
|
|
29765
|
-
// ../../src/core/harness/pi/protocol.ts
|
|
29766
|
-
function parsePiLine(line3) {
|
|
29767
|
-
try {
|
|
29768
|
-
const value = JSON.parse(line3);
|
|
29769
|
-
if (typeof value === "object" && value !== null && typeof value.type === "string") {
|
|
29770
|
-
return value;
|
|
29771
|
-
}
|
|
29772
|
-
} catch {
|
|
29773
|
-
}
|
|
29774
|
-
return void 0;
|
|
29775
|
-
}
|
|
29776
|
-
var init_protocol = __esm({
|
|
29777
|
-
"../../src/core/harness/pi/protocol.ts"() {
|
|
29778
|
-
"use strict";
|
|
30432
|
+
PI_READ_TOOLS = ["read", "bash", "grep", "find", "ls"];
|
|
29779
30433
|
}
|
|
29780
30434
|
});
|
|
29781
30435
|
|
|
@@ -29923,6 +30577,7 @@ async function runPiHarness(deps, run2) {
|
|
|
29923
30577
|
let writeUp;
|
|
29924
30578
|
let writeUpAt;
|
|
29925
30579
|
let hardStopped = false;
|
|
30580
|
+
let bypass;
|
|
29926
30581
|
const toolsBlocked = () => {
|
|
29927
30582
|
if (!writeUp) return void 0;
|
|
29928
30583
|
if (writeUp.kind === "time")
|
|
@@ -29931,14 +30586,16 @@ async function runPiHarness(deps, run2) {
|
|
|
29931
30586
|
return "the run has hit its turn guard: no more tool calls \u2014 write your final answer now";
|
|
29932
30587
|
return "an operator asked this run to stop: no more tool calls \u2014 write your final answer now";
|
|
29933
30588
|
};
|
|
30589
|
+
const rules = { ...run2.rules, identity: run2.agent.identity };
|
|
29934
30590
|
const live = {
|
|
29935
30591
|
runId: run2.runId,
|
|
29936
30592
|
tools: run2.tools,
|
|
29937
30593
|
toolContext: run2.toolContext,
|
|
29938
30594
|
...run2.backend ? { backend: run2.backend } : {},
|
|
29939
|
-
rules
|
|
30595
|
+
rules,
|
|
29940
30596
|
emit,
|
|
29941
30597
|
toolSpan: (callId) => bridge.openSpan(callId),
|
|
30598
|
+
gateSaw: (callId) => bridge.gateSaw(callId),
|
|
29942
30599
|
toolsBlocked
|
|
29943
30600
|
};
|
|
29944
30601
|
const forget = deps.registry.register(live);
|
|
@@ -29948,6 +30605,7 @@ async function runPiHarness(deps, run2) {
|
|
|
29948
30605
|
model: { id: run2.model.id, providerType: run2.model.providerType, maxTokens: run2.agent.maxTokens },
|
|
29949
30606
|
harnessUrl: deps.harnessUrl,
|
|
29950
30607
|
...run2.effort ? { effort: run2.effort } : {},
|
|
30608
|
+
identity: run2.agent.identity,
|
|
29951
30609
|
system: run2.system,
|
|
29952
30610
|
relayTools: run2.tools.map((t) => t.name)
|
|
29953
30611
|
};
|
|
@@ -29983,25 +30641,33 @@ async function runPiHarness(deps, run2) {
|
|
|
29983
30641
|
let session;
|
|
29984
30642
|
if (run2.resume) {
|
|
29985
30643
|
const settled2 = settlementResults(run2.resume.settlements);
|
|
29986
|
-
session = {
|
|
30644
|
+
session = {
|
|
30645
|
+
stem: "resumed",
|
|
30646
|
+
messages: settled2 ? [...run2.resume.messages, settled2] : run2.resume.messages,
|
|
30647
|
+
compactions: run2.resume.compactions ?? []
|
|
30648
|
+
};
|
|
29987
30649
|
} else {
|
|
29988
30650
|
const earlier2 = splitSeed(run2.messages).session;
|
|
29989
|
-
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2 };
|
|
30651
|
+
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2, compactions: [] };
|
|
29990
30652
|
}
|
|
29991
30653
|
let sessionPath;
|
|
29992
30654
|
if (session) {
|
|
29993
30655
|
sessionPath = `${paths.sessionDir}/${session.stem}-${now()}.jsonl`;
|
|
29994
30656
|
await container.writeFile(
|
|
29995
30657
|
sessionPath,
|
|
29996
|
-
piSessionFile(
|
|
29997
|
-
|
|
29998
|
-
|
|
29999
|
-
|
|
30000
|
-
|
|
30001
|
-
|
|
30658
|
+
piSessionFile(
|
|
30659
|
+
session.messages,
|
|
30660
|
+
{
|
|
30661
|
+
cwd: run2.rules.checkout,
|
|
30662
|
+
model: {
|
|
30663
|
+
provider: run2.model.provider,
|
|
30664
|
+
id: run2.model.id,
|
|
30665
|
+
api: run2.model.providerType === "anthropic" ? "anthropic-messages" : "openai-completions"
|
|
30666
|
+
},
|
|
30667
|
+
at: now()
|
|
30002
30668
|
},
|
|
30003
|
-
|
|
30004
|
-
|
|
30669
|
+
session.compactions
|
|
30670
|
+
)
|
|
30005
30671
|
);
|
|
30006
30672
|
}
|
|
30007
30673
|
if (run2.resume) {
|
|
@@ -30108,8 +30774,15 @@ async function runPiHarness(deps, run2) {
|
|
|
30108
30774
|
if (next.done) break;
|
|
30109
30775
|
const event = parsePiLine(next.value);
|
|
30110
30776
|
if (!event) continue;
|
|
30777
|
+
bridge.judgeGate = !catchingUp;
|
|
30111
30778
|
const obs = bridge.observe(event);
|
|
30112
30779
|
for (const reply of obs.replies) transport.send(reply);
|
|
30780
|
+
if (obs.gateBypassed) {
|
|
30781
|
+
bypass = new GateBypassed(obs.gateBypassed.tool, obs.gateBypassed.callId);
|
|
30782
|
+
note("harness_error", `${bypass.message} \u2014 the run is stopped`);
|
|
30783
|
+
transport.send({ type: "abort" });
|
|
30784
|
+
break;
|
|
30785
|
+
}
|
|
30113
30786
|
if (obs.response) {
|
|
30114
30787
|
const r = obs.response;
|
|
30115
30788
|
if (r.id === "state" && r.success === true && typeof r.data?.sessionFile === "string") {
|
|
@@ -30123,6 +30796,7 @@ async function runPiHarness(deps, run2) {
|
|
|
30123
30796
|
}
|
|
30124
30797
|
}
|
|
30125
30798
|
if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
|
|
30799
|
+
if (obs.compaction) await mirror.onCompaction(obs.compaction, bridge.turns);
|
|
30126
30800
|
if (obs.providerError !== void 0) {
|
|
30127
30801
|
if (catchingUp)
|
|
30128
30802
|
note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
|
|
@@ -30140,6 +30814,7 @@ async function runPiHarness(deps, run2) {
|
|
|
30140
30814
|
note("stopped", hardStopNote(), "hard");
|
|
30141
30815
|
return HARD_STOP_MESSAGE;
|
|
30142
30816
|
}
|
|
30817
|
+
if (bypass) throw bypass;
|
|
30143
30818
|
if (!settled) {
|
|
30144
30819
|
const tail = await container.tail(paths.errLog, 2e3);
|
|
30145
30820
|
throw new Error(`pi exited before the run settled${tail.trim() ? `: ${redactAndCap(tail.trim(), 400)}` : ""}`);
|
|
@@ -30152,15 +30827,17 @@ async function runPiHarness(deps, run2) {
|
|
|
30152
30827
|
return text || "_(no response)_";
|
|
30153
30828
|
} finally {
|
|
30154
30829
|
transport?.close();
|
|
30155
|
-
bridge.closeOpenSpans(
|
|
30830
|
+
bridge.closeOpenSpans(
|
|
30831
|
+
hardStopped ? "the run was hard-stopped" : bypass ? "the run was stopped: a tool call bypassed the gate" : "the run ended"
|
|
30832
|
+
);
|
|
30156
30833
|
save();
|
|
30157
30834
|
forget();
|
|
30158
30835
|
if (pid !== void 0) await container.kill(pid).catch(() => {
|
|
30159
30836
|
});
|
|
30160
|
-
agentSpan?.end(hardStopped ? "error" : "ok");
|
|
30837
|
+
agentSpan?.end(hardStopped || bypass ? "error" : "ok");
|
|
30161
30838
|
}
|
|
30162
30839
|
}
|
|
30163
|
-
var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CONTINUE_PROMPT, PromptRefused;
|
|
30840
|
+
var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CONTINUE_PROMPT, PromptRefused, GateBypassed;
|
|
30164
30841
|
var init_harness = __esm({
|
|
30165
30842
|
"../../src/core/harness/pi/harness.ts"() {
|
|
30166
30843
|
"use strict";
|
|
@@ -30181,6 +30858,12 @@ var init_harness = __esm({
|
|
|
30181
30858
|
this.name = "PromptRefused";
|
|
30182
30859
|
}
|
|
30183
30860
|
};
|
|
30861
|
+
GateBypassed = class extends Error {
|
|
30862
|
+
constructor(tool, callId) {
|
|
30863
|
+
super(`the gate was bypassed: pi ran ${tool} (call ${callId}) without asking the bot`);
|
|
30864
|
+
this.name = "GateBypassed";
|
|
30865
|
+
}
|
|
30866
|
+
};
|
|
30184
30867
|
}
|
|
30185
30868
|
});
|
|
30186
30869
|
|
|
@@ -30648,8 +31331,8 @@ var init_descriptionTurn = __esm({
|
|
|
30648
31331
|
// ../../src/core/statusCardLabel.ts
|
|
30649
31332
|
function quietSuffix(quietMs, inFlightTool) {
|
|
30650
31333
|
if (quietMs <= QUIET_SUFFIX_AFTER_MS) return "";
|
|
30651
|
-
const
|
|
30652
|
-
return inFlightTool ? ` \u2014 running ${inFlightTool} (${
|
|
31334
|
+
const secs2 = Math.round(quietMs / 1e3);
|
|
31335
|
+
return inFlightTool ? ` \u2014 running ${inFlightTool} (${secs2}s)` : ` \u2014 thinking (${secs2}s since last tool)`;
|
|
30653
31336
|
}
|
|
30654
31337
|
function inFlightToolAfter(current, e) {
|
|
30655
31338
|
if (e.type === "tool_call") return e.tool;
|
|
@@ -30701,7 +31384,7 @@ async function runLoop2(deps, ctx) {
|
|
|
30701
31384
|
wait,
|
|
30702
31385
|
parentRunId,
|
|
30703
31386
|
coordinator,
|
|
30704
|
-
|
|
31387
|
+
seed
|
|
30705
31388
|
} = ctx;
|
|
30706
31389
|
const agent = budgetedAgent(ctx.agent, profile);
|
|
30707
31390
|
const { executor, binding } = round2.selection;
|
|
@@ -30804,7 +31487,7 @@ async function runLoop2(deps, ctx) {
|
|
|
30804
31487
|
handoff = h;
|
|
30805
31488
|
ledgerRun?.setState({ handoff: h });
|
|
30806
31489
|
};
|
|
30807
|
-
const restoredDispositions =
|
|
31490
|
+
const restoredDispositions = parseDispositionsInput({ dispositions: restored.dispositions })?.dispositions;
|
|
30808
31491
|
let dispositions = restoredDispositions;
|
|
30809
31492
|
const onDispositions = (d) => {
|
|
30810
31493
|
dispositions = d;
|
|
@@ -30822,10 +31505,23 @@ async function runLoop2(deps, ctx) {
|
|
|
30822
31505
|
let runDiagnosis;
|
|
30823
31506
|
const releaseWorkspace = (span) => round2.release({ hardStopped: run2.control.requested === "hard", ...span ? { span } : {} });
|
|
30824
31507
|
const attachFile = io.attachFile?.bind(io);
|
|
31508
|
+
const uploadTicket = io.uploadTicket?.bind(io);
|
|
31509
|
+
const nextStagedIndex = ctx.stagingIndex ?? stagingIndex();
|
|
31510
|
+
let artifactSeq = 0;
|
|
31511
|
+
const runUrl = runPageLink(run2.id);
|
|
31512
|
+
const artifacts = deps.artifacts ? {
|
|
31513
|
+
store: deps.artifacts,
|
|
31514
|
+
runId: run2.id,
|
|
31515
|
+
nextSeq: () => ++artifactSeq,
|
|
31516
|
+
...runUrl ? { runUrl } : {},
|
|
31517
|
+
reply: (text) => io.reply(text)
|
|
31518
|
+
} : void 0;
|
|
30825
31519
|
const toolContext = {
|
|
30826
31520
|
executor,
|
|
30827
31521
|
reportProgress,
|
|
30828
31522
|
...attachFile ? { attach: attachFile } : {},
|
|
31523
|
+
...artifacts ? { artifacts } : {},
|
|
31524
|
+
...uploadTicket ? { uploadTicket } : {},
|
|
30829
31525
|
web: webCapability(),
|
|
30830
31526
|
skills: deps.skills,
|
|
30831
31527
|
github: githubCapabilityFor(deps, msg.userId),
|
|
@@ -30838,7 +31534,7 @@ async function runLoop2(deps, ctx) {
|
|
|
30838
31534
|
onDigest,
|
|
30839
31535
|
onPrDescription,
|
|
30840
31536
|
onHandoff,
|
|
30841
|
-
|
|
31537
|
+
onDispositions
|
|
30842
31538
|
};
|
|
30843
31539
|
const harness = effectiveHarness(agent, deps.config.config.harness);
|
|
30844
31540
|
try {
|
|
@@ -30899,6 +31595,7 @@ async function runLoop2(deps, ctx) {
|
|
|
30899
31595
|
...resume ? {
|
|
30900
31596
|
resume: {
|
|
30901
31597
|
messages: resume.plan.messages,
|
|
31598
|
+
compactions: resume.plan.compactions,
|
|
30902
31599
|
settlements: resume.plan.settlements,
|
|
30903
31600
|
remainingMs: resume.plan.remainingMs,
|
|
30904
31601
|
turn: resume.plan.turn,
|
|
@@ -30927,6 +31624,23 @@ async function runLoop2(deps, ctx) {
|
|
|
30927
31624
|
// operator stop from /runs
|
|
30928
31625
|
inbox: admitted.inbox,
|
|
30929
31626
|
// thread follow-ups steered into this run (thread-admission item 2)
|
|
31627
|
+
// A steered follow-up's staged files (record 0033): copied into the
|
|
31628
|
+
// store and pulled into this workspace before the model reads the turn.
|
|
31629
|
+
...deps.artifacts ? {
|
|
31630
|
+
stageFollowUps: async (inputs) => {
|
|
31631
|
+
const files = inputs.flatMap((i) => i.staged ?? []);
|
|
31632
|
+
if (files.length === 0) return "";
|
|
31633
|
+
const staged = await stageIntoWorkspace(files, {
|
|
31634
|
+
store: deps.artifacts,
|
|
31635
|
+
threadKey: msg.threadKey,
|
|
31636
|
+
publish: (e) => registry.publish(run2.id, e),
|
|
31637
|
+
nextIndex: nextStagedIndex,
|
|
31638
|
+
executor,
|
|
31639
|
+
resident: round2.selection.resident !== void 0
|
|
31640
|
+
});
|
|
31641
|
+
return staged.line;
|
|
31642
|
+
}
|
|
31643
|
+
} : {},
|
|
30930
31644
|
// The step record before each step's tools (run-history item 35).
|
|
30931
31645
|
...ledgerRun ? { onStep: ledgerRun.step.bind(ledgerRun) } : {},
|
|
30932
31646
|
// A resume re-enters the loop from the plan (run-history item 37).
|
|
@@ -31150,7 +31864,8 @@ async function runLoop2(deps, ctx) {
|
|
|
31150
31864
|
...dispositions !== void 0 ? { dispositions } : {},
|
|
31151
31865
|
...reviewPost !== void 0 ? { reviewPost } : {},
|
|
31152
31866
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
31153
|
-
...coordinator !== void 0 ? { coordinator } : {}
|
|
31867
|
+
...coordinator !== void 0 ? { coordinator } : {},
|
|
31868
|
+
...seed !== void 0 ? { seed } : {}
|
|
31154
31869
|
});
|
|
31155
31870
|
if (runFailed)
|
|
31156
31871
|
await root.span(
|
|
@@ -31209,6 +31924,7 @@ var init_runLoop = __esm({
|
|
|
31209
31924
|
init_secrets2();
|
|
31210
31925
|
init_record();
|
|
31211
31926
|
init_reply();
|
|
31927
|
+
init_staging();
|
|
31212
31928
|
init_run2();
|
|
31213
31929
|
}
|
|
31214
31930
|
});
|
|
@@ -31459,6 +32175,12 @@ function openPlanCursor(graph, selected) {
|
|
|
31459
32175
|
for (const id of order) visit(id, []);
|
|
31460
32176
|
return cursor;
|
|
31461
32177
|
}
|
|
32178
|
+
function matchDispositions(findings, dispositions) {
|
|
32179
|
+
const issued = new Set(findings.map((f) => f.id));
|
|
32180
|
+
const matched = dispositions.filter((d) => issued.has(d.findingId));
|
|
32181
|
+
const dropped = [...new Set(dispositions.filter((d) => !issued.has(d.findingId)).map((d) => d.findingId))];
|
|
32182
|
+
return { matched, dropped };
|
|
32183
|
+
}
|
|
31462
32184
|
var MIN, SHIP_ROUND_RESERVE_MS, PLAN_ID_PATTERN, PLAN_BRANCH, UNIT_REF, SLUG_MAX, INSTANCE_ID_MAX, WAIT_MARGIN_MS, WAIT_CHUNK_MS, MERGE_POLL_MS, MERGE_WAIT_MAX_MS, BUSY_RETRY_MS;
|
|
31463
32185
|
var init_coordinator = __esm({
|
|
31464
32186
|
"../../src/core/ship/coordinator.ts"() {
|
|
@@ -31481,29 +32203,6 @@ var init_coordinator = __esm({
|
|
|
31481
32203
|
}
|
|
31482
32204
|
});
|
|
31483
32205
|
|
|
31484
|
-
// ../../src/core/ship/codingChild.ts
|
|
31485
|
-
function buildShipFixTurn(input) {
|
|
31486
|
-
const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings \u2014 address its prose)";
|
|
31487
|
-
return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address EVERY finding below, nits included: record one disposition per finding with submit_dispositions (fixed|declined, with a note), squash to coherent commits, resubmit the PR description with submit_pr_description, and push the branch. Never merge and never approve.
|
|
31488
|
-
|
|
31489
|
-
Findings:
|
|
31490
|
-
${findings}
|
|
31491
|
-
|
|
31492
|
-
Review:
|
|
31493
|
-
${input.review}`;
|
|
31494
|
-
}
|
|
31495
|
-
function withContractInFirstUserTurn(messages, block) {
|
|
31496
|
-
const at = messages.findIndex((m) => m.role === "user");
|
|
31497
|
-
if (at < 0) return [...messages, { role: "user", content: [{ type: "text", text: block }] }];
|
|
31498
|
-
return messages.map((m, i) => i === at ? { ...m, content: [...m.content, { type: "text", text: block }] } : m);
|
|
31499
|
-
}
|
|
31500
|
-
var init_codingChild = __esm({
|
|
31501
|
-
"../../src/core/ship/codingChild.ts"() {
|
|
31502
|
-
"use strict";
|
|
31503
|
-
init_reviewVerdict();
|
|
31504
|
-
}
|
|
31505
|
-
});
|
|
31506
|
-
|
|
31507
32206
|
// ../../src/core/ship/reviewChild.ts
|
|
31508
32207
|
function buildShipReviewTurn(input) {
|
|
31509
32208
|
const at = input.headSha ? ` at head \`${input.headSha}\`` : "";
|
|
@@ -31512,13 +32211,15 @@ function buildShipReviewTurn(input) {
|
|
|
31512
32211
|
}
|
|
31513
32212
|
const findings = input.prior.findings.map(formatFinding).join("\n") || "(none recorded)";
|
|
31514
32213
|
const dispositions = input.prior.dispositions.map((d) => `${d.findingId}: ${d.disposition}${d.note ? ` \u2014 ${d.note}` : ""}`).join("\n") || "(none recorded)";
|
|
32214
|
+
const dropped = input.prior.dropped !== void 0 && input.prior.dropped.length > 0 ? `
|
|
32215
|
+
Dispositions naming no finding of the previous round (dropped): ${input.prior.dropped.join(", ")}` : "";
|
|
31515
32216
|
return `Re-review pull request ${input.where}${at} \u2014 review round ${input.round} of this ship pipeline. Load the \`re-review-delta\` skill: narrow your READING to the delta since the previously reviewed head and verify each prior finding's disposition, but your verdict still covers the full diff against base. Carry every unresolved prior finding forward under its existing id.
|
|
31516
32217
|
|
|
31517
32218
|
Previous round's findings:
|
|
31518
32219
|
${findings}
|
|
31519
32220
|
|
|
31520
32221
|
Fix round's dispositions:
|
|
31521
|
-
${dispositions}`;
|
|
32222
|
+
${dispositions}${dropped}`;
|
|
31522
32223
|
}
|
|
31523
32224
|
var init_reviewChild = __esm({
|
|
31524
32225
|
"../../src/core/ship/reviewChild.ts"() {
|
|
@@ -31564,6 +32265,16 @@ async function contractFor(instance, unit, readers) {
|
|
|
31564
32265
|
...issue ? { issue } : {}
|
|
31565
32266
|
});
|
|
31566
32267
|
}
|
|
32268
|
+
function findingsRequest(input) {
|
|
32269
|
+
const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings, address its prose)";
|
|
32270
|
+
return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note), squash to coherent commits, resubmit the pull request description with submit_pr_description, and push the branch. Never merge and never approve.
|
|
32271
|
+
|
|
32272
|
+
Findings:
|
|
32273
|
+
${findings}
|
|
32274
|
+
|
|
32275
|
+
Review:
|
|
32276
|
+
${input.review}`;
|
|
32277
|
+
}
|
|
31567
32278
|
async function composeChild(brief, instance, unit, readers) {
|
|
31568
32279
|
const where = `${instance.repo}#${"pr" in brief ? brief.pr : ""}`;
|
|
31569
32280
|
switch (brief.kind) {
|
|
@@ -31573,10 +32284,13 @@ async function composeChild(brief, instance, unit, readers) {
|
|
|
31573
32284
|
return { preset: "coding", prompt, ref: unit.branch, contract };
|
|
31574
32285
|
}
|
|
31575
32286
|
case "review": {
|
|
31576
|
-
|
|
31577
|
-
|
|
31578
|
-
|
|
31579
|
-
|
|
32287
|
+
let prior;
|
|
32288
|
+
if (brief.prior !== void 0) {
|
|
32289
|
+
const findings = (await facts(readers, brief.prior.reviewRunId)).findings ?? [];
|
|
32290
|
+
const recorded = brief.prior.codingRunId !== void 0 ? (await facts(readers, brief.prior.codingRunId)).dispositions ?? [] : [];
|
|
32291
|
+
const { matched, dropped } = matchDispositions(findings, recorded);
|
|
32292
|
+
prior = { findings, dispositions: matched, dropped };
|
|
32293
|
+
}
|
|
31580
32294
|
const turn = buildShipReviewTurn({
|
|
31581
32295
|
where,
|
|
31582
32296
|
round: brief.round,
|
|
@@ -31588,16 +32302,18 @@ async function composeChild(brief, instance, unit, readers) {
|
|
|
31588
32302
|
|
|
31589
32303
|
${turn}`, contract };
|
|
31590
32304
|
}
|
|
31591
|
-
case "
|
|
32305
|
+
case "findings": {
|
|
31592
32306
|
const review = await facts(readers, brief.reviewRunId);
|
|
31593
|
-
const findings = review.findings ?? [];
|
|
31594
32307
|
return {
|
|
31595
32308
|
preset: "coding",
|
|
31596
|
-
prompt:
|
|
31597
|
-
ref: unit.branch
|
|
31598
|
-
fixRound: { findingIds: findings.map((f) => f.id) }
|
|
32309
|
+
prompt: findingsRequest({ where, findings: review.findings ?? [], review: review.finalReply ?? "" }),
|
|
32310
|
+
ref: unit.branch
|
|
31599
32311
|
};
|
|
31600
32312
|
}
|
|
32313
|
+
default: {
|
|
32314
|
+
const unknown = brief;
|
|
32315
|
+
throw new Error(`unknown brief kind ${JSON.stringify(unknown.kind)}`);
|
|
32316
|
+
}
|
|
31601
32317
|
}
|
|
31602
32318
|
}
|
|
31603
32319
|
async function facts(readers, runId3) {
|
|
@@ -31610,7 +32326,8 @@ var init_briefs = __esm({
|
|
|
31610
32326
|
"../../src/core/coordinator/briefs.ts"() {
|
|
31611
32327
|
"use strict";
|
|
31612
32328
|
init_directives();
|
|
31613
|
-
|
|
32329
|
+
init_reviewVerdict();
|
|
32330
|
+
init_coordinator();
|
|
31614
32331
|
init_contract();
|
|
31615
32332
|
init_preflight();
|
|
31616
32333
|
init_reviewChild();
|
|
@@ -31675,7 +32392,7 @@ async function plan(deps, input) {
|
|
|
31675
32392
|
try {
|
|
31676
32393
|
planId = planIdOf(request.planPath);
|
|
31677
32394
|
} catch (err2) {
|
|
31678
|
-
return { ok: false, reply: `\u{1F6AB} ${
|
|
32395
|
+
return { ok: false, reply: `\u{1F6AB} ${describe5(err2)}` };
|
|
31679
32396
|
}
|
|
31680
32397
|
let text;
|
|
31681
32398
|
try {
|
|
@@ -31683,7 +32400,7 @@ async function plan(deps, input) {
|
|
|
31683
32400
|
} catch (err2) {
|
|
31684
32401
|
return {
|
|
31685
32402
|
ok: false,
|
|
31686
|
-
reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${
|
|
32403
|
+
reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${describe5(err2)}`
|
|
31687
32404
|
};
|
|
31688
32405
|
}
|
|
31689
32406
|
const graph = parsePlanGraph(text, planId);
|
|
@@ -31696,7 +32413,7 @@ async function plan(deps, input) {
|
|
|
31696
32413
|
try {
|
|
31697
32414
|
selected = openPlanCursor(graph, request.units).order;
|
|
31698
32415
|
} catch (err2) {
|
|
31699
|
-
return { ok: false, reply: `\u{1F6AB} ${
|
|
32416
|
+
return { ok: false, reply: `\u{1F6AB} ${describe5(err2)}` };
|
|
31700
32417
|
}
|
|
31701
32418
|
return { ok: true, planned: { kind: "plan", planId, path: request.planPath, base, graph, selected, identity: identity2 } };
|
|
31702
32419
|
}
|
|
@@ -31812,7 +32529,7 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
31812
32529
|
try {
|
|
31813
32530
|
answer = await deps.create(instance.id);
|
|
31814
32531
|
} catch (err2) {
|
|
31815
|
-
answer = { kind: "unanswered", reason:
|
|
32532
|
+
answer = { kind: "unanswered", reason: describe5(err2) };
|
|
31816
32533
|
}
|
|
31817
32534
|
switch (answer.kind) {
|
|
31818
32535
|
case "created": {
|
|
@@ -31834,7 +32551,7 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
31834
32551
|
);
|
|
31835
32552
|
}
|
|
31836
32553
|
}
|
|
31837
|
-
var refused3,
|
|
32554
|
+
var refused3, describe5, RUNNING, ENDED;
|
|
31838
32555
|
var init_handOff = __esm({
|
|
31839
32556
|
"../../src/core/coordinator/handOff.ts"() {
|
|
31840
32557
|
"use strict";
|
|
@@ -31842,7 +32559,7 @@ var init_handOff = __esm({
|
|
|
31842
32559
|
init_coordinator();
|
|
31843
32560
|
init_briefs();
|
|
31844
32561
|
refused3 = (reply) => ({ status: "aborted", reply });
|
|
31845
|
-
|
|
32562
|
+
describe5 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
31846
32563
|
RUNNING = /* @__PURE__ */ new Set(["queued", "running", "paused", "waiting", "waitingForPause"]);
|
|
31847
32564
|
ENDED = /* @__PURE__ */ new Set(["complete", "errored", "terminated"]);
|
|
31848
32565
|
}
|
|
@@ -32459,6 +33176,18 @@ var init_ship = __esm({
|
|
|
32459
33176
|
}
|
|
32460
33177
|
});
|
|
32461
33178
|
|
|
33179
|
+
// ../../src/core/ship/codingChild.ts
|
|
33180
|
+
function withContractInFirstUserTurn(messages, block) {
|
|
33181
|
+
const at = messages.findIndex((m) => m.role === "user");
|
|
33182
|
+
if (at < 0) return [...messages, { role: "user", content: [{ type: "text", text: block }] }];
|
|
33183
|
+
return messages.map((m, i) => i === at ? { ...m, content: [...m.content, { type: "text", text: block }] } : m);
|
|
33184
|
+
}
|
|
33185
|
+
var init_codingChild = __esm({
|
|
33186
|
+
"../../src/core/ship/codingChild.ts"() {
|
|
33187
|
+
"use strict";
|
|
33188
|
+
}
|
|
33189
|
+
});
|
|
33190
|
+
|
|
32462
33191
|
// ../../src/core/dispatch/settle.ts
|
|
32463
33192
|
function settleThread(deps, ctx) {
|
|
32464
33193
|
const { msg, admitted, runLoopStarted, control } = ctx;
|
|
@@ -32533,22 +33262,22 @@ function createRunEnding(deps) {
|
|
|
32533
33262
|
try {
|
|
32534
33263
|
deps.registry.seal(id, replyOk === void 0 ? {} : { replyOk });
|
|
32535
33264
|
} catch (err2) {
|
|
32536
|
-
log(`[ending] seal ${id}: ${
|
|
33265
|
+
log(`[ending] seal ${id}: ${describe6(err2)}`);
|
|
32537
33266
|
}
|
|
32538
33267
|
try {
|
|
32539
33268
|
afterSeal?.();
|
|
32540
33269
|
} catch (err2) {
|
|
32541
|
-
log(`[ending] after seal ${id}: ${
|
|
33270
|
+
log(`[ending] after seal ${id}: ${describe6(err2)}`);
|
|
32542
33271
|
}
|
|
32543
33272
|
}
|
|
32544
33273
|
for (const entry of pending2.splice(0)) {
|
|
32545
33274
|
try {
|
|
32546
33275
|
const result = deps.registry.seal(entry.runId);
|
|
32547
33276
|
void Promise.resolve(entry.write(result, entry.failedAfterFinish)).catch(
|
|
32548
|
-
(err2) => log(`[ending] record ${entry.runId}: ${
|
|
33277
|
+
(err2) => log(`[ending] record ${entry.runId}: ${describe6(err2)}`)
|
|
32549
33278
|
);
|
|
32550
33279
|
} catch (err2) {
|
|
32551
|
-
log(`[ending] record ${entry.runId}: ${
|
|
33280
|
+
log(`[ending] record ${entry.runId}: ${describe6(err2)}`);
|
|
32552
33281
|
}
|
|
32553
33282
|
}
|
|
32554
33283
|
};
|
|
@@ -32558,7 +33287,7 @@ function createRunEnding(deps) {
|
|
|
32558
33287
|
try {
|
|
32559
33288
|
deps.onFinished?.(runId3);
|
|
32560
33289
|
} catch (err2) {
|
|
32561
|
-
log(`[ending] finished ${runId3}: ${
|
|
33290
|
+
log(`[ending] finished ${runId3}: ${describe6(err2)}`);
|
|
32562
33291
|
}
|
|
32563
33292
|
},
|
|
32564
33293
|
register(entry) {
|
|
@@ -32586,7 +33315,7 @@ function createRunEnding(deps) {
|
|
|
32586
33315
|
}
|
|
32587
33316
|
};
|
|
32588
33317
|
}
|
|
32589
|
-
function
|
|
33318
|
+
function describe6(err2) {
|
|
32590
33319
|
return err2 instanceof Error ? err2.message : String(err2);
|
|
32591
33320
|
}
|
|
32592
33321
|
var init_runEnding = __esm({
|
|
@@ -32715,6 +33444,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32715
33444
|
if (outcome.kind === "redispatch")
|
|
32716
33445
|
return dispatch(deps, msg, io, {
|
|
32717
33446
|
...opts.parent ? { parent: opts.parent } : {},
|
|
33447
|
+
...opts.seed ? { seed: opts.seed } : {},
|
|
32718
33448
|
...opts.coordinator ? { coordinator: opts.coordinator } : {}
|
|
32719
33449
|
});
|
|
32720
33450
|
if (outcome.kind !== "proceed") return ended;
|
|
@@ -32784,7 +33514,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32784
33514
|
}
|
|
32785
33515
|
const contractBlock = opts.contract ? renderContract(opts.contract, { maxChars: DEFAULT_CONTRACT_MAX_CHARS }).text : void 0;
|
|
32786
33516
|
const requestText = route3?.parts ? compoundBrief(directives.text, route3.parts) : directives.text;
|
|
32787
|
-
const
|
|
33517
|
+
const seed = opts.seed ? "parent" : "channel";
|
|
33518
|
+
const built = buildMessages(opts.seed ?? history, requestText, msg.images, msg.documents);
|
|
32788
33519
|
const messages = resume ? resume.plan.messages : contractBlock !== void 0 && agent.name !== "review" ? withContractInFirstUserTurn(built, contractBlock) : built;
|
|
32789
33520
|
const headPreflight = await authorizePrHead({
|
|
32790
33521
|
msg,
|
|
@@ -32821,11 +33552,21 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32821
33552
|
admitted,
|
|
32822
33553
|
parentRunId,
|
|
32823
33554
|
coordinator,
|
|
33555
|
+
seed,
|
|
33556
|
+
...opts.seed ? { seedTurns: opts.seed } : {},
|
|
32824
33557
|
agentSource,
|
|
32825
33558
|
...routeEvent ? { route: routeEvent } : {}
|
|
32826
33559
|
});
|
|
32827
33560
|
const { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta } = registration;
|
|
32828
33561
|
registered = run2;
|
|
33562
|
+
const staged = !resume && deps.artifacts && msg.staged && msg.staged.length > 0 ? msg.staged : [];
|
|
33563
|
+
const nextStagedIndex = stagingIndex();
|
|
33564
|
+
const stagedCopies = staged.length > 0 && agent.machine !== "none" ? copyStaged(staged, {
|
|
33565
|
+
store: deps.artifacts,
|
|
33566
|
+
threadKey: msg.threadKey,
|
|
33567
|
+
nextIndex: nextStagedIndex,
|
|
33568
|
+
publish: (e) => registry.publish(runId3, e)
|
|
33569
|
+
}) : void 0;
|
|
32829
33570
|
const reservation = await reserveRun(deps, {
|
|
32830
33571
|
msg,
|
|
32831
33572
|
agent,
|
|
@@ -32843,7 +33584,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32843
33584
|
admitted,
|
|
32844
33585
|
root,
|
|
32845
33586
|
parentRunId,
|
|
32846
|
-
coordinator
|
|
33587
|
+
coordinator,
|
|
33588
|
+
seed
|
|
32847
33589
|
});
|
|
32848
33590
|
if (reservation) {
|
|
32849
33591
|
reserved = reservation.reserved;
|
|
@@ -32873,6 +33615,22 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32873
33615
|
});
|
|
32874
33616
|
return ended;
|
|
32875
33617
|
}
|
|
33618
|
+
if (staged.length > 0) {
|
|
33619
|
+
const line3 = stagedCopies ? attachmentsLine(
|
|
33620
|
+
await pullStaged(await stagedCopies, {
|
|
33621
|
+
store: deps.artifacts,
|
|
33622
|
+
executor,
|
|
33623
|
+
resident: resident !== void 0
|
|
33624
|
+
})
|
|
33625
|
+
) : noWorkspaceLine(staged);
|
|
33626
|
+
const request = messages[messages.length - 1];
|
|
33627
|
+
if (request && request.role === "user" && line3) {
|
|
33628
|
+
request.content = Array.isArray(request.content) ? [...request.content, { type: "text", text: line3 }] : [
|
|
33629
|
+
{ type: "text", text: request.content },
|
|
33630
|
+
{ type: "text", text: line3 }
|
|
33631
|
+
];
|
|
33632
|
+
}
|
|
33633
|
+
}
|
|
32876
33634
|
const bearer = mintRunBearer(deps, { runId: runId3, agent, profile, resolved, registry, root, clock: clock3 });
|
|
32877
33635
|
const headGate = await authorizeAttachedHead(deps, {
|
|
32878
33636
|
msg,
|
|
@@ -32937,7 +33695,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32937
33695
|
registry,
|
|
32938
33696
|
resume,
|
|
32939
33697
|
parentRunId,
|
|
32940
|
-
coordinator
|
|
33698
|
+
coordinator,
|
|
33699
|
+
seed
|
|
32941
33700
|
});
|
|
32942
33701
|
ledgerRun = await claimRun(deps, {
|
|
32943
33702
|
msg,
|
|
@@ -32960,7 +33719,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32960
33719
|
clock: clock3,
|
|
32961
33720
|
root,
|
|
32962
33721
|
parentRunId,
|
|
32963
|
-
coordinator
|
|
33722
|
+
coordinator,
|
|
33723
|
+
seed
|
|
32964
33724
|
});
|
|
32965
33725
|
const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
|
|
32966
33726
|
{ core: deps, dispatch, registry, clock: clock3 },
|
|
@@ -32980,6 +33740,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32980
33740
|
agent,
|
|
32981
33741
|
profile,
|
|
32982
33742
|
resolved,
|
|
33743
|
+
stagingIndex: nextStagedIndex,
|
|
32983
33744
|
provider,
|
|
32984
33745
|
model,
|
|
32985
33746
|
messages,
|
|
@@ -33013,7 +33774,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33013
33774
|
wait,
|
|
33014
33775
|
parentRunId,
|
|
33015
33776
|
coordinator,
|
|
33016
|
-
|
|
33777
|
+
seed,
|
|
33017
33778
|
...bearer !== void 0 ? { bearer } : {}
|
|
33018
33779
|
});
|
|
33019
33780
|
const { answer, prNote, toolCalls, runDiagnosis, checklistAsLeft, checklistCheckedOff, releaseWorkspace } = ran;
|
|
@@ -33128,6 +33889,7 @@ var init_dispatcher = __esm({
|
|
|
33128
33889
|
init_route();
|
|
33129
33890
|
init_authorize2();
|
|
33130
33891
|
init_messages();
|
|
33892
|
+
init_staging();
|
|
33131
33893
|
init_provision();
|
|
33132
33894
|
init_run2();
|
|
33133
33895
|
init_runLoop();
|
|
@@ -33146,7 +33908,7 @@ var init_dispatcher = __esm({
|
|
|
33146
33908
|
});
|
|
33147
33909
|
|
|
33148
33910
|
// ../../src/core/runHistoryWriter.ts
|
|
33149
|
-
function
|
|
33911
|
+
function describe7(err2) {
|
|
33150
33912
|
return err2 instanceof Error ? err2.message : String(err2);
|
|
33151
33913
|
}
|
|
33152
33914
|
function createRunHistoryWriter(opts) {
|
|
@@ -33163,7 +33925,7 @@ function createRunHistoryWriter(opts) {
|
|
|
33163
33925
|
try {
|
|
33164
33926
|
opts.onPersisted?.(id);
|
|
33165
33927
|
} catch (err2) {
|
|
33166
|
-
opts.warn(`[run-history] onPersisted hook failed for ${id}: ${
|
|
33928
|
+
opts.warn(`[run-history] onPersisted hook failed for ${id}: ${describe7(err2)}`);
|
|
33167
33929
|
}
|
|
33168
33930
|
};
|
|
33169
33931
|
const attemptAll = async (record2, flag2, sink, span) => {
|
|
@@ -33186,12 +33948,12 @@ function createRunHistoryWriter(opts) {
|
|
|
33186
33948
|
}
|
|
33187
33949
|
if (err2 instanceof PermanentStoreError) {
|
|
33188
33950
|
failures++;
|
|
33189
|
-
opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${
|
|
33951
|
+
opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${describe7(err2)}`);
|
|
33190
33952
|
return;
|
|
33191
33953
|
}
|
|
33192
33954
|
if (attempt >= attempts) {
|
|
33193
33955
|
failures++;
|
|
33194
|
-
opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${
|
|
33956
|
+
opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${describe7(err2)}`);
|
|
33195
33957
|
return;
|
|
33196
33958
|
}
|
|
33197
33959
|
await sleep2(jittered(RUN_HISTORY_RETRY_DELAYS_MS[attempt - 1]));
|
|
@@ -33211,7 +33973,7 @@ function createRunHistoryWriter(opts) {
|
|
|
33211
33973
|
}
|
|
33212
33974
|
const p = attemptAll(record2, flag2, writeOpts?.via ?? opts.store, writeOpts?.span).catch((err2) => {
|
|
33213
33975
|
failures++;
|
|
33214
|
-
opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${
|
|
33976
|
+
opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${describe7(err2)}`);
|
|
33215
33977
|
}).finally(() => {
|
|
33216
33978
|
if (flag2) provisionalFlags.delete(flag2);
|
|
33217
33979
|
void inFlight.delete(p);
|
|
@@ -33288,7 +34050,12 @@ function toListItem(record2, bytes) {
|
|
|
33288
34050
|
return { ...rest, bytes };
|
|
33289
34051
|
}
|
|
33290
34052
|
function retentionPolicyOf(cfg) {
|
|
33291
|
-
return clampRetentionPolicy({
|
|
34053
|
+
return clampRetentionPolicy({
|
|
34054
|
+
retentionDays: cfg.retentionDays,
|
|
34055
|
+
maxRuns: cfg.maxRuns,
|
|
34056
|
+
maxBytes: cfg.maxBytes,
|
|
34057
|
+
sessionLogMaxBytes: cfg.sessionLogMaxBytes
|
|
34058
|
+
});
|
|
33292
34059
|
}
|
|
33293
34060
|
function buildRunStore(cfg, secrets, deps) {
|
|
33294
34061
|
if (!cfg) return null;
|
|
@@ -33532,6 +34299,24 @@ var init_flusher = __esm({
|
|
|
33532
34299
|
}
|
|
33533
34300
|
});
|
|
33534
34301
|
|
|
34302
|
+
// ../../src/core/runLedger/sessionLog.ts
|
|
34303
|
+
function sessionKey(threadKey, agent) {
|
|
34304
|
+
return `${threadKey}:${agent ?? "-"}`;
|
|
34305
|
+
}
|
|
34306
|
+
function requestIndex(seed) {
|
|
34307
|
+
for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
|
|
34308
|
+
return 0;
|
|
34309
|
+
}
|
|
34310
|
+
var DEFAULT_SESSION_LOG_MAX_BYTES;
|
|
34311
|
+
var init_sessionLog = __esm({
|
|
34312
|
+
"../../src/core/runLedger/sessionLog.ts"() {
|
|
34313
|
+
"use strict";
|
|
34314
|
+
init_runRecord();
|
|
34315
|
+
init_runRecord();
|
|
34316
|
+
DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
|
|
34317
|
+
}
|
|
34318
|
+
});
|
|
34319
|
+
|
|
33535
34320
|
// ../../src/core/runLedger/types.ts
|
|
33536
34321
|
var LEASE_MS, HEARTBEAT_MS, APPEND_FLUSH_MS, APPEND_FLUSH_EVENTS, TRANSCRIPT_REQUEST_BYTES, TRANSCRIPT_PART_BYTES, ATTACHMENT_REF_BYTES, GEN_PATTERN;
|
|
33537
34322
|
var init_types5 = __esm({
|
|
@@ -33577,36 +34362,45 @@ function createLedgerWriteThrough(opts) {
|
|
|
33577
34362
|
"[ledger] state Worker has no run-ledger routes \u2014 deploy it before this bot version; runs are not tracked until then"
|
|
33578
34363
|
);
|
|
33579
34364
|
};
|
|
33580
|
-
async function claim(req,
|
|
34365
|
+
async function claim(req, opts2 = {}) {
|
|
33581
34366
|
for (let attempt = 1; ; attempt++) {
|
|
33582
34367
|
try {
|
|
34368
|
+
let session;
|
|
34369
|
+
if (opts2.seed) {
|
|
34370
|
+
const key = sessionKey(req.threadKey, req.meta.agent);
|
|
34371
|
+
const next = await ledger.sessionTail(key);
|
|
34372
|
+
session = { key, seedFrom: next, request: next + requestIndex(opts2.seed), range: { from: next } };
|
|
34373
|
+
}
|
|
33583
34374
|
const result = await ledger.claim({
|
|
33584
34375
|
runId: req.runId,
|
|
33585
34376
|
threadKey: req.threadKey,
|
|
33586
34377
|
gen,
|
|
33587
34378
|
leaseMs,
|
|
33588
34379
|
startedAt: req.startedAt,
|
|
33589
|
-
meta: req.meta,
|
|
34380
|
+
meta: session ? { ...req.meta, session } : req.meta,
|
|
33590
34381
|
card: req.card ?? null,
|
|
33591
34382
|
system: req.system,
|
|
33592
34383
|
tools: req.tools,
|
|
33593
34384
|
...req.state !== void 0 ? { state: req.state } : {},
|
|
33594
|
-
...phase ? { phase } : {}
|
|
34385
|
+
...opts2.phase ? { phase: opts2.phase } : {}
|
|
33595
34386
|
});
|
|
33596
|
-
if (result.ok)
|
|
33597
|
-
|
|
34387
|
+
if (result.ok) {
|
|
34388
|
+
if (session) await ledger.claimSession(session.key, req.runId, gen);
|
|
34389
|
+
return session ? { outcome: "ok", session } : { outcome: "ok" };
|
|
34390
|
+
}
|
|
34391
|
+
if (result.live.runId === req.runId) return { outcome: "fenced" };
|
|
33598
34392
|
warn(
|
|
33599
34393
|
`[ledger] ${req.threadKey} not tracked: the thread's live row belongs to run ${result.live.runId} (started ${new Date(result.live.startedAt).toISOString()}) \u2014 reclaim is the resume phase's`
|
|
33600
34394
|
);
|
|
33601
|
-
return "untracked";
|
|
34395
|
+
return { outcome: "untracked" };
|
|
33602
34396
|
} catch (err2) {
|
|
33603
34397
|
if (err2 instanceof RouteMissingError) {
|
|
33604
34398
|
routeMissing();
|
|
33605
|
-
return "untracked";
|
|
34399
|
+
return { outcome: "untracked" };
|
|
33606
34400
|
}
|
|
33607
34401
|
if (err2 instanceof PermanentStoreError || attempt >= claimAttempts) {
|
|
33608
|
-
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${
|
|
33609
|
-
return "untracked";
|
|
34402
|
+
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${describe8(err2)}`);
|
|
34403
|
+
return { outcome: "untracked" };
|
|
33610
34404
|
}
|
|
33611
34405
|
await sleep2(RETRY_MS[Math.min(attempt, RETRY_MS.length) - 1]);
|
|
33612
34406
|
}
|
|
@@ -33626,6 +34420,15 @@ function createLedgerWriteThrough(opts) {
|
|
|
33626
34420
|
seeded = false;
|
|
33627
34421
|
adopted = false;
|
|
33628
34422
|
handedOff = false;
|
|
34423
|
+
/** The run's place in its session log (session-log item 2); undefined for
|
|
34424
|
+
* a run without a conversation of its own and for an adopted row claimed
|
|
34425
|
+
* before the log existed. */
|
|
34426
|
+
session;
|
|
34427
|
+
/** Turns of the run's conversation on the ledger, counted from its seed —
|
|
34428
|
+
* the last step record's `turnIndex` — so the finish can close the range. */
|
|
34429
|
+
turnsWritten;
|
|
34430
|
+
/** A detach left the log short of what the model saw: the record says `broken`. */
|
|
34431
|
+
broken = false;
|
|
33629
34432
|
stepNo;
|
|
33630
34433
|
lastSeq;
|
|
33631
34434
|
state;
|
|
@@ -33646,7 +34449,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
33646
34449
|
},
|
|
33647
34450
|
...opts.schedule ? { schedule: opts.schedule } : {}
|
|
33648
34451
|
});
|
|
33649
|
-
constructor(req, from = {
|
|
34452
|
+
constructor(req, from = {
|
|
34453
|
+
stepNo: 0,
|
|
34454
|
+
lastSeq: 0
|
|
34455
|
+
}) {
|
|
33650
34456
|
this.runId = req.runId;
|
|
33651
34457
|
this.threadKey = req.threadKey;
|
|
33652
34458
|
this.state = req.state ?? {};
|
|
@@ -33655,6 +34461,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
33655
34461
|
this.stepNo = from.stepNo;
|
|
33656
34462
|
this.lastSeq = from.lastSeq;
|
|
33657
34463
|
this.adopted = from.resumable === true;
|
|
34464
|
+
this.session = from.session;
|
|
34465
|
+
}
|
|
34466
|
+
/** A reservation learns its session when the prompt's claim promotes it. */
|
|
34467
|
+
bindSession(session) {
|
|
34468
|
+
this.session = session;
|
|
33658
34469
|
}
|
|
33659
34470
|
get resumable() {
|
|
33660
34471
|
return !this.detached && (this.seeded || this.adopted);
|
|
@@ -33662,11 +34473,24 @@ function createLedgerWriteThrough(opts) {
|
|
|
33662
34473
|
markHandedOff() {
|
|
33663
34474
|
this.handedOff = true;
|
|
33664
34475
|
}
|
|
34476
|
+
/** The run's range as the finished record carries it (session-log item 2):
|
|
34477
|
+
* closed at the last turn written, or `broken` after a detach — the log
|
|
34478
|
+
* ends short of what the model saw, and the next run in the session must
|
|
34479
|
+
* know. A run that wrote no turn leaves the range open. */
|
|
34480
|
+
sessionOnRecord() {
|
|
34481
|
+
if (!this.session) return void 0;
|
|
34482
|
+
if (this.broken) return { ...this.session, range: "broken" };
|
|
34483
|
+
const from = this.session.range === "broken" ? this.session.seedFrom : this.session.range.from;
|
|
34484
|
+
const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.session.seedFrom + this.turnsWritten - 1 : void 0;
|
|
34485
|
+
return { ...this.session, range: to !== void 0 && to >= from ? { from, to } : { from } };
|
|
34486
|
+
}
|
|
33665
34487
|
sink = {
|
|
33666
|
-
put: async (
|
|
34488
|
+
put: async (plain4) => {
|
|
33667
34489
|
this.finished = true;
|
|
33668
34490
|
live.delete(this);
|
|
33669
34491
|
await this.close();
|
|
34492
|
+
const session = this.sessionOnRecord();
|
|
34493
|
+
const record2 = session ? { ...plain4, session } : plain4;
|
|
33670
34494
|
let result;
|
|
33671
34495
|
try {
|
|
33672
34496
|
result = await ledger.finish(this.runId, gen, record2);
|
|
@@ -33687,6 +34511,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33687
34511
|
detach(reason) {
|
|
33688
34512
|
if (this.detached) return;
|
|
33689
34513
|
this.detached = true;
|
|
34514
|
+
this.broken = true;
|
|
33690
34515
|
warn(`[ledger] ${this.threadKey} run ${this.runId} detached: ${reason} \u2014 this run is not resumable`);
|
|
33691
34516
|
this.stopHeartbeat();
|
|
33692
34517
|
live.delete(this);
|
|
@@ -33704,13 +34529,15 @@ function createLedgerWriteThrough(opts) {
|
|
|
33704
34529
|
* no record at all was killed before its conversation was stored and
|
|
33705
34530
|
* closes `interrupted`. */
|
|
33706
34531
|
async seed(messages, budgetMs) {
|
|
33707
|
-
const
|
|
34532
|
+
const base = this.session?.seedFrom ?? 0;
|
|
34533
|
+
const turns = messages.map((message2, i) => ({ idx: base + i, message: message2 }));
|
|
33708
34534
|
try {
|
|
33709
|
-
const seeded = await ledger.seed(this.runId, gen, turns);
|
|
34535
|
+
const seeded = await ledger.seed(this.runId, gen, turns, this.session?.key);
|
|
33710
34536
|
if (!seeded.ok) {
|
|
33711
34537
|
this.detach(`seed refused (${seeded.reason})`);
|
|
33712
34538
|
return;
|
|
33713
34539
|
}
|
|
34540
|
+
this.turnsWritten = messages.length;
|
|
33714
34541
|
const recorded = await ledger.step(
|
|
33715
34542
|
this.runId,
|
|
33716
34543
|
gen,
|
|
@@ -33729,16 +34556,19 @@ function createLedgerWriteThrough(opts) {
|
|
|
33729
34556
|
if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
|
|
33730
34557
|
else this.seeded = true;
|
|
33731
34558
|
} catch (err2) {
|
|
33732
|
-
this.detach(`seed failed: ${
|
|
34559
|
+
this.detach(`seed failed: ${describe8(err2)}`);
|
|
33733
34560
|
}
|
|
33734
34561
|
}
|
|
33735
34562
|
async step(report2) {
|
|
33736
34563
|
if (this.detached) return;
|
|
33737
|
-
const
|
|
34564
|
+
const base = this.session?.seedFrom ?? 0;
|
|
34565
|
+
const turns = report2.turns.map((message2, i) => ({ idx: base + report2.firstIdx + i, message: message2 }));
|
|
34566
|
+
if (report2.compaction)
|
|
34567
|
+
turns.push({ idx: base + report2.firstIdx + report2.turns.length, compaction: report2.compaction });
|
|
33738
34568
|
const record2 = {
|
|
33739
34569
|
step: ++this.stepNo,
|
|
33740
34570
|
seq: this.lastSeq,
|
|
33741
|
-
turnIndex: report2.firstIdx +
|
|
34571
|
+
turnIndex: report2.firstIdx + turns.length,
|
|
33742
34572
|
inFlight: report2.inFlight,
|
|
33743
34573
|
inboxConsumedSeq: report2.inboxConsumedSeq,
|
|
33744
34574
|
remainingMs: report2.remainingMs,
|
|
@@ -33747,12 +34577,13 @@ function createLedgerWriteThrough(opts) {
|
|
|
33747
34577
|
};
|
|
33748
34578
|
for (let attempt = 1; ; attempt++) {
|
|
33749
34579
|
try {
|
|
33750
|
-
const result = await ledger.step(this.runId, gen, record2, turns);
|
|
34580
|
+
const result = await ledger.step(this.runId, gen, record2, turns, this.session?.key);
|
|
33751
34581
|
if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
|
|
34582
|
+
else this.turnsWritten = record2.turnIndex;
|
|
33752
34583
|
return;
|
|
33753
34584
|
} catch (err2) {
|
|
33754
34585
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
33755
|
-
this.detach(`step ${record2.step} failed: ${
|
|
34586
|
+
this.detach(`step ${record2.step} failed: ${describe8(err2)}`);
|
|
33756
34587
|
return;
|
|
33757
34588
|
}
|
|
33758
34589
|
await sleep2(RETRY_MS[0]);
|
|
@@ -33785,7 +34616,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33785
34616
|
} catch (err2) {
|
|
33786
34617
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
33787
34618
|
this.stateDirty = true;
|
|
33788
|
-
warn(`[ledger] ${this.threadKey} state not written: ${
|
|
34619
|
+
warn(`[ledger] ${this.threadKey} state not written: ${describe8(err2)}`);
|
|
33789
34620
|
return;
|
|
33790
34621
|
}
|
|
33791
34622
|
await sleep2(RETRY_MS[0]);
|
|
@@ -33803,7 +34634,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33803
34634
|
this.detach(`finishing refused (fenced)`);
|
|
33804
34635
|
return "fenced";
|
|
33805
34636
|
} catch (err2) {
|
|
33806
|
-
warn(`[ledger] ${this.threadKey} finishing failed: ${
|
|
34637
|
+
warn(`[ledger] ${this.threadKey} finishing failed: ${describe8(err2)}`);
|
|
33807
34638
|
return "unavailable";
|
|
33808
34639
|
}
|
|
33809
34640
|
}
|
|
@@ -33818,7 +34649,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33818
34649
|
warn(`[ledger] ${this.threadKey} abandon refused (fenced) \u2014 the row is another generation's`);
|
|
33819
34650
|
} catch (err2) {
|
|
33820
34651
|
warn(
|
|
33821
|
-
`[ledger] ${this.threadKey} abandon failed: ${
|
|
34652
|
+
`[ledger] ${this.threadKey} abandon failed: ${describe8(err2)} \u2014 the sweep will take the row once its lease lapses`
|
|
33822
34653
|
);
|
|
33823
34654
|
}
|
|
33824
34655
|
}
|
|
@@ -33839,7 +34670,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33839
34670
|
this.onStop(result.stop);
|
|
33840
34671
|
}
|
|
33841
34672
|
} catch (err2) {
|
|
33842
|
-
warn(`[ledger] ${this.threadKey} heartbeat failed: ${
|
|
34673
|
+
warn(`[ledger] ${this.threadKey} heartbeat failed: ${describe8(err2)}`);
|
|
33843
34674
|
}
|
|
33844
34675
|
}
|
|
33845
34676
|
stopHeartbeat() {
|
|
@@ -33856,8 +34687,8 @@ function createLedgerWriteThrough(opts) {
|
|
|
33856
34687
|
return {
|
|
33857
34688
|
gen,
|
|
33858
34689
|
async reserve(req) {
|
|
33859
|
-
const claimed2 = await claim({ ...req, system: "", tools: [], state: {} }, "attaching");
|
|
33860
|
-
if (claimed2 !== "ok") return void 0;
|
|
34690
|
+
const claimed2 = await claim({ ...req, system: "", tools: [], state: {} }, { phase: "attaching" });
|
|
34691
|
+
if (claimed2.outcome !== "ok") return void 0;
|
|
33861
34692
|
const run2 = new TrackedRun(req);
|
|
33862
34693
|
run2.startHeartbeat();
|
|
33863
34694
|
live.add(run2);
|
|
@@ -33865,35 +34696,46 @@ function createLedgerWriteThrough(opts) {
|
|
|
33865
34696
|
},
|
|
33866
34697
|
async open(req) {
|
|
33867
34698
|
const reserved = req.reservation;
|
|
34699
|
+
const seed = req.seed?.messages;
|
|
33868
34700
|
if (reserved instanceof TrackedRun) {
|
|
33869
34701
|
if (!reserved.tracked()) return void 0;
|
|
33870
|
-
const claimed3 = await claim(req);
|
|
33871
|
-
if (claimed3 === "fenced") {
|
|
34702
|
+
const claimed3 = await claim(req, seed ? { seed } : {});
|
|
34703
|
+
if (claimed3.outcome === "fenced") {
|
|
33872
34704
|
reserved.detach("promotion refused (fenced)");
|
|
33873
34705
|
return void 0;
|
|
33874
34706
|
}
|
|
33875
|
-
if (claimed3 !== "ok") {
|
|
34707
|
+
if (claimed3.outcome !== "ok") {
|
|
33876
34708
|
await reserved.close();
|
|
33877
34709
|
live.delete(reserved);
|
|
33878
34710
|
return void 0;
|
|
33879
34711
|
}
|
|
34712
|
+
if (claimed3.session) reserved.bindSession(claimed3.session);
|
|
33880
34713
|
if (req.seed) await reserved.seed(req.seed.messages, req.seed.budgetMs);
|
|
33881
34714
|
return reserved;
|
|
33882
34715
|
}
|
|
33883
|
-
const claimed2 = await claim(req);
|
|
33884
|
-
if (claimed2 === "fenced") {
|
|
34716
|
+
const claimed2 = await claim(req, seed ? { seed } : {});
|
|
34717
|
+
if (claimed2.outcome === "fenced") {
|
|
33885
34718
|
warn(`[ledger] ${req.threadKey} not tracked: run ${req.runId} is live under another generation`);
|
|
33886
34719
|
return void 0;
|
|
33887
34720
|
}
|
|
33888
|
-
if (claimed2 !== "ok") return void 0;
|
|
33889
|
-
const run2 = new TrackedRun(req
|
|
34721
|
+
if (claimed2.outcome !== "ok") return void 0;
|
|
34722
|
+
const run2 = new TrackedRun(req, {
|
|
34723
|
+
stepNo: 0,
|
|
34724
|
+
lastSeq: 0,
|
|
34725
|
+
...claimed2.session ? { session: claimed2.session } : {}
|
|
34726
|
+
});
|
|
33890
34727
|
if (req.seed) await run2.seed(req.seed.messages, req.seed.budgetMs);
|
|
33891
34728
|
run2.startHeartbeat();
|
|
33892
34729
|
live.add(run2);
|
|
33893
34730
|
return run2;
|
|
33894
34731
|
},
|
|
33895
34732
|
adopt(req) {
|
|
33896
|
-
const run2 = new TrackedRun(req, {
|
|
34733
|
+
const run2 = new TrackedRun(req, {
|
|
34734
|
+
stepNo: req.lastStep,
|
|
34735
|
+
lastSeq: req.lastSeq,
|
|
34736
|
+
resumable: true,
|
|
34737
|
+
...req.session ? { session: req.session } : {}
|
|
34738
|
+
});
|
|
33897
34739
|
run2.startHeartbeat();
|
|
33898
34740
|
live.add(run2);
|
|
33899
34741
|
return run2;
|
|
@@ -33906,7 +34748,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33906
34748
|
warn(`[ledger] inbox push refused for run ${runId3}: no live row \u2014 the follow-up rides in memory only`);
|
|
33907
34749
|
return void 0;
|
|
33908
34750
|
} catch (err2) {
|
|
33909
|
-
warn(`[ledger] inbox push failed for run ${runId3}: ${
|
|
34751
|
+
warn(`[ledger] inbox push failed for run ${runId3}: ${describe8(err2)} \u2014 the follow-up rides in memory only`);
|
|
33910
34752
|
return void 0;
|
|
33911
34753
|
}
|
|
33912
34754
|
},
|
|
@@ -33915,7 +34757,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33915
34757
|
return await ledger.readInbox(runId3, afterSeq);
|
|
33916
34758
|
} catch (err2) {
|
|
33917
34759
|
warn(
|
|
33918
|
-
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}: ${
|
|
34760
|
+
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}: ${describe8(err2)} \u2014 resuming with the reclaim's snapshot only`
|
|
33919
34761
|
);
|
|
33920
34762
|
return [];
|
|
33921
34763
|
}
|
|
@@ -33932,17 +34774,18 @@ function createLedgerWriteThrough(opts) {
|
|
|
33932
34774
|
for (const r of candidates) if (markedSet.has(r.runId)) r.markHandedOff();
|
|
33933
34775
|
return { marked };
|
|
33934
34776
|
} catch (err2) {
|
|
33935
|
-
return { marked: [], failed:
|
|
34777
|
+
return { marked: [], failed: describe8(err2) };
|
|
33936
34778
|
}
|
|
33937
34779
|
}
|
|
33938
34780
|
};
|
|
33939
34781
|
}
|
|
33940
|
-
var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS,
|
|
34782
|
+
var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS, describe8;
|
|
33941
34783
|
var init_writeThrough = __esm({
|
|
33942
34784
|
"../../src/core/runLedger/writeThrough.ts"() {
|
|
33943
34785
|
"use strict";
|
|
33944
34786
|
init_runStoreWorker();
|
|
33945
34787
|
init_flusher();
|
|
34788
|
+
init_sessionLog();
|
|
33946
34789
|
init_types5();
|
|
33947
34790
|
NullLedgerWriteThrough = class {
|
|
33948
34791
|
constructor(gen, fallback) {
|
|
@@ -34000,7 +34843,7 @@ var init_writeThrough = __esm({
|
|
|
34000
34843
|
}
|
|
34001
34844
|
};
|
|
34002
34845
|
RETRY_MS = [200, 800];
|
|
34003
|
-
|
|
34846
|
+
describe8 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
34004
34847
|
}
|
|
34005
34848
|
});
|
|
34006
34849
|
|
|
@@ -34092,6 +34935,24 @@ var init_residentFleet = __esm({
|
|
|
34092
34935
|
});
|
|
34093
34936
|
|
|
34094
34937
|
// ../../src/providers/openaiCompat.ts
|
|
34938
|
+
function buildOpenAIBody(req) {
|
|
34939
|
+
const messages = [];
|
|
34940
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
34941
|
+
for (const m of req.messages) messages.push(...toOAIMessages(m));
|
|
34942
|
+
const body = {
|
|
34943
|
+
model: req.model,
|
|
34944
|
+
max_tokens: req.maxTokens,
|
|
34945
|
+
messages
|
|
34946
|
+
};
|
|
34947
|
+
if (req.tools && req.tools.length > 0) {
|
|
34948
|
+
body.tools = req.tools.map((t) => ({
|
|
34949
|
+
type: "function",
|
|
34950
|
+
function: { name: t.name, description: t.description, parameters: t.inputSchema }
|
|
34951
|
+
}));
|
|
34952
|
+
if (req.toolChoice) body.tool_choice = { type: "function", function: { name: req.toolChoice.name } };
|
|
34953
|
+
}
|
|
34954
|
+
return body;
|
|
34955
|
+
}
|
|
34095
34956
|
function toOAIMessages(m) {
|
|
34096
34957
|
if (m.role === "assistant") {
|
|
34097
34958
|
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
@@ -34178,20 +35039,7 @@ var init_openaiCompat = __esm({
|
|
|
34178
35039
|
this.apiKey = cfg.apiKeyEnv ? processSecrets.named(cfg.apiKeyEnv) : void 0;
|
|
34179
35040
|
}
|
|
34180
35041
|
async complete(req) {
|
|
34181
|
-
const
|
|
34182
|
-
if (req.system) messages.push({ role: "system", content: req.system });
|
|
34183
|
-
for (const m of req.messages) messages.push(...toOAIMessages(m));
|
|
34184
|
-
const body = {
|
|
34185
|
-
model: req.model,
|
|
34186
|
-
max_tokens: req.maxTokens,
|
|
34187
|
-
messages
|
|
34188
|
-
};
|
|
34189
|
-
if (req.tools && req.tools.length > 0) {
|
|
34190
|
-
body.tools = req.tools.map((t) => ({
|
|
34191
|
-
type: "function",
|
|
34192
|
-
function: { name: t.name, description: t.description, parameters: t.inputSchema }
|
|
34193
|
-
}));
|
|
34194
|
-
}
|
|
35042
|
+
const body = buildOpenAIBody(req);
|
|
34195
35043
|
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
34196
35044
|
method: "POST",
|
|
34197
35045
|
headers: {
|
|
@@ -34683,6 +35531,7 @@ function classifyDocument(mimetype, name) {
|
|
|
34683
35531
|
async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
|
|
34684
35532
|
const images = [];
|
|
34685
35533
|
const skipped = [];
|
|
35534
|
+
const skippedFiles = [];
|
|
34686
35535
|
let bytes = 0;
|
|
34687
35536
|
const candidates = [];
|
|
34688
35537
|
for (const f of files ?? []) {
|
|
@@ -34690,6 +35539,7 @@ async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
|
|
|
34690
35539
|
const url = f.url_private_download ?? f.url_private;
|
|
34691
35540
|
if (!url || !f.mimetype || !IMAGE_TYPES2.has(f.mimetype) || (f.size ?? 0) > MAX_IMAGE_BYTES || candidates.length >= maxImages) {
|
|
34692
35541
|
skipped.push(label);
|
|
35542
|
+
skippedFiles.push(f);
|
|
34693
35543
|
continue;
|
|
34694
35544
|
}
|
|
34695
35545
|
candidates.push({ f, label, url, mediaType: f.mimetype });
|
|
@@ -34703,12 +35553,13 @@ async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
|
|
|
34703
35553
|
const buf2 = downloads[i];
|
|
34704
35554
|
if (!buf2 || buf2.byteLength > MAX_IMAGE_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
|
|
34705
35555
|
skipped.push(label);
|
|
35556
|
+
skippedFiles.push(f);
|
|
34706
35557
|
return;
|
|
34707
35558
|
}
|
|
34708
35559
|
bytes += buf2.byteLength;
|
|
34709
35560
|
images.push({ mediaType, data: buf2.toString("base64"), name: f.name });
|
|
34710
35561
|
});
|
|
34711
|
-
return { images, skipped, bytes };
|
|
35562
|
+
return { images, skipped, skippedFiles, bytes };
|
|
34712
35563
|
}
|
|
34713
35564
|
async function downloadSlackFile(url, label, isLoginPage) {
|
|
34714
35565
|
const token2 = processSecrets.get("SLACK_BOT_TOKEN");
|
|
@@ -34727,6 +35578,7 @@ async function downloadSlackFile(url, label, isLoginPage) {
|
|
|
34727
35578
|
async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
34728
35579
|
const documents = [];
|
|
34729
35580
|
const skipped = [];
|
|
35581
|
+
const skippedFiles = [];
|
|
34730
35582
|
let bytes = 0;
|
|
34731
35583
|
const candidates = [];
|
|
34732
35584
|
for (const f of files ?? []) {
|
|
@@ -34735,6 +35587,7 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
|
34735
35587
|
const kind = classifyDocument(f.mimetype, f.name);
|
|
34736
35588
|
if (!url || !kind || (f.size ?? 0) > MAX_DOCUMENT_BYTES || candidates.length >= maxDocs) {
|
|
34737
35589
|
skipped.push(label);
|
|
35590
|
+
skippedFiles.push(f);
|
|
34738
35591
|
continue;
|
|
34739
35592
|
}
|
|
34740
35593
|
candidates.push({ f, label, url, kind });
|
|
@@ -34748,6 +35601,7 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
|
34748
35601
|
const buf2 = downloads[i];
|
|
34749
35602
|
if (!buf2 || buf2.byteLength > MAX_DOCUMENT_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
|
|
34750
35603
|
skipped.push(label);
|
|
35604
|
+
skippedFiles.push(f);
|
|
34751
35605
|
return;
|
|
34752
35606
|
}
|
|
34753
35607
|
bytes += buf2.byteLength;
|
|
@@ -34757,9 +35611,43 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
|
34757
35611
|
name: f.name
|
|
34758
35612
|
});
|
|
34759
35613
|
});
|
|
34760
|
-
return { documents, skipped, bytes };
|
|
35614
|
+
return { documents, skipped, skippedFiles, bytes };
|
|
34761
35615
|
}
|
|
34762
|
-
|
|
35616
|
+
function stagedFiles(unsupported, opts) {
|
|
35617
|
+
if (!opts.staging) return { staged: [], skipped: unsupported.map(fileLabel) };
|
|
35618
|
+
const staged = [];
|
|
35619
|
+
const skipped = [];
|
|
35620
|
+
let total = 0;
|
|
35621
|
+
for (const f of unsupported) {
|
|
35622
|
+
const label = fileLabel(f);
|
|
35623
|
+
const url = f.url_private_download ?? f.url_private;
|
|
35624
|
+
const size = f.size ?? 0;
|
|
35625
|
+
if (isSecretFile(f.name)) {
|
|
35626
|
+
skipped.push(`${label} \u2014 looks like a credential or key file`);
|
|
35627
|
+
continue;
|
|
35628
|
+
}
|
|
35629
|
+
if (!url || !f.name || size <= 0) {
|
|
35630
|
+
skipped.push(`${label} \u2014 Slack gave no download URL, name or size`);
|
|
35631
|
+
continue;
|
|
35632
|
+
}
|
|
35633
|
+
if (size > MAX_STAGED_FILE_BYTES) {
|
|
35634
|
+
skipped.push(`${label} \u2014 ${size} bytes is over the ${MAX_STAGED_FILE_BYTES}-byte per-file ceiling`);
|
|
35635
|
+
continue;
|
|
35636
|
+
}
|
|
35637
|
+
if (staged.length >= MAX_STAGED_PER_MESSAGE) {
|
|
35638
|
+
skipped.push(`${label} \u2014 more than ${MAX_STAGED_PER_MESSAGE} files on one message`);
|
|
35639
|
+
continue;
|
|
35640
|
+
}
|
|
35641
|
+
if (total + size > opts.maxBytesPerMessage) {
|
|
35642
|
+
skipped.push(`${label} \u2014 the message's files exceed the ${opts.maxBytesPerMessage}-byte budget`);
|
|
35643
|
+
continue;
|
|
35644
|
+
}
|
|
35645
|
+
total += size;
|
|
35646
|
+
staged.push({ name: f.name, size, type: f.mimetype ?? "application/octet-stream", url, messageId: opts.messageId });
|
|
35647
|
+
}
|
|
35648
|
+
return { staged, skipped };
|
|
35649
|
+
}
|
|
35650
|
+
var IMAGE_TYPES2, MAX_IMAGE_BYTES, MAX_IMAGES_PER_MESSAGE, MAX_HISTORY_IMAGES, MAX_HISTORY_IMAGE_BYTES, PDF_TYPE2, TEXT_MIME_TYPES, GENERIC_MIME_TYPES, TEXT_EXTENSIONS, SECRET_FILE_EXTENSIONS, SECRET_FILE_PREFIXES, MAX_DOCUMENT_BYTES, MAX_DOCS_PER_MESSAGE, MAX_HISTORY_DOCS, MAX_HISTORY_DOCUMENT_BYTES, fileLabel, MAX_STAGED_FILE_BYTES, MAX_STAGED_PER_MESSAGE;
|
|
34763
35651
|
var init_attachments = __esm({
|
|
34764
35652
|
"../../src/channels/slack/attachments.ts"() {
|
|
34765
35653
|
"use strict";
|
|
@@ -34842,6 +35730,8 @@ var init_attachments = __esm({
|
|
|
34842
35730
|
MAX_HISTORY_DOCS = 20;
|
|
34843
35731
|
MAX_HISTORY_DOCUMENT_BYTES = 32 * 1024 * 1024;
|
|
34844
35732
|
fileLabel = (f) => `${f.name ?? f.id ?? "file"} (${f.mimetype ?? "unknown type"})`;
|
|
35733
|
+
MAX_STAGED_FILE_BYTES = 1073741824;
|
|
35734
|
+
MAX_STAGED_PER_MESSAGE = 10;
|
|
34845
35735
|
}
|
|
34846
35736
|
});
|
|
34847
35737
|
|
|
@@ -35480,7 +36370,13 @@ async function handle(deps, { client, statusClient }, ev) {
|
|
|
35480
36370
|
const receivedAt = systemClock();
|
|
35481
36371
|
const trace = startRequestRoot(deps, { channel: "slack", receivedAt, originAt: tsMs(ev.ts) });
|
|
35482
36372
|
try {
|
|
35483
|
-
const received = await trace.root.span(
|
|
36373
|
+
const received = await trace.root.span(
|
|
36374
|
+
"slack.receive",
|
|
36375
|
+
(span) => receiveSlackMessage(client, ev, span, {
|
|
36376
|
+
staging: deps.artifacts !== void 0,
|
|
36377
|
+
maxBytesPerMessage: deps.config.config.artifacts?.inbound?.maxBytesPerMessage ?? ARTIFACT_DEFAULTS.maxBytesPerMessage
|
|
36378
|
+
})
|
|
36379
|
+
);
|
|
35484
36380
|
if (!received) {
|
|
35485
36381
|
trace.root.end("ok", { status: "refused" });
|
|
35486
36382
|
return;
|
|
@@ -35495,7 +36391,7 @@ async function handle(deps, { client, statusClient }, ev) {
|
|
|
35495
36391
|
if (!trace.root.ended) trace.root.end("error", { status: "failed" });
|
|
35496
36392
|
}
|
|
35497
36393
|
}
|
|
35498
|
-
async function receiveSlackMessage(client, ev, span) {
|
|
36394
|
+
async function receiveSlackMessage(client, ev, span, policy) {
|
|
35499
36395
|
const drop = await dedupeDelivery(client, ev);
|
|
35500
36396
|
if (drop) {
|
|
35501
36397
|
console.log(`[redelivery] ${ev.channel}:${ev.ts} dropped: ${drop}`);
|
|
@@ -35509,12 +36405,13 @@ async function receiveSlackMessage(client, ev, span) {
|
|
|
35509
36405
|
const delayNote = ev.caughtUp ? new SlackIO(client, ev).reply(catchUpDelayNote(ev.ts, systemClock())).catch((err2) => {
|
|
35510
36406
|
console.error(`[catch-up] ${ev.channel}:${ev.ts} delay note failed: ${err2.message}`);
|
|
35511
36407
|
}) : Promise.resolve();
|
|
35512
|
-
const [{ images,
|
|
36408
|
+
const [{ images, skippedFiles: imagePassSkipped }, { documents, skippedFiles: documentPassSkipped }] = await Promise.all([
|
|
35513
36409
|
fetchImages(ev.files, MAX_IMAGES_PER_MESSAGE),
|
|
35514
36410
|
fetchDocuments(ev.files, MAX_DOCS_PER_MESSAGE),
|
|
35515
36411
|
delayNote
|
|
35516
36412
|
]);
|
|
35517
|
-
const
|
|
36413
|
+
const unsupported = imagePassSkipped.filter((f) => documentPassSkipped.includes(f));
|
|
36414
|
+
const { staged, skipped } = stagedFiles(unsupported, { ...policy, messageId: ev.ts });
|
|
35518
36415
|
const [channelName, userName, team] = await Promise.all([
|
|
35519
36416
|
resolveChannelName(client, ev.channel),
|
|
35520
36417
|
resolveUserName(client, ev.user),
|
|
@@ -35532,7 +36429,8 @@ async function receiveSlackMessage(client, ev, span) {
|
|
|
35532
36429
|
userName,
|
|
35533
36430
|
sourceUrl: team ? slackPermalink(team, ev.channel, ev.ts, ev.threadTs) : void 0,
|
|
35534
36431
|
images: images.length > 0 ? images : void 0,
|
|
35535
|
-
documents: documents.length > 0 ? documents : void 0
|
|
36432
|
+
documents: documents.length > 0 ? documents : void 0,
|
|
36433
|
+
...staged.length > 0 ? { staged } : {}
|
|
35536
36434
|
};
|
|
35537
36435
|
}
|
|
35538
36436
|
function resumeSlackIO(client, run2, opts = {}) {
|
|
@@ -35599,6 +36497,7 @@ var init_slack = __esm({
|
|
|
35599
36497
|
init_statusCard();
|
|
35600
36498
|
init_slackCatchUp();
|
|
35601
36499
|
init_secrets2();
|
|
36500
|
+
init_config();
|
|
35602
36501
|
init_slackCatchUpStatus();
|
|
35603
36502
|
init_slackSocketStatus();
|
|
35604
36503
|
init_slackTriggers();
|
|
@@ -35664,6 +36563,30 @@ ${file.text}`));
|
|
|
35664
36563
|
initial_comment: mdToMrkdwn(file.lead)
|
|
35665
36564
|
});
|
|
35666
36565
|
}
|
|
36566
|
+
/** The external-upload flow (docs/reference/specs/slack-channel.md item 10,
|
|
36567
|
+
* record 0033): `files.getUploadURLExternal` mints a URL for exactly
|
|
36568
|
+
* `size` bytes under `name`; the run's container POSTs the file there; then
|
|
36569
|
+
* `complete` shares it into this thread with the lead as the comment
|
|
36570
|
+
* (`files.completeUploadExternal`). The bot never sees the bytes. A ticket
|
|
36571
|
+
* Slack answers without a URL or id is refused by name. */
|
|
36572
|
+
async uploadTicket(file) {
|
|
36573
|
+
const minted = await this.client.files.getUploadURLExternal({ filename: file.name, length: file.size });
|
|
36574
|
+
const url = minted.upload_url;
|
|
36575
|
+
const id = minted.file_id;
|
|
36576
|
+
if (!url || !id)
|
|
36577
|
+
throw new Error(`files.getUploadURLExternal answered without an upload_url and file_id for ${file.name}`);
|
|
36578
|
+
return {
|
|
36579
|
+
url,
|
|
36580
|
+
complete: async (lead) => {
|
|
36581
|
+
await this.client.files.completeUploadExternal({
|
|
36582
|
+
files: [{ id, title: file.name }],
|
|
36583
|
+
channel_id: this.ev.channel,
|
|
36584
|
+
thread_ts: this.ev.threadTs,
|
|
36585
|
+
initial_comment: mdToMrkdwn(lead)
|
|
36586
|
+
});
|
|
36587
|
+
}
|
|
36588
|
+
};
|
|
36589
|
+
}
|
|
35667
36590
|
async post(mrkdwn) {
|
|
35668
36591
|
for (const chunk of chunkText(mrkdwn, SLACK_MSG_LIMIT)) {
|
|
35669
36592
|
await this.client.chat.postMessage({
|
|
@@ -36385,31 +37308,6 @@ var init_mcp3 = __esm({
|
|
|
36385
37308
|
}
|
|
36386
37309
|
});
|
|
36387
37310
|
|
|
36388
|
-
// ../../src/artifacts/contentType.ts
|
|
36389
|
-
var INLINE_IMAGE_TYPES;
|
|
36390
|
-
var init_contentType = __esm({
|
|
36391
|
-
"../../src/artifacts/contentType.ts"() {
|
|
36392
|
-
"use strict";
|
|
36393
|
-
INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
36394
|
-
}
|
|
36395
|
-
});
|
|
36396
|
-
|
|
36397
|
-
// ../../src/artifacts/keys.ts
|
|
36398
|
-
function safeBasename(name) {
|
|
36399
|
-
const segments2 = name.split(/[\\/]+/).filter((s) => s.length > 0);
|
|
36400
|
-
const last = segments2[segments2.length - 1] ?? "";
|
|
36401
|
-
const reduced = last.replace(SAFE, "_").replace(/\.{2,}/g, "_").replace(/_+/g, "_");
|
|
36402
|
-
if (reduced === "" || /^[._-]+$/.test(reduced)) return "file";
|
|
36403
|
-
return reduced;
|
|
36404
|
-
}
|
|
36405
|
-
var SAFE;
|
|
36406
|
-
var init_keys = __esm({
|
|
36407
|
-
"../../src/artifacts/keys.ts"() {
|
|
36408
|
-
"use strict";
|
|
36409
|
-
SAFE = /[^A-Za-z0-9._-]+/g;
|
|
36410
|
-
}
|
|
36411
|
-
});
|
|
36412
|
-
|
|
36413
37311
|
// ../../src/channels/scheduledPanel.ts
|
|
36414
37312
|
function buildScheduledRows(schedules, firings, liveRuns, now) {
|
|
36415
37313
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -37226,7 +38124,7 @@ var init_liveView = __esm({
|
|
|
37226
38124
|
// ../../src/channels/webAssets.ts
|
|
37227
38125
|
import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync3 } from "node:fs";
|
|
37228
38126
|
import { join as join13 } from "node:path";
|
|
37229
|
-
function
|
|
38127
|
+
function contentTypeFor2(path) {
|
|
37230
38128
|
const dot = path.lastIndexOf(".");
|
|
37231
38129
|
return (dot === -1 ? void 0 : CONTENT_TYPES[path.slice(dot)]) ?? "application/octet-stream";
|
|
37232
38130
|
}
|
|
@@ -37271,7 +38169,7 @@ function loadWebAssets(distDir) {
|
|
|
37271
38169
|
return true;
|
|
37272
38170
|
}
|
|
37273
38171
|
res.writeHead(200, {
|
|
37274
|
-
"content-type":
|
|
38172
|
+
"content-type": contentTypeFor2(path),
|
|
37275
38173
|
"content-length": String(body.byteLength),
|
|
37276
38174
|
"cache-control": "public, max-age=31536000, immutable"
|
|
37277
38175
|
});
|
|
@@ -38157,6 +39055,16 @@ function turnRows(idx, message2, opts = {}) {
|
|
|
38157
39055
|
const refBytes = opts.attachmentRefBytes ?? ATTACHMENT_REF_BYTES;
|
|
38158
39056
|
const rows = [];
|
|
38159
39057
|
const attachments = [];
|
|
39058
|
+
if (isCompaction(message2)) {
|
|
39059
|
+
const json2 = JSON.stringify({ compaction: message2.compaction });
|
|
39060
|
+
const bytes = utf8ByteLength(json2);
|
|
39061
|
+
if (bytes > partBytes) {
|
|
39062
|
+
throw new Error(
|
|
39063
|
+
`transcript: the compaction row at turn ${idx} is ${bytes} bytes, over the ${partBytes} row budget`
|
|
39064
|
+
);
|
|
39065
|
+
}
|
|
39066
|
+
return { rows: [{ idx, part: 0, json: json2 }], attachments };
|
|
39067
|
+
}
|
|
38160
39068
|
message2.content.forEach((part, i) => {
|
|
38161
39069
|
let stored = part;
|
|
38162
39070
|
if (hasData(part) && part.data.length > refBytes) {
|
|
@@ -38190,49 +39098,69 @@ function chunkRows(rows, maxBytes) {
|
|
|
38190
39098
|
if (current.length > 0) chunks.push(current);
|
|
38191
39099
|
return chunks;
|
|
38192
39100
|
}
|
|
38193
|
-
function assembleTranscript(rows, attachments) {
|
|
39101
|
+
function assembleTranscript(rows, attachments, base = 0) {
|
|
38194
39102
|
const byRef = new Map(attachments.map((a) => [a.ref, a]));
|
|
38195
39103
|
const byTurn = /* @__PURE__ */ new Map();
|
|
38196
39104
|
for (const row of rows) {
|
|
38197
|
-
|
|
38198
|
-
|
|
39105
|
+
const idx = row.idx - base;
|
|
39106
|
+
let parts = byTurn.get(idx);
|
|
39107
|
+
if (!parts) byTurn.set(idx, parts = /* @__PURE__ */ new Map());
|
|
38199
39108
|
parts.set(row.part, JSON.parse(row.json));
|
|
38200
39109
|
}
|
|
38201
39110
|
const messages = [];
|
|
39111
|
+
const compactions = [];
|
|
39112
|
+
const messageIndexOf = /* @__PURE__ */ new Map();
|
|
39113
|
+
const gap = (why) => ({
|
|
39114
|
+
complete: false,
|
|
39115
|
+
turns: messages.length + compactions.length,
|
|
39116
|
+
messages,
|
|
39117
|
+
compactions,
|
|
39118
|
+
gap: why
|
|
39119
|
+
});
|
|
38202
39120
|
const turnCount = byTurn.size === 0 ? 0 : Math.max(...byTurn.keys()) + 1;
|
|
38203
39121
|
for (let idx = 0; idx < turnCount; idx++) {
|
|
38204
39122
|
const parts = byTurn.get(idx);
|
|
38205
|
-
if (!parts) return
|
|
39123
|
+
if (!parts) return gap(`turn ${idx} is missing`);
|
|
39124
|
+
const first = parts.get(0);
|
|
39125
|
+
if (first && "compaction" in first) {
|
|
39126
|
+
compactions.push({ before: messages.length, entry: first.compaction });
|
|
39127
|
+
continue;
|
|
39128
|
+
}
|
|
38206
39129
|
const content = [];
|
|
38207
39130
|
let role;
|
|
38208
39131
|
const partCount = Math.max(...parts.keys()) + 1;
|
|
38209
39132
|
for (let p = 0; p < partCount; p++) {
|
|
38210
39133
|
const stored = parts.get(p);
|
|
38211
|
-
if (!stored)
|
|
38212
|
-
return { complete: false, turns: messages.length, messages, gap: `turn ${idx} is missing part ${p}` };
|
|
39134
|
+
if (!stored || "compaction" in stored) return gap(`turn ${idx} is missing part ${p}`);
|
|
38213
39135
|
role = stored.role;
|
|
38214
39136
|
const part = stored.part;
|
|
38215
39137
|
if (part.dataRef !== void 0) {
|
|
38216
39138
|
const att = byRef.get(part.dataRef);
|
|
38217
|
-
if (!att)
|
|
38218
|
-
return { complete: false, turns: messages.length, messages, gap: `attachment ${part.dataRef} is missing` };
|
|
39139
|
+
if (!att) return gap(`attachment ${part.dataRef} is missing`);
|
|
38219
39140
|
const { dataRef: _ref, ...rest } = part;
|
|
38220
39141
|
content.push({ ...rest, data: att.data });
|
|
38221
39142
|
} else {
|
|
38222
39143
|
content.push(part);
|
|
38223
39144
|
}
|
|
38224
39145
|
}
|
|
39146
|
+
messageIndexOf.set(idx, messages.length);
|
|
38225
39147
|
messages.push({ role: role ?? "user", content });
|
|
38226
39148
|
}
|
|
38227
|
-
|
|
39149
|
+
for (const c of compactions) {
|
|
39150
|
+
if (c.entry.keptFrom === void 0) continue;
|
|
39151
|
+
const kept = messageIndexOf.get(c.entry.keptFrom - base);
|
|
39152
|
+
if (kept !== void 0) c.keptBefore = kept;
|
|
39153
|
+
}
|
|
39154
|
+
return { complete: true, turns: messages.length + compactions.length, messages, compactions };
|
|
38228
39155
|
}
|
|
38229
|
-
var attachmentRef;
|
|
39156
|
+
var attachmentRef, isCompaction;
|
|
38230
39157
|
var init_transcript = __esm({
|
|
38231
39158
|
"../../src/core/runLedger/transcript.ts"() {
|
|
38232
39159
|
"use strict";
|
|
38233
39160
|
init_runRecord();
|
|
38234
39161
|
init_types5();
|
|
38235
39162
|
attachmentRef = (idx, part) => `t${idx}p${part}`;
|
|
39163
|
+
isCompaction = (v) => "compaction" in v;
|
|
38236
39164
|
}
|
|
38237
39165
|
});
|
|
38238
39166
|
|
|
@@ -38247,6 +39175,9 @@ function buildRunLedger(cfg, secrets, deps = {}) {
|
|
|
38247
39175
|
baseUrl: worker.baseUrl,
|
|
38248
39176
|
token: token2.reveal(),
|
|
38249
39177
|
storeKey: RUN_STORE_KEY,
|
|
39178
|
+
// The same clamped policy the run store proposes, so the session logs and
|
|
39179
|
+
// the run records are bounded by one configuration.
|
|
39180
|
+
sessionLogMaxBytes: retentionPolicyOf(cfg).sessionLogMaxBytes,
|
|
38250
39181
|
...deps.fetch ? { fetch: deps.fetch } : {}
|
|
38251
39182
|
});
|
|
38252
39183
|
}
|
|
@@ -38255,7 +39186,9 @@ var init_runLedgerWorker = __esm({
|
|
|
38255
39186
|
"../../src/core/runLedgerWorker.ts"() {
|
|
38256
39187
|
"use strict";
|
|
38257
39188
|
init_runRecord();
|
|
39189
|
+
init_runStore();
|
|
38258
39190
|
init_runStoreWorker();
|
|
39191
|
+
init_sessionLog();
|
|
38259
39192
|
init_transcript();
|
|
38260
39193
|
init_types5();
|
|
38261
39194
|
WorkerRunLedger = class {
|
|
@@ -38319,41 +39252,94 @@ var init_runLedgerWorker = __esm({
|
|
|
38319
39252
|
}
|
|
38320
39253
|
};
|
|
38321
39254
|
}
|
|
38322
|
-
await this.post("/runs/transcript/owner", { runId: req.runId, gen: req.gen });
|
|
38323
39255
|
return { ok: true };
|
|
38324
39256
|
}
|
|
38325
|
-
|
|
39257
|
+
/** The write route and its body for a run's rows: the session log when the
|
|
39258
|
+
* run has one, the run's own transcript object otherwise. */
|
|
39259
|
+
target(runId3, gen, session) {
|
|
39260
|
+
if (session === void 0) return { path: "/runs/transcript/write", body: { runId: runId3, gen } };
|
|
39261
|
+
this.checkSessionKey(session);
|
|
39262
|
+
return { path: "/runs/session/write", body: { key: session, gen } };
|
|
39263
|
+
}
|
|
39264
|
+
async writeTurns(runId3, gen, turns, session) {
|
|
38326
39265
|
const rows = [];
|
|
38327
39266
|
const attachments = [];
|
|
38328
39267
|
for (const t of turns) {
|
|
38329
|
-
const out = turnRows(t.idx, t.message);
|
|
39268
|
+
const out = turnRows(t.idx, "message" in t ? t.message : { compaction: t.compaction });
|
|
38330
39269
|
rows.push(...out.rows);
|
|
38331
39270
|
attachments.push(...out.attachments);
|
|
38332
39271
|
}
|
|
39272
|
+
const { path, body } = this.target(runId3, gen, session);
|
|
38333
39273
|
for (const a of attachments) {
|
|
38334
|
-
const r = await this.post(
|
|
39274
|
+
const r = await this.post(path, { ...body, rows: [], attachments: [a] });
|
|
38335
39275
|
const f = this.fenceResult(r);
|
|
38336
39276
|
if (!f.ok) return f;
|
|
38337
39277
|
}
|
|
38338
39278
|
const chunks = chunkRows(rows, TRANSCRIPT_REQUEST_BYTES - 4096);
|
|
38339
39279
|
for (const chunk of chunks.length ? chunks : [[]]) {
|
|
38340
39280
|
if (chunk.length === 0 && attachments.length > 0) continue;
|
|
38341
|
-
const r = await this.post(
|
|
39281
|
+
const r = await this.post(path, { ...body, rows: chunk, attachments: [] });
|
|
38342
39282
|
const f = this.fenceResult(r);
|
|
38343
39283
|
if (!f.ok) return f;
|
|
38344
39284
|
}
|
|
38345
39285
|
return { ok: true };
|
|
38346
39286
|
}
|
|
38347
|
-
async seed(runId3, gen, turns) {
|
|
39287
|
+
async seed(runId3, gen, turns, session) {
|
|
38348
39288
|
this.checkIds(runId3, gen);
|
|
38349
|
-
return this.writeTurns(runId3, gen, turns);
|
|
39289
|
+
return this.writeTurns(runId3, gen, turns, session);
|
|
38350
39290
|
}
|
|
38351
|
-
async step(runId3, gen, record2, turns) {
|
|
39291
|
+
async step(runId3, gen, record2, turns, session) {
|
|
38352
39292
|
this.checkIds(runId3, gen);
|
|
38353
|
-
const written = await this.writeTurns(runId3, gen, turns);
|
|
39293
|
+
const written = await this.writeTurns(runId3, gen, turns, session);
|
|
38354
39294
|
if (!written.ok) return written;
|
|
38355
39295
|
return this.fenceResult(await this.post("/runs/step", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 }));
|
|
38356
39296
|
}
|
|
39297
|
+
checkSessionKey(key) {
|
|
39298
|
+
if (!SESSION_KEY_PATTERN.test(key))
|
|
39299
|
+
throw new PermanentStoreError(`run ledger: malformed session key ${JSON.stringify(key)}`);
|
|
39300
|
+
}
|
|
39301
|
+
async sessionTail(key) {
|
|
39302
|
+
this.checkSessionKey(key);
|
|
39303
|
+
const r = await this.post("/runs/session/tail", { key });
|
|
39304
|
+
return typeof r.data.next === "number" ? r.data.next : 0;
|
|
39305
|
+
}
|
|
39306
|
+
async claimSession(key, runId3, gen, maxBytes) {
|
|
39307
|
+
this.checkSessionKey(key);
|
|
39308
|
+
this.checkIds(runId3, gen);
|
|
39309
|
+
await this.post("/runs/session/owner", {
|
|
39310
|
+
key,
|
|
39311
|
+
runId: runId3,
|
|
39312
|
+
gen,
|
|
39313
|
+
maxBytes: maxBytes ?? this.opts.sessionLogMaxBytes ?? DEFAULT_SESSION_LOG_MAX_BYTES
|
|
39314
|
+
});
|
|
39315
|
+
}
|
|
39316
|
+
async releaseSession(key, runId3, gen) {
|
|
39317
|
+
this.checkSessionKey(key);
|
|
39318
|
+
this.checkIds(runId3, gen);
|
|
39319
|
+
return this.fenceResult(await this.post("/runs/session/clear-owner", { key, runId: runId3, gen }));
|
|
39320
|
+
}
|
|
39321
|
+
async readSession(key, from, to) {
|
|
39322
|
+
this.checkSessionKey(key);
|
|
39323
|
+
const r = await this.post("/runs/session/read", { key, from, ...to !== void 0 ? { to } : {} });
|
|
39324
|
+
return assembleTranscript(
|
|
39325
|
+
Array.isArray(r.data.rows) ? r.data.rows : [],
|
|
39326
|
+
Array.isArray(r.data.attachments) ? r.data.attachments : [],
|
|
39327
|
+
from
|
|
39328
|
+
);
|
|
39329
|
+
}
|
|
39330
|
+
async readSessionTail(key, maxBytes) {
|
|
39331
|
+
this.checkSessionKey(key);
|
|
39332
|
+
const r = await this.post("/runs/session/read-tail", { key, maxBytes });
|
|
39333
|
+
const from = typeof r.data.from === "number" ? r.data.from : 0;
|
|
39334
|
+
return {
|
|
39335
|
+
from,
|
|
39336
|
+
transcript: assembleTranscript(
|
|
39337
|
+
Array.isArray(r.data.rows) ? r.data.rows : [],
|
|
39338
|
+
Array.isArray(r.data.attachments) ? r.data.attachments : [],
|
|
39339
|
+
from
|
|
39340
|
+
)
|
|
39341
|
+
};
|
|
39342
|
+
}
|
|
38357
39343
|
async heartbeat(runId3, gen, leaseMs) {
|
|
38358
39344
|
this.checkIds(runId3, gen);
|
|
38359
39345
|
const r = await this.post("/runs/heartbeat", { storeKey: this.opts.storeKey, runId: runId3, gen, leaseMs });
|
|
@@ -38405,22 +39391,23 @@ var init_runLedgerWorker = __esm({
|
|
|
38405
39391
|
const r = await this.post("/runs/finish", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 });
|
|
38406
39392
|
const f = this.fenceResult(r);
|
|
38407
39393
|
if (!f.ok) return f;
|
|
38408
|
-
|
|
38409
|
-
|
|
39394
|
+
if (record2.session)
|
|
39395
|
+
await this.post("/runs/session/clear-owner", { key: record2.session.key, runId: runId3, gen }).catch(() => {
|
|
39396
|
+
});
|
|
38410
39397
|
return { ok: true, stored: r.data.stored === true };
|
|
38411
39398
|
}
|
|
38412
39399
|
async abandon(runId3, gen) {
|
|
38413
39400
|
this.checkIds(runId3, gen);
|
|
38414
|
-
|
|
38415
|
-
if (!f.ok) return f;
|
|
38416
|
-
await this.post("/runs/transcript/clear", { runId: runId3 }).catch(() => {
|
|
38417
|
-
});
|
|
38418
|
-
return f;
|
|
39401
|
+
return this.fenceResult(await this.post("/runs/abandon", { storeKey: this.opts.storeKey, runId: runId3, gen }));
|
|
38419
39402
|
}
|
|
38420
39403
|
async reclaim(gen, now, leaseMs) {
|
|
38421
39404
|
const r = await this.post("/runs/reclaim", { storeKey: this.opts.storeKey, gen, now, leaseMs });
|
|
38422
39405
|
const runs = Array.isArray(r.data.runs) ? r.data.runs : [];
|
|
38423
|
-
for (const run2 of runs)
|
|
39406
|
+
for (const run2 of runs) {
|
|
39407
|
+
const session = run2.row.meta?.session;
|
|
39408
|
+
if (session) await this.claimSession(session.key, run2.row.runId, gen);
|
|
39409
|
+
else await this.post("/runs/transcript/owner", { runId: run2.row.runId, gen });
|
|
39410
|
+
}
|
|
38424
39411
|
return runs;
|
|
38425
39412
|
}
|
|
38426
39413
|
async listLive() {
|
|
@@ -38468,6 +39455,116 @@ var init_decisions = __esm({
|
|
|
38468
39455
|
}
|
|
38469
39456
|
});
|
|
38470
39457
|
|
|
39458
|
+
// ../../src/core/runLedger/resume.ts
|
|
39459
|
+
function transcriptSource(meta) {
|
|
39460
|
+
return meta.session ? { kind: "session", key: meta.session.key, from: meta.session.seedFrom } : { kind: "run" };
|
|
39461
|
+
}
|
|
39462
|
+
function settlementFor(toolUse, tools) {
|
|
39463
|
+
const tool = tools.get(toolUse.name);
|
|
39464
|
+
if (!tool) {
|
|
39465
|
+
return {
|
|
39466
|
+
toolUse,
|
|
39467
|
+
action: "synthetic",
|
|
39468
|
+
text: `Tool ${toolUse.name} is not available after the bot restarted; continue without it.`
|
|
39469
|
+
};
|
|
39470
|
+
}
|
|
39471
|
+
if (tool.sideEffectFree || RERUN_SAFE_TOOLS.has(toolUse.name)) return { toolUse, action: "rerun" };
|
|
39472
|
+
return {
|
|
39473
|
+
toolUse,
|
|
39474
|
+
action: "synthetic",
|
|
39475
|
+
text: `The bot restarted while this ${toolUse.name} call was in flight; its effects are unknown \u2014 re-check them before re-running it.`
|
|
39476
|
+
};
|
|
39477
|
+
}
|
|
39478
|
+
function planResume(input) {
|
|
39479
|
+
const { transcript, lastStep } = input;
|
|
39480
|
+
if (!lastStep) return { kind: "interrupted", why: "no step record: killed before its conversation was stored" };
|
|
39481
|
+
if (!transcript.complete) return { kind: "interrupted", why: `transcript incomplete: ${transcript.gap}` };
|
|
39482
|
+
const completeness = transcriptCompleteness({
|
|
39483
|
+
lastStep,
|
|
39484
|
+
seedTurns: lastStep.turnIndex,
|
|
39485
|
+
transcriptTurns: transcript.turns
|
|
39486
|
+
});
|
|
39487
|
+
if (completeness.kind === "interrupted") return completeness;
|
|
39488
|
+
const tools = /* @__PURE__ */ new Map();
|
|
39489
|
+
for (const t of input.tools) tools.set(t.name, t);
|
|
39490
|
+
const messages = transcript.messages;
|
|
39491
|
+
const last = messages[messages.length - 1];
|
|
39492
|
+
const calls = toolUsesOf(last);
|
|
39493
|
+
if (completeness.kind === "resume") {
|
|
39494
|
+
if (lastStep.inFlight.length === 0) {
|
|
39495
|
+
if (last?.role !== "user") {
|
|
39496
|
+
return {
|
|
39497
|
+
kind: "interrupted",
|
|
39498
|
+
why: "transcript ends with an assistant turn but the last step recorded nothing in flight"
|
|
39499
|
+
};
|
|
39500
|
+
}
|
|
39501
|
+
return {
|
|
39502
|
+
kind: "resume",
|
|
39503
|
+
messages,
|
|
39504
|
+
compactions: transcript.compactions,
|
|
39505
|
+
settlements: [],
|
|
39506
|
+
stepRecorded: true,
|
|
39507
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39508
|
+
step: lastStep.step,
|
|
39509
|
+
turn: lastStep.turn,
|
|
39510
|
+
iteration: lastStep.iteration,
|
|
39511
|
+
remainingMs: lastStep.remainingMs
|
|
39512
|
+
};
|
|
39513
|
+
}
|
|
39514
|
+
const recorded = new Set(lastStep.inFlight.map((c) => c.callId));
|
|
39515
|
+
if (calls.length === 0 || calls.length !== recorded.size || !calls.every((c) => recorded.has(c.id))) {
|
|
39516
|
+
return {
|
|
39517
|
+
kind: "interrupted",
|
|
39518
|
+
why: `the last step's calls in flight (${[...recorded].join(", ")}) do not match the transcript's last assistant turn`
|
|
39519
|
+
};
|
|
39520
|
+
}
|
|
39521
|
+
return {
|
|
39522
|
+
kind: "resume",
|
|
39523
|
+
messages,
|
|
39524
|
+
compactions: transcript.compactions,
|
|
39525
|
+
settlements: calls.map((c) => settlementFor(c, tools)),
|
|
39526
|
+
stepRecorded: true,
|
|
39527
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39528
|
+
step: lastStep.step,
|
|
39529
|
+
turn: lastStep.turn,
|
|
39530
|
+
iteration: lastStep.iteration,
|
|
39531
|
+
remainingMs: lastStep.remainingMs
|
|
39532
|
+
};
|
|
39533
|
+
}
|
|
39534
|
+
if (calls.length === 0) {
|
|
39535
|
+
return { kind: "interrupted", why: "the next step's turns landed but its assistant turn has no tool calls" };
|
|
39536
|
+
}
|
|
39537
|
+
const bookkeepingOnly = calls.every((c) => c.name === "update_status");
|
|
39538
|
+
return {
|
|
39539
|
+
kind: "resume",
|
|
39540
|
+
messages,
|
|
39541
|
+
compactions: transcript.compactions,
|
|
39542
|
+
settlements: calls.map((c) => ({ toolUse: c, action: "rerun" })),
|
|
39543
|
+
stepRecorded: false,
|
|
39544
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39545
|
+
step: lastStep.step + 1,
|
|
39546
|
+
turn: lastStep.turn + (bookkeepingOnly ? 0 : 1),
|
|
39547
|
+
iteration: lastStep.iteration + 1,
|
|
39548
|
+
remainingMs: lastStep.remainingMs
|
|
39549
|
+
};
|
|
39550
|
+
}
|
|
39551
|
+
var RERUN_SAFE_TOOLS, toolUsesOf;
|
|
39552
|
+
var init_resume = __esm({
|
|
39553
|
+
"../../src/core/runLedger/resume.ts"() {
|
|
39554
|
+
"use strict";
|
|
39555
|
+
init_decisions();
|
|
39556
|
+
RERUN_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
39557
|
+
"write_file",
|
|
39558
|
+
"update_status",
|
|
39559
|
+
"submit_verdict",
|
|
39560
|
+
"submit_pr_description",
|
|
39561
|
+
"submit_handoff",
|
|
39562
|
+
"submit_dispositions"
|
|
39563
|
+
]);
|
|
39564
|
+
toolUsesOf = (message2) => message2?.role === "assistant" ? message2.content.filter((p) => p.type === "tool_use") : [];
|
|
39565
|
+
}
|
|
39566
|
+
});
|
|
39567
|
+
|
|
38471
39568
|
// ../../src/core/boot.ts
|
|
38472
39569
|
function closureNote(agent, prUrl2) {
|
|
38473
39570
|
if (agent === "ship") return shipInterruptedNote(prUrl2);
|
|
@@ -38491,7 +39588,7 @@ async function reclaimRuns(opts) {
|
|
|
38491
39588
|
reclaimed = await ledger.reclaim(gen, now(), LEASE_MS);
|
|
38492
39589
|
} catch (err2) {
|
|
38493
39590
|
warn(
|
|
38494
|
-
err2 instanceof RouteMissingError ? "[reclaim] state Worker has no run-ledger routes \u2014 nothing to take over" : `[reclaim] reclaim failed: ${
|
|
39591
|
+
err2 instanceof RouteMissingError ? "[reclaim] state Worker has no run-ledger routes \u2014 nothing to take over" : `[reclaim] reclaim failed: ${describe9(err2)} \u2014 runs the last generation left stay on the ledger until the next boot`
|
|
38495
39592
|
);
|
|
38496
39593
|
return outcome;
|
|
38497
39594
|
}
|
|
@@ -38515,7 +39612,7 @@ async function reclaimRuns(opts) {
|
|
|
38515
39612
|
status3 = "interrupted";
|
|
38516
39613
|
why = "reserved at admission without its request: nothing to restart from";
|
|
38517
39614
|
} else {
|
|
38518
|
-
const verdict = await completenessVerdict(ledger, row
|
|
39615
|
+
const verdict = await completenessVerdict(ledger, row, run2.lastStep);
|
|
38519
39616
|
if (verdict.resumable && run2.lastStep) {
|
|
38520
39617
|
const events2 = await ledger.readEvents(row.runId);
|
|
38521
39618
|
outcome.resumable.push({
|
|
@@ -38558,8 +39655,8 @@ async function reclaimRuns(opts) {
|
|
|
38558
39655
|
`[reclaim] ${row.runId} ${row.threadKey} closed ${status3} (from ${run2.reclaimedFrom}; ${why}; ${events.length} event(s))`
|
|
38559
39656
|
);
|
|
38560
39657
|
} catch (err2) {
|
|
38561
|
-
outcome.failed.push({ runId: row.runId, error:
|
|
38562
|
-
warn(`[reclaim] ${row.runId} ${row.threadKey}: ${
|
|
39658
|
+
outcome.failed.push({ runId: row.runId, error: describe9(err2) });
|
|
39659
|
+
warn(`[reclaim] ${row.runId} ${row.threadKey}: ${describe9(err2)} \u2014 left on the ledger for the next boot`);
|
|
38563
39660
|
}
|
|
38564
39661
|
}
|
|
38565
39662
|
try {
|
|
@@ -38575,7 +39672,7 @@ async function reclaimRuns(opts) {
|
|
|
38575
39672
|
});
|
|
38576
39673
|
}
|
|
38577
39674
|
} catch (err2) {
|
|
38578
|
-
warn(`[reclaim] listing live runs failed: ${
|
|
39675
|
+
warn(`[reclaim] listing live runs failed: ${describe9(err2)} \u2014 the card sweep runs without the ledger's guard`);
|
|
38579
39676
|
}
|
|
38580
39677
|
if (reclaimed.length > 0 || outcome.liveElsewhere.length > 0) {
|
|
38581
39678
|
log(
|
|
@@ -38597,7 +39694,7 @@ function startReclaimSweep(opts) {
|
|
|
38597
39694
|
await opts.onOutcome(outcome);
|
|
38598
39695
|
}
|
|
38599
39696
|
} catch (err2) {
|
|
38600
|
-
warn(`[reclaim] sweep failed: ${
|
|
39697
|
+
warn(`[reclaim] sweep failed: ${describe9(err2)}`);
|
|
38601
39698
|
} finally {
|
|
38602
39699
|
running = false;
|
|
38603
39700
|
}
|
|
@@ -38616,13 +39713,14 @@ async function closeReclaimed(ledger, gen, input) {
|
|
|
38616
39713
|
const result = await ledger.finish(input.row.runId, gen, record2);
|
|
38617
39714
|
return result.ok ? { ok: true, record: record2 } : { ok: false, reason: result.reason ?? "refused" };
|
|
38618
39715
|
}
|
|
38619
|
-
async function completenessVerdict(ledger,
|
|
39716
|
+
async function completenessVerdict(ledger, row, lastStep) {
|
|
38620
39717
|
if (!lastStep) return { resumable: false, why: "no step record: killed before its conversation was stored" };
|
|
38621
39718
|
let transcript;
|
|
38622
39719
|
try {
|
|
38623
|
-
|
|
39720
|
+
const source2 = transcriptSource(row.meta);
|
|
39721
|
+
transcript = source2.kind === "session" ? await ledger.readSession(source2.key, source2.from) : await ledger.readTranscript(row.runId);
|
|
38624
39722
|
} catch (err2) {
|
|
38625
|
-
return { resumable: false, why: `transcript unreadable (${
|
|
39723
|
+
return { resumable: false, why: `transcript unreadable (${describe9(err2)})` };
|
|
38626
39724
|
}
|
|
38627
39725
|
if (!transcript.complete) return { resumable: false, why: `transcript incomplete: ${transcript.gap}` };
|
|
38628
39726
|
const verdict = transcriptCompleteness({
|
|
@@ -38643,12 +39741,13 @@ async function completenessVerdict(ledger, runId3, lastStep) {
|
|
|
38643
39741
|
return { resumable: false, why: verdict.why };
|
|
38644
39742
|
}
|
|
38645
39743
|
}
|
|
38646
|
-
var TERMINAL,
|
|
39744
|
+
var TERMINAL, describe9;
|
|
38647
39745
|
var init_boot = __esm({
|
|
38648
39746
|
"../../src/core/boot.ts"() {
|
|
38649
39747
|
"use strict";
|
|
38650
39748
|
init_runStoreWorker();
|
|
38651
39749
|
init_decisions();
|
|
39750
|
+
init_resume();
|
|
38652
39751
|
init_types5();
|
|
38653
39752
|
init_record();
|
|
38654
39753
|
init_shipPipeline();
|
|
@@ -38659,111 +39758,7 @@ var init_boot = __esm({
|
|
|
38659
39758
|
"failed",
|
|
38660
39759
|
"interrupted"
|
|
38661
39760
|
]);
|
|
38662
|
-
|
|
38663
|
-
}
|
|
38664
|
-
});
|
|
38665
|
-
|
|
38666
|
-
// ../../src/core/runLedger/resume.ts
|
|
38667
|
-
function settlementFor(toolUse, tools) {
|
|
38668
|
-
const tool = tools.get(toolUse.name);
|
|
38669
|
-
if (!tool) {
|
|
38670
|
-
return {
|
|
38671
|
-
toolUse,
|
|
38672
|
-
action: "synthetic",
|
|
38673
|
-
text: `Tool ${toolUse.name} is not available after the bot restarted; continue without it.`
|
|
38674
|
-
};
|
|
38675
|
-
}
|
|
38676
|
-
if (tool.sideEffectFree || RERUN_SAFE_TOOLS.has(toolUse.name)) return { toolUse, action: "rerun" };
|
|
38677
|
-
return {
|
|
38678
|
-
toolUse,
|
|
38679
|
-
action: "synthetic",
|
|
38680
|
-
text: `The bot restarted while this ${toolUse.name} call was in flight; its effects are unknown \u2014 re-check them before re-running it.`
|
|
38681
|
-
};
|
|
38682
|
-
}
|
|
38683
|
-
function planResume(input) {
|
|
38684
|
-
const { transcript, lastStep } = input;
|
|
38685
|
-
if (!lastStep) return { kind: "interrupted", why: "no step record: killed before its conversation was stored" };
|
|
38686
|
-
if (!transcript.complete) return { kind: "interrupted", why: `transcript incomplete: ${transcript.gap}` };
|
|
38687
|
-
const completeness = transcriptCompleteness({
|
|
38688
|
-
lastStep,
|
|
38689
|
-
seedTurns: lastStep.turnIndex,
|
|
38690
|
-
transcriptTurns: transcript.turns
|
|
38691
|
-
});
|
|
38692
|
-
if (completeness.kind === "interrupted") return completeness;
|
|
38693
|
-
const tools = /* @__PURE__ */ new Map();
|
|
38694
|
-
for (const t of input.tools) tools.set(t.name, t);
|
|
38695
|
-
const messages = transcript.messages;
|
|
38696
|
-
const last = messages[messages.length - 1];
|
|
38697
|
-
const calls = toolUsesOf(last);
|
|
38698
|
-
if (completeness.kind === "resume") {
|
|
38699
|
-
if (lastStep.inFlight.length === 0) {
|
|
38700
|
-
if (last?.role !== "user") {
|
|
38701
|
-
return {
|
|
38702
|
-
kind: "interrupted",
|
|
38703
|
-
why: "transcript ends with an assistant turn but the last step recorded nothing in flight"
|
|
38704
|
-
};
|
|
38705
|
-
}
|
|
38706
|
-
return {
|
|
38707
|
-
kind: "resume",
|
|
38708
|
-
messages,
|
|
38709
|
-
settlements: [],
|
|
38710
|
-
stepRecorded: true,
|
|
38711
|
-
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
38712
|
-
step: lastStep.step,
|
|
38713
|
-
turn: lastStep.turn,
|
|
38714
|
-
iteration: lastStep.iteration,
|
|
38715
|
-
remainingMs: lastStep.remainingMs
|
|
38716
|
-
};
|
|
38717
|
-
}
|
|
38718
|
-
const recorded = new Set(lastStep.inFlight.map((c) => c.callId));
|
|
38719
|
-
if (calls.length === 0 || calls.length !== recorded.size || !calls.every((c) => recorded.has(c.id))) {
|
|
38720
|
-
return {
|
|
38721
|
-
kind: "interrupted",
|
|
38722
|
-
why: `the last step's calls in flight (${[...recorded].join(", ")}) do not match the transcript's last assistant turn`
|
|
38723
|
-
};
|
|
38724
|
-
}
|
|
38725
|
-
return {
|
|
38726
|
-
kind: "resume",
|
|
38727
|
-
messages,
|
|
38728
|
-
settlements: calls.map((c) => settlementFor(c, tools)),
|
|
38729
|
-
stepRecorded: true,
|
|
38730
|
-
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
38731
|
-
step: lastStep.step,
|
|
38732
|
-
turn: lastStep.turn,
|
|
38733
|
-
iteration: lastStep.iteration,
|
|
38734
|
-
remainingMs: lastStep.remainingMs
|
|
38735
|
-
};
|
|
38736
|
-
}
|
|
38737
|
-
if (calls.length === 0) {
|
|
38738
|
-
return { kind: "interrupted", why: "the next step's turns landed but its assistant turn has no tool calls" };
|
|
38739
|
-
}
|
|
38740
|
-
const bookkeepingOnly = calls.every((c) => c.name === "update_status");
|
|
38741
|
-
return {
|
|
38742
|
-
kind: "resume",
|
|
38743
|
-
messages,
|
|
38744
|
-
settlements: calls.map((c) => ({ toolUse: c, action: "rerun" })),
|
|
38745
|
-
stepRecorded: false,
|
|
38746
|
-
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
38747
|
-
step: lastStep.step + 1,
|
|
38748
|
-
turn: lastStep.turn + (bookkeepingOnly ? 0 : 1),
|
|
38749
|
-
iteration: lastStep.iteration + 1,
|
|
38750
|
-
remainingMs: lastStep.remainingMs
|
|
38751
|
-
};
|
|
38752
|
-
}
|
|
38753
|
-
var RERUN_SAFE_TOOLS, toolUsesOf;
|
|
38754
|
-
var init_resume = __esm({
|
|
38755
|
-
"../../src/core/runLedger/resume.ts"() {
|
|
38756
|
-
"use strict";
|
|
38757
|
-
init_decisions();
|
|
38758
|
-
RERUN_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
38759
|
-
"write_file",
|
|
38760
|
-
"update_status",
|
|
38761
|
-
"submit_verdict",
|
|
38762
|
-
"submit_pr_description",
|
|
38763
|
-
"submit_handoff",
|
|
38764
|
-
"submit_dispositions"
|
|
38765
|
-
]);
|
|
38766
|
-
toolUsesOf = (message2) => message2?.role === "assistant" ? message2.content.filter((p) => p.type === "tool_use") : [];
|
|
39761
|
+
describe9 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
38767
39762
|
}
|
|
38768
39763
|
});
|
|
38769
39764
|
|
|
@@ -39495,11 +40490,14 @@ var init_adminModelProxy = __esm({
|
|
|
39495
40490
|
|
|
39496
40491
|
// ../../src/core/harness/pi/toolRules.ts
|
|
39497
40492
|
import { isAbsolute as isAbsolute5, relative, resolve as resolve11 } from "node:path";
|
|
40493
|
+
function reachFor(identity2) {
|
|
40494
|
+
return identity2 === "write" ? CODING_REACH : READ_REACH;
|
|
40495
|
+
}
|
|
39498
40496
|
function judgeToolCall(tool, input, ctx) {
|
|
39499
40497
|
const bundle = PI_TOOL_BUNDLES[tool];
|
|
39500
|
-
if (bundle === void 0) return outside(`${tool} is not in any bundle the
|
|
39501
|
-
if (!
|
|
39502
|
-
return outside(`${tool} is the \`${bundle}\` bundle
|
|
40498
|
+
if (bundle === void 0) return outside(`${tool} is not in any bundle the ${ctx.identity} identity reaches`);
|
|
40499
|
+
if (!reachFor(ctx.identity).has(bundle)) {
|
|
40500
|
+
return outside(`${tool} is the \`${bundle}\` bundle, outside the ${ctx.identity} identity's reach`);
|
|
39503
40501
|
}
|
|
39504
40502
|
const args = isRecord5(input) ? input : {};
|
|
39505
40503
|
if (tool === "bash") return judgeBash(args.command, ctx);
|
|
@@ -39514,6 +40512,13 @@ function judgeBash(command2, ctx) {
|
|
|
39514
40512
|
if (MERGE_OR_APPROVE.test(command2)) {
|
|
39515
40513
|
return refused4("merge/approve \u2014 a coding run never merges or approves a pull request");
|
|
39516
40514
|
}
|
|
40515
|
+
if (ctx.identity !== "write") {
|
|
40516
|
+
if (ANY_PUSH.test(command2)) return refused4("read-only \u2014 a read-identity run never pushes");
|
|
40517
|
+
if (GH_WRITE_VERB.test(command2) || GH_API_WRITE.test(command2) || CURL_GITHUB_WRITE.test(command2)) {
|
|
40518
|
+
return refused4("read-only \u2014 a read-identity run never writes to GitHub");
|
|
40519
|
+
}
|
|
40520
|
+
return allowed;
|
|
40521
|
+
}
|
|
39517
40522
|
for (const match of command2.matchAll(GIT_PUSH)) {
|
|
39518
40523
|
const verdict = judgePush(match[1], ctx);
|
|
39519
40524
|
if (verdict.verdict !== "allowed") return verdict;
|
|
@@ -39549,7 +40554,7 @@ function judgePath(path, ctx) {
|
|
|
39549
40554
|
if (rel.startsWith("..") || isAbsolute5(rel)) return refused4(`path \u2014 \`${path}\` resolves outside the checkout`);
|
|
39550
40555
|
return allowed;
|
|
39551
40556
|
}
|
|
39552
|
-
var PI_TOOL_BUNDLES, CODING_REACH, allowed, refused4, outside, isRecord5, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_PUSH;
|
|
40557
|
+
var PI_TOOL_BUNDLES, CODING_REACH, READ_REACH, allowed, refused4, outside, isRecord5, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_THEN_PUSH, GIT_PUSH, ANY_PUSH, GH_WRITE_VERB, WRITE_METHOD, GH_API_WRITE, CURL_GITHUB_WRITE;
|
|
39553
40558
|
var init_toolRules = __esm({
|
|
39554
40559
|
"../../src/core/harness/pi/toolRules.ts"() {
|
|
39555
40560
|
"use strict";
|
|
@@ -39573,6 +40578,7 @@ var init_toolRules = __esm({
|
|
|
39573
40578
|
"github-write",
|
|
39574
40579
|
"pr"
|
|
39575
40580
|
]);
|
|
40581
|
+
READ_REACH = /* @__PURE__ */ new Set(["shell", "files", "web", "search", "github-read", "verdict"]);
|
|
39576
40582
|
allowed = { verdict: "allowed" };
|
|
39577
40583
|
refused4 = (reason) => ({ verdict: "refused", reason });
|
|
39578
40584
|
outside = (reason) => ({ verdict: "outside-profile", reason });
|
|
@@ -39581,7 +40587,17 @@ var init_toolRules = __esm({
|
|
|
39581
40587
|
ENV_DUMP = /(^|[;&|(]\s*)((printenv|env)(\s+-[A-Za-z0-9-]+)*|export\s+-p|declare\s+-[a-zA-Z]*[px][a-zA-Z]*|set)\s*($|[|;&>)])|\/proc\/(self|\d+)\/environ|process\.env(?![.[\w])/;
|
|
39582
40588
|
CREDENTIAL_VAR = /\$\{?[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b|printenv\s+[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b/;
|
|
39583
40589
|
MERGE_OR_APPROVE = /\bgh\s+pr\s+(merge|review)\b|\/pulls\/\d+\/(merge|reviews)\b/;
|
|
39584
|
-
|
|
40590
|
+
GIT_THEN_PUSH = String.raw`\bgit(?:\s+(?:-C\s+\S+|--git-dir=\S+|--work-tree=\S+|-c\s+\S+|--no-pager))*\s+push\b`;
|
|
40591
|
+
GIT_PUSH = new RegExp(`${GIT_THEN_PUSH}([^;&|]*)`, "g");
|
|
40592
|
+
ANY_PUSH = new RegExp(GIT_THEN_PUSH);
|
|
40593
|
+
GH_WRITE_VERB = /\bgh\s+(pr|issue)\s+(comment|create|edit|close|reopen|ready|lock|unlock|review|merge|delete|transfer|pin|unpin)\b/;
|
|
40594
|
+
WRITE_METHOD = "(POST|post|PATCH|patch|PUT|put|DELETE|delete)";
|
|
40595
|
+
GH_API_WRITE = new RegExp(
|
|
40596
|
+
`\\bgh\\s+api\\b(?=[^;&|]*(?:\\s(?:-X|--method)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-f|-F|--field|--raw-field|--input)[\\s=]))`
|
|
40597
|
+
);
|
|
40598
|
+
CURL_GITHUB_WRITE = new RegExp(
|
|
40599
|
+
`\\bcurl\\b(?=[^;&|]*api\\.github\\.com)(?=[^;&|]*(?:\\s(?:-X|--request)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-d|--data(?:-\\w+)?|--json|-T|--upload-file)[\\s=]))`
|
|
40600
|
+
);
|
|
39585
40601
|
}
|
|
39586
40602
|
});
|
|
39587
40603
|
|
|
@@ -39590,6 +40606,7 @@ function relayedToolDefinitions(harness) {
|
|
|
39590
40606
|
return harness.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
39591
40607
|
}
|
|
39592
40608
|
function authorizeToolCall(harness, ask) {
|
|
40609
|
+
harness.gateSaw(ask.toolCallId);
|
|
39593
40610
|
const blocked = harness.toolsBlocked();
|
|
39594
40611
|
const refuse3 = (reason) => {
|
|
39595
40612
|
harness.emit({
|
|
@@ -39949,9 +40966,12 @@ function parseBrief(v) {
|
|
|
39949
40966
|
const p = b.prior;
|
|
39950
40967
|
if (typeof p !== "object" || p === null || typeof p.reviewRunId !== "string" || !RUN_ID_PATTERN.test(p.reviewRunId))
|
|
39951
40968
|
return invalid("brief.prior.reviewRunId must be a run id");
|
|
39952
|
-
if (p.
|
|
39953
|
-
return invalid("brief.prior.
|
|
39954
|
-
prior = {
|
|
40969
|
+
if (p.codingRunId !== void 0 && (typeof p.codingRunId !== "string" || !RUN_ID_PATTERN.test(p.codingRunId)))
|
|
40970
|
+
return invalid("brief.prior.codingRunId must be a run id");
|
|
40971
|
+
prior = {
|
|
40972
|
+
reviewRunId: p.reviewRunId,
|
|
40973
|
+
...p.codingRunId !== void 0 ? { codingRunId: p.codingRunId } : {}
|
|
40974
|
+
};
|
|
39955
40975
|
}
|
|
39956
40976
|
return {
|
|
39957
40977
|
ok: true,
|
|
@@ -39965,15 +40985,15 @@ function parseBrief(v) {
|
|
|
39965
40985
|
}
|
|
39966
40986
|
};
|
|
39967
40987
|
}
|
|
39968
|
-
case "
|
|
40988
|
+
case "findings": {
|
|
39969
40989
|
const n2 = pr();
|
|
39970
40990
|
if (!n2.ok) return n2;
|
|
39971
40991
|
const review = runId3("reviewRunId");
|
|
39972
40992
|
if (!review.ok) return review;
|
|
39973
|
-
return { ok: true, value: { kind: "
|
|
40993
|
+
return { ok: true, value: { kind: "findings", unit: b.unit, pr: n2.value, reviewRunId: review.value } };
|
|
39974
40994
|
}
|
|
39975
40995
|
default:
|
|
39976
|
-
return invalid("brief.kind must be contract, review or
|
|
40996
|
+
return invalid("brief.kind must be contract, review or findings");
|
|
39977
40997
|
}
|
|
39978
40998
|
}
|
|
39979
40999
|
function parseObject(text) {
|
|
@@ -40036,6 +41056,30 @@ function unitThread(instance, row) {
|
|
|
40036
41056
|
const sourceUrl = row?.sourceUrl ?? (threadKey === instance.threadKey ? instance.sourceUrl : void 0);
|
|
40037
41057
|
return { threadKey, sourceUrl };
|
|
40038
41058
|
}
|
|
41059
|
+
async function openThreadFromRequester(deps, instance, lead, at) {
|
|
41060
|
+
const parent = deps.ioFor({ threadKey: instance.threadKey, userId: instance.userId });
|
|
41061
|
+
if (!parent?.openThread) return { ok: false, response: json(503, { ok: false, error: "no_channel", at }) };
|
|
41062
|
+
try {
|
|
41063
|
+
const opened = await parent.openThread(lead);
|
|
41064
|
+
return {
|
|
41065
|
+
ok: true,
|
|
41066
|
+
thread: {
|
|
41067
|
+
threadKey: opened.thread.threadKey,
|
|
41068
|
+
...opened.thread.sourceUrl !== void 0 ? { sourceUrl: opened.thread.sourceUrl } : {}
|
|
41069
|
+
}
|
|
41070
|
+
};
|
|
41071
|
+
} catch (err2) {
|
|
41072
|
+
return { ok: false, response: json(502, { ok: false, error: "thread_failed", message: describe10(err2), at }) };
|
|
41073
|
+
}
|
|
41074
|
+
}
|
|
41075
|
+
async function ensureReviewThread(deps, instance, row, at) {
|
|
41076
|
+
if (row.reviewThread !== void 0) return { ok: true, row, thread: row.reviewThread };
|
|
41077
|
+
const opened = await openThreadFromRequester(deps, instance, reviewLead(instance, row), at);
|
|
41078
|
+
if (!opened.ok) return opened;
|
|
41079
|
+
const updated = { ...row, reviewThread: opened.thread };
|
|
41080
|
+
await deps.instances.putUnits([updated]);
|
|
41081
|
+
return { ok: true, row: updated, thread: opened.thread };
|
|
41082
|
+
}
|
|
40039
41083
|
function authorizeStep(headers, step, deps) {
|
|
40040
41084
|
const raw = headers.authorization;
|
|
40041
41085
|
const authorization = Array.isArray(raw) ? raw[0] : raw;
|
|
@@ -40109,6 +41153,7 @@ function watched(io, on) {
|
|
|
40109
41153
|
};
|
|
40110
41154
|
if (io.attach) out.attach = (file) => io.attach(file);
|
|
40111
41155
|
if (io.attachFile) out.attachFile = (file) => io.attachFile(file);
|
|
41156
|
+
if (io.uploadTicket) out.uploadTicket = (file) => io.uploadTicket(file);
|
|
40112
41157
|
if (io.runFinished) out.runFinished = (receipt) => io.runFinished(receipt);
|
|
40113
41158
|
if (io.openThread) out.openThread = (lead) => io.openThread(lead);
|
|
40114
41159
|
return out;
|
|
@@ -40123,8 +41168,19 @@ async function spawn6(body, deps) {
|
|
|
40123
41168
|
const at = (deps.clock ?? systemClock)();
|
|
40124
41169
|
const unit = await unitRowOf(deps, instance, req.unit);
|
|
40125
41170
|
if (!unit.ok) return unit.response;
|
|
40126
|
-
const
|
|
40127
|
-
if (
|
|
41171
|
+
const own = unitThread(instance, unit.row);
|
|
41172
|
+
if (own.threadKey === void 0) return json(409, { ok: false, error: "unit_not_started", unit: req.unit, at });
|
|
41173
|
+
let row = unit.row;
|
|
41174
|
+
let thread = {
|
|
41175
|
+
threadKey: own.threadKey,
|
|
41176
|
+
...own.sourceUrl !== void 0 ? { sourceUrl: own.sourceUrl } : {}
|
|
41177
|
+
};
|
|
41178
|
+
if (req.preset === "review" && row !== void 0) {
|
|
41179
|
+
const review = await ensureReviewThread(deps, instance, row, at);
|
|
41180
|
+
if (!review.ok) return review.response;
|
|
41181
|
+
row = review.row;
|
|
41182
|
+
thread = review.thread;
|
|
41183
|
+
}
|
|
40128
41184
|
const threadKey = thread.threadKey;
|
|
40129
41185
|
const key = idempotencyKeyFor(instance.id, req.step);
|
|
40130
41186
|
const live = await liveOnThread(deps.runs, instance, threadKey);
|
|
@@ -40135,13 +41191,12 @@ async function spawn6(body, deps) {
|
|
|
40135
41191
|
if (!io) return json(503, { ok: false, error: "no_channel", at });
|
|
40136
41192
|
let turn;
|
|
40137
41193
|
if (req.brief !== void 0) {
|
|
40138
|
-
if (
|
|
41194
|
+
if (row === void 0) return json(400, { ok: false, error: "a brief needs the unit it runs for" });
|
|
40139
41195
|
try {
|
|
40140
|
-
|
|
40141
|
-
turn = composed;
|
|
41196
|
+
turn = await composeChild(req.brief, instance, row, briefReaders(deps, instance, io));
|
|
40142
41197
|
} catch (err2) {
|
|
40143
|
-
log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${
|
|
40144
|
-
return json(502, { ok: false, error: "brief_failed", message:
|
|
41198
|
+
log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe10(err2)}`);
|
|
41199
|
+
return json(502, { ok: false, error: "brief_failed", message: describe10(err2), at });
|
|
40145
41200
|
}
|
|
40146
41201
|
} else {
|
|
40147
41202
|
turn = { prompt: req.prompt };
|
|
@@ -40184,15 +41239,14 @@ async function spawn6(body, deps) {
|
|
|
40184
41239
|
};
|
|
40185
41240
|
const settled = deps.dispatch(msg, child, {
|
|
40186
41241
|
coordinator: tag,
|
|
40187
|
-
...turn.contract !== void 0 ? { contract: turn.contract } : {}
|
|
40188
|
-
...turn.fixRound !== void 0 ? { fixRound: turn.fixRound } : {}
|
|
41242
|
+
...turn.contract !== void 0 ? { contract: turn.contract } : {}
|
|
40189
41243
|
}).then(
|
|
40190
41244
|
(outcome) => ({ kind: "ended", outcome }),
|
|
40191
41245
|
(err2) => ({ kind: "threw", err: err2 })
|
|
40192
41246
|
);
|
|
40193
41247
|
void settled.then((end) => {
|
|
40194
41248
|
if (end.kind === "threw")
|
|
40195
|
-
log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${
|
|
41249
|
+
log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${describe10(end.err)}`);
|
|
40196
41250
|
});
|
|
40197
41251
|
const first = await Promise.race([started.then((id) => ({ kind: "started", id })), settled]);
|
|
40198
41252
|
if (first.kind === "started" || startedId !== void 0) {
|
|
@@ -40200,7 +41254,7 @@ async function spawn6(body, deps) {
|
|
|
40200
41254
|
log(`[coordinator] ${instance.id} ${req.step}: spawned ${req.preset} run ${runId3} in ${threadKey}`);
|
|
40201
41255
|
return json(200, { ok: true, runId: runId3, threadKey, at });
|
|
40202
41256
|
}
|
|
40203
|
-
if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message:
|
|
41257
|
+
if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message: describe10(first.err), at });
|
|
40204
41258
|
const refusal = first.outcome.refusal;
|
|
40205
41259
|
if (refusal === "coordinator_thread_live") {
|
|
40206
41260
|
const now = await liveOnThread(deps.runs, instance, threadKey);
|
|
@@ -40353,7 +41407,7 @@ async function prCheck(body, deps) {
|
|
|
40353
41407
|
at
|
|
40354
41408
|
});
|
|
40355
41409
|
} catch (err2) {
|
|
40356
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
41410
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
|
|
40357
41411
|
}
|
|
40358
41412
|
}
|
|
40359
41413
|
async function plan2(body, deps) {
|
|
@@ -40402,19 +41456,18 @@ async function unitStart(body, deps) {
|
|
|
40402
41456
|
...instance.sourceUrl !== void 0 ? { sourceUrl: instance.sourceUrl } : {}
|
|
40403
41457
|
};
|
|
40404
41458
|
} else {
|
|
40405
|
-
const
|
|
40406
|
-
if (!
|
|
40407
|
-
|
|
40408
|
-
|
|
40409
|
-
|
|
40410
|
-
|
|
40411
|
-
|
|
40412
|
-
|
|
40413
|
-
|
|
40414
|
-
|
|
40415
|
-
return json(502, { ok: false, error: "thread_failed", message: describe8(err2), at });
|
|
40416
|
-
}
|
|
41459
|
+
const opened = await openThreadFromRequester(deps, instance, unitLead(instance, row), at);
|
|
41460
|
+
if (!opened.ok) return opened.response;
|
|
41461
|
+
row = { ...row, ...opened.thread };
|
|
41462
|
+
}
|
|
41463
|
+
}
|
|
41464
|
+
if (row.reviewThread === void 0) {
|
|
41465
|
+
const review = await ensureReviewThread(deps, instance, row, at);
|
|
41466
|
+
if (!review.ok) {
|
|
41467
|
+
if (row !== unit.row) await deps.instances.putUnits([row]);
|
|
41468
|
+
return review.response;
|
|
40417
41469
|
}
|
|
41470
|
+
row = review.row;
|
|
40418
41471
|
}
|
|
40419
41472
|
if (row.issue === void 0 && row.unit !== TASK_UNIT) {
|
|
40420
41473
|
const issue = await unitIssueOf(deps, instance.repo, row.unit);
|
|
@@ -40422,10 +41475,13 @@ async function unitStart(body, deps) {
|
|
|
40422
41475
|
}
|
|
40423
41476
|
row = { ...row, startedAt: row.startedAt ?? at };
|
|
40424
41477
|
await deps.instances.putUnits([row]);
|
|
40425
|
-
(deps.log ?? console.log)(
|
|
41478
|
+
(deps.log ?? console.log)(
|
|
41479
|
+
`[coordinator] ${instance.id} ${row.unit}: started in ${row.threadKey}, review in ${row.reviewThread?.threadKey}`
|
|
41480
|
+
);
|
|
40426
41481
|
return json(200, {
|
|
40427
41482
|
ok: true,
|
|
40428
41483
|
threadKey: row.threadKey,
|
|
41484
|
+
reviewThreadKey: row.reviewThread?.threadKey,
|
|
40429
41485
|
branch: row.branch,
|
|
40430
41486
|
base: instance.base ?? "main",
|
|
40431
41487
|
...row.issue !== void 0 ? { issue: row.issue } : {},
|
|
@@ -40437,6 +41493,12 @@ function unitLead(instance, row) {
|
|
|
40437
41493
|
const from = instance.sourceUrl !== void 0 ? `[the *ship* run](${instance.sourceUrl})` : "the *ship* run";
|
|
40438
41494
|
return `\u21B3 *ship* unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""} for ${who2}, from ${from}: \`${row.branch}\` in ${instance.repo}`;
|
|
40439
41495
|
}
|
|
41496
|
+
function reviewLead(instance, row) {
|
|
41497
|
+
const who2 = instance.userName ?? instance.userId;
|
|
41498
|
+
const from = instance.sourceUrl !== void 0 ? `[the *ship* run](${instance.sourceUrl})` : "the *ship* run";
|
|
41499
|
+
const what = row.unit === TASK_UNIT ? "the task" : `unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""}`;
|
|
41500
|
+
return `\u21B3 *ship* review of ${what} for ${who2}, from ${from}: \`${row.branch}\` in ${instance.repo}`;
|
|
41501
|
+
}
|
|
40440
41502
|
async function branch(body, deps) {
|
|
40441
41503
|
const id = parseInstanceId(body.parentInstanceId);
|
|
40442
41504
|
if (!id.ok) return json(400, { ok: false, error: id.error });
|
|
@@ -40454,7 +41516,7 @@ async function branch(body, deps) {
|
|
|
40454
41516
|
await deps.createBranchRef(instance.repo, name, base);
|
|
40455
41517
|
return json(200, { ok: true, branch: name, base, at });
|
|
40456
41518
|
} catch (err2) {
|
|
40457
|
-
return json(200, { ok: false, reason:
|
|
41519
|
+
return json(200, { ok: false, reason: describe10(err2), at });
|
|
40458
41520
|
}
|
|
40459
41521
|
}
|
|
40460
41522
|
function unitLines(units) {
|
|
@@ -40506,7 +41568,7 @@ async function round(body, deps) {
|
|
|
40506
41568
|
instance,
|
|
40507
41569
|
units.map((u) => u.unit === updated.unit ? updated : u)
|
|
40508
41570
|
).catch(
|
|
40509
|
-
(err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${
|
|
41571
|
+
(err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${describe10(err2)}`)
|
|
40510
41572
|
);
|
|
40511
41573
|
return json(200, { ok: true, at });
|
|
40512
41574
|
}
|
|
@@ -40540,7 +41602,7 @@ async function unitEnd(body, deps) {
|
|
|
40540
41602
|
told = true;
|
|
40541
41603
|
} catch (err2) {
|
|
40542
41604
|
(deps.log ?? console.warn)(
|
|
40543
|
-
`[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${
|
|
41605
|
+
`[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${describe10(err2)}`
|
|
40544
41606
|
);
|
|
40545
41607
|
}
|
|
40546
41608
|
}
|
|
@@ -40554,7 +41616,7 @@ ${ending.report}` + (rendered !== void 0 ? `
|
|
|
40554
41616
|
${rendered}` : "");
|
|
40555
41617
|
await deps.github.commentIssue(instance.repo, row.issue, comment).catch(
|
|
40556
41618
|
(err2) => (deps.log ?? console.warn)(
|
|
40557
|
-
`[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${
|
|
41619
|
+
`[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${describe10(err2)}`
|
|
40558
41620
|
)
|
|
40559
41621
|
);
|
|
40560
41622
|
}
|
|
@@ -40609,7 +41671,7 @@ async function merge(body, deps, subject2) {
|
|
|
40609
41671
|
try {
|
|
40610
41672
|
facts2 = await deps.fetchPrFacts(pr);
|
|
40611
41673
|
} catch (err2) {
|
|
40612
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
41674
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
|
|
40613
41675
|
}
|
|
40614
41676
|
if (facts2 === void 0)
|
|
40615
41677
|
return json(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
|
|
@@ -40661,7 +41723,7 @@ async function merge(body, deps, subject2) {
|
|
|
40661
41723
|
title: facts2.title ?? `Merge pull request #${pr.number}`
|
|
40662
41724
|
});
|
|
40663
41725
|
} catch (err2) {
|
|
40664
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
41726
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
|
|
40665
41727
|
}
|
|
40666
41728
|
if (!merged.ok) {
|
|
40667
41729
|
log(
|
|
@@ -40836,13 +41898,13 @@ function createAdminCoordinatorHandler(deps) {
|
|
|
40836
41898
|
}
|
|
40837
41899
|
write(res, await answerCoordinatorStep(door, read.body, deps));
|
|
40838
41900
|
} catch (err2) {
|
|
40839
|
-
(deps.log ?? console.error)(`[coordinator] ${
|
|
41901
|
+
(deps.log ?? console.error)(`[coordinator] ${describe10(err2)}`);
|
|
40840
41902
|
write(res, json(500, { ok: false, error: "internal error" }));
|
|
40841
41903
|
}
|
|
40842
41904
|
})();
|
|
40843
41905
|
};
|
|
40844
41906
|
}
|
|
40845
|
-
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json,
|
|
41907
|
+
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json, describe10, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
40846
41908
|
var init_adminCoordinator = __esm({
|
|
40847
41909
|
"../../src/channels/adminCoordinator.ts"() {
|
|
40848
41910
|
"use strict";
|
|
@@ -40871,11 +41933,11 @@ var init_adminCoordinator = __esm({
|
|
|
40871
41933
|
PRESETS_OF_KIND = {
|
|
40872
41934
|
contract: "coding",
|
|
40873
41935
|
review: "review",
|
|
40874
|
-
|
|
41936
|
+
findings: "coding"
|
|
40875
41937
|
};
|
|
40876
41938
|
invalid = (error) => ({ ok: false, error });
|
|
40877
41939
|
json = (status3, body) => ({ status: status3, body });
|
|
40878
|
-
|
|
41940
|
+
describe10 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
40879
41941
|
EVERY_RUN = { kind: "all" };
|
|
40880
41942
|
REVIEW_POSTED_CHECKS = 3;
|
|
40881
41943
|
REVIEW_POSTED_RECHECK_MS = 2e3;
|