@cydm/happy-elves 0.1.0-beta.43 → 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.
- package/README.md +2 -0
- package/apps/cli/dist/commands/account.js +0 -1
- package/apps/cli/dist/commands/app.js +0 -1
- package/apps/cli/dist/commands/collect.js +0 -1
- package/apps/cli/dist/commands/command.js +0 -1
- package/apps/cli/dist/commands/config.js +0 -1
- package/apps/cli/dist/commands/daemon.js +0 -1
- package/apps/cli/dist/commands/lib/args.js +0 -1
- package/apps/cli/dist/commands/lib/audit.js +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-config.js +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-output.js +0 -1
- package/apps/cli/dist/commands/lib/bootstrap.js +0 -1
- package/apps/cli/dist/commands/lib/config.js +0 -1
- package/apps/cli/dist/commands/lib/doctor.js +0 -1
- package/apps/cli/dist/commands/lib/exit.js +0 -1
- package/apps/cli/dist/commands/lib/index.js +0 -1
- package/apps/cli/dist/commands/lib/json.js +0 -1
- package/apps/cli/dist/commands/lib/local-daemon.js +0 -1
- package/apps/cli/dist/commands/lib/loop-store.js +0 -1
- package/apps/cli/dist/commands/lib/paths.js +0 -1
- package/apps/cli/dist/commands/lib/relay-http.js +0 -1
- package/apps/cli/dist/commands/lib/scope.d.ts +3 -4
- package/apps/cli/dist/commands/lib/scope.js +13 -10
- package/apps/cli/dist/commands/lib/session-output.js +10 -5
- package/apps/cli/dist/commands/lib/session-view.js +0 -1
- package/apps/cli/dist/commands/lib/status.js +0 -1
- package/apps/cli/dist/commands/lib/types.d.ts +3 -0
- package/apps/cli/dist/commands/lib/types.js +0 -1
- package/apps/cli/dist/commands/lib/usage.js +0 -1
- package/apps/cli/dist/commands/lib/workspace.js +0 -1
- package/apps/cli/dist/commands/loop.js +63 -16
- package/apps/cli/dist/commands/machine.js +0 -1
- package/apps/cli/dist/commands/relay.js +20 -2
- package/apps/cli/dist/commands/remote.js +0 -1
- package/apps/cli/dist/commands/session.js +0 -1
- package/apps/cli/dist/commands/token.js +0 -1
- package/apps/cli/dist/commands/turn.js +0 -1
- package/apps/cli/dist/errors.js +0 -1
- package/apps/cli/dist/index.js +0 -1
- package/apps/daemon/dist/app.js +0 -1
- package/apps/daemon/dist/args.js +0 -1
- package/apps/daemon/dist/audit.js +0 -1
- package/apps/daemon/dist/cli.js +0 -1
- package/apps/daemon/dist/config.js +0 -1
- package/apps/daemon/dist/errors.js +0 -1
- package/apps/daemon/dist/loop/runner.js +99 -28
- package/apps/daemon/dist/loop/store.js +0 -1
- package/apps/daemon/dist/pair.js +0 -1
- package/apps/daemon/dist/paths.js +0 -1
- package/apps/daemon/dist/process.js +0 -1
- package/apps/daemon/dist/relay/connection.js +0 -1
- package/apps/daemon/dist/relay/devtools.js +10 -6
- package/apps/daemon/dist/relay/register.js +0 -1
- package/apps/daemon/dist/relay/send.js +0 -1
- package/apps/daemon/dist/relay-http.js +0 -1
- package/apps/daemon/dist/runtime/external-provider.d.ts +1 -1
- package/apps/daemon/dist/runtime/external-provider.js +255 -105
- package/apps/daemon/dist/runtime.d.ts +1 -1
- package/apps/daemon/dist/runtime.js +2 -3
- package/apps/daemon/dist/session/create-session.js +0 -1
- package/apps/daemon/dist/session/directory.d.ts +2 -1
- package/apps/daemon/dist/session/directory.js +58 -53
- package/apps/daemon/dist/session/events.js +0 -1
- package/apps/daemon/dist/session/file-preview.js +4 -7
- package/apps/daemon/dist/session/lifecycle.js +0 -1
- package/apps/daemon/dist/session/metadata.js +0 -1
- package/apps/daemon/dist/session/primitives.js +0 -1
- package/apps/daemon/dist/session/prompt.js +163 -85
- package/apps/daemon/dist/session/runtime-handle.js +0 -1
- package/apps/daemon/dist/session/workspace-paths.d.ts +5 -0
- package/apps/daemon/dist/session/workspace-paths.js +42 -0
- package/apps/daemon/dist/start.js +40 -12
- package/apps/daemon/dist/state.d.ts +5 -0
- package/apps/daemon/dist/state.js +0 -1
- package/apps/daemon/dist/turn-coordinator.js +0 -1
- package/apps/daemon/dist/types.d.ts +12 -0
- package/apps/daemon/dist/types.js +0 -1
- package/apps/daemon/dist/utils.js +0 -1
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/auth.js +13 -3
- package/apps/relay/dist/connections.d.ts +22 -7
- package/apps/relay/dist/connections.js +0 -1
- package/apps/relay/dist/controller-handlers.d.ts +17 -7
- package/apps/relay/dist/controller-handlers.js +437 -25
- package/apps/relay/dist/db.js +162 -10
- package/apps/relay/dist/errors.js +0 -1
- package/apps/relay/dist/event-visibility.d.ts +7 -0
- package/apps/relay/dist/event-visibility.js +46 -0
- package/apps/relay/dist/http-routes.js +104 -52
- package/apps/relay/dist/http-schemas.d.ts +9 -1
- package/apps/relay/dist/http-schemas.js +5 -3
- package/apps/relay/dist/index.js +19 -2
- package/apps/relay/dist/machine-command-result-handlers.d.ts +4 -0
- package/apps/relay/dist/machine-command-result-handlers.js +532 -0
- package/apps/relay/dist/machine-handler-context.d.ts +79 -0
- package/apps/relay/dist/machine-handler-context.js +1 -0
- package/apps/relay/dist/machine-handlers.d.ts +3 -50
- package/apps/relay/dist/machine-handlers.js +9 -750
- package/apps/relay/dist/machine-reliability.d.ts +7 -0
- package/apps/relay/dist/machine-reliability.js +12 -0
- package/apps/relay/dist/projection-repository.d.ts +87 -0
- package/apps/relay/dist/projection-repository.js +220 -0
- package/apps/relay/dist/projections.js +0 -1
- package/apps/relay/dist/relay-context.d.ts +62 -7
- package/apps/relay/dist/relay-context.js +314 -39
- package/apps/relay/dist/repositories.js +2 -2
- package/apps/relay/dist/retention.js +0 -1
- package/apps/relay/dist/scope.d.ts +14 -1
- package/apps/relay/dist/scope.js +144 -17
- package/apps/relay/dist/security.js +0 -1
- package/apps/relay/dist/session-projection-reducer.d.ts +4 -0
- package/apps/relay/dist/session-projection-reducer.js +448 -0
- package/apps/relay/dist/types.d.ts +8 -0
- package/apps/relay/dist/types.js +0 -1
- package/apps/relay/dist/websocket.js +41 -3
- package/npm-shrinkwrap.json +1615 -0
- package/package.json +25 -8
- package/packages/agent-sdk/dist/index.d.ts +15 -232
- package/packages/agent-sdk/dist/index.js +44 -14
- package/packages/client/dist/account.d.ts +0 -1
- package/packages/client/dist/account.js +0 -1
- package/packages/client/dist/client.d.ts +3 -3
- package/packages/client/dist/client.js +17 -5
- package/packages/client/dist/errors.d.ts +0 -1
- package/packages/client/dist/errors.js +0 -1
- package/packages/client/dist/http.d.ts +0 -1
- package/packages/client/dist/http.js +0 -1
- package/packages/client/dist/index.d.ts +4 -2
- package/packages/client/dist/index.js +2 -1
- package/packages/client/dist/live.d.ts +178 -0
- package/packages/client/dist/live.js +1052 -0
- package/packages/client/dist/parsers.d.ts +0 -1
- package/packages/client/dist/parsers.js +3 -1
- package/packages/client/dist/session-event-order.d.ts +16 -0
- package/packages/client/dist/session-event-order.js +83 -0
- package/packages/client/dist/session-event-window.d.ts +5 -0
- package/packages/client/dist/session-event-window.js +54 -0
- package/packages/client/dist/transport.d.ts +4 -6
- package/packages/client/dist/transport.js +59 -24
- package/packages/client/dist/types.d.ts +4 -1
- package/packages/client/dist/types.js +0 -1
- package/packages/client/dist/validation.d.ts +0 -1
- package/packages/client/dist/validation.js +0 -1
- package/packages/pie-provider/dist/index.d.ts +0 -1
- package/packages/pie-provider/dist/index.js +90 -50
- package/packages/provider-protocol/dist/index.d.ts +843 -0
- package/packages/provider-protocol/dist/index.js +332 -0
- package/packages/runtime/dist/index.d.ts +4 -111
- package/packages/runtime/dist/index.js +0 -1
- package/packages/runtime-acpx/dist/index.d.ts +0 -1
- package/packages/runtime-acpx/dist/index.js +0 -1
- package/packages/runtime-cli/dist/async-queue.d.ts +0 -1
- package/packages/runtime-cli/dist/async-queue.js +0 -1
- package/packages/runtime-cli/dist/claude-history.d.ts +0 -1
- package/packages/runtime-cli/dist/claude-history.js +0 -1
- package/packages/runtime-cli/dist/claude-json.d.ts +0 -1
- package/packages/runtime-cli/dist/claude-json.js +0 -1
- package/packages/runtime-cli/dist/claude-rename.d.ts +0 -1
- package/packages/runtime-cli/dist/claude-rename.js +0 -1
- package/packages/runtime-cli/dist/claude-session-store.d.ts +0 -1
- package/packages/runtime-cli/dist/claude-session-store.js +0 -1
- package/packages/runtime-cli/dist/claude-turn.d.ts +0 -1
- package/packages/runtime-cli/dist/claude-turn.js +37 -8
- package/packages/runtime-cli/dist/codex-app-server.d.ts +18 -3
- package/packages/runtime-cli/dist/codex-app-server.js +160 -19
- package/packages/runtime-cli/dist/codex-approvals.d.ts +0 -1
- package/packages/runtime-cli/dist/codex-approvals.js +0 -1
- package/packages/runtime-cli/dist/codex-history.d.ts +0 -1
- package/packages/runtime-cli/dist/codex-history.js +0 -1
- package/packages/runtime-cli/dist/codex-json-rpc.d.ts +0 -1
- package/packages/runtime-cli/dist/codex-json-rpc.js +51 -3
- package/packages/runtime-cli/dist/codex-lifecycle.d.ts +0 -1
- package/packages/runtime-cli/dist/codex-lifecycle.js +0 -1
- package/packages/runtime-cli/dist/codex-protocol.d.ts +0 -1
- package/packages/runtime-cli/dist/codex-protocol.js +0 -1
- package/packages/runtime-cli/dist/command-resolver.d.ts +0 -1
- package/packages/runtime-cli/dist/command-resolver.js +0 -1
- package/packages/runtime-cli/dist/history-merge.d.ts +0 -1
- package/packages/runtime-cli/dist/history-merge.js +15 -3
- package/packages/runtime-cli/dist/index.d.ts +0 -1
- package/packages/runtime-cli/dist/index.js +15 -2
- package/packages/runtime-cli/dist/permissions.d.ts +0 -1
- package/packages/runtime-cli/dist/permissions.js +0 -1
- package/packages/runtime-cli/dist/session-store.d.ts +2 -2
- package/packages/runtime-cli/dist/session-store.js +0 -1
- package/packages/shared/dist/args.d.ts +0 -1
- package/packages/shared/dist/args.js +0 -1
- package/packages/shared/dist/crypto.d.ts +0 -1
- package/packages/shared/dist/crypto.js +0 -1
- package/packages/shared/dist/ids.d.ts +0 -1
- package/packages/shared/dist/ids.js +0 -1
- package/packages/shared/dist/index.d.ts +0 -1
- package/packages/shared/dist/index.js +0 -1
- package/packages/shared/dist/protocol-schemas.d.ts +0 -1
- package/packages/shared/dist/protocol-schemas.js +0 -1
- package/packages/shared/dist/protocol-types.d.ts +2 -1
- package/packages/shared/dist/protocol-types.js +0 -1
- package/packages/shared/dist/protocol.d.ts +19 -1
- package/packages/shared/dist/protocol.js +23 -1
- package/packages/shared/dist/session-name.d.ts +0 -1
- package/packages/shared/dist/session-name.js +0 -1
- package/packages/shared/dist/session-state.d.ts +0 -1
- 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
|
```
|
|
@@ -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
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
114
|
-
await
|
|
115
|
-
await
|
|
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
|
|
@@ -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,9 +1,10 @@
|
|
|
1
|
-
import { CliError, ControllerClient, DEFAULT_COMMAND_TIMEOUT_MS, appendLoop, compactText,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
package/apps/cli/dist/errors.js
CHANGED
package/apps/cli/dist/index.js
CHANGED
package/apps/daemon/dist/app.js
CHANGED
package/apps/daemon/dist/args.js
CHANGED
package/apps/daemon/dist/cli.js
CHANGED