@cydm/happy-elves 0.1.0-beta.73 → 0.1.0-beta.75

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.
Files changed (109) hide show
  1. package/apps/cli/dist/commands/lib/bootstrap-daemon.d.ts +22 -1
  2. package/apps/cli/dist/commands/lib/bootstrap-daemon.js +73 -1
  3. package/apps/cli/dist/commands/lib/bootstrap-device-recovery.d.ts +5 -2
  4. package/apps/cli/dist/commands/lib/bootstrap-device-recovery.js +42 -51
  5. package/apps/cli/dist/commands/lib/bootstrap.js +14 -13
  6. package/apps/daemon/dist/gateway/runtime.js +1 -0
  7. package/apps/daemon/dist/lifecycle/detached-helper-bundle.mjs +77 -4
  8. package/apps/daemon/dist/loop/runner.js +1 -0
  9. package/apps/daemon/dist/memory/commands.js +44 -42
  10. package/apps/daemon/dist/memory/flush.js +1 -0
  11. package/apps/daemon/dist/memory/inject.d.ts +1 -0
  12. package/apps/daemon/dist/memory/inject.js +6 -4
  13. package/apps/daemon/dist/memory/managers.d.ts +5 -3
  14. package/apps/daemon/dist/memory/managers.js +110 -34
  15. package/apps/daemon/dist/memory/project-commands.d.ts +3 -0
  16. package/apps/daemon/dist/memory/project-commands.js +394 -0
  17. package/apps/daemon/dist/relay/connection.js +10 -1
  18. package/apps/daemon/dist/relay/register.js +2 -0
  19. package/apps/daemon/dist/relay/send.js +1 -0
  20. package/apps/daemon/dist/runtime/external-provider.js +47 -3
  21. package/apps/daemon/dist/session/lifecycle.d.ts +4 -1
  22. package/apps/daemon/dist/session/lifecycle.js +156 -20
  23. package/apps/daemon/dist/session/metadata.d.ts +2 -1
  24. package/apps/daemon/dist/session/metadata.js +10 -1
  25. package/apps/daemon/dist/session/prompt.js +59 -7
  26. package/apps/daemon/dist/turn-coordinator.d.ts +1 -1
  27. package/apps/daemon/dist/types.d.ts +2 -0
  28. package/apps/daemon/package.json +1 -1
  29. package/apps/relay/dist/controller-handlers.js +223 -4
  30. package/apps/relay/dist/db.js +69 -1
  31. package/apps/relay/dist/http-routes.js +1 -1
  32. package/apps/relay/dist/http-schemas.d.ts +1 -0
  33. package/apps/relay/dist/http-schemas.js +1 -1
  34. package/apps/relay/dist/machine-command-result-handlers.js +111 -2
  35. package/apps/relay/dist/machine-handler-context.d.ts +7 -0
  36. package/apps/relay/dist/projections.js +7 -0
  37. package/apps/relay/dist/relay-context.js +12 -2
  38. package/apps/relay/dist/scope.js +5 -0
  39. package/apps/relay/dist/types.d.ts +3 -1
  40. package/apps/relay/dist/websocket.js +3 -0
  41. package/build-identity.json +2 -2
  42. package/npm-shrinkwrap.json +5 -5
  43. package/package.json +1 -1
  44. package/packages/client/dist/client.d.ts +20 -2
  45. package/packages/client/dist/client.js +298 -3
  46. package/packages/client/dist/index.d.ts +2 -2
  47. package/packages/client/dist/live-types.d.ts +1 -1
  48. package/packages/client/dist/transport.d.ts +5 -0
  49. package/packages/client/dist/transport.js +19 -1
  50. package/packages/client/dist/types.d.ts +22 -0
  51. package/packages/daemon-service/dist/index.js +14 -2
  52. package/packages/memory/dist/index.d.ts +2 -1
  53. package/packages/memory/dist/index.js +2 -1
  54. package/packages/memory/dist/manager.d.ts +18 -0
  55. package/packages/memory/dist/manager.js +108 -8
  56. package/packages/memory/dist/projects.d.ts +13 -0
  57. package/packages/memory/dist/projects.js +211 -0
  58. package/packages/runtime/dist/index.d.ts +19 -0
  59. package/packages/runtime-acpx/dist/index.js +6 -0
  60. package/packages/runtime-cli/dist/claude-turn.d.ts +1 -0
  61. package/packages/runtime-cli/dist/claude-turn.js +1 -0
  62. package/packages/runtime-cli/dist/codex-app-server.d.ts +6 -0
  63. package/packages/runtime-cli/dist/codex-app-server.js +129 -3
  64. package/packages/runtime-cli/dist/codex-json-rpc.js +32 -8
  65. package/packages/runtime-cli/dist/codex-protocol.d.ts +1 -1
  66. package/packages/runtime-cli/dist/codex-protocol.js +4 -1
  67. package/packages/runtime-cli/dist/index.js +10 -0
  68. package/packages/runtime-cli/dist/session-store.d.ts +1 -0
  69. package/packages/runtime-cli/dist/session-store.js +32 -0
  70. package/packages/shared/dist/crypto.d.ts +28 -1
  71. package/packages/shared/dist/crypto.js +89 -1
  72. package/packages/shared/dist/protocol-schemas.d.ts +7 -0
  73. package/packages/shared/dist/protocol-schemas.js +5 -0
  74. package/packages/shared/dist/protocol-types.d.ts +78 -1
  75. package/packages/shared/dist/protocol.d.ts +54 -2
  76. package/packages/shared/dist/protocol.js +61 -1
  77. package/packages/shared/dist/session-state.d.ts +1 -1
  78. package/apps/cli/dist/commands/interaction.d.ts +0 -2
  79. package/apps/cli/dist/commands/interaction.js +0 -416
  80. package/apps/cli/dist/commands/lib/loop-store.d.ts +0 -9
  81. package/apps/cli/dist/commands/lib/loop-store.js +0 -88
  82. package/apps/daemon/dist/agent-interaction-ipc.d.ts +0 -45
  83. package/apps/daemon/dist/agent-interaction-ipc.js +0 -578
  84. package/apps/daemon/dist/agent-interaction-mcp.d.ts +0 -6
  85. package/apps/daemon/dist/agent-interaction-mcp.js +0 -259
  86. package/apps/daemon/dist/interaction-claim-history.d.ts +0 -4
  87. package/apps/daemon/dist/interaction-claim-history.js +0 -51
  88. package/apps/daemon/dist/interaction-claim-journal.d.ts +0 -20
  89. package/apps/daemon/dist/interaction-claim-journal.js +0 -139
  90. package/apps/daemon/dist/interaction-local-state.d.ts +0 -3
  91. package/apps/daemon/dist/interaction-local-state.js +0 -12
  92. package/apps/daemon/dist/interaction-privacy.d.ts +0 -10
  93. package/apps/daemon/dist/interaction-privacy.js +0 -23
  94. package/apps/daemon/dist/interaction-prompt-redaction-cache.d.ts +0 -7
  95. package/apps/daemon/dist/interaction-prompt-redaction-cache.js +0 -254
  96. package/apps/daemon/dist/interaction-runtime.d.ts +0 -58
  97. package/apps/daemon/dist/interaction-runtime.js +0 -674
  98. package/apps/daemon/dist/lifecycle/index.d.ts +0 -7
  99. package/apps/daemon/dist/lifecycle/index.js +0 -6
  100. package/apps/relay/dist/session-record-handlers.d.ts +0 -15
  101. package/apps/relay/dist/session-record-handlers.js +0 -148
  102. package/apps/relay/dist/session-record-repository.d.ts +0 -38
  103. package/apps/relay/dist/session-record-repository.js +0 -312
  104. package/packages/runtime-cli/dist/session-environment.d.ts +0 -7
  105. package/packages/runtime-cli/dist/session-environment.js +0 -24
  106. package/packages/shared/dist/interaction.d.ts +0 -95
  107. package/packages/shared/dist/interaction.js +0 -169
  108. package/packages/shared/dist/session-record.d.ts +0 -2
  109. package/packages/shared/dist/session-record.js +0 -4
