@cydm/happy-elves 0.1.0-beta.240 → 0.1.0-beta.241

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.
@@ -311,6 +311,7 @@ export function structuredOutputForRunningTurn(sessionId, turnId) {
311
311
  };
312
312
  }
313
313
  function foldAliasedRuntimeEvents(events) {
314
+ events = dedupeSyntheticHistoricalEvents(events);
314
315
  const profiles = new Map();
315
316
  const runtimeToRequest = new Map();
316
317
  for (const event of events) {
@@ -349,6 +350,57 @@ function foldAliasedRuntimeEvents(events) {
349
350
  runtimeToRequest: duplicateRuntimeToRequest,
350
351
  };
351
352
  }
353
+ function dedupeSyntheticHistoricalEvents(events) {
354
+ const groups = new Map();
355
+ for (const event of events) {
356
+ const key = historicalComparableEventKey(event);
357
+ if (!key)
358
+ continue;
359
+ groups.set(key, [...(groups.get(key) ?? []), event]);
360
+ }
361
+ const keepIds = new Set();
362
+ const dropIds = new Set();
363
+ for (const group of groups.values()) {
364
+ if (group.length < 2 || !group.some(isSyntheticHistoricalEvent))
365
+ continue;
366
+ const preferred = group.find((event) => !isSyntheticHistoricalEvent(event)) ?? group[0];
367
+ if (preferred)
368
+ keepIds.add(preferred.id);
369
+ for (const event of group) {
370
+ if (event.id !== preferred?.id)
371
+ dropIds.add(event.id);
372
+ }
373
+ }
374
+ return events.filter((event) => !dropIds.has(event.id) || keepIds.has(event.id));
375
+ }
376
+ function isSyntheticHistoricalEvent(event) {
377
+ const messageId = event.messageId ?? "";
378
+ return messageId.startsWith("hist_") || messageId.startsWith("tail_");
379
+ }
380
+ function historicalComparableEventKey(event) {
381
+ const payload = event.decoded;
382
+ if (!payload)
383
+ return undefined;
384
+ if (payload.type === "user_prompt")
385
+ return [event.turnId, "user_prompt", comparableOutput(payload.text)].join("\0");
386
+ if (payload.type === "text_delta" && payload.stream !== "thought") {
387
+ return [event.turnId, "text_delta", payload.stream, comparableOutput(payload.text)].join("\0");
388
+ }
389
+ if (payload.type === "done") {
390
+ return [
391
+ event.turnId,
392
+ "done",
393
+ payload.status,
394
+ payload.currentHead ?? "",
395
+ payload.lastTurnId ?? "",
396
+ comparableOutput(payload.error ?? payload.stopReason ?? ""),
397
+ ].join("\0");
398
+ }
399
+ if (payload.type === "memory_used") {
400
+ return [event.turnId, "memory_used", payload.mode, payload.projectKey, JSON.stringify(payload.memories)].join("\0");
401
+ }
402
+ return undefined;
403
+ }
352
404
  function isDuplicateRuntimeOutput(requestOutput, runtimeOutput) {
353
405
  const request = comparableOutput(requestOutput);
354
406
  if (!request)
@@ -384,7 +436,7 @@ export async function rebuildTurnOutput(client, sessionId, turnId) {
384
436
  }
385
437
  export function conciseEvents(events) {
386
438
  const result = [];
387
- for (const event of events) {
439
+ for (const event of dedupeSyntheticHistoricalEvents(events)) {
388
440
  const payload = event.decoded;
389
441
  if (!payload)
390
442
  continue;
@@ -76,6 +76,8 @@ Session management:
76
76
  happy-elves turn result <sessionId> [<turnId>] [--verbose] [--json]
77
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
+ happy-elves turn reconcile <sessionId> [turnId] --json
80
+ happy-elves turn recover <sessionId> [turnId] --text <continuation> --confirm-risk [--detach] --json
79
81
  happy-elves turn cancel <sessionId> <turnId> [--reason <reason>] --json
80
82
  happy-elves turn rewind <sessionId> <turn-or-checkpoint-id> --json
81
83
  happy-elves turn fork <sessionId> <turn-or-checkpoint-id> --name <name> --json
@@ -84,6 +86,8 @@ Turn management:
84
86
  run starts one agent execution. It waits by default; use --detach for
85
87
  background execution.
86
88
  wait/result inspect a specific turn.
89
+ reconcile checks provider state without starting, resuming, or cancelling work.
90
+ recover creates a risk-confirmed recovery session and only sends the new continuation.
87
91
  rewind/fork operate on a turn or checkpoint id supplied by runtime history.
88
92
  `,
89
93
  collect: `Usage:
@@ -214,6 +218,8 @@ Core commands:
214
218
  turn run <sessionId> --text <prompt> [--timeout 1000s] [--json]
215
219
  turn wait <sessionId> <turnId> [--timeout 1000s] [--json]
216
220
  turn result <sessionId> [<turnId>] [--json]
221
+ turn reconcile <sessionId> [turnId] --json
222
+ turn recover <sessionId> [turnId] --text <continuation> --confirm-risk [--json]
217
223
  turn cancel <sessionId> <turnId> [--reason <reason>] --json
218
224
  turn rewind <sessionId> <turn-or-checkpoint-id> --json
219
225
  turn fork <sessionId> <turn-or-checkpoint-id> --name <name> --json
@@ -427,7 +427,7 @@ export async function handleSession({ domain, action, positional, flags }) {
427
427
  const latestCompletedTurn = turns.find((turn) => turn.status === "completed");
428
428
  const latestTerminalTurn = turns.find((turn) => turn.status !== "running");
429
429
  const orchestration = deriveOrchestration(session, recentEvents);
430
- const latestOutput = await readStoredTurnOutput(session.id).catch(() => undefined);
430
+ const latestOutput = await readStoredTurnOutput(session.id, latestCompletedTurn?.turnId).catch(async () => await readStoredTurnOutput(session.id).catch(() => undefined));
431
431
  const terminalOutput = latestCompletedTurn?.output || latestCompletedTurn?.terminalText || latestTerminalTurn?.output || latestTerminalTurn?.terminalText || undefined;
432
432
  const data = {
433
433
  state: orchestration.state,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-cli",
3
- "version": "0.1.0-beta.240",
3
+ "version": "0.1.0-beta.241",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,7 +1,9 @@
1
- import { type MachineCommand } from "../../../../packages/shared/dist/index.js";
1
+ import { type MachineCommand, type ScheduleRecord } from "../../../../packages/shared/dist/index.js";
2
2
  import type { DaemonConfig } from "../types.js";
3
3
  type ScheduleCommand = Extract<MachineCommand, {
4
4
  type: "machine:schedule";
5
5
  }>;
6
6
  export declare function handleScheduleCommand(ws: WebSocket, config: DaemonConfig, command: ScheduleCommand): Promise<void>;
7
+ declare function withSchedulePreview(schedule: ScheduleRecord): ScheduleRecord;
8
+ export declare const __testWithSchedulePreview: typeof withSchedulePreview;
7
9
  export {};
@@ -55,10 +55,16 @@ async function executeScheduleCommand(config, command) {
55
55
  }
56
56
  if (command.action === "pause" || command.action === "resume" || command.action === "delete") {
57
57
  const nextStatus = command.action === "pause" ? "paused" : command.action === "resume" ? "active" : "deleted";
58
+ const nowMs = Date.now();
58
59
  const store = await updateScheduleStore((current) => ({
59
60
  ...current,
60
61
  schedules: current.schedules.map((schedule) => schedule.id === command.scheduleId
61
- ? { ...schedule, status: nextStatus, updatedAt: new Date().toISOString() }
62
+ ? {
63
+ ...schedule,
64
+ status: nextStatus,
65
+ ...(nextStatus === "active" ? { nextRunAt: nextRunAtForSchedule(schedule, nowMs) } : { nextRunAt: undefined }),
66
+ updatedAt: new Date(nowMs).toISOString(),
67
+ }
62
68
  : schedule),
63
69
  }));
64
70
  return {
@@ -100,12 +106,20 @@ function requireSchedule(store, scheduleId) {
100
106
  return schedule;
101
107
  }
102
108
  function withSchedulePreview(schedule) {
109
+ if (schedule.status !== "active") {
110
+ return {
111
+ ...schedule,
112
+ nextRunAt: undefined,
113
+ previewRunAt: [],
114
+ };
115
+ }
103
116
  return {
104
117
  ...schedule,
105
118
  nextRunAt: schedule.nextRunAt ?? nextRunAtForSchedule(schedule, Date.now()),
106
119
  previewRunAt: nextRunPreviewForSchedule(schedule, 3, Date.now()),
107
120
  };
108
121
  }
122
+ export const __testWithSchedulePreview = withSchedulePreview;
109
123
  function normalizeCreateInput(input) {
110
124
  const prompt = input.prompt?.trim();
111
125
  if (!prompt)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.240",
3
+ "version": "0.1.0-beta.241",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.240",
3
+ "version": "0.1.0-beta.241",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {