@matheuskrumenauer/tanya 0.17.0 → 0.17.6
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/dist/{chunk-5PSV2Y3X.js → chunk-3NV2QP7J.js} +576 -249
- package/dist/chunk-3NV2QP7J.js.map +1 -0
- package/dist/cli.js +91 -5
- package/dist/cli.js.map +1 -1
- package/dist/{runInkChat-AZFI7553.js → runInkChat-SVBEQCQ4.js} +76 -9
- package/dist/runInkChat-SVBEQCQ4.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-5PSV2Y3X.js.map +0 -1
- package/dist/runInkChat-AZFI7553.js.map +0 -1
|
@@ -1276,8 +1276,15 @@ import { join as join5 } from "path";
|
|
|
1276
1276
|
var ROUTES_SCHEMA_VERSION = 1;
|
|
1277
1277
|
var BUILT_IN_ROUTE_DEFAULTS = {
|
|
1278
1278
|
provider: "openai",
|
|
1279
|
-
model: "gpt-4.1-mini"
|
|
1279
|
+
model: "gpt-4.1-mini",
|
|
1280
|
+
maxInputTokens: 128e3
|
|
1280
1281
|
};
|
|
1282
|
+
var BUILT_IN_ROUTE_CASCADE = [
|
|
1283
|
+
{ provider: "deepseek", model: "deepseek-chat", maxInputTokens: 128e3 },
|
|
1284
|
+
{ provider: "openai", model: "gpt-5-codex", maxInputTokens: 2e5 },
|
|
1285
|
+
{ provider: "claude", model: "claude-sonnet-4-6", maxInputTokens: 1e6 },
|
|
1286
|
+
{ provider: "gemini", model: "gemini-1.5-pro", maxInputTokens: 2e6 }
|
|
1287
|
+
];
|
|
1281
1288
|
function builtInRouteTable(defaults = BUILT_IN_ROUTE_DEFAULTS) {
|
|
1282
1289
|
return {
|
|
1283
1290
|
version: ROUTES_SCHEMA_VERSION,
|
|
@@ -1317,7 +1324,8 @@ function builtInRouteTable(defaults = BUILT_IN_ROUTE_DEFAULTS) {
|
|
|
1317
1324
|
reasoningCap: { maxTokens: 8e3 }
|
|
1318
1325
|
}
|
|
1319
1326
|
],
|
|
1320
|
-
defaults
|
|
1327
|
+
defaults,
|
|
1328
|
+
cascade: BUILT_IN_ROUTE_CASCADE
|
|
1321
1329
|
};
|
|
1322
1330
|
}
|
|
1323
1331
|
|
|
@@ -1343,12 +1351,21 @@ function loadRouteTable(options) {
|
|
|
1343
1351
|
...sourceRoutes(user.value?.routes ?? [], "user"),
|
|
1344
1352
|
...sourceRoutes(builtIn.routes, "built-in")
|
|
1345
1353
|
];
|
|
1354
|
+
const defaultSource = project.value?.defaults ? "project" : user.value?.defaults ? "user" : "runtime-default";
|
|
1355
|
+
const defaults = project.value?.defaults ?? user.value?.defaults ?? builtIn.defaults;
|
|
1356
|
+
const cascadeSource = project.value ? "project" : user.value ? "user" : "built-in";
|
|
1357
|
+
const cascade = sourceCascade(
|
|
1358
|
+
project.value ? cascadeOrLegacyDefault(project.value) : user.value ? cascadeOrLegacyDefault(user.value) : builtIn.cascade ?? cascadeOrLegacyDefault(builtIn),
|
|
1359
|
+
cascadeSource
|
|
1360
|
+
);
|
|
1346
1361
|
return {
|
|
1347
1362
|
table: {
|
|
1348
1363
|
version: ROUTES_SCHEMA_VERSION,
|
|
1349
1364
|
routes,
|
|
1350
|
-
defaults
|
|
1351
|
-
defaultSource
|
|
1365
|
+
defaults,
|
|
1366
|
+
defaultSource,
|
|
1367
|
+
cascade,
|
|
1368
|
+
cascadeSource,
|
|
1352
1369
|
sources
|
|
1353
1370
|
},
|
|
1354
1371
|
issues
|
|
@@ -1371,14 +1388,16 @@ function validateRouteTable(input) {
|
|
|
1371
1388
|
issues.push({ path: "$.version", message: `Expected schema version ${ROUTES_SCHEMA_VERSION}.` });
|
|
1372
1389
|
}
|
|
1373
1390
|
const routes = validateRoutes(input.routes, issues);
|
|
1374
|
-
const defaults = validateTarget(input.defaults, "$.defaults", issues);
|
|
1391
|
+
const defaults = input.defaults === void 0 ? validateLegacyDefaultTarget(input, issues) : validateTarget(input.defaults, "$.defaults", issues);
|
|
1392
|
+
const cascade = input.cascade === void 0 ? void 0 : validateCascade(input.cascade, issues);
|
|
1375
1393
|
if (issues.length > 0) return { ok: false, issues };
|
|
1376
1394
|
return {
|
|
1377
1395
|
ok: true,
|
|
1378
1396
|
value: {
|
|
1379
1397
|
version: ROUTES_SCHEMA_VERSION,
|
|
1380
1398
|
routes,
|
|
1381
|
-
defaults
|
|
1399
|
+
defaults,
|
|
1400
|
+
...cascade ? { cascade } : {}
|
|
1382
1401
|
},
|
|
1383
1402
|
issues: []
|
|
1384
1403
|
};
|
|
@@ -1427,7 +1446,21 @@ function readRouteFile(file, _source) {
|
|
|
1427
1446
|
function sourceRoutes(routes, source) {
|
|
1428
1447
|
return routes.map((route) => ({ ...route, source }));
|
|
1429
1448
|
}
|
|
1449
|
+
function sourceCascade(cascade, source) {
|
|
1450
|
+
return cascade.map((route) => ({ ...route, source }));
|
|
1451
|
+
}
|
|
1452
|
+
function cascadeOrLegacyDefault(table) {
|
|
1453
|
+
return table.cascade?.length ? table.cascade : [targetToCascade(table.defaults)];
|
|
1454
|
+
}
|
|
1455
|
+
function targetToCascade(target) {
|
|
1456
|
+
return {
|
|
1457
|
+
provider: target.provider,
|
|
1458
|
+
model: target.model,
|
|
1459
|
+
maxInputTokens: target.maxInputTokens ?? contextWindowForProvider(target.provider)
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1430
1462
|
function validateRoutes(input, issues) {
|
|
1463
|
+
if (input === void 0) return [];
|
|
1431
1464
|
if (!Array.isArray(input)) {
|
|
1432
1465
|
issues.push({ path: "$.routes", message: "Expected an array." });
|
|
1433
1466
|
return [];
|
|
@@ -1450,12 +1483,31 @@ function validateRoutes(input, issues) {
|
|
|
1450
1483
|
match,
|
|
1451
1484
|
provider: target.provider,
|
|
1452
1485
|
model: target.model,
|
|
1486
|
+
...target.maxInputTokens ? { maxInputTokens: target.maxInputTokens } : {},
|
|
1453
1487
|
...fallback ? { fallback } : {},
|
|
1454
1488
|
...item.escalate !== void 0 ? { escalate: Boolean(item.escalate) } : {},
|
|
1455
1489
|
...reasoningCap ? { reasoningCap } : {}
|
|
1456
1490
|
}];
|
|
1457
1491
|
});
|
|
1458
1492
|
}
|
|
1493
|
+
function validateCascade(input, issues) {
|
|
1494
|
+
if (!Array.isArray(input)) {
|
|
1495
|
+
issues.push({ path: "$.cascade", message: "Expected an array when present." });
|
|
1496
|
+
return [];
|
|
1497
|
+
}
|
|
1498
|
+
return input.flatMap((item, index) => {
|
|
1499
|
+
const path = `$.cascade[${index}]`;
|
|
1500
|
+
const target = validateTarget(item, path, issues);
|
|
1501
|
+
const stepTypes = validateStepTypes(isRecord2(item) ? item.stepTypes ?? item.step_types : void 0, `${path}.stepTypes`, issues);
|
|
1502
|
+
if (!target || !target.maxInputTokens) return [];
|
|
1503
|
+
return [{
|
|
1504
|
+
provider: target.provider,
|
|
1505
|
+
model: target.model,
|
|
1506
|
+
maxInputTokens: target.maxInputTokens,
|
|
1507
|
+
...stepTypes ? { stepTypes } : {}
|
|
1508
|
+
}];
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1459
1511
|
function validateReasoningCap(input, path, issues) {
|
|
1460
1512
|
if (input === void 0) return null;
|
|
1461
1513
|
if (!isRecord2(input)) {
|
|
@@ -1468,6 +1520,22 @@ function validateReasoningCap(input, path, issues) {
|
|
|
1468
1520
|
}
|
|
1469
1521
|
return { maxTokens: Math.floor(input.maxTokens) };
|
|
1470
1522
|
}
|
|
1523
|
+
function validateStepTypes(input, path, issues) {
|
|
1524
|
+
if (input === void 0) return null;
|
|
1525
|
+
if (!Array.isArray(input)) {
|
|
1526
|
+
issues.push({ path, message: "Expected an array of step types when present." });
|
|
1527
|
+
return null;
|
|
1528
|
+
}
|
|
1529
|
+
const out = [];
|
|
1530
|
+
input.forEach((item, index) => {
|
|
1531
|
+
if (typeof item !== "string" || !STEP_TYPES.has(item)) {
|
|
1532
|
+
issues.push({ path: `${path}[${index}]`, message: "Expected a known step type." });
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
out.push(item);
|
|
1536
|
+
});
|
|
1537
|
+
return out.length ? out : null;
|
|
1538
|
+
}
|
|
1471
1539
|
function validateMatch(input, path, issues) {
|
|
1472
1540
|
if (typeof input === "string") {
|
|
1473
1541
|
if (!STEP_TYPES.has(input)) {
|
|
@@ -1498,26 +1566,76 @@ function validateTarget(input, path, issues) {
|
|
|
1498
1566
|
issues.push({ path, message: "Expected an object." });
|
|
1499
1567
|
return { provider: "", model: "" };
|
|
1500
1568
|
}
|
|
1501
|
-
|
|
1569
|
+
const provider = typeof input.provider === "string" && input.provider.trim() ? input.provider.trim() : typeof input.cli === "string" && input.cli.trim() ? input.cli.trim() : "";
|
|
1570
|
+
const model = typeof input.model === "string" && input.model.trim() ? input.model.trim() : "";
|
|
1571
|
+
const maxInputTokens = numericField(input.maxInputTokens ?? input.max_input_tokens);
|
|
1572
|
+
if (!provider) {
|
|
1502
1573
|
issues.push({ path: `${path}.provider`, message: "Expected a non-empty provider string." });
|
|
1503
1574
|
}
|
|
1504
|
-
if (
|
|
1575
|
+
if (!model) {
|
|
1505
1576
|
issues.push({ path: `${path}.model`, message: "Expected a non-empty model string." });
|
|
1506
1577
|
}
|
|
1578
|
+
if ((input.maxInputTokens !== void 0 || input.max_input_tokens !== void 0) && !maxInputTokens) {
|
|
1579
|
+
issues.push({ path: `${path}.maxInputTokens`, message: "Expected a positive number." });
|
|
1580
|
+
}
|
|
1507
1581
|
return {
|
|
1508
|
-
provider
|
|
1509
|
-
model
|
|
1582
|
+
provider,
|
|
1583
|
+
model,
|
|
1584
|
+
...maxInputTokens ? { maxInputTokens } : {}
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function validateLegacyDefaultTarget(input, issues) {
|
|
1588
|
+
const defaultModel = typeof input.default_model === "string" && input.default_model.trim() ? input.default_model.trim() : typeof input.defaultModel === "string" && input.defaultModel.trim() ? input.defaultModel.trim() : "";
|
|
1589
|
+
const defaultProvider = typeof input.default_cli === "string" && input.default_cli.trim() ? input.default_cli.trim() : typeof input.default_provider === "string" && input.default_provider.trim() ? input.default_provider.trim() : typeof input.defaultProvider === "string" && input.defaultProvider.trim() ? input.defaultProvider.trim() : "";
|
|
1590
|
+
if (!defaultModel) {
|
|
1591
|
+
issues.push({ path: "$.defaults", message: "Expected defaults or legacy default_model." });
|
|
1592
|
+
}
|
|
1593
|
+
const provider = defaultProvider || inferProviderForModel(defaultModel);
|
|
1594
|
+
if (!provider) {
|
|
1595
|
+
issues.push({ path: "$.default_cli", message: "Expected default_cli/default_provider for this default_model." });
|
|
1596
|
+
}
|
|
1597
|
+
const maxInputTokens = numericField(input.maxInputTokens ?? input.max_input_tokens);
|
|
1598
|
+
return {
|
|
1599
|
+
provider,
|
|
1600
|
+
model: defaultModel,
|
|
1601
|
+
...maxInputTokens ? { maxInputTokens } : {}
|
|
1510
1602
|
};
|
|
1511
1603
|
}
|
|
1512
1604
|
function routeMatches(match, stepType, text2) {
|
|
1513
1605
|
if (typeof match === "string") return match === stepType;
|
|
1514
1606
|
return new RegExp(match.regex).test(text2);
|
|
1515
1607
|
}
|
|
1608
|
+
function numericField(value) {
|
|
1609
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
1610
|
+
return Math.floor(value);
|
|
1611
|
+
}
|
|
1612
|
+
function inferProviderForModel(model) {
|
|
1613
|
+
if (/^deepseek-/i.test(model)) return "deepseek";
|
|
1614
|
+
if (/^(?:gpt-|o\d|o\d-|chatgpt)/i.test(model)) return "openai";
|
|
1615
|
+
if (/^claude-/i.test(model)) return "claude";
|
|
1616
|
+
if (/^gemini-/i.test(model)) return "gemini";
|
|
1617
|
+
if (/^qwen/i.test(model)) return "qwen";
|
|
1618
|
+
return "";
|
|
1619
|
+
}
|
|
1620
|
+
function contextWindowForProvider(provider) {
|
|
1621
|
+
switch (provider) {
|
|
1622
|
+
case "claude":
|
|
1623
|
+
return 1e6;
|
|
1624
|
+
case "gemini":
|
|
1625
|
+
return 2e6;
|
|
1626
|
+
case "openai":
|
|
1627
|
+
return 2e5;
|
|
1628
|
+
default:
|
|
1629
|
+
return 128e3;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1516
1632
|
function isRecord2(value) {
|
|
1517
1633
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1518
1634
|
}
|
|
1519
1635
|
|
|
1520
1636
|
// src/router/classify.ts
|
|
1637
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1638
|
+
import { join as join6 } from "path";
|
|
1521
1639
|
function classifyStep(state) {
|
|
1522
1640
|
const lastAssistant = state.lastAssistantMessage ?? lastMessageWithRole(state.messages ?? [], "assistant");
|
|
1523
1641
|
const pendingToolCalls = state.pendingToolCalls ?? lastAssistant?.tool_calls ?? [];
|
|
@@ -1530,8 +1648,38 @@ function classifyStep(state) {
|
|
|
1530
1648
|
if (content.trim() !== "" && pendingToolCalls.length === 0 && toolResultsSinceUser.length >= 2) {
|
|
1531
1649
|
return "synthesis";
|
|
1532
1650
|
}
|
|
1651
|
+
if (looksLikeCodeEditingTask(state)) return "tool_call";
|
|
1533
1652
|
return "unknown";
|
|
1534
1653
|
}
|
|
1654
|
+
function looksLikeCodeEditingTask(state) {
|
|
1655
|
+
const runContext = state.runContext;
|
|
1656
|
+
if (runContext?.task?.kind === "coding") return true;
|
|
1657
|
+
if (runContext?.expected_report && Object.keys(runContext.expected_report).length > 0) return true;
|
|
1658
|
+
if (runContext?.verification?.commands?.length) return true;
|
|
1659
|
+
const taskText2 = [
|
|
1660
|
+
runContext?.task?.title,
|
|
1661
|
+
runContext?.task?.summary,
|
|
1662
|
+
runContext?.stack,
|
|
1663
|
+
...runContext?.languages ?? [],
|
|
1664
|
+
...runContext?.frameworks ?? [],
|
|
1665
|
+
state.prompt,
|
|
1666
|
+
...userMessageContent(state.messages ?? [])
|
|
1667
|
+
].filter(Boolean).join("\n");
|
|
1668
|
+
if (/\bgo-backend-[a-z0-9_-]+\b/i.test(taskText2)) return true;
|
|
1669
|
+
if (/template\s*(?:id|ID)?\s*[:=]?\s*["'`]?go-backend-[a-z0-9_-]+/i.test(taskText2)) return true;
|
|
1670
|
+
if (/EXISTING CODE DETECTED/i.test(taskText2) && /\bEXECUTE\s+mode\b/i.test(taskText2)) return true;
|
|
1671
|
+
return Boolean(state.cwd && isCodeWorkspace(state.cwd) && taskText2.length > 1200);
|
|
1672
|
+
}
|
|
1673
|
+
function isCodeWorkspace(cwd) {
|
|
1674
|
+
return [
|
|
1675
|
+
"go.mod",
|
|
1676
|
+
"package.json",
|
|
1677
|
+
"Cargo.toml",
|
|
1678
|
+
"pyproject.toml",
|
|
1679
|
+
"requirements.txt",
|
|
1680
|
+
"Package.swift"
|
|
1681
|
+
].some((marker) => existsSync6(join6(cwd, marker)));
|
|
1682
|
+
}
|
|
1535
1683
|
function hasVerificationTool(toolCalls) {
|
|
1536
1684
|
return toolCalls.some((tool) => {
|
|
1537
1685
|
if (isPreferredVerification(tool)) return true;
|
|
@@ -1570,6 +1718,9 @@ function messagesSinceLastUser(messages) {
|
|
|
1570
1718
|
}
|
|
1571
1719
|
return messages;
|
|
1572
1720
|
}
|
|
1721
|
+
function userMessageContent(messages) {
|
|
1722
|
+
return messages.filter((message) => message.role === "user" && typeof message.content === "string").map((message) => message.content ?? "");
|
|
1723
|
+
}
|
|
1573
1724
|
|
|
1574
1725
|
// src/providers/adapters/deepseek.ts
|
|
1575
1726
|
var deepSeekAdapter = {
|
|
@@ -1745,13 +1896,13 @@ function normalizeProviderId(provider) {
|
|
|
1745
1896
|
|
|
1746
1897
|
// src/memory/runArchive.ts
|
|
1747
1898
|
import { appendFile, mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
|
|
1748
|
-
import { existsSync as
|
|
1749
|
-
import { join as
|
|
1899
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1900
|
+
import { join as join7 } from "path";
|
|
1750
1901
|
var appendQueues = /* @__PURE__ */ new Map();
|
|
1751
1902
|
async function appendArchive(runId, messages, options = {}) {
|
|
1752
1903
|
if (messages.length === 0) return;
|
|
1753
1904
|
const path = archivePath(options.workspace ?? process.cwd(), runId);
|
|
1754
|
-
await mkdir3(
|
|
1905
|
+
await mkdir3(join7(options.workspace ?? process.cwd(), ".tanya", "runs", runId), { recursive: true });
|
|
1755
1906
|
const payload = messages.map((message) => JSON.stringify(message)).join("\n") + "\n";
|
|
1756
1907
|
const previous = appendQueues.get(path) ?? Promise.resolve();
|
|
1757
1908
|
const next = previous.then(() => appendFile(path, payload, "utf8"));
|
|
@@ -1761,7 +1912,7 @@ async function appendArchive(runId, messages, options = {}) {
|
|
|
1761
1912
|
}
|
|
1762
1913
|
async function readArchive(runId, options = {}) {
|
|
1763
1914
|
const path = archivePath(options.workspace ?? process.cwd(), runId);
|
|
1764
|
-
if (!
|
|
1915
|
+
if (!existsSync7(path)) return [];
|
|
1765
1916
|
const raw = await readFile2(path, "utf8");
|
|
1766
1917
|
return raw.split(/\r?\n/).filter(Boolean).flatMap((line) => {
|
|
1767
1918
|
try {
|
|
@@ -1813,7 +1964,7 @@ function fileTouchPathsFromArchive(entries) {
|
|
|
1813
1964
|
return [...paths].sort();
|
|
1814
1965
|
}
|
|
1815
1966
|
function archivePath(workspace, runId) {
|
|
1816
|
-
return
|
|
1967
|
+
return join7(workspace, ".tanya", "runs", runId, "archive.jsonl");
|
|
1817
1968
|
}
|
|
1818
1969
|
function fileTouchPathsFromArchivedContent(content) {
|
|
1819
1970
|
const paths = /* @__PURE__ */ new Set();
|
|
@@ -2140,41 +2291,61 @@ var EscalationExhaustedError = class extends Error {
|
|
|
2140
2291
|
}
|
|
2141
2292
|
};
|
|
2142
2293
|
function resolveRouteWithContextGuard(params) {
|
|
2143
|
-
const primary = resolveRoute(params.stepType, params.table, params.routeText);
|
|
2144
2294
|
const estimate = estimateCompactTokens(params.messages);
|
|
2295
|
+
const safetyFactor = routeSafetyFactor();
|
|
2296
|
+
if (params.forcedRoute) {
|
|
2297
|
+
return resolveForcedRoute(params.forcedRoute, params.stepType, estimate, safetyFactor);
|
|
2298
|
+
}
|
|
2299
|
+
const primary = resolveRoute(params.stepType, params.table, params.routeText);
|
|
2300
|
+
const guardedPrimary = codeEditingDeepSeekChatGuard(primary, params);
|
|
2145
2301
|
const candidates = [
|
|
2146
|
-
|
|
2147
|
-
...
|
|
2148
|
-
|
|
2149
|
-
provider: params.table.defaults.provider,
|
|
2150
|
-
model: params.table.defaults.model,
|
|
2151
|
-
match: "defaults",
|
|
2152
|
-
escalate: true,
|
|
2153
|
-
source: params.table.defaultSource,
|
|
2154
|
-
reason: "matched route defaults"
|
|
2155
|
-
}
|
|
2302
|
+
guardedPrimary,
|
|
2303
|
+
...guardedPrimary.fallback ? [routeFromTarget(guardedPrimary.fallback, guardedPrimary, "fallback")] : [],
|
|
2304
|
+
...cascadeRoutesForStep(params.table, params.stepType)
|
|
2156
2305
|
];
|
|
2157
2306
|
const seen = /* @__PURE__ */ new Set();
|
|
2307
|
+
const attempts = [];
|
|
2158
2308
|
for (const candidate of candidates) {
|
|
2159
2309
|
const key = `${candidate.provider}/${candidate.model}`;
|
|
2160
2310
|
if (seen.has(key)) continue;
|
|
2161
2311
|
seen.add(key);
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2312
|
+
const attempt = routeAttempt(candidate, estimate, safetyFactor);
|
|
2313
|
+
attempts.push(attempt);
|
|
2314
|
+
if (estimate <= attempt.safetyLimit) {
|
|
2315
|
+
if (attempts.length === 1 && candidate === primary) return candidate;
|
|
2316
|
+
const reason = attempts.length > 1 ? `${candidate.reason}; cascade-fit; declined earlier route due to context-window guard` : candidate.reason;
|
|
2317
|
+
return {
|
|
2318
|
+
...candidate,
|
|
2319
|
+
reason,
|
|
2320
|
+
cascade: {
|
|
2321
|
+
reason: "cascade-fit",
|
|
2322
|
+
estimatedTokens: estimate,
|
|
2323
|
+
safetyFactor,
|
|
2324
|
+
attemptedRoutes: attempts,
|
|
2325
|
+
selectedRoute: attempt,
|
|
2326
|
+
selectedIndex: attempts.length - 1
|
|
2327
|
+
}
|
|
2328
|
+
};
|
|
2165
2329
|
}
|
|
2166
2330
|
}
|
|
2331
|
+
const highest = highestAttempt(attempts);
|
|
2332
|
+
if (highest) {
|
|
2333
|
+
throw new RouteContextOverflowError(
|
|
2334
|
+
`Estimated ${estimate.toLocaleString("en-US")} tokens exceeds the largest configured route ${highest.provider}/${highest.model} (${highest.maxInputTokens.toLocaleString("en-US")} tokens \xD7 ${safetyFactor} safety = ${highest.safetyLimit.toLocaleString("en-US")}). Reduce prompt or add a higher-context route.`
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2167
2337
|
throw new RouteContextOverflowError(
|
|
2168
2338
|
`No route can fit estimated ${estimate} tokens for step ${params.stepType}.`
|
|
2169
2339
|
);
|
|
2170
2340
|
}
|
|
2171
2341
|
function contextWindowForTarget(target) {
|
|
2172
|
-
return resolveProviderAdapter({ provider: target.provider }).capabilities.contextWindow;
|
|
2342
|
+
return target.maxInputTokens ?? modelContextWindow(target) ?? resolveProviderAdapter({ provider: target.provider }).capabilities.contextWindow;
|
|
2173
2343
|
}
|
|
2174
2344
|
function routeFromTarget(target, primary, label) {
|
|
2175
2345
|
return {
|
|
2176
2346
|
provider: target.provider,
|
|
2177
2347
|
model: target.model,
|
|
2348
|
+
...target.maxInputTokens ? { maxInputTokens: target.maxInputTokens } : {},
|
|
2178
2349
|
match: primary.match,
|
|
2179
2350
|
escalate: primary.escalate,
|
|
2180
2351
|
...primary.reasoningCap ? { reasoningCap: primary.reasoningCap } : {},
|
|
@@ -2182,11 +2353,96 @@ function routeFromTarget(target, primary, label) {
|
|
|
2182
2353
|
reason: `matched ${label} for ${primary.provider}/${primary.model}`
|
|
2183
2354
|
};
|
|
2184
2355
|
}
|
|
2356
|
+
function cascadeRoutesForStep(table, stepType) {
|
|
2357
|
+
return table.cascade.filter((entry) => !entry.stepTypes || entry.stepTypes.includes(stepType)).map((entry) => ({
|
|
2358
|
+
provider: entry.provider,
|
|
2359
|
+
model: entry.model,
|
|
2360
|
+
maxInputTokens: entry.maxInputTokens,
|
|
2361
|
+
match: "defaults",
|
|
2362
|
+
escalate: true,
|
|
2363
|
+
source: entry.source,
|
|
2364
|
+
reason: `matched route cascade ${entry.provider}/${entry.model}`
|
|
2365
|
+
}));
|
|
2366
|
+
}
|
|
2367
|
+
function resolveForcedRoute(target, stepType, estimate, safetyFactor) {
|
|
2368
|
+
const candidate = {
|
|
2369
|
+
provider: target.provider,
|
|
2370
|
+
model: target.model,
|
|
2371
|
+
...target.maxInputTokens ? { maxInputTokens: target.maxInputTokens } : {},
|
|
2372
|
+
match: stepType,
|
|
2373
|
+
escalate: false,
|
|
2374
|
+
source: "session",
|
|
2375
|
+
reason: `forced route ${target.provider}/${target.model}`
|
|
2376
|
+
};
|
|
2377
|
+
const attempt = routeAttempt(candidate, estimate, safetyFactor);
|
|
2378
|
+
if (estimate <= attempt.safetyLimit) return candidate;
|
|
2379
|
+
throw new RouteContextOverflowError(
|
|
2380
|
+
`Forced route ${target.provider}/${target.model} cannot fit estimated ${estimate.toLocaleString("en-US")} tokens (${attempt.maxInputTokens.toLocaleString("en-US")} tokens \xD7 ${safetyFactor} safety = ${attempt.safetyLimit.toLocaleString("en-US")}). Reduce prompt or choose a higher-context forced route.`
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
function routeAttempt(route, _estimate, safetyFactor) {
|
|
2384
|
+
const maxInputTokens = contextWindowForTarget(route);
|
|
2385
|
+
return {
|
|
2386
|
+
provider: route.provider,
|
|
2387
|
+
model: route.model,
|
|
2388
|
+
maxInputTokens,
|
|
2389
|
+
safetyLimit: Math.floor(maxInputTokens * safetyFactor),
|
|
2390
|
+
source: route.source,
|
|
2391
|
+
reason: route.reason
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
function highestAttempt(attempts) {
|
|
2395
|
+
return attempts.reduce((highest, attempt) => {
|
|
2396
|
+
if (!highest || attempt.maxInputTokens > highest.maxInputTokens) return attempt;
|
|
2397
|
+
return highest;
|
|
2398
|
+
}, null);
|
|
2399
|
+
}
|
|
2400
|
+
function routeSafetyFactor(env = process.env) {
|
|
2401
|
+
const raw = Number(envValue(env, "TANYA_ROUTE_SAFETY_FACTOR"));
|
|
2402
|
+
if (!Number.isFinite(raw) || raw <= 0 || raw > 1) return 0.85;
|
|
2403
|
+
return raw;
|
|
2404
|
+
}
|
|
2405
|
+
function modelContextWindow(target) {
|
|
2406
|
+
const model = target.model.toLowerCase();
|
|
2407
|
+
if (target.provider === "claude" || model.startsWith("claude-")) return 1e6;
|
|
2408
|
+
if (target.provider === "gemini" || model.startsWith("gemini-")) return 2e6;
|
|
2409
|
+
if (model.includes("gpt-5-codex") || model.includes("o3-codex")) return 2e5;
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
function codeEditingDeepSeekChatGuard(primary, params) {
|
|
2413
|
+
if (params.stepType !== "unknown") return primary;
|
|
2414
|
+
if (!isDeepSeekChat(primary)) return primary;
|
|
2415
|
+
const codeTaskState = {
|
|
2416
|
+
messages: params.messages,
|
|
2417
|
+
...params.prompt ? { prompt: params.prompt } : {},
|
|
2418
|
+
...params.cwd ? { cwd: params.cwd } : {},
|
|
2419
|
+
...params.runContext ? { runContext: params.runContext } : {}
|
|
2420
|
+
};
|
|
2421
|
+
if (!looksLikeCodeEditingTask(codeTaskState)) {
|
|
2422
|
+
return primary;
|
|
2423
|
+
}
|
|
2424
|
+
const target = firstNonDeepSeekChatTarget([
|
|
2425
|
+
primary.fallback,
|
|
2426
|
+
params.table.defaults,
|
|
2427
|
+
{ provider: "deepseek", model: "deepseek-reasoner" }
|
|
2428
|
+
]);
|
|
2429
|
+
if (!target) return primary;
|
|
2430
|
+
return {
|
|
2431
|
+
...routeFromTarget(target, primary, "fallback"),
|
|
2432
|
+
reason: `${primary.reason}; declined deepseek-chat for code-editing task`
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function firstNonDeepSeekChatTarget(targets) {
|
|
2436
|
+
return targets.find((target) => Boolean(target && !isDeepSeekChat(target))) ?? null;
|
|
2437
|
+
}
|
|
2438
|
+
function isDeepSeekChat(target) {
|
|
2439
|
+
return target.provider === "deepseek" && target.model === "deepseek-chat";
|
|
2440
|
+
}
|
|
2185
2441
|
|
|
2186
2442
|
// src/context/loader.ts
|
|
2187
2443
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2188
|
-
import { existsSync as
|
|
2189
|
-
import { join as
|
|
2444
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
2445
|
+
import { join as join8, relative as relative3 } from "path";
|
|
2190
2446
|
var ignoredNames = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", ".turbo", ".cache"]);
|
|
2191
2447
|
var artifactIgnoredNames = /* @__PURE__ */ new Set([".DS_Store", ".git", "node_modules", "dist", "build"]);
|
|
2192
2448
|
var instructionFiles = ["TANYA.md", "TANIA.md", "AGENTS.md", "CLAUDE.md", "README.md", ".tania/INSTRUCTIONS.md"];
|
|
@@ -2200,7 +2456,7 @@ var projectMarkers = [
|
|
|
2200
2456
|
];
|
|
2201
2457
|
function readIfExists(filePath, maxChars = 4e3) {
|
|
2202
2458
|
try {
|
|
2203
|
-
if (!
|
|
2459
|
+
if (!existsSync8(filePath)) return null;
|
|
2204
2460
|
const content = readFileSync5(filePath, "utf8");
|
|
2205
2461
|
return content.length > maxChars ? `${content.slice(0, maxChars)}
|
|
2206
2462
|
[truncated]` : content;
|
|
@@ -2210,7 +2466,7 @@ function readIfExists(filePath, maxChars = 4e3) {
|
|
|
2210
2466
|
}
|
|
2211
2467
|
function readFullIfExists(filePath) {
|
|
2212
2468
|
try {
|
|
2213
|
-
if (!
|
|
2469
|
+
if (!existsSync8(filePath)) return null;
|
|
2214
2470
|
return readFileSync5(filePath, "utf8");
|
|
2215
2471
|
} catch {
|
|
2216
2472
|
return null;
|
|
@@ -2250,14 +2506,14 @@ function scoreArtifactDirectory(name, taskHint) {
|
|
|
2250
2506
|
}
|
|
2251
2507
|
function artifactDirectoryPreview(artifactsRoot, directory, maxEntries = 10) {
|
|
2252
2508
|
try {
|
|
2253
|
-
return readdirSync2(
|
|
2509
|
+
return readdirSync2(join8(artifactsRoot, directory), { withFileTypes: true }).filter((entry) => !artifactIgnoredNames.has(entry.name)).sort((a, b) => Number(a.isDirectory()) - Number(b.isDirectory()) || a.name.localeCompare(b.name)).slice(0, maxEntries).map((entry) => entry.isDirectory() ? `${directory}/${entry.name}/` : `${directory}/${entry.name}`);
|
|
2254
2510
|
} catch {
|
|
2255
2511
|
return [];
|
|
2256
2512
|
}
|
|
2257
2513
|
}
|
|
2258
2514
|
function buildArtifactIndexBlock(workspace, taskHint = "") {
|
|
2259
|
-
const artifactsRoot =
|
|
2260
|
-
if (!
|
|
2515
|
+
const artifactsRoot = join8(workspace, "artifacts");
|
|
2516
|
+
if (!existsSync8(artifactsRoot)) return "";
|
|
2261
2517
|
let entries;
|
|
2262
2518
|
try {
|
|
2263
2519
|
entries = readdirSync2(artifactsRoot, { withFileTypes: true });
|
|
@@ -2270,13 +2526,13 @@ function buildArtifactIndexBlock(workspace, taskHint = "") {
|
|
|
2270
2526
|
preview: artifactDirectoryPreview(artifactsRoot, entry.name)
|
|
2271
2527
|
})).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
2272
2528
|
const rulesCandidates = [
|
|
2273
|
-
{ path: "artifacts/prompts/RULES.md", absolutePath:
|
|
2274
|
-
{ path: "artifacts/RULES.md", absolutePath:
|
|
2529
|
+
{ path: "artifacts/prompts/RULES.md", absolutePath: join8(artifactsRoot, "prompts", "RULES.md") },
|
|
2530
|
+
{ path: "artifacts/RULES.md", absolutePath: join8(artifactsRoot, "RULES.md") }
|
|
2275
2531
|
];
|
|
2276
2532
|
const rulesRead = rulesCandidates.map((candidate) => ({ path: candidate.path, content: readFullIfExists(candidate.absolutePath) })).find((candidate) => candidate.content !== null && candidate.content.trim().length > 0);
|
|
2277
2533
|
const indexReads = [
|
|
2278
|
-
{ path: "artifacts/description.md", content: readIfExists(
|
|
2279
|
-
{ path: "artifacts/README.md", content: readIfExists(
|
|
2534
|
+
{ path: "artifacts/description.md", content: readIfExists(join8(artifactsRoot, "description.md"), 12e3) },
|
|
2535
|
+
{ path: "artifacts/README.md", content: readIfExists(join8(artifactsRoot, "README.md"), 12e3) }
|
|
2280
2536
|
].filter((entry) => entry.content !== null && entry.content.trim().length > 0);
|
|
2281
2537
|
if (!rulesRead && indexReads.length === 0 && directories.length === 0) return "";
|
|
2282
2538
|
const lines = [
|
|
@@ -2306,7 +2562,7 @@ function buildArtifactIndexBlock(workspace, taskHint = "") {
|
|
|
2306
2562
|
const maxArtifactFiles = 4;
|
|
2307
2563
|
const scoredFiles = [];
|
|
2308
2564
|
for (const directory of directories) {
|
|
2309
|
-
const subdirPath =
|
|
2565
|
+
const subdirPath = join8(artifactsRoot, directory.name);
|
|
2310
2566
|
let mdFiles = [];
|
|
2311
2567
|
try {
|
|
2312
2568
|
mdFiles = readdirSync2(subdirPath, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
@@ -2318,7 +2574,7 @@ function buildArtifactIndexBlock(workspace, taskHint = "") {
|
|
|
2318
2574
|
if (fileScore > 0) {
|
|
2319
2575
|
scoredFiles.push({
|
|
2320
2576
|
path: `artifacts/${directory.name}/${mdFile}`,
|
|
2321
|
-
fullPath:
|
|
2577
|
+
fullPath: join8(subdirPath, mdFile),
|
|
2322
2578
|
score: fileScore
|
|
2323
2579
|
});
|
|
2324
2580
|
}
|
|
@@ -2366,7 +2622,7 @@ function collectTree(workspace, maxEntries = 120, maxDepth = 2) {
|
|
|
2366
2622
|
for (const entry of dirents) {
|
|
2367
2623
|
if (entries.length >= maxEntries) return;
|
|
2368
2624
|
if (ignoredNames.has(entry.name)) continue;
|
|
2369
|
-
const fullPath =
|
|
2625
|
+
const fullPath = join8(dir, entry.name);
|
|
2370
2626
|
const rel = relative3(workspace, fullPath).replace(/\\/g, "/");
|
|
2371
2627
|
entries.push(entry.isDirectory() ? `${rel}/` : rel);
|
|
2372
2628
|
if (entry.isDirectory()) walk(fullPath, depth + 1);
|
|
@@ -2378,9 +2634,9 @@ function collectTree(workspace, maxEntries = 120, maxDepth = 2) {
|
|
|
2378
2634
|
function detectProjectTypes(workspace) {
|
|
2379
2635
|
const types = /* @__PURE__ */ new Set();
|
|
2380
2636
|
for (const marker of projectMarkers) {
|
|
2381
|
-
if (
|
|
2637
|
+
if (existsSync8(join8(workspace, marker.file))) types.add(marker.type);
|
|
2382
2638
|
}
|
|
2383
|
-
if (
|
|
2639
|
+
if (existsSync8(join8(workspace, "gradlew")) || existsSync8(join8(workspace, "settings.gradle")) || existsSync8(join8(workspace, "settings.gradle.kts"))) {
|
|
2384
2640
|
types.add("android");
|
|
2385
2641
|
}
|
|
2386
2642
|
try {
|
|
@@ -2401,7 +2657,7 @@ function collectTypeScriptFiles(workspace) {
|
|
|
2401
2657
|
}
|
|
2402
2658
|
for (const entry of dirents) {
|
|
2403
2659
|
if (ignoredNames.has(entry.name)) continue;
|
|
2404
|
-
const fullPath =
|
|
2660
|
+
const fullPath = join8(dir, entry.name);
|
|
2405
2661
|
if (entry.isDirectory()) {
|
|
2406
2662
|
walk(fullPath);
|
|
2407
2663
|
continue;
|
|
@@ -2425,7 +2681,7 @@ function formatExportName(entry) {
|
|
|
2425
2681
|
return entry.isDefault ? `${entry.name} (default)` : entry.name;
|
|
2426
2682
|
}
|
|
2427
2683
|
function parseFileExports(workspace, file) {
|
|
2428
|
-
const content = readIfExists(
|
|
2684
|
+
const content = readIfExists(join8(workspace, file), 2e5);
|
|
2429
2685
|
if (!content) return null;
|
|
2430
2686
|
const exports = [];
|
|
2431
2687
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2487,7 +2743,7 @@ function buildExportMap(workspace) {
|
|
|
2487
2743
|
function detectVerificationSuggestions(workspace, projectTypes, packageScripts) {
|
|
2488
2744
|
const suggestions = /* @__PURE__ */ new Set();
|
|
2489
2745
|
if (projectTypes.includes("android")) {
|
|
2490
|
-
const gradle =
|
|
2746
|
+
const gradle = existsSync8(join8(workspace, "gradlew")) ? "./gradlew" : "gradle";
|
|
2491
2747
|
suggestions.add(`${gradle} test`);
|
|
2492
2748
|
suggestions.add(`${gradle} assembleDebug`);
|
|
2493
2749
|
}
|
|
@@ -2505,7 +2761,7 @@ function detectVerificationSuggestions(workspace, projectTypes, packageScripts)
|
|
|
2505
2761
|
return [...suggestions].slice(0, 8);
|
|
2506
2762
|
}
|
|
2507
2763
|
function readPackageScripts(workspace) {
|
|
2508
|
-
const content = readIfExists(
|
|
2764
|
+
const content = readIfExists(join8(workspace, "package.json"), 8e4);
|
|
2509
2765
|
if (!content) return {};
|
|
2510
2766
|
try {
|
|
2511
2767
|
const parsed = JSON.parse(content);
|
|
@@ -2525,7 +2781,7 @@ function loadWorkspaceSummary(workspace) {
|
|
|
2525
2781
|
const gitStatus = isGitRepo ? runGit(workspace, ["status", "--short"]) ?? "" : null;
|
|
2526
2782
|
const instructionReads = [];
|
|
2527
2783
|
for (const fileName of instructionFiles) {
|
|
2528
|
-
const filePath =
|
|
2784
|
+
const filePath = join8(workspace, fileName);
|
|
2529
2785
|
const content = readIfExists(filePath, fileName === "README.md" ? 6e3 : 4e3);
|
|
2530
2786
|
if (content !== null) {
|
|
2531
2787
|
instructionReads.push({ path: fileName, content });
|
|
@@ -2582,20 +2838,20 @@ function buildContextBlock(workspace) {
|
|
|
2582
2838
|
}
|
|
2583
2839
|
|
|
2584
2840
|
// src/skills/load.ts
|
|
2585
|
-
import { existsSync as
|
|
2586
|
-
import { dirname as dirname5, join as
|
|
2841
|
+
import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
2842
|
+
import { dirname as dirname5, join as join10, relative as relative4 } from "path";
|
|
2587
2843
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2588
2844
|
import { load as parseYaml } from "js-yaml";
|
|
2589
2845
|
|
|
2590
2846
|
// src/integrations/discovery.ts
|
|
2591
|
-
import { existsSync as
|
|
2592
|
-
import { basename, dirname as dirname4, join as
|
|
2847
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
2848
|
+
import { basename, dirname as dirname4, join as join9 } from "path";
|
|
2593
2849
|
import { fileURLToPath } from "url";
|
|
2594
2850
|
var moduleRoot = dirname4(fileURLToPath(import.meta.url));
|
|
2595
2851
|
var defaultIntegrationsRoot = null;
|
|
2596
2852
|
function safeIsDirectory(path) {
|
|
2597
2853
|
try {
|
|
2598
|
-
return
|
|
2854
|
+
return existsSync9(path) && statSync2(path).isDirectory();
|
|
2599
2855
|
} catch {
|
|
2600
2856
|
return false;
|
|
2601
2857
|
}
|
|
@@ -2610,7 +2866,7 @@ function safeReadDirectory(path) {
|
|
|
2610
2866
|
function hasMarkdownFile(root) {
|
|
2611
2867
|
const entries = safeReadDirectory(root);
|
|
2612
2868
|
for (const entry of entries) {
|
|
2613
|
-
const path =
|
|
2869
|
+
const path = join9(root, entry.name);
|
|
2614
2870
|
if (entry.isFile() && entry.name.endsWith(".md")) return true;
|
|
2615
2871
|
if (entry.isDirectory() && hasMarkdownFile(path)) return true;
|
|
2616
2872
|
}
|
|
@@ -2619,14 +2875,14 @@ function hasMarkdownFile(root) {
|
|
|
2619
2875
|
function resolveDefaultSkillsRoot() {
|
|
2620
2876
|
const candidates = [
|
|
2621
2877
|
moduleRoot,
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2878
|
+
join9(moduleRoot, "skills"),
|
|
2879
|
+
join9(moduleRoot, "..", "src", "skills"),
|
|
2880
|
+
join9(moduleRoot, "..", "skills")
|
|
2625
2881
|
];
|
|
2626
2882
|
for (const candidate of candidates) {
|
|
2627
2883
|
if (safeIsDirectory(candidate) && hasMarkdownFile(candidate)) return candidate;
|
|
2628
2884
|
}
|
|
2629
|
-
return
|
|
2885
|
+
return join9(moduleRoot, "..", "skills");
|
|
2630
2886
|
}
|
|
2631
2887
|
function packageRootFromSkillsRoot(skillsRoot) {
|
|
2632
2888
|
const parent = dirname4(skillsRoot);
|
|
@@ -2637,7 +2893,7 @@ function packageRootFromSkillsRoot(skillsRoot) {
|
|
|
2637
2893
|
function integrationsRoot(env = process.env) {
|
|
2638
2894
|
const override = envValue(env, "TANYA_INTEGRATIONS_DIR").trim();
|
|
2639
2895
|
if (override) return override;
|
|
2640
|
-
defaultIntegrationsRoot ??=
|
|
2896
|
+
defaultIntegrationsRoot ??= join9(packageRootFromSkillsRoot(resolveDefaultSkillsRoot()), "integrations");
|
|
2641
2897
|
return defaultIntegrationsRoot;
|
|
2642
2898
|
}
|
|
2643
2899
|
function discoverIntegrationEntries(kind, opts = {}) {
|
|
@@ -2646,14 +2902,14 @@ function discoverIntegrationEntries(kind, opts = {}) {
|
|
|
2646
2902
|
const entries = [];
|
|
2647
2903
|
const integrations = safeReadDirectory(root).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
|
|
2648
2904
|
for (const integration of integrations) {
|
|
2649
|
-
const kindRoot =
|
|
2905
|
+
const kindRoot = join9(root, integration.name, kind);
|
|
2650
2906
|
if (!safeIsDirectory(kindRoot)) continue;
|
|
2651
2907
|
const discovered = safeReadDirectory(kindRoot).filter((entry) => entry.isDirectory() || entry.isFile()).sort((a, b) => a.name.localeCompare(b.name));
|
|
2652
2908
|
for (const entry of discovered) {
|
|
2653
2909
|
entries.push({
|
|
2654
2910
|
integration: integration.name,
|
|
2655
2911
|
kind,
|
|
2656
|
-
path:
|
|
2912
|
+
path: join9(kindRoot, entry.name)
|
|
2657
2913
|
});
|
|
2658
2914
|
}
|
|
2659
2915
|
}
|
|
@@ -2685,7 +2941,7 @@ function estimateTokens(text2) {
|
|
|
2685
2941
|
}
|
|
2686
2942
|
function safeExists(path) {
|
|
2687
2943
|
try {
|
|
2688
|
-
return
|
|
2944
|
+
return existsSync10(path);
|
|
2689
2945
|
} catch {
|
|
2690
2946
|
return false;
|
|
2691
2947
|
}
|
|
@@ -2693,13 +2949,13 @@ function safeExists(path) {
|
|
|
2693
2949
|
function resolveDefaultSkillsRoot2() {
|
|
2694
2950
|
const candidates = [
|
|
2695
2951
|
moduleRoot2,
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2952
|
+
join10(moduleRoot2, "skills"),
|
|
2953
|
+
join10(moduleRoot2, "..", "src", "skills"),
|
|
2954
|
+
join10(moduleRoot2, "..", "skills")
|
|
2699
2955
|
];
|
|
2700
2956
|
for (const candidate of candidates) {
|
|
2701
2957
|
try {
|
|
2702
|
-
if (
|
|
2958
|
+
if (existsSync10(candidate) && statSync3(candidate).isDirectory() && collectSkillFiles(candidate).length > 0) return candidate;
|
|
2703
2959
|
} catch {
|
|
2704
2960
|
}
|
|
2705
2961
|
}
|
|
@@ -2737,7 +2993,7 @@ function walkWorkspace(workspace, maxEntries = 8e3) {
|
|
|
2737
2993
|
for (const entry of entries) {
|
|
2738
2994
|
if (files.length + dirs.length >= maxEntries) return;
|
|
2739
2995
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
2740
|
-
const fullPath =
|
|
2996
|
+
const fullPath = join10(current, entry.name);
|
|
2741
2997
|
const relPath = relative4(workspace, fullPath).replace(/\\/g, "/");
|
|
2742
2998
|
if (entry.isDirectory()) {
|
|
2743
2999
|
dirs.push(relPath);
|
|
@@ -2761,7 +3017,7 @@ function collectSkillFiles(skillsRoot) {
|
|
|
2761
3017
|
}
|
|
2762
3018
|
for (const entry of entries) {
|
|
2763
3019
|
if (entry.name.startsWith(".")) continue;
|
|
2764
|
-
const fullPath =
|
|
3020
|
+
const fullPath = join10(current, entry.name);
|
|
2765
3021
|
if (entry.isDirectory()) {
|
|
2766
3022
|
walk(fullPath);
|
|
2767
3023
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -2857,7 +3113,7 @@ function pathMatchesGlob(path, glob) {
|
|
|
2857
3113
|
return new RegExp(`^${escaped}$`).test(path);
|
|
2858
3114
|
}
|
|
2859
3115
|
function packageJsonHasDependency(workspace, dep) {
|
|
2860
|
-
const parsed = readJson(
|
|
3116
|
+
const parsed = readJson(join10(workspace, "package.json"));
|
|
2861
3117
|
if (!parsed) return false;
|
|
2862
3118
|
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
2863
3119
|
const deps = parsed[key];
|
|
@@ -2879,7 +3135,7 @@ function selectContentFiles(files) {
|
|
|
2879
3135
|
}
|
|
2880
3136
|
function anyContentContains(workspace, files, needle) {
|
|
2881
3137
|
return files.some((file) => {
|
|
2882
|
-
const content = safeRead(
|
|
3138
|
+
const content = safeRead(join10(workspace, file), 12e4);
|
|
2883
3139
|
return typeof needle === "string" ? content.includes(needle) : needle.test(content);
|
|
2884
3140
|
});
|
|
2885
3141
|
}
|
|
@@ -2890,20 +3146,20 @@ function detectWorkspaceSignals(ctx) {
|
|
|
2890
3146
|
const go = hasRootFile(files, "go.mod");
|
|
2891
3147
|
const pkgMigrations = files.some((file) => /^pkg\/[^/]+\/migrations\//.test(file));
|
|
2892
3148
|
const goHouse = go && pkgMigrations && anyContentContains(workspace, contentFiles, "Module.Attach");
|
|
2893
|
-
const goHuma = go && (safeRead(
|
|
3149
|
+
const goHuma = go && (safeRead(join10(workspace, "go.sum")).includes("github.com/danielgtaylor/huma") || dirs.includes("internal/store/gen") || normalize(ctx.hints.stack ?? "") === "backend-go-huma" || /artifacts\/backend-go|backend-go-huma/i.test(ctx.taskHint ?? ""));
|
|
2894
3150
|
const ios = hasRootFile(files, "Package.swift") || hasRootDir(dirs, /(?:^|\/)[^/]+\.xcodeproj$/);
|
|
2895
3151
|
const swiftData = ios && anyContentContains(workspace, contentFiles, "@Model");
|
|
2896
3152
|
const revenueCatIos = ios && (anyContentContains(workspace, contentFiles, "import RevenueCat") || anyContentContains(workspace, contentFiles, /RevenueCat/i));
|
|
2897
3153
|
const gradleFiles = files.filter((file) => /(?:^|\/)build\.gradle(?:\.kts)?$/.test(file));
|
|
2898
|
-
const android = hasRootFile(files, "build.gradle.kts") || gradleFiles.some((file) => file.endsWith("build.gradle.kts") || /kotlin/i.test(safeRead(
|
|
2899
|
-
const libsVersions = safeRead(
|
|
3154
|
+
const android = hasRootFile(files, "build.gradle.kts") || gradleFiles.some((file) => file.endsWith("build.gradle.kts") || /kotlin/i.test(safeRead(join10(workspace, file), 8e4)));
|
|
3155
|
+
const libsVersions = safeRead(join10(workspace, "gradle/libs.versions.toml"), 12e4);
|
|
2900
3156
|
const room = android && /room/i.test(libsVersions);
|
|
2901
3157
|
const retrofit = android && anyContentContains(workspace, contentFiles, /import\s+retrofit2\b/);
|
|
2902
3158
|
const revenueCatAndroid = android && anyContentContains(workspace, contentFiles, "com.revenuecat.purchases");
|
|
2903
|
-
const packageJson = safeRead(
|
|
3159
|
+
const packageJson = safeRead(join10(workspace, "package.json"), 12e4);
|
|
2904
3160
|
const next = (hasRootFile(files, "next.config.js") || hasRootFile(files, "next.config.ts") || hasRootFile(files, "next.config.mjs")) && /"next"\s*:/.test(packageJson);
|
|
2905
3161
|
const tailwindV4 = anyContentContains(workspace, files.filter((file) => file.endsWith(".css")), '@import "tailwindcss"') || packageJsonHasDependency(workspace, "@tailwindcss/postcss");
|
|
2906
|
-
const shadcn = hasRootFile(files, "components.json") || safeExists(
|
|
3162
|
+
const shadcn = hasRootFile(files, "components.json") || safeExists(join10(workspace, "components/ui")) || safeExists(join10(workspace, "src/components/ui")) || dirs.includes("components/ui") || dirs.includes("src/components/ui");
|
|
2907
3163
|
const fastlane = hasRootFile(files, "fastlane/Fastfile");
|
|
2908
3164
|
return {
|
|
2909
3165
|
files,
|
|
@@ -2933,7 +3189,7 @@ function frontmatterConditionReason(condition, ctx, signals) {
|
|
|
2933
3189
|
case "always":
|
|
2934
3190
|
return "always";
|
|
2935
3191
|
case "workspace.has":
|
|
2936
|
-
return safeExists(
|
|
3192
|
+
return safeExists(join10(ctx.workspace, condition.path)) ? "workspace" : null;
|
|
2937
3193
|
case "workspace.hasGlob":
|
|
2938
3194
|
return [...signals.files, ...signals.dirs].some((path) => pathMatchesGlob(path, condition.glob)) ? "workspace" : null;
|
|
2939
3195
|
case "workspace.packageJson":
|
|
@@ -3141,7 +3397,7 @@ function loadSkillPacksFromFiles(ctx, skillsRoot, files) {
|
|
|
3141
3397
|
return enforceBudget(matched).map((pack) => ({
|
|
3142
3398
|
slug: pack.frontmatter.slug,
|
|
3143
3399
|
title: pack.title,
|
|
3144
|
-
sourcePath:
|
|
3400
|
+
sourcePath: join10(skillsRoot, `${pack.relativePath}.md`),
|
|
3145
3401
|
content: pack.body,
|
|
3146
3402
|
tokens: pack.tokens,
|
|
3147
3403
|
reason: pack.reason
|
|
@@ -3201,11 +3457,11 @@ function formatSkillPackSummary(packs) {
|
|
|
3201
3457
|
}
|
|
3202
3458
|
|
|
3203
3459
|
// src/agent/systemPrompt.ts
|
|
3204
|
-
import { existsSync as
|
|
3205
|
-
import { basename as basename2, join as
|
|
3460
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
3461
|
+
import { basename as basename2, join as join11 } from "path";
|
|
3206
3462
|
function readProjectInstructions(workspace) {
|
|
3207
|
-
const path =
|
|
3208
|
-
if (!
|
|
3463
|
+
const path = join11(workspace, ".tania", "INSTRUCTIONS.md");
|
|
3464
|
+
if (!existsSync11(path)) return "";
|
|
3209
3465
|
try {
|
|
3210
3466
|
const content = readFileSync7(path, "utf8").trim();
|
|
3211
3467
|
return content ? `
|
|
@@ -3860,9 +4116,9 @@ function withoutParent(ctx) {
|
|
|
3860
4116
|
}
|
|
3861
4117
|
|
|
3862
4118
|
// src/safety/permissions/rules.ts
|
|
3863
|
-
import { existsSync as
|
|
4119
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
3864
4120
|
import { homedir as homedir2 } from "os";
|
|
3865
|
-
import { join as
|
|
4121
|
+
import { join as join12 } from "path";
|
|
3866
4122
|
|
|
3867
4123
|
// src/safety/permissions/schema.ts
|
|
3868
4124
|
var PERMISSIONS_SCHEMA_VERSION = 1;
|
|
@@ -4045,15 +4301,15 @@ function positiveNumber(value) {
|
|
|
4045
4301
|
function loadPermissionRules(options) {
|
|
4046
4302
|
const home = options.home ?? homedir2();
|
|
4047
4303
|
const candidates = [
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4304
|
+
join12(home, ".tania", "permissions.json"),
|
|
4305
|
+
join12(home, ".tanya", "permissions.json"),
|
|
4306
|
+
join12(options.cwd, ".tania", "permissions.json")
|
|
4051
4307
|
];
|
|
4052
4308
|
let rules = cloneRules(DEFAULT_PERMISSION_RULES);
|
|
4053
4309
|
const sources = [];
|
|
4054
4310
|
const issues = [];
|
|
4055
4311
|
for (const file of candidates) {
|
|
4056
|
-
if (!
|
|
4312
|
+
if (!existsSync12(file)) continue;
|
|
4057
4313
|
const parsed = parsePermissionsJson(readFileSync8(file, "utf8"));
|
|
4058
4314
|
if (!parsed.ok) {
|
|
4059
4315
|
issues.push(...parsed.issues.map((issue2) => ({ ...issue2, file })));
|
|
@@ -4187,9 +4443,9 @@ function globPrefix(glob) {
|
|
|
4187
4443
|
}
|
|
4188
4444
|
|
|
4189
4445
|
// src/tools/fsTools.ts
|
|
4190
|
-
import { existsSync as
|
|
4446
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6 } from "fs";
|
|
4191
4447
|
import { cp, mkdir as mkdir7, readFile as readFile8, realpath, unlink, writeFile as writeFile7 } from "fs/promises";
|
|
4192
|
-
import { basename as basename4, dirname as dirname9, isAbsolute, join as
|
|
4448
|
+
import { basename as basename4, dirname as dirname9, isAbsolute, join as join16, relative as relative8, resolve as resolve7 } from "path";
|
|
4193
4449
|
import { spawn as spawn2 } from "child_process";
|
|
4194
4450
|
import sharp2 from "sharp";
|
|
4195
4451
|
|
|
@@ -4663,8 +4919,8 @@ var validateAndroidLauncherIconSetTool = {
|
|
|
4663
4919
|
|
|
4664
4920
|
// src/tools/projectContextTools.ts
|
|
4665
4921
|
import { readdir as readdir2, readFile as readFile4, stat } from "fs/promises";
|
|
4666
|
-
import { existsSync as
|
|
4667
|
-
import { basename as basename3, extname, join as
|
|
4922
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4923
|
+
import { basename as basename3, extname, join as join13, relative as relative5, resolve as resolve4 } from "path";
|
|
4668
4924
|
var ignoredNames2 = /* @__PURE__ */ new Set([
|
|
4669
4925
|
".git",
|
|
4670
4926
|
"node_modules",
|
|
@@ -4803,7 +5059,7 @@ async function collectFiles(root, maxFiles, current = root, out = []) {
|
|
|
4803
5059
|
for (const entry of entries) {
|
|
4804
5060
|
if (out.length >= maxFiles) break;
|
|
4805
5061
|
if (ignoredNames2.has(entry.name)) continue;
|
|
4806
|
-
const fullPath =
|
|
5062
|
+
const fullPath = join13(current, entry.name);
|
|
4807
5063
|
const rel = normalizePath(relative5(root, fullPath));
|
|
4808
5064
|
if (entry.isDirectory()) {
|
|
4809
5065
|
if (entry.name === ".tania") {
|
|
@@ -4823,13 +5079,13 @@ async function collectFiles(root, maxFiles, current = root, out = []) {
|
|
|
4823
5079
|
async function collectTanyaFiles(root, taniaDir, maxFiles, out) {
|
|
4824
5080
|
for (const rel of ["INSTRUCTIONS.md", "artifacts/manifest.json"]) {
|
|
4825
5081
|
if (out.length >= maxFiles) return;
|
|
4826
|
-
const abs =
|
|
5082
|
+
const abs = join13(taniaDir, rel);
|
|
4827
5083
|
try {
|
|
4828
5084
|
if ((await stat(abs)).isFile()) out.push(normalizePath(relative5(root, abs)));
|
|
4829
5085
|
} catch {
|
|
4830
5086
|
}
|
|
4831
5087
|
}
|
|
4832
|
-
const contextDir =
|
|
5088
|
+
const contextDir = join13(taniaDir, "context");
|
|
4833
5089
|
try {
|
|
4834
5090
|
if ((await stat(contextDir)).isDirectory()) {
|
|
4835
5091
|
out.push(normalizePath(relative5(root, contextDir)) + "/");
|
|
@@ -4848,7 +5104,7 @@ async function readExcerpt(path, maxChars) {
|
|
|
4848
5104
|
}
|
|
4849
5105
|
}
|
|
4850
5106
|
async function readPackageScripts2(root) {
|
|
4851
|
-
const path =
|
|
5107
|
+
const path = join13(root, "package.json");
|
|
4852
5108
|
try {
|
|
4853
5109
|
const parsed = JSON.parse(await readFile4(path, "utf8"));
|
|
4854
5110
|
return Object.fromEntries(
|
|
@@ -5287,7 +5543,7 @@ var inspectProjectContextTool = {
|
|
|
5287
5543
|
const candidates = ["artifacts/description.md", ".tania/artifacts/description.md"];
|
|
5288
5544
|
for (const rel of candidates) {
|
|
5289
5545
|
const abs = resolve4(root, rel);
|
|
5290
|
-
if (
|
|
5546
|
+
if (existsSync13(abs)) {
|
|
5291
5547
|
const head = (await readFile4(abs, "utf8")).split("\n").slice(0, 24).join("\n");
|
|
5292
5548
|
artifactsCatalog = { hint: `Read ${rel} (full file) for the complete artifact catalog before writing code from scratch.`, head };
|
|
5293
5549
|
break;
|
|
@@ -5394,9 +5650,9 @@ var buildTaskBriefTool = {
|
|
|
5394
5650
|
};
|
|
5395
5651
|
|
|
5396
5652
|
// src/obsidian/search.ts
|
|
5397
|
-
import { existsSync as
|
|
5653
|
+
import { existsSync as existsSync14 } from "fs";
|
|
5398
5654
|
import { mkdir as mkdir5, readdir as readdir3, readFile as readFile5, stat as stat2, writeFile as writeFile4 } from "fs/promises";
|
|
5399
|
-
import { dirname as dirname7, join as
|
|
5655
|
+
import { dirname as dirname7, join as join14, relative as relative6, resolve as resolve5 } from "path";
|
|
5400
5656
|
var ignoredNames3 = /* @__PURE__ */ new Set([".obsidian", ".trash", ".git", "node_modules", ".tania"]);
|
|
5401
5657
|
var stopwords = /* @__PURE__ */ new Set([
|
|
5402
5658
|
"a",
|
|
@@ -5438,7 +5694,7 @@ async function collectMarkdownFiles(root, maxFiles, current = root, out = []) {
|
|
|
5438
5694
|
for (const entry of entries) {
|
|
5439
5695
|
if (out.length >= maxFiles) break;
|
|
5440
5696
|
if (ignoredNames3.has(entry.name)) continue;
|
|
5441
|
-
const fullPath =
|
|
5697
|
+
const fullPath = join14(current, entry.name);
|
|
5442
5698
|
if (entry.isDirectory()) {
|
|
5443
5699
|
await collectMarkdownFiles(root, maxFiles, fullPath, out);
|
|
5444
5700
|
} else if (entry.isFile() && /\.md$/i.test(entry.name)) {
|
|
@@ -5497,7 +5753,7 @@ function safeMaterializedPath(relPath) {
|
|
|
5497
5753
|
}
|
|
5498
5754
|
async function searchObsidianNotes(input) {
|
|
5499
5755
|
const vault = resolve5(input.vaultPath);
|
|
5500
|
-
if (!
|
|
5756
|
+
if (!existsSync14(vault)) return [];
|
|
5501
5757
|
const queryTerms = terms(input.query);
|
|
5502
5758
|
const maxResults = Math.min(input.maxResults ?? 5, 20);
|
|
5503
5759
|
const maxFiles = Math.min(input.maxFiles ?? 1e3, 5e3);
|
|
@@ -5649,18 +5905,18 @@ var searchObsidianNotesTool = {
|
|
|
5649
5905
|
};
|
|
5650
5906
|
|
|
5651
5907
|
// src/memory/resultCache.ts
|
|
5652
|
-
import { existsSync as
|
|
5908
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync2, readdirSync as readdirSync5, rmSync, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
5653
5909
|
import { readFile as readFile6 } from "fs/promises";
|
|
5654
|
-
import { join as
|
|
5910
|
+
import { join as join15 } from "path";
|
|
5655
5911
|
var RESULT_CACHE_MAX_BYTES = 100 * 1024 * 1024;
|
|
5656
5912
|
function safeSegment(value) {
|
|
5657
5913
|
return value.replace(/[^A-Za-z0-9_.-]+/g, "_").slice(0, 160) || "result";
|
|
5658
5914
|
}
|
|
5659
5915
|
function resultCacheDir(workspace, runId) {
|
|
5660
|
-
return
|
|
5916
|
+
return join15(workspace, ".tania", "cache", "results", safeSegment(runId));
|
|
5661
5917
|
}
|
|
5662
5918
|
function resultCachePath(workspace, runId, toolCallId) {
|
|
5663
|
-
return
|
|
5919
|
+
return join15(resultCacheDir(workspace, runId), `${safeSegment(toolCallId)}.txt`);
|
|
5664
5920
|
}
|
|
5665
5921
|
function writeCachedToolResult(workspace, runId, toolCallId, content) {
|
|
5666
5922
|
const dir = resultCacheDir(workspace, runId);
|
|
@@ -5672,7 +5928,7 @@ function writeCachedToolResult(workspace, runId, toolCallId, content) {
|
|
|
5672
5928
|
}
|
|
5673
5929
|
async function readCachedToolResult(workspace, runId, toolCallId, range) {
|
|
5674
5930
|
const path = resultCachePath(workspace, runId, toolCallId);
|
|
5675
|
-
if (!
|
|
5931
|
+
if (!existsSync15(path)) return null;
|
|
5676
5932
|
const buffer = await readFile6(path);
|
|
5677
5933
|
if (!range) return buffer.toString("utf8");
|
|
5678
5934
|
const start = Math.max(0, Math.floor(range.startByte));
|
|
@@ -5702,7 +5958,7 @@ function evictRunCache(dir) {
|
|
|
5702
5958
|
function cacheEntries(dir) {
|
|
5703
5959
|
try {
|
|
5704
5960
|
return readdirSync5(dir).filter((file) => file.endsWith(".txt")).flatMap((file) => {
|
|
5705
|
-
const path =
|
|
5961
|
+
const path = join15(dir, file);
|
|
5706
5962
|
try {
|
|
5707
5963
|
const stat8 = statSync4(path);
|
|
5708
5964
|
return stat8.isFile() ? [{ path, size: stat8.size, mtimeMs: stat8.mtimeMs }] : [];
|
|
@@ -5788,7 +6044,7 @@ var expandResultTool = {
|
|
|
5788
6044
|
};
|
|
5789
6045
|
|
|
5790
6046
|
// src/agent/subAgentContext.ts
|
|
5791
|
-
import { existsSync as
|
|
6047
|
+
import { existsSync as existsSync16, realpathSync as realpathSync2 } from "fs";
|
|
5792
6048
|
import { randomUUID } from "crypto";
|
|
5793
6049
|
import { relative as relative7, resolve as resolve6 } from "path";
|
|
5794
6050
|
function createRootRunId(now = /* @__PURE__ */ new Date()) {
|
|
@@ -5806,7 +6062,7 @@ function resolveSubAgentWorkspace(parentWorkspace, requestedWorkspace) {
|
|
|
5806
6062
|
if (lexicalRel.startsWith("..") || lexicalRel === "..") {
|
|
5807
6063
|
throw new Error(`Sub-agent workspace escapes parent workspace: ${requestedWorkspace ?? target}`);
|
|
5808
6064
|
}
|
|
5809
|
-
if (!
|
|
6065
|
+
if (!existsSync16(target)) return target;
|
|
5810
6066
|
const realParent = realpathSync2(parentWorkspace);
|
|
5811
6067
|
const realTarget = realpathSync2(target);
|
|
5812
6068
|
const realRel = relative7(realParent, realTarget);
|
|
@@ -6495,7 +6751,11 @@ var recordMetricsDashboardHandoffTool = {
|
|
|
6495
6751
|
|
|
6496
6752
|
// src/tools/fsTools.ts
|
|
6497
6753
|
var ignoredNames4 = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", ".turbo", ".cache"]);
|
|
6498
|
-
|
|
6754
|
+
function getProgressThrottleMs() {
|
|
6755
|
+
const raw = Number(process.env.TANYA_PROGRESS_THROTTLE_MS);
|
|
6756
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : 2e3;
|
|
6757
|
+
}
|
|
6758
|
+
var PROGRESS_THROTTLE_MS = getProgressThrottleMs();
|
|
6499
6759
|
function isProtectedLocalConfigPath(filePath) {
|
|
6500
6760
|
return basename4(filePath.trim().replace(/\\/g, "/")) === "local.properties";
|
|
6501
6761
|
}
|
|
@@ -6537,13 +6797,13 @@ function asOptionalBoolean3(input, key, fallback) {
|
|
|
6537
6797
|
return fallback;
|
|
6538
6798
|
}
|
|
6539
6799
|
async function pathExists(path) {
|
|
6540
|
-
return
|
|
6800
|
+
return existsSync17(path);
|
|
6541
6801
|
}
|
|
6542
6802
|
function collectFiles2(root, maxFiles, current = root, out = []) {
|
|
6543
6803
|
if (out.length >= maxFiles) return out;
|
|
6544
6804
|
for (const entry of readdirSync6(current, { withFileTypes: true })) {
|
|
6545
6805
|
if (ignoredNames4.has(entry.name)) continue;
|
|
6546
|
-
const fullPath =
|
|
6806
|
+
const fullPath = join16(current, entry.name);
|
|
6547
6807
|
if (entry.isDirectory()) {
|
|
6548
6808
|
collectFiles2(root, maxFiles, fullPath, out);
|
|
6549
6809
|
} else if (entry.isFile()) {
|
|
@@ -6637,7 +6897,7 @@ function runShell(script, context, timeoutMs, cwd = context.workspace) {
|
|
|
6637
6897
|
if (!context.onProgress || !chunk) return;
|
|
6638
6898
|
progressBuffers[stream] += chunk;
|
|
6639
6899
|
if (progressTimers[stream]) return;
|
|
6640
|
-
progressTimers[stream] = setTimeout(() => flushProgress(stream),
|
|
6900
|
+
progressTimers[stream] = setTimeout(() => flushProgress(stream), getProgressThrottleMs());
|
|
6641
6901
|
progressTimers[stream]?.unref?.();
|
|
6642
6902
|
};
|
|
6643
6903
|
const outputSoFar = () => `${stdout2}${stderr ? `
|
|
@@ -7421,16 +7681,16 @@ var validateAppleProjectFilesTool = {
|
|
|
7421
7681
|
let pbxprojText = "";
|
|
7422
7682
|
if (xcodeprojPath) {
|
|
7423
7683
|
const projectDir = resolveInsideWorkspace(context.workspace, ensureRelativePath3(xcodeprojPath));
|
|
7424
|
-
if (!
|
|
7684
|
+
if (!existsSync17(projectDir)) {
|
|
7425
7685
|
problems.push(`Missing ${xcodeprojPath}.`);
|
|
7426
7686
|
} else {
|
|
7427
7687
|
const pbxprojPath = resolveInsideWorkspace(context.workspace, `${xcodeprojPath.replace(/\/+$/, "")}/project.pbxproj`);
|
|
7428
|
-
if (
|
|
7688
|
+
if (existsSync17(pbxprojPath)) pbxprojText = await readFile8(pbxprojPath, "utf8");
|
|
7429
7689
|
}
|
|
7430
7690
|
}
|
|
7431
7691
|
for (const requiredPath of requiredPaths) {
|
|
7432
7692
|
const relPath = ensureRelativePath3(requiredPath);
|
|
7433
|
-
if (!
|
|
7693
|
+
if (!existsSync17(resolveInsideWorkspace(context.workspace, relPath))) {
|
|
7434
7694
|
problems.push(`Missing ${relPath}.`);
|
|
7435
7695
|
}
|
|
7436
7696
|
if (requireProjectReferences && pbxprojText) {
|
|
@@ -7478,7 +7738,7 @@ var validateFastlaneConfigTool = {
|
|
|
7478
7738
|
let fastfile = "";
|
|
7479
7739
|
const fastfileRel = ensureRelativePath3(fastfilePath);
|
|
7480
7740
|
const fastfileAbs = resolveInsideWorkspace(context.workspace, fastfileRel);
|
|
7481
|
-
if (!
|
|
7741
|
+
if (!existsSync17(fastfileAbs)) {
|
|
7482
7742
|
problems.push(`Missing ${fastfileRel}.`);
|
|
7483
7743
|
} else {
|
|
7484
7744
|
fastfile = await readFile8(fastfileAbs, "utf8");
|
|
@@ -7516,11 +7776,11 @@ var validateFastlaneConfigTool = {
|
|
|
7516
7776
|
}
|
|
7517
7777
|
for (const file of requiredFiles) {
|
|
7518
7778
|
const relPath = ensureRelativePath3(file);
|
|
7519
|
-
if (!
|
|
7779
|
+
if (!existsSync17(resolveInsideWorkspace(context.workspace, relPath))) problems.push(`Missing ${relPath}.`);
|
|
7520
7780
|
}
|
|
7521
7781
|
for (const file of forbiddenFiles) {
|
|
7522
7782
|
const relPath = ensureRelativePath3(file);
|
|
7523
|
-
if (
|
|
7783
|
+
if (existsSync17(resolveInsideWorkspace(context.workspace, relPath))) problems.push(`Forbidden file exists: ${relPath}.`);
|
|
7524
7784
|
}
|
|
7525
7785
|
return {
|
|
7526
7786
|
ok: problems.length === 0,
|
|
@@ -7623,7 +7883,7 @@ async function findLargestAppIconPng(context, viewPath) {
|
|
|
7623
7883
|
const viewDir = dirname9(viewPath).replace(/\\/g, "/");
|
|
7624
7884
|
const appIconDir = `${viewDir}/Assets.xcassets/AppIcon.appiconset`;
|
|
7625
7885
|
const appIconAbs = resolveInsideWorkspace(context.workspace, appIconDir);
|
|
7626
|
-
if (!
|
|
7886
|
+
if (!existsSync17(appIconAbs)) return null;
|
|
7627
7887
|
const pngs = readdirSync6(appIconAbs, { withFileTypes: true }).filter((entry) => entry.isFile() && /\.png$/i.test(entry.name)).map((entry) => `${appIconDir}/${entry.name}`);
|
|
7628
7888
|
if (pngs.length === 0) return null;
|
|
7629
7889
|
const score = (path) => {
|
|
@@ -7736,7 +7996,7 @@ var createIosSplashTool = {
|
|
|
7736
7996
|
]) ?? await findLargestAppIconPng(context, viewPath);
|
|
7737
7997
|
if (resolvedSourceIcon) {
|
|
7738
7998
|
const sourceAbs = isAbsolute(resolvedSourceIcon) ? resolvedSourceIcon : resolveInsideWorkspace(context.workspace, ensureRelativePath3(resolvedSourceIcon));
|
|
7739
|
-
if (!
|
|
7999
|
+
if (!existsSync17(sourceAbs)) {
|
|
7740
8000
|
return { ok: false, summary: "Source splash icon not found.", error: `Missing source icon: ${resolvedSourceIcon}` };
|
|
7741
8001
|
}
|
|
7742
8002
|
await mkdir7(dirname9(resolveInsideWorkspace(context.workspace, iconPath)), { recursive: true });
|
|
@@ -7880,7 +8140,7 @@ function addDependencyLine(gradle, line) {
|
|
|
7880
8140
|
async function maybePatchAndroidGradle(context, rootGradlePath, moduleGradlePath) {
|
|
7881
8141
|
const files = [];
|
|
7882
8142
|
const rootAbs = resolveInsideWorkspace(context.workspace, rootGradlePath);
|
|
7883
|
-
if (
|
|
8143
|
+
if (existsSync17(rootAbs)) {
|
|
7884
8144
|
const rootGradle = await readFile8(rootAbs, "utf8");
|
|
7885
8145
|
const nextRootGradle = addLineBeforeClosingPluginsBlock(rootGradle, 'id("com.google.devtools.ksp") version "1.9.24-1.0.20" apply false');
|
|
7886
8146
|
if (nextRootGradle !== rootGradle) {
|
|
@@ -7889,7 +8149,7 @@ async function maybePatchAndroidGradle(context, rootGradlePath, moduleGradlePath
|
|
|
7889
8149
|
}
|
|
7890
8150
|
}
|
|
7891
8151
|
const moduleAbs = resolveInsideWorkspace(context.workspace, moduleGradlePath);
|
|
7892
|
-
if (
|
|
8152
|
+
if (existsSync17(moduleAbs)) {
|
|
7893
8153
|
let moduleGradle = await readFile8(moduleAbs, "utf8");
|
|
7894
8154
|
const before = moduleGradle;
|
|
7895
8155
|
moduleGradle = addLineBeforeClosingPluginsBlock(moduleGradle, 'id("com.google.devtools.ksp")');
|
|
@@ -7960,7 +8220,7 @@ var createAndroidFoundationTool = {
|
|
|
7960
8220
|
];
|
|
7961
8221
|
for (const [path, content] of outputs) {
|
|
7962
8222
|
const target = resolveInsideWorkspace(context.workspace, path);
|
|
7963
|
-
if (preserveExisting &&
|
|
8223
|
+
if (preserveExisting && existsSync17(target)) continue;
|
|
7964
8224
|
await mkdir7(dirname9(target), { recursive: true });
|
|
7965
8225
|
await writeFile7(target, content, "utf8");
|
|
7966
8226
|
files.push(path);
|
|
@@ -8277,8 +8537,8 @@ var commitPlatformChangesTool = {
|
|
|
8277
8537
|
const cleanPath = normalizeRelativePathForGit(path);
|
|
8278
8538
|
const workspaceCandidate = resolveInsideWorkspace(realWorkspace, cleanPath);
|
|
8279
8539
|
const repoCandidate = resolveInsideWorkspace(realRepoRoot, cleanPath);
|
|
8280
|
-
if (
|
|
8281
|
-
if (
|
|
8540
|
+
if (existsSync17(repoCandidate)) return relative8(realRepoRoot, await realpath(repoCandidate)).replace(/\\/g, "/");
|
|
8541
|
+
if (existsSync17(workspaceCandidate)) return relative8(realRepoRoot, await realpath(workspaceCandidate)).replace(/\\/g, "/");
|
|
8282
8542
|
const workspacePrefix = normalizeRelativePathForGit(relative8(realRepoRoot, realWorkspace));
|
|
8283
8543
|
if (workspacePrefix && workspacePrefix !== "." && (cleanPath === workspacePrefix || cleanPath.startsWith(`${workspacePrefix}/`))) return cleanPath;
|
|
8284
8544
|
const workspaceRelative = normalizeRelativePathForGit(relative8(realRepoRoot, workspaceCandidate));
|
|
@@ -8400,7 +8660,7 @@ function defaultTools() {
|
|
|
8400
8660
|
runCommandTool,
|
|
8401
8661
|
runShellTool
|
|
8402
8662
|
].map((tool) => /^validate_/.test(tool.name) || tool.name === "scan_secrets" ? { ...tool, preferredModel: verificationPreferredModel } : tool);
|
|
8403
|
-
return tools.filter((tool) => tool.name !== "search" ||
|
|
8663
|
+
return tools.filter((tool) => tool.name !== "search" || existsSync17("/usr/bin/rg") || existsSync17("/opt/homebrew/bin/rg") || existsSync17("/usr/local/bin/rg"));
|
|
8404
8664
|
}
|
|
8405
8665
|
|
|
8406
8666
|
// src/tools/registry.ts
|
|
@@ -8429,7 +8689,7 @@ var ToolRegistry = class {
|
|
|
8429
8689
|
// src/memory/goldenTasks.ts
|
|
8430
8690
|
import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile8, rename } from "fs/promises";
|
|
8431
8691
|
import { createHash as createHash2 } from "crypto";
|
|
8432
|
-
import { dirname as dirname10, join as
|
|
8692
|
+
import { dirname as dirname10, join as join17 } from "path";
|
|
8433
8693
|
var GOLDEN_TASK_MAX_BYTES = 10 * 1024 * 1024;
|
|
8434
8694
|
var GOLDEN_TASK_KEEP_LATEST = 200;
|
|
8435
8695
|
async function rotateGoldenTaskFileIfTooLarge(memoryPath) {
|
|
@@ -8468,7 +8728,7 @@ function taskSignature(runContext, manifest) {
|
|
|
8468
8728
|
}
|
|
8469
8729
|
async function recordGoldenTaskMemory(workspace, manifest, runContext) {
|
|
8470
8730
|
if (!enabled(runContext)) return;
|
|
8471
|
-
const memoryPath =
|
|
8731
|
+
const memoryPath = join17(workspace, ".tania", "memory", "golden-tasks.jsonl");
|
|
8472
8732
|
const validationErrors = manifest.validation?.issues.filter((issue2) => issue2.severity === "error") ?? [];
|
|
8473
8733
|
const record = {
|
|
8474
8734
|
schemaVersion: 1,
|
|
@@ -8492,7 +8752,7 @@ async function recordGoldenTaskMemory(workspace, manifest, runContext) {
|
|
|
8492
8752
|
await rotateGoldenTaskFileIfTooLarge(memoryPath);
|
|
8493
8753
|
}
|
|
8494
8754
|
async function readGoldenTaskMemory(workspace) {
|
|
8495
|
-
const memoryPath =
|
|
8755
|
+
const memoryPath = join17(workspace, ".tania", "memory", "golden-tasks.jsonl");
|
|
8496
8756
|
let raw = "";
|
|
8497
8757
|
try {
|
|
8498
8758
|
raw = await readFile9(memoryPath, "utf8");
|
|
@@ -8552,9 +8812,9 @@ function validateGoldenTaskSummary(summary) {
|
|
|
8552
8812
|
import { appendFile as appendFile3, mkdir as mkdir9, readFile as readFile10 } from "fs/promises";
|
|
8553
8813
|
import { createHash as createHash3 } from "crypto";
|
|
8554
8814
|
import { homedir as homedir3 } from "os";
|
|
8555
|
-
import { dirname as dirname11, join as
|
|
8815
|
+
import { dirname as dirname11, join as join18 } from "path";
|
|
8556
8816
|
function memoryRoot() {
|
|
8557
|
-
return envValue({}, "TANYA_MEMORY_HOME").trim() ||
|
|
8817
|
+
return envValue({}, "TANYA_MEMORY_HOME").trim() || join18(homedir3(), ".tania", "memory");
|
|
8558
8818
|
}
|
|
8559
8819
|
function taskSignature2(runContext, attempts) {
|
|
8560
8820
|
const source = JSON.stringify({
|
|
@@ -8578,7 +8838,7 @@ async function recordRepairRunMemory(runContext, attempts, manifest) {
|
|
|
8578
8838
|
finalIssueIds: finalErrors,
|
|
8579
8839
|
finalBlockers: manifest.blockers
|
|
8580
8840
|
};
|
|
8581
|
-
const path =
|
|
8841
|
+
const path = join18(memoryRoot(), "repair-runs.jsonl");
|
|
8582
8842
|
await mkdir9(dirname11(path), { recursive: true });
|
|
8583
8843
|
await appendFile3(path, `${JSON.stringify(record)}
|
|
8584
8844
|
`, "utf8");
|
|
@@ -8586,7 +8846,7 @@ async function recordRepairRunMemory(runContext, attempts, manifest) {
|
|
|
8586
8846
|
|
|
8587
8847
|
// src/obsidian/vaultAppender.ts
|
|
8588
8848
|
import { appendFile as appendFile4, mkdir as mkdir10, writeFile as writeFile9 } from "fs/promises";
|
|
8589
|
-
import { dirname as dirname12, join as
|
|
8849
|
+
import { dirname as dirname12, join as join19 } from "path";
|
|
8590
8850
|
function dailyNoteName(date = /* @__PURE__ */ new Date()) {
|
|
8591
8851
|
const year = date.getFullYear();
|
|
8592
8852
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
@@ -8620,7 +8880,7 @@ function buildTaskSection(manifest, runContext) {
|
|
|
8620
8880
|
].join("\n");
|
|
8621
8881
|
}
|
|
8622
8882
|
async function appendTaskToVault(vaultPath, manifest, runContext) {
|
|
8623
|
-
const notePath =
|
|
8883
|
+
const notePath = join19(vaultPath, dailyNoteName());
|
|
8624
8884
|
await mkdir10(dirname12(notePath), { recursive: true });
|
|
8625
8885
|
try {
|
|
8626
8886
|
await writeFile9(notePath, "", { flag: "wx" });
|
|
@@ -8630,21 +8890,21 @@ async function appendTaskToVault(vaultPath, manifest, runContext) {
|
|
|
8630
8890
|
}
|
|
8631
8891
|
|
|
8632
8892
|
// src/memory/auditLog.ts
|
|
8633
|
-
import { existsSync as
|
|
8893
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync as statSync5, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8634
8894
|
import { gzipSync } from "zlib";
|
|
8635
|
-
import { basename as basename5, join as
|
|
8895
|
+
import { basename as basename5, join as join20 } from "path";
|
|
8636
8896
|
var DEFAULT_AUDIT_MAX_BYTES = 50 * 1024 * 1024;
|
|
8637
8897
|
var DEFAULT_AUDIT_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
8638
8898
|
function appendAuditDecision(workspace, entry, options = {}) {
|
|
8639
8899
|
const path = auditPath(workspace);
|
|
8640
|
-
mkdirSync3(
|
|
8900
|
+
mkdirSync3(join20(workspace, ".tania"), { recursive: true });
|
|
8641
8901
|
rotateAuditIfNeeded(workspace, options);
|
|
8642
8902
|
writeFileSync3(path, `${JSON.stringify(entry)}
|
|
8643
8903
|
`, { encoding: "utf8", flag: "a" });
|
|
8644
8904
|
}
|
|
8645
8905
|
function readAuditDecisions(workspace, filters = {}) {
|
|
8646
8906
|
const path = auditPath(workspace);
|
|
8647
|
-
if (!
|
|
8907
|
+
if (!existsSync18(path)) return [];
|
|
8648
8908
|
const sinceTs = filters.sinceMs === void 0 ? null : Date.now() - filters.sinceMs;
|
|
8649
8909
|
const entries = readFileSync9(path, "utf8").split("\n").filter(Boolean).flatMap((line) => {
|
|
8650
8910
|
try {
|
|
@@ -8657,20 +8917,20 @@ function readAuditDecisions(workspace, filters = {}) {
|
|
|
8657
8917
|
return entries.slice(Math.max(0, entries.length - limit));
|
|
8658
8918
|
}
|
|
8659
8919
|
function auditPath(workspace) {
|
|
8660
|
-
return
|
|
8920
|
+
return join20(workspace, ".tania", "audit.jsonl");
|
|
8661
8921
|
}
|
|
8662
8922
|
function rotateAuditIfNeeded(workspace, options) {
|
|
8663
8923
|
const path = auditPath(workspace);
|
|
8664
|
-
if (!
|
|
8924
|
+
if (!existsSync18(path)) return;
|
|
8665
8925
|
const stats = statSync5(path);
|
|
8666
8926
|
const maxBytes = options.maxBytes ?? DEFAULT_AUDIT_MAX_BYTES;
|
|
8667
8927
|
const maxAgeMs = options.maxAgeMs ?? DEFAULT_AUDIT_MAX_AGE_MS;
|
|
8668
8928
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
8669
8929
|
if (stats.size < maxBytes && now.getTime() - stats.mtimeMs < maxAgeMs) return;
|
|
8670
|
-
const archiveDir =
|
|
8930
|
+
const archiveDir = join20(workspace, ".tania", "audit", "archive");
|
|
8671
8931
|
mkdirSync3(archiveDir, { recursive: true });
|
|
8672
8932
|
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
8673
|
-
const archivePath2 =
|
|
8933
|
+
const archivePath2 = join20(archiveDir, `audit-${stamp}-${basename5(path)}.gz`);
|
|
8674
8934
|
writeFileSync3(archivePath2, gzipSync(readFileSync9(path)));
|
|
8675
8935
|
unlinkSync(path);
|
|
8676
8936
|
}
|
|
@@ -8740,9 +9000,9 @@ var FileReadDedupCache = class {
|
|
|
8740
9000
|
};
|
|
8741
9001
|
|
|
8742
9002
|
// src/memory/reasoningArchive.ts
|
|
8743
|
-
import { existsSync as
|
|
9003
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync, statSync as statSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
8744
9004
|
import { appendFile as appendFile5 } from "fs/promises";
|
|
8745
|
-
import { join as
|
|
9005
|
+
import { join as join21 } from "path";
|
|
8746
9006
|
async function appendReasoningChunk(params) {
|
|
8747
9007
|
if (!params.content) return;
|
|
8748
9008
|
const dir = reasoningRunDir(params.workspace, params.runId);
|
|
@@ -8761,7 +9021,7 @@ async function appendReasoningChunk(params) {
|
|
|
8761
9021
|
}
|
|
8762
9022
|
function readReasoningArchive(workspace, runId) {
|
|
8763
9023
|
const file = reasoningArchivePath(workspace, runId);
|
|
8764
|
-
if (!
|
|
9024
|
+
if (!existsSync19(file)) return [];
|
|
8765
9025
|
return readFileSync10(file, "utf8").split(/\n+/).filter(Boolean).flatMap((line) => {
|
|
8766
9026
|
try {
|
|
8767
9027
|
const parsed = JSON.parse(line);
|
|
@@ -8783,7 +9043,7 @@ function readReasoningArchive(workspace, runId) {
|
|
|
8783
9043
|
}
|
|
8784
9044
|
function evictReasoningFromArchive(workspace, runId, thresholdBytes) {
|
|
8785
9045
|
const file = reasoningArchivePath(workspace, runId);
|
|
8786
|
-
if (!
|
|
9046
|
+
if (!existsSync19(file)) return 0;
|
|
8787
9047
|
const before = statSync6(file).size;
|
|
8788
9048
|
if (before <= thresholdBytes) return 0;
|
|
8789
9049
|
const entries = readReasoningArchive(workspace, runId);
|
|
@@ -8803,40 +9063,40 @@ function evictReasoningFromArchive(workspace, runId, thresholdBytes) {
|
|
|
8803
9063
|
return Math.max(0, before - statSync6(file).size);
|
|
8804
9064
|
}
|
|
8805
9065
|
function reasoningArchivePath(workspace, runId) {
|
|
8806
|
-
return
|
|
9066
|
+
return join21(reasoningRunDir(workspace, runId), "reasoning.jsonl");
|
|
8807
9067
|
}
|
|
8808
9068
|
function reasoningRunDir(workspace, runId) {
|
|
8809
|
-
return
|
|
9069
|
+
return join21(workspace, ".tania", "runs", runId);
|
|
8810
9070
|
}
|
|
8811
9071
|
|
|
8812
9072
|
// src/mcp/client.ts
|
|
8813
|
-
import { createWriteStream, existsSync as
|
|
8814
|
-
import { join as
|
|
9073
|
+
import { createWriteStream, existsSync as existsSync21, mkdirSync as mkdirSync5, renameSync as renameSync2, statSync as statSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
9074
|
+
import { join as join23 } from "path";
|
|
8815
9075
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8816
9076
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8817
9077
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
8818
9078
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
8819
9079
|
|
|
8820
9080
|
// src/mcp/config.ts
|
|
8821
|
-
import { existsSync as
|
|
9081
|
+
import { existsSync as existsSync20, readFileSync as readFileSync11 } from "fs";
|
|
8822
9082
|
import { homedir as homedir4 } from "os";
|
|
8823
|
-
import { join as
|
|
9083
|
+
import { join as join22 } from "path";
|
|
8824
9084
|
var EMPTY_CONFIG = { version: 1, servers: [] };
|
|
8825
9085
|
var TRANSPORTS = /* @__PURE__ */ new Set(["stdio", "sse", "http"]);
|
|
8826
9086
|
function loadMcpConfig(options) {
|
|
8827
9087
|
const home = options.home ?? homedir4();
|
|
8828
|
-
const userCurrent =
|
|
8829
|
-
const userLegacy =
|
|
8830
|
-
const userFile =
|
|
9088
|
+
const userCurrent = join22(home, ".tanya", "mcp.json");
|
|
9089
|
+
const userLegacy = join22(home, ".tania", "mcp.json");
|
|
9090
|
+
const userFile = existsSync20(userCurrent) ? userCurrent : existsSync20(userLegacy) ? userLegacy : null;
|
|
8831
9091
|
const candidates = [
|
|
8832
9092
|
...userFile ? [userFile] : [],
|
|
8833
|
-
|
|
9093
|
+
join22(options.cwd, ".tania", "mcp.json")
|
|
8834
9094
|
];
|
|
8835
9095
|
let config = cloneConfig(EMPTY_CONFIG);
|
|
8836
9096
|
const sources = [];
|
|
8837
9097
|
const issues = [];
|
|
8838
9098
|
for (const file of candidates) {
|
|
8839
|
-
if (!
|
|
9099
|
+
if (!existsSync20(file)) continue;
|
|
8840
9100
|
const parsed = parseMcpConfig(readFileSync11(file, "utf8"), file);
|
|
8841
9101
|
if (!parsed.ok) {
|
|
8842
9102
|
issues.push(...parsed.issues);
|
|
@@ -9077,7 +9337,7 @@ var McpClientManager = class {
|
|
|
9077
9337
|
createTransport(config) {
|
|
9078
9338
|
if (config.transport === "stdio") {
|
|
9079
9339
|
const logPath = mcpLogPath(this.workspace, config.name);
|
|
9080
|
-
mkdirSync5(
|
|
9340
|
+
mkdirSync5(join23(this.workspace, ".tania", "mcp", "logs"), { recursive: true });
|
|
9081
9341
|
rotateMcpLogIfNeeded(logPath);
|
|
9082
9342
|
const transport = new StdioClientTransport({
|
|
9083
9343
|
command: config.command ?? "",
|
|
@@ -9225,15 +9485,15 @@ function mcpCallTimeoutMs() {
|
|
|
9225
9485
|
return Math.max(1e3, numberEnvValue(process.env, "TANYA_MCP_CALL_TIMEOUT_MS", 3e4));
|
|
9226
9486
|
}
|
|
9227
9487
|
function mcpLogPath(workspace, serverName) {
|
|
9228
|
-
return
|
|
9488
|
+
return join23(workspace, ".tania", "mcp", "logs", `${serverName}.log`);
|
|
9229
9489
|
}
|
|
9230
9490
|
function rotateMcpLogIfNeeded(path) {
|
|
9231
|
-
if (!
|
|
9491
|
+
if (!existsSync21(path)) return;
|
|
9232
9492
|
try {
|
|
9233
9493
|
if (statSync7(path).size < 10 * 1024 * 1024) return;
|
|
9234
9494
|
const rotated = `${path}.1`;
|
|
9235
9495
|
try {
|
|
9236
|
-
if (
|
|
9496
|
+
if (existsSync21(rotated)) unlinkSync2(rotated);
|
|
9237
9497
|
} catch {
|
|
9238
9498
|
}
|
|
9239
9499
|
renameSync2(path, rotated);
|
|
@@ -9248,7 +9508,7 @@ function sleep2(ms) {
|
|
|
9248
9508
|
}
|
|
9249
9509
|
|
|
9250
9510
|
// src/agent/verifier/index.ts
|
|
9251
|
-
import { existsSync as
|
|
9511
|
+
import { existsSync as existsSync23, readFileSync as readFileSync12 } from "fs";
|
|
9252
9512
|
|
|
9253
9513
|
// src/agent/verifier/shell.ts
|
|
9254
9514
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -9285,8 +9545,8 @@ var realShell = async (cwd, command, args, options) => {
|
|
|
9285
9545
|
};
|
|
9286
9546
|
|
|
9287
9547
|
// src/agent/verifier/verifiers/goBackend.ts
|
|
9288
|
-
import { existsSync as
|
|
9289
|
-
import { dirname as dirname13, join as
|
|
9548
|
+
import { existsSync as existsSync22, readdirSync as readdirSync7, statSync as statSync8 } from "fs";
|
|
9549
|
+
import { dirname as dirname13, join as join24, relative as relative9, resolve as resolve8 } from "path";
|
|
9290
9550
|
import { load as loadYaml } from "js-yaml";
|
|
9291
9551
|
|
|
9292
9552
|
// src/agent/verifier/types.ts
|
|
@@ -9331,9 +9591,9 @@ function listDir(path) {
|
|
|
9331
9591
|
}
|
|
9332
9592
|
}
|
|
9333
9593
|
function dirContainsGoFile(path) {
|
|
9334
|
-
if (!
|
|
9594
|
+
if (!existsSync22(path)) return false;
|
|
9335
9595
|
for (const entry of listDir(path)) {
|
|
9336
|
-
const full =
|
|
9596
|
+
const full = join24(path, entry);
|
|
9337
9597
|
try {
|
|
9338
9598
|
const st = statSync8(full);
|
|
9339
9599
|
if (st.isFile() && entry.endsWith(".go")) return true;
|
|
@@ -9354,7 +9614,7 @@ function scanGoFiles(workspace) {
|
|
|
9354
9614
|
visited += 1;
|
|
9355
9615
|
for (const entry of listDir(dir)) {
|
|
9356
9616
|
if (entry === "node_modules" || entry === ".git" || entry === "dist" || entry === "build") continue;
|
|
9357
|
-
const full =
|
|
9617
|
+
const full = join24(dir, entry);
|
|
9358
9618
|
try {
|
|
9359
9619
|
const st = statSync8(full);
|
|
9360
9620
|
if (st.isFile() && entry.endsWith(".go")) {
|
|
@@ -9429,7 +9689,7 @@ function sqlFilesUnder(path) {
|
|
|
9429
9689
|
if (!dir) break;
|
|
9430
9690
|
visited += 1;
|
|
9431
9691
|
for (const entry of listDir(dir)) {
|
|
9432
|
-
const full =
|
|
9692
|
+
const full = join24(dir, entry);
|
|
9433
9693
|
try {
|
|
9434
9694
|
const st = statSync8(full);
|
|
9435
9695
|
if (st.isFile() && entry.endsWith(".sql")) out.push(full);
|
|
@@ -9467,7 +9727,7 @@ function referencedSqlQueryFiles(workspace, queryPath) {
|
|
|
9467
9727
|
const cleanQueryPath = queryPath.trim();
|
|
9468
9728
|
if (!cleanQueryPath) return [];
|
|
9469
9729
|
if (!/[*?]/.test(cleanQueryPath)) {
|
|
9470
|
-
return sqlFilesUnder(
|
|
9730
|
+
return sqlFilesUnder(join24(workspace, cleanQueryPath));
|
|
9471
9731
|
}
|
|
9472
9732
|
const firstGlob = cleanQueryPath.search(/[*?]/);
|
|
9473
9733
|
const prefix = cleanQueryPath.slice(0, firstGlob);
|
|
@@ -9487,13 +9747,13 @@ var goBackendVerifier = {
|
|
|
9487
9747
|
id: "go-backend",
|
|
9488
9748
|
platform: "go-backend",
|
|
9489
9749
|
appliesTo(ctx) {
|
|
9490
|
-
if (ctx.fileExists(
|
|
9750
|
+
if (ctx.fileExists(join24(ctx.workspace, "go.mod"))) return true;
|
|
9491
9751
|
const text2 = combinedTaskText(ctx.runContext, ctx.prompt);
|
|
9492
9752
|
return /\bgo\.mod\b/.test(text2) || /\bgolang\b/.test(text2);
|
|
9493
9753
|
},
|
|
9494
9754
|
async run(ctx) {
|
|
9495
9755
|
const checks = [];
|
|
9496
|
-
const goModPath =
|
|
9756
|
+
const goModPath = join24(ctx.workspace, "go.mod");
|
|
9497
9757
|
const goModText = ctx.readText(goModPath);
|
|
9498
9758
|
const text2 = combinedTaskText(ctx.runContext, ctx.prompt);
|
|
9499
9759
|
checks.push(makeCheck({
|
|
@@ -9518,7 +9778,7 @@ var goBackendVerifier = {
|
|
|
9518
9778
|
requireDep(/github\.com\/go-chi\/chi\/v5/, "chi/v5", mentionsAny(text2, CHI_PATTERNS));
|
|
9519
9779
|
requireDep(/github\.com\/jackc\/pgx\/v5/, "pgx/v5", mentionsAny(text2, PGX_PATTERNS));
|
|
9520
9780
|
if (mentionsAny(text2, REST_SERVER_PATTERNS)) {
|
|
9521
|
-
const mainPath =
|
|
9781
|
+
const mainPath = join24(ctx.workspace, "cmd", "server", "main.go");
|
|
9522
9782
|
const present = ctx.fileExists(mainPath);
|
|
9523
9783
|
checks.push(makeCheck({
|
|
9524
9784
|
id: "cmd-server-main",
|
|
@@ -9528,7 +9788,7 @@ var goBackendVerifier = {
|
|
|
9528
9788
|
error: present ? void 0 : "expected entrypoint at cmd/server/main.go"
|
|
9529
9789
|
}));
|
|
9530
9790
|
}
|
|
9531
|
-
const internalPath =
|
|
9791
|
+
const internalPath = join24(ctx.workspace, "internal");
|
|
9532
9792
|
if (mentionsAny(text2, INTERNAL_PKG_PATTERNS)) {
|
|
9533
9793
|
const hasInternalGo = dirContainsGoFile(internalPath);
|
|
9534
9794
|
checks.push(makeCheck({
|
|
@@ -9539,7 +9799,7 @@ var goBackendVerifier = {
|
|
|
9539
9799
|
error: hasInternalGo ? void 0 : "expected at least one Go package under internal/"
|
|
9540
9800
|
}));
|
|
9541
9801
|
}
|
|
9542
|
-
const sqlcPath =
|
|
9802
|
+
const sqlcPath = join24(ctx.workspace, "sqlc.yaml");
|
|
9543
9803
|
const sqlcText = ctx.readText(sqlcPath);
|
|
9544
9804
|
if (sqlcText && mentionsAny(text2, SQLC_PATTERNS)) {
|
|
9545
9805
|
for (const entry of parseSqlcConfig(sqlcText)) {
|
|
@@ -9556,7 +9816,7 @@ var goBackendVerifier = {
|
|
|
9556
9816
|
}));
|
|
9557
9817
|
continue;
|
|
9558
9818
|
}
|
|
9559
|
-
const target =
|
|
9819
|
+
const target = join24(ctx.workspace, dir);
|
|
9560
9820
|
const present = ctx.fileExists(target) && dirContainsGoFile(target);
|
|
9561
9821
|
checks.push(makeCheck({
|
|
9562
9822
|
id: `sqlc-out-${dir}`,
|
|
@@ -9630,10 +9890,10 @@ var goBackendVerifier = {
|
|
|
9630
9890
|
};
|
|
9631
9891
|
|
|
9632
9892
|
// src/agent/verifier/verifiers/nodeBackend.ts
|
|
9633
|
-
import { join as
|
|
9893
|
+
import { join as join25 } from "path";
|
|
9634
9894
|
var BACKEND_DEP_HINTS = ["express", "fastify", "@nestjs/core", "hono", "koa", "@fastify/", "@hono/"];
|
|
9635
9895
|
function readPackageJson(ctx) {
|
|
9636
|
-
const text2 = ctx.readText(
|
|
9896
|
+
const text2 = ctx.readText(join25(ctx.workspace, "package.json"));
|
|
9637
9897
|
if (!text2) return null;
|
|
9638
9898
|
try {
|
|
9639
9899
|
return JSON.parse(text2);
|
|
@@ -9694,10 +9954,10 @@ var nodeBackendVerifier = {
|
|
|
9694
9954
|
};
|
|
9695
9955
|
|
|
9696
9956
|
// src/agent/verifier/verifiers/frontend.ts
|
|
9697
|
-
import { join as
|
|
9957
|
+
import { join as join26 } from "path";
|
|
9698
9958
|
var FRONTEND_DEP_HINTS = ["next", "react", "vite", "@vitejs/", "@tanstack/router", "remix", "@remix-run/"];
|
|
9699
9959
|
function readPackageJson2(ctx) {
|
|
9700
|
-
const text2 = ctx.readText(
|
|
9960
|
+
const text2 = ctx.readText(join26(ctx.workspace, "package.json"));
|
|
9701
9961
|
if (!text2) return null;
|
|
9702
9962
|
try {
|
|
9703
9963
|
return JSON.parse(text2);
|
|
@@ -9758,16 +10018,16 @@ var frontendVerifier = {
|
|
|
9758
10018
|
};
|
|
9759
10019
|
|
|
9760
10020
|
// src/agent/verifier/verifiers/mobile.ts
|
|
9761
|
-
import { join as
|
|
10021
|
+
import { join as join27 } from "path";
|
|
9762
10022
|
var iosVerifier = {
|
|
9763
10023
|
id: "ios",
|
|
9764
10024
|
platform: "ios",
|
|
9765
10025
|
appliesTo(ctx) {
|
|
9766
|
-
return ctx.fileExists(
|
|
10026
|
+
return ctx.fileExists(join27(ctx.workspace, "Package.swift"));
|
|
9767
10027
|
},
|
|
9768
10028
|
async run(ctx) {
|
|
9769
10029
|
const checks = [];
|
|
9770
|
-
const text2 = ctx.readText(
|
|
10030
|
+
const text2 = ctx.readText(join27(ctx.workspace, "Package.swift"));
|
|
9771
10031
|
checks.push(makeCheck({
|
|
9772
10032
|
id: "package-swift-present",
|
|
9773
10033
|
description: "Package.swift exists",
|
|
@@ -9782,12 +10042,12 @@ var androidVerifier = {
|
|
|
9782
10042
|
id: "android",
|
|
9783
10043
|
platform: "android",
|
|
9784
10044
|
appliesTo(ctx) {
|
|
9785
|
-
return ctx.fileExists(
|
|
10045
|
+
return ctx.fileExists(join27(ctx.workspace, "build.gradle.kts")) || ctx.fileExists(join27(ctx.workspace, "settings.gradle.kts"));
|
|
9786
10046
|
},
|
|
9787
10047
|
async run(ctx) {
|
|
9788
10048
|
const checks = [];
|
|
9789
|
-
const buildGradle = ctx.readText(
|
|
9790
|
-
const settingsGradle = ctx.readText(
|
|
10049
|
+
const buildGradle = ctx.readText(join27(ctx.workspace, "build.gradle.kts"));
|
|
10050
|
+
const settingsGradle = ctx.readText(join27(ctx.workspace, "settings.gradle.kts"));
|
|
9791
10051
|
const passed = buildGradle !== null || settingsGradle !== null;
|
|
9792
10052
|
checks.push(makeCheck({
|
|
9793
10053
|
id: "gradle-config-present",
|
|
@@ -9822,7 +10082,7 @@ function buildContext(options) {
|
|
|
9822
10082
|
runContext: options.runContext,
|
|
9823
10083
|
prompt: options.prompt ?? "",
|
|
9824
10084
|
shell: options.shell ?? defaultShell(),
|
|
9825
|
-
fileExists: (path) =>
|
|
10085
|
+
fileExists: (path) => existsSync23(path),
|
|
9826
10086
|
readText: (path) => {
|
|
9827
10087
|
try {
|
|
9828
10088
|
return readFileSync12(path, "utf8");
|
|
@@ -9876,8 +10136,8 @@ async function verifyFinalState(options) {
|
|
|
9876
10136
|
|
|
9877
10137
|
// src/agent/forbiddenPatterns.ts
|
|
9878
10138
|
import { readFile as readFile11, writeFile as writeFile10, mkdir as mkdir11 } from "fs/promises";
|
|
9879
|
-
import { existsSync as
|
|
9880
|
-
import { join as
|
|
10139
|
+
import { existsSync as existsSync24 } from "fs";
|
|
10140
|
+
import { join as join28 } from "path";
|
|
9881
10141
|
var TEST_DIR_EXCLUSIONS = /(?:^|\/)(?:test|tests|__tests__|spec|specs|androidTest)(?:\/|$)/;
|
|
9882
10142
|
var DEFAULT_FORBIDDEN_PATTERNS = [
|
|
9883
10143
|
{
|
|
@@ -10095,8 +10355,8 @@ var DEFAULT_FORBIDDEN_PATTERNS = [
|
|
|
10095
10355
|
}
|
|
10096
10356
|
];
|
|
10097
10357
|
async function loadProjectForbiddenPatterns(workspace) {
|
|
10098
|
-
const candidate =
|
|
10099
|
-
if (!
|
|
10358
|
+
const candidate = join28(workspace, ".tania", "forbidden-patterns.json");
|
|
10359
|
+
if (!existsSync24(candidate)) return [];
|
|
10100
10360
|
try {
|
|
10101
10361
|
const raw = await readFile11(candidate, "utf8");
|
|
10102
10362
|
const parsed = JSON.parse(raw);
|
|
@@ -10122,7 +10382,7 @@ async function scanForbiddenPatterns(workspace, changedFiles, patterns2) {
|
|
|
10122
10382
|
if (matchingPatterns.length === 0) continue;
|
|
10123
10383
|
let content;
|
|
10124
10384
|
try {
|
|
10125
|
-
content = await readFile11(
|
|
10385
|
+
content = await readFile11(join28(workspace, file), "utf8");
|
|
10126
10386
|
} catch {
|
|
10127
10387
|
continue;
|
|
10128
10388
|
}
|
|
@@ -10146,10 +10406,10 @@ async function scanForbiddenPatterns(workspace, changedFiles, patterns2) {
|
|
|
10146
10406
|
}
|
|
10147
10407
|
async function recordFireMetrics(workspace, fireCounts) {
|
|
10148
10408
|
try {
|
|
10149
|
-
const metricsDir =
|
|
10150
|
-
const metricsPath =
|
|
10409
|
+
const metricsDir = join28(workspace, ".tania", "memory");
|
|
10410
|
+
const metricsPath = join28(metricsDir, "forbidden-patterns-metrics.json");
|
|
10151
10411
|
let existing = { totals: {}, lastFiredAt: {} };
|
|
10152
|
-
if (
|
|
10412
|
+
if (existsSync24(metricsPath)) {
|
|
10153
10413
|
try {
|
|
10154
10414
|
const raw = await readFile11(metricsPath, "utf8");
|
|
10155
10415
|
const parsed = JSON.parse(raw);
|
|
@@ -10177,11 +10437,11 @@ async function recordFireMetrics(workspace, fireCounts) {
|
|
|
10177
10437
|
|
|
10178
10438
|
// src/agent/validators/core.ts
|
|
10179
10439
|
import { readdir as readdir4, readFile as readFile12 } from "fs/promises";
|
|
10180
|
-
import { join as
|
|
10440
|
+
import { join as join30 } from "path";
|
|
10181
10441
|
|
|
10182
10442
|
// src/agent/validators/rules/load.ts
|
|
10183
10443
|
import { readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync9 } from "fs";
|
|
10184
|
-
import { join as
|
|
10444
|
+
import { join as join29 } from "path";
|
|
10185
10445
|
|
|
10186
10446
|
// src/agent/validators/rules/index.ts
|
|
10187
10447
|
var builtInValidatorRuleFiles = [];
|
|
@@ -10305,7 +10565,7 @@ function jsonFiles(path) {
|
|
|
10305
10565
|
if (stat8.isFile()) return path.endsWith(".json") ? [path] : [];
|
|
10306
10566
|
if (!stat8.isDirectory()) return [];
|
|
10307
10567
|
try {
|
|
10308
|
-
return readdirSync8(path, { withFileTypes: true }).flatMap((entry) => jsonFiles(
|
|
10568
|
+
return readdirSync8(path, { withFileTypes: true }).flatMap((entry) => jsonFiles(join29(path, entry.name))).sort((a, b) => a.localeCompare(b));
|
|
10309
10569
|
} catch {
|
|
10310
10570
|
return [];
|
|
10311
10571
|
}
|
|
@@ -10462,7 +10722,7 @@ function featureCoveredByText(feature, implementationText) {
|
|
|
10462
10722
|
}
|
|
10463
10723
|
async function readWorkspaceFile(workspace, filePath) {
|
|
10464
10724
|
try {
|
|
10465
|
-
return await readFile12(
|
|
10725
|
+
return await readFile12(join30(workspace, filePath), "utf8");
|
|
10466
10726
|
} catch {
|
|
10467
10727
|
return null;
|
|
10468
10728
|
}
|
|
@@ -10479,7 +10739,7 @@ async function findWorkspaceFiles(workspace, predicate, options = {}) {
|
|
|
10479
10739
|
if (results.length >= limit) return;
|
|
10480
10740
|
let entries;
|
|
10481
10741
|
try {
|
|
10482
|
-
entries = await readdir4(
|
|
10742
|
+
entries = await readdir4(join30(workspace, relativeDir), { withFileTypes: true });
|
|
10483
10743
|
} catch {
|
|
10484
10744
|
return;
|
|
10485
10745
|
}
|
|
@@ -12259,7 +12519,7 @@ function commitStillRequired(manifest, beforeGitSnapshot, runContext) {
|
|
|
12259
12519
|
|
|
12260
12520
|
// src/agent/report.ts
|
|
12261
12521
|
import { execFile as execFile4 } from "child_process";
|
|
12262
|
-
import { existsSync as
|
|
12522
|
+
import { existsSync as existsSync25 } from "fs";
|
|
12263
12523
|
import { readdir as readdir6, rm, stat as stat6 } from "fs/promises";
|
|
12264
12524
|
import { resolve as resolve10 } from "path";
|
|
12265
12525
|
import { promisify as promisify4 } from "util";
|
|
@@ -12293,7 +12553,7 @@ async function cleanupGeneratedNoise(workspace) {
|
|
|
12293
12553
|
}
|
|
12294
12554
|
for (const relPath of generatedFastlanePaths) {
|
|
12295
12555
|
const absPath = resolve10(workspace, relPath);
|
|
12296
|
-
if (!
|
|
12556
|
+
if (!existsSync25(absPath)) continue;
|
|
12297
12557
|
if (await pathIsGitTracked(workspace, relPath)) continue;
|
|
12298
12558
|
try {
|
|
12299
12559
|
await rm(absPath, { recursive: true, force: true });
|
|
@@ -13060,9 +13320,9 @@ function levenshtein(a, b) {
|
|
|
13060
13320
|
}
|
|
13061
13321
|
|
|
13062
13322
|
// src/agent/runner.ts
|
|
13063
|
-
import { existsSync as
|
|
13323
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync6, readdirSync as readdirSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
13064
13324
|
import { cp as cp2, mkdir as mkdir12, rm as rm2, stat as stat7 } from "fs/promises";
|
|
13065
|
-
import { dirname as dirname14, isAbsolute as isAbsolute2, join as
|
|
13325
|
+
import { dirname as dirname14, isAbsolute as isAbsolute2, join as join31, relative as relative11, resolve as resolve11 } from "path";
|
|
13066
13326
|
var CONTEXT_TOKEN_LIMIT = 48e3;
|
|
13067
13327
|
var permissionModes = /* @__PURE__ */ new Set(["default", "ask", "bypass", "plan"]);
|
|
13068
13328
|
var sessionSpendTokens = 0;
|
|
@@ -13103,7 +13363,7 @@ function materializedContextCleanupEnabled(manifest, runContext) {
|
|
|
13103
13363
|
async function cleanupMaterializedContext(workspace, manifest, runContext) {
|
|
13104
13364
|
if (!materializedContextCleanupEnabled(manifest, runContext)) return;
|
|
13105
13365
|
const taniaDir = resolve11(workspace, ".tania");
|
|
13106
|
-
if (!
|
|
13366
|
+
if (!existsSync26(taniaDir)) return;
|
|
13107
13367
|
if (await hasTrackedPathUnder(workspace, ".tania")) return;
|
|
13108
13368
|
try {
|
|
13109
13369
|
await rm2(taniaDir, { recursive: true, force: true });
|
|
@@ -13139,7 +13399,7 @@ function rotateRunSummaryFiles(runsDir) {
|
|
|
13139
13399
|
if (excess <= 0) return;
|
|
13140
13400
|
for (const stale of entries.slice(0, excess)) {
|
|
13141
13401
|
try {
|
|
13142
|
-
unlinkSync3(
|
|
13402
|
+
unlinkSync3(join31(runsDir, stale));
|
|
13143
13403
|
} catch {
|
|
13144
13404
|
}
|
|
13145
13405
|
}
|
|
@@ -13148,11 +13408,11 @@ function rotateRunSummaryFiles(runsDir) {
|
|
|
13148
13408
|
}
|
|
13149
13409
|
function logRunSummarySilently(params) {
|
|
13150
13410
|
try {
|
|
13151
|
-
const runsDir =
|
|
13152
|
-
const outputDir = params.parentRunId ?
|
|
13411
|
+
const runsDir = join31(params.workspace, ".tania", "runs");
|
|
13412
|
+
const outputDir = params.parentRunId ? join31(runsDir, params.parentRunId) : runsDir;
|
|
13153
13413
|
mkdirSync6(outputDir, { recursive: true });
|
|
13154
13414
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
13155
|
-
const logPath =
|
|
13415
|
+
const logPath = join31(outputDir, params.parentRunId ? `${params.runId}.json` : `${params.runId}.json`);
|
|
13156
13416
|
writeFileSync5(
|
|
13157
13417
|
logPath,
|
|
13158
13418
|
JSON.stringify(
|
|
@@ -13297,7 +13557,7 @@ function pruneStaleRepairReminders(messages) {
|
|
|
13297
13557
|
return messages.filter((msg, idx) => idx === lastIndex || !isRepairReminder(msg));
|
|
13298
13558
|
}
|
|
13299
13559
|
function isTypeScriptProject(workspace) {
|
|
13300
|
-
return
|
|
13560
|
+
return existsSync26(join31(workspace, "tsconfig.json"));
|
|
13301
13561
|
}
|
|
13302
13562
|
function repairAttemptBudget(options) {
|
|
13303
13563
|
const configured = typeof options.runContext?.metadata?.repairAttempts === "number" ? options.runContext.metadata.repairAttempts : typeof options.runContext?.metadata?.repairAttempts === "string" ? Number(options.runContext.metadata.repairAttempts) : options.repairAttempts;
|
|
@@ -13480,6 +13740,32 @@ function auditModelRouted(workspace, context, event) {
|
|
|
13480
13740
|
reason: event.reason
|
|
13481
13741
|
});
|
|
13482
13742
|
}
|
|
13743
|
+
function forcedRouteFromRunContext(runContext) {
|
|
13744
|
+
const metadata = runContext?.metadata;
|
|
13745
|
+
if (!metadata) return null;
|
|
13746
|
+
const forcedModel = stringMetadata(metadata, "forced_model") ?? stringMetadata(metadata, "forcedModel");
|
|
13747
|
+
const forcedProvider = stringMetadata(metadata, "forced_cli") ?? stringMetadata(metadata, "forcedCli") ?? stringMetadata(metadata, "forced_provider") ?? stringMetadata(metadata, "forcedProvider");
|
|
13748
|
+
if (!forcedModel && !forcedProvider) return null;
|
|
13749
|
+
if (forcedModel?.includes("/") && !forcedProvider) {
|
|
13750
|
+
const [provider2, model] = forcedModel.split("/", 2);
|
|
13751
|
+
if (provider2?.trim() && model?.trim()) return { provider: provider2.trim(), model: model.trim() };
|
|
13752
|
+
}
|
|
13753
|
+
const provider = forcedProvider ?? inferForcedProvider(forcedModel ?? "");
|
|
13754
|
+
if (!provider || !forcedModel) return null;
|
|
13755
|
+
return { provider, model: forcedModel };
|
|
13756
|
+
}
|
|
13757
|
+
function stringMetadata(metadata, key) {
|
|
13758
|
+
const value = metadata[key];
|
|
13759
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
13760
|
+
}
|
|
13761
|
+
function inferForcedProvider(model) {
|
|
13762
|
+
if (/^deepseek-/i.test(model)) return "deepseek";
|
|
13763
|
+
if (/^(?:gpt-|o\d|o\d-|chatgpt)/i.test(model)) return "openai";
|
|
13764
|
+
if (/^claude-/i.test(model)) return "claude";
|
|
13765
|
+
if (/^gemini-/i.test(model)) return "gemini";
|
|
13766
|
+
if (/^qwen/i.test(model)) return "qwen";
|
|
13767
|
+
return null;
|
|
13768
|
+
}
|
|
13483
13769
|
function auditEscalation(workspace, context, event) {
|
|
13484
13770
|
appendAuditDecision(workspace, {
|
|
13485
13771
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -13742,10 +14028,12 @@ async function runAgent(options) {
|
|
|
13742
14028
|
let lastProviderKey = providerKey(activeProvider);
|
|
13743
14029
|
if (options.routing?.enabled) {
|
|
13744
14030
|
try {
|
|
14031
|
+
const forcedRoute = forcedRouteFromRunContext(options.runContext);
|
|
13745
14032
|
const initialRoute = resolveRouteWithContextGuard({
|
|
13746
14033
|
stepType: "planning",
|
|
13747
14034
|
table: options.routing.table,
|
|
13748
|
-
messages: [...options.history ?? [], { role: "user", content: options.prompt }]
|
|
14035
|
+
messages: [...options.history ?? [], { role: "user", content: options.prompt }],
|
|
14036
|
+
...forcedRoute ? { forcedRoute } : {}
|
|
13749
14037
|
});
|
|
13750
14038
|
activeProvider = options.routing.providerFactory(initialRoute);
|
|
13751
14039
|
lastProviderKey = providerKey(activeProvider);
|
|
@@ -13961,7 +14249,7 @@ async function runAgent(options) {
|
|
|
13961
14249
|
const outputRootValue = options.runContext?.metadata?.artifactOutputRoot;
|
|
13962
14250
|
if (typeof outputRootValue !== "string" || !outputRootValue.trim()) return [];
|
|
13963
14251
|
const localOutputRoot = resolve11(workspace, ".tania", "artifact-output");
|
|
13964
|
-
if (!
|
|
14252
|
+
if (!existsSync26(localOutputRoot)) return [];
|
|
13965
14253
|
const localFiles = await listFilesRecursive(localOutputRoot);
|
|
13966
14254
|
if (localFiles.length === 0) return [];
|
|
13967
14255
|
const outputRoot = resolve11(outputRootValue);
|
|
@@ -13988,6 +14276,7 @@ async function runAgent(options) {
|
|
|
13988
14276
|
promptTokens: totalPromptTokens,
|
|
13989
14277
|
completionTokens: totalCompletionTokens,
|
|
13990
14278
|
reasoningTokens: totalReasoningTokens,
|
|
14279
|
+
costUsd: runSpendUsd,
|
|
13991
14280
|
systemPromptTokens,
|
|
13992
14281
|
repoMapTokens,
|
|
13993
14282
|
toolResultTokens: totalToolResultTokens
|
|
@@ -14065,11 +14354,31 @@ async function runAgent(options) {
|
|
|
14065
14354
|
auditModelRouted(workspace, permissionContext, event2);
|
|
14066
14355
|
return { provider: provider2, stepType: forced.stepType };
|
|
14067
14356
|
}
|
|
14068
|
-
const
|
|
14069
|
-
|
|
14357
|
+
const classifierState = {
|
|
14358
|
+
messages,
|
|
14359
|
+
turnIndex: turn,
|
|
14360
|
+
pendingToolCalls: pendingToolCallsForRouting(),
|
|
14361
|
+
cwd: workspace,
|
|
14362
|
+
...options.prompt ? { prompt: options.prompt } : {},
|
|
14363
|
+
...options.runContext ? { runContext: options.runContext } : {}
|
|
14364
|
+
};
|
|
14365
|
+
const stepType = classifyStep(classifierState);
|
|
14366
|
+
const forcedRoute = forcedRouteFromRunContext(options.runContext);
|
|
14367
|
+
const route = forcedRoute ? resolveRouteWithContextGuard({
|
|
14070
14368
|
stepType,
|
|
14071
14369
|
table: options.routing.table,
|
|
14072
|
-
messages
|
|
14370
|
+
messages,
|
|
14371
|
+
prompt: options.prompt,
|
|
14372
|
+
cwd: workspace,
|
|
14373
|
+
...options.runContext ? { runContext: options.runContext } : {},
|
|
14374
|
+
forcedRoute
|
|
14375
|
+
}) : preferredRouteForStep(stepType) ?? resolveRouteWithContextGuard({
|
|
14376
|
+
stepType,
|
|
14377
|
+
table: options.routing.table,
|
|
14378
|
+
messages,
|
|
14379
|
+
prompt: options.prompt,
|
|
14380
|
+
cwd: workspace,
|
|
14381
|
+
...options.runContext ? { runContext: options.runContext } : {}
|
|
14073
14382
|
});
|
|
14074
14383
|
const provider = options.routing.providerFactory(route);
|
|
14075
14384
|
const key = providerKey(provider);
|
|
@@ -14085,6 +14394,24 @@ async function runAgent(options) {
|
|
|
14085
14394
|
cacheImpact
|
|
14086
14395
|
};
|
|
14087
14396
|
await options.sink(event);
|
|
14397
|
+
if (route.cascade && route.cascade.selectedIndex > 0) {
|
|
14398
|
+
await options.sink({
|
|
14399
|
+
type: "provider.raw",
|
|
14400
|
+
provider: provider.id,
|
|
14401
|
+
model: provider.model,
|
|
14402
|
+
event: {
|
|
14403
|
+
type: "model_routed",
|
|
14404
|
+
reason: "cascade-fit",
|
|
14405
|
+
stepType,
|
|
14406
|
+
provider: provider.id,
|
|
14407
|
+
model: provider.model,
|
|
14408
|
+
attempted_routes: route.cascade.attemptedRoutes,
|
|
14409
|
+
estimated_tokens: route.cascade.estimatedTokens,
|
|
14410
|
+
safety_factor: route.cascade.safetyFactor,
|
|
14411
|
+
selected_route: route.cascade.selectedRoute
|
|
14412
|
+
}
|
|
14413
|
+
});
|
|
14414
|
+
}
|
|
14088
14415
|
auditModelRouted(workspace, permissionContext, event);
|
|
14089
14416
|
return { provider, stepType, route };
|
|
14090
14417
|
}
|
|
@@ -14947,7 +15274,7 @@ import { stdout } from "process";
|
|
|
14947
15274
|
// src/sessions/storage.ts
|
|
14948
15275
|
import { randomBytes } from "crypto";
|
|
14949
15276
|
import {
|
|
14950
|
-
existsSync as
|
|
15277
|
+
existsSync as existsSync27,
|
|
14951
15278
|
mkdirSync as mkdirSync7,
|
|
14952
15279
|
readFileSync as readFileSync14,
|
|
14953
15280
|
readdirSync as readdirSync10,
|
|
@@ -14957,7 +15284,7 @@ import {
|
|
|
14957
15284
|
appendFileSync
|
|
14958
15285
|
} from "fs";
|
|
14959
15286
|
import { homedir as homedir5 } from "os";
|
|
14960
|
-
import { basename as basename6, dirname as dirname15, join as
|
|
15287
|
+
import { basename as basename6, dirname as dirname15, join as join32, parse, relative as relative12, resolve as resolve12 } from "path";
|
|
14961
15288
|
var activeSessionPaths = /* @__PURE__ */ new Map();
|
|
14962
15289
|
function defaultCwd(cwd) {
|
|
14963
15290
|
return resolve12(cwd ?? process.cwd());
|
|
@@ -14966,13 +15293,13 @@ function defaultHome(homeDir) {
|
|
|
14966
15293
|
return resolve12(homeDir ?? homedir5());
|
|
14967
15294
|
}
|
|
14968
15295
|
function globalSessionsDir(homeDir) {
|
|
14969
|
-
return
|
|
15296
|
+
return join32(defaultHome(homeDir), ".tania", "sessions", "global");
|
|
14970
15297
|
}
|
|
14971
15298
|
function findProjectTaniaDir(cwd) {
|
|
14972
15299
|
let current = resolve12(cwd);
|
|
14973
15300
|
while (true) {
|
|
14974
|
-
const candidate =
|
|
14975
|
-
if (
|
|
15301
|
+
const candidate = join32(current, ".tania");
|
|
15302
|
+
if (existsSync27(candidate) && statSync10(candidate).isDirectory()) return candidate;
|
|
14976
15303
|
const parent = dirname15(current);
|
|
14977
15304
|
if (parent === current) return null;
|
|
14978
15305
|
current = parent;
|
|
@@ -14980,14 +15307,14 @@ function findProjectTaniaDir(cwd) {
|
|
|
14980
15307
|
}
|
|
14981
15308
|
function resolveSessionsDir(options = {}) {
|
|
14982
15309
|
const projectTania = findProjectTaniaDir(defaultCwd(options.cwd));
|
|
14983
|
-
if (projectTania) return { dir:
|
|
15310
|
+
if (projectTania) return { dir: join32(projectTania, "sessions"), scope: "project" };
|
|
14984
15311
|
return { dir: globalSessionsDir(options.homeDir), scope: "global" };
|
|
14985
15312
|
}
|
|
14986
15313
|
function ensureSessionsDir(dir, scope) {
|
|
14987
15314
|
mkdirSync7(dir, { recursive: true });
|
|
14988
15315
|
if (scope === "project") {
|
|
14989
|
-
const ignorePath =
|
|
14990
|
-
if (!
|
|
15316
|
+
const ignorePath = join32(dir, ".gitignore");
|
|
15317
|
+
if (!existsSync27(ignorePath)) writeFileSync6(ignorePath, "*\n", "utf8");
|
|
14991
15318
|
}
|
|
14992
15319
|
}
|
|
14993
15320
|
function shortLabel(content) {
|
|
@@ -15130,7 +15457,7 @@ function sanitizeTurn(turn) {
|
|
|
15130
15457
|
return sanitized;
|
|
15131
15458
|
}
|
|
15132
15459
|
function sessionPath(dir, id) {
|
|
15133
|
-
return
|
|
15460
|
+
return join32(dir, `${id}.json`);
|
|
15134
15461
|
}
|
|
15135
15462
|
function jsonlPathFor(path) {
|
|
15136
15463
|
return path.replace(/\.json$/, ".jsonl");
|
|
@@ -15153,7 +15480,7 @@ function readBaseSession(path) {
|
|
|
15153
15480
|
};
|
|
15154
15481
|
}
|
|
15155
15482
|
function readTurnsFromJsonl(path, fallbackJsonPath) {
|
|
15156
|
-
if (!
|
|
15483
|
+
if (!existsSync27(path)) return { turns: readBaseSession(fallbackJsonPath).turns, warnings: [] };
|
|
15157
15484
|
const raw = readFileSync14(path, "utf8");
|
|
15158
15485
|
const turns = [];
|
|
15159
15486
|
const warnings = [];
|
|
@@ -15224,7 +15551,7 @@ function latestUpdatedAt(base, turns) {
|
|
|
15224
15551
|
}
|
|
15225
15552
|
function resolveSessionJsonPath(sessionId, options = {}) {
|
|
15226
15553
|
const active = activeSessionPaths.get(sessionId);
|
|
15227
|
-
if (active &&
|
|
15554
|
+
if (active && existsSync27(active)) return active;
|
|
15228
15555
|
const matches = findSessionPathMatches(sessionId, options);
|
|
15229
15556
|
if (matches.length === 1) return matches[0].path;
|
|
15230
15557
|
if (matches.length > 1) throw new Error(`Session id "${sessionId}" is ambiguous: ${matches.map((match) => match.id).join(", ")}`);
|
|
@@ -15271,15 +15598,15 @@ function readSessionSummaries(dir, scope) {
|
|
|
15271
15598
|
});
|
|
15272
15599
|
}
|
|
15273
15600
|
function sessionJsonFiles(dir) {
|
|
15274
|
-
if (!
|
|
15275
|
-
return readdirSync10(dir).filter((file) => file.endsWith(".json")).sort().map((file) =>
|
|
15601
|
+
if (!existsSync27(dir)) return [];
|
|
15602
|
+
return readdirSync10(dir).filter((file) => file.endsWith(".json")).sort().map((file) => join32(dir, file));
|
|
15276
15603
|
}
|
|
15277
15604
|
function sessionDirsForListing(options) {
|
|
15278
15605
|
if (options.global) return [{ dir: globalSessionsDir(options.homeDir), scope: "global" }];
|
|
15279
15606
|
const dirs = [];
|
|
15280
15607
|
const cwd = defaultCwd(options.cwd);
|
|
15281
15608
|
const projectTania = findProjectTaniaDir(cwd);
|
|
15282
|
-
if (projectTania) dirs.push({ dir:
|
|
15609
|
+
if (projectTania) dirs.push({ dir: join32(projectTania, "sessions"), scope: "project" });
|
|
15283
15610
|
dirs.push({ dir: globalSessionsDir(options.homeDir), scope: "global" });
|
|
15284
15611
|
return dirs;
|
|
15285
15612
|
}
|
|
@@ -15569,10 +15896,10 @@ function parseDuration(raw) {
|
|
|
15569
15896
|
registerCommand(auditCommand);
|
|
15570
15897
|
|
|
15571
15898
|
// src/safety/permissions/config.ts
|
|
15572
|
-
import { existsSync as
|
|
15573
|
-
import { dirname as dirname16, join as
|
|
15899
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync15, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
15900
|
+
import { dirname as dirname16, join as join33 } from "path";
|
|
15574
15901
|
function writeProjectPermissionMode(cwd, mode) {
|
|
15575
|
-
const path =
|
|
15902
|
+
const path = join33(cwd, ".tania", "permissions.json");
|
|
15576
15903
|
const current = readProjectPermissions(path);
|
|
15577
15904
|
const next = {
|
|
15578
15905
|
...current,
|
|
@@ -15586,7 +15913,7 @@ function writeProjectPermissionMode(cwd, mode) {
|
|
|
15586
15913
|
return path;
|
|
15587
15914
|
}
|
|
15588
15915
|
function appendProjectSpendRule(cwd, rule) {
|
|
15589
|
-
const path =
|
|
15916
|
+
const path = join33(cwd, ".tania", "permissions.json");
|
|
15590
15917
|
const current = readProjectPermissions(path);
|
|
15591
15918
|
const next = {
|
|
15592
15919
|
...current,
|
|
@@ -15596,7 +15923,7 @@ function appendProjectSpendRule(cwd, rule) {
|
|
|
15596
15923
|
return path;
|
|
15597
15924
|
}
|
|
15598
15925
|
function readProjectPermissions(path) {
|
|
15599
|
-
if (!
|
|
15926
|
+
if (!existsSync28(path)) return { ...DEFAULT_PERMISSION_RULES };
|
|
15600
15927
|
const parsed = parsePermissionsJson(readFileSync15(path, "utf8"));
|
|
15601
15928
|
return parsed.ok ? parsed.value : { ...DEFAULT_PERMISSION_RULES };
|
|
15602
15929
|
}
|
|
@@ -15892,7 +16219,7 @@ registerCommand(helpCommand);
|
|
|
15892
16219
|
|
|
15893
16220
|
// src/commands/builtin/memory.ts
|
|
15894
16221
|
import { readdir as readdir7, readFile as readFile13 } from "fs/promises";
|
|
15895
|
-
import { join as
|
|
16222
|
+
import { join as join34 } from "path";
|
|
15896
16223
|
var DEFAULT_LIMIT2 = 10;
|
|
15897
16224
|
var memoryCommand = {
|
|
15898
16225
|
name: "memory",
|
|
@@ -15973,7 +16300,7 @@ function indent(text2) {
|
|
|
15973
16300
|
}
|
|
15974
16301
|
registerCommand(memoryCommand);
|
|
15975
16302
|
async function readChildRunSummary(workspace, runId) {
|
|
15976
|
-
const runsRoot =
|
|
16303
|
+
const runsRoot = join34(workspace, ".tania", "runs");
|
|
15977
16304
|
const path = await findRunSummaryPath(runsRoot, `${runId}.json`);
|
|
15978
16305
|
if (!path) return null;
|
|
15979
16306
|
try {
|
|
@@ -15995,7 +16322,7 @@ async function findRunSummaryPath(dir, filename) {
|
|
|
15995
16322
|
return null;
|
|
15996
16323
|
}
|
|
15997
16324
|
for (const entry of entries) {
|
|
15998
|
-
const path =
|
|
16325
|
+
const path = join34(dir, entry.name);
|
|
15999
16326
|
if (entry.isFile() && entry.name === filename) return path;
|
|
16000
16327
|
if (entry.isDirectory()) {
|
|
16001
16328
|
const nested = await findRunSummaryPath(path, filename);
|
|
@@ -16280,8 +16607,8 @@ registerCommand(resumeCommand);
|
|
|
16280
16607
|
registerCommand(saveCommand);
|
|
16281
16608
|
|
|
16282
16609
|
// src/commands/project.ts
|
|
16283
|
-
import { existsSync as
|
|
16284
|
-
import { basename as basename7, extname as extname2, join as
|
|
16610
|
+
import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
|
|
16611
|
+
import { basename as basename7, extname as extname2, join as join35, relative as relative13 } from "path";
|
|
16285
16612
|
import { pathToFileURL } from "url";
|
|
16286
16613
|
import { tsImport } from "tsx/esm/api";
|
|
16287
16614
|
var supportedExtensions = /* @__PURE__ */ new Set([".js", ".ts", ".sh"]);
|
|
@@ -16291,8 +16618,8 @@ async function loadProjectCommands(workspace) {
|
|
|
16291
16618
|
if (loadedWorkspace === workspace) return;
|
|
16292
16619
|
loadedWorkspace = workspace;
|
|
16293
16620
|
removeCommandsByCategory("project");
|
|
16294
|
-
const commandsDir =
|
|
16295
|
-
if (!
|
|
16621
|
+
const commandsDir = join35(workspace, ".tania", "commands");
|
|
16622
|
+
if (!existsSync29(commandsDir)) return;
|
|
16296
16623
|
let files = [];
|
|
16297
16624
|
try {
|
|
16298
16625
|
files = readdirSync11(commandsDir).filter((file) => supportedExtensions.has(extname2(file))).sort();
|
|
@@ -16301,7 +16628,7 @@ async function loadProjectCommands(workspace) {
|
|
|
16301
16628
|
return;
|
|
16302
16629
|
}
|
|
16303
16630
|
for (const file of files) {
|
|
16304
|
-
const path =
|
|
16631
|
+
const path = join35(commandsDir, file);
|
|
16305
16632
|
try {
|
|
16306
16633
|
const extension2 = extname2(file);
|
|
16307
16634
|
if (extension2 === ".sh") {
|
|
@@ -16501,15 +16828,15 @@ function splitCommandArgs(input) {
|
|
|
16501
16828
|
}
|
|
16502
16829
|
|
|
16503
16830
|
// src/safety/permissions/learning.ts
|
|
16504
|
-
import { existsSync as
|
|
16831
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as renameSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
16505
16832
|
import { homedir as homedir6 } from "os";
|
|
16506
|
-
import { dirname as dirname17, join as
|
|
16833
|
+
import { dirname as dirname17, join as join36 } from "path";
|
|
16507
16834
|
function permissionPatternForInput(tool, input) {
|
|
16508
16835
|
return `${tool}:${escapeRegex2(inputShape(input))}`;
|
|
16509
16836
|
}
|
|
16510
16837
|
function appendLearnedPermissionRule(options) {
|
|
16511
16838
|
const home = options.home ?? homedir6();
|
|
16512
|
-
const path =
|
|
16839
|
+
const path = join36(home, ".tanya", "permissions.json");
|
|
16513
16840
|
const pattern2 = permissionPatternForInput(options.tool, options.input);
|
|
16514
16841
|
const current = readUserPermissions(path);
|
|
16515
16842
|
const next = {
|
|
@@ -16525,7 +16852,7 @@ function appendLearnedPermissionRule(options) {
|
|
|
16525
16852
|
return pattern2;
|
|
16526
16853
|
}
|
|
16527
16854
|
function readUserPermissions(path) {
|
|
16528
|
-
if (!
|
|
16855
|
+
if (!existsSync30(path)) return { ...DEFAULT_PERMISSION_RULES };
|
|
16529
16856
|
const parsed = parsePermissionsJson(readFileSync16(path, "utf8"));
|
|
16530
16857
|
if (!parsed.ok) return { ...DEFAULT_PERMISSION_RULES };
|
|
16531
16858
|
return parsed.value;
|
|
@@ -16876,4 +17203,4 @@ export {
|
|
|
16876
17203
|
dispatchInteractiveCommand,
|
|
16877
17204
|
startInteractiveChat
|
|
16878
17205
|
};
|
|
16879
|
-
//# sourceMappingURL=chunk-
|
|
17206
|
+
//# sourceMappingURL=chunk-3NV2QP7J.js.map
|