@@ -1,6 +1,6 @@
1
1
  import { type ControllerClient, type ControllerSnapshot } from "../../../../../packages/client/dist/index.js";
2
2
  import type { MachineSnapshot } from "../../../../../packages/shared/dist/index.js";
3
- import { localDaemonStatus, readLocalDaemonPidIdentity, type SpawnedDaemonHandle } from "./local-daemon.js";
3
+ import { inspectLocalDaemonRuntime, localDaemonStatus, readLocalDaemonPidIdentity, stopLocalDaemon, stopSpawnedDaemon, type SpawnedDaemonHandle } from "./local-daemon.js";
4
4
  import type { DaemonConfig } from "./types.js";
5
5
  export declare function startFailureMayUseCommittedConfig(error: unknown): boolean;
6
6
  export declare function ensureDaemonRunning(expectedConfig: DaemonConfig, cwd?: string): Promise<{
@@ -12,6 +12,27 @@ export declare function ensureDaemonRunning(expectedConfig: DaemonConfig, cwd?:
12
12
  committedConfigMayBeInUse?: boolean;
13
13
  autostartNeedsAttention?: string;
14
14
  }>;
15
+ type BootstrapDaemonRestartOptions = {
16
+ readStatus?: typeof localDaemonStatus;
17
+ inspectRuntime?: typeof inspectLocalDaemonRuntime;
18
+ readIdentity?: typeof readLocalDaemonPidIdentity;
19
+ stopLocal?: typeof stopLocalDaemon;
20
+ stopStarted?: typeof stopSpawnedDaemon;
21
+ ensureRunning?: typeof ensureDaemonRunning;
22
+ assertConfigFence?: (expectedConfig: DaemonConfig) => Promise<void>;
23
+ };
24
+ /**
25
+ * `happy-elves start` is an explicit foreground recovery/entry command. When
26
+ * it finds a verified local Daemon, restart that exact owner so the process
27
+ * always loads the currently installed package before the browser opens.
28
+ *
29
+ * The lower-level `ensureDaemonRunning()` deliberately remains idempotent for
30
+ * `happy-elves daemon start`, enrollment rollback, and service recovery.
31
+ */
32
+ export declare function restartDaemonForBootstrap(expectedConfig: DaemonConfig, cwd?: string, options?: BootstrapDaemonRestartOptions): Promise<Awaited<ReturnType<typeof ensureDaemonRunning>> & {
33
+ restarted: boolean;
34
+ previousInstanceGeneration?: string;
35
+ }>;
15
36
  export type DaemonReadinessResult = {
16
37
  phase: "relay-authenticated" | "process-exited" | "owner-changed" | "relay-pending";
17
38
  local: Awaited<ReturnType<typeof localDaemonStatus>>;
@@ -3,7 +3,7 @@ import { daemonRuntimeConfigFingerprint } from "../../../../../packages/shared/d
3
3
  import { CliError } from "../../errors.js";
4
4
  import { startDaemonTimeoutMs } from "./paths.js";
5
5
  import { readDaemonConfig } from "./config.js";
6
- import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, inspectLocalDaemonRuntime, ensureRunningDaemonAutostart, localDaemonStatus, readLocalDaemonPidIdentity, spawnDaemonStartTracked, stopSpawnedDaemon, } from "./local-daemon.js";
6
+ import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, inspectLocalDaemonRuntime, ensureRunningDaemonAutostart, localDaemonStatus, readLocalDaemonPidIdentity, spawnDaemonStartTracked, stopLocalDaemon, stopSpawnedDaemon, } from "./local-daemon.js";
7
7
  const committedConfigMayBeInUseErrors = new WeakSet();
