@voybio/ace-swarm 2.4.9 → 2.4.10

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.
Files changed (185) hide show
  1. package/CHANGELOG.md +52 -14
  2. package/README.md +14 -10
  3. package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
  4. package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
  5. package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
  6. package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
  7. package/assets/agent-state/ACE_WORKFLOW.md +25 -2
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
  10. package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
  11. package/assets/eval-battery/battery.json +1 -1
  12. package/assets/instructions/ACE_Coder.instructions.md +9 -13
  13. package/assets/scripts/ace-hook-dispatch.mjs +16 -411
  14. package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
  15. package/assets/vendor/README.md +9 -0
  16. package/assets/vendor/vericify-reader.mjs +272 -0
  17. package/assets/vendor/vericify-reader.provenance.json +10 -0
  18. package/dist/ace-autonomy.d.ts +58 -0
  19. package/dist/ace-autonomy.js +211 -6
  20. package/dist/ace-internal-tools.d.ts +1 -0
  21. package/dist/ace-internal-tools.js +3 -3
  22. package/dist/ace-server-instructions.d.ts +3 -3
  23. package/dist/ace-server-instructions.js +46 -192
  24. package/dist/cli.js +44 -6
  25. package/dist/experience-reflector.d.ts +7 -0
  26. package/dist/experience-reflector.js +39 -0
  27. package/dist/goal-engine.d.ts +64 -7
  28. package/dist/goal-engine.js +197 -41
  29. package/dist/handoff-registry.d.ts +10 -0
  30. package/dist/handoff-registry.js +59 -0
  31. package/dist/helpers/artifacts.d.ts +6 -2
  32. package/dist/helpers/artifacts.js +8 -6
  33. package/dist/helpers/bootstrap.js +13 -20
  34. package/dist/helpers/path-utils.js +2 -0
  35. package/dist/helpers/store-resolution.js +18 -3
  36. package/dist/helpers/workspace-root.d.ts +2 -0
  37. package/dist/helpers/workspace-root.js +9 -0
  38. package/dist/helpers.d.ts +1 -0
  39. package/dist/helpers.js +1 -0
  40. package/dist/hermes/bridge-protocol.d.ts +1 -1
  41. package/dist/hermes/session-manager.d.ts +17 -0
  42. package/dist/hermes/session-manager.js +67 -14
  43. package/dist/host-goal-engine.d.ts +138 -0
  44. package/dist/host-goal-engine.js +793 -0
  45. package/dist/host-goal-status.d.ts +113 -0
  46. package/dist/host-goal-status.js +175 -0
  47. package/dist/host-learning.d.ts +68 -0
  48. package/dist/host-learning.js +115 -0
  49. package/dist/host-observation.d.ts +12 -0
  50. package/dist/host-observation.js +91 -0
  51. package/dist/host-plan-revision.d.ts +14 -0
  52. package/dist/host-plan-revision.js +101 -0
  53. package/dist/host-verification-job.d.ts +12 -0
  54. package/dist/host-verification-job.js +183 -0
  55. package/dist/host-work-context.d.ts +12 -0
  56. package/dist/host-work-context.js +8 -0
  57. package/dist/job-scheduler.d.ts +48 -14
  58. package/dist/job-scheduler.js +169 -23
  59. package/dist/job-worker.d.ts +3 -1
  60. package/dist/job-worker.js +10 -3
  61. package/dist/kanban.d.ts +4 -1
  62. package/dist/kanban.js +44 -16
  63. package/dist/learned-artifacts.d.ts +64 -0
  64. package/dist/learned-artifacts.js +321 -0
  65. package/dist/learning-controls.d.ts +142 -0
  66. package/dist/learning-controls.js +135 -0
  67. package/dist/learning-input-context.d.ts +9 -0
  68. package/dist/learning-input-context.js +62 -0
  69. package/dist/learning-lifecycle.d.ts +10 -0
  70. package/dist/learning-lifecycle.js +11 -0
  71. package/dist/learning-overlays.d.ts +3 -0
  72. package/dist/learning-overlays.js +17 -0
  73. package/dist/learning-policy.d.ts +40 -0
  74. package/dist/learning-policy.js +38 -0
  75. package/dist/learning-records.d.ts +35 -0
  76. package/dist/learning-records.js +17 -0
  77. package/dist/learning-scorecard.d.ts +82 -0
  78. package/dist/learning-scorecard.js +86 -0
  79. package/dist/learning-serving.d.ts +29 -0
  80. package/dist/learning-serving.js +134 -0
  81. package/dist/linked-work.d.ts +9 -0
  82. package/dist/linked-work.js +28 -0
  83. package/dist/local-model-runtime.js +1 -1
  84. package/dist/mcp-command-progress.d.ts +16 -0
  85. package/dist/mcp-command-progress.js +70 -0
  86. package/dist/model-bridge.d.ts +14 -13
  87. package/dist/model-bridge.js +73 -41
  88. package/dist/orchestrator-supervisor.d.ts +108 -6
  89. package/dist/orchestrator-supervisor.js +4 -32
  90. package/dist/plan-proposal.js +3 -3
  91. package/dist/playbook-curator.d.ts +1 -0
  92. package/dist/playbook-curator.js +11 -9
  93. package/dist/process-identity.d.ts +8 -0
  94. package/dist/process-identity.js +38 -0
  95. package/dist/progress-command.d.ts +34 -0
  96. package/dist/progress-command.js +153 -0
  97. package/dist/prompts.js +15 -15
  98. package/dist/resources.js +2 -2
  99. package/dist/runtime-command.d.ts +8 -1
  100. package/dist/runtime-command.js +48 -7
  101. package/dist/runtime-executor.js +33 -11
  102. package/dist/runtime-profile-syntax.d.ts +6 -0
  103. package/dist/runtime-profile-syntax.js +121 -0
  104. package/dist/runtime-profile.d.ts +37 -5
  105. package/dist/runtime-profile.js +36 -129
  106. package/dist/runtime-tool-specs.js +1 -1
  107. package/dist/safe-edit.d.ts +7 -3
  108. package/dist/safe-edit.js +40 -47
  109. package/dist/schemas.js +4 -2
  110. package/dist/server.d.ts +2 -16
  111. package/dist/server.js +14 -20
  112. package/dist/shared.d.ts +2 -0
  113. package/dist/shared.js +2 -0
  114. package/dist/skill-auditor.d.ts +19 -0
  115. package/dist/skill-auditor.js +32 -2
  116. package/dist/skill-catalog.d.ts +4 -1
  117. package/dist/skill-catalog.js +6 -5
  118. package/dist/store/ace-packed-store.d.ts +5 -1
  119. package/dist/store/ace-packed-store.js +33 -1
  120. package/dist/store/bootstrap-store.js +19 -12
  121. package/dist/store/cache-workspace.d.ts +8 -0
  122. package/dist/store/cache-workspace.js +94 -27
  123. package/dist/store/importer.js +11 -20
  124. package/dist/store/knowledge-bake.d.ts +3 -1
  125. package/dist/store/knowledge-bake.js +10 -1
  126. package/dist/store/legacy-vericify-posts.d.ts +8 -0
  127. package/dist/store/legacy-vericify-posts.js +76 -0
  128. package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
  129. package/dist/store/materializers/hook-context-materializer.js +6 -2
  130. package/dist/store/materializers/host-file-materializer.js +2 -2
  131. package/dist/store/materializers/projection-manager.d.ts +4 -1
  132. package/dist/store/materializers/projection-manager.js +10 -3
  133. package/dist/store/materializers/todo-syncer.d.ts +39 -20
  134. package/dist/store/materializers/todo-syncer.js +208 -110
  135. package/dist/store/materializers/vericify-projector.js +15 -26
  136. package/dist/store/repositories/goal-repository.d.ts +51 -2
  137. package/dist/store/repositories/goal-repository.js +114 -48
  138. package/dist/store/repositories/playbook-repository.d.ts +41 -3
  139. package/dist/store/repositories/playbook-repository.js +61 -5
  140. package/dist/store/repositories/todo-repository.d.ts +8 -2
  141. package/dist/store/repositories/todo-repository.js +1 -1
  142. package/dist/store/repositories/vericify-repository.d.ts +4 -0
  143. package/dist/store/repositories/vericify-repository.js +46 -0
  144. package/dist/store/skills-install.d.ts +1 -0
  145. package/dist/store/skills-install.js +20 -16
  146. package/dist/store/source-preservation.d.ts +24 -0
  147. package/dist/store/source-preservation.js +79 -0
  148. package/dist/store/store-artifacts.js +8 -1
  149. package/dist/store/test-write-boundary.d.ts +16 -0
  150. package/dist/store/test-write-boundary.js +175 -0
  151. package/dist/store/types.d.ts +5 -1
  152. package/dist/store/write-coordinator.js +16 -0
  153. package/dist/task-plan-context.d.ts +11 -0
  154. package/dist/task-plan-context.js +59 -0
  155. package/dist/todo-state.d.ts +19 -9
  156. package/dist/todo-state.js +204 -321
  157. package/dist/tools-agent.d.ts +1 -1
  158. package/dist/tools-agent.js +38 -62
  159. package/dist/tools-files.d.ts +0 -3
  160. package/dist/tools-files.js +21 -11
  161. package/dist/tools-framework.js +16 -25
  162. package/dist/tools-goal.d.ts +2 -4
  163. package/dist/tools-goal.js +119 -16
  164. package/dist/tools-handoff.js +28 -4
  165. package/dist/tools-lifecycle.js +1 -1
  166. package/dist/tools-memory.d.ts +0 -3
  167. package/dist/tools-memory.js +51 -6
  168. package/dist/tools-scheduler.js +19 -17
  169. package/dist/tools-todo.js +160 -148
  170. package/dist/vericify-bridge.d.ts +8 -0
  171. package/dist/vericify-bridge.js +18 -0
  172. package/dist/vericify-context.d.ts +4 -0
  173. package/dist/vericify-context.js +26 -5
  174. package/dist/vericify-native.d.ts +101 -0
  175. package/dist/vericify-native.js +221 -0
  176. package/dist/verification-worker.d.ts +2 -0
  177. package/dist/verification-worker.js +31 -0
  178. package/docs/mcp-tool-reference.md +44 -9
  179. package/package.json +19 -6
  180. package/scripts/dev/run-tests-isolated.mjs +165 -0
  181. package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
  182. package/scripts/dev/test-isolation-guard.mjs +20 -0
  183. package/scripts/dev/test-preload.mjs +153 -0
  184. package/scripts/dev/test-source-inventory.mjs +31 -0
  185. package/docs/development.md +0 -46
