@fitlab-ai/agent-infra 0.9.3 → 0.9.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 +3 -1
- package/README.zh-CN.md +3 -1
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +144 -123
- package/dist/lib/agent-clients/adapter.js +94 -4
- package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
- package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
- package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +382 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +163 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -0
- package/dist/lib/agent-clients/adapters/codex.js +2 -2
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
- package/dist/lib/agent-clients/adapters/opencode.js +15 -3
- package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/dist/lib/agent-clients/registry.js +9 -1
- package/dist/lib/git/worktree-identity.js +64 -0
- package/dist/lib/internal/codex-lifecycle.js +202 -0
- package/dist/lib/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +45 -13
- package/dist/lib/internal/task-create.js +48 -0
- package/dist/lib/internal/task-ledger.js +12 -3
- package/dist/lib/internal/task-lifecycle.js +19 -1
- package/dist/lib/internal/task-orchestration.js +170 -27
- package/dist/lib/platform/github-client.js +19 -2
- package/dist/lib/platform/pr-checks.js +1 -1
- package/dist/lib/platform/pull-requests.js +47 -1
- package/dist/lib/platform/releases.js +13 -5
- package/dist/lib/process-data/index.js +239 -0
- package/dist/lib/process-data/normalize.js +198 -0
- package/dist/lib/process-data/privacy.js +33 -0
- package/dist/lib/process-data/reconcile.js +95 -0
- package/dist/lib/process-data/repair.js +58 -0
- package/dist/lib/process-data/sources.js +345 -0
- package/dist/lib/process-data/store.js +282 -0
- package/dist/lib/process-data/types.js +2 -0
- package/dist/lib/run/index.js +4 -3
- package/dist/lib/run/tui.js +7 -8
- package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
- package/dist/lib/sandbox/capture.js +7 -2
- package/dist/lib/sandbox/commands/create.js +110 -228
- package/dist/lib/sandbox/commands/enter.js +15 -47
- package/dist/lib/sandbox/commands/list-running.js +16 -6
- package/dist/lib/sandbox/commands/ls.js +10 -6
- package/dist/lib/sandbox/commands/rebuild.js +4 -3
- package/dist/lib/sandbox/commands/refresh.js +4 -84
- package/dist/lib/sandbox/commands/rm.js +24 -3
- package/dist/lib/sandbox/commands/show.js +12 -3
- package/dist/lib/sandbox/commands/start.js +7 -4
- package/dist/lib/sandbox/config.js +2 -0
- package/dist/lib/sandbox/constants.js +6 -0
- package/dist/lib/sandbox/control/client.js +58 -0
- package/dist/lib/sandbox/control/protocol.js +54 -0
- package/dist/lib/sandbox/control/server.js +144 -0
- package/dist/lib/sandbox/dockerfile.js +36 -13
- package/dist/lib/sandbox/mounts.js +17 -4
- package/dist/lib/sandbox/recovery.js +128 -5
- package/dist/lib/sandbox/redaction.js +12 -0
- package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/dist/lib/sandbox/workspace-identity.js +95 -0
- package/dist/lib/sandbox/workspace-view.js +109 -0
- package/dist/lib/task/activity-intent.js +257 -51
- package/dist/lib/task/activity-log.js +32 -5
- package/dist/lib/task/commit-finalization.js +278 -0
- package/dist/lib/task/commit-intent.js +165 -0
- package/dist/lib/task/create-service.js +137 -0
- package/dist/lib/task/create.js +333 -0
- package/dist/lib/task/decision-intents.js +31 -17
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/implementation-inputs.js +54 -7
- package/dist/lib/task/ledger-intents.js +35 -1
- package/dist/lib/task/orchestration.js +505 -34
- package/dist/lib/task/resolve-ref.js +41 -1
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/workspace-snapshot.js +78 -3
- package/lib/agent-clients/adapter.ts +130 -6
- package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
- package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
- package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
- package/lib/agent-clients/adapters/claude-code.ts +21 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +442 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +307 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +250 -0
- package/lib/agent-clients/adapters/codex.ts +2 -2
- package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
- package/lib/agent-clients/adapters/opencode.ts +18 -3
- package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/lib/agent-clients/registry.ts +9 -1
- package/lib/git/worktree-identity.ts +86 -0
- package/lib/internal/codex-lifecycle.ts +204 -0
- package/lib/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +50 -14
- package/lib/internal/task-create.ts +49 -0
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +22 -1
- package/lib/internal/task-orchestration.ts +117 -21
- package/lib/platform/github-client.ts +20 -2
- package/lib/platform/pr-checks.ts +1 -1
- package/lib/platform/pull-requests.ts +49 -1
- package/lib/platform/releases.ts +16 -4
- package/lib/process-data/index.ts +224 -0
- package/lib/process-data/normalize.ts +202 -0
- package/lib/process-data/privacy.ts +41 -0
- package/lib/process-data/reconcile.ts +110 -0
- package/lib/process-data/repair.ts +64 -0
- package/lib/process-data/sources.ts +354 -0
- package/lib/process-data/store.ts +315 -0
- package/lib/process-data/types.ts +135 -0
- package/lib/run/index.ts +5 -3
- package/lib/run/tui.ts +11 -8
- package/lib/sandbox/agent-client-reconciler.ts +44 -2
- package/lib/sandbox/capture.ts +9 -2
- package/lib/sandbox/commands/create.ts +127 -260
- package/lib/sandbox/commands/enter.ts +13 -59
- package/lib/sandbox/commands/list-running.ts +23 -6
- package/lib/sandbox/commands/ls.ts +22 -6
- package/lib/sandbox/commands/rebuild.ts +4 -3
- package/lib/sandbox/commands/refresh.ts +4 -126
- package/lib/sandbox/commands/rm.ts +24 -3
- package/lib/sandbox/commands/show.ts +29 -3
- package/lib/sandbox/commands/start.ts +6 -4
- package/lib/sandbox/config.ts +4 -0
- package/lib/sandbox/constants.ts +8 -0
- package/lib/sandbox/control/client.ts +79 -0
- package/lib/sandbox/control/protocol.ts +110 -0
- package/lib/sandbox/control/server.ts +155 -0
- package/lib/sandbox/dockerfile.ts +66 -13
- package/lib/sandbox/mounts.ts +24 -4
- package/lib/sandbox/recovery.ts +163 -6
- package/lib/sandbox/redaction.ts +14 -0
- package/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/lib/sandbox/tool-types.ts +16 -0
- package/lib/sandbox/workspace-identity.ts +123 -0
- package/lib/sandbox/workspace-view.ts +150 -0
- package/lib/task/activity-intent.ts +350 -61
- package/lib/task/activity-log.ts +46 -7
- package/lib/task/commit-finalization.ts +376 -0
- package/lib/task/commit-intent.ts +230 -0
- package/lib/task/create-service.ts +171 -0
- package/lib/task/create.ts +372 -0
- package/lib/task/decision-intents.ts +32 -12
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/implementation-inputs.ts +62 -8
- package/lib/task/ledger-intents.ts +43 -3
- package/lib/task/orchestration.ts +673 -41
- package/lib/task/resolve-ref.ts +46 -1
- package/lib/task/review-fingerprint.ts +9 -0
- package/lib/task/workspace-snapshot.ts +98 -4
- package/package.json +7 -7
- package/templates/.agents/hooks/lifecycle-delegation.js +47 -3
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +11 -2
- package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
- package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +3 -0
- package/templates/.agents/rules/task-management.zh-CN.md +3 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +21 -17
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +28 -16
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +50 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +56 -0
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.claude/rules/tool-preferences.en.md +20 -0
- package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
- package/templates/.codex/hooks.json +50 -0
- package/templates/.opencode/commands/watch-pr.en.md +1 -1
- package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
|
@@ -91,6 +91,46 @@ function enumerateTaskDirs(repoRoot) {
|
|
|
91
91
|
}
|
|
92
92
|
return out;
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Enumerate every task directory, including the cold YYYY/MM/DD archive.
|
|
96
|
+
* Invalid directory names, symlinks and entries without task.md are ignored;
|
|
97
|
+
* duplicate task ids fail closed because their identity would be ambiguous.
|
|
98
|
+
*/
|
|
99
|
+
function enumerateAllTaskDirs(repoRoot) {
|
|
100
|
+
const out = [];
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
const add = (taskId, taskDir, state) => {
|
|
103
|
+
if (!TASK_ID_RE.test(taskId))
|
|
104
|
+
return;
|
|
105
|
+
const stat = fs.lstatSync(taskDir);
|
|
106
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !fs.existsSync(path.join(taskDir, 'task.md')))
|
|
107
|
+
return;
|
|
108
|
+
if (seen.has(taskId))
|
|
109
|
+
throw new Error(`duplicate task identity: ${taskId}`);
|
|
110
|
+
seen.add(taskId);
|
|
111
|
+
out.push({ taskId, taskDir, state });
|
|
112
|
+
};
|
|
113
|
+
for (const state of FLAT_WORKSPACE_DIRS) {
|
|
114
|
+
const base = path.join(repoRoot, '.agents', 'workspace', state);
|
|
115
|
+
if (!fs.existsSync(base))
|
|
116
|
+
continue;
|
|
117
|
+
for (const taskId of fs.readdirSync(base).sort())
|
|
118
|
+
add(taskId, path.join(base, taskId), state);
|
|
119
|
+
}
|
|
120
|
+
const archive = path.join(repoRoot, '.agents', 'workspace', 'archive');
|
|
121
|
+
for (const year of listSortedNumeric(archive, 4).reverse()) {
|
|
122
|
+
const yearDir = path.join(archive, year);
|
|
123
|
+
for (const month of listSortedNumeric(yearDir, 2).reverse()) {
|
|
124
|
+
const monthDir = path.join(yearDir, month);
|
|
125
|
+
for (const day of listSortedNumeric(monthDir, 2).reverse()) {
|
|
126
|
+
const dayDir = path.join(monthDir, day);
|
|
127
|
+
for (const taskId of fs.readdirSync(dayDir).sort())
|
|
128
|
+
add(taskId, path.join(dayDir, taskId), 'archive');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
94
134
|
function locateHotTaskDirs(repoRoot, taskId) {
|
|
95
135
|
return FLAT_WORKSPACE_DIRS.flatMap((state) => {
|
|
96
136
|
const taskDir = path.join(repoRoot, '.agents', 'workspace', state, taskId);
|
|
@@ -266,5 +306,5 @@ function resolveTaskContext(taskRef, options = {}) {
|
|
|
266
306
|
const match = matches[0];
|
|
267
307
|
return { ok: true, repoRoot, ...match, state: 'active' };
|
|
268
308
|
}
|
|
269
|
-
export { resolveTaskRef, resolveTaskContext, detectRepoRoot, enumerateTaskDirs, locateHotTaskDirs, TASK_ID_RE };
|
|
309
|
+
export { resolveTaskRef, resolveTaskContext, detectRepoRoot, enumerateTaskDirs, enumerateAllTaskDirs, locateHotTaskDirs, TASK_ID_RE };
|
|
270
310
|
//# sourceMappingURL=resolve-ref.js.map
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { artifactName, maxRound } from "./review-artifacts.js";
|
|
4
|
+
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
5
|
+
export function parseReviewedGitTree(value) {
|
|
6
|
+
return REVIEWED_TREE_RE.test(value) ? value : null;
|
|
7
|
+
}
|
|
4
8
|
// Paths excluded from the post-review coverage gate. Fail-closed: the gate
|
|
5
9
|
// covers ALL tracked changes by default. This default denylist is EMPTY —
|
|
6
10
|
// projects opt into exclusions explicitly via review.post_review_exclude_globs.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -25,12 +26,70 @@ function captureWorktreeTree(repoRoot, forcedPath) {
|
|
|
25
26
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
function
|
|
29
|
+
function captureLegacyWorkspaceSnapshot(repoRoot, taskId) {
|
|
29
30
|
const workspaceRelative = taskId
|
|
30
31
|
? `.agents/workspace/active/${taskId}`
|
|
31
32
|
: '.agents/workspace/active';
|
|
32
33
|
return captureWorktreeTree(repoRoot, workspaceRelative);
|
|
33
34
|
}
|
|
35
|
+
function taskFileFingerprints(stateRoot, taskId) {
|
|
36
|
+
const activeRoot = path.join(stateRoot, '.agents', 'workspace', 'active');
|
|
37
|
+
const roots = taskId ? [path.join(activeRoot, taskId)] : [activeRoot];
|
|
38
|
+
const records = [];
|
|
39
|
+
const visit = (absolute, relative) => {
|
|
40
|
+
const stat = fs.lstatSync(absolute);
|
|
41
|
+
if (stat.isDirectory()) {
|
|
42
|
+
for (const name of fs.readdirSync(absolute).sort()) {
|
|
43
|
+
visit(path.join(absolute, name), path.posix.join(relative, name));
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!stat.isFile() && !stat.isSymbolicLink())
|
|
48
|
+
return;
|
|
49
|
+
const content = stat.isSymbolicLink()
|
|
50
|
+
? Buffer.from(fs.readlinkSync(absolute))
|
|
51
|
+
: fs.readFileSync(absolute);
|
|
52
|
+
records.push({
|
|
53
|
+
path: relative,
|
|
54
|
+
mode: stat.mode & 0o777,
|
|
55
|
+
kind: stat.isSymbolicLink() ? 'symlink' : 'file',
|
|
56
|
+
sha256: createHash('sha256').update(content).digest('hex')
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
for (const root of roots) {
|
|
60
|
+
if (!fs.existsSync(root))
|
|
61
|
+
continue;
|
|
62
|
+
const relative = path.posix.join('.agents/workspace/active', path.relative(activeRoot, root).split(path.sep).join('/'));
|
|
63
|
+
visit(root, relative.replace(/\/$/, ''));
|
|
64
|
+
}
|
|
65
|
+
return records.sort((left, right) => left.path.localeCompare(right.path));
|
|
66
|
+
}
|
|
67
|
+
function encodeWorkspaceFingerprint(value) {
|
|
68
|
+
return `ws2:${Buffer.from(JSON.stringify(value)).toString('base64url')}`;
|
|
69
|
+
}
|
|
70
|
+
function decodeWorkspaceFingerprint(value) {
|
|
71
|
+
if (!value.startsWith('ws2:'))
|
|
72
|
+
return null;
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(Buffer.from(value.slice(4), 'base64url').toString('utf8'));
|
|
75
|
+
if (parsed.version !== 2 || !/^[a-f0-9]{40,64}$/.test(parsed.gitTree) || !Array.isArray(parsed.taskFiles)) {
|
|
76
|
+
throw new Error('invalid workspace fingerprint fields');
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new Error(`ORCHESTRATION_SNAPSHOT_FAILED: ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function captureWorkspaceSnapshot(contextOrRoot, taskId) {
|
|
85
|
+
if (typeof contextOrRoot === 'string')
|
|
86
|
+
return captureLegacyWorkspaceSnapshot(contextOrRoot, taskId ?? null);
|
|
87
|
+
return encodeWorkspaceFingerprint({
|
|
88
|
+
version: 2,
|
|
89
|
+
gitTree: captureWorktreeTree(contextOrRoot.gitRoot, null),
|
|
90
|
+
taskFiles: taskFileFingerprints(contextOrRoot.stateRoot, contextOrRoot.taskId)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
34
93
|
function captureRepositorySnapshot(repoRoot) {
|
|
35
94
|
return {
|
|
36
95
|
head: git(repoRoot, null, ['rev-parse', 'HEAD']).trim(),
|
|
@@ -39,8 +98,24 @@ function captureRepositorySnapshot(repoRoot) {
|
|
|
39
98
|
};
|
|
40
99
|
}
|
|
41
100
|
function diffWorkspaceSnapshots(repoRoot, before, after) {
|
|
42
|
-
const
|
|
43
|
-
|
|
101
|
+
const beforeV2 = decodeWorkspaceFingerprint(before);
|
|
102
|
+
const afterV2 = decodeWorkspaceFingerprint(after);
|
|
103
|
+
if ((beforeV2 === null) !== (afterV2 === null)) {
|
|
104
|
+
throw new Error('ORCHESTRATION_SNAPSHOT_FAILED: workspace fingerprint versions cannot be mixed');
|
|
105
|
+
}
|
|
106
|
+
if (!beforeV2 || !afterV2) {
|
|
107
|
+
const output = git(repoRoot, null, ['diff', '--name-only', '-z', before, after]);
|
|
108
|
+
return output.split('\0').filter(Boolean);
|
|
109
|
+
}
|
|
110
|
+
const output = git(repoRoot, null, ['diff', '--name-only', '-z', beforeV2.gitTree, afterV2.gitTree]);
|
|
111
|
+
const changed = new Set(output.split('\0').filter(Boolean));
|
|
112
|
+
const beforeFiles = new Map(beforeV2.taskFiles.map((record) => [record.path, JSON.stringify(record)]));
|
|
113
|
+
const afterFiles = new Map(afterV2.taskFiles.map((record) => [record.path, JSON.stringify(record)]));
|
|
114
|
+
for (const filePath of new Set([...beforeFiles.keys(), ...afterFiles.keys()])) {
|
|
115
|
+
if (beforeFiles.get(filePath) !== afterFiles.get(filePath))
|
|
116
|
+
changed.add(filePath);
|
|
117
|
+
}
|
|
118
|
+
return [...changed].sort();
|
|
44
119
|
}
|
|
45
120
|
export { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots };
|
|
46
121
|
//# sourceMappingURL=workspace-snapshot.js.map
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../sandbox/tool-types.ts';
|
|
13
13
|
import type {
|
|
14
14
|
AgentClientSandboxRecoveryCheck,
|
|
15
|
+
AgentClientSandboxImageDescriptor,
|
|
15
16
|
AgentClientSandboxDescriptor,
|
|
16
17
|
SandboxAlias,
|
|
17
18
|
SandboxTool,
|
|
@@ -38,8 +39,8 @@ type AgentClientModelSelectionContext =
|
|
|
38
39
|
}>;
|
|
39
40
|
|
|
40
41
|
type AgentClientDelegationEvidence = Readonly<{
|
|
41
|
-
actualModel: 'host-event' | 'unavailable';
|
|
42
|
-
actualReasoningEffort: 'host-event' | 'spawn-ack' | 'unavailable';
|
|
42
|
+
actualModel: 'host-event' | 'app-server' | 'unavailable';
|
|
43
|
+
actualReasoningEffort: 'host-event' | 'app-server' | 'spawn-ack' | 'unavailable';
|
|
43
44
|
}>;
|
|
44
45
|
|
|
45
46
|
type AgentClientSeedCommand = Readonly<{
|
|
@@ -50,12 +51,21 @@ type AgentClientSeedCommand = Readonly<{
|
|
|
50
51
|
target: string;
|
|
51
52
|
}>;
|
|
52
53
|
|
|
54
|
+
type AgentClientCustomCommandDescriptor = Readonly<{
|
|
55
|
+
target: string;
|
|
56
|
+
frontmatter: Readonly<Record<string, string | boolean>>;
|
|
57
|
+
argumentsToken?: string;
|
|
58
|
+
includeUsage?: boolean;
|
|
59
|
+
inheritDisableModelInvocation?: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
53
62
|
type AgentClientProjectDescriptor = Readonly<{
|
|
54
63
|
ownedPathPrefixes: readonly string[];
|
|
55
64
|
managed: readonly string[];
|
|
56
65
|
merged: readonly string[];
|
|
57
66
|
ejected: readonly string[];
|
|
58
67
|
seedCommands: readonly AgentClientSeedCommand[];
|
|
68
|
+
customCommand?: AgentClientCustomCommandDescriptor;
|
|
59
69
|
}>;
|
|
60
70
|
|
|
61
71
|
type AgentClientAdapter = Readonly<{
|
|
@@ -81,12 +91,59 @@ type AgentClientManifestEntry = Readonly<{
|
|
|
81
91
|
managed: readonly string[];
|
|
82
92
|
merged: readonly string[];
|
|
83
93
|
ejected: readonly string[];
|
|
94
|
+
customCommand?: AgentClientCustomCommandDescriptor;
|
|
84
95
|
}>;
|
|
85
96
|
|
|
86
97
|
const PROJECT_ASSET_CATEGORIES = ['managed', 'merged', 'ejected'] as const;
|
|
87
98
|
const MAX_SANDBOX_HOOK_TIMEOUT_MS = 300_000;
|
|
88
99
|
const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
89
100
|
|
|
101
|
+
function freezeSandboxImageDescriptor(
|
|
102
|
+
id: AgentClientId,
|
|
103
|
+
value: AgentClientSandboxImageDescriptor | undefined
|
|
104
|
+
): AgentClientSandboxImageDescriptor | undefined {
|
|
105
|
+
if (value === undefined) return undefined;
|
|
106
|
+
const keys = value && typeof value === 'object' && !Array.isArray(value)
|
|
107
|
+
? Object.keys(value)
|
|
108
|
+
: [];
|
|
109
|
+
const fragment = value?.dockerfileFragment;
|
|
110
|
+
const exclusions = value?.dotfilesExclusions;
|
|
111
|
+
const validExclusion = (entry: unknown): entry is string => {
|
|
112
|
+
if (
|
|
113
|
+
typeof entry !== 'string'
|
|
114
|
+
|| entry === ''
|
|
115
|
+
|| entry.startsWith('/')
|
|
116
|
+
|| entry.endsWith('/')
|
|
117
|
+
|| entry.includes('\\')
|
|
118
|
+
|| /[*?[\]]/.test(entry)
|
|
119
|
+
) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return entry.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..');
|
|
123
|
+
};
|
|
124
|
+
if (
|
|
125
|
+
!value
|
|
126
|
+
|| typeof value !== 'object'
|
|
127
|
+
|| Array.isArray(value)
|
|
128
|
+
|| keys.length === 0
|
|
129
|
+
|| keys.some((key) => !['dockerfileFragment', 'dotfilesExclusions'].includes(key))
|
|
130
|
+
|| (fragment !== undefined && (typeof fragment !== 'string' || !TOOL_ID_PATTERN.test(fragment)))
|
|
131
|
+
|| (exclusions !== undefined && (
|
|
132
|
+
!Array.isArray(exclusions)
|
|
133
|
+
|| exclusions.some((entry) => !validExclusion(entry))
|
|
134
|
+
|| new Set(exclusions).size !== exclusions.length
|
|
135
|
+
))
|
|
136
|
+
) {
|
|
137
|
+
throw new Error(`Agent Client '${id}' has an invalid sandbox image descriptor`);
|
|
138
|
+
}
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
...(fragment === undefined ? {} : { dockerfileFragment: fragment }),
|
|
141
|
+
...(exclusions === undefined
|
|
142
|
+
? {}
|
|
143
|
+
: { dotfilesExclusions: Object.freeze([...exclusions]) })
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
90
147
|
function freezeRecoveryProbe(
|
|
91
148
|
id: AgentClientId,
|
|
92
149
|
checkId: string,
|
|
@@ -274,8 +331,8 @@ function defineAgentClientAdapter(
|
|
|
274
331
|
const evidence = candidate.delegationEvidence;
|
|
275
332
|
if (
|
|
276
333
|
!evidence
|
|
277
|
-
|| !['host-event', 'unavailable'].includes(evidence.actualModel)
|
|
278
|
-
|| !['host-event', 'spawn-ack', 'unavailable'].includes(evidence.actualReasoningEffort)
|
|
334
|
+
|| !['host-event', 'app-server', 'unavailable'].includes(evidence.actualModel)
|
|
335
|
+
|| !['host-event', 'app-server', 'spawn-ack', 'unavailable'].includes(evidence.actualReasoningEffort)
|
|
279
336
|
) {
|
|
280
337
|
throw new Error(`Agent Client '${candidate.id}' has invalid delegation evidence`);
|
|
281
338
|
}
|
|
@@ -394,9 +451,68 @@ function defineAgentClientAdapter(
|
|
|
394
451
|
});
|
|
395
452
|
});
|
|
396
453
|
|
|
454
|
+
let customCommand: AgentClientCustomCommandDescriptor | undefined;
|
|
455
|
+
if (candidate.project.customCommand !== undefined) {
|
|
456
|
+
const descriptor = candidate.project.customCommand;
|
|
457
|
+
const placeholders = typeof descriptor?.target === 'string'
|
|
458
|
+
? [...descriptor.target.matchAll(/\$\{([^}]+)\}/g)].map((match) => match[1])
|
|
459
|
+
: [];
|
|
460
|
+
const targetProbe = typeof descriptor?.target === 'string'
|
|
461
|
+
? descriptor.target.replaceAll('${skillName}', 'skill')
|
|
462
|
+
: '';
|
|
463
|
+
const frontmatter = descriptor?.frontmatter;
|
|
464
|
+
if (
|
|
465
|
+
!descriptor
|
|
466
|
+
|| typeof descriptor !== 'object'
|
|
467
|
+
|| placeholders.length !== 1
|
|
468
|
+
|| placeholders[0] !== 'skillName'
|
|
469
|
+
|| targetProbe.includes('${')
|
|
470
|
+
|| !isProjectRelativeLiteral(targetProbe)
|
|
471
|
+
|| !paths.some((prefix) => targetProbe.startsWith(prefix))
|
|
472
|
+
|| !frontmatter
|
|
473
|
+
|| typeof frontmatter !== 'object'
|
|
474
|
+
|| Array.isArray(frontmatter)
|
|
475
|
+
|| Object.values(frontmatter).some((value) =>
|
|
476
|
+
typeof value !== 'string' && typeof value !== 'boolean'
|
|
477
|
+
)
|
|
478
|
+
|| (
|
|
479
|
+
descriptor.argumentsToken !== undefined
|
|
480
|
+
&& (
|
|
481
|
+
typeof descriptor.argumentsToken !== 'string'
|
|
482
|
+
|| descriptor.argumentsToken === ''
|
|
483
|
+
|| /[\r\n]/.test(descriptor.argumentsToken)
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
|| (
|
|
487
|
+
descriptor.includeUsage !== undefined
|
|
488
|
+
&& typeof descriptor.includeUsage !== 'boolean'
|
|
489
|
+
)
|
|
490
|
+
|| (
|
|
491
|
+
descriptor.inheritDisableModelInvocation !== undefined
|
|
492
|
+
&& typeof descriptor.inheritDisableModelInvocation !== 'boolean'
|
|
493
|
+
)
|
|
494
|
+
) {
|
|
495
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid custom command`);
|
|
496
|
+
}
|
|
497
|
+
customCommand = Object.freeze({
|
|
498
|
+
target: descriptor.target,
|
|
499
|
+
frontmatter: Object.freeze({ ...frontmatter }),
|
|
500
|
+
...(descriptor.argumentsToken === undefined
|
|
501
|
+
? {}
|
|
502
|
+
: { argumentsToken: descriptor.argumentsToken }),
|
|
503
|
+
...(descriptor.includeUsage === undefined
|
|
504
|
+
? {}
|
|
505
|
+
: { includeUsage: descriptor.includeUsage }),
|
|
506
|
+
...(descriptor.inheritDisableModelInvocation === undefined
|
|
507
|
+
? {}
|
|
508
|
+
: { inheritDisableModelInvocation: descriptor.inheritDisableModelInvocation })
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
397
512
|
if (!candidate.sandbox || typeof candidate.sandbox.createTool !== 'function') {
|
|
398
513
|
throw new Error(`Agent Client '${candidate.id}' requires a sandbox descriptor`);
|
|
399
514
|
}
|
|
515
|
+
const image = freezeSandboxImageDescriptor(candidate.id, candidate.sandbox.image);
|
|
400
516
|
const hookIds = new Set<string>();
|
|
401
517
|
const hooks = Object.freeze(candidate.sandbox.hooks.map((hook) => {
|
|
402
518
|
if (
|
|
@@ -515,9 +631,16 @@ function defineAgentClientAdapter(
|
|
|
515
631
|
project: Object.freeze({
|
|
516
632
|
ownedPathPrefixes: Object.freeze(paths),
|
|
517
633
|
...projectAssets,
|
|
518
|
-
seedCommands: Object.freeze(seedCommands)
|
|
634
|
+
seedCommands: Object.freeze(seedCommands),
|
|
635
|
+
...(customCommand === undefined ? {} : { customCommand })
|
|
519
636
|
}),
|
|
520
|
-
sandbox: Object.freeze({
|
|
637
|
+
sandbox: Object.freeze({
|
|
638
|
+
createTool,
|
|
639
|
+
aliases,
|
|
640
|
+
hooks,
|
|
641
|
+
recoveryChecks,
|
|
642
|
+
...(image === undefined ? {} : { image })
|
|
643
|
+
})
|
|
521
644
|
});
|
|
522
645
|
}
|
|
523
646
|
|
|
@@ -525,6 +648,7 @@ export { defineAgentClientAdapter };
|
|
|
525
648
|
export type {
|
|
526
649
|
AgentClientAdapter,
|
|
527
650
|
AgentClientCapabilities,
|
|
651
|
+
AgentClientCustomCommandDescriptor,
|
|
528
652
|
AgentClientDelegationEvidence,
|
|
529
653
|
AgentClientManifestEntry,
|
|
530
654
|
AgentClientModelSelectionContext,
|
|
@@ -2,7 +2,8 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { hostJoin } from '
|
|
5
|
+
import { hostJoin } from '../../sandbox/engines/wsl2-paths.ts';
|
|
6
|
+
import { redactCommandError } from '../../sandbox/redaction.ts';
|
|
6
7
|
|
|
7
8
|
type ExecFn = (file: string, args: string[], options?: Record<string, unknown>) => string | Buffer | void;
|
|
8
9
|
type ReadFn = (targetPath: string) => string;
|
|
@@ -112,12 +113,6 @@ type CredentialPayload = {
|
|
|
112
113
|
|
|
113
114
|
const LOCKED_PATTERN = /errSecInteractionNotAllowed|User interaction is not allowed/i;
|
|
114
115
|
const NOT_FOUND_PATTERN = /errSecItemNotFound|specified item could not be found/i;
|
|
115
|
-
const REDACTION_PATTERNS = [
|
|
116
|
-
{ pattern: /\{[^{}]*"claudeAiOauth"[\s\S]*?\}\s*\}/g, replacement: '[REDACTED credentials blob]' },
|
|
117
|
-
{ pattern: /sk-ant-[A-Za-z0-9_-]{20,}/g, replacement: '[REDACTED claude token]' },
|
|
118
|
-
{ pattern: /gh[psoru]_[A-Za-z0-9]{30,}/g, replacement: '[REDACTED github token]' },
|
|
119
|
-
{ pattern: /Bearer\s+[A-Za-z0-9._~+/=-]{20,}/gi, replacement: 'Bearer [REDACTED]' }
|
|
120
|
-
];
|
|
121
116
|
|
|
122
117
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
123
118
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -131,18 +126,8 @@ function errorMessage(error: unknown): string {
|
|
|
131
126
|
return error instanceof Error ? error.message : 'unknown error';
|
|
132
127
|
}
|
|
133
128
|
|
|
134
|
-
export function redactCommandError(text: unknown): string {
|
|
135
|
-
if (!text || typeof text !== 'string') {
|
|
136
|
-
return '';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return REDACTION_PATTERNS.reduce(
|
|
140
|
-
(result, { pattern, replacement }) => result.replace(pattern, replacement),
|
|
141
|
-
text
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
129
|
export const redactSecurityOutput = redactCommandError;
|
|
130
|
+
export { redactCommandError };
|
|
146
131
|
|
|
147
132
|
function extractStderrSafely(error: unknown): string {
|
|
148
133
|
const stderr = typeof error === 'object' && error !== null && 'stderr' in error
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import {
|
|
4
|
+
buildLockedGuidance,
|
|
5
|
+
discoverProjects,
|
|
6
|
+
formatCredentialWarnings,
|
|
7
|
+
formatRemaining,
|
|
8
|
+
reconcileClaudeCredentials,
|
|
9
|
+
redactCommandError,
|
|
10
|
+
validateClaudeCredentialsEnvOverride
|
|
11
|
+
} from './claude-code-credentials.ts';
|
|
12
|
+
import { runProbe } from '../../sandbox/shell.ts';
|
|
13
|
+
|
|
14
|
+
const USAGE = 'Usage: ai sandbox refresh';
|
|
15
|
+
|
|
16
|
+
type ReconcileOptions = NonNullable<Parameters<typeof reconcileClaudeCredentials>[1]>;
|
|
17
|
+
|
|
18
|
+
type RefreshDeps = Partial<ReconcileOptions> & {
|
|
19
|
+
spawnFn?: typeof runProbe;
|
|
20
|
+
discoverFn?: typeof discoverProjects;
|
|
21
|
+
writeStdout?: (chunk: string) => unknown;
|
|
22
|
+
writeStderr?: (chunk: string) => unknown;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function probeClaudeStatus(spawnFn: typeof runProbe = runProbe): { ok: boolean; stderr: string; error: string | null } {
|
|
26
|
+
const result = spawnFn('claude', ['/status'], {
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
timeout: 30_000
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
ok: result.status === 0,
|
|
33
|
+
stderr: result.stderr?.toString() ?? '',
|
|
34
|
+
error: result.error?.message ?? null
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runClaudeCodeRefresh(args: string[], deps: RefreshDeps = {}): Promise<number> {
|
|
39
|
+
const {
|
|
40
|
+
spawnFn = runProbe,
|
|
41
|
+
execFn,
|
|
42
|
+
readFn,
|
|
43
|
+
existsFn,
|
|
44
|
+
writeFn,
|
|
45
|
+
writeHostFn,
|
|
46
|
+
discoverFn = discoverProjects,
|
|
47
|
+
writeStdout = (chunk: string) => process.stdout.write(chunk),
|
|
48
|
+
writeStderr = (chunk: string) => process.stderr.write(chunk)
|
|
49
|
+
} = deps;
|
|
50
|
+
|
|
51
|
+
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
52
|
+
writeStdout(`${USAGE}\n`);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { positionals } = parseArgs({ args, allowPositionals: true, strict: true });
|
|
57
|
+
if (positionals.length > 0) {
|
|
58
|
+
throw new Error(USAGE);
|
|
59
|
+
}
|
|
60
|
+
validateClaudeCredentialsEnvOverride();
|
|
61
|
+
|
|
62
|
+
const home = homedir();
|
|
63
|
+
if (!home) {
|
|
64
|
+
throw new Error('sandbox: home directory is required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const projects = discoverFn(home);
|
|
68
|
+
if (projects.length === 0) {
|
|
69
|
+
writeStdout('No project credentials to refresh.\n');
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const reconcileOptions = { execFn, readFn, existsFn, writeFn, writeHostFn, projects };
|
|
74
|
+
let result = reconcileClaudeCredentials(home, reconcileOptions);
|
|
75
|
+
if (result.status === 'STALE_ACCESS' && result.authoritative === null) {
|
|
76
|
+
writeStdout('Host credentials appear stale; probing claude /status to trigger refresh...\n');
|
|
77
|
+
const probe = probeClaudeStatus(spawnFn);
|
|
78
|
+
if (!probe.ok) {
|
|
79
|
+
writeStderr(`Probe failed: ${redactCommandError(probe.stderr || probe.error || 'unknown error')}\n`);
|
|
80
|
+
writeStderr('Run "claude /login" on the host to renew credentials.\n');
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
writeStdout('Probe succeeded; re-inspecting host credentials.\n');
|
|
84
|
+
result = reconcileClaudeCredentials(home, reconcileOptions);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (result.status === 'MISSING') {
|
|
88
|
+
writeStderr('No Claude Code credentials found on host.\n');
|
|
89
|
+
writeStderr('Run "claude /login" on the host to authenticate.\n');
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (result.status === 'KEYCHAIN_LOCKED') {
|
|
94
|
+
writeStderr(`${buildLockedGuidance()}\n`);
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (result.status === 'KEYCHAIN_ERROR') {
|
|
99
|
+
writeStderr(`Host keychain error: ${redactCommandError(result.detail || 'unknown error')}\n`);
|
|
100
|
+
writeStderr(`${buildLockedGuidance()}\n`);
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (result.status === 'KEYCHAIN_WRITE_FAILED') {
|
|
105
|
+
writeStderr(`[host] keychain write failed: ${formatCredentialWarnings(result.warnings) || 'unknown error'}\n`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (result.status !== 'OK') {
|
|
110
|
+
writeStderr('Host credentials still invalid after probe; run "claude /login".\n');
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (result.authoritative && result.authoritative !== 'host' && result.hostWritten) {
|
|
115
|
+
writeStdout(`[host] reconciled from ${result.authoritative}\n`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const project of projects) {
|
|
119
|
+
const action = result.filesWritten.includes(project) ? 'updated' : 'unchanged';
|
|
120
|
+
writeStdout(`[${project}] ${action}; expires in ${formatRemaining(result.expiresAt)}\n`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const failure of result.fileErrors) {
|
|
124
|
+
writeStderr(`[${failure.project}] sync failed: ${failure.error}\n`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result.fileErrors.length > 0 ? 1 : 0;
|
|
128
|
+
}
|