@cydm/happy-elves 0.1.0-beta.57 → 0.1.0-beta.59

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 (66) hide show
  1. package/apps/cli/dist/commands/lib/exit.js +2 -0
  2. package/apps/cli/dist/commands/lib/orchestrator.d.ts +5 -1
  3. package/apps/cli/dist/commands/lib/orchestrator.js +6 -0
  4. package/apps/cli/dist/commands/lib/session-output.js +1 -1
  5. package/apps/cli/dist/commands/lib/session-view.d.ts +2 -1
  6. package/apps/cli/dist/commands/lib/session-view.js +34 -23
  7. package/apps/cli/dist/commands/lib/usage.js +28 -7
  8. package/apps/cli/dist/commands/machine.js +29 -5
  9. package/apps/cli/dist/commands/orchestrator.js +381 -92
  10. package/apps/cli/dist/commands/session.js +28 -1
  11. package/apps/cli/dist/commands/skill.js +84 -2
  12. package/apps/cli/dist/commands/turn.js +1 -1
  13. package/apps/cli/dist/skills/registry.js +26 -7
  14. package/apps/daemon/dist/gateway/runtime.js +32 -7
  15. package/apps/daemon/dist/relay/connection-diagnostics.d.ts +30 -0
  16. package/apps/daemon/dist/relay/connection-diagnostics.js +60 -0
  17. package/apps/daemon/dist/relay/connection.js +65 -16
  18. package/apps/daemon/dist/relay/devtools.js +2 -0
  19. package/apps/daemon/dist/runtime/external-provider.js +3 -2
  20. package/apps/daemon/dist/runtime.js +1 -1
  21. package/apps/daemon/dist/session/lifecycle.d.ts +2 -1
  22. package/apps/daemon/dist/session/lifecycle.js +42 -7
  23. package/apps/daemon/dist/session/metadata.d.ts +2 -0
  24. package/apps/daemon/dist/session/metadata.js +49 -0
  25. package/apps/daemon/dist/session/primitives.js +23 -13
  26. package/apps/daemon/dist/session/prompt.js +11 -7
  27. package/apps/daemon/dist/types.d.ts +6 -0
  28. package/apps/daemon/package.json +1 -1
  29. package/apps/relay/dist/connections.d.ts +3 -0
  30. package/apps/relay/dist/controller-handlers.d.ts +2 -1
  31. package/apps/relay/dist/controller-handlers.js +119 -8
  32. package/apps/relay/dist/db.js +2 -0
  33. package/apps/relay/dist/http-routes.js +9 -3
  34. package/apps/relay/dist/http-schemas.d.ts +23 -0
  35. package/apps/relay/dist/http-schemas.js +2 -0
  36. package/apps/relay/dist/index.js +2 -1
  37. package/apps/relay/dist/machine-command-result-handlers.js +29 -11
  38. package/apps/relay/dist/metadata-source.d.ts +3 -0
  39. package/apps/relay/dist/metadata-source.js +16 -0
  40. package/apps/relay/dist/projection-repository.d.ts +0 -7
  41. package/apps/relay/dist/projection-repository.js +0 -6
  42. package/apps/relay/dist/projections.js +2 -1
  43. package/apps/relay/dist/relay-context.d.ts +6 -20
  44. package/apps/relay/dist/relay-context.js +11 -1
  45. package/apps/relay/dist/repositories.d.ts +3 -1
  46. package/apps/relay/dist/repositories.js +23 -7
  47. package/apps/relay/dist/session-projection-reducer.js +13 -18
  48. package/apps/relay/dist/types.d.ts +2 -0
  49. package/npm-shrinkwrap.json +532 -2
  50. package/package.json +2 -1
  51. package/packages/client/dist/client.d.ts +7 -2
  52. package/packages/client/dist/client.js +39 -4
  53. package/packages/client/dist/live.js +9 -1
  54. package/packages/client/dist/parsers.js +2 -0
  55. package/packages/client/dist/types.d.ts +3 -0
  56. package/packages/pie-provider/dist/index.js +3 -3
  57. package/packages/runtime/dist/index.d.ts +1 -0
  58. package/packages/runtime-acpx/dist/index.d.ts +2 -2
  59. package/packages/runtime-acpx/dist/index.js +1 -1
  60. package/packages/runtime-cli/dist/codex-protocol.js +1 -1
  61. package/packages/runtime-cli/dist/permissions.js +3 -3
  62. package/packages/shared/dist/protocol-schemas.d.ts +25 -0
  63. package/packages/shared/dist/protocol-schemas.js +18 -0
  64. package/packages/shared/dist/protocol-types.d.ts +18 -0
  65. package/packages/shared/dist/protocol.d.ts +22 -0
  66. package/packages/shared/dist/protocol.js +14 -0
