@cydm/happy-elves 0.1.0-beta.248 → 0.1.0-beta.254

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 (44) hide show
  1. package/apps/cli/dist/commands/session.js +2 -1
  2. package/apps/cli/dist/commands/turn.js +7 -2
  3. package/apps/cli/package.json +1 -1
  4. package/apps/daemon/dist/gateway/runtime.js +2 -0
  5. package/apps/daemon/dist/relay/admission.d.ts +2 -0
  6. package/apps/daemon/dist/relay/connection.js +5 -1
  7. package/apps/daemon/dist/relay/register.js +2 -0
  8. package/apps/daemon/dist/runtime/external-provider.js +56 -14
  9. package/apps/daemon/dist/session/events.js +10 -0
  10. package/apps/daemon/dist/session/lifecycle.js +7 -0
  11. package/apps/daemon/dist/session/metadata.d.ts +1 -1
  12. package/apps/daemon/dist/session/metadata.js +2 -0
  13. package/apps/daemon/dist/session/prompt.d.ts +2 -1
  14. package/apps/daemon/dist/session/prompt.js +122 -2
  15. package/apps/daemon/dist/types.d.ts +3 -0
  16. package/apps/daemon/package.json +1 -1
  17. package/apps/relay/dist/controller-handlers.js +27 -0
  18. package/apps/relay/dist/http-routes.js +2 -0
  19. package/apps/relay/dist/http-schemas.d.ts +2 -0
  20. package/apps/relay/dist/http-schemas.js +2 -0
  21. package/apps/relay/dist/machine-handlers.js +14 -2
  22. package/apps/relay/dist/relay-context.js +45 -1
  23. package/package.json +1 -1
  24. package/packages/agent-sdk/dist/index.d.ts +39 -0
  25. package/packages/agent-sdk/dist/index.js +12 -0
  26. package/packages/client/dist/client.js +8 -0
  27. package/packages/client/dist/types.d.ts +2 -0
  28. package/packages/runtime/dist/index.d.ts +30 -0
  29. package/packages/runtime-cli/dist/claude-turn.d.ts +1 -0
  30. package/packages/runtime-cli/dist/claude-turn.js +1 -0
  31. package/packages/runtime-cli/dist/codex-app-server.d.ts +5 -2
  32. package/packages/runtime-cli/dist/codex-app-server.js +25 -16
  33. package/packages/runtime-cli/dist/codex-protocol.d.ts +2 -2
  34. package/packages/runtime-cli/dist/codex-protocol.js +4 -4
  35. package/packages/runtime-cli/dist/index.js +79 -2
  36. package/packages/runtime-cli/dist/session-store.d.ts +1 -0
  37. package/packages/runtime-cli/dist/session-store.js +9 -8
  38. package/packages/shared/dist/index.d.ts +1 -0
  39. package/packages/shared/dist/index.js +1 -0
  40. package/packages/shared/dist/protocol-schemas.d.ts +14 -0
  41. package/packages/shared/dist/protocol-schemas.js +7 -0
  42. package/packages/shared/dist/protocol-types.d.ts +32 -0
  43. package/packages/shared/dist/protocol.d.ts +26 -0
  44. package/packages/shared/dist/protocol.js +29 -0
