@fitlab-ai/agent-infra 0.9.1 → 0.9.2
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 +16 -4
- package/README.zh-CN.md +16 -4
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +15 -0
- package/dist/lib/agent-client.js +163 -0
- package/dist/lib/agent-clients/adapter.js +294 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
- package/dist/lib/agent-clients/adapters/codex.js +54 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/index.js +20 -0
- package/dist/lib/agent-clients/adapters/opencode.js +64 -0
- package/dist/lib/agent-clients/config.js +177 -0
- package/dist/lib/agent-clients/custom-tuis.js +99 -0
- package/dist/lib/agent-clients/invocation.js +9 -0
- package/dist/lib/agent-clients/next-steps.js +38 -0
- package/dist/lib/agent-clients/project-assets.js +45 -0
- package/dist/lib/agent-clients/reconcile.js +235 -0
- package/dist/lib/agent-clients/registry.js +72 -0
- package/dist/lib/agent-clients/schema.js +57 -0
- package/dist/lib/agent-clients/types.js +29 -0
- package/dist/lib/builtin-tuis.js +9 -14
- package/dist/lib/defaults.json +85 -73
- package/dist/lib/git/review-snapshot.js +25 -10
- package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
- package/dist/lib/init.js +47 -91
- package/dist/lib/internal/agent-client.js +117 -0
- package/dist/lib/internal/git-workflow.js +1 -1
- package/dist/lib/internal/release-workflow.js +71 -16
- package/dist/lib/internal/task-orchestration.js +151 -0
- package/dist/lib/internal/task-review.js +65 -0
- package/dist/lib/merge.js +2 -1
- package/dist/lib/platform/adapters.js +38 -1
- package/dist/lib/platform/change-request-git-evidence.js +113 -0
- package/dist/lib/platform/merged-pr-equivalence.js +41 -11
- package/dist/lib/platform/pr-checks.js +38 -10
- package/dist/lib/platform/pull-requests.js +110 -6
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +8 -10
- package/dist/lib/prompt.js +15 -3
- package/dist/lib/release/channels.js +4 -1
- package/dist/lib/release/workflow.js +4 -2
- package/dist/lib/remove-dir.js +25 -0
- package/dist/lib/run/tui.js +8 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +76 -180
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +3 -2
- package/dist/lib/sandbox/config.js +21 -4
- package/dist/lib/sandbox/constants.js +3 -0
- package/dist/lib/sandbox/dockerfile.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/shell.js +1 -1
- package/dist/lib/sandbox/tool-types.js +8 -0
- package/dist/lib/sandbox/tools.js +2 -126
- package/dist/lib/task/artifact-lifecycle.js +1 -49
- package/dist/lib/task/commands/decisions.js +50 -20
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +100 -1
- package/dist/lib/task/orchestration.js +420 -0
- package/dist/lib/task/review-artifacts.js +143 -59
- package/dist/lib/task/review-finalization.js +166 -0
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +156 -28
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +3 -1
- package/dist/lib/task/workspace-snapshot.js +34 -0
- package/dist/lib/update.js +42 -161
- package/lib/agent-client.ts +214 -0
- package/lib/agent-clients/adapter.ts +461 -0
- package/lib/agent-clients/adapters/claude-code.ts +70 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
- package/lib/agent-clients/adapters/codex.ts +58 -0
- package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
- package/lib/agent-clients/adapters/index.ts +27 -0
- package/lib/agent-clients/adapters/opencode.ts +66 -0
- package/lib/agent-clients/config.ts +278 -0
- package/lib/agent-clients/custom-tuis.ts +149 -0
- package/lib/agent-clients/invocation.ts +19 -0
- package/lib/agent-clients/next-steps.ts +72 -0
- package/lib/agent-clients/project-assets.ts +96 -0
- package/lib/agent-clients/reconcile.ts +324 -0
- package/lib/agent-clients/registry.ts +144 -0
- package/lib/agent-clients/schema.ts +62 -0
- package/lib/agent-clients/types.ts +78 -0
- package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
- package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
- package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
- package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
- package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
- package/lib/builtin-tuis.ts +16 -15
- package/lib/defaults.json +24 -12
- package/lib/git/review-snapshot.ts +39 -9
- package/lib/git/reviewed-commit-equivalence.ts +135 -0
- package/lib/init.ts +53 -121
- package/lib/internal/agent-client.ts +131 -0
- package/lib/internal/git-workflow.ts +1 -1
- package/lib/internal/release-workflow.ts +68 -16
- package/lib/internal/task-orchestration.ts +132 -0
- package/lib/internal/task-review.ts +65 -0
- package/lib/merge.ts +2 -1
- package/lib/platform/adapters.ts +142 -3
- package/lib/platform/change-request-git-evidence.ts +159 -0
- package/lib/platform/merged-pr-equivalence.ts +51 -14
- package/lib/platform/pr-checks.ts +50 -22
- package/lib/platform/pull-requests.ts +134 -25
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +12 -10
- package/lib/prompt.ts +16 -3
- package/lib/release/channels.ts +4 -1
- package/lib/release/workflow.ts +4 -3
- package/lib/remove-dir.ts +28 -0
- package/lib/run/tui.ts +9 -1
- package/lib/sandbox/agent-client-reconciler.ts +378 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +111 -192
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rm.ts +3 -2
- package/lib/sandbox/config.ts +31 -4
- package/lib/sandbox/constants.ts +6 -0
- package/lib/sandbox/dockerfile.ts +2 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/tool-types.ts +143 -0
- package/lib/sandbox/tools.ts +5 -157
- package/lib/task/artifact-lifecycle.ts +1 -38
- package/lib/task/commands/decisions.ts +53 -20
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +110 -2
- package/lib/task/orchestration.ts +543 -0
- package/lib/task/review-artifacts.ts +201 -69
- package/lib/task/review-finalization.ts +275 -0
- package/lib/task/review-fingerprint.ts +5 -0
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +177 -28
- package/lib/task/verification-review.ts +1 -0
- package/lib/task/verification.ts +5 -2
- package/lib/task/workspace-snapshot.ts +36 -0
- package/lib/update.ts +48 -184
- package/package.json +2 -2
- package/templates/.agents/README.en.md +21 -20
- package/templates/.agents/README.zh-CN.md +21 -20
- package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/commit-and-pr.en.md +1 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
- package/templates/.agents/rules/human-decision-context.en.md +14 -12
- package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
- package/templates/.agents/rules/next-step-output.en.md +16 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
- package/templates/.agents/rules/release-commands.github.en.md +11 -5
- package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
- package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +11 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
- package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
- package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
- package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
- package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
- package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
- package/templates/.agents/skills/test/SKILL.en.md +4 -4
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
- package/templates/.claude/commands/run-task.en.md +8 -0
- package/templates/.claude/commands/run-task.zh-CN.md +8 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
- package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
- package/templates/.opencode/commands/run-task.en.md +11 -0
- package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { AgentClientConfigError, normalizeAgentClients } from './agent-clients/config.ts';
|
|
5
|
+
import {
|
|
6
|
+
applyAgentClientReconciliation,
|
|
7
|
+
planAgentClientReconciliation
|
|
8
|
+
} from './agent-clients/reconcile.ts';
|
|
9
|
+
import { listAgentClientAdapters } from './agent-clients/registry.ts';
|
|
10
|
+
import { AGENT_CLIENT_IDS, isAgentClientId } from './agent-clients/types.ts';
|
|
11
|
+
import type { AgentClientState } from './agent-clients/types.ts';
|
|
12
|
+
import { closePrompt, multiSelect } from './prompt.ts';
|
|
13
|
+
import { resolveTemplateDir } from './paths.ts';
|
|
14
|
+
|
|
15
|
+
const USAGE = `Usage: ai agent-client <operation>
|
|
16
|
+
|
|
17
|
+
Operations:
|
|
18
|
+
list List built-in Agent Clients
|
|
19
|
+
status Show project integration and sandbox state
|
|
20
|
+
enable <client-id> Enable project integration only
|
|
21
|
+
disable <client-id> Disable project integration only
|
|
22
|
+
configure Configure integration and sandbox state interactively
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
class AgentClientCommandError extends Error {
|
|
26
|
+
readonly code: string;
|
|
27
|
+
|
|
28
|
+
constructor(code: string, message: string) {
|
|
29
|
+
super(`${code}: ${message}`);
|
|
30
|
+
this.name = 'AgentClientCommandError';
|
|
31
|
+
this.code = code;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readConfig(projectRoot: string): Record<string, unknown> {
|
|
36
|
+
const configPath = path.join(projectRoot, '.agents', '.airc.json');
|
|
37
|
+
if (!fs.existsSync(configPath)) {
|
|
38
|
+
throw new AgentClientCommandError(
|
|
39
|
+
'AGENT_CLIENT_CONFIG_NOT_FOUND',
|
|
40
|
+
'No .agents/.airc.json found. Run "ai init" first.'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
let parsed: unknown;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new AgentClientCommandError(
|
|
48
|
+
'AGENT_CLIENT_CONFIG_INVALID',
|
|
49
|
+
error instanceof Error ? error.message : String(error)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
53
|
+
throw new AgentClientCommandError(
|
|
54
|
+
'AGENT_CLIENT_CONFIG_INVALID',
|
|
55
|
+
'configuration root must be an object'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return parsed as Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function choices() {
|
|
62
|
+
return listAgentClientAdapters().map((adapter) => ({
|
|
63
|
+
id: adapter.id,
|
|
64
|
+
label: adapter.displayName
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function selected(state: AgentClientState, field: 'enabled' | 'installInSandbox'): string[] {
|
|
69
|
+
return AGENT_CLIENT_IDS.filter((id) => state[id][field]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function workflowPlan(
|
|
73
|
+
projectRoot: string,
|
|
74
|
+
config: Record<string, unknown>,
|
|
75
|
+
mutation: Parameters<typeof planAgentClientReconciliation>[0]['mutation']
|
|
76
|
+
) {
|
|
77
|
+
const templateRoot = resolveTemplateDir();
|
|
78
|
+
if (!templateRoot) {
|
|
79
|
+
throw new AgentClientCommandError('AGENT_CLIENT_TEMPLATE_NOT_FOUND', 'Template directory not found');
|
|
80
|
+
}
|
|
81
|
+
const platform = config.platform;
|
|
82
|
+
const platformType = typeof platform === 'object' && platform !== null && 'type' in platform
|
|
83
|
+
? String(platform.type ?? 'github')
|
|
84
|
+
: 'github';
|
|
85
|
+
return planAgentClientReconciliation({
|
|
86
|
+
config,
|
|
87
|
+
mutation,
|
|
88
|
+
projectRoot,
|
|
89
|
+
templateRoot,
|
|
90
|
+
platformType,
|
|
91
|
+
language: String(config.language ?? 'en')
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function printState(state: AgentClientState): void {
|
|
96
|
+
for (const adapter of listAgentClientAdapters()) {
|
|
97
|
+
const value = state[adapter.id];
|
|
98
|
+
process.stdout.write(
|
|
99
|
+
` ${adapter.id}: enabled=${String(value.enabled)} installInSandbox=${String(value.installInSandbox)}\n`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function cmdAgentClient(args: string[]): Promise<number> {
|
|
105
|
+
const operation = args[0];
|
|
106
|
+
if (!operation || operation === '--help' || operation === '-h' || operation === 'help') {
|
|
107
|
+
process.stdout.write(USAGE);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (operation === 'list') {
|
|
112
|
+
if (args.length !== 1) {
|
|
113
|
+
throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'list accepts no arguments');
|
|
114
|
+
}
|
|
115
|
+
for (const adapter of listAgentClientAdapters()) {
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
`${adapter.id}\t${adapter.displayName}\t${adapter.invocation}\tcommands=${adapter.capabilities.commands.level}\tsandbox=${adapter.capabilities.sandbox.level}\n`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const projectRoot = process.cwd();
|
|
124
|
+
const config = readConfig(projectRoot);
|
|
125
|
+
try {
|
|
126
|
+
if (operation === 'status') {
|
|
127
|
+
if (args.length !== 1) {
|
|
128
|
+
throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'status accepts no arguments');
|
|
129
|
+
}
|
|
130
|
+
const plan = workflowPlan(projectRoot, config, { type: 'none' });
|
|
131
|
+
process.stdout.write(`source: ${plan.source}\n`);
|
|
132
|
+
printState(plan.desired);
|
|
133
|
+
for (const seed of plan.seedOperations) {
|
|
134
|
+
process.stdout.write(` seed ${seed.target}: ${seed.kind}\n`);
|
|
135
|
+
}
|
|
136
|
+
for (const diagnostic of plan.diagnostics) {
|
|
137
|
+
process.stdout.write(` diagnostic ${diagnostic.code} at ${diagnostic.path}\n`);
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (operation === 'enable' || operation === 'disable') {
|
|
143
|
+
if (args.length !== 2) {
|
|
144
|
+
throw new AgentClientCommandError(
|
|
145
|
+
'AGENT_CLIENT_ARGUMENT_INVALID',
|
|
146
|
+
`${operation} requires exactly one client id`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const id = args[1];
|
|
150
|
+
if (!isAgentClientId(id)) {
|
|
151
|
+
throw new AgentClientCommandError('UNKNOWN_AGENT_CLIENT', `Unknown Agent Client '${String(id)}'`);
|
|
152
|
+
}
|
|
153
|
+
const plan = workflowPlan(projectRoot, config, {
|
|
154
|
+
type: 'set-enabled',
|
|
155
|
+
id,
|
|
156
|
+
value: operation === 'enable'
|
|
157
|
+
});
|
|
158
|
+
process.stdout.write('Before:\n');
|
|
159
|
+
printState(plan.before);
|
|
160
|
+
const result = applyAgentClientReconciliation(plan);
|
|
161
|
+
process.stdout.write('After:\n');
|
|
162
|
+
printState(plan.desired);
|
|
163
|
+
process.stdout.write(
|
|
164
|
+
`Result: ${result.status}; applied=${result.applied.length}; protected=${result.protected.length}\n`
|
|
165
|
+
);
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (operation === 'configure') {
|
|
170
|
+
if (args.length !== 1) {
|
|
171
|
+
throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'configure accepts no arguments');
|
|
172
|
+
}
|
|
173
|
+
const normalized = normalizeAgentClients(config);
|
|
174
|
+
let enabled: string[];
|
|
175
|
+
let installed: string[];
|
|
176
|
+
try {
|
|
177
|
+
enabled = await multiSelect(
|
|
178
|
+
'Agent Client project integrations',
|
|
179
|
+
choices(),
|
|
180
|
+
selected(normalized.state, 'enabled')
|
|
181
|
+
);
|
|
182
|
+
installed = await multiSelect(
|
|
183
|
+
'Agent Clients installed in sandbox',
|
|
184
|
+
choices(),
|
|
185
|
+
selected(normalized.state, 'installInSandbox')
|
|
186
|
+
);
|
|
187
|
+
} finally {
|
|
188
|
+
closePrompt();
|
|
189
|
+
}
|
|
190
|
+
const state = Object.fromEntries(AGENT_CLIENT_IDS.map((id) => [id, {
|
|
191
|
+
enabled: enabled.includes(id),
|
|
192
|
+
installInSandbox: installed.includes(id)
|
|
193
|
+
}])) as AgentClientState;
|
|
194
|
+
const plan = workflowPlan(projectRoot, config, { type: 'replace-state', state });
|
|
195
|
+
const result = applyAgentClientReconciliation(plan);
|
|
196
|
+
process.stdout.write(
|
|
197
|
+
`Result: ${result.status}; applied=${result.applied.length}; protected=${result.protected.length}\n`
|
|
198
|
+
);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
throw new AgentClientCommandError(
|
|
203
|
+
'AGENT_CLIENT_OPERATION_INVALID',
|
|
204
|
+
`Unknown operation '${operation}'`
|
|
205
|
+
);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error instanceof AgentClientConfigError) {
|
|
208
|
+
throw new AgentClientCommandError(error.code, error.message);
|
|
209
|
+
}
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { AgentClientCommandError, cmdAgentClient };
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_CLIENT_CAPABILITY_IDS,
|
|
3
|
+
AGENT_CLIENT_SUPPORT_LEVELS,
|
|
4
|
+
isAgentClientId
|
|
5
|
+
} from './types.ts';
|
|
6
|
+
import type {
|
|
7
|
+
AgentClientCapabilityMap,
|
|
8
|
+
AgentClientId
|
|
9
|
+
} from './types.ts';
|
|
10
|
+
import {
|
|
11
|
+
SANDBOX_HOOK_PHASES
|
|
12
|
+
} from '../sandbox/tool-types.ts';
|
|
13
|
+
import type {
|
|
14
|
+
AgentClientSandboxRecoveryCheck,
|
|
15
|
+
AgentClientSandboxDescriptor,
|
|
16
|
+
SandboxAlias,
|
|
17
|
+
SandboxTool,
|
|
18
|
+
SandboxToolContext
|
|
19
|
+
} from '../sandbox/tool-types.ts';
|
|
20
|
+
|
|
21
|
+
type AgentClientCapabilities = AgentClientCapabilityMap;
|
|
22
|
+
|
|
23
|
+
type AgentClientSeedCommand = Readonly<{
|
|
24
|
+
templates: Readonly<{
|
|
25
|
+
en: string;
|
|
26
|
+
'zh-CN': string;
|
|
27
|
+
}>;
|
|
28
|
+
target: string;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
type AgentClientProjectDescriptor = Readonly<{
|
|
32
|
+
ownedPathPrefixes: readonly string[];
|
|
33
|
+
managed: readonly string[];
|
|
34
|
+
merged: readonly string[];
|
|
35
|
+
ejected: readonly string[];
|
|
36
|
+
seedCommands: readonly AgentClientSeedCommand[];
|
|
37
|
+
}>;
|
|
38
|
+
|
|
39
|
+
type AgentClientAdapter = Readonly<{
|
|
40
|
+
id: AgentClientId;
|
|
41
|
+
displayName: string;
|
|
42
|
+
invocation: string;
|
|
43
|
+
capabilities: AgentClientCapabilities;
|
|
44
|
+
project: AgentClientProjectDescriptor;
|
|
45
|
+
sandbox: AgentClientSandboxDescriptor;
|
|
46
|
+
}>;
|
|
47
|
+
|
|
48
|
+
type AgentClientRegistry = Readonly<
|
|
49
|
+
Record<AgentClientId, AgentClientAdapter>
|
|
50
|
+
>;
|
|
51
|
+
|
|
52
|
+
type AgentClientManifestEntry = Readonly<{
|
|
53
|
+
id: AgentClientId;
|
|
54
|
+
displayName: string;
|
|
55
|
+
invocation: string;
|
|
56
|
+
ownedPathPrefixes: readonly string[];
|
|
57
|
+
managed: readonly string[];
|
|
58
|
+
merged: readonly string[];
|
|
59
|
+
ejected: readonly string[];
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
const PROJECT_ASSET_CATEGORIES = ['managed', 'merged', 'ejected'] as const;
|
|
63
|
+
const MAX_SANDBOX_HOOK_TIMEOUT_MS = 300_000;
|
|
64
|
+
const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
65
|
+
|
|
66
|
+
function freezeRecoveryProbe(
|
|
67
|
+
id: AgentClientId,
|
|
68
|
+
checkId: string,
|
|
69
|
+
value: AgentClientSandboxRecoveryCheck['probe']
|
|
70
|
+
): AgentClientSandboxRecoveryCheck['probe'] {
|
|
71
|
+
if (
|
|
72
|
+
!value
|
|
73
|
+
|| typeof value.script !== 'string'
|
|
74
|
+
|| value.script.trim() === ''
|
|
75
|
+
|| !Array.isArray(value.args)
|
|
76
|
+
|| value.args.some((arg) => typeof arg !== 'string')
|
|
77
|
+
|| (value.user !== undefined && (typeof value.user !== 'string' || value.user === ''))
|
|
78
|
+
) {
|
|
79
|
+
throw new Error(`Agent Client '${id}' recovery check '${checkId}' has an invalid probe`);
|
|
80
|
+
}
|
|
81
|
+
return Object.freeze({
|
|
82
|
+
script: value.script,
|
|
83
|
+
args: Object.freeze([...value.args]),
|
|
84
|
+
...(value.user === undefined ? {} : { user: value.user })
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function freezeTool(tool: SandboxTool): SandboxTool {
|
|
89
|
+
if (typeof tool.id !== 'string' || !TOOL_ID_PATTERN.test(tool.id)) {
|
|
90
|
+
throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
|
|
91
|
+
}
|
|
92
|
+
if (!tool.install?.cmd || !['npm', 'shell'].includes(tool.install.type)) {
|
|
93
|
+
throw new Error(`Sandbox tool '${tool.id}' has invalid install`);
|
|
94
|
+
}
|
|
95
|
+
if (!tool.containerMount.startsWith('/')) {
|
|
96
|
+
throw new Error(`Sandbox tool '${tool.id}' containerMount must be absolute`);
|
|
97
|
+
}
|
|
98
|
+
if (!tool.versionCmd) {
|
|
99
|
+
throw new Error(`Sandbox tool '${tool.id}' requires versionCmd`);
|
|
100
|
+
}
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
...tool,
|
|
103
|
+
install: Object.freeze({ ...tool.install }),
|
|
104
|
+
...(tool.envVars ? { envVars: Object.freeze({ ...tool.envVars }) } : {}),
|
|
105
|
+
...(tool.hostPreSeedFiles ? {
|
|
106
|
+
hostPreSeedFiles: Object.freeze(tool.hostPreSeedFiles.map((entry) => Object.freeze({ ...entry })))
|
|
107
|
+
} : {}),
|
|
108
|
+
...(tool.hostPreSeedDirs ? {
|
|
109
|
+
hostPreSeedDirs: Object.freeze(tool.hostPreSeedDirs.map((entry) => Object.freeze({ ...entry })))
|
|
110
|
+
} : {}),
|
|
111
|
+
...(tool.pathRewriteFiles ? { pathRewriteFiles: Object.freeze([...tool.pathRewriteFiles]) } : {}),
|
|
112
|
+
...(tool.hostLiveMounts ? {
|
|
113
|
+
hostLiveMounts: Object.freeze(tool.hostLiveMounts.map((entry) => Object.freeze({ ...entry })))
|
|
114
|
+
} : {}),
|
|
115
|
+
...(tool.postSetupCmds ? { postSetupCmds: Object.freeze([...tool.postSetupCmds]) } : {}),
|
|
116
|
+
...(tool.tmpfs ? {
|
|
117
|
+
tmpfs: Object.freeze({
|
|
118
|
+
...tool.tmpfs,
|
|
119
|
+
...(tool.tmpfs.seed ? { seed: Object.freeze([...tool.tmpfs.seed]) } : {})
|
|
120
|
+
})
|
|
121
|
+
} : {})
|
|
122
|
+
}) as SandboxTool;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function freezeAliases(id: AgentClientId, aliases: readonly SandboxAlias[]): readonly SandboxAlias[] {
|
|
126
|
+
const names = new Set<string>();
|
|
127
|
+
return Object.freeze(aliases.map((alias) => {
|
|
128
|
+
if (!alias.name || !alias.command || names.has(alias.name)) {
|
|
129
|
+
throw new Error(`Agent Client '${id}' has invalid or duplicate sandbox alias '${alias.name}'`);
|
|
130
|
+
}
|
|
131
|
+
names.add(alias.name);
|
|
132
|
+
return Object.freeze({ ...alias });
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isProjectRelativeLiteral(value: string): boolean {
|
|
137
|
+
if (
|
|
138
|
+
value.length === 0
|
|
139
|
+
|| value.startsWith('/')
|
|
140
|
+
|| /^[a-zA-Z]:/.test(value)
|
|
141
|
+
|| value.includes('\\')
|
|
142
|
+
|| /[*?[\]{}]/.test(value)
|
|
143
|
+
) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return !value.split('/').some((segment) => segment === '..');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function defineAgentClientAdapter(
|
|
150
|
+
candidate: AgentClientAdapter
|
|
151
|
+
): AgentClientAdapter {
|
|
152
|
+
if (!isAgentClientId(candidate.id)) {
|
|
153
|
+
throw new Error(`Unknown Agent Client ID '${String(candidate.id)}'`);
|
|
154
|
+
}
|
|
155
|
+
if (typeof candidate.displayName !== 'string' || candidate.displayName.trim() === '') {
|
|
156
|
+
throw new Error(`Agent Client '${candidate.id}' requires a display name`);
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
typeof candidate.invocation !== 'string'
|
|
160
|
+
|| candidate.invocation.trim() === ''
|
|
161
|
+
|| /[\r\n]/.test(candidate.invocation)
|
|
162
|
+
) {
|
|
163
|
+
throw new Error(`Agent Client '${candidate.id}' requires a single-line invocation`);
|
|
164
|
+
}
|
|
165
|
+
const invocationPlaceholders = [
|
|
166
|
+
...candidate.invocation.matchAll(/\$\{([^}]+)\}/g)
|
|
167
|
+
].map((match) => match[1]);
|
|
168
|
+
if (
|
|
169
|
+
!invocationPlaceholders.includes('skillName')
|
|
170
|
+
|| invocationPlaceholders.some((placeholder) =>
|
|
171
|
+
placeholder !== 'skillName' && placeholder !== 'projectName'
|
|
172
|
+
)
|
|
173
|
+
|| candidate.invocation
|
|
174
|
+
.replaceAll('${skillName}', '')
|
|
175
|
+
.replaceAll('${projectName}', '')
|
|
176
|
+
.includes('${')
|
|
177
|
+
) {
|
|
178
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid invocation`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
typeof candidate.capabilities !== 'object'
|
|
183
|
+
|| candidate.capabilities === null
|
|
184
|
+
|| Array.isArray(candidate.capabilities)
|
|
185
|
+
) {
|
|
186
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid capability map`);
|
|
187
|
+
}
|
|
188
|
+
const capabilityKeys = Object.keys(candidate.capabilities);
|
|
189
|
+
if (
|
|
190
|
+
capabilityKeys.length !== AGENT_CLIENT_CAPABILITY_IDS.length
|
|
191
|
+
|| AGENT_CLIENT_CAPABILITY_IDS.some((capability) =>
|
|
192
|
+
!Object.prototype.hasOwnProperty.call(candidate.capabilities, capability)
|
|
193
|
+
)
|
|
194
|
+
) {
|
|
195
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid capability map`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const capabilities = Object.fromEntries(
|
|
199
|
+
AGENT_CLIENT_CAPABILITY_IDS.map((capability) => {
|
|
200
|
+
const support = candidate.capabilities[capability];
|
|
201
|
+
if (
|
|
202
|
+
typeof support !== 'object'
|
|
203
|
+
|| support === null
|
|
204
|
+
|| !AGENT_CLIENT_SUPPORT_LEVELS.includes(support.level)
|
|
205
|
+
) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Agent Client '${candidate.id}' has invalid support for '${capability}'`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return [capability, Object.freeze({ level: support.level })];
|
|
211
|
+
})
|
|
212
|
+
) as AgentClientCapabilities;
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
typeof candidate.project !== 'object'
|
|
216
|
+
|| candidate.project === null
|
|
217
|
+
|| Array.isArray(candidate.project)
|
|
218
|
+
) {
|
|
219
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid project descriptor`);
|
|
220
|
+
}
|
|
221
|
+
const ownedPathPrefixes = candidate.project.ownedPathPrefixes;
|
|
222
|
+
if (!Array.isArray(ownedPathPrefixes) || ownedPathPrefixes.length === 0) {
|
|
223
|
+
throw new Error(`Agent Client '${candidate.id}' requires owned path prefixes`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const paths = ownedPathPrefixes.map((prefix) => {
|
|
227
|
+
if (
|
|
228
|
+
typeof prefix !== 'string'
|
|
229
|
+
|| !isProjectRelativeLiteral(prefix)
|
|
230
|
+
|| !prefix.endsWith('/')
|
|
231
|
+
) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Agent Client '${candidate.id}' has invalid owned path prefix '${String(prefix)}'`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return prefix;
|
|
237
|
+
});
|
|
238
|
+
if (new Set(paths).size !== paths.length) {
|
|
239
|
+
throw new Error(`Agent Client '${candidate.id}' has duplicate owned path prefixes`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const projectAssets = Object.fromEntries(
|
|
243
|
+
PROJECT_ASSET_CATEGORIES.map((category) => {
|
|
244
|
+
const values = candidate.project[category];
|
|
245
|
+
if (!Array.isArray(values)) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Agent Client '${candidate.id}' has invalid '${category}' project assets`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const assets = values.map((asset) => {
|
|
251
|
+
if (
|
|
252
|
+
typeof asset !== 'string'
|
|
253
|
+
|| !isProjectRelativeLiteral(asset)
|
|
254
|
+
|| !paths.some((prefix) =>
|
|
255
|
+
asset === prefix.slice(0, -1) || asset.startsWith(prefix)
|
|
256
|
+
)
|
|
257
|
+
) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Agent Client '${candidate.id}' has invalid '${category}' project asset '${String(asset)}'`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return asset;
|
|
263
|
+
});
|
|
264
|
+
if (new Set(assets).size !== assets.length) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Agent Client '${candidate.id}' has duplicate '${category}' project assets`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return [category, Object.freeze(assets)];
|
|
270
|
+
})
|
|
271
|
+
) as Pick<AgentClientProjectDescriptor, 'managed' | 'merged' | 'ejected'>;
|
|
272
|
+
|
|
273
|
+
const categorizedAssets = PROJECT_ASSET_CATEGORIES.flatMap((category) =>
|
|
274
|
+
projectAssets[category].map((asset) => ({ asset, category }))
|
|
275
|
+
);
|
|
276
|
+
const seenAssets = new Map<string, string>();
|
|
277
|
+
for (const { asset, category } of categorizedAssets) {
|
|
278
|
+
const previous = seenAssets.get(asset);
|
|
279
|
+
if (previous) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`Agent Client '${candidate.id}' project asset '${asset}' overlaps '${previous}' and '${category}'`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
seenAssets.set(asset, category);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!Array.isArray(candidate.project.seedCommands)) {
|
|
288
|
+
throw new Error(`Agent Client '${candidate.id}' has invalid seed commands`);
|
|
289
|
+
}
|
|
290
|
+
const seedTargets = new Set<string>();
|
|
291
|
+
const seedCommands = candidate.project.seedCommands.map((seed) => {
|
|
292
|
+
if (!seed || typeof seed !== 'object') {
|
|
293
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid seed command`);
|
|
294
|
+
}
|
|
295
|
+
const placeholders = [
|
|
296
|
+
...seed.target.matchAll(/\$\{([^}]+)\}/g)
|
|
297
|
+
].map((match) => match[1]);
|
|
298
|
+
const targetProbe = seed.target.replaceAll('${projectName}', 'project');
|
|
299
|
+
if (
|
|
300
|
+
placeholders.some((placeholder) => placeholder !== 'projectName')
|
|
301
|
+
|| targetProbe.includes('${')
|
|
302
|
+
|| !isProjectRelativeLiteral(targetProbe)
|
|
303
|
+
|| !paths.some((prefix) =>
|
|
304
|
+
targetProbe === prefix.slice(0, -1) || targetProbe.startsWith(prefix)
|
|
305
|
+
)
|
|
306
|
+
|| seedTargets.has(seed.target)
|
|
307
|
+
) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Agent Client '${candidate.id}' has invalid seed target '${String(seed.target)}'`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (
|
|
313
|
+
!seed.templates
|
|
314
|
+
|| typeof seed.templates.en !== 'string'
|
|
315
|
+
|| typeof seed.templates['zh-CN'] !== 'string'
|
|
316
|
+
|| !isProjectRelativeLiteral(seed.templates.en)
|
|
317
|
+
|| !isProjectRelativeLiteral(seed.templates['zh-CN'])
|
|
318
|
+
) {
|
|
319
|
+
throw new Error(`Agent Client '${candidate.id}' has invalid seed templates`);
|
|
320
|
+
}
|
|
321
|
+
seedTargets.add(seed.target);
|
|
322
|
+
return Object.freeze({
|
|
323
|
+
templates: Object.freeze({ ...seed.templates }),
|
|
324
|
+
target: seed.target
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
if (!candidate.sandbox || typeof candidate.sandbox.createTool !== 'function') {
|
|
329
|
+
throw new Error(`Agent Client '${candidate.id}' requires a sandbox descriptor`);
|
|
330
|
+
}
|
|
331
|
+
const hookIds = new Set<string>();
|
|
332
|
+
const hooks = Object.freeze(candidate.sandbox.hooks.map((hook) => {
|
|
333
|
+
if (
|
|
334
|
+
!hook.id
|
|
335
|
+
|| hookIds.has(hook.id)
|
|
336
|
+
|| !SANDBOX_HOOK_PHASES.includes(hook.phase)
|
|
337
|
+
|| typeof hook.run !== 'function'
|
|
338
|
+
) {
|
|
339
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid sandbox hook`);
|
|
340
|
+
}
|
|
341
|
+
if (
|
|
342
|
+
hook.timeoutMs !== undefined
|
|
343
|
+
&& (
|
|
344
|
+
!Number.isInteger(hook.timeoutMs)
|
|
345
|
+
|| hook.timeoutMs <= 0
|
|
346
|
+
|| hook.timeoutMs > MAX_SANDBOX_HOOK_TIMEOUT_MS
|
|
347
|
+
)
|
|
348
|
+
) {
|
|
349
|
+
throw new Error(`Agent Client '${candidate.id}' hook '${hook.id}' has invalid timeoutMs`);
|
|
350
|
+
}
|
|
351
|
+
hookIds.add(hook.id);
|
|
352
|
+
return Object.freeze({ ...hook });
|
|
353
|
+
}));
|
|
354
|
+
const recoveryCheckIds = new Set<string>();
|
|
355
|
+
if (
|
|
356
|
+
candidate.sandbox.recoveryChecks !== undefined
|
|
357
|
+
&& !Array.isArray(candidate.sandbox.recoveryChecks)
|
|
358
|
+
) {
|
|
359
|
+
throw new Error(`Agent Client '${candidate.id}' has invalid recovery checks`);
|
|
360
|
+
}
|
|
361
|
+
const recoveryChecks = Object.freeze(
|
|
362
|
+
(candidate.sandbox.recoveryChecks ?? []).map((check) => {
|
|
363
|
+
if (
|
|
364
|
+
!check
|
|
365
|
+
|| typeof check.id !== 'string'
|
|
366
|
+
|| !TOOL_ID_PATTERN.test(check.id)
|
|
367
|
+
|| recoveryCheckIds.has(check.id)
|
|
368
|
+
) {
|
|
369
|
+
throw new Error(`Agent Client '${candidate.id}' has an invalid recovery check`);
|
|
370
|
+
}
|
|
371
|
+
const probe = freezeRecoveryProbe(candidate.id, check.id, check.probe);
|
|
372
|
+
const when = check.when === undefined
|
|
373
|
+
? undefined
|
|
374
|
+
: freezeRecoveryProbe(candidate.id, check.id, check.when);
|
|
375
|
+
if (
|
|
376
|
+
!check.finding
|
|
377
|
+
|| !['permissions', 'builtin-link', 'hard-failure'].includes(check.finding.repairKind)
|
|
378
|
+
|| typeof check.finding.message !== 'string'
|
|
379
|
+
|| check.finding.message.trim() === ''
|
|
380
|
+
|| (
|
|
381
|
+
check.finding.path !== undefined
|
|
382
|
+
&& (typeof check.finding.path !== 'string' || !check.finding.path.startsWith('/'))
|
|
383
|
+
)
|
|
384
|
+
|| (
|
|
385
|
+
check.finding.repairKind === 'permissions'
|
|
386
|
+
&& check.finding.path === undefined
|
|
387
|
+
)
|
|
388
|
+
) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Agent Client '${candidate.id}' recovery check '${check.id}' has an invalid finding`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
let repair: AgentClientSandboxRecoveryCheck['repair'];
|
|
394
|
+
if (check.repair !== undefined) {
|
|
395
|
+
if (
|
|
396
|
+
typeof check.repair.user !== 'string'
|
|
397
|
+
|| check.repair.user === ''
|
|
398
|
+
|| typeof check.repair.command !== 'string'
|
|
399
|
+
|| check.repair.command === ''
|
|
400
|
+
|| !Array.isArray(check.repair.args)
|
|
401
|
+
|| check.repair.args.some((arg: unknown) => typeof arg !== 'string')
|
|
402
|
+
) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Agent Client '${candidate.id}' recovery check '${check.id}' has an invalid repair`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
repair = Object.freeze({
|
|
408
|
+
user: check.repair.user,
|
|
409
|
+
command: check.repair.command,
|
|
410
|
+
args: Object.freeze([...check.repair.args])
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (check.finding.repairKind === 'builtin-link' && repair === undefined) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Agent Client '${candidate.id}' recovery check '${check.id}' requires a repair`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
recoveryCheckIds.add(check.id);
|
|
419
|
+
return Object.freeze({
|
|
420
|
+
id: check.id,
|
|
421
|
+
...(when === undefined ? {} : { when }),
|
|
422
|
+
probe,
|
|
423
|
+
finding: Object.freeze({ ...check.finding }),
|
|
424
|
+
...(repair === undefined ? {} : { repair })
|
|
425
|
+
});
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
const aliases = freezeAliases(candidate.id, candidate.sandbox.aliases);
|
|
429
|
+
const createTool = (context: SandboxToolContext): SandboxTool => {
|
|
430
|
+
const tool = freezeTool(candidate.sandbox.createTool(context));
|
|
431
|
+
if (tool.id !== candidate.id) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
`Agent Client '${candidate.id}' sandbox tool id must match the adapter id`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return tool;
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
return Object.freeze({
|
|
440
|
+
id: candidate.id,
|
|
441
|
+
displayName: candidate.displayName,
|
|
442
|
+
invocation: candidate.invocation,
|
|
443
|
+
capabilities: Object.freeze(capabilities),
|
|
444
|
+
project: Object.freeze({
|
|
445
|
+
ownedPathPrefixes: Object.freeze(paths),
|
|
446
|
+
...projectAssets,
|
|
447
|
+
seedCommands: Object.freeze(seedCommands)
|
|
448
|
+
}),
|
|
449
|
+
sandbox: Object.freeze({ createTool, aliases, hooks, recoveryChecks })
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export { defineAgentClientAdapter };
|
|
454
|
+
export type {
|
|
455
|
+
AgentClientAdapter,
|
|
456
|
+
AgentClientCapabilities,
|
|
457
|
+
AgentClientManifestEntry,
|
|
458
|
+
AgentClientProjectDescriptor,
|
|
459
|
+
AgentClientSeedCommand,
|
|
460
|
+
AgentClientRegistry
|
|
461
|
+
};
|