@cydm/happy-elves 0.1.0-beta.86 → 0.1.0-beta.87

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-cli",
3
- "version": "0.1.0-beta.86",
3
+ "version": "0.1.0-beta.87",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,4 +1,30 @@
1
1
  import { type MachineCommand } from "../../../../packages/shared/dist/index.js";
2
2
  import type { DaemonConfig } from "../types.js";
3
+ type PromptCommand = Extract<MachineCommand, {
4
+ type: "machine:prompt";
5
+ }>;
6
+ type PromptExecutionPhase = "decrypt-prompt" | "decrypt-memory-options" | "prepare-memory-context" | "emit-submitted" | "emit-memory-status" | "start-turn" | "attach-turn" | "audit-turn-start" | "stream-turn" | "await-turn-result" | "write-memory" | "refresh-runtime-metadata" | "emit-terminal" | "refresh-authoritative-transcript" | "send-turn-done" | "terminal-hooks";
3
7
  export declare function handlePrompt(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
8
+ export declare function promptFailureResponse(command: PromptCommand, phase: PromptExecutionPhase, error: unknown): {
9
+ type: "machine:error";
10
+ requestId: string;
11
+ sessionId: string;
12
+ code: string;
13
+ message: string;
14
+ };
15
+ export declare function promptFailureAuditEvent(config: Pick<DaemonConfig, "machineId">, command: PromptCommand, phase: PromptExecutionPhase, error: unknown): {
16
+ sessionId: string;
17
+ machineId: string;
18
+ actor: "system";
19
+ action: string;
20
+ summary: string;
21
+ evidence: {
22
+ requestId: string;
23
+ turnId: string;
24
+ code: string;
25
+ phase: PromptExecutionPhase;
26
+ message: string;
27
+ };
28
+ };
4
29
  export declare function handleCancel(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
30
+ export {};
@@ -52,19 +52,24 @@ export async function handlePrompt(ws, config, command) {
52
52
  activePromptRequests.delete(command.requestId);
53
53
  releaseSessionTurn(command.sessionId, command.requestId);
54
54
  };
55
+ let phase = "decrypt-prompt";
55
56
  try {
56
57
  const prompt = await decryptJson(config.accountSecret, command.encryptedPrompt);
57
58
  const rawText = prompt.type === "user_prompt" ? prompt.text : JSON.stringify(prompt);
59
+ phase = "decrypt-memory-options";
58
60
  const memoryOptions = await decryptMemoryOptions(config, command);
61
+ phase = "prepare-memory-context";
59
62
  const memoryContext = memoryOptions
60
63
  ? await prepareMemoryContext(memoryOptions, rawText)
61
64
  : { text: rawText, used: [], options: undefined };
62
65
  const text = memoryContext.text;
66
+ phase = "emit-submitted";
63
67
  await emitEvent(ws, config, command.sessionId, command.turnId, {
64
68
  type: "status",
65
69
  text: "Prompt submitted to runtime",
66
70
  });
67
71
  if (memoryOptions) {
72
+ phase = "emit-memory-status";
68
73
  await emitEvent(ws, config, command.sessionId, command.turnId, {
69
74
  type: "status",
70
75
  tag: "memory",
@@ -90,6 +95,7 @@ export async function handlePrompt(ws, config, command) {
90
95
  }
91
96
  }
92
97
  const previousBackendSessionId = session.handle.backendSessionId;
98
+ phase = "start-turn";
93
99
  const turn = getRuntime(session.cwd).startTurn({
94
100
  handle: session.handle,
95
101
  text,
@@ -98,9 +104,11 @@ export async function handlePrompt(ws, config, command) {
98
104
  requestId: command.requestId,
99
105
  timeoutMs: command.timeoutMs,
100
106
  });
107
+ phase = "attach-turn";
101
108
  if (!attachClaimedTurn(command.sessionId, command.requestId, command.turnId, turn)) {
102
109
  throw new Error(`Session turn claim was lost before start: ${command.sessionId}`);
103
110
  }
111
+ phase = "audit-turn-start";
104
112
  await appendAudit({
105
113
  sessionId: command.sessionId,
106
114
  machineId: config.machineId,
@@ -118,6 +126,7 @@ export async function handlePrompt(ws, config, command) {
118
126
  },
119
127
  });
120
128
  let outputPreview = "";
129
+ phase = "stream-turn";
121
130
  for await (const event of turn.events) {
122
131
  const payload = runtimeEventToPayload(event);
123
132
  if (payload.type === "text_delta" && payload.stream !== "thought") {
@@ -125,7 +134,9 @@ export async function handlePrompt(ws, config, command) {
125
134
  }
126
135
  await emitEvent(ws, config, command.sessionId, command.turnId, payload);
127
136
  }
137
+ phase = "await-turn-result";
128
138
  const result = await turn.result;
139
+ phase = "write-memory";
129
140
  const dailyMemory = await writeTurnMemory(memoryOptions, {
130
141
  promptText: rawText,
131
142
  outputPreview,
@@ -144,6 +155,7 @@ export async function handlePrompt(ws, config, command) {
144
155
  ].filter(Boolean).join(" · "),
145
156
  });
146
157
  }
158
+ phase = "refresh-runtime-metadata";
147
159
  const refreshedMetadata = session.handle.backendSessionId
148
160
  ? await encryptedSessionMetadata(config, command.sessionId, session, {
149
161
  currentHead: result.currentHead,
@@ -165,6 +177,7 @@ export async function handlePrompt(ws, config, command) {
165
177
  },
166
178
  });
167
179
  }
180
+ phase = "emit-terminal";
168
181
  await emitEvent(ws, config, command.sessionId, command.turnId, result.status === "failed"
169
182
  ? {
170
183
  type: "done",
@@ -180,8 +193,10 @@ export async function handlePrompt(ws, config, command) {
180
193
  currentHead: result.currentHead,
181
194
  lastTurnId: result.lastTurnId,
182
195
  });
196
+ phase = "refresh-authoritative-transcript";
183
197
  await refreshAuthoritativeTranscriptAfterTurn(ws, config, session, command.requestId, result.currentHead, result.lastTurnId, result.status);
184
198
  releasePromptTurn();
199
+ phase = "send-turn-done";
185
200
  await sendReliable({
186
201
  type: "machine:turnDone",
187
202
  sessionId: command.sessionId,
@@ -192,6 +207,7 @@ export async function handlePrompt(ws, config, command) {
192
207
  error: result.status === "failed" ? result.error.message : undefined,
193
208
  encryptedMetadata: refreshedMetadata,
194
209
  }, ws);
210
+ phase = "terminal-hooks";
195
211
  await handleGatewayTurnTerminal(config, {
196
212
  sessionId: command.sessionId,
197
213
  requestId: command.requestId,
@@ -231,10 +247,82 @@ export async function handlePrompt(ws, config, command) {
231
247
  },
232
248
  });
233
249
  }
250
+ catch (error) {
251
+ const response = promptFailureResponse(command, phase, error);
252
+ await appendAudit(promptFailureAuditEvent(config, command, phase, error)).catch((auditError) => {
253
+ console.error(`Prompt failure audit failed for ${command.turnId}: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
254
+ });
255
+ await sendCommandResponse(ws, response);
256
+ }
234
257
  finally {
235
258
  releasePromptTurn();
236
259
  }
237
260
  }
261
+ export function promptFailureResponse(command, phase, error) {
262
+ const code = promptFailureCode(phase, error);
263
+ return {
264
+ type: "machine:error",
265
+ requestId: command.requestId,
266
+ sessionId: command.sessionId,
267
+ code,
268
+ message: promptFailureMessage(code, error),
269
+ };
270
+ }
271
+ export function promptFailureAuditEvent(config, command, phase, error) {
272
+ const code = promptFailureCode(phase, error);
273
+ return {
274
+ sessionId: command.sessionId,
275
+ machineId: config.machineId,
276
+ actor: "system",
277
+ action: "turn.prompt.failed",
278
+ summary: `Failed to run prompt turn ${command.turnId}`,
279
+ evidence: {
280
+ requestId: command.requestId,
281
+ turnId: command.turnId,
282
+ code,
283
+ phase,
284
+ message: safeErrorMessage(error),
285
+ },
286
+ };
287
+ }
288
+ function promptFailureCode(phase, error) {
289
+ if (phase === "decrypt-prompt" && isCryptoOperationFailure(error))
290
+ return "PROMPT_DECRYPT_FAILED";
291
+ if (phase === "decrypt-memory-options" && isCryptoOperationFailure(error))
292
+ return "MEMORY_OPTIONS_DECRYPT_FAILED";
293
+ if (phase === "start-turn" || phase === "attach-turn")
294
+ return "TURN_START_FAILED";
295
+ if (phase === "stream-turn" || phase === "await-turn-result")
296
+ return "TURN_RUNTIME_FAILED";
297
+ if (phase === "refresh-runtime-metadata")
298
+ return "SESSION_METADATA_REFRESH_FAILED";
299
+ if (phase === "emit-submitted" || phase === "emit-memory-status" || phase === "emit-terminal")
300
+ return "SESSION_EVENT_EMIT_FAILED";
301
+ return "PROMPT_EXECUTION_FAILED";
302
+ }
303
+ function promptFailureMessage(code, error) {
304
+ if (code === "PROMPT_DECRYPT_FAILED") {
305
+ return "Failed to decrypt the prompt for this daemon. Re-pair the daemon with the correct account secret.";
306
+ }
307
+ if (code === "MEMORY_OPTIONS_DECRYPT_FAILED") {
308
+ return "Failed to decrypt memory options for this turn.";
309
+ }
310
+ return safeErrorMessage(error);
311
+ }
312
+ function isCryptoOperationFailure(error) {
313
+ const record = error && typeof error === "object" ? error : {};
314
+ const name = typeof record.name === "string" ? record.name : "";
315
+ const code = typeof record.code === "string" ? record.code : "";
316
+ const message = safeErrorMessage(error);
317
+ return name === "OperationError" ||
318
+ code === "ERR_OSSL_BAD_DECRYPT" ||
319
+ /operation-specific reason|decrypt|authentication tag|Unsupported state or unable to authenticate data/i.test(message);
320
+ }
321
+ function safeErrorMessage(error) {
322
+ if (error instanceof Error && error.message)
323
+ return error.message;
324
+ return String(error);
325
+ }
238
326
  async function decryptMemoryOptions(config, command) {
239
327
  if (!command.encryptedMemoryOptions)
240
328
  return undefined;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.86",
3
+ "version": "0.1.0-beta.87",
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.86",
3
+ "version": "0.1.0-beta.87",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {