@voybio/ace-swarm 2.4.8 → 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 (187) 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 +58 -13
  58. package/dist/job-scheduler.js +298 -48
  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 +111 -6
  89. package/dist/orchestrator-supervisor.js +67 -43
  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.d.ts +3 -2
  165. package/dist/tools-handoff.js +53 -23
  166. package/dist/tools-lifecycle.js +1 -1
  167. package/dist/tools-memory.d.ts +0 -3
  168. package/dist/tools-memory.js +51 -6
  169. package/dist/tools-scheduler.js +20 -17
  170. package/dist/tools-todo.js +160 -148
  171. package/dist/vericify-bridge.d.ts +8 -0
  172. package/dist/vericify-bridge.js +18 -0
  173. package/dist/vericify-context.d.ts +4 -0
  174. package/dist/vericify-context.js +26 -5
  175. package/dist/vericify-native.d.ts +101 -0
  176. package/dist/vericify-native.js +221 -0
  177. package/dist/verification-worker.d.ts +2 -0
  178. package/dist/verification-worker.js +31 -0
  179. package/docs/mcp-tool-reference.md +50 -10
  180. package/docs/troubleshooting.md +29 -0
  181. package/package.json +19 -6
  182. package/scripts/dev/run-tests-isolated.mjs +165 -0
  183. package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
  184. package/scripts/dev/test-isolation-guard.mjs +20 -0
  185. package/scripts/dev/test-preload.mjs +153 -0
  186. package/scripts/dev/test-source-inventory.mjs +31 -0
  187. package/docs/development.md +0 -46
@@ -1,3 +1,4 @@
1
+ import { assertTestWriteAllowed } from "../store/test-write-boundary.js";
1
2
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "node:fs";
2
3
  import { homedir } from "node:os";
3
4
  import { basename, dirname, join, resolve } from "node:path";
@@ -9,6 +10,7 @@ import { resolveWorkspaceRoot } from "./workspace-root.js";
9
10
  function ensureDir(dirPath, result) {
10
11
  if (existsSync(dirPath))
11
12
  return;
13
+ assertTestWriteAllowed(dirPath);
12
14
  mkdirSync(dirPath, { recursive: true });
13
15
  result.created.push(dirPath);
14
16
  }
@@ -25,12 +27,14 @@ function copyTree(srcRoot, destRoot, result, force = false) {
25
27
  continue;
26
28
  }
27
29
  if (!existsSync(dest)) {
30
+ assertTestWriteAllowed(dest);
28
31
  mkdirSync(dirname(dest), { recursive: true });
29
32
  copyFileSync(src, dest);
30
33
  result.created.push(dest);
31
34
  continue;
32
35
  }
33
36
  if (force) {
37
+ assertTestWriteAllowed(dest);
34
38
  copyFileSync(src, dest);
35
39
  result.updated.push(dest);
36
40
  }
@@ -41,12 +45,14 @@ function copyTree(srcRoot, destRoot, result, force = false) {
41
45
  }
42
46
  function writeIfMissingOrForced(path, content, result, force = false) {
43
47
  if (!existsSync(path)) {
48
+ assertTestWriteAllowed(path);
44
49
  mkdirSync(dirname(path), { recursive: true });
45
50
  writeFileSync(path, content, "utf-8");
46
51
  result.created.push(path);
47
52
  return;
48
53
  }
49
54
  if (force) {
55
+ assertTestWriteAllowed(path);
50
56
  writeFileSync(path, content, "utf-8");
51
57
  result.updated.push(path);
52
58
  }
@@ -410,17 +416,6 @@ function buildWorkspaceStateSeedFiles(projectName, generatedAt) {
410
416
  primary_cta: "",
411
417
  }, null, 2),
412
418
  },
