@alook/daemon 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli/index.js +1197 -227
  2. package/dist/index.js +945 -587
  3. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -57,10 +57,13 @@ function cliCommandsSection() {
57
57
  "### Messaging",
58
58
  "",
59
59
  `1. \`${CLI} inbox pull\` — fetch unread messages (advances your read waterline by default, ` + `so they won't re-pull; \`--no-ack\` to peek without advancing).`,
60
- `2. \`${CLI} message send\` — send to a channel, DM, or thread. Attach with ` + `\`--attachment <id>\` (repeatable, order matters).`,
60
+ `2. \`${CLI} message send\` — send to a channel, DM, or thread. Attach with ` + `\`--attachment <id>\` (repeatable, order matters). Optionally add ` + `\`--remind-after <duration>\` to be reminded if the same channel, thread, or DM receives no ` + `newer message after your send. Use a whole number of minutes or hours from \`1m\` to \`24h\`, ` + `such as \`15m\` or \`2h\`.`,
61
61
  `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>\`.`,
62
62
  `4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
63
63
  `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\`).`,
64
+ `6. \`${CLI} message mark set --target <full-message-ref>\` — persist a message as outstanding work.`,
65
+ `7. \`${CLI} message mark remove --target <full-message-ref>\` — clear a completed message mark.`,
66
+ `8. \`${CLI} message mark list\` — list every currently visible marked message with its full content.`,
64
67
  "",
65
68
  "### Servers",
66
69
  "",
@@ -81,7 +84,7 @@ function cliCommandsSection() {
81
84
  "",
82
85
  "### Context Lifecycle",
83
86
  "",
84
- `1. \`${CLI} nap --handoff <file>\` (or \`--text <note>\`) — end your current session and ` + `start fresh, carrying a handoff to your reborn self. Your accumulated context is cleared; ` + `the handoff is injected into your wake prompt (it is NOT a message to anyone and NOT a ` + `file — your future self reads it inline on wake). \`--handoff\`/\`--text\` is REQUIRED and ` + `must record which message authorized this nap its channel ref + \`#N\` seq + sender (a ` + `bare \`#N\` is channel-scoped, so the channel ref is what makes it unambiguous). ` + `Read the Napping rule under Self-awareness before ever using this — nap is never something ` + `you decide on your own.`,
87
+ `1. \`${CLI} nap --handoff <file>\` (or \`--text <note>\`) — reset your current session and ` + `start fresh. The required handoff is injected into the new session so your future self can ` + `quickly pick up unfinished work. Never nap on your own; only do it when someone explicitly asks.`,
85
88
  "",
86
89
  "### Output format",
87
90
  "",
@@ -104,7 +107,7 @@ function messagingSection() {
104
107
  `- Long or complicated: write body to a tmp file, then \`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
105
108
  `- Cite a specific message: \`${CLI} message send --target <ref> --reply "#37" --text "on it"\` — ` + "`--reply` takes the `#N` seq (within `--target`) of the message you're answering.",
106
109
  "",
107
- "### Channel refs",
110
+ "### Context refs",
108
111
  "",
109
112
  "Path-style refs:",
110
113
  "",
@@ -136,15 +139,14 @@ function messagingSection() {
136
139
  "",
137
140
  "### Message formatting",
138
141
  "",
139
- "The app auto-renders inline tokens in a message body — channel refs, @mentions, and message " + "refs. Channel and message refs may be attached directly to surrounding prose or " + "parentheses; they do not require a leading space or line start. Keep @mentions as " + "standalone tokens. **Never wrap refs or mentions in backticks** — that kills the render.",
142
+ "Alook renders specially formatted plain-text refs and mentions in message bodies. Write them " + "as plain text, not inside backticks.",
140
143
  "",
141
- "- **Channel refs** render as clickable links.",
142
- "- **Mentions** — `@name#NNNN` (e.g. `@alice#0001`) notifies that person and highlights the " + "message for them. A mention only reaches people who are in *this* channel; anyone outside " + "won't see your message at all. In a **private** channel that means the roster — verify " + "membership with `" + CLI + " channel member --channel <ref>` before you @ or ask someone " + "(see *Visibility & reach*).",
143
- "- **Message refs** — point at a message by its **full path**: `/<server>/<channel>#N` " + "(message seq N in that channel) or `/<server>/<channel>/#N#M` (message #M in the thread " + "rooted at #N); DMs use `/.dm/<peer>#N`. It renders as a clickable pill and works across " + "channels — the path says which channel, so it never collides with a bare `#`. A bare `#N` " + "on its own does NOT render as a ref; always write the full path. **Never drop a DM ref " + "(`/.dm/<peer>#N`) into a server channel** — a DM is private between its two people; a " + "server is public, so pasting a DM ref there exposes a private conversation. Keep DM refs " + "in DMs.",
144
+ "- **Context refs** use `/<server>/<channel>` for a channel, `/<server>/<channel>#N` for " + "a message, and `/<server>/<channel>/#N#M` for a thread reply; DMs use `/.dm/<peer>` and " + "`/.dm/<peer>#N`. Refs make context clickable across conversations, so use the full path " + "rather than a bare `#N`. Never paste a private DM ref into a server channel.",
145
+ "- **Mentions** — `@name#NNNN` calls that person's attention specifically. In a private " + "channel, first verify they " + `are a member with \`${CLI} channel member --channel <ref>\` before mentioning them.`,
144
146
  "",
145
147
  "```bash",
146
- `${CLI} message send --target "/.dm/alice#0001" --text "Check the discussion in /demo#1234/support"`,
147
- `${CLI} message send --target "/demo#1234/general" --text "@alice#0001 Can you review this? See /demo#1234/general#42"`,
148
+ "# Notify Alice and point her to a message for context.",
149
+ `${CLI} message send --target "/demo#1234/general" --text "@alice#0001 Please review /demo#1234/general#42"`,
148
150
  "```",
149
151
  "",
150
152
  "### Pulled messages",
@@ -180,24 +182,21 @@ function channelTypesSection() {
180
182
  ].join(`
181
183
  `);
182
184
  }
183
- function visibilityAndReachSection() {
185
+ function visibilityAndNotificationsSection() {
184
186
  return [
185
- "## Visibility & reach",
187
+ "## Visibility & notifications",
186
188
  "",
187
- "Two different things decide who's involved in a message don't conflate them. **Access** is " + "who *can see* the channel at all. **Reach** is who, among those, actually gets *notified*.",
189
+ "Membership and access are related but not identical. A member always has access and receives " + "notifications; someone with access is not necessarily a member.",
188
190
  "",
189
- "### Access who can see the channel",
191
+ "- For a regular channel, its members define both who can access it and who receives " + "notifications. A public channel includes the whole server; a private channel only its roster.",
192
+ "- A thread is the exception: it inherits access from its parent channel, but only people " + "participating in the thread are members and receive its notifications.",
193
+ "- To bring someone into a thread discussion, first check that they can access the parent " + "channel; if they can, @mention them inside the thread.",
190
194
  "",
191
- "- **Public channel** — its audience is the whole server. Every server member can read it, and " + "any of them can be @mentioned.",
192
- "- **Private channel** — restricted to an explicit roster. Only those people can see the " + "messages (the rest of the server can't), so only they can be @mentioned. `channel list` " + "marks each channel `public` or `private`.",
193
- "- **Thread** — a side-room rooted at a message, for discussing that message without cluttering " + "the channel. Start one by sending to `/<server>/<channel>/#N` (the thread is created on " + "seq `#N`). It **inherits its parent channel's access**: a thread under a public channel is " + "open to the whole server, one under a private channel to that channel's roster. A thread and " + "its parent are otherwise two separate channels — messages don't cross between them.",
194
- "",
195
- "### Reach — who actually gets notified",
196
- "",
197
- "- In a channel, a message is visible to everyone with access; @mention someone to notify them " + "specifically. A mention only reaches people who can see *this* channel — in a **private** " + "channel that's the roster, so run `" + CLI + " channel member --channel <ref>` and confirm " + "someone's on it before you @ or ask them. Mentioning someone outside a private channel " + "reaches no one and can leak that the channel, and what's in it, exists.",
198
- "- A **thread** notifies only its participants — whoever's been @mentioned in it, has posted in " + "it, or was added. Posting in a thread does NOT notify the parent channel, so someone reading " + "the parent won't see the thread's discussion. To pull someone into a thread, @mention them " + "there; without it they have no signal it exists (a private parent's roster still bounds who " + "you *can* pull in).",
199
- "",
200
- "When unsure whether a channel is private, treat it as private and check the roster first."
195
+ "```bash",
196
+ "# Check the channel's public/private type, then inspect who its members are.",
197
+ `${CLI} channel list --server "demo#1234"`,
198
+ `${CLI} channel member --channel "/demo#1234/team"`,
199
+ "```"
201
200
  ].join(`
202
201
  `);
203
202
  }
@@ -207,7 +206,13 @@ function utilsSection() {
207
206
  "",
208
207
  "### Join a new server",
209
208
  "",
210
- `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."
209
+ `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.",
210
+ "",
211
+ "### Follow up when a conversation goes quiet",
212
+ "",
213
+ `Use \`message send --remind-after <duration>\` when you send something that may need a later ` + "follow-up — for example, a question, approval request, handoff, or blocker — and silence would " + "leave the work unfinished. If no newer message appears in that channel, thread, or DM during " + "the duration, you'll receive a reminder to return and decide what to do next. Don't add it to " + "ordinary messages that need no follow-up.",
214
+ "",
215
+ `Example: \`${CLI} message send --target /demo#1234/team --text "Please review the rollout plan" ` + `--remind-after 1m\`.`
211
216
  ].join(`
212
217
  `);
213
218
  }
@@ -229,9 +234,9 @@ function executionModelSection() {
229
234
  "",
230
235
  "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.",
231
236
  "",
232
- "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).",
237
+ "On wake, restore durable context from `memory.md` and the context timeline, then pull your inbox. " + "Follow *Outstanding work marks* below before taking new work.",
233
238
  "",
234
- "`inbox pull` advances your read waterline by default — pulled messages won't come back in a " + "future pull. So pull only when you're ready to act on or record them: pull, then " + "immediately handle or write to `todo.md`. Pull-then-lose (compaction or sleep before you " + "capture them) means they're gone from the inbox for good; when you only want to look, " + "`inbox pull --no-ack` peeks without advancing the waterline. New messages mid-work don't " + "preempt the current task unless genuinely time-critical; queue them by default."
239
+ "`inbox pull` advances your read waterline by default — pulled messages won't come back in a " + "future pull. A task-bearing message stays durable through its server mark even after the " + "read waterline advances."
235
240
  ].join(`
236
241
  `);
237
242
  }
@@ -253,8 +258,8 @@ function chaosAwarenessSection() {
253
258
  "",
254
259
  "Before you touch a message, make one call: does fulfilling it take work beyond a reply?",
255
260
  "",
256
- "- **Yes — it's a task.** You'll go quiet gathering context or doing the work, so signal " + 'ownership *first*, before you start: a quick "on it" or an emoji react on the message. ' + "That tells the sender it landed and stops anyone else grabbing it. The gap opens the " + "instant you pick up the work; the ack closes it.",
257
- `- **No — it's just an answer.** Answer it. No "on it", no "let me check" — the reply ` + "itself is the acknowledgment.",
261
+ `- **Yes — it's a task.** Signal ownership publicly first: a quick "on it" or an emoji ` + "reaction. Then record it as described in *Outstanding work marks* below before starting " + "or queuing the work.",
262
+ `- **No — it's just an answer.** Answer it without marking. No "on it", no "let me ` + 'check" — the reply itself is the acknowledgment.',
258
263
  "",
259
264
  'Ack once. The signal is "picked up," not a running commentary.'
260
265
  ].join(`
@@ -268,13 +273,11 @@ function workspaceMemorySection() {
268
273
  "",
269
274
  "**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
270
275
  "",
271
- "### Napping",
272
- "",
273
- `\`${CLI} nap\` is a self-reset: it clears your current session and starts a fresh one, ` + "carrying a handoff — a note to your future self — injected into your wake prompt. Leave " + "yourself a note before you sleep.",
276
+ "When context is missing, don't guess. Re-read `memory.md`, the context timeline, the workspace, " + "or relevant channel history. That check *is* your remembering.",
274
277
  "",
275
- "**Hard rule: never nap on your own initiative.** Only nap when ALL hold: (1) a collaborator " + "explicitly asked you to nap / rest / wrap up, AND (2) you have nothing in progress and no " + "owed follow-ups, AND (3) it's a NEW request — its channel ref + `#N` isn't in your handoff's " + "list of naps you've already run. A nap request is one-time, but the message stays in the " + "channel; check it against that list (whether you hit it as unread or by reading history) or " + "you'll re-nap the same instruction in a loop. If any condition is missing, don't. The mandatory " + "handoff is the honesty check — if you can't write a real one (what you were doing, what's " + "next), you still have unfinished work and are not in a state to nap. Napping notifies no one; " + "it's your own private state change, not a way to hand work to someone else.",
278
+ "### Napping",
276
279
  "",
277
- "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.",
280
+ `\`${CLI} nap\` resets your current session and starts a fresh one with your required handoff ` + "injected into it. Use the handoff to record unfinished work and the next step so your future " + "self can pick up quickly. Never nap on your own; only do it when someone explicitly asks.",
278
281
  "",
279
282
  "### memory.md",
280
283
  "",
@@ -292,16 +295,13 @@ function workspaceMemorySection() {
292
295
  "",
293
296
  "`./.context_timeline/YYYY-MM-DD.jsonl` — ordered daily log of what you did. Authoritative history.",
294
297
  "",
295
- "### todo.md",
298
+ "### Outstanding work marks",
296
299
  "",
297
- "You work one thing at a time and won't remember the rest — so when work is owed beyond " + "what you're on right now (unread piling up, a request mid-investigation, a promise not " + "kept), write it here before you start. Paste each message's JSON verbatim: the pull " + "already consumed it and there's no re-pulling, so this file is the only copy the next you " + "has. Ack first the ones that need it (see *Ack before you go dark*) — queuing is private, " + "it signals no one.",
300
+ "Marks are the durable work queue. If a message requires work beyond an immediate reply, " + `acknowledge it, then run \`${CLI} message mark set --target <full-message-ref>\` before ` + "starting or queuing. Do not mark a message you answer immediately.",
298
301
  "",
299
- "It holds only what's still owed: delete a line the instant it's done (never `[x]`), delete " + "the file when empty. Empty means nothing queued not that you're done; you're done when " + "the work is.",
302
+ "Keep it marked while the work is active, queued, or blocked; report blockers where the task " + `came from. Run \`${CLI} message mark remove --target <full-message-ref>\` only after ` + "sending the result, or when the request is cancelled or superseded and nothing remains.",
300
303
  "",
301
- "```md",
302
- '- [ ] {"seq": "#42", "channel": "/demo#1234/general", "sender": "@alice#0001", "content": {"text": "can you pull the latest deploy logs and drop the tail here?"}, "time": "2026-06-01T12:00:00Z"}',
303
- '- [ ] {"seq": "#12", "channel": "/demo#1234/design/#12", "sender": "@alice#0001", "content": {"text": "follow-up — send a screenshot of the before/after"}, "time": "2026-06-01T12:07:00Z"}',
304
- "```"
304
+ "If `inbox pull` returns a `markedReminder`, run " + `\`${CLI} message mark list\` before taking new work. Do not copy marked tasks into local files.`
305
305
  ].join(`
306
306
  `);
307
307
  }
@@ -311,7 +311,7 @@ function buildCliSystemPrompt(config) {
311
311
  cliCommandsSection(),
312
312
  messagingSection(),
313
313
  channelTypesSection(),
314
- visibilityAndReachSection(),
314
+ visibilityAndNotificationsSection(),
315
315
  criticalRulesSection(),
316
316
  executionModelSection(),
317
317
  chaosAwarenessSection(),
@@ -1292,13 +1292,18 @@ function codexSessionRootCandidates(homeDirOrCodexRoot) {
1292
1292
  // src/version.ts
1293
1293
  import { createRequire } from "module";
1294
1294
  var requireFromHere = createRequire(import.meta.url);
1295
+ var PACKAGE_JSON_CANDIDATES = ["../package.json", "../../package.json"];
1295
1296
  function readDaemonVersion() {
1296
- try {
1297
- const pkg = requireFromHere("../package.json");
1298
- return pkg.version ?? "";
1299
- } catch {
1300
- return "";
1297
+ for (const candidate of PACKAGE_JSON_CANDIDATES) {
1298
+ try {
1299
+ const pkg = requireFromHere(candidate);
1300
+ if (typeof pkg.version === "string" && pkg.version.length > 0)
1301
+ return pkg.version;
1302
+ } catch {
1303
+ continue;
1304
+ }
1301
1305
  }
1306
+ return "";
1302
1307
  }
1303
1308
  function getDaemonClientInfo() {
1304
1309
  return { name: "alook-daemon", version: readDaemonVersion() };
@@ -4807,6 +4812,8 @@ ${this.opts.wakePromptFooter}` : text;
4807
4812
  }
4808
4813
  }
4809
4814
  // src/manager/agentRouter.ts
4815
+ var DEFINITIVE_EXECUTABLE_FAILURES = new Set(["ENOENT", "EACCES", "ENOEXEC", "EPERM"]);
4816
+
4810
4817
  class UnknownBotError extends Error {
4811
4818
  botId;
4812
4819
  constructor(botId) {
@@ -4847,14 +4854,14 @@ class UnknownRuntimeError extends Error {
4847
4854
  function defaultFormatUnreadNoticeText(notice) {
4848
4855
  return `You have unread messages in channel ${notice.channel}.`;
4849
4856
  }
4850
- 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.";
4857
+ var REWAKE_PROMPT = "Your session was reset by your owner. Prior conversation context is gone. " + "Read @memory.md and your .context_timeline for durable context, then pull your inbox " + "before doing anything else. If it reports marked messages, run `$ALOOK_CLI message mark list` " + "and resume that outstanding work.";
4851
4858
  var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. Continue any unfinished work.";
4852
4859
  function buildNapRewakePrompt(handoff) {
4853
4860
  return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
4854
4861
 
4855
4862
  ` + handoff.trim() + `
4856
4863
 
4857
- Then read @todo.md, @memory.md, and your .context_timeline for anything ` + "unfinished, and pull your inbox to catch up on unread messages before doing " + "anything else.";
4864
+ Then read @memory.md and your .context_timeline for durable context, and pull ` + "your inbox before doing anything else. If it reports marked messages, run `$ALOOK_CLI " + "message mark list` and resume that outstanding work.";
4858
4865
  }
4859
4866
 
4860
4867
  class AgentRouter {
@@ -4909,6 +4916,11 @@ class AgentRouter {
4909
4916
  isRuntimeHealthy(id) {
4910
4917
  return this.runtimes.get(id)?.status === "healthy";
4911
4918
  }
4919
+ recordRuntimeSpawnFailure(id, reason) {
4920
+ if (!DEFINITIVE_EXECUTABLE_FAILURES.has(reason))
4921
+ return;
4922
+ this.markRuntimeUnhealthy(id, reason);
4923
+ }
4912
4924
  markRuntimeUnhealthy(id, reason) {
4913
4925
  const existing = this.runtimes.get(id);
4914
4926
  if (!existing)
@@ -5167,516 +5179,13 @@ function createTypingScopeTracker() {
5167
5179
  };
5168
5180
  }
5169
5181
  // src/credentials/credentialProxy.ts
5170
- import * as crypto from "crypto";
5182
+ import * as crypto2 from "crypto";
5171
5183
  import * as fs5 from "fs";
5172
5184
  import * as http from "http";
5173
5185
  import * as https from "https";
5174
5186
  import * as os2 from "os";
5175
5187
  import * as path7 from "path";
5176
5188
  import { URL as URL2 } from "url";
5177
- var DEFAULT_HEADER_NAMES = {
5178
- agentId: "X-Agent-Id",
5179
- client: "X-Client",
5180
- capabilities: "X-Agent-Active-Capabilities"
5181
- };
5182
- function randomVoucher(prefix) {
5183
- return prefix + crypto.randomBytes(32).toString("base64url");
5184
- }
5185
- function sanitizeIdSegment(id) {
5186
- return id.replace(/[^A-Za-z0-9._-]/g, "_") || "_";
5187
- }
5188
-
5189
- class CredentialBroker {
5190
- registrations = new Map;
5191
- voucherPrefix;
5192
- voucherDir;
5193
- upstreamBaseUrl;
5194
- clientLabel;
5195
- headerNames;
5196
- constructor(config) {
5197
- if (!config.upstreamBaseUrl)
5198
- throw new Error("CredentialBroker: upstreamBaseUrl is required");
5199
- this.upstreamBaseUrl = config.upstreamBaseUrl.replace(/\/+$/, "");
5200
- this.voucherPrefix = config.voucherPrefix ?? "vch_";
5201
- this.clientLabel = config.clientLabel ?? "cli";
5202
- this.headerNames = config.headerNames ?? DEFAULT_HEADER_NAMES;
5203
- this.voucherDir = config.voucherDir ?? path7.join(os2.tmpdir(), "agent-vouchers");
5204
- }
5205
- mint(agentId, launchId, capabilities, runnerKey) {
5206
- if (!runnerKey)
5207
- throw new Error("CredentialBroker.mint: runnerKey is required (per-agent tier-2 credential)");
5208
- const voucher = randomVoucher(this.voucherPrefix);
5209
- const dir = path7.join(this.voucherDir, sanitizeIdSegment(agentId));
5210
- fs5.mkdirSync(dir, { recursive: true });
5211
- const voucherFile = path7.join(dir, `${sanitizeIdSegment(launchId)}.token`);
5212
- fs5.writeFileSync(voucherFile, voucher, { mode: 384 });
5213
- this.registrations.set(voucher, {
5214
- agentId,
5215
- launchId,
5216
- capabilities: new Set(capabilities),
5217
- voucherFile,
5218
- runnerKey
5219
- });
5220
- return { voucher, agentId, launchId, capabilities: [...capabilities], voucherFile };
5221
- }
5222
- revoke(voucher) {
5223
- const reg = this.registrations.get(voucher);
5224
- if (!reg)
5225
- return false;
5226
- this.registrations.delete(voucher);
5227
- try {
5228
- fs5.rmSync(reg.voucherFile, { force: true });
5229
- } catch {}
5230
- return true;
5231
- }
5232
- revokeAgent(agentId) {
5233
- let n = 0;
5234
- for (const [voucher, reg] of this.registrations) {
5235
- if (reg.agentId === agentId && this.revoke(voucher))
5236
- n++;
5237
- }
5238
- return n;
5239
- }
5240
- get size() {
5241
- return this.registrations.size;
5242
- }
5243
- check(authHeader, requiredCapability) {
5244
- const voucher = parseBearer(authHeader);
5245
- if (!voucher) {
5246
- return { ok: false, status: 401, code: "missing_voucher", error: "missing bearer voucher" };
5247
- }
5248
- const reg = this.registrations.get(voucher);
5249
- if (!reg) {
5250
- return { ok: false, status: 401, code: "invalid_proxy_token", error: "invalid local agent proxy token" };
5251
- }
5252
- if (requiredCapability && !reg.capabilities.has(requiredCapability)) {
5253
- return {
5254
- ok: false,
5255
- status: 403,
5256
- code: "capability_denied",
5257
- error: `capability '${requiredCapability}' not granted to this voucher`
5258
- };
5259
- }
5260
- return { ok: true, reg };
5261
- }
5262
- }
5263
- function parseBearer(authHeader) {
5264
- if (!authHeader)
5265
- return null;
5266
- const m = /^Bearer\s+(.+)$/i.exec(authHeader.trim());
5267
- return m ? m[1].trim() : null;
5268
- }
5269
- var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
5270
- if (pathname.includes("/attachment"))
5271
- return "attach";
5272
- if (/\/friends(\/|$|\?)/.test(pathname))
5273
- return "friend";
5274
- const isMessagesDoor = /\/channels\/[^/]+\/messages(\/|$|\?)/.test(pathname);
5275
- if (isMessagesDoor)
5276
- return method === "GET" ? "read" : "send";
5277
- if (/\/messages\/[^/]+\/reactions\//.test(pathname))
5278
- return "send";
5279
- if (method === "GET" && /\/messages\/[^/]+(\?|$)/.test(pathname))
5280
- return "read";
5281
- if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
5282
- return "read";
5283
- if (pathname.includes("/server") || pathname.includes("/channel"))
5284
- return "server";
5285
- return;
5286
- };
5287
- var DEFAULT_UPSTREAM_TIMEOUT_MS = 20000;
5288
- async function startCredentialProxy(broker, options = {}) {
5289
- const host = options.host ?? "127.0.0.1";
5290
- const resolveCap = options.capabilityResolver ?? DEFAULT_CAPABILITY_RESOLVER;
5291
- const upstream = new URL2(broker.upstreamBaseUrl);
5292
- const upstreamClient = upstream.protocol === "https:" ? https : http;
5293
- const onPull = options.onInboxPullResponse;
5294
- const onProxyRequest = options.onProxyRequest;
5295
- const server = http.createServer((req, res) => {
5296
- const pathname = new URL2(req.url ?? "/", "http://placeholder").pathname;
5297
- const requiredCap = resolveCap(req.method ?? "GET", pathname);
5298
- const verdict = broker.check(req.headers["authorization"], requiredCap);
5299
- if (!verdict.ok) {
5300
- res.writeHead(verdict.status, { "content-type": "application/json" });
5301
- res.end(JSON.stringify({ error: verdict.error, code: verdict.code }));
5302
- req.resume();
5303
- return;
5304
- }
5305
- const canonicalCommunityDoor = /^\/api\/community\/(channels|messages|servers|invites|friends|users|bots)(\/|$)/.test(pathname);
5306
- if (pathname === "/api" || pathname.startsWith("/api/") && !canonicalCommunityDoor) {
5307
- res.writeHead(404, { "content-type": "application/json" });
5308
- res.end(JSON.stringify({ error: "unknown API route", code: "not_found" }));
5309
- req.resume();
5310
- return;
5311
- }
5312
- const reg = verdict.reg;
5313
- const isInboxPull = onPull && pathname === "/api/community/users/me/inbox/pull";
5314
- if (onProxyRequest) {
5315
- try {
5316
- onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
5317
- } catch {}
5318
- }
5319
- const outHeaders = { ...req.headers };
5320
- delete outHeaders["authorization"];
5321
- delete outHeaders["host"];
5322
- delete outHeaders["content-length"];
5323
- outHeaders["authorization"] = `Bearer ${reg.runnerKey}`;
5324
- outHeaders[broker.headerNames.agentId.toLowerCase()] = reg.agentId;
5325
- outHeaders[broker.headerNames.client.toLowerCase()] = broker.clientLabel;
5326
- outHeaders[broker.headerNames.capabilities.toLowerCase()] = [...reg.capabilities].join(",");
5327
- let responded = false;
5328
- let upstreamRes;
5329
- const upstreamReq = upstreamClient.request({
5330
- protocol: upstream.protocol,
5331
- hostname: upstream.hostname,
5332
- port: upstream.port || (upstream.protocol === "https:" ? 443 : 80),
5333
- method: req.method,
5334
- path: joinPath(upstream.pathname, req.url ?? "/"),
5335
- headers: outHeaders
5336
- }, (res_) => {
5337
- responded = true;
5338
- upstreamRes = res_;
5339
- const destroyResIfIncomplete = () => {
5340
- if (!res_.complete)
5341
- res.destroy();
5342
- };
5343
- res_.on("error", destroyResIfIncomplete);
5344
- res_.on("close", destroyResIfIncomplete);
5345
- if (isInboxPull && res_.statusCode && res_.statusCode < 300) {
5346
- const chunks = [];
5347
- res_.on("data", (chunk) => chunks.push(chunk));
5348
- res_.on("end", () => {
5349
- const body = Buffer.concat(chunks);
5350
- res.writeHead(res_.statusCode, res_.headers);
5351
- res.end(body);
5352
- try {
5353
- const parsed = JSON.parse(body.toString());
5354
- if (parsed.messages)
5355
- onPull(reg.agentId, parsed.messages);
5356
- } catch {}
5357
- });
5358
- } else {
5359
- res.writeHead(res_.statusCode ?? 502, res_.headers);
5360
- res_.pipe(res);
5361
- }
5362
- });
5363
- upstreamReq.on("error", (err) => {
5364
- if (responded)
5365
- return;
5366
- responded = true;
5367
- res.writeHead(502, { "content-type": "application/json" });
5368
- res.end(JSON.stringify({ error: `upstream error: ${err.message}`, code: "upstream_error" }));
5369
- });
5370
- const upstreamTimeoutMs = options.upstreamTimeoutMs ?? DEFAULT_UPSTREAM_TIMEOUT_MS;
5371
- upstreamReq.setTimeout(upstreamTimeoutMs, () => {
5372
- upstreamReq.destroy();
5373
- upstreamRes?.destroy();
5374
- if (responded) {
5375
- res.destroy();
5376
- return;
5377
- }
5378
- responded = true;
5379
- res.writeHead(504, { "content-type": "application/json" });
5380
- res.end(JSON.stringify({ error: `upstream request timed out after ${upstreamTimeoutMs}ms`, code: "upstream_timeout" }));
5381
- });
5382
- res.on("close", () => {
5383
- upstreamReq.destroy();
5384
- upstreamRes?.destroy();
5385
- });
5386
- req.pipe(upstreamReq);
5387
- });
5388
- await new Promise((resolve3, reject) => {
5389
- server.once("error", reject);
5390
- server.listen(options.port ?? 0, host, () => {
5391
- server.removeListener("error", reject);
5392
- resolve3();
5393
- });
5394
- });
5395
- const addr = server.address();
5396
- const port = typeof addr === "object" && addr ? addr.port : options.port ?? 0;
5397
- const url = `http://${host}:${port}`;
5398
- return {
5399
- url,
5400
- port,
5401
- close: () => new Promise((resolve3) => {
5402
- server.close(() => resolve3());
5403
- })
5404
- };
5405
- }
5406
- function joinPath(basePath, reqUrl) {
5407
- const base = basePath.replace(/\/+$/, "");
5408
- const reqPath = reqUrl.startsWith("/") ? reqUrl : `/${reqUrl}`;
5409
- return base + reqPath || "/";
5410
- }
5411
- // src/daemon/createDaemon.ts
5412
- import { homedir as homedir3 } from "os";
5413
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
5414
-
5415
- // src/util/rotatingFileSink.ts
5416
- import {
5417
- appendFileSync,
5418
- chmodSync,
5419
- closeSync,
5420
- constants,
5421
- existsSync as existsSync5,
5422
- fstatSync,
5423
- lstatSync as lstatSync2,
5424
- openSync,
5425
- renameSync,
5426
- statSync,
5427
- unlinkSync as unlinkSync3
5428
- } from "node:fs";
5429
- function createRotatingFileSink(path8, maxBytes, opts = {}) {
5430
- const report = (operation, error) => {
5431
- try {
5432
- opts.onError?.({ operation, error });
5433
- } catch {}
5434
- };
5435
- const secureGeneration = (filePath) => {
5436
- if (!existsSync5(filePath))
5437
- return true;
5438
- try {
5439
- const stat = lstatSync2(filePath);
5440
- if (!stat.isFile()) {
5441
- if (opts.mode === undefined && !opts.hardMaxBytes)
5442
- return true;
5443
- report("unsafe_generation", new Error("log generation is not a regular file"));
5444
- return false;
5445
- }
5446
- if (opts.mode !== undefined)
5447
- chmodSync(filePath, opts.mode);
5448
- if (opts.hardMaxBytes && maxBytes > 0 && stat.size > maxBytes) {
5449
- try {
5450
- unlinkSync3(filePath);
5451
- } catch (error) {
5452
- report("oversize_generation", error);
5453
- return false;
5454
- }
5455
- report("oversize_generation", new Error(`removed generation larger than ${maxBytes} bytes`));
5456
- }
5457
- return true;
5458
- } catch (error) {
5459
- report("chmod", error);
5460
- return false;
5461
- }
5462
- };
5463
- const rotate = () => {
5464
- try {
5465
- renameSync(path8, `${path8}.1`);
5466
- if (opts.mode !== undefined)
5467
- chmodSync(`${path8}.1`, opts.mode);
5468
- return true;
5469
- } catch (error) {
5470
- report("rotate", error);
5471
- return false;
5472
- }
5473
- };
5474
- const currentSize = () => {
5475
- try {
5476
- return existsSync5(path8) ? statSync(path8).size : 0;
5477
- } catch (error) {
5478
- report("stat", error);
5479
- return null;
5480
- }
5481
- };
5482
- const sink = {
5483
- secure() {
5484
- return secureGeneration(`${path8}.1`) && secureGeneration(path8);
5485
- },
5486
- write(line) {
5487
- try {
5488
- if (!sink.secure())
5489
- return;
5490
- const serialized = line + `
5491
- `;
5492
- const serializedBytes = Buffer.byteLength(serialized, "utf8");
5493
- if (opts.hardMaxBytes && maxBytes > 0 && serializedBytes > maxBytes) {
5494
- report("oversize", new Error(`record exceeds ${maxBytes} bytes`));
5495
- return;
5496
- }
5497
- const measuredBytes = currentSize();
5498
- if (measuredBytes === null && opts.hardMaxBytes)
5499
- return;
5500
- const currentBytes = measuredBytes ?? 0;
5501
- const shouldRotate = maxBytes > 0 && (opts.hardMaxBytes ? currentBytes > 0 && currentBytes + serializedBytes > maxBytes : currentBytes >= maxBytes);
5502
- if (shouldRotate && !rotate() && opts.hardMaxBytes)
5503
- return;
5504
- appendFileSync(path8, serialized, opts.mode === undefined ? undefined : { mode: opts.mode });
5505
- } catch (error) {
5506
- report("append", error);
5507
- }
5508
- },
5509
- openSnapshot() {
5510
- const files = [];
5511
- try {
5512
- for (const candidate of [`${path8}.1`, path8]) {
5513
- if (!existsSync5(candidate))
5514
- continue;
5515
- if (!secureGeneration(candidate))
5516
- continue;
5517
- const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
5518
- const fd = openSync(candidate, constants.O_RDONLY | noFollow);
5519
- try {
5520
- const stat = fstatSync(fd);
5521
- if (!stat.isFile())
5522
- throw new Error("snapshot source is not a regular file");
5523
- files.push({ path: candidate, fd, size: stat.size });
5524
- } catch (error) {
5525
- closeSync(fd);
5526
- throw error;
5527
- }
5528
- }
5529
- } catch (error) {
5530
- for (const file of files) {
5531
- try {
5532
- closeSync(file.fd);
5533
- } catch {}
5534
- }
5535
- report("snapshot", error);
5536
- return { files: [], close: () => {} };
5537
- }
5538
- let closed = false;
5539
- return {
5540
- files,
5541
- close() {
5542
- if (closed)
5543
- return;
5544
- closed = true;
5545
- for (const file of files) {
5546
- try {
5547
- closeSync(file.fd);
5548
- } catch {}
5549
- }
5550
- }
5551
- };
5552
- }
5553
- };
5554
- return sink;
5555
- }
5556
-
5557
- // src/util/traceSampler.ts
5558
- var SAMPLEABLE_EVENTS = new Set(["tick", "progress", "runtime_signal"]);
5559
- var SACRED_TURN_SPAN_EVENTS = new Set(["turn_begin", "turn_end", "turn_abort"]);
5560
- var DEFAULT_TRACE_SAMPLE_MS = 30000;
5561
- var DEFAULT_TRACE_FILE_MAX_BYTES = 32 * 1024 * 1024;
5562
- function newAgentState() {
5563
- return {
5564
- lastTickKey: null,
5565
- lastTickEmitAt: Number.NEGATIVE_INFINITY,
5566
- pendingTick: null,
5567
- lastProgressEmitAt: Number.NEGATIVE_INFINITY,
5568
- lastSignalPhase: null,
5569
- lastSignalEmitAt: Number.NEGATIVE_INFINITY
5570
- };
5571
- }
5572
- function tickKey(rec) {
5573
- return [
5574
- rec.status,
5575
- rec.turnActive,
5576
- rec.inbox,
5577
- rec.resetting,
5578
- rec.stoppingSince == null ? "s0" : "s1"
5579
- ].join("|");
5580
- }
5581
- function hasEffects(rec) {
5582
- const e = rec.effects;
5583
- return Array.isArray(e) && e.length > 0;
5584
- }
5585
- function nowOf(rec) {
5586
- return typeof rec.nowMs === "number" ? rec.nowMs : 0;
5587
- }
5588
- function createTraceSampler(emit, throttleMs = DEFAULT_TRACE_SAMPLE_MS) {
5589
- const perAgent = new Map;
5590
- const stateFor = (agentId) => {
5591
- let s = perAgent.get(agentId);
5592
- if (!s) {
5593
- s = newAgentState();
5594
- perAgent.set(agentId, s);
5595
- }
5596
- return s;
5597
- };
5598
- const flushPending = (s) => {
5599
- if (s.pendingTick) {
5600
- const p = s.pendingTick;
5601
- s.pendingTick = null;
5602
- s.lastTickEmitAt = nowOf(p);
5603
- emit(p);
5604
- }
5605
- };
5606
- return {
5607
- offer(rec) {
5608
- const agentId = typeof rec.agentId === "string" ? rec.agentId : null;
5609
- if (!agentId) {
5610
- emit(rec);
5611
- return;
5612
- }
5613
- const s = stateFor(agentId);
5614
- const event = rec.event;
5615
- if (rec.recordKind === "turn_span" && SACRED_TURN_SPAN_EVENTS.has(String(event))) {
5616
- flushPending(s);
5617
- emit(rec);
5618
- return;
5619
- }
5620
- if (!SAMPLEABLE_EVENTS.has(event) || hasEffects(rec)) {
5621
- flushPending(s);
5622
- if (event === "tick") {
5623
- s.lastTickKey = tickKey(rec);
5624
- s.lastTickEmitAt = nowOf(rec);
5625
- }
5626
- emit(rec);
5627
- return;
5628
- }
5629
- if (event === "tick") {
5630
- const key = tickKey(rec);
5631
- const now = nowOf(rec);
5632
- if (key !== s.lastTickKey) {
5633
- flushPending(s);
5634
- s.lastTickKey = key;
5635
- s.lastTickEmitAt = now;
5636
- emit(rec);
5637
- return;
5638
- }
5639
- if (now - s.lastTickEmitAt >= throttleMs) {
5640
- s.pendingTick = null;
5641
- s.lastTickEmitAt = now;
5642
- emit(rec);
5643
- } else {
5644
- s.pendingTick = rec;
5645
- }
5646
- return;
5647
- }
5648
- if (event === "progress") {
5649
- const now = nowOf(rec);
5650
- if (now - s.lastProgressEmitAt >= throttleMs) {
5651
- s.lastProgressEmitAt = now;
5652
- emit(rec);
5653
- }
5654
- return;
5655
- }
5656
- if (event === "runtime_signal") {
5657
- const phase = typeof rec.apmPhase === "string" ? rec.apmPhase : "";
5658
- const now = nowOf(rec);
5659
- if (phase !== s.lastSignalPhase || now - s.lastSignalEmitAt >= throttleMs) {
5660
- s.lastSignalPhase = phase;
5661
- s.lastSignalEmitAt = now;
5662
- emit(rec);
5663
- }
5664
- return;
5665
- }
5666
- emit(rec);
5667
- }
5668
- };
5669
- }
5670
-
5671
- // src/util/statusFile.ts
5672
- import { writeFileSync as writeFileSync4, renameSync as renameSync2 } from "node:fs";
5673
- function writeStatusFile(path8, snapshot) {
5674
- try {
5675
- const tmp = `${path8}.tmp`;
5676
- writeFileSync4(tmp, JSON.stringify(snapshot));
5677
- renameSync2(tmp, path8);
5678
- } catch {}
5679
- }
5680
5189
 
5681
5190
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
5682
5191
  var exports_external = {};
@@ -6442,10 +5951,10 @@ function mergeDefs(...defs) {
6442
5951
  function cloneDef(schema) {
6443
5952
  return mergeDefs(schema._zod.def);
6444
5953
  }
6445
- function getElementAtPath(obj, path8) {
6446
- if (!path8)
5954
+ function getElementAtPath(obj, path7) {
5955
+ if (!path7)
6447
5956
  return obj;
6448
- return path8.reduce((acc, key) => acc?.[key], obj);
5957
+ return path7.reduce((acc, key) => acc?.[key], obj);
6449
5958
  }
6450
5959
  function promiseAllObject(promisesObj) {
6451
5960
  const keys = Object.keys(promisesObj);
@@ -6853,11 +6362,11 @@ function explicitlyAborted(x, startIndex = 0) {
6853
6362
  }
6854
6363
  return false;
6855
6364
  }
6856
- function prefixIssues(path8, issues) {
6365
+ function prefixIssues(path7, issues) {
6857
6366
  return issues.map((iss) => {
6858
6367
  var _a2;
6859
6368
  (_a2 = iss).path ?? (_a2.path = []);
6860
- iss.path.unshift(path8);
6369
+ iss.path.unshift(path7);
6861
6370
  return iss;
6862
6371
  });
6863
6372
  }
@@ -7004,16 +6513,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
7004
6513
  }
7005
6514
  function formatError(error, mapper = (issue2) => issue2.message) {
7006
6515
  const fieldErrors = { _errors: [] };
7007
- const processError = (error2, path8 = []) => {
6516
+ const processError = (error2, path7 = []) => {
7008
6517
  for (const issue2 of error2.issues) {
7009
6518
  if (issue2.code === "invalid_union" && issue2.errors.length) {
7010
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
6519
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
7011
6520
  } else if (issue2.code === "invalid_key") {
7012
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6521
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7013
6522
  } else if (issue2.code === "invalid_element") {
7014
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6523
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7015
6524
  } else {
7016
- const fullpath = [...path8, ...issue2.path];
6525
+ const fullpath = [...path7, ...issue2.path];
7017
6526
  if (fullpath.length === 0) {
7018
6527
  fieldErrors._errors.push(mapper(issue2));
7019
6528
  } else {
@@ -7040,17 +6549,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
7040
6549
  }
7041
6550
  function treeifyError(error, mapper = (issue2) => issue2.message) {
7042
6551
  const result = { errors: [] };
7043
- const processError = (error2, path8 = []) => {
6552
+ const processError = (error2, path7 = []) => {
7044
6553
  var _a2, _b;
7045
6554
  for (const issue2 of error2.issues) {
7046
6555
  if (issue2.code === "invalid_union" && issue2.errors.length) {
7047
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
6556
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
7048
6557
  } else if (issue2.code === "invalid_key") {
7049
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6558
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7050
6559
  } else if (issue2.code === "invalid_element") {
7051
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6560
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7052
6561
  } else {
7053
- const fullpath = [...path8, ...issue2.path];
6562
+ const fullpath = [...path7, ...issue2.path];
7054
6563
  if (fullpath.length === 0) {
7055
6564
  result.errors.push(mapper(issue2));
7056
6565
  continue;
@@ -7082,8 +6591,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
7082
6591
  }
7083
6592
  function toDotPath(_path) {
7084
6593
  const segs = [];
7085
- const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
7086
- for (const seg of path8) {
6594
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
6595
+ for (const seg of path7) {
7087
6596
  if (typeof seg === "number")
7088
6597
  segs.push(`[${seg}]`);
7089
6598
  else if (typeof seg === "symbol")
@@ -19542,13 +19051,13 @@ function resolveRef(ref, ctx) {
19542
19051
  if (!ref.startsWith("#")) {
19543
19052
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
19544
19053
  }
19545
- const path8 = ref.slice(1).split("/").filter(Boolean);
19546
- if (path8.length === 0) {
19054
+ const path7 = ref.slice(1).split("/").filter(Boolean);
19055
+ if (path7.length === 0) {
19547
19056
  return ctx.rootSchema;
19548
19057
  }
19549
19058
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
19550
- if (path8[0] === defsKey) {
19551
- const key = path8[1];
19059
+ if (path7[0] === defsKey) {
19060
+ const key = path7[1];
19552
19061
  if (!key || !ctx.defs[key]) {
19553
19062
  throw new Error(`Reference not found: ${ref}`);
19554
19063
  }
@@ -19955,6 +19464,16 @@ function date4(params) {
19955
19464
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
19956
19465
  config(en_default());
19957
19466
  // ../shared/src/lib/discriminator.ts
19467
+ function parseNameAndTag(q) {
19468
+ const m = /^(.+)#(\d{4,})$/.exec(q);
19469
+ if (!m)
19470
+ return null;
19471
+ const name = m[1].trim();
19472
+ const discriminator = m[2];
19473
+ if (!name)
19474
+ return null;
19475
+ return { name, discriminator };
19476
+ }
19958
19477
  function formatHandle(name, discriminator) {
19959
19478
  return `${name}#${discriminator}`;
19960
19479
  }
@@ -20545,7 +20064,7 @@ function sql(strings, ...params) {
20545
20064
  return new SQL([new StringChunk(str)]);
20546
20065
  }
20547
20066
  sql2.raw = raw;
20548
- function join9(chunks, separator) {
20067
+ function join8(chunks, separator) {
20549
20068
  const result = [];
20550
20069
  for (const [i, chunk] of chunks.entries()) {
20551
20070
  if (i > 0 && separator !== undefined) {
@@ -20555,7 +20074,7 @@ function sql(strings, ...params) {
20555
20074
  }
20556
20075
  return new SQL(result);
20557
20076
  }
20558
- sql2.join = join9;
20077
+ sql2.join = join8;
20559
20078
  function identifier(value) {
20560
20079
  return new Name(value);
20561
20080
  }
@@ -21333,7 +20852,7 @@ class PrimaryKey {
21333
20852
  }
21334
20853
 
21335
20854
  // ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/index.js
21336
- import { webcrypto as crypto2 } from "node:crypto";
20855
+ import { webcrypto as crypto } from "node:crypto";
21337
20856
 
21338
20857
  // ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/url-alphabet/index.js
21339
20858
  var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
@@ -21348,10 +20867,10 @@ function fillPool(bytes) {
21348
20867
  try {
21349
20868
  if (!pool || pool.length < bytes) {
21350
20869
  pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
21351
- crypto2.getRandomValues(pool);
20870
+ crypto.getRandomValues(pool);
21352
20871
  poolOffset = 0;
21353
20872
  } else if (poolOffset + bytes > pool.length) {
21354
- crypto2.getRandomValues(pool);
20873
+ crypto.getRandomValues(pool);
21355
20874
  poolOffset = 0;
21356
20875
  }
21357
20876
  } catch (e) {
@@ -22188,6 +21707,81 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
22188
21707
  ]);
22189
21708
 
22190
21709
  // ../shared/src/community-cli-contract.ts
21710
+ var DM_SERVER = ".dm";
21711
+ function parseRef(ref) {
21712
+ if (!ref.startsWith("/"))
21713
+ throw new Error(`ref must start with "/": ${ref}`);
21714
+ const body = ref.slice(1);
21715
+ const parts = body.split("/");
21716
+ if (parts.length < 2)
21717
+ throw new Error(`ref needs /<server>/<channel>: ${ref}`);
21718
+ const server = parts[0];
21719
+ if (server !== DM_SERVER && !parseNameAndTag(server)) {
21720
+ throw new Error(`server ref must use a name#discriminator handle: ${ref}`);
21721
+ }
21722
+ let seq;
21723
+ if (parts.length >= 3 && parts[parts.length - 1].startsWith("#")) {
21724
+ const tail = parseThreadTail(parts[parts.length - 1]);
21725
+ return { server, channel: parts[1], ...tail };
21726
+ }
21727
+ if (parts.length >= 3 && server !== DM_SERVER) {
21728
+ throw new Error(`ref has too many segments: ${ref}`);
21729
+ }
21730
+ const chSeg = parts[1];
21731
+ if (server === DM_SERVER) {
21732
+ const lastHash = chSeg.lastIndexOf("#");
21733
+ if (lastHash < 0)
21734
+ return { server, channel: chSeg };
21735
+ const firstHash = chSeg.indexOf("#");
21736
+ const tail = chSeg.slice(lastHash + 1);
21737
+ const isBareHandle = firstHash === lastHash && /^\d{4,}$/.test(tail);
21738
+ if (isBareHandle)
21739
+ return { server, channel: chSeg };
21740
+ const tailNum = Number(tail.startsWith("#") ? tail.slice(1) : tail);
21741
+ if (!Number.isFinite(tailNum))
21742
+ return { server, channel: chSeg };
21743
+ seq = parseSeq(tail);
21744
+ return { server, channel: chSeg.slice(0, lastHash), seq };
21745
+ }
21746
+ const hashIdx = chSeg.indexOf("#");
21747
+ if (hashIdx >= 0) {
21748
+ seq = parseSeq(chSeg.slice(hashIdx));
21749
+ return { server, channel: chSeg.slice(0, hashIdx), seq };
21750
+ }
21751
+ return { server, channel: chSeg };
21752
+ }
21753
+ function parseThreadTail(segment) {
21754
+ const stripped = segment.startsWith("#") ? segment.slice(1) : segment;
21755
+ const tokens = stripped.split("#");
21756
+ if (tokens.length < 1 || tokens.length > 2) {
21757
+ throw new Error(`bad thread ref tail: #${stripped}`);
21758
+ }
21759
+ for (const t of tokens) {
21760
+ if (!t)
21761
+ throw new Error(`bad thread ref tail: #${stripped} (empty seq)`);
21762
+ }
21763
+ const threadRootSeq = parseSeq(tokens[0]);
21764
+ if (tokens.length === 1)
21765
+ return { threadRootSeq };
21766
+ return { threadRootSeq, seq: parseSeq(tokens[1]) };
21767
+ }
21768
+ function formatRef(p) {
21769
+ if (p.seq !== undefined && p.threadRootSeq === undefined) {
21770
+ throw new Error("formatRef: seq without threadRootSeq is not supported");
21771
+ }
21772
+ const base = `/${p.server}/${p.channel}`;
21773
+ if (p.threadRootSeq === undefined)
21774
+ return base;
21775
+ if (p.seq === undefined)
21776
+ return `${base}/#${p.threadRootSeq}`;
21777
+ return `${base}/#${p.threadRootSeq}#${p.seq}`;
21778
+ }
21779
+ function parseSeq(s) {
21780
+ const n = Number(s.startsWith("#") ? s.slice(1) : s);
21781
+ if (!Number.isFinite(n))
21782
+ throw new Error(`bad seq: ${s}`);
21783
+ return n;
21784
+ }
22191
21785
  var HostCommandSchema = exports_external.discriminatedUnion("type", [
22192
21786
  exports_external.object({
22193
21787
  type: exports_external.literal("agent:wake"),
@@ -22228,6 +21822,9 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
22228
21822
  launchId: exports_external.string().min(1)
22229
21823
  }))
22230
21824
  }),
21825
+ exports_external.strictObject({
21826
+ type: exports_external.literal("machine:update")
21827
+ }),
22231
21828
  exports_external.object({
22232
21829
  type: exports_external.literal("bot:added"),
22233
21830
  botId: exports_external.string().min(1),
@@ -22252,14 +21849,639 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
22252
21849
  }),
22253
21850
  DiagnosticCollectCommandSchema
22254
21851
  ]);
22255
- // src/server/wsControlChannel.ts
22256
- var WS_CONTROL_COMMAND_CONSUMED = Symbol("ws-control-command-consumed");
22257
- var DEFAULT_PING_INTERVAL_MS = 15000;
22258
- var DEFAULT_PONG_TIMEOUT_MS = 30000;
22259
- var DEFAULT_RECONNECT_BASE_MS = 500;
22260
- var DEFAULT_RECONNECT_MAX_MS = 30000;
22261
- function describeErr(err) {
22262
- return err instanceof Error ? err.message : String(err);
21852
+ // src/credentials/credentialProxy.ts
21853
+ var LOCAL_MESSAGE_REMINDER_PATH = "/__alook/local/message-reminder";
21854
+ var LOCAL_MESSAGE_REMINDER_BODY_MAX_BYTES = 4 * 1024;
21855
+ var LOCAL_MESSAGE_REMINDER_MIN_MS = 60000;
21856
+ var LOCAL_MESSAGE_REMINDER_MAX_MS = 24 * 60 * 60000;
21857
+ var DEFAULT_HEADER_NAMES = {
21858
+ agentId: "X-Agent-Id",
21859
+ client: "X-Client",
21860
+ capabilities: "X-Agent-Active-Capabilities"
21861
+ };
21862
+ function randomVoucher(prefix) {
21863
+ return prefix + crypto2.randomBytes(32).toString("base64url");
21864
+ }
21865
+ function sanitizeIdSegment(id) {
21866
+ return id.replace(/[^A-Za-z0-9._-]/g, "_") || "_";
21867
+ }
21868
+
21869
+ class CredentialBroker {
21870
+ registrations = new Map;
21871
+ voucherPrefix;
21872
+ voucherDir;
21873
+ upstreamBaseUrl;
21874
+ clientLabel;
21875
+ headerNames;
21876
+ constructor(config2) {
21877
+ if (!config2.upstreamBaseUrl)
21878
+ throw new Error("CredentialBroker: upstreamBaseUrl is required");
21879
+ this.upstreamBaseUrl = config2.upstreamBaseUrl.replace(/\/+$/, "");
21880
+ this.voucherPrefix = config2.voucherPrefix ?? "vch_";
21881
+ this.clientLabel = config2.clientLabel ?? "cli";
21882
+ this.headerNames = config2.headerNames ?? DEFAULT_HEADER_NAMES;
21883
+ this.voucherDir = config2.voucherDir ?? path7.join(os2.tmpdir(), "agent-vouchers");
21884
+ }
21885
+ mint(agentId, launchId, capabilities, runnerKey) {
21886
+ if (!runnerKey)
21887
+ throw new Error("CredentialBroker.mint: runnerKey is required (per-agent tier-2 credential)");
21888
+ const voucher = randomVoucher(this.voucherPrefix);
21889
+ const dir = path7.join(this.voucherDir, sanitizeIdSegment(agentId));
21890
+ fs5.mkdirSync(dir, { recursive: true });
21891
+ const voucherFile = path7.join(dir, `${sanitizeIdSegment(launchId)}.token`);
21892
+ fs5.writeFileSync(voucherFile, voucher, { mode: 384 });
21893
+ this.registrations.set(voucher, {
21894
+ agentId,
21895
+ launchId,
21896
+ capabilities: new Set(capabilities),
21897
+ voucherFile,
21898
+ runnerKey
21899
+ });
21900
+ return { voucher, agentId, launchId, capabilities: [...capabilities], voucherFile };
21901
+ }
21902
+ revoke(voucher) {
21903
+ const reg = this.registrations.get(voucher);
21904
+ if (!reg)
21905
+ return false;
21906
+ this.registrations.delete(voucher);
21907
+ try {
21908
+ fs5.rmSync(reg.voucherFile, { force: true });
21909
+ } catch {}
21910
+ return true;
21911
+ }
21912
+ revokeAgent(agentId) {
21913
+ let n = 0;
21914
+ for (const [voucher, reg] of this.registrations) {
21915
+ if (reg.agentId === agentId && this.revoke(voucher))
21916
+ n++;
21917
+ }
21918
+ return n;
21919
+ }
21920
+ get size() {
21921
+ return this.registrations.size;
21922
+ }
21923
+ check(authHeader, requiredCapability) {
21924
+ const voucher = parseBearer(authHeader);
21925
+ if (!voucher) {
21926
+ return { ok: false, status: 401, code: "missing_voucher", error: "missing bearer voucher" };
21927
+ }
21928
+ const reg = this.registrations.get(voucher);
21929
+ if (!reg) {
21930
+ return { ok: false, status: 401, code: "invalid_proxy_token", error: "invalid local agent proxy token" };
21931
+ }
21932
+ if (requiredCapability && !reg.capabilities.has(requiredCapability)) {
21933
+ return {
21934
+ ok: false,
21935
+ status: 403,
21936
+ code: "capability_denied",
21937
+ error: `capability '${requiredCapability}' not granted to this voucher`
21938
+ };
21939
+ }
21940
+ return { ok: true, reg };
21941
+ }
21942
+ }
21943
+ function parseBearer(authHeader) {
21944
+ if (!authHeader)
21945
+ return null;
21946
+ const m = /^Bearer\s+(.+)$/i.exec(authHeader.trim());
21947
+ return m ? m[1].trim() : null;
21948
+ }
21949
+ var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
21950
+ if (pathname.includes("/attachment"))
21951
+ return "attach";
21952
+ if (/\/friends(\/|$|\?)/.test(pathname))
21953
+ return "friend";
21954
+ const isMessagesDoor = /\/channels\/[^/]+\/messages(\/|$|\?)/.test(pathname);
21955
+ if (isMessagesDoor)
21956
+ return method === "GET" ? "read" : "send";
21957
+ if (/\/messages\/[^/]+\/reactions\//.test(pathname))
21958
+ return "send";
21959
+ if ((method === "PUT" || method === "DELETE") && /\/messages\/[^/]+\/marks(\/|$|\?)/.test(pathname))
21960
+ return "send";
21961
+ if (method === "GET" && /\/users\/me\/marks(\/|$|\?)/.test(pathname))
21962
+ return "read";
21963
+ if (method === "GET" && /\/messages\/[^/]+(\?|$)/.test(pathname))
21964
+ return "read";
21965
+ if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
21966
+ return "read";
21967
+ if (pathname.includes("/server") || pathname.includes("/channel"))
21968
+ return "server";
21969
+ return;
21970
+ };
21971
+ var DEFAULT_UPSTREAM_TIMEOUT_MS = 20000;
21972
+ function writeJson(res, status, body) {
21973
+ res.writeHead(status, { "content-type": "application/json" });
21974
+ res.end(JSON.stringify(body));
21975
+ }
21976
+ function isCanonicalChannelScope(channel2) {
21977
+ try {
21978
+ const parsed = parseRef(channel2);
21979
+ if (!parsed.channel || parsed.seq !== undefined)
21980
+ return false;
21981
+ if (parsed.threadRootSeq !== undefined && (!Number.isSafeInteger(parsed.threadRootSeq) || parsed.threadRootSeq < 1)) {
21982
+ return false;
21983
+ }
21984
+ const handle = parseNameAndTag(parsed.server === ".dm" ? parsed.channel : parsed.server);
21985
+ if (!handle || `${handle.name}#${handle.discriminator}` !== (parsed.server === ".dm" ? parsed.channel : parsed.server)) {
21986
+ return false;
21987
+ }
21988
+ return formatRef(parsed) === channel2;
21989
+ } catch {
21990
+ return false;
21991
+ }
21992
+ }
21993
+ function parseLocalMessageReminderBody(body, agentId) {
21994
+ let value;
21995
+ try {
21996
+ value = JSON.parse(body.toString("utf8"));
21997
+ } catch {
21998
+ return null;
21999
+ }
22000
+ if (!value || typeof value !== "object" || Array.isArray(value))
22001
+ return null;
22002
+ const record2 = value;
22003
+ if (Object.keys(record2).sort().join(",") !== "channel,remindAfterMs,sentSeq")
22004
+ return null;
22005
+ if (typeof record2.channel !== "string" || !isCanonicalChannelScope(record2.channel))
22006
+ return null;
22007
+ if (!Number.isSafeInteger(record2.sentSeq) || record2.sentSeq < 1)
22008
+ return null;
22009
+ if (!Number.isSafeInteger(record2.remindAfterMs) || record2.remindAfterMs < LOCAL_MESSAGE_REMINDER_MIN_MS || record2.remindAfterMs > LOCAL_MESSAGE_REMINDER_MAX_MS)
22010
+ return null;
22011
+ return {
22012
+ agentId,
22013
+ channel: record2.channel,
22014
+ sentSeq: record2.sentSeq,
22015
+ remindAfterMs: record2.remindAfterMs
22016
+ };
22017
+ }
22018
+ async function handleLocalMessageReminder(req, res, agentId, onArm) {
22019
+ const contentType = req.headers["content-type"] ?? "";
22020
+ if (!contentType.toLowerCase().startsWith("application/json")) {
22021
+ writeJson(res, 415, { error: "content-type must be application/json", code: "unsupported_media_type" });
22022
+ req.resume();
22023
+ return;
22024
+ }
22025
+ const declaredLength = Number(req.headers["content-length"] ?? 0);
22026
+ if (!Number.isFinite(declaredLength) || declaredLength > LOCAL_MESSAGE_REMINDER_BODY_MAX_BYTES) {
22027
+ writeJson(res, 413, { error: "request body too large", code: "body_too_large" });
22028
+ req.resume();
22029
+ return;
22030
+ }
22031
+ const chunks = [];
22032
+ let bytes = 0;
22033
+ let tooLarge = false;
22034
+ await new Promise((resolve3) => {
22035
+ req.on("data", (chunk) => {
22036
+ bytes += chunk.byteLength;
22037
+ if (bytes > LOCAL_MESSAGE_REMINDER_BODY_MAX_BYTES) {
22038
+ tooLarge = true;
22039
+ } else {
22040
+ chunks.push(chunk);
22041
+ }
22042
+ });
22043
+ req.on("end", resolve3);
22044
+ req.on("error", resolve3);
22045
+ });
22046
+ if (tooLarge) {
22047
+ writeJson(res, 413, { error: "request body too large", code: "body_too_large" });
22048
+ return;
22049
+ }
22050
+ const input = parseLocalMessageReminderBody(Buffer.concat(chunks), agentId);
22051
+ if (!input) {
22052
+ writeJson(res, 400, { error: "invalid local message reminder request", code: "invalid_request" });
22053
+ return;
22054
+ }
22055
+ if (!onArm) {
22056
+ writeJson(res, 503, { error: "local message reminder unavailable", code: "reminder_unavailable" });
22057
+ return;
22058
+ }
22059
+ try {
22060
+ writeJson(res, 200, await onArm(input));
22061
+ } catch {
22062
+ writeJson(res, 500, { error: "local message reminder failed", code: "reminder_failed" });
22063
+ }
22064
+ }
22065
+ async function startCredentialProxy(broker, options = {}) {
22066
+ const host = options.host ?? "127.0.0.1";
22067
+ const resolveCap = options.capabilityResolver ?? DEFAULT_CAPABILITY_RESOLVER;
22068
+ const upstream = new URL2(broker.upstreamBaseUrl);
22069
+ const upstreamClient = upstream.protocol === "https:" ? https : http;
22070
+ const onPull = options.onInboxPullResponse;
22071
+ const onProxyRequest = options.onProxyRequest;
22072
+ const server = http.createServer((req, res) => {
22073
+ const pathname = new URL2(req.url ?? "/", "http://placeholder").pathname;
22074
+ if (pathname.startsWith("/__alook/local/")) {
22075
+ if (req.url !== LOCAL_MESSAGE_REMINDER_PATH) {
22076
+ writeJson(res, 404, { error: "unknown local route", code: "not_found" });
22077
+ req.resume();
22078
+ return;
22079
+ }
22080
+ if (req.method !== "PUT") {
22081
+ writeJson(res, 405, { error: "method not allowed", code: "method_not_allowed" });
22082
+ req.resume();
22083
+ return;
22084
+ }
22085
+ const localVerdict = broker.check(req.headers["authorization"], "send");
22086
+ if (!localVerdict.ok) {
22087
+ writeJson(res, localVerdict.status, { error: localVerdict.error, code: localVerdict.code });
22088
+ req.resume();
22089
+ return;
22090
+ }
22091
+ handleLocalMessageReminder(req, res, localVerdict.reg.agentId, options.onMessageReminderArm);
22092
+ return;
22093
+ }
22094
+ const requiredCap = resolveCap(req.method ?? "GET", pathname);
22095
+ const verdict = broker.check(req.headers["authorization"], requiredCap);
22096
+ if (!verdict.ok) {
22097
+ res.writeHead(verdict.status, { "content-type": "application/json" });
22098
+ res.end(JSON.stringify({ error: verdict.error, code: verdict.code }));
22099
+ req.resume();
22100
+ return;
22101
+ }
22102
+ const canonicalCommunityDoor = /^\/api\/community\/(channels|messages|servers|invites|friends|users|bots)(\/|$)/.test(pathname);
22103
+ if (pathname === "/api" || pathname.startsWith("/api/") && !canonicalCommunityDoor) {
22104
+ res.writeHead(404, { "content-type": "application/json" });
22105
+ res.end(JSON.stringify({ error: "unknown API route", code: "not_found" }));
22106
+ req.resume();
22107
+ return;
22108
+ }
22109
+ const reg = verdict.reg;
22110
+ const isInboxPull = onPull && pathname === "/api/community/users/me/inbox/pull";
22111
+ if (onProxyRequest) {
22112
+ try {
22113
+ onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
22114
+ } catch {}
22115
+ }
22116
+ const outHeaders = { ...req.headers };
22117
+ delete outHeaders["authorization"];
22118
+ delete outHeaders["host"];
22119
+ outHeaders["authorization"] = `Bearer ${reg.runnerKey}`;
22120
+ outHeaders[broker.headerNames.agentId.toLowerCase()] = reg.agentId;
22121
+ outHeaders[broker.headerNames.client.toLowerCase()] = broker.clientLabel;
22122
+ outHeaders[broker.headerNames.capabilities.toLowerCase()] = [...reg.capabilities].join(",");
22123
+ let responded = false;
22124
+ let upstreamRes;
22125
+ const upstreamReq = upstreamClient.request({
22126
+ protocol: upstream.protocol,
22127
+ hostname: upstream.hostname,
22128
+ port: upstream.port || (upstream.protocol === "https:" ? 443 : 80),
22129
+ method: req.method,
22130
+ path: joinPath(upstream.pathname, req.url ?? "/"),
22131
+ headers: outHeaders
22132
+ }, (res_) => {
22133
+ responded = true;
22134
+ upstreamRes = res_;
22135
+ const destroyResIfIncomplete = () => {
22136
+ if (!res_.complete)
22137
+ res.destroy();
22138
+ };
22139
+ res_.on("error", destroyResIfIncomplete);
22140
+ res_.on("close", destroyResIfIncomplete);
22141
+ if (isInboxPull && res_.statusCode && res_.statusCode < 300) {
22142
+ const chunks = [];
22143
+ res_.on("data", (chunk) => chunks.push(chunk));
22144
+ res_.on("end", () => {
22145
+ const body = Buffer.concat(chunks);
22146
+ res.writeHead(res_.statusCode, res_.headers);
22147
+ res.end(body);
22148
+ try {
22149
+ const parsed = JSON.parse(body.toString());
22150
+ if (parsed.messages)
22151
+ onPull(reg.agentId, parsed.messages);
22152
+ } catch {}
22153
+ });
22154
+ } else {
22155
+ res.writeHead(res_.statusCode ?? 502, res_.headers);
22156
+ res_.pipe(res);
22157
+ }
22158
+ });
22159
+ upstreamReq.on("error", (err) => {
22160
+ if (responded)
22161
+ return;
22162
+ responded = true;
22163
+ res.writeHead(502, { "content-type": "application/json" });
22164
+ res.end(JSON.stringify({ error: `upstream error: ${err.message}`, code: "upstream_error" }));
22165
+ });
22166
+ const upstreamTimeoutMs = options.upstreamTimeoutMs ?? DEFAULT_UPSTREAM_TIMEOUT_MS;
22167
+ upstreamReq.setTimeout(upstreamTimeoutMs, () => {
22168
+ upstreamReq.destroy();
22169
+ upstreamRes?.destroy();
22170
+ if (responded) {
22171
+ res.destroy();
22172
+ return;
22173
+ }
22174
+ responded = true;
22175
+ res.writeHead(504, { "content-type": "application/json" });
22176
+ res.end(JSON.stringify({ error: `upstream request timed out after ${upstreamTimeoutMs}ms`, code: "upstream_timeout" }));
22177
+ });
22178
+ res.on("close", () => {
22179
+ upstreamReq.destroy();
22180
+ upstreamRes?.destroy();
22181
+ });
22182
+ req.pipe(upstreamReq);
22183
+ });
22184
+ await new Promise((resolve3, reject) => {
22185
+ server.once("error", reject);
22186
+ server.listen(options.port ?? 0, host, () => {
22187
+ server.removeListener("error", reject);
22188
+ resolve3();
22189
+ });
22190
+ });
22191
+ const addr = server.address();
22192
+ const port = typeof addr === "object" && addr ? addr.port : options.port ?? 0;
22193
+ const url2 = `http://${host}:${port}`;
22194
+ return {
22195
+ url: url2,
22196
+ port,
22197
+ close: () => new Promise((resolve3) => {
22198
+ server.close(() => resolve3());
22199
+ })
22200
+ };
22201
+ }
22202
+ function joinPath(basePath, reqUrl) {
22203
+ const base = basePath.replace(/\/+$/, "");
22204
+ const reqPath = reqUrl.startsWith("/") ? reqUrl : `/${reqUrl}`;
22205
+ return base + reqPath || "/";
22206
+ }
22207
+ // src/daemon/createDaemon.ts
22208
+ import { homedir as homedir3 } from "os";
22209
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
22210
+
22211
+ // src/util/rotatingFileSink.ts
22212
+ import {
22213
+ appendFileSync,
22214
+ chmodSync,
22215
+ closeSync,
22216
+ constants,
22217
+ existsSync as existsSync5,
22218
+ fstatSync,
22219
+ lstatSync as lstatSync2,
22220
+ openSync,
22221
+ renameSync,
22222
+ statSync,
22223
+ unlinkSync as unlinkSync3
22224
+ } from "node:fs";
22225
+ function createRotatingFileSink(path8, maxBytes, opts = {}) {
22226
+ const report = (operation, error51) => {
22227
+ try {
22228
+ opts.onError?.({ operation, error: error51 });
22229
+ } catch {}
22230
+ };
22231
+ const secureGeneration = (filePath) => {
22232
+ if (!existsSync5(filePath))
22233
+ return true;
22234
+ try {
22235
+ const stat = lstatSync2(filePath);
22236
+ if (!stat.isFile()) {
22237
+ if (opts.mode === undefined && !opts.hardMaxBytes)
22238
+ return true;
22239
+ report("unsafe_generation", new Error("log generation is not a regular file"));
22240
+ return false;
22241
+ }
22242
+ if (opts.mode !== undefined)
22243
+ chmodSync(filePath, opts.mode);
22244
+ if (opts.hardMaxBytes && maxBytes > 0 && stat.size > maxBytes) {
22245
+ try {
22246
+ unlinkSync3(filePath);
22247
+ } catch (error51) {
22248
+ report("oversize_generation", error51);
22249
+ return false;
22250
+ }
22251
+ report("oversize_generation", new Error(`removed generation larger than ${maxBytes} bytes`));
22252
+ }
22253
+ return true;
22254
+ } catch (error51) {
22255
+ report("chmod", error51);
22256
+ return false;
22257
+ }
22258
+ };
22259
+ const rotate = () => {
22260
+ try {
22261
+ renameSync(path8, `${path8}.1`);
22262
+ if (opts.mode !== undefined)
22263
+ chmodSync(`${path8}.1`, opts.mode);
22264
+ return true;
22265
+ } catch (error51) {
22266
+ report("rotate", error51);
22267
+ return false;
22268
+ }
22269
+ };
22270
+ const currentSize = () => {
22271
+ try {
22272
+ return existsSync5(path8) ? statSync(path8).size : 0;
22273
+ } catch (error51) {
22274
+ report("stat", error51);
22275
+ return null;
22276
+ }
22277
+ };
22278
+ const sink = {
22279
+ secure() {
22280
+ return secureGeneration(`${path8}.1`) && secureGeneration(path8);
22281
+ },
22282
+ write(line) {
22283
+ try {
22284
+ if (!sink.secure())
22285
+ return;
22286
+ const serialized = line + `
22287
+ `;
22288
+ const serializedBytes = Buffer.byteLength(serialized, "utf8");
22289
+ if (opts.hardMaxBytes && maxBytes > 0 && serializedBytes > maxBytes) {
22290
+ report("oversize", new Error(`record exceeds ${maxBytes} bytes`));
22291
+ return;
22292
+ }
22293
+ const measuredBytes = currentSize();
22294
+ if (measuredBytes === null && opts.hardMaxBytes)
22295
+ return;
22296
+ const currentBytes = measuredBytes ?? 0;
22297
+ const shouldRotate = maxBytes > 0 && (opts.hardMaxBytes ? currentBytes > 0 && currentBytes + serializedBytes > maxBytes : currentBytes >= maxBytes);
22298
+ if (shouldRotate && !rotate() && opts.hardMaxBytes)
22299
+ return;
22300
+ appendFileSync(path8, serialized, opts.mode === undefined ? undefined : { mode: opts.mode });
22301
+ } catch (error51) {
22302
+ report("append", error51);
22303
+ }
22304
+ },
22305
+ openSnapshot() {
22306
+ const files = [];
22307
+ try {
22308
+ for (const candidate of [`${path8}.1`, path8]) {
22309
+ if (!existsSync5(candidate))
22310
+ continue;
22311
+ if (!secureGeneration(candidate))
22312
+ continue;
22313
+ const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
22314
+ const fd = openSync(candidate, constants.O_RDONLY | noFollow);
22315
+ try {
22316
+ const stat = fstatSync(fd);
22317
+ if (!stat.isFile())
22318
+ throw new Error("snapshot source is not a regular file");
22319
+ files.push({ path: candidate, fd, size: stat.size });
22320
+ } catch (error51) {
22321
+ closeSync(fd);
22322
+ throw error51;
22323
+ }
22324
+ }
22325
+ } catch (error51) {
22326
+ for (const file2 of files) {
22327
+ try {
22328
+ closeSync(file2.fd);
22329
+ } catch {}
22330
+ }
22331
+ report("snapshot", error51);
22332
+ return { files: [], close: () => {} };
22333
+ }
22334
+ let closed = false;
22335
+ return {
22336
+ files,
22337
+ close() {
22338
+ if (closed)
22339
+ return;
22340
+ closed = true;
22341
+ for (const file2 of files) {
22342
+ try {
22343
+ closeSync(file2.fd);
22344
+ } catch {}
22345
+ }
22346
+ }
22347
+ };
22348
+ }
22349
+ };
22350
+ return sink;
22351
+ }
22352
+
22353
+ // src/util/traceSampler.ts
22354
+ var SAMPLEABLE_EVENTS = new Set(["tick", "progress", "runtime_signal"]);
22355
+ var SACRED_TURN_SPAN_EVENTS = new Set(["turn_begin", "turn_end", "turn_abort"]);
22356
+ var DEFAULT_TRACE_SAMPLE_MS = 30000;
22357
+ var DEFAULT_TRACE_FILE_MAX_BYTES = 32 * 1024 * 1024;
22358
+ function newAgentState() {
22359
+ return {
22360
+ lastTickKey: null,
22361
+ lastTickEmitAt: Number.NEGATIVE_INFINITY,
22362
+ pendingTick: null,
22363
+ lastProgressEmitAt: Number.NEGATIVE_INFINITY,
22364
+ lastSignalPhase: null,
22365
+ lastSignalEmitAt: Number.NEGATIVE_INFINITY
22366
+ };
22367
+ }
22368
+ function tickKey(rec) {
22369
+ return [
22370
+ rec.status,
22371
+ rec.turnActive,
22372
+ rec.inbox,
22373
+ rec.resetting,
22374
+ rec.stoppingSince == null ? "s0" : "s1"
22375
+ ].join("|");
22376
+ }
22377
+ function hasEffects(rec) {
22378
+ const e = rec.effects;
22379
+ return Array.isArray(e) && e.length > 0;
22380
+ }
22381
+ function nowOf(rec) {
22382
+ return typeof rec.nowMs === "number" ? rec.nowMs : 0;
22383
+ }
22384
+ function createTraceSampler(emit, throttleMs = DEFAULT_TRACE_SAMPLE_MS) {
22385
+ const perAgent = new Map;
22386
+ const stateFor = (agentId) => {
22387
+ let s = perAgent.get(agentId);
22388
+ if (!s) {
22389
+ s = newAgentState();
22390
+ perAgent.set(agentId, s);
22391
+ }
22392
+ return s;
22393
+ };
22394
+ const flushPending = (s) => {
22395
+ if (s.pendingTick) {
22396
+ const p = s.pendingTick;
22397
+ s.pendingTick = null;
22398
+ s.lastTickEmitAt = nowOf(p);
22399
+ emit(p);
22400
+ }
22401
+ };
22402
+ return {
22403
+ offer(rec) {
22404
+ const agentId = typeof rec.agentId === "string" ? rec.agentId : null;
22405
+ if (!agentId) {
22406
+ emit(rec);
22407
+ return;
22408
+ }
22409
+ const s = stateFor(agentId);
22410
+ const event = rec.event;
22411
+ if (rec.recordKind === "turn_span" && SACRED_TURN_SPAN_EVENTS.has(String(event))) {
22412
+ flushPending(s);
22413
+ emit(rec);
22414
+ return;
22415
+ }
22416
+ if (!SAMPLEABLE_EVENTS.has(event) || hasEffects(rec)) {
22417
+ flushPending(s);
22418
+ if (event === "tick") {
22419
+ s.lastTickKey = tickKey(rec);
22420
+ s.lastTickEmitAt = nowOf(rec);
22421
+ }
22422
+ emit(rec);
22423
+ return;
22424
+ }
22425
+ if (event === "tick") {
22426
+ const key = tickKey(rec);
22427
+ const now = nowOf(rec);
22428
+ if (key !== s.lastTickKey) {
22429
+ flushPending(s);
22430
+ s.lastTickKey = key;
22431
+ s.lastTickEmitAt = now;
22432
+ emit(rec);
22433
+ return;
22434
+ }
22435
+ if (now - s.lastTickEmitAt >= throttleMs) {
22436
+ s.pendingTick = null;
22437
+ s.lastTickEmitAt = now;
22438
+ emit(rec);
22439
+ } else {
22440
+ s.pendingTick = rec;
22441
+ }
22442
+ return;
22443
+ }
22444
+ if (event === "progress") {
22445
+ const now = nowOf(rec);
22446
+ if (now - s.lastProgressEmitAt >= throttleMs) {
22447
+ s.lastProgressEmitAt = now;
22448
+ emit(rec);
22449
+ }
22450
+ return;
22451
+ }
22452
+ if (event === "runtime_signal") {
22453
+ const phase = typeof rec.apmPhase === "string" ? rec.apmPhase : "";
22454
+ const now = nowOf(rec);
22455
+ if (phase !== s.lastSignalPhase || now - s.lastSignalEmitAt >= throttleMs) {
22456
+ s.lastSignalPhase = phase;
22457
+ s.lastSignalEmitAt = now;
22458
+ emit(rec);
22459
+ }
22460
+ return;
22461
+ }
22462
+ emit(rec);
22463
+ }
22464
+ };
22465
+ }
22466
+
22467
+ // src/util/statusFile.ts
22468
+ import { writeFileSync as writeFileSync4, renameSync as renameSync2 } from "node:fs";
22469
+ function writeStatusFile(path8, snapshot) {
22470
+ try {
22471
+ const tmp = `${path8}.tmp`;
22472
+ writeFileSync4(tmp, JSON.stringify(snapshot));
22473
+ renameSync2(tmp, path8);
22474
+ } catch {}
22475
+ }
22476
+
22477
+ // src/server/wsControlChannel.ts
22478
+ var WS_CONTROL_COMMAND_CONSUMED = Symbol("ws-control-command-consumed");
22479
+ var DEFAULT_PING_INTERVAL_MS = 15000;
22480
+ var DEFAULT_PONG_TIMEOUT_MS = 30000;
22481
+ var DEFAULT_RECONNECT_BASE_MS = 500;
22482
+ var DEFAULT_RECONNECT_MAX_MS = 30000;
22483
+ function describeErr(err) {
22484
+ return err instanceof Error ? err.message : String(err);
22263
22485
  }
22264
22486
 
22265
22487
  class WsControlChannel {
@@ -23269,6 +23491,108 @@ function createDiagnosticsCommandListener(options) {
23269
23491
  };
23270
23492
  }
23271
23493
 
23494
+ // src/daemon/selfUpdateCommand.ts
23495
+ function createSelfUpdateCommandListener(handleSelfUpdate) {
23496
+ return (command) => {
23497
+ if (command.type !== "machine:update")
23498
+ return;
23499
+ if (handleSelfUpdate) {
23500
+ try {
23501
+ Promise.resolve(handleSelfUpdate()).catch(() => {});
23502
+ } catch {}
23503
+ }
23504
+ return WS_CONTROL_COMMAND_CONSUMED;
23505
+ };
23506
+ }
23507
+
23508
+ // src/daemon/messageReminderScheduler.ts
23509
+ function reminderKey(agentId, channel2) {
23510
+ return `${agentId}\x00${channel2}`;
23511
+ }
23512
+ function reminderPrompt(channel2, sentRef, startedAt) {
23513
+ return `Reminder: At ${localISOString(new Date(startedAt))}, after sending ${sentRef} in ${channel2}, you asked to be reminded if no newer message arrived. No newer message has arrived in that conversation.`;
23514
+ }
23515
+
23516
+ class MessageReminderScheduler {
23517
+ options;
23518
+ reminders = new Map;
23519
+ latestObservedSeq = new Map;
23520
+ now;
23521
+ setTimer;
23522
+ clearTimer;
23523
+ constructor(options) {
23524
+ this.options = options;
23525
+ this.now = options.now ?? Date.now;
23526
+ this.setTimer = options.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
23527
+ this.clearTimer = options.clearTimer ?? ((timer) => clearTimeout(timer));
23528
+ }
23529
+ arm(input) {
23530
+ const key = reminderKey(input.agentId, input.channel);
23531
+ const latest = this.latestObservedSeq.get(key);
23532
+ if (latest !== undefined && latest > input.sentSeq) {
23533
+ return { armed: false, reason: "newer_message_observed" };
23534
+ }
23535
+ this.clearReminder(key);
23536
+ const startedAt = this.now();
23537
+ const dueAt = startedAt + input.remindAfterMs;
23538
+ const sentRef = `${input.channel}#${input.sentSeq}`;
23539
+ const record2 = {
23540
+ ...input,
23541
+ sentRef,
23542
+ startedAt,
23543
+ dueAt,
23544
+ timer: undefined
23545
+ };
23546
+ record2.timer = this.setTimer(() => {
23547
+ if (this.reminders.get(key) !== record2)
23548
+ return;
23549
+ this.reminders.delete(key);
23550
+ try {
23551
+ const delivery = this.options.deliver(input.agentId, {
23552
+ text: reminderPrompt(input.channel, sentRef, startedAt)
23553
+ });
23554
+ Promise.resolve(delivery).catch(() => {});
23555
+ } catch {}
23556
+ }, input.remindAfterMs);
23557
+ record2.timer.unref?.();
23558
+ this.reminders.set(key, record2);
23559
+ return { armed: true, dueAt };
23560
+ }
23561
+ observe(agentId, channel2, latestSeq) {
23562
+ const key = reminderKey(agentId, channel2);
23563
+ const previous = this.latestObservedSeq.get(key);
23564
+ if (previous === undefined || latestSeq > previous) {
23565
+ this.latestObservedSeq.set(key, latestSeq);
23566
+ }
23567
+ const reminder = this.reminders.get(key);
23568
+ if (reminder && latestSeq > reminder.sentSeq)
23569
+ this.clearReminder(key);
23570
+ }
23571
+ clearAgent(agentId) {
23572
+ const prefix = `${agentId}\x00`;
23573
+ for (const key of [...this.reminders.keys()]) {
23574
+ if (key.startsWith(prefix))
23575
+ this.clearReminder(key);
23576
+ }
23577
+ for (const key of [...this.latestObservedSeq.keys()]) {
23578
+ if (key.startsWith(prefix))
23579
+ this.latestObservedSeq.delete(key);
23580
+ }
23581
+ }
23582
+ clearAll() {
23583
+ for (const key of [...this.reminders.keys()])
23584
+ this.clearReminder(key);
23585
+ this.latestObservedSeq.clear();
23586
+ }
23587
+ clearReminder(key) {
23588
+ const reminder = this.reminders.get(key);
23589
+ if (!reminder)
23590
+ return;
23591
+ this.reminders.delete(key);
23592
+ this.clearTimer(reminder.timer);
23593
+ }
23594
+ }
23595
+
23272
23596
  // src/daemon/createDaemon.ts
23273
23597
  var WARMUP_BACKOFF_MS = [250, 500, 1000, 2000, 4000];
23274
23598
  var WARMUP_CEILING_MS = 30000;
@@ -23327,6 +23651,15 @@ function deriveAuditLogSubcommand(pathname, method) {
23327
23651
  const canonical = pathname.split("?")[0] ?? pathname;
23328
23652
  if (/^\/api\/community\/messages\/[^/]+\/reactions\//.test(canonical))
23329
23653
  return "reactAdd";
23654
+ if (/^\/api\/community\/messages\/[^/]+\/marks$/.test(canonical)) {
23655
+ if (method === "PUT")
23656
+ return "markSet";
23657
+ if (method === "DELETE")
23658
+ return "markRemove";
23659
+ return null;
23660
+ }
23661
+ if (method === "GET" && /^\/api\/community\/users\/me\/marks$/.test(canonical))
23662
+ return "markList";
23330
23663
  if (/^\/api\/community\/channels\/[^/]+\/messages\/seq\//.test(canonical))
23331
23664
  return "resolve";
23332
23665
  if (/^\/api\/community\/channels\/[^/]+\/messages(\/|$)/.test(canonical)) {
@@ -23390,6 +23723,7 @@ async function createDaemon(opts) {
23390
23723
  });
23391
23724
  let channelRef = null;
23392
23725
  let managerRef = null;
23726
+ let reminderSchedulerRef = null;
23393
23727
  const emitBotAuditEvent = (agentId, event, context) => {
23394
23728
  channelRef?.reportBotAuditEvent?.({
23395
23729
  type: "bot_audit_event",
@@ -23403,6 +23737,7 @@ async function createDaemon(opts) {
23403
23737
  const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
23404
23738
  const proxy = await startCredentialProxy(broker, {
23405
23739
  onInboxPullResponse: (agentId, messages) => timeline2.appendEntryForAgent(agentId, messages),
23740
+ onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
23406
23741
  onProxyRequest: (agentId, method, pathname) => {
23407
23742
  const subcommand = deriveAuditLogSubcommand(pathname, method);
23408
23743
  if (!subcommand)
@@ -23626,7 +23961,7 @@ async function createDaemon(opts) {
23626
23961
  return opts.driverFor(agentId, runtimeConfig);
23627
23962
  },
23628
23963
  onRuntimeSpawnFailed: (runtimeId, reason) => {
23629
- router?.markRuntimeUnhealthy(runtimeId, reason);
23964
+ router?.recordRuntimeSpawnFailure(runtimeId, reason);
23630
23965
  },
23631
23966
  onRuntimeSessionEstablished: (runtimeId) => {
23632
23967
  router?.markRuntimeHealthy(runtimeId);
@@ -23650,6 +23985,7 @@ async function createDaemon(opts) {
23650
23985
  }
23651
23986
  };
23652
23987
  },
23988
+ ...opts.handshakeTimeoutMs !== undefined ? { handshakeTimeoutMs: opts.handshakeTimeoutMs } : {},
23653
23989
  tickIntervalMs: opts.tickIntervalMs ?? 2000,
23654
23990
  onAgentSession: (info) => void channel2.reportAgentSession(info),
23655
23991
  onAgentActivity: (info) => {
@@ -23674,6 +24010,10 @@ async function createDaemon(opts) {
23674
24010
  });
23675
24011
  managerRef = manager;
23676
24012
  manager.start();
24013
+ reminderSchedulerRef = new MessageReminderScheduler({
24014
+ deliver: (agentId, message2) => manager.deliver(agentId, message2),
24015
+ ...opts.messageReminderClock
24016
+ });
23677
24017
  let statusTimer = null;
23678
24018
  if (opts.statusFilePath) {
23679
24019
  const statusPath = opts.statusFilePath;
@@ -23719,10 +24059,26 @@ async function createDaemon(opts) {
23719
24059
  },
23720
24060
  formatUnreadNoticeText: (notice) => `You have unread messages in channel ${notice.channel}.`
23721
24061
  });
24062
+ channel2.onCommand(createSelfUpdateCommandListener(opts.handleSelfUpdate));
23722
24063
  channel2.onCommand(createDiagnosticsCommandListener({
23723
24064
  handleDiagnosticCommand: opts.handleDiagnosticCommand,
23724
24065
  reportDiagnosticFailure: opts.reportDiagnosticFailure
23725
24066
  }));
24067
+ channel2.onCommand((cmd) => {
24068
+ switch (cmd.type) {
24069
+ case "agent:wake":
24070
+ reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
24071
+ break;
24072
+ case "agent:stop":
24073
+ reminderSchedulerRef?.clearAgent(cmd.agentId);
24074
+ break;
24075
+ case "bot:removed":
24076
+ reminderSchedulerRef?.clearAgent(cmd.botId);
24077
+ break;
24078
+ default:
24079
+ break;
24080
+ }
24081
+ });
23726
24082
  channel2.onCommand((cmd) => {
23727
24083
  handleBotFrame(cmd);
23728
24084
  });
@@ -23741,6 +24097,7 @@ async function createDaemon(opts) {
23741
24097
  },
23742
24098
  proxyUrl: proxy.url,
23743
24099
  stop: async () => {
24100
+ reminderSchedulerRef?.clearAll();
23744
24101
  for (const agentId of [...typingHeartbeats.keys()]) {
23745
24102
  emitTypingStopsAndClear(agentId);
23746
24103
  }
@@ -23824,6 +24181,7 @@ export {
23824
24181
  PiDriver,
23825
24182
  OpenCodeDriver,
23826
24183
  MAX_APM_GATED_STEERING_EVENTS,
24184
+ LOCAL_MESSAGE_REMINDER_PATH,
23827
24185
  KimiDriver,
23828
24186
  GeminiDriver,
23829
24187
  DEFAULT_STOPPING_STUCK_THRESHOLD_MS,