@gtc6244/immediacy-daemon 0.1.1 → 0.1.3

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 (77) hide show
  1. package/README.md +58 -25
  2. package/dist/src/agent/agent.d.ts +19 -6
  3. package/dist/src/agent/agent.js +3 -1
  4. package/dist/src/agent/agent.js.map +1 -1
  5. package/dist/src/agent/anthropic-probe.d.ts +24 -0
  6. package/dist/src/agent/anthropic-probe.js +81 -0
  7. package/dist/src/agent/anthropic-probe.js.map +1 -0
  8. package/dist/src/agent/claude.d.ts +4 -2
  9. package/dist/src/agent/claude.js +32 -4
  10. package/dist/src/agent/claude.js.map +1 -1
  11. package/dist/src/agent/codex.d.ts +1 -1
  12. package/dist/src/agent/codex.js +26 -3
  13. package/dist/src/agent/codex.js.map +1 -1
  14. package/dist/src/agent/images.d.ts +21 -0
  15. package/dist/src/agent/images.js +54 -0
  16. package/dist/src/agent/images.js.map +1 -0
  17. package/dist/src/agent/mock.d.ts +1 -1
  18. package/dist/src/agent/mock.js +28 -3
  19. package/dist/src/agent/mock.js.map +1 -1
  20. package/dist/src/agent/ollama-probe.d.ts +28 -0
  21. package/dist/src/agent/ollama-probe.js +76 -0
  22. package/dist/src/agent/ollama-probe.js.map +1 -0
  23. package/dist/src/agent/ollama.d.ts +42 -0
  24. package/dist/src/agent/ollama.js +64 -0
  25. package/dist/src/agent/ollama.js.map +1 -0
  26. package/dist/src/agent/openai-probe.d.ts +18 -0
  27. package/dist/src/agent/openai-probe.js +83 -0
  28. package/dist/src/agent/openai-probe.js.map +1 -0
  29. package/dist/src/agent/openrouter-probe.d.ts +21 -0
  30. package/dist/src/agent/openrouter-probe.js +77 -0
  31. package/dist/src/agent/openrouter-probe.js.map +1 -0
  32. package/dist/src/cli/paths.d.ts +2 -0
  33. package/dist/src/cli/paths.js +14 -7
  34. package/dist/src/cli/paths.js.map +1 -1
  35. package/dist/src/cli.js +40 -0
  36. package/dist/src/cli.js.map +1 -1
  37. package/dist/src/crypto.js +7 -2
  38. package/dist/src/crypto.js.map +1 -1
  39. package/dist/src/hub.d.ts +7 -0
  40. package/dist/src/hub.js +14 -0
  41. package/dist/src/hub.js.map +1 -1
  42. package/dist/src/index.d.ts +3 -0
  43. package/dist/src/index.js +35 -3
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/jobs/scheduler.d.ts +1 -0
  46. package/dist/src/jobs/scheduler.js +1 -0
  47. package/dist/src/jobs/scheduler.js.map +1 -1
  48. package/dist/src/media-watch.d.ts +29 -0
  49. package/dist/src/media-watch.js +108 -0
  50. package/dist/src/media-watch.js.map +1 -0
  51. package/dist/src/media.d.ts +54 -0
  52. package/dist/src/media.js +156 -0
  53. package/dist/src/media.js.map +1 -0
  54. package/dist/src/model-efforts.d.ts +49 -0
  55. package/dist/src/model-efforts.js +107 -0
  56. package/dist/src/model-efforts.js.map +1 -0
  57. package/dist/src/models.d.ts +50 -17
  58. package/dist/src/models.js +119 -21
  59. package/dist/src/models.js.map +1 -1
  60. package/dist/src/projects.d.ts +7 -0
  61. package/dist/src/projects.js +14 -0
  62. package/dist/src/projects.js.map +1 -1
  63. package/dist/src/protocol.d.ts +163 -9
  64. package/dist/src/protocol.js +1 -1
  65. package/dist/src/server.js +141 -5
  66. package/dist/src/server.js.map +1 -1
  67. package/dist/src/types.d.ts +9 -0
  68. package/dist/src/updater.d.ts +78 -0
  69. package/dist/src/updater.js +240 -0
  70. package/dist/src/updater.js.map +1 -0
  71. package/dist/src/workspace.d.ts +25 -0
  72. package/dist/src/workspace.js +159 -6
  73. package/dist/src/workspace.js.map +1 -1
  74. package/package.json +4 -2
  75. package/dist/src/migrate-state-dir.d.ts +0 -1
  76. package/dist/src/migrate-state-dir.js +0 -47
  77. package/dist/src/migrate-state-dir.js.map +0 -1
