@voybio/ace-swarm 0.2.3 → 0.2.5
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/README.md +21 -11
- package/assets/.agents/ACE/ACE-Init/AGENTS.md +7 -1
- package/assets/.agents/ACE/orchestrator/AGENTS.md +4 -1
- package/assets/.agents/skills/eval-harness/SKILL.md +14 -0
- package/assets/.agents/skills/handoff-lint/SKILL.md +14 -0
- package/assets/.agents/skills/incident-commander/SKILL.md +14 -0
- package/assets/.agents/skills/memory-curator/SKILL.md +14 -0
- package/assets/.agents/skills/release-sentry/SKILL.md +14 -0
- package/assets/.agents/skills/risk-quant/SKILL.md +14 -0
- package/assets/.agents/skills/schema-forge/SKILL.md +14 -0
- package/assets/.agents/skills/state-auditor/SKILL.md +14 -0
- package/assets/agent-state/MODULES/gates/gate-correctness.json +1 -1
- package/dist/cli.js +128 -66
- package/dist/helpers.d.ts +3 -1
- package/dist/helpers.js +49 -31
- package/dist/local-model-runtime.js +6 -51
- package/dist/store/bootstrap-store.d.ts +1 -0
- package/dist/store/bootstrap-store.js +14 -27
- package/dist/store/cache-workspace.d.ts +22 -0
- package/dist/store/cache-workspace.js +143 -0
- package/dist/store/materializers/context-snapshot-materializer.js +1 -6
- package/dist/tools-agent.js +69 -58
- package/dist/tools-framework.js +121 -47
- package/dist/tui/index.js +3 -3
- package/dist/tui/local-model-contract.js +7 -25
- package/dist/tui/provider-discovery.d.ts +6 -0
- package/dist/tui/provider-discovery.js +84 -9
- package/package.json +1 -1
package/dist/helpers.js
CHANGED
|
@@ -8,8 +8,8 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { buildHostInstructionText } from "./ace-server-instructions.js";
|
|
9
9
|
import { isInside, normalizeRelPath } from "./shared.js";
|
|
10
10
|
import { getWorkspaceStorePath, listStoreKeysSync, parseVirtualStorePath, readStoreBlobSync, readVirtualStorePathSync, toVirtualStorePath, } from "./store/store-snapshot.js";
|
|
11
|
-
import { isOperationalArtifactPath, operationalArtifactKey, writeStoreBlobSync,
|
|
12
|
-
import {
|
|
11
|
+
import { isOperationalArtifactPath, operationalArtifactKey, writeStoreBlobSync, } from "./store/store-artifacts.js";
|
|
12
|
+
import { buildProviderDoctorCommands, buildOpenAiCompatibleBaseUrl, defaultModelForProvider, normalizeLocalBaseUrl, normalizeProvider, } from "./tui/provider-discovery.js";
|
|
13
13
|
export { isInside, isReadError, normalizeRelPath, looksLikeSwarmHandoffPath } from "./shared.js";
|
|
14
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname = dirname(__filename);
|
|
@@ -111,6 +111,14 @@ export const ACE_GITHUB_ROOT_REL = ".github";
|
|
|
111
111
|
export const ACE_HOST_AGENTS_REL = "AGENTS.md";
|
|
112
112
|
export const ACE_HOST_CLAUDE_REL = "CLAUDE.md";
|
|
113
113
|
export const ACE_HOST_CURSOR_RULES_REL = ".cursorrules";
|
|
114
|
+
const ACE_PROJECTED_WORKSPACE_FILES = new Set([
|
|
115
|
+
`${ACE_TASKS_ROOT_REL}/todo.md`,
|
|
116
|
+
`${ACE_ROOT_REL}/ace-hook-context.json`,
|
|
117
|
+
`${ACE_ROOT_REL}/agent-state/job-queue.json`,
|
|
118
|
+
`${ACE_ROOT_REL}/agent-state/job-locks.json`,
|
|
119
|
+
`${ACE_ROOT_REL}/agent-state/scheduler-lease.json`,
|
|
120
|
+
`${ACE_ROOT_REL}/agent-state/context-snapshots/index.json`,
|
|
121
|
+
]);
|
|
114
122
|
const ACE_MANAGED_PREFIXES = [
|
|
115
123
|
"agent-state",
|
|
116
124
|
"global-state",
|
|
@@ -128,7 +136,14 @@ export const ALL_MCP_CLIENTS = [
|
|
|
128
136
|
"cursor",
|
|
129
137
|
"antigravity",
|
|
130
138
|
];
|
|
131
|
-
export const ALL_LLM_PROVIDERS = [
|
|
139
|
+
export const ALL_LLM_PROVIDERS = [
|
|
140
|
+
"ollama",
|
|
141
|
+
"llama.cpp",
|
|
142
|
+
"codex",
|
|
143
|
+
"claude",
|
|
144
|
+
"gemini",
|
|
145
|
+
"copilot",
|
|
146
|
+
];
|
|
132
147
|
export const ALL_AGENTS = [
|
|
133
148
|
"orchestrator",
|
|
134
149
|
"vos",
|
|
@@ -379,6 +394,10 @@ export function mapAceWorkspaceRelativePath(path) {
|
|
|
379
394
|
}
|
|
380
395
|
return normalized;
|
|
381
396
|
}
|
|
397
|
+
export function isProjectedAceWorkspacePath(filePath) {
|
|
398
|
+
const canonical = mapAceWorkspaceRelativePath(normalizeRelPath(filePath));
|
|
399
|
+
return ACE_PROJECTED_WORKSPACE_FILES.has(canonical);
|
|
400
|
+
}
|
|
382
401
|
export function acePath(...segments) {
|
|
383
402
|
return resolve(currentWorkspaceRoot(), ACE_ROOT_REL, ...segments);
|
|
384
403
|
}
|
|
@@ -525,6 +544,9 @@ function resolveStoreFallbackKeys(filePath) {
|
|
|
525
544
|
const statePrefix = `${ACE_ROOT_REL}/agent-state/`;
|
|
526
545
|
if (canonical.startsWith(statePrefix)) {
|
|
527
546
|
const rel = canonical.slice(statePrefix.length);
|
|
547
|
+
if (!rel.startsWith("context-snapshots/")) {
|
|
548
|
+
keys.add(`state/artifacts/agent-state/${rel}`);
|
|
549
|
+
}
|
|
528
550
|
const runtimeProjectionKey = {
|
|
529
551
|
"job-queue.json": "state/artifacts/agent-state/job-queue.json",
|
|
530
552
|
"job-locks.json": "state/artifacts/agent-state/job-locks.json",
|
|
@@ -542,6 +564,10 @@ function resolveStoreFallbackKeys(filePath) {
|
|
|
542
564
|
if (rel)
|
|
543
565
|
keys.add(`state/memory/context_snapshots/${rel}`);
|
|
544
566
|
}
|
|
567
|
+
const skillsPrefix = `${ACE_SKILLS_ROOT_REL}/`;
|
|
568
|
+
if (canonical.startsWith(skillsPrefix)) {
|
|
569
|
+
keys.add(`knowledge/skills/${canonical.slice(skillsPrefix.length)}`);
|
|
570
|
+
}
|
|
545
571
|
const tasksPrefix = `${ACE_TASKS_ROOT_REL}/`;
|
|
546
572
|
if (canonical.startsWith(tasksPrefix)) {
|
|
547
573
|
keys.add(`knowledge/tasks/${canonical.slice(tasksPrefix.length)}`);
|
|
@@ -577,6 +603,9 @@ function resolveStoreFallbackKeys(filePath) {
|
|
|
577
603
|
}
|
|
578
604
|
return [...keys];
|
|
579
605
|
}
|
|
606
|
+
export function resolveStoreFallbackKeysForPath(filePath) {
|
|
607
|
+
return resolveStoreFallbackKeys(filePath);
|
|
608
|
+
}
|
|
580
609
|
function readFirstAvailable(paths) {
|
|
581
610
|
const found = firstExistingPath(paths);
|
|
582
611
|
if (!found) {
|
|
@@ -624,15 +653,14 @@ export function safeWrite(filePath, content) {
|
|
|
624
653
|
const normalized = normalizeRelPath(filePath);
|
|
625
654
|
const canonical = mapAceWorkspaceRelativePath(normalized);
|
|
626
655
|
const root = currentWorkspaceRoot();
|
|
627
|
-
|
|
628
|
-
const rel = canonical.slice(`${ACE_ROOT_REL}/`.length);
|
|
629
|
-
if (isOperationalArtifactPath(rel) && existsSync(getWorkspaceStorePath(root))) {
|
|
630
|
-
return writeOperationalArtifactSync(root, rel, content);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
656
|
+
let storeVirtualPath;
|
|
633
657
|
const storeKeys = resolveStoreFallbackKeys(filePath);
|
|
634
658
|
if (storeKeys.length > 0 && existsSync(getWorkspaceStorePath(root))) {
|
|
635
|
-
writeStoreBlobSync(root, storeKeys[0], content);
|
|
659
|
+
storeVirtualPath = writeStoreBlobSync(root, storeKeys[0], content);
|
|
660
|
+
const isAcePath = canonical === ACE_ROOT_REL || canonical.startsWith(`${ACE_ROOT_REL}/`);
|
|
661
|
+
if (isAcePath && !isProjectedAceWorkspacePath(canonical)) {
|
|
662
|
+
return storeVirtualPath;
|
|
663
|
+
}
|
|
636
664
|
}
|
|
637
665
|
const abs = resolveWorkspaceWritePath(filePath);
|
|
638
666
|
mkdirSync(dirname(abs), { recursive: true });
|
|
@@ -1325,10 +1353,11 @@ export function bootstrapAceWorkspace(options = {}) {
|
|
|
1325
1353
|
const projectName = options.projectName?.trim() || "ACE Project";
|
|
1326
1354
|
const includeMcpConfig = options.includeMcpConfig ?? true;
|
|
1327
1355
|
const includeClientConfigBundle = options.includeClientConfigBundle ?? true;
|
|
1328
|
-
const
|
|
1356
|
+
const normalizedProvider = normalizeProvider(options.llmProvider);
|
|
1357
|
+
const llmProvider = normalizedProvider;
|
|
1329
1358
|
const llmModel = options.llmModel?.trim() ||
|
|
1330
1359
|
options.ollamaModel?.trim() ||
|
|
1331
|
-
(llmProvider
|
|
1360
|
+
defaultModelForProvider(llmProvider);
|
|
1332
1361
|
const llmBaseUrl = normalizeLocalBaseUrl(options.llmBaseUrl ?? options.ollamaBaseUrl);
|
|
1333
1362
|
if (llmProvider && !ALL_LLM_PROVIDERS.includes(llmProvider)) {
|
|
1334
1363
|
throw new Error(`Unsupported llmProvider: ${llmProvider}`);
|
|
@@ -1654,12 +1683,14 @@ export function bootstrapAceWorkspace(options = {}) {
|
|
|
1654
1683
|
...(llmProvider
|
|
1655
1684
|
? [
|
|
1656
1685
|
"",
|
|
1657
|
-
"##
|
|
1686
|
+
"## LLM Runtime Profile",
|
|
1658
1687
|
`- \`${ACE_LLM_ROOT_REL}/llm-profile.json\` (generated for ${llmProvider})`,
|
|
1659
|
-
`- \`${ACE_LLM_ROOT_REL}/doctor-checks.md\` (commands to verify
|
|
1688
|
+
`- \`${ACE_LLM_ROOT_REL}/doctor-checks.md\` (commands to verify provider runtime)`,
|
|
1660
1689
|
llmBaseUrl
|
|
1661
1690
|
? `- Run \`ace doctor --llm ${llmProvider} --model ${llmModel} --base-url ${llmBaseUrl}\``
|
|
1662
|
-
:
|
|
1691
|
+
: llmProvider === "ollama" || llmProvider === "llama.cpp"
|
|
1692
|
+
? `- Run \`ace doctor --llm ${llmProvider} --model ${llmModel} --scan\` to discover a local endpoint`
|
|
1693
|
+
: `- Run \`ace doctor --llm ${llmProvider} --model ${llmModel}\` after exporting the provider credentials`,
|
|
1663
1694
|
]
|
|
1664
1695
|
: []),
|
|
1665
1696
|
].join("\n"), result, force);
|
|
@@ -1678,27 +1709,14 @@ export function bootstrapAceWorkspace(options = {}) {
|
|
|
1678
1709
|
}
|
|
1679
1710
|
}
|
|
1680
1711
|
writeIfMissingOrForced(wsPath(".ace", "llm-profile.json"), JSON.stringify(profilePayload, null, 2), result, force);
|
|
1681
|
-
const doctorCommands = llmProvider
|
|
1682
|
-
? [
|
|
1683
|
-
"ollama serve",
|
|
1684
|
-
`ollama pull ${llmModel}`,
|
|
1685
|
-
...(llmBaseUrl ? [`curl -s ${llmBaseUrl}/api/tags`] : []),
|
|
1686
|
-
]
|
|
1687
|
-
: [
|
|
1688
|
-
"# Start llama-server separately, for example:",
|
|
1689
|
-
"# llama-server -m /path/to/model.gguf --port 8080",
|
|
1690
|
-
...(llmBaseUrl ? [`curl -s ${buildOpenAiCompatibleBaseUrl(llmBaseUrl)}/models`] : []),
|
|
1691
|
-
];
|
|
1712
|
+
const doctorCommands = buildProviderDoctorCommands(llmProvider, llmModel, llmBaseUrl);
|
|
1692
1713
|
writeIfMissingOrForced(wsPath(".ace", "doctor-checks.md"), [
|
|
1693
|
-
`# ACE + ${llmProvider}
|
|
1714
|
+
`# ACE + ${llmProvider} Runtime Checks`,
|
|
1694
1715
|
"",
|
|
1695
|
-
"Run these commands to verify
|
|
1716
|
+
"Run these commands to verify ACE runtime readiness:",
|
|
1696
1717
|
"",
|
|
1697
1718
|
"```bash",
|
|
1698
1719
|
...doctorCommands,
|
|
1699
|
-
llmBaseUrl
|
|
1700
|
-
? `ace doctor --llm ${llmProvider} --model ${llmModel} --base-url ${llmBaseUrl}`
|
|
1701
|
-
: `ace doctor --llm ${llmProvider} --model ${llmModel} --scan`,
|
|
1702
1720
|
"```",
|
|
1703
1721
|
"",
|
|
1704
1722
|
`MCP is still configured separately via \`${ACE_VSCODE_ROOT_REL}/mcp.json\` or \`${ACE_BUNDLE_ROOT_REL}/*\`.`,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
1
|
import { resolve } from "node:path";
|
|
3
|
-
import { ROLE_ENUM
|
|
2
|
+
import { ROLE_ENUM } from "./shared.js";
|
|
4
3
|
import { resolveWorkspaceRoot } from "./helpers.js";
|
|
5
4
|
import { executeAceInternalTool } from "./ace-internal-tools.js";
|
|
6
5
|
import { ModelBridge } from "./model-bridge.js";
|
|
@@ -28,61 +27,17 @@ function normalizeRoleCandidate(input) {
|
|
|
28
27
|
? candidate
|
|
29
28
|
: undefined;
|
|
30
29
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
if (!match?.[1])
|
|
34
|
-
return undefined;
|
|
35
|
-
const firstAgent = match[1]
|
|
36
|
-
.split(",")
|
|
37
|
-
.map((entry) => entry.trim())
|
|
38
|
-
.find(Boolean);
|
|
39
|
-
return normalizeRoleCandidate(firstAgent);
|
|
40
|
-
}
|
|
41
|
-
function hasAlignedTaskContract(workspaceRoot) {
|
|
42
|
-
const required = [
|
|
43
|
-
"agent-state/TASK.md",
|
|
44
|
-
"agent-state/SCOPE.md",
|
|
45
|
-
"agent-state/QUALITY_GATES.md",
|
|
46
|
-
"agent-state/HANDOFF.json",
|
|
47
|
-
];
|
|
48
|
-
return required.every((relativePath) => {
|
|
49
|
-
const absolute = resolve(workspaceRoot, relativePath);
|
|
50
|
-
if (!existsSync(absolute))
|
|
51
|
-
return false;
|
|
52
|
-
return readFileSync(absolute, "utf-8").trim().length > 0;
|
|
53
|
-
});
|
|
30
|
+
function fallbackRoleForTask() {
|
|
31
|
+
return "orchestrator";
|
|
54
32
|
}
|
|
55
|
-
function
|
|
56
|
-
if (!hasAlignedTaskContract(workspaceRoot)) {
|
|
57
|
-
return "orchestrator";
|
|
58
|
-
}
|
|
59
|
-
const { domain } = scoreDomains(task);
|
|
60
|
-
switch (domain) {
|
|
61
|
-
case "engineering":
|
|
62
|
-
return "coders";
|
|
63
|
-
case "venture":
|
|
64
|
-
return "vos";
|
|
65
|
-
case "ux":
|
|
66
|
-
return "ui";
|
|
67
|
-
default:
|
|
68
|
-
return "orchestrator";
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
async function resolveRole(task, workspaceRoot, sessionId, requestedRole) {
|
|
33
|
+
async function resolveRole(task, sessionId, requestedRole) {
|
|
72
34
|
const explicitRole = normalizeRoleCandidate(requestedRole);
|
|
73
35
|
if (explicitRole) {
|
|
74
36
|
return { role: explicitRole, routingSummary: undefined };
|
|
75
37
|
}
|
|
76
38
|
const routing = await executeAceInternalTool("route_task", { description: task, domain: "unknown" }, sessionId);
|
|
77
39
|
const routingSummary = extractTextContent(routing);
|
|
78
|
-
|
|
79
|
-
const fallbackRole = fallbackRoleForTask(task, workspaceRoot);
|
|
80
|
-
return {
|
|
81
|
-
role: routedRole && !(routedRole === "orchestrator" && fallbackRole !== "orchestrator")
|
|
82
|
-
? routedRole
|
|
83
|
-
: fallbackRole,
|
|
84
|
-
routingSummary,
|
|
85
|
-
};
|
|
40
|
+
return { role: fallbackRoleForTask(), routingSummary };
|
|
86
41
|
}
|
|
87
42
|
function resolveTier(requested, provider, model, role) {
|
|
88
43
|
if (requested && requested !== "auto")
|
|
@@ -152,7 +107,7 @@ export async function runLocalModelTask(options) {
|
|
|
152
107
|
ollamaUrl: options.ollamaUrl,
|
|
153
108
|
});
|
|
154
109
|
const bridge = new ModelBridge(options.clients ?? createDefaultModelBridgeClients(runtime));
|
|
155
|
-
const { role, routingSummary } = await resolveRole(options.task,
|
|
110
|
+
const { role, routingSummary } = await resolveRole(options.task, undefined, options.role);
|
|
156
111
|
const tier = resolveTier(options.tier, runtime.provider, runtime.model, role);
|
|
157
112
|
const result = await bridge.run({
|
|
158
113
|
task: options.task,
|
|
@@ -27,7 +27,7 @@ import { ProjectionManager } from "./materializers/projection-manager.js";
|
|
|
27
27
|
import { TodoSyncer } from "./materializers/todo-syncer.js";
|
|
28
28
|
import { OPERATIONAL_ARTIFACT_REL_PATHS, operationalArtifactKey, } from "./store-artifacts.js";
|
|
29
29
|
import { STORE_VERSION } from "./types.js";
|
|
30
|
-
import {
|
|
30
|
+
import { buildProviderDoctorCommands, defaultModelForProvider, normalizeLocalBaseUrl, normalizeProvider, } from "../tui/provider-discovery.js";
|
|
31
31
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
function getAssetsRoot() {
|
|
33
33
|
// Resolve relative to this file's location in the installed package
|
|
@@ -40,45 +40,32 @@ function getAssetsRoot() {
|
|
|
40
40
|
async function applyLlmRuntimeConfig(store, opts) {
|
|
41
41
|
if (!opts.llm)
|
|
42
42
|
return;
|
|
43
|
-
const
|
|
43
|
+
const provider = normalizeProvider(opts.llm) ?? opts.llm;
|
|
44
|
+
const configuredModel = opts.model ?? defaultModelForProvider(provider);
|
|
45
|
+
const configuredBaseUrl = normalizeLocalBaseUrl(opts.baseUrl ?? opts.ollamaUrl);
|
|
44
46
|
const profilePayload = {
|
|
45
|
-
provider
|
|
47
|
+
provider,
|
|
46
48
|
model: configuredModel,
|
|
47
49
|
generated_at: new Date().toISOString(),
|
|
48
50
|
};
|
|
49
|
-
if (
|
|
50
|
-
profilePayload.base_url =
|
|
51
|
-
if (
|
|
52
|
-
profilePayload.api_compat_base_url =
|
|
53
|
-
?
|
|
54
|
-
: `${
|
|
51
|
+
if (configuredBaseUrl) {
|
|
52
|
+
profilePayload.base_url = configuredBaseUrl;
|
|
53
|
+
if (provider === "ollama") {
|
|
54
|
+
profilePayload.api_compat_base_url = configuredBaseUrl.endsWith("/v1")
|
|
55
|
+
? configuredBaseUrl
|
|
56
|
+
: `${configuredBaseUrl}/v1`;
|
|
55
57
|
profilePayload.default_api_key = "ollama";
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
await store.setJSON("state/runtime/llm_profile", profilePayload);
|
|
59
|
-
const doctorCommands =
|
|
60
|
-
? [
|
|
61
|
-
"ollama serve",
|
|
62
|
-
`ollama pull ${configuredModel}`,
|
|
63
|
-
...(opts.ollamaUrl ? [`curl -s ${opts.ollamaUrl}/api/tags`] : []),
|
|
64
|
-
]
|
|
65
|
-
: [
|
|
66
|
-
"# Start llama-server separately, for example:",
|
|
67
|
-
"# llama-server -m /path/to/model.gguf --port 8080",
|
|
68
|
-
...(opts.ollamaUrl
|
|
69
|
-
? [`curl -s ${opts.ollamaUrl.endsWith("/v1") ? opts.ollamaUrl : `${opts.ollamaUrl}/v1`}/models`]
|
|
70
|
-
: []),
|
|
71
|
-
];
|
|
61
|
+
const doctorCommands = buildProviderDoctorCommands(provider, configuredModel, configuredBaseUrl);
|
|
72
62
|
await store.setBlob("state/runtime/doctor_checks.md", [
|
|
73
|
-
`# ACE + ${
|
|
63
|
+
`# ACE + ${provider} Runtime Checks`,
|
|
74
64
|
"",
|
|
75
|
-
"Run these commands to verify
|
|
65
|
+
"Run these commands to verify ACE runtime readiness:",
|
|
76
66
|
"",
|
|
77
67
|
"```bash",
|
|
78
68
|
...doctorCommands,
|
|
79
|
-
opts.ollamaUrl
|
|
80
|
-
? `ace doctor --llm ${opts.llm} --model ${configuredModel} --base-url ${opts.ollamaUrl}`
|
|
81
|
-
: `ace doctor --llm ${opts.llm} --model ${configuredModel} --scan`,
|
|
82
69
|
"```",
|
|
83
70
|
"",
|
|
84
71
|
].join("\n"));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface CacheWorkspaceOptions {
|
|
2
|
+
dryRun?: boolean;
|
|
3
|
+
clean?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface CacheWorkspaceResult {
|
|
6
|
+
storePath: string;
|
|
7
|
+
dry_run: boolean;
|
|
8
|
+
clean: boolean;
|
|
9
|
+
scanned_files: number;
|
|
10
|
+
cached_files: number;
|
|
11
|
+
removed_files: number;
|
|
12
|
+
kept_projected_files: number;
|
|
13
|
+
skipped_files: number;
|
|
14
|
+
warnings: string[];
|
|
15
|
+
cached: Array<{
|
|
16
|
+
path: string;
|
|
17
|
+
key: string;
|
|
18
|
+
bytes: number;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare function cacheWorkspaceArtifacts(workspaceRoot: string, options?: CacheWorkspaceOptions): Promise<CacheWorkspaceResult>;
|
|
22
|
+
//# sourceMappingURL=cache-workspace.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { ACE_ROOT_REL, isProjectedAceWorkspacePath, mapAceWorkspaceRelativePath, resolveStoreFallbackKeysForPath, } from "../helpers.js";
|
|
4
|
+
import { normalizeRelPath } from "../shared.js";
|
|
5
|
+
import { writeStoreBlobsSync } from "./store-artifacts.js";
|
|
6
|
+
const SCAN_ROOTS_REL = [
|
|
7
|
+
"agent-state",
|
|
8
|
+
".agents/ACE/agent-state",
|
|
9
|
+
".agents/ACE/tasks",
|
|
10
|
+
".agents/ACE/skills",
|
|
11
|
+
];
|
|
12
|
+
function isInside(base, target) {
|
|
13
|
+
const rel = relative(base, target);
|
|
14
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
15
|
+
}
|
|
16
|
+
function collectFiles(root) {
|
|
17
|
+
const out = [];
|
|
18
|
+
if (!existsSync(root))
|
|
19
|
+
return out;
|
|
20
|
+
const walk = (dir) => {
|
|
21
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
22
|
+
const abs = join(dir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
walk(abs);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (entry.isFile()) {
|
|
28
|
+
out.push(abs);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(root);
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function pruneEmptyParents(path, stopAt) {
|
|
36
|
+
let cursor = dirname(path);
|
|
37
|
+
while (isInside(stopAt, cursor) && cursor !== stopAt && existsSync(cursor)) {
|
|
38
|
+
const entries = readdirSync(cursor);
|
|
39
|
+
if (entries.length > 0)
|
|
40
|
+
break;
|
|
41
|
+
rmSync(cursor, { recursive: true, force: true });
|
|
42
|
+
cursor = dirname(cursor);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function isTextBuffer(input) {
|
|
46
|
+
return !input.includes(0);
|
|
47
|
+
}
|
|
48
|
+
function selectScanRoots(workspaceRoot) {
|
|
49
|
+
return SCAN_ROOTS_REL.map((relPath) => join(workspaceRoot, relPath)).filter((abs) => existsSync(abs));
|
|
50
|
+
}
|
|
51
|
+
export async function cacheWorkspaceArtifacts(workspaceRoot, options = {}) {
|
|
52
|
+
const dryRun = options.dryRun ?? false;
|
|
53
|
+
const clean = options.clean ?? true;
|
|
54
|
+
const storePath = join(workspaceRoot, ".agents", "ACE", "ace-state.ace");
|
|
55
|
+
if (!existsSync(storePath)) {
|
|
56
|
+
throw new Error(`No store found at ${storePath}. Run 'ace init' first.`);
|
|
57
|
+
}
|
|
58
|
+
const warnings = [];
|
|
59
|
+
const scanRoots = selectScanRoots(workspaceRoot);
|
|
60
|
+
const candidates = [];
|
|
61
|
+
let scanned = 0;
|
|
62
|
+
let keptProjected = 0;
|
|
63
|
+
let skipped = 0;
|
|
64
|
+
for (const scanRoot of scanRoots) {
|
|
65
|
+
for (const absPath of collectFiles(scanRoot)) {
|
|
66
|
+
scanned += 1;
|
|
67
|
+
const relPath = normalizeRelPath(relative(workspaceRoot, absPath));
|
|
68
|
+
const canonicalPath = mapAceWorkspaceRelativePath(relPath);
|
|
69
|
+
if (!canonicalPath.startsWith(`${ACE_ROOT_REL}/`)) {
|
|
70
|
+
skipped += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (canonicalPath === `${ACE_ROOT_REL}/ace-state.ace`) {
|
|
74
|
+
skipped += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (isProjectedAceWorkspacePath(canonicalPath)) {
|
|
78
|
+
keptProjected += 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const raw = readFileSync(absPath);
|
|
82
|
+
if (!isTextBuffer(raw)) {
|
|
83
|
+
skipped += 1;
|
|
84
|
+
warnings.push(`Skipped non-text artifact: ${canonicalPath}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const fallbackKeys = resolveStoreFallbackKeysForPath(canonicalPath);
|
|
88
|
+
if (fallbackKeys.length === 0) {
|
|
89
|
+
skipped += 1;
|
|
90
|
+
warnings.push(`No store fallback key for ${canonicalPath}`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
candidates.push({
|
|
94
|
+
absPath,
|
|
95
|
+
canonicalPath,
|
|
96
|
+
key: fallbackKeys[0],
|
|
97
|
+
content: raw.toString("utf-8"),
|
|
98
|
+
bytes: raw.byteLength,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const dedupByKey = new Map();
|
|
103
|
+
for (const candidate of candidates) {
|
|
104
|
+
dedupByKey.set(candidate.key, candidate);
|
|
105
|
+
}
|
|
106
|
+
const uniqueForWrite = [...dedupByKey.values()];
|
|
107
|
+
if (!dryRun && uniqueForWrite.length > 0) {
|
|
108
|
+
writeStoreBlobsSync(workspaceRoot, uniqueForWrite.map((candidate) => ({
|
|
109
|
+
key: candidate.key,
|
|
110
|
+
content: candidate.content,
|
|
111
|
+
})));
|
|
112
|
+
}
|
|
113
|
+
let removed = 0;
|
|
114
|
+
if (!dryRun && clean && candidates.length > 0) {
|
|
115
|
+
const removedPaths = new Set();
|
|
116
|
+
for (const candidate of candidates) {
|
|
117
|
+
if (removedPaths.has(candidate.absPath))
|
|
118
|
+
continue;
|
|
119
|
+
rmSync(candidate.absPath, { force: true });
|
|
120
|
+
removedPaths.add(candidate.absPath);
|
|
121
|
+
removed += 1;
|
|
122
|
+
const pruneRoot = scanRoots.find((root) => isInside(root, candidate.absPath)) ?? workspaceRoot;
|
|
123
|
+
pruneEmptyParents(candidate.absPath, pruneRoot);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
storePath,
|
|
128
|
+
dry_run: dryRun,
|
|
129
|
+
clean,
|
|
130
|
+
scanned_files: scanned,
|
|
131
|
+
cached_files: candidates.length,
|
|
132
|
+
removed_files: removed,
|
|
133
|
+
kept_projected_files: keptProjected,
|
|
134
|
+
skipped_files: skipped,
|
|
135
|
+
warnings,
|
|
136
|
+
cached: candidates.map((candidate) => ({
|
|
137
|
+
path: candidate.canonicalPath,
|
|
138
|
+
key: candidate.key,
|
|
139
|
+
bytes: candidate.bytes,
|
|
140
|
+
})),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=cache-workspace.js.map
|
|
@@ -26,11 +26,6 @@ export class ContextSnapshotMaterializer {
|
|
|
26
26
|
if (!existsSync(dir))
|
|
27
27
|
mkdirSync(dir, { recursive: true });
|
|
28
28
|
const snapshots = await this.repo.listSnapshots();
|
|
29
|
-
const retained = new Set(["index.json"]);
|
|
30
|
-
for (const snapshot of snapshots) {
|
|
31
|
-
retained.add(snapshot.file);
|
|
32
|
-
writeText(join(dir, snapshot.file), `${JSON.stringify(snapshot.record, null, 2)}\n`);
|
|
33
|
-
}
|
|
34
29
|
writeText(join(dir, "index.json"), `${JSON.stringify({
|
|
35
30
|
snapshots: snapshots.map(({ name, file, timestamp, summary }) => ({
|
|
36
31
|
name,
|
|
@@ -42,7 +37,7 @@ export class ContextSnapshotMaterializer {
|
|
|
42
37
|
for (const file of readdirSync(dir)) {
|
|
43
38
|
if (!file.endsWith(".json"))
|
|
44
39
|
continue;
|
|
45
|
-
if (
|
|
40
|
+
if (file === "index.json")
|
|
46
41
|
continue;
|
|
47
42
|
rmSync(join(dir, file), { force: true });
|
|
48
43
|
}
|