@cydm/happy-elves 0.1.0-beta.82 → 0.1.0-beta.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/cli/dist/commands/daemon.js +5 -1
- package/apps/cli/dist/commands/gateway.js +96 -38
- package/apps/cli/dist/commands/lib/args.js +17 -0
- package/apps/cli/dist/commands/lib/doctor.js +133 -11
- package/apps/cli/dist/commands/lib/json.d.ts +1 -1
- package/apps/cli/dist/commands/lib/json.js +5 -2
- package/apps/cli/dist/commands/lib/status.js +5 -1
- package/apps/cli/dist/commands/lib/usage.js +30 -31
- package/apps/cli/dist/commands/memory.js +96 -63
- package/apps/cli/dist/commands/relay.js +5 -1
- package/apps/cli/dist/commands/schedule.js +156 -143
- package/apps/cli/dist/commands/turn.js +59 -23
- package/apps/cli/package.json +6 -0
- package/apps/daemon/dist/gateway/adapters.d.ts +37 -0
- package/apps/daemon/dist/gateway/adapters.js +2 -0
- package/apps/daemon/dist/gateway/feishu.d.ts +69 -0
- package/apps/daemon/dist/gateway/feishu.js +493 -0
- package/apps/daemon/dist/gateway/format.d.ts +2 -0
- package/apps/daemon/dist/gateway/format.js +19 -0
- package/apps/daemon/dist/gateway/handler.d.ts +7 -0
- package/apps/daemon/dist/gateway/handler.js +125 -0
- package/apps/daemon/dist/gateway/runtime.d.ts +33 -0
- package/apps/daemon/dist/gateway/runtime.js +738 -0
- package/apps/daemon/dist/gateway/store.d.ts +91 -0
- package/apps/daemon/dist/gateway/store.js +125 -0
- package/apps/daemon/dist/memory/handler.d.ts +7 -0
- package/apps/daemon/dist/memory/handler.js +95 -0
- package/apps/daemon/dist/memory/manager.d.ts +41 -0
- package/apps/daemon/dist/memory/manager.js +319 -0
- package/apps/daemon/dist/paths.d.ts +3 -0
- package/apps/daemon/dist/paths.js +3 -0
- package/apps/daemon/dist/relay/admission.d.ts +28 -0
- package/apps/daemon/dist/relay/admission.js +31 -0
- package/apps/daemon/dist/relay/connection.js +27 -0
- package/apps/daemon/dist/schedule/handler.d.ts +7 -0
- package/apps/daemon/dist/schedule/handler.js +127 -0
- package/apps/daemon/dist/schedule/runner.d.ts +12 -0
- package/apps/daemon/dist/schedule/runner.js +447 -0
- package/apps/daemon/dist/schedule/store.d.ts +10 -0
- package/apps/daemon/dist/schedule/store.js +137 -0
- package/apps/daemon/dist/session/events.js +27 -29
- package/apps/daemon/dist/session/prompt.js +104 -2
- package/apps/daemon/dist/start.js +5 -1
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/controller-handlers.d.ts +21 -0
- package/apps/relay/dist/controller-handlers.js +100 -2
- package/apps/relay/dist/db.js +73 -0
- package/apps/relay/dist/errors.d.ts +2 -1
- package/apps/relay/dist/errors.js +3 -1
- package/apps/relay/dist/http-routes.js +208 -5
- package/apps/relay/dist/http-schemas.d.ts +77 -0
- package/apps/relay/dist/http-schemas.js +26 -0
- package/apps/relay/dist/index.js +1 -1
- package/apps/relay/dist/machine-handlers.d.ts +12 -0
- package/apps/relay/dist/machine-handlers.js +57 -2
- package/apps/relay/dist/relay-context.d.ts +26 -1
- package/apps/relay/dist/relay-context.js +54 -0
- package/apps/relay/dist/types.d.ts +15 -0
- package/package.json +3 -2
- package/packages/agent-sdk/dist/index.d.ts +10 -0
- package/packages/client/dist/client.d.ts +4 -1
- package/packages/client/dist/client.js +62 -1
- package/packages/client/dist/index.d.ts +1 -1
- package/packages/client/dist/transport.d.ts +15 -0
- package/packages/client/dist/transport.js +21 -0
- package/packages/client/dist/types.d.ts +42 -1
- package/packages/runtime/dist/index.d.ts +10 -0
- package/packages/runtime-cli/dist/codex-app-server.js +57 -0
- package/packages/shared/dist/protocol-schemas.d.ts +959 -0
- package/packages/shared/dist/protocol-schemas.js +224 -0
- package/packages/shared/dist/protocol-types.d.ts +291 -0
- package/packages/shared/dist/protocol.d.ts +108 -2
- package/packages/shared/dist/protocol.js +106 -1
|
@@ -57,9 +57,13 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
57
57
|
for (const check of checks) {
|
|
58
58
|
console.log(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`);
|
|
59
59
|
}
|
|
60
|
+
if (!data.ok)
|
|
61
|
+
process.exitCode = 1;
|
|
60
62
|
return true;
|
|
61
63
|
}
|
|
62
|
-
|
|
64
|
+
if (!data.ok)
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
ok("daemon.doctor", data, {}, data.ok);
|
|
63
67
|
return true;
|
|
64
68
|
}
|
|
65
69
|
if (domain === "daemon" && action === "config") {
|
|
@@ -1,71 +1,98 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { encryptJson } from "../../../../packages/shared/dist/index.js";
|
|
4
|
-
import { CliError, ControllerClient, ok, readConfig, readGatewayStore,
|
|
4
|
+
import { CliError, ControllerClient, ok, readConfig, readGatewayStore, requireString, updateGatewayStore, wantsJson, } from "./lib/index.js";
|
|
5
5
|
export async function handleGateway({ domain, action, positional, flags }) {
|
|
6
6
|
if (domain !== "gateway")
|
|
7
7
|
return false;
|
|
8
8
|
if (action === "enable") {
|
|
9
|
+
const config = await readConfig(flags);
|
|
10
|
+
const client = new ControllerClient(config);
|
|
9
11
|
const type = gatewayChannelType(typeof flags.channel === "string" ? flags.channel : "fake");
|
|
10
12
|
const id = gatewayChannelId(flags, positional[0], type);
|
|
11
13
|
const targetMachineId = requireString(flags, "machine");
|
|
12
|
-
const targetCwd =
|
|
14
|
+
const targetCwd = typeof flags.cwd === "string" ? flags.cwd : undefined;
|
|
15
|
+
const targetSessionId = typeof flags.session === "string" ? flags.session : undefined;
|
|
16
|
+
if (!targetCwd && !targetSessionId)
|
|
17
|
+
throw new CliError("Gateway enable requires --cwd or --session", "MISSING_ARGUMENT");
|
|
13
18
|
const defaultAgent = typeof flags.agent === "string" ? flags.agent : "codex";
|
|
14
19
|
const defaultModel = typeof flags.model === "string" ? flags.model : undefined;
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
const appId = typeof flags["app-id"] === "string" ? flags["app-id"] : undefined;
|
|
21
|
+
const appSecret = flags["app-secret-stdin"] === true ? await readSecretFromStdin() : undefined;
|
|
22
|
+
const domainValue = typeof flags.domain === "string" ? flags.domain : "feishu";
|
|
23
|
+
if (domainValue !== "feishu" && domainValue !== "lark")
|
|
24
|
+
throw new CliError("Gateway --domain must be feishu or lark", "INVALID_ARGUMENT");
|
|
25
|
+
const inboundMode = typeof flags.inbound === "string" ? flags.inbound : "mention";
|
|
26
|
+
if (inboundMode !== "mention" && inboundMode !== "all")
|
|
27
|
+
throw new CliError("Gateway --inbound must be mention or all", "INVALID_ARGUMENT");
|
|
28
|
+
const reactionEmoji = typeof flags["reaction-emoji"] === "string" ? flags["reaction-emoji"] : "Typing";
|
|
29
|
+
const allowedChatIds = csvFlag(flags["allow-chat"]);
|
|
30
|
+
const allowedSenderIds = csvFlag(flags["allow-sender"]);
|
|
31
|
+
const allowAnySender = flags["allow-any-sender"] === true;
|
|
32
|
+
if (type === "lark" && (!appId || !appSecret)) {
|
|
33
|
+
throw new CliError("Lark gateway requires --app-id and --app-secret-stdin; appSecret is stored only in the target daemon config.", "MISSING_ARGUMENT");
|
|
19
34
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
if (type === "lark" && !allowAnySender && allowedChatIds.length === 0 && allowedSenderIds.length === 0) {
|
|
36
|
+
throw new CliError("Lark gateway requires --allow-any-sender or at least one --allow-chat/--allow-sender policy.", "MISSING_ARGUMENT");
|
|
37
|
+
}
|
|
38
|
+
if (type === "lark" && (typeof flags.token === "string" || typeof flags.secret === "string")) {
|
|
39
|
+
throw new CliError("Lark gateway no longer accepts --token/--secret; use --app-id and --app-secret-stdin.", "INVALID_ARGUMENT");
|
|
40
|
+
}
|
|
41
|
+
const result = await client.gateway({
|
|
42
|
+
machineId: targetMachineId,
|
|
43
|
+
action: "upsert",
|
|
44
|
+
channel: {
|
|
25
45
|
id,
|
|
26
46
|
type,
|
|
27
47
|
enabled: true,
|
|
28
48
|
targetMachineId,
|
|
49
|
+
targetSessionId,
|
|
29
50
|
targetCwd,
|
|
30
51
|
defaultAgent,
|
|
31
52
|
...(defaultModel ? { defaultModel } : {}),
|
|
32
|
-
...(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
...(type === "lark" ? {
|
|
54
|
+
domain: domainValue,
|
|
55
|
+
appId,
|
|
56
|
+
appSecret,
|
|
57
|
+
inboundMode,
|
|
58
|
+
allowAnySender,
|
|
59
|
+
...(allowedChatIds.length > 0 ? { allowedChatIds } : {}),
|
|
60
|
+
...(allowedSenderIds.length > 0 ? { allowedSenderIds } : {}),
|
|
61
|
+
processingReaction: {
|
|
62
|
+
enabled: flags["disable-reaction"] !== true,
|
|
63
|
+
emojiType: reactionEmoji,
|
|
64
|
+
},
|
|
65
|
+
} : {}),
|
|
66
|
+
},
|
|
40
67
|
});
|
|
41
|
-
|
|
42
|
-
ok("gateway.enable", { channel: redactedGatewayChannel(channel) });
|
|
68
|
+
ok("gateway.enable", { channel: result.channel });
|
|
43
69
|
return true;
|
|
44
70
|
}
|
|
45
71
|
if (action === "disable") {
|
|
72
|
+
const config = await readConfig(flags);
|
|
73
|
+
const client = new ControllerClient(config);
|
|
74
|
+
const machineId = await resolveGatewayMachineId(client, flags);
|
|
46
75
|
const id = gatewayChannelId(flags, positional[0], "fake");
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
...current,
|
|
50
|
-
channels: current.channels.map((channel) => channel.id === id ? { ...channel, enabled: false, updatedAt: now } : channel),
|
|
51
|
-
}));
|
|
52
|
-
const channel = mustFindChannel(store, id);
|
|
53
|
-
ok("gateway.disable", { channel: redactedGatewayChannel(channel) });
|
|
76
|
+
const result = await client.gateway({ machineId, action: "disable", channelId: id });
|
|
77
|
+
ok("gateway.disable", { channel: result.channel });
|
|
54
78
|
return true;
|
|
55
79
|
}
|
|
56
80
|
if (action === "status") {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
const config = await readConfig(flags);
|
|
82
|
+
const client = new ControllerClient(config);
|
|
83
|
+
const machineId = await resolveGatewayMachineId(client, flags);
|
|
84
|
+
const result = await client.gateway({ machineId, action: "list" });
|
|
85
|
+
const includeDebug = flags["include-debug"] === true;
|
|
86
|
+
const channels = (result.channels ?? []).filter((channel) => includeDebug || channel.type === "lark");
|
|
87
|
+
const data = { machineId, channels };
|
|
62
88
|
if (!wantsJson(flags)) {
|
|
63
89
|
if (data.channels.length === 0) {
|
|
64
|
-
console.log("No gateways configured.");
|
|
90
|
+
console.log(includeDebug ? "No gateways configured." : "No product gateways configured.");
|
|
65
91
|
return true;
|
|
66
92
|
}
|
|
67
93
|
for (const channel of data.channels) {
|
|
68
|
-
|
|
94
|
+
const target = channel.targetSessionId ? `session:${channel.targetSessionId}` : channel.targetCwd ?? "";
|
|
95
|
+
console.log(`${channel.id} ${channel.type} ${channel.enabled ? "enabled" : "disabled"} ${target} ${channel.listening ? "listening" : "stopped"}`);
|
|
69
96
|
}
|
|
70
97
|
return true;
|
|
71
98
|
}
|
|
@@ -75,13 +102,19 @@ export async function handleGateway({ domain, action, positional, flags }) {
|
|
|
75
102
|
if (action === "test") {
|
|
76
103
|
const config = await readConfig(flags);
|
|
77
104
|
const client = new ControllerClient(config);
|
|
105
|
+
const machineId = await resolveGatewayMachineId(client, flags);
|
|
78
106
|
const channelId = gatewayChannelId(flags, positional[0], "fake");
|
|
79
107
|
const externalConversationId = requireString(flags, "conversation");
|
|
80
108
|
const externalMessageId = requireString(flags, "message-id");
|
|
81
109
|
const text = requireString(flags, "text");
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
const result = await client.gateway({
|
|
111
|
+
machineId,
|
|
112
|
+
action: "test",
|
|
113
|
+
channelId,
|
|
114
|
+
externalConversationId,
|
|
115
|
+
externalMessageId,
|
|
116
|
+
text,
|
|
117
|
+
});
|
|
85
118
|
ok("gateway.test", result);
|
|
86
119
|
return true;
|
|
87
120
|
}
|
|
@@ -337,6 +370,31 @@ function gatewayChannelType(value) {
|
|
|
337
370
|
function gatewayChannelId(flags, positional, fallbackType) {
|
|
338
371
|
return positional || (typeof flags.name === "string" ? flags.name : undefined) || (typeof flags.channel === "string" ? flags.channel : fallbackType);
|
|
339
372
|
}
|
|
373
|
+
function csvFlag(value) {
|
|
374
|
+
if (typeof value !== "string")
|
|
375
|
+
return [];
|
|
376
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
377
|
+
}
|
|
378
|
+
async function readSecretFromStdin() {
|
|
379
|
+
let value = "";
|
|
380
|
+
process.stdin.setEncoding("utf8");
|
|
381
|
+
for await (const chunk of process.stdin) {
|
|
382
|
+
value += chunk;
|
|
383
|
+
}
|
|
384
|
+
const secret = value.trim();
|
|
385
|
+
if (!secret)
|
|
386
|
+
throw new CliError("--app-secret-stdin was set but stdin was empty", "MISSING_ARGUMENT");
|
|
387
|
+
return secret;
|
|
388
|
+
}
|
|
389
|
+
async function resolveGatewayMachineId(client, flags) {
|
|
390
|
+
if (typeof flags.machine === "string" && flags.machine.length > 0)
|
|
391
|
+
return flags.machine;
|
|
392
|
+
const snapshot = await client.snapshot();
|
|
393
|
+
const machine = snapshot.machines.find((item) => item.online) ?? snapshot.machines[0];
|
|
394
|
+
if (!machine)
|
|
395
|
+
throw new CliError("No daemon machine is available; pass --machine <machineId>", "MACHINE_NOT_FOUND");
|
|
396
|
+
return machine.id;
|
|
397
|
+
}
|
|
340
398
|
function mustFindChannel(store, id) {
|
|
341
399
|
const channel = store.channels.find((item) => item.id === id);
|
|
342
400
|
if (!channel)
|
|
@@ -2,22 +2,32 @@ import { parseFlags as parseSharedFlags } from "../../../../../packages/shared/d
|
|
|
2
2
|
import { CliError } from "../../errors.js";
|
|
3
3
|
const valueFlags = new Set([
|
|
4
4
|
"agent",
|
|
5
|
+
"app-id",
|
|
5
6
|
"at",
|
|
6
7
|
"channel",
|
|
7
8
|
"code",
|
|
8
9
|
"conversation",
|
|
10
|
+
"cron",
|
|
9
11
|
"controller",
|
|
10
12
|
"cwd",
|
|
13
|
+
"date",
|
|
11
14
|
"device-id",
|
|
12
15
|
"device-name",
|
|
16
|
+
"every-ms",
|
|
17
|
+
"execution",
|
|
13
18
|
"from",
|
|
14
19
|
"host",
|
|
20
|
+
"domain",
|
|
15
21
|
"invite-secret",
|
|
22
|
+
"inbound",
|
|
16
23
|
"join-url-base",
|
|
17
24
|
"label",
|
|
25
|
+
"layer",
|
|
18
26
|
"limit",
|
|
19
27
|
"machine",
|
|
20
28
|
"machine-id",
|
|
29
|
+
"memory",
|
|
30
|
+
"memory-mode",
|
|
21
31
|
"message-id",
|
|
22
32
|
"memory-project",
|
|
23
33
|
"memory-top-n",
|
|
@@ -35,6 +45,7 @@ const valueFlags = new Set([
|
|
|
35
45
|
"prompt-file",
|
|
36
46
|
"every",
|
|
37
47
|
"reason",
|
|
48
|
+
"reaction-emoji",
|
|
38
49
|
"relay",
|
|
39
50
|
"root",
|
|
40
51
|
"runtime-limit",
|
|
@@ -56,6 +67,7 @@ const valueFlags = new Set([
|
|
|
56
67
|
"text-file",
|
|
57
68
|
"timeout",
|
|
58
69
|
"timeout-ms",
|
|
70
|
+
"timezone",
|
|
59
71
|
"wait-timeout-ms",
|
|
60
72
|
"inactivity-timeout",
|
|
61
73
|
"runtime-timeout",
|
|
@@ -69,10 +81,14 @@ const valueFlags = new Set([
|
|
|
69
81
|
]);
|
|
70
82
|
const booleanFlags = new Set([
|
|
71
83
|
"all",
|
|
84
|
+
"allow-any-sender",
|
|
85
|
+
"app-secret-stdin",
|
|
72
86
|
"detach",
|
|
87
|
+
"disable-reaction",
|
|
73
88
|
"help",
|
|
74
89
|
"h",
|
|
75
90
|
"include-archived",
|
|
91
|
+
"include-debug",
|
|
76
92
|
"json",
|
|
77
93
|
"keep-source",
|
|
78
94
|
"local",
|
|
@@ -82,6 +98,7 @@ const booleanFlags = new Set([
|
|
|
82
98
|
"summary",
|
|
83
99
|
"verbose",
|
|
84
100
|
"wait",
|
|
101
|
+
"manual",
|
|
85
102
|
]);
|
|
86
103
|
const knownFlags = new Set([...valueFlags, ...booleanFlags]);
|
|
87
104
|
const dashPrefixedValueFlags = new Set(["secret"]);
|
|
@@ -1,49 +1,171 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import cliPackage from "../../../package.json" with { type: "json" };
|
|
3
|
+
import { ControllerClient } from "../../../../../packages/client/dist/index.js";
|
|
2
4
|
import { configDir, configPath } from "./paths.js";
|
|
3
5
|
import { redactedControllerConfig, redactedDaemonConfig } from "./config.js";
|
|
4
6
|
import { ok, wantsJson } from "./json.js";
|
|
5
|
-
import { localDaemonStatus } from "./local-daemon.js";
|
|
7
|
+
import { daemonBinaryUpdatedAfterStart, localDaemonStatus } from "./local-daemon.js";
|
|
6
8
|
import { readExistingControllerConfig, readExistingDaemonConfig, relayHealth } from "./relay-http.js";
|
|
7
9
|
import { startRelayUrl } from "./bootstrap-config.js";
|
|
8
10
|
export async function topLevelDoctor(flags) {
|
|
11
|
+
const strict = flags.strict === true;
|
|
9
12
|
const checks = [];
|
|
10
13
|
const existingController = await readExistingControllerConfig().catch((error) => {
|
|
11
|
-
checks.push({
|
|
14
|
+
checks.push({
|
|
15
|
+
name: "controller-config",
|
|
16
|
+
ok: false,
|
|
17
|
+
severity: "error",
|
|
18
|
+
layer: "compatible",
|
|
19
|
+
message: error instanceof Error ? error.message : String(error),
|
|
20
|
+
});
|
|
12
21
|
return undefined;
|
|
13
22
|
});
|
|
14
23
|
if (existingController) {
|
|
15
|
-
checks.push({
|
|
24
|
+
checks.push({
|
|
25
|
+
name: "controller-config",
|
|
26
|
+
ok: true,
|
|
27
|
+
severity: "info",
|
|
28
|
+
layer: "compatible",
|
|
29
|
+
message: configPath,
|
|
30
|
+
details: redactedControllerConfig(existingController),
|
|
31
|
+
});
|
|
16
32
|
}
|
|
17
33
|
const relay = startRelayUrl(flags, existingController);
|
|
34
|
+
let relayReachable = false;
|
|
18
35
|
try {
|
|
19
36
|
const health = await relayHealth(relay.relayUrl);
|
|
37
|
+
relayReachable = health.ok;
|
|
20
38
|
checks.push({
|
|
21
39
|
name: "relay",
|
|
22
40
|
ok: health.ok,
|
|
41
|
+
severity: health.ok ? "info" : "error",
|
|
42
|
+
layer: "compatible",
|
|
23
43
|
message: health.ok ? "reachable" : `HTTP ${health.status}`,
|
|
24
44
|
details: { relayUrl: relay.relayUrl, body: health.body },
|
|
25
45
|
});
|
|
26
46
|
}
|
|
27
47
|
catch (error) {
|
|
28
|
-
checks.push({
|
|
48
|
+
checks.push({
|
|
49
|
+
name: "relay",
|
|
50
|
+
ok: false,
|
|
51
|
+
severity: "error",
|
|
52
|
+
layer: "compatible",
|
|
53
|
+
message: error instanceof Error ? error.message : String(error),
|
|
54
|
+
details: { relayUrl: relay.relayUrl },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
let snapshot;
|
|
58
|
+
if (existingController && relayReachable) {
|
|
59
|
+
try {
|
|
60
|
+
snapshot = await new ControllerClient({ ...existingController, relayUrl: relay.relayUrl }).snapshot();
|
|
61
|
+
checks.push({
|
|
62
|
+
name: "controller-snapshot",
|
|
63
|
+
ok: true,
|
|
64
|
+
severity: "info",
|
|
65
|
+
layer: "compatible",
|
|
66
|
+
message: "controller token accepted",
|
|
67
|
+
details: { machines: snapshot.machines.length, sessions: snapshot.sessions.length },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
checks.push({
|
|
72
|
+
name: "controller-snapshot",
|
|
73
|
+
ok: false,
|
|
74
|
+
severity: "error",
|
|
75
|
+
layer: "compatible",
|
|
76
|
+
message: error instanceof Error ? error.message : String(error),
|
|
77
|
+
details: { relayUrl: relay.relayUrl },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
29
80
|
}
|
|
30
81
|
const existingDaemon = await readExistingDaemonConfig().catch((error) => {
|
|
31
|
-
checks.push({
|
|
82
|
+
checks.push({
|
|
83
|
+
name: "daemon-config",
|
|
84
|
+
ok: false,
|
|
85
|
+
severity: "error",
|
|
86
|
+
layer: "compatible",
|
|
87
|
+
message: error instanceof Error ? error.message : String(error),
|
|
88
|
+
});
|
|
32
89
|
return undefined;
|
|
33
90
|
});
|
|
34
91
|
if (existingDaemon) {
|
|
35
|
-
checks.push({
|
|
92
|
+
checks.push({
|
|
93
|
+
name: "daemon-config",
|
|
94
|
+
ok: true,
|
|
95
|
+
severity: "info",
|
|
96
|
+
layer: "compatible",
|
|
97
|
+
message: path.join(configDir, "daemon.json"),
|
|
98
|
+
details: redactedDaemonConfig(existingDaemon),
|
|
99
|
+
});
|
|
36
100
|
}
|
|
37
101
|
const local = await localDaemonStatus();
|
|
38
|
-
checks.push({
|
|
102
|
+
checks.push({
|
|
103
|
+
name: "daemon-process",
|
|
104
|
+
ok: local.running,
|
|
105
|
+
severity: local.running ? "info" : "error",
|
|
106
|
+
layer: "compatible",
|
|
107
|
+
message: local.running ? "running" : "not running",
|
|
108
|
+
details: local,
|
|
109
|
+
});
|
|
110
|
+
const binaryStale = await daemonBinaryUpdatedAfterStart(local);
|
|
111
|
+
checks.push({
|
|
112
|
+
name: "daemon-binary-alignment",
|
|
113
|
+
ok: !binaryStale,
|
|
114
|
+
severity: binaryStale ? "warning" : "info",
|
|
115
|
+
layer: "aligned",
|
|
116
|
+
message: binaryStale ? "daemon binary changed after daemon start; restart daemon to align" : "daemon binary is not newer than running daemon",
|
|
117
|
+
});
|
|
118
|
+
if (existingDaemon && snapshot) {
|
|
119
|
+
const daemonMachine = snapshot.machines.find((machine) => machine.id === existingDaemon.machineId);
|
|
120
|
+
const daemonVersion = daemonMachine?.capabilities.daemonVersion;
|
|
121
|
+
const aligned = !daemonVersion || daemonVersion === cliPackage.version;
|
|
122
|
+
checks.push({
|
|
123
|
+
name: "cli-daemon-version-alignment",
|
|
124
|
+
ok: aligned,
|
|
125
|
+
severity: aligned ? "info" : "warning",
|
|
126
|
+
layer: "aligned",
|
|
127
|
+
message: aligned
|
|
128
|
+
? "CLI and daemon package versions are aligned or daemon version is unavailable"
|
|
129
|
+
: `CLI ${cliPackage.version} differs from daemon ${daemonVersion}`,
|
|
130
|
+
details: { cliVersion: cliPackage.version, daemonVersion, machineId: existingDaemon.machineId },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const compatible = checks.filter((check) => check.layer === "compatible" && check.severity === "error").every((check) => check.ok);
|
|
134
|
+
const aligned = checks.filter((check) => check.layer === "aligned" && check.severity === "warning").every((check) => check.ok);
|
|
135
|
+
const warnings = checks.filter((check) => !check.ok && check.severity === "warning").map((check) => ({
|
|
136
|
+
name: check.name,
|
|
137
|
+
message: check.message,
|
|
138
|
+
details: check.details,
|
|
139
|
+
}));
|
|
140
|
+
const healthy = compatible && (strict ? aligned : true);
|
|
39
141
|
if (wantsJson(flags)) {
|
|
40
|
-
|
|
142
|
+
if (!healthy)
|
|
143
|
+
process.exitCode = 1;
|
|
144
|
+
ok("doctor", { ok: healthy, compatible, aligned, strict, warnings, checks }, {}, healthy);
|
|
41
145
|
return;
|
|
42
146
|
}
|
|
43
|
-
|
|
44
|
-
console.log(`
|
|
147
|
+
console.log(`Happy Elves doctor: ${healthy ? "ok" : "issues found"}`);
|
|
148
|
+
console.log(`compatible: ${compatible ? "yes" : "no"}`);
|
|
149
|
+
console.log(`aligned: ${aligned ? "yes" : "no"}${strict ? " (strict)" : ""}`);
|
|
150
|
+
const failures = checks.filter((check) => !check.ok && check.severity === "error");
|
|
151
|
+
if (failures.length > 0)
|
|
152
|
+
console.log("Failures:");
|
|
153
|
+
for (const check of failures) {
|
|
154
|
+
console.log(`fail ${check.name}: ${check.message}`);
|
|
155
|
+
}
|
|
156
|
+
const warningChecks = checks.filter((check) => !check.ok && check.severity === "warning");
|
|
157
|
+
if (warningChecks.length > 0)
|
|
158
|
+
console.log("Warnings:");
|
|
159
|
+
for (const check of warningChecks) {
|
|
160
|
+
console.log(`warn ${check.name}: ${check.message}`);
|
|
161
|
+
}
|
|
162
|
+
if (failures.length === 0 && warningChecks.length === 0)
|
|
163
|
+
console.log("Checks:");
|
|
45
164
|
for (const check of checks) {
|
|
46
|
-
|
|
165
|
+
if (check.ok)
|
|
166
|
+
console.log(`ok ${check.name}: ${check.message}`);
|
|
47
167
|
}
|
|
168
|
+
if (!healthy)
|
|
169
|
+
process.exitCode = 1;
|
|
48
170
|
}
|
|
49
171
|
//# sourceMappingURL=doctor.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonEnvelope } from "../../../../../packages/shared/dist/index.js";
|
|
2
|
-
export declare function ok<T>(type: string, data: T, meta?: JsonEnvelope["meta"]): void;
|
|
2
|
+
export declare function ok<T>(type: string, data: T, meta?: JsonEnvelope["meta"], success?: boolean): void;
|
|
3
3
|
export declare function writeError(code: string, message: string, meta?: JsonEnvelope["meta"], capability?: string): void;
|
|
4
4
|
export declare function wantsJson(flags: Record<string, string | boolean>): boolean;
|
|
5
5
|
export declare function wantsVerbose(flags: Record<string, string | boolean>): boolean;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export function ok(type, data, meta = {}) {
|
|
2
|
-
|
|
1
|
+
export function ok(type, data, meta = {}, success = true) {
|
|
2
|
+
const envelope = success
|
|
3
|
+
? { ok: true, type, data, meta }
|
|
4
|
+
: { ok: false, type, data, meta };
|
|
5
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
3
6
|
}
|
|
4
7
|
export function writeError(code, message, meta = {}, capability) {
|
|
5
8
|
console.error(JSON.stringify({
|
|
@@ -43,7 +43,9 @@ export async function topLevelStatus(flags) {
|
|
|
43
43
|
} : undefined,
|
|
44
44
|
};
|
|
45
45
|
if (wantsJson(flags)) {
|
|
46
|
-
|
|
46
|
+
if (!data.relay.ok)
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
ok("status", data, {}, data.relay.ok);
|
|
47
49
|
return;
|
|
48
50
|
}
|
|
49
51
|
console.log(`Relay: ${config.relayUrl}${relayError ? ` (unreachable: ${relayError})` : ""}`);
|
|
@@ -59,5 +61,7 @@ export async function topLevelStatus(flags) {
|
|
|
59
61
|
console.log(`Result: happy-elves turn result ${latestSession.id}`);
|
|
60
62
|
console.log(`Run: happy-elves turn run ${latestSession.id} --text "..."`);
|
|
61
63
|
}
|
|
64
|
+
if (!data.relay.ok)
|
|
65
|
+
process.exitCode = 1;
|
|
62
66
|
}
|
|
63
67
|
//# sourceMappingURL=status.js.map
|
|
@@ -107,43 +107,42 @@ Workspace sync records account-level UI/library state shared across devices:
|
|
|
107
107
|
session stars, recent sessions, pinned workspaces, and current project defaults.
|
|
108
108
|
`,
|
|
109
109
|
gateway: `Usage:
|
|
110
|
-
happy-elves gateway enable [<channelId>] --channel
|
|
111
|
-
happy-elves gateway
|
|
112
|
-
happy-elves gateway
|
|
113
|
-
happy-elves gateway
|
|
114
|
-
happy-elves gateway
|
|
110
|
+
happy-elves gateway enable [<channelId>] --channel lark --machine <machineId> (--cwd <cwd> | --session <sessionId>) [--agent codex] [--model <model>] --json
|
|
111
|
+
happy-elves gateway enable lark-main --channel lark --machine <machineId> (--cwd <cwd> | --session <sessionId>) --app-id <appId> --app-secret-stdin [--domain feishu|lark] [--inbound mention|all] --json
|
|
112
|
+
happy-elves gateway disable [<channelId>] [--machine <machineId>] --json
|
|
113
|
+
happy-elves gateway status [--machine <machineId>] [--include-debug] [--json]
|
|
114
|
+
happy-elves gateway test [<channelId>] [--machine <machineId>] --conversation <externalConversationId> --message-id <externalMessageId> --text <prompt> --json
|
|
115
|
+
happy-elves gateway serve [--host 127.0.0.1] [--port 8789] --json # debug only
|
|
115
116
|
|
|
116
|
-
Gateway v1
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
Gateway v1 official Feishu/Lark path is daemon-backed SDK long connection.
|
|
118
|
+
App secrets are read from stdin and stored only in the target daemon config.
|
|
119
|
+
gateway serve is kept for fake/local debugging only; lark-cli is not a product dependency.
|
|
119
120
|
`,
|
|
120
121
|
memory: `Usage:
|
|
121
|
-
happy-elves memory
|
|
122
|
-
happy-elves memory
|
|
123
|
-
happy-elves memory
|
|
124
|
-
happy-elves memory
|
|
125
|
-
happy-elves memory
|
|
126
|
-
happy-elves memory
|
|
127
|
-
happy-elves memory disable --project <projectKey> --json
|
|
128
|
-
happy-elves memory enable-injection --project <projectKey> --json
|
|
129
|
-
happy-elves memory disable-injection --project <projectKey> --json
|
|
122
|
+
happy-elves memory overview [--machine <machineId>] [--project <projectKey>] [--cwd <cwd>] --json
|
|
123
|
+
happy-elves memory save [--machine <machineId>] [--project <projectKey>] [--layer user|curated|daily] (--text <text> | --text-file <path>) [--title <title>] [--date YYYY-MM-DD] --json
|
|
124
|
+
happy-elves memory read <USER.md|MEMORY.md|memory/YYYY-MM-DD.md> [--project <projectKey>] --json
|
|
125
|
+
happy-elves memory search --query <text> [--project <projectKey>] [--limit 5] --json
|
|
126
|
+
happy-elves memory reindex [--project <projectKey>] --json
|
|
127
|
+
happy-elves memory sync [--project <projectKey>] --json
|
|
130
128
|
|
|
131
|
-
Memory
|
|
132
|
-
memory
|
|
129
|
+
Memory uses daemon-local Markdown files: USER.md, MEMORY.md, and memory/YYYY-MM-DD.md.
|
|
130
|
+
Session prompts can run memory off, readonly, or readwrite; readwrite writes daily notes.
|
|
133
131
|
`,
|
|
134
132
|
schedule: `Usage:
|
|
135
|
-
happy-elves schedule create --session <sessionId> --at <iso-time> (--prompt <text> | --prompt-file <path>) [--name <name>] --json
|
|
136
|
-
happy-elves schedule list --json
|
|
137
|
-
happy-elves schedule show <scheduleId> --json
|
|
138
|
-
happy-elves schedule history <scheduleId> --json
|
|
139
|
-
happy-elves schedule
|
|
140
|
-
happy-elves schedule
|
|
141
|
-
happy-elves schedule
|
|
142
|
-
happy-elves schedule
|
|
143
|
-
happy-elves schedule run-
|
|
133
|
+
happy-elves schedule create [--session <sessionId> | --cwd <cwd> --agent codex] (--manual | --at <iso-time> | --every 5m | --cron "*/30 * * * *") (--prompt <text> | --prompt-file <path>) [--execution continue|isolated] [--memory inherit|off|readonly|readwrite] [--name <name>] [--machine <machineId>] --json
|
|
134
|
+
happy-elves schedule list [--machine <machineId>] --json
|
|
135
|
+
happy-elves schedule show <scheduleId> [--machine <machineId>] --json
|
|
136
|
+
happy-elves schedule history <scheduleId> [--machine <machineId>] --json
|
|
137
|
+
happy-elves schedule run <scheduleId> [--machine <machineId>] --json
|
|
138
|
+
happy-elves schedule pause <scheduleId> [--machine <machineId>] --json
|
|
139
|
+
happy-elves schedule resume <scheduleId> [--machine <machineId>] --json
|
|
140
|
+
happy-elves schedule delete <scheduleId> [--machine <machineId>] --json
|
|
141
|
+
happy-elves schedule run-due [--machine <machineId>] --json
|
|
142
|
+
happy-elves schedule run-missed <scheduleId> <occurrenceId> [--machine <machineId>] --json
|
|
144
143
|
|
|
145
|
-
Schedule v1 is
|
|
146
|
-
|
|
144
|
+
Schedule v1 is daemon-local repeat automation. Runs use normal Relay admission
|
|
145
|
+
with deterministic requestId/turnId per occurrence. Offline due times are marked
|
|
147
146
|
missed and are never auto-caught-up.
|
|
148
147
|
`,
|
|
149
148
|
loop: `Usage:
|
|
@@ -227,7 +226,7 @@ Core commands:
|
|
|
227
226
|
workspace pin-cwd --machine <machineId> --cwd <cwd> --json
|
|
228
227
|
|
|
229
228
|
gateway status --json
|
|
230
|
-
gateway enable --channel fake --machine <machineId> --cwd <cwd> --json
|
|
229
|
+
gateway enable --channel fake --machine <machineId> --cwd <cwd> --json # debug only
|
|
231
230
|
gateway test --conversation <id> --message-id <id> --text <prompt> --json
|
|
232
231
|
|
|
233
232
|
memory save --project <key> --text <fact> --json
|