@cydm/happy-elves 0.1.0-beta.267 → 0.1.0-beta.271
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/cli/dist/commands/lib/args.js +4 -0
- package/apps/cli/dist/commands/lib/usage.js +4 -0
- package/apps/cli/dist/commands/memory.js +26 -6
- package/apps/cli/dist/commands/remote.js +130 -1
- package/apps/cli/package.json +1 -1
- package/apps/daemon/dist/paths.d.ts +2 -0
- package/apps/daemon/dist/paths.js +2 -0
- package/apps/daemon/dist/relay/connection.js +5 -0
- package/apps/daemon/dist/relay/lifecycle.d.ts +7 -0
- package/apps/daemon/dist/relay/lifecycle.js +476 -0
- package/apps/daemon/dist/relay/register.js +1 -0
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/controller-handlers.js +33 -0
- package/apps/relay/dist/machine-handlers.js +12 -0
- package/apps/relay/dist/projections.js +2 -0
- package/package.json +1 -1
- package/packages/client/dist/client.d.ts +2 -1
- package/packages/client/dist/client.js +16 -1
- package/packages/client/dist/index.d.ts +1 -1
- package/packages/client/dist/transport.d.ts +5 -0
- package/packages/client/dist/transport.js +7 -0
- package/packages/client/dist/types.d.ts +17 -1
- package/packages/shared/dist/protocol-schemas.d.ts +78 -2
- package/packages/shared/dist/protocol-schemas.js +30 -0
- package/packages/shared/dist/protocol-types.d.ts +33 -0
- package/packages/shared/dist/protocol.d.ts +30 -2
- package/packages/shared/dist/protocol.js +33 -1
|
@@ -32,6 +32,7 @@ const valueFlags = new Set([
|
|
|
32
32
|
"memory-project",
|
|
33
33
|
"memory-top-n",
|
|
34
34
|
"name",
|
|
35
|
+
"package-url",
|
|
35
36
|
"model",
|
|
36
37
|
"mode",
|
|
37
38
|
"output",
|
|
@@ -61,6 +62,7 @@ const valueFlags = new Set([
|
|
|
61
62
|
"sessions",
|
|
62
63
|
"session",
|
|
63
64
|
"secret",
|
|
65
|
+
"sha256",
|
|
64
66
|
"since",
|
|
65
67
|
"source",
|
|
66
68
|
"stop-on",
|
|
@@ -81,6 +83,8 @@ const valueFlags = new Set([
|
|
|
81
83
|
"url",
|
|
82
84
|
"turn",
|
|
83
85
|
"until",
|
|
86
|
+
"version",
|
|
87
|
+
"wait-timeout",
|
|
84
88
|
]);
|
|
85
89
|
const booleanFlags = new Set([
|
|
86
90
|
"all",
|
|
@@ -30,6 +30,10 @@ Check local controller config, relay reachability, and daemon state.
|
|
|
30
30
|
happy-elves remote add-controller [--device-id <id>] [--device-name <name>] --json
|
|
31
31
|
happy-elves remote rename <deviceId> --name <name> --json
|
|
32
32
|
happy-elves remote revoke <deviceId> --json
|
|
33
|
+
happy-elves remote daemon-status <machineId> [--json]
|
|
34
|
+
happy-elves remote daemon-restart <machineId> [--wait] [--timeout 2m] [--json]
|
|
35
|
+
happy-elves remote daemon-update <machineId> [--version latest|0.1.0-beta.N] [--package-url <url> --sha256 <hex>] [--wait] [--timeout 10m] [--json]
|
|
36
|
+
happy-elves remote daemon-logs <machineId> [--tail 100] [--json]
|
|
33
37
|
`,
|
|
34
38
|
machine: `Usage:
|
|
35
39
|
happy-elves machine list [--verbose] [--json]
|
|
@@ -9,6 +9,7 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
9
9
|
const projectKey = await resolveMemoryProjectKey(client, flags, machineId);
|
|
10
10
|
const result = await client.memory({ machineId, action: "overview", projectKey, mode: parseMemoryMode(flags.mode) ?? "readonly" });
|
|
11
11
|
if (!wantsJson(flags)) {
|
|
12
|
+
printMemoryProject(projectKey);
|
|
12
13
|
const files = result.overview?.files ?? [];
|
|
13
14
|
if (files.length === 0)
|
|
14
15
|
console.log("No memory files.");
|
|
@@ -17,7 +18,7 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
17
18
|
console.log(`${file.layer} ${file.path} ${file.lineCount} lines ${file.updatedAt}`);
|
|
18
19
|
return true;
|
|
19
20
|
}
|
|
20
|
-
ok(`memory.${action}`, result);
|
|
21
|
+
ok(`memory.${action}`, withMemoryProject(result, projectKey));
|
|
21
22
|
return true;
|
|
22
23
|
}
|
|
23
24
|
if (action === "save" || action === "write") {
|
|
@@ -43,7 +44,8 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
43
44
|
...(typeof flags.date === "string" ? { date: flags.date } : {}),
|
|
44
45
|
},
|
|
45
46
|
});
|
|
46
|
-
|
|
47
|
+
printMemoryProjectIfHuman(flags, projectKey);
|
|
48
|
+
ok("memory.save", withMemoryProject(result, projectKey));
|
|
47
49
|
return true;
|
|
48
50
|
}
|
|
49
51
|
if (action === "read") {
|
|
@@ -52,10 +54,11 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
52
54
|
const path = typeof flags.path === "string" ? flags.path : requirePositional(positional[0], "path");
|
|
53
55
|
const result = await client.memory({ machineId, action: "read", projectKey, path });
|
|
54
56
|
if (!wantsJson(flags)) {
|
|
57
|
+
printMemoryProject(projectKey);
|
|
55
58
|
console.log(result.content ?? "");
|
|
56
59
|
return true;
|
|
57
60
|
}
|
|
58
|
-
ok("memory.read", result);
|
|
61
|
+
ok("memory.read", withMemoryProject(result, projectKey));
|
|
59
62
|
return true;
|
|
60
63
|
}
|
|
61
64
|
if (action === "search") {
|
|
@@ -67,6 +70,7 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
67
70
|
throw new CliError("Invalid --limit", "INVALID_ARGUMENT");
|
|
68
71
|
const result = await client.memory({ machineId, action: "search", projectKey, query, limit });
|
|
69
72
|
if (!wantsJson(flags)) {
|
|
73
|
+
printMemoryProject(projectKey);
|
|
70
74
|
const results = result.results ?? [];
|
|
71
75
|
if (results.length === 0)
|
|
72
76
|
console.log("No matching memory.");
|
|
@@ -80,7 +84,7 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
80
84
|
}
|
|
81
85
|
return true;
|
|
82
86
|
}
|
|
83
|
-
ok("memory.search", result);
|
|
87
|
+
ok("memory.search", withMemoryProject(result, projectKey));
|
|
84
88
|
return true;
|
|
85
89
|
}
|
|
86
90
|
if (action === "promote") {
|
|
@@ -101,17 +105,33 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
101
105
|
...(typeof flags.title === "string" ? { title: flags.title } : {}),
|
|
102
106
|
},
|
|
103
107
|
});
|
|
104
|
-
|
|
108
|
+
printMemoryProjectIfHuman(flags, projectKey);
|
|
109
|
+
ok("memory.promote", withMemoryProject(result, projectKey));
|
|
105
110
|
return true;
|
|
106
111
|
}
|
|
107
112
|
if (action === "reindex" || action === "sync") {
|
|
108
113
|
const machineId = await resolveMemoryMachineId(client, flags);
|
|
109
114
|
const projectKey = await resolveMemoryProjectKey(client, flags, machineId);
|
|
110
|
-
|
|
115
|
+
const result = await client.memory({ machineId, action, projectKey, mode: parseMemoryMode(flags.mode) ?? "readonly" });
|
|
116
|
+
printMemoryProjectIfHuman(flags, projectKey);
|
|
117
|
+
ok(`memory.${action}`, withMemoryProject(result, projectKey));
|
|
111
118
|
return true;
|
|
112
119
|
}
|
|
113
120
|
return false;
|
|
114
121
|
}
|
|
122
|
+
function withMemoryProject(result, projectKey) {
|
|
123
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
124
|
+
return { ...result, projectKey };
|
|
125
|
+
}
|
|
126
|
+
return { result, projectKey };
|
|
127
|
+
}
|
|
128
|
+
function printMemoryProjectIfHuman(flags, projectKey) {
|
|
129
|
+
if (!wantsJson(flags))
|
|
130
|
+
printMemoryProject(projectKey);
|
|
131
|
+
}
|
|
132
|
+
function printMemoryProject(projectKey) {
|
|
133
|
+
console.error(`Memory project: ${projectKey}`);
|
|
134
|
+
}
|
|
115
135
|
async function resolveMemoryMachineId(client, flags) {
|
|
116
136
|
if (typeof flags.machine === "string" && flags.machine.length > 0)
|
|
117
137
|
return flags.machine;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
|
-
import { ControllerClient, configPath, ok, parseControllerJoinUrl, parseDurationMs, randomId, readConfig, relayHealth, requirePositional, requireRelayUrl, requireString, showMachine, wantsJson, wantsVerbose, writeConfig } from "./lib/index.js";
|
|
2
|
+
import { CliError, ControllerClient, configPath, ok, parseControllerJoinUrl, parseDurationMs, randomId, readConfig, relayHealth, requirePositional, requireRelayUrl, requireString, showMachine, wantsJson, wantsVerbose, writeConfig } from "./lib/index.js";
|
|
3
3
|
export async function handleRemote({ domain, action, positional, flags }) {
|
|
4
4
|
if (domain === "remote" && action === "devices") {
|
|
5
5
|
const config = await readConfig(flags);
|
|
@@ -81,6 +81,56 @@ export async function handleRemote({ domain, action, positional, flags }) {
|
|
|
81
81
|
ok("remote.status", data);
|
|
82
82
|
return true;
|
|
83
83
|
}
|
|
84
|
+
if (domain === "remote" && action === "daemon-status") {
|
|
85
|
+
const config = await readConfig(flags);
|
|
86
|
+
const machineId = requirePositional(positional[0], "machineId");
|
|
87
|
+
const result = await new ControllerClient(config).daemonLifecycle({
|
|
88
|
+
machineId,
|
|
89
|
+
action: "status",
|
|
90
|
+
waitTimeoutMs: parseDurationMs(flags["wait-timeout"], 45_000),
|
|
91
|
+
});
|
|
92
|
+
printDaemonLifecycle("remote.daemon-status", result, flags);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (domain === "remote" && action === "daemon-logs") {
|
|
96
|
+
const config = await readConfig(flags);
|
|
97
|
+
const machineId = requirePositional(positional[0], "machineId");
|
|
98
|
+
const tail = typeof flags.tail === "string" ? Number(flags.tail) : 100;
|
|
99
|
+
if (!Number.isInteger(tail) || tail <= 0)
|
|
100
|
+
throw new CliError("--tail must be a positive integer", "INVALID_ARGUMENT");
|
|
101
|
+
const result = await new ControllerClient(config).daemonLifecycle({
|
|
102
|
+
machineId,
|
|
103
|
+
action: "logs",
|
|
104
|
+
tail,
|
|
105
|
+
waitTimeoutMs: parseDurationMs(flags["wait-timeout"], 45_000),
|
|
106
|
+
});
|
|
107
|
+
printDaemonLifecycle("remote.daemon-logs", result, flags);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (domain === "remote" && (action === "daemon-restart" || action === "daemon-update")) {
|
|
111
|
+
const config = await readConfig(flags);
|
|
112
|
+
const client = new ControllerClient(config);
|
|
113
|
+
const machineId = requirePositional(positional[0], "machineId");
|
|
114
|
+
const isUpdate = action === "daemon-update";
|
|
115
|
+
const result = await client.daemonLifecycle({
|
|
116
|
+
machineId,
|
|
117
|
+
action: isUpdate ? "update" : "restart",
|
|
118
|
+
targetVersion: isUpdate ? (typeof flags.version === "string" ? flags.version : "latest") : undefined,
|
|
119
|
+
packageUrl: isUpdate && typeof flags["package-url"] === "string" ? flags["package-url"] : undefined,
|
|
120
|
+
sha256: isUpdate && typeof flags.sha256 === "string" ? flags.sha256 : undefined,
|
|
121
|
+
waitTimeoutMs: parseDurationMs(flags["wait-timeout"], 45_000),
|
|
122
|
+
});
|
|
123
|
+
let finalResult = result;
|
|
124
|
+
if (result.accepted === true && flags.wait) {
|
|
125
|
+
finalResult = await waitForDaemonLifecycle(client, machineId, result.requestId, isUpdate ? "update" : "restart", {
|
|
126
|
+
timeoutMs: parseDurationMs(flags.timeout, isUpdate ? 10 * 60_000 : 2 * 60_000),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
printDaemonLifecycle(isUpdate ? "remote.daemon-update" : "remote.daemon-restart", finalResult, flags);
|
|
130
|
+
if (finalResult.accepted === false || finalResult.updateState?.status === "failed")
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
84
134
|
if (domain === "remote" &&
|
|
85
135
|
((action === "pairing" && positional[0] === "new") || (action === "machine-pairing" && positional[0] === "new"))) {
|
|
86
136
|
const config = await readConfig(flags);
|
|
@@ -148,4 +198,83 @@ export async function handleRemote({ domain, action, positional, flags }) {
|
|
|
148
198
|
}
|
|
149
199
|
return false;
|
|
150
200
|
}
|
|
201
|
+
function printDaemonLifecycle(type, result, flags) {
|
|
202
|
+
const success = result.accepted !== false && result.updateState?.status !== "failed";
|
|
203
|
+
if (wantsJson(flags)) {
|
|
204
|
+
ok(type, result, { requestId: result.requestId, machineId: result.machineId }, success);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const state = result.updateState;
|
|
208
|
+
const summary = [
|
|
209
|
+
`Machine: ${result.machineId}`,
|
|
210
|
+
`Action: ${result.action}`,
|
|
211
|
+
`Version: ${result.version ?? "-"}`,
|
|
212
|
+
`Running: ${result.running === undefined ? "-" : result.running ? "yes" : "no"}`,
|
|
213
|
+
`Install: ${result.installMode ?? "-"}`,
|
|
214
|
+
];
|
|
215
|
+
if (result.updateSupported === false)
|
|
216
|
+
summary.push(`Update: unsupported (${result.updateUnsupportedReason ?? "unknown"})`);
|
|
217
|
+
if (result.accepted !== undefined)
|
|
218
|
+
summary.push(`Accepted: ${result.accepted ? "yes" : "no"}`);
|
|
219
|
+
if (state) {
|
|
220
|
+
summary.push(`Update state: ${state.status}`);
|
|
221
|
+
if (state.targetVersion)
|
|
222
|
+
summary.push(`Target: ${state.targetVersion}`);
|
|
223
|
+
if (state.toVersion)
|
|
224
|
+
summary.push(`Installed: ${state.toVersion}`);
|
|
225
|
+
if (state.logPath)
|
|
226
|
+
summary.push(`Log: ${state.logPath}`);
|
|
227
|
+
if (state.error)
|
|
228
|
+
summary.push(`Error: ${state.error}`);
|
|
229
|
+
}
|
|
230
|
+
for (const line of summary)
|
|
231
|
+
console.log(line);
|
|
232
|
+
if (result.logs?.length) {
|
|
233
|
+
console.log("Logs:");
|
|
234
|
+
for (const line of result.logs)
|
|
235
|
+
console.log(line);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function waitForDaemonLifecycle(client, machineId, requestId, action, options) {
|
|
239
|
+
const deadline = Date.now() + Math.max(1, options.timeoutMs);
|
|
240
|
+
let lastResult;
|
|
241
|
+
let lastError;
|
|
242
|
+
while (Date.now() < deadline) {
|
|
243
|
+
try {
|
|
244
|
+
const result = await client.daemonLifecycle({ machineId, action: "status", waitTimeoutMs: 15_000 });
|
|
245
|
+
lastResult = result;
|
|
246
|
+
const state = result.updateState;
|
|
247
|
+
if (state?.requestId === requestId) {
|
|
248
|
+
if (state.status === "succeeded" || state.status === "failed")
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
else if (action === "restart" && result.running === true) {
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
lastError = error;
|
|
257
|
+
const code = errorCode(error);
|
|
258
|
+
if (code !== "MACHINE_OFFLINE" && code !== "COMMAND_TIMEOUT" && code !== "RELAY_CONNECT_TIMEOUT")
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
await sleep(1000);
|
|
262
|
+
}
|
|
263
|
+
if (lastResult)
|
|
264
|
+
return {
|
|
265
|
+
...lastResult,
|
|
266
|
+
accepted: false,
|
|
267
|
+
updateUnsupportedReason: `WAIT_TIMEOUT_${requestId}`,
|
|
268
|
+
};
|
|
269
|
+
throw new CliError(`Timed out waiting for daemon ${action}: ${errorMessage(lastError)}`, "COMMAND_TIMEOUT");
|
|
270
|
+
}
|
|
271
|
+
function errorCode(error) {
|
|
272
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : undefined;
|
|
273
|
+
}
|
|
274
|
+
function errorMessage(error) {
|
|
275
|
+
return error instanceof Error ? error.message : String(error ?? "unknown");
|
|
276
|
+
}
|
|
277
|
+
function sleep(ms) {
|
|
278
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
279
|
+
}
|
|
151
280
|
//# sourceMappingURL=remote.js.map
|
package/apps/cli/package.json
CHANGED
|
@@ -8,6 +8,8 @@ export declare const loopStoreLockPath: string;
|
|
|
8
8
|
export declare const gatewayStorePath: string;
|
|
9
9
|
export declare const scheduleStorePath: string;
|
|
10
10
|
export declare const memoryRootDir: string;
|
|
11
|
+
export declare const updatesDir: string;
|
|
12
|
+
export declare const updateStatePath: string;
|
|
11
13
|
export declare const pidPath: string;
|
|
12
14
|
export declare const daemonScriptPath: string;
|
|
13
15
|
export declare const daemonScriptRelativePath: string;
|
|
@@ -13,6 +13,8 @@ export const loopStoreLockPath = path.join(configDir, "loops.json.lock");
|
|
|
13
13
|
export const gatewayStorePath = path.join(configDir, "gateway.json");
|
|
14
14
|
export const scheduleStorePath = path.join(configDir, "schedules.json");
|
|
15
15
|
export const memoryRootDir = path.join(configDir, "memory");
|
|
16
|
+
export const updatesDir = path.join(configDir, "updates");
|
|
17
|
+
export const updateStatePath = path.join(configDir, "update-state.json");
|
|
16
18
|
export const pidPath = path.join(configDir, "daemon.pid");
|
|
17
19
|
export const daemonScriptPath = fileURLToPath(new URL(import.meta.url.endsWith(".ts") ? "./cli.ts" : "./cli.js", import.meta.url));
|
|
18
20
|
export const daemonScriptRelativePath = path.relative(process.cwd(), daemonScriptPath);
|
|
@@ -12,6 +12,7 @@ import { handleFork, handleRename, handleRewind } from "../session/primitives.js
|
|
|
12
12
|
import { handleReconcileTurn } from "../session/reconcile.js";
|
|
13
13
|
import { parseRelayMessage, relayWsUrl } from "../relay-http.js";
|
|
14
14
|
import { handleDevExec, handleDiagnose } from "./devtools.js";
|
|
15
|
+
import { handleDaemonLifecycleCommand } from "./lifecycle.js";
|
|
15
16
|
import { registerMachine } from "./register.js";
|
|
16
17
|
import { clearActiveRelaySocket, configureRelaySender, flushRelayOutbox, handleMachineMessageAck, send, sendCommandResponse, setActiveRelaySocket, } from "./send.js";
|
|
17
18
|
async function handleServerMessage(ws, config, message) {
|
|
@@ -63,6 +64,10 @@ async function handleServerMessage(ws, config, message) {
|
|
|
63
64
|
await handleDevExec(ws, config, message);
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
67
|
+
if (message.type === "machine:daemonLifecycle") {
|
|
68
|
+
await handleDaemonLifecycleCommand(ws, config, message);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
66
71
|
if (message.type === "machine:gateway") {
|
|
67
72
|
await handleGatewayCommand(ws, config, message);
|
|
68
73
|
return;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MachineCommand } from "../../../../packages/shared/dist/index.js";
|
|
2
|
+
import type { DaemonConfig } from "../types.js";
|
|
3
|
+
type DaemonLifecycleCommand = Extract<MachineCommand, {
|
|
4
|
+
type: "machine:daemonLifecycle";
|
|
5
|
+
}>;
|
|
6
|
+
export declare function handleDaemonLifecycleCommand(ws: WebSocket, config: DaemonConfig, command: DaemonLifecycleCommand): Promise<void>;
|
|
7
|
+
export {};
|