@alook/daemon 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
@@ -16742,6 +16742,7 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
16742
16742
 
16743
16743
  // ../shared/src/community-cli-contract.ts
16744
16744
  var DM_SERVER = ".dm";
16745
+ var CONTROL_HEARTBEAT_CAPABILITY = "control-heartbeat-v1";
16745
16746
  function parseRef(ref) {
16746
16747
  if (!ref.startsWith("/"))
16747
16748
  throw new Error(`ref must start with "/": ${ref}`);
@@ -16817,6 +16818,10 @@ function parseSeq(s) {
16817
16818
  return n;
16818
16819
  }
16819
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
+ }),
16820
16825
  exports_external.object({
16821
16826
  type: exports_external.literal("agent:wake"),
16822
16827
  agentId: exports_external.string().min(1),
@@ -17456,6 +17461,7 @@ var CommunityMachineSummarySchema = exports_external.object({
17456
17461
  var HostReadyMessageSchema = exports_external.object({
17457
17462
  type: exports_external.literal("ready"),
17458
17463
  runtimeReport: CommunityMachineRuntimeListSchema,
17464
+ capabilities: exports_external.array(exports_external.string().min(1).max(64)).max(16).optional().default([]),
17459
17465
  runningAgents: exports_external.array(exports_external.string()).default([]),
17460
17466
  hostname: exports_external.string().optional(),
17461
17467
  platform: exports_external.string().optional(),
@@ -17507,6 +17513,14 @@ var AgentWakeAckMessageSchema = exports_external.object({
17507
17513
  status: exports_external.enum(["ok", "error"]),
17508
17514
  error: exports_external.object({ code: exports_external.string().optional(), message: exports_external.string().optional() }).optional()
17509
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
+ });
17510
17524
  var CommunityPairTokenResponseSchema = exports_external.object({
17511
17525
  tokenId: exports_external.string(),
17512
17526
  expiresAt: exports_external.string()
@@ -17515,6 +17529,7 @@ var CommunityDaemonActivateRequestSchema = exports_external.object({
17515
17529
  hostname: exports_external.string(),
17516
17530
  platform: exports_external.string(),
17517
17531
  arch: exports_external.string(),
17532
+ expectedMachineId: exports_external.string().min(1).optional(),
17518
17533
  osRelease: exports_external.string().optional(),
17519
17534
  daemonVersion: exports_external.string().optional(),
17520
17535
  runtimeReport: CommunityMachineRuntimeListSchema.optional()
@@ -17522,7 +17537,12 @@ var CommunityDaemonActivateRequestSchema = exports_external.object({
17522
17537
  var CommunityDaemonActivateResponseSchema = exports_external.object({
17523
17538
  credential: exports_external.string(),
17524
17539
  machineId: exports_external.string(),
17525
- 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"])
17526
17546
  });
17527
17547
  var CommunityDaemonEnrollAgentRequestSchema = exports_external.object({
17528
17548
  agentId: exports_external.string().min(1).max(128)
@@ -18162,26 +18182,6 @@ var channelJoinBaselineGuard = sql`${communityMessage.createdAt} > COALESCE(${co
18162
18182
 
18163
18183
  // ../shared/src/db/queries/community/mention.ts
18164
18184
  var MENTION_INSERT_MAX_ROWS = maxRowsPerInsert(5);
18165
- // ../shared/src/community/bot-activity-presets.ts
18166
- var BOT_ACTIVITY_PRESETS = {
18167
- idle: { emoji: "\uD83D\uDCA4", text: "Idle" },
18168
- starting: { emoji: "\uD83C\uDF00", text: "Waking up" },
18169
- stopping: { emoji: "\uD83C\uDF19", text: "Wrapping up" }
18170
- };
18171
- var RUNNING_PRESETS = [
18172
- { emoji: "⚡", text: "Working on it" },
18173
- { emoji: "\uD83D\uDEE0️", text: "Cooking" },
18174
- { emoji: "\uD83E\uDDE0", text: "Thinking hard" },
18175
- { emoji: "\uD83D\uDD27", text: "Tinkering" },
18176
- { emoji: "\uD83D\uDE80", text: "On it" },
18177
- { emoji: "\uD83D\uDD25", text: "In the zone" }
18178
- ];
18179
- var BOT_ACTIVITY_STATUS_PAIRS = [
18180
- BOT_ACTIVITY_PRESETS.idle,
18181
- BOT_ACTIVITY_PRESETS.starting,
18182
- BOT_ACTIVITY_PRESETS.stopping,
18183
- ...RUNNING_PRESETS
18184
- ];
18185
18185
  // ../shared/src/community-ws-events.ts
18186
18186
  var string4 = exports_external.string();
18187
18187
  var nullableString = string4.nullable();
@@ -18638,6 +18638,68 @@ var WS_EVENTS = {
18638
18638
  BOT_AUDIT_EVENT: "community:bot.audit_event"
18639
18639
  };
18640
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
+ ];
18641
18703
  // ../shared/src/db/index.ts
18642
18704
  var allSchema = { ...exports_schema, ...exports_community_schema, ...exports_community_machine_schema };
18643
18705
  // ../shared/src/db/queries/task.ts
@@ -19649,8 +19711,8 @@ function ensureSymlinks(workDir) {
19649
19711
  try {
19650
19712
  const stat = lstatSync(aliasPath);
19651
19713
  if (stat.isSymbolicLink()) {
19652
- const target = readlinkSync(aliasPath);
19653
- if (target === CANONICAL_FILE)
19714
+ const target2 = readlinkSync(aliasPath);
19715
+ if (target2 === CANONICAL_FILE)
19654
19716
  continue;
19655
19717
  unlinkSync2(aliasPath);
19656
19718
  } else {
@@ -19789,11 +19851,11 @@ function buildClaudeProviderIsolationEnv(ctx) {
19789
19851
  fs4.mkdirSync(configDir, { recursive: true });
19790
19852
  const hostClaude = path4.join(process.env.HOME || ".", ".claude");
19791
19853
  for (const sub of ["skills", "commands"]) {
19792
- const target = path4.join(hostClaude, sub);
19854
+ const target2 = path4.join(hostClaude, sub);
19793
19855
  const link = path4.join(configDir, sub);
19794
19856
  try {
19795
- if (fs4.existsSync(target) && !fs4.existsSync(link))
19796
- fs4.symlinkSync(target, link);
19857
+ if (fs4.existsSync(target2) && !fs4.existsSync(link))
19858
+ fs4.symlinkSync(target2, link);
19797
19859
  } catch {}
19798
19860
  }
19799
19861
  return {
@@ -20027,9 +20089,9 @@ function resolveHomePath(relativePath, deps = {}) {
20027
20089
  }
20028
20090
  function resolveSpawnSpec(command, args, override, deps = {}, platform = process.platform) {
20029
20091
  const trimmed = override?.trim();
20030
- const target = trimmed && trimmed.length > 0 ? trimmed : command;
20092
+ const target2 = trimmed && trimmed.length > 0 ? trimmed : command;
20031
20093
  const looksLikePath = trimmed !== undefined && trimmed.length > 0 && /[\\/]/.test(trimmed);
20032
- const resolved = looksLikePath ? target : resolveCommandOnPath(target, deps) ?? target;
20094
+ const resolved = looksLikePath ? target2 : resolveCommandOnPath(target2, deps) ?? target2;
20033
20095
  return { command: resolved, args, shell: needsWindowsShimShell(resolved, platform) };
20034
20096
  }
20035
20097
  function resolveClaudeCommand(deps = {}) {
@@ -21366,8 +21428,8 @@ function listRuntimeIds() {
21366
21428
  var SELECTABLE_RUNTIMES = new Set(["claude", "codex", "opencode", "pi", "cursor"]);
21367
21429
  function resolveAlookCliPath(moduleDir) {
21368
21430
  const thisDir = moduleDir ?? path8.dirname(fileURLToPath(import.meta.url));
21369
- const target = path8.basename(thisDir) === "dist" ? path8.resolve(thisDir, "cli", "index.js") : path8.resolve(thisDir, "..", "scripts", "alook-shim.mjs");
21370
- 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;
21371
21433
  }
21372
21434
  function deriveCliFallbackCandidates(cliPath) {
21373
21435
  if (!cliPath)
@@ -21771,6 +21833,236 @@ function writeStatusFile(path9, snapshot) {
21771
21833
  } catch {}
21772
21834
  }
21773
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
+
21774
22066
  // src/server/wsControlChannel.ts
21775
22067
  var WS_CONTROL_COMMAND_CONSUMED = Symbol("ws-control-command-consumed");
21776
22068
  var DEFAULT_PING_INTERVAL_MS = 15000;
@@ -21785,6 +22077,7 @@ class WsControlChannel {
21785
22077
  opts;
21786
22078
  statusValue = "idle";
21787
22079
  commandCbs = [];
22080
+ wakeDesiredCbs = [];
21788
22081
  resyncHooks = [];
21789
22082
  ws = null;
21790
22083
  attempt = 0;
@@ -21794,6 +22087,7 @@ class WsControlChannel {
21794
22087
  pongDeadline = 0;
21795
22088
  resyncProvider = null;
21796
22089
  log;
22090
+ wakeCoordinator = new WakeCoordinator;
21797
22091
  constructor(opts) {
21798
22092
  this.opts = opts;
21799
22093
  this.log = opts.logger ?? createLogger2({ header: "@alook/daemon:ws" });
@@ -21816,6 +22110,30 @@ class WsControlChannel {
21816
22110
  onCommand(cb) {
21817
22111
  this.commandCbs.push(cb);
21818
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
+ }
21819
22137
  onResync(provider) {
21820
22138
  this.resyncProvider = provider;
21821
22139
  }
@@ -21832,6 +22150,7 @@ class WsControlChannel {
21832
22150
  this.sendFrame({ type: "agent_session", ...info });
21833
22151
  }
21834
22152
  async reportAgentActivity(info) {
22153
+ this.wakeCoordinator.recordAgentActivity(info.agentId, info.state);
21835
22154
  this.sendFrame({ type: "agent_activity", ...info });
21836
22155
  }
21837
22156
  reportAgentTyping(info) {
@@ -21844,6 +22163,7 @@ class WsControlChannel {
21844
22163
  this.sendFrame(frame);
21845
22164
  }
21846
22165
  async reportWakeAck(info) {
22166
+ this.wakeCoordinator.recordDeliveryAck(info.agentId, info.launchId, info.status);
21847
22167
  this.sendFrame({ type: "agent_wake_ack", ...info });
21848
22168
  }
21849
22169
  async reportStoppedAck(info) {
@@ -21915,27 +22235,75 @@ class WsControlChannel {
21915
22235
  return;
21916
22236
  }
21917
22237
  this.attempt = 0;
21918
- const parsed = HostCommandSchema.safeParse(frame);
21919
- if (!parsed.success) {
21920
- this.log.warn("dropped malformed HostCommand frame", {
21921
- type: frame.type,
21922
- issues: parsed.error.issues.map((i) => ({ path: i.path.join("."), code: i.code }))
21923
- });
21924
- return;
21925
- }
21926
- 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 = [];
21927
22244
  for (const cb of this.commandCbs) {
21928
22245
  try {
21929
22246
  const result = cb(cmd);
21930
22247
  if (result === WS_CONTROL_COMMAND_CONSUMED)
21931
22248
  break;
21932
- Promise.resolve(result).catch((err) => {
22249
+ pending.push(Promise.resolve(result).catch((err) => {
21933
22250
  this.log.warn("command listener threw", { type: cmd.type, err: describeErr(err) });
21934
- });
22251
+ }));
21935
22252
  } catch (err) {
21936
22253
  this.log.warn("command listener threw synchronously", { type: cmd.type, err: describeErr(err) });
21937
22254
  }
21938
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);
21939
22307
  }
21940
22308
  onSocketClosed(code, reason) {
21941
22309
  this.log.warn("control channel closed", { code, reason: reason ? String(reason) : "" });
@@ -21970,7 +22338,10 @@ class WsControlChannel {
21970
22338
  this.pingTimer = setInterval(() => {
21971
22339
  if (this.now() > this.pongDeadline) {
21972
22340
  this.log.warn("heartbeat pong timeout — forcing reconnect");
21973
- this.ws?.close();
22341
+ if (this.ws?.terminate)
22342
+ this.ws.terminate();
22343
+ else
22344
+ this.ws?.close();
21974
22345
  return;
21975
22346
  }
21976
22347
  this.log.debug("heartbeat ping");
@@ -22122,6 +22493,14 @@ function writeJson(res, status, body) {
22122
22493
  res.writeHead(status, { "content-type": "application/json" });
22123
22494
  res.end(JSON.stringify(body));
22124
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
+ }
22125
22504
  function isCanonicalChannelScope(channel2) {
22126
22505
  try {
22127
22506
  const parsed = parseRef(channel2);
@@ -22218,6 +22597,11 @@ async function startCredentialProxy(broker, options = {}) {
22218
22597
  const upstreamClient = upstream.protocol === "https:" ? https : http;
22219
22598
  const onPull = options.onInboxPullResponse;
22220
22599
  const onProxyRequest = options.onProxyRequest;
22600
+ const reportInboxPullObservationError = (failure) => {
22601
+ try {
22602
+ options.onInboxPullObservationError?.(failure);
22603
+ } catch {}
22604
+ };
22221
22605
  const server = http.createServer((req, res) => {
22222
22606
  const pathname = new URL2(req.url ?? "/", "http://placeholder").pathname;
22223
22607
  if (pathname.startsWith("/__alook/local/")) {
@@ -22256,7 +22640,20 @@ async function startCredentialProxy(broker, options = {}) {
22256
22640
  return;
22257
22641
  }
22258
22642
  const reg = verdict.reg;
22259
- 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
+ }
22260
22657
  if (onProxyRequest) {
22261
22658
  try {
22262
22659
  onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
@@ -22269,6 +22666,8 @@ async function startCredentialProxy(broker, options = {}) {
22269
22666
  outHeaders[broker.headerNames.agentId.toLowerCase()] = reg.agentId;
22270
22667
  outHeaders[broker.headerNames.client.toLowerCase()] = broker.clientLabel;
22271
22668
  outHeaders[broker.headerNames.capabilities.toLowerCase()] = [...reg.capabilities].join(",");
22669
+ if (isInboxPull)
22670
+ outHeaders["accept-encoding"] = "identity";
22272
22671
  let responded = false;
22273
22672
  let upstreamRes;
22274
22673
  const upstreamReq = upstreamClient.request({
@@ -22287,18 +22686,54 @@ async function startCredentialProxy(broker, options = {}) {
22287
22686
  };
22288
22687
  res_.on("error", destroyResIfIncomplete);
22289
22688
  res_.on("close", destroyResIfIncomplete);
22290
- if (isInboxPull && res_.statusCode && res_.statusCode < 300) {
22689
+ if (shouldObserveInboxPull && res_.statusCode !== undefined && res_.statusCode >= 200 && res_.statusCode < 300) {
22291
22690
  const chunks = [];
22292
22691
  res_.on("data", (chunk2) => chunks.push(chunk2));
22293
22692
  res_.on("end", () => {
22294
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
+ }
22295
22735
  res.writeHead(res_.statusCode, res_.headers);
22296
22736
  res.end(body);
22297
- try {
22298
- const parsed = JSON.parse(body.toString());
22299
- if (parsed.messages)
22300
- onPull(reg.agentId, parsed.messages);
22301
- } catch {}
22302
22737
  });
22303
22738
  } else {
22304
22739
  res.writeHead(res_.statusCode ?? 502, res_.headers);
@@ -23217,17 +23652,17 @@ function extractToolAudit(rawName, rawInput) {
23217
23652
  return { name, suppressed: false };
23218
23653
  return { name, target: truncateTargetToCodeUnits(firstLine), suppressed: false };
23219
23654
  }
23220
- let target;
23655
+ let target2;
23221
23656
  if (cls === "file_target")
23222
- target = pickFileTarget(rawInput);
23657
+ target2 = pickFileTarget(rawInput);
23223
23658
  else if (cls === "pattern")
23224
- target = pickPatternTarget(rawInput);
23659
+ target2 = pickPatternTarget(rawInput);
23225
23660
  else
23226
- target = pickFallthroughTarget(rawInput);
23227
- if (typeof target !== "string" || target.length === 0) {
23661
+ target2 = pickFallthroughTarget(rawInput);
23662
+ if (typeof target2 !== "string" || target2.length === 0) {
23228
23663
  return { name, suppressed: false };
23229
23664
  }
23230
- return { name, target: truncateTargetToCodeUnits(target), suppressed: false };
23665
+ return { name, target: truncateTargetToCodeUnits(target2), suppressed: false };
23231
23666
  }
23232
23667
  function truncateThinking(text2) {
23233
23668
  const chars = [...text2].length;
@@ -24060,6 +24495,7 @@ class AgentRouter {
24060
24495
  buildReady() {
24061
24496
  return {
24062
24497
  runtimeReport: [...this.runtimes.values()],
24498
+ capabilities: [CONTROL_HEARTBEAT_CAPABILITY],
24063
24499
  runningAgents: [...this.running],
24064
24500
  hostname: this.opts.hostname,
24065
24501
  platform: this.opts.platform,
@@ -24902,6 +25338,8 @@ function createTimelineRecorder(opts) {
24902
25338
  sessionByAgent.set(agentId, sessionId);
24903
25339
  },
24904
25340
  appendEntryForAgent(agentId, messages) {
25341
+ if (messages.length === 0)
25342
+ return;
24905
25343
  const dir = dirFor(agentId);
24906
25344
  if (!prepareTimelineDirectory(dir))
24907
25345
  return;
@@ -25321,7 +25759,16 @@ async function createDaemon(opts) {
25321
25759
  const typingTracker = createTypingScopeTracker();
25322
25760
  const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
25323
25761
  const proxy = await startCredentialProxy(broker, {
25324
- 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
+ },
25325
25772
  onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
25326
25773
  onProxyRequest: (agentId, method, pathname) => {
25327
25774
  const subcommand = deriveAuditLogSubcommand(pathname, method);
@@ -25422,11 +25869,29 @@ async function createDaemon(opts) {
25422
25869
  if (!res.ok)
25423
25870
  throw new Error(`resync-wakes ${res.status}`);
25424
25871
  const json2 = await res.json();
25425
- log2.info("wake resync completed", { woken: json2.woken ?? 0 });
25872
+ log2.info("wake resync completed", { attempted: json2.attempted ?? 0 });
25426
25873
  } catch (err) {
25427
25874
  log2.warn("wake resync failed", { err: err instanceof Error ? err.message : String(err) });
25428
25875
  }
25429
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
+ }
25430
25895
  const enrollAgent = async (agentId) => {
25431
25896
  const existing = enrolledKeys.get(agentId);
25432
25897
  if (existing)
@@ -25652,11 +26117,11 @@ async function createDaemon(opts) {
25652
26117
  handleDiagnosticCommand: opts.handleDiagnosticCommand,
25653
26118
  reportDiagnosticFailure: opts.reportDiagnosticFailure
25654
26119
  }));
26120
+ channel2.onWakeDesiredAdvance((cmd) => {
26121
+ reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
26122
+ });
25655
26123
  channel2.onCommand((cmd) => {
25656
26124
  switch (cmd.type) {
25657
- case "agent:wake":
25658
- reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
25659
- break;
25660
26125
  case "agent:stop":
25661
26126
  reminderSchedulerRef?.clearAgent(cmd.agentId);
25662
26127
  break;
@@ -25673,6 +26138,7 @@ async function createDaemon(opts) {
25673
26138
  channel2.onOpen(() => {
25674
26139
  coldStartWarmup();
25675
26140
  resyncPendingWakes();
26141
+ resyncPendingDiagnostics();
25676
26142
  });
25677
26143
  channel2.connect();
25678
26144
  await router.start();
@@ -27504,6 +27970,7 @@ var STOP_GRACE_MS = 5000;
27504
27970
  var STOP_KILL_GRACE_MS = 2000;
27505
27971
  var POLL_MS2 = 100;
27506
27972
  var START_RECEIPT_TIMEOUT_MS = 15000;
27973
+ var PAIRING_ACTIVATION_TIMEOUT_MS = 30000;
27507
27974
  var RUNNER_TERM_GRACE_MS = 2000;
27508
27975
  var RUNNER_KILL_GRACE_MS = 2000;
27509
27976
  var MACHINE_ID_PATTERN = /^cm_[A-Za-z0-9_-]{8,64}$/;
@@ -28046,20 +28513,58 @@ function findExistingCredentialForBearer(baseDir, bearer) {
28046
28513
  }
28047
28514
  return null;
28048
28515
  }
28049
- async function activatePairingToken(serverUrl, tokenId, hostname4, platform, arch, osRelease, daemonVersion, runtimeReport) {
28050
- const res = await fetch(`${serverUrl}/api/community/daemon/activate`, {
28051
- method: "POST",
28052
- headers: {
28053
- "content-type": "application/json",
28054
- authorization: `Bearer ${tokenId}`
28055
- },
28056
- body: JSON.stringify({ hostname: hostname4, platform, arch, osRelease, daemonVersion, runtimeReport })
28057
- });
28058
- const json2 = await res.json().catch(() => ({}));
28059
- if (!res.ok || !json2.credential || !json2.machineId) {
28060
- 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";
28061
28567
  }
28062
- return { credential: json2.credential, machineId: json2.machineId };
28063
28568
  }
28064
28569
  async function resolveMachineIdentity(serverUrl, credential) {
28065
28570
  const response = await fetch(`${serverUrl}/api/community/daemon/identity`, {
@@ -28087,12 +28592,13 @@ async function prepareDaemonStart(opts) {
28087
28592
  throw new Error("daemon package version is not a strict release version");
28088
28593
  const ownerToken = crypto5.randomBytes(24).toString("base64url");
28089
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;
28090
28596
  let coarseLockPath = null;
28091
28597
  let launchLockPath = null;
28092
28598
  try {
28093
28599
  let machineKey = persisted?.credential;
28094
- let machineId = persisted ? validateMachineId(persisted.machineId) : undefined;
28095
- if (machineKey && machineId) {
28600
+ let machineId = persisted ? validateMachineId(persisted.machineId) : reconnectMachineId;
28601
+ if (machineId) {
28096
28602
  launchLockPath = acquireLaunchLock(baseDir, machineId, ownerToken, opts.resumeRequestId);
28097
28603
  } else {
28098
28604
  coarseLockPath = acquireCoarseLock(baseDir, ownerToken);
@@ -28103,9 +28609,13 @@ async function prepareDaemonStart(opts) {
28103
28609
  const runtimeReport = await detectRuntimes();
28104
28610
  const healthyRuntimeIds = runtimeReport.filter((runtime) => runtime.status === "healthy").map((runtime) => runtime.id);
28105
28611
  if (opts.machineKey.startsWith("cmt_")) {
28106
- 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);
28107
28614
  machineKey = activated.credential;
28108
28615
  machineId = validateMachineId(activated.machineId);
28616
+ if (opts.expectedMachineId && machineId !== validateMachineId(opts.expectedMachineId)) {
28617
+ throw new Error("activate returned a different machine identity");
28618
+ }
28109
28619
  } else {
28110
28620
  machineKey ??= opts.machineKey;
28111
28621
  machineId ??= validateMachineId(await resolveMachineIdentity(serverUrl, opts.machineKey));
@@ -28119,6 +28629,7 @@ async function prepareDaemonStart(opts) {
28119
28629
  wsUrl,
28120
28630
  daemonVersion
28121
28631
  });
28632
+ if (false) {}
28122
28633
  if (coarseLockPath)
28123
28634
  removeOwnedFile(coarseLockPath, process.pid, ownerToken);
28124
28635
  const startedAt = new Date().toISOString();
@@ -28177,6 +28688,66 @@ async function daemonStartById(opts) {
28177
28688
  foreground: opts.foreground
28178
28689
  });
28179
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
+ }
28180
28751
  function runnerArguments() {
28181
28752
  const command = process.env.ALOOK_DAEMON_PACKAGE_WRAPPER === "1" ? ["run"] : ["daemon", "run"];
28182
28753
  return [...process.execArgv, process.argv[1], ...command];
@@ -28633,7 +29204,7 @@ async function cmdMessageSend(opts, stdin) {
28633
29204
  }
28634
29205
  replyToSeq = n;
28635
29206
  }
28636
- const nonce = randomUUID6();
29207
+ const nonce = randomUUID7();
28637
29208
  const res = await sendWithRetry(api2, {
28638
29209
  agentId: agent2,
28639
29210
  channel: channel2,
@@ -28666,30 +29237,30 @@ async function cmdMessageSend(opts, stdin) {
28666
29237
  }
28667
29238
  async function cmdMessageEmoji(opts) {
28668
29239
  const api2 = getApi();
28669
- const target = opts.target;
29240
+ const target2 = opts.target;
28670
29241
  const emoji3 = opts.emoji;
28671
- if (!target)
29242
+ if (!target2)
28672
29243
  throw new CliError("message emoji: --target <ref> is required (e.g. /demo#1234/general#42)");
28673
29244
  if (!emoji3)
28674
29245
  throw new CliError("message emoji: --emoji <string> is required");
28675
- const { channel: channel2, seq } = parseMessageTarget("message emoji", target);
29246
+ const { channel: channel2, seq } = parseMessageTarget("message emoji", target2);
28676
29247
  if (Buffer.byteLength(emoji3, "utf8") > MAX_EMOJI_BYTES) {
28677
29248
  const err = new CliError("emoji is too long");
28678
29249
  err.hint = "use a single emoji, not a phrase";
28679
29250
  throw err;
28680
29251
  }
28681
29252
  const res = await api2.reactAdd({ channel: channel2, seq, emoji: emoji3 });
28682
- return { target, emoji: emoji3, duplicate: res.duplicate === true };
29253
+ return { target: target2, emoji: emoji3, duplicate: res.duplicate === true };
28683
29254
  }
28684
- function parseMessageTarget(command, target) {
29255
+ function parseMessageTarget(command, target2) {
28685
29256
  let parsed;
28686
29257
  try {
28687
- parsed = parseRef(target);
29258
+ parsed = parseRef(target2);
28688
29259
  } catch (err) {
28689
29260
  throw new CliError(`${command}: ${err.message}`);
28690
29261
  }
28691
29262
  if (parsed.seq === undefined) {
28692
- 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)`);
28693
29264
  err.hint = "pass --target /<server>/<channel>#N, /<server>/<channel>/#N#M for thread reply, or /.dm/<peer>#N";
28694
29265
  throw err;
28695
29266
  }
@@ -28698,21 +29269,21 @@ function parseMessageTarget(command, target) {
28698
29269
  }
28699
29270
  async function cmdMessageMarkSet(opts) {
28700
29271
  const api2 = getApi();
28701
- const target = opts.target;
28702
- if (!target)
29272
+ const target2 = opts.target;
29273
+ if (!target2)
28703
29274
  throw new CliError("message mark set: --target <ref> is required");
28704
- const request = parseMessageTarget("message mark set", target);
29275
+ const request = parseMessageTarget("message mark set", target2);
28705
29276
  await withTransientMutationRetry(() => api2.markSet(request));
28706
- return { target, marked: true };
29277
+ return { target: target2, marked: true };
28707
29278
  }
28708
29279
  async function cmdMessageMarkRemove(opts) {
28709
29280
  const api2 = getApi();
28710
- const target = opts.target;
28711
- if (!target)
29281
+ const target2 = opts.target;
29282
+ if (!target2)
28712
29283
  throw new CliError("message mark remove: --target <ref> is required");
28713
- const request = parseMessageTarget("message mark remove", target);
29284
+ const request = parseMessageTarget("message mark remove", target2);
28714
29285
  await withTransientMutationRetry(() => api2.markRemove(request));
28715
- return { target, marked: false };
29286
+ return { target: target2, marked: false };
28716
29287
  }
28717
29288
  async function cmdMessageMarkList(opts) {
28718
29289
  const api2 = getApi();
@@ -28722,9 +29293,9 @@ async function cmdMessageMarkList(opts) {
28722
29293
  async function cmdAttachmentUpload(opts) {
28723
29294
  const api2 = getApi();
28724
29295
  const agent2 = agentId(opts);
28725
- const target = opts.target;
29296
+ const target2 = opts.target;
28726
29297
  const filePath = opts.file;
28727
- if (!target)
29298
+ if (!target2)
28728
29299
  throw new CliError("message attachment upload: --target <ref> is required");
28729
29300
  if (!filePath)
28730
29301
  throw new CliError("message attachment upload: --file <path> is required");
@@ -28765,7 +29336,7 @@ async function cmdAttachmentUpload(opts) {
28765
29336
  }
28766
29337
  const result = await api2.attachmentUpload({
28767
29338
  agentId: agent2,
28768
- target,
29339
+ target: target2,
28769
29340
  file: { data: new Uint8Array(bytes), filename, contentType },
28770
29341
  ...thumbnail ? { thumbnail } : {},
28771
29342
  ...width !== undefined ? { width } : {},
@@ -29123,6 +29694,16 @@ function buildProgram(stdin) {
29123
29694
  daemon.command("run", { hidden: true }).exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async () => {
29124
29695
  await daemonRunFromIpc();
29125
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
+ });
29126
29707
  daemon.command("resume", { hidden: true }).requiredOption("--id <machineId>").requiredOption("--base-dir <path>").requiredOption("--request-id <id>").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
29127
29708
  const localOpts = this.opts();
29128
29709
  await daemonResume({