omnexx 0.5.0 → 0.6.0

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 (43) hide show
  1. package/dist/chat-server-NB3GPOMD.js +205 -0
  2. package/dist/chat-server-NB3GPOMD.js.map +1 -0
  3. package/dist/chat-settings-D3WKVSFU.js +110 -0
  4. package/dist/chat-settings-D3WKVSFU.js.map +1 -0
  5. package/dist/{chunk-YMIRAOIB.js → chunk-55VJCO7L.js} +12 -4
  6. package/dist/chunk-55VJCO7L.js.map +1 -0
  7. package/dist/{chunk-CLAS22GZ.js → chunk-5T4EFIY2.js} +10 -76
  8. package/dist/chunk-5T4EFIY2.js.map +1 -0
  9. package/dist/chunk-7PC4SSYS.js +78 -0
  10. package/dist/chunk-7PC4SSYS.js.map +1 -0
  11. package/dist/chunk-7PNGHZYH.js +87 -0
  12. package/dist/chunk-7PNGHZYH.js.map +1 -0
  13. package/dist/{chunk-IJPR52MO.js → chunk-AOIUX7TJ.js} +6 -4
  14. package/dist/{chunk-IJPR52MO.js.map → chunk-AOIUX7TJ.js.map} +1 -1
  15. package/dist/{start-Y5B2FUMR.js → chunk-M64SDRL3.js} +250 -1008
  16. package/dist/chunk-M64SDRL3.js.map +1 -0
  17. package/dist/{chunk-TZ3KO3PA.js → chunk-N2WJ2YTX.js} +571 -461
  18. package/dist/chunk-N2WJ2YTX.js.map +1 -0
  19. package/dist/{chunk-OXDXHUTX.js → chunk-PY47EEHG.js} +279 -345
  20. package/dist/chunk-PY47EEHG.js.map +1 -0
  21. package/dist/{chunk-Y74QSMFT.js → chunk-S3TUDM5B.js} +23 -100
  22. package/dist/chunk-S3TUDM5B.js.map +1 -0
  23. package/dist/chunk-UZ6WXVKI.js +146 -0
  24. package/dist/chunk-UZ6WXVKI.js.map +1 -0
  25. package/dist/cli.js +33 -24
  26. package/dist/cli.js.map +1 -1
  27. package/dist/model-picker-7NLP3QO6.js +20 -0
  28. package/dist/model-picker-7NLP3QO6.js.map +1 -0
  29. package/dist/{ollama-VFXDRFQ4.js → ollama-V6LN6HAR.js} +6 -4
  30. package/dist/{ollama-VFXDRFQ4.js.map → ollama-V6LN6HAR.js.map} +1 -1
  31. package/dist/{print-KUDY6NOG.js → print-OUSVBDAA.js} +5 -4
  32. package/dist/{print-KUDY6NOG.js.map → print-OUSVBDAA.js.map} +1 -1
  33. package/dist/session-YXEM22IA.js +26 -0
  34. package/dist/session-YXEM22IA.js.map +1 -0
  35. package/dist/start-ZJQLZBS6.js +709 -0
  36. package/dist/start-ZJQLZBS6.js.map +1 -0
  37. package/package.json +2 -2
  38. package/dist/chunk-CLAS22GZ.js.map +0 -1
  39. package/dist/chunk-OXDXHUTX.js.map +0 -1
  40. package/dist/chunk-TZ3KO3PA.js.map +0 -1
  41. package/dist/chunk-Y74QSMFT.js.map +0 -1
  42. package/dist/chunk-YMIRAOIB.js.map +0 -1
  43. package/dist/start-Y5B2FUMR.js.map +0 -1
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ println
4
+ } from "./chunk-7PC4SSYS.js";
5
+ import {
6
+ describeError
7
+ } from "./chunk-7PNGHZYH.js";
8
+ import "./chunk-2H7UOFLK.js";
9
+
10
+ // src/cli/chat-server.ts
11
+ import { createInterface } from "readline";
12
+ var MODES = /* @__PURE__ */ new Set(["chat", "plan", "run"]);
13
+ var EMIT_EVERY_MS = 40;
14
+ function parseCommand(line) {
15
+ let data;
16
+ try {
17
+ data = JSON.parse(line);
18
+ } catch {
19
+ throw new Error("expected one JSON object per line");
20
+ }
21
+ if (typeof data !== "object" || data === null || typeof data.type !== "string")
22
+ throw new Error('expected an object with a "type"');
23
+ return data;
24
+ }
25
+ function text(c, key) {
26
+ const v = c[key];
27
+ if (typeof v !== "string" || !v.trim()) throw new Error(`"${c.type}" needs a "${key}" string`);
28
+ return v;
29
+ }
30
+ async function serveChat(io, runCli, opts) {
31
+ const { Session } = await import("./session-YXEM22IA.js");
32
+ const session = new Session(io, runCli);
33
+ const write = (msg) => {
34
+ println(io.stdout, JSON.stringify(msg));
35
+ };
36
+ const fail = (err) => {
37
+ write({ type: "error", message: describeError(err) });
38
+ };
39
+ let sentUpTo = -1;
40
+ let clearedAt = -1;
41
+ let timer;
42
+ const emit = () => {
43
+ timer = void 0;
44
+ const fresh = session.entries.filter((e) => e.id > sentUpTo);
45
+ sentUpTo = session.entries.at(-1)?.id ?? sentUpTo;
46
+ const t = session.chatTelemetry;
47
+ const state = {
48
+ type: "state",
49
+ entries: fresh,
50
+ live: session.live,
51
+ todos: session.todos,
52
+ pending: session.pending?.question ?? null,
53
+ mode: session.mode,
54
+ busy: session.busy !== void 0 || session.chatBusy,
55
+ paused: session.chatPaused,
56
+ autoApprove: session.autoApprove,
57
+ model: session.chatModelName ?? null,
58
+ context: session.context ?? null,
59
+ usage: t ? {
60
+ startedAt: t.startedAt ?? null,
61
+ tokens: t.tokens,
62
+ usd: t.usd,
63
+ turns: t.turns,
64
+ toolCalls: t.toolCalls,
65
+ toolErrors: t.toolErrors,
66
+ compactions: t.compactions
67
+ } : null,
68
+ run: session.runId ? { id: session.runId, alive: session.runAlive, ...session.info } : null
69
+ };
70
+ write(state);
71
+ };
72
+ const schedule = () => {
73
+ timer ??= setTimeout(emit, EMIT_EVERY_MS);
74
+ };
75
+ session.onChange(schedule);
76
+ const sendModels = async () => {
77
+ const { loadModelChoices } = await import("./model-picker-7NLP3QO6.js");
78
+ const { items, unreachable } = await loadModelChoices(io);
79
+ write({
80
+ type: "models",
81
+ items: items.filter((i) => i.kind === "model"),
82
+ unreachable,
83
+ current: session.chatModelName ?? null
84
+ });
85
+ };
86
+ const sendSettings = async () => {
87
+ const { providerSettings } = await import("./chat-settings-D3WKVSFU.js");
88
+ write({ type: "settings", ...await providerSettings(io) });
89
+ };
90
+ const providersChanged = async (message) => {
91
+ session.push("system", message);
92
+ await sendSettings();
93
+ await sendModels();
94
+ };
95
+ const run = (p) => {
96
+ p.catch(fail).finally(schedule);
97
+ };
98
+ const handle = (c) => {
99
+ switch (c.type) {
100
+ case "send":
101
+ run(session.submit(text(c, "text")));
102
+ return;
103
+ case "answer":
104
+ if (!session.pending) throw new Error("nothing is waiting for an answer");
105
+ run(session.submit(c.yes === true ? "y" : "n"));
106
+ return;
107
+ case "stop":
108
+ case "pause":
109
+ case "resume":
110
+ case "undo":
111
+ case "compact":
112
+ run(session.command(c.type));
113
+ return;
114
+ case "clear":
115
+ run(
116
+ session.command("clear").then(() => {
117
+ clearedAt = session.entries.at(-1)?.id ?? clearedAt;
118
+ sentUpTo = clearedAt;
119
+ write({ type: "ready", version: opts.version });
120
+ emit();
121
+ })
122
+ );
123
+ return;
124
+ case "mode": {
125
+ const mode = c.mode;
126
+ if (!MODES.has(mode)) throw new Error("mode is one of chat, plan, run");
127
+ if (session.mode !== mode) session.setMode(mode);
128
+ return;
129
+ }
130
+ case "yolo":
131
+ if (c.on === true !== session.autoApprove) run(session.submit("/yolo"));
132
+ return;
133
+ case "model":
134
+ run(session.switchModel(text(c, "ref")));
135
+ return;
136
+ case "models":
137
+ run(sendModels());
138
+ return;
139
+ case "settings":
140
+ run(sendSettings());
141
+ return;
142
+ case "addProvider": {
143
+ const str = (k) => {
144
+ const v = c[k];
145
+ return typeof v === "string" ? v : void 0;
146
+ };
147
+ run(
148
+ import("./chat-settings-D3WKVSFU.js").then(async ({ addProvider }) => {
149
+ await providersChanged(
150
+ await addProvider(io, {
151
+ provider: str("provider"),
152
+ key: str("key"),
153
+ url: str("url")
154
+ })
155
+ );
156
+ })
157
+ );
158
+ return;
159
+ }
160
+ case "removeProvider":
161
+ run(
162
+ import("./chat-settings-D3WKVSFU.js").then(async ({ removeProvider }) => {
163
+ await providersChanged(await removeProvider(io, text(c, "name")));
164
+ })
165
+ );
166
+ return;
167
+ case "connect": {
168
+ const name = typeof c.name === "string" && c.name.trim() ? ` --name ${c.name.trim()}` : "";
169
+ run(session.submit(`/model ${text(c, "value")}${name}`));
170
+ return;
171
+ }
172
+ case "sync":
173
+ sentUpTo = clearedAt;
174
+ write({ type: "ready", version: opts.version });
175
+ emit();
176
+ return;
177
+ default:
178
+ throw new Error(`unknown command "${c.type}"`);
179
+ }
180
+ };
181
+ await session.loadCommands();
182
+ await session.continueChat().catch(fail);
183
+ await session.greet(false).catch(fail);
184
+ write({ type: "ready", version: opts.version });
185
+ emit();
186
+ const lines = createInterface({ input: io.stdin, crlfDelay: Infinity });
187
+ for await (const line of lines) {
188
+ if (!line.trim()) continue;
189
+ try {
190
+ handle(parseCommand(line));
191
+ } catch (err) {
192
+ fail(err);
193
+ }
194
+ }
195
+ if (session.chatBusy) await session.command("stop");
196
+ if (timer) {
197
+ clearTimeout(timer);
198
+ emit();
199
+ }
200
+ return 0;
201
+ }
202
+ export {
203
+ serveChat
204
+ };
205
+ //# sourceMappingURL=chat-server-NB3GPOMD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/chat-server.ts"],"sourcesContent":["import { createInterface } from 'node:readline';\nimport { describeError } from '../errors.js';\nimport type { Telemetry } from '../telemetry/aggregate.js';\nimport type { TodoItem } from '../tools/todo.js';\nimport type { Entry, Mode, RunCli } from '../tui/session.js';\nimport { println, type CliIO } from './io.js';\n\n/**\n * `omnexx chat-server`: the interactive session behind a JSON-lines protocol on stdin/stdout, for\n * editor panels (the VS Code chat). It drives the same Session as the terminal UI, so slash\n * commands, approvals, model switching and plan mode behave the same way.\n *\n * In (one JSON object per line): send {text} · answer {yes} · stop · pause · resume · mode {mode} ·\n * yolo {on} ·\n * model {ref} · models · connect {value, name?} · undo · clear · compact · sync (resend all) ·\n * settings · addProvider {provider?, key?, url?} · removeProvider {name}.\n * Out: ready {version} · state (below; `entries` holds only lines not sent before) · models\n * {items, unreachable, current} · settings {catalog, connected} · error {message}.\n */\n\nexport interface ChatUsage {\n /** When the chat's first event happened (epoch ms), for an uptime clock. */\n startedAt: number | null;\n tokens: Telemetry['tokens'];\n usd: number;\n turns: number;\n toolCalls: number;\n toolErrors: number;\n compactions: number;\n}\n\nexport interface ChatState {\n type: 'state';\n entries: Entry[];\n /** The reply as it streams in; empty once it lands in `entries`. */\n live: string;\n todos: TodoItem[];\n /** A question waiting for allow / deny, or null. */\n pending: string | null;\n mode: Mode;\n busy: boolean;\n paused: boolean;\n autoApprove: boolean;\n model: string | null;\n context: { used: number; limit: number } | null;\n usage: ChatUsage | null;\n run: { id: string; alive: boolean; phase?: string; done?: number; tasks?: number } | null;\n}\n\nconst MODES = new Set<Mode>(['chat', 'plan', 'run']);\n/** Coalesce bursts of changes (a streaming reply fires one per token) into one state line. */\nconst EMIT_EVERY_MS = 40;\n\ntype Command = { type: string } & Record<string, unknown>;\n\nfunction parseCommand(line: string): Command {\n let data: unknown;\n try {\n data = JSON.parse(line);\n } catch {\n throw new Error('expected one JSON object per line');\n }\n if (typeof data !== 'object' || data === null || typeof (data as Command).type !== 'string')\n throw new Error('expected an object with a \"type\"');\n return data as Command;\n}\n\nfunction text(c: Command, key: string): string {\n const v = c[key];\n if (typeof v !== 'string' || !v.trim()) throw new Error(`\"${c.type}\" needs a \"${key}\" string`);\n return v;\n}\n\nexport async function serveChat(\n io: CliIO,\n runCli: RunCli,\n opts: { version: string },\n): Promise<number> {\n const { Session } = await import('../tui/session.js');\n const session = new Session(io, runCli);\n const write = (msg: object): void => {\n println(io.stdout, JSON.stringify(msg));\n };\n const fail = (err: unknown): void => {\n write({ type: 'error', message: describeError(err) });\n };\n\n let sentUpTo = -1;\n /** The last entry before the latest new chat; a reloaded panel starts after it. */\n let clearedAt = -1;\n let timer: NodeJS.Timeout | undefined;\n const emit = (): void => {\n timer = undefined;\n const fresh = session.entries.filter((e) => e.id > sentUpTo);\n sentUpTo = session.entries.at(-1)?.id ?? sentUpTo;\n const t = session.chatTelemetry;\n const state: ChatState = {\n type: 'state',\n entries: fresh,\n live: session.live,\n todos: session.todos,\n pending: session.pending?.question ?? null,\n mode: session.mode,\n busy: session.busy !== undefined || session.chatBusy,\n paused: session.chatPaused,\n autoApprove: session.autoApprove,\n model: session.chatModelName ?? null,\n context: session.context ?? null,\n usage: t\n ? {\n startedAt: t.startedAt ?? null,\n tokens: t.tokens,\n usd: t.usd,\n turns: t.turns,\n toolCalls: t.toolCalls,\n toolErrors: t.toolErrors,\n compactions: t.compactions,\n }\n : null,\n run: session.runId ? { id: session.runId, alive: session.runAlive, ...session.info } : null,\n };\n write(state);\n };\n const schedule = (): void => {\n timer ??= setTimeout(emit, EMIT_EVERY_MS);\n };\n session.onChange(schedule);\n\n const sendModels = async (): Promise<void> => {\n const { loadModelChoices } = await import('../tui/model-picker.js');\n const { items, unreachable } = await loadModelChoices(io);\n write({\n type: 'models',\n items: items.filter((i) => i.kind === 'model'),\n unreachable,\n current: session.chatModelName ?? null,\n });\n };\n const sendSettings = async (): Promise<void> => {\n const { providerSettings } = await import('./chat-settings.js');\n write({ type: 'settings', ...(await providerSettings(io)) });\n };\n /** After a provider change: say what happened, then refresh the settings page and models. */\n const providersChanged = async (message: string): Promise<void> => {\n session.push('system', message);\n await sendSettings();\n await sendModels();\n };\n\n const run = (p: Promise<unknown>): void => {\n p.catch(fail).finally(schedule);\n };\n\n const handle = (c: Command): void => {\n switch (c.type) {\n case 'send':\n run(session.submit(text(c, 'text')));\n return;\n case 'answer':\n if (!session.pending) throw new Error('nothing is waiting for an answer');\n run(session.submit(c.yes === true ? 'y' : 'n'));\n return;\n case 'stop':\n case 'pause':\n case 'resume':\n case 'undo':\n case 'compact':\n run(session.command(c.type));\n return;\n case 'clear':\n run(\n session.command('clear').then(() => {\n // A new chat starts the panel over: nothing from before the clear is sent again.\n clearedAt = session.entries.at(-1)?.id ?? clearedAt;\n sentUpTo = clearedAt;\n write({ type: 'ready', version: opts.version });\n emit();\n }),\n );\n return;\n case 'mode': {\n const mode = c.mode as Mode;\n if (!MODES.has(mode)) throw new Error('mode is one of chat, plan, run');\n if (session.mode !== mode) session.setMode(mode);\n return;\n }\n case 'yolo':\n // /yolo toggles; only send it when the state differs from what was asked.\n if ((c.on === true) !== session.autoApprove) run(session.submit('/yolo'));\n return;\n case 'model':\n run(session.switchModel(text(c, 'ref')));\n return;\n case 'models':\n run(sendModels());\n return;\n case 'settings':\n run(sendSettings());\n return;\n case 'addProvider': {\n const str = (k: string): string | undefined => {\n const v = c[k];\n return typeof v === 'string' ? v : undefined;\n };\n run(\n import('./chat-settings.js').then(async ({ addProvider }) => {\n await providersChanged(\n await addProvider(io, {\n provider: str('provider'),\n key: str('key'),\n url: str('url'),\n }),\n );\n }),\n );\n return;\n }\n case 'removeProvider':\n run(\n import('./chat-settings.js').then(async ({ removeProvider }) => {\n await providersChanged(await removeProvider(io, text(c, 'name')));\n }),\n );\n return;\n case 'connect': {\n const name = typeof c.name === 'string' && c.name.trim() ? ` --name ${c.name.trim()}` : '';\n run(session.submit(`/model ${text(c, 'value')}${name}`));\n return;\n }\n case 'sync':\n // A reloaded panel: start it over with the conversation since the last new chat.\n sentUpTo = clearedAt;\n write({ type: 'ready', version: opts.version });\n emit();\n return;\n default:\n throw new Error(`unknown command \"${c.type}\"`);\n }\n };\n\n await session.loadCommands();\n await session.continueChat().catch(fail);\n await session.greet(false).catch(fail);\n write({ type: 'ready', version: opts.version });\n emit();\n\n const lines = createInterface({ input: io.stdin, crlfDelay: Infinity });\n for await (const line of lines) {\n if (!line.trim()) continue;\n try {\n handle(parseCommand(line));\n } catch (err) {\n fail(err);\n }\n }\n // The editor closed the pipe: end any turn in progress, then flush the last state.\n if (session.chatBusy) await session.command('stop');\n if (timer) {\n clearTimeout(timer);\n emit();\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ connect,
4
+ describeConnect
5
+ } from "./chunk-AOIUX7TJ.js";
6
+ import {
7
+ PROVIDER_TEMPLATES,
8
+ removeEndpointBlock
9
+ } from "./chunk-5T4EFIY2.js";
10
+ import "./chunk-7PC4SSYS.js";
11
+ import {
12
+ clearKey,
13
+ findProviderKey,
14
+ loadConfig,
15
+ maskKey,
16
+ resolvePaths,
17
+ userConfigFile
18
+ } from "./chunk-S3TUDM5B.js";
19
+ import {
20
+ UsageError
21
+ } from "./chunk-7PNGHZYH.js";
22
+ import "./chunk-2H7UOFLK.js";
23
+
24
+ // src/cli/chat-settings.ts
25
+ var ANTHROPIC = {
26
+ name: "anthropic",
27
+ label: "Anthropic (Claude)",
28
+ baseUrl: "https://api.anthropic.com",
29
+ local: false,
30
+ signup: "https://console.anthropic.com/settings/keys",
31
+ keyPrefix: "sk-ant-"
32
+ };
33
+ var NAME = /^[a-z][a-z0-9_-]{0,39}$/;
34
+ function providerCatalog() {
35
+ const listed = Object.entries(PROVIDER_TEMPLATES).filter(([name]) => name !== "custom").map(([name, t]) => ({
36
+ name,
37
+ label: t.label ?? name,
38
+ baseUrl: t.baseUrl,
39
+ local: t.local === true,
40
+ ...t.signup ? { signup: t.signup } : {},
41
+ ...t.keyPrefix ? { keyPrefix: t.keyPrefix } : {}
42
+ }));
43
+ return [ANTHROPIC, ...listed];
44
+ }
45
+ async function providerSettings(io) {
46
+ const paths = resolvePaths(io.env);
47
+ const { config } = await loadConfig({ cwd: io.cwd, env: io.env });
48
+ const catalog = providerCatalog();
49
+ const labelOf = (name) => catalog.find((c) => c.name === name)?.label ?? name;
50
+ const connected = [];
51
+ const anthropic = await findProviderKey(paths, io.env, "anthropic");
52
+ if (anthropic)
53
+ connected.push({
54
+ name: "anthropic",
55
+ label: ANTHROPIC.label,
56
+ baseUrl: ANTHROPIC.baseUrl,
57
+ local: false,
58
+ free: false,
59
+ key: maskKey(anthropic.key),
60
+ keySource: anthropic.source
61
+ });
62
+ for (const [name, ep] of Object.entries(config.providers.endpoints)) {
63
+ const found = await findProviderKey(paths, io.env, name, ep.api_key_env);
64
+ connected.push({
65
+ name,
66
+ label: labelOf(name),
67
+ baseUrl: ep.base_url,
68
+ local: PROVIDER_TEMPLATES[name]?.local === true || /\/\/(localhost|127\.0\.0\.1)[:/]/.test(ep.base_url),
69
+ free: ep.free,
70
+ key: found ? maskKey(found.key) : null,
71
+ keySource: found?.source ?? null
72
+ });
73
+ }
74
+ return { catalog, connected };
75
+ }
76
+ async function addProvider(io, a) {
77
+ const url = a.url?.trim();
78
+ const provider = a.provider?.trim().toLowerCase();
79
+ const key = a.key?.trim() ? a.key.trim() : void 0;
80
+ if (url && !/^https?:\/\/\S+$/i.test(url))
81
+ throw new UsageError("the URL must start with http:// or https://");
82
+ if (provider && !NAME.test(provider))
83
+ throw new UsageError(
84
+ `"${provider}" is not a usable name`,
85
+ "lowercase letters, digits, _ and -"
86
+ );
87
+ if (!url && !provider && !key) throw new UsageError("pick a provider or enter a URL");
88
+ const target = url ?? provider ?? key ?? "";
89
+ const r = await connect(io, target, url ? key : target === key ? void 0 : key, {
90
+ ...url && provider ? { name: provider } : {}
91
+ });
92
+ return describeConnect(r);
93
+ }
94
+ async function removeProvider(io, name) {
95
+ const lower = name.trim().toLowerCase();
96
+ if (!NAME.test(lower)) throw new UsageError(`"${name}" is not a provider name`);
97
+ const paths = resolvePaths(io.env);
98
+ const removedBlock = lower === "anthropic" ? false : await removeEndpointBlock(userConfigFile(paths), lower);
99
+ const removedKey = await clearKey(paths, lower);
100
+ if (!removedBlock && !removedKey)
101
+ return `${lower} had nothing to remove (a key from an environment variable stays set)`;
102
+ return `removed ${lower}${removedKey ? " and its stored key" : ""}`;
103
+ }
104
+ export {
105
+ addProvider,
106
+ providerCatalog,
107
+ providerSettings,
108
+ removeProvider
109
+ };
110
+ //# sourceMappingURL=chat-settings-D3WKVSFU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/chat-settings.ts"],"sourcesContent":["import { clearKey, findProviderKey, maskKey } from '../auth/keys.js';\nimport { loadConfig } from '../config/load.js';\nimport { resolvePaths, userConfigFile } from '../core/paths.js';\nimport { UsageError } from '../errors.js';\nimport { PROVIDER_TEMPLATES, removeEndpointBlock } from './commands/providers.js';\nimport { connect, describeConnect } from './connect.js';\nimport type { CliIO } from './io.js';\n\n/**\n * Provider settings for editor panels (`omnexx chat-server`): what can be added, what is set up,\n * and adding or removing one. Keys go to the 0600 credentials file and are only ever shown masked.\n */\n\nexport interface CatalogProvider {\n name: string;\n label: string;\n baseUrl: string;\n /** A local server the person runs themselves: no key. */\n local: boolean;\n /** Where to get a key. */\n signup?: string;\n /** How its keys start, for a placeholder. */\n keyPrefix?: string;\n}\n\nexport interface ConnectedProvider {\n name: string;\n label: string;\n baseUrl: string;\n local: boolean;\n free: boolean;\n /** The key, masked (`sk-…wxyz`), or null when none is set. */\n key: string | null;\n /** Where the key comes from: an environment variable or the credentials file. */\n keySource: string | null;\n}\n\nexport interface ProviderSettings {\n catalog: CatalogProvider[];\n connected: ConnectedProvider[];\n}\n\nconst ANTHROPIC: CatalogProvider = {\n name: 'anthropic',\n label: 'Anthropic (Claude)',\n baseUrl: 'https://api.anthropic.com',\n local: false,\n signup: 'https://console.anthropic.com/settings/keys',\n keyPrefix: 'sk-ant-',\n};\n\nconst NAME = /^[a-z][a-z0-9_-]{0,39}$/;\n\nexport function providerCatalog(): CatalogProvider[] {\n const listed = Object.entries(PROVIDER_TEMPLATES)\n .filter(([name]) => name !== 'custom')\n .map(([name, t]) => ({\n name,\n label: t.label ?? name,\n baseUrl: t.baseUrl,\n local: t.local === true,\n ...(t.signup ? { signup: t.signup } : {}),\n ...(t.keyPrefix ? { keyPrefix: t.keyPrefix } : {}),\n }));\n return [ANTHROPIC, ...listed];\n}\n\nexport async function providerSettings(io: CliIO): Promise<ProviderSettings> {\n const paths = resolvePaths(io.env);\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const catalog = providerCatalog();\n const labelOf = (name: string): string => catalog.find((c) => c.name === name)?.label ?? name;\n const connected: ConnectedProvider[] = [];\n const anthropic = await findProviderKey(paths, io.env, 'anthropic');\n if (anthropic)\n connected.push({\n name: 'anthropic',\n label: ANTHROPIC.label,\n baseUrl: ANTHROPIC.baseUrl,\n local: false,\n free: false,\n key: maskKey(anthropic.key),\n keySource: anthropic.source,\n });\n for (const [name, ep] of Object.entries(config.providers.endpoints)) {\n const found = await findProviderKey(paths, io.env, name, ep.api_key_env);\n connected.push({\n name,\n label: labelOf(name),\n baseUrl: ep.base_url,\n local:\n PROVIDER_TEMPLATES[name]?.local === true ||\n /\\/\\/(localhost|127\\.0\\.0\\.1)[:/]/.test(ep.base_url),\n free: ep.free,\n key: found ? maskKey(found.key) : null,\n keySource: found?.source ?? null,\n });\n }\n return { catalog, connected };\n}\n\nexport interface AddProvider {\n /** A catalog name, or a new name for a custom URL. */\n provider?: string | undefined;\n key?: string | undefined;\n /** For a custom or local OpenAI-compatible server. */\n url?: string | undefined;\n}\n\n/** Connect a provider from the settings form; returns what to tell the person. */\nexport async function addProvider(io: CliIO, a: AddProvider): Promise<string> {\n const url = a.url?.trim();\n const provider = a.provider?.trim().toLowerCase();\n const key = a.key?.trim() ? a.key.trim() : undefined;\n if (url && !/^https?:\\/\\/\\S+$/i.test(url))\n throw new UsageError('the URL must start with http:// or https://');\n if (provider && !NAME.test(provider))\n throw new UsageError(\n `\"${provider}\" is not a usable name`,\n 'lowercase letters, digits, _ and -',\n );\n if (!url && !provider && !key) throw new UsageError('pick a provider or enter a URL');\n // A catalog provider is connected by name; a URL gets the name it was given, if any.\n const target = url ?? provider ?? key ?? '';\n const r = await connect(io, target, url ? key : target === key ? undefined : key, {\n ...(url && provider ? { name: provider } : {}),\n });\n return describeConnect(r);\n}\n\n/** Remove a provider: its endpoint block and its stored key. Environment variables stay. */\nexport async function removeProvider(io: CliIO, name: string): Promise<string> {\n const lower = name.trim().toLowerCase();\n if (!NAME.test(lower)) throw new UsageError(`\"${name}\" is not a provider name`);\n const paths = resolvePaths(io.env);\n const removedBlock =\n lower === 'anthropic' ? false : await removeEndpointBlock(userConfigFile(paths), lower);\n const removedKey = await clearKey(paths, lower);\n if (!removedBlock && !removedKey)\n return `${lower} had nothing to remove (a key from an environment variable stays set)`;\n return `removed ${lower}${removedKey ? ' and its stored key' : ''}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAM,YAA6B;AAAA,EACjC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,WAAW;AACb;AAEA,IAAM,OAAO;AAEN,SAAS,kBAAqC;AACnD,QAAM,SAAS,OAAO,QAAQ,kBAAkB,EAC7C,OAAO,CAAC,CAAC,IAAI,MAAM,SAAS,QAAQ,EACpC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO;AAAA,IACnB;AAAA,IACA,OAAO,EAAE,SAAS;AAAA,IAClB,SAAS,EAAE;AAAA,IACX,OAAO,EAAE,UAAU;AAAA,IACnB,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,IACvC,GAAI,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,IAAI,CAAC;AAAA,EAClD,EAAE;AACJ,SAAO,CAAC,WAAW,GAAG,MAAM;AAC9B;AAEA,eAAsB,iBAAiB,IAAsC;AAC3E,QAAM,QAAQ,aAAa,GAAG,GAAG;AACjC,QAAM,EAAE,OAAO,IAAI,MAAM,WAAW,EAAE,KAAK,GAAG,KAAK,KAAK,GAAG,IAAI,CAAC;AAChE,QAAM,UAAU,gBAAgB;AAChC,QAAM,UAAU,CAAC,SAAyB,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,SAAS;AACzF,QAAM,YAAiC,CAAC;AACxC,QAAM,YAAY,MAAM,gBAAgB,OAAO,GAAG,KAAK,WAAW;AAClE,MAAI;AACF,cAAU,KAAK;AAAA,MACb,MAAM;AAAA,MACN,OAAO,UAAU;AAAA,MACjB,SAAS,UAAU;AAAA,MACnB,OAAO;AAAA,MACP,MAAM;AAAA,MACN,KAAK,QAAQ,UAAU,GAAG;AAAA,MAC1B,WAAW,UAAU;AAAA,IACvB,CAAC;AACH,aAAW,CAAC,MAAM,EAAE,KAAK,OAAO,QAAQ,OAAO,UAAU,SAAS,GAAG;AACnE,UAAM,QAAQ,MAAM,gBAAgB,OAAO,GAAG,KAAK,MAAM,GAAG,WAAW;AACvE,cAAU,KAAK;AAAA,MACb;AAAA,MACA,OAAO,QAAQ,IAAI;AAAA,MACnB,SAAS,GAAG;AAAA,MACZ,OACE,mBAAmB,IAAI,GAAG,UAAU,QACpC,mCAAmC,KAAK,GAAG,QAAQ;AAAA,MACrD,MAAM,GAAG;AAAA,MACT,KAAK,QAAQ,QAAQ,MAAM,GAAG,IAAI;AAAA,MAClC,WAAW,OAAO,UAAU;AAAA,IAC9B,CAAC;AAAA,EACH;AACA,SAAO,EAAE,SAAS,UAAU;AAC9B;AAWA,eAAsB,YAAY,IAAW,GAAiC;AAC5E,QAAM,MAAM,EAAE,KAAK,KAAK;AACxB,QAAM,WAAW,EAAE,UAAU,KAAK,EAAE,YAAY;AAChD,QAAM,MAAM,EAAE,KAAK,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI;AAC3C,MAAI,OAAO,CAAC,oBAAoB,KAAK,GAAG;AACtC,UAAM,IAAI,WAAW,6CAA6C;AACpE,MAAI,YAAY,CAAC,KAAK,KAAK,QAAQ;AACjC,UAAM,IAAI;AAAA,MACR,IAAI,QAAQ;AAAA,MACZ;AAAA,IACF;AACF,MAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAK,OAAM,IAAI,WAAW,gCAAgC;AAEpF,QAAM,SAAS,OAAO,YAAY,OAAO;AACzC,QAAM,IAAI,MAAM,QAAQ,IAAI,QAAQ,MAAM,MAAM,WAAW,MAAM,SAAY,KAAK;AAAA,IAChF,GAAI,OAAO,WAAW,EAAE,MAAM,SAAS,IAAI,CAAC;AAAA,EAC9C,CAAC;AACD,SAAO,gBAAgB,CAAC;AAC1B;AAGA,eAAsB,eAAe,IAAW,MAA+B;AAC7E,QAAM,QAAQ,KAAK,KAAK,EAAE,YAAY;AACtC,MAAI,CAAC,KAAK,KAAK,KAAK,EAAG,OAAM,IAAI,WAAW,IAAI,IAAI,0BAA0B;AAC9E,QAAM,QAAQ,aAAa,GAAG,GAAG;AACjC,QAAM,eACJ,UAAU,cAAc,QAAQ,MAAM,oBAAoB,eAAe,KAAK,GAAG,KAAK;AACxF,QAAM,aAAa,MAAM,SAAS,OAAO,KAAK;AAC9C,MAAI,CAAC,gBAAgB,CAAC;AACpB,WAAO,GAAG,KAAK;AACjB,SAAO,WAAW,KAAK,GAAG,aAAa,wBAAwB,EAAE;AACnE;","names":[]}
@@ -11,7 +11,9 @@ import {
11
11
  buildCodemap,
12
12
  clearOldToolResults,
13
13
  contextTokens,
14
+ emptyTelemetry,
14
15
  estimateTokens,
16
+ fold,
15
17
  git,
16
18
  helperRunner,
17
19
  parseFrontmatter,
@@ -32,13 +34,15 @@ import {
32
34
  transcriptFor,
33
35
  workerTools,
34
36
  writeJsonAtomic
35
- } from "./chunk-TZ3KO3PA.js";
37
+ } from "./chunk-N2WJ2YTX.js";
36
38
  import {
37
- ConfigError,
38
39
  loadConfig,
39
40
  parseDuration,
40
41
  runsDir
41
- } from "./chunk-Y74QSMFT.js";
42
+ } from "./chunk-S3TUDM5B.js";
43
+ import {
44
+ ConfigError
45
+ } from "./chunk-7PNGHZYH.js";
42
46
 
