@toothfairyai/tfdesign 0.4.8 → 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.
- package/apps/web/out/404/index.html +1 -1
- package/apps/web/out/404.html +1 -1
- package/apps/web/out/__next.$oc$slug.__PAGE__.txt +3 -3
- package/apps/web/out/__next.$oc$slug.txt +3 -3
- package/apps/web/out/__next._full.txt +16 -16
- package/apps/web/out/__next._head.txt +4 -4
- package/apps/web/out/__next._index.txt +6 -7
- package/apps/web/out/__next._tree.txt +3 -3
- package/apps/web/out/_next/static/chunks/0-94cql6nl0jv.css +2 -0
- package/apps/web/out/_next/static/chunks/0-_4nyrx9a0p_.js +4586 -0
- package/apps/web/out/_next/static/chunks/012el4mxdksfa.js +1 -0
- package/apps/web/out/_next/static/chunks/033hdyh92lap-.js +1 -0
- package/apps/web/out/_next/static/chunks/{0~saitcv01ulm.js → 09kj3eb8dudq..js} +1 -3
- package/apps/web/out/_next/static/chunks/0eyc01y70gfu8.js +5 -0
- package/apps/web/out/_next/static/chunks/0gfud9-ayub5k.css +1 -0
- package/apps/web/out/_next/static/chunks/0otg4bwz5am.y.css +2 -0
- package/apps/web/out/_next/static/chunks/0qgizcmqgdduz.js +1 -0
- package/apps/web/out/_next/static/chunks/0rjd9p0m-xust.js +31 -0
- package/apps/web/out/_next/static/chunks/111k.piti8qwb.js +1 -0
- package/apps/web/out/_next/static/chunks/1434omkut_t67.js +1 -0
- package/apps/web/out/_next/static/chunks/{turbopack-0qjykn58ffuft.js → turbopack-15aa1bhzoau.r.js} +1 -3
- package/apps/web/out/_not-found/__next._full.txt +23 -19
- package/apps/web/out/_not-found/__next._head.txt +4 -4
- package/apps/web/out/_not-found/__next._index.txt +6 -7
- package/apps/web/out/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/apps/web/out/_not-found/__next._not-found.txt +3 -3
- package/apps/web/out/_not-found/__next._tree.txt +3 -3
- package/apps/web/out/_not-found/index.html +1 -1
- package/apps/web/out/_not-found/index.txt +23 -19
- package/apps/web/out/align-center.png +0 -0
- package/apps/web/out/align-right.png +0 -0
- package/apps/web/out/align-text.png +0 -0
- package/apps/web/out/app-icon.png +0 -0
- package/apps/web/out/app-icon.svg +1 -1
- package/apps/web/out/bottom-alignment.png +0 -0
- package/apps/web/out/fonts/Gilroy-Regular.woff2 +0 -0
- package/apps/web/out/fonts/Gilroy-SemiBold.woff2 +0 -0
- package/apps/web/out/html2canvas.min.js +20 -0
- package/apps/web/out/icon-add-file.png +0 -0
- package/apps/web/out/icon-copy.png +0 -0
- package/apps/web/out/icon-remove.png +0 -0
- package/apps/web/out/index.html +1 -1
- package/apps/web/out/index.txt +16 -16
- package/apps/web/out/left-alignment.png +0 -0
- package/apps/web/out/logo.svg +14 -5
- package/apps/web/out/top-alignment.png +0 -0
- package/dist/{chunk-VIER63EE.js → chunk-Q2ALQSZX.js} +590 -110
- package/dist/cli.js +20 -5
- package/dist/{server-RXVSJGZG.js → server-PZVSL7GD.js} +1 -1
- package/package.json +11 -1
- package/apps/web/out/_next/static/chunks/00.vhdzyqgugx.js.map +0 -1
- package/apps/web/out/_next/static/chunks/026q~go~dh3ct.js.map +0 -1
- package/apps/web/out/_next/static/chunks/03lp76x6ml3q7.js.map +0 -1
- package/apps/web/out/_next/static/chunks/03rwb84ufo-ml.css +0 -2
- package/apps/web/out/_next/static/chunks/03~yq9q893hmn.js.map +0 -1
- package/apps/web/out/_next/static/chunks/05z1ica28h4v4.js.map +0 -1
- package/apps/web/out/_next/static/chunks/06pwisw_r78h0.js.map +0 -1
- package/apps/web/out/_next/static/chunks/07e0~_ag4wd_2.js +0 -3
- package/apps/web/out/_next/static/chunks/0856t9l8e24kl.js +0 -3459
- package/apps/web/out/_next/static/chunks/09dn2kc7uy573.css +0 -3
- package/apps/web/out/_next/static/chunks/0_h7u0wcctdvf.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0bfs9u56d4mzi.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0bnhnqs10zvm0.js +0 -3
- package/apps/web/out/_next/static/chunks/0b~oqibb-klk8.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0dlz0h-q.e8t5.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0e2qn3b70-t2i.js +0 -34
- package/apps/web/out/_next/static/chunks/0fv_wqm4-r1hb.js +0 -12
- package/apps/web/out/_next/static/chunks/0fysc_wo2kzsu.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0i-mjuoftnsa3.js +0 -3
- package/apps/web/out/_next/static/chunks/0ix3s5zdxhkjq.js +0 -12
- package/apps/web/out/_next/static/chunks/0ovz1kz3yt45_.js +0 -33
- package/apps/web/out/_next/static/chunks/0ps7zql~4nu4c.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0qans9q_q-pop.js +0 -7
- package/apps/web/out/_next/static/chunks/0t3k3fodzjqbk.js +0 -3
- package/apps/web/out/_next/static/chunks/0vh07vpip-7jr.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0weq586uhw-26.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0y1f9xlua9~_u.css +0 -2
- package/apps/web/out/_next/static/chunks/0zvsnz1lw0thi.js.map +0 -1
- package/apps/web/out/_next/static/chunks/0~ge_azerwvf9.css.map +0 -6
- package/apps/web/out/_next/static/chunks/0~wbkak703p3z.js +0 -3
- package/apps/web/out/_next/static/chunks/11uef98fc_tfp.css.map +0 -1
- package/apps/web/out/_next/static/chunks/14hojq718_2.u.js +0 -34
- package/apps/web/out/_next/static/chunks/15j8a~eyh6.eg.css.map +0 -1
- package/apps/web/out/_next/static/chunks/16cm9w~y8~hqh.js.map +0 -1
- package/apps/web/out/_next/static/chunks/17p64lujxa85m.js +0 -3
- package/apps/web/out/agent-icons/aider.png +0 -0
- package/apps/web/out/agent-icons/amr.svg +0 -8
- package/apps/web/out/agent-icons/antigravity.svg +0 -1
- package/apps/web/out/agent-icons/claude.svg +0 -1
- package/apps/web/out/agent-icons/codex.svg +0 -1
- package/apps/web/out/agent-icons/copilot.svg +0 -1
- package/apps/web/out/agent-icons/cursor-agent.svg +0 -1
- package/apps/web/out/agent-icons/deepseek.svg +0 -1
- package/apps/web/out/agent-icons/devin.png +0 -0
- package/apps/web/out/agent-icons/gemini.svg +0 -1
- package/apps/web/out/agent-icons/grok-build.svg +0 -15
- package/apps/web/out/agent-icons/hermes.svg +0 -1
- package/apps/web/out/agent-icons/kilo.svg +0 -1
- package/apps/web/out/agent-icons/kimi.svg +0 -1
- package/apps/web/out/agent-icons/kiro.svg +0 -1
- package/apps/web/out/agent-icons/mimo.svg +0 -1
- package/apps/web/out/agent-icons/opencode.svg +0 -1
- package/apps/web/out/agent-icons/pi.svg +0 -1
- package/apps/web/out/agent-icons/qoder.svg +0 -1
- package/apps/web/out/agent-icons/qwen.svg +0 -1
- package/apps/web/out/agent-icons/reasonix.svg +0 -37
- package/apps/web/out/agent-icons/trae-cli.png +0 -0
- package/apps/web/out/agent-icons/vibe.svg +0 -1
- package/apps/web/out/brand-icon.svg +0 -5
- package/apps/web/out/desktop-pet/__next._full.txt +0 -20
- package/apps/web/out/desktop-pet/__next._head.txt +0 -6
- package/apps/web/out/desktop-pet/__next._index.txt +0 -8
- package/apps/web/out/desktop-pet/__next._tree.txt +0 -3
- package/apps/web/out/desktop-pet/__next.desktop-pet.__PAGE__.txt +0 -6
- package/apps/web/out/desktop-pet/__next.desktop-pet.txt +0 -5
- package/apps/web/out/desktop-pet/index.html +0 -1
- package/apps/web/out/desktop-pet/index.txt +0 -20
- package/apps/web/out/logo.png +0 -0
- package/apps/web/out/model-icons/black-forest-labs.svg +0 -10
- package/apps/web/out/model-icons/bytedance.svg +0 -1
- package/apps/web/out/model-icons/elevenlabs.svg +0 -1
- package/apps/web/out/model-icons/fishaudio.svg +0 -1
- package/apps/web/out/model-icons/flux.svg +0 -1
- package/apps/web/out/model-icons/google-gemini.svg +0 -1
- package/apps/web/out/model-icons/minimax.svg +0 -1
- package/apps/web/out/model-icons/openai.svg +0 -1
- package/apps/web/out/model-icons/openrouter.svg +0 -1
- package/apps/web/out/model-icons/suno.svg +0 -1
- package/apps/web/out/model-icons/x.svg +0 -1
- package/apps/web/out/official_badge.svg +0 -48
- package/apps/web/out/remixicon.woff2 +0 -0
- /package/apps/web/out/_next/static/{EOfAKklAkTBpVS50QG3jX → LZdEU9ZvDchB9VDyfg54d}/_buildManifest.js +0 -0
- /package/apps/web/out/_next/static/{EOfAKklAkTBpVS50QG3jX → LZdEU9ZvDchB9VDyfg54d}/_clientMiddlewareManifest.js +0 -0
- /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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
2035
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
14566
|
-
if (Array.isArray(
|
|
14567
|
-
for (const item of
|
|
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 =
|
|
14589
|
-
const
|
|
14590
|
-
|
|
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 (
|
|
14593
|
-
const
|
|
14594
|
-
const
|
|
14595
|
-
|
|
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 =
|
|
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
|
|
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
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
db.exec(
|
|
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 {
|
|
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))
|
|
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) ?? {
|
|
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())
|
|
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(
|
|
16772
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 &&
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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,
|