@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,247 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { formatCredentialWarnings, formatRemaining, hasClaudeProviderAuth, inspectClaudeKeychainStatus, prepareClaudeCredentials, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from './claude-code-credentials.js';
|
|
4
|
+
function readHostJsonSafe(filePath) {
|
|
5
|
+
if (!filePath || !fs.existsSync(filePath))
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
9
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
10
|
+
? parsed
|
|
11
|
+
: null;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS = [
|
|
18
|
+
'model',
|
|
19
|
+
'fallbackModel',
|
|
20
|
+
'availableModels',
|
|
21
|
+
'modelOverrides',
|
|
22
|
+
'enforceAvailableModels',
|
|
23
|
+
'advisorModel',
|
|
24
|
+
'apiKeyHelper',
|
|
25
|
+
'effortLevel'
|
|
26
|
+
];
|
|
27
|
+
function isJsonObjectRecord(value) {
|
|
28
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
function mergeMissingStringEnvFields(target, source) {
|
|
31
|
+
if (!isJsonObjectRecord(source.env))
|
|
32
|
+
return false;
|
|
33
|
+
if (Object.hasOwn(target, 'env') && !isJsonObjectRecord(target.env))
|
|
34
|
+
return false;
|
|
35
|
+
let targetEnv = target.env;
|
|
36
|
+
let changed = false;
|
|
37
|
+
for (const [key, value] of Object.entries(source.env)) {
|
|
38
|
+
if (typeof value !== 'string' || value === '')
|
|
39
|
+
continue;
|
|
40
|
+
if (!targetEnv) {
|
|
41
|
+
targetEnv = {};
|
|
42
|
+
target.env = targetEnv;
|
|
43
|
+
}
|
|
44
|
+
if (!Object.hasOwn(targetEnv, key)) {
|
|
45
|
+
targetEnv[key] = value;
|
|
46
|
+
changed = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return changed;
|
|
50
|
+
}
|
|
51
|
+
function mergeMissingTopLevelSettings(target, source) {
|
|
52
|
+
let changed = false;
|
|
53
|
+
for (const key of CLAUDE_SETTINGS_INHERIT_TOP_LEVEL_KEYS) {
|
|
54
|
+
if (!Object.hasOwn(source, key) || Object.hasOwn(target, key))
|
|
55
|
+
continue;
|
|
56
|
+
const value = source[key];
|
|
57
|
+
if (value === null || value === undefined || value === '')
|
|
58
|
+
continue;
|
|
59
|
+
target[key] = value;
|
|
60
|
+
changed = true;
|
|
61
|
+
}
|
|
62
|
+
return changed;
|
|
63
|
+
}
|
|
64
|
+
function requireCreateContext(context, hookId) {
|
|
65
|
+
if (!context)
|
|
66
|
+
throw new Error(`${hookId} requires create context`);
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
function requireEnterContext(context, hookId) {
|
|
70
|
+
if (!context)
|
|
71
|
+
throw new Error(`${hookId} requires enter context`);
|
|
72
|
+
return context;
|
|
73
|
+
}
|
|
74
|
+
export function ensureClaudeOnboarding(toolDir, hostHomeDir) {
|
|
75
|
+
const claudeJsonPath = path.join(toolDir, '.claude.json');
|
|
76
|
+
let data = {};
|
|
77
|
+
if (fs.existsSync(claudeJsonPath)) {
|
|
78
|
+
try {
|
|
79
|
+
data = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8'));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Malformed JSON is replaced with a valid managed sandbox baseline.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let changed = false;
|
|
86
|
+
if (!data.hasCompletedOnboarding) {
|
|
87
|
+
data.hasCompletedOnboarding = true;
|
|
88
|
+
changed = true;
|
|
89
|
+
}
|
|
90
|
+
if (!data.projects) {
|
|
91
|
+
data.projects = {};
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
if (!data.projects['/workspace']) {
|
|
95
|
+
data.projects['/workspace'] = {};
|
|
96
|
+
changed = true;
|
|
97
|
+
}
|
|
98
|
+
if (!data.projects['/workspace'].hasTrustDialogAccepted) {
|
|
99
|
+
data.projects['/workspace'].hasTrustDialogAccepted = true;
|
|
100
|
+
changed = true;
|
|
101
|
+
}
|
|
102
|
+
if (hostHomeDir) {
|
|
103
|
+
const hostClaudeJson = readHostJsonSafe(path.join(hostHomeDir, '.claude.json'));
|
|
104
|
+
if (hostClaudeJson
|
|
105
|
+
&& typeof hostClaudeJson.model === 'string'
|
|
106
|
+
&& hostClaudeJson.model !== ''
|
|
107
|
+
&& !Object.hasOwn(data, 'model')) {
|
|
108
|
+
data.model = hostClaudeJson.model;
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
if (hostClaudeJson) {
|
|
112
|
+
for (const key of Object.keys(hostClaudeJson)) {
|
|
113
|
+
if (/^unpin.*LaunchEffort$/.test(key)
|
|
114
|
+
&& hostClaudeJson[key] === true
|
|
115
|
+
&& !Object.hasOwn(data, key)) {
|
|
116
|
+
data[key] = true;
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (changed)
|
|
123
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(data, null, 4), 'utf8');
|
|
124
|
+
}
|
|
125
|
+
export function ensureClaudeSettings(toolDir, hostHomeDir) {
|
|
126
|
+
const settingsPath = path.join(toolDir, 'settings.json');
|
|
127
|
+
let data = {};
|
|
128
|
+
if (fs.existsSync(settingsPath)) {
|
|
129
|
+
try {
|
|
130
|
+
data = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Malformed JSON is replaced with a valid managed sandbox baseline.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
let changed = false;
|
|
137
|
+
if (data.skipDangerousModePermissionPrompt !== true) {
|
|
138
|
+
data.skipDangerousModePermissionPrompt = true;
|
|
139
|
+
changed = true;
|
|
140
|
+
}
|
|
141
|
+
if (hostHomeDir) {
|
|
142
|
+
const hostSettings = readHostJsonSafe(path.join(hostHomeDir, '.claude', 'settings.json'));
|
|
143
|
+
if (hostSettings) {
|
|
144
|
+
changed = mergeMissingStringEnvFields(data, hostSettings) || changed;
|
|
145
|
+
changed = mergeMissingTopLevelSettings(data, hostSettings) || changed;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (changed)
|
|
149
|
+
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 4), 'utf8');
|
|
150
|
+
}
|
|
151
|
+
export function formatCredentialSyncStatus(result, isTTY = process.stderr.isTTY, providerAuthAvailable = false) {
|
|
152
|
+
if (providerAuthAvailable && (result.status === 'STALE_ACCESS' || result.status === 'MISSING'))
|
|
153
|
+
return null;
|
|
154
|
+
if (result.status === 'STALE_ACCESS') {
|
|
155
|
+
return 'Warning: Claude Code credentials on host appear stale. Run "ai sandbox refresh" or "claude /login" to renew.\n';
|
|
156
|
+
}
|
|
157
|
+
if (result.status === 'MISSING') {
|
|
158
|
+
return 'Warning: Claude Code credentials missing on host. Run "claude /login" to authenticate.\n';
|
|
159
|
+
}
|
|
160
|
+
if (result.status === 'KEYCHAIN_WRITE_FAILED') {
|
|
161
|
+
return `Warning: A sandbox refresh produced newer credentials but host Keychain write failed (${formatCredentialWarnings(result.warnings)}). Run "ai sandbox refresh" again or "claude /status" on the host to retry.\n`;
|
|
162
|
+
}
|
|
163
|
+
if (result.status === 'KEYCHAIN_LOCKED' || result.status === 'KEYCHAIN_ERROR') {
|
|
164
|
+
return 'Warning: Host keychain is unavailable; Claude credential sync skipped. Run "ai sandbox refresh" for details.\n';
|
|
165
|
+
}
|
|
166
|
+
if (result.status === 'OK' && result.authoritative !== 'host') {
|
|
167
|
+
const message = `Synced Claude Code credentials from sandbox refresh back to host (expires in ${formatRemaining(result.expiresAt)})`;
|
|
168
|
+
return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
|
|
169
|
+
}
|
|
170
|
+
if (result.status === 'OK' && result.filesWritten.length > 0) {
|
|
171
|
+
const message = `Synced Claude Code credentials from host Keychain (expires in ${formatRemaining(result.expiresAt)})`;
|
|
172
|
+
return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const claudeCodeCredentialPreflightHook = {
|
|
177
|
+
id: 'claude-code-credential-preflight',
|
|
178
|
+
phase: 'prepare',
|
|
179
|
+
run: async ({ create }) => {
|
|
180
|
+
const { hostHome, hostEnv, project, resolvedTools } = requireCreateContext(create, 'claude-code-credential-preflight');
|
|
181
|
+
try {
|
|
182
|
+
validateClaudeCredentialsEnvOverride(hostEnv);
|
|
183
|
+
const outcome = prepareClaudeCredentials(hostHome, project, [...resolvedTools], undefined, undefined, (home) => inspectClaudeKeychainStatus(home, undefined, { envFn: () => ({ ...hostEnv }) }));
|
|
184
|
+
if (outcome.status === 'SKIPPED') {
|
|
185
|
+
return {
|
|
186
|
+
status: 'warning',
|
|
187
|
+
message: 'Claude Code credentials not found on host - creating this sandbox WITHOUT Claude Code credentials.\n'
|
|
188
|
+
+ ' Claude Code is still installed in the image but will not be authenticated.\n'
|
|
189
|
+
+ ' To enable it: run "claude" once on the host to complete login, then re-run "ai sandbox create".'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return { status: 'ready' };
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
return {
|
|
196
|
+
status: 'fatal',
|
|
197
|
+
message: redactCommandError(error instanceof Error ? error.message : 'unknown credential error')
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const claudeCodeBeforeContainerCreateHook = {
|
|
203
|
+
id: 'claude-code-before-container-create',
|
|
204
|
+
phase: 'before-container-create',
|
|
205
|
+
run: async ({ create }) => {
|
|
206
|
+
const { hostHome, resolvedTools } = requireCreateContext(create, 'claude-code-before-container-create');
|
|
207
|
+
const claudeCodeEntry = resolvedTools.find(({ tool }) => tool.id === 'claude-code');
|
|
208
|
+
if (claudeCodeEntry) {
|
|
209
|
+
ensureClaudeOnboarding(claudeCodeEntry.dir, hostHome);
|
|
210
|
+
ensureClaudeSettings(claudeCodeEntry.dir, hostHome);
|
|
211
|
+
}
|
|
212
|
+
return { status: 'ready' };
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const claudeCodeBeforeEnterHook = {
|
|
216
|
+
id: 'claude-code-before-enter',
|
|
217
|
+
phase: 'before-enter',
|
|
218
|
+
run: async ({ enter }) => {
|
|
219
|
+
const { hostHome, hostEnv } = requireEnterContext(enter, 'claude-code-before-enter');
|
|
220
|
+
try {
|
|
221
|
+
validateClaudeCredentialsEnvOverride(hostEnv);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
status: 'fatal',
|
|
226
|
+
message: redactCommandError(error instanceof Error ? error.message : 'invalid credentials override')
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const providerAuthAvailable = hasClaudeProviderAuth(hostHome);
|
|
231
|
+
const result = reconcileClaudeCredentials(hostHome, { envFn: () => ({ ...hostEnv }) });
|
|
232
|
+
const message = formatCredentialSyncStatus(result, process.stderr.isTTY, providerAuthAvailable);
|
|
233
|
+
return {
|
|
234
|
+
status: 'ready',
|
|
235
|
+
...(message === null ? {} : { message })
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
status: 'warning',
|
|
241
|
+
message: `Failed to sync Claude Code credentials: ${redactCommandError(error instanceof Error ? error.message : 'unknown error')}`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
export { claudeCodeBeforeContainerCreateHook, claudeCodeBeforeEnterHook, claudeCodeCredentialPreflightHook };
|
|
247
|
+
//# sourceMappingURL=claude-code-sandbox.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineAgentClientAdapter } from '../adapter.js';
|
|
2
2
|
import { hostJoin } from '../../sandbox/engines/wsl2-paths.js';
|
|
3
|
+
import { claudeCodeBeforeContainerCreateHook, claudeCodeBeforeEnterHook, claudeCodeCredentialPreflightHook } from './claude-code-sandbox.js';
|
|
3
4
|
const claudeCodeAdapter = defineAgentClientAdapter({
|
|
4
5
|
id: 'claude-code',
|
|
5
6
|
displayName: 'Claude Code',
|
|
@@ -25,7 +26,7 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
25
26
|
},
|
|
26
27
|
project: {
|
|
27
28
|
ownedPathPrefixes: ['.claude/'],
|
|
28
|
-
managed: ['.claude/commands/', '.claude/agents/'],
|
|
29
|
+
managed: ['.claude/commands/', '.claude/agents/', '.claude/rules/'],
|
|
29
30
|
merged: ['.claude/settings.json'],
|
|
30
31
|
ejected: [],
|
|
31
32
|
seedCommands: [{
|
|
@@ -34,9 +35,18 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
34
35
|
'zh-CN': '.claude/commands/update-agent-infra.zh-CN.md'
|
|
35
36
|
},
|
|
36
37
|
target: '.claude/commands/update-agent-infra.md'
|
|
37
|
-
}]
|
|
38
|
+
}],
|
|
39
|
+
customCommand: {
|
|
40
|
+
target: '.claude/commands/${skillName}.md',
|
|
41
|
+
frontmatter: {},
|
|
42
|
+
includeUsage: true,
|
|
43
|
+
inheritDisableModelInvocation: true
|
|
44
|
+
}
|
|
38
45
|
},
|
|
39
46
|
sandbox: {
|
|
47
|
+
image: {
|
|
48
|
+
dockerfileFragment: 'claude-code'
|
|
49
|
+
},
|
|
40
50
|
createTool: ({ home, project }) => ({
|
|
41
51
|
id: 'claude-code',
|
|
42
52
|
name: 'Claude Code',
|
|
@@ -64,7 +74,11 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
64
74
|
{ name: 'claude-yolo', command: 'claude --dangerously-skip-permissions; tput ed' },
|
|
65
75
|
{ name: 'cy', command: 'claude --dangerously-skip-permissions; tput ed' }
|
|
66
76
|
],
|
|
67
|
-
hooks: [
|
|
77
|
+
hooks: [
|
|
78
|
+
claudeCodeCredentialPreflightHook,
|
|
79
|
+
claudeCodeBeforeContainerCreateHook,
|
|
80
|
+
claudeCodeBeforeEnterHook
|
|
81
|
+
]
|
|
68
82
|
}
|
|
69
83
|
});
|
|
70
84
|
export { claudeCodeAdapter };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
function readJsonObject(filePath) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
6
|
+
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
7
|
+
? parsed
|
|
8
|
+
: null;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function resolveXdgRoot(value, fallback) {
|
|
15
|
+
return typeof value === 'string' && value !== '' && path.isAbsolute(value)
|
|
16
|
+
? value
|
|
17
|
+
: fallback;
|
|
18
|
+
}
|
|
19
|
+
function ensureOpenCodeSandboxState(toolDir, hostHome, hostEnv = {}) {
|
|
20
|
+
const canonicalConfigDir = path.join(toolDir, '.xdg', 'config', 'opencode');
|
|
21
|
+
const canonicalStateDir = path.join(toolDir, '.xdg', 'state', 'opencode');
|
|
22
|
+
const canonicalConfigPath = path.join(canonicalConfigDir, 'opencode.json');
|
|
23
|
+
const legacyConfigPath = path.join(toolDir, 'opencode.json');
|
|
24
|
+
fs.mkdirSync(canonicalConfigDir, { recursive: true });
|
|
25
|
+
fs.mkdirSync(canonicalStateDir, { recursive: true });
|
|
26
|
+
if (!fs.existsSync(canonicalConfigPath) && fs.existsSync(legacyConfigPath)) {
|
|
27
|
+
fs.copyFileSync(legacyConfigPath, canonicalConfigPath, fs.constants.COPYFILE_EXCL);
|
|
28
|
+
}
|
|
29
|
+
const hostConfigRoot = resolveXdgRoot(hostEnv.XDG_CONFIG_HOME, path.join(hostHome, '.config'));
|
|
30
|
+
const hostConfig = readJsonObject(path.join(hostConfigRoot, 'opencode', 'opencode.json'));
|
|
31
|
+
if (!hostConfig) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let sandboxConfig = {};
|
|
35
|
+
if (fs.existsSync(canonicalConfigPath)) {
|
|
36
|
+
const existing = readJsonObject(canonicalConfigPath);
|
|
37
|
+
if (!existing) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
sandboxConfig = existing;
|
|
41
|
+
}
|
|
42
|
+
let changed = false;
|
|
43
|
+
for (const key of ['model', 'small_model']) {
|
|
44
|
+
if (Object.hasOwn(sandboxConfig, key)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const value = hostConfig[key];
|
|
48
|
+
if (typeof value === 'string' && value !== '') {
|
|
49
|
+
sandboxConfig[key] = value;
|
|
50
|
+
changed = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (changed) {
|
|
54
|
+
fs.writeFileSync(canonicalConfigPath, JSON.stringify(sandboxConfig, null, 2), 'utf8');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const opencodeBeforeContainerCreateHook = {
|
|
58
|
+
id: 'opencode-before-container-create',
|
|
59
|
+
phase: 'before-container-create',
|
|
60
|
+
async run(context) {
|
|
61
|
+
const create = context.create;
|
|
62
|
+
const entry = create?.resolvedTools.find(({ tool }) => tool.id === 'opencode');
|
|
63
|
+
if (!create || !entry) {
|
|
64
|
+
return {
|
|
65
|
+
status: 'fatal',
|
|
66
|
+
message: 'OpenCode sandbox hook requires its resolved tool state.'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
ensureOpenCodeSandboxState(entry.dir, create.hostHome, create.hostEnv);
|
|
70
|
+
return { status: 'ready' };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const opencodeRecoveryChecks = [
|
|
74
|
+
{
|
|
75
|
+
id: 'command-available',
|
|
76
|
+
probe: { script: 'command -v "$1"', args: ['opencode'] },
|
|
77
|
+
finding: {
|
|
78
|
+
repairKind: 'hard-failure',
|
|
79
|
+
message: 'OpenCode is not available on PATH inside the sandbox.'
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'config-writable',
|
|
84
|
+
probe: {
|
|
85
|
+
script: 'probe="$1/.agent-infra-opencode-config-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
|
|
86
|
+
args: ['/home/devuser/.local/share/opencode/.xdg/config/opencode']
|
|
87
|
+
},
|
|
88
|
+
finding: {
|
|
89
|
+
repairKind: 'permissions',
|
|
90
|
+
message: 'OpenCode config directory is not writable by devuser.',
|
|
91
|
+
path: '/home/devuser/.local/share/opencode/.xdg/config/opencode'
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'state-writable',
|
|
96
|
+
probe: {
|
|
97
|
+
script: 'probe="$1/.agent-infra-opencode-state-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
|
|
98
|
+
args: ['/home/devuser/.local/share/opencode/.xdg/state/opencode']
|
|
99
|
+
},
|
|
100
|
+
finding: {
|
|
101
|
+
repairKind: 'permissions',
|
|
102
|
+
message: 'OpenCode state directory is not writable by devuser.',
|
|
103
|
+
path: '/home/devuser/.local/share/opencode/.xdg/state/opencode'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
export { ensureOpenCodeSandboxState, opencodeBeforeContainerCreateHook, opencodeRecoveryChecks };
|
|
108
|
+
//# sourceMappingURL=opencode-sandbox.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineAgentClientAdapter } from '../adapter.js';
|
|
2
2
|
import { hostJoin } from '../../sandbox/engines/wsl2-paths.js';
|
|
3
|
+
import { opencodeBeforeContainerCreateHook, opencodeRecoveryChecks } from './opencode-sandbox.js';
|
|
3
4
|
const YOLO_PERMISSION = '{"*":"allow","read":"allow","bash":"allow","edit":"allow","webfetch":"allow","external_directory":"allow","doom_loop":"allow"}';
|
|
4
5
|
const opencodeAdapter = defineAgentClientAdapter({
|
|
5
6
|
id: 'opencode',
|
|
@@ -35,9 +36,17 @@ const opencodeAdapter = defineAgentClientAdapter({
|
|
|
35
36
|
'zh-CN': '.opencode/commands/update-agent-infra.zh-CN.md'
|
|
36
37
|
},
|
|
37
38
|
target: '.opencode/commands/update-agent-infra.md'
|
|
38
|
-
}]
|
|
39
|
+
}],
|
|
40
|
+
customCommand: {
|
|
41
|
+
target: '.opencode/commands/${skillName}.md',
|
|
42
|
+
frontmatter: { agent: 'general', subtask: false },
|
|
43
|
+
argumentsToken: '$ARGUMENTS'
|
|
44
|
+
}
|
|
39
45
|
},
|
|
40
46
|
sandbox: {
|
|
47
|
+
image: {
|
|
48
|
+
dotfilesExclusions: ['.config/opencode']
|
|
49
|
+
},
|
|
41
50
|
createTool: ({ home }) => ({
|
|
42
51
|
id: 'opencode',
|
|
43
52
|
name: 'OpenCode',
|
|
@@ -47,7 +56,9 @@ const opencodeAdapter = defineAgentClientAdapter({
|
|
|
47
56
|
versionCmd: 'opencode --version',
|
|
48
57
|
setupHint: 'Configure OpenCode credentials inside the container before first use.',
|
|
49
58
|
envVars: {
|
|
50
|
-
|
|
59
|
+
XDG_DATA_HOME: '/home/devuser/.local/share',
|
|
60
|
+
XDG_CONFIG_HOME: '/home/devuser/.local/share/opencode/.xdg/config',
|
|
61
|
+
XDG_STATE_HOME: '/home/devuser/.local/share/opencode/.xdg/state'
|
|
51
62
|
},
|
|
52
63
|
hostLiveMounts: [
|
|
53
64
|
{
|
|
@@ -66,7 +77,8 @@ const opencodeAdapter = defineAgentClientAdapter({
|
|
|
66
77
|
command: `OPENCODE_PERMISSION='${YOLO_PERMISSION}' opencode; tput ed`
|
|
67
78
|
}
|
|
68
79
|
],
|
|
69
|
-
hooks: []
|
|
80
|
+
hooks: [opencodeBeforeContainerCreateHook],
|
|
81
|
+
recoveryChecks: opencodeRecoveryChecks
|
|
70
82
|
}
|
|
71
83
|
});
|
|
72
84
|
export { opencodeAdapter };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
RUN cat > /usr/local/bin/cc-token-status <<'SCRIPT' && chmod +x /usr/local/bin/cc-token-status
|
|
2
|
+
#!/bin/sh
|
|
3
|
+
set -eu
|
|
4
|
+
|
|
5
|
+
SETTINGS_FILE="/home/devuser/.claude/settings.json"
|
|
6
|
+
if [ -r "$SETTINGS_FILE" ] && jq -e '
|
|
7
|
+
def has_nonempty($v):
|
|
8
|
+
if ($v | type) == "string" then ($v | gsub("^\\s+|\\s+$"; "") | length) > 0 else false end;
|
|
9
|
+
(if type == "object" then . else {} end) as $settings
|
|
10
|
+
| (if ($settings.env | type) == "object" then $settings.env else {} end) as $env
|
|
11
|
+
| has_nonempty($env.ANTHROPIC_AUTH_TOKEN)
|
|
12
|
+
or has_nonempty($env.ANTHROPIC_API_KEY)
|
|
13
|
+
or has_nonempty($settings.apiKeyHelper)
|
|
14
|
+
' "$SETTINGS_FILE" >/dev/null 2>&1; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
CRED_FILE="/home/devuser/.claude/.credentials.json"
|
|
19
|
+
[ -r "$CRED_FILE" ] || exit 0
|
|
20
|
+
|
|
21
|
+
EXPIRES_MS=$(jq -r '(.claudeAiOauth.expiresAt // .expiresAt) // empty' "$CRED_FILE" 2>/dev/null || true)
|
|
22
|
+
case "$EXPIRES_MS" in
|
|
23
|
+
''|*[!0-9]*) exit 0 ;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
NOW_MS=$(($(date +%s) * 1000))
|
|
27
|
+
DIFF_MS=$((EXPIRES_MS - NOW_MS))
|
|
28
|
+
DIFF_S=$((DIFF_MS / 1000))
|
|
29
|
+
|
|
30
|
+
DIM='#[fg=colour245]'
|
|
31
|
+
YELLOW='#[fg=yellow]'
|
|
32
|
+
YELLOW_BOLD='#[fg=yellow,bold]'
|
|
33
|
+
RED_BOLD='#[fg=red,bold]'
|
|
34
|
+
RED_REV='#[fg=red,reverse]'
|
|
35
|
+
RESET='#[default]'
|
|
36
|
+
|
|
37
|
+
if [ "$DIFF_S" -le 0 ]; then
|
|
38
|
+
ELAPSED=$(( -DIFF_S ))
|
|
39
|
+
M=$((ELAPSED / 60))
|
|
40
|
+
printf '%sClaude Code auth EXPIRED %dm ago%s' "$RED_REV" "$M" "$RESET"
|
|
41
|
+
elif [ "$DIFF_S" -lt 60 ]; then
|
|
42
|
+
printf '%sClaude Code auth expires in %ds%s' "$RED_BOLD" "$DIFF_S" "$RESET"
|
|
43
|
+
elif [ "$DIFF_S" -lt 300 ]; then
|
|
44
|
+
M=$((DIFF_S / 60))
|
|
45
|
+
S=$((DIFF_S % 60))
|
|
46
|
+
printf '%sClaude Code auth expires in %dm %ds%s' "$RED_BOLD" "$M" "$S" "$RESET"
|
|
47
|
+
elif [ "$DIFF_S" -lt 1800 ]; then
|
|
48
|
+
M=$((DIFF_S / 60))
|
|
49
|
+
printf '%sClaude Code auth expires in %dm%s' "$YELLOW_BOLD" "$M" "$RESET"
|
|
50
|
+
elif [ "$DIFF_S" -lt 3600 ]; then
|
|
51
|
+
M=$((DIFF_S / 60))
|
|
52
|
+
printf '%sClaude Code auth expires in %dm%s' "$YELLOW" "$M" "$RESET"
|
|
53
|
+
else
|
|
54
|
+
TOTAL_M=$((DIFF_S / 60))
|
|
55
|
+
H=$((TOTAL_M / 60))
|
|
56
|
+
M=$((TOTAL_M % 60))
|
|
57
|
+
printf '%sClaude Code auth expires in %dh %dm%s' "$DIM" "$H" "$M" "$RESET"
|
|
58
|
+
fi
|
|
59
|
+
SCRIPT
|
|
60
|
+
|
|
61
|
+
RUN test -s /usr/local/bin/cc-token-status && test -x /usr/local/bin/cc-token-status
|
|
62
|
+
|
|
63
|
+
RUN printf '%s\n' \
|
|
64
|
+
"set -g status-right '#(/usr/local/bin/cc-token-status) | %H:%M'" \
|
|
65
|
+
>> /etc/tmux.conf
|
|
@@ -71,7 +71,15 @@ function createAgentClientManifest() {
|
|
|
71
71
|
ownedPathPrefixes: Object.freeze([...adapter.project.ownedPathPrefixes]),
|
|
72
72
|
managed: Object.freeze([...adapter.project.managed]),
|
|
73
73
|
merged: Object.freeze([...adapter.project.merged]),
|
|
74
|
-
ejected: Object.freeze([...adapter.project.ejected])
|
|
74
|
+
ejected: Object.freeze([...adapter.project.ejected]),
|
|
75
|
+
...(adapter.project.customCommand === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: {
|
|
78
|
+
customCommand: Object.freeze({
|
|
79
|
+
...adapter.project.customCommand,
|
|
80
|
+
frontmatter: Object.freeze({ ...adapter.project.customCommand.frontmatter })
|
|
81
|
+
})
|
|
82
|
+
})
|
|
75
83
|
})));
|
|
76
84
|
}
|
|
77
85
|
export { AGENT_CLIENT_REGISTRY, createAgentClientManifest, getAgentClientAdapter, getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection, listAgentClientAdapters, listEnabledAgentClientAdapters, listEnabledAgentClientAdaptersByCapability, listInstalledAgentClientAdapters };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { requestSandboxControl } from '../sandbox/control/client.js';
|
|
2
|
+
import { serveSandboxControl } from '../sandbox/control/server.js';
|
|
3
|
+
function sandboxControl(args) {
|
|
4
|
+
const [operation, ...rest] = args;
|
|
5
|
+
if (operation === 'serve') {
|
|
6
|
+
const manifestIndex = rest.indexOf('--manifest');
|
|
7
|
+
const manifest = manifestIndex >= 0 ? rest[manifestIndex + 1] : undefined;
|
|
8
|
+
if (!manifest)
|
|
9
|
+
throw new Error('sandbox-control serve requires --manifest');
|
|
10
|
+
serveSandboxControl(manifest);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (operation === 'client') {
|
|
14
|
+
const [family = '', ...commandArgs] = rest;
|
|
15
|
+
const response = requestSandboxControl({ family, args: commandArgs });
|
|
16
|
+
process.stdout.write(response.stdout);
|
|
17
|
+
process.stderr.write(response.stderr);
|
|
18
|
+
process.exitCode = response.exitCode;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
throw new Error('Usage: agent-infra-internal sandbox-control serve --manifest <path> | client <family> [args...]');
|
|
22
|
+
}
|
|
23
|
+
export { sandboxControl };
|
|
24
|
+
//# sourceMappingURL=sandbox-control.js.map
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const USAGE = `Usage: agent-infra-internal task-activity <task-ref>
|
|
1
|
+
import { applyPrReviewActivityIntent, inspectPrReviewActivity } from '../task/activity-intent.js';
|
|
2
|
+
const USAGE = `Usage: agent-infra-internal task-activity <task-ref> pr-review-inspect
|
|
3
|
+
agent-infra-internal task-activity <task-ref> pr-review-start --agent <agent> --artifact <canonical.md> --head <40hex> [--dry-run]
|
|
4
|
+
agent-infra-internal task-activity <task-ref> pr-review-complete --agent <agent> --artifact <canonical.md> --head <40hex> --verdict <approved|changes-requested|commented> --blockers <N> --major <N> --minor <N> [--dry-run]
|
|
5
|
+
agent-infra-internal task-activity <task-ref> pr-review-terminate --agent <agent> --artifact <canonical.md> --head <40hex> --outcome <aborted|superseded> --reason <single-line> [--dry-run]
|
|
3
6
|
`;
|
|
7
|
+
const OPERATIONS = new Set(['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate']);
|
|
4
8
|
const FLAGS = {
|
|
5
|
-
'--step': 'step',
|
|
6
9
|
'--agent': 'agent',
|
|
7
|
-
'--
|
|
8
|
-
'--
|
|
10
|
+
'--artifact': 'artifact',
|
|
11
|
+
'--head': 'head',
|
|
12
|
+
'--verdict': 'verdict',
|
|
13
|
+
'--blockers': 'blockers',
|
|
14
|
+
'--major': 'major',
|
|
15
|
+
'--minor': 'minor',
|
|
16
|
+
'--outcome': 'outcome',
|
|
17
|
+
'--reason': 'reason'
|
|
18
|
+
};
|
|
19
|
+
const COMMON = ['agent', 'artifact', 'head'];
|
|
20
|
+
const ALLOWED = {
|
|
21
|
+
'pr-review-inspect': new Set(['kind', 'taskRef']),
|
|
22
|
+
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun']),
|
|
23
|
+
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun']),
|
|
24
|
+
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'])
|
|
9
25
|
};
|
|
10
26
|
function usageFailure(message) {
|
|
11
27
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ACTIVITY_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -18,8 +34,8 @@ function taskActivity(args = []) {
|
|
|
18
34
|
return;
|
|
19
35
|
}
|
|
20
36
|
const [taskRef, kind] = args;
|
|
21
|
-
if (!taskRef || taskRef.startsWith('--') || !kind || kind
|
|
22
|
-
usageFailure('task ref and
|
|
37
|
+
if (!taskRef || taskRef.startsWith('--') || !kind || !OPERATIONS.has(kind)) {
|
|
38
|
+
usageFailure('task ref and a supported PR review intent are required');
|
|
23
39
|
return;
|
|
24
40
|
}
|
|
25
41
|
const values = { kind, taskRef };
|
|
@@ -50,21 +66,37 @@ function taskActivity(args = []) {
|
|
|
50
66
|
return;
|
|
51
67
|
}
|
|
52
68
|
seen.add(flag);
|
|
53
|
-
values[key] = value;
|
|
69
|
+
values[key] = ['blockers', 'major', 'minor'].includes(key) ? Number(value) : value;
|
|
54
70
|
}
|
|
55
|
-
const allowed =
|
|
71
|
+
const allowed = ALLOWED[kind];
|
|
56
72
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
57
73
|
if (unexpected) {
|
|
58
|
-
usageFailure(
|
|
74
|
+
usageFailure(`${kind} does not accept '${unexpected}'`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (kind === 'pr-review-inspect') {
|
|
78
|
+
const result = inspectPrReviewActivity(values);
|
|
79
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
80
|
+
if (result.status === 'failed')
|
|
81
|
+
process.exitCode = 1;
|
|
59
82
|
return;
|
|
60
83
|
}
|
|
61
|
-
for (const key of
|
|
84
|
+
for (const key of COMMON) {
|
|
85
|
+
if (values[key] === undefined) {
|
|
86
|
+
usageFailure(`${kind} requires '${key}'`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const operationRequired = kind === 'pr-review-complete'
|
|
91
|
+
? ['verdict', 'blockers', 'major', 'minor']
|
|
92
|
+
: kind === 'pr-review-terminate' ? ['outcome', 'reason'] : [];
|
|
93
|
+
for (const key of operationRequired) {
|
|
62
94
|
if (values[key] === undefined) {
|
|
63
|
-
usageFailure(
|
|
95
|
+
usageFailure(`${kind} requires '${key}'`);
|
|
64
96
|
return;
|
|
65
97
|
}
|
|
66
98
|
}
|
|
67
|
-
const result =
|
|
99
|
+
const result = applyPrReviewActivityIntent(values);
|
|
68
100
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
69
101
|
if (result.status === 'failed')
|
|
70
102
|
process.exitCode = 1;
|