@alook/daemon 0.1.18 → 0.1.20

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
@@ -17679,7 +17679,7 @@ var AuditLogWakeTriggerPayloadSchema = exports_external.object({
17679
17679
  reason: exports_external.enum(["unread", "mention"])
17680
17680
  });
17681
17681
  var AuditLogSessionResetPayloadSchema = exports_external.object({
17682
- trigger: exports_external.enum(["single", "reset_all"])
17682
+ trigger: exports_external.enum(["single", "reset_all", "idle_timeout"])
17683
17683
  });
17684
17684
  var AuditLogNapPayloadSchema = exports_external.object({
17685
17685
  trigger: exports_external.literal("nap")
@@ -17722,10 +17722,33 @@ var BotAuditEventKindSchema = exports_external.enum([
17722
17722
  ]);
17723
17723
  var HostBotAuditEventFrameSchema = exports_external.object({
17724
17724
  type: exports_external.literal("bot_audit_event"),
17725
+ eventId: exports_external.string().min(1).max(128).optional(),
17726
+ occurredAt: exports_external.string().datetime({ offset: true }).optional(),
17725
17727
  agentId: exports_external.string().min(1),
17726
17728
  sessionId: exports_external.string().nullable().optional(),
17727
17729
  launchId: exports_external.string().nullable().optional(),
17728
17730
  event: BotAuditEventSchema
17731
+ }).superRefine((frame, ctx) => {
17732
+ if (frame.event.kind === "session_reset" && frame.event.payload.trigger === "idle_timeout" && (!frame.eventId || !frame.occurredAt)) {
17733
+ if (!frame.eventId) {
17734
+ ctx.addIssue({
17735
+ code: "custom",
17736
+ path: ["eventId"],
17737
+ message: "eventId is required for idle_timeout session_reset"
17738
+ });
17739
+ }
17740
+ if (!frame.occurredAt) {
17741
+ ctx.addIssue({
17742
+ code: "custom",
17743
+ path: ["occurredAt"],
17744
+ message: "occurredAt is required for idle_timeout session_reset"
17745
+ });
17746
+ }
17747
+ }
17748
+ });
17749
+ var BotAuditEventAckFrameSchema = exports_external.strictObject({
17750
+ type: exports_external.literal("bot_audit_event_ack"),
17751
+ eventId: exports_external.string().min(1).max(128)
17729
17752
  });
17730
17753
  // ../shared/src/db/community-schema.ts
17731
17754
  var exports_community_schema = {};
@@ -17736,6 +17759,7 @@ __export(exports_community_schema, {
17736
17759
  communityServerFolderItem: () => communityServerFolderItem,
17737
17760
  communityServerFolder: () => communityServerFolder,
17738
17761
  communityServer: () => communityServer,
17762
+ communityReadStateRevision: () => communityReadStateRevision,
17739
17763
  communityReadState: () => communityReadState,
17740
17764
  communityReaction: () => communityReaction,
17741
17765
  communityPin: () => communityPin,
@@ -17900,6 +17924,10 @@ var communityReadState = sqliteTable("community_read_state", {
17900
17924
  lastReadMessageId: text("last_read_message_id"),
17901
17925
  lastReadSeq: integer2("last_read_seq").notNull().default(0)
17902
17926
  }, (t) => [index("idx_read_state_user").on(t.userId)]);
17927
+ var communityReadStateRevision = sqliteTable("community_read_state_revision", {
17928
+ userId: text("user_id").primaryKey().references(() => user.id, { onDelete: "cascade" }),
17929
+ revision: integer2("revision").notNull().default(0)
17930
+ });
17903
17931
  var communityReaction = sqliteTable("community_reaction", {
17904
17932
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
17905
17933
  messageId: text("message_id").notNull().references(() => communityMessage.id, { onDelete: "cascade" }),
@@ -18493,6 +18521,24 @@ var communityUnreadBumpSchema = exports_external.strictObject({
18493
18521
  railChannelId: string4.optional(),
18494
18522
  isMention: exports_external.boolean().optional()
18495
18523
  });
18524
+ var readStateEnvelopeFields = {
18525
+ revision: exports_external.number().int().positive(),
18526
+ inboxChanged: exports_external.literal(true)
18527
+ };
18528
+ var communityReadStateAdvancedSchema = exports_external.strictObject({
18529
+ type: exports_external.literal("community:read_state.advanced"),
18530
+ ...readStateEnvelopeFields
18531
+ });
18532
+ var communityInboxChangedSchema = exports_external.strictObject({
18533
+ type: exports_external.literal("community:inbox.changed"),
18534
+ ...readStateEnvelopeFields,
18535
+ reason: exports_external.enum([
18536
+ "read_all",
18537
+ "mention_read_all",
18538
+ "mention_dismiss",
18539
+ "notification_policy"
18540
+ ])
18541
+ });
18496
18542
  var communityPresenceUpdateSchema = exports_external.strictObject({
18497
18543
  type: exports_external.literal("community:presence.update"),
18498
18544
  userId: string4,
@@ -18588,6 +18634,8 @@ var CommunityWsEventDiscriminatedSchema = exports_external.discriminatedUnion("t
18588
18634
  communityInviteCreateSchema,
18589
18635
  communityMentionCreateSchema,
18590
18636
  communityUnreadBumpSchema,
18637
+ communityReadStateAdvancedSchema,
18638
+ communityInboxChangedSchema,
18591
18639
  communityPresenceUpdateSchema,
18592
18640
  communityStatusUpdateSchema,
18593
18641
  communityMachineCreatedSchema,
@@ -18632,6 +18680,8 @@ var WS_EVENTS = {
18632
18680
  INVITE_CREATE: "community:invite.create",
18633
18681
  MENTION_CREATE: "community:mention.create",
18634
18682
  UNREAD_BUMP: "community:unread.bump",
18683
+ READ_STATE_ADVANCED: "community:read_state.advanced",
18684
+ INBOX_CHANGED: "community:inbox.changed",
18635
18685
  PRESENCE_UPDATE: "community:presence.update",
18636
18686
  STATUS_UPDATE: "community:status.update",
18637
18687
  MACHINE_CREATED: "community:machine.created",
@@ -25681,6 +25731,7 @@ var DEFAULT_PING_INTERVAL_MS = 15000;
25681
25731
  var DEFAULT_PONG_TIMEOUT_MS = 30000;
25682
25732
  var DEFAULT_RECONNECT_BASE_MS = 500;
25683
25733
  var DEFAULT_RECONNECT_MAX_MS = 30000;
25734
+ var DEFAULT_AUDIT_ACK_RETRY_MS = 5000;
25684
25735
  function describeErr(err) {
25685
25736
  return err instanceof Error ? err.message : String(err);
25686
25737
  }
@@ -25696,8 +25747,10 @@ class WsControlChannel {
25696
25747
  closedByUser = false;
25697
25748
  authRejected = false;
25698
25749
  pingTimer = null;
25750
+ auditRetryTimer = null;
25699
25751
  pongDeadline = 0;
25700
25752
  resyncProvider = null;
25753
+ pendingBotAuditEvents = new Map;
25701
25754
  log;
25702
25755
  wakeCoordinator = new WakeCoordinator;
25703
25756
  constructor(opts) {
@@ -25715,6 +25768,7 @@ class WsControlChannel {
25715
25768
  close() {
25716
25769
  this.closedByUser = true;
25717
25770
  this.clearHeartbeat();
25771
+ this.clearAuditRetry();
25718
25772
  this.ws?.close();
25719
25773
  this.ws = null;
25720
25774
  this.statusValue = "closed";
@@ -25772,7 +25826,26 @@ class WsControlChannel {
25772
25826
  this.sendFrame({ type: "agent_typing_stop", ...info });
25773
25827
  }
25774
25828
  async reportBotAuditEvent(frame) {
25829
+ if (frame.event.kind === "session_reset" && frame.event.payload.trigger === "idle_timeout") {
25830
+ if (!frame.eventId || !frame.occurredAt) {
25831
+ this.log.error("durable idle-reset audit missing local receipt", {
25832
+ agentId: frame.agentId
25833
+ });
25834
+ return;
25835
+ }
25836
+ this.pendingBotAuditEvents.set(frame.eventId, frame);
25837
+ this.sendFrame(frame);
25838
+ this.scheduleAuditRetry();
25839
+ return;
25840
+ }
25841
+ this.sendFrame(frame);
25842
+ }
25843
+ restorePendingBotAuditEvent(frame) {
25844
+ if (!frame.eventId || !frame.occurredAt)
25845
+ return;
25846
+ this.pendingBotAuditEvents.set(frame.eventId, frame);
25775
25847
  this.sendFrame(frame);
25848
+ this.scheduleAuditRetry();
25776
25849
  }
25777
25850
  async reportWakeAck(info) {
25778
25851
  this.wakeCoordinator.recordDeliveryAck(info.agentId, info.launchId, info.status);
@@ -25800,10 +25873,14 @@ class WsControlChannel {
25800
25873
  const liveActivities = activities ?? [];
25801
25874
  for (const a of liveActivities)
25802
25875
  this.sendFrame({ type: "agent_activity", ...a });
25876
+ for (const frame of this.pendingBotAuditEvents.values())
25877
+ this.sendFrame(frame);
25878
+ this.scheduleAuditRetry();
25803
25879
  this.log.info("resync sent", {
25804
25880
  ready: ready.runtimeReport.length,
25805
25881
  sessions: sessions.length,
25806
- activities: liveActivities.length
25882
+ activities: liveActivities.length,
25883
+ pendingAuditEvents: this.pendingBotAuditEvents.size
25807
25884
  });
25808
25885
  }
25809
25886
  for (const hook of this.resyncHooks) {
@@ -25846,6 +25923,34 @@ class WsControlChannel {
25846
25923
  this.opts.onAuthRejected?.();
25847
25924
  return;
25848
25925
  }
25926
+ const auditAck = BotAuditEventAckFrameSchema.safeParse(frame);
25927
+ if (auditAck.success) {
25928
+ this.attempt = 0;
25929
+ const pending = this.pendingBotAuditEvents.get(auditAck.data.eventId);
25930
+ if (!pending)
25931
+ return;
25932
+ let durableCleared = this.opts.onBotAuditEventAck === undefined;
25933
+ try {
25934
+ durableCleared = this.opts.onBotAuditEventAck?.({
25935
+ agentId: pending.agentId,
25936
+ eventId: auditAck.data.eventId
25937
+ }) ?? true;
25938
+ } catch (err) {
25939
+ this.log.warn("bot audit ack local clear threw", {
25940
+ eventId: auditAck.data.eventId,
25941
+ err: describeErr(err)
25942
+ });
25943
+ }
25944
+ if (durableCleared)
25945
+ this.pendingBotAuditEvents.delete(auditAck.data.eventId);
25946
+ else
25947
+ this.log.warn("bot audit ack local clear deferred", { eventId: auditAck.data.eventId });
25948
+ if (this.pendingBotAuditEvents.size === 0)
25949
+ this.clearAuditRetry();
25950
+ else
25951
+ this.scheduleAuditRetry();
25952
+ return;
25953
+ }
25849
25954
  this.attempt = 0;
25850
25955
  this.ingestCommand(frame).catch((err) => {
25851
25956
  this.log.warn("command ingress failed", { type: frame.type, err: describeErr(err) });
@@ -25920,6 +26025,7 @@ class WsControlChannel {
25920
26025
  onSocketClosed(code, reason) {
25921
26026
  this.log.warn("control channel closed", { code, reason: reason ? String(reason) : "" });
25922
26027
  this.clearHeartbeat();
26028
+ this.clearAuditRetry();
25923
26029
  this.ws = null;
25924
26030
  if (this.closedByUser)
25925
26031
  return;
@@ -25967,6 +26073,26 @@ class WsControlChannel {
25967
26073
  this.pingTimer = null;
25968
26074
  }
25969
26075
  }
26076
+ scheduleAuditRetry() {
26077
+ if (this.auditRetryTimer || this.pendingBotAuditEvents.size === 0)
26078
+ return;
26079
+ const delayMs = this.opts.auditAckRetryMs ?? DEFAULT_AUDIT_ACK_RETRY_MS;
26080
+ this.auditRetryTimer = setTimeout(() => {
26081
+ this.auditRetryTimer = null;
26082
+ if (this.statusValue === "open") {
26083
+ for (const frame of this.pendingBotAuditEvents.values())
26084
+ this.sendFrame(frame);
26085
+ }
26086
+ this.scheduleAuditRetry();
26087
+ }, delayMs);
26088
+ this.auditRetryTimer.unref?.();
26089
+ }
26090
+ clearAuditRetry() {
26091
+ if (!this.auditRetryTimer)
26092
+ return;
26093
+ clearTimeout(this.auditRetryTimer);
26094
+ this.auditRetryTimer = null;
26095
+ }
25970
26096
  now() {
25971
26097
  return this.opts.now ? this.opts.now() : Date.now();
25972
26098
  }
@@ -27670,14 +27796,14 @@ class AgentProcessManager {
27670
27796
  const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
27671
27797
  return effects.length > 0;
27672
27798
  }
27673
- forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
27674
- if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId))
27799
+ forgetSession(agentId, barrierType = "reset_session", forgottenSessionId, pendingIdleResetEvent) {
27800
+ if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId, pendingIdleResetEvent))
27675
27801
  return false;
27676
27802
  this.dispatch({ type: "reset_session", agentId });
27677
27803
  return true;
27678
27804
  }
27679
- forgetSessionSources(agentId, barrierType, forgottenSessionId) {
27680
- const persisted = this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
27805
+ forgetSessionSources(agentId, barrierType, forgottenSessionId, pendingIdleResetEvent) {
27806
+ const persisted = pendingIdleResetEvent ? this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId, pendingIdleResetEvent) : this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
27681
27807
  if (persisted === false)
27682
27808
  return false;
27683
27809
  this.resumeSessions.delete(agentId);
@@ -27781,6 +27907,9 @@ class AgentProcessManager {
27781
27907
  snapshot() {
27782
27908
  return this.state;
27783
27909
  }
27910
+ runningAgentCount() {
27911
+ return this.sessions.size;
27912
+ }
27784
27913
  auditContext(agentId) {
27785
27914
  return {
27786
27915
  sessionId: this.liveSessions.get(agentId) ?? null,
@@ -28253,7 +28382,11 @@ ${this.opts.wakePromptFooter}` : text2;
28253
28382
  break;
28254
28383
  case "reset_idle_session": {
28255
28384
  const spawnState = this.activeSpawnState.get(effect.agentId);
28256
- const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
28385
+ const completion = {
28386
+ eventId: `bae_${randomUUID5()}`,
28387
+ occurredAt: new Date(this.now()).toISOString()
28388
+ };
28389
+ const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId, completion);
28257
28390
  if (!persisted) {
28258
28391
  this.log.error("idle session reset barrier was not persisted; reset deferred", {
28259
28392
  agentId: effect.agentId,
@@ -28265,6 +28398,23 @@ ${this.opts.wakePromptFooter}` : text2;
28265
28398
  if (spawnState)
28266
28399
  spawnState.discardEvents = true;
28267
28400
  this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
28401
+ if (this.opts.onBotAuditEvent) {
28402
+ try {
28403
+ this.opts.onBotAuditEvent(effect.agentId, {
28404
+ kind: "session_reset",
28405
+ payload: { trigger: "idle_timeout" }
28406
+ }, {
28407
+ sessionId: null,
28408
+ launchId: null,
28409
+ ...completion
28410
+ });
28411
+ } catch (err) {
28412
+ this.log.debug("audit emit failed (idle session reset)", {
28413
+ agentId: effect.agentId,
28414
+ err: String(err)
28415
+ });
28416
+ }
28417
+ }
28268
28418
  this.log.info("idle agent session reset", {
28269
28419
  agentId: effect.agentId,
28270
28420
  sessionId: effect.sessionId
@@ -28902,6 +29052,7 @@ Then read @memory.md and your .context_timeline for durable context, and pull `
28902
29052
  class AgentRouter {
28903
29053
  opts;
28904
29054
  running = new Set;
29055
+ nextWakeAdmissionOrdinal = 1;
28905
29056
  runtimes = new Map;
28906
29057
  pendingResend = false;
28907
29058
  scheduleResend;
@@ -29062,7 +29213,7 @@ class AgentRouter {
29062
29213
  this.opts.typingTracker?.add(cmd.agentId, channelScope);
29063
29214
  const text2 = (this.opts.formatUnreadNoticeText ?? defaultFormatUnreadNoticeText)(cmd.unreadNotice);
29064
29215
  const producedEffect = this.opts.manager.deliver(cmd.agentId, {
29065
- id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}`,
29216
+ id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}:admission:${this.nextWakeAdmissionOrdinal++}`,
29066
29217
  seq: cmd.unreadNotice.latestSeq,
29067
29218
  text: text2
29068
29219
  });
@@ -29286,11 +29437,13 @@ var TIMELINE_READ_CHUNK_BYTES = 65536;
29286
29437
  var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
29287
29438
  var RESUME_CONTROL_FILENAME = ".resume-control.json";
29288
29439
  var RESUME_CONTROL_MAX_BYTES = 4096;
29440
+ var MAX_PENDING_IDLE_RESET_EVENTS = 32;
29289
29441
  var EMPTY_RESUME_CONTROL = {
29290
29442
  version: 1,
29291
29443
  attemptedSessionId: null,
29292
29444
  fencedSessionId: null,
29293
- fullBarrier: null
29445
+ fullBarrier: null,
29446
+ pendingIdleResetEvents: []
29294
29447
  };
29295
29448
  function isBarrier(entry) {
29296
29449
  return entry.system !== undefined;
@@ -29680,7 +29833,8 @@ function readResumeControlState(timelineDir) {
29680
29833
  const raw = bounded.subarray(0, bytesRead).toString("utf8");
29681
29834
  const value = JSON.parse(raw);
29682
29835
  const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
29683
- if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
29836
+ const pendingIdleResetEvents = value.pendingIdleResetEvents === undefined ? [] : value.pendingIdleResetEvents;
29837
+ if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || !Array.isArray(pendingIdleResetEvents) || pendingIdleResetEvents.length > MAX_PENDING_IDLE_RESET_EVENTS || pendingIdleResetEvents.some((event) => !event || typeof event !== "object" || typeof event.eventId !== "string" || event.eventId.length < 1 || event.eventId.length > 128 || typeof event.occurredAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(event.occurredAt) || !Number.isFinite(Date.parse(event.occurredAt))) || new Set(pendingIdleResetEvents.map((event) => event.eventId)).size !== pendingIdleResetEvents.length || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
29684
29838
  return { kind: "invalid" };
29685
29839
  return {
29686
29840
  kind: "state",
@@ -29688,7 +29842,8 @@ function readResumeControlState(timelineDir) {
29688
29842
  version: 1,
29689
29843
  attemptedSessionId: value.attemptedSessionId,
29690
29844
  fencedSessionId: value.fencedSessionId,
29691
- fullBarrier: value.fullBarrier
29845
+ fullBarrier: value.fullBarrier,
29846
+ pendingIdleResetEvents
29692
29847
  }
29693
29848
  };
29694
29849
  } catch {
@@ -29711,11 +29866,14 @@ function updateResumeControlState(timelineDir, update) {
29711
29866
  const current = readResumeControlState(timelineDir);
29712
29867
  const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
29713
29868
  const next = update({ ...base });
29869
+ if (!Array.isArray(next.pendingIdleResetEvents) || next.pendingIdleResetEvents.length > MAX_PENDING_IDLE_RESET_EVENTS || next.pendingIdleResetEvents.some((event) => !event || typeof event !== "object" || typeof event.eventId !== "string" || event.eventId.length < 1 || event.eventId.length > 128 || typeof event.occurredAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(event.occurredAt) || !Number.isFinite(Date.parse(event.occurredAt))) || new Set(next.pendingIdleResetEvents.map((event) => event.eventId)).size !== next.pendingIdleResetEvents.length)
29870
+ return false;
29714
29871
  const canonical = {
29715
29872
  version: 1,
29716
29873
  attemptedSessionId: next.attemptedSessionId,
29717
29874
  fencedSessionId: next.fencedSessionId,
29718
- fullBarrier: next.fullBarrier
29875
+ fullBarrier: next.fullBarrier,
29876
+ pendingIdleResetEvents: next.pendingIdleResetEvents
29719
29877
  };
29720
29878
  const body = JSON.stringify(canonical) + `
29721
29879
  `;
@@ -30379,7 +30537,7 @@ function createTimelineRecorder(opts) {
30379
30537
  clearSessionStall(agentId, sessionId) {
30380
30538
  return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
30381
30539
  },
30382
- forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
30540
+ forgetSession(agentId, barrierType = "reset_session", forgottenSessionId, pendingIdleResetEvent) {
30383
30541
  retryPending(agentId);
30384
30542
  const dir = dirFor(agentId);
30385
30543
  if (!prepareTimelineDirectory(dir))
@@ -30391,7 +30549,8 @@ function createTimelineRecorder(opts) {
30391
30549
  ...state,
30392
30550
  attemptedSessionId: null,
30393
30551
  fencedSessionId: null,
30394
- fullBarrier: barrierType
30552
+ fullBarrier: barrierType,
30553
+ pendingIdleResetEvents: pendingIdleResetEvent && !state.pendingIdleResetEvents.some(({ eventId }) => eventId === pendingIdleResetEvent.eventId) ? [...state.pendingIdleResetEvents, pendingIdleResetEvent] : state.pendingIdleResetEvents
30395
30554
  }));
30396
30555
  } else if (barrierType === "stall_recovery") {
30397
30556
  persisted = updateResumeControlState(dir, (state) => ({
@@ -30422,6 +30581,19 @@ function createTimelineRecorder(opts) {
30422
30581
  const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
30423
30582
  handleTrackedResult(agentId, null, result);
30424
30583
  return true;
30584
+ },
30585
+ pendingIdleResetEvents(agentId) {
30586
+ const state = readResumeControlState(dirFor(agentId));
30587
+ return state.kind === "state" ? state.state.pendingIdleResetEvents.map((event) => ({ ...event })) : [];
30588
+ },
30589
+ acknowledgeIdleResetEvent(agentId, eventId) {
30590
+ const dir = dirFor(agentId);
30591
+ if (!prepareTimelineDirectory(dir))
30592
+ return false;
30593
+ return updateResumeControlState(dir, (state) => ({
30594
+ ...state,
30595
+ pendingIdleResetEvents: state.pendingIdleResetEvents.filter((event) => event.eventId !== eventId)
30596
+ }));
30425
30597
  }
30426
30598
  };
30427
30599
  function appendStallMarker(agentId, type, sessionId) {
@@ -30976,6 +31148,8 @@ async function createDaemon(opts) {
30976
31148
  const emitBotAuditEvent = (agentId, event, context) => {
30977
31149
  channelRef?.reportBotAuditEvent?.({
30978
31150
  type: "bot_audit_event",
31151
+ ...context?.eventId ? { eventId: context.eventId } : {},
31152
+ ...context?.occurredAt ? { occurredAt: context.occurredAt } : {},
30979
31153
  agentId,
30980
31154
  sessionId: context?.sessionId ?? null,
30981
31155
  launchId: context?.launchId ?? null,
@@ -31054,6 +31228,7 @@ async function createDaemon(opts) {
31054
31228
  typingTracker.clear(agentId);
31055
31229
  }
31056
31230
  const botsById = new Map;
31231
+ let botCacheReady = false;
31057
31232
  async function listMyBotsHttp() {
31058
31233
  const res = await fetch(`${opts.serverUrl}/api/community/daemon/bots`, {
31059
31234
  method: "GET",
@@ -31064,22 +31239,27 @@ async function createDaemon(opts) {
31064
31239
  const json2 = await res.json();
31065
31240
  return json2.bots ?? [];
31066
31241
  }
31242
+ function replaceBotCache(bots) {
31243
+ botsById.clear();
31244
+ for (const b of bots) {
31245
+ botsById.set(b.id, {
31246
+ name: b.name,
31247
+ discriminator: b.discriminator,
31248
+ description: b.description,
31249
+ ownerName: b.ownerName,
31250
+ ownerDiscriminator: b.ownerDiscriminator
31251
+ });
31252
+ restorePendingIdleResetEvents(b.id);
31253
+ }
31254
+ botCacheReady = true;
31255
+ }
31067
31256
  async function coldStartWarmup() {
31068
31257
  const start = Date.now();
31069
31258
  let attempt = 0;
31070
31259
  while (Date.now() - start < WARMUP_CEILING_MS) {
31071
31260
  try {
31072
31261
  const bots = await listMyBotsHttp();
31073
- botsById.clear();
31074
- for (const b of bots) {
31075
- botsById.set(b.id, {
31076
- name: b.name,
31077
- discriminator: b.discriminator,
31078
- description: b.description,
31079
- ownerName: b.ownerName,
31080
- ownerDiscriminator: b.ownerDiscriminator
31081
- });
31082
- }
31262
+ replaceBotCache(bots);
31083
31263
  log2.info("cold-start bot-cache warmup succeeded", { bots: bots.length, attempt });
31084
31264
  return;
31085
31265
  } catch {
@@ -31164,9 +31344,23 @@ async function createDaemon(opts) {
31164
31344
  headers: { Authorization: `Bearer ${opts.machineKey}` },
31165
31345
  webSocketFactory: opts.webSocketFactory,
31166
31346
  onAuthRejected: opts.onAuthRejected,
31347
+ onBotAuditEventAck: ({ agentId, eventId }) => timeline2.acknowledgeIdleResetEvent(agentId, eventId),
31167
31348
  logger: log2.child("ws")
31168
31349
  });
31169
31350
  channelRef = channel2;
31351
+ function restorePendingIdleResetEvents(agentId) {
31352
+ for (const pending of timeline2.pendingIdleResetEvents(agentId)) {
31353
+ channel2.restorePendingBotAuditEvent({
31354
+ type: "bot_audit_event",
31355
+ eventId: pending.eventId,
31356
+ occurredAt: pending.occurredAt,
31357
+ agentId,
31358
+ sessionId: null,
31359
+ launchId: null,
31360
+ event: { kind: "session_reset", payload: { trigger: "idle_timeout" } }
31361
+ });
31362
+ }
31363
+ }
31170
31364
  function handleBotFrame(cmd) {
31171
31365
  switch (cmd.type) {
31172
31366
  case "bot:added":
@@ -31177,6 +31371,7 @@ async function createDaemon(opts) {
31177
31371
  ownerName: cmd.ownerName,
31178
31372
  ownerDiscriminator: cmd.ownerDiscriminator
31179
31373
  });
31374
+ restorePendingIdleResetEvents(cmd.botId);
31180
31375
  log2.debug("bot:added", { botId: cmd.botId, name: cmd.name });
31181
31376
  break;
31182
31377
  case "bot:updated": {
@@ -31300,7 +31495,14 @@ async function createDaemon(opts) {
31300
31495
  const statusPath = opts.statusFilePath;
31301
31496
  const writeStatus = () => {
31302
31497
  const nowMs = Date.now();
31303
- writeStatusFile(statusPath, { writtenAt: nowMs, agents: manager.statusProjection(nowMs) });
31498
+ writeStatusFile(statusPath, {
31499
+ writtenAt: nowMs,
31500
+ agentSummary: {
31501
+ total: botCacheReady ? botsById.size : null,
31502
+ running: manager.runningAgentCount()
31503
+ },
31504
+ agents: manager.statusProjection(nowMs)
31505
+ });
31304
31506
  };
31305
31507
  writeStatus();
31306
31508
  statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
@@ -31325,15 +31527,7 @@ async function createDaemon(opts) {
31325
31527
  if (!botsById.has(agentId)) {
31326
31528
  try {
31327
31529
  const bots = await listMyBotsHttp();
31328
- for (const b of bots) {
31329
- botsById.set(b.id, {
31330
- name: b.name,
31331
- discriminator: b.discriminator,
31332
- description: b.description,
31333
- ownerName: b.ownerName,
31334
- ownerDiscriminator: b.ownerDiscriminator
31335
- });
31336
- }
31530
+ replaceBotCache(bots);
31337
31531
  } catch {}
31338
31532
  }
31339
31533
  if (!botsById.has(agentId)) {
@@ -33649,8 +33843,13 @@ function daemonList(opts) {
33649
33843
  if (alive && statusPath) {
33650
33844
  const s = daemonStatusFromFile(statusPath, now);
33651
33845
  if (s.found) {
33652
- agents = s.agents.length;
33653
- running = s.agents.filter((a) => a.derivedActivity === "running").length;
33846
+ if (s.agentSummary) {
33847
+ agents = s.agentSummary.total;
33848
+ running = s.agentSummary.running;
33849
+ } else {
33850
+ agents = s.agents.length;
33851
+ running = s.agents.filter((a) => a.derivedActivity === "running").length;
33852
+ }
33654
33853
  lastActiveMs = daemonLastActiveMs(s, now);
33655
33854
  }
33656
33855
  }
@@ -33665,7 +33864,24 @@ function daemonList(opts) {
33665
33864
  return results;
33666
33865
  }
33667
33866
  var STATUS_STALE_MS = 20000;
33668
- var MISSING_STATUS = { found: false, ageMs: null, freshness: "missing", writtenAt: null, agents: [] };
33867
+ var MISSING_STATUS = {
33868
+ found: false,
33869
+ ageMs: null,
33870
+ freshness: "missing",
33871
+ writtenAt: null,
33872
+ agentSummary: null,
33873
+ agents: []
33874
+ };
33875
+ function validAgentSummary(value) {
33876
+ if (!value || typeof value !== "object")
33877
+ return null;
33878
+ const summary = value;
33879
+ const validTotal = summary.total === null || Number.isInteger(summary.total) && summary.total >= 0;
33880
+ const validRunning = Number.isInteger(summary.running) && summary.running >= 0;
33881
+ if (!validTotal || !validRunning)
33882
+ return null;
33883
+ return { total: summary.total, running: summary.running };
33884
+ }
33669
33885
  function daemonStatusFromFile(statusPath, nowMs) {
33670
33886
  if (!fs12.existsSync(statusPath))
33671
33887
  return MISSING_STATUS;
@@ -33677,6 +33893,7 @@ function daemonStatusFromFile(statusPath, nowMs) {
33677
33893
  ageMs,
33678
33894
  freshness: ageMs <= STATUS_STALE_MS ? "fresh" : "stale",
33679
33895
  writtenAt: snap.writtenAt,
33896
+ agentSummary: validAgentSummary(snap.agentSummary),
33680
33897
  agents: Array.isArray(snap.agents) ? snap.agents : []
33681
33898
  };
33682
33899
  } catch {