@cydm/happy-elves 0.1.0-beta.42 → 0.1.0-beta.44

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 (204) hide show
  1. package/README.md +2 -0
  2. package/apps/cli/dist/commands/account.js +0 -1
  3. package/apps/cli/dist/commands/app.js +0 -1
  4. package/apps/cli/dist/commands/collect.js +0 -1
  5. package/apps/cli/dist/commands/command.js +0 -1
  6. package/apps/cli/dist/commands/config.js +0 -1
  7. package/apps/cli/dist/commands/daemon.js +0 -1
  8. package/apps/cli/dist/commands/lib/args.js +0 -1
  9. package/apps/cli/dist/commands/lib/audit.js +0 -1
  10. package/apps/cli/dist/commands/lib/bootstrap-config.js +0 -1
  11. package/apps/cli/dist/commands/lib/bootstrap-daemon.js +0 -1
  12. package/apps/cli/dist/commands/lib/bootstrap-output.js +0 -1
  13. package/apps/cli/dist/commands/lib/bootstrap.js +0 -1
  14. package/apps/cli/dist/commands/lib/config.js +0 -1
  15. package/apps/cli/dist/commands/lib/doctor.js +0 -1
  16. package/apps/cli/dist/commands/lib/exit.js +0 -1
  17. package/apps/cli/dist/commands/lib/index.js +0 -1
  18. package/apps/cli/dist/commands/lib/json.js +0 -1
  19. package/apps/cli/dist/commands/lib/local-daemon.js +0 -1
  20. package/apps/cli/dist/commands/lib/loop-store.js +0 -1
  21. package/apps/cli/dist/commands/lib/paths.js +0 -1
  22. package/apps/cli/dist/commands/lib/relay-http.js +0 -1
  23. package/apps/cli/dist/commands/lib/scope.d.ts +3 -4
  24. package/apps/cli/dist/commands/lib/scope.js +13 -10
  25. package/apps/cli/dist/commands/lib/session-output.js +10 -5
  26. package/apps/cli/dist/commands/lib/session-view.js +0 -1
  27. package/apps/cli/dist/commands/lib/status.js +0 -1
  28. package/apps/cli/dist/commands/lib/types.d.ts +3 -0
  29. package/apps/cli/dist/commands/lib/types.js +0 -1
  30. package/apps/cli/dist/commands/lib/usage.js +0 -1
  31. package/apps/cli/dist/commands/lib/workspace.js +0 -1
  32. package/apps/cli/dist/commands/loop.js +63 -16
  33. package/apps/cli/dist/commands/machine.js +0 -1
  34. package/apps/cli/dist/commands/relay.js +20 -2
  35. package/apps/cli/dist/commands/remote.js +0 -1
  36. package/apps/cli/dist/commands/session.js +0 -1
  37. package/apps/cli/dist/commands/token.js +0 -1
  38. package/apps/cli/dist/commands/turn.js +0 -1
  39. package/apps/cli/dist/errors.js +0 -1
  40. package/apps/cli/dist/index.js +0 -1
  41. package/apps/daemon/dist/app.js +0 -1
  42. package/apps/daemon/dist/args.js +0 -1
  43. package/apps/daemon/dist/audit.js +0 -1
  44. package/apps/daemon/dist/cli.js +0 -1
  45. package/apps/daemon/dist/config.js +0 -1
  46. package/apps/daemon/dist/errors.js +0 -1
  47. package/apps/daemon/dist/loop/runner.js +99 -28
  48. package/apps/daemon/dist/loop/store.js +0 -1
  49. package/apps/daemon/dist/pair.js +0 -1
  50. package/apps/daemon/dist/paths.js +0 -1
  51. package/apps/daemon/dist/process.js +0 -1
  52. package/apps/daemon/dist/relay/connection.js +0 -1
  53. package/apps/daemon/dist/relay/devtools.js +10 -6
  54. package/apps/daemon/dist/relay/register.js +0 -1
  55. package/apps/daemon/dist/relay/send.js +0 -1
  56. package/apps/daemon/dist/relay-http.js +0 -1
  57. package/apps/daemon/dist/runtime/external-provider.d.ts +1 -1
  58. package/apps/daemon/dist/runtime/external-provider.js +255 -105
  59. package/apps/daemon/dist/runtime.d.ts +1 -1
  60. package/apps/daemon/dist/runtime.js +2 -3
  61. package/apps/daemon/dist/session/create-session.js +0 -1
  62. package/apps/daemon/dist/session/directory.d.ts +2 -1
  63. package/apps/daemon/dist/session/directory.js +58 -53
  64. package/apps/daemon/dist/session/events.js +0 -1
  65. package/apps/daemon/dist/session/file-preview.js +4 -7
  66. package/apps/daemon/dist/session/lifecycle.js +0 -1
  67. package/apps/daemon/dist/session/metadata.js +0 -1
  68. package/apps/daemon/dist/session/primitives.js +0 -1
  69. package/apps/daemon/dist/session/prompt.js +163 -85
  70. package/apps/daemon/dist/session/runtime-handle.js +0 -1
  71. package/apps/daemon/dist/session/workspace-paths.d.ts +5 -0
  72. package/apps/daemon/dist/session/workspace-paths.js +42 -0
  73. package/apps/daemon/dist/start.js +40 -12
  74. package/apps/daemon/dist/state.d.ts +5 -0
  75. package/apps/daemon/dist/state.js +0 -1
  76. package/apps/daemon/dist/turn-coordinator.js +0 -1
  77. package/apps/daemon/dist/types.d.ts +12 -0
  78. package/apps/daemon/dist/types.js +0 -1
  79. package/apps/daemon/dist/utils.js +0 -1
  80. package/apps/daemon/package.json +1 -1
  81. package/apps/relay/dist/auth.js +13 -3
  82. package/apps/relay/dist/connections.d.ts +22 -7
  83. package/apps/relay/dist/connections.js +0 -1
  84. package/apps/relay/dist/controller-handlers.d.ts +17 -7
  85. package/apps/relay/dist/controller-handlers.js +437 -25
  86. package/apps/relay/dist/db.js +162 -10
  87. package/apps/relay/dist/errors.js +0 -1
  88. package/apps/relay/dist/event-visibility.d.ts +7 -0
  89. package/apps/relay/dist/event-visibility.js +46 -0
  90. package/apps/relay/dist/http-routes.js +104 -52
  91. package/apps/relay/dist/http-schemas.d.ts +9 -1
  92. package/apps/relay/dist/http-schemas.js +5 -3
  93. package/apps/relay/dist/index.js +19 -2
  94. package/apps/relay/dist/machine-command-result-handlers.d.ts +4 -0
  95. package/apps/relay/dist/machine-command-result-handlers.js +532 -0
  96. package/apps/relay/dist/machine-handler-context.d.ts +79 -0
  97. package/apps/relay/dist/machine-handler-context.js +1 -0
  98. package/apps/relay/dist/machine-handlers.d.ts +3 -50
  99. package/apps/relay/dist/machine-handlers.js +9 -750
  100. package/apps/relay/dist/machine-reliability.d.ts +7 -0
  101. package/apps/relay/dist/machine-reliability.js +12 -0
  102. package/apps/relay/dist/projection-repository.d.ts +87 -0
  103. package/apps/relay/dist/projection-repository.js +220 -0
  104. package/apps/relay/dist/projections.js +0 -1
  105. package/apps/relay/dist/relay-context.d.ts +62 -7
  106. package/apps/relay/dist/relay-context.js +314 -39
  107. package/apps/relay/dist/repositories.js +2 -2
  108. package/apps/relay/dist/retention.js +0 -1
  109. package/apps/relay/dist/scope.d.ts +14 -1
  110. package/apps/relay/dist/scope.js +144 -17
  111. package/apps/relay/dist/security.js +0 -1
  112. package/apps/relay/dist/session-projection-reducer.d.ts +4 -0
  113. package/apps/relay/dist/session-projection-reducer.js +448 -0
  114. package/apps/relay/dist/types.d.ts +8 -0
  115. package/apps/relay/dist/types.js +0 -1
  116. package/apps/relay/dist/websocket.js +41 -3
  117. package/npm-shrinkwrap.json +1615 -0
  118. package/package.json +25 -8
  119. package/packages/agent-sdk/dist/index.d.ts +15 -232
  120. package/packages/agent-sdk/dist/index.js +44 -14
  121. package/packages/client/dist/account.d.ts +0 -1
  122. package/packages/client/dist/account.js +0 -1
  123. package/packages/client/dist/client.d.ts +3 -3
  124. package/packages/client/dist/client.js +17 -5
  125. package/packages/client/dist/errors.d.ts +0 -1
  126. package/packages/client/dist/errors.js +0 -1
  127. package/packages/client/dist/http.d.ts +0 -1
  128. package/packages/client/dist/http.js +0 -1
  129. package/packages/client/dist/index.d.ts +4 -2
  130. package/packages/client/dist/index.js +2 -1
  131. package/packages/client/dist/live.d.ts +178 -0
  132. package/packages/client/dist/live.js +1052 -0
  133. package/packages/client/dist/parsers.d.ts +0 -1
  134. package/packages/client/dist/parsers.js +3 -1
  135. package/packages/client/dist/session-event-order.d.ts +16 -0
  136. package/packages/client/dist/session-event-order.js +83 -0
  137. package/packages/client/dist/session-event-window.d.ts +5 -0
  138. package/packages/client/dist/session-event-window.js +54 -0
  139. package/packages/client/dist/transport.d.ts +4 -6
  140. package/packages/client/dist/transport.js +59 -24
  141. package/packages/client/dist/types.d.ts +4 -1
  142. package/packages/client/dist/types.js +0 -1
  143. package/packages/client/dist/validation.d.ts +0 -1
  144. package/packages/client/dist/validation.js +0 -1
  145. package/packages/pie-provider/dist/index.d.ts +0 -1
  146. package/packages/pie-provider/dist/index.js +90 -50
  147. package/packages/provider-protocol/dist/index.d.ts +843 -0
  148. package/packages/provider-protocol/dist/index.js +332 -0
  149. package/packages/runtime/dist/index.d.ts +4 -111
  150. package/packages/runtime/dist/index.js +0 -1
  151. package/packages/runtime-acpx/dist/index.d.ts +0 -1
  152. package/packages/runtime-acpx/dist/index.js +0 -1
  153. package/packages/runtime-cli/dist/async-queue.d.ts +0 -1
  154. package/packages/runtime-cli/dist/async-queue.js +0 -1
  155. package/packages/runtime-cli/dist/claude-history.d.ts +0 -1
  156. package/packages/runtime-cli/dist/claude-history.js +0 -1
  157. package/packages/runtime-cli/dist/claude-json.d.ts +0 -1
  158. package/packages/runtime-cli/dist/claude-json.js +0 -1
  159. package/packages/runtime-cli/dist/claude-rename.d.ts +0 -1
  160. package/packages/runtime-cli/dist/claude-rename.js +0 -1
  161. package/packages/runtime-cli/dist/claude-session-store.d.ts +0 -1
  162. package/packages/runtime-cli/dist/claude-session-store.js +0 -1
  163. package/packages/runtime-cli/dist/claude-turn.d.ts +0 -1
  164. package/packages/runtime-cli/dist/claude-turn.js +37 -8
  165. package/packages/runtime-cli/dist/codex-app-server.d.ts +18 -3
  166. package/packages/runtime-cli/dist/codex-app-server.js +160 -19
  167. package/packages/runtime-cli/dist/codex-approvals.d.ts +0 -1
  168. package/packages/runtime-cli/dist/codex-approvals.js +0 -1
  169. package/packages/runtime-cli/dist/codex-history.d.ts +0 -1
  170. package/packages/runtime-cli/dist/codex-history.js +0 -1
  171. package/packages/runtime-cli/dist/codex-json-rpc.d.ts +0 -1
  172. package/packages/runtime-cli/dist/codex-json-rpc.js +51 -3
  173. package/packages/runtime-cli/dist/codex-lifecycle.d.ts +0 -1
  174. package/packages/runtime-cli/dist/codex-lifecycle.js +0 -1
  175. package/packages/runtime-cli/dist/codex-protocol.d.ts +0 -1
  176. package/packages/runtime-cli/dist/codex-protocol.js +0 -1
  177. package/packages/runtime-cli/dist/command-resolver.d.ts +0 -1
  178. package/packages/runtime-cli/dist/command-resolver.js +0 -1
  179. package/packages/runtime-cli/dist/history-merge.d.ts +0 -1
  180. package/packages/runtime-cli/dist/history-merge.js +15 -3
  181. package/packages/runtime-cli/dist/index.d.ts +0 -1
  182. package/packages/runtime-cli/dist/index.js +15 -2
  183. package/packages/runtime-cli/dist/permissions.d.ts +0 -1
  184. package/packages/runtime-cli/dist/permissions.js +0 -1
  185. package/packages/runtime-cli/dist/session-store.d.ts +2 -2
  186. package/packages/runtime-cli/dist/session-store.js +0 -1
  187. package/packages/shared/dist/args.d.ts +0 -1
  188. package/packages/shared/dist/args.js +0 -1
  189. package/packages/shared/dist/crypto.d.ts +0 -1
  190. package/packages/shared/dist/crypto.js +0 -1
  191. package/packages/shared/dist/ids.d.ts +0 -1
  192. package/packages/shared/dist/ids.js +0 -1
  193. package/packages/shared/dist/index.d.ts +0 -1
  194. package/packages/shared/dist/index.js +0 -1
  195. package/packages/shared/dist/protocol-schemas.d.ts +0 -1
  196. package/packages/shared/dist/protocol-schemas.js +0 -1
  197. package/packages/shared/dist/protocol-types.d.ts +2 -1
  198. package/packages/shared/dist/protocol-types.js +0 -1
  199. package/packages/shared/dist/protocol.d.ts +19 -1
  200. package/packages/shared/dist/protocol.js +23 -1
  201. package/packages/shared/dist/session-name.d.ts +0 -1
  202. package/packages/shared/dist/session-name.js +0 -1
  203. package/packages/shared/dist/session-state.d.ts +0 -1
  204. package/packages/shared/dist/session-state.js +0 -1
