@alpacakit/agents 0.1.0-beta.39 → 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 +142 -1
- 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/cli.js +182 -0
- package/dist/agents/claude/host.d.ts +14 -0
- package/dist/agents/claude/host.d.ts.map +1 -0
- package/dist/agents/claude/host.js +319 -0
- package/dist/agents/{claude.d.ts → claude/index.d.ts} +11 -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 +25 -0
- 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 +50 -1
- package/dist/codex/app-server.d.ts.map +1 -1
- package/dist/codex/app-server.js +140 -66
- package/dist/codex/index.d.ts +3 -2
- package/dist/codex/index.d.ts.map +1 -1
- package/dist/codex/index.js +2 -1
- package/dist/codex/protocol.d.ts +101 -0
- package/dist/codex/protocol.d.ts.map +1 -1
- package/dist/codex/protocol.js +78 -0
- 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 +104 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +29 -0
- 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 +23 -0
- package/dist/host/session-files.d.ts.map +1 -0
- package/dist/host/session-files.js +92 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/internal/controlled-process/supervisor-runtime.js +27 -1
- 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/definition.d.ts +11 -2
- package/dist/kernel/definition.d.ts.map +1 -1
- package/dist/kernel/facets.d.ts +38 -1
- package/dist/kernel/facets.d.ts.map +1 -1
- package/dist/kernel/facets.js +20 -0
- 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/runtime.d.ts +10 -6
- package/dist/kernel/runtime.d.ts.map +1 -1
- package/dist/kernel/runtime.js +4 -1
- package/dist/kernel/session.d.ts +104 -0
- package/dist/kernel/session.d.ts.map +1 -0
- package/dist/kernel/session.js +314 -0
- package/dist/testing/fake-exec.d.ts +10 -5
- package/dist/testing/fake-exec.d.ts.map +1 -1
- package/dist/testing/fake-exec.js +17 -2
- 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.d.ts.map +0 -1
- package/dist/agents/claude.js +0 -543
- package/dist/agents/codex.d.ts +0 -19
- package/dist/agents/codex.d.ts.map +0 -1
- package/dist/agents/codex.js +0 -377
- package/dist/agents/openai-compatible/pi/session.d.ts.map +0 -1
- /package/dist/agents/openai-compatible/pi/{session.js → run.js} +0 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The claude host plane: auth materials and sign-in, installed skills
|
|
3
|
+
* (`~/.claude/skills`, project `.claude/skills`, plugins), and MCP
|
|
4
|
+
* registration through the CLI. Outside a run and a session; every path
|
|
5
|
+
* comes from `HostContext.location`, never `os.homedir()`.
|
|
6
|
+
*/
|
|
7
|
+
import { cp, mkdir, readdir, readFile, rm, stat } from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { LOGIN_METHOD, } from "../../auth/login.js";
|
|
10
|
+
import { cliLoginProbe } from "../../auth/login-probe.js";
|
|
11
|
+
import { AUTH_MATERIAL_KIND, envVarAuthMaterial } from "../../auth/material.js";
|
|
12
|
+
import { requireSecret } from "../../auth/secrets.js";
|
|
13
|
+
import { NO_FAILURE_EVIDENCE } from "../../core/cli-child.js";
|
|
14
|
+
import { AGENT_ERROR_CODE, AgentError } from "../../core/error.js";
|
|
15
|
+
import { isErrnoCode } from "../../core/fs.js";
|
|
16
|
+
import { TEXT_ENCODING } from "../../core/values.js";
|
|
17
|
+
import { INSTALL_STATUS, MCP_TRANSPORT, SKILL_SOURCE_KIND, } from "../../host/capabilities.js";
|
|
18
|
+
import { withStagedGithubDirectory, } from "../../host/github-dir.js";
|
|
19
|
+
import { listMcpServersViaCli } from "../../host/mcp-list.js";
|
|
20
|
+
import { hostCommandFailedError, runHostCommand, runHostCommandResult, } from "../../host/run-command.js";
|
|
21
|
+
import { cliLoginCommand } from "../../kernel/cli-login.js";
|
|
22
|
+
import { CLAUDE, CLAUDE_API_KEY_ENV_VAR, CLAUDE_CONFIG_DIR_ENV_VAR, } from "./cli.js";
|
|
23
|
+
const COMPETING_TOKEN_VARS = [
|
|
24
|
+
CLAUDE_API_KEY_ENV_VAR,
|
|
25
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
26
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
27
|
+
];
|
|
28
|
+
const COMPETING_CONFIG_VARS = [
|
|
29
|
+
CLAUDE_CONFIG_DIR_ENV_VAR,
|
|
30
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
31
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
32
|
+
];
|
|
33
|
+
const CLAUDE_PROJECT_SKILL_ROOT = CLAUDE.files.root;
|
|
34
|
+
const SKILLS_DIRECTORY = "skills";
|
|
35
|
+
const SKILL_MARKDOWN = "SKILL.md";
|
|
36
|
+
const PLUGIN_ALREADY_INSTALLED = /already installed/i;
|
|
37
|
+
const CLAUDE_SKILL_SOURCE = {
|
|
38
|
+
project: "claude:project",
|
|
39
|
+
user: "claude:user",
|
|
40
|
+
};
|
|
41
|
+
const homeDirHandler = envVarAuthMaterial({
|
|
42
|
+
set: (material) => ({ [CLAUDE_CONFIG_DIR_ENV_VAR]: material.path }),
|
|
43
|
+
unset: COMPETING_TOKEN_VARS,
|
|
44
|
+
check: (material, ctx) => cliLoginProbe({
|
|
45
|
+
command: CLAUDE.command,
|
|
46
|
+
args: CLAUDE.auth.statusArgs,
|
|
47
|
+
envPatch: {
|
|
48
|
+
set: { [CLAUDE_CONFIG_DIR_ENV_VAR]: material.path },
|
|
49
|
+
unset: COMPETING_TOKEN_VARS,
|
|
50
|
+
},
|
|
51
|
+
ctx,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* `claude auth login --claudeai` writes its credential into
|
|
56
|
+
* `CLAUDE_CONFIG_DIR`, so pointing that at the requested home is the whole
|
|
57
|
+
* isolation mechanism — the competing token variables are unset for the same
|
|
58
|
+
* reason `homeDirHandler` unsets them: an inherited token would authenticate
|
|
59
|
+
* the CLI without ever populating the home we are creating.
|
|
60
|
+
*
|
|
61
|
+
* Only `oauth` is offered. The CLI has no device-code flow (`--console` /
|
|
62
|
+
* `--sso` are still browser flows), and declaring an unimplemented method
|
|
63
|
+
* would make `agent.login.supports("device")` lie.
|
|
64
|
+
*/
|
|
65
|
+
function claudeLoginCommand(args) {
|
|
66
|
+
return (request, ctx) => cliLoginCommand({
|
|
67
|
+
command: CLAUDE.command,
|
|
68
|
+
args,
|
|
69
|
+
envPatch: {
|
|
70
|
+
set: { [CLAUDE_CONFIG_DIR_ENV_VAR]: request.homeDir },
|
|
71
|
+
unset: COMPETING_TOKEN_VARS,
|
|
72
|
+
},
|
|
73
|
+
request,
|
|
74
|
+
ctx,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Install/list roots in priority order; the first is the install target. */
|
|
78
|
+
function claudeSkillRoots(ctx) {
|
|
79
|
+
const userRoot = {
|
|
80
|
+
root: path.join(ctx.location.homeDir, CLAUDE_PROJECT_SKILL_ROOT, SKILLS_DIRECTORY),
|
|
81
|
+
source: CLAUDE_SKILL_SOURCE.user,
|
|
82
|
+
};
|
|
83
|
+
if (ctx.location.projectDir === null) {
|
|
84
|
+
return [userRoot];
|
|
85
|
+
}
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
root: path.join(ctx.location.projectDir, CLAUDE_PROJECT_SKILL_ROOT, SKILLS_DIRECTORY),
|
|
89
|
+
source: CLAUDE_SKILL_SOURCE.project,
|
|
90
|
+
},
|
|
91
|
+
userRoot,
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
function parseSkillMetadata(markdown) {
|
|
95
|
+
const frontmatter = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---/u);
|
|
96
|
+
if (frontmatter?.[1] === undefined) {
|
|
97
|
+
return { name: null, description: null };
|
|
98
|
+
}
|
|
99
|
+
let name = null;
|
|
100
|
+
let description = null;
|
|
101
|
+
for (const line of frontmatter[1].split(/\r?\n/u)) {
|
|
102
|
+
const separator = line.indexOf(":");
|
|
103
|
+
if (separator < 0) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const key = line.slice(0, separator).trim();
|
|
107
|
+
const value = line
|
|
108
|
+
.slice(separator + 1)
|
|
109
|
+
.trim()
|
|
110
|
+
.replace(/^["']|["']$/gu, "");
|
|
111
|
+
if (key === "name") {
|
|
112
|
+
name = value;
|
|
113
|
+
}
|
|
114
|
+
if (key === "description") {
|
|
115
|
+
description = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { name, description };
|
|
119
|
+
}
|
|
120
|
+
function skillListReadError(target, cause) {
|
|
121
|
+
return new AgentError({
|
|
122
|
+
code: AGENT_ERROR_CODE.failed,
|
|
123
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.failed),
|
|
124
|
+
message: `Could not read installed claude skills: ${target}`,
|
|
125
|
+
data: NO_FAILURE_EVIDENCE,
|
|
126
|
+
cause,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function readSkillRootEntries(root) {
|
|
130
|
+
try {
|
|
131
|
+
return await readdir(root, { withFileTypes: true });
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// D5 discipline: only "the root does not exist" reads as empty.
|
|
135
|
+
if (isErrnoCode(error, "ENOENT") || isErrnoCode(error, "ENOTDIR")) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
throw skillListReadError(root, error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function readSkillMarkdown(location) {
|
|
142
|
+
try {
|
|
143
|
+
return await readFile(path.join(location, SKILL_MARKDOWN), TEXT_ENCODING);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
// A directory without SKILL.md is simply not a skill; anything else
|
|
147
|
+
// (permissions, I/O) must surface instead of hiding installed skills.
|
|
148
|
+
if (isErrnoCode(error, "ENOENT")) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
throw skillListReadError(location, error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function listClaudeSkills(ctx) {
|
|
155
|
+
const byId = new Map();
|
|
156
|
+
for (const { root, source } of claudeSkillRoots(ctx)) {
|
|
157
|
+
for (const entry of await readSkillRootEntries(root)) {
|
|
158
|
+
if (!entry.isDirectory() || byId.has(entry.name)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const location = path.join(root, entry.name);
|
|
162
|
+
const markdown = await readSkillMarkdown(location);
|
|
163
|
+
if (markdown === null) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const metadata = parseSkillMetadata(markdown);
|
|
167
|
+
byId.set(entry.name, {
|
|
168
|
+
id: entry.name,
|
|
169
|
+
name: metadata.name ?? entry.name,
|
|
170
|
+
description: metadata.description ?? "",
|
|
171
|
+
location,
|
|
172
|
+
source,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return [...byId.values()];
|
|
177
|
+
}
|
|
178
|
+
async function installLocalClaudeSkill(ctx, source, options) {
|
|
179
|
+
await assertSkillDirectory(source.path);
|
|
180
|
+
const skillId = path.basename(path.resolve(source.path));
|
|
181
|
+
const location = path.join(claudeSkillRoot(ctx), skillId);
|
|
182
|
+
if ((await pathExists(location)) && options.overwrite !== true) {
|
|
183
|
+
return { status: INSTALL_STATUS.alreadyInstalled, location };
|
|
184
|
+
}
|
|
185
|
+
if (options.overwrite === true) {
|
|
186
|
+
await rm(location, { recursive: true, force: true });
|
|
187
|
+
}
|
|
188
|
+
await mkdir(path.dirname(location), { recursive: true });
|
|
189
|
+
await cp(source.path, location, { recursive: true });
|
|
190
|
+
return { status: INSTALL_STATUS.installed, location };
|
|
191
|
+
}
|
|
192
|
+
function claudeSkillRoot(ctx) {
|
|
193
|
+
return claudeSkillRoots(ctx)[0].root;
|
|
194
|
+
}
|
|
195
|
+
async function assertSkillDirectory(directory) {
|
|
196
|
+
try {
|
|
197
|
+
const skill = await stat(path.join(directory, SKILL_MARKDOWN));
|
|
198
|
+
if (skill.isFile()) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Convert below to the library's typed boundary error.
|
|
204
|
+
}
|
|
205
|
+
throw new AgentError({
|
|
206
|
+
code: AGENT_ERROR_CODE.invalidInput,
|
|
207
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.invalidInput),
|
|
208
|
+
message: `Skill source does not contain ${SKILL_MARKDOWN}: ${directory}`,
|
|
209
|
+
data: { reason: `missing ${SKILL_MARKDOWN}` },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async function pathExists(target) {
|
|
213
|
+
try {
|
|
214
|
+
await stat(target);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function installGithubClaudeSkill(ctx, source, options) {
|
|
222
|
+
return withStagedGithubDirectory(ctx, source, (stagedSkill) => installLocalClaudeSkill(ctx, { kind: SKILL_SOURCE_KIND.localDir, path: stagedSkill }, options));
|
|
223
|
+
}
|
|
224
|
+
async function installClaudePlugin(ctx, source, options) {
|
|
225
|
+
if (options.overwrite === true) {
|
|
226
|
+
// `claude plugin install` has no overwrite semantics; refusing is
|
|
227
|
+
// honest, silently ignoring the request is not.
|
|
228
|
+
throw new AgentError({
|
|
229
|
+
code: AGENT_ERROR_CODE.invalidInput,
|
|
230
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.invalidInput),
|
|
231
|
+
message: "Claude plugin installation cannot overwrite an installed plugin",
|
|
232
|
+
data: { reason: "plugin install does not support overwrite" },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const result = await runHostCommandResult(ctx, {
|
|
236
|
+
command: CLAUDE.command,
|
|
237
|
+
args: [CLAUDE.plugin.plugin, CLAUDE.plugin.install, source.name],
|
|
238
|
+
});
|
|
239
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
240
|
+
if (result.exit.code === 0) {
|
|
241
|
+
return {
|
|
242
|
+
status: INSTALL_STATUS.installed,
|
|
243
|
+
location: `claude:plugin:${source.name}`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (PLUGIN_ALREADY_INSTALLED.test(output)) {
|
|
247
|
+
return {
|
|
248
|
+
status: INSTALL_STATUS.alreadyInstalled,
|
|
249
|
+
location: `claude:plugin:${source.name}`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
throw hostCommandFailedError(CLAUDE.command, result);
|
|
253
|
+
}
|
|
254
|
+
async function addClaudeStdioMcp(ctx, name, spec, options) {
|
|
255
|
+
await runHostCommand(ctx, {
|
|
256
|
+
command: CLAUDE.command,
|
|
257
|
+
args: [
|
|
258
|
+
CLAUDE.mcp.mcp,
|
|
259
|
+
CLAUDE.mcp.addJson,
|
|
260
|
+
name,
|
|
261
|
+
JSON.stringify({
|
|
262
|
+
command: spec.command,
|
|
263
|
+
args: spec.args,
|
|
264
|
+
env: spec.env,
|
|
265
|
+
...(spec.cwd === null ? {} : { cwd: spec.cwd }),
|
|
266
|
+
}),
|
|
267
|
+
CLAUDE.mcp.scope,
|
|
268
|
+
options.scope,
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
status: INSTALL_STATUS.installed,
|
|
273
|
+
location: `claude:${options.scope}:${name}`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async function removeClaudeMcp(ctx, name, options) {
|
|
277
|
+
await runHostCommand(ctx, {
|
|
278
|
+
command: CLAUDE.command,
|
|
279
|
+
args: [
|
|
280
|
+
CLAUDE.mcp.mcp,
|
|
281
|
+
CLAUDE.mcp.remove,
|
|
282
|
+
name,
|
|
283
|
+
CLAUDE.mcp.scope,
|
|
284
|
+
options.scope,
|
|
285
|
+
],
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async function listClaudeMcp(ctx) {
|
|
289
|
+
return listMcpServersViaCli(ctx, { command: CLAUDE.command });
|
|
290
|
+
}
|
|
291
|
+
export const claudeAuth = {
|
|
292
|
+
[AUTH_MATERIAL_KIND.homeDir]: homeDirHandler,
|
|
293
|
+
[AUTH_MATERIAL_KIND.apiKey]: envVarAuthMaterial({
|
|
294
|
+
set: async (material, secrets) => ({
|
|
295
|
+
[CLAUDE_API_KEY_ENV_VAR]: await requireSecret(secrets, material.secret),
|
|
296
|
+
}),
|
|
297
|
+
unset: COMPETING_CONFIG_VARS,
|
|
298
|
+
}),
|
|
299
|
+
};
|
|
300
|
+
export const claudeLogin = {
|
|
301
|
+
methods: {
|
|
302
|
+
[LOGIN_METHOD.oauth]: claudeLoginCommand(CLAUDE.auth.loginArgs),
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
export const claudeSkills = {
|
|
306
|
+
list: listClaudeSkills,
|
|
307
|
+
install: {
|
|
308
|
+
[SKILL_SOURCE_KIND.localDir]: installLocalClaudeSkill,
|
|
309
|
+
[SKILL_SOURCE_KIND.githubDir]: installGithubClaudeSkill,
|
|
310
|
+
[SKILL_SOURCE_KIND.plugin]: installClaudePlugin,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
export const claudeMcp = {
|
|
314
|
+
list: listClaudeMcp,
|
|
315
|
+
add: {
|
|
316
|
+
[MCP_TRANSPORT.stdio]: addClaudeStdioMcp,
|
|
317
|
+
},
|
|
318
|
+
remove: removeClaudeMcp,
|
|
319
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* `claude -p [--model m] [--effort e] --output-format stream-json
|
|
4
|
-
* --permission-mode <mode> [tool args] --verbose`, prompt on stdin
|
|
5
|
-
*
|
|
6
|
-
* grants,
|
|
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`.
|
|
7
8
|
*/
|
|
8
|
-
import { type FailureMatcher } from "
|
|
9
|
-
import type { AgentDefinition } from "
|
|
10
|
-
|
|
11
|
-
export declare const CLAUDE_API_KEY_ENV_VAR = "ANTHROPIC_API_KEY";
|
|
9
|
+
import { type FailureMatcher } from "../../kernel/classify.js";
|
|
10
|
+
import type { AgentDefinition } from "../../kernel/definition.js";
|
|
11
|
+
import { type ClaudeSessionPermissions } from "./cli.js";
|
|
12
12
|
/**
|
|
13
13
|
* Structured quota signals in Claude CLI NDJSON output (v1 F2), scanned
|
|
14
14
|
* per line: rate_limit_event rejected (with resetsAt), api_error_status
|
|
@@ -23,5 +23,5 @@ export type ClaudeOptions = {
|
|
|
23
23
|
*/
|
|
24
24
|
readonly customizations?: "inherit" | "disabled";
|
|
25
25
|
};
|
|
26
|
-
export declare function claude(options?: ClaudeOptions): AgentDefinition<"claude">;
|
|
27
|
-
//# sourceMappingURL=
|
|
26
|
+
export declare function claude(options?: ClaudeOptions): AgentDefinition<"claude", ClaudeSessionPermissions>;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agents/claude/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,EACL,KAAK,cAAc,EAEpB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,OAAO,EAGL,KAAK,wBAAwB,EAI9B,MAAM,UAAU,CAAC;AA8BlB;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,cAAc,CA0CvD;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;CAClD,CAAC;AAEF,wBAAgB,MAAM,CACpB,OAAO,GAAE,aAAkB,GAC1B,eAAe,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAmDrD"}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { type SessionCapability } from "../../kernel/session.js";
|
|
20
|
+
import { type ClaudeSessionPermissions } from "./cli.js";
|
|
21
|
+
export type ClaudeSessionOptions = {
|
|
22
|
+
readonly customizations: "inherit" | "disabled";
|
|
23
|
+
};
|
|
24
|
+
export declare function claudeSessions(options: ClaudeSessionOptions): SessionCapability<ClaudeSessionPermissions>;
|
|
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"}
|