talon-agent 3.0.5 → 3.1.1

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 (63) hide show
  1. package/package.json +8 -5
  2. package/src/backend/kilo/models/index.ts +4 -1
  3. package/src/backend/kilo/server.ts +17 -4
  4. package/src/backend/openai-agents/session.ts +1 -1
  5. package/src/backend/opencode/models/index.ts +4 -1
  6. package/src/backend/opencode/server.ts +17 -4
  7. package/src/backend/shared/system-prompt.ts +5 -0
  8. package/src/cli/events.ts +1 -1
  9. package/src/cli/index.ts +1 -1
  10. package/src/cli/tasks.ts +5 -1
  11. package/src/core/background/triggers/pid.ts +28 -0
  12. package/src/core/background/triggers/resume.ts +4 -24
  13. package/src/core/background/triggers/spawn.ts +1 -1
  14. package/src/core/engine/gateway-actions/plugins.ts +3 -3
  15. package/src/core/prompt/invalidation.ts +23 -0
  16. package/src/core/types.ts +2 -2
  17. package/src/frontend/discord/callbacks/components.ts +1 -1
  18. package/src/frontend/discord/commands/settings.ts +1 -1
  19. package/src/frontend/native/auth.ts +52 -0
  20. package/src/frontend/native/extensions.ts +2 -2
  21. package/src/frontend/native/index.ts +8 -2
  22. package/src/frontend/native/server.ts +96 -15
  23. package/src/frontend/telegram/callbacks/model.ts +1 -1
  24. package/src/frontend/telegram/commands/admin.ts +1 -1
  25. package/src/frontend/telegram/commands/settings.ts +1 -1
  26. package/src/frontend/telegram/formatting.ts +1 -1
  27. package/src/frontend/terminal/commands.ts +1 -1
  28. package/src/native/htmlents-wasm-bytes.ts +4 -4
  29. package/src/native/htmlents.ts +4 -4
  30. package/src/native/registry.ts +9 -9
  31. package/src/native/runtime.ts +2 -2
  32. package/src/native/sqlguard-wasm-bytes.ts +4 -4
  33. package/src/native/sqlguard.ts +2 -2
  34. package/src/native/strsim-wasm-bytes.ts +4 -4
  35. package/src/native/strsim.ts +3 -3
  36. package/src/plugins/github/index.ts +1 -1
  37. package/src/plugins/mem0/index.ts +1 -1
  38. package/src/plugins/mempalace/index.ts +1 -1
  39. package/src/plugins/playwright/index.ts +1 -1
  40. package/src/storage/chat-settings.ts +3 -60
  41. package/src/storage/cron-store.ts +6 -80
  42. package/src/storage/goal-store.ts +10 -19
  43. package/src/storage/history.ts +3 -14
  44. package/src/storage/journal.ts +20 -9
  45. package/src/storage/media-index.ts +2 -22
  46. package/src/storage/repositories/chat-settings-repo.ts +55 -1
  47. package/src/storage/repositories/cron-repo.ts +82 -6
  48. package/src/storage/repositories/goals-repo.ts +20 -1
  49. package/src/storage/repositories/history-repo.ts +14 -1
  50. package/src/storage/repositories/media-index-repo.ts +23 -1
  51. package/src/storage/repositories/scripts-repo.ts +16 -1
  52. package/src/storage/repositories/sessions-repo.ts +1 -1
  53. package/src/storage/repositories/triggers-repo.ts +60 -5
  54. package/src/storage/script-store.ts +2 -15
  55. package/src/storage/session-record.ts +220 -0
  56. package/src/storage/sessions.ts +22 -210
  57. package/src/storage/trigger-store.ts +6 -60
  58. package/src/types/assets.d.ts +9 -0
  59. package/src/types/effort.ts +12 -0
  60. package/src/util/config.ts +9 -2
  61. package/src/util/harden.ts +50 -0
  62. package/src/util/log.ts +2 -1
  63. package/tsconfig.json +6 -0
@@ -25,7 +25,7 @@ import { createHash, timingSafeEqual } from "node:crypto";
25
25
  import { createReadStream } from "node:fs";
26
26
  import { stat } from "node:fs/promises";
27
27
  import { extname } from "node:path";
28
- import { log, logError, logDebug } from "../../util/log.js";
28
+ import { log, logError, logDebug, logWarn } from "../../util/log.js";
29
29
  import { formatFingerprint, type BridgeTlsIdentity } from "./tls.js";
