@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.
Files changed (61) hide show
  1. package/.claude/skills/mastermind-createorg/SKILL.md +26 -3
  2. package/dist/src/commands/events.d.ts +9 -0
  3. package/dist/src/commands/events.d.ts.map +1 -0
  4. package/dist/src/commands/events.js +89 -0
  5. package/dist/src/commands/events.js.map +1 -0
  6. package/dist/src/commands/index.d.ts +1 -1
  7. package/dist/src/commands/index.d.ts.map +1 -1
  8. package/dist/src/commands/index.js +2 -1
  9. package/dist/src/commands/index.js.map +1 -1
  10. package/dist/src/commands/org-observe.d.ts +21 -0
  11. package/dist/src/commands/org-observe.d.ts.map +1 -1
  12. package/dist/src/commands/org-observe.js +79 -0
  13. package/dist/src/commands/org-observe.js.map +1 -1
  14. package/dist/src/commands/org.d.ts.map +1 -1
  15. package/dist/src/commands/org.js +20 -0
  16. package/dist/src/commands/org.js.map +1 -1
  17. package/dist/src/orgrt/approvals.d.ts +16 -1
  18. package/dist/src/orgrt/approvals.d.ts.map +1 -1
  19. package/dist/src/orgrt/approvals.js +39 -2
  20. package/dist/src/orgrt/approvals.js.map +1 -1
  21. package/dist/src/orgrt/copilot-runner.d.ts +14 -0
  22. package/dist/src/orgrt/copilot-runner.d.ts.map +1 -0
  23. package/dist/src/orgrt/copilot-runner.js +250 -0
  24. package/dist/src/orgrt/copilot-runner.js.map +1 -0
  25. package/dist/src/orgrt/crush-runner.d.ts +24 -0
  26. package/dist/src/orgrt/crush-runner.d.ts.map +1 -0
  27. package/dist/src/orgrt/crush-runner.js +251 -0
  28. package/dist/src/orgrt/crush-runner.js.map +1 -0
  29. package/dist/src/orgrt/daemon.d.ts +40 -1
  30. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  31. package/dist/src/orgrt/daemon.js +63 -2
  32. package/dist/src/orgrt/daemon.js.map +1 -1
  33. package/dist/src/orgrt/grok-runner.d.ts +18 -0
  34. package/dist/src/orgrt/grok-runner.d.ts.map +1 -0
  35. package/dist/src/orgrt/grok-runner.js +323 -0
  36. package/dist/src/orgrt/grok-runner.js.map +1 -0
  37. package/dist/src/orgrt/pi-rpc-runner.d.ts +68 -0
  38. package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -0
  39. package/dist/src/orgrt/pi-rpc-runner.js +400 -0
  40. package/dist/src/orgrt/pi-rpc-runner.js.map +1 -0
  41. package/dist/src/orgrt/pi-runner.d.ts +16 -0
  42. package/dist/src/orgrt/pi-runner.d.ts.map +1 -0
  43. package/dist/src/orgrt/pi-runner.js +264 -0
  44. package/dist/src/orgrt/pi-runner.js.map +1 -0
  45. package/dist/src/orgrt/qwen-runner.d.ts +18 -0
  46. package/dist/src/orgrt/qwen-runner.d.ts.map +1 -0
  47. package/dist/src/orgrt/qwen-runner.js +267 -0
  48. package/dist/src/orgrt/qwen-runner.js.map +1 -0
  49. package/dist/src/orgrt/types.d.ts +18 -0
  50. package/dist/src/orgrt/types.d.ts.map +1 -1
  51. package/dist/src/orgrt/types.js +2 -2
  52. package/dist/src/orgrt/types.js.map +1 -1
  53. package/dist/src/orgrt/usage-proxy.d.ts +32 -0
  54. package/dist/src/orgrt/usage-proxy.d.ts.map +1 -0
  55. package/dist/src/orgrt/usage-proxy.js +226 -0
  56. package/dist/src/orgrt/usage-proxy.js.map +1 -0
  57. package/dist/src/ui/dashboard.html +63 -0
  58. package/dist/src/ui/orgs.html +8 -1
  59. package/dist/src/ui/routes-org.mjs +26 -0
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. 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":"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"}
@@ -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;