@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.
- package/apps/cli/dist/commands/lib/bootstrap-daemon.d.ts +22 -1
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js +73 -1
- package/apps/cli/dist/commands/lib/bootstrap-device-recovery.d.ts +5 -2
- package/apps/cli/dist/commands/lib/bootstrap-device-recovery.js +42 -51
- package/apps/cli/dist/commands/lib/bootstrap.js +14 -13
- package/apps/daemon/dist/gateway/runtime.js +1 -0
- package/apps/daemon/dist/lifecycle/detached-helper-bundle.mjs +77 -4
- package/apps/daemon/dist/loop/runner.js +1 -0
- package/apps/daemon/dist/memory/commands.js +44 -42
- package/apps/daemon/dist/memory/flush.js +1 -0
- package/apps/daemon/dist/memory/inject.d.ts +1 -0
- package/apps/daemon/dist/memory/inject.js +6 -4
- package/apps/daemon/dist/memory/managers.d.ts +5 -3
- package/apps/daemon/dist/memory/managers.js +110 -34
- package/apps/daemon/dist/memory/project-commands.d.ts +3 -0
- package/apps/daemon/dist/memory/project-commands.js +394 -0
- package/apps/daemon/dist/relay/connection.js +10 -1
- package/apps/daemon/dist/relay/register.js +2 -0
- package/apps/daemon/dist/relay/send.js +1 -0
- package/apps/daemon/dist/runtime/external-provider.js +47 -3
- package/apps/daemon/dist/session/lifecycle.d.ts +4 -1
- package/apps/daemon/dist/session/lifecycle.js +156 -20
- package/apps/daemon/dist/session/metadata.d.ts +2 -1
- package/apps/daemon/dist/session/metadata.js +10 -1
- package/apps/daemon/dist/session/prompt.js +59 -7
- package/apps/daemon/dist/turn-coordinator.d.ts +1 -1
- package/apps/daemon/dist/types.d.ts +2 -0
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/controller-handlers.js +223 -4
- package/apps/relay/dist/db.js +69 -1
- package/apps/relay/dist/http-routes.js +1 -1
- package/apps/relay/dist/http-schemas.d.ts +1 -0
- package/apps/relay/dist/http-schemas.js +1 -1
- package/apps/relay/dist/machine-command-result-handlers.js +111 -2
- package/apps/relay/dist/machine-handler-context.d.ts +7 -0
- package/apps/relay/dist/projections.js +7 -0
- package/apps/relay/dist/relay-context.js +12 -2
- package/apps/relay/dist/scope.js +5 -0
- package/apps/relay/dist/types.d.ts +3 -1
- package/apps/relay/dist/websocket.js +3 -0
- package/build-identity.json +2 -2
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
- package/packages/client/dist/client.d.ts +20 -2
- package/packages/client/dist/client.js +298 -3
- package/packages/client/dist/index.d.ts +2 -2
- package/packages/client/dist/live-types.d.ts +1 -1
- package/packages/client/dist/transport.d.ts +5 -0
- package/packages/client/dist/transport.js +19 -1
- package/packages/client/dist/types.d.ts +22 -0
- package/packages/daemon-service/dist/index.js +14 -2
- package/packages/memory/dist/index.d.ts +2 -1
- package/packages/memory/dist/index.js +2 -1
- package/packages/memory/dist/manager.d.ts +18 -0
- package/packages/memory/dist/manager.js +108 -8
- package/packages/memory/dist/projects.d.ts +13 -0
- package/packages/memory/dist/projects.js +211 -0
- package/packages/runtime/dist/index.d.ts +19 -0
- package/packages/runtime-acpx/dist/index.js +6 -0
- package/packages/runtime-cli/dist/claude-turn.d.ts +1 -0
- package/packages/runtime-cli/dist/claude-turn.js +1 -0
- package/packages/runtime-cli/dist/codex-app-server.d.ts +6 -0
- package/packages/runtime-cli/dist/codex-app-server.js +129 -3
- package/packages/runtime-cli/dist/codex-json-rpc.js +32 -8
- package/packages/runtime-cli/dist/codex-protocol.d.ts +1 -1
- package/packages/runtime-cli/dist/codex-protocol.js +4 -1
- package/packages/runtime-cli/dist/index.js +10 -0
- package/packages/runtime-cli/dist/session-store.d.ts +1 -0
- package/packages/runtime-cli/dist/session-store.js +32 -0
- package/packages/shared/dist/crypto.d.ts +28 -1
- package/packages/shared/dist/crypto.js +89 -1
- package/packages/shared/dist/protocol-schemas.d.ts +7 -0
- package/packages/shared/dist/protocol-schemas.js +5 -0
- package/packages/shared/dist/protocol-types.d.ts +78 -1
- package/packages/shared/dist/protocol.d.ts +54 -2
- package/packages/shared/dist/protocol.js +61 -1
- package/packages/shared/dist/session-state.d.ts +1 -1
- package/apps/cli/dist/commands/interaction.d.ts +0 -2
- package/apps/cli/dist/commands/interaction.js +0 -416
- package/apps/cli/dist/commands/lib/loop-store.d.ts +0 -9
- package/apps/cli/dist/commands/lib/loop-store.js +0 -88
- package/apps/daemon/dist/agent-interaction-ipc.d.ts +0 -45
- package/apps/daemon/dist/agent-interaction-ipc.js +0 -578
- package/apps/daemon/dist/agent-interaction-mcp.d.ts +0 -6
- package/apps/daemon/dist/agent-interaction-mcp.js +0 -259
- package/apps/daemon/dist/interaction-claim-history.d.ts +0 -4
- package/apps/daemon/dist/interaction-claim-history.js +0 -51
- package/apps/daemon/dist/interaction-claim-journal.d.ts +0 -20
- package/apps/daemon/dist/interaction-claim-journal.js +0 -139
- package/apps/daemon/dist/interaction-local-state.d.ts +0 -3
- package/apps/daemon/dist/interaction-local-state.js +0 -12
- package/apps/daemon/dist/interaction-privacy.d.ts +0 -10
- package/apps/daemon/dist/interaction-privacy.js +0 -23
- package/apps/daemon/dist/interaction-prompt-redaction-cache.d.ts +0 -7
- package/apps/daemon/dist/interaction-prompt-redaction-cache.js +0 -254
- package/apps/daemon/dist/interaction-runtime.d.ts +0 -58
- package/apps/daemon/dist/interaction-runtime.js +0 -674
- package/apps/daemon/dist/lifecycle/index.d.ts +0 -7
- package/apps/daemon/dist/lifecycle/index.js +0 -6
- package/apps/relay/dist/session-record-handlers.d.ts +0 -15
- package/apps/relay/dist/session-record-handlers.js +0 -148
- package/apps/relay/dist/session-record-repository.d.ts +0 -38
- package/apps/relay/dist/session-record-repository.js +0 -312
- package/packages/runtime-cli/dist/session-environment.d.ts +0 -7
- package/packages/runtime-cli/dist/session-environment.js +0 -24
- package/packages/shared/dist/interaction.d.ts +0 -95
- package/packages/shared/dist/interaction.js +0 -169
- package/packages/shared/dist/session-record.d.ts +0 -2
- 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
|
|
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,
|
|
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 {
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
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 =
|
|
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
|
|
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:
|
|
321
|
-
|
|
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
|
|
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,
|