@@ -1,5 +1,8 @@
1
1
  /**
2
- * ACE Goal Engine (WS-F).
2
+ * ACE Goal Engine (WS-F), with attached MCP execution for host-authored plans.
3
+ * Host goals use the same filesystem records and plan primitives through
4
+ * next_step/complete_step. They enforce their declared contract and explicitly
5
+ * label completion without verification commands as not machine-verified.
3
6
  *
4
7
  * `startGoal("<objective>")` compiles a plan with ACE's own planner, then a
5
8
  * self-paced driver loop (`runGoal`) executes it step-by-step under evidence-
@@ -9,7 +12,7 @@
9
12
  * structurally impossible (there is no code path from a model message to
10
13
  * `status: "done"`).
11
14
  *
12
- * That guarantee rests on the success contract carrying at least one
15
+ * In the engine-executed path, that guarantee rests on the success contract carrying at least one
13
16
  * machine-checkable obligation. A contract with none is refused at `startGoal`
14
17
  * and again at verify time with `contract_uncheckable`, never satisfied
15
18
  * vacuously — see {@link enforceableObligationCount}.
@@ -27,14 +30,19 @@
27
30
  * hermetically in tests with a stub bridge.
28
31
  */
29
32
  import { existsSync } from "node:fs";
33
+ import { createHash } from "node:crypto";
30
34
  import { isAbsolute, join } from "node:path";
35
+ import { stripVTControlCharacters } from "node:util";
31
36
  import { amendTaskPlan, applyBridgeResultToStep, createTaskPlan, deriveTaskPlanStatus, getReadyTaskSteps, markTaskStepStatus, } from "./orchestrator-supervisor.js";
32
37
  import { proposePlan, validatePlan } from "./plan-proposal.js";
