@coreplane/switchboard 1.255.0 → 1.256.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/deploy/cloudflare-resident/drain.ts +55 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +177 -32
- 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 +31 -0
- package/dist/assets/src/core/coordinator/driver.ts +11 -1
- package/dist/assets/src/core/pipelineStanding.ts +57 -0
- package/dist/assets/src/core/reviewVerdict.ts +49 -0
- package/dist/assets/src/core/ship/contract.ts +20 -30
- package/dist/assets/src/core/ship/coordinator.ts +195 -68
- package/dist/assets/web/dist/.vite/manifest.json +31 -31
- package/dist/assets/web/dist/assets/{HomePage-Be7jLLnU.js → HomePage-BEiStFwW.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-D_RD6wLo.js → ResidentDetailPage-jYhEeeyu.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-BLkSuCxo.js → ResidentsIndexPage-DvFmlV4U.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-DvWzR1JQ.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-CvZ-TOT3.js +9 -0
- package/dist/assets/web/dist/assets/{RunsIndexPage-DW-HHuZa.js → RunsIndexPage-JqVSDOok.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-3aYsDf-q.js → ScheduledPage-Viim-bus.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-DG-p5Xy1.js → SettingsPage-CYUy8McC.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-CEnGlyAL.js → StatusDot-Dv6UMaPy.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-CiunVowT.js → Tooltip-Bd-5Rypv.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-B81wEnKN.js +1 -0
- package/dist/assets/web/dist/assets/{dist-luhv3YSo.js → dist-Nl3uaxrP.js} +1 -1
- package/dist/assets/web/dist/assets/{main-mAKx_zo9.js → main-DRxWlffc.js} +2 -2
- package/dist/cli.js +319 -64
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-CfuZqf_O.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-CmWYGR36.js +0 -9
- package/dist/assets/web/dist/assets/UnitRoutePage-iOYnmTcQ.js +0 -1
package/dist/cli.js
CHANGED
|
@@ -1933,7 +1933,7 @@ function hasApproach(unit) {
|
|
|
1933
1933
|
function renderFirstInstruction(rebase) {
|
|
1934
1934
|
const branch2 = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
|
|
1935
1935
|
const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
|
|
1936
|
-
const gates =
|
|
1936
|
+
const gates = `${FAST_GATES_POINTER} ${GATE_RECEIPTS}`;
|
|
1937
1937
|
return `Rebase ${branch2} onto ${onto} before any other work \u2014 the parent unit has merged and the base has moved; the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. Push the branch as soon as the change exists and the fast gates pass \u2014 the project's full verification is CI's gate, run there after the push with any fix as a further commit; an unpushed tree does not survive the run's end. ${gates} Right before each push, fetch ${onto} again and rebase once more if it moved while you worked, so the pull request is not born conflicting. At the wind-down note, commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`;
|
|
1938
1938
|
}
|
|
1939
1939
|
function renderSpecRow(row2, dropText) {
|
|
@@ -2019,7 +2019,7 @@ ${renderGuards(contract.guards)}`
|
|
|
2019
2019
|
}
|
|
2020
2020
|
return { text, chars: text.length, dropped, overBudget: text.length > maxChars };
|
|
2021
2021
|
}
|
|
2022
|
-
var PR_TITLE_GUARD, GUARDS, CONTRACT_HEADING, CONTRACT_SECTION_HEADINGS, DEFAULT_CONTRACT_MAX_CHARS, PLAN_MAX_CHARS, UNIT_HEADING, ANY_HEADING, BULLET, SPEC_MENTION, ITEM_LIST, NAMED_REF, ANY_MD_MENTION, SPEC_ITEM, TITLE_MAX, APPROACH_DROPPED, TIMEOUT_ON_LONG_COMMANDS,
|
|
2022
|
+
var PR_TITLE_GUARD, GUARDS, CONTRACT_HEADING, CONTRACT_SECTION_HEADINGS, DEFAULT_CONTRACT_MAX_CHARS, PLAN_MAX_CHARS, UNIT_HEADING, ANY_HEADING, BULLET, SPEC_MENTION, ITEM_LIST, NAMED_REF, ANY_MD_MENTION, SPEC_ITEM, TITLE_MAX, APPROACH_DROPPED, TIMEOUT_ON_LONG_COMMANDS, FAST_GATES_POINTER, GATE_RECEIPTS, DROP_NOTE;
|
|
2023
2023
|
var init_contract = __esm({
|
|
2024
2024
|
"../../src/core/ship/contract.ts"() {
|
|
2025
2025
|
"use strict";
|
|
@@ -2067,8 +2067,8 @@ var init_contract = __esm({
|
|
|
2067
2067
|
TITLE_MAX = 80;
|
|
2068
2068
|
APPROACH_DROPPED = "- **Approach**: (dropped to fit the contract's budget \u2014 read it in the plan)";
|
|
2069
2069
|
TIMEOUT_ON_LONG_COMMANDS = "State a timeout on any command you expect to run longer than a minute: a timeout that reaches past the loop's end is refused before the command runs, never cut midway.";
|
|
2070
|
-
|
|
2071
|
-
|
|
2070
|
+
FAST_GATES_POINTER = "The fast gates are the ones your preset instructions name (THE FAST GATES): the changed-set forms, never the whole project \u2014 the full suite, the full typecheck and the full verification are CI's, never yours to run.";
|
|
2071
|
+
GATE_RECEIPTS = "Paste each command's exit line into the PR description's validation table as the row's proof; a gate you could not run goes under the handoff's unproven list and is never claimed clean.";
|
|
2072
2072
|
DROP_NOTE = {
|
|
2073
2073
|
approach: "the unit's Approach text was dropped (read it in the plan)",
|
|
2074
2074
|
agentRules: "the agent rules were dropped (read the file at the head)",
|
|
@@ -2129,7 +2129,7 @@ function getAgent(name) {
|
|
|
2129
2129
|
}
|
|
2130
2130
|
return a;
|
|
2131
2131
|
}
|
|
2132
|
-
var MACHINE_CLASSES, IDENTITIES, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, BREVITY_RULE, FENCED_CONTENT_RULE, NOTEPAD, CHECKS_BY_COST, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, CODING_SYSTEM_SEEDED, REVIEW_FINAL_MESSAGE, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, REVIEW_SYSTEM_SEEDED, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, COMPOUND_PRESET, WORK_PRESETS, AGENTS;
|
|
2132
|
+
var MACHINE_CLASSES, IDENTITIES, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, BREVITY_RULE, FENCED_CONTENT_RULE, NOTEPAD, CHECKS_BY_COST, TOUCHED_TESTS_COMMAND, FAST_GATES_BEFORE_PUSH, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, CODING_SYSTEM_SEEDED, REVIEW_FINAL_MESSAGE, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, REVIEW_SYSTEM_SEEDED, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, COMPOUND_PRESET, WORK_PRESETS, AGENTS;
|
|
2133
2133
|
var init_registry = __esm({
|
|
2134
2134
|
"../../src/agents/registry.ts"() {
|
|
2135
2135
|
"use strict";
|
|
@@ -2167,6 +2167,8 @@ Text stays in your message; do not attach what you can say.`;
|
|
|
2167
2167
|
NOTEPAD = `YOUR NOTES AND YOUR REACH BACK. This thread's conversation outlives your context window and this run: every turn \u2014 yours, the person's, every tool call and its output, from this run and the runs before it in this thread \u2014 is kept in a log you can search with the \`recall\` tool (words \u2192 the matching turns with their numbers; a turn number \u2192 that turn whole). When something you need is no longer in front of you, recall it instead of redoing the work or guessing.
|
|
2168
2168
|
Keep notes with the \`notes\` tool: one short document, replaced whole each time, at most 8 KiB \u2014 decisions and their reasons, the names of things you found (files, tests, commits, the head your tests were green at), what is not yet proven. They are the one thing sure to survive a compaction and to reach the next run in this thread: they ride your system prompt at its start and come back to you right after a compaction. A person reads them too, on the run's page, so write them as a document and never as one paragraph: Markdown, a \`##\` heading per section \u2014 \`Done\`, \`In progress\`, \`Next\`, \`Facts\` (names, ids, heads, the reasons behind decisions), leaving out a section with nothing in it \u2014 one bullet per item, one line per bullet, no prose walls. Write them when you decide something worth keeping, not only at the end.`;
|
|
2169
2169
|
CHECKS_BY_COST = `CHECKS BY COST \u2014 push before the expensive ones. Every check you might run has a cost class: seconds (a formatter or a linter on the files you touched, one test file, a docs, link or spec check, the typecheck of one package) or minutes (the whole test suite, a build, a dependency install, an end-to-end or full verification). Know a command's class before you run it \u2014 from the project's own scripts and CI configuration, from how long it took last time, or by the class above when you have nothing better. Prove each change with the cheapest check that can prove it, matched to the change's scope and scoped to the changed set \u2014 the tests nearest your change, the touched project's typecheck, the changed files' formatting, never the whole tree: a documentation change gets the documentation checks, one module gets its own tests, a shared type gets the typecheck. Every CI pipeline runs the tests, the types, the formatting and the full verification on your push, so you never run them again: you validate and fix your own change before pushing, at the changed-set scope. Passing the full test suite and the full typecheck is NOT part of your criteria: CI is that gate and the only place they run \u2014 on a shared machine they cost minutes that every other run pays for. As soon as the change exists and those checks pass, commit and push \u2014 the pushed branch is the deliverable, and an unpushed tree does not survive the run's end. Beyond the changed set, use judgement about what this change needs rather than a checklist, fixing forward with further commits and pushes. Never start an operation whose expected duration does not fit the time you have left minus what a commit, a push and the description need: push what there is and say plainly what is unverified instead. At the wind-down note, commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS} The description's validation names exactly what ran; what did not run is CI's to gate, and you say so.`;
|
|
2170
|
+
TOUCHED_TESTS_COMMAND = "`npx vitest run` on the test files you touched, by name,";
|
|
2171
|
+
FAST_GATES_BEFORE_PUSH = `THE FAST GATES, before every push \u2014 each scoped to the changed set, never the whole project: ${TOUCHED_TESTS_COMMAND} once (never \`--changed\`, never a directory: on a moving base that is most of the suite), \`tsc --noEmit -p\` the touched tsconfig under \`NODE_OPTIONS=--max-old-space-size=6144\`, \`npx prettier --check\` on the changed files, \`npm run hygiene:check\` and \`npm run specs:check\` \u2014 then your judgement on what else this change needs, not a longer checklist. The full verification is CI's gate \u2014 \`npm run verify\` runs there on your push, never here: push a head early and let CI judge it, fixing forward with further commits and pushes.`;
|
|
2170
2172
|
CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
|
|
2171
2173
|
|
|
2172
2174
|
You work inside a dedicated workspace directory with bash, read_file, and write_file tools. ${SANDBOX_TOOLCHAIN}
|
|
@@ -2189,6 +2191,8 @@ Workflow for shipping a PR:
|
|
|
2189
2191
|
|
|
2190
2192
|
${CHECKS_BY_COST}
|
|
2191
2193
|
|
|
2194
|
+
${FAST_GATES_BEFORE_PUSH}
|
|
2195
|
+
|
|
2192
2196
|
${NEVER_MERGE}
|
|
2193
2197
|
|
|
2194
2198
|
${UNIT_CONTRACT}
|
|
@@ -2230,6 +2234,8 @@ Workflow for shipping a change:
|
|
|
2230
2234
|
|
|
2231
2235
|
${CHECKS_BY_COST}
|
|
2232
2236
|
|
|
2237
|
+
${FAST_GATES_BEFORE_PUSH}
|
|
2238
|
+
|
|
2233
2239
|
${NEVER_MERGE}
|
|
2234
2240
|
|
|
2235
2241
|
${UNIT_CONTRACT}
|
|
@@ -2266,6 +2272,8 @@ Workflow for shipping a change:
|
|
|
2266
2272
|
|
|
2267
2273
|
${CHECKS_BY_COST}
|
|
2268
2274
|
|
|
2275
|
+
${FAST_GATES_BEFORE_PUSH}
|
|
2276
|
+
|
|
2269
2277
|
${NEVER_MERGE}
|
|
2270
2278
|
|
|
2271
2279
|
${UNIT_CONTRACT}
|
|
@@ -2969,6 +2977,19 @@ function verdictLine(verdict) {
|
|
|
2969
2977
|
const summary = oneLine2(verdict.summary);
|
|
2970
2978
|
return summary ? `${token2} ${summary}` : token2;
|
|
2971
2979
|
}
|
|
2980
|
+
function severityCount(sev, n2) {
|
|
2981
|
+
if (sev === "blocking") return `${n2} blocker${n2 === 1 ? "" : "s"}`;
|
|
2982
|
+
if (sev === "nit") return `${n2} nit${n2 === 1 ? "" : "s"}`;
|
|
2983
|
+
return `${n2} ${sev}`;
|
|
2984
|
+
}
|
|
2985
|
+
function verdictCountsLine(verdict) {
|
|
2986
|
+
if (verdict.verdict === "approve") return "LGTM";
|
|
2987
|
+
const counts = severityCounts(verdict.findings ?? []);
|
|
2988
|
+
return counts ? `Changes requested: ${counts}` : "Changes requested";
|
|
2989
|
+
}
|
|
2990
|
+
function severityCounts(findings) {
|
|
2991
|
+
return FINDING_SEVERITIES.map((sev) => [sev, findings.filter((f) => f.severity === sev).length]).filter(([, n2]) => n2 > 0).map(([sev, n2]) => severityCount(sev, n2)).join(", ");
|
|
2992
|
+
}
|
|
2972
2993
|
function formatFinding(f) {
|
|
2973
2994
|
const location = f.line !== void 0 ? `${f.file}:${f.line}` : f.file;
|
|
2974
2995
|
return `[${f.severity}] ${f.id} ${location} \u2014 ${f.title}${f.humanGated ? " (human-gated)" : ""}`;
|
|
@@ -3045,6 +3066,9 @@ ${prose2}
|
|
|
3045
3066
|
}
|
|
3046
3067
|
function buildReviewChannelReply(input) {
|
|
3047
3068
|
const { answer, verdict, posted, liveUrl } = input;
|
|
3069
|
+
if (!shows(input.verbosity ?? "verbose", "verbose") && verdict !== void 0 && verdict.findings !== void 0 && posted !== void 0) {
|
|
3070
|
+
return `${verdictCountsLine(verdict)} \u2014 https://github.com/${posted.repo}/pull/${posted.number}`;
|
|
3071
|
+
}
|
|
3048
3072
|
const tail = [
|
|
3049
3073
|
...posted && verdict ? [`Posted to ${posted.repo}#${posted.number}`] : [],
|
|
3050
3074
|
...liveUrl ? [`[Live run](${liveUrl})`] : []
|
|
@@ -3133,6 +3157,7 @@ var init_reviewVerdict = __esm({
|
|
|
3133
3157
|
"use strict";
|
|
3134
3158
|
init_reviewedHead();
|
|
3135
3159
|
init_redact();
|
|
3160
|
+
init_verbosity();
|
|
3136
3161
|
FINDING_SEVERITIES = ["blocking", "major", "minor", "nit"];
|
|
3137
3162
|
ADDRESS_SEVERITIES = FINDING_SEVERITIES;
|
|
3138
3163
|
DEFAULT_ADDRESS_SEVERITY = "minor";
|
|
@@ -3151,10 +3176,20 @@ var init_reviewVerdict = __esm({
|
|
|
3151
3176
|
});
|
|
3152
3177
|
|
|
3153
3178
|
// ../../src/core/ship/coordinator.ts
|
|
3154
|
-
function
|
|
3179
|
+
function interruptionCauseOfWords(words) {
|
|
3180
|
+
if (/\bcontainer (?:was )?replaced\b|\breplac(?:ed|ement) container\b|\bworkspace lost\b|\bcould not be re-attached\b/i.test(
|
|
3181
|
+
words
|
|
3182
|
+
))
|
|
3183
|
+
return "container_replaced";
|
|
3184
|
+
if (/\bsandbox (?:recycled|worker|exec|runtime)\b/i.test(words)) return "sandbox_fault";
|
|
3185
|
+
if (/\bbot restart(?:ed|s)?\b|\b(?:next|previous|another) generation\b/i.test(words)) return "bot_restart";
|
|
3186
|
+
return void 0;
|
|
3187
|
+
}
|
|
3188
|
+
function shipInterruptedNote(prUrl2, cause) {
|
|
3155
3189
|
const stands = prUrl2 ? `Its work stands on GitHub: ${prUrl2}.` : "Whatever it pushed stands on its pipeline branch; no PR was opened yet.";
|
|
3156
3190
|
const reissue = prUrl2 ? `To continue the review loop, re-issue \`agent:ship\` in this thread with only the PR URL (${prUrl2}).` : "To continue, re-issue `agent:ship` in this thread with the task \u2014 round 0 runs again on the same branch.";
|
|
3157
|
-
|
|
3191
|
+
const opening = cause === "container_replaced" ? "\u26A0\uFE0F The resident container running this pipeline's child was replaced (a deploy's image swap) and the child could not resume, so the pipeline stopped." : cause === "sandbox_fault" ? "\u26A0\uFE0F The sandbox running this pipeline's child failed and the child could not resume, so the pipeline stopped." : cause === "bot_restart" ? "\u26A0\uFE0F The bot restarted while this ship pipeline was running, so the pipeline stopped." : "\u26A0\uFE0F This ship pipeline's child was interrupted and could not resume, so the pipeline stopped.";
|
|
3192
|
+
return `${opening} ${stands} ${reissue}`;
|
|
3158
3193
|
}
|
|
3159
3194
|
function planIdOf(planPath) {
|
|
3160
3195
|
const base = planPath.split("/").pop() ?? "";
|
|
@@ -5247,6 +5282,12 @@ var init_contract2 = __esm({
|
|
|
5247
5282
|
});
|
|
5248
5283
|
|
|
5249
5284
|
// ../../src/core/pipelineStanding.ts
|
|
5285
|
+
function endingWordOf(kind) {
|
|
5286
|
+
return ENDING_WORDS[kind] ?? kind;
|
|
5287
|
+
}
|
|
5288
|
+
function roundOutcomeWordOf(outcome) {
|
|
5289
|
+
return ROUND_OUTCOME_WORDS[outcome] ?? outcome;
|
|
5290
|
+
}
|
|
5250
5291
|
function isShipEvent(e) {
|
|
5251
5292
|
return e.type === "ship_round" || e.type === "ship_unit";
|
|
5252
5293
|
}
|
|
@@ -5367,7 +5408,7 @@ function isPipelineSummaryShape(v) {
|
|
|
5367
5408
|
}
|
|
5368
5409
|
return true;
|
|
5369
5410
|
}
|
|
5370
|
-
var STAGES, OPEN_STAGES, ROUND_STAGE, ENDING_STAGE, isFinite_;
|
|
5411
|
+
var STAGES, OPEN_STAGES, ROUND_STAGE, ENDING_STAGE, ENDING_WORDS, ROUND_OUTCOME_WORDS, isFinite_;
|
|
5371
5412
|
var init_pipelineStanding = __esm({
|
|
5372
5413
|
"../../src/core/pipelineStanding.ts"() {
|
|
5373
5414
|
"use strict";
|
|
@@ -5417,6 +5458,41 @@ var init_pipelineStanding = __esm({
|
|
|
5417
5458
|
idle: "idle",
|
|
5418
5459
|
blocked: "ended"
|
|
5419
5460
|
};
|
|
5461
|
+
ENDING_WORDS = {
|
|
5462
|
+
merged: "merged",
|
|
5463
|
+
already_landed: "merged",
|
|
5464
|
+
merge_ready: "merge-ready",
|
|
5465
|
+
held: "held",
|
|
5466
|
+
merge_refused: "not merged",
|
|
5467
|
+
round_cap: "round cap reached",
|
|
5468
|
+
wall_clock_cap: "out of budget",
|
|
5469
|
+
review_pending: "review pending",
|
|
5470
|
+
stopped: "stopped",
|
|
5471
|
+
aborted: "aborted",
|
|
5472
|
+
transient: "aborted",
|
|
5473
|
+
continued: "continuing",
|
|
5474
|
+
no_verdict: "no verdict",
|
|
5475
|
+
interrupted: "interrupted",
|
|
5476
|
+
refused: "refused",
|
|
5477
|
+
idle: "idle",
|
|
5478
|
+
blocked: "blocked"
|
|
5479
|
+
};
|
|
5480
|
+
ROUND_OUTCOME_WORDS = {
|
|
5481
|
+
started: "started",
|
|
5482
|
+
pr_opened: "pull request opened",
|
|
5483
|
+
completed: "done",
|
|
5484
|
+
approve: "approved",
|
|
5485
|
+
request_changes: "changes requested",
|
|
5486
|
+
no_verdict: "no verdict",
|
|
5487
|
+
checks_failed: "checks failed",
|
|
5488
|
+
transient: "retried",
|
|
5489
|
+
enqueued: "queued to merge",
|
|
5490
|
+
dequeued: "removed from the merge queue",
|
|
5491
|
+
aborted: "aborted",
|
|
5492
|
+
stopped: "stopped",
|
|
5493
|
+
continued: "continuing",
|
|
5494
|
+
idle: "idle"
|
|
5495
|
+
};
|
|
5420
5496
|
isFinite_ = (v) => typeof v === "number" && Number.isFinite(v);
|
|
5421
5497
|
}
|
|
5422
5498
|
});
|
|
@@ -8659,6 +8735,9 @@ function drainSkippedLine(step, tokenEnv, tag = "deploy:all") {
|
|
|
8659
8735
|
}
|
|
8660
8736
|
function drainLiftedLine(step, answer, drain, tag = "deploy:all") {
|
|
8661
8737
|
const ok2 = !("error" in answer) && answer.status >= 200 && answer.status < 300;
|
|
8738
|
+
const held2 = !("error" in answer) && answer.body.cleared === false && Array.isArray(answer.body.held) ? answer.body.held.filter((h) => typeof h === "string") : [];
|
|
8739
|
+
if (ok2 && held2.length > 0)
|
|
8740
|
+
return `[${tag}] ${step}: fleet stays closed \u2014 ${held2.join(", ")} still report${held2.length === 1 ? "s" : ""} the pre-deploy image; it reopens by itself on the last container's new-image report${drain.until ? ` (backstop ${drain.until})` : ""}`;
|
|
8662
8741
|
if (drain.drained) {
|
|
8663
8742
|
if (ok2) return `[${tag}] ${step}: fleet reopened`;
|
|
8664
8743
|
return `[${tag}] ${step}: fleet NOT reopened (${answerWords(answer)}) \u2014 it reopens by itself${drain.until ? ` at ${drain.until}` : " when the drain ends"}; \`POST /undrain\` with the drain or admin bearer reopens it now`;
|
|
@@ -8674,10 +8753,10 @@ function reconcileLine(step, answer, tag = "deploy:all") {
|
|
|
8674
8753
|
if (!ok2)
|
|
8675
8754
|
return `[${tag}] ${step}: the fleet could NOT be reconciled onto the new image (${answerWords(answer)}) \u2014 a stale container restarts on its next quiet attach or refresh instead`;
|
|
8676
8755
|
const rows = Array.isArray(answer.body.reconciled) ? answer.body.reconciled : [];
|
|
8677
|
-
const
|
|
8678
|
-
if (
|
|
8679
|
-
return `[${tag}] ${step}: fleet reconciled
|
|
8680
|
-
return `[${tag}] ${step}: fleet reconciled
|
|
8756
|
+
const unverified = rows.filter((r) => r.verified !== true);
|
|
8757
|
+
if (unverified.length > 0)
|
|
8758
|
+
return `[${tag}] ${step}: fleet reconciled, but not every container is verified on the new image yet (${reconcileWords(rows)}) \u2014 the drain holds for the unverified until each reports`;
|
|
8759
|
+
return `[${tag}] ${step}: fleet reconciled and every container verified on the new image (${reconcileWords(rows) || "no residents"})`;
|
|
8681
8760
|
}
|
|
8682
8761
|
async function postJson(url, bearer, body) {
|
|
8683
8762
|
try {
|
|
@@ -8707,7 +8786,9 @@ var init_residentDrain = __esm({
|
|
|
8707
8786
|
RESIDENT_DRAIN_TOKEN_ENV = "RESIDENT_DRAIN_TOKEN";
|
|
8708
8787
|
RESIDENT_DRAINED_WAIT_MAX_MS = DRAIN.deployWaitMaxMs;
|
|
8709
8788
|
DRAIN_MARGIN_MINUTES = DRAIN.marginMinutes;
|
|
8710
|
-
reconcileWords = (rows) => rows.map(
|
|
8789
|
+
reconcileWords = (rows) => rows.map(
|
|
8790
|
+
(r) => `${typeof r.resource === "string" ? r.resource : "?"} ${String(r.result ?? "?")}${r.verified === true ? "" : " (unverified)"}`
|
|
8791
|
+
).join(", ");
|
|
8711
8792
|
DRAINED_GAVE_UP_SUFFIX = " \u2014 the fleet was drained for the whole wait, so what refused was already in flight when the drain began and outlived the budget; the drain is lifted";
|
|
8712
8793
|
}
|
|
8713
8794
|
});
|
|
@@ -33514,7 +33595,8 @@ async function deliverAnswer(ctx) {
|
|
|
33514
33595
|
answer,
|
|
33515
33596
|
verdict: ctx.verdict,
|
|
33516
33597
|
posted: ctx.reviewPost?.posted ? ctx.reviewPost.target : void 0,
|
|
33517
|
-
liveUrl
|
|
33598
|
+
liveUrl,
|
|
33599
|
+
...ctx.verbosity !== void 0 ? { verbosity: ctx.verbosity } : {}
|
|
33518
33600
|
}) : answer;
|
|
33519
33601
|
await ending.sealAfterReply(
|
|
33520
33602
|
() => root.span(
|
|
@@ -35847,6 +35929,10 @@ var init_commandChat = __esm({
|
|
|
35847
35929
|
});
|
|
35848
35930
|
|
|
35849
35931
|
// ../../src/core/dispatch/handBack.ts
|
|
35932
|
+
function renderHandBackLine(line3) {
|
|
35933
|
+
return `${HAND_BACK_PREFIX}
|
|
35934
|
+
\`${line3}\``;
|
|
35935
|
+
}
|
|
35850
35936
|
var HAND_BACK_PREFIX;
|
|
35851
35937
|
var init_handBack = __esm({
|
|
35852
35938
|
"../../src/core/dispatch/handBack.ts"() {
|
|
@@ -40105,24 +40191,11 @@ ${text}` : text;
|
|
|
40105
40191
|
if (res.ok && res.followUp) postSettledOutcome(res.followUp, io, root);
|
|
40106
40192
|
return { kind: "command", command: def.id, outcome: res.ok ? "ok" : "error" };
|
|
40107
40193
|
}
|
|
40108
|
-
async function
|
|
40109
|
-
const {
|
|
40110
|
-
|
|
40111
|
-
await recordRoutedDecision(deps, msg, io, def, { ...route3, outcome }, text, ending, trace);
|
|
40112
|
-
await ending.sealAfterReply(
|
|
40113
|
-
async () => {
|
|
40114
|
-
},
|
|
40115
|
-
() => root.span("post.reply", post)
|
|
40116
|
-
);
|
|
40117
|
-
return { kind: "command", command: def.id, outcome };
|
|
40118
|
-
};
|
|
40119
|
-
const cut = receipt.length > ROUTE_RECEIPT_CAP;
|
|
40120
|
-
const handBack = cut ? `${HAND_BACK_PREFIX} ${receipt}
|
|
40121
|
-
${HAND_BACK_CUT_NOTE}` : `${HAND_BACK_PREFIX} ${receipt}`;
|
|
40122
|
-
const store = deps.confirmations;
|
|
40123
|
-
if (!io.offer || !store) return answer(handBack, "hand_back", () => io.reply(handBack));
|
|
40194
|
+
async function mintConfirmationOffer(args) {
|
|
40195
|
+
const { io, store, msg, def, input, receipt, model } = args;
|
|
40196
|
+
if (!io.offer || !store) return { kind: "no_click" };
|
|
40124
40197
|
const line3 = chatInvocation(def, input);
|
|
40125
|
-
if (redactSecrets(line3) !== line3) return
|
|
40198
|
+
if (redactSecrets(line3) !== line3) return { kind: "unshowable" };
|
|
40126
40199
|
const risk = def.annotations?.risk?.(input) ?? "";
|
|
40127
40200
|
let row2;
|
|
40128
40201
|
try {
|
|
@@ -40135,7 +40208,7 @@ ${HAND_BACK_CUT_NOTE}` : `${HAND_BACK_PREFIX} ${receipt}`;
|
|
|
40135
40208
|
input,
|
|
40136
40209
|
receipt,
|
|
40137
40210
|
risk,
|
|
40138
|
-
model
|
|
40211
|
+
model
|
|
40139
40212
|
},
|
|
40140
40213
|
CONFIRMATION_TTL_MS
|
|
40141
40214
|
);
|
|
@@ -40143,13 +40216,43 @@ ${HAND_BACK_CUT_NOTE}` : `${HAND_BACK_PREFIX} ${receipt}`;
|
|
|
40143
40216
|
console.warn(
|
|
40144
40217
|
`[route] ${msg.threadKey} confirmation store unreachable at mint, handing back: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
40145
40218
|
);
|
|
40219
|
+
return { kind: "store_unreachable" };
|
|
40220
|
+
}
|
|
40221
|
+
return { kind: "offered", shown: { id: row2.id, line: line3, risk, expiresAt: row2.expiresAt } };
|
|
40222
|
+
}
|
|
40223
|
+
async function answerHandBack(branch2, msg, def, input, route3, receipt, confirm, root) {
|
|
40224
|
+
const { deps, io, ending, trace } = branch2;
|
|
40225
|
+
const answer = async (text, outcome, post) => {
|
|
40226
|
+
await recordRoutedDecision(deps, msg, io, def, { ...route3, outcome }, text, ending, trace);
|
|
40227
|
+
await ending.sealAfterReply(
|
|
40228
|
+
async () => {
|
|
40229
|
+
},
|
|
40230
|
+
() => root.span("post.reply", post)
|
|
40231
|
+
);
|
|
40232
|
+
return { kind: "command", command: def.id, outcome };
|
|
40233
|
+
};
|
|
40234
|
+
const cut = receipt.length > ROUTE_RECEIPT_CAP;
|
|
40235
|
+
const handBack = cut ? `${HAND_BACK_PREFIX} ${receipt}
|
|
40236
|
+
${HAND_BACK_CUT_NOTE}` : `${HAND_BACK_PREFIX} ${receipt}`;
|
|
40237
|
+
const mint = await mintConfirmationOffer({
|
|
40238
|
+
io,
|
|
40239
|
+
store: deps.confirmations,
|
|
40240
|
+
msg,
|
|
40241
|
+
def,
|
|
40242
|
+
input,
|
|
40243
|
+
receipt,
|
|
40244
|
+
model: route3.model
|
|
40245
|
+
});
|
|
40246
|
+
if (mint.kind === "no_click") return answer(handBack, "hand_back", () => io.reply(handBack));
|
|
40247
|
+
if (mint.kind === "unshowable") return answer(UNSHOWABLE_LINE, "hand_back", () => io.reply(UNSHOWABLE_LINE));
|
|
40248
|
+
if (mint.kind === "store_unreachable") {
|
|
40146
40249
|
const text = `${handBack}
|
|
40147
40250
|
${STORE_UNREACHABLE_NOTE}`;
|
|
40148
40251
|
return answer(text, "hand_back", () => io.reply(text));
|
|
40149
40252
|
}
|
|
40150
|
-
const shown =
|
|
40253
|
+
const shown = mint.shown;
|
|
40151
40254
|
console.log(
|
|
40152
|
-
`[route] ${msg.threadKey} offered ${def.id} as confirmation ${
|
|
40255
|
+
`[route] ${msg.threadKey} offered ${def.id} as confirmation ${shown.id} (confirm asked by ${confirm.scope})`
|
|
40153
40256
|
);
|
|
40154
40257
|
return answer(renderOffer(shown), "offered", () => renderConfirmationOffer(io, shown));
|
|
40155
40258
|
}
|
|
@@ -41953,6 +42056,17 @@ var init_seed = __esm({
|
|
|
41953
42056
|
});
|
|
41954
42057
|
|
|
41955
42058
|
// ../../src/core/dispatch/operator.ts
|
|
42059
|
+
function ownedProjection(p) {
|
|
42060
|
+
return {
|
|
42061
|
+
presets: [],
|
|
42062
|
+
commands: p.commands.filter((c) => c.id === "steer.run" || c.effect === "read")
|
|
42063
|
+
};
|
|
42064
|
+
}
|
|
42065
|
+
function ownerNote(owner) {
|
|
42066
|
+
const who2 = owner.kind === "live" ? `a live run${owner.runId !== void 0 ? ` (\`${owner.runId}\`)` : ""}` : `the unfinished plan unit ${owner.unit}`;
|
|
42067
|
+
const steer = owner.kind === "live" && owner.runId !== void 0 ? ` bind \`steer run ${owner.runId} <words>\` to deliver it,` : "";
|
|
42068
|
+
return `This thread is owned by ${who2}: the request below is a follow-up for that owner. To act on it,${steer} bind a read command, or ask a question. Any other decision \u2014 a refusal, a preset, a write \u2014 folds the whole message into the owner unchanged and posts no answer.`;
|
|
42069
|
+
}
|
|
41956
42070
|
function operatorProjection(input) {
|
|
41957
42071
|
return {
|
|
41958
42072
|
presets: input.presets.filter((p) => input.allowedPresets.includes(p.name)),
|
|
@@ -41960,7 +42074,7 @@ function operatorProjection(input) {
|
|
|
41960
42074
|
};
|
|
41961
42075
|
}
|
|
41962
42076
|
function buildOperatorPrompt(input) {
|
|
41963
|
-
const
|
|
42077
|
+
const projection = input.owner ? ownedProjection(input.projection) : input.projection;
|
|
41964
42078
|
const commandList = projection.commands.map((c) => `- \`${c.tool.name}\`: ${oneLine(c.tool.description ?? c.id)}`).join("\n");
|
|
41965
42079
|
const system = [
|
|
41966
42080
|
// 1. Rules.
|
|
@@ -41979,6 +42093,7 @@ function buildOperatorPrompt(input) {
|
|
|
41979
42093
|
// 4. Tail, oldest first.
|
|
41980
42094
|
...input.tail.length > 0 ? ["The thread so far, oldest first:", ...input.tail.map((t) => `<turn>${quoteTurn(t.text)}</turn>`), ""] : [],
|
|
41981
42095
|
...input.pendingQuestion ? [`A question is pending: ${OPERATOR_QUESTION_MARKER} \`${input.pendingQuestion.proposal}\``, ""] : [],
|
|
42096
|
+
...input.owner ? [ownerNote(input.owner), ""] : [],
|
|
41982
42097
|
// 5. Request.
|
|
41983
42098
|
"<request>",
|
|
41984
42099
|
quoteRequest(input.text),
|
|
@@ -42050,7 +42165,8 @@ function bindViolationOf(line3, ordinal2, guard) {
|
|
|
42050
42165
|
const preset = presetBindOf(line3, guard.presets);
|
|
42051
42166
|
if (preset !== void 0) {
|
|
42052
42167
|
const words = oneLine(guard.requestText).trim();
|
|
42053
|
-
|
|
42168
|
+
const stripped = oneLine(stripDirectiveHead(guard.requestText, preset)).trim();
|
|
42169
|
+
if (words.length > 0 && !oneLine(line3).includes(words) && !oneLine(line3).includes(stripped))
|
|
42054
42170
|
return `bind ${ordinal2} names the preset "${preset}" but drops the request's own words; bind the preset on the request verbatim`;
|
|
42055
42171
|
return void 0;
|
|
42056
42172
|
}
|
|
@@ -42107,6 +42223,17 @@ function parseOperatorDecision(answer, guard) {
|
|
|
42107
42223
|
if (typeof text !== "string" || text.trim().length === 0) return refused7("a refusal with no text");
|
|
42108
42224
|
return { kind: "refusal", cause, text: redactAndCap(text, ROUTE_RECEIPT_CAP), reason: tidy(reason2) };
|
|
42109
42225
|
}
|
|
42226
|
+
function ownedDecisionRuns(event, owner, commands) {
|
|
42227
|
+
if (event.outcome !== "binds") return false;
|
|
42228
|
+
return (event.binds ?? []).every((bind) => {
|
|
42229
|
+
const parsed = commands ? parseChatCommand(bind.line, commands) : null;
|
|
42230
|
+
if (parsed?.kind !== "invoke") return false;
|
|
42231
|
+
const def = commands.list().find((c) => c.id === parsed.id);
|
|
42232
|
+
if (!def) return false;
|
|
42233
|
+
if (def.id === "steer.run") return !(owner.kind === "live" && owner.runId === void 0);
|
|
42234
|
+
return boundBlastRadius(def, parsed.input) === "read";
|
|
42235
|
+
});
|
|
42236
|
+
}
|
|
42110
42237
|
function bindFromAnswer(text, pending2) {
|
|
42111
42238
|
if (!/^yes[.!]?$/i.test(text.trim())) return void 0;
|
|
42112
42239
|
return { line: operatorLine(pending2.proposal), reason: "yes to the pending question's proposal", confirmed: true };
|
|
@@ -42253,6 +42380,7 @@ async function operatorStage(deps, ctx) {
|
|
|
42253
42380
|
projection,
|
|
42254
42381
|
tail,
|
|
42255
42382
|
...pending2 ? { pendingQuestion: pending2 } : {},
|
|
42383
|
+
...ctx.owner ? { owner: ctx.owner } : {},
|
|
42256
42384
|
...commands ? { registryParses: (line3) => parseChatCommand(line3, commands)?.kind === "invoke" } : {}
|
|
42257
42385
|
},
|
|
42258
42386
|
model
|
|
@@ -42273,6 +42401,10 @@ function presetBindOf(line3, presets) {
|
|
|
42273
42401
|
const name = head ? head[1] : /^(\S+)/.exec(trimmed)?.[1];
|
|
42274
42402
|
return name !== void 0 && presets.includes(name) ? name : void 0;
|
|
42275
42403
|
}
|
|
42404
|
+
function stripDirectiveHead(text, preset) {
|
|
42405
|
+
const m = /^([^\s:]+):(\S+)\s+(\S[\s\S]*)$/.exec(text.trim());
|
|
42406
|
+
return m && m[2] === preset ? m[3].trim() : text;
|
|
42407
|
+
}
|
|
42276
42408
|
function presetRequestOf(line3) {
|
|
42277
42409
|
const tail = /^(?:agent:\S+|\S+)\s+(.*\S)\s*$/s.exec(line3.trim());
|
|
42278
42410
|
return tail ? tail[1] : void 0;
|
|
@@ -42319,12 +42451,24 @@ function verifierModelOf(deps) {
|
|
|
42319
42451
|
}
|
|
42320
42452
|
async function executeOperatorDecision(deps, ctx) {
|
|
42321
42453
|
const { event, io, msg } = ctx;
|
|
42454
|
+
const verbose = shows(
|
|
42455
|
+
deps.config.verbosityFor(msg.channelId, msg.userId, parseDirectives(msg.text).verbosity),
|
|
42456
|
+
"verbose"
|
|
42457
|
+
);
|
|
42322
42458
|
const answered = { kind: "answered" };
|
|
42323
42459
|
if (event.outcome === "question") {
|
|
42324
42460
|
await io.reply(event.question ?? "");
|
|
42325
42461
|
await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
|
|
42326
42462
|
return answered;
|
|
42327
42463
|
}
|
|
42464
|
+
if (ctx.owner !== void 0 && !ownedDecisionRuns(event, ctx.owner, deps.commands)) {
|
|
42465
|
+
const confirmed = (event.binds ?? []).find((b) => b.confirmed);
|
|
42466
|
+
const request2 = confirmed !== void 0 ? presetBindOf(
|
|
42467
|
+
confirmed.line,
|
|
42468
|
+
routablePresets().map((p) => p.name)
|
|
42469
|
+
) !== void 0 ? presetRequestOf(confirmed.line) ?? confirmed.line : confirmed.line : void 0;
|
|
42470
|
+
return { kind: "fold", ...request2 !== void 0 ? { request: request2 } : {} };
|
|
42471
|
+
}
|
|
42328
42472
|
if (event.outcome === "refusal") {
|
|
42329
42473
|
await io.reply(event.refusalText ?? "");
|
|
42330
42474
|
await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
|
|
@@ -42346,13 +42490,18 @@ async function executeOperatorDecision(deps, ctx) {
|
|
|
42346
42490
|
const def = parsed?.kind === "invoke" ? commands?.list().find((c) => c.id === parsed.id) : void 0;
|
|
42347
42491
|
const bound = parsed?.kind === "invoke" && def ? { def, radius: boundBlastRadius(def, parsed.input) } : void 0;
|
|
42348
42492
|
const preset = bound ? void 0 : presetBindOf(bind.line, presetNames);
|
|
42349
|
-
const
|
|
42493
|
+
const requestWords = preset !== void 0 && !bind.confirmed ? stripDirectiveHead(msg.text, preset) : void 0;
|
|
42494
|
+
const runs = preset !== void 0 ? bind.confirmed ? bind.line : redactSecrets(`agent:${preset} ${requestWords}`) : bind.line;
|
|
42350
42495
|
const line3 = preset !== void 0 ? operatorLine(runs) : bind.line;
|
|
42351
42496
|
let verified;
|
|
42352
42497
|
if (verifierHolds(bind.line, bound, preset !== void 0)) {
|
|
42353
42498
|
const model = verifierModelOf(deps);
|
|
42354
42499
|
const verdict = model ? await verifyOperatorBind(await authorTurnsOnce(), runs, model) : { agrees: false, reason: "no model to verify on" };
|
|
42355
42500
|
if (!verdict.agrees) {
|
|
42501
|
+
if (bound === void 0 && !bind.confirmed) {
|
|
42502
|
+
for (const rest of binds.slice(i + 1)) await io.reply(renderHandBackLine(rest.line));
|
|
42503
|
+
return { kind: "fallback", reason: `the verifier held the bind: ${verdict.reason}`, carried };
|
|
42504
|
+
}
|
|
42356
42505
|
await io.reply(renderVerifierHandBack(bind.line, verdict.reason));
|
|
42357
42506
|
continue;
|
|
42358
42507
|
}
|
|
@@ -42361,17 +42510,16 @@ async function executeOperatorDecision(deps, ctx) {
|
|
|
42361
42510
|
if (preset !== void 0) {
|
|
42362
42511
|
const identity2 = presets.find((p) => p.name === preset)?.identity;
|
|
42363
42512
|
const radius2 = identity2 === "write" ? "write" : "read";
|
|
42364
|
-
|
|
42513
|
+
if (verbose)
|
|
42514
|
+
await io.reply(`${renderOperatorReceipt(line3, radius2, bind.reason)}${verified ? `
|
|
42365
42515
|
${verified}` : ""}`);
|
|
42366
|
-
for (const rest of binds.slice(i + 1)) await io.reply(
|
|
42367
|
-
|
|
42368
|
-
const request2 = bind.confirmed ? presetRequestOf(bind.line) : void 0;
|
|
42516
|
+
for (const rest of binds.slice(i + 1)) await io.reply(renderHandBackLine(rest.line));
|
|
42517
|
+
const request2 = bind.confirmed ? presetRequestOf(bind.line) : requestWords !== void 0 && requestWords !== msg.text ? requestWords : void 0;
|
|
42369
42518
|
return { kind: "route", preset, ...request2 !== void 0 ? { request: request2 } : {}, carried };
|
|
42370
42519
|
}
|
|
42371
42520
|
if (!parsed || parsed.kind !== "invoke" || !def || !bound) {
|
|
42372
|
-
await io.reply(`${verified ? `${verified}
|
|
42373
|
-
` : ""}${
|
|
42374
|
-
\`${bind.line}\``);
|
|
42521
|
+
await io.reply(`${verbose && verified ? `${verified}
|
|
42522
|
+
` : ""}${renderHandBackLine(bind.line)}`);
|
|
42375
42523
|
continue;
|
|
42376
42524
|
}
|
|
42377
42525
|
const radius = bound.radius;
|
|
@@ -42379,12 +42527,34 @@ ${verified}` : ""}`);
|
|
|
42379
42527
|
const receipt = `${renderOperatorReceipt(bind.line, radius, bind.reason)}${verified ? `
|
|
42380
42528
|
${verified}` : ""}`;
|
|
42381
42529
|
if (!runsNow) {
|
|
42382
|
-
await
|
|
42383
|
-
|
|
42384
|
-
|
|
42530
|
+
const mint = await mintConfirmationOffer({
|
|
42531
|
+
io,
|
|
42532
|
+
store: deps.confirmations,
|
|
42533
|
+
msg,
|
|
42534
|
+
def: bound.def,
|
|
42535
|
+
input: parsed.input,
|
|
42536
|
+
receipt: routeReceipt(bound.def, parsed.input),
|
|
42537
|
+
// The row's model is the decider's, as the routed offer stores the
|
|
42538
|
+
// router's: the operator runs on the ref behind `defaults.models.general`.
|
|
42539
|
+
model: deps.config.config.defaults.models["general"] ?? ""
|
|
42540
|
+
});
|
|
42541
|
+
if (mint.kind === "offered") {
|
|
42542
|
+
if (verbose) await io.reply(receipt);
|
|
42543
|
+
await renderConfirmationOffer(io, mint.shown);
|
|
42544
|
+
continue;
|
|
42545
|
+
}
|
|
42546
|
+
if (mint.kind === "unshowable") {
|
|
42547
|
+
await io.reply(`${verbose ? `${receipt}
|
|
42548
|
+
` : ""}${UNSHOWABLE_LINE}`);
|
|
42549
|
+
continue;
|
|
42550
|
+
}
|
|
42551
|
+
const note = mint.kind === "store_unreachable" ? `
|
|
42552
|
+
${STORE_UNREACHABLE_NOTE}` : "";
|
|
42553
|
+
await io.reply(`${verbose ? `${receipt}
|
|
42554
|
+
` : ""}${renderHandBackLine(bind.line)}${note}`);
|
|
42385
42555
|
continue;
|
|
42386
42556
|
}
|
|
42387
|
-
await io.reply(receipt);
|
|
42557
|
+
if (verbose) await io.reply(receipt);
|
|
42388
42558
|
const res = await runChatCommand(deps, msg, io, parsed, ctx.ending, ctx.trace, carried ? {} : { operator: event });
|
|
42389
42559
|
carried = true;
|
|
42390
42560
|
if (res.text.length > 0) await io.reply(res.text);
|
|
@@ -42397,6 +42567,8 @@ var init_operator = __esm({
|
|
|
42397
42567
|
"../../src/core/dispatch/operator.ts"() {
|
|
42398
42568
|
"use strict";
|
|
42399
42569
|
init_provider();
|
|
42570
|
+
init_directives();
|
|
42571
|
+
init_verbosity();
|
|
42400
42572
|
init_redact();
|
|
42401
42573
|
init_sessionLog();
|
|
42402
42574
|
init_actor();
|
|
@@ -42404,6 +42576,7 @@ var init_operator = __esm({
|
|
|
42404
42576
|
init_commandRegistry();
|
|
42405
42577
|
init_commandChat();
|
|
42406
42578
|
init_handBack();
|
|
42579
|
+
init_confirmations();
|
|
42407
42580
|
init_structured();
|
|
42408
42581
|
init_commandRun();
|
|
42409
42582
|
init_reply();
|
|
@@ -51750,7 +51923,7 @@ async function abandonLostWorkspace(ctx) {
|
|
|
51750
51923
|
registry,
|
|
51751
51924
|
runId: run2.id,
|
|
51752
51925
|
coordinator,
|
|
51753
|
-
kind: "interrupted",
|
|
51926
|
+
kind: restored ? "resumed" : "interrupted",
|
|
51754
51927
|
reason: summary,
|
|
51755
51928
|
clock: clock4,
|
|
51756
51929
|
...workflow ? { workflow } : {}
|
|
@@ -52278,16 +52451,31 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
52278
52451
|
let operatorPreset;
|
|
52279
52452
|
let operatorRequest;
|
|
52280
52453
|
let operatorThread;
|
|
52454
|
+
let pageOwner;
|
|
52281
52455
|
if (operatorMode !== "off") {
|
|
52282
52456
|
const runsService2 = deps.runs ?? createRunsService({ registry, store: deps.runStore });
|
|
52283
52457
|
operatorThread = opts.thread ?? await readThread(runsService2, msg.threadKey);
|
|
52458
|
+
const slot = admission.get(msg.threadKey);
|
|
52459
|
+
const liveElsewhere = deps.threadsElsewhere.get(msg.threadKey) !== void 0;
|
|
52460
|
+
if (slot === void 0 && !liveElsewhere && operatorThread !== void 0 && deps.coordinatorInstances !== void 0)
|
|
52461
|
+
pageOwner = await ownerOf2(operatorThread, (id) => deps.coordinatorInstances.listUnits(id), msg.threadKey);
|
|
52462
|
+
const threadOwner = slot !== void 0 || liveElsewhere ? { kind: "live", ...slot?.runId !== void 0 ? { runId: slot.runId } : {} } : pageOwner?.kind === "live" ? (
|
|
52463
|
+
// A hosted pipeline runner takes no inbox (thread-admission item
|
|
52464
|
+
// 9's seed rule): a steer offered here would queue words nothing
|
|
52465
|
+
// drains, so the owner rides without a run id — the prompt
|
|
52466
|
+
// offers no steer line, the executor folds a steer bind like any
|
|
52467
|
+
// other, and the fold runs on to the seed refusal below, which
|
|
52468
|
+
// names the unit threads to reply in.
|
|
52469
|
+
pageOwner.run.instanceId !== void 0 ? { kind: "live" } : { kind: "live", runId: pageOwner.run.id }
|
|
52470
|
+
) : pageOwner?.kind === "unit" ? { kind: "unit", unit: pageOwner.unit.unit } : void 0;
|
|
52284
52471
|
operatorEvent = await root.span(
|
|
52285
52472
|
"dispatch.operator",
|
|
52286
52473
|
() => operatorStage(deps, {
|
|
52287
52474
|
msg,
|
|
52288
52475
|
mode: operatorMode,
|
|
52289
52476
|
...operatorThread ? { thread: operatorThread } : {},
|
|
52290
|
-
...opts.intake ? { intake: opts.intake } : {}
|
|
52477
|
+
...opts.intake ? { intake: opts.intake } : {},
|
|
52478
|
+
...threadOwner ? { owner: threadOwner } : {}
|
|
52291
52479
|
})
|
|
52292
52480
|
);
|
|
52293
52481
|
const live = operatorEvent ? admission.get(msg.threadKey) : void 0;
|
|
@@ -52304,13 +52492,19 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
52304
52492
|
ending,
|
|
52305
52493
|
trace,
|
|
52306
52494
|
event: operatorEvent,
|
|
52307
|
-
...operatorThread ? { thread: operatorThread } : {}
|
|
52495
|
+
...operatorThread ? { thread: operatorThread } : {},
|
|
52496
|
+
...threadOwner ? { owner: threadOwner } : {}
|
|
52308
52497
|
})
|
|
52309
52498
|
);
|
|
52310
52499
|
if (execution.kind === "answered") return ended;
|
|
52311
|
-
|
|
52312
|
-
|
|
52313
|
-
|
|
52500
|
+
if (execution.kind !== "fold" && execution.carried) operatorEvent = void 0;
|
|
52501
|
+
if (execution.kind === "route") {
|
|
52502
|
+
operatorPreset = execution.preset;
|
|
52503
|
+
operatorRequest = execution.request;
|
|
52504
|
+
} else if (execution.kind === "fallback" && operatorEvent !== void 0) {
|
|
52505
|
+
operatorEvent = { ...operatorEvent, reason: execution.reason };
|
|
52506
|
+
}
|
|
52507
|
+
if (execution.kind === "fold" && execution.request !== void 0) operatorRequest = execution.request;
|
|
52314
52508
|
}
|
|
52315
52509
|
}
|
|
52316
52510
|
const recordPendingOperator = async () => {
|
|
@@ -52351,7 +52545,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
52351
52545
|
let route3;
|
|
52352
52546
|
const threadLive = admission.get(msg.threadKey) !== void 0 || !resume && !restart && deps.threadsElsewhere.get(msg.threadKey) !== void 0;
|
|
52353
52547
|
if (thread && !threadLive && deps.coordinatorInstances !== void 0) {
|
|
52354
|
-
const owner = await ownerOf2(thread, (id) => deps.coordinatorInstances.listUnits(id), msg.threadKey);
|
|
52548
|
+
const owner = pageOwner ?? await ownerOf2(thread, (id) => deps.coordinatorInstances.listUnits(id), msg.threadKey);
|
|
52355
52549
|
if (owner.kind === "live" && owner.run.instanceId !== void 0) {
|
|
52356
52550
|
const units = await deps.coordinatorInstances.listUnits(owner.run.instanceId).catch(() => []);
|
|
52357
52551
|
const open2 = units.filter((u) => u.ending === void 0);
|
|
@@ -53044,6 +53238,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
53044
53238
|
answer,
|
|
53045
53239
|
verdict: ran.verdict,
|
|
53046
53240
|
reviewPost: ran.reviewPost,
|
|
53241
|
+
verbosity: resolved.verbosity,
|
|
53047
53242
|
liveUrl,
|
|
53048
53243
|
prNote,
|
|
53049
53244
|
stopped,
|
|
@@ -65892,7 +66087,7 @@ var init_processMetrics = __esm({
|
|
|
65892
66087
|
|
|
65893
66088
|
// ../../src/core/boot.ts
|
|
65894
66089
|
function closureNote(agent, prUrl2) {
|
|
65895
|
-
if (agent === "ship") return shipInterruptedNote(prUrl2);
|
|
66090
|
+
if (agent === "ship") return shipInterruptedNote(prUrl2, "bot_restart");
|
|
65896
66091
|
return "The bot restarted while this run was in flight and it could not be resumed, so this card stopped updating. Re-send your request to run it again.";
|
|
65897
66092
|
}
|
|
65898
66093
|
function prUrlOf(events) {
|
|
@@ -67317,6 +67512,17 @@ async function readRecord(body, deps) {
|
|
|
67317
67512
|
const finalReply = finalReplyOf2(record2.events);
|
|
67318
67513
|
const pr = prOpenedOf(record2.events);
|
|
67319
67514
|
const instanceRow = await deps.instances.get(id.value);
|
|
67515
|
+
if (view.status === "interrupted") {
|
|
67516
|
+
const successor = await restartedChildOf(deps, id.value, view).catch(() => void 0);
|
|
67517
|
+
if (successor !== void 0)
|
|
67518
|
+
return json3(200, {
|
|
67519
|
+
ok: true,
|
|
67520
|
+
...instanceRow?.stop !== void 0 ? { stopped: true } : {},
|
|
67521
|
+
run: { id: successor, finished: false },
|
|
67522
|
+
restartedAs: successor,
|
|
67523
|
+
at
|
|
67524
|
+
});
|
|
67525
|
+
}
|
|
67320
67526
|
let posted;
|
|
67321
67527
|
if (record2.verdict !== void 0 && record2.reviewHead !== void 0 && typeof body.unit === "string") {
|
|
67322
67528
|
const instance = instanceRow;
|
|
@@ -67352,19 +67558,64 @@ async function readRecord(body, deps) {
|
|
|
67352
67558
|
...record2.handoff !== void 0 ? { handoffLists: record2.handoff } : {},
|
|
67353
67559
|
// The failure by name (run-history item 57): a `provider_transient` lets
|
|
67354
67560
|
// the machine re-run a round-0 child that pushed nothing (issue 1932).
|
|
67355
|
-
...record2.failure !== void 0 ? { failure: record2.failure } : {}
|
|
67561
|
+
...record2.failure !== void 0 ? { failure: record2.failure } : {},
|
|
67562
|
+
// What ended an interrupted child (issue 1876), off its record's own
|
|
67563
|
+
// events: the unit's ending names the actual cause in the user's nouns.
|
|
67564
|
+
...view.status === "interrupted" ? interruptionOf(record2.events) : {}
|
|
67356
67565
|
},
|
|
67357
67566
|
at
|
|
67358
67567
|
});
|
|
67359
67568
|
}
|
|
67569
|
+
function interruptionOf(events) {
|
|
67570
|
+
if (events === void 0) return {};
|
|
67571
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
67572
|
+
const e = events[i];
|
|
67573
|
+
const words = e.type === "child_interrupted" ? e.reason : e.type === "run_note" && (e.kind === "resumed" || e.kind === "sandbox_restarted" || e.kind === "harness_error") ? e.summary : void 0;
|
|
67574
|
+
if (words === void 0) continue;
|
|
67575
|
+
const cause = interruptionCauseOfWords(words);
|
|
67576
|
+
if (cause !== void 0) return { interruption: cause };
|
|
67577
|
+
}
|
|
67578
|
+
return {};
|
|
67579
|
+
}
|
|
67580
|
+
async function restartedChildOf(deps, instanceId, view) {
|
|
67581
|
+
if (view.threadKey === void 0 || view.idempotencyKey === void 0) return void 0;
|
|
67582
|
+
const listing = await deps.runs.listRuns({
|
|
67583
|
+
status: "all",
|
|
67584
|
+
visibleTo: { kind: "all" },
|
|
67585
|
+
threadKey: view.threadKey,
|
|
67586
|
+
limit: RUN_LIST_MAX_LIMIT
|
|
67587
|
+
});
|
|
67588
|
+
const successor = listing.runs.find(
|
|
67589
|
+
(r) => r.id !== view.id && r.parentInstanceId === instanceId && r.idempotencyKey === view.idempotencyKey && r.startedAt >= view.startedAt
|
|
67590
|
+
);
|
|
67591
|
+
return successor?.id;
|
|
67592
|
+
}
|
|
67360
67593
|
function isEmptyBranchRefusal(err2) {
|
|
67361
67594
|
const message4 = err2 instanceof Error ? err2.message : String(err2);
|
|
67362
67595
|
return /HTTP 422\b/.test(message4) && /no commits between/i.test(message4);
|
|
67363
67596
|
}
|
|
67597
|
+
function cutAtWordBoundary(line3, max, after) {
|
|
67598
|
+
const tidy2 = (s) => s.replace(/[\s.,;:—–-]+$/u, "");
|
|
67599
|
+
if (line3.length <= max) return tidy2(line3);
|
|
67600
|
+
const head = line3.slice(0, max + 1);
|
|
67601
|
+
const space = head.lastIndexOf(" ");
|
|
67602
|
+
return tidy2(space > after ? head.slice(0, space) : line3.slice(0, max));
|
|
67603
|
+
}
|
|
67604
|
+
function recoveredFallbackTitle(row2, branch2, planId) {
|
|
67605
|
+
const raw = (row2?.title ?? `${row2?.unit ?? "the unit"} \u2014 ${branch2}`).trim();
|
|
67606
|
+
if (checkPrTitle(raw, prTitleVocabulary_default).ok) return raw;
|
|
67607
|
+
const words = raw.split(/\s+/u);
|
|
67608
|
+
const first = (words[0] ?? "").toLowerCase().replace(/:$/u, "");
|
|
67609
|
+
const type = prTitleVocabulary_default.types.includes(first) ? first : "chore";
|
|
67610
|
+
const rest = (type === first ? words.slice(1) : words).join(" ");
|
|
67611
|
+
const area = planId?.split("-").find((seg) => prTitleVocabulary_default.scopes.includes(seg) && !BOT_SCOPES.includes(seg));
|
|
67612
|
+
const prefix = `${type}${area !== void 0 ? `(${area})` : ""}: `;
|
|
67613
|
+
return cutAtWordBoundary(`${prefix}${rest === "" ? branch2 : rest}`, TITLE_MAX_LENGTH, prefix.length);
|
|
67614
|
+
}
|
|
67364
67615
|
async function recoverPushedBranch(deps, instance, row2, branch2, runId3) {
|
|
67365
67616
|
if (instance.base === void 0) return { kind: "none", why: "no_base" };
|
|
67366
67617
|
const unitName = row2?.unit ?? "the unit";
|
|
67367
|
-
let title = row2
|
|
67618
|
+
let title = recoveredFallbackTitle(row2, branch2, instance.plan?.id ?? parsePlanBranch(branch2)?.planId);
|
|
67368
67619
|
let prBody = `Opened by the plan runner from the pushed branch \`${branch2}\`: the coding run ${runId3} of ${unitName} ended before it could open the pull request or submit its description. The review round asks for the description.`;
|
|
67369
67620
|
try {
|
|
67370
67621
|
const full = await deps.runs.getRun(runId3, { include: "messages" });
|
|
@@ -67682,10 +67933,10 @@ async function branch(body, deps) {
|
|
|
67682
67933
|
function unitLines(units, generated, severity) {
|
|
67683
67934
|
return units.map((u) => {
|
|
67684
67935
|
const last = u.rounds.at(-1);
|
|
67685
|
-
const state = u.ending ? u.ending.kind : u.idle ? (
|
|
67686
|
-
// The idle line names the old kind (record 0051): `idle ·
|
|
67687
|
-
`idle \xB7 ${u.idle.why}`
|
|
67688
|
-
) : last ? `${shipRoundHeader({ index: last.index, agent: last.agent }, severity)} \xB7 ${last.outcome}${last.gate ? ` \xB7 \u26A0\uFE0F gate fired: ${last.gate.findings.join(", ")} at or above ${last.gate.level}` : ""}` : u.threadKey ? "starting" : "waiting";
|
|
67936
|
+
const state = u.ending ? endingWordOf(u.ending.kind) : u.idle ? (
|
|
67937
|
+
// The idle line names the old kind's word (record 0051): `idle · out of budget`.
|
|
67938
|
+
`idle \xB7 ${endingWordOf(u.idle.why)}`
|
|
67939
|
+
) : last ? `${shipRoundHeader({ index: last.index, agent: last.agent }, severity)} \xB7 ${roundOutcomeWordOf(last.outcome)}${last.gate ? ` \xB7 \u26A0\uFE0F gate fired: ${last.gate.findings.join(", ")} at or above ${last.gate.level}` : ""}` : u.threadKey ? "starting" : "waiting";
|
|
67689
67940
|
const seg = u.ending === void 0 && u.segments !== void 0 && u.segments.length > 0 ? `segment ${u.segments[u.segments.length - 1].index} \xB7 ` : "";
|
|
67690
67941
|
return generated ? `${seg}${state}` : `${u.unit} \xB7 ${seg}${state}`;
|
|
67691
67942
|
});
|
|
@@ -68177,9 +68428,10 @@ async function checksStep(body, deps) {
|
|
|
68177
68428
|
}
|
|
68178
68429
|
function planSummary(units, generated = false) {
|
|
68179
68430
|
const lines = units.map((u) => {
|
|
68180
|
-
const
|
|
68431
|
+
const kind = u.ending ? u.ending.kind : u.threadKey ? "unfinished" : "not started";
|
|
68432
|
+
const how = u.ending ? endingWordOf(u.ending.kind) : kind;
|
|
68181
68433
|
const pr = u.pr ? ` \u2014 ${u.pr.url}` : "";
|
|
68182
|
-
return generated ? `${ENDING_ICON[
|
|
68434
|
+
return generated ? `${ENDING_ICON[kind] ?? "\u2022"} ${how}${pr}` : `${ENDING_ICON[kind] ?? "\u2022"} ${u.unit} \u2014 ${how}${pr}`;
|
|
68183
68435
|
});
|
|
68184
68436
|
return lines.join("\n");
|
|
68185
68437
|
}
|
|
@@ -68384,8 +68636,11 @@ var init_adminCoordinator = __esm({
|
|
|
68384
68636
|
init_runFriction();
|
|
68385
68637
|
init_runRecord();
|
|
68386
68638
|
init_coordinator();
|
|
68639
|
+
init_prTitle();
|
|
68640
|
+
init_prTitleVocabulary();
|
|
68387
68641
|
init_handoff();
|
|
68388
68642
|
init_reviewedHead();
|
|
68643
|
+
init_pipelineStanding();
|
|
68389
68644
|
init_shipPipeline();
|
|
68390
68645
|
init_statusCardFrame();
|
|
68391
68646
|
init_clock();
|