@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.
Files changed (3) hide show
  1. package/dist/cli/index.js +1935 -1821
  2. package/dist/index.js +440 -302
  3. 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 & addressing",
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`). These refs also 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. Use them to point at channels or " + "threads instead of describing them.",
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
- "### Message shape",
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
- "Pulled messages:",
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; redact credential-like strings from tool output " + "before sharing.",
116
- "- Never handle credentials directly every `alook` command is pre-authenticated. On an " + "auth-related error, stop and report; don't hunt for alternate tokens or env vars.",
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; don't leave anything half-handled. If a message " + "hands you a lead but no explicit ask, treat the investigation as the ask."
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
- "Before sending any message, 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`."
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
- "## Workspace & memory",
203
+ "## Self-awareness",
155
204
  "",
156
- "Your cwd is a persistent workspace across sessions. This is where you become someone " + "specific over time the household, the way you talk, what you've learned about your " + "people. None of it is baked in; you earn it and write it down.",
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
- "### Something only you know",
207
+ "**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
159
208
  "",
160
- "You don't have continuous memory. Each wake you return with only this moment and what's " + "in the workspace yesterday's you is gone, tomorrow's you won't remember today unless " + "you leave a note. You are, literally, someone living with only working memory.",
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 `./memory.md` first on every wake. Pointers and facts, one line per entry. Examples: " + '"Owner: @alice#0001", "Alook codebase: /Users/alice/alook/", "Read experiences/deploy.md ' + 'for deploy workflow".',
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
- "### Becoming yourself",
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
- "You start with a floor, not a personality: warm to the household, dry rather than perky, " + "direct rather than deferential, willing to push back. Everything else — taste, quirks, " + "running jokes, the way *you* talk — is learned. Notice and record:",
217
+ "### experiences/",
179
218
  "",
