opencode-swarm 7.132.2 → 7.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/agents/agent-output-schema.d.ts +74 -0
- package/dist/agents/critic.d.ts +2 -1
- package/dist/agents/reviewer.d.ts +1 -1
- package/dist/background/completion-observer.d.ts +17 -5
- package/dist/background/pending-delegations.d.ts +315 -2
- package/dist/background/stage-b-gates.d.ts +2 -0
- package/dist/cli/{config-doctor-bp5spb79.js → config-doctor-t5r92674.js} +3 -3
- package/dist/cli/{core-wg3re04e.js → core-d6yd2jzv.js} +2 -2
- package/dist/cli/{curation-policy-7zp2m9gv.js → curation-policy-3smfn2a3.js} +7 -7
- package/dist/cli/{curator-drift-y1wm4atk.js → curator-drift-tsyyf4b4.js} +4 -4
- package/dist/cli/{curator-h067mrcc.js → curator-hva8f3nf.js} +31 -29
- package/dist/cli/{curator-llm-factory-605d08hf.js → curator-llm-factory-p3aqev12.js} +31 -29
- package/dist/cli/{dispatch-3mhgqj2x.js → dispatch-gxbeb2ps.js} +2 -2
- package/dist/cli/{evidence-summary-service-jfagmvrh.js → evidence-summary-service-mrzsjpg4.js} +12 -11
- package/dist/cli/{gate-evidence-cy2te3x8.js → gate-evidence-b5v1xvxp.js} +4 -4
- package/dist/cli/guardrail-explain-k8mcvk61.js +57 -0
- package/dist/cli/{guardrail-log-aa112fxv.js → guardrail-log-absma1rv.js} +5 -5
- package/dist/cli/{hive-promoter-nv0m5rzk.js → hive-promoter-jwd51gzk.js} +31 -29
- package/dist/cli/{index-g4mnf3p5.js → index-1j3682j8.js} +5 -5
- package/dist/cli/{index-6ccynjv3.js → index-2njes601.js} +8 -8
- package/dist/cli/{index-w9cb5zwh.js → index-2t1n9k7b.js} +4 -4
- package/dist/cli/{index-f6480ee6.js → index-3dtjhr26.js} +5259 -1759
- package/dist/cli/{index-2tn5h2zp.js → index-4p02dp92.js} +6 -6
- package/dist/cli/index-b4z7s917.js +80 -0
- package/dist/cli/{index-bvp2v7k1.js → index-b5ek2cge.js} +266 -19
- package/dist/cli/index-b8zgtz81.js +123 -0
- package/dist/cli/{index-3s9rfnqq.js → index-baawkm48.js} +12 -12
- package/dist/cli/{index-fgcmxjp7.js → index-c3e0ymrg.js} +1 -1
- package/dist/cli/{index-q27bajqb.js → index-cfkvc5q9.js} +3 -3
- package/dist/cli/{index-7mkpsw6g.js → index-dd7ttx8x.js} +1 -1
- package/dist/cli/{index-jgfcp9bh.js → index-dgxgvzab.js} +7 -7
- package/dist/cli/{index-8d3m0ge3.js → index-eedq2y40.js} +48 -34
- package/dist/cli/{index-630badhp.js → index-g87ty4wp.js} +149 -6
- package/dist/cli/{index-yvv1wt2h.js → index-hgh01f6h.js} +25 -3
- package/dist/cli/{index-9cf1yr67.js → index-j6ywarjp.js} +3 -3
- package/dist/cli/{index-mwkwyej1.js → index-jpkxjmfw.js} +5 -5
- package/dist/cli/{index-03zyn94g.js → index-k742ecbm.js} +178 -222
- package/dist/cli/{index-y5qp59rc.js → index-kapharv7.js} +10 -4
- package/dist/cli/{index-djwemsjn.js → index-m2s0gbzt.js} +2 -2
- package/dist/cli/{index-4ff0x4cg.js → index-p8hezdtz.js} +1 -1
- package/dist/cli/{index-k4tmx21m.js → index-rkyhv4p5.js} +2 -2
- package/dist/cli/{index-3x761jqv.js → index-wk1h5ec2.js} +1 -1
- package/dist/cli/{index-jj4earrh.js → index-xfejq1p9.js} +1 -1
- package/dist/cli/{index-rt5jgktq.js → index-y0kk3rtv.js} +2 -2
- package/dist/cli/index-y3v1404y.js +1625 -0
- package/dist/cli/index.js +35 -31
- package/dist/cli/{knowledge-escalator-j0nceas7.js → knowledge-escalator-mkdac2m7.js} +8 -8
- package/dist/cli/{knowledge-events-gpqc62jr.js → knowledge-events-jjc9qbe0.js} +6 -6
- package/dist/cli/{knowledge-link-4yd2z8ev.js → knowledge-link-nc4y2zkq.js} +5 -5
- package/dist/cli/{knowledge-store-ggxr3ww5.js → knowledge-store-9e0w1kxm.js} +6 -6
- package/dist/cli/{knowledge-validator-007smap6.js → knowledge-validator-h1nrn2pk.js} +9 -9
- package/dist/cli/pending-delegations-v9e40hfk.js +96 -0
- package/dist/cli/{pr-subscriptions-zqbmctwz.js → pr-subscriptions-ztq40hrt.js} +7 -7
- package/dist/cli/{scan-cursor-wz4bff7a.js → scan-cursor-mk3tq3sf.js} +7 -7
- package/dist/cli/{schema-023gwne7.js → schema-bjb0yzsq.js} +7 -3
- package/dist/cli/{scope-persistence-dm5bab50.js → scope-persistence-hz3zmmsv.js} +5 -5
- package/dist/cli/{skill-generator-rtsg1m7x.js → skill-generator-p1de0wkr.js} +10 -10
- package/dist/cli/worktree-collision-ownership-c4bdnjtx.js +221 -0
- package/dist/commands/command-dispatch.d.ts +7 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/registry.d.ts +16 -0
- package/dist/commands/review.d.ts +21 -0
- package/dist/config/agent-names.d.ts +3 -3
- package/dist/config/evidence-schema.d.ts +47 -47
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +58 -6
- package/dist/consensus/contracts.d.ts +3 -3
- package/dist/evaluation/ephemeral-agent-dispatcher.d.ts +64 -0
- package/dist/evaluation/model-dispatcher.d.ts +11 -5
- package/dist/hooks/auto-review.d.ts +30 -65
- package/dist/hooks/delegation-gate/worktree-collision-ownership.d.ts +28 -0
- package/dist/hooks/delegation-gate/worktree-isolation.d.ts +63 -1
- package/dist/hooks/delegation-gate/worktree-merge-status.d.ts +14 -0
- package/dist/hooks/delegation-gate/worktree-ownership-tag.d.ts +19 -0
- package/dist/hooks/delegation-gate/worktree-provisioning-owner.d.ts +30 -0
- package/dist/hooks/delegation-gate.d.ts +15 -6
- package/dist/hooks/guardrails/tool-before.d.ts +8 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/init-orphan-recovery.d.ts +17 -0
- package/dist/hooks/review-receipt-collector.d.ts +53 -10
- package/dist/hooks/review-receipt-scope.d.ts +74 -0
- package/dist/hooks/review-receipt.d.ts +104 -10
- package/dist/hooks/reviewer-scope-file-fingerprint.d.ts +21 -0
- package/dist/hooks/reviewer-scope-lifecycle.d.ts +19 -0
- package/dist/hooks/task-result-classifier.d.ts +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +583 -524
- package/dist/memory/schema.d.ts +1 -1
- package/dist/review/contracts.d.ts +29 -0
- package/dist/review/diff-source.d.ts +134 -0
- package/dist/review/engine.d.ts +60 -0
- package/dist/review/evidence.d.ts +88 -0
- package/dist/review/finding-validator.d.ts +77 -0
- package/dist/review/phase-runner.d.ts +37 -0
- package/dist/review/runtime.d.ts +32 -0
- package/dist/scope/scope-binding.d.ts +9 -0
- package/dist/services/config-doctor.d.ts +12 -0
- package/dist/session/snapshot-writer.d.ts +2 -0
- package/dist/state.d.ts +222 -1
- package/dist/tools/dispatch-lanes.d.ts +3 -1
- package/dist/tools/epic-record-divergence.d.ts +5 -5
- package/dist/tools/lean-turbo-review.d.ts +4 -1
- package/dist/tools/lean-turbo-run-phase.d.ts +3 -1
- package/dist/tools/phase-complete/gates/final-review-gate.d.ts +18 -0
- package/dist/tools/phase-complete/gates/index.d.ts +1 -0
- package/dist/tools/phase-complete/gates/types.d.ts +10 -0
- package/dist/tools/phase-complete.d.ts +13 -1
- package/dist/tools/plugin-registration.d.ts +3 -1
- package/dist/tools/swarm-command.d.ts +4 -1
- package/dist/tools/tool-metadata.d.ts +22 -22
- package/dist/turbo/lean/integration.d.ts +18 -2
- package/dist/turbo/lean/reviewer.d.ts +19 -3
- package/dist/turbo/lean/runner.d.ts +1 -1
- package/dist/worktree/merge.d.ts +47 -1
- package/package.json +1 -1
- package/dist/cli/guardrail-explain-s7vzm6z7.js +0 -55
- package/dist/cli/index-y2pfmd0z.js +0 -259
- package/dist/cli/pending-delegations-37t4xecr.js +0 -34
- package/dist/cli/{index-9f71ye47.js → index-13xxjfhn.js} +3 -3
- package/dist/cli/{index-s80bsjkj.js → index-2ghkk9ve.js} +6 -6
- package/dist/cli/{index-76kvm9z5.js → index-5nybajn9.js} +3 -3
|
@@ -0,0 +1,1625 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
validateSwarmPath
|
|
4
|
+
} from "./index-2ghkk9ve.js";
|
|
5
|
+
import {
|
|
6
|
+
withEvidenceLock
|
|
7
|
+
} from "./index-5nybajn9.js";
|
|
8
|
+
import {
|
|
9
|
+
exports_external,
|
|
10
|
+
init_zod
|
|
11
|
+
} from "./index-bpmtbmy9.js";
|
|
12
|
+
import {
|
|
13
|
+
bunWrite
|
|
14
|
+
} from "./index-qe3v54nb.js";
|
|
15
|
+
import {
|
|
16
|
+
init_logger,
|
|
17
|
+
warn
|
|
18
|
+
} from "./index-zgwm4ryv.js";
|
|
19
|
+
|
|
20
|
+
// src/background/pending-delegations.ts
|
|
21
|
+
init_zod();
|
|
22
|
+
import { createHash } from "crypto";
|
|
23
|
+
import * as fs from "fs";
|
|
24
|
+
import * as path from "path";
|
|
25
|
+
init_logger();
|
|
26
|
+
var BACKGROUND_DELEGATIONS_FILE = "background-delegations.jsonl";
|
|
27
|
+
var BACKGROUND_DELEGATION_FALLBACK_DIR = "background-delegation-fallback";
|
|
28
|
+
var BACKGROUND_CODER_RESERVATIONS_FILE = "background-coder-reservations.json";
|
|
29
|
+
var MAX_LIVE_BACKGROUND_FALLBACKS = 256;
|
|
30
|
+
var MAX_LIVE_BACKGROUND_CODER_RESERVATIONS = 256;
|
|
31
|
+
var MAX_BACKGROUND_OBSERVED_FILES = 5000;
|
|
32
|
+
var MAX_BACKGROUND_ADVISORY_CHARS = 4000;
|
|
33
|
+
var MAX_RECOVERY_LEDGER_BYTES = 4 * 1024 * 1024;
|
|
34
|
+
var MAX_RECOVERY_FALLBACK_BYTES = 1024 * 1024;
|
|
35
|
+
var STORE_LOCK_AGENT = "background";
|
|
36
|
+
var STORE_LOCK_TASK = "background-delegations";
|
|
37
|
+
var FALLBACK_LOCK_TASK = "background-delegation-fallback";
|
|
38
|
+
var RESERVATION_LOCK_TASK = "background-coder-reservations";
|
|
39
|
+
var ADVISORY_PREPARE_LEASE_MS = 30000;
|
|
40
|
+
var INGESTION_CLAIM_LEASE_MS = 30000;
|
|
41
|
+
var BACKGROUND_INGESTION_LEASE_MS = 30000;
|
|
42
|
+
var ResultSchema = exports_external.object({
|
|
43
|
+
text: exports_external.string().optional(),
|
|
44
|
+
error: exports_external.string().optional(),
|
|
45
|
+
chars: exports_external.number(),
|
|
46
|
+
truncated: exports_external.boolean(),
|
|
47
|
+
digest: exports_external.string(),
|
|
48
|
+
outputRef: exports_external.string().optional(),
|
|
49
|
+
outputPreviewChars: exports_external.number().optional(),
|
|
50
|
+
outputDegraded: exports_external.boolean().optional(),
|
|
51
|
+
outputArtifactError: exports_external.string().optional(),
|
|
52
|
+
transcriptIncomplete: exports_external.boolean().optional(),
|
|
53
|
+
messageCount: exports_external.number().optional()
|
|
54
|
+
}).strict();
|
|
55
|
+
var WorkspaceSchema = exports_external.object({
|
|
56
|
+
directory: exports_external.string(),
|
|
57
|
+
gitHead: exports_external.string().nullable(),
|
|
58
|
+
dirtyHash: exports_external.string().nullable(),
|
|
59
|
+
changedFiles: exports_external.array(exports_external.string()).nullable().optional(),
|
|
60
|
+
prHeadSha: exports_external.string().nullable(),
|
|
61
|
+
scope: exports_external.string().nullable()
|
|
62
|
+
}).strict();
|
|
63
|
+
var TaskChangeContextSchema = exports_external.object({
|
|
64
|
+
declaredFiles: exports_external.array(exports_external.string()).nullable(),
|
|
65
|
+
baseline: WorkspaceSchema
|
|
66
|
+
}).strict();
|
|
67
|
+
var WorktreeDescriptorSchema = exports_external.object({
|
|
68
|
+
callID: exports_external.string().min(1).max(256),
|
|
69
|
+
parentSessionId: exports_external.string().min(1).max(256),
|
|
70
|
+
taskId: exports_external.string().min(1).max(256),
|
|
71
|
+
planTaskId: exports_external.string().min(1).max(256).nullable(),
|
|
72
|
+
worktreePath: exports_external.string().min(1).max(4096),
|
|
73
|
+
branchName: exports_external.string().min(1).max(1024),
|
|
74
|
+
worktreeId: exports_external.string().min(1).max(256),
|
|
75
|
+
worktreeSessionId: exports_external.string().min(1).max(256),
|
|
76
|
+
mergeStrategy: exports_external.enum(["merge", "rebase", "cherry-pick"]),
|
|
77
|
+
laneIndex: exports_external.number().int().nonnegative().max(255),
|
|
78
|
+
worktreeDir: exports_external.string().min(1).max(4096).nullable()
|
|
79
|
+
}).strict();
|
|
80
|
+
var TerminalResultSchema = exports_external.object({
|
|
81
|
+
eventId: exports_external.string().min(1).max(256),
|
|
82
|
+
status: exports_external.enum(["completed", "error", "cancelled"]),
|
|
83
|
+
recordedAt: exports_external.number().int().nonnegative(),
|
|
84
|
+
result: ResultSchema
|
|
85
|
+
}).strict();
|
|
86
|
+
var NormalizedObservedFileSchema = exports_external.string().min(1).max(4096).refine((value) => normalizeObservedFile(value) === value);
|
|
87
|
+
var SettlementProvenanceSchema = exports_external.object({
|
|
88
|
+
correlationId: exports_external.string().min(1).max(256),
|
|
89
|
+
parentSessionId: exports_external.string().min(1).max(256),
|
|
90
|
+
callID: exports_external.string().max(256),
|
|
91
|
+
planTaskId: exports_external.string().min(1).max(256).nullable(),
|
|
92
|
+
baseline: WorkspaceSchema,
|
|
93
|
+
worktree: WorktreeDescriptorSchema.nullable()
|
|
94
|
+
}).strict();
|
|
95
|
+
var SettlementOutcomeSchema = exports_external.object({
|
|
96
|
+
kind: exports_external.enum(["shared-root", "standard-worktree"]),
|
|
97
|
+
result: exports_external.enum(["ready", "merged", "unchanged", "partial", "failed"]),
|
|
98
|
+
reason: exports_external.string().min(1).max(2000).optional(),
|
|
99
|
+
sourceHeadAfterCommit: exports_external.string().min(1).max(256).nullable().optional(),
|
|
100
|
+
targetHeadBeforeMerge: exports_external.string().min(1).max(256).nullable().optional(),
|
|
101
|
+
targetHeadAfterMerge: exports_external.string().min(1).max(256).nullable().optional()
|
|
102
|
+
}).strict();
|
|
103
|
+
var CoderSettlementSchema = exports_external.object({
|
|
104
|
+
state: exports_external.enum(["pending", "settling", "settled", "preserved"]),
|
|
105
|
+
provenance: SettlementProvenanceSchema,
|
|
106
|
+
operationId: exports_external.string().min(1).max(256).optional(),
|
|
107
|
+
sourceHeadAfterCommit: exports_external.string().min(1).max(256).nullable().optional(),
|
|
108
|
+
targetHeadBeforeMerge: exports_external.string().min(1).max(256).nullable().optional(),
|
|
109
|
+
observedFiles: exports_external.array(NormalizedObservedFileSchema).max(MAX_BACKGROUND_OBSERVED_FILES).nullable(),
|
|
110
|
+
outcome: SettlementOutcomeSchema.optional(),
|
|
111
|
+
updatedAt: exports_external.number().int().nonnegative()
|
|
112
|
+
}).strict().superRefine((value, context) => {
|
|
113
|
+
if (value.state !== "pending" && !value.operationId) {
|
|
114
|
+
context.addIssue({
|
|
115
|
+
code: exports_external.ZodIssueCode.custom,
|
|
116
|
+
message: "settling, settled, and preserved states require operationId"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if ((value.state === "settled" || value.state === "preserved") && !value.outcome) {
|
|
120
|
+
context.addIssue({
|
|
121
|
+
code: exports_external.ZodIssueCode.custom,
|
|
122
|
+
message: "terminal settlement state requires outcome"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
var AdvisoryPreparationSchema = exports_external.object({
|
|
127
|
+
id: exports_external.string().min(1).max(256),
|
|
128
|
+
preparedAt: exports_external.number().int().nonnegative(),
|
|
129
|
+
leaseExpiresAt: exports_external.number().int().nonnegative()
|
|
130
|
+
}).strict();
|
|
131
|
+
var AdvisoryInboxSchema = exports_external.object({
|
|
132
|
+
eventId: exports_external.string().min(1).max(256),
|
|
133
|
+
parentSessionId: exports_external.string().min(1).max(256),
|
|
134
|
+
state: exports_external.enum(["pending", "delivered"]),
|
|
135
|
+
message: exports_external.string().min(1).max(MAX_BACKGROUND_ADVISORY_CHARS),
|
|
136
|
+
createdAt: exports_external.number().int().nonnegative(),
|
|
137
|
+
preparation: AdvisoryPreparationSchema.optional(),
|
|
138
|
+
deliveredAt: exports_external.number().int().nonnegative().optional()
|
|
139
|
+
}).strict().superRefine((value, context) => {
|
|
140
|
+
if (value.state === "delivered" && value.deliveredAt === undefined) {
|
|
141
|
+
context.addIssue({
|
|
142
|
+
code: exports_external.ZodIssueCode.custom,
|
|
143
|
+
message: "delivered advisory requires deliveredAt"
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (value.state === "delivered" && value.preparation !== undefined) {
|
|
147
|
+
context.addIssue({
|
|
148
|
+
code: exports_external.ZodIssueCode.custom,
|
|
149
|
+
message: "delivered advisory cannot retain a preparation lease"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
var DelegationIngestionSchema = exports_external.object({
|
|
154
|
+
state: exports_external.enum(["claimed", "retryable", "consumed"]),
|
|
155
|
+
attempt: exports_external.number().int().positive(),
|
|
156
|
+
updatedAt: exports_external.number().int().nonnegative(),
|
|
157
|
+
claimToken: exports_external.string().min(1).max(256),
|
|
158
|
+
leaseExpiresAt: exports_external.number().int().nonnegative().optional()
|
|
159
|
+
}).strict().superRefine((value, context) => {
|
|
160
|
+
if (value.state === "claimed" && value.leaseExpiresAt === undefined) {
|
|
161
|
+
context.addIssue({
|
|
162
|
+
code: exports_external.ZodIssueCode.custom,
|
|
163
|
+
message: "claimed ingestion requires a lease expiry"
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (value.state !== "claimed" && value.leaseExpiresAt !== undefined) {
|
|
167
|
+
context.addIssue({
|
|
168
|
+
code: exports_external.ZodIssueCode.custom,
|
|
169
|
+
message: "completed ingestion cannot retain a claim lease"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
var BackgroundCoderReservationSchema = exports_external.object({
|
|
174
|
+
reservationId: exports_external.string().min(1).max(256),
|
|
175
|
+
parentSessionId: exports_external.string().min(1).max(256),
|
|
176
|
+
planTaskId: exports_external.string().min(1).max(256).nullable(),
|
|
177
|
+
callID: exports_external.string().min(1).max(256),
|
|
178
|
+
state: exports_external.enum(["reserved", "bound"]),
|
|
179
|
+
correlationId: exports_external.string().min(1).max(256).nullable(),
|
|
180
|
+
createdAt: exports_external.number().int().nonnegative(),
|
|
181
|
+
updatedAt: exports_external.number().int().nonnegative()
|
|
182
|
+
}).strict().superRefine((value, context) => {
|
|
183
|
+
if (value.state === "reserved" && value.correlationId !== null) {
|
|
184
|
+
context.addIssue({
|
|
185
|
+
code: exports_external.ZodIssueCode.custom,
|
|
186
|
+
message: "unbound reservation cannot have a correlationId"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (value.state === "bound" && value.correlationId === null) {
|
|
190
|
+
context.addIssue({
|
|
191
|
+
code: exports_external.ZodIssueCode.custom,
|
|
192
|
+
message: "bound reservation requires a correlationId"
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
var BackgroundCoderReservationStoreSchema = exports_external.object({
|
|
197
|
+
schemaVersion: exports_external.literal(1),
|
|
198
|
+
reservations: exports_external.array(BackgroundCoderReservationSchema).max(MAX_LIVE_BACKGROUND_CODER_RESERVATIONS)
|
|
199
|
+
}).strict().superRefine((value, context) => {
|
|
200
|
+
const ids = new Set;
|
|
201
|
+
for (const reservation of value.reservations) {
|
|
202
|
+
if (ids.has(reservation.reservationId)) {
|
|
203
|
+
context.addIssue({
|
|
204
|
+
code: exports_external.ZodIssueCode.custom,
|
|
205
|
+
message: "duplicate reservationId"
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (reservation.reservationId !== buildBackgroundCoderReservationId({
|
|
210
|
+
parentSessionId: reservation.parentSessionId,
|
|
211
|
+
planTaskId: reservation.planTaskId,
|
|
212
|
+
callID: reservation.callID
|
|
213
|
+
})) {
|
|
214
|
+
context.addIssue({
|
|
215
|
+
code: exports_external.ZodIssueCode.custom,
|
|
216
|
+
message: "reservationId does not match owner identity"
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
ids.add(reservation.reservationId);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
var PromptSchema = exports_external.object({
|
|
224
|
+
text: exports_external.string(),
|
|
225
|
+
chars: exports_external.number(),
|
|
226
|
+
truncated: exports_external.boolean(),
|
|
227
|
+
digest: exports_external.string()
|
|
228
|
+
}).strict();
|
|
229
|
+
var RecordSchema = exports_external.object({
|
|
230
|
+
schemaVersion: exports_external.union([exports_external.literal(1), exports_external.literal(2), exports_external.literal(3)]),
|
|
231
|
+
correlationId: exports_external.string().min(1),
|
|
232
|
+
jobId: exports_external.string().nullable(),
|
|
233
|
+
subagentSessionId: exports_external.string().min(1),
|
|
234
|
+
parentSessionId: exports_external.string().min(1),
|
|
235
|
+
callID: exports_external.string(),
|
|
236
|
+
normalizedAgent: exports_external.string(),
|
|
237
|
+
swarmPrefixedAgent: exports_external.string(),
|
|
238
|
+
planTaskId: exports_external.string().nullable(),
|
|
239
|
+
evidenceTaskId: exports_external.string().nullable(),
|
|
240
|
+
status: exports_external.enum([
|
|
241
|
+
"pending",
|
|
242
|
+
"running",
|
|
243
|
+
"ingestion_error",
|
|
244
|
+
"completed",
|
|
245
|
+
"error",
|
|
246
|
+
"cancelled",
|
|
247
|
+
"stale",
|
|
248
|
+
"consumed"
|
|
249
|
+
]),
|
|
250
|
+
createdAt: exports_external.number(),
|
|
251
|
+
updatedAt: exports_external.number(),
|
|
252
|
+
batchId: exports_external.string().optional(),
|
|
253
|
+
laneId: exports_external.string().optional(),
|
|
254
|
+
mode: exports_external.string().optional(),
|
|
255
|
+
workflowLane: exports_external.string().optional(),
|
|
256
|
+
ownedWorkflowLanes: exports_external.array(exports_external.string().min(1).max(120)).min(1).max(11).optional(),
|
|
257
|
+
promptHash: exports_external.string().optional(),
|
|
258
|
+
workspace: WorkspaceSchema.optional(),
|
|
259
|
+
taskChangeContext: TaskChangeContextSchema.optional(),
|
|
260
|
+
worktree: WorktreeDescriptorSchema.optional(),
|
|
261
|
+
coderReservationId: exports_external.string().min(1).max(256).optional(),
|
|
262
|
+
prompt: PromptSchema.optional(),
|
|
263
|
+
generation: exports_external.number().optional(),
|
|
264
|
+
terminalResult: TerminalResultSchema.optional(),
|
|
265
|
+
coderSettlement: CoderSettlementSchema.optional(),
|
|
266
|
+
advisoryInbox: AdvisoryInboxSchema.optional(),
|
|
267
|
+
ingestion: DelegationIngestionSchema.optional(),
|
|
268
|
+
result: ResultSchema.optional(),
|
|
269
|
+
completedAt: exports_external.number().optional()
|
|
270
|
+
}).strict();
|
|
271
|
+
var FallbackArtifactSchema = exports_external.object({
|
|
272
|
+
schemaVersion: exports_external.literal(1),
|
|
273
|
+
correlationId: exports_external.string().min(1).max(256),
|
|
274
|
+
createdAt: exports_external.number().int().nonnegative(),
|
|
275
|
+
record: RecordSchema
|
|
276
|
+
}).strict().superRefine((value, context) => {
|
|
277
|
+
if (value.record.correlationId !== value.correlationId || value.record.subagentSessionId !== value.correlationId || value.record.status !== "pending") {
|
|
278
|
+
context.addIssue({
|
|
279
|
+
code: exports_external.ZodIssueCode.custom,
|
|
280
|
+
message: "fallback identity/status does not match its pending record"
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
function storePath(directory) {
|
|
285
|
+
return validateSwarmPath(directory, BACKGROUND_DELEGATIONS_FILE);
|
|
286
|
+
}
|
|
287
|
+
function ensureSwarmDir(directory) {
|
|
288
|
+
fs.mkdirSync(path.resolve(directory, ".swarm"), { recursive: true });
|
|
289
|
+
}
|
|
290
|
+
function readDelegations(directory) {
|
|
291
|
+
let raw;
|
|
292
|
+
try {
|
|
293
|
+
raw = fs.readFileSync(storePath(directory), "utf-8");
|
|
294
|
+
} catch (err) {
|
|
295
|
+
if (err.code === "ENOENT")
|
|
296
|
+
return [];
|
|
297
|
+
warn(`[background] readDelegations failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
const folded = new Map;
|
|
301
|
+
for (const line of raw.split(`
|
|
302
|
+
`)) {
|
|
303
|
+
const trimmed = line.trim();
|
|
304
|
+
if (trimmed.length === 0)
|
|
305
|
+
continue;
|
|
306
|
+
let parsedJson;
|
|
307
|
+
try {
|
|
308
|
+
parsedJson = JSON.parse(trimmed);
|
|
309
|
+
} catch {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const result = RecordSchema.safeParse(parsedJson);
|
|
313
|
+
if (!result.success)
|
|
314
|
+
continue;
|
|
315
|
+
folded.set(result.data.correlationId, result.data);
|
|
316
|
+
}
|
|
317
|
+
return [...folded.values()];
|
|
318
|
+
}
|
|
319
|
+
function scanDelegationsForRecovery(directory) {
|
|
320
|
+
let absolutePath;
|
|
321
|
+
try {
|
|
322
|
+
absolutePath = storePath(directory);
|
|
323
|
+
const stat = fs.statSync(absolutePath);
|
|
324
|
+
if (stat.size > MAX_RECOVERY_LEDGER_BYTES) {
|
|
325
|
+
return {
|
|
326
|
+
status: "uncertain",
|
|
327
|
+
reason: `background delegation ledger exceeds the ${MAX_RECOVERY_LEDGER_BYTES}-byte recovery bound`
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (error.code === "ENOENT") {
|
|
332
|
+
return { status: "ok", owners: [] };
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
status: "uncertain",
|
|
336
|
+
reason: `background delegation ledger metadata is unreadable: ${error instanceof Error ? error.message : String(error)}`
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
let raw;
|
|
340
|
+
try {
|
|
341
|
+
raw = fs.readFileSync(absolutePath, "utf-8");
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return {
|
|
344
|
+
status: "uncertain",
|
|
345
|
+
reason: `background delegation ledger is unreadable: ${error instanceof Error ? error.message : String(error)}`
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_RECOVERY_LEDGER_BYTES) {
|
|
349
|
+
return {
|
|
350
|
+
status: "uncertain",
|
|
351
|
+
reason: `background delegation ledger changed beyond the ${MAX_RECOVERY_LEDGER_BYTES}-byte recovery bound`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const folded = new Map;
|
|
355
|
+
let lineNumber = 0;
|
|
356
|
+
for (const line of raw.split(`
|
|
357
|
+
`)) {
|
|
358
|
+
lineNumber += 1;
|
|
359
|
+
const trimmed = line.trim();
|
|
360
|
+
if (!trimmed)
|
|
361
|
+
continue;
|
|
362
|
+
let parsedJson;
|
|
363
|
+
try {
|
|
364
|
+
parsedJson = JSON.parse(trimmed);
|
|
365
|
+
} catch {
|
|
366
|
+
return {
|
|
367
|
+
status: "uncertain",
|
|
368
|
+
reason: `background delegation ledger has malformed JSON at line ${lineNumber}`
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const parsed = RecordSchema.safeParse(parsedJson);
|
|
372
|
+
if (!parsed.success) {
|
|
373
|
+
return {
|
|
374
|
+
status: "uncertain",
|
|
375
|
+
reason: `background delegation ledger has an invalid record at line ${lineNumber}`
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
folded.set(parsed.data.correlationId, parsed.data);
|
|
379
|
+
}
|
|
380
|
+
return { status: "ok", owners: [...folded.values()] };
|
|
381
|
+
}
|
|
382
|
+
function findByCorrelationId(directory, correlationId) {
|
|
383
|
+
if (!correlationId)
|
|
384
|
+
return null;
|
|
385
|
+
for (const record of readDelegations(directory)) {
|
|
386
|
+
if (record.correlationId === correlationId)
|
|
387
|
+
return record;
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
function appendRecord(directory, record) {
|
|
392
|
+
ensureSwarmDir(directory);
|
|
393
|
+
fs.appendFileSync(storePath(directory), `${JSON.stringify(record)}
|
|
394
|
+
`, "utf-8");
|
|
395
|
+
}
|
|
396
|
+
function buildPendingRecord(input, now) {
|
|
397
|
+
return {
|
|
398
|
+
schemaVersion: input.worktree ? 3 : input.batchId ? 2 : 1,
|
|
399
|
+
correlationId: input.correlationId,
|
|
400
|
+
jobId: input.jobId,
|
|
401
|
+
subagentSessionId: input.subagentSessionId,
|
|
402
|
+
parentSessionId: input.parentSessionId,
|
|
403
|
+
callID: input.callID,
|
|
404
|
+
normalizedAgent: input.normalizedAgent,
|
|
405
|
+
swarmPrefixedAgent: input.swarmPrefixedAgent,
|
|
406
|
+
planTaskId: input.planTaskId,
|
|
407
|
+
evidenceTaskId: input.evidenceTaskId,
|
|
408
|
+
status: "pending",
|
|
409
|
+
createdAt: now,
|
|
410
|
+
updatedAt: now,
|
|
411
|
+
...input.batchId ? { batchId: input.batchId } : {},
|
|
412
|
+
...input.laneId ? { laneId: input.laneId } : {},
|
|
413
|
+
...input.mode ? { mode: input.mode } : {},
|
|
414
|
+
...input.workflowLane ? { workflowLane: input.workflowLane } : {},
|
|
415
|
+
...input.ownedWorkflowLanes?.length ? { ownedWorkflowLanes: [...input.ownedWorkflowLanes] } : {},
|
|
416
|
+
...input.promptHash ? { promptHash: input.promptHash } : {},
|
|
417
|
+
...input.workspace ? { workspace: input.workspace } : {},
|
|
418
|
+
...input.taskChangeContext ? { taskChangeContext: input.taskChangeContext } : {},
|
|
419
|
+
...input.worktree ? { worktree: input.worktree } : {},
|
|
420
|
+
...input.coderReservationId ? { coderReservationId: input.coderReservationId } : {},
|
|
421
|
+
...input.prompt ? { prompt: input.prompt } : {},
|
|
422
|
+
...input.generation !== undefined ? { generation: input.generation } : {}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
async function recordPendingDelegation(directory, input, options = {}) {
|
|
426
|
+
const now = Date.now();
|
|
427
|
+
const record = buildPendingRecord(input, now);
|
|
428
|
+
const parsedRecord = RecordSchema.safeParse(record);
|
|
429
|
+
if (!parsedRecord.success) {
|
|
430
|
+
warn("[background] recordPendingDelegation rejected invalid input");
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
435
|
+
if (options.staleTimeoutMs && options.staleTimeoutMs > 0) {
|
|
436
|
+
sweepStaleLocked(directory, options.staleTimeoutMs, now);
|
|
437
|
+
}
|
|
438
|
+
appendRecord(directory, parsedRecord.data);
|
|
439
|
+
});
|
|
440
|
+
return parsedRecord.data;
|
|
441
|
+
} catch (err) {
|
|
442
|
+
warn(`[background] recordPendingDelegation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function buildPromptSnapshot(text, maxChars) {
|
|
447
|
+
const boundedMax = Math.max(0, Math.min(maxChars, 20000));
|
|
448
|
+
const truncated = text.length > boundedMax;
|
|
449
|
+
const bounded = truncated ? text.slice(0, boundedMax) : text;
|
|
450
|
+
return {
|
|
451
|
+
text: bounded,
|
|
452
|
+
chars: text.length,
|
|
453
|
+
truncated,
|
|
454
|
+
digest: createHash("sha256").update(text).digest("hex")
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
async function appendDelegationTransition(directory, correlationId, transition) {
|
|
458
|
+
const now = Date.now();
|
|
459
|
+
try {
|
|
460
|
+
let next = null;
|
|
461
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
462
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
463
|
+
if (!current)
|
|
464
|
+
return;
|
|
465
|
+
if (isTerminal(current.status) && transition.status !== "consumed" && transition.status !== "ingestion_error" && !(current.status === "completed" && transition.status === "stale")) {
|
|
466
|
+
next = current;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
next = {
|
|
470
|
+
...current,
|
|
471
|
+
schemaVersion: current.schemaVersion === 1 ? 2 : current.schemaVersion,
|
|
472
|
+
status: transition.status,
|
|
473
|
+
updatedAt: now,
|
|
474
|
+
...transition.completedAt !== undefined ? { completedAt: transition.completedAt } : transition.status === "completed" || transition.status === "error" ? { completedAt: now } : {},
|
|
475
|
+
...transition.result ? { result: transition.result } : {}
|
|
476
|
+
};
|
|
477
|
+
appendRecord(directory, next);
|
|
478
|
+
});
|
|
479
|
+
return next;
|
|
480
|
+
} catch (err) {
|
|
481
|
+
warn(`[background] appendDelegationTransition failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function buildBackgroundCompletionEventId(input) {
|
|
486
|
+
const canonical = JSON.stringify([
|
|
487
|
+
input.correlationId,
|
|
488
|
+
input.jobId,
|
|
489
|
+
input.status,
|
|
490
|
+
input.resultDigest
|
|
491
|
+
]);
|
|
492
|
+
return `bgc1:${createHash("sha256").update(canonical).digest("hex")}`;
|
|
493
|
+
}
|
|
494
|
+
function sameJson(left, right) {
|
|
495
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
496
|
+
}
|
|
497
|
+
function sameTerminalEvent(left, right) {
|
|
498
|
+
return left.eventId === right.eventId && left.status === right.status && sameJson(left.result, right.result);
|
|
499
|
+
}
|
|
500
|
+
function settlementProvenanceFor(record) {
|
|
501
|
+
const baseline = record.taskChangeContext?.baseline;
|
|
502
|
+
if (!baseline)
|
|
503
|
+
return null;
|
|
504
|
+
return {
|
|
505
|
+
correlationId: record.correlationId,
|
|
506
|
+
parentSessionId: record.parentSessionId,
|
|
507
|
+
callID: record.callID,
|
|
508
|
+
planTaskId: record.planTaskId,
|
|
509
|
+
baseline,
|
|
510
|
+
worktree: record.worktree ?? null
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function terminalDisposition(record) {
|
|
514
|
+
if (record.status === "consumed")
|
|
515
|
+
return "consumed";
|
|
516
|
+
if (record.coderSettlement?.state === "settling")
|
|
517
|
+
return "resume_settlement";
|
|
518
|
+
if (record.coderSettlement?.state === "preserved")
|
|
519
|
+
return "preserved";
|
|
520
|
+
if (record.status === "ingestion_error")
|
|
521
|
+
return "retry_ingestion";
|
|
522
|
+
return "duplicate";
|
|
523
|
+
}
|
|
524
|
+
async function claimTerminalResult(directory, correlationId, terminalResult) {
|
|
525
|
+
const parsedTerminal = TerminalResultSchema.safeParse(terminalResult);
|
|
526
|
+
if (!correlationId || !parsedTerminal.success)
|
|
527
|
+
return null;
|
|
528
|
+
let claim = null;
|
|
529
|
+
try {
|
|
530
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
531
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
532
|
+
if (!current)
|
|
533
|
+
return;
|
|
534
|
+
if (current.terminalResult) {
|
|
535
|
+
if (!sameTerminalEvent(current.terminalResult, parsedTerminal.data)) {
|
|
536
|
+
warn(`[background] claimTerminalResult: different terminal event for ` + `correlationId=${correlationId}; ` + `existing={status: ${current.terminalResult.status}, eventId: ${current.terminalResult.eventId}} ` + `incoming={status: ${parsedTerminal.data.status}, eventId: ${parsedTerminal.data.eventId}}; ` + `rejected`);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
claim = {
|
|
540
|
+
disposition: terminalDisposition(current),
|
|
541
|
+
record: current
|
|
542
|
+
};
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (current.status !== "pending" && current.status !== "running") {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
let coderSettlement = current.coderSettlement;
|
|
549
|
+
if (current.normalizedAgent === "coder" && !coderSettlement) {
|
|
550
|
+
const provenance = settlementProvenanceFor(current);
|
|
551
|
+
if (provenance) {
|
|
552
|
+
coderSettlement = {
|
|
553
|
+
state: "pending",
|
|
554
|
+
provenance,
|
|
555
|
+
observedFiles: null,
|
|
556
|
+
updatedAt: parsedTerminal.data.recordedAt
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const next = {
|
|
561
|
+
...current,
|
|
562
|
+
schemaVersion: 3,
|
|
563
|
+
status: parsedTerminal.data.status,
|
|
564
|
+
terminalResult: parsedTerminal.data,
|
|
565
|
+
result: parsedTerminal.data.result,
|
|
566
|
+
completedAt: parsedTerminal.data.recordedAt,
|
|
567
|
+
updatedAt: parsedTerminal.data.recordedAt,
|
|
568
|
+
...coderSettlement ? { coderSettlement } : {}
|
|
569
|
+
};
|
|
570
|
+
if (!RecordSchema.safeParse(next).success)
|
|
571
|
+
return;
|
|
572
|
+
appendRecord(directory, next);
|
|
573
|
+
claim = { disposition: "claimed", record: next };
|
|
574
|
+
});
|
|
575
|
+
return claim;
|
|
576
|
+
} catch (err) {
|
|
577
|
+
warn(`[background] claimTerminalResult failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function claimCoderSettlement(directory, correlationId, operationId, input = {}) {
|
|
582
|
+
if (!correlationId || !operationId || operationId.length > 256)
|
|
583
|
+
return null;
|
|
584
|
+
let claim = null;
|
|
585
|
+
try {
|
|
586
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
587
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
588
|
+
if (!current || current.normalizedAgent !== "coder" || current.terminalResult?.status !== "completed") {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const provenance = settlementProvenanceFor(current);
|
|
592
|
+
if (!provenance)
|
|
593
|
+
return;
|
|
594
|
+
const existing = current.coderSettlement ?? {
|
|
595
|
+
state: "pending",
|
|
596
|
+
provenance,
|
|
597
|
+
observedFiles: null,
|
|
598
|
+
updatedAt: current.updatedAt
|
|
599
|
+
};
|
|
600
|
+
if (existing.state === "settled" || existing.state === "preserved") {
|
|
601
|
+
claim = {
|
|
602
|
+
disposition: existing.state,
|
|
603
|
+
record: current
|
|
604
|
+
};
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (existing.state === "settling" && existing.operationId !== operationId) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (existing.sourceHeadAfterCommit !== undefined && input.sourceHeadAfterCommit !== undefined && existing.sourceHeadAfterCommit !== input.sourceHeadAfterCommit) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (existing.targetHeadBeforeMerge !== undefined && input.targetHeadBeforeMerge !== undefined && existing.targetHeadBeforeMerge !== input.targetHeadBeforeMerge) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const settlement = {
|
|
617
|
+
...existing,
|
|
618
|
+
state: "settling",
|
|
619
|
+
operationId,
|
|
620
|
+
...input.sourceHeadAfterCommit !== undefined ? { sourceHeadAfterCommit: input.sourceHeadAfterCommit } : {},
|
|
621
|
+
...input.targetHeadBeforeMerge !== undefined ? { targetHeadBeforeMerge: input.targetHeadBeforeMerge } : {},
|
|
622
|
+
updatedAt: Date.now()
|
|
623
|
+
};
|
|
624
|
+
const next = {
|
|
625
|
+
...current,
|
|
626
|
+
schemaVersion: 3,
|
|
627
|
+
coderSettlement: settlement,
|
|
628
|
+
updatedAt: settlement.updatedAt
|
|
629
|
+
};
|
|
630
|
+
if (!RecordSchema.safeParse(next).success)
|
|
631
|
+
return;
|
|
632
|
+
appendRecord(directory, next);
|
|
633
|
+
claim = {
|
|
634
|
+
disposition: existing.state === "settling" ? "resume" : "claimed",
|
|
635
|
+
record: next
|
|
636
|
+
};
|
|
637
|
+
});
|
|
638
|
+
return claim;
|
|
639
|
+
} catch (err) {
|
|
640
|
+
warn(`[background] claimCoderSettlement failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function normalizeObservedFile(value) {
|
|
645
|
+
if (typeof value !== "string")
|
|
646
|
+
return null;
|
|
647
|
+
const trimmed = value.trim().replace(/\\/g, "/");
|
|
648
|
+
if (trimmed.length === 0 || trimmed.length > 4096 || trimmed.includes("\x00") || path.posix.isAbsolute(trimmed) || /^[A-Za-z]:\//.test(trimmed)) {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
const normalized = path.posix.normalize(trimmed).replace(/^\.\//, "");
|
|
652
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.endsWith("/")) {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
return normalized;
|
|
656
|
+
}
|
|
657
|
+
function normalizeBackgroundObservedFiles(files) {
|
|
658
|
+
if (!Array.isArray(files) || files.length > MAX_BACKGROUND_OBSERVED_FILES) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const normalized = new Set;
|
|
662
|
+
for (const file of files) {
|
|
663
|
+
const candidate = normalizeObservedFile(file);
|
|
664
|
+
if (!candidate)
|
|
665
|
+
return null;
|
|
666
|
+
normalized.add(candidate);
|
|
667
|
+
}
|
|
668
|
+
return [...normalized].sort();
|
|
669
|
+
}
|
|
670
|
+
async function updateCoderSettlement(directory, correlationId, input) {
|
|
671
|
+
if (!correlationId || !input.operationId)
|
|
672
|
+
return null;
|
|
673
|
+
let result = null;
|
|
674
|
+
try {
|
|
675
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
676
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
677
|
+
const existing = current?.coderSettlement;
|
|
678
|
+
if (!current || !existing)
|
|
679
|
+
return;
|
|
680
|
+
if (existing.state === "settled" || existing.state === "preserved") {
|
|
681
|
+
result = current;
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (existing.state !== "settling" || existing.operationId !== input.operationId) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (existing.sourceHeadAfterCommit !== undefined && input.sourceHeadAfterCommit !== undefined && existing.sourceHeadAfterCommit !== input.sourceHeadAfterCommit) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (existing.targetHeadBeforeMerge !== undefined && input.targetHeadBeforeMerge !== undefined && existing.targetHeadBeforeMerge !== input.targetHeadBeforeMerge) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if ((input.state === "settled" || input.state === "preserved") && !input.outcome) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (input.outcome && (existing.provenance.worktree && input.outcome.kind !== "standard-worktree" || !existing.provenance.worktree && input.outcome.kind !== "shared-root")) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (input.state === "settled" && input.outcome && (input.outcome.result === "partial" || input.outcome.result === "failed")) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (input.state === "preserved" && input.outcome && input.outcome.result !== "partial" && input.outcome.result !== "failed") {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
let observedFiles = existing.observedFiles;
|
|
706
|
+
if (input.observedFiles !== undefined) {
|
|
707
|
+
if (input.observedFiles === null) {
|
|
708
|
+
observedFiles = null;
|
|
709
|
+
} else {
|
|
710
|
+
const normalized = normalizeBackgroundObservedFiles(input.observedFiles);
|
|
711
|
+
if (!normalized)
|
|
712
|
+
return;
|
|
713
|
+
observedFiles = normalized;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (input.state === "settled" && observedFiles === null)
|
|
717
|
+
return;
|
|
718
|
+
const updatedAt = Date.now();
|
|
719
|
+
const settlement = {
|
|
720
|
+
...existing,
|
|
721
|
+
state: input.state,
|
|
722
|
+
...input.sourceHeadAfterCommit !== undefined ? { sourceHeadAfterCommit: input.sourceHeadAfterCommit } : {},
|
|
723
|
+
...input.targetHeadBeforeMerge !== undefined ? { targetHeadBeforeMerge: input.targetHeadBeforeMerge } : {},
|
|
724
|
+
observedFiles,
|
|
725
|
+
...input.outcome ? { outcome: input.outcome } : {},
|
|
726
|
+
updatedAt
|
|
727
|
+
};
|
|
728
|
+
const next = {
|
|
729
|
+
...current,
|
|
730
|
+
schemaVersion: 3,
|
|
731
|
+
coderSettlement: settlement,
|
|
732
|
+
updatedAt
|
|
733
|
+
};
|
|
734
|
+
if (!RecordSchema.safeParse(next).success)
|
|
735
|
+
return;
|
|
736
|
+
appendRecord(directory, next);
|
|
737
|
+
result = next;
|
|
738
|
+
});
|
|
739
|
+
return result;
|
|
740
|
+
} catch (err) {
|
|
741
|
+
warn(`[background] updateCoderSettlement failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
async function claimDelegationIngestion(directory, correlationId, options) {
|
|
746
|
+
if (!correlationId || !options.claimantId)
|
|
747
|
+
return null;
|
|
748
|
+
const now = options.now ?? Date.now();
|
|
749
|
+
const leaseMs = Math.max(1000, Math.min(options.leaseMs ?? INGESTION_CLAIM_LEASE_MS, 5 * 60000));
|
|
750
|
+
let claim = null;
|
|
751
|
+
try {
|
|
752
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
753
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
754
|
+
if (!current?.terminalResult)
|
|
755
|
+
return;
|
|
756
|
+
if (current.status === "consumed") {
|
|
757
|
+
claim = { disposition: "consumed", record: current };
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (current.ingestion?.state === "claimed" && (current.ingestion.leaseExpiresAt ?? Number.MAX_SAFE_INTEGER) > now) {
|
|
761
|
+
claim = { disposition: "busy", record: current };
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (current.coderSettlement?.state === "preserved") {
|
|
765
|
+
claim = { disposition: "preserved", record: current };
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (current.terminalResult.status !== "completed" || current.normalizedAgent === "coder" && current.coderSettlement?.state !== "settled") {
|
|
769
|
+
claim = { disposition: "not_ready", record: current };
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (current.status !== "completed" && current.status !== "ingestion_error") {
|
|
773
|
+
claim = { disposition: "not_ready", record: current };
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const disposition = current.status === "ingestion_error" || current.ingestion?.state === "claimed" ? "retry" : "claimed";
|
|
777
|
+
const attempt = (current.ingestion?.attempt ?? 0) + 1;
|
|
778
|
+
const claimToken = createHash("sha256").update(JSON.stringify([correlationId, options.claimantId, attempt, now])).digest("hex");
|
|
779
|
+
const next = {
|
|
780
|
+
...current,
|
|
781
|
+
schemaVersion: 3,
|
|
782
|
+
ingestion: {
|
|
783
|
+
state: "claimed",
|
|
784
|
+
attempt,
|
|
785
|
+
updatedAt: now,
|
|
786
|
+
claimToken,
|
|
787
|
+
leaseExpiresAt: now + leaseMs
|
|
788
|
+
},
|
|
789
|
+
updatedAt: now
|
|
790
|
+
};
|
|
791
|
+
appendRecord(directory, next);
|
|
792
|
+
claim = { disposition, record: next };
|
|
793
|
+
});
|
|
794
|
+
return claim;
|
|
795
|
+
} catch (err) {
|
|
796
|
+
warn(`[background] claimDelegationIngestion failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function recordDelegationIngestionResult(directory, correlationId, claimToken, success, options = {}) {
|
|
801
|
+
if (!correlationId || !claimToken)
|
|
802
|
+
return null;
|
|
803
|
+
let result = null;
|
|
804
|
+
try {
|
|
805
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
806
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
807
|
+
if (!current)
|
|
808
|
+
return;
|
|
809
|
+
if (success && current.status === "consumed" && current.ingestion?.state === "consumed" && current.ingestion.claimToken === claimToken || !success && current.status === "ingestion_error" && current.ingestion?.state === "retryable" && current.ingestion.claimToken === claimToken) {
|
|
810
|
+
result = current;
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (current.ingestion?.state !== "claimed" || current.ingestion.claimToken !== claimToken || (current.ingestion.leaseExpiresAt ?? 0) <= (options.now ?? Date.now()))
|
|
814
|
+
return;
|
|
815
|
+
const updatedAt = options.now ?? Date.now();
|
|
816
|
+
const next = {
|
|
817
|
+
...current,
|
|
818
|
+
schemaVersion: 3,
|
|
819
|
+
status: success ? "consumed" : "ingestion_error",
|
|
820
|
+
ingestion: {
|
|
821
|
+
state: success ? "consumed" : "retryable",
|
|
822
|
+
attempt: current.ingestion.attempt,
|
|
823
|
+
claimToken,
|
|
824
|
+
updatedAt
|
|
825
|
+
},
|
|
826
|
+
updatedAt
|
|
827
|
+
};
|
|
828
|
+
appendRecord(directory, next);
|
|
829
|
+
result = next;
|
|
830
|
+
});
|
|
831
|
+
return result;
|
|
832
|
+
} catch (err) {
|
|
833
|
+
warn(`[background] recordDelegationIngestionResult failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
async function putPendingBackgroundAdvisory(directory, correlationId, input) {
|
|
838
|
+
const createdAt = input.createdAt ?? Date.now();
|
|
839
|
+
const parsed = AdvisoryInboxSchema.safeParse({
|
|
840
|
+
eventId: input.eventId,
|
|
841
|
+
parentSessionId: input.parentSessionId,
|
|
842
|
+
state: "pending",
|
|
843
|
+
message: input.message,
|
|
844
|
+
createdAt
|
|
845
|
+
});
|
|
846
|
+
if (!correlationId || !parsed.success)
|
|
847
|
+
return null;
|
|
848
|
+
let result = null;
|
|
849
|
+
try {
|
|
850
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
851
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
852
|
+
if (!current?.terminalResult || current.terminalResult.eventId !== parsed.data.eventId || current.parentSessionId !== parsed.data.parentSessionId) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (current.advisoryInbox) {
|
|
856
|
+
if (current.advisoryInbox.eventId === parsed.data.eventId && current.advisoryInbox.parentSessionId === parsed.data.parentSessionId && current.advisoryInbox.message === parsed.data.message) {
|
|
857
|
+
result = current.advisoryInbox;
|
|
858
|
+
}
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const next = {
|
|
862
|
+
...current,
|
|
863
|
+
schemaVersion: 3,
|
|
864
|
+
advisoryInbox: parsed.data,
|
|
865
|
+
updatedAt: createdAt
|
|
866
|
+
};
|
|
867
|
+
appendRecord(directory, next);
|
|
868
|
+
result = parsed.data;
|
|
869
|
+
});
|
|
870
|
+
return result;
|
|
871
|
+
} catch (err) {
|
|
872
|
+
warn(`[background] putPendingBackgroundAdvisory failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
async function preparePendingBackgroundAdvisories(directory, parentSessionId, options) {
|
|
877
|
+
if (!parentSessionId || !options.preparationId)
|
|
878
|
+
return [];
|
|
879
|
+
const now = options.now ?? Date.now();
|
|
880
|
+
const leaseMs = Math.max(1000, Math.min(options.leaseMs ?? ADVISORY_PREPARE_LEASE_MS, 5 * 60000));
|
|
881
|
+
const prepared = [];
|
|
882
|
+
try {
|
|
883
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
884
|
+
for (const current of readDelegations(directory)) {
|
|
885
|
+
if (prepared.length >= 1)
|
|
886
|
+
break;
|
|
887
|
+
const advisory = current.advisoryInbox;
|
|
888
|
+
if (!advisory || advisory.parentSessionId !== parentSessionId || advisory.state !== "pending") {
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
if (advisory.preparation && advisory.preparation.id !== options.preparationId && advisory.preparation.leaseExpiresAt > now) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
const nextAdvisory = {
|
|
895
|
+
...advisory,
|
|
896
|
+
preparation: {
|
|
897
|
+
id: options.preparationId,
|
|
898
|
+
preparedAt: now,
|
|
899
|
+
leaseExpiresAt: now + leaseMs
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
const next = {
|
|
903
|
+
...current,
|
|
904
|
+
schemaVersion: 3,
|
|
905
|
+
advisoryInbox: nextAdvisory,
|
|
906
|
+
updatedAt: now
|
|
907
|
+
};
|
|
908
|
+
appendRecord(directory, next);
|
|
909
|
+
prepared.push(nextAdvisory);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
return prepared;
|
|
913
|
+
} catch (err) {
|
|
914
|
+
warn(`[background] preparePendingBackgroundAdvisories failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function releaseBackgroundAdvisoryPreparation(directory, parentSessionId, preparationId, eventIds) {
|
|
919
|
+
const uniqueEventIds = [...new Set(eventIds)];
|
|
920
|
+
if (!parentSessionId || !preparationId || uniqueEventIds.length !== 1) {
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
try {
|
|
924
|
+
return await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
925
|
+
const byEvent = new Map;
|
|
926
|
+
for (const current of readDelegations(directory)) {
|
|
927
|
+
const advisory = current.advisoryInbox;
|
|
928
|
+
if (advisory?.parentSessionId === parentSessionId && uniqueEventIds.includes(advisory.eventId)) {
|
|
929
|
+
byEvent.set(advisory.eventId, current);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
for (const eventId of uniqueEventIds) {
|
|
933
|
+
const advisory = byEvent.get(eventId)?.advisoryInbox;
|
|
934
|
+
if (!advisory || advisory.state !== "pending" || advisory.preparation?.id !== preparationId) {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
for (const eventId of uniqueEventIds) {
|
|
939
|
+
const current = byEvent.get(eventId);
|
|
940
|
+
if (!current?.advisoryInbox)
|
|
941
|
+
return false;
|
|
942
|
+
const now = Date.now();
|
|
943
|
+
const next = {
|
|
944
|
+
...current,
|
|
945
|
+
schemaVersion: 3,
|
|
946
|
+
advisoryInbox: {
|
|
947
|
+
...current.advisoryInbox,
|
|
948
|
+
preparation: undefined
|
|
949
|
+
},
|
|
950
|
+
updatedAt: now
|
|
951
|
+
};
|
|
952
|
+
appendRecord(directory, next);
|
|
953
|
+
}
|
|
954
|
+
return true;
|
|
955
|
+
});
|
|
956
|
+
} catch (err) {
|
|
957
|
+
warn(`[background] releasePreparedBackgroundAdvisories failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
async function acknowledgeObservedBackgroundAdvisories(directory, parentSessionId, observedTexts) {
|
|
962
|
+
if (!parentSessionId || observedTexts.length === 0)
|
|
963
|
+
return 0;
|
|
964
|
+
let acknowledged = 0;
|
|
965
|
+
try {
|
|
966
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
967
|
+
const now = Date.now();
|
|
968
|
+
for (const current of readDelegations(directory)) {
|
|
969
|
+
const advisory = current.advisoryInbox;
|
|
970
|
+
if (!advisory || advisory.parentSessionId !== parentSessionId || advisory.state !== "pending" || !observedTexts.some((text) => {
|
|
971
|
+
const advisoryBlock = text.match(/\[ADVISORIES\]([\s\S]*?)\[\/ADVISORIES\]/);
|
|
972
|
+
if (advisoryBlock) {
|
|
973
|
+
return advisoryBlock[1].split(`
|
|
974
|
+
---
|
|
975
|
+
`).some((entry) => entry.trim() === advisory.message);
|
|
976
|
+
}
|
|
977
|
+
return text.includes(advisory.message);
|
|
978
|
+
})) {
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
const next = {
|
|
982
|
+
...current,
|
|
983
|
+
schemaVersion: 3,
|
|
984
|
+
advisoryInbox: {
|
|
985
|
+
...advisory,
|
|
986
|
+
state: "delivered",
|
|
987
|
+
deliveredAt: now,
|
|
988
|
+
preparation: undefined
|
|
989
|
+
},
|
|
990
|
+
updatedAt: now
|
|
991
|
+
};
|
|
992
|
+
appendRecord(directory, next);
|
|
993
|
+
acknowledged += 1;
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
return acknowledged;
|
|
997
|
+
} catch (err) {
|
|
998
|
+
warn(`[background] acknowledgeObservedBackgroundAdvisories failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
999
|
+
return 0;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async function releasePreparedBackgroundAdvisories(directory, parentSessionId, preparationId, eventIds) {
|
|
1003
|
+
return releaseBackgroundAdvisoryPreparation(directory, parentSessionId, preparationId, eventIds);
|
|
1004
|
+
}
|
|
1005
|
+
function findByBatchId(directory, batchId, opts) {
|
|
1006
|
+
if (!batchId)
|
|
1007
|
+
return [];
|
|
1008
|
+
return readDelegations(directory).filter((record) => record.batchId === batchId && (opts?.parentSessionId === undefined || record.parentSessionId === opts.parentSessionId));
|
|
1009
|
+
}
|
|
1010
|
+
function findOpenAsyncLaneBatches(directory) {
|
|
1011
|
+
return readDelegations(directory).filter((record) => record.batchId !== undefined && (record.status === "pending" || record.status === "running"));
|
|
1012
|
+
}
|
|
1013
|
+
function isTerminal(status) {
|
|
1014
|
+
return status === "completed" || status === "error" || status === "cancelled" || status === "stale" || status === "consumed";
|
|
1015
|
+
}
|
|
1016
|
+
function sweepStaleLocked(directory, timeoutMs, now) {
|
|
1017
|
+
let swept = 0;
|
|
1018
|
+
for (const record of readDelegations(directory)) {
|
|
1019
|
+
if (record.status !== "pending" && record.status !== "running" && record.status !== "ingestion_error")
|
|
1020
|
+
continue;
|
|
1021
|
+
if (now - record.updatedAt <= timeoutMs)
|
|
1022
|
+
continue;
|
|
1023
|
+
appendRecord(directory, {
|
|
1024
|
+
...record,
|
|
1025
|
+
status: "stale",
|
|
1026
|
+
updatedAt: now
|
|
1027
|
+
});
|
|
1028
|
+
swept += 1;
|
|
1029
|
+
}
|
|
1030
|
+
return swept;
|
|
1031
|
+
}
|
|
1032
|
+
async function sweepStaleDelegations(directory, timeoutMs) {
|
|
1033
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
1034
|
+
return 0;
|
|
1035
|
+
try {
|
|
1036
|
+
return await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => sweepStaleLocked(directory, timeoutMs, Date.now()));
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
warn(`[background] sweepStaleDelegations failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1039
|
+
return 0;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function fallbackRelativePath(correlationId) {
|
|
1043
|
+
const digest = createHash("sha256").update(correlationId).digest("hex");
|
|
1044
|
+
return path.join(BACKGROUND_DELEGATION_FALLBACK_DIR, `${digest}.json`);
|
|
1045
|
+
}
|
|
1046
|
+
function fallbackPath(directory, correlationId) {
|
|
1047
|
+
return validateSwarmPath(directory, fallbackRelativePath(correlationId));
|
|
1048
|
+
}
|
|
1049
|
+
function fallbackDirectoryPath(directory) {
|
|
1050
|
+
return path.dirname(validateSwarmPath(directory, path.join(BACKGROUND_DELEGATION_FALLBACK_DIR, ".containment-anchor")));
|
|
1051
|
+
}
|
|
1052
|
+
async function readFallbackFile(directory, correlationId) {
|
|
1053
|
+
for (let attempt = 0;attempt < 5; attempt++) {
|
|
1054
|
+
try {
|
|
1055
|
+
const raw = await fs.promises.readFile(fallbackPath(directory, correlationId), "utf-8");
|
|
1056
|
+
const parsedJson = JSON.parse(raw);
|
|
1057
|
+
const parsed = FallbackArtifactSchema.safeParse(parsedJson);
|
|
1058
|
+
if (!parsed.success || parsed.data.correlationId !== correlationId) {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
return parsed.data;
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
const code = err.code;
|
|
1064
|
+
if (code === "ENOENT" && attempt < 4) {
|
|
1065
|
+
await new Promise((resolve2) => setTimeout(resolve2, 10));
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
async function readDelegationFallback(directory, correlationId) {
|
|
1074
|
+
if (!correlationId)
|
|
1075
|
+
return null;
|
|
1076
|
+
return readFallbackFile(directory, correlationId);
|
|
1077
|
+
}
|
|
1078
|
+
async function listDelegationFallbacks(directory) {
|
|
1079
|
+
let entries;
|
|
1080
|
+
try {
|
|
1081
|
+
entries = fs.readdirSync(fallbackDirectoryPath(directory), {
|
|
1082
|
+
withFileTypes: true
|
|
1083
|
+
}).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).slice(0, MAX_LIVE_BACKGROUND_FALLBACKS);
|
|
1084
|
+
} catch {
|
|
1085
|
+
return [];
|
|
1086
|
+
}
|
|
1087
|
+
const artifacts = [];
|
|
1088
|
+
for (const entry of entries) {
|
|
1089
|
+
try {
|
|
1090
|
+
const raw = await fs.promises.readFile(path.join(fallbackDirectoryPath(directory), entry.name), "utf-8");
|
|
1091
|
+
const parsed = FallbackArtifactSchema.safeParse(JSON.parse(raw));
|
|
1092
|
+
if (parsed.success)
|
|
1093
|
+
artifacts.push(parsed.data);
|
|
1094
|
+
} catch {}
|
|
1095
|
+
}
|
|
1096
|
+
return artifacts;
|
|
1097
|
+
}
|
|
1098
|
+
async function scanDelegationFallbacksForRecovery(directory) {
|
|
1099
|
+
let fallbackDir;
|
|
1100
|
+
let entries;
|
|
1101
|
+
try {
|
|
1102
|
+
fallbackDir = fallbackDirectoryPath(directory);
|
|
1103
|
+
entries = fs.readdirSync(fallbackDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json"));
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
if (error.code === "ENOENT") {
|
|
1106
|
+
return { status: "ok", owners: [] };
|
|
1107
|
+
}
|
|
1108
|
+
return {
|
|
1109
|
+
status: "uncertain",
|
|
1110
|
+
reason: `background fallback directory is unreadable: ${error instanceof Error ? error.message : String(error)}`
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (entries.length > MAX_LIVE_BACKGROUND_FALLBACKS) {
|
|
1114
|
+
return {
|
|
1115
|
+
status: "uncertain",
|
|
1116
|
+
reason: `background fallback owner count exceeds the ${MAX_LIVE_BACKGROUND_FALLBACKS}-artifact safety bound`
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
const artifacts = [];
|
|
1120
|
+
for (const entry of entries) {
|
|
1121
|
+
const artifactPath = path.join(fallbackDir, entry.name);
|
|
1122
|
+
let raw;
|
|
1123
|
+
try {
|
|
1124
|
+
const stat = fs.statSync(artifactPath);
|
|
1125
|
+
if (stat.size > MAX_RECOVERY_FALLBACK_BYTES) {
|
|
1126
|
+
return {
|
|
1127
|
+
status: "uncertain",
|
|
1128
|
+
reason: `background fallback artifact "${entry.name}" exceeds the recovery size bound`
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
raw = await fs.promises.readFile(artifactPath, "utf-8");
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
return {
|
|
1134
|
+
status: "uncertain",
|
|
1135
|
+
reason: `background fallback artifact "${entry.name}" is unreadable: ${error instanceof Error ? error.message : String(error)}`
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_RECOVERY_FALLBACK_BYTES) {
|
|
1139
|
+
return {
|
|
1140
|
+
status: "uncertain",
|
|
1141
|
+
reason: `background fallback artifact "${entry.name}" changed beyond the recovery size bound`
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
let parsedJson;
|
|
1145
|
+
try {
|
|
1146
|
+
parsedJson = JSON.parse(raw);
|
|
1147
|
+
} catch {
|
|
1148
|
+
return {
|
|
1149
|
+
status: "uncertain",
|
|
1150
|
+
reason: `background fallback artifact "${entry.name}" has malformed JSON`
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
const parsed = FallbackArtifactSchema.safeParse(parsedJson);
|
|
1154
|
+
if (!parsed.success) {
|
|
1155
|
+
return {
|
|
1156
|
+
status: "uncertain",
|
|
1157
|
+
reason: `background fallback artifact "${entry.name}" has an invalid owner record`
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
artifacts.push(parsed.data);
|
|
1161
|
+
}
|
|
1162
|
+
return { status: "ok", owners: artifacts };
|
|
1163
|
+
}
|
|
1164
|
+
function countFallbackFiles(directory) {
|
|
1165
|
+
try {
|
|
1166
|
+
return fs.readdirSync(fallbackDirectoryPath(directory), {
|
|
1167
|
+
withFileTypes: true
|
|
1168
|
+
}).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).length;
|
|
1169
|
+
} catch (err) {
|
|
1170
|
+
if (err.code === "ENOENT")
|
|
1171
|
+
return 0;
|
|
1172
|
+
return MAX_LIVE_BACKGROUND_FALLBACKS;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
async function writeDelegationFallback(directory, input, options = {}) {
|
|
1176
|
+
const now = Date.now();
|
|
1177
|
+
const record = {
|
|
1178
|
+
...buildPendingRecord(input, now),
|
|
1179
|
+
schemaVersion: 3
|
|
1180
|
+
};
|
|
1181
|
+
const artifact = {
|
|
1182
|
+
schemaVersion: 1,
|
|
1183
|
+
correlationId: input.correlationId,
|
|
1184
|
+
createdAt: now,
|
|
1185
|
+
record
|
|
1186
|
+
};
|
|
1187
|
+
const parsed = FallbackArtifactSchema.safeParse(artifact);
|
|
1188
|
+
if (!parsed.success)
|
|
1189
|
+
return null;
|
|
1190
|
+
const requestedCap = Math.floor(Math.max(1, options.maxLive ?? MAX_LIVE_BACKGROUND_FALLBACKS));
|
|
1191
|
+
const maxLive = Math.min(requestedCap, MAX_LIVE_BACKGROUND_FALLBACKS);
|
|
1192
|
+
try {
|
|
1193
|
+
return await withEvidenceLock(directory, BACKGROUND_DELEGATION_FALLBACK_DIR, STORE_LOCK_AGENT, FALLBACK_LOCK_TASK, async () => {
|
|
1194
|
+
const existing = await readFallbackFile(directory, input.correlationId);
|
|
1195
|
+
if (existing) {
|
|
1196
|
+
return samePendingRecord(existing.record, parsed.data.record) ? existing : null;
|
|
1197
|
+
}
|
|
1198
|
+
if (countFallbackFiles(directory) >= maxLive)
|
|
1199
|
+
return null;
|
|
1200
|
+
const absPath = fallbackPath(directory, input.correlationId);
|
|
1201
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
1202
|
+
await bunWrite(absPath, `${JSON.stringify(parsed.data)}
|
|
1203
|
+
`);
|
|
1204
|
+
return parsed.data;
|
|
1205
|
+
});
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
warn(`[background] writeDelegationFallback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
async function removeDelegationFallback(directory, correlationId) {
|
|
1212
|
+
if (!correlationId)
|
|
1213
|
+
return false;
|
|
1214
|
+
try {
|
|
1215
|
+
return await withEvidenceLock(directory, BACKGROUND_DELEGATION_FALLBACK_DIR, STORE_LOCK_AGENT, FALLBACK_LOCK_TASK, async () => {
|
|
1216
|
+
try {
|
|
1217
|
+
await fs.promises.unlink(fallbackPath(directory, correlationId));
|
|
1218
|
+
return true;
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
return err.code === "ENOENT";
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
} catch (err) {
|
|
1224
|
+
warn(`[background] removeDelegationFallback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async function findDelegationForCompletion(directory, correlationId) {
|
|
1229
|
+
if (!correlationId)
|
|
1230
|
+
return null;
|
|
1231
|
+
const primary = findByCorrelationId(directory, correlationId);
|
|
1232
|
+
if (primary)
|
|
1233
|
+
return { source: "primary", record: primary };
|
|
1234
|
+
const fallback = await readDelegationFallback(directory, correlationId);
|
|
1235
|
+
return fallback ? { source: "fallback", record: fallback.record, fallback } : null;
|
|
1236
|
+
}
|
|
1237
|
+
function samePromotionIdentity(primary, fallback) {
|
|
1238
|
+
return primary.correlationId === fallback.correlationId && primary.subagentSessionId === fallback.subagentSessionId && primary.parentSessionId === fallback.parentSessionId && primary.callID === fallback.callID && primary.planTaskId === fallback.planTaskId && primary.coderReservationId === fallback.coderReservationId && sameJson(primary.worktree ?? null, fallback.worktree ?? null) && sameJson(primary.taskChangeContext ?? null, fallback.taskChangeContext ?? null);
|
|
1239
|
+
}
|
|
1240
|
+
function samePendingRecord(left, right) {
|
|
1241
|
+
const {
|
|
1242
|
+
createdAt: _leftCreatedAt,
|
|
1243
|
+
updatedAt: _leftUpdatedAt,
|
|
1244
|
+
...leftIdentity
|
|
1245
|
+
} = left;
|
|
1246
|
+
const {
|
|
1247
|
+
createdAt: _rightCreatedAt,
|
|
1248
|
+
updatedAt: _rightUpdatedAt,
|
|
1249
|
+
...rightIdentity
|
|
1250
|
+
} = right;
|
|
1251
|
+
return sameJson(leftIdentity, rightIdentity);
|
|
1252
|
+
}
|
|
1253
|
+
async function promoteDelegationFallback(directory, correlationId) {
|
|
1254
|
+
if (!correlationId)
|
|
1255
|
+
return null;
|
|
1256
|
+
try {
|
|
1257
|
+
return await withEvidenceLock(directory, BACKGROUND_DELEGATION_FALLBACK_DIR, STORE_LOCK_AGENT, FALLBACK_LOCK_TASK, async () => {
|
|
1258
|
+
const fallback = await readFallbackFile(directory, correlationId);
|
|
1259
|
+
if (!fallback) {
|
|
1260
|
+
const primary = findByCorrelationId(directory, correlationId);
|
|
1261
|
+
return primary ? { source: "primary", record: primary } : null;
|
|
1262
|
+
}
|
|
1263
|
+
let promoted = null;
|
|
1264
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
1265
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
1266
|
+
if (current) {
|
|
1267
|
+
if (samePromotionIdentity(current, fallback.record)) {
|
|
1268
|
+
promoted = current;
|
|
1269
|
+
}
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
appendRecord(directory, fallback.record);
|
|
1273
|
+
promoted = fallback.record;
|
|
1274
|
+
});
|
|
1275
|
+
if (!promoted)
|
|
1276
|
+
return null;
|
|
1277
|
+
try {
|
|
1278
|
+
await fs.promises.unlink(fallbackPath(directory, correlationId));
|
|
1279
|
+
} catch (err) {
|
|
1280
|
+
if (err.code !== "ENOENT") {
|
|
1281
|
+
return null;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return { source: "primary", record: promoted };
|
|
1285
|
+
});
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
warn(`[background] promoteDelegationFallback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
var MAX_BACKGROUND_CODER_RESERVATION_STORE_BYTES = 2 * 1024 * 1024;
|
|
1292
|
+
function reservationStorePath(directory) {
|
|
1293
|
+
return validateSwarmPath(directory, BACKGROUND_CODER_RESERVATIONS_FILE);
|
|
1294
|
+
}
|
|
1295
|
+
function buildBackgroundCoderReservationId(input) {
|
|
1296
|
+
const ownerIdentity = input.planTaskId === null ? ["call", input.parentSessionId, input.callID] : ["task", input.parentSessionId, input.planTaskId];
|
|
1297
|
+
return `bgcr1:${createHash("sha256").update(JSON.stringify(ownerIdentity)).digest("hex")}`;
|
|
1298
|
+
}
|
|
1299
|
+
function scanBackgroundCoderReservationsForAdmission(directory) {
|
|
1300
|
+
let absolutePath;
|
|
1301
|
+
let raw;
|
|
1302
|
+
try {
|
|
1303
|
+
absolutePath = reservationStorePath(directory);
|
|
1304
|
+
const stat = fs.statSync(absolutePath);
|
|
1305
|
+
if (stat.size > MAX_BACKGROUND_CODER_RESERVATION_STORE_BYTES) {
|
|
1306
|
+
return {
|
|
1307
|
+
status: "uncertain",
|
|
1308
|
+
reason: `background coder reservation store exceeds the ${MAX_BACKGROUND_CODER_RESERVATION_STORE_BYTES}-byte safety bound`
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
raw = fs.readFileSync(absolutePath, "utf-8");
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
if (error.code === "ENOENT") {
|
|
1314
|
+
return { status: "ok", reservations: [] };
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
status: "uncertain",
|
|
1318
|
+
reason: `background coder reservation store is unreadable: ${error instanceof Error ? error.message : String(error)}`
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_BACKGROUND_CODER_RESERVATION_STORE_BYTES) {
|
|
1322
|
+
return {
|
|
1323
|
+
status: "uncertain",
|
|
1324
|
+
reason: "background coder reservation store changed beyond its safety bound"
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
let parsedJson;
|
|
1328
|
+
try {
|
|
1329
|
+
parsedJson = JSON.parse(raw);
|
|
1330
|
+
} catch {
|
|
1331
|
+
return {
|
|
1332
|
+
status: "uncertain",
|
|
1333
|
+
reason: "background coder reservation store contains malformed JSON"
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
const parsed = BackgroundCoderReservationStoreSchema.safeParse(parsedJson);
|
|
1337
|
+
if (!parsed.success) {
|
|
1338
|
+
return {
|
|
1339
|
+
status: "uncertain",
|
|
1340
|
+
reason: "background coder reservation store failed strict schema validation"
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
return { status: "ok", reservations: parsed.data.reservations };
|
|
1344
|
+
}
|
|
1345
|
+
async function writeBackgroundCoderReservations(directory, reservations) {
|
|
1346
|
+
const parsed = BackgroundCoderReservationStoreSchema.safeParse({
|
|
1347
|
+
schemaVersion: 1,
|
|
1348
|
+
reservations
|
|
1349
|
+
});
|
|
1350
|
+
if (!parsed.success)
|
|
1351
|
+
return false;
|
|
1352
|
+
try {
|
|
1353
|
+
await bunWrite(reservationStorePath(directory), `${JSON.stringify(parsed.data)}
|
|
1354
|
+
`);
|
|
1355
|
+
return true;
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
warn(`[background] reservation write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function reservationOwnerKey(input) {
|
|
1362
|
+
return input.planTaskId === null ? `call:${input.parentSessionId}:${input.callID}` : `task:${input.parentSessionId}:${input.planTaskId}`;
|
|
1363
|
+
}
|
|
1364
|
+
function isActiveCoderOwner(record) {
|
|
1365
|
+
if (record.normalizedAgent !== "coder" || record.status === "consumed") {
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
if (record.status === "pending" || record.status === "running" || record.status === "completed" || record.status === "ingestion_error") {
|
|
1369
|
+
return true;
|
|
1370
|
+
}
|
|
1371
|
+
if (record.coderSettlement?.state === "preserved")
|
|
1372
|
+
return true;
|
|
1373
|
+
return record.worktree !== undefined;
|
|
1374
|
+
}
|
|
1375
|
+
async function scanDurableCoderOwners(directory) {
|
|
1376
|
+
const fallbackScan = await scanDelegationFallbacksForRecovery(directory);
|
|
1377
|
+
if (fallbackScan.status === "uncertain")
|
|
1378
|
+
return fallbackScan;
|
|
1379
|
+
const primaryScan = scanDelegationsForRecovery(directory);
|
|
1380
|
+
if (primaryScan.status === "uncertain")
|
|
1381
|
+
return primaryScan;
|
|
1382
|
+
const recordsByCorrelation = new Map;
|
|
1383
|
+
for (const artifact of fallbackScan.owners) {
|
|
1384
|
+
recordsByCorrelation.set(artifact.correlationId, artifact.record);
|
|
1385
|
+
}
|
|
1386
|
+
const primaryByCorrelation = new Map;
|
|
1387
|
+
const primaryByReservationId = new Map;
|
|
1388
|
+
for (const record of primaryScan.owners) {
|
|
1389
|
+
primaryByCorrelation.set(record.correlationId, record);
|
|
1390
|
+
if (record.coderReservationId) {
|
|
1391
|
+
const owners = primaryByReservationId.get(record.coderReservationId) ?? [];
|
|
1392
|
+
owners.push(record);
|
|
1393
|
+
primaryByReservationId.set(record.coderReservationId, owners);
|
|
1394
|
+
}
|
|
1395
|
+
recordsByCorrelation.set(record.correlationId, record);
|
|
1396
|
+
}
|
|
1397
|
+
return {
|
|
1398
|
+
status: "ok",
|
|
1399
|
+
recordsByCorrelation,
|
|
1400
|
+
primaryByCorrelation,
|
|
1401
|
+
primaryByReservationId
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
function exactReservationOwnerCoordinates(reservation, record) {
|
|
1405
|
+
return record.coderReservationId === reservation.reservationId && record.parentSessionId === reservation.parentSessionId && record.planTaskId === reservation.planTaskId && record.callID === reservation.callID;
|
|
1406
|
+
}
|
|
1407
|
+
function exactReservationRecordMatch(reservation, record) {
|
|
1408
|
+
return exactReservationOwnerCoordinates(reservation, record) && record.correlationId === reservation.correlationId;
|
|
1409
|
+
}
|
|
1410
|
+
function findExactPrimaryReservationOwner(reservation, ownerScan) {
|
|
1411
|
+
if (reservation.correlationId !== null) {
|
|
1412
|
+
const primary = ownerScan.primaryByCorrelation.get(reservation.correlationId);
|
|
1413
|
+
return primary && exactReservationRecordMatch(reservation, primary) ? primary : null;
|
|
1414
|
+
}
|
|
1415
|
+
const exactOwners = (ownerScan.primaryByReservationId.get(reservation.reservationId) ?? []).filter((record) => exactReservationOwnerCoordinates(reservation, record));
|
|
1416
|
+
return exactOwners.length === 1 ? exactOwners[0] : null;
|
|
1417
|
+
}
|
|
1418
|
+
function hasProvenReleasedReservationOwner(record) {
|
|
1419
|
+
if (record.status === "consumed")
|
|
1420
|
+
return true;
|
|
1421
|
+
return record.worktree === undefined && (record.status === "error" || record.status === "cancelled") && record.terminalResult?.status === record.status;
|
|
1422
|
+
}
|
|
1423
|
+
function validateReservationIdentity(input) {
|
|
1424
|
+
return typeof input.parentSessionId === "string" && input.parentSessionId.length > 0 && input.parentSessionId.length <= 256 && input.parentSessionId.trim() === input.parentSessionId && typeof input.callID === "string" && input.callID.length > 0 && input.callID.length <= 256 && input.callID.trim() === input.callID && (input.planTaskId === null || typeof input.planTaskId === "string" && input.planTaskId.length > 0 && input.planTaskId.length <= 256 && input.planTaskId.trim() === input.planTaskId);
|
|
1425
|
+
}
|
|
1426
|
+
async function reserveBackgroundCoderSlot(directory, input) {
|
|
1427
|
+
if (!validateReservationIdentity(input) || !Number.isInteger(input.maxConcurrent) || input.maxConcurrent < 1 || input.maxConcurrent > 64 || (input.occupiedTaskIds?.length ?? 0) > 64) {
|
|
1428
|
+
return { ok: false, reason: "invalid" };
|
|
1429
|
+
}
|
|
1430
|
+
const occupiedTaskIds = new Set;
|
|
1431
|
+
for (const taskId of input.occupiedTaskIds ?? []) {
|
|
1432
|
+
if (typeof taskId !== "string" || taskId.length === 0 || taskId.length > 256 || taskId.trim() !== taskId) {
|
|
1433
|
+
return { ok: false, reason: "invalid" };
|
|
1434
|
+
}
|
|
1435
|
+
occupiedTaskIds.add(taskId);
|
|
1436
|
+
}
|
|
1437
|
+
try {
|
|
1438
|
+
return await withEvidenceLock(directory, BACKGROUND_CODER_RESERVATIONS_FILE, STORE_LOCK_AGENT, RESERVATION_LOCK_TASK, async () => {
|
|
1439
|
+
const reservationScan = scanBackgroundCoderReservationsForAdmission(directory);
|
|
1440
|
+
if (reservationScan.status === "uncertain") {
|
|
1441
|
+
return {
|
|
1442
|
+
ok: false,
|
|
1443
|
+
reason: "uncertain",
|
|
1444
|
+
detail: reservationScan.reason
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
const ownerScan = await scanDurableCoderOwners(directory);
|
|
1448
|
+
if (ownerScan.status === "uncertain") {
|
|
1449
|
+
return {
|
|
1450
|
+
ok: false,
|
|
1451
|
+
reason: "uncertain",
|
|
1452
|
+
detail: ownerScan.reason
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
const reservations = reservationScan.reservations.filter((reservation2) => {
|
|
1456
|
+
const primary = findExactPrimaryReservationOwner(reservation2, ownerScan);
|
|
1457
|
+
return !(primary && hasProvenReleasedReservationOwner(primary));
|
|
1458
|
+
});
|
|
1459
|
+
if (reservations.length !== reservationScan.reservations.length && !await writeBackgroundCoderReservations(directory, reservations)) {
|
|
1460
|
+
return {
|
|
1461
|
+
ok: false,
|
|
1462
|
+
reason: "uncertain",
|
|
1463
|
+
detail: "finished reservation reconciliation could not be persisted"
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
const activeOwnerKeys = new Set;
|
|
1467
|
+
for (const taskId of occupiedTaskIds) {
|
|
1468
|
+
activeOwnerKeys.add(reservationOwnerKey({
|
|
1469
|
+
parentSessionId: input.parentSessionId,
|
|
1470
|
+
planTaskId: taskId,
|
|
1471
|
+
callID: ""
|
|
1472
|
+
}));
|
|
1473
|
+
}
|
|
1474
|
+
for (const reservation2 of reservations) {
|
|
1475
|
+
if (reservation2.parentSessionId === input.parentSessionId) {
|
|
1476
|
+
activeOwnerKeys.add(reservationOwnerKey(reservation2));
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
for (const record of ownerScan.recordsByCorrelation.values()) {
|
|
1480
|
+
if (record.parentSessionId === input.parentSessionId && isActiveCoderOwner(record)) {
|
|
1481
|
+
activeOwnerKeys.add(reservationOwnerKey({
|
|
1482
|
+
parentSessionId: record.parentSessionId,
|
|
1483
|
+
planTaskId: record.planTaskId,
|
|
1484
|
+
callID: record.callID
|
|
1485
|
+
}));
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const reservationId = buildBackgroundCoderReservationId(input);
|
|
1489
|
+
const existing = reservations.find((reservation2) => reservation2.reservationId === reservationId);
|
|
1490
|
+
if (existing) {
|
|
1491
|
+
return {
|
|
1492
|
+
ok: false,
|
|
1493
|
+
reason: input.planTaskId === null ? "duplicate_call" : "duplicate_task",
|
|
1494
|
+
activeCount: activeOwnerKeys.size,
|
|
1495
|
+
existing
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
const incomingOwnerKey = reservationOwnerKey(input);
|
|
1499
|
+
if (input.planTaskId !== null && activeOwnerKeys.has(incomingOwnerKey)) {
|
|
1500
|
+
return {
|
|
1501
|
+
ok: false,
|
|
1502
|
+
reason: "duplicate_task",
|
|
1503
|
+
activeCount: activeOwnerKeys.size
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
if (activeOwnerKeys.size >= input.maxConcurrent) {
|
|
1507
|
+
return {
|
|
1508
|
+
ok: false,
|
|
1509
|
+
reason: "capacity",
|
|
1510
|
+
activeCount: activeOwnerKeys.size
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
if (reservations.length >= MAX_LIVE_BACKGROUND_CODER_RESERVATIONS) {
|
|
1514
|
+
return {
|
|
1515
|
+
ok: false,
|
|
1516
|
+
reason: "capacity",
|
|
1517
|
+
activeCount: activeOwnerKeys.size,
|
|
1518
|
+
detail: "durable reservation store is at its hard safety cap"
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
const now = input.now ?? Date.now();
|
|
1522
|
+
const reservation = {
|
|
1523
|
+
reservationId,
|
|
1524
|
+
parentSessionId: input.parentSessionId,
|
|
1525
|
+
planTaskId: input.planTaskId,
|
|
1526
|
+
callID: input.callID,
|
|
1527
|
+
state: "reserved",
|
|
1528
|
+
correlationId: null,
|
|
1529
|
+
createdAt: now,
|
|
1530
|
+
updatedAt: now
|
|
1531
|
+
};
|
|
1532
|
+
if (!await writeBackgroundCoderReservations(directory, [
|
|
1533
|
+
...reservations,
|
|
1534
|
+
reservation
|
|
1535
|
+
])) {
|
|
1536
|
+
return {
|
|
1537
|
+
ok: false,
|
|
1538
|
+
reason: "uncertain",
|
|
1539
|
+
detail: "durable reservation claim could not be persisted"
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
return {
|
|
1543
|
+
ok: true,
|
|
1544
|
+
reservation,
|
|
1545
|
+
activeCount: activeOwnerKeys.size + 1
|
|
1546
|
+
};
|
|
1547
|
+
});
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
return {
|
|
1550
|
+
ok: false,
|
|
1551
|
+
reason: "uncertain",
|
|
1552
|
+
detail: `background coder reservation lock failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
async function bindBackgroundCoderReservation(directory, input) {
|
|
1557
|
+
if (!validateReservationIdentity(input) || !input.reservationId || !input.correlationId || input.correlationId.length > 256 || input.correlationId.trim() !== input.correlationId || input.reservationId !== buildBackgroundCoderReservationId(input)) {
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
return await withEvidenceLock(directory, BACKGROUND_CODER_RESERVATIONS_FILE, STORE_LOCK_AGENT, RESERVATION_LOCK_TASK, async () => {
|
|
1562
|
+
const scan = scanBackgroundCoderReservationsForAdmission(directory);
|
|
1563
|
+
if (scan.status === "uncertain")
|
|
1564
|
+
return null;
|
|
1565
|
+
const index = scan.reservations.findIndex((reservation) => reservation.reservationId === input.reservationId);
|
|
1566
|
+
if (index < 0)
|
|
1567
|
+
return null;
|
|
1568
|
+
const current = scan.reservations[index];
|
|
1569
|
+
if (current.parentSessionId !== input.parentSessionId || current.planTaskId !== input.planTaskId || current.callID !== input.callID) {
|
|
1570
|
+
return null;
|
|
1571
|
+
}
|
|
1572
|
+
if (current.state === "bound") {
|
|
1573
|
+
return current.correlationId === input.correlationId ? current : null;
|
|
1574
|
+
}
|
|
1575
|
+
const next = {
|
|
1576
|
+
...current,
|
|
1577
|
+
state: "bound",
|
|
1578
|
+
correlationId: input.correlationId,
|
|
1579
|
+
updatedAt: input.now ?? Date.now()
|
|
1580
|
+
};
|
|
1581
|
+
const reservations = [...scan.reservations];
|
|
1582
|
+
reservations[index] = next;
|
|
1583
|
+
return await writeBackgroundCoderReservations(directory, reservations) ? next : null;
|
|
1584
|
+
});
|
|
1585
|
+
} catch {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
async function releaseBackgroundCoderReservation(directory, input) {
|
|
1590
|
+
if (!validateReservationIdentity(input) || !input.reservationId || input.reservationId !== buildBackgroundCoderReservationId(input) || input.correlationId !== null && (input.correlationId.length === 0 || input.correlationId.length > 256 || input.correlationId.trim() !== input.correlationId)) {
|
|
1591
|
+
return false;
|
|
1592
|
+
}
|
|
1593
|
+
try {
|
|
1594
|
+
return await withEvidenceLock(directory, BACKGROUND_CODER_RESERVATIONS_FILE, STORE_LOCK_AGENT, RESERVATION_LOCK_TASK, async () => {
|
|
1595
|
+
const scan = scanBackgroundCoderReservationsForAdmission(directory);
|
|
1596
|
+
if (scan.status === "uncertain")
|
|
1597
|
+
return false;
|
|
1598
|
+
const index = scan.reservations.findIndex((reservation) => reservation.reservationId === input.reservationId);
|
|
1599
|
+
if (index < 0)
|
|
1600
|
+
return false;
|
|
1601
|
+
const current = scan.reservations[index];
|
|
1602
|
+
if (current.parentSessionId !== input.parentSessionId || current.planTaskId !== input.planTaskId || current.callID !== input.callID || current.correlationId !== input.correlationId) {
|
|
1603
|
+
return false;
|
|
1604
|
+
}
|
|
1605
|
+
if (input.reason === "consumed") {
|
|
1606
|
+
if (current.state !== "bound" || !current.correlationId) {
|
|
1607
|
+
return false;
|
|
1608
|
+
}
|
|
1609
|
+
const ownerScan = await scanDurableCoderOwners(directory);
|
|
1610
|
+
if (ownerScan.status === "uncertain")
|
|
1611
|
+
return false;
|
|
1612
|
+
const primary = ownerScan.primaryByCorrelation.get(current.correlationId);
|
|
1613
|
+
if (!primary || primary.status !== "consumed" || !exactReservationRecordMatch(current, primary)) {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
const reservations = scan.reservations.filter((_, reservationIndex) => reservationIndex !== index);
|
|
1618
|
+
return writeBackgroundCoderReservations(directory, reservations);
|
|
1619
|
+
});
|
|
1620
|
+
} catch {
|
|
1621
|
+
return false;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
export { BACKGROUND_DELEGATIONS_FILE, BACKGROUND_DELEGATION_FALLBACK_DIR, BACKGROUND_CODER_RESERVATIONS_FILE, MAX_LIVE_BACKGROUND_FALLBACKS, MAX_LIVE_BACKGROUND_CODER_RESERVATIONS, MAX_BACKGROUND_OBSERVED_FILES, MAX_BACKGROUND_ADVISORY_CHARS, BACKGROUND_INGESTION_LEASE_MS, readDelegations, scanDelegationsForRecovery, findByCorrelationId, recordPendingDelegation, buildPromptSnapshot, appendDelegationTransition, buildBackgroundCompletionEventId, claimTerminalResult, claimCoderSettlement, normalizeBackgroundObservedFiles, updateCoderSettlement, claimDelegationIngestion, recordDelegationIngestionResult, putPendingBackgroundAdvisory, preparePendingBackgroundAdvisories, acknowledgeObservedBackgroundAdvisories, releasePreparedBackgroundAdvisories, findByBatchId, findOpenAsyncLaneBatches, sweepStaleDelegations, readDelegationFallback, listDelegationFallbacks, scanDelegationFallbacksForRecovery, writeDelegationFallback, removeDelegationFallback, findDelegationForCompletion, promoteDelegationFallback, buildBackgroundCoderReservationId, scanBackgroundCoderReservationsForAdmission, reserveBackgroundCoderSlot, bindBackgroundCoderReservation, releaseBackgroundCoderReservation };
|