@cydm/happy-elves 0.1.0-beta.84 → 0.1.0-beta.86
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 +87 -3
- package/apps/cli/dist/commands/lib/args.js +3 -0
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js +2 -2
- package/apps/cli/dist/commands/lib/gateway-store.d.ts +0 -4
- package/apps/cli/dist/commands/lib/gateway-store.js +0 -8
- package/apps/cli/dist/commands/lib/index.d.ts +0 -1
- package/apps/cli/dist/commands/lib/index.js +0 -1
- package/apps/cli/dist/commands/lib/local-daemon.d.ts +1 -0
- package/apps/cli/dist/commands/lib/local-daemon.js +14 -0
- package/apps/cli/dist/commands/lib/paths.d.ts +0 -1
- package/apps/cli/dist/commands/lib/paths.js +0 -1
- package/apps/cli/dist/commands/lib/session-output.d.ts +12 -0
- package/apps/cli/dist/commands/lib/session-output.js +49 -0
- package/apps/cli/dist/commands/lib/usage.js +3 -2
- package/apps/cli/dist/commands/memory.js +48 -1
- package/apps/cli/dist/commands/schedule.js +5 -2
- package/apps/cli/dist/commands/turn.js +147 -0
- package/apps/cli/dist/commands/workspace.js +10 -2
- package/apps/cli/package.json +1 -1
- package/apps/daemon/dist/gateway/adapters.d.ts +2 -1
- package/apps/daemon/dist/gateway/feishu.d.ts +0 -4
- package/apps/daemon/dist/gateway/feishu.js +1 -1
- package/apps/daemon/dist/gateway/runtime.d.ts +0 -5
- package/apps/daemon/dist/gateway/runtime.js +1 -4
- package/apps/daemon/dist/gateway/store.d.ts +2 -1
- package/apps/daemon/dist/memory/handler.js +15 -5
- package/apps/daemon/dist/memory/manager.d.ts +7 -4
- package/apps/daemon/dist/memory/manager.js +51 -11
- package/apps/daemon/dist/relay/connection.js +5 -0
- package/apps/daemon/dist/relay/send.js +1 -0
- package/apps/daemon/dist/runtime/external-provider.js +119 -0
- package/apps/daemon/dist/schedule/handler.js +12 -5
- package/apps/daemon/dist/schedule/runner.d.ts +9 -1
- package/apps/daemon/dist/schedule/runner.js +84 -4
- package/apps/daemon/dist/schedule/store.d.ts +1 -2
- package/apps/daemon/dist/schedule/store.js +7 -1
- package/apps/daemon/dist/session/prompt.js +18 -0
- package/apps/daemon/dist/session/reconcile.d.ts +3 -0
- package/apps/daemon/dist/session/reconcile.js +235 -0
- package/apps/daemon/dist/turn-coordinator.d.ts +1 -0
- package/apps/daemon/dist/turn-coordinator.js +4 -0
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/controller-handlers.d.ts +9 -0
- package/apps/relay/dist/controller-handlers.js +53 -3
- package/apps/relay/dist/db.js +2 -0
- package/apps/relay/dist/http-routes.js +10 -4
- package/apps/relay/dist/http-schemas.d.ts +5 -0
- package/apps/relay/dist/http-schemas.js +1 -0
- package/apps/relay/dist/index.js +28 -0
- package/apps/relay/dist/machine-handlers.js +58 -0
- package/package.json +1 -1
- package/packages/agent-sdk/dist/index.d.ts +31 -0
- package/packages/agent-sdk/dist/index.js +4 -0
- package/packages/client/dist/account.js +1 -0
- package/packages/client/dist/client.d.ts +2 -1
- package/packages/client/dist/client.js +13 -1
- package/packages/client/dist/parsers.js +4 -0
- package/packages/client/dist/transport.d.ts +5 -0
- package/packages/client/dist/transport.js +7 -0
- package/packages/client/dist/types.d.ts +11 -2
- package/packages/runtime/dist/index.d.ts +26 -0
- package/packages/runtime-acpx/dist/index.js +8 -0
- package/packages/runtime-cli/dist/index.js +77 -0
- package/packages/shared/dist/protocol-schemas.d.ts +96 -3
- package/packages/shared/dist/protocol-schemas.js +35 -1
- package/packages/shared/dist/protocol-types.d.ts +36 -1
- package/packages/shared/dist/protocol.d.ts +29 -4
- package/packages/shared/dist/protocol.js +32 -3
- package/apps/cli/dist/commands/lib/schedule-store.d.ts +0 -28
- package/apps/cli/dist/commands/lib/schedule-store.js +0 -32
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, idleSessionStatuses, localDaemonStatus, ok, parsePairingClaimResponse, randomId, readConfig, readDaemonConfig, readLocalAuditLog, readRelayJson, redactedDaemonConfig, requirePositional, requireRelayUrl, requireString, showMachine, spawnDaemonStart, stopLocalDaemon, throwRelayHttpError, wantsJson, writeDaemonConfig } from "./lib/index.js";
|
|
3
|
+
import { CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, ensureDaemonRunning, idleSessionStatuses, localDaemonStatus, ok, parsePairingClaimResponse, randomId, readConfig, readDaemonConfig, readLocalAuditLog, readRelayJson, redactedDaemonConfig, requirePositional, requireRelayUrl, requireString, restartDaemonForNewConfig, showMachine, spawnDaemonStart, stopLocalDaemon, throwRelayHttpError, waitForMachineProjection, wantsJson, writeDaemonConfig } from "./lib/index.js";
|
|
4
4
|
export async function handleDaemon({ domain, action, flags }) {
|
|
5
5
|
if (domain === "daemon" && action === "pair") {
|
|
6
6
|
const relayUrl = requireRelayUrl(flags);
|
|
@@ -25,11 +25,87 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
25
25
|
machineToken: claimed.machineToken,
|
|
26
26
|
accountSecret,
|
|
27
27
|
};
|
|
28
|
+
const localBefore = await localDaemonStatus();
|
|
28
29
|
const daemonConfigPath = await writeDaemonConfig(daemonConfig);
|
|
29
|
-
|
|
30
|
+
let daemonStart = { skipped: true, reason: "not requested" };
|
|
31
|
+
let projection = { checked: false, reason: "not checked" };
|
|
32
|
+
let startError;
|
|
33
|
+
if (flags["no-start"] === true) {
|
|
34
|
+
daemonStart = { skipped: true, reason: "--no-start" };
|
|
35
|
+
projection = { checked: false, reason: "--no-start" };
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
try {
|
|
39
|
+
const startResult = localBefore.running
|
|
40
|
+
? await restartDaemonForNewConfig(relayUrl)
|
|
41
|
+
: await ensureDaemonRunning(relayUrl);
|
|
42
|
+
daemonStart = {
|
|
43
|
+
skipped: false,
|
|
44
|
+
restarted: localBefore.running,
|
|
45
|
+
local: startResult.local,
|
|
46
|
+
started: startResult.started,
|
|
47
|
+
...(startResult.startedPid ? { startedPid: startResult.startedPid } : {}),
|
|
48
|
+
...("stopped" in startResult ? { stop: startResult.stopped } : {}),
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
const controllerConfig = await readConfig({ relay: relayUrl });
|
|
52
|
+
const machine = await waitForMachineProjection(new ControllerClient(controllerConfig), claimed.machineId, 10_000);
|
|
53
|
+
projection = {
|
|
54
|
+
checked: true,
|
|
55
|
+
online: machine?.online === true,
|
|
56
|
+
machineId: claimed.machineId,
|
|
57
|
+
...(machine?.lastSeen ? { lastSeen: machine.lastSeen } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
projection = {
|
|
62
|
+
checked: false,
|
|
63
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const code = error instanceof CliError ? error.code : "DAEMON_START_FAILED";
|
|
69
|
+
startError = { code, message: error instanceof Error ? error.message : String(error) };
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
daemonStart = { skipped: true, reason: "start failed" };
|
|
72
|
+
projection = { checked: false, reason: "daemon did not start" };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const data = {
|
|
30
76
|
path: daemonConfigPath,
|
|
31
77
|
config: redactedDaemonConfig(daemonConfig),
|
|
32
|
-
|
|
78
|
+
daemon: daemonStart,
|
|
79
|
+
projection,
|
|
80
|
+
...(startError ? { error: startError } : {}),
|
|
81
|
+
};
|
|
82
|
+
const commandOk = !startError && !(projection.checked && !projection.online);
|
|
83
|
+
if (!commandOk)
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
if (!wantsJson(flags)) {
|
|
86
|
+
console.log(`Paired ${machineName} with ${relayUrl}`);
|
|
87
|
+
console.log(`Config: ${daemonConfigPath}`);
|
|
88
|
+
if (startError) {
|
|
89
|
+
console.error(`Daemon start failed: ${startError.message}`);
|
|
90
|
+
}
|
|
91
|
+
else if (daemonStart.skipped) {
|
|
92
|
+
console.log(`Daemon start: skipped (${daemonStart.reason})`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log(`${daemonStart.restarted ? "Restarted" : "Started"} daemon${daemonStart.startedPid ? ` pid ${daemonStart.startedPid}` : ""}`);
|
|
96
|
+
}
|
|
97
|
+
if (projection.checked) {
|
|
98
|
+
console.log(`Relay projection: ${projection.online ? "online" : "offline"} (${projection.machineId})`);
|
|
99
|
+
if (!projection.online) {
|
|
100
|
+
console.log("If this stays offline, run happy-elves daemon logs --tail 100 and happy-elves daemon status --local.");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(`Relay projection: not checked (${projection.reason})`);
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
ok("daemon.pair", data, { machineId: claimed.machineId }, commandOk);
|
|
33
109
|
return true;
|
|
34
110
|
}
|
|
35
111
|
if (domain === "daemon" && action === "doctor") {
|
|
@@ -118,6 +194,14 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
118
194
|
if (domain === "daemon" && action === "start") {
|
|
119
195
|
const status = await localDaemonStatus();
|
|
120
196
|
if (status.running) {
|
|
197
|
+
const existingConfig = await readDaemonConfig();
|
|
198
|
+
if (typeof flags.relay === "string")
|
|
199
|
+
existingConfig.relayUrl = requireRelayUrl(flags);
|
|
200
|
+
const restarted = await ensureDaemonRunning(existingConfig.relayUrl);
|
|
201
|
+
if (restarted.started) {
|
|
202
|
+
ok("daemon.start", { ...restarted.local, started: true, startedPid: restarted.startedPid, restartedForChangedConfig: true });
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
121
205
|
ok("daemon.start", { ...status, started: false });
|
|
122
206
|
return true;
|
|
123
207
|
}
|
|
@@ -62,6 +62,7 @@ const valueFlags = new Set([
|
|
|
62
62
|
"session",
|
|
63
63
|
"secret",
|
|
64
64
|
"since",
|
|
65
|
+
"source",
|
|
65
66
|
"stop-on",
|
|
66
67
|
"tail",
|
|
67
68
|
"tags",
|
|
@@ -85,6 +86,7 @@ const booleanFlags = new Set([
|
|
|
85
86
|
"all",
|
|
86
87
|
"allow-any-sender",
|
|
87
88
|
"app-secret-stdin",
|
|
89
|
+
"confirm-risk",
|
|
88
90
|
"detach",
|
|
89
91
|
"disable-reaction",
|
|
90
92
|
"help",
|
|
@@ -95,6 +97,7 @@ const booleanFlags = new Set([
|
|
|
95
97
|
"keep-source",
|
|
96
98
|
"local",
|
|
97
99
|
"no-open",
|
|
100
|
+
"no-start",
|
|
98
101
|
"no-wait",
|
|
99
102
|
"repair-head",
|
|
100
103
|
"summary",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CliError } from "../../errors.js";
|
|
2
2
|
import { startDaemonTimeoutMs } from "./paths.js";
|
|
3
3
|
import { readDaemonConfig } from "./config.js";
|
|
4
|
-
import { daemonBinaryUpdatedAfterStart, localDaemonStatus, spawnDaemonStart, stopLocalDaemon } from "./local-daemon.js";
|
|
4
|
+
import { daemonBinaryUpdatedAfterStart, daemonConfigUpdatedAfterStart, localDaemonStatus, spawnDaemonStart, stopLocalDaemon } from "./local-daemon.js";
|
|
5
5
|
export async function ensureDaemonRunning(relayUrl, cwd = process.cwd()) {
|
|
6
6
|
const current = await localDaemonStatus();
|
|
7
7
|
if (current.running) {
|
|
8
|
-
if (await daemonBinaryUpdatedAfterStart(current)) {
|
|
8
|
+
if ((await daemonBinaryUpdatedAfterStart(current)) || (await daemonConfigUpdatedAfterStart(current))) {
|
|
9
9
|
return await restartDaemonForNewConfig(relayUrl, cwd);
|
|
10
10
|
}
|
|
11
11
|
return { local: current, started: false };
|
|
@@ -36,7 +36,3 @@ export type GatewayStore = {
|
|
|
36
36
|
};
|
|
37
37
|
export declare function readGatewayStore(): Promise<GatewayStore>;
|
|
38
38
|
export declare function updateGatewayStore(update: (store: GatewayStore) => GatewayStore | Promise<GatewayStore>): Promise<GatewayStore>;
|
|
39
|
-
export declare function redactedGatewayChannel(channel: GatewayChannelConfig): Omit<GatewayChannelConfig, "localToken" | "localSecret"> & {
|
|
40
|
-
hasLocalToken: boolean;
|
|
41
|
-
hasLocalSecret: boolean;
|
|
42
|
-
};
|
|
@@ -30,12 +30,4 @@ export async function updateGatewayStore(update) {
|
|
|
30
30
|
await writeGatewayStore(next);
|
|
31
31
|
return next;
|
|
32
32
|
}
|
|
33
|
-
export function redactedGatewayChannel(channel) {
|
|
34
|
-
const { localToken, localSecret, ...rest } = channel;
|
|
35
|
-
return {
|
|
36
|
-
...rest,
|
|
37
|
-
hasLocalToken: Boolean(localToken),
|
|
38
|
-
hasLocalSecret: Boolean(localSecret),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
33
|
//# sourceMappingURL=gateway-store.js.map
|
|
@@ -17,7 +17,6 @@ export * from "./loop-store.js";
|
|
|
17
17
|
export * from "./paths.js";
|
|
18
18
|
export * from "./relay-db-audit.js";
|
|
19
19
|
export * from "./relay-http.js";
|
|
20
|
-
export * from "./schedule-store.js";
|
|
21
20
|
export * from "./scope.js";
|
|
22
21
|
export * from "./session-output.js";
|
|
23
22
|
export * from "./session-view.js";
|
|
@@ -17,7 +17,6 @@ export * from "./loop-store.js";
|
|
|
17
17
|
export * from "./paths.js";
|
|
18
18
|
export * from "./relay-db-audit.js";
|
|
19
19
|
export * from "./relay-http.js";
|
|
20
|
-
export * from "./schedule-store.js";
|
|
21
20
|
export * from "./scope.js";
|
|
22
21
|
export * from "./session-output.js";
|
|
23
22
|
export * from "./session-view.js";
|
|
@@ -4,6 +4,7 @@ export declare function localDaemonStatus(): Promise<{
|
|
|
4
4
|
running: boolean;
|
|
5
5
|
}>;
|
|
6
6
|
export declare function daemonBinaryUpdatedAfterStart(status: Awaited<ReturnType<typeof localDaemonStatus>>): Promise<boolean>;
|
|
7
|
+
export declare function daemonConfigUpdatedAfterStart(status: Awaited<ReturnType<typeof localDaemonStatus>>): Promise<boolean>;
|
|
7
8
|
export declare function spawnDaemonStart(args: string[], cwd?: string): number;
|
|
8
9
|
export declare function stopLocalDaemon(timeoutMs?: number): Promise<{
|
|
9
10
|
pidPath: string;
|
|
@@ -102,6 +102,20 @@ export async function daemonBinaryUpdatedAfterStart(status) {
|
|
|
102
102
|
return false;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
export async function daemonConfigUpdatedAfterStart(status) {
|
|
106
|
+
if (!status.running || !status.pid)
|
|
107
|
+
return false;
|
|
108
|
+
const startedAt = readProcessStartedAtMs(status.pid);
|
|
109
|
+
if (!startedAt)
|
|
110
|
+
return false;
|
|
111
|
+
try {
|
|
112
|
+
const config = await fs.stat(path.join(configDir, "daemon.json"));
|
|
113
|
+
return config.mtimeMs > startedAt + 1000;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
105
119
|
export function spawnDaemonStart(args, cwd = process.cwd()) {
|
|
106
120
|
const child = spawn(process.execPath, [daemonCliPath(), "start", ...args], {
|
|
107
121
|
cwd,
|
|
@@ -12,5 +12,4 @@ 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
14
|
export const gatewayStorePath = path.join(configDir, "gateway.json");
|
|
15
|
-
export const scheduleStorePath = path.join(configDir, "schedules.json");
|
|
16
15
|
//# sourceMappingURL=paths.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ControllerClient, DecodedSessionEvent } from "../../../../../packages/client/dist/index.js";
|
|
2
|
+
import type { MemoryCitation, MemoryLayer } from "../../../../../packages/shared/dist/index.js";
|
|
2
3
|
export declare function compactText(value: string, max?: number): string;
|
|
3
4
|
export declare function lastAssistantOutput(events: DecodedSessionEvent[]): string;
|
|
4
5
|
export declare function compareSessionEventLogicalOrder(left: DecodedSessionEvent, right: DecodedSessionEvent): number;
|
|
@@ -14,6 +15,14 @@ export type StoredTurnOutput = {
|
|
|
14
15
|
startedAt?: number;
|
|
15
16
|
completedAt: number;
|
|
16
17
|
eventCount: number;
|
|
18
|
+
usedMemories?: UsedMemoryReference[];
|
|
19
|
+
};
|
|
20
|
+
type UsedMemoryReference = {
|
|
21
|
+
id: string;
|
|
22
|
+
layer: MemoryLayer;
|
|
23
|
+
path: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
citation: MemoryCitation;
|
|
17
26
|
};
|
|
18
27
|
export type TurnSummary = {
|
|
19
28
|
turnId: string;
|
|
@@ -30,6 +39,7 @@ export type TurnSummary = {
|
|
|
30
39
|
firstEventId: number;
|
|
31
40
|
lastEventId: number;
|
|
32
41
|
eventCount: number;
|
|
42
|
+
usedMemories?: UsedMemoryReference[];
|
|
33
43
|
};
|
|
34
44
|
export type StructuredCommandOutput = {
|
|
35
45
|
state: string;
|
|
@@ -43,6 +53,7 @@ export type StructuredCommandOutput = {
|
|
|
43
53
|
latest?: string;
|
|
44
54
|
metadata?: string;
|
|
45
55
|
};
|
|
56
|
+
usedMemories?: UsedMemoryReference[];
|
|
46
57
|
};
|
|
47
58
|
export declare function writeTurnOutput(sessionId: string, turnId: string, events: DecodedSessionEvent[]): Promise<StoredTurnOutput>;
|
|
48
59
|
export declare function readStoredTurnOutput(sessionId: string, turnId?: string): Promise<StoredTurnOutput | undefined>;
|
|
@@ -59,3 +70,4 @@ export declare function conciseEvents(events: DecodedSessionEvent[]): Array<{
|
|
|
59
70
|
}>;
|
|
60
71
|
export declare function printConciseEvents(events: DecodedSessionEvent[]): void;
|
|
61
72
|
export declare function readPromptTextFile(filePath: string): Promise<string>;
|
|
73
|
+
export {};
|
|
@@ -48,6 +48,45 @@ function assistantOutputForTurn(events, turnId) {
|
|
|
48
48
|
.join("")
|
|
49
49
|
.trim();
|
|
50
50
|
}
|
|
51
|
+
function usedMemoriesForTurn(events, turnId) {
|
|
52
|
+
return dedupeUsedMemories(events
|
|
53
|
+
.filter((event) => event.turnId === turnId && event.decoded?.type === "memory_used")
|
|
54
|
+
.flatMap((event) => event.decoded?.type === "memory_used" ? event.decoded.memories : []));
|
|
55
|
+
}
|
|
56
|
+
function dedupeUsedMemories(memories) {
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const result = [];
|
|
59
|
+
for (const memory of memories) {
|
|
60
|
+
const key = [
|
|
61
|
+
memory.id,
|
|
62
|
+
memory.path,
|
|
63
|
+
memory.citation.path,
|
|
64
|
+
memory.citation.startLine,
|
|
65
|
+
memory.citation.endLine,
|
|
66
|
+
].join(":");
|
|
67
|
+
if (seen.has(key))
|
|
68
|
+
continue;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
result.push(memory);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
function isUsedMemoryArray(value) {
|
|
75
|
+
return Array.isArray(value) && value.every((item) => {
|
|
76
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
77
|
+
return false;
|
|
78
|
+
const record = item;
|
|
79
|
+
const citation = record.citation;
|
|
80
|
+
return typeof record.id === "string" &&
|
|
81
|
+
(record.layer === "user" || record.layer === "curated" || record.layer === "daily") &&
|
|
82
|
+
typeof record.path === "string" &&
|
|
83
|
+
(!record.title || typeof record.title === "string") &&
|
|
84
|
+
Boolean(citation) &&
|
|
85
|
+
typeof citation?.path === "string" &&
|
|
86
|
+
typeof citation?.startLine === "number" &&
|
|
87
|
+
typeof citation?.endLine === "number";
|
|
88
|
+
});
|
|
89
|
+
}
|
|
51
90
|
function sessionEventLogicalOrder(event) {
|
|
52
91
|
const messageKey = event.messageId ?? "";
|
|
53
92
|
if (event.occurredAt !== undefined) {
|
|
@@ -99,6 +138,7 @@ export async function writeTurnOutput(sessionId, turnId, events) {
|
|
|
99
138
|
await fs.mkdir(paths.dir, { recursive: true });
|
|
100
139
|
const output = assistantOutputForTurn(events, turnId) || terminalTextForDone(events, turnId);
|
|
101
140
|
const turnEvents = events.filter((event) => event.turnId === turnId);
|
|
141
|
+
const usedMemories = usedMemoriesForTurn(events, turnId);
|
|
102
142
|
const doneEvent = [...turnEvents].reverse().find((event) => event.decoded?.type === "done");
|
|
103
143
|
const metadata = {
|
|
104
144
|
sessionId,
|
|
@@ -107,6 +147,7 @@ export async function writeTurnOutput(sessionId, turnId, events) {
|
|
|
107
147
|
startedAt: turnEvents[0]?.createdAt,
|
|
108
148
|
completedAt: doneEvent?.createdAt ?? Date.now(),
|
|
109
149
|
eventCount: turnEvents.length,
|
|
150
|
+
...(usedMemories.length > 0 ? { usedMemories } : {}),
|
|
110
151
|
outputPath: paths.turn,
|
|
111
152
|
latestPath: paths.latest,
|
|
112
153
|
};
|
|
@@ -158,6 +199,7 @@ export async function readStoredTurnOutput(sessionId, turnId) {
|
|
|
158
199
|
startedAt: typeof metadata.startedAt === "number" ? metadata.startedAt : undefined,
|
|
159
200
|
completedAt: typeof metadata.completedAt === "number" ? metadata.completedAt : 0,
|
|
160
201
|
eventCount: typeof metadata.eventCount === "number" ? metadata.eventCount : 0,
|
|
202
|
+
...(isUsedMemoryArray(metadata.usedMemories) ? { usedMemories: metadata.usedMemories } : {}),
|
|
161
203
|
};
|
|
162
204
|
}
|
|
163
205
|
export function summarizeTurns(events) {
|
|
@@ -177,6 +219,7 @@ export function summarizeTurns(events) {
|
|
|
177
219
|
firstEventId: event.id,
|
|
178
220
|
lastEventId: event.id,
|
|
179
221
|
eventCount: 0,
|
|
222
|
+
usedMemories: [],
|
|
180
223
|
order,
|
|
181
224
|
};
|
|
182
225
|
current.startedAt ??= event.createdAt;
|
|
@@ -200,6 +243,9 @@ export function summarizeTurns(events) {
|
|
|
200
243
|
current.checkpointId = payload.currentHead;
|
|
201
244
|
current.runtimeTurnId = payload.lastTurnId;
|
|
202
245
|
}
|
|
246
|
+
else if (payload?.type === "memory_used") {
|
|
247
|
+
current.usedMemories = dedupeUsedMemories([...current.usedMemories, ...payload.memories]);
|
|
248
|
+
}
|
|
203
249
|
turns.set(event.turnId, current);
|
|
204
250
|
}
|
|
205
251
|
return [...turns.values()]
|
|
@@ -218,6 +264,7 @@ export function summarizeTurns(events) {
|
|
|
218
264
|
firstEventId: turn.firstEventId,
|
|
219
265
|
lastEventId: turn.lastEventId,
|
|
220
266
|
eventCount: turn.eventCount,
|
|
267
|
+
...(turn.usedMemories.length > 0 ? { usedMemories: turn.usedMemories } : {}),
|
|
221
268
|
order: turn.order,
|
|
222
269
|
}))
|
|
223
270
|
.sort((left, right) => right.order.time - left.order.time ||
|
|
@@ -236,6 +283,7 @@ export function structuredOutputForTurn(sessionId, turn) {
|
|
|
236
283
|
status: turn.status,
|
|
237
284
|
finalOutput,
|
|
238
285
|
...(turn.status === "failed" && finalOutput ? { error: finalOutput } : {}),
|
|
286
|
+
...(turn.usedMemories?.length ? { usedMemories: turn.usedMemories } : {}),
|
|
239
287
|
};
|
|
240
288
|
}
|
|
241
289
|
export function structuredOutputForStoredTurn(output) {
|
|
@@ -246,6 +294,7 @@ export function structuredOutputForStoredTurn(output) {
|
|
|
246
294
|
status: output.status,
|
|
247
295
|
finalOutput: output.output || undefined,
|
|
248
296
|
...(output.status === "failed" && output.output ? { error: output.output } : {}),
|
|
297
|
+
...(output.usedMemories?.length ? { usedMemories: output.usedMemories } : {}),
|
|
249
298
|
artifactPaths: {
|
|
250
299
|
output: output.outputPath,
|
|
251
300
|
latest: output.latestPath,
|
|
@@ -74,7 +74,7 @@ Session management:
|
|
|
74
74
|
happy-elves turn list <sessionId> [--limit 10] [--verbose] [--json]
|
|
75
75
|
happy-elves turn status <sessionId> <turnId> [--verbose] [--json]
|
|
76
76
|
happy-elves turn result <sessionId> [<turnId>] [--verbose] [--json]
|
|
77
|
-
happy-elves turn run <sessionId> (--text <prompt> | --text-file <path>) [--detach] [--timeout 1000s] [--inactivity-timeout 1000s] [--permission-mode approve-reads|approve-all|deny-all] [--json]
|
|
77
|
+
happy-elves turn run <sessionId> (--text <prompt> | --text-file <path>) [--detach] [--timeout 1000s] [--inactivity-timeout 1000s] [--permission-mode approve-reads|approve-all|deny-all] [--memory off|readonly|readwrite] [--memory-project <key>] [--memory-top-n 3] [--json]
|
|
78
78
|
happy-elves turn wait <sessionId> <turnId> [--timeout 1000s] --json
|
|
79
79
|
happy-elves turn cancel <sessionId> <turnId> [--reason <reason>] --json
|
|
80
80
|
happy-elves turn rewind <sessionId> <turn-or-checkpoint-id> --json
|
|
@@ -124,6 +124,7 @@ gateway serve is kept for fake/local debugging only; lark-cli is not a product d
|
|
|
124
124
|
happy-elves memory save [--machine <machineId>] [--project <projectKey>] [--layer user|curated|daily] (--text <text> | --text-file <path>) [--title <title>] [--date YYYY-MM-DD] --json
|
|
125
125
|
happy-elves memory read <USER.md|MEMORY.md|memory/YYYY-MM-DD.md> [--project <projectKey>] --json
|
|
126
126
|
happy-elves memory search --query <text> [--project <projectKey>] [--limit 5] --json
|
|
127
|
+
happy-elves memory promote --source <path:start-end[@contentHash]> --layer user|curated [--project <projectKey>] [--title <title>] --json
|
|
127
128
|
happy-elves memory reindex [--project <projectKey>] --json
|
|
128
129
|
happy-elves memory sync [--project <projectKey>] --json
|
|
129
130
|
|
|
@@ -156,7 +157,7 @@ missed and are never auto-caught-up.
|
|
|
156
157
|
happy-elves loop delete <loopId> --json
|
|
157
158
|
`,
|
|
158
159
|
daemon: `Usage:
|
|
159
|
-
happy-elves daemon pair --relay <url> --code <code> --secret <account-secret> [--name <machine-name>] --json
|
|
160
|
+
happy-elves daemon pair --relay <url> --code <code> --secret <account-secret> [--name <machine-name>] [--no-start] --json
|
|
160
161
|
happy-elves daemon start [--relay <url>] [--json]
|
|
161
162
|
happy-elves daemon stop [--json]
|
|
162
163
|
happy-elves daemon restart [--relay <url>] [--json]
|
|
@@ -72,7 +72,9 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
72
72
|
console.log("No matching memory.");
|
|
73
73
|
else
|
|
74
74
|
for (const item of results) {
|
|
75
|
-
|
|
75
|
+
const hash = item.citation.contentHash ? ` hash=${item.citation.contentHash.slice(0, 10)}` : "";
|
|
76
|
+
const stale = item.citation.stale ? " stale" : "";
|
|
77
|
+
console.log(`${item.path}:${item.citation.startLine}-${item.citation.endLine}${hash}${stale} ${item.snippet}`);
|
|
76
78
|
if (wantsVerbose(flags))
|
|
77
79
|
console.log(` score=${item.score}`);
|
|
78
80
|
}
|
|
@@ -81,6 +83,27 @@ export async function handleMemory({ domain, action, positional, flags }) {
|
|
|
81
83
|
ok("memory.search", result);
|
|
82
84
|
return true;
|
|
83
85
|
}
|
|
86
|
+
if (action === "promote") {
|
|
87
|
+
const machineId = await resolveMemoryMachineId(client, flags);
|
|
88
|
+
const projectKey = await resolveMemoryProjectKey(client, flags, machineId);
|
|
89
|
+
const source = typeof flags.source === "string" ? parseMemoryCitation(flags.source) : undefined;
|
|
90
|
+
if (!source)
|
|
91
|
+
throw new CliError("Missing --source <path:start-end>", "MISSING_ARGUMENT");
|
|
92
|
+
const targetLayer = parsePromotionLayer(flags.layer) ?? "curated";
|
|
93
|
+
const result = await client.memory({
|
|
94
|
+
machineId,
|
|
95
|
+
action: "promote",
|
|
96
|
+
projectKey,
|
|
97
|
+
promote: {
|
|
98
|
+
projectKey,
|
|
99
|
+
source,
|
|
100
|
+
targetLayer,
|
|
101
|
+
...(typeof flags.title === "string" ? { title: flags.title } : {}),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
ok("memory.promote", result);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
84
107
|
if (action === "reindex" || action === "sync") {
|
|
85
108
|
const machineId = await resolveMemoryMachineId(client, flags);
|
|
86
109
|
const projectKey = await resolveMemoryProjectKey(client, flags, machineId);
|
|
@@ -128,4 +151,28 @@ function parseMemoryMode(value) {
|
|
|
128
151
|
return value;
|
|
129
152
|
throw new CliError("Invalid --mode; expected off|readonly|readwrite", "INVALID_ARGUMENT");
|
|
130
153
|
}
|
|
154
|
+
function parsePromotionLayer(value) {
|
|
155
|
+
const layer = parseMemoryLayer(value);
|
|
156
|
+
if (!layer)
|
|
157
|
+
return undefined;
|
|
158
|
+
if (layer === "user" || layer === "curated")
|
|
159
|
+
return layer;
|
|
160
|
+
throw new CliError("Invalid --layer for promotion; expected user|curated", "INVALID_ARGUMENT");
|
|
161
|
+
}
|
|
162
|
+
function parseMemoryCitation(value) {
|
|
163
|
+
const match = value.match(/^(.+):(\d+)-(\d+)(?:@([A-Za-z0-9._-]+))?$/);
|
|
164
|
+
if (!match)
|
|
165
|
+
throw new CliError("Invalid --source; expected path:start-end or path:start-end@contentHash", "INVALID_ARGUMENT");
|
|
166
|
+
const startLine = Number(match[2]);
|
|
167
|
+
const endLine = Number(match[3]);
|
|
168
|
+
if (!Number.isInteger(startLine) || !Number.isInteger(endLine) || startLine < 1 || endLine < startLine) {
|
|
169
|
+
throw new CliError("Invalid --source line range", "INVALID_ARGUMENT");
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
path: match[1],
|
|
173
|
+
startLine,
|
|
174
|
+
endLine,
|
|
175
|
+
...(match[4] ? { contentHash: match[4] } : {}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
131
178
|
//# sourceMappingURL=memory.js.map
|
|
@@ -48,8 +48,10 @@ export async function handleSchedule({ domain, action, positional, flags }) {
|
|
|
48
48
|
if (schedules.length === 0)
|
|
49
49
|
console.log("No schedules.");
|
|
50
50
|
else
|
|
51
|
-
for (const schedule of schedules)
|
|
52
|
-
|
|
51
|
+
for (const schedule of schedules) {
|
|
52
|
+
const preview = schedule.previewRunAt?.length ? ` preview:${schedule.previewRunAt.join(",")}` : "";
|
|
53
|
+
console.log(`${schedule.id} ${schedule.status} ${cadenceLabel(schedule)} next:${schedule.nextRunAt ?? "manual"} source:${schedule.sourceSessionId ?? schedule.sessionId ?? "pending"}${preview}`);
|
|
54
|
+
}
|
|
53
55
|
return true;
|
|
54
56
|
}
|
|
55
57
|
ok("schedule.list", { machineId, schedules: schedules.map(compactSchedule) });
|
|
@@ -216,6 +218,7 @@ function compactSchedule(schedule) {
|
|
|
216
218
|
stopOn: schedule.stopOn,
|
|
217
219
|
dueAt: schedule.dueAt,
|
|
218
220
|
nextRunAt: schedule.nextRunAt,
|
|
221
|
+
previewRunAt: schedule.previewRunAt,
|
|
219
222
|
status: schedule.status,
|
|
220
223
|
runCount: schedule.runCount ?? schedule.runs.length,
|
|
221
224
|
lastRun: schedule.lastRun ?? schedule.runs[0],
|