@alook/daemon 0.1.6 → 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 +1004 -150
  2. package/dist/index.js +893 -549
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ 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\`).`,
@@ -206,7 +206,13 @@ function utilsSection() {
206
206
  "",
207
207
  "### Join a new server",
208
208
  "",
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."
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\`.`
210
216
  ].join(`
211
217
  `);
212
218
  }
@@ -1286,13 +1292,18 @@ function codexSessionRootCandidates(homeDirOrCodexRoot) {
1286
1292
  // src/version.ts
1287
1293
  import { createRequire } from "module";
1288
1294
  var requireFromHere = createRequire(import.meta.url);
1295
+ var PACKAGE_JSON_CANDIDATES = ["../package.json", "../../package.json"];
1289
1296
  function readDaemonVersion() {
1290
- try {
1291
- const pkg = requireFromHere("../package.json");
1292
- return pkg.version ?? "";
1293
- } catch {
1294
- 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
+ }
1295
1305
  }
1306
+ return "";
1296
1307
  }
1297
1308
  function getDaemonClientInfo() {
1298
1309
  return { name: "alook-daemon", version: readDaemonVersion() };
@@ -5168,519 +5179,13 @@ function createTypingScopeTracker() {
5168
5179
  };
5169
5180
  }
5170
5181
  // src/credentials/credentialProxy.ts
5171
- import * as crypto from "crypto";
5182
+ import * as crypto2 from "crypto";
5172
5183
  import * as fs5 from "fs";
5173
5184
  import * as http from "http";
5174
5185
  import * as https from "https";
5175
5186
  import * as os2 from "os";
5176
5187
  import * as path7 from "path";
5177
5188
  import { URL as URL2 } from "url";
