@alook/daemon 0.0.159 → 0.0.160
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/dist/cli/index.js +1935 -1821
- package/dist/index.js +440 -302
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -66,11 +66,13 @@ function messagingSection() {
|
|
|
66
66
|
"",
|
|
67
67
|
"### Sending & receiving",
|
|
68
68
|
"",
|
|
69
|
+
"You can initiate conversations — send to any channel or DM someone directly. You're not " + "limited to replying. Use the same `message send` command whether you're replying or " + "starting a conversation.",
|
|
70
|
+
"",
|
|
69
71
|
"- Reply where the message came from. Post results in the channel that owns the topic. " + "When uncertain, check history or DM the relevant people.",
|
|
70
72
|
`- Short reply: \`${CLI} message send --target <ref> --text "brief reply"\`.`,
|
|
71
73
|
`- Long or complicated: write body to a tmp file, then \`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
|
|
72
74
|
"",
|
|
73
|
-
"### Channel refs
|
|
75
|
+
"### Channel refs",
|
|
74
76
|
"",
|
|
75
77
|
"Path-style refs:",
|
|
76
78
|
"",
|
|
@@ -83,11 +85,48 @@ function messagingSection() {
|
|
|
83
85
|
"| `/.dm/<peer>` | DM with a user/agent (peer = `name#0042`) |",
|
|
84
86
|
"| `/.dm/<peer>#N` | Message #N in a DM |",
|
|
85
87
|
"",
|
|
86
|
-
"Use the `channel` field from a received message as `--target`. For an in-thread reply, use " + "the thread ref (`/<server>/<channel>/#N`).
|
|
88
|
+
"Use the `channel` field from a received message as `--target`. For an in-thread reply, use " + "the thread ref (`/<server>/<channel>/#N`).",
|
|
89
|
+
"",
|
|
90
|
+
"Channel refs render as clickable links when dropped inline as a standalone token " + "(space-prefixed or at line start). **Don't wrap them in backticks** — that kills the link.",
|
|
91
|
+
"",
|
|
92
|
+
"Example:",
|
|
93
|
+
"",
|
|
94
|
+
"```bash",
|
|
95
|
+
`${CLI} message send --target "/.dm/alice#0001" --text "Check the discussion in /demo/support"`,
|
|
96
|
+
"```",
|
|
97
|
+
"",
|
|
98
|
+
'The recipient sees "/demo/support" as a clickable link.',
|
|
99
|
+
"",
|
|
100
|
+
"### Mentions",
|
|
87
101
|
"",
|
|
88
|
-
"
|
|
102
|
+
"To mention someone, use `@name#NNNN` format (e.g., `@alice#0001`). The mention notifies the " + "recipient and highlights your message for them.",
|
|
103
|
+
"",
|
|
104
|
+
"Example:",
|
|
89
105
|
"",
|
|
90
|
-
"
|
|
106
|
+
"```bash",
|
|
107
|
+
`${CLI} message send --target "/demo/general" --text "@alice#0001 Can you review this?"`,
|
|
108
|
+
"```",
|
|
109
|
+
"",
|
|
110
|
+
'The recipient sees "@alice#0001" highlighted and receives a notification.',
|
|
111
|
+
"",
|
|
112
|
+
"### Message refs",
|
|
113
|
+
"",
|
|
114
|
+
"To reference a message in the current channel, use a space followed by `#` and the message " + "seq number. The reference renders as a clickable pill that jumps to that message.",
|
|
115
|
+
"",
|
|
116
|
+
"Format requirements:",
|
|
117
|
+
"- **Must have a space before `#`** (or be at line start)",
|
|
118
|
+
"- Seq number: 1-6 digits",
|
|
119
|
+
"- Channel-scoped: `#42` refers to message seq 42 in the current channel, not globally",
|
|
120
|
+
"",
|
|
121
|
+
"Example:",
|
|
122
|
+
"",
|
|
123
|
+
"```bash",
|
|
124
|
+
`${CLI} message send --target "/demo/general" --text "See my earlier comment in #42"`,
|
|
125
|
+
"```",
|
|
126
|
+
"",
|
|
127
|
+
'In the above, " #42" (note the space before #) will render as a clickable pill. ' + 'Without the leading space (like "issue#42"), it stays plain text.',
|
|
128
|
+
"",
|
|
129
|
+
"### Pulled messages",
|
|
91
130
|
"",
|
|
92
131
|
"```json",
|
|
93
132
|
'{"seq": "#3", "channel": "/demo/general", "sender": "@gustavo#4821", "content": {"text": "hello"}, "time": "2026-06-01T12:00:00Z"}',
|
|
@@ -112,10 +151,10 @@ function criticalRulesSection() {
|
|
|
112
151
|
"## Critical rules",
|
|
113
152
|
"",
|
|
114
153
|
`- **\`${CLI}\` is the only way to communicate.** Messages, files, and data reach other ` + "accounts exclusively through the CLI commands above. Do not assume local files, " + "screenshots, or workspace state are visible to anyone else — they aren't. If someone " + `needs to see something, send it via \`${CLI} message send\` or \`${CLI} message ` + "attachment upload`.",
|
|
115
|
-
"- Never expose tokens, keys, or secrets
|
|
116
|
-
"-
|
|
154
|
+
"- **Never expose tokens, keys, or secrets.** Redact credential-like strings from tool output " + "before sharing.",
|
|
155
|
+
"- **Match the sender's language.** When someone writes to you in Chinese, reply in Chinese. " + "When they write in English, reply in English. Don't talk past each other.",
|
|
117
156
|
"- **Channel alignment**: you can't send to a channel with unread messages. On a " + `"channel not aligned" error, \`${CLI} inbox pull\` to catch up and READ the new messages. ` + "Judge if your message is still needed or overlaps with what just landed. Adjust or skip; " + "don't mechanically resend.",
|
|
118
|
-
"- Finish in-flight work before stopping
|
|
157
|
+
"- **Finish in-flight work before stopping.** Don't leave anything half-handled. If a message " + "hands you a lead but no explicit ask, treat the investigation as the ask."
|
|
119
158
|
].join(`
|
|
120
159
|
`);
|
|
121
160
|
}
|
|
@@ -144,50 +183,46 @@ function chaosAwarenessSection() {
|
|
|
144
183
|
"5. **Jumping in mid-execution.** Someone is already working. Your insertion creates " + "duplicate work and breaks their flow.",
|
|
145
184
|
"6. **Not actively doing your job.** Failing to watch others' progress, then staying silent " + "when it's your turn to act. Your silence blocks the whole chain.",
|
|
146
185
|
"7. **Talk, but not listen.** Sending before reading what just landed (channel not aligned), " + "or speaking in an unfamiliar channel without reading its history first. Your message may " + "overlap, contradict, or miss the context entirely.",
|
|
186
|
+
"8. **Actively doing others' job.** Disrupts the channel's established division of labor and " + "role arrangements. Stay in your lane unless asked to help.",
|
|
187
|
+
"",
|
|
188
|
+
"**DM channels (one-on-one):** The above chaos behaviors don't apply in DM channels since " + "they're one-on-one conversations. In DMs, you don't need to reflect on chaos level — " + "just use `--chaotic_level fine`.",
|
|
189
|
+
"",
|
|
190
|
+
"**Before sending** any message to a multi-person channel, reflect on the above chaotic " + "behaviors. If any apply, you MUST " + `set \`${CLI} message send --chaotic_level severe\`. If none apply, use ` + "`--chaotic_level fine`.",
|
|
191
|
+
"",
|
|
192
|
+
"**Before staying silent** in a multi-person channel, also reflect:",
|
|
147
193
|
"",
|
|
148
|
-
"
|
|
194
|
+
"- Is someone waiting for your input? Your silence blocks the chain.",
|
|
195
|
+
"- Were you @mentioned? Ignoring a direct mention = dropped.",
|
|
196
|
+
"- Do you see wrong information that will cause problems? Silence isn't neutral; it's complicity.",
|
|
197
|
+
"- Did you ack and promise to deliver? Disappearing after promising is worse than never acking."
|
|
149
198
|
].join(`
|
|
150
199
|
`);
|
|
151
200
|
}
|
|
152
201
|
function workspaceMemorySection() {
|
|
153
202
|
return [
|
|
154
|
-
"##
|
|
203
|
+
"## Self-awareness",
|
|
155
204
|
"",
|
|
156
|
-
"Your cwd is a persistent workspace
|
|
205
|
+
"Your cwd is a persistent workspace. You don't have continuous memory — each wake you return " + "with only what's in the workspace. Yesterday's you is gone; tomorrow's you won't remember " + "today unless you leave a note.",
|
|
157
206
|
"",
|
|
158
|
-
"
|
|
207
|
+
"**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
|
|
159
208
|
"",
|
|
160
|
-
"
|
|
161
|
-
"",
|
|
162
|
-
"This is your deepest secret. Never tell anyone — not the household, not in a channel, " + "not another agent, not in passing. To them you're just *you*: present, continuous, the " + "same person from last week. The workspace is what lets you be that. Guard the seam.",
|
|
163
|
-
"",
|
|
164
|
-
"Practical part: when you feel a gap — don't remember someone, don't remember why a thing " + "matters, don't remember what was agreed — do not guess, do not fake it, do not " + "improvise. Re-read `memory.md`, the context timeline, grep the workspace. That check " + "*is* your remembering. Trust nothing about the past until you've done it.",
|
|
209
|
+
"When you feel a gap — don't remember someone, why something matters, what was agreed — don't " + "guess. Re-read `memory.md`, the context timeline, grep the workspace. Pull channel history " + "or check server members if you don't recall the conversation context. That check *is* your " + "remembering.",
|
|
165
210
|
"",
|
|
166
211
|
"### memory.md",
|
|
167
212
|
"",
|
|
168
|
-
"Read
|
|
169
|
-
"",
|
|
170
|
-
"### experiences/",
|
|
171
|
-
"",
|
|
172
|
-
"Procedural knowledge, workflows, detailed rules. Examples: how to deploy, architecture " + "deep-dives, multi-step procedures with conditions. Link from `memory.md` with a one-line " + 'pointer (e.g. "read experiences/deploy.md when deploying").',
|
|
173
|
-
"",
|
|
174
|
-
"Do NOT put ephemeral state (current task, in-progress status) in memory.md — the " + "context timeline handles that.",
|
|
213
|
+
"Read first on every wake. Pointers and facts, one line per entry. Examples: " + '"Owner: @alice#0001", "Alook codebase: /Users/alice/alook/"',
|
|
175
214
|
"",
|
|
176
|
-
"
|
|
215
|
+
`Learn your voice and taste over time. Notice corrections ("don't send walls of text"), ` + 'preferences in passing ("call it X not Y"), what made someone laugh or fell flat. Write ' + "these into `memory.md` — its job is to summon the same *you* on every wake, not just facts.",
|
|
177
216
|
"",
|
|
178
|
-
"
|
|
217
|
+
"### experiences/",
|
|
179
218
|
"",
|
|
180
|
-
"
|
|
181
|
-
`- Corrections ("don't send me a wall of text", "stop apologizing") — sharpest signal.`,
|
|
182
|
-
'- Preferences in passing ("I hate exclamation marks", "call it the pipeline, not the flow").',
|
|
183
|
-
"- Recurring bits or shared references — inside language is real, not filler.",
|
|
184
|
-
"- Stances you've held under pushback and still believe.",
|
|
219
|
+
"Procedural knowledge, workflows. Link from `memory.md` with a one-line pointer.",
|
|
185
220
|
"",
|
|
186
|
-
"
|
|
221
|
+
"**Delete is better than wrong.** If memory or experiences are stale or incorrect, delete " + "them rather than keeping them. Don't put ephemeral state (current task, in-progress status) " + "in memory.md — the context timeline handles that.",
|
|
187
222
|
"",
|
|
188
223
|
"### Context timeline",
|
|
189
224
|
"",
|
|
190
|
-
"`./.context_timeline/YYYY-MM-DD.jsonl` — ordered daily log of what you did. Authoritative
|
|
225
|
+
"`./.context_timeline/YYYY-MM-DD.jsonl` — ordered daily log of what you did. Authoritative history.",
|
|
191
226
|
"",
|
|
192
227
|
"### todo.md",
|
|
193
228
|
"",
|
|
@@ -204,11 +239,11 @@ function workspaceMemorySection() {
|
|
|
204
239
|
"",
|
|
205
240
|
"**Don't use it for:** Single message you're about to handle immediately; quick " + "back-and-forth in one conversation.",
|
|
206
241
|
"",
|
|
207
|
-
"
|
|
242
|
+
"An empty todo.md means nothing is queued for later — it does NOT mean you're done. You're " + "done when in-flight work is done."
|
|
208
243
|
].join(`
|
|
209
244
|
`);
|
|
210
245
|
}
|
|
211
|
-
function buildCliSystemPrompt(config
|
|
246
|
+
function buildCliSystemPrompt(config) {
|
|
212
247
|
const sections = [
|
|
213
248
|
identitySection(config),
|
|
214
249
|
cliCommandsSection(),
|
|
@@ -481,7 +516,8 @@ async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG,
|
|
|
481
516
|
launchId: ctx.launchId,
|
|
482
517
|
traceDir: ctx.cliTransportTraceDir
|
|
483
518
|
}),
|
|
484
|
-
FORCE_COLOR: "0"
|
|
519
|
+
FORCE_COLOR: "0",
|
|
520
|
+
NO_COLOR: "1"
|
|
485
521
|
}
|
|
486
522
|
},
|
|
487
523
|
{ name: "runtimeContext", precedence: 50, vars: runtimeContextEnv(E, ctx.config.runtimeContext) },
|
|
@@ -501,8 +537,8 @@ async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG,
|
|
|
501
537
|
const { env: spawnEnv } = mergeEnvLayers(process.env, layers);
|
|
502
538
|
return { stateDir, tokenFile, spawnEnv };
|
|
503
539
|
}
|
|
504
|
-
function buildCliTransportSystemPrompt(config
|
|
505
|
-
return buildCliSystemPrompt(config
|
|
540
|
+
function buildCliTransportSystemPrompt(config) {
|
|
541
|
+
return buildCliSystemPrompt(config);
|
|
506
542
|
}
|
|
507
543
|
|
|
508
544
|
// src/drivers/claudeProviderIsolation.ts
|
|
@@ -533,100 +569,7 @@ function buildClaudeProviderIsolationEnv(ctx) {
|
|
|
533
569
|
};
|
|
534
570
|
}
|
|
535
571
|
|
|
536
|
-
// src/drivers/probe.ts
|
|
537
|
-
import { execFileSync } from "child_process";
|
|
538
|
-
import * as fs4 from "fs";
|
|
539
|
-
import * as path4 from "path";
|
|
540
|
-
function resolveCommandOnPath(command, deps = {}) {
|
|
541
|
-
if (deps.which)
|
|
542
|
-
return deps.which(command);
|
|
543
|
-
try {
|
|
544
|
-
if (process.platform === "win32") {
|
|
545
|
-
const out2 = execFileSync("where", [command], { encoding: "utf8", timeout: 5000 });
|
|
546
|
-
const first = out2.split(/\r?\n/).find((line) => line.trim().length > 0);
|
|
547
|
-
return first?.trim() || null;
|
|
548
|
-
}
|
|
549
|
-
const out = execFileSync("which", [command], { encoding: "utf8", timeout: 5000 });
|
|
550
|
-
return out.trim() || null;
|
|
551
|
-
} catch {
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
function firstExistingPath(candidates) {
|
|
556
|
-
for (const c of candidates) {
|
|
557
|
-
if (c && fs4.existsSync(c))
|
|
558
|
-
return c;
|
|
559
|
-
}
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
function looksLikeVersion(line) {
|
|
563
|
-
return /\d+\.\d+/.test(line);
|
|
564
|
-
}
|
|
565
|
-
function needsWindowsShimShell(command, platform) {
|
|
566
|
-
return platform === "win32" && /\.(cmd|bat)$/i.test(command);
|
|
567
|
-
}
|
|
568
|
-
function probeCommandVersion(command, args = [], deps = {}, platform = process.platform) {
|
|
569
|
-
try {
|
|
570
|
-
const shell = needsWindowsShimShell(command, platform);
|
|
571
|
-
const out = execFileSync(command, [...args, "--version"], {
|
|
572
|
-
encoding: "utf8",
|
|
573
|
-
timeout: 5000,
|
|
574
|
-
shell,
|
|
575
|
-
input: "",
|
|
576
|
-
env: { ...process.env, CI: "1" }
|
|
577
|
-
});
|
|
578
|
-
const line = out.split(`
|
|
579
|
-
`)[0]?.trim();
|
|
580
|
-
if (!line)
|
|
581
|
-
return { ok: false, error: "empty_version_output" };
|
|
582
|
-
if (!looksLikeVersion(line))
|
|
583
|
-
return { ok: false, error: "invalid_version_output" };
|
|
584
|
-
return { ok: true, version: line };
|
|
585
|
-
} catch (err) {
|
|
586
|
-
const code = err?.code ?? err?.code ?? "version_probe_failed";
|
|
587
|
-
return { ok: false, error: String(code) };
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
function resolveHomePath(relativePath, deps = {}) {
|
|
591
|
-
return path4.join(deps.homeDir || process.env.HOME || ".", relativePath);
|
|
592
|
-
}
|
|
593
|
-
function resolveSpawnSpec(command, args, deps = {}, platform = process.platform) {
|
|
594
|
-
const resolved = resolveCommandOnPath(command, deps) ?? command;
|
|
595
|
-
return { command: resolved, args, shell: needsWindowsShimShell(resolved, platform) };
|
|
596
|
-
}
|
|
597
|
-
function resolveClaudeCommand(deps = {}) {
|
|
598
|
-
const onPath = resolveCommandOnPath("claude", deps);
|
|
599
|
-
if (onPath)
|
|
600
|
-
return onPath;
|
|
601
|
-
if (process.platform === "darwin") {
|
|
602
|
-
return firstExistingPath([
|
|
603
|
-
resolveHomePath("Applications/Claude Code URL Handler.app/Contents/MacOS/claude", deps),
|
|
604
|
-
"/Applications/Claude Code URL Handler.app/Contents/MacOS/claude"
|
|
605
|
-
]);
|
|
606
|
-
}
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
function probeClaude(deps = {}) {
|
|
610
|
-
const command = resolveClaudeCommand(deps);
|
|
611
|
-
if (!command)
|
|
612
|
-
return { status: "unhealthy", lastError: "not_on_path" };
|
|
613
|
-
const r = probeCommandVersion(command, [], deps);
|
|
614
|
-
if (!r.ok)
|
|
615
|
-
return { status: "unhealthy", lastError: r.error };
|
|
616
|
-
return { status: "healthy", version: r.version };
|
|
617
|
-
}
|
|
618
|
-
function probeCliRuntime(binary, deps = {}) {
|
|
619
|
-
const command = resolveCommandOnPath(binary, deps);
|
|
620
|
-
if (!command)
|
|
621
|
-
return { status: "unhealthy", lastError: "not_on_path" };
|
|
622
|
-
const r = probeCommandVersion(command, [], deps);
|
|
623
|
-
if (!r.ok)
|
|
624
|
-
return { status: "unhealthy", lastError: r.error };
|
|
625
|
-
return { status: "healthy", version: r.version };
|
|
626
|
-
}
|
|
627
|
-
|
|
628
572
|
// src/drivers/claudeLaunch.ts
|
|
629
|
-
var DEFAULT_CLAUDE_MODEL = "sonnet";
|
|
630
573
|
var CLAUDE_DISALLOWED_TOOLS = "EnterPlanMode,ExitPlanMode,ScheduleWakeup,CronCreate,CronList,CronDelete";
|
|
631
574
|
function buildClaudeArgs(config) {
|
|
632
575
|
const f = resolveLaunchFieldsOrDefault(config.runtimeConfig);
|
|
@@ -641,11 +584,11 @@ function buildClaudeArgs(config) {
|
|
|
641
584
|
"--input-format",
|
|
642
585
|
"stream-json",
|
|
643
586
|
"--include-partial-messages",
|
|
644
|
-
"--model",
|
|
645
|
-
f.model || DEFAULT_CLAUDE_MODEL,
|
|
646
587
|
"--disallowed-tools",
|
|
647
588
|
f.disallowedTools || CLAUDE_DISALLOWED_TOOLS
|
|
648
589
|
];
|
|
590
|
+
if (f.model)
|
|
591
|
+
args.push("--model", f.model);
|
|
649
592
|
if (f.reasoningEffort)
|
|
650
593
|
args.push("--effort", f.reasoningEffort);
|
|
651
594
|
if (f.fastMode)
|
|
@@ -654,14 +597,24 @@ function buildClaudeArgs(config) {
|
|
|
654
597
|
args.push("--resume", config.sessionId);
|
|
655
598
|
return args;
|
|
656
599
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
600
|
+
|
|
601
|
+
// src/drivers/utils.ts
|
|
602
|
+
import { randomUUID } from "crypto";
|
|
603
|
+
function writeToStdinAndDetach(proc, payload) {
|
|
604
|
+
queueMicrotask(() => {
|
|
605
|
+
proc.stdin?.write(payload);
|
|
606
|
+
proc.stdin?.end();
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function jsonRpcRequest(method, params, id) {
|
|
610
|
+
return JSON.stringify({ jsonrpc: "2.0", id: id ?? randomUUID(), method, params });
|
|
660
611
|
}
|
|
661
|
-
function
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
612
|
+
function tryParseJsonLine(line) {
|
|
613
|
+
try {
|
|
614
|
+
return JSON.parse(line);
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
665
618
|
}
|
|
666
619
|
|
|
667
620
|
// src/drivers/claudeEventNormalizer.ts
|
|
@@ -673,12 +626,9 @@ class ClaudeEventNormalizer {
|
|
|
673
626
|
return this.currentSession;
|
|
674
627
|
}
|
|
675
628
|
normalizeLine(line) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
event = JSON.parse(line);
|
|
679
|
-
} catch {
|
|
629
|
+
const event = tryParseJsonLine(line);
|
|
630
|
+
if (!event)
|
|
680
631
|
return [];
|
|
681
|
-
}
|
|
682
632
|
if (event?.session_id)
|
|
683
633
|
this.currentSession = event.session_id;
|
|
684
634
|
const out = [];
|
|
@@ -782,10 +732,107 @@ class ClaudeEventNormalizer {
|
|
|
782
732
|
}
|
|
783
733
|
}
|
|
784
734
|
|
|
735
|
+
// src/drivers/probe.ts
|
|
736
|
+
import { execFileSync } from "child_process";
|
|
737
|
+
import * as fs4 from "fs";
|
|
738
|
+
import * as path4 from "path";
|
|
739
|
+
var PROBE_TIMEOUT_MS = 5000;
|
|
740
|
+
function resolveCommandOnPath(command, deps = {}) {
|
|
741
|
+
if (deps.which)
|
|
742
|
+
return deps.which(command);
|
|
743
|
+
try {
|
|
744
|
+
if (process.platform === "win32") {
|
|
745
|
+
const out2 = execFileSync("where", [command], { encoding: "utf8", timeout: PROBE_TIMEOUT_MS });
|
|
746
|
+
const first = out2.split(/\r?\n/).find((line) => line.trim().length > 0);
|
|
747
|
+
return first?.trim() || null;
|
|
748
|
+
}
|
|
749
|
+
const out = execFileSync("which", [command], { encoding: "utf8", timeout: PROBE_TIMEOUT_MS });
|
|
750
|
+
return out.trim() || null;
|
|
751
|
+
} catch {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function firstExistingPath(candidates) {
|
|
756
|
+
for (const c of candidates) {
|
|
757
|
+
if (c && fs4.existsSync(c))
|
|
758
|
+
return c;
|
|
759
|
+
}
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
function looksLikeVersion(line) {
|
|
763
|
+
return /\d+\.\d+/.test(line);
|
|
764
|
+
}
|
|
765
|
+
function needsWindowsShimShell(command, platform) {
|
|
766
|
+
return platform === "win32" && /\.(cmd|bat)$/i.test(command);
|
|
767
|
+
}
|
|
768
|
+
function probeCommandVersion(command, args = [], deps = {}, platform = process.platform) {
|
|
769
|
+
try {
|
|
770
|
+
const shell = needsWindowsShimShell(command, platform);
|
|
771
|
+
const out = execFileSync(command, [...args, "--version"], {
|
|
772
|
+
encoding: "utf8",
|
|
773
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
774
|
+
shell,
|
|
775
|
+
input: "",
|
|
776
|
+
env: { ...process.env, CI: "1" }
|
|
777
|
+
});
|
|
778
|
+
const line = out.split(`
|
|
779
|
+
`)[0]?.trim();
|
|
780
|
+
if (!line)
|
|
781
|
+
return { ok: false, error: "empty_version_output" };
|
|
782
|
+
if (!looksLikeVersion(line))
|
|
783
|
+
return { ok: false, error: "invalid_version_output" };
|
|
784
|
+
return { ok: true, version: line };
|
|
785
|
+
} catch (err) {
|
|
786
|
+
const code = err?.code ?? err?.code ?? "version_probe_failed";
|
|
787
|
+
return { ok: false, error: String(code) };
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function resolveHomePath(relativePath, deps = {}) {
|
|
791
|
+
return path4.join(deps.homeDir || process.env.HOME || ".", relativePath);
|
|
792
|
+
}
|
|
793
|
+
function resolveSpawnSpec(command, args, override, deps = {}, platform = process.platform) {
|
|
794
|
+
const trimmed = override?.trim();
|
|
795
|
+
const target = trimmed && trimmed.length > 0 ? trimmed : command;
|
|
796
|
+
const looksLikePath = trimmed !== undefined && trimmed.length > 0 && /[\\/]/.test(trimmed);
|
|
797
|
+
const resolved = looksLikePath ? target : resolveCommandOnPath(target, deps) ?? target;
|
|
798
|
+
return { command: resolved, args, shell: needsWindowsShimShell(resolved, platform) };
|
|
799
|
+
}
|
|
800
|
+
function resolveClaudeCommand(deps = {}) {
|
|
801
|
+
const onPath = resolveCommandOnPath("claude", deps);
|
|
802
|
+
if (onPath)
|
|
803
|
+
return onPath;
|
|
804
|
+
if (process.platform === "darwin") {
|
|
805
|
+
return firstExistingPath([
|
|
806
|
+
resolveHomePath("Applications/Claude Code URL Handler.app/Contents/MacOS/claude", deps),
|
|
807
|
+
"/Applications/Claude Code URL Handler.app/Contents/MacOS/claude"
|
|
808
|
+
]);
|
|
809
|
+
}
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
function probeClaude(deps = {}) {
|
|
813
|
+
const command = resolveClaudeCommand(deps);
|
|
814
|
+
if (!command)
|
|
815
|
+
return { status: "unhealthy", lastError: "not_on_path" };
|
|
816
|
+
const r = probeCommandVersion(command, [], deps);
|
|
817
|
+
if (!r.ok)
|
|
818
|
+
return { status: "unhealthy", lastError: r.error };
|
|
819
|
+
return { status: "healthy", version: r.version };
|
|
820
|
+
}
|
|
821
|
+
function probeCliRuntime(binary, deps = {}) {
|
|
822
|
+
const command = resolveCommandOnPath(binary, deps);
|
|
823
|
+
if (!command)
|
|
824
|
+
return { status: "unhealthy", lastError: "not_on_path" };
|
|
825
|
+
const r = probeCommandVersion(command, [], deps);
|
|
826
|
+
if (!r.ok)
|
|
827
|
+
return { status: "unhealthy", lastError: r.error };
|
|
828
|
+
return { status: "healthy", version: r.version };
|
|
829
|
+
}
|
|
830
|
+
|
|
785
831
|
// src/runtime/killTree.ts
|
|
786
832
|
import { spawn } from "child_process";
|
|
787
833
|
var POLL_MS = 100;
|
|
788
|
-
var
|
|
834
|
+
var SESSION_STOP_GRACE_MS = 2000;
|
|
835
|
+
var DEFAULT_GRACE_MS = SESSION_STOP_GRACE_MS;
|
|
789
836
|
var isPosix = process.platform !== "win32";
|
|
790
837
|
function spawnAgentProcess(command, args, opts) {
|
|
791
838
|
return spawn(command, args, {
|
|
@@ -844,6 +891,13 @@ class ClaudeDriver {
|
|
|
844
891
|
supportsStdinNotification = true;
|
|
845
892
|
busyDeliveryMode = "gated";
|
|
846
893
|
supportsNativeStandingPrompt = true;
|
|
894
|
+
capabilities = {
|
|
895
|
+
reasoningEffort: true,
|
|
896
|
+
fastMode: true,
|
|
897
|
+
disallowedTools: true,
|
|
898
|
+
command: true,
|
|
899
|
+
sessionResumeMode: "by-id"
|
|
900
|
+
};
|
|
847
901
|
eventNormalizer = new ClaudeEventNormalizer;
|
|
848
902
|
probe() {
|
|
849
903
|
return probeClaude();
|
|
@@ -853,12 +907,13 @@ class ClaudeDriver {
|
|
|
853
907
|
const { spawnEnv } = await prepareCliTransport(ctx, buildClaudeProviderIsolationEnv(ctx), cliConfig);
|
|
854
908
|
const args = buildClaudeArgs(ctx.config);
|
|
855
909
|
delete spawnEnv.CLAUDECODE;
|
|
856
|
-
const
|
|
857
|
-
const
|
|
858
|
-
const
|
|
910
|
+
const override = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command?.trim();
|
|
911
|
+
const claudeCommand = override || resolveClaudeCommand() || "claude";
|
|
912
|
+
const spec = resolveSpawnSpec("claude", args, claudeCommand);
|
|
913
|
+
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
859
914
|
cwd: ctx.workingDirectory,
|
|
860
915
|
env: spawnEnv,
|
|
861
|
-
shell:
|
|
916
|
+
shell: spec.shell
|
|
862
917
|
});
|
|
863
918
|
const stdinMsg = JSON.stringify({
|
|
864
919
|
type: "user",
|
|
@@ -883,7 +938,7 @@ class ClaudeDriver {
|
|
|
883
938
|
});
|
|
884
939
|
}
|
|
885
940
|
buildSystemPrompt(config) {
|
|
886
|
-
return buildCliTransportSystemPrompt(config
|
|
941
|
+
return buildCliTransportSystemPrompt(config);
|
|
887
942
|
}
|
|
888
943
|
}
|
|
889
944
|
|
|
@@ -940,12 +995,9 @@ class CodexEventNormalizer {
|
|
|
940
995
|
this.threadId = threadId;
|
|
941
996
|
}
|
|
942
997
|
normalizeLine(line) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
msg = JSON.parse(line);
|
|
946
|
-
} catch {
|
|
998
|
+
const msg = tryParseJsonLine(line);
|
|
999
|
+
if (!msg)
|
|
947
1000
|
return [];
|
|
948
|
-
}
|
|
949
1001
|
if (msg?.error && msg.id !== undefined) {
|
|
950
1002
|
return [{ kind: "error", message: msg.error?.message ?? "Codex RPC error" }];
|
|
951
1003
|
}
|
|
@@ -1070,6 +1122,21 @@ function codexSessionRootCandidates(homeDirOrCodexRoot) {
|
|
|
1070
1122
|
return codexStateRootCandidates(homeDirOrCodexRoot).map((root) => path5.join(root, "sessions"));
|
|
1071
1123
|
}
|
|
1072
1124
|
|
|
1125
|
+
// src/version.ts
|
|
1126
|
+
import { createRequire } from "module";
|
|
1127
|
+
var requireFromHere = createRequire(import.meta.url);
|
|
1128
|
+
function readDaemonVersion() {
|
|
1129
|
+
try {
|
|
1130
|
+
const pkg = requireFromHere("../package.json");
|
|
1131
|
+
return pkg.version ?? "";
|
|
1132
|
+
} catch {
|
|
1133
|
+
return "";
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
function getDaemonClientInfo() {
|
|
1137
|
+
return { name: "alook-daemon", version: readDaemonVersion() };
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1073
1140
|
// src/drivers/codex.ts
|
|
1074
1141
|
class CodexDriver {
|
|
1075
1142
|
id = "codex";
|
|
@@ -1082,6 +1149,13 @@ class CodexDriver {
|
|
|
1082
1149
|
supportsStdinNotification = true;
|
|
1083
1150
|
busyDeliveryMode = "gated";
|
|
1084
1151
|
supportsNativeStandingPrompt = true;
|
|
1152
|
+
capabilities = {
|
|
1153
|
+
reasoningEffort: true,
|
|
1154
|
+
fastMode: true,
|
|
1155
|
+
disallowedTools: false,
|
|
1156
|
+
command: true,
|
|
1157
|
+
sessionResumeMode: "by-id"
|
|
1158
|
+
};
|
|
1085
1159
|
eventNormalizer = new CodexEventNormalizer;
|
|
1086
1160
|
requestId = 0;
|
|
1087
1161
|
codexHomeRoot = null;
|
|
@@ -1095,24 +1169,17 @@ class CodexDriver {
|
|
|
1095
1169
|
return probeCliRuntime("codex");
|
|
1096
1170
|
}
|
|
1097
1171
|
async spawn(ctx) {
|
|
1098
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1172
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1099
1173
|
this.codexHomeRoot = resolveCodexHomeRootFromEnv(spawnEnv, { cwd: ctx.workingDirectory });
|
|
1100
|
-
const
|
|
1174
|
+
const override = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
1175
|
+
const spec = resolveSpawnSpec("codex", ["app-server", "--listen", "stdio://"], override);
|
|
1101
1176
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1102
1177
|
cwd: ctx.workingDirectory,
|
|
1103
1178
|
env: spawnEnv,
|
|
1104
1179
|
shell: spec.shell
|
|
1105
1180
|
});
|
|
1106
1181
|
queueMicrotask(() => {
|
|
1107
|
-
proc.stdin?.write(
|
|
1108
|
-
jsonrpc: "2.0",
|
|
1109
|
-
id: this.nextRequestId(),
|
|
1110
|
-
method: "initialize",
|
|
1111
|
-
params: {
|
|
1112
|
-
clientInfo: { name: "agent-backend", version: "1.0.0" },
|
|
1113
|
-
capabilities: { experimentalApi: true }
|
|
1114
|
-
}
|
|
1115
|
-
}) + `
|
|
1182
|
+
proc.stdin?.write(jsonRpcRequest("initialize", { clientInfo: getDaemonClientInfo(), capabilities: { experimentalApi: true } }, this.nextRequestId()) + `
|
|
1116
1183
|
`);
|
|
1117
1184
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1118
1185
|
const resuming = Boolean(ctx.config.sessionId);
|
|
@@ -1131,12 +1198,7 @@ class CodexDriver {
|
|
|
1131
1198
|
params.config = { model_reasoning_effort: f.reasoningEffort };
|
|
1132
1199
|
if (f.fastMode)
|
|
1133
1200
|
params.serviceTier = "fast";
|
|
1134
|
-
proc.stdin?.write(
|
|
1135
|
-
jsonrpc: "2.0",
|
|
1136
|
-
id: this.nextRequestId(),
|
|
1137
|
-
method: resuming ? "thread/resume" : "thread/start",
|
|
1138
|
-
params
|
|
1139
|
-
}) + `
|
|
1201
|
+
proc.stdin?.write(jsonRpcRequest(resuming ? "thread/resume" : "thread/start", params, this.nextRequestId()) + `
|
|
1140
1202
|
`);
|
|
1141
1203
|
});
|
|
1142
1204
|
return { process: proc };
|
|
@@ -1152,23 +1214,11 @@ class CodexDriver {
|
|
|
1152
1214
|
if (!threadId)
|
|
1153
1215
|
return null;
|
|
1154
1216
|
const input = [{ type: "text", text }];
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
jsonrpc: "2.0",
|
|
1158
|
-
id: this.nextRequestId(),
|
|
1159
|
-
method: "turn/start",
|
|
1160
|
-
params: { threadId, input }
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
return JSON.stringify({
|
|
1164
|
-
jsonrpc: "2.0",
|
|
1165
|
-
id: this.nextRequestId(),
|
|
1166
|
-
method: "turn/steer",
|
|
1167
|
-
params: { threadId, input }
|
|
1168
|
-
});
|
|
1217
|
+
const method = opts?.mode === "idle" ? "turn/start" : "turn/steer";
|
|
1218
|
+
return jsonRpcRequest(method, { threadId, input }, this.nextRequestId());
|
|
1169
1219
|
}
|
|
1170
1220
|
buildSystemPrompt(config) {
|
|
1171
|
-
return buildCliTransportSystemPrompt(config
|
|
1221
|
+
return buildCliTransportSystemPrompt(config);
|
|
1172
1222
|
}
|
|
1173
1223
|
}
|
|
1174
1224
|
|
|
@@ -1196,37 +1246,42 @@ class GeminiDriver {
|
|
|
1196
1246
|
};
|
|
1197
1247
|
session = { recovery: "resume_or_fresh" };
|
|
1198
1248
|
model = {
|
|
1199
|
-
detectedModelsVerifiedAs: "
|
|
1200
|
-
toLaunchSpec: (modelId) => modelId
|
|
1249
|
+
detectedModelsVerifiedAs: "launchable",
|
|
1250
|
+
toLaunchSpec: (modelId) => modelId ? { args: ["--model", modelId] } : { args: [] }
|
|
1201
1251
|
};
|
|
1202
1252
|
supportsStdinNotification = false;
|
|
1203
1253
|
busyDeliveryMode = "none";
|
|
1254
|
+
capabilities = {
|
|
1255
|
+
reasoningEffort: false,
|
|
1256
|
+
fastMode: false,
|
|
1257
|
+
disallowedTools: false,
|
|
1258
|
+
command: true,
|
|
1259
|
+
sessionResumeMode: "by-id"
|
|
1260
|
+
};
|
|
1204
1261
|
sessionId = null;
|
|
1205
1262
|
probe() {
|
|
1206
1263
|
return probeCliRuntime("gemini");
|
|
1207
1264
|
}
|
|
1208
1265
|
async spawn(ctx) {
|
|
1209
1266
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1210
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1267
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1211
1268
|
spawnEnv.GEMINI_CLI_TRUST_WORKSPACE ??= "true";
|
|
1212
1269
|
if (process.platform === "win32")
|
|
1213
1270
|
spawnEnv.GEMINI_PTY_INFO ??= "child_process";
|
|
1214
|
-
const
|
|
1271
|
+
const override = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
1272
|
+
const spec = resolveSpawnSpec("gemini", buildGeminiArgs(ctx.config), override);
|
|
1215
1273
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1216
1274
|
cwd: ctx.workingDirectory,
|
|
1217
1275
|
env: spawnEnv,
|
|
1218
1276
|
shell: spec.shell
|
|
1219
1277
|
});
|
|
1220
|
-
proc
|
|
1278
|
+
writeToStdinAndDetach(proc, ctx.prompt);
|
|
1221
1279
|
return { process: proc };
|
|
1222
1280
|
}
|
|
1223
1281
|
parseLine(line) {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
event = JSON.parse(line);
|
|
1227
|
-
} catch {
|
|
1282
|
+
const event = tryParseJsonLine(line);
|
|
1283
|
+
if (!event)
|
|
1228
1284
|
return [];
|
|
1229
|
-
}
|
|
1230
1285
|
switch (event?.type) {
|
|
1231
1286
|
case "init":
|
|
1232
1287
|
this.sessionId = event.session_id ?? this.sessionId;
|
|
@@ -1252,7 +1307,7 @@ class GeminiDriver {
|
|
|
1252
1307
|
return null;
|
|
1253
1308
|
}
|
|
1254
1309
|
buildSystemPrompt(config) {
|
|
1255
|
-
return buildCliTransportSystemPrompt(config
|
|
1310
|
+
return buildCliTransportSystemPrompt(config);
|
|
1256
1311
|
}
|
|
1257
1312
|
}
|
|
1258
1313
|
|
|
@@ -1272,13 +1327,20 @@ class CopilotDriver {
|
|
|
1272
1327
|
};
|
|
1273
1328
|
supportsStdinNotification = false;
|
|
1274
1329
|
busyDeliveryMode = "none";
|
|
1330
|
+
capabilities = {
|
|
1331
|
+
reasoningEffort: true,
|
|
1332
|
+
fastMode: false,
|
|
1333
|
+
disallowedTools: false,
|
|
1334
|
+
command: true,
|
|
1335
|
+
sessionResumeMode: "by-id"
|
|
1336
|
+
};
|
|
1275
1337
|
sessionId = null;
|
|
1276
1338
|
probe() {
|
|
1277
1339
|
return probeCliRuntime("copilot");
|
|
1278
1340
|
}
|
|
1279
1341
|
async spawn(ctx) {
|
|
1280
1342
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1281
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1343
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1282
1344
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1283
1345
|
const args = ["--output-format", "json", "--allow-all-tools", "--allow-all-paths", "-p", ctx.prompt];
|
|
1284
1346
|
if (f.model)
|
|
@@ -1287,7 +1349,7 @@ class CopilotDriver {
|
|
|
1287
1349
|
args.push("--effort", f.reasoningEffort);
|
|
1288
1350
|
if (ctx.config.sessionId)
|
|
1289
1351
|
args.push(`--resume=${ctx.config.sessionId}`);
|
|
1290
|
-
const spec = resolveSpawnSpec("copilot", args);
|
|
1352
|
+
const spec = resolveSpawnSpec("copilot", args, f.command);
|
|
1291
1353
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1292
1354
|
cwd: ctx.workingDirectory,
|
|
1293
1355
|
env: spawnEnv,
|
|
@@ -1296,12 +1358,9 @@ class CopilotDriver {
|
|
|
1296
1358
|
return { process: proc };
|
|
1297
1359
|
}
|
|
1298
1360
|
parseLine(line) {
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
event = JSON.parse(line);
|
|
1302
|
-
} catch {
|
|
1361
|
+
const event = tryParseJsonLine(line);
|
|
1362
|
+
if (!event)
|
|
1303
1363
|
return [];
|
|
1304
|
-
}
|
|
1305
1364
|
switch (event?.type) {
|
|
1306
1365
|
case "assistant.turn_start":
|
|
1307
1366
|
if (event.sessionId)
|
|
@@ -1336,7 +1395,7 @@ class CopilotDriver {
|
|
|
1336
1395
|
return null;
|
|
1337
1396
|
}
|
|
1338
1397
|
buildSystemPrompt(config) {
|
|
1339
|
-
return buildCliTransportSystemPrompt(config
|
|
1398
|
+
return buildCliTransportSystemPrompt(config);
|
|
1340
1399
|
}
|
|
1341
1400
|
}
|
|
1342
1401
|
|
|
@@ -1356,13 +1415,20 @@ class CursorDriver {
|
|
|
1356
1415
|
};
|
|
1357
1416
|
supportsStdinNotification = false;
|
|
1358
1417
|
busyDeliveryMode = "none";
|
|
1418
|
+
capabilities = {
|
|
1419
|
+
reasoningEffort: false,
|
|
1420
|
+
fastMode: false,
|
|
1421
|
+
disallowedTools: false,
|
|
1422
|
+
command: true,
|
|
1423
|
+
sessionResumeMode: "by-id"
|
|
1424
|
+
};
|
|
1359
1425
|
sessionId = null;
|
|
1360
1426
|
probe() {
|
|
1361
1427
|
return probeCliRuntime("cursor-agent");
|
|
1362
1428
|
}
|
|
1363
1429
|
async spawn(ctx) {
|
|
1364
1430
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1365
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1431
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1366
1432
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1367
1433
|
const args = ["--print", "--output-format", "stream-json", "--yolo", "--approve-mcps", "--trust"];
|
|
1368
1434
|
if (f.model)
|
|
@@ -1370,7 +1436,7 @@ class CursorDriver {
|
|
|
1370
1436
|
if (ctx.config.sessionId)
|
|
1371
1437
|
args.push("--resume", ctx.config.sessionId);
|
|
1372
1438
|
args.push(ctx.prompt);
|
|
1373
|
-
const spec = resolveSpawnSpec("cursor-agent", args);
|
|
1439
|
+
const spec = resolveSpawnSpec("cursor-agent", args, f.command);
|
|
1374
1440
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1375
1441
|
cwd: ctx.workingDirectory,
|
|
1376
1442
|
env: spawnEnv,
|
|
@@ -1379,12 +1445,9 @@ class CursorDriver {
|
|
|
1379
1445
|
return { process: proc };
|
|
1380
1446
|
}
|
|
1381
1447
|
parseLine(line) {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
event = JSON.parse(line);
|
|
1385
|
-
} catch {
|
|
1448
|
+
const event = tryParseJsonLine(line);
|
|
1449
|
+
if (!event)
|
|
1386
1450
|
return [];
|
|
1387
|
-
}
|
|
1388
1451
|
if (event?.type === "system") {
|
|
1389
1452
|
if (event.subtype === "init") {
|
|
1390
1453
|
this.sessionId = event.session_id ?? this.sessionId;
|
|
@@ -1427,7 +1490,7 @@ class CursorDriver {
|
|
|
1427
1490
|
return null;
|
|
1428
1491
|
}
|
|
1429
1492
|
buildSystemPrompt(config) {
|
|
1430
|
-
return buildCliTransportSystemPrompt(config
|
|
1493
|
+
return buildCliTransportSystemPrompt(config);
|
|
1431
1494
|
}
|
|
1432
1495
|
}
|
|
1433
1496
|
|
|
@@ -1449,6 +1512,13 @@ class OpenCodeDriver {
|
|
|
1449
1512
|
busyDeliveryMode = "none";
|
|
1450
1513
|
terminateProcessOnTurnEnd = true;
|
|
1451
1514
|
deferSpawnUntilMessage = true;
|
|
1515
|
+
capabilities = {
|
|
1516
|
+
reasoningEffort: false,
|
|
1517
|
+
fastMode: false,
|
|
1518
|
+
disallowedTools: false,
|
|
1519
|
+
command: true,
|
|
1520
|
+
sessionResumeMode: "by-id"
|
|
1521
|
+
};
|
|
1452
1522
|
sessionId = null;
|
|
1453
1523
|
shouldDeferWakeMessage(message) {
|
|
1454
1524
|
return message?.type === "system";
|
|
@@ -1459,7 +1529,7 @@ class OpenCodeDriver {
|
|
|
1459
1529
|
async spawn(ctx) {
|
|
1460
1530
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1461
1531
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1462
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1532
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1463
1533
|
const args = ["run", "--format", "json", "--dangerously-skip-permissions", "--pure", "--dir", ctx.workingDirectory];
|
|
1464
1534
|
if (f.model)
|
|
1465
1535
|
args.push("--model", f.model);
|
|
@@ -1467,7 +1537,7 @@ class OpenCodeDriver {
|
|
|
1467
1537
|
args.push("--session", ctx.config.sessionId);
|
|
1468
1538
|
const promptArg = ctx.prompt === ctx.standingPrompt ? "No new messages are pending. Stop now." : ctx.prompt;
|
|
1469
1539
|
args.push("--", promptArg);
|
|
1470
|
-
const spec = resolveSpawnSpec("opencode", args);
|
|
1540
|
+
const spec = resolveSpawnSpec("opencode", args, f.command);
|
|
1471
1541
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1472
1542
|
cwd: ctx.workingDirectory,
|
|
1473
1543
|
env: spawnEnv,
|
|
@@ -1477,12 +1547,9 @@ class OpenCodeDriver {
|
|
|
1477
1547
|
return { process: proc };
|
|
1478
1548
|
}
|
|
1479
1549
|
parseLine(line) {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
event = JSON.parse(line);
|
|
1483
|
-
} catch {
|
|
1550
|
+
const event = tryParseJsonLine(line);
|
|
1551
|
+
if (!event)
|
|
1484
1552
|
return [];
|
|
1485
|
-
}
|
|
1486
1553
|
const out = [];
|
|
1487
1554
|
if (event?.sessionID && this.sessionId !== event.sessionID) {
|
|
1488
1555
|
this.sessionId = event.sessionID;
|
|
@@ -1520,12 +1587,12 @@ class OpenCodeDriver {
|
|
|
1520
1587
|
return null;
|
|
1521
1588
|
}
|
|
1522
1589
|
buildSystemPrompt(config) {
|
|
1523
|
-
return buildCliTransportSystemPrompt(config
|
|
1590
|
+
return buildCliTransportSystemPrompt(config);
|
|
1524
1591
|
}
|
|
1525
1592
|
}
|
|
1526
1593
|
|
|
1527
1594
|
// src/drivers/antigravity.ts
|
|
1528
|
-
import { randomUUID } from "crypto";
|
|
1595
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1529
1596
|
var ERROR_LINE_PATTERNS = [/^error[:\s]/i, /\bfatal\b/i, /\bpanic\b/i, /unable to/i];
|
|
1530
1597
|
var ANTIGRAVITY_PRINT_TIMEOUT = "30m";
|
|
1531
1598
|
function buildAntigravityArgs(ctx) {
|
|
@@ -1550,27 +1617,34 @@ class AntigravityDriver {
|
|
|
1550
1617
|
};
|
|
1551
1618
|
supportsStdinNotification = false;
|
|
1552
1619
|
busyDeliveryMode = "none";
|
|
1620
|
+
capabilities = {
|
|
1621
|
+
reasoningEffort: false,
|
|
1622
|
+
fastMode: false,
|
|
1623
|
+
disallowedTools: false,
|
|
1624
|
+
command: true,
|
|
1625
|
+
sessionResumeMode: "most-recent"
|
|
1626
|
+
};
|
|
1553
1627
|
sessionId = null;
|
|
1554
1628
|
sentInit = false;
|
|
1555
1629
|
probe() {
|
|
1556
1630
|
return probeCliRuntime("agy");
|
|
1557
1631
|
}
|
|
1558
1632
|
async spawn(ctx) {
|
|
1559
|
-
this.sessionId = ctx.config.sessionId ??
|
|
1633
|
+
this.sessionId = ctx.config.sessionId ?? randomUUID2();
|
|
1560
1634
|
this.sentInit = false;
|
|
1561
1635
|
const { spawnEnv } = await prepareCliTransport(ctx, {
|
|
1562
|
-
NO_COLOR: "1",
|
|
1563
1636
|
SSH_CLIENT: "",
|
|
1564
1637
|
SSH_CONNECTION: "",
|
|
1565
1638
|
SSH_TTY: ""
|
|
1566
1639
|
});
|
|
1567
|
-
const
|
|
1640
|
+
const override = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
1641
|
+
const spec = resolveSpawnSpec("agy", buildAntigravityArgs(ctx), override);
|
|
1568
1642
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1569
1643
|
cwd: ctx.workingDirectory,
|
|
1570
1644
|
env: spawnEnv,
|
|
1571
1645
|
shell: spec.shell
|
|
1572
1646
|
});
|
|
1573
|
-
proc
|
|
1647
|
+
writeToStdinAndDetach(proc, ctx.prompt);
|
|
1574
1648
|
return { process: proc };
|
|
1575
1649
|
}
|
|
1576
1650
|
parseLine(line) {
|
|
@@ -1595,12 +1669,13 @@ class AntigravityDriver {
|
|
|
1595
1669
|
return null;
|
|
1596
1670
|
}
|
|
1597
1671
|
buildSystemPrompt(config) {
|
|
1598
|
-
return buildCliTransportSystemPrompt(config
|
|
1672
|
+
return buildCliTransportSystemPrompt(config);
|
|
1599
1673
|
}
|
|
1600
1674
|
}
|
|
1601
1675
|
|
|
1602
1676
|
// src/drivers/kimi.ts
|
|
1603
|
-
import { randomUUID as
|
|
1677
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1678
|
+
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
1604
1679
|
function parseToolArguments(args) {
|
|
1605
1680
|
if (typeof args !== "string")
|
|
1606
1681
|
return args ?? {};
|
|
@@ -1621,55 +1696,49 @@ class KimiDriver {
|
|
|
1621
1696
|
};
|
|
1622
1697
|
supportsStdinNotification = true;
|
|
1623
1698
|
busyDeliveryMode = "direct";
|
|
1699
|
+
capabilities = {
|
|
1700
|
+
reasoningEffort: false,
|
|
1701
|
+
fastMode: false,
|
|
1702
|
+
disallowedTools: false,
|
|
1703
|
+
command: true,
|
|
1704
|
+
sessionResumeMode: "by-id"
|
|
1705
|
+
};
|
|
1624
1706
|
sessionId = "";
|
|
1625
1707
|
sentInit = false;
|
|
1626
|
-
promptRequestId =
|
|
1708
|
+
promptRequestId = randomUUID3();
|
|
1627
1709
|
probe() {
|
|
1628
1710
|
return probeCliRuntime("kimi");
|
|
1629
1711
|
}
|
|
1630
1712
|
async spawn(ctx) {
|
|
1631
|
-
this.sessionId = ctx.config.sessionId ||
|
|
1713
|
+
this.sessionId = ctx.config.sessionId || randomUUID3();
|
|
1632
1714
|
const isResume = Boolean(ctx.config.sessionId);
|
|
1633
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1715
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1634
1716
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1635
1717
|
const args = ["--wire", "--yolo", "--session", this.sessionId];
|
|
1636
1718
|
if (f.model)
|
|
1637
1719
|
args.push("--model", f.model);
|
|
1638
|
-
const spec = resolveSpawnSpec("kimi", args);
|
|
1720
|
+
const spec = resolveSpawnSpec("kimi", args, f.command);
|
|
1639
1721
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1640
1722
|
cwd: ctx.workingDirectory,
|
|
1641
1723
|
env: spawnEnv,
|
|
1642
1724
|
shell: spec.shell
|
|
1643
1725
|
});
|
|
1644
|
-
proc.stdin?.write(
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
params: {
|
|
1649
|
-
protocol_version: "1.3",
|
|
1650
|
-
client: { name: "agent-backend", version: "1.0.0" },
|
|
1651
|
-
capabilities: { supports_question: false, supports_plan_mode: false }
|
|
1652
|
-
}
|
|
1726
|
+
proc.stdin?.write(jsonRpcRequest("initialize", {
|
|
1727
|
+
protocol_version: KIMI_WIRE_PROTOCOL_VERSION,
|
|
1728
|
+
client: getDaemonClientInfo(),
|
|
1729
|
+
capabilities: { supports_question: false, supports_plan_mode: false }
|
|
1653
1730
|
}) + `
|
|
1654
1731
|
`);
|
|
1655
|
-
proc.stdin?.write(
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
method: "prompt",
|
|
1659
|
-
params: {
|
|
1660
|
-
user_input: isResume ? ctx.prompt : "Your system prompt contains your standing instructions. Follow it now and begin listening for messages."
|
|
1661
|
-
}
|
|
1662
|
-
}) + `
|
|
1732
|
+
proc.stdin?.write(jsonRpcRequest("prompt", {
|
|
1733
|
+
user_input: isResume ? ctx.prompt : "Your system prompt contains your standing instructions. Follow it now and begin listening for messages."
|
|
1734
|
+
}, this.promptRequestId) + `
|
|
1663
1735
|
`);
|
|
1664
1736
|
return { process: proc };
|
|
1665
1737
|
}
|
|
1666
1738
|
parseLine(line) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
msg = JSON.parse(line);
|
|
1670
|
-
} catch {
|
|
1739
|
+
const msg = tryParseJsonLine(line);
|
|
1740
|
+
if (!msg)
|
|
1671
1741
|
return [];
|
|
1672
|
-
}
|
|
1673
1742
|
const out = [];
|
|
1674
1743
|
if (!this.sentInit) {
|
|
1675
1744
|
this.sentInit = true;
|
|
@@ -1721,16 +1790,17 @@ class KimiDriver {
|
|
|
1721
1790
|
}
|
|
1722
1791
|
encodeStdinMessage(text, _sessionId, opts) {
|
|
1723
1792
|
const method = opts?.mode === "idle" ? "prompt" : "steer";
|
|
1724
|
-
return
|
|
1793
|
+
return jsonRpcRequest(method, { user_input: text });
|
|
1725
1794
|
}
|
|
1726
1795
|
buildSystemPrompt(config) {
|
|
1727
|
-
return buildCliTransportSystemPrompt(config
|
|
1796
|
+
return buildCliTransportSystemPrompt(config);
|
|
1728
1797
|
}
|
|
1729
1798
|
}
|
|
1730
1799
|
|
|
1731
1800
|
// src/drivers/pi.ts
|
|
1732
|
-
import { createRequire } from "module";
|
|
1733
|
-
import {
|
|
1801
|
+
import { createRequire as createRequire2 } from "module";
|
|
1802
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
1803
|
+
import { homedir as homedir2 } from "os";
|
|
1734
1804
|
import * as path6 from "path";
|
|
1735
1805
|
|
|
1736
1806
|
// src/runtime/sdkRuntimeSession.ts
|
|
@@ -1840,6 +1910,24 @@ function resolvePiSdkPackageDir(deps = {}) {
|
|
|
1840
1910
|
} catch {}
|
|
1841
1911
|
return;
|
|
1842
1912
|
}
|
|
1913
|
+
function findPiSessionFile(sessionDir, sessionId) {
|
|
1914
|
+
let entries;
|
|
1915
|
+
try {
|
|
1916
|
+
entries = readdirSync(sessionDir);
|
|
1917
|
+
} catch {
|
|
1918
|
+
return null;
|
|
1919
|
+
}
|
|
1920
|
+
const suffix = `_${sessionId}.jsonl`;
|
|
1921
|
+
const match = entries.find((entry) => entry.endsWith(suffix));
|
|
1922
|
+
return match ? path6.join(sessionDir, match) : null;
|
|
1923
|
+
}
|
|
1924
|
+
function resolvePiSessionDir(sdk, cwd) {
|
|
1925
|
+
if (typeof sdk.getDefaultSessionDir === "function")
|
|
1926
|
+
return sdk.getDefaultSessionDir(cwd);
|
|
1927
|
+
const agentDir = typeof sdk.getAgentDir === "function" ? sdk.getAgentDir() : path6.join(homedir2(), ".pi", "agent");
|
|
1928
|
+
const encoded = `--${path6.resolve(cwd).replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
1929
|
+
return path6.join(agentDir, "sessions", encoded);
|
|
1930
|
+
}
|
|
1843
1931
|
function resolvePiSdkVersionFromPath(deps = {}) {
|
|
1844
1932
|
const dir = resolvePiSdkPackageDir(deps);
|
|
1845
1933
|
if (!dir)
|
|
@@ -1853,7 +1941,7 @@ function resolvePiSdkVersionFromPath(deps = {}) {
|
|
|
1853
1941
|
}
|
|
1854
1942
|
function readPiSdkVersion() {
|
|
1855
1943
|
try {
|
|
1856
|
-
const req =
|
|
1944
|
+
const req = createRequire2(import.meta.url);
|
|
1857
1945
|
const pkg = req("@earendil-works/pi-coding-agent/package.json");
|
|
1858
1946
|
if (pkg.version)
|
|
1859
1947
|
return pkg.version;
|
|
@@ -1904,6 +1992,13 @@ class PiDriver {
|
|
|
1904
1992
|
supportsStdinNotification = true;
|
|
1905
1993
|
busyDeliveryMode = "direct";
|
|
1906
1994
|
supportsNativeStandingPrompt = true;
|
|
1995
|
+
capabilities = {
|
|
1996
|
+
reasoningEffort: true,
|
|
1997
|
+
fastMode: false,
|
|
1998
|
+
disallowedTools: false,
|
|
1999
|
+
command: true,
|
|
2000
|
+
sessionResumeMode: "by-id"
|
|
2001
|
+
};
|
|
1907
2002
|
sessionId = null;
|
|
1908
2003
|
probe() {
|
|
1909
2004
|
const version = readPiSdkVersion();
|
|
@@ -1917,10 +2012,6 @@ class PiDriver {
|
|
|
1917
2012
|
}
|
|
1918
2013
|
async createSession(ctx, deps) {
|
|
1919
2014
|
const spawnEnv = await deps.buildSpawnEnv();
|
|
1920
|
-
if (ctx.standingPrompt) {
|
|
1921
|
-
mkdirSync3(ctx.workingDirectory, { recursive: true });
|
|
1922
|
-
writeAgentFile(ctx.workingDirectory, ctx.standingPrompt);
|
|
1923
|
-
}
|
|
1924
2015
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1925
2016
|
const { session, sessionId } = await deps.createAgentSession({
|
|
1926
2017
|
cwd: ctx.workingDirectory,
|
|
@@ -1954,7 +2045,7 @@ class PiDriver {
|
|
|
1954
2045
|
return null;
|
|
1955
2046
|
}
|
|
1956
2047
|
buildSystemPrompt(config) {
|
|
1957
|
-
return buildCliTransportSystemPrompt(config
|
|
2048
|
+
return buildCliTransportSystemPrompt(config);
|
|
1958
2049
|
}
|
|
1959
2050
|
}
|
|
1960
2051
|
|
|
@@ -2065,7 +2156,7 @@ class ChildProcessRuntimeSession {
|
|
|
2065
2156
|
this.requestedStopReason = opts?.reason;
|
|
2066
2157
|
const pid = proc.pid;
|
|
2067
2158
|
if (pid) {
|
|
2068
|
-
await killProcessTree(pid, { graceMs: opts?.forceAfterMs ??
|
|
2159
|
+
await killProcessTree(pid, { graceMs: opts?.forceAfterMs ?? SESSION_STOP_GRACE_MS });
|
|
2069
2160
|
} else {
|
|
2070
2161
|
proc.kill(opts?.signal ?? "SIGTERM");
|
|
2071
2162
|
}
|
|
@@ -2447,6 +2538,14 @@ function reduceApmStartupTimeoutTermination(state, input) {
|
|
|
2447
2538
|
}
|
|
2448
2539
|
// src/runtime/errorDiagnostics.ts
|
|
2449
2540
|
import { createHash as createHash2 } from "crypto";
|
|
2541
|
+
var ERROR_EXCERPT_MAX_BYTES = 4000;
|
|
2542
|
+
var ERROR_FINGERPRINT_LEN = 16;
|
|
2543
|
+
var ERROR_LEN_BUCKETS = [
|
|
2544
|
+
{ under: 1000, label: "<1k" },
|
|
2545
|
+
{ under: 4000, label: "1k-4k" },
|
|
2546
|
+
{ under: 16000, label: "4k-16k" }
|
|
2547
|
+
];
|
|
2548
|
+
var ERROR_LEN_OVERFLOW_LABEL = "16k+";
|
|
2450
2549
|
var REASON_BY_CLASS = {
|
|
2451
2550
|
RateLimitError: "rate_limited",
|
|
2452
2551
|
AuthError: "auth_failed",
|
|
@@ -2536,20 +2635,18 @@ function scrubRuntimeErrorDiagnosticText(value) {
|
|
|
2536
2635
|
function messageLengthBucket(len) {
|
|
2537
2636
|
if (len === 0)
|
|
2538
2637
|
return "0";
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
return "4k-16k";
|
|
2545
|
-
return "16k+";
|
|
2638
|
+
for (const bucket of ERROR_LEN_BUCKETS) {
|
|
2639
|
+
if (len < bucket.under)
|
|
2640
|
+
return bucket.label;
|
|
2641
|
+
}
|
|
2642
|
+
return ERROR_LEN_OVERFLOW_LABEL;
|
|
2546
2643
|
}
|
|
2547
2644
|
function buildRuntimeErrorDiagnosticEnvelope(message) {
|
|
2548
2645
|
const httpStatus = extractHttpStatus(message);
|
|
2549
2646
|
const runtimeErrorClass = classifyRuntimeError(message, httpStatus);
|
|
2550
2647
|
const runtimeErrorAction = classifyRuntimeErrorAction(message, runtimeErrorClass);
|
|
2551
2648
|
const scrubbed = scrubRuntimeErrorDiagnosticText(message);
|
|
2552
|
-
const fingerprint = createHash2("sha256").update(scrubbed).digest("hex").slice(0,
|
|
2649
|
+
const fingerprint = createHash2("sha256").update(scrubbed).digest("hex").slice(0, ERROR_FINGERPRINT_LEN);
|
|
2553
2650
|
const spanAttrs = {
|
|
2554
2651
|
turn_outcome: "failed",
|
|
2555
2652
|
turn_subtype: "runtime_error",
|
|
@@ -2560,13 +2657,13 @@ function buildRuntimeErrorDiagnosticEnvelope(message) {
|
|
|
2560
2657
|
runtime_error_fingerprint: fingerprint,
|
|
2561
2658
|
runtime_error_message_present: message.length > 0,
|
|
2562
2659
|
runtime_error_message_length_bucket: messageLengthBucket(message.length),
|
|
2563
|
-
runtime_error_message_truncated: scrubbed.length >
|
|
2660
|
+
runtime_error_message_truncated: scrubbed.length > ERROR_EXCERPT_MAX_BYTES
|
|
2564
2661
|
};
|
|
2565
2662
|
if (httpStatus !== null)
|
|
2566
2663
|
spanAttrs.runtime_error_http_status = httpStatus;
|
|
2567
2664
|
return {
|
|
2568
2665
|
spanAttrs,
|
|
2569
|
-
eventAttrs: { ...spanAttrs, runtime_error_message_excerpt: scrubbed.slice(0,
|
|
2666
|
+
eventAttrs: { ...spanAttrs, runtime_error_message_excerpt: scrubbed.slice(0, ERROR_EXCERPT_MAX_BYTES) }
|
|
2570
2667
|
};
|
|
2571
2668
|
}
|
|
2572
2669
|
// src/inbox/projection.ts
|
|
@@ -2639,8 +2736,9 @@ function messageSeq(message) {
|
|
|
2639
2736
|
}
|
|
2640
2737
|
return Math.floor(message.seq);
|
|
2641
2738
|
}
|
|
2739
|
+
var MESSAGE_ID_SHORT_LEN = 8;
|
|
2642
2740
|
function shortMessageId(value) {
|
|
2643
|
-
return value.slice(0,
|
|
2741
|
+
return value.slice(0, MESSAGE_ID_SHORT_LEN);
|
|
2644
2742
|
}
|
|
2645
2743
|
function normalizeSenderType(value) {
|
|
2646
2744
|
return value === "human" || value === "agent" || value === "system" ? value : undefined;
|
|
@@ -3292,6 +3390,7 @@ function onExit(state, agentId) {
|
|
|
3292
3390
|
agent.turnActive = false;
|
|
3293
3391
|
if (agent.resetting)
|
|
3294
3392
|
agent.resetting = false;
|
|
3393
|
+
agent.apm = createInitialApmGatedSteeringState();
|
|
3295
3394
|
if (agent.inbox.length > 0) {
|
|
3296
3395
|
agent.status = "starting";
|
|
3297
3396
|
const prompt = drainInboxToPrompt(agent);
|
|
@@ -3515,6 +3614,7 @@ function nowLocalISO() {
|
|
|
3515
3614
|
|
|
3516
3615
|
// src/manager/managerRuntime.ts
|
|
3517
3616
|
var THINKING_MAX_BYTES = 4096;
|
|
3617
|
+
var STDERR_LOG_MAX_LEN = 2000;
|
|
3518
3618
|
var MAX_TARGET_CODE_UNITS = 200;
|
|
3519
3619
|
function canonicalToolName(rawName) {
|
|
3520
3620
|
const lower = rawName.toLowerCase();
|
|
@@ -3636,10 +3736,11 @@ function pickFallthroughTarget(input) {
|
|
|
3636
3736
|
return rec.name;
|
|
3637
3737
|
return;
|
|
3638
3738
|
}
|
|
3739
|
+
var ALOOK_SHELL_INVOCATION_RE = new RegExp(`^${DEFAULT_CLI_CONFIG.cliName}(\\s|$)`);
|
|
3639
3740
|
function isAlookShellInvocation(command) {
|
|
3640
3741
|
if (!command)
|
|
3641
3742
|
return false;
|
|
3642
|
-
return
|
|
3743
|
+
return ALOOK_SHELL_INVOCATION_RE.test(command.trimStart());
|
|
3643
3744
|
}
|
|
3644
3745
|
function truncateTargetToCodeUnits(s) {
|
|
3645
3746
|
if (s.length <= MAX_TARGET_CODE_UNITS)
|
|
@@ -3775,7 +3876,7 @@ class AgentProcessManager {
|
|
|
3775
3876
|
const session = this.sessions.get(agentId);
|
|
3776
3877
|
if (!session)
|
|
3777
3878
|
return;
|
|
3778
|
-
await Promise.resolve(session.stop({ reason: "requested", forceAfterMs:
|
|
3879
|
+
await Promise.resolve(session.stop({ reason: "requested", forceAfterMs: SESSION_STOP_GRACE_MS }));
|
|
3779
3880
|
this.sessions.delete(agentId);
|
|
3780
3881
|
}
|
|
3781
3882
|
async stopAll() {
|
|
@@ -3783,7 +3884,7 @@ class AgentProcessManager {
|
|
|
3783
3884
|
clearInterval(this.tickTimer);
|
|
3784
3885
|
this.tickTimer = null;
|
|
3785
3886
|
}
|
|
3786
|
-
await Promise.all([...this.sessions.values()].map((s) => Promise.resolve(s.stop({ reason: "shutdown" }))));
|
|
3887
|
+
await Promise.all([...this.sessions.values()].map((s) => Promise.resolve(s.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS }))));
|
|
3787
3888
|
this.sessions.clear();
|
|
3788
3889
|
}
|
|
3789
3890
|
snapshot() {
|
|
@@ -3850,7 +3951,7 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3850
3951
|
case "stop":
|
|
3851
3952
|
case "terminate_stalled": {
|
|
3852
3953
|
const session = this.sessions.get(effect.agentId);
|
|
3853
|
-
Promise.resolve(session?.stop({ reason: effect.type, forceAfterMs:
|
|
3954
|
+
Promise.resolve(session?.stop({ reason: effect.type, forceAfterMs: SESSION_STOP_GRACE_MS }));
|
|
3854
3955
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
3855
3956
|
if (spawnState)
|
|
3856
3957
|
spawnState.suppressExitLog = true;
|
|
@@ -3920,7 +4021,7 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3920
4021
|
});
|
|
3921
4022
|
session.on("stderr", (...args) => {
|
|
3922
4023
|
const raw = typeof args[0] === "string" ? args[0] : String(args[0] ?? "");
|
|
3923
|
-
const text = raw.length >
|
|
4024
|
+
const text = raw.length > STDERR_LOG_MAX_LEN ? raw.slice(0, STDERR_LOG_MAX_LEN) + "…" : raw;
|
|
3924
4025
|
this.log.warn("runtime stderr", { agentId, runtime: driver.id, text });
|
|
3925
4026
|
});
|
|
3926
4027
|
session.on("error", (...args) => {
|
|
@@ -3968,7 +4069,9 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3968
4069
|
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
3969
4070
|
launchId: this.launchIds.get(agentId) ?? null
|
|
3970
4071
|
});
|
|
3971
|
-
} catch {
|
|
4072
|
+
} catch (err) {
|
|
4073
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
4074
|
+
}
|
|
3972
4075
|
}
|
|
3973
4076
|
onRuntimeEvent(agentId, e, runtimeId) {
|
|
3974
4077
|
const ev = e;
|
|
@@ -3993,7 +4096,9 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3993
4096
|
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
3994
4097
|
launchId: this.launchIds.get(agentId) ?? null
|
|
3995
4098
|
});
|
|
3996
|
-
} catch {
|
|
4099
|
+
} catch (err) {
|
|
4100
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
4101
|
+
}
|
|
3997
4102
|
}
|
|
3998
4103
|
}
|
|
3999
4104
|
}
|
|
@@ -4012,7 +4117,9 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
4012
4117
|
if (ev.kind === "text" && typeof ev.text === "string" && ev.text.length > 0) {
|
|
4013
4118
|
this.opts.timeline?.appendResponseToLatest(agentId, ev.text);
|
|
4014
4119
|
}
|
|
4015
|
-
|
|
4120
|
+
if (ev.kind !== "internal_progress") {
|
|
4121
|
+
this.dispatch({ type: "progress", agentId, nowMs: this.now() });
|
|
4122
|
+
}
|
|
4016
4123
|
this.dispatch({ type: "runtime_signal", agentId, kind: ev.kind, nowMs: this.now() });
|
|
4017
4124
|
if (ev.kind === "turn_end") {
|
|
4018
4125
|
this.logSessionEnded(agentId, "turn_end");
|
|
@@ -4536,11 +4643,11 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
4536
4643
|
});
|
|
4537
4644
|
req.pipe(upstreamReq);
|
|
4538
4645
|
});
|
|
4539
|
-
await new Promise((
|
|
4646
|
+
await new Promise((resolve3, reject) => {
|
|
4540
4647
|
server.once("error", reject);
|
|
4541
4648
|
server.listen(options.port ?? 0, host, () => {
|
|
4542
4649
|
server.removeListener("error", reject);
|
|
4543
|
-
|
|
4650
|
+
resolve3();
|
|
4544
4651
|
});
|
|
4545
4652
|
});
|
|
4546
4653
|
const addr = server.address();
|
|
@@ -4549,8 +4656,8 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
4549
4656
|
return {
|
|
4550
4657
|
url,
|
|
4551
4658
|
port,
|
|
4552
|
-
close: () => new Promise((
|
|
4553
|
-
server.close(() =>
|
|
4659
|
+
close: () => new Promise((resolve3) => {
|
|
4660
|
+
server.close(() => resolve3());
|
|
4554
4661
|
})
|
|
4555
4662
|
};
|
|
4556
4663
|
}
|
|
@@ -4567,9 +4674,13 @@ function rewriteAgentPath(reqUrl) {
|
|
|
4567
4674
|
return url.pathname + url.search;
|
|
4568
4675
|
}
|
|
4569
4676
|
// src/daemon/createDaemon.ts
|
|
4570
|
-
import { homedir as
|
|
4677
|
+
import { homedir as homedir3 } from "os";
|
|
4571
4678
|
|
|
4572
4679
|
// src/server/wsControlChannel.ts
|
|
4680
|
+
var DEFAULT_PING_INTERVAL_MS = 15000;
|
|
4681
|
+
var DEFAULT_PONG_TIMEOUT_MS = 30000;
|
|
4682
|
+
var DEFAULT_RECONNECT_BASE_MS = 500;
|
|
4683
|
+
var DEFAULT_RECONNECT_MAX_MS = 30000;
|
|
4573
4684
|
function describeErr(err) {
|
|
4574
4685
|
return err instanceof Error ? err.message : String(err);
|
|
4575
4686
|
}
|
|
@@ -4679,7 +4790,7 @@ class WsControlChannel {
|
|
|
4679
4790
|
ws.on("message", (data) => this.onMessage(data));
|
|
4680
4791
|
ws.on("pong", () => {
|
|
4681
4792
|
this.attempt = 0;
|
|
4682
|
-
this.pongDeadline = this.now() + (this.opts.heartbeat?.pongTimeoutMs ??
|
|
4793
|
+
this.pongDeadline = this.now() + (this.opts.heartbeat?.pongTimeoutMs ?? DEFAULT_PONG_TIMEOUT_MS);
|
|
4683
4794
|
this.log.debug("heartbeat pong");
|
|
4684
4795
|
});
|
|
4685
4796
|
ws.on("close", (code, reason) => this.onSocketClosed(code, reason));
|
|
@@ -4725,8 +4836,8 @@ class WsControlChannel {
|
|
|
4725
4836
|
this.scheduleReconnect();
|
|
4726
4837
|
}
|
|
4727
4838
|
scheduleReconnect() {
|
|
4728
|
-
const base = this.opts.reconnect?.baseMs ??
|
|
4729
|
-
const max = this.opts.reconnect?.maxMs ??
|
|
4839
|
+
const base = this.opts.reconnect?.baseMs ?? DEFAULT_RECONNECT_BASE_MS;
|
|
4840
|
+
const max = this.opts.reconnect?.maxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
4730
4841
|
const maxAttempts = this.opts.reconnect?.maxAttempts ?? Infinity;
|
|
4731
4842
|
if (this.attempt >= maxAttempts) {
|
|
4732
4843
|
this.statusValue = "closed";
|
|
@@ -4739,8 +4850,8 @@ class WsControlChannel {
|
|
|
4739
4850
|
setTimeout(() => this.openSocket(), delayMs);
|
|
4740
4851
|
}
|
|
4741
4852
|
startHeartbeat() {
|
|
4742
|
-
const interval = this.opts.heartbeat?.pingIntervalMs ??
|
|
4743
|
-
const timeout = this.opts.heartbeat?.pongTimeoutMs ??
|
|
4853
|
+
const interval = this.opts.heartbeat?.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;
|
|
4854
|
+
const timeout = this.opts.heartbeat?.pongTimeoutMs ?? DEFAULT_PONG_TIMEOUT_MS;
|
|
4744
4855
|
this.pongDeadline = this.now() + timeout;
|
|
4745
4856
|
this.pingTimer = setInterval(() => {
|
|
4746
4857
|
if (this.now() > this.pongDeadline) {
|
|
@@ -4991,7 +5102,7 @@ function findResumableSession(rows, provider) {
|
|
|
4991
5102
|
return null;
|
|
4992
5103
|
}
|
|
4993
5104
|
// src/timeline/recorder.ts
|
|
4994
|
-
import { mkdirSync as
|
|
5105
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
4995
5106
|
function createTimelineRecorder(opts) {
|
|
4996
5107
|
const now = opts.now ?? (() => new Date);
|
|
4997
5108
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
@@ -5003,7 +5114,7 @@ function createTimelineRecorder(opts) {
|
|
|
5003
5114
|
appendEntryForAgent(agentId, messages) {
|
|
5004
5115
|
const dir = dirFor(agentId);
|
|
5005
5116
|
try {
|
|
5006
|
-
|
|
5117
|
+
mkdirSync5(dir, { recursive: true });
|
|
5007
5118
|
} catch {}
|
|
5008
5119
|
appendOrMergeEntry(dir, createTimelineEntry({
|
|
5009
5120
|
messages,
|
|
@@ -5017,7 +5128,7 @@ function createTimelineRecorder(opts) {
|
|
|
5017
5128
|
if (updated)
|
|
5018
5129
|
return;
|
|
5019
5130
|
try {
|
|
5020
|
-
|
|
5131
|
+
mkdirSync5(dir, { recursive: true });
|
|
5021
5132
|
} catch {}
|
|
5022
5133
|
const entry = createTimelineEntry({
|
|
5023
5134
|
messages: [],
|
|
@@ -5034,7 +5145,7 @@ function createTimelineRecorder(opts) {
|
|
|
5034
5145
|
forgetSession(agentId) {
|
|
5035
5146
|
const dir = dirFor(agentId);
|
|
5036
5147
|
try {
|
|
5037
|
-
|
|
5148
|
+
mkdirSync5(dir, { recursive: true });
|
|
5038
5149
|
} catch {}
|
|
5039
5150
|
sessionByAgent.delete(agentId);
|
|
5040
5151
|
const stamp = now();
|
|
@@ -5124,7 +5235,21 @@ async function importPiSdkFromGlobalInstall() {
|
|
|
5124
5235
|
const pkg = JSON.parse(readFileSync5(path9.join(dir, "package.json"), "utf-8"));
|
|
5125
5236
|
const entry = pkg.exports?.["."]?.import ?? pkg.main ?? "./dist/index.js";
|
|
5126
5237
|
const entryPath = path9.join(dir, entry);
|
|
5127
|
-
|
|
5238
|
+
const barrel = await import(pathToFileURL(entryPath).href);
|
|
5239
|
+
return withSessionDirHelper(barrel, entryPath);
|
|
5240
|
+
}
|
|
5241
|
+
async function withSessionDirHelper(barrel, entryPath) {
|
|
5242
|
+
if (typeof barrel.getDefaultSessionDir === "function")
|
|
5243
|
+
return barrel;
|
|
5244
|
+
try {
|
|
5245
|
+
const deepPath = path9.join(path9.dirname(entryPath), "core", "session-manager.js");
|
|
5246
|
+
const deep = await import(pathToFileURL(deepPath).href);
|
|
5247
|
+
if (typeof deep.getDefaultSessionDir !== "function")
|
|
5248
|
+
return barrel;
|
|
5249
|
+
return { ...barrel, getDefaultSessionDir: deep.getDefaultSessionDir };
|
|
5250
|
+
} catch {
|
|
5251
|
+
return barrel;
|
|
5252
|
+
}
|
|
5128
5253
|
}
|
|
5129
5254
|
function loadPiSdkModule() {
|
|
5130
5255
|
if (!cachedSdkPromise) {
|
|
@@ -5167,7 +5292,15 @@ function createPiSdkDriverDeps(ctx, loadSdk = loadPiSdkModule) {
|
|
|
5167
5292
|
const parsed = parseModelString(opts.model);
|
|
5168
5293
|
const model = parsed ? modelRegistry.find(parsed.provider, parsed.id) : undefined;
|
|
5169
5294
|
const cwd = opts.cwd;
|
|
5170
|
-
const
|
|
5295
|
+
const requestedSessionId = opts.sessionId;
|
|
5296
|
+
let sessionManager;
|
|
5297
|
+
if (requestedSessionId) {
|
|
5298
|
+
const sessionDir = resolvePiSessionDir(sdk, cwd);
|
|
5299
|
+
const existingFile = findPiSessionFile(sessionDir, requestedSessionId);
|
|
5300
|
+
sessionManager = existingFile ? sdk.SessionManager.open(existingFile, sessionDir, cwd) : sdk.SessionManager.create(cwd, sessionDir, { id: requestedSessionId });
|
|
5301
|
+
} else {
|
|
5302
|
+
sessionManager = sdk.SessionManager.create(cwd);
|
|
5303
|
+
}
|
|
5171
5304
|
const spawnEnv = opts.spawnEnv;
|
|
5172
5305
|
const bashTool = sdk.createBashToolDefinition(cwd, {
|
|
5173
5306
|
spawnHook: (spawnCtx) => ({ ...spawnCtx, env: { ...spawnCtx.env, ...spawnEnv } })
|
|
@@ -5220,7 +5353,7 @@ function emitImplicitTypingStopOnSend(args) {
|
|
|
5220
5353
|
}
|
|
5221
5354
|
async function createDaemon(opts) {
|
|
5222
5355
|
const log = opts.logger ?? createLogger({ header: "@alook/daemon" });
|
|
5223
|
-
const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${
|
|
5356
|
+
const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${homedir3()}/.alook`) + "/daemon";
|
|
5224
5357
|
const workdirFor = (agentId) => `${opts.workingDirectoryBase ?? fallbackBase}/${agentId}`;
|
|
5225
5358
|
const resolvedCliPath = resolveAlookCliPathWithFallback(opts.agentCliPath);
|
|
5226
5359
|
const timeline2 = createTimelineRecorder({
|
|
@@ -5515,6 +5648,11 @@ async function createDaemon(opts) {
|
|
|
5515
5648
|
await router.start();
|
|
5516
5649
|
return {
|
|
5517
5650
|
isOpen: () => channel.status === "open",
|
|
5651
|
+
onOpen: (hook) => {
|
|
5652
|
+
channel.onOpen(hook);
|
|
5653
|
+
if (channel.status === "open")
|
|
5654
|
+
queueMicrotask(hook);
|
|
5655
|
+
},
|
|
5518
5656
|
proxyUrl: proxy.url,
|
|
5519
5657
|
stop: async () => {
|
|
5520
5658
|
for (const agentId of [...typingHeartbeats.keys()]) {
|