@@ -0,0 +1,156 @@
1
+ /**
2
+ * MediaStore — durable, content-addressed store for agent-produced images.
3
+ *
4
+ * When an agent surfaces an image (a screenshot returned by a tool, or a PNG it
5
+ * wrote into the worktree), the daemon persists the bytes here and broadcasts a
6
+ * lightweight `event.assistant_image` carrying only metadata (id + mediaType +
7
+ * size). Clients fetch the bytes on demand (`media.get`, or the web app's
8
+ * immutable `/media/<ws>/<id>` HTTP endpoint), so multi-MB screenshots never
9
+ * bloat event frames, the chatlog, or the E2E-sealed channel.
10
+ *
11
+ * Files live under `~/.immediacy/media/<wsId>/<id>.<ext>` (override via
12
+ * MEDIA_DIR), mirroring the per-workspace partitioning of the chatlog — so
13
+ * removing a workspace's media is a single directory delete, and the orphan
14
+ * sweep can reconcile against the live workspace set.
15
+ *
16
+ * The id is the sha256 of the bytes, so identical images collapse to one file
17
+ * and the id is a stable, immutable cache key clients can persist forever.
18
+ */
19
+ import { createHash } from "node:crypto";
20
+ import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
21
+ import { homedir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { logger } from "./log.js";
24
+ const MEDIA_DIR = process.env.MEDIA_DIR ?? join(homedir(), ".immediacy", "media");
25
+ export class MediaStore {
26
+ dir;
27
+ constructor(dir = MEDIA_DIR) {
28
+ this.dir = dir;
29
+ }
30
+ wsDir(ws) {
31
+ return join(this.dir, safeId(ws));
32
+ }
33
+ /**
34
+ * Persist an image for a workspace and return its metadata. Idempotent: the
35
+ * same bytes hash to the same id, so a re-emitted screenshot is written once.
36
+ * `name` is a display hint (e.g. a relative worktree path for a file-sourced
37
+ * image); a default is synthesized from the id + type otherwise.
38
+ */
39
+ async put(ws, mediaType, base64, name) {
40
+ const bytes = Buffer.from(base64, "base64");
41
+ const id = createHash("sha256").update(bytes).digest("hex");
42
+ const ext = extFor(mediaType);
43
+ const file = join(this.wsDir(ws), `${id}.${ext}`);
44
+ await mkdir(this.wsDir(ws), { recursive: true });
45
+ // Content-addressed → if it already exists, the bytes are identical.
46
+ if (!(await exists(file)))
47
+ await writeFile(file, bytes);
48
+ return {
49
+ id,
50
+ name: name && name.trim() ? name : `image-${id.slice(0, 8)}.${ext}`,
51
+ mediaType,
52
+ size: bytes.length,
53
+ };
54
+ }
55
+ /** Fetch stored bytes by id (base64), or null if absent. */
56
+ async get(ws, id) {
57
+ if (!isHexId(id))
58
+ return null; // ids are sha256 hex; reject anything else
59
+ let names;
60
+ try {
61
+ names = await readdir(this.wsDir(ws));
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ const match = names.find((n) => n.startsWith(`${id}.`));
67
+ if (!match)
68
+ return null;
69
+ try {
70
+ const bytes = await readFile(join(this.wsDir(ws), match));
71
+ return { mediaType: mimeFor(match), data: bytes.toString("base64") };
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ /** Drop all media for a workspace (Tier-1 cleanup on workspace removal). */
78
+ async remove(ws) {
79
+ try {
80
+ await rm(this.wsDir(ws), { recursive: true, force: true });
81
+ }
82
+ catch {
83
+ /* best-effort */
84
+ }
85
+ }
86
+ /** Workspace ids that currently have a media directory (for the orphan sweep). */
87
+ async listWorkspaces() {
88
+ try {
89
+ const entries = await readdir(this.dir, { withFileTypes: true });
90
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
91
+ }
92
+ catch {
93
+ return [];
94
+ }
95
+ }
96
+ /**
97
+ * Tier-2 orphan sweep: delete media dirs whose workspace id is not in `live`.
98
+ * Returns the number of workspace dirs removed. Best-effort; never throws.
99
+ */
100
+ async sweepOrphans(live) {
101
+ let removed = 0;
102
+ for (const ws of await this.listWorkspaces()) {
103
+ if (live.has(ws))
104
+ continue;
105
+ try {
106
+ await rm(join(this.dir, ws), { recursive: true, force: true });
107
+ removed++;
108
+ }
109
+ catch {
110
+ /* best-effort */
111
+ }
112
+ }
113
+ if (removed > 0) {
114
+ logger.info("system", "media orphan sweep removed workspace media", {
115
+ data: { removed },
116
+ });
117
+ }
118
+ return removed;
119
+ }
120
+ }
121
+ const MIME_EXT = {
122
+ "image/png": "png",
123
+ "image/jpeg": "jpg",
124
+ "image/gif": "gif",
125
+ "image/webp": "webp",
126
+ "image/bmp": "bmp",
127
+ "image/svg+xml": "svg",
128
+ };
129
+ const EXT_MIME = Object.fromEntries(Object.entries(MIME_EXT).map(([m, e]) => [e, m]));
130
+ function extFor(mediaType) {
131
+ return MIME_EXT[mediaType] ?? "bin";
132
+ }
133
+ function mimeFor(fileName) {
134
+ const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
135
+ return EXT_MIME[ext] ?? "application/octet-stream";
136
+ }
137
+ async function exists(p) {
138
+ try {
139
+ await stat(p);
140
+ return true;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ function isHexId(id) {
147
+ return /^[a-f0-9]{64}$/.test(id);
148
+ }
149
+ /** Guard a workspace id used as a directory name against path traversal. */
150
+ function safeId(ws) {
151
+ if (!/^[A-Za-z0-9._-]+$/.test(ws) || ws === "." || ws === "..") {
152
+ throw new Error(`unsafe workspace id: ${JSON.stringify(ws)}`);
153
+ }
154
+ return ws;
155
+ }
156
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/media.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Reasoning-effort policy — the single source of truth for which effort levels
3
+ * each provider/family exposes, and the labels shown to clients.
4
+ *
5
+ * Effort ("how hard the model thinks") is a per-model knob:
6
+ * - Claude → low / medium / high / xhigh / max, gated by model family
7
+ * (see effortsForClaude). Handed to the Agent SDK's `effort`
8
+ * option; unsupported levels are silently downgraded by the CLI.
9
+ * - Codex → minimal / low / medium / high, passed to the Codex CLI as
10
+ * `-c model_reasoning_effort=<level>`.
11
+ * - OpenRouter → low / medium / high when the model advertises `reasoning`
12
+ * in its `supported_parameters`.
13
+ * - Ollama → none.
14
+ *
15
+ * Discovery probes (anthropic/openai/openrouter) and the static fallback
16
+ * catalog in models.ts both call these helpers, so a model carries the same
17
+ * effort set however it was surfaced.
18
+ */
19
+ import type { ModelEffort } from "./protocol.js";
20
+ /** True if `iso` is missing/unparseable or falls within the trailing 12 months. */
21
+ export declare function withinLastYear(iso?: string): boolean;
22
+ /**
23
+ * Effort levels a Claude model supports, by family (per Anthropic's effort docs):
24
+ * - Fable / Mythos, Opus 4.7+/4.8 → low, medium, high, xhigh, max
25
+ * - Opus 4.6, Sonnet 4.6 → low, medium, high, max
26
+ * - Opus 4.5 → low, medium, high
27
+ * - Sonnet 4.5 / Haiku / older → none (effort would error / no-op)
28
+ * Returns undefined when the model has no effort knob.
29
+ */
30
+ export declare function effortsForClaude(id: string): ModelEffort[] | undefined;
31
+ /**
32
+ * Effort levels a Codex/OpenAI model supports:
33
+ * - GPT-5 family → minimal, low, medium, high
34
+ * - o-series (o1/o3/o4…) → low, medium, high
35
+ * - everything else (incl. the "codex" account-default sentinel) → none
36
+ */
37
+ export declare function effortsForCodex(id: string): ModelEffort[] | undefined;
38
+ /**
39
+ * Effort levels for an OpenRouter model — low/medium/high when it advertises
40
+ * `reasoning` support, else none. OpenRouter maps these onto its
41
+ * `reasoning: { effort }` request field.
42
+ */
43
+ export declare function effortsForOpenRouter(supportedParameters?: string[]): ModelEffort[] | undefined;
44
+ /** Prefer "high" for Claude (the coding/agentic sweet spot), else the top level. */
45
+ export declare function DEFAULT_CLAUDE_EFFORT(efforts: ModelEffort[]): string;
46
+ /** Prefer "medium" for Codex, else the top level. */
47
+ export declare function DEFAULT_CODEX_EFFORT(efforts: ModelEffort[]): string;
48
+ /** Prefer "medium" for OpenRouter, else the top level. */
49
+ export declare function DEFAULT_OPENROUTER_EFFORT(efforts: ModelEffort[]): string;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Reasoning-effort policy — the single source of truth for which effort levels
3
+ * each provider/family exposes, and the labels shown to clients.
4
+ *
5
+ * Effort ("how hard the model thinks") is a per-model knob:
6
+ * - Claude → low / medium / high / xhigh / max, gated by model family
7
+ * (see effortsForClaude). Handed to the Agent SDK's `effort`
8
+ * option; unsupported levels are silently downgraded by the CLI.
9
+ * - Codex → minimal / low / medium / high, passed to the Codex CLI as
10
+ * `-c model_reasoning_effort=<level>`.
11
+ * - OpenRouter → low / medium / high when the model advertises `reasoning`
12
+ * in its `supported_parameters`.
13
+ * - Ollama → none.
14
+ *
15
+ * Discovery probes (anthropic/openai/openrouter) and the static fallback
16
+ * catalog in models.ts both call these helpers, so a model carries the same
17
+ * effort set however it was surfaced.
18
+ */
19
+ const YEAR_MS = 365 * 24 * 60 * 60 * 1000;
20
+ /** Human-readable label for each effort id. */
21
+ const EFFORT_LABELS = {
22
+ minimal: "Minimal",
23
+ low: "Low",
24
+ medium: "Medium",
25
+ high: "High",
26
+ xhigh: "Extra high",
27
+ max: "Max",
28
+ };
29
+ /** Build `ModelEffort[]` from ordered ids, labeling each from the table above. */
30
+ function mk(ids) {
31
+ return ids.map((id) => ({ id, label: EFFORT_LABELS[id] ?? id }));
32
+ }
33
+ /** True if `iso` is missing/unparseable or falls within the trailing 12 months. */
34
+ export function withinLastYear(iso) {
35
+ if (!iso)
36
+ return true;
37
+ const t = Date.parse(iso);
38
+ if (Number.isNaN(t))
39
+ return true;
40
+ return t >= Date.now() - YEAR_MS;
41
+ }
42
+ /**
43
+ * Effort levels a Claude model supports, by family (per Anthropic's effort docs):
44
+ * - Fable / Mythos, Opus 4.7+/4.8 → low, medium, high, xhigh, max
45
+ * - Opus 4.6, Sonnet 4.6 → low, medium, high, max
46
+ * - Opus 4.5 → low, medium, high
47
+ * - Sonnet 4.5 / Haiku / older → none (effort would error / no-op)
48
+ * Returns undefined when the model has no effort knob.
49
+ */
50
+ export function effortsForClaude(id) {
51
+ const s = id.toLowerCase();
52
+ if (/fable|mythos/.test(s))
53
+ return mk(["low", "medium", "high", "xhigh", "max"]);
54
+ if (/opus-4-(7|8)\b/.test(s))
55
+ return mk(["low", "medium", "high", "xhigh", "max"]);
56
+ if (/opus-4-6\b/.test(s))
57
+ return mk(["low", "medium", "high", "max"]);
58
+ if (/sonnet-4-6\b/.test(s))
59
+ return mk(["low", "medium", "high", "max"]);
60
+ if (/opus-4-5\b/.test(s))
61
+ return mk(["low", "medium", "high"]);
62
+ return undefined;
63
+ }
64
+ /**
65
+ * Effort levels a Codex/OpenAI model supports:
66
+ * - GPT-5 family → minimal, low, medium, high
67
+ * - o-series (o1/o3/o4…) → low, medium, high
68
+ * - everything else (incl. the "codex" account-default sentinel) → none
69
+ */
70
+ export function effortsForCodex(id) {
71
+ const s = id.toLowerCase();
72
+ if (s === "codex")
73
+ return undefined;
74
+ if (/^gpt-5/.test(s))
75
+ return mk(["minimal", "low", "medium", "high"]);
76
+ if (/^o[0-9]/.test(s))
77
+ return mk(["low", "medium", "high"]);
78
+ return undefined;
79
+ }
80
+ /**
81
+ * Effort levels for an OpenRouter model — low/medium/high when it advertises
82
+ * `reasoning` support, else none. OpenRouter maps these onto its
83
+ * `reasoning: { effort }` request field.
84
+ */
85
+ export function effortsForOpenRouter(supportedParameters) {
86
+ if (supportedParameters?.includes("reasoning")) {
87
+ return mk(["low", "medium", "high"]);
88
+ }
89
+ return undefined;
90
+ }
91
+ /** Prefer "high" for Claude (the coding/agentic sweet spot), else the top level. */
92
+ export function DEFAULT_CLAUDE_EFFORT(efforts) {
93
+ return pick(efforts, "high");
94
+ }
95
+ /** Prefer "medium" for Codex, else the top level. */
96
+ export function DEFAULT_CODEX_EFFORT(efforts) {
97
+ return pick(efforts, "medium");
98
+ }
99
+ /** Prefer "medium" for OpenRouter, else the top level. */
100
+ export function DEFAULT_OPENROUTER_EFFORT(efforts) {
101
+ return pick(efforts, "medium");
102
+ }
103
+ function pick(efforts, preferred) {
104
+ const hit = efforts.find((e) => e.id === preferred);
105
+ return (hit ?? efforts[efforts.length - 1])?.id ?? preferred;
106
+ }
107
+ //# sourceMappingURL=model-efforts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-efforts.js","sourceRoot":"","sources":["../../src/model-efforts.ts"],"names":[],"mappings":"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"}
@@ -1,36 +1,69 @@
1
1
  /**
2
- * Model catalog — the daemon's source of truth for which agent models the app
3
- * may pick from.
2
+ * Model catalog — the daemon's source of truth for which agent models (and, for
3
+ * models that support it, which reasoning-effort levels) the app may pick from.
4
4
  *
5
5
  * A model's `provider` decides which agent backend runs it:
6
- * - "claude" → ClaudeAgent (Agent SDK, `model` option)
7
- * - "codex" → CodexAgent (OpenAI Codex CLI, `-m` flag)
6
+ * - "claude" → ClaudeAgent (Agent SDK, `model` + `effort` options)
7
+ * - "codex" → CodexAgent (OpenAI Codex CLI, `-m` + `-c
8
+ * model_reasoning_effort=<level>`)
8
9
  * - "openrouter" → OpenRouterAgent (Agent SDK against OpenRouter's
9
- * Anthropic-compatible endpoint; the `id` is an OpenRouter
10
- * `vendor/model` slug — see OpenRouterAgent / OPENROUTER_API_KEY)
10
+ * Anthropic-compatible endpoint; `id` is an OpenRouter
11
+ * `vendor/model` slug)
12
+ * - "ollama" → OllamaAgent (Agent SDK against a local Ollama's
13
+ * Anthropic-compatible endpoint; `id` is an Ollama model tag.
14
+ * Runs on-device, no key, no billing)
11
15
  *
12
- * The list is deliberately scoped to models released in roughly the last year
13
- * (as of 2026-08). `id` is the exact string handed to the backend; keep them in
14
- * sync with each provider's model IDs. The Dart mirror is app/lib/models.dart.
16
+ * DISCOVERY. Online providers are enumerated live from their model APIs and
17
+ * scoped to the last 12 months (see anthropic/openai/openrouter-probe.ts); the
18
+ * effort levels each model supports come from model-efforts.ts. Ollama is
19
+ * discovered from whatever is installed locally (every model, no date filter).
20
+ * The static {@link FALLBACK_MODELS} below is only used for a provider whose
21
+ * live probe returned nothing — no API key, or an unreachable endpoint — so the
22
+ * picker is never empty. `id` is the exact string handed to the backend.
23
+ *
24
+ * The Dart mirror is packages/immediacy_core/lib/models.dart (ModelInfo /
25
+ * ModelEffort shape only).
15
26
  */
16
27
  import type { ModelInfo, ModelProvider } from "./protocol.js";
17
- /**
18
- * Available models, in display order. Exactly one is marked `default: true`;
19
- * that model backs new workspaces when the client doesn't pick one, and any
20
- * workspace restored from before model selection existed.
21
- */
22
- export declare const MODELS: ModelInfo[];
23
- export declare const DEFAULT_MODEL: ModelInfo;
28
+ import type { CredentialsStore } from "./credentials.js";
29
+ /** The id backing new workspaces when a client doesn't pick a model. */
30
+ export declare const DEFAULT_MODEL_ID = "claude-opus-4-8";
31
+ export declare const FALLBACK_MODELS: ModelInfo[];
24
32
  /**
25
33
  * Sentinel Codex model id meaning "don't pass -m; use the account's default".
26
34
  * Passing an explicit model id requires API-key billing; a ChatGPT-login Codex
27
35
  * only accepts its plan default, so this is the safe cross-setup choice.
28
36
  */
29
37
  export declare const CODEX_DEFAULT_MODEL = "codex";
30
- /** Look up a model by id (undefined id → the default). */
38
+ /**
39
+ * The full catalog surfaced to clients: live-discovered online models (Claude /
40
+ * Codex / OpenRouter, scoped to the last 12 months) plus every locally-installed
41
+ * Ollama model. A provider with no live results falls back to its curated static
42
+ * entries so the picker is never empty. Recomputed on each read so a fresh probe
43
+ * shows up without restarting the daemon.
44
+ */
45
+ export declare function allModels(): ModelInfo[];
46
+ /** The model that backs new workspaces when a client doesn't pick one. */
47
+ export declare function defaultModel(): ModelInfo;
48
+ /**
49
+ * Refresh every provider's catalog from its live API, using the resolved key
50
+ * for each (Anthropic/OpenAI from CredentialsStore; OpenRouter listing is
51
+ * keyless; Ollama is local). Runs the four probes concurrently; each is
52
+ * independently best-effort and never throws.
53
+ */
54
+ export declare function refreshAllModels(credentials?: CredentialsStore, force?: boolean): Promise<void>;
55
+ /** Look up a model by id (undefined id → the default). Includes discovered
56
+ * online + Ollama models so a workspace resolves to the right backend. */
31
57
  export declare function modelById(id?: string): ModelInfo | undefined;
32
58
  /**
33
59
  * Which backend runs a given model id. Unknown or missing ids fall back to
34
60
  * "claude" so restored/legacy workspaces keep working.
35
61
  */
36
62
  export declare function providerFor(id?: string): ModelProvider;
63
+ /**
64
+ * Resolve a client-supplied effort id against a model's supported set: returns
65
+ * the id only if the model actually offers it, else undefined (so an invalid or
66
+ * stale effort never reaches a backend). Falls back to the model's `defaultEffort`
67
+ * when `effortId` is absent.
68
+ */
69
+ export declare function resolveEffort(modelId: string | undefined, effortId: string | undefined): string | undefined;
@@ -1,34 +1,66 @@
1
1
  /**
2
- * Model catalog — the daemon's source of truth for which agent models the app
3
- * may pick from.
2
+ * Model catalog — the daemon's source of truth for which agent models (and, for
3
+ * models that support it, which reasoning-effort levels) the app may pick from.
4
4
  *
5
5
  * A model's `provider` decides which agent backend runs it:
6
- * - "claude" → ClaudeAgent (Agent SDK, `model` option)
7
- * - "codex" → CodexAgent (OpenAI Codex CLI, `-m` flag)
6
+ * - "claude" → ClaudeAgent (Agent SDK, `model` + `effort` options)
7
+ * - "codex" → CodexAgent (OpenAI Codex CLI, `-m` + `-c
8
+ * model_reasoning_effort=<level>`)
8
9
  * - "openrouter" → OpenRouterAgent (Agent SDK against OpenRouter's
9
- * Anthropic-compatible endpoint; the `id` is an OpenRouter
10
- * `vendor/model` slug — see OpenRouterAgent / OPENROUTER_API_KEY)
10
+ * Anthropic-compatible endpoint; `id` is an OpenRouter
11
+ * `vendor/model` slug)
12
+ * - "ollama" → OllamaAgent (Agent SDK against a local Ollama's
13
+ * Anthropic-compatible endpoint; `id` is an Ollama model tag.
14
+ * Runs on-device, no key, no billing)
11
15
  *
12
- * The list is deliberately scoped to models released in roughly the last year
13
- * (as of 2026-08). `id` is the exact string handed to the backend; keep them in
14
- * sync with each provider's model IDs. The Dart mirror is app/lib/models.dart.
16
+ * DISCOVERY. Online providers are enumerated live from their model APIs and
17
+ * scoped to the last 12 months (see anthropic/openai/openrouter-probe.ts); the
18
+ * effort levels each model supports come from model-efforts.ts. Ollama is
19
+ * discovered from whatever is installed locally (every model, no date filter).
20
+ * The static {@link FALLBACK_MODELS} below is only used for a provider whose
21
+ * live probe returned nothing — no API key, or an unreachable endpoint — so the
22
+ * picker is never empty. `id` is the exact string handed to the backend.
23
+ *
24
+ * The Dart mirror is packages/immediacy_core/lib/models.dart (ModelInfo /
25
+ * ModelEffort shape only).
15
26
  */
27
+ import { ollamaModels, refreshOllamaModels } from "./agent/ollama-probe.js";
28
+ import { anthropicModels, refreshAnthropicModels, } from "./agent/anthropic-probe.js";
29
+ import { openaiModels, refreshOpenAIModels } from "./agent/openai-probe.js";
30
+ import { openrouterModels, refreshOpenRouterModels, } from "./agent/openrouter-probe.js";
31
+ import { effortsForClaude, effortsForCodex, DEFAULT_CLAUDE_EFFORT, DEFAULT_CODEX_EFFORT, } from "./model-efforts.js";
32
+ /** The id backing new workspaces when a client doesn't pick a model. */
33
+ export const DEFAULT_MODEL_ID = "claude-opus-4-8";
34
+ /** Attach the effort set for a static Claude/Codex entry (keeps it in sync). */
35
+ function withClaudeEfforts(m) {
36
+ const efforts = effortsForClaude(m.id);
37
+ return efforts
38
+ ? { ...m, efforts, defaultEffort: DEFAULT_CLAUDE_EFFORT(efforts) }
39
+ : m;
40
+ }
41
+ function withCodexEfforts(m) {
42
+ const efforts = effortsForCodex(m.id);
43
+ return efforts
44
+ ? { ...m, efforts, defaultEffort: DEFAULT_CODEX_EFFORT(efforts) }
45
+ : m;
46
+ }
16
47
  /**
17
- * Available models, in display order. Exactly one is marked `default: true`;
18
- * that model backs new workspaces when the client doesn't pick one, and any
19
- * workspace restored from before model selection existed.
48
+ * Curated fallback catalog (release-scoped to roughly the last year), used only
49
+ * for a provider whose live probe yielded nothing. Effort metadata is attached
50
+ * from the shared policy so a fallback entry matches its discovered twin.
20
51
  */
21
- export const MODELS = [
52
+ const FALLBACK_CLAUDE = [
22
53
  // Anthropic — Claude (Agent SDK).
23
54
  { id: "claude-opus-4-8", label: "Claude Opus 4.8", provider: "claude", default: true },
24
55
  { id: "claude-fable-5", label: "Claude Fable 5", provider: "claude" },
25
56
  { id: "claude-opus-4-7", label: "Claude Opus 4.7", provider: "claude" },
26
57
  { id: "claude-opus-4-6", label: "Claude Opus 4.6", provider: "claude" },
27
58
  { id: "claude-opus-4-5", label: "Claude Opus 4.5", provider: "claude" },
28
- { id: "claude-sonnet-5", label: "Claude Sonnet 5", provider: "claude" },
29
59
  { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", provider: "claude" },
30
60
  { id: "claude-sonnet-4-5", label: "Claude Sonnet 4.5", provider: "claude" },
31
61
  { id: "claude-haiku-4-5", label: "Claude Haiku 4.5", provider: "claude" },
62
+ ].map((m) => withClaudeEfforts(m));
63
+ const FALLBACK_CODEX = [
32
64
  // OpenAI — Codex CLI. "codex" is a sentinel meaning "let Codex pick the
33
65
  // account's default model" — the only option that works on a ChatGPT login
34
66
  // (explicit ids require API-key billing). See CODEX_DEFAULT_MODEL.
@@ -38,28 +70,78 @@ export const MODELS = [
38
70
  { id: "gpt-5-mini", label: "GPT-5 mini", provider: "codex" },
39
71
  { id: "o4-mini", label: "OpenAI o4-mini", provider: "codex" },
40
72
  { id: "o3", label: "OpenAI o3", provider: "codex" },
73
+ ].map((m) => withCodexEfforts(m));
74
+ const FALLBACK_OPENROUTER = [
41
75
  // OpenRouter — routed through the Agent SDK against OpenRouter's
42
76
  // Anthropic-compatible endpoint (needs OPENROUTER_API_KEY). `id` is the exact
43
- // OpenRouter `vendor/model` slug; this is a curated starter set of capable
44
- // agentic models the native Claude/Codex backends don't already cover — add
45
- // any slug from https://openrouter.ai/models here.
77
+ // OpenRouter `vendor/model` slug; a curated starter set the native
78
+ // Claude/Codex backends don't already cover.
46
79
  { id: "google/gemini-3.7-flash", label: "Gemini 3.7 Flash", provider: "openrouter" },
47
80
  { id: "deepseek/deepseek-v4-pro-0813", label: "DeepSeek V4 Pro", provider: "openrouter" },
48
81
  { id: "x-ai/grok-4.6", label: "Grok 4.6", provider: "openrouter" },
49
82
  { id: "qwen/qwen3.8-max", label: "Qwen3.8 Max", provider: "openrouter" },
50
83
  ];
51
- export const DEFAULT_MODEL = MODELS.find((m) => m.default) ?? MODELS[0];
84
+ export const FALLBACK_MODELS = [
85
+ ...FALLBACK_CLAUDE,
86
+ ...FALLBACK_CODEX,
87
+ ...FALLBACK_OPENROUTER,
88
+ ];
52
89
  /**
53
90
  * Sentinel Codex model id meaning "don't pass -m; use the account's default".
54
91
  * Passing an explicit model id requires API-key billing; a ChatGPT-login Codex
55
92
  * only accepts its plan default, so this is the safe cross-setup choice.
56
93
  */
57
94
  export const CODEX_DEFAULT_MODEL = "codex";
58
- /** Look up a model by id (undefined id → the default). */
95
+ /** Fallback entries for one provider (used when its live probe is empty). */
96
+ function fallbackFor(provider) {
97
+ return FALLBACK_MODELS.filter((m) => m.provider === provider);
98
+ }
99
+ /**
100
+ * The full catalog surfaced to clients: live-discovered online models (Claude /
101
+ * Codex / OpenRouter, scoped to the last 12 months) plus every locally-installed
102
+ * Ollama model. A provider with no live results falls back to its curated static
103
+ * entries so the picker is never empty. Recomputed on each read so a fresh probe
104
+ * shows up without restarting the daemon.
105
+ */
106
+ export function allModels() {
107
+ const claude = anthropicModels();
108
+ const codex = openaiModels();
109
+ const openrouter = openrouterModels();
110
+ return [
111
+ ...(claude.length ? claude : fallbackFor("claude")),
112
+ ...(codex.length ? codex : fallbackFor("codex")),
113
+ ...(openrouter.length ? openrouter : fallbackFor("openrouter")),
114
+ ...ollamaModels(),
115
+ ];
116
+ }
117
+ /** The model that backs new workspaces when a client doesn't pick one. */
118
+ export function defaultModel() {
119
+ const models = allModels();
120
+ return (models.find((m) => m.id === DEFAULT_MODEL_ID) ??
121
+ models.find((m) => m.default) ??
122
+ models[0] ??
123
+ FALLBACK_MODELS[0]);
124
+ }
125
+ /**
126
+ * Refresh every provider's catalog from its live API, using the resolved key
127
+ * for each (Anthropic/OpenAI from CredentialsStore; OpenRouter listing is
128
+ * keyless; Ollama is local). Runs the four probes concurrently; each is
129
+ * independently best-effort and never throws.
130
+ */
131
+ export async function refreshAllModels(credentials, force = false) {
132
+ await Promise.allSettled([
133
+ refreshAnthropicModels(credentials?.getKey("anthropic"), force),
134
+ refreshOpenAIModels(credentials?.getKey("openai"), force),
135
+ refreshOpenRouterModels(credentials?.getKey("openrouter"), force),
136
+ refreshOllamaModels(force),
137
+ ]);
138
+ }
139
+ /** Look up a model by id (undefined id → the default). Includes discovered
140
+ * online + Ollama models so a workspace resolves to the right backend. */
59
141
  export function modelById(id) {
60
142
  if (!id)
61
- return DEFAULT_MODEL;
62
- return MODELS.find((m) => m.id === id);
143
+ return defaultModel();
144
+ return allModels().find((m) => m.id === id);
63
145
  }
64
146
  /**
65
147
  * Which backend runs a given model id. Unknown or missing ids fall back to
@@ -68,4 +150,20 @@ export function modelById(id) {
68
150
  export function providerFor(id) {
69
151
  return modelById(id)?.provider ?? "claude";
70
152
  }
153
+ /**
154
+ * Resolve a client-supplied effort id against a model's supported set: returns
155
+ * the id only if the model actually offers it, else undefined (so an invalid or
156
+ * stale effort never reaches a backend). Falls back to the model's `defaultEffort`
157
+ * when `effortId` is absent.
158
+ */
159
+ export function resolveEffort(modelId, effortId) {
160
+ const model = modelById(modelId);
161
+ if (!model?.efforts?.length)
162
+ return undefined;
163
+ if (effortId && model.efforts.some((e) => e.id === effortId))
164
+ return effortId;
165
+ if (!effortId)
166
+ return model.defaultEffort;
167
+ return undefined;
168
+ }
71
169
  //# sourceMappingURL=models.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/models.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/models.ts"],"names":[],"mappings":"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"}
@@ -72,6 +72,13 @@ export declare class ProjectStore {
72
72
  ensureForSource(src: ProjectSource): Promise<ProjectRecord>;
73
73
  /** Remove a project. The caller guards that no worktrees still reference it. */
74
74
  remove(id: string): Promise<void>;
75
+ /**
76
+ * Rename a project — display name only. The on-disk managed dir (`slugFor`) is
77
+ * derived from the name at create time and never recomputed, so it is left as
78
+ * is (matching existing behavior). No-op on an unknown id, a blank name, or an
79
+ * unchanged name; persists only when the name actually changes.
80
+ */
81
+ rename(id: string, name: string): Promise<void>;
75
82
  /**
76
83
  * Rewrite the stored project order. `list()` returns projects in Map insertion
77
84
  * order, so we rebuild the Map in the requested order. Unknown ids are ignored;