@fitlab-ai/agent-infra 0.9.3 → 0.9.4
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 +2 -0
- package/README.zh-CN.md +2 -0
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +134 -123
- package/dist/lib/agent-clients/adapter.js +92 -2
- 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/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/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +45 -13
- 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 +122 -18
- 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 +109 -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 +44 -0
- package/dist/lib/sandbox/control/protocol.js +35 -0
- package/dist/lib/sandbox/control/server.js +109 -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 +103 -0
- package/dist/lib/task/activity-intent.js +257 -51
- package/dist/lib/task/commit-finalization.js +267 -0
- package/dist/lib/task/commit-intent.js +165 -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 +363 -28
- package/dist/lib/task/resolve-ref.js +41 -1
- package/lib/agent-clients/adapter.ts +126 -2
- 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/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/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +50 -14
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +22 -1
- package/lib/internal/task-orchestration.ts +81 -11
- 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 +126 -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 +55 -0
- package/lib/sandbox/control/protocol.ts +75 -0
- package/lib/sandbox/control/server.ts +117 -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 +143 -0
- package/lib/task/activity-intent.ts +350 -61
- package/lib/task/commit-finalization.ts +356 -0
- package/lib/task/commit-intent.ts +230 -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 +501 -33
- package/lib/task/resolve-ref.ts +46 -1
- package/package.json +1 -1
- 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 +2 -0
- package/templates/.agents/rules/task-management.zh-CN.md +2 -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 -11
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -11
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -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/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/.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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isRemovedHashShortIdInput } from '../task/short-id.js';
|
|
4
|
+
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
5
|
+
const SHORT_ID_RE = /^\d+$/;
|
|
6
|
+
function stripQuotes(value) {
|
|
7
|
+
return value.replace(/^(?:"([^"]*)"|'([^']*)')$/, '$1$2');
|
|
8
|
+
}
|
|
9
|
+
function taskBranch(taskMd, taskId) {
|
|
10
|
+
const content = fs.readFileSync(taskMd, 'utf8');
|
|
11
|
+
const value = content.match(/^branch:\s*(.+)$/m)?.[1]?.trim()
|
|
12
|
+
?? content.match(/^- \*\*(?:分支|Branch)\*\*:[ \t]*`?([^`\n]+)`?$/m)?.[1]?.trim();
|
|
13
|
+
if (!value)
|
|
14
|
+
throw new Error(`Task ${taskId} has no branch field in task.md`);
|
|
15
|
+
return stripQuotes(value);
|
|
16
|
+
}
|
|
17
|
+
function activeRegistry(repoRoot) {
|
|
18
|
+
const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', '.short-ids.json');
|
|
19
|
+
if (!fs.existsSync(registryPath))
|
|
20
|
+
return {};
|
|
21
|
+
const value = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
23
|
+
throw new Error('Active task short-id registry is invalid');
|
|
24
|
+
}
|
|
25
|
+
const ids = 'ids' in value && value.ids && typeof value.ids === 'object' && !Array.isArray(value.ids)
|
|
26
|
+
? value.ids
|
|
27
|
+
: value;
|
|
28
|
+
return Object.fromEntries(Object.entries(ids).filter((entry) => SHORT_ID_RE.test(entry[0]) && typeof entry[1] === 'string' && TASK_ID_RE.test(entry[1])));
|
|
29
|
+
}
|
|
30
|
+
function taskIdentity(repoRoot, taskId, registry) {
|
|
31
|
+
const taskMd = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
|
|
32
|
+
if (!fs.existsSync(taskMd))
|
|
33
|
+
throw new Error(`Active task not found: ${taskId}`);
|
|
34
|
+
const shortIds = Object.entries(registry).filter(([, id]) => id === taskId).map(([shortId]) => shortId);
|
|
35
|
+
if (shortIds.length !== 1) {
|
|
36
|
+
throw new Error(`Task ${taskId} must have exactly one active short id`);
|
|
37
|
+
}
|
|
38
|
+
return { mode: 'task-bound', taskId, shortId: shortIds[0] };
|
|
39
|
+
}
|
|
40
|
+
export function resolveSandboxTarget(requestedRef, repoRoot) {
|
|
41
|
+
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
42
|
+
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
43
|
+
}
|
|
44
|
+
const registry = activeRegistry(repoRoot);
|
|
45
|
+
if (SHORT_ID_RE.test(requestedRef)) {
|
|
46
|
+
const matchingShortIds = Object.keys(registry).filter((shortId) => shortId === requestedRef || Number(shortId) === Number(requestedRef));
|
|
47
|
+
if (matchingShortIds.length > 1) {
|
|
48
|
+
throw new Error(`Short id '${requestedRef}' is ambiguous in active task registry`);
|
|
49
|
+
}
|
|
50
|
+
const canonicalShortId = matchingShortIds[0];
|
|
51
|
+
const taskId = canonicalShortId ? registry[canonicalShortId] : undefined;
|
|
52
|
+
if (!taskId)
|
|
53
|
+
throw new Error(`Short id '${requestedRef}' not found in active task registry`);
|
|
54
|
+
const workspace = taskIdentity(repoRoot, taskId, registry);
|
|
55
|
+
return { requestedRef, branch: taskBranch(path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md'), taskId), workspace };
|
|
56
|
+
}
|
|
57
|
+
if (TASK_ID_RE.test(requestedRef)) {
|
|
58
|
+
const workspace = taskIdentity(repoRoot, requestedRef, registry);
|
|
59
|
+
return { requestedRef, branch: taskBranch(path.join(repoRoot, '.agents', 'workspace', 'active', requestedRef, 'task.md'), requestedRef), workspace };
|
|
60
|
+
}
|
|
61
|
+
const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
62
|
+
const matches = fs.existsSync(activeRoot)
|
|
63
|
+
? fs.readdirSync(activeRoot, { withFileTypes: true }).flatMap((entry) => {
|
|
64
|
+
if (!entry.isDirectory() || !TASK_ID_RE.test(entry.name))
|
|
65
|
+
return [];
|
|
66
|
+
const taskMd = path.join(activeRoot, entry.name, 'task.md');
|
|
67
|
+
if (!fs.existsSync(taskMd))
|
|
68
|
+
return [];
|
|
69
|
+
return taskBranch(taskMd, entry.name) === requestedRef ? [entry.name] : [];
|
|
70
|
+
})
|
|
71
|
+
: [];
|
|
72
|
+
if (matches.length > 1) {
|
|
73
|
+
throw new Error(`SANDBOX_TASK_IDENTITY_AMBIGUOUS: branch '${requestedRef}' is bound to multiple active tasks`);
|
|
74
|
+
}
|
|
75
|
+
if (matches.length === 1) {
|
|
76
|
+
const taskId = matches[0];
|
|
77
|
+
return { requestedRef, branch: requestedRef, workspace: taskIdentity(repoRoot, taskId, registry) };
|
|
78
|
+
}
|
|
79
|
+
return { requestedRef, branch: requestedRef, workspace: { mode: 'branch-only' } };
|
|
80
|
+
}
|
|
81
|
+
export function parseSandboxWorkspaceIdentity(labels, keys) {
|
|
82
|
+
const mode = labels[keys.mode];
|
|
83
|
+
const taskId = labels[keys.taskId];
|
|
84
|
+
if (mode === 'branch-only' && taskId === undefined)
|
|
85
|
+
return { mode: 'branch-only' };
|
|
86
|
+
if (mode === 'task-bound' && typeof taskId === 'string' && TASK_ID_RE.test(taskId)) {
|
|
87
|
+
return { mode: 'task-bound', taskId };
|
|
88
|
+
}
|
|
89
|
+
return { mode: 'legacy-invalid' };
|
|
90
|
+
}
|
|
91
|
+
export function sameSandboxWorkspaceIdentity(left, right) {
|
|
92
|
+
return left.mode === right.mode
|
|
93
|
+
&& (left.mode !== 'task-bound' || (right.mode === 'task-bound' && left.taskId === right.taskId));
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=workspace-identity.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export function sandboxWorkspaceViewPaths(params) {
|
|
5
|
+
const identityKey = params.identity.mode === 'task-bound'
|
|
6
|
+
? `task-bound:${params.identity.taskId}`
|
|
7
|
+
: 'branch-only';
|
|
8
|
+
const digest = createHash('sha256').update(identityKey).digest('hex').slice(0, 16);
|
|
9
|
+
const projectRoot = path.resolve(params.base, params.project);
|
|
10
|
+
const root = path.resolve(projectRoot, params.container, digest);
|
|
11
|
+
return {
|
|
12
|
+
root,
|
|
13
|
+
taskMountPath: params.identity.mode === 'task-bound'
|
|
14
|
+
? path.join(root, 'active', params.identity.taskId)
|
|
15
|
+
: null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function sandboxControlPaths(params) {
|
|
19
|
+
const identityKey = params.identity.mode === 'task-bound'
|
|
20
|
+
? `task-bound:${params.identity.taskId}`
|
|
21
|
+
: 'branch-only';
|
|
22
|
+
const digest = createHash('sha256').update(identityKey).digest('hex').slice(0, 16);
|
|
23
|
+
const root = path.resolve(params.base, params.project, params.container, digest);
|
|
24
|
+
return { root, channelDir: path.join(root, 'channel'), manifestPath: path.join(root, 'manifest.json') };
|
|
25
|
+
}
|
|
26
|
+
function assertSafeDirectory(directory, expectedBase) {
|
|
27
|
+
const relative = path.relative(expectedBase, directory);
|
|
28
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
29
|
+
throw new Error(`Sandbox workspace view escapes its configured root: ${directory}`);
|
|
30
|
+
}
|
|
31
|
+
if (fs.existsSync(directory) && fs.lstatSync(directory).isSymbolicLink()) {
|
|
32
|
+
throw new Error(`Sandbox workspace view must not be a symbolic link: ${directory}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
36
|
+
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
37
|
+
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
38
|
+
const activeRoot = fs.realpathSync.native(path.join(repoRoot, '.agents', 'workspace', 'active'));
|
|
39
|
+
const source = path.join(activeRoot, taskId);
|
|
40
|
+
const stat = fs.lstatSync(source);
|
|
41
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
42
|
+
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} must be a real directory`);
|
|
43
|
+
}
|
|
44
|
+
const canonical = fs.realpathSync.native(source);
|
|
45
|
+
if (path.dirname(canonical) !== activeRoot) {
|
|
46
|
+
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} escapes the active workspace`);
|
|
47
|
+
}
|
|
48
|
+
return canonical;
|
|
49
|
+
}
|
|
50
|
+
export function materializeSandboxWorkspaceView(params) {
|
|
51
|
+
const projectRoot = path.resolve(params.base, params.project);
|
|
52
|
+
const { root, taskMountPath } = sandboxWorkspaceViewPaths(params);
|
|
53
|
+
assertSafeDirectory(projectRoot, path.resolve(params.base));
|
|
54
|
+
assertSafeDirectory(root, projectRoot);
|
|
55
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
56
|
+
for (const state of ['active', 'completed', 'blocked', 'archive']) {
|
|
57
|
+
const stateDir = path.join(root, state);
|
|
58
|
+
assertSafeDirectory(stateDir, root);
|
|
59
|
+
fs.rmSync(stateDir, { recursive: true, force: true });
|
|
60
|
+
fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
61
|
+
}
|
|
62
|
+
const active = path.join(root, 'active');
|
|
63
|
+
const registry = {
|
|
64
|
+
version: 1,
|
|
65
|
+
ids: params.identity.mode === 'task-bound'
|
|
66
|
+
? { [params.identity.shortId]: params.identity.taskId }
|
|
67
|
+
: {}
|
|
68
|
+
};
|
|
69
|
+
fs.writeFileSync(path.join(active, '.short-ids.json'), `${JSON.stringify(registry)}\n`, {
|
|
70
|
+
encoding: 'utf8',
|
|
71
|
+
mode: 0o600
|
|
72
|
+
});
|
|
73
|
+
if (taskMountPath) {
|
|
74
|
+
assertSafeDirectory(taskMountPath, active);
|
|
75
|
+
fs.mkdirSync(taskMountPath, { recursive: true, mode: 0o700 });
|
|
76
|
+
}
|
|
77
|
+
return { root, taskMountPath };
|
|
78
|
+
}
|
|
79
|
+
export function materializeSandboxControl(params) {
|
|
80
|
+
const { root, channelDir, manifestPath } = sandboxControlPaths(params);
|
|
81
|
+
assertSafeDirectory(root, path.resolve(params.base));
|
|
82
|
+
for (const queue of ['requests', 'responses']) {
|
|
83
|
+
const directory = path.join(channelDir, queue);
|
|
84
|
+
assertSafeDirectory(directory, root);
|
|
85
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
86
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
87
|
+
}
|
|
88
|
+
const token = randomBytes(32).toString('hex');
|
|
89
|
+
const manifest = {
|
|
90
|
+
version: 1,
|
|
91
|
+
repoRoot: fs.realpathSync.native(params.repoRoot),
|
|
92
|
+
project: params.project,
|
|
93
|
+
container: params.container,
|
|
94
|
+
branch: params.branch,
|
|
95
|
+
mode: params.identity.mode,
|
|
96
|
+
taskId: params.identity.mode === 'task-bound' ? params.identity.taskId : null,
|
|
97
|
+
token,
|
|
98
|
+
channelDir
|
|
99
|
+
};
|
|
100
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest)}\n`, { mode: 0o600 });
|
|
101
|
+
return { channelDir, manifestPath, token };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=workspace-view.js.map
|
|
@@ -1,73 +1,260 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import
|
|
2
|
+
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
3
3
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
4
4
|
import { locateActivityLog, appendActivityEntry } from './activity-log.js';
|
|
5
|
-
import { buildArtifactLinkSection, parseArtifactName } from './artifact-lifecycle.js';
|
|
5
|
+
import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts, parseArtifactName } from './artifact-lifecycle.js';
|
|
6
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
6
7
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
8
|
+
const HEAD_RE = /^[0-9a-f]{40}$/;
|
|
9
|
+
const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
|
|
10
|
+
const VERDICTS = new Set(['approved', 'changes-requested', 'commented']);
|
|
11
|
+
const OUTCOMES = new Set(['aborted', 'superseded']);
|
|
12
|
+
const SUCCESS_STATUSES = new Set(['applied', 'no-op']);
|
|
13
|
+
const ARTIFACT_STATUSES = new Set([
|
|
14
|
+
'pending', 'applied', 'no-op', 'blocked', 'failed', 'aborted', 'superseded'
|
|
15
|
+
]);
|
|
7
16
|
function failed(intent, code, message, taskId = null) {
|
|
8
17
|
return {
|
|
9
|
-
status: 'failed', changed: false, intent:
|
|
10
|
-
artifact: intent.artifact
|
|
18
|
+
status: 'failed', changed: false, intent: intent.kind, taskId,
|
|
19
|
+
artifact: intent.artifact, operations: [], error: { code, message }
|
|
11
20
|
};
|
|
12
21
|
}
|
|
13
|
-
function
|
|
14
|
-
return
|
|
22
|
+
function inspectionFailed(code, message, taskId = null) {
|
|
23
|
+
return {
|
|
24
|
+
status: 'failed', changed: false, intent: 'pr-review-inspect', taskId,
|
|
25
|
+
snapshot: null, error: { code, message }
|
|
26
|
+
};
|
|
15
27
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
28
|
+
function parseArtifactRecord(identity) {
|
|
29
|
+
let content;
|
|
30
|
+
try {
|
|
31
|
+
content = fs.readFileSync(identity.path, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return { code: 'ACTIVITY_ARTIFACT_READ_FAILED', message: error instanceof Error ? error.message : String(error) };
|
|
35
|
+
}
|
|
36
|
+
const heads = [...content.matchAll(/^- \*\*(?:被审 head SHA|Reviewed Head SHA)\*\*[::]\s*([0-9a-f]{40})\s*$/gm)];
|
|
37
|
+
if (heads.length !== 1) {
|
|
38
|
+
return { code: 'ACTIVITY_ARTIFACT_INVALID', message: `${identity.name} must contain exactly one reviewed head SHA` };
|
|
39
|
+
}
|
|
40
|
+
const statuses = [...content.matchAll(/^- \*\*(?:正式 Review 状态|Formal Review Status)\*\*[::][ \t]*([a-z-]+)[^\r\n]*$/gm)];
|
|
41
|
+
const status = statuses[0]?.[1];
|
|
42
|
+
if (statuses.length !== 1 || !status || !ARTIFACT_STATUSES.has(status)) {
|
|
43
|
+
return { code: 'ACTIVITY_ARTIFACT_INVALID', message: `${identity.name} must contain exactly one supported formal review status` };
|
|
44
|
+
}
|
|
45
|
+
return { identity, head: heads[0][1], status: status };
|
|
46
|
+
}
|
|
47
|
+
function toIdentity(state, terminal = false) {
|
|
48
|
+
return {
|
|
49
|
+
round: state.identity.round,
|
|
50
|
+
artifact: state.identity.name,
|
|
51
|
+
head: state.head,
|
|
52
|
+
agent: terminal ? state.terminalAgent : state.startedAgent,
|
|
53
|
+
artifactStatus: state.status,
|
|
54
|
+
note: terminal ? state.terminalNote : null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function readSnapshot(taskRef, repoRoot) {
|
|
58
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot });
|
|
25
59
|
if (!resolved.ok)
|
|
26
|
-
return
|
|
60
|
+
return { ok: false, code: resolved.code, message: resolved.message, taskId: resolved.taskId };
|
|
27
61
|
if (resolved.state !== 'active') {
|
|
28
|
-
return
|
|
62
|
+
return { ok: false, code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active`, taskId: resolved.taskId };
|
|
29
63
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!step || !agent || !note || /[\r\n]/.test(intent.step) || /[\r\n]/.test(intent.agent) || /[\r\n]/.test(intent.note)) {
|
|
34
|
-
return failed(intent, 'ACTIVITY_INTENT_INVALID', 'step, agent and note must be non-empty single-line values', resolved.taskId);
|
|
35
|
-
}
|
|
36
|
-
let artifact = null;
|
|
37
|
-
if (intent.artifact !== undefined) {
|
|
38
|
-
artifact = parseArtifactName(intent.artifact);
|
|
39
|
-
if (!artifact || artifact.family !== 'pr-review') {
|
|
40
|
-
return failed(intent, 'ACTIVITY_ARTIFACT_INVALID', `artifact '${intent.artifact}' is not a canonical pr-review artifact`, resolved.taskId);
|
|
41
|
-
}
|
|
64
|
+
const inventory = inspectTaskArtifacts(taskRef, 'pr-review', { repoRoot: resolved.repoRoot });
|
|
65
|
+
if (inventory.status === 'failed') {
|
|
66
|
+
return { ok: false, code: inventory.error?.code ?? 'ACTIVITY_ARTIFACT_INVALID', message: inventory.error?.message ?? 'artifact inspection failed', taskId: resolved.taskId };
|
|
42
67
|
}
|
|
68
|
+
const topology = assertWritableInventory(inventory);
|
|
69
|
+
if (topology)
|
|
70
|
+
return { ok: false, code: topology.code, message: topology.message, taskId: resolved.taskId };
|
|
43
71
|
let content;
|
|
44
72
|
try {
|
|
45
73
|
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
46
74
|
}
|
|
47
75
|
catch (error) {
|
|
48
|
-
return
|
|
76
|
+
return { ok: false, code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), taskId: resolved.taskId };
|
|
77
|
+
}
|
|
78
|
+
const activity = locateActivityLog(content);
|
|
79
|
+
if (!activity)
|
|
80
|
+
return { ok: false, code: 'ACTIVITY_SECTION_MISSING', message: 'task has no unique Activity Log section', taskId: resolved.taskId };
|
|
81
|
+
const knownRounds = new Set(inventory.artifacts.map((artifact) => artifact.round));
|
|
82
|
+
for (const entry of activity.entries) {
|
|
83
|
+
const match = REVIEW_ACTION_RE.exec(entry.step);
|
|
84
|
+
if (match && !knownRounds.has(Number(match[1]))) {
|
|
85
|
+
return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `Activity Log round ${match[1]} has no canonical pr-review artifact`, taskId: resolved.taskId };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const states = [];
|
|
89
|
+
for (const identity of inventory.artifacts) {
|
|
90
|
+
const parsed = parseArtifactRecord(identity);
|
|
91
|
+
if ('code' in parsed)
|
|
92
|
+
return { ok: false, ...parsed, taskId: resolved.taskId };
|
|
93
|
+
const base = `Review PR (Round ${identity.round})`;
|
|
94
|
+
const started = activity.entries.filter((entry) => entry.step === `${base} [started]`);
|
|
95
|
+
const terminal = activity.entries.filter((entry) => entry.step === base);
|
|
96
|
+
if (started.length > 1 || terminal.length > 1) {
|
|
97
|
+
return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `${base} has duplicate started or terminal rows`, taskId: resolved.taskId };
|
|
98
|
+
}
|
|
99
|
+
if (terminal.length === 1 && started.length === 0 && !terminal[0].note.startsWith('receipt ')) {
|
|
100
|
+
return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `${base} has a terminal row without a matching started row`, taskId: resolved.taskId };
|
|
101
|
+
}
|
|
102
|
+
if (terminal.length === 1) {
|
|
103
|
+
const note = terminal[0].note;
|
|
104
|
+
const terminalMatchesStatus = (note.startsWith('Verdict: ') && SUCCESS_STATUSES.has(parsed.status))
|
|
105
|
+
|| (note.startsWith('Outcome: Aborted, ') && parsed.status === 'aborted')
|
|
106
|
+
|| (note.startsWith('Outcome: Superseded, ') && parsed.status === 'superseded')
|
|
107
|
+
|| (note.startsWith('receipt ') && SUCCESS_STATUSES.has(parsed.status));
|
|
108
|
+
if (!terminalMatchesStatus) {
|
|
109
|
+
return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `${base} terminal note conflicts with artifact status ${parsed.status}`, taskId: resolved.taskId };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
states.push({
|
|
113
|
+
...parsed,
|
|
114
|
+
startedAgent: started[0]?.agent ?? null,
|
|
115
|
+
terminalAgent: terminal[0]?.agent ?? null,
|
|
116
|
+
terminalNote: terminal[0]?.note ?? null
|
|
117
|
+
});
|
|
49
118
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
119
|
+
const openStates = states.filter((state) => state.startedAgent !== null && state.terminalNote === null);
|
|
120
|
+
const preparedStates = states.filter((state) => state.status === 'pending' && state.startedAgent === null && state.terminalNote === null);
|
|
121
|
+
if (openStates.length > 1 || preparedStates.length > 1) {
|
|
122
|
+
return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: 'multiple prepared or open PR review rounds exist', taskId: resolved.taskId };
|
|
123
|
+
}
|
|
124
|
+
const terminalStates = states.filter((state) => state.terminalNote !== null);
|
|
125
|
+
const successfulStates = states.filter((state) => SUCCESS_STATUSES.has(state.status));
|
|
126
|
+
const latestTerminal = terminalStates.at(-1) ?? null;
|
|
127
|
+
const latestSuccessful = successfulStates.at(-1) ?? null;
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
taskId: resolved.taskId,
|
|
131
|
+
taskDir: resolved.taskDir,
|
|
132
|
+
taskMdPath: resolved.taskMdPath,
|
|
133
|
+
repoRoot: resolved.repoRoot,
|
|
134
|
+
content,
|
|
135
|
+
states,
|
|
136
|
+
snapshot: {
|
|
137
|
+
nextRound: inventory.next?.round ?? 1,
|
|
138
|
+
prepared: preparedStates[0] ? toIdentity(preparedStates[0]) : null,
|
|
139
|
+
open: openStates[0] ? toIdentity(openStates[0]) : null,
|
|
140
|
+
latestTerminal: latestTerminal ? toIdentity(latestTerminal, true) : null,
|
|
141
|
+
latestSuccessful: latestSuccessful ? toIdentity(latestSuccessful, true) : null
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function inspectPrReviewActivity(intent, options = {}) {
|
|
146
|
+
const state = readSnapshot(intent.taskRef, options.repoRoot);
|
|
147
|
+
if (!state.ok)
|
|
148
|
+
return inspectionFailed(state.code, state.message, state.taskId);
|
|
149
|
+
return { status: 'ready', changed: false, intent: intent.kind, taskId: state.taskId, snapshot: state.snapshot, error: null };
|
|
150
|
+
}
|
|
151
|
+
function validateIntent(intent) {
|
|
152
|
+
const agent = normalizeAgentToken(intent.agent);
|
|
153
|
+
if (!agent)
|
|
154
|
+
return { code: 'ACTIVITY_INTENT_INVALID', message: 'agent must be a supported collaborator token' };
|
|
155
|
+
const artifact = parseArtifactName(intent.artifact);
|
|
156
|
+
if (!artifact || artifact.family !== 'pr-review') {
|
|
157
|
+
return { code: 'ACTIVITY_ARTIFACT_INVALID', message: `artifact '${intent.artifact}' is not a canonical pr-review artifact` };
|
|
158
|
+
}
|
|
159
|
+
if (!HEAD_RE.test(intent.head))
|
|
160
|
+
return { code: 'ACTIVITY_INTENT_INVALID', message: 'head must be a lowercase 40-character SHA' };
|
|
161
|
+
if (intent.kind === 'pr-review-complete') {
|
|
162
|
+
if (!VERDICTS.has(intent.verdict))
|
|
163
|
+
return { code: 'ACTIVITY_INTENT_INVALID', message: 'verdict is invalid' };
|
|
164
|
+
if (![intent.blockers, intent.major, intent.minor].every((value) => Number.isSafeInteger(value) && value >= 0)) {
|
|
165
|
+
return { code: 'ACTIVITY_INTENT_INVALID', message: 'finding counts must be non-negative safe integers' };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (intent.kind === 'pr-review-terminate') {
|
|
169
|
+
if (!OUTCOMES.has(intent.outcome))
|
|
170
|
+
return { code: 'ACTIVITY_INTENT_INVALID', message: 'outcome is invalid' };
|
|
171
|
+
if (!intent.reason.trim() || /[\r\n]/.test(intent.reason) || intent.reason !== intent.reason.trim()) {
|
|
172
|
+
return { code: 'ACTIVITY_INTENT_INVALID', message: 'reason must be a non-empty trimmed single-line value' };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { agent, artifact };
|
|
176
|
+
}
|
|
177
|
+
function verdictLabel(verdict) {
|
|
178
|
+
if (verdict === 'approved')
|
|
179
|
+
return 'Approved';
|
|
180
|
+
if (verdict === 'changes-requested')
|
|
181
|
+
return 'Changes Requested';
|
|
182
|
+
return 'Commented';
|
|
183
|
+
}
|
|
184
|
+
function outcomeLabel(outcome) {
|
|
185
|
+
return outcome === 'aborted' ? 'Aborted' : 'Superseded';
|
|
186
|
+
}
|
|
187
|
+
function terminalNote(intent) {
|
|
188
|
+
if (intent.kind === 'pr-review-complete') {
|
|
189
|
+
return `Verdict: ${verdictLabel(intent.verdict)}, blockers: ${intent.blockers}, major: ${intent.major}, minor: ${intent.minor} → ${intent.artifact}`;
|
|
190
|
+
}
|
|
191
|
+
return `Outcome: ${outcomeLabel(intent.outcome)}, reason: ${intent.reason} → ${intent.artifact}`;
|
|
192
|
+
}
|
|
193
|
+
function applyLocked(intent, normalizedAgent, options) {
|
|
194
|
+
const state = readSnapshot(intent.taskRef, options.repoRoot);
|
|
195
|
+
if (!state.ok)
|
|
196
|
+
return failed(intent, state.code, state.message, state.taskId);
|
|
197
|
+
const parsed = parseArtifactName(intent.artifact);
|
|
198
|
+
const roundState = state.states.find((item) => item.identity.name === parsed.name);
|
|
199
|
+
if (!roundState)
|
|
200
|
+
return failed(intent, 'ACTIVITY_ARTIFACT_INVALID', `artifact '${intent.artifact}' is not landed`, state.taskId);
|
|
201
|
+
if (roundState.head !== intent.head) {
|
|
202
|
+
return failed(intent, 'ACTIVITY_IDENTITY_MISMATCH', `head does not match ${intent.artifact}`, state.taskId);
|
|
203
|
+
}
|
|
204
|
+
const base = `Review PR (Round ${parsed.round})`;
|
|
205
|
+
let step;
|
|
206
|
+
let note;
|
|
207
|
+
let linkArtifact = false;
|
|
208
|
+
if (intent.kind === 'pr-review-start') {
|
|
209
|
+
if (roundState.terminalNote !== null) {
|
|
210
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${base} is already terminal`, state.taskId);
|
|
211
|
+
}
|
|
212
|
+
if (roundState.startedAgent !== null) {
|
|
213
|
+
return { status: 'no-op', changed: false, intent: intent.kind, taskId: state.taskId, artifact: intent.artifact, operations: [], error: null };
|
|
214
|
+
}
|
|
215
|
+
if (roundState.status !== 'pending') {
|
|
216
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} must be pending before start`, state.taskId);
|
|
217
|
+
}
|
|
218
|
+
if (state.snapshot.open && state.snapshot.open.artifact !== intent.artifact) {
|
|
219
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `another PR review round is open: ${state.snapshot.open.artifact}`, state.taskId);
|
|
220
|
+
}
|
|
221
|
+
step = `${base} [started]`;
|
|
222
|
+
note = 'started';
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
note = terminalNote(intent);
|
|
226
|
+
if (roundState.terminalNote !== null) {
|
|
227
|
+
if (roundState.terminalNote === note) {
|
|
228
|
+
return { status: 'no-op', changed: false, intent: intent.kind, taskId: state.taskId, artifact: intent.artifact, operations: [], error: null };
|
|
229
|
+
}
|
|
230
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${base} has a different terminal payload`, state.taskId);
|
|
231
|
+
}
|
|
232
|
+
if (roundState.startedAgent === null) {
|
|
233
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${base} has no matching started row`, state.taskId);
|
|
234
|
+
}
|
|
235
|
+
if (intent.kind === 'pr-review-complete' && !SUCCESS_STATUSES.has(roundState.status)) {
|
|
236
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} must be applied or no-op before completion`, state.taskId);
|
|
237
|
+
}
|
|
238
|
+
if (intent.kind === 'pr-review-terminate' && roundState.status !== intent.outcome) {
|
|
239
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} status must match outcome ${intent.outcome}`, state.taskId);
|
|
240
|
+
}
|
|
241
|
+
step = base;
|
|
242
|
+
linkArtifact = true;
|
|
53
243
|
}
|
|
54
244
|
let metadata;
|
|
55
245
|
try {
|
|
56
246
|
metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
|
|
57
247
|
}
|
|
58
248
|
catch (error) {
|
|
59
|
-
return failed(intent, 'METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error),
|
|
60
|
-
}
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
size: 0,
|
|
69
|
-
mtimeMs: 0
|
|
70
|
-
});
|
|
249
|
+
return failed(intent, 'METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), state.taskId);
|
|
250
|
+
}
|
|
251
|
+
const section = locateActivityLog(state.content);
|
|
252
|
+
const mutations = [{
|
|
253
|
+
kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading,
|
|
254
|
+
body: appendActivityEntry(section, { time: metadata.timestamp, step, agent: normalizedAgent, note })
|
|
255
|
+
}];
|
|
256
|
+
if (linkArtifact) {
|
|
257
|
+
const link = buildArtifactLinkSection(state.content, roundState.identity);
|
|
71
258
|
mutations.push({ kind: 'section', aliases: link.aliases, heading: link.heading, body: link.body });
|
|
72
259
|
}
|
|
73
260
|
const writeResult = writeTask({
|
|
@@ -78,10 +265,10 @@ export function applyActivityAppendIntent(intent, options = {}) {
|
|
|
78
265
|
}, {
|
|
79
266
|
...options,
|
|
80
267
|
taskLocation: {
|
|
81
|
-
repoRoot:
|
|
82
|
-
taskId:
|
|
83
|
-
taskMdPath:
|
|
84
|
-
state:
|
|
268
|
+
repoRoot: state.repoRoot,
|
|
269
|
+
taskId: state.taskId,
|
|
270
|
+
taskMdPath: state.taskMdPath,
|
|
271
|
+
state: 'active'
|
|
85
272
|
},
|
|
86
273
|
metadataProvider: () => metadata
|
|
87
274
|
});
|
|
@@ -91,11 +278,30 @@ export function applyActivityAppendIntent(intent, options = {}) {
|
|
|
91
278
|
return {
|
|
92
279
|
status: writeResult.status,
|
|
93
280
|
changed: writeResult.changed,
|
|
94
|
-
intent:
|
|
281
|
+
intent: intent.kind,
|
|
95
282
|
taskId: writeResult.taskId,
|
|
96
|
-
artifact: intent.artifact
|
|
283
|
+
artifact: intent.artifact,
|
|
97
284
|
operations: writeResult.operations,
|
|
98
285
|
error: null
|
|
99
286
|
};
|
|
100
287
|
}
|
|
288
|
+
export function applyPrReviewActivityIntent(intent, options = {}) {
|
|
289
|
+
const validated = validateIntent(intent);
|
|
290
|
+
if ('code' in validated)
|
|
291
|
+
return failed(intent, validated.code, validated.message);
|
|
292
|
+
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
293
|
+
if (!resolved.ok)
|
|
294
|
+
return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
295
|
+
if (resolved.state !== 'active') {
|
|
296
|
+
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity:${intent.kind}`, () => applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot }), options.lockOptions);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
if (error instanceof TaskExecutionLockError)
|
|
303
|
+
return failed(intent, error.code, error.message, resolved.taskId);
|
|
304
|
+
return failed(intent, 'ACTIVITY_LOCK_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
101
307
|
//# sourceMappingURL=activity-intent.js.map
|