@indigoai-us/hq-cli 5.109.16 → 5.111.0
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/CHANGELOG.md +38 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +363 -0
- package/assets/bot-workers/setup/context/quick-reference.md +199 -0
- package/assets/bot-workers/setup/skills/first-company.md +71 -0
- package/assets/bot-workers/setup/skills/standing-help.md +74 -0
- package/assets/bot-workers/setup/worker.yaml +422 -0
- package/dist/commands/bot-continuity.d.ts +28 -0
- package/dist/commands/bot-continuity.js +68 -0
- package/dist/commands/bot.d.ts +73 -0
- package/dist/commands/bot.js +776 -0
- package/dist/commands/skill.d.ts +12 -3
- package/dist/commands/skill.js +79 -3
- package/dist/commands/workers.d.ts +2 -14
- package/dist/commands/workers.js +2 -8
- package/dist/lib/bot/api.d.ts +202 -0
- package/dist/lib/bot/api.js +202 -0
- package/dist/lib/bot/company-bind.d.ts +27 -0
- package/dist/lib/bot/company-bind.js +62 -0
- package/dist/lib/bot/config.d.ts +106 -0
- package/dist/lib/bot/config.js +141 -0
- package/dist/lib/bot/continuity-download.d.ts +28 -0
- package/dist/lib/bot/continuity-download.js +75 -0
- package/dist/lib/bot/continuity-install.d.ts +14 -0
- package/dist/lib/bot/continuity-install.js +101 -0
- package/dist/lib/bot/continuity.d.ts +66 -0
- package/dist/lib/bot/continuity.js +301 -0
- package/dist/lib/bot/creds.d.ts +24 -0
- package/dist/lib/bot/creds.js +51 -0
- package/dist/lib/bot/daemon.d.ts +75 -0
- package/dist/lib/bot/daemon.js +316 -0
- package/dist/lib/bot/inbox-state.d.ts +18 -0
- package/dist/lib/bot/inbox-state.js +51 -0
- package/dist/lib/bot/index.d.ts +16 -0
- package/dist/lib/bot/index.js +16 -0
- package/dist/lib/bot/inflight.d.ts +40 -0
- package/dist/lib/bot/inflight.js +44 -0
- package/dist/lib/bot/log.d.ts +13 -0
- package/dist/lib/bot/log.js +59 -0
- package/dist/lib/bot/owner-context.d.ts +75 -0
- package/dist/lib/bot/owner-context.js +151 -0
- package/dist/lib/bot/paths.d.ts +61 -0
- package/dist/lib/bot/paths.js +103 -0
- package/dist/lib/bot/progress.d.ts +84 -0
- package/dist/lib/bot/progress.js +167 -0
- package/dist/lib/bot/promote.d.ts +16 -0
- package/dist/lib/bot/promote.js +106 -0
- package/dist/lib/bot/promotion-hold.d.ts +24 -0
- package/dist/lib/bot/promotion-hold.js +103 -0
- package/dist/lib/bot/promotion-receipt.d.ts +9 -0
- package/dist/lib/bot/promotion-receipt.js +56 -0
- package/dist/lib/bot/promotion-upload.d.ts +16 -0
- package/dist/lib/bot/promotion-upload.js +65 -0
- package/dist/lib/bot/prompt.d.ts +103 -0
- package/dist/lib/bot/prompt.js +329 -0
- package/dist/lib/bot/room-policy.d.ts +53 -0
- package/dist/lib/bot/room-policy.js +73 -0
- package/dist/lib/bot/run.d.ts +98 -0
- package/dist/lib/bot/run.js +787 -0
- package/dist/lib/bot/runtime/claude.d.ts +49 -0
- package/dist/lib/bot/runtime/claude.js +151 -0
- package/dist/lib/bot/runtime/codex.d.ts +28 -0
- package/dist/lib/bot/runtime/codex.js +147 -0
- package/dist/lib/bot/runtime/grok.d.ts +16 -0
- package/dist/lib/bot/runtime/grok.js +67 -0
- package/dist/lib/bot/runtime/index.d.ts +35 -0
- package/dist/lib/bot/runtime/index.js +279 -0
- package/dist/lib/bot/runtime/messages-stream.d.ts +27 -0
- package/dist/lib/bot/runtime/messages-stream.js +85 -0
- package/dist/lib/bot/runtime/types.d.ts +136 -0
- package/dist/lib/bot/runtime/types.js +51 -0
- package/dist/lib/bot/scaffold.d.ts +38 -0
- package/dist/lib/bot/scaffold.js +94 -0
- package/dist/lib/bot/session.d.ts +19 -0
- package/dist/lib/bot/session.js +39 -0
- package/dist/lib/bot/status.d.ts +40 -0
- package/dist/lib/bot/status.js +66 -0
- package/dist/lib/bot/worker-source.d.ts +66 -0
- package/dist/lib/bot/worker-source.js +283 -0
- package/dist/lib/workers-registry/read.d.ts +15 -0
- package/dist/lib/workers-registry/read.js +17 -0
- package/dist/register-all.js +2 -0
- package/package.json +2 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code headless adapter.
|
|
3
|
+
*
|
|
4
|
+
* claude -p <prompt> --output-format stream-json --verbose --permission-mode <mode>
|
|
5
|
+
* (<mode> = bypassPermissions by default — verified headless in HQ on
|
|
6
|
+
* 2026-09-11: `hq --version` ran without a prompt in 3.9 s; acceptEdits
|
|
7
|
+
* when the bot's autoApprove is false; HQ_BOT_CLAUDE_PERMISSION_MODE
|
|
8
|
+
* overrides both)
|
|
9
|
+
* --append-system-prompt-file <file> --effort <level> [--model m]
|
|
10
|
+
* (--session-id <uuid> | --resume <uuid>)
|
|
11
|
+
*
|
|
12
|
+
* stdout is NDJSON (see messages-stream.ts): each finished assistant message
|
|
13
|
+
* is posted as progress while the turn runs; the closing `result` event
|
|
14
|
+
* carries the final text and session_id.
|
|
15
|
+
* Verified against Claude Code 2.1.258 on 2026-09-12 (fixtures/claude-stream.ndjson).
|
|
16
|
+
*/
|
|
17
|
+
import type { BotRuntime } from "./types.js";
|
|
18
|
+
export declare const CLAUDE_PERMISSION_MODE_ENV = "HQ_BOT_CLAUDE_PERMISSION_MODE";
|
|
19
|
+
export declare const DEFAULT_CLAUDE_PERMISSION_MODE = "bypassPermissions";
|
|
20
|
+
export declare const GATED_CLAUDE_PERMISSION_MODE = "acceptEdits";
|
|
21
|
+
/** No-prompt mode that works even where bypassPermissions is disabled by policy:
|
|
22
|
+
* Claude Code's own classifier approves routine tool calls. */
|
|
23
|
+
export declare const FALLBACK_CLAUDE_PERMISSION_MODE = "auto";
|
|
24
|
+
/** Bots think at medium effort: chat replies stay quick without dropping
|
|
25
|
+
* HQ's hooks or context. HQ_BOT_CLAUDE_EFFORT overrides it. */
|
|
26
|
+
export declare const CLAUDE_EFFORT_ENV = "HQ_BOT_CLAUDE_EFFORT";
|
|
27
|
+
export declare const DEFAULT_CLAUDE_EFFORT = "medium";
|
|
28
|
+
export declare function claudeEffort(env?: NodeJS.ProcessEnv): string;
|
|
29
|
+
export declare function claudePermissionMode(env?: NodeJS.ProcessEnv, autoApprove?: boolean, explicit?: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* True when Claude Code refused to start because bypassPermissions is turned
|
|
32
|
+
* off for this user (managed/enterprise settings: disableBypassPermissionsMode).
|
|
33
|
+
* Matched loosely on purpose — the wording has changed across releases.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isClaudeBypassDisabled(text: string): boolean;
|
|
36
|
+
/** True when `name` is an executable file in one of PATH's directories. */
|
|
37
|
+
export declare function onPath(name: string, env: NodeJS.ProcessEnv): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The Claude desktop app keeps its own Claude Code under
|
|
40
|
+
* ~/Library/Application Support/Claude/claude-code/<version>/. A Mac that only
|
|
41
|
+
* has the desktop app has no `claude` on PATH, yet the HQ app counts it as
|
|
42
|
+
* installed, so a bot must be able to use it too. Newest verified version wins
|
|
43
|
+
* (versions compare numerically; unverified or half-downloaded ones are
|
|
44
|
+
* skipped), matching the HQ desktop app's own lookup.
|
|
45
|
+
*/
|
|
46
|
+
export declare function bundledClaudeBin(homeDir?: string): string | null;
|
|
47
|
+
export declare function resolveClaudeBinary(env: NodeJS.ProcessEnv, homeDir?: string): string;
|
|
48
|
+
export declare function createClaudeRuntime(env?: NodeJS.ProcessEnv): BotRuntime;
|
|
49
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code headless adapter.
|
|
3
|
+
*
|
|
4
|
+
* claude -p <prompt> --output-format stream-json --verbose --permission-mode <mode>
|
|
5
|
+
* (<mode> = bypassPermissions by default — verified headless in HQ on
|
|
6
|
+
* 2026-09-11: `hq --version` ran without a prompt in 3.9 s; acceptEdits
|
|
7
|
+
* when the bot's autoApprove is false; HQ_BOT_CLAUDE_PERMISSION_MODE
|
|
8
|
+
* overrides both)
|
|
9
|
+
* --append-system-prompt-file <file> --effort <level> [--model m]
|
|
10
|
+
* (--session-id <uuid> | --resume <uuid>)
|
|
11
|
+
*
|
|
12
|
+
* stdout is NDJSON (see messages-stream.ts): each finished assistant message
|
|
13
|
+
* is posted as progress while the turn runs; the closing `result` event
|
|
14
|
+
* carries the final text and session_id.
|
|
15
|
+
* Verified against Claude Code 2.1.258 on 2026-09-12 (fixtures/claude-stream.ndjson).
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as os from "node:os";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { messagesStreamEvents, messagesStreamFailure, messagesStreamResult } from "./messages-stream.js";
|
|
21
|
+
export const CLAUDE_PERMISSION_MODE_ENV = "HQ_BOT_CLAUDE_PERMISSION_MODE";
|
|
22
|
+
export const DEFAULT_CLAUDE_PERMISSION_MODE = "bypassPermissions";
|
|
23
|
+
export const GATED_CLAUDE_PERMISSION_MODE = "acceptEdits";
|
|
24
|
+
/** No-prompt mode that works even where bypassPermissions is disabled by policy:
|
|
25
|
+
* Claude Code's own classifier approves routine tool calls. */
|
|
26
|
+
export const FALLBACK_CLAUDE_PERMISSION_MODE = "auto";
|
|
27
|
+
/** Bots think at medium effort: chat replies stay quick without dropping
|
|
28
|
+
* HQ's hooks or context. HQ_BOT_CLAUDE_EFFORT overrides it. */
|
|
29
|
+
export const CLAUDE_EFFORT_ENV = "HQ_BOT_CLAUDE_EFFORT";
|
|
30
|
+
export const DEFAULT_CLAUDE_EFFORT = "medium";
|
|
31
|
+
export function claudeEffort(env = process.env) {
|
|
32
|
+
return env[CLAUDE_EFFORT_ENV]?.trim() || DEFAULT_CLAUDE_EFFORT;
|
|
33
|
+
}
|
|
34
|
+
export function claudePermissionMode(env = process.env, autoApprove = true, explicit) {
|
|
35
|
+
const raw = env[CLAUDE_PERMISSION_MODE_ENV]?.trim();
|
|
36
|
+
if (raw)
|
|
37
|
+
return raw;
|
|
38
|
+
if (explicit?.trim())
|
|
39
|
+
return explicit.trim();
|
|
40
|
+
return autoApprove ? DEFAULT_CLAUDE_PERMISSION_MODE : GATED_CLAUDE_PERMISSION_MODE;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* True when Claude Code refused to start because bypassPermissions is turned
|
|
44
|
+
* off for this user (managed/enterprise settings: disableBypassPermissionsMode).
|
|
45
|
+
* Matched loosely on purpose — the wording has changed across releases.
|
|
46
|
+
*/
|
|
47
|
+
export function isClaudeBypassDisabled(text) {
|
|
48
|
+
const t = text.toLowerCase();
|
|
49
|
+
if (!/bypass ?permissions|disablebypasspermissionsmode|skip.permissions/.test(t))
|
|
50
|
+
return false;
|
|
51
|
+
return /disabled|not (allowed|permitted|enabled|available)|policy|managed|forbidden|cannot be used/.test(t);
|
|
52
|
+
}
|
|
53
|
+
/** True when `name` is an executable file in one of PATH's directories. */
|
|
54
|
+
export function onPath(name, env) {
|
|
55
|
+
for (const dir of (env.PATH ?? "").split(path.delimiter)) {
|
|
56
|
+
if (!dir)
|
|
57
|
+
continue;
|
|
58
|
+
try {
|
|
59
|
+
fs.accessSync(path.join(dir, name), fs.constants.X_OK);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* keep looking */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The Claude desktop app keeps its own Claude Code under
|
|
70
|
+
* ~/Library/Application Support/Claude/claude-code/<version>/. A Mac that only
|
|
71
|
+
* has the desktop app has no `claude` on PATH, yet the HQ app counts it as
|
|
72
|
+
* installed, so a bot must be able to use it too. Newest verified version wins
|
|
73
|
+
* (versions compare numerically; unverified or half-downloaded ones are
|
|
74
|
+
* skipped), matching the HQ desktop app's own lookup.
|
|
75
|
+
*/
|
|
76
|
+
export function bundledClaudeBin(homeDir = os.homedir()) {
|
|
77
|
+
const managed = path.join(homeDir, "Library", "Application Support", "Claude", "claude-code");
|
|
78
|
+
let names;
|
|
79
|
+
try {
|
|
80
|
+
names = fs.readdirSync(managed);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const candidates = [];
|
|
86
|
+
for (const name of names) {
|
|
87
|
+
if (!/^\d+(\.\d+)*$/.test(name))
|
|
88
|
+
continue;
|
|
89
|
+
const dir = path.join(managed, name);
|
|
90
|
+
const bin = path.join(dir, "claude.app", "Contents", "MacOS", "claude");
|
|
91
|
+
if (!fs.existsSync(path.join(dir, ".verified")) || !fs.existsSync(bin))
|
|
92
|
+
continue;
|
|
93
|
+
candidates.push({ version: name.split(".").map(Number), bin });
|
|
94
|
+
}
|
|
95
|
+
candidates.sort((a, b) => {
|
|
96
|
+
for (let i = 0; i < Math.max(a.version.length, b.version.length); i++) {
|
|
97
|
+
const d = (a.version[i] ?? 0) - (b.version[i] ?? 0);
|
|
98
|
+
if (d !== 0)
|
|
99
|
+
return d;
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
});
|
|
103
|
+
return candidates.at(-1)?.bin ?? null;
|
|
104
|
+
}
|
|
105
|
+
export function resolveClaudeBinary(env, homeDir) {
|
|
106
|
+
if (onPath("claude", env))
|
|
107
|
+
return "claude";
|
|
108
|
+
return bundledClaudeBin(homeDir ?? env.HOME ?? os.homedir()) ?? "claude";
|
|
109
|
+
}
|
|
110
|
+
export function createClaudeRuntime(env = process.env) {
|
|
111
|
+
return {
|
|
112
|
+
id: "claude",
|
|
113
|
+
binary: "claude",
|
|
114
|
+
resolveBinary: (turnEnv) => resolveClaudeBinary(turnEnv),
|
|
115
|
+
buildArgs(input, scratch) {
|
|
116
|
+
const args = [
|
|
117
|
+
"-p",
|
|
118
|
+
input.prompt,
|
|
119
|
+
"--output-format",
|
|
120
|
+
"stream-json",
|
|
121
|
+
"--verbose",
|
|
122
|
+
"--permission-mode",
|
|
123
|
+
claudePermissionMode(env, input.autoApprove !== false, input.permissionMode),
|
|
124
|
+
"--append-system-prompt-file",
|
|
125
|
+
input.systemPromptFile,
|
|
126
|
+
"--effort",
|
|
127
|
+
input.effort?.trim() || claudeEffort(env),
|
|
128
|
+
];
|
|
129
|
+
// Keep the HQ tree writable even if a caller runs the turn elsewhere.
|
|
130
|
+
if (input.hqRoot && input.hqRoot !== input.cwd)
|
|
131
|
+
args.push("--add-dir", input.hqRoot);
|
|
132
|
+
if (input.model)
|
|
133
|
+
args.push("--model", input.model);
|
|
134
|
+
if (input.sessionId)
|
|
135
|
+
args.push("--resume", input.sessionId);
|
|
136
|
+
else
|
|
137
|
+
args.push("--session-id", scratch.newSessionId);
|
|
138
|
+
return args;
|
|
139
|
+
},
|
|
140
|
+
parseOutput(stdout) {
|
|
141
|
+
return messagesStreamResult(stdout, "claude");
|
|
142
|
+
},
|
|
143
|
+
parseStreamLine(line) {
|
|
144
|
+
return messagesStreamEvents(line);
|
|
145
|
+
},
|
|
146
|
+
failureReason(stdout) {
|
|
147
|
+
return messagesStreamFailure(stdout);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI headless adapter.
|
|
3
|
+
*
|
|
4
|
+
* codex exec --json --skip-git-repo-check -C <cwd> -o <file> [-m m] <prompt>
|
|
5
|
+
* codex exec resume --json --skip-git-repo-check -o <file> [-m m] <sid> <prompt>
|
|
6
|
+
*
|
|
7
|
+
* stdout is JSONL: {"type":"thread.started","thread_id"}, then
|
|
8
|
+
* {"type":"item.completed","item":{"type":"agent_message","text"}} …
|
|
9
|
+
* The final agent message is also written to `-o <file>` (authoritative).
|
|
10
|
+
* Each `agent_message` item is posted as progress while the turn runs.
|
|
11
|
+
* The system prompt rides as a preamble in the prompt (codex exec has no
|
|
12
|
+
* system-prompt flag); it is small and stable across turns.
|
|
13
|
+
* Verified against Codex CLI on 2026-09-10 (event names; the model itself
|
|
14
|
+
* cannot run under this machine's account).
|
|
15
|
+
*/
|
|
16
|
+
import { type BotRuntime, type RuntimeStreamEvent, type RuntimeTurnInput } from "./types.js";
|
|
17
|
+
/** Progress events in one codex JSONL line: finished agent messages and tool starts. */
|
|
18
|
+
export declare function codexStreamEvents(line: string): RuntimeStreamEvent[];
|
|
19
|
+
export declare function codexPrompt(input: RuntimeTurnInput): string;
|
|
20
|
+
/**
|
|
21
|
+
* Pull the real failure out of codex's JSONL. `turn.failed` / `error` events
|
|
22
|
+
* carry the API error, sometimes as a JSON string
|
|
23
|
+
* ({"error":{"message":"You've hit your usage limit…"}}); unwrap it. Setup
|
|
24
|
+
* warnings (`item.completed` with `item.type === "error"`) are not failures.
|
|
25
|
+
*/
|
|
26
|
+
export declare function codexFailureReason(stdout: string): string | undefined;
|
|
27
|
+
export declare function createCodexRuntime(): BotRuntime;
|
|
28
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI headless adapter.
|
|
3
|
+
*
|
|
4
|
+
* codex exec --json --skip-git-repo-check -C <cwd> -o <file> [-m m] <prompt>
|
|
5
|
+
* codex exec resume --json --skip-git-repo-check -o <file> [-m m] <sid> <prompt>
|
|
6
|
+
*
|
|
7
|
+
* stdout is JSONL: {"type":"thread.started","thread_id"}, then
|
|
8
|
+
* {"type":"item.completed","item":{"type":"agent_message","text"}} …
|
|
9
|
+
* The final agent message is also written to `-o <file>` (authoritative).
|
|
10
|
+
* Each `agent_message` item is posted as progress while the turn runs.
|
|
11
|
+
* The system prompt rides as a preamble in the prompt (codex exec has no
|
|
12
|
+
* system-prompt flag); it is small and stable across turns.
|
|
13
|
+
* Verified against Codex CLI on 2026-09-10 (event names; the model itself
|
|
14
|
+
* cannot run under this machine's account).
|
|
15
|
+
*/
|
|
16
|
+
import { parseJsonLine, RuntimeError } from "./types.js";
|
|
17
|
+
/** Progress events in one codex JSONL line: finished agent messages and tool starts. */
|
|
18
|
+
export function codexStreamEvents(line) {
|
|
19
|
+
const ev = parseJsonLine(line);
|
|
20
|
+
if (!ev || !ev.item || typeof ev.item !== "object")
|
|
21
|
+
return [];
|
|
22
|
+
const item = ev.item;
|
|
23
|
+
if (ev.type === "item.completed" && item.type === "agent_message" && typeof item.text === "string" && item.text.trim()) {
|
|
24
|
+
return [{ kind: "message", text: item.text.trim() }];
|
|
25
|
+
}
|
|
26
|
+
if (ev.type === "item.started" && typeof item.type === "string" && /command|tool|mcp/.test(item.type)) {
|
|
27
|
+
return [{ kind: "tool", name: item.type }];
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
export function codexPrompt(input) {
|
|
32
|
+
if (input.sessionId)
|
|
33
|
+
return input.prompt;
|
|
34
|
+
return `${input.systemPrompt}\n\n---\n\nMessage from your owner:\n\n${input.prompt}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Pull the real failure out of codex's JSONL. `turn.failed` / `error` events
|
|
38
|
+
* carry the API error, sometimes as a JSON string
|
|
39
|
+
* ({"error":{"message":"You've hit your usage limit…"}}); unwrap it. Setup
|
|
40
|
+
* warnings (`item.completed` with `item.type === "error"`) are not failures.
|
|
41
|
+
*/
|
|
42
|
+
export function codexFailureReason(stdout) {
|
|
43
|
+
let reason;
|
|
44
|
+
for (const line of stdout.split("\n")) {
|
|
45
|
+
const t = line.trim();
|
|
46
|
+
if (!t.startsWith("{"))
|
|
47
|
+
continue;
|
|
48
|
+
let ev;
|
|
49
|
+
try {
|
|
50
|
+
ev = JSON.parse(t);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (ev.type !== "turn.failed" && ev.type !== "error")
|
|
56
|
+
continue;
|
|
57
|
+
const err = ev.error && typeof ev.error === "object" ? ev.error : undefined;
|
|
58
|
+
const raw = typeof err?.message === "string" ? err.message : typeof ev.message === "string" ? ev.message : undefined;
|
|
59
|
+
if (raw)
|
|
60
|
+
reason = unwrapApiError(raw);
|
|
61
|
+
}
|
|
62
|
+
return reason?.trim() || undefined;
|
|
63
|
+
}
|
|
64
|
+
function unwrapApiError(raw) {
|
|
65
|
+
const t = raw.trim();
|
|
66
|
+
if (!t.startsWith("{"))
|
|
67
|
+
return t;
|
|
68
|
+
try {
|
|
69
|
+
const obj = JSON.parse(t);
|
|
70
|
+
const inner = obj.error && typeof obj.error === "object" ? obj.error : undefined;
|
|
71
|
+
if (typeof inner?.message === "string")
|
|
72
|
+
return inner.message;
|
|
73
|
+
if (typeof obj.message === "string")
|
|
74
|
+
return obj.message;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* not JSON */
|
|
78
|
+
}
|
|
79
|
+
return t;
|
|
80
|
+
}
|
|
81
|
+
export function createCodexRuntime() {
|
|
82
|
+
return {
|
|
83
|
+
id: "codex",
|
|
84
|
+
binary: "codex",
|
|
85
|
+
buildArgs(input, scratch) {
|
|
86
|
+
const common = ["--json", "--skip-git-repo-check", "-o", scratch.outputFile];
|
|
87
|
+
if (input.autoApprove !== false) {
|
|
88
|
+
// Headless: nobody can answer an approval prompt, so a gated command
|
|
89
|
+
// would just fail. The bot runs with the owner's permissions by design.
|
|
90
|
+
common.push("--dangerously-bypass-approvals-and-sandbox");
|
|
91
|
+
}
|
|
92
|
+
else if (input.hqRoot && input.hqRoot !== input.cwd) {
|
|
93
|
+
common.push("-c", `sandbox_workspace_write.writable_roots=[${JSON.stringify(input.hqRoot)}]`);
|
|
94
|
+
}
|
|
95
|
+
if (input.model)
|
|
96
|
+
common.push("-m", input.model);
|
|
97
|
+
common.push("-c", `model_reasoning_effort=${JSON.stringify(input.effort?.trim() || "medium")}`);
|
|
98
|
+
if (input.sessionId) {
|
|
99
|
+
return ["exec", "resume", ...common, input.sessionId, codexPrompt(input)];
|
|
100
|
+
}
|
|
101
|
+
return ["exec", ...common, "-C", input.cwd, codexPrompt(input)];
|
|
102
|
+
},
|
|
103
|
+
parseOutput(stdout, extra) {
|
|
104
|
+
let sessionId;
|
|
105
|
+
let lastMessage;
|
|
106
|
+
let errorMessage;
|
|
107
|
+
for (const line of stdout.split("\n")) {
|
|
108
|
+
const t = line.trim();
|
|
109
|
+
if (!t.startsWith("{"))
|
|
110
|
+
continue;
|
|
111
|
+
let ev;
|
|
112
|
+
try {
|
|
113
|
+
ev = JSON.parse(t);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (ev.type === "thread.started" && typeof ev.thread_id === "string")
|
|
119
|
+
sessionId = ev.thread_id;
|
|
120
|
+
if (ev.type === "item.completed" && ev.item && typeof ev.item === "object") {
|
|
121
|
+
const item = ev.item;
|
|
122
|
+
if (item.type === "agent_message" && typeof item.text === "string")
|
|
123
|
+
lastMessage = item.text;
|
|
124
|
+
if (item.type === "error" && typeof item.message === "string")
|
|
125
|
+
errorMessage = item.message;
|
|
126
|
+
}
|
|
127
|
+
if (ev.type === "turn.failed" || ev.type === "error") {
|
|
128
|
+
const msg = typeof ev.message === "string" ? ev.message : JSON.stringify(ev).slice(0, 200);
|
|
129
|
+
errorMessage = msg;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const fromFile = extra.outputFileText?.trim();
|
|
133
|
+
const text = (fromFile || lastMessage || "").trim();
|
|
134
|
+
if (!text) {
|
|
135
|
+
throw new RuntimeError(`codex produced no reply${errorMessage ? `: ${errorMessage.slice(0, 200)}` : ""}`, (errorMessage ?? stdout.slice(-400)).slice(0, 400), null);
|
|
136
|
+
}
|
|
137
|
+
return { text, sessionId };
|
|
138
|
+
},
|
|
139
|
+
parseStreamLine(line) {
|
|
140
|
+
return codexStreamEvents(line);
|
|
141
|
+
},
|
|
142
|
+
failureReason(stdout) {
|
|
143
|
+
return codexFailureReason(stdout);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok CLI headless adapter.
|
|
3
|
+
*
|
|
4
|
+
* grok -p <prompt> --output-format streaming-messages-json --always-approve
|
|
5
|
+
* --no-subagents --cwd <cwd> [-m m] [-r <sessionId>]
|
|
6
|
+
*
|
|
7
|
+
* stdout is NDJSON in the Anthropic Messages shape (see messages-stream.ts).
|
|
8
|
+
* The plain `json` format glued every interim sentence into one reply; the
|
|
9
|
+
* streaming format lets each message be posted as it is written.
|
|
10
|
+
* Verified against grok (grok-4.6) on 2026-09-12 (fixtures/grok-stream.ndjson);
|
|
11
|
+
* the older `{ text, sessionId }` envelope is still accepted.
|
|
12
|
+
*/
|
|
13
|
+
import { type BotRuntime, type RuntimeTurnInput } from "./types.js";
|
|
14
|
+
export declare function grokPrompt(input: RuntimeTurnInput): string;
|
|
15
|
+
export declare function createGrokRuntime(): BotRuntime;
|
|
16
|
+
//# sourceMappingURL=grok.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok CLI headless adapter.
|
|
3
|
+
*
|
|
4
|
+
* grok -p <prompt> --output-format streaming-messages-json --always-approve
|
|
5
|
+
* --no-subagents --cwd <cwd> [-m m] [-r <sessionId>]
|
|
6
|
+
*
|
|
7
|
+
* stdout is NDJSON in the Anthropic Messages shape (see messages-stream.ts).
|
|
8
|
+
* The plain `json` format glued every interim sentence into one reply; the
|
|
9
|
+
* streaming format lets each message be posted as it is written.
|
|
10
|
+
* Verified against grok (grok-4.6) on 2026-09-12 (fixtures/grok-stream.ndjson);
|
|
11
|
+
* the older `{ text, sessionId }` envelope is still accepted.
|
|
12
|
+
*/
|
|
13
|
+
import { messagesStreamEvents, messagesStreamFailure, messagesStreamResult } from "./messages-stream.js";
|
|
14
|
+
import { parseJsonLine, RuntimeError } from "./types.js";
|
|
15
|
+
export function grokPrompt(input) {
|
|
16
|
+
if (input.sessionId)
|
|
17
|
+
return input.prompt;
|
|
18
|
+
return `${input.systemPrompt}\n\n---\n\nMessage from your owner:\n\n${input.prompt}`;
|
|
19
|
+
}
|
|
20
|
+
export function createGrokRuntime() {
|
|
21
|
+
return {
|
|
22
|
+
id: "grok",
|
|
23
|
+
binary: "grok",
|
|
24
|
+
buildArgs(input) {
|
|
25
|
+
const args = [
|
|
26
|
+
"-p",
|
|
27
|
+
grokPrompt(input),
|
|
28
|
+
"--output-format",
|
|
29
|
+
"streaming-messages-json",
|
|
30
|
+
"--always-approve",
|
|
31
|
+
"--no-subagents",
|
|
32
|
+
"--cwd",
|
|
33
|
+
input.cwd,
|
|
34
|
+
];
|
|
35
|
+
if (input.model)
|
|
36
|
+
args.push("-m", input.model);
|
|
37
|
+
args.push("--reasoning-effort", input.effort?.trim() || "medium");
|
|
38
|
+
if (input.sessionId)
|
|
39
|
+
args.push("-r", input.sessionId);
|
|
40
|
+
return args;
|
|
41
|
+
},
|
|
42
|
+
parseOutput(stdout) {
|
|
43
|
+
// Older grok: one `{ text, sessionId }` object.
|
|
44
|
+
const legacy = stdout
|
|
45
|
+
.split("\n")
|
|
46
|
+
.map(parseJsonLine)
|
|
47
|
+
.find((ev) => ev && typeof ev.text === "string" && ev.type === undefined);
|
|
48
|
+
if (legacy) {
|
|
49
|
+
const text = String(legacy.text);
|
|
50
|
+
if (!text.trim())
|
|
51
|
+
throw new RuntimeError("grok produced no reply", stdout.trim().slice(-400), null);
|
|
52
|
+
return { text, sessionId: typeof legacy.sessionId === "string" ? legacy.sessionId : undefined };
|
|
53
|
+
}
|
|
54
|
+
const result = messagesStreamResult(stdout, "grok");
|
|
55
|
+
if (!result.text.trim())
|
|
56
|
+
throw new RuntimeError("grok produced no reply", stdout.trim().slice(-400), null);
|
|
57
|
+
return result;
|
|
58
|
+
},
|
|
59
|
+
parseStreamLine(line) {
|
|
60
|
+
return messagesStreamEvents(line);
|
|
61
|
+
},
|
|
62
|
+
failureReason(stdout) {
|
|
63
|
+
return messagesStreamFailure(stdout);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=grok.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BotRuntimeId } from "../config.js";
|
|
2
|
+
import { type BotRuntime, type RuntimeTurnInput, type RuntimeTurnResult, type SpawnLike } from "./types.js";
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
export { createClaudeRuntime, claudePermissionMode } from "./claude.js";
|
|
5
|
+
export { createCodexRuntime, codexPrompt } from "./codex.js";
|
|
6
|
+
export { createGrokRuntime, grokPrompt } from "./grok.js";
|
|
7
|
+
export declare function runtimeFor(id: BotRuntimeId, env?: NodeJS.ProcessEnv): BotRuntime;
|
|
8
|
+
/**
|
|
9
|
+
* Real child spawn, stdin closed. stdout is delivered line by line while the
|
|
10
|
+
* process runs (for streaming runtimes) and also returned whole at the end.
|
|
11
|
+
* No time limit unless `timeoutMs` is set; `signal` kills the process.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Every live descendant of `pid` (children first found, deepest last). The
|
|
15
|
+
* model CLI starts its own tools and shells; stopping only the top process
|
|
16
|
+
* leaves those running and holding the output pipe open, so a stop would hang
|
|
17
|
+
* until they finish. POSIX only; empty when `ps` is unavailable.
|
|
18
|
+
*/
|
|
19
|
+
export declare function descendantPids(pid: number, listProcesses?: () => string): number[];
|
|
20
|
+
/** After a stop, wait this long for the output pipes to close before settling anyway. */
|
|
21
|
+
export declare const STOP_SETTLE_GRACE_MS = 2000;
|
|
22
|
+
export declare const defaultSpawn: SpawnLike;
|
|
23
|
+
export interface RunTurnDeps {
|
|
24
|
+
spawnImpl?: SpawnLike;
|
|
25
|
+
env?: NodeJS.ProcessEnv;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
scratchDir?: string;
|
|
28
|
+
newSessionId?: () => string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run one turn: spawn the runtime CLI, parse the reply, clean up scratch.
|
|
32
|
+
* Throws RuntimeError (with a bounded stderr excerpt) on any failure.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runRuntimeTurn(runtime: BotRuntime, input: RuntimeTurnInput, deps?: RunTurnDeps): Promise<RuntimeTurnResult>;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|