30
30
  import {
31
31
  BRIDGE_PROTOCOL_VERSION,
@@ -149,12 +149,32 @@ const MAX_BODY_BYTES = 256 * 1024;
149
149
  const MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
150
150
  const PORT_FALLBACKS = 5;
151
151
 
152
+ // Failed-auth lockout: after this many wrong tokens from one address inside
153
+ // the window, that address gets 429s until the window lapses. The token's
154
+ // 256 bits make brute force hopeless anyway — this is about not letting an
155
+ // internet-facing bridge be hammered for free (and giving fail2ban-style
156
+ // tooling a clean signal in the log). Only *presented-and-wrong* secrets
157
+ // count: tokenless probes are just scanners finding a locked door.
158
+ const AUTH_LOCKOUT_MAX_FAILURES = 20;
159
+ const AUTH_LOCKOUT_WINDOW_MS = 15 * 60 * 1000;
160
+ /** Hard cap on tracked addresses so the map can't become a memory lever. */
161
+ const AUTH_LOCKOUT_MAX_TRACKED = 10_000;
162
+
163
+ /**
164
+ * ok: request carries the right token (or none is required).
165
+ * anonymous: no credential presented — a pre-pairing probe, not an attack.
166
+ * bad: a credential was presented and it is wrong.
167
+ */
168
+ type AuthState = "ok" | "anonymous" | "bad";
169
+
152
170
  export class BridgeServer {
153
171
  private server: Server | null = null;
154
172
  private clients = new Set<ServerResponse>();
155
173
  private pingTimer: ReturnType<typeof setInterval> | undefined;
156
174
  private port = 0;
157
175
  private tlsIdentity: BridgeTlsIdentity | null = null;
176
+ /** Wrong-token counts per remote address (behind a proxy: per proxy). */
177
+ private authFailures = new Map<string, { count: number; resetAt: number }>();
158
178
 
159
179
  constructor(
160
180
  private readonly opts: {
@@ -311,21 +331,43 @@ export class BridgeServer {
311
331
  return;
312
332
  }
313
333
 
334
+ const remote = req.socket.remoteAddress ?? "unknown";
335
+ if (this.authLockedOut(remote)) {
336
+ res.writeHead(429, {
337
+ ...this.jsonHeaders(),
338
+ "Retry-After": String(Math.ceil(AUTH_LOCKOUT_WINDOW_MS / 1000)),
339
+ });
340
+ res.end(
341
+ JSON.stringify({ ok: false, error: "Too many failed auth attempts" }),
342
+ );
343
+ return;
344
+ }
345
+
346
+ const auth = this.authState(req, url);
347
+ if (auth === "bad") this.recordAuthFailure(remote);
348
+ else if (auth === "ok") this.authFailures.delete(remote);
349
+
314
350
  // /health is unauthenticated so clients can discover/ping the bridge
315
- // before they hold a token. It exposes no chat content.
351
+ // before they hold a token. Pre-auth it serves only what pairing needs
352
+ // (identity, protocol, fingerprint) — operational details like bot name,
353
+ // backend, and chat count are not for internet scanners to enumerate.
316
354
  if (method === "GET" && path === "/health") {
317
- const s = this.handlers.status();
318
- return this.json(res, 200, {
355
+ const base = {
319
356
  app: "talon-bridge",
320
357
  ok: true,
321
358
  protocol: BRIDGE_PROTOCOL_VERSION,
322
- host: this.opts.host,
323
359
  port: this.port,
324
360
  scheme: this.getScheme(),
325
361
  // The certificate's own hash — public by definition (any TLS client
326
362
  // sees the certificate), surfaced so pairing UIs can display it.
327
363
  fingerprint: this.getFingerprint(),
328
364
  authRequired: Boolean(this.opts.token),
365
+ };
366
+ if (auth !== "ok") return this.json(res, 200, base);
367
+ const s = this.handlers.status();
368
+ return this.json(res, 200, {
369
+ ...base,
370
+ host: this.opts.host,
329
371
  startedAt: this.opts.startedAt,
330
372
  botName: s.botName,
331
373
  backend: s.backend,
@@ -335,7 +377,7 @@ export class BridgeServer {
335
377
  });
336
378
  }
337
379
 
338
- if (!this.authOk(req, url)) {
380
+ if (auth !== "ok") {
339
381
  return this.json(res, 401, { ok: false, error: "Unauthorized" });
340
382
  }
341
383
 
@@ -677,17 +719,54 @@ export class BridgeServer {
677
719
 
678
720
  // ── Helpers ──────────────────────────────────────────────────────────────
679
721
 
680
- private authOk(req: IncomingMessage, url: URL): boolean {
681
- if (!this.opts.token) return true;
722
+ private authState(req: IncomingMessage, url: URL): AuthState {
723
+ if (!this.opts.token) return "ok";
682
724
  const header = req.headers["authorization"];
683
- if (
684
- typeof header === "string" &&
685
- header.startsWith("Bearer ") &&
686
- this.tokenMatches(header.slice("Bearer ".length))
687
- )
688
- return true;
725
+ const fromHeader =
726
+ typeof header === "string" && header.startsWith("Bearer ")
727
+ ? header.slice("Bearer ".length)
728
+ : null;
689
729
  // EventSource can't set headers, so SSE clients pass ?token=… instead.
690
- return this.tokenMatches(url.searchParams.get("token"));
730
+ const candidate = fromHeader ?? url.searchParams.get("token");
731
+ if (candidate === null) return "anonymous";
732
+ return this.tokenMatches(candidate) ? "ok" : "bad";
733
+ }
734
+
735
+ private authLockedOut(remote: string): boolean {
736
+ const entry = this.authFailures.get(remote);
737
+ if (!entry) return false;
738
+ if (Date.now() >= entry.resetAt) {
739
+ this.authFailures.delete(remote);
740
+ return false;
741
+ }
742
+ return entry.count >= AUTH_LOCKOUT_MAX_FAILURES;
743
+ }
744
+
745
+ private recordAuthFailure(remote: string): void {
746
+ const now = Date.now();
747
+ const entry = this.authFailures.get(remote);
748
+ if (!entry || now >= entry.resetAt) {
749
+ if (this.authFailures.size >= AUTH_LOCKOUT_MAX_TRACKED) {
750
+ for (const [ip, e] of this.authFailures) {
751
+ if (now >= e.resetAt) this.authFailures.delete(ip);
752
+ }
753
+ // Still saturated after pruning live entries — under that much churn
754
+ // dropping the newest attacker beats unbounded growth.
755
+ if (this.authFailures.size >= AUTH_LOCKOUT_MAX_TRACKED) return;
756
+ }
757
+ this.authFailures.set(remote, {
758
+ count: 1,
759
+ resetAt: now + AUTH_LOCKOUT_WINDOW_MS,
760
+ });
761
+ return;
762
+ }
763
+ entry.count++;
764
+ if (entry.count === AUTH_LOCKOUT_MAX_FAILURES) {
765
+ logWarn(
766
+ "native",
767
+ `Bridge auth lockout for ${remote} (${AUTH_LOCKOUT_MAX_FAILURES} wrong tokens in ${AUTH_LOCKOUT_WINDOW_MS / 60_000}m)`,
768
+ );
769
+ }
691
770
  }
692
771
 
693
772
  /**
@@ -708,6 +787,8 @@ export class BridgeServer {
708
787
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
709
788
  "Access-Control-Allow-Headers": "Authorization, Content-Type",
710
789
  "Access-Control-Max-Age": "86400",
790
+ // Every response states its type; never let a browser guess one.
791
+ "X-Content-Type-Options": "nosniff",
711
792
  };
712
793
  }
713
794
 
@@ -11,8 +11,8 @@ import type { Context } from "grammy";
11
11
  import {
12
12
  setChatModelForBackend,
13
13
  setChatBackend,
14
- resolveModelName,
15
14
  } from "../../../storage/chat-settings.js";
15
+ import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
16
16
  import { resetSession } from "../../../storage/sessions.js";
17
17
  import { clearHistory } from "../../../storage/history.js";
18
18
  import {
@@ -211,7 +211,7 @@ export function registerAdminCommands(
211
211
  }
212
212
 
213
213
  // Unknown /command → "did you mean ...?" via the C similarity core
214
- // (native/strsim-c). Registered after every real command, so grammY
214
+ // (native/strsim-wasm). Registered after every real command, so grammY
215
215
  // only reaches this when nothing above matched. Only bare commands
216
216
  // are intercepted — a close miss gets a suggestion, anything else
217
217
  // keeps flowing to the agent as a normal message.
@@ -9,9 +9,9 @@ import {
9
9
  setChatBackend,
10
10
  setChatEffort,
11
11
  setChatPulseInterval,
12
- resolveModelName,
13
12
  type EffortLevel,
14
13
  } from "../../../storage/chat-settings.js";
14
+ import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
15
15
  import {
16
16
  registerChat,
17
17
  disablePulse,
@@ -18,7 +18,7 @@ export function splitMessage(text: string, max: number): string[] {
18
18
  /**
19
19
  * Escape HTML special characters for Telegram HTML parse mode.
20
20
  * Must be applied to all text that is NOT inside an HTML tag.
21
- * Delegates to the C++ core (native/htmlents-cpp): one pass over the
21
+ * Delegates to the Rust core (native/htmlents-wasm): one pass over the
22
22
  * bytes instead of the five chained regex passes this replaces.
23
23
  */
24
24
  export function escapeHtml(text: string): string {
@@ -20,8 +20,8 @@ import {
20
20
  getChatSettings,
21
21
  setChatModel,
22
22
  setChatEffort,
23
- resolveModelName,
24
23
  } from "../../storage/chat-settings.js";
24
+ import { resolveModelId as resolveModelName } from "../../core/models/catalog.js";
25
25
  import {
26
26
  getAllSessions,
27
27
  getSession,
@@ -1,7 +1,7 @@
1
1
  // Generated by native/shared/build-lib.mjs — do not edit by hand.
2
- // Rebuild with `npm run build:cpp` after changing the source.
3
- // Source: native/htmlents-cpp (1448 bytes of wasm32-freestanding).
2
+ // Rebuild with `npm run build:wasm` after changing the source.
3
+ // Source: native/htmlents-wasm (1195 bytes of wasm32-unknown-unknown).
4
4
 
5
- /** native/htmlents-cpp artifact, base64-encoded. Decoded + instantiated lazily by src/native/htmlents.ts. */
5
+ /** native/htmlents-wasm artifact, base64-encoded. Decoded + instantiated lazily by src/native/htmlents.ts. */
6
6
  export const HTMLENTS_WASM_BASE64 =
7
- "AGFzbQEAAAABEQNgAX8Bf2ACf38AYAJ/fwF/AwQDAAECBAUBcAEBAQUDAQARBgkBfwFBgIDAAAsHKgQGbWVtb3J5AgAFYWxsb2MAAAdkZWFsbG9jAAELZXNjYXBlX2h0bWwAAgruCQOPAQEDfwJAIAANAEEADwtBACEBQQBBACgCyIDAgAAiAkHQgMCAACACG0EHakF4cSICNgLIgMCAAAJAIAIgAGoiACACSQ0AAkAgAD8AQRB0IgNNDQAgACADa0H//wNqQRB2QABBf0YNAQtBACAANgLIgMCAAEEAQQAoAsyAwIAAQQFqNgLMgMCAACACIQELIAELQAACQCAARQ0AIAFFDQBBACgCzIDAgAAiAEUNAEEAIABBf2oiADYCzIDAgAAgAA0AQQBB0IDAgAA2AsiAwIAACwuZCAEJf0EAIQICQAJAIAENAEEMIQNBACEEDAELIAFBAXEhBQJAAkAgAUEBRw0AQQAhBEEAIQMMAQsgAUF+cSEGQQAhBEEAIQMDQEGAgMCAACEHQQEhCEEBIQkCQAJAAkACQAJAAkAgACADaiIKLQAAQV5qDh0CBQUFBAMFBQUFBQUFBQUFBQUFBQUFBQUFBQAFAQULQaiAwIAAIQcMAwtBsIDAgAAhBwwCC0G4gMCAACEHDAELQcCAwIAAIQcLIAcoAgQhCQsgCSAEaiEHQYCAwIAAIQkCQAJAAkACQAJAAkAgCkEBai0AAEFeag4dAQUFBQQABQUFBQUFBQUFBQUFBQUFBQUFBQUDBQIFC0HAgMCAACEJDAMLQbiAwIAAIQkMAgtBsIDAgAAhCQwBC0GogMCAACEJCyAJKAIEIQgLIAggB2ohBCAGIANBAmoiA0cNAAsLAkAgBUUNAEGAgMCAACEJQQEhCAJAAkACQAJAAkACQCAAIANqLQAAQV5qDh0BBQUFBAAFBQUFBQUFBQUFBQUFBQUFBQUFBQMFAgULQcCAwIAAIQkMAwtBuIDAgAAhCQwCC0GwgMCAACEJDAELQaiAwIAAIQkLIAkoAgQhCAsgCCAEaiEECyAEQQxqIgMNAEEADwtBAEEAKALIgMCAACIIQdCAwIAAIAgbQQdqQXhxIgU2AsiAwIAAAkAgBSADaiIIIAVJDQACQCAIPwBBEHQiCU0NACAIIAlrQf//A2pBEHZAAEF/Rg0BC0EAIQJBACAINgLIgMCAAEEAQQAoAsyAwIAAQQFqNgLMgMCAACAFRQ0AIAUgBDoACCAFQQE2AgQgBSADOgAAIAUgBEEYdjoACyAFIARBEHY6AAogBSAEQQh2OgAJIAUgA0EYdjoAAyAFIANBEHY6AAIgBSADQQh2OgABAkAgAUUNACAFQQxqIQNBACECA0BBgIDAgAAhCAJAAkACQAJAAkACQAJAIAAgAmotAAAiCUFeag4dAgUFBQQDBQUFBQUFBQUFBQUFBQUFBQUFBQUABQEFC0GogMCAACEIDAMLQbCAwIAAIQgMAgtBuIDAgAAhCAwBC0HAgMCAACEICyAIKAIEIglFDQEgCUEDcSEEIAgoAgAhCkEAIQgCQCAJQQRJDQAgCUF8cSEGQQAhCANAIAMgCGoiCSAKIAhqIgctAAA6AAAgCUEBaiAHQQFqLQAAOgAAIAlBAmogB0ECai0AADoAACAJQQNqIAdBA2otAAA6AAAgBiAIQQRqIghHDQALIAMgCGohAwsgBEUNASAKIAhqIQgDQCADIAgtAAA6AAAgCEEBaiEIIANBAWohAyAEQX9qIgQNAAwCCwsgAyAJOgAAIANBAWohAwsgAkEBaiICIAFHDQALCyAFIQILIAILC1EBAEGAgMAAC0gZABAABQAAACZxdW90OwAmbHQ7ACZndDsAJmFtcDsAJiMzOTsAAAAADwAQAAQAAAAUABAABAAAAAgAEAAGAAAAHwAQAAUAAAA=";
7
+ "AGFzbQEAAAABKQdgBX9/f39/AGADf39/AGACf38AYAR/f39/AGABfwF/YAJ/fwF/YAAAAwsKAAECAwIEAgUCBgUDAQARBhkDfwFBgIDAAAt/AEGggMAAC38AQaCAwAALB0UGBm1lbW9yeQIABWFsbG9jAAULX19oZWFwX2Jhc2UDAQdkZWFsbG9jAAYLZXNjYXBlX2h0bWwABwpfX2RhdGFfZW5kAwIK3gcKNQACQCACIAFJDQAgAiAESw0AIAAgAiABazYCBCAAIAMgAWo2AgAPCyABIAIgBBCBgICAAAALCQAQiYCAgAAAC40BAQJ/QQAhAgJAAkACQAJAAkACQAJAAkAgAUH/AXEiAUFeag4GBAcHBwEFAAsgAUFEag4DAQYCBgtBgIDAgAAhAgwEC0GFgMCAACECQQQhAwwEC0GJgMCAACECQQQhAwwDC0GNgMCAACECQQYhAwwCC0GTgMCAACECC0EFIQMLIAAgAzYCBCAAIAI2AgALKgACQCABIANHDQACQCABRQ0AIAAgAiAB/AoAAAsPCyABIAMQhICAgAAACwkAEImAgIAAAAuiAQEDfwJAIAANAEEADwtBACEBAkBBACgCmIDAgAAiAg0AQaCAwIAAIQJBAEGggMCAADYCmIDAgAALAkACQCACQQdqQXhxIgIgAGoiACACSQ0AIAA/AEEQdCIDTQ0BQQAhASAAIANrQRB2IABB//8DcUEAR2pAAEF/Rw0BCyABDwtBACAANgKYgMCAAEEAQQAoApyAwIAAQQFqNgKcgMCAACACC0AAAkAgAEUNACABRQ0AQQAoApyAwIAAIgBFDQBBACAAQX9qIgA2ApyAwIAAIAANAEEAQaCAwIAANgKYgMCAAAsL5AMBB38jgICAgABBwABrIgIkgICAgABBACEDQQAhBAJAIAFFDQBBACEEIAAhBSABIQYDQCACQTBqIAUtAAAQgoCAgAAgAigCNEEBIAIoAjAbIARqIQQgBUEBaiEFIAZBf2oiBg0ACwtBDCEFAkAgBEEMaiIHEIWAgIAAIghFDQAgAEEBIAEbIQYgAkEoakEAQQQgCCAHEICAgIAAIAIoAiwhAyACKAIoIQAgAiAHNgI8IAAgAyACQTxqQQQQg4CAgAAgAkEgakEEQQggCCAHEICAgIAAIAIoAiQhAyACKAIgIQAgAkEBNgI8IAAgAyACQTxqQQQQg4CAgAAgAkEYakEIQQwgCCAHEICAgIAAIAIoAhwhAyACKAIYIQAgAiAENgI8IAAgAyACQTxqQQQQg4CAgAADQAJAAkACQCABRQ0AIAJBEGogBi0AACIDEIKAgIAAIAIoAhAiBEUNASACQQhqIAUgAigCFCIDIAVqIgAgCCAHEICAgIAAIAIoAgggAigCDCAEIAMQg4CAgAAgACEFDAILIAghAwwDCwJAIAUgB08NACAIIAVqIAM6AAAgBUEBaiEFDAELIAUgBxCIgICAAAALIAZBAWohBiABQX9qIQEMAAsLIAJBwABqJICAgIAAIAMLCQAQiYCAgAAACwMAAAsLIQEAQYCAwAALGCZhbXA7Jmx0OyZndDsmcXVvdDsmIzM5Ow==";
@@ -1,10 +1,10 @@
1
1
  /**
2
- * HTML entity escaping — TypeScript boundary over the C++ wasm module.
2
+ * HTML entity escaping — TypeScript boundary over the Rust wasm module.
3
3
  *
4
4
  * One single-pass escaper for Telegram HTML parse mode, replacing the
5
5
  * five chained regex passes it supersedes. Every outbound Telegram
6
- * render flows through here (frontend/telegram/formatting.ts). The C++
7
- * source lives in native/htmlents-cpp and exports a three-function
6
+ * render flows through here (frontend/telegram/formatting.ts). The Rust
7
+ * source lives in native/htmlents-wasm and exports a three-function
8
8
  * C ABI (alloc / dealloc / escape_html) — see that module's README for
9
9
  * the contract, and src/native/runtime.ts for the embedding and memory
10
10
  * conventions shared by every native module.
@@ -24,7 +24,7 @@ import {
24
24
  type WasmCoreExports,
25
25
  } from "./runtime.js";
26
26
 
27
- /** The C-ABI surface exported by native/htmlents-cpp. */
27
+ /** The C-ABI surface exported by native/htmlents-wasm. */
28
28
  interface HtmlentsExports extends WasmCoreExports {
29
29
  escape_html(inputPtr: number, len: number): number;
30
30
  }
@@ -75,9 +75,9 @@ export const NATIVE_MODULES: readonly NativeModuleSpec[] = [
75
75
  },
76
76
  {
77
77
  name: "strsim",
78
- language: "C",
79
- target: "wasm32-freestanding",
80
- sourceDir: "native/strsim-c",
78
+ language: "Rust",
79
+ target: "wasm32-unknown-unknown",
80
+ sourceDir: "native/strsim-wasm",
81
81
  sizeBytes: async () =>
82
82
  base64ByteLength(
83
83
  (await import("./strsim-wasm-bytes.js")).STRSIM_WASM_BASE64,
@@ -90,9 +90,9 @@ export const NATIVE_MODULES: readonly NativeModuleSpec[] = [
90
90
  },
91
91
  {
92
92
  name: "sqlguard",
93
- language: "C",
94
- target: "wasm32-freestanding",
95
- sourceDir: "native/sqlguard-c",
93
+ language: "Rust",
94
+ target: "wasm32-unknown-unknown",
95
+ sourceDir: "native/sqlguard-wasm",
96
96
  sizeBytes: async () =>
97
97
  base64ByteLength(
98
98
  (await import("./sqlguard-wasm-bytes.js")).SQLGUARD_WASM_BASE64,
@@ -107,9 +107,9 @@ export const NATIVE_MODULES: readonly NativeModuleSpec[] = [
107
107
  },
108
108
  {
109
109
  name: "htmlents",
110
- language: "C++",
111
- target: "wasm32-freestanding",
112
- sourceDir: "native/htmlents-cpp",
110
+ language: "Rust",
111
+ target: "wasm32-unknown-unknown",
112
+ sourceDir: "native/htmlents-wasm",
113
113
  sizeBytes: async () =>
114
114
  base64ByteLength(
115
115
  (await import("./htmlents-wasm-bytes.js")).HTMLENTS_WASM_BASE64,
@@ -2,8 +2,8 @@
2
2
  * Embedded-wasm runtime — the one place that knows how Talon's native
3
3
  * modules cross the JS boundary.
4
4
  *
5
- * Every wasm module in the native plane (Rust blake3, Zig textops,
6
- * C strsim, C++ htmlents) follows the same contract:
5
+ * Every wasm module in the native plane (Rust blake3, strsim,
6
+ * sqlguard, htmlents; Zig textops) follows the same contract:
7
7
  *
8
8
  * - The artifact ships as base64 inside a generated `*-bytes.ts`
9
9
  * module, so it survives `bun build --compile` single binaries —
@@ -1,7 +1,7 @@
1
1
  // Generated by native/shared/build-lib.mjs — do not edit by hand.
2
- // Rebuild with `npm run build:c` after changing the source.
3
- // Source: native/sqlguard-c (2252 bytes of wasm32-freestanding).
2
+ // Rebuild with `npm run build:wasm` after changing the source.
3
+ // Source: native/sqlguard-wasm (1805 bytes of wasm32-unknown-unknown).
4
4
 
5
- /** native/sqlguard-c artifact, base64-encoded. Decoded + instantiated lazily by src/native/sqlguard.ts. */
5
+ /** native/sqlguard-wasm artifact, base64-encoded. Decoded + instantiated lazily by src/native/sqlguard.ts. */
6
6
  export const SQLGUARD_WASM_BASE64 =
7
- "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";
7
+ "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";
@@ -6,7 +6,7 @@
6
6
  * pattern for a LIKE search (get_user_messages), and `ftsQuote` turns
7
7
  * free-form text into a literal FTS5 MATCH expression (search_history).
8
8
  *
9
- * The C source lives in native/sqlguard-c and exports a four-function
9
+ * The Rust source lives in native/sqlguard-wasm and exports a four-function
10
10
  * C ABI (alloc / dealloc / escape_like / fts_quote) — see that module's
11
11
  * README for the contract, and src/native/runtime.ts for the embedding
12
12
  * and memory conventions shared by every native module.
@@ -27,7 +27,7 @@ import {
27
27
  } from "./runtime.js";
28
28
  import { SQLGUARD_WASM_BASE64 } from "./sqlguard-wasm-bytes.js";
29
29
 
30
- /** The C-ABI surface exported by native/sqlguard-c. */
30
+ /** The C-ABI surface exported by native/sqlguard-wasm. */
31
31
  interface SqlguardExports extends WasmCoreExports {
32
32
  escape_like(inputPtr: number, len: number): number;
33
33
  fts_quote(inputPtr: number, len: number): number;
@@ -1,7 +1,7 @@
1
1
  // Generated by native/shared/build-lib.mjs — do not edit by hand.
2
- // Rebuild with `npm run build:c` after changing the source.
3
- // Source: native/strsim-c (775 bytes of wasm32-freestanding).
2
+ // Rebuild with `npm run build:wasm` after changing the source.
3
+ // Source: native/strsim-wasm (725 bytes of wasm32-unknown-unknown).
4
4
 
5
- /** native/strsim-c artifact, base64-encoded. Decoded + instantiated lazily by src/native/strsim.ts. */
5
+ /** native/strsim-wasm artifact, base64-encoded. Decoded + instantiated lazily by src/native/strsim.ts. */
6
6
  export const STRSIM_WASM_BASE64 =
7
- "AGFzbQEAAAABEwNgAX8Bf2ACf38AYAR/f39/AX8DBAMAAQIEBQFwAQEBBQMBABEGCQF/AUGAgMAACwcqBAZtZW1vcnkCAAVhbGxvYwAAB2RlYWxsb2MAAQtsZXZlbnNodGVpbgACCp4FA48BAQN/AkAgAA0AQQAPC0EAIQFBAEEAKAKAgMCAACICQaCgwIAAIAIbQQdqQXhxIgI2AoCAwIAAAkAgAiAAaiIAIAJJDQACQCAAPwBBEHQiA00NACAAIANrQf//A2pBEHZAAEF/Rg0BC0EAIAA2AoCAwIAAQQBBACgChIDAgABBAWo2AoSAwIAAIAIhAQsgAQtAAAJAIABFDQAgAUUNAEEAKAKEgMCAACIARQ0AQQAgAEF/aiIANgKEgMCAACAADQBBAEGgoMCAADYCgIDAgAALC8kDAQl/QX8hBAJAIAFBgAhLDQAgA0GACEsNAAJAIAENACADDwsCQCADDQAgAQ8LIANBAWoiBUEHcSEGQQAhBAJAIANBB0kNACAFQfgfcSEHQQAhBEGQgMCAACEFA0AgBSAENgIAIAVBHGogBEEHajYCACAFQRhqIARBBmo2AgAgBUEUaiAEQQVqNgIAIAVBEGogBEEEajYCACAFQQxqIARBA2o2AgAgBUEIaiAEQQJqNgIAIAVBBGogBEEBajYCACAFQSBqIQUgBEEIaiIEIAdHDQALCwJAIAZFDQAgBEECdEGQgMCAAGohBQNAIAUgBDYCACAFQQRqIQUgBEEBaiEEIAZBf2oiBg0ACwsgAEF/aiEIQQAoApCAwIAAIQZBASEFA0BBlIDAgAAhBCAIIAUiCWotAABB/wFxIQogAyEHIAIhBSAJIQADQCAEIABBAWoiACAEKAIAIgtBAWoiDCAGIAogBS0AAEdqIgYgDCAGSRsiBiAAIAZJGyIANgIAIARBBGohBCAFQQFqIQUgCyEGIAdBf2oiBw0ACyAJQQFqIQUgCSEGIAkgAUcNAAtBACAJNgKQgMCAACADQQJ0KAKQgMCAACEECyAECw==";
7
+ "AGFzbQEAAAABFgRgAX8Bf2ACf38AYAR/f39/AX9gAAADBgUAAQIBAwUDAQARBhkDfwFBgIDAAAt/AEGQoMAAC38AQYygwAALB0UGBm1lbW9yeQIABWFsbG9jAAALX19oZWFwX2Jhc2UDAQdkZWFsbG9jAAELbGV2ZW5zaHRlaW4AAgpfX2RhdGFfZW5kAwIKwwQFogEBA38CQCAADQBBAA8LQQAhAQJAQQAoAoSgwIAAIgINAEGQoMCAACECQQBBkKDAgAA2AoSgwIAACwJAAkAgAkEHakF4cSICIABqIgAgAkkNACAAPwBBEHQiA00NAUEAIQEgACADa0EQdiAAQf//A3FBAEdqQABBf0cNAQsgAQ8LQQAgADYChKDAgABBAEEAKAKIoMCAAEEBajYCiKDAgAAgAgtAAAJAIABFDQAgAUUNAEEAKAKIoMCAACIARQ0AQQAgAEF/aiIANgKIoMCAACAADQBBAEGQoMCAADYChKDAgAALC80CAQl/QX8hBAJAIAFBgAhLDQAgA0GACEsNAAJAIAENACADDwsCQCADDQAgAQ8LIANBAWohBUEAIQRBgIDAgAAhBgJAA0AgBSAERg0BIAYgBDYCACAGQQRqIQYgBEEBaiEEDAALCyAAIAFqIQdBACEIQQAoAoCAwIAAIQUCQANAIAAgB0YNASAALQAAIQFBACEEQQAgCEEBaiIINgKAgMCAACAAQQFqIQBBhIDAgAAhBiABQf8BcSEJIAghAQNAIAIgBGohCgJAIAMgBEcNACAIIQUMAgsgBEEBaiELAkAgBEGACEcNACALQYEIEIOAgIAAAAsgBiABQQFqIgQgBigCACIMQQFqIgEgBSAJIAotAABHaiIFIAEgBUkbIgUgBCAFSRsiATYCACAGQQRqIQYgCyEEIAwhBQwACwsLIANBAnQoAoCAwIAAIQQLIAQLCQAQhICAgAAACwMAAAs=";
@@ -2,8 +2,8 @@
2
2
  * String similarity — TypeScript boundary over the C wasm module.
3
3
  *
4
4
  * Powers "did you mean ...?" suggestions for unknown Telegram slash
5
- * commands and unknown CLI subcommands. The C source lives in
6
- * native/strsim-c and exports a three-function C ABI (alloc / dealloc /
5
+ * commands and unknown CLI subcommands. The Rust source lives in
6
+ * native/strsim-wasm and exports a three-function C ABI (alloc / dealloc /
7
7
  * levenshtein) — see that module's README for the contract, and
8
8
  * src/native/runtime.ts for the embedding and memory conventions
9
9
  * shared by every native module.
@@ -29,7 +29,7 @@ import {
29
29
  /** Returned by the wasm side when either input exceeds its DP buffer. */
30
30
  const OVERFLOW = 0xffffffff;
31
31
 
32
- /** The C-ABI surface exported by native/strsim-c. */
32
+ /** The C-ABI surface exported by native/strsim-wasm. */
33
33
  interface StrsimExports extends WasmCoreExports {
34
34
  levenshtein(aPtr: number, aLen: number, bPtr: number, bLen: number): number;
35
35
  }
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import { execFileSync } from "node:child_process";
15
- import type { TalonPlugin } from "../../core/plugin/index.js";
15
+ import type { TalonPlugin } from "../../core/plugin/types.js";
16
16
  import { log, logWarn } from "../../util/log.js";
17
17
 
18
18
  /**
@@ -21,7 +21,7 @@
21
21
  import { readFileSync } from "node:fs";
22
22
  import { resolve } from "node:path";
23
23
  import { fileURLToPath } from "node:url";
24
- import type { TalonPlugin } from "../../core/plugin/index.js";
24
+ import type { TalonPlugin } from "../../core/plugin/types.js";
25
25
  import { log, logWarn } from "../../util/log.js";
26
26
  import { dirs } from "../../util/paths.js";
27
27
 
@@ -18,7 +18,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
18
18
  import { resolve } from "node:path";
19
19
  import { execFile as execFileCb, execFileSync } from "node:child_process";
20
20
  import { promisify } from "node:util";
21
- import type { TalonPlugin } from "../../core/plugin/index.js";
21
+ import type { TalonPlugin } from "../../core/plugin/types.js";
22
22
  import { log, logWarn } from "../../util/log.js";
23
23
  import { dirs } from "../../util/paths.js";
24
24
 
@@ -21,7 +21,7 @@
21
21
 
22
22
  import { existsSync, readFileSync } from "node:fs";
23
23
  import { resolve } from "node:path";
24
- import type { TalonPlugin } from "../../core/plugin/index.js";
24
+ import type { TalonPlugin } from "../../core/plugin/types.js";
25
25
  import { log } from "../../util/log.js";
26
26
 
27
27
  export function createPlaywrightPlugin(config: {
@@ -22,63 +22,12 @@ import { recordError } from "../util/watchdog.js";
22
22
  import { files } from "../util/paths.js";
23
23
  import { importLegacyJson } from "./legacy-import.js";
24
24
  import * as repo from "./repositories/chat-settings-repo.js";
25
- import type { ReasoningEffortLevel } from "../core/types.js";
25
+ import type { ReasoningEffortLevel } from "../types/effort.js";
26
26
 
27
27
  export type EffortLevel = ReasoningEffortLevel;
28
28
 
29
- export type ChatSettings = {
30
- /**
31
- * Per-backend model overrides for this chat. Keyed by backend id
32
- * (`"claude"`, `"codex"`, `"openai-agents"`, etc). Each entry is the
33
- * model id the user picked on that backend.
34
- *
35
- * Switching backends preserves each side's last pick — your Codex
36
- * chat remembers `gpt-5.5`, your OpenRouter chat remembers
37
- * `meta-llama/...`. Replaces the single legacy `model` field which
38
- * couldn't differentiate per-backend choices and produced the
39
- * orphan-bug class (model from backend X persisting when switching
40
- * to backend Y).
41
- *
42
- * Resolution order (see `core/models/active-model.ts`):
43
- * 1. `modelByBackend[activeBackend]` if it validates on the catalog
44
- * 2. `backend.getDefaultModel()` (canonical for backends that have one)
45
- * 3. `config.backendDefaults[activeBackend]` (operator override)
46
- * 4. `config.model` (only when activeBackend === config.backend)
47
- * 5. null → "No model selected" UI + send guard refuses.
48
- */
49
- modelByBackend?: Record<string, string>;
50
- /**
51
- * @deprecated Single-slot model field. Retained for back-compat with
52
- * old stores; migrated into `modelByBackend` on load. New writes go
53
- * through `setChatModelForBackend` instead.
54
- */
55
- model?: string;
56
- /**
57
- * Backend override for this chat. When set, queries from this chat
58
- * route to the override backend instead of the global `config.backend`.
59
- * The backend controller refcounts pool instances, so two chats on
60
- * two different backends keep both alive concurrently.
61
- *
62
- * Stored as the registry id (e.g. `"claude"`, `"openai-agents"`).
63
- * Cleared via `setChatBackend(cid, undefined)` — chat reverts to
64
- * the global default.
65
- */
66
- backend?: string;
67
- /** Effort level override (maps to SDK thinking + effort options). */
68
- effort?: EffortLevel;
69
- /** Whether pulse is enabled for this chat. */
70
- pulse?: boolean;
71
- /** Per-chat pulse check interval in milliseconds. */
72
- pulseIntervalMs?: number;
73
- /** Last message ID checked by pulse (persisted to avoid reprocessing on restart). */
74
- pulseLastCheckMsgId?: number;
75
- /**
76
- * When true, the model picker filters to free-tier models by default.
77
- * Only meaningful for backends that report free-tier metadata (currently
78
- * `openai-agents` against OpenRouter); other backends ignore the flag.
79
- */
80
- freeOnly?: boolean;
81
- };
29
+ export type { ChatSettings } from "./repositories/chat-settings-repo.js";
30
+ import type { ChatSettings } from "./repositories/chat-settings-repo.js";
82
31
 
83
32
  // In-memory cache over the chat_settings table. Reads serve live
84
33
  // references from here; writes go through persist() so each mutation
@@ -488,9 +437,3 @@ export const EFFORT_LEVELS: EffortLevel[] = [
488
437
  "max",
489
438
  "xhigh",
490
439
  ];
491
-
492
- /**
493
- * Resolve a user-provided model name (alias or full ID) to the canonical model ID.
494
- * Delegates to the model registry; falls through unknown names unchanged.
495
- */
496
- export { resolveModelId as resolveModelName } from "../core/models/catalog.js";
@@ -20,87 +20,13 @@ import { inTransaction } from "./db.js";
20
20
  import * as repo from "./repositories/cron-repo.js";
21
21
  import { nextDueMs, type CatchupPolicy } from "../native/scheduler-core.js";
22
22
 
23
- export type CronJobType = "message" | "query";
23
+ export type {
24
+ CronJob,
25
+ CronJobType,
26
+ CronRunStatus,
27
+ } from "./repositories/cron-repo.js";
24
28
  export type { CatchupPolicy };
25
-
26
- /** Outcome of the most recent execution — surfaced in list_cron_jobs. */
27
- export type CronRunStatus = "ok" | "error";
28
-
29
- export type CronJob = {
30
- id: string;
31
- chatId: string;
32
- /**
33
- * Cron expression (5-field: minute hour day month weekday). Optional — a job
34
- * carries EITHER `schedule` (cron mode) OR `everyMs` (interval mode), never
35
- * both. The store validator (`isCronJob`) enforces exactly one.
36
- */
37
- schedule?: string;
38
- /**
39
- * Fixed interval in milliseconds (interval mode). Mutually exclusive with
40
- * `schedule`. The job fires roughly every `everyMs` after its anchor
41
- * (`lastRunAt`, else `startAt`, else `createdAt`). Wires the native
42
- * scheduler-core interval math (next-due + missed-run catch-up) directly.
43
- */
44
- everyMs?: number;
45
- /** "message" sends content as text; "query" runs content as a Claude prompt with tools */
46
- type: CronJobType;
47
- /** The message text or query prompt */
48
- content: string;
49
- /** Human-readable name for the job */
50
- name: string;
51
- enabled: boolean;
52
- createdAt: number;
53
- lastRunAt?: number;
54
- runCount: number;
55
- /** IANA timezone (e.g. "America/New_York"). Defaults to system timezone. */
56
- timezone?: string;
57
- /**
58
- * Optional model override for `query` jobs. Unset = the chat's model. `query`
59
- * cron jobs run as an isolated one-shot (no chat session), so unlike triggers
60
- * the model may be on a different provider — see `provider`.
61
- */
62
- model?: string;
63
- /**
64
- * Optional provider/backend id for the override (e.g. a cheaper provider than
65
- * the chat). Requires `model`. Unset = the chat's backend. Since cron runs
66
- * isolated, a different provider is fine here.
67
- */
68
- provider?: string;
69
- /**
70
- * Optional short brief that becomes the isolated agent's system prompt — what
71
- * the job is and how to do it. Useful to orient a cheaper override model.
72
- */
73
- instructions?: string;
74
- /**
75
- * Don't fire before this epoch-ms instant (a delayed start / "not before").
76
- * Unset = eligible immediately.
77
- */
78
- startAt?: number;
79
- /**
80
- * Don't fire after this epoch-ms instant; the job auto-disables once now
81
- * passes it (a natural expiry / "until"). Unset = no end.
82
- */
83
- endAt?: number;
84
- /**
85
- * Auto-disable after this many total runs (`runCount >= maxRuns`). A value of
86
- * 1 makes the job one-shot. Unset = unbounded.
87
- */
88
- maxRuns?: number;
89
- /**
90
- * Missed-run policy for runs that were due while Talon was down:
91
- * "skip" (default) — drop them, resume on the next due tick
92
- * "once" — collapse any number of missed runs into a single catch-up
93
- * "all" — replay every missed run, capped by CATCHUP_MAX
94
- * Decided by the native scheduler-core `catchupRunCount`.
95
- */
96
- catchup?: CatchupPolicy;
97
- /** Status of the most recent execution. */
98
- lastStatus?: CronRunStatus;
99
- /** Error message from the most recent failed execution (cleared on success). */
100
- lastError?: string;
101
- /** Wall-clock duration of the most recent execution, in ms. */
102
- lastDurationMs?: number;
103
- };
29
+ import type { CronJob, CronRunStatus } from "./repositories/cron-repo.js";
104
30
 
105
31
  /** Telemetry recorded after each execution attempt. */
106
32
  export type CronRunOutcome = {
@@ -17,25 +17,16 @@
17
17
  import { randomUUID } from "node:crypto";
18
18
  import * as repo from "./repositories/goals-repo.js";
19
19
 
20
- export type GoalStatus = "active" | "paused" | "completed" | "abandoned";
21
- export type GoalPriority = "low" | "normal" | "high";
22
-
23
- export type Goal = {
24
- id: string;
25
- /** Chat the goal belongs to — progress reports route back here. */
26
- chatId: string;
27
- title: string;
28
- description?: string;
29
- status: GoalStatus;
30
- priority: GoalPriority;
31
- createdAt: number;
32
- updatedAt: number;
33
- /** Optional soft deadline (unix ms). */
34
- dueAt?: number;
35
- /** Rolling note from the last progress update. */
36
- lastProgressNote?: string;
37
- lastProgressAt?: number;
38
- };
20
+ export type {
21
+ Goal,
22
+ GoalPriority,
23
+ GoalStatus,
24
+ } from "./repositories/goals-repo.js";
25
+ import type {
26
+ Goal,
27
+ GoalPriority,
28
+ GoalStatus,
29
+ } from "./repositories/goals-repo.js";
39
30
 
40
31
  export const GOAL_STATUSES: readonly GoalStatus[] = [
41
32
  "active",