@@ -353,8 +353,9 @@ export async function handleSession({ domain, action, positional, flags }) {
353
353
  const requestedRuntimeName = typeof flags.name === "string" ? flags.name : undefined;
354
354
  const name = "main";
355
355
  const permissionMode = parsePermissionMode(flags["permission-mode"] ?? flags.permission);
356
+ const model = typeof flags.model === "string" && flags.model.trim() ? flags.model.trim() : undefined;
356
357
  const client = await makeClient();
357
- const result = await client.createSession({ machineId, agent, cwd, name, runtimeName: requestedRuntimeName, permissionMode });
358
+ const result = await client.createSession({ machineId, agent, cwd, name, runtimeName: requestedRuntimeName, permissionMode, model });
358
359
  const session = result.sessionId ? await client.getSession(result.sessionId) : undefined;
359
360
  const projectedSession = session
360
361
  ? wantsVerbose(flags)
@@ -201,6 +201,8 @@ export async function handleTurn({ domain, action, positional, flags }) {
201
201
  : "";
202
202
  if (!continuation.trim())
203
203
  throw new CliError("Missing --text or --text-file", "MISSING_ARGUMENT");
204
+ const permissionMode = parsePermissionMode(flags["permission-mode"] ?? flags.permission);
205
+ const model = typeof flags.model === "string" && flags.model.trim() ? flags.model.trim() : undefined;
204
206
  const sourceSession = await requireProjectedSession(client, sessionId, "turn.recover");
205
207
  const sourceMetadata = await client.decodeSessionMetadata(sourceSession);
206
208
  const sourceName = sourceMetadata.name ?? sourceSession.name;
@@ -229,7 +231,8 @@ export async function handleTurn({ domain, action, positional, flags }) {
229
231
  cwd: sourceMetadata.cwd ?? sourceSession.cwd,
230
232
  name: recoveryName,
231
233
  createdFrom: "turn.recover",
232
- permissionMode: parsePermissionMode(flags["permission-mode"] ?? flags.permission),
234
+ permissionMode,
235
+ model,
233
236
  });
234
237
  if (!created.sessionId)
235
238
  throw new CliError("Recovery session create was acknowledged without sessionId", "RELAY_RESPONSE_INVALID");
@@ -237,7 +240,6 @@ export async function handleTurn({ domain, action, positional, flags }) {
237
240
  promptText = recoveryPromptWithContext(turns, inputTurnId, continuation);
238
241
  }
239
242
  await client.wait(recoverySessionId, { timeoutMs: 120_000 });
240
- const permissionMode = parsePermissionMode(flags["permission-mode"] ?? flags.permission);
241
243
  const wait = flags.detach !== true && flags["no-wait"] !== true;
242
244
  const memoryMode = parseMemoryModeFlag(flags["memory-mode"] ?? flags.memory) ?? "readonly";
243
245
  const recoverySession = await client.getSession(recoverySessionId);
@@ -250,6 +252,7 @@ export async function handleTurn({ domain, action, positional, flags }) {
250
252
  wait,
251
253
  permissionMode,
252
254
  memoryMode,
255
+ model,
253
256
  ...(memoryProject ? { memoryProjectKey: memoryProject } : {}),
254
257
  memoryTopN: 3,
255
258
  waitTimeoutMs: parseDurationMs(flags.timeout, DEFAULT_COMMAND_TIMEOUT_MS),
@@ -289,6 +292,7 @@ export async function handleTurn({ domain, action, positional, flags }) {
289
292
  const ackTimeoutMs = 15_000;
290
293
  const inactivityTimeoutMs = parseDurationMs(flags["inactivity-timeout"] ?? flags["runtime-timeout"], DEFAULT_COMMAND_TIMEOUT_MS);
291
294
  const memoryMode = parseMemoryModeFlag(flags["memory-mode"] ?? flags.memory) ?? "readonly";
295
+ const model = typeof flags.model === "string" && flags.model.trim() ? flags.model.trim() : undefined;
292
296
  const session = await client.getSession(sessionId);
293
297
  const memoryProject = typeof flags["memory-project"] === "string"
294
298
  ? flags["memory-project"]
@@ -302,6 +306,7 @@ export async function handleTurn({ domain, action, positional, flags }) {
302
306
  wait,
303
307
  permissionMode,
304
308
  memoryMode,
309
+ model,
305
310
  ...(memoryProject ? { memoryProjectKey: memoryProject } : {}),
306
311
  memoryTopN,
307
312
  waitTimeoutMs: watchTimeoutMs,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-cli",
3
- "version": "0.1.0-beta.248",
3
+ "version": "0.1.0-beta.254",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -294,6 +294,7 @@ async function createGatewayMapping(config, channel, externalConversationId, cre
294
294
  agent: channel.defaultAgent,
295
295
  cwd: channel.targetCwd,
296
296
  name: `Gateway ${channel.type}: ${externalConversationId}`,
297
+ ...(channel.defaultModel ? { model: channel.defaultModel } : {}),
297
298
  encryptedMetadata: await encryptJson(config.accountSecret, `gateway:create:${createRequestId}`, {
298
299
  createdFrom: "gateway",
299
300
  gateway: {
@@ -353,6 +354,7 @@ async function admitGatewayPromptNow(config, channel, input) {
353
354
  text: input.text,
354
355
  }),
355
356
  permissionMode: "approve-reads",
357
+ ...(channel.defaultModel ? { model: channel.defaultModel } : {}),
356
358
  });
357
359
  const turnId = sent.turnId ?? input.turnId;
358
360
  const lastAdmitted = {
@@ -15,6 +15,7 @@ export declare function admitMachineCreateSession(config: DaemonConfig, input: {
15
15
  agent: string;
16
16
  cwd: string;
17
17
  name: string;
18
+ model?: string;
18
19
  encryptedMetadata?: EncryptedEnvelope;
19
20
  }): Promise<MachineAdmissionAck>;
20
21
  export declare function admitMachinePrompt(config: DaemonConfig, input: {
@@ -24,5 +25,6 @@ export declare function admitMachinePrompt(config: DaemonConfig, input: {
24
25
  encryptedPrompt: EncryptedEnvelope;
25
26
  encryptedMemoryOptions?: EncryptedEnvelope;
26
27
  permissionMode?: PermissionMode;
28
+ model?: string;
27
29
  timeoutMs?: number;
28
30
  }): Promise<MachineAdmissionAck>;
@@ -7,7 +7,7 @@ import { tickSchedules } from "../schedule/runner.js";
7
7
  import { handleListDirectory } from "../session/directory.js";
8
8
  import { handlePreviewFile } from "../session/file-preview.js";
9
9
  import { handleClose, handleCreateSession, handleImportHistoricalSession, handleListHistoricalSessions, handleResume } from "../session/lifecycle.js";
10
- import { handleCancel, handlePrompt } from "../session/prompt.js";
10
+ import { handleCancel, handleOptionResponse, handlePrompt } from "../session/prompt.js";
11
11
  import { handleFork, handleRename, handleRewind } from "../session/primitives.js";
12
12
  import { handleReconcileTurn } from "../session/reconcile.js";
13
13
  import { parseRelayMessage, relayWsUrl } from "../relay-http.js";
@@ -87,6 +87,10 @@ async function handleServerMessage(ws, config, message) {
87
87
  await handleCancel(ws, config, message);
88
88
  return;
89
89
  }
90
+ if (message.type === "machine:optionResponse") {
91
+ await handleOptionResponse(ws, config, message);
92
+ return;
93
+ }
90
94
  if (message.type === "machine:reconcileTurn") {
91
95
  await handleReconcileTurn(ws, config, message);
92
96
  return;
@@ -22,6 +22,8 @@ function agentProfileForRegister(agent) {
22
22
  id: agent.id,
23
23
  label: agent.label,
24
24
  ...(agent.command ? { command: agent.command } : {}),
25
+ ...(agent.defaultModel ? { defaultModel: agent.defaultModel } : {}),
26
+ ...(agent.models && agent.models.length > 0 ? { models: agent.models } : {}),
25
27
  ...(agent.historyModel ? { historyModel: agent.historyModel } : {}),
26
28
  ...(agent.sessionPrimitives ? { sessionPrimitives: runtimeAgentPrimitives(agent.sessionPrimitives) } : {}),
27
29
  };
@@ -226,6 +226,7 @@ export function createExternalAgentRuntimeProvider(options) {
226
226
  agentSessionId: handle.sessionId,
227
227
  externalProviderId: handle.provider,
228
228
  externalHandle: handle,
229
+ ...(handle.model ? { model: handle.model } : {}),
229
230
  };
230
231
  }
231
232
  function sdkHandle(handle) {
@@ -282,20 +283,26 @@ export function createExternalAgentRuntimeProvider(options) {
282
283
  return options.fallback.getCapabilities();
283
284
  },
284
285
  listAgents() {
285
- const externalAgents = clients.flatMap((client) => client.listAgents()).map((agent) => ({
286
- id: agent.id,
287
- label: agent.label,
288
- command: agent.command,
289
- historyModel: normalizeHistoryModel(agent.historyModel),
290
- sessionPrimitives: agent.capabilities?.sessionPrimitives
291
- ? {
292
- resume: fromSdkPrimitive(agent.capabilities.sessionPrimitives.resume),
293
- rewind: fromSdkPrimitive(agent.capabilities.sessionPrimitives.rewind),
294
- fork: fromSdkPrimitive(agent.capabilities.sessionPrimitives.fork),
295
- rename: agent.capabilities.sessionPrimitives.rename ? fromSdkPrimitive(agent.capabilities.sessionPrimitives.rename) : "unsupported",
296
- }
297
- : undefined,
298
- }));
286
+ const externalAgents = clients.flatMap((client) => client.listAgents()).map((agent) => {
287
+ const defaultModel = normalizedModelId(agent.defaultModel);
288
+ const models = normalizeModelOptions(agent.models);
289
+ return {
290
+ id: agent.id,
291
+ label: agent.label,
292
+ command: agent.command,
293
+ ...(defaultModel ? { defaultModel } : {}),
294
+ ...(models ? { models } : {}),
295
+ historyModel: normalizeHistoryModel(agent.historyModel),
296
+ sessionPrimitives: agent.capabilities?.sessionPrimitives
297
+ ? {
298
+ resume: fromSdkPrimitive(agent.capabilities.sessionPrimitives.resume),
299
+ rewind: fromSdkPrimitive(agent.capabilities.sessionPrimitives.rewind),
300
+ fork: fromSdkPrimitive(agent.capabilities.sessionPrimitives.fork),
301
+ rename: agent.capabilities.sessionPrimitives.rename ? fromSdkPrimitive(agent.capabilities.sessionPrimitives.rename) : "unsupported",
302
+ }
303
+ : undefined,
304
+ };
305
+ });
299
306
  const agentsById = new Map();
300
307
  for (const agent of externalAgents)
301
308
  agentsById.set(agent.id, agent);
@@ -318,6 +325,7 @@ export function createExternalAgentRuntimeProvider(options) {
318
325
  ...(input.resumeHandle ? { resumeHandle: sdkHandle(input.resumeHandle) } : {}),
319
326
  cwd: input.cwd,
320
327
  permissionMode: input.permissionMode,
328
+ model: input.model,
321
329
  restartableEmptySession: input.restartableEmptySession,
322
330
  });
323
331
  return toRuntimeHandle(input.sessionKey, handle, input.cwd);
@@ -466,6 +474,7 @@ export function createExternalAgentRuntimeProvider(options) {
466
474
  mode: input.mode,
467
475
  permissionMode: input.permissionMode,
468
476
  requestId: input.requestId,
477
+ model: input.model ?? input.handle.model,
469
478
  });
470
479
  },
471
480
  cancel(input) {
@@ -523,6 +532,31 @@ function emptyPageUnlessCursorStale(error) {
523
532
  throw error;
524
533
  return { sessions: [] };
525
534
  }
535
+ function normalizedModelId(value) {
536
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
537
+ }
538
+ function normalizeModelOptions(value) {
539
+ if (!Array.isArray(value))
540
+ return undefined;
541
+ const seen = new Set();
542
+ const models = value.flatMap((item) => {
543
+ if (!isRecord(item))
544
+ return [];
545
+ const id = normalizedModelId(item.id);
546
+ if (!id || seen.has(id))
547
+ return [];
548
+ seen.add(id);
549
+ const label = normalizedModelId(item.label);
550
+ const reason = normalizedModelId(item.reason);
551
+ return [{
552
+ id,
553
+ ...(label ? { label } : {}),
554
+ ...(item.unavailable === true ? { unavailable: true } : {}),
555
+ ...(reason ? { reason } : {}),
556
+ }];
557
+ });
558
+ return models.length > 0 ? models : undefined;
559
+ }
526
560
  class ExternalProviderClient {
527
561
  config;
528
562
  id;
@@ -653,6 +687,14 @@ class ExternalProviderClient {
653
687
  cancel: async (input) => {
654
688
  await this.request("cancel", { handle: params.handle, reason: input?.reason });
655
689
  },
690
+ respondOption: async (input) => {
691
+ await this.request("respondOption", {
692
+ handle: params.handle,
693
+ optionRequestId: input.optionRequestId,
694
+ ...(input.selectedOptionId ? { selectedOptionId: input.selectedOptionId } : {}),
695
+ ...(input.text ? { text: input.text } : {}),
696
+ });
697
+ },
656
698
  closeStream: async (input) => {
657
699
  await this.request("cancel", { handle: params.handle, reason: input?.reason ?? "stream closed" });
658
700
  },
@@ -147,6 +147,16 @@ export function runtimeEventToPayload(event) {
147
147
  title: event.title,
148
148
  };
149
149
  }
150
+ if (event.type === "option_request") {
151
+ return {
152
+ type: "option_request",
153
+ optionRequestId: event.optionRequestId,
154
+ ...(event.title ? { title: event.title } : {}),
155
+ message: event.message,
156
+ options: event.options,
157
+ ...(event.allowFreeText ? { allowFreeText: true } : {}),
158
+ };
159
+ }
150
160
  if (event.type === "status") {
151
161
  if (typeof event.used === "number" && Number.isFinite(event.used)) {
152
162
  return {
@@ -15,6 +15,7 @@ export async function handleCreateSession(ws, config, command) {
15
15
  const cwd = command.cwd || process.cwd();
16
16
  try {
17
17
  const metadata = await commandSessionMetadata(config, command.encryptedMetadata);
18
+ const model = modelFromCommand(command.model ?? metadata?.model);
18
19
  const runtime = getRuntime(cwd);
19
20
  let handle = await runtime.ensureSession({
20
21
  sessionKey: command.sessionId,
@@ -22,6 +23,7 @@ export async function handleCreateSession(ws, config, command) {
22
23
  mode: "persistent",
23
24
  cwd,
24
25
  permissionMode: metadata?.permissionMode,
26
+ model,
25
27
  });
26
28
  let providerName;
27
29
  const requestedRuntimeName = metadata?.requestedRuntimeName;
@@ -39,6 +41,7 @@ export async function handleCreateSession(ws, config, command) {
39
41
  agent: command.agent,
40
42
  cwd,
41
43
  name: providerName ?? command.name,
44
+ ...(model ? { model } : {}),
42
45
  handle,
43
46
  };
44
47
  sessions.set(command.sessionId, state);
@@ -53,6 +56,7 @@ export async function handleCreateSession(ws, config, command) {
53
56
  agent: command.agent,
54
57
  cwd,
55
58
  name: state.name,
59
+ model,
56
60
  requestedRuntimeName: requestedRuntimeName ? true : undefined,
57
61
  runtime: {
58
62
  acpxRecordId: handle.acpxRecordId,
@@ -78,6 +82,9 @@ export async function handleCreateSession(ws, config, command) {
78
82
  await sendCommandResponse(ws, createSessionFailureResponse(command, error));
79
83
  }
80
84
  }
85
+ function modelFromCommand(value) {
86
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
87
+ }
81
88
  export async function handleImportHistoricalSession(ws, config, command) {
82
89
  if (command.type !== "machine:importHistoricalSession")
83
90
  return;
@@ -1,6 +1,6 @@
1
1
  import { type EncryptedEnvelope, type MachineCommand } from "../../../../packages/shared/dist/index.js";
2
2
  import type { DaemonConfig, SessionFallback, SessionMetadata, SessionState } from "../types.js";
3
- export declare function encryptedSessionMetadata(config: DaemonConfig, sessionId: string, state: Pick<SessionState, "agent" | "cwd" | "name" | "handle">, extra: Record<string, unknown>): Promise<EncryptedEnvelope>;
3
+ export declare function encryptedSessionMetadata(config: DaemonConfig, sessionId: string, state: Pick<SessionState, "agent" | "cwd" | "name" | "handle" | "model">, extra: Record<string, unknown>): Promise<EncryptedEnvelope>;
4
4
  export declare function commandSessionMetadata(config: DaemonConfig, encryptedMetadata?: EncryptedEnvelope): Promise<SessionMetadata | undefined>;
5
5
  export declare function sessionFallbackFromCommand(config: DaemonConfig, command: Extract<MachineCommand, {
6
6
  type: "machine:prompt" | "machine:resume" | "machine:rewind" | "machine:fork" | "machine:renameSession" | "machine:close";
@@ -4,6 +4,7 @@ export function encryptedSessionMetadata(config, sessionId, state, extra) {
4
4
  agent: state.agent,
5
5
  cwd: state.cwd,
6
6
  name: state.name,
7
+ ...(state.model ? { model: state.model } : {}),
7
8
  runtime: {
8
9
  backend: state.handle.backend,
9
10
  runtimeSessionName: state.handle.runtimeSessionName,
@@ -36,6 +37,7 @@ export async function sessionFallbackFromCommand(config, command) {
36
37
  agent: metadata.agent ?? "codex",
37
38
  cwd: metadata.cwd || process.cwd(),
38
39
  name: metadata.name ?? command.sessionId,
40
+ ...(typeof metadata.model === "string" && metadata.model.trim() ? { model: metadata.model.trim() } : {}),
39
41
  emptyRuntimeSession: metadata.emptyRuntimeSession === true,
40
42
  historicalBackfill: historicalBackfillStateFromMetadata(metadata),
41
43
  runtime: metadata.runtime,
@@ -3,8 +3,9 @@ import type { DaemonConfig } from "../types.js";
3
3
  type PromptCommand = Extract<MachineCommand, {
4
4
  type: "machine:prompt";
5
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";
6
+ type PromptExecutionPhase = "decrypt-prompt" | "prepare-attachments" | "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";
7
7
  export declare function handlePrompt(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
8
+ export declare function handleOptionResponse(ws: WebSocket, config: DaemonConfig, command: MachineCommand): Promise<void>;
8
9
  export declare function promptFailureResponse(command: PromptCommand, phase: PromptExecutionPhase, error: unknown): {
9
10
  type: "machine:error";
10
11
  requestId: string;
@@ -56,12 +56,15 @@ export async function handlePrompt(ws, config, command) {
56
56
  try {
57
57
  const prompt = await decryptJson(config.accountSecret, command.encryptedPrompt);
58
58
  const rawText = prompt.type === "user_prompt" ? prompt.text : JSON.stringify(prompt);
59
+ const promptAttachments = prompt.type === "user_prompt" ? normalizePromptAttachments(prompt.attachments) : [];
60
+ phase = "prepare-attachments";
61
+ const preparedPrompt = preparePromptAttachments(rawText, promptAttachments);
59
62
  phase = "decrypt-memory-options";
60
63
  const memoryOptions = await decryptMemoryOptions(config, command);
61
64
  phase = "prepare-memory-context";
62
65
  const memoryContext = memoryOptions
63
- ? await prepareMemoryContext(memoryOptions, rawText)
64
- : { text: rawText, used: [], options: undefined };
66
+ ? await prepareMemoryContext(memoryOptions, preparedPrompt.text)
67
+ : { text: preparedPrompt.text, used: [], options: undefined };
65
68
  const text = memoryContext.text;
66
69
  phase = "emit-submitted";
67
70
  await emitEvent(ws, config, command.sessionId, command.turnId, {
@@ -95,12 +98,15 @@ export async function handlePrompt(ws, config, command) {
95
98
  }
96
99
  }
97
100
  const previousBackendSessionId = session.handle.backendSessionId;
101
+ const model = modelFromCommand(command.model) ?? session.model ?? session.handle.model;
98
102
  phase = "start-turn";
99
103
  const turn = getRuntime(session.cwd).startTurn({
100
104
  handle: session.handle,
101
105
  text,
102
106
  mode: "prompt",
103
107
  permissionMode: command.permissionMode,
108
+ model,
109
+ attachments: preparedPrompt.runtimeAttachments,
104
110
  requestId: command.requestId,
105
111
  timeoutMs: command.timeoutMs,
106
112
  });
@@ -120,7 +126,10 @@ export async function handlePrompt(ws, config, command) {
120
126
  turnId: command.turnId,
121
127
  promptChars: text.length,
122
128
  rawPromptChars: rawText.length,
129
+ attachmentCount: promptAttachments.length,
130
+ runtimeAttachmentCount: preparedPrompt.runtimeAttachments.length,
123
131
  permissionMode: command.permissionMode ?? "approve-reads",
132
+ model,
124
133
  memoryMode: memoryOptions?.mode,
125
134
  usedMemories: memoryContext.used.map((item) => ({ path: item.path, citation: item.citation })),
126
135
  },
@@ -258,6 +267,53 @@ export async function handlePrompt(ws, config, command) {
258
267
  releasePromptTurn();
259
268
  }
260
269
  }
270
+ export async function handleOptionResponse(ws, config, command) {
271
+ if (command.type !== "machine:optionResponse")
272
+ return;
273
+ const activeTurnId = sessionTurns.activeTurnId(command.sessionId);
274
+ const activeTurn = sessionTurns.activeTurn(command.sessionId);
275
+ if (!activeTurn || activeTurnId !== command.turnId) {
276
+ await sendCommandResponse(ws, {
277
+ type: "machine:error",
278
+ requestId: command.requestId,
279
+ sessionId: command.sessionId,
280
+ code: "OPTION_REQUEST_NOT_ACTIVE",
281
+ message: "No matching active turn is waiting for this option response",
282
+ });
283
+ return;
284
+ }
285
+ if (!activeTurn.respondOption) {
286
+ await sendCommandResponse(ws, {
287
+ type: "machine:error",
288
+ requestId: command.requestId,
289
+ sessionId: command.sessionId,
290
+ code: "OPTION_RESPONSE_UNSUPPORTED",
291
+ message: "This runtime does not support Happy Elves option responses",
292
+ });
293
+ return;
294
+ }
295
+ await activeTurn.respondOption({
296
+ optionRequestId: command.optionRequestId,
297
+ ...(command.selectedOptionId ? { selectedOptionId: command.selectedOptionId } : {}),
298
+ ...(command.text ? { text: command.text } : {}),
299
+ });
300
+ await emitEvent(ws, config, command.sessionId, command.turnId, {
301
+ type: "option_response",
302
+ optionRequestId: command.optionRequestId,
303
+ ...(command.selectedOptionId ? { selectedOptionId: command.selectedOptionId } : {}),
304
+ ...(command.text ? { text: command.text } : {}),
305
+ });
306
+ await sendCommandResponse(ws, {
307
+ type: "machine:optionResponded",
308
+ requestId: command.requestId,
309
+ sessionId: command.sessionId,
310
+ turnId: command.turnId,
311
+ optionRequestId: command.optionRequestId,
312
+ });
313
+ }
314
+ function modelFromCommand(value) {
315
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
316
+ }
261
317
  export function promptFailureResponse(command, phase, error) {
262
318
  const code = promptFailureCode(phase, error);
263
319
  return {
@@ -433,4 +489,68 @@ function truncateOutputPreview(value, maxChars) {
433
489
  return value;
434
490
  return value.slice(value.length - maxChars);
435
491
  }
492
+ function normalizePromptAttachments(value) {
493
+ if (!Array.isArray(value))
494
+ return [];
495
+ return value.flatMap((attachment) => {
496
+ if (!attachment ||
497
+ typeof attachment.id !== "string" ||
498
+ typeof attachment.name !== "string" ||
499
+ typeof attachment.mediaType !== "string" ||
500
+ typeof attachment.data !== "string" ||
501
+ typeof attachment.sizeBytes !== "number")
502
+ return [];
503
+ return [{
504
+ id: attachment.id,
505
+ name: attachment.name,
506
+ mediaType: attachment.mediaType,
507
+ sizeBytes: attachment.sizeBytes,
508
+ data: attachment.data,
509
+ }];
510
+ });
511
+ }
512
+ function preparePromptAttachments(text, attachments) {
513
+ const textBlocks = [];
514
+ const runtimeAttachments = [];
515
+ for (const attachment of attachments) {
516
+ const decodedText = decodeTextAttachment(attachment);
517
+ if (decodedText !== undefined) {
518
+ textBlocks.push([
519
+ `--- Attachment: ${attachment.name} (${attachment.mediaType}, ${attachment.sizeBytes} bytes) ---`,
520
+ decodedText,
521
+ "--- End attachment ---",
522
+ ].join("\n"));
523
+ continue;
524
+ }
525
+ runtimeAttachments.push({
526
+ name: attachment.name,
527
+ mediaType: attachment.mediaType,
528
+ sizeBytes: attachment.sizeBytes,
529
+ data: attachment.data,
530
+ });
531
+ }
532
+ return {
533
+ text: textBlocks.length > 0
534
+ ? `${text}\n\nAttached text files:\n\n${textBlocks.join("\n\n")}`
535
+ : text,
536
+ runtimeAttachments,
537
+ };
538
+ }
539
+ function decodeTextAttachment(attachment) {
540
+ const mediaType = attachment.mediaType.toLowerCase();
541
+ const textLike = mediaType.startsWith("text/") ||
542
+ mediaType.includes("json") ||
543
+ mediaType.includes("xml") ||
544
+ mediaType.includes("yaml") ||
545
+ mediaType.includes("csv") ||
546
+ /\.(txt|md|markdown|json|jsonl|yaml|yml|csv|tsv|log|xml|html|css|js|ts|tsx|jsx|vue|py|rb|go|rs|java|c|cc|cpp|h|hpp|sh|zsh|bash|ps1)$/iu.test(attachment.name);
547
+ if (!textLike)
548
+ return undefined;
549
+ try {
550
+ return Buffer.from(attachment.data, "base64").toString("utf8").slice(0, 64 * 1024);
551
+ }
552
+ catch {
553
+ return undefined;
554
+ }
555
+ }
436
556
  //# sourceMappingURL=prompt.js.map
@@ -31,6 +31,7 @@ export type SessionState = {
31
31
  agent: string;
32
32
  cwd: string;
33
33
  name: string;
34
+ model?: string;
34
35
  handle: RuntimeHandle;
35
36
  historicalBackfill?: HistoricalBackfillState;
36
37
  };
@@ -53,6 +54,7 @@ export type SessionMetadata = {
53
54
  cwd?: string;
54
55
  name?: string;
55
56
  providerName?: string;
57
+ model?: string;
56
58
  requestedRuntimeName?: string;
57
59
  permissionMode?: PermissionMode;
58
60
  importedFromRuntimeSessionId?: string;
@@ -83,6 +85,7 @@ export type SessionMetadata = {
83
85
  };
84
86
  };
85
87
  export type SessionFallback = Pick<SessionState, "agent" | "cwd" | "name"> & {
88
+ model?: string;
86
89
  emptyRuntimeSession?: boolean;
87
90
  historicalBackfill?: HistoricalBackfillState;
88
91
  runtime?: SessionMetadata["runtime"];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.248",
3
+ "version": "0.1.0-beta.254",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -106,6 +106,7 @@ export function handleControllerMessage(context, connection, message) {
106
106
  agent: message.agent,
107
107
  cwd: message.cwd,
108
108
  name: message.name,
109
+ model: message.model,
109
110
  encryptedMetadata: message.encryptedMetadata,
110
111
  };
111
112
  db.exec("BEGIN");
@@ -158,6 +159,7 @@ export function handleControllerMessage(context, connection, message) {
158
159
  encryptedPrompt: message.encryptedPrompt,
159
160
  encryptedMetadata: sessionRow.encrypted_metadata ? parseJson(sessionRow.encrypted_metadata, undefined) : undefined,
160
161
  permissionMode: message.permissionMode,
162
+ model: message.model,
161
163
  encryptedMemoryOptions: message.encryptedMemoryOptions,
162
164
  timeoutMs: message.timeoutMs,
163
165
  };
@@ -241,6 +243,31 @@ export function handleControllerMessage(context, connection, message) {
241
243
  sendAck(connection, { type: "server:ack", requestId: message.requestId, ok: true });
242
244
  return;
243
245
  }
246
+ if (message.type === "controller:optionResponse") {
247
+ if (!allowControllerAction(connection, "session.run", message.requestId))
248
+ return;
249
+ const sessionRow = allowControllerSession(connection, message.sessionId, message.requestId);
250
+ if (!sessionRow)
251
+ return;
252
+ if (rejectDisallowedSessionCommand(sessionRow, message.requestId, "prompt"))
253
+ return;
254
+ const machineConnection = connectedMachine(connection.accountId, sessionRow.machine_id);
255
+ if (!machineConnection) {
256
+ sendMachineOffline(connection, message.requestId);
257
+ return;
258
+ }
259
+ send(machineConnection.socket, {
260
+ type: "machine:optionResponse",
261
+ requestId: message.requestId,
262
+ sessionId: message.sessionId,
263
+ turnId: message.turnId,
264
+ optionRequestId: message.optionRequestId,
265
+ selectedOptionId: message.selectedOptionId,
266
+ text: message.text,
267
+ });
268
+ sendAck(connection, { type: "server:ack", requestId: message.requestId, ok: true });
269
+ return;
270
+ }
244
271
  if (message.type === "controller:reconcileTurn") {
245
272
  if (!allowControllerAction(connection, "session.run", message.requestId))
246
273
  return;
@@ -273,6 +273,7 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
273
273
  agent: body.agent,
274
274
  cwd: body.cwd,
275
275
  name: body.name,
276
+ model: body.model,
276
277
  encryptedMetadata: body.encryptedMetadata,
277
278
  };
278
279
  context.db.exec("BEGIN");
@@ -330,6 +331,7 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
330
331
  encryptedPrompt: body.encryptedPrompt,
331
332
  encryptedMetadata: sessionRow.encrypted_metadata ? context.parseJson(sessionRow.encrypted_metadata, undefined) : undefined,
332
333
  permissionMode: body.permissionMode,
334
+ model: body.model,
333
335
  encryptedMemoryOptions: body.encryptedMemoryOptions,
334
336
  timeoutMs: body.timeoutMs,
335
337
  };
@@ -134,6 +134,7 @@ export declare const machineCreateSessionAdmissionSchema: z.ZodObject<{
134
134
  agent: z.ZodString;
135
135
  cwd: z.ZodString;
136
136
  name: z.ZodString;
137
+ model: z.ZodOptional<z.ZodString>;
137
138
  encryptedMetadata: z.ZodOptional<z.ZodObject<{
138
139
  v: z.ZodLiteral<1>;
139
140
  alg: z.ZodLiteral<"A256GCM">;
@@ -170,6 +171,7 @@ export declare const machinePromptAdmissionSchema: z.ZodObject<{
170
171
  "approve-reads": "approve-reads";
171
172
  "deny-all": "deny-all";
172
173
  }>>;
174
+ model: z.ZodOptional<z.ZodString>;
173
175
  encryptedMemoryOptions: z.ZodOptional<z.ZodObject<{
174
176
  v: z.ZodLiteral<1>;
175
177
  alg: z.ZodLiteral<"A256GCM">;
@@ -122,6 +122,7 @@ export const machineCreateSessionAdmissionSchema = z.object({
122
122
  agent: z.string().min(1),
123
123
  cwd: z.string(),
124
124
  name: z.string().min(1),
125
+ model: z.string().min(1).optional(),
125
126
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
126
127
  });
127
128
  export const machinePromptAdmissionSchema = z.object({
@@ -130,6 +131,7 @@ export const machinePromptAdmissionSchema = z.object({
130
131
  turnId: z.string().min(1),
131
132
  encryptedPrompt: encryptedEnvelopeSchema,
132
133
  permissionMode: z.enum(["approve-all", "approve-reads", "deny-all"]).optional(),
134
+ model: z.string().min(1).optional(),
133
135
  encryptedMemoryOptions: encryptedEnvelopeSchema.optional(),
134
136
  timeoutMs: z.number().positive().optional(),
135
137
  });