@cydm/happy-elves 0.1.0-beta.84 → 0.1.0-beta.85
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 +2 -0
- 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/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 +2 -1
- 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/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
|
@@ -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",
|
|
@@ -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";
|
|
@@ -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
|
|
|
@@ -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],
|
|
@@ -70,6 +70,42 @@ function findTurnSummaryByAnyId(turns, id) {
|
|
|
70
70
|
turn.runtimeTurnId === id ||
|
|
71
71
|
turn.checkpointId === id);
|
|
72
72
|
}
|
|
73
|
+
function recoveryPromptWithContext(turns, inputTurnId, continuation) {
|
|
74
|
+
const chronological = [...turns].reverse();
|
|
75
|
+
const selected = [];
|
|
76
|
+
for (const turn of chronological) {
|
|
77
|
+
if (inputTurnId && (turn.turnId === inputTurnId || turn.runtimeTurnId === inputTurnId || turn.checkpointId === inputTurnId))
|
|
78
|
+
break;
|
|
79
|
+
if (turn.status === "running")
|
|
80
|
+
break;
|
|
81
|
+
selected.push(turn);
|
|
82
|
+
}
|
|
83
|
+
const blocks = selected.map((turn) => {
|
|
84
|
+
const lines = [`Turn ${turn.turnId} (${turn.status})`];
|
|
85
|
+
if (turn.prompt.trim())
|
|
86
|
+
lines.push(`User:\n${turn.prompt.trim()}`);
|
|
87
|
+
const output = (turn.output || turn.terminalText).trim();
|
|
88
|
+
if (output)
|
|
89
|
+
lines.push(`Assistant:\n${output}`);
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
});
|
|
92
|
+
const context = clampEnd(blocks.join("\n\n---\n\n"), 32 * 1024);
|
|
93
|
+
if (!context.trim())
|
|
94
|
+
return continuation;
|
|
95
|
+
return [
|
|
96
|
+
"Recovered context from the original Happy Elves session. The previous turn could not be confirmed, so do not assume it completed and do not replay its original prompt.",
|
|
97
|
+
"",
|
|
98
|
+
context,
|
|
99
|
+
"",
|
|
100
|
+
"User continuation:",
|
|
101
|
+
continuation,
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
function clampEnd(value, maxChars) {
|
|
105
|
+
if (value.length <= maxChars)
|
|
106
|
+
return value;
|
|
107
|
+
return value.slice(value.length - maxChars);
|
|
108
|
+
}
|
|
73
109
|
async function waitForTurn(client, sessionId, turnId, timeoutMs) {
|
|
74
110
|
const startedAt = Date.now();
|
|
75
111
|
while (true) {
|
|
@@ -127,6 +163,117 @@ export async function handleTurn({ domain, action, positional, flags }) {
|
|
|
127
163
|
ok("turn.status", wantsVerbose(flags) ? { sessionId, inputTurnId: turnId, turn } : { sessionId, inputTurnId: turnId, ...compactTurn(turn) }, { sessionId, turnId: turn.turnId });
|
|
128
164
|
return true;
|
|
129
165
|
}
|
|
166
|
+
if (action === "reconcile") {
|
|
167
|
+
const sessionId = requirePositional(positional[0], "sessionId");
|
|
168
|
+
const turnId = positional[1] ?? (typeof flags.turn === "string" ? flags.turn : undefined);
|
|
169
|
+
if (turnId)
|
|
170
|
+
rejectEventPrimitiveId(turnId);
|
|
171
|
+
const result = await client.reconcileTurn(sessionId, turnId);
|
|
172
|
+
if (wantsJson(flags)) {
|
|
173
|
+
ok("turn.reconcile", result, { requestId: result.requestId, sessionId, turnId: result.turnId ?? turnId });
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.log(`Reconcile: ${result.status}${result.terminalStatus ? ` (${result.terminalStatus})` : ""}`);
|
|
177
|
+
if (result.turnId)
|
|
178
|
+
console.log(`Turn: ${result.turnId}`);
|
|
179
|
+
if (result.providerTurnId)
|
|
180
|
+
console.log(`Provider turn: ${result.providerTurnId}`);
|
|
181
|
+
if (result.reason)
|
|
182
|
+
console.log(`Reason: ${result.reason}`);
|
|
183
|
+
if (result.status !== "terminal" && result.status !== "active") {
|
|
184
|
+
console.log("Unable to confirm whether the previous execution finished. Use turn recover with --confirm-risk before sending a continuation.");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (action === "recover") {
|
|
190
|
+
const sessionId = requirePositional(positional[0], "sessionId");
|
|
191
|
+
const inputTurnId = positional[1] ?? (typeof flags.turn === "string" ? flags.turn : undefined);
|
|
192
|
+
if (inputTurnId)
|
|
193
|
+
rejectEventPrimitiveId(inputTurnId);
|
|
194
|
+
if (flags["confirm-risk"] !== true) {
|
|
195
|
+
throw new CliError("Recovery can duplicate external side effects if the original turn later completes. Re-run with --confirm-risk to create a Recovery Session.", "RECOVERY_CONFIRMATION_REQUIRED");
|
|
196
|
+
}
|
|
197
|
+
const continuation = typeof flags.text === "string"
|
|
198
|
+
? flags.text
|
|
199
|
+
: typeof flags["text-file"] === "string"
|
|
200
|
+
? await readPromptTextFile(flags["text-file"])
|
|
201
|
+
: "";
|
|
202
|
+
if (!continuation.trim())
|
|
203
|
+
throw new CliError("Missing --text or --text-file", "MISSING_ARGUMENT");
|
|
204
|
+
const sourceSession = await requireProjectedSession(client, sessionId, "turn.recover");
|
|
205
|
+
const sourceMetadata = await client.decodeSessionMetadata(sourceSession);
|
|
206
|
+
const sourceName = sourceMetadata.name ?? sourceSession.name;
|
|
207
|
+
const recoveryName = `${sourceName} Recovery`;
|
|
208
|
+
const turns = summarizeTurns(await client.history(sessionId, 1000));
|
|
209
|
+
const targetTurn = inputTurnId ? findTurnSummaryByAnyId(turns, inputTurnId) : turns[0];
|
|
210
|
+
let recoverySessionId;
|
|
211
|
+
let recoveryMode = "new-session-context";
|
|
212
|
+
let forkError;
|
|
213
|
+
const checkpoint = targetTurn?.checkpointId && !targetTurn.checkpointId.startsWith("event:") ? targetTurn.checkpointId : undefined;
|
|
214
|
+
if (checkpoint) {
|
|
215
|
+
try {
|
|
216
|
+
const fork = await client.fork(sessionId, { from: checkpoint, name: recoveryName });
|
|
217
|
+
recoverySessionId = fork.sessionId;
|
|
218
|
+
recoveryMode = "checkpoint-fork";
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
forkError = error instanceof Error ? error.message : String(error);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
let promptText = continuation;
|
|
225
|
+
if (!recoverySessionId) {
|
|
226
|
+
const created = await client.createSession({
|
|
227
|
+
machineId: sourceSession.machineId,
|
|
228
|
+
agent: sourceMetadata.agent ?? sourceSession.agent,
|
|
229
|
+
cwd: sourceMetadata.cwd ?? sourceSession.cwd,
|
|
230
|
+
name: recoveryName,
|
|
231
|
+
createdFrom: "turn.recover",
|
|
232
|
+
permissionMode: parsePermissionMode(flags["permission-mode"] ?? flags.permission),
|
|
233
|
+
});
|
|
234
|
+
if (!created.sessionId)
|
|
235
|
+
throw new CliError("Recovery session create was acknowledged without sessionId", "RELAY_RESPONSE_INVALID");
|
|
236
|
+
recoverySessionId = created.sessionId;
|
|
237
|
+
promptText = recoveryPromptWithContext(turns, inputTurnId, continuation);
|
|
238
|
+
}
|
|
239
|
+
await client.wait(recoverySessionId, { timeoutMs: 120_000 });
|
|
240
|
+
const permissionMode = parsePermissionMode(flags["permission-mode"] ?? flags.permission);
|
|
241
|
+
const wait = flags.detach !== true && flags["no-wait"] !== true;
|
|
242
|
+
const memoryMode = parseMemoryModeFlag(flags["memory-mode"] ?? flags.memory) ?? "readonly";
|
|
243
|
+
const recoverySession = await client.getSession(recoverySessionId);
|
|
244
|
+
const memoryProject = typeof flags["memory-project"] === "string"
|
|
245
|
+
? flags["memory-project"]
|
|
246
|
+
: recoverySession
|
|
247
|
+
? `${recoverySession.machineId}:${recoverySession.cwd}`
|
|
248
|
+
: undefined;
|
|
249
|
+
const result = await client.run(recoverySessionId, promptText, {
|
|
250
|
+
wait,
|
|
251
|
+
permissionMode,
|
|
252
|
+
memoryMode,
|
|
253
|
+
...(memoryProject ? { memoryProjectKey: memoryProject } : {}),
|
|
254
|
+
memoryTopN: 3,
|
|
255
|
+
waitTimeoutMs: parseDurationMs(flags.timeout, DEFAULT_COMMAND_TIMEOUT_MS),
|
|
256
|
+
ackTimeoutMs: 15_000,
|
|
257
|
+
});
|
|
258
|
+
if (wantsJson(flags)) {
|
|
259
|
+
ok("turn.recover", {
|
|
260
|
+
sourceSessionId: sessionId,
|
|
261
|
+
sourceTurnId: targetTurn?.turnId ?? inputTurnId,
|
|
262
|
+
recoverySessionId,
|
|
263
|
+
recoveryTurnId: result.turnId,
|
|
264
|
+
recoveryMode,
|
|
265
|
+
...(forkError ? { forkError } : {}),
|
|
266
|
+
}, { requestId: result.requestId, sessionId: recoverySessionId, turnId: result.turnId });
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
console.log(`Recovery session: ${recoverySessionId}`);
|
|
270
|
+
console.log(`Recovery mode: ${recoveryMode}`);
|
|
271
|
+
if (forkError)
|
|
272
|
+
console.log(`Checkpoint fork fallback: ${forkError}`);
|
|
273
|
+
console.log(`Turn: ${result.turnId}`);
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
130
277
|
if (action === "run") {
|
|
131
278
|
const sessionId = requirePositional(positional[0], "sessionId");
|
|
132
279
|
const text = typeof flags.text === "string"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ControllerClient, ok, readConfig, requirePositional, requireString, wantsJson } from "./lib/index.js";
|
|
1
|
+
import { CliError, ControllerClient, ok, readConfig, requirePositional, requireString, wantsJson } from "./lib/index.js";
|
|
2
2
|
export async function handleWorkspace({ domain, action, positional, flags }) {
|
|
3
3
|
if (domain !== "workspace")
|
|
4
4
|
return false;
|
|
@@ -49,9 +49,17 @@ export async function handleWorkspace({ domain, action, positional, flags }) {
|
|
|
49
49
|
const machineId = typeof flags.machine === "string" ? flags.machine : undefined;
|
|
50
50
|
const cwd = typeof flags.cwd === "string" ? flags.cwd : undefined;
|
|
51
51
|
const agent = typeof flags.agent === "string" ? flags.agent : undefined;
|
|
52
|
-
|
|
52
|
+
const memoryMode = parseMemoryMode(flags["memory-mode"] ?? flags.memory);
|
|
53
|
+
ok("workspace.prefer", await client.setWorkspaceProjectPreference({ projectKey, machineId, cwd, agent, ...(memoryMode ? { memoryMode } : {}) }));
|
|
53
54
|
return true;
|
|
54
55
|
}
|
|
55
56
|
return false;
|
|
56
57
|
}
|
|
58
|
+
function parseMemoryMode(value) {
|
|
59
|
+
if (value === undefined || value === false)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (value === "off" || value === "readonly" || value === "readwrite")
|
|
62
|
+
return value;
|
|
63
|
+
throw new CliError("Invalid --memory-mode; expected off|readonly|readwrite", "INVALID_ARGUMENT");
|
|
64
|
+
}
|
|
57
65
|
//# sourceMappingURL=workspace.js.map
|
package/apps/cli/package.json
CHANGED
|
@@ -14,7 +14,7 @@ export type GatewayInboundResult = {
|
|
|
14
14
|
duplicate?: boolean;
|
|
15
15
|
queued?: boolean;
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
type GatewayInboundSender = (input: {
|
|
18
18
|
gatewayId: string;
|
|
19
19
|
platform: "feishu" | "fake";
|
|
20
20
|
platformEventId: string;
|
|
@@ -35,3 +35,4 @@ export type GatewayAdapterContext = {
|
|
|
35
35
|
readChannel: () => Promise<GatewayChannelConfig | undefined>;
|
|
36
36
|
sendInbound: GatewayInboundSender;
|
|
37
37
|
};
|
|
38
|
+
export {};
|
|
@@ -56,10 +56,6 @@ export declare function createFeishuProcessingReactionTracker(client: LarkClient
|
|
|
56
56
|
clear(): Promise<void>;
|
|
57
57
|
pendingCount(): number;
|
|
58
58
|
};
|
|
59
|
-
export declare function createFeishuBotOpenIdResolver(client: LarkClient): {
|
|
60
|
-
get(): Promise<FeishuBotIdentity | undefined>;
|
|
61
|
-
cached(): FeishuBotIdentity | undefined;
|
|
62
|
-
};
|
|
63
59
|
export declare function normalizeFeishuMessage(data: unknown): NormalizedFeishuInbound | undefined;
|
|
64
60
|
export declare function shouldHandleFeishuInbound(profile: {
|
|
65
61
|
inboundMode: "mention" | "all";
|
|
@@ -144,7 +144,7 @@ export function createFeishuProcessingReactionTracker(client, input = {}) {
|
|
|
144
144
|
}
|
|
145
145
|
return { start, complete, clear, pendingCount: () => pending.size };
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
function createFeishuBotOpenIdResolver(client) {
|
|
148
148
|
let cached;
|
|
149
149
|
let inflight;
|
|
150
150
|
async function get() {
|
|
@@ -18,11 +18,6 @@ type GatewayTerminalInput = {
|
|
|
18
18
|
error?: string;
|
|
19
19
|
outputPreview?: string;
|
|
20
20
|
};
|
|
21
|
-
export declare function gatewayRuntimeIsListening(): boolean;
|
|
22
|
-
export declare function gatewayRuntimeEndpoint(): {
|
|
23
|
-
host: string;
|
|
24
|
-
port: number;
|
|
25
|
-
};
|
|
26
21
|
export declare function startGatewayRuntime(config: DaemonConfig): Promise<void>;
|
|
27
22
|
export declare function stopGatewayRuntime(): Promise<void>;
|
|
28
23
|
export declare function syncGatewayRuntime(config?: DaemonConfig | undefined): Promise<void>;
|
|
@@ -10,10 +10,7 @@ let gatewayServer;
|
|
|
10
10
|
let gatewayRuntimeConfig;
|
|
11
11
|
const gatewayAdapters = new Map();
|
|
12
12
|
let gatewaySyncChain = Promise.resolve();
|
|
13
|
-
|
|
14
|
-
return Boolean(gatewayServer?.listening) || gatewayAdapters.size > 0;
|
|
15
|
-
}
|
|
16
|
-
export function gatewayRuntimeEndpoint() {
|
|
13
|
+
function gatewayRuntimeEndpoint() {
|
|
17
14
|
return {
|
|
18
15
|
host: process.env.HAPPY_ELVES_GATEWAY_HOST || "127.0.0.1",
|
|
19
16
|
port: parseGatewayPort(process.env.HAPPY_ELVES_GATEWAY_PORT),
|
|
@@ -66,7 +66,7 @@ export type GatewayPendingReply = {
|
|
|
66
66
|
createdAt: string;
|
|
67
67
|
updatedAt: string;
|
|
68
68
|
};
|
|
69
|
-
|
|
69
|
+
type GatewayReplyHistory = {
|
|
70
70
|
channelId: string;
|
|
71
71
|
externalConversationId: string;
|
|
72
72
|
externalMessageId: string;
|
|
@@ -89,3 +89,4 @@ export declare function readGatewayStore(): Promise<GatewayStore>;
|
|
|
89
89
|
export declare function updateGatewayStore(update: (store: GatewayStore) => GatewayStore | Promise<GatewayStore>): Promise<GatewayStore>;
|
|
90
90
|
export declare function publicGatewayChannel(channel: GatewayChannelConfig, listening?: boolean): GatewayChannelPublic;
|
|
91
91
|
export declare function upsertGatewayChannel(current: GatewayStore, input: GatewayChannelInput, defaultMachineId: string): GatewayStore;
|
|
92
|
+
export {};
|