package/README.md CHANGED
@@ -11,5 +11,7 @@ happy-elves account create --relay https://relay.happyelves.ai --device-name "CL
11
11
  ## Self-host relay
12
12
 
13
13
  ```bash
14
+ HAPPY_ELVES_ENV=production \
15
+ HAPPY_ELVES_ALLOWED_ORIGINS=https://your-controller.example \
14
16
  happy-elves relay serve --host 0.0.0.0 --port 8787 --json
15
17
  ```
@@ -49,4 +49,3 @@ export async function handleAccount({ domain, action, flags }) {
49
49
  }
50
50
  return false;
51
51
  }
52
- //# sourceMappingURL=account.js.map
@@ -97,4 +97,3 @@ function cliVersion() {
97
97
  }
98
98
  return "0.1.0";
99
99
  }
100
- //# sourceMappingURL=app.js.map
@@ -28,4 +28,3 @@ export async function handleCollect({ domain, action, flags }) {
28
28
  }
29
29
  return true;
30
30
  }
31
- //# sourceMappingURL=collect.js.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=command.js.map
@@ -28,4 +28,3 @@ export async function handleConfig({ domain, action, flags }) {
28
28
  }
29
29
  return false;
30
30
  }
31
- //# sourceMappingURL=config.js.map
@@ -216,4 +216,3 @@ export async function handleDaemon({ domain, action, flags }) {
216
216
  }