43
47
  // src/tui/attach.ts
44
48
  import { readFile, stat } from "fs/promises";
@@ -281,6 +285,7 @@ var CodeChat = class _CodeChat {
281
285
  this.models = (typeof refs === "string" ? [refs] : refs).map((r) => chatModel(r, config));
282
286
  this.contextUsed = this.baseTokens();
283
287
  events.onEvent((e) => {
288
+ fold(this.telemetry, e);
284
289
  this.onEvent(e);
285
290
  });
286
291
  }
@@ -301,6 +306,8 @@ var CodeChat = class _CodeChat {
301
306
  view;
302
307
  models;
303
308
  usd = 0;
309
+ /** Token, tool and turn totals for this conversation, folded from its events. */
310
+ telemetry = emptyTelemetry();
304
311
  /** Estimated tokens the next request starts with (system + tools + conversation). */
305
312
  contextUsed = 0;
306
313
  static async open(io, ask, model) {
@@ -697,6 +704,7 @@ ${this.ctx.redactor.text(transcriptFor(head))}`
697
704
  /** Forget the conversation (the codebase map and instructions stay). */
698
705
  clear() {
699
706
  this.history = [];
707
+ this.telemetry = emptyTelemetry();
700
708
  this.contextUsed = this.baseTokens();
701
709
  void this.ctx.runningContext?.archive();
702
710
  }
@@ -707,4 +715,4 @@ export {
707
715
  resolveModelLenient,
708
716
  CodeChat
709
717
  };
710
- //# sourceMappingURL=chunk-YMIRAOIB.js.map
718
+ //# sourceMappingURL=chunk-55VJCO7L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tui/attach.ts","../src/tui/code-chat.ts","../src/tui/undo.ts","../src/instructions/load.ts","../src/providers/profiles.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, isAbsolute, resolve } from 'node:path';\nimport type { ContentBlock, ImageMediaType } from '../providers/types.js';\n\nconst MEDIA: Record<string, ImageMediaType> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n};\nconst MAX_IMAGE_BYTES = 8 * 1024 * 1024;\n\n/**\n * Image paths in a message: plain, quoted, or as a terminal pastes a dragged file (spaces escaped\n * with backslashes). Paths that don't exist are ignored, so ordinary text never attaches anything.\n */\nexport function imagePathsIn(text: string): string[] {\n const re =\n /'([^']+\\.(?:png|jpe?g|gif|webp))'|\"([^\"]+\\.(?:png|jpe?g|gif|webp))\"|((?:\\\\ |[^\\s'\"])+\\.(?:png|jpe?g|gif|webp))/gi;\n const out: string[] = [];\n for (const m of text.matchAll(re)) {\n const p = (m[1] ?? m[2] ?? m[3] ?? '').replace(/\\\\ /g, ' ');\n if (p) out.push(p);\n }\n return out;\n}\n\n/** Read the images a message points at into content blocks; notes say what was skipped and why. */\nexport async function attachImages(\n text: string,\n cwd: string,\n): Promise<{ blocks: ContentBlock[]; attached: string[]; skipped: string[] }> {\n const blocks: ContentBlock[] = [];\n const attached: string[] = [];\n const skipped: string[] = [];\n for (const raw of imagePathsIn(text)) {\n const p = raw.startsWith('~/')\n ? resolve(homedir(), raw.slice(2))\n : isAbsolute(raw)\n ? raw\n : resolve(cwd, raw);\n const info = await stat(p).catch(() => undefined);\n if (!info?.isFile()) continue;\n if (info.size > MAX_IMAGE_BYTES) {\n skipped.push(`${basename(p)} (over 8 MB)`);\n continue;\n }\n const ext = p.split('.').pop()?.toLowerCase() ?? '';\n const mediaType = MEDIA[ext];\n if (!mediaType) continue;\n blocks.push({ type: 'image', mediaType, data: (await readFile(p)).toString('base64') });\n attached.push(basename(p));\n }\n return { blocks, attached, skipped };\n}\n","import { randomBytes } from 'node:crypto';\nimport { mkdir, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { buildCodemap, renderCodemap } from '../agent/codemap.js';\nimport { clearOldToolResults, contextTokens, transcriptFor } from '../agent/compaction.js';\nimport { runAgentLoop } from '../agent/loop.js';\nimport type { CliIO } from '../cli/io.js';\nimport { resolveRunDeps } from '../cli/run-deps.js';\nimport type { OmnexxConfig } from '../config/schema.js';\nimport { loadConfig } from '../config/load.js';\nimport { parseDuration } from '../config/duration.js';\nimport { EventLog, type OmnexxEvent } from '../core/events.js';\nimport { RunStore } from '../core/run-store.js';\nimport { RunningContext } from '../core/running-context.js';\nimport { readTextOr, writeJsonAtomic } from '../core/atomic.js';\nimport { runsDir } from '../core/paths.js';\nimport { QuotaLedger } from '../providers/quota-ledger.js';\nimport { restoreTree, snapshotTree } from './undo.js';\nimport { renderNotes } from '../core/notes.js';\nimport type { OmnexxPaths } from '../core/paths.js';\nimport { readRepoNotes, saveToRepoMemory } from '../core/repo-memory.js';\nimport { realClock, type Clock } from '../core/clock.js';\nimport { estimateTokens } from '../core/tokens.js';\nimport { loadInstructions, renderInstructions } from '../instructions/load.js';\nimport type { ResolvedModel } from '../providers/pricing.js';\nimport { resolveModelLenient } from '../providers/profiles.js';\nimport type { ContentBlock, Message, Provider } from '../providers/types.js';\nimport { scrubEnv } from '../security/env-scrub.js';\nimport { PathJail } from '../security/paths.js';\nimport { Redactor } from '../security/redact.js';\nimport { readOnlyTools, workerTools, toolSpec } from '../tools/registry.js';\nimport type { ToolWhere } from '../tools/extra/types.js';\nimport { helperRunner } from '../agent/subagent.js';\nimport type { TodoItem } from '../tools/todo.js';\nimport type { Tool, ToolContext } from '../tools/types.js';\nimport { runGates } from '../verify/gates.js';\nimport { emptyTelemetry, fold, type Telemetry } from '../telemetry/aggregate.js';\n\nexport const CHAT_SYSTEM = `You are Omnexx in chat mode: a coding agent working turn by turn with a person at the keyboard, directly in their repository (your cwd).\n\n- Do what they ask, then stop and reply briefly: what you changed and what you checked.\n- For anything with more than two steps, write a todo list first with the todo tool and keep it current: one item in_progress at a time, done as you finish.\n- Use the context tool to pull in wider context (general, repo, heat, recent) only when it helps; save what you learn about the codebase with context update.\n- Before each tool call, say in one short sentence what you are about to do and why.\n- Explore with the codebase map, outline, search and ranged reads. Edit with str_replace / multi_edit; write_file for new or small files.\n- Run the relevant tests with bash before you finish when you changed code.\n- The person may send messages while you work; they arrive marked as such. Read them and adjust at once.\n- Some actions need their OK (paths outside the repo, commands the policy refuses); they are asked for you. If they say no, find another way or explain.\n- Ask a question instead of guessing when the request is ambiguous and the choice matters.`;\n\n/** Prepended to a message in plan mode. */\nconst PLAN_MODE = `[Plan mode] Don't change anything yet: no edits and no commands. Investigate with the read-only tools, then reply with a concrete step-by-step plan: the files to change and how, the approach, how you will test it, and any risks or questions for me. Put the steps in the todo list. Stop after the plan; I'll approve it or ask for changes.`;\n\n/** Where a chat's conversation is saved, inside its store. */\nconst CHAT_FILE = 'chat.json';\n\n/** Tools chat leaves out: helpers need a run. */\nconst CHAT_DENIED = new Set(['task']);\n\n/** What the chat shows the person. */\nexport type ChatLine =\n | { kind: 'act'; text: string }\n | { kind: 'think'; text: string }\n | { kind: 'out'; text: string }\n | { kind: 'feed'; text: string }\n | { kind: 'err'; text: string };\n\nexport interface ChatView {\n line: (l: ChatLine) => void;\n /** Reply text as it streams in (cleared when the reply lands as an `out` line). */\n stream: (delta: string) => void;\n todo: (items: TodoItem[]) => void;\n}\n\nconst firstLine = (t: string): string =>\n t\n .split('\\n')\n .find((l) => l.trim())\n ?.trim() ?? '';\n\n/** The line of a failed action's output worth showing: the error, not the `[exit 1, …]` header. */\nexport function errorLine(t: string): string {\n const lines = t\n .split('\\n')\n .map((l) => l.trim())\n .filter(Boolean);\n const telling = lines.find(\n (l) =>\n !l.startsWith('[exit') &&\n /error|fail|not ok|assert|expected|cannot|denied|refused|✗/i.test(l),\n );\n return telling ?? lines.find((l) => !l.startsWith('[exit')) ?? lines[0] ?? '';\n}\n\n/** Price an unpriced model at $0 so chat works on any endpoint; spend then reads $0.00. */\nfunction chatModel(ref: string, config: OmnexxConfig): ResolvedModel {\n const m = resolveModelLenient(ref, config);\n return {\n ...m,\n price: m.price ?? {\n id: m.id,\n input: 0,\n output: 0,\n cache_write_5m: 0,\n cache_write_1h: 0,\n cache_read: 0,\n },\n };\n}\n\n/**\n * Turn-by-turn coding in the user's own checkout (no worktree, no rollback). The conversation\n * carries across messages; messages typed mid-turn reach the model at its next step; Esc ends a\n * turn cleanly; long conversations are trimmed, then summarized, between messages.\n */\nexport class CodeChat {\n private history: Message[] = [];\n /** Set by open(); where repo memory lives. */\n paths: OmnexxPaths | undefined;\n /** Before each message: the files as they were, and how long the conversation was. */\n private snapshots: { tree: string | undefined; historyLength: number }[] = [];\n private queue: string[] = [];\n private abort: AbortController | undefined;\n private view: ChatView | undefined;\n private models: ResolvedModel[];\n usd = 0;\n /** Token, tool and turn totals for this conversation, folded from its events. */\n telemetry: Telemetry = emptyTelemetry();\n /** Estimated tokens the next request starts with (system + tools + conversation). */\n contextUsed = 0;\n\n private constructor(\n private readonly config: OmnexxConfig,\n private readonly provider: Provider,\n private readonly tools: readonly Tool[],\n private readonly system: { text: string; cacheBreakpoint?: boolean }[],\n private readonly ctx: ToolContext,\n private readonly events: EventLog,\n private readonly store: RunStore,\n model: string | undefined,\n ) {\n const refs = model ? [model] : config.models.worker;\n this.models = (typeof refs === 'string' ? [refs] : refs).map((r) => chatModel(r, config));\n this.contextUsed = this.baseTokens();\n // One listener for the whole chat: every action shows as a line while it happens.\n events.onEvent((e) => {\n fold(this.telemetry, e);\n this.onEvent(e);\n });\n }\n\n static async open(\n io: CliIO,\n ask: (q: string) => Promise<boolean>,\n model?: string,\n ): Promise<CodeChat> {\n // Build the provider for the chat model only (not every role's), so chatting on one\n // endpoint never needs keys for the others.\n const { config: base } = await loadConfig({ cwd: io.cwd, env: io.env });\n const ref = model ?? base.models.chat;\n const { config, paths, deps } = await resolveRunDeps(\n io,\n io.cwd,\n ref ? { models: { planner: ref, worker: ref, cheap: ref } } : {},\n );\n const store = new RunStore(\n paths,\n `chat_${new Date().toISOString().replace(/\\D/g, '').slice(0, 14)}_${randomBytes(3).toString('hex')}`,\n );\n await mkdir(store.logsDir, { recursive: true, mode: 0o700 });\n const redactor = Redactor.fromEnv(io.env, deps.secrets);\n const clock = io.clock ?? realClock;\n const events = new EventLog(store.eventsPath, store.runId, redactor, clock);\n const tmp = join('/tmp', `omnexx-${store.runId}`);\n await mkdir(tmp, { recursive: true, mode: 0o700 });\n const jail = new PathJail(io.cwd);\n let n = 0;\n const ctx: ToolContext = {\n jail,\n env: scrubEnv(io.env, { passthrough: config.policy.env_passthrough, set: { TMPDIR: tmp } }),\n store,\n events,\n redactor,\n policy: {\n root: jail.root,\n home: io.env.HOME ?? '/nonexistent',\n allowNetwork: config.policy.allow_network,\n extraDeny: config.policy.deny,\n scratch: [tmp, `/private${tmp}`],\n },\n cycle: 0,\n maxCmdTimeoutMs: parseDuration(config.budget.max_cmd_timeout),\n notesMaxTokens: config.context.notes_max_tokens,\n today: new Date(clock.now()).toISOString().slice(0, 10),\n nextCommandId: () => `cmd-chat-${++n}`,\n edited: new Set(),\n reads: new Map(),\n ask,\n runningContext: new RunningContext(store, 'chat'),\n };\n const tools = (await workerTools(config, { repoRoot: jail.root, env: io.env })).filter(\n (t) => !CHAT_DENIED.has(t.name),\n );\n const instructions = renderInstructions(await loadInstructions(jail.root, jail.root));\n const codemap = renderCodemap(\n await buildCodemap(jail.root),\n config.context.repo_map_max_tokens,\n );\n // What earlier runs and chats learned about this repo; the remember tool adds to it.\n const known = await readRepoNotes(paths, jail.root);\n if (known.length) await store.writeNotes(known);\n const system = [\n { text: CHAT_SYSTEM },\n ...(instructions ? [{ text: instructions }] : []),\n ...(known.length ? [{ text: `# Lessons about this repo\\n\\n${renderNotes(known)}` }] : []),\n { text: codemap, cacheBreakpoint: true },\n ];\n const chat = new CodeChat(config, deps.provider, tools, system, ctx, events, store, ref);\n chat.paths = paths;\n // Chat gets helpers too (the task and agent tools), on the chat model and the chat's spend.\n ctx.subagent = chat.helpers({ repoRoot: jail.root, env: io.env }, clock);\n return chat;\n }\n\n /** Read-only helpers that run on this chat's model, budget and spend. */\n helpers(where: ToolWhere, clock: Clock): NonNullable<ToolContext['subagent']> {\n const cfg = this.config.context;\n return helperRunner(\n {\n tools: () => readOnlyTools(this.config, where),\n models: () => Promise.resolve(this.models),\n maxTokens: Math.min(this.config.providers.anthropic.max_tokens, 4_096),\n limits: { tokens: cfg.subagent_max_tokens, turns: cfg.subagent_max_turns },\n loop: {\n provider: this.provider,\n budget: this.config.budget,\n clock,\n events: this.events,\n spentUsd: () => this.usd,\n onUsage: (_u, usd) => {\n this.usd += usd;\n return Promise.resolve();\n },\n control: () => Promise.resolve(this.paused ? 'pause' : 'continue'),\n pausePollMs: 300,\n },\n },\n this.ctx,\n );\n }\n\n /** \"provider:model\" of the model chat talks to. */\n get model(): string {\n const m = this.models[0];\n return m ? `${m.provider}:${m.alias}` : '?';\n }\n\n /** The conversation so far, to carry into a chat on another model (/model). */\n get conversation(): Message[] {\n return this.history;\n }\n\n set conversation(m: Message[]) {\n this.history = m;\n this.contextUsed = this.baseTokens() + contextTokens(m);\n }\n\n get busy(): boolean {\n return this.abort !== undefined;\n }\n\n /** A message typed while the agent works: it joins the conversation at the next step. */\n steer(text: string): void {\n this.queue.push(text);\n }\n\n /** Messages that arrived after the agent's last step: the session sends them next. */\n takeLeftovers(): string[] {\n return this.queue.splice(0);\n }\n\n /** /pause: hold at the next step boundary until resume() (the loop polls control()). */\n paused = false;\n\n /** Esc: end the current turn, keeping every completed step. */\n interrupt(): void {\n this.abort?.abort();\n }\n\n /** The context limit the chat compacts at. */\n get contextLimit(): number {\n return this.config.context.compact_at;\n }\n\n private baseTokens(): number {\n return estimateTokens(JSON.stringify([this.system, this.tools.map(toolSpec)]));\n }\n\n private onEvent(e: OmnexxEvent): void {\n const v = this.view;\n if (!v) return;\n const str = (x: unknown): string => (typeof x === 'string' ? x : '');\n switch (e.type) {\n case 'tool.start':\n v.line({ kind: 'act', text: str(e.doing) });\n return;\n case 'tool.call':\n if (e.isError)\n v.line({ kind: 'err', text: ` ✗ ${errorLine(str(e.error)).slice(0, 160)}` });\n return;\n case 'agent.thinking':\n v.line({ kind: 'think', text: firstLine(str(e.text)).slice(0, 200) });\n return;\n case 'todo.update':\n v.todo(Array.isArray(e.items) ? (e.items as TodoItem[]) : []);\n return;\n case 'steer.applied':\n v.line({ kind: 'feed', text: 'read your new message' });\n return;\n case 'context.cleared':\n v.line({ kind: 'feed', text: 'trimmed old tool output to keep the context small' });\n return;\n case 'provider.retry':\n v.line({\n kind: 'feed',\n text: `waiting for the model provider (${str(e.error).slice(0, 80)})`,\n });\n return;\n case 'provider.failover':\n v.line({\n kind: 'feed',\n text: `switching from ${str(e.provider)} (${str(e.error).slice(0, 60)})`,\n });\n return;\n default:\n }\n }\n\n /** One message: the agent works until it replies. */\n async send(\n text: string,\n view: ChatView,\n opts: { plan?: boolean; images?: ContentBlock[] } = {},\n ): Promise<void> {\n // Plan mode: read-only tools, and the ask goes in the message (the cached prefix is unchanged).\n const tools = opts.plan ? this.tools.filter((t) => t.readOnly) : this.tools;\n const ask = opts.plan ? `${PLAN_MODE}\\n\\n${text}` : text;\n this.view = view;\n this.snapshots.push({\n tree: await snapshotTree(this.ctx.jail.root),\n historyLength: this.history.length,\n });\n this.ctx.edited.clear();\n this.ctx.reads?.clear();\n await this.compactIfNeeded();\n const abort = new AbortController();\n this.abort = abort;\n try {\n const result = await runAgentLoop(\n {\n system: this.system,\n history: this.history,\n first: { role: 'user', content: [{ type: 'text', text: ask }, ...(opts.images ?? [])] },\n tools: tools.map(toolSpec),\n },\n {\n provider: this.provider,\n models: this.models,\n ...(this.paths\n ? (() => {\n const quota = new QuotaLedger(join(this.paths.configHome, 'quota.json'));\n return {\n modelExhausted: (r: string) => quota.exhaustedAt(r) !== undefined,\n markExhausted: (r: string) => {\n quota.mark(r);\n },\n };\n })()\n : {}),\n tools,\n toolCtx: this.ctx,\n budget: { ...this.config.budget, max_turns_per_cycle: 200 },\n maxTokens: this.config.providers.anthropic.max_tokens,\n clock: realClock,\n events: this.events,\n spentUsd: () => this.usd,\n onUsage: (_u, usd) => {\n this.usd += usd;\n return Promise.resolve();\n },\n control: () => Promise.resolve(this.paused ? 'pause' : 'continue'),\n pausePollMs: 300,\n signal: abort.signal,\n onDelta: (d) => {\n if (d.text) view.stream(d.text);\n },\n pendingInput: () => this.queue.splice(0),\n // Within one long turn only old tool output is trimmed; summaries happen between turns.\n compaction: {\n settings: {\n clearAt: this.config.context.clear_tool_results_at,\n keepToolResults: this.config.context.keep_tool_results,\n compactAt: Number.MAX_SAFE_INTEGER,\n keepTurns: this.config.context.compact_keep_turns,\n },\n },\n },\n );\n this.history = result.messages;\n view.stream('');\n if (result.finalText) view.line({ kind: 'out', text: result.finalText });\n if (result.end === 'stop-now')\n view.line({ kind: 'feed', text: 'stopped. tell me what to do instead' });\n else if (result.end !== 'done') view.line({ kind: 'err', text: `(stopped: ${result.end})` });\n } finally {\n this.abort = undefined;\n this.paused = false;\n this.contextUsed = this.baseTokens() + contextTokens(this.history);\n }\n if (this.ctx.edited.size && this.config.gates.length) await this.check(view);\n await this.rememberForRepo();\n await this.persist();\n }\n\n /**\n * /undo: put the files back as they were before the last message and drop that exchange.\n * Returns the paths restored, or undefined when there is nothing to undo.\n */\n async undo(): Promise<string[] | undefined> {\n const snap = this.snapshots.pop();\n if (!snap) return undefined;\n const touched = snap.tree ? await restoreTree(this.ctx.jail.root, snap.tree) : [];\n this.history = this.history.slice(0, snap.historyLength);\n this.contextUsed = this.baseTokens() + contextTokens(this.history);\n await this.persist();\n return touched;\n }\n\n /** Save the conversation so `omnexx --continue` can pick it up. */\n private async persist(): Promise<void> {\n await writeJsonAtomic(this.store.file(CHAT_FILE), {\n cwd: this.ctx.jail.root,\n model: this.model,\n savedAt: Date.now(),\n history: this.history,\n }).catch(() => undefined);\n }\n\n /** Load the newest saved chat for this folder into this one (--continue). Returns its length. */\n async resumeLatest(): Promise<number> {\n if (!this.paths) return 0;\n const dirs = (await readdir(runsDir(this.paths)).catch(() => [] as string[]))\n .filter((d) => d.startsWith('chat_') && d !== this.store.runId)\n .sort()\n .reverse();\n for (const d of dirs) {\n const raw = await readTextOr(join(runsDir(this.paths), d, CHAT_FILE), '');\n if (!raw) continue;\n const saved = JSON.parse(raw) as { cwd?: string; history?: Message[] };\n if (saved.cwd !== this.ctx.jail.root || !saved.history?.length) continue;\n this.conversation = saved.history;\n return saved.history.filter(\n (m) => m.role === 'user' && m.content.some((b) => b.type === 'text'),\n ).length;\n }\n return 0;\n }\n\n /** Lessons the agent recorded this message become part of the repo's memory. */\n private async rememberForRepo(): Promise<void> {\n if (!this.paths) return;\n await saveToRepoMemory(\n this.paths,\n this.ctx.jail.root,\n await this.store.readNotes(),\n this.config.context.notes_max_tokens,\n ).catch(() => undefined);\n }\n\n /** Between messages: trim old tool output, then summarize the oldest exchanges if still big. */\n private async compactIfNeeded(force = false): Promise<void> {\n const limit = this.config.context.compact_at;\n if (!force && contextTokens(this.history) < this.config.context.clear_tool_results_at) return;\n this.history = clearOldToolResults(\n this.history,\n this.config.context.keep_tool_results,\n ).messages;\n if (!force && contextTokens(this.history) < limit) return;\n // Keep the last few exchanges verbatim; an exchange starts at a user message with text.\n const starts = this.history.flatMap((m, i) =>\n m.role === 'user' && m.content.some((b) => b.type === 'text') ? [i] : [],\n );\n const cut = starts.at(-Math.max(1, this.config.context.compact_keep_turns));\n if (cut === undefined || cut === 0) return;\n const head = this.history.slice(0, cut);\n const summary = await this.summarize(head).catch(() => undefined);\n // Your own running context survives the summary verbatim: it's where the work stands.\n const running = (await this.ctx.runningContext?.forPrompt()) ?? '';\n if (!summary) return;\n this.history = [\n {\n role: 'user',\n content: [\n {\n type: 'text',\n text: `Summary of our conversation so far:\\n${summary}${running ? `\\n\\n${running}` : ''}`,\n },\n ],\n },\n {\n role: 'assistant',\n content: [{ type: 'text', text: 'Understood; continuing from there.' }],\n },\n ...this.history.slice(cut),\n ];\n this.view?.line({\n kind: 'feed',\n text: 'summarized the earlier conversation to free up context',\n });\n this.contextUsed = this.baseTokens() + contextTokens(this.history);\n }\n\n /** /compact. */\n async compact(view: ChatView): Promise<void> {\n this.view = view;\n await this.compactIfNeeded(true);\n }\n\n private async summarize(head: readonly Message[]): Promise<string | undefined> {\n const m = this.models[0];\n if (!m) return undefined;\n const res = await this.provider.complete({\n model: m.id,\n route: m.provider,\n system: [\n { text: 'You compress a coding conversation so it can continue without the transcript.' },\n ],\n tools: [],\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'text',\n text: `Summarize: what the user asked for and decided, what was changed (file paths), what was verified, and what is still open. Concrete and brief.\\n\\n${this.ctx.redactor.text(transcriptFor(head))}`,\n },\n ],\n },\n ],\n maxTokens: 1_500,\n messageBreakpoints: [],\n });\n const t = res.content\n .flatMap((b) => (b.type === 'text' ? [b.text] : []))\n .join('\\n')\n .trim();\n return t || undefined;\n }\n\n private async check(view: ChatView): Promise<void> {\n view.line({ kind: 'act', text: 'running your checks' });\n const results = await runGates(this.config.gates, {\n cwd: this.ctx.jail.root,\n env: this.ctx.env,\n logsDir: this.store.logsDir,\n label: `chat${Date.now()}`,\n maxCmdTimeoutMs: this.ctx.maxCmdTimeoutMs,\n redact: (s) => this.ctx.redactor.text(s),\n });\n const line = results\n .map(\n (r) =>\n `${r.name} ${r.exitCode === 0 ? '✓' : `✗${r.failures.length ? ` (${r.failures.length})` : ''}`}`,\n )\n .join(' ');\n view.line({\n kind: results.every((r) => r.exitCode === 0) ? 'feed' : 'err',\n text: `checks ${line}`,\n });\n }\n\n /** Forget the conversation (the codebase map and instructions stay). */\n clear(): void {\n this.history = [];\n this.telemetry = emptyTelemetry();\n this.contextUsed = this.baseTokens();\n // A fresh conversation starts a fresh running context; the old one is kept with the chat.\n void this.ctx.runningContext?.archive();\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport { rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { git } from '../git/git.js';\n\n/**\n * The working tree (tracked and untracked, not ignored files) as a git tree object, written\n * through a throwaway index so the person's index, stash and branches are never touched.\n * Undefined outside a git repository.\n */\nexport async function snapshotTree(root: string): Promise<string | undefined> {\n const index = join(tmpdir(), `omnexx-snap-${randomUUID()}`);\n const env = { GIT_INDEX_FILE: index };\n try {\n const add = await git(root, ['add', '-A'], { allowFailure: true, env });\n if (add.exitCode !== 0) return undefined;\n const tree = await git(root, ['write-tree'], { allowFailure: true, env });\n return tree.exitCode === 0 ? tree.stdout.trim() : undefined;\n } finally {\n await rm(index, { force: true });\n }\n}\n\n/**\n * Put the working tree back to `tree`: changed and deleted files are restored, files created\n * since are removed. Returns the paths it touched.\n */\nexport async function restoreTree(root: string, tree: string): Promise<string[]> {\n const now = await snapshotTree(root);\n if (!now || now === tree) return [];\n const diff = await git(root, ['diff', '--name-status', '--no-renames', '-z', tree, now]);\n const parts = diff.stdout.split('\\0').filter(Boolean);\n const touched: string[] = [];\n const restore: string[] = [];\n for (let i = 0; i + 1 < parts.length; i += 2) {\n const status = parts[i];\n const path = parts[i + 1] ?? '';\n touched.push(path);\n if (status === 'A') await rm(join(root, path), { force: true });\n else restore.push(path);\n }\n if (restore.length)\n await git(root, ['restore', `--source=${tree}`, '--worktree', '--', ...restore]);\n return touched;\n}\n","import { readdir } from 'node:fs/promises';\nimport { isAbsolute, join, relative, sep } from 'node:path';\nimport { CHARS_PER_TOKEN } from '../core/tokens.js';\nimport { parseFrontmatter } from './frontmatter.js';\nimport { readTextIfExists } from './read.js';\n\n/** Total instruction budget across every file, in estimated tokens. */\nexport const INSTRUCTIONS_MAX_TOKENS = 8_000;\n\nconst TRUNCATE_MARKER = '… (truncated)';\n\nexport interface InstructionFile {\n /** Repo-relative POSIX path, used as the section header (and in the truncation note). */\n path: string;\n /** Raw file text; rendering trims it. Cut by the budget before it gets here. */\n text: string;\n}\n\nexport interface LoadedInstructions {\n /**\n * Render order, which is also the precedence order: the first file survives longest when the\n * 8k-token budget cuts. Root files in the documented order, then nested files shallow → deep.\n */\n files: InstructionFile[];\n /** Files dropped or cut to fit the budget, lowest precedence first. */\n truncated: string[];\n}\n\n/** Root files in precedence order; nested files are appended after these. */\nconst ROOT_FILES = ['OMNEXX.md', 'AGENTS.md', 'CLAUDE.md'] as const;\nconst NESTED_FILES = ['AGENTS.md', 'CLAUDE.md'] as const;\n\nlet last: LoadedInstructions | undefined;\n\n/** Directories between the repo root and cwdInRepo, shallow first; [] when cwd is outside. */\nfunction nestedDirs(repoRoot: string, cwdInRepo: string): string[] {\n const rel = relative(repoRoot, cwdInRepo);\n if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) return [];\n const dirs: string[] = [];\n let acc = '';\n for (const part of rel.split(sep).filter(Boolean)) {\n acc = acc === '' ? part : join(acc, part);\n dirs.push(acc);\n }\n return dirs;\n}\n\nfunction compose(files: readonly InstructionFile[], truncated: readonly string[]): string {\n if (files.length === 0) return '';\n const parts = ['# Project instructions', ''];\n for (const f of files) parts.push(`### ${f.path}`, '', f.text.trim(), '');\n let out = parts.join('\\n').trimEnd();\n if (truncated.length > 0) {\n out += `\\n\\n> Note: project instructions were truncated to fit the ${INSTRUCTIONS_MAX_TOKENS}-token budget; cut: ${truncated.join(', ')}`;\n }\n return out;\n}\n\n/**\n * Read every project instruction file relevant to cwdInRepo: the root files in the documented\n * precedence order, `.cursor/rules/*.mdc` (frontmatter stripped, sorted by name), and nested\n * AGENTS.md/CLAUDE.md in the parent directories of cwdInRepo. The combined render is capped at\n * 8k tokens, cutting the lowest precedence (last rendered) files first and noting every cut.\n * The same repo and cwd always produce byte-identical output, so the prompt prefix stays stable.\n */\nexport async function loadInstructions(\n repoRoot: string,\n cwdInRepo: string,\n): Promise<LoadedInstructions> {\n const files: InstructionFile[] = [];\n const push = async (path: string, file: string, frontmatter = false): Promise<void> => {\n const raw = await readTextIfExists(file);\n if (raw === undefined) return;\n const text = frontmatter ? parseFrontmatter(raw).body : raw;\n if (text.trim()) files.push({ path, text });\n };\n for (const name of ROOT_FILES) await push(name, join(repoRoot, name));\n const rulesDir = join(repoRoot, '.cursor', 'rules');\n const rules = await readdir(rulesDir).catch(() => []);\n for (const name of [...rules]\n .filter((n) => n.endsWith('.mdc'))\n .sort((a, b) => a.localeCompare(b))) {\n await push(`.cursor/rules/${name}`, join(rulesDir, name), true);\n }\n await push(\n '.github/copilot-instructions.md',\n join(repoRoot, '.github', 'copilot-instructions.md'),\n );\n for (const dir of nestedDirs(repoRoot, cwdInRepo)) {\n const posixDir = dir.split(sep).join('/');\n for (const name of NESTED_FILES) await push(`${posixDir}/${name}`, join(repoRoot, dir, name));\n }\n\n const budget = INSTRUCTIONS_MAX_TOKENS * CHARS_PER_TOKEN;\n const truncated: string[] = [];\n let out = compose(files, truncated);\n while (out.length > budget && files.length > 1) {\n const dropped = files.pop();\n if (dropped) truncated.push(dropped.path);\n out = compose(files, truncated);\n }\n if (out.length > budget) {\n // One file bigger than the whole budget: keep its head instead of dropping it.\n const only = files[0];\n if (only) {\n truncated.push(only.path);\n let text = only.text;\n for (;;) {\n only.text = text;\n out = compose(files, truncated);\n if (out.length <= budget) break;\n const keep = text.length - (out.length - budget) - TRUNCATE_MARKER.length - 1;\n const next = `${text.slice(0, Math.max(0, keep))}\\n${TRUNCATE_MARKER}`;\n if (next === text) break;\n text = next;\n }\n }\n }\n const loaded: LoadedInstructions = { files, truncated };\n last = loaded;\n return loaded;\n}\n\n/**\n * Render the instructions for the prompt prefix. Zero-arg form uses the result of the last\n * `loadInstructions` call (prompts.ts wires this); pass an explicit value in tests. Returns ''\n * when nothing was loaded, so callers can skip the block entirely.\n */\nexport function renderInstructions(\n loaded: LoadedInstructions = last ?? { files: [], truncated: [] },\n): string {\n return compose(loaded.files, loaded.truncated);\n}\n","import { ConfigError } from '../errors.js';\nimport type { OmnexxConfig, PriceConfig } from '../config/schema.js';\nimport { BUILTIN_PRICING, resolveModel, type ResolvedModel } from './pricing.js';\n\n/** A resolved model whose price may be unknown (unpriced). */\nexport interface PricedModel extends Omit<ResolvedModel, 'price'> {\n price: PriceConfig | undefined;\n}\n\nexport interface CostBreakdown {\n usd: number | undefined;\n unpriced: boolean;\n}\n\n/**\n * Lenient sibling of `resolveModel` (src/providers/pricing.ts): never throws for a\n * missing price. Unknown models resolve to their literal id with `price: undefined`\n * so tokens are still counted while cost stays unknown. Unknown providers still\n * throw: that is a config error, not a pricing gap. Run-deps and the loop can\n * switch to this resolver (see INTEGRATION note in docs/integration-notes.md).\n */\nexport function resolveModelLenient(ref: string, config: OmnexxConfig): PricedModel {\n if (!/^[a-z][a-z0-9_-]*:\\S+$/.test(ref))\n throw new ConfigError(`bad model ref \"${ref}\"`, 'expected \"<provider>:<alias-or-model-id>\"');\n try {\n const m = resolveModel(ref, config);\n return m;\n } catch (err) {\n if (!(err instanceof ConfigError)) throw err;\n // Unknown provider (\"unknown provider ...\") or unknown price (\"no price for\n // model ...\"): both resolve leniently. Only malformed refs still throw, and\n // resolveModel throws those as ConfigError too, so check the prefix.\n if (\n !err.message.startsWith('no price for model') &&\n !err.message.startsWith('unknown provider')\n )\n throw err;\n const cut = ref.indexOf(':');\n const provider = ref.slice(0, cut);\n const name = ref.slice(cut + 1);\n const known = Object.entries({ ...BUILTIN_PRICING, ...config.pricing }).find(\n ([, p]) => p.id === name,\n );\n if (known) return { provider, alias: known[0], id: name, price: known[1] };\n return { provider, alias: name, id: name, price: undefined };\n }\n}\n\n/** A role's failover chain, in order, never throwing for missing prices. */\nexport function resolveChainLenient(\n refs: string | readonly string[],\n config: OmnexxConfig,\n): PricedModel[] {\n return (typeof refs === 'string' ? [refs] : refs).map((r) => resolveModelLenient(r, config));\n}\n\n/** Cost in USD, or undefined when the model has no price. Tokens are always counted. */\nexport function costOf(\n usage: {\n uncached: number;\n cacheWrite5m: number;\n cacheWrite1h: number;\n cacheRead: number;\n output: number;\n },\n price: PriceConfig | undefined,\n): CostBreakdown {\n if (!price) return { usd: undefined, unpriced: true };\n const M = 1_000_000;\n return {\n usd:\n (usage.uncached * price.input +\n usage.cacheWrite5m * price.cache_write_5m +\n usage.cacheWrite1h * price.cache_write_1h +\n usage.cacheRead * price.cache_read +\n usage.output * price.output) /\n M,\n unpriced: false,\n };\n}\n\n/** Display for a cost that may be unknown: \"$1.2345\" or \"\\u2013\". */\nexport function formatCost(usd: number | undefined): string {\n return usd === undefined ? '\\u2013' : `$${usd.toFixed(4)}`;\n}\n\n/** Worst-case cost for pre-flight, or undefined when unpriced (token caps apply). */\nexport function worstCaseOf(\n inputTokens: number,\n maxOutput: number,\n price: PriceConfig | undefined,\n): number | undefined {\n if (!price) return undefined;\n const M = 1_000_000;\n const inRate = Math.max(price.input, price.cache_write_5m, price.cache_write_1h);\n return (inputTokens * inRate + maxOutput * price.output) / M;\n}\n"],"mappings":"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","names":["readdir","join","isAbsolute","join","isAbsolute","join","join","readdir"]}