5178
- var DEFAULT_HEADER_NAMES = {
5179
- agentId: "X-Agent-Id",
5180
- client: "X-Client",
5181
- capabilities: "X-Agent-Active-Capabilities"
5182
- };
5183
- function randomVoucher(prefix) {
5184
- return prefix + crypto.randomBytes(32).toString("base64url");
5185
- }
5186
- function sanitizeIdSegment(id) {
5187
- return id.replace(/[^A-Za-z0-9._-]/g, "_") || "_";
5188
- }
5189
-
5190
- class CredentialBroker {
5191
- registrations = new Map;
5192
- voucherPrefix;
5193
- voucherDir;
5194
- upstreamBaseUrl;
5195
- clientLabel;
5196
- headerNames;
5197
- constructor(config) {
5198
- if (!config.upstreamBaseUrl)
5199
- throw new Error("CredentialBroker: upstreamBaseUrl is required");
5200
- this.upstreamBaseUrl = config.upstreamBaseUrl.replace(/\/+$/, "");
5201
- this.voucherPrefix = config.voucherPrefix ?? "vch_";
5202
- this.clientLabel = config.clientLabel ?? "cli";
5203
- this.headerNames = config.headerNames ?? DEFAULT_HEADER_NAMES;
5204
- this.voucherDir = config.voucherDir ?? path7.join(os2.tmpdir(), "agent-vouchers");
5205
- }
5206
- mint(agentId, launchId, capabilities, runnerKey) {
5207
- if (!runnerKey)
5208
- throw new Error("CredentialBroker.mint: runnerKey is required (per-agent tier-2 credential)");
5209
- const voucher = randomVoucher(this.voucherPrefix);
5210
- const dir = path7.join(this.voucherDir, sanitizeIdSegment(agentId));
5211
- fs5.mkdirSync(dir, { recursive: true });
5212
- const voucherFile = path7.join(dir, `${sanitizeIdSegment(launchId)}.token`);
5213
- fs5.writeFileSync(voucherFile, voucher, { mode: 384 });
5214
- this.registrations.set(voucher, {
5215
- agentId,
5216
- launchId,
5217
- capabilities: new Set(capabilities),
5218
- voucherFile,
5219
- runnerKey
5220
- });
5221
- return { voucher, agentId, launchId, capabilities: [...capabilities], voucherFile };
5222
- }
5223
- revoke(voucher) {
5224
- const reg = this.registrations.get(voucher);
5225
- if (!reg)
5226
- return false;
5227
- this.registrations.delete(voucher);
5228
- try {
5229
- fs5.rmSync(reg.voucherFile, { force: true });
5230
- } catch {}
5231
- return true;
5232
- }
5233
- revokeAgent(agentId) {
5234
- let n = 0;
5235
- for (const [voucher, reg] of this.registrations) {
5236
- if (reg.agentId === agentId && this.revoke(voucher))
5237
- n++;
5238
- }
5239
- return n;
5240
- }
5241
- get size() {
5242
- return this.registrations.size;
5243
- }
5244
- check(authHeader, requiredCapability) {
5245
- const voucher = parseBearer(authHeader);
5246
- if (!voucher) {
5247
- return { ok: false, status: 401, code: "missing_voucher", error: "missing bearer voucher" };
5248
- }
5249
- const reg = this.registrations.get(voucher);
5250
- if (!reg) {
5251
- return { ok: false, status: 401, code: "invalid_proxy_token", error: "invalid local agent proxy token" };
5252
- }
5253
- if (requiredCapability && !reg.capabilities.has(requiredCapability)) {
5254
- return {
5255
- ok: false,
5256
- status: 403,
5257
- code: "capability_denied",
5258
- error: `capability '${requiredCapability}' not granted to this voucher`
5259
- };
5260
- }
5261
- return { ok: true, reg };
5262
- }
5263
- }
5264
- function parseBearer(authHeader) {
5265
- if (!authHeader)
5266
- return null;
5267
- const m = /^Bearer\s+(.+)$/i.exec(authHeader.trim());
5268
- return m ? m[1].trim() : null;
5269
- }
5270
- var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
5271
- if (pathname.includes("/attachment"))
5272
- return "attach";
5273
- if (/\/friends(\/|$|\?)/.test(pathname))
5274
- return "friend";
5275
- const isMessagesDoor = /\/channels\/[^/]+\/messages(\/|$|\?)/.test(pathname);
5276
- if (isMessagesDoor)
5277
- return method === "GET" ? "read" : "send";
5278
- if (/\/messages\/[^/]+\/reactions\//.test(pathname))
5279
- return "send";
5280
- if ((method === "PUT" || method === "DELETE") && /\/messages\/[^/]+\/marks(\/|$|\?)/.test(pathname))
5281
- return "send";
5282
- if (method === "GET" && /\/users\/me\/marks(\/|$|\?)/.test(pathname))
5283
- return "read";
5284
- if (method === "GET" && /\/messages\/[^/]+(\?|$)/.test(pathname))
5285
- return "read";
5286
- if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
5287
- return "read";
5288
- if (pathname.includes("/server") || pathname.includes("/channel"))
5289
- return "server";
5290
- return;
5291
- };
5292
- var DEFAULT_UPSTREAM_TIMEOUT_MS = 20000;
5293
- async function startCredentialProxy(broker, options = {}) {
5294
- const host = options.host ?? "127.0.0.1";
5295
- const resolveCap = options.capabilityResolver ?? DEFAULT_CAPABILITY_RESOLVER;
5296
- const upstream = new URL2(broker.upstreamBaseUrl);
5297
- const upstreamClient = upstream.protocol === "https:" ? https : http;
5298
- const onPull = options.onInboxPullResponse;
5299
- const onProxyRequest = options.onProxyRequest;
5300
- const server = http.createServer((req, res) => {
5301
- const pathname = new URL2(req.url ?? "/", "http://placeholder").pathname;
5302
- const requiredCap = resolveCap(req.method ?? "GET", pathname);
5303
- const verdict = broker.check(req.headers["authorization"], requiredCap);
5304
- if (!verdict.ok) {
5305
- res.writeHead(verdict.status, { "content-type": "application/json" });
5306
- res.end(JSON.stringify({ error: verdict.error, code: verdict.code }));
5307
- req.resume();
5308
- return;
5309
- }
5310
- const canonicalCommunityDoor = /^\/api\/community\/(channels|messages|servers|invites|friends|users|bots)(\/|$)/.test(pathname);
5311
- if (pathname === "/api" || pathname.startsWith("/api/") && !canonicalCommunityDoor) {
5312
- res.writeHead(404, { "content-type": "application/json" });
5313
- res.end(JSON.stringify({ error: "unknown API route", code: "not_found" }));
5314
- req.resume();
5315
- return;
5316
- }
5317
- const reg = verdict.reg;
5318
- const isInboxPull = onPull && pathname === "/api/community/users/me/inbox/pull";
5319
- if (onProxyRequest) {
5320
- try {
5321
- onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
5322
- } catch {}
5323
- }
5324
- const outHeaders = { ...req.headers };
5325
- delete outHeaders["authorization"];
5326
- delete outHeaders["host"];
5327
- outHeaders["authorization"] = `Bearer ${reg.runnerKey}`;
5328
- outHeaders[broker.headerNames.agentId.toLowerCase()] = reg.agentId;
5329
- outHeaders[broker.headerNames.client.toLowerCase()] = broker.clientLabel;
5330
- outHeaders[broker.headerNames.capabilities.toLowerCase()] = [...reg.capabilities].join(",");
5331
- let responded = false;
5332
- let upstreamRes;
5333
- const upstreamReq = upstreamClient.request({
5334
- protocol: upstream.protocol,
5335
- hostname: upstream.hostname,
5336
- port: upstream.port || (upstream.protocol === "https:" ? 443 : 80),
5337
- method: req.method,
5338
- path: joinPath(upstream.pathname, req.url ?? "/"),
5339
- headers: outHeaders
5340
- }, (res_) => {
5341
- responded = true;
5342
- upstreamRes = res_;
5343
- const destroyResIfIncomplete = () => {
5344
- if (!res_.complete)
5345
- res.destroy();
5346
- };
5347
- res_.on("error", destroyResIfIncomplete);
5348
- res_.on("close", destroyResIfIncomplete);
5349
- if (isInboxPull && res_.statusCode && res_.statusCode < 300) {
5350
- const chunks = [];
5351
- res_.on("data", (chunk) => chunks.push(chunk));
5352
- res_.on("end", () => {
5353
- const body = Buffer.concat(chunks);
5354
- res.writeHead(res_.statusCode, res_.headers);
5355
- res.end(body);
5356
- try {
5357
- const parsed = JSON.parse(body.toString());
5358
- if (parsed.messages)
5359
- onPull(reg.agentId, parsed.messages);
5360
- } catch {}
5361
- });
5362
- } else {
5363
- res.writeHead(res_.statusCode ?? 502, res_.headers);
5364
- res_.pipe(res);
5365
- }
5366
- });
5367
- upstreamReq.on("error", (err) => {
5368
- if (responded)
5369
- return;
5370
- responded = true;
5371
- res.writeHead(502, { "content-type": "application/json" });
5372
- res.end(JSON.stringify({ error: `upstream error: ${err.message}`, code: "upstream_error" }));
5373
- });
5374
- const upstreamTimeoutMs = options.upstreamTimeoutMs ?? DEFAULT_UPSTREAM_TIMEOUT_MS;
5375
- upstreamReq.setTimeout(upstreamTimeoutMs, () => {
5376
- upstreamReq.destroy();
5377
- upstreamRes?.destroy();
5378
- if (responded) {
5379
- res.destroy();
5380
- return;
5381
- }
5382
- responded = true;
5383
- res.writeHead(504, { "content-type": "application/json" });
5384
- res.end(JSON.stringify({ error: `upstream request timed out after ${upstreamTimeoutMs}ms`, code: "upstream_timeout" }));
5385
- });
5386
- res.on("close", () => {
5387
- upstreamReq.destroy();
5388
- upstreamRes?.destroy();
5389
- });
5390
- req.pipe(upstreamReq);
5391
- });
5392
- await new Promise((resolve3, reject) => {
5393
- server.once("error", reject);
5394
- server.listen(options.port ?? 0, host, () => {
5395
- server.removeListener("error", reject);
5396
- resolve3();
5397
- });
5398
- });
5399
- const addr = server.address();
5400
- const port = typeof addr === "object" && addr ? addr.port : options.port ?? 0;
5401
- const url = `http://${host}:${port}`;
5402
- return {
5403
- url,
5404
- port,
5405
- close: () => new Promise((resolve3) => {
5406
- server.close(() => resolve3());
5407
- })
5408
- };
5409
- }
5410
- function joinPath(basePath, reqUrl) {
5411
- const base = basePath.replace(/\/+$/, "");
5412
- const reqPath = reqUrl.startsWith("/") ? reqUrl : `/${reqUrl}`;
5413
- return base + reqPath || "/";
5414
- }
5415
- // src/daemon/createDaemon.ts
5416
- import { homedir as homedir3 } from "os";
5417
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
5418
-
5419
- // src/util/rotatingFileSink.ts
5420
- import {
5421
- appendFileSync,
5422
- chmodSync,
5423
- closeSync,
5424
- constants,
5425
- existsSync as existsSync5,
5426
- fstatSync,
5427
- lstatSync as lstatSync2,
5428
- openSync,
5429
- renameSync,
5430
- statSync,
5431
- unlinkSync as unlinkSync3
5432
- } from "node:fs";
5433
- function createRotatingFileSink(path8, maxBytes, opts = {}) {
5434
- const report = (operation, error) => {
5435
- try {
5436
- opts.onError?.({ operation, error });
5437
- } catch {}
5438
- };
5439
- const secureGeneration = (filePath) => {
5440
- if (!existsSync5(filePath))
5441
- return true;
5442
- try {
5443
- const stat = lstatSync2(filePath);
5444
- if (!stat.isFile()) {
5445
- if (opts.mode === undefined && !opts.hardMaxBytes)
5446
- return true;
5447
- report("unsafe_generation", new Error("log generation is not a regular file"));
5448
- return false;
5449
- }
5450
- if (opts.mode !== undefined)
5451
- chmodSync(filePath, opts.mode);
5452
- if (opts.hardMaxBytes && maxBytes > 0 && stat.size > maxBytes) {
5453
- try {
5454
- unlinkSync3(filePath);
5455
- } catch (error) {
5456
- report("oversize_generation", error);
5457
- return false;
5458
- }
5459
- report("oversize_generation", new Error(`removed generation larger than ${maxBytes} bytes`));
5460
- }
5461
- return true;
5462
- } catch (error) {
5463
- report("chmod", error);
5464
- return false;
5465
- }
5466
- };
5467
- const rotate = () => {
5468
- try {
5469
- renameSync(path8, `${path8}.1`);
5470
- if (opts.mode !== undefined)
5471
- chmodSync(`${path8}.1`, opts.mode);
5472
- return true;
5473
- } catch (error) {
5474
- report("rotate", error);
5475
- return false;
5476
- }
5477
- };
5478
- const currentSize = () => {
5479
- try {
5480
- return existsSync5(path8) ? statSync(path8).size : 0;
5481
- } catch (error) {
5482
- report("stat", error);
5483
- return null;
5484
- }
5485
- };
5486
- const sink = {
5487
- secure() {
5488
- return secureGeneration(`${path8}.1`) && secureGeneration(path8);
5489
- },
5490
- write(line) {
5491
- try {
5492
- if (!sink.secure())
5493
- return;
5494
- const serialized = line + `
5495
- `;
5496
- const serializedBytes = Buffer.byteLength(serialized, "utf8");
5497
- if (opts.hardMaxBytes && maxBytes > 0 && serializedBytes > maxBytes) {
5498
- report("oversize", new Error(`record exceeds ${maxBytes} bytes`));
5499
- return;
5500
- }
5501
- const measuredBytes = currentSize();
5502
- if (measuredBytes === null && opts.hardMaxBytes)
5503
- return;
5504
- const currentBytes = measuredBytes ?? 0;
5505
- const shouldRotate = maxBytes > 0 && (opts.hardMaxBytes ? currentBytes > 0 && currentBytes + serializedBytes > maxBytes : currentBytes >= maxBytes);
5506
- if (shouldRotate && !rotate() && opts.hardMaxBytes)
5507
- return;
5508
- appendFileSync(path8, serialized, opts.mode === undefined ? undefined : { mode: opts.mode });
5509
- } catch (error) {
5510
- report("append", error);
5511
- }
5512
- },
5513
- openSnapshot() {
5514
- const files = [];
5515
- try {
5516
- for (const candidate of [`${path8}.1`, path8]) {
5517
- if (!existsSync5(candidate))
5518
- continue;
5519
- if (!secureGeneration(candidate))
5520
- continue;
5521
- const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
5522
- const fd = openSync(candidate, constants.O_RDONLY | noFollow);
5523
- try {
5524
- const stat = fstatSync(fd);
5525
- if (!stat.isFile())
5526
- throw new Error("snapshot source is not a regular file");
5527
- files.push({ path: candidate, fd, size: stat.size });
5528
- } catch (error) {
5529
- closeSync(fd);
5530
- throw error;
5531
- }
5532
- }
5533
- } catch (error) {
5534
- for (const file of files) {
5535
- try {
5536
- closeSync(file.fd);
5537
- } catch {}
5538
- }
5539
- report("snapshot", error);
5540
- return { files: [], close: () => {} };
5541
- }
5542
- let closed = false;
5543
- return {
5544
- files,
5545
- close() {
5546
- if (closed)
5547
- return;
5548
- closed = true;
5549
- for (const file of files) {
5550
- try {
5551
- closeSync(file.fd);
5552
- } catch {}
5553
- }
5554
- }
5555
- };
5556
- }
5557
- };
5558
- return sink;
5559
- }
5560
-
5561
- // src/util/traceSampler.ts
5562
- var SAMPLEABLE_EVENTS = new Set(["tick", "progress", "runtime_signal"]);
5563
- var SACRED_TURN_SPAN_EVENTS = new Set(["turn_begin", "turn_end", "turn_abort"]);
5564
- var DEFAULT_TRACE_SAMPLE_MS = 30000;
5565
- var DEFAULT_TRACE_FILE_MAX_BYTES = 32 * 1024 * 1024;
5566
- function newAgentState() {
5567
- return {
5568
- lastTickKey: null,
5569
- lastTickEmitAt: Number.NEGATIVE_INFINITY,
5570
- pendingTick: null,
5571
- lastProgressEmitAt: Number.NEGATIVE_INFINITY,
5572
- lastSignalPhase: null,
5573
- lastSignalEmitAt: Number.NEGATIVE_INFINITY
5574
- };
5575
- }
5576
- function tickKey(rec) {
5577
- return [
5578
- rec.status,
5579
- rec.turnActive,
5580
- rec.inbox,
5581
- rec.resetting,
5582
- rec.stoppingSince == null ? "s0" : "s1"
5583
- ].join("|");
5584
- }
5585
- function hasEffects(rec) {
5586
- const e = rec.effects;
5587
- return Array.isArray(e) && e.length > 0;
5588
- }
5589
- function nowOf(rec) {
5590
- return typeof rec.nowMs === "number" ? rec.nowMs : 0;
5591
- }
5592
- function createTraceSampler(emit, throttleMs = DEFAULT_TRACE_SAMPLE_MS) {
5593
- const perAgent = new Map;
5594
- const stateFor = (agentId) => {
5595
- let s = perAgent.get(agentId);
5596
- if (!s) {
5597
- s = newAgentState();
5598
- perAgent.set(agentId, s);
5599
- }
5600
- return s;
5601
- };
5602
- const flushPending = (s) => {
5603
- if (s.pendingTick) {
5604
- const p = s.pendingTick;
5605
- s.pendingTick = null;
5606
- s.lastTickEmitAt = nowOf(p);
5607
- emit(p);
5608
- }
5609
- };
5610
- return {
5611
- offer(rec) {
5612
- const agentId = typeof rec.agentId === "string" ? rec.agentId : null;
5613
- if (!agentId) {
5614
- emit(rec);
5615
- return;
5616
- }
5617
- const s = stateFor(agentId);
5618
- const event = rec.event;
5619
- if (rec.recordKind === "turn_span" && SACRED_TURN_SPAN_EVENTS.has(String(event))) {
5620
- flushPending(s);
5621
- emit(rec);
5622
- return;
5623
- }
5624
- if (!SAMPLEABLE_EVENTS.has(event) || hasEffects(rec)) {
5625
- flushPending(s);
5626
- if (event === "tick") {
5627
- s.lastTickKey = tickKey(rec);
5628
- s.lastTickEmitAt = nowOf(rec);
5629
- }
5630
- emit(rec);
5631
- return;
5632
- }
5633
- if (event === "tick") {
5634
- const key = tickKey(rec);
5635
- const now = nowOf(rec);
5636
- if (key !== s.lastTickKey) {
5637
- flushPending(s);
5638
- s.lastTickKey = key;
5639
- s.lastTickEmitAt = now;
5640
- emit(rec);
5641
- return;
5642
- }
5643
- if (now - s.lastTickEmitAt >= throttleMs) {
5644
- s.pendingTick = null;
5645
- s.lastTickEmitAt = now;
5646
- emit(rec);
5647
- } else {
5648
- s.pendingTick = rec;
5649
- }
5650
- return;
5651
- }
5652
- if (event === "progress") {
5653
- const now = nowOf(rec);
5654
- if (now - s.lastProgressEmitAt >= throttleMs) {
5655
- s.lastProgressEmitAt = now;
5656
- emit(rec);
5657
- }
5658
- return;
5659
- }
5660
- if (event === "runtime_signal") {
5661
- const phase = typeof rec.apmPhase === "string" ? rec.apmPhase : "";
5662
- const now = nowOf(rec);
5663
- if (phase !== s.lastSignalPhase || now - s.lastSignalEmitAt >= throttleMs) {
5664
- s.lastSignalPhase = phase;
5665
- s.lastSignalEmitAt = now;
5666
- emit(rec);
5667
- }
5668
- return;
5669
- }
5670
- emit(rec);
5671
- }
5672
- };
5673
- }
5674
-
5675
- // src/util/statusFile.ts
5676
- import { writeFileSync as writeFileSync4, renameSync as renameSync2 } from "node:fs";
5677
- function writeStatusFile(path8, snapshot) {
5678
- try {
5679
- const tmp = `${path8}.tmp`;
5680
- writeFileSync4(tmp, JSON.stringify(snapshot));
5681
- renameSync2(tmp, path8);
5682
- } catch {}
5683
- }
5684
5189
 