217
217
  return false;
218
218
  }
219
- //# sourceMappingURL=daemon.js.map
@@ -79,4 +79,3 @@ export function parseCliFlags(argv) {
79
79
  }
80
80
  return parsed;
81
81
  }
82
- //# sourceMappingURL=args.js.map
@@ -23,4 +23,3 @@ export async function readLocalAuditLog(tail = 100) {
23
23
  throw error;
24
24
  }
25
25
  }
26
- //# sourceMappingURL=audit.js.map
@@ -147,4 +147,3 @@ export function startRelayUrl(flags, existing) {
147
147
  const relayUrl = normalizeRelayUrl(defaultHostedRelayUrl);
148
148
  return { relayUrl, explicit: false };
149
149
  }
150
- //# sourceMappingURL=bootstrap-config.js.map
@@ -43,4 +43,3 @@ export async function waitForMachineProjection(client, machineId, timeoutMs = 50
43
43
  }
44
44
  return lastSeen;
45
45
  }
46
- //# sourceMappingURL=bootstrap-daemon.js.map
@@ -37,4 +37,3 @@ export function openUrl(url) {
37
37
  return { opened: false, error: error instanceof Error ? error.message : String(error) };
38
38
  }
39
39
  }
40
- //# sourceMappingURL=bootstrap-output.js.map
@@ -93,4 +93,3 @@ export async function startBootstrap(flags) {
93
93
  daemon: data.daemon,
94
94
  });
