@coreplane/switchboard 1.214.0 → 1.215.1
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 +636 -284
- 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.`;
|
|
@@ -24696,15 +24746,18 @@ function parseRouteAnswer(raw, allowed2, compound) {
|
|
|
24696
24746
|
}
|
|
24697
24747
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
24698
24748
|
return { preset: void 0, reason: `not a single JSON object: ${tidyReason(trimmed)}` };
|
|
24699
|
-
const { preset, reason, parts } = parsed;
|
|
24700
|
-
|
|
24749
|
+
const { preset: named, reason, parts } = parsed;
|
|
24750
|
+
const preset = typeof named === "string" ? named : Array.isArray(parts) ? COMPOUND_PRESET : void 0;
|
|
24751
|
+
const inferred = typeof named !== "string" && preset === COMPOUND_PRESET;
|
|
24752
|
+
if (preset === void 0)
|
|
24701
24753
|
return { preset: void 0, reason: `missing preset in the router's answer: ${tidyReason(trimmed)}` };
|
|
24702
|
-
if (typeof reason !== "string")
|
|
24754
|
+
if (typeof reason !== "string" && !inferred)
|
|
24703
24755
|
return { preset: void 0, reason: `missing reason in the router's answer: ${tidyReason(trimmed)}` };
|
|
24704
|
-
|
|
24756
|
+
const tidy = typeof reason === "string" ? tidyReason(reason) : "compound inferred from parts";
|
|
24757
|
+
if (preset === COMPOUND_PRESET) return parseCompound(parts, tidy, allowed2, compound);
|
|
24705
24758
|
if (!allowed2.includes(preset))
|
|
24706
24759
|
return { preset: void 0, reason: `router said "${tidyReason(preset)}", not a preset the requester may run` };
|
|
24707
|
-
return { preset, reason:
|
|
24760
|
+
return { preset, reason: tidy };
|
|
24708
24761
|
}
|
|
24709
24762
|
function parseCompound(parts, reason, allowed2, offer) {
|
|
24710
24763
|
const rejected = (why) => ({
|
|
@@ -24729,16 +24782,20 @@ function parseCompound(parts, reason, allowed2, offer) {
|
|
|
24729
24782
|
if (tidy.length === 0) return rejected(`part ${n2} has no text`);
|
|
24730
24783
|
out.push({ preset, text: tidy });
|
|
24731
24784
|
}
|
|
24785
|
+
const writer = out.find((p) => identityOf(p.preset) === "write");
|
|
24786
|
+
if (writer) return { preset: writer.preset, reason, collapsed: { presets: out.map((p) => p.preset) } };
|
|
24732
24787
|
return { preset: COMPOUND_PRESET, reason, parts: out };
|
|
24733
24788
|
}
|
|
24734
24789
|
async function route(input, model, opts = {}) {
|
|
24735
|
-
const
|
|
24790
|
+
const { compound: offer, ...rest } = input;
|
|
24791
|
+
const offered = rest.presets.filter((p) => rest.allowed.includes(p.name));
|
|
24736
24792
|
if (offered.length === 0) return { preset: void 0, reason: "no preset the requester may run" };
|
|
24737
|
-
const
|
|
24793
|
+
const compound = offer && partPresets(offered).length > 0 ? offer : void 0;
|
|
24794
|
+
const prompt = buildRoutePrompt({ ...rest, presets: offered, ...compound ? { compound } : {} });
|
|
24738
24795
|
let raw;
|
|
24739
24796
|
try {
|
|
24740
24797
|
raw = await model(prompt, {
|
|
24741
|
-
maxTokens: routeMaxOutputTokens(
|
|
24798
|
+
maxTokens: routeMaxOutputTokens(compound),
|
|
24742
24799
|
signal: AbortSignal.timeout(opts.timeoutMs ?? ROUTE_TIMEOUT_MS)
|
|
24743
24800
|
});
|
|
24744
24801
|
} catch (err2) {
|
|
@@ -24750,7 +24807,7 @@ async function route(input, model, opts = {}) {
|
|
|
24750
24807
|
return parseRouteAnswer(
|
|
24751
24808
|
raw,
|
|
24752
24809
|
offered.map((p) => p.name),
|
|
24753
|
-
|
|
24810
|
+
compound
|
|
24754
24811
|
);
|
|
24755
24812
|
}
|
|
24756
24813
|
function providerRouteModel(provider, model, opts = {}) {
|
|
@@ -24824,18 +24881,24 @@ async function routeRequest(deps, ctx) {
|
|
|
24824
24881
|
effort: directives.effort ?? sticky.effort
|
|
24825
24882
|
}
|
|
24826
24883
|
});
|
|
24827
|
-
const parts = decision
|
|
24884
|
+
const { parts, collapsed } = decision;
|
|
24828
24885
|
console.log(
|
|
24829
|
-
`[route] ${msg.threadKey} routed to ${decision.preset} on ${modelRef2}${parts ? ` (${parts.length} parts)` : ""}: ${decision.reason}`
|
|
24886
|
+
`[route] ${msg.threadKey} routed to ${decision.preset} on ${modelRef2}${parts ? ` (${parts.length} parts)` : ""}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}: ${decision.reason}`
|
|
24830
24887
|
);
|
|
24831
24888
|
return {
|
|
24832
24889
|
kind: "routed",
|
|
24833
24890
|
resolved,
|
|
24834
|
-
route: {
|
|
24891
|
+
route: {
|
|
24892
|
+
preset: decision.preset,
|
|
24893
|
+
reason: decision.reason,
|
|
24894
|
+
model: modelRef2,
|
|
24895
|
+
...parts ? { parts } : {},
|
|
24896
|
+
...collapsed ? { collapsed } : {}
|
|
24897
|
+
}
|
|
24835
24898
|
};
|
|
24836
24899
|
}
|
|
24837
|
-
function routedLabel(reason) {
|
|
24838
|
-
return `routed: ${reason}`;
|
|
24900
|
+
function routedLabel(reason, collapsed) {
|
|
24901
|
+
return `routed: ${reason}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}`;
|
|
24839
24902
|
}
|
|
24840
24903
|
function routedPartLines(parts) {
|
|
24841
24904
|
return parts.map((p) => {
|
|
@@ -24851,7 +24914,7 @@ function compoundBrief(text, parts) {
|
|
|
24851
24914
|
...parts.map((p, i) => `${i + 1}. \`${p.preset}\`: ${p.text}`)
|
|
24852
24915
|
].join("\n");
|
|
24853
24916
|
}
|
|
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;
|
|
24917
|
+
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
24918
|
var init_route = __esm({
|
|
24856
24919
|
"../../src/core/dispatch/route.ts"() {
|
|
24857
24920
|
"use strict";
|
|
@@ -24868,6 +24931,7 @@ var init_route = __esm({
|
|
|
24868
24931
|
ROUTE_PART_TEXT_CAP = 1e3;
|
|
24869
24932
|
ROUTE_PART_LINE_CAP = 100;
|
|
24870
24933
|
COMPOUND_BRIEF_HEADING = "Routed as a compound request";
|
|
24934
|
+
identityOf = (preset) => AGENTS[preset]?.identity;
|
|
24871
24935
|
ROUTED_CARD_FOOTER = "reply agent:<preset> to run it another way";
|
|
24872
24936
|
}
|
|
24873
24937
|
});
|
|
@@ -26638,7 +26702,7 @@ ${raw.trim()}`;
|
|
|
26638
26702
|
};
|
|
26639
26703
|
submitDispositionsTool = {
|
|
26640
26704
|
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
|
|
26705
|
+
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
26706
|
inputSchema: {
|
|
26643
26707
|
type: "object",
|
|
26644
26708
|
properties: {
|
|
@@ -26661,15 +26725,7 @@ ${raw.trim()}`;
|
|
|
26661
26725
|
async run(input, ctx) {
|
|
26662
26726
|
const parsed = parseDispositionsInput(input);
|
|
26663
26727
|
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)";
|
|
26728
|
+
if (!ctx.onDispositions) return "no run is recording dispositions here";
|
|
26673
26729
|
ctx.onDispositions(parsed.dispositions);
|
|
26674
26730
|
const drops = parsed.dropped.length ? ` (dropped: ${parsed.dropped.join("; ")})` : "";
|
|
26675
26731
|
return `dispositions recorded: ${parsed.dispositions.length}${drops}; a later call replaces this one`;
|
|
@@ -28953,7 +29009,7 @@ function startMemoryRead(deps, ctx) {
|
|
|
28953
29009
|
async function openAckCard(deps, ctx) {
|
|
28954
29010
|
const { io, agent, resolved, startedAt, clock: clock3, root, trace, route: route3 } = ctx;
|
|
28955
29011
|
const shell = createCardShell({
|
|
28956
|
-
label: `*${agent.name}* on \`${resolved.modelRef}\`${route3 ? ` \xB7 ${routedLabel(route3.reason)}` : ""}`,
|
|
29012
|
+
label: `*${agent.name}* on \`${resolved.modelRef}\`${route3 ? ` \xB7 ${routedLabel(route3.reason, route3.collapsed)}` : ""}`,
|
|
28957
29013
|
startedAt,
|
|
28958
29014
|
now: clock3,
|
|
28959
29015
|
...route3?.parts ? { lead: routedPartLines(route3.parts) } : {},
|
|
@@ -29872,7 +29928,7 @@ var init_bridge2 = __esm({
|
|
|
29872
29928
|
this.onToolEnd(event, out);
|
|
29873
29929
|
break;
|
|
29874
29930
|
case "compaction_end":
|
|
29875
|
-
this.onCompactionEnd(event);
|
|
29931
|
+
this.onCompactionEnd(event, out);
|
|
29876
29932
|
break;
|
|
29877
29933
|
case "summarization_retry_scheduled":
|
|
29878
29934
|
this.summarizationRetries++;
|
|
@@ -29986,7 +30042,7 @@ var init_bridge2 = __esm({
|
|
|
29986
30042
|
}
|
|
29987
30043
|
}
|
|
29988
30044
|
}
|
|
29989
|
-
onCompactionEnd(event) {
|
|
30045
|
+
onCompactionEnd(event, out) {
|
|
29990
30046
|
const result = isRecord3(event.result) ? event.result : void 0;
|
|
29991
30047
|
if (event.aborted === true || !result) {
|
|
29992
30048
|
this.note(
|
|
@@ -30003,6 +30059,13 @@ var init_bridge2 = __esm({
|
|
|
30003
30059
|
"compacted",
|
|
30004
30060
|
`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
30061
|
);
|
|
30062
|
+
if (typeof result.summary === "string") {
|
|
30063
|
+
out.compaction = {
|
|
30064
|
+
summary: result.summary,
|
|
30065
|
+
...before !== void 0 ? { tokensBefore: before } : {},
|
|
30066
|
+
...typeof result.firstKeptEntryId === "string" ? { firstKeptEntryId: result.firstKeptEntryId } : {}
|
|
30067
|
+
};
|
|
30068
|
+
}
|
|
30006
30069
|
}
|
|
30007
30070
|
onUiRequest(event, out) {
|
|
30008
30071
|
const method = str5(event.method);
|
|
@@ -30071,7 +30134,7 @@ function chatMessageOf(message2) {
|
|
|
30071
30134
|
return void 0;
|
|
30072
30135
|
}
|
|
30073
30136
|
}
|
|
30074
|
-
function piSessionFile(messages, opts) {
|
|
30137
|
+
function piSessionFile(messages, opts, compactions = []) {
|
|
30075
30138
|
const lines = [];
|
|
30076
30139
|
const stamp = new Date(opts.at).toISOString();
|
|
30077
30140
|
lines.push(
|
|
@@ -30080,12 +30143,35 @@ function piSessionFile(messages, opts) {
|
|
|
30080
30143
|
let parentId = null;
|
|
30081
30144
|
let n2 = 0;
|
|
30082
30145
|
const toolNames = /* @__PURE__ */ new Map();
|
|
30146
|
+
const firstEntryOf = [];
|
|
30147
|
+
const nextId = () => (n2++).toString(16).padStart(8, "0");
|
|
30083
30148
|
const push = (message2) => {
|
|
30084
|
-
const id = (
|
|
30149
|
+
const id = nextId();
|
|
30085
30150
|
lines.push(JSON.stringify({ type: "message", id, parentId, timestamp: stamp, message: message2 }));
|
|
30086
30151
|
parentId = id;
|
|
30152
|
+
return id;
|
|
30087
30153
|
};
|
|
30088
|
-
|
|
30154
|
+
const pushCompaction = (c) => {
|
|
30155
|
+
const id = nextId();
|
|
30156
|
+
const kept = c.keptBefore !== void 0 ? firstEntryOf[c.keptBefore] : void 0;
|
|
30157
|
+
lines.push(
|
|
30158
|
+
JSON.stringify({
|
|
30159
|
+
type: "compaction",
|
|
30160
|
+
id,
|
|
30161
|
+
parentId,
|
|
30162
|
+
timestamp: stamp,
|
|
30163
|
+
summary: c.entry.summary,
|
|
30164
|
+
firstKeptEntryId: kept ?? c.entry.firstKeptEntryId ?? "",
|
|
30165
|
+
tokensBefore: c.entry.tokensBefore ?? 0
|
|
30166
|
+
})
|
|
30167
|
+
);
|
|
30168
|
+
parentId = id;
|
|
30169
|
+
};
|
|
30170
|
+
const byPosition = /* @__PURE__ */ new Map();
|
|
30171
|
+
for (const c of compactions) byPosition.set(c.before, [...byPosition.get(c.before) ?? [], c]);
|
|
30172
|
+
messages.forEach((message2, i) => {
|
|
30173
|
+
for (const c of byPosition.get(i) ?? []) pushCompaction(c);
|
|
30174
|
+
firstEntryOf[i] = n2.toString(16).padStart(8, "0");
|
|
30089
30175
|
if (message2.role === "assistant") {
|
|
30090
30176
|
const content = [];
|
|
30091
30177
|
for (const p of message2.content) {
|
|
@@ -30113,7 +30199,7 @@ function piSessionFile(messages, opts) {
|
|
|
30113
30199
|
stopReason: hasTool ? "toolUse" : "stop",
|
|
30114
30200
|
timestamp: opts.at
|
|
30115
30201
|
});
|
|
30116
|
-
|
|
30202
|
+
return;
|
|
30117
30203
|
}
|
|
30118
30204
|
const results = message2.content.filter(
|
|
30119
30205
|
(p) => p.type === "tool_result"
|
|
@@ -30140,7 +30226,8 @@ function piSessionFile(messages, opts) {
|
|
|
30140
30226
|
});
|
|
30141
30227
|
}
|
|
30142
30228
|
if (rest.length > 0) push({ role: "user", content: rest, timestamp: opts.at });
|
|
30143
|
-
}
|
|
30229
|
+
});
|
|
30230
|
+
for (const c of byPosition.get(messages.length) ?? []) pushCompaction(c);
|
|
30144
30231
|
return lines.join("\n") + "\n";
|
|
30145
30232
|
}
|
|
30146
30233
|
function sessionIdOf(at) {
|
|
@@ -30197,6 +30284,28 @@ var init_mirror = __esm({
|
|
|
30197
30284
|
this.idx += turns.length;
|
|
30198
30285
|
await this.deps.onStep(report2);
|
|
30199
30286
|
}
|
|
30287
|
+
/** pi compacted (session-log item 6): the results pending since the last
|
|
30288
|
+
* assistant turn go as the user turn they would have been, the entry as the
|
|
30289
|
+
* row after them — its own step with nothing in flight, so the log holds
|
|
30290
|
+
* the summary where pi wrote it and the index moves past it. */
|
|
30291
|
+
async onCompaction(entry, turn) {
|
|
30292
|
+
if (!this.deps.onStep) return;
|
|
30293
|
+
const turns = [];
|
|
30294
|
+
if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
|
|
30295
|
+
this.pendingUser = [];
|
|
30296
|
+
const report2 = {
|
|
30297
|
+
turns,
|
|
30298
|
+
firstIdx: this.idx,
|
|
30299
|
+
compaction: entry,
|
|
30300
|
+
inFlight: [],
|
|
30301
|
+
turn,
|
|
30302
|
+
iteration: this.iteration,
|
|
30303
|
+
remainingMs: Math.max(0, this.deps.remainingMs()),
|
|
30304
|
+
inboxConsumedSeq: this.inboxConsumedSeq
|
|
30305
|
+
};
|
|
30306
|
+
this.idx += turns.length + 1;
|
|
30307
|
+
await this.deps.onStep(report2);
|
|
30308
|
+
}
|
|
30200
30309
|
};
|
|
30201
30310
|
}
|
|
30202
30311
|
});
|
|
@@ -30535,25 +30644,33 @@ async function runPiHarness(deps, run2) {
|
|
|
30535
30644
|
let session;
|
|
30536
30645
|
if (run2.resume) {
|
|
30537
30646
|
const settled2 = settlementResults(run2.resume.settlements);
|
|
30538
|
-
session = {
|
|
30647
|
+
session = {
|
|
30648
|
+
stem: "resumed",
|
|
30649
|
+
messages: settled2 ? [...run2.resume.messages, settled2] : run2.resume.messages,
|
|
30650
|
+
compactions: run2.resume.compactions ?? []
|
|
30651
|
+
};
|
|
30539
30652
|
} else {
|
|
30540
30653
|
const earlier2 = splitSeed(run2.messages).session;
|
|
30541
|
-
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2 };
|
|
30654
|
+
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2, compactions: [] };
|
|
30542
30655
|
}
|
|
30543
30656
|
let sessionPath;
|
|
30544
30657
|
if (session) {
|
|
30545
30658
|
sessionPath = `${paths.sessionDir}/${session.stem}-${now()}.jsonl`;
|
|
30546
30659
|
await container.writeFile(
|
|
30547
30660
|
sessionPath,
|
|
30548
|
-
piSessionFile(
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
|
|
30552
|
-
|
|
30553
|
-
|
|
30661
|
+
piSessionFile(
|
|
30662
|
+
session.messages,
|
|
30663
|
+
{
|
|
30664
|
+
cwd: run2.rules.checkout,
|
|
30665
|
+
model: {
|
|
30666
|
+
provider: run2.model.provider,
|
|
30667
|
+
id: run2.model.id,
|
|
30668
|
+
api: run2.model.providerType === "anthropic" ? "anthropic-messages" : "openai-completions"
|
|
30669
|
+
},
|
|
30670
|
+
at: now()
|
|
30554
30671
|
},
|
|
30555
|
-
|
|
30556
|
-
|
|
30672
|
+
session.compactions
|
|
30673
|
+
)
|
|
30557
30674
|
);
|
|
30558
30675
|
}
|
|
30559
30676
|
if (run2.resume) {
|
|
@@ -30682,6 +30799,7 @@ async function runPiHarness(deps, run2) {
|
|
|
30682
30799
|
}
|
|
30683
30800
|
}
|
|
30684
30801
|
if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
|
|
30802
|
+
if (obs.compaction) await mirror.onCompaction(obs.compaction, bridge.turns);
|
|
30685
30803
|
if (obs.providerError !== void 0) {
|
|
30686
30804
|
if (catchingUp)
|
|
30687
30805
|
note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
|
|
@@ -31269,8 +31387,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31269
31387
|
wait,
|
|
31270
31388
|
parentRunId,
|
|
31271
31389
|
coordinator,
|
|
31272
|
-
seed
|
|
31273
|
-
fixRound
|
|
31390
|
+
seed
|
|
31274
31391
|
} = ctx;
|
|
31275
31392
|
const agent = budgetedAgent(ctx.agent, profile);
|
|
31276
31393
|
const { executor, binding } = round2.selection;
|
|
@@ -31373,7 +31490,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31373
31490
|
handoff = h;
|
|
31374
31491
|
ledgerRun?.setState({ handoff: h });
|
|
31375
31492
|
};
|
|
31376
|
-
const restoredDispositions =
|
|
31493
|
+
const restoredDispositions = parseDispositionsInput({ dispositions: restored.dispositions })?.dispositions;
|
|
31377
31494
|
let dispositions = restoredDispositions;
|
|
31378
31495
|
const onDispositions = (d) => {
|
|
31379
31496
|
dispositions = d;
|
|
@@ -31420,7 +31537,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31420
31537
|
onDigest,
|
|
31421
31538
|
onPrDescription,
|
|
31422
31539
|
onHandoff,
|
|
31423
|
-
|
|
31540
|
+
onDispositions
|
|
31424
31541
|
};
|
|
31425
31542
|
const harness = effectiveHarness(agent, deps.config.config.harness);
|
|
31426
31543
|
try {
|
|
@@ -31481,6 +31598,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31481
31598
|
...resume ? {
|
|
31482
31599
|
resume: {
|
|
31483
31600
|
messages: resume.plan.messages,
|
|
31601
|
+
compactions: resume.plan.compactions,
|
|
31484
31602
|
settlements: resume.plan.settlements,
|
|
31485
31603
|
remainingMs: resume.plan.remainingMs,
|
|
31486
31604
|
turn: resume.plan.turn,
|
|
@@ -32060,6 +32178,12 @@ function openPlanCursor(graph, selected) {
|
|
|
32060
32178
|
for (const id of order) visit(id, []);
|
|
32061
32179
|
return cursor;
|
|
32062
32180
|
}
|
|
32181
|
+
function matchDispositions(findings, dispositions) {
|
|
32182
|
+
const issued = new Set(findings.map((f) => f.id));
|
|
32183
|
+
const matched = dispositions.filter((d) => issued.has(d.findingId));
|
|
32184
|
+
const dropped = [...new Set(dispositions.filter((d) => !issued.has(d.findingId)).map((d) => d.findingId))];
|
|
32185
|
+
return { matched, dropped };
|
|
32186
|
+
}
|
|
32063
32187
|
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
32188
|
var init_coordinator = __esm({
|
|
32065
32189
|
"../../src/core/ship/coordinator.ts"() {
|
|
@@ -32082,29 +32206,6 @@ var init_coordinator = __esm({
|
|
|
32082
32206
|
}
|
|
32083
32207
|
});
|
|
32084
32208
|
|
|
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
32209
|
// ../../src/core/ship/reviewChild.ts
|
|
32109
32210
|
function buildShipReviewTurn(input) {
|
|
32110
32211
|
const at = input.headSha ? ` at head \`${input.headSha}\`` : "";
|
|
@@ -32113,13 +32214,15 @@ function buildShipReviewTurn(input) {
|
|
|
32113
32214
|
}
|
|
32114
32215
|
const findings = input.prior.findings.map(formatFinding).join("\n") || "(none recorded)";
|
|
32115
32216
|
const dispositions = input.prior.dispositions.map((d) => `${d.findingId}: ${d.disposition}${d.note ? ` \u2014 ${d.note}` : ""}`).join("\n") || "(none recorded)";
|
|
32217
|
+
const dropped = input.prior.dropped !== void 0 && input.prior.dropped.length > 0 ? `
|
|
32218
|
+
Dispositions naming no finding of the previous round (dropped): ${input.prior.dropped.join(", ")}` : "";
|
|
32116
32219
|
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
32220
|
|
|
32118
32221
|
Previous round's findings:
|
|
32119
32222
|
${findings}
|
|
32120
32223
|
|
|
32121
32224
|
Fix round's dispositions:
|
|
32122
|
-
${dispositions}`;
|
|
32225
|
+
${dispositions}${dropped}`;
|
|
32123
32226
|
}
|
|
32124
32227
|
var init_reviewChild = __esm({
|
|
32125
32228
|
"../../src/core/ship/reviewChild.ts"() {
|
|
@@ -32165,6 +32268,16 @@ async function contractFor(instance, unit, readers) {
|
|
|
32165
32268
|
...issue ? { issue } : {}
|
|
32166
32269
|
});
|
|
32167
32270
|
}
|
|
32271
|
+
function findingsRequest(input) {
|
|
32272
|
+
const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings, address its prose)";
|
|
32273
|
+
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.
|
|
32274
|
+
|
|
32275
|
+
Findings:
|
|
32276
|
+
${findings}
|
|
32277
|
+
|
|
32278
|
+
Review:
|
|
32279
|
+
${input.review}`;
|
|
32280
|
+
}
|
|
32168
32281
|
async function composeChild(brief, instance, unit, readers) {
|
|
32169
32282
|
const where = `${instance.repo}#${"pr" in brief ? brief.pr : ""}`;
|
|
32170
32283
|
switch (brief.kind) {
|
|
@@ -32174,10 +32287,13 @@ async function composeChild(brief, instance, unit, readers) {
|
|
|
32174
32287
|
return { preset: "coding", prompt, ref: unit.branch, contract };
|
|
32175
32288
|
}
|
|
32176
32289
|
case "review": {
|
|
32177
|
-
|
|
32178
|
-
|
|
32179
|
-
|
|
32180
|
-
|
|
32290
|
+
let prior;
|
|
32291
|
+
if (brief.prior !== void 0) {
|
|
32292
|
+
const findings = (await facts(readers, brief.prior.reviewRunId)).findings ?? [];
|
|
32293
|
+
const recorded = brief.prior.codingRunId !== void 0 ? (await facts(readers, brief.prior.codingRunId)).dispositions ?? [] : [];
|
|
32294
|
+
const { matched, dropped } = matchDispositions(findings, recorded);
|
|
32295
|
+
prior = { findings, dispositions: matched, dropped };
|
|
32296
|
+
}
|
|
32181
32297
|
const turn = buildShipReviewTurn({
|
|
32182
32298
|
where,
|
|
32183
32299
|
round: brief.round,
|
|
@@ -32189,16 +32305,18 @@ async function composeChild(brief, instance, unit, readers) {
|
|
|
32189
32305
|
|
|
32190
32306
|
${turn}`, contract };
|
|
32191
32307
|
}
|
|
32192
|
-
case "
|
|
32308
|
+
case "findings": {
|
|
32193
32309
|
const review = await facts(readers, brief.reviewRunId);
|
|
32194
|
-
const findings = review.findings ?? [];
|
|
32195
32310
|
return {
|
|
32196
32311
|
preset: "coding",
|
|
32197
|
-
prompt:
|
|
32198
|
-
ref: unit.branch
|
|
32199
|
-
fixRound: { findingIds: findings.map((f) => f.id) }
|
|
32312
|
+
prompt: findingsRequest({ where, findings: review.findings ?? [], review: review.finalReply ?? "" }),
|
|
32313
|
+
ref: unit.branch
|
|
32200
32314
|
};
|
|
32201
32315
|
}
|
|
32316
|
+
default: {
|
|
32317
|
+
const unknown = brief;
|
|
32318
|
+
throw new Error(`unknown brief kind ${JSON.stringify(unknown.kind)}`);
|
|
32319
|
+
}
|
|
32202
32320
|
}
|
|
32203
32321
|
}
|
|
32204
32322
|
async function facts(readers, runId3) {
|
|
@@ -32211,7 +32329,8 @@ var init_briefs = __esm({
|
|
|
32211
32329
|
"../../src/core/coordinator/briefs.ts"() {
|
|
32212
32330
|
"use strict";
|
|
32213
32331
|
init_directives();
|
|
32214
|
-
|
|
32332
|
+
init_reviewVerdict();
|
|
32333
|
+
init_coordinator();
|
|
32215
32334
|
init_contract();
|
|
32216
32335
|
init_preflight();
|
|
32217
32336
|
init_reviewChild();
|
|
@@ -33060,6 +33179,18 @@ var init_ship = __esm({
|
|
|
33060
33179
|
}
|
|
33061
33180
|
});
|
|
33062
33181
|
|
|
33182
|
+
// ../../src/core/ship/codingChild.ts
|
|
33183
|
+
function withContractInFirstUserTurn(messages, block) {
|
|
33184
|
+
const at = messages.findIndex((m) => m.role === "user");
|
|
33185
|
+
if (at < 0) return [...messages, { role: "user", content: [{ type: "text", text: block }] }];
|
|
33186
|
+
return messages.map((m, i) => i === at ? { ...m, content: [...m.content, { type: "text", text: block }] } : m);
|
|
33187
|
+
}
|
|
33188
|
+
var init_codingChild = __esm({
|
|
33189
|
+
"../../src/core/ship/codingChild.ts"() {
|
|
33190
|
+
"use strict";
|
|
33191
|
+
}
|
|
33192
|
+
});
|
|
33193
|
+
|
|
33063
33194
|
// ../../src/core/dispatch/settle.ts
|
|
33064
33195
|
function settleThread(deps, ctx) {
|
|
33065
33196
|
const { msg, admitted, runLoopStarted, control } = ctx;
|
|
@@ -33647,7 +33778,6 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33647
33778
|
parentRunId,
|
|
33648
33779
|
coordinator,
|
|
33649
33780
|
seed,
|
|
33650
|
-
...opts.fixRound ? { fixRound: opts.fixRound } : {},
|
|
33651
33781
|
...bearer !== void 0 ? { bearer } : {}
|
|
33652
33782
|
});
|
|
33653
33783
|
const { answer, prNote, toolCalls, runDiagnosis, checklistAsLeft, checklistCheckedOff, releaseWorkspace } = ran;
|
|
@@ -33923,7 +34053,12 @@ function toListItem(record2, bytes) {
|
|
|
33923
34053
|
return { ...rest, bytes };
|
|
33924
34054
|
}
|
|
33925
34055
|
function retentionPolicyOf(cfg) {
|
|
33926
|
-
return clampRetentionPolicy({
|
|
34056
|
+
return clampRetentionPolicy({
|
|
34057
|
+
retentionDays: cfg.retentionDays,
|
|
34058
|
+
maxRuns: cfg.maxRuns,
|
|
34059
|
+
maxBytes: cfg.maxBytes,
|
|
34060
|
+
sessionLogMaxBytes: cfg.sessionLogMaxBytes
|
|
34061
|
+
});
|
|
33927
34062
|
}
|
|
33928
34063
|
function buildRunStore(cfg, secrets, deps) {
|
|
33929
34064
|
if (!cfg) return null;
|
|
@@ -34167,6 +34302,24 @@ var init_flusher = __esm({
|
|
|
34167
34302
|
}
|
|
34168
34303
|
});
|
|
34169
34304
|
|
|
34305
|
+
// ../../src/core/runLedger/sessionLog.ts
|
|
34306
|
+
function sessionKey(threadKey, agent) {
|
|
34307
|
+
return `${threadKey}:${agent ?? "-"}`;
|
|
34308
|
+
}
|
|
34309
|
+
function requestIndex(seed) {
|
|
34310
|
+
for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
|
|
34311
|
+
return 0;
|
|
34312
|
+
}
|
|
34313
|
+
var DEFAULT_SESSION_LOG_MAX_BYTES;
|
|
34314
|
+
var init_sessionLog = __esm({
|
|
34315
|
+
"../../src/core/runLedger/sessionLog.ts"() {
|
|
34316
|
+
"use strict";
|
|
34317
|
+
init_runRecord();
|
|
34318
|
+
init_runRecord();
|
|
34319
|
+
DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
|
|
34320
|
+
}
|
|
34321
|
+
});
|
|
34322
|
+
|
|
34170
34323
|
// ../../src/core/runLedger/types.ts
|
|
34171
34324
|
var LEASE_MS, HEARTBEAT_MS, APPEND_FLUSH_MS, APPEND_FLUSH_EVENTS, TRANSCRIPT_REQUEST_BYTES, TRANSCRIPT_PART_BYTES, ATTACHMENT_REF_BYTES, GEN_PATTERN;
|
|
34172
34325
|
var init_types5 = __esm({
|
|
@@ -34212,36 +34365,45 @@ function createLedgerWriteThrough(opts) {
|
|
|
34212
34365
|
"[ledger] state Worker has no run-ledger routes \u2014 deploy it before this bot version; runs are not tracked until then"
|
|
34213
34366
|
);
|
|
34214
34367
|
};
|
|
34215
|
-
async function claim(req,
|
|
34368
|
+
async function claim(req, opts2 = {}) {
|
|
34216
34369
|
for (let attempt = 1; ; attempt++) {
|
|
34217
34370
|
try {
|
|
34371
|
+
let session;
|
|
34372
|
+
if (opts2.seed) {
|
|
34373
|
+
const key = sessionKey(req.threadKey, req.meta.agent);
|
|
34374
|
+
const next = await ledger.sessionTail(key);
|
|
34375
|
+
session = { key, seedFrom: next, request: next + requestIndex(opts2.seed), range: { from: next } };
|
|
34376
|
+
}
|
|
34218
34377
|
const result = await ledger.claim({
|
|
34219
34378
|
runId: req.runId,
|
|
34220
34379
|
threadKey: req.threadKey,
|
|
34221
34380
|
gen,
|
|
34222
34381
|
leaseMs,
|
|
34223
34382
|
startedAt: req.startedAt,
|
|
34224
|
-
meta: req.meta,
|
|
34383
|
+
meta: session ? { ...req.meta, session } : req.meta,
|
|
34225
34384
|
card: req.card ?? null,
|
|
34226
34385
|
system: req.system,
|
|
34227
34386
|
tools: req.tools,
|
|
34228
34387
|
...req.state !== void 0 ? { state: req.state } : {},
|
|
34229
|
-
...phase ? { phase } : {}
|
|
34388
|
+
...opts2.phase ? { phase: opts2.phase } : {}
|
|
34230
34389
|
});
|
|
34231
|
-
if (result.ok)
|
|
34232
|
-
|
|
34390
|
+
if (result.ok) {
|
|
34391
|
+
if (session) await ledger.claimSession(session.key, req.runId, gen);
|
|
34392
|
+
return session ? { outcome: "ok", session } : { outcome: "ok" };
|
|
34393
|
+
}
|
|
34394
|
+
if (result.live.runId === req.runId) return { outcome: "fenced" };
|
|
34233
34395
|
warn(
|
|
34234
34396
|
`[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
34397
|
);
|
|
34236
|
-
return "untracked";
|
|
34398
|
+
return { outcome: "untracked" };
|
|
34237
34399
|
} catch (err2) {
|
|
34238
34400
|
if (err2 instanceof RouteMissingError) {
|
|
34239
34401
|
routeMissing();
|
|
34240
|
-
return "untracked";
|
|
34402
|
+
return { outcome: "untracked" };
|
|
34241
34403
|
}
|
|
34242
34404
|
if (err2 instanceof PermanentStoreError || attempt >= claimAttempts) {
|
|
34243
34405
|
warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${describe8(err2)}`);
|
|
34244
|
-
return "untracked";
|
|
34406
|
+
return { outcome: "untracked" };
|
|
34245
34407
|
}
|
|
34246
34408
|
await sleep2(RETRY_MS[Math.min(attempt, RETRY_MS.length) - 1]);
|
|
34247
34409
|
}
|
|
@@ -34261,6 +34423,15 @@ function createLedgerWriteThrough(opts) {
|
|
|
34261
34423
|
seeded = false;
|
|
34262
34424
|
adopted = false;
|
|
34263
34425
|
handedOff = false;
|
|
34426
|
+
/** The run's place in its session log (session-log item 2); undefined for
|
|
34427
|
+
* a run without a conversation of its own and for an adopted row claimed
|
|
34428
|
+
* before the log existed. */
|
|
34429
|
+
session;
|
|
34430
|
+
/** Turns of the run's conversation on the ledger, counted from its seed —
|
|
34431
|
+
* the last step record's `turnIndex` — so the finish can close the range. */
|
|
34432
|
+
turnsWritten;
|
|
34433
|
+
/** A detach left the log short of what the model saw: the record says `broken`. */
|
|
34434
|
+
broken = false;
|
|
34264
34435
|
stepNo;
|
|
34265
34436
|
lastSeq;
|
|
34266
34437
|
state;
|
|
@@ -34281,7 +34452,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
34281
34452
|
},
|
|
34282
34453
|
...opts.schedule ? { schedule: opts.schedule } : {}
|
|
34283
34454
|
});
|
|
34284
|
-
constructor(req, from = {
|
|
34455
|
+
constructor(req, from = {
|
|
34456
|
+
stepNo: 0,
|
|
34457
|
+
lastSeq: 0
|
|
34458
|
+
}) {
|
|
34285
34459
|
this.runId = req.runId;
|
|
34286
34460
|
this.threadKey = req.threadKey;
|
|
34287
34461
|
this.state = req.state ?? {};
|
|
@@ -34290,6 +34464,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
34290
34464
|
this.stepNo = from.stepNo;
|
|
34291
34465
|
this.lastSeq = from.lastSeq;
|
|
34292
34466
|
this.adopted = from.resumable === true;
|
|
34467
|
+
this.session = from.session;
|
|
34468
|
+
}
|
|
34469
|
+
/** A reservation learns its session when the prompt's claim promotes it. */
|
|
34470
|
+
bindSession(session) {
|
|
34471
|
+
this.session = session;
|
|
34293
34472
|
}
|
|
34294
34473
|
get resumable() {
|
|
34295
34474
|
return !this.detached && (this.seeded || this.adopted);
|
|
@@ -34297,11 +34476,24 @@ function createLedgerWriteThrough(opts) {
|
|
|
34297
34476
|
markHandedOff() {
|
|
34298
34477
|
this.handedOff = true;
|
|
34299
34478
|
}
|
|
34479
|
+
/** The run's range as the finished record carries it (session-log item 2):
|
|
34480
|
+
* closed at the last turn written, or `broken` after a detach — the log
|
|
34481
|
+
* ends short of what the model saw, and the next run in the session must
|
|
34482
|
+
* know. A run that wrote no turn leaves the range open. */
|
|
34483
|
+
sessionOnRecord() {
|
|
34484
|
+
if (!this.session) return void 0;
|
|
34485
|
+
if (this.broken) return { ...this.session, range: "broken" };
|
|
34486
|
+
const from = this.session.range === "broken" ? this.session.seedFrom : this.session.range.from;
|
|
34487
|
+
const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.session.seedFrom + this.turnsWritten - 1 : void 0;
|
|
34488
|
+
return { ...this.session, range: to !== void 0 && to >= from ? { from, to } : { from } };
|
|
34489
|
+
}
|
|
34300
34490
|
sink = {
|
|
34301
|
-
put: async (
|
|
34491
|
+
put: async (plain4) => {
|
|
34302
34492
|
this.finished = true;
|
|
34303
34493
|
live.delete(this);
|
|
34304
34494
|
await this.close();
|
|
34495
|
+
const session = this.sessionOnRecord();
|
|
34496
|
+
const record2 = session ? { ...plain4, session } : plain4;
|
|
34305
34497
|
let result;
|
|
34306
34498
|
try {
|
|
34307
34499
|
result = await ledger.finish(this.runId, gen, record2);
|
|
@@ -34322,6 +34514,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
34322
34514
|
detach(reason) {
|
|
34323
34515
|
if (this.detached) return;
|
|
34324
34516
|
this.detached = true;
|
|
34517
|
+
this.broken = true;
|
|
34325
34518
|
warn(`[ledger] ${this.threadKey} run ${this.runId} detached: ${reason} \u2014 this run is not resumable`);
|
|
34326
34519
|
this.stopHeartbeat();
|
|
34327
34520
|
live.delete(this);
|
|
@@ -34339,13 +34532,15 @@ function createLedgerWriteThrough(opts) {
|
|
|
34339
34532
|
* no record at all was killed before its conversation was stored and
|
|
34340
34533
|
* closes `interrupted`. */
|
|
34341
34534
|
async seed(messages, budgetMs) {
|
|
34342
|
-
const
|
|
34535
|
+
const base = this.session?.seedFrom ?? 0;
|
|
34536
|
+
const turns = messages.map((message2, i) => ({ idx: base + i, message: message2 }));
|
|
34343
34537
|
try {
|
|
34344
|
-
const seeded = await ledger.seed(this.runId, gen, turns);
|
|
34538
|
+
const seeded = await ledger.seed(this.runId, gen, turns, this.session?.key);
|
|
34345
34539
|
if (!seeded.ok) {
|
|
34346
34540
|
this.detach(`seed refused (${seeded.reason})`);
|
|
34347
34541
|
return;
|
|
34348
34542
|
}
|
|
34543
|
+
this.turnsWritten = messages.length;
|
|
34349
34544
|
const recorded = await ledger.step(
|
|
34350
34545
|
this.runId,
|
|
34351
34546
|
gen,
|
|
@@ -34369,11 +34564,14 @@ function createLedgerWriteThrough(opts) {
|
|
|
34369
34564
|
}
|
|
34370
34565
|
async step(report2) {
|
|
34371
34566
|
if (this.detached) return;
|
|
34372
|
-
const
|
|
34567
|
+
const base = this.session?.seedFrom ?? 0;
|
|
34568
|
+
const turns = report2.turns.map((message2, i) => ({ idx: base + report2.firstIdx + i, message: message2 }));
|
|
34569
|
+
if (report2.compaction)
|
|
34570
|
+
turns.push({ idx: base + report2.firstIdx + report2.turns.length, compaction: report2.compaction });
|
|
34373
34571
|
const record2 = {
|
|
34374
34572
|
step: ++this.stepNo,
|
|
34375
34573
|
seq: this.lastSeq,
|
|
34376
|
-
turnIndex: report2.firstIdx +
|
|
34574
|
+
turnIndex: report2.firstIdx + turns.length,
|
|
34377
34575
|
inFlight: report2.inFlight,
|
|
34378
34576
|
inboxConsumedSeq: report2.inboxConsumedSeq,
|
|
34379
34577
|
remainingMs: report2.remainingMs,
|
|
@@ -34382,8 +34580,9 @@ function createLedgerWriteThrough(opts) {
|
|
|
34382
34580
|
};
|
|
34383
34581
|
for (let attempt = 1; ; attempt++) {
|
|
34384
34582
|
try {
|
|
34385
|
-
const result = await ledger.step(this.runId, gen, record2, turns);
|
|
34583
|
+
const result = await ledger.step(this.runId, gen, record2, turns, this.session?.key);
|
|
34386
34584
|
if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
|
|
34585
|
+
else this.turnsWritten = record2.turnIndex;
|
|
34387
34586
|
return;
|
|
34388
34587
|
} catch (err2) {
|
|
34389
34588
|
if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
|
|
@@ -34491,8 +34690,8 @@ function createLedgerWriteThrough(opts) {
|
|
|
34491
34690
|
return {
|
|
34492
34691
|
gen,
|
|
34493
34692
|
async reserve(req) {
|
|
34494
|
-
const claimed2 = await claim({ ...req, system: "", tools: [], state: {} }, "attaching");
|
|
34495
|
-
if (claimed2 !== "ok") return void 0;
|
|
34693
|
+
const claimed2 = await claim({ ...req, system: "", tools: [], state: {} }, { phase: "attaching" });
|
|
34694
|
+
if (claimed2.outcome !== "ok") return void 0;
|
|
34496
34695
|
const run2 = new TrackedRun(req);
|
|
34497
34696
|
run2.startHeartbeat();
|
|
34498
34697
|
live.add(run2);
|
|
@@ -34500,35 +34699,46 @@ function createLedgerWriteThrough(opts) {
|
|
|
34500
34699
|
},
|
|
34501
34700
|
async open(req) {
|
|
34502
34701
|
const reserved = req.reservation;
|
|
34702
|
+
const seed = req.seed?.messages;
|
|
34503
34703
|
if (reserved instanceof TrackedRun) {
|
|
34504
34704
|
if (!reserved.tracked()) return void 0;
|
|
34505
|
-
const claimed3 = await claim(req);
|
|
34506
|
-
if (claimed3 === "fenced") {
|
|
34705
|
+
const claimed3 = await claim(req, seed ? { seed } : {});
|
|
34706
|
+
if (claimed3.outcome === "fenced") {
|
|
34507
34707
|
reserved.detach("promotion refused (fenced)");
|
|
34508
34708
|
return void 0;
|
|
34509
34709
|
}
|
|
34510
|
-
if (claimed3 !== "ok") {
|
|
34710
|
+
if (claimed3.outcome !== "ok") {
|
|
34511
34711
|
await reserved.close();
|
|
34512
34712
|
live.delete(reserved);
|
|
34513
34713
|
return void 0;
|
|
34514
34714
|
}
|
|
34715
|
+
if (claimed3.session) reserved.bindSession(claimed3.session);
|
|
34515
34716
|
if (req.seed) await reserved.seed(req.seed.messages, req.seed.budgetMs);
|
|
34516
34717
|
return reserved;
|
|
34517
34718
|
}
|
|
34518
|
-
const claimed2 = await claim(req);
|
|
34519
|
-
if (claimed2 === "fenced") {
|
|
34719
|
+
const claimed2 = await claim(req, seed ? { seed } : {});
|
|
34720
|
+
if (claimed2.outcome === "fenced") {
|
|
34520
34721
|
warn(`[ledger] ${req.threadKey} not tracked: run ${req.runId} is live under another generation`);
|
|
34521
34722
|
return void 0;
|
|
34522
34723
|
}
|
|
34523
|
-
if (claimed2 !== "ok") return void 0;
|
|
34524
|
-
const run2 = new TrackedRun(req
|
|
34724
|
+
if (claimed2.outcome !== "ok") return void 0;
|
|
34725
|
+
const run2 = new TrackedRun(req, {
|
|
34726
|
+
stepNo: 0,
|
|
34727
|
+
lastSeq: 0,
|
|
34728
|
+
...claimed2.session ? { session: claimed2.session } : {}
|
|
34729
|
+
});
|
|
34525
34730
|
if (req.seed) await run2.seed(req.seed.messages, req.seed.budgetMs);
|
|
34526
34731
|
run2.startHeartbeat();
|
|
34527
34732
|
live.add(run2);
|
|
34528
34733
|
return run2;
|
|
34529
34734
|
},
|
|
34530
34735
|
adopt(req) {
|
|
34531
|
-
const run2 = new TrackedRun(req, {
|
|
34736
|
+
const run2 = new TrackedRun(req, {
|
|
34737
|
+
stepNo: req.lastStep,
|
|
34738
|
+
lastSeq: req.lastSeq,
|
|
34739
|
+
resumable: true,
|
|
34740
|
+
...req.session ? { session: req.session } : {}
|
|
34741
|
+
});
|
|
34532
34742
|
run2.startHeartbeat();
|
|
34533
34743
|
live.add(run2);
|
|
34534
34744
|
return run2;
|
|
@@ -34578,6 +34788,7 @@ var init_writeThrough = __esm({
|
|
|
34578
34788
|
"use strict";
|
|
34579
34789
|
init_runStoreWorker();
|
|
34580
34790
|
init_flusher();
|
|
34791
|
+
init_sessionLog();
|
|
34581
34792
|
init_types5();
|
|
34582
34793
|
NullLedgerWriteThrough = class {
|
|
34583
34794
|
constructor(gen, fallback) {
|
|
@@ -38847,6 +39058,16 @@ function turnRows(idx, message2, opts = {}) {
|
|
|
38847
39058
|
const refBytes = opts.attachmentRefBytes ?? ATTACHMENT_REF_BYTES;
|
|
38848
39059
|
const rows = [];
|
|
38849
39060
|
const attachments = [];
|
|
39061
|
+
if (isCompaction(message2)) {
|
|
39062
|
+
const json2 = JSON.stringify({ compaction: message2.compaction });
|
|
39063
|
+
const bytes = utf8ByteLength(json2);
|
|
39064
|
+
if (bytes > partBytes) {
|
|
39065
|
+
throw new Error(
|
|
39066
|
+
`transcript: the compaction row at turn ${idx} is ${bytes} bytes, over the ${partBytes} row budget`
|
|
39067
|
+
);
|
|
39068
|
+
}
|
|
39069
|
+
return { rows: [{ idx, part: 0, json: json2 }], attachments };
|
|
39070
|
+
}
|
|
38850
39071
|
message2.content.forEach((part, i) => {
|
|
38851
39072
|
let stored = part;
|
|
38852
39073
|
if (hasData(part) && part.data.length > refBytes) {
|
|
@@ -38880,49 +39101,69 @@ function chunkRows(rows, maxBytes) {
|
|
|
38880
39101
|
if (current.length > 0) chunks.push(current);
|
|
38881
39102
|
return chunks;
|
|
38882
39103
|
}
|
|
38883
|
-
function assembleTranscript(rows, attachments) {
|
|
39104
|
+
function assembleTranscript(rows, attachments, base = 0) {
|
|
38884
39105
|
const byRef = new Map(attachments.map((a) => [a.ref, a]));
|
|
38885
39106
|
const byTurn = /* @__PURE__ */ new Map();
|
|
38886
39107
|
for (const row of rows) {
|
|
38887
|
-
|
|
38888
|
-
|
|
39108
|
+
const idx = row.idx - base;
|
|
39109
|
+
let parts = byTurn.get(idx);
|
|
39110
|
+
if (!parts) byTurn.set(idx, parts = /* @__PURE__ */ new Map());
|
|
38889
39111
|
parts.set(row.part, JSON.parse(row.json));
|
|
38890
39112
|
}
|
|
38891
39113
|
const messages = [];
|
|
39114
|
+
const compactions = [];
|
|
39115
|
+
const messageIndexOf = /* @__PURE__ */ new Map();
|
|
39116
|
+
const gap = (why) => ({
|
|
39117
|
+
complete: false,
|
|
39118
|
+
turns: messages.length + compactions.length,
|
|
39119
|
+
messages,
|
|
39120
|
+
compactions,
|
|
39121
|
+
gap: why
|
|
39122
|
+
});
|
|
38892
39123
|
const turnCount = byTurn.size === 0 ? 0 : Math.max(...byTurn.keys()) + 1;
|
|
38893
39124
|
for (let idx = 0; idx < turnCount; idx++) {
|
|
38894
39125
|
const parts = byTurn.get(idx);
|
|
38895
|
-
if (!parts) return
|
|
39126
|
+
if (!parts) return gap(`turn ${idx} is missing`);
|
|
39127
|
+
const first = parts.get(0);
|
|
39128
|
+
if (first && "compaction" in first) {
|
|
39129
|
+
compactions.push({ before: messages.length, entry: first.compaction });
|
|
39130
|
+
continue;
|
|
39131
|
+
}
|
|
38896
39132
|
const content = [];
|
|
38897
39133
|
let role;
|
|
38898
39134
|
const partCount = Math.max(...parts.keys()) + 1;
|
|
38899
39135
|
for (let p = 0; p < partCount; p++) {
|
|
38900
39136
|
const stored = parts.get(p);
|
|
38901
|
-
if (!stored)
|
|
38902
|
-
return { complete: false, turns: messages.length, messages, gap: `turn ${idx} is missing part ${p}` };
|
|
39137
|
+
if (!stored || "compaction" in stored) return gap(`turn ${idx} is missing part ${p}`);
|
|
38903
39138
|
role = stored.role;
|
|
38904
39139
|
const part = stored.part;
|
|
38905
39140
|
if (part.dataRef !== void 0) {
|
|
38906
39141
|
const att = byRef.get(part.dataRef);
|
|
38907
|
-
if (!att)
|
|
38908
|
-
return { complete: false, turns: messages.length, messages, gap: `attachment ${part.dataRef} is missing` };
|
|
39142
|
+
if (!att) return gap(`attachment ${part.dataRef} is missing`);
|
|
38909
39143
|
const { dataRef: _ref, ...rest } = part;
|
|
38910
39144
|
content.push({ ...rest, data: att.data });
|
|
38911
39145
|
} else {
|
|
38912
39146
|
content.push(part);
|
|
38913
39147
|
}
|
|
38914
39148
|
}
|
|
39149
|
+
messageIndexOf.set(idx, messages.length);
|
|
38915
39150
|
messages.push({ role: role ?? "user", content });
|
|
38916
39151
|
}
|
|
38917
|
-
|
|
39152
|
+
for (const c of compactions) {
|
|
39153
|
+
if (c.entry.keptFrom === void 0) continue;
|
|
39154
|
+
const kept = messageIndexOf.get(c.entry.keptFrom - base);
|
|
39155
|
+
if (kept !== void 0) c.keptBefore = kept;
|
|
39156
|
+
}
|
|
39157
|
+
return { complete: true, turns: messages.length + compactions.length, messages, compactions };
|
|
38918
39158
|
}
|
|
38919
|
-
var attachmentRef;
|
|
39159
|
+
var attachmentRef, isCompaction;
|
|
38920
39160
|
var init_transcript = __esm({
|
|
38921
39161
|
"../../src/core/runLedger/transcript.ts"() {
|
|
38922
39162
|
"use strict";
|
|
38923
39163
|
init_runRecord();
|
|
38924
39164
|
init_types5();
|
|
38925
39165
|
attachmentRef = (idx, part) => `t${idx}p${part}`;
|
|
39166
|
+
isCompaction = (v) => "compaction" in v;
|
|
38926
39167
|
}
|
|
38927
39168
|
});
|
|
38928
39169
|
|
|
@@ -38937,6 +39178,9 @@ function buildRunLedger(cfg, secrets, deps = {}) {
|
|
|
38937
39178
|
baseUrl: worker.baseUrl,
|
|
38938
39179
|
token: token2.reveal(),
|
|
38939
39180
|
storeKey: RUN_STORE_KEY,
|
|
39181
|
+
// The same clamped policy the run store proposes, so the session logs and
|
|
39182
|
+
// the run records are bounded by one configuration.
|
|
39183
|
+
sessionLogMaxBytes: retentionPolicyOf(cfg).sessionLogMaxBytes,
|
|
38940
39184
|
...deps.fetch ? { fetch: deps.fetch } : {}
|
|
38941
39185
|
});
|
|
38942
39186
|
}
|
|
@@ -38945,7 +39189,9 @@ var init_runLedgerWorker = __esm({
|
|
|
38945
39189
|
"../../src/core/runLedgerWorker.ts"() {
|
|
38946
39190
|
"use strict";
|
|
38947
39191
|
init_runRecord();
|
|
39192
|
+
init_runStore();
|
|
38948
39193
|
init_runStoreWorker();
|
|
39194
|
+
init_sessionLog();
|
|
38949
39195
|
init_transcript();
|
|
38950
39196
|
init_types5();
|
|
38951
39197
|
WorkerRunLedger = class {
|
|
@@ -39009,41 +39255,94 @@ var init_runLedgerWorker = __esm({
|
|
|
39009
39255
|
}
|
|
39010
39256
|
};
|
|
39011
39257
|
}
|
|
39012
|
-
await this.post("/runs/transcript/owner", { runId: req.runId, gen: req.gen });
|
|
39013
39258
|
return { ok: true };
|
|
39014
39259
|
}
|
|
39015
|
-
|
|
39260
|
+
/** The write route and its body for a run's rows: the session log when the
|
|
39261
|
+
* run has one, the run's own transcript object otherwise. */
|
|
39262
|
+
target(runId3, gen, session) {
|
|
39263
|
+
if (session === void 0) return { path: "/runs/transcript/write", body: { runId: runId3, gen } };
|
|
39264
|
+
this.checkSessionKey(session);
|
|
39265
|
+
return { path: "/runs/session/write", body: { key: session, gen } };
|
|
39266
|
+
}
|
|
39267
|
+
async writeTurns(runId3, gen, turns, session) {
|
|
39016
39268
|
const rows = [];
|
|
39017
39269
|
const attachments = [];
|
|
39018
39270
|
for (const t of turns) {
|
|
39019
|
-
const out = turnRows(t.idx, t.message);
|
|
39271
|
+
const out = turnRows(t.idx, "message" in t ? t.message : { compaction: t.compaction });
|
|
39020
39272
|
rows.push(...out.rows);
|
|
39021
39273
|
attachments.push(...out.attachments);
|
|
39022
39274
|
}
|
|
39275
|
+
const { path, body } = this.target(runId3, gen, session);
|
|
39023
39276
|
for (const a of attachments) {
|
|
39024
|
-
const r = await this.post(
|
|
39277
|
+
const r = await this.post(path, { ...body, rows: [], attachments: [a] });
|
|
39025
39278
|
const f = this.fenceResult(r);
|
|
39026
39279
|
if (!f.ok) return f;
|
|
39027
39280
|
}
|
|
39028
39281
|
const chunks = chunkRows(rows, TRANSCRIPT_REQUEST_BYTES - 4096);
|
|
39029
39282
|
for (const chunk of chunks.length ? chunks : [[]]) {
|
|
39030
39283
|
if (chunk.length === 0 && attachments.length > 0) continue;
|
|
39031
|
-
const r = await this.post(
|
|
39284
|
+
const r = await this.post(path, { ...body, rows: chunk, attachments: [] });
|
|
39032
39285
|
const f = this.fenceResult(r);
|
|
39033
39286
|
if (!f.ok) return f;
|
|
39034
39287
|
}
|
|
39035
39288
|
return { ok: true };
|
|
39036
39289
|
}
|
|
39037
|
-
async seed(runId3, gen, turns) {
|
|
39290
|
+
async seed(runId3, gen, turns, session) {
|
|
39038
39291
|
this.checkIds(runId3, gen);
|
|
39039
|
-
return this.writeTurns(runId3, gen, turns);
|
|
39292
|
+
return this.writeTurns(runId3, gen, turns, session);
|
|
39040
39293
|
}
|
|
39041
|
-
async step(runId3, gen, record2, turns) {
|
|
39294
|
+
async step(runId3, gen, record2, turns, session) {
|
|
39042
39295
|
this.checkIds(runId3, gen);
|
|
39043
|
-
const written = await this.writeTurns(runId3, gen, turns);
|
|
39296
|
+
const written = await this.writeTurns(runId3, gen, turns, session);
|
|
39044
39297
|
if (!written.ok) return written;
|
|
39045
39298
|
return this.fenceResult(await this.post("/runs/step", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 }));
|
|
39046
39299
|
}
|
|
39300
|
+
checkSessionKey(key) {
|
|
39301
|
+
if (!SESSION_KEY_PATTERN.test(key))
|
|
39302
|
+
throw new PermanentStoreError(`run ledger: malformed session key ${JSON.stringify(key)}`);
|
|
39303
|
+
}
|
|
39304
|
+
async sessionTail(key) {
|
|
39305
|
+
this.checkSessionKey(key);
|
|
39306
|
+
const r = await this.post("/runs/session/tail", { key });
|
|
39307
|
+
return typeof r.data.next === "number" ? r.data.next : 0;
|
|
39308
|
+
}
|
|
39309
|
+
async claimSession(key, runId3, gen, maxBytes) {
|
|
39310
|
+
this.checkSessionKey(key);
|
|
39311
|
+
this.checkIds(runId3, gen);
|
|
39312
|
+
await this.post("/runs/session/owner", {
|
|
39313
|
+
key,
|
|
39314
|
+
runId: runId3,
|
|
39315
|
+
gen,
|
|
39316
|
+
maxBytes: maxBytes ?? this.opts.sessionLogMaxBytes ?? DEFAULT_SESSION_LOG_MAX_BYTES
|
|
39317
|
+
});
|
|
39318
|
+
}
|
|
39319
|
+
async releaseSession(key, runId3, gen) {
|
|
39320
|
+
this.checkSessionKey(key);
|
|
39321
|
+
this.checkIds(runId3, gen);
|
|
39322
|
+
return this.fenceResult(await this.post("/runs/session/clear-owner", { key, runId: runId3, gen }));
|
|
39323
|
+
}
|
|
39324
|
+
async readSession(key, from, to) {
|
|
39325
|
+
this.checkSessionKey(key);
|
|
39326
|
+
const r = await this.post("/runs/session/read", { key, from, ...to !== void 0 ? { to } : {} });
|
|
39327
|
+
return assembleTranscript(
|
|
39328
|
+
Array.isArray(r.data.rows) ? r.data.rows : [],
|
|
39329
|
+
Array.isArray(r.data.attachments) ? r.data.attachments : [],
|
|
39330
|
+
from
|
|
39331
|
+
);
|
|
39332
|
+
}
|
|
39333
|
+
async readSessionTail(key, maxBytes) {
|
|
39334
|
+
this.checkSessionKey(key);
|
|
39335
|
+
const r = await this.post("/runs/session/read-tail", { key, maxBytes });
|
|
39336
|
+
const from = typeof r.data.from === "number" ? r.data.from : 0;
|
|
39337
|
+
return {
|
|
39338
|
+
from,
|
|
39339
|
+
transcript: assembleTranscript(
|
|
39340
|
+
Array.isArray(r.data.rows) ? r.data.rows : [],
|
|
39341
|
+
Array.isArray(r.data.attachments) ? r.data.attachments : [],
|
|
39342
|
+
from
|
|
39343
|
+
)
|
|
39344
|
+
};
|
|
39345
|
+
}
|
|
39047
39346
|
async heartbeat(runId3, gen, leaseMs) {
|
|
39048
39347
|
this.checkIds(runId3, gen);
|
|
39049
39348
|
const r = await this.post("/runs/heartbeat", { storeKey: this.opts.storeKey, runId: runId3, gen, leaseMs });
|
|
@@ -39095,22 +39394,23 @@ var init_runLedgerWorker = __esm({
|
|
|
39095
39394
|
const r = await this.post("/runs/finish", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 });
|
|
39096
39395
|
const f = this.fenceResult(r);
|
|
39097
39396
|
if (!f.ok) return f;
|
|
39098
|
-
|
|
39099
|
-
|
|
39397
|
+
if (record2.session)
|
|
39398
|
+
await this.post("/runs/session/clear-owner", { key: record2.session.key, runId: runId3, gen }).catch(() => {
|
|
39399
|
+
});
|
|
39100
39400
|
return { ok: true, stored: r.data.stored === true };
|
|
39101
39401
|
}
|
|
39102
39402
|
async abandon(runId3, gen) {
|
|
39103
39403
|
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;
|
|
39404
|
+
return this.fenceResult(await this.post("/runs/abandon", { storeKey: this.opts.storeKey, runId: runId3, gen }));
|
|
39109
39405
|
}
|
|
39110
39406
|
async reclaim(gen, now, leaseMs) {
|
|
39111
39407
|
const r = await this.post("/runs/reclaim", { storeKey: this.opts.storeKey, gen, now, leaseMs });
|
|
39112
39408
|
const runs = Array.isArray(r.data.runs) ? r.data.runs : [];
|
|
39113
|
-
for (const run2 of runs)
|
|
39409
|
+
for (const run2 of runs) {
|
|
39410
|
+
const session = run2.row.meta?.session;
|
|
39411
|
+
if (session) await this.claimSession(session.key, run2.row.runId, gen);
|
|
39412
|
+
else await this.post("/runs/transcript/owner", { runId: run2.row.runId, gen });
|
|
39413
|
+
}
|
|
39114
39414
|
return runs;
|
|
39115
39415
|
}
|
|
39116
39416
|
async listLive() {
|
|
@@ -39158,6 +39458,116 @@ var init_decisions = __esm({
|
|
|
39158
39458
|
}
|
|
39159
39459
|
});
|
|
39160
39460
|
|
|
39461
|
+
// ../../src/core/runLedger/resume.ts
|
|
39462
|
+
function transcriptSource(meta) {
|
|
39463
|
+
return meta.session ? { kind: "session", key: meta.session.key, from: meta.session.seedFrom } : { kind: "run" };
|
|
39464
|
+
}
|
|
39465
|
+
function settlementFor(toolUse, tools) {
|
|
39466
|
+
const tool = tools.get(toolUse.name);
|
|
39467
|
+
if (!tool) {
|
|
39468
|
+
return {
|
|
39469
|
+
toolUse,
|
|
39470
|
+
action: "synthetic",
|
|
39471
|
+
text: `Tool ${toolUse.name} is not available after the bot restarted; continue without it.`
|
|
39472
|
+
};
|
|
39473
|
+
}
|
|
39474
|
+
if (tool.sideEffectFree || RERUN_SAFE_TOOLS.has(toolUse.name)) return { toolUse, action: "rerun" };
|
|
39475
|
+
return {
|
|
39476
|
+
toolUse,
|
|
39477
|
+
action: "synthetic",
|
|
39478
|
+
text: `The bot restarted while this ${toolUse.name} call was in flight; its effects are unknown \u2014 re-check them before re-running it.`
|
|
39479
|
+
};
|
|
39480
|
+
}
|
|
39481
|
+
function planResume(input) {
|
|
39482
|
+
const { transcript, lastStep } = input;
|
|
39483
|
+
if (!lastStep) return { kind: "interrupted", why: "no step record: killed before its conversation was stored" };
|
|
39484
|
+
if (!transcript.complete) return { kind: "interrupted", why: `transcript incomplete: ${transcript.gap}` };
|
|
39485
|
+
const completeness = transcriptCompleteness({
|
|
39486
|
+
lastStep,
|
|
39487
|
+
seedTurns: lastStep.turnIndex,
|
|
39488
|
+
transcriptTurns: transcript.turns
|
|
39489
|
+
});
|
|
39490
|
+
if (completeness.kind === "interrupted") return completeness;
|
|
39491
|
+
const tools = /* @__PURE__ */ new Map();
|
|
39492
|
+
for (const t of input.tools) tools.set(t.name, t);
|
|
39493
|
+
const messages = transcript.messages;
|
|
39494
|
+
const last = messages[messages.length - 1];
|
|
39495
|
+
const calls = toolUsesOf(last);
|
|
39496
|
+
if (completeness.kind === "resume") {
|
|
39497
|
+
if (lastStep.inFlight.length === 0) {
|
|
39498
|
+
if (last?.role !== "user") {
|
|
39499
|
+
return {
|
|
39500
|
+
kind: "interrupted",
|
|
39501
|
+
why: "transcript ends with an assistant turn but the last step recorded nothing in flight"
|
|
39502
|
+
};
|
|
39503
|
+
}
|
|
39504
|
+
return {
|
|
39505
|
+
kind: "resume",
|
|
39506
|
+
messages,
|
|
39507
|
+
compactions: transcript.compactions,
|
|
39508
|
+
settlements: [],
|
|
39509
|
+
stepRecorded: true,
|
|
39510
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39511
|
+
step: lastStep.step,
|
|
39512
|
+
turn: lastStep.turn,
|
|
39513
|
+
iteration: lastStep.iteration,
|
|
39514
|
+
remainingMs: lastStep.remainingMs
|
|
39515
|
+
};
|
|
39516
|
+
}
|
|
39517
|
+
const recorded = new Set(lastStep.inFlight.map((c) => c.callId));
|
|
39518
|
+
if (calls.length === 0 || calls.length !== recorded.size || !calls.every((c) => recorded.has(c.id))) {
|
|
39519
|
+
return {
|
|
39520
|
+
kind: "interrupted",
|
|
39521
|
+
why: `the last step's calls in flight (${[...recorded].join(", ")}) do not match the transcript's last assistant turn`
|
|
39522
|
+
};
|
|
39523
|
+
}
|
|
39524
|
+
return {
|
|
39525
|
+
kind: "resume",
|
|
39526
|
+
messages,
|
|
39527
|
+
compactions: transcript.compactions,
|
|
39528
|
+
settlements: calls.map((c) => settlementFor(c, tools)),
|
|
39529
|
+
stepRecorded: true,
|
|
39530
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39531
|
+
step: lastStep.step,
|
|
39532
|
+
turn: lastStep.turn,
|
|
39533
|
+
iteration: lastStep.iteration,
|
|
39534
|
+
remainingMs: lastStep.remainingMs
|
|
39535
|
+
};
|
|
39536
|
+
}
|
|
39537
|
+
if (calls.length === 0) {
|
|
39538
|
+
return { kind: "interrupted", why: "the next step's turns landed but its assistant turn has no tool calls" };
|
|
39539
|
+
}
|
|
39540
|
+
const bookkeepingOnly = calls.every((c) => c.name === "update_status");
|
|
39541
|
+
return {
|
|
39542
|
+
kind: "resume",
|
|
39543
|
+
messages,
|
|
39544
|
+
compactions: transcript.compactions,
|
|
39545
|
+
settlements: calls.map((c) => ({ toolUse: c, action: "rerun" })),
|
|
39546
|
+
stepRecorded: false,
|
|
39547
|
+
inboxConsumedSeq: lastStep.inboxConsumedSeq,
|
|
39548
|
+
step: lastStep.step + 1,
|
|
39549
|
+
turn: lastStep.turn + (bookkeepingOnly ? 0 : 1),
|
|
39550
|
+
iteration: lastStep.iteration + 1,
|
|
39551
|
+
remainingMs: lastStep.remainingMs
|
|
39552
|
+
};
|
|
39553
|
+
}
|
|
39554
|
+
var RERUN_SAFE_TOOLS, toolUsesOf;
|
|
39555
|
+
var init_resume = __esm({
|
|
39556
|
+
"../../src/core/runLedger/resume.ts"() {
|
|
39557
|
+
"use strict";
|
|
39558
|
+
init_decisions();
|
|
39559
|
+
RERUN_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
39560
|
+
"write_file",
|
|
39561
|
+
"update_status",
|
|
39562
|
+
"submit_verdict",
|
|
39563
|
+
"submit_pr_description",
|
|
39564
|
+
"submit_handoff",
|
|
39565
|
+
"submit_dispositions"
|
|
39566
|
+
]);
|
|
39567
|
+
toolUsesOf = (message2) => message2?.role === "assistant" ? message2.content.filter((p) => p.type === "tool_use") : [];
|
|
39568
|
+
}
|
|
39569
|
+
});
|
|
39570
|
+
|
|
39161
39571
|
// ../../src/core/boot.ts
|
|
39162
39572
|
function closureNote(agent, prUrl2) {
|
|
39163
39573
|
if (agent === "ship") return shipInterruptedNote(prUrl2);
|
|
@@ -39205,7 +39615,7 @@ async function reclaimRuns(opts) {
|
|
|
39205
39615
|
status3 = "interrupted";
|
|
39206
39616
|
why = "reserved at admission without its request: nothing to restart from";
|
|
39207
39617
|
} else {
|
|
39208
|
-
const verdict = await completenessVerdict(ledger, row
|
|
39618
|
+
const verdict = await completenessVerdict(ledger, row, run2.lastStep);
|
|
39209
39619
|
if (verdict.resumable && run2.lastStep) {
|
|
39210
39620
|
const events2 = await ledger.readEvents(row.runId);
|
|
39211
39621
|
outcome.resumable.push({
|
|
@@ -39306,11 +39716,12 @@ async function closeReclaimed(ledger, gen, input) {
|
|
|
39306
39716
|
const result = await ledger.finish(input.row.runId, gen, record2);
|
|
39307
39717
|
return result.ok ? { ok: true, record: record2 } : { ok: false, reason: result.reason ?? "refused" };
|
|
39308
39718
|
}
|
|
39309
|
-
async function completenessVerdict(ledger,
|
|
39719
|
+
async function completenessVerdict(ledger, row, lastStep) {
|
|
39310
39720
|
if (!lastStep) return { resumable: false, why: "no step record: killed before its conversation was stored" };
|
|
39311
39721
|
let transcript;
|
|
39312
39722
|
try {
|
|
39313
|
-
|
|
39723
|
+
const source2 = transcriptSource(row.meta);
|
|
39724
|
+
transcript = source2.kind === "session" ? await ledger.readSession(source2.key, source2.from) : await ledger.readTranscript(row.runId);
|
|
39314
39725
|
} catch (err2) {
|
|
39315
39726
|
return { resumable: false, why: `transcript unreadable (${describe9(err2)})` };
|
|
39316
39727
|
}
|
|
@@ -39339,6 +39750,7 @@ var init_boot = __esm({
|
|
|
39339
39750
|
"use strict";
|
|
39340
39751
|
init_runStoreWorker();
|
|
39341
39752
|
init_decisions();
|
|
39753
|
+
init_resume();
|
|
39342
39754
|
init_types5();
|
|
39343
39755
|
init_record();
|
|
39344
39756
|
init_shipPipeline();
|
|
@@ -39353,110 +39765,6 @@ var init_boot = __esm({
|
|
|
39353
39765
|
}
|
|
39354
39766
|
});
|
|
39355
39767
|
|
|
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
39768
|
// ../../src/core/resumeLaunch.ts
|
|
39461
39769
|
function knownToolsFor(agent) {
|
|
39462
39770
|
return (TOOLSETS[agent.toolset] ?? []).map((t) => ({
|
|
@@ -40661,9 +40969,12 @@ function parseBrief(v) {
|
|
|
40661
40969
|
const p = b.prior;
|
|
40662
40970
|
if (typeof p !== "object" || p === null || typeof p.reviewRunId !== "string" || !RUN_ID_PATTERN.test(p.reviewRunId))
|
|
40663
40971
|
return invalid("brief.prior.reviewRunId must be a run id");
|
|
40664
|
-
if (p.
|
|
40665
|
-
return invalid("brief.prior.
|
|
40666
|
-
prior = {
|
|
40972
|
+
if (p.codingRunId !== void 0 && (typeof p.codingRunId !== "string" || !RUN_ID_PATTERN.test(p.codingRunId)))
|
|
40973
|
+
return invalid("brief.prior.codingRunId must be a run id");
|
|
40974
|
+
prior = {
|
|
40975
|
+
reviewRunId: p.reviewRunId,
|
|
40976
|
+
...p.codingRunId !== void 0 ? { codingRunId: p.codingRunId } : {}
|
|
40977
|
+
};
|
|
40667
40978
|
}
|
|
40668
40979
|
return {
|
|
40669
40980
|
ok: true,
|
|
@@ -40677,15 +40988,15 @@ function parseBrief(v) {
|
|
|
40677
40988
|
}
|
|
40678
40989
|
};
|
|
40679
40990
|
}
|
|
40680
|
-
case "
|
|
40991
|
+
case "findings": {
|
|
40681
40992
|
const n2 = pr();
|
|
40682
40993
|
if (!n2.ok) return n2;
|
|
40683
40994
|
const review = runId3("reviewRunId");
|
|
40684
40995
|
if (!review.ok) return review;
|
|
40685
|
-
return { ok: true, value: { kind: "
|
|
40996
|
+
return { ok: true, value: { kind: "findings", unit: b.unit, pr: n2.value, reviewRunId: review.value } };
|
|
40686
40997
|
}
|
|
40687
40998
|
default:
|
|
40688
|
-
return invalid("brief.kind must be contract, review or
|
|
40999
|
+
return invalid("brief.kind must be contract, review or findings");
|
|
40689
41000
|
}
|
|
40690
41001
|
}
|
|
40691
41002
|
function parseObject(text) {
|
|
@@ -40748,6 +41059,30 @@ function unitThread(instance, row) {
|
|
|
40748
41059
|
const sourceUrl = row?.sourceUrl ?? (threadKey === instance.threadKey ? instance.sourceUrl : void 0);
|
|
40749
41060
|
return { threadKey, sourceUrl };
|
|
40750
41061
|
}
|
|
41062
|
+
async function openThreadFromRequester(deps, instance, lead, at) {
|
|
41063
|
+
const parent = deps.ioFor({ threadKey: instance.threadKey, userId: instance.userId });
|
|
41064
|
+
if (!parent?.openThread) return { ok: false, response: json(503, { ok: false, error: "no_channel", at }) };
|
|
41065
|
+
try {
|
|
41066
|
+
const opened = await parent.openThread(lead);
|
|
41067
|
+
return {
|
|
41068
|
+
ok: true,
|
|
41069
|
+
thread: {
|
|
41070
|
+
threadKey: opened.thread.threadKey,
|
|
41071
|
+
...opened.thread.sourceUrl !== void 0 ? { sourceUrl: opened.thread.sourceUrl } : {}
|
|
41072
|
+
}
|
|
41073
|
+
};
|
|
41074
|
+
} catch (err2) {
|
|
41075
|
+
return { ok: false, response: json(502, { ok: false, error: "thread_failed", message: describe10(err2), at }) };
|
|
41076
|
+
}
|
|
41077
|
+
}
|
|
41078
|
+
async function ensureReviewThread(deps, instance, row, at) {
|
|
41079
|
+
if (row.reviewThread !== void 0) return { ok: true, row, thread: row.reviewThread };
|
|
41080
|
+
const opened = await openThreadFromRequester(deps, instance, reviewLead(instance, row), at);
|
|
41081
|
+
if (!opened.ok) return opened;
|
|
41082
|
+
const updated = { ...row, reviewThread: opened.thread };
|
|
41083
|
+
await deps.instances.putUnits([updated]);
|
|
41084
|
+
return { ok: true, row: updated, thread: opened.thread };
|
|
41085
|
+
}
|
|
40751
41086
|
function authorizeStep(headers, step, deps) {
|
|
40752
41087
|
const raw = headers.authorization;
|
|
40753
41088
|
const authorization = Array.isArray(raw) ? raw[0] : raw;
|
|
@@ -40836,8 +41171,19 @@ async function spawn6(body, deps) {
|
|
|
40836
41171
|
const at = (deps.clock ?? systemClock)();
|
|
40837
41172
|
const unit = await unitRowOf(deps, instance, req.unit);
|
|
40838
41173
|
if (!unit.ok) return unit.response;
|
|
40839
|
-
const
|
|
40840
|
-
if (
|
|
41174
|
+
const own = unitThread(instance, unit.row);
|
|
41175
|
+
if (own.threadKey === void 0) return json(409, { ok: false, error: "unit_not_started", unit: req.unit, at });
|
|
41176
|
+
let row = unit.row;
|
|
41177
|
+
let thread = {
|
|
41178
|
+
threadKey: own.threadKey,
|
|
41179
|
+
...own.sourceUrl !== void 0 ? { sourceUrl: own.sourceUrl } : {}
|
|
41180
|
+
};
|
|
41181
|
+
if (req.preset === "review" && row !== void 0) {
|
|
41182
|
+
const review = await ensureReviewThread(deps, instance, row, at);
|
|
41183
|
+
if (!review.ok) return review.response;
|
|
41184
|
+
row = review.row;
|
|
41185
|
+
thread = review.thread;
|
|
41186
|
+
}
|
|
40841
41187
|
const threadKey = thread.threadKey;
|
|
40842
41188
|
const key = idempotencyKeyFor(instance.id, req.step);
|
|
40843
41189
|
const live = await liveOnThread(deps.runs, instance, threadKey);
|
|
@@ -40848,10 +41194,9 @@ async function spawn6(body, deps) {
|
|
|
40848
41194
|
if (!io) return json(503, { ok: false, error: "no_channel", at });
|
|
40849
41195
|
let turn;
|
|
40850
41196
|
if (req.brief !== void 0) {
|
|
40851
|
-
if (
|
|
41197
|
+
if (row === void 0) return json(400, { ok: false, error: "a brief needs the unit it runs for" });
|
|
40852
41198
|
try {
|
|
40853
|
-
|
|
40854
|
-
turn = composed;
|
|
41199
|
+
turn = await composeChild(req.brief, instance, row, briefReaders(deps, instance, io));
|
|
40855
41200
|
} catch (err2) {
|
|
40856
41201
|
log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe10(err2)}`);
|
|
40857
41202
|
return json(502, { ok: false, error: "brief_failed", message: describe10(err2), at });
|
|
@@ -40897,8 +41242,7 @@ async function spawn6(body, deps) {
|
|
|
40897
41242
|
};
|
|
40898
41243
|
const settled = deps.dispatch(msg, child, {
|
|
40899
41244
|
coordinator: tag,
|
|
40900
|
-
...turn.contract !== void 0 ? { contract: turn.contract } : {}
|
|
40901
|
-
...turn.fixRound !== void 0 ? { fixRound: turn.fixRound } : {}
|
|
41245
|
+
...turn.contract !== void 0 ? { contract: turn.contract } : {}
|
|
40902
41246
|
}).then(
|
|
40903
41247
|
(outcome) => ({ kind: "ended", outcome }),
|
|
40904
41248
|
(err2) => ({ kind: "threw", err: err2 })
|
|
@@ -41115,30 +41459,32 @@ async function unitStart(body, deps) {
|
|
|
41115
41459
|
...instance.sourceUrl !== void 0 ? { sourceUrl: instance.sourceUrl } : {}
|
|
41116
41460
|
};
|
|
41117
41461
|
} 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
|
-
}
|
|
41462
|
+
const opened = await openThreadFromRequester(deps, instance, unitLead(instance, row), at);
|
|
41463
|
+
if (!opened.ok) return opened.response;
|
|
41464
|
+
row = { ...row, ...opened.thread };
|
|
41130
41465
|
}
|
|
41131
41466
|
}
|
|
41467
|
+
if (row.reviewThread === void 0) {
|
|
41468
|
+
const review = await ensureReviewThread(deps, instance, row, at);
|
|
41469
|
+
if (!review.ok) {
|
|
41470
|
+
if (row !== unit.row) await deps.instances.putUnits([row]);
|
|
41471
|
+
return review.response;
|
|
41472
|
+
}
|
|
41473
|
+
row = review.row;
|
|
41474
|
+
}
|
|
41132
41475
|
if (row.issue === void 0 && row.unit !== TASK_UNIT) {
|
|
41133
41476
|
const issue = await unitIssueOf(deps, instance.repo, row.unit);
|
|
41134
41477
|
if (issue !== void 0) row = { ...row, issue };
|
|
41135
41478
|
}
|
|
41136
41479
|
row = { ...row, startedAt: row.startedAt ?? at };
|
|
41137
41480
|
await deps.instances.putUnits([row]);
|
|
41138
|
-
(deps.log ?? console.log)(
|
|
41481
|
+
(deps.log ?? console.log)(
|
|
41482
|
+
`[coordinator] ${instance.id} ${row.unit}: started in ${row.threadKey}, review in ${row.reviewThread?.threadKey}`
|
|
41483
|
+
);
|
|
41139
41484
|
return json(200, {
|
|
41140
41485
|
ok: true,
|
|
41141
41486
|
threadKey: row.threadKey,
|
|
41487
|
+
reviewThreadKey: row.reviewThread?.threadKey,
|
|
41142
41488
|
branch: row.branch,
|
|
41143
41489
|
base: instance.base ?? "main",
|
|
41144
41490
|
...row.issue !== void 0 ? { issue: row.issue } : {},
|
|
@@ -41150,6 +41496,12 @@ function unitLead(instance, row) {
|
|
|
41150
41496
|
const from = instance.sourceUrl !== void 0 ? `[the *ship* run](${instance.sourceUrl})` : "the *ship* run";
|
|
41151
41497
|
return `\u21B3 *ship* unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""} for ${who2}, from ${from}: \`${row.branch}\` in ${instance.repo}`;
|
|
41152
41498
|
}
|
|
41499
|
+
function reviewLead(instance, row) {
|
|
41500
|
+
const who2 = instance.userName ?? instance.userId;
|
|
41501
|
+
const from = instance.sourceUrl !== void 0 ? `[the *ship* run](${instance.sourceUrl})` : "the *ship* run";
|
|
41502
|
+
const what = row.unit === TASK_UNIT ? "the task" : `unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""}`;
|
|
41503
|
+
return `\u21B3 *ship* review of ${what} for ${who2}, from ${from}: \`${row.branch}\` in ${instance.repo}`;
|
|
41504
|
+
}
|
|
41153
41505
|
async function branch(body, deps) {
|
|
41154
41506
|
const id = parseInstanceId(body.parentInstanceId);
|
|
41155
41507
|
if (!id.ok) return json(400, { ok: false, error: id.error });
|
|
@@ -41584,7 +41936,7 @@ var init_adminCoordinator = __esm({
|
|
|
41584
41936
|
PRESETS_OF_KIND = {
|
|
41585
41937
|
contract: "coding",
|
|
41586
41938
|
review: "review",
|
|
41587
|
-
|
|
41939
|
+
findings: "coding"
|
|
41588
41940
|
};
|
|
41589
41941
|
invalid = (error) => ({ ok: false, error });
|
|
41590
41942
|
json = (status3, body) => ({ status: status3, body });
|