@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.
Files changed (70) hide show
  1. package/apps/cli/dist/commands/daemon.js +87 -3
  2. package/apps/cli/dist/commands/lib/args.js +3 -0
  3. package/apps/cli/dist/commands/lib/bootstrap-daemon.js +2 -2
  4. package/apps/cli/dist/commands/lib/gateway-store.d.ts +0 -4
  5. package/apps/cli/dist/commands/lib/gateway-store.js +0 -8
  6. package/apps/cli/dist/commands/lib/index.d.ts +0 -1
  7. package/apps/cli/dist/commands/lib/index.js +0 -1
  8. package/apps/cli/dist/commands/lib/local-daemon.d.ts +1 -0
  9. package/apps/cli/dist/commands/lib/local-daemon.js +14 -0
  10. package/apps/cli/dist/commands/lib/paths.d.ts +0 -1
  11. package/apps/cli/dist/commands/lib/paths.js +0 -1
  12. package/apps/cli/dist/commands/lib/session-output.d.ts +12 -0
  13. package/apps/cli/dist/commands/lib/session-output.js +49 -0
  14. package/apps/cli/dist/commands/lib/usage.js +3 -2
  15. package/apps/cli/dist/commands/memory.js +48 -1
  16. package/apps/cli/dist/commands/schedule.js +5 -2
  17. package/apps/cli/dist/commands/turn.js +147 -0
  18. package/apps/cli/dist/commands/workspace.js +10 -2
  19. package/apps/cli/package.json +1 -1
  20. package/apps/daemon/dist/gateway/adapters.d.ts +2 -1
  21. package/apps/daemon/dist/gateway/feishu.d.ts +0 -4
  22. package/apps/daemon/dist/gateway/feishu.js +1 -1
  23. package/apps/daemon/dist/gateway/runtime.d.ts +0 -5
  24. package/apps/daemon/dist/gateway/runtime.js +1 -4
  25. package/apps/daemon/dist/gateway/store.d.ts +2 -1
  26. package/apps/daemon/dist/memory/handler.js +15 -5
  27. package/apps/daemon/dist/memory/manager.d.ts +7 -4
  28. package/apps/daemon/dist/memory/manager.js +51 -11
  29. package/apps/daemon/dist/relay/connection.js +5 -0
  30. package/apps/daemon/dist/relay/send.js +1 -0
  31. package/apps/daemon/dist/runtime/external-provider.js +119 -0
  32. package/apps/daemon/dist/schedule/handler.js +12 -5
  33. package/apps/daemon/dist/schedule/runner.d.ts +9 -1
  34. package/apps/daemon/dist/schedule/runner.js +84 -4
  35. package/apps/daemon/dist/schedule/store.d.ts +1 -2
  36. package/apps/daemon/dist/schedule/store.js +7 -1
  37. package/apps/daemon/dist/session/prompt.js +18 -0
  38. package/apps/daemon/dist/session/reconcile.d.ts +3 -0
  39. package/apps/daemon/dist/session/reconcile.js +235 -0
  40. package/apps/daemon/dist/turn-coordinator.d.ts +1 -0
  41. package/apps/daemon/dist/turn-coordinator.js +4 -0
  42. package/apps/daemon/package.json +1 -1
  43. package/apps/relay/dist/controller-handlers.d.ts +9 -0
  44. package/apps/relay/dist/controller-handlers.js +53 -3
  45. package/apps/relay/dist/db.js +2 -0
  46. package/apps/relay/dist/http-routes.js +10 -4
  47. package/apps/relay/dist/http-schemas.d.ts +5 -0
  48. package/apps/relay/dist/http-schemas.js +1 -0
  49. package/apps/relay/dist/index.js +28 -0
  50. package/apps/relay/dist/machine-handlers.js +58 -0
  51. package/package.json +1 -1
  52. package/packages/agent-sdk/dist/index.d.ts +31 -0
  53. package/packages/agent-sdk/dist/index.js +4 -0
  54. package/packages/client/dist/account.js +1 -0
  55. package/packages/client/dist/client.d.ts +2 -1
  56. package/packages/client/dist/client.js +13 -1
  57. package/packages/client/dist/parsers.js +4 -0
  58. package/packages/client/dist/transport.d.ts +5 -0
  59. package/packages/client/dist/transport.js +7 -0
  60. package/packages/client/dist/types.d.ts +11 -2
  61. package/packages/runtime/dist/index.d.ts +26 -0
  62. package/packages/runtime-acpx/dist/index.js +8 -0
  63. package/packages/runtime-cli/dist/index.js +77 -0
  64. package/packages/shared/dist/protocol-schemas.d.ts +96 -3
  65. package/packages/shared/dist/protocol-schemas.js +35 -1
  66. package/packages/shared/dist/protocol-types.d.ts +36 -1
  67. package/packages/shared/dist/protocol.d.ts +29 -4
  68. package/packages/shared/dist/protocol.js +32 -3
  69. package/apps/cli/dist/commands/lib/schedule-store.d.ts +0 -28
  70. 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
- ok("daemon.pair", {
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,
@@ -8,4 +8,3 @@ export declare const daemonPidPath: string;
8
8
  export declare const loopStorePath: string;
9
9
  export declare const loopStoreLockPath: string;
10
10
  export declare const gatewayStorePath: string;
11
- export declare const scheduleStorePath: string;
@@ -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
- console.log(`${item.path}:${item.citation.startLine}-${item.citation.endLine} ${item.snippet}`);
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
- console.log(`${schedule.id} ${schedule.status} ${cadenceLabel(schedule)} next:${schedule.nextRunAt ?? "manual"} source:${schedule.sourceSessionId ?? schedule.sessionId ?? "pending"}`);
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],