@voybio/ace-swarm 0.2.5 → 2.4.1

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 (144) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/README.md +21 -13
  3. package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
  4. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  5. package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
  6. package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
  7. package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
  10. package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
  11. package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
  12. package/assets/agent-state/STATUS.md +2 -2
  13. package/assets/agent-state/runtime-tool-specs.json +70 -2
  14. package/assets/instructions/ACE_Coder.instructions.md +13 -0
  15. package/assets/instructions/ACE_UI.instructions.md +11 -0
  16. package/assets/scripts/ace-hook-dispatch.mjs +70 -6
  17. package/assets/scripts/render-mcp-configs.sh +19 -5
  18. package/dist/ace-context.js +91 -11
  19. package/dist/ace-internal-tools.d.ts +3 -1
  20. package/dist/ace-internal-tools.js +10 -2
  21. package/dist/ace-server-instructions.js +3 -3
  22. package/dist/ace-state-resolver.js +5 -3
  23. package/dist/agent-runtime/role-adapters.d.ts +18 -1
  24. package/dist/agent-runtime/role-adapters.js +49 -5
  25. package/dist/astgrep-index.d.ts +57 -1
  26. package/dist/astgrep-index.js +140 -4
  27. package/dist/cli.js +232 -35
  28. package/dist/discovery-runtime-wrappers.d.ts +108 -0
  29. package/dist/discovery-runtime-wrappers.js +615 -0
  30. package/dist/handoff-registry.js +5 -5
  31. package/dist/helpers/artifacts.d.ts +19 -0
  32. package/dist/helpers/artifacts.js +152 -0
  33. package/dist/helpers/bootstrap.d.ts +24 -0
  34. package/dist/helpers/bootstrap.js +894 -0
  35. package/dist/helpers/constants.d.ts +53 -0
  36. package/dist/helpers/constants.js +295 -0
  37. package/dist/helpers/drift.d.ts +13 -0
  38. package/dist/helpers/drift.js +45 -0
  39. package/dist/helpers/path-utils.d.ts +24 -0
  40. package/dist/helpers/path-utils.js +123 -0
  41. package/dist/helpers/store-resolution.d.ts +19 -0
  42. package/dist/helpers/store-resolution.js +305 -0
  43. package/dist/helpers/workspace-root.d.ts +3 -0
  44. package/dist/helpers/workspace-root.js +80 -0
  45. package/dist/helpers.d.ts +8 -125
  46. package/dist/helpers.js +8 -1768
  47. package/dist/job-scheduler.js +33 -7
  48. package/dist/json-sanitizer.d.ts +16 -0
  49. package/dist/json-sanitizer.js +26 -0
  50. package/dist/local-model-policy.d.ts +27 -0
  51. package/dist/local-model-policy.js +84 -0
  52. package/dist/local-model-runtime.d.ts +6 -0
  53. package/dist/local-model-runtime.js +33 -21
  54. package/dist/model-bridge.d.ts +13 -1
  55. package/dist/model-bridge.js +410 -23
  56. package/dist/orchestrator-supervisor.d.ts +56 -0
  57. package/dist/orchestrator-supervisor.js +179 -1
  58. package/dist/plan-proposal.d.ts +115 -0
  59. package/dist/plan-proposal.js +1073 -0
  60. package/dist/run-ledger.js +3 -3
  61. package/dist/runtime-command.d.ts +8 -0
  62. package/dist/runtime-command.js +38 -6
  63. package/dist/runtime-executor.d.ts +20 -1
  64. package/dist/runtime-executor.js +737 -172
  65. package/dist/runtime-profile.d.ts +32 -0
  66. package/dist/runtime-profile.js +89 -13
  67. package/dist/runtime-tool-specs.d.ts +39 -0
  68. package/dist/runtime-tool-specs.js +144 -28
  69. package/dist/safe-edit.d.ts +7 -0
  70. package/dist/safe-edit.js +163 -37
  71. package/dist/schemas.js +48 -1
  72. package/dist/server.js +51 -0
  73. package/dist/shared.d.ts +3 -2
  74. package/dist/shared.js +2 -0
  75. package/dist/status-events.js +9 -6
  76. package/dist/store/ace-packed-store.d.ts +3 -2
  77. package/dist/store/ace-packed-store.js +188 -110
  78. package/dist/store/bootstrap-store.d.ts +2 -1
  79. package/dist/store/bootstrap-store.js +102 -83
  80. package/dist/store/cache-workspace.js +11 -5
  81. package/dist/store/materializers/context-snapshot-materializer.js +6 -2
  82. package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
  83. package/dist/store/materializers/hook-context-materializer.js +11 -21
  84. package/dist/store/materializers/host-file-materializer.js +6 -0
  85. package/dist/store/materializers/projection-manager.d.ts +0 -1
  86. package/dist/store/materializers/projection-manager.js +5 -13
  87. package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
  88. package/dist/store/materializers/vericify-projector.d.ts +7 -7
  89. package/dist/store/materializers/vericify-projector.js +11 -11
  90. package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
  91. package/dist/store/repositories/local-model-runtime-repository.js +242 -6
  92. package/dist/store/repositories/vericify-repository.d.ts +1 -1
  93. package/dist/store/skills-install.d.ts +4 -0
  94. package/dist/store/skills-install.js +21 -12
  95. package/dist/store/state-reader.d.ts +2 -0
  96. package/dist/store/state-reader.js +20 -0
  97. package/dist/store/store-artifacts.d.ts +7 -0
  98. package/dist/store/store-artifacts.js +27 -1
  99. package/dist/store/store-authority-audit.d.ts +18 -1
  100. package/dist/store/store-authority-audit.js +115 -5
  101. package/dist/store/store-snapshot.d.ts +3 -0
  102. package/dist/store/store-snapshot.js +22 -2
  103. package/dist/store/workspace-store-paths.d.ts +39 -0
  104. package/dist/store/workspace-store-paths.js +94 -0
  105. package/dist/store/write-coordinator.d.ts +65 -0
  106. package/dist/store/write-coordinator.js +386 -0
  107. package/dist/todo-state.js +5 -5
  108. package/dist/tools-agent.d.ts +20 -0
  109. package/dist/tools-agent.js +789 -25
  110. package/dist/tools-discovery.js +136 -1
  111. package/dist/tools-files.d.ts +7 -0
  112. package/dist/tools-files.js +1002 -11
  113. package/dist/tools-framework.js +105 -66
  114. package/dist/tools-handoff.js +2 -2
  115. package/dist/tools-lifecycle.js +4 -4
  116. package/dist/tools-memory.js +6 -6
  117. package/dist/tools-todo.js +2 -2
  118. package/dist/tracker-adapters.d.ts +1 -1
  119. package/dist/tracker-adapters.js +13 -18
  120. package/dist/tracker-sync.js +5 -3
  121. package/dist/tui/agent-runner.js +3 -1
  122. package/dist/tui/chat.js +103 -7
  123. package/dist/tui/dashboard.d.ts +1 -0
  124. package/dist/tui/dashboard.js +43 -0
  125. package/dist/tui/index.js +10 -1
  126. package/dist/tui/layout.d.ts +20 -0
  127. package/dist/tui/layout.js +31 -1
  128. package/dist/tui/local-model-contract.d.ts +6 -2
  129. package/dist/tui/local-model-contract.js +16 -3
  130. package/dist/tui/ollama.d.ts +8 -1
  131. package/dist/tui/ollama.js +53 -12
  132. package/dist/tui/openai-compatible.d.ts +13 -0
  133. package/dist/tui/openai-compatible.js +305 -5
  134. package/dist/tui/provider-discovery.d.ts +1 -0
  135. package/dist/tui/provider-discovery.js +35 -11
  136. package/dist/vericify-bridge.d.ts +6 -1
  137. package/dist/vericify-bridge.js +27 -3
  138. package/dist/workspace-manager.d.ts +30 -3
  139. package/dist/workspace-manager.js +257 -27
  140. package/package.json +1 -2
  141. package/dist/internal-tool-runtime.d.ts +0 -21
  142. package/dist/internal-tool-runtime.js +0 -136
  143. package/dist/store/workspace-snapshot.d.ts +0 -26
  144. package/dist/store/workspace-snapshot.js +0 -107
