talon-agent 3.8.1 → 3.9.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 (55) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -2
  3. package/src/backend/claude-sdk/handler.ts +1 -1
  4. package/src/backend/codex/handler/message.ts +1 -1
  5. package/src/backend/kilo/handler/index.ts +1 -2
  6. package/src/backend/kilo/handler/state.ts +3 -8
  7. package/src/backend/kilo/index.ts +2 -28
  8. package/src/backend/openai-agents/handler/message.ts +1 -1
  9. package/src/backend/openai-agents/session.ts +1 -1
  10. package/src/backend/opencode/handler/index.ts +1 -2
  11. package/src/backend/opencode/handler/state.ts +3 -8
  12. package/src/backend/opencode/index.ts +0 -7
  13. package/src/backend/shared/delivery.ts +1 -1
  14. package/src/backend/shared/handle-retry.ts +1 -1
  15. package/src/backend/shared/index.ts +3 -2
  16. package/src/backend/shared/turn-interrupt.ts +1 -1
  17. package/src/cli/index.ts +0 -8
  18. package/src/core/bus/index.ts +1 -9
  19. package/src/core/mesh/registry.ts +106 -4
  20. package/src/core/mesh/service.ts +22 -7
  21. package/src/core/mesh/transfers.ts +33 -4
  22. package/src/core/plugin/index.ts +0 -9
  23. package/src/core/prompt/index.ts +1 -16
  24. package/src/core/vfs/index.ts +2 -21
  25. package/src/core/weaver/index.ts +5 -9
  26. package/src/frontend/discord/actions/messaging.ts +73 -13
  27. package/src/frontend/discord/actions/shared.ts +21 -3
  28. package/src/frontend/discord/commands/admin.ts +1 -1
  29. package/src/frontend/discord/errors.ts +16 -5
  30. package/src/frontend/discord/formatting.ts +19 -4
  31. package/src/frontend/native/index.ts +22 -8
  32. package/src/frontend/native/protocol.ts +6 -0
  33. package/src/frontend/native/server.ts +171 -12
  34. package/src/frontend/shared/format.ts +6 -0
  35. package/src/frontend/telegram/actions/messaging.ts +120 -24
  36. package/src/frontend/telegram/callbacks/metrics.ts +1 -1
  37. package/src/frontend/telegram/commands/admin.ts +1 -1
  38. package/src/frontend/telegram/formatting.ts +64 -19
  39. package/src/frontend/terminal/commands.ts +46 -8
  40. package/src/frontend/terminal/input.ts +110 -12
  41. package/src/frontend/terminal/renderer.ts +2 -0
  42. package/src/{util → storage}/metrics.ts +1 -1
  43. package/src/storage/scheduled-store.ts +6 -3
  44. package/src/util/config.ts +8 -0
  45. package/src/util/log.ts +1 -0
  46. package/src/backend/codex/index.ts +0 -33
  47. package/src/backend/openai-agents/index.ts +0 -48
  48. package/src/cli/plugin-entries.ts +0 -104
  49. package/src/core/agent-runtime/index.ts +0 -62
  50. package/src/core/background/index.ts +0 -21
  51. package/src/core/engine/index.ts +0 -20
  52. package/src/core/models/index.ts +0 -17
  53. package/src/core/soul/index.ts +0 -140
  54. package/src/core/tools/mcp-server.ts +0 -111
  55. /package/src/{backend/shared → util}/session-name.ts +0 -0
@@ -15,7 +15,15 @@ import {
15
15
  resolveModel as coreResolveModel,
16
16
  getModels,
17
17
  } from "../../core/models/catalog.js";