95
95
  }
96
- //# sourceMappingURL=bootstrap.js.map
@@ -146,4 +146,3 @@ export async function writeConfig(config) {
146
146
  await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
147
147
  await fs.chmod(configPath, 0o600);
148
148
  }
149
- //# sourceMappingURL=config.js.map
@@ -46,4 +46,3 @@ export async function topLevelDoctor(flags) {
46
46
  console.log(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`);
47
47
  }
48
48
  }
49
- //# sourceMappingURL=doctor.js.map
@@ -129,4 +129,3 @@ export function handleCliError(error) {
129
129
  }, enriched.capability);
130
130
  process.exit(1);
131
131
  }
132
- //# sourceMappingURL=exit.js.map
@@ -22,4 +22,3 @@ export * from "./status.js";
22
22
  export * from "./types.js";
23
23
  export * from "./usage.js";
24
24
  export * from "./workspace.js";
25
- //# sourceMappingURL=index.js.map
@@ -14,4 +14,3 @@ export function wantsJson(flags) {
14
14
  export function wantsVerbose(flags) {
15
15
  return flags.verbose === true || flags.format === "verbose";
16
16
  }
17
- //# sourceMappingURL=json.js.map
@@ -130,4 +130,3 @@ export async function stopLocalDaemon(timeoutMs = 5000) {
130
130
  }
131
131
  return { ...status, stopped: false, running: isPidAlive(status.pid) };
132
132
  }
133
- //# sourceMappingURL=local-daemon.js.map
@@ -75,4 +75,3 @@ export async function appendLoop(loop) {
75
75
  return loop;
76
76
  });
77
77
  }
78
- //# sourceMappingURL=loop-store.js.map
@@ -11,4 +11,3 @@ export const startDaemonTimeoutMs = 10_000;
11
11
  export const daemonPidPath = path.join(configDir, "daemon.pid");
12
12
  export const loopStorePath = path.join(configDir, "loops.json");
13
13
  export const loopStoreLockPath = path.join(configDir, "loops.json.lock");
14
- //# sourceMappingURL=paths.js.map
@@ -99,4 +99,3 @@ export function isAuthTokenInvalid(error) {
99
99
  const record = error;
100
100
  return record.code === "AUTH_TOKEN_INVALID" || record.message === "Invalid token";
101
101
  }
102
- //# sourceMappingURL=relay-http.js.map
@@ -1,13 +1,12 @@
1
- import type { ControllerClient, ScopedTokenScope } from "../../../../../packages/client/dist/index.js";
1
+ import type { ScopedTokenScope } from "../../../../../packages/client/dist/index.js";
2
2
  import type { LoopRecord, PermissionMode } from "./types.js";
3
3
  export declare const DEFAULT_COMMAND_TIMEOUT_MS: number;
4
4
  export declare function parseDurationMs(value: string | boolean | undefined, fallbackMs: number): number;
5
5
  export declare function parseCsvFlag(value: string | boolean | undefined): string[] | undefined;
6
6
  export declare function scopeAllows(list: string[] | undefined, value: string | undefined): boolean;
7
7
  export declare function requireScopedAction(scope: ScopedTokenScope | undefined, action: string): void;
8
- export declare function requireScopedLoop(scope: ScopedTokenScope | undefined, loop: LoopRecord): void;
9
- export declare function localScopeFor(client: ControllerClient): Promise<ScopedTokenScope | undefined>;
10
- export declare function loopVisibleToScope(loop: LoopRecord, scope: ScopedTokenScope | undefined): boolean;
8
+ export declare function requireScopedLoop(scope: ScopedTokenScope | undefined, loop: LoopRecord, tokenId?: string, machineId?: string | undefined): void;
9
+ export declare function loopVisibleToScope(loop: LoopRecord, scope: ScopedTokenScope | undefined, tokenId?: string, machineId?: string | undefined): boolean;
11
10
  export declare function parseEventCursor(value: string | boolean | undefined): `cursor_${number}` | number | undefined;
12
11
  export declare function parsePermissionMode(value: string | boolean | undefined): PermissionMode | undefined;
13
12
  export declare function parseControllerJoinUrl(value: string): {
@@ -23,7 +23,7 @@ export function parseCsvFlag(value) {
23
23
  return value.split(",").map((item) => item.trim()).filter(Boolean);
24
24
  }
25
25
  export function scopeAllows(list, value) {
26
- return !list || list.length === 0 || (value !== undefined && list.includes(value));
26
+ return Array.isArray(list) && value !== undefined && list.includes(value);
27
27
  }
28
28
  export function requireScopedAction(scope, action) {
29
29
  if (!scope)
@@ -32,21 +32,25 @@ export function requireScopedAction(scope, action) {
32
32
  throw new CliError(`Scoped token cannot perform ${action}`, "SCOPED_TOKEN_FORBIDDEN");
33
33
  }
34
34
  }
35
- export function requireScopedLoop(scope, loop) {
35
+ export function requireScopedLoop(scope, loop, tokenId, machineId = loop.machineId) {
36
36
  if (!scope)
37
37
  return;
38
- if (!scopeAllows(scope.loops, loop.id) || !scopeAllows(scope.sessions, loop.sessionId)) {
38
+ const sessionAllowed = scopeAllows(scope.sessions, loop.sessionId);
39
+ const machineAllowed = scopeAllows(scope.machines, machineId);
40
+ if (tokenId && loop.creatorTokenId === tokenId && sessionAllowed && machineAllowed)
41
+ return;
42
+ if (!scopeAllows(scope.loops, loop.id) || !sessionAllowed || !machineAllowed) {
39
43
  throw new CliError("Scoped token cannot access this loop", "SCOPED_TOKEN_FORBIDDEN");
40
44
  }
41
45
  }
42
- export async function localScopeFor(client) {
43
- const self = await client.tokenSelf();
44
- return self.full ? undefined : self.scope;
45
- }
46
- export function loopVisibleToScope(loop, scope) {
46
+ export function loopVisibleToScope(loop, scope, tokenId, machineId = loop.machineId) {
47
47
  if (!scope)
48
48
  return true;
49
- return scopeAllows(scope.loops, loop.id) && scopeAllows(scope.sessions, loop.sessionId);
49
+ const sessionAllowed = scopeAllows(scope.sessions, loop.sessionId);
50
+ const machineAllowed = scopeAllows(scope.machines, machineId);
51
+ if (tokenId && loop.creatorTokenId === tokenId && sessionAllowed && machineAllowed)
52
+ return true;
53
+ return scopeAllows(scope.loops, loop.id) && sessionAllowed && machineAllowed;
50
54
  }
51
55
  export function parseEventCursor(value) {
52
56
  if (typeof value !== "string")
@@ -97,4 +101,3 @@ export function parseControllerJoinUrl(value) {
97
101
  url.hash = "";
98
102
  return { relayUrl: normalizeRelayUrl(relay ?? url.toString()), code, inviteSecret };
99
103
  }
100
- //# sourceMappingURL=scope.js.map
@@ -96,7 +96,8 @@ export async function writeTurnOutput(sessionId, turnId, events) {
96
96
  const paths = outputPaths(sessionId, turnId);
97
97
  if (!paths.turn || !paths.metadata)
98
98
  throw new CliError("Output path construction failed", "OUTPUT_FAILED");
99
- await fs.mkdir(paths.dir, { recursive: true });
99
+ await fs.mkdir(paths.dir, { recursive: true, mode: 0o700 });
100
+ await fs.chmod(paths.dir, 0o700).catch(() => { });
100
101
  const output = assistantOutputForTurn(events, turnId) || terminalTextForDone(events, turnId);
101
102
  const turnEvents = events.filter((event) => event.turnId === turnId);
102
103
  const doneEvent = [...turnEvents].reverse().find((event) => event.decoded?.type === "done");
@@ -110,18 +111,23 @@ export async function writeTurnOutput(sessionId, turnId, events) {
110
111
  outputPath: paths.turn,
111
112
  latestPath: paths.latest,
112
113
  };
113
- await fs.writeFile(paths.turn, output ? `${output}\n` : "", "utf8");
114
- await fs.writeFile(paths.metadata, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
115
- await fs.writeFile(paths.latestMetadata, `${JSON.stringify({ sessionId, turnId, metadataPath: paths.metadata, outputPath: paths.turn }, null, 2)}\n`, "utf8");
114
+ await writePrivateFile(paths.turn, output ? `${output}\n` : "");
115
+ await writePrivateFile(paths.metadata, `${JSON.stringify(metadata, null, 2)}\n`);
116
+ await writePrivateFile(paths.latestMetadata, `${JSON.stringify({ sessionId, turnId, metadataPath: paths.metadata, outputPath: paths.turn }, null, 2)}\n`);
116
117
  await fs.rm(paths.latest, { force: true });
117
118
  try {
118
119
  await fs.symlink(path.basename(paths.turn), paths.latest);
119
120
  }
120
121
  catch {
121
122
  await fs.copyFile(paths.turn, paths.latest);
123
+ await fs.chmod(paths.latest, 0o600).catch(() => { });
122
124
  }
123
125
  return { ...metadata, output, metadataPath: paths.metadata };
124
126
  }
127
+ async function writePrivateFile(filePath, content) {
128
+ await fs.writeFile(filePath, content, { encoding: "utf8", mode: 0o600 });
129
+ await fs.chmod(filePath, 0o600).catch(() => { });
130
+ }
125
131
  export async function readStoredTurnOutput(sessionId, turnId) {
126
132
  const paths = outputPaths(sessionId, turnId);
127
133
  let outputPath = paths.turn;
@@ -373,4 +379,3 @@ export async function readPromptTextFile(filePath) {
373
379
  throw new CliError(`Prompt file is not readable: ${filePath}`, "INPUT_FILE_UNREADABLE");
374
380
  }
375
381
  }
376
- //# sourceMappingURL=session-output.js.map
@@ -194,4 +194,3 @@ export async function machineSessionOverview(client, sessions, machineId) {
194
194
  sessions: projectedSessions,
195
195
  };
196
196
  }
197
- //# sourceMappingURL=session-view.js.map
@@ -60,4 +60,3 @@ export async function topLevelStatus(flags) {
60
60
  console.log(`Run: happy-elves turn run ${latestSession.id} --text "..."`);
61
61
  }
62
62
  }
63
- //# sourceMappingURL=status.js.map
@@ -45,6 +45,7 @@ export type OrchestrationSummary = {
45
45
  export type LoopRecord = {
46
46
  id: string;
47
47
  sessionId: string;
48
+ machineId?: string;
48
49
  prompt: string;
49
50
  everyMs?: number;
50
51
  stopOn: string[];
@@ -60,6 +61,8 @@ export type LoopRecord = {
60
61
  name: string;
61
62
  runtime?: unknown;
62
63
  };
64
+ creatorTokenId?: string;
65
+ creatorTokenExpiresAt?: number;
63
66
  creatorScope?: ScopedTokenScope;
64
67
  lastRun?: LoopRunRecord;
65
68
  runs?: LoopRunRecord[];
@@ -1,2 +1 @@
1
1
  export const idleSessionStatuses = ["ready", "completed", "cancelled"];
2
- //# sourceMappingURL=types.js.map
@@ -252,4 +252,3 @@ export function usage() {
252
252
  writeError("USAGE", "Unsupported or incomplete command. Run happy-elves --help for usage.");
253
253
  process.exit(1);
254
254
  }
255
- //# sourceMappingURL=usage.js.map
@@ -68,4 +68,3 @@ export async function listWorkspaceHistoricalSessions(client, input) {
68
68
  machines: rows.map((row) => row.machine),
69
69
  };
70
70
  }
71
- //# sourceMappingURL=workspace.js.map
@@ -1,9 +1,10 @@
1
- import { CliError, ControllerClient, DEFAULT_COMMAND_TIMEOUT_MS, appendLoop, compactText, localScopeFor, loopVisibleToScope, ok, parseDurationMs, parseStopOn, randomId, readConfig, readLoopStore, readPromptTextFile, requirePositional, requireScopedAction, requireScopedLoop, scopeAllows, updateLoop, wantsJson, wantsVerbose } from "./lib/index.js";
1
+ import { CliError, ControllerClient, DEFAULT_COMMAND_TIMEOUT_MS, appendLoop, compactText, loopVisibleToScope, ok, parseDurationMs, parseStopOn, randomId, readConfig, readLoopStore, readPromptTextFile, requirePositional, requireScopedAction, requireScopedLoop, scopeAllows, updateLoop, wantsJson, wantsVerbose } from "./lib/index.js";
2
2
  export async function handleLoop({ domain, action, positional, flags }) {
3
3
  if (domain === "loop" && action === "create") {
4
4
  const config = await readConfig(flags);
5
5
  const client = new ControllerClient(config);
6
- const localScope = await localScopeFor(client);
6
+ const scoped = await scopedContext(client);
7
+ const localScope = scoped.scope;
7
8
  requireScopedAction(localScope, "loop.create");
8
9
  const sessionId = requirePositional(positional[0], "sessionId");
9
10
  const prompt = typeof flags.prompt === "string"
@@ -19,14 +20,18 @@ export async function handleLoop({ domain, action, positional, flags }) {
19
20
  const session = await client.getSession(sessionId);
20
21
  if (!session)
21
22
  throw new CliError("Session not found", "SESSION_NOT_FOUND");
22
- if (!scopeAllows(localScope?.sessions, sessionId)) {
23
+ if (localScope && !scopeAllows(localScope.sessions, sessionId)) {
23
24
  throw new CliError("Scoped token cannot access this session", "SCOPED_TOKEN_FORBIDDEN");
24
25
  }
26
+ if (localScope && !scopeAllows(localScope.machines, session.machineId)) {
27
+ throw new CliError("Scoped token cannot access this machine", "SCOPED_TOKEN_FORBIDDEN");
28
+ }
25
29
  const metadata = await client.decodeSessionMetadata(session);
26
30
  const nowIso = new Date().toISOString();
27
31
  const loop = {
28
32
  id: randomId("loop"),
29
33
  sessionId,
34
+ machineId: session.machineId,
30
35
  prompt,
31
36
  everyMs,
32
37
  stopOn: parseStopOn(flags["stop-on"]),
@@ -41,6 +46,8 @@ export async function handleLoop({ domain, action, positional, flags }) {
41
46
  name: metadata.name ?? session.name,
42
47
  runtime: metadata.runtime,
43
48
  },
49
+ creatorTokenId: scoped.tokenId,
50
+ creatorTokenExpiresAt: scoped.expiresAt,
44
51
  creatorScope: localScope,
45
52
  };
46
53
  await appendLoop(loop);
@@ -49,10 +56,18 @@ export async function handleLoop({ domain, action, positional, flags }) {
49
56
  }
50
57
  if (domain === "loop" && action === "list") {
51
58
  const config = await readConfig(flags);
52
- const localScope = await localScopeFor(new ControllerClient(config));
59
+ const client = new ControllerClient(config);
60
+ const scoped = await scopedContext(client);
61
+ const localScope = scoped.scope;
53
62
  requireScopedAction(localScope, "loop.list");
54
63
  const store = await readLoopStore();
55
- const allLoops = store.loops.filter((loop) => loop.status !== "deleted" && loopVisibleToScope(loop, localScope));
64
+ const visibleLoops = await Promise.all(store.loops.map(async (loop) => ({
65
+ loop,
66
+ machineId: await loopMachineId(client, loop, localScope),
67
+ })));
68
+ const allLoops = visibleLoops
69
+ .filter(({ loop, machineId }) => loop.status !== "deleted" && loopVisibleToScope(loop, localScope, scoped.tokenId, machineId))
70
+ .map(({ loop }) => loop);
56
71
  const loops = flags.all === true ? allLoops : allLoops.filter((loop) => loop.status !== "stopped");
57
72
  if (!wantsJson(flags)) {
58
73
  if (loops.length === 0) {
@@ -83,24 +98,29 @@ export async function handleLoop({ domain, action, positional, flags }) {
83
98
  }
84
99
  if (domain === "loop" && action === "show") {
85
100
  const config = await readConfig(flags);
86
- const localScope = await localScopeFor(new ControllerClient(config));
101
+ const client = new ControllerClient(config);
102
+ const scoped = await scopedContext(client);
103
+ const localScope = scoped.scope;
87
104
  const loopId = requirePositional(positional[0], "loopId");
88
105
  const store = await readLoopStore();
89
106
  const loop = store.loops.find((candidate) => candidate.id === loopId && candidate.status !== "deleted");
90
107
  if (!loop)
91
108
  throw new CliError("Loop not found", "LOOP_NOT_FOUND");
92
109
  requireScopedAction(localScope, "loop.show");
93
- requireScopedLoop(localScope, loop);
110
+ requireScopedLoop(localScope, loop, scoped.tokenId, await loopMachineId(client, loop, localScope));
94
111
  ok("loop.show", { loop }, { sessionId: loop.sessionId });
95
112
  return true;
96
113
  }
97
114
  if (domain === "loop" && action === "pause") {
98
115
  const config = await readConfig(flags);
99
- const localScope = await localScopeFor(new ControllerClient(config));
116
+ const client = new ControllerClient(config);
117
+ const scoped = await scopedContext(client);
118
+ const localScope = scoped.scope;
100
119
  const loopId = requirePositional(positional[0], "loopId");
101
120
  requireScopedAction(localScope, "loop.pause");
121
+ const currentLoop = await findLoop(loopId);
122
+ requireScopedLoop(localScope, currentLoop, scoped.tokenId, await loopMachineId(client, currentLoop, localScope));
102
123
  const loop = await updateLoop(loopId, (current) => {
103
- requireScopedLoop(localScope, current);
104
124
  return { ...current, status: "paused" };
105
125
  });
106
126
  ok("loop.pause", { loop }, { sessionId: loop.sessionId });
@@ -108,11 +128,14 @@ export async function handleLoop({ domain, action, positional, flags }) {
108
128
  }
109
129
  if (domain === "loop" && action === "resume") {
110
130
  const config = await readConfig(flags);
111
- const localScope = await localScopeFor(new ControllerClient(config));
131
+ const client = new ControllerClient(config);
132
+ const scoped = await scopedContext(client);
133
+ const localScope = scoped.scope;
112
134
  const loopId = requirePositional(positional[0], "loopId");
113
135
  requireScopedAction(localScope, "loop.resume");
136
+ const currentLoop = await findLoop(loopId);
137
+ requireScopedLoop(localScope, currentLoop, scoped.tokenId, await loopMachineId(client, currentLoop, localScope));
114
138
  const loop = await updateLoop(loopId, (current) => {
115
- requireScopedLoop(localScope, current);
116
139
  return {
117
140
  ...current,
118
141
  status: "active",
@@ -124,11 +147,14 @@ export async function handleLoop({ domain, action, positional, flags }) {
124
147
  }
125
148
  if (domain === "loop" && action === "delete") {
126
149
  const config = await readConfig(flags);
127
- const localScope = await localScopeFor(new ControllerClient(config));
150
+ const client = new ControllerClient(config);
151
+ const scoped = await scopedContext(client);
152
+ const localScope = scoped.scope;
128
153
  const loopId = requirePositional(positional[0], "loopId");
129
154
  requireScopedAction(localScope, "loop.delete");
155
+ const currentLoop = await findLoop(loopId);
156
+ requireScopedLoop(localScope, currentLoop, scoped.tokenId, await loopMachineId(client, currentLoop, localScope));
130
157
  const loop = await updateLoop(loopId, (current) => {
131
- requireScopedLoop(localScope, current);
132
158
  return { ...current, status: "deleted", pendingRunId: undefined };
133
159
  });
134
160
  ok("loop.delete", { loopId: loop.id, status: "deleted" }, { sessionId: loop.sessionId });
@@ -136,12 +162,15 @@ export async function handleLoop({ domain, action, positional, flags }) {
136
162
  }
137
163
  if (domain === "loop" && action === "run") {
138
164
  const config = await readConfig(flags);
139
- const localScope = await localScopeFor(new ControllerClient(config));
165
+ const client = new ControllerClient(config);
166
+ const scoped = await scopedContext(client);
167
+ const localScope = scoped.scope;
140
168
  const loopId = requirePositional(positional[0], "loopId");
141
169
  requireScopedAction(localScope, "loop.run");
142
170
  const runId = randomId("looprun");
171
+ const currentLoop = await findLoop(loopId);
172
+ requireScopedLoop(localScope, currentLoop, scoped.tokenId, await loopMachineId(client, currentLoop, localScope));
143
173
  const before = await updateLoop(loopId, (current) => {
144
- requireScopedLoop(localScope, current);
145
174
  return { ...current, pendingRunId: runId };
146
175
  });
147
176
  const timeoutMs = parseDurationMs(flags.timeout, DEFAULT_COMMAND_TIMEOUT_MS);
@@ -162,4 +191,22 @@ export async function handleLoop({ domain, action, positional, flags }) {
162
191
  }
163
192
  return false;
164
193
  }
165
- //# sourceMappingURL=loop.js.map
194
+ async function findLoop(loopId) {
195
+ const store = await readLoopStore();
196
+ const loop = store.loops.find((candidate) => candidate.id === loopId && candidate.status !== "deleted");
197
+ if (!loop)
198
+ throw new CliError("Loop not found", "LOOP_NOT_FOUND");
199
+ return loop;
200
+ }
201
+ async function loopMachineId(client, loop, scope) {
202
+ if (loop.machineId)
203
+ return loop.machineId;
204
+ if (!scope)
205
+ return undefined;
206
+ const session = await client.getSession(loop.sessionId);
207
+ return session?.machineId;
208
+ }
209
+ async function scopedContext(client) {
210
+ const self = await client.tokenSelf();
211
+ return self.full ? {} : { scope: self.scope, tokenId: self.tokenId, expiresAt: self.expiresAt };
212
+ }
@@ -258,4 +258,3 @@ export async function handleMachine({ domain, action, positional, flags }) {
258
258
  }
259
259
  return false;
260
260
  }
261
- //# sourceMappingURL=machine.js.map
@@ -6,6 +6,8 @@ export async function handleRelay({ domain, action, flags }) {
6
6
  if (!Number.isInteger(relayPort) || relayPort <= 0 || relayPort > 65535) {
7
7
  throw new CliError(`Invalid --port: ${flags.port}`, "INVALID_ARGUMENT");
8
8
  }
9
+ if (wantsJson(flags))
10
+ process.env.HAPPY_ELVES_RELAY_LOG_STDERR = "1";
9
11
  const { startRelay } = await import("../../../relay/dist/index.js");
10
12
  const address = await startRelay({ host, port: relayPort });
11
13
  ok("relay.serve", {
@@ -19,7 +21,24 @@ export async function handleRelay({ domain, action, flags }) {
19
21
  }
20
22
  if (domain === "relay" && action === "status") {
21
23
  const relayUrl = typeof flags.relay === "string" ? requireRelayUrl(flags) : "http://localhost:8787";
22
- const health = await relayHealth(relayUrl);
24
+ let health;
25
+ try {
26
+ health = await relayHealth(relayUrl);
27
+ }
28
+ catch (error) {
29
+ if (!wantsJson(flags))
30
+ throw error;
31
+ health = {
32
+ ok: false,
33
+ status: 0,
34
+ body: {
35
+ error: {
36
+ code: "RELAY_UNREACHABLE",
37
+ message: error instanceof Error ? error.message : String(error),
38
+ },
39
+ },
40
+ };
41
+ }
23
42
  if (wantsJson(flags)) {
24
43
  ok("relay.status", { relayUrl, health });
25
44
  }
@@ -58,4 +77,3 @@ export async function handleRelay({ domain, action, flags }) {
58
77
  }
59
78
  return false;
60
79
  }
61
- //# sourceMappingURL=relay.js.map
@@ -140,4 +140,3 @@ export async function handleRemote({ domain, action, positional, flags }) {
140
140
  }
141
141
  return false;
142
142
  }
143
- //# sourceMappingURL=remote.js.map
@@ -745,4 +745,3 @@ export async function handleSession({ domain, action, positional, flags }) {
745
745
  }
746
746
  return false;
747
747
  }
748
- //# sourceMappingURL=session.js.map
@@ -48,4 +48,3 @@ export async function handleToken({ domain, action, positional, flags }) {
48
48
  }
49
49
  return false;
50
50
  }
51
- //# sourceMappingURL=token.js.map
@@ -244,4 +244,3 @@ export async function handleTurn({ domain, action, positional, flags }) {
244
244
  }
245
245
  return false;
246
246
  }
247
- //# sourceMappingURL=turn.js.map
@@ -6,4 +6,3 @@ export class CliError extends Error {
6
6
  this.name = "CliError";
7
7
  }
8
8
  }
9
- //# sourceMappingURL=errors.js.map
@@ -1,4 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  import { handleCliError, main } from "./commands/app.js";
3
3
  main().catch(handleCliError);
4
- //# sourceMappingURL=index.js.map
@@ -115,4 +115,3 @@ export function handleDaemonError(error) {
115
115
  console.error(error instanceof Error ? error.message : String(error));
116
116
  process.exit(1);
117
117
  }
118
- //# sourceMappingURL=app.js.map
@@ -12,4 +12,3 @@ export function requireString(flags, key) {
12
12
  }
13
13
  return value;
14
14
  }
15
- //# sourceMappingURL=args.js.map
@@ -48,4 +48,3 @@ export async function showLogs(argv) {
48
48
  console.log(JSON.stringify(entry));
49
49
  }
50
50
  }
51
- //# sourceMappingURL=audit.js.map
@@ -1,3 +1,2 @@
1
1
  import { handleDaemonError, main } from "./app.js";
2
2
  main().catch(handleDaemonError);
3
- //# sourceMappingURL=cli.js.map
@@ -88,4 +88,3 @@ export async function writeConfig(config) {
88
88
  await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
89
89
  await fs.chmod(configPath, 0o600);
90
90
  }
91
- //# sourceMappingURL=config.js.map
@@ -19,4 +19,3 @@ export function writeJsonError(error) {
19
19
  },
20
20
  }, null, 2));
21
21
  }
22
- //# sourceMappingURL=errors.js.map