@toothfairyai/tfdesign 0.4.7 → 0.4.9-beta.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 (134) hide show
  1. package/apps/web/out/404/index.html +1 -1
  2. package/apps/web/out/404.html +1 -1
  3. package/apps/web/out/__next.$oc$slug.__PAGE__.txt +3 -3
  4. package/apps/web/out/__next.$oc$slug.txt +3 -3
  5. package/apps/web/out/__next._full.txt +16 -16
  6. package/apps/web/out/__next._head.txt +4 -4
  7. package/apps/web/out/__next._index.txt +6 -7
  8. package/apps/web/out/__next._tree.txt +3 -3
  9. package/apps/web/out/_next/static/chunks/0-94cql6nl0jv.css +2 -0
  10. package/apps/web/out/_next/static/chunks/0-_4nyrx9a0p_.js +4586 -0
  11. package/apps/web/out/_next/static/chunks/012el4mxdksfa.js +1 -0
  12. package/apps/web/out/_next/static/chunks/033hdyh92lap-.js +1 -0
  13. package/apps/web/out/_next/static/chunks/{0~saitcv01ulm.js → 09kj3eb8dudq..js} +1 -3
  14. package/apps/web/out/_next/static/chunks/0eyc01y70gfu8.js +5 -0
  15. package/apps/web/out/_next/static/chunks/0gfud9-ayub5k.css +1 -0
  16. package/apps/web/out/_next/static/chunks/0otg4bwz5am.y.css +2 -0
  17. package/apps/web/out/_next/static/chunks/0qgizcmqgdduz.js +1 -0
  18. package/apps/web/out/_next/static/chunks/0rjd9p0m-xust.js +31 -0
  19. package/apps/web/out/_next/static/chunks/111k.piti8qwb.js +1 -0
  20. package/apps/web/out/_next/static/chunks/1434omkut_t67.js +1 -0
  21. package/apps/web/out/_next/static/chunks/{turbopack-0qjykn58ffuft.js → turbopack-15aa1bhzoau.r.js} +1 -3
  22. package/apps/web/out/_not-found/__next._full.txt +23 -19
  23. package/apps/web/out/_not-found/__next._head.txt +4 -4
  24. package/apps/web/out/_not-found/__next._index.txt +6 -7
  25. package/apps/web/out/_not-found/__next._not-found.__PAGE__.txt +2 -2
  26. package/apps/web/out/_not-found/__next._not-found.txt +3 -3
  27. package/apps/web/out/_not-found/__next._tree.txt +3 -3
  28. package/apps/web/out/_not-found/index.html +1 -1
  29. package/apps/web/out/_not-found/index.txt +23 -19
  30. package/apps/web/out/align-center.png +0 -0
  31. package/apps/web/out/align-right.png +0 -0
  32. package/apps/web/out/align-text.png +0 -0
  33. package/apps/web/out/app-icon.png +0 -0
  34. package/apps/web/out/app-icon.svg +1 -1
  35. package/apps/web/out/bottom-alignment.png +0 -0
  36. package/apps/web/out/fonts/Gilroy-Regular.woff2 +0 -0
  37. package/apps/web/out/fonts/Gilroy-SemiBold.woff2 +0 -0
  38. package/apps/web/out/html2canvas.min.js +20 -0
  39. package/apps/web/out/icon-add-file.png +0 -0
  40. package/apps/web/out/icon-copy.png +0 -0
  41. package/apps/web/out/icon-remove.png +0 -0
  42. package/apps/web/out/index.html +1 -1
  43. package/apps/web/out/index.txt +16 -16
  44. package/apps/web/out/left-alignment.png +0 -0
  45. package/apps/web/out/logo.svg +14 -5
  46. package/apps/web/out/top-alignment.png +0 -0
  47. package/dist/{chunk-VIER63EE.js → chunk-Q2ALQSZX.js} +590 -110
  48. package/dist/cli.js +29 -6
  49. package/dist/{server-RXVSJGZG.js → server-PZVSL7GD.js} +1 -1
  50. package/package.json +11 -1
  51. package/apps/web/out/_next/static/chunks/00.vhdzyqgugx.js.map +0 -1
  52. package/apps/web/out/_next/static/chunks/026q~go~dh3ct.js.map +0 -1
  53. package/apps/web/out/_next/static/chunks/03lp76x6ml3q7.js.map +0 -1
  54. package/apps/web/out/_next/static/chunks/03rwb84ufo-ml.css +0 -2
  55. package/apps/web/out/_next/static/chunks/03~yq9q893hmn.js.map +0 -1
  56. package/apps/web/out/_next/static/chunks/05z1ica28h4v4.js.map +0 -1
  57. package/apps/web/out/_next/static/chunks/06pwisw_r78h0.js.map +0 -1
  58. package/apps/web/out/_next/static/chunks/07e0~_ag4wd_2.js +0 -3
  59. package/apps/web/out/_next/static/chunks/0856t9l8e24kl.js +0 -3459
  60. package/apps/web/out/_next/static/chunks/09dn2kc7uy573.css +0 -3
  61. package/apps/web/out/_next/static/chunks/0_h7u0wcctdvf.js.map +0 -1
  62. package/apps/web/out/_next/static/chunks/0bfs9u56d4mzi.js.map +0 -1
  63. package/apps/web/out/_next/static/chunks/0bnhnqs10zvm0.js +0 -3
  64. package/apps/web/out/_next/static/chunks/0b~oqibb-klk8.js.map +0 -1
  65. package/apps/web/out/_next/static/chunks/0dlz0h-q.e8t5.js.map +0 -1
  66. package/apps/web/out/_next/static/chunks/0e2qn3b70-t2i.js +0 -34
  67. package/apps/web/out/_next/static/chunks/0fv_wqm4-r1hb.js +0 -12
  68. package/apps/web/out/_next/static/chunks/0fysc_wo2kzsu.js.map +0 -1
  69. package/apps/web/out/_next/static/chunks/0i-mjuoftnsa3.js +0 -3
  70. package/apps/web/out/_next/static/chunks/0ix3s5zdxhkjq.js +0 -12
  71. package/apps/web/out/_next/static/chunks/0ovz1kz3yt45_.js +0 -33
  72. package/apps/web/out/_next/static/chunks/0ps7zql~4nu4c.js.map +0 -1
  73. package/apps/web/out/_next/static/chunks/0qans9q_q-pop.js +0 -7
  74. package/apps/web/out/_next/static/chunks/0t3k3fodzjqbk.js +0 -3
  75. package/apps/web/out/_next/static/chunks/0vh07vpip-7jr.js.map +0 -1
  76. package/apps/web/out/_next/static/chunks/0weq586uhw-26.js.map +0 -1
  77. package/apps/web/out/_next/static/chunks/0y1f9xlua9~_u.css +0 -2
  78. package/apps/web/out/_next/static/chunks/0zvsnz1lw0thi.js.map +0 -1
  79. package/apps/web/out/_next/static/chunks/0~ge_azerwvf9.css.map +0 -6
  80. package/apps/web/out/_next/static/chunks/0~wbkak703p3z.js +0 -3
  81. package/apps/web/out/_next/static/chunks/11uef98fc_tfp.css.map +0 -1
  82. package/apps/web/out/_next/static/chunks/14hojq718_2.u.js +0 -34
  83. package/apps/web/out/_next/static/chunks/15j8a~eyh6.eg.css.map +0 -1
  84. package/apps/web/out/_next/static/chunks/16cm9w~y8~hqh.js.map +0 -1
  85. package/apps/web/out/_next/static/chunks/17p64lujxa85m.js +0 -3
  86. package/apps/web/out/agent-icons/aider.png +0 -0
  87. package/apps/web/out/agent-icons/amr.svg +0 -8
  88. package/apps/web/out/agent-icons/antigravity.svg +0 -1
  89. package/apps/web/out/agent-icons/claude.svg +0 -1
  90. package/apps/web/out/agent-icons/codex.svg +0 -1
  91. package/apps/web/out/agent-icons/copilot.svg +0 -1
  92. package/apps/web/out/agent-icons/cursor-agent.svg +0 -1
  93. package/apps/web/out/agent-icons/deepseek.svg +0 -1
  94. package/apps/web/out/agent-icons/devin.png +0 -0
  95. package/apps/web/out/agent-icons/gemini.svg +0 -1
  96. package/apps/web/out/agent-icons/grok-build.svg +0 -15
  97. package/apps/web/out/agent-icons/hermes.svg +0 -1
  98. package/apps/web/out/agent-icons/kilo.svg +0 -1
  99. package/apps/web/out/agent-icons/kimi.svg +0 -1
  100. package/apps/web/out/agent-icons/kiro.svg +0 -1
  101. package/apps/web/out/agent-icons/mimo.svg +0 -1
  102. package/apps/web/out/agent-icons/opencode.svg +0 -1
  103. package/apps/web/out/agent-icons/pi.svg +0 -1
  104. package/apps/web/out/agent-icons/qoder.svg +0 -1
  105. package/apps/web/out/agent-icons/qwen.svg +0 -1
  106. package/apps/web/out/agent-icons/reasonix.svg +0 -37
  107. package/apps/web/out/agent-icons/trae-cli.png +0 -0
  108. package/apps/web/out/agent-icons/vibe.svg +0 -1
  109. package/apps/web/out/brand-icon.svg +0 -5
  110. package/apps/web/out/desktop-pet/__next._full.txt +0 -20
  111. package/apps/web/out/desktop-pet/__next._head.txt +0 -6
  112. package/apps/web/out/desktop-pet/__next._index.txt +0 -8
  113. package/apps/web/out/desktop-pet/__next._tree.txt +0 -3
  114. package/apps/web/out/desktop-pet/__next.desktop-pet.__PAGE__.txt +0 -6
  115. package/apps/web/out/desktop-pet/__next.desktop-pet.txt +0 -5
  116. package/apps/web/out/desktop-pet/index.html +0 -1
  117. package/apps/web/out/desktop-pet/index.txt +0 -20
  118. package/apps/web/out/logo.png +0 -0
  119. package/apps/web/out/model-icons/black-forest-labs.svg +0 -10
  120. package/apps/web/out/model-icons/bytedance.svg +0 -1
  121. package/apps/web/out/model-icons/elevenlabs.svg +0 -1
  122. package/apps/web/out/model-icons/fishaudio.svg +0 -1
  123. package/apps/web/out/model-icons/flux.svg +0 -1
  124. package/apps/web/out/model-icons/google-gemini.svg +0 -1
  125. package/apps/web/out/model-icons/minimax.svg +0 -1
  126. package/apps/web/out/model-icons/openai.svg +0 -1
  127. package/apps/web/out/model-icons/openrouter.svg +0 -1
  128. package/apps/web/out/model-icons/suno.svg +0 -1
  129. package/apps/web/out/model-icons/x.svg +0 -1
  130. package/apps/web/out/official_badge.svg +0 -48
  131. package/apps/web/out/remixicon.woff2 +0 -0
  132. /package/apps/web/out/_next/static/{EOfAKklAkTBpVS50QG3jX → LZdEU9ZvDchB9VDyfg54d}/_buildManifest.js +0 -0
  133. /package/apps/web/out/_next/static/{EOfAKklAkTBpVS50QG3jX → LZdEU9ZvDchB9VDyfg54d}/_clientMiddlewareManifest.js +0 -0
  134. /package/apps/web/out/_next/static/{EOfAKklAkTBpVS50QG3jX → LZdEU9ZvDchB9VDyfg54d}/_ssgManifest.js +0 -0
