@cydm/happy-elves 0.1.0-beta.251 → 0.1.0-beta.258

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.251",
3
+ "version": "0.1.0-beta.258",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.251",
3
+ "version": "0.1.0-beta.258",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -21,17 +21,47 @@ function primitiveSupport(value) {
21
21
  function historyModel(value) {
22
22
  return value === "append-only" || value === "authoritative-linear" ? value : undefined;
23
23
  }
24
+ function stringField(value) {
25
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
26
+ }
27
+ function agentModelOptions(value) {
28
+ if (!Array.isArray(value))
29
+ return undefined;
30
+ const seen = new Set();
31
+ const models = value.flatMap((item) => {
32
+ if (!isRecord(item))
33
+ return [];
34
+ const id = stringField(item.id);
35
+ if (!id || seen.has(id))
36
+ return [];
37
+ seen.add(id);
38
+ const label = stringField(item.label);
39
+ const reason = stringField(item.reason);
40
+ return [{
41
+ id,
42
+ ...(label ? { label } : {}),
43
+ ...(item.unavailable === true ? { unavailable: true } : {}),
44
+ ...(reason ? { reason } : {}),
45
+ }];
46
+ });
47
+ return models.length > 0 ? models : undefined;
48
+ }
24
49
  export function normalizeMachineCapabilities(value) {
25
50
  const record = isRecord(value) ? value : {};
26
51
  const agents = Array.isArray(record.agents)
27
52
  ? record.agents.flatMap((agent) => {
28
53
  if (!isRecord(agent) || typeof agent.id !== "string" || typeof agent.label !== "string")
29
54
  return [];
55
+ const agentHistoryModel = historyModel(agent.historyModel);
56
+ const defaultModel = stringField(agent.defaultModel);
57
+ const models = agentModelOptions(agent.models);
30
58
  return [{
31
59
  id: agent.id,
32
60
  label: agent.label,
33
61
  ...(typeof agent.command === "string" ? { command: agent.command } : {}),
34
- ...(historyModel(agent.historyModel) ? { historyModel: historyModel(agent.historyModel) } : {}),
62
+ ...(agentHistoryModel ? { historyModel: agentHistoryModel } : {}),
63
+ ...(defaultModel ? { defaultModel } : {}),
64
+ ...(models ? { models } : {}),
35
65
  ...(isRecord(agent.sessionPrimitives)
36
66
  ? {
37
67
  sessionPrimitives: {
@@ -195,10 +195,54 @@ export function createRelayContext(options) {
195
195
  };
196
196
  }
197
197
  function activeSessionClaim(accountId, sessionId) {
198
- return options.db
198
+ const claim = options.db
199
199
  .prepare(`SELECT * FROM relay_session_claims
200
200
  WHERE account_id = ? AND session_id = ? AND state = 'active'`)
201
201
  .get(accountId, sessionId);
202
+ if (!claim)
203
+ return undefined;
204
+ if (isStaleSessionClaim(claim)) {
205
+ options.db
206
+ .prepare("DELETE FROM relay_session_claims WHERE account_id = ? AND session_id = ? AND request_id = ?")
207
+ .run(claim.account_id, claim.session_id, claim.request_id);
208
+ return undefined;
209
+ }
210
+ return claim;
211
+ }
212
+ function isStaleSessionClaim(claim) {
213
+ const admission = options.db
214
+ .prepare(`SELECT dispatch, finished_at, terminal_status
215
+ FROM relay_admissions
216
+ WHERE account_id = ? AND request_id = ?`)
217
+ .get(claim.account_id, claim.request_id);
218
+ if (admission && (admission.finished_at !== null || admission.terminal_status !== null))
219
+ return true;
220
+ const session = options.db
221
+ .prepare(`SELECT status, current_head, last_turn_id, updated_at
222
+ FROM sessions
223
+ WHERE account_id = ? AND id = ?`)
224
+ .get(claim.account_id, claim.session_id);
225
+ if (admission?.dispatch === "delivered" &&
226
+ session &&
227
+ !["new", "running", "offline"].includes(session.status) &&
228
+ session.updated_at > claim.created_at &&
229
+ session.current_head !== claim.turn_id &&
230
+ session.last_turn_id !== claim.turn_id) {
231
+ return true;
232
+ }
233
+ const newerTerminal = options.db
234
+ .prepare(`SELECT 1
235
+ FROM relay_admissions
236
+ WHERE account_id = ?
237
+ AND session_id = ?
238
+ AND kind = 'prompt'
239
+ AND request_id <> ?
240
+ AND finished_at IS NOT NULL
241
+ AND terminal_status IS NOT NULL
242
+ AND created_at > ?
243
+ LIMIT 1`)
244
+ .get(claim.account_id, claim.session_id, claim.request_id, claim.created_at);
245
+ return Boolean(newerTerminal);
202
246
  }
203
247
  function claimSessionTurn(params) {
204
248
  const existing = activeSessionClaim(params.accountId, params.sessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.251",
3
+ "version": "0.1.0-beta.258",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,6 @@
1
1
  import path from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import os from "node:os";
2
4
  import { readClaudeHistoricalEvents, readClaudeHistoricalSessionPage } from "./claude-history.js";
3
5
  import { renameClaudeSession } from "./claude-rename.js";
4
6
  import { forkAndTruncateClaudeSession, forkClaudeSession } from "./claude-session-store.js";
@@ -73,7 +75,7 @@ export function createCliRuntimeProvider(options) {
73
75
  },
74
76
  listAgents() {
75
77
  return [
76
- { id: "codex", label: "Codex", ...agentModelsFromEnv("HAPPY_ELVES_CODEX") },
78
+ { id: "codex", label: "Codex", ...codexAgentModels() },
77
79
  { id: "claude", label: "Claude Code", ...agentModelsFromEnv("HAPPY_ELVES_CLAUDE") },
78
80
  ];
79
81
  },
@@ -331,6 +333,16 @@ function agentModelsFromEnv(prefix) {
331
333
  ...(models.length > 0 ? { models } : {}),
332
334
  };
333
335
  }
336
+ function codexAgentModels() {
337
+ const configured = agentModelsFromEnv("HAPPY_ELVES_CODEX");
338
+ if (configured.models && configured.models.length > 0)
339
+ return configured;
340
+ const cachedModels = modelOptionsFromCodexCache(process.env.CODEX_HOME);
341
+ return {
342
+ ...(configured.defaultModel ? { defaultModel: configured.defaultModel } : {}),
343
+ ...(cachedModels.length > 0 ? { models: cachedModels } : {}),
344
+ };
345
+ }
334
346
  function modelOptionsFromEnv(value) {
335
347
  const seen = new Set();
336
348
  return (value ?? "")
@@ -347,4 +359,60 @@ function modelOptionsFromEnv(value) {
347
359
  return [{ id, ...(label ? { label } : {}) }];
348
360
  });
349
361
  }
362
+ function modelOptionsFromCodexCache(codexHome) {
363
+ const cachePath = codexModelCachePaths(codexHome).find((candidate) => existsSync(candidate));
364
+ if (!cachePath)
365
+ return [];
366
+ try {
367
+ const parsed = JSON.parse(readFileSync(cachePath, "utf8"));
368
+ const models = Array.isArray(parsed)
369
+ ? parsed
370
+ : isRecord(parsed) && Array.isArray(parsed.models)
371
+ ? parsed.models
372
+ : [];
373
+ const seen = new Set();
374
+ return models.flatMap((item) => {
375
+ if (!isRecord(item))
376
+ return [];
377
+ if (typeof item.visibility === "string" && item.visibility !== "list")
378
+ return [];
379
+ const id = stringField(item, "slug") || stringField(item, "id") || stringField(item, "model") || stringField(item, "name");
380
+ if (!id || seen.has(id))
381
+ return [];
382
+ seen.add(id);
383
+ const label = stringField(item, "display_name") || stringField(item, "displayName") || stringField(item, "label");
384
+ return [{ id, ...(label ? { label } : {}) }];
385
+ });
386
+ }
387
+ catch {
388
+ return [];
389
+ }
390
+ }
391
+ function codexModelCachePaths(codexHome) {
392
+ const candidates = [
393
+ process.env.HAPPY_ELVES_CODEX_MODELS_CACHE,
394
+ codexHome?.trim() ? path.join(codexHome.trim(), "models_cache.json") : undefined,
395
+ process.env.APPDATA ? path.join(process.env.APPDATA, "Codex", "models_cache.json") : undefined,
396
+ process.env.APPDATA ? path.join(process.env.APPDATA, "codex", "models_cache.json") : undefined,
397
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, "Codex", "models_cache.json") : undefined,
398
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, "codex", "models_cache.json") : undefined,
399
+ path.join(os.homedir(), ".codex", "models_cache.json"),
400
+ process.env.USERPROFILE ? path.join(process.env.USERPROFILE, ".codex", "models_cache.json") : undefined,
401
+ ];
402
+ const seen = new Set();
403
+ return candidates.flatMap((candidate) => {
404
+ const normalized = candidate?.trim();
405
+ if (!normalized || seen.has(normalized))
406
+ return [];
407
+ seen.add(normalized);
408
+ return [normalized];
409
+ });
410
+ }
411
+ function isRecord(value) {
412
+ return typeof value === "object" && value !== null && !Array.isArray(value);
413
+ }
414
+ function stringField(record, key) {
415
+ const value = record[key];
416
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
417
+ }
350
418
  //# sourceMappingURL=index.js.map