@@ -3,19 +3,30 @@ import { randomUUID } from "node:crypto";
3
3
  import { existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { basename, dirname, join, relative, resolve } from "node:path";
6
- import { resolveWorkspaceArtifactPath as resolveWorkspaceArtifactPathHelper, safeRead, safeWrite, resolveWorkspaceRoot, } from "./helpers.js";
6
+ import { resolveWorkspaceArtifactPath as resolveWorkspaceArtifactPathHelper, safeRead, safeWrite, safeWriteAsync, resolveWorkspaceRoot, } from "./helpers.js";
7
7
  import { storeExistsSync } from "./store/store-snapshot.js";
8
- import { operationalArtifactVirtualPath, writeStoreBlobsSync, } from "./store/store-artifacts.js";
9
- import { getRuntimeProfilePath, readRuntimeProfile } from "./runtime-profile.js";
8
+ import { operationalArtifactVirtualPath, writeStoreBlobsQueued, } from "./store/store-artifacts.js";
9
+ import { getRuntimeProfilePath, loadRuntimeProfile } from "./runtime-profile.js";
10
10
  import { validateWorkspaceSessionRegistryPayload } from "./schemas.js";
11
11
  import { isInside, isReadError, slugify } from "./shared.js";
12
- import { appendStatusEvent } from "./status-events.js";
12
+ import { appendStatusEventSafe } from "./status-events.js";
13
13
  import { buildAutonomyHookEnv, normalizeAutonomyPolicy, normalizeContinuityPolicy, } from "./ace-autonomy.js";
14
14
  export const WORKSPACE_SESSION_REGISTRY_REL_PATH = "agent-state/runtime-workspaces.json";
15
15
  const DEFAULT_MANAGED_WORKSPACE_ROOT = ".agents/ACE/.ace/workspaces";
16
16
  const DEFAULT_HOOK_TIMEOUT_MS = 15_000;
17
17
  const MAX_HOOK_TIMEOUT_MS = 60_000;
18
18
  const MIN_HOOK_TIMEOUT_MS = 1_000;
19
+ export function requiresManagedWorkspace(ctx) {
20
+ if (ctx.surface_kind === "unattended_runtime")
21
+ return true;
22
+ if (ctx.surface_kind === "scheduled_job" && ctx.produces_artifacts_for_downstream)
23
+ return true;
24
+ if (ctx.surface_kind === "supervised_step" && ctx.performs_file_mutations)
25
+ return true;
26
+ if (ctx.multi_turn_bridge)
27
+ return true;
28
+ return false;
29
+ }
19
30
  function defaultHookState() {
20
31
  return { status: "not_run" };
21
32
  }
@@ -27,6 +38,41 @@ function defaultHookSummary() {
27
38
  before_remove: defaultHookState(),
28
39
  };
29
40
  }