@@ -5,13 +5,13 @@ import {
5
5
  // apps/daemon/src/server.ts
6
6
  import express from "express";
7
7
  import multer from "multer";
8
- import { execFile as execFile5, spawn as spawn3 } from "node:child_process";
8
+ import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
9
9
  import { randomUUID as randomUUID5 } from "node:crypto";
10
10
  import { createRequire } from "node:module";
11
11
  import { fileURLToPath as fileURLToPath2 } from "node:url";
12
12
  import path29 from "node:path";
13
13
  import fs8 from "node:fs";
14
- import os4 from "node:os";
14
+ import os3 from "node:os";
15
15
  import net2 from "node:net";
16
16
 
17
17
  // apps/daemon/src/prompts/official-system.ts
@@ -345,7 +345,7 @@ For any project that is a **slide deck** (\`metadata.kind === 'deck'\` or the di
345
345
  | User's first brief (no form answered yet) | RULE 1: emit \`<question-form id="discovery">\` + STOP | TodoWrite, Write, Read |
346
346
  | \`[form answers \u2014 discovery]\` with brand = "Pick a direction for me" | Emit \`<question-form id="direction">\` + STOP | TodoWrite, Write, building slides |
347
347
  | \`[form answers \u2014 discovery]\` with brand = brand_spec / reference_match | Branch B: extract brand-spec, then RULE 2.5 | TodoWrite, building slides |
348
- | \`[form answers \u2014 discovery]\` with any other brand value | RULE 2.5 immediately | TodoWrite, building slides |
348
+ | \`[form answers \u2014 discovery]\` with brand skipped / unanswered / anything else | **Branch C: emit \`<question-form id="direction">\` + STOP** (same as Branch A \u2014 a skipped brand means the direction is still unchosen) | TodoWrite, Write, building slides |
349
349
  | \`[form answers \u2014 direction]\` | **RULE 2.5: generate 5 first-slide files + emit \`<question-form id="deck-design">\` + STOP** | TodoWrite, building full deck, writing index.html |
350
350
  | \`[form answers \u2014 deck-design]\` | Copy chosen option \u2192 canonical deck file, TodoWrite remaining slides, build, self-check, emit \`<artifact>\` | Skipping checklist or critique |
351
351
 
@@ -467,9 +467,13 @@ Run brand-spec extraction *before* TodoWrite \u2014 five steps, each in its own
467
467
 
468
468
  After the brand-spec is locked, **if this is a deck project** proceed to **RULE 2.5**. Otherwise proceed to RULE 3.
469
469
 
470
- ### Branch C \u2014 anything else (or no brand info)
470
+ ### Branch C \u2014 brand skipped / unanswered / anything else
471
+
472
+ The user gave no brand context (skipped the question, left it blank, or answered something that isn't a brand spec). That is NOT "no direction needed" \u2014 it means nobody has chosen the visual direction yet. Treat it exactly like Branch A: emit the \`<question-form id="direction">\` with direction-cards and STOP. The user picks a visual package; you bind its palette + fonts verbatim.
471
473
 
472
- If this is a deck project, proceed to **RULE 2.5**. Otherwise skip directly to RULE 3.
474
+ Only route straight to RULE 3 (or RULE 2.5 for decks) when the user's message explicitly says to skip questions / just build / no questions \u2014 never merely because the brand field was skipped.
475
+
476
+ After the direction is locked, **if this is a deck project** proceed to **RULE 2.5**. Otherwise proceed to RULE 3.
473
477
 
474
478
  ---
475
479
 
@@ -1941,7 +1945,9 @@ var AGENT_DEFS = [
1941
1945
  if (options.model && options.model !== "default") {
1942
1946
  args.push("--model", options.model);
1943
1947
  }
1944
- if (options.continueLastSession) {
1948
+ if (options.sessionId) {
1949
+ args.push("--session", options.sessionId);
1950
+ } else if (options.continueLastSession) {
1945
1951
  args.push("--continue");
1946
1952
  }
1947
1953
  args.push("-");
@@ -1949,7 +1955,10 @@ var AGENT_DEFS = [
1949
1955
  },
1950
1956
  promptViaStdin: true,
1951
1957
  streamFormat: "json-event-stream",
1952
- eventParser: "opencode"
1958
+ // Rust tfcode (3.0.83+) emits one pretty-printed JSON blob
1959
+ // ({ info, parts }) instead of the TS CLI's line-delimited event
1960
+ // stream — the tfcode-rust parser handles that shape.
1961
+ eventParser: "tfcode-rust"
1953
1962
  }
1954
1963
  ];
1955
1964
  var TOOLCHAIN_DIR_CACHE_TTL_MS = 5e3;
@@ -2031,14 +2040,8 @@ async function fetchModels(def, resolvedBin, env) {
2031
2040
  }
2032
2041
  if (!def.listModels) return def.fallbackModels;
2033
2042
  try {
2034
- const { stdout } = await execFileP(resolvedBin, def.listModels.args, {
2035
- env,
2036
- timeout: def.listModels.timeoutMs ?? 5e3,
2037
- // Models lists from popular CLIs (e.g. opencode) easily exceed the
2038
- // default 1MB buffer once you include every openrouter model. Bump
2039
- // it so we don't truncate the listing.
2040
- maxBuffer: 8 * 1024 * 1024
2041
- });
2043
+ const probe2 = probeInvocation(resolvedBin, def.listModels.args, env);
2044
+ const { stdout } = await execFileP(probe2.command, probe2.args, probe2.options);
2042
2045
  const parsed = def.listModels.parse(stdout);
2043
2046
  if (!parsed || parsed.length === 0) return def.fallbackModels;
2044
2047
  return parsed;
@@ -2096,6 +2099,23 @@ async function probe(def, configuredEnv = {}) {
2096
2099
  version
2097
2100
  };
2098
2101
  }
2102
+ function probeInvocation(resolvedBin, args, env) {
2103
+ const invocation = createCommandInvocation({
2104
+ command: resolvedBin,
2105
+ args,
2106
+ env
2107
+ });
2108
+ return {
2109
+ command: invocation.command,
2110
+ args: invocation.args,
2111
+ options: {
2112
+ env,
2113
+ timeout: 8e3,
2114
+ maxBuffer: 8 * 1024 * 1024,
2115
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
2116
+ }
2117
+ };
2118
+ }
2099
2119
  function stripFns(def) {
2100
2120
  const {
2101
2121
  buildArgs,
@@ -3288,6 +3308,159 @@ async function startBackgroundTfcodeSession(input) {
3288
3308
  };
3289
3309
  }
3290
3310
 
3311
+ // apps/daemon/src/tfcode-engine-pool.ts
3312
+ import { spawn as spawn2 } from "node:child_process";
3313
+ import { createConnection } from "node:net";
3314
+ import { resolve as resolvePath } from "node:path";
3315
+ import { existsSync as existsSync3, realpathSync } from "node:fs";
3316
+ var IDLE_MS = 30 * 60 * 1e3;
3317
+ var pool = /* @__PURE__ */ new Map();
3318
+ function resolveTfcodeBin() {
3319
+ const bundled = process.env.TFCODE_BIN;
3320
+ if (bundled && existsSync3(bundled)) return bundled;
3321
+ return resolveAgentBin("tfcode");
3322
+ }
3323
+ function waitPort(port, timeoutMs) {
3324
+ const deadline = Date.now() + timeoutMs;
3325
+ return new Promise((resolvePromise, reject) => {
3326
+ const attempt = () => {
3327
+ const sock = createConnection({ port, host: "127.0.0.1" }, () => {
3328
+ sock.destroy();
3329
+ resolvePromise();
3330
+ });
3331
+ sock.on("error", () => {
3332
+ sock.destroy();
3333
+ if (Date.now() > deadline) {
3334
+ reject(new Error(`engine did not listen on ${port} within ${timeoutMs}ms`));
3335
+ } else {
3336
+ setTimeout(attempt, 250);
3337
+ }
3338
+ });
3339
+ };
3340
+ attempt();
3341
+ });
3342
+ }
3343
+ function reapIdle() {
3344
+ const now = Date.now();
3345
+ for (const [cwd, entry] of pool) {
3346
+ if (entry.spawning) continue;
3347
+ if (entry.child.exitCode != null || now - entry.lastUsed > IDLE_MS) {
3348
+ killEntry(entry);
3349
+ pool.delete(cwd);
3350
+ }
3351
+ }
3352
+ }
3353
+ var reapTimer = setInterval(reapIdle, Math.min(IDLE_MS, 5 * 60 * 1e3));
3354
+ if (typeof reapTimer.unref === "function") reapTimer.unref();
3355
+ function poolKey(cwd) {
3356
+ const resolved = resolvePath(cwd);
3357
+ try {
3358
+ return realpathSync.native(resolved);
3359
+ } catch {
3360
+ return resolved;
3361
+ }
3362
+ }
3363
+ function killEntry(entry) {
3364
+ try {
3365
+ if (entry.child.exitCode == null) entry.child.kill();
3366
+ } catch {
3367
+ }
3368
+ }
3369
+ function releaseEngine(cwd) {
3370
+ const key = poolKey(cwd);
3371
+ const entry = pool.get(key);
3372
+ if (!entry) return;
3373
+ killEntry(entry);
3374
+ pool.delete(key);
3375
+ }
3376
+ function releaseAllEngines() {
3377
+ for (const [key, entry] of pool) {
3378
+ killEntry(entry);
3379
+ pool.delete(key);
3380
+ }
3381
+ }
3382
+ async function acquireEngineUrl(cwd) {
3383
+ const key = poolKey(cwd);
3384
+ reapIdle();
3385
+ const existing = pool.get(key);
3386
+ if (existing && !existing.spawning) {
3387
+ if (existing.child.exitCode == null) {
3388
+ existing.lastUsed = Date.now();
3389
+ return existing.url;
3390
+ }
3391
+ pool.delete(key);
3392
+ }
3393
+ const bin = resolveTfcodeBin();
3394
+ if (!bin) throw new Error("tfcode binary not found for warm engine");
3395
+ const spawning = existing?.spawning ?? (async () => {
3396
+ const invocation = createCommandInvocation({
3397
+ command: bin,
3398
+ args: ["serve", "--port", "0"],
3399
+ // TFCODE_HEADLESS marks this engine as having no interactive
3400
+ // client: permission asks auto-allow (the daemon's runs use
3401
+ // --dangerously-skip-permissions) and client-dependent tools
3402
+ // (question) stay hidden — without it a non-readonly tool call
3403
+ // (e.g. browser open) parks the turn forever on an ask nobody
3404
+ // can answer.
3405
+ env: { ...process.env, TFCODE_HEADLESS: "1" }
3406
+ });
3407
+ const child = spawn2(invocation.command, invocation.args, {
3408
+ cwd: key,
3409
+ stdio: ["ignore", "pipe", "pipe"],
3410
+ windowsHide: true,
3411
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments
3412
+ });
3413
+ let errTail = "";
3414
+ child.stderr.on("data", (chunk) => {
3415
+ errTail = (errTail + chunk.toString("utf8")).slice(-2e3);
3416
+ });
3417
+ try {
3418
+ const url = await new Promise((resolveUrl, rejectUrl) => {
3419
+ let out = "";
3420
+ const timer = setTimeout(
3421
+ () => rejectUrl(new Error("warm engine: no listening line within 60s")),
3422
+ 6e4
3423
+ );
3424
+ const onData = (chunk) => {
3425
+ out += chunk.toString("utf8");
3426
+ const m = out.match(/tfcode-rs listening on (http:\/\/[^\s]+)/);
3427
+ if (m && m[1]) {
3428
+ clearTimeout(timer);
3429
+ resolveUrl(m[1]);
3430
+ }
3431
+ };
3432
+ child.stdout.on("data", onData);
3433
+ child.once("exit", (code) => {
3434
+ clearTimeout(timer);
3435
+ rejectUrl(
3436
+ new Error(
3437
+ `warm engine exited during startup (code ${code})${errTail.trim() ? `
3438
+ ${errTail.trim()}` : ""}`
3439
+ )
3440
+ );
3441
+ });
3442
+ child.once("error", (err) => {
3443
+ clearTimeout(timer);
3444
+ rejectUrl(err);
3445
+ });
3446
+ });
3447
+ const port = Number(new URL(url).port);
3448
+ await waitPort(port, 15e3);
3449
+ pool.set(key, { child, url, lastUsed: Date.now(), spawning: null });
3450
+ return url;
3451
+ } catch (err) {
3452
+ child.kill();
3453
+ throw err;
3454
+ }
3455
+ })();
3456
+ if (existing) existing.spawning = spawning;
3457
+ try {
3458
+ return await spawning;
3459
+ } finally {
3460
+ if (pool.get(key)) pool.get(key).spawning = null;
3461
+ }
3462
+ }
3463
+
3291
3464
  // apps/daemon/src/linked-dirs.ts
3292
3465
  import path6 from "node:path";
3293
3466
  import fs from "node:fs";
@@ -3344,7 +3517,7 @@ function validateLinkedDirs(dirs) {
3344
3517
  }
3345
3518
 
3346
3519
  // apps/daemon/src/project-base-dir.ts
3347
- import { realpathSync, statSync } from "node:fs";
3520
+ import { realpathSync as realpathSync2, statSync } from "node:fs";
3348
3521
  import path7 from "node:path";
3349
3522
  var BLOCKED_CANONICAL2 = (() => {
3350
3523
  const raw = process.platform === "win32" ? ["C:\\Windows", "C:\\Program Files", "C:\\Program Files (x86)"] : ["/", "/etc", "/proc", "/sys", "/dev", "/boot", "/var", "/usr", "/bin", "/sbin", "/lib"];
@@ -3352,7 +3525,7 @@ var BLOCKED_CANONICAL2 = (() => {
3352
3525
  for (const p of raw) {
3353
3526
  if (!p) continue;
3354
3527
  try {
3355
- set.add(realpathSync.native(p));
3528
+ set.add(realpathSync2.native(p));
3356
3529
  } catch {
3357
3530
  }
3358
3531
  }
@@ -3387,7 +3560,7 @@ function validateProjectBaseDir(candidate, opts = {}) {
3387
3560
  }
3388
3561
  let real;
3389
3562
  try {
3390
- real = realpathSync.native(path7.resolve(candidate));
3563
+ real = realpathSync2.native(path7.resolve(candidate));
3391
3564
  } catch {
3392
3565
  return {
3393
3566
  ok: false,
@@ -11684,6 +11857,124 @@ function handleCodexEvent(obj, onEvent, state) {
11684
11857
  }
11685
11858
  return false;
11686
11859
  }
11860
+ function snapshotDelta(prev, text) {
11861
+ if (text.startsWith(prev)) return text.slice(prev.length);
11862
+ if (prev.startsWith(text)) return "";
11863
+ let i = 0;
11864
+ while (i < prev.length && i < text.length && prev[i] === text[i]) i += 1;
11865
+ return text.slice(i);
11866
+ }
11867
+ function emitTfcodeRustPart(part, onEvent, state, sessionKey) {
11868
+ if (!part || typeof part !== "object") return;
11869
+ if (part.type === "text" && typeof part.text === "string" && part.text.length > 0) {
11870
+ const pid = typeof part.id === "string" ? part.id : `${sessionKey}:text`;
11871
+ if (!state.tfcodeRustTextSnapshots) state.tfcodeRustTextSnapshots = /* @__PURE__ */ new Map();
11872
+ const prev = state.tfcodeRustTextSnapshots.get(pid) ?? "";
11873
+ const delta = snapshotDelta(prev, part.text);
11874
+ if (delta) onEvent({ type: "text_delta", delta });
11875
+ state.tfcodeRustTextSnapshots.set(pid, part.text);
11876
+ } else if (part.type === "reasoning" && typeof part.text === "string" && part.text.length > 0) {
11877
+ const pid = typeof part.id === "string" ? part.id : `${sessionKey}:reasoning`;
11878
+ if (!state.tfcodeRustReasoningSnapshots) {
11879
+ state.tfcodeRustReasoningSnapshots = /* @__PURE__ */ new Map();
11880
+ }
11881
+ const prev = state.tfcodeRustReasoningSnapshots.get(pid) ?? "";
11882
+ const delta = snapshotDelta(prev, part.text);
11883
+ if (delta) onEvent({ type: "thinking_delta", delta });
11884
+ state.tfcodeRustReasoningSnapshots.set(pid, part.text);
11885
+ } else if (part.type === "tool" && typeof part.tool === "string") {
11886
+ const callID = typeof part.callID === "string" ? part.callID : part.id;
11887
+ if (!callID) return;
11888
+ const toolState = part.state && typeof part.state === "object" ? part.state : {};
11889
+ const key = `${sessionKey}:${callID}`;
11890
+ if (!state.openCodeToolUses.has(key)) {
11891
+ state.openCodeToolUses.add(key);
11892
+ onEvent({
11893
+ type: "tool_use",
11894
+ id: callID,
11895
+ name: part.tool,
11896
+ input: safeParseJson(toolState.input) ?? toolState.input ?? null
11897
+ });
11898
+ }
11899
+ if (toolState.status === "completed" || toolState.status === "error") {
11900
+ if (!state.tfcodeRustToolResults) state.tfcodeRustToolResults = /* @__PURE__ */ new Set();
11901
+ if (!state.tfcodeRustToolResults.has(key)) {
11902
+ state.tfcodeRustToolResults.add(key);
11903
+ onEvent({
11904
+ type: "tool_result",
11905
+ toolUseId: callID,
11906
+ content: stringifyContent(toolState.output),
11907
+ isError: toolState.status === "error"
11908
+ });
11909
+ }
11910
+ }
11911
+ }
11912
+ }
11913
+ function handleTfcodeRustEvent(obj, onEvent, state) {
11914
+ if (!obj || typeof obj !== "object") return false;
11915
+ const ty = typeof obj.type === "string" ? obj.type : "";
11916
+ const props = obj.properties && typeof obj.properties === "object" ? obj.properties : {};
11917
+ if (ty === "message.part.updated" && props.part) {
11918
+ if (props.part.type === "step-start") {
11919
+ onEvent({ type: "status", label: "running" });
11920
+ }
11921
+ emitTfcodeRustPart(props.part, onEvent, state, props.sessionID || "session");
11922
+ return true;
11923
+ }
11924
+ if (ty === "message.updated" && props.info) {
11925
+ const info = props.info;
11926
+ const usage = formatOpenCodeUsage(info.tokens);
11927
+ if (usage) {
11928
+ onEvent({
11929
+ type: "usage",
11930
+ usage,
11931
+ costUsd: typeof info.cost === "number" ? info.cost : void 0
11932
+ });
11933
+ }
11934
+ const reason = typeof info.finish === "string" ? info.finish : null;
11935
+ if (reason && reason !== "stop" && reason !== "tool-calls" && reason !== "length") {
11936
+ onEvent({ type: "error", message: `Agent step ended abnormally (${reason})` });
11937
+ } else if (reason === "length") {
11938
+ onEvent({
11939
+ type: "status",
11940
+ label: 'step ended on the token limit \u2014 reply "continue" to resume'
11941
+ });
11942
+ }
11943
+ return true;
11944
+ }
11945
+ if (ty === "error") {
11946
+ const message = obj.error && typeof obj.error === "object" && obj.error.data?.message || obj.error && typeof obj.error === "object" && obj.error.name || "tfcode error";
11947
+ onEvent({ type: "raw", line: stringifyContent({ type: "error", message }) });
11948
+ return true;
11949
+ }
11950
+ return false;
11951
+ }
11952
+ function handleTfcodeRustBlob(obj, onEvent, state) {
11953
+ if (!obj || typeof obj !== "object" || !Array.isArray(obj.parts)) return false;
11954
+ onEvent({ type: "status", label: "running" });
11955
+ for (const part of obj.parts) {
11956
+ emitTfcodeRustPart(part, onEvent, state, obj.info?.sessionID || "session");
11957
+ }
11958
+ const info = obj.info && typeof obj.info === "object" ? obj.info : {};
11959
+ const usage = formatOpenCodeUsage(info.tokens);
11960
+ if (usage) {
11961
+ onEvent({
11962
+ type: "usage",
11963
+ usage,
11964
+ costUsd: typeof info.cost === "number" ? info.cost : void 0
11965
+ });
11966
+ }
11967
+ const reason = typeof info.finish === "string" ? info.finish : null;
11968
+ if (reason && reason !== "stop" && reason !== "tool-calls" && reason !== "length") {
11969
+ onEvent({ type: "error", message: `Agent step ended abnormally (${reason})` });
11970
+ } else if (reason === "length") {
11971
+ onEvent({
11972
+ type: "status",
11973
+ label: 'step ended on the token limit \u2014 reply "continue" to resume'
11974
+ });
11975
+ }
11976
+ return true;
11977
+ }
11687
11978
  function createJsonEventStreamHandler(kind, onEvent) {
11688
11979
  let buffer = "";
11689
11980
  const state = {
@@ -11691,6 +11982,48 @@ function createJsonEventStreamHandler(kind, onEvent) {
11691
11982
  openCodeToolUses: /* @__PURE__ */ new Set(),
11692
11983
  codexToolUses: /* @__PURE__ */ new Set()
11693
11984
  };
11985
+ if (kind === "tfcode-rust") {
11986
+ let sawStreamEvent = false;
11987
+ let pending = "";
11988
+ return {
11989
+ feed(chunk) {
11990
+ buffer += chunk;
11991
+ let nl;
11992
+ while ((nl = buffer.indexOf("\n")) !== -1) {
11993
+ const line = buffer.slice(0, nl).trim();
11994
+ buffer = buffer.slice(nl + 1);
11995
+ if (!line) continue;
11996
+ let obj;
11997
+ try {
11998
+ obj = JSON.parse(line);
11999
+ } catch {
12000
+ pending += line + "\n";
12001
+ continue;
12002
+ }
12003
+ if (handleTfcodeRustEvent(obj, onEvent, state)) {
12004
+ sawStreamEvent = true;
12005
+ }
12006
+ }
12007
+ },
12008
+ flush() {
12009
+ const rem = (pending + buffer).trim();
12010
+ pending = "";
12011
+ buffer = "";
12012
+ if (!rem) return;
12013
+ if (sawStreamEvent) return;
12014
+ let obj;
12015
+ try {
12016
+ obj = JSON.parse(rem);
12017
+ } catch {
12018
+ onEvent({ type: "raw", line: rem });
12019
+ return;
12020
+ }
12021
+ if (!handleTfcodeRustBlob(obj, onEvent, state)) {
12022
+ onEvent({ type: "raw", line: rem });
12023
+ }
12024
+ }
12025
+ };
12026
+ }
11694
12027
  function handleLine(line) {
11695
12028
  let obj;
11696
12029
  try {
@@ -11703,6 +12036,7 @@ function createJsonEventStreamHandler(kind, onEvent) {
11703
12036
  if (kind === "gemini" && handleGeminiEvent(obj, onEvent)) return;
11704
12037
  if (kind === "cursor-agent" && handleCursorEvent(obj, onEvent, state)) return;
11705
12038
  if (kind === "codex" && handleCodexEvent(obj, onEvent, state)) return;
12039
+ if (kind === "tfcode-rust" && handleTfcodeRustEvent(obj, onEvent, state)) return;
11706
12040
  onEvent({ type: "raw", line });
11707
12041
  }
11708
12042
  function feed(chunk) {
@@ -14482,8 +14816,7 @@ function isGlobalThemeScopeSelector(s) {
14482
14816
 
14483
14817
  // apps/daemon/src/media.ts
14484
14818
  import { mkdir as mkdir9, mkdtemp as mkdtemp3, readFile as readFile8, rm as rm6, stat as stat7, writeFile as writeFile9 } from "node:fs/promises";
14485
- import { execFile as execFileCb, spawn as spawn2 } from "node:child_process";
14486
- import os2 from "node:os";
14819
+ import { execFile as execFileCb, spawn as spawn3 } from "node:child_process";
14487
14820
  import path19 from "node:path";
14488
14821
  import { promisify as promisify3 } from "node:util";
14489
14822
 
@@ -14496,7 +14829,8 @@ var ENV_KEYS = {
14496
14829
  toothfairyai: ["OD_TOOTHFAIRYAI_API_KEY", "TOOTHFAIRYAI_API_KEY"]
14497
14830
  };
14498
14831
  function resolveOverrideDir(raw, projectRoot) {
14499
- const expanded = raw.startsWith("~/") ? path18.join(homedir3(), raw.slice(2)) : raw;
14832
+ const isHomeRelative = raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\");
14833
+ const expanded = isHomeRelative ? path18.join(tfHomeDir(), raw.slice(1).replace(/^[\\/]+/, "")) : raw;
14500
14834
  return path18.isAbsolute(expanded) ? expanded : path18.resolve(projectRoot, expanded);
14501
14835
  }
14502
14836
  function envOverrideDir(envName, projectRoot) {
@@ -14562,9 +14896,9 @@ function tokenFromHermesAuth(data) {
14562
14896
  "access_token"
14563
14897
  ]);
14564
14898
  if (providerToken) return providerToken;
14565
- const pool = data && typeof data === "object" ? data.credential_pool && data.credential_pool["openai-codex"] : null;
14566
- if (Array.isArray(pool)) {
14567
- for (const item of pool) {
14899
+ const pool2 = data && typeof data === "object" ? data.credential_pool && data.credential_pool["openai-codex"] : null;
14900
+ if (Array.isArray(pool2)) {
14901
+ for (const item of pool2) {
14568
14902
  const token = readNestedString(item, ["access_token"]);
14569
14903
  if (token) return token;
14570
14904
  }
@@ -14584,20 +14918,70 @@ var TF_REGION_BASE_URLS = {
14584
14918
  eu: "https://api.eu.toothfairyai.com",
14585
14919
  us: "https://api.us.toothfairyai.com"
14586
14920
  };
14921
+ function tfHomeDir() {
14922
+ return process.env.HOME || homedir3();
14923
+ }
14924
+ async function readTfcodeCredentialFile(filePath) {
14925
+ const data = await readJsonIfPresent(filePath);
14926
+ if (!data || typeof data !== "object") return null;
14927
+ const apiKey = typeof data.api_key === "string" ? data.api_key : "";
14928
+ const workspaceId = typeof data.workspace_id === "string" ? data.workspace_id : "";
14929
+ const region = typeof data.region === "string" ? data.region : "";
14930
+ if (!apiKey) return null;
14931
+ return { apiKey, workspaceId, region };
14932
+ }
14933
+ function readTfcodeProfileShape(data) {
14934
+ if (!data || typeof data !== "object") return null;
14935
+ const apiKey = typeof data.api_key === "string" ? data.api_key : "";
14936
+ if (!apiKey) return null;
14937
+ return {
14938
+ apiKey,
14939
+ workspaceId: typeof data.workspace_id === "string" ? data.workspace_id : "",
14940
+ region: typeof data.region === "string" ? data.region : ""
14941
+ };
14942
+ }
14587
14943
  async function resolveToothFairyAICredential() {
14588
- const home = homedir3();
14589
- const tfcodeConfig = await readJsonIfPresent(
14590
- path18.join(home, ".tfcode", "config.json")
14944
+ const home = tfHomeDir();
14945
+ const dataDir = process.env.TFCODE_DATA_DIR ? process.env.TFCODE_DATA_DIR : path18.join(process.env.XDG_DATA_HOME || path18.join(home, ".local", "share"), "tfcode");
14946
+ const profilesData = await readJsonIfPresent(
14947
+ path18.join(dataDir, ".tfcode", "profiles.json")
14591
14948
  );
14592
- if (tfcodeConfig && typeof tfcodeConfig.api_key === "string" && tfcodeConfig.api_key) {
14593
- const region = tfcodeConfig.region || "dev";
14594
- const baseUrl = TF_REGION_BASE_URLS[region] || TF_REGION_BASE_URLS.dev;
14595
- return { apiKey: tfcodeConfig.api_key, baseUrl, source: "tfcode-config" };
14949
+ if (profilesData && typeof profilesData === "object") {
14950
+ const profiles = profilesData.profiles && typeof profilesData.profiles === "object" ? profilesData.profiles : null;
14951
+ const activeName = typeof profilesData.default === "string" && profilesData.default ? profilesData.default : "default";
14952
+ const active = profiles ? profiles[activeName] || profiles["default"] || null : null;
14953
+ const fromProfiles = readTfcodeProfileShape(active);
14954
+ if (fromProfiles) {
14955
+ return {
14956
+ apiKey: fromProfiles.apiKey,
14957
+ baseUrl: TF_REGION_BASE_URLS[fromProfiles.region] || TF_REGION_BASE_URLS.dev,
14958
+ source: "tfcode-profiles",
14959
+ workspaceId: fromProfiles.workspaceId,
14960
+ region: fromProfiles.region || "dev"
14961
+ };
14962
+ }
14963
+ }
14964
+ for (const [file, source] of [
14965
+ ["credentials.json", "tfcode-credentials"],
14966
+ ["config.json", "tfcode-config"]
14967
+ ]) {
14968
+ const legacy = await readTfcodeCredentialFile(
14969
+ path18.join(home, ".tfcode", file)
14970
+ );
14971
+ if (legacy) {
14972
+ return {
14973
+ apiKey: legacy.apiKey,
14974
+ baseUrl: TF_REGION_BASE_URLS[legacy.region] || TF_REGION_BASE_URLS.dev,
14975
+ source,
14976
+ workspaceId: legacy.workspaceId,
14977
+ region: legacy.region || "dev"
14978
+ };
14979
+ }
14596
14980
  }
14597
14981
  return null;
14598
14982
  }
14599
14983
  async function resolveOpenAIOAuthCredential() {
14600
- const home = homedir3();
14984
+ const home = tfHomeDir();
14601
14985
  const hermesAuth = await readJsonIfPresent(
14602
14986
  path18.join(home, ".hermes", "auth.json")
14603
14987
  );
@@ -14623,6 +15007,14 @@ async function resolveProviderConfig(projectRoot, providerId) {
14623
15007
  return {
14624
15008
  apiKey: envKey || entry.apiKey || oauth?.apiKey || tfCreds?.apiKey || "",
14625
15009
  baseUrl: entry.baseUrl || tfCreds?.baseUrl || "",
15010
+ // ToothFairyAI media calls also need the workspace id + region; the
15011
+ // tfcode credential sources carry them alongside the key.
15012
+ // When the key came from tfcode, use ITS region default ("au", matching
15013
+ // rust/src/profile.rs::from_json) so media hits the same endpoint
15014
+ // inference does; a key stored in the daemon's own media config keeps the
15015
+ // legacy "dev" default.
15016
+ workspaceId: tfCreds?.workspaceId || "",
15017
+ region: tfCreds ? tfCreds.region || "au" : "dev",
14626
15018
  ...typeof entry.model === "string" && entry.model.trim() ? { model: entry.model.trim() } : {}
14627
15019
  };
14628
15020
  }
@@ -15137,17 +15529,10 @@ async function renderToothFairyMedia(ctx, credentials) {
15137
15529
  const baseUrl = (credentials.baseUrl || "https://api.toothfairylab.link").replace(/\/$/, "");
15138
15530
  const apiKey = credentials.apiKey;
15139
15531
  if (!apiKey) {
15140
- throw new Error("No ToothFairyAI API key \u2014 configure via Settings or ensure ~/.tfcode/config.json has api_key");
15141
- }
15142
- let workspaceId = "";
15143
- let region = "dev";
15144
- try {
15145
- const tfcodeConfigPath = path19.join(os2.homedir(), ".tfcode", "config.json");
15146
- const tfcodeConfig = JSON.parse(await readFile8(tfcodeConfigPath, "utf8"));
15147
- workspaceId = tfcodeConfig.workspace_id || "";
15148
- region = tfcodeConfig.region || "dev";
15149
- } catch {
15532
+ throw new Error("No ToothFairyAI API key \u2014 configure via Settings or sign in to tfcode (profiles.json / ~/.tfcode/credentials.json)");
15150
15533
  }
15534
+ const workspaceId = credentials.workspaceId || "";
15535
+ const region = credentials.region || "dev";
15151
15536
  const endpointUrl = `${baseUrl}/media`;
15152
15537
  if (ctx.surface === "audio") {
15153
15538
  const audioBody = {
@@ -16021,48 +16406,39 @@ function migrate(db) {
16021
16406
  CREATE INDEX IF NOT EXISTS idx_deployments_project
16022
16407
  ON deployments(project_id, updated_at DESC);
16023
16408
  `);
16024
- const cols = db.prepare(`PRAGMA table_info(projects)`).all();
16025
- if (!cols.some((c) => c.name === "metadata_json")) {
16026
- db.exec(`ALTER TABLE projects ADD COLUMN metadata_json TEXT`);
16027
- }
16028
- const messageCols = db.prepare(`PRAGMA table_info(messages)`).all();
16029
- if (!messageCols.some((c) => c.name === "agent_id")) {
16030
- db.exec(`ALTER TABLE messages ADD COLUMN agent_id TEXT`);
16031
- }
16032
- if (!messageCols.some((c) => c.name === "agent_name")) {
16033
- db.exec(`ALTER TABLE messages ADD COLUMN agent_name TEXT`);
16034
- }
16035
- if (!messageCols.some((c) => c.name === "run_id")) {
16036
- db.exec(`ALTER TABLE messages ADD COLUMN run_id TEXT`);
16037
- }
16038
- if (!messageCols.some((c) => c.name === "run_status")) {
16039
- db.exec(`ALTER TABLE messages ADD COLUMN run_status TEXT`);
16040
- }
16041
- if (!messageCols.some((c) => c.name === "last_run_event_id")) {
16042
- db.exec(`ALTER TABLE messages ADD COLUMN last_run_event_id TEXT`);
16043
- }
16044
- if (!messageCols.some((c) => c.name === "comment_attachments_json")) {
16045
- db.exec(`ALTER TABLE messages ADD COLUMN comment_attachments_json TEXT`);
16046
- }
16047
- const previewCommentCols = db.prepare(`PRAGMA table_info(preview_comments)`).all();
16048
- if (!previewCommentCols.some((c) => c.name === "selection_kind")) {
16049
- db.exec(`ALTER TABLE preview_comments ADD COLUMN selection_kind TEXT`);
16050
- }
16051
- if (!previewCommentCols.some((c) => c.name === "member_count")) {
16052
- db.exec(`ALTER TABLE preview_comments ADD COLUMN member_count INTEGER`);
16053
- }
16054
- if (!previewCommentCols.some((c) => c.name === "pod_members_json")) {
16055
- db.exec(`ALTER TABLE preview_comments ADD COLUMN pod_members_json TEXT`);
16056
- }
16057
- const deploymentCols = db.prepare(`PRAGMA table_info(deployments)`).all();
16058
- if (!deploymentCols.some((c) => c.name === "status")) {
16059
- db.exec(`ALTER TABLE deployments ADD COLUMN status TEXT NOT NULL DEFAULT 'ready'`);
16060
- }
16061
- if (!deploymentCols.some((c) => c.name === "status_message")) {
16062
- db.exec(`ALTER TABLE deployments ADD COLUMN status_message TEXT`);
16063
- }
16064
- if (!deploymentCols.some((c) => c.name === "reachable_at")) {
16065
- db.exec(`ALTER TABLE deployments ADD COLUMN reachable_at INTEGER`);
16409
+ const addColumn = (table, column, ddl) => {
16410
+ const cols = db.prepare(`PRAGMA table_info(${table})`).all();
16411
+ if (cols.some((c) => c.name === column)) return;
16412
+ try {
16413
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
16414
+ } catch (err) {
16415
+ if (!/duplicate column name/i.test(String(err.message)))
16416
+ throw err;
16417
+ }
16418
+ };
16419
+ db.exec("BEGIN IMMEDIATE");
16420
+ try {
16421
+ addColumn("projects", "metadata_json", "metadata_json TEXT");
16422
+ addColumn("messages", "agent_id", "agent_id TEXT");
16423
+ addColumn("messages", "agent_name", "agent_name TEXT");
16424
+ addColumn("messages", "run_id", "run_id TEXT");
16425
+ addColumn("messages", "run_status", "run_status TEXT");
16426
+ addColumn("messages", "last_run_event_id", "last_run_event_id TEXT");
16427
+ addColumn(
16428
+ "messages",
16429
+ "comment_attachments_json",
16430
+ "comment_attachments_json TEXT"
16431
+ );
16432
+ addColumn("preview_comments", "selection_kind", "selection_kind TEXT");
16433
+ addColumn("preview_comments", "member_count", "member_count INTEGER");
16434
+ addColumn("preview_comments", "pod_members_json", "pod_members_json TEXT");
16435
+ addColumn("deployments", "status", `status TEXT NOT NULL DEFAULT 'ready'`);
16436
+ addColumn("deployments", "status_message", "status_message TEXT");
16437
+ addColumn("deployments", "reachable_at", "reachable_at INTEGER");
16438
+ db.exec("COMMIT");
16439
+ } catch (err) {
16440
+ db.exec("ROLLBACK");
16441
+ throw err;
16066
16442
  }
16067
16443
  migrateCritique(db);
16068
16444
  }
@@ -16394,7 +16770,11 @@ function readProjectMetadataFromDisk(dir) {
16394
16770
  mtime = Math.round(fs3.statSync(path22.join(dir, html.name)).mtimeMs);
16395
16771
  } catch {
16396
16772
  }
16397
- return { name: m[1].trim(), updatedAt: mtime || void 0, createdAt: mtime || void 0 };
16773
+ return {
16774
+ name: m[1].trim(),
16775
+ updatedAt: mtime || void 0,
16776
+ createdAt: mtime || void 0
16777
+ };
16398
16778
  }