@@ -127,6 +127,8 @@ export function handleCliError(error) {
127
127
  latestCompletedTurnId: enriched.latestCompletedTurnId,
128
128
  commandAcked: enriched.commandAcked,
129
129
  rawStatus: enriched.rawStatus,
130
+ lastTransientError: enriched.lastTransientError,
131
+ retryCount: enriched.retryCount,
130
132
  }, enriched.capability, enriched.retryable);
131
133
  process.exit(1);
132
134
  }
@@ -1,12 +1,16 @@
1
- import type { SessionSnapshot } from "../../../../../packages/shared/dist/index.js";
1
+ import type { PermissionMode, SessionSnapshot } from "../../../../../packages/shared/dist/index.js";
2
2
  import type { OrchestrationSummary } from "./types.js";
3
3
  import type { TurnSummary } from "./session-output.js";
4
4
  export type OrchestratorStatus = "idle" | "inProgress" | "completed" | "needsInput" | "blocked" | "failed" | "cancelled" | "offline" | "closed";
5
5
  export type OrchestratorRequestRecord = {
6
6
  turnId: string;
7
7
  promptSha256: string;
8
+ inputSha256?: string;
9
+ sourceOutputSha256?: string;
10
+ runtimeTurnId?: string;
8
11
  model?: string | null;
9
12
  thoughtLevel?: string | null;
13
+ permissionMode?: PermissionMode;
10
14
  createdAt: number;
11
15
  };
