@alpacakit/agents 0.2.0-beta.2 → 0.2.0-beta.3
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 +21 -0
- package/dist/agents/builtin.d.ts +2 -2
- package/dist/agents/builtin.d.ts.map +1 -1
- package/dist/agents/builtin.js +6 -4
- package/dist/agents/claude/cli.d.ts +133 -0
- package/dist/agents/claude/cli.d.ts.map +1 -0
- package/dist/agents/{claude-stream.js → claude/cli.js} +81 -10
- package/dist/agents/claude/host.d.ts +14 -0
- package/dist/agents/claude/host.d.ts.map +1 -0
- package/dist/agents/{claude.js → claude/host.js} +51 -174
- package/dist/agents/{claude.d.ts → claude/index.d.ts} +10 -11
- package/dist/agents/claude/index.d.ts.map +1 -0
- package/dist/agents/claude/index.js +134 -0
- package/dist/agents/{claude-session.d.ts → claude/session.d.ts} +3 -15
- package/dist/agents/claude/session.d.ts.map +1 -0
- package/dist/agents/claude/session.js +321 -0
- package/dist/agents/codex/cli.d.ts +132 -0
- package/dist/agents/codex/cli.d.ts.map +1 -0
- package/dist/agents/codex/cli.js +110 -0
- package/dist/agents/codex/host.d.ts +14 -0
- package/dist/agents/codex/host.d.ts.map +1 -0
- package/dist/agents/codex/host.js +221 -0
- package/dist/agents/codex/index.d.ts +14 -0
- package/dist/agents/codex/index.d.ts.map +1 -0
- package/dist/agents/codex/index.js +127 -0
- package/dist/agents/codex/session.d.ts +21 -0
- package/dist/agents/codex/session.d.ts.map +1 -0
- package/dist/agents/codex/session.js +390 -0
- package/dist/agents/openai-compatible/fetch-engine.d.ts.map +1 -1
- package/dist/agents/openai-compatible/fetch-engine.js +3 -2
- package/dist/agents/openai-compatible/pi/engine.js +1 -1
- package/dist/agents/openai-compatible/pi/{session.d.ts → run.d.ts} +1 -1
- package/dist/agents/openai-compatible/pi/run.d.ts.map +1 -0
- package/dist/agents/openai-compatible/pi/sandbox.d.ts.map +1 -1
- package/dist/auth/login-probe.d.ts +6 -4
- package/dist/auth/login-probe.d.ts.map +1 -1
- package/dist/auth/login-probe.js +19 -13
- package/dist/auth/material.d.ts +1 -1
- package/dist/auth/material.d.ts.map +1 -1
- package/dist/codex/app-server.d.ts.map +1 -1
- package/dist/codex/app-server.js +13 -42
- package/dist/codex/index.d.ts +1 -1
- package/dist/codex/index.d.ts.map +1 -1
- package/dist/config/agent-config-validation.d.ts +1 -1
- package/dist/config/agent-config-validation.d.ts.map +1 -1
- package/dist/config/agent-configs.d.ts.map +1 -1
- package/dist/config/agent-configs.js +3 -3
- package/dist/config/compose.d.ts.map +1 -1
- package/dist/config/config.d.ts +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/model.d.ts +0 -1
- package/dist/config/model.d.ts.map +1 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +1 -1
- package/dist/config/store.d.ts.map +1 -1
- package/dist/config/store.js +6 -24
- package/dist/config/support.d.ts +0 -1
- package/dist/config/support.d.ts.map +1 -1
- package/dist/config/support.js +0 -9
- package/dist/core/cli-child.d.ts +56 -0
- package/dist/core/cli-child.d.ts.map +1 -0
- package/dist/core/cli-child.js +105 -0
- package/dist/core/fs.d.ts +1 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +10 -2
- package/dist/core/node-ports.d.ts.map +1 -1
- package/dist/core/node-ports.js +1 -5
- package/dist/core/session.d.ts +4 -0
- package/dist/core/session.d.ts.map +1 -1
- package/dist/fleet/store.d.ts.map +1 -1
- package/dist/fleet/store.js +4 -3
- package/dist/host/availability.d.ts +2 -1
- package/dist/host/availability.d.ts.map +1 -1
- package/dist/host/github-dir.d.ts.map +1 -1
- package/dist/host/github-dir.js +2 -1
- package/dist/host/mcp-list.d.ts.map +1 -1
- package/dist/host/mcp-list.js +5 -6
- package/dist/host/run-command.d.ts +2 -1
- package/dist/host/run-command.d.ts.map +1 -1
- package/dist/host/run-command.js +10 -19
- package/dist/host/session-files.d.ts +2 -1
- package/dist/host/session-files.d.ts.map +1 -1
- package/dist/host/session-files.js +2 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/kernel/classify.d.ts +1 -2
- package/dist/kernel/classify.d.ts.map +1 -1
- package/dist/kernel/classify.js +1 -1
- package/dist/kernel/cli-agent.d.ts.map +1 -1
- package/dist/kernel/cli-agent.js +26 -85
- package/dist/kernel/cli-login.d.ts.map +1 -1
- package/dist/kernel/cli-login.js +9 -30
- package/dist/kernel/facets.d.ts +1 -1
- package/dist/kernel/facets.d.ts.map +1 -1
- package/dist/kernel/login.d.ts.map +1 -1
- package/dist/kernel/login.js +2 -1
- package/dist/kernel/options.d.ts +1 -1
- package/dist/kernel/options.d.ts.map +1 -1
- package/dist/kernel/run.d.ts.map +1 -1
- package/dist/kernel/run.js +2 -1
- package/dist/kernel/session.d.ts +46 -39
- package/dist/kernel/session.d.ts.map +1 -1
- package/dist/kernel/session.js +128 -69
- package/dist/testing/fake-exec.d.ts +9 -10
- package/dist/testing/fake-exec.d.ts.map +1 -1
- package/dist/testing/fake-exec.js +14 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -1
- package/package.json +2 -2
- package/dist/agents/claude-session.d.ts.map +0 -1
- package/dist/agents/claude-session.js +0 -419
- package/dist/agents/claude-stream.d.ts +0 -65
- package/dist/agents/claude-stream.d.ts.map +0 -1
- package/dist/agents/claude.d.ts.map +0 -1
- package/dist/agents/codex-session.d.ts +0 -52
- package/dist/agents/codex-session.d.ts.map +0 -1
- package/dist/agents/codex-session.js +0 -495
- package/dist/agents/codex.d.ts +0 -20
- package/dist/agents/codex.d.ts.map +0 -1
- package/dist/agents/codex.js +0 -379
- package/dist/agents/openai-compatible/pi/session.d.ts.map +0 -1
- package/dist/core/process-close.d.ts +0 -11
- package/dist/core/process-close.d.ts.map +0 -1
- package/dist/core/process-close.js +0 -27
- /package/dist/agents/openai-compatible/pi/{session.js → run.js} +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in claude agent: the one-shot run contract
|
|
3
|
+
* (`claude -p [--model m] [--effort e] --output-format stream-json
|
|
4
|
+
* --permission-mode <mode> [tool args] --verbose`, prompt on stdin;
|
|
5
|
+
* read-only grants map to `--allowedTools`, `Bash(cmd:*)` for shell
|
|
6
|
+
* grants, no grants means `--tools ""`) composed with the session adapter
|
|
7
|
+
* and the host plane. Vocabulary lives in `cli.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { AGENT_ERROR_CODE } from "../../core/error.js";
|
|
10
|
+
import { readBoolean, readJsonRecord, readNumber, readRecord, readString, } from "../../core/json.js";
|
|
11
|
+
import { cliAvailability } from "../../host/availability.js";
|
|
12
|
+
import { parseEpochTimestamp, } from "../../kernel/classify.js";
|
|
13
|
+
import { defineCliAgent } from "../../kernel/cli-agent.js";
|
|
14
|
+
import { GRANT_KIND, SANDBOX_MODE } from "../../kernel/options.js";
|
|
15
|
+
import { CLAUDE, CLAUDE_PERMISSION_MODE, parseClaudeLine, parseClaudeResult, permissionModeArgs, } from "./cli.js";
|
|
16
|
+
import { claudeAuth, claudeLogin, claudeMcp, claudeSkills } from "./host.js";
|
|
17
|
+
import { claudeSessions } from "./session.js";
|
|
18
|
+
function grantToToolArg(grant) {
|
|
19
|
+
return grant.kind === GRANT_KIND.shell
|
|
20
|
+
? `Bash(${grant.command}:*)`
|
|
21
|
+
: grant.name;
|
|
22
|
+
}
|
|
23
|
+
function sharedArgs(ctx, customizations) {
|
|
24
|
+
return [
|
|
25
|
+
CLAUDE.cli.prompt,
|
|
26
|
+
...(ctx.model === null ? [] : [CLAUDE.cli.model, ctx.model]),
|
|
27
|
+
...(ctx.effort === null ? [] : [CLAUDE.cli.effort, ctx.effort]),
|
|
28
|
+
CLAUDE.cli.outputFormat,
|
|
29
|
+
CLAUDE.cli.streamJson,
|
|
30
|
+
...(customizations === "disabled"
|
|
31
|
+
? [
|
|
32
|
+
CLAUDE.cli.safeMode,
|
|
33
|
+
CLAUDE.cli.strictMcpConfig,
|
|
34
|
+
CLAUDE.cli.noSessionPersistence,
|
|
35
|
+
]
|
|
36
|
+
: []),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Structured quota signals in Claude CLI NDJSON output (v1 F2), scanned
|
|
41
|
+
* per line: rate_limit_event rejected (with resetsAt), api_error_status
|
|
42
|
+
* 429, and error === "rate_limit". Exported for custom agents that speak
|
|
43
|
+
* the same stream format.
|
|
44
|
+
*/
|
|
45
|
+
export function structuredQuotaMatcher() {
|
|
46
|
+
return (evidence) => {
|
|
47
|
+
let fallbackCooldownUntil = null;
|
|
48
|
+
for (const line of evidence.stdout.split(/\r?\n/)) {
|
|
49
|
+
const record = readJsonRecord(line.trim());
|
|
50
|
+
if (record === null) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const rateLimitInfo = readRecord(record, "rate_limit_info");
|
|
54
|
+
const resetsAt = rateLimitInfo === null ? null : readNumber(rateLimitInfo, "resetsAt");
|
|
55
|
+
const cooldownUntil = resetsAt === null ? null : parseEpochTimestamp(resetsAt);
|
|
56
|
+
const rejected = rateLimitInfo !== null &&
|
|
57
|
+
readString(rateLimitInfo, "status") === "rejected";
|
|
58
|
+
if (readString(record, "type") === "rate_limit_event" && rejected) {
|
|
59
|
+
return {
|
|
60
|
+
code: AGENT_ERROR_CODE.quota,
|
|
61
|
+
message: "Provider rejected the request for rate limiting",
|
|
62
|
+
data: { cooldownUntil },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (cooldownUntil !== null) {
|
|
66
|
+
fallbackCooldownUntil = cooldownUntil;
|
|
67
|
+
}
|
|
68
|
+
const apiError = readBoolean(record, "is_error") === true &&
|
|
69
|
+
readNumber(record, "api_error_status") === 429;
|
|
70
|
+
const rateLimitError = readString(record, "error") === "rate_limit" &&
|
|
71
|
+
readString(record, "type") !== null;
|
|
72
|
+
if (apiError || rateLimitError) {
|
|
73
|
+
return {
|
|
74
|
+
code: AGENT_ERROR_CODE.quota,
|
|
75
|
+
message: "Provider reported a rate-limit error",
|
|
76
|
+
data: { cooldownUntil: fallbackCooldownUntil },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function claude(options = {}) {
|
|
84
|
+
const customizations = options.customizations ?? "inherit";
|
|
85
|
+
const definition = defineCliAgent({
|
|
86
|
+
id: CLAUDE.id,
|
|
87
|
+
command: CLAUDE.command,
|
|
88
|
+
args: {
|
|
89
|
+
[SANDBOX_MODE.full]: (_sandbox, ctx) => [
|
|
90
|
+
...sharedArgs(ctx, customizations),
|
|
91
|
+
...permissionModeArgs(CLAUDE_PERMISSION_MODE.bypassPermissions),
|
|
92
|
+
CLAUDE.cli.verbose,
|
|
93
|
+
],
|
|
94
|
+
[SANDBOX_MODE.readOnly]: (sandbox, ctx) => [
|
|
95
|
+
...sharedArgs(ctx, customizations),
|
|
96
|
+
...permissionModeArgs(CLAUDE_PERMISSION_MODE.default),
|
|
97
|
+
...(sandbox.grants.length === 0
|
|
98
|
+
? [CLAUDE.cli.tools, ""]
|
|
99
|
+
: [
|
|
100
|
+
CLAUDE.cli.allowedTools,
|
|
101
|
+
sandbox.grants.map(grantToToolArg).join(","),
|
|
102
|
+
]),
|
|
103
|
+
CLAUDE.cli.verbose,
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
parseLine: parseClaudeLine,
|
|
107
|
+
parseResult: parseClaudeResult,
|
|
108
|
+
classify: [structuredQuotaMatcher()],
|
|
109
|
+
// F6: Claude CLI can report a structured quota rejection on exit 0.
|
|
110
|
+
classifyOnSuccess: [structuredQuotaMatcher()],
|
|
111
|
+
availability: cliAvailability({
|
|
112
|
+
command: CLAUDE.command,
|
|
113
|
+
commandMissingRemediation: {
|
|
114
|
+
detail: "Install Claude Code and ensure `claude` is on PATH.",
|
|
115
|
+
command: null,
|
|
116
|
+
},
|
|
117
|
+
authentication: {
|
|
118
|
+
args: CLAUDE.auth.statusArgs,
|
|
119
|
+
remediation: {
|
|
120
|
+
detail: "Sign in to Claude Code.",
|
|
121
|
+
command: {
|
|
122
|
+
executable: CLAUDE.command,
|
|
123
|
+
args: CLAUDE.auth.loginArgs,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
auth: claudeAuth,
|
|
129
|
+
login: claudeLogin,
|
|
130
|
+
skills: claudeSkills,
|
|
131
|
+
mcp: claudeMcp,
|
|
132
|
+
});
|
|
133
|
+
return { ...definition, session: claudeSessions({ customizations }) };
|
|
134
|
+
}
|
|
@@ -16,22 +16,10 @@
|
|
|
16
16
|
* is emitted until then), `--resume <id>` continues a recorded one;
|
|
17
17
|
* - the child needs `USER` in its env: without it the CLI reports "Not logged in".
|
|
18
18
|
*/
|
|
19
|
-
import { type SessionCapability } from "
|
|
20
|
-
|
|
21
|
-
readonly default: "default";
|
|
22
|
-
readonly acceptEdits: "acceptEdits";
|
|
23
|
-
readonly auto: "auto";
|
|
24
|
-
readonly bypassPermissions: "bypassPermissions";
|
|
25
|
-
readonly dontAsk: "dontAsk";
|
|
26
|
-
readonly plan: "plan";
|
|
27
|
-
};
|
|
28
|
-
export type ClaudePermissionMode = (typeof CLAUDE_PERMISSION_MODE)[keyof typeof CLAUDE_PERMISSION_MODE];
|
|
29
|
-
/** Spelled as the transcript's `permissionMode` records it. */
|
|
30
|
-
export type ClaudeSessionPermissions = {
|
|
31
|
-
readonly mode: ClaudePermissionMode;
|
|
32
|
-
};
|
|
19
|
+
import { type SessionCapability } from "../../kernel/session.js";
|
|
20
|
+
import { type ClaudeSessionPermissions } from "./cli.js";
|
|
33
21
|
export type ClaudeSessionOptions = {
|
|
34
22
|
readonly customizations: "inherit" | "disabled";
|
|
35
23
|
};
|
|
36
24
|
export declare function claudeSessions(options: ClaudeSessionOptions): SessionCapability<ClaudeSessionPermissions>;
|
|
37
|
-
//# sourceMappingURL=
|
|
25
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/agents/claude/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAoCH,OAAO,EAGL,KAAK,iBAAiB,EAOvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAIL,KAAK,wBAAwB,EAG9B,MAAM,UAAU,CAAC;AAqBlB,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,cAAc,EAAE,SAAS,GAAG,UAAU,CAAC;CACjD,CAAC;AAuWF,wBAAgB,cAAc,CAC5B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CAAC,wBAAwB,CAAC,CAoB7C"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude session adapter: one `claude -p --input-format stream-json
|
|
3
|
+
* --output-format stream-json` process held open across turns.
|
|
4
|
+
*
|
|
5
|
+
* Protocol facts (claude 2.1.270, T52 / T61 lab runs):
|
|
6
|
+
* - a turn is one user line `{"type":"user","message":{"role":"user","content":…}}`
|
|
7
|
+
* and ends with the `result` line; a user line written mid-turn is absorbed
|
|
8
|
+
* into the running turn at its next tool boundary (`steer`);
|
|
9
|
+
* - `{"type":"control_request","request_id":…,"request":{"subtype":"interrupt"}}`
|
|
10
|
+
* is answered by a `control_response`, and the turn then ends with
|
|
11
|
+
* `is_error: true` / `terminal_reason: "aborted_tools"`;
|
|
12
|
+
* - approval prompts reach stdout only with `--permission-prompt-tool stdio`, as
|
|
13
|
+
* `control_request` / `can_use_tool`, and are answered with a
|
|
14
|
+
* `control_response` whose `behavior` is `allow` (+ `updatedInput`) or `deny`;
|
|
15
|
+
* - `--session-id <uuid>` fixes the id before the first turn (no `init` line
|
|
16
|
+
* is emitted until then), `--resume <id>` continues a recorded one;
|
|
17
|
+
* - the child needs `USER` in its env: without it the CLI reports "Not logged in".
|
|
18
|
+
*/
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { AGENT_ERROR_CODE, AgentError, TIMEOUT_KIND, } from "../../core/error.js";
|
|
22
|
+
import { readJsonRecord, readNumber, readRecord, readString, } from "../../core/json.js";
|
|
23
|
+
import { redactSecrets } from "../../core/redact.js";
|
|
24
|
+
import { AGENT_EVENT_TYPE } from "../../core/run.js";
|
|
25
|
+
import { APPROVAL_DECISION, SESSION_OWNER_KIND, TURN_OUTCOME, } from "../../core/session.js";
|
|
26
|
+
import { fileExists, readDirectoryNames, readJsonFile, scanJsonlFirstLast, } from "../../host/session-files.js";
|
|
27
|
+
import { assertHostSessionId, SessionProcess, driveSession, sessionStateError, } from "../../kernel/session.js";
|
|
28
|
+
import { CLAUDE, CLAUDE_PERMISSION_MODE, parseClaudeRecord, permissionModeArgs, } from "./cli.js";
|
|
29
|
+
const CONTROL_RESPONSE_TIMEOUT_MS = 30_000;
|
|
30
|
+
const DECLINED_MESSAGE = "Declined by the session owner";
|
|
31
|
+
function sessionArgs(permissions, target, options) {
|
|
32
|
+
return [
|
|
33
|
+
CLAUDE.cli.prompt,
|
|
34
|
+
CLAUDE.cli.inputFormat,
|
|
35
|
+
CLAUDE.cli.streamJson,
|
|
36
|
+
CLAUDE.cli.outputFormat,
|
|
37
|
+
CLAUDE.cli.streamJson,
|
|
38
|
+
CLAUDE.cli.verbose,
|
|
39
|
+
...permissionModeArgs(permissions.mode),
|
|
40
|
+
CLAUDE.cli.permissionPromptTool,
|
|
41
|
+
CLAUDE.cli.stdio,
|
|
42
|
+
// A session's whole point is the recorded conversation, so `disabled`
|
|
43
|
+
// drops only the customization flags, never `--no-session-persistence`.
|
|
44
|
+
...(options.customizations === "disabled"
|
|
45
|
+
? [CLAUDE.cli.safeMode, CLAUDE.cli.strictMcpConfig]
|
|
46
|
+
: []),
|
|
47
|
+
target.kind === "resume" ? CLAUDE.cli.resume : CLAUDE.cli.sessionId,
|
|
48
|
+
target.sessionId,
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The Claude side of a Session: user lines and control requests out,
|
|
53
|
+
* stream-json records in. Control responses are correlated here (they
|
|
54
|
+
* answer this protocol's own requests); everything else is decoded into
|
|
55
|
+
* the driver's sink. Claude's stream carries no turn id, so the turn id
|
|
56
|
+
* is a per-session counter.
|
|
57
|
+
*/
|
|
58
|
+
class ClaudeProtocol {
|
|
59
|
+
process;
|
|
60
|
+
ctx;
|
|
61
|
+
pendingControls = new Map();
|
|
62
|
+
turnCount = 0;
|
|
63
|
+
interruptRequested = false;
|
|
64
|
+
incoming;
|
|
65
|
+
constructor(process, ctx) {
|
|
66
|
+
this.process = process;
|
|
67
|
+
this.ctx = ctx;
|
|
68
|
+
this.incoming = this.records();
|
|
69
|
+
}
|
|
70
|
+
get exit() {
|
|
71
|
+
return this.process.exit;
|
|
72
|
+
}
|
|
73
|
+
get stderrTail() {
|
|
74
|
+
return this.process.stderr;
|
|
75
|
+
}
|
|
76
|
+
async startTurn(text) {
|
|
77
|
+
this.turnCount += 1;
|
|
78
|
+
this.writeUserLine(text);
|
|
79
|
+
return String(this.turnCount);
|
|
80
|
+
}
|
|
81
|
+
async steer(_turnId, text) {
|
|
82
|
+
this.writeUserLine(text);
|
|
83
|
+
}
|
|
84
|
+
async interrupt() {
|
|
85
|
+
const requestId = randomUUID();
|
|
86
|
+
this.interruptRequested = true;
|
|
87
|
+
const answered = this.awaitControlResponse(requestId);
|
|
88
|
+
this.process.writeLine(JSON.stringify({
|
|
89
|
+
type: CLAUDE.json.type.controlRequest,
|
|
90
|
+
[CLAUDE.json.field.requestId]: requestId,
|
|
91
|
+
request: { subtype: CLAUDE.json.subtype.interrupt },
|
|
92
|
+
}));
|
|
93
|
+
try {
|
|
94
|
+
await answered;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// An unanswered interrupt must not color the next turn's outcome.
|
|
98
|
+
this.interruptRequested = false;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
respond(approval, decision) {
|
|
103
|
+
const response = decision === APPROVAL_DECISION.accept
|
|
104
|
+
? {
|
|
105
|
+
behavior: CLAUDE.json.behavior.allow,
|
|
106
|
+
updatedInput: approval.input,
|
|
107
|
+
}
|
|
108
|
+
: {
|
|
109
|
+
behavior: CLAUDE.json.behavior.deny,
|
|
110
|
+
message: DECLINED_MESSAGE,
|
|
111
|
+
};
|
|
112
|
+
this.process.writeLine(JSON.stringify({
|
|
113
|
+
type: CLAUDE.json.type.controlResponse,
|
|
114
|
+
response: {
|
|
115
|
+
subtype: CLAUDE.json.subtype.success,
|
|
116
|
+
[CLAUDE.json.field.requestId]: approval.requestId,
|
|
117
|
+
response,
|
|
118
|
+
},
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
async close() {
|
|
122
|
+
await this.process.close();
|
|
123
|
+
}
|
|
124
|
+
decode(record, sink) {
|
|
125
|
+
const type = readString(record, CLAUDE.json.field.type);
|
|
126
|
+
if (type === CLAUDE.json.type.assistant) {
|
|
127
|
+
for (const event of parseClaudeRecord(record)) {
|
|
128
|
+
if (event.type === AGENT_EVENT_TYPE.message) {
|
|
129
|
+
sink.message(event.text);
|
|
130
|
+
}
|
|
131
|
+
else if (event.type === AGENT_EVENT_TYPE.tool) {
|
|
132
|
+
sink.tool(event.name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (type === CLAUDE.json.type.result) {
|
|
138
|
+
const isError = record[CLAUDE.json.field.isError] === true;
|
|
139
|
+
const outcome = this.interruptRequested
|
|
140
|
+
? TURN_OUTCOME.interrupted
|
|
141
|
+
: isError
|
|
142
|
+
? TURN_OUTCOME.failed
|
|
143
|
+
: TURN_OUTCOME.completed;
|
|
144
|
+
this.interruptRequested = false;
|
|
145
|
+
sink.endTurn(outcome, readString(record, CLAUDE.json.field.result));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (type === CLAUDE.json.type.controlRequest) {
|
|
149
|
+
this.decodeControlRequest(record, sink);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
writeUserLine(text) {
|
|
153
|
+
this.process.writeLine(JSON.stringify({
|
|
154
|
+
type: CLAUDE.json.type.user,
|
|
155
|
+
message: { role: CLAUDE.json.type.user, content: text },
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
awaitControlResponse(requestId) {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const cancelTimeout = this.ctx.clock.setTimeout(() => {
|
|
161
|
+
this.pendingControls.delete(requestId);
|
|
162
|
+
reject(new AgentError({
|
|
163
|
+
code: AGENT_ERROR_CODE.timeout,
|
|
164
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.timeout),
|
|
165
|
+
message: "Claude did not answer the control request in time",
|
|
166
|
+
data: {
|
|
167
|
+
kind: TIMEOUT_KIND.total,
|
|
168
|
+
limitMs: CONTROL_RESPONSE_TIMEOUT_MS,
|
|
169
|
+
},
|
|
170
|
+
agentId: CLAUDE.id,
|
|
171
|
+
}));
|
|
172
|
+
}, CONTROL_RESPONSE_TIMEOUT_MS);
|
|
173
|
+
this.pendingControls.set(requestId, { resolve, reject, cancelTimeout });
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** stdout records minus the control responses, which are answered here; settles waiters when the child is gone. */
|
|
177
|
+
async *records() {
|
|
178
|
+
try {
|
|
179
|
+
for await (const line of this.process.lines()) {
|
|
180
|
+
const record = readJsonRecord(line);
|
|
181
|
+
if (record === null) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (readString(record, CLAUDE.json.field.type) ===
|
|
185
|
+
CLAUDE.json.type.controlResponse) {
|
|
186
|
+
this.handleControlResponse(record);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
yield record;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
for (const pending of this.pendingControls.values()) {
|
|
194
|
+
pending.cancelTimeout();
|
|
195
|
+
pending.reject(sessionStateError(CLAUDE.id, "session process exited"));
|
|
196
|
+
}
|
|
197
|
+
this.pendingControls.clear();
|
|
198
|
+
this.interruptRequested = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
decodeControlRequest(record, sink) {
|
|
202
|
+
const requestId = readString(record, CLAUDE.json.field.requestId);
|
|
203
|
+
const request = readRecord(record, CLAUDE.json.field.request);
|
|
204
|
+
const subtype = request === null ? null : readString(request, CLAUDE.json.field.subtype);
|
|
205
|
+
if (requestId === null || request === null) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (subtype !== CLAUDE.json.subtype.canUseTool) {
|
|
209
|
+
this.ctx.logger.log("warn", `ignoring unsupported claude control request "${subtype ?? "?"}"`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const toolName = readString(request, CLAUDE.json.field.toolName) ?? "";
|
|
213
|
+
const description = readString(request, CLAUDE.json.field.description);
|
|
214
|
+
sink.approvalRequested(requestId, description === null ? toolName : `${toolName} ${description}`, { requestId, input: request[CLAUDE.json.field.input] });
|
|
215
|
+
}
|
|
216
|
+
handleControlResponse(record) {
|
|
217
|
+
const response = readRecord(record, CLAUDE.json.field.response);
|
|
218
|
+
const requestId = response === null
|
|
219
|
+
? null
|
|
220
|
+
: readString(response, CLAUDE.json.field.requestId);
|
|
221
|
+
if (response === null || requestId === null) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const pending = this.pendingControls.get(requestId);
|
|
225
|
+
if (pending === undefined) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.pendingControls.delete(requestId);
|
|
229
|
+
pending.cancelTimeout();
|
|
230
|
+
if (readString(response, CLAUDE.json.field.subtype) ===
|
|
231
|
+
CLAUDE.json.subtype.error) {
|
|
232
|
+
pending.reject(sessionStateError(CLAUDE.id, redactSecrets(readString(response, CLAUDE.json.field.error) ??
|
|
233
|
+
"control request failed")));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
pending.resolve();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function openClaudeSession(target, options, ctx, sessionOptions) {
|
|
240
|
+
const process = await SessionProcess.spawn({
|
|
241
|
+
command: CLAUDE.command,
|
|
242
|
+
args: sessionArgs(options.permissions, target, sessionOptions),
|
|
243
|
+
cwd: options.workspace,
|
|
244
|
+
ctx,
|
|
245
|
+
agentId: CLAUDE.id,
|
|
246
|
+
});
|
|
247
|
+
return driveSession({
|
|
248
|
+
id: target.sessionId,
|
|
249
|
+
agentId: CLAUDE.id,
|
|
250
|
+
protocol: new ClaudeProtocol(process, ctx),
|
|
251
|
+
logger: ctx.logger,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function claudeRoot(ctx, ...segments) {
|
|
255
|
+
return path.join(ctx.location.homeDir, CLAUDE.files.root, ...segments);
|
|
256
|
+
}
|
|
257
|
+
/** `~/.claude/projects/<encoded cwd>/<id>.jsonl` — the project directory name is not derivable, so scan. */
|
|
258
|
+
async function findClaudeTranscript(ctx, id) {
|
|
259
|
+
const root = claudeRoot(ctx, CLAUDE.files.projects);
|
|
260
|
+
for (const project of await readDirectoryNames(root)) {
|
|
261
|
+
const candidate = path.join(root, project, `${id}${CLAUDE.files.transcriptExtension}`);
|
|
262
|
+
if (await fileExists(candidate)) {
|
|
263
|
+
return candidate;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
/** `~/.claude/sessions/<pid>.json` — one file per live CLI process, keyed by pid. */
|
|
269
|
+
async function claudeRegistryOwner(ctx, id) {
|
|
270
|
+
const root = claudeRoot(ctx, CLAUDE.files.sessions);
|
|
271
|
+
for (const name of await readDirectoryNames(root)) {
|
|
272
|
+
if (!name.endsWith(CLAUDE.files.registryExtension)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const entry = await readJsonFile(path.join(root, name));
|
|
276
|
+
if (entry === null || readString(entry, CLAUDE.files.sessionId) !== id) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
kind: SESSION_OWNER_KIND.process,
|
|
281
|
+
pid: readNumber(entry, CLAUDE.files.pid),
|
|
282
|
+
entrypoint: readString(entry, CLAUDE.files.entrypoint),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return { kind: SESSION_OWNER_KIND.none };
|
|
286
|
+
}
|
|
287
|
+
function permissionModeFrom(value) {
|
|
288
|
+
return value !== null &&
|
|
289
|
+
Object.values(CLAUDE_PERMISSION_MODE).includes(value)
|
|
290
|
+
? value
|
|
291
|
+
: null;
|
|
292
|
+
}
|
|
293
|
+
async function inspectClaudeSession(id, ctx) {
|
|
294
|
+
assertHostSessionId(id, CLAUDE.id);
|
|
295
|
+
const transcript = await findClaudeTranscript(ctx, id);
|
|
296
|
+
if (transcript === null) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
const cwd = await scanJsonlFirstLast(transcript, (record) => readString(record, CLAUDE.files.cwd));
|
|
300
|
+
if (cwd.first === null) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
const modes = await scanJsonlFirstLast(transcript, (record) => readString(record, CLAUDE.files.permissionMode));
|
|
304
|
+
const mode = permissionModeFrom(modes.last);
|
|
305
|
+
return {
|
|
306
|
+
workspace: cwd.first,
|
|
307
|
+
lastPermissions: mode === null ? null : { mode },
|
|
308
|
+
owner: await claudeRegistryOwner(ctx, id),
|
|
309
|
+
archived: false,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
export function claudeSessions(options) {
|
|
313
|
+
return {
|
|
314
|
+
open: (resolved, ctx) => openClaudeSession({ kind: "new", sessionId: randomUUID() }, resolved, ctx, options),
|
|
315
|
+
resume: async (id, resolved, ctx) => {
|
|
316
|
+
assertHostSessionId(id, CLAUDE.id);
|
|
317
|
+
return openClaudeSession({ kind: "resume", sessionId: id }, resolved, ctx, options);
|
|
318
|
+
},
|
|
319
|
+
inspect: inspectClaudeSession,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Codex vocabulary, spelled once: the `codex exec` flags, the app-server
|
|
3
|
+
* notification and file fields the session reads, the permission words, and
|
|
4
|
+
* the host's own file names. The run adapter (`index.ts`), the session
|
|
5
|
+
* adapter (`session.ts`), and the host plane (`host.ts`) all read from here;
|
|
6
|
+
* JSON-RPC method names and sandbox words come from `codex/protocol.ts`.
|
|
7
|
+
*/
|
|
8
|
+
import type { CODEX_SANDBOX_MODE } from "../../codex/protocol.js";
|
|
9
|
+
export declare const CODEX_HOME_ENV_VAR = "CODEX_HOME";
|
|
10
|
+
export declare const CODEX_API_KEY_ENV_VAR = "OPENAI_API_KEY";
|
|
11
|
+
export declare const CODEX: {
|
|
12
|
+
readonly id: "codex";
|
|
13
|
+
readonly command: "codex";
|
|
14
|
+
readonly cli: {
|
|
15
|
+
readonly exec: "exec";
|
|
16
|
+
readonly model: "--model";
|
|
17
|
+
readonly config: "-c";
|
|
18
|
+
readonly reasoningEffortConfigKey: "model_reasoning_effort";
|
|
19
|
+
readonly json: "--json";
|
|
20
|
+
readonly skipGitRepoCheck: "--skip-git-repo-check";
|
|
21
|
+
readonly sandbox: "--sandbox";
|
|
22
|
+
readonly workspace: "-C";
|
|
23
|
+
};
|
|
24
|
+
readonly auth: {
|
|
25
|
+
readonly statusArgs: readonly ["login", "status"];
|
|
26
|
+
readonly oauthLoginArgs: readonly ["login"];
|
|
27
|
+
readonly deviceLoginArgs: readonly ["login", "--device-auth"];
|
|
28
|
+
};
|
|
29
|
+
readonly json: {
|
|
30
|
+
readonly field: {
|
|
31
|
+
readonly type: "type";
|
|
32
|
+
readonly text: "text";
|
|
33
|
+
readonly item: "item";
|
|
34
|
+
};
|
|
35
|
+
readonly event: {
|
|
36
|
+
readonly turnCompleted: "turn.completed";
|
|
37
|
+
readonly agentMessage: "agent_message";
|
|
38
|
+
readonly itemCompleted: "item.completed";
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
readonly mcp: {
|
|
42
|
+
readonly add: "add";
|
|
43
|
+
readonly env: "--env";
|
|
44
|
+
readonly executableSeparator: "--";
|
|
45
|
+
readonly mcp: "mcp";
|
|
46
|
+
readonly remove: "remove";
|
|
47
|
+
};
|
|
48
|
+
readonly notification: {
|
|
49
|
+
readonly threadId: "threadId";
|
|
50
|
+
readonly turnId: "turnId";
|
|
51
|
+
readonly turn: "turn";
|
|
52
|
+
readonly item: "item";
|
|
53
|
+
readonly id: "id";
|
|
54
|
+
readonly type: "type";
|
|
55
|
+
readonly text: "text";
|
|
56
|
+
readonly status: "status";
|
|
57
|
+
readonly items: "items";
|
|
58
|
+
readonly phase: "phase";
|
|
59
|
+
readonly finalAnswer: "final_answer";
|
|
60
|
+
readonly agentMessage: "agentMessage";
|
|
61
|
+
readonly userMessage: "userMessage";
|
|
62
|
+
readonly reasoning: "reasoning";
|
|
63
|
+
readonly command: "command";
|
|
64
|
+
readonly reason: "reason";
|
|
65
|
+
};
|
|
66
|
+
readonly turnStatus: {
|
|
67
|
+
readonly completed: "completed";
|
|
68
|
+
readonly interrupted: "interrupted";
|
|
69
|
+
readonly failed: "failed";
|
|
70
|
+
};
|
|
71
|
+
readonly decision: {
|
|
72
|
+
readonly accept: "accept";
|
|
73
|
+
readonly decline: "decline";
|
|
74
|
+
};
|
|
75
|
+
readonly error: {
|
|
76
|
+
readonly archived: "is archived";
|
|
77
|
+
readonly methodNotFound: -32601;
|
|
78
|
+
readonly unsupported: "unsupported server request";
|
|
79
|
+
};
|
|
80
|
+
readonly files: {
|
|
81
|
+
readonly root: ".codex";
|
|
82
|
+
readonly sessions: "sessions";
|
|
83
|
+
readonly archivedSessions: "archived_sessions";
|
|
84
|
+
readonly writerLocks: "thread-writer-locks";
|
|
85
|
+
readonly lockExtension: ".lock";
|
|
86
|
+
readonly rolloutExtension: ".jsonl";
|
|
87
|
+
readonly type: "type";
|
|
88
|
+
readonly payload: "payload";
|
|
89
|
+
readonly sessionMeta: "session_meta";
|
|
90
|
+
readonly turnContext: "turn_context";
|
|
91
|
+
readonly cwd: "cwd";
|
|
92
|
+
readonly approvalPolicy: "approval_policy";
|
|
93
|
+
readonly approvalsReviewer: "approvals_reviewer";
|
|
94
|
+
readonly sandboxPolicy: "sandbox_policy";
|
|
95
|
+
readonly writableRoots: "writable_roots";
|
|
96
|
+
readonly networkAccess: "network_access";
|
|
97
|
+
readonly excludeTmpdirEnvVar: "exclude_tmpdir_env_var";
|
|
98
|
+
readonly excludeSlashTmp: "exclude_slash_tmp";
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/** `approval_policy` words (codex 0.153.4 `AskForApproval`; the granular object form is not modeled). */
|
|
102
|
+
export declare const CODEX_APPROVAL_POLICY: {
|
|
103
|
+
readonly untrusted: "untrusted";
|
|
104
|
+
readonly onRequest: "on-request";
|
|
105
|
+
readonly never: "never";
|
|
106
|
+
};
|
|
107
|
+
export type CodexApprovalPolicy = (typeof CODEX_APPROVAL_POLICY)[keyof typeof CODEX_APPROVAL_POLICY];
|
|
108
|
+
export declare const CODEX_APPROVALS_REVIEWER: {
|
|
109
|
+
readonly user: "user";
|
|
110
|
+
readonly autoReview: "auto_review";
|
|
111
|
+
readonly guardianSubagent: "guardian_subagent";
|
|
112
|
+
};
|
|
113
|
+
export type CodexApprovalsReviewer = (typeof CODEX_APPROVALS_REVIEWER)[keyof typeof CODEX_APPROVALS_REVIEWER];
|
|
114
|
+
/** Spelled as the rollout's `turn_context.sandbox_policy` records it. */
|
|
115
|
+
export type { CodexSandboxMode } from "../../codex/protocol.js";
|
|
116
|
+
export type CodexSandboxPolicy = {
|
|
117
|
+
readonly type: typeof CODEX_SANDBOX_MODE.readOnly;
|
|
118
|
+
} | {
|
|
119
|
+
readonly type: typeof CODEX_SANDBOX_MODE.dangerFullAccess;
|
|
120
|
+
} | {
|
|
121
|
+
readonly type: typeof CODEX_SANDBOX_MODE.workspaceWrite;
|
|
122
|
+
readonly writableRoots: readonly string[];
|
|
123
|
+
readonly networkAccess: boolean;
|
|
124
|
+
readonly excludeTmpdirEnvVar: boolean;
|
|
125
|
+
readonly excludeSlashTmp: boolean;
|
|
126
|
+
};
|
|
127
|
+
export type CodexSessionPermissions = {
|
|
128
|
+
readonly approvalPolicy: CodexApprovalPolicy;
|
|
129
|
+
readonly approvalsReviewer: CodexApprovalsReviewer;
|
|
130
|
+
readonly sandbox: CodexSandboxPolicy;
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/agents/codex/cli.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAC/C,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;AAEtD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFR,CAAC;AAEX,yGAAyG;AACzG,eAAO,MAAM,qBAAqB;;;;CAIxB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAC7B,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,OAAO,qBAAqB,CAAC,CAAC;AAErE,eAAO,MAAM,wBAAwB;;;;CAI3B,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAChC,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3E,yEAAyE;AACzE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,kBAAkB,CAAC,QAAQ,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,kBAAkB,CAAC,gBAAgB,CAAA;CAAE,GAC7D;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,kBAAkB,CAAC,cAAc,CAAC;IACxD,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CACnC,CAAC;AAEN,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAC7C,QAAQ,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;CACtC,CAAC"}
|