@alook/daemon 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4819,382 +4819,6 @@ ${this.opts.wakePromptFooter}` : text;
4819
4819
  }
4820
4820
  }
4821
4821
  }
4822
- // src/manager/agentRouter.ts
4823
- var DEFINITIVE_EXECUTABLE_FAILURES = new Set(["ENOENT", "EACCES", "ENOEXEC", "EPERM"]);
4824
-
4825
- class UnknownBotError extends Error {
4826
- botId;
4827
- constructor(botId) {
4828
- super(`Bot not in this daemon's cache: ${botId}`);
4829
- this.botId = botId;
4830
- this.name = "UnknownBotError";
4831
- }
4832
- }
4833
-
4834
- class BotEnrollFailedError extends Error {
4835
- botId;
4836
- constructor(botId, cause) {
4837
- super(`Failed to enroll bot ${botId}: ${cause instanceof Error ? cause.message : String(cause)}`);
4838
- this.botId = botId;
4839
- this.name = "BotEnrollFailedError";
4840
- }
4841
- }
4842
- function classifyErrorCode(err) {
4843
- if (err instanceof UnknownBotError)
4844
- return "bot_unknown";
4845
- if (err instanceof BotEnrollFailedError)
4846
- return "bot_enroll_failed";
4847
- if (err instanceof UnknownRuntimeError)
4848
- return "bot_runtime_missing";
4849
- return "internal_error";
4850
- }
4851
-
4852
- class UnknownRuntimeError extends Error {
4853
- requested;
4854
- available;
4855
- constructor(requested, available) {
4856
- super(`Runtime not available on this host: ${requested ?? "<unspecified>"} — installed: ${available.join(", ") || "(none)"}`);
4857
- this.requested = requested;
4858
- this.available = available;
4859
- this.name = "UnknownRuntimeError";
4860
- }
4861
- }
4862
- function defaultFormatUnreadNoticeText(notice) {
4863
- return `You have unread messages in channel ${notice.channel}.`;
4864
- }
4865
- 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.";
4866
- var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. Continue any unfinished work.";
4867
- function buildNapRewakePrompt(handoff) {
4868
- return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
4869
-
4870
- ` + handoff + `
4871
-
4872
- 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.";
4873
- }
4874
-
4875
- class AgentRouter {
4876
- opts;
4877
- running = new Set;
4878
- runtimes = new Map;
4879
- pendingResend = false;
4880
- scheduleResend;
4881
- log;
4882
- constructor(opts) {
4883
- this.opts = opts;
4884
- this.log = opts.logger ?? createLogger({ header: "@alook/daemon:router" });
4885
- this.scheduleResend = opts.scheduleReadyResend ?? queueMicrotask.bind(globalThis);
4886
- for (const r of opts.runtimeReport) {
4887
- this.runtimes.set(r.id, {
4888
- id: r.id,
4889
- version: r.version,
4890
- status: r.status ?? "healthy",
4891
- lastError: r.lastError,
4892
- lastErrorAt: r.lastErrorAt
4893
- });
4894
- }
4895
- }
4896
- async start() {
4897
- this.opts.channel.onCommand((cmd) => this.onCommand(cmd));
4898
- this.opts.channel.onResync?.(() => ({
4899
- ready: this.buildReady(),
4900
- sessions: this.opts.manager.liveSessionReports(),
4901
- activities: this.opts.manager.liveAgentActivities()
4902
- }));
4903
- await this.opts.channel.reportReady(this.buildReady());
4904
- }
4905
- buildReady() {
4906
- return {
4907
- runtimeReport: [...this.runtimes.values()],
4908
- runningAgents: [...this.running],
4909
- hostname: this.opts.hostname,
4910
- platform: this.opts.platform,
4911
- arch: this.opts.arch,
4912
- osRelease: this.opts.osRelease,
4913
- daemonVersion: this.opts.daemonVersion
4914
- };
4915
- }
4916
- healthyRuntimeIds() {
4917
- const out = [];
4918
- for (const r of this.runtimes.values()) {
4919
- if (r.status === "healthy")
4920
- out.push(r.id);
4921
- }
4922
- return out;
4923
- }
4924
- isRuntimeHealthy(id) {
4925
- return this.runtimes.get(id)?.status === "healthy";
4926
- }
4927
- recordRuntimeSpawnFailure(id, reason) {
4928
- if (!DEFINITIVE_EXECUTABLE_FAILURES.has(reason))
4929
- return;
4930
- this.markRuntimeUnhealthy(id, reason);
4931
- }
4932
- markRuntimeUnhealthy(id, reason) {
4933
- const existing = this.runtimes.get(id);
4934
- if (!existing)
4935
- return;
4936
- const nowIso = new Date().toISOString();
4937
- if (existing.status === "unhealthy" && existing.lastError === reason)
4938
- return;
4939
- this.runtimes.set(id, {
4940
- ...existing,
4941
- status: "unhealthy",
4942
- lastError: reason,
4943
- lastErrorAt: nowIso
4944
- });
4945
- this.log.warn("runtime marked unhealthy", { runtimeId: id, reason });
4946
- this.scheduleReadyFrameResend();
4947
- }
4948
- markRuntimeHealthy(id) {
4949
- const existing = this.runtimes.get(id);
4950
- if (!existing)
4951
- return;
4952
- if (existing.status === "healthy" && !existing.lastError && !existing.lastErrorAt)
4953
- return;
4954
- this.runtimes.set(id, {
4955
- id: existing.id,
4956
- version: existing.version,
4957
- status: "healthy"
4958
- });
4959
- this.log.info("runtime marked healthy again", { runtimeId: id });
4960
- this.scheduleReadyFrameResend();
4961
- }
4962
- markLocallyStopped(agentId) {
4963
- if (!this.running.delete(agentId))
4964
- return;
4965
- this.log.info("agent removed from running set (local stop)", { agentId });
4966
- this.scheduleReadyFrameResend();
4967
- }
4968
- scheduleReadyFrameResend() {
4969
- if (this.pendingResend)
4970
- return;
4971
- this.pendingResend = true;
4972
- this.scheduleResend(() => {
4973
- this.pendingResend = false;
4974
- try {
4975
- this.opts.channel.sendReady?.(this.buildReady());
4976
- } catch {}
4977
- });
4978
- }
4979
- async runRestartCommand(agentId, launchId, opName, run) {
4980
- this.log.info(`${opName} received`, { agentId, launchId });
4981
- try {
4982
- await this.opts.onBeforeAgent?.(agentId);
4983
- await run();
4984
- this.running.add(agentId);
4985
- this.scheduleReadyFrameResend();
4986
- this.log.info(`${opName} ok`, { agentId });
4987
- } catch (err) {
4988
- if (err instanceof UnknownRuntimeError) {
4989
- const frame = {
4990
- type: "session.error",
4991
- code: "runtime_not_available",
4992
- agentId,
4993
- launchId,
4994
- payload: { requested: err.requested ?? null, available: err.available }
4995
- };
4996
- await this.opts.channel.reportSessionError?.(frame);
4997
- this.log.info(`${opName} error`, { agentId, "error.code": "runtime_not_available" });
4998
- return;
4999
- }
5000
- const code = classifyErrorCode(err);
5001
- await this.opts.channel.reportWakeAck?.({
5002
- agentId,
5003
- launchId,
5004
- status: "error",
5005
- error: { code, message: err instanceof Error ? err.message : String(err) }
5006
- });
5007
- this.log.warn(`${opName} failed`, {
5008
- agentId,
5009
- "error.code": code,
5010
- err: err instanceof Error ? err.message : String(err)
5011
- });
5012
- }
5013
- }
5014
- async onCommand(cmd) {
5015
- switch (cmd.type) {
5016
- case "agent:wake":
5017
- this.log.info("agent:wake received", {
5018
- agentId: cmd.agentId,
5019
- channel: cmd.unreadNotice.channel,
5020
- latestSeq: cmd.unreadNotice.latestSeq
5021
- });
5022
- try {
5023
- const beforeStatus = this.opts.manager.snapshot?.().agents?.[cmd.agentId]?.status ?? "unregistered";
5024
- const wasActive = this.opts.typingTracker?.hasAny(cmd.agentId) ?? false;
5025
- await this.opts.onBeforeAgent?.(cmd.agentId);
5026
- this.opts.manager.register(cmd.agentId, {
5027
- runtimeConfig: cmd.config,
5028
- sessionId: cmd.sessionId,
5029
- launchId: cmd.launchId
5030
- });
5031
- this.running.add(cmd.agentId);
5032
- const channelScope = cmd.unreadNotice.channelId;
5033
- if (channelScope)
5034
- this.opts.typingTracker?.add(cmd.agentId, channelScope);
5035
- const text = (this.opts.formatUnreadNoticeText ?? defaultFormatUnreadNoticeText)(cmd.unreadNotice);
5036
- const producedEffect = this.opts.manager.deliver(cmd.agentId, {
5037
- seq: cmd.unreadNotice.latestSeq,
5038
- text
5039
- });
5040
- if (producedEffect === false && beforeStatus === "running") {
5041
- this.log.info("agent:wake produced no effect (coalesced onto running agent)", {
5042
- agentId: cmd.agentId,
5043
- latestSeq: cmd.unreadNotice.latestSeq
5044
- });
5045
- }
5046
- if (channelScope && wasActive && beforeStatus === "running") {
5047
- this.opts.channel.reportAgentTyping?.({
5048
- agentId: cmd.agentId,
5049
- channelId: channelScope
5050
- });
5051
- }
5052
- await this.opts.channel.reportWakeAck?.({
5053
- agentId: cmd.agentId,
5054
- launchId: cmd.launchId,
5055
- status: "ok"
5056
- });
5057
- this.log.info("agent:wake ack", { agentId: cmd.agentId, status: "ok" });
5058
- } catch (err) {
5059
- if (err instanceof UnknownRuntimeError) {
5060
- const frame = {
5061
- type: "session.error",
5062
- code: "runtime_not_available",
5063
- agentId: cmd.agentId,
5064
- launchId: cmd.launchId,
5065
- payload: {
5066
- requested: err.requested ?? null,
5067
- available: err.available
5068
- }
5069
- };
5070
- await this.opts.channel.reportSessionError?.(frame);
5071
- await this.opts.channel.reportWakeAck?.({
5072
- agentId: cmd.agentId,
5073
- launchId: cmd.launchId,
5074
- status: "error",
5075
- error: {
5076
- code: "bot_runtime_missing",
5077
- message: err.message
5078
- }
5079
- });
5080
- this.log.info("agent:wake ack", {
5081
- agentId: cmd.agentId,
5082
- status: "error",
5083
- "error.code": "bot_runtime_missing"
5084
- });
5085
- return;
5086
- }
5087
- {
5088
- const code = classifyErrorCode(err);
5089
- await this.opts.channel.reportWakeAck?.({
5090
- agentId: cmd.agentId,
5091
- launchId: cmd.launchId,
5092
- status: "error",
5093
- error: {
5094
- code,
5095
- message: err instanceof Error ? err.message : String(err)
5096
- }
5097
- });
5098
- this.log.info("agent:wake ack", { agentId: cmd.agentId, status: "error", "error.code": code });
5099
- }
5100
- return;
5101
- }
5102
- break;
5103
- case "agent:reset":
5104
- await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:reset", () => this.opts.manager.resetSession(cmd.agentId, {
5105
- runtimeConfig: cmd.config,
5106
- launchId: cmd.launchId,
5107
- rewakePrompt: REWAKE_PROMPT
5108
- }));
5109
- break;
5110
- case "machine:reset_all":
5111
- for (const r of cmd.resets) {
5112
- await this.runRestartCommand(r.agentId, r.launchId, "agent:reset", () => this.opts.manager.resetSession(r.agentId, {
5113
- runtimeConfig: r.config,
5114
- launchId: r.launchId,
5115
- rewakePrompt: REWAKE_PROMPT
5116
- }));
5117
- }
5118
- break;
5119
- case "agent:nap":
5120
- await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:nap", () => this.opts.manager.resetSession(cmd.agentId, {
5121
- runtimeConfig: cmd.config,
5122
- launchId: cmd.launchId,
5123
- rewakePrompt: buildNapRewakePrompt(cmd.handoff),
5124
- barrierType: "nap"
5125
- }));
5126
- break;
5127
- case "agent:model_switch":
5128
- await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:model_switch", () => this.opts.manager.switchModel(cmd.agentId, {
5129
- runtimeConfig: cmd.config,
5130
- launchId: cmd.launchId,
5131
- rewakePrompt: MODEL_SWITCH_REWAKE_PROMPT
5132
- }));
5133
- break;
5134
- case "agent:stop":
5135
- this.log.info("agent:stop received", { agentId: cmd.agentId });
5136
- try {
5137
- this.running.delete(cmd.agentId);
5138
- this.opts.manager.stop(cmd.agentId);
5139
- await this.opts.channel.reportStoppedAck?.({
5140
- agentId: cmd.agentId,
5141
- status: "ok"
5142
- });
5143
- this.log.info("agent:stop ack", { agentId: cmd.agentId, status: "ok" });
5144
- } catch (err) {
5145
- const code = classifyErrorCode(err);
5146
- await this.opts.channel.reportStoppedAck?.({
5147
- agentId: cmd.agentId,
5148
- status: "error",
5149
- error: {
5150
- code,
5151
- message: err instanceof Error ? err.message : String(err)
5152
- }
5153
- });
5154
- this.log.info("agent:stop ack", { agentId: cmd.agentId, status: "error", "error.code": code });
5155
- }
5156
- break;
5157
- case "bot:added":
5158
- case "bot:updated":
5159
- case "bot:removed":
5160
- break;
5161
- }
5162
- }
5163
- }
5164
- // src/manager/typingScopeTracker.ts
5165
- function createTypingScopeTracker() {
5166
- const scopes = new Map;
5167
- return {
5168
- add(agentId, channelId) {
5169
- let set = scopes.get(agentId);
5170
- if (!set) {
5171
- set = new Set;
5172
- scopes.set(agentId, set);
5173
- }
5174
- set.add(channelId);
5175
- },
5176
- snapshot(agentId) {
5177
- const set = scopes.get(agentId);
5178
- return set ? [...set] : [];
5179
- },
5180
- hasAny(agentId) {
5181
- const set = scopes.get(agentId);
5182
- return !!set && set.size > 0;
5183
- },
5184
- clear(agentId) {
5185
- scopes.delete(agentId);
5186
- }
5187
- };
5188
- }
5189
- // src/credentials/credentialProxy.ts
5190
- import * as crypto2 from "crypto";
5191
- import * as fs5 from "fs";
5192
- import * as http from "http";
5193
- import * as https from "https";
5194
- import * as os2 from "os";
5195
- import * as path7 from "path";
5196
- import { URL as URL2 } from "url";
5197
-
5198
4822
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
5199
4823
  var exports_external = {};