12
16
  export type OrchestratorMetadata = {
@@ -78,8 +78,14 @@ export function readOrchestratorMetadata(metadata) {
78
78
  requests[requestId] = {
79
79
  turnId: candidate.turnId,
80
80
  promptSha256: candidate.promptSha256,
81
+ ...(typeof candidate.inputSha256 === "string" ? { inputSha256: candidate.inputSha256 } : {}),
82
+ ...(typeof candidate.sourceOutputSha256 === "string" ? { sourceOutputSha256: candidate.sourceOutputSha256 } : {}),
83
+ ...(typeof candidate.runtimeTurnId === "string" ? { runtimeTurnId: candidate.runtimeTurnId } : {}),
81
84
  ...(candidate.model === null || typeof candidate.model === "string" ? { model: candidate.model } : {}),
82
85
  ...(candidate.thoughtLevel === null || typeof candidate.thoughtLevel === "string" ? { thoughtLevel: candidate.thoughtLevel } : {}),
86
+ ...(candidate.permissionMode === "approve-all" || candidate.permissionMode === "approve-reads" || candidate.permissionMode === "deny-all"
87
+ ? { permissionMode: candidate.permissionMode }
88
+ : {}),
83
89
  createdAt: candidate.createdAt,
84
90
  };
85
91
  }
@@ -234,7 +234,7 @@ export function summarizeTurns(events) {
234
234
  .map(({ order: _order, ...turn }) => turn);
235
235
  }
236
236
  export function structuredOutputForTurn(sessionId, turn) {
237
- const finalOutput = turn.output || turn.terminalText || undefined;
237
+ const finalOutput = turn.status !== "running" ? turn.output || turn.terminalText || undefined : undefined;
238
238
  return {
239
239
  state: turn.status,
240
240
  sessionId,
@@ -1,11 +1,12 @@
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
3
  import { type MachineMetadata, type OrchestrationSummary, type OrchestrationState } from "./types.js";
4
+ import { type TurnSummary } from "./session-output.js";
4
5
  export declare function showMachine(client: ControllerClient, machine: MachineSnapshot): Promise<MachineSnapshot & {
5
6
  defaultCwd?: string;
6
7
  metadata: MachineMetadata;
7
8
  }>;
8
- export declare function deriveOrchestration(session: SessionSnapshot, events?: DecodedSessionEvent[]): OrchestrationSummary;
9
+ export declare function deriveOrchestration(session: SessionSnapshot, events?: DecodedSessionEvent[], focusTurn?: Pick<TurnSummary, "turnId" | "status">): OrchestrationSummary;
9
10
  export declare function showSession(client: ControllerClient, session: SessionSnapshot, events?: DecodedSessionEvent[]): Promise<SessionSnapshot & {
10
11
  metadata: unknown;
11
12
  orchestration: OrchestrationSummary;
@@ -38,23 +38,44 @@ function orchestrationStateFromStatus(status) {
38
38
  return "blocked";
39
39
  return "closed";
40
40
  }
41
- export function deriveOrchestration(session, events = []) {
42
- const turns = summarizeTurns(events);
43
- const latestTurn = turns[0];
44
- const activeTurn = turns.find((turn) => turn.status === "running");
45
- if (session.status === "failed" && latestTurn?.status === "running") {
46
- return {
47
- state: "active",
48
- rawStatus: session.status,
49
- needsUser: false,
50
- blocked: false,
51
- completed: false,
41
+ function canTrustVisibleRunningTurn(status) {
42
+ return status !== "closed" && status !== "offline" && status !== "cancelled";
43
+ }
44
+ function visibleRunningTurnSummary(session, turnId) {
45
+ return {
46
+ state: "active",
47
+ rawStatus: session.status,
48
+ needsUser: false,
49
+ blocked: false,
50
+ completed: false,
51
+ ...(session.status !== "running" ? {
52
52
  statusConflict: {
53
53
  rawStatus: session.status,
54
54
  evidence: "running-turn",
55
- turnId: latestTurn.turnId,
55
+ turnId,
56
56
  },
57
- };
57
+ } : {}),
58
+ };
59
+ }
60
+ export function deriveOrchestration(session, events = [], focusTurn) {
61
+ const turns = summarizeTurns(events);
62
+ const latestTurn = turns[0];
63
+ const activeTurn = turns.find((turn) => turn.status === "running") ?? (focusTurn?.status === "running" ? focusTurn : undefined);
64
+ const latestTerminalTurn = turns.find((turn) => turn.status !== "running");
65
+ const relevantTurnId = activeTurn?.turnId ?? latestTerminalTurn?.turnId;
66
+ const needsUserEvent = relevantTurnId
67
+ ? [...events].reverse().find((event) => event.turnId === relevantTurnId && eventNeedsUser(event))
68
+ : undefined;
69
+ if (needsUserEvent && activeTurn)
70
+ return needsUserSummary(session, needsUserEvent);
71
+ if (needsUserEvent && latestTerminalTurn && latestTerminalTurn.status !== "completed") {
72
+ return needsUserSummary(session, needsUserEvent);
73
+ }
74
+ if (latestTurn?.status === "running" && canTrustVisibleRunningTurn(session.status)) {
75
+ return visibleRunningTurnSummary(session, latestTurn.turnId);
76
+ }
77
+ if (focusTurn?.status === "running" && canTrustVisibleRunningTurn(session.status)) {
78
+ return visibleRunningTurnSummary(session, focusTurn.turnId);
58
79
  }
59
80
  if (session.status !== "running") {
60
81
  const state = orchestrationStateFromStatus(session.status);
@@ -66,16 +87,6 @@ export function deriveOrchestration(session, events = []) {
66
87
  completed: state === "completed",
67
88
  };
68
89
  }
69
- const latestTerminalTurn = turns.find((turn) => turn.status !== "running");
70
- const relevantTurnId = activeTurn?.turnId ?? latestTerminalTurn?.turnId;
71
- const needsUserEvent = relevantTurnId
72
- ? [...events].reverse().find((event) => event.turnId === relevantTurnId && eventNeedsUser(event))
73
- : undefined;
74
- if (needsUserEvent && activeTurn)
75
- return needsUserSummary(session, needsUserEvent);
76
- if (needsUserEvent && latestTerminalTurn && latestTerminalTurn.status !== "completed") {
77
- return needsUserSummary(session, needsUserEvent);
78
- }
79
90
  if (activeTurn) {
80
91
  return {
81
92
  state: "active",
@@ -31,8 +31,10 @@ Check local controller config, relay reachability, and daemon state.
31
31
  happy-elves remote revoke <deviceId> --json
32
32
  `,
33
33
  machine: `Usage:
34
- happy-elves machine list [--verbose] [--json]
35
- happy-elves machine status <machineId> [--verbose] [--json]
34
+ happy-elves machine list [--include-archived] [--verbose] [--json]
35
+ happy-elves machine status <machineId> [--include-archived] [--verbose] [--json]
36
+ happy-elves machine archive <machineId> [--json]
37
+ happy-elves machine unarchive <machineId> [--json]
36
38
  happy-elves machine diagnose <machineId> [--json]
37
39
  happy-elves machine directory <machineId> [--path <path>] [--json]
38
40
  happy-elves machine exec <machineId> --shell <command> [--cwd <path>] [--timeout-ms <ms>] [--wait-timeout-ms <ms>] [--json]
@@ -58,6 +60,9 @@ Codex-like orchestration:
58
60
  returns a deterministic turnId for the supplied request id. wait-thread waits
59
61
  for that exact turn id instead of following the latest turn. handoff injects
60
62
  bounded source turn context directly into the target prompt.
63
+ Permission defaults to trusted-local Yolo (approve-all). Use --permission-mode
64
+ default/ask/accept-edits/approve-reads/deny-all/read-only only to tighten one
65
+ turn.
61
66
  `,
62
67
  session: `Usage:
63
68
  happy-elves session create --machine <machineId> [--agent codex] [--cwd <path>] [--name <name>] [--permission-mode <mode>] [--model <key>] [--thought-level <key>|--thinking <key>] --json
@@ -88,7 +93,7 @@ Session management:
88
93
  happy-elves turn list <sessionId> [--limit 10] [--verbose] [--json]
89
94
  happy-elves turn status <sessionId> <turnId> [--verbose] [--json]
90
95
  happy-elves turn result <sessionId> [<turnId>] [--verbose] [--json]
91
- happy-elves turn run <sessionId> (--text <prompt> | --text-file <path>) [--detach] [--timeout 1000s] [--runtime-timeout 1000s] [--permission-mode approve-reads|approve-all|deny-all] [--model <key>] [--thought-level <key>|--thinking <key>] [--json]
96
+ happy-elves turn run <sessionId> (--text <prompt> | --text-file <path>) [--detach] [--timeout 1000s] [--runtime-timeout 1000s] [--permission-mode <mode>] [--model <key>] [--thought-level <key>|--thinking <key>] [--json]
92
97
  happy-elves turn wait <sessionId> <turnId> [--timeout 1000s] --json
93
98
  happy-elves turn cancel <sessionId> <turnId> [--reason <reason>] --json
94
99
  happy-elves turn rewind <sessionId> <turn-or-checkpoint-id> --json
@@ -96,7 +101,9 @@ Session management:
96
101
 
97
102
  Turn management:
98
103
  run starts one agent execution. It waits by default; use --detach for
99
- background execution.
104
+ background execution. Permission defaults to trusted-local Yolo (approve-all);
105
+ pass --permission-mode default/ask/accept-edits/approve-reads/deny-all/read-only
106
+ to tighten.
100
107
  wait/result inspect a specific turn.
101
108
  rewind/fork operate on a turn or checkpoint id supplied by runtime history.
102
109
  `,
@@ -164,7 +171,7 @@ export function commandUsage(command, exitCode = 1) {
164
171
  export function usageText() {
165
172
  return `happy-elves
166
173
 
167
- Happy Elves is a remote controller and orchestration layer for coding agents across machines. The local agent remains the authority for runtime state. Agents should run happy-elves --skill before using this CLI.
174
+ Happy Elves is a remote controller and orchestration layer for coding agents across machines. The local agent remains the authority for runtime state. Agents should run happy-elves skill doctor --name happy-elves-orchestrator --json before orchestrating; happy-elves --skill remains legacy guidance.
168
175
 
169
176
  Core commands:
170
177
  machine list [--json]
@@ -248,6 +255,19 @@ Recommended agent workflow:
248
255
  5. orchestrator read-turn <threadId> <turnId> --json
249
256
  6. orchestrator handoff --from <sourceThreadId> --turn <sourceTurnId> --to <targetThreadId> --text <instruction> --request-id <stable-id> --json
250
257
 
258
+ Permission defaults:
259
+ Happy Elves is trusted-local by default. If orchestrator send-message omits
260
+ --permission-mode, the effective permission is approve-all. Pass
261
+ --permission-mode default/ask/accept-edits/approve-reads/deny-all/read-only
262
+ only when you intentionally want to tighten a turn. Inspect
263
+ effectivePermissionMode and permissionSource before diagnosing sandbox, git,
264
+ desktop, or backend permission failures.
265
+
266
+ Orchestrator idempotency:
267
+ Reusing the same request id with the same prompt/model/thinking/permission
268
+ returns the same turn. Reusing it with different prompt/model/thinking/
269
+ permission returns REQUEST_ID_CONFLICT.
270
+
251
271
  Concepts:
252
272
  Loaded session: a Happy Elves control-plane session that can be operated by
253
273
  this CLI.
@@ -282,8 +302,9 @@ Output modes:
282
302
  thread's history is unavailable, the item reports historyStatus/historyError
283
303
  instead of pretending to be healthy inProgress.
284
304
  send-message requires --request-id and is detached by default. Reusing the
285
- same request id with the same prompt/model/thinking returns the same turn;
286
- reusing it with different prompt/model/thinking returns REQUEST_ID_CONFLICT.
305
+ same request id with the same prompt/model/thinking/permission returns the
306
+ same turn; reusing it with different prompt/model/thinking/permission returns
307
+ REQUEST_ID_CONFLICT.
287
308
  After send-message, keep the orchestrator turn open and wait on the exact
288
309
  returned turn id until terminal. Do not stop after dispatching the prompt.
289
310
  For direct handoff, prefer handoff or send-message --context-turn. The CLI
@@ -6,6 +6,8 @@ function structuredMachineSummary(machine) {
6
6
  machineId: machine.id,
7
7
  name: machine.metadata.name ?? machine.name,
8
8
  online: machine.online,
9
+ archived: Boolean(machine.archivedAt),
10
+ archivedAt: machine.archivedAt,
9
11
  sessionCount: summary.sessionCount,
10
12
  runningCount: summary.runningSessionCount,
11
13
  blockedCount: summary.blockedSessionCount,
@@ -15,6 +17,8 @@ function structuredMachineVerbose(machine) {
15
17
  return {
16
18
  machineId: machine.id,
17
19
  online: machine.online,
20
+ archived: Boolean(machine.archivedAt),
21
+ archivedAt: machine.archivedAt,
18
22
  lastSeen: machine.lastSeen,
19
23
  name: machine.metadata.name ?? machine.name,
20
24
  defaultCwd: machine.defaultCwd,
@@ -28,14 +32,17 @@ function structuredMachineVerbose(machine) {
28
32
  function printMachineLine(machine) {
29
33
  const name = machine.metadata.name ?? machine.name;
30
34
  const summary = machine.summary;
31
- console.log(`${machine.id} ${machine.online ? "online" : "offline"} ${compactText(name, 32)} sessions:${summary.sessionCount} running:${summary.runningSessionCount} blocked:${summary.blockedSessionCount}`);
35
+ const state = machine.archivedAt ? "archived" : machine.online ? "online" : "offline";
36
+ console.log(`${machine.id} ${state} ${compactText(name, 32)} sessions:${summary.sessionCount} running:${summary.runningSessionCount} blocked:${summary.blockedSessionCount}`);
32
37
  }
33
38
  function printMachineDetails(machine) {
34
39
  const name = machine.metadata.name ?? machine.name;
35
40
  const agents = machine.capabilities.agents.map((agent) => agent.id).join(", ") || "-";
36
41
  const summary = machine.summary;
37
42
  console.log(`${name} (${machine.id})`);
38
- console.log(` status: ${machine.online ? "online" : "offline"} agents: ${agents}`);
43
+ console.log(` status: ${machine.archivedAt ? "archived" : machine.online ? "online" : "offline"} agents: ${agents}`);
44
+ if (machine.archivedAt)
45
+ console.log(` archived: ${new Date(machine.archivedAt).toISOString()}`);
39
46
  console.log(` cwd: ${machine.defaultCwd ?? "-"}`);
40
47
  console.log(` sessions: ${summary.sessionCount} total, ${summary.runningSessionCount} running, ${summary.readySessionCount} ready, ${summary.needsUserSessionCount} needs user`);
41
48
  console.log(` sessions: happy-elves session list --machine ${machine.id} --limit 20`);
@@ -171,7 +178,7 @@ export async function handleMachine({ domain, action, positional, flags }) {
171
178
  return true;
172
179
  }
173
180
  if (action === "list") {
174
- const snapshot = await client.snapshot();
181
+ const snapshot = await client.snapshot({ includeArchived: flags["include-archived"] === true });
175
182
  const machines = await Promise.all(snapshot.machines.map(async (machine) => ({
176
183
  ...(await showMachine(client, machine)),
177
184
  ...(await machineSessionOverview(client, snapshot.sessions, machine.id)),
@@ -192,9 +199,26 @@ export async function handleMachine({ domain, action, positional, flags }) {
192
199
  });
193
200
  return true;
194
201
  }
202
+ if (action === "archive" || action === "unarchive" || action === "restore") {
203
+ const machineId = requirePositional(positional[0], "machineId");
204
+ const machine = action === "archive"
205
+ ? await client.archiveMachine(machineId)
206
+ : await client.unarchiveMachine(machineId);
207
+ const projectedMachine = await showMachine(client, machine);
208
+ const overviewSnapshot = await client.snapshot({ includeArchived: true });
209
+ const overview = await machineSessionOverview(client, overviewSnapshot.sessions, machine.id);
210
+ if (!wantsJson(flags)) {
211
+ printMachineLine({ ...projectedMachine, ...overview });
212
+ return true;
213
+ }
214
+ ok(action === "archive" ? "machine.archive" : "machine.unarchive", {
215
+ machine: structuredMachineVerbose({ ...projectedMachine, ...overview }),
216
+ }, { machineId: machine.id });
217
+ return true;
218
+ }
195
219
  if (action === "status") {
196
220
  const machineId = requirePositional(positional[0], "machineId");
197
- const snapshot = await client.snapshot();
221
+ const snapshot = await client.snapshot({ includeArchived: flags["include-archived"] === true });
198
222
  const machine = snapshot.machines.find((item) => item.id === machineId);
199
223
  if (!machine)
200
224
  throw new CliError("Machine not found", "MACHINE_NOT_FOUND");
@@ -212,7 +236,7 @@ export async function handleMachine({ domain, action, positional, flags }) {
212
236
  machineId: machine.id,
213
237
  name: projectedMachine.metadata.name ?? machine.name,
214
238
  online: machine.online,
215
- state: machine.online ? "available" : "offline",
239
+ state: machine.archivedAt ? "archived" : machine.online ? "available" : "offline",
216
240
  summary: overview.summary,
217
241
  }),
218
242
  }, { machineId: machine.id });