@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.
- package/CHANGELOG.md +19 -1
- package/README.md +21 -13
- package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
- package/assets/agent-state/EVIDENCE_LOG.md +1 -1
- package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
- package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
- package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
- package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
- package/assets/agent-state/STATUS.md +2 -2
- package/assets/agent-state/runtime-tool-specs.json +70 -2
- package/assets/instructions/ACE_Coder.instructions.md +13 -0
- package/assets/instructions/ACE_UI.instructions.md +11 -0
- package/assets/scripts/ace-hook-dispatch.mjs +70 -6
- package/assets/scripts/render-mcp-configs.sh +19 -5
- package/dist/ace-context.js +91 -11
- package/dist/ace-internal-tools.d.ts +3 -1
- package/dist/ace-internal-tools.js +10 -2
- package/dist/ace-server-instructions.js +3 -3
- package/dist/ace-state-resolver.js +5 -3
- package/dist/agent-runtime/role-adapters.d.ts +18 -1
- package/dist/agent-runtime/role-adapters.js +49 -5
- package/dist/astgrep-index.d.ts +57 -1
- package/dist/astgrep-index.js +140 -4
- package/dist/cli.js +232 -35
- package/dist/discovery-runtime-wrappers.d.ts +108 -0
- package/dist/discovery-runtime-wrappers.js +615 -0
- package/dist/handoff-registry.js +5 -5
- package/dist/helpers/artifacts.d.ts +19 -0
- package/dist/helpers/artifacts.js +152 -0
- package/dist/helpers/bootstrap.d.ts +24 -0
- package/dist/helpers/bootstrap.js +894 -0
- package/dist/helpers/constants.d.ts +53 -0
- package/dist/helpers/constants.js +295 -0
- package/dist/helpers/drift.d.ts +13 -0
- package/dist/helpers/drift.js +45 -0
- package/dist/helpers/path-utils.d.ts +24 -0
- package/dist/helpers/path-utils.js +123 -0
- package/dist/helpers/store-resolution.d.ts +19 -0
- package/dist/helpers/store-resolution.js +305 -0
- package/dist/helpers/workspace-root.d.ts +3 -0
- package/dist/helpers/workspace-root.js +80 -0
- package/dist/helpers.d.ts +8 -125
- package/dist/helpers.js +8 -1768
- package/dist/job-scheduler.js +33 -7
- package/dist/json-sanitizer.d.ts +16 -0
- package/dist/json-sanitizer.js +26 -0
- package/dist/local-model-policy.d.ts +27 -0
- package/dist/local-model-policy.js +84 -0
- package/dist/local-model-runtime.d.ts +6 -0
- package/dist/local-model-runtime.js +33 -21
- package/dist/model-bridge.d.ts +13 -1
- package/dist/model-bridge.js +410 -23
- package/dist/orchestrator-supervisor.d.ts +56 -0
- package/dist/orchestrator-supervisor.js +179 -1
- package/dist/plan-proposal.d.ts +115 -0
- package/dist/plan-proposal.js +1073 -0
- package/dist/run-ledger.js +3 -3
- package/dist/runtime-command.d.ts +8 -0
- package/dist/runtime-command.js +38 -6
- package/dist/runtime-executor.d.ts +20 -1
- package/dist/runtime-executor.js +737 -172
- package/dist/runtime-profile.d.ts +32 -0
- package/dist/runtime-profile.js +89 -13
- package/dist/runtime-tool-specs.d.ts +39 -0
- package/dist/runtime-tool-specs.js +144 -28
- package/dist/safe-edit.d.ts +7 -0
- package/dist/safe-edit.js +163 -37
- package/dist/schemas.js +48 -1
- package/dist/server.js +51 -0
- package/dist/shared.d.ts +3 -2
- package/dist/shared.js +2 -0
- package/dist/status-events.js +9 -6
- package/dist/store/ace-packed-store.d.ts +3 -2
- package/dist/store/ace-packed-store.js +188 -110
- package/dist/store/bootstrap-store.d.ts +2 -1
- package/dist/store/bootstrap-store.js +102 -83
- package/dist/store/cache-workspace.js +11 -5
- package/dist/store/materializers/context-snapshot-materializer.js +6 -2
- package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
- package/dist/store/materializers/hook-context-materializer.js +11 -21
- package/dist/store/materializers/host-file-materializer.js +6 -0
- package/dist/store/materializers/projection-manager.d.ts +0 -1
- package/dist/store/materializers/projection-manager.js +5 -13
- package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
- package/dist/store/materializers/vericify-projector.d.ts +7 -7
- package/dist/store/materializers/vericify-projector.js +11 -11
- package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
- package/dist/store/repositories/local-model-runtime-repository.js +242 -6
- package/dist/store/repositories/vericify-repository.d.ts +1 -1
- package/dist/store/skills-install.d.ts +4 -0
- package/dist/store/skills-install.js +21 -12
- package/dist/store/state-reader.d.ts +2 -0
- package/dist/store/state-reader.js +20 -0
- package/dist/store/store-artifacts.d.ts +7 -0
- package/dist/store/store-artifacts.js +27 -1
- package/dist/store/store-authority-audit.d.ts +18 -1
- package/dist/store/store-authority-audit.js +115 -5
- package/dist/store/store-snapshot.d.ts +3 -0
- package/dist/store/store-snapshot.js +22 -2
- package/dist/store/workspace-store-paths.d.ts +39 -0
- package/dist/store/workspace-store-paths.js +94 -0
- package/dist/store/write-coordinator.d.ts +65 -0
- package/dist/store/write-coordinator.js +386 -0
- package/dist/todo-state.js +5 -5
- package/dist/tools-agent.d.ts +20 -0
- package/dist/tools-agent.js +789 -25
- package/dist/tools-discovery.js +136 -1
- package/dist/tools-files.d.ts +7 -0
- package/dist/tools-files.js +1002 -11
- package/dist/tools-framework.js +105 -66
- package/dist/tools-handoff.js +2 -2
- package/dist/tools-lifecycle.js +4 -4
- package/dist/tools-memory.js +6 -6
- package/dist/tools-todo.js +2 -2
- package/dist/tracker-adapters.d.ts +1 -1
- package/dist/tracker-adapters.js +13 -18
- package/dist/tracker-sync.js +5 -3
- package/dist/tui/agent-runner.js +3 -1
- package/dist/tui/chat.js +103 -7
- package/dist/tui/dashboard.d.ts +1 -0
- package/dist/tui/dashboard.js +43 -0
- package/dist/tui/index.js +10 -1
- package/dist/tui/layout.d.ts +20 -0
- package/dist/tui/layout.js +31 -1
- package/dist/tui/local-model-contract.d.ts +6 -2
- package/dist/tui/local-model-contract.js +16 -3
- package/dist/tui/ollama.d.ts +8 -1
- package/dist/tui/ollama.js +53 -12
- package/dist/tui/openai-compatible.d.ts +13 -0
- package/dist/tui/openai-compatible.js +305 -5
- package/dist/tui/provider-discovery.d.ts +1 -0
- package/dist/tui/provider-discovery.js +35 -11
- package/dist/vericify-bridge.d.ts +6 -1
- package/dist/vericify-bridge.js +27 -3
- package/dist/workspace-manager.d.ts +30 -3
- package/dist/workspace-manager.js +257 -27
- package/package.json +1 -2
- package/dist/internal-tool-runtime.d.ts +0 -21
- package/dist/internal-tool-runtime.js +0 -136
- package/dist/store/workspace-snapshot.d.ts +0 -26
- 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,
|
|
9
|
-
import { getRuntimeProfilePath,
|
|
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 {
|
|
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
|
-
|
|
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
|
|
61
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
513
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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
|