@alook/daemon 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -98,7 +98,7 @@ var init_nanoid = () => {};
98
98
  // src/cli/index.ts
99
99
  import { Command, CommanderError } from "commander";
100
100
  import { realpathSync as realpathSync2 } from "node:fs";
101
- import { randomUUID as randomUUID6 } from "node:crypto";
101
+ import { randomUUID as randomUUID7 } from "node:crypto";
102
102
  import { pathToFileURL as pathToFileURL2 } from "node:url";
103
103
 
104
104
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
@@ -14945,6 +14945,9 @@ class Name {
14945
14945
  return new SQL([this]);
14946
14946
  }
14947
14947
  }
14948
+ function isDriverValueEncoder(value) {
14949
+ return typeof value === "object" && value !== null && "mapToDriverValue" in value && typeof value.mapToDriverValue === "function";
14950
+ }
14948
14951
  var noopDecoder = {
14949
14952
  mapFromDriverValue: (value) => value
14950
14953
  };
@@ -15838,6 +15841,39 @@ class PrimaryKey {
15838
15841
  }
15839
15842
  }
15840
15843
 
15844
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260702.1_@opentelemetry+api@1.9.1_@typ_5aa9492b131e5fac58fa98e88e5c0ad3/node_modules/drizzle-orm/sql/expressions/conditions.js
15845
+ function bindIfParam(value, column) {
15846
+ if (isDriverValueEncoder(column) && !isSQLWrapper(value) && !is(value, Param) && !is(value, Placeholder) && !is(value, Column) && !is(value, Table) && !is(value, View)) {
15847
+ return new Param(value, column);
15848
+ }
15849
+ return value;
15850
+ }
15851
+ var eq = (left, right) => {
15852
+ return sql`${left} = ${bindIfParam(right, left)}`;
15853
+ };
15854
+ function and(...unfilteredConditions) {
15855
+ const conditions = unfilteredConditions.filter((c) => c !== undefined);
15856
+ if (conditions.length === 0) {
15857
+ return;
15858
+ }
15859
+ if (conditions.length === 1) {
15860
+ return new SQL(conditions);
15861
+ }
15862
+ return new SQL([
15863
+ new StringChunk("("),
15864
+ sql.join(conditions, new StringChunk(" and ")),
15865
+ new StringChunk(")")
15866
+ ]);
15867
+ }
15868
+ function isNotNull(value) {
15869
+ return sql`${value} is not null`;
15870
+ }
15871
+
15872
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260702.1_@opentelemetry+api@1.9.1_@typ_5aa9492b131e5fac58fa98e88e5c0ad3/node_modules/drizzle-orm/sql/expressions/select.js
15873
+ function desc(column) {
15874
+ return sql`${column} desc`;
15875
+ }
15876
+
15841
15877
  // ../shared/src/db/community-machine-schema.ts
15842
15878
  init_nanoid();
15843
15879
 
@@ -16706,6 +16742,7 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
16706
16742
 
16707
16743
  // ../shared/src/community-cli-contract.ts
16708
16744
  var DM_SERVER = ".dm";
