@cydm/happy-elves 0.1.0-beta.79 → 0.1.0-beta.80
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/daemon.js +28 -18
- package/apps/cli/dist/commands/gateway.js +22 -3
- package/apps/cli/dist/commands/lib/args.js +3 -1
- package/apps/cli/dist/commands/lib/bootstrap-daemon.d.ts +1 -1
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js +3 -2
- package/apps/cli/dist/commands/lib/bootstrap-device-recovery.js +4 -3
- package/apps/cli/dist/commands/lib/bootstrap-output.d.ts +1 -1
- package/apps/cli/dist/commands/lib/bootstrap-output.js +1 -1
- package/apps/cli/dist/commands/lib/bootstrap.js +7 -4
- package/apps/cli/dist/commands/lib/index.d.ts +1 -0
- package/apps/cli/dist/commands/lib/index.js +1 -0
- package/apps/cli/dist/commands/lib/machine-state.d.ts +3 -0
- package/apps/cli/dist/commands/lib/machine-state.js +14 -0
- package/apps/cli/dist/commands/lib/orchestrator.d.ts +1 -1
- package/apps/cli/dist/commands/lib/orchestrator.js +13 -10
- package/apps/cli/dist/commands/lib/session-output.d.ts +8 -1
- package/apps/cli/dist/commands/lib/session-output.js +35 -0
- package/apps/cli/dist/commands/lib/session-view.d.ts +1 -1
- package/apps/cli/dist/commands/lib/session-view.js +42 -44
- package/apps/cli/dist/commands/lib/status.js +12 -6
- package/apps/cli/dist/commands/lib/types.d.ts +5 -1
- package/apps/cli/dist/commands/lib/types.js +1 -1
- package/apps/cli/dist/commands/lib/usage.js +14 -9
- package/apps/cli/dist/commands/lib/workspace.js +6 -5
- package/apps/cli/dist/commands/loop.js +4 -4
- package/apps/cli/dist/commands/machine.js +13 -7
- package/apps/cli/dist/commands/orchestrator.js +101 -38
- package/apps/cli/dist/commands/remote.js +7 -7
- package/apps/cli/dist/commands/session.js +52 -28
- package/apps/cli/dist/commands/skill.d.ts +3 -1
- package/apps/cli/dist/commands/skill.js +44 -15
- package/apps/cli/dist/commands/turn.js +98 -6
- package/apps/cli/dist/skills/registry.d.ts +1 -1
- package/apps/cli/dist/skills/registry.js +13 -2
- package/apps/daemon/dist/gateway/runtime.js +279 -126
- package/apps/daemon/dist/lifecycle/detached-helper-bundle.mjs +304 -15
- package/apps/daemon/dist/loop/runner.js +398 -163
- package/apps/daemon/dist/memory/config.d.ts +2 -2
- package/apps/daemon/dist/memory/config.js +5 -4
- package/apps/daemon/dist/memory/flush.js +314 -57
- package/apps/daemon/dist/paths.d.ts +2 -0
- package/apps/daemon/dist/paths.js +2 -0
- package/apps/daemon/dist/relay/connection.js +12 -1
- package/apps/daemon/dist/relay/register.js +2 -1
- package/apps/daemon/dist/relay/send.js +44 -4
- package/apps/daemon/dist/runtime/cancel-settlement.js +4 -0
- package/apps/daemon/dist/runtime/catalog.d.ts +18 -0
- package/apps/daemon/dist/runtime/catalog.js +119 -0
- package/apps/daemon/dist/runtime/external-provider.js +59 -30
- package/apps/daemon/dist/runtime/provider-start-journal.d.ts +64 -0
- package/apps/daemon/dist/runtime/provider-start-journal.js +208 -0
- package/apps/daemon/dist/runtime/recovery-context.d.ts +4 -0
- package/apps/daemon/dist/runtime/recovery-context.js +28 -0
- package/apps/daemon/dist/runtime/session-recovery.d.ts +0 -3
- package/apps/daemon/dist/runtime/session-recovery.js +2 -30
- package/apps/daemon/dist/runtime/turn-projection.d.ts +27 -0
- package/apps/daemon/dist/runtime/turn-projection.js +117 -0
- package/apps/daemon/dist/runtime/turn-supervision.d.ts +45 -0
- package/apps/daemon/dist/runtime/turn-supervision.js +390 -0
- package/apps/daemon/dist/runtime.js +3 -0
- package/apps/daemon/dist/session/events.js +19 -0
- package/apps/daemon/dist/session/prompt.js +285 -95
- package/apps/daemon/dist/session/reconcile.d.ts +4 -0
- package/apps/daemon/dist/session/reconcile.js +225 -0
- package/apps/daemon/dist/session/retry-recovery.d.ts +3 -0
- package/apps/daemon/dist/session/retry-recovery.js +169 -0
- package/apps/daemon/dist/state.d.ts +22 -0
- package/apps/daemon/dist/turn-coordinator.d.ts +9 -0
- package/apps/daemon/dist/turn-coordinator.js +24 -1
- package/apps/daemon/dist/types.d.ts +1 -1
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/connections.d.ts +4 -1
- package/apps/relay/dist/connections.js +1 -1
- package/apps/relay/dist/controller-handlers.d.ts +4 -0
- package/apps/relay/dist/controller-handlers.js +399 -9
- package/apps/relay/dist/db.js +396 -1
- package/apps/relay/dist/event-visibility.d.ts +1 -1
- package/apps/relay/dist/event-visibility.js +9 -5
- package/apps/relay/dist/http-routes.js +57 -27
- package/apps/relay/dist/index.js +3 -1
- package/apps/relay/dist/machine-command-result-handlers.js +99 -18
- package/apps/relay/dist/machine-handler-context.d.ts +4 -0
- package/apps/relay/dist/machine-state.d.ts +27 -0
- package/apps/relay/dist/machine-state.js +39 -0
- package/apps/relay/dist/projection-repository.js +28 -6
- package/apps/relay/dist/projections.js +46 -2
- package/apps/relay/dist/relay-context.d.ts +3 -0
- package/apps/relay/dist/relay-context.js +186 -17
- package/apps/relay/dist/repositories.js +12 -0
- package/apps/relay/dist/scope.js +26 -1
- package/apps/relay/dist/session-creation-failure.d.ts +4 -0
- package/apps/relay/dist/session-creation-failure.js +54 -0
- package/apps/relay/dist/session-projection-reducer.js +350 -18
- package/apps/relay/dist/turn-repository.d.ts +175 -0
- package/apps/relay/dist/turn-repository.js +695 -0
- package/apps/relay/dist/types.d.ts +65 -0
- package/apps/relay/dist/websocket.js +87 -18
- package/build-identity.json +3 -3
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
- package/packages/agent-sdk/dist/index.d.ts +27 -2
- package/packages/agent-sdk/dist/index.js +75 -1
- package/packages/client/dist/client.d.ts +7 -2
- package/packages/client/dist/client.js +242 -31
- package/packages/client/dist/index.d.ts +2 -1
- package/packages/client/dist/index.js +1 -0
- package/packages/client/dist/live-session-state.js +10 -1
- package/packages/client/dist/live-state.js +7 -0
- package/packages/client/dist/live-types.d.ts +8 -2
- package/packages/client/dist/live.d.ts +4 -0
- package/packages/client/dist/live.js +60 -2
- package/packages/client/dist/recovery-context.d.ts +37 -0
- package/packages/client/dist/recovery-context.js +97 -0
- package/packages/client/dist/transport.d.ts +14 -1
- package/packages/client/dist/transport.js +85 -52
- package/packages/client/dist/turns.d.ts +9 -0
- package/packages/client/dist/turns.js +34 -0
- package/packages/client/dist/types.d.ts +28 -7
- package/packages/client/dist/ui-probe.js +2 -1
- package/packages/client/dist/validation.js +30 -12
- package/packages/pie-provider/dist/index.js +268 -33
- package/packages/pie-provider/dist/turn-observation-store.d.ts +44 -0
- package/packages/pie-provider/dist/turn-observation-store.js +306 -0
- package/packages/provider-protocol/dist/index.d.ts +520 -1
- package/packages/provider-protocol/dist/index.js +123 -2
- package/packages/runtime/dist/index.d.ts +35 -3
- package/packages/runtime/dist/index.js +66 -1
- package/packages/runtime-acpx/dist/index.d.ts +1 -1
- package/packages/runtime-acpx/dist/index.js +130 -1
- package/packages/runtime-acpx/dist/turn-observation-store.d.ts +40 -0
- package/packages/runtime-acpx/dist/turn-observation-store.js +213 -0
- package/packages/runtime-cli/dist/claude-turn.d.ts +4 -2
- package/packages/runtime-cli/dist/claude-turn.js +54 -20
- package/packages/runtime-cli/dist/codex-app-server.d.ts +24 -4
- package/packages/runtime-cli/dist/codex-app-server.js +481 -143
- package/packages/runtime-cli/dist/codex-protocol.d.ts +1 -1
- package/packages/runtime-cli/dist/codex-protocol.js +2 -1
- package/packages/runtime-cli/dist/index.js +139 -4
- package/packages/runtime-cli/dist/persistent-turn-observation-store.d.ts +50 -0
- package/packages/runtime-cli/dist/persistent-turn-observation-store.js +252 -0
- package/packages/runtime-cli/dist/turn-observation.d.ts +33 -0
- package/packages/runtime-cli/dist/turn-observation.js +114 -0
- package/packages/shared/dist/crypto.d.ts +1 -1
- package/packages/shared/dist/crypto.js +3 -3
- package/packages/shared/dist/index.d.ts +1 -0
- package/packages/shared/dist/index.js +1 -0
- package/packages/shared/dist/node/gateway-store.d.ts +15 -1
- package/packages/shared/dist/node/gateway-store.js +39 -1
- package/packages/shared/dist/protocol-schemas.d.ts +87 -4
- package/packages/shared/dist/protocol-schemas.js +57 -0
- package/packages/shared/dist/protocol-types.d.ts +24 -0
- package/packages/shared/dist/protocol.d.ts +141 -2
- package/packages/shared/dist/protocol.js +256 -14
- package/packages/shared/dist/session-state.js +2 -6
- package/packages/shared/dist/turn-state.d.ts +72 -0
- package/packages/shared/dist/turn-state.js +24 -0
|
@@ -2,7 +2,7 @@ import { createHash, timingSafeEqual } from "node:crypto";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { daemonLifecycleRequestIdV1Schema, } from "../../../../packages/shared/dist/index.js";
|
|
5
|
-
import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, assertNoPendingEnrollmentAuthority, canonicalRelayUrl, cliErrorFromFetch, CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, diagnoseInstallation, emitDoctorResult, ensureDaemonRunning,
|
|
5
|
+
import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, assertNoPendingEnrollmentAuthority, canonicalRelayUrl, cliErrorFromFetch, CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, diagnoseInstallation, emitDoctorResult, ensureDaemonRunning, localDaemonStatus, machineAvailabilityLabel, machineIsAvailable, ok, parsePairingClaimResponse, randomId, readConfig, readDaemonConfig, readDaemonProjectionBaseline, readLocalAuditLog, readRelayJson, redactedDaemonConfig, requirePositional, requireRelayUrl, requireString, restartDaemonForBootstrap, showMachine, startDaemonTimeoutMs, stopLocalDaemon, stopSpawnedDaemon, throwRelayHttpError, waitForDaemonReadiness, waitForDaemonSelfReadiness, wantsJson, withAccountConfigTransaction } from "./lib/index.js";
|
|
6
6
|
function positiveLimit(flags, fallback) {
|
|
7
7
|
const value = flags.limit;
|
|
8
8
|
if (value === undefined)
|
|
@@ -57,7 +57,7 @@ async function waitForLocalDaemonReadiness(client, config, baseline, daemon, req
|
|
|
57
57
|
if (readiness.phase === "process-exited" || readiness.phase === "owner-changed") {
|
|
58
58
|
throw new CliError("Daemon exited before it authenticated with the relay. Run happy-elves daemon doctor and happy-elves daemon logs.", "DAEMON_START_FAILED");
|
|
59
59
|
}
|
|
60
|
-
if (readiness.phase !== "relay-authenticated" || !readiness.machine
|
|
60
|
+
if (readiness.phase !== "relay-authenticated" || !machineIsAvailable(readiness.machine)) {
|
|
61
61
|
const relayDetail = readiness.lastRelayErrorCode ? ` Last relay error: ${readiness.lastRelayErrorCode}.` : "";
|
|
62
62
|
throw new CliError(`Daemon is running, but it did not authenticate with the relay before the startup deadline.${relayDetail} Run happy-elves daemon doctor and happy-elves daemon logs.`, "DAEMON_ONLINE_TIMEOUT");
|
|
63
63
|
}
|
|
@@ -107,8 +107,8 @@ async function remoteDaemonLifecycle(flags, action) {
|
|
|
107
107
|
if (matches.length !== 1) {
|
|
108
108
|
throw new CliError(`Relay returned duplicate Daemon identities: ${machineId}`, "RELAY_RESPONSE_INVALID");
|
|
109
109
|
}
|
|
110
|
-
if (!machine
|
|
111
|
-
throw new CliError(`Daemon is
|
|
110
|
+
if (!machineIsAvailable(machine))
|
|
111
|
+
throw new CliError(`Daemon is not available: ${machineId}`, "MACHINE_UNAVAILABLE");
|
|
112
112
|
const capability = machine.capabilities.daemonLifecycleV1;
|
|
113
113
|
const expectedInstanceGeneration = machine.capabilities.daemonInstanceGeneration;
|
|
114
114
|
const supported = capability !== undefined &&
|
|
@@ -181,9 +181,13 @@ async function daemonOverview(client, input) {
|
|
|
181
181
|
const machines = input.machineId ? snapshot.machines.filter((machine) => machine.id === input.machineId) : snapshot.machines;
|
|
182
182
|
const machineIds = new Set(machines.map((machine) => machine.id));
|
|
183
183
|
const allSessions = snapshot.sessions.filter((session) => machineIds.has(session.machineId));
|
|
184
|
-
const counts = {};
|
|
185
|
-
for (const session of allSessions)
|
|
186
|
-
|
|
184
|
+
const counts = { supervision: {}, execution: {} };
|
|
185
|
+
for (const session of allSessions) {
|
|
186
|
+
const supervision = session.supervision.status;
|
|
187
|
+
const execution = session.execution.status;
|
|
188
|
+
counts.supervision[supervision] = (counts.supervision[supervision] ?? 0) + 1;
|
|
189
|
+
counts.execution[execution] = (counts.execution[execution] ?? 0) + 1;
|
|
190
|
+
}
|
|
187
191
|
const sessions = input.includeSessions ? (input.all ? allSessions : allSessions.slice(0, input.limit)) : [];
|
|
188
192
|
return {
|
|
189
193
|
machines,
|
|
@@ -437,8 +441,9 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
437
441
|
capabilities: session.capabilities,
|
|
438
442
|
})) : [];
|
|
439
443
|
const sessionCount = overview.page.total;
|
|
440
|
-
const
|
|
441
|
-
const
|
|
444
|
+
const activeExecutionCount = overview.counts.execution.active ?? 0;
|
|
445
|
+
const unresolvedExecutionCount = (overview.counts.execution.unknown ?? 0) +
|
|
446
|
+
(overview.counts.execution["legacy-unresolved"] ?? 0);
|
|
442
447
|
const data = {
|
|
443
448
|
configPath,
|
|
444
449
|
local,
|
|
@@ -448,7 +453,9 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
448
453
|
machines: includeSessions ? projectedMachines.map((machine) => ({
|
|
449
454
|
machineId: machine.id,
|
|
450
455
|
name: machine.metadata.name ?? machine.name,
|
|
451
|
-
|
|
456
|
+
available: machineIsAvailable(machine),
|
|
457
|
+
daemon: machine.daemon,
|
|
458
|
+
connection: machine.connection,
|
|
452
459
|
lastSeen: machine.lastSeen,
|
|
453
460
|
daemonVersion: machine.capabilities.daemonVersion,
|
|
454
461
|
defaultCwd: machine.defaultCwd,
|
|
@@ -459,17 +466,20 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
459
466
|
})) : machines.map((machine) => ({
|
|
460
467
|
machineId: machine.id,
|
|
461
468
|
name: machine.name,
|
|
462
|
-
|
|
469
|
+
available: machineIsAvailable(machine),
|
|
470
|
+
daemon: machine.daemon,
|
|
471
|
+
connection: machine.connection,
|
|
463
472
|
lastSeen: machine.lastSeen,
|
|
464
473
|
})),
|
|
465
474
|
...(includeSessions ? { sessions: projectedSessions, page: overview.page } : {}),
|
|
466
475
|
summary: {
|
|
467
476
|
machineCount: machines.length,
|
|
468
|
-
|
|
477
|
+
availableMachineCount: machines.filter(machineIsAvailable).length,
|
|
469
478
|
sessionCount,
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
479
|
+
activeExecutionCount,
|
|
480
|
+
unresolvedExecutionCount,
|
|
481
|
+
supervisionCounts: overview.counts.supervision,
|
|
482
|
+
executionCounts: overview.counts.execution,
|
|
473
483
|
...(includeSessions ? {
|
|
474
484
|
blockedSessionCount: projectedSessions.filter((session) => session.orchestration.blocked).length,
|
|
475
485
|
activeSessionCount: projectedSessions.filter((session) => session.orchestration.state === "active").length,
|
|
@@ -481,10 +491,10 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
481
491
|
if (!wantsJson(flags)) {
|
|
482
492
|
console.log(`Account: ${data.relayReachable ? "connected" : "unavailable"}`);
|
|
483
493
|
console.log(data.local.running ? `Local daemon: running${data.local.pid ? ` pid ${data.local.pid}` : ""}` : "Local daemon: stopped");
|
|
484
|
-
console.log(`Daemons: ${data.summary.
|
|
485
|
-
console.log(`Sessions: ${data.summary.sessionCount} total, ${data.summary.
|
|
494
|
+
console.log(`Daemons: ${data.summary.availableMachineCount}/${data.summary.machineCount} available`);
|
|
495
|
+
console.log(`Sessions: ${data.summary.sessionCount} total, ${data.summary.activeExecutionCount} active execution, ${data.summary.unresolvedExecutionCount} unresolved`);
|
|
486
496
|
for (const machine of data.machines) {
|
|
487
|
-
console.log(`- ${machine.name} (${machine.machineId}) ${machine
|
|
497
|
+
console.log(`- ${machine.name} (${machine.machineId}) ${"daemon" in machine && "connection" in machine ? machineAvailabilityLabel(machine) : "unavailable"}`);
|
|
488
498
|
if ("defaultCwd" in machine)
|
|
489
499
|
console.log(` cwd: ${machine.defaultCwd ?? "-"}`);
|
|
490
500
|
}
|
|
@@ -30,10 +30,10 @@ export async function handleGateway({ domain, action, positional, flags }) {
|
|
|
30
30
|
statePath: gatewayStatePath,
|
|
31
31
|
});
|
|
32
32
|
if (!wantsJson(flags)) {
|
|
33
|
-
console.log(`Gateway store
|
|
33
|
+
console.log(`Gateway store: ${gatewayStoreInspectionLabel(inspection)}`);
|
|
34
34
|
console.log(`artifact digest: ${inspection.artifactDigest}`);
|
|
35
35
|
for (const [name, file] of Object.entries(inspection.files)) {
|
|
36
|
-
console.log(`${name}: ${file.status}`);
|
|
36
|
+
console.log(`${name}: ${file.status}; contents ${file.contentValidation.status}${file.contentValidation.errorCode ? ` (${file.contentValidation.errorCode})` : ""}`);
|
|
37
37
|
}
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
@@ -681,8 +681,12 @@ async function handleRemoteGateway(action, positional, flags, machineId) {
|
|
|
681
681
|
if (action === "store" && positional[0] === "inspect") {
|
|
682
682
|
const result = await client.inspectGatewayStore(machineId);
|
|
683
683
|
if (!wantsJson(flags)) {
|
|
684
|
-
console.log(`Gateway store on Daemon ${machineId}: ${result
|
|
684
|
+
console.log(`Gateway store on Daemon ${machineId}: ${gatewayStoreInspectionLabel(result)}`);
|
|
685
685
|
console.log(`artifact digest: ${result.artifactDigest}`);
|
|
686
|
+
for (const [name, file] of Object.entries(result.files)) {
|
|
687
|
+
const content = file.contentValidation;
|
|
688
|
+
console.log(`${name}: ${file.status}; contents ${content?.status ?? "status unavailable"}${content?.errorCode ? ` (${content.errorCode})` : ""}`);
|
|
689
|
+
}
|
|
686
690
|
}
|
|
687
691
|
else
|
|
688
692
|
ok("gateway.store.inspect", { machineId, ...result }, { machineId });
|
|
@@ -1188,6 +1192,21 @@ function stringFlag(flags, key) {
|
|
|
1188
1192
|
const value = flags[key];
|
|
1189
1193
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1190
1194
|
}
|
|
1195
|
+
function gatewayStoreInspectionLabel(inspection) {
|
|
1196
|
+
if (inspection.repairRequired)
|
|
1197
|
+
return "repair required";
|
|
1198
|
+
const validations = Object.values(inspection.files).map((file) => file.contentValidation?.status);
|
|
1199
|
+
if (validations.some((status) => status === "invalid"))
|
|
1200
|
+
return "content invalid";
|
|
1201
|
+
if (validations.some((status) => status === "unsupported"))
|
|
1202
|
+
return "content version unsupported";
|
|
1203
|
+
if (validations.some((status) => status === "unreadable"))
|
|
1204
|
+
return "content unreadable";
|
|
1205
|
+
// An older Daemon cannot prove the store content it did not inspect.
|
|
1206
|
+
if (validations.some((status) => status === undefined))
|
|
1207
|
+
return "content status unavailable";
|
|
1208
|
+
return "healthy";
|
|
1209
|
+
}
|
|
1191
1210
|
function isRecord(value) {
|
|
1192
1211
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1193
1212
|
}
|
|
@@ -97,7 +97,7 @@ const valueFlags = new Set([
|
|
|
97
97
|
"thought-level",
|
|
98
98
|
"thinking",
|
|
99
99
|
"wait-timeout-ms",
|
|
100
|
-
"
|
|
100
|
+
"inactivity-timeout",
|
|
101
101
|
"to",
|
|
102
102
|
"token",
|
|
103
103
|
"ttl",
|
|
@@ -110,6 +110,7 @@ const booleanFlags = new Set([
|
|
|
110
110
|
"all",
|
|
111
111
|
"allow-all-users",
|
|
112
112
|
"clear-default-chat",
|
|
113
|
+
"confirm-risk",
|
|
113
114
|
"close-source",
|
|
114
115
|
"daily",
|
|
115
116
|
"deny-all-users",
|
|
@@ -141,6 +142,7 @@ const booleanFlags = new Set([
|
|
|
141
142
|
"respond-all",
|
|
142
143
|
"summary",
|
|
143
144
|
"starred",
|
|
145
|
+
"strict",
|
|
144
146
|
"verbose",
|
|
145
147
|
"wait",
|
|
146
148
|
"yes",
|
|
@@ -47,7 +47,7 @@ type DaemonReadinessOptions = {
|
|
|
47
47
|
* before it is considered ready. This prevents an online snapshot left by
|
|
48
48
|
* the previous connection from satisfying a new start.
|
|
49
49
|
*/
|
|
50
|
-
baseline?: Pick<MachineSnapshot, "
|
|
50
|
+
baseline?: Pick<MachineSnapshot, "daemon" | "connection" | "lastSeen">;
|
|
51
51
|
expectedInstanceGeneration?: string;
|
|
52
52
|
requireFreshProjection?: boolean;
|
|
53
53
|
readLocalStatus?: typeof localDaemonStatus;
|
|
@@ -4,6 +4,7 @@ import { CliError } from "../../errors.js";
|
|
|
4
4
|
import { startDaemonTimeoutMs } from "./paths.js";
|
|
5
5
|
import { readDaemonConfig } from "./config.js";
|
|
6
6
|
import { assertLocalDaemonRuntimeMatchesConfig, assertLocalDaemonVerified, inspectLocalDaemonRuntime, localDaemonStatus, readLocalDaemonPidIdentity, spawnDaemonStartTracked, stopLocalDaemon, stopSpawnedDaemon, } from "./local-daemon.js";
|
|
7
|
+
import { machineIsAvailable } from "./machine-state.js";
|
|
7
8
|
const committedConfigMayBeInUseErrors = new WeakSet();
|
|
8
9
|
export function startFailureMayUseCommittedConfig(error) {
|
|
9
10
|
return Boolean(error && typeof error === "object" && committedConfigMayBeInUseErrors.has(error));
|
|
@@ -331,7 +332,7 @@ export async function waitForDaemonReadiness(client, machineId, options = {}) {
|
|
|
331
332
|
continue;
|
|
332
333
|
}
|
|
333
334
|
machine = snapshot.machines.find((candidate) => candidate.id === machineId);
|
|
334
|
-
if (machine
|
|
335
|
+
if (machine && machineIsAvailable(machine) && projectionIsFresh(machine, options)) {
|
|
335
336
|
// Close the process/projection TOCTOU window: the relay may still expose
|
|
336
337
|
// a fresh online row for a daemon that exited while snapshot() was in
|
|
337
338
|
// flight. Re-verify the local owner before declaring startup complete.
|
|
@@ -484,7 +485,7 @@ function projectionIsFresh(machine, options) {
|
|
|
484
485
|
return true;
|
|
485
486
|
if (!options.baseline)
|
|
486
487
|
return true;
|
|
487
|
-
if (options.baseline
|
|
488
|
+
if (!machineIsAvailable(options.baseline))
|
|
488
489
|
return true;
|
|
489
490
|
return machine.lastSeen > options.baseline.lastSeen;
|
|
490
491
|
}
|
|
@@ -9,6 +9,7 @@ import { CliError } from "../../errors.js";
|
|
|
9
9
|
import { ok } from "./json.js";
|
|
10
10
|
import { assertLocalDaemonVerified, localDaemonStatus, stopSpawnedDaemon, } from "./local-daemon.js";
|
|
11
11
|
import { withAccountConfigTransaction } from "./config.js";
|
|
12
|
+
import { machineIsAvailable } from "./machine-state.js";
|
|
12
13
|
import { controllerBaseUrl } from "./bootstrap-config.js";
|
|
13
14
|
import { readDaemonProjectionBaseline, restartDaemonForBootstrap, waitForDaemonReadiness, } from "./bootstrap-daemon.js";
|
|
14
15
|
import { openUrl } from "./bootstrap-output.js";
|
|
@@ -232,7 +233,7 @@ export async function startDeviceRecoveryBootstrap(params) {
|
|
|
232
233
|
const daemonDisplayName = daemon.name && daemon.name !== daemonConfig.machineId
|
|
233
234
|
? daemon.name
|
|
234
235
|
: daemonConfig.machineName;
|
|
235
|
-
console.log(`Daemon ${daemonDisplayName} is ${daemon.online ? "
|
|
236
|
+
console.log(`Daemon ${daemonDisplayName} is ${daemon.online ? "available" : "connected; needs attention"}.`);
|
|
236
237
|
if (params.flags["no-open"] === true) {
|
|
237
238
|
console.log("");
|
|
238
239
|
console.log("Open Happy Elves:");
|
|
@@ -301,7 +302,7 @@ async function ensureRecoveredDaemonReady(params) {
|
|
|
301
302
|
timeoutMs: Math.min(startDaemonTimeoutMs, 5_000),
|
|
302
303
|
});
|
|
303
304
|
return {
|
|
304
|
-
online: machine
|
|
305
|
+
online: machineIsAvailable(machine),
|
|
305
306
|
name: machine?.name ?? params.daemonConfig.machineName,
|
|
306
307
|
};
|
|
307
308
|
}
|
|
@@ -346,7 +347,7 @@ async function ensureRecoveredDaemonReady(params) {
|
|
|
346
347
|
requireFreshProjection: started.started || started.restarted,
|
|
347
348
|
timeoutMs: startDaemonTimeoutMs,
|
|
348
349
|
});
|
|
349
|
-
if (readiness.phase !== "relay-authenticated" || !readiness.machine
|
|
350
|
+
if (readiness.phase !== "relay-authenticated" || !machineIsAvailable(readiness.machine)) {
|
|
350
351
|
throw new CliError("The Daemon did not authenticate before the startup deadline.", "DAEMON_ONLINE_TIMEOUT");
|
|
351
352
|
}
|
|
352
353
|
const confirmedDaemonConfig = await transaction.readDaemon();
|
|
@@ -18,7 +18,7 @@ export function writeHumanStartOutput(data) {
|
|
|
18
18
|
console.log("This one-time link expires in 2 minutes.");
|
|
19
19
|
console.log("");
|
|
20
20
|
console.log("Daemon");
|
|
21
|
-
console.log(`${data.daemon.
|
|
21
|
+
console.log(`${data.daemon.status}, ${data.daemon.name}${data.daemon.pid ? ` (pid ${data.daemon.pid})` : ""}`);
|
|
22
22
|
}
|
|
23
23
|
export function openUrl(url, options = {}) {
|
|
24
24
|
try {
|
|
@@ -14,6 +14,7 @@ import { readDaemonProjectionBaseline, restartDaemonForBootstrap, startFailureMa
|
|
|
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";
|
|
17
|
+
import { machineIsAvailable } from "./machine-state.js";
|
|
17
18
|
import { clearFreshStartIntent, freshStartIntentMatchesConfigs, loadOrCreateFreshStartIntent, readFreshStartIntent, } from "./fresh-start-intent.js";
|
|
18
19
|
export async function startBootstrap(flags) {
|
|
19
20
|
if (typeof flags.join === "string") {
|
|
@@ -208,7 +209,7 @@ export async function startBootstrap(flags) {
|
|
|
208
209
|
if (readiness.phase === "process-exited" || readiness.phase === "owner-changed") {
|
|
209
210
|
throw new CliError("Daemon exited before it authenticated with the relay. Run happy-elves doctor and happy-elves daemon logs.", "DAEMON_START_FAILED");
|
|
210
211
|
}
|
|
211
|
-
if (readiness.phase !== "relay-authenticated" || !machine
|
|
212
|
+
if (readiness.phase !== "relay-authenticated" || !machineIsAvailable(machine)) {
|
|
212
213
|
const relayDetail = readiness.lastRelayErrorCode ? ` Last relay error: ${readiness.lastRelayErrorCode}.` : "";
|
|
213
214
|
throw new CliError(`Daemon is running, but it did not authenticate with the relay before the startup deadline.${relayDetail} Run happy-elves doctor and happy-elves daemon logs.`, "DAEMON_ONLINE_TIMEOUT");
|
|
214
215
|
}
|
|
@@ -307,13 +308,15 @@ export async function startBootstrap(flags) {
|
|
|
307
308
|
machine: {
|
|
308
309
|
id: daemonState.config.machineId,
|
|
309
310
|
name: machine?.name ?? daemonState.config.machineName,
|
|
310
|
-
|
|
311
|
+
available: machineIsAvailable(machine),
|
|
312
|
+
daemon: machine?.daemon,
|
|
313
|
+
connection: machine?.connection,
|
|
311
314
|
lastSeen: machine?.lastSeen,
|
|
312
315
|
},
|
|
313
316
|
daemon: {
|
|
314
317
|
status: daemonNeedsAttention
|
|
315
318
|
? "needs-attention"
|
|
316
|
-
: daemon.local.running ? "
|
|
319
|
+
: daemon.local.running ? "running" : "starting",
|
|
317
320
|
pid: daemon.local.pid ?? daemon.startedPid,
|
|
318
321
|
pidPath: daemon.local.pidPath,
|
|
319
322
|
started: daemon.started,
|
|
@@ -346,7 +349,7 @@ export async function startBootstrap(flags) {
|
|
|
346
349
|
openError: openResult.error,
|
|
347
350
|
daemon: {
|
|
348
351
|
name: data.machine.name,
|
|
349
|
-
|
|
352
|
+
status: data.machine.available ? "available" : data.machine.connection?.status ?? "starting",
|
|
350
353
|
pid: data.daemon.pid,
|
|
351
354
|
},
|
|
352
355
|
});
|
|
@@ -17,6 +17,7 @@ export * from "./doctor.js";
|
|
|
17
17
|
export * from "./exit.js";
|
|
18
18
|
export * from "./json.js";
|
|
19
19
|
export * from "./local-daemon.js";
|
|
20
|
+
export * from "./machine-state.js";
|
|
20
21
|
export * from "./orchestrator.js";
|
|
21
22
|
export * from "./pending-enrollment.js";
|
|
22
23
|
export * from "./paths.js";
|
|
@@ -17,6 +17,7 @@ export * from "./doctor.js";
|
|
|
17
17
|
export * from "./exit.js";
|
|
18
18
|
export * from "./json.js";
|
|
19
19
|
export * from "./local-daemon.js";
|
|
20
|
+
export * from "./machine-state.js";
|
|
20
21
|
export * from "./orchestrator.js";
|
|
21
22
|
export * from "./pending-enrollment.js";
|
|
22
23
|
export * from "./paths.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { MachineSnapshot } from "../../../../../packages/shared/dist/index.js";
|
|
2
|
+
export declare function machineIsAvailable(machine: Pick<MachineSnapshot, "daemon" | "connection"> | undefined): boolean;
|
|
3
|
+
export declare function machineAvailabilityLabel(machine: Pick<MachineSnapshot, "daemon" | "connection">): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function machineIsAvailable(machine) {
|
|
2
|
+
return machine?.daemon.status === "running" && machine.connection.status === "connected";
|
|
3
|
+
}
|
|
4
|
+
export function machineAvailabilityLabel(machine) {
|
|
5
|
+
if (machine.daemon.status === "stopped")
|
|
6
|
+
return "daemon-stopped";
|
|
7
|
+
if (machine.daemon.status === "unknown")
|
|
8
|
+
return "daemon-unknown";
|
|
9
|
+
if (machine.connection.status === "reconnecting")
|
|
10
|
+
return "reconnecting";
|
|
11
|
+
if (machine.connection.status === "disconnected")
|
|
12
|
+
return "disconnected";
|
|
13
|
+
return "available";
|
|
14
|
+
}
|
|
@@ -32,7 +32,7 @@ export declare const orchestratorRequestMetadataCapacity = 256;
|
|
|
32
32
|
export declare function promptSha256(text: string): string;
|
|
33
33
|
export declare function deterministicTurnId(threadId: string, requestId: string): string;
|
|
34
34
|
export declare function deterministicRelayRequestId(threadId: string, requestId: string): string;
|
|
35
|
-
export declare function normalizeOrchestratorStatus(session: Pick<SessionSnapshot, "status">, orchestration: OrchestrationSummary,
|
|
35
|
+
export declare function normalizeOrchestratorStatus(session: Pick<SessionSnapshot, "status" | "daemon" | "connection" | "supervision" | "execution">, orchestration: OrchestrationSummary, _latestTurn?: Pick<TurnSummary, "status">): OrchestratorStatus;
|
|
36
36
|
export declare function normalizeOrchestratorTag(value: string): string;
|
|
37
37
|
export declare function normalizeOrchestratorTags(values: readonly string[]): string[];
|
|
38
38
|
export declare function readOrchestratorMetadata(metadata: Record<string, unknown>): OrchestratorMetadata;
|
|
@@ -16,25 +16,28 @@ export function deterministicRelayRequestId(threadId, requestId) {
|
|
|
16
16
|
const digest = createHash("sha256").update("orchestrator").update("\0").update(threadId).update("\0").update(requestId).digest("base64url");
|
|
17
17
|
return `req_${digest.slice(0, 28)}`;
|
|
18
18
|
}
|
|
19
|
-
export function normalizeOrchestratorStatus(session, orchestration,
|
|
19
|
+
export function normalizeOrchestratorStatus(session, orchestration, _latestTurn) {
|
|
20
20
|
if (session.status === "closed")
|
|
21
21
|
return "closed";
|
|
22
|
-
if (session.status === "offline")
|
|
23
|
-
return "offline";
|
|
24
22
|
if (orchestration.needsUser)
|
|
25
23
|
return "needsInput";
|
|
26
|
-
if (
|
|
24
|
+
if (session.supervision.status === "failed" ||
|
|
25
|
+
session.execution.status === "unknown" ||
|
|
26
|
+
session.execution.status === "legacy-unresolved")
|
|
27
27
|
return "blocked";
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
if (session.execution.status === "active" ||
|
|
29
|
+
session.supervision.status === "watching" ||
|
|
30
|
+
session.supervision.status === "reconciling" ||
|
|
31
|
+
session.supervision.status === "settling")
|
|
31
32
|
return "inProgress";
|
|
32
|
-
if (
|
|
33
|
+
if (session.execution.status === "failed")
|
|
33
34
|
return "failed";
|
|
34
|
-
if (
|
|
35
|
+
if (session.execution.status === "cancelled")
|
|
35
36
|
return "cancelled";
|
|
36
|
-
if (
|
|
37
|
+
if (session.execution.status === "completed")
|
|
37
38
|
return "completed";
|
|
39
|
+
if (session.daemon.status !== "running" || session.connection.status !== "connected")
|
|
40
|
+
return "offline";
|
|
38
41
|
return "idle";
|
|
39
42
|
}
|
|
40
43
|
export function normalizeOrchestratorTag(value) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ControllerClient, type DecodedSessionEvent } from "../../../../../packages/client/dist/index.js";
|
|
2
|
+
import type { TurnSnapshotV2 } from "../../../../../packages/shared/dist/index.js";
|
|
2
3
|
export declare function compactText(value: string, max?: number): string;
|
|
3
4
|
export declare function lastAssistantOutput(events: DecodedSessionEvent[]): string;
|
|
4
5
|
export declare function compareSessionEventLogicalOrder(left: DecodedSessionEvent, right: DecodedSessionEvent): number;
|
|
@@ -31,6 +32,12 @@ export type TurnSummary = {
|
|
|
31
32
|
lastEventId: number;
|
|
32
33
|
eventCount: number;
|
|
33
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Keep transcript content for display, but derive settlement exclusively from
|
|
37
|
+
* the immutable turn record. A plain/backfilled `done` event is not proof of
|
|
38
|
+
* provider completion and therefore remains projected as running.
|
|
39
|
+
*/
|
|
40
|
+
export declare function applyAuthoritativeTurnSnapshot(summary: TurnSummary | undefined, turn: TurnSnapshotV2 | undefined, requestedTurnId: string): TurnSummary | undefined;
|
|
34
41
|
export type StructuredCommandOutput = {
|
|
35
42
|
state: string;
|
|
36
43
|
sessionId: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { authoritativeTerminalTurnStatus, } from "../../../../../packages/client/dist/index.js";
|
|
3
4
|
import { CliError } from "../../errors.js";
|
|
4
5
|
import { outputRoot } from "./paths.js";
|
|
5
6
|
function eventText(event) {
|
|
@@ -86,6 +87,40 @@ export function displayPath(filePath) {
|
|
|
86
87
|
const relative = path.relative(process.cwd(), filePath);
|
|
87
88
|
return relative.startsWith("..") ? filePath : relative || ".";
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Keep transcript content for display, but derive settlement exclusively from
|
|
92
|
+
* the immutable turn record. A plain/backfilled `done` event is not proof of
|
|
93
|
+
* provider completion and therefore remains projected as running.
|
|
94
|
+
*/
|
|
95
|
+
export function applyAuthoritativeTurnSnapshot(summary, turn, requestedTurnId) {
|
|
96
|
+
if (!summary && !turn)
|
|
97
|
+
return undefined;
|
|
98
|
+
const terminalStatus = authoritativeTerminalTurnStatus(turn);
|
|
99
|
+
if (summary) {
|
|
100
|
+
return {
|
|
101
|
+
...summary,
|
|
102
|
+
status: terminalStatus ?? "running",
|
|
103
|
+
completedAt: terminalStatus === undefined ? undefined : (summary.completedAt ?? turn?.updatedAt),
|
|
104
|
+
updatedAt: Math.max(summary.updatedAt, turn?.updatedAt ?? 0),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (!turn)
|
|
108
|
+
return undefined;
|
|
109
|
+
return {
|
|
110
|
+
turnId: turn.turnId,
|
|
111
|
+
...(requestedTurnId === turn.turnId ? {} : { runtimeTurnId: requestedTurnId }),
|
|
112
|
+
status: terminalStatus ?? "running",
|
|
113
|
+
prompt: "",
|
|
114
|
+
output: "",
|
|
115
|
+
terminalText: "",
|
|
116
|
+
startedAt: turn.createdAt,
|
|
117
|
+
...(terminalStatus === undefined ? {} : { completedAt: turn.updatedAt }),
|
|
118
|
+
updatedAt: turn.updatedAt,
|
|
119
|
+
firstEventId: 0,
|
|
120
|
+
lastEventId: 0,
|
|
121
|
+
eventCount: 0,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
89
124
|
function terminalTextForDone(events, turnId) {
|
|
90
125
|
const done = [...events].reverse().find((event) => event.turnId === turnId && event.decoded?.type === "done")?.decoded;
|
|
91
126
|
if (done?.type !== "done")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ControllerClient, DecodedSessionEvent } from "../../../../../packages/client/dist/index.js";
|
|
2
2
|
import type { MachineSnapshot, SessionSnapshot } from "../../../../../packages/shared/dist/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { MachineMetadata, OrchestrationSummary, OrchestrationState } from "./types.js";
|
|
4
4
|
import { type TurnSummary } from "./session-output.js";
|
|
5
5
|
export declare function showMachine(client: ControllerClient, machine: MachineSnapshot): Promise<MachineSnapshot & {
|
|
6
6
|
defaultCwd?: string;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { CliError } from "../../errors.js";
|
|
3
|
-
import { idleSessionStatuses } from "./types.js";
|
|
4
3
|
import { summarizeTurns } from "./session-output.js";
|
|
5
4
|
export async function showMachine(client, machine) {
|
|
6
5
|
const metadata = (await client.decodeMachineMetadata(machine));
|
|
@@ -25,26 +24,42 @@ function eventNeedsUser(event) {
|
|
|
25
24
|
return true;
|
|
26
25
|
return false;
|
|
27
26
|
}
|
|
28
|
-
function
|
|
29
|
-
if (status === "
|
|
30
|
-
return "
|
|
31
|
-
if (status === "
|
|
27
|
+
function orchestrationStateFromSnapshot(session) {
|
|
28
|
+
if (session.status === "closed")
|
|
29
|
+
return "closed";
|
|
30
|
+
if (session.supervision.status === "failed")
|
|
31
|
+
return "blocked";
|
|
32
|
+
if (session.execution.status === "unknown" || session.execution.status === "legacy-unresolved")
|
|
33
|
+
return "blocked";
|
|
34
|
+
if (session.execution.status === "failed")
|
|
35
|
+
return "blocked";
|
|
36
|
+
if (session.execution.status === "active" || session.supervision.status === "watching")
|
|
37
|
+
return "active";
|
|
38
|
+
if (session.supervision.status === "reconciling" || session.supervision.status === "settling")
|
|
32
39
|
return "active";
|
|
33
|
-
if (status === "
|
|
34
|
-
return "idle";
|
|
35
|
-
if (status === "completed")
|
|
40
|
+
if (session.execution.status === "completed")
|
|
36
41
|
return "completed";
|
|
37
|
-
if (status === "
|
|
38
|
-
return "
|
|
39
|
-
return "
|
|
42
|
+
if (session.status === "new" && session.execution.status === "idle")
|
|
43
|
+
return "pending";
|
|
44
|
+
return "idle";
|
|
45
|
+
}
|
|
46
|
+
function canTrustVisibleRunningTurn(session) {
|
|
47
|
+
return session.status !== "closed" &&
|
|
48
|
+
(session.execution.status === "active" || session.supervision.status === "watching");
|
|
40
49
|
}
|
|
41
|
-
function
|
|
42
|
-
return
|
|
50
|
+
function operationalProjection(session) {
|
|
51
|
+
return {
|
|
52
|
+
daemon: session.daemon.status,
|
|
53
|
+
connection: session.connection.status,
|
|
54
|
+
supervision: session.supervision.status,
|
|
55
|
+
execution: session.execution.status,
|
|
56
|
+
rawStatus: session.status,
|
|
57
|
+
};
|
|
43
58
|
}
|
|
44
59
|
function visibleRunningTurnSummary(session, turnId) {
|
|
45
60
|
return {
|
|
46
61
|
state: "active",
|
|
47
|
-
|
|
62
|
+
...operationalProjection(session),
|
|
48
63
|
needsUser: false,
|
|
49
64
|
blocked: false,
|
|
50
65
|
completed: false,
|
|
@@ -71,35 +86,16 @@ export function deriveOrchestration(session, events = [], focusTurn) {
|
|
|
71
86
|
if (needsUserEvent && latestTerminalTurn && latestTerminalTurn.status !== "completed") {
|
|
72
87
|
return needsUserSummary(session, needsUserEvent);
|
|
73
88
|
}
|
|
74
|
-
if (latestTurn?.status === "running" && canTrustVisibleRunningTurn(session
|
|
89
|
+
if (latestTurn?.status === "running" && canTrustVisibleRunningTurn(session)) {
|
|
75
90
|
return visibleRunningTurnSummary(session, latestTurn.turnId);
|
|
76
91
|
}
|
|
77
|
-
if (focusTurn?.status === "running" && canTrustVisibleRunningTurn(session
|
|
92
|
+
if (focusTurn?.status === "running" && canTrustVisibleRunningTurn(session)) {
|
|
78
93
|
return visibleRunningTurnSummary(session, focusTurn.turnId);
|
|
79
94
|
}
|
|
80
|
-
|
|
81
|
-
const state = orchestrationStateFromStatus(session.status);
|
|
82
|
-
return {
|
|
83
|
-
state,
|
|
84
|
-
rawStatus: session.status,
|
|
85
|
-
needsUser: false,
|
|
86
|
-
blocked: state === "blocked",
|
|
87
|
-
completed: state === "completed",
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if (activeTurn) {
|
|
91
|
-
return {
|
|
92
|
-
state: "active",
|
|
93
|
-
rawStatus: session.status,
|
|
94
|
-
needsUser: false,
|
|
95
|
-
blocked: false,
|
|
96
|
-
completed: false,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
const state = orchestrationStateFromStatus(session.status);
|
|
95
|
+
const state = orchestrationStateFromSnapshot(session);
|
|
100
96
|
return {
|
|
101
97
|
state,
|
|
102
|
-
|
|
98
|
+
...operationalProjection(session),
|
|
103
99
|
needsUser: false,
|
|
104
100
|
blocked: state === "blocked",
|
|
105
101
|
completed: state === "completed",
|
|
@@ -114,7 +110,7 @@ function needsUserSummary(session, event) {
|
|
|
114
110
|
: undefined;
|
|
115
111
|
return {
|
|
116
112
|
state: "needs-user",
|
|
117
|
-
|
|
113
|
+
...operationalProjection(session),
|
|
118
114
|
needsUser: true,
|
|
119
115
|
blocked: true,
|
|
120
116
|
completed: false,
|
|
@@ -202,17 +198,19 @@ export async function machineSessionOverview(client, sessions, machineId) {
|
|
|
202
198
|
lastTurnId: session.lastTurnId,
|
|
203
199
|
};
|
|
204
200
|
}));
|
|
201
|
+
const openSessions = projectedSessions.filter((session) => session.status !== "closed");
|
|
202
|
+
const settledSessions = openSessions.filter((session) => session.orchestration.supervision === "idle");
|
|
205
203
|
return {
|
|
206
204
|
summary: {
|
|
207
205
|
sessionCount: machineSessions.length,
|
|
208
|
-
runningSessionCount:
|
|
209
|
-
idleSessionCount:
|
|
206
|
+
runningSessionCount: openSessions.filter((session) => session.orchestration.execution === "active").length,
|
|
207
|
+
idleSessionCount: settledSessions.filter((session) => session.status !== "new" && ["idle", "completed", "cancelled"].includes(session.orchestration.execution)).length,
|
|
210
208
|
blockedSessionCount: projectedSessions.filter((session) => session.orchestration.blocked).length,
|
|
211
|
-
readySessionCount:
|
|
209
|
+
readySessionCount: settledSessions.filter((session) => session.status !== "new" && session.orchestration.execution === "idle").length,
|
|
212
210
|
completedSessionCount: projectedSessions.filter((session) => session.orchestration.completed).length,
|
|
213
|
-
failedSessionCount:
|
|
214
|
-
cancelledSessionCount:
|
|
215
|
-
offlineSessionCount:
|
|
211
|
+
failedSessionCount: openSessions.filter((session) => session.orchestration.execution === "failed").length,
|
|
212
|
+
cancelledSessionCount: openSessions.filter((session) => session.orchestration.execution === "cancelled").length,
|
|
213
|
+
offlineSessionCount: openSessions.filter((session) => session.orchestration.daemon !== "running" || session.orchestration.connection !== "connected").length,
|
|
216
214
|
closedSessionCount: projectedSessions.filter((session) => session.status === "closed").length,
|
|
217
215
|
activeSessionCount: projectedSessions.filter((session) => session.orchestration.state === "active").length,
|
|
218
216
|
needsUserSessionCount: projectedSessions.filter((session) => session.orchestration.needsUser).length,
|