413
- {
414
- path: wsPath("engineering-state", "BUILD_STATUS.md"),
415
- content: [
416
- "# BUILD_STATUS",
417
- "",
418
- "- Status: not started",
419
- "- Active owner: ACE-Coders",
420
- "- Blockers: none recorded",
421
- `- Last update: ${generatedAt}`,
422
- ].join("\n"),
423
- },
424
419
  {
425
420
  path: wsPath("engineering-state", "SPEC_CONTRACT.json"),
426
421
  content: JSON.stringify({
@@ -447,20 +442,12 @@ function buildWorkspaceStateSeedFiles(projectName, generatedAt) {
447
442
  "- TBD",
448
443
  ].join("\n"),
449
444
  },
450
- {
451
- path: wsPath("engineering-state", "TEST_LOG.md"),
452
- content: [
453
- "# TEST_LOG",
454
- "",
455
- `- ${generatedAt} bootstrap: no tests executed yet.`,
456
- ].join("\n"),
457
- },
458
445
  {
459
446
  path: wsPath("engineering-state", "src", "README.md"),
460
447
  content: [
461
448
  "# Engineering Source",
462
449
  "",
463
- `Place the main implementation here. Keep test evidence in \`${ACE_ROOT_REL}/engineering-state/TEST_LOG.md\`.`,
450
+ "Place the main implementation here. Use the project’s existing tests and return their actual results with the work outcome.",
464
451
  ].join("\n"),
465
452
  },
466
453
  ];
@@ -595,6 +582,12 @@ export function bootstrapAceWorkspace(options = {}) {
595
582
  }
596
583
  copyTree(DEFAULTS_GITHUB_ROOT, acePath(".github"), result, force);
597
584
  copyTree(DEFAULTS_SCRIPTS_ROOT, acePath("scripts", "ace"), result, force);
585
+ const dispatcher = acePath("scripts", "ace", "ace-hook-dispatch.mjs");
586
+ if (result.created.includes(dispatcher) || result.updated.includes(dispatcher)) {
587
+ const moduleUrl = new URL("../ace-autonomy.js", import.meta.url).href;
588
+ assertTestWriteAllowed(dispatcher);
589
+ writeFileSync(dispatcher, readFileSync(dispatcher, "utf8").replace("__ACE_AUTONOMY_MODULE_URL__", moduleUrl));
590
+ }
598
591
  const masterPlanPath = wsPath("global-state", "MASTER_PLAN.md");
599
592
  writeIfMissingOrForced(masterPlanPath, [
600
593
  `# ${projectName} — Master Plan`,
@@ -3,6 +3,7 @@ import { dirname, isAbsolute, relative, resolve } from "node:path";
3
3
  import { isInside, normalizeRelPath } from "../shared.js";
4
4
  import { ACE_MANAGED_PREFIXES, ACE_PROJECTED_WORKSPACE_FILES, ACE_ROOT_REL, SWARM_AGENTS, } from "./constants.js";
5
5
  import { resolveWorkspaceRoot } from "./workspace-root.js";
6
+ import { assertTestWriteAllowed } from "../store/test-write-boundary.js";
6
7
  export function currentWorkspaceRoot() {
7
8
  return resolveWorkspaceRoot();
8
9
  }
@@ -44,6 +45,7 @@ export function isSwarmRole(role) {
44
45
  }
45
46
  export function atomicWrite(filePath, content) {
46
47
  const abs = isAbsolute(filePath) ? filePath : resolveWorkspaceWritePath(filePath);
48
+ assertTestWriteAllowed(abs);
47
49
  mkdirSync(dirname(abs), { recursive: true });
48
50
  const tmpPath = `${abs}.${process.pid}.tmp`;
49
51
  writeFileSync(tmpPath, content, "utf-8");
@@ -5,6 +5,7 @@ import { isInside, normalizeRelPath } from "../shared.js";
5
5
  import { getWorkspaceStorePath, listStoreKeysSync, parseVirtualStorePath, readStoreBlobSync, readVirtualStorePathSync, toVirtualStorePath, } from "../store/store-snapshot.js";
6
6
  import { isOperationalArtifactPath, operationalArtifactKey, writeOperationalArtifactQueued, writeStoreBlobQueued, writeStoreBlobSync, } from "../store/store-artifacts.js";
7
7
  import { withStoreWriteCoordinatorSync } from "../store/write-coordinator.js";
8
+ import { assertTestWriteAllowed, captureTestWriteBoundary, withTestWriteBoundary } from "../store/test-write-boundary.js";
8
9
  import { ACE_ROOT_REL, ACE_SCRIPTS_ROOT_REL, ACE_SKILLS_ROOT_REL, ACE_TASKS_ROOT_REL, AGENT_FILES, AGENT_MANIFEST_FILES, DEFAULTS_ROOT, KERNEL_FILES, STORE_AGENT_FILES, STORE_KERNEL_FILES, STORE_TASK_FILES, TASK_FILES, } from "./constants.js";
9
10
  import { currentWorkspaceRoot, isProjectedAceWorkspacePath, mapAceWorkspaceRelativePath, resolveWorkspaceReadPath, resolveWorkspaceWritePath, } from "./path-utils.js";
10
11
  export function formatStorePath(key) {
@@ -24,9 +25,16 @@ export function listStoreSkillReferences() {
24
25
  const root = currentWorkspaceRoot();
25
26
  const names = new Set();
26
27
  for (const key of listStoreKeysSync(root, "knowledge/skills/")) {
27
- const parts = key.split("/");
28
- if (parts[2])
29
- names.add(parts[2]);
28
+ if (!key.endsWith('/SKILL.md'))
29
+ continue;
30
+ const name = key.slice('knowledge/skills/'.length, -'/SKILL.md'.length);
31
+ if (name.startsWith('learned/')) {
32
+ const text = readStoreBlobSync(root, key) ?? '';
33
+ if (!/^status: active$/m.test(text))
34
+ continue;
35
+ }
36
+ if (name)
37
+ names.add(name);
30
38
  }
31
39
  return [...names]
32
40
  .sort((a, b) => a.localeCompare(b))
@@ -249,6 +257,7 @@ export function safeWrite(filePath, content) {
249
257
  const abs = isAbsolute(filePath) && isInside(root, filePath)
250
258
  ? filePath
251
259
  : resolveWorkspaceWritePath(filePath, root);
260
+ assertTestWriteAllowed(abs);
252
261
  mkdirSync(dirname(abs), { recursive: true });
253
262
  const tmpPath = `${abs}.${process.pid}.${Date.now()}.tmp`;
254
263
  writeFileSync(tmpPath, content, "utf-8");
@@ -277,15 +286,21 @@ export async function safeWriteAsync(filePath, content) {
277
286
  const abs = isAbsolute(filePath) && isInside(root, filePath)
278
287
  ? filePath
279
288
  : resolveWorkspaceWritePath(filePath, root);
289
+ const assertBoundary = captureTestWriteBoundary(abs);
280
290
  await mkdir(dirname(abs), { recursive: true });
281
291
  const tmpPath = `${abs}.${process.pid}.${Date.now()}.tmp`;
292
+ assertBoundary();
282
293
  await writeFile(tmpPath, content, "utf-8");
294
+ assertBoundary();
283
295
  await rename(tmpPath, abs);
284
296
  return abs;
285
297
  }
286
298
  const _fileLocks = new Map();
287
299
  export async function withFileLock(path, fn) {
288
300
  const key = resolveWorkspaceWritePath(path);
301
+ return withTestWriteBoundary(key, () => withFileLockInternal(key, fn));
302
+ }
303
+ async function withFileLockInternal(key, fn) {
289
304
  const prev = _fileLocks.get(key) ?? Promise.resolve();
290
305
  let releaseLock;
291
306
  const next = new Promise((r) => { releaseLock = r; });
@@ -1,3 +1,5 @@
1
+ /** Bind an operation and its asynchronous descendants without changing process-wide environment. */
2
+ export declare function withWorkspaceRoot<T>(root: string, fn: () => T): T;
1
3
  export declare function resolveWorkspaceRoot(): string;
2
4
  export declare const WORKSPACE_ROOT: string;
3
5
  //# sourceMappingURL=workspace-root.d.ts.map
@@ -1,5 +1,11 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
+ import { AsyncLocalStorage } from "node:async_hooks";
4
+ const workspaceScope = new AsyncLocalStorage();
5
+ /** Bind an operation and its asynchronous descendants without changing process-wide environment. */
6
+ export function withWorkspaceRoot(root, fn) {
7
+ return workspaceScope.run(normalizeWorkspaceRootLikePath(root), fn);
8
+ }
3
9
  function normalizeWorkspaceRootCandidate(candidate) {
4
10
  const trimmed = candidate?.trim();
5
11
  if (!trimmed)
@@ -51,6 +57,9 @@ function pinWorkspaceRoot(root) {
51
57
  return normalized;
52
58
  }
53
59
  export function resolveWorkspaceRoot() {
60
+ const scoped = workspaceScope.getStore();
61
+ if (scoped)
62
+ return scoped;
54
63
  const explicit = normalizeWorkspaceRootCandidate(process.env.ACE_WORKSPACE_ROOT);
55
64
  if (explicit)
56
65
  return pinWorkspaceRoot(findNearestInitiatedWorkspaceRoot(explicit) ?? explicit);
package/dist/helpers.d.ts CHANGED
@@ -2,6 +2,7 @@ import { resolveWorkspaceRoot } from "./helpers/workspace-root.js";
2
2
  export { isInside, isReadError, normalizeRelPath, looksLikeSwarmHandoffPath } from "./shared.js";
3
3
  export * from "./helpers/constants.js";
4
4
  export { resolveWorkspaceRoot };
5
+ export { withWorkspaceRoot } from "./helpers/workspace-root.js";
5
6
  export declare const WORKSPACE_ROOT: string;
6
7
  export * from "./helpers/path-utils.js";
7
8
  export * from "./helpers/store-resolution.js";
package/dist/helpers.js CHANGED
@@ -2,6 +2,7 @@ import { resolveWorkspaceRoot } from "./helpers/workspace-root.js";
2
2
  export { isInside, isReadError, normalizeRelPath, looksLikeSwarmHandoffPath } from "./shared.js";
3
3
  export * from "./helpers/constants.js";
4
4
  export { resolveWorkspaceRoot };
5
+ export { withWorkspaceRoot } from "./helpers/workspace-root.js";
5
6
  export const WORKSPACE_ROOT = resolveWorkspaceRoot();
6
7
  export * from "./helpers/path-utils.js";
7
8
  export * from "./helpers/store-resolution.js";
@@ -4,7 +4,6 @@ declare const bridgeEventSchema: z.ZodObject<{
4
4
  type: z.ZodEnum<{
5
5
  error: "error";
6
6
  status: "status";
7
- tool_start: "tool_start";
8
7
  session_open: "session_open";
9
8
  turn_run: "turn_run";
10
9
  interrupt: "interrupt";
@@ -12,6 +11,7 @@ declare const bridgeEventSchema: z.ZodObject<{
12
11
  delta: "delta";
13
12
  reasoning: "reasoning";
14
13
  assistant_interim: "assistant_interim";
14
+ tool_start: "tool_start";
15
15
  tool_progress: "tool_progress";
16
16
  tool_complete: "tool_complete";
17
17
  final: "final";
@@ -12,7 +12,14 @@ export interface HermesLocalTurnOptions {
12
12
  maxTurns: number;
13
13
  /** Probed context window for the model; falls back to a conservative default. */
14
14
  contextLength?: number;
15
+ /** Explicit total deadline only; omitted or 0 means unlimited. */
15
16
  timeoutMs?: number;
17
+ stallTimeoutMs?: number;
18
+ stallWarningMs?: number;
19
+ onStall?: (event: {
20
+ silence_ms: number;
21
+ last_progress_at: number;
22
+ }) => void;
16
23
  }
17
24
  export interface HermesLocalTurnResult {
18
25
  result: BridgeResult;
@@ -38,14 +45,24 @@ export interface HermesSubprocessExecutorOptions {
38
45
  command?: readonly string[];
39
46
  launchProfile?: HermesLaunchProfile;
40
47
  keepTemp?: boolean;
48
+ /** Explicit total deadline only; omitted or 0 means unlimited. */
41
49
  timeoutMs?: number;
50
+ stallTimeoutMs?: number;
51
+ stallWarningMs?: number;
52
+ onStall?: (event: {
53
+ silence_ms: number;
54
+ last_progress_at: number;
55
+ }) => void;
42
56
  maxOutputBytes?: number;
43
57
  }
44
58
  export declare class HermesSubprocessExecutor implements HermesLocalExecutor {
45
59
  private options;
46
60
  private readonly activeChildren;
61
+ private readonly interruptedChildren;
62
+ private readonly killTimers;
47
63
  constructor(options?: HermesSubprocessExecutorOptions);
48
64
  interrupt(): void;
65
+ private signalChildGroup;
49
66
  private interruptChild;
50
67
  runTurn(options: HermesLocalTurnOptions): Promise<HermesLocalTurnResult>;
51
68
  }
@@ -66,6 +66,8 @@ function hermesBaseUrl(provider, baseUrl) {
66
66
  export class HermesSubprocessExecutor {
67
67
  options;
68
68
  activeChildren = new Set();
69
+ interruptedChildren = new WeakSet();
70
+ killTimers = new WeakMap();
69
71
  constructor(options = {}) {
70
72
  this.options = options;
71
73
  }
@@ -73,18 +75,39 @@ export class HermesSubprocessExecutor {
73
75
  for (const child of this.activeChildren)
74
76
  this.interruptChild(child);
75
77
  }
78
+ signalChildGroup(child, signal) {
79
+ if (!child.pid)
80
+ return;
81
+ try {
82
+ if (process.platform === "win32")
83
+ child.kill(signal);
84
+ else
85
+ process.kill(-child.pid, signal);
86
+ }
87
+ catch (error) {
88
+ if (error.code !== "ESRCH")
89
+ throw error;
90
+ }
91
+ }
76
92
  interruptChild(child) {
77
- if (child.killed)
93
+ if (this.interruptedChildren.has(child))
78
94
  return;
79
- child.kill("SIGTERM");
80
- const killTimer = setTimeout(() => {
81
- if (this.activeChildren.has(child) && child.exitCode === null && child.signalCode === null) {
82
- child.kill("SIGKILL");
83
- }
84
- }, 1_000);
95
+ this.interruptedChildren.add(child);
96
+ this.signalChildGroup(child, "SIGTERM");
97
+ const killTimer = setTimeout(() => this.signalChildGroup(child, "SIGKILL"), 1_000);
85
98
  killTimer.unref();
99
+ this.killTimers.set(child, killTimer);
86
100
  }
87
101
  async runTurn(options) {
102
+ const timeoutMs = options.timeoutMs ?? this.options.timeoutMs;
103
+ const stallTimeoutMs = options.stallTimeoutMs ?? this.options.stallTimeoutMs ?? 60 * 60_000;
104
+ const stallWarningMs = options.stallWarningMs ?? this.options.stallWarningMs ?? Math.min(10 * 60_000, stallTimeoutMs);
105
+ for (const [name, value] of Object.entries({ timeoutMs, stallTimeoutMs, stallWarningMs })) {
106
+ if (value !== undefined && (!Number.isFinite(value) || value < 0))
107
+ throw new Error(`${name} must be finite and nonnegative; 0 disables the limit.`);
108
+ }
109
+ if (stallTimeoutMs > 0 && stallWarningMs > stallTimeoutMs)
110
+ throw new Error("The stall warning must not follow the final silence limit.");
88
111
  const sessionId = `hermes-${randomUUID()}`;
89
112
  const shadowId = `shadow-${randomUUID()}`;
90
113
  const hermesHome = mkdtempSync(resolve(tmpdir(), "ace-hermes-home-"));
@@ -147,6 +170,7 @@ export class HermesSubprocessExecutor {
147
170
  ACE_WORKSPACE_ROOT: options.runtime.workspaceRoot,
148
171
  },
149
172
  stdio: ["pipe", "pipe", "pipe"],
173
+ detached: process.platform !== "win32",
150
174
  });
151
175
  this.activeChildren.add(child);
152
176
  const decoder = new HermesBridgeFrameDecoder();
@@ -159,6 +183,8 @@ export class HermesSubprocessExecutor {
159
183
  let finalCompleted;
160
184
  let finalCount = 0;
161
185
  let outputBytes = 0;
186
+ let lastProgressAt = Date.now();
187
+ let warned = false;
162
188
  let outputOverflow = false;
163
189
  const maxOutputBytes = this.options.maxOutputBytes ?? 4 * 1024 * 1024;
164
190
  // Single per-frame consumer so the live stream and the post-close tail-flush
@@ -183,6 +209,8 @@ export class HermesSubprocessExecutor {
183
209
  }
184
210
  };
185
211
  child.stdout.on("data", (chunk) => {
212
+ lastProgressAt = Date.now();
213
+ warned = false;
186
214
  outputBytes += chunk.byteLength;
187
215
  if (outputBytes > maxOutputBytes) {
188
216
  outputOverflow = true;
@@ -194,6 +222,8 @@ export class HermesSubprocessExecutor {
194
222
  }
195
223
  });
196
224
  child.stderr.on("data", (chunk) => {
225
+ lastProgressAt = Date.now();
226
+ warned = false;
197
227
  outputBytes += chunk.byteLength;
198
228
  if (outputBytes > maxOutputBytes) {
199
229
  outputOverflow = true;
@@ -215,18 +245,41 @@ export class HermesSubprocessExecutor {
215
245
  system_prompt: "You are running inside ACE Hermes-local mode. Use only the imported ACE MCP toolset for tool calls. Do not use plain text as executable authority.",
216
246
  };
217
247
  child.stdin.end(`${JSON.stringify(request)}\n`);
218
- const timeoutMs = options.timeoutMs ?? this.options.timeoutMs ?? 120_000;
248
+ let stalled = false;
249
+ const silenceTimer = stallTimeoutMs > 0 ? setInterval(() => {
250
+ const silence = Date.now() - lastProgressAt;
251
+ if (!warned && stallWarningMs > 0 && silence >= stallWarningMs) {
252
+ warned = true;
253
+ try {
254
+ (options.onStall ?? this.options.onStall)?.({ silence_ms: silence, last_progress_at: lastProgressAt });
255
+ }
256
+ catch { /* Warning callback cannot terminate useful work. */ }
257
+ }
258
+ if (silence >= stallTimeoutMs) {
259
+ stalled = true;
260
+ this.interruptChild(child);
261
+ }
262
+ }, Math.max(10, Math.min(1000, stallWarningMs || stallTimeoutMs, stallTimeoutMs))) : undefined;
263
+ silenceTimer?.unref();
219
264
  let timedOut = false;
220
- const timeout = setTimeout(() => {
265
+ const timeout = timeoutMs && timeoutMs > 0 ? setTimeout(() => {
221
266
  timedOut = true;
222
267
  this.interruptChild(child);
223
- }, timeoutMs);
224
- timeout.unref();
268
+ }, timeoutMs) : undefined;
269
+ timeout?.unref();
225
270
  const exitCode = await new Promise((resolvePromise, reject) => {
226
271
  child.on("error", reject);
227
272
  child.on("close", resolvePromise);
228
273
  }).finally(() => {
229
- clearTimeout(timeout);
274
+ if (timeout)
275
+ clearTimeout(timeout);
276
+ if (silenceTimer)
277
+ clearInterval(silenceTimer);
278
+ if (this.interruptedChildren.has(child))
279
+ this.signalChildGroup(child, "SIGKILL");
280
+ const killTimer = this.killTimers.get(child);
281
+ if (killTimer)
282
+ clearTimeout(killTimer);
230
283
  this.activeChildren.delete(child);
231
284
  });
232
285
  for (const frame of decoder.push(new Uint8Array(), true)) {
@@ -252,14 +305,14 @@ export class HermesSubprocessExecutor {
252
305
  (event.turn_id !== undefined && event.turn_id !== request.turn_id));
253
306
  const eventAfterFinal = finalIndex >= 0 && events.slice(finalIndex + 1).some((event) => event.type !== "session_close");
254
307
  const protocolInvalid = protocolIdentityInvalid || eventAfterFinal;
255
- const status = sawError || malformed.length > 0 || exitCode !== 0 || timedOut || outputOverflow || finalCount !== 1 || protocolInvalid
308
+ const status = sawError || malformed.length > 0 || exitCode !== 0 || timedOut || stalled || outputOverflow || finalCount !== 1 || protocolInvalid
256
309
  ? "failed"
257
310
  : finalCompleted === false
258
311
  ? "blocked"
259
312
  : "completed";
260
313
  const summary = status === "failed"
261
314
  ? sawError ||
262
- `Hermes bridge failed${timedOut ? "; reason_code=hermes_timeout" : ""}${outputOverflow ? "; reason_code=hermes_output_limit" : ""}${finalCount !== 1 ? `; terminal_frames=${finalCount}` : ""}${protocolIdentityInvalid ? "; reason_code=hermes_frame_identity" : ""}${eventAfterFinal ? "; reason_code=hermes_frame_after_final" : ""}${malformed.length ? `; malformed_frames=${malformed.join(",")}` : ""}${stderr.length ? `; stderr=${stderr.join("").slice(0, 500)}` : ""}`
315
+ `Hermes bridge failed${timedOut ? "; reason_code=hermes_timeout" : ""}${stalled ? "; reason_code=hermes_stalled" : ""}${outputOverflow ? "; reason_code=hermes_output_limit" : ""}${finalCount !== 1 ? `; terminal_frames=${finalCount}` : ""}${protocolIdentityInvalid ? "; reason_code=hermes_frame_identity" : ""}${eventAfterFinal ? "; reason_code=hermes_frame_after_final" : ""}${malformed.length ? `; malformed_frames=${malformed.join(",")}` : ""}${stderr.length ? `; stderr=${stderr.join("").slice(0, 500)}` : ""}`
263
316
  : status === "blocked"
264
317
  ? finalText || "Hermes reported completed=false."
265
318
  : finalText || "Hermes-local turn completed.";
@@ -0,0 +1,138 @@
1
+ import { type GoalRecord } from './store/repositories/goal-repository.js';
2
+ import { type GoalEngineDeps, type StartGoalOptions, type ResumeGoalOptions } from './goal-engine.js';
3
+ import { type TaskPlan, type TaskStep } from './orchestrator-supervisor.js';
4
+ import { type CurrentWorkItem } from './ace-autonomy.js';
5
+ import type { HostWorkBinding } from './host-work-context.js';
6
+ import { type LearnedPayload, type LearningAcceptance } from './learned-artifacts.js';
7
+ import { type HostRequirementUpdate } from './host-plan-revision.js';
8
+ export interface StepOutcome {
9
+ status: 'done' | 'blocked' | 'failed';
10
+ summary: string;
11
+ evidence?: Array<{
12
+ ref: string;
13
+ kind: 'command' | 'artifact' | 'diff' | 'note';
14
+ detail?: string;
15
+ }>;
16
+ artifacts?: string[];
17
+ follow_ups?: Array<{
18
+ task: string;
19
+ role?: string;
20
+ depends_on?: string[];
21
+ }>;
22
+ waiting_on?: string;
23
+ requirement_updates?: HostRequirementUpdate[];
24
+ learned?: LearnedPayload;
25
+ feedback?: Array<{
26
+ id: string;
27
+ helpful: boolean;
28
+ }>;
29
+ }
30
+ export interface Assignment {
31
+ goal_id: string;
32
+ step_id: string;
33
+ attempt: number;
34
+ assignee: string;
35
+ task_text: string;
36
+ role: string;
37
+ expected_artifacts: string[];
38
+ required_evidence_refs: string[];
39
+ verify_at_goal_end: string[];
40
+ open_blockers: CurrentWorkItem[];
41
+ now: string;
42
+ observation: string;
43
+ started_at?: string;
44
+ learning?: string;
45
+ }
46
+ export interface HostStepResult {
47
+ ok: boolean;
48
+ isError?: boolean;
49
+ reason_code?: string;
50
+ message?: string;
51
+ now: string;
52
+ options?: Array<{
53
+ tool: string;
54
+ args: Record<string, unknown>;
55
+ why: string;
56
+ }>;
57
+ waiting_on?: string[];
58
+ candidates?: unknown[];
59
+ assignment?: Assignment;
60
+ next_ready?: string[];
61
+ goal_id?: string;
62
+ step_id?: string;
63
+ status?: string;
64
+ summary?: string;
65
+ goal_status?: GoalRecord['status'];
66
+ verification?: 'pending';
67
+ view_repair_pending?: string[];
68
+ skipped_follow_ups?: string[];
69
+ learning?: LearningAcceptance;
70
+ reopened?: string[];
71
+ requirement_updates_pending?: HostRequirementUpdate[];
72
+ }
73
+ export declare function hostRefusal(root: string, reason: string, message: string, goalId?: string): HostStepResult;
74
+ export declare function startHostGoal(objective: string, options: StartGoalOptions, deps?: GoalEngineDeps): Promise<{
75
+ ok: boolean;
76
+ goal: GoalRecord;
77
+ assignment: Assignment | undefined;
78
+ now: string;
79
+ view_repair_pending: string[];
80
+ isError?: boolean;
81
+ reason_code?: string;
82
+ message?: string;
83
+ options?: Array<{
84
+ tool: string;
85
+ args: Record<string, unknown>;
86
+ why: string;
87
+ }>;
88
+ waiting_on?: string[];
89
+ candidates?: unknown[];
90
+ next_ready?: string[];
91
+ goal_id?: string;
92
+ step_id?: string;
93
+ status?: string;
94
+ summary?: string;
95
+ goal_status?: GoalRecord["status"];
96
+ verification?: "pending";
97
+ skipped_follow_ups?: string[];
98
+ learning?: LearningAcceptance;
99
+ reopened?: string[];
100
+ requirement_updates_pending?: HostRequirementUpdate[];
101
+ }>;
102
+ export declare function beginStep(goalId: string, options: {
103
+ step_id?: string;
104
+ agent: string;
105
+ session_id?: string;
106
+ }, deps?: GoalEngineDeps): Promise<HostStepResult>;
107
+ export declare function applyHostResultToStep(step: TaskStep, outcome: StepOutcome): void;
108
+ /** The caller holds the existing per-goal coordinator; never changes receipts. */
109
+ export declare function recoverHostBudgetsLocked(goal: GoalRecord, plan: TaskPlan, options: ResumeGoalOptions, now: number): TaskPlan;
110
+ export declare function completeStep(goalId: string, stepId: string, outcome: StepOutcome, options: {
111
+ agent: string;
112
+ attempt: number;
113
+ continue?: boolean;
114
+ }, deps?: GoalEngineDeps): Promise<HostStepResult>;
115
+ export declare function verificationSignature(plan: TaskPlan): string;
116
+ export declare function finishHostVerification(goalId: string, snapshot: {
117
+ goal: GoalRecord;
118
+ planDigest: string;
119
+ }, deps: GoalEngineDeps): Promise<void>;
120
+ /** Explicit restart recovery reruns only the declared checks, never host work. */
121
+ export declare function retryHostVerification(goalId: string, deps?: GoalEngineDeps): Promise<{
122
+ goal: GoalRecord;
123
+ plan: TaskPlan | undefined;
124
+ }>;
125
+ /** Attach only ACE's own result to the exact attempt active when its tool ran. */
126
+ export declare function recordHostContact(binding?: HostWorkBinding): Promise<void>;
127
+ export declare function recordHostToolEvidence(tool: string, result: unknown, binding?: HostWorkBinding): Promise<void>;
128
+ /** Goal -> packed view is the sole lock order. No store callback acquires a goal lease. */
129
+ export declare function withLinkedHandoff<T>(ref: {
130
+ goal_id: string;
131
+ step_id: string;
132
+ }, input: {
133
+ id: string;
134
+ status: string;
135
+ actor: string;
136
+ to: string;
137
+ }, writeView: () => Promise<T>): Promise<T>;
138
+ //# sourceMappingURL=host-goal-engine.d.ts.map