@coreplane/switchboard 1.213.0 → 1.214.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 +5 -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/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/runEvents.ts +6 -1
- package/dist/assets/src/core/runFriction.ts +9 -2
- package/dist/assets/src/core/runLedger/types.ts +4 -0
- package/dist/assets/src/core/runRecord.ts +12 -0
- package/dist/assets/src/core/trace/workerTrace.ts +2 -0
- package/dist/assets/src/deploy/profile.ts +11 -0
- package/dist/assets/src/execution/residentText.ts +17 -2
- package/dist/assets/src/providers/types.ts +6 -0
- package/dist/cli.js +1069 -356
- 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");
|
|
@@ -2917,7 +2934,7 @@ function validateRuntimeOverrides(ro) {
|
|
|
2917
2934
|
}
|
|
2918
2935
|
}
|
|
2919
2936
|
}
|
|
2920
|
-
var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
|
|
2937
|
+
var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, ROUTE_ANSWER_MODES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
|
|
2921
2938
|
var init_validate = __esm({
|
|
2922
2939
|
"../../src/config/validate.ts"() {
|
|
2923
2940
|
"use strict";
|
|
@@ -2966,7 +2983,8 @@ var init_validate = __esm({
|
|
|
2966
2983
|
};
|
|
2967
2984
|
BOUNDARY_KEYS = { maxMinutes: true, maxIdentity: true, machines: true };
|
|
2968
2985
|
MIN_BOUNDARY_MINUTES = 2;
|
|
2969
|
-
|
|
2986
|
+
ROUTE_ANSWER_MODES = ["tool", "text"];
|
|
2987
|
+
ROUTING_KEYS = { auto: true, model: true, answer: true };
|
|
2970
2988
|
SHIP_KEYS = { maxRounds: true, maxMinutes: true };
|
|
2971
2989
|
SPAWN_KEYS = { maxChildren: true };
|
|
2972
2990
|
}
|
|
@@ -2987,7 +3005,7 @@ function identityWithin(identity2, cap) {
|
|
|
2987
3005
|
return IDENTITY_ORDER[identity2] <= IDENTITY_ORDER[cap];
|
|
2988
3006
|
}
|
|
2989
3007
|
function budgetedAgent(agent, profile) {
|
|
2990
|
-
return { ...agent, maxMinutes: profile.minutes };
|
|
3008
|
+
return { ...agent, maxMinutes: profile.minutes, maxTurns: runawayTurnCap(profile.minutes) };
|
|
2991
3009
|
}
|
|
2992
3010
|
function intersectBoundaries(layers) {
|
|
2993
3011
|
const out = {};
|
|
@@ -3058,6 +3076,7 @@ var IDENTITY_ORDER, MACHINE_ORDER;
|
|
|
3058
3076
|
var init_profile = __esm({
|
|
3059
3077
|
"../../src/config/profile.ts"() {
|
|
3060
3078
|
"use strict";
|
|
3079
|
+
init_registry();
|
|
3061
3080
|
IDENTITY_ORDER = { none: 0, read: 1, write: 2 };
|
|
3062
3081
|
MACHINE_ORDER = { none: 0, blank: 1, "repo-cold": 2, "repo-resident": 3 };
|
|
3063
3082
|
}
|
|
@@ -4833,7 +4852,14 @@ var init_profile2 = __esm({
|
|
|
4833
4852
|
images: z2.enum(IMAGE_MODES).default("build"),
|
|
4834
4853
|
/** The Cloudflare Access application in front of the bot's dashboards, when
|
|
4835
4854
|
* 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()
|
|
4855
|
+
access: z2.object({ teamDomain: hostname, aud: z2.string().regex(/^[0-9a-f]{64}$/) }).optional(),
|
|
4856
|
+
/** The artifact store's bucket (docs/reference/specs/execution.md item 20), when the installation
|
|
4857
|
+
* has one: the bot Worker's template binds it as `ARTIFACTS` and `deploy` creates it before the
|
|
4858
|
+
* upload. The bot's runtime config (`artifacts.r2.bucket`) must name the same bucket — the two
|
|
4859
|
+
* are held equal by `artifacts check`, not by the profile. R2's own bucket-name rules. */
|
|
4860
|
+
artifacts: z2.object({
|
|
4861
|
+
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")
|
|
4862
|
+
}).optional()
|
|
4837
4863
|
});
|
|
4838
4864
|
EXAMPLE_ACCOUNT = "00000000000000000000000000000000";
|
|
4839
4865
|
}
|
|
@@ -4906,7 +4932,11 @@ function planDeploy(opts, host, loaded, images) {
|
|
|
4906
4932
|
setEnv: w.preflight ? { [w.preflight.baseUrlEnv]: w.baseUrl, ...opts.force ? { [w.preflight.forceEnv]: "1" } : {} } : {},
|
|
4907
4933
|
...opts.force && w.preflight ? { forcedBy: w.preflight.forceEnv } : {},
|
|
4908
4934
|
requiredEnv: w.requiredEnv ?? [],
|
|
4909
|
-
|
|
4935
|
+
// The bot's `npm run deploy` creates the artifacts bucket before the upload
|
|
4936
|
+
// (deploy/cloudflare/ensure-bucket.mjs) when the profile names one — that
|
|
4937
|
+
// needs R2 on the credential, checked up front like every capability; an
|
|
4938
|
+
// installation without artifacts asks nothing extra of its token.
|
|
4939
|
+
capabilities: [...w.capabilities ?? [], ...w.name === "bot" && profile.artifacts ? [R2_CAPABILITY] : []],
|
|
4910
4940
|
retryOnPreflightRefusal: !!w.preflight && !opts.force,
|
|
4911
4941
|
...w.waitMaxMs !== void 0 ? { waitMaxMs: w.waitMaxMs } : {},
|
|
4912
4942
|
...w.preflight?.healthUrl ? { healthUrl: w.preflight.healthUrl } : {},
|
|
@@ -5039,7 +5069,7 @@ function classifyDeployOutput(exitCode, output) {
|
|
|
5039
5069
|
const firstError = lines.find((l) => isErrorish(l) && !/^npm ERR!/i.test(l)) ?? lines.find(isErrorish);
|
|
5040
5070
|
return { kind: "failed", reason: firstError ?? `exit ${exitCode}` };
|
|
5041
5071
|
}
|
|
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;
|
|
5072
|
+
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
5073
|
var init_plan = __esm({
|
|
5044
5074
|
"../../src/deploy/plan.ts"() {
|
|
5045
5075
|
"use strict";
|
|
@@ -5051,6 +5081,10 @@ var init_plan = __esm({
|
|
|
5051
5081
|
UNSET_ENV = ["CLOUDFLARE_ACCOUNT_ID"];
|
|
5052
5082
|
SANDBOX_CONTAINER_CLASS = "SwitchboardSandbox";
|
|
5053
5083
|
SANDBOX_BEARER_ENV = "SANDBOX_TOKEN";
|
|
5084
|
+
R2_CAPABILITY = {
|
|
5085
|
+
command: ["wrangler", "r2", "bucket", "list"],
|
|
5086
|
+
needs: "Workers R2 Storage: Edit"
|
|
5087
|
+
};
|
|
5054
5088
|
RESIDENT_BEARER_ENVS = ["RESIDENT_ADMIN_TOKEN", "RESIDENT_OPERATOR_TOKEN", "RESIDENT_READ_TOKEN"];
|
|
5055
5089
|
RESIDENT_WAIT_MAX_MS = 30 * 6e4;
|
|
5056
5090
|
CONTAINERS_CAPABILITY = {
|
|
@@ -5123,10 +5157,7 @@ var init_plan = __esm({
|
|
|
5123
5157
|
// A container image like the bot's — checked here too, since an --affected
|
|
5124
5158
|
// release can select the resident without the bot — plus the BACKUP_BUCKET
|
|
5125
5159
|
// R2 binding wrangler validates on deploy.
|
|
5126
|
-
capabilities: [
|
|
5127
|
-
CONTAINERS_CAPABILITY,
|
|
5128
|
-
{ command: ["wrangler", "r2", "bucket", "list"], needs: "Workers R2 Storage: Edit" }
|
|
5129
|
-
],
|
|
5160
|
+
capabilities: [CONTAINERS_CAPABILITY, R2_CAPABILITY],
|
|
5130
5161
|
waitMaxMs: RESIDENT_WAIT_MAX_MS,
|
|
5131
5162
|
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
5163
|
},
|
|
@@ -5680,6 +5711,7 @@ function templateView(profile, kind, published) {
|
|
|
5680
5711
|
...hasImage2(kind) ? { image: containerImage(kind, profile, published) } : {},
|
|
5681
5712
|
urls: { publicBaseUrl: urls.publicBaseUrl, stateWorkerUrl: urls.stateWorkerUrl },
|
|
5682
5713
|
access: profile.access,
|
|
5714
|
+
artifacts: profile.artifacts,
|
|
5683
5715
|
bot: { script: profile.workers.bot.script }
|
|
5684
5716
|
};
|
|
5685
5717
|
}
|
|
@@ -7811,7 +7843,8 @@ var init_runEvents = __esm({
|
|
|
7811
7843
|
"review_not_posted",
|
|
7812
7844
|
"compacted",
|
|
7813
7845
|
"harness_error",
|
|
7814
|
-
"tool_refused"
|
|
7846
|
+
"tool_refused",
|
|
7847
|
+
"stuck_loop"
|
|
7815
7848
|
];
|
|
7816
7849
|
ACTOR_ID_CHARS = "A-Za-z0-9:@._-";
|
|
7817
7850
|
ACTOR_ID_MAX = 128;
|
|
@@ -8108,6 +8141,13 @@ var init_host3 = __esm({
|
|
|
8108
8141
|
});
|
|
8109
8142
|
|
|
8110
8143
|
// ../../src/execution/binaryRead.ts
|
|
8144
|
+
function statCommandFor(resolvedPath) {
|
|
8145
|
+
return `stat -c %s -- ${resolvedPath}`;
|
|
8146
|
+
}
|
|
8147
|
+
function parseByteSize(stdout) {
|
|
8148
|
+
const m = /^\s*(\d{1,15})\s*$/.exec(stdout);
|
|
8149
|
+
return m ? Number(m[1]) : null;
|
|
8150
|
+
}
|
|
8111
8151
|
function tooLargeMessage(path, bytes) {
|
|
8112
8152
|
const size = bytes === void 0 ? "" : ` (${bytes} bytes)`;
|
|
8113
8153
|
return `${path}${size} is over the ${MAX_READ_BYTES}-byte cap of a binary read`;
|
|
@@ -8731,9 +8771,9 @@ function sendDeadlineMs(budgetMs) {
|
|
|
8731
8771
|
return budgetMs + EXEC_CALL_MARGIN_MS;
|
|
8732
8772
|
}
|
|
8733
8773
|
function noAnswerMessage(route3, budgetMs) {
|
|
8734
|
-
const
|
|
8774
|
+
const secs2 = (ms) => Math.round(ms / 1e3);
|
|
8735
8775
|
const exec = route3 === "/exec";
|
|
8736
|
-
return `sandbox worker ${route3} gave no answer within ${
|
|
8776
|
+
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
8777
|
}
|
|
8738
8778
|
function waitForSlot(ms, waitedMs, signal) {
|
|
8739
8779
|
return new Promise((resolve12, reject) => {
|
|
@@ -8896,6 +8936,9 @@ ${parts}`);
|
|
|
8896
8936
|
function residentText(text) {
|
|
8897
8937
|
return redactAndCap(stripAnsi(text), RESIDENT_TEXT_CAP);
|
|
8898
8938
|
}
|
|
8939
|
+
function residentErrorText(text) {
|
|
8940
|
+
return redactAndCap(stripAnsi(text), RESIDENT_ERROR_CAP);
|
|
8941
|
+
}
|
|
8899
8942
|
function sanitizeResidentBody(data) {
|
|
8900
8943
|
return walk(data, SANITIZE_DEPTH);
|
|
8901
8944
|
}
|
|
@@ -8905,7 +8948,7 @@ function walk(value, depth) {
|
|
|
8905
8948
|
const src = value;
|
|
8906
8949
|
const out = {};
|
|
8907
8950
|
for (const [key, v] of Object.entries(src)) {
|
|
8908
|
-
out[key] = SANITIZED_FIELDS.includes(key) && typeof v === "string" ? residentText(v) : walk(v, depth - 1);
|
|
8951
|
+
out[key] = SANITIZED_FIELDS.includes(key) && typeof v === "string" ? key === "error" ? residentErrorText(v) : residentText(v) : walk(v, depth - 1);
|
|
8909
8952
|
}
|
|
8910
8953
|
if (typeof src.stderr === "string" && src.stderr === src.error && typeof out.error === "string") {
|
|
8911
8954
|
out.stderr = out.error;
|
|
@@ -8915,12 +8958,13 @@ function walk(value, depth) {
|
|
|
8915
8958
|
function residentState(value) {
|
|
8916
8959
|
return typeof value === "string" && PROBE_STATES.has(value) ? value : "unknown";
|
|
8917
8960
|
}
|
|
8918
|
-
var RESIDENT_TEXT_CAP, SANITIZED_FIELDS, SANITIZE_DEPTH, PROBE_STATES;
|
|
8961
|
+
var RESIDENT_TEXT_CAP, RESIDENT_ERROR_CAP, SANITIZED_FIELDS, SANITIZE_DEPTH, PROBE_STATES;
|
|
8919
8962
|
var init_residentText = __esm({
|
|
8920
8963
|
"../../src/execution/residentText.ts"() {
|
|
8921
8964
|
"use strict";
|
|
8922
8965
|
init_redact();
|
|
8923
8966
|
RESIDENT_TEXT_CAP = 300;
|
|
8967
|
+
RESIDENT_ERROR_CAP = 1e3;
|
|
8924
8968
|
SANITIZED_FIELDS = ["error", "reason", "summary"];
|
|
8925
8969
|
SANITIZE_DEPTH = 4;
|
|
8926
8970
|
PROBE_STATES = /* @__PURE__ */ new Set([
|
|
@@ -10928,7 +10972,8 @@ function buildAnthropicParams(req) {
|
|
|
10928
10972
|
...req.system ? { system: [{ type: "text", text: req.system, cache_control: cache2 }] } : {},
|
|
10929
10973
|
messages,
|
|
10930
10974
|
...effort2 ? { output_config: { effort: effort2 } } : {},
|
|
10931
|
-
...tools.length > 0 ? { tools } : {}
|
|
10975
|
+
...tools.length > 0 ? { tools } : {},
|
|
10976
|
+
...req.toolChoice ? { tool_choice: { type: "tool", name: req.toolChoice.name } } : {}
|
|
10932
10977
|
};
|
|
10933
10978
|
}
|
|
10934
10979
|
function effortFor(model, effort2) {
|
|
@@ -13288,7 +13333,7 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13288
13333
|
}
|
|
13289
13334
|
if (ev.type === "tool_call") {
|
|
13290
13335
|
toolCalls++;
|
|
13291
|
-
if (failedCalls.has(
|
|
13336
|
+
if (failedCalls.has(callSignature(ev.tool, ev.summary))) {
|
|
13292
13337
|
findings.push({
|
|
13293
13338
|
category: "retry",
|
|
13294
13339
|
severity: "low",
|
|
@@ -13310,7 +13355,7 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13310
13355
|
const durationMs = span ? durationOfSpan(span) : void 0;
|
|
13311
13356
|
const timed = (f) => durationMs !== void 0 ? { ...f, durationMs } : f;
|
|
13312
13357
|
const interval = span && durationMs !== void 0 ? { interval: { start: span.startedAt, end: span.startedAt + durationMs } } : {};
|
|
13313
|
-
if (!ev.ok) failedCalls.add(
|
|
13358
|
+
if (!ev.ok) failedCalls.add(callSignature(ev.tool, callSummary));
|
|
13314
13359
|
if (ev.infra) {
|
|
13315
13360
|
findings.push(
|
|
13316
13361
|
timed({
|
|
@@ -13503,7 +13548,7 @@ function formatFrictionReport(d) {
|
|
|
13503
13548
|
}
|
|
13504
13549
|
return lines.join("\n");
|
|
13505
13550
|
}
|
|
13506
|
-
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED;
|
|
13551
|
+
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED, callSignature;
|
|
13507
13552
|
var init_runFriction = __esm({
|
|
13508
13553
|
"../../src/core/runFriction.ts"() {
|
|
13509
13554
|
"use strict";
|
|
@@ -13546,6 +13591,7 @@ var init_runFriction = __esm({
|
|
|
13546
13591
|
DEFAULT_SLOW_MODEL_TURN_MS = 6e4;
|
|
13547
13592
|
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
13593
|
QUOTED = /"(?:[^"\\]|\\.)*"|'[^']*'/g;
|
|
13594
|
+
callSignature = (tool, summary) => `${tool} ${summary}`;
|
|
13549
13595
|
}
|
|
13550
13596
|
});
|
|
13551
13597
|
|
|
@@ -13694,6 +13740,7 @@ function isRunRecord(v) {
|
|
|
13694
13740
|
if (r.profile !== void 0 && !isRunProfileRecord(r.profile)) return false;
|
|
13695
13741
|
if (r.parentRunId !== void 0 && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
|
|
13696
13742
|
return false;
|
|
13743
|
+
if (r.seed !== void 0 && !RUN_SEEDS.includes(r.seed)) return false;
|
|
13697
13744
|
if (r.parentInstanceId === void 0 !== (r.idempotencyKey === void 0)) return false;
|
|
13698
13745
|
if (r.parentInstanceId !== void 0 && (typeof r.parentInstanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.parentInstanceId)))
|
|
13699
13746
|
return false;
|
|
@@ -13825,7 +13872,7 @@ function dropSpanPairsFromTheMiddle(events, eventBudget) {
|
|
|
13825
13872
|
}
|
|
13826
13873
|
return events.filter((_, k) => !dropped.has(k));
|
|
13827
13874
|
}
|
|
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;
|
|
13875
|
+
var RUN_STATUSES, RUN_ID_PATTERN, REVIEW_HEAD_PATTERN, RUN_SEEDS, 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
13876
|
var init_runRecord = __esm({
|
|
13830
13877
|
"../../src/core/runRecord.ts"() {
|
|
13831
13878
|
"use strict";
|
|
@@ -13837,6 +13884,7 @@ var init_runRecord = __esm({
|
|
|
13837
13884
|
RUN_STATUSES = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
|
|
13838
13885
|
RUN_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13839
13886
|
REVIEW_HEAD_PATTERN = /^[0-9a-f]{7,40}$/;
|
|
13887
|
+
RUN_SEEDS = ["channel", "parent"];
|
|
13840
13888
|
MACHINE_CLASSES_IN_RECORD = {
|
|
13841
13889
|
none: true,
|
|
13842
13890
|
blank: true,
|
|
@@ -19896,7 +19944,7 @@ function createRunsService(deps) {
|
|
|
19896
19944
|
const analyze = deps.analyze ?? ((events, opts) => analyzeRunFriction(events, opts));
|
|
19897
19945
|
const clock3 = deps.clock ?? systemClock;
|
|
19898
19946
|
const warn = deps.warn ?? ((m) => console.warn(m));
|
|
19899
|
-
const
|
|
19947
|
+
const describe11 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
19900
19948
|
let listing;
|
|
19901
19949
|
const liveRows = () => {
|
|
19902
19950
|
if (!ledger) return null;
|
|
@@ -19916,7 +19964,7 @@ function createRunsService(deps) {
|
|
|
19916
19964
|
try {
|
|
19917
19965
|
rows = await pending2;
|
|
19918
19966
|
} catch (err2) {
|
|
19919
|
-
warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${
|
|
19967
|
+
warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${describe11(err2)}`);
|
|
19920
19968
|
return [];
|
|
19921
19969
|
}
|
|
19922
19970
|
const foreign = rows.filter((row) => !registry.getById(row.runId));
|
|
@@ -19926,7 +19974,7 @@ function createRunsService(deps) {
|
|
|
19926
19974
|
try {
|
|
19927
19975
|
events = await ledger.readEvents(row.runId);
|
|
19928
19976
|
} catch (err2) {
|
|
19929
|
-
warn(`[runs] run ledger events read failed for ${row.runId}: ${
|
|
19977
|
+
warn(`[runs] run ledger events read failed for ${row.runId}: ${describe11(err2)}`);
|
|
19930
19978
|
}
|
|
19931
19979
|
return ledgerView(row, events);
|
|
19932
19980
|
})
|
|
@@ -19940,7 +19988,7 @@ function createRunsService(deps) {
|
|
|
19940
19988
|
if (!row) return null;
|
|
19941
19989
|
return { row, events: await ledger.readEvents(id) };
|
|
19942
19990
|
} catch (err2) {
|
|
19943
|
-
warn(`[runs] run ledger read failed for ${id}: ${
|
|
19991
|
+
warn(`[runs] run ledger read failed for ${id}: ${describe11(err2)}`);
|
|
19944
19992
|
return null;
|
|
19945
19993
|
}
|
|
19946
19994
|
};
|
|
@@ -19958,7 +20006,7 @@ function createRunsService(deps) {
|
|
|
19958
20006
|
row = await storeSummary(id);
|
|
19959
20007
|
} catch (err2) {
|
|
19960
20008
|
warn(
|
|
19961
|
-
`[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${
|
|
20009
|
+
`[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${describe11(err2)}`
|
|
19962
20010
|
);
|
|
19963
20011
|
return {};
|
|
19964
20012
|
}
|
|
@@ -20127,7 +20175,7 @@ function createRunsService(deps) {
|
|
|
20127
20175
|
const r = await ledger.requestStop(id, mode);
|
|
20128
20176
|
if (r.ok) return { ok: true, value: { id, mode, state: "stopping" } };
|
|
20129
20177
|
} catch (err2) {
|
|
20130
|
-
warn(`[runs] run ledger stop failed for ${id}: ${
|
|
20178
|
+
warn(`[runs] run ledger stop failed for ${id}: ${describe11(err2)}`);
|
|
20131
20179
|
}
|
|
20132
20180
|
}
|
|
20133
20181
|
return await storeSummary(id) ? conflict : notFound;
|
|
@@ -21502,6 +21550,11 @@ function liveViewLink(id, token2) {
|
|
|
21502
21550
|
if (!base) return void 0;
|
|
21503
21551
|
return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}?t=${encodeURIComponent(token2)}`;
|
|
21504
21552
|
}
|
|
21553
|
+
function runPageLink(id) {
|
|
21554
|
+
const base = process.env.PUBLIC_BASE_URL?.trim();
|
|
21555
|
+
if (!base) return void 0;
|
|
21556
|
+
return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}`;
|
|
21557
|
+
}
|
|
21505
21558
|
function stripPlatformPrefix(id) {
|
|
21506
21559
|
const i = id.indexOf(":");
|
|
21507
21560
|
return i === -1 ? id : id.slice(i + 1);
|
|
@@ -21748,7 +21801,11 @@ function durableInboxMessage(msg, text, at, from) {
|
|
|
21748
21801
|
...msg.userName !== void 0 ? { userName: msg.userName } : {},
|
|
21749
21802
|
...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
|
|
21750
21803
|
...msg.channelName !== void 0 ? { channelName: msg.channelName } : {},
|
|
21751
|
-
...from !== void 0 ? { fromRunId: from.runId } : {}
|
|
21804
|
+
...from !== void 0 ? { fromRunId: from.runId } : {},
|
|
21805
|
+
// Staged references are metadata (record 0033) — a few hundred bytes each —
|
|
21806
|
+
// so they ride the base row and survive the restart whatever the
|
|
21807
|
+
// attachments below do.
|
|
21808
|
+
...msg.staged && msg.staged.length > 0 ? { staged: msg.staged.map(stagedRow) } : {}
|
|
21752
21809
|
};
|
|
21753
21810
|
const images = msg.images ?? [];
|
|
21754
21811
|
const documents = msg.documents ?? [];
|
|
@@ -21761,6 +21818,17 @@ function durableInboxMessage(msg, text, at, from) {
|
|
|
21761
21818
|
if (Buffer.byteLength(JSON.stringify(withAttachments), "utf8") <= DURABLE_INBOX_MAX_BYTES) return withAttachments;
|
|
21762
21819
|
return { ...base, attachmentsDropped: { images: images.length, documents: documents.length } };
|
|
21763
21820
|
}
|
|
21821
|
+
function stagedFromInbox(v) {
|
|
21822
|
+
if (!Array.isArray(v)) return void 0;
|
|
21823
|
+
const out = v.flatMap((e) => {
|
|
21824
|
+
if (typeof e !== "object" || e === null) return [];
|
|
21825
|
+
const r = e;
|
|
21826
|
+
if (typeof r.name !== "string" || typeof r.size !== "number" || typeof r.type !== "string" || typeof r.url !== "string" || typeof r.messageId !== "string")
|
|
21827
|
+
return [];
|
|
21828
|
+
return [{ name: r.name, size: r.size, type: r.type, url: r.url, messageId: r.messageId }];
|
|
21829
|
+
});
|
|
21830
|
+
return out.length > 0 ? out : void 0;
|
|
21831
|
+
}
|
|
21764
21832
|
function messageFromInbox(stored, fallbackAt) {
|
|
21765
21833
|
const m = stored;
|
|
21766
21834
|
const str7 = (k) => typeof m[k] === "string" ? m[k] : void 0;
|
|
@@ -21777,6 +21845,7 @@ function messageFromInbox(stored, fallbackAt) {
|
|
|
21777
21845
|
const at = typeof m.at === "number" && Number.isFinite(m.at) ? m.at : fallbackAt;
|
|
21778
21846
|
const images = attachmentsFromInbox(m.images);
|
|
21779
21847
|
const documents = attachmentsFromInbox(m.documents);
|
|
21848
|
+
const staged = stagedFromInbox(m.staged);
|
|
21780
21849
|
const note = droppedNote(m.attachmentsDropped);
|
|
21781
21850
|
const msg = {
|
|
21782
21851
|
channelId,
|
|
@@ -21789,7 +21858,8 @@ ${note}` : text,
|
|
|
21789
21858
|
...sourceUrl !== void 0 ? { sourceUrl } : {},
|
|
21790
21859
|
...channelName !== void 0 ? { channelName } : {},
|
|
21791
21860
|
...images ? { images } : {},
|
|
21792
|
-
...documents ? { documents } : {}
|
|
21861
|
+
...documents ? { documents } : {},
|
|
21862
|
+
...staged ? { staged } : {}
|
|
21793
21863
|
};
|
|
21794
21864
|
return { msg, at, ...fromRunId !== void 0 ? { from: { runId: fromRunId } } : {} };
|
|
21795
21865
|
}
|
|
@@ -21810,7 +21880,7 @@ function droppedNote(dropped) {
|
|
|
21810
21880
|
if (n2 <= 0) return void 0;
|
|
21811
21881
|
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
21882
|
}
|
|
21813
|
-
var DURABLE_INBOX_MAX_BYTES, attachmentRow;
|
|
21883
|
+
var DURABLE_INBOX_MAX_BYTES, attachmentRow, stagedRow;
|
|
21814
21884
|
var init_inboxMessage = __esm({
|
|
21815
21885
|
"../../src/core/runLedger/inboxMessage.ts"() {
|
|
21816
21886
|
"use strict";
|
|
@@ -21820,6 +21890,7 @@ var init_inboxMessage = __esm({
|
|
|
21820
21890
|
data: a.data,
|
|
21821
21891
|
...a.name !== void 0 ? { name: a.name } : {}
|
|
21822
21892
|
});
|
|
21893
|
+
stagedRow = (s) => ({ name: s.name, size: s.size, type: s.type, url: s.url, messageId: s.messageId });
|
|
21823
21894
|
}
|
|
21824
21895
|
});
|
|
21825
21896
|
|
|
@@ -21851,13 +21922,15 @@ function mergeFollowUps(inputs) {
|
|
|
21851
21922
|
const last = inputs[inputs.length - 1];
|
|
21852
21923
|
const images = inputs.flatMap((i) => i.images ?? []);
|
|
21853
21924
|
const documents = inputs.flatMap((i) => i.documents ?? []);
|
|
21925
|
+
const staged = inputs.flatMap((i) => i.staged ?? []);
|
|
21854
21926
|
return {
|
|
21855
21927
|
text: inputs.map((i) => i.text).join("\n\n"),
|
|
21856
21928
|
userId: last.userId,
|
|
21857
21929
|
...last.userName !== void 0 ? { userName: last.userName } : {},
|
|
21858
21930
|
...last.sourceUrl !== void 0 ? { sourceUrl: last.sourceUrl } : {},
|
|
21859
21931
|
...images.length > 0 ? { images } : {},
|
|
21860
|
-
...documents.length > 0 ? { documents } : {}
|
|
21932
|
+
...documents.length > 0 ? { documents } : {},
|
|
21933
|
+
...staged.length > 0 ? { staged } : {}
|
|
21861
21934
|
};
|
|
21862
21935
|
}
|
|
21863
21936
|
var FollowUpInbox, ThreadAdmission, elapsed, linkSuffix, followUpHeader, supersededHeader;
|
|
@@ -21969,6 +22042,7 @@ function interruptedRunRecord(summary, snap, finishedAt) {
|
|
|
21969
22042
|
channelVisibility: summary.channelVisibility ?? "unknown",
|
|
21970
22043
|
repo: summary.repo,
|
|
21971
22044
|
...summary.parentRunId !== void 0 ? { parentRunId: summary.parentRunId } : {},
|
|
22045
|
+
...summary.seed !== void 0 ? { seed: summary.seed } : {},
|
|
21972
22046
|
...summary.parentInstanceId !== void 0 && summary.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: summary.parentInstanceId, idempotencyKey: summary.idempotencyKey } } : {},
|
|
21973
22047
|
finishedAt,
|
|
21974
22048
|
status: "interrupted",
|
|
@@ -22004,6 +22078,7 @@ function reclaimedRunRecord(input) {
|
|
|
22004
22078
|
// included); a row from before profiles existed leaves the record without one.
|
|
22005
22079
|
...row.meta.profile && row.meta.agent ? { profile: { preset: row.meta.agent, ...row.meta.profile } } : {},
|
|
22006
22080
|
...row.meta.parentRunId !== void 0 ? { parentRunId: row.meta.parentRunId } : {},
|
|
22081
|
+
...row.meta.seed !== void 0 ? { seed: row.meta.seed } : {},
|
|
22007
22082
|
// A coordinator's child keeps its instance and key on the close (item 48),
|
|
22008
22083
|
// so the state Worker's finish still sends the parent its event.
|
|
22009
22084
|
...row.meta.parentInstanceId !== void 0 && row.meta.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: row.meta.parentInstanceId, idempotencyKey: row.meta.idempotencyKey } } : {},
|
|
@@ -22072,7 +22147,8 @@ function assembleRunRecord(input) {
|
|
|
22072
22147
|
...input.reviewPost !== void 0 ? { reviewPost: redactReviewPost(input.reviewPost) } : {},
|
|
22073
22148
|
...input.profile !== void 0 ? { profile: input.profile } : {},
|
|
22074
22149
|
...input.parentRunId !== void 0 ? { parentRunId: input.parentRunId } : {},
|
|
22075
|
-
...coordinatorFields(input.coordinator)
|
|
22150
|
+
...coordinatorFields(input.coordinator),
|
|
22151
|
+
...input.seed !== void 0 ? { seed: input.seed } : {}
|
|
22076
22152
|
});
|
|
22077
22153
|
return fitted.eventCount !== fitted.storedEventCount ? { ...fitted, truncated: true } : fitted;
|
|
22078
22154
|
}
|
|
@@ -22080,7 +22156,20 @@ function profileRecordOf(agent, profile) {
|
|
|
22080
22156
|
return { preset: agent.name, ...profile };
|
|
22081
22157
|
}
|
|
22082
22158
|
function writeTombstone(deps, ctx) {
|
|
22083
|
-
const {
|
|
22159
|
+
const {
|
|
22160
|
+
msg,
|
|
22161
|
+
agent,
|
|
22162
|
+
profile,
|
|
22163
|
+
resolved,
|
|
22164
|
+
repoCtx,
|
|
22165
|
+
channelVisibility,
|
|
22166
|
+
run: run2,
|
|
22167
|
+
registry,
|
|
22168
|
+
resume,
|
|
22169
|
+
parentRunId,
|
|
22170
|
+
coordinator,
|
|
22171
|
+
seed
|
|
22172
|
+
} = ctx;
|
|
22084
22173
|
if (!resume) {
|
|
22085
22174
|
const startSnap = registry.snapshot(run2.id, run2.token);
|
|
22086
22175
|
if (startSnap) {
|
|
@@ -22096,6 +22185,7 @@ function writeTombstone(deps, ctx) {
|
|
|
22096
22185
|
profile: profileRecordOf(agent, profile),
|
|
22097
22186
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
22098
22187
|
...coordinator !== void 0 ? { coordinator } : {},
|
|
22188
|
+
...seed !== void 0 ? { seed } : {},
|
|
22099
22189
|
finishedAt: startSnap.startedAt,
|
|
22100
22190
|
status: "interrupted",
|
|
22101
22191
|
diagnosis: analyzeRunFriction(startSnap.events, {
|
|
@@ -22130,7 +22220,8 @@ function registerFinishRecord(deps, ctx) {
|
|
|
22130
22220
|
dispositions,
|
|
22131
22221
|
reviewPost,
|
|
22132
22222
|
parentRunId,
|
|
22133
|
-
coordinator
|
|
22223
|
+
coordinator,
|
|
22224
|
+
seed
|
|
22134
22225
|
} = ctx;
|
|
22135
22226
|
ending.register({
|
|
22136
22227
|
runId: run2.id,
|
|
@@ -22155,7 +22246,8 @@ function registerFinishRecord(deps, ctx) {
|
|
|
22155
22246
|
...dispositions !== void 0 ? { dispositions } : {},
|
|
22156
22247
|
...reviewPost !== void 0 ? { reviewPost } : {},
|
|
22157
22248
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
22158
|
-
...coordinator !== void 0 ? { coordinator } : {}
|
|
22249
|
+
...coordinator !== void 0 ? { coordinator } : {},
|
|
22250
|
+
...seed !== void 0 ? { seed } : {}
|
|
22159
22251
|
}),
|
|
22160
22252
|
{ span: root, ...ledgerRun ? { via: ledgerRun.sink } : {} }
|
|
22161
22253
|
)
|
|
@@ -22188,6 +22280,7 @@ function followUpOf(msg, text, at, opts) {
|
|
|
22188
22280
|
...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
|
|
22189
22281
|
...msg.images !== void 0 ? { images: msg.images } : {},
|
|
22190
22282
|
...msg.documents !== void 0 ? { documents: msg.documents } : {},
|
|
22283
|
+
...msg.staged !== void 0 ? { staged: msg.staged } : {},
|
|
22191
22284
|
at,
|
|
22192
22285
|
...opts.ledgerSeq !== void 0 ? { ledgerSeq: opts.ledgerSeq } : {},
|
|
22193
22286
|
...opts.from !== void 0 ? { from: opts.from } : {},
|
|
@@ -23353,6 +23446,7 @@ function summaryOf(run2) {
|
|
|
23353
23446
|
...m?.parentRunId !== void 0 ? { parentRunId: m.parentRunId } : {},
|
|
23354
23447
|
...m?.parentInstanceId !== void 0 ? { parentInstanceId: m.parentInstanceId } : {},
|
|
23355
23448
|
...m?.idempotencyKey !== void 0 ? { idempotencyKey: m.idempotencyKey } : {},
|
|
23449
|
+
...m?.seed !== void 0 ? { seed: m.seed } : {},
|
|
23356
23450
|
finished: run2.finished,
|
|
23357
23451
|
startedAt: run2.startedAt,
|
|
23358
23452
|
...run2.finishedAt !== void 0 ? { finishedAt: run2.finishedAt } : {},
|
|
@@ -24254,6 +24348,21 @@ var init_awaitChildren = __esm({
|
|
|
24254
24348
|
}
|
|
24255
24349
|
});
|
|
24256
24350
|
|
|
24351
|
+
// ../../src/core/dispatch/textTurns.ts
|
|
24352
|
+
function textTurnsOf(messages) {
|
|
24353
|
+
const turns = [];
|
|
24354
|
+
for (const m of messages) {
|
|
24355
|
+
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n\n");
|
|
24356
|
+
if (text.length > 0) turns.push({ role: m.role, text });
|
|
24357
|
+
}
|
|
24358
|
+
return turns;
|
|
24359
|
+
}
|
|
24360
|
+
var init_textTurns = __esm({
|
|
24361
|
+
"../../src/core/dispatch/textTurns.ts"() {
|
|
24362
|
+
"use strict";
|
|
24363
|
+
}
|
|
24364
|
+
});
|
|
24365
|
+
|
|
24257
24366
|
// ../../src/core/dispatch/spawn.ts
|
|
24258
24367
|
function maxChildrenOf(cfg) {
|
|
24259
24368
|
return cfg?.maxChildren ?? DEFAULT_MAX_CHILDREN;
|
|
@@ -24290,6 +24399,7 @@ function watchedChild(io, on) {
|
|
|
24290
24399
|
};
|
|
24291
24400
|
if (io.attach) watched2.attach = (file) => io.attach(file);
|
|
24292
24401
|
if (io.attachFile) watched2.attachFile = (file) => io.attachFile(file);
|
|
24402
|
+
if (io.uploadTicket) watched2.uploadTicket = (file) => io.uploadTicket(file);
|
|
24293
24403
|
if (io.runFinished) watched2.runFinished = (receipt) => io.runFinished(receipt);
|
|
24294
24404
|
if (io.openThread) watched2.openThread = (lead) => io.openThread(lead);
|
|
24295
24405
|
return watched2;
|
|
@@ -24302,6 +24412,12 @@ async function spawnChild(deps, parent, request) {
|
|
|
24302
24412
|
`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
24413
|
);
|
|
24304
24414
|
}
|
|
24415
|
+
if (AGENTS[request.preset]?.identity === "write") {
|
|
24416
|
+
return refused5(
|
|
24417
|
+
"spawn_identity",
|
|
24418
|
+
`\`${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}\``
|
|
24419
|
+
);
|
|
24420
|
+
}
|
|
24305
24421
|
const minutesLeft = Math.floor(parent.remainingMs / 6e4);
|
|
24306
24422
|
if (minutesLeft < MIN_BOUNDARY_MINUTES) {
|
|
24307
24423
|
return refused5(
|
|
@@ -24354,8 +24470,10 @@ async function spawnChild(deps, parent, request) {
|
|
|
24354
24470
|
lastReply = text;
|
|
24355
24471
|
}
|
|
24356
24472
|
});
|
|
24473
|
+
const seed = parent.conversation ? textTurnsOf(parent.conversation) : void 0;
|
|
24357
24474
|
const settled = deps.dispatch(deps.core, child, io, {
|
|
24358
|
-
parent: { runId: parent.runId, depth: parent.depth + 1, remainingMs: parent.remainingMs }
|
|
24475
|
+
parent: { runId: parent.runId, depth: parent.depth + 1, remainingMs: parent.remainingMs },
|
|
24476
|
+
...seed ? { seed } : {}
|
|
24359
24477
|
}).then(
|
|
24360
24478
|
(outcome) => ({ kind: "ended", outcome }),
|
|
24361
24479
|
(err2) => ({ kind: "threw", err: err2 })
|
|
@@ -24396,8 +24514,8 @@ function spawnCapabilityFor(deps, parent) {
|
|
|
24396
24514
|
};
|
|
24397
24515
|
let previous = Promise.resolve();
|
|
24398
24516
|
return {
|
|
24399
|
-
spawn: (request,
|
|
24400
|
-
const turn = previous.then(() => spawnChild(withHook, { ...parent,
|
|
24517
|
+
spawn: (request, at) => {
|
|
24518
|
+
const turn = previous.then(() => spawnChild(withHook, { ...parent, ...at }, request));
|
|
24401
24519
|
previous = turn.catch(() => {
|
|
24402
24520
|
});
|
|
24403
24521
|
return turn;
|
|
@@ -24441,11 +24559,13 @@ var DEFAULT_MAX_CHILDREN, MAX_SPAWN_DEPTH, describe2, nullSpawnCapability;
|
|
|
24441
24559
|
var init_spawn = __esm({
|
|
24442
24560
|
"../../src/core/dispatch/spawn.ts"() {
|
|
24443
24561
|
"use strict";
|
|
24562
|
+
init_registry();
|
|
24444
24563
|
init_validate();
|
|
24445
24564
|
init_actor();
|
|
24446
24565
|
init_runsService();
|
|
24447
24566
|
init_admission();
|
|
24448
24567
|
init_awaitChildren();
|
|
24568
|
+
init_textTurns();
|
|
24449
24569
|
DEFAULT_MAX_CHILDREN = 3;
|
|
24450
24570
|
MAX_SPAWN_DEPTH = 1;
|
|
24451
24571
|
describe2 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
@@ -24476,6 +24596,46 @@ function renderPresetTable(presets) {
|
|
|
24476
24596
|
);
|
|
24477
24597
|
return ["| name | what it does | machine | credential | budget |", "|---|---|---|---|---|", ...rows].join("\n");
|
|
24478
24598
|
}
|
|
24599
|
+
function routeMaxOutputTokens(compound) {
|
|
24600
|
+
const PART_JSON_OVERHEAD = 40;
|
|
24601
|
+
const ANSWER_JSON_OVERHEAD = 80;
|
|
24602
|
+
const chars = ANSWER_JSON_OVERHEAD + ROUTE_REASON_CAP + (compound ? compound.maxParts * (ROUTE_PART_TEXT_CAP + PART_JSON_OVERHEAD) : 0);
|
|
24603
|
+
return Math.max(ROUTE_MIN_OUTPUT_TOKENS, Math.ceil(chars / 3));
|
|
24604
|
+
}
|
|
24605
|
+
function routeTool(presets, compound) {
|
|
24606
|
+
const names = presets.map((p) => p.name);
|
|
24607
|
+
const table = presets.map((p) => `${p.name}: ${oneLine(p.description)}`).join("; ");
|
|
24608
|
+
const properties = {
|
|
24609
|
+
preset: {
|
|
24610
|
+
type: "string",
|
|
24611
|
+
enum: compound ? [...names, COMPOUND_PRESET] : names,
|
|
24612
|
+
description: `the least capable preset whose description covers the request \u2014 ${table}${compound ? `; ${COMPOUND_PRESET}: only for a compound request, with parts` : ""}`
|
|
24613
|
+
},
|
|
24614
|
+
reason: { type: "string", description: "one line, under 100 characters: why this preset" },
|
|
24615
|
+
...compound ? {
|
|
24616
|
+
parts: {
|
|
24617
|
+
type: "array",
|
|
24618
|
+
description: `only when the request has two or more INDEPENDENT asks on different subjects, each rewritten so it stands alone, with preset "${COMPOUND_PRESET}". 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.`,
|
|
24619
|
+
minItems: 2,
|
|
24620
|
+
maxItems: compound.maxParts,
|
|
24621
|
+
items: {
|
|
24622
|
+
type: "object",
|
|
24623
|
+
additionalProperties: false,
|
|
24624
|
+
required: ["preset", "text"],
|
|
24625
|
+
properties: {
|
|
24626
|
+
preset: { type: "string", enum: names, description: "the preset this part runs on" },
|
|
24627
|
+
text: { type: "string", description: "the part as a request of its own" }
|
|
24628
|
+
}
|
|
24629
|
+
}
|
|
24630
|
+
}
|
|
24631
|
+
} : {}
|
|
24632
|
+
};
|
|
24633
|
+
return {
|
|
24634
|
+
name: ROUTE_TOOL_NAME,
|
|
24635
|
+
description: "Route the request: name the preset it runs on and why.",
|
|
24636
|
+
inputSchema: { type: "object", additionalProperties: false, required: ["preset", "reason"], properties }
|
|
24637
|
+
};
|
|
24638
|
+
}
|
|
24479
24639
|
function quoteRequest(text) {
|
|
24480
24640
|
const bent = text.replace(/<(\/?)request>/gi, "\u2039$1request\u203A");
|
|
24481
24641
|
if (bent.length <= ROUTE_TEXT_CAP) return bent;
|
|
@@ -24509,7 +24669,7 @@ function buildRoutePrompt(input) {
|
|
|
24509
24669
|
quoteRequest(input.text),
|
|
24510
24670
|
"</request>"
|
|
24511
24671
|
].join("\n");
|
|
24512
|
-
return { system, user };
|
|
24672
|
+
return { system, user, tool: routeTool(input.presets, input.compound) };
|
|
24513
24673
|
}
|
|
24514
24674
|
function compoundRules(offer) {
|
|
24515
24675
|
return [
|
|
@@ -24520,7 +24680,7 @@ function compoundRules(offer) {
|
|
|
24520
24680
|
}
|
|
24521
24681
|
function imperativeRule(writers) {
|
|
24522
24682
|
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.`;
|
|
24683
|
+
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
24684
|
}
|
|
24525
24685
|
function tidyReason(reason) {
|
|
24526
24686
|
const line3 = oneLine(redactAndCap(reason, ROUTE_REASON_CAP));
|
|
@@ -24537,8 +24697,10 @@ function parseRouteAnswer(raw, allowed2, compound) {
|
|
|
24537
24697
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
24538
24698
|
return { preset: void 0, reason: `not a single JSON object: ${tidyReason(trimmed)}` };
|
|
24539
24699
|
const { preset, reason, parts } = parsed;
|
|
24540
|
-
if (typeof preset !== "string")
|
|
24541
|
-
|
|
24700
|
+
if (typeof preset !== "string")
|
|
24701
|
+
return { preset: void 0, reason: `missing preset in the router's answer: ${tidyReason(trimmed)}` };
|
|
24702
|
+
if (typeof reason !== "string")
|
|
24703
|
+
return { preset: void 0, reason: `missing reason in the router's answer: ${tidyReason(trimmed)}` };
|
|
24542
24704
|
if (preset === COMPOUND_PRESET) return parseCompound(parts, tidyReason(reason), allowed2, compound);
|
|
24543
24705
|
if (!allowed2.includes(preset))
|
|
24544
24706
|
return { preset: void 0, reason: `router said "${tidyReason(preset)}", not a preset the requester may run` };
|
|
@@ -24576,7 +24738,7 @@ async function route(input, model, opts = {}) {
|
|
|
24576
24738
|
let raw;
|
|
24577
24739
|
try {
|
|
24578
24740
|
raw = await model(prompt, {
|
|
24579
|
-
maxTokens:
|
|
24741
|
+
maxTokens: routeMaxOutputTokens(input.compound),
|
|
24580
24742
|
signal: AbortSignal.timeout(opts.timeoutMs ?? ROUTE_TIMEOUT_MS)
|
|
24581
24743
|
});
|
|
24582
24744
|
} catch (err2) {
|
|
@@ -24591,15 +24753,22 @@ async function route(input, model, opts = {}) {
|
|
|
24591
24753
|
input.compound
|
|
24592
24754
|
);
|
|
24593
24755
|
}
|
|
24594
|
-
function providerRouteModel(provider, model) {
|
|
24595
|
-
|
|
24756
|
+
function providerRouteModel(provider, model, opts = {}) {
|
|
24757
|
+
const forced = (opts.answer ?? "tool") === "tool";
|
|
24758
|
+
return async (prompt, call2) => {
|
|
24596
24759
|
const result = await provider.complete({
|
|
24597
24760
|
model,
|
|
24598
24761
|
system: prompt.system,
|
|
24599
24762
|
messages: [{ role: "user", content: [{ type: "text", text: prompt.user }] }],
|
|
24600
|
-
maxTokens:
|
|
24601
|
-
signal:
|
|
24763
|
+
maxTokens: call2.maxTokens,
|
|
24764
|
+
signal: call2.signal,
|
|
24765
|
+
...forced ? { tools: [prompt.tool], toolChoice: { type: "tool", name: prompt.tool.name } } : {}
|
|
24602
24766
|
});
|
|
24767
|
+
if (result.stopReason === "max_tokens") throw new Error(`answer cut at the output cap (${call2.maxTokens} tokens)`);
|
|
24768
|
+
const answer = result.content.find(
|
|
24769
|
+
(p) => p.type === "tool_use" && p.name === prompt.tool.name
|
|
24770
|
+
);
|
|
24771
|
+
if (answer) return JSON.stringify(answer.input);
|
|
24603
24772
|
return result.content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
24604
24773
|
};
|
|
24605
24774
|
}
|
|
@@ -24617,7 +24786,9 @@ async function routeRequest(deps, ctx) {
|
|
|
24617
24786
|
if (deps.routeModel) model = deps.routeModel;
|
|
24618
24787
|
else {
|
|
24619
24788
|
const ref = parseModelRef(modelRef2);
|
|
24620
|
-
model = providerRouteModel(deps.providers.get(ref.provider), ref.model
|
|
24789
|
+
model = providerRouteModel(deps.providers.get(ref.provider), ref.model, {
|
|
24790
|
+
...cfg.routing?.answer ? { answer: cfg.routing.answer } : {}
|
|
24791
|
+
});
|
|
24621
24792
|
}
|
|
24622
24793
|
} catch (err2) {
|
|
24623
24794
|
console.log(`[route] ${msg.threadKey} not routed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
@@ -24680,7 +24851,7 @@ function compoundBrief(text, parts) {
|
|
|
24680
24851
|
...parts.map((p, i) => `${i + 1}. \`${p.preset}\`: ${p.text}`)
|
|
24681
24852
|
].join("\n");
|
|
24682
24853
|
}
|
|
24683
|
-
var ROUTE_TEXT_CAP, ROUTE_REASON_CAP,
|
|
24854
|
+
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, ROUTED_CARD_FOOTER;
|
|
24684
24855
|
var init_route = __esm({
|
|
24685
24856
|
"../../src/core/dispatch/route.ts"() {
|
|
24686
24857
|
"use strict";
|
|
@@ -24691,7 +24862,8 @@ var init_route = __esm({
|
|
|
24691
24862
|
init_spawn();
|
|
24692
24863
|
ROUTE_TEXT_CAP = 2e3;
|
|
24693
24864
|
ROUTE_REASON_CAP = 120;
|
|
24694
|
-
|
|
24865
|
+
ROUTE_MIN_OUTPUT_TOKENS = 200;
|
|
24866
|
+
ROUTE_TOOL_NAME = "route";
|
|
24695
24867
|
ROUTE_TIMEOUT_MS = 8e3;
|
|
24696
24868
|
ROUTE_PART_TEXT_CAP = 1e3;
|
|
24697
24869
|
ROUTE_PART_LINE_CAP = 100;
|
|
@@ -25723,19 +25895,162 @@ ${skill.body}${footer}`;
|
|
|
25723
25895
|
}
|
|
25724
25896
|
});
|
|
25725
25897
|
|
|
25898
|
+
// ../../src/artifacts/contentType.ts
|
|
25899
|
+
function contentTypeFor(name) {
|
|
25900
|
+
const dot = name.lastIndexOf(".");
|
|
25901
|
+
if (dot < 0 || dot === name.length - 1) return OCTET_STREAM;
|
|
25902
|
+
return BY_EXTENSION[name.slice(dot + 1).toLowerCase()] ?? OCTET_STREAM;
|
|
25903
|
+
}
|
|
25904
|
+
var BY_EXTENSION, OCTET_STREAM, INLINE_IMAGE_TYPES;
|
|
25905
|
+
var init_contentType = __esm({
|
|
25906
|
+
"../../src/artifacts/contentType.ts"() {
|
|
25907
|
+
"use strict";
|
|
25908
|
+
BY_EXTENSION = {
|
|
25909
|
+
png: "image/png",
|
|
25910
|
+
jpg: "image/jpeg",
|
|
25911
|
+
jpeg: "image/jpeg",
|
|
25912
|
+
gif: "image/gif",
|
|
25913
|
+
webp: "image/webp",
|
|
25914
|
+
svg: "image/svg+xml",
|
|
25915
|
+
pdf: "application/pdf",
|
|
25916
|
+
mp4: "video/mp4",
|
|
25917
|
+
webm: "video/webm",
|
|
25918
|
+
mov: "video/quicktime",
|
|
25919
|
+
mp3: "audio/mpeg",
|
|
25920
|
+
wav: "audio/wav",
|
|
25921
|
+
txt: "text/plain",
|
|
25922
|
+
md: "text/markdown",
|
|
25923
|
+
csv: "text/csv",
|
|
25924
|
+
json: "application/json",
|
|
25925
|
+
html: "text/html",
|
|
25926
|
+
zip: "application/zip",
|
|
25927
|
+
gz: "application/gzip",
|
|
25928
|
+
tar: "application/x-tar"
|
|
25929
|
+
};
|
|
25930
|
+
OCTET_STREAM = "application/octet-stream";
|
|
25931
|
+
INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
25932
|
+
}
|
|
25933
|
+
});
|
|
25934
|
+
|
|
25935
|
+
// ../../src/artifacts/keys.ts
|
|
25936
|
+
function safeBasename(name) {
|
|
25937
|
+
const segments2 = name.split(/[\\/]+/).filter((s) => s.length > 0);
|
|
25938
|
+
const last = segments2[segments2.length - 1] ?? "";
|
|
25939
|
+
const reduced = last.replace(SAFE, "_").replace(/\.{2,}/g, "_").replace(/_+/g, "_");
|
|
25940
|
+
if (reduced === "" || /^[._-]+$/.test(reduced)) return "file";
|
|
25941
|
+
return reduced;
|
|
25942
|
+
}
|
|
25943
|
+
function threadKeySafe(threadKey) {
|
|
25944
|
+
return threadKey.replace(/:/g, "-").replace(SAFE, "_");
|
|
25945
|
+
}
|
|
25946
|
+
function outboundKey(runId3, seq, name) {
|
|
25947
|
+
return `runs/${runId3}/out/${seq}-${safeBasename(name)}`;
|
|
25948
|
+
}
|
|
25949
|
+
function inboundKey(threadKey, messageTs, index, name) {
|
|
25950
|
+
return `threads/${threadKeySafe(threadKey)}/in/${messageTs}/${index}-${safeBasename(name)}`;
|
|
25951
|
+
}
|
|
25952
|
+
var SAFE;
|
|
25953
|
+
var init_keys = __esm({
|
|
25954
|
+
"../../src/artifacts/keys.ts"() {
|
|
25955
|
+
"use strict";
|
|
25956
|
+
SAFE = /[^A-Za-z0-9._-]+/g;
|
|
25957
|
+
}
|
|
25958
|
+
});
|
|
25959
|
+
|
|
25726
25960
|
// ../../src/tools/attach.ts
|
|
25961
|
+
function transferBudgetMs(size) {
|
|
25962
|
+
return Math.ceil(size / TRANSFER_FLOOR_BYTES_PER_MS) + TRANSFER_SETUP_MS;
|
|
25963
|
+
}
|
|
25727
25964
|
function fileNameOf2(path) {
|
|
25728
25965
|
const segments2 = path.split("/").filter(Boolean);
|
|
25729
25966
|
return segments2[segments2.length - 1] ?? path;
|
|
25730
25967
|
}
|
|
25731
|
-
|
|
25968
|
+
function putCommandFor(path, contentType, url) {
|
|
25969
|
+
return `curl -fsS -T ${shellQuote(path)} -H ${shellQuote(`Content-Type: ${contentType}`)} ${shellQuote(url)}`;
|
|
25970
|
+
}
|
|
25971
|
+
function postCommandFor(path, url) {
|
|
25972
|
+
return `curl -fsS --data-binary @${shellQuote(path)} ${shellQuote(url)}`;
|
|
25973
|
+
}
|
|
25974
|
+
async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
25975
|
+
const exec = (command2, timeoutMs2) => {
|
|
25976
|
+
const opts = { timeoutMs: timeoutMs2, ...ctx.signal ? { signal: ctx.signal } : {} };
|
|
25977
|
+
return ctx.executor.exec(command2, opts);
|
|
25978
|
+
};
|
|
25979
|
+
const statOut = await exec(statCommandFor(shellQuote(path)), STAT_TIMEOUT_MS);
|
|
25980
|
+
if (parseExitPrefix(statOut).failed) return `error: could not read ${path}: ${statOut.trim()}`;
|
|
25981
|
+
const size = parseByteSize(statOut);
|
|
25982
|
+
if (size === null) return `error: could not measure ${path}: ${statOut.trim()}`;
|
|
25983
|
+
if (size === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
25984
|
+
if (size > MAX_ARTIFACT_BYTES) {
|
|
25985
|
+
return `error: ${path} is ${size} bytes; attach_file takes files up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) \u2014 link to the file instead`;
|
|
25986
|
+
}
|
|
25987
|
+
let timeoutMs = BASH_TIMEOUT_MAX_MS;
|
|
25988
|
+
if (ctx.remainingMs) {
|
|
25989
|
+
const budget = bashBudgetWithinRun(BASH_TIMEOUT_MAX_MS, ctx.remainingMs());
|
|
25990
|
+
if (budget.kind === "exhausted") return `error: ${budget.note}`;
|
|
25991
|
+
if (budget.kind === "clipped") timeoutMs = budget.timeoutMs;
|
|
25992
|
+
}
|
|
25993
|
+
const needed = transferBudgetMs(size);
|
|
25994
|
+
if (needed > timeoutMs) {
|
|
25995
|
+
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`;
|
|
25996
|
+
}
|
|
25997
|
+
const contentType = contentTypeFor(name);
|
|
25998
|
+
const key = outboundKey(artifacts.runId, artifacts.nextSeq(), name);
|
|
25999
|
+
const putUrl = await artifacts.store.presignPut(key, contentType);
|
|
26000
|
+
const putOut = await exec(putCommandFor(path, contentType, putUrl), timeoutMs);
|
|
26001
|
+
if (parseExitPrefix(putOut).failed) {
|
|
26002
|
+
return `error: the upload of ${name} to the artifact store failed: ${putOut.trim()}`;
|
|
26003
|
+
}
|
|
26004
|
+
const head = await artifacts.store.head(key);
|
|
26005
|
+
if (!head)
|
|
26006
|
+
return `error: the artifact store holds nothing under ${key} after the upload of ${name} \u2014 nothing was posted`;
|
|
26007
|
+
if (head.size !== size) {
|
|
26008
|
+
return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured \u2014 nothing was posted`;
|
|
26009
|
+
}
|
|
26010
|
+
ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType });
|
|
26011
|
+
if (!ctx.uploadTicket) {
|
|
26012
|
+
const where = artifacts.runUrl ? ` \u2014 ${artifacts.runUrl}` : "";
|
|
26013
|
+
await artifacts.reply(`${lead}
|
|
26014
|
+
\u{1F4CE} ${name} (${size} bytes) is on the run page${where}`);
|
|
26015
|
+
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`;
|
|
26016
|
+
}
|
|
26017
|
+
const kept = "the file is kept on the run page";
|
|
26018
|
+
let ticket;
|
|
26019
|
+
try {
|
|
26020
|
+
ticket = await ctx.uploadTicket({ name, size });
|
|
26021
|
+
} catch (err2) {
|
|
26022
|
+
return `error: the channel refused an upload ticket for ${name}: ${describe3(err2)}; ${kept}`;
|
|
26023
|
+
}
|
|
26024
|
+
const postOut = await exec(postCommandFor(path, ticket.url), timeoutMs);
|
|
26025
|
+
if (parseExitPrefix(postOut).failed) {
|
|
26026
|
+
return `error: the upload of ${name} to the channel failed: ${postOut.trim()}; ${kept}`;
|
|
26027
|
+
}
|
|
26028
|
+
try {
|
|
26029
|
+
await ticket.complete(lead);
|
|
26030
|
+
} catch (err2) {
|
|
26031
|
+
return `error: the channel refused to complete the upload of ${name}: ${describe3(err2)}; ${kept}`;
|
|
26032
|
+
}
|
|
26033
|
+
return `attached ${name} (${size} bytes) to the conversation and the run page`;
|
|
26034
|
+
}
|
|
26035
|
+
var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe3, secs, attachFileTool;
|
|
25732
26036
|
var init_attach = __esm({
|
|
25733
26037
|
"../../src/tools/attach.ts"() {
|
|
25734
26038
|
"use strict";
|
|
26039
|
+
init_contentType();
|
|
26040
|
+
init_keys();
|
|
26041
|
+
init_runEvents();
|
|
26042
|
+
init_bashTimeout();
|
|
25735
26043
|
init_binaryRead();
|
|
26044
|
+
init_shellQuote();
|
|
26045
|
+
MAX_ARTIFACT_BYTES = 1073741824;
|
|
26046
|
+
TRANSFER_FLOOR_BYTES_PER_MS = 1024;
|
|
26047
|
+
TRANSFER_SETUP_MS = 1e4;
|
|
26048
|
+
STAT_TIMEOUT_MS = 3e4;
|
|
26049
|
+
describe3 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
26050
|
+
secs = (ms) => Math.max(0, Math.round(ms / 1e3));
|
|
25736
26051
|
attachFileTool = {
|
|
25737
26052
|
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
|
|
26053
|
+
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
26054
|
inputSchema: {
|
|
25740
26055
|
type: "object",
|
|
25741
26056
|
properties: {
|
|
@@ -25752,6 +26067,7 @@ var init_attach = __esm({
|
|
|
25752
26067
|
if (!path) return "error: path is required";
|
|
25753
26068
|
const name = fileNameOf2(path);
|
|
25754
26069
|
const lead = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : name;
|
|
26070
|
+
if (ctx.artifacts) return attachThroughStore(path, name, lead, ctx.artifacts, ctx);
|
|
25755
26071
|
if (!ctx.attach) {
|
|
25756
26072
|
return "attach_file is not available here: this conversation's channel takes no file uploads \u2014 link to the file instead";
|
|
25757
26073
|
}
|
|
@@ -25763,13 +26079,13 @@ var init_attach = __esm({
|
|
|
25763
26079
|
try {
|
|
25764
26080
|
bytes = await readBytes(path);
|
|
25765
26081
|
} catch (err2) {
|
|
25766
|
-
return `error: could not read ${path}: ${
|
|
26082
|
+
return `error: could not read ${path}: ${describe3(err2)}`;
|
|
25767
26083
|
}
|
|
25768
26084
|
if (bytes.byteLength === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
25769
26085
|
try {
|
|
25770
26086
|
await ctx.attach({ name, bytes, lead });
|
|
25771
26087
|
} catch (err2) {
|
|
25772
|
-
return `error: the channel refused the upload of ${name}: ${
|
|
26088
|
+
return `error: the channel refused the upload of ${name}: ${describe3(err2)}`;
|
|
25773
26089
|
}
|
|
25774
26090
|
return `attached ${name} (${bytes.byteLength} bytes) to the conversation`;
|
|
25775
26091
|
}
|
|
@@ -25902,7 +26218,7 @@ async function nextTick(wait, ids, ms, signal) {
|
|
|
25902
26218
|
signal?.removeEventListener("abort", end);
|
|
25903
26219
|
}
|
|
25904
26220
|
}
|
|
25905
|
-
var UNAVAILABLE2, NOT_FOUND,
|
|
26221
|
+
var UNAVAILABLE2, NOT_FOUND, CHILD_PRESETS, WRITE_PRESETS, REPO_PRESETS, CHILDREN_PAGES_MAX, spawnRunTool, sendToRunTool, AWAIT_IDS_MAX, awaitRunsTool, listRunsTool, getRunStatusTool, RUN_TOOLS;
|
|
25906
26222
|
var init_runs2 = __esm({
|
|
25907
26223
|
"../../src/tools/runs.ts"() {
|
|
25908
26224
|
"use strict";
|
|
@@ -25917,16 +26233,24 @@ var init_runs2 = __esm({
|
|
|
25917
26233
|
init_runsService();
|
|
25918
26234
|
UNAVAILABLE2 = "run tools are not available in this context.";
|
|
25919
26235
|
NOT_FOUND = "not_found";
|
|
25920
|
-
|
|
26236
|
+
CHILD_PRESETS = () => Object.values(AGENTS).filter((a) => a.name !== "conductor" && a.identity !== "write").map((a) => a.name);
|
|
26237
|
+
WRITE_PRESETS = () => Object.values(AGENTS).filter((a) => a.identity === "write").map((a) => a.name);
|
|
26238
|
+
REPO_PRESETS = () => Object.values(AGENTS).filter((a) => machineNeedsRepo(a.machine)).map((a) => a.name);
|
|
25921
26239
|
CHILDREN_PAGES_MAX = 25;
|
|
25922
26240
|
spawnRunTool = {
|
|
25923
26241
|
name: "spawn_run",
|
|
25924
|
-
description:
|
|
26242
|
+
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
26243
|
inputSchema: {
|
|
25926
26244
|
type: "object",
|
|
25927
26245
|
properties: {
|
|
25928
|
-
preset: {
|
|
25929
|
-
|
|
26246
|
+
preset: {
|
|
26247
|
+
type: "string",
|
|
26248
|
+
description: `The preset the child runs: one of ${CHILD_PRESETS().join(", ")} (a preset that writes \u2014 ${WRITE_PRESETS().join(", ")} \u2014 is refused spawn_identity)`
|
|
26249
|
+
},
|
|
26250
|
+
prompt: {
|
|
26251
|
+
type: "string",
|
|
26252
|
+
description: "What the child should do \u2014 its one new turn after this conversation's text"
|
|
26253
|
+
},
|
|
25930
26254
|
repo: { type: "string", description: "owner/name of the repository the child works in, for a repository preset" },
|
|
25931
26255
|
budget: { type: "integer", description: "The child's wall clock in whole minutes, at least 2 (optional)" }
|
|
25932
26256
|
},
|
|
@@ -25934,9 +26258,10 @@ var init_runs2 = __esm({
|
|
|
25934
26258
|
},
|
|
25935
26259
|
async run(input, ctx) {
|
|
25936
26260
|
const preset = String(input.preset ?? "");
|
|
25937
|
-
if (!Object.hasOwn(AGENTS, preset))
|
|
26261
|
+
if (!Object.hasOwn(AGENTS, preset))
|
|
26262
|
+
return `error: unknown preset "${preset}" \u2014 one of ${CHILD_PRESETS().join(", ")}`;
|
|
25938
26263
|
const prompt = String(input.prompt ?? "").trim();
|
|
25939
|
-
if (!prompt) return "error: prompt is required \u2014 the child
|
|
26264
|
+
if (!prompt) return "error: prompt is required \u2014 what the child should do, after this conversation's text";
|
|
25940
26265
|
const request = { preset, prompt };
|
|
25941
26266
|
if (input.repo !== void 0 && String(input.repo).trim()) request.repo = String(input.repo).trim();
|
|
25942
26267
|
if (input.budget !== void 0) {
|
|
@@ -25946,7 +26271,10 @@ var init_runs2 = __esm({
|
|
|
25946
26271
|
request.budget = budget;
|
|
25947
26272
|
}
|
|
25948
26273
|
const spawn7 = ctx.spawn ?? nullSpawnCapability;
|
|
25949
|
-
const out = await spawn7.spawn(request,
|
|
26274
|
+
const out = await spawn7.spawn(request, {
|
|
26275
|
+
remainingMs: ctx.remainingMs?.() ?? Number.POSITIVE_INFINITY,
|
|
26276
|
+
...ctx.conversation ? { conversation: ctx.conversation() } : {}
|
|
26277
|
+
});
|
|
25950
26278
|
if (out.kind === "refused") return `spawn refused (${out.reason}): ${out.message}`;
|
|
25951
26279
|
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
26280
|
}
|
|
@@ -26673,14 +27001,34 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26673
27001
|
const deadline = now() + (opts.resume ? opts.resume.remainingMs : opts.agent.maxMinutes * 6e4);
|
|
26674
27002
|
const warnAt = deadline - Math.min(3 * 6e4, opts.agent.maxMinutes * 15e3);
|
|
26675
27003
|
toolContext.remainingMs = () => deadline - now();
|
|
27004
|
+
toolContext.conversation = () => messages;
|
|
26676
27005
|
let warned = false;
|
|
26677
27006
|
let sandboxDead = false;
|
|
27007
|
+
let stuckLoop = false;
|
|
27008
|
+
let stuckSig;
|
|
27009
|
+
let stuckStreak = 0;
|
|
27010
|
+
let stuckNudged = false;
|
|
27011
|
+
const trackStuck = (tu, ok2) => {
|
|
27012
|
+
const sig = callSignature(tu.name, redactAndCap(describeToolCall(tu)));
|
|
27013
|
+
if (ok2) {
|
|
27014
|
+
if (sig === stuckSig) {
|
|
27015
|
+
stuckSig = void 0;
|
|
27016
|
+
stuckStreak = 0;
|
|
27017
|
+
stuckNudged = false;
|
|
27018
|
+
}
|
|
27019
|
+
return;
|
|
27020
|
+
}
|
|
27021
|
+
if (sig !== stuckSig) stuckNudged = false;
|
|
27022
|
+
stuckStreak = sig === stuckSig ? stuckStreak + 1 : 1;
|
|
27023
|
+
stuckSig = sig;
|
|
27024
|
+
};
|
|
26678
27025
|
let turn = 0;
|
|
26679
27026
|
const wouldStep = (turns, iteration) => turns < opts.agent.maxTurns && iteration < opts.agent.maxTurns * 2 && now() < deadline && !control?.requested;
|
|
26680
27027
|
const pendingFollowUps = () => (opts.inbox?.size ?? 0) > 0;
|
|
26681
27028
|
let inboxConsumedSeq = opts.resume?.inboxConsumedSeq ?? 0;
|
|
26682
|
-
const drainFollowUps = (superseded = false) => {
|
|
27029
|
+
const drainFollowUps = async (superseded = false) => {
|
|
26683
27030
|
const inputs = opts.inbox?.drain() ?? [];
|
|
27031
|
+
const stagedLine = opts.stageFollowUps ? await opts.stageFollowUps(inputs) : "";
|
|
26684
27032
|
for (const input of inputs) {
|
|
26685
27033
|
if (input.ledgerSeq !== void 0 && input.ledgerSeq > inboxConsumedSeq) inboxConsumedSeq = input.ledgerSeq;
|
|
26686
27034
|
const source2 = {
|
|
@@ -26691,7 +27039,10 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26691
27039
|
emit({ type: "input", text: redactSecrets(input.text), ...Object.keys(source2).length > 0 ? { source: source2 } : {} });
|
|
26692
27040
|
note("follow_up", `follow-up folded in: ${redactSecrets(followUpSnippet(input))}`);
|
|
26693
27041
|
}
|
|
26694
|
-
const
|
|
27042
|
+
const prompt = followUpPrompt(inputs, { superseded });
|
|
27043
|
+
const parts = [{ type: "text", text: stagedLine ? `${prompt}
|
|
27044
|
+
|
|
27045
|
+
${stagedLine}` : prompt }];
|
|
26695
27046
|
for (const input of inputs) {
|
|
26696
27047
|
for (const img of input.images ?? []) parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
|
|
26697
27048
|
for (const doc of input.documents ?? [])
|
|
@@ -26708,12 +27059,15 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26708
27059
|
const body = async (callSpan) => {
|
|
26709
27060
|
if (announceIt) announce(tu, callSpan?.id);
|
|
26710
27061
|
const spanId = callSpan ? { spanId: callSpan.id } : {};
|
|
26711
|
-
const settle2 = (ok2, extra = {}) =>
|
|
26712
|
-
|
|
26713
|
-
ok:
|
|
26714
|
-
|
|
26715
|
-
|
|
26716
|
-
|
|
27062
|
+
const settle2 = (ok2, extra = {}) => {
|
|
27063
|
+
trackStuck(tu, ok2);
|
|
27064
|
+
callSpan?.end(ok2 ? "ok" : "error", {
|
|
27065
|
+
callId: tu.id,
|
|
27066
|
+
ok: ok2,
|
|
27067
|
+
...extra.exitCode !== void 0 ? { exitCode: extra.exitCode } : {},
|
|
27068
|
+
...extra.infra ? { infra: true } : {}
|
|
27069
|
+
});
|
|
27070
|
+
};
|
|
26717
27071
|
const tool = toolsByName.get(tu.name);
|
|
26718
27072
|
if (!tool) {
|
|
26719
27073
|
emit({
|
|
@@ -26897,7 +27251,7 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26897
27251
|
turn++;
|
|
26898
27252
|
if (text2) emit({ type: "assistant", text: redactSecrets(text2) });
|
|
26899
27253
|
messages.push({ role: "assistant", content: result.content });
|
|
26900
|
-
messages.push({ role: "user", content: drainFollowUps(true) });
|
|
27254
|
+
messages.push({ role: "user", content: await drainFollowUps(true) });
|
|
26901
27255
|
continue;
|
|
26902
27256
|
}
|
|
26903
27257
|
return text2 || "_(no response)_";
|
|
@@ -26922,6 +27276,10 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26922
27276
|
reportedUpTo = messages.length;
|
|
26923
27277
|
}
|
|
26924
27278
|
const results = await dispatchToolUses(toolUses);
|
|
27279
|
+
if (!stuckNudged && stuckStreak >= STUCK_NUDGE_AT) {
|
|
27280
|
+
stuckNudged = true;
|
|
27281
|
+
results.push({ type: "text", text: stuckLoopNudge() });
|
|
27282
|
+
}
|
|
26925
27283
|
if (!warned && now() >= warnAt) {
|
|
26926
27284
|
warned = true;
|
|
26927
27285
|
const minutesLeft = Math.max(1, Math.round((deadline - now()) / 6e4));
|
|
@@ -26929,12 +27287,16 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26929
27287
|
results.push({ type: "text", text: wrapUpInstruction(minutesLeft) });
|
|
26930
27288
|
}
|
|
26931
27289
|
const dead = execTracker.consecutiveInfraFailures >= MAX_CONSECUTIVE_INFRA_FAILURES;
|
|
26932
|
-
if (pendingFollowUps() && !dead && wouldStep(turn, iteration + 1)) results.push(...drainFollowUps());
|
|
27290
|
+
if (pendingFollowUps() && !dead && wouldStep(turn, iteration + 1)) results.push(...await drainFollowUps());
|
|
26933
27291
|
messages.push({ role: "user", content: results });
|
|
26934
27292
|
if (dead) {
|
|
26935
27293
|
sandboxDead = true;
|
|
26936
27294
|
break;
|
|
26937
27295
|
}
|
|
27296
|
+
if (stuckStreak >= STUCK_WRITE_UP_AT) {
|
|
27297
|
+
stuckLoop = true;
|
|
27298
|
+
break;
|
|
27299
|
+
}
|
|
26938
27300
|
}
|
|
26939
27301
|
narrateHeld();
|
|
26940
27302
|
if (control?.requested === "soft") {
|
|
@@ -26946,6 +27308,11 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
26946
27308
|
note("sandbox_dead", `${diagnosis} \u2014 aborting instead of retrying into a dead sandbox`);
|
|
26947
27309
|
return await finishSandboxDead(complete2, opts, messages, system, diagnosis);
|
|
26948
27310
|
}
|
|
27311
|
+
if (stuckLoop) {
|
|
27312
|
+
note("stuck_loop", stuckLoopNote());
|
|
27313
|
+
const text2 = await runFinale(complete2, opts, messages, system, stuckLoopInstruction());
|
|
27314
|
+
return stuckLoopAnswer(text2);
|
|
27315
|
+
}
|
|
26949
27316
|
const wasTimeout = now() >= deadline;
|
|
26950
27317
|
const elapsedMs = opts.agent.maxMinutes * 6e4 - (deadline - now());
|
|
26951
27318
|
const pace = turnGuardPace(turn, elapsedMs);
|
|
@@ -27047,7 +27414,7 @@ function withSpanId(e, spanId) {
|
|
|
27047
27414
|
return e;
|
|
27048
27415
|
}
|
|
27049
27416
|
}
|
|
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;
|
|
27417
|
+
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
27418
|
var init_runner = __esm({
|
|
27052
27419
|
"../../src/runner.ts"() {
|
|
27053
27420
|
"use strict";
|
|
@@ -27058,6 +27425,7 @@ var init_runner = __esm({
|
|
|
27058
27425
|
init_tracingExecutor();
|
|
27059
27426
|
init_workspace();
|
|
27060
27427
|
init_formatDuration();
|
|
27428
|
+
init_runFriction();
|
|
27061
27429
|
MAX_CONSECUTIVE_INFRA_FAILURES = 2;
|
|
27062
27430
|
HardStopError = class extends Error {
|
|
27063
27431
|
constructor() {
|
|
@@ -27083,6 +27451,14 @@ ${text}` : `Stopped at the ${maxMinutes}-minute budget without finishing. Partia
|
|
|
27083
27451
|
turnGuardAnswer = (text, pace) => text ? `\u26A0\uFE0F _Stopped after ${pace} \u2014 that pace looks like a loop; findings so far:_
|
|
27084
27452
|
|
|
27085
27453
|
${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.`;
|
|
27454
|
+
STUCK_NUDGE_AT = 3;
|
|
27455
|
+
STUCK_WRITE_UP_AT = 6;
|
|
27456
|
+
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.`;
|
|
27457
|
+
stuckLoopNote = () => `stuck loop: the same call failed ${STUCK_WRITE_UP_AT} times identically \u2014 writing up findings so far`;
|
|
27458
|
+
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}`;
|
|
27459
|
+
stuckLoopAnswer = (text) => text ? `\u26A0\uFE0F _Stopped after the same call failed ${STUCK_WRITE_UP_AT} times identically \u2014 findings so far:_
|
|
27460
|
+
|
|
27461
|
+
${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
27462
|
softStopAnswer = (text) => text ? `\u23F9 _Stopped early by an operator (soft stop) \u2014 findings so far:_
|
|
27087
27463
|
|
|
27088
27464
|
${text}` : "\u23F9 Stopped early by an operator (soft stop) before any findings were written. Partial work may exist in the workspace.";
|
|
@@ -27925,11 +28301,107 @@ var init_messages = __esm({
|
|
|
27925
28301
|
init_runEvents();
|
|
27926
28302
|
init_runRecord();
|
|
27927
28303
|
init_reply();
|
|
28304
|
+
init_textTurns();
|
|
27928
28305
|
CONTEXT_MAX_ITEMS = 20;
|
|
27929
28306
|
CONTEXT_MAX_BYTES = 256 * 1024;
|
|
27930
28307
|
}
|
|
27931
28308
|
});
|
|
27932
28309
|
|
|
28310
|
+
// ../../src/core/dispatch/staging.ts
|
|
28311
|
+
function stagedBasename(index, name) {
|
|
28312
|
+
return `${index}-${safeBasename(name)}`;
|
|
28313
|
+
}
|
|
28314
|
+
function pullCommandFor(url, basename4) {
|
|
28315
|
+
return `mkdir -p ${ATTACHMENTS_DIR} && curl -fsS -o ${shellQuote(`${ATTACHMENTS_DIR}/${basename4}`)} ${shellQuote(url)}`;
|
|
28316
|
+
}
|
|
28317
|
+
function excludeCommand() {
|
|
28318
|
+
const entry = shellQuote(`${ATTACHMENTS_DIR}/`);
|
|
28319
|
+
return `mkdir -p .git/info && { grep -qxF ${entry} .git/info/exclude 2>/dev/null || echo ${entry} >> .git/info/exclude; }`;
|
|
28320
|
+
}
|
|
28321
|
+
function formatSize(bytes) {
|
|
28322
|
+
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
|
|
28323
|
+
if (bytes >= 1e6) return `${Math.round(bytes / 1e6)} MB`;
|
|
28324
|
+
if (bytes >= 1e3) return `${Math.round(bytes / 1e3)} KB`;
|
|
28325
|
+
return `${bytes} B`;
|
|
28326
|
+
}
|
|
28327
|
+
function attachmentsLine(outcomes) {
|
|
28328
|
+
const landed = outcomes.filter((o) => o.error === void 0);
|
|
28329
|
+
const failed = outcomes.filter((o) => o.error !== void 0);
|
|
28330
|
+
const parts = [];
|
|
28331
|
+
if (landed.length > 0) {
|
|
28332
|
+
parts.push(
|
|
28333
|
+
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map((o) => `${o.basename} (${formatSize(o.file.size)}, ${o.file.type})`).join(", ")}`
|
|
28334
|
+
);
|
|
28335
|
+
}
|
|
28336
|
+
for (const o of failed) parts.push(`${o.file.name} could not be staged: ${o.error}`);
|
|
28337
|
+
return parts.join(". ");
|
|
28338
|
+
}
|
|
28339
|
+
function noWorkspaceLine(files) {
|
|
28340
|
+
return files.map(
|
|
28341
|
+
(f) => `this agent has no workspace for ${f.name} (${formatSize(f.size)}, ${f.type}); ask \`agent:coding\` to work with it`
|
|
28342
|
+
).join(". ");
|
|
28343
|
+
}
|
|
28344
|
+
function stagingIndex() {
|
|
28345
|
+
let n2 = 0;
|
|
28346
|
+
return () => ++n2;
|
|
28347
|
+
}
|
|
28348
|
+
async function copyStaged(files, deps) {
|
|
28349
|
+
return Promise.all(
|
|
28350
|
+
files.map(async (file) => {
|
|
28351
|
+
const index = deps.nextIndex();
|
|
28352
|
+
const basename4 = stagedBasename(index, file.name);
|
|
28353
|
+
const key = inboundKey(deps.threadKey, file.messageId, index, file.name);
|
|
28354
|
+
try {
|
|
28355
|
+
await deps.store.copyFromUrl({ url: file.url, size: file.size, key });
|
|
28356
|
+
deps.publish?.({
|
|
28357
|
+
type: "artifact",
|
|
28358
|
+
direction: "in",
|
|
28359
|
+
key,
|
|
28360
|
+
name: file.name,
|
|
28361
|
+
size: file.size,
|
|
28362
|
+
contentType: file.type
|
|
28363
|
+
});
|
|
28364
|
+
return { file, basename: basename4, key };
|
|
28365
|
+
} catch (err2) {
|
|
28366
|
+
return { file, basename: basename4, key, error: `the copy into the store failed: ${describe4(err2)}` };
|
|
28367
|
+
}
|
|
28368
|
+
})
|
|
28369
|
+
);
|
|
28370
|
+
}
|
|
28371
|
+
async function pullStaged(outcomes, deps) {
|
|
28372
|
+
const landed = outcomes.filter((o) => o.error === void 0);
|
|
28373
|
+
if (landed.length === 0) return [...outcomes];
|
|
28374
|
+
const timeoutMs = deps.timeoutMs ?? BASH_TIMEOUT_MAX_MS;
|
|
28375
|
+
const opts = { timeoutMs, ...deps.signal ? { signal: deps.signal } : {} };
|
|
28376
|
+
if (deps.resident) await deps.executor.exec(excludeCommand(), opts);
|
|
28377
|
+
const pulled = /* @__PURE__ */ new Map();
|
|
28378
|
+
for (const o of landed) {
|
|
28379
|
+
const url = await deps.store.presignGet(o.key);
|
|
28380
|
+
const out = await deps.executor.exec(pullCommandFor(url, o.basename), opts);
|
|
28381
|
+
pulled.set(o.key, parseExitPrefix(out).failed ? `the pull into the workspace failed: ${out.trim()}` : void 0);
|
|
28382
|
+
}
|
|
28383
|
+
return outcomes.map((o) => {
|
|
28384
|
+
const error = o.error ?? pulled.get(o.key);
|
|
28385
|
+
return error === void 0 ? { file: o.file, basename: o.basename, key: o.key } : { ...o, error };
|
|
28386
|
+
});
|
|
28387
|
+
}
|
|
28388
|
+
async function stageIntoWorkspace(files, deps) {
|
|
28389
|
+
const outcomes = await pullStaged(await copyStaged(files, deps), deps);
|
|
28390
|
+
return { line: attachmentsLine(outcomes), outcomes };
|
|
28391
|
+
}
|
|
28392
|
+
var ATTACHMENTS_DIR, describe4;
|
|
28393
|
+
var init_staging = __esm({
|
|
28394
|
+
"../../src/core/dispatch/staging.ts"() {
|
|
28395
|
+
"use strict";
|
|
28396
|
+
init_keys();
|
|
28397
|
+
init_bashTimeout();
|
|
28398
|
+
init_shellQuote();
|
|
28399
|
+
init_runEvents();
|
|
28400
|
+
ATTACHMENTS_DIR = "attachments";
|
|
28401
|
+
describe4 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
28402
|
+
}
|
|
28403
|
+
});
|
|
28404
|
+
|
|
27933
28405
|
// ../../src/core/modelProxy/runBearers.ts
|
|
27934
28406
|
import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
27935
28407
|
function token(runId3, secret2) {
|
|
@@ -28525,6 +28997,8 @@ async function registerRun(deps, ctx) {
|
|
|
28525
28997
|
admitted,
|
|
28526
28998
|
parentRunId,
|
|
28527
28999
|
coordinator,
|
|
29000
|
+
seed,
|
|
29001
|
+
seedTurns,
|
|
28528
29002
|
agentSource,
|
|
28529
29003
|
route: route3
|
|
28530
29004
|
} = ctx;
|
|
@@ -28557,7 +29031,8 @@ async function registerRun(deps, ctx) {
|
|
|
28557
29031
|
...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
|
|
28558
29032
|
...msg.userName !== void 0 ? { userName: msg.userName } : {},
|
|
28559
29033
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
28560
|
-
...coordinatorFields(coordinator)
|
|
29034
|
+
...coordinatorFields(coordinator),
|
|
29035
|
+
...seed !== void 0 ? { seed } : {}
|
|
28561
29036
|
},
|
|
28562
29037
|
// Under the run's id, at the card's start (the reservation's, or the
|
|
28563
29038
|
// carried row's) — a resume replays its events, a restart starts them
|
|
@@ -28606,7 +29081,7 @@ async function registerRun(deps, ctx) {
|
|
|
28606
29081
|
if (!resume) publishMeta(repoCtx);
|
|
28607
29082
|
if (!resume && route3) registry.publish(run2.id, { type: "route", ...route3, at: clock3() });
|
|
28608
29083
|
if (!resume && deps.config.config.runHistory?.includeContext !== false) {
|
|
28609
|
-
for (const text of contextMessageTexts(history, humanize)) publishText("context", text);
|
|
29084
|
+
for (const text of contextMessageTexts(seedTurns ?? history, humanize)) publishText("context", text);
|
|
28610
29085
|
}
|
|
28611
29086
|
return { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta };
|
|
28612
29087
|
}
|
|
@@ -28628,7 +29103,8 @@ async function reserveRun(deps, ctx) {
|
|
|
28628
29103
|
admitted,
|
|
28629
29104
|
root,
|
|
28630
29105
|
parentRunId,
|
|
28631
|
-
coordinator
|
|
29106
|
+
coordinator,
|
|
29107
|
+
seed
|
|
28632
29108
|
} = ctx;
|
|
28633
29109
|
if (!resume && !restart) {
|
|
28634
29110
|
const requestRow = durableInboxMessage(msg, msg.text, receivedAt);
|
|
@@ -28656,6 +29132,7 @@ async function reserveRun(deps, ctx) {
|
|
|
28656
29132
|
profile,
|
|
28657
29133
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
28658
29134
|
...coordinatorFields(coordinator),
|
|
29135
|
+
...seed !== void 0 ? { seed } : {},
|
|
28659
29136
|
request: requestRow
|
|
28660
29137
|
},
|
|
28661
29138
|
card: card.handle ?? null,
|
|
@@ -28885,7 +29362,8 @@ async function claimRun(deps, ctx) {
|
|
|
28885
29362
|
clock: clock3,
|
|
28886
29363
|
root,
|
|
28887
29364
|
parentRunId,
|
|
28888
|
-
coordinator
|
|
29365
|
+
coordinator,
|
|
29366
|
+
seed
|
|
28889
29367
|
} = ctx;
|
|
28890
29368
|
const { resident, binding } = selection;
|
|
28891
29369
|
let ledgerRun = ctx.ledgerRun;
|
|
@@ -28915,6 +29393,7 @@ async function claimRun(deps, ctx) {
|
|
|
28915
29393
|
profile,
|
|
28916
29394
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
28917
29395
|
...coordinatorFields(coordinator),
|
|
29396
|
+
...seed !== void 0 ? { seed } : {},
|
|
28918
29397
|
selection: resident === true ? "resident" : "sandbox",
|
|
28919
29398
|
...binding?.workspace !== void 0 ? { workspace: binding.workspace } : {},
|
|
28920
29399
|
...requestRow !== void 0 ? { request: requestRow } : {}
|
|
@@ -29113,6 +29592,157 @@ var init_container = __esm({
|
|
|
29113
29592
|
}
|
|
29114
29593
|
});
|
|
29115
29594
|
|
|
29595
|
+
// ../../src/core/harness/pi/extensionSource.ts
|
|
29596
|
+
var BLOCKED_AT_DOOR_PREFIX, BLOCKED_UNAVAILABLE_PREFIX, PI_EXTENSION_SOURCE;
|
|
29597
|
+
var init_extensionSource = __esm({
|
|
29598
|
+
"../../src/core/harness/pi/extensionSource.ts"() {
|
|
29599
|
+
"use strict";
|
|
29600
|
+
BLOCKED_AT_DOOR_PREFIX = "authorization refused at the door: ";
|
|
29601
|
+
BLOCKED_UNAVAILABLE_PREFIX = "authorization unavailable: ";
|
|
29602
|
+
PI_EXTENSION_SOURCE = `// Switchboard's pi harness extension. Written into the run's directory by the
|
|
29603
|
+
// bot before pi starts; loaded with \`-e\`. Imports nothing.
|
|
29604
|
+
|
|
29605
|
+
const BEARER_ENV = "SWITCHBOARD_RUN_BEARER";
|
|
29606
|
+
const URL_ENV = "SWITCHBOARD_HARNESS_URL";
|
|
29607
|
+
/** How long the tool_call hook keeps asking an unreachable bot before it blocks. */
|
|
29608
|
+
const AUTHORIZE_WAIT_MS = 90_000;
|
|
29609
|
+
const AUTHORIZE_RETRY_MS = 2_000;
|
|
29610
|
+
|
|
29611
|
+
function settings() {
|
|
29612
|
+
const url = process.env[URL_ENV];
|
|
29613
|
+
const bearer = process.env[BEARER_ENV];
|
|
29614
|
+
if (!url || !bearer) throw new Error("switchboard harness: " + URL_ENV + " and " + BEARER_ENV + " must be set");
|
|
29615
|
+
return { base: url.replace(/\\/$/, ""), bearer };
|
|
29616
|
+
}
|
|
29617
|
+
|
|
29618
|
+
/** The bot answered, and said no: the status rides along so a caller can tell
|
|
29619
|
+
* a verdict from a bot it could not reach. */
|
|
29620
|
+
class HarnessAnswerError extends Error {
|
|
29621
|
+
constructor(message, status) {
|
|
29622
|
+
super(message);
|
|
29623
|
+
this.status = status;
|
|
29624
|
+
}
|
|
29625
|
+
}
|
|
29626
|
+
|
|
29627
|
+
async function call(method, path, body, signal) {
|
|
29628
|
+
const { base, bearer } = settings();
|
|
29629
|
+
const res = await fetch(base + path, {
|
|
29630
|
+
method,
|
|
29631
|
+
headers: { authorization: "Bearer " + bearer, "content-type": "application/json", accept: "application/json" },
|
|
29632
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
29633
|
+
signal,
|
|
29634
|
+
});
|
|
29635
|
+
const text = await res.text();
|
|
29636
|
+
let json;
|
|
29637
|
+
try {
|
|
29638
|
+
json = JSON.parse(text);
|
|
29639
|
+
} catch {
|
|
29640
|
+
throw new Error("switchboard harness: " + method + " " + path + " answered " + res.status + " with a body that is not JSON");
|
|
29641
|
+
}
|
|
29642
|
+
if (!res.ok) {
|
|
29643
|
+
throw new HarnessAnswerError(
|
|
29644
|
+
"switchboard harness: " + method + " " + path + " answered " + res.status + ": " + (json.error || text),
|
|
29645
|
+
res.status,
|
|
29646
|
+
);
|
|
29647
|
+
}
|
|
29648
|
+
return json;
|
|
29649
|
+
}
|
|
29650
|
+
|
|
29651
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
29652
|
+
|
|
29653
|
+
/** The bot's verdict on one tool call. A verdict answers at once, and so does
|
|
29654
|
+
* a refusal at the door \u2014 a 4xx: the bearer wrong, expired or revoked, the
|
|
29655
|
+
* run not on the harness \u2014 since nothing later changes it. A bot that cannot
|
|
29656
|
+
* be reached or is failing (a network error, a 5xx, a body that is not JSON)
|
|
29657
|
+
* is asked again every two seconds until the wait is up, then the call is
|
|
29658
|
+
* blocked naming why. */
|
|
29659
|
+
async function authorize(event) {
|
|
29660
|
+
const started = Date.now();
|
|
29661
|
+
let lastError = "";
|
|
29662
|
+
for (;;) {
|
|
29663
|
+
try {
|
|
29664
|
+
return await call("POST", "/harness/authorize", {
|
|
29665
|
+
toolCallId: event.toolCallId,
|
|
29666
|
+
tool: event.toolName,
|
|
29667
|
+
input: event.input,
|
|
29668
|
+
});
|
|
29669
|
+
} catch (err) {
|
|
29670
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
29671
|
+
const status = err instanceof HarnessAnswerError ? err.status : undefined;
|
|
29672
|
+
if (status !== undefined && status >= 400 && status < 500) {
|
|
29673
|
+
return { allow: false, reason: "${BLOCKED_AT_DOOR_PREFIX}" + lastError };
|
|
29674
|
+
}
|
|
29675
|
+
if (Date.now() - started >= AUTHORIZE_WAIT_MS) {
|
|
29676
|
+
return { allow: false, reason: "${BLOCKED_UNAVAILABLE_PREFIX}the bot did not answer for 90 s (" + lastError + ")" };
|
|
29677
|
+
}
|
|
29678
|
+
await sleep(AUTHORIZE_RETRY_MS);
|
|
29679
|
+
}
|
|
29680
|
+
}
|
|
29681
|
+
}
|
|
29682
|
+
|
|
29683
|
+
function relayTool(def) {
|
|
29684
|
+
return {
|
|
29685
|
+
name: def.name,
|
|
29686
|
+
label: def.name,
|
|
29687
|
+
description: def.description,
|
|
29688
|
+
parameters: def.inputSchema,
|
|
29689
|
+
async execute(toolCallId, params, signal) {
|
|
29690
|
+
const answer = await call("POST", "/harness/tool", { toolCallId, tool: def.name, input: params }, signal);
|
|
29691
|
+
if (answer.isError) throw new Error(answer.content.map((c) => (c.type === "text" ? c.text : "")).join("\\n"));
|
|
29692
|
+
return { content: answer.content, details: {} };
|
|
29693
|
+
},
|
|
29694
|
+
};
|
|
29695
|
+
}
|
|
29696
|
+
|
|
29697
|
+
export default async function switchboardHarness(pi) {
|
|
29698
|
+
const { tools } = await call("GET", "/harness/tools");
|
|
29699
|
+
for (const def of tools) pi.registerTool(relayTool(def));
|
|
29700
|
+
pi.on("tool_call", async (event) => {
|
|
29701
|
+
const verdict = await authorize(event);
|
|
29702
|
+
if (!verdict.allow) return { block: true, reason: verdict.reason };
|
|
29703
|
+
return undefined;
|
|
29704
|
+
});
|
|
29705
|
+
}
|
|
29706
|
+
`;
|
|
29707
|
+
}
|
|
29708
|
+
});
|
|
29709
|
+
|
|
29710
|
+
// ../../src/core/harness/pi/protocol.ts
|
|
29711
|
+
function parsePiLine(line3) {
|
|
29712
|
+
try {
|
|
29713
|
+
const value = JSON.parse(line3);
|
|
29714
|
+
if (typeof value === "object" && value !== null && typeof value.type === "string") {
|
|
29715
|
+
return value;
|
|
29716
|
+
}
|
|
29717
|
+
} catch {
|
|
29718
|
+
}
|
|
29719
|
+
return void 0;
|
|
29720
|
+
}
|
|
29721
|
+
function piAnsweredWithoutRunning(event) {
|
|
29722
|
+
if (event.type !== "tool_execution_end" || event.isError !== true) return void 0;
|
|
29723
|
+
const tool = typeof event.toolName === "string" ? event.toolName : "";
|
|
29724
|
+
const result = event.result;
|
|
29725
|
+
const content = Array.isArray(result?.content) ? result.content : [];
|
|
29726
|
+
const text = content.filter(
|
|
29727
|
+
(p) => typeof p === "object" && p !== null && p.type === "text" && typeof p.text === "string"
|
|
29728
|
+
).map((p) => p.text).join("\n");
|
|
29729
|
+
const validation = `Validation failed for tool "${tool}":
|
|
29730
|
+
`;
|
|
29731
|
+
if (text.startsWith(validation)) {
|
|
29732
|
+
const problems = text.slice(validation.length).split("\n\n")[0].split("\n").map((line3) => line3.replace(/^\s*-\s*/, "").trim()).filter((line3) => line3.length > 0);
|
|
29733
|
+
return `its arguments failed pi's validation (${problems.join("; ")})`;
|
|
29734
|
+
}
|
|
29735
|
+
if (text.startsWith(`Tool call "${tool}" was not executed: the response hit the output token limit`))
|
|
29736
|
+
return "its arguments were cut by the output token limit";
|
|
29737
|
+
if (text === `Tool ${tool} not found`) return "the tool is not on pi's list";
|
|
29738
|
+
return void 0;
|
|
29739
|
+
}
|
|
29740
|
+
var init_protocol = __esm({
|
|
29741
|
+
"../../src/core/harness/pi/protocol.ts"() {
|
|
29742
|
+
"use strict";
|
|
29743
|
+
}
|
|
29744
|
+
});
|
|
29745
|
+
|
|
29116
29746
|
// ../../src/core/harness/pi/bridge.ts
|
|
29117
29747
|
function describePiToolCall(tool, args) {
|
|
29118
29748
|
const input = isRecord3(args) ? args : void 0;
|
|
@@ -29140,6 +29770,8 @@ var init_bridge2 = __esm({
|
|
|
29140
29770
|
"use strict";
|
|
29141
29771
|
init_formatDuration();
|
|
29142
29772
|
init_runEvents();
|
|
29773
|
+
init_extensionSource();
|
|
29774
|
+
init_protocol();
|
|
29143
29775
|
PI_EVENT_DISPOSITION = {
|
|
29144
29776
|
response: "structure",
|
|
29145
29777
|
agent_start: "structure",
|
|
@@ -29182,8 +29814,23 @@ var init_bridge2 = __esm({
|
|
|
29182
29814
|
/** Text beside a bookkeeping-only call, held until the next message decides (run-loop item 15). */
|
|
29183
29815
|
heldAnswer;
|
|
29184
29816
|
assistantStartedAt;
|
|
29817
|
+
/** The calls under way: their span, their tool, and whether their end is
|
|
29818
|
+
* judged against the gate (harness-pi item 7). */
|
|
29185
29819
|
openTools = /* @__PURE__ */ new Map();
|
|
29820
|
+
/** The calls the gate saw — the extension asked `/harness/authorize` for
|
|
29821
|
+
* them (`gateSaw`) — not yet ended. */
|
|
29822
|
+
vetted = /* @__PURE__ */ new Set();
|
|
29823
|
+
/** Whether a call starting now is judged against the gate when it ends.
|
|
29824
|
+
* The harness turns it off while catching up on a re-attach: those calls
|
|
29825
|
+
* were vetted by the bot generation that died, and this one never heard. */
|
|
29826
|
+
judgeGate = true;
|
|
29186
29827
|
summarizationRetries = 0;
|
|
29828
|
+
/** The gate saw this call: the extension's `tool_call` hook asked the bot
|
|
29829
|
+
* for it, whatever the answer. Called from the authorize route, before pi
|
|
29830
|
+
* runs the tool — so it always precedes the call's `tool_execution_end`. */
|
|
29831
|
+
gateSaw(callId) {
|
|
29832
|
+
this.vetted.add(callId);
|
|
29833
|
+
}
|
|
29187
29834
|
/** The run's answer as it stands: the last text-only assistant message, else a held bookkeeping text. */
|
|
29188
29835
|
answer() {
|
|
29189
29836
|
return this.answerText ?? this.heldAnswer;
|
|
@@ -29222,7 +29869,7 @@ var init_bridge2 = __esm({
|
|
|
29222
29869
|
this.onToolStart(event);
|
|
29223
29870
|
break;
|
|
29224
29871
|
case "tool_execution_end":
|
|
29225
|
-
this.onToolEnd(event);
|
|
29872
|
+
this.onToolEnd(event, out);
|
|
29226
29873
|
break;
|
|
29227
29874
|
case "compaction_end":
|
|
29228
29875
|
this.onCompactionEnd(event);
|
|
@@ -29285,7 +29932,7 @@ var init_bridge2 = __esm({
|
|
|
29285
29932
|
const tool = str5(event.toolName);
|
|
29286
29933
|
const callId = str5(event.toolCallId);
|
|
29287
29934
|
const span = this.deps.agentSpan?.start(`tool.${tool}`);
|
|
29288
|
-
this.openTools.set(callId, { span, tool });
|
|
29935
|
+
this.openTools.set(callId, { span, tool, judged: this.judgeGate });
|
|
29289
29936
|
this.toolCalls++;
|
|
29290
29937
|
const input = isRecord3(event.args) ? event.args : void 0;
|
|
29291
29938
|
const command2 = tool === "bash" && typeof input?.command === "string" ? { command: redactAndCap(input.command, COMMAND_CAP) } : {};
|
|
@@ -29298,7 +29945,7 @@ var init_bridge2 = __esm({
|
|
|
29298
29945
|
...span ? { spanId: span.id } : {}
|
|
29299
29946
|
});
|
|
29300
29947
|
}
|
|
29301
|
-
onToolEnd(event) {
|
|
29948
|
+
onToolEnd(event, out) {
|
|
29302
29949
|
const callId = str5(event.toolCallId);
|
|
29303
29950
|
const open = this.openTools.get(callId);
|
|
29304
29951
|
this.openTools.delete(callId);
|
|
@@ -29321,6 +29968,23 @@ var init_bridge2 = __esm({
|
|
|
29321
29968
|
ok: ok2,
|
|
29322
29969
|
...exit.exitCode !== void 0 ? { exitCode: exit.exitCode } : {}
|
|
29323
29970
|
});
|
|
29971
|
+
const seen = this.vetted.delete(callId);
|
|
29972
|
+
if (!seen && open?.judged) {
|
|
29973
|
+
const rejection = piAnsweredWithoutRunning(event);
|
|
29974
|
+
if (rejection !== void 0) {
|
|
29975
|
+
this.note(
|
|
29976
|
+
"harness_error",
|
|
29977
|
+
`pi answered the ${tool} call ${callId} itself, before the gate: ${redactAndCap(rejection, 300)}; nothing ran`
|
|
29978
|
+
);
|
|
29979
|
+
} else if (isError && (text.startsWith(BLOCKED_AT_DOOR_PREFIX) || text.startsWith(BLOCKED_UNAVAILABLE_PREFIX))) {
|
|
29980
|
+
this.note(
|
|
29981
|
+
"harness_error",
|
|
29982
|
+
`the extension blocked the ${tool} call ${callId} without the gate's verdict: ${redactAndCap(text, 300)}; nothing ran`
|
|
29983
|
+
);
|
|
29984
|
+
} else {
|
|
29985
|
+
out.gateBypassed = { callId, tool };
|
|
29986
|
+
}
|
|
29987
|
+
}
|
|
29324
29988
|
}
|
|
29325
29989
|
onCompactionEnd(event) {
|
|
29326
29990
|
const result = isRecord3(event.result) ? event.result : void 0;
|
|
@@ -29360,6 +30024,7 @@ var init_bridge2 = __esm({
|
|
|
29360
30024
|
this.emit({ type: "tool_result", tool: open.tool, ok: false, callId, summary: redactAndCap(reason) });
|
|
29361
30025
|
}
|
|
29362
30026
|
this.openTools.clear();
|
|
30027
|
+
this.vetted.clear();
|
|
29363
30028
|
}
|
|
29364
30029
|
};
|
|
29365
30030
|
}
|
|
@@ -29536,120 +30201,10 @@ var init_mirror = __esm({
|
|
|
29536
30201
|
}
|
|
29537
30202
|
});
|
|
29538
30203
|
|
|
29539
|
-
// ../../src/core/harness/pi/extensionSource.ts
|
|
29540
|
-
var PI_EXTENSION_SOURCE;
|
|
29541
|
-
var init_extensionSource = __esm({
|
|
29542
|
-
"../../src/core/harness/pi/extensionSource.ts"() {
|
|
29543
|
-
"use strict";
|
|
29544
|
-
PI_EXTENSION_SOURCE = `// Switchboard's pi harness extension. Written into the run's directory by the
|
|
29545
|
-
// bot before pi starts; loaded with \`-e\`. Imports nothing.
|
|
29546
|
-
|
|
29547
|
-
const BEARER_ENV = "SWITCHBOARD_RUN_BEARER";
|
|
29548
|
-
const URL_ENV = "SWITCHBOARD_HARNESS_URL";
|
|
29549
|
-
/** How long the tool_call hook keeps asking an unreachable bot before it blocks. */
|
|
29550
|
-
const AUTHORIZE_WAIT_MS = 90_000;
|
|
29551
|
-
const AUTHORIZE_RETRY_MS = 2_000;
|
|
29552
|
-
|
|
29553
|
-
function settings() {
|
|
29554
|
-
const url = process.env[URL_ENV];
|
|
29555
|
-
const bearer = process.env[BEARER_ENV];
|
|
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 + ")" };
|
|
29619
|
-
}
|
|
29620
|
-
await sleep(AUTHORIZE_RETRY_MS);
|
|
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
|
-
`;
|
|
29649
|
-
}
|
|
29650
|
-
});
|
|
29651
|
-
|
|
29652
30204
|
// ../../src/core/harness/pi/process.ts
|
|
30205
|
+
function piBuiltinToolsFor(identity2) {
|
|
30206
|
+
return identity2 === "write" ? PI_BUILTIN_TOOLS : PI_READ_TOOLS;
|
|
30207
|
+
}
|
|
29653
30208
|
function piThinkingLevel(effort2) {
|
|
29654
30209
|
return effort2;
|
|
29655
30210
|
}
|
|
@@ -29679,7 +30234,7 @@ function piLaunchArgs(spec) {
|
|
|
29679
30234
|
"-e",
|
|
29680
30235
|
spec.paths.extension,
|
|
29681
30236
|
"--tools",
|
|
29682
|
-
[...
|
|
30237
|
+
[...piBuiltinToolsFor(spec.identity), ...spec.relayTools].join(","),
|
|
29683
30238
|
"--provider",
|
|
29684
30239
|
PROXY_PROVIDER,
|
|
29685
30240
|
"--model",
|
|
@@ -29698,6 +30253,13 @@ function piLaunchEnv(spec, bearer) {
|
|
|
29698
30253
|
PI_TELEMETRY: "0"
|
|
29699
30254
|
};
|
|
29700
30255
|
}
|
|
30256
|
+
function takesAdaptiveThinking(modelId) {
|
|
30257
|
+
const version2 = /(?:^|-)(\d{1,2})(?:-(\d{1,2}))?(?=-|$)/.exec(modelId);
|
|
30258
|
+
if (!version2) return true;
|
|
30259
|
+
const major = Number(version2[1]);
|
|
30260
|
+
const minor = version2[2] === void 0 ? 0 : Number(version2[2]);
|
|
30261
|
+
return major > 4 || major === 4 && minor >= 6;
|
|
30262
|
+
}
|
|
29701
30263
|
function piModelsJson(spec) {
|
|
29702
30264
|
const anthropic = spec.model.providerType === "anthropic";
|
|
29703
30265
|
const base = spec.harnessUrl.replace(/\/$/, "");
|
|
@@ -29713,6 +30275,7 @@ function piModelsJson(spec) {
|
|
|
29713
30275
|
id: spec.model.id,
|
|
29714
30276
|
name: spec.model.id,
|
|
29715
30277
|
reasoning: true,
|
|
30278
|
+
...anthropic ? { compat: { forceAdaptiveThinking: takesAdaptiveThinking(spec.model.id) } } : {},
|
|
29716
30279
|
input: ["text", "image"],
|
|
29717
30280
|
contextWindow: 2e5,
|
|
29718
30281
|
maxTokens: spec.model.maxTokens,
|
|
@@ -29726,17 +30289,18 @@ function piModelsJson(spec) {
|
|
|
29726
30289
|
2
|
|
29727
30290
|
) + "\n";
|
|
29728
30291
|
}
|
|
29729
|
-
function harnessPromptNote(relayTools) {
|
|
30292
|
+
function harnessPromptNote(relayTools, identity2) {
|
|
30293
|
+
const write = identity2 === "write";
|
|
29730
30294
|
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).",
|
|
30295
|
+
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.",
|
|
30296
|
+
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
30297
|
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
30298
|
].join(" ");
|
|
29735
30299
|
}
|
|
29736
30300
|
function piSystemPrompt(spec) {
|
|
29737
30301
|
return `${spec.system.trimEnd()}
|
|
29738
30302
|
|
|
29739
|
-
${harnessPromptNote(spec.relayTools)}
|
|
30303
|
+
${harnessPromptNote(spec.relayTools, spec.identity)}
|
|
29740
30304
|
`;
|
|
29741
30305
|
}
|
|
29742
30306
|
function piSettingsJson() {
|
|
@@ -29750,7 +30314,7 @@ function piLaunchFiles(spec) {
|
|
|
29750
30314
|
{ path: spec.paths.extension, content: PI_EXTENSION_SOURCE }
|
|
29751
30315
|
];
|
|
29752
30316
|
}
|
|
29753
|
-
var RUN_BEARER_ENV, HARNESS_URL_ENV, PROXY_PROVIDER, PI_BUILTIN_TOOLS;
|
|
30317
|
+
var RUN_BEARER_ENV, HARNESS_URL_ENV, PROXY_PROVIDER, PI_BUILTIN_TOOLS, PI_READ_TOOLS;
|
|
29754
30318
|
var init_process = __esm({
|
|
29755
30319
|
"../../src/core/harness/pi/process.ts"() {
|
|
29756
30320
|
"use strict";
|
|
@@ -29759,23 +30323,7 @@ var init_process = __esm({
|
|
|
29759
30323
|
HARNESS_URL_ENV = "SWITCHBOARD_HARNESS_URL";
|
|
29760
30324
|
PROXY_PROVIDER = "switchboard";
|
|
29761
30325
|
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";
|
|
30326
|
+
PI_READ_TOOLS = ["read", "bash", "grep", "find", "ls"];
|
|
29779
30327
|
}
|
|
29780
30328
|
});
|
|
29781
30329
|
|
|
@@ -29923,6 +30471,7 @@ async function runPiHarness(deps, run2) {
|
|
|
29923
30471
|
let writeUp;
|
|
29924
30472
|
let writeUpAt;
|
|
29925
30473
|
let hardStopped = false;
|
|
30474
|
+
let bypass;
|
|
29926
30475
|
const toolsBlocked = () => {
|
|
29927
30476
|
if (!writeUp) return void 0;
|
|
29928
30477
|
if (writeUp.kind === "time")
|
|
@@ -29931,14 +30480,16 @@ async function runPiHarness(deps, run2) {
|
|
|
29931
30480
|
return "the run has hit its turn guard: no more tool calls \u2014 write your final answer now";
|
|
29932
30481
|
return "an operator asked this run to stop: no more tool calls \u2014 write your final answer now";
|
|
29933
30482
|
};
|
|
30483
|
+
const rules = { ...run2.rules, identity: run2.agent.identity };
|
|
29934
30484
|
const live = {
|
|
29935
30485
|
runId: run2.runId,
|
|
29936
30486
|
tools: run2.tools,
|
|
29937
30487
|
toolContext: run2.toolContext,
|
|
29938
30488
|
...run2.backend ? { backend: run2.backend } : {},
|
|
29939
|
-
rules
|
|
30489
|
+
rules,
|
|
29940
30490
|
emit,
|
|
29941
30491
|
toolSpan: (callId) => bridge.openSpan(callId),
|
|
30492
|
+
gateSaw: (callId) => bridge.gateSaw(callId),
|
|
29942
30493
|
toolsBlocked
|
|
29943
30494
|
};
|
|
29944
30495
|
const forget = deps.registry.register(live);
|
|
@@ -29948,6 +30499,7 @@ async function runPiHarness(deps, run2) {
|
|
|
29948
30499
|
model: { id: run2.model.id, providerType: run2.model.providerType, maxTokens: run2.agent.maxTokens },
|
|
29949
30500
|
harnessUrl: deps.harnessUrl,
|
|
29950
30501
|
...run2.effort ? { effort: run2.effort } : {},
|
|
30502
|
+
identity: run2.agent.identity,
|
|
29951
30503
|
system: run2.system,
|
|
29952
30504
|
relayTools: run2.tools.map((t) => t.name)
|
|
29953
30505
|
};
|
|
@@ -30108,8 +30660,15 @@ async function runPiHarness(deps, run2) {
|
|
|
30108
30660
|
if (next.done) break;
|
|
30109
30661
|
const event = parsePiLine(next.value);
|
|
30110
30662
|
if (!event) continue;
|
|
30663
|
+
bridge.judgeGate = !catchingUp;
|
|
30111
30664
|
const obs = bridge.observe(event);
|
|
30112
30665
|
for (const reply of obs.replies) transport.send(reply);
|
|
30666
|
+
if (obs.gateBypassed) {
|
|
30667
|
+
bypass = new GateBypassed(obs.gateBypassed.tool, obs.gateBypassed.callId);
|
|
30668
|
+
note("harness_error", `${bypass.message} \u2014 the run is stopped`);
|
|
30669
|
+
transport.send({ type: "abort" });
|
|
30670
|
+
break;
|
|
30671
|
+
}
|
|
30113
30672
|
if (obs.response) {
|
|
30114
30673
|
const r = obs.response;
|
|
30115
30674
|
if (r.id === "state" && r.success === true && typeof r.data?.sessionFile === "string") {
|
|
@@ -30140,6 +30699,7 @@ async function runPiHarness(deps, run2) {
|
|
|
30140
30699
|
note("stopped", hardStopNote(), "hard");
|
|
30141
30700
|
return HARD_STOP_MESSAGE;
|
|
30142
30701
|
}
|
|
30702
|
+
if (bypass) throw bypass;
|
|
30143
30703
|
if (!settled) {
|
|
30144
30704
|
const tail = await container.tail(paths.errLog, 2e3);
|
|
30145
30705
|
throw new Error(`pi exited before the run settled${tail.trim() ? `: ${redactAndCap(tail.trim(), 400)}` : ""}`);
|
|
@@ -30152,15 +30712,17 @@ async function runPiHarness(deps, run2) {
|
|
|
30152
30712
|
return text || "_(no response)_";
|
|
30153
30713
|
} finally {
|
|
30154
30714
|
transport?.close();
|
|
30155
|
-
bridge.closeOpenSpans(
|
|
30715
|
+
bridge.closeOpenSpans(
|
|
30716
|
+
hardStopped ? "the run was hard-stopped" : bypass ? "the run was stopped: a tool call bypassed the gate" : "the run ended"
|
|
30717
|
+
);
|
|
30156
30718
|
save();
|
|
30157
30719
|
forget();
|
|
30158
30720
|
if (pid !== void 0) await container.kill(pid).catch(() => {
|
|
30159
30721
|
});
|
|
30160
|
-
agentSpan?.end(hardStopped ? "error" : "ok");
|
|
30722
|
+
agentSpan?.end(hardStopped || bypass ? "error" : "ok");
|
|
30161
30723
|
}
|
|
30162
30724
|
}
|
|
30163
|
-
var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CONTINUE_PROMPT, PromptRefused;
|
|
30725
|
+
var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CONTINUE_PROMPT, PromptRefused, GateBypassed;
|
|
30164
30726
|
var init_harness = __esm({
|
|
30165
30727
|
"../../src/core/harness/pi/harness.ts"() {
|
|
30166
30728
|
"use strict";
|
|
@@ -30181,6 +30743,12 @@ var init_harness = __esm({
|
|
|
30181
30743
|
this.name = "PromptRefused";
|
|
30182
30744
|
}
|
|
30183
30745
|
};
|
|
30746
|
+
GateBypassed = class extends Error {
|
|
30747
|
+
constructor(tool, callId) {
|
|
30748
|
+
super(`the gate was bypassed: pi ran ${tool} (call ${callId}) without asking the bot`);
|
|
30749
|
+
this.name = "GateBypassed";
|
|
30750
|
+
}
|
|
30751
|
+
};
|
|
30184
30752
|
}
|
|
30185
30753
|
});
|
|
30186
30754
|
|
|
@@ -30648,8 +31216,8 @@ var init_descriptionTurn = __esm({
|
|
|
30648
31216
|
// ../../src/core/statusCardLabel.ts
|
|
30649
31217
|
function quietSuffix(quietMs, inFlightTool) {
|
|
30650
31218
|
if (quietMs <= QUIET_SUFFIX_AFTER_MS) return "";
|
|
30651
|
-
const
|
|
30652
|
-
return inFlightTool ? ` \u2014 running ${inFlightTool} (${
|
|
31219
|
+
const secs2 = Math.round(quietMs / 1e3);
|
|
31220
|
+
return inFlightTool ? ` \u2014 running ${inFlightTool} (${secs2}s)` : ` \u2014 thinking (${secs2}s since last tool)`;
|
|
30653
31221
|
}
|
|
30654
31222
|
function inFlightToolAfter(current, e) {
|
|
30655
31223
|
if (e.type === "tool_call") return e.tool;
|
|
@@ -30701,6 +31269,7 @@ async function runLoop2(deps, ctx) {
|
|
|
30701
31269
|
wait,
|
|
30702
31270
|
parentRunId,
|
|
30703
31271
|
coordinator,
|
|
31272
|
+
seed,
|
|
30704
31273
|
fixRound
|
|
30705
31274
|
} = ctx;
|
|
30706
31275
|
const agent = budgetedAgent(ctx.agent, profile);
|
|
@@ -30822,10 +31391,23 @@ async function runLoop2(deps, ctx) {
|
|
|
30822
31391
|
let runDiagnosis;
|
|
30823
31392
|
const releaseWorkspace = (span) => round2.release({ hardStopped: run2.control.requested === "hard", ...span ? { span } : {} });
|
|
30824
31393
|
const attachFile = io.attachFile?.bind(io);
|
|
31394
|
+
const uploadTicket = io.uploadTicket?.bind(io);
|
|
31395
|
+
const nextStagedIndex = ctx.stagingIndex ?? stagingIndex();
|
|
31396
|
+
let artifactSeq = 0;
|
|
31397
|
+
const runUrl = runPageLink(run2.id);
|
|
31398
|
+
const artifacts = deps.artifacts ? {
|
|
31399
|
+
store: deps.artifacts,
|
|
31400
|
+
runId: run2.id,
|
|
31401
|
+
nextSeq: () => ++artifactSeq,
|
|
31402
|
+
...runUrl ? { runUrl } : {},
|
|
31403
|
+
reply: (text) => io.reply(text)
|
|
31404
|
+
} : void 0;
|
|
30825
31405
|
const toolContext = {
|
|
30826
31406
|
executor,
|
|
30827
31407
|
reportProgress,
|
|
30828
31408
|
...attachFile ? { attach: attachFile } : {},
|
|
31409
|
+
...artifacts ? { artifacts } : {},
|
|
31410
|
+
...uploadTicket ? { uploadTicket } : {},
|
|
30829
31411
|
web: webCapability(),
|
|
30830
31412
|
skills: deps.skills,
|
|
30831
31413
|
github: githubCapabilityFor(deps, msg.userId),
|
|
@@ -30927,6 +31509,23 @@ async function runLoop2(deps, ctx) {
|
|
|
30927
31509
|
// operator stop from /runs
|
|
30928
31510
|
inbox: admitted.inbox,
|
|
30929
31511
|
// thread follow-ups steered into this run (thread-admission item 2)
|
|
31512
|
+
// A steered follow-up's staged files (record 0033): copied into the
|
|
31513
|
+
// store and pulled into this workspace before the model reads the turn.
|
|
31514
|
+
...deps.artifacts ? {
|
|
31515
|
+
stageFollowUps: async (inputs) => {
|
|
31516
|
+
const files = inputs.flatMap((i) => i.staged ?? []);
|
|
31517
|
+
if (files.length === 0) return "";
|
|
31518
|
+
const staged = await stageIntoWorkspace(files, {
|
|
31519
|
+
store: deps.artifacts,
|
|
31520
|
+
threadKey: msg.threadKey,
|
|
31521
|
+
publish: (e) => registry.publish(run2.id, e),
|
|
31522
|
+
nextIndex: nextStagedIndex,
|
|
31523
|
+
executor,
|
|
31524
|
+
resident: round2.selection.resident !== void 0
|
|
31525
|
+
});
|
|
31526
|
+
return staged.line;
|
|
31527
|
+
}
|
|
31528
|
+
} : {},
|
|
30930
31529
|
// The step record before each step's tools (run-history item 35).
|
|
30931
31530
|
...ledgerRun ? { onStep: ledgerRun.step.bind(ledgerRun) } : {},
|
|
30932
31531
|
// A resume re-enters the loop from the plan (run-history item 37).
|
|
@@ -31150,7 +31749,8 @@ async function runLoop2(deps, ctx) {
|
|
|
31150
31749
|
...dispositions !== void 0 ? { dispositions } : {},
|
|
31151
31750
|
...reviewPost !== void 0 ? { reviewPost } : {},
|
|
31152
31751
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
31153
|
-
...coordinator !== void 0 ? { coordinator } : {}
|
|
31752
|
+
...coordinator !== void 0 ? { coordinator } : {},
|
|
31753
|
+
...seed !== void 0 ? { seed } : {}
|
|
31154
31754
|
});
|
|
31155
31755
|
if (runFailed)
|
|
31156
31756
|
await root.span(
|
|
@@ -31209,6 +31809,7 @@ var init_runLoop = __esm({
|
|
|
31209
31809
|
init_secrets2();
|
|
31210
31810
|
init_record();
|
|
31211
31811
|
init_reply();
|
|
31812
|
+
init_staging();
|
|
31212
31813
|
init_run2();
|
|
31213
31814
|
}
|
|
31214
31815
|
});
|
|
@@ -31675,7 +32276,7 @@ async function plan(deps, input) {
|
|
|
31675
32276
|
try {
|
|
31676
32277
|
planId = planIdOf(request.planPath);
|
|
31677
32278
|
} catch (err2) {
|
|
31678
|
-
return { ok: false, reply: `\u{1F6AB} ${
|
|
32279
|
+
return { ok: false, reply: `\u{1F6AB} ${describe5(err2)}` };
|
|
31679
32280
|
}
|
|
31680
32281
|
let text;
|
|
31681
32282
|
try {
|
|
@@ -31683,7 +32284,7 @@ async function plan(deps, input) {
|
|
|
31683
32284
|
} catch (err2) {
|
|
31684
32285
|
return {
|
|
31685
32286
|
ok: false,
|
|
31686
|
-
reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${
|
|
32287
|
+
reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${describe5(err2)}`
|
|
31687
32288
|
};
|
|
31688
32289
|
}
|
|
31689
32290
|
const graph = parsePlanGraph(text, planId);
|
|
@@ -31696,7 +32297,7 @@ async function plan(deps, input) {
|
|
|
31696
32297
|
try {
|
|
31697
32298
|
selected = openPlanCursor(graph, request.units).order;
|
|
31698
32299
|
} catch (err2) {
|
|
31699
|
-
return { ok: false, reply: `\u{1F6AB} ${
|
|
32300
|
+
return { ok: false, reply: `\u{1F6AB} ${describe5(err2)}` };
|
|
31700
32301
|
}
|
|
31701
32302
|
return { ok: true, planned: { kind: "plan", planId, path: request.planPath, base, graph, selected, identity: identity2 } };
|
|
31702
32303
|
}
|
|
@@ -31812,7 +32413,7 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
31812
32413
|
try {
|
|
31813
32414
|
answer = await deps.create(instance.id);
|
|
31814
32415
|
} catch (err2) {
|
|
31815
|
-
answer = { kind: "unanswered", reason:
|
|
32416
|
+
answer = { kind: "unanswered", reason: describe5(err2) };
|
|
31816
32417
|
}
|
|
31817
32418
|
switch (answer.kind) {
|
|
31818
32419
|
case "created": {
|
|
@@ -31834,7 +32435,7 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
31834
32435
|
);
|
|
31835
32436
|
}
|
|
31836
32437
|
}
|
|
31837
|
-
var refused3,
|
|
32438
|
+
var refused3, describe5, RUNNING, ENDED;
|
|
31838
32439
|
var init_handOff = __esm({
|
|
31839
32440
|
"../../src/core/coordinator/handOff.ts"() {
|
|
31840
32441
|
"use strict";
|
|
@@ -31842,7 +32443,7 @@ var init_handOff = __esm({
|
|
|
31842
32443
|
init_coordinator();
|
|
31843
32444
|
init_briefs();
|
|
31844
32445
|
refused3 = (reply) => ({ status: "aborted", reply });
|
|
31845
|
-
|
|
32446
|
+
describe5 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
31846
32447
|
RUNNING = /* @__PURE__ */ new Set(["queued", "running", "paused", "waiting", "waitingForPause"]);
|
|
31847
32448
|
ENDED = /* @__PURE__ */ new Set(["complete", "errored", "terminated"]);
|
|
31848
32449
|
}
|
|
@@ -32533,22 +33134,22 @@ function createRunEnding(deps) {
|
|
|
32533
33134
|
try {
|
|
32534
33135
|
deps.registry.seal(id, replyOk === void 0 ? {} : { replyOk });
|
|
32535
33136
|
} catch (err2) {
|
|
32536
|
-
log(`[ending] seal ${id}: ${
|
|
33137
|
+
log(`[ending] seal ${id}: ${describe6(err2)}`);
|
|
32537
33138
|
}
|
|
32538
33139
|
try {
|
|
32539
33140
|
afterSeal?.();
|
|
32540
33141
|
} catch (err2) {
|
|
32541
|
-
log(`[ending] after seal ${id}: ${
|
|
33142
|
+
log(`[ending] after seal ${id}: ${describe6(err2)}`);
|
|
32542
33143
|
}
|
|
32543
33144
|
}
|
|
32544
33145
|
for (const entry of pending2.splice(0)) {
|
|
32545
33146
|
try {
|
|
32546
33147
|
const result = deps.registry.seal(entry.runId);
|
|
32547
33148
|
void Promise.resolve(entry.write(result, entry.failedAfterFinish)).catch(
|
|
32548
|
-
(err2) => log(`[ending] record ${entry.runId}: ${
|
|
33149
|
+
(err2) => log(`[ending] record ${entry.runId}: ${describe6(err2)}`)
|
|
32549
33150
|
);
|
|
32550
33151
|
} catch (err2) {
|
|
32551
|
-
log(`[ending] record ${entry.runId}: ${
|
|
33152
|
+
log(`[ending] record ${entry.runId}: ${describe6(err2)}`);
|
|
32552
33153
|
}
|
|
32553
33154
|
}
|
|
32554
33155
|
};
|
|
@@ -32558,7 +33159,7 @@ function createRunEnding(deps) {
|
|
|
32558
33159
|
try {
|
|
32559
33160
|
deps.onFinished?.(runId3);
|
|
32560
33161
|
} catch (err2) {
|
|
32561
|
-
log(`[ending] finished ${runId3}: ${
|
|
33162
|
+
log(`[ending] finished ${runId3}: ${describe6(err2)}`);
|
|
32562
33163
|
}
|
|
32563
33164
|
},
|
|
32564
33165
|
register(entry) {
|
|
@@ -32586,7 +33187,7 @@ function createRunEnding(deps) {
|
|
|
32586
33187
|
}
|
|
32587
33188
|
};
|
|
32588
33189
|
}
|
|
32589
|
-
function
|
|
33190
|
+
function describe6(err2) {
|
|
32590
33191
|
return err2 instanceof Error ? err2.message : String(err2);
|
|
32591
33192
|
}
|
|
32592
33193
|
var init_runEnding = __esm({
|
|
@@ -32715,6 +33316,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32715
33316
|
if (outcome.kind === "redispatch")
|
|
32716
33317
|
return dispatch(deps, msg, io, {
|
|
32717
33318
|
...opts.parent ? { parent: opts.parent } : {},
|
|
33319
|
+
...opts.seed ? { seed: opts.seed } : {},
|
|
32718
33320
|
...opts.coordinator ? { coordinator: opts.coordinator } : {}
|
|
32719
33321
|
});
|
|
32720
33322
|
if (outcome.kind !== "proceed") return ended;
|
|
@@ -32784,7 +33386,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32784
33386
|
}
|
|
32785
33387
|
const contractBlock = opts.contract ? renderContract(opts.contract, { maxChars: DEFAULT_CONTRACT_MAX_CHARS }).text : void 0;
|
|
32786
33388
|
const requestText = route3?.parts ? compoundBrief(directives.text, route3.parts) : directives.text;
|
|
32787
|
-
const
|
|
33389
|
+
const seed = opts.seed ? "parent" : "channel";
|
|
33390
|
+
const built = buildMessages(opts.seed ?? history, requestText, msg.images, msg.documents);
|
|
32788
33391
|
const messages = resume ? resume.plan.messages : contractBlock !== void 0 && agent.name !== "review" ? withContractInFirstUserTurn(built, contractBlock) : built;
|
|
32789
33392
|
const headPreflight = await authorizePrHead({
|
|
32790
33393
|
msg,
|
|
@@ -32821,11 +33424,21 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32821
33424
|
admitted,
|
|
32822
33425
|
parentRunId,
|
|
32823
33426
|
coordinator,
|
|
33427
|
+
seed,
|
|
33428
|
+
...opts.seed ? { seedTurns: opts.seed } : {},
|
|
32824
33429
|
agentSource,
|
|
32825
33430
|
...routeEvent ? { route: routeEvent } : {}
|
|
32826
33431
|
});
|
|
32827
33432
|
const { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta } = registration;
|
|
32828
33433
|
registered = run2;
|
|
33434
|
+
const staged = !resume && deps.artifacts && msg.staged && msg.staged.length > 0 ? msg.staged : [];
|
|
33435
|
+
const nextStagedIndex = stagingIndex();
|
|
33436
|
+
const stagedCopies = staged.length > 0 && agent.machine !== "none" ? copyStaged(staged, {
|
|
33437
|
+
store: deps.artifacts,
|
|
33438
|
+
threadKey: msg.threadKey,
|
|
33439
|
+
nextIndex: nextStagedIndex,
|
|
33440
|
+
publish: (e) => registry.publish(runId3, e)
|
|
33441
|
+
}) : void 0;
|
|
32829
33442
|
const reservation = await reserveRun(deps, {
|
|
32830
33443
|
msg,
|
|
32831
33444
|
agent,
|
|
@@ -32843,7 +33456,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32843
33456
|
admitted,
|
|
32844
33457
|
root,
|
|
32845
33458
|
parentRunId,
|
|
32846
|
-
coordinator
|
|
33459
|
+
coordinator,
|
|
33460
|
+
seed
|
|
32847
33461
|
});
|
|
32848
33462
|
if (reservation) {
|
|
32849
33463
|
reserved = reservation.reserved;
|
|
@@ -32873,6 +33487,22 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32873
33487
|
});
|
|
32874
33488
|
return ended;
|
|
32875
33489
|
}
|
|
33490
|
+
if (staged.length > 0) {
|
|
33491
|
+
const line3 = stagedCopies ? attachmentsLine(
|
|
33492
|
+
await pullStaged(await stagedCopies, {
|
|
33493
|
+
store: deps.artifacts,
|
|
33494
|
+
executor,
|
|
33495
|
+
resident: resident !== void 0
|
|
33496
|
+
})
|
|
33497
|
+
) : noWorkspaceLine(staged);
|
|
33498
|
+
const request = messages[messages.length - 1];
|
|
33499
|
+
if (request && request.role === "user" && line3) {
|
|
33500
|
+
request.content = Array.isArray(request.content) ? [...request.content, { type: "text", text: line3 }] : [
|
|
33501
|
+
{ type: "text", text: request.content },
|
|
33502
|
+
{ type: "text", text: line3 }
|
|
33503
|
+
];
|
|
33504
|
+
}
|
|
33505
|
+
}
|
|
32876
33506
|
const bearer = mintRunBearer(deps, { runId: runId3, agent, profile, resolved, registry, root, clock: clock3 });
|
|
32877
33507
|
const headGate = await authorizeAttachedHead(deps, {
|
|
32878
33508
|
msg,
|
|
@@ -32937,7 +33567,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32937
33567
|
registry,
|
|
32938
33568
|
resume,
|
|
32939
33569
|
parentRunId,
|
|
32940
|
-
coordinator
|
|
33570
|
+
coordinator,
|
|
33571
|
+
seed
|
|
32941
33572
|
});
|
|
32942
33573
|
ledgerRun = await claimRun(deps, {
|
|
32943
33574
|
msg,
|
|
@@ -32960,7 +33591,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32960
33591
|
clock: clock3,
|
|
32961
33592
|
root,
|
|
32962
33593
|
parentRunId,
|
|
32963
|
-
coordinator
|
|
33594
|
+
coordinator,
|
|
33595
|
+
seed
|
|
32964
33596
|
});
|
|
32965
33597
|
const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
|
|
32966
33598
|
{ core: deps, dispatch, registry, clock: clock3 },
|
|
@@ -32980,6 +33612,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
32980
33612
|
agent,
|
|
32981
33613
|
profile,
|
|
32982
33614
|
resolved,
|
|
33615
|
+
stagingIndex: nextStagedIndex,
|
|
32983
33616
|
provider,
|
|
32984
33617
|
model,
|
|
32985
33618
|
messages,
|
|
@@ -33013,6 +33646,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33013
33646
|
wait,
|
|
33014
33647
|
parentRunId,
|
|
33015
33648
|
coordinator,
|
|
33649
|
+
seed,
|
|
33016
33650
|
...opts.fixRound ? { fixRound: opts.fixRound } : {},
|
|
33017
33651
|
...bearer !== void 0 ? { bearer } : {}
|
|
33018
33652
|
});
|
|
@@ -33128,6 +33762,7 @@ var init_dispatcher = __esm({
|
|
|
33128
33762
|
init_route();
|
|
33129
33763
|
init_authorize2();
|
|
33130
33764
|
init_messages();
|
|
33765
|
+
init_staging();
|
|
33131
33766
|
init_provision();
|
|
33132
33767
|
init_run2();
|
|
33133
33768
|
init_runLoop();
|
|
@@ -33146,7 +33781,7 @@ var init_dispatcher = __esm({
|
|
|
33146
33781
|
});
|
|
33147
33782
|
|
|
33148
33783
|
// ../../src/core/runHistoryWriter.ts
|
|
33149
|
-
function
|
|
33784
|
+
function describe7(err2) {
|
|
33150
33785
|
return err2 instanceof Error ? err2.message : String(err2);
|
|
33151
33786
|
}
|
|
33152
33787
|
function createRunHistoryWriter(opts) {
|
|
@@ -33163,7 +33798,7 @@ function createRunHistoryWriter(opts) {
|
|
|
33163
33798
|
try {
|
|
33164
33799
|
opts.onPersisted?.(id);
|
|
33165
33800
|
} catch (err2) {
|
|
33166
|
-
opts.warn(`[run-history] onPersisted hook failed for ${id}: ${
|
|
33801
|
+
opts.warn(`[run-history] onPersisted hook failed for ${id}: ${describe7(err2)}`);
|
|
33167
33802
|
}
|
|
33168
33803
|
};
|
|
33169
33804
|
const attemptAll = async (record2, flag2, sink, span) => {
|
|
@@ -33186,12 +33821,12 @@ function createRunHistoryWriter(opts) {
|
|
|
33186
33821
|
}
|
|
33187
33822
|
if (err2 instanceof PermanentStoreError) {
|
|
33188
33823
|
failures++;
|
|
33189
|
-
opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${
|
|
33824
|
+
opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${describe7(err2)}`);
|
|
33190
33825
|
return;
|
|
33191
33826
|
}
|
|
33192
33827
|
if (attempt >= attempts) {
|
|
33193
33828
|
failures++;
|
|
33194
|
-
opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${
|
|
33829
|
+
opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${describe7(err2)}`);
|
|
33195
33830
|
return;
|
|
33196
33831
|
}
|
|
33197
33832
|
await sleep2(jittered(RUN_HISTORY_RETRY_DELAYS_MS[attempt - 1]));
|
|
@@ -33211,7 +33846,7 @@ function createRunHistoryWriter(opts) {
|
|
|
33211
33846
|
}
|
|
33212
33847
|
const p = attemptAll(record2, flag2, writeOpts?.via ?? opts.store, writeOpts?.span).catch((err2) => {
|
|
33213
33848
|
failures++;
|
|
33214
|
-
opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${
|
|
33849
|
+
opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${describe7(err2)}`);
|
|
33215
33850
|
}).finally(() => {
|
|
33216
33851
|
if (flag2) provisionalFlags.delete(flag2);
|
|
33217
33852
|
void inFlight.delete(p);
|
|
@@ -33605,7 +34240,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33605
34240
|
return "untracked";
|
|
33606
34241
|
}
|
|
33607
34242
|
if (err2 instanceof PermanentStoreError || attempt >= claimAttempts) {
|
|
33608
|
-
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${
|
|
34243
|
+
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${describe8(err2)}`);
|
|
33609
34244
|
return "untracked";
|
|
33610
34245
|
}
|
|
33611
34246
|
await sleep2(RETRY_MS[Math.min(attempt, RETRY_MS.length) - 1]);
|
|
@@ -33729,7 +34364,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33729
34364
|
if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
|
|
33730
34365
|
else this.seeded = true;
|
|
33731
34366
|
} catch (err2) {
|
|
33732
|
-
this.detach(`seed failed: ${
|
|
34367
|
+
this.detach(`seed failed: ${describe8(err2)}`);
|
|
33733
34368
|
}
|
|
33734
34369
|
}
|
|
33735
34370
|
async step(report2) {
|
|
@@ -33752,7 +34387,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33752
34387
|
return;
|
|
33753
34388
|
} catch (err2) {
|
|
33754
34389
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
33755
|
-
this.detach(`step ${record2.step} failed: ${
|
|
34390
|
+
this.detach(`step ${record2.step} failed: ${describe8(err2)}`);
|
|
33756
34391
|
return;
|
|
33757
34392
|
}
|
|
33758
34393
|
await sleep2(RETRY_MS[0]);
|
|
@@ -33785,7 +34420,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33785
34420
|
} catch (err2) {
|
|
33786
34421
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
33787
34422
|
this.stateDirty = true;
|
|
33788
|
-
warn(`[ledger] ${this.threadKey} state not written: ${
|
|
34423
|
+
warn(`[ledger] ${this.threadKey} state not written: ${describe8(err2)}`);
|
|
33789
34424
|
return;
|
|
33790
34425
|
}
|
|
33791
34426
|
await sleep2(RETRY_MS[0]);
|
|
@@ -33803,7 +34438,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33803
34438
|
this.detach(`finishing refused (fenced)`);
|
|
33804
34439
|
return "fenced";
|
|
33805
34440
|
} catch (err2) {
|
|
33806
|
-
warn(`[ledger] ${this.threadKey} finishing failed: ${
|
|
34441
|
+
warn(`[ledger] ${this.threadKey} finishing failed: ${describe8(err2)}`);
|
|
33807
34442
|
return "unavailable";
|
|
33808
34443
|
}
|
|
33809
34444
|
}
|
|
@@ -33818,7 +34453,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33818
34453
|
warn(`[ledger] ${this.threadKey} abandon refused (fenced) \u2014 the row is another generation's`);
|
|
33819
34454
|
} catch (err2) {
|
|
33820
34455
|
warn(
|
|
33821
|
-
`[ledger] ${this.threadKey} abandon failed: ${
|
|
34456
|
+
`[ledger] ${this.threadKey} abandon failed: ${describe8(err2)} \u2014 the sweep will take the row once its lease lapses`
|
|
33822
34457
|
);
|
|
33823
34458
|
}
|
|
33824
34459
|
}
|
|
@@ -33839,7 +34474,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33839
34474
|
this.onStop(result.stop);
|
|
33840
34475
|
}
|
|
33841
34476
|
} catch (err2) {
|
|
33842
|
-
warn(`[ledger] ${this.threadKey} heartbeat failed: ${
|
|
34477
|
+
warn(`[ledger] ${this.threadKey} heartbeat failed: ${describe8(err2)}`);
|
|
33843
34478
|
}
|
|
33844
34479
|
}
|
|
33845
34480
|
stopHeartbeat() {
|
|
@@ -33906,7 +34541,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33906
34541
|
warn(`[ledger] inbox push refused for run ${runId3}: no live row \u2014 the follow-up rides in memory only`);
|
|
33907
34542
|
return void 0;
|
|
33908
34543
|
} catch (err2) {
|
|
33909
|
-
warn(`[ledger] inbox push failed for run ${runId3}: ${
|
|
34544
|
+
warn(`[ledger] inbox push failed for run ${runId3}: ${describe8(err2)} \u2014 the follow-up rides in memory only`);
|
|
33910
34545
|
return void 0;
|
|
33911
34546
|
}
|
|
33912
34547
|
},
|
|
@@ -33915,7 +34550,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
33915
34550
|
return await ledger.readInbox(runId3, afterSeq);
|
|
33916
34551
|
} catch (err2) {
|
|
33917
34552
|
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}: ${
|
|
34553
|
+
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
34554
|
);
|
|
33920
34555
|
return [];
|
|
33921
34556
|
}
|
|
@@ -33932,12 +34567,12 @@ function createLedgerWriteThrough(opts) {
|
|
|
33932
34567
|
for (const r of candidates) if (markedSet.has(r.runId)) r.markHandedOff();
|
|
33933
34568
|
return { marked };
|
|
33934
34569
|
} catch (err2) {
|
|
33935
|
-
return { marked: [], failed:
|
|
34570
|
+
return { marked: [], failed: describe8(err2) };
|
|
33936
34571
|
}
|
|
33937
34572
|
}
|
|
33938
34573
|
};
|
|
33939
34574
|
}
|
|
33940
|
-
var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS,
|
|
34575
|
+
var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS, describe8;
|
|
33941
34576
|
var init_writeThrough = __esm({
|
|
33942
34577
|
"../../src/core/runLedger/writeThrough.ts"() {
|
|
33943
34578
|
"use strict";
|
|
@@ -34000,7 +34635,7 @@ var init_writeThrough = __esm({
|
|
|
34000
34635
|
}
|
|
34001
34636
|
};
|
|
34002
34637
|
RETRY_MS = [200, 800];
|
|
34003
|
-
|
|
34638
|
+
describe8 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
34004
34639
|
}
|
|
34005
34640
|
});
|
|
34006
34641
|
|
|
@@ -34092,6 +34727,24 @@ var init_residentFleet = __esm({
|
|
|
34092
34727
|
});
|
|
34093
34728
|
|
|
34094
34729
|
// ../../src/providers/openaiCompat.ts
|
|
34730
|
+
function buildOpenAIBody(req) {
|
|
34731
|
+
const messages = [];
|
|
34732
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
34733
|
+
for (const m of req.messages) messages.push(...toOAIMessages(m));
|
|
34734
|
+
const body = {
|
|
34735
|
+
model: req.model,
|
|
34736
|
+
max_tokens: req.maxTokens,
|
|
34737
|
+
messages
|
|
34738
|
+
};
|
|
34739
|
+
if (req.tools && req.tools.length > 0) {
|
|
34740
|
+
body.tools = req.tools.map((t) => ({
|
|
34741
|
+
type: "function",
|
|
34742
|
+
function: { name: t.name, description: t.description, parameters: t.inputSchema }
|
|
34743
|
+
}));
|
|
34744
|
+
if (req.toolChoice) body.tool_choice = { type: "function", function: { name: req.toolChoice.name } };
|
|
34745
|
+
}
|
|
34746
|
+
return body;
|
|
34747
|
+
}
|
|
34095
34748
|
function toOAIMessages(m) {
|
|
34096
34749
|
if (m.role === "assistant") {
|
|
34097
34750
|
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
@@ -34178,20 +34831,7 @@ var init_openaiCompat = __esm({
|
|
|
34178
34831
|
this.apiKey = cfg.apiKeyEnv ? processSecrets.named(cfg.apiKeyEnv) : void 0;
|
|
34179
34832
|
}
|
|
34180
34833
|
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
|
-
}
|
|
34834
|
+
const body = buildOpenAIBody(req);
|
|
34195
34835
|
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
34196
34836
|
method: "POST",
|
|
34197
34837
|
headers: {
|
|
@@ -34683,6 +35323,7 @@ function classifyDocument(mimetype, name) {
|
|
|
34683
35323
|
async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
|
|
34684
35324
|
const images = [];
|
|
34685
35325
|
const skipped = [];
|
|
35326
|
+
const skippedFiles = [];
|
|
34686
35327
|
let bytes = 0;
|
|
34687
35328
|
const candidates = [];
|
|
34688
35329
|
for (const f of files ?? []) {
|
|
@@ -34690,6 +35331,7 @@ async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
|
|
|
34690
35331
|
const url = f.url_private_download ?? f.url_private;
|
|
34691
35332
|
if (!url || !f.mimetype || !IMAGE_TYPES2.has(f.mimetype) || (f.size ?? 0) > MAX_IMAGE_BYTES || candidates.length >= maxImages) {
|
|
34692
35333
|
skipped.push(label);
|
|
35334
|
+
skippedFiles.push(f);
|
|
34693
35335
|
continue;
|
|
34694
35336
|
}
|
|
34695
35337
|
candidates.push({ f, label, url, mediaType: f.mimetype });
|
|
@@ -34703,12 +35345,13 @@ async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
|
|
|
34703
35345
|
const buf2 = downloads[i];
|
|
34704
35346
|
if (!buf2 || buf2.byteLength > MAX_IMAGE_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
|
|
34705
35347
|
skipped.push(label);
|
|
35348
|
+
skippedFiles.push(f);
|
|
34706
35349
|
return;
|
|
34707
35350
|
}
|
|
34708
35351
|
bytes += buf2.byteLength;
|
|
34709
35352
|
images.push({ mediaType, data: buf2.toString("base64"), name: f.name });
|
|
34710
35353
|
});
|
|
34711
|
-
return { images, skipped, bytes };
|
|
35354
|
+
return { images, skipped, skippedFiles, bytes };
|
|
34712
35355
|
}
|
|
34713
35356
|
async function downloadSlackFile(url, label, isLoginPage) {
|
|
34714
35357
|
const token2 = processSecrets.get("SLACK_BOT_TOKEN");
|
|
@@ -34727,6 +35370,7 @@ async function downloadSlackFile(url, label, isLoginPage) {
|
|
|
34727
35370
|
async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
34728
35371
|
const documents = [];
|
|
34729
35372
|
const skipped = [];
|
|
35373
|
+
const skippedFiles = [];
|
|
34730
35374
|
let bytes = 0;
|
|
34731
35375
|
const candidates = [];
|
|
34732
35376
|
for (const f of files ?? []) {
|
|
@@ -34735,6 +35379,7 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
|
34735
35379
|
const kind = classifyDocument(f.mimetype, f.name);
|
|
34736
35380
|
if (!url || !kind || (f.size ?? 0) > MAX_DOCUMENT_BYTES || candidates.length >= maxDocs) {
|
|
34737
35381
|
skipped.push(label);
|
|
35382
|
+
skippedFiles.push(f);
|
|
34738
35383
|
continue;
|
|
34739
35384
|
}
|
|
34740
35385
|
candidates.push({ f, label, url, kind });
|
|
@@ -34748,6 +35393,7 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
|
34748
35393
|
const buf2 = downloads[i];
|
|
34749
35394
|
if (!buf2 || buf2.byteLength > MAX_DOCUMENT_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
|
|
34750
35395
|
skipped.push(label);
|
|
35396
|
+
skippedFiles.push(f);
|
|
34751
35397
|
return;
|
|
34752
35398
|
}
|
|
34753
35399
|
bytes += buf2.byteLength;
|
|
@@ -34757,9 +35403,43 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
|
|
|
34757
35403
|
name: f.name
|
|
34758
35404
|
});
|
|
34759
35405
|
});
|
|
34760
|
-
return { documents, skipped, bytes };
|
|
35406
|
+
return { documents, skipped, skippedFiles, bytes };
|
|
35407
|
+
}
|
|
35408
|
+
function stagedFiles(unsupported, opts) {
|
|
35409
|
+
if (!opts.staging) return { staged: [], skipped: unsupported.map(fileLabel) };
|
|
35410
|
+
const staged = [];
|
|
35411
|
+
const skipped = [];
|
|
35412
|
+
let total = 0;
|
|
35413
|
+
for (const f of unsupported) {
|
|
35414
|
+
const label = fileLabel(f);
|
|
35415
|
+
const url = f.url_private_download ?? f.url_private;
|
|
35416
|
+
const size = f.size ?? 0;
|
|
35417
|
+
if (isSecretFile(f.name)) {
|
|
35418
|
+
skipped.push(`${label} \u2014 looks like a credential or key file`);
|
|
35419
|
+
continue;
|
|
35420
|
+
}
|
|
35421
|
+
if (!url || !f.name || size <= 0) {
|
|
35422
|
+
skipped.push(`${label} \u2014 Slack gave no download URL, name or size`);
|
|
35423
|
+
continue;
|
|
35424
|
+
}
|
|
35425
|
+
if (size > MAX_STAGED_FILE_BYTES) {
|
|
35426
|
+
skipped.push(`${label} \u2014 ${size} bytes is over the ${MAX_STAGED_FILE_BYTES}-byte per-file ceiling`);
|
|
35427
|
+
continue;
|
|
35428
|
+
}
|
|
35429
|
+
if (staged.length >= MAX_STAGED_PER_MESSAGE) {
|
|
35430
|
+
skipped.push(`${label} \u2014 more than ${MAX_STAGED_PER_MESSAGE} files on one message`);
|
|
35431
|
+
continue;
|
|
35432
|
+
}
|
|
35433
|
+
if (total + size > opts.maxBytesPerMessage) {
|
|
35434
|
+
skipped.push(`${label} \u2014 the message's files exceed the ${opts.maxBytesPerMessage}-byte budget`);
|
|
35435
|
+
continue;
|
|
35436
|
+
}
|
|
35437
|
+
total += size;
|
|
35438
|
+
staged.push({ name: f.name, size, type: f.mimetype ?? "application/octet-stream", url, messageId: opts.messageId });
|
|
35439
|
+
}
|
|
35440
|
+
return { staged, skipped };
|
|
34761
35441
|
}
|
|
34762
|
-
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;
|
|
35442
|
+
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
35443
|
var init_attachments = __esm({
|
|
34764
35444
|
"../../src/channels/slack/attachments.ts"() {
|
|
34765
35445
|
"use strict";
|
|
@@ -34842,6 +35522,8 @@ var init_attachments = __esm({
|
|
|
34842
35522
|
MAX_HISTORY_DOCS = 20;
|
|
34843
35523
|
MAX_HISTORY_DOCUMENT_BYTES = 32 * 1024 * 1024;
|
|
34844
35524
|
fileLabel = (f) => `${f.name ?? f.id ?? "file"} (${f.mimetype ?? "unknown type"})`;
|
|
35525
|
+
MAX_STAGED_FILE_BYTES = 1073741824;
|
|
35526
|
+
MAX_STAGED_PER_MESSAGE = 10;
|
|
34845
35527
|
}
|
|
34846
35528
|
});
|
|
34847
35529
|
|
|
@@ -35480,7 +36162,13 @@ async function handle(deps, { client, statusClient }, ev) {
|
|
|
35480
36162
|
const receivedAt = systemClock();
|
|
35481
36163
|
const trace = startRequestRoot(deps, { channel: "slack", receivedAt, originAt: tsMs(ev.ts) });
|
|
35482
36164
|
try {
|
|
35483
|
-
const received = await trace.root.span(
|
|
36165
|
+
const received = await trace.root.span(
|
|
36166
|
+
"slack.receive",
|
|
36167
|
+
(span) => receiveSlackMessage(client, ev, span, {
|
|
36168
|
+
staging: deps.artifacts !== void 0,
|
|
36169
|
+
maxBytesPerMessage: deps.config.config.artifacts?.inbound?.maxBytesPerMessage ?? ARTIFACT_DEFAULTS.maxBytesPerMessage
|
|
36170
|
+
})
|
|
36171
|
+
);
|
|
35484
36172
|
if (!received) {
|
|
35485
36173
|
trace.root.end("ok", { status: "refused" });
|
|
35486
36174
|
return;
|
|
@@ -35495,7 +36183,7 @@ async function handle(deps, { client, statusClient }, ev) {
|
|
|
35495
36183
|
if (!trace.root.ended) trace.root.end("error", { status: "failed" });
|
|
35496
36184
|
}
|
|
35497
36185
|
}
|
|
35498
|
-
async function receiveSlackMessage(client, ev, span) {
|
|
36186
|
+
async function receiveSlackMessage(client, ev, span, policy) {
|
|
35499
36187
|
const drop = await dedupeDelivery(client, ev);
|
|
35500
36188
|
if (drop) {
|
|
35501
36189
|
console.log(`[redelivery] ${ev.channel}:${ev.ts} dropped: ${drop}`);
|
|
@@ -35509,12 +36197,13 @@ async function receiveSlackMessage(client, ev, span) {
|
|
|
35509
36197
|
const delayNote = ev.caughtUp ? new SlackIO(client, ev).reply(catchUpDelayNote(ev.ts, systemClock())).catch((err2) => {
|
|
35510
36198
|
console.error(`[catch-up] ${ev.channel}:${ev.ts} delay note failed: ${err2.message}`);
|
|
35511
36199
|
}) : Promise.resolve();
|
|
35512
|
-
const [{ images,
|
|
36200
|
+
const [{ images, skippedFiles: imagePassSkipped }, { documents, skippedFiles: documentPassSkipped }] = await Promise.all([
|
|
35513
36201
|
fetchImages(ev.files, MAX_IMAGES_PER_MESSAGE),
|
|
35514
36202
|
fetchDocuments(ev.files, MAX_DOCS_PER_MESSAGE),
|
|
35515
36203
|
delayNote
|
|
35516
36204
|
]);
|
|
35517
|
-
const
|
|
36205
|
+
const unsupported = imagePassSkipped.filter((f) => documentPassSkipped.includes(f));
|
|
36206
|
+
const { staged, skipped } = stagedFiles(unsupported, { ...policy, messageId: ev.ts });
|
|
35518
36207
|
const [channelName, userName, team] = await Promise.all([
|
|
35519
36208
|
resolveChannelName(client, ev.channel),
|
|
35520
36209
|
resolveUserName(client, ev.user),
|
|
@@ -35532,7 +36221,8 @@ async function receiveSlackMessage(client, ev, span) {
|
|
|
35532
36221
|
userName,
|
|
35533
36222
|
sourceUrl: team ? slackPermalink(team, ev.channel, ev.ts, ev.threadTs) : void 0,
|
|
35534
36223
|
images: images.length > 0 ? images : void 0,
|
|
35535
|
-
documents: documents.length > 0 ? documents : void 0
|
|
36224
|
+
documents: documents.length > 0 ? documents : void 0,
|
|
36225
|
+
...staged.length > 0 ? { staged } : {}
|
|
35536
36226
|
};
|
|
35537
36227
|
}
|
|
35538
36228
|
function resumeSlackIO(client, run2, opts = {}) {
|
|
@@ -35599,6 +36289,7 @@ var init_slack = __esm({
|
|
|
35599
36289
|
init_statusCard();
|
|
35600
36290
|
init_slackCatchUp();
|
|
35601
36291
|
init_secrets2();
|
|
36292
|
+
init_config();
|
|
35602
36293
|
init_slackCatchUpStatus();
|
|
35603
36294
|
init_slackSocketStatus();
|
|
35604
36295
|
init_slackTriggers();
|
|
@@ -35664,6 +36355,30 @@ ${file.text}`));
|
|
|
35664
36355
|
initial_comment: mdToMrkdwn(file.lead)
|
|
35665
36356
|
});
|
|
35666
36357
|
}
|
|
36358
|
+
/** The external-upload flow (docs/reference/specs/slack-channel.md item 10,
|
|
36359
|
+
* record 0033): `files.getUploadURLExternal` mints a URL for exactly
|
|
36360
|
+
* `size` bytes under `name`; the run's container POSTs the file there; then
|
|
36361
|
+
* `complete` shares it into this thread with the lead as the comment
|
|
36362
|
+
* (`files.completeUploadExternal`). The bot never sees the bytes. A ticket
|
|
36363
|
+
* Slack answers without a URL or id is refused by name. */
|
|
36364
|
+
async uploadTicket(file) {
|
|
36365
|
+
const minted = await this.client.files.getUploadURLExternal({ filename: file.name, length: file.size });
|
|
36366
|
+
const url = minted.upload_url;
|
|
36367
|
+
const id = minted.file_id;
|
|
36368
|
+
if (!url || !id)
|
|
36369
|
+
throw new Error(`files.getUploadURLExternal answered without an upload_url and file_id for ${file.name}`);
|
|
36370
|
+
return {
|
|
36371
|
+
url,
|
|
36372
|
+
complete: async (lead) => {
|
|
36373
|
+
await this.client.files.completeUploadExternal({
|
|
36374
|
+
files: [{ id, title: file.name }],
|
|
36375
|
+
channel_id: this.ev.channel,
|
|
36376
|
+
thread_ts: this.ev.threadTs,
|
|
36377
|
+
initial_comment: mdToMrkdwn(lead)
|
|
36378
|
+
});
|
|
36379
|
+
}
|
|
36380
|
+
};
|
|
36381
|
+
}
|
|
35667
36382
|
async post(mrkdwn) {
|
|
35668
36383
|
for (const chunk of chunkText(mrkdwn, SLACK_MSG_LIMIT)) {
|
|
35669
36384
|
await this.client.chat.postMessage({
|
|
@@ -36385,31 +37100,6 @@ var init_mcp3 = __esm({
|
|
|
36385
37100
|
}
|
|
36386
37101
|
});
|
|
36387
37102
|
|
|
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
37103
|
// ../../src/channels/scheduledPanel.ts
|
|
36414
37104
|
function buildScheduledRows(schedules, firings, liveRuns, now) {
|
|
36415
37105
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -37226,7 +37916,7 @@ var init_liveView = __esm({
|
|
|
37226
37916
|
// ../../src/channels/webAssets.ts
|
|
37227
37917
|
import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync3 } from "node:fs";
|
|
37228
37918
|
import { join as join13 } from "node:path";
|
|
37229
|
-
function
|
|
37919
|
+
function contentTypeFor2(path) {
|
|
37230
37920
|
const dot = path.lastIndexOf(".");
|
|
37231
37921
|
return (dot === -1 ? void 0 : CONTENT_TYPES[path.slice(dot)]) ?? "application/octet-stream";
|
|
37232
37922
|
}
|
|
@@ -37271,7 +37961,7 @@ function loadWebAssets(distDir) {
|
|
|
37271
37961
|
return true;
|
|
37272
37962
|
}
|
|
37273
37963
|
res.writeHead(200, {
|
|
37274
|
-
"content-type":
|
|
37964
|
+
"content-type": contentTypeFor2(path),
|
|
37275
37965
|
"content-length": String(body.byteLength),
|
|
37276
37966
|
"cache-control": "public, max-age=31536000, immutable"
|
|
37277
37967
|
});
|
|
@@ -38491,7 +39181,7 @@ async function reclaimRuns(opts) {
|
|
|
38491
39181
|
reclaimed = await ledger.reclaim(gen, now(), LEASE_MS);
|
|
38492
39182
|
} catch (err2) {
|
|
38493
39183
|
warn(
|
|
38494
|
-
err2 instanceof RouteMissingError ? "[reclaim] state Worker has no run-ledger routes \u2014 nothing to take over" : `[reclaim] reclaim failed: ${
|
|
39184
|
+
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
39185
|
);
|
|
38496
39186
|
return outcome;
|
|
38497
39187
|
}
|
|
@@ -38558,8 +39248,8 @@ async function reclaimRuns(opts) {
|
|
|
38558
39248
|
`[reclaim] ${row.runId} ${row.threadKey} closed ${status3} (from ${run2.reclaimedFrom}; ${why}; ${events.length} event(s))`
|
|
38559
39249
|
);
|
|
38560
39250
|
} catch (err2) {
|
|
38561
|
-
outcome.failed.push({ runId: row.runId, error:
|
|
38562
|
-
warn(`[reclaim] ${row.runId} ${row.threadKey}: ${
|
|
39251
|
+
outcome.failed.push({ runId: row.runId, error: describe9(err2) });
|
|
39252
|
+
warn(`[reclaim] ${row.runId} ${row.threadKey}: ${describe9(err2)} \u2014 left on the ledger for the next boot`);
|
|
38563
39253
|
}
|
|
38564
39254
|
}
|
|
38565
39255
|
try {
|
|
@@ -38575,7 +39265,7 @@ async function reclaimRuns(opts) {
|
|
|
38575
39265
|
});
|
|
38576
39266
|
}
|
|
38577
39267
|
} catch (err2) {
|
|
38578
|
-
warn(`[reclaim] listing live runs failed: ${
|
|
39268
|
+
warn(`[reclaim] listing live runs failed: ${describe9(err2)} \u2014 the card sweep runs without the ledger's guard`);
|
|
38579
39269
|
}
|
|
38580
39270
|
if (reclaimed.length > 0 || outcome.liveElsewhere.length > 0) {
|
|
38581
39271
|
log(
|
|
@@ -38597,7 +39287,7 @@ function startReclaimSweep(opts) {
|
|
|
38597
39287
|
await opts.onOutcome(outcome);
|
|
38598
39288
|
}
|
|
38599
39289
|
} catch (err2) {
|
|
38600
|
-
warn(`[reclaim] sweep failed: ${
|
|
39290
|
+
warn(`[reclaim] sweep failed: ${describe9(err2)}`);
|
|
38601
39291
|
} finally {
|
|
38602
39292
|
running = false;
|
|
38603
39293
|
}
|
|
@@ -38622,7 +39312,7 @@ async function completenessVerdict(ledger, runId3, lastStep) {
|
|
|
38622
39312
|
try {
|
|
38623
39313
|
transcript = await ledger.readTranscript(runId3);
|
|
38624
39314
|
} catch (err2) {
|
|
38625
|
-
return { resumable: false, why: `transcript unreadable (${
|
|
39315
|
+
return { resumable: false, why: `transcript unreadable (${describe9(err2)})` };
|
|
38626
39316
|
}
|
|
38627
39317
|
if (!transcript.complete) return { resumable: false, why: `transcript incomplete: ${transcript.gap}` };
|
|
38628
39318
|
const verdict = transcriptCompleteness({
|
|
@@ -38643,7 +39333,7 @@ async function completenessVerdict(ledger, runId3, lastStep) {
|
|
|
38643
39333
|
return { resumable: false, why: verdict.why };
|
|
38644
39334
|
}
|
|
38645
39335
|
}
|
|
38646
|
-
var TERMINAL,
|
|
39336
|
+
var TERMINAL, describe9;
|
|
38647
39337
|
var init_boot = __esm({
|
|
38648
39338
|
"../../src/core/boot.ts"() {
|
|
38649
39339
|
"use strict";
|
|
@@ -38659,7 +39349,7 @@ var init_boot = __esm({
|
|
|
38659
39349
|
"failed",
|
|
38660
39350
|
"interrupted"
|
|
38661
39351
|
]);
|
|
38662
|
-
|
|
39352
|
+
describe9 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
38663
39353
|
}
|
|
38664
39354
|
});
|
|
38665
39355
|
|
|
@@ -39495,11 +40185,14 @@ var init_adminModelProxy = __esm({
|
|
|
39495
40185
|
|
|
39496
40186
|
// ../../src/core/harness/pi/toolRules.ts
|
|
39497
40187
|
import { isAbsolute as isAbsolute5, relative, resolve as resolve11 } from "node:path";
|
|
40188
|
+
function reachFor(identity2) {
|
|
40189
|
+
return identity2 === "write" ? CODING_REACH : READ_REACH;
|
|
40190
|
+
}
|
|
39498
40191
|
function judgeToolCall(tool, input, ctx) {
|
|
39499
40192
|
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
|
|
40193
|
+
if (bundle === void 0) return outside(`${tool} is not in any bundle the ${ctx.identity} identity reaches`);
|
|
40194
|
+
if (!reachFor(ctx.identity).has(bundle)) {
|
|
40195
|
+
return outside(`${tool} is the \`${bundle}\` bundle, outside the ${ctx.identity} identity's reach`);
|
|
39503
40196
|
}
|
|
39504
40197
|
const args = isRecord5(input) ? input : {};
|
|
39505
40198
|
if (tool === "bash") return judgeBash(args.command, ctx);
|
|
@@ -39514,6 +40207,13 @@ function judgeBash(command2, ctx) {
|
|
|
39514
40207
|
if (MERGE_OR_APPROVE.test(command2)) {
|
|
39515
40208
|
return refused4("merge/approve \u2014 a coding run never merges or approves a pull request");
|
|
39516
40209
|
}
|
|
40210
|
+
if (ctx.identity !== "write") {
|
|
40211
|
+
if (ANY_PUSH.test(command2)) return refused4("read-only \u2014 a read-identity run never pushes");
|
|
40212
|
+
if (GH_WRITE_VERB.test(command2) || GH_API_WRITE.test(command2) || CURL_GITHUB_WRITE.test(command2)) {
|
|
40213
|
+
return refused4("read-only \u2014 a read-identity run never writes to GitHub");
|
|
40214
|
+
}
|
|
40215
|
+
return allowed;
|
|
40216
|
+
}
|
|
39517
40217
|
for (const match of command2.matchAll(GIT_PUSH)) {
|
|
39518
40218
|
const verdict = judgePush(match[1], ctx);
|
|
39519
40219
|
if (verdict.verdict !== "allowed") return verdict;
|
|
@@ -39549,7 +40249,7 @@ function judgePath(path, ctx) {
|
|
|
39549
40249
|
if (rel.startsWith("..") || isAbsolute5(rel)) return refused4(`path \u2014 \`${path}\` resolves outside the checkout`);
|
|
39550
40250
|
return allowed;
|
|
39551
40251
|
}
|
|
39552
|
-
var PI_TOOL_BUNDLES, CODING_REACH, allowed, refused4, outside, isRecord5, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_PUSH;
|
|
40252
|
+
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
40253
|
var init_toolRules = __esm({
|
|
39554
40254
|
"../../src/core/harness/pi/toolRules.ts"() {
|
|
39555
40255
|
"use strict";
|
|
@@ -39573,6 +40273,7 @@ var init_toolRules = __esm({
|
|
|
39573
40273
|
"github-write",
|
|
39574
40274
|
"pr"
|
|
39575
40275
|
]);
|
|
40276
|
+
READ_REACH = /* @__PURE__ */ new Set(["shell", "files", "web", "search", "github-read", "verdict"]);
|
|
39576
40277
|
allowed = { verdict: "allowed" };
|
|
39577
40278
|
refused4 = (reason) => ({ verdict: "refused", reason });
|
|
39578
40279
|
outside = (reason) => ({ verdict: "outside-profile", reason });
|
|
@@ -39581,7 +40282,17 @@ var init_toolRules = __esm({
|
|
|
39581
40282
|
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
40283
|
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
40284
|
MERGE_OR_APPROVE = /\bgh\s+pr\s+(merge|review)\b|\/pulls\/\d+\/(merge|reviews)\b/;
|
|
39584
|
-
|
|
40285
|
+
GIT_THEN_PUSH = String.raw`\bgit(?:\s+(?:-C\s+\S+|--git-dir=\S+|--work-tree=\S+|-c\s+\S+|--no-pager))*\s+push\b`;
|
|
40286
|
+
GIT_PUSH = new RegExp(`${GIT_THEN_PUSH}([^;&|]*)`, "g");
|
|
40287
|
+
ANY_PUSH = new RegExp(GIT_THEN_PUSH);
|
|
40288
|
+
GH_WRITE_VERB = /\bgh\s+(pr|issue)\s+(comment|create|edit|close|reopen|ready|lock|unlock|review|merge|delete|transfer|pin|unpin)\b/;
|
|
40289
|
+
WRITE_METHOD = "(POST|post|PATCH|patch|PUT|put|DELETE|delete)";
|
|
40290
|
+
GH_API_WRITE = new RegExp(
|
|
40291
|
+
`\\bgh\\s+api\\b(?=[^;&|]*(?:\\s(?:-X|--method)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-f|-F|--field|--raw-field|--input)[\\s=]))`
|
|
40292
|
+
);
|
|
40293
|
+
CURL_GITHUB_WRITE = new RegExp(
|
|
40294
|
+
`\\bcurl\\b(?=[^;&|]*api\\.github\\.com)(?=[^;&|]*(?:\\s(?:-X|--request)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-d|--data(?:-\\w+)?|--json|-T|--upload-file)[\\s=]))`
|
|
40295
|
+
);
|
|
39585
40296
|
}
|
|
39586
40297
|
});
|
|
39587
40298
|
|
|
@@ -39590,6 +40301,7 @@ function relayedToolDefinitions(harness) {
|
|
|
39590
40301
|
return harness.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
39591
40302
|
}
|
|
39592
40303
|
function authorizeToolCall(harness, ask) {
|
|
40304
|
+
harness.gateSaw(ask.toolCallId);
|
|
39593
40305
|
const blocked = harness.toolsBlocked();
|
|
39594
40306
|
const refuse3 = (reason) => {
|
|
39595
40307
|
harness.emit({
|
|
@@ -40109,6 +40821,7 @@ function watched(io, on) {
|
|
|
40109
40821
|
};
|
|
40110
40822
|
if (io.attach) out.attach = (file) => io.attach(file);
|
|
40111
40823
|
if (io.attachFile) out.attachFile = (file) => io.attachFile(file);
|
|
40824
|
+
if (io.uploadTicket) out.uploadTicket = (file) => io.uploadTicket(file);
|
|
40112
40825
|
if (io.runFinished) out.runFinished = (receipt) => io.runFinished(receipt);
|
|
40113
40826
|
if (io.openThread) out.openThread = (lead) => io.openThread(lead);
|
|
40114
40827
|
return out;
|
|
@@ -40140,8 +40853,8 @@ async function spawn6(body, deps) {
|
|
|
40140
40853
|
const composed = await composeChild(req.brief, instance, unit.row, briefReaders(deps, instance, io));
|
|
40141
40854
|
turn = composed;
|
|
40142
40855
|
} 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:
|
|
40856
|
+
log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe10(err2)}`);
|
|
40857
|
+
return json(502, { ok: false, error: "brief_failed", message: describe10(err2), at });
|
|
40145
40858
|
}
|
|
40146
40859
|
} else {
|
|
40147
40860
|
turn = { prompt: req.prompt };
|
|
@@ -40192,7 +40905,7 @@ async function spawn6(body, deps) {
|
|
|
40192
40905
|
);
|
|
40193
40906
|
void settled.then((end) => {
|
|
40194
40907
|
if (end.kind === "threw")
|
|
40195
|
-
log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${
|
|
40908
|
+
log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${describe10(end.err)}`);
|
|
40196
40909
|
});
|
|
40197
40910
|
const first = await Promise.race([started.then((id) => ({ kind: "started", id })), settled]);
|
|
40198
40911
|
if (first.kind === "started" || startedId !== void 0) {
|
|
@@ -40200,7 +40913,7 @@ async function spawn6(body, deps) {
|
|
|
40200
40913
|
log(`[coordinator] ${instance.id} ${req.step}: spawned ${req.preset} run ${runId3} in ${threadKey}`);
|
|
40201
40914
|
return json(200, { ok: true, runId: runId3, threadKey, at });
|
|
40202
40915
|
}
|
|
40203
|
-
if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message:
|
|
40916
|
+
if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message: describe10(first.err), at });
|
|
40204
40917
|
const refusal = first.outcome.refusal;
|
|
40205
40918
|
if (refusal === "coordinator_thread_live") {
|
|
40206
40919
|
const now = await liveOnThread(deps.runs, instance, threadKey);
|
|
@@ -40353,7 +41066,7 @@ async function prCheck(body, deps) {
|
|
|
40353
41066
|
at
|
|
40354
41067
|
});
|
|
40355
41068
|
} catch (err2) {
|
|
40356
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
41069
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
|
|
40357
41070
|
}
|
|
40358
41071
|
}
|
|
40359
41072
|
async function plan2(body, deps) {
|
|
@@ -40412,7 +41125,7 @@ async function unitStart(body, deps) {
|
|
|
40412
41125
|
...opened.thread.sourceUrl !== void 0 ? { sourceUrl: opened.thread.sourceUrl } : {}
|
|
40413
41126
|
};
|
|
40414
41127
|
} catch (err2) {
|
|
40415
|
-
return json(502, { ok: false, error: "thread_failed", message:
|
|
41128
|
+
return json(502, { ok: false, error: "thread_failed", message: describe10(err2), at });
|
|
40416
41129
|
}
|
|
40417
41130
|
}
|
|
40418
41131
|
}
|
|
@@ -40454,7 +41167,7 @@ async function branch(body, deps) {
|
|
|
40454
41167
|
await deps.createBranchRef(instance.repo, name, base);
|
|
40455
41168
|
return json(200, { ok: true, branch: name, base, at });
|
|
40456
41169
|
} catch (err2) {
|
|
40457
|
-
return json(200, { ok: false, reason:
|
|
41170
|
+
return json(200, { ok: false, reason: describe10(err2), at });
|
|
40458
41171
|
}
|
|
40459
41172
|
}
|
|
40460
41173
|
function unitLines(units) {
|
|
@@ -40506,7 +41219,7 @@ async function round(body, deps) {
|
|
|
40506
41219
|
instance,
|
|
40507
41220
|
units.map((u) => u.unit === updated.unit ? updated : u)
|
|
40508
41221
|
).catch(
|
|
40509
|
-
(err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${
|
|
41222
|
+
(err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${describe10(err2)}`)
|
|
40510
41223
|
);
|
|
40511
41224
|
return json(200, { ok: true, at });
|
|
40512
41225
|
}
|
|
@@ -40540,7 +41253,7 @@ async function unitEnd(body, deps) {
|
|
|
40540
41253
|
told = true;
|
|
40541
41254
|
} catch (err2) {
|
|
40542
41255
|
(deps.log ?? console.warn)(
|
|
40543
|
-
`[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${
|
|
41256
|
+
`[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${describe10(err2)}`
|
|
40544
41257
|
);
|
|
40545
41258
|
}
|
|
40546
41259
|
}
|
|
@@ -40554,7 +41267,7 @@ ${ending.report}` + (rendered !== void 0 ? `
|
|
|
40554
41267
|
${rendered}` : "");
|
|
40555
41268
|
await deps.github.commentIssue(instance.repo, row.issue, comment).catch(
|
|
40556
41269
|
(err2) => (deps.log ?? console.warn)(
|
|
40557
|
-
`[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${
|
|
41270
|
+
`[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${describe10(err2)}`
|
|
40558
41271
|
)
|
|
40559
41272
|
);
|
|
40560
41273
|
}
|
|
@@ -40609,7 +41322,7 @@ async function merge(body, deps, subject2) {
|
|
|
40609
41322
|
try {
|
|
40610
41323
|
facts2 = await deps.fetchPrFacts(pr);
|
|
40611
41324
|
} catch (err2) {
|
|
40612
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
41325
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
|
|
40613
41326
|
}
|
|
40614
41327
|
if (facts2 === void 0)
|
|
40615
41328
|
return json(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
|
|
@@ -40661,7 +41374,7 @@ async function merge(body, deps, subject2) {
|
|
|
40661
41374
|
title: facts2.title ?? `Merge pull request #${pr.number}`
|
|
40662
41375
|
});
|
|
40663
41376
|
} catch (err2) {
|
|
40664
|
-
return json(502, { ok: false, error: "github_unavailable", message:
|
|
41377
|
+
return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
|
|
40665
41378
|
}
|
|
40666
41379
|
if (!merged.ok) {
|
|
40667
41380
|
log(
|
|
@@ -40836,13 +41549,13 @@ function createAdminCoordinatorHandler(deps) {
|
|
|
40836
41549
|
}
|
|
40837
41550
|
write(res, await answerCoordinatorStep(door, read.body, deps));
|
|
40838
41551
|
} catch (err2) {
|
|
40839
|
-
(deps.log ?? console.error)(`[coordinator] ${
|
|
41552
|
+
(deps.log ?? console.error)(`[coordinator] ${describe10(err2)}`);
|
|
40840
41553
|
write(res, json(500, { ok: false, error: "internal error" }));
|
|
40841
41554
|
}
|
|
40842
41555
|
})();
|
|
40843
41556
|
};
|
|
40844
41557
|
}
|
|
40845
|
-
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json,
|
|
41558
|
+
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
41559
|
var init_adminCoordinator = __esm({
|
|
40847
41560
|
"../../src/channels/adminCoordinator.ts"() {
|
|
40848
41561
|
"use strict";
|
|
@@ -40875,7 +41588,7 @@ var init_adminCoordinator = __esm({
|
|
|
40875
41588
|
};
|
|
40876
41589
|
invalid = (error) => ({ ok: false, error });
|
|
40877
41590
|
json = (status3, body) => ({ status: status3, body });
|
|
40878
|
-
|
|
41591
|
+
describe10 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
40879
41592
|
EVERY_RUN = { kind: "all" };
|
|
40880
41593
|
REVIEW_POSTED_CHECKS = 3;
|
|
40881
41594
|
REVIEW_POSTED_RECHECK_MS = 2e3;
|