18
- import { buildCacheDisplay } from "../shared/status-context.js";
18
+ import {
19
+ buildCacheDisplay,
20
+ buildContextDisplay,
21
+ } from "../shared/status-context.js";
22
+ import {
23
+ formatDuration,
24
+ formatTokenCount,
25
+ formatUsd,
26
+ } from "../shared/format.js";
19
27
  import {
20
28
  getChatSettings,
21
29
  setChatModel,
@@ -282,7 +290,7 @@ export function registerBuiltinCommands(): void {
282
290
  let displayCacheRead = u.totalCacheRead;
283
291
  let displayCacheWrite = u.totalCacheWrite;
284
292
  let backendModelLine = "";
285
- let backendContextLine = "";
293
+ let contextWindow = u.contextWindow;
286
294
  ctx.renderer.writeln();
287
295
  const nameStr = info.sessionName ? `"${info.sessionName}" · ` : "";
288
296
 
@@ -313,25 +321,55 @@ export function registerBuiltinCommands(): void {
313
321
  if (modelInfo) {
314
322
  backendModelLine = ` ${pc.bold(label)} ${modelInfo.displayName} · ${modelInfo.providerName}${modelInfo.free ? " · free" : ""}`;
315
323
  if (modelInfo.contextWindow) {
316
- backendContextLine = cache
317
- ? ` ${pc.dim(`context window ${modelInfo.contextWindow.toLocaleString()} · cache ${cache.read.toLocaleString()} / ${cache.write.toLocaleString()}`)}`
318
- : ` ${pc.dim(`context window ${modelInfo.contextWindow.toLocaleString()}`)}`;
324
+ contextWindow ||= modelInfo.contextWindow;
319
325
  }
320
326
  }
321
327
  }
322
328
 
329
+ const context = buildContextDisplay({
330
+ contextTokens: u.contextTokens,
331
+ lastPromptTokens: u.lastPromptTokens,
332
+ contextWindow,
333
+ });
334
+ const contextUsed = context.known
335
+ ? formatTokenCount(context.used)
336
+ : "unknown";
337
+ const contextMax = context.max
338
+ ? formatTokenCount(context.max)
339
+ : "unknown";
340
+ const avgResponseMs =
341
+ info.turns > 0 && u.totalResponseMs
342
+ ? Math.round(u.totalResponseMs / info.turns)
343
+ : 0;
344
+ const fastestResponseMs =
345
+ Number.isFinite(u.fastestResponseMs) && u.fastestResponseMs > 0
346
+ ? u.fastestResponseMs
347
+ : 0;
348
+
323
349
  ctx.renderer.writeln(
324
350
  ` ${pc.bold("Session")} ${nameStr}turns ${info.turns}${cache ? ` · ${cache.hitPct}% cache` : ""}`,
325
351
  );
326
352
  ctx.renderer.writeln(
327
353
  ` ${pc.dim(`in ${displayInputTokens.toLocaleString()} · out ${displayOutputTokens.toLocaleString()} tokens`)}`,
328
354
  );
355
+ ctx.renderer.writeln();
356
+ ctx.renderer.writeln(
357
+ ` ${pc.bold("Context")} ${contextUsed} / ${contextMax} (${context.known ? `${context.pct}%` : "unknown"})${context.warn ? pc.yellow(" nearing limit") : ""}`,
358
+ );
359
+ ctx.renderer.writeln(
360
+ ` ${context.warn ? pc.yellow(context.bar) : pc.dim(context.bar)}`,
361
+ );
362
+ ctx.renderer.writeln(
363
+ ` ${pc.dim(`response last ${u.lastResponseMs ? formatDuration(u.lastResponseMs) : "—"} · avg ${avgResponseMs ? formatDuration(avgResponseMs) : "—"} · best ${fastestResponseMs ? formatDuration(fastestResponseMs) : "—"}`)}`,
364
+ );
365
+ if (u.estimatedCostUsd > 0) {
366
+ ctx.renderer.writeln(
367
+ ` ${pc.dim(`estimated session cost ${formatUsd(u.estimatedCostUsd)}`)}`,
368
+ );
369
+ }
329
370
  if (backendModelLine) {
330
371
  ctx.renderer.writeln();
331
372
  ctx.renderer.writeln(backendModelLine);
332
- if (backendContextLine) {
333
- ctx.renderer.writeln(backendContextLine);
334
- }
335
373
  }
336
374
 
337
375
  const plugins = getLoadedPlugins();
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Input is a list of parts: text segments and collapsed paste blocks.
5
5
  * You can type, paste, type more, paste again. Backspace removes from the end.
6
- * Enter submits everything. Ctrl+U clears all.
6
+ * Enter submits everything. Ctrl+U clears all. Up/Down walk prompt history.
7
7
  */
8
8
 
9
9
  import pc from "picocolors";
@@ -19,9 +19,10 @@ export type InputHandler = {
19
19
  resume(): void;
20
20
  };
21
21
 
22
- type TextPart = { type: "text"; content: string };
23
- type PastePart = { type: "paste"; content: string };
24
- type Part = TextPart | PastePart;
22
+ export type InputPart =
23
+ { type: "text"; content: string } | { type: "paste"; content: string };
24
+ type TextPart = Extract<InputPart, { type: "text" }>;
25
+ type PastePart = Extract<InputPart, { type: "paste" }>;
25
26
 
26
27
  // ── Constants ────────────────────────────────────────────────────────────────
27
28
 
@@ -29,6 +30,79 @@ const PASTE_COLLAPSE_LINES = 3;
29
30
  const PASTE_COLLAPSE_CHARS = 150;
30
31
  const PASTE_START = "\x1b[200~";
31
32
  const PASTE_END = "\x1b[201~";
33
+ const MAX_HISTORY_ITEMS = 100;
34
+
35
+ function cloneParts(parts: readonly InputPart[]): InputPart[] {
36
+ return parts.map((part) => ({ ...part }));
37
+ }
38
+
39
+ function partsText(parts: readonly InputPart[]): string {
40
+ return parts
41
+ .map((part) => part.content)
42
+ .join("\n")
43
+ .trim();
44
+ }
45
+
46
+ /**
47
+ * In-process prompt history. Keeping this out of the persistent stores avoids
48
+ * creating a second plaintext transcript while still giving terminal users
49
+ * the expected Up/Down workflow.
50
+ */
51
+ export class PromptHistory {
52
+ private readonly entries: InputPart[][] = [];
53
+ private cursor = 0;
54
+ private draft: InputPart[] | null = null;
55
+
56
+ constructor(private readonly maxItems = MAX_HISTORY_ITEMS) {}
57
+
58
+ add(parts: readonly InputPart[]): void {
59
+ const text = partsText(parts);
60
+ if (!text) return;
61
+
62
+ const latest = this.entries.at(-1);
63
+ if (!latest || partsText(latest) !== text) {
64
+ this.entries.push(cloneParts(parts));
65
+ if (this.entries.length > this.maxItems) this.entries.shift();
66
+ }
67
+ this.resetNavigation();
68
+ }
69
+
70
+ move(
71
+ direction: "previous" | "next",
72
+ current: readonly InputPart[],
73
+ ): InputPart[] | undefined {
74
+ if (this.entries.length === 0) return undefined;
75
+
76
+ if (direction === "previous") {
77
+ if (this.cursor === this.entries.length) {
78
+ this.draft = cloneParts(current);
79
+ }
80
+ if (this.cursor > 0) this.cursor--;
81
+ return cloneParts(this.entries[this.cursor]!);
82
+ }
83
+
84
+ if (this.cursor === this.entries.length) return undefined;
85
+ if (this.cursor < this.entries.length - 1) {
86
+ this.cursor++;
87
+ return cloneParts(this.entries[this.cursor]!);
88
+ }
89
+
90
+ this.cursor = this.entries.length;
91
+ return cloneParts(this.draft ?? [{ type: "text", content: "" }]);
92
+ }
93
+
94
+ /** Preserve an edited recalled prompt as the current draft. */
95
+ detach(parts: readonly InputPart[]): void {
96
+ if (this.cursor === this.entries.length) return;
97
+ this.cursor = this.entries.length;
98
+ this.draft = cloneParts(parts);
99
+ }
100
+
101
+ resetNavigation(): void {
102
+ this.cursor = this.entries.length;
103
+ this.draft = null;
104
+ }
105
+ }
32
106
 
33
107
  // ── Factory ──────────────────────────────────────────────────────────────────
34
108
 
@@ -38,7 +112,8 @@ export function createInput(promptStr: string): InputHandler {
38
112
  let paused = false;
39
113
 
40
114
  // Input is an ordered list of parts
41
- let parts: Part[] = [{ type: "text", content: "" }];
115
+ let parts: InputPart[] = [{ type: "text", content: "" }];
116
+ const history = new PromptHistory();
42
117
 
43
118
  // Bracketed paste accumulation
44
119
  let inPaste = false;
@@ -46,7 +121,7 @@ export function createInput(promptStr: string): InputHandler {
46
121
 
47
122
  // ── Helpers ──
48
123
 
49
- function lastPart(): Part {
124
+ function lastPart(): InputPart {
50
125
  return parts[parts.length - 1]!;
51
126
  }
52
127
 
@@ -99,18 +174,17 @@ export function createInput(promptStr: string): InputHandler {
99
174
  }
100
175
 
101
176
  function getFullText(): string {
102
- return parts
103
- .map((p) => p.content)
104
- .join("\n")
105
- .trim();
177
+ return partsText(parts);
106
178
  }
107
179
 
108
180
  function clear(): void {
109
181
  parts = [{ type: "text", content: "" }];
182
+ history.resetNavigation();
110
183
  }
111
184
 
112
185
  function submit(): void {
113
186
  const text = getFullText();
187
+ if (!pendingResolve) history.add(parts);
114
188
  clear();
115
189
  process.stdout.write("\n");
116
190
 
@@ -135,6 +209,7 @@ export function createInput(promptStr: string): InputHandler {
135
209
  // Short paste — inline into current text part
136
210
  ensureTrailingText().content += text.replace(/\n/g, " ");
137
211
  }
212
+ history.detach(parts);
138
213
  redraw();
139
214
  }
140
215
 
@@ -160,6 +235,14 @@ export function createInput(promptStr: string): InputHandler {
160
235
  if (parts.length === 0) parts.push({ type: "text", content: "" });
161
236
  ensureTrailingText();
162
237
  }
238
+ history.detach(parts);
239
+ redraw();
240
+ }
241
+
242
+ function navigateHistory(direction: "previous" | "next"): void {
243
+ const recalled = history.move(direction, parts);
244
+ if (!recalled) return;
245
+ parts = recalled;
163
246
  redraw();
164
247
  }
165
248
 
@@ -236,9 +319,22 @@ export function createInput(promptStr: string): InputHandler {
236
319
 
237
320
  if (code === 0x1b) {
238
321
  if (i + 1 < chunk.length && chunk[i + 1] === "[") {
239
- // ANSI escape sequence (arrows, etc.) — skip
322
+ // CSI escape sequence. Up/Down navigate history; the rest are
323
+ // ignored until cursor editing is implemented.
324
+ let end = i + 2;
325
+ while (end < chunk.length && chunk.charCodeAt(end) < 0x40) end++;
326
+ const final = chunk[end];
327
+ if (final === "A") navigateHistory("previous");
328
+ if (final === "B") navigateHistory("next");
329
+ i = end;
330
+ } else if (
331
+ i + 2 < chunk.length &&
332
+ chunk[i + 1] === "O" &&
333
+ (chunk[i + 2] === "A" || chunk[i + 2] === "B")
334
+ ) {
335
+ // Some terminals use application-cursor sequences (ESC O A/B).
336
+ navigateHistory(chunk[i + 2] === "A" ? "previous" : "next");
240
337
  i += 2;
241
- while (i < chunk.length && chunk.charCodeAt(i) < 0x40) i++;
242
338
  } else if (pendingResolve) {
243
339
  // Bare Escape during waitForInput — cancel
244
340
  const resolve = pendingResolve;
@@ -253,6 +349,7 @@ export function createInput(promptStr: string): InputHandler {
253
349
  if (code === 0x09) {
254
350
  // Tab
255
351
  ensureTrailingText().content += " ";
352
+ history.detach(parts);
256
353
  redraw();
257
354
  continue;
258
355
  }
@@ -261,6 +358,7 @@ export function createInput(promptStr: string): InputHandler {
261
358
 
262
359
  // Printable char
263
360
  ensureTrailingText().content += ch;
361
+ history.detach(parts);
264
362
  redraw();
265
363
  }
266
364
  });
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import pc from "picocolors";
10
+ import { formatUsd } from "../shared/format.js";
10
11
 
11
12
  // ── Types ────────────────────────────────────────────────────────────────────
12
13
 
@@ -193,6 +194,7 @@ export function createRenderer(cols?: number, displayName = "Talon"): Renderer {
193
194
  if (typeof info.cacheHitPct === "number") {
194
195
  p.push(`${info.cacheHitPct}% cache`);
195
196
  }
197
+ if (info.costUsd > 0) p.push(formatUsd(info.costUsd));
196
198
  if (tools > 0) p.push(`${tools} tool${tools > 1 ? "s" : ""}`);
197
199
  writeln();
198
200
  writeln(` ${pc.dim(p.join(" · "))}`);
@@ -8,7 +8,7 @@ import {
8
8
  todayUtc,
9
9
  type MetricsGrain,
10
10
  type MetricsLatencyAgg,
11
- } from "../storage/sessions.js";
11
+ } from "./sessions.js";
12
12
 
13
13
  const legacyCounters = new Map<string, number>();
14
14
 
@@ -40,9 +40,12 @@ export type ScheduledMessage = {
40
40
  /** Epoch ms when the message should be sent. */
41
41
  fireAt: number;
42
42
  createdAt: number;
43
- /** Telegram: numeric message id to reply to. */
44
- replyTo?: number;
45
- /** Telegram: inline keyboard rows. */
43
+ /**
44
+ * Message id to reply to, in the owning frontend's native shape:
45
+ * numeric for Telegram, a snowflake string for Discord.
46
+ */
47
+ replyTo?: number | string;
48
+ /** Inline keyboard / button rows to replay at fire time. */
46
49
  rows?: ScheduledButtonRow[];
47
50
  };
48
51
 
@@ -173,6 +173,14 @@ const nativeConfigSchema = z
173
173
  * (~/.talon/keys/bridge-token) — the network never gets an open bridge.
174
174
  */
175
175
  token: z.string().optional(),
176
+ /**
177
+ * Origins allowed to call the bridge from a BROWSER. Native clients
178
+ * (Electron main, Flutter, curl, talon-node) send no Origin header and
179
+ * never need an entry here. Anything listed gets a matching
180
+ * Access-Control-Allow-Origin; everything else is refused 403, so a
181
+ * random web page cannot drive the agent API on the user's machine.
182
+ */
183
+ allowedOrigins: z.array(z.string().min(1)).optional(),
176
184
  /**
177
185
  * Serve the bridge over TLS with a persistent self-signed certificate
178
186
  * (~/.talon/keys/); clients pin its SHA-256 fingerprint on first
package/src/util/log.ts CHANGED
@@ -53,6 +53,7 @@ export type LogComponent =
53
53
  | "teams"
54
54
  | "discord"
55
55
  | "native"
56
+ | "mesh"
56
57
  | "config"
57
58
  | "access"
58
59
  | "github"
@@ -1,33 +0,0 @@
1
- /**
2
- * Codex backend — barrel re-export.
3
- *
4
- * Wraps `@openai/codex-sdk` (which in turn spawns the `codex` CLI from
5
- * `@openai/codex`) and exposes a Backend-compatible API. The
6
- * implementation is split across focused modules:
7
- *
8
- * - `constants.ts` — system-prompt suffix, default model.
9
- * - `state.ts` — per-process state container.
10
- * - `init.ts` — backend init + lazy Codex-instance construction.
11
- * - `mcp-config.ts` — Talon MCP plugins → Codex `--config` TOML.
12
- * - `handler.ts` — main message handler (streaming, end_turn).
13
- * - `factory.ts` — registry registration.
14
- *
15
- * MCP integration: Codex configures MCP servers at thread-creation
16
- * time via TOML overrides. Talon rebuilds the `Codex` instance when
17
- * the active chat id changes so each chat sees its own MCP server
18
- * environment (frontend-tools + plugin servers).
19
- */
20
-
21
- export {
22
- CODEX_SYSTEM_PROMPT_SUFFIX,
23
- CODEX_DEFAULT_MODEL,
24
- CODEX_DEFAULT_WORKING_DIRECTORY,
25
- } from "./constants.js";
26
-
27
- export { initCodexAgent, ensureCodex } from "./init.js";
28
-
29
- export { handleMessage, getActiveAbort } from "./handler/index.js";
30
-
31
- export { runOneShotAgent } from "./one-shot.js";
32
-
33
- export { buildCodexMcpServers, type CodexMcpServer } from "./mcp-config.js";
@@ -1,48 +0,0 @@
1
- /**
2
- * OpenAI Agents backend — barrel.
3
- *
4
- * Importing this module registers the backend with the registry via
5
- * `factory.ts`'s side-effect import. Most callers should NOT need to
6
- * reach in here — the bootstrap layer picks the active backend by id.
7
- */
8
-
9
- import "./factory.js";
10
-
11
- export {
12
- initOpenAIAgentsAgent,
13
- getOpenAIApiKey,
14
- getOpenAIBaseUrl,
15
- triggerDiscoveryRefresh,
16
- } from "./init.js";
17
- export {
18
- awaitDiscovery,
19
- startDiscovery,
20
- refreshDiscovery,
21
- hasDiscoveredCatalog,
22
- fetchEndpointModels,
23
- extractCapabilities,
24
- } from "./discovery.js";
25
- export { handleMessage, getActiveAbort } from "./handler/index.js";
26
- export { resetState, getState } from "./state.js";
27
- export {
28
- resolveModel,
29
- getModelInfo,
30
- getSettingsPresentation,
31
- getProviders,
32
- getProviderModels,
33
- formatModelError,
34
- listModels,
35
- } from "./models.js";
36
- export {
37
- OPENAI_AGENTS_SYSTEM_PROMPT_SUFFIX,
38
- OPENAI_AGENTS_DEFAULT_MODEL,
39
- OPENAI_AGENTS_MAX_TURNS,
40
- OPENAI_AGENTS_AGENT_NAME,
41
- } from "./constants.js";
42
- export {
43
- getOrCreateBundle,
44
- releaseBundle,
45
- releaseAllBundles,
46
- getActiveBundleIds,
47
- } from "./mcp-pool.js";
48
- export type { OpenAIAgentsMcpBundle, BundleInputs } from "./mcp-pool.js";
@@ -1,104 +0,0 @@
1
- /**
2
- * Pure helpers over the config `plugins` array for the `talon plugin`
3
- * command group. The CLI edits config.json as JSON — entries here are the
4
- * on-disk shape (`core/plugin/types.ts` PluginEntry), not loaded plugins.
5
- * No filesystem or process access — everything is testable data-in/data-out.
6
- */
7
-
8
- /** On-disk plugin entry — path-based module or standalone MCP server. */
9
- export type PluginEntryJson = {
10
- path?: string;
11
- config?: Record<string, unknown>;
12
- name?: string;
13
- command?: string;
14
- args?: string[];
15
- env?: Record<string, string>;
16
- enabled?: boolean;
17
- };
18
-
19
- /**
20
- * Built-in plugins toggled by their own config sections (`github.enabled`,
21
- * …) rather than `plugins[]` entries — see core/plugin/builtins.ts.
22
- */
23
- export const BUILTIN_PLUGINS = [
24
- "github",
25
- "mempalace",
26
- "mem0",
27
- "playwright",
28
- ] as const;
29
-
30
- export function isBuiltinPlugin(name: string): boolean {
31
- return (BUILTIN_PLUGINS as readonly string[]).includes(name);
32
- }
33
-
34
- /** Split a config path on either separator — Windows configs carry `\`. */
35
- function pathSegments(path: string): string[] {
36
- return path.split(/[\\/]+/).filter(Boolean);
37
- }
38
-
39
- /**
40
- * Display name for an entry: the MCP name, or for path entries the package
41
- * name — segments after the last `node_modules` (preserving `@scope/pkg`),
42
- * else the folder basename.
43
- */
44
- export function entryDisplayName(entry: PluginEntryJson): string {
45
- if (entry.name) return entry.name;
46
- if (!entry.path) return "(invalid entry)";
47
- const segments = pathSegments(entry.path);
48
- const nm = segments.lastIndexOf("node_modules");
49
- if (nm >= 0 && nm < segments.length - 1) {
50
- return segments.slice(nm + 1).join("/");
51
- }
52
- return segments[segments.length - 1] ?? entry.path;
53
- }
54
-
55
- /** Does `token` identify this entry? Display name, basename, or full path. */
56
- export function entryMatches(entry: PluginEntryJson, token: string): boolean {
57
- if (entryDisplayName(entry) === token) return true;
58
- if (!entry.path) return false;
59
- const segments = pathSegments(entry.path);
60
- return entry.path === token || segments[segments.length - 1] === token;
61
- }
62
-
63
- export function findEntryIndex(
64
- entries: PluginEntryJson[],
65
- token: string,
66
- ): number {
67
- return entries.findIndex((entry) => entryMatches(entry, token));
68
- }
69
-
70
- /**
71
- * Index of an entry with the same identity as `candidate` (same module
72
- * path or same MCP name) — the duplicate an install must replace.
73
- */
74
- export function findConflictIndex(
75
- entries: PluginEntryJson[],
76
- candidate: PluginEntryJson,
77
- ): number {
78
- return entries.findIndex((entry) =>
79
- candidate.path !== undefined
80
- ? entry.path === candidate.path
81
- : entry.name !== undefined && entry.name === candidate.name,
82
- );
83
- }
84
-
85
- /**
86
- * Copy of `entry` with the enabled state applied. Enabled is the default,
87
- * so enabling removes the key rather than writing `enabled: true`.
88
- */
89
- export function withEnabled(
90
- entry: PluginEntryJson,
91
- enabled: boolean,
92
- ): PluginEntryJson {
93
- const { enabled: _drop, ...rest } = entry;
94
- return enabled ? rest : { ...rest, enabled: false };
95
- }
96
-
97
- /**
98
- * Package name of an npm spec, version stripped: `pkg@1.2` → `pkg`,
99
- * `@scope/pkg@^1` → `@scope/pkg`. A leading `@` is the scope, not a version.
100
- */
101
- export function npmSpecName(spec: string): string {
102
- const at = spec.lastIndexOf("@");
103
- return at > 0 ? spec.slice(0, at) : spec;
104
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * Agent runtime — barrel for the primitives every backend, frontend,
3
- * and dispatcher consumer reads through. See `agent-runtime/README.md`
4
- * for the module map.
5
- *
6
- * - `AgentEvent` + helpers (events.ts)
7
- * - `ModelRef` + helpers (model-ref.ts)
8
- * - `Backend` + capability interfaces + `composeBackend` (capabilities.ts)
9
- * - Backend contract assertions (contract-tests.ts)
10
- */
11
-
12
- export {
13
- type AgentEvent,
14
- type AgentError,
15
- type AgentErrorKind,
16
- type AgentResult,
17
- type UsageSnapshot,
18
- addUsage,
19
- AgentRunError,
20
- classifiedToAgentError,
21
- emptyUsage,
22
- isAgentEventOf,
23
- isAgentRunTerminator,
24
- toolInputToRecord,
25
- } from "./events.js";
26
-
27
- export {
28
- type BackendId,
29
- type CacheSupport,
30
- type ModelRef,
31
- type ModelSource,
32
- BACKEND_IDS,
33
- isBackendId,
34
- makeBareModelRef,
35
- sameModelRef,
36
- } from "./model-ref.js";
37
-
38
- export {
39
- type Backend,
40
- type BackgroundRunner,
41
- type ChatBackend,
42
- type ChatRunParams,
43
- type ModelCatalog,
44
- type SessionBackend,
45
- type SystemControl,
46
- type ToolRefreshResult,
47
- type ToolRuntime,
48
- type UsageTelemetry,
49
- composeBackend,
50
- } from "./capabilities.js";
51
-
52
- export {
53
- assertBackendContract,
54
- assertBackendIdentity,
55
- assertBackgroundRunnerLifecycle,
56
- assertChatBackendEmitsRunStarted,
57
- assertChatBackendEmitsSingleUsage,
58
- assertChatBackendTerminates,
59
- assertCompletedUsageMatchesUsageEvent,
60
- assertModelCatalogDefaultShape,
61
- assertUsageTelemetryShape,
62
- } from "./contract-tests.js";
@@ -1,21 +0,0 @@
1
- /**
2
- * Background agents — everything that runs without a user message.
3
- *
4
- * - `heartbeat` — periodic autonomous agent pass ("anything need
5
- * doing?"), prompted by prompts/heartbeat.md.
6
- * - `dream` — memory-consolidation agent (daily notes → memory,
7
- * mempalace mining), prompted by prompts/dream.md.
8
- * - `pulse` — lightweight liveness/typing-indicator ticker.
9
- * - `cron` — persistent user-scheduled jobs (message / query).
10
- * - `triggers` — user-authored watcher scripts that fire wake-ups.
11
- *
12
- * All of these drive the backend through the same `BackgroundRunner`
13
- * surface (core/agent-runtime/capabilities.ts) — they never talk to a
14
- * concrete backend directly.
15
- */
16
-
17
- export * from "./heartbeat/index.js";
18
- export * from "./dream.js";
19
- export * from "./pulse.js";
20
- export * from "./cron.js";
21
- export * from "./triggers/index.js";
@@ -1,20 +0,0 @@
1
- /**
2
- * Engine — the message-flow core.
3
- *
4
- * - `dispatcher` — per-chat serial, cross-chat parallel turn
5
- * execution; the single entry point for "run this user message".
6
- * - `gateway` — loopback HTTP bridge the MCP tool
7
- * subprocesses call back into.
8
- * - `gateway-actions` — the action handlers the bridge dispatches
9
- * to (send/edit/react/cron/trigger/admin …).
10
- * - `backend-controller` — backend lifecycle: init, hot-swap,
11
- * cleanup, registry lookup.
12
- *
13
- * Everything here is frontend- and backend-agnostic: frontends feed
14
- * the dispatcher, backends are reached through the agent-runtime
15
- * capability slots.
16
- */
17
-
18
- export * from "./dispatcher.js";
19
- export * from "./gateway.js";
20
- export * from "./backend-controller/index.js";
@@ -1,17 +0,0 @@
1
- /**
2
- * Model layer — everything about "which model is the chat using".
3
- *
4
- * - `catalog` — model-id resolution, defaults, and the
5
- * curated cross-backend catalog.
6
- * - `active-model` — per-chat active-model resolution
7
- * (chat-settings → config → backend default).
8
- * - `reasoning-levels` — the reasoning-effort vocabulary shared by
9
- * every backend (order, labels, normalisation).
10
- *
11
- * Presentation helpers (menus, status lines) live in `frontend/` —
12
- * this layer is pure domain.
13
- */
14
-
15
- export * from "./catalog.js";
16
- export * from "./active-model.js";
17
- export * from "./reasoning-levels.js";