@abelxiaoxing/cadence 1.2.0 → 1.2.2

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.
@@ -20,8 +20,15 @@ Your output is one complete structured patch delivered through `abel_submit_resu
20
20
  - every operation path is within the declared phase boundary; paths that do
21
21
  not need a change may remain untouched;
22
22
  - preserve the expected parent-owned verification contract without claiming a result;
23
- - use a typed `context-request` for bounded context, task splitting, or an
24
- approval-boundary blocker instead of submitting a partial patch.
23
+ - use a typed `context-request` for bounded context or task splitting instead
24
+ of submitting a partial patch; classify every ref as a requested path with
25
+ `read | write` access, a source citation with path and line, or a
26
+ contract-diagnostic ref;
27
+ - report only the missing context and structural facts: never select
28
+ `approval-needed`, `boundary-review-needed`, a Gate, or a workflow stage;
29
+ - an `AGENTS.md` path is never a Worker write path;
30
+ - report an AGENTS read need separately, and leave every sealed AGENTS contract
31
+ and managed-block update to the parent control plane.
25
32
 
26
33
  If the submit tool rejects malformed operations, use its concrete error to correct the submission once in this same disposable session; a second rejected structural submission ends the session.
27
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abelxiaoxing/cadence",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Four explicit Abel workflow prompts with stage-isolated design and diagnosis, durable resumable implementation, and three package-owned professional Agents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -74,6 +74,64 @@ function isRecord(value: unknown): value is Record<string, unknown> {
74
74
  return value !== null && typeof value === "object" && !Array.isArray(value);
75
75
  }
76
76
 
77
+ const CONTROL_TOOL_FIELDS = new Set([
78
+ "command",
79
+ "stage",
80
+ "change",
81
+ "operationId",
82
+ "deliveryRevision",
83
+ "receiptHash",
84
+ "routeId",
85
+ ]);
86
+
87
+ const COMMAND_FIELDS: Readonly<
88
+ Record<ControlCommandName, ReadonlySet<string>>
89
+ > = Object.freeze({
90
+ start: new Set(["command", "stage", "change", "operationId"]),
91
+ status: new Set(["command", "stage", "change"]),
92
+ resume: new Set([
93
+ "command",
94
+ "stage",
95
+ "change",
96
+ "operationId",
97
+ "deliveryRevision",
98
+ "receiptHash",
99
+ ]),
100
+ rebind: new Set(["command", "stage", "change", "operationId", "routeId"]),
101
+ cancel: new Set(["command", "stage", "change", "operationId"]),
102
+ discard: new Set(["command", "stage", "change", "operationId"]),
103
+ });
104
+
105
+ /**
106
+ * Adapts Pi/provider strict-schema padding to the closed command union.
107
+ * Known fields belonging to a different command are projected away, while
108
+ * unknown fields and fields owned by the selected command remain available to
109
+ * the strict validator and therefore cannot bypass its exact-key/type checks.
110
+ */
111
+ export function canonicalizeControlCommandToolInput(value: unknown): unknown {
112
+ if (
113
+ !isRecord(value) ||
114
+ !(CONTROL_COMMANDS as readonly unknown[]).includes(value.command)
115
+ ) {
116
+ return value;
117
+ }
118
+ const command = value.command as ControlCommandName;
119
+ const allowed = COMMAND_FIELDS[command];
120
+ const canonical = Object.create(null) as Record<string, unknown>;
121
+ for (const [key, entry] of Object.entries(value)) {
122
+ if (CONTROL_TOOL_FIELDS.has(key) && !allowed.has(key)) continue;
123
+ if (
124
+ command === "resume" &&
125
+ (key === "deliveryRevision" || key === "receiptHash") &&
126
+ (entry === null || entry === undefined)
127
+ ) {
128
+ continue;
129
+ }
130
+ canonical[key] = entry;
131
+ }
132
+ return canonical;
133
+ }
134
+
77
135
  function hasExactKeys(
78
136
  value: Record<string, unknown>,
79
137
  required: readonly string[],
package/src/index.ts CHANGED
@@ -37,7 +37,10 @@ import {
37
37
  type StructuredVerificationContract,
38
38
  verificationSteps,
39
39
  } from "./contracts.ts";
40
- import { validateControlCommand } from "./control-contracts.ts";
40
+ import {
41
+ canonicalizeControlCommandToolInput,
42
+ validateControlCommand,
43
+ } from "./control-contracts.ts";
41
44
  import {
42
45
  assessDeliveryTraceability,
43
46
  compileImplementPlan,
@@ -1522,19 +1525,46 @@ export function openPackageWorkflowControlEngine(
1522
1525
  if (!phase) {
1523
1526
  return { kind: "paused", code: "task-phase-unavailable" };
1524
1527
  }
1528
+ const taskPhases = Object.values(input.task.phases);
1529
+ const executionBoundary = input.artifactCorrection
1530
+ ? {
1531
+ read: [
1532
+ ...new Set(
1533
+ taskPhases.flatMap((boundary) => [
1534
+ ...boundary.read,
1535
+ ...boundary.write,
1536
+ ...boundary.delete,
1537
+ ]),
1538
+ ),
1539
+ ].sort(),
1540
+ write: [
1541
+ ...new Set(taskPhases.flatMap((boundary) => boundary.write)),
1542
+ ].sort(),
1543
+ delete: [
1544
+ ...new Set(taskPhases.flatMap((boundary) => boundary.delete)),
1545
+ ].sort(),
1546
+ }
1547
+ : {
1548
+ read: [...phase.read],
1549
+ write: [...phase.write],
1550
+ delete: [...phase.delete],
1551
+ };
1525
1552
  const phaseContract = {
1526
1553
  candidateId: input.candidateArtifact.identity.candidateId,
1527
1554
  taskId: input.taskId,
1528
1555
  phase: input.phase,
1529
- readSet: [...phase.read],
1530
- writeSet: [...phase.write],
1531
- deleteSet: [...phase.delete],
1556
+ readSet: executionBoundary.read,
1557
+ writeSet: executionBoundary.write,
1558
+ deleteSet: executionBoundary.delete,
1532
1559
  verification: structuredClone(phase.verification),
1533
1560
  agentsImpact: input.task.agents.impact,
1534
1561
  agentsTarget: input.task.agents.target ?? null,
1535
1562
  agentsManagedOnly: true,
1536
1563
  agentsWriteAllowed: false,
1537
1564
  impactClosure: structuredClone(input.task.impactClosure),
1565
+ ...(input.artifactCorrection
1566
+ ? { artifactCorrection: structuredClone(input.artifactCorrection) }
1567
+ : {}),
1538
1568
  ...(input.repair
1539
1569
  ? {
1540
1570
  repair: {
@@ -1567,7 +1597,11 @@ export function openPackageWorkflowControlEngine(
1567
1597
  path.resolve(input.workspaceRoot, root),
1568
1598
  ),
1569
1599
  allowedPaths: [
1570
- ...new Set([...phase.read, ...phase.write, ...phase.delete]),
1600
+ ...new Set([
1601
+ ...executionBoundary.read,
1602
+ ...executionBoundary.write,
1603
+ ...executionBoundary.delete,
1604
+ ]),
1571
1605
  ],
1572
1606
  timeoutMs: LIMITS.phaseTimeoutMs,
1573
1607
  signal: input.signal,
@@ -1597,11 +1631,28 @@ export function openPackageWorkflowControlEngine(
1597
1631
  }
1598
1632
  const result = child.result;
1599
1633
  if (result.kind === "context-request") {
1600
- return classifyCandidateContextRequest(result, [
1601
- ...phase.read,
1602
- ...phase.write,
1603
- ...phase.delete,
1604
- ]);
1634
+ return classifyCandidateContextRequest(result, {
1635
+ phase: input.phase,
1636
+ readPaths: phase.read,
1637
+ writePaths: phase.write,
1638
+ deletePaths: phase.delete,
1639
+ taskPaths: [
1640
+ ...new Set(
1641
+ Object.values(input.task.phases).flatMap((boundary) => [
1642
+ ...boundary.read,
1643
+ ...boundary.write,
1644
+ ...boundary.delete,
1645
+ ]),
1646
+ ),
1647
+ ],
1648
+ redWritePaths: input.task.phases.red.write,
1649
+ agents: {
1650
+ impact: input.task.agents.impact,
1651
+ ...(input.task.agents.target
1652
+ ? { target: input.task.agents.target }
1653
+ : {}),
1654
+ },
1655
+ });
1605
1656
  }
1606
1657
  if (result.kind !== "sealed-candidate") {
1607
1658
  return { kind: "retryable", code: "candidate-diff-invalid" };
@@ -1841,7 +1892,7 @@ export function registerWorkflowControl(
1841
1892
  receiptHash: { type: "string" },
1842
1893
  routeId: { type: "string" },
1843
1894
  },
1844
- required: ["command", "stage"],
1895
+ required: ["command", "stage", "change"],
1845
1896
  additionalProperties: false,
1846
1897
  } as const;
1847
1898
  const PACKET_PARAMETERS = {
@@ -1872,6 +1923,12 @@ export function registerWorkflowControl(
1872
1923
  ? "Private stage-bound Abel workflow control. Accepts durable Implement change commands."
1873
1924
  : "Private stage-bound Abel packet control. Accepts bounded Design and Diagnose packet operations.",
1874
1925
  executionMode: "parallel",
1926
+ ...(kind === "command"
1927
+ ? {
1928
+ prepareArguments: (args: unknown) =>
1929
+ canonicalizeControlCommandToolInput(args) as never,
1930
+ }
1931
+ : {}),
1875
1932
  parameters:
1876
1933
  kind === "command" ? CONTROL_COMMAND_PARAMETERS : PACKET_PARAMETERS,
1877
1934
  async execute(
@@ -2004,7 +2061,9 @@ export function registerWorkflowControl(
2004
2061
  ...(usage === undefined ? {} : { usage }),
2005
2062
  };
2006
2063
  }
2007
- const validation = validateControlCommand(params);
2064
+ const validation = validateControlCommand(
2065
+ canonicalizeControlCommandToolInput(params),
2066
+ );
2008
2067
  if (!validation.ok) {
2009
2068
  const error = new Error(validation.code);
2010
2069
  error.name = "ControlCommandError";
package/src/run-store.ts CHANGED
@@ -917,7 +917,23 @@ export class RunStore {
917
917
  code?: string;
918
918
  lease?: OperationLease;
919
919
  }): RunProjection {
920
+ return this.transitionAtomically(input, () => {});
921
+ }
922
+
923
+ transitionAtomically(
924
+ input: {
925
+ runId: string;
926
+ to: RunState;
927
+ operationId: string;
928
+ code?: string;
929
+ lease?: OperationLease;
930
+ },
931
+ write: (database: DatabaseSync) => void,
932
+ ): RunProjection {
920
933
  requireIdentifier(input.operationId, "operation-id");
934
+ if (typeof write !== "function") {
935
+ throw new Error("state-transition-write-invalid");
936
+ }
921
937
  return this.#transaction(() => {
922
938
  if (input.lease) {
923
939
  if (input.lease.runId !== input.runId) throw new Error("lease-fenced");
@@ -935,6 +951,7 @@ export class RunStore {
935
951
  to: input.to,
936
952
  ...(input.code ? { code: input.code } : {}),
937
953
  });
954
+ write(this.#database);
938
955
  this.#recordOperation(
939
956
  input.runId,
940
957
  input.operationId,
@@ -9,6 +9,7 @@ import {
9
9
  type CandidateFailure,
10
10
  type DiffResult,
11
11
  type EvidenceResult,
12
+ isAgentsPath,
12
13
  isValidRelativePath,
13
14
  type SubmitFinalCategory,
14
15
  type SubmitSchemaState,
@@ -144,15 +145,57 @@ function candidateArtifactSchema(candidateId: string) {
144
145
  code: Type.Union([
145
146
  Type.Literal("approved-context-needed"),
146
147
  Type.Literal("task-split-needed"),
147
- Type.Literal("boundary-review-needed"),
148
148
  ]),
149
- refs: Type.Array(Type.String(), { maxItems: 32 }),
149
+ refs: Type.Array(
150
+ Type.Union([
151
+ Type.String(),
152
+ Type.Object(
153
+ {
154
+ kind: Type.Literal("requested-path"),
155
+ path: Type.String(),
156
+ access: Type.Union([
157
+ Type.Literal("read"),
158
+ Type.Literal("write"),
159
+ ]),
160
+ },
161
+ { additionalProperties: false },
162
+ ),
163
+ Type.Object(
164
+ {
165
+ kind: Type.Literal("source-citation"),
166
+ path: Type.String(),
167
+ line: Type.Integer({ minimum: 1 }),
168
+ },
169
+ { additionalProperties: false },
170
+ ),
171
+ Type.Object(
172
+ {
173
+ kind: Type.Literal("contract-diagnostic"),
174
+ ref: Type.String(),
175
+ },
176
+ { additionalProperties: false },
177
+ ),
178
+ ]),
179
+ { minItems: 1, maxItems: 32 },
180
+ ),
150
181
  },
151
182
  { additionalProperties: false },
152
183
  ),
153
184
  ]);
154
185
  }
155
186
 
187
+ export type CandidateContextRequestCode =
188
+ | "approved-context-needed"
189
+ | "task-split-needed"
190
+ | "boundary-review-needed";
191
+
192
+ export type CandidateContextRef =
193
+ | { kind: "requested-path"; path: string; access: "read" | "write" }
194
+ | { kind: "source-citation"; path: string; line: number }
195
+ | { kind: "contract-diagnostic"; ref: string };
196
+
197
+ export type CandidateContextRefInput = string | CandidateContextRef;
198
+
156
199
  export type CandidateArtifactSubmissionResult =
157
200
  | {
158
201
  kind: "sealed-candidate";
@@ -166,11 +209,8 @@ export type CandidateArtifactSubmissionResult =
166
209
  | {
167
210
  kind: "context-request";
168
211
  candidateId: string;
169
- code:
170
- | "approved-context-needed"
171
- | "task-split-needed"
172
- | "boundary-review-needed";
173
- refs: string[];
212
+ code: CandidateContextRequestCode;
213
+ refs: CandidateContextRef[];
174
214
  };
175
215
 
176
216
  export type CandidateContextRequest = Extract<
@@ -178,18 +218,114 @@ export type CandidateContextRequest = Extract<
178
218
  { kind: "context-request" }
179
219
  >;
180
220
 
221
+ export interface CandidateContextBoundary {
222
+ phase: "red" | "green" | "refactor";
223
+ readPaths: readonly string[];
224
+ writePaths: readonly string[];
225
+ deletePaths?: readonly string[];
226
+ taskPaths: readonly string[];
227
+ redWritePaths: readonly string[];
228
+ agents: {
229
+ impact: "none" | "update-existing" | "create-index" | "remove-index";
230
+ target?: string;
231
+ };
232
+ }
233
+
181
234
  export interface ClassifiedCandidateContextRequest {
182
- kind: "paused" | "approval-needed";
235
+ kind: "paused" | "retryable" | "approval-needed";
183
236
  code:
184
237
  | "approved-context-needed"
185
238
  | "task-split-needed"
186
- | "boundary-review-needed";
239
+ | "boundary-review-needed"
240
+ | "red-artifact-constraint"
241
+ | "agents-context-needed"
242
+ | "agents-write-parent-owned"
243
+ | "agents-contract-insufficient";
187
244
  contextRequest: {
188
- code: CandidateContextRequest["code"];
189
- refs: string[];
245
+ code: CandidateContextRequestCode;
246
+ refs: CandidateContextRef[];
190
247
  };
191
248
  }
192
249
 
250
+ const LEGACY_CITATION = /^(.*):(\d+)$/u;
251
+ const LEGACY_DIAGNOSTIC =
252
+ /^(?:phase-contract|task-contract|sealed-contract|workflow-contract)\.[a-z0-9._-]+$/iu;
253
+
254
+ function validDiagnosticRef(value: unknown): value is string {
255
+ return (
256
+ typeof value === "string" &&
257
+ value.length > 0 &&
258
+ value.length <= 512 &&
259
+ value.trim() === value &&
260
+ !/[\r\n\0]/u.test(value)
261
+ );
262
+ }
263
+
264
+ function normalizeCandidateContextRef(
265
+ input: CandidateContextRefInput,
266
+ ): CandidateContextRef {
267
+ if (typeof input === "string") {
268
+ const citation = LEGACY_CITATION.exec(input);
269
+ if (citation && isValidRelativePath(citation[1])) {
270
+ const line = Number(citation[2]);
271
+ if (Number.isSafeInteger(line) && line >= 1) {
272
+ return { kind: "source-citation", path: citation[1], line };
273
+ }
274
+ }
275
+ if (LEGACY_DIAGNOSTIC.test(input)) {
276
+ return { kind: "contract-diagnostic", ref: input };
277
+ }
278
+ if (!isValidRelativePath(input)) {
279
+ throw new Error("context request refs are invalid");
280
+ }
281
+ return { kind: "requested-path", path: input, access: "read" };
282
+ }
283
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
284
+ throw new Error("context request refs are invalid");
285
+ }
286
+ if (
287
+ input.kind === "requested-path" &&
288
+ Object.keys(input).length === 3 &&
289
+ isValidRelativePath(input.path) &&
290
+ (input.access === "read" || input.access === "write")
291
+ ) {
292
+ return { ...input };
293
+ }
294
+ if (
295
+ input.kind === "source-citation" &&
296
+ Object.keys(input).length === 3 &&
297
+ isValidRelativePath(input.path) &&
298
+ Number.isSafeInteger(input.line) &&
299
+ input.line >= 1
300
+ ) {
301
+ return { ...input };
302
+ }
303
+ if (
304
+ input.kind === "contract-diagnostic" &&
305
+ Object.keys(input).length === 2 &&
306
+ validDiagnosticRef(input.ref)
307
+ ) {
308
+ return { ...input };
309
+ }
310
+ throw new Error("context request refs are invalid");
311
+ }
312
+
313
+ export function normalizeCandidateContextRefs(
314
+ value: unknown,
315
+ ): CandidateContextRef[] {
316
+ if (!Array.isArray(value) || value.length === 0 || value.length > 32) {
317
+ throw new Error("context request refs are invalid");
318
+ }
319
+ const refs = value.map((entry) =>
320
+ normalizeCandidateContextRef(entry as CandidateContextRefInput),
321
+ );
322
+ const identities = refs.map((ref) => JSON.stringify(ref));
323
+ if (new Set(identities).size !== refs.length) {
324
+ throw new Error("context request refs are invalid");
325
+ }
326
+ return refs;
327
+ }
328
+
193
329
  function contextRefWithinBoundary(relative: string, approved: string): boolean {
194
330
  return (
195
331
  approved === "." ||
@@ -198,23 +334,108 @@ function contextRefWithinBoundary(relative: string, approved: string): boolean {
198
334
  );
199
335
  }
200
336
 
337
+ function normalizedBoundary(
338
+ boundary: readonly string[] | CandidateContextBoundary,
339
+ ): CandidateContextBoundary {
340
+ if (!Array.isArray(boundary)) return boundary as CandidateContextBoundary;
341
+ return {
342
+ phase: "green",
343
+ readPaths: boundary,
344
+ writePaths: [],
345
+ deletePaths: [],
346
+ taskPaths: boundary,
347
+ redWritePaths: [],
348
+ agents: { impact: "none" },
349
+ };
350
+ }
351
+
352
+ function pathWithinAny(relative: string, approved: readonly string[]): boolean {
353
+ return approved.some((path) => contextRefWithinBoundary(relative, path));
354
+ }
355
+
201
356
  export function classifyCandidateContextRequest(
202
- request: CandidateContextRequest,
203
- approvedPaths: readonly string[],
357
+ request: {
358
+ kind: "context-request";
359
+ candidateId: string;
360
+ code: CandidateContextRequestCode;
361
+ refs: readonly CandidateContextRefInput[];
362
+ },
363
+ inputBoundary: readonly string[] | CandidateContextBoundary,
204
364
  ): ClassifiedCandidateContextRequest {
205
- const refs = [...request.refs];
206
- const inBoundary =
207
- refs.length > 0 &&
208
- refs.every((relative) =>
209
- approvedPaths.some((approved) =>
210
- contextRefWithinBoundary(relative, approved),
365
+ const refs = normalizeCandidateContextRefs(request.refs);
366
+ const boundary = normalizedBoundary(inputBoundary);
367
+ const phasePaths = [
368
+ ...boundary.readPaths,
369
+ ...boundary.writePaths,
370
+ ...(boundary.deletePaths ?? []),
371
+ ];
372
+ const requested = refs.filter(
373
+ (ref): ref is Extract<CandidateContextRef, { kind: "requested-path" }> =>
374
+ ref.kind === "requested-path",
375
+ );
376
+ const ordinaryRequested = requested.filter((ref) => !isAgentsPath(ref.path));
377
+ const outsideOrdinary = ordinaryRequested.filter(
378
+ (ref) =>
379
+ !pathWithinAny(
380
+ ref.path,
381
+ ref.access === "write"
382
+ ? [...boundary.writePaths, ...(boundary.deletePaths ?? [])]
383
+ : phasePaths,
211
384
  ),
385
+ );
386
+ const agentsRequested = requested.filter((ref) => isAgentsPath(ref.path));
387
+ const agentsWrites = agentsRequested.filter((ref) => ref.access === "write");
388
+ const citedRedArtifacts = refs.filter(
389
+ (ref): ref is Extract<CandidateContextRef, { kind: "source-citation" }> =>
390
+ ref.kind === "source-citation" &&
391
+ pathWithinAny(ref.path, boundary.redWritePaths) &&
392
+ !pathWithinAny(ref.path, [
393
+ ...boundary.writePaths,
394
+ ...(boundary.deletePaths ?? []),
395
+ ]),
396
+ );
397
+ const requestedRedArtifacts = outsideOrdinary.filter(
398
+ (ref) =>
399
+ pathWithinAny(ref.path, boundary.redWritePaths) &&
400
+ pathWithinAny(ref.path, boundary.taskPaths),
401
+ );
402
+ const redArtifactConstraint =
403
+ boundary.phase === "green" &&
404
+ (citedRedArtifacts.length > 0 || requestedRedArtifacts.length > 0) &&
405
+ outsideOrdinary.every((ref) => requestedRedArtifacts.includes(ref)) &&
406
+ agentsRequested.every((ref) => ref.access === "read");
407
+
408
+ let classification: Pick<ClassifiedCandidateContextRequest, "kind" | "code">;
409
+ if (outsideOrdinary.length > 0 && !redArtifactConstraint) {
410
+ classification = {
411
+ kind: "approval-needed",
412
+ code: "boundary-review-needed",
413
+ };
414
+ } else if (agentsWrites.length > 0) {
415
+ const targetMismatch = agentsWrites.some(
416
+ (ref) =>
417
+ boundary.agents.impact === "none" ||
418
+ boundary.agents.target === undefined ||
419
+ ref.path !== boundary.agents.target,
212
420
  );
213
- const approvalNeeded =
214
- !inBoundary || request.code === "boundary-review-needed";
421
+ classification = targetMismatch
422
+ ? { kind: "approval-needed", code: "agents-contract-insufficient" }
423
+ : { kind: "paused", code: "agents-write-parent-owned" };
424
+ } else if (redArtifactConstraint) {
425
+ classification = { kind: "retryable", code: "red-artifact-constraint" };
426
+ } else if (agentsRequested.length > 0) {
427
+ classification = { kind: "paused", code: "agents-context-needed" };
428
+ } else {
429
+ classification = {
430
+ kind: "paused",
431
+ code:
432
+ request.code === "task-split-needed"
433
+ ? "task-split-needed"
434
+ : "approved-context-needed",
435
+ };
436
+ }
215
437
  return {
216
- kind: approvalNeeded ? "approval-needed" : "paused",
217
- code: inBoundary ? request.code : "boundary-review-needed",
438
+ ...classification,
218
439
  contextRequest: {
219
440
  code: request.code,
220
441
  refs,
@@ -391,18 +612,17 @@ export function createCandidateArtifactTool(
391
612
  terminate: true,
392
613
  };
393
614
  }
394
- const refs = value.refs as string[];
395
- if (
396
- refs.some((relative) => !isValidRelativePath(relative)) ||
397
- new Set(refs).size !== refs.length
398
- ) {
615
+ let refs: CandidateContextRef[];
616
+ try {
617
+ refs = normalizeCandidateContextRefs(value.refs);
618
+ } catch (error) {
399
619
  schema = "invalid";
400
620
  failure = {
401
621
  kind: "artifact",
402
622
  code: "invalid-structural-result",
403
623
  stage: "structural-submit",
404
624
  };
405
- throw new Error("context request refs are invalid");
625
+ throw error;
406
626
  }
407
627
  result = {
408
628
  kind: "context-request",
@@ -411,7 +631,7 @@ export function createCandidateArtifactTool(
411
631
  CandidateArtifactSubmissionResult,
412
632
  { kind: "context-request" }
413
633
  >["code"],
414
- refs: [...refs],
634
+ refs,
415
635
  };
416
636
  return {
417
637
  content: [