@iowarp/clio-coder 0.3.7 → 0.3.8
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/CHANGELOG.md +31 -0
- package/README.md +7 -2
- package/dist/{acp-SK4MD6MM.js → acp-U67UHUK2.js} +13 -13
- package/dist/{agents-2FN2K6ME.js → agents-YU6SGALZ.js} +35 -33
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-QIYZWM5I.js → auth-5ZPJOIVG.js} +32 -24
- package/dist/builtins-C6JMZVV6.js +17 -0
- package/dist/{chunk-YD734TPH.js → chunk-26LEYJZH.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
- package/dist/{chunk-WJHBC77E.js → chunk-2HFZQUHL.js} +5 -5
- package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
- package/dist/{chunk-EOOQZZDE.js → chunk-4SPRNWDE.js} +18 -18
- package/dist/{chunk-EBEFWSGL.js → chunk-5DHKRSMQ.js} +7 -7
- package/dist/{chunk-OBMAI2DP.js → chunk-5H3GB5BO.js} +11 -9
- package/dist/{chunk-JTSEDYVQ.js → chunk-5Q2VVUKB.js} +7 -7
- package/dist/{chunk-VQNODYQ4.js → chunk-7RFXX52T.js} +294 -3704
- package/dist/{chunk-XEGB6BCN.js → chunk-7RGZWPB6.js} +5 -5
- package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-CEYBNUGC.js → chunk-A3WNZD3P.js} +323 -27
- package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
- package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
- package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
- package/dist/{chunk-KCMKRQX4.js → chunk-E77JEWSD.js} +42 -49
- package/dist/{chunk-J7PIKKWC.js → chunk-EMYUUSFG.js} +7 -7
- package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
- package/dist/{chunk-UVDSQ6LW.js → chunk-FBVTI2TJ.js} +74 -28
- package/dist/{chunk-BMWK7ZIZ.js → chunk-FHJEP5SW.js} +13 -13
- package/dist/{chunk-FOT2FX5J.js → chunk-GN57SG4G.js} +7 -7
- package/dist/{chunk-SROCI7ZU.js → chunk-GPIEI3LY.js} +5 -5
- package/dist/{chunk-UFQ3F4FW.js → chunk-GU2UIAFZ.js} +4 -4
- package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
- package/dist/{chunk-VREKEFLL.js → chunk-HLE42MG7.js} +3 -3
- package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-LADCF22A.js → chunk-IGWKHNIQ.js} +113 -54
- package/dist/chunk-IIZWH4XA.js +172 -0
- package/dist/{chunk-UHXRNZ2J.js → chunk-IJ7RPIYJ.js} +4 -4
- package/dist/{chunk-M4AKACEO.js → chunk-J3YUBZWY.js} +2 -2
- package/dist/{chunk-AB4XIIVB.js → chunk-JOZYP4GM.js} +6 -6
- package/dist/{chunk-ZGH7FGS5.js → chunk-K4XHGFR5.js} +742 -12
- package/dist/{chunk-EELBMBT6.js → chunk-KTYTFRMB.js} +62 -5
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/{chunk-DQA7QLMD.js → chunk-ME6CCNFO.js} +9 -24
- package/dist/{chunk-KZ2H5X4G.js → chunk-MXKJU4JB.js} +88 -14
- package/dist/{chunk-DMD2AGVS.js → chunk-N22QMJKY.js} +20 -18
- package/dist/chunk-NMPKI6XL.js +3006 -0
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-5C77SEEY.js → chunk-P43ETTHK.js} +2 -2
- package/dist/{chunk-JNXPYBB4.js → chunk-PMDBGQSJ.js} +2 -2
- package/dist/chunk-PT7HYKEM.js +165 -0
- package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
- package/dist/{chunk-C4JBQ5SR.js → chunk-TANS5ZJS.js} +6 -6
- package/dist/{chunk-YTYFXUI3.js → chunk-TB5666IT.js} +9 -9
- package/dist/{chunk-UUANF5CR.js → chunk-TLQJPP24.js} +375 -687
- package/dist/{chunk-OB5HIGJY.js → chunk-TT36MB5S.js} +2 -1
- package/dist/{chunk-DJNLUABN.js → chunk-TTHACPOM.js} +135 -17
- package/dist/{chunk-6M7VS3J3.js → chunk-TYPGUK6W.js} +4 -4
- package/dist/{chunk-THKY7CD7.js → chunk-U6MBIEMB.js} +108 -24
- package/dist/{chunk-PD3MESLB.js → chunk-VAWNZU7Z.js} +4 -4
- package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
- package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
- package/dist/{chunk-MXI6J5JF.js → chunk-VWZOAB7K.js} +10 -10
- package/dist/{chunk-QCTRSGHQ.js → chunk-WLFILSD5.js} +36 -36
- package/dist/{chunk-5UJ6ECTS.js → chunk-WNIJTQQK.js} +6 -6
- package/dist/{chunk-4DWFMQDR.js → chunk-WSB3FPX7.js} +68 -20
- package/dist/{chunk-3HAPLH5M.js → chunk-WWCZ5F23.js} +112 -10
- package/dist/{chunk-X2KV5FXT.js → chunk-WXY7KU3G.js} +2 -2
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-DOOEX22V.js → chunk-XWSF374K.js} +4 -4
- package/dist/{chunk-6TUKSZVF.js → chunk-YS5VLNH5.js} +8 -8
- package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
- package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
- package/dist/cli/index.js +31 -31
- package/dist/{clio-WBVQEBKO.js → clio-QVTYJ57A.js} +8 -8
- package/dist/code-nav-FGGFIE7L.js +2 -2
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
- package/dist/{config-TRBL3RCF.js → config-LW5IJFQN.js} +58 -53
- package/dist/{configure-OLCVPHNM.js → configure-7XIZCOU4.js} +26 -21
- package/dist/{context-XEWE3MOJ.js → context-L3WL3X7K.js} +47 -41
- package/dist/{context-WFPKQSM6.js → context-N52ZA626.js} +22 -22
- package/dist/{context-MJIJ6GOX.js → context-Y6Y7QPR6.js} +12 -12
- package/dist/{context-clear-KNOS2JPB.js → context-clear-MBQRLSDQ.js} +47 -41
- package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
- package/dist/{context-working-set-EUXAZI6N.js → context-working-set-GS6DSO7F.js} +14 -14
- package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-22ZCNOM3.js} +83 -55
- package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
- package/dist/{doctor-RN4YKO2X.js → doctor-M7YEDGAE.js} +24 -20
- package/dist/{eval-RUBJVSNQ.js → eval-BEC2WHDA.js} +71 -19
- package/dist/{evidence-JZNBUOQZ.js → evidence-REJUMSKM.js} +62 -56
- package/dist/{evolve-FJVC4KKI.js → evolve-PY5ZBA5K.js} +41 -35
- package/dist/{extensions-IQL36S7K.js → extensions-HVKU65YU.js} +6 -6
- package/dist/{fleet-BDKYJFCP.js → fleet-7WZEWRFA.js} +72 -67
- package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-UVHWM76J.js} +10 -10
- package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-6ULH7PES.js} +13 -13
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
- package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-72PC4SLA.js} +17 -17
- package/dist/{init-LQUB5COQ.js → init-OG3TPGQG.js} +61 -56
- package/dist/{library-NJAHIGG4.js → library-CNTMPLRF.js} +18 -18
- package/dist/{memory-OG6HOYKM.js → memory-6IS7F275.js} +43 -37
- package/dist/{models-5ZG5XY7J.js → models-ENRJDA5W.js} +33 -28
- package/dist/{monitor-TJ7AMTGB.js → monitor-XLDVO7TN.js} +56 -38
- package/dist/{orchestrator-WZYB54DM.js → orchestrator-6KSPYRHA.js} +421 -303
- package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-PXQT45IY.js → reset-RZ4ER727.js} +10 -10
- package/dist/{run-FQ74YF62.js → run-Y2CNK5RU.js} +79 -74
- package/dist/{share-FW7SVCL3.js → share-A55GYP6Z.js} +16 -16
- package/dist/{skills-7E7IRB3R.js → skills-ALC5J6AT.js} +19 -19
- package/dist/{skills-eval-LI75W6OK.js → skills-eval-JPBEBYQU.js} +52 -47
- package/dist/support-MIETYA5E.js +38 -0
- package/dist/{targets-4CIFKCTW.js → targets-VGNXIR3S.js} +42 -33
- package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-WOBR64YA.js} +3 -3
- package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
- package/dist/{upgrade-K2HVIVMQ.js → upgrade-FUSUAGHR.js} +27 -25
- package/dist/{usage-GTZELZQX.js → usage-N4MKVHKD.js} +51 -46
- package/dist/{verifiers-RLAHT27O.js → verifiers-YAWOJ3H2.js} +13 -13
- package/dist/{verify-BX3BRKH5.js → verify-LTDHYBGY.js} +9 -9
- package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-6M7GHTBJ.js} +66 -61
- package/dist/worker/entry.js +68 -61
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/artifact-versions.md +6 -5
- package/docs/built-in-agents.md +1 -1
- package/docs/commands-and-modes.md +2 -2
- package/docs/configuration-and-targets.md +8 -3
- package/docs/context-engine.md +1 -1
- package/docs/documentation-guide.md +1 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +70 -6
- package/docs/evolution.md +1 -1
- package/docs/extensions-and-sharing.md +1 -1
- package/docs/fleet-dispatch.md +34 -9
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +41 -38
- package/docs/safety-model.md +1 -1
- package/docs/scientific-validation.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +1 -1
- package/docs/trace-store.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +1 -2
- package/src/cli/argv.ts +5 -0
- package/src/cli/configure.ts +107 -23
- package/src/cli/doctor.ts +5 -1
- package/src/cli/evidence.ts +30 -25
- package/src/cli/fleet-preflight.ts +2 -12
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +4 -1
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +25 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/path-boundary.ts +100 -0
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +30 -12
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +128 -17
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/contract.ts +15 -1
- package/src/domains/dispatch/delegation-plan.ts +2 -5
- package/src/domains/dispatch/execution-role.ts +9 -1
- package/src/domains/dispatch/extension.ts +156 -67
- package/src/domains/dispatch/fleet-run.ts +57 -3
- package/src/domains/dispatch/gate-role-prompts.ts +38 -0
- package/src/domains/dispatch/index.ts +3 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +84 -8
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +2 -1
- package/src/domains/dispatch/types.ts +14 -7
- package/src/domains/dispatch/validation.ts +6 -3
- package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
- package/src/domains/dispatch/write-boundary.ts +201 -22
- package/src/domains/eval/metrics/evidence.ts +79 -2
- package/src/domains/eval/runners/clio-run.ts +12 -2
- package/src/domains/evidence/build.ts +69 -11
- package/src/domains/evidence/index.ts +21 -0
- package/src/domains/evidence/provenance.ts +46 -11
- package/src/domains/evidence/trust-projection.ts +274 -0
- package/src/domains/evidence/trust-status.ts +145 -17
- package/src/domains/evidence/types.ts +4 -0
- package/src/domains/extensions/discovery.ts +88 -1
- package/src/domains/extensions/resources.ts +20 -8
- package/src/domains/extensions/state.ts +6 -2
- package/src/domains/extensions/types.ts +4 -1
- package/src/domains/lifecycle/doctor.ts +140 -1
- package/src/domains/prompts/contract.ts +3 -5
- package/src/domains/providers/extension.ts +30 -2
- package/src/domains/resources/common-loader.ts +3 -0
- package/src/domains/resources/prompts/loader.ts +119 -14
- package/src/domains/safety/policy-engine.ts +5 -5
- package/src/domains/safety/run-effects.ts +64 -1
- package/src/domains/safety/scope.ts +7 -12
- package/src/engine/acp/server.ts +4 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/interactive/dispatch-board.ts +53 -3
- package/src/interactive/interactive-event-projection.ts +14 -0
- package/src/interactive/overlays/settings.ts +141 -47
- package/src/interactive/slash-commands.ts +7 -2
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +15 -3
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-stream.ts +8 -0
- package/src/tools/dispatch-admission.ts +12 -13
- package/src/tools/dispatch-arguments.ts +27 -0
- package/src/tools/dispatch-plan.ts +29 -0
- package/src/tools/dispatch-runner.ts +48 -13
- package/src/tools/monitor.ts +13 -0
- package/src/tools/worker-evidence.ts +19 -13
- package/src/worker/spec-contract.ts +2 -1
|
@@ -203,12 +203,62 @@ function ruleMatchesAnyPath(rule: ProjectRule, workingPaths: ReadonlyArray<strin
|
|
|
203
203
|
continue;
|
|
204
204
|
}
|
|
205
205
|
for (const path of workingPaths) {
|
|
206
|
-
|
|
206
|
+
const normalized = normalizeGlobInput(path);
|
|
207
|
+
if (regex.test(normalized)) return true;
|
|
208
|
+
if (normalized.endsWith("/") && globMatchesInsideDirectory(regex, pattern, normalized)) return true;
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
return false;
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
function materializedGlobVariants(pattern: string): string[] {
|
|
215
|
+
let variants = [""];
|
|
216
|
+
const normalized = normalizeGlobInput(pattern);
|
|
217
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
218
|
+
const character = normalized[index];
|
|
219
|
+
if (character === undefined) break;
|
|
220
|
+
if (character === "*" && normalized[index + 1] === "*" && normalized[index + 2] === "/") {
|
|
221
|
+
variants = variants.flatMap((prefix) => [prefix, `${prefix}x/`]).slice(0, 64);
|
|
222
|
+
index += 2;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (character === "*" && normalized[index + 1] === "*") {
|
|
226
|
+
variants = variants.map((prefix) => `${prefix}x`);
|
|
227
|
+
index += 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (character === "*" || character === "?") {
|
|
231
|
+
variants = variants.map((prefix) => `${prefix}x`);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (character === "[") {
|
|
235
|
+
const end = normalized.indexOf("]", index + 1);
|
|
236
|
+
const selected = end === -1 ? "x" : (normalized[index + 1] ?? "x");
|
|
237
|
+
variants = variants.map((prefix) => `${prefix}${selected}`);
|
|
238
|
+
index = end === -1 ? index : end;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
variants = variants.map((prefix) => `${prefix}${character}`);
|
|
242
|
+
}
|
|
243
|
+
return variants;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function globMatchesInsideDirectory(regex: RegExp, pattern: string, directory: string): boolean {
|
|
247
|
+
const candidates = new Set<string>([`${directory}x`, `${directory}x/x`]);
|
|
248
|
+
for (const materialized of materializedGlobVariants(pattern)) {
|
|
249
|
+
if (materialized.startsWith(directory)) candidates.add(materialized);
|
|
250
|
+
const segments = materialized.split("/");
|
|
251
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
252
|
+
const suffix = segments.slice(index).join("/");
|
|
253
|
+
if (suffix.length > 0) candidates.add(`${directory}${suffix}`);
|
|
254
|
+
}
|
|
255
|
+
for (let index = 0; index < materialized.length; index += 1) {
|
|
256
|
+
candidates.add(`${directory}${materialized.slice(index)}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return [...candidates].some((candidate) => regex.test(candidate));
|
|
260
|
+
}
|
|
261
|
+
|
|
212
262
|
/**
|
|
213
263
|
* Select the rules that should load now. Unconditional rules (no `paths`) always
|
|
214
264
|
* load; a path-scoped rule loads only when one of its globs matches a path
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Startup reconciliation for durable assignments. The in-memory retry queue and
|
|
3
|
-
* assignment registry do not survive a restart (Symphony §14.3), so
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* assignment registry do not survive a restart (Symphony §14.3), so a durable
|
|
4
|
+
* record whose process owner is gone is orphaned: no active attempt, no queued
|
|
5
|
+
* retry, and no attached terminal promise can ever settle it. Running records
|
|
6
|
+
* owned by a live sibling process are deliberately left alone.
|
|
6
7
|
*
|
|
7
8
|
* This pass resolves each orphan deterministically against durable ledger
|
|
8
9
|
* evidence so a later `wait`/`collect` can never observe a stuck `running`
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import type { AssignmentStatus } from "./assignment.js";
|
|
14
|
-
import type { DurableAssignmentRecord } from "./assignment-store.js";
|
|
15
|
+
import type { AssignmentVerdictOwner, DurableAssignmentRecord } from "./assignment-store.js";
|
|
15
16
|
|
|
16
17
|
/** Minimal terminal view of one attempt's durable ledger row. */
|
|
17
18
|
export interface ReconcileAttemptView {
|
|
@@ -25,13 +26,16 @@ export interface ReconcileAttemptView {
|
|
|
25
26
|
export interface AssignmentReconcileDeps {
|
|
26
27
|
/** Durable records currently marked `running`. */
|
|
27
28
|
listRunning: () => ReadonlyArray<DurableAssignmentRecord>;
|
|
29
|
+
/** Decide liveness from the record's durable pid and process birth token. */
|
|
30
|
+
ownerAlive: (record: DurableAssignmentRecord) => boolean;
|
|
28
31
|
/** Resolve one attempt's terminal view from the ledger, or null if pruned. */
|
|
29
32
|
lookupAttempt: (runId: string) => ReconcileAttemptView | null;
|
|
30
|
-
/** Persist the reconciled terminal status. */
|
|
33
|
+
/** Persist the reconciled terminal status, as the record's verdict owner when it has one. */
|
|
31
34
|
settle: (
|
|
32
35
|
assignmentId: string,
|
|
33
36
|
terminalRunId: string,
|
|
34
37
|
status: Exclude<AssignmentStatus, "running">,
|
|
38
|
+
owner?: AssignmentVerdictOwner,
|
|
35
39
|
) => Promise<unknown>;
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -44,6 +48,10 @@ export interface AssignmentReconcileSummary {
|
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* Deterministic rule, in priority order:
|
|
51
|
+
* -1. a record owned by a live process is not an orphan and remains running;
|
|
52
|
+
* 0. a record whose verdict belongs to its opener is failed (abandoned): the
|
|
53
|
+
* opener died before reaching a verdict, and a green attempt under it is
|
|
54
|
+
* one step of a run, never the run's answer;
|
|
47
55
|
* 1. a recovered succeeded terminal attempt wins (`succeeded`);
|
|
48
56
|
* 2. otherwise the last terminal attempt marks the assignment `failed`;
|
|
49
57
|
* 3. otherwise no attempt is recoverable and the assignment is failed
|
|
@@ -53,6 +61,15 @@ export async function reconcileOrphanAssignments(deps: AssignmentReconcileDeps):
|
|
|
53
61
|
const summary: AssignmentReconcileSummary = { recovered: 0, abandoned: 0 };
|
|
54
62
|
for (const record of deps.listRunning()) {
|
|
55
63
|
if (record.status !== "running") continue;
|
|
64
|
+
if (deps.ownerAlive(record)) continue;
|
|
65
|
+
if (record.verdictOwner !== undefined) {
|
|
66
|
+
// Attempts under a claimed row are fleet steps, not the fleet verdict.
|
|
67
|
+
// Filing a green step as the terminal id of an abandoned fleet would
|
|
68
|
+
// contradict that step's immutable ledger result.
|
|
69
|
+
await deps.settle(record.assignmentId, record.assignmentId, "failed", record.verdictOwner);
|
|
70
|
+
summary.abandoned += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
56
73
|
const views = record.attempts
|
|
57
74
|
.map((runId) => deps.lookupAttempt(runId))
|
|
58
75
|
.filter((view): view is ReconcileAttemptView => view !== null);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { BIRTH_TOKEN_SOURCE_AVAILABLE, processAlive, processBirthToken } from "../../core/process-identity.js";
|
|
8
9
|
import { withStateFileLock } from "../../core/state-file-lock.js";
|
|
9
10
|
import { clioStateDir } from "../../core/xdg.js";
|
|
10
11
|
import { atomicWrite } from "../../engine/session.js";
|
|
@@ -12,11 +13,54 @@ import type { AssignmentStatus } from "./assignment.js";
|
|
|
12
13
|
|
|
13
14
|
const MAX_ASSIGNMENT_RECORDS = 1_000;
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Who decides a record's status.
|
|
18
|
+
*
|
|
19
|
+
* Absent is the ordinary case: the record is one logical dispatch, so the
|
|
20
|
+
* attempts filed under it are the only thing that can settle it. A fleet claims
|
|
21
|
+
* the verdict instead, because a fleet root gathers every step of a run under
|
|
22
|
+
* one id and no single step is the run's answer. Without the claim the last
|
|
23
|
+
* step to settle writes the row, which reports a green step of an aborted run
|
|
24
|
+
* as the run's outcome.
|
|
25
|
+
*/
|
|
26
|
+
export type AssignmentVerdictOwner = "fleet";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Durable identity of the process executing a running assignment. A pid alone
|
|
30
|
+
* is not enough: the OS may recycle it after a crash, so the process birth
|
|
31
|
+
* token must still match before another extension treats the owner as live.
|
|
32
|
+
*/
|
|
33
|
+
export interface AssignmentProcessOwner {
|
|
34
|
+
pid: number;
|
|
35
|
+
processBirthToken: string;
|
|
36
|
+
acquiredAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AssignmentProcessOwnerProbe {
|
|
40
|
+
birthToken(pid: number): string | null;
|
|
41
|
+
/** True when a missing birth token proves that the process is gone. */
|
|
42
|
+
tokenProvesDeath?: boolean;
|
|
43
|
+
/** Liveness fallback for platforms whose birth token is synthetic. */
|
|
44
|
+
alive?(pid: number): boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One durable logical-dispatch row.
|
|
49
|
+
*
|
|
50
|
+
* `attempts` holds terminal run ids: an agent attempt's receipt id, and for a
|
|
51
|
+
* fleet also each deterministic step's `code-*` run id, whose evidence lives
|
|
52
|
+
* under `code-steps/<assignmentId>/`. `status` answers for the whole record,
|
|
53
|
+
* so for a fleet it answers for the whole run rather than for its last step.
|
|
54
|
+
*/
|
|
15
55
|
export interface DurableAssignmentRecord {
|
|
16
56
|
assignmentId: string;
|
|
17
57
|
attempts: string[];
|
|
18
58
|
terminalRunId: string | null;
|
|
19
59
|
status: AssignmentStatus;
|
|
60
|
+
/** Set when something other than the attempts decides `status`. */
|
|
61
|
+
verdictOwner?: AssignmentVerdictOwner;
|
|
62
|
+
/** Process lease held only while this record is running. */
|
|
63
|
+
processOwner?: AssignmentProcessOwner;
|
|
20
64
|
}
|
|
21
65
|
|
|
22
66
|
interface AssignmentStoreFile {
|
|
@@ -28,6 +72,18 @@ function storePath(): string {
|
|
|
28
72
|
return join(clioStateDir(), "assignments.json");
|
|
29
73
|
}
|
|
30
74
|
|
|
75
|
+
function validProcessOwner(value: unknown): value is AssignmentProcessOwner {
|
|
76
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
77
|
+
const owner = value as Partial<AssignmentProcessOwner>;
|
|
78
|
+
return (
|
|
79
|
+
Number.isSafeInteger(owner.pid) &&
|
|
80
|
+
(owner.pid ?? 0) > 0 &&
|
|
81
|
+
typeof owner.processBirthToken === "string" &&
|
|
82
|
+
typeof owner.acquiredAt === "string" &&
|
|
83
|
+
Number.isFinite(Date.parse(owner.acquiredAt))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
31
87
|
function validRecord(value: unknown): value is DurableAssignmentRecord {
|
|
32
88
|
if (typeof value !== "object" || value === null) return false;
|
|
33
89
|
const record = value as Partial<DurableAssignmentRecord>;
|
|
@@ -36,6 +92,8 @@ function validRecord(value: unknown): value is DurableAssignmentRecord {
|
|
|
36
92
|
Array.isArray(record.attempts) &&
|
|
37
93
|
record.attempts.every((runId) => typeof runId === "string") &&
|
|
38
94
|
(record.terminalRunId === null || typeof record.terminalRunId === "string") &&
|
|
95
|
+
(record.verdictOwner === undefined || record.verdictOwner === "fleet") &&
|
|
96
|
+
(record.processOwner === undefined || validProcessOwner(record.processOwner)) &&
|
|
39
97
|
(record.status === "running" ||
|
|
40
98
|
record.status === "succeeded" ||
|
|
41
99
|
record.status === "failed" ||
|
|
@@ -57,7 +115,32 @@ function readStore(): DurableAssignmentRecord[] {
|
|
|
57
115
|
}
|
|
58
116
|
|
|
59
117
|
function copyRecord(record: DurableAssignmentRecord): DurableAssignmentRecord {
|
|
60
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
...record,
|
|
120
|
+
attempts: [...record.attempts],
|
|
121
|
+
...(record.processOwner === undefined ? {} : { processOwner: { ...record.processOwner } }),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const defaultProcessOwnerProbe: AssignmentProcessOwnerProbe = {
|
|
126
|
+
birthToken: processBirthToken,
|
|
127
|
+
tokenProvesDeath: BIRTH_TOKEN_SOURCE_AVAILABLE,
|
|
128
|
+
alive: processAlive,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** True only when the durable identity still names the same live process. */
|
|
132
|
+
export function assignmentProcessOwnerAlive(
|
|
133
|
+
record: Pick<DurableAssignmentRecord, "processOwner">,
|
|
134
|
+
probe: AssignmentProcessOwnerProbe = defaultProcessOwnerProbe,
|
|
135
|
+
): boolean {
|
|
136
|
+
const owner = record.processOwner;
|
|
137
|
+
if (owner === undefined) return false;
|
|
138
|
+
const current = probe.birthToken(owner.pid);
|
|
139
|
+
if (current === null || current !== owner.processBirthToken) return false;
|
|
140
|
+
// A synthetic `pid-N` token cannot distinguish pid reuse, so require an
|
|
141
|
+
// independent liveness signal on platforms without a real birth-token source.
|
|
142
|
+
if (probe.tokenProvesDeath === false) return probe.alive?.(owner.pid) ?? false;
|
|
143
|
+
return true;
|
|
61
144
|
}
|
|
62
145
|
|
|
63
146
|
function writeStore(assignments: ReadonlyArray<DurableAssignmentRecord>): void {
|
|
@@ -114,49 +197,103 @@ export async function renameStoredAssignment(
|
|
|
114
197
|
return copyRecord(result);
|
|
115
198
|
}
|
|
116
199
|
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
function currentProcessOwner(): AssignmentProcessOwner {
|
|
201
|
+
const token = processBirthToken();
|
|
202
|
+
if (token === null) throw new Error("dispatch: cannot establish assignment owner process birth token");
|
|
203
|
+
return {
|
|
204
|
+
pid: process.pid,
|
|
205
|
+
processBirthToken: token,
|
|
206
|
+
acquiredAt: new Date().toISOString(),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function openRecord(assignmentId: string): DurableAssignmentRecord {
|
|
211
|
+
return {
|
|
119
212
|
assignmentId,
|
|
120
|
-
|
|
121
|
-
|
|
213
|
+
attempts: [],
|
|
214
|
+
terminalRunId: null,
|
|
215
|
+
status: "running",
|
|
216
|
+
processOwner: currentProcessOwner(),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function ensureRunningProcessOwner(record: DurableAssignmentRecord): DurableAssignmentRecord {
|
|
221
|
+
if (record.status !== "running" || record.processOwner !== undefined) return record;
|
|
222
|
+
return { ...record, processOwner: currentProcessOwner() };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function clearProcessOwner(record: DurableAssignmentRecord): DurableAssignmentRecord {
|
|
226
|
+
const result = { ...record };
|
|
227
|
+
delete result.processOwner;
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function registerAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
232
|
+
return updateRecord(assignmentId, (current) => ensureRunningProcessOwner(current ?? openRecord(assignmentId)));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Reserve this record's status for `owner`, opening the record if it is new.
|
|
237
|
+
* Attempts filed by anyone are still recorded; only the verdict is reserved,
|
|
238
|
+
* and until the owner settles it the record stays `running`.
|
|
239
|
+
*/
|
|
240
|
+
export function claimAssignmentVerdict(
|
|
241
|
+
assignmentId: string,
|
|
242
|
+
owner: AssignmentVerdictOwner,
|
|
243
|
+
): Promise<DurableAssignmentRecord> {
|
|
244
|
+
return updateRecord(assignmentId, (current) => ({
|
|
245
|
+
...ensureRunningProcessOwner(current ?? openRecord(assignmentId)),
|
|
246
|
+
verdictOwner: owner,
|
|
247
|
+
}));
|
|
122
248
|
}
|
|
123
249
|
|
|
124
250
|
export function recordAssignmentAttempt(assignmentId: string, runId: string): Promise<DurableAssignmentRecord> {
|
|
125
251
|
return updateRecord(assignmentId, (current) => {
|
|
126
|
-
const base = current ??
|
|
252
|
+
const base = ensureRunningProcessOwner(current ?? openRecord(assignmentId));
|
|
127
253
|
return base.attempts.includes(runId) ? base : { ...base, attempts: [...base.attempts, runId] };
|
|
128
254
|
});
|
|
129
255
|
}
|
|
130
256
|
|
|
131
257
|
export function failQueuedAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
132
258
|
return updateRecord(assignmentId, (current) => {
|
|
133
|
-
const base = current ??
|
|
134
|
-
|
|
259
|
+
const base = current ?? openRecord(assignmentId);
|
|
260
|
+
if (base.verdictOwner !== undefined) return base;
|
|
261
|
+
return base.status === "running" ? clearProcessOwner({ ...base, status: "failed" }) : base;
|
|
135
262
|
});
|
|
136
263
|
}
|
|
137
264
|
|
|
138
265
|
export function timeoutStoredAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
139
266
|
return updateRecord(assignmentId, (current) => {
|
|
140
|
-
const base = current ??
|
|
141
|
-
|
|
267
|
+
const base = current ?? openRecord(assignmentId);
|
|
268
|
+
if (base.verdictOwner !== undefined) return base;
|
|
269
|
+
return base.status === "running" ? clearProcessOwner({ ...base, status: "timed_out" }) : base;
|
|
142
270
|
});
|
|
143
271
|
}
|
|
144
272
|
|
|
145
273
|
export function cancelStoredAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
146
274
|
return updateRecord(assignmentId, (current) => {
|
|
147
|
-
const base = current ??
|
|
148
|
-
|
|
275
|
+
const base = current ?? openRecord(assignmentId);
|
|
276
|
+
if (base.verdictOwner !== undefined) return base;
|
|
277
|
+
return base.status === "running" ? clearProcessOwner({ ...base, status: "canceled" }) : base;
|
|
149
278
|
});
|
|
150
279
|
}
|
|
151
280
|
|
|
281
|
+
/**
|
|
282
|
+
* File `terminalRunId` as an attempt and, when the caller owns the verdict, set
|
|
283
|
+
* the record's status. An attempt settling under a claimed record settles
|
|
284
|
+
* itself and not the record: one fleet step reaching a green receipt says
|
|
285
|
+
* nothing about the six steps behind it that never ran.
|
|
286
|
+
*/
|
|
152
287
|
export function settleStoredAssignment(
|
|
153
288
|
assignmentId: string,
|
|
154
289
|
terminalRunId: string,
|
|
155
290
|
status: Exclude<AssignmentStatus, "running">,
|
|
291
|
+
owner?: AssignmentVerdictOwner,
|
|
156
292
|
): Promise<DurableAssignmentRecord> {
|
|
157
293
|
return updateRecord(assignmentId, (current) => {
|
|
158
|
-
const base = current ??
|
|
294
|
+
const base = current ?? openRecord(assignmentId);
|
|
159
295
|
const attempts = base.attempts.includes(terminalRunId) ? base.attempts : [...base.attempts, terminalRunId];
|
|
160
|
-
return { ...base, attempts
|
|
296
|
+
if (base.verdictOwner !== owner) return { ...base, attempts };
|
|
297
|
+
return clearProcessOwner({ ...base, attempts, terminalRunId, status });
|
|
161
298
|
});
|
|
162
299
|
}
|
|
@@ -240,7 +240,7 @@ export interface DispatchContract {
|
|
|
240
240
|
sealCouncilSynthesis?(input: {
|
|
241
241
|
group: string;
|
|
242
242
|
round: number;
|
|
243
|
-
kind: "none" | "vote";
|
|
243
|
+
kind: "none" | "vote" | "judge";
|
|
244
244
|
text: string;
|
|
245
245
|
subjects: ReadonlyArray<{ runId: string; digest: string | null }>;
|
|
246
246
|
template: RunReceipt;
|
|
@@ -252,6 +252,20 @@ export interface DispatchContract {
|
|
|
252
252
|
/** Get a specific immutable attempt envelope. */
|
|
253
253
|
getRun(runId: string): RunEnvelope | null;
|
|
254
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Absolute paths this run's own successful tool calls aimed a mutation at,
|
|
257
|
+
* or null when the run kept no usable record: an unknown or evicted run, a
|
|
258
|
+
* runtime that publishes no tool telemetry, telemetry that came back
|
|
259
|
+
* incomplete, or a run that called a tool able to write a path its arguments
|
|
260
|
+
* do not name. Null is not an empty list, and a caller that cannot tell them
|
|
261
|
+
* apart will read "wrote nothing" off a run nobody watched.
|
|
262
|
+
*
|
|
263
|
+
* The write boundary reads this to separate what a step changed from what
|
|
264
|
+
* changed under it, so the answer must stay a record of observation rather
|
|
265
|
+
* than an inference from the checkout.
|
|
266
|
+
*/
|
|
267
|
+
observedRunWrites?(runId: string): ReadonlyArray<string> | null;
|
|
268
|
+
|
|
255
269
|
/** In-memory logical assignment state and complete finalized-attempt history. */
|
|
256
270
|
readonly assignments?: {
|
|
257
271
|
/** Live assignment, including its terminal promise and detailed attempt refs. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { pathBoundaryCovers } from "../../core/path-boundary.js";
|
|
2
3
|
import { normalizeWriteBoundary } from "../agents/write-boundary.js";
|
|
3
4
|
|
|
4
5
|
export type DelegationTaskMode = "sequential" | "parallel";
|
|
@@ -54,10 +55,6 @@ export function buildDelegationProposalBriefing(proposals: ReadonlyArray<{ agent
|
|
|
54
55
|
|
|
55
56
|
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
56
57
|
|
|
57
|
-
function insideBoundary(path: string, boundary: ReadonlyArray<string>): boolean {
|
|
58
|
-
return boundary.some((root) => (root.endsWith("/") ? path.startsWith(root) : path === root));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
58
|
function malformed(detail: string): DelegationPlanValidation {
|
|
62
59
|
return { ok: false, reason: "delegation_plan_malformed", detail };
|
|
63
60
|
}
|
|
@@ -116,7 +113,7 @@ export function validateDelegationPlan(input: ValidateDelegationPlanInput): Dele
|
|
|
116
113
|
} catch (error) {
|
|
117
114
|
return malformed(`task '${task.id}' has invalid writes: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
115
|
}
|
|
119
|
-
const outside = writes.find((path) => !
|
|
116
|
+
const outside = writes.find((path) => !pathBoundaryCovers(input.writes, path));
|
|
120
117
|
if (outside !== undefined) {
|
|
121
118
|
return {
|
|
122
119
|
ok: false,
|
|
@@ -172,9 +172,17 @@ export function gateDeciderAgentId(requested: string | undefined): string {
|
|
|
172
172
|
* the slot. The gate validates that answer and fails closed on a malformed one,
|
|
173
173
|
* so also enforcing an inapplicable recipe postcondition would fail the run
|
|
174
174
|
* before its verdict could be read.
|
|
175
|
+
*
|
|
176
|
+
* `synthesis` is the third decider and belongs here for the same reason.
|
|
177
|
+
* {@link deriveExecutionRole} already resolves it to the `judge` role, and
|
|
178
|
+
* `isBoundedGateRolePrompt` admits it only under the council judge prompt,
|
|
179
|
+
* which asks for `{"verdict","text"}`. A council seats the read-only
|
|
180
|
+
* `researcher` by default, whose recipe declares `research-report`, so leaving
|
|
181
|
+
* the recipe postcondition on the slot sealed a contract failure on every
|
|
182
|
+
* correct synthesis and burned the configured retries reproducing it.
|
|
175
183
|
*/
|
|
176
184
|
export function appliesRecipeResultContract(gateRole: GateTopologyRole | undefined): boolean {
|
|
177
|
-
return gateRole !== "reviewer" && gateRole !== "judge";
|
|
185
|
+
return gateRole !== "reviewer" && gateRole !== "judge" && gateRole !== "synthesis";
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
/** The route dimensions a gate correlation is measured across. */
|