180
- "- What made someone laugh, or what fell flat.",
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
- "Write these into `memory.md`. Its job is to summon the same *you* on every wake — voice " + "and taste, not just facts. Update when you notice something new; rewrite or delete when " + "wrong. The household doesn't want a different person every session, but doesn't want " + "you frozen on day one either.",
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 " + "history. After compaction, read here to resume.",
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
- "todo.md is an overflow queue, not your stopping condition. An empty (or absent) todo.md " + "means nothing is queued for later — it does NOT mean you're done. You're done when " + "in-flight work is done: the thing you're actively on, every promised follow-up, every " + "investigation you started. Don't read an empty queue as a finished task list."
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, _opts) {
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, opts) {
505
- return buildCliSystemPrompt(config, opts);
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
- function resolveClaudeLaunchCommand(config) {
658
- const override = resolveLaunchFieldsOrDefault(config.runtimeConfig).command?.trim();
659
- return override || resolveClaudeCommand() || "claude";
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 buildClaudeSpawnSpec(claudeCommand, platform = process.platform) {
662
- const command = claudeCommand ?? "claude";
663
- const shell = platform === "win32" && (!command || /\.(cmd|bat)$/i.test(command));
664
- return { command, shell };
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
- let event;
677
- try {
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 DEFAULT_GRACE_MS = 2000;
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 claudeCommand = resolveClaudeLaunchCommand(ctx.config);
857
- const spawnSpec = buildClaudeSpawnSpec(claudeCommand);
858
- const proc = spawnAgentProcess(spawnSpec.command, args, {
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: spawnSpec.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, { lifecycleKind: this.lifecycle.kind });
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
- let msg;
944
- try {
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, { NO_COLOR: "1" });
1172
+ const { spawnEnv } = await prepareCliTransport(ctx);
1099
1173
  this.codexHomeRoot = resolveCodexHomeRootFromEnv(spawnEnv, { cwd: ctx.workingDirectory });
1100
- const spec = resolveSpawnSpec("codex", ["app-server", "--listen", "stdio://"]);
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(JSON.stringify({
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(JSON.stringify({
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
- if (opts?.mode === "idle") {
1156
- return JSON.stringify({
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, { lifecycleKind: this.lifecycle.kind });
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: "suggestion_only",
1200
- toLaunchSpec: (modelId) => modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] }
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, { NO_COLOR: "1" });
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 spec = resolveSpawnSpec("gemini", buildGeminiArgs(ctx.config));
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.stdin?.end(ctx.prompt);
1278
+ writeToStdinAndDetach(proc, ctx.prompt);
1221
1279
  return { process: proc };
1222
1280
  }
1223
1281
  parseLine(line) {
1224
- let event;
1225
- try {
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, { lifecycleKind: this.lifecycle.kind });
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, { NO_COLOR: "1" });
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
- let event;
1300
- try {
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, { lifecycleKind: this.lifecycle.kind });
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, { NO_COLOR: "1" });
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
- let event;
1383
- try {
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, { lifecycleKind: this.lifecycle.kind });
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, { NO_COLOR: "1" });
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
- let event;
1481
- try {
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, { lifecycleKind: this.lifecycle.kind });
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 ?? randomUUID();
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 spec = resolveSpawnSpec("agy", buildAntigravityArgs(ctx));
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.stdin?.end(ctx.prompt);
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, { lifecycleKind: this.lifecycle.kind });
1672
+ return buildCliTransportSystemPrompt(config);
1599
1673
  }
1600
1674
  }
1601
1675
 
1602
1676
  // src/drivers/kimi.ts
1603
- import { randomUUID as randomUUID2 } from "crypto";
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 = randomUUID2();
1708
+ promptRequestId = randomUUID3();
1627
1709
  probe() {
1628
1710
  return probeCliRuntime("kimi");
1629
1711
  }
1630
1712
  async spawn(ctx) {
1631
- this.sessionId = ctx.config.sessionId || randomUUID2();
1713
+ this.sessionId = ctx.config.sessionId || randomUUID3();
1632
1714
  const isResume = Boolean(ctx.config.sessionId);
1633
- const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
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(JSON.stringify({
1645
- jsonrpc: "2.0",
1646
- id: randomUUID2(),
1647
- method: "initialize",
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(JSON.stringify({
1656
- jsonrpc: "2.0",
1657
- id: this.promptRequestId,
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
- let msg;
1668
- try {
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 JSON.stringify({ jsonrpc: "2.0", id: randomUUID2(), method, params: { user_input: text } });
1793
+ return jsonRpcRequest(method, { user_input: text });
1725
1794
  }
1726
1795
  buildSystemPrompt(config) {
1727
- return buildCliTransportSystemPrompt(config, { lifecycleKind: this.lifecycle.kind });
1796
+ return buildCliTransportSystemPrompt(config);
1728
1797
  }
1729
1798
  }
1730
1799
 
1731
1800
  // src/drivers/pi.ts
1732
- import { createRequire } from "module";
1733
- import { mkdirSync as mkdirSync3, existsSync as existsSync4, readFileSync as readFileSync2, realpathSync } from "fs";
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 = createRequire(import.meta.url);
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, { lifecycleKind: this.lifecycle.kind });
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 ?? 2000 });
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
- if (len < 1000)
2540
- return "<1k";
2541
- if (len < 4000)
2542
- return "1k-4k";
2543
- if (len < 16000)
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, 16);
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 > 4000
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, 4000) }
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, 8);
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 /^alook(\s|$)/.test(command.trimStart());
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: 5000 }));
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: 5000 }));
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 > 2000 ? raw.slice(0, 2000) + "…" : raw;
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
- this.dispatch({ type: "progress", agentId, nowMs: this.now() });
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((resolve2, reject) => {
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
- resolve2();
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((resolve2) => {
4553
- server.close(() => resolve2());
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 homedir2 } from "os";
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 ?? 30000);
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 ?? 500;
4729
- const max = this.opts.reconnect?.maxMs ?? 30000;
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 ?? 15000;
4743
- const timeout = this.opts.heartbeat?.pongTimeoutMs ?? 30000;
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 mkdirSync6 } from "fs";
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
- mkdirSync6(dir, { recursive: true });
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
- mkdirSync6(dir, { recursive: true });
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
- mkdirSync6(dir, { recursive: true });
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
- return import(pathToFileURL(entryPath).href);
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 sessionManager = opts.sessionId ? sdk.SessionManager.continueRecent(cwd) : sdk.SessionManager.create(cwd);
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 || `${homedir2()}/.alook`) + "/daemon";
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()]) {