8
8
  export function startFailureMayUseCommittedConfig(error) {
9
9
  return Boolean(error && typeof error === "object" && committedConfigMayBeInUseErrors.has(error));
@@ -183,6 +183,78 @@ export async function ensureDaemonRunning(expectedConfig, cwd = process.cwd()) {
183
183
  throw error;
184
184
  }
185
185
  }
186
+ /**
187
+ * `happy-elves start` is an explicit foreground recovery/entry command. When
188
+ * it finds a verified local Daemon, restart that exact owner so the process
189
+ * always loads the currently installed package before the browser opens.
190
+ *
191
+ * The lower-level `ensureDaemonRunning()` deliberately remains idempotent for
192
+ * `happy-elves daemon start`, enrollment rollback, and service recovery.
193
+ */
194
+ export async function restartDaemonForBootstrap(expectedConfig, cwd = process.cwd(), options = {}) {
195
+ const readStatus = options.readStatus ?? localDaemonStatus;
196
+ const inspectRuntime = options.inspectRuntime ?? inspectLocalDaemonRuntime;
197
+ const readIdentity = options.readIdentity ?? readLocalDaemonPidIdentity;
198
+ const stopLocal = options.stopLocal ?? stopLocalDaemon;
199
+ const stopStarted = options.stopStarted ?? stopSpawnedDaemon;
200
+ const ensureRunning = options.ensureRunning ?? ensureDaemonRunning;
201
+ const assertConfigFence = options.assertConfigFence ?? assertDaemonConfigFence;
202
+ await assertConfigFence(expectedConfig);
203
+ const current = await readStatus();
204
+ assertLocalDaemonVerified(current);
205
+ if (!current.running) {
206
+ return {
207
+ ...await ensureRunning(expectedConfig, cwd),
208
+ restarted: false,
209
+ };
210
+ }
211
+ const owner = await readIdentity();
212
+ if (!owner.record || owner.record.pid !== current.pid) {
213
+ throw new CliError("The verified Daemon owner identity changed before restart; no process was signalled.", "DAEMON_PID_INVALID");
214
+ }
215
+ const runtime = await inspectRuntime(expectedConfig);
216
+ if (runtime.state !== "match" && runtime.state !== "missing") {
217
+ const code = runtime.state === "invalid"
218
+ ? "DAEMON_RUNTIME_IDENTITY_INVALID"
219
+ : runtime.state === "owner-mismatch"
220
+ ? "DAEMON_RUNTIME_IDENTITY_MISMATCH"
221
+ : "DAEMON_RUNTIME_CONFIG_MISMATCH";
222
+ throw new CliError("The running Daemon identity could not be matched to daemon.json, so happy-elves start did not restart or signal it.", code);
223
+ }
224
+ if (runtime.state === "match" &&
225
+ (runtime.identity.pid !== owner.record.pid ||
226
+ runtime.identity.instanceNonce !== owner.record.instanceNonce ||
227
+ runtime.identity.startedAt !== owner.record.startedAt)) {
228
+ throw new CliError("The Daemon runtime owner changed while restart was being verified; no process was signalled.", "DAEMON_RUNTIME_IDENTITY_MISMATCH");
229
+ }
230
+ const previousInstanceGeneration = owner.record.instanceNonce;
231
+ await assertConfigFence(expectedConfig);
232
+ const stopped = await stopLocal(startDaemonTimeoutMs, {
233
+ pid: owner.record.pid,
234
+ instanceNonce: owner.record.instanceNonce,
235
+ startedAt: owner.record.startedAt,
236
+ });
237
+ if (stopped.running || stopped.alive) {
238
+ throw new CliError("Daemon did not stop before the restart deadline; no replacement process was started.", "DAEMON_STOP_FAILED");
239
+ }
240
+ const started = await ensureRunning(expectedConfig, cwd);
241
+ if (started.instanceGeneration === previousInstanceGeneration) {
242
+ if (started.startedHandle) {
243
+ try {
244
+ await stopStarted(started.startedHandle);
245
+ }
246
+ catch (error) {
247
+ throw new CliError(`Daemon restart returned a stale generation and its replacement cleanup failed: ${error instanceof Error ? error.message : String(error)}`, "DAEMON_START_CLEANUP_FAILED");
248
+ }
249
+ }
250
+ throw new CliError("Daemon restart did not publish a fresh process generation.", "DAEMON_RESTART_GENERATION_MISMATCH");
251
+ }
252
+ return {
253
+ ...started,
254
+ restarted: true,
255
+ previousInstanceGeneration,
256
+ };
257
+ }
186
258
  function committedConfigInUseError(message) {
187
259
  return markCommittedConfigMayBeInUse(new CliError(message, "DAEMON_START_CONFLICT"));
188
260
  }
