@monoes/monomindcli 2.9.21 → 2.9.22
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/.claude/skills/mastermind-createorg/SKILL.md +26 -3
- package/dist/src/commands/events.d.ts +9 -0
- package/dist/src/commands/events.d.ts.map +1 -0
- package/dist/src/commands/events.js +89 -0
- package/dist/src/commands/events.js.map +1 -0
- package/dist/src/commands/index.d.ts +1 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +2 -1
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/org-observe.d.ts +21 -0
- package/dist/src/commands/org-observe.d.ts.map +1 -1
- package/dist/src/commands/org-observe.js +79 -0
- package/dist/src/commands/org-observe.js.map +1 -1
- package/dist/src/commands/org.d.ts.map +1 -1
- package/dist/src/commands/org.js +20 -0
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/orgrt/approvals.d.ts +16 -1
- package/dist/src/orgrt/approvals.d.ts.map +1 -1
- package/dist/src/orgrt/approvals.js +39 -2
- package/dist/src/orgrt/approvals.js.map +1 -1
- package/dist/src/orgrt/copilot-runner.d.ts +14 -0
- package/dist/src/orgrt/copilot-runner.d.ts.map +1 -0
- package/dist/src/orgrt/copilot-runner.js +250 -0
- package/dist/src/orgrt/copilot-runner.js.map +1 -0
- package/dist/src/orgrt/crush-runner.d.ts +24 -0
- package/dist/src/orgrt/crush-runner.d.ts.map +1 -0
- package/dist/src/orgrt/crush-runner.js +251 -0
- package/dist/src/orgrt/crush-runner.js.map +1 -0
- package/dist/src/orgrt/daemon.d.ts +40 -1
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +63 -2
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/grok-runner.d.ts +18 -0
- package/dist/src/orgrt/grok-runner.d.ts.map +1 -0
- package/dist/src/orgrt/grok-runner.js +323 -0
- package/dist/src/orgrt/grok-runner.js.map +1 -0
- package/dist/src/orgrt/pi-rpc-runner.d.ts +68 -0
- package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -0
- package/dist/src/orgrt/pi-rpc-runner.js +400 -0
- package/dist/src/orgrt/pi-rpc-runner.js.map +1 -0
- package/dist/src/orgrt/pi-runner.d.ts +16 -0
- package/dist/src/orgrt/pi-runner.d.ts.map +1 -0
- package/dist/src/orgrt/pi-runner.js +264 -0
- package/dist/src/orgrt/pi-runner.js.map +1 -0
- package/dist/src/orgrt/qwen-runner.d.ts +18 -0
- package/dist/src/orgrt/qwen-runner.d.ts.map +1 -0
- package/dist/src/orgrt/qwen-runner.js +267 -0
- package/dist/src/orgrt/qwen-runner.js.map +1 -0
- package/dist/src/orgrt/types.d.ts +18 -0
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +2 -2
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/orgrt/usage-proxy.d.ts +32 -0
- package/dist/src/orgrt/usage-proxy.d.ts.map +1 -0
- package/dist/src/orgrt/usage-proxy.js +226 -0
- package/dist/src/orgrt/usage-proxy.js.map +1 -0
- package/dist/src/ui/dashboard.html +63 -0
- package/dist/src/ui/orgs.html +8 -1
- package/dist/src/ui/routes-org.mjs +26 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// packages/@monomind/cli/src/orgrt/copilot-runner.ts
|
|
2
|
+
/**
|
|
3
|
+
* CopilotAgentRunner — AgentRunner impl backed by the GitHub Copilot CLI
|
|
4
|
+
* (`copilot`, https://docs.github.com/en/copilot/reference/copilot-cli-reference).
|
|
5
|
+
*
|
|
6
|
+
* Architectural pattern: SAME as the other subprocess runners — spawn the
|
|
7
|
+
* CLI, parse its output, normalize to AgentMessage.
|
|
8
|
+
*
|
|
9
|
+
* Auth: GitHub's own device/browser login flow (`copilot` handles this
|
|
10
|
+
* itself on first run). No env vars set here.
|
|
11
|
+
*
|
|
12
|
+
* Subprocess protocol — per public docs, NOT byte-verified against a running
|
|
13
|
+
* binary:
|
|
14
|
+
* - Invocation: `copilot -p "<prompt>" --output-format json -s
|
|
15
|
+
* --allow-all-tools --no-ask-user [--model=<model>]`. `-s`
|
|
16
|
+
* (silent) suppresses the stats/decoration footer that otherwise mixes
|
|
17
|
+
* into stdout (documented GitHub issue: without `-s`, response text can
|
|
18
|
+
* be absent from plain stdout entirely — `--output-format json` + `-s`
|
|
19
|
+
* is the documented workaround). `--no-ask-user` disables the CLI's
|
|
20
|
+
* ask_user tool so a headless run can never block waiting on a question
|
|
21
|
+
* with no human to answer it — confirmed by cross-checking a second,
|
|
22
|
+
* independent public agentic-CLI wrapper's provider table, which lists
|
|
23
|
+
* the identical `-s --allow-all-tools --no-ask-user` combination for
|
|
24
|
+
* Copilot's non-interactive mode; this runner was missing `--no-ask-user`
|
|
25
|
+
* until that cross-check.
|
|
26
|
+
* - Output: NDJSON; assistant text arrives on `assistant.message`-shaped
|
|
27
|
+
* events. The exact full field list isn't published, so parseCopilotEvents
|
|
28
|
+
* tolerates a couple of plausible shapes (an explicit `type`/`kind` of
|
|
29
|
+
* 'assistant.message' or 'assistant' carrying `content`/`text`) and
|
|
30
|
+
* fails closed (no text extracted) on anything else, rather than
|
|
31
|
+
* guessing wrong and emitting garbage.
|
|
32
|
+
* - Session/resume: Copilot documents a `--resume=<id>` flag, but nothing
|
|
33
|
+
* in this runner's output parsing surfaces a session id to pass back in
|
|
34
|
+
* (the same gap the cross-check source above notes about its own
|
|
35
|
+
* integration), so resume can't be wired up yet — every mailbox prompt
|
|
36
|
+
* is a fresh `copilot -p` invocation, same disclosed limitation as
|
|
37
|
+
* CrushAgentRunner. Revisit if a session-id-bearing event/field is found.
|
|
38
|
+
* - Token usage: NOT documented for `--output-format json`, and Copilot
|
|
39
|
+
* CLI has no documented custom-base-URL override (it talks to GitHub's
|
|
40
|
+
* own Copilot backend, not a passthrough-able OpenAI/Anthropic
|
|
41
|
+
* endpoint), so this runner does not attempt usage-proxy accounting —
|
|
42
|
+
* it always reports 0 tokens. Revisit if GitHub documents a usage field
|
|
43
|
+
* or a proxyable endpoint later.
|
|
44
|
+
*
|
|
45
|
+
* Org tools — FENCE PROTOCOL: same approach as the other subprocess runners.
|
|
46
|
+
*/
|
|
47
|
+
import { spawn } from 'node:child_process';
|
|
48
|
+
import { buildToolProtocol, parseToolCalls, executeToolCall, formatToolResults, MAX_TOOL_ROUNDS, TOOL_CALL_RE } from './tool-fence.js';
|
|
49
|
+
const TURN_TIMEOUT_MS = 2 * 60 * 60 * 1000;
|
|
50
|
+
/** Distinct from TURN_TIMEOUT_MS: catches a hung first-run interactive prompt
|
|
51
|
+
* fast instead of waiting out the full turn timeout. Any output at all
|
|
52
|
+
* disarms it. Copilot CLI has a DOCUMENTED bug where its directory-trust
|
|
53
|
+
* and path-access prompts can hang indefinitely in CI/headless invocations
|
|
54
|
+
* with no way to answer them — this is the primary mitigation for copilot
|
|
55
|
+
* specifically, not just a defensive backstop. */
|
|
56
|
+
const STARTUP_GRACE_MS = 45_000;
|
|
57
|
+
function coerceText(v) {
|
|
58
|
+
if (typeof v === 'string')
|
|
59
|
+
return v;
|
|
60
|
+
if (Array.isArray(v)) {
|
|
61
|
+
return v
|
|
62
|
+
.filter((b) => !!b && typeof b === 'object' && b.type === 'text')
|
|
63
|
+
.map((b) => b.text)
|
|
64
|
+
.join('\n');
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Pure NDJSON parser — exported for unit testing against fixture lines
|
|
69
|
+
* (copilot-runner.test.ts). */
|
|
70
|
+
export function parseCopilotEvents(lines) {
|
|
71
|
+
const rawTexts = [];
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed || !trimmed.startsWith('{'))
|
|
75
|
+
continue;
|
|
76
|
+
let ev;
|
|
77
|
+
try {
|
|
78
|
+
ev = JSON.parse(trimmed);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const kind = ev.type ?? ev.kind;
|
|
84
|
+
if (kind === 'assistant.message' || kind === 'assistant') {
|
|
85
|
+
const text = coerceText(ev.content) ?? ev.text ?? coerceText(ev.message?.content) ?? ev.message?.text;
|
|
86
|
+
if (text)
|
|
87
|
+
rawTexts.push(text);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (ev.role === 'assistant') {
|
|
91
|
+
const text = coerceText(ev.content) ?? ev.text;
|
|
92
|
+
if (text)
|
|
93
|
+
rawTexts.push(text);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const texts = rawTexts.map((t) => t.replace(TOOL_CALL_RE, '').trim());
|
|
97
|
+
return { texts, rawTexts };
|
|
98
|
+
}
|
|
99
|
+
export class CopilotAgentRunner {
|
|
100
|
+
copilotBin;
|
|
101
|
+
constructor(copilotBin) {
|
|
102
|
+
this.copilotBin = copilotBin;
|
|
103
|
+
}
|
|
104
|
+
async *run(args) {
|
|
105
|
+
const bin = this.copilotBin || process.env.COPILOT_CLI_BIN || 'copilot';
|
|
106
|
+
try {
|
|
107
|
+
for await (const p of args.prompt) {
|
|
108
|
+
const text = typeof p === 'string' ? p : (p?.message?.content ?? String(p ?? ''));
|
|
109
|
+
let nextPrompt = `${args.systemPrompt}${buildToolProtocol(args.tools)}\n\n---\n\n${text}`;
|
|
110
|
+
for (let round = 0; round <= MAX_TOOL_ROUNDS; round++) {
|
|
111
|
+
const outcome = await this.runTurn(bin, nextPrompt, args);
|
|
112
|
+
if (outcome.hangSuspected) {
|
|
113
|
+
throw new Error(`CopilotAgentRunner: copilot produced no output within ${STARTUP_GRACE_MS / 1000}s and was ` +
|
|
114
|
+
'killed. This is a documented copilot CLI issue: its directory-trust/path-access prompts can ' +
|
|
115
|
+
'hang indefinitely in headless invocations with no way to answer them. Run `copilot` once ' +
|
|
116
|
+
'manually in a real terminal in this project to accept any prompts (and confirm --add-dir ' +
|
|
117
|
+
`covers every path it needs), then retry.${outcome.stderrTail ? `\nstderr: ${outcome.stderrTail.slice(-500)}` : ''}`);
|
|
118
|
+
}
|
|
119
|
+
if (outcome.exitCode !== 0) {
|
|
120
|
+
throw new Error(`CopilotAgentRunner: copilot failed (exit ${outcome.exitCode})` +
|
|
121
|
+
(outcome.timedOut ? ` — killed after exceeding the ${TURN_TIMEOUT_MS / 3_600_000}h turn timeout` : '') +
|
|
122
|
+
(outcome.stderrTail ? `\nstderr: ${outcome.stderrTail.slice(-500)}` : ''));
|
|
123
|
+
}
|
|
124
|
+
for (const t of outcome.texts) {
|
|
125
|
+
if (t.trim())
|
|
126
|
+
yield { type: 'assistant', text: t };
|
|
127
|
+
}
|
|
128
|
+
const malformed = [];
|
|
129
|
+
const calls = parseToolCalls(outcome.rawTexts, (raw, err) => malformed.push(`[monomind] ignored malformed tool_call fence (${err}): ${raw.slice(0, 200)}`));
|
|
130
|
+
for (const note of malformed)
|
|
131
|
+
yield { type: 'assistant', text: note };
|
|
132
|
+
if (calls.length === 0) {
|
|
133
|
+
yield { type: 'result', subtype: 'success', input_tokens: 0, output_tokens: 0 };
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (round === MAX_TOOL_ROUNDS) {
|
|
137
|
+
yield { type: 'assistant', text: `[monomind] tool-call round cap (${MAX_TOOL_ROUNDS}) reached — dropping ${calls.length} pending tool call(s)` };
|
|
138
|
+
yield { type: 'result', subtype: 'success', input_tokens: 0, output_tokens: 0 };
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
const results = [];
|
|
142
|
+
for (const call of calls)
|
|
143
|
+
results.push(await executeToolCall(args.tools, call, args.canUseTool));
|
|
144
|
+
nextPrompt = `${args.systemPrompt}${buildToolProtocol(args.tools)}\n\n---\n\n${formatToolResults(calls, results)}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (err.code === 'ENOENT') {
|
|
150
|
+
throw new Error('CopilotAgentRunner requires the GitHub Copilot CLI (copilot) on PATH. ' +
|
|
151
|
+
'Install it: npm install -g @github/copilot, then run `copilot` once to ' +
|
|
152
|
+
'authenticate. Or unset the runtime to use Claude.');
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
runTurn(bin, prompt, args) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const cliArgs = ['-p', prompt, '--output-format', 'json', '-s', '--allow-all-tools', '--no-ask-user'];
|
|
160
|
+
if (args.model)
|
|
161
|
+
cliArgs.push(`--model=${args.model}`);
|
|
162
|
+
cliArgs.push('--add-dir', args.cwd);
|
|
163
|
+
const child = spawn(bin, cliArgs, {
|
|
164
|
+
cwd: args.cwd,
|
|
165
|
+
env: { ...process.env, ...args.env },
|
|
166
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
167
|
+
});
|
|
168
|
+
let stderrTail = '';
|
|
169
|
+
child.stderr?.on('data', (c) => { stderrTail = (stderrTail + c.toString()).slice(-4000); });
|
|
170
|
+
let sawOutput = false;
|
|
171
|
+
let timedOut = false;
|
|
172
|
+
let hangSuspected = false;
|
|
173
|
+
const KILL_GRACE_MS = 5000;
|
|
174
|
+
let killTimer;
|
|
175
|
+
const timer = setTimeout(() => {
|
|
176
|
+
timedOut = true;
|
|
177
|
+
child.kill('SIGTERM');
|
|
178
|
+
killTimer = setTimeout(() => { try {
|
|
179
|
+
child.kill('SIGKILL');
|
|
180
|
+
}
|
|
181
|
+
catch { /* already gone */ } }, KILL_GRACE_MS);
|
|
182
|
+
killTimer.unref?.();
|
|
183
|
+
}, TURN_TIMEOUT_MS);
|
|
184
|
+
// See STARTUP_GRACE_MS — disarmed by the first stdout chunk below.
|
|
185
|
+
let hangTimer = setTimeout(() => {
|
|
186
|
+
hangSuspected = true;
|
|
187
|
+
child.kill('SIGTERM');
|
|
188
|
+
killTimer = setTimeout(() => { try {
|
|
189
|
+
child.kill('SIGKILL');
|
|
190
|
+
}
|
|
191
|
+
catch { /* already gone */ } }, KILL_GRACE_MS);
|
|
192
|
+
killTimer.unref?.();
|
|
193
|
+
}, STARTUP_GRACE_MS);
|
|
194
|
+
const exitPromise = new Promise((res, rej) => {
|
|
195
|
+
child.on('error', rej);
|
|
196
|
+
child.on('close', (code) => res(code ?? 1));
|
|
197
|
+
});
|
|
198
|
+
exitPromise.catch(() => { });
|
|
199
|
+
const readLines = (async () => {
|
|
200
|
+
const lines = [];
|
|
201
|
+
let buf = '';
|
|
202
|
+
for await (const chunk of child.stdout) {
|
|
203
|
+
if (!sawOutput) {
|
|
204
|
+
sawOutput = true;
|
|
205
|
+
if (hangTimer) {
|
|
206
|
+
clearTimeout(hangTimer);
|
|
207
|
+
hangTimer = undefined;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
buf += chunk.toString();
|
|
211
|
+
const parts = buf.split('\n');
|
|
212
|
+
buf = parts.pop() ?? '';
|
|
213
|
+
lines.push(...parts);
|
|
214
|
+
}
|
|
215
|
+
if (buf.trim())
|
|
216
|
+
lines.push(buf);
|
|
217
|
+
return lines;
|
|
218
|
+
})();
|
|
219
|
+
// Timer cleanup lives in a top-level .finally() (not nested inside a
|
|
220
|
+
// success-path .then()) so it runs on EITHER path — a stdout stream
|
|
221
|
+
// error would otherwise skip straight to reject() and leave the
|
|
222
|
+
// TURN_TIMEOUT_MS/hangTimer/killTimer timers running past the
|
|
223
|
+
// process's actual lifetime.
|
|
224
|
+
Promise.all([readLines, exitPromise])
|
|
225
|
+
.then(([lines, exitCode]) => {
|
|
226
|
+
const parsed = parseCopilotEvents(lines);
|
|
227
|
+
resolve({ texts: parsed.texts, rawTexts: parsed.rawTexts, exitCode, stderrTail, timedOut, hangSuspected });
|
|
228
|
+
}, (err) => {
|
|
229
|
+
// A stdout stream error (the reject path) means the process is
|
|
230
|
+
// still ALIVE and unmanaged — none of the timeout/hang timers
|
|
231
|
+
// would have fired to kill it. Without this, that error would
|
|
232
|
+
// orphan the child. child.kill() on an already-dead process is a
|
|
233
|
+
// documented no-op, so this is safe on every path.
|
|
234
|
+
try {
|
|
235
|
+
child.kill('SIGTERM');
|
|
236
|
+
}
|
|
237
|
+
catch { /* already gone */ }
|
|
238
|
+
reject(err);
|
|
239
|
+
})
|
|
240
|
+
.finally(() => {
|
|
241
|
+
clearTimeout(timer);
|
|
242
|
+
if (hangTimer)
|
|
243
|
+
clearTimeout(hangTimer);
|
|
244
|
+
if (killTimer)
|
|
245
|
+
clearTimeout(killTimer);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=copilot-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot-runner.js","sourceRoot":"","sources":["../../../src/orgrt/copilot-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentRunner, AgentRunArgs, AgentMessage } from './agent-runner.js';
|
|
2
|
+
/** Strip tool_call fences and normalize whitespace on crush's plain-text
|
|
3
|
+
* stdout. Exported for unit testing (crush-runner.test.ts). */
|
|
4
|
+
export declare function parseCrushOutput(stdout: string): {
|
|
5
|
+
text: string;
|
|
6
|
+
rawText: string;
|
|
7
|
+
};
|
|
8
|
+
export interface CrushAgentRunnerOptions {
|
|
9
|
+
crushBin?: string;
|
|
10
|
+
/** Enable usage-proxy accounting. Off by default since the base-URL env
|
|
11
|
+
* var it relies on is an unconfirmed guess (see file header). */
|
|
12
|
+
usageProxy?: {
|
|
13
|
+
upstreamBaseUrl: string;
|
|
14
|
+
baseUrlEnvVar?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare class CrushAgentRunner implements AgentRunner {
|
|
18
|
+
private crushBin?;
|
|
19
|
+
private usageProxyOpts?;
|
|
20
|
+
constructor(opts?: CrushAgentRunnerOptions);
|
|
21
|
+
run(args: AgentRunArgs): AsyncIterable<AgentMessage>;
|
|
22
|
+
private runTurn;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=crush-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crush-runner.d.ts","sourceRoot":"","sources":["../../../src/orgrt/crush-runner.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAWjF;gEACgE;AAChE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAIlF;AAaD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;sEACkE;IAClE,UAAU,CAAC,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAC,CAAqD;IAE5E,YAAY,IAAI,GAAE,uBAA4B,EAQ7C;IAEM,GAAG,CAAC,IAAI,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC,CAyF1D;IAED,OAAO,CAAC,OAAO;CAuFhB"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// packages/@monomind/cli/src/orgrt/crush-runner.ts
|
|
2
|
+
/**
|
|
3
|
+
* CrushAgentRunner — AgentRunner impl backed by the Crush CLI
|
|
4
|
+
* (`crush`, https://github.com/charmbracelet/crush).
|
|
5
|
+
*
|
|
6
|
+
* Architectural difference from the other subprocess runners: `crush run
|
|
7
|
+
* "<prompt>"` (per its public CLI docs) is a plain one-shot invocation —
|
|
8
|
+
* text response to stdout, no documented JSON output mode. On the FIRST turn
|
|
9
|
+
* this runner sends the system prompt + tool protocol; subsequent turns add
|
|
10
|
+
* `--continue` (resume the most-recently-used session — confirmed by
|
|
11
|
+
* cross-checking a second, independent public agentic-CLI wrapper's provider
|
|
12
|
+
* table, which lists `--session <id>` / `--continue` as crush's resume
|
|
13
|
+
* mechanism) instead of re-sending the full system prompt, so conversational
|
|
14
|
+
* context is NOT re-derived from scratch every turn the way the original
|
|
15
|
+
* (pre-cross-check) revision of this runner did.
|
|
16
|
+
*
|
|
17
|
+
* RISK, not independently confirmed either way: if crush's "most recently
|
|
18
|
+
* used session" is scoped GLOBALLY rather than per working directory, two
|
|
19
|
+
* different crush-backed roles running concurrently in the same org (each
|
|
20
|
+
* with its OWN cwd via args.cwd) could cross-contaminate — role B's
|
|
21
|
+
* `--continue` might resume role A's conversation instead of its own. crush
|
|
22
|
+
* DOES support an explicit `--session <id>` flag per the same source, which
|
|
23
|
+
* would be the correct fix (capture the session id crush's own output
|
|
24
|
+
* reports, if any, and pass it explicitly instead of relying on "most
|
|
25
|
+
* recent") — not implemented here because crush's plain-text stdout doesn't
|
|
26
|
+
* appear to surface a session id to capture. Validate against a live
|
|
27
|
+
* install before running multiple concurrent crush roles in one org.
|
|
28
|
+
*
|
|
29
|
+
* Usage accounting — crush's plain-text output carries no token counts, so
|
|
30
|
+
* this runner optionally routes crush's LLM traffic through a
|
|
31
|
+
* UsageProxyServer (usage-proxy.ts): crush supports pointing its provider at
|
|
32
|
+
* a custom OpenAI-compatible base URL for BYOK/self-hosted setups, so the
|
|
33
|
+
* runner sets that env var to the local proxy before spawning and reads
|
|
34
|
+
* totals() after the turn. The exact env var crush reads for a runtime
|
|
35
|
+
* base-URL override was not confirmed against a live install (its provider
|
|
36
|
+
* config is normally a JSON file, `~/.config/crush/crush.json`), so the var
|
|
37
|
+
* name is an OVERRIDABLE BEST GUESS (`OPENAI_BASE_URL`, constructor param
|
|
38
|
+
* `baseUrlEnvVar`) — if it doesn't match your crush config, usage simply
|
|
39
|
+
* stays at 0 (fails closed, never breaks the turn itself). A second,
|
|
40
|
+
* independent source's own integration notes state more confidently that
|
|
41
|
+
* crush has NO base-URL env override at all and instead reads a runtime
|
|
42
|
+
* `provider.base_url` from a generated JSON config file pointed to by an
|
|
43
|
+
* env var — if true, this env-var guess does nothing (which the fails-closed
|
|
44
|
+
* design already tolerates) and the real fix is writing that config file
|
|
45
|
+
* before spawn, not renaming the env var. Left as a known follow-up rather
|
|
46
|
+
* than implemented here, since that source's own claim isn't independently
|
|
47
|
+
* confirmed either. Fix the org's provider config or pass the right var
|
|
48
|
+
* name via `baseUrlEnvVar` once confirmed against a live install.
|
|
49
|
+
*
|
|
50
|
+
* Org tools — FENCE PROTOCOL: same approach as the other subprocess runners.
|
|
51
|
+
*/
|
|
52
|
+
import { spawn } from 'node:child_process';
|
|
53
|
+
import { buildToolProtocol, parseToolCalls, executeToolCall, formatToolResults, MAX_TOOL_ROUNDS, TOOL_CALL_RE } from './tool-fence.js';
|
|
54
|
+
import { UsageProxyServer } from './usage-proxy.js';
|
|
55
|
+
const TURN_TIMEOUT_MS = 2 * 60 * 60 * 1000;
|
|
56
|
+
/** Distinct from TURN_TIMEOUT_MS: catches a hung first-run interactive prompt
|
|
57
|
+
* (trust/telemetry gate) fast instead of waiting out the full turn timeout.
|
|
58
|
+
* Fires only if the process has produced ZERO stdout by this point — any
|
|
59
|
+
* output at all disarms it, since a slow model response is not a hang. */
|
|
60
|
+
const STARTUP_GRACE_MS = 45_000;
|
|
61
|
+
/** Strip tool_call fences and normalize whitespace on crush's plain-text
|
|
62
|
+
* stdout. Exported for unit testing (crush-runner.test.ts). */
|
|
63
|
+
export function parseCrushOutput(stdout) {
|
|
64
|
+
const rawText = stdout.trim();
|
|
65
|
+
const text = rawText.replace(TOOL_CALL_RE, '').trim();
|
|
66
|
+
return { text, rawText };
|
|
67
|
+
}
|
|
68
|
+
export class CrushAgentRunner {
|
|
69
|
+
crushBin;
|
|
70
|
+
usageProxyOpts;
|
|
71
|
+
constructor(opts = {}) {
|
|
72
|
+
this.crushBin = opts.crushBin;
|
|
73
|
+
if (opts.usageProxy) {
|
|
74
|
+
this.usageProxyOpts = {
|
|
75
|
+
upstreamBaseUrl: opts.usageProxy.upstreamBaseUrl,
|
|
76
|
+
baseUrlEnvVar: opts.usageProxy.baseUrlEnvVar ?? 'OPENAI_BASE_URL',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async *run(args) {
|
|
81
|
+
const bin = this.crushBin || process.env.CRUSH_CLI_BIN || 'crush';
|
|
82
|
+
let proxy;
|
|
83
|
+
if (this.usageProxyOpts) {
|
|
84
|
+
proxy = new UsageProxyServer({ upstreamBaseUrl: this.usageProxyOpts.upstreamBaseUrl, apiStyle: 'openai' });
|
|
85
|
+
await proxy.start();
|
|
86
|
+
}
|
|
87
|
+
// Set once the first crush invocation completes — subsequent calls pass
|
|
88
|
+
// --continue (resume the most-recently-used session) instead of
|
|
89
|
+
// re-sending the full system prompt from scratch every turn.
|
|
90
|
+
let sessionStarted = false;
|
|
91
|
+
try {
|
|
92
|
+
for await (const p of args.prompt) {
|
|
93
|
+
const text = typeof p === 'string' ? p : (p?.message?.content ?? String(p ?? ''));
|
|
94
|
+
let nextPrompt = sessionStarted ? text : `${args.systemPrompt}${buildToolProtocol(args.tools)}\n\n---\n\n${text}`;
|
|
95
|
+
// Reset ONCE per mailbox prompt, not per tool-call round — totals()
|
|
96
|
+
// is read after the LAST round below, so resetting inside the round
|
|
97
|
+
// loop was discarding every round's usage except the final one.
|
|
98
|
+
proxy?.reset();
|
|
99
|
+
for (let round = 0; round <= MAX_TOOL_ROUNDS; round++) {
|
|
100
|
+
const outcome = await this.runTurn(bin, nextPrompt, args, proxy, sessionStarted);
|
|
101
|
+
sessionStarted = true;
|
|
102
|
+
if (outcome.hangSuspected) {
|
|
103
|
+
throw new Error(`CrushAgentRunner: crush produced no output within ${STARTUP_GRACE_MS / 1000}s and was killed. ` +
|
|
104
|
+
'This usually means it is stuck on a first-run interactive prompt (trust/telemetry gate) ' +
|
|
105
|
+
'that headless mode has no way to answer. Run `crush` once manually in a real terminal in ' +
|
|
106
|
+
`this project to accept any prompts, then retry.${outcome.stderrTail ? `\nstderr: ${outcome.stderrTail.slice(-500)}` : ''}`);
|
|
107
|
+
}
|
|
108
|
+
if (outcome.exitCode !== 0) {
|
|
109
|
+
throw new Error(`CrushAgentRunner: crush run failed (exit ${outcome.exitCode})` +
|
|
110
|
+
(outcome.timedOut ? ` — killed after exceeding the ${TURN_TIMEOUT_MS / 3_600_000}h turn timeout` : '') +
|
|
111
|
+
(outcome.stderrTail ? `\nstderr: ${outcome.stderrTail.slice(-500)}` : ''));
|
|
112
|
+
}
|
|
113
|
+
if (outcome.text)
|
|
114
|
+
yield { type: 'assistant', text: outcome.text };
|
|
115
|
+
const malformed = [];
|
|
116
|
+
const calls = parseToolCalls([outcome.rawText], (raw, err) => malformed.push(`[monomind] ignored malformed tool_call fence (${err}): ${raw.slice(0, 200)}`));
|
|
117
|
+
for (const note of malformed)
|
|
118
|
+
yield { type: 'assistant', text: note };
|
|
119
|
+
if (calls.length === 0) {
|
|
120
|
+
const totals = proxy?.totals();
|
|
121
|
+
yield {
|
|
122
|
+
type: 'result',
|
|
123
|
+
subtype: 'success',
|
|
124
|
+
input_tokens: totals?.inputTokens ?? 0,
|
|
125
|
+
output_tokens: totals?.outputTokens ?? 0,
|
|
126
|
+
};
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (round === MAX_TOOL_ROUNDS) {
|
|
130
|
+
yield { type: 'assistant', text: `[monomind] tool-call round cap (${MAX_TOOL_ROUNDS}) reached — dropping ${calls.length} pending tool call(s)` };
|
|
131
|
+
const totals = proxy?.totals();
|
|
132
|
+
yield { type: 'result', subtype: 'success', input_tokens: totals?.inputTokens ?? 0, output_tokens: totals?.outputTokens ?? 0 };
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
// sessionStarted is true by now (set right after the first
|
|
136
|
+
// runTurn call above) — the retry continues the same crush
|
|
137
|
+
// session via --continue instead of re-sending the system prompt.
|
|
138
|
+
const results = [];
|
|
139
|
+
for (const call of calls)
|
|
140
|
+
results.push(await executeToolCall(args.tools, call, args.canUseTool));
|
|
141
|
+
nextPrompt = formatToolResults(calls, results);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if (err.code === 'ENOENT') {
|
|
147
|
+
throw new Error('CrushAgentRunner requires the Crush CLI (crush) on PATH. ' +
|
|
148
|
+
'Install it per https://github.com/charmbracelet/crush, then configure a provider. ' +
|
|
149
|
+
'Or unset the runtime to use Claude.');
|
|
150
|
+
}
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
await proxy?.stop();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
runTurn(bin, prompt, args, proxy, continueSession) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const cliArgs = ['run', prompt, '--yolo'];
|
|
160
|
+
if (args.model)
|
|
161
|
+
cliArgs.push('--model', args.model);
|
|
162
|
+
if (continueSession)
|
|
163
|
+
cliArgs.push('--continue');
|
|
164
|
+
// CRUSH_DISABLE_PROVIDER_AUTO_UPDATE: confirmed via crush's own docs —
|
|
165
|
+
// suppresses a first-run/periodic provider-list update check that would
|
|
166
|
+
// otherwise add an unpredictable network round-trip to a headless turn.
|
|
167
|
+
const env = {
|
|
168
|
+
...process.env,
|
|
169
|
+
CRUSH_DISABLE_PROVIDER_AUTO_UPDATE: '1',
|
|
170
|
+
...args.env,
|
|
171
|
+
};
|
|
172
|
+
if (proxy && this.usageProxyOpts)
|
|
173
|
+
env[this.usageProxyOpts.baseUrlEnvVar] = proxy.url();
|
|
174
|
+
const child = spawn(bin, cliArgs, { cwd: args.cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
175
|
+
let stderrTail = '';
|
|
176
|
+
child.stderr?.on('data', (c) => { stderrTail = (stderrTail + c.toString()).slice(-4000); });
|
|
177
|
+
let sawOutput = false;
|
|
178
|
+
let timedOut = false;
|
|
179
|
+
let hangSuspected = false;
|
|
180
|
+
const KILL_GRACE_MS = 5000;
|
|
181
|
+
let killTimer;
|
|
182
|
+
const timer = setTimeout(() => {
|
|
183
|
+
timedOut = true;
|
|
184
|
+
child.kill('SIGTERM');
|
|
185
|
+
killTimer = setTimeout(() => { try {
|
|
186
|
+
child.kill('SIGKILL');
|
|
187
|
+
}
|
|
188
|
+
catch { /* already gone */ } }, KILL_GRACE_MS);
|
|
189
|
+
killTimer.unref?.();
|
|
190
|
+
}, TURN_TIMEOUT_MS);
|
|
191
|
+
// See STARTUP_GRACE_MS — disarmed by the first stdout chunk below.
|
|
192
|
+
let hangTimer = setTimeout(() => {
|
|
193
|
+
hangSuspected = true;
|
|
194
|
+
child.kill('SIGTERM');
|
|
195
|
+
killTimer = setTimeout(() => { try {
|
|
196
|
+
child.kill('SIGKILL');
|
|
197
|
+
}
|
|
198
|
+
catch { /* already gone */ } }, KILL_GRACE_MS);
|
|
199
|
+
killTimer.unref?.();
|
|
200
|
+
}, STARTUP_GRACE_MS);
|
|
201
|
+
const exitPromise = new Promise((res, rej) => {
|
|
202
|
+
child.on('error', rej);
|
|
203
|
+
child.on('close', (code) => res(code ?? 1));
|
|
204
|
+
});
|
|
205
|
+
exitPromise.catch(() => { });
|
|
206
|
+
const readStdout = (async () => {
|
|
207
|
+
let stdout = '';
|
|
208
|
+
for await (const chunk of child.stdout) {
|
|
209
|
+
if (!sawOutput) {
|
|
210
|
+
sawOutput = true;
|
|
211
|
+
if (hangTimer) {
|
|
212
|
+
clearTimeout(hangTimer);
|
|
213
|
+
hangTimer = undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
stdout += chunk.toString();
|
|
217
|
+
}
|
|
218
|
+
return stdout;
|
|
219
|
+
})();
|
|
220
|
+
// Timer cleanup lives in a top-level .finally() (not nested inside a
|
|
221
|
+
// success-path .then()) so it runs on EITHER path — a stdout stream
|
|
222
|
+
// error would otherwise skip straight to reject() and leave the
|
|
223
|
+
// TURN_TIMEOUT_MS/hangTimer/killTimer timers running past the
|
|
224
|
+
// process's actual lifetime.
|
|
225
|
+
Promise.all([readStdout, exitPromise])
|
|
226
|
+
.then(([stdout, exitCode]) => {
|
|
227
|
+
const parsed = parseCrushOutput(stdout);
|
|
228
|
+
resolve({ text: parsed.text, rawText: parsed.rawText, exitCode, stderrTail, timedOut, hangSuspected });
|
|
229
|
+
}, (err) => {
|
|
230
|
+
// A stdout stream error (the reject path) means the process is
|
|
231
|
+
// still ALIVE and unmanaged — none of the timeout/hang timers
|
|
232
|
+
// would have fired to kill it. Without this, that error would
|
|
233
|
+
// orphan the child. child.kill() on an already-dead process is a
|
|
234
|
+
// documented no-op, so this is safe on every path.
|
|
235
|
+
try {
|
|
236
|
+
child.kill('SIGTERM');
|
|
237
|
+
}
|
|
238
|
+
catch { /* already gone */ }
|
|
239
|
+
reject(err);
|
|
240
|
+
})
|
|
241
|
+
.finally(() => {
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
if (hangTimer)
|
|
244
|
+
clearTimeout(hangTimer);
|
|
245
|
+
if (killTimer)
|
|
246
|
+
clearTimeout(killTimer);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=crush-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crush-runner.js","sourceRoot":"","sources":["../../../src/orgrt/crush-runner.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACvI,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC3C;;;2EAG2E;AAC3E,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC;gEACgE;AAChE,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC;AAoBD,MAAM,OAAO,gBAAgB;IACnB,QAAQ,CAAU;IAClB,cAAc,CAAsD;IAE5E,YAAY,IAAI,GAA4B,EAAE;QAC5C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,cAAc,GAAG;gBACpB,eAAe,EAAE,IAAI,CAAC,UAAU,CAAC,eAAe;gBAChD,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa,IAAI,iBAAiB;aAClE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,CAAC,GAAG,CAAC,IAAkB;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC;QAElE,IAAI,KAAmC,CAAC;QACxC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,KAAK,GAAG,IAAI,gBAAgB,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC3G,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QAED,wEAAwE;QACxE,gEAAgE;QAChE,6DAA6D;QAC7D,IAAI,cAAc,GAAG,KAAK,CAAC;QAE3B,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAClC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAClF,IAAI,UAAU,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC;gBAElH,oEAAoE;gBACpE,oEAAoE;gBACpE,gEAAgE;gBAChE,KAAK,EAAE,KAAK,EAAE,CAAC;gBAEf,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,eAAe,EAAE,KAAK,EAAE,EAAE,CAAC;oBACtD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;oBACjF,cAAc,GAAG,IAAI,CAAC;oBAEtB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;wBAC1B,MAAM,IAAI,KAAK,CACb,qDAAqD,gBAAgB,GAAG,IAAI,oBAAoB;4BAChG,0FAA0F;4BAC1F,2FAA2F;4BAC3F,kDAAkD,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC5H,CAAC;oBACJ,CAAC;oBACD,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;wBAC3B,MAAM,IAAI,KAAK,CACb,4CAA4C,OAAO,CAAC,QAAQ,GAAG;4BAC/D,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,iCAAiC,eAAe,GAAG,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;4BACtG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC1E,CAAC;oBACJ,CAAC;oBAED,IAAI,OAAO,CAAC,IAAI;wBAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;oBAElE,MAAM,SAAS,GAAa,EAAE,CAAC;oBAC/B,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAC1E,iDAAiD,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAC9E,CAAC,CAAC;oBACH,KAAK,MAAM,IAAI,IAAI,SAAS;wBAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;oBACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACvB,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,EAAE,CAAC;wBAC/B,MAAM;4BACJ,IAAI,EAAE,QAAQ;4BACd,OAAO,EAAE,SAAS;4BAClB,YAAY,EAAE,MAAM,EAAE,WAAW,IAAI,CAAC;4BACtC,aAAa,EAAE,MAAM,EAAE,YAAY,IAAI,CAAC;yBACzC,CAAC;wBACF,MAAM;oBACR,CAAC;oBAED,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;wBAC9B,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,mCAAmC,eAAe,wBAAwB,KAAK,CAAC,MAAM,uBAAuB,EAAE,CAAC;wBACjJ,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,EAAE,CAAC;wBAC/B,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,IAAI,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,IAAI,CAAC,EAAE,CAAC;wBAC/H,MAAM;oBACR,CAAC;oBAED,2DAA2D;oBAC3D,2DAA2D;oBAC3D,kEAAkE;oBAClE,MAAM,OAAO,GAAa,EAAE,CAAC;oBAC7B,KAAK,MAAM,IAAI,IAAI,KAAK;wBAAE,OAAO,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;oBACjG,UAAU,GAAG,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACjD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrD,MAAM,IAAI,KAAK,CACb,2DAA2D;oBAC3D,oFAAoF;oBACpF,qCAAqC,CACtC,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;IAEO,OAAO,CACb,GAAW,EACX,MAAc,EACd,IAAkB,EAClB,KAAmC,EACnC,eAAwB;QAExB,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClD,MAAM,OAAO,GAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACpD,IAAI,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACpD,IAAI,eAAe;gBAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAEhD,uEAAuE;YACvE,wEAAwE;YACxE,wEAAwE;YACxE,MAAM,GAAG,GAAuC;gBAC9C,GAAG,OAAO,CAAC,GAAG;gBACd,kCAAkC,EAAE,GAAG;gBACvC,GAAG,IAAI,CAAC,GAAG;aACZ,CAAC;YACF,IAAI,KAAK,IAAI,IAAI,CAAC,cAAc;gBAAE,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;YAEvF,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YAE7F,IAAI,UAAU,GAAG,EAAE,CAAC;YACpB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,GAAG,UAAU,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAEpG,IAAI,SAAS,GAAG,KAAK,CAAC;YACtB,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,aAAa,GAAG,KAAK,CAAC;YAC1B,MAAM,aAAa,GAAG,IAAI,CAAC;YAC3B,IAAI,SAAoD,CAAC;YACzD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,QAAQ,GAAG,IAAI,CAAC;gBAChB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACtB,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;oBAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;gBAC7G,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,eAAe,CAAC,CAAC;YAEpB,mEAAmE;YACnE,IAAI,SAAS,GAA8C,UAAU,CAAC,GAAG,EAAE;gBACzE,aAAa,GAAG,IAAI,CAAC;gBACrB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACtB,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;oBAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;gBAC7G,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAErB,MAAM,WAAW,GAAG,IAAI,OAAO,CAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBACnD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBACvB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC;YACH,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAE5B,MAAM,UAAU,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,IAAI,MAAM,GAAG,EAAE,CAAC;gBAChB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK,CAAC,MAA+B,EAAE,CAAC;oBAChE,IAAI,CAAC,SAAS,EAAE,CAAC;wBAAC,SAAS,GAAG,IAAI,CAAC;wBAAC,IAAI,SAAS,EAAE,CAAC;4BAAC,YAAY,CAAC,SAAS,CAAC,CAAC;4BAAC,SAAS,GAAG,SAAS,CAAC;wBAAC,CAAC;oBAAC,CAAC;oBACxG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC7B,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC,CAAC,EAAE,CAAC;YAEL,qEAAqE;YACrE,oEAAoE;YACpE,gEAAgE;YAChE,8DAA8D;YAC9D,6BAA6B;YAC7B,OAAO,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;iBACnC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE;gBAC3B,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACxC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC;YACzG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE;gBACT,+DAA+D;gBAC/D,8DAA8D;gBAC9D,8DAA8D;gBAC9D,iEAAiE;gBACjE,mDAAmD;gBACnD,IAAI,CAAC;oBAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;gBAC3D,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,IAAI,SAAS;oBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;gBACvC,IAAI,SAAS;oBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;YACzC,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -14,7 +14,19 @@ import { type RoleFence } from './fence.js';
|
|
|
14
14
|
* undefined for the default path keeps Claude/Antigravity orgs byte-for-byte
|
|
15
15
|
* unchanged. Callers pass `role.runtime ?? def.runtime` as `orgRuntime`
|
|
16
16
|
* (see resolveRoleRunner). */
|
|
17
|
-
export type RuntimeKind = 'claude' | 'kimicode' | 'opencode' | 'vercel' | 'codex' | 'antigravity'
|
|
17
|
+
export type RuntimeKind = 'claude' | 'kimicode' | 'opencode' | 'vercel' | 'codex' | 'antigravity' | 'grok' | 'qwen' | 'crush' | 'copilot' | 'pi'
|
|
18
|
+
/** Opt-in alternate to 'pi': keeps the pi subprocess alive for the whole
|
|
19
|
+
* mailbox session (--mode rpc) instead of spawning fresh per turn — see
|
|
20
|
+
* pi-rpc-runner.ts's header for the protocol source and its one
|
|
21
|
+
* documented-but-unverified heuristic (turn-completion detection via
|
|
22
|
+
* get_state polling). Prefer plain 'pi' unless you specifically want
|
|
23
|
+
* session-lifetime context continuity and have validated this against a
|
|
24
|
+
* live pi install. No 'qwen-rpc' equivalent exists yet — qwen's
|
|
25
|
+
* --input-format stream-json is real but its exact message schema wasn't
|
|
26
|
+
* independently confirmed the way pi's rpc.md was, so implementing it
|
|
27
|
+
* would mean guessing at a wire format rather than building against a
|
|
28
|
+
* literal, sourced example. */
|
|
29
|
+
| 'pi-rpc';
|
|
18
30
|
export type ProviderKind = 'subscription' | 'api-key' | 'base-url' | 'bedrock' | 'vertex' | 'gemini' | 'openai' | 'vercel-api-key' | 'codex' | 'antigravity';
|
|
19
31
|
export declare function resolveRunner(orgRuntime?: RuntimeKind, providerKind?: ProviderKind): AgentRunner | undefined;
|
|
20
32
|
/** Per-session variant: a role's own `runtime` field wins over the org-level
|
|
@@ -26,6 +38,33 @@ export declare function resolveRoleRunner(roleRuntime?: RuntimeKind, orgRuntime?
|
|
|
26
38
|
/** Per-role token budget: a role's own `budget_tokens` wins; otherwise the
|
|
27
39
|
* even split of run_config.budget_tokens across all roles. */
|
|
28
40
|
export declare function roleTokenBudget(role: OrgRole, def: OrgDef): number;
|
|
41
|
+
/** Idle watchdog's per-tick recovery check: given the previous nudge timestamp,
|
|
42
|
+
* the cumulative nudge count, and the timestamp of the most recent real tool
|
|
43
|
+
* call, returns the nudge count that should carry forward now that fresh
|
|
44
|
+
* activity means the org is no longer idle.
|
|
45
|
+
*
|
|
46
|
+
* `nudgedAt !== 0` means we're recovering from an outstanding nudge. Resetting
|
|
47
|
+
* the counter here means it only ever tracks UNRESOLVED idle spells in a row,
|
|
48
|
+
* not a lifetime total — a long-running org that goes idle and recovers any
|
|
49
|
+
* number of times (e.g. periodic checkpoints on a slow background task) is
|
|
50
|
+
* never punished for having had several separate, healthy idle spells over
|
|
51
|
+
* its lifetime. Before this existed, `nudges` only ever incremented, so a run
|
|
52
|
+
* that answered every single nudge with real work still hit the "org idle
|
|
53
|
+
* again after 3 nudges" cap and got force-stopped on its 4th idle spell —
|
|
54
|
+
* observed live killing an in-progress 24h soak test under 90 minutes in.
|
|
55
|
+
*
|
|
56
|
+
* But "recovering" must mean genuine forward progress, not just any bus
|
|
57
|
+
* event — a bare, content-free reply to the nudge (a boss that answers with
|
|
58
|
+
* "✓ Complete" and calls no tools at all) still updates lastActivity, so the
|
|
59
|
+
* org isn't flagged as silent, but it accomplishes nothing: nobody outside
|
|
60
|
+
* the boss's own turn ever sees it, since role coordination only happens via
|
|
61
|
+
* tool calls (org_send, org_task, ...). Requiring `lastToolActivity >=
|
|
62
|
+
* nudgedAt` — a real tool call happened AFTER this nudge was sent — closes
|
|
63
|
+
* that gap: observed live, a boss stuck responding to four consecutive
|
|
64
|
+
* 10-minute nudges with one-line acknowledgments and zero tool calls looped
|
|
65
|
+
* indefinitely making no progress, because every trivial reply reset the cap
|
|
66
|
+
* that was supposed to catch exactly this. */
|
|
67
|
+
export declare function resolvedIdleNudgeCount(nudgedAt: number, nudges: number, lastToolActivity: number): number;
|
|
29
68
|
/** Bounded ring buffer for agent terminal scrollback. */
|
|
30
69
|
export declare class ScrollbackBuffer {
|
|
31
70
|
private maxLines;
|