@alook/daemon 0.1.40 → 0.1.41

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
@@ -22092,7 +22092,7 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
22092
22092
  agentId: exports_external.string().min(1),
22093
22093
  config: exports_external.unknown(),
22094
22094
  launchId: exports_external.string().min(1),
22095
- handoff: exports_external.string().min(1)
22095
+ handoff: exports_external.string().refine((value) => value.trim().length > 0).optional()
22096
22096
  }),
22097
22097
  exports_external.object({
22098
22098
  type: exports_external.literal("agent:model_switch"),
@@ -23130,8 +23130,8 @@ var CommunityAgentJoinServerRequestSchema = exports_external.object({
23130
23130
  });
23131
23131
  var CommunityAgentNapRequestSchema = exports_external.object({
23132
23132
  handoff: exports_external.string().refine((value) => value.trim().length > 0, {
23133
- message: "handoff is required"
23134
- })
23133
+ message: "handoff must not be blank"
23134
+ }).optional()
23135
23135
  });
23136
23136
  var CommunityAgentMessagePropertyRequestSchema = exports_external.object({
23137
23137
  channel: exports_external.string().min(1),
@@ -35749,7 +35749,7 @@ var DEFAULT_TURN_SILENCE_POLICY = {
35749
35749
  normalBudgetMs: 360000
35750
35750
  };
35751
35751
  var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
35752
- var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
35752
+ var DEFAULT_IDLE_RESET_TIMEOUT_MS = 3 * 60 * 60 * 1000;
35753
35753
  var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
35754
35754
  var DEFAULT_STOPPING_STUCK_THRESHOLD_MS = 30000;
35755
35755
  function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS, idleResetTimeoutMs = DEFAULT_IDLE_RESET_TIMEOUT_MS) {
@@ -35861,22 +35861,6 @@ function reduceManager(state, event) {
35861
35861
  a.stalledSessionId = null;
35862
35862
  a.idleSince = null;
35863
35863
  });
35864
- case "idle_reset_committed": {
35865
- const existing = state.agents[event.agentId];
35866
- if (!existing)
35867
- return { state, effects: [] };
35868
- const agent2 = clone2(existing);
35869
- agent2.sessionId = null;
35870
- agent2.stalledSessionId = null;
35871
- agent2.idleSince = null;
35872
- if (agent2.status !== "running")
35873
- return commit(state, agent2, []);
35874
- agent2.status = "stopping";
35875
- agent2.stoppingSince = event.nowMs;
35876
- return commit(state, agent2, [
35877
- { type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
35878
- ]);
35879
- }
35880
35864
  case "begin_reset":
35881
35865
  if (!state.agents[event.agentId])
35882
35866
  return { state, effects: [] };
@@ -36287,7 +36271,8 @@ function onTick(state, nowMs) {
36287
36271
  }
36288
36272
  const idleResetEligible = a.idleSince !== null && a.sessionId !== null && a.inbox.length === 0 && a.pendingAdmissions.length === 0 && !a.resetting && state.idleResetTimeoutMs > 0 && Number.isFinite(state.idleResetTimeoutMs) && (a.status === "idle" && lease.state === "detached" || a.status === "running" && lease.state === "none" && lease.lastTerminal !== null);
36289
36273
  if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
36290
- effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
36274
+ agents[id] = { ...a, idleSince: nowMs };
36275
+ effects.push({ type: "maintain_idle_memory", agentId: id, sessionId: a.sessionId });
36291
36276
  continue;
36292
36277
  }
36293
36278
  const idleEligible = a.status === "running" && lease.state === "none" && a.pendingAdmissions.length === 0 && lease.lastTerminal !== null && a.inbox.length === 0 && state.idleTimeoutMs > 0 && Number.isFinite(state.idleTimeoutMs);
@@ -36400,6 +36385,18 @@ function withAgent(state, agentId, make, effects) {
36400
36385
  const agent2 = make(state.agents[agentId]);
36401
36386
  return { state: { ...state, agents: { ...state.agents, [agentId]: agent2 } }, effects };
36402
36387
  }
36388
+ // src/manager/memoryMaintenancePrompt.ts
36389
+ var MEMORY_MAINTENANCE_PROMPT = `The daemon is preparing to reset this idle session. First, clean up your memory so the next session does not inherit incorrect or outdated notes.
36390
+
36391
+ 1. Consistency: Read memory.md and experiences. Merge duplicates and resolve conflicting notes against original decisions. Check links.
36392
+ 2. Facts: Verify claims, especially work progress, against the latest original records. Search the context timeline and task discussions through their latest outcomes. Correct outdated or unsupported claims; keep uncertainty explicit.
36393
+ 3. Durability: Keep only lasting facts, preferences, and reusable lessons or procedures. Remove task status, milestones, temporary plans, and diaries; extract a reusable lesson only when useful. Keep memory.md brief with links; put procedures, scope, and reasons in experiences. Reason from first principles: distill specific events into underlying causes, constraints, and reusable principles. Omit incidental dates and details; retain context only when it changes the principle’s validity or scope.
36394
+
36395
+ Edit only your own memory files.
36396
+
36397
+ If new work arrives, handle it first and defer nap. Otherwise, after completing the review and any needed edits, your final action is to run the following command without --handoff. Do not finish with only a written reply. This instruction authorizes that nap:
36398
+
36399
+ $ALOOK_CLI nap`;
36403
36400
  // src/runtimeConfig.ts
36404
36401
  function toAgentBackendSelection(config2) {
36405
36402
  return toBuiltinBackendSelection(config2);
@@ -36635,7 +36632,7 @@ function cliCommandsSection() {
36635
36632
  "",
36636
36633
  "### Context Lifecycle",
36637
36634
  "",
36638
- `1. \`${CLI} nap --handoff <file>\` — reset the current session from a required handoff file.`,
36635
+ `1. \`${CLI} nap [--handoff <file>]\` — reset the current session; optionally wake a fresh session with a handoff file.`,
36639
36636
  "",
36640
36637
  "### Output format",
36641
36638
  "",
@@ -36856,7 +36853,7 @@ function workspaceMemorySection(config2) {
36856
36853
  "",
36857
36854
  "### Napping",
36858
36855
  "",
36859
- `\`${CLI} nap\` resets your current session and starts a fresh one with your required handoff ` + "injected into it. Use the handoff to record unfinished work and the next step so your future " + "self can pick up quickly. Never nap on your own; only do it when someone explicitly asks.",
36856
+ `\`${CLI} nap\` resets your current session. With optional --handoff <file>, it starts a fresh one ` + "with your note injected; without it, the next real message starts fresh. Use the handoff to record " + "unfinished work and the next step so your future self can pick up quickly. " + "Never nap on your own; only do it when someone explicitly asks.",
36860
36857
  "",
36861
36858
  "### memory.md",
36862
36859
  "",
@@ -37397,8 +37394,15 @@ class AgentProcessManager {
37397
37394
  this.opts.timeline?.fenceSession(agentId);
37398
37395
  this.abortCurrentTurn(agentId, opts.abortCause);
37399
37396
  this.markResetting(agentId);
37397
+ const previousOwner = this.activeSpawnState.get(agentId);
37398
+ if (opts.forgetSession && previousOwner)
37399
+ previousOwner.discardEvents = true;
37400
37400
  const status = this.state.agents[agentId]?.status;
37401
37401
  if (status === "idle") {
37402
+ if (opts.rewakePrompt === undefined) {
37403
+ this.dispatch({ type: "exit", agentId });
37404
+ return;
37405
+ }
37402
37406
  try {
37403
37407
  this.deliver(agentId, {
37404
37408
  id: `${opts.launchId}:${opts.abortCause}:rewake`,
@@ -37414,11 +37418,16 @@ class AgentProcessManager {
37414
37418
  }
37415
37419
  return;
37416
37420
  }
37417
- this.enqueueRewake(agentId, {
37418
- id: `${opts.launchId}:${opts.abortCause}:rewake`,
37419
- text: opts.rewakePrompt
37420
- });
37421
+ if (opts.rewakePrompt !== undefined) {
37422
+ this.enqueueRewake(agentId, {
37423
+ id: `${opts.launchId}:${opts.abortCause}:rewake`,
37424
+ text: opts.rewakePrompt
37425
+ });
37426
+ }
37427
+ const previousSession = previousOwner?.session ?? this.sessions.get(agentId);
37421
37428
  await this.stop(agentId);
37429
+ if (opts.rewakePrompt === undefined && previousSession)
37430
+ await previousSession.closed;
37422
37431
  }
37423
37432
  async switchModel(agentId, opts) {
37424
37433
  await this.restartAgent(agentId, {
@@ -37960,44 +37969,13 @@ ${this.opts.wakePromptFooter}` : text2;
37960
37969
  mode: effect.mode
37961
37970
  });
37962
37971
  break;
37963
- case "reset_idle_session": {
37964
- const spawnState = this.activeSpawnState.get(effect.agentId);
37965
- const completion = {
37966
- eventId: `bae_${randomUUID6()}`,
37967
- occurredAt: new Date(this.now()).toISOString()
37968
- };
37969
- const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId, completion);
37970
- if (!persisted) {
37971
- this.log.error("idle session reset barrier was not persisted; reset deferred", {
37972
- agentId: effect.agentId,
37973
- sessionId: effect.sessionId
37974
- });
37975
- this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
37972
+ case "maintain_idle_memory": {
37973
+ const agent2 = this.state.agents[effect.agentId];
37974
+ if (!agent2 || agent2.sessionId !== effect.sessionId || agent2.resetting || agent2.idleSince === null)
37976
37975
  break;
37977
- }
37978
- if (spawnState)
37979
- spawnState.discardEvents = true;
37980
- this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
37981
- if (this.opts.onBotAuditEvent) {
37982
- try {
37983
- this.opts.onBotAuditEvent(effect.agentId, {
37984
- kind: "session_reset",
37985
- payload: { trigger: "idle_timeout" }
37986
- }, {
37987
- sessionId: null,
37988
- launchId: null,
37989
- ...completion
37990
- });
37991
- } catch (err) {
37992
- this.log.debug("audit emit failed (idle session reset)", {
37993
- agentId: effect.agentId,
37994
- err: String(err)
37995
- });
37996
- }
37997
- }
37998
- this.log.info("idle agent session reset", {
37999
- agentId: effect.agentId,
38000
- sessionId: effect.sessionId
37976
+ this.deliver(effect.agentId, {
37977
+ id: `${effect.agentId}:memory-maintenance:${randomUUID6()}`,
37978
+ text: MEMORY_MAINTENANCE_PROMPT
38001
37979
  });
38002
37980
  break;
38003
37981
  }
@@ -38653,7 +38631,15 @@ class UnknownRuntimeError extends Error {
38653
38631
  function defaultFormatUnreadNoticeText() {
38654
38632
  return "You have unread messages.";
38655
38633
  }
38656
- var REWAKE_PROMPT = "Your session was reset by your owner. Prior conversation context is gone. " + "Read @memory.md and your .context_timeline for durable context, then pull your inbox " + "before doing anything else. If it reports marked messages, run `$ALOOK_CLI message mark list` " + "and resume that outstanding work.";
38634
+ var REWAKE_PROMPT = `Your session was reset by your owner. Prior conversation context is gone. Read @memory.md and your .context_timeline for durable context, then pull your inbox. If it reports marked messages, run \`$ALOOK_CLI message mark list\` to identify outstanding work. Before continuing work, review memory.md and experiences:
38635
+
38636
+ 1. Consistency: Read memory.md and experiences. Merge duplicates and resolve conflicting notes against original decisions. Check links.
38637
+ 2. Facts: Verify claims, especially work progress, against the latest original records. Search the context timeline and task discussions through their latest outcomes. Correct outdated or unsupported claims; keep uncertainty explicit.
38638
+ 3. Durability: Keep only lasting facts, preferences, and reusable lessons or procedures. Remove task status, milestones, temporary plans, and diaries; extract a reusable lesson only when useful. Keep memory.md brief with links; put procedures, scope, and reasons in experiences. Reason from first principles: distill specific events into underlying causes, constraints, and reusable principles. Omit incidental dates and details; retain context only when it changes the principle’s validity or scope.
38639
+
38640
+ Edit only your own memory files.
38641
+
38642
+ After completing the review and any needed edits, resume outstanding work and handle your inbox messages.`;
38657
38643
  var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. Continue any unfinished work.";
38658
38644
  function buildNapRewakePrompt(handoff2) {
38659
38645
  return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
@@ -38773,13 +38759,17 @@ class AgentRouter {
38773
38759
  } catch {}
38774
38760
  });
38775
38761
  }
38776
- async runRestartCommand(agentId, launchId, opName, run) {
38762
+ async runRestartCommand(agentId, launchId, opName, run, rewake = true) {
38777
38763
  this.log.info(`${opName} received`, { agentId, launchId });
38778
38764
  try {
38779
38765
  await this.opts.onBeforeAgent?.(agentId);
38780
38766
  await run();
38781
- this.running.add(agentId);
38782
- this.scheduleReadyFrameResend();
38767
+ if (rewake) {
38768
+ this.running.add(agentId);
38769
+ this.scheduleReadyFrameResend();
38770
+ } else {
38771
+ await this.opts.channel.reportStoppedAck?.({ agentId, launchId, status: "ok" });
38772
+ }
38783
38773
  this.log.info(`${opName} ok`, { agentId });
38784
38774
  } catch (err) {
38785
38775
  if (err instanceof UnknownRuntimeError) {
@@ -38957,9 +38947,9 @@ class AgentRouter {
38957
38947
  await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:nap", () => this.opts.manager.resetSession(cmd.agentId, {
38958
38948
  runtimeConfig: cmd.config,
38959
38949
  launchId: cmd.launchId,
38960
- rewakePrompt: buildNapRewakePrompt(cmd.handoff),
38950
+ rewakePrompt: cmd.handoff === undefined ? undefined : buildNapRewakePrompt(cmd.handoff),
38961
38951
  barrierType: "nap"
38962
- }));
38952
+ }), cmd.handoff !== undefined);
38963
38953
  break;
38964
38954
  case "agent:model_switch":
38965
38955
  await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:model_switch", () => this.opts.manager.switchModel(cmd.agentId, {
@@ -45285,10 +45275,10 @@ async function cmdNap(opts) {
45285
45275
  filePath: fileFlag,
45286
45276
  fileOption: "--handoff <file>"
45287
45277
  });
45288
- if (handoff2 === undefined || handoff2.trim().length === 0) {
45289
- throw new CliError("nap: a handoff is required — pass --handoff <file>");
45278
+ if (handoff2 !== undefined && handoff2.trim().length === 0) {
45279
+ throw new CliError("nap: handoff file must not be blank");
45290
45280
  }
45291
- return await api2.nap({ handoff: handoff2 });
45281
+ return await api2.nap(handoff2 === undefined ? {} : { handoff: handoff2 });
45292
45282
  }
45293
45283
  function buildProgram(stdin) {
45294
45284
  const program = new Command("alook").description("agent CLI").exitOverride().configureOutput({
@@ -45417,7 +45407,7 @@ function buildProgram(stdin) {
45417
45407
  const result = await cmdSettingProfile({ ...program.opts(), ...this.opts() });
45418
45408
  printEnvelope({ success: result });
45419
45409
  });
45420
- 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() {
45410
+ program.command("nap").description("reset your session; optionally wake a fresh session with a handoff (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() {
45421
45411
  const localOpts = this.opts();
45422
45412
  const globalOpts = program.opts();
45423
45413
  const result = await cmdNap({ ...globalOpts, ...localOpts });
package/dist/index.js CHANGED
@@ -10072,7 +10072,7 @@ var DEFAULT_TURN_SILENCE_POLICY = {
10072
10072
  normalBudgetMs: 360000
10073
10073
  };
10074
10074
  var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
10075
- var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
10075
+ var DEFAULT_IDLE_RESET_TIMEOUT_MS = 3 * 60 * 60 * 1000;
10076
10076
  var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
10077
10077
  var DEFAULT_STOPPING_STUCK_THRESHOLD_MS = 30000;
10078
10078
  function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS, idleResetTimeoutMs = DEFAULT_IDLE_RESET_TIMEOUT_MS) {
@@ -10184,22 +10184,6 @@ function reduceManager(state, event) {
10184
10184
  a.stalledSessionId = null;
10185
10185
  a.idleSince = null;
10186
10186
  });
10187
- case "idle_reset_committed": {
10188
- const existing = state.agents[event.agentId];
10189
- if (!existing)
10190
- return { state, effects: [] };
10191
- const agent = clone(existing);
10192
- agent.sessionId = null;
10193
- agent.stalledSessionId = null;
10194
- agent.idleSince = null;
10195
- if (agent.status !== "running")
10196
- return commit(state, agent, []);
10197
- agent.status = "stopping";
10198
- agent.stoppingSince = event.nowMs;
10199
- return commit(state, agent, [
10200
- { type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
10201
- ]);
10202
- }
10203
10187
  case "begin_reset":
10204
10188
  if (!state.agents[event.agentId])
10205
10189
  return { state, effects: [] };
@@ -10610,7 +10594,8 @@ function onTick(state, nowMs) {
10610
10594
  }
10611
10595
  const idleResetEligible = a.idleSince !== null && a.sessionId !== null && a.inbox.length === 0 && a.pendingAdmissions.length === 0 && !a.resetting && state.idleResetTimeoutMs > 0 && Number.isFinite(state.idleResetTimeoutMs) && (a.status === "idle" && lease.state === "detached" || a.status === "running" && lease.state === "none" && lease.lastTerminal !== null);
10612
10596
  if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
10613
- effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
10597
+ agents[id] = { ...a, idleSince: nowMs };
10598
+ effects.push({ type: "maintain_idle_memory", agentId: id, sessionId: a.sessionId });
10614
10599
  continue;
10615
10600
  }
10616
10601
  const idleEligible = a.status === "running" && lease.state === "none" && a.pendingAdmissions.length === 0 && lease.lastTerminal !== null && a.inbox.length === 0 && state.idleTimeoutMs > 0 && Number.isFinite(state.idleTimeoutMs);
@@ -10723,6 +10708,18 @@ function withAgent(state, agentId, make, effects) {
10723
10708
  const agent = make(state.agents[agentId]);
10724
10709
  return { state: { ...state, agents: { ...state.agents, [agentId]: agent } }, effects };
10725
10710
  }
10711
+ // src/manager/memoryMaintenancePrompt.ts
10712
+ var MEMORY_MAINTENANCE_PROMPT = `The daemon is preparing to reset this idle session. First, clean up your memory so the next session does not inherit incorrect or outdated notes.
10713
+
10714
+ 1. Consistency: Read memory.md and experiences. Merge duplicates and resolve conflicting notes against original decisions. Check links.
10715
+ 2. Facts: Verify claims, especially work progress, against the latest original records. Search the context timeline and task discussions through their latest outcomes. Correct outdated or unsupported claims; keep uncertainty explicit.
10716
+ 3. Durability: Keep only lasting facts, preferences, and reusable lessons or procedures. Remove task status, milestones, temporary plans, and diaries; extract a reusable lesson only when useful. Keep memory.md brief with links; put procedures, scope, and reasons in experiences. Reason from first principles: distill specific events into underlying causes, constraints, and reusable principles. Omit incidental dates and details; retain context only when it changes the principle’s validity or scope.
10717
+
10718
+ Edit only your own memory files.
10719
+
10720
+ If new work arrives, handle it first and defer nap. Otherwise, after completing the review and any needed edits, your final action is to run the following command without --handoff. Do not finish with only a written reply. This instruction authorizes that nap:
10721
+
10722
+ $ALOOK_CLI nap`;
10726
10723
  // src/runtimeConfig.ts
10727
10724
  function toAgentBackendSelection(config) {
10728
10725
  return toBuiltinBackendSelection(config);
@@ -10816,7 +10813,7 @@ function cliCommandsSection() {
10816
10813
  "",
10817
10814
  "### Context Lifecycle",
10818
10815
  "",
10819
- `1. \`${CLI} nap --handoff <file>\` — reset the current session from a required handoff file.`,
10816
+ `1. \`${CLI} nap [--handoff <file>]\` — reset the current session; optionally wake a fresh session with a handoff file.`,
10820
10817
  "",
10821
10818
  "### Output format",
10822
10819
  "",
@@ -11037,7 +11034,7 @@ function workspaceMemorySection(config) {
11037
11034
  "",
11038
11035
  "### Napping",
11039
11036
  "",
11040
- `\`${CLI} nap\` resets your current session and starts a fresh one with your required handoff ` + "injected into it. Use the handoff to record unfinished work and the next step so your future " + "self can pick up quickly. Never nap on your own; only do it when someone explicitly asks.",
11037
+ `\`${CLI} nap\` resets your current session. With optional --handoff <file>, it starts a fresh one ` + "with your note injected; without it, the next real message starts fresh. Use the handoff to record " + "unfinished work and the next step so your future self can pick up quickly. " + "Never nap on your own; only do it when someone explicitly asks.",
11041
11038
  "",
11042
11039
  "### memory.md",
11043
11040
  "",
@@ -11642,8 +11639,15 @@ class AgentProcessManager {
11642
11639
  this.opts.timeline?.fenceSession(agentId);
11643
11640
  this.abortCurrentTurn(agentId, opts.abortCause);
11644
11641
  this.markResetting(agentId);
11642
+ const previousOwner = this.activeSpawnState.get(agentId);
11643
+ if (opts.forgetSession && previousOwner)
11644
+ previousOwner.discardEvents = true;
11645
11645
  const status = this.state.agents[agentId]?.status;
11646
11646
  if (status === "idle") {
11647
+ if (opts.rewakePrompt === undefined) {
11648
+ this.dispatch({ type: "exit", agentId });
11649
+ return;
11650
+ }
11647
11651
  try {
11648
11652
  this.deliver(agentId, {
11649
11653
  id: `${opts.launchId}:${opts.abortCause}:rewake`,
@@ -11659,11 +11663,16 @@ class AgentProcessManager {
11659
11663
  }
11660
11664
  return;
11661
11665
  }
11662
- this.enqueueRewake(agentId, {
11663
- id: `${opts.launchId}:${opts.abortCause}:rewake`,
11664
- text: opts.rewakePrompt
11665
- });
11666
+ if (opts.rewakePrompt !== undefined) {
11667
+ this.enqueueRewake(agentId, {
11668
+ id: `${opts.launchId}:${opts.abortCause}:rewake`,
11669
+ text: opts.rewakePrompt
11670
+ });
11671
+ }
11672
+ const previousSession = previousOwner?.session ?? this.sessions.get(agentId);
11666
11673
  await this.stop(agentId);
11674
+ if (opts.rewakePrompt === undefined && previousSession)
11675
+ await previousSession.closed;
11667
11676
  }
11668
11677
  async switchModel(agentId, opts) {
11669
11678
  await this.restartAgent(agentId, {
@@ -12205,44 +12214,13 @@ ${this.opts.wakePromptFooter}` : text;
12205
12214
  mode: effect.mode
12206
12215
  });
12207
12216
  break;
12208
- case "reset_idle_session": {
12209
- const spawnState = this.activeSpawnState.get(effect.agentId);
12210
- const completion = {
12211
- eventId: `bae_${randomUUID6()}`,
12212
- occurredAt: new Date(this.now()).toISOString()
12213
- };
12214
- const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId, completion);
12215
- if (!persisted) {
12216
- this.log.error("idle session reset barrier was not persisted; reset deferred", {
12217
- agentId: effect.agentId,
12218
- sessionId: effect.sessionId
12219
- });
12220
- this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
12217
+ case "maintain_idle_memory": {
12218
+ const agent = this.state.agents[effect.agentId];
12219
+ if (!agent || agent.sessionId !== effect.sessionId || agent.resetting || agent.idleSince === null)
12221
12220
  break;
12222
- }
12223
- if (spawnState)
12224
- spawnState.discardEvents = true;
12225
- this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
12226
- if (this.opts.onBotAuditEvent) {
12227
- try {
12228
- this.opts.onBotAuditEvent(effect.agentId, {
12229
- kind: "session_reset",
12230
- payload: { trigger: "idle_timeout" }
12231
- }, {
12232
- sessionId: null,
12233
- launchId: null,
12234
- ...completion
12235
- });
12236
- } catch (err) {
12237
- this.log.debug("audit emit failed (idle session reset)", {
12238
- agentId: effect.agentId,
12239
- err: String(err)
12240
- });
12241
- }
12242
- }
12243
- this.log.info("idle agent session reset", {
12244
- agentId: effect.agentId,
12245
- sessionId: effect.sessionId
12221
+ this.deliver(effect.agentId, {
12222
+ id: `${effect.agentId}:memory-maintenance:${randomUUID6()}`,
12223
+ text: MEMORY_MAINTENANCE_PROMPT
12246
12224
  });
12247
12225
  break;
12248
12226
  }
@@ -34846,7 +34824,7 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
34846
34824
  agentId: exports_external.string().min(1),
34847
34825
  config: exports_external.unknown(),
34848
34826
  launchId: exports_external.string().min(1),
34849
- handoff: exports_external.string().min(1)
34827
+ handoff: exports_external.string().refine((value) => value.trim().length > 0).optional()
34850
34828
  }),
34851
34829
  exports_external.object({
34852
34830
  type: exports_external.literal("agent:model_switch"),
@@ -34937,7 +34915,15 @@ class UnknownRuntimeError extends Error {
34937
34915
  function defaultFormatUnreadNoticeText() {
34938
34916
  return "You have unread messages.";
34939
34917
  }
34940
- var REWAKE_PROMPT = "Your session was reset by your owner. Prior conversation context is gone. " + "Read @memory.md and your .context_timeline for durable context, then pull your inbox " + "before doing anything else. If it reports marked messages, run `$ALOOK_CLI message mark list` " + "and resume that outstanding work.";
34918
+ var REWAKE_PROMPT = `Your session was reset by your owner. Prior conversation context is gone. Read @memory.md and your .context_timeline for durable context, then pull your inbox. If it reports marked messages, run \`$ALOOK_CLI message mark list\` to identify outstanding work. Before continuing work, review memory.md and experiences:
34919
+
34920
+ 1. Consistency: Read memory.md and experiences. Merge duplicates and resolve conflicting notes against original decisions. Check links.
34921
+ 2. Facts: Verify claims, especially work progress, against the latest original records. Search the context timeline and task discussions through their latest outcomes. Correct outdated or unsupported claims; keep uncertainty explicit.
34922
+ 3. Durability: Keep only lasting facts, preferences, and reusable lessons or procedures. Remove task status, milestones, temporary plans, and diaries; extract a reusable lesson only when useful. Keep memory.md brief with links; put procedures, scope, and reasons in experiences. Reason from first principles: distill specific events into underlying causes, constraints, and reusable principles. Omit incidental dates and details; retain context only when it changes the principle’s validity or scope.
34923
+
34924
+ Edit only your own memory files.
34925
+
34926
+ After completing the review and any needed edits, resume outstanding work and handle your inbox messages.`;
34941
34927
  var MODEL_SWITCH_REWAKE_PROMPT = "You were just switched to a different model. Continue any unfinished work.";
34942
34928
  function buildNapRewakePrompt(handoff2) {
34943
34929
  return "You took a nap: you reset your own session, so prior conversation context " + `is gone. Before sleeping you left yourself this handoff —
@@ -35057,13 +35043,17 @@ class AgentRouter {
35057
35043
  } catch {}
35058
35044
  });
35059
35045
  }
35060
- async runRestartCommand(agentId, launchId, opName, run) {
35046
+ async runRestartCommand(agentId, launchId, opName, run, rewake = true) {
35061
35047
  this.log.info(`${opName} received`, { agentId, launchId });
35062
35048
  try {
35063
35049
  await this.opts.onBeforeAgent?.(agentId);
35064
35050
  await run();
35065
- this.running.add(agentId);
35066
- this.scheduleReadyFrameResend();
35051
+ if (rewake) {
35052
+ this.running.add(agentId);
35053
+ this.scheduleReadyFrameResend();
35054
+ } else {
35055
+ await this.opts.channel.reportStoppedAck?.({ agentId, launchId, status: "ok" });
35056
+ }
35067
35057
  this.log.info(`${opName} ok`, { agentId });
35068
35058
  } catch (err) {
35069
35059
  if (err instanceof UnknownRuntimeError) {
@@ -35241,9 +35231,9 @@ class AgentRouter {
35241
35231
  await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:nap", () => this.opts.manager.resetSession(cmd.agentId, {
35242
35232
  runtimeConfig: cmd.config,
35243
35233
  launchId: cmd.launchId,
35244
- rewakePrompt: buildNapRewakePrompt(cmd.handoff),
35234
+ rewakePrompt: cmd.handoff === undefined ? undefined : buildNapRewakePrompt(cmd.handoff),
35245
35235
  barrierType: "nap"
35246
- }));
35236
+ }), cmd.handoff !== undefined);
35247
35237
  break;
35248
35238
  case "agent:model_switch":
35249
35239
  await this.runRestartCommand(cmd.agentId, cmd.launchId, "agent:model_switch", () => this.opts.manager.switchModel(cmd.agentId, {
@@ -37078,8 +37068,8 @@ var CommunityAgentJoinServerRequestSchema = exports_external.object({
37078
37068
  });
37079
37069
  var CommunityAgentNapRequestSchema = exports_external.object({
37080
37070
  handoff: exports_external.string().refine((value) => value.trim().length > 0, {
37081
- message: "handoff is required"
37082
- })
37071
+ message: "handoff must not be blank"
37072
+ }).optional()
37083
37073
  });
37084
37074
  var CommunityAgentMessagePropertyRequestSchema = exports_external.object({
37085
37075
  channel: exports_external.string().min(1),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/daemon",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "Alook agent daemon — host-side runtime backend, process manager, credential proxy, and control plane.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",