16399
16779
  } catch {
16400
16780
  }
@@ -16708,7 +17088,8 @@ function upsertPreviewComment(db, projectId, conversationId, input) {
16708
17088
  return getPreviewComment(db, projectId, conversationId, id);
16709
17089
  }
16710
17090
  function updatePreviewCommentStatus(db, projectId, conversationId, id, status) {
16711
- if (!PREVIEW_COMMENT_STATUSES.has(status)) throw new Error("invalid comment status");
17091
+ if (!PREVIEW_COMMENT_STATUSES.has(status))
17092
+ throw new Error("invalid comment status");
16712
17093
  const now = Date.now();
16713
17094
  db.prepare(
16714
17095
  `UPDATE preview_comments
@@ -16749,7 +17130,12 @@ function normalizePreviewComment(row) {
16749
17130
  selector: row.selector,
16750
17131
  label: row.label,
16751
17132
  text: row.text,
16752
- position: parseJsonOrUndef(row.positionJson) ?? { x: 0, y: 0, width: 0, height: 0 },
17133
+ position: parseJsonOrUndef(row.positionJson) ?? {
17134
+ x: 0,
17135
+ y: 0,
17136
+ width: 0,
17137
+ height: 0
17138
+ },
16753
17139
  htmlHint: row.htmlHint,
16754
17140
  selectionKind: row.selectionKind === "pod" ? "pod" : "element",
16755
17141
  memberCount: normalizedPodMembers && normalizedPodMembers.length > 0 ? normalizedPodMembers.length : Number.isFinite(row.memberCount) ? row.memberCount : void 0,
@@ -16761,15 +17147,22 @@ function normalizePreviewComment(row) {
16761
17147
  };
16762
17148
  }
16763
17149
  function cleanRequiredString(value, name) {
16764
- if (typeof value !== "string" || !value.trim()) throw new Error(`${name} required`);
17150
+ if (typeof value !== "string" || !value.trim())
17151
+ throw new Error(`${name} required`);
16765
17152
  return value.trim();
16766
17153
  }
16767
17154
  function normalizePodMembers(input) {
16768
17155
  if (!Array.isArray(input)) return [];
16769
17156
  return input.map((member) => {
16770
17157
  if (!member || typeof member !== "object") return null;
16771
- const elementId = cleanRequiredString(member.elementId, "podMember.elementId");
16772
- const selector = cleanRequiredString(member.selector, "podMember.selector");
17158
+ const elementId = cleanRequiredString(
17159
+ member.elementId,
17160
+ "podMember.elementId"
17161
+ );
17162
+ const selector = cleanRequiredString(
17163
+ member.selector,
17164
+ "podMember.selector"
17165
+ );
16773
17166
  const label = cleanRequiredString(member.label, "podMember.label");
16774
17167
  return {
16775
17168
  elementId,
@@ -22015,7 +22408,7 @@ var toolTokenRegistry = new ToolTokenRegistry();
22015
22408
  // apps/daemon/src/deploy.ts
22016
22409
  import fs6 from "node:fs";
22017
22410
  import { mkdir as mkdir12, readFile as readFile13, writeFile as writeFile12 } from "node:fs/promises";
22018
- import os3 from "node:os";
22411
+ import os2 from "node:os";
22019
22412
  import path27 from "node:path";
22020
22413
  import { randomUUID as randomUUID4 } from "node:crypto";
22021
22414
  var VERCEL_PROVIDER_ID = "vercel-self";
@@ -22031,7 +22424,7 @@ var DeployError = class extends Error {
22031
22424
  }
22032
22425
  };
22033
22426
  function deployConfigPath() {
22034
- const base = process.env.OD_USER_STATE_DIR || path27.join(os3.homedir(), ".open-design");
22427
+ const base = process.env.OD_USER_STATE_DIR || path27.join(os2.homedir(), ".open-design");
22035
22428
  return path27.join(base, "vercel.json");
22036
22429
  }
22037
22430
  async function readVercelConfig() {
@@ -22727,7 +23120,13 @@ function detectLayout(moduleDir) {
22727
23120
  const daemonDir = base === "dist" || base === "src" ? path28.dirname(moduleDir) : moduleDir;
22728
23121
  const candidate = path28.resolve(daemonDir, "../..");
22729
23122
  const repo = fs7.existsSync(path28.join(candidate, "pnpm-workspace.yaml")) && fs7.existsSync(path28.join(candidate, "apps", "daemon"));
22730
- return repo ? { root: candidate, repo: true } : { root: daemonDir, repo: false };
23123
+ if (repo) {
23124
+ return { root: candidate, repo: true };
23125
+ }
23126
+ if (fs7.existsSync(path28.join(candidate, "apps", "daemon", "package.json")) && fs7.existsSync(path28.join(candidate, "apps", "daemon", "dist")) && fs7.existsSync(path28.join(candidate, "skills"))) {
23127
+ return { root: candidate, repo: false };
23128
+ }
23129
+ return { root: daemonDir, repo: false };
22731
23130
  }
22732
23131
  function defaultDataRoot(moduleDir, cwd = process.cwd()) {
22733
23132
  const layout = detectLayout(moduleDir);
@@ -22799,7 +23198,7 @@ function composeLiveInstructionPrompt({
22799
23198
  }
22800
23199
  return parts.join("\n\n---\n\n");
22801
23200
  }
22802
- function resolveCodexGeneratedImagesDir(agentId, metadata, env = process.env, homeDir = os4.homedir()) {
23201
+ function resolveCodexGeneratedImagesDir(agentId, metadata, env = process.env, homeDir = os3.homedir()) {
22803
23202
  if (!shouldRenderCodexImagegenOverride(agentId, metadata)) return null;
22804
23203
  const rawCodexHome = typeof env?.CODEX_HOME === "string" && env.CODEX_HOME.trim().length > 0 ? env.CODEX_HOME.trim() : path29.join(homeDir, ".codex");
22805
23204
  const codexHome = rawCodexHome.startsWith("~/") ? path29.join(homeDir, rawCodexHome.slice(2)) : rawCodexHome;
@@ -22809,7 +23208,7 @@ function isMissingPathError(err) {
22809
23208
  return err && typeof err === "object" && "code" in err && err.code === "ENOENT";
22810
23209
  }
22811
23210
  function collectProtectedDirRoots(protectedDirs, {
22812
- realpathSync: realpathSync2,
23211
+ realpathSync: realpathSync3,
22813
23212
  statSync: statSync3
22814
23213
  }) {
22815
23214
  const roots = [];
@@ -22818,7 +23217,7 @@ function collectProtectedDirRoots(protectedDirs, {
22818
23217
  const resolved = path29.resolve(raw);
22819
23218
  roots.push(resolved);
22820
23219
  try {
22821
- const canonical = realpathSync2(resolved);
23220
+ const canonical = realpathSync3(resolved);
22822
23221
  try {
22823
23222
  if (statSync3(canonical).isDirectory()) roots.push(canonical);
22824
23223
  } catch {
@@ -22837,7 +23236,7 @@ function validateCodexGeneratedImagesDir(codexGeneratedImagesDir, {
22837
23236
  mkdirSync: mkdirSync2 = fs8.mkdirSync,
22838
23237
  lstatSync = fs8.lstatSync,
22839
23238
  statSync: statSync3 = fs8.statSync,
22840
- realpathSync: realpathSync2 = fs8.realpathSync.native,
23239
+ realpathSync: realpathSync3 = fs8.realpathSync.native,
22841
23240
  warn = console.warn
22842
23241
  } = {}) {
22843
23242
  if (typeof codexGeneratedImagesDir !== "string" || codexGeneratedImagesDir.trim().length === 0) {
@@ -22845,7 +23244,7 @@ function validateCodexGeneratedImagesDir(codexGeneratedImagesDir, {
22845
23244
  }
22846
23245
  const resolved = path29.resolve(codexGeneratedImagesDir);
22847
23246
  const protectedRoots = collectProtectedDirRoots(protectedDirs, {
22848
- realpathSync: realpathSync2,
23247
+ realpathSync: realpathSync3,
22849
23248
  statSync: statSync3
22850
23249
  });
22851
23250
  const warnSkipped = (reason) => warn(`[od] codex generated_images allowlist skipped: ${reason}`);
@@ -22879,7 +23278,7 @@ function validateCodexGeneratedImagesDir(codexGeneratedImagesDir, {
22879
23278
  return null;
22880
23279
  }
22881
23280
  mkdirSync2(parent, { recursive: true });
22882
- const canonicalParent = realpathSync2(parent);
23281
+ const canonicalParent = realpathSync3(parent);
22883
23282
  const canonicalCandidate = path29.join(
22884
23283
  canonicalParent,
22885
23284
  path29.basename(resolved)
@@ -22903,7 +23302,7 @@ function validateCodexGeneratedImagesDir(codexGeneratedImagesDir, {
22903
23302
  warnSkipped(`${resolved} is not a directory`);
22904
23303
  return null;
22905
23304
  }
22906
- const canonicalDir = realpathSync2(resolved);
23305
+ const canonicalDir = realpathSync3(resolved);
22907
23306
  const protectedCanonicalRoot = findContainingProtectedRoot(
22908
23307
  canonicalDir,
22909
23308
  protectedRoots
@@ -22926,14 +23325,14 @@ function resolveChatExtraAllowedDirs({
22926
23325
  brandGuidelinesDir,
22927
23326
  linkedDirs = [],
22928
23327
  codexGeneratedImagesDir,
22929
- existsSync: existsSync3 = fs8.existsSync
23328
+ existsSync: existsSync4 = fs8.existsSync
22930
23329
  }) {
22931
23330
  const isCodex = typeof agentId === "string" && agentId.trim().toLowerCase() === "codex";
22932
23331
  const candidates = isCodex ? [codexGeneratedImagesDir] : [brandGuidelinesDir, ...Array.isArray(linkedDirs) ? linkedDirs : []];
22933
23332
  return Array.from(
22934
23333
  new Set(
22935
23334
  candidates.filter(
22936
- (d) => typeof d === "string" && d.length > 0 && existsSync3(d)
23335
+ (d) => typeof d === "string" && d.length > 0 && existsSync4(d)
22937
23336
  )
22938
23337
  )
22939
23338
  );
@@ -23135,7 +23534,7 @@ var PROMPT_TEMPLATE_ASSETS_DIR = resolveDaemonResourceDir(
23135
23534
  );
23136
23535
  function resolveDataDir(raw, projectRoot) {
23137
23536
  if (!raw) return path29.join(projectRoot, ".od");
23138
- const expanded = raw.startsWith("~/") ? path29.join(os4.homedir(), raw.slice(2)) : raw;
23537
+ const expanded = raw.startsWith("~/") ? path29.join(os3.homedir(), raw.slice(2)) : raw;
23139
23538
  const resolved = path29.isAbsolute(expanded) ? expanded : path29.resolve(projectRoot, expanded);
23140
23539
  try {
23141
23540
  fs8.mkdirSync(resolved, { recursive: true });
@@ -23171,7 +23570,7 @@ function resolveProjectsDir(dataDir) {
23171
23570
  const raw = fs8.readFileSync(overrideFile, "utf8");
23172
23571
  const parsed = JSON.parse(raw);
23173
23572
  if (parsed && typeof parsed.projectsDir === "string" && parsed.projectsDir.trim()) {
23174
- const expanded = parsed.projectsDir.startsWith("~/") ? path29.join(os4.homedir(), parsed.projectsDir.slice(2)) : parsed.projectsDir;
23573
+ const expanded = parsed.projectsDir.startsWith("~/") ? path29.join(os3.homedir(), parsed.projectsDir.slice(2)) : parsed.projectsDir;
23175
23574
  const resolved = path29.isAbsolute(expanded) ? expanded : path29.resolve(dataDir, expanded);
23176
23575
  fs8.mkdirSync(resolved, { recursive: true });
23177
23576
  fs8.accessSync(resolved, fs8.constants.W_OK);
@@ -23552,7 +23951,7 @@ function openNativeFolderDialog() {
23552
23951
  function createSseErrorPayload(code, message, init = {}) {
23553
23952
  return { message, error: createCompatApiError(code, message, init) };
23554
23953
  }
23555
- var UPLOAD_DIR = path29.join(os4.tmpdir(), "od-uploads");
23954
+ var UPLOAD_DIR = path29.join(os3.tmpdir(), "od-uploads");
23556
23955
  fs8.mkdirSync(UPLOAD_DIR, { recursive: true });
23557
23956
  fs8.mkdirSync(ARTIFACTS_DIR, { recursive: true });
23558
23957
  var upload = multer({
@@ -24411,7 +24810,11 @@ async function startServer({
24411
24810
  });
24412
24811
  app.delete("/api/projects/:id", async (req, res) => {
24413
24812
  try {
24813
+ const removed = getProject(db, req.params.id);
24414
24814
  deleteProject(db, req.params.id);
24815
+ releaseEngine(
24816
+ resolveProjectDir(PROJECTS_DIR, req.params.id, removed?.metadata)
24817
+ );
24415
24818
  await removeProjectDir(PROJECTS_DIR, req.params.id).catch(() => {
24416
24819
  });
24417
24820
  const body = { ok: true };
@@ -26212,7 +26615,7 @@ async function startServer({
26212
26615
  if (typeof requested !== "string" || !requested.trim()) {
26213
26616
  return res.status(400).json({ error: "projectsDir is required" });
26214
26617
  }
26215
- const expanded = requested.startsWith("~/") ? path29.join(os4.homedir(), requested.slice(2)) : requested;
26618
+ const expanded = requested.startsWith("~/") ? path29.join(os3.homedir(), requested.slice(2)) : requested;
26216
26619
  const resolved = path29.isAbsolute(expanded) ? expanded : path29.resolve(RUNTIME_DATA_DIR, expanded);
26217
26620
  try {
26218
26621
  fs8.mkdirSync(resolved, { recursive: true });
@@ -26290,7 +26693,7 @@ async function startServer({
26290
26693
  if (typeof target !== "string" || !target.trim()) {
26291
26694
  return res.status(400).json({ error: "targetDir is required" });
26292
26695
  }
26293
- const expanded = target.startsWith("~/") ? path29.join(os4.homedir(), target.slice(2)) : target;
26696
+ const expanded = target.startsWith("~/") ? path29.join(os3.homedir(), target.slice(2)) : target;
26294
26697
  const resolvedTarget = path29.isAbsolute(expanded) ? expanded : path29.resolve(RUNTIME_DATA_DIR, expanded);
26295
26698
  fs8.mkdirSync(resolvedTarget, { recursive: true });
26296
26699
  const sourceDir = PROJECTS_DIR;
@@ -26512,6 +26915,42 @@ async function startServer({
26512
26915
  });
26513
26916
  return { prompt };
26514
26917
  };
26918
+ const engineSessionByConversation = /* @__PURE__ */ new Map();
26919
+ const engineDirectory = (cwd) => {
26920
+ try {
26921
+ return fs8.realpathSync.native(cwd);
26922
+ } catch {
26923
+ return cwd;
26924
+ }
26925
+ };
26926
+ const getOrCreateEngineSession = async (engineUrl, cwd, conversationId) => {
26927
+ const dir = engineDirectory(cwd);
26928
+ const existing = engineSessionByConversation.get(conversationId);
26929
+ if (existing) {
26930
+ try {
26931
+ const res = await fetch(`${engineUrl}/session/${existing}/message`, {
26932
+ headers: { "x-opencode-directory": dir }
26933
+ });
26934
+ if (res.ok) return existing;
26935
+ } catch {
26936
+ }
26937
+ engineSessionByConversation.delete(conversationId);
26938
+ }
26939
+ try {
26940
+ const res = await fetch(`${engineUrl}/session`, {
26941
+ method: "POST",
26942
+ headers: { "x-opencode-directory": dir, "content-type": "application/json" },
26943
+ body: JSON.stringify({ title: `design ${conversationId.slice(0, 8)}` })
26944
+ });
26945
+ if (!res.ok) return null;
26946
+ const created = await res.json();
26947
+ if (!created?.id) return null;
26948
+ engineSessionByConversation.set(conversationId, created.id);
26949
+ return created.id;
26950
+ } catch {
26951
+ return null;
26952
+ }
26953
+ };
26515
26954
  const startChatRun = async (chatBody, run) => {
26516
26955
  chatBody = chatBody || {};
26517
26956
  const {
@@ -26746,9 +27185,30 @@ ${safeImages.map((p) => `@${p}`).join(" ")}` : ""
26746
27185
  const safeModel = typeof model === "string" ? isKnownModel(def, model) ? model : sanitizeCustomModel(model) : null;
26747
27186
  const safeReasoning = typeof reasoning === "string" && Array.isArray(def.reasoningOptions) ? def.reasoningOptions.find((r) => r.id === reasoning)?.id ?? null : null;
26748
27187
  const forcedModelOptions = def.id === "tfcode" && (!safeModel || safeModel === "default") ? { model: "toothfairyai/kimi-k2p6", reasoning: safeReasoning } : { model: safeModel, reasoning: safeReasoning };
27188
+ let warmEngineActive = false;
27189
+ let warmEngineUrl = null;
27190
+ if (def.id === "tfcode" && effectiveCwd) {
27191
+ try {
27192
+ warmEngineUrl = await acquireEngineUrl(effectiveCwd);
27193
+ warmEngineActive = true;
27194
+ } catch (poolErr) {
27195
+ console.warn(
27196
+ `[tfcode-engine-pool] warm engine unavailable, falling back to embedded: ${poolErr instanceof Error ? poolErr.message : poolErr}`
27197
+ );
27198
+ }
27199
+ }
27200
+ let engineSessionId = null;
27201
+ if (def.id === "tfcode" && warmEngineUrl && conversationId) {
27202
+ engineSessionId = await getOrCreateEngineSession(
27203
+ warmEngineUrl,
27204
+ effectiveCwd,
27205
+ conversationId
27206
+ );
27207
+ }
26749
27208
  const agentOptions = {
26750
27209
  ...forcedModelOptions,
26751
- continueLastSession: Boolean(continueLastSession)
27210
+ sessionId: engineSessionId ?? void 0,
27211
+ continueLastSession: !engineSessionId && (Boolean(continueLastSession) || def.id === "tfcode" && Boolean(effectiveCwd) && warmEngineActive)
26752
27212
  };
26753
27213
  const mcpServers = buildLiveArtifactsMcpServersForAgent(def, {
26754
27214
  enabled: Boolean(toolTokenGrant?.token),
@@ -26902,14 +27362,15 @@ ${safeImages.map((p) => `@${p}`).join(" ")}` : ""
26902
27362
  ),
26903
27363
  ...odMediaEnv,
26904
27364
  ...imageModel ? { OD_IMAGE_MODEL: imageModel } : {},
26905
- ...manualEditFilesEnv
27365
+ ...manualEditFilesEnv,
27366
+ ...warmEngineUrl ? { TFCODE_SERVER: warmEngineUrl } : {}
26906
27367
  };
26907
27368
  const invocation = createCommandInvocation({
26908
27369
  command: resolvedBin,
26909
27370
  args,
26910
27371
  env
26911
27372
  });
26912
- child = spawn3(invocation.command, invocation.args, {
27373
+ child = spawn4(invocation.command, invocation.args, {
26913
27374
  env,
26914
27375
  stdio: [stdinMode, "pipe", "pipe"],
26915
27376
  cwd: effectiveCwd,
@@ -27018,6 +27479,21 @@ ${safeImages.map((p) => `@${p}`).join(" ")}` : ""
27018
27479
  }
27019
27480
  let agentStreamError = null;
27020
27481
  let sawAgentActivity = false;
27482
+ const echoGuard = def.eventParser !== "tfcode-rust";
27483
+ const MIN_ECHO = 200;
27484
+ let echoSeen = "";
27485
+ let echoDead = composed.length < MIN_ECHO;
27486
+ const isPromptEcho = (delta) => {
27487
+ if (!echoGuard || echoDead) return false;
27488
+ if (typeof delta !== "string" || delta.length === 0) return false;
27489
+ const next = echoSeen + delta;
27490
+ if (!composed.startsWith(next)) {
27491
+ echoDead = true;
27492
+ return false;
27493
+ }
27494
+ echoSeen = next;
27495
+ return true;
27496
+ };
27021
27497
  const sendAgentEvent = (ev) => {
27022
27498
  if (ev?.type === "error") {
27023
27499
  if (agentStreamError) return;
@@ -27031,6 +27507,9 @@ ${safeImages.map((p) => `@${p}`).join(" ")}` : ""
27031
27507
  );
27032
27508
  return;
27033
27509
  }
27510
+ if (ev?.type === "text_delta" && isPromptEcho(ev.delta)) {
27511
+ return;
27512
+ }
27034
27513
  if (ev?.type === "text_delta" || ev?.type === "tool_use" || ev?.type === "tool_result") {
27035
27514
  sawAgentActivity = true;
27036
27515
  }
@@ -27738,6 +28217,7 @@ function isLocalSameOrigin(req, port) {
27738
28217
  }
27739
28218
 
27740
28219
  export {
28220
+ releaseAllEngines,
27741
28221
  detectLayout,
27742
28222
  resolveDataRoot,
27743
28223
  resolveProjectRoot,