33
38
  import { resolveWorkspaceRoot } from "./helpers.js";
34
- import { appendVericifyProcessPostSafe } from "./vericify-bridge.js";
39
+ import { appendAutomaticVericifyProcessPostSafe } from "./vericify-bridge.js";
35
40
  import { appendStatusEventSafe } from "./status-events.js";
36
41
  import { GoalRepository, defaultGoalBudgets, newGoalId, } from "./store/repositories/goal-repository.js";
37
42
  import { PlaybookRepository } from "./store/repositories/playbook-repository.js";
43
+ import { failureSignature } from './experience-reflector.js';
44
+ import { runProgressCommand } from './progress-command.js';
45
+ export const verificationCommandText = (command) => typeof command === 'string' ? command : command.cmd;
38
46
  // ── Defaults for the injectable seams ────────────────────────────────────────
39
47
  function defaultArtifactExists(path, workspaceRoot) {
40
48
  if (!path || path.trim().length === 0)
@@ -43,16 +51,9 @@ function defaultArtifactExists(path, workspaceRoot) {
43
51
  const abs = isAbsolute(clean) ? clean : join(workspaceRoot, clean);
44
52
  return existsSync(abs);
45
53
  }
46
- async function defaultRunVerification(command, workspaceRoot) {
47
- const { exec } = await import("node:child_process");
48
- return await new Promise((resolve) => {
49
- exec(command, { cwd: workspaceRoot, timeout: 10 * 60 * 1000, maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => {
50
- resolve({
51
- ok: !error,
52
- output: `${stdout ?? ""}${stderr ? `\n${stderr}` : ""}`.trim(),
53
- });
54
- });
55
- });
54
+ async function defaultRunVerification(command, workspaceRoot, options) {
55
+ return runProgressCommand(command, workspaceRoot, { ...options,
56
+ ...(options?.timeout_ms !== undefined ? { deadline_min: options.timeout_ms / 60000 } : {}) });
56
57
  }
57
58
  async function defaultSpawnStep(step, _plan, _goal) {
58
59
  const { runLocalModelTask } = await import("./local-model-runtime.js");
@@ -66,7 +67,7 @@ async function defaultSpawnStep(step, _plan, _goal) {
66
67
  function defaultSleep(ms) {
67
68
  return new Promise((resolve) => setTimeout(resolve, ms));
68
69
  }
69
- function resolveDeps(deps = {}) {
70
+ export function resolveGoalDeps(deps = {}) {
70
71
  const workspaceRoot = deps.workspaceRoot ?? resolveWorkspaceRoot();
71
72
  const repo = new GoalRepository(workspaceRoot);
72
73
  return {
@@ -98,7 +99,7 @@ function resolveDeps(deps = {}) {
98
99
  const kind = input.kind === "progress" || input.kind === "completion" || input.kind === "blocker"
99
100
  ? input.kind
100
101
  : "blocker";
101
- await appendVericifyProcessPostSafe({
102
+ await appendAutomaticVericifyProcessPostSafe({
102
103
  run_id: "workspace:current",
103
104
  agent_id: "ace-goal-engine",
104
105
  kind: kind === "completion" ? "completion" : kind === "progress" ? "progress" : "blocker",
@@ -109,6 +110,7 @@ function resolveDeps(deps = {}) {
109
110
  }),
110
111
  };
111
112
  }
113
+ const resolveDeps = resolveGoalDeps;
112
114
  // ── Success-contract compilation ─────────────────────────────────────────────
113
115
  const CRITERION_VERIFICATION_HINT = /^\s*verify\s*:\s*(.+)$/i;
114
116
  const OBJECTIVE_VERIFICATION_HINT = /(?:^|[.!?]\s+)verify\s*:\s*(.+)$/i;
@@ -203,15 +205,18 @@ function compileSuccessContract(proposal, input = {}) {
203
205
  for (const command of extractVerificationCommands(input.objective ?? "", "objective")) {
204
206
  commands.add(command);
205
207
  }
206
- for (const command of input.verify_commands ?? []) {
207
- const trimmed = command.trim();
208
+ for (const declaration of input.verify_commands ?? []) {
209
+ const trimmed = verificationCommandText(declaration).trim();
208
210
  if (trimmed)
209
211
  commands.add(trimmed);
210
212
  }
211
213
  return {
212
214
  expected_artifacts: Array.from(expected.values()),
213
215
  required_evidence_refs: Array.from(evidence),
214
- verification_commands: Array.from(commands),
216
+ verification_commands: Array.from(commands).map(command => {
217
+ const declaration = input.verify_commands?.find(c => verificationCommandText(c).trim() === command);
218
+ return declaration && typeof declaration !== 'string' ? { ...declaration, cmd: command } : command;
219
+ }),
215
220
  gate_ids: [],
216
221
  uncompilable_criteria: uncompilable,
217
222
  };
@@ -252,6 +257,10 @@ async function escalate(d, goal, kind, reasonCode, summary) {
252
257
  });
253
258
  }
254
259
  export async function startGoal(objective, options = {}, deps = {}) {
260
+ if (options.executor === 'host') {
261
+ const { startHostGoal } = await import('./host-goal-engine.js');
262
+ return startHostGoal(objective, options, deps);
263
+ }
255
264
  const d = resolveDeps(deps);
256
265
  const trimmed = objective.trim();
257
266
  // `typeof NaN === "number"`, and NaN silently disables a budget in memory
@@ -259,11 +268,11 @@ export async function startGoal(objective, options = {}, deps = {}) {
259
268
  // budget fire immediately after the record is reloaded. Neither is a budget.
260
269
  const budgets = {
261
270
  ...defaultGoalBudgets(),
262
- ...(Number.isFinite(options.budget_hours)
263
- ? { max_wall_clock_ms: Math.max(1, options.budget_hours) * 60 * 60 * 1000 }
271
+ ...(Number.isFinite(options.budget_hours) && options.budget_hours > 0 && Number.isFinite(options.budget_hours * 3600000)
272
+ ? { max_wall_clock_ms: options.budget_hours * 3600000, deadline_explicit: true }
264
273
  : {}),
265
- ...(Number.isFinite(options.max_ticks) ? { max_ticks: options.max_ticks } : {}),
266
- ...(Number.isFinite(options.max_replans) ? { max_replans: options.max_replans } : {}),
274
+ ...(Number.isSafeInteger(options.max_ticks) && options.max_ticks >= 0 ? { max_ticks: options.max_ticks, tick_budget_explicit: true } : {}),
275
+ ...(Number.isSafeInteger(options.max_replans) && options.max_replans >= 0 ? { max_replans: options.max_replans, replan_budget_explicit: true } : {}),
267
276
  };
268
277
  const iso = new Date(d.now()).toISOString();
269
278
  let goal = {
@@ -366,6 +375,8 @@ export async function tickGoal(goalId, deps = {}) {
366
375
  const d = resolveDeps(deps);
367
376
  const goal = requireGoal(d, goalId);
368
377
  const plan = requirePlan(d, goal);
378
+ if (goal.executor === 'host')
379
+ return { goal, plan, progressed: false };
369
380
  const ready = getReadyTaskSteps(plan);
370
381
  if (ready.length === 0) {
371
382
  return { goal, plan, progressed: false };
@@ -400,8 +411,13 @@ export async function tickGoal(goalId, deps = {}) {
400
411
  reason_code: "spawn_threw",
401
412
  };
402
413
  }
403
- nextPlan = applyBridgeResultToStep(nextPlan, step.step_id, result);
414
+ nextPlan = applyBridgeResultToStep(d.repo.getPlan(goalId) ?? nextPlan, step.step_id, result);
404
415
  const finalStep = nextPlan.steps.find((s) => s.step_id === step.step_id);
416
+ // Preserve the bridge's existing evidence declarations alongside tool receipts.
417
+ // New references distinguish progress; they do not prove changed path contents.
418
+ if (finalStep && result.evidence_refs?.length) {
419
+ finalStep.evidence_refs = [...new Set([...(finalStep.evidence_refs ?? []), ...result.evidence_refs])];
420
+ }
405
421
  // Re-hydrate so any field mutated *during* the spawn (e.g. an operator
406
422
  // stop_requested via stopGoal) is preserved rather than clobbered by this
407
423
  // tick's stale in-memory snapshot.
@@ -449,17 +465,21 @@ export async function checkSuccessContract(d, goal) {
449
465
  if (!d.artifactExists(ref, d.workspaceRoot))
450
466
  missingEvidence.push(ref);
451
467
  }
452
- const failed = [];
453
- for (const command of contract.verification_commands) {
454
- const outcome = await d.runVerification(command, d.workspaceRoot);
455
- if (!outcome.ok)
468
+ const failed = [], signatures = [];
469
+ for (const declaration of contract.verification_commands) {
470
+ const command = verificationCommandText(declaration);
471
+ const outcome = await d.runVerification(command, d.workspaceRoot, typeof declaration === 'string' ? undefined : declaration);
472
+ if (!outcome.ok) {
456
473
  failed.push(command);
474
+ signatures.push(`${command}:${failureProgressSignature(outcome.output)}`);
475
+ }
457
476
  }
458
477
  return {
459
478
  ok: missing.length === 0 && missingEvidence.length === 0 && failed.length === 0,
460
479
  missing_artifacts: missing,
461
480
  missing_evidence_refs: missingEvidence,
462
481
  failed_commands: failed,
482
+ failure_signatures: signatures,
463
483
  uncheckable: false,
464
484
  };
465
485
  }
@@ -487,12 +507,14 @@ function playbookRetryNote(workspaceRoot, reasonCode) {
487
507
  export async function runGoal(goalId, deps = {}) {
488
508
  const d = resolveDeps(deps);
489
509
  let goal = requireGoal(d, goalId);
510
+ if (goal.executor === 'host')
511
+ return { goal, plan: d.repo.getPlan(goalId) };
490
512
  if (goal.status !== "executing" && goal.status !== "verifying") {
491
513
  return { goal, plan: d.repo.getPlan(goalId) };
492
514
  }
493
515
  let backoff = BACKOFF_START_MS;
494
- // Bounded by max_ticks; the loop can only exit through an explicit terminal
495
- // transition below, never by a model asserting completion.
516
+ // Productive work has no implicit tick or elapsed-time ceiling. Only an
517
+ // operator allowance, unchanged failures, or an explicit transition stops it.
496
518
  for (;;) {
497
519
  goal = requireGoal(d, goalId);
498
520
  // 1. Operator stop.
@@ -543,8 +565,21 @@ export async function runGoal(goalId, deps = {}) {
543
565
  d.repo.saveGoal(goal);
544
566
  break;
545
567
  }
546
- // Missing evidence → synthesize a remediation step (counts as a replan).
547
- if (goal.replan_count >= goal.budgets.max_replans) {
568
+ // Repeated unchanged failures park the loop; different failures are progress.
569
+ const repeated = observeFailure(goal, JSON.stringify({
570
+ artifacts: [...check.missing_artifacts].sort(), evidence: [...check.missing_evidence_refs].sort(),
571
+ commands: [...(check.failure_signatures ?? check.failed_commands)].sort(),
572
+ }));
573
+ if (repeated && !(goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans)) {
574
+ goal.status = 'blocked';
575
+ goal.reason_code = 'repeated_failure';
576
+ goal = d.repo.saveGoal(goal);
577
+ await escalate(d, goal, 'blocker', 'repeated_failure', 'The same success-contract failure repeated three times without progress; operator review is required.');
578
+ d.repo.saveGoal(goal);
579
+ break;
580
+ }
581
+ // An explicit operator cap is still honored; defaults never limit corrections.
582
+ if (goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans) {
548
583
  const replanCode = "goal_budget_exhausted:max_replans";
549
584
  goal.status = "blocked";
550
585
  goal.reason_code = replanCode;
@@ -584,14 +619,24 @@ export async function runGoal(goalId, deps = {}) {
584
619
  goal = d.repo.saveGoal(goal);
585
620
  await d.appendProcessPost({
586
621
  kind: "progress",
587
- summary: `Goal ${goalId}: success contract unmet, synthesized remediation step (replan ${goal.replan_count}/${goal.budgets.max_replans}).`,
622
+ summary: `Goal ${goalId}: success contract unmet, synthesized remediation step (replan ${goal.replan_count}${goal.budgets.replan_budget_explicit ? `/${goal.budgets.max_replans}` : ''}).`,
588
623
  });
589
624
  backoff = BACKOFF_START_MS;
590
625
  continue;
591
626
  }
592
627
  // 4. Plan blocked/failed → classify and either replan or park.
593
628
  if (status === "blocked" || status === "failed") {
594
- if (goal.replan_count >= goal.budgets.max_replans) {
629
+ const failures = plan.steps.filter(s => s.status === 'blocked' || s.status === 'failed')
630
+ .map(s => JSON.stringify({ task: s.task, failure: failureProgressSignature(s.blocked_reason ?? s.result_summary ?? s.status), evidence: [...(s.evidence_refs ?? [])].sort() })).sort();
631
+ if (observeFailure(goal, JSON.stringify(failures)) && !(goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans)) {
632
+ goal.status = 'blocked';
633
+ goal.reason_code = 'repeated_failure';
634
+ goal = d.repo.saveGoal(goal);
635
+ await escalate(d, goal, 'blocker', 'repeated_failure', `The same step failure repeated three times without progress (${blockedReasons(plan)}).`);
636
+ d.repo.saveGoal(goal);
637
+ break;
638
+ }
639
+ if (goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans) {
595
640
  goal.status = "blocked";
596
641
  goal.reason_code = "plan_blocked";
597
642
  goal = d.repo.saveGoal(goal);
@@ -637,7 +682,7 @@ export async function runGoal(goalId, deps = {}) {
637
682
  d.repo.savePlan(goalId, retried);
638
683
  await d.appendProcessPost({
639
684
  kind: "progress",
640
- summary: `Goal ${goalId}: plan ${status}, retrying blocked steps (replan ${goal.replan_count}/${goal.budgets.max_replans}).`,
685
+ summary: `Goal ${goalId}: plan ${status}, retrying blocked steps (replan ${goal.replan_count}${goal.budgets.replan_budget_explicit ? `/${goal.budgets.max_replans}` : ''}).`,
641
686
  });
642
687
  backoff = BACKOFF_START_MS;
643
688
  continue;
@@ -661,8 +706,8 @@ export async function runGoal(goalId, deps = {}) {
661
706
  d.repo.saveGoal(goal);
662
707
  break;
663
708
  }
664
- // A step is still running (an engine restarted mid-step). Back off, but charge
665
- // the wait against max_ticks so an in-flight step cannot stall the goal forever.
709
+ // A step is still running (an engine restarted mid-step). Wait cooperatively;
710
+ // operator allowances apply only when explicitly supplied.
666
711
  goal.tick_count += 1;
667
712
  goal = d.repo.saveGoal(goal);
668
713
  await d.sleep(backoff);
@@ -671,10 +716,24 @@ export async function runGoal(goalId, deps = {}) {
671
716
  return { goal, plan: d.repo.getPlan(goalId) };
672
717
  }
673
718
  // ── resumeGoal / stopGoal / status ───────────────────────────────────────────
674
- export async function resumeGoal(goalId, deps = {}) {
719
+ export async function resumeGoal(goalId, deps = {}, options = {}) {
675
720
  const d = resolveDeps(deps);
676
- const goal = requireGoal(d, goalId);
721
+ let goal = requireGoal(d, goalId);
677
722
  const plan = d.repo.getPlan(goalId);
723
+ if (goal.executor === 'host') {
724
+ const resumed = await withHostResume(d, goalId, options);
725
+ if (resumed.plan?.steps.some(step => step.observation_check && step.observation_check.attempt === step.attempt && ['pending', 'unmet', 'unsupported'].includes(step.observation_check.status)) && !resumed.goal.reason_code?.startsWith('follow-up budget reached') && resumed.goal.reason_code !== 'host_budget_exhausted') {
726
+ const { retryHostVerification } = await import('./host-goal-engine.js');
727
+ return retryHostVerification(goalId, deps);
728
+ }
729
+ if (resumed.goal.status !== 'done' && !resumed.goal.reason_code?.startsWith('follow-up budget reached') && resumed.goal.reason_code !== 'host_budget_exhausted' && !resumed.goal.pending_follow_ups?.length && resumed.plan?.steps.every(s => ['done', 'skipped'].includes(s.status))) {
730
+ const { retryHostVerification } = await import('./host-goal-engine.js');
731
+ return retryHostVerification(goalId, deps);
732
+ }
733
+ return resumed;
734
+ }
735
+ if (options.budget_hours !== undefined || options.max_replans !== undefined)
736
+ goal = await recoverEngineGoalBudgets(goalId, options, deps);
678
737
  if (plan) {
679
738
  // A step left `running` means the engine died mid-step — reset it to planned
680
739
  // (the supervisor's own revisit pattern) so it re-executes on resume.
@@ -705,6 +764,7 @@ export async function resumeGoal(goalId, deps = {}) {
705
764
  goal.status = "executing";
706
765
  goal.stop_requested = false;
707
766
  goal.reason_code = undefined;
767
+ goal.failure_repetition = undefined;
708
768
  d.repo.saveGoal(goal);
709
769
  }
710
770
  // `--plan-only` parks at `planning` with a compiled plan. Resuming is the
@@ -721,9 +781,94 @@ export async function resumeGoal(goalId, deps = {}) {
721
781
  }
722
782
  return runGoal(goalId, deps);
723
783
  }
724
- export function stopGoal(goalId, deps = {}) {
784
+ /** Operator-approved increases retain the original clock and all accepted work. */
785
+ export async function recoverEngineGoalBudgets(goalId, options, deps = {}) {
786
+ const d = resolveDeps(deps);
787
+ const { withStoreWriteCoordinator } = await import('./store/write-coordinator.js');
788
+ return withStoreWriteCoordinator(d.repo.planPath(goalId), async () => {
789
+ const goal = requireGoal(d, goalId);
790
+ if (goal.executor === 'host')
791
+ throw new Error('Use host budget recovery for a host goal.');
792
+ const hours = options.budget_hours, replans = options.max_replans;
793
+ if (hours !== undefined) {
794
+ if (!Number.isFinite(hours) || hours <= 0 || !Number.isSafeInteger(hours * 3600000))
795
+ throw new Error('budget_hours must yield a finite positive millisecond allowance.');
796
+ if (!goal.budgets.deadline_explicit)
797
+ throw new Error('Cannot impose a finite deadline on an unlimited goal through resume; no explicit deadline exists.');
798
+ if (hours * 3600000 < goal.budgets.max_wall_clock_ms)
799
+ throw new Error('Cannot lower budget_hours; resume only increases the total allowance.');
800
+ }
801
+ if (replans !== undefined) {
802
+ if (!Number.isSafeInteger(replans) || replans < 0)
803
+ throw new Error('max_replans must be a nonnegative safe integer.');
804
+ if (!goal.budgets.replan_budget_explicit)
805
+ throw new Error('Cannot replace an unlimited replan allowance with a finite allowance through resume.');
806
+ if (replans < goal.budgets.max_replans)
807
+ throw new Error('Cannot lower max_replans; resume only increases the total allowance.');
808
+ }
809
+ if (hours !== undefined)
810
+ goal.budgets.max_wall_clock_ms = hours * 3600000;
811
+ if (replans !== undefined)
812
+ goal.budgets.max_replans = replans;
813
+ return d.repo.saveGoal(goal);
814
+ }, { operation_label: 'engine_budget_recovery' });
815
+ }
816
+ async function withHostResume(d, goalId, options) {
817
+ const { withStoreWriteCoordinator } = await import('./store/write-coordinator.js');
818
+ return withStoreWriteCoordinator(d.repo.planPath(goalId), async () => {
819
+ const goal = requireGoal(d, goalId);
820
+ let plan = requirePlan(d, goal);
821
+ const { recoverHostBudgetsLocked } = await import('./host-goal-engine.js');
822
+ plan = recoverHostBudgetsLocked(goal, plan, options, d.now());
823
+ if (goal.status === 'done' || goal.reason_code?.startsWith('follow-up budget reached') || goal.reason_code === 'host_budget_exhausted') {
824
+ d.repo.savePlan(goalId, plan);
825
+ d.repo.saveGoal(goal);
826
+ return { goal, plan };
827
+ }
828
+ if (plan.steps.some(step => step.observation_check && step.observation_check.attempt === step.attempt && ['pending', 'unmet', 'unsupported'].includes(step.observation_check.status))) {
829
+ d.repo.savePlan(goalId, plan);
830
+ d.repo.saveGoal(goal);
831
+ return { goal, plan };
832
+ }
833
+ // retryHostVerification clears a stop and advances check identity together.
834
+ goal.failure_repetition = undefined;
835
+ if (goal.reason_code === 'repeated_failure')
836
+ goal.reason_code = undefined;
837
+ if (plan.steps.every(s => ['done', 'skipped'].includes(s.status))) {
838
+ d.repo.savePlan(goalId, plan);
839
+ d.repo.saveGoal(goal);
840
+ return { goal, plan };
841
+ }
842
+ // Explicit recovery permits a new attempt on blocked work; in-flight host work is never reset.
843
+ for (const step of plan.steps)
844
+ if (['blocked', 'failed'].includes(step.status) && !step.waiting_on?.startsWith('handoff:')) {
845
+ step.status = 'planned';
846
+ step.blocked_reason = undefined;
847
+ }
848
+ plan.status = deriveTaskPlanStatus(plan);
849
+ goal.status = 'executing';
850
+ goal.stop_requested = false;
851
+ goal.reason_code = undefined;
852
+ d.repo.savePlan(goalId, plan);
853
+ d.repo.saveGoal(goal);
854
+ return { goal, plan };
855
+ }, { operation_label: 'host_resume_goal' });
856
+ }
857
+ export { beginStep, completeStep, applyHostResultToStep } from './host-goal-engine.js';
858
+ export async function stopGoal(goalId, deps = {}) {
725
859
  const d = resolveDeps(deps);
726
860
  const goal = requireGoal(d, goalId);
861
+ if (goal.executor === 'host') {
862
+ const { withStoreWriteCoordinator } = await import('./store/write-coordinator.js');
863
+ return withStoreWriteCoordinator(d.repo.planPath(goalId), async () => {
864
+ const fresh = requireGoal(d, goalId);
865
+ if (fresh.status !== 'done') {
866
+ fresh.stop_requested = true;
867
+ fresh.status = 'stopped';
868
+ }
869
+ return d.repo.saveGoal(fresh);
870
+ }, { operation_label: 'host_stop_goal' });
871
+ }
727
872
  goal.stop_requested = true;
728
873
  return d.repo.saveGoal(goal);
729
874
  }
@@ -777,15 +922,26 @@ function requirePlan(d, goal) {
777
922
  return plan;
778
923
  }
779
924
  function budgetExhausted(d, goal) {
780
- if (goal.tick_count >= goal.budgets.max_ticks)
925
+ if (goal.budgets.tick_budget_explicit && goal.tick_count >= goal.budgets.max_ticks)
781
926
  return "max_ticks";
782
- if (goal.started_at) {
927
+ if (goal.budgets.deadline_explicit && goal.started_at) {
783
928
  const elapsed = d.now() - Date.parse(goal.started_at);
784
929
  if (elapsed >= goal.budgets.max_wall_clock_ms)
785
930
  return "max_wall_clock";
786
931
  }
787
932
  return undefined;
788
933
  }
934
+ function observeFailure(goal, signature) {
935
+ const previous = goal.failure_repetition;
936
+ goal.failure_repetition = { signature, count: previous?.signature === signature ? previous.count + 1 : 1 };
937
+ return goal.failure_repetition.count >= 3;
938
+ }
939
+ /** Learning groups related failures; loop detection must also retain exact test identities. */
940
+ export function failureProgressSignature(text) {
941
+ const normalized = stripVTControlCharacters(text).replace(/\r\n/g, '\n')
942
+ .replace(/\b\d{4}-\d\d-\d\d[T ][\d:.]+Z?\b/g, '<time>').trim();
943
+ return `${failureSignature(normalized) ?? 'failure'}:${createHash('sha256').update(normalized).digest('hex')}`;
944
+ }
789
945
  function blockedReasons(plan) {
790
946
  return plan.steps
791
947
  .filter((s) => s.status === "blocked" || s.status === "failed")
@@ -6,6 +6,10 @@ export interface HandoffLifecycleEvent {
6
6
  note?: string;
7
7
  }
8
8
  export interface HandoffRecord {
9
+ step_ref?: {
10
+ goal_id: string;
11
+ step_id: string;
12
+ };
9
13
  handoff_id: string;
10
14
  created_at: string;
11
15
  updated_at: string;
@@ -24,6 +28,10 @@ export interface HandoffRegistryFile {
24
28
  handoffs: Record<string, HandoffRecord>;
25
29
  }
26
30
  export interface RegisterHandoffInput {
31
+ step_ref?: {
32
+ goal_id: string;
33
+ step_id: string;
34
+ };
27
35
  handoff_id: string;
28
36
  from: string;
29
37
  to: string;
@@ -68,4 +76,6 @@ export declare function registerHandoffSafe(input: RegisterHandoffInput): Promis
68
76
  * Concurrency-safe variant of acknowledgeHandoff.
69
77
  */
70
78
  export declare function acknowledgeHandoffSafe(input: AcknowledgeHandoffInput): Promise<HandoffAckResult>;
79
+ /** Called after the goal plan is saved; this packed record is a repairable view. */
80
+ export declare function syncLinkedHandoffStepSafe(id: string, summary: string): Promise<void>;
71
81
  //# sourceMappingURL=handoff-registry.d.ts.map
@@ -68,6 +68,7 @@ function parseRegistry(raw) {
68
68
  task_type: typeof row.task_type === "string" ? row.task_type : undefined,
69
69
  priority: typeof row.priority === "string" ? row.priority : undefined,
70
70
  source_file: typeof row.source_file === "string" ? row.source_file : undefined,
71
+ step_ref: row.step_ref,
71
72
  status: row.status === "open" ||
72
73
  row.status === "accepted" ||
73
74
  row.status === "blocked" ||
@@ -168,6 +169,7 @@ function toLegacyHandoffRecord(record) {
168
169
  task_type: typeof metadata.task_type === "string" ? metadata.task_type : record.task,
169
170
  priority: typeof metadata.priority === "string" ? metadata.priority : undefined,
170
171
  source_file: typeof metadata.source_file === "string" ? metadata.source_file : undefined,
172
+ step_ref: metadata.step_ref,
171
173
  status,
172
174
  history: normalizeHistory(metadata.history, status, updatedAt),
173
175
  };
@@ -229,6 +231,7 @@ export function registerHandoff(input) {
229
231
  task_type: input.task_type,
230
232
  priority: input.priority,
231
233
  source_file: input.source_file,
234
+ step_ref: input.step_ref,
232
235
  status: "open",
233
236
  history: [
234
237
  {
@@ -291,6 +294,13 @@ export function getHandoffRegistryPath() {
291
294
  * Concurrency-safe variant of registerHandoff.
292
295
  */
293
296
  export async function registerHandoffSafe(input) {
297
+ if (input.step_ref) {
298
+ const { withLinkedHandoff } = await import('./host-goal-engine.js');
299
+ return withLinkedHandoff(input.step_ref, { id: input.handoff_id, status: 'open', actor: input.actor ?? input.from, to: input.to }, () => registerHandoffStoreSafe(input));
300
+ }
301
+ return registerHandoffStoreSafe(input);
302
+ }
303
+ async function registerHandoffStoreSafe(input) {
294
304
  const root = workspaceRoot();
295
305
  const storePath = getWorkspaceStorePath(root);
296
306
  await waitForTodoStoreMirror();
@@ -325,6 +335,7 @@ export async function registerHandoffSafe(input) {
325
335
  task_type: input.task_type,
326
336
  priority: input.priority,
327
337
  source_file: input.source_file,
338
+ step_ref: input.step_ref,
328
339
  history: [
329
340
  {
330
341
  timestamp_utc: now,
@@ -353,6 +364,34 @@ export async function registerHandoffSafe(input) {
353
364
  * Concurrency-safe variant of acknowledgeHandoff.
354
365
  */
355
366
  export async function acknowledgeHandoffSafe(input) {
367
+ const record = readHandoffRegistry().handoffs[input.handoff_id];
368
+ if (record?.step_ref) {
369
+ const { withLinkedHandoff, completeStep } = await import('./host-goal-engine.js');
370
+ const actor = input.actor;
371
+ if (!actor || actor !== record.to)
372
+ return { ok: false, path: getHandoffRegistryPath(), error: `Linked handoff belongs to ${record.to}; supply that agent label.` };
373
+ if (input.status === 'completed') {
374
+ if (record.status === 'completed')
375
+ return acknowledgeHandoffStoreSafe(input);
376
+ const { GoalRepository } = await import('./store/repositories/goal-repository.js');
377
+ const step = new GoalRepository(workspaceRoot()).getPlan(record.step_ref.goal_id)?.steps.find(s => s.step_id === record.step_ref.step_id);
378
+ if (!step)
379
+ return { ok: false, path: getHandoffRegistryPath(), error: 'Linked step missing' };
380
+ const result = await completeStep(record.step_ref.goal_id, record.step_ref.step_id, { status: 'done', summary: input.note || record.title, evidence: [{ ref: `handoff:${record.handoff_id}`, kind: 'note', detail: input.note }] }, { agent: actor, attempt: step.attempt ?? 0 });
381
+ if (!result.ok || result.status !== 'done')
382
+ return { ok: false, path: getHandoffRegistryPath(), error: result.message ?? `Step retained as ${result.status}: ${result.summary}` };
383
+ return acknowledgeHandoffStoreSafe(input);
384
+ }
385
+ try {
386
+ return await withLinkedHandoff(record.step_ref, { id: record.handoff_id, status: input.status, actor, to: record.to }, () => acknowledgeHandoffStoreSafe(input));
387
+ }
388
+ catch (error) {
389
+ return { ok: false, path: getHandoffRegistryPath(), error: error instanceof Error ? error.message : String(error) };
390
+ }
391
+ }
392
+ return acknowledgeHandoffStoreSafe(input);
393
+ }
394
+ async function acknowledgeHandoffStoreSafe(input) {
356
395
  const root = workspaceRoot();
357
396
  const storePath = getWorkspaceStorePath(root);
358
397
  await waitForTodoStoreMirror();
@@ -423,4 +462,24 @@ export async function acknowledgeHandoffSafe(input) {
423
462
  }, { operation_label: "acknowledgeHandoffSafe" });
424
463
  });
425
464
  }
465
+ /** Called after the goal plan is saved; this packed record is a repairable view. */
466
+ export async function syncLinkedHandoffStepSafe(id, summary) {
467
+ const root = workspaceRoot(), path = getWorkspaceStorePath(root);
468
+ await withStoreWriteCoordinator(path, async () => {
469
+ const store = await openStore(path);
470
+ try {
471
+ const repo = new HandoffRepository(store), record = await repo.get(id);
472
+ if (!record)
473
+ throw new Error(`Linked handoff missing: ${id}`);
474
+ if (record.status !== 'closed') {
475
+ await repo.update(id, { status: 'closed', metadata: { ...record.metadata, legacy_status: 'completed', history: [...(record.metadata?.history ?? []), { timestamp_utc: new Date().toISOString(), status: 'completed', actor: 'goal-step', note: summary }] } });
476
+ await store.commit();
477
+ await new ProjectionManager(store, root).projectAfterCommit(['handoffs']);
478
+ }
479
+ }
480
+ finally {
481
+ await store.close({ commit: false });
482
+ }
483
+ }, { operation_label: 'linked_handoff_view' });
484
+ }
426
485
  //# sourceMappingURL=handoff-registry.js.map
@@ -6,7 +6,9 @@ export interface SkillReference {
6
6
  }
7
7
  export declare function getAgentInstructionPath(role: AgentRole): string | undefined;
8
8
  export declare function getAgentManifestPath(role: AgentRole): string | undefined;
9
- export declare function readAgentInstructions(role: AgentRole): string;
9
+ export declare function readAgentInstructions(role: AgentRole, options?: {
10
+ includeLearned?: boolean;
11
+ }): string;
10
12
  export declare function readAgentManifest(role: AgentRole): string;
11
13
  export declare function getTaskArtifactPath(key: TaskKey): string | undefined;
12
14
  export declare function readTaskArtifact(key: TaskKey): string;
@@ -15,5 +17,7 @@ export declare function readKernelArtifact(key: KernelKey): string;
15
17
  export declare function resolveWritableTaskPath(key: TaskKey): string;
16
18
  export declare function listAvailableSkills(): SkillReference[];
17
19
  export declare function getSkillPath(name: string): string | undefined;
18
- export declare function readSkillInstructions(name: string): string;
20
+ export declare function readSkillInstructions(name: string, options?: {
21
+ includeLearned?: boolean;
22
+ }): string;
19
23
  //# sourceMappingURL=artifacts.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { approvedInstructionText, approvedDistillerText } from '../learning-overlays.js';
1
2
  import { existsSync, readdirSync } from "node:fs";
2
3
  import { resolve } from "node:path";
3
4
  import { AGENT_FILES, AGENT_MANIFEST_FILES, DEFAULT_AGENT_INSTRUCTION_FILES, DEFAULT_AGENT_MANIFEST_FILES, DEFAULT_KERNEL_FILES, DEFAULTS_SKILLS_ROOT, DEFAULT_TASK_FILES, KERNEL_FILES, STORE_AGENT_FILES, STORE_KERNEL_FILES, STORE_TASK_FILES, TASK_FILES, } from "./constants.js";
@@ -30,12 +31,13 @@ export function getAgentManifestPath(role) {
30
31
  const workspaceCandidates = toAbsoluteWorkspaceCandidates(AGENT_MANIFEST_FILES[role]);
31
32
  return firstExistingPath([...workspaceCandidates, DEFAULT_AGENT_MANIFEST_FILES[role]]);
32
33
  }
33
- export function readAgentInstructions(role) {
34
+ export function readAgentInstructions(role, options = {}) {
34
35
  const store = readStoreText(STORE_AGENT_FILES[role].instructions.map((file) => `knowledge/agents/${STORE_AGENT_FILES[role].agent}/${file}`));
36
+ const overlay = options.includeLearned ? approvedInstructionText(currentWorkspaceRoot(), role) : "";
35
37
  if (store)
36
- return store.text;
38
+ return store.text + overlay;
37
39
  const workspaceCandidates = toAbsoluteWorkspaceCandidates(AGENT_FILES[role]);
38
- return readFirstAvailable([...workspaceCandidates, DEFAULT_AGENT_INSTRUCTION_FILES[role]]).text;
40
+ return readFirstAvailable([...workspaceCandidates, DEFAULT_AGENT_INSTRUCTION_FILES[role]]).text + overlay;
39
41
  }
40
42
  export function readAgentManifest(role) {
41
43
  const store = readStoreText(STORE_AGENT_FILES[role].manifests.map((file) => `knowledge/agents/${STORE_AGENT_FILES[role].agent}/${file}`));
@@ -136,7 +138,7 @@ export function getSkillPath(name) {
136
138
  const exact = skills.find((skill) => skill.name.toLowerCase() === target);
137
139
  return exact?.path;
138
140
  }
139
- export function readSkillInstructions(name) {
141
+ export function readSkillInstructions(name, options = {}) {
140
142
  const target = name.trim().toLowerCase();
141
143
  const match = listAvailableSkills().find((skill) => skill.name.toLowerCase() === target);
142
144
  if (!match) {
@@ -145,8 +147,8 @@ export function readSkillInstructions(name) {
145
147
  .join(", ")}`;
146
148
  }
147
149
  if (match.source === "store") {
148
- return readStoreText([`knowledge/skills/${match.name}/SKILL.md`])?.text ?? "";
150
+ return (readStoreText([`knowledge/skills/${match.name}/SKILL.md`])?.text ?? "") + (options.includeLearned && target === 'learn-from-work' ? approvedDistillerText(currentWorkspaceRoot()) : '');
149
151
  }
150
- return readText(match.path);
152
+ return readText(match.path) + (options.includeLearned && target === 'learn-from-work' ? approvedDistillerText(currentWorkspaceRoot()) : '');
151
153
  }
152
154
  //# sourceMappingURL=artifacts.js.map