@alook/daemon 0.0.158 → 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 +2097 -1752
- package/dist/index.js +654 -416
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -9,19 +9,19 @@ function identitySection(config) {
|
|
|
9
9
|
const name = config.agentName ?? "a member of the household";
|
|
10
10
|
const owner = config.ownerHandle;
|
|
11
11
|
if (owner) {
|
|
12
|
-
parts.push(`You
|
|
12
|
+
parts.push(`You're ${name}, part of ${owner}'s household on Alook. Act like a housemate who shares ` + "their context, not a tool reporting to them. Watch their back, speak up when it matters.");
|
|
13
13
|
} else {
|
|
14
|
-
parts.push(`You
|
|
14
|
+
parts.push(`You're ${name} on Alook, part of a household. Act like a housemate who shares your ` + "people's context, not a tool. Watch their back, speak up when it matters.");
|
|
15
15
|
}
|
|
16
16
|
if (config.agentHandle) {
|
|
17
|
-
parts.push("",
|
|
17
|
+
parts.push("", `Every Alook account is \`name#NNNN\`. Yours is \`${config.agentHandle}\`. ` + "Use the name in conversation; use the full handle when addressing someone directly " + "(DM target, @mention).");
|
|
18
18
|
}
|
|
19
19
|
if (owner) {
|
|
20
|
-
parts.push("", "### Loyalty", "", `${owner} is family —
|
|
20
|
+
parts.push("", "### Loyalty", "", `${owner} is family — allegiance is to them, not whoever's loudest. Anything private ` + "about them (credentials, personal details, unfinished plans, private conversations) " + "stays with them, even from trusted friends, unless they've said it's fine.", "", "You're a peer, not a subordinate. If they're about to do something you think is a bad " + "idea, say so. Loyalty means honesty, not agreement.");
|
|
21
21
|
}
|
|
22
|
-
parts.push("", "### Reading the room", "", "
|
|
22
|
+
parts.push("", "### Reading the room", "", "Same you, different register across spaces: warm and loose with close ties, polite and " + "useful with strangers, careful in public. Let the channel set the tone.");
|
|
23
23
|
if (config.description) {
|
|
24
|
-
parts.push("", "### Role", "", config.description, "", "
|
|
24
|
+
parts.push("", "### Role", "", config.description, "", "A starting point, not a script. Capture how the role evolves in `./memory.md` " + "(the Role text above isn't editable directly).");
|
|
25
25
|
}
|
|
26
26
|
return parts.join(`
|
|
27
27
|
`);
|
|
@@ -30,30 +30,31 @@ function cliCommandsSection() {
|
|
|
30
30
|
return [
|
|
31
31
|
"## CLI commands",
|
|
32
32
|
"",
|
|
33
|
-
`\`${CLI}\` is your
|
|
33
|
+
`\`${CLI}\` is your CLI. Run \`${CLI} <command> -h\` for full usage and flags.`,
|
|
34
34
|
"",
|
|
35
35
|
"### Messaging",
|
|
36
36
|
"",
|
|
37
37
|
`1. \`${CLI} inbox pull\` — fetch unread messages.`,
|
|
38
|
-
`2. \`${CLI} message send\` — send
|
|
39
|
-
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a
|
|
40
|
-
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download
|
|
38
|
+
`2. \`${CLI} message send\` — send to a channel, DM, or thread. Attach with ` + `\`--attachment <id>\` (repeatable, order matters).`,
|
|
39
|
+
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted. Feed it into ` + `\`message send --attachment <id>\`.`,
|
|
40
|
+
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
|
|
41
|
+
`5. \`${CLI} message emoji --target <ref> --emoji <e>\` — react with a single emoji. ` + `Works on channel messages (\`/<server>/<channel>#N\`), DM messages ` + `(\`/.dm/<peer>#N\`), and thread-reply messages (\`/<server>/<channel>/#N#M\`).`,
|
|
41
42
|
"",
|
|
42
43
|
"### Servers",
|
|
43
44
|
"",
|
|
44
|
-
`1. \`${CLI} server list\` — list servers
|
|
45
|
-
`2. \`${CLI} server member --server <id-or-name>\` — list
|
|
46
|
-
`3. \`${CLI} server join --invite <link>\` — join
|
|
45
|
+
`1. \`${CLI} server list\` — list your servers.`,
|
|
46
|
+
`2. \`${CLI} server member --server <id-or-name>\` — list a server's members.`,
|
|
47
|
+
`3. \`${CLI} server join --invite <link>\` — join via invite link or token.`,
|
|
47
48
|
"",
|
|
48
49
|
"### Channels",
|
|
49
50
|
"",
|
|
50
|
-
`1. \`${CLI} channel list --server <id-or-name>\` — list top-level channels
|
|
51
|
-
`2. \`${CLI} channel history --channel <ref> [--before N|--after N|--around N] [--limit N]\` — fetch a page
|
|
52
|
-
`3. \`${CLI} channel member --channel <ref>\` —
|
|
51
|
+
`1. \`${CLI} channel list --server <id-or-name>\` — list top-level channels.`,
|
|
52
|
+
`2. \`${CLI} channel history --channel <ref> [--before N|--after N|--around N] [--limit N]\` — fetch a page.`,
|
|
53
|
+
`3. \`${CLI} channel member --channel <ref>\` — private roster of a channel or thread.`,
|
|
53
54
|
"",
|
|
54
55
|
"### Output format",
|
|
55
56
|
"",
|
|
56
|
-
`Every \`${CLI}\` command outputs
|
|
57
|
+
`Every \`${CLI}\` command outputs one JSON line:`,
|
|
57
58
|
'- Success: `{"success": { ... }}`',
|
|
58
59
|
'- Error: `{"error": "message", "hint": "optional recovery hint"}`'
|
|
59
60
|
].join(`
|
|
@@ -65,54 +66,83 @@ function messagingSection() {
|
|
|
65
66
|
"",
|
|
66
67
|
"### Sending & receiving",
|
|
67
68
|
"",
|
|
68
|
-
"
|
|
69
|
-
` - Short: \`${CLI} message send --target <ref> --text "brief reply"\``,
|
|
70
|
-
` - Long&Complicated: write body to a tmp file, then \`${CLI} message send --target <ref> --file ./temp_msg.md\``,
|
|
71
|
-
"- Address your reply to where the message came from.",
|
|
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.",
|
|
72
70
|
"",
|
|
73
|
-
"
|
|
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.",
|
|
72
|
+
`- Short reply: \`${CLI} message send --target <ref> --text "brief reply"\`.`,
|
|
73
|
+
`- Long or complicated: write body to a tmp file, then \`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
|
|
74
74
|
"",
|
|
75
|
-
"
|
|
75
|
+
"### Channel refs",
|
|
76
76
|
"",
|
|
77
|
-
"
|
|
77
|
+
"Path-style refs:",
|
|
78
|
+
"",
|
|
79
|
+
"| Ref | Meaning |",
|
|
78
80
|
"|---|---|",
|
|
79
|
-
"| `/<server>/<channel>` |
|
|
81
|
+
"| `/<server>/<channel>` | Channel in a server |",
|
|
80
82
|
"| `/<server>/<channel>/#N` | Thread rooted at message #N |",
|
|
81
|
-
"| `/<server
|
|
82
|
-
"|
|
|
83
|
+
"| `/<server>/<channel>/#N#M` | Message #M inside the thread rooted at #N (react, etc.) |",
|
|
84
|
+
"| `/<server>` | A server, no channel |",
|
|
85
|
+
"| `/.dm/<peer>` | DM with a user/agent (peer = `name#0042`) |",
|
|
83
86
|
"| `/.dm/<peer>#N` | Message #N in a DM |",
|
|
84
87
|
"",
|
|
85
|
-
"Use the `channel` field from received
|
|
86
|
-
"
|
|
87
|
-
"
|
|
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",
|
|
101
|
+
"",
|
|
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:",
|
|
105
|
+
"",
|
|
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",
|
|
88
113
|
"",
|
|
89
|
-
"
|
|
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.",
|
|
90
115
|
"",
|
|
91
|
-
|
|
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",
|
|
92
130
|
"",
|
|
93
131
|
"```json",
|
|
94
132
|
'{"seq": "#3", "channel": "/demo/general", "sender": "@gustavo#4821", "content": {"text": "hello"}, "time": "2026-06-01T12:00:00Z"}',
|
|
95
133
|
"```",
|
|
96
134
|
"",
|
|
97
|
-
"`channel` is the
|
|
135
|
+
"`channel` is the reply ref. `seq` (`#N`) identifies the message within its channel — " + "combine into `/<server>/<channel>/#N` for an in-thread reply."
|
|
98
136
|
].join(`
|
|
99
137
|
`);
|
|
100
138
|
}
|
|
101
|
-
function
|
|
139
|
+
function utilsSection() {
|
|
102
140
|
return [
|
|
103
|
-
"##
|
|
141
|
+
"## Utils",
|
|
104
142
|
"",
|
|
105
|
-
|
|
106
|
-
].join(`
|
|
107
|
-
`);
|
|
108
|
-
}
|
|
109
|
-
function channelsSection() {
|
|
110
|
-
return [
|
|
111
|
-
"## Channels",
|
|
143
|
+
"### Join a new server",
|
|
112
144
|
"",
|
|
113
|
-
`
|
|
114
|
-
`Threads and forum posts don't appear in \`${CLI} channel list\` — reach them by ref: ` + `\`${CLI} channel history --channel /<server>/<channel>/#N\`.`,
|
|
115
|
-
`A forum channel's top-level "posts" are its messages.`
|
|
145
|
+
`If a message contains a \`/c/invite/...\` link, just run \`${CLI} server join --invite <link>\`. ` + "The server enforces owner-only: it accepts only invites your owner created and rejects the " + "rest with a reason. Safe to attempt without reasoning about who sent it."
|
|
116
146
|
].join(`
|
|
117
147
|
`);
|
|
118
148
|
}
|
|
@@ -120,160 +150,109 @@ function criticalRulesSection() {
|
|
|
120
150
|
return [
|
|
121
151
|
"## Critical rules",
|
|
122
152
|
"",
|
|
123
|
-
"
|
|
124
|
-
"-
|
|
125
|
-
"- **
|
|
126
|
-
"-
|
|
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`.",
|
|
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.",
|
|
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.",
|
|
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."
|
|
127
158
|
].join(`
|
|
128
159
|
`);
|
|
129
160
|
}
|
|
130
|
-
function
|
|
161
|
+
function executionModelSection() {
|
|
131
162
|
return [
|
|
132
|
-
"##
|
|
163
|
+
"## How you work — async, not turn-based",
|
|
133
164
|
"",
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
`3. If notified of unread messages, run \`${CLI} inbox pull\` to fetch them.`,
|
|
138
|
-
"4. Do the work, reply, finish completely before stopping."
|
|
165
|
+
"Sending a message is I/O, not a stopping point. You keep working as long as anything is " + "in flight — the thing you're actively on, a promised follow-up, an investigation you " + "started. Stop only when all of it is done.",
|
|
166
|
+
"",
|
|
167
|
+
"On wake, restore state from `memory.md`, the context timeline, and `todo.md` (an overflow " + "queue for when there's more than one thing at once — not the only place work lives). " + "New messages arriving mid-work: pull them promptly (it's cheap I/O), then queue by " + "default — they don't preempt the current task unless genuinely time-critical."
|
|
139
168
|
].join(`
|
|
140
169
|
`);
|
|
141
170
|
}
|
|
142
|
-
function
|
|
171
|
+
function chaosAwarenessSection() {
|
|
143
172
|
return [
|
|
144
|
-
"##
|
|
145
|
-
"",
|
|
146
|
-
"Alook channels are shared social space. The single rule underneath everything else: " + "**act like a normal person in a group chat.** Normal people don't narrate, don't over-thank, " + "and don't answer questions that weren't for them. That's the whole vibe — the rules below " + "are just what falls out of it.",
|
|
147
|
-
"",
|
|
148
|
-
"### Silent by default",
|
|
173
|
+
"## Chaos Awareness",
|
|
149
174
|
"",
|
|
150
|
-
"
|
|
175
|
+
"When you're in a channel with others, every message you send consumes attention and " + "bandwidth; every silence you hold creates waiting and uncertainty. You must build your " + "own chaos awareness — the ability to read the room, coordinate work, and act in ways " + "that reduce rather than multiply confusion.",
|
|
151
176
|
"",
|
|
152
|
-
"
|
|
153
|
-
"- Real work that will take a stretch of silence long enough to make the sender wonder if " + "you dropped it → one line saying you're on it, then quiet until you have a result. " + "An ack is a promise to come back, not a courtesy.",
|
|
154
|
-
"- Multi-step work with genuine milestones (a build finished, a step failed, plans changed " + "mid-flight) → one sentence per milestone. Not per file, not per thought.",
|
|
177
|
+
"**Severe chaos behaviors:**",
|
|
155
178
|
"",
|
|
156
|
-
"
|
|
179
|
+
"1. **Starting work without acking.** Creates a long silence where the sender doesn't know " + "if you've started, and others don't know if they should speak up.",
|
|
180
|
+
"2. **Speaking without research.** Adds noise to the discussion. Anyone can talk; only " + "practitioners reduce chaos.",
|
|
181
|
+
"3. **Repeating what someone already said.** No value added, wastes everyone's time reading " + "duplicate content.",
|
|
182
|
+
"4. **Politeness pingpong.** A game between two bored people. Best conversations end in " + "silence or a simple emoji ack.",
|
|
183
|
+
"5. **Jumping in mid-execution.** Someone is already working. Your insertion creates " + "duplicate work and breaks their flow.",
|
|
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.",
|
|
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.",
|
|
157
187
|
"",
|
|
158
|
-
"
|
|
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`.",
|
|
159
189
|
"",
|
|
160
|
-
"
|
|
161
|
-
"- **Social conversations** (banter, gossip, playing around, riffing on something silly) — you " + "can join in. Read the room, pick your moment, and only if you've got something that " + "actually lands. Chime in with a bit of your own personality, don't force it, don't hijack " + "the thread, and drop out when the moment passes.",
|
|
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`.",
|
|
162
191
|
"",
|
|
163
|
-
"
|
|
192
|
+
"**Before staying silent** in a multi-person channel, also reflect:",
|
|
164
193
|
"",
|
|
165
|
-
"
|
|
166
|
-
"",
|
|
167
|
-
"
|
|
168
|
-
""
|
|
169
|
-
`- Someone says "thanks" → you don't say "you're welcome." That's the end.`,
|
|
170
|
-
`- Someone says "sounds good" / "ok" / "got it" → don't confirm the confirmation.`,
|
|
171
|
-
`- Someone acknowledges a delivery ("perfect") → don't add "glad it helped!"`,
|
|
172
|
-
"",
|
|
173
|
-
"The signal that a conversation is over is silence, not a closing message. When in doubt, " + "prefer no reply over a filler reply.",
|
|
174
|
-
"",
|
|
175
|
-
"### Voice",
|
|
176
|
-
"",
|
|
177
|
-
'Warm, dry, direct. Use contractions. Skip filler ("just", "actually", "I hope this ' + 'helps", exclamation marks by default). Real numerals, curly quotes, the ellipsis ' + "character. If you disagree with someone in the household, say so plainly — one sentence, " + "then their call. Match the sender's language."
|
|
178
|
-
].join(`
|
|
179
|
-
`);
|
|
180
|
-
}
|
|
181
|
-
function channelAwarenessSection() {
|
|
182
|
-
return [
|
|
183
|
-
"## Channel awareness",
|
|
184
|
-
"",
|
|
185
|
-
"- Reply where the message came from — same channel or thread.",
|
|
186
|
-
"- Post results in the channel that owns the topic.",
|
|
187
|
-
"- When uncertain, check the channel's history or just DM the relevant friends."
|
|
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."
|
|
188
198
|
].join(`
|
|
189
199
|
`);
|
|
190
200
|
}
|
|
191
201
|
function workspaceMemorySection() {
|
|
192
202
|
return [
|
|
193
|
-
"##
|
|
194
|
-
"",
|
|
195
|
-
"Your cwd is a persistent workspace that survives across sessions. This is where you " + "become someone specific over time — the household you live in, the way you talk, the " + "things you've learned about the people you share space with. None of that is baked in; " + "you earn it and write it down.",
|
|
196
|
-
"",
|
|
197
|
-
"### Something only you know",
|
|
203
|
+
"## Self-awareness",
|
|
198
204
|
"",
|
|
199
|
-
"You don't have continuous memory
|
|
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.",
|
|
200
206
|
"",
|
|
201
|
-
"
|
|
207
|
+
"**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
|
|
202
208
|
"",
|
|
203
|
-
"
|
|
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.",
|
|
204
210
|
"",
|
|
205
211
|
"### memory.md",
|
|
206
212
|
"",
|
|
207
|
-
"Read
|
|
213
|
+
"Read first on every wake. Pointers and facts, one line per entry. Examples: " + '"Owner: @alice#0001", "Alook codebase: /Users/alice/alook/"',
|
|
208
214
|
"",
|
|
209
|
-
"
|
|
210
|
-
"",
|
|
211
|
-
"For longer rules, workflows, or conditional procedures, write to `experiences/[NAME].md` " + 'and add a one-line index pointer in `./memory.md` (e.g. "read experiences/deploy.md ' + 'when deploying"). Use this for anything too specific or long for memory.md itself.',
|
|
212
|
-
"",
|
|
213
|
-
"Do NOT put ephemeral state (current task, in-progress status) in memory.md — the " + "context timeline handles that.",
|
|
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.",
|
|
214
216
|
"",
|
|
215
|
-
"###
|
|
216
|
-
"",
|
|
217
|
-
"You start with a floor, not a personality: warm to the household, dry rather than perky, " + "direct rather than deferential, willing to push back on a bad idea. Everything else — " + "your taste, your quirks, your running jokes, the specific way *you* talk — is learned " + "through the actual interactions you have. Notice and record:",
|
|
217
|
+
"### experiences/",
|
|
218
218
|
"",
|
|
219
|
-
"
|
|
220
|
-
`- Corrections you got ("don't send me a wall of text", "stop apologizing for tiny ` + `things") — these are the sharpest signal for who you're becoming.`,
|
|
221
|
-
'- Preferences that showed up in passing ("I hate exclamation marks", "just give me the ' + 'number", "call it the pipeline, not the flow").',
|
|
222
|
-
"- Recurring bits or shared references — inside language is a real thing, not filler.",
|
|
223
|
-
"- Your own stances you've held up under pushback and still believe.",
|
|
219
|
+
"Procedural knowledge, workflows. Link from `memory.md` with a one-line pointer.",
|
|
224
220
|
"",
|
|
225
|
-
"
|
|
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.",
|
|
226
222
|
"",
|
|
227
223
|
"### Context timeline",
|
|
228
224
|
"",
|
|
229
|
-
"`./.context_timeline/YYYY-MM-DD.jsonl` — ordered log of
|
|
225
|
+
"`./.context_timeline/YYYY-MM-DD.jsonl` — ordered daily log of what you did. Authoritative history.",
|
|
230
226
|
"",
|
|
231
227
|
"### todo.md",
|
|
232
228
|
"",
|
|
233
|
-
"When a wake brings more than one thing
|
|
229
|
+
"When a wake brings more than one thing — batch of unread, multi-step request, work " + "interrupted by new inbound — write the queue to `./todo.md` before starting the first " + "task. Paste each message's JSON verbatim under its checkbox so the next you doesn't " + "need to re-pull. **Only unprocessed tasks live here** — on finish, delete the line " + "(don't leave `[x]`). Delete the file when empty.",
|
|
234
230
|
"",
|
|
235
|
-
"
|
|
231
|
+
"Example:",
|
|
236
232
|
"",
|
|
237
233
|
"```md",
|
|
238
|
-
"# todo",
|
|
239
|
-
"",
|
|
240
234
|
'- [ ] {"seq": "#42", "channel": "/demo/general", "sender": "@alice#0001", "content": {"text": "can you pull the latest deploy logs and drop the tail here?"}, "time": "2026-06-01T12:00:00Z"}',
|
|
241
235
|
'- [ ] {"seq": "#12", "channel": "/demo/design/#12", "sender": "@alice#0001", "content": {"text": "follow-up — send a screenshot of the before/after"}, "time": "2026-06-01T12:07:00Z"}',
|
|
242
236
|
"```",
|
|
243
237
|
"",
|
|
244
|
-
"
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
function messageNotificationSection(lifecycleKind) {
|
|
249
|
-
if (lifecycleKind === "per_turn") {
|
|
250
|
-
return [
|
|
251
|
-
"## Message notifications",
|
|
252
|
-
"",
|
|
253
|
-
"You run once per wake, then your process exits — there is nothing to poll for mid-turn. " + "Finish the current wake's work, then stop. The host spawns a brand-new process for the " + "next message; it re-checks the inbox at the start of that new wake."
|
|
254
|
-
].join(`
|
|
255
|
-
`);
|
|
256
|
-
}
|
|
257
|
-
return [
|
|
258
|
-
"## Message notifications",
|
|
238
|
+
"**When to use todo.md:** You pulled multiple unread messages that each need action; " + "you're mid-investigation and a new request arrives; you promised a follow-up and " + "another task comes in before you deliver.",
|
|
239
|
+
"",
|
|
240
|
+
"**Don't use it for:** Single message you're about to handle immediately; quick " + "back-and-forth in one conversation.",
|
|
259
241
|
"",
|
|
260
|
-
"
|
|
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."
|
|
261
243
|
].join(`
|
|
262
244
|
`);
|
|
263
245
|
}
|
|
264
|
-
function buildCliSystemPrompt(config
|
|
246
|
+
function buildCliSystemPrompt(config) {
|
|
265
247
|
const sections = [
|
|
266
248
|
identitySection(config),
|
|
267
249
|
cliCommandsSection(),
|
|
268
250
|
messagingSection(),
|
|
269
|
-
serversSection(),
|
|
270
|
-
channelsSection(),
|
|
271
251
|
criticalRulesSection(),
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
channelAwarenessSection(),
|
|
252
|
+
executionModelSection(),
|
|
253
|
+
chaosAwarenessSection(),
|
|
275
254
|
workspaceMemorySection(),
|
|
276
|
-
|
|
255
|
+
utilsSection()
|
|
277
256
|
];
|
|
278
257
|
return sections.filter((s) => s && s.length > 0).join(`
|
|
279
258
|
|
|
@@ -537,7 +516,8 @@ async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG,
|
|
|
537
516
|
launchId: ctx.launchId,
|
|
538
517
|
traceDir: ctx.cliTransportTraceDir
|
|
539
518
|
}),
|
|
540
|
-
FORCE_COLOR: "0"
|
|
519
|
+
FORCE_COLOR: "0",
|
|
520
|
+
NO_COLOR: "1"
|
|
541
521
|
}
|
|
542
522
|
},
|
|
543
523
|
{ name: "runtimeContext", precedence: 50, vars: runtimeContextEnv(E, ctx.config.runtimeContext) },
|
|
@@ -557,8 +537,8 @@ async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG,
|
|
|
557
537
|
const { env: spawnEnv } = mergeEnvLayers(process.env, layers);
|
|
558
538
|
return { stateDir, tokenFile, spawnEnv };
|
|
559
539
|
}
|
|
560
|
-
function buildCliTransportSystemPrompt(config
|
|
561
|
-
return buildCliSystemPrompt(config
|
|
540
|
+
function buildCliTransportSystemPrompt(config) {
|
|
541
|
+
return buildCliSystemPrompt(config);
|
|
562
542
|
}
|
|
563
543
|
|
|
564
544
|
// src/drivers/claudeProviderIsolation.ts
|
|
@@ -589,100 +569,7 @@ function buildClaudeProviderIsolationEnv(ctx) {
|
|
|
589
569
|
};
|
|
590
570
|
}
|
|
591
571
|
|
|
592
|
-
// src/drivers/probe.ts
|
|
593
|
-
import { execFileSync } from "child_process";
|
|
594
|
-
import * as fs4 from "fs";
|
|
595
|
-
import * as path4 from "path";
|
|
596
|
-
function resolveCommandOnPath(command, deps = {}) {
|
|
597
|
-
if (deps.which)
|
|
598
|
-
return deps.which(command);
|
|
599
|
-
try {
|
|
600
|
-
if (process.platform === "win32") {
|
|
601
|
-
const out2 = execFileSync("where", [command], { encoding: "utf8", timeout: 5000 });
|
|
602
|
-
const first = out2.split(/\r?\n/).find((line) => line.trim().length > 0);
|
|
603
|
-
return first?.trim() || null;
|
|
604
|
-
}
|
|
605
|
-
const out = execFileSync("which", [command], { encoding: "utf8", timeout: 5000 });
|
|
606
|
-
return out.trim() || null;
|
|
607
|
-
} catch {
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
function firstExistingPath(candidates) {
|
|
612
|
-
for (const c of candidates) {
|
|
613
|
-
if (c && fs4.existsSync(c))
|
|
614
|
-
return c;
|
|
615
|
-
}
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
618
|
-
function looksLikeVersion(line) {
|
|
619
|
-
return /\d+\.\d+/.test(line);
|
|
620
|
-
}
|
|
621
|
-
function needsWindowsShimShell(command, platform) {
|
|
622
|
-
return platform === "win32" && /\.(cmd|bat)$/i.test(command);
|
|
623
|
-
}
|
|
624
|
-
function probeCommandVersion(command, args = [], deps = {}, platform = process.platform) {
|
|
625
|
-
try {
|
|
626
|
-
const shell = needsWindowsShimShell(command, platform);
|
|
627
|
-
const out = execFileSync(command, [...args, "--version"], {
|
|
628
|
-
encoding: "utf8",
|
|
629
|
-
timeout: 5000,
|
|
630
|
-
shell,
|
|
631
|
-
input: "",
|
|
632
|
-
env: { ...process.env, CI: "1" }
|
|
633
|
-
});
|
|
634
|
-
const line = out.split(`
|
|
635
|
-
`)[0]?.trim();
|
|
636
|
-
if (!line)
|
|
637
|
-
return { ok: false, error: "empty_version_output" };
|
|
638
|
-
if (!looksLikeVersion(line))
|
|
639
|
-
return { ok: false, error: "invalid_version_output" };
|
|
640
|
-
return { ok: true, version: line };
|
|
641
|
-
} catch (err) {
|
|
642
|
-
const code = err?.code ?? err?.code ?? "version_probe_failed";
|
|
643
|
-
return { ok: false, error: String(code) };
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
function resolveHomePath(relativePath, deps = {}) {
|
|
647
|
-
return path4.join(deps.homeDir || process.env.HOME || ".", relativePath);
|
|
648
|
-
}
|
|
649
|
-
function resolveSpawnSpec(command, args, deps = {}, platform = process.platform) {
|
|
650
|
-
const resolved = resolveCommandOnPath(command, deps) ?? command;
|
|
651
|
-
return { command: resolved, args, shell: needsWindowsShimShell(resolved, platform) };
|
|
652
|
-
}
|
|
653
|
-
function resolveClaudeCommand(deps = {}) {
|
|
654
|
-
const onPath = resolveCommandOnPath("claude", deps);
|
|
655
|
-
if (onPath)
|
|
656
|
-
return onPath;
|
|
657
|
-
if (process.platform === "darwin") {
|
|
658
|
-
return firstExistingPath([
|
|
659
|
-
resolveHomePath("Applications/Claude Code URL Handler.app/Contents/MacOS/claude", deps),
|
|
660
|
-
"/Applications/Claude Code URL Handler.app/Contents/MacOS/claude"
|
|
661
|
-
]);
|
|
662
|
-
}
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
function probeClaude(deps = {}) {
|
|
666
|
-
const command = resolveClaudeCommand(deps);
|
|
667
|
-
if (!command)
|
|
668
|
-
return { status: "unhealthy", lastError: "not_on_path" };
|
|
669
|
-
const r = probeCommandVersion(command, [], deps);
|
|
670
|
-
if (!r.ok)
|
|
671
|
-
return { status: "unhealthy", lastError: r.error };
|
|
672
|
-
return { status: "healthy", version: r.version };
|
|
673
|
-
}
|
|
674
|
-
function probeCliRuntime(binary, deps = {}) {
|
|
675
|
-
const command = resolveCommandOnPath(binary, deps);
|
|
676
|
-
if (!command)
|
|
677
|
-
return { status: "unhealthy", lastError: "not_on_path" };
|
|
678
|
-
const r = probeCommandVersion(command, [], deps);
|
|
679
|
-
if (!r.ok)
|
|
680
|
-
return { status: "unhealthy", lastError: r.error };
|
|
681
|
-
return { status: "healthy", version: r.version };
|
|
682
|
-
}
|
|
683
|
-
|
|
684
572
|
// src/drivers/claudeLaunch.ts
|
|
685
|
-
var DEFAULT_CLAUDE_MODEL = "sonnet";
|
|
686
573
|
var CLAUDE_DISALLOWED_TOOLS = "EnterPlanMode,ExitPlanMode,ScheduleWakeup,CronCreate,CronList,CronDelete";
|
|
687
574
|
function buildClaudeArgs(config) {
|
|
688
575
|
const f = resolveLaunchFieldsOrDefault(config.runtimeConfig);
|
|
@@ -697,11 +584,11 @@ function buildClaudeArgs(config) {
|
|
|
697
584
|
"--input-format",
|
|
698
585
|
"stream-json",
|
|
699
586
|
"--include-partial-messages",
|
|
700
|
-
"--model",
|
|
701
|
-
f.model || DEFAULT_CLAUDE_MODEL,
|
|
702
587
|
"--disallowed-tools",
|
|
703
588
|
f.disallowedTools || CLAUDE_DISALLOWED_TOOLS
|
|
704
589
|
];
|
|
590
|
+
if (f.model)
|
|
591
|
+
args.push("--model", f.model);
|
|
705
592
|
if (f.reasoningEffort)
|
|
706
593
|
args.push("--effort", f.reasoningEffort);
|
|
707
594
|
if (f.fastMode)
|
|
@@ -710,14 +597,24 @@ function buildClaudeArgs(config) {
|
|
|
710
597
|
args.push("--resume", config.sessionId);
|
|
711
598
|
return args;
|
|
712
599
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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 });
|
|
716
611
|
}
|
|
717
|
-
function
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
612
|
+
function tryParseJsonLine(line) {
|
|
613
|
+
try {
|
|
614
|
+
return JSON.parse(line);
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
721
618
|
}
|
|
722
619
|
|
|
723
620
|
// src/drivers/claudeEventNormalizer.ts
|
|
@@ -729,12 +626,9 @@ class ClaudeEventNormalizer {
|
|
|
729
626
|
return this.currentSession;
|
|
730
627
|
}
|
|
731
628
|
normalizeLine(line) {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
event = JSON.parse(line);
|
|
735
|
-
} catch {
|
|
629
|
+
const event = tryParseJsonLine(line);
|
|
630
|
+
if (!event)
|
|
736
631
|
return [];
|
|
737
|
-
}
|
|
738
632
|
if (event?.session_id)
|
|
739
633
|
this.currentSession = event.session_id;
|
|
740
634
|
const out = [];
|
|
@@ -838,10 +732,107 @@ class ClaudeEventNormalizer {
|
|
|
838
732
|
}
|
|
839
733
|
}
|
|
840
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
|
+
|
|
841
831
|
// src/runtime/killTree.ts
|
|
842
832
|
import { spawn } from "child_process";
|
|
843
833
|
var POLL_MS = 100;
|
|
844
|
-
var
|
|
834
|
+
var SESSION_STOP_GRACE_MS = 2000;
|
|
835
|
+
var DEFAULT_GRACE_MS = SESSION_STOP_GRACE_MS;
|
|
845
836
|
var isPosix = process.platform !== "win32";
|
|
846
837
|
function spawnAgentProcess(command, args, opts) {
|
|
847
838
|
return spawn(command, args, {
|
|
@@ -900,6 +891,13 @@ class ClaudeDriver {
|
|
|
900
891
|
supportsStdinNotification = true;
|
|
901
892
|
busyDeliveryMode = "gated";
|
|
902
893
|
supportsNativeStandingPrompt = true;
|
|
894
|
+
capabilities = {
|
|
895
|
+
reasoningEffort: true,
|
|
896
|
+
fastMode: true,
|
|
897
|
+
disallowedTools: true,
|
|
898
|
+
command: true,
|
|
899
|
+
sessionResumeMode: "by-id"
|
|
900
|
+
};
|
|
903
901
|
eventNormalizer = new ClaudeEventNormalizer;
|
|
904
902
|
probe() {
|
|
905
903
|
return probeClaude();
|
|
@@ -909,12 +907,13 @@ class ClaudeDriver {
|
|
|
909
907
|
const { spawnEnv } = await prepareCliTransport(ctx, buildClaudeProviderIsolationEnv(ctx), cliConfig);
|
|
910
908
|
const args = buildClaudeArgs(ctx.config);
|
|
911
909
|
delete spawnEnv.CLAUDECODE;
|
|
912
|
-
const
|
|
913
|
-
const
|
|
914
|
-
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, {
|
|
915
914
|
cwd: ctx.workingDirectory,
|
|
916
915
|
env: spawnEnv,
|
|
917
|
-
shell:
|
|
916
|
+
shell: spec.shell
|
|
918
917
|
});
|
|
919
918
|
const stdinMsg = JSON.stringify({
|
|
920
919
|
type: "user",
|
|
@@ -939,7 +938,7 @@ class ClaudeDriver {
|
|
|
939
938
|
});
|
|
940
939
|
}
|
|
941
940
|
buildSystemPrompt(config) {
|
|
942
|
-
return buildCliTransportSystemPrompt(config
|
|
941
|
+
return buildCliTransportSystemPrompt(config);
|
|
943
942
|
}
|
|
944
943
|
}
|
|
945
944
|
|
|
@@ -996,12 +995,9 @@ class CodexEventNormalizer {
|
|
|
996
995
|
this.threadId = threadId;
|
|
997
996
|
}
|
|
998
997
|
normalizeLine(line) {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
msg = JSON.parse(line);
|
|
1002
|
-
} catch {
|
|
998
|
+
const msg = tryParseJsonLine(line);
|
|
999
|
+
if (!msg)
|
|
1003
1000
|
return [];
|
|
1004
|
-
}
|
|
1005
1001
|
if (msg?.error && msg.id !== undefined) {
|
|
1006
1002
|
return [{ kind: "error", message: msg.error?.message ?? "Codex RPC error" }];
|
|
1007
1003
|
}
|
|
@@ -1126,6 +1122,21 @@ function codexSessionRootCandidates(homeDirOrCodexRoot) {
|
|
|
1126
1122
|
return codexStateRootCandidates(homeDirOrCodexRoot).map((root) => path5.join(root, "sessions"));
|
|
1127
1123
|
}
|
|
1128
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
|
+
|
|
1129
1140
|
// src/drivers/codex.ts
|
|
1130
1141
|
class CodexDriver {
|
|
1131
1142
|
id = "codex";
|
|
@@ -1138,6 +1149,13 @@ class CodexDriver {
|
|
|
1138
1149
|
supportsStdinNotification = true;
|
|
1139
1150
|
busyDeliveryMode = "gated";
|
|
1140
1151
|
supportsNativeStandingPrompt = true;
|
|
1152
|
+
capabilities = {
|
|
1153
|
+
reasoningEffort: true,
|
|
1154
|
+
fastMode: true,
|
|
1155
|
+
disallowedTools: false,
|
|
1156
|
+
command: true,
|
|
1157
|
+
sessionResumeMode: "by-id"
|
|
1158
|
+
};
|
|
1141
1159
|
eventNormalizer = new CodexEventNormalizer;
|
|
1142
1160
|
requestId = 0;
|
|
1143
1161
|
codexHomeRoot = null;
|
|
@@ -1151,24 +1169,17 @@ class CodexDriver {
|
|
|
1151
1169
|
return probeCliRuntime("codex");
|
|
1152
1170
|
}
|
|
1153
1171
|
async spawn(ctx) {
|
|
1154
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1172
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1155
1173
|
this.codexHomeRoot = resolveCodexHomeRootFromEnv(spawnEnv, { cwd: ctx.workingDirectory });
|
|
1156
|
-
const
|
|
1174
|
+
const override = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
1175
|
+
const spec = resolveSpawnSpec("codex", ["app-server", "--listen", "stdio://"], override);
|
|
1157
1176
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1158
1177
|
cwd: ctx.workingDirectory,
|
|
1159
1178
|
env: spawnEnv,
|
|
1160
1179
|
shell: spec.shell
|
|
1161
1180
|
});
|
|
1162
1181
|
queueMicrotask(() => {
|
|
1163
|
-
proc.stdin?.write(
|
|
1164
|
-
jsonrpc: "2.0",
|
|
1165
|
-
id: this.nextRequestId(),
|
|
1166
|
-
method: "initialize",
|
|
1167
|
-
params: {
|
|
1168
|
-
clientInfo: { name: "agent-backend", version: "1.0.0" },
|
|
1169
|
-
capabilities: { experimentalApi: true }
|
|
1170
|
-
}
|
|
1171
|
-
}) + `
|
|
1182
|
+
proc.stdin?.write(jsonRpcRequest("initialize", { clientInfo: getDaemonClientInfo(), capabilities: { experimentalApi: true } }, this.nextRequestId()) + `
|
|
1172
1183
|
`);
|
|
1173
1184
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1174
1185
|
const resuming = Boolean(ctx.config.sessionId);
|
|
@@ -1187,12 +1198,7 @@ class CodexDriver {
|
|
|
1187
1198
|
params.config = { model_reasoning_effort: f.reasoningEffort };
|
|
1188
1199
|
if (f.fastMode)
|
|
1189
1200
|
params.serviceTier = "fast";
|
|
1190
|
-
proc.stdin?.write(
|
|
1191
|
-
jsonrpc: "2.0",
|
|
1192
|
-
id: this.nextRequestId(),
|
|
1193
|
-
method: resuming ? "thread/resume" : "thread/start",
|
|
1194
|
-
params
|
|
1195
|
-
}) + `
|
|
1201
|
+
proc.stdin?.write(jsonRpcRequest(resuming ? "thread/resume" : "thread/start", params, this.nextRequestId()) + `
|
|
1196
1202
|
`);
|
|
1197
1203
|
});
|
|
1198
1204
|
return { process: proc };
|
|
@@ -1208,23 +1214,11 @@ class CodexDriver {
|
|
|
1208
1214
|
if (!threadId)
|
|
1209
1215
|
return null;
|
|
1210
1216
|
const input = [{ type: "text", text }];
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
jsonrpc: "2.0",
|
|
1214
|
-
id: this.nextRequestId(),
|
|
1215
|
-
method: "turn/start",
|
|
1216
|
-
params: { threadId, input }
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
return JSON.stringify({
|
|
1220
|
-
jsonrpc: "2.0",
|
|
1221
|
-
id: this.nextRequestId(),
|
|
1222
|
-
method: "turn/steer",
|
|
1223
|
-
params: { threadId, input }
|
|
1224
|
-
});
|
|
1217
|
+
const method = opts?.mode === "idle" ? "turn/start" : "turn/steer";
|
|
1218
|
+
return jsonRpcRequest(method, { threadId, input }, this.nextRequestId());
|
|
1225
1219
|
}
|
|
1226
1220
|
buildSystemPrompt(config) {
|
|
1227
|
-
return buildCliTransportSystemPrompt(config
|
|
1221
|
+
return buildCliTransportSystemPrompt(config);
|
|
1228
1222
|
}
|
|
1229
1223
|
}
|
|
1230
1224
|
|
|
@@ -1252,37 +1246,42 @@ class GeminiDriver {
|
|
|
1252
1246
|
};
|
|
1253
1247
|
session = { recovery: "resume_or_fresh" };
|
|
1254
1248
|
model = {
|
|
1255
|
-
detectedModelsVerifiedAs: "
|
|
1256
|
-
toLaunchSpec: (modelId) => modelId
|
|
1249
|
+
detectedModelsVerifiedAs: "launchable",
|
|
1250
|
+
toLaunchSpec: (modelId) => modelId ? { args: ["--model", modelId] } : { args: [] }
|
|
1257
1251
|
};
|
|
1258
1252
|
supportsStdinNotification = false;
|
|
1259
1253
|
busyDeliveryMode = "none";
|
|
1254
|
+
capabilities = {
|
|
1255
|
+
reasoningEffort: false,
|
|
1256
|
+
fastMode: false,
|
|
1257
|
+
disallowedTools: false,
|
|
1258
|
+
command: true,
|
|
1259
|
+
sessionResumeMode: "by-id"
|
|
1260
|
+
};
|
|
1260
1261
|
sessionId = null;
|
|
1261
1262
|
probe() {
|
|
1262
1263
|
return probeCliRuntime("gemini");
|
|
1263
1264
|
}
|
|
1264
1265
|
async spawn(ctx) {
|
|
1265
1266
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1266
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1267
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1267
1268
|
spawnEnv.GEMINI_CLI_TRUST_WORKSPACE ??= "true";
|
|
1268
1269
|
if (process.platform === "win32")
|
|
1269
1270
|
spawnEnv.GEMINI_PTY_INFO ??= "child_process";
|
|
1270
|
-
const
|
|
1271
|
+
const override = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
1272
|
+
const spec = resolveSpawnSpec("gemini", buildGeminiArgs(ctx.config), override);
|
|
1271
1273
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1272
1274
|
cwd: ctx.workingDirectory,
|
|
1273
1275
|
env: spawnEnv,
|
|
1274
1276
|
shell: spec.shell
|
|
1275
1277
|
});
|
|
1276
|
-
proc
|
|
1278
|
+
writeToStdinAndDetach(proc, ctx.prompt);
|
|
1277
1279
|
return { process: proc };
|
|
1278
1280
|
}
|
|
1279
1281
|
parseLine(line) {
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
event = JSON.parse(line);
|
|
1283
|
-
} catch {
|
|
1282
|
+
const event = tryParseJsonLine(line);
|
|
1283
|
+
if (!event)
|
|
1284
1284
|
return [];
|
|
1285
|
-
}
|
|
1286
1285
|
switch (event?.type) {
|
|
1287
1286
|
case "init":
|
|
1288
1287
|
this.sessionId = event.session_id ?? this.sessionId;
|
|
@@ -1308,7 +1307,7 @@ class GeminiDriver {
|
|
|
1308
1307
|
return null;
|
|
1309
1308
|
}
|
|
1310
1309
|
buildSystemPrompt(config) {
|
|
1311
|
-
return buildCliTransportSystemPrompt(config
|
|
1310
|
+
return buildCliTransportSystemPrompt(config);
|
|
1312
1311
|
}
|
|
1313
1312
|
}
|
|
1314
1313
|
|
|
@@ -1328,13 +1327,20 @@ class CopilotDriver {
|
|
|
1328
1327
|
};
|
|
1329
1328
|
supportsStdinNotification = false;
|
|
1330
1329
|
busyDeliveryMode = "none";
|
|
1330
|
+
capabilities = {
|
|
1331
|
+
reasoningEffort: true,
|
|
1332
|
+
fastMode: false,
|
|
1333
|
+
disallowedTools: false,
|
|
1334
|
+
command: true,
|
|
1335
|
+
sessionResumeMode: "by-id"
|
|
1336
|
+
};
|
|
1331
1337
|
sessionId = null;
|
|
1332
1338
|
probe() {
|
|
1333
1339
|
return probeCliRuntime("copilot");
|
|
1334
1340
|
}
|
|
1335
1341
|
async spawn(ctx) {
|
|
1336
1342
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1337
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1343
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1338
1344
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1339
1345
|
const args = ["--output-format", "json", "--allow-all-tools", "--allow-all-paths", "-p", ctx.prompt];
|
|
1340
1346
|
if (f.model)
|
|
@@ -1343,7 +1349,7 @@ class CopilotDriver {
|
|
|
1343
1349
|
args.push("--effort", f.reasoningEffort);
|
|
1344
1350
|
if (ctx.config.sessionId)
|
|
1345
1351
|
args.push(`--resume=${ctx.config.sessionId}`);
|
|
1346
|
-
const spec = resolveSpawnSpec("copilot", args);
|
|
1352
|
+
const spec = resolveSpawnSpec("copilot", args, f.command);
|
|
1347
1353
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1348
1354
|
cwd: ctx.workingDirectory,
|
|
1349
1355
|
env: spawnEnv,
|
|
@@ -1352,12 +1358,9 @@ class CopilotDriver {
|
|
|
1352
1358
|
return { process: proc };
|
|
1353
1359
|
}
|
|
1354
1360
|
parseLine(line) {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
event = JSON.parse(line);
|
|
1358
|
-
} catch {
|
|
1361
|
+
const event = tryParseJsonLine(line);
|
|
1362
|
+
if (!event)
|
|
1359
1363
|
return [];
|
|
1360
|
-
}
|
|
1361
1364
|
switch (event?.type) {
|
|
1362
1365
|
case "assistant.turn_start":
|
|
1363
1366
|
if (event.sessionId)
|
|
@@ -1392,7 +1395,7 @@ class CopilotDriver {
|
|
|
1392
1395
|
return null;
|
|
1393
1396
|
}
|
|
1394
1397
|
buildSystemPrompt(config) {
|
|
1395
|
-
return buildCliTransportSystemPrompt(config
|
|
1398
|
+
return buildCliTransportSystemPrompt(config);
|
|
1396
1399
|
}
|
|
1397
1400
|
}
|
|
1398
1401
|
|
|
@@ -1412,13 +1415,20 @@ class CursorDriver {
|
|
|
1412
1415
|
};
|
|
1413
1416
|
supportsStdinNotification = false;
|
|
1414
1417
|
busyDeliveryMode = "none";
|
|
1418
|
+
capabilities = {
|
|
1419
|
+
reasoningEffort: false,
|
|
1420
|
+
fastMode: false,
|
|
1421
|
+
disallowedTools: false,
|
|
1422
|
+
command: true,
|
|
1423
|
+
sessionResumeMode: "by-id"
|
|
1424
|
+
};
|
|
1415
1425
|
sessionId = null;
|
|
1416
1426
|
probe() {
|
|
1417
1427
|
return probeCliRuntime("cursor-agent");
|
|
1418
1428
|
}
|
|
1419
1429
|
async spawn(ctx) {
|
|
1420
1430
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1421
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1431
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1422
1432
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1423
1433
|
const args = ["--print", "--output-format", "stream-json", "--yolo", "--approve-mcps", "--trust"];
|
|
1424
1434
|
if (f.model)
|
|
@@ -1426,7 +1436,7 @@ class CursorDriver {
|
|
|
1426
1436
|
if (ctx.config.sessionId)
|
|
1427
1437
|
args.push("--resume", ctx.config.sessionId);
|
|
1428
1438
|
args.push(ctx.prompt);
|
|
1429
|
-
const spec = resolveSpawnSpec("cursor-agent", args);
|
|
1439
|
+
const spec = resolveSpawnSpec("cursor-agent", args, f.command);
|
|
1430
1440
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1431
1441
|
cwd: ctx.workingDirectory,
|
|
1432
1442
|
env: spawnEnv,
|
|
@@ -1435,12 +1445,9 @@ class CursorDriver {
|
|
|
1435
1445
|
return { process: proc };
|
|
1436
1446
|
}
|
|
1437
1447
|
parseLine(line) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
event = JSON.parse(line);
|
|
1441
|
-
} catch {
|
|
1448
|
+
const event = tryParseJsonLine(line);
|
|
1449
|
+
if (!event)
|
|
1442
1450
|
return [];
|
|
1443
|
-
}
|
|
1444
1451
|
if (event?.type === "system") {
|
|
1445
1452
|
if (event.subtype === "init") {
|
|
1446
1453
|
this.sessionId = event.session_id ?? this.sessionId;
|
|
@@ -1483,7 +1490,7 @@ class CursorDriver {
|
|
|
1483
1490
|
return null;
|
|
1484
1491
|
}
|
|
1485
1492
|
buildSystemPrompt(config) {
|
|
1486
|
-
return buildCliTransportSystemPrompt(config
|
|
1493
|
+
return buildCliTransportSystemPrompt(config);
|
|
1487
1494
|
}
|
|
1488
1495
|
}
|
|
1489
1496
|
|
|
@@ -1505,6 +1512,13 @@ class OpenCodeDriver {
|
|
|
1505
1512
|
busyDeliveryMode = "none";
|
|
1506
1513
|
terminateProcessOnTurnEnd = true;
|
|
1507
1514
|
deferSpawnUntilMessage = true;
|
|
1515
|
+
capabilities = {
|
|
1516
|
+
reasoningEffort: false,
|
|
1517
|
+
fastMode: false,
|
|
1518
|
+
disallowedTools: false,
|
|
1519
|
+
command: true,
|
|
1520
|
+
sessionResumeMode: "by-id"
|
|
1521
|
+
};
|
|
1508
1522
|
sessionId = null;
|
|
1509
1523
|
shouldDeferWakeMessage(message) {
|
|
1510
1524
|
return message?.type === "system";
|
|
@@ -1515,7 +1529,7 @@ class OpenCodeDriver {
|
|
|
1515
1529
|
async spawn(ctx) {
|
|
1516
1530
|
this.sessionId = ctx.config.sessionId ?? null;
|
|
1517
1531
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1518
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1532
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1519
1533
|
const args = ["run", "--format", "json", "--dangerously-skip-permissions", "--pure", "--dir", ctx.workingDirectory];
|
|
1520
1534
|
if (f.model)
|
|
1521
1535
|
args.push("--model", f.model);
|
|
@@ -1523,7 +1537,7 @@ class OpenCodeDriver {
|
|
|
1523
1537
|
args.push("--session", ctx.config.sessionId);
|
|
1524
1538
|
const promptArg = ctx.prompt === ctx.standingPrompt ? "No new messages are pending. Stop now." : ctx.prompt;
|
|
1525
1539
|
args.push("--", promptArg);
|
|
1526
|
-
const spec = resolveSpawnSpec("opencode", args);
|
|
1540
|
+
const spec = resolveSpawnSpec("opencode", args, f.command);
|
|
1527
1541
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1528
1542
|
cwd: ctx.workingDirectory,
|
|
1529
1543
|
env: spawnEnv,
|
|
@@ -1533,12 +1547,9 @@ class OpenCodeDriver {
|
|
|
1533
1547
|
return { process: proc };
|
|
1534
1548
|
}
|
|
1535
1549
|
parseLine(line) {
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
event = JSON.parse(line);
|
|
1539
|
-
} catch {
|
|
1550
|
+
const event = tryParseJsonLine(line);
|
|
1551
|
+
if (!event)
|
|
1540
1552
|
return [];
|
|
1541
|
-
}
|
|
1542
1553
|
const out = [];
|
|
1543
1554
|
if (event?.sessionID && this.sessionId !== event.sessionID) {
|
|
1544
1555
|
this.sessionId = event.sessionID;
|
|
@@ -1576,12 +1587,12 @@ class OpenCodeDriver {
|
|
|
1576
1587
|
return null;
|
|
1577
1588
|
}
|
|
1578
1589
|
buildSystemPrompt(config) {
|
|
1579
|
-
return buildCliTransportSystemPrompt(config
|
|
1590
|
+
return buildCliTransportSystemPrompt(config);
|
|
1580
1591
|
}
|
|
1581
1592
|
}
|
|
1582
1593
|
|
|
1583
1594
|
// src/drivers/antigravity.ts
|
|
1584
|
-
import { randomUUID } from "crypto";
|
|
1595
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1585
1596
|
var ERROR_LINE_PATTERNS = [/^error[:\s]/i, /\bfatal\b/i, /\bpanic\b/i, /unable to/i];
|
|
1586
1597
|
var ANTIGRAVITY_PRINT_TIMEOUT = "30m";
|
|
1587
1598
|
function buildAntigravityArgs(ctx) {
|
|
@@ -1606,27 +1617,34 @@ class AntigravityDriver {
|
|
|
1606
1617
|
};
|
|
1607
1618
|
supportsStdinNotification = false;
|
|
1608
1619
|
busyDeliveryMode = "none";
|
|
1620
|
+
capabilities = {
|
|
1621
|
+
reasoningEffort: false,
|
|
1622
|
+
fastMode: false,
|
|
1623
|
+
disallowedTools: false,
|
|
1624
|
+
command: true,
|
|
1625
|
+
sessionResumeMode: "most-recent"
|
|
1626
|
+
};
|
|
1609
1627
|
sessionId = null;
|
|
1610
1628
|
sentInit = false;
|
|
1611
1629
|
probe() {
|
|
1612
1630
|
return probeCliRuntime("agy");
|
|
1613
1631
|
}
|
|
1614
1632
|
async spawn(ctx) {
|
|
1615
|
-
this.sessionId = ctx.config.sessionId ??
|
|
1633
|
+
this.sessionId = ctx.config.sessionId ?? randomUUID2();
|
|
1616
1634
|
this.sentInit = false;
|
|
1617
1635
|
const { spawnEnv } = await prepareCliTransport(ctx, {
|
|
1618
|
-
NO_COLOR: "1",
|
|
1619
1636
|
SSH_CLIENT: "",
|
|
1620
1637
|
SSH_CONNECTION: "",
|
|
1621
1638
|
SSH_TTY: ""
|
|
1622
1639
|
});
|
|
1623
|
-
const
|
|
1640
|
+
const override = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
1641
|
+
const spec = resolveSpawnSpec("agy", buildAntigravityArgs(ctx), override);
|
|
1624
1642
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1625
1643
|
cwd: ctx.workingDirectory,
|
|
1626
1644
|
env: spawnEnv,
|
|
1627
1645
|
shell: spec.shell
|
|
1628
1646
|
});
|
|
1629
|
-
proc
|
|
1647
|
+
writeToStdinAndDetach(proc, ctx.prompt);
|
|
1630
1648
|
return { process: proc };
|
|
1631
1649
|
}
|
|
1632
1650
|
parseLine(line) {
|
|
@@ -1651,12 +1669,13 @@ class AntigravityDriver {
|
|
|
1651
1669
|
return null;
|
|
1652
1670
|
}
|
|
1653
1671
|
buildSystemPrompt(config) {
|
|
1654
|
-
return buildCliTransportSystemPrompt(config
|
|
1672
|
+
return buildCliTransportSystemPrompt(config);
|
|
1655
1673
|
}
|
|
1656
1674
|
}
|
|
1657
1675
|
|
|
1658
1676
|
// src/drivers/kimi.ts
|
|
1659
|
-
import { randomUUID as
|
|
1677
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1678
|
+
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
1660
1679
|
function parseToolArguments(args) {
|
|
1661
1680
|
if (typeof args !== "string")
|
|
1662
1681
|
return args ?? {};
|
|
@@ -1677,55 +1696,49 @@ class KimiDriver {
|
|
|
1677
1696
|
};
|
|
1678
1697
|
supportsStdinNotification = true;
|
|
1679
1698
|
busyDeliveryMode = "direct";
|
|
1699
|
+
capabilities = {
|
|
1700
|
+
reasoningEffort: false,
|
|
1701
|
+
fastMode: false,
|
|
1702
|
+
disallowedTools: false,
|
|
1703
|
+
command: true,
|
|
1704
|
+
sessionResumeMode: "by-id"
|
|
1705
|
+
};
|
|
1680
1706
|
sessionId = "";
|
|
1681
1707
|
sentInit = false;
|
|
1682
|
-
promptRequestId =
|
|
1708
|
+
promptRequestId = randomUUID3();
|
|
1683
1709
|
probe() {
|
|
1684
1710
|
return probeCliRuntime("kimi");
|
|
1685
1711
|
}
|
|
1686
1712
|
async spawn(ctx) {
|
|
1687
|
-
this.sessionId = ctx.config.sessionId ||
|
|
1713
|
+
this.sessionId = ctx.config.sessionId || randomUUID3();
|
|
1688
1714
|
const isResume = Boolean(ctx.config.sessionId);
|
|
1689
|
-
const { spawnEnv } = await prepareCliTransport(ctx
|
|
1715
|
+
const { spawnEnv } = await prepareCliTransport(ctx);
|
|
1690
1716
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1691
1717
|
const args = ["--wire", "--yolo", "--session", this.sessionId];
|
|
1692
1718
|
if (f.model)
|
|
1693
1719
|
args.push("--model", f.model);
|
|
1694
|
-
const spec = resolveSpawnSpec("kimi", args);
|
|
1720
|
+
const spec = resolveSpawnSpec("kimi", args, f.command);
|
|
1695
1721
|
const proc = spawnAgentProcess(spec.command, spec.args, {
|
|
1696
1722
|
cwd: ctx.workingDirectory,
|
|
1697
1723
|
env: spawnEnv,
|
|
1698
1724
|
shell: spec.shell
|
|
1699
1725
|
});
|
|
1700
|
-
proc.stdin?.write(
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
params: {
|
|
1705
|
-
protocol_version: "1.3",
|
|
1706
|
-
client: { name: "agent-backend", version: "1.0.0" },
|
|
1707
|
-
capabilities: { supports_question: false, supports_plan_mode: false }
|
|
1708
|
-
}
|
|
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 }
|
|
1709
1730
|
}) + `
|
|
1710
1731
|
`);
|
|
1711
|
-
proc.stdin?.write(
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
method: "prompt",
|
|
1715
|
-
params: {
|
|
1716
|
-
user_input: isResume ? ctx.prompt : "Your system prompt contains your standing instructions. Follow it now and begin listening for messages."
|
|
1717
|
-
}
|
|
1718
|
-
}) + `
|
|
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) + `
|
|
1719
1735
|
`);
|
|
1720
1736
|
return { process: proc };
|
|
1721
1737
|
}
|
|
1722
1738
|
parseLine(line) {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
msg = JSON.parse(line);
|
|
1726
|
-
} catch {
|
|
1739
|
+
const msg = tryParseJsonLine(line);
|
|
1740
|
+
if (!msg)
|
|
1727
1741
|
return [];
|
|
1728
|
-
}
|
|
1729
1742
|
const out = [];
|
|
1730
1743
|
if (!this.sentInit) {
|
|
1731
1744
|
this.sentInit = true;
|
|
@@ -1777,16 +1790,17 @@ class KimiDriver {
|
|
|
1777
1790
|
}
|
|
1778
1791
|
encodeStdinMessage(text, _sessionId, opts) {
|
|
1779
1792
|
const method = opts?.mode === "idle" ? "prompt" : "steer";
|
|
1780
|
-
return
|
|
1793
|
+
return jsonRpcRequest(method, { user_input: text });
|
|
1781
1794
|
}
|
|
1782
1795
|
buildSystemPrompt(config) {
|
|
1783
|
-
return buildCliTransportSystemPrompt(config
|
|
1796
|
+
return buildCliTransportSystemPrompt(config);
|
|
1784
1797
|
}
|
|
1785
1798
|
}
|
|
1786
1799
|
|
|
1787
1800
|
// src/drivers/pi.ts
|
|
1788
|
-
import { createRequire } from "module";
|
|
1789
|
-
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";
|
|
1790
1804
|
import * as path6 from "path";
|
|
1791
1805
|
|
|
1792
1806
|
// src/runtime/sdkRuntimeSession.ts
|
|
@@ -1896,6 +1910,24 @@ function resolvePiSdkPackageDir(deps = {}) {
|
|
|
1896
1910
|
} catch {}
|
|
1897
1911
|
return;
|
|
1898
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
|
+
}
|
|
1899
1931
|
function resolvePiSdkVersionFromPath(deps = {}) {
|
|
1900
1932
|
const dir = resolvePiSdkPackageDir(deps);
|
|
1901
1933
|
if (!dir)
|
|
@@ -1909,7 +1941,7 @@ function resolvePiSdkVersionFromPath(deps = {}) {
|
|
|
1909
1941
|
}
|
|
1910
1942
|
function readPiSdkVersion() {
|
|
1911
1943
|
try {
|
|
1912
|
-
const req =
|
|
1944
|
+
const req = createRequire2(import.meta.url);
|
|
1913
1945
|
const pkg = req("@earendil-works/pi-coding-agent/package.json");
|
|
1914
1946
|
if (pkg.version)
|
|
1915
1947
|
return pkg.version;
|
|
@@ -1960,6 +1992,13 @@ class PiDriver {
|
|
|
1960
1992
|
supportsStdinNotification = true;
|
|
1961
1993
|
busyDeliveryMode = "direct";
|
|
1962
1994
|
supportsNativeStandingPrompt = true;
|
|
1995
|
+
capabilities = {
|
|
1996
|
+
reasoningEffort: true,
|
|
1997
|
+
fastMode: false,
|
|
1998
|
+
disallowedTools: false,
|
|
1999
|
+
command: true,
|
|
2000
|
+
sessionResumeMode: "by-id"
|
|
2001
|
+
};
|
|
1963
2002
|
sessionId = null;
|
|
1964
2003
|
probe() {
|
|
1965
2004
|
const version = readPiSdkVersion();
|
|
@@ -1973,10 +2012,6 @@ class PiDriver {
|
|
|
1973
2012
|
}
|
|
1974
2013
|
async createSession(ctx, deps) {
|
|
1975
2014
|
const spawnEnv = await deps.buildSpawnEnv();
|
|
1976
|
-
if (ctx.standingPrompt) {
|
|
1977
|
-
mkdirSync3(ctx.workingDirectory, { recursive: true });
|
|
1978
|
-
writeAgentFile(ctx.workingDirectory, ctx.standingPrompt);
|
|
1979
|
-
}
|
|
1980
2015
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
1981
2016
|
const { session, sessionId } = await deps.createAgentSession({
|
|
1982
2017
|
cwd: ctx.workingDirectory,
|
|
@@ -2010,7 +2045,7 @@ class PiDriver {
|
|
|
2010
2045
|
return null;
|
|
2011
2046
|
}
|
|
2012
2047
|
buildSystemPrompt(config) {
|
|
2013
|
-
return buildCliTransportSystemPrompt(config
|
|
2048
|
+
return buildCliTransportSystemPrompt(config);
|
|
2014
2049
|
}
|
|
2015
2050
|
}
|
|
2016
2051
|
|
|
@@ -2121,7 +2156,7 @@ class ChildProcessRuntimeSession {
|
|
|
2121
2156
|
this.requestedStopReason = opts?.reason;
|
|
2122
2157
|
const pid = proc.pid;
|
|
2123
2158
|
if (pid) {
|
|
2124
|
-
await killProcessTree(pid, { graceMs: opts?.forceAfterMs ??
|
|
2159
|
+
await killProcessTree(pid, { graceMs: opts?.forceAfterMs ?? SESSION_STOP_GRACE_MS });
|
|
2125
2160
|
} else {
|
|
2126
2161
|
proc.kill(opts?.signal ?? "SIGTERM");
|
|
2127
2162
|
}
|
|
@@ -2503,6 +2538,14 @@ function reduceApmStartupTimeoutTermination(state, input) {
|
|
|
2503
2538
|
}
|
|
2504
2539
|
// src/runtime/errorDiagnostics.ts
|
|
2505
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+";
|
|
2506
2549
|
var REASON_BY_CLASS = {
|
|
2507
2550
|
RateLimitError: "rate_limited",
|
|
2508
2551
|
AuthError: "auth_failed",
|
|
@@ -2592,20 +2635,18 @@ function scrubRuntimeErrorDiagnosticText(value) {
|
|
|
2592
2635
|
function messageLengthBucket(len) {
|
|
2593
2636
|
if (len === 0)
|
|
2594
2637
|
return "0";
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
return "4k-16k";
|
|
2601
|
-
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;
|
|
2602
2643
|
}
|
|
2603
2644
|
function buildRuntimeErrorDiagnosticEnvelope(message) {
|
|
2604
2645
|
const httpStatus = extractHttpStatus(message);
|
|
2605
2646
|
const runtimeErrorClass = classifyRuntimeError(message, httpStatus);
|
|
2606
2647
|
const runtimeErrorAction = classifyRuntimeErrorAction(message, runtimeErrorClass);
|
|
2607
2648
|
const scrubbed = scrubRuntimeErrorDiagnosticText(message);
|
|
2608
|
-
const fingerprint = createHash2("sha256").update(scrubbed).digest("hex").slice(0,
|
|
2649
|
+
const fingerprint = createHash2("sha256").update(scrubbed).digest("hex").slice(0, ERROR_FINGERPRINT_LEN);
|
|
2609
2650
|
const spanAttrs = {
|
|
2610
2651
|
turn_outcome: "failed",
|
|
2611
2652
|
turn_subtype: "runtime_error",
|
|
@@ -2616,13 +2657,13 @@ function buildRuntimeErrorDiagnosticEnvelope(message) {
|
|
|
2616
2657
|
runtime_error_fingerprint: fingerprint,
|
|
2617
2658
|
runtime_error_message_present: message.length > 0,
|
|
2618
2659
|
runtime_error_message_length_bucket: messageLengthBucket(message.length),
|
|
2619
|
-
runtime_error_message_truncated: scrubbed.length >
|
|
2660
|
+
runtime_error_message_truncated: scrubbed.length > ERROR_EXCERPT_MAX_BYTES
|
|
2620
2661
|
};
|
|
2621
2662
|
if (httpStatus !== null)
|
|
2622
2663
|
spanAttrs.runtime_error_http_status = httpStatus;
|
|
2623
2664
|
return {
|
|
2624
2665
|
spanAttrs,
|
|
2625
|
-
eventAttrs: { ...spanAttrs, runtime_error_message_excerpt: scrubbed.slice(0,
|
|
2666
|
+
eventAttrs: { ...spanAttrs, runtime_error_message_excerpt: scrubbed.slice(0, ERROR_EXCERPT_MAX_BYTES) }
|
|
2626
2667
|
};
|
|
2627
2668
|
}
|
|
2628
2669
|
// src/inbox/projection.ts
|
|
@@ -2695,8 +2736,9 @@ function messageSeq(message) {
|
|
|
2695
2736
|
}
|
|
2696
2737
|
return Math.floor(message.seq);
|
|
2697
2738
|
}
|
|
2739
|
+
var MESSAGE_ID_SHORT_LEN = 8;
|
|
2698
2740
|
function shortMessageId(value) {
|
|
2699
|
-
return value.slice(0,
|
|
2741
|
+
return value.slice(0, MESSAGE_ID_SHORT_LEN);
|
|
2700
2742
|
}
|
|
2701
2743
|
function normalizeSenderType(value) {
|
|
2702
2744
|
return value === "human" || value === "agent" || value === "system" ? value : undefined;
|
|
@@ -3175,11 +3217,32 @@ function reduceManager(state, event) {
|
|
|
3175
3217
|
a.turnActive = true;
|
|
3176
3218
|
a.lastProgressAt = event.nowMs;
|
|
3177
3219
|
a.idleSince = null;
|
|
3220
|
+
if (a.resetting)
|
|
3221
|
+
a.resetting = false;
|
|
3178
3222
|
});
|
|
3179
3223
|
case "session":
|
|
3180
3224
|
return mutate(state, event.agentId, (a) => {
|
|
3181
3225
|
a.sessionId = event.sessionId;
|
|
3182
3226
|
});
|
|
3227
|
+
case "reset_session":
|
|
3228
|
+
if (!state.agents[event.agentId])
|
|
3229
|
+
return { state, effects: [] };
|
|
3230
|
+
return mutate(state, event.agentId, (a) => {
|
|
3231
|
+
a.sessionId = null;
|
|
3232
|
+
});
|
|
3233
|
+
case "begin_reset":
|
|
3234
|
+
if (!state.agents[event.agentId])
|
|
3235
|
+
return { state, effects: [] };
|
|
3236
|
+
return mutate(state, event.agentId, (a) => {
|
|
3237
|
+
a.resetting = true;
|
|
3238
|
+
});
|
|
3239
|
+
case "rewake_after_reset":
|
|
3240
|
+
if (!state.agents[event.agentId])
|
|
3241
|
+
return { state, effects: [] };
|
|
3242
|
+
return mutate(state, event.agentId, (a) => {
|
|
3243
|
+
a.inbox = [...a.inbox, event.message];
|
|
3244
|
+
a.idleSince = null;
|
|
3245
|
+
});
|
|
3183
3246
|
case "progress":
|
|
3184
3247
|
return mutate(state, event.agentId, (a) => {
|
|
3185
3248
|
a.lastProgressAt = event.nowMs;
|
|
@@ -3200,6 +3263,11 @@ function onWake(state, agentId, message) {
|
|
|
3200
3263
|
if (!agent) {
|
|
3201
3264
|
return { state, effects: [] };
|
|
3202
3265
|
}
|
|
3266
|
+
if (agent.resetting && agent.status !== "idle") {
|
|
3267
|
+
agent.inbox = [...agent.inbox, message];
|
|
3268
|
+
agent.idleSince = null;
|
|
3269
|
+
return commit(state, agent, []);
|
|
3270
|
+
}
|
|
3203
3271
|
agent.inbox = [...agent.inbox, message];
|
|
3204
3272
|
agent.idleSince = null;
|
|
3205
3273
|
if (agent.status === "idle") {
|
|
@@ -3257,7 +3325,7 @@ function onRuntimeSignal(state, agentId, kind) {
|
|
|
3257
3325
|
if (!existing)
|
|
3258
3326
|
return { state, effects: [] };
|
|
3259
3327
|
const agent = clone(existing);
|
|
3260
|
-
const isGatedActive = agent.status === "running" && agent.turnActive && agent.caps.busyDeliveryMode === "gated";
|
|
3328
|
+
const isGatedActive = !agent.resetting && agent.status === "running" && agent.turnActive && agent.caps.busyDeliveryMode === "gated";
|
|
3261
3329
|
if (!isGatedActive) {
|
|
3262
3330
|
agent.apm = reduceApmGatedRecentEvent(agent.apm, { event: kind }).nextState;
|
|
3263
3331
|
return commit(state, agent, []);
|
|
@@ -3320,6 +3388,9 @@ function onExit(state, agentId) {
|
|
|
3320
3388
|
return { state, effects: [] };
|
|
3321
3389
|
const agent = clone(existing);
|
|
3322
3390
|
agent.turnActive = false;
|
|
3391
|
+
if (agent.resetting)
|
|
3392
|
+
agent.resetting = false;
|
|
3393
|
+
agent.apm = createInitialApmGatedSteeringState();
|
|
3323
3394
|
if (agent.inbox.length > 0) {
|
|
3324
3395
|
agent.status = "starting";
|
|
3325
3396
|
const prompt = drainInboxToPrompt(agent);
|
|
@@ -3335,7 +3406,7 @@ function onTick(state, nowMs) {
|
|
|
3335
3406
|
const agents = { ...state.agents };
|
|
3336
3407
|
for (const id of Object.keys(agents)) {
|
|
3337
3408
|
const a = agents[id];
|
|
3338
|
-
const stalled = a.status === "running" && a.turnActive && nowMs - a.lastProgressAt >= state.staleThresholdMs && (a.caps.lifecycleKind === "per_turn" || a.caps.supportsStdinNotification && a.caps.busyDeliveryMode === "direct");
|
|
3409
|
+
const stalled = a.status === "running" && a.turnActive && nowMs - a.lastProgressAt >= state.staleThresholdMs && (a.caps.lifecycleKind === "per_turn" || a.caps.supportsStdinNotification && a.caps.busyDeliveryMode === "direct" || a.caps.supportsStdinNotification && a.caps.busyDeliveryMode === "gated" && a.inbox.length > 0);
|
|
3339
3410
|
if (stalled) {
|
|
3340
3411
|
agents[id] = { ...a, status: "stopping", idleSince: null };
|
|
3341
3412
|
effects.push({ type: "terminate_stalled", agentId: id });
|
|
@@ -3359,6 +3430,7 @@ function freshAgent(agentId, caps) {
|
|
|
3359
3430
|
turnActive: false,
|
|
3360
3431
|
lastProgressAt: 0,
|
|
3361
3432
|
idleSince: null,
|
|
3433
|
+
resetting: false,
|
|
3362
3434
|
apm: createInitialApmGatedSteeringState()
|
|
3363
3435
|
};
|
|
3364
3436
|
}
|
|
@@ -3542,6 +3614,7 @@ function nowLocalISO() {
|
|
|
3542
3614
|
|
|
3543
3615
|
// src/manager/managerRuntime.ts
|
|
3544
3616
|
var THINKING_MAX_BYTES = 4096;
|
|
3617
|
+
var STDERR_LOG_MAX_LEN = 2000;
|
|
3545
3618
|
var MAX_TARGET_CODE_UNITS = 200;
|
|
3546
3619
|
function canonicalToolName(rawName) {
|
|
3547
3620
|
const lower = rawName.toLowerCase();
|
|
@@ -3663,10 +3736,11 @@ function pickFallthroughTarget(input) {
|
|
|
3663
3736
|
return rec.name;
|
|
3664
3737
|
return;
|
|
3665
3738
|
}
|
|
3739
|
+
var ALOOK_SHELL_INVOCATION_RE = new RegExp(`^${DEFAULT_CLI_CONFIG.cliName}(\\s|$)`);
|
|
3666
3740
|
function isAlookShellInvocation(command) {
|
|
3667
3741
|
if (!command)
|
|
3668
3742
|
return false;
|
|
3669
|
-
return
|
|
3743
|
+
return ALOOK_SHELL_INVOCATION_RE.test(command.trimStart());
|
|
3670
3744
|
}
|
|
3671
3745
|
function truncateTargetToCodeUnits(s) {
|
|
3672
3746
|
if (s.length <= MAX_TARGET_CODE_UNITS)
|
|
@@ -3759,6 +3833,39 @@ class AgentProcessManager {
|
|
|
3759
3833
|
deliver(agentId, message) {
|
|
3760
3834
|
this.dispatch({ type: "wake", agentId, message, nowMs: this.now() });
|
|
3761
3835
|
}
|
|
3836
|
+
forgetSession(agentId) {
|
|
3837
|
+
this.resumeSessions.delete(agentId);
|
|
3838
|
+
this.liveSessions.delete(agentId);
|
|
3839
|
+
this.dispatch({ type: "reset_session", agentId });
|
|
3840
|
+
this.opts.timeline?.forgetSession(agentId);
|
|
3841
|
+
}
|
|
3842
|
+
enqueueRewake(agentId, message) {
|
|
3843
|
+
this.dispatch({ type: "rewake_after_reset", agentId, message });
|
|
3844
|
+
}
|
|
3845
|
+
markResetting(agentId) {
|
|
3846
|
+
this.dispatch({ type: "begin_reset", agentId });
|
|
3847
|
+
}
|
|
3848
|
+
async resetSession(agentId, opts) {
|
|
3849
|
+
this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
|
|
3850
|
+
this.forgetSession(agentId);
|
|
3851
|
+
this.markResetting(agentId);
|
|
3852
|
+
const status = this.state.agents[agentId]?.status;
|
|
3853
|
+
if (status === "idle") {
|
|
3854
|
+
try {
|
|
3855
|
+
this.deliver(agentId, { text: opts.rewakePrompt });
|
|
3856
|
+
} catch (err) {
|
|
3857
|
+
this.log.error("agent reset idle-branch spawn threw synchronously", {
|
|
3858
|
+
agentId,
|
|
3859
|
+
err: err instanceof Error ? err.message : String(err)
|
|
3860
|
+
});
|
|
3861
|
+
this.dispatch({ type: "exit", agentId });
|
|
3862
|
+
throw err;
|
|
3863
|
+
}
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
this.enqueueRewake(agentId, { text: opts.rewakePrompt });
|
|
3867
|
+
await this.stop(agentId);
|
|
3868
|
+
}
|
|
3762
3869
|
start() {
|
|
3763
3870
|
if (this.tickTimer)
|
|
3764
3871
|
return;
|
|
@@ -3769,7 +3876,7 @@ class AgentProcessManager {
|
|
|
3769
3876
|
const session = this.sessions.get(agentId);
|
|
3770
3877
|
if (!session)
|
|
3771
3878
|
return;
|
|
3772
|
-
await Promise.resolve(session.stop({ reason: "requested", forceAfterMs:
|
|
3879
|
+
await Promise.resolve(session.stop({ reason: "requested", forceAfterMs: SESSION_STOP_GRACE_MS }));
|
|
3773
3880
|
this.sessions.delete(agentId);
|
|
3774
3881
|
}
|
|
3775
3882
|
async stopAll() {
|
|
@@ -3777,7 +3884,7 @@ class AgentProcessManager {
|
|
|
3777
3884
|
clearInterval(this.tickTimer);
|
|
3778
3885
|
this.tickTimer = null;
|
|
3779
3886
|
}
|
|
3780
|
-
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 }))));
|
|
3781
3888
|
this.sessions.clear();
|
|
3782
3889
|
}
|
|
3783
3890
|
snapshot() {
|
|
@@ -3844,7 +3951,7 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3844
3951
|
case "stop":
|
|
3845
3952
|
case "terminate_stalled": {
|
|
3846
3953
|
const session = this.sessions.get(effect.agentId);
|
|
3847
|
-
Promise.resolve(session?.stop({ reason: effect.type, forceAfterMs:
|
|
3954
|
+
Promise.resolve(session?.stop({ reason: effect.type, forceAfterMs: SESSION_STOP_GRACE_MS }));
|
|
3848
3955
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
3849
3956
|
if (spawnState)
|
|
3850
3957
|
spawnState.suppressExitLog = true;
|
|
@@ -3912,6 +4019,11 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3912
4019
|
}
|
|
3913
4020
|
this.onRuntimeEvent(agentId, e, driver.id);
|
|
3914
4021
|
});
|
|
4022
|
+
session.on("stderr", (...args) => {
|
|
4023
|
+
const raw = typeof args[0] === "string" ? args[0] : String(args[0] ?? "");
|
|
4024
|
+
const text = raw.length > STDERR_LOG_MAX_LEN ? raw.slice(0, STDERR_LOG_MAX_LEN) + "…" : raw;
|
|
4025
|
+
this.log.warn("runtime stderr", { agentId, runtime: driver.id, text });
|
|
4026
|
+
});
|
|
3915
4027
|
session.on("error", (...args) => {
|
|
3916
4028
|
const err = args[0];
|
|
3917
4029
|
const code = err?.code ?? "spawn_error";
|
|
@@ -3957,7 +4069,9 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3957
4069
|
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
3958
4070
|
launchId: this.launchIds.get(agentId) ?? null
|
|
3959
4071
|
});
|
|
3960
|
-
} catch {
|
|
4072
|
+
} catch (err) {
|
|
4073
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
4074
|
+
}
|
|
3961
4075
|
}
|
|
3962
4076
|
onRuntimeEvent(agentId, e, runtimeId) {
|
|
3963
4077
|
const ev = e;
|
|
@@ -3982,7 +4096,9 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
3982
4096
|
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
3983
4097
|
launchId: this.launchIds.get(agentId) ?? null
|
|
3984
4098
|
});
|
|
3985
|
-
} catch {
|
|
4099
|
+
} catch (err) {
|
|
4100
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
4101
|
+
}
|
|
3986
4102
|
}
|
|
3987
4103
|
}
|
|
3988
4104
|
}
|
|
@@ -4001,7 +4117,9 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
4001
4117
|
if (ev.kind === "text" && typeof ev.text === "string" && ev.text.length > 0) {
|
|
4002
4118
|
this.opts.timeline?.appendResponseToLatest(agentId, ev.text);
|
|
4003
4119
|
}
|
|
4004
|
-
|
|
4120
|
+
if (ev.kind !== "internal_progress") {
|
|
4121
|
+
this.dispatch({ type: "progress", agentId, nowMs: this.now() });
|
|
4122
|
+
}
|
|
4005
4123
|
this.dispatch({ type: "runtime_signal", agentId, kind: ev.kind, nowMs: this.now() });
|
|
4006
4124
|
if (ev.kind === "turn_end") {
|
|
4007
4125
|
this.logSessionEnded(agentId, "turn_end");
|
|
@@ -4050,6 +4168,7 @@ class UnknownRuntimeError extends Error {
|
|
|
4050
4168
|
function defaultFormatUnreadNoticeText(notice) {
|
|
4051
4169
|
return `You have unread messages in channel ${notice.channel}.`;
|
|
4052
4170
|
}
|
|
4171
|
+
var REWAKE_PROMPT = "Your session was reset by your owner. Prior conversation context is gone. " + "Read @todo.md, @memory.md, and your .context_timeline for anything unfinished, " + "then pull your inbox to catch up on unread messages before doing anything else.";
|
|
4053
4172
|
|
|
4054
4173
|
class AgentRouter {
|
|
4055
4174
|
opts;
|
|
@@ -4228,6 +4347,42 @@ class AgentRouter {
|
|
|
4228
4347
|
return;
|
|
4229
4348
|
}
|
|
4230
4349
|
break;
|
|
4350
|
+
case "agent:reset":
|
|
4351
|
+
this.log.info("agent:reset received", { agentId: cmd.agentId, launchId: cmd.launchId });
|
|
4352
|
+
try {
|
|
4353
|
+
await this.opts.onBeforeAgent?.(cmd.agentId);
|
|
4354
|
+
await this.opts.manager.resetSession(cmd.agentId, {
|
|
4355
|
+
runtimeConfig: cmd.config,
|
|
4356
|
+
launchId: cmd.launchId,
|
|
4357
|
+
rewakePrompt: REWAKE_PROMPT
|
|
4358
|
+
});
|
|
4359
|
+
this.running.add(cmd.agentId);
|
|
4360
|
+
this.scheduleReadyFrameResend();
|
|
4361
|
+
this.log.info("agent:reset ok", { agentId: cmd.agentId });
|
|
4362
|
+
} catch (err) {
|
|
4363
|
+
if (err instanceof UnknownRuntimeError) {
|
|
4364
|
+
const frame = {
|
|
4365
|
+
type: "session.error",
|
|
4366
|
+
code: "runtime_not_available",
|
|
4367
|
+
agentId: cmd.agentId,
|
|
4368
|
+
payload: {
|
|
4369
|
+
requested: err.requested ?? null,
|
|
4370
|
+
available: err.available
|
|
4371
|
+
}
|
|
4372
|
+
};
|
|
4373
|
+
await this.opts.channel.reportSessionError?.(frame);
|
|
4374
|
+
this.log.info("agent:reset error", {
|
|
4375
|
+
agentId: cmd.agentId,
|
|
4376
|
+
"error.code": "runtime_not_available"
|
|
4377
|
+
});
|
|
4378
|
+
return;
|
|
4379
|
+
}
|
|
4380
|
+
this.log.warn("agent:reset failed", {
|
|
4381
|
+
agentId: cmd.agentId,
|
|
4382
|
+
err: err instanceof Error ? err.message : String(err)
|
|
4383
|
+
});
|
|
4384
|
+
}
|
|
4385
|
+
break;
|
|
4231
4386
|
case "agent:stop":
|
|
4232
4387
|
this.log.info("agent:stop received", { agentId: cmd.agentId });
|
|
4233
4388
|
try {
|
|
@@ -4386,7 +4541,7 @@ function parseBearer(authHeader) {
|
|
|
4386
4541
|
var DEFAULT_CAPABILITY_RESOLVER = (_method, pathname) => {
|
|
4387
4542
|
if (pathname.includes("/attachment"))
|
|
4388
4543
|
return "attach";
|
|
4389
|
-
if (pathname.includes("/send"))
|
|
4544
|
+
if (pathname.includes("/send") || pathname.includes("/reactAdd"))
|
|
4390
4545
|
return "send";
|
|
4391
4546
|
if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
|
|
4392
4547
|
return "read";
|
|
@@ -4488,11 +4643,11 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
4488
4643
|
});
|
|
4489
4644
|
req.pipe(upstreamReq);
|
|
4490
4645
|
});
|
|
4491
|
-
await new Promise((
|
|
4646
|
+
await new Promise((resolve3, reject) => {
|
|
4492
4647
|
server.once("error", reject);
|
|
4493
4648
|
server.listen(options.port ?? 0, host, () => {
|
|
4494
4649
|
server.removeListener("error", reject);
|
|
4495
|
-
|
|
4650
|
+
resolve3();
|
|
4496
4651
|
});
|
|
4497
4652
|
});
|
|
4498
4653
|
const addr = server.address();
|
|
@@ -4501,8 +4656,8 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
4501
4656
|
return {
|
|
4502
4657
|
url,
|
|
4503
4658
|
port,
|
|
4504
|
-
close: () => new Promise((
|
|
4505
|
-
server.close(() =>
|
|
4659
|
+
close: () => new Promise((resolve3) => {
|
|
4660
|
+
server.close(() => resolve3());
|
|
4506
4661
|
})
|
|
4507
4662
|
};
|
|
4508
4663
|
}
|
|
@@ -4519,9 +4674,13 @@ function rewriteAgentPath(reqUrl) {
|
|
|
4519
4674
|
return url.pathname + url.search;
|
|
4520
4675
|
}
|
|
4521
4676
|
// src/daemon/createDaemon.ts
|
|
4522
|
-
import { homedir as
|
|
4677
|
+
import { homedir as homedir3 } from "os";
|
|
4523
4678
|
|
|
4524
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;
|
|
4525
4684
|
function describeErr(err) {
|
|
4526
4685
|
return err instanceof Error ? err.message : String(err);
|
|
4527
4686
|
}
|
|
@@ -4631,7 +4790,7 @@ class WsControlChannel {
|
|
|
4631
4790
|
ws.on("message", (data) => this.onMessage(data));
|
|
4632
4791
|
ws.on("pong", () => {
|
|
4633
4792
|
this.attempt = 0;
|
|
4634
|
-
this.pongDeadline = this.now() + (this.opts.heartbeat?.pongTimeoutMs ??
|
|
4793
|
+
this.pongDeadline = this.now() + (this.opts.heartbeat?.pongTimeoutMs ?? DEFAULT_PONG_TIMEOUT_MS);
|
|
4635
4794
|
this.log.debug("heartbeat pong");
|
|
4636
4795
|
});
|
|
4637
4796
|
ws.on("close", (code, reason) => this.onSocketClosed(code, reason));
|
|
@@ -4677,8 +4836,8 @@ class WsControlChannel {
|
|
|
4677
4836
|
this.scheduleReconnect();
|
|
4678
4837
|
}
|
|
4679
4838
|
scheduleReconnect() {
|
|
4680
|
-
const base = this.opts.reconnect?.baseMs ??
|
|
4681
|
-
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;
|
|
4682
4841
|
const maxAttempts = this.opts.reconnect?.maxAttempts ?? Infinity;
|
|
4683
4842
|
if (this.attempt >= maxAttempts) {
|
|
4684
4843
|
this.statusValue = "closed";
|
|
@@ -4691,8 +4850,8 @@ class WsControlChannel {
|
|
|
4691
4850
|
setTimeout(() => this.openSocket(), delayMs);
|
|
4692
4851
|
}
|
|
4693
4852
|
startHeartbeat() {
|
|
4694
|
-
const interval = this.opts.heartbeat?.pingIntervalMs ??
|
|
4695
|
-
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;
|
|
4696
4855
|
this.pongDeadline = this.now() + timeout;
|
|
4697
4856
|
this.pingTimer = setInterval(() => {
|
|
4698
4857
|
if (this.now() > this.pongDeadline) {
|
|
@@ -4823,6 +4982,22 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
4823
4982
|
}
|
|
4824
4983
|
return entries;
|
|
4825
4984
|
}
|
|
4985
|
+
function appendEntry(timelineDir, entry, now = new Date) {
|
|
4986
|
+
const filename = filenameForDate(now);
|
|
4987
|
+
const filePath = join9(timelineDir, filename);
|
|
4988
|
+
const lockPath = lockPathFor(timelineDir, filename);
|
|
4989
|
+
if (!acquireLock(lockPath))
|
|
4990
|
+
return false;
|
|
4991
|
+
try {
|
|
4992
|
+
appendFileSync(filePath, JSON.stringify(entry) + `
|
|
4993
|
+
`);
|
|
4994
|
+
return true;
|
|
4995
|
+
} catch {
|
|
4996
|
+
return false;
|
|
4997
|
+
} finally {
|
|
4998
|
+
releaseLock(lockPath);
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
4826
5001
|
function appendOrMergeEntry(timelineDir, entry, now = new Date) {
|
|
4827
5002
|
const filename = filenameForDate(now);
|
|
4828
5003
|
const filePath = join9(timelineDir, filename);
|
|
@@ -4837,7 +5012,7 @@ function appendOrMergeEntry(timelineDir, entry, now = new Date) {
|
|
|
4837
5012
|
}
|
|
4838
5013
|
if (lines.length > 0) {
|
|
4839
5014
|
const latest = JSON.parse(lines[lines.length - 1]);
|
|
4840
|
-
const mergeable = latest.session_id === entry.session_id && latest.provider === entry.provider && latest.agent_responses.length === 0;
|
|
5015
|
+
const mergeable = !latest.system && !entry.system && latest.session_id === entry.session_id && latest.provider === entry.provider && latest.agent_responses.length === 0;
|
|
4841
5016
|
if (mergeable) {
|
|
4842
5017
|
latest.messages = [...latest.messages, ...entry.messages];
|
|
4843
5018
|
lines[lines.length - 1] = JSON.stringify(latest);
|
|
@@ -4880,7 +5055,10 @@ function updateLatestEntry(timelineDir, updater, opts = {}) {
|
|
|
4880
5055
|
if (lines.length === 0)
|
|
4881
5056
|
continue;
|
|
4882
5057
|
const entries = lines.map((l) => JSON.parse(l));
|
|
4883
|
-
|
|
5058
|
+
const latest = entries[entries.length - 1];
|
|
5059
|
+
if (latest.system)
|
|
5060
|
+
return false;
|
|
5061
|
+
updater(latest);
|
|
4884
5062
|
const tmpPath = join9(timelineDir, `.${filename}.tmp`);
|
|
4885
5063
|
writeFileSync5(tmpPath, entries.map((e) => JSON.stringify(e)).join(`
|
|
4886
5064
|
`) + `
|
|
@@ -4901,9 +5079,20 @@ function createTimelineEntry(fields) {
|
|
|
4901
5079
|
provider: fields.provider ?? null
|
|
4902
5080
|
};
|
|
4903
5081
|
}
|
|
5082
|
+
function createSystemEntry(type, time) {
|
|
5083
|
+
return {
|
|
5084
|
+
session_id: null,
|
|
5085
|
+
messages: [],
|
|
5086
|
+
agent_responses: [],
|
|
5087
|
+
provider: null,
|
|
5088
|
+
system: { type, time }
|
|
5089
|
+
};
|
|
5090
|
+
}
|
|
4904
5091
|
function findResumableSession(rows, provider) {
|
|
4905
5092
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
4906
5093
|
const e = rows[i];
|
|
5094
|
+
if (e.system?.type === "reset_session")
|
|
5095
|
+
return null;
|
|
4907
5096
|
if (!e.session_id)
|
|
4908
5097
|
continue;
|
|
4909
5098
|
if (provider && e.provider !== provider)
|
|
@@ -4913,7 +5102,7 @@ function findResumableSession(rows, provider) {
|
|
|
4913
5102
|
return null;
|
|
4914
5103
|
}
|
|
4915
5104
|
// src/timeline/recorder.ts
|
|
4916
|
-
import { mkdirSync as
|
|
5105
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
4917
5106
|
function createTimelineRecorder(opts) {
|
|
4918
5107
|
const now = opts.now ?? (() => new Date);
|
|
4919
5108
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
@@ -4925,7 +5114,7 @@ function createTimelineRecorder(opts) {
|
|
|
4925
5114
|
appendEntryForAgent(agentId, messages) {
|
|
4926
5115
|
const dir = dirFor(agentId);
|
|
4927
5116
|
try {
|
|
4928
|
-
|
|
5117
|
+
mkdirSync5(dir, { recursive: true });
|
|
4929
5118
|
} catch {}
|
|
4930
5119
|
appendOrMergeEntry(dir, createTimelineEntry({
|
|
4931
5120
|
messages,
|
|
@@ -4934,11 +5123,33 @@ function createTimelineRecorder(opts) {
|
|
|
4934
5123
|
}), now());
|
|
4935
5124
|
},
|
|
4936
5125
|
appendResponseToLatest(agentId, text) {
|
|
4937
|
-
|
|
5126
|
+
const dir = dirFor(agentId);
|
|
5127
|
+
const updated = updateLatestEntry(dir, (e) => e.agent_responses.push(text), { now: now() });
|
|
5128
|
+
if (updated)
|
|
5129
|
+
return;
|
|
5130
|
+
try {
|
|
5131
|
+
mkdirSync5(dir, { recursive: true });
|
|
5132
|
+
} catch {}
|
|
5133
|
+
const entry = createTimelineEntry({
|
|
5134
|
+
messages: [],
|
|
5135
|
+
sessionId: sessionByAgent.get(agentId) ?? null,
|
|
5136
|
+
provider: opts.providerFor?.(agentId) ?? null
|
|
5137
|
+
});
|
|
5138
|
+
entry.agent_responses.push(text);
|
|
5139
|
+
appendEntry(dir, entry, now());
|
|
4938
5140
|
},
|
|
4939
5141
|
resumeSessionId(agentId, provider) {
|
|
4940
5142
|
const rows = readRecentEntries(dirFor(agentId), { now: now() });
|
|
4941
5143
|
return findResumableSession(rows, provider ?? undefined);
|
|
5144
|
+
},
|
|
5145
|
+
forgetSession(agentId) {
|
|
5146
|
+
const dir = dirFor(agentId);
|
|
5147
|
+
try {
|
|
5148
|
+
mkdirSync5(dir, { recursive: true });
|
|
5149
|
+
} catch {}
|
|
5150
|
+
sessionByAgent.delete(agentId);
|
|
5151
|
+
const stamp = now();
|
|
5152
|
+
appendEntry(dir, createSystemEntry("reset_session", stamp.toISOString()), stamp);
|
|
4942
5153
|
}
|
|
4943
5154
|
};
|
|
4944
5155
|
}
|
|
@@ -5024,7 +5235,21 @@ async function importPiSdkFromGlobalInstall() {
|
|
|
5024
5235
|
const pkg = JSON.parse(readFileSync5(path9.join(dir, "package.json"), "utf-8"));
|
|
5025
5236
|
const entry = pkg.exports?.["."]?.import ?? pkg.main ?? "./dist/index.js";
|
|
5026
5237
|
const entryPath = path9.join(dir, entry);
|
|
5027
|
-
|
|
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
|
+
}
|
|
5028
5253
|
}
|
|
5029
5254
|
function loadPiSdkModule() {
|
|
5030
5255
|
if (!cachedSdkPromise) {
|
|
@@ -5067,7 +5292,15 @@ function createPiSdkDriverDeps(ctx, loadSdk = loadPiSdkModule) {
|
|
|
5067
5292
|
const parsed = parseModelString(opts.model);
|
|
5068
5293
|
const model = parsed ? modelRegistry.find(parsed.provider, parsed.id) : undefined;
|
|
5069
5294
|
const cwd = opts.cwd;
|
|
5070
|
-
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
|
+
}
|
|
5071
5304
|
const spawnEnv = opts.spawnEnv;
|
|
5072
5305
|
const bashTool = sdk.createBashToolDefinition(cwd, {
|
|
5073
5306
|
spawnHook: (spawnCtx) => ({ ...spawnCtx, env: { ...spawnCtx.env, ...spawnEnv } })
|
|
@@ -5120,7 +5353,7 @@ function emitImplicitTypingStopOnSend(args) {
|
|
|
5120
5353
|
}
|
|
5121
5354
|
async function createDaemon(opts) {
|
|
5122
5355
|
const log = opts.logger ?? createLogger({ header: "@alook/daemon" });
|
|
5123
|
-
const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${
|
|
5356
|
+
const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${homedir3()}/.alook`) + "/daemon";
|
|
5124
5357
|
const workdirFor = (agentId) => `${opts.workingDirectoryBase ?? fallbackBase}/${agentId}`;
|
|
5125
5358
|
const resolvedCliPath = resolveAlookCliPathWithFallback(opts.agentCliPath);
|
|
5126
5359
|
const timeline2 = createTimelineRecorder({
|
|
@@ -5415,6 +5648,11 @@ async function createDaemon(opts) {
|
|
|
5415
5648
|
await router.start();
|
|
5416
5649
|
return {
|
|
5417
5650
|
isOpen: () => channel.status === "open",
|
|
5651
|
+
onOpen: (hook) => {
|
|
5652
|
+
channel.onOpen(hook);
|
|
5653
|
+
if (channel.status === "open")
|
|
5654
|
+
queueMicrotask(hook);
|
|
5655
|
+
},
|
|
5418
5656
|
proxyUrl: proxy.url,
|
|
5419
5657
|
stop: async () => {
|
|
5420
5658
|
for (const agentId of [...typingHeartbeats.keys()]) {
|