16745
+ var CONTROL_HEARTBEAT_CAPABILITY = "control-heartbeat-v1";
16709
16746
  function parseRef(ref) {
16710
16747
  if (!ref.startsWith("/"))
16711
16748
  throw new Error(`ref must start with "/": ${ref}`);
@@ -16781,6 +16818,10 @@ function parseSeq(s) {
16781
16818
  return n;
16782
16819
  }
16783
16820
  var HostCommandSchema = exports_external.discriminatedUnion("type", [
16821
+ exports_external.strictObject({
16822
+ type: exports_external.literal("machine:heartbeat"),
16823
+ nonce: exports_external.string().min(1).max(128)
16824
+ }),
16784
16825
  exports_external.object({
16785
16826
  type: exports_external.literal("agent:wake"),
16786
16827
  agentId: exports_external.string().min(1),
@@ -17420,6 +17461,7 @@ var CommunityMachineSummarySchema = exports_external.object({
17420
17461
  var HostReadyMessageSchema = exports_external.object({
17421
17462
  type: exports_external.literal("ready"),
17422
17463
  runtimeReport: CommunityMachineRuntimeListSchema,
17464
+ capabilities: exports_external.array(exports_external.string().min(1).max(64)).max(16).optional().default([]),
17423
17465
  runningAgents: exports_external.array(exports_external.string()).default([]),
17424
17466
  hostname: exports_external.string().optional(),
17425
17467
  platform: exports_external.string().optional(),
@@ -17471,6 +17513,14 @@ var AgentWakeAckMessageSchema = exports_external.object({
17471
17513
  status: exports_external.enum(["ok", "error"]),
17472
17514
  error: exports_external.object({ code: exports_external.string().optional(), message: exports_external.string().optional() }).optional()
17473
17515
  });
17516
+ var MachineHeartbeatAckMessageSchema = exports_external.strictObject({
17517
+ type: exports_external.literal("machine_heartbeat_ack"),
17518
+ nonce: exports_external.string().min(1).max(128)
17519
+ });
17520
+ var DiagnosticCommandAckMessageSchema = exports_external.strictObject({
17521
+ type: exports_external.literal("diagnostics_ack"),
17522
+ reportId: DiagnosticReportIdSchema
17523
+ });
17474
17524
  var CommunityPairTokenResponseSchema = exports_external.object({
17475
17525
  tokenId: exports_external.string(),
17476
17526
  expiresAt: exports_external.string()
@@ -17479,6 +17529,7 @@ var CommunityDaemonActivateRequestSchema = exports_external.object({
17479
17529
  hostname: exports_external.string(),
17480
17530
  platform: exports_external.string(),
17481
17531
  arch: exports_external.string(),
17532
+ expectedMachineId: exports_external.string().min(1).optional(),
17482
17533
  osRelease: exports_external.string().optional(),
17483
17534
  daemonVersion: exports_external.string().optional(),
17484
17535
  runtimeReport: CommunityMachineRuntimeListSchema.optional()
@@ -17486,7 +17537,12 @@ var CommunityDaemonActivateRequestSchema = exports_external.object({
17486
17537
  var CommunityDaemonActivateResponseSchema = exports_external.object({
17487
17538
  credential: exports_external.string(),
17488
17539
  machineId: exports_external.string(),
17489
- expiresAt: exports_external.string().nullable()
17540
+ expiresAt: exports_external.string().nullable(),
17541
+ sessionOutcome: exports_external.literal("committed")
17542
+ });
17543
+ var CommunityDaemonActivateErrorResponseSchema = exports_external.object({
17544
+ error: exports_external.string(),
17545
+ sessionOutcome: exports_external.enum(["not_committed", "unknown"])
17490
17546
  });
17491
17547
  var CommunityDaemonEnrollAgentRequestSchema = exports_external.object({
17492
17548
  agentId: exports_external.string().min(1).max(128)
@@ -17576,13 +17632,6 @@ var CommunityAgentResolveRequestSchema = exports_external.object({
17576
17632
  var CommunityAgentListChannelsRequestSchema = exports_external.object({
17577
17633
  server: exports_external.string().min(1).optional()
17578
17634
  });
17579
- var CommunityAgentCreatePostRequestSchema = exports_external.object({
17580
- forum: exports_external.string().min(1),
17581
- title: exports_external.string().min(1),
17582
- content: CommunityAgentMessageContentSchema,
17583
- attachments: exports_external.array(exports_external.string().min(1)).max(MAX_ATTACHMENTS_PER_MESSAGE).default([]),
17584
- nonce: exports_external.string().min(1).max(128).optional()
17585
- }).refine((d) => d.content.text.trim().length > 0 || d.attachments.length > 0, { message: "post must have text or attachments" });
17586
17635
  var CommunityAgentListMembersRequestSchema = exports_external.object({
17587
17636
  server: exports_external.string().min(1),
17588
17637
  limit: exports_external.number().int().positive().optional(),
@@ -17596,7 +17645,9 @@ var CommunityAgentJoinServerRequestSchema = exports_external.object({
17596
17645
  invite: exports_external.string().min(1)
17597
17646
  });
17598
17647
  var CommunityAgentNapRequestSchema = exports_external.object({
17599
- handoff: exports_external.string().trim().min(1)
17648
+ handoff: exports_external.string().refine((value) => value.trim().length > 0, {
17649
+ message: "handoff is required"
17650
+ })
17600
17651
  });
17601
17652
  var CommunityAgentReactAddRequestSchema = exports_external.object({
17602
17653
  channel: exports_external.string().min(1),
@@ -17748,6 +17799,7 @@ var communityChannel = sqliteTable("community_channel", {
17748
17799
  index("idx_channel_server_position").on(t.serverId, t.position),
17749
17800
  index("idx_channel_server_last_message").on(t.serverId, t.lastMessageAt),
17750
17801
  index("idx_channel_parent").on(t.parentChannelId),
17802
+ index("idx_channel_forum_created").on(t.parentChannelId, desc(t.createdAt), desc(t.id)).where(and(eq(t.type, "thread"), eq(t.archived, 0), isNotNull(t.parentMessageId))),
17751
17803
  uniqueIndex("idx_channel_server_name").on(t.serverId, t.name).where(sql`parent_channel_id IS NULL`)
17752
17804
  ]);
17753
17805
  var communityChannelMember = sqliteTable("community_channel_member", {
@@ -18130,26 +18182,6 @@ var channelJoinBaselineGuard = sql`${communityMessage.createdAt} > COALESCE(${co
18130
18182
 
18131
18183
  // ../shared/src/db/queries/community/mention.ts
18132
18184
  var MENTION_INSERT_MAX_ROWS = maxRowsPerInsert(5);
18133
- // ../shared/src/community/bot-activity-presets.ts
18134
- var BOT_ACTIVITY_PRESETS = {
18135
- idle: { emoji: "\uD83D\uDCA4", text: "Idle" },
18136
- starting: { emoji: "\uD83C\uDF00", text: "Waking up" },
18137
- stopping: { emoji: "\uD83C\uDF19", text: "Wrapping up" }
18138
- };
18139
- var RUNNING_PRESETS = [
18140
- { emoji: "⚡", text: "Working on it" },
18141
- { emoji: "\uD83D\uDEE0️", text: "Cooking" },
18142
- { emoji: "\uD83E\uDDE0", text: "Thinking hard" },
18143
- { emoji: "\uD83D\uDD27", text: "Tinkering" },
18144
- { emoji: "\uD83D\uDE80", text: "On it" },
18145
- { emoji: "\uD83D\uDD25", text: "In the zone" }
18146
- ];
18147
- var BOT_ACTIVITY_STATUS_PAIRS = [
18148
- BOT_ACTIVITY_PRESETS.idle,
18149
- BOT_ACTIVITY_PRESETS.starting,
18150
- BOT_ACTIVITY_PRESETS.stopping,
18151
- ...RUNNING_PRESETS
18152
- ];
18153
18185
  // ../shared/src/community-ws-events.ts
18154
18186
  var string4 = exports_external.string();
18155
18187
  var nullableString = string4.nullable();
@@ -18606,6 +18638,68 @@ var WS_EVENTS = {
18606
18638
  BOT_AUDIT_EVENT: "community:bot.audit_event"
18607
18639
  };
18608
18640
  var COMMUNITY_EVENT_TYPES = new Set(Object.values(WS_EVENTS));
18641
+ var COMMUNITY_USER_TARGET_MAX_BYTES = 128;
18642
+ function utf8ByteLength(value) {
18643
+ return new TextEncoder().encode(value).byteLength;
18644
+ }
18645
+ function isWellFormedUnicode(value) {
18646
+ for (let index2 = 0;index2 < value.length; index2 += 1) {
18647
+ const codeUnit = value.charCodeAt(index2);
18648
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
18649
+ const next = value.charCodeAt(index2 + 1);
18650
+ if (!Number.isInteger(next) || next < 56320 || next > 57343)
18651
+ return false;
18652
+ index2 += 1;
18653
+ } else if (codeUnit >= 56320 && codeUnit <= 57343) {
18654
+ return false;
18655
+ }
18656
+ }
18657
+ return true;
18658
+ }
18659
+ function isValidCommunityUserTarget(value) {
18660
+ return typeof value === "string" && value.length > 0 && isWellFormedUnicode(value) && utf8ByteLength(value) <= COMMUNITY_USER_TARGET_MAX_BYTES;
18661
+ }
18662
+
18663
+ // ../shared/src/community-message-delivery.ts
18664
+ var MESSAGE_DELIVERY_MAX_USERS = 1000;
18665
+ var target = exports_external.string().refine(isValidCommunityUserTarget);
18666
+ var targetList = exports_external.array(target).max(MESSAGE_DELIVERY_MAX_USERS);
18667
+ var memberAddedSchema = exports_external.strictObject({
18668
+ userId: target,
18669
+ serverId: exports_external.string().min(1),
18670
+ channelId: exports_external.string().min(1)
18671
+ });
18672
+ var batchShape = exports_external.strictObject({
18673
+ messageId: exports_external.string().min(1),
18674
+ messageEvent: exports_external.unknown(),
18675
+ contentUserIds: targetList,
18676
+ unreadPlainUserIds: targetList,
18677
+ unreadMentionUserIds: targetList,
18678
+ mentionUserIds: targetList,
18679
+ memberAdded: memberAddedSchema.optional(),
18680
+ parentProjection: exports_external.unknown().optional(),
18681
+ parentProjectionUserIds: targetList.optional()
18682
+ });
18683
+ // ../shared/src/community/bot-activity-presets.ts
18684
+ var BOT_ACTIVITY_PRESETS = {
18685
+ idle: { emoji: "\uD83D\uDCA4", text: "Idle" },
18686
+ starting: { emoji: "\uD83C\uDF00", text: "Waking up" },
18687
+ stopping: { emoji: "\uD83C\uDF19", text: "Wrapping up" }
18688
+ };
18689
+ var RUNNING_PRESETS = [
18690
+ { emoji: "⚡", text: "Working on it" },
18691
+ { emoji: "\uD83D\uDEE0️", text: "Cooking" },
18692
+ { emoji: "\uD83E\uDDE0", text: "Thinking hard" },
18693
+ { emoji: "\uD83D\uDD27", text: "Tinkering" },
18694
+ { emoji: "\uD83D\uDE80", text: "On it" },
18695
+ { emoji: "\uD83D\uDD25", text: "In the zone" }
18696
+ ];
18697
+ var BOT_ACTIVITY_STATUS_PAIRS = [
18698
+ BOT_ACTIVITY_PRESETS.idle,
18699
+ BOT_ACTIVITY_PRESETS.starting,
18700
+ BOT_ACTIVITY_PRESETS.stopping,
18701
+ ...RUNNING_PRESETS
18702
+ ];
18609
18703
  // ../shared/src/db/index.ts
18610
18704
  var allSchema = { ...exports_schema, ...exports_community_schema, ...exports_community_machine_schema };
18611
18705
  // ../shared/src/db/queries/task.ts
@@ -18769,50 +18863,6 @@ function createProxyServerApi(config2) {
18769
18863
  });
18770
18864
  return parseJsonResponse(res, "send");
18771
18865
  }
18772
- async function callCreatePost(req) {
18773
- const endpoint = `${base}/api/community/channels/${REF_PLACEHOLDER_ID}/messages`;
18774
- const openerNonce = req.nonce !== undefined ? `${req.nonce}:opener` : undefined;
18775
- const replyNonce = req.nonce !== undefined ? `${req.nonce}:reply` : undefined;
18776
- const openerRes = await fetchImpl(endpoint, {
18777
- method: "POST",
18778
- headers: {
18779
- "content-type": "application/json",
18780
- authorization: `Bearer ${config2.voucher}`
18781
- },
18782
- body: JSON.stringify({
18783
- channel: req.forum,
18784
- content: { text: req.title },
18785
- attachments: req.attachments ?? [],
18786
- ...openerNonce !== undefined ? { nonce: openerNonce } : {}
18787
- })
18788
- });
18789
- const opener = await parseJsonResponse(openerRes, "createPost opener");
18790
- if (opener.state !== "sent" || !opener.message || !opener.threadId) {
18791
- throw new Error("createPost: upstream response missing opener thread");
18792
- }
18793
- const threadRef = `${req.forum}/#${opener.message.seq.replace(/^#/, "")}`;
18794
- const replyRes = await fetchImpl(endpoint, {
18795
- method: "POST",
18796
- headers: {
18797
- "content-type": "application/json",
18798
- authorization: `Bearer ${config2.voucher}`
18799
- },
18800
- body: JSON.stringify({
18801
- channel: threadRef,
18802
- content: req.content,
18803
- attachments: req.attachments ?? [],
18804
- ...replyNonce !== undefined ? { nonce: replyNonce } : {}
18805
- })
18806
- });
18807
- const reply = await parseJsonResponse(replyRes, "createPost reply");
18808
- if (reply.state !== "sent" || !reply.message)
18809
- throw new Error("createPost: upstream response missing reply");
18810
- return {
18811
- ref: reply.message.channel,
18812
- name: req.title,
18813
- seq: Number(reply.message.seq.replace(/^#/, ""))
18814
- };
18815
- }
18816
18866
  async function callRead(req) {
18817
18867
  const q = new URLSearchParams;
18818
18868
  q.set("ref", req.channel);
@@ -19096,7 +19146,6 @@ function createProxyServerApi(config2) {
19096
19146
  inboxSnapshot: (_r) => callInboxSnapshot(),
19097
19147
  ack: callAck,
19098
19148
  send: callSend,
19099
- createPost: callCreatePost,
19100
19149
  read: callRead,
19101
19150
  resolve: callResolve,
19102
19151
  listMembers: callListMembers,
@@ -19162,7 +19211,7 @@ function cliCommandsSection() {
19162
19211
  "### Messaging",
19163
19212
  "",
19164
19213
  `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).`,
19165
- `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\`.`,
19214
+ `2. \`${CLI} message send\` — send to a channel, DM, or thread. ` + `For a short body, use explicit \`--stdin\` with a quoted heredoc; for a long or complicated body, ` + `use \`--file <path>\`. 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\`.`,
19166
19215
  `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>\`.`,
19167
19216
  `4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
19168
19217
  `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\`).`,
@@ -19193,7 +19242,7 @@ function cliCommandsSection() {
19193
19242
  "",
19194
19243
  "### Context Lifecycle",
19195
19244
  "",
19196
- `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.`,
19245
+ `1. \`${CLI} nap --handoff <file>\` — 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.`,
19197
19246
  "",
19198
19247
  "### Output format",
19199
19248
  "",
@@ -19212,9 +19261,11 @@ function messagingSection() {
19212
19261
  "You can initiate conversations — send to any channel or DM someone directly. You're not " + "limited to replying. Use the same `message send` command whether you're replying or " + "starting a conversation.",
19213
19262
  "",
19214
19263
  "- Reply where the message came from. Post results in the channel that owns the topic. " + "When uncertain, read history (below) or DM the relevant people.",
19215
- `- Short reply: \`${CLI} message send --target <ref> --text "brief reply"\`.`,
19216
- `- Long or complicated: write body to a tmp file, then \`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
19217
- `- 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.",
19264
+ "Free-form message bodies never go in command arguments.",
19265
+ "",
19266
+ `- Short reply: use \`${CLI} message send --target <ref> --stdin\` with the quoted-heredoc form shown ` + "under *Message formatting*. Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
19267
+ `- Long or complicated: write the body to a temporary file with a filesystem tool, then ` + `\`${CLI} message send --target <ref> --file ./temp_msg.md\`.`,
19268
+ `- Cite a specific message: add \`--reply "#37"\` to either form — \`--reply\` takes the \`#N\` seq ` + "(within `--target`) of the message you're answering.",
19218
19269
  "",
19219
19270
  "### Context refs",
19220
19271
  "",
@@ -19254,8 +19305,10 @@ function messagingSection() {
19254
19305
  "- **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.`,
19255
19306
  "",
19256
19307
  "```bash",
19257
- "# Notify Alice and point her to a message for context.",
19258
- `${CLI} message send --target "/demo#1234/general" --text "@alice#0001 Please review /demo#1234/general#42"`,
19308
+ "# Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
19309
+ `${CLI} message send --target "/demo#1234/general" --stdin <<'ALOOK_MESSAGE_7F3C'`,
19310
+ "@alice#0001 Please review /demo#1234/general#42",
19311
+ "ALOOK_MESSAGE_7F3C",
19259
19312
  "```",
19260
19313
  "",
19261
19314
  "### Pulled messages",
@@ -19321,7 +19374,7 @@ function utilsSection() {
19321
19374
  "",
19322
19375
  `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.",
19323
19376
  "",
19324
- `Example: \`${CLI} message send --target /demo#1234/team --text "Please review the rollout plan" ` + `--remind-after 1m\`.`
19377
+ `Example: ${CLI} message send --target "/demo#1234/team" --remind-after 1m --file ./message.md`
19325
19378
  ].join(`
19326
19379
  `);
19327
19380
  }
@@ -19658,8 +19711,8 @@ function ensureSymlinks(workDir) {
19658
19711
  try {
19659
19712
  const stat = lstatSync(aliasPath);
19660
19713
  if (stat.isSymbolicLink()) {
19661
- const target = readlinkSync(aliasPath);
19662
- if (target === CANONICAL_FILE)
19714
+ const target2 = readlinkSync(aliasPath);
19715
+ if (target2 === CANONICAL_FILE)
19663
19716
  continue;
19664
19717
  unlinkSync2(aliasPath);
19665
19718
  } else {
@@ -19798,11 +19851,11 @@ function buildClaudeProviderIsolationEnv(ctx) {
19798
19851
  fs4.mkdirSync(configDir, { recursive: true });
19799
19852
  const hostClaude = path4.join(process.env.HOME || ".", ".claude");
19800
19853
  for (const sub of ["skills", "commands"]) {
19801
- const target = path4.join(hostClaude, sub);
19854
+ const target2 = path4.join(hostClaude, sub);
19802
19855
  const link = path4.join(configDir, sub);
19803
19856
  try {
19804
- if (fs4.existsSync(target) && !fs4.existsSync(link))
19805
- fs4.symlinkSync(target, link);
19857
+ if (fs4.existsSync(target2) && !fs4.existsSync(link))
19858
+ fs4.symlinkSync(target2, link);
19806
19859
  } catch {}
19807
19860
  }
19808
19861
  return {
@@ -20036,9 +20089,9 @@ function resolveHomePath(relativePath, deps = {}) {
20036
20089
  }
20037
20090
  function resolveSpawnSpec(command, args, override, deps = {}, platform = process.platform) {
20038
20091
  const trimmed = override?.trim();
20039
- const target = trimmed && trimmed.length > 0 ? trimmed : command;
20092
+ const target2 = trimmed && trimmed.length > 0 ? trimmed : command;
20040
20093
  const looksLikePath = trimmed !== undefined && trimmed.length > 0 && /[\\/]/.test(trimmed);
20041
- const resolved = looksLikePath ? target : resolveCommandOnPath(target, deps) ?? target;
20094
+ const resolved = looksLikePath ? target2 : resolveCommandOnPath(target2, deps) ?? target2;
20042
20095
  return { command: resolved, args, shell: needsWindowsShimShell(resolved, platform) };
20043
20096
  }
20044
20097
  function resolveClaudeCommand(deps = {}) {
@@ -21375,8 +21428,8 @@ function listRuntimeIds() {
21375
21428
  var SELECTABLE_RUNTIMES = new Set(["claude", "codex", "opencode", "pi", "cursor"]);
21376
21429
  function resolveAlookCliPath(moduleDir) {
21377
21430
  const thisDir = moduleDir ?? path8.dirname(fileURLToPath(import.meta.url));
21378
- const target = path8.basename(thisDir) === "dist" ? path8.resolve(thisDir, "cli", "index.js") : path8.resolve(thisDir, "..", "scripts", "alook-shim.mjs");
21379
- return fs6.existsSync(target) ? target : null;
21431
+ const target2 = path8.basename(thisDir) === "dist" ? path8.resolve(thisDir, "cli", "index.js") : path8.resolve(thisDir, "..", "scripts", "alook-shim.mjs");
21432
+ return fs6.existsSync(target2) ? target2 : null;
21380
21433
  }
21381
21434
  function deriveCliFallbackCandidates(cliPath) {
21382
21435
  if (!cliPath)
@@ -21780,6 +21833,236 @@ function writeStatusFile(path9, snapshot) {
21780
21833
  } catch {}
21781
21834
  }
21782
21835
 
21836
+ // src/manager/wakeCoordinator.ts
21837
+ class WakeCoordinator {
21838
+ agents = new Map;
21839
+ async run(command, dispatch, onDesiredAdvance) {
21840
+ const state = this.agent(command.agentId);
21841
+ const channel2 = command.unreadNotice.channel;
21842
+ const seq = command.unreadNotice.latestSeq;
21843
+ const scope = state.scopes.get(channel2);
21844
+ const coveredSeq = Math.max(scope?.modelSeenSeq ?? 0, scope?.admittedSeq ?? 0);
21845
+ if (seq <= coveredSeq) {
21846
+ this.rememberReplacement(state, command);
21847
+ return { state: "suppressed", coveredSeq };
21848
+ }
21849
+ const desiredAdvanced = !scope || seq > scope.desiredSeq;
21850
+ if (!scope || seq >= scope.desiredSeq) {
21851
+ state.scopes.set(channel2, {
21852
+ desiredSeq: Math.max(scope?.desiredSeq ?? 0, seq),
21853
+ admittedSeq: scope?.admittedSeq ?? 0,
21854
+ modelSeenSeq: scope?.modelSeenSeq ?? 0,
21855
+ command
21856
+ });
21857
+ }
21858
+ if (desiredAdvanced)
21859
+ onDesiredAdvance?.(command);
21860
+ state.dispatch = dispatch;
21861
+ if (state.active || state.admitting) {
21862
+ this.rememberReplacement(state, command);
21863
+ return { state: "suppressed", coveredSeq: seq };
21864
+ }
21865
+ await this.admit(command.agentId, command);
21866
+ return { state: "accepted" };
21867
+ }
21868
+ modelSeenGeneration(agentId) {
21869
+ return this.agent(agentId).observationGeneration;
21870
+ }
21871
+ recordModelSeen(agentId, messages, generation) {
21872
+ const state = this.agent(agentId);
21873
+ if (generation !== state.observationGeneration)
21874
+ return false;
21875
+ for (const message2 of messages) {
21876
+ const seq = Number(message2.seq.startsWith("#") ? message2.seq.slice(1) : message2.seq);
21877
+ if (!Number.isSafeInteger(seq) || seq <= 0)
21878
+ continue;
21879
+ const scope = state.scopes.get(message2.channel);
21880
+ if (scope) {
21881
+ scope.modelSeenSeq = Math.max(scope.modelSeenSeq, seq);
21882
+ } else {
21883
+ state.scopes.set(message2.channel, {
21884
+ desiredSeq: 0,
21885
+ admittedSeq: 0,
21886
+ modelSeenSeq: seq,
21887
+ command: null
21888
+ });
21889
+ }
21890
+ }
21891
+ return true;
21892
+ }
21893
+ recordAgentActivity(agentId, activity) {
21894
+ const state = this.agent(agentId);
21895
+ if (activity === "starting" || activity === "running") {
21896
+ state.active = true;
21897
+ return;
21898
+ }
21899
+ if (activity !== "idle")
21900
+ return;
21901
+ state.active = false;
21902
+ state.activeAdmission = null;
21903
+ const next = this.nextPending(state);
21904
+ if (next && state.dispatch)
21905
+ this.admit(agentId, next);
21906
+ }
21907
+ recordDeliveryAck(agentId, launchId, status) {
21908
+ const state = this.agents.get(agentId);
21909
+ const admission = state?.activeAdmission;
21910
+ if (!state || !admission || admission.launchId !== launchId)
21911
+ return;
21912
+ if (status === "ok")
21913
+ return;
21914
+ for (const [channel2, coverage] of admission.coverage) {
21915
+ const scope = state.scopes.get(channel2);
21916
+ if (scope?.admittedSeq === coverage.admittedSeq) {
21917
+ scope.admittedSeq = coverage.previousSeq;
21918
+ }
21919
+ }
21920
+ state.active = false;
21921
+ state.failedAdmissionLaunchId = launchId;
21922
+ state.retryAfterFailure = state.coalescedReplacement;
21923
+ state.coalescedReplacement = null;
21924
+ state.activeAdmission = null;
21925
+ if (!state.admitting && state.retryAfterFailure) {
21926
+ this.retryFailedAdmission(agentId, state).catch(() => {});
21927
+ }
21928
+ }
21929
+ invalidate(agentId, clearModelSeen) {
21930
+ const state = this.agent(agentId);
21931
+ state.observationGeneration += 1;
21932
+ state.active = false;
21933
+ state.admitting = false;
21934
+ state.activeAdmission = null;
21935
+ state.failedAdmissionLaunchId = null;
21936
+ state.coalescedReplacement = null;
21937
+ state.retryAfterFailure = null;
21938
+ if (clearModelSeen) {
21939
+ state.scopes.clear();
21940
+ return;
21941
+ }
21942
+ for (const scope of state.scopes.values()) {
21943
+ scope.desiredSeq = scope.modelSeenSeq;
21944
+ scope.admittedSeq = scope.modelSeenSeq;
21945
+ }
21946
+ }
21947
+ agent(agentId) {
21948
+ let state = this.agents.get(agentId);
21949
+ if (!state) {
21950
+ state = {
21951
+ active: false,
21952
+ admitting: false,
21953
+ activeAdmission: null,
21954
+ failedAdmissionLaunchId: null,
21955
+ coalescedReplacement: null,
21956
+ retryAfterFailure: null,
21957
+ observationGeneration: 0,
21958
+ scopes: new Map,
21959
+ dispatch: null
21960
+ };
21961
+ this.agents.set(agentId, state);
21962
+ }
21963
+ return state;
21964
+ }
21965
+ nextPending(state) {
21966
+ let next = null;
21967
+ for (const scope of state.scopes.values()) {
21968
+ if (scope.desiredSeq <= Math.max(scope.modelSeenSeq, scope.admittedSeq))
21969
+ continue;
21970
+ if (!scope.command)
21971
+ continue;
21972
+ if (scope.command.launchId === state.failedAdmissionLaunchId)
21973
+ continue;
21974
+ if (!next || scope.desiredSeq > next.desiredSeq)
21975
+ next = scope;
21976
+ }
21977
+ return next?.command ?? null;
21978
+ }
21979
+ async admit(agentId, preferred) {
21980
+ const state = this.agent(agentId);
21981
+ if (state.active || state.admitting || !state.dispatch)
21982
+ return;
21983
+ const admissionGeneration = state.observationGeneration;
21984
+ const command = preferred ?? this.nextPending(state);
21985
+ if (!command)
21986
+ return;
21987
+ const commandScope = state.scopes.get(command.unreadNotice.channel);
21988
+ if (commandScope && command.unreadNotice.latestSeq >= commandScope.desiredSeq) {
21989
+ commandScope.command = command;
21990
+ }
21991
+ const coverage = new Map;
21992
+ for (const [channel2, scope] of state.scopes) {
21993
+ if (scope.desiredSeq <= Math.max(scope.modelSeenSeq, scope.admittedSeq))
21994
+ continue;
21995
+ coverage.set(channel2, {
21996
+ previousSeq: scope.admittedSeq,
21997
+ admittedSeq: scope.desiredSeq
21998
+ });
21999
+ scope.admittedSeq = scope.desiredSeq;
22000
+ scope.command = command;
22001
+ }
22002
+ if (coverage.size === 0)
22003
+ return;
22004
+ state.admitting = true;
22005
+ state.active = true;
22006
+ state.failedAdmissionLaunchId = null;
22007
+ state.coalescedReplacement = null;
22008
+ state.activeAdmission = { launchId: command.launchId, coverage };
22009
+ let dispatchError;
22010
+ try {
22011
+ await state.dispatch(command);
22012
+ } catch (error51) {
22013
+ if (state.observationGeneration === admissionGeneration) {
22014
+ for (const [channel2, admitted] of coverage) {
22015
+ const scope = state.scopes.get(channel2);
22016
+ if (scope?.admittedSeq === admitted.admittedSeq) {
22017
+ scope.admittedSeq = admitted.previousSeq;
22018
+ }
22019
+ }
22020
+ state.active = false;
22021
+ state.failedAdmissionLaunchId = command.launchId;
22022
+ state.retryAfterFailure = state.coalescedReplacement;
22023
+ state.coalescedReplacement = null;
22024
+ state.activeAdmission = null;
22025
+ }
22026
+ dispatchError = error51;
22027
+ } finally {
22028
+ if (state.observationGeneration === admissionGeneration) {
22029
+ state.admitting = false;
22030
+ }
22031
+ }
22032
+ if (state.observationGeneration === admissionGeneration) {
22033
+ await this.retryFailedAdmission(agentId, state);
22034
+ if (!state.active && !state.admitting) {
22035
+ const next = this.nextPending(state);
22036
+ if (next)
22037
+ await this.admit(agentId, next);
22038
+ }
22039
+ }
22040
+ if (dispatchError)
22041
+ throw dispatchError;
22042
+ }
22043
+ rememberReplacement(state, command) {
22044
+ const failedOrActiveLaunchId = state.activeAdmission?.launchId ?? state.failedAdmissionLaunchId;
22045
+ if (!failedOrActiveLaunchId || command.launchId === failedOrActiveLaunchId)
22046
+ return;
22047
+ const scope = state.scopes.get(command.unreadNotice.channel);
22048
+ if (!scope || command.unreadNotice.latestSeq < scope.desiredSeq)
22049
+ return;
22050
+ state.coalescedReplacement = command;
22051
+ if (state.failedAdmissionLaunchId)
22052
+ state.retryAfterFailure = command;
22053
+ }
22054
+ async retryFailedAdmission(agentId, state) {
22055
+ if (state.active || state.admitting)
22056
+ return;
22057
+ const replacement = state.retryAfterFailure;
22058
+ state.retryAfterFailure = null;
22059
+ if (!replacement)
22060
+ return;
22061
+ state.failedAdmissionLaunchId = null;
22062
+ await this.admit(agentId, replacement);
22063
+ }
22064
+ }
22065
+
21783
22066
  // src/server/wsControlChannel.ts
21784
22067
  var WS_CONTROL_COMMAND_CONSUMED = Symbol("ws-control-command-consumed");
21785
22068
  var DEFAULT_PING_INTERVAL_MS = 15000;
@@ -21794,6 +22077,7 @@ class WsControlChannel {
21794
22077
  opts;
21795
22078
  statusValue = "idle";
21796
22079
  commandCbs = [];
22080
+ wakeDesiredCbs = [];
21797
22081
  resyncHooks = [];
21798
22082
  ws = null;
21799
22083
  attempt = 0;
@@ -21803,6 +22087,7 @@ class WsControlChannel {
21803
22087
  pongDeadline = 0;
21804
22088
  resyncProvider = null;
21805
22089
  log;
22090
+ wakeCoordinator = new WakeCoordinator;
21806
22091
  constructor(opts) {
21807
22092
  this.opts = opts;
21808
22093
  this.log = opts.logger ?? createLogger2({ header: "@alook/daemon:ws" });
@@ -21825,6 +22110,30 @@ class WsControlChannel {
21825
22110
  onCommand(cb) {
21826
22111
  this.commandCbs.push(cb);
21827
22112
  }
22113
+ onWakeDesiredAdvance(cb) {
22114
+ this.wakeDesiredCbs.push(cb);
22115
+ }
22116
+ async ingestCommand(frame) {
22117
+ const parsed = HostCommandSchema.safeParse(frame);
22118
+ if (!parsed.success) {
22119
+ this.log.warn("dropped malformed HostCommand frame", {
22120
+ type: typeof frame === "object" && frame !== null && "type" in frame ? String(frame.type) : "unknown",
22121
+ issues: parsed.error.issues.map((issue3) => ({
22122
+ path: issue3.path.join("."),
22123
+ code: issue3.code
22124
+ }))
22125
+ });
22126
+ return false;
22127
+ }
22128
+ await this.dispatchIngressCommand(parsed.data);
22129
+ return true;
22130
+ }
22131
+ modelSeenGeneration(agentId) {
22132
+ return this.wakeCoordinator.modelSeenGeneration(agentId);
22133
+ }
22134
+ recordModelSeen(agentId, messages, generation = this.wakeCoordinator.modelSeenGeneration(agentId)) {
22135
+ return this.wakeCoordinator.recordModelSeen(agentId, messages, generation);
22136
+ }
21828
22137
  onResync(provider) {
21829
22138
  this.resyncProvider = provider;
21830
22139
  }
@@ -21841,6 +22150,7 @@ class WsControlChannel {
21841
22150
  this.sendFrame({ type: "agent_session", ...info });
21842
22151
  }
21843
22152
  async reportAgentActivity(info) {
22153
+ this.wakeCoordinator.recordAgentActivity(info.agentId, info.state);
21844
22154
  this.sendFrame({ type: "agent_activity", ...info });
21845
22155
  }
21846
22156
  reportAgentTyping(info) {
@@ -21853,6 +22163,7 @@ class WsControlChannel {
21853
22163
  this.sendFrame(frame);
21854
22164
  }
21855
22165
  async reportWakeAck(info) {
22166
+ this.wakeCoordinator.recordDeliveryAck(info.agentId, info.launchId, info.status);
21856
22167
  this.sendFrame({ type: "agent_wake_ack", ...info });
21857
22168
  }
21858
22169
  async reportStoppedAck(info) {
@@ -21924,27 +22235,75 @@ class WsControlChannel {
21924
22235
  return;
21925
22236
  }
21926
22237
  this.attempt = 0;
21927
- const parsed = HostCommandSchema.safeParse(frame);
21928
- if (!parsed.success) {
21929
- this.log.warn("dropped malformed HostCommand frame", {
21930
- type: frame.type,
21931
- issues: parsed.error.issues.map((i) => ({ path: i.path.join("."), code: i.code }))
21932
- });
21933
- return;
21934
- }
21935
- const cmd = parsed.data;
22238
+ this.ingestCommand(frame).catch((err) => {
22239
+ this.log.warn("command ingress failed", { type: frame.type, err: describeErr(err) });
22240
+ });
22241
+ }
22242
+ async dispatchListeners(cmd) {
22243
+ const pending = [];
21936
22244
  for (const cb of this.commandCbs) {
21937
22245
  try {
21938
22246
  const result = cb(cmd);
21939
22247
  if (result === WS_CONTROL_COMMAND_CONSUMED)
21940
22248
  break;
21941
- Promise.resolve(result).catch((err) => {
22249
+ pending.push(Promise.resolve(result).catch((err) => {
21942
22250
  this.log.warn("command listener threw", { type: cmd.type, err: describeErr(err) });
21943
- });
22251
+ }));
21944
22252
  } catch (err) {
21945
22253
  this.log.warn("command listener threw synchronously", { type: cmd.type, err: describeErr(err) });
21946
22254
  }
21947
22255
  }
22256
+ await Promise.all(pending);
22257
+ }
22258
+ async dispatchIngressCommand(cmd) {
22259
+ if (cmd.type === "machine:heartbeat") {
22260
+ this.sendFrame({ type: "machine_heartbeat_ack", nonce: cmd.nonce });
22261
+ return;
22262
+ }
22263
+ if (cmd.type === "diagnostics:collect") {
22264
+ this.sendFrame({ type: "diagnostics_ack", reportId: cmd.reportId });
22265
+ await this.dispatchListeners(cmd);
22266
+ return;
22267
+ }
22268
+ if (cmd.type === "agent:wake") {
22269
+ const result = await this.wakeCoordinator.run(cmd, (accepted) => this.dispatchListeners(accepted), (advanced) => {
22270
+ for (const cb of this.wakeDesiredCbs) {
22271
+ try {
22272
+ cb(advanced);
22273
+ } catch (err) {
22274
+ this.log.warn("wake desired observer threw", { err: describeErr(err) });
22275
+ }
22276
+ }
22277
+ });
22278
+ if (result.state === "suppressed") {
22279
+ await this.reportWakeAck({
22280
+ agentId: cmd.agentId,
22281
+ launchId: cmd.launchId,
22282
+ status: "ok"
22283
+ });
22284
+ this.log.debug("duplicate wake suppressed", {
22285
+ agentId: cmd.agentId,
22286
+ channel: cmd.unreadNotice.channel,
22287
+ latestSeq: cmd.unreadNotice.latestSeq,
22288
+ coveredSeq: result.coveredSeq
22289
+ });
22290
+ }
22291
+ return;
22292
+ }
22293
+ if (cmd.type === "machine:reset_all") {
22294
+ for (const reset of cmd.resets)
22295
+ this.wakeCoordinator.invalidate(reset.agentId, true);
22296
+ await this.dispatchListeners(cmd);
22297
+ return;
22298
+ }
22299
+ if (cmd.type === "agent:stop" || cmd.type === "agent:model_switch") {
22300
+ this.wakeCoordinator.invalidate(cmd.agentId, false);
22301
+ } else if (cmd.type === "agent:reset" || cmd.type === "agent:nap") {
22302
+ this.wakeCoordinator.invalidate(cmd.agentId, true);
22303
+ } else if (cmd.type === "bot:removed") {
22304
+ this.wakeCoordinator.invalidate(cmd.botId, true);
22305
+ }
22306
+ await this.dispatchListeners(cmd);
21948
22307
  }
21949
22308
  onSocketClosed(code, reason) {
21950
22309
  this.log.warn("control channel closed", { code, reason: reason ? String(reason) : "" });
@@ -21979,7 +22338,10 @@ class WsControlChannel {
21979
22338
  this.pingTimer = setInterval(() => {
21980
22339
  if (this.now() > this.pongDeadline) {
21981
22340
  this.log.warn("heartbeat pong timeout — forcing reconnect");
21982
- this.ws?.close();
22341
+ if (this.ws?.terminate)
22342
+ this.ws.terminate();
22343
+ else
22344
+ this.ws?.close();
21983
22345
  return;
21984
22346
  }
21985
22347
  this.log.debug("heartbeat ping");
@@ -22131,6 +22493,14 @@ function writeJson(res, status, body) {
22131
22493
  res.writeHead(status, { "content-type": "application/json" });
22132
22494
  res.end(JSON.stringify(body));
22133
22495
  }
22496
+ function normalizeContentEncoding(value) {
22497
+ const normalized = (Array.isArray(value) ? value.join(",") : value ?? "").trim().toLowerCase();
22498
+ if (!normalized || normalized === "identity")
22499
+ return "identity";
22500
+ if (normalized === "gzip" || normalized === "deflate" || normalized === "br")
22501
+ return normalized;
22502
+ return "other";
22503
+ }
22134
22504
  function isCanonicalChannelScope(channel2) {
22135
22505
  try {
22136
22506
  const parsed = parseRef(channel2);
@@ -22227,6 +22597,11 @@ async function startCredentialProxy(broker, options = {}) {
22227
22597
  const upstreamClient = upstream.protocol === "https:" ? https : http;
22228
22598
  const onPull = options.onInboxPullResponse;
22229
22599
  const onProxyRequest = options.onProxyRequest;
22600
+ const reportInboxPullObservationError = (failure) => {
22601
+ try {
22602
+ options.onInboxPullObservationError?.(failure);
22603
+ } catch {}
22604
+ };
22230
22605
  const server = http.createServer((req, res) => {
22231
22606
  const pathname = new URL2(req.url ?? "/", "http://placeholder").pathname;
22232
22607
  if (pathname.startsWith("/__alook/local/")) {
@@ -22265,7 +22640,20 @@ async function startCredentialProxy(broker, options = {}) {
22265
22640
  return;
22266
22641
  }
22267
22642
  const reg = verdict.reg;
22268
- const isInboxPull = onPull && pathname === "/api/community/users/me/inbox/pull";
22643
+ const isInboxPull = req.method === "POST" && pathname === "/api/community/users/me/inbox/pull";
22644
+ const shouldObserveInboxPull = Boolean(isInboxPull && onPull);
22645
+ let inboxPullObservationToken;
22646
+ if (shouldObserveInboxPull) {
22647
+ try {
22648
+ inboxPullObservationToken = options.onInboxPullStart?.(reg.agentId);
22649
+ } catch {
22650
+ reportInboxPullObservationError({
22651
+ agentId: reg.agentId,
22652
+ reason: "observer_failed",
22653
+ contentEncoding: "identity"
22654
+ });
22655
+ }
22656
+ }
22269
22657
  if (onProxyRequest) {
22270
22658
  try {
22271
22659
  onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
@@ -22278,6 +22666,8 @@ async function startCredentialProxy(broker, options = {}) {
22278
22666
  outHeaders[broker.headerNames.agentId.toLowerCase()] = reg.agentId;
22279
22667
  outHeaders[broker.headerNames.client.toLowerCase()] = broker.clientLabel;
22280
22668
  outHeaders[broker.headerNames.capabilities.toLowerCase()] = [...reg.capabilities].join(",");
22669
+ if (isInboxPull)
22670
+ outHeaders["accept-encoding"] = "identity";
22281
22671
  let responded = false;
22282
22672
  let upstreamRes;
22283
22673
  const upstreamReq = upstreamClient.request({
@@ -22296,18 +22686,54 @@ async function startCredentialProxy(broker, options = {}) {
22296
22686
  };
22297
22687
  res_.on("error", destroyResIfIncomplete);
22298
22688
  res_.on("close", destroyResIfIncomplete);
22299
- if (isInboxPull && res_.statusCode && res_.statusCode < 300) {
22689
+ if (shouldObserveInboxPull && res_.statusCode !== undefined && res_.statusCode >= 200 && res_.statusCode < 300) {
22300
22690
  const chunks = [];
22301
22691
  res_.on("data", (chunk2) => chunks.push(chunk2));
22302
22692
  res_.on("end", () => {
22303
22693
  const body = Buffer.concat(chunks);
22694
+ const contentEncoding = normalizeContentEncoding(res_.headers["content-encoding"]);
22695
+ if (contentEncoding !== "identity") {
22696
+ reportInboxPullObservationError({
22697
+ agentId: reg.agentId,
22698
+ reason: "unexpected_content_encoding",
22699
+ contentEncoding
22700
+ });
22701
+ } else {
22702
+ let parsed;
22703
+ try {
22704
+ parsed = JSON.parse(body.toString("utf8"));
22705
+ } catch {
22706
+ reportInboxPullObservationError({
22707
+ agentId: reg.agentId,
22708
+ reason: "invalid_json",
22709
+ contentEncoding
22710
+ });
22711
+ }
22712
+ if (parsed !== undefined) {
22713
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || !Array.isArray(parsed.messages)) {
22714
+ reportInboxPullObservationError({
22715
+ agentId: reg.agentId,
22716
+ reason: "invalid_inbox_shape",
22717
+ contentEncoding
22718
+ });
22719
+ } else {
22720
+ const messages = parsed.messages;
22721
+ if (messages.length > 0) {
22722
+ try {
22723
+ onPull(reg.agentId, messages, inboxPullObservationToken);
22724
+ } catch {
22725
+ reportInboxPullObservationError({
22726
+ agentId: reg.agentId,
22727
+ reason: "observer_failed",
22728
+ contentEncoding
22729
+ });
22730
+ }
22731
+ }
22732
+ }
22733
+ }
22734
+ }
22304
22735
  res.writeHead(res_.statusCode, res_.headers);
22305
22736
  res.end(body);
22306
- try {
22307
- const parsed = JSON.parse(body.toString());
22308
- if (parsed.messages)
22309
- onPull(reg.agentId, parsed.messages);
22310
- } catch {}
22311
22737
  });
22312
22738
  } else {
22313
22739
  res.writeHead(res_.statusCode ?? 502, res_.headers);
@@ -23226,17 +23652,17 @@ function extractToolAudit(rawName, rawInput) {
23226
23652
  return { name, suppressed: false };
23227
23653
  return { name, target: truncateTargetToCodeUnits(firstLine), suppressed: false };
23228
23654
  }
23229
- let target;
23655
+ let target2;
23230
23656
  if (cls === "file_target")
23231
- target = pickFileTarget(rawInput);
23657
+ target2 = pickFileTarget(rawInput);
23232
23658
  else if (cls === "pattern")
23233
- target = pickPatternTarget(rawInput);
23659
+ target2 = pickPatternTarget(rawInput);
23234
23660
  else
23235
- target = pickFallthroughTarget(rawInput);
23236
- if (typeof target !== "string" || target.length === 0) {
23661
+ target2 = pickFallthroughTarget(rawInput);
23662
+ if (typeof target2 !== "string" || target2.length === 0) {
23237
23663
  return { name, suppressed: false };
23238
23664
  }
23239
- return { name, target: truncateTargetToCodeUnits(target), suppressed: false };
23665
+ return { name, target: truncateTargetToCodeUnits(target2), suppressed: false };
23240
23666
  }
23241
23667
  function truncateThinking(text2) {
23242
23668
  const chars = [...text2].length;
@@ -24031,7 +24457,7 @@ var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. C
24031
24457
  function buildNapRewakePrompt(handoff) {
24032
24458
  return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
24033
24459
 
24034
- ` + handoff.trim() + `
24460
+ ` + handoff + `
24035
24461
 
24036
24462
  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.";
24037
24463
  }
@@ -24069,6 +24495,7 @@ class AgentRouter {
24069
24495
  buildReady() {
24070
24496
  return {
24071
24497
  runtimeReport: [...this.runtimes.values()],
24498
+ capabilities: [CONTROL_HEARTBEAT_CAPABILITY],
24072
24499
  runningAgents: [...this.running],
24073
24500
  hostname: this.opts.hostname,
24074
24501
  platform: this.opts.platform,
@@ -24911,6 +25338,8 @@ function createTimelineRecorder(opts) {
24911
25338
  sessionByAgent.set(agentId, sessionId);
24912
25339
  },
24913
25340
  appendEntryForAgent(agentId, messages) {
25341
+ if (messages.length === 0)
25342
+ return;
24914
25343
  const dir = dirFor(agentId);
24915
25344
  if (!prepareTimelineDirectory(dir))
24916
25345
  return;
@@ -25330,7 +25759,16 @@ async function createDaemon(opts) {
25330
25759
  const typingTracker = createTypingScopeTracker();
25331
25760
  const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
25332
25761
  const proxy = await startCredentialProxy(broker, {
25333
- onInboxPullResponse: (agentId, messages) => timeline2.appendEntryForAgent(agentId, messages),
25762
+ onInboxPullStart: (agentId) => channelRef?.modelSeenGeneration(agentId),
25763
+ onInboxPullResponse: (agentId, messages, observationToken) => {
25764
+ timeline2.appendEntryForAgent(agentId, messages);
25765
+ if (typeof observationToken === "number") {
25766
+ channelRef?.recordModelSeen(agentId, messages, observationToken);
25767
+ }
25768
+ },
25769
+ onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
25770
+ log2.warn("inbox pull timeline observation failed", { agentId, reason, contentEncoding });
25771
+ },
25334
25772
  onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
25335
25773
  onProxyRequest: (agentId, method, pathname) => {
25336
25774
  const subcommand = deriveAuditLogSubcommand(pathname, method);
@@ -25431,11 +25869,29 @@ async function createDaemon(opts) {
25431
25869
  if (!res.ok)
25432
25870
  throw new Error(`resync-wakes ${res.status}`);
25433
25871
  const json2 = await res.json();
25434
- log2.info("wake resync completed", { woken: json2.woken ?? 0 });
25872
+ log2.info("wake resync completed", { attempted: json2.attempted ?? 0 });
25435
25873
  } catch (err) {
25436
25874
  log2.warn("wake resync failed", { err: err instanceof Error ? err.message : String(err) });
25437
25875
  }
25438
25876
  }
25877
+ async function resyncPendingDiagnostics() {
25878
+ try {
25879
+ const res = await fetch(`${opts.serverUrl}/api/community/daemon/resync-diagnostics`, {
25880
+ method: "POST",
25881
+ headers: { authorization: `Bearer ${opts.machineKey}` }
25882
+ });
25883
+ if (!res.ok)
25884
+ throw new Error(`resync-diagnostics ${res.status}`);
25885
+ const json2 = await res.json();
25886
+ log2.info("diagnostics resync completed", {
25887
+ pending: json2.pending ?? 0,
25888
+ attempted: json2.attempted ?? 0,
25889
+ ambiguous: json2.ambiguous ?? 0
25890
+ });
25891
+ } catch (err) {
25892
+ log2.warn("diagnostics resync failed", { err: err instanceof Error ? err.message : String(err) });
25893
+ }
25894
+ }
25439
25895
  const enrollAgent = async (agentId) => {
25440
25896
  const existing = enrolledKeys.get(agentId);
25441
25897
  if (existing)
@@ -25611,7 +26067,10 @@ async function createDaemon(opts) {
25611
26067
  let statusTimer = null;
25612
26068
  if (opts.statusFilePath) {
25613
26069
  const statusPath = opts.statusFilePath;
25614
- const writeStatus = () => writeStatusFile(statusPath, { writtenAt: Date.now(), agents: manager.statusProjection(Date.now()) });
26070
+ const writeStatus = () => {
26071
+ const nowMs = Date.now();
26072
+ writeStatusFile(statusPath, { writtenAt: nowMs, agents: manager.statusProjection(nowMs) });
26073
+ };
25615
26074
  writeStatus();
25616
26075
  statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
25617
26076
  statusTimer.unref?.();
@@ -25658,11 +26117,11 @@ async function createDaemon(opts) {
25658
26117
  handleDiagnosticCommand: opts.handleDiagnosticCommand,
25659
26118
  reportDiagnosticFailure: opts.reportDiagnosticFailure
25660
26119
  }));
26120
+ channel2.onWakeDesiredAdvance((cmd) => {
26121
+ reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
26122
+ });
25661
26123
  channel2.onCommand((cmd) => {
25662
26124
  switch (cmd.type) {
25663
- case "agent:wake":
25664
- reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
25665
- break;
25666
26125
  case "agent:stop":
25667
26126
  reminderSchedulerRef?.clearAgent(cmd.agentId);
25668
26127
  break;
@@ -25679,6 +26138,7 @@ async function createDaemon(opts) {
25679
26138
  channel2.onOpen(() => {
25680
26139
  coldStartWarmup();
25681
26140
  resyncPendingWakes();
26141
+ resyncPendingDiagnostics();
25682
26142
  });
25683
26143
  channel2.connect();
25684
26144
  await router.start();
@@ -27510,6 +27970,7 @@ var STOP_GRACE_MS = 5000;
27510
27970
  var STOP_KILL_GRACE_MS = 2000;
27511
27971
  var POLL_MS2 = 100;
27512
27972
  var START_RECEIPT_TIMEOUT_MS = 15000;
27973
+ var PAIRING_ACTIVATION_TIMEOUT_MS = 30000;
27513
27974
  var RUNNER_TERM_GRACE_MS = 2000;
27514
27975
  var RUNNER_KILL_GRACE_MS = 2000;
27515
27976
  var MACHINE_ID_PATTERN = /^cm_[A-Za-z0-9_-]{8,64}$/;
@@ -27856,6 +28317,22 @@ function reconcileLegacyMachineKeyOwnership(baseDir, machineKey) {
27856
28317
  removePidFileIfMatches(candidate, current);
27857
28318
  }
27858
28319
  }
28320
+ function daemonLastActiveMs(snapshot, nowMs) {
28321
+ const writtenAt = snapshot.writtenAt;
28322
+ if (writtenAt == null || !Number.isFinite(writtenAt) || writtenAt < 0)
28323
+ return null;
28324
+ let latest = null;
28325
+ for (const agent2 of snapshot.agents) {
28326
+ const sinceProgressMs = agent2.sinceProgressMs;
28327
+ if (!Number.isFinite(sinceProgressMs) || sinceProgressMs < 0)
28328
+ continue;
28329
+ const progressAt = writtenAt - sinceProgressMs;
28330
+ if (progressAt <= 0 || progressAt > nowMs)
28331
+ continue;
28332
+ latest = latest == null ? progressAt : Math.max(latest, progressAt);
28333
+ }
28334
+ return latest;
28335
+ }
27859
28336
  function daemonList(opts) {
27860
28337
  const baseDir = opts.baseDir || process.env.ALOOK_DATA_DIR || DEFAULT_BASE_DIR;
27861
28338
  const dir = daemonsDir(baseDir);
@@ -27879,7 +28356,7 @@ function daemonList(opts) {
27879
28356
  if (s.found) {
27880
28357
  agents = s.agents.length;
27881
28358
  running = s.agents.filter((a) => a.derivedActivity === "running").length;
27882
- lastActiveMs = s.writtenAt;
28359
+ lastActiveMs = daemonLastActiveMs(s, now);
27883
28360
  }
27884
28361
  }
27885
28362
  results.push({ id, pid: data.pid, alive, agents, running, lastActiveMs });
@@ -28036,20 +28513,58 @@ function findExistingCredentialForBearer(baseDir, bearer) {
28036
28513
  }
28037
28514
  return null;
28038
28515
  }
28039
- async function activatePairingToken(serverUrl, tokenId, hostname4, platform, arch, osRelease, daemonVersion, runtimeReport) {
28040
- const res = await fetch(`${serverUrl}/api/community/daemon/activate`, {
28041
- method: "POST",
28042
- headers: {
28043
- "content-type": "application/json",
28044
- authorization: `Bearer ${tokenId}`
28045
- },
28046
- body: JSON.stringify({ hostname: hostname4, platform, arch, osRelease, daemonVersion, runtimeReport })
28047
- });
28048
- const json2 = await res.json().catch(() => ({}));
28049
- if (!res.ok || !json2.credential || !json2.machineId) {
28050
- throw new Error(json2.error ?? `activate failed (${res.status})`);
28516
+ async function activatePairingToken(serverUrl, tokenId, hostname4, platform, arch, osRelease, daemonVersion, runtimeReport, expectedMachineId) {
28517
+ let res;
28518
+ let json2;
28519
+ let timedOut = false;
28520
+ const controller = new AbortController;
28521
+ const timeout = setTimeout(() => {
28522
+ timedOut = true;
28523
+ controller.abort();
28524
+ }, PAIRING_ACTIVATION_TIMEOUT_MS);
28525
+ timeout.unref?.();
28526
+ try {
28527
+ res = await fetch(`${serverUrl}/api/community/daemon/activate`, {
28528
+ method: "POST",
28529
+ headers: {
28530
+ "content-type": "application/json",
28531
+ authorization: `Bearer ${tokenId}`
28532
+ },
28533
+ body: JSON.stringify({
28534
+ hostname: hostname4,
28535
+ platform,
28536
+ arch,
28537
+ osRelease,
28538
+ daemonVersion,
28539
+ runtimeReport,
28540
+ ...expectedMachineId ? { expectedMachineId } : {}
28541
+ }),
28542
+ signal: controller.signal
28543
+ });
28544
+ json2 = await res.json().catch(() => null);
28545
+ } catch (error51) {
28546
+ throw new PairingActivationError(timedOut ? "activation timed out before commit status was known" : error51 instanceof Error ? error51.message : String(error51), "unknown");
28547
+ } finally {
28548
+ clearTimeout(timeout);
28549
+ }
28550
+ if (!res.ok) {
28551
+ const parsed2 = CommunityDaemonActivateErrorResponseSchema.safeParse(json2);
28552
+ throw new PairingActivationError(parsed2.success ? parsed2.data.error : `activate failed (${res.status})`, res.status >= 400 && res.status < 500 && parsed2.success ? parsed2.data.sessionOutcome : "unknown");
28553
+ }
28554
+ const parsed = CommunityDaemonActivateResponseSchema.safeParse(json2);
28555
+ if (!parsed.success) {
28556
+ throw new PairingActivationError("activate returned an invalid response", "unknown");
28557
+ }
28558
+ return { credential: parsed.data.credential, machineId: parsed.data.machineId };
28559
+ }
28560
+
28561
+ class PairingActivationError extends Error {
28562
+ sessionOutcome;
28563
+ constructor(message2, sessionOutcome) {
28564
+ super(message2);
28565
+ this.sessionOutcome = sessionOutcome;
28566
+ this.name = "PairingActivationError";
28051
28567
  }
28052
- return { credential: json2.credential, machineId: json2.machineId };
28053
28568
  }
28054
28569
  async function resolveMachineIdentity(serverUrl, credential) {
28055
28570
  const response = await fetch(`${serverUrl}/api/community/daemon/identity`, {
@@ -28077,12 +28592,13 @@ async function prepareDaemonStart(opts) {
28077
28592
  throw new Error("daemon package version is not a strict release version");
28078
28593
  const ownerToken = crypto5.randomBytes(24).toString("base64url");
28079
28594
  const persisted = opts.machineKey.startsWith("cmk_") ? findExistingCredentialForBearer(baseDir, opts.machineKey) : null;
28595
+ const reconnectMachineId = opts.machineKey.startsWith("cmt_") && opts.expectedMachineId && opts.resumeRequestId ? validateMachineId(opts.expectedMachineId) : undefined;
28080
28596
  let coarseLockPath = null;
28081
28597
  let launchLockPath = null;
28082
28598
  try {
28083
28599
  let machineKey = persisted?.credential;
28084
- let machineId = persisted ? validateMachineId(persisted.machineId) : undefined;
28085
- if (machineKey && machineId) {
28600
+ let machineId = persisted ? validateMachineId(persisted.machineId) : reconnectMachineId;
28601
+ if (machineId) {
28086
28602
  launchLockPath = acquireLaunchLock(baseDir, machineId, ownerToken, opts.resumeRequestId);
28087
28603
  } else {
28088
28604
  coarseLockPath = acquireCoarseLock(baseDir, ownerToken);
@@ -28093,9 +28609,13 @@ async function prepareDaemonStart(opts) {
28093
28609
  const runtimeReport = await detectRuntimes();
28094
28610
  const healthyRuntimeIds = runtimeReport.filter((runtime) => runtime.status === "healthy").map((runtime) => runtime.id);
28095
28611
  if (opts.machineKey.startsWith("cmt_")) {
28096
- const activated = await activatePairingToken(serverUrl, opts.machineKey, os3.hostname(), process.platform, process.arch, os3.release(), daemonVersion, runtimeReport);
28612
+ opts.onActivationAttempt?.();
28613
+ const activated = await activatePairingToken(serverUrl, opts.machineKey, os3.hostname(), process.platform, process.arch, os3.release(), daemonVersion, runtimeReport, opts.expectedMachineId);
28097
28614
  machineKey = activated.credential;
28098
28615
  machineId = validateMachineId(activated.machineId);
28616
+ if (opts.expectedMachineId && machineId !== validateMachineId(opts.expectedMachineId)) {
28617
+ throw new Error("activate returned a different machine identity");
28618
+ }
28099
28619
  } else {
28100
28620
  machineKey ??= opts.machineKey;
28101
28621
  machineId ??= validateMachineId(await resolveMachineIdentity(serverUrl, opts.machineKey));
@@ -28109,6 +28629,7 @@ async function prepareDaemonStart(opts) {
28109
28629
  wsUrl,
28110
28630
  daemonVersion
28111
28631
  });
28632
+ if (false) {}
28112
28633
  if (coarseLockPath)
28113
28634
  removeOwnedFile(coarseLockPath, process.pid, ownerToken);
28114
28635
  const startedAt = new Date().toISOString();
@@ -28167,6 +28688,66 @@ async function daemonStartById(opts) {
28167
28688
  foreground: opts.foreground
28168
28689
  });
28169
28690
  }
28691
+ function pidOwnershipEqual(a, b) {
28692
+ if (!a || !b)
28693
+ return a === b;
28694
+ return a.pid === b.pid && a.machineId === b.machineId && a.startedAt === b.startedAt && a.ownerToken === b.ownerToken;
28695
+ }
28696
+ async function daemonReconnect(opts, deps = {}) {
28697
+ if (!opts.machineKey.startsWith("cmt_")) {
28698
+ throw new Error("daemon reconnect requires a cmt_ pairing token");
28699
+ }
28700
+ const machineId = validateMachineId(opts.id);
28701
+ const baseDir = opts.baseDir || process.env.ALOOK_DATA_DIR || DEFAULT_BASE_DIR;
28702
+ const launch = readDaemonLaunchRecord(baseDir, machineId);
28703
+ const pidPath = pidfilePathById(baseDir, machineId);
28704
+ const before = readPidFile(pidPath);
28705
+ if (before && (before.machineId !== machineId || !before.startedAt || !before.ownerToken)) {
28706
+ throw new Error("daemon reconnect cannot verify the current pid ownership tuple");
28707
+ }
28708
+ const requestId = crypto5.randomUUID();
28709
+ const replacement = acquireDaemonReplacementLock({ baseDir, machineId, requestId });
28710
+ let oldStopped = false;
28711
+ let activationAttempted = false;
28712
+ try {
28713
+ const owned = readPidFile(pidPath);
28714
+ if (!pidOwnershipEqual(before, owned)) {
28715
+ throw new Error("daemon ownership changed after reconnect lock acquisition");
28716
+ }
28717
+ if (owned && (deps.isProcessAlive ?? isProcessAlive)(owned.pid)) {
28718
+ await (deps.stopExactDaemonPid ?? stopExactDaemonPid)(owned.pid);
28719
+ oldStopped = true;
28720
+ removePidFileIfMatches(pidPath, owned);
28721
+ } else if (owned) {
28722
+ removePidFileIfMatches(pidPath, owned);
28723
+ }
28724
+ try {
28725
+ await (deps.start ?? daemonStart)({
28726
+ machineKey: opts.machineKey,
28727
+ serverUrl: opts.serverUrl ?? launch.serverUrl,
28728
+ wsUrl: opts.wsUrl ?? launch.wsUrl,
28729
+ baseDir,
28730
+ resumeRequestId: requestId,
28731
+ expectedMachineId: machineId,
28732
+ onActivationAttempt: () => {
28733
+ activationAttempted = true;
28734
+ }
28735
+ });
28736
+ } catch (error51) {
28737
+ const canRestoreOldEpoch = !activationAttempted || error51 instanceof PairingActivationError && error51.sessionOutcome === "not_committed";
28738
+ if (oldStopped && canRestoreOldEpoch) {
28739
+ try {
28740
+ await (deps.resume ?? daemonResume)({ id: machineId, baseDir, requestId });
28741
+ } catch (rollbackError) {
28742
+ throw new AggregateError([error51, rollbackError], "reconnect failed before rotation and the prior daemon could not be resumed");
28743
+ }
28744
+ }
28745
+ throw error51;
28746
+ }
28747
+ } finally {
28748
+ removeReplacementLockIfMatches(replacement.path, replacement.lock);
28749
+ }
28750
+ }
28170
28751
  function runnerArguments() {
28171
28752
  const command = process.env.ALOOK_DAEMON_PACKAGE_WRAPPER === "1" ? ["run"] : ["daemon", "run"];
28172
28753
  return [...process.execArgv, process.argv[1], ...command];
@@ -28502,7 +29083,38 @@ var TEXT_ESCAPE_MAP = { n: `
28502
29083
  function decodeTextEscapes(s) {
28503
29084
  return s.replace(/\\(.)/g, (m, c) => TEXT_ESCAPE_MAP[c] ?? m);
28504
29085
  }
28505
- var CLIENT_MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
29086
+ async function readLiteralInput(args) {
29087
+ const { command, stdinSelected, stdin, filePath, fileOption } = args;
29088
+ if (stdinSelected && filePath !== undefined) {
29089
+ throw new CliError(`${command}: --stdin and ${fileOption} are mutually exclusive`);
29090
+ }
29091
+ if (stdinSelected) {
29092
+ if (!stdin)
29093
+ throw new CliError(`${command}: stdin is unavailable`);
29094
+ if (stdin.isTTY === true) {
29095
+ throw new CliError(`${command}: --stdin requires piped input; use ${fileOption} in an interactive terminal`);
29096
+ }
29097
+ try {
29098
+ const chunks = [];
29099
+ for await (const chunk2 of stdin) {
29100
+ chunks.push(typeof chunk2 === "string" ? Buffer.from(chunk2, "utf8") : Buffer.from(chunk2));
29101
+ }
29102
+ return Buffer.concat(chunks).toString("utf8");
29103
+ } catch (err) {
29104
+ throw new CliError(`${command}: cannot read stdin: ${err.message}`);
29105
+ }
29106
+ }
29107
+ if (filePath !== undefined) {
29108
+ const fs13 = await import("fs/promises");
29109
+ try {
29110
+ return await fs13.readFile(filePath, "utf8");
29111
+ } catch (err) {
29112
+ throw new CliError(`${command}: cannot read file: ${err.message}`);
29113
+ }
29114
+ }
29115
+ return;
29116
+ }
29117
+ var CLIENT_MAX_ATTACHMENT_BYTES = 26214400;
28506
29118
  function contentTypeFromFilename(filename) {
28507
29119
  const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
28508
29120
  switch (ext) {
@@ -28560,7 +29172,7 @@ async function withTransientMutationRetry(mutation) {
28560
29172
  async function sendWithRetry(api2, req) {
28561
29173
  return withTransientMutationRetry(() => api2.send(req));
28562
29174
  }
28563
- async function cmdMessageSend(opts) {
29175
+ async function cmdMessageSend(opts, stdin) {
28564
29176
  const remindAfterFlag = opts.remindAfter;
28565
29177
  const remindAfterMs = remindAfterFlag === undefined ? undefined : parseRemindAfter(remindAfterFlag);
28566
29178
  const api2 = getApi();
@@ -28568,21 +29180,18 @@ async function cmdMessageSend(opts) {
28568
29180
  const channel2 = opts.target;
28569
29181
  if (!channel2)
28570
29182
  throw new CliError("message send: --target <ref> is required (e.g. /demo-workspace#1234/general)");
28571
- let text2;
28572
29183
  const fileFlag = opts.file;
28573
- const textFlag = opts.text;
28574
- if (fileFlag) {
28575
- const fs13 = await import("fs");
28576
- if (!fs13.existsSync(fileFlag))
28577
- throw new CliError(`message send: file not found: ${fileFlag}`);
28578
- text2 = fs13.readFileSync(fileFlag, "utf8").trim();
28579
- } else if (typeof textFlag === "string") {
28580
- text2 = decodeTextEscapes(textFlag);
28581
- }
29184
+ const text2 = await readLiteralInput({
29185
+ command: "message send",
29186
+ stdinSelected: opts.stdin === true,
29187
+ stdin,
29188
+ filePath: fileFlag,
29189
+ fileOption: "--file <path>"
29190
+ });
28582
29191
  const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
28583
29192
  const hasText = typeof text2 === "string" && text2.trim().length > 0;
28584
29193
  if (!hasText && attachmentIds.length === 0) {
28585
- throw new CliError("message send: --text <text>, --file <path>, or --attachment <id> is required");
29194
+ throw new CliError("message send: --stdin, --file <path>, or --attachment <id> is required");
28586
29195
  }
28587
29196
  let replyToSeq;
28588
29197
  const replyFlag = opts.reply;
@@ -28595,7 +29204,7 @@ async function cmdMessageSend(opts) {
28595
29204
  }
28596
29205
  replyToSeq = n;
28597
29206
  }
28598
- const nonce = randomUUID6();
29207
+ const nonce = randomUUID7();
28599
29208
  const res = await sendWithRetry(api2, {
28600
29209
  agentId: agent2,
28601
29210
  channel: channel2,
@@ -28626,71 +29235,32 @@ async function cmdMessageSend(opts) {
28626
29235
  return { sent, reminder: { armed: false, reason: "local reminder request failed" } };
28627
29236
  }
28628
29237
  }
28629
- async function createPostWithRetry(api2, req) {
28630
- return withTransientMutationRetry(() => api2.createPost(req));
28631
- }
28632
- async function cmdMessagePost(opts) {
28633
- const api2 = getApi();
28634
- const agent2 = agentId(opts);
28635
- const forum = opts.target;
28636
- if (!forum)
28637
- throw new CliError("message post: --target <forum-ref> is required (e.g. /demo#1234/ideas)");
28638
- const title = opts.title;
28639
- if (!title || title.trim().length === 0)
28640
- throw new CliError("message post: --title <name> is required");
28641
- let text2;
28642
- const fileFlag = opts.file;
28643
- const textFlag = opts.text;
28644
- if (fileFlag) {
28645
- const fs13 = await import("fs");
28646
- if (!fs13.existsSync(fileFlag))
28647
- throw new CliError(`message post: file not found: ${fileFlag}`);
28648
- text2 = fs13.readFileSync(fileFlag, "utf8").trim();
28649
- } else if (typeof textFlag === "string") {
28650
- text2 = decodeTextEscapes(textFlag);
28651
- }
28652
- const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
28653
- const hasText = typeof text2 === "string" && text2.trim().length > 0;
28654
- if (!hasText && attachmentIds.length === 0) {
28655
- throw new CliError("message post: --text <text>, --file <path>, or --attachment <id> is required");
28656
- }
28657
- const nonce = randomUUID6();
28658
- const res = await createPostWithRetry(api2, {
28659
- agentId: agent2,
28660
- forum,
28661
- title: title.trim(),
28662
- content: { text: text2 ?? "" },
28663
- attachments: attachmentIds.length > 0 ? attachmentIds : undefined,
28664
- nonce
28665
- });
28666
- return { posted: res.ref };
28667
- }
28668
29238
  async function cmdMessageEmoji(opts) {
28669
29239
  const api2 = getApi();
28670
- const target = opts.target;
29240
+ const target2 = opts.target;
28671
29241
  const emoji3 = opts.emoji;
28672
- if (!target)
29242
+ if (!target2)
28673
29243
  throw new CliError("message emoji: --target <ref> is required (e.g. /demo#1234/general#42)");
28674
29244
  if (!emoji3)
28675
29245
  throw new CliError("message emoji: --emoji <string> is required");
28676
- const { channel: channel2, seq } = parseMessageTarget("message emoji", target);
29246
+ const { channel: channel2, seq } = parseMessageTarget("message emoji", target2);
28677
29247
  if (Buffer.byteLength(emoji3, "utf8") > MAX_EMOJI_BYTES) {
28678
29248
  const err = new CliError("emoji is too long");
28679
29249
  err.hint = "use a single emoji, not a phrase";
28680
29250
  throw err;
28681
29251
  }
28682
29252
  const res = await api2.reactAdd({ channel: channel2, seq, emoji: emoji3 });
28683
- return { target, emoji: emoji3, duplicate: res.duplicate === true };
29253
+ return { target: target2, emoji: emoji3, duplicate: res.duplicate === true };
28684
29254
  }
28685
- function parseMessageTarget(command, target) {
29255
+ function parseMessageTarget(command, target2) {
28686
29256
  let parsed;
28687
29257
  try {
28688
- parsed = parseRef(target);
29258
+ parsed = parseRef(target2);
28689
29259
  } catch (err) {
28690
29260
  throw new CliError(`${command}: ${err.message}`);
28691
29261
  }
28692
29262
  if (parsed.seq === undefined) {
28693
- const err = new CliError(`${command} needs a ref with a seq (e.g. ${target}#42)`);
29263
+ const err = new CliError(`${command} needs a ref with a seq (e.g. ${target2}#42)`);
28694
29264
  err.hint = "pass --target /<server>/<channel>#N, /<server>/<channel>/#N#M for thread reply, or /.dm/<peer>#N";
28695
29265
  throw err;
28696
29266
  }
@@ -28699,21 +29269,21 @@ function parseMessageTarget(command, target) {
28699
29269
  }
28700
29270
  async function cmdMessageMarkSet(opts) {
28701
29271
  const api2 = getApi();
28702
- const target = opts.target;
28703
- if (!target)
29272
+ const target2 = opts.target;
29273
+ if (!target2)
28704
29274
  throw new CliError("message mark set: --target <ref> is required");
28705
- const request = parseMessageTarget("message mark set", target);
29275
+ const request = parseMessageTarget("message mark set", target2);
28706
29276
  await withTransientMutationRetry(() => api2.markSet(request));
28707
- return { target, marked: true };
29277
+ return { target: target2, marked: true };
28708
29278
  }
28709
29279
  async function cmdMessageMarkRemove(opts) {
28710
29280
  const api2 = getApi();
28711
- const target = opts.target;
28712
- if (!target)
29281
+ const target2 = opts.target;
29282
+ if (!target2)
28713
29283
  throw new CliError("message mark remove: --target <ref> is required");
28714
- const request = parseMessageTarget("message mark remove", target);
29284
+ const request = parseMessageTarget("message mark remove", target2);
28715
29285
  await withTransientMutationRetry(() => api2.markRemove(request));
28716
- return { target, marked: false };
29286
+ return { target: target2, marked: false };
28717
29287
  }
28718
29288
  async function cmdMessageMarkList(opts) {
28719
29289
  const api2 = getApi();
@@ -28723,9 +29293,9 @@ async function cmdMessageMarkList(opts) {
28723
29293
  async function cmdAttachmentUpload(opts) {
28724
29294
  const api2 = getApi();
28725
29295
  const agent2 = agentId(opts);
28726
- const target = opts.target;
29296
+ const target2 = opts.target;
28727
29297
  const filePath = opts.file;
28728
- if (!target)
29298
+ if (!target2)
28729
29299
  throw new CliError("message attachment upload: --target <ref> is required");
28730
29300
  if (!filePath)
28731
29301
  throw new CliError("message attachment upload: --file <path> is required");
@@ -28766,7 +29336,7 @@ async function cmdAttachmentUpload(opts) {
28766
29336
  }
28767
29337
  const result = await api2.attachmentUpload({
28768
29338
  agentId: agent2,
28769
- target,
29339
+ target: target2,
28770
29340
  file: { data: new Uint8Array(bytes), filename, contentType },
28771
29341
  ...thumbnail ? { thumbnail } : {},
28772
29342
  ...width !== undefined ? { width } : {},
@@ -28846,6 +29416,7 @@ async function cmdInboxPull(opts) {
28846
29416
  const pulledAt = nowLocalISO();
28847
29417
  let acked = 0;
28848
29418
  let ackError;
29419
+ let failed;
28849
29420
  if (opts.ack !== false && messages.length > 0) {
28850
29421
  const latest = new Map;
28851
29422
  for (const m of messages) {
@@ -28855,8 +29426,9 @@ async function cmdInboxPull(opts) {
28855
29426
  latest.set(m.channel, { channel: m.channel, seq: seqN });
28856
29427
  }
28857
29428
  try {
28858
- await api2.ack({ agentId: agent2, cursors: [...latest.values()] });
28859
- acked = latest.size;
29429
+ const result = await api2.ack({ agentId: agent2, cursors: [...latest.values()] });
29430
+ acked = result.applied.length;
29431
+ failed = result.failed;
28860
29432
  } catch (err) {
28861
29433
  ackError = err instanceof Error ? err.message : String(err);
28862
29434
  }
@@ -28866,6 +29438,7 @@ async function cmdInboxPull(opts) {
28866
29438
  hasMore,
28867
29439
  acked,
28868
29440
  pulledAt,
29441
+ ...failed ? { failed } : {},
28869
29442
  ...ackError ? { ackError } : {},
28870
29443
  ...markedCount > 0 ? {
28871
29444
  markedReminder: `You have ${markedCount} marked ${markedCount === 1 ? "message" : "messages"}. Resolve ${markedCount === 1 ? "it" : "them"} before going dark unless blocked.`
@@ -28961,38 +29534,28 @@ async function cmdFriendList(opts) {
28961
29534
  async function cmdNap(opts) {
28962
29535
  const api2 = getApi();
28963
29536
  const fileFlag = opts.handoff;
28964
- const textFlag = opts.text;
28965
- let handoff;
28966
- if (fileFlag) {
28967
- const fs13 = await import("fs");
28968
- if (!fs13.existsSync(fileFlag))
28969
- throw new CliError(`nap: handoff file not found: ${fileFlag}`);
28970
- handoff = fs13.readFileSync(fileFlag, "utf8").trim();
28971
- } else if (typeof textFlag === "string") {
28972
- handoff = decodeTextEscapes(textFlag).trim();
28973
- }
28974
- if (!handoff) {
28975
- throw new CliError("nap: a handoff is required — pass --handoff <file> or --text <note>");
29537
+ const handoff = await readLiteralInput({
29538
+ command: "nap",
29539
+ stdinSelected: false,
29540
+ filePath: fileFlag,
29541
+ fileOption: "--handoff <file>"
29542
+ });
29543
+ if (handoff === undefined || handoff.trim().length === 0) {
29544
+ throw new CliError("nap: a handoff is required — pass --handoff <file>");
28976
29545
  }
28977
29546
  return await api2.nap({ handoff });
28978
29547
  }
28979
- function buildProgram() {
29548
+ function buildProgram(stdin) {
28980
29549
  const program = new Command("alook").description("agent CLI").exitOverride().configureOutput({
28981
29550
  writeOut: () => {},
28982
29551
  writeErr: () => {}
28983
29552
  }).option("--agent <id>", "agent identity (or ALOOK_AGENT_ID env)");
28984
29553
  const message2 = program.command("message").description("message operations").exitOverride();
28985
29554
  message2.configureOutput({ writeOut: () => {}, writeErr: () => {} });
28986
- message2.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace#1234/general)").option("--text <text>", "inline message body (short messages)").option("--file <path>", "read message body from a file (long messages)").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = message order)", (v, prev = []) => [...prev, v], []).option("--reply <seq>", 'reply to a message by its seq in --target (e.g. "#37" or 37)').option("--remind-after <duration>", "optionally arm one local follow-up wake after 1m..24h; a newer same-scope message or daemon restart cancels it").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
28987
- const localOpts = this.opts();
28988
- const globalOpts = program.opts();
28989
- const result = await cmdMessageSend({ ...globalOpts, ...localOpts });
28990
- printEnvelope({ success: result });
28991
- });
28992
- message2.command("post").description("create a new forum post in a forum").option("--target <forum-ref>", "the forum to post in (path-style ref, e.g. /demo#1234/ideas)").option("--title <name>", "the post title (its slug becomes the post's address)").option("--text <text>", "inline post body (short)").option("--file <path>", "read post body from a file (long)").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = body order)", (v, prev = []) => [...prev, v], []).exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
29555
+ message2.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace#1234/general)").option("--stdin", "read the literal UTF-8 message body from non-TTY stdin").option("--file <path>", "read the literal UTF-8 message body from a file").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = message order)", (v, prev = []) => [...prev, v], []).option("--reply <seq>", 'reply to a message by its seq in --target (e.g. "#37" or 37)').option("--remind-after <duration>", "optionally arm one local follow-up wake after 1m..24h; a newer same-scope message or daemon restart cancels it").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
28993
29556
  const localOpts = this.opts();
28994
29557
  const globalOpts = program.opts();
28995
- const result = await cmdMessagePost({ ...globalOpts, ...localOpts });
29558
+ const result = await cmdMessageSend({ ...globalOpts, ...localOpts }, stdin);
28996
29559
  printEnvelope({ success: result });
28997
29560
  });
28998
29561
  message2.command("emoji").description("react to a message with a single emoji").requiredOption("--target <ref>", "message ref (path-style, e.g. /demo#1234/general#42 or /.dm/peer#0007#42)").requiredOption("--emoji <string>", "single emoji character").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
@@ -29097,7 +29660,7 @@ function buildProgram() {
29097
29660
  const result = await cmdSettingProfile({ ...program.opts(), ...this.opts() });
29098
29661
  printEnvelope({ success: result });
29099
29662
  });
29100
- program.command("nap").description("end your session and start fresh, carrying a handoff to your reborn self (read the nap rule first)").option("--handoff <file>", "path to your handoff note (your note to your reborn self)").option("--text <note>", "inline handoff note (alternative to --handoff)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
29663
+ program.command("nap").description("end your session and start fresh, carrying a handoff to your reborn self (read the nap rule first)").option("--handoff <file>", "path to your handoff note (your note to your reborn self)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
29101
29664
  const localOpts = this.opts();
29102
29665
  const globalOpts = program.opts();
29103
29666
  const result = await cmdNap({ ...globalOpts, ...localOpts });
@@ -29131,6 +29694,16 @@ function buildProgram() {
29131
29694
  daemon.command("run", { hidden: true }).exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async () => {
29132
29695
  await daemonRunFromIpc();
29133
29696
  });
29697
+ daemon.command("reconnect").description("rotate and reconnect one previously paired machine").requiredOption("--id <machineId>", "machine id shown by daemon list").requiredOption("--machine-key <key>", "cmt_ reconnect token").option("--server-url <url>", "server HTTP URL (defaults to the saved launch record)").option("--ws-url <url>", "server WebSocket URL (defaults to the saved launch record)").option("--base-dir <path>", "data directory for agent workspaces and pidfile").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
29698
+ const localOpts = this.opts();
29699
+ await daemonReconnect({
29700
+ id: localOpts.id,
29701
+ machineKey: localOpts.machineKey,
29702
+ serverUrl: localOpts.serverUrl,
29703
+ wsUrl: localOpts.wsUrl,
29704
+ baseDir: localOpts.baseDir
29705
+ });
29706
+ });
29134
29707
  daemon.command("resume", { hidden: true }).requiredOption("--id <machineId>").requiredOption("--base-dir <path>").requiredOption("--request-id <id>").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
29135
29708
  const localOpts = this.opts();
29136
29709
  await daemonResume({
@@ -29178,8 +29751,9 @@ function buildProgram() {
29178
29751
  });
29179
29752
  return program;
29180
29753
  }
29181
- async function main(argv = process.argv.slice(2)) {
29182
- const program = buildProgram();
29754
+ async function main(argv = process.argv.slice(2), io = {}) {
29755
+ const stdin = io.stdin ?? process.stdin;
29756
+ const program = buildProgram(stdin);
29183
29757
  let internalExitCode = 0;
29184
29758
  try {
29185
29759
  await program.parseAsync(argv, { from: "user" });