5685
5190
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
5686
5191
  var exports_external = {};
@@ -6446,10 +5951,10 @@ function mergeDefs(...defs) {
6446
5951
  function cloneDef(schema) {
6447
5952
  return mergeDefs(schema._zod.def);
6448
5953
  }
6449
- function getElementAtPath(obj, path8) {
6450
- if (!path8)
5954
+ function getElementAtPath(obj, path7) {
5955
+ if (!path7)
6451
5956
  return obj;
6452
- return path8.reduce((acc, key) => acc?.[key], obj);
5957
+ return path7.reduce((acc, key) => acc?.[key], obj);
6453
5958
  }
6454
5959
  function promiseAllObject(promisesObj) {
6455
5960
  const keys = Object.keys(promisesObj);
@@ -6857,11 +6362,11 @@ function explicitlyAborted(x, startIndex = 0) {
6857
6362
  }
6858
6363
  return false;
6859
6364
  }
6860
- function prefixIssues(path8, issues) {
6365
+ function prefixIssues(path7, issues) {
6861
6366
  return issues.map((iss) => {
6862
6367
  var _a2;
6863
6368
  (_a2 = iss).path ?? (_a2.path = []);
6864
- iss.path.unshift(path8);
6369
+ iss.path.unshift(path7);
6865
6370
  return iss;
6866
6371
  });
6867
6372
  }
@@ -7008,16 +6513,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
7008
6513
  }
7009
6514
  function formatError(error, mapper = (issue2) => issue2.message) {
7010
6515
  const fieldErrors = { _errors: [] };
7011
- const processError = (error2, path8 = []) => {
6516
+ const processError = (error2, path7 = []) => {
7012
6517
  for (const issue2 of error2.issues) {
7013
6518
  if (issue2.code === "invalid_union" && issue2.errors.length) {
7014
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
6519
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
7015
6520
  } else if (issue2.code === "invalid_key") {
7016
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6521
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7017
6522
  } else if (issue2.code === "invalid_element") {
7018
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6523
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7019
6524
  } else {
7020
- const fullpath = [...path8, ...issue2.path];
6525
+ const fullpath = [...path7, ...issue2.path];
7021
6526
  if (fullpath.length === 0) {
7022
6527
  fieldErrors._errors.push(mapper(issue2));
7023
6528
  } else {
@@ -7044,17 +6549,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
7044
6549
  }
7045
6550
  function treeifyError(error, mapper = (issue2) => issue2.message) {
7046
6551
  const result = { errors: [] };
7047
- const processError = (error2, path8 = []) => {
6552
+ const processError = (error2, path7 = []) => {
7048
6553
  var _a2, _b;
7049
6554
  for (const issue2 of error2.issues) {
7050
6555
  if (issue2.code === "invalid_union" && issue2.errors.length) {
7051
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
6556
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
7052
6557
  } else if (issue2.code === "invalid_key") {
7053
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6558
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7054
6559
  } else if (issue2.code === "invalid_element") {
7055
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
6560
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
7056
6561
  } else {
7057
- const fullpath = [...path8, ...issue2.path];
6562
+ const fullpath = [...path7, ...issue2.path];
7058
6563
  if (fullpath.length === 0) {
7059
6564
  result.errors.push(mapper(issue2));
7060
6565
  continue;
@@ -7086,8 +6591,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
7086
6591
  }
7087
6592
  function toDotPath(_path) {
7088
6593
  const segs = [];
7089
- const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
7090
- for (const seg of path8) {
6594
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
6595
+ for (const seg of path7) {
7091
6596
  if (typeof seg === "number")
7092
6597
  segs.push(`[${seg}]`);
7093
6598
  else if (typeof seg === "symbol")
@@ -19546,13 +19051,13 @@ function resolveRef(ref, ctx) {
19546
19051
  if (!ref.startsWith("#")) {
19547
19052
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
19548
19053
  }
19549
- const path8 = ref.slice(1).split("/").filter(Boolean);
19550
- if (path8.length === 0) {
19054
+ const path7 = ref.slice(1).split("/").filter(Boolean);
19055
+ if (path7.length === 0) {
19551
19056
  return ctx.rootSchema;
19552
19057
  }
19553
19058
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
19554
- if (path8[0] === defsKey) {
19555
- const key = path8[1];
19059
+ if (path7[0] === defsKey) {
19060
+ const key = path7[1];
19556
19061
  if (!key || !ctx.defs[key]) {
19557
19062
  throw new Error(`Reference not found: ${ref}`);
19558
19063
  }
@@ -19959,6 +19464,16 @@ function date4(params) {
19959
19464
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
19960
19465
  config(en_default());
19961
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
+ }
19962
19477
  function formatHandle(name, discriminator) {
19963
19478
  return `${name}#${discriminator}`;
19964
19479
  }
@@ -20549,7 +20064,7 @@ function sql(strings, ...params) {
20549
20064
  return new SQL([new StringChunk(str)]);
20550
20065
  }
20551
20066
  sql2.raw = raw;
20552
- function join9(chunks, separator) {
20067
+ function join8(chunks, separator) {
20553
20068
  const result = [];
20554
20069
  for (const [i, chunk] of chunks.entries()) {
20555
20070
  if (i > 0 && separator !== undefined) {
@@ -20559,7 +20074,7 @@ function sql(strings, ...params) {
20559
20074
  }
20560
20075
  return new SQL(result);
20561
20076
  }
20562
- sql2.join = join9;
20077
+ sql2.join = join8;
20563
20078
  function identifier(value) {
20564
20079
  return new Name(value);
20565
20080
  }
@@ -21337,7 +20852,7 @@ class PrimaryKey {
21337
20852
  }
21338
20853
 
21339
20854
  // ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/index.js
21340
- import { webcrypto as crypto2 } from "node:crypto";
20855
+ import { webcrypto as crypto } from "node:crypto";
21341
20856
 
21342
20857
  // ../../node_modules/.pnpm/nanoid@5.1.16/node_modules/nanoid/url-alphabet/index.js
21343
20858
  var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
@@ -21352,10 +20867,10 @@ function fillPool(bytes) {
21352
20867
  try {
21353
20868
  if (!pool || pool.length < bytes) {
21354
20869
  pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
21355
- crypto2.getRandomValues(pool);
20870
+ crypto.getRandomValues(pool);
21356
20871
  poolOffset = 0;
21357
20872
  } else if (poolOffset + bytes > pool.length) {
21358
- crypto2.getRandomValues(pool);
20873
+ crypto.getRandomValues(pool);
21359
20874
  poolOffset = 0;
21360
20875
  }
21361
20876
  } catch (e) {
@@ -22192,6 +21707,81 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
22192
21707
  ]);
22193
21708
 
22194
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
+ }
22195
21785
  var HostCommandSchema = exports_external.discriminatedUnion("type", [
22196
21786
  exports_external.object({
22197
21787
  type: exports_external.literal("agent:wake"),
@@ -22232,6 +21822,9 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
22232
21822
  launchId: exports_external.string().min(1)
22233
21823
  }))
22234
21824
  }),
21825
+ exports_external.strictObject({
21826
+ type: exports_external.literal("machine:update")
21827
+ }),
22235
21828
  exports_external.object({
22236
21829
  type: exports_external.literal("bot:added"),
22237
21830
  botId: exports_external.string().min(1),
@@ -22256,16 +21849,641 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
22256
21849
  }),
22257
21850
  DiagnosticCollectCommandSchema
22258
21851
  ]);
22259
- // src/server/wsControlChannel.ts
22260
- var WS_CONTROL_COMMAND_CONSUMED = Symbol("ws-control-command-consumed");
22261
- var DEFAULT_PING_INTERVAL_MS = 15000;
22262
- var DEFAULT_PONG_TIMEOUT_MS = 30000;
22263
- var DEFAULT_RECONNECT_BASE_MS = 500;
22264
- var DEFAULT_RECONNECT_MAX_MS = 30000;
22265
- function describeErr(err) {
22266
- 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");
22267
21864
  }
22268
-
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);
22485
+ }
22486
+
22269
22487
  class WsControlChannel {
22270
22488
  opts;
22271
22489
  statusValue = "idle";
@@ -23273,6 +23491,108 @@ function createDiagnosticsCommandListener(options) {
23273
23491
  };
23274
23492
  }
23275
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
+
23276
23596
  // src/daemon/createDaemon.ts
23277
23597
  var WARMUP_BACKOFF_MS = [250, 500, 1000, 2000, 4000];
23278
23598
  var WARMUP_CEILING_MS = 30000;
@@ -23403,6 +23723,7 @@ async function createDaemon(opts) {
23403
23723
  });
23404
23724
  let channelRef = null;
23405
23725
  let managerRef = null;
23726
+ let reminderSchedulerRef = null;
23406
23727
  const emitBotAuditEvent = (agentId, event, context) => {
23407
23728
  channelRef?.reportBotAuditEvent?.({
23408
23729
  type: "bot_audit_event",
@@ -23416,6 +23737,7 @@ async function createDaemon(opts) {
23416
23737
  const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
23417
23738
  const proxy = await startCredentialProxy(broker, {
23418
23739
  onInboxPullResponse: (agentId, messages) => timeline2.appendEntryForAgent(agentId, messages),
23740
+ onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
23419
23741
  onProxyRequest: (agentId, method, pathname) => {
23420
23742
  const subcommand = deriveAuditLogSubcommand(pathname, method);
23421
23743
  if (!subcommand)
@@ -23688,6 +24010,10 @@ async function createDaemon(opts) {
23688
24010
  });
23689
24011
  managerRef = manager;
23690
24012
  manager.start();
24013
+ reminderSchedulerRef = new MessageReminderScheduler({
24014
+ deliver: (agentId, message2) => manager.deliver(agentId, message2),
24015
+ ...opts.messageReminderClock
24016
+ });
23691
24017
  let statusTimer = null;
23692
24018
  if (opts.statusFilePath) {
23693
24019
  const statusPath = opts.statusFilePath;
@@ -23733,10 +24059,26 @@ async function createDaemon(opts) {
23733
24059
  },
23734
24060
  formatUnreadNoticeText: (notice) => `You have unread messages in channel ${notice.channel}.`
23735
24061
  });
24062
+ channel2.onCommand(createSelfUpdateCommandListener(opts.handleSelfUpdate));
23736
24063
  channel2.onCommand(createDiagnosticsCommandListener({
23737
24064
  handleDiagnosticCommand: opts.handleDiagnosticCommand,
23738
24065
  reportDiagnosticFailure: opts.reportDiagnosticFailure
23739
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
+ });
23740
24082
  channel2.onCommand((cmd) => {
23741
24083
  handleBotFrame(cmd);
23742
24084
  });
@@ -23755,6 +24097,7 @@ async function createDaemon(opts) {
23755
24097
  },
23756
24098
  proxyUrl: proxy.url,
23757
24099
  stop: async () => {
24100
+ reminderSchedulerRef?.clearAll();
23758
24101
  for (const agentId of [...typingHeartbeats.keys()]) {
23759
24102
  emitTypingStopsAndClear(agentId);
23760
24103
  }
@@ -23838,6 +24181,7 @@ export {
23838
24181
  PiDriver,
23839
24182
  OpenCodeDriver,
23840
24183
  MAX_APM_GATED_STEERING_EVENTS,
24184
+ LOCAL_MESSAGE_REMINDER_PATH,
23841
24185
  KimiDriver,
23842
24186
  GeminiDriver,
23843
24187
  DEFAULT_STOPPING_STUCK_THRESHOLD_MS,