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