@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/README.md +36 -0
- package/dist/cli/index.js +677 -96
- package/dist/index.js +928 -521
- package/package.json +1 -1
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
|
-
|
|
21737
|
-
|
|
21596
|
+
recordRuntimeSpawnFailure(id, reason) {
|
|
21597
|
+
if (!DEFINITIVE_EXECUTABLE_FAILURES.has(reason))
|
|
21598
|
+
return;
|
|
21599
|
+
this.markRuntimeUnhealthy(id, reason);
|
|
21738
21600
|
}
|
|
21739
|
-
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
|
|
21743
|
-
|
|
21744
|
-
|
|
21745
|
-
|
|
21746
|
-
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
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
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
21758
|
-
|
|
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
|
-
|
|
21761
|
-
|
|
21762
|
-
|
|
21763
|
-
|
|
21764
|
-
|
|
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
|
-
|
|
21769
|
-
if (
|
|
21770
|
-
|
|
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
|
-
|
|
21773
|
-
|
|
21774
|
-
|
|
21775
|
-
|
|
21776
|
-
|
|
21777
|
-
|
|
21778
|
-
|
|
21779
|
-
|
|
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
|
-
|
|
21789
|
-
|
|
21790
|
-
|
|
21791
|
-
|
|
21792
|
-
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
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?.
|
|
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
|
-
|
|
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", {
|
|
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();
|