5200
4824
  __export(exports_external, {
@@ -21717,6 +21341,7 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
21717
21341
 
21718
21342
  // ../shared/src/community-cli-contract.ts
21719
21343
  var DM_SERVER = ".dm";
21344
+ var CONTROL_HEARTBEAT_CAPABILITY = "control-heartbeat-v1";
21720
21345
  function parseRef(ref) {
21721
21346
  if (!ref.startsWith("/"))
21722
21347
  throw new Error(`ref must start with "/": ${ref}`);
@@ -21728,137 +21353,516 @@ function parseRef(ref) {
21728
21353
  if (server !== DM_SERVER && !parseNameAndTag(server)) {
21729
21354
  throw new Error(`server ref must use a name#discriminator handle: ${ref}`);
21730
21355
  }
21731
- let seq;
21732
- if (parts.length >= 3 && parts[parts.length - 1].startsWith("#")) {
21733
- const tail = parseThreadTail(parts[parts.length - 1]);
21734
- return { server, channel: parts[1], ...tail };
21356
+ let seq;
21357
+ if (parts.length >= 3 && parts[parts.length - 1].startsWith("#")) {
21358
+ const tail = parseThreadTail(parts[parts.length - 1]);
21359
+ return { server, channel: parts[1], ...tail };
21360
+ }
21361
+ if (parts.length >= 3 && server !== DM_SERVER) {
21362
+ throw new Error(`ref has too many segments: ${ref}`);
21363
+ }
21364
+ const chSeg = parts[1];
21365
+ if (server === DM_SERVER) {
21366
+ const lastHash = chSeg.lastIndexOf("#");
21367
+ if (lastHash < 0)
21368
+ return { server, channel: chSeg };
21369
+ const firstHash = chSeg.indexOf("#");
21370
+ const tail = chSeg.slice(lastHash + 1);
21371
+ const isBareHandle = firstHash === lastHash && /^\d{4,}$/.test(tail);
21372
+ if (isBareHandle)
21373
+ return { server, channel: chSeg };
21374
+ const tailNum = Number(tail.startsWith("#") ? tail.slice(1) : tail);
21375
+ if (!Number.isFinite(tailNum))
21376
+ return { server, channel: chSeg };
21377
+ seq = parseSeq(tail);
21378
+ return { server, channel: chSeg.slice(0, lastHash), seq };
21379
+ }
21380
+ const hashIdx = chSeg.indexOf("#");
21381
+ if (hashIdx >= 0) {
21382
+ seq = parseSeq(chSeg.slice(hashIdx));
21383
+ return { server, channel: chSeg.slice(0, hashIdx), seq };
21384
+ }
21385
+ return { server, channel: chSeg };
21386
+ }
21387
+ function parseThreadTail(segment) {
21388
+ const stripped = segment.startsWith("#") ? segment.slice(1) : segment;
21389
+ const tokens = stripped.split("#");
21390
+ if (tokens.length < 1 || tokens.length > 2) {
21391
+ throw new Error(`bad thread ref tail: #${stripped}`);
21392
+ }
21393
+ for (const t of tokens) {
21394
+ if (!t)
21395
+ throw new Error(`bad thread ref tail: #${stripped} (empty seq)`);
21396
+ }
21397
+ const threadRootSeq = parseSeq(tokens[0]);
21398
+ if (tokens.length === 1)
21399
+ return { threadRootSeq };
21400
+ return { threadRootSeq, seq: parseSeq(tokens[1]) };
21401
+ }
21402
+ function formatRef(p) {
21403
+ if (p.seq !== undefined && p.threadRootSeq === undefined) {
21404
+ throw new Error("formatRef: seq without threadRootSeq is not supported");
21405
+ }
21406
+ const base = `/${p.server}/${p.channel}`;
21407
+ if (p.threadRootSeq === undefined)
21408
+ return base;
21409
+ if (p.seq === undefined)
21410
+ return `${base}/#${p.threadRootSeq}`;
21411
+ return `${base}/#${p.threadRootSeq}#${p.seq}`;
21412
+ }
21413
+ function parseSeq(s) {
21414
+ const n = Number(s.startsWith("#") ? s.slice(1) : s);
21415
+ if (!Number.isFinite(n))
21416
+ throw new Error(`bad seq: ${s}`);
21417
+ return n;
21418
+ }
21419
+ var HostCommandSchema = exports_external.discriminatedUnion("type", [
21420
+ exports_external.strictObject({
21421
+ type: exports_external.literal("machine:heartbeat"),
21422
+ nonce: exports_external.string().min(1).max(128)
21423
+ }),
21424
+ exports_external.object({
21425
+ type: exports_external.literal("agent:wake"),
21426
+ agentId: exports_external.string().min(1),
21427
+ config: exports_external.unknown(),
21428
+ sessionId: exports_external.string().optional(),
21429
+ launchId: exports_external.string().min(1),
21430
+ unreadNotice: exports_external.unknown()
21431
+ }),
21432
+ exports_external.object({
21433
+ type: exports_external.literal("agent:stop"),
21434
+ agentId: exports_external.string().min(1)
21435
+ }),
21436
+ exports_external.object({
21437
+ type: exports_external.literal("agent:reset"),
21438
+ agentId: exports_external.string().min(1),
21439
+ config: exports_external.unknown(),
21440
+ launchId: exports_external.string().min(1)
21441
+ }),
21442
+ exports_external.object({
21443
+ type: exports_external.literal("agent:nap"),
21444
+ agentId: exports_external.string().min(1),
21445
+ config: exports_external.unknown(),
21446
+ launchId: exports_external.string().min(1),
21447
+ handoff: exports_external.string().min(1)
21448
+ }),
21449
+ exports_external.object({
21450
+ type: exports_external.literal("agent:model_switch"),
21451
+ agentId: exports_external.string().min(1),
21452
+ config: exports_external.unknown(),
21453
+ launchId: exports_external.string().min(1)
21454
+ }),
21455
+ exports_external.object({
21456
+ type: exports_external.literal("machine:reset_all"),
21457
+ resets: exports_external.array(exports_external.object({
21458
+ agentId: exports_external.string().min(1),
21459
+ config: exports_external.unknown(),
21460
+ launchId: exports_external.string().min(1)
21461
+ }))
21462
+ }),
21463
+ exports_external.strictObject({
21464
+ type: exports_external.literal("machine:update")
21465
+ }),
21466
+ exports_external.object({
21467
+ type: exports_external.literal("bot:added"),
21468
+ botId: exports_external.string().min(1),
21469
+ name: exports_external.string().optional(),
21470
+ discriminator: exports_external.string().optional(),
21471
+ description: exports_external.string().optional(),
21472
+ ownerName: exports_external.string().optional(),
21473
+ ownerDiscriminator: exports_external.string().optional()
21474
+ }),
21475
+ exports_external.object({
21476
+ type: exports_external.literal("bot:updated"),
21477
+ botId: exports_external.string().min(1),
21478
+ name: exports_external.string().optional(),
21479
+ discriminator: exports_external.string().optional(),
21480
+ description: exports_external.string().optional(),
21481
+ ownerName: exports_external.string().optional(),
21482
+ ownerDiscriminator: exports_external.string().optional()
21483
+ }),
21484
+ exports_external.object({
21485
+ type: exports_external.literal("bot:removed"),
21486
+ botId: exports_external.string().min(1)
21487
+ }),
21488
+ DiagnosticCollectCommandSchema
21489
+ ]);
21490
+ // src/manager/agentRouter.ts
21491
+ var DEFINITIVE_EXECUTABLE_FAILURES = new Set(["ENOENT", "EACCES", "ENOEXEC", "EPERM"]);
21492
+
21493
+ class UnknownBotError extends Error {
21494
+ botId;
21495
+ constructor(botId) {
21496
+ super(`Bot not in this daemon's cache: ${botId}`);
21497
+ this.botId = botId;
21498
+ this.name = "UnknownBotError";
21499
+ }
21500
+ }
21501
+
21502
+ class BotEnrollFailedError extends Error {
21503
+ botId;
21504
+ constructor(botId, cause) {
21505
+ super(`Failed to enroll bot ${botId}: ${cause instanceof Error ? cause.message : String(cause)}`);
21506
+ this.botId = botId;
21507
+ this.name = "BotEnrollFailedError";
21508
+ }
21509
+ }
21510
+ function classifyErrorCode(err) {
21511
+ if (err instanceof UnknownBotError)
21512
+ return "bot_unknown";
21513
+ if (err instanceof BotEnrollFailedError)
21514
+ return "bot_enroll_failed";
21515
+ if (err instanceof UnknownRuntimeError)
21516
+ return "bot_runtime_missing";
21517
+ return "internal_error";
21518
+ }
21519
+
21520
+ class UnknownRuntimeError extends Error {
21521
+ requested;
21522
+ available;
21523
+ constructor(requested, available) {
21524
+ super(`Runtime not available on this host: ${requested ?? "<unspecified>"} — installed: ${available.join(", ") || "(none)"}`);
21525
+ this.requested = requested;
21526
+ this.available = available;
21527
+ this.name = "UnknownRuntimeError";
21528
+ }
21529
+ }
21530
+ function defaultFormatUnreadNoticeText(notice) {
21531
+ return `You have unread messages in channel ${notice.channel}.`;
21532
+ }
21533
+ 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.";
21534
+ var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. Continue any unfinished work.";
21535
+ function buildNapRewakePrompt(handoff) {
21536
+ return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
21537
+
21538
+ ` + handoff + `
21539
+
21540
+ 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.";
21541
+ }
21542
+
21543
+ class AgentRouter {
21544
+ opts;
21545
+ running = new Set;
21546
+ runtimes = new Map;
21547
+ pendingResend = false;
21548
+ scheduleResend;
21549
+ log;
21550
+ constructor(opts) {
21551
+ this.opts = opts;
21552
+ this.log = opts.logger ?? createLogger({ header: "@alook/daemon:router" });
21553
+ this.scheduleResend = opts.scheduleReadyResend ?? queueMicrotask.bind(globalThis);
21554
+ for (const r of opts.runtimeReport) {
21555
+ this.runtimes.set(r.id, {
21556
+ id: r.id,
21557
+ version: r.version,
21558
+ status: r.status ?? "healthy",
21559
+ lastError: r.lastError,
21560
+ lastErrorAt: r.lastErrorAt
21561
+ });
21562
+ }
21563
+ }
21564
+ async start() {
21565
+ this.opts.channel.onCommand((cmd) => this.onCommand(cmd));
21566
+ this.opts.channel.onResync?.(() => ({
21567
+ ready: this.buildReady(),
21568
+ sessions: this.opts.manager.liveSessionReports(),
21569
+ activities: this.opts.manager.liveAgentActivities()
21570
+ }));
21571
+ await this.opts.channel.reportReady(this.buildReady());
21572
+ }
21573
+ buildReady() {
21574
+ return {
21575
+ runtimeReport: [...this.runtimes.values()],
21576
+ capabilities: [CONTROL_HEARTBEAT_CAPABILITY],
21577
+ runningAgents: [...this.running],
21578
+ hostname: this.opts.hostname,
21579
+ platform: this.opts.platform,
21580
+ arch: this.opts.arch,
21581
+ osRelease: this.opts.osRelease,
21582
+ daemonVersion: this.opts.daemonVersion
21583
+ };
21584
+ }
21585
+ healthyRuntimeIds() {
21586
+ const out = [];
21587
+ for (const r of this.runtimes.values()) {
21588
+ if (r.status === "healthy")
21589
+ out.push(r.id);
21590
+ }
21591
+ return out;
21592
+ }
21593
+ isRuntimeHealthy(id) {
21594
+ return this.runtimes.get(id)?.status === "healthy";
21735
21595
  }
21736
- if (parts.length >= 3 && server !== DM_SERVER) {
21737
- throw new Error(`ref has too many segments: ${ref}`);
21596
+ recordRuntimeSpawnFailure(id, reason) {
21597
+ if (!DEFINITIVE_EXECUTABLE_FAILURES.has(reason))
21598
+ return;
21599
+ this.markRuntimeUnhealthy(id, reason);
21738
21600
  }
21739
- const chSeg = parts[1];
21740
- if (server === DM_SERVER) {
21741
- const lastHash = chSeg.lastIndexOf("#");
21742
- if (lastHash < 0)
21743
- return { server, channel: chSeg };
21744
- const firstHash = chSeg.indexOf("#");
21745
- const tail = chSeg.slice(lastHash + 1);
21746
- const isBareHandle = firstHash === lastHash && /^\d{4,}$/.test(tail);
21747
- if (isBareHandle)
21748
- return { server, channel: chSeg };
21749
- const tailNum = Number(tail.startsWith("#") ? tail.slice(1) : tail);
21750
- if (!Number.isFinite(tailNum))
21751
- return { server, channel: chSeg };
21752
- seq = parseSeq(tail);
21753
- return { server, channel: chSeg.slice(0, lastHash), seq };
21601
+ markRuntimeUnhealthy(id, reason) {
21602
+ const existing = this.runtimes.get(id);
21603
+ if (!existing)
21604
+ return;
21605
+ const nowIso = new Date().toISOString();
21606
+ if (existing.status === "unhealthy" && existing.lastError === reason)
21607
+ return;
21608
+ this.runtimes.set(id, {
21609
+ ...existing,
21610
+ status: "unhealthy",
21611
+ lastError: reason,
21612
+ lastErrorAt: nowIso
21613
+ });
21614
+ this.log.warn("runtime marked unhealthy", { runtimeId: id, reason });
21615
+ this.scheduleReadyFrameResend();
21754
21616
  }
21755
- const hashIdx = chSeg.indexOf("#");
21756
- if (hashIdx >= 0) {
21757
- seq = parseSeq(chSeg.slice(hashIdx));
21758
- return { server, channel: chSeg.slice(0, hashIdx), seq };
21617
+ markRuntimeHealthy(id) {
21618
+ const existing = this.runtimes.get(id);
21619
+ if (!existing)
21620
+ return;
21621
+ if (existing.status === "healthy" && !existing.lastError && !existing.lastErrorAt)
21622
+ return;
21623
+ this.runtimes.set(id, {
21624
+ id: existing.id,
21625
+ version: existing.version,
21626
+ status: "healthy"
21627
+ });
21628
+ this.log.info("runtime marked healthy again", { runtimeId: id });
21629
+ this.scheduleReadyFrameResend();
21759
21630
  }
21760
- return { server, channel: chSeg };
21761
- }
21762
- function parseThreadTail(segment) {
21763
- const stripped = segment.startsWith("#") ? segment.slice(1) : segment;
21764
- const tokens = stripped.split("#");
21765
- if (tokens.length < 1 || tokens.length > 2) {
21766
- throw new Error(`bad thread ref tail: #${stripped}`);
21631
+ markLocallyStopped(agentId) {
21632
+ if (!this.running.delete(agentId))
21633
+ return;
21634
+ this.log.info("agent removed from running set (local stop)", { agentId });
21635
+ this.scheduleReadyFrameResend();
21767
21636
  }
21768
- for (const t of tokens) {
21769
- if (!t)
21770
- throw new Error(`bad thread ref tail: #${stripped} (empty seq)`);
21637
+ scheduleReadyFrameResend() {
21638
+ if (this.pendingResend)
21639
+ return;
21640
+ this.pendingResend = true;
21641
+ this.scheduleResend(() => {
21642
+ this.pendingResend = false;
21643
+ try {
21644
+ this.opts.channel.sendReady?.(this.buildReady());
21645
+ } catch {}
21646
+ });
21771
21647
  }
21772
- const threadRootSeq = parseSeq(tokens[0]);
21773
- if (tokens.length === 1)
21774
- return { threadRootSeq };
21775
- return { threadRootSeq, seq: parseSeq(tokens[1]) };
21776
- }
21777
- function formatRef(p) {
21778
- if (p.seq !== undefined && p.threadRootSeq === undefined) {
21779
- throw new Error("formatRef: seq without threadRootSeq is not supported");
21648
+ async runRestartCommand(agentId, launchId, opName, run) {
21649
+ this.log.info(`${opName} received`, { agentId, launchId });
21650
+ try {
21651
+ await this.opts.onBeforeAgent?.(agentId);
21652
+ await run();
21653
+ this.running.add(agentId);
21654
+ this.scheduleReadyFrameResend();
21655
+ this.log.info(`${opName} ok`, { agentId });
21656
+ } catch (err) {
21657
+ if (err instanceof UnknownRuntimeError) {
21658
+ const frame = {
21659
+ type: "session.error",
21660
+ code: "runtime_not_available",
21661
+ agentId,
21662
+ launchId,
21663
+ payload: { requested: err.requested ?? null, available: err.available }
21664
+ };
21665
+ await this.opts.channel.reportSessionError?.(frame);
21666
+ this.log.info(`${opName} error`, { agentId, "error.code": "runtime_not_available" });
21667
+ return;
21668
+ }
21669
+ const code = classifyErrorCode(err);
21670
+ await this.opts.channel.reportWakeAck?.({
21671
+ agentId,
21672
+ launchId,
21673
+ status: "error",
21674
+ error: { code, message: err instanceof Error ? err.message : String(err) }
21675
+ });
21676
+ this.log.warn(`${opName} failed`, {
21677
+ agentId,
21678
+ "error.code": code,
21679
+ err: err instanceof Error ? err.message : String(err)
21680
+ });
21681
+ }
21682
+ }
21683
+ async onCommand(cmd) {
21684
+ switch (cmd.type) {
21685
+ case "agent:wake":
21686
+ this.log.info("agent:wake received", {
21687
+ agentId: cmd.agentId,
21688
+ channel: cmd.unreadNotice.channel,
21689
+ latestSeq: cmd.unreadNotice.latestSeq
21690
+ });
21691
+ try {
21692
+ const beforeStatus = this.opts.manager.snapshot?.().agents?.[cmd.agentId]?.status ?? "unregistered";
21693
+ const wasActive = this.opts.typingTracker?.hasAny(cmd.agentId) ?? false;
21694
+ await this.opts.onBeforeAgent?.(cmd.agentId);
21695
+ this.opts.manager.register(cmd.agentId, {
21696
+ runtimeConfig: cmd.config,
21697
+ sessionId: cmd.sessionId,
21698
+ launchId: cmd.launchId
21699
+ });
21700
+ this.running.add(cmd.agentId);
21701
+ const channelScope = cmd.unreadNotice.channelId;
21702
+ if (channelScope)
21703
+ this.opts.typingTracker?.add(cmd.agentId, channelScope);
21704
+ const text2 = (this.opts.formatUnreadNoticeText ?? defaultFormatUnreadNoticeText)(cmd.unreadNotice);
21705
+ const producedEffect = this.opts.manager.deliver(cmd.agentId, {
21706
+ seq: cmd.unreadNotice.latestSeq,
21707
+ text: text2
21708
+ });
21709
+ if (producedEffect === false && beforeStatus === "running") {
21710
+ this.log.info("agent:wake produced no effect (coalesced onto running agent)", {
21711
+ agentId: cmd.agentId,
21712
+ latestSeq: cmd.unreadNotice.latestSeq
21713
+ });
21714
+ }
21715
+ if (channelScope && wasActive && beforeStatus === "running") {
21716
+ this.opts.channel.reportAgentTyping?.({
21717
+ agentId: cmd.agentId,
21718
+ channelId: channelScope
21719
+ });
21720
+ }
21721
+ await this.opts.channel.reportWakeAck?.({
21722
+ agentId: cmd.agentId,
21723
+ launchId: cmd.launchId,
21724
+ status: "ok"
21725
+ });
21726
+ this.log.info("agent:wake ack", { agentId: cmd.agentId, status: "ok" });
21727
+ } catch (err) {
21728
+ if (err instanceof UnknownRuntimeError) {
21729
+ const frame = {
21730
+ type: "session.error",
21731
+ code: "runtime_not_available",
21732
+ agentId: cmd.agentId,
21733
+ launchId: cmd.launchId,
21734
+ payload: {
21735
+ requested: err.requested ?? null,
21736
+ available: err.available
21737
+ }
21738
+ };
21739
+ await this.opts.channel.reportSessionError?.(frame);
21740
+ await this.opts.channel.reportWakeAck?.({
21741
+ agentId: cmd.agentId,
21742
+ launchId: cmd.launchId,
21743
+ status: "error",
21744
+ error: {
21745
+ code: "bot_runtime_missing",
21746
+ message: err.message
21747
+ }
21748
+ });
21749
+ this.log.info("agent:wake ack", {
21750
+ agentId: cmd.agentId,
21751
+ status: "error",
21752
+ "error.code": "bot_runtime_missing"
21753
+ });
21754
+ return;
21755
+ }
21756
+ {
21757
+ const code = classifyErrorCode(err);
21758
+ await this.opts.channel.reportWakeAck?.({
21759
+ agentId: cmd.agentId,
21760
+ launchId: cmd.launchId,
21761
+ status: "error",
21762
+ error: {
21763
+ code,
21764
+ message: err instanceof Error ? err.message : String(err)
21765
+ }
21766
+ });
21767
+ this.log.info("agent:wake ack", { agentId: cmd.agentId, status: "error", "error.code": code });
21768
+ }
21769
+ return;
21770
+ }
21771
+ break;
21772
+ case "agent:reset":
21773
+ await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:reset", () => this.opts.manager.resetSession(cmd.agentId, {
21774
+ runtimeConfig: cmd.config,
21775
+ launchId: cmd.launchId,
21776
+ rewakePrompt: REWAKE_PROMPT
21777
+ }));
21778
+ break;
21779
+ case "machine:reset_all":
21780
+ for (const r of cmd.resets) {
21781
+ await this.runRestartCommand(r.agentId, r.launchId, "agent:reset", () => this.opts.manager.resetSession(r.agentId, {
21782
+ runtimeConfig: r.config,
21783
+ launchId: r.launchId,
21784
+ rewakePrompt: REWAKE_PROMPT
21785
+ }));
21786
+ }
21787
+ break;
21788
+ case "agent:nap":
21789
+ await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:nap", () => this.opts.manager.resetSession(cmd.agentId, {
21790
+ runtimeConfig: cmd.config,
21791
+ launchId: cmd.launchId,
21792
+ rewakePrompt: buildNapRewakePrompt(cmd.handoff),
21793
+ barrierType: "nap"
21794
+ }));
21795
+ break;
21796
+ case "agent:model_switch":
21797
+ await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:model_switch", () => this.opts.manager.switchModel(cmd.agentId, {
21798
+ runtimeConfig: cmd.config,
21799
+ launchId: cmd.launchId,
21800
+ rewakePrompt: MODEL_SWITCH_REWAKE_PROMPT
21801
+ }));
21802
+ break;
21803
+ case "agent:stop":
21804
+ this.log.info("agent:stop received", { agentId: cmd.agentId });
21805
+ try {
21806
+ this.running.delete(cmd.agentId);
21807
+ this.opts.manager.stop(cmd.agentId);
21808
+ await this.opts.channel.reportStoppedAck?.({
21809
+ agentId: cmd.agentId,
21810
+ status: "ok"
21811
+ });
21812
+ this.log.info("agent:stop ack", { agentId: cmd.agentId, status: "ok" });
21813
+ } catch (err) {
21814
+ const code = classifyErrorCode(err);
21815
+ await this.opts.channel.reportStoppedAck?.({
21816
+ agentId: cmd.agentId,
21817
+ status: "error",
21818
+ error: {
21819
+ code,
21820
+ message: err instanceof Error ? err.message : String(err)
21821
+ }
21822
+ });
21823
+ this.log.info("agent:stop ack", { agentId: cmd.agentId, status: "error", "error.code": code });
21824
+ }
21825
+ break;
21826
+ case "bot:added":
21827
+ case "bot:updated":
21828
+ case "bot:removed":
21829
+ break;
21830
+ }
21780
21831
  }
21781
- const base = `/${p.server}/${p.channel}`;
21782
- if (p.threadRootSeq === undefined)
21783
- return base;
21784
- if (p.seq === undefined)
21785
- return `${base}/#${p.threadRootSeq}`;
21786
- return `${base}/#${p.threadRootSeq}#${p.seq}`;
21787
21832
  }
21788
- function parseSeq(s) {
21789
- const n = Number(s.startsWith("#") ? s.slice(1) : s);
21790
- if (!Number.isFinite(n))
21791
- throw new Error(`bad seq: ${s}`);
21792
- return n;
21833
+ // src/manager/typingScopeTracker.ts
21834
+ function createTypingScopeTracker() {
21835
+ const scopes = new Map;
21836
+ return {
21837
+ add(agentId, channelId) {
21838
+ let set2 = scopes.get(agentId);
21839
+ if (!set2) {
21840
+ set2 = new Set;
21841
+ scopes.set(agentId, set2);
21842
+ }
21843
+ set2.add(channelId);
21844
+ },
21845
+ snapshot(agentId) {
21846
+ const set2 = scopes.get(agentId);
21847
+ return set2 ? [...set2] : [];
21848
+ },
21849
+ hasAny(agentId) {
21850
+ const set2 = scopes.get(agentId);
21851
+ return !!set2 && set2.size > 0;
21852
+ },
21853
+ clear(agentId) {
21854
+ scopes.delete(agentId);
21855
+ }
21856
+ };
21793
21857
  }
21794
- var HostCommandSchema = exports_external.discriminatedUnion("type", [
21795
- exports_external.object({
21796
- type: exports_external.literal("agent:wake"),
21797
- agentId: exports_external.string().min(1),
21798
- config: exports_external.unknown(),
21799
- sessionId: exports_external.string().optional(),
21800
- launchId: exports_external.string().min(1),
21801
- unreadNotice: exports_external.unknown()
21802
- }),
21803
- exports_external.object({
21804
- type: exports_external.literal("agent:stop"),
21805
- agentId: exports_external.string().min(1)
21806
- }),
21807
- exports_external.object({
21808
- type: exports_external.literal("agent:reset"),
21809
- agentId: exports_external.string().min(1),
21810
- config: exports_external.unknown(),
21811
- launchId: exports_external.string().min(1)
21812
- }),
21813
- exports_external.object({
21814
- type: exports_external.literal("agent:nap"),
21815
- agentId: exports_external.string().min(1),
21816
- config: exports_external.unknown(),
21817
- launchId: exports_external.string().min(1),
21818
- handoff: exports_external.string().min(1)
21819
- }),
21820
- exports_external.object({
21821
- type: exports_external.literal("agent:model_switch"),
21822
- agentId: exports_external.string().min(1),
21823
- config: exports_external.unknown(),
21824
- launchId: exports_external.string().min(1)
21825
- }),
21826
- exports_external.object({
21827
- type: exports_external.literal("machine:reset_all"),
21828
- resets: exports_external.array(exports_external.object({
21829
- agentId: exports_external.string().min(1),
21830
- config: exports_external.unknown(),
21831
- launchId: exports_external.string().min(1)
21832
- }))
21833
- }),
21834
- exports_external.strictObject({
21835
- type: exports_external.literal("machine:update")
21836
- }),
21837
- exports_external.object({
21838
- type: exports_external.literal("bot:added"),
21839
- botId: exports_external.string().min(1),
21840
- name: exports_external.string().optional(),
21841
- discriminator: exports_external.string().optional(),
21842
- description: exports_external.string().optional(),
21843
- ownerName: exports_external.string().optional(),
21844
- ownerDiscriminator: exports_external.string().optional()
21845
- }),
21846
- exports_external.object({
21847
- type: exports_external.literal("bot:updated"),
21848
- botId: exports_external.string().min(1),
21849
- name: exports_external.string().optional(),
21850
- discriminator: exports_external.string().optional(),
21851
- description: exports_external.string().optional(),
21852
- ownerName: exports_external.string().optional(),
21853
- ownerDiscriminator: exports_external.string().optional()
21854
- }),
21855
- exports_external.object({
21856
- type: exports_external.literal("bot:removed"),
21857
- botId: exports_external.string().min(1)
21858
- }),
21859
- DiagnosticCollectCommandSchema
21860
- ]);
21861
21858
  // src/credentials/credentialProxy.ts
21859
+ import * as crypto2 from "crypto";
21860
+ import * as fs5 from "fs";
21861
+ import * as http from "http";
21862
+ import * as https from "https";
21863
+ import * as os2 from "os";
21864
+ import * as path7 from "path";
21865
+ import { URL as URL2 } from "url";
21862
21866
  var LOCAL_MESSAGE_REMINDER_PATH = "/__alook/local/message-reminder";
21863
21867
  var LOCAL_MESSAGE_REMINDER_BODY_MAX_BYTES = 4 * 1024;
21864
21868
  var LOCAL_MESSAGE_REMINDER_MIN_MS = 60000;
@@ -21984,6 +21988,14 @@ function writeJson(res, status, body) {
21984
21988
  res.writeHead(status, { "content-type": "application/json" });
21985
21989
  res.end(JSON.stringify(body));
21986
21990
  }
21991
+ function normalizeContentEncoding(value) {
21992
+ const normalized = (Array.isArray(value) ? value.join(",") : value ?? "").trim().toLowerCase();
21993
+ if (!normalized || normalized === "identity")
21994
+ return "identity";
21995
+ if (normalized === "gzip" || normalized === "deflate" || normalized === "br")
21996
+ return normalized;
21997
+ return "other";
21998
+ }
21987
21999
  function isCanonicalChannelScope(channel2) {
21988
22000
  try {
21989
22001
  const parsed = parseRef(channel2);
@@ -22080,6 +22092,11 @@ async function startCredentialProxy(broker, options = {}) {
22080
22092
  const upstreamClient = upstream.protocol === "https:" ? https : http;
22081
22093
  const onPull = options.onInboxPullResponse;
22082
22094
  const onProxyRequest = options.onProxyRequest;
22095
+ const reportInboxPullObservationError = (failure) => {
22096
+ try {
22097
+ options.onInboxPullObservationError?.(failure);
22098
+ } catch {}
22099
+ };
22083
22100
  const server = http.createServer((req, res) => {
22084
22101
  const pathname = new URL2(req.url ?? "/", "http://placeholder").pathname;
22085
22102
  if (pathname.startsWith("/__alook/local/")) {
@@ -22118,7 +22135,20 @@ async function startCredentialProxy(broker, options = {}) {
22118
22135
  return;
22119
22136
  }
22120
22137
  const reg = verdict.reg;
22121
- const isInboxPull = onPull && pathname === "/api/community/users/me/inbox/pull";
22138
+ const isInboxPull = req.method === "POST" && pathname === "/api/community/users/me/inbox/pull";
22139
+ const shouldObserveInboxPull = Boolean(isInboxPull && onPull);
22140
+ let inboxPullObservationToken;
22141
+ if (shouldObserveInboxPull) {
22142
+ try {
22143
+ inboxPullObservationToken = options.onInboxPullStart?.(reg.agentId);
22144
+ } catch {
22145
+ reportInboxPullObservationError({
22146
+ agentId: reg.agentId,
22147
+ reason: "observer_failed",
22148
+ contentEncoding: "identity"
22149
+ });
22150
+ }
22151
+ }
22122
22152
  if (onProxyRequest) {
22123
22153
  try {
22124
22154
  onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
@@ -22131,6 +22161,8 @@ async function startCredentialProxy(broker, options = {}) {
22131
22161
  outHeaders[broker.headerNames.agentId.toLowerCase()] = reg.agentId;
22132
22162
  outHeaders[broker.headerNames.client.toLowerCase()] = broker.clientLabel;
22133
22163
  outHeaders[broker.headerNames.capabilities.toLowerCase()] = [...reg.capabilities].join(",");
22164
+ if (isInboxPull)
22165
+ outHeaders["accept-encoding"] = "identity";
22134
22166
  let responded = false;
22135
22167
  let upstreamRes;
22136
22168
  const upstreamReq = upstreamClient.request({
@@ -22149,18 +22181,54 @@ async function startCredentialProxy(broker, options = {}) {
22149
22181
  };
22150
22182
  res_.on("error", destroyResIfIncomplete);
22151
22183
  res_.on("close", destroyResIfIncomplete);
22152
- if (isInboxPull && res_.statusCode && res_.statusCode < 300) {
22184
+ if (shouldObserveInboxPull && res_.statusCode !== undefined && res_.statusCode >= 200 && res_.statusCode < 300) {
22153
22185
  const chunks = [];
22154
22186
  res_.on("data", (chunk) => chunks.push(chunk));
22155
22187
  res_.on("end", () => {
22156
22188
  const body = Buffer.concat(chunks);
22189
+ const contentEncoding = normalizeContentEncoding(res_.headers["content-encoding"]);
22190
+ if (contentEncoding !== "identity") {
22191
+ reportInboxPullObservationError({
22192
+ agentId: reg.agentId,
22193
+ reason: "unexpected_content_encoding",
22194
+ contentEncoding
22195
+ });
22196
+ } else {
22197
+ let parsed;
22198
+ try {
22199
+ parsed = JSON.parse(body.toString("utf8"));
22200
+ } catch {
22201
+ reportInboxPullObservationError({
22202
+ agentId: reg.agentId,
22203
+ reason: "invalid_json",
22204
+ contentEncoding
22205
+ });
22206
+ }
22207
+ if (parsed !== undefined) {
22208
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || !Array.isArray(parsed.messages)) {
22209
+ reportInboxPullObservationError({
22210
+ agentId: reg.agentId,
22211
+ reason: "invalid_inbox_shape",
22212
+ contentEncoding
22213
+ });
22214
+ } else {
22215
+ const messages = parsed.messages;
22216
+ if (messages.length > 0) {
22217
+ try {
22218
+ onPull(reg.agentId, messages, inboxPullObservationToken);
22219
+ } catch {
22220
+ reportInboxPullObservationError({
22221
+ agentId: reg.agentId,
22222
+ reason: "observer_failed",
22223
+ contentEncoding
22224
+ });
22225
+ }
22226
+ }
22227
+ }
22228
+ }
22229
+ }
22157
22230
  res.writeHead(res_.statusCode, res_.headers);
22158
22231
  res.end(body);
22159
- try {
22160
- const parsed = JSON.parse(body.toString());
22161
- if (parsed.messages)
22162
- onPull(reg.agentId, parsed.messages);
22163
- } catch {}
22164
22232
  });
22165
22233
  } else {
22166
22234
  res.writeHead(res_.statusCode ?? 502, res_.headers);
@@ -22485,6 +22553,236 @@ function writeStatusFile(path8, snapshot) {
22485
22553
  } catch {}
22486
22554
  }
22487
22555
 
22556
+ // src/manager/wakeCoordinator.ts
22557
+ class WakeCoordinator {
22558
+ agents = new Map;
22559
+ async run(command, dispatch, onDesiredAdvance) {
22560
+ const state = this.agent(command.agentId);
22561
+ const channel2 = command.unreadNotice.channel;
22562
+ const seq = command.unreadNotice.latestSeq;
22563
+ const scope = state.scopes.get(channel2);
22564
+ const coveredSeq = Math.max(scope?.modelSeenSeq ?? 0, scope?.admittedSeq ?? 0);
22565
+ if (seq <= coveredSeq) {
22566
+ this.rememberReplacement(state, command);
22567
+ return { state: "suppressed", coveredSeq };
22568
+ }
22569
+ const desiredAdvanced = !scope || seq > scope.desiredSeq;
22570
+ if (!scope || seq >= scope.desiredSeq) {
22571
+ state.scopes.set(channel2, {
22572
+ desiredSeq: Math.max(scope?.desiredSeq ?? 0, seq),
22573
+ admittedSeq: scope?.admittedSeq ?? 0,
22574
+ modelSeenSeq: scope?.modelSeenSeq ?? 0,
22575
+ command
22576
+ });
22577
+ }
22578
+ if (desiredAdvanced)
22579
+ onDesiredAdvance?.(command);
22580
+ state.dispatch = dispatch;
22581
+ if (state.active || state.admitting) {
22582
+ this.rememberReplacement(state, command);
22583
+ return { state: "suppressed", coveredSeq: seq };
22584
+ }
22585
+ await this.admit(command.agentId, command);
22586
+ return { state: "accepted" };
22587
+ }
22588
+ modelSeenGeneration(agentId) {
22589
+ return this.agent(agentId).observationGeneration;
22590
+ }
22591
+ recordModelSeen(agentId, messages, generation) {
22592
+ const state = this.agent(agentId);
22593
+ if (generation !== state.observationGeneration)
22594
+ return false;
22595
+ for (const message2 of messages) {
22596
+ const seq = Number(message2.seq.startsWith("#") ? message2.seq.slice(1) : message2.seq);
22597
+ if (!Number.isSafeInteger(seq) || seq <= 0)
22598
+ continue;
22599
+ const scope = state.scopes.get(message2.channel);
22600
+ if (scope) {
22601
+ scope.modelSeenSeq = Math.max(scope.modelSeenSeq, seq);
22602
+ } else {
22603
+ state.scopes.set(message2.channel, {
22604
+ desiredSeq: 0,
22605
+ admittedSeq: 0,
22606
+ modelSeenSeq: seq,
22607
+ command: null
22608
+ });
22609
+ }
22610
+ }
22611
+ return true;
22612
+ }
22613
+ recordAgentActivity(agentId, activity) {
22614
+ const state = this.agent(agentId);
22615
+ if (activity === "starting" || activity === "running") {
22616
+ state.active = true;
22617
+ return;
22618
+ }
22619
+ if (activity !== "idle")
22620
+ return;
22621
+ state.active = false;
22622
+ state.activeAdmission = null;
22623
+ const next = this.nextPending(state);
22624
+ if (next && state.dispatch)
22625
+ this.admit(agentId, next);
22626
+ }
22627
+ recordDeliveryAck(agentId, launchId, status) {
22628
+ const state = this.agents.get(agentId);
22629
+ const admission = state?.activeAdmission;
22630
+ if (!state || !admission || admission.launchId !== launchId)
22631
+ return;
22632
+ if (status === "ok")
22633
+ return;
22634
+ for (const [channel2, coverage] of admission.coverage) {
22635
+ const scope = state.scopes.get(channel2);
22636
+ if (scope?.admittedSeq === coverage.admittedSeq) {
22637
+ scope.admittedSeq = coverage.previousSeq;
22638
+ }
22639
+ }
22640
+ state.active = false;
22641
+ state.failedAdmissionLaunchId = launchId;
22642
+ state.retryAfterFailure = state.coalescedReplacement;
22643
+ state.coalescedReplacement = null;
22644
+ state.activeAdmission = null;
22645
+ if (!state.admitting && state.retryAfterFailure) {
22646
+ this.retryFailedAdmission(agentId, state).catch(() => {});
22647
+ }
22648
+ }
22649
+ invalidate(agentId, clearModelSeen) {
22650
+ const state = this.agent(agentId);
22651
+ state.observationGeneration += 1;
22652
+ state.active = false;
22653
+ state.admitting = false;
22654
+ state.activeAdmission = null;
22655
+ state.failedAdmissionLaunchId = null;
22656
+ state.coalescedReplacement = null;
22657
+ state.retryAfterFailure = null;
22658
+ if (clearModelSeen) {
22659
+ state.scopes.clear();
22660
+ return;
22661
+ }
22662
+ for (const scope of state.scopes.values()) {
22663
+ scope.desiredSeq = scope.modelSeenSeq;
22664
+ scope.admittedSeq = scope.modelSeenSeq;
22665
+ }
22666
+ }
22667
+ agent(agentId) {
22668
+ let state = this.agents.get(agentId);
22669
+ if (!state) {
22670
+ state = {
22671
+ active: false,
22672
+ admitting: false,
22673
+ activeAdmission: null,
22674
+ failedAdmissionLaunchId: null,
22675
+ coalescedReplacement: null,
22676
+ retryAfterFailure: null,
22677
+ observationGeneration: 0,
22678
+ scopes: new Map,
22679
+ dispatch: null
22680
+ };
22681
+ this.agents.set(agentId, state);
22682
+ }
22683
+ return state;
22684
+ }
22685
+ nextPending(state) {
22686
+ let next = null;
22687
+ for (const scope of state.scopes.values()) {
22688
+ if (scope.desiredSeq <= Math.max(scope.modelSeenSeq, scope.admittedSeq))
22689
+ continue;
22690
+ if (!scope.command)
22691
+ continue;
22692
+ if (scope.command.launchId === state.failedAdmissionLaunchId)
22693
+ continue;
22694
+ if (!next || scope.desiredSeq > next.desiredSeq)
22695
+ next = scope;
22696
+ }
22697
+ return next?.command ?? null;
22698
+ }
22699
+ async admit(agentId, preferred) {
22700
+ const state = this.agent(agentId);
22701
+ if (state.active || state.admitting || !state.dispatch)
22702
+ return;
22703
+ const admissionGeneration = state.observationGeneration;
22704
+ const command = preferred ?? this.nextPending(state);
22705
+ if (!command)
22706
+ return;
22707
+ const commandScope = state.scopes.get(command.unreadNotice.channel);
22708
+ if (commandScope && command.unreadNotice.latestSeq >= commandScope.desiredSeq) {
22709
+ commandScope.command = command;
22710
+ }
22711
+ const coverage = new Map;
22712
+ for (const [channel2, scope] of state.scopes) {
22713
+ if (scope.desiredSeq <= Math.max(scope.modelSeenSeq, scope.admittedSeq))
22714
+ continue;
22715
+ coverage.set(channel2, {
22716
+ previousSeq: scope.admittedSeq,
22717
+ admittedSeq: scope.desiredSeq
22718
+ });
22719
+ scope.admittedSeq = scope.desiredSeq;
22720
+ scope.command = command;
22721
+ }
22722
+ if (coverage.size === 0)
22723
+ return;
22724
+ state.admitting = true;
22725
+ state.active = true;
22726
+ state.failedAdmissionLaunchId = null;
22727
+ state.coalescedReplacement = null;
22728
+ state.activeAdmission = { launchId: command.launchId, coverage };
22729
+ let dispatchError;
22730
+ try {
22731
+ await state.dispatch(command);
22732
+ } catch (error51) {
22733
+ if (state.observationGeneration === admissionGeneration) {
22734
+ for (const [channel2, admitted] of coverage) {
22735
+ const scope = state.scopes.get(channel2);
22736
+ if (scope?.admittedSeq === admitted.admittedSeq) {
22737
+ scope.admittedSeq = admitted.previousSeq;
22738
+ }
22739
+ }
22740
+ state.active = false;
22741
+ state.failedAdmissionLaunchId = command.launchId;
22742
+ state.retryAfterFailure = state.coalescedReplacement;
22743
+ state.coalescedReplacement = null;
22744
+ state.activeAdmission = null;
22745
+ }
22746
+ dispatchError = error51;
22747
+ } finally {
22748
+ if (state.observationGeneration === admissionGeneration) {
22749
+ state.admitting = false;
22750
+ }
22751
+ }
22752
+ if (state.observationGeneration === admissionGeneration) {
22753
+ await this.retryFailedAdmission(agentId, state);
22754
+ if (!state.active && !state.admitting) {
22755
+ const next = this.nextPending(state);
22756
+ if (next)
22757
+ await this.admit(agentId, next);
22758
+ }
22759
+ }
22760
+ if (dispatchError)
22761
+ throw dispatchError;
22762
+ }
22763
+ rememberReplacement(state, command) {
22764
+ const failedOrActiveLaunchId = state.activeAdmission?.launchId ?? state.failedAdmissionLaunchId;
22765
+ if (!failedOrActiveLaunchId || command.launchId === failedOrActiveLaunchId)
22766
+ return;
22767
+ const scope = state.scopes.get(command.unreadNotice.channel);
22768
+ if (!scope || command.unreadNotice.latestSeq < scope.desiredSeq)
22769
+ return;
22770
+ state.coalescedReplacement = command;
22771
+ if (state.failedAdmissionLaunchId)
22772
+ state.retryAfterFailure = command;
22773
+ }
22774
+ async retryFailedAdmission(agentId, state) {
22775
+ if (state.active || state.admitting)
22776
+ return;
22777
+ const replacement = state.retryAfterFailure;
22778
+ state.retryAfterFailure = null;
22779
+ if (!replacement)
22780
+ return;
22781
+ state.failedAdmissionLaunchId = null;
22782
+ await this.admit(agentId, replacement);
22783
+ }
22784
+ }
22785
+
22488
22786
  // src/server/wsControlChannel.ts
22489
22787
  var WS_CONTROL_COMMAND_CONSUMED = Symbol("ws-control-command-consumed");
22490
22788
  var DEFAULT_PING_INTERVAL_MS = 15000;
@@ -22499,6 +22797,7 @@ class WsControlChannel {
22499
22797
  opts;
22500
22798
  statusValue = "idle";
22501
22799
  commandCbs = [];
22800
+ wakeDesiredCbs = [];
22502
22801
  resyncHooks = [];
22503
22802
  ws = null;
22504
22803
  attempt = 0;
@@ -22508,6 +22807,7 @@ class WsControlChannel {
22508
22807
  pongDeadline = 0;
22509
22808
  resyncProvider = null;
22510
22809
  log;
22810
+ wakeCoordinator = new WakeCoordinator;
22511
22811
  constructor(opts) {
22512
22812
  this.opts = opts;
22513
22813
  this.log = opts.logger ?? createLogger({ header: "@alook/daemon:ws" });
@@ -22530,6 +22830,30 @@ class WsControlChannel {
22530
22830
  onCommand(cb) {
22531
22831
  this.commandCbs.push(cb);
22532
22832
  }
22833
+ onWakeDesiredAdvance(cb) {
22834
+ this.wakeDesiredCbs.push(cb);
22835
+ }
22836
+ async ingestCommand(frame) {
22837
+ const parsed = HostCommandSchema.safeParse(frame);
22838
+ if (!parsed.success) {
22839
+ this.log.warn("dropped malformed HostCommand frame", {
22840
+ type: typeof frame === "object" && frame !== null && "type" in frame ? String(frame.type) : "unknown",
22841
+ issues: parsed.error.issues.map((issue3) => ({
22842
+ path: issue3.path.join("."),
22843
+ code: issue3.code
22844
+ }))
22845
+ });
22846
+ return false;
22847
+ }
22848
+ await this.dispatchIngressCommand(parsed.data);
22849
+ return true;
22850
+ }
22851
+ modelSeenGeneration(agentId) {
22852
+ return this.wakeCoordinator.modelSeenGeneration(agentId);
22853
+ }
22854
+ recordModelSeen(agentId, messages, generation = this.wakeCoordinator.modelSeenGeneration(agentId)) {
22855
+ return this.wakeCoordinator.recordModelSeen(agentId, messages, generation);
22856
+ }
22533
22857
  onResync(provider) {
22534
22858
  this.resyncProvider = provider;
22535
22859
  }
@@ -22546,6 +22870,7 @@ class WsControlChannel {
22546
22870
  this.sendFrame({ type: "agent_session", ...info });
22547
22871
  }
22548
22872
  async reportAgentActivity(info) {
22873
+ this.wakeCoordinator.recordAgentActivity(info.agentId, info.state);
22549
22874
  this.sendFrame({ type: "agent_activity", ...info });
22550
22875
  }
22551
22876
  reportAgentTyping(info) {
@@ -22558,6 +22883,7 @@ class WsControlChannel {
22558
22883
  this.sendFrame(frame);
22559
22884
  }
22560
22885
  async reportWakeAck(info) {
22886
+ this.wakeCoordinator.recordDeliveryAck(info.agentId, info.launchId, info.status);
22561
22887
  this.sendFrame({ type: "agent_wake_ack", ...info });
22562
22888
  }
22563
22889
  async reportStoppedAck(info) {
@@ -22629,27 +22955,75 @@ class WsControlChannel {
22629
22955
  return;
22630
22956
  }
22631
22957
  this.attempt = 0;
22632
- const parsed = HostCommandSchema.safeParse(frame);
22633
- if (!parsed.success) {
22634
- this.log.warn("dropped malformed HostCommand frame", {
22635
- type: frame.type,
22636
- issues: parsed.error.issues.map((i) => ({ path: i.path.join("."), code: i.code }))
22637
- });
22638
- return;
22639
- }
22640
- const cmd = parsed.data;
22958
+ this.ingestCommand(frame).catch((err) => {
22959
+ this.log.warn("command ingress failed", { type: frame.type, err: describeErr(err) });
22960
+ });
22961
+ }
22962
+ async dispatchListeners(cmd) {
22963
+ const pending = [];
22641
22964
  for (const cb of this.commandCbs) {
22642
22965
  try {
22643
22966
  const result = cb(cmd);
22644
22967
  if (result === WS_CONTROL_COMMAND_CONSUMED)
22645
22968
  break;
22646
- Promise.resolve(result).catch((err) => {
22969
+ pending.push(Promise.resolve(result).catch((err) => {
22647
22970
  this.log.warn("command listener threw", { type: cmd.type, err: describeErr(err) });
22648
- });
22971
+ }));
22649
22972
  } catch (err) {
22650
22973
  this.log.warn("command listener threw synchronously", { type: cmd.type, err: describeErr(err) });
22651
22974
  }
22652
22975
  }
22976
+ await Promise.all(pending);
22977
+ }
22978
+ async dispatchIngressCommand(cmd) {
22979
+ if (cmd.type === "machine:heartbeat") {
22980
+ this.sendFrame({ type: "machine_heartbeat_ack", nonce: cmd.nonce });
22981
+ return;
22982
+ }
22983
+ if (cmd.type === "diagnostics:collect") {
22984
+ this.sendFrame({ type: "diagnostics_ack", reportId: cmd.reportId });
22985
+ await this.dispatchListeners(cmd);
22986
+ return;
22987
+ }
22988
+ if (cmd.type === "agent:wake") {
22989
+ const result = await this.wakeCoordinator.run(cmd, (accepted) => this.dispatchListeners(accepted), (advanced) => {
22990
+ for (const cb of this.wakeDesiredCbs) {
22991
+ try {
22992
+ cb(advanced);
22993
+ } catch (err) {
22994
+ this.log.warn("wake desired observer threw", { err: describeErr(err) });
22995
+ }
22996
+ }
22997
+ });
22998
+ if (result.state === "suppressed") {
22999
+ await this.reportWakeAck({
23000
+ agentId: cmd.agentId,
23001
+ launchId: cmd.launchId,
23002
+ status: "ok"
23003
+ });
23004
+ this.log.debug("duplicate wake suppressed", {
23005
+ agentId: cmd.agentId,
23006
+ channel: cmd.unreadNotice.channel,
23007
+ latestSeq: cmd.unreadNotice.latestSeq,
23008
+ coveredSeq: result.coveredSeq
23009
+ });
23010
+ }
23011
+ return;
23012
+ }
23013
+ if (cmd.type === "machine:reset_all") {
23014
+ for (const reset of cmd.resets)
23015
+ this.wakeCoordinator.invalidate(reset.agentId, true);
23016
+ await this.dispatchListeners(cmd);
23017
+ return;
23018
+ }
23019
+ if (cmd.type === "agent:stop" || cmd.type === "agent:model_switch") {
23020
+ this.wakeCoordinator.invalidate(cmd.agentId, false);
23021
+ } else if (cmd.type === "agent:reset" || cmd.type === "agent:nap") {
23022
+ this.wakeCoordinator.invalidate(cmd.agentId, true);
23023
+ } else if (cmd.type === "bot:removed") {
23024
+ this.wakeCoordinator.invalidate(cmd.botId, true);
23025
+ }
23026
+ await this.dispatchListeners(cmd);
22653
23027
  }
22654
23028
  onSocketClosed(code, reason) {
22655
23029
  this.log.warn("control channel closed", { code, reason: reason ? String(reason) : "" });
@@ -22684,7 +23058,10 @@ class WsControlChannel {
22684
23058
  this.pingTimer = setInterval(() => {
22685
23059
  if (this.now() > this.pongDeadline) {
22686
23060
  this.log.warn("heartbeat pong timeout — forcing reconnect");
22687
- this.ws?.close();
23061
+ if (this.ws?.terminate)
23062
+ this.ws.terminate();
23063
+ else
23064
+ this.ws?.close();
22688
23065
  return;
22689
23066
  }
22690
23067
  this.log.debug("heartbeat ping");
@@ -23263,6 +23640,8 @@ function createTimelineRecorder(opts) {
23263
23640
  sessionByAgent.set(agentId, sessionId);
23264
23641
  },
23265
23642
  appendEntryForAgent(agentId, messages) {
23643
+ if (messages.length === 0)
23644
+ return;
23266
23645
  const dir = dirFor(agentId);
23267
23646
  if (!prepareTimelineDirectory(dir))
23268
23647
  return;
@@ -23751,7 +24130,16 @@ async function createDaemon(opts) {
23751
24130
  const typingTracker = createTypingScopeTracker();
23752
24131
  const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
23753
24132
  const proxy = await startCredentialProxy(broker, {
23754
- onInboxPullResponse: (agentId, messages) => timeline2.appendEntryForAgent(agentId, messages),
24133
+ onInboxPullStart: (agentId) => channelRef?.modelSeenGeneration(agentId),
24134
+ onInboxPullResponse: (agentId, messages, observationToken) => {
24135
+ timeline2.appendEntryForAgent(agentId, messages);
24136
+ if (typeof observationToken === "number") {
24137
+ channelRef?.recordModelSeen(agentId, messages, observationToken);
24138
+ }
24139
+ },
24140
+ onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
24141
+ log.warn("inbox pull timeline observation failed", { agentId, reason, contentEncoding });
24142
+ },
23755
24143
  onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
23756
24144
  onProxyRequest: (agentId, method, pathname) => {
23757
24145
  const subcommand = deriveAuditLogSubcommand(pathname, method);
@@ -23852,11 +24240,29 @@ async function createDaemon(opts) {
23852
24240
  if (!res.ok)
23853
24241
  throw new Error(`resync-wakes ${res.status}`);
23854
24242
  const json2 = await res.json();
23855
- log.info("wake resync completed", { woken: json2.woken ?? 0 });
24243
+ log.info("wake resync completed", { attempted: json2.attempted ?? 0 });
23856
24244
  } catch (err) {
23857
24245
  log.warn("wake resync failed", { err: err instanceof Error ? err.message : String(err) });
23858
24246
  }
23859
24247
  }
24248
+ async function resyncPendingDiagnostics() {
24249
+ try {
24250
+ const res = await fetch(`${opts.serverUrl}/api/community/daemon/resync-diagnostics`, {
24251
+ method: "POST",
24252
+ headers: { authorization: `Bearer ${opts.machineKey}` }
24253
+ });
24254
+ if (!res.ok)
24255
+ throw new Error(`resync-diagnostics ${res.status}`);
24256
+ const json2 = await res.json();
24257
+ log.info("diagnostics resync completed", {
24258
+ pending: json2.pending ?? 0,
24259
+ attempted: json2.attempted ?? 0,
24260
+ ambiguous: json2.ambiguous ?? 0
24261
+ });
24262
+ } catch (err) {
24263
+ log.warn("diagnostics resync failed", { err: err instanceof Error ? err.message : String(err) });
24264
+ }
24265
+ }
23860
24266
  const enrollAgent = async (agentId) => {
23861
24267
  const existing = enrolledKeys.get(agentId);
23862
24268
  if (existing)
@@ -24082,11 +24488,11 @@ async function createDaemon(opts) {
24082
24488
  handleDiagnosticCommand: opts.handleDiagnosticCommand,
24083
24489
  reportDiagnosticFailure: opts.reportDiagnosticFailure
24084
24490
  }));
24491
+ channel2.onWakeDesiredAdvance((cmd) => {
24492
+ reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
24493
+ });
24085
24494
  channel2.onCommand((cmd) => {
24086
24495
  switch (cmd.type) {
24087
- case "agent:wake":
24088
- reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
24089
- break;
24090
24496
  case "agent:stop":
24091
24497
  reminderSchedulerRef?.clearAgent(cmd.agentId);
24092
24498
  break;
@@ -24103,6 +24509,7 @@ async function createDaemon(opts) {
24103
24509
  channel2.onOpen(() => {
24104
24510
  coldStartWarmup();
24105
24511
  resyncPendingWakes();
24512
+ resyncPendingDiagnostics();
24106
24513
  });
24107
24514
  channel2.connect();
24108
24515
  await router.start();