@@ -2,7 +2,7 @@ import { ControllerClient } from "../../../../../packages/client/dist/index.js";
2
2
  import { type DeviceRecoveryIntentV1 } from "../../../../../packages/shared/dist/index.js";
3
3
  import { stopSpawnedDaemon } from "./local-daemon.js";
4
4
  import { type StartConfigPreflight } from "./bootstrap-config.js";
5
- import { ensureDaemonRunning } from "./bootstrap-daemon.js";
5
+ import { type ensureDaemonRunning } from "./bootstrap-daemon.js";
6
6
  export declare function startDeviceRecoveryBootstrap(params: {
7
7
  flags: Record<string, string | boolean>;
8
8
  preflight: StartConfigPreflight;
@@ -12,12 +12,15 @@ export declare function startDeviceRecoveryBootstrap(params: {
12
12
  }): Promise<void>;
13
13
  export declare function deviceRecoveryIntentNeedsReplacement(intent: Pick<DeviceRecoveryIntentV1, "status">): boolean;
14
14
  export declare function isTerminalRecoveryIntentCreationFailure(error: unknown): boolean;
15
- export declare function projectRecoveredDaemonReadinessFailure(started: Awaited<ReturnType<typeof ensureDaemonRunning>>, machineName: string, readinessError: unknown, options?: {
15
+ export declare function projectRecoveredDaemonReadinessFailure(started: Awaited<ReturnType<typeof ensureDaemonRunning>> & {
16
+ restarted?: boolean;
17
+ }, machineName: string, readinessError: unknown, options?: {
16
18
  stopStarted?: typeof stopSpawnedDaemon;
17
19
  }): Promise<{
18
20
  online: false;
19
21
  pid?: number;
20
22
  started: boolean;
23
+ restarted: boolean;
21
24
  name: string;
22
25
  needsAttention: string;
23
26
  }>;
@@ -7,10 +7,10 @@ import { accountFingerprintV1, deviceRecoveryTtlMsV1, randomId, } from "../../..
7
7
  import { daemonRuntimeConfigFingerprint } from "../../../../../packages/shared/dist/node/daemon-process.js";
8
8
  import { CliError } from "../../errors.js";
9
9
  import { ok } from "./json.js";
10
- import { assertLocalDaemonVerified, ensureRunningDaemonAutostart, inspectLocalDaemonRuntime, localDaemonStatus, stopSpawnedDaemon, } from "./local-daemon.js";
10
+ import { assertLocalDaemonVerified, localDaemonStatus, stopSpawnedDaemon, } from "./local-daemon.js";
11
11
  import { withAccountConfigTransaction } from "./config.js";
12
12
  import { controllerBaseUrl } from "./bootstrap-config.js";
13
- import { ensureDaemonRunning, readDaemonProjectionBaseline, waitForDaemonReadiness, } from "./bootstrap-daemon.js";
13
+ import { readDaemonProjectionBaseline, restartDaemonForBootstrap, waitForDaemonReadiness, } from "./bootstrap-daemon.js";
14
14
  import { openUrl } from "./bootstrap-output.js";
15
15
  import { assertNoPendingDaemonEnrollmentForDeviceRecovery } from "./daemon-enrollment-intent.js";
16
16
  import { configDir, configPath, deviceRecoveryPendingPath, startDaemonTimeoutMs } from "./paths.js";
@@ -318,34 +318,10 @@ async function ensureRecoveredDaemonReady(params) {
318
318
  ...projection,
319
319
  ...(current.pid ? { pid: current.pid } : {}),
320
320
  started: false,
321
+ restarted: false,
321
322
  needsAttention: error instanceof Error ? error.message : "The local Daemon owner needs verification.",
322
323
  };
323
324
  }
324
- if (current.running) {
325
- const runtime = await inspectLocalDaemonRuntime(params.daemonConfig);
326
- if (runtime.state !== "match") {
327
- const projection = await readProjection();
328
- const needsAttention = runtime.state === "missing"
329
- ? "Automatic startup metadata will be repaired on the next controlled Daemon restart."
330
- : "The running Daemon identity needs repair before Happy Elves can manage its process.";
331
- return {
332
- ...projection,
333
- ...(current.pid ? { pid: current.pid } : {}),
334
- started: false,
335
- needsAttention,
336
- };
337
- }
338
- const autostart = await ensureRunningDaemonAutostart();
339
- const projection = await readProjection();
340
- return {
341
- ...projection,
342
- ...(current.pid ? { pid: current.pid } : {}),
343
- started: false,
344
- ...(autostart.state === "needs-attention"
345
- ? { needsAttention: autostart.message }
346
- : {}),
347
- };
348
- }
349
325
  let baseline;
350
326
  try {
351
327
  baseline = await readDaemonProjectionBaseline(client, params.daemonConfig.machineId, {
@@ -357,37 +333,51 @@ async function ensureRecoveredDaemonReady(params) {
357
333
  }
358
334
  let started;
359
335
  try {
360
- started = await ensureDaemonRunning(params.daemonConfig, params.cwd);
361
- }
362
- catch (error) {
363
- const projection = await readProjection();
364
- return {
365
- ...projection,
366
- ...(current.pid ? { pid: current.pid } : {}),
367
- started: false,
368
- needsAttention: error instanceof Error ? error.message : "The Daemon could not be started automatically.",
369
- };
370
- }
371
- try {
372
- const readiness = await waitForDaemonReadiness(client, params.daemonConfig.machineId, {
373
- baseline,
374
- expectedInstanceGeneration: started.instanceGeneration,
375
- requireFreshProjection: started.started,
376
- timeoutMs: startDaemonTimeoutMs,
336
+ const ready = await withAccountConfigTransaction(async (transaction) => {
337
+ const currentDaemonConfig = await transaction.readDaemon();
338
+ if (!currentDaemonConfig ||
339
+ daemonRuntimeConfigFingerprint(currentDaemonConfig) !== daemonRuntimeConfigFingerprint(params.daemonConfig)) {
340
+ throw new CliError("The local Daemon identity changed before restart. No process was signalled.", "CONFIG_CHANGED_DURING_START");
341
+ }
342
+ started = await restartDaemonForBootstrap(params.daemonConfig, params.cwd);
343
+ const readiness = await waitForDaemonReadiness(client, params.daemonConfig.machineId, {
344
+ baseline,
345
+ expectedInstanceGeneration: started.instanceGeneration,
346
+ requireFreshProjection: started.started || started.restarted,
347
+ timeoutMs: startDaemonTimeoutMs,
348
+ });
349
+ if (readiness.phase !== "relay-authenticated" || !readiness.machine?.online) {
350
+ throw new CliError("The Daemon did not authenticate before the startup deadline.", "DAEMON_ONLINE_TIMEOUT");
351
+ }
352
+ const confirmedDaemonConfig = await transaction.readDaemon();
353
+ if (!confirmedDaemonConfig ||
354
+ daemonRuntimeConfigFingerprint(confirmedDaemonConfig) !== daemonRuntimeConfigFingerprint(params.daemonConfig)) {
355
+ throw new CliError("The local Daemon identity changed during restart.", "CONFIG_CHANGED_DURING_START");
356
+ }
357
+ return { started, readiness };
377
358
  });
378
- if (readiness.phase !== "relay-authenticated" || !readiness.machine?.online) {
379
- throw new CliError("The Daemon did not authenticate before the startup deadline.", "DAEMON_ONLINE_TIMEOUT");
380
- }
381
- const pid = started.local.pid ?? started.startedPid;
359
+ const pid = ready.started.local.pid ?? ready.started.startedPid;
382
360
  return {
383
361
  online: true,
384
362
  ...(pid ? { pid } : {}),
385
- started: started.started,
386
- name: readiness.machine.name ?? params.daemonConfig.machineName,
363
+ started: ready.started.started,
364
+ restarted: ready.started.restarted,
365
+ name: ready.readiness.machine?.name ?? params.daemonConfig.machineName,
387
366
  };
388
367
  }
389
368
  catch (error) {
390
- return await projectRecoveredDaemonReadinessFailure(started, params.daemonConfig.machineName, error);
369
+ if (started) {
370
+ return await projectRecoveredDaemonReadinessFailure(started, params.daemonConfig.machineName, error);
371
+ }
372
+ const settled = await localDaemonStatus().catch(() => current);
373
+ return {
374
+ online: false,
375
+ name: params.daemonConfig.machineName,
376
+ ...(settled.pid ? { pid: settled.pid } : {}),
377
+ started: false,
378
+ restarted: current.running && !settled.running,
379
+ needsAttention: error instanceof Error ? error.message : "The Daemon could not be restarted automatically.",
380
+ };
391
381
  }
392
382
  }
393
383
  export async function projectRecoveredDaemonReadinessFailure(started, machineName, readinessError, options = {}) {
@@ -408,6 +398,7 @@ export async function projectRecoveredDaemonReadinessFailure(started, machineNam
408
398
  online: false,
409
399
  ...(started.local.pid ?? started.startedPid ? { pid: started.local.pid ?? started.startedPid } : {}),
410
400
  started: daemonCleanupError ? started.started : false,
401
+ restarted: started.restarted === true,
411
402
  name: machineName,
412
403
  needsAttention,
413
404
  };
@@ -10,7 +10,7 @@ import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, inspe
10
10
  import { redactedRelayUrl, withAccountConfigTransaction, } from "./config.js";
11
11
  import { cliErrorFromFetch } from "./relay-http.js";
12
12
  import { controllerBaseUrl, ensureControllerConfig, ensureDaemonPaired, readStartConfigPreflight } from "./bootstrap-config.js";
13
- import { ensureDaemonRunning, readDaemonProjectionBaseline, startFailureMayUseCommittedConfig, waitForDaemonReadiness, } from "./bootstrap-daemon.js";
13
+ import { readDaemonProjectionBaseline, restartDaemonForBootstrap, startFailureMayUseCommittedConfig, waitForDaemonReadiness, } from "./bootstrap-daemon.js";
14
14
  import { openUrl, writeHumanStartOutput } from "./bootstrap-output.js";
15
15
  import { startJoinBootstrap } from "./bootstrap-join.js";
16
16
  import { reconcileSatisfiedDeviceRecoveryPending, startDeviceRecoveryBootstrap } from "./bootstrap-device-recovery.js";
@@ -85,16 +85,15 @@ export async function startBootstrap(flags) {
85
85
  if (daemonBefore.running && preflight.kind !== "ready") {
86
86
  throw new CliError("A verified daemon is running, but the matching controller/daemon config pair is incomplete. No configuration or daemon process was changed. Run happy-elves doctor before recovering this workspace.", "CONFIG_SPLIT_BRAIN");
87
87
  }
88
- // A running owner is accepted only when its signed runtime marker proves
89
- // that it loaded the same daemon identity we just read under this lease.
90
- // Ordinary `start` never restarts a healthy daemon because package files
91
- // happen to have a newer mtime.
88
+ // Top-level `happy-elves start` deliberately restarts a verified owner so
89
+ // it always loads the currently installed package. A missing marker is the
90
+ // supported legacy-upgrade case: exact PID v2 ownership still fences the
91
+ // stop, and the replacement publishes a fresh marker. Invalid, mismatched,
92
+ // or unverified identities remain fail-closed and are never signalled.
92
93
  if (!daemonProcessNeedsAttention && daemonBefore.running && preflight.daemon) {
93
94
  const runtime = await inspectLocalDaemonRuntime(preflight.daemon);
94
- if (runtime.state !== "match") {
95
- daemonProcessNeedsAttention = runtime.state === "missing"
96
- ? "Automatic startup metadata will be repaired on the next controlled Daemon restart."
97
- : "The running Daemon identity needs repair before Happy Elves can manage its process.";
95
+ if (runtime.state !== "match" && runtime.state !== "missing") {
96
+ daemonProcessNeedsAttention = "The running Daemon identity needs repair before Happy Elves can manage its process.";
98
97
  }
99
98
  }
100
99
  const networkDeadline = Date.now() + startDaemonTimeoutMs;
@@ -187,19 +186,20 @@ export async function startBootstrap(flags) {
187
186
  started: false,
188
187
  startedPid: undefined,
189
188
  committedConfigMayBeInUse: daemonBefore.running,
189
+ restarted: false,
190
190
  },
191
191
  machine: baseline,
192
192
  invite,
193
193
  daemonProcessNeedsAttention,
194
194
  };
195
195
  }
196
- const daemon = await ensureDaemonRunning(daemonState.config, cwd);
196
+ const daemon = await restartDaemonForBootstrap(daemonState.config, cwd);
197
197
  startedHandle = daemon.startedHandle;
198
198
  committedConfigMayBeInUse = daemon.committedConfigMayBeInUse === true;
199
199
  const readiness = await waitForDaemonReadiness(client, daemonState.config.machineId, {
200
200
  baseline,
201
201
  expectedInstanceGeneration: daemon.instanceGeneration,
202
- requireFreshProjection: daemon.started,
202
+ requireFreshProjection: daemon.started || daemon.restarted,
203
203
  // Lifetime-lock acquisition/reclaim and relay-authenticated readiness
204
204
  // are separate bounded phases.
205
205
  timeoutMs: startDaemonTimeoutMs,
@@ -317,8 +317,9 @@ export async function startBootstrap(flags) {
317
317
  pid: daemon.local.pid ?? daemon.startedPid,
318
318
  pidPath: daemon.local.pidPath,
319
319
  started: daemon.started,
320
- alreadyRunning: daemonBefore.running || daemon.committedConfigMayBeInUse === true,
321
- restarted: false,
320
+ alreadyRunning: daemon.restarted !== true &&
321
+ (daemonBefore.running || daemon.committedConfigMayBeInUse === true),
322
+ restarted: daemon.restarted,
322
323
  ...(daemonNeedsAttention ? { needsAttention: daemonNeedsAttention } : {}),
323
324
  },
324
325
  ...(invite ? {
@@ -923,6 +923,7 @@ async function runGatewayPrompt(ws, config, gateway, binding, input, session, le
923
923
  }
924
924
  const started = await startGatewayProviderTurnWithFence(lease, input, binding, () => withCancelSettlement(getRuntime(session.cwd).startTurn({
925
925
  handle: session.handle,
926
+ applicationContext: prepared.applicationContext,
926
927
  text: prepared.text,
927
928
  mode: "prompt",
928
929
  permissionMode,
@@ -17831,7 +17831,9 @@ var capabilitiesSchema = external_exports.object({
17831
17831
  memory: external_exports.boolean().optional(),
17832
17832
  memoryTurnOptionsV1: external_exports.boolean().optional(),
17833
17833
  memoryCommandsV1: external_exports.boolean().optional(),
17834
+ memoryProjectsV1: external_exports.boolean().optional(),
17834
17835
  gatewayCommandsV1: external_exports.boolean().optional(),
17836
+ sessionProviderRestoreV1: external_exports.boolean().optional(),
17835
17837
  filePreview: external_exports.boolean().optional(),
17836
17838
  scheduleAuthContextV1: external_exports.boolean().optional(),
17837
17839
  scheduleCreateSemanticDigestV1: external_exports.boolean().optional(),
@@ -17876,6 +17878,9 @@ var sessionSnapshotSchema = external_exports.object({
17876
17878
  }).optional(),
17877
17879
  sourceSessionId: external_exports.string().min(1).optional(),
17878
17880
  sourceCheckpointId: external_exports.string().min(1).optional(),
17881
+ recovery: external_exports.object({
17882
+ code: external_exports.literal("provider-archived")
17883
+ }).optional(),
17879
17884
  historyModel: external_exports.enum(["append-only", "authoritative-linear"]).optional(),
17880
17885
  capabilities: sessionCapabilitiesSchema.optional()
17881
17886
  });
@@ -18169,6 +18174,14 @@ var controllerMessageSchema = external_exports.discriminatedUnion("type", [
18169
18174
  encryptedPayload: encryptedEnvelopeSchema,
18170
18175
  semanticDigest: external_exports.string().min(1)
18171
18176
  }),
18177
+ external_exports.object({
18178
+ type: external_exports.literal("controller:memoryProjectCommand"),
18179
+ requestId: external_exports.string().min(1),
18180
+ machineId: external_exports.string().min(1),
18181
+ action: external_exports.enum(["list", "overview", "read", "search", "reindex"]),
18182
+ encryptedPayload: encryptedEnvelopeSchema,
18183
+ semanticDigest: external_exports.string().min(1)
18184
+ }),
18172
18185
  external_exports.object({
18173
18186
  type: external_exports.literal("controller:gatewayCommand"),
18174
18187
  requestId: external_exports.string().min(1),
@@ -18189,6 +18202,11 @@ var controllerMessageSchema = external_exports.discriminatedUnion("type", [
18189
18202
  requestId: external_exports.string().min(1),
18190
18203
  sessionId: external_exports.string().min(1)
18191
18204
  }),
18205
+ external_exports.object({
18206
+ type: external_exports.literal("controller:restoreSession"),
18207
+ requestId: external_exports.string().min(1),
18208
+ sessionId: external_exports.string().min(1)
18209
+ }),
18192
18210
  external_exports.object({
18193
18211
  type: external_exports.literal("controller:listHistoricalSessions"),
18194
18212
  requestId: external_exports.string().min(1),
@@ -18433,6 +18451,14 @@ var machineMessageSchema = external_exports.discriminatedUnion("type", [
18433
18451
  messageId: external_exports.string().min(1).optional(),
18434
18452
  encryptedMetadata: encryptedEnvelopeSchema.optional()
18435
18453
  }),
18454
+ external_exports.object({
18455
+ type: external_exports.literal("machine:sessionRestored"),
18456
+ requestId: external_exports.string().min(1),
18457
+ sessionId: external_exports.string().min(1),
18458
+ name: external_exports.string().min(1).optional(),
18459
+ messageId: external_exports.string().min(1).optional(),
18460
+ encryptedMetadata: encryptedEnvelopeSchema.optional()
18461
+ }),
18436
18462
  external_exports.object({
18437
18463
  type: external_exports.literal("machine:historicalSessions"),
18438
18464
  requestId: external_exports.string().min(1),
@@ -18532,6 +18558,13 @@ var machineMessageSchema = external_exports.discriminatedUnion("type", [
18532
18558
  encryptedPayload: encryptedEnvelopeSchema,
18533
18559
  messageId: external_exports.string().min(1).optional()
18534
18560
  }),
18561
+ external_exports.object({
18562
+ type: external_exports.literal("machine:memoryProjectResult"),
18563
+ requestId: external_exports.string().min(1),
18564
+ action: external_exports.enum(["list", "overview", "read", "search", "reindex"]),
18565
+ encryptedPayload: encryptedEnvelopeSchema,
18566
+ messageId: external_exports.string().min(1).optional()
18567
+ }),
18535
18568
  external_exports.object({
18536
18569
  type: external_exports.literal("machine:gatewayResult"),
18537
18570
  requestId: external_exports.string().min(1),
@@ -18568,7 +18601,8 @@ var clientMessageSchema = external_exports.union([
18568
18601
  capabilities: external_exports.object({
18569
18602
  daemonLifecycleEventsV1: external_exports.boolean().optional(),
18570
18603
  daemonAutostartV1: external_exports.boolean().optional(),
18571
- resourcePreferenceInvalidationV1: external_exports.boolean().optional()
18604
+ resourcePreferenceInvalidationV1: external_exports.boolean().optional(),
18605
+ daemonProjectsPreferenceV1: external_exports.boolean().optional()
18572
18606
  }).optional()
18573
18607
  }),
18574
18608
  controllerMessageSchema,
@@ -18610,6 +18644,21 @@ var machineCommandSchema = external_exports.discriminatedUnion("type", [
18610
18644
  semanticDigest: external_exports.string().min(1),
18611
18645
  authorization: scheduleAuthorizationContextV1Schema
18612
18646
  }),
18647
+ external_exports.object({
18648
+ type: external_exports.literal("machine:memoryProjectCommand"),
18649
+ requestId: external_exports.string().min(1),
18650
+ controllerRequestId: external_exports.string().min(1),
18651
+ machineId: external_exports.string().min(1),
18652
+ action: external_exports.enum(["list", "overview", "read", "search", "reindex"]),
18653
+ encryptedPayload: encryptedEnvelopeSchema,
18654
+ semanticDigest: external_exports.string().min(1),
18655
+ projectCandidates: external_exports.array(external_exports.object({
18656
+ sessionId: external_exports.string().min(1),
18657
+ cwd: external_exports.string().min(1).max(4096).refine((value) => !value.includes("\0"), "cwd must not contain NUL"),
18658
+ updatedAt: external_exports.number().int().nonnegative()
18659
+ })).max(500),
18660
+ authorization: scheduleAuthorizationContextV1Schema
18661
+ }),
18613
18662
  external_exports.object({
18614
18663
  type: external_exports.literal("machine:gatewayCommand"),
18615
18664
  requestId: external_exports.string().min(1),
@@ -18637,6 +18686,12 @@ var machineCommandSchema = external_exports.discriminatedUnion("type", [
18637
18686
  sessionId: external_exports.string().min(1),
18638
18687
  encryptedMetadata: encryptedEnvelopeSchema.optional()
18639
18688
  }),
18689
+ external_exports.object({
18690
+ type: external_exports.literal("machine:restoreSession"),
18691
+ requestId: external_exports.string().min(1),
18692
+ sessionId: external_exports.string().min(1),
18693
+ encryptedMetadata: encryptedEnvelopeSchema.optional()
18694
+ }),
18640
18695
  external_exports.object({
18641
18696
  type: external_exports.literal("machine:listHistoricalSessions"),
18642
18697
  requestId: external_exports.string().min(1),
@@ -18759,6 +18814,7 @@ var _machineClientMessageTypeSchema = external_exports.enum([
18759
18814
  "machine:sessionHeadAdvanced",
18760
18815
  "machine:turnDone",
18761
18816
  "machine:sessionResumed",
18817
+ "machine:sessionRestored",
18762
18818
  "machine:historicalSessions",
18763
18819
  "machine:directoryListed",
18764
18820
  "machine:filePreviewed",
@@ -18771,6 +18827,7 @@ var _machineClientMessageTypeSchema = external_exports.enum([
18771
18827
  "machine:cancelResult",
18772
18828
  "machine:scheduleResult",
18773
18829
  "machine:memoryResult",
18830
+ "machine:memoryProjectResult",
18774
18831
  "machine:gatewayResult",
18775
18832
  "machine:daemonLifecycleResult",
18776
18833
  "machine:error"
@@ -18783,6 +18840,7 @@ var serverMessageSchema = external_exports.union([
18783
18840
  machineMessages: external_exports.array(external_exports.string().min(1)).optional(),
18784
18841
  scheduleCreateSemanticDigestV1: external_exports.boolean().optional(),
18785
18842
  memoryCommandsV1: external_exports.boolean().optional(),
18843
+ memoryProjectsV1: external_exports.boolean().optional(),
18786
18844
  memoryTurnOptionsV1: external_exports.boolean().optional(),
18787
18845
  gatewayCommandsV1: external_exports.boolean().optional(),
18788
18846
  enrollmentInvitesV1: external_exports.boolean().optional(),
@@ -18869,6 +18927,13 @@ var serverMessageSchema = external_exports.union([
18869
18927
  action: external_exports.string().min(1),
18870
18928
  encryptedPayload: encryptedEnvelopeSchema
18871
18929
  }),
18930
+ external_exports.object({
18931
+ type: external_exports.literal("server:memoryProjectResult"),
18932
+ requestId: external_exports.string().min(1),
18933
+ machineId: external_exports.string().min(1),
18934
+ action: external_exports.enum(["list", "overview", "read", "search", "reindex"]),
18935
+ encryptedPayload: encryptedEnvelopeSchema
18936
+ }),
18872
18937
  external_exports.object({
18873
18938
  type: external_exports.literal("server:gatewayResult"),
18874
18939
  requestId: external_exports.string().min(1),
@@ -18888,7 +18953,7 @@ var serverMessageSchema = external_exports.union([
18888
18953
  type: external_exports.literal("server:resourcePreferenceInvalidated"),
18889
18954
  resourceType: external_exports.enum(["session", "daemon"]),
18890
18955
  resourceId: external_exports.string().min(1),
18891
- preferenceKey: external_exports.enum(["session.star.v1", "daemon.alias.v1"]),
18956
+ preferenceKey: external_exports.enum(["session.star.v1", "daemon.alias.v1", "daemon.projects.v1"]),
18892
18957
  revision: external_exports.number().int().positive()
18893
18958
  }),
18894
18959
  external_exports.object({
@@ -20539,10 +20604,9 @@ function windowsTaskBackend(run) {
20539
20604
  return {
20540
20605
  async inspect(spec) {
20541
20606
  const [result, identity] = await Promise.all([
20542
- run(command, ["/Query", "/TN", spec.serviceId, "/XML"]),
20607
+ inspectWindowsTask(run, command, spec.serviceId),
20543
20608
  run(identityCommand, ["/user", "/fo", "csv", "/nh"])
20544
20609
  ]);
20545
- requireManagerInspectionQuery(result, "schtasks /Query");
20546
20610
  const registered = result.exitCode === 0;
20547
20611
  if (registered)
20548
20612
  requireManagerInspectionSuccess(identity, "whoami /user");
@@ -20576,6 +20640,15 @@ function windowsTaskBackend(run) {
20576
20640
  }
20577
20641
  };
20578
20642
  }
20643
+ async function inspectWindowsTask(run, command, serviceId) {
20644
+ const result = await run(command, ["/Query", "/TN", serviceId, "/XML"]);
20645
+ requireManagerInspectionSettled(result, "schtasks /Query");
20646
+ if (result.exitCode === 0 || managerObjectMissing(result))
20647
+ return result;
20648
+ const managerProbe = await run(command, ["/Query", "/FO", "CSV", "/NH"]);
20649
+ requireManagerInspectionSuccess(managerProbe, "schtasks /Query");
20650
+ return result;
20651
+ }
20579
20652
  function windowsTaskDefinitionMatches(spec, xmlSource, userSid) {
20580
20653
  const triggers = singleXmlElementBody(xmlSource, "Triggers");
20581
20654
  const principals = singleXmlElementBody(xmlSource, "Principals");
@@ -242,6 +242,7 @@ async function runLoopOnce(ws, config, loop, reason) {
242
242
  };
243
243
  const turn = withCancelSettlement(runtime.startTurn({
244
244
  handle: turnHandle,
245
+ applicationContext: prepared.applicationContext,
245
246
  text: prepared.text,
246
247
  mode: "prompt",
247
248
  permissionMode,