41
+ function deriveManagedWorkspaceTrigger(ctx) {
42
+ if (!ctx)
43
+ return {};
44
+ const reasons = [];
45
+ if (ctx.surface_kind === "unattended_runtime") {
46
+ reasons.push({
47
+ trigger: "unattended_runtime",
48
+ summary: "required for unattended runtime execution",
49
+ });
50
+ }
51
+ if (ctx.surface_kind === "supervised_step" && ctx.performs_file_mutations) {
52
+ reasons.push({
53
+ trigger: "supervised_step_file_mutation",
54
+ summary: "required because the supervised step mutates files",
55
+ });
56
+ }
57
+ if (ctx.surface_kind === "scheduled_job" && ctx.produces_artifacts_for_downstream) {
58
+ reasons.push({
59
+ trigger: "scheduled_job_downstream_artifacts",
60
+ summary: "required because the scheduled job produces downstream artifacts",
61
+ });
62
+ }
63
+ if (ctx.multi_turn_bridge) {
64
+ reasons.push({
65
+ trigger: "multi_turn_bridge",
66
+ summary: "required because the bridge spans multiple turns and may write to disk",
67
+ });
68
+ }
69
+ return reasons.length > 0
70
+ ? {
71
+ trigger: reasons[0].trigger,
72
+ trigger_summary: reasons.map((entry) => entry.summary).join("; "),
73
+ }
74
+ : {};
75
+ }
30
76
  function defaultRegistry() {
31
77
  return {
32
78
  version: 1,
@@ -41,7 +87,26 @@ function clampHookTimeout(input) {
41
87
  }
42
88
  function emitWorkspaceEvent(eventType, summary, payload) {
43
89
  try {
44
- appendStatusEvent({
90
+ void appendStatusEventSafe({
91
+ source_module: "capability-framework",
92
+ event_type: eventType,
93
+ status: eventType === "WORKSPACE_SESSION_CREATED" ||
94
+ eventType === "WORKSPACE_SESSION_REMOVED"
95
+ ? "done"
96
+ : "fail",
97
+ summary,
98
+ payload,
99
+ }).catch(() => {
100
+ // Workspace lifecycle must not fail because status logging failed.
101
+ });
102
+ }
103
+ catch {
104
+ // Workspace lifecycle must not fail because status logging failed.
105
+ }
106
+ }
107
+ async function emitWorkspaceEventAsync(eventType, summary, payload) {
108
+ try {
109
+ await appendStatusEventSafe({
45
110
  source_module: "capability-framework",
46
111
  event_type: eventType,
47
112
  status: eventType === "WORKSPACE_SESSION_CREATED" ||
@@ -57,11 +122,8 @@ function emitWorkspaceEvent(eventType, summary, payload) {
57
122
  }
58
123
  }
59
124
  export function resolveRuntimeWorkspaceRoot(rootOverride) {
60
- const workspaceRoot = resolveWorkspaceRoot();
61
- const configured = rootOverride?.trim() ||
62
- readRuntimeProfile().workspace.root ||
63
- DEFAULT_MANAGED_WORKSPACE_ROOT;
64
- return resolve(configured.startsWith("/") ? configured : resolve(workspaceRoot, configured));
125
+ const configured = rootOverride?.trim() || DEFAULT_MANAGED_WORKSPACE_ROOT;
126
+ return resolve(configured.startsWith("/") ? configured : resolve(resolveWorkspaceRoot(), configured));
65
127
  }
66
128
  function resolveTargetPath(rootPath, target) {
67
129
  return resolve(target.startsWith("/") ? target : resolve(rootPath, target));
@@ -80,7 +142,7 @@ function canonicalizeCandidatePath(targetPath) {
80
142
  return resolve(existing, ...remainder);
81
143
  }
82
144
  function parseWorkspaceRegistry(raw) {
83
- if (isReadError(raw))
145
+ if (isReadError(raw) || raw.trim() === "" || raw.includes("\x00"))
84
146
  return defaultRegistry();
85
147
  let parsed;
86
148
  try {
@@ -111,6 +173,13 @@ function writeWorkspaceRegistry(registry) {
111
173
  }
112
174
  return safeWriteWorkspaceFile(WORKSPACE_SESSION_REGISTRY_REL_PATH, JSON.stringify(registry, null, 2));
113
175
  }
176
+ async function writeWorkspaceRegistryAsync(registry) {
177
+ const validation = validateWorkspaceSessionRegistryPayload(registry);
178
+ if (!validation.ok) {
179
+ throw new Error(`Workspace session registry failed validation (${validation.schema}): ${validation.errors.join("; ")}`);
180
+ }
181
+ return safeWriteAsync(WORKSPACE_SESSION_REGISTRY_REL_PATH, JSON.stringify(registry, null, 2));
182
+ }
114
183
  function findSessionIndex(registry, input) {
115
184
  if (input.session_id) {
116
185
  return registry.sessions.findIndex((session) => session.session_id === input.session_id);
@@ -132,6 +201,34 @@ function deriveWorkspacePath(rootPath, input, sessionId) {
132
201
  function workspaceHookStateFromResult(result) {
133
202
  return result.detail ? { status: result.status, detail: result.detail } : { status: result.status };
134
203
  }
204
+ function refreshWorkspaceSessionHookSummary(session) {
205
+ const orderedKinds = [
206
+ "before_remove",
207
+ "after_run",
208
+ "before_run",
209
+ "after_create",
210
+ ];
211
+ const firstFailure = orderedKinds.find((kind) => session.hooks[kind].status === "failed");
212
+ const latestExecuted = orderedKinds.find((kind) => session.hooks[kind].status !== "not_run");
213
+ const summaryKind = firstFailure ?? latestExecuted;
214
+ session.hook_health = orderedKinds.some((kind) => session.hooks[kind].status === "failed")
215
+ ? "failed"
216
+ : "ok";
217
+ session.hook_summary = summaryKind
218
+ ? `${summaryKind}: ${session.hooks[summaryKind].detail ?? session.hooks[summaryKind].status}`
219
+ : undefined;
220
+ }
221
+ export function projectWorkspaceSessionRuntimeSummary(session) {
222
+ return {
223
+ active_workspace_path: session.workspace_path,
224
+ active_workspace_session_id: session.session_id,
225
+ workspace_session_status: session.status,
226
+ workspace_hook_health: session.hook_health,
227
+ workspace_hook_summary: session.hook_summary,
228
+ managed_workspace_trigger: session.trigger,
229
+ managed_workspace_trigger_summary: session.trigger_summary,
230
+ };
231
+ }
135
232
  function readWorkspaceRegistry() {
136
233
  return parseWorkspaceRegistry(safeReadWorkspaceFile(WORKSPACE_SESSION_REGISTRY_REL_PATH));
137
234
  }
@@ -253,7 +350,7 @@ function listWorkspaceFiles(rootPath) {
253
350
  walk(rootPath);
254
351
  return entries.sort((a, b) => a.localeCompare(b));
255
352
  }
256
- function captureWorkspaceToStore(session) {
353
+ async function captureWorkspaceToStore(session) {
257
354
  const workspaceRoot = resolveWorkspaceRoot();
258
355
  if (!storeExistsSync(workspaceRoot) || !existsSync(session.workspace_path)) {
259
356
  return undefined;
@@ -287,7 +384,9 @@ function captureWorkspaceToStore(session) {
287
384
  files: manifestFiles,
288
385
  };
289
386
  blobs.push({ key: manifestKey, content: JSON.stringify(manifest, null, 2) });
290
- const written = writeStoreBlobsSync(workspaceRoot, blobs);
387
+ const written = await writeStoreBlobsQueued(workspaceRoot, blobs, {
388
+ operation_label: "captureWorkspaceToStore",
389
+ });
291
390
  const storePath = (written[0] ?? "").split("#")[0];
292
391
  return {
293
392
  workspace_path: `${storePath}#${baseKey}`,
@@ -297,8 +396,21 @@ function captureWorkspaceToStore(session) {
297
396
  file_count: manifestFiles.length,
298
397
  };
299
398
  }
300
- export function runWorkspaceHook(kind, session, timeoutMs) {
301
- const runtimeProfile = readRuntimeProfile();
399
+ export function runWorkspaceHook(kind, session, timeoutMs, runtimeProfilePath) {
400
+ const runtimeProfileResult = runtimeProfilePath
401
+ ? loadRuntimeProfile(runtimeProfilePath)
402
+ : loadRuntimeProfile();
403
+ if (!runtimeProfileResult.ok) {
404
+ const message = runtimeProfileResult.errors.join("; ");
405
+ return {
406
+ ok: false,
407
+ kind,
408
+ status: "failed",
409
+ command: null,
410
+ detail: message,
411
+ };
412
+ }
413
+ const runtimeProfile = runtimeProfileResult.profile;
302
414
  const command = runtimeProfile.workspace.hooks[kind];
303
415
  if (!command) {
304
416
  return {
@@ -366,9 +478,15 @@ export function createWorkspaceSession(input) {
366
478
  return { ok: false, registry_path: registryPath, error: message };
367
479
  }
368
480
  const useEphemeralRoot = shouldUseEphemeralWorkspaceRoot(input);
481
+ const runtimeProfileResult = input.runtime_profile_path
482
+ ? loadRuntimeProfile(input.runtime_profile_path)
483
+ : undefined;
484
+ const runtimeWorkspaceRoot = runtimeProfileResult?.ok
485
+ ? runtimeProfileResult.profile.workspace.root
486
+ : DEFAULT_MANAGED_WORKSPACE_ROOT;
369
487
  const rootPath = useEphemeralRoot
370
488
  ? mkdtempSync(join(tmpdir(), "ace-runtime-workspaces-"))
371
- : resolveRuntimeWorkspaceRoot(input.root);
489
+ : resolveRuntimeWorkspaceRoot(input.root ?? runtimeWorkspaceRoot);
372
490
  const workspacePath = deriveWorkspacePath(rootPath, input, sessionId);
373
491
  const validation = validateManagedWorkspacePath(rootPath, workspacePath, {
374
492
  allowExternalRoot: useEphemeralRoot,
@@ -405,14 +523,16 @@ export function createWorkspaceSession(input) {
405
523
  root_path: validation.root_path,
406
524
  status: "active",
407
525
  source: input.source,
526
+ ...deriveManagedWorkspaceTrigger(input.trigger_context),
408
527
  objective_id: input.objective_id,
409
528
  tracker_item_id: input.tracker_item_id,
410
529
  created_at: now,
411
530
  updated_at: now,
412
531
  hooks: defaultHookSummary(),
413
532
  };
414
- const hookResult = runWorkspaceHook("after_create", session, input.hooks_timeout_ms);
533
+ const hookResult = runWorkspaceHook("after_create", session, input.hooks_timeout_ms, input.runtime_profile_path);
415
534
  session.hooks.after_create = workspaceHookStateFromResult(hookResult);
535
+ refreshWorkspaceSessionHookSummary(session);
416
536
  if (!hookResult.ok) {
417
537
  session.status = "failed";
418
538
  session.last_error = hookResult.detail;
@@ -447,7 +567,110 @@ export function createWorkspaceSession(input) {
447
567
  });
448
568
  return { ok: true, registry_path: registryPath, session, validation };
449
569
  }
450
- export function removeWorkspaceSession(input) {
570
+ export async function createWorkspaceSessionAsync(input) {
571
+ const registryPath = getWorkspaceSessionRegistryPath();
572
+ const sessionId = input.session_id?.trim() || randomUUID();
573
+ const now = new Date().toISOString();
574
+ let registry;
575
+ try {
576
+ registry = readWorkspaceRegistry();
577
+ }
578
+ catch (error) {
579
+ const message = error instanceof Error ? error.message : String(error);
580
+ await emitWorkspaceEventAsync("WORKSPACE_SESSION_FAILED", "Workspace session create failed because the registry could not be read.", { error: message });
581
+ return { ok: false, registry_path: registryPath, error: message };
582
+ }
583
+ const useEphemeralRoot = shouldUseEphemeralWorkspaceRoot(input);
584
+ const runtimeProfileResult = input.runtime_profile_path
585
+ ? loadRuntimeProfile(input.runtime_profile_path)
586
+ : undefined;
587
+ const runtimeWorkspaceRoot = runtimeProfileResult?.ok
588
+ ? runtimeProfileResult.profile.workspace.root
589
+ : DEFAULT_MANAGED_WORKSPACE_ROOT;
590
+ const rootPath = useEphemeralRoot
591
+ ? mkdtempSync(join(tmpdir(), "ace-runtime-workspaces-"))
592
+ : resolveRuntimeWorkspaceRoot(input.root ?? runtimeWorkspaceRoot);
593
+ const workspacePath = deriveWorkspacePath(rootPath, input, sessionId);
594
+ const validation = validateManagedWorkspacePath(rootPath, workspacePath, {
595
+ allowExternalRoot: useEphemeralRoot,
596
+ });
597
+ if (!validation.ok) {
598
+ await emitWorkspaceEventAsync("WORKSPACE_SESSION_FAILED", `Workspace session create rejected for ${workspacePath}`, { validation });
599
+ return {
600
+ ok: false,
601
+ registry_path: registryPath,
602
+ error: validation.reason,
603
+ validation,
604
+ };
605
+ }
606
+ const duplicate = registry.sessions.find((session) => resolve(session.workspace_path) === validation.target_path &&
607
+ session.status !== "archived" &&
608
+ session.status !== "removed");
609
+ if (duplicate) {
610
+ const error = `Workspace path is already managed by session ${duplicate.session_id}.`;
611
+ await emitWorkspaceEventAsync("WORKSPACE_SESSION_FAILED", error, { session_id: duplicate.session_id, workspace_path: duplicate.workspace_path });
612
+ return { ok: false, registry_path: registryPath, error, validation };
613
+ }
614
+ if (existsSync(validation.target_path)) {
615
+ const error = `Workspace path already exists and cannot be claimed: ${validation.target_path}`;
616
+ await emitWorkspaceEventAsync("WORKSPACE_SESSION_FAILED", error, {
617
+ workspace_path: validation.target_path,
618
+ });
619
+ return { ok: false, registry_path: registryPath, error, validation };
620
+ }
621
+ mkdirSync(validation.root_path, { recursive: true });
622
+ mkdirSync(validation.target_path, { recursive: true });
623
+ const session = {
624
+ session_id: sessionId,
625
+ workspace_path: validation.target_path,
626
+ root_path: validation.root_path,
627
+ status: "active",
628
+ source: input.source,
629
+ ...deriveManagedWorkspaceTrigger(input.trigger_context),
630
+ objective_id: input.objective_id,
631
+ tracker_item_id: input.tracker_item_id,
632
+ created_at: now,
633
+ updated_at: now,
634
+ hooks: defaultHookSummary(),
635
+ };
636
+ const hookResult = runWorkspaceHook("after_create", session, input.hooks_timeout_ms, input.runtime_profile_path);
637
+ session.hooks.after_create = workspaceHookStateFromResult(hookResult);
638
+ refreshWorkspaceSessionHookSummary(session);
639
+ if (!hookResult.ok) {
640
+ session.status = "failed";
641
+ session.last_error = hookResult.detail;
642
+ }
643
+ registry.sessions.push(session);
644
+ registry.updated_at = now;
645
+ await writeWorkspaceRegistryAsync(registry);
646
+ if (!hookResult.ok) {
647
+ await emitWorkspaceEventAsync("WORKSPACE_HOOK_FAILED", `Workspace hook after_create failed for ${session.session_id}`, {
648
+ session_id: session.session_id,
649
+ workspace_path: session.workspace_path,
650
+ detail: hookResult.detail,
651
+ });
652
+ await emitWorkspaceEventAsync("WORKSPACE_SESSION_FAILED", `Workspace session ${session.session_id} created with failed after_create hook.`, {
653
+ session_id: session.session_id,
654
+ workspace_path: session.workspace_path,
655
+ detail: hookResult.detail,
656
+ });
657
+ return {
658
+ ok: false,
659
+ registry_path: registryPath,
660
+ session,
661
+ error: hookResult.detail ?? "after_create hook failed",
662
+ validation,
663
+ };
664
+ }
665
+ await emitWorkspaceEventAsync("WORKSPACE_SESSION_CREATED", `Workspace session ${session.session_id} created.`, {
666
+ session_id: session.session_id,
667
+ workspace_path: session.workspace_path,
668
+ root_path: session.root_path,
669
+ source: session.source,
670
+ });
671
+ return { ok: true, registry_path: registryPath, session, validation };
672
+ }
673
+ export async function removeWorkspaceSession(input) {
451
674
  const registryPath = getWorkspaceSessionRegistryPath();
452
675
  const now = new Date().toISOString();
453
676
  let registry;
@@ -483,14 +706,15 @@ export function removeWorkspaceSession(input) {
483
706
  validation,
484
707
  };
485
708
  }
486
- const hookResult = runWorkspaceHook("before_remove", session, input.hooks_timeout_ms);
709
+ const hookResult = runWorkspaceHook("before_remove", session, input.hooks_timeout_ms, input.runtime_profile_path);
487
710
  session.hooks.before_remove = workspaceHookStateFromResult(hookResult);
711
+ refreshWorkspaceSessionHookSummary(session);
488
712
  session.updated_at = now;
489
713
  if (!hookResult.ok) {
490
714
  session.status = "failed";
491
715
  session.last_error = hookResult.detail;
492
716
  registry.updated_at = now;
493
- writeWorkspaceRegistry(registry);
717
+ await writeWorkspaceRegistryAsync(registry);
494
718
  emitWorkspaceEvent("WORKSPACE_HOOK_FAILED", `Workspace hook before_remove failed for ${session.session_id}`, {
495
719
  session_id: session.session_id,
496
720
  workspace_path: session.workspace_path,
@@ -509,8 +733,13 @@ export function removeWorkspaceSession(input) {
509
733
  validation,
510
734
  };
511
735
  }
512
- const retention = readRuntimeProfile().workspace.retention;
513
- const captured = captureWorkspaceToStore(session);
736
+ const runtimeProfileResult = input.runtime_profile_path
737
+ ? loadRuntimeProfile(input.runtime_profile_path)
738
+ : undefined;
739
+ const retention = runtimeProfileResult?.ok
740
+ ? runtimeProfileResult.profile.workspace.retention
741
+ : "delete";
742
+ const captured = await captureWorkspaceToStore(session);
514
743
  if (retention === "archive" && !captured) {
515
744
  const archivePath = resolve(validation.root_path, ".archive", session.session_id);
516
745
  const archiveValidation = validateManagedWorkspacePath(validation.root_path, archivePath, { allowExternalRoot });
@@ -518,7 +747,7 @@ export function removeWorkspaceSession(input) {
518
747
  session.status = "failed";
519
748
  session.last_error = archiveValidation.reason;
520
749
  registry.updated_at = now;
521
- writeWorkspaceRegistry(registry);
750
+ await writeWorkspaceRegistryAsync(registry);
522
751
  emitWorkspaceEvent("WORKSPACE_SESSION_FAILED", `Archive target rejected for workspace session ${session.session_id}`, { session_id: session.session_id, validation: archiveValidation });
523
752
  return {
524
753
  ok: false,
@@ -556,7 +785,7 @@ export function removeWorkspaceSession(input) {
556
785
  session.updated_at = now;
557
786
  delete session.last_error;
558
787
  registry.updated_at = now;
559
- writeWorkspaceRegistry(registry);
788
+ await writeWorkspaceRegistryAsync(registry);
560
789
  emitWorkspaceEvent("WORKSPACE_SESSION_REMOVED", `Workspace session ${session.session_id} ${session.status}.`, {
561
790
  session_id: session.session_id,
562
791
  workspace_path: session.workspace_path,
@@ -564,7 +793,7 @@ export function removeWorkspaceSession(input) {
564
793
  });
565
794
  return { ok: true, registry_path: registryPath, session, validation };
566
795
  }
567
- export function runWorkspaceSessionHook(input) {
796
+ export async function runWorkspaceSessionHook(input) {
568
797
  const registryPath = getWorkspaceSessionRegistryPath();
569
798
  const now = new Date().toISOString();
570
799
  let registry;
@@ -601,8 +830,9 @@ export function runWorkspaceSessionHook(input) {
601
830
  };
602
831
  }
603
832
  const session = registry.sessions[sessionIndex];
604
- const hookResult = runWorkspaceHook(input.kind, session, input.hooks_timeout_ms);
833
+ const hookResult = runWorkspaceHook(input.kind, session, input.hooks_timeout_ms, input.runtime_profile_path);
605
834
  session.hooks[input.kind] = workspaceHookStateFromResult(hookResult);
835
+ refreshWorkspaceSessionHookSummary(session);
606
836
  session.updated_at = now;
607
837
  if (!hookResult.ok) {
608
838
  session.status = "failed";
@@ -612,7 +842,7 @@ export function runWorkspaceSessionHook(input) {
612
842
  delete session.last_error;
613
843
  }
614
844
  registry.updated_at = now;
615
- writeWorkspaceRegistry(registry);
845
+ await writeWorkspaceRegistryAsync(registry);
616
846
  if (!hookResult.ok) {
617
847
  emitWorkspaceEvent("WORKSPACE_HOOK_FAILED", `Workspace hook ${input.kind} failed for ${session.session_id}`, {
618
848
  session_id: session.session_id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voybio/ace-swarm",
3
- "version": "0.2.5",
3
+ "version": "2.4.1",
4
4
  "description": "ACE Framework MCP server and CLI — single-file ACEPACK state, local-model serving, agent orchestration, and host compliance enforcement.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -69,7 +69,6 @@
69
69
  "dependencies": {
70
70
  "@modelcontextprotocol/sdk": "^1.26.0",
71
71
  "typescript": "^5.9.3",
72
- "vericify": "*",
73
72
  "zod": "^4.3.6"
74
73
  },
75
74
  "devDependencies": {
@@ -1,21 +0,0 @@
1
- import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
- export interface InternalToolDescriptor {
3
- name: string;
4
- description: string;
5
- inputSchema?: unknown;
6
- outputSchema?: unknown;
7
- }
8
- export interface InternalToolExecutionContext {
9
- sessionId?: string;
10
- }
11
- export interface InternalToolExecutionResult {
12
- ok: boolean;
13
- name: string;
14
- summary: string;
15
- result?: CallToolResult;
16
- error?: string;
17
- }
18
- export declare function listInternalTools(toolScope?: string[]): InternalToolDescriptor[];
19
- export declare function executeInternalTool(name: string, args?: Record<string, unknown>, context?: InternalToolExecutionContext): Promise<InternalToolExecutionResult>;
20
- export declare function resetInternalToolRuntime(): void;
21
- //# sourceMappingURL=internal-tool-runtime.d.ts.map
@@ -1,136 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { z } from "zod";
3
- import { createAceServer } from "./server.js";
4
- function isPlainObject(value) {
5
- return !!value && typeof value === "object" && !Array.isArray(value);
6
- }
7
- function isZodSchema(value) {
8
- return Boolean(value) && typeof value === "object" && "safeParseAsync" in value;
9
- }
10
- function buildZodObject(shape) {
11
- if (!isPlainObject(shape))
12
- return undefined;
13
- const values = Object.values(shape);
14
- if (!values.every((value) => isZodSchema(value)))
15
- return undefined;
16
- return z.object(shape);
17
- }
18
- function describeSchema(schema) {
19
- if (isPlainObject(schema)) {
20
- const properties = Object.fromEntries(Object.entries(schema).map(([key, value]) => [
21
- key,
22
- {
23
- description: isZodSchema(value)
24
- ? value.description ?? value._def?.typeName ?? "input"
25
- : "input",
26
- },
27
- ]));
28
- return { type: "object", properties };
29
- }
30
- if (isZodSchema(schema)) {
31
- return {
32
- type: schema._def?.typeName ?? "schema",
33
- description: schema.description ?? "tool input",
34
- };
35
- }
36
- return undefined;
37
- }
38
- function summarizeResult(name, result) {
39
- const textBlocks = Array.isArray(result.content)
40
- ? result.content
41
- .filter((entry) => {
42
- return isPlainObject(entry) && entry.type === "text" && typeof entry.text === "string";
43
- })
44
- .map((entry) => entry.text.trim())
45
- .filter(Boolean)
46
- : [];
47
- const summary = textBlocks[0] ?? `${name} completed`;
48
- return summary.length <= 240 ? summary : `${summary.slice(0, 239).trimEnd()}…`;
49
- }
50
- let cachedTools;
51
- function getRegisteredTools() {
52
- if (cachedTools)
53
- return cachedTools;
54
- const server = createAceServer({ toolGovernance: false });
55
- const registry = server
56
- ._registeredTools;
57
- cachedTools = new Map(Object.entries(registry ?? {}).filter(([, tool]) => tool.enabled !== false));
58
- return cachedTools;
59
- }
60
- export function listInternalTools(toolScope) {
61
- const allowed = toolScope ? new Set(toolScope) : undefined;
62
- return [...getRegisteredTools().entries()]
63
- .filter(([name]) => !allowed || allowed.has(name))
64
- .map(([name, tool]) => ({
65
- name,
66
- description: tool.description ?? tool.title ?? "ACE MCP tool",
67
- inputSchema: describeSchema(tool.inputSchema),
68
- outputSchema: describeSchema(tool.outputSchema),
69
- }))
70
- .sort((left, right) => left.name.localeCompare(right.name));
71
- }
72
- export async function executeInternalTool(name, args = {}, context = {}) {
73
- const tool = getRegisteredTools().get(name);
74
- if (!tool) {
75
- return {
76
- ok: false,
77
- name,
78
- summary: `Unknown tool: ${name}`,
79
- error: `Unknown tool: ${name}`,
80
- };
81
- }
82
- let validatedArgs = args;
83
- try {
84
- if (tool.inputSchema) {
85
- const schema = isZodSchema(tool.inputSchema) ? tool.inputSchema : buildZodObject(tool.inputSchema);
86
- if (schema) {
87
- const parsed = await schema.safeParseAsync(args);
88
- if (!parsed.success) {
89
- const message = parsed.error.issues.map((issue) => issue.message).join("; ");
90
- return {
91
- ok: false,
92
- name,
93
- summary: `Input validation failed for ${name}`,
94
- error: message,
95
- };
96
- }
97
- if (isPlainObject(parsed.data)) {
98
- validatedArgs = parsed.data;
99
- }
100
- }
101
- }
102
- const extra = {
103
- signal: new AbortController().signal,
104
- sessionId: context.sessionId ?? `internal-${randomUUID()}`,
105
- requestId: `internal-${randomUUID()}`,
106
- sendNotification: async () => undefined,
107
- sendRequest: async () => {
108
- throw new Error("Internal tool runtime does not support nested MCP requests.");
109
- },
110
- };
111
- const raw = tool.inputSchema
112
- ? await tool.handler(validatedArgs, extra)
113
- : await tool.handler(extra);
114
- const result = raw;
115
- return {
116
- ok: !Boolean(result?.isError),
117
- name,
118
- summary: summarizeResult(name, result),
119
- result,
120
- error: result.isError ? summarizeResult(name, result) : undefined,
121
- };
122
- }
123
- catch (error) {
124
- const message = error instanceof Error ? error.message : String(error);
125
- return {
126
- ok: false,
127
- name,
128
- summary: `${name} failed`,
129
- error: message,
130
- };
131
- }
132
- }
133
- export function resetInternalToolRuntime() {
134
- cachedTools = undefined;
135
- }
136
- //# sourceMappingURL=internal-tool-runtime.js.map
@@ -1,26 +0,0 @@
1
- export interface WorkspaceSnapshotFileRecord {
2
- path: string;
3
- size: number;
4
- mtime_ms: number;
5
- encoding: "utf8" | "base64";
6
- key: string;
7
- virtual_path: string;
8
- }
9
- export interface WorkspaceSnapshotResult {
10
- version: 1;
11
- captured_at: string;
12
- session_id: string;
13
- source_workspace_path: string;
14
- virtual_root: string;
15
- manifest_key: string;
16
- manifest_path: string;
17
- file_count: number;
18
- files: WorkspaceSnapshotFileRecord[];
19
- skipped_paths: Array<{
20
- path: string;
21
- reason: string;
22
- }>;
23
- }
24
- export declare function workspaceSnapshotVirtualRoot(workspaceRoot: string, sessionId: string): string;
25
- export declare function persistWorkspaceSnapshot(workspaceRoot: string, sessionId: string, sourceWorkspacePath: string): Promise<WorkspaceSnapshotResult>;
26
- //# sourceMappingURL=workspace-snapshot.d.ts.map