@coreplane/switchboard 1.214.0 → 1.215.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/config/config.example.yaml +2 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +568 -6
- package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +11 -2
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/coordinator/contract.ts +6 -0
- package/dist/assets/src/core/runEvents.ts +12 -8
- package/dist/assets/src/core/runLedger/sessionLog.ts +207 -0
- package/dist/assets/src/core/runLedger/transcript.ts +187 -0
- package/dist/assets/src/core/runLedger/types.ts +22 -5
- package/dist/assets/src/core/runRecord.ts +50 -1
- package/dist/assets/src/core/ship/coordinator.ts +70 -33
- package/dist/assets/src/execution/residentInstanceId.ts +11 -3
- package/dist/cli.js +628 -279
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2892,8 +2892,11 @@ function validateRunHistory(rh) {
|
|
|
2892
2892
|
if (v !== void 0 && (!Number.isInteger(v) || v < 1))
|
|
2893
2893
|
throw new Error(`config.yaml: runHistory.${key} must be an integer >= 1`);
|
|
2894
2894
|
}
|
|
2895
|
-
|
|
2896
|
-
|
|
2895
|
+
for (const key of ["maxBytes", "sessionLogMaxBytes"]) {
|
|
2896
|
+
const v = rh[key];
|
|
2897
|
+
if (v !== void 0 && (!Number.isInteger(v) || v < 1))
|
|
2898
|
+
throw new Error(`config.yaml: runHistory.${key} must be an integer >= 1`);
|
|
2899
|
+
}
|
|
2897
2900
|
if (rh.store !== void 0 && rh.store !== "worker" && rh.store !== "file")
|
|
2898
2901
|
throw new Error('config.yaml: runHistory.store must be "worker" or "file"');
|
|
2899
2902
|
if (rh.worker !== void 0) {
|
|
@@ -12790,6 +12793,7 @@ function isCoordinatorUnit(v) {
|
|
|
12790
12793
|
if (!isText(r.unit, 32) || !isText(r.slug) || !isText(r.branch) || !isOptionalText(r.title)) return false;
|
|
12791
12794
|
if (!Array.isArray(r.dependsOn) || !r.dependsOn.every((d) => isText(d, 32))) return false;
|
|
12792
12795
|
if (!isOptionalText(r.threadKey) || !isOptionalText(r.sourceUrl)) return false;
|
|
12796
|
+
if (r.reviewThread !== void 0 && !isThread(r.reviewThread)) return false;
|
|
12793
12797
|
if (r.issue !== void 0 && !isFinite(r.issue)) return false;
|
|
12794
12798
|
if (r.pr !== void 0 && !isPr(r.pr)) return false;
|
|
12795
12799
|
if (r.resume !== void 0 && !isResume(r.resume)) return false;
|
|
@@ -12800,7 +12804,7 @@ function isCoordinatorUnit(v) {
|
|
|
12800
12804
|
if (r.startedAt !== void 0 && !isFinite(r.startedAt)) return false;
|
|
12801
12805
|
return true;
|
|
12802
12806
|
}
|
|
12803
|
-
var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, REPO_SLUG2, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isObject2, isPr, isResume;
|
|
12807
|
+
var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, REPO_SLUG2, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isObject2, isPr, isResume, isThread;
|
|
12804
12808
|
var init_contract3 = __esm({
|
|
12805
12809
|
"../../src/core/coordinator/contract.ts"() {
|
|
12806
12810
|
"use strict";
|
|
@@ -12821,6 +12825,7 @@ var init_contract3 = __esm({
|
|
|
12821
12825
|
isObject2 = (v) => typeof v === "object" && v !== null;
|
|
12822
12826
|
isPr = (v) => isObject2(v) && isFinite(v.number) && isText(v.url, 2048);
|
|
12823
12827
|
isResume = (v) => isObject2(v) && isFinite(v.pr) && (v.headSha === void 0 || isText(v.headSha)) && (v.url === void 0 || isText(v.url, 2048));
|
|
12828
|
+
isThread = (v) => isObject2(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
|
|
12824
12829
|
}
|
|
12825
12830
|
});
|
|
12826
12831
|
|
|
@@ -13596,6 +13601,17 @@ var init_runFriction = __esm({
|
|
|
13596
13601
|
});
|
|
13597
13602
|
|
|
13598
13603
|
// ../../src/core/runRecord.ts
|
|
13604
|
+
function isRunSession(v) {
|
|
13605
|
+
if (typeof v !== "object" || v === null) return false;
|
|
13606
|
+
const s = v;
|
|
13607
|
+
if (typeof s.key !== "string" || !SESSION_KEY_PATTERN.test(s.key)) return false;
|
|
13608
|
+
if (!isIndex(s.seedFrom) || !isIndex(s.request) || s.request < s.seedFrom) return false;
|
|
13609
|
+
if (s.range === "broken") return true;
|
|
13610
|
+
if (typeof s.range !== "object" || s.range === null) return false;
|
|
13611
|
+
const range = s.range;
|
|
13612
|
+
if (!isIndex(range.from) || range.from < s.seedFrom) return false;
|
|
13613
|
+
return range.to === void 0 || isIndex(range.to) && range.to >= range.from;
|
|
13614
|
+
}
|
|
13599
13615
|
function isRunProfileRecord(v) {
|
|
13600
13616
|
if (typeof v !== "object" || v === null) return false;
|
|
13601
13617
|
const p = v;
|
|
@@ -13674,7 +13690,12 @@ function clampRetentionPolicy(partial) {
|
|
|
13674
13690
|
const [lo, hi] = RETENTION_BOUNDS[k];
|
|
13675
13691
|
return Math.min(hi, Math.max(lo, n2));
|
|
13676
13692
|
};
|
|
13677
|
-
return {
|
|
13693
|
+
return {
|
|
13694
|
+
retentionDays: field("retentionDays"),
|
|
13695
|
+
maxRuns: field("maxRuns"),
|
|
13696
|
+
maxBytes: field("maxBytes"),
|
|
13697
|
+
sessionLogMaxBytes: field("sessionLogMaxBytes")
|
|
13698
|
+
};
|
|
13678
13699
|
}
|
|
13679
13700
|
function isOptionalString(v) {
|
|
13680
13701
|
return v === void 0 || typeof v === "string";
|
|
@@ -13741,6 +13762,7 @@ function isRunRecord(v) {
|
|
|
13741
13762
|
if (r.parentRunId !== void 0 && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
|
|
13742
13763
|
return false;
|
|
13743
13764
|
if (r.seed !== void 0 && !RUN_SEEDS.includes(r.seed)) return false;
|
|
13765
|
+
if (r.session !== void 0 && !isRunSession(r.session)) return false;
|
|
13744
13766
|
if (r.parentInstanceId === void 0 !== (r.idempotencyKey === void 0)) return false;
|
|
13745
13767
|
if (r.parentInstanceId !== void 0 && (typeof r.parentInstanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.parentInstanceId)))
|
|
13746
13768
|
return false;
|
|
@@ -13872,7 +13894,7 @@ function dropSpanPairsFromTheMiddle(events, eventBudget) {
|
|
|
13872
13894
|
}
|
|
13873
13895
|
return events.filter((_, k) => !dropped.has(k));
|
|
13874
13896
|
}
|
|
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;
|
|
13897
|
+
var RUN_STATUSES, RUN_ID_PATTERN, REVIEW_HEAD_PATTERN, RUN_SEEDS, SESSION_KEY_PATTERN, isIndex, MACHINE_CLASSES_IN_RECORD, IDENTITIES_IN_RECORD, BOUNDARY_SCOPES_IN_RECORD, RUN_LIST_DEFAULT_LIMIT, RUN_LIST_MAX_LIMIT, RUN_EVENTS_DEFAULT_PAGE, RUN_EVENTS_MAX_PAGE, CHANNEL_VISIBILITIES2, KIB, MIB, GIB2, DEFAULT_RETENTION_POLICY, RETENTION_BOUNDS, MAX_RECORD_BYTES, MAX_EVENT_BYTES, ELLIPSIS, encoder;
|
|
13876
13898
|
var init_runRecord = __esm({
|
|
13877
13899
|
"../../src/core/runRecord.ts"() {
|
|
13878
13900
|
"use strict";
|
|
@@ -13885,6 +13907,8 @@ var init_runRecord = __esm({
|
|
|
13885
13907
|
RUN_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13886
13908
|
REVIEW_HEAD_PATTERN = /^[0-9a-f]{7,40}$/;
|
|
13887
13909
|
RUN_SEEDS = ["channel", "parent"];
|
|
13910
|
+
SESSION_KEY_PATTERN = /^[A-Za-z0-9_.:@+/-]{1,512}$/;
|
|
13911
|
+
isIndex = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
13888
13912
|
MACHINE_CLASSES_IN_RECORD = {
|
|
13889
13913
|
none: true,
|
|
13890
13914
|
blank: true,
|
|
@@ -13910,12 +13934,14 @@ var init_runRecord = __esm({
|
|
|
13910
13934
|
DEFAULT_RETENTION_POLICY = {
|
|
13911
13935
|
retentionDays: 30,
|
|
13912
13936
|
maxRuns: 5e3,
|
|
13913
|
-
maxBytes: 2 * GIB2
|
|
13937
|
+
maxBytes: 2 * GIB2,
|
|
13938
|
+
sessionLogMaxBytes: 200 * MIB
|
|
13914
13939
|
};
|
|
13915
13940
|
RETENTION_BOUNDS = {
|
|
13916
13941
|
retentionDays: [1, 365],
|
|
13917
13942
|
maxRuns: [1, 2e4],
|
|
13918
|
-
maxBytes: [16 * MIB, 8 * GIB2]
|
|
13943
|
+
maxBytes: [16 * MIB, 8 * GIB2],
|
|
13944
|
+
sessionLogMaxBytes: [16 * MIB, 2 * GIB2]
|
|
13919
13945
|
};
|
|
13920
13946
|
MAX_RECORD_BYTES = 1.5 * MIB;
|
|
13921
13947
|
MAX_EVENT_BYTES = 64 * KIB;
|
|
@@ -22079,6 +22105,9 @@ function reclaimedRunRecord(input) {
|
|
|
22079
22105
|
...row.meta.profile && row.meta.agent ? { profile: { preset: row.meta.agent, ...row.meta.profile } } : {},
|
|
22080
22106
|
...row.meta.parentRunId !== void 0 ? { parentRunId: row.meta.parentRunId } : {},
|
|
22081
22107
|
...row.meta.seed !== void 0 ? { seed: row.meta.seed } : {},
|
|
22108
|
+
// The row's place in its session log (item 53): the range stays open on a
|
|
22109
|
+
// record closed here, since the closer has no turn count to end it with.
|
|
22110
|
+
...row.meta.session !== void 0 ? { session: row.meta.session } : {},
|
|
22082
22111
|
// A coordinator's child keeps its instance and key on the close (item 48),
|
|
22083
22112
|
// so the state Worker's finish still sends the parent its event.
|
|
22084
22113
|
...row.meta.parentInstanceId !== void 0 && row.meta.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: row.meta.parentInstanceId, idempotencyKey: row.meta.idempotencyKey } } : {},
|
|
@@ -22148,7 +22177,8 @@ function assembleRunRecord(input) {
|
|
|
22148
22177
|
...input.profile !== void 0 ? { profile: input.profile } : {},
|
|
22149
22178
|
...input.parentRunId !== void 0 ? { parentRunId: input.parentRunId } : {},
|
|
22150
22179
|
...coordinatorFields(input.coordinator),
|
|
22151
|
-
...input.seed !== void 0 ? { seed: input.seed } : {}
|
|
22180
|
+
...input.seed !== void 0 ? { seed: input.seed } : {},
|
|
22181
|
+
...input.session !== void 0 ? { session: input.session } : {}
|
|
22152
22182
|
});
|
|
22153
22183
|
return fitted.eventCount !== fitted.storedEventCount ? { ...fitted, truncated: true } : fitted;
|
|
22154
22184
|
}
|
|
@@ -22346,7 +22376,9 @@ async function admit(deps, ctx) {
|
|
|
22346
22376
|
threadKey: msg.threadKey,
|
|
22347
22377
|
state: resume.row.state,
|
|
22348
22378
|
lastStep: resume.lastStep.step,
|
|
22349
|
-
lastSeq: resume.lastSeq
|
|
22379
|
+
lastSeq: resume.lastSeq,
|
|
22380
|
+
// The row's place in its session log, so the resumed run appends where it left off.
|
|
22381
|
+
...resume.row.meta.session ? { session: resume.row.meta.session } : {}
|
|
22350
22382
|
});
|
|
22351
22383
|
await root.span(
|
|
22352
22384
|
"dispatch.admission",
|
|
@@ -22496,6 +22528,8 @@ async function adoptCarriedRun(deps, ctx) {
|
|
|
22496
22528
|
state: resume.row.state,
|
|
22497
22529
|
lastStep: resume.lastStep.step,
|
|
22498
22530
|
lastSeq: resume.lastSeq,
|
|
22531
|
+
// The row's place in its session log, so the resumed run appends where it left off.
|
|
22532
|
+
...resume.row.meta.session ? { session: resume.row.meta.session } : {},
|
|
22499
22533
|
...ctx.hooks.adopt
|
|
22500
22534
|
});
|
|
22501
22535
|
}
|
|
@@ -24590,6 +24624,12 @@ function routablePresets() {
|
|
|
24590
24624
|
maxMinutes
|
|
24591
24625
|
}));
|
|
24592
24626
|
}
|
|
24627
|
+
function partPresets(presets) {
|
|
24628
|
+
return presets.filter((p) => p.identity !== "write");
|
|
24629
|
+
}
|
|
24630
|
+
function nameList(names) {
|
|
24631
|
+
return names.length <= 1 ? names.join("") : `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}`;
|
|
24632
|
+
}
|
|
24593
24633
|
function renderPresetTable(presets) {
|
|
24594
24634
|
const rows = presets.map(
|
|
24595
24635
|
(p) => `| \`${p.name}\` | ${oneLine(p.description)} | ${p.machine} | ${p.identity} | ${p.maxMinutes} min |`
|
|
@@ -24604,7 +24644,10 @@ function routeMaxOutputTokens(compound) {
|
|
|
24604
24644
|
}
|
|
24605
24645
|
function routeTool(presets, compound) {
|
|
24606
24646
|
const names = presets.map((p) => p.name);
|
|
24647
|
+
const readers = partPresets(presets).map((p) => p.name);
|
|
24648
|
+
const writers = presets.filter((p) => p.identity === "write").map((p) => p.name);
|
|
24607
24649
|
const table = presets.map((p) => `${p.name}: ${oneLine(p.description)}`).join("; ");
|
|
24650
|
+
const writeAsk = writers.length > 0 ? ` An ask that needs ${nameList(writers)} is never a part: when any part would need it, omit parts and answer ${nameList(writers)} for the whole request.` : "";
|
|
24608
24651
|
const properties = {
|
|
24609
24652
|
preset: {
|
|
24610
24653
|
type: "string",
|
|
@@ -24615,7 +24658,7 @@ function routeTool(presets, compound) {
|
|
|
24615
24658
|
...compound ? {
|
|
24616
24659
|
parts: {
|
|
24617
24660
|
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}"
|
|
24661
|
+
description: `only when the request has two or more INDEPENDENT asks on different subjects, each rewritten so it stands alone and each on a read-only preset (${nameList(readers)}), with preset "${COMPOUND_PRESET}".${writeAsk} A single ask with several steps is one request on one preset: omit parts and name that preset. When one ask is in doubt, omit parts.`,
|
|
24619
24662
|
minItems: 2,
|
|
24620
24663
|
maxItems: compound.maxParts,
|
|
24621
24664
|
items: {
|
|
@@ -24623,7 +24666,7 @@ function routeTool(presets, compound) {
|
|
|
24623
24666
|
additionalProperties: false,
|
|
24624
24667
|
required: ["preset", "text"],
|
|
24625
24668
|
properties: {
|
|
24626
|
-
preset: { type: "string", enum:
|
|
24669
|
+
preset: { type: "string", enum: readers, description: "the read-only preset this part runs on" },
|
|
24627
24670
|
text: { type: "string", description: "the part as a request of its own" }
|
|
24628
24671
|
}
|
|
24629
24672
|
}
|
|
@@ -24660,7 +24703,7 @@ function buildRoutePrompt(input) {
|
|
|
24660
24703
|
"",
|
|
24661
24704
|
"Presets you may pick:",
|
|
24662
24705
|
renderPresetTable(input.presets),
|
|
24663
|
-
...input.compound ? ["", ...compoundRules(input.compound)] : []
|
|
24706
|
+
...input.compound ? ["", ...compoundRules(input.compound, input.presets)] : []
|
|
24664
24707
|
].join("\n");
|
|
24665
24708
|
const user = [
|
|
24666
24709
|
`Earlier directives in this thread: ${directivesLine(input.recentDirectives)}`,
|
|
@@ -24671,13 +24714,20 @@ function buildRoutePrompt(input) {
|
|
|
24671
24714
|
].join("\n");
|
|
24672
24715
|
return { system, user, tool: routeTool(input.presets, input.compound) };
|
|
24673
24716
|
}
|
|
24674
|
-
function compoundRules(offer) {
|
|
24717
|
+
function compoundRules(offer, presets) {
|
|
24718
|
+
const readers = partPresets(presets).map((p) => p.name);
|
|
24719
|
+
const writers = presets.filter((p) => p.identity === "write").map((p) => `\`${p.name}\``);
|
|
24675
24720
|
return [
|
|
24676
24721
|
"Compound requests: when the request has two or more INDEPENDENT parts \u2014 neither part needs the other's result, and each would stand alone as a request of its own \u2014 answer this form instead, and only then:",
|
|
24677
|
-
`{"preset": "${COMPOUND_PRESET}", "parts": [{"text": "<one part, rewritten so it stands alone>", "preset": "<
|
|
24678
|
-
`Independent means neither part needs the other's result. A single ask with several steps ("clone it, run the tests, tell me what fails") is NOT compound: it is one request on one preset, however many steps it takes. Two asks on two different subjects ("summarize what is in the docs folder, and run the lint on main in a sandbox") ARE compound even when both are read-only or would land on the same preset \u2014 the same preset may appear twice. When one ask is in doubt, do not split it. At most ${offer.maxParts} parts
|
|
24722
|
+
`{"preset": "${COMPOUND_PRESET}", "parts": [{"text": "<one part, rewritten so it stands alone>", "preset": "<one of ${readers.join(", ")}>"}, \u2026], "reason": "<one line: why the parts are independent>"}`,
|
|
24723
|
+
`Independent means neither part needs the other's result. A single ask with several steps ("clone it, run the tests, tell me what fails") is NOT compound: it is one request on one preset, however many steps it takes. Two asks on two different subjects ("summarize what is in the docs folder, and run the lint on main in a sandbox") ARE compound even when both are read-only or would land on the same preset \u2014 the same preset may appear twice. When one ask is in doubt, do not split it. At most ${offer.maxParts} parts. Each part runs as a child that only reads, so each part's preset is one of ${readers.map((r) => `\`${r}\``).join(", ")} (the rows above whose credential is none or read), chosen for that part alone by the same rules as a single request \u2014 least capable first.`,
|
|
24724
|
+
...writers.length > 0 ? [writeAskRule(writers)] : []
|
|
24679
24725
|
];
|
|
24680
24726
|
}
|
|
24727
|
+
function writeAskRule(writers) {
|
|
24728
|
+
const names = writers.join(" or ");
|
|
24729
|
+
return `An ask that needs ${names} is never a part: a part runs as a child that only reads, and ${names} pushes. When any part of the request would need ${names}, do not split it: answer ${names} alone for the whole request as typed, and it reads what it must before it changes anything ("review PR 7 and fix what it finds" is one ${names} request; "research X and open a PR for Y" is one ${names} request).`;
|
|
24730
|
+
}
|
|
24681
24731
|
function imperativeRule(writers) {
|
|
24682
24732
|
const names = writers.map((w) => `\`${w}\``).join(" or ");
|
|
24683
24733
|
return `Short imperatives: one terse order to change something or to make a failure go away \u2014 "fix it", "make it pass", "make the tests green", "add X", "rename Y", "bump Z" \u2014 is a request to change code even when it names no file, repository or cause: the channel or thread it arrives in is bound to a repository, and the preset that implements changes finds the failure itself. For it, answer ${names}. A question or a read-only ask about the same failure \u2014 "why did ci fail?", "check whether ci is red", "tell me why the build failed", "list the failing tests" \u2014 changes nothing: answer a read-only preset that covers it, never ${names}. An ask to look at, check or judge a pull request \u2014 named by a link or a number, or the thread's own ("this PR") \u2014 is a review, not an order to change it; a question about a failure with no pull request in view is not a review.`;
|
|
@@ -24729,16 +24779,20 @@ function parseCompound(parts, reason, allowed2, offer) {
|
|
|
24729
24779
|
if (tidy.length === 0) return rejected(`part ${n2} has no text`);
|
|
24730
24780
|
out.push({ preset, text: tidy });
|
|
24731
24781
|
}
|
|
24782
|
+
const writer = out.find((p) => identityOf(p.preset) === "write");
|
|
24783
|
+
if (writer) return { preset: writer.preset, reason, collapsed: { presets: out.map((p) => p.preset) } };
|
|
24732
24784
|
return { preset: COMPOUND_PRESET, reason, parts: out };
|
|
24733
24785
|
}
|
|
24734
24786
|
async function route(input, model, opts = {}) {
|
|
24735
|
-
const
|
|
24787
|
+
const { compound: offer, ...rest } = input;
|
|
24788
|
+
const offered = rest.presets.filter((p) => rest.allowed.includes(p.name));
|
|
24736
24789
|
if (offered.length === 0) return { preset: void 0, reason: "no preset the requester may run" };
|
|
24737
|
-
const
|
|
24790
|
+
const compound = offer && partPresets(offered).length > 0 ? offer : void 0;
|
|
24791
|
+
const prompt = buildRoutePrompt({ ...rest, presets: offered, ...compound ? { compound } : {} });
|
|
24738
24792
|
let raw;
|
|
24739
24793
|
try {
|
|
24740
24794
|
raw = await model(prompt, {
|
|
24741
|
-
maxTokens: routeMaxOutputTokens(
|
|
24795
|
+
maxTokens: routeMaxOutputTokens(compound),
|
|
24742
24796
|
signal: AbortSignal.timeout(opts.timeoutMs ?? ROUTE_TIMEOUT_MS)
|
|
24743
24797
|
});
|
|
24744
24798
|
} catch (err2) {
|
|
@@ -24750,7 +24804,7 @@ async function route(input, model, opts = {}) {
|
|
|
24750
24804
|
return parseRouteAnswer(
|
|
24751
24805
|
raw,
|
|
24752
24806
|
offered.map((p) => p.name),
|
|
24753
|
-
|
|
24807
|
+
compound
|
|
24754
24808
|
);
|
|
24755
24809
|
}
|
|
24756
24810
|
function providerRouteModel(provider, model, opts = {}) {
|
|
@@ -24824,18 +24878,24 @@ async function routeRequest(deps, ctx) {
|
|
|
24824
24878
|
effort: directives.effort ?? sticky.effort
|
|
24825
24879
|
}
|
|
24826
24880
|
});
|
|
24827
|
-
const parts = decision
|
|
24881
|
+
const { parts, collapsed } = decision;
|
|
24828
24882
|
console.log(
|
|
24829
|
-
`[route] ${msg.threadKey} routed to ${decision.preset} on ${modelRef2}${parts ? ` (${parts.length} parts)` : ""}: ${decision.reason}`
|
|
24883
|
+
`[route] ${msg.threadKey} routed to ${decision.preset} on ${modelRef2}${parts ? ` (${parts.length} parts)` : ""}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}: ${decision.reason}`
|
|
24830
24884
|
);
|
|
24831
24885
|
return {
|
|
24832
24886
|
kind: "routed",
|
|
24833
24887
|
resolved,
|
|
24834
|
-
route: {
|
|
24888
|
+
route: {
|
|
24889
|
+
preset: decision.preset,
|
|
24890
|
+
reason: decision.reason,
|
|
24891
|
+
model: modelRef2,
|
|
24892
|
+
...parts ? { parts } : {},
|
|
24893
|
+
...collapsed ? { collapsed } : {}
|
|
24894
|
+
}
|
|
24835
24895
|
};
|
|
24836
24896
|
}
|
|
24837
|
-
function routedLabel(reason) {
|
|
24838
|
-
return `routed: ${reason}`;
|
|
24897
|
+
function routedLabel(reason, collapsed) {
|
|
24898
|
+
return `routed: ${reason}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}`;
|
|
24839
24899
|
}
|
|
24840
24900
|
function routedPartLines(parts) {
|
|
24841
24901
|
return parts.map((p) => {
|
|
@@ -24851,7 +24911,7 @@ function compoundBrief(text, parts) {
|
|
|
24851
24911
|
...parts.map((p, i) => `${i + 1}. \`${p.preset}\`: ${p.text}`)
|
|
24852
24912
|
].join("\n");
|
|
24853
24913
|
}
|
|
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;
|
|
24914
|
+
var ROUTE_TEXT_CAP, ROUTE_REASON_CAP, ROUTE_MIN_OUTPUT_TOKENS, ROUTE_TOOL_NAME, ROUTE_TIMEOUT_MS, ROUTE_PART_TEXT_CAP, ROUTE_PART_LINE_CAP, COMPOUND_BRIEF_HEADING, identityOf, ROUTED_CARD_FOOTER;
|
|
24855
24915
|
var init_route = __esm({
|
|
24856
24916
|
"../../src/core/dispatch/route.ts"() {
|
|
24857
24917
|
"use strict";
|
|
@@ -24868,6 +24928,7 @@ var init_route = __esm({
|
|
|
24868
24928
|
ROUTE_PART_TEXT_CAP = 1e3;
|
|
24869
24929
|
ROUTE_PART_LINE_CAP = 100;
|
|
24870
24930
|
COMPOUND_BRIEF_HEADING = "Routed as a compound request";
|
|
24931
|
+
identityOf = (preset) => AGENTS[preset]?.identity;
|
|
24871
24932
|
ROUTED_CARD_FOOTER = "reply agent:<preset> to run it another way";
|
|
24872
24933
|
}
|
|
24873
24934
|
});
|
|
@@ -26638,7 +26699,7 @@ ${raw.trim()}`;
|
|
|
26638
26699
|
};
|
|
26639
26700
|
submitDispositionsTool = {
|
|
26640
26701
|
name: "submit_dispositions",
|
|
26641
|
-
description: "Record one disposition per review finding after addressing them: `fixed` (the finding is addressed in your pushed code) or `declined` (deliberately not doing it \u2014 the note says why). `findingId` is the finding's stable id from the review (F1, F2, \u2026) \u2014 use exactly those ids; an
|
|
26702
|
+
description: "Record one disposition per review finding after addressing them: `fixed` (the finding is addressed in your pushed code) or `declined` (deliberately not doing it \u2014 the note says why). `findingId` is the finding's stable id from the review (F1, F2, \u2026) \u2014 use exactly those ids; an id the review never issued answers nothing and is dropped when the plan runner reads your record. Every finding gets exactly one entry, every severity included (nits too). Call it once with the complete set after your last push; a later call replaces the earlier one. The set rides this run's record, where the plan runner reads it for the re-review.",
|
|
26642
26703
|
inputSchema: {
|
|
26643
26704
|
type: "object",
|
|
26644
26705
|
properties: {
|
|
@@ -26661,15 +26722,7 @@ ${raw.trim()}`;
|
|
|
26661
26722
|
async run(input, ctx) {
|
|
26662
26723
|
const parsed = parseDispositionsInput(input);
|
|
26663
26724
|
if (!parsed) return "error: dispositions must be an array of { findingId, disposition: fixed|declined, note }";
|
|
26664
|
-
if (ctx.
|
|
26665
|
-
const known = new Set(ctx.knownFindingIds);
|
|
26666
|
-
const unknown = [...new Set(parsed.dispositions.map((d) => d.findingId).filter((id) => !known.has(id)))];
|
|
26667
|
-
if (unknown.length) {
|
|
26668
|
-
return `error: unknown finding id${unknown.length === 1 ? "" : "s"} ${unknown.join(", ")} \u2014 use exactly the ids from the review's findings list`;
|
|
26669
|
-
}
|
|
26670
|
-
}
|
|
26671
|
-
if (!ctx.onDispositions)
|
|
26672
|
-
return "no ship fix round is active here \u2014 dispositions were not recorded (they apply only when addressing a ship review's findings)";
|
|
26725
|
+
if (!ctx.onDispositions) return "no run is recording dispositions here";
|
|
26673
26726
|
ctx.onDispositions(parsed.dispositions);
|
|
26674
26727
|
const drops = parsed.dropped.length ? ` (dropped: ${parsed.dropped.join("; ")})` : "";
|
|
26675
26728
|
return `dispositions recorded: ${parsed.dispositions.length}${drops}; a later call replaces this one`;
|
|
@@ -28953,7 +29006,7 @@ function startMemoryRead(deps, ctx) {
|
|
|
28953
29006
|
async function openAckCard(deps, ctx) {
|
|
28954
29007
|
const { io, agent, resolved, startedAt, clock: clock3, root, trace, route: route3 } = ctx;
|
|
28955
29008
|
const shell = createCardShell({
|
|
28956
|
-
label: `*${agent.name}* on \`${resolved.modelRef}\`${route3 ? ` \xB7 ${routedLabel(route3.reason)}` : ""}`,
|
|
29009
|
+
label: `*${agent.name}* on \`${resolved.modelRef}\`${route3 ? ` \xB7 ${routedLabel(route3.reason, route3.collapsed)}` : ""}`,
|
|
28957
29010
|
startedAt,
|
|
28958
29011
|
now: clock3,
|
|
28959
29012
|
...route3?.parts ? { lead: routedPartLines(route3.parts) } : {},
|
|
@@ -29872,7 +29925,7 @@ var init_bridge2 = __esm({
|
|
|
29872
29925
|
this.onToolEnd(event, out);
|
|
29873
29926
|
break;
|
|
29874
29927
|
case "compaction_end":
|
|
29875
|
-
this.onCompactionEnd(event);
|
|
29928
|
+
this.onCompactionEnd(event, out);
|
|
29876
29929
|
break;
|
|
29877
29930
|
case "summarization_retry_scheduled":
|
|
29878
29931
|
this.summarizationRetries++;
|
|
@@ -29986,7 +30039,7 @@ var init_bridge2 = __esm({
|
|
|
29986
30039
|
}
|
|
29987
30040
|
}
|
|
29988
30041
|
}
|
|
29989
|
-
onCompactionEnd(event) {
|
|
30042
|
+
onCompactionEnd(event, out) {
|
|
29990
30043
|
const result = isRecord3(event.result) ? event.result : void 0;
|
|
29991
30044
|
if (event.aborted === true || !result) {
|
|
29992
30045
|
this.note(
|
|
@@ -30003,6 +30056,13 @@ var init_bridge2 = __esm({
|
|
|
30003
30056
|
"compacted",
|
|
30004
30057
|
`pi compacted the context (${str5(event.reason)}): ${before ?? "?"} \u2192 about ${after ?? "?"} tokens; the transcript keeps the originals${retries > 0 ? `; ${retries} summarization retr${retries === 1 ? "y" : "ies"}` : ""}`
|
|
30005
30058
|
);
|
|
30059
|
+
if (typeof result.summary === "string") {
|
|
30060
|
+
out.compaction = {
|
|
30061
|
+
summary: result.summary,
|
|
30062
|
+
...before !== void 0 ? { tokensBefore: before } : {},
|
|
30063
|
+
...typeof result.firstKeptEntryId === "string" ? { firstKeptEntryId: result.firstKeptEntryId } : {}
|
|
30064
|
+
};
|
|
30065
|
+
}
|
|
30006
30066
|
}
|
|
30007
30067
|
onUiRequest(event, out) {
|
|
30008
30068
|
const method = str5(event.method);
|
|
@@ -30071,7 +30131,7 @@ function chatMessageOf(message2) {
|
|
|
30071
30131
|
return void 0;
|
|
30072
30132
|
}
|
|
30073
30133
|
}
|
|
30074
|
-
function piSessionFile(messages, opts) {
|
|
30134
|
+
function piSessionFile(messages, opts, compactions = []) {
|
|
30075
30135
|
const lines = [];
|
|
30076
30136
|
const stamp = new Date(opts.at).toISOString();
|
|
30077
30137
|
lines.push(
|
|
@@ -30080,12 +30140,35 @@ function piSessionFile(messages, opts) {
|
|
|
30080
30140
|
let parentId = null;
|
|
30081
30141
|
let n2 = 0;
|
|
30082
30142
|
const toolNames = /* @__PURE__ */ new Map();
|
|
30143
|
+
const firstEntryOf = [];
|
|
30144
|
+
const nextId = () => (n2++).toString(16).padStart(8, "0");
|
|
30083
30145
|
const push = (message2) => {
|
|
30084
|
-
const id = (
|
|
30146
|
+
const id = nextId();
|
|
30085
30147
|
lines.push(JSON.stringify({ type: "message", id, parentId, timestamp: stamp, message: message2 }));
|
|
30086
30148
|
parentId = id;
|
|
30149
|
+
return id;
|
|
30087
30150
|
};
|
|
30088
|
-
|
|
30151
|
+
const pushCompaction = (c) => {
|
|
30152
|
+
const id = nextId();
|
|
30153
|
+
const kept = c.keptBefore !== void 0 ? firstEntryOf[c.keptBefore] : void 0;
|
|
30154
|
+
lines.push(
|
|
30155
|
+
JSON.stringify({
|
|
30156
|
+
type: "compaction",
|
|
30157
|
+
id,
|
|
30158
|
+
parentId,
|
|
30159
|
+
timestamp: stamp,
|
|
30160
|
+
summary: c.entry.summary,
|
|
30161
|
+
firstKeptEntryId: kept ?? c.entry.firstKeptEntryId ?? "",
|
|
30162
|
+
tokensBefore: c.entry.tokensBefore ?? 0
|
|
30163
|
+
})
|
|
30164
|
+
);
|
|
30165
|
+
parentId = id;
|
|
30166
|
+
};
|
|
30167
|
+
const byPosition = /* @__PURE__ */ new Map();
|
|
30168
|
+
for (const c of compactions) byPosition.set(c.before, [...byPosition.get(c.before) ?? [], c]);
|
|
30169
|
+
messages.forEach((message2, i) => {
|
|
30170
|
+
for (const c of byPosition.get(i) ?? []) pushCompaction(c);
|
|
30171
|
+
firstEntryOf[i] = n2.toString(16).padStart(8, "0");
|
|
30089
30172
|
if (message2.role === "assistant") {
|
|
30090
30173
|
const content = [];
|
|
30091
30174
|
for (const p of message2.content) {
|
|
@@ -30113,7 +30196,7 @@ function piSessionFile(messages, opts) {
|
|
|
30113
30196
|
stopReason: hasTool ? "toolUse" : "stop",
|
|
30114
30197
|
timestamp: opts.at
|
|
30115
30198
|
});
|
|
30116
|
-
|
|
30199
|
+
return;
|
|
30117
30200
|
}
|
|
30118
30201
|
const results = message2.content.filter(
|
|
30119
30202
|
(p) => p.type === "tool_result"
|
|
@@ -30140,7 +30223,8 @@ function piSessionFile(messages, opts) {
|
|
|
30140
30223
|
});
|
|
30141
30224
|
}
|
|
30142
30225
|
if (rest.length > 0) push({ role: "user", content: rest, timestamp: opts.at });
|
|
30143
|
-
}
|
|
30226
|
+
});
|
|
30227
|
+
for (const c of byPosition.get(messages.length) ?? []) pushCompaction(c);
|
|
30144
30228
|
return lines.join("\n") + "\n";
|
|
30145
30229
|
}
|
|
30146
30230
|
function sessionIdOf(at) {
|
|
@@ -30197,6 +30281,28 @@ var init_mirror = __esm({
|
|
|
30197
30281
|
this.idx += turns.length;
|
|
30198
30282
|
await this.deps.onStep(report2);
|
|
30199
30283
|
}
|
|
30284
|
+
/** pi compacted (session-log item 6): the results pending since the last
|
|
30285
|
+
* assistant turn go as the user turn they would have been, the entry as the
|
|
30286
|
+
* row after them — its own step with nothing in flight, so the log holds
|
|
30287
|
+
* the summary where pi wrote it and the index moves past it. */
|
|
30288
|
+
async onCompaction(entry, turn) {
|
|
30289
|
+
if (!this.deps.onStep) return;
|
|
30290
|
+
const turns = [];
|
|
30291
|
+
if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
|
|
30292
|
+
this.pendingUser = [];
|
|
30293
|
+
const report2 = {
|
|
30294
|
+
turns,
|
|
30295
|
+
firstIdx: this.idx,
|
|
30296
|
+
compaction: entry,
|
|
30297
|
+
inFlight: [],
|
|
30298
|
+
turn,
|
|
30299
|
+
iteration: this.iteration,
|
|
30300
|
+
remainingMs: Math.max(0, this.deps.remainingMs()),
|
|
30301
|
+
inboxConsumedSeq: this.inboxConsumedSeq
|
|
30302
|
+
};
|
|
30303
|
+
this.idx += turns.length + 1;
|
|
30304
|
+
await this.deps.onStep(report2);
|
|
30305
|
+
}
|
|
30200
30306
|
};
|
|
30201
30307
|
}
|
|
30202
30308
|
});
|
|
@@ -30535,25 +30641,33 @@ async function runPiHarness(deps, run2) {
|
|
|
30535
30641
|
let session;
|
|
30536
30642
|
if (run2.resume) {
|
|
30537
30643
|
const settled2 = settlementResults(run2.resume.settlements);
|
|
30538
|
-
session = {
|
|
30644
|
+
session = {
|
|
30645
|
+
stem: "resumed",
|
|
30646
|
+
messages: settled2 ? [...run2.resume.messages, settled2] : run2.resume.messages,
|
|
30647
|
+
compactions: run2.resume.compactions ?? []
|
|
30648
|
+
};
|
|
30539
30649
|
} else {
|
|
30540
30650
|
const earlier2 = splitSeed(run2.messages).session;
|
|
30541
|
-
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2 };
|
|
30651
|
+
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2, compactions: [] };
|
|
30542
30652
|
}
|
|
30543
30653
|
let sessionPath;
|
|
30544
30654
|
if (session) {
|
|
30545
30655
|
sessionPath = `${paths.sessionDir}/${session.stem}-${now()}.jsonl`;
|
|
30546
30656
|
await container.writeFile(
|
|
30547
30657
|
sessionPath,
|
|
30548
|
-
piSessionFile(
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
|
|
30552
|
-
|
|
30553
|
-
|
|
30658
|
+
piSessionFile(
|
|
30659
|
+
session.messages,
|
|
30660
|
+
{
|
|
30661
|
+
cwd: run2.rules.checkout,
|
|
30662
|
+
model: {
|
|
30663
|
+
provider: run2.model.provider,
|
|
30664
|
+
id: run2.model.id,
|
|
30665
|
+
api: run2.model.providerType === "anthropic" ? "anthropic-messages" : "openai-completions"
|
|
30666
|
+
},
|
|
30667
|
+
at: now()
|
|
30554
30668
|
},
|
|
30555
|
-
|
|
30556
|
-
|
|
30669
|
+
session.compactions
|
|
30670
|
+
)
|
|
30557
30671
|
);
|
|
30558
30672
|
}
|
|
30559
30673
|
if (run2.resume) {
|
|
@@ -30682,6 +30796,7 @@ async function runPiHarness(deps, run2) {
|
|
|
30682
30796
|
}
|
|
30683
30797
|
}
|
|
30684
30798
|
if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
|
|
30799
|
+
if (obs.compaction) await mirror.onCompaction(obs.compaction, bridge.turns);
|
|
30685
30800
|
if (obs.providerError !== void 0) {
|
|
30686
30801
|
if (catchingUp)
|
|
30687
30802
|
note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
|
|
@@ -31269,8 +31384,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31269
31384
|
wait,
|
|
31270
31385
|
parentRunId,
|
|
31271
31386
|
coordinator,
|
|
31272
|
-
seed
|
|
31273
|
-
fixRound
|
|
31387
|
+
seed
|
|
31274
31388
|
} = ctx;
|
|
31275
31389
|
const agent = budgetedAgent(ctx.agent, profile);
|
|
31276
31390
|
const { executor, binding } = round2.selection;
|
|
@@ -31373,7 +31487,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31373
31487
|
handoff = h;
|
|
31374
31488
|
ledgerRun?.setState({ handoff: h });
|
|
31375
31489
|
};
|
|
31376
|
-
const restoredDispositions =
|
|
31490
|
+
const restoredDispositions = parseDispositionsInput({ dispositions: restored.dispositions })?.dispositions;
|
|
31377
31491
|
let dispositions = restoredDispositions;
|
|
31378
31492
|
const onDispositions = (d) => {
|
|
31379
31493
|
dispositions = d;
|
|
@@ -31420,7 +31534,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31420
31534
|
onDigest,
|
|
31421
31535
|
onPrDescription,
|
|
31422
31536
|
onHandoff,
|
|
31423
|
-
|
|
31537
|
+
onDispositions
|
|
31424
31538
|
};
|
|
31425
31539
|
const harness = effectiveHarness(agent, deps.config.config.harness);
|
|
31426
31540
|
try {
|
|
@@ -31481,6 +31595,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31481
31595
|
...resume ? {
|
|
31482
31596
|
resume: {
|
|
31483
31597
|
messages: resume.plan.messages,
|
|
31598
|
+
compactions: resume.plan.compactions,
|
|
31484
31599
|
settlements: resume.plan.settlements,
|
|
31485
31600
|
remainingMs: resume.plan.remainingMs,
|
|
31486
31601
|
turn: resume.plan.turn,
|
|
@@ -32060,6 +32175,12 @@ function openPlanCursor(graph, selected) {
|
|
|
32060
32175
|
for (const id of order) visit(id, []);
|
|
32061
32176
|
return cursor;
|
|
32062
32177
|
}
|
|
32178
|
+
function matchDispositions(findings, dispositions) {
|
|
32179
|
+
const issued = new Set(findings.map((f) => f.id));
|
|
32180
|
+
const matched = dispositions.filter((d) => issued.has(d.findingId));
|
|
32181
|
+
const dropped = [...new Set(dispositions.filter((d) => !issued.has(d.findingId)).map((d) => d.findingId))];
|
|
32182
|
+
return { matched, dropped };
|
|
32183
|
+
}
|
|
32063
32184
|
var MIN, SHIP_ROUND_RESERVE_MS, PLAN_ID_PATTERN, PLAN_BRANCH, UNIT_REF, SLUG_MAX, INSTANCE_ID_MAX, WAIT_MARGIN_MS, WAIT_CHUNK_MS, MERGE_POLL_MS, MERGE_WAIT_MAX_MS, BUSY_RETRY_MS;
|
|
32064
32185
|
var init_coordinator = __esm({
|
|
32065
32186
|
"../../src/core/ship/coordinator.ts"() {
|
|
@@ -32082,29 +32203,6 @@ var init_coordinator = __esm({
|
|
|
32082
32203
|
}
|
|
32083
32204
|
});
|
|
32084
32205
|
|
|
32085
|
-
// ../../src/core/ship/codingChild.ts
|
|
32086
|
-
function buildShipFixTurn(input) {
|
|
32087
|
-
const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings \u2014 address its prose)";
|
|
32088
|
-
return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address EVERY finding below, nits included: record one disposition per finding with submit_dispositions (fixed|declined, with a note), squash to coherent commits, resubmit the PR description with submit_pr_description, and push the branch. Never merge and never approve.
|
|
32089
|
-
|
|
32090
|
-
Findings:
|
|
32091
|
-
${findings}
|
|
32092
|
-
|
|
32093
|
-
Review:
|
|
32094
|
-
${input.review}`;
|
|
32095
|
-
}
|
|
32096
|
-
function withContractInFirstUserTurn(messages, block) {
|
|
32097
|
-
const at = messages.findIndex((m) => m.role === "user");
|
|
32098
|
-
if (at < 0) return [...messages, { role: "user", content: [{ type: "text", text: block }] }];
|
|
32099
|
-
return messages.map((m, i) => i === at ? { ...m, content: [...m.content, { type: "text", text: block }] } : m);
|
|
32100
|
-
}
|
|
32101
|
-
var init_codingChild = __esm({
|
|
32102
|
-
"../../src/core/ship/codingChild.ts"() {
|
|
32103
|
-
"use strict";
|
|
32104
|
-
init_reviewVerdict();
|
|
32105
|
-
}
|
|
32106
|
-
});
|
|
32107
|
-
|
|
32108
32206
|
// ../../src/core/ship/reviewChild.ts
|
|
32109
32207
|
function buildShipReviewTurn(input) {
|
|
32110
32208
|
const at = input.headSha ? ` at head \`${input.headSha}\`` : "";
|
|
@@ -32113,13 +32211,15 @@ function buildShipReviewTurn(input) {
|
|
|
32113
32211
|
}
|
|
32114
32212
|
const findings = input.prior.findings.map(formatFinding).join("\n") || "(none recorded)";
|
|
32115
32213
|
const dispositions = input.prior.dispositions.map((d) => `${d.findingId}: ${d.disposition}${d.note ? ` \u2014 ${d.note}` : ""}`).join("\n") || "(none recorded)";
|
|
32214
|
+
const dropped = input.prior.dropped !== void 0 && input.prior.dropped.length > 0 ? `
|
|
32215
|
+
Dispositions naming no finding of the previous round (dropped): ${input.prior.dropped.join(", ")}` : "";
|
|
32116
32216
|
return `Re-review pull request ${input.where}${at} \u2014 review round ${input.round} of this ship pipeline. Load the \`re-review-delta\` skill: narrow your READING to the delta since the previously reviewed head and verify each prior finding's disposition, but your verdict still covers the full diff against base. Carry every unresolved prior finding forward under its existing id.
|
|
32117
32217
|
|
|
32118
32218
|
Previous round's findings:
|
|
32119
32219
|
${findings}
|
|
32120
32220
|
|
|
32121
32221
|
Fix round's dispositions:
|
|
32122
|
-
${dispositions}`;
|
|
32222
|
+
${dispositions}${dropped}`;
|
|
32123
32223
|
}
|
|
32124
32224
|
var init_reviewChild = __esm({
|
|
32125
32225
|
"../../src/core/ship/reviewChild.ts"() {
|
|
@@ -32165,6 +32265,16 @@ async function contractFor(instance, unit, readers) {
|
|
|
32165
32265
|
...issue ? { issue } : {}
|
|
32166
32266
|
});
|
|
32167
32267
|
}
|
|
32268
|
+
function findingsRequest(input) {
|
|
32269
|
+
const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings, address its prose)";
|
|
32270
|
+
return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note), squash to coherent commits, resubmit the pull request description with submit_pr_description, and push the branch. Never merge and never approve.
|
|
32271
|
+
|
|
32272
|
+
Findings:
|
|
32273
|
+
${findings}
|
|
32274
|
+
|
|
32275
|
+
Review:
|
|
32276
|
+
${input.review}`;
|
|
32277
|
+
}
|
|
32168
32278
|
async function composeChild(brief, instance, unit, readers) {
|
|
32169
32279
|
const where = `${instance.repo}#${"pr" in brief ? brief.pr : ""}`;
|
|
32170
32280
|
switch (brief.kind) {
|
|
@@ -32174,10 +32284,13 @@ async function composeChild(brief, instance, unit, readers) {
|
|
|
32174
32284
|
return { preset: "coding", prompt, ref: unit.branch, contract };
|
|
32175
32285
|
}
|
|
32176
32286
|
case "review": {
|
|
32177
|
-
|
|
32178
|
-
|
|
32179
|
-
|
|
32180
|
-
|
|
32287
|
+
let prior;
|
|
32288
|
+
if (brief.prior !== void 0) {
|
|
32289
|
+
const findings = (await facts(readers, brief.prior.reviewRunId)).findings ?? [];
|
|
32290
|
+
const recorded = brief.prior.codingRunId !== void 0 ? (await facts(readers, brief.prior.codingRunId)).dispositions ?? [] : [];
|
|
32291
|
+
const { matched, dropped } = matchDispositions(findings, recorded);
|
|
32292
|
+
prior = { findings, dispositions: matched, dropped };
|
|
32293
|
+
}
|
|
32181
32294
|
const turn = buildShipReviewTurn({
|
|
32182
32295
|
where,
|
|
32183
32296
|
round: brief.round,
|
|
@@ -32189,16 +32302,18 @@ async function composeChild(brief, instance, unit, readers) {
|
|
|
32189
32302
|
|
|
32190
32303
|
${turn}`, contract };
|
|
32191
32304
|
}
|
|
32192
|
-
case "
|
|
32305
|
+
case "findings": {
|
|
32193
32306
|
const review = await facts(readers, brief.reviewRunId);
|
|
32194
|
-
const findings = review.findings ?? [];
|
|
32195
32307
|
return {
|
|
32196
32308
|
preset: "coding",
|
|
32197
|
-
prompt:
|
|
32198
|
-
ref: unit.branch
|
|
32199
|
-
fixRound: { findingIds: findings.map((f) => f.id) }
|
|
32309
|
+
prompt: findingsRequest({ where, findings: review.findings ?? [], review: review.finalReply ?? "" }),
|
|
32310
|
+
ref: unit.branch
|
|
32200
32311
|
};
|
|
32201
32312
|
}
|
|
32313
|
+
default: {
|
|
32314
|
+
const unknown = brief;
|
|
32315
|
+
throw new Error(`unknown brief kind ${JSON.stringify(unknown.kind)}`);
|
|
32316
|
+
}
|
|
32202
32317
|
}
|
|
32203
32318
|
}
|
|
32204
32319
|
async function facts(readers, runId3) {
|
|
@@ -32211,7 +32326,8 @@ var init_briefs = __esm({
|
|
|
32211
32326
|
"../../src/core/coordinator/briefs.ts"() {
|
|
32212
32327
|
"use strict";
|
|
32213
32328
|
init_directives();
|
|
32214
|
-
|
|
32329
|
+
init_reviewVerdict();
|
|
32330
|
+
init_coordinator();
|
|
32215
32331
|
init_contract();
|
|
32216
32332
|
init_preflight();
|
|
32217
32333
|
init_reviewChild();
|
|
@@ -33060,6 +33176,18 @@ var init_ship = __esm({
|
|
|
33060
33176
|
}
|
|
33061
33177
|
});
|
|
33062
33178
|
|
|
33179
|
+
// ../../src/core/ship/codingChild.ts
|
|
33180
|
+
function withContractInFirstUserTurn(messages, block) {
|
|
33181
|
+
const at = messages.findIndex((m) => m.role === "user");
|
|
33182
|
+
if (at < 0) return [...messages, { role: "user", content: [{ type: "text", text: block }] }];
|
|
33183
|
+
return messages.map((m, i) => i === at ? { ...m, content: [...m.content, { type: "text", text: block }] } : m);
|
|
33184
|
+
}
|
|
33185
|
+
var init_codingChild = __esm({
|
|
33186
|
+
"../../src/core/ship/codingChild.ts"() {
|
|
33187
|
+
"use strict";
|
|
33188
|
+
}
|
|
33189
|
+
});
|
|
33190
|
+
|
|
33063
33191
|
// ../../src/core/dispatch/settle.ts
|
|
33064
33192
|
function settleThread(deps, ctx) {
|
|
33065
33193
|
const { msg, admitted, runLoopStarted, control } = ctx;
|
|
@@ -33647,7 +33775,6 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33647
33775
|
parentRunId,
|
|
33648
33776
|
coordinator,
|
|
33649
33777
|
seed,
|
|
33650
|
-
...opts.fixRound ? { fixRound: opts.fixRound } : {},
|
|
33651
33778
|
...bearer !== void 0 ? { bearer } : {}
|
|
33652
33779
|
});
|
|
33653
33780
|
const { answer, prNote, toolCalls, runDiagnosis, checklistAsLeft, checklistCheckedOff, releaseWorkspace } = ran;
|
|
@@ -33923,7 +34050,12 @@ function toListItem(record2, bytes) {
|
|
|
33923
34050
|
return { ...rest, bytes };
|
|
33924
34051
|
}
|
|
33925
34052
|
function retentionPolicyOf(cfg) {
|
|
33926
|
-
return clampRetentionPolicy({
|
|
34053
|
+
return clampRetentionPolicy({
|
|
34054
|
+
retentionDays: cfg.retentionDays,
|
|
34055
|
+
maxRuns: cfg.maxRuns,
|
|
34056
|
+
maxBytes: cfg.maxBytes,
|
|
34057
|
+
sessionLogMaxBytes: cfg.sessionLogMaxBytes
|
|
34058
|
+
});
|
|
33927
34059
|
}
|
|
33928
34060
|
function buildRunStore(cfg, secrets, deps) {
|
|
33929
34061
|
if (!cfg) return null;
|
|
@@ -34167,6 +34299,24 @@ var init_flusher = __esm({
|
|
|
34167
34299
|
}
|
|
34168
34300
|
});
|
|
34169
34301
|
|
|
34302
|
+
// ../../src/core/runLedger/sessionLog.ts
|
|
34303
|
+
function sessionKey(threadKey, agent) {
|
|
34304
|
+
return `${threadKey}:${agent ?? "-"}`;
|
|
34305
|
+
}
|
|
34306
|
+
function requestIndex(seed) {
|
|
34307
|
+
for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
|
|
34308
|
+
return 0;
|
|
34309
|
+
}
|
|
34310
|
+
var DEFAULT_SESSION_LOG_MAX_BYTES;
|
|
34311
|
+
var init_sessionLog = __esm({
|
|
34312
|
+
"../../src/core/runLedger/sessionLog.ts"() {
|
|
34313
|
+
"use strict";
|
|
34314
|
+
init_runRecord();
|
|
34315
|
+
init_runRecord();
|
|
34316
|
+
DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
|
|
34317
|
+
}
|
|
34318
|
+
});
|
|
34319
|
+
|
|
34170
34320
|
// ../../src/core/runLedger/types.ts
|
|
34171
34321
|
var LEASE_MS, HEARTBEAT_MS, APPEND_FLUSH_MS, APPEND_FLUSH_EVENTS, TRANSCRIPT_REQUEST_BYTES, TRANSCRIPT_PART_BYTES, ATTACHMENT_REF_BYTES, GEN_PATTERN;
|
|
34172
34322
|
var init_types5 = __esm({
|
|
@@ -34212,36 +34362,45 @@ function createLedgerWriteThrough(opts) {
|
|
|
34212
34362
|
"[ledger] state Worker has no run-ledger routes \u2014 deploy it before this bot version; runs are not tracked until then"
|
|
34213
34363
|
);
|
|
34214
34364
|
};
|
|
34215
|
-
async function claim(req,
|
|
34365
|
+
async function claim(req, opts2 = {}) {
|
|
34216
34366
|
for (let attempt = 1; ; attempt++) {
|
|
34217
34367
|
try {
|
|
34368
|
+
let session;
|
|
34369
|
+
if (opts2.seed) {
|
|
34370
|
+
const key = sessionKey(req.threadKey, req.meta.agent);
|
|
34371
|
+
const next = await ledger.sessionTail(key);
|
|
34372
|
+
session = { key, seedFrom: next, request: next + requestIndex(opts2.seed), range: { from: next } };
|
|
34373
|
+
}
|
|
34218
34374
|
const result = await ledger.claim({
|
|
34219
34375
|
runId: req.runId,
|
|
34220
34376
|
threadKey: req.threadKey,
|
|
34221
34377
|
gen,
|
|
34222
34378
|
leaseMs,
|
|
34223
34379
|
startedAt: req.startedAt,
|
|
34224
|
-
meta: req.meta,
|
|
34380
|
+
meta: session ? { ...req.meta, session } : req.meta,
|
|
34225
34381
|
card: req.card ?? null,
|
|
34226
34382
|
system: req.system,
|
|
34227
34383
|
tools: req.tools,
|
|
34228
34384
|
...req.state !== void 0 ? { state: req.state } : {},
|
|
34229
|
-
...phase ? { phase } : {}
|
|
34385
|
+
...opts2.phase ? { phase: opts2.phase } : {}
|
|
34230
34386
|
});
|
|
34231
|
-
if (result.ok)
|
|
34232
|
-
|
|
34387
|
+
if (result.ok) {
|
|
34388
|
+
if (session) await ledger.claimSession(session.key, req.runId, gen);
|
|
34389
|
+
return session ? { outcome: "ok", session } : { outcome: "ok" };
|
|
34390
|
+
}
|
|
34391
|
+
if (result.live.runId === req.runId) return { outcome: "fenced" };
|
|
34233
34392
|
warn(
|
|
34234
34393
|
`[ledger] ${req.threadKey} not tracked: the thread's live row belongs to run ${result.live.runId} (started ${new Date(result.live.startedAt).toISOString()}) \u2014 reclaim is the resume phase's`
|
|
34235
34394
|
);
|
|
34236
|
-
return "untracked";
|
|
34395
|
+
return { outcome: "untracked" };
|
|
34237
34396
|
} catch (err2) {
|
|
34238
34397
|
if (err2 instanceof RouteMissingError) {
|
|
34239
34398
|
routeMissing();
|
|
34240
|
-
return "untracked";
|
|
34399
|
+
return { outcome: "untracked" };
|
|
34241
34400
|
}
|
|
34242
34401
|
if (err2 instanceof PermanentStoreError || attempt >= claimAttempts) {
|
|
34243
34402
|
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${describe8(err2)}`);
|
|
34244
|
-
return "untracked";
|
|
34403
|
+
return { outcome: "untracked" };
|
|
34245
34404
|
}
|
|
34246
34405
|
await sleep2(RETRY_MS[Math.min(attempt, RETRY_MS.length) - 1]);
|
|
34247
34406
|
}
|
|
@@ -34261,6 +34420,15 @@ function createLedgerWriteThrough(opts) {
|
|
|
34261
34420
|
seeded = false;
|
|
34262
34421
|
adopted = false;
|
|
34263
34422
|
handedOff = false;
|
|
34423
|
+
/** The run's place in its session log (session-log item 2); undefined for
|
|
34424
|
+
* a run without a conversation of its own and for an adopted row claimed
|
|
34425
|
+
* before the log existed. */
|
|
34426
|
+
session;
|
|
34427
|
+
/** Turns of the run's conversation on the ledger, counted from its seed —
|
|
34428
|
+
* the last step record's `turnIndex` — so the finish can close the range. */
|
|
34429
|
+
turnsWritten;
|
|
34430
|
+
/** A detach left the log short of what the model saw: the record says `broken`. */
|
|
34431
|
+
broken = false;
|
|
34264
34432
|
stepNo;
|
|
34265
34433
|
lastSeq;
|
|
34266
34434
|
state;
|
|
@@ -34281,7 +34449,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
34281
34449
|
},
|
|
34282
34450
|
...opts.schedule ? { schedule: opts.schedule } : {}
|
|
34283
34451
|
});
|
|
34284
|
-
constructor(req, from = {
|
|
34452
|
+
constructor(req, from = {
|
|
34453
|
+
stepNo: 0,
|
|
34454
|
+
lastSeq: 0
|
|
34455
|
+
}) {
|
|
34285
34456
|
this.runId = req.runId;
|
|
34286
34457
|
this.threadKey = req.threadKey;
|
|
34287
34458
|
this.state = req.state ?? {};
|
|
@@ -34290,6 +34461,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
34290
34461
|
this.stepNo = from.stepNo;
|
|
34291
34462
|
this.lastSeq = from.lastSeq;
|
|
34292
34463
|
this.adopted = from.resumable === true;
|
|
34464
|
+
this.session = from.session;
|
|
34465
|
+
}
|
|
34466
|
+
/** A reservation learns its session when the prompt's claim promotes it. */
|
|
34467
|
+
bindSession(session) {
|
|
34468
|
+
this.session = session;
|
|
34293
34469
|
}
|
|
34294
34470
|
get resumable() {
|
|
34295
34471
|
return !this.detached && (this.seeded || this.adopted);
|
|
@@ -34297,11 +34473,24 @@ function createLedgerWriteThrough(opts) {
|
|
|
34297
34473
|
markHandedOff() {
|
|
34298
34474
|
this.handedOff = true;
|
|
34299
34475
|
}
|
|
34476
|
+
/** The run's range as the finished record carries it (session-log item 2):
|
|
34477
|
+
* closed at the last turn written, or `broken` after a detach — the log
|
|
34478
|
+
* ends short of what the model saw, and the next run in the session must
|
|
34479
|
+
* know. A run that wrote no turn leaves the range open. */
|
|
34480
|
+
sessionOnRecord() {
|
|
34481
|
+
if (!this.session) return void 0;
|
|
34482
|
+
if (this.broken) return { ...this.session, range: "broken" };
|
|
34483
|
+
const from = this.session.range === "broken" ? this.session.seedFrom : this.session.range.from;
|
|
34484
|
+
const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.session.seedFrom + this.turnsWritten - 1 : void 0;
|
|
34485
|
+
return { ...this.session, range: to !== void 0 && to >= from ? { from, to } : { from } };
|
|
34486
|
+
}
|
|
34300
34487
|
sink = {
|
|
34301
|
-
put: async (
|
|
34488
|
+
put: async (plain4) => {
|
|
34302
34489
|
this.finished = true;
|
|
34303
34490
|
live.delete(this);
|
|
34304
34491
|
await this.close();
|
|
34492
|
+
const session = this.sessionOnRecord();
|
|
34493
|
+
const record2 = session ? { ...plain4, session } : plain4;
|
|
34305
34494
|
let result;
|
|
34306
34495
|
try {
|
|
34307
34496
|
result = await ledger.finish(this.runId, gen, record2);
|
|
@@ -34322,6 +34511,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
34322
34511
|
detach(reason) {
|
|
34323
34512
|
if (this.detached) return;
|
|
34324
34513
|
this.detached = true;
|
|
34514
|
+
this.broken = true;
|
|
34325
34515
|
warn(`[ledger] ${this.threadKey} run ${this.runId} detached: ${reason} \u2014 this run is not resumable`);
|
|
34326
34516
|
this.stopHeartbeat();
|
|
34327
34517
|
live.delete(this);
|
|
@@ -34339,13 +34529,15 @@ function createLedgerWriteThrough(opts) {
|
|
|
34339
34529
|
* no record at all was killed before its conversation was stored and
|
|
34340
34530
|
* closes `interrupted`. */
|
|
34341
34531
|
async seed(messages, budgetMs) {
|
|
34342
|
-
const
|
|
34532
|
+
const base = this.session?.seedFrom ?? 0;
|
|
34533
|
+
const turns = messages.map((message2, i) => ({ idx: base + i, message: message2 }));
|
|
34343
34534
|
try {
|
|
34344
|
-
const seeded = await ledger.seed(this.runId, gen, turns);
|
|
34535
|
+
const seeded = await ledger.seed(this.runId, gen, turns, this.session?.key);
|
|
34345
34536
|
if (!seeded.ok) {
|
|
34346
34537
|
this.detach(`seed refused (${seeded.reason})`);
|
|
34347
34538
|
return;
|
|
34348
34539
|
}
|
|
34540
|
+
this.turnsWritten = messages.length;
|
|
34349
34541
|
const recorded = await ledger.step(
|
|
34350
34542
|
this.runId,
|
|
34351
34543
|
gen,
|
|
@@ -34369,11 +34561,14 @@ function createLedgerWriteThrough(opts) {
|
|
|
34369
34561
|
}
|
|
34370
34562
|
async step(report2) {
|
|
34371
34563
|
if (this.detached) return;
|
|
34372
|
-
const
|
|
34564
|
+
const base = this.session?.seedFrom ?? 0;
|
|
34565
|
+
const turns = report2.turns.map((message2, i) => ({ idx: base + report2.firstIdx + i, message: message2 }));
|
|
34566
|
+
if (report2.compaction)
|
|
34567
|
+
turns.push({ idx: base + report2.firstIdx + report2.turns.length, compaction: report2.compaction });
|
|
34373
34568
|
const record2 = {
|
|
34374
34569
|
step: ++this.stepNo,
|
|
34375
34570
|
seq: this.lastSeq,
|
|
34376
|
-
turnIndex: report2.firstIdx +
|
|
34571
|
+
turnIndex: report2.firstIdx + turns.length,
|
|
34377
34572
|
inFlight: report2.inFlight,
|
|
34378
34573
|
inboxConsumedSeq: report2.inboxConsumedSeq,
|
|
34379
34574
|
remainingMs: report2.remainingMs,
|
|
@@ -34382,8 +34577,9 @@ function createLedgerWriteThrough(opts) {
|
|
|
34382
34577
|
};
|
|
34383
34578
|
for (let attempt = 1; ; attempt++) {
|
|
34384
34579
|
try {
|
|
34385
|
-
const result = await ledger.step(this.runId, gen, record2, turns);
|
|
34580
|
+
const result = await ledger.step(this.runId, gen, record2, turns, this.session?.key);
|
|
34386
34581
|
if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
|
|
34582
|
+
else this.turnsWritten = record2.turnIndex;
|
|
34387
34583
|
return;
|
|
34388
34584
|
} catch (err2) {
|
|
34389
34585
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
@@ -34491,8 +34687,8 @@ function createLedgerWriteThrough(opts) {
|
|
|
34491
34687
|
return {
|
|
34492
34688
|
gen,
|
|
34493
34689
|
async reserve(req) {
|
|
34494
|
-
const claimed2 = await claim({ ...req, system: "", tools: [], state: {} }, "attaching");
|
|
34495
|
-
if (claimed2 !== "ok") return void 0;
|
|
34690
|
+
const claimed2 = await claim({ ...req, system: "", tools: [], state: {} }, { phase: "attaching" });
|
|
34691
|
+
if (claimed2.outcome !== "ok") return void 0;
|
|
34496
34692
|
const run2 = new TrackedRun(req);
|
|
34497
34693
|
run2.startHeartbeat();
|
|
34498
34694
|
live.add(run2);
|
|
@@ -34500,35 +34696,46 @@ function createLedgerWriteThrough(opts) {
|
|
|
34500
34696
|
},
|
|
34501
34697
|
async open(req) {
|
|
34502
34698
|
const reserved = req.reservation;
|
|
34699
|
+
const seed = req.seed?.messages;
|
|
34503
34700
|
if (reserved instanceof TrackedRun) {
|
|
34504
34701
|
if (!reserved.tracked()) return void 0;
|
|
34505
|
-
const claimed3 = await claim(req);
|
|
34506
|
-
if (claimed3 === "fenced") {
|
|
34702
|
+
const claimed3 = await claim(req, seed ? { seed } : {});
|
|
34703
|
+
if (claimed3.outcome === "fenced") {
|
|
34507
34704
|
reserved.detach("promotion refused (fenced)");
|
|
34508
34705
|
return void 0;
|
|
34509
34706
|
}
|
|
34510
|
-
if (claimed3 !== "ok") {
|
|
34707
|
+
if (claimed3.outcome !== "ok") {
|
|
34511
34708
|
await reserved.close();
|
|
34512
34709
|
live.delete(reserved);
|
|
34513
34710
|
return void 0;
|
|
34514
34711
|
}
|
|
34712
|
+
if (claimed3.session) reserved.bindSession(claimed3.session);
|
|
34515
34713
|
if (req.seed) await reserved.seed(req.seed.messages, req.seed.budgetMs);
|
|
34516
34714
|
return reserved;
|
|
34517
34715
|
}
|
|
34518
|
-
const claimed2 = await claim(req);
|
|
34519
|
-
if (claimed2 === "fenced") {
|
|
34716
|
+
const claimed2 = await claim(req, seed ? { seed } : {});
|
|
34717
|
+
if (claimed2.outcome === "fenced") {
|
|
34520
34718
|
warn(`[ledger] ${req.threadKey} not tracked: run ${req.runId} is live under another generation`);
|
|
34521
34719
|
return void 0;
|
|
34522
34720
|
}
|
|
34523
|
-
if (claimed2 !== "ok") return void 0;
|
|
34524
|
-
const run2 = new TrackedRun(req
|
|
34721
|
+
if (claimed2.outcome !== "ok") return void 0;
|
|
34722
|
+
const run2 = new TrackedRun(req, {
|
|
34723
|
+
stepNo: 0,
|
|
34724
|
+
lastSeq: 0,
|
|
34725
|
+
...claimed2.session ? { session: claimed2.session } : {}
|
|
34726
|
+
});
|
|
34525
34727
|
if (req.seed) await run2.seed(req.seed.messages, req.seed.budgetMs);
|
|
34526
34728
|
run2.startHeartbeat();
|
|
34527
34729
|
live.add(run2);
|
|
34528
34730
|
return run2;
|
|
34529
34731
|
},
|
|
34530
34732
|
adopt(req) {
|
|
34531
|
-
const run2 = new TrackedRun(req, {
|
|
34733
|
+
const run2 = new TrackedRun(req, {
|
|
34734
|
+
stepNo: req.lastStep,
|
|
34735
|
+
lastSeq: req.lastSeq,
|
|
34736
|
+
resumable: true,
|
|
34737
|
+
...req.session ? { session: req.session } : {}
|
|
34738
|
+
});
|
|
34532
34739
|
run2.startHeartbeat();
|
|
34533
34740
|
live.add(run2);
|
|
34534
34741
|
return run2;
|
|
@@ -34578,6 +34785,7 @@ var init_writeThrough = __esm({
|
|
|
34578
34785
|
"use strict";
|
|
34579
34786
|
init_runStoreWorker();
|
|
34580
34787
|
init_flusher();
|
|
34788
|
+
init_sessionLog();
|
|
34581
34789
|
init_types5();
|
|
34582
34790
|
NullLedgerWriteThrough = class {
|
|
34583
34791
|
constructor(gen, fallback) {
|
|
@@ -38847,6 +39055,16 @@ function turnRows(idx, message2, opts = {}) {
|
|
|
38847
39055
|
const refBytes = opts.attachmentRefBytes ?? ATTACHMENT_REF_BYTES;
|
|
38848
39056
|
const rows = [];
|
|
38849
39057
|
const attachments = [];
|
|
39058
|
+
if (isCompaction(message2)) {
|
|
39059
|
+
const json2 = JSON.stringify({ compaction: message2.compaction });
|
|
39060
|
+
const bytes = utf8ByteLength(json2);
|
|
39061
|
+
if (bytes > partBytes) {
|
|
39062
|
+
throw new Error(
|
|
39063
|
+
`transcript: the compaction row at turn ${idx} is ${bytes} bytes, over the ${partBytes} row budget`
|
|
39064
|
+
);
|
|
39065
|
+
}
|
|
39066
|
+
return { rows: [{ idx, part: 0, json: json2 }], attachments };
|
|
39067
|
+
}
|
|
38850
39068
|
message2.content.forEach((part, i) => {
|
|
38851
39069
|
let stored = part;
|
|
38852
39070
|
if (hasData(part) && part.data.length > refBytes) {
|
|
@@ -38880,49 +39098,69 @@ function chunkRows(rows, maxBytes) {
|
|
|
38880
39098
|
if (current.length > 0) chunks.push(current);
|
|
38881
39099
|
return chunks;
|
|
38882
39100
|
}
|
|
38883
|
-
function assembleTranscript(rows, attachments) {
|
|
39101
|
+
function assembleTranscript(rows, attachments, base = 0) {
|
|
38884
39102
|
const byRef = new Map(attachments.map((a) => [a.ref, a]));
|
|
38885
39103
|
const byTurn = /* @__PURE__ */ new Map();
|
|
38886
39104
|
for (const row of rows) {
|
|
38887
|
-
|
|
38888
|
-
|
|
39105
|
+
const idx = row.idx - base;
|
|
39106
|
+
let parts = byTurn.get(idx);
|
|
39107
|
+
if (!parts) byTurn.set(idx, parts = /* @__PURE__ */ new Map());
|
|
38889
39108
|
parts.set(row.part, JSON.parse(row.json));
|
|
38890
39109
|
}
|
|
38891
39110
|
const messages = [];
|
|
39111
|
+
const compactions = [];
|
|
39112
|
+
const messageIndexOf = /* @__PURE__ */ new Map();
|
|
39113
|
+
const gap = (why) => ({
|
|
39114
|
+
complete: false,
|
|
39115
|
+
turns: messages.length + compactions.length,
|
|
39116
|
+
messages,
|
|
39117
|
+
compactions,
|
|
39118
|
+
gap: why
|
|
39119
|
+
});
|
|
38892
39120
|
const turnCount = byTurn.size === 0 ? 0 : Math.max(...byTurn.keys()) + 1;
|
|
38893
39121
|
for (let idx = 0; idx < turnCount; idx++) {
|
|
38894
39122
|
const parts = byTurn.get(idx);
|
|
38895
|
-
if (!parts) return
|
|
39123
|
+
if (!parts) return gap(`turn ${idx} is missing`);
|
|
39124
|
+
const first = parts.get(0);
|
|
39125
|
+
if (first && "compaction" in first) {
|
|
39126
|
+
compactions.push({ before: messages.length, entry: first.compaction });
|
|
39127
|
+
continue;
|
|
39128
|
+
}
|
|
38896
39129
|
const content = [];
|
|
38897
39130
|
let role;
|
|
38898
39131
|
const partCount = Math.max(...parts.keys()) + 1;
|
|
38899
39132
|
for (let p = 0; p < partCount; p++) {
|
|
38900
39133
|
const stored = parts.get(p);
|
|
38901
|
-
if (!stored)
|
|
38902
|
-
return { complete: false, turns: messages.length, messages, gap: `turn ${idx} is missing part ${p}` };
|
|
39134
|
+
if (!stored || "compaction" in stored) return gap(`turn ${idx} is missing part ${p}`);
|
|
38903
39135
|
role = stored.role;
|
|
38904
39136
|
const part = stored.part;
|
|
38905
39137
|
if (part.dataRef !== void 0) {
|
|
38906
39138
|
const att = byRef.get(part.dataRef);
|
|
38907
|
-
if (!att)
|
|
38908
|
-
return { complete: false, turns: messages.length, messages, gap: `attachment ${part.dataRef} is missing` };
|
|
39139
|
+
if (!att) return gap(`attachment ${part.dataRef} is missing`);
|
|
38909
39140
|
const { dataRef: _ref, ...rest } = part;
|
|
38910
39141
|
content.push({ ...rest, data: att.data });
|
|
38911
39142
|
} else {
|
|
38912
39143
|
content.push(part);
|
|
38913
39144
|
}
|
|
38914
39145
|
}
|
|
39146
|
+
messageIndexOf.set(idx, messages.length);
|
|
38915
39147
|
messages.push({ role: role ?? "user", content });
|
|
38916
39148
|
}
|
|
38917
|
-
|
|
39149
|
+
for (const c of compactions) {
|
|
39150
|
+
if (c.entry.keptFrom === void 0) continue;
|
|
39151
|
+
const kept = messageIndexOf.get(c.entry.keptFrom - base);
|
|
39152
|
+
if (kept !== void 0) c.keptBefore = kept;
|
|
39153
|
+
}
|
|
39154
|
+
return { complete: true, turns: messages.length + compactions.length, messages, compactions };
|
|
38918
39155
|
}
|
|
38919
|
-
var attachmentRef;
|
|
39156
|
+
var attachmentRef, isCompaction;
|
|
38920
39157
|
var init_transcript = __esm({
|
|
38921
39158
|
"../../src/core/runLedger/transcript.ts"() {
|
|
38922
39159
|
"use strict";
|
|
38923
39160
|
init_runRecord();
|
|
38924
39161
|
init_types5();
|
|
38925
39162
|
attachmentRef = (idx, part) => `t${idx}p${part}`;
|
|
39163
|
+
isCompaction = (v) => "compaction" in v;
|
|
38926
39164
|
}
|
|
38927
39165
|
});
|
|
38928
39166
|
|
|
@@ -38937,6 +39175,9 @@ function buildRunLedger(cfg, secrets, deps = {}) {
|
|
|
38937
39175
|
baseUrl: worker.baseUrl,
|
|
38938
39176
|
token: token2.reveal(),
|
|
38939
39177
|
storeKey: RUN_STORE_KEY,
|
|
39178
|
+
// The same clamped policy the run store proposes, so the session logs and
|
|
39179
|
+
// the run records are bounded by one configuration.
|
|
39180
|
+
sessionLogMaxBytes: retentionPolicyOf(cfg).sessionLogMaxBytes,
|
|
38940
39181
|
...deps.fetch ? { fetch: deps.fetch } : {}
|
|
38941
39182
|
});
|
|
38942
39183
|
}
|
|
@@ -38945,7 +39186,9 @@ var init_runLedgerWorker = __esm({
|
|
|
38945
39186
|
"../../src/core/runLedgerWorker.ts"() {
|
|
38946
39187
|
"use strict";
|
|
38947
39188
|
init_runRecord();
|
|
39189
|
+
init_runStore();
|
|
38948
39190
|
init_runStoreWorker();
|
|
39191
|
+
init_sessionLog();
|
|
38949
39192
|
init_transcript();
|
|
38950
39193
|
init_types5();
|
|
38951
39194
|
WorkerRunLedger = class {
|
|
@@ -39009,41 +39252,94 @@ var init_runLedgerWorker = __esm({
|
|
|
39009
39252
|
}
|
|
39010
39253
|
};
|
|
39011
39254
|
}
|
|
39012
|
-
await this.post("/runs/transcript/owner", { runId: req.runId, gen: req.gen });
|
|
39013
39255
|
return { ok: true };
|
|
39014
39256
|
}
|
|
39015
|
-
|
|
39257
|
+
/** The write route and its body for a run's rows: the session log when the
|
|
39258
|
+
* run has one, the run's own transcript object otherwise. */
|
|
39259
|
+
target(runId3, gen, session) {
|
|
39260
|
+
if (session === void 0) return { path: "/runs/transcript/write", body: { runId: runId3, gen } };
|
|
39261
|
+
this.checkSessionKey(session);
|
|
39262
|
+
return { path: "/runs/session/write", body: { key: session, gen } };
|
|
39263
|
+
}
|
|
39264
|
+
async writeTurns(runId3, gen, turns, session) {
|
|
39016
39265
|
const rows = [];
|
|
39017
39266
|
const attachments = [];
|
|
39018
39267
|
for (const t of turns) {
|
|
39019
|
-
const out = turnRows(t.idx, t.message);
|
|
39268
|
+
const out = turnRows(t.idx, "message" in t ? t.message : { compaction: t.compaction });
|
|
39020
39269
|
rows.push(...out.rows);
|
|
39021
39270
|
attachments.push(...out.attachments);
|
|
39022
39271
|
}
|
|
39272
|
+
const { path, body } = this.target(runId3, gen, session);
|
|
39023
39273
|
for (const a of attachments) {
|
|
39024
|
-
const r = await this.post(
|
|
39274
|
+
const r = await this.post(path, { ...body, rows: [], attachments: [a] });
|
|
39025
39275
|
const f = this.fenceResult(r);
|
|
39026
39276
|
if (!f.ok) return f;
|
|
39027
39277
|
}
|
|
39028
39278
|
const chunks = chunkRows(rows, TRANSCRIPT_REQUEST_BYTES - 4096);
|
|
39029
39279
|
for (const chunk of chunks.length ? chunks : [[]]) {
|
|
39030
39280
|
if (chunk.length === 0 && attachments.length > 0) continue;
|
|
39031
|
-
const r = await this.post(
|
|
39281
|
+
const r = await this.post(path, { ...body, rows: chunk, attachments: [] });
|
|
39032
39282
|
const f = this.fenceResult(r);
|
|
39033
39283
|
if (!f.ok) return f;
|
|
39034
39284
|
}
|
|
39035
39285
|
return { ok: true };
|
|
39036
39286
|
}
|
|
39037
|
-
async seed(runId3, gen, turns) {
|
|
39287
|
+
async seed(runId3, gen, turns, session) {
|
|
39038
39288
|
this.checkIds(runId3, gen);
|
|
39039
|
-
return this.writeTurns(runId3, gen, turns);
|
|
39289
|
+
return this.writeTurns(runId3, gen, turns, session);
|
|
39040
39290
|
}
|
|
39041
|
-
async step(runId3, gen, record2, turns) {
|
|
39291
|
+
async step(runId3, gen, record2, turns, session) {
|
|
39042
39292
|
this.checkIds(runId3, gen);
|
|
39043
|
-
const written = await this.writeTurns(runId3, gen, turns);
|
|
39293
|
+
const written = await this.writeTurns(runId3, gen, turns, session);
|
|
39044
39294
|
if (!written.ok) return written;
|
|
39045
39295
|
return this.fenceResult(await this.post("/runs/step", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 }));
|
|
39046
39296
|
}
|
|
39297
|
+
checkSessionKey(key) {
|
|
39298
|
+
if (!SESSION_KEY_PATTERN.test(key))
|
|
39299
|
+
throw new PermanentStoreError(`run ledger: malformed session key ${JSON.stringify(key)}`);
|
|
39300
|
+
}
|
|
39301
|
+
async sessionTail(key) {
|
|
39302
|
+
this.checkSessionKey(key);
|
|
39303
|
+
const r = await this.post("/runs/session/tail", { key });
|
|
39304
|
+
return typeof r.data.next === "number" ? r.data.next : 0;
|
|
39305
|
+
}
|
|
39306
|
+
async claimSession(key, runId3, gen, maxBytes) {
|
|
39307
|
+
this.checkSessionKey(key);
|
|
39308
|
+
this.checkIds(runId3, gen);
|
|
39309
|
+
await this.post("/runs/session/owner", {
|
|
39310
|
+
key,
|
|
39311
|
+
runId: runId3,
|
|
39312
|
+
gen,
|
|
39313
|
+
maxBytes: maxBytes ?? this.opts.sessionLogMaxBytes ?? DEFAULT_SESSION_LOG_MAX_BYTES
|
|
39314
|
+
});
|
|
39315
|
+
}
|
|
39316
|
+
async releaseSession(key, runId3, gen) {
|
|
39317
|
+
this.checkSessionKey(key);
|
|
39318
|
+
this.checkIds(runId3, gen);
|
|
39319
|
+
return this.fenceResult(await this.post("/runs/session/clear-owner", { key, runId: runId3, gen }));
|
|
39320
|
+
}
|
|
39321
|
+
async readSession(key, from, to) {
|
|
39322
|
+
this.checkSessionKey(key);
|
|
39323
|
+
const r = await this.post("/runs/session/read", { key, from, ...to !== void 0 ? { to } : {} });
|
|
39324
|
+
return assembleTranscript(
|
|
39325
|
+
Array.isArray(r.data.rows) ? r.data.rows : [],
|
|
39326
|
+
Array.isArray(r.data.attachments) ? r.data.attachments : [],
|
|
39327
|
+
from
|
|
39328
|
+
);
|
|
39329
|
+
}
|
|
39330
|
+
async readSessionTail(key, maxBytes) {
|
|
39331
|
+
this.checkSessionKey(key);
|
|
39332
|
+
const r = await this.post("/runs/session/read-tail", { key, maxBytes });
|
|
39333
|
+
const from = typeof r.data.from === "number" ? r.data.from : 0;
|
|
39334
|
+
return {
|
|
39335
|
+
from,
|
|
39336
|
+
transcript: assembleTranscript(
|
|
39337
|
+
Array.isArray(r.data.rows) ? r.data.rows : [],
|
|
39338
|
+
Array.isArray(r.data.attachments) ? r.data.attachments : [],
|
|
39339
|
+
from
|
|
39340
|
+
)
|
|
39341
|
+
};
|
|
39342
|
+
}
|
|
39047
39343
|
async heartbeat(runId3, gen, leaseMs) {
|
|
39048
39344
|
this.checkIds(runId3, gen);
|
|
39049
39345
|
const r = await this.post("/runs/heartbeat", { storeKey: this.opts.storeKey, runId: runId3, gen, leaseMs });
|
|
@@ -39095,22 +39391,23 @@ var init_runLedgerWorker = __esm({
|
|
|
39095
39391
|
const r = await this.post("/runs/finish", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 });
|
|
39096
39392
|
const f = this.fenceResult(r);
|
|
39097
39393
|
if (!f.ok) return f;
|
|
39098
|
-
|
|
39099
|
-
|
|
39394
|
+
if (record2.session)
|
|
39395
|
+
await this.post("/runs/session/clear-owner", { key: record2.session.key, runId: runId3, gen }).catch(() => {
|
|
39396
|
+
});
|
|
39100
39397
|
return { ok: true, stored: r.data.stored === true };
|
|
39101
39398
|
}
|
|
39102
39399
|
async abandon(runId3, gen) {
|
|
39103
39400
|
this.checkIds(runId3, gen);
|
|
39104
|
-
|
|
39105
|
-
if (!f.ok) return f;
|
|
39106
|
-
await this.post("/runs/transcript/clear", { runId: runId3 }).catch(() => {
|
|
39107
|
-
});
|
|
39108
|
-
return f;
|
|
39401
|
+
return this.fenceResult(await this.post("/runs/abandon", { storeKey: this.opts.storeKey, runId: runId3, gen }));
|
|
39109
39402
|
}
|
|
39110
39403
|
async reclaim(gen, now, leaseMs) {
|
|
39111
39404
|
const r = await this.post("/runs/reclaim", { storeKey: this.opts.storeKey, gen, now, leaseMs });
|
|
39112
39405
|
const runs = Array.isArray(r.data.runs) ? r.data.runs : [];
|
|
39113
|
-
for (const run2 of runs)
|
|
39406
|
+
for (const run2 of runs) {
|
|
39407
|
+
const session = run2.row.meta?.session;
|
|
39408
|
+
if (session) await this.claimSession(session.key, run2.row.runId, gen);
|
|
39409
|
+
else await this.post("/runs/transcript/owner", { runId: run2.row.runId, gen });
|
|
39410
|
+
}
|
|
39114
39411
|
return runs;
|
|
39115
39412
|
}
|
|
39116
39413
|
async listLive() {
|
|
@@ -39158,6 +39455,116 @@ var init_decisions = __esm({
|
|
|
39158
39455
|
}
|
|
39159
39456
|
});
|
|
39160
39457
|
|
|
39458
|
+
// ../../src/core/runLedger/resume.ts
|
|
39459
|
+
function transcriptSource(meta) {
|
|
39460
|
+
return meta.session ? { kind: "session", key: meta.session.key, from: meta.session.seedFrom } : { kind: "run" };
|
|
39461
|
+
}
|
|
39462
|
+
function settlementFor(toolUse, tools) {
|
|
39463
|
+
const tool = tools.get(toolUse.name);
|
|
39464
|
+
if (!tool) {
|
|
39465
|
+
return {
|
|
39466
|
+
toolUse,
|
|
39467
|
+
action: "synthetic",
|
|
39468
|
+
text: `Tool ${toolUse.name} is not available after the bot restarted; continue without it.`
|
|
39469
|
+
};
|
|
39470
|
+
}
|
|
39471
|
+
if (tool.sideEffectFree || RERUN_SAFE_TOOLS.has(toolUse.name)) return { toolUse, action: "rerun" };
|
|
39472
|
+
return {
|
|
39473
|
+
toolUse,
|
|
39474
|
+
action: "synthetic",
|
|
39475
|
+
text: `The bot restarted while this ${toolUse.name} call was in flight; its effects are unknown \u2014 re-check them before re-running it.`
|
|
39476
|
+
};
|
|
39477
|
+
}
|
|
39478
|
+
function planResume(input) {
|
|
39479
|
+
const { transcript, lastStep } = input;
|
|
39480
|
+
if (!lastStep) return { kind: "interrupted", why: "no step record: killed before its conversation was stored" };
|
|
39481
|
+
if (!transcript.complete) return { kind: "interrupted", why: `transcript incomplete: ${transcript.gap}` };
|
|
39482
|
+
const completeness = transcriptCompleteness({
|
|
39483
|
+
lastStep,
|
|
39484
|
+
seedTurns: lastStep.turnIndex,
|
|
39485
|
+
transcriptTurns: transcript.turns
|
|
39486
|
+
});
|
|
39487
|
+
if (completeness.kind === "interrupted") return completeness;
|
|
39488
|
+
const tools = /* @__PURE__ */ new Map();
|
|
39489
|
+
for (const t of input.tools) tools.set(t.name, t);
|
|
39490
|
+
const messages = transcript.messages;
|
|
39491
|
+
const last = messages[messages.length - 1];
|
|
39492
|
+
const calls = toolUsesOf(last);
|
|
39493
|
+
if (completeness.kind === "resume") {
|
|
39494
|
+
if (lastStep.inFlight.length === 0) {
|
|
39495
|
+
if (last?.role !== "user") {
|
|
39496
|
+
return {
|
|
39497
|
+
kind: "interrupted",
|
|
39498
|
+
why: "transcript ends with an assistant turn but the last step recorded nothing in flight"
|
|
39499
|
+
};
|
|
39500
|
+
}
|
|
39501
|
+
return {
|
|
39502
|
+
kind: "resume",
|
|
39503
|
+
messages,
|
|
39504
|
+
compactions: transcript.compactions,
|
|
39505
|
+
settlements: [],
|
|
39506
|
+
stepRecorded: true,
|
|
39507
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39508
|
+
step: lastStep.step,
|
|
39509
|
+
turn: lastStep.turn,
|
|
39510
|
+
iteration: lastStep.iteration,
|
|
39511
|
+
remainingMs: lastStep.remainingMs
|
|
39512
|
+
};
|
|
39513
|
+
}
|
|
39514
|
+
const recorded = new Set(lastStep.inFlight.map((c) => c.callId));
|
|
39515
|
+
if (calls.length === 0 || calls.length !== recorded.size || !calls.every((c) => recorded.has(c.id))) {
|
|
39516
|
+
return {
|
|
39517
|
+
kind: "interrupted",
|
|
39518
|
+
why: `the last step's calls in flight (${[...recorded].join(", ")}) do not match the transcript's last assistant turn`
|
|
39519
|
+
};
|
|
39520
|
+
}
|
|
39521
|
+
return {
|
|
39522
|
+
kind: "resume",
|
|
39523
|
+
messages,
|
|
39524
|
+
compactions: transcript.compactions,
|
|
39525
|
+
settlements: calls.map((c) => settlementFor(c, tools)),
|
|
39526
|
+
stepRecorded: true,
|
|
39527
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39528
|
+
step: lastStep.step,
|
|
39529
|
+
turn: lastStep.turn,
|
|
39530
|
+
iteration: lastStep.iteration,
|
|
39531
|
+
remainingMs: lastStep.remainingMs
|
|
39532
|
+
};
|
|
39533
|
+
}
|
|
39534
|
+
if (calls.length === 0) {
|
|
39535
|
+
return { kind: "interrupted", why: "the next step's turns landed but its assistant turn has no tool calls" };
|
|
39536
|
+
}
|
|
39537
|
+
const bookkeepingOnly = calls.every((c) => c.name === "update_status");
|
|
39538
|
+
return {
|
|
39539
|
+
kind: "resume",
|
|
39540
|
+
messages,
|
|
39541
|
+
compactions: transcript.compactions,
|
|
39542
|
+
settlements: calls.map((c) => ({ toolUse: c, action: "rerun" })),
|
|
39543
|
+
stepRecorded: false,
|
|
39544
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39545
|
+
step: lastStep.step + 1,
|
|
39546
|
+
turn: lastStep.turn + (bookkeepingOnly ? 0 : 1),
|
|
39547
|
+
iteration: lastStep.iteration + 1,
|
|
39548
|
+
remainingMs: lastStep.remainingMs
|
|
39549
|
+
};
|
|
39550
|
+
}
|
|
39551
|
+
var RERUN_SAFE_TOOLS, toolUsesOf;
|
|
39552
|
+
var init_resume = __esm({
|
|
39553
|
+
"../../src/core/runLedger/resume.ts"() {
|
|
39554
|
+
"use strict";
|
|
39555
|
+
init_decisions();
|
|
39556
|
+
RERUN_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
39557
|
+
"write_file",
|
|
39558
|
+
"update_status",
|
|
39559
|
+
"submit_verdict",
|
|
39560
|
+
"submit_pr_description",
|
|
39561
|
+
"submit_handoff",
|
|
39562
|
+
"submit_dispositions"
|
|
39563
|
+
]);
|
|
39564
|
+
toolUsesOf = (message2) => message2?.role === "assistant" ? message2.content.filter((p) => p.type === "tool_use") : [];
|
|
39565
|
+
}
|
|
39566
|
+
});
|
|
39567
|
+
|
|
39161
39568
|
// ../../src/core/boot.ts
|
|
39162
39569
|
function closureNote(agent, prUrl2) {
|
|
39163
39570
|
if (agent === "ship") return shipInterruptedNote(prUrl2);
|
|
@@ -39205,7 +39612,7 @@ async function reclaimRuns(opts) {
|
|
|
39205
39612
|
status3 = "interrupted";
|
|
39206
39613
|
why = "reserved at admission without its request: nothing to restart from";
|
|
39207
39614
|
} else {
|
|
39208
|
-
const verdict = await completenessVerdict(ledger, row
|
|
39615
|
+
const verdict = await completenessVerdict(ledger, row, run2.lastStep);
|
|
39209
39616
|
if (verdict.resumable && run2.lastStep) {
|
|
39210
39617
|
const events2 = await ledger.readEvents(row.runId);
|
|
39211
39618
|
outcome.resumable.push({
|
|
@@ -39306,11 +39713,12 @@ async function closeReclaimed(ledger, gen, input) {
|
|
|
39306
39713
|
const result = await ledger.finish(input.row.runId, gen, record2);
|
|
39307
39714
|
return result.ok ? { ok: true, record: record2 } : { ok: false, reason: result.reason ?? "refused" };
|
|
39308
39715
|
}
|
|
39309
|
-
async function completenessVerdict(ledger,
|
|
39716
|
+
async function completenessVerdict(ledger, row, lastStep) {
|
|
39310
39717
|
if (!lastStep) return { resumable: false, why: "no step record: killed before its conversation was stored" };
|
|
39311
39718
|
let transcript;
|
|
39312
39719
|
try {
|
|
39313
|
-
|
|
39720
|
+
const source2 = transcriptSource(row.meta);
|
|
39721
|
+
transcript = source2.kind === "session" ? await ledger.readSession(source2.key, source2.from) : await ledger.readTranscript(row.runId);
|
|
39314
39722
|
} catch (err2) {
|
|
39315
39723
|
return { resumable: false, why: `transcript unreadable (${describe9(err2)})` };
|
|
39316
39724
|
}
|
|
@@ -39339,6 +39747,7 @@ var init_boot = __esm({
|
|
|
39339
39747
|
"use strict";
|
|
39340
39748
|
init_runStoreWorker();
|
|
39341
39749
|
init_decisions();
|
|
39750
|
+
init_resume();
|
|
39342
39751
|
init_types5();
|
|
39343
39752
|
init_record();
|
|
39344
39753
|
init_shipPipeline();
|
|
@@ -39353,110 +39762,6 @@ var init_boot = __esm({
|
|
|
39353
39762
|
}
|
|
39354
39763
|
});
|
|
39355
39764
|
|
|
39356
|
-
// ../../src/core/runLedger/resume.ts
|
|
39357
|
-
function settlementFor(toolUse, tools) {
|
|
39358
|
-
const tool = tools.get(toolUse.name);
|
|
39359
|
-
if (!tool) {
|
|
39360
|
-
return {
|
|
39361
|
-
toolUse,
|
|
39362
|
-
action: "synthetic",
|
|
39363
|
-
text: `Tool ${toolUse.name} is not available after the bot restarted; continue without it.`
|
|
39364
|
-
};
|
|
39365
|
-
}
|
|
39366
|
-
if (tool.sideEffectFree || RERUN_SAFE_TOOLS.has(toolUse.name)) return { toolUse, action: "rerun" };
|
|
39367
|
-
return {
|
|
39368
|
-
toolUse,
|
|
39369
|
-
action: "synthetic",
|
|
39370
|
-
text: `The bot restarted while this ${toolUse.name} call was in flight; its effects are unknown \u2014 re-check them before re-running it.`
|
|
39371
|
-
};
|
|
39372
|
-
}
|
|
39373
|
-
function planResume(input) {
|
|
39374
|
-
const { transcript, lastStep } = input;
|
|
39375
|
-
if (!lastStep) return { kind: "interrupted", why: "no step record: killed before its conversation was stored" };
|
|
39376
|
-
if (!transcript.complete) return { kind: "interrupted", why: `transcript incomplete: ${transcript.gap}` };
|
|
39377
|
-
const completeness = transcriptCompleteness({
|
|
39378
|
-
lastStep,
|
|
39379
|
-
seedTurns: lastStep.turnIndex,
|
|
39380
|
-
transcriptTurns: transcript.turns
|
|
39381
|
-
});
|
|
39382
|
-
if (completeness.kind === "interrupted") return completeness;
|
|
39383
|
-
const tools = /* @__PURE__ */ new Map();
|
|
39384
|
-
for (const t of input.tools) tools.set(t.name, t);
|
|
39385
|
-
const messages = transcript.messages;
|
|
39386
|
-
const last = messages[messages.length - 1];
|
|
39387
|
-
const calls = toolUsesOf(last);
|
|
39388
|
-
if (completeness.kind === "resume") {
|
|
39389
|
-
if (lastStep.inFlight.length === 0) {
|
|
39390
|
-
if (last?.role !== "user") {
|
|
39391
|
-
return {
|
|
39392
|
-
kind: "interrupted",
|
|
39393
|
-
why: "transcript ends with an assistant turn but the last step recorded nothing in flight"
|
|
39394
|
-
};
|
|
39395
|
-
}
|
|
39396
|
-
return {
|
|
39397
|
-
kind: "resume",
|
|
39398
|
-
messages,
|
|
39399
|
-
settlements: [],
|
|
39400
|
-
stepRecorded: true,
|
|
39401
|
-
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39402
|
-
step: lastStep.step,
|
|
39403
|
-
turn: lastStep.turn,
|
|
39404
|
-
iteration: lastStep.iteration,
|
|
39405
|
-
remainingMs: lastStep.remainingMs
|
|
39406
|
-
};
|
|
39407
|
-
}
|
|
39408
|
-
const recorded = new Set(lastStep.inFlight.map((c) => c.callId));
|
|
39409
|
-
if (calls.length === 0 || calls.length !== recorded.size || !calls.every((c) => recorded.has(c.id))) {
|
|
39410
|
-
return {
|
|
39411
|
-
kind: "interrupted",
|
|
39412
|
-
why: `the last step's calls in flight (${[...recorded].join(", ")}) do not match the transcript's last assistant turn`
|
|
39413
|
-
};
|
|
39414
|
-
}
|
|
39415
|
-
return {
|
|
39416
|
-
kind: "resume",
|
|
39417
|
-
messages,
|
|
39418
|
-
settlements: calls.map((c) => settlementFor(c, tools)),
|
|
39419
|
-
stepRecorded: true,
|
|
39420
|
-
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39421
|
-
step: lastStep.step,
|
|
39422
|
-
turn: lastStep.turn,
|
|
39423
|
-
iteration: lastStep.iteration,
|
|
39424
|
-
remainingMs: lastStep.remainingMs
|
|
39425
|
-
};
|
|
39426
|
-
}
|
|
39427
|
-
if (calls.length === 0) {
|
|
39428
|
-
return { kind: "interrupted", why: "the next step's turns landed but its assistant turn has no tool calls" };
|
|
39429
|
-
}
|
|
39430
|
-
const bookkeepingOnly = calls.every((c) => c.name === "update_status");
|
|
39431
|
-
return {
|
|
39432
|
-
kind: "resume",
|
|
39433
|
-
messages,
|
|
39434
|
-
settlements: calls.map((c) => ({ toolUse: c, action: "rerun" })),
|
|
39435
|
-
stepRecorded: false,
|
|
39436
|
-
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39437
|
-
step: lastStep.step + 1,
|
|
39438
|
-
turn: lastStep.turn + (bookkeepingOnly ? 0 : 1),
|
|
39439
|
-
iteration: lastStep.iteration + 1,
|
|
39440
|
-
remainingMs: lastStep.remainingMs
|
|
39441
|
-
};
|
|
39442
|
-
}
|
|
39443
|
-
var RERUN_SAFE_TOOLS, toolUsesOf;
|
|
39444
|
-
var init_resume = __esm({
|
|
39445
|
-
"../../src/core/runLedger/resume.ts"() {
|
|
39446
|
-
"use strict";
|
|
39447
|
-
init_decisions();
|
|
39448
|
-
RERUN_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
39449
|
-
"write_file",
|
|
39450
|
-
"update_status",
|
|
39451
|
-
"submit_verdict",
|
|
39452
|
-
"submit_pr_description",
|
|
39453
|
-
"submit_handoff",
|
|
39454
|
-
"submit_dispositions"
|
|
39455
|
-
]);
|
|
39456
|
-
toolUsesOf = (message2) => message2?.role === "assistant" ? message2.content.filter((p) => p.type === "tool_use") : [];
|
|
39457
|
-
}
|
|
39458
|
-
});
|
|
39459
|
-
|
|
39460
39765
|
// ../../src/core/resumeLaunch.ts
|
|
39461
39766
|
function knownToolsFor(agent) {
|
|
39462
39767
|
return (TOOLSETS[agent.toolset] ?? []).map((t) => ({
|
|
@@ -40661,9 +40966,12 @@ function parseBrief(v) {
|
|
|
40661
40966
|
const p = b.prior;
|
|
40662
40967
|
if (typeof p !== "object" || p === null || typeof p.reviewRunId !== "string" || !RUN_ID_PATTERN.test(p.reviewRunId))
|
|
40663
40968
|
return invalid("brief.prior.reviewRunId must be a run id");
|
|
40664
|
-
if (p.
|
|
40665
|
-
return invalid("brief.prior.
|
|
40666
|
-
prior = {
|
|
40969
|
+
if (p.codingRunId !== void 0 && (typeof p.codingRunId !== "string" || !RUN_ID_PATTERN.test(p.codingRunId)))
|
|
40970
|
+
return invalid("brief.prior.codingRunId must be a run id");
|
|
40971
|
+
prior = {
|
|
40972
|
+
reviewRunId: p.reviewRunId,
|
|
40973
|
+
...p.codingRunId !== void 0 ? { codingRunId: p.codingRunId } : {}
|
|
40974
|
+
};
|
|
40667
40975
|
}
|
|
40668
40976
|
return {
|
|
40669
40977
|
ok: true,
|
|
@@ -40677,15 +40985,15 @@ function parseBrief(v) {
|
|
|
40677
40985
|
}
|
|
40678
40986
|
};
|
|
40679
40987
|
}
|
|
40680
|
-
case "
|
|
40988
|
+
case "findings": {
|
|
40681
40989
|
const n2 = pr();
|
|
40682
40990
|
if (!n2.ok) return n2;
|
|
40683
40991
|
const review = runId3("reviewRunId");
|
|
40684
40992
|
if (!review.ok) return review;
|
|
40685
|
-
return { ok: true, value: { kind: "
|
|
40993
|
+
return { ok: true, value: { kind: "findings", unit: b.unit, pr: n2.value, reviewRunId: review.value } };
|
|
40686
40994
|
}
|
|
40687
40995
|
default:
|
|
40688
|
-
return invalid("brief.kind must be contract, review or
|
|
40996
|
+
return invalid("brief.kind must be contract, review or findings");
|
|
40689
40997
|
}
|
|
40690
40998
|
}
|
|
40691
40999
|
function parseObject(text) {
|
|
@@ -40748,6 +41056,30 @@ function unitThread(instance, row) {
|
|
|
40748
41056
|
const sourceUrl = row?.sourceUrl ?? (threadKey === instance.threadKey ? instance.sourceUrl : void 0);
|
|
40749
41057
|
return { threadKey, sourceUrl };
|
|
40750
41058
|
}
|
|
41059
|
+
async function openThreadFromRequester(deps, instance, lead, at) {
|
|
41060
|
+
const parent = deps.ioFor({ threadKey: instance.threadKey, userId: instance.userId });
|
|
41061
|
+
if (!parent?.openThread) return { ok: false, response: json(503, { ok: false, error: "no_channel", at }) };
|
|
41062
|
+
try {
|
|
41063
|
+
const opened = await parent.openThread(lead);
|
|
41064
|
+
return {
|
|
41065
|
+
ok: true,
|
|
41066
|
+
thread: {
|
|
41067
|
+
threadKey: opened.thread.threadKey,
|
|
41068
|
+
...opened.thread.sourceUrl !== void 0 ? { sourceUrl: opened.thread.sourceUrl } : {}
|
|
41069
|
+
}
|
|
41070
|
+
};
|
|
41071
|
+
} catch (err2) {
|
|
41072
|
+
return { ok: false, response: json(502, { ok: false, error: "thread_failed", message: describe10(err2), at }) };
|
|
41073
|
+
}
|
|
41074
|
+
}
|
|
41075
|
+
async function ensureReviewThread(deps, instance, row, at) {
|
|
41076
|
+
if (row.reviewThread !== void 0) return { ok: true, row, thread: row.reviewThread };
|
|
41077
|
+
const opened = await openThreadFromRequester(deps, instance, reviewLead(instance, row), at);
|
|
41078
|
+
if (!opened.ok) return opened;
|
|
41079
|
+
const updated = { ...row, reviewThread: opened.thread };
|
|
41080
|
+
await deps.instances.putUnits([updated]);
|
|
41081
|
+
return { ok: true, row: updated, thread: opened.thread };
|
|
41082
|
+
}
|
|
40751
41083
|
function authorizeStep(headers, step, deps) {
|
|
40752
41084
|
const raw = headers.authorization;
|
|
40753
41085
|
const authorization = Array.isArray(raw) ? raw[0] : raw;
|
|
@@ -40836,8 +41168,19 @@ async function spawn6(body, deps) {
|
|
|
40836
41168
|
const at = (deps.clock ?? systemClock)();
|
|
40837
41169
|
const unit = await unitRowOf(deps, instance, req.unit);
|
|
40838
41170
|
if (!unit.ok) return unit.response;
|
|
40839
|
-
const
|
|
40840
|
-
if (
|
|
41171
|
+
const own = unitThread(instance, unit.row);
|
|
41172
|
+
if (own.threadKey === void 0) return json(409, { ok: false, error: "unit_not_started", unit: req.unit, at });
|
|
41173
|
+
let row = unit.row;
|
|
41174
|
+
let thread = {
|
|
41175
|
+
threadKey: own.threadKey,
|
|
41176
|
+
...own.sourceUrl !== void 0 ? { sourceUrl: own.sourceUrl } : {}
|
|
41177
|
+
};
|
|
41178
|
+
if (req.preset === "review" && row !== void 0) {
|
|
41179
|
+
const review = await ensureReviewThread(deps, instance, row, at);
|
|
41180
|
+
if (!review.ok) return review.response;
|
|
41181
|
+
row = review.row;
|
|
41182
|
+
thread = review.thread;
|
|
41183
|
+
}
|
|
40841
41184
|
const threadKey = thread.threadKey;
|
|
40842
41185
|
const key = idempotencyKeyFor(instance.id, req.step);
|
|
40843
41186
|
const live = await liveOnThread(deps.runs, instance, threadKey);
|
|
@@ -40848,10 +41191,9 @@ async function spawn6(body, deps) {
|
|
|
40848
41191
|
if (!io) return json(503, { ok: false, error: "no_channel", at });
|
|
40849
41192
|
let turn;
|
|
40850
41193
|
if (req.brief !== void 0) {
|
|
40851
|
-
if (
|
|
41194
|
+
if (row === void 0) return json(400, { ok: false, error: "a brief needs the unit it runs for" });
|
|
40852
41195
|
try {
|
|
40853
|
-
|
|
40854
|
-
turn = composed;
|
|
41196
|
+
turn = await composeChild(req.brief, instance, row, briefReaders(deps, instance, io));
|
|
40855
41197
|
} catch (err2) {
|
|
40856
41198
|
log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe10(err2)}`);
|
|
40857
41199
|
return json(502, { ok: false, error: "brief_failed", message: describe10(err2), at });
|
|
@@ -40897,8 +41239,7 @@ async function spawn6(body, deps) {
|
|
|
40897
41239
|
};
|
|
40898
41240
|
const settled = deps.dispatch(msg, child, {
|
|
40899
41241
|
coordinator: tag,
|
|
40900
|
-
...turn.contract !== void 0 ? { contract: turn.contract } : {}
|
|
40901
|
-
...turn.fixRound !== void 0 ? { fixRound: turn.fixRound } : {}
|
|
41242
|
+
...turn.contract !== void 0 ? { contract: turn.contract } : {}
|
|
40902
41243
|
}).then(
|
|
40903
41244
|
(outcome) => ({ kind: "ended", outcome }),
|
|
40904
41245
|
(err2) => ({ kind: "threw", err: err2 })
|
|
@@ -41115,30 +41456,32 @@ async function unitStart(body, deps) {
|
|
|
41115
41456
|
...instance.sourceUrl !== void 0 ? { sourceUrl: instance.sourceUrl } : {}
|
|
41116
41457
|
};
|
|
41117
41458
|
} else {
|
|
41118
|
-
const
|
|
41119
|
-
if (!
|
|
41120
|
-
|
|
41121
|
-
const opened = await parent.openThread(unitLead(instance, row));
|
|
41122
|
-
row = {
|
|
41123
|
-
...row,
|
|
41124
|
-
threadKey: opened.thread.threadKey,
|
|
41125
|
-
...opened.thread.sourceUrl !== void 0 ? { sourceUrl: opened.thread.sourceUrl } : {}
|
|
41126
|
-
};
|
|
41127
|
-
} catch (err2) {
|
|
41128
|
-
return json(502, { ok: false, error: "thread_failed", message: describe10(err2), at });
|
|
41129
|
-
}
|
|
41459
|
+
const opened = await openThreadFromRequester(deps, instance, unitLead(instance, row), at);
|
|
41460
|
+
if (!opened.ok) return opened.response;
|
|
41461
|
+
row = { ...row, ...opened.thread };
|
|
41130
41462
|
}
|
|
41131
41463
|
}
|
|
41464
|
+
if (row.reviewThread === void 0) {
|
|
41465
|
+
const review = await ensureReviewThread(deps, instance, row, at);
|
|
41466
|
+
if (!review.ok) {
|
|
41467
|
+
if (row !== unit.row) await deps.instances.putUnits([row]);
|
|
41468
|
+
return review.response;
|
|
41469
|
+
}
|
|
41470
|
+
row = review.row;
|
|
41471
|
+
}
|
|
41132
41472
|
if (row.issue === void 0 && row.unit !== TASK_UNIT) {
|
|
41133
41473
|
const issue = await unitIssueOf(deps, instance.repo, row.unit);
|
|
41134
41474
|
if (issue !== void 0) row = { ...row, issue };
|
|
41135
41475
|
}
|
|
41136
41476
|
row = { ...row, startedAt: row.startedAt ?? at };
|
|
41137
41477
|
await deps.instances.putUnits([row]);
|
|
41138
|
-
(deps.log ?? console.log)(
|
|
41478
|
+
(deps.log ?? console.log)(
|
|
41479
|
+
`[coordinator] ${instance.id} ${row.unit}: started in ${row.threadKey}, review in ${row.reviewThread?.threadKey}`
|
|
41480
|
+
);
|
|
41139
41481
|
return json(200, {
|
|
41140
41482
|
ok: true,
|
|
41141
41483
|
threadKey: row.threadKey,
|
|
41484
|
+
reviewThreadKey: row.reviewThread?.threadKey,
|
|
41142
41485
|
branch: row.branch,
|
|
41143
41486
|
base: instance.base ?? "main",
|
|
41144
41487
|
...row.issue !== void 0 ? { issue: row.issue } : {},
|
|
@@ -41150,6 +41493,12 @@ function unitLead(instance, row) {
|
|
|
41150
41493
|
const from = instance.sourceUrl !== void 0 ? `[the *ship* run](${instance.sourceUrl})` : "the *ship* run";
|
|
41151
41494
|
return `\u21B3 *ship* unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""} for ${who2}, from ${from}: \`${row.branch}\` in ${instance.repo}`;
|
|
41152
41495
|
}
|
|
41496
|
+
function reviewLead(instance, row) {
|
|
41497
|
+
const who2 = instance.userName ?? instance.userId;
|
|
41498
|
+
const from = instance.sourceUrl !== void 0 ? `[the *ship* run](${instance.sourceUrl})` : "the *ship* run";
|
|
41499
|
+
const what = row.unit === TASK_UNIT ? "the task" : `unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""}`;
|
|
41500
|
+
return `\u21B3 *ship* review of ${what} for ${who2}, from ${from}: \`${row.branch}\` in ${instance.repo}`;
|
|
41501
|
+
}
|
|
41153
41502
|
async function branch(body, deps) {
|
|
41154
41503
|
const id = parseInstanceId(body.parentInstanceId);
|
|
41155
41504
|
if (!id.ok) return json(400, { ok: false, error: id.error });
|
|
@@ -41584,7 +41933,7 @@ var init_adminCoordinator = __esm({
|
|
|
41584
41933
|
PRESETS_OF_KIND = {
|
|
41585
41934
|
contract: "coding",
|
|
41586
41935
|
review: "review",
|
|
41587
|
-
|
|
41936
|
+
findings: "coding"
|
|
41588
41937
|
};
|
|
41589
41938
|
invalid = (error) => ({ ok: false, error });
|
|
41590
41939
|
json = (status3, body) => ({ status: status3, body });
|