@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,136 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
AgentClientSandboxHook,
|
|
6
|
+
AgentClientSandboxRecoveryCheck
|
|
7
|
+
} from '../../sandbox/tool-types.ts';
|
|
8
|
+
|
|
9
|
+
type JsonObject = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
function readJsonObject(filePath: string): JsonObject | null {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as unknown;
|
|
14
|
+
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
15
|
+
? parsed as JsonObject
|
|
16
|
+
: null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveXdgRoot(value: string | undefined, fallback: string): string {
|
|
23
|
+
return typeof value === 'string' && value !== '' && path.isAbsolute(value)
|
|
24
|
+
? value
|
|
25
|
+
: fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureOpenCodeSandboxState(
|
|
29
|
+
toolDir: string,
|
|
30
|
+
hostHome: string,
|
|
31
|
+
hostEnv: Readonly<NodeJS.ProcessEnv> = {}
|
|
32
|
+
): void {
|
|
33
|
+
const canonicalConfigDir = path.join(toolDir, '.xdg', 'config', 'opencode');
|
|
34
|
+
const canonicalStateDir = path.join(toolDir, '.xdg', 'state', 'opencode');
|
|
35
|
+
const canonicalConfigPath = path.join(canonicalConfigDir, 'opencode.json');
|
|
36
|
+
const legacyConfigPath = path.join(toolDir, 'opencode.json');
|
|
37
|
+
fs.mkdirSync(canonicalConfigDir, { recursive: true });
|
|
38
|
+
fs.mkdirSync(canonicalStateDir, { recursive: true });
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(canonicalConfigPath) && fs.existsSync(legacyConfigPath)) {
|
|
41
|
+
fs.copyFileSync(legacyConfigPath, canonicalConfigPath, fs.constants.COPYFILE_EXCL);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hostConfigRoot = resolveXdgRoot(
|
|
45
|
+
hostEnv.XDG_CONFIG_HOME,
|
|
46
|
+
path.join(hostHome, '.config')
|
|
47
|
+
);
|
|
48
|
+
const hostConfig = readJsonObject(
|
|
49
|
+
path.join(hostConfigRoot, 'opencode', 'opencode.json')
|
|
50
|
+
);
|
|
51
|
+
if (!hostConfig) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let sandboxConfig: JsonObject = {};
|
|
56
|
+
if (fs.existsSync(canonicalConfigPath)) {
|
|
57
|
+
const existing = readJsonObject(canonicalConfigPath);
|
|
58
|
+
if (!existing) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
sandboxConfig = existing;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let changed = false;
|
|
65
|
+
for (const key of ['model', 'small_model']) {
|
|
66
|
+
if (Object.hasOwn(sandboxConfig, key)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const value = hostConfig[key];
|
|
70
|
+
if (typeof value === 'string' && value !== '') {
|
|
71
|
+
sandboxConfig[key] = value;
|
|
72
|
+
changed = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (changed) {
|
|
76
|
+
fs.writeFileSync(canonicalConfigPath, JSON.stringify(sandboxConfig, null, 2), 'utf8');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const opencodeBeforeContainerCreateHook: AgentClientSandboxHook = {
|
|
81
|
+
id: 'opencode-before-container-create',
|
|
82
|
+
phase: 'before-container-create',
|
|
83
|
+
async run(context) {
|
|
84
|
+
const create = context.create;
|
|
85
|
+
const entry = create?.resolvedTools.find(({ tool }) => tool.id === 'opencode');
|
|
86
|
+
if (!create || !entry) {
|
|
87
|
+
return {
|
|
88
|
+
status: 'fatal',
|
|
89
|
+
message: 'OpenCode sandbox hook requires its resolved tool state.'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
ensureOpenCodeSandboxState(entry.dir, create.hostHome, create.hostEnv);
|
|
93
|
+
return { status: 'ready' };
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const opencodeRecoveryChecks: readonly AgentClientSandboxRecoveryCheck[] = [
|
|
98
|
+
{
|
|
99
|
+
id: 'command-available',
|
|
100
|
+
probe: { script: 'command -v "$1"', args: ['opencode'] },
|
|
101
|
+
finding: {
|
|
102
|
+
repairKind: 'hard-failure',
|
|
103
|
+
message: 'OpenCode is not available on PATH inside the sandbox.'
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'config-writable',
|
|
108
|
+
probe: {
|
|
109
|
+
script: 'probe="$1/.agent-infra-opencode-config-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
|
|
110
|
+
args: ['/home/devuser/.local/share/opencode/.xdg/config/opencode']
|
|
111
|
+
},
|
|
112
|
+
finding: {
|
|
113
|
+
repairKind: 'permissions',
|
|
114
|
+
message: 'OpenCode config directory is not writable by devuser.',
|
|
115
|
+
path: '/home/devuser/.local/share/opencode/.xdg/config/opencode'
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'state-writable',
|
|
120
|
+
probe: {
|
|
121
|
+
script: 'probe="$1/.agent-infra-opencode-state-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
|
|
122
|
+
args: ['/home/devuser/.local/share/opencode/.xdg/state/opencode']
|
|
123
|
+
},
|
|
124
|
+
finding: {
|
|
125
|
+
repairKind: 'permissions',
|
|
126
|
+
message: 'OpenCode state directory is not writable by devuser.',
|
|
127
|
+
path: '/home/devuser/.local/share/opencode/.xdg/state/opencode'
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
ensureOpenCodeSandboxState,
|
|
134
|
+
opencodeBeforeContainerCreateHook,
|
|
135
|
+
opencodeRecoveryChecks
|
|
136
|
+
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { defineAgentClientAdapter } from '../adapter.ts';
|
|
2
2
|
import { hostJoin } from '../../sandbox/engines/wsl2-paths.ts';
|
|
3
|
+
import {
|
|
4
|
+
opencodeBeforeContainerCreateHook,
|
|
5
|
+
opencodeRecoveryChecks
|
|
6
|
+
} from './opencode-sandbox.ts';
|
|
3
7
|
|
|
4
8
|
const YOLO_PERMISSION = '{"*":"allow","read":"allow","bash":"allow","edit":"allow","webfetch":"allow","external_directory":"allow","doom_loop":"allow"}';
|
|
5
9
|
|
|
@@ -37,9 +41,17 @@ const opencodeAdapter = defineAgentClientAdapter({
|
|
|
37
41
|
'zh-CN': '.opencode/commands/update-agent-infra.zh-CN.md'
|
|
38
42
|
},
|
|
39
43
|
target: '.opencode/commands/update-agent-infra.md'
|
|
40
|
-
}]
|
|
44
|
+
}],
|
|
45
|
+
customCommand: {
|
|
46
|
+
target: '.opencode/commands/${skillName}.md',
|
|
47
|
+
frontmatter: { agent: 'general', subtask: false },
|
|
48
|
+
argumentsToken: '$ARGUMENTS'
|
|
49
|
+
}
|
|
41
50
|
},
|
|
42
51
|
sandbox: {
|
|
52
|
+
image: {
|
|
53
|
+
dotfilesExclusions: ['.config/opencode']
|
|
54
|
+
},
|
|
43
55
|
createTool: ({ home }) => ({
|
|
44
56
|
id: 'opencode',
|
|
45
57
|
name: 'OpenCode',
|
|
@@ -49,7 +61,9 @@ const opencodeAdapter = defineAgentClientAdapter({
|
|
|
49
61
|
versionCmd: 'opencode --version',
|
|
50
62
|
setupHint: 'Configure OpenCode credentials inside the container before first use.',
|
|
51
63
|
envVars: {
|
|
52
|
-
|
|
64
|
+
XDG_DATA_HOME: '/home/devuser/.local/share',
|
|
65
|
+
XDG_CONFIG_HOME: '/home/devuser/.local/share/opencode/.xdg/config',
|
|
66
|
+
XDG_STATE_HOME: '/home/devuser/.local/share/opencode/.xdg/state'
|
|
53
67
|
},
|
|
54
68
|
hostLiveMounts: [
|
|
55
69
|
{
|
|
@@ -68,7 +82,8 @@ const opencodeAdapter = defineAgentClientAdapter({
|
|
|
68
82
|
command: `OPENCODE_PERMISSION='${YOLO_PERMISSION}' opencode; tput ed`
|
|
69
83
|
}
|
|
70
84
|
],
|
|
71
|
-
hooks: []
|
|
85
|
+
hooks: [opencodeBeforeContainerCreateHook],
|
|
86
|
+
recoveryChecks: opencodeRecoveryChecks
|
|
72
87
|
}
|
|
73
88
|
});
|
|
74
89
|
|
|
@@ -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
|
|
@@ -135,7 +135,15 @@ function createAgentClientManifest(): readonly AgentClientManifestEntry[] {
|
|
|
135
135
|
ownedPathPrefixes: Object.freeze([...adapter.project.ownedPathPrefixes]),
|
|
136
136
|
managed: Object.freeze([...adapter.project.managed]),
|
|
137
137
|
merged: Object.freeze([...adapter.project.merged]),
|
|
138
|
-
ejected: Object.freeze([...adapter.project.ejected])
|
|
138
|
+
ejected: Object.freeze([...adapter.project.ejected]),
|
|
139
|
+
...(adapter.project.customCommand === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: {
|
|
142
|
+
customCommand: Object.freeze({
|
|
143
|
+
...adapter.project.customCommand,
|
|
144
|
+
frontmatter: Object.freeze({ ...adapter.project.customCommand.frontmatter })
|
|
145
|
+
})
|
|
146
|
+
})
|
|
139
147
|
}))
|
|
140
148
|
);
|
|
141
149
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { requestSandboxControl } from '../sandbox/control/client.ts';
|
|
2
|
+
import { serveSandboxControl } from '../sandbox/control/server.ts';
|
|
3
|
+
|
|
4
|
+
function sandboxControl(args: string[]): void {
|
|
5
|
+
const [operation, ...rest] = args;
|
|
6
|
+
if (operation === 'serve') {
|
|
7
|
+
const manifestIndex = rest.indexOf('--manifest');
|
|
8
|
+
const manifest = manifestIndex >= 0 ? rest[manifestIndex + 1] : undefined;
|
|
9
|
+
if (!manifest) 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
|
+
|
|
24
|
+
export { sandboxControl };
|
|
@@ -1,13 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
applyPrReviewActivityIntent,
|
|
3
|
+
inspectPrReviewActivity
|
|
4
|
+
} from '../task/activity-intent.ts';
|
|
5
|
+
import type {
|
|
6
|
+
PrReviewActivityIntent,
|
|
7
|
+
PrReviewInspectIntent
|
|
8
|
+
} from '../task/activity-intent.ts';
|
|
3
9
|
|
|
4
|
-
const USAGE = `Usage: agent-infra-internal task-activity <task-ref>
|
|
10
|
+
const USAGE = `Usage: agent-infra-internal task-activity <task-ref> pr-review-inspect
|
|
11
|
+
agent-infra-internal task-activity <task-ref> pr-review-start --agent <agent> --artifact <canonical.md> --head <40hex> [--dry-run]
|
|
12
|
+
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]
|
|
13
|
+
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]
|
|
5
14
|
`;
|
|
15
|
+
|
|
16
|
+
const OPERATIONS = new Set(['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate']);
|
|
6
17
|
const FLAGS: Record<string, string> = {
|
|
7
|
-
'--step': 'step',
|
|
8
18
|
'--agent': 'agent',
|
|
9
|
-
'--
|
|
10
|
-
'--
|
|
19
|
+
'--artifact': 'artifact',
|
|
20
|
+
'--head': 'head',
|
|
21
|
+
'--verdict': 'verdict',
|
|
22
|
+
'--blockers': 'blockers',
|
|
23
|
+
'--major': 'major',
|
|
24
|
+
'--minor': 'minor',
|
|
25
|
+
'--outcome': 'outcome',
|
|
26
|
+
'--reason': 'reason'
|
|
27
|
+
};
|
|
28
|
+
const COMMON = ['agent', 'artifact', 'head'] as const;
|
|
29
|
+
const ALLOWED: Record<string, ReadonlySet<string>> = {
|
|
30
|
+
'pr-review-inspect': new Set(['kind', 'taskRef']),
|
|
31
|
+
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun']),
|
|
32
|
+
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun']),
|
|
33
|
+
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'])
|
|
11
34
|
};
|
|
12
35
|
|
|
13
36
|
function usageFailure(message: string): void {
|
|
@@ -19,8 +42,8 @@ function usageFailure(message: string): void {
|
|
|
19
42
|
function taskActivity(args: string[] = []): void {
|
|
20
43
|
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
|
|
21
44
|
const [taskRef, kind] = args;
|
|
22
|
-
if (!taskRef || taskRef.startsWith('--') || !kind || kind
|
|
23
|
-
usageFailure('task ref and
|
|
45
|
+
if (!taskRef || taskRef.startsWith('--') || !kind || !OPERATIONS.has(kind)) {
|
|
46
|
+
usageFailure('task ref and a supported PR review intent are required');
|
|
24
47
|
return;
|
|
25
48
|
}
|
|
26
49
|
const values: Record<string, unknown> = { kind, taskRef };
|
|
@@ -39,15 +62,28 @@ function taskActivity(args: string[] = []): void {
|
|
|
39
62
|
const value = args[++index];
|
|
40
63
|
if (value === undefined || value.startsWith('--')) { usageFailure(`option '${flag}' requires a value`); return; }
|
|
41
64
|
seen.add(flag);
|
|
42
|
-
values[key] = value;
|
|
65
|
+
values[key] = ['blockers', 'major', 'minor'].includes(key) ? Number(value) : value;
|
|
43
66
|
}
|
|
44
|
-
|
|
67
|
+
|
|
68
|
+
const allowed = ALLOWED[kind]!;
|
|
45
69
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
46
|
-
if (unexpected) { usageFailure(
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
|
|
71
|
+
if (kind === 'pr-review-inspect') {
|
|
72
|
+
const result = inspectPrReviewActivity(values as PrReviewInspectIntent);
|
|
73
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
74
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const key of COMMON) {
|
|
78
|
+
if (values[key] === undefined) { usageFailure(`${kind} requires '${key}'`); return; }
|
|
79
|
+
}
|
|
80
|
+
const operationRequired = kind === 'pr-review-complete'
|
|
81
|
+
? ['verdict', 'blockers', 'major', 'minor']
|
|
82
|
+
: kind === 'pr-review-terminate' ? ['outcome', 'reason'] : [];
|
|
83
|
+
for (const key of operationRequired) {
|
|
84
|
+
if (values[key] === undefined) { usageFailure(`${kind} requires '${key}'`); return; }
|
|
49
85
|
}
|
|
50
|
-
const result =
|
|
86
|
+
const result = applyPrReviewActivityIntent(values as PrReviewActivityIntent);
|
|
51
87
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
52
88
|
if (result.status === 'failed') process.exitCode = 1;
|
|
53
89
|
}
|
|
@@ -9,9 +9,10 @@ const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [inte
|
|
|
9
9
|
const FLAGS: Record<string, string> = {
|
|
10
10
|
'--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
|
|
11
11
|
'--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
|
|
12
|
-
'--status': 'status', '--artifact': 'artifact'
|
|
12
|
+
'--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation'
|
|
13
13
|
};
|
|
14
14
|
const NUMERIC = new Set(['ordinal', 'round']);
|
|
15
|
+
const BOOLEAN = new Set(['needsImplementation']);
|
|
15
16
|
|
|
16
17
|
function usageFailure(message: string): void {
|
|
17
18
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LEDGER_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -39,7 +40,11 @@ function taskLedger(args: string[] = []): void {
|
|
|
39
40
|
if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
|
|
40
41
|
const value = args[++index];
|
|
41
42
|
if (value === undefined || value.startsWith('--')) { usageFailure(`option '${flag}' requires a value`); return; }
|
|
42
|
-
|
|
43
|
+
if (BOOLEAN.has(key) && value !== 'true' && value !== 'false') {
|
|
44
|
+
usageFailure(`option '${flag}' must be true or false`); return;
|
|
45
|
+
}
|
|
46
|
+
seen.add(flag);
|
|
47
|
+
values[key] = NUMERIC.has(key) ? Number(value) : BOOLEAN.has(key) ? value === 'true' : value;
|
|
43
48
|
}
|
|
44
49
|
const required: Record<string, string[]> = {
|
|
45
50
|
'finding-upsert': ['stage', 'reviewArtifact', 'ordinal', 'severity', 'evidence'],
|
|
@@ -49,7 +54,11 @@ function taskLedger(args: string[] = []): void {
|
|
|
49
54
|
'decision-upsert': ['id', 'stage', 'artifact'],
|
|
50
55
|
'stage-status': ['stage']
|
|
51
56
|
};
|
|
52
|
-
const
|
|
57
|
+
const optional: Record<string, string[]> = {
|
|
58
|
+
'finding-review': ['needsImplementation'],
|
|
59
|
+
'decision-upsert': ['needsImplementation']
|
|
60
|
+
};
|
|
61
|
+
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? [])]);
|
|
53
62
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
54
63
|
const missing = required[kind]!.find((key) => values[key] === undefined);
|
|
55
64
|
if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
2
2
|
import { applyTaskLifecycle, lifecycleIntentCatalog } from '../task/lifecycle.ts';
|
|
3
3
|
import type { TaskLifecycleRequest } from '../task/lifecycle.ts';
|
|
4
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
4
6
|
|
|
5
7
|
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\n`;
|
|
6
8
|
const FLAGS: Record<string, string> = {
|
|
@@ -37,7 +39,26 @@ function taskLifecycle(args: string[] = []): void {
|
|
|
37
39
|
const agent = normalizeAgentToken(String(values.agent ?? ''));
|
|
38
40
|
if (!agent) { usageFailure(`invalid --agent '${values.agent}': ${AGENT_USAGE_HINT}`); return; }
|
|
39
41
|
values.agent = agent;
|
|
40
|
-
const
|
|
42
|
+
const resolved = resolveTaskRef(String(values.taskRef));
|
|
43
|
+
if (!resolved.ok) {
|
|
44
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let result: ReturnType<typeof applyTaskLifecycle>;
|
|
49
|
+
try {
|
|
50
|
+
result = withTaskExecutionLock(
|
|
51
|
+
resolved.repoRoot,
|
|
52
|
+
resolved.taskId,
|
|
53
|
+
`task-lifecycle.${String(values.intent)}`,
|
|
54
|
+
() => applyTaskLifecycle(values as TaskLifecycleRequest)
|
|
55
|
+
);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
58
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
41
62
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
42
63
|
if (result.status === 'failed') process.exitCode = 1;
|
|
43
64
|
}
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
2
2
|
import {
|
|
3
|
+
abortCommitIntent,
|
|
3
4
|
activateMatchingOrchestrationDelegation,
|
|
4
5
|
activateOrchestrationDelegation,
|
|
5
6
|
advanceOrchestration,
|
|
7
|
+
beginCommitIntent,
|
|
6
8
|
beginOrResumeOrchestration,
|
|
7
|
-
|
|
9
|
+
checkpointCommitIntent,
|
|
10
|
+
completeCommitIntent,
|
|
8
11
|
pauseOrchestration,
|
|
9
12
|
prepareOrchestrationDelegation,
|
|
13
|
+
recoverCommitIntent,
|
|
10
14
|
routeOrchestration,
|
|
11
15
|
sealMatchingOrchestrationDelegation,
|
|
12
16
|
sealOrchestrationDelegation,
|
|
17
|
+
statusCommitIntent,
|
|
13
18
|
statusOrchestration
|
|
14
19
|
} from '../task/orchestration.ts';
|
|
15
20
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
16
21
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
17
22
|
|
|
18
|
-
const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|
|
|
23
|
+
const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|hook-stop|advance|pause|status|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-status> [options]\n';
|
|
19
24
|
|
|
20
25
|
function usageFailure(message: string): void {
|
|
21
26
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -36,21 +41,32 @@ function taskOrchestration(args: string[] = []): void {
|
|
|
36
41
|
return;
|
|
37
42
|
}
|
|
38
43
|
const [taskRef, intent] = args;
|
|
39
|
-
if (![
|
|
44
|
+
if (![
|
|
45
|
+
'begin-or-resume', 'route', 'prepare', 'hook-start', 'hook-stop', 'advance', 'pause', 'status',
|
|
46
|
+
'commit-begin', 'commit-checkpoint', 'commit-complete', 'commit-recover', 'commit-abort', 'commit-status'
|
|
47
|
+
].includes(intent!)) {
|
|
40
48
|
usageFailure(`unknown intent '${intent}'`);
|
|
41
49
|
return;
|
|
42
50
|
}
|
|
43
51
|
const values: Record<string, string> = {};
|
|
44
52
|
const seen = new Set<string>();
|
|
53
|
+
let orchestrated = false;
|
|
45
54
|
for (let index = 2; index < args.length; index += 1) {
|
|
46
55
|
const flag = args[index]!;
|
|
56
|
+
if (flag === '--orchestrated') {
|
|
57
|
+
if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
|
|
58
|
+
seen.add(flag);
|
|
59
|
+
orchestrated = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
47
62
|
if (![
|
|
48
63
|
'--max-steps', '--executor-model', '--executor-reasoning-effort',
|
|
49
64
|
'--reviewer-model', '--reviewer-reasoning-effort',
|
|
50
65
|
'--client', '--requested-model', '--requested-reasoning-effort', '--parent-id', '--before-fingerprint',
|
|
51
66
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
52
67
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason',
|
|
53
|
-
'--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent'
|
|
68
|
+
'--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent',
|
|
69
|
+
'--baseline-head', '--token', '--kind', '--head', '--remote', '--ref', '--expected-head'
|
|
54
70
|
].includes(flag)) {
|
|
55
71
|
usageFailure(`unknown option '${flag}'`);
|
|
56
72
|
return;
|
|
@@ -67,13 +83,73 @@ function taskOrchestration(args: string[] = []): void {
|
|
|
67
83
|
}
|
|
68
84
|
values[flag] = value;
|
|
69
85
|
}
|
|
86
|
+
const commitIntentFlags: Record<string, readonly string[]> = {
|
|
87
|
+
'commit-begin': ['--agent', '--baseline-head', '--orchestrated'],
|
|
88
|
+
'commit-checkpoint': ['--token', '--kind', '--head', '--remote', '--ref'],
|
|
89
|
+
'commit-complete': ['--token', '--agent'],
|
|
90
|
+
'commit-recover': ['--agent'],
|
|
91
|
+
'commit-abort': ['--token', '--expected-head'],
|
|
92
|
+
'commit-status': []
|
|
93
|
+
};
|
|
94
|
+
if (intent! in commitIntentFlags) {
|
|
95
|
+
const allowed = commitIntentFlags[intent!]!;
|
|
96
|
+
const invalid = [...seen].find((flag) => !allowed.includes(flag));
|
|
97
|
+
if (invalid) { usageFailure(`intent '${intent}' does not accept '${invalid}'`); return; }
|
|
98
|
+
} else {
|
|
99
|
+
const commitOnly = [...seen].find((flag) => [
|
|
100
|
+
'--orchestrated', '--baseline-head', '--token', '--kind', '--head', '--remote', '--ref', '--expected-head'
|
|
101
|
+
].includes(flag));
|
|
102
|
+
if (commitOnly) { usageFailure(`intent '${intent}' does not accept '${commitOnly}'`); return; }
|
|
103
|
+
}
|
|
70
104
|
const requireValues = (flags: string[]) => flags.find((flag) => values[flag] === undefined);
|
|
71
105
|
if (values['--client'] !== undefined && !isAgentClientId(values['--client'])) {
|
|
72
106
|
usageFailure(`unknown client '${values['--client']}'`);
|
|
73
107
|
return;
|
|
74
108
|
}
|
|
75
109
|
let result;
|
|
76
|
-
if (intent === 'begin
|
|
110
|
+
if (intent === 'commit-begin') {
|
|
111
|
+
const missing = requireValues(['--agent', '--baseline-head']);
|
|
112
|
+
if (missing) { usageFailure(`intent 'commit-begin' requires '${missing}'`); return; }
|
|
113
|
+
const agent = normalizeAgentToken(values['--agent']!);
|
|
114
|
+
if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
|
|
115
|
+
result = beginCommitIntent(taskRef!, {
|
|
116
|
+
agent, orchestrated, baselineHead: values['--baseline-head']!
|
|
117
|
+
});
|
|
118
|
+
} else if (intent === 'commit-checkpoint') {
|
|
119
|
+
const missing = requireValues(['--token', '--kind', '--head']);
|
|
120
|
+
if (missing) { usageFailure(`intent 'commit-checkpoint' requires '${missing}'`); return; }
|
|
121
|
+
if (!['committed', 'pushed'].includes(values['--kind']!)) {
|
|
122
|
+
usageFailure('--kind must be committed or pushed'); return;
|
|
123
|
+
}
|
|
124
|
+
if (values['--kind'] === 'pushed') {
|
|
125
|
+
const pushedMissing = requireValues(['--remote', '--ref']);
|
|
126
|
+
if (pushedMissing) { usageFailure(`pushed checkpoint requires '${pushedMissing}'`); return; }
|
|
127
|
+
} else if (values['--remote'] !== undefined || values['--ref'] !== undefined) {
|
|
128
|
+
usageFailure('committed checkpoint does not accept --remote or --ref'); return;
|
|
129
|
+
}
|
|
130
|
+
result = checkpointCommitIntent(taskRef!, {
|
|
131
|
+
token: values['--token']!, kind: values['--kind'] as 'committed' | 'pushed',
|
|
132
|
+
head: values['--head']!, remote: values['--remote'], ref: values['--ref']
|
|
133
|
+
});
|
|
134
|
+
} else if (intent === 'commit-complete') {
|
|
135
|
+
const missing = requireValues(['--token', '--agent']);
|
|
136
|
+
if (missing) { usageFailure(`intent 'commit-complete' requires '${missing}'`); return; }
|
|
137
|
+
const agent = normalizeAgentToken(values['--agent']!);
|
|
138
|
+
if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
|
|
139
|
+
result = completeCommitIntent(taskRef!, { token: values['--token']!, agent });
|
|
140
|
+
} else if (intent === 'commit-recover') {
|
|
141
|
+
const missing = requireValues(['--agent']);
|
|
142
|
+
if (missing) { usageFailure(`intent 'commit-recover' requires '${missing}'`); return; }
|
|
143
|
+
const agent = normalizeAgentToken(values['--agent']!);
|
|
144
|
+
if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
|
|
145
|
+
result = recoverCommitIntent(taskRef!, { agent });
|
|
146
|
+
} else if (intent === 'commit-abort') {
|
|
147
|
+
const missing = requireValues(['--token', '--expected-head']);
|
|
148
|
+
if (missing) { usageFailure(`intent 'commit-abort' requires '${missing}'`); return; }
|
|
149
|
+
result = abortCommitIntent(taskRef!, { token: values['--token']!, expectedHead: values['--expected-head']! });
|
|
150
|
+
} else if (intent === 'commit-status') {
|
|
151
|
+
result = statusCommitIntent(taskRef!);
|
|
152
|
+
} else if (intent === 'begin-or-resume') {
|
|
77
153
|
const missing = requireValues(['--client']);
|
|
78
154
|
if (missing) { usageFailure(`intent 'begin-or-resume' requires '${missing}'`); return; }
|
|
79
155
|
const maxSteps = values['--max-steps'] === undefined ? undefined : Number(values['--max-steps']);
|
|
@@ -150,12 +226,6 @@ function taskOrchestration(args: string[] = []): void {
|
|
|
150
226
|
changedPaths: values['--changed-paths'] ? values['--changed-paths']!.split(',').filter(Boolean) : []
|
|
151
227
|
});
|
|
152
228
|
}
|
|
153
|
-
} else if (intent === 'stage-completed') {
|
|
154
|
-
const missing = requireValues(['--agent']);
|
|
155
|
-
if (missing) { usageFailure(`intent 'stage-completed' requires '${missing}'`); return; }
|
|
156
|
-
const agent = normalizeAgentToken(values['--agent']!);
|
|
157
|
-
if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
|
|
158
|
-
result = completeCommitOrchestrationStage(taskRef!, agent);
|
|
159
229
|
} else if (intent === 'advance') {
|
|
160
230
|
result = advanceOrchestration(taskRef!);
|
|
161
231
|
} else {
|
|
@@ -24,6 +24,15 @@ type ClientOptions = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
const MINIMUM_GITHUB_CLI_VERSION = '2.16.0';
|
|
27
|
+
const GITHUB_CLI_MAX_BUFFER = 64 * 1024 * 1024;
|
|
28
|
+
const ERROR_DETAIL_LIMIT = 4096;
|
|
29
|
+
|
|
30
|
+
function boundedFailureDetail(result: RunResult): string {
|
|
31
|
+
const diagnostic = `${result.stderr}\n${result.error?.message || ''}`.trim() || result.stdout.trim();
|
|
32
|
+
return diagnostic.length <= ERROR_DETAIL_LIMIT
|
|
33
|
+
? diagnostic
|
|
34
|
+
: `${diagnostic.slice(0, ERROR_DETAIL_LIMIT)}… [truncated]`;
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
function defaultRunner(args: string[], options: RunOptions): RunResult {
|
|
29
38
|
const command = process.env.AGENT_INFRA_GH_BIN || 'gh';
|
|
@@ -38,6 +47,7 @@ function defaultRunner(args: string[], options: RunOptions): RunResult {
|
|
|
38
47
|
encoding: 'utf8',
|
|
39
48
|
env: process.env,
|
|
40
49
|
input: options.input,
|
|
50
|
+
maxBuffer: GITHUB_CLI_MAX_BUFFER,
|
|
41
51
|
shell: false
|
|
42
52
|
});
|
|
43
53
|
return {
|
|
@@ -60,7 +70,15 @@ function defaultSleep(delayMs: number): void {
|
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
function classifyGitHubFailure(result: RunResult): PlatformError {
|
|
63
|
-
const
|
|
73
|
+
const errorCode = result.error && 'code' in result.error ? (result.error as NodeJS.ErrnoException).code : undefined;
|
|
74
|
+
if (errorCode === 'ENOBUFS') {
|
|
75
|
+
return {
|
|
76
|
+
code: 'PLATFORM_OUTPUT_TOO_LARGE',
|
|
77
|
+
message: 'GitHub CLI output exceeded the configured limit',
|
|
78
|
+
retryable: false
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const detail = boundedFailureDetail(result);
|
|
64
82
|
const lower = detail.toLowerCase();
|
|
65
83
|
if (/\b401\b|bad credentials|authentication required|not logged into/.test(lower)) {
|
|
66
84
|
return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required', retryable: false };
|
|
@@ -77,7 +95,7 @@ function classifyGitHubFailure(result: RunResult): PlatformError {
|
|
|
77
95
|
if (/\b422\b|validation failed/.test(lower)) {
|
|
78
96
|
return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request', retryable: false };
|
|
79
97
|
}
|
|
80
|
-
if (
|
|
98
|
+
if (errorCode === 'ENOENT') {
|
|
81
99
|
return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable', retryable: false };
|
|
82
100
|
}
|
|
83
101
|
return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed', retryable: false };
|