zidane 5.4.3 → 5.5.2
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/README.md +30 -1
- package/dist/{agent-Yu8uhpy-.d.ts → agent-CvImMxMQ.d.ts} +183 -3
- package/dist/agent-CvImMxMQ.d.ts.map +1 -0
- package/dist/chat.d.ts +371 -15
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +203 -2
- package/dist/chat.js.map +1 -0
- package/dist/contexts/docker.d.ts +1 -1
- package/dist/contexts-BOtMvzli.js +472 -0
- package/dist/contexts-BOtMvzli.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/{errors-CDwtPIMX.js → errors-C5VSakmT.js} +1 -1
- package/dist/{errors-CDwtPIMX.js.map → errors-C5VSakmT.js.map} +1 -1
- package/dist/{index-DklfxeYy.d.ts → index-B0uc2C5x.d.ts} +3 -3
- package/dist/{index-DklfxeYy.d.ts.map → index-B0uc2C5x.d.ts.map} +1 -1
- package/dist/{index-BiO_5Hm4.d.ts → index-CbS75MD3.d.ts} +2 -2
- package/dist/index-CbS75MD3.d.ts.map +1 -0
- package/dist/{index-j9tY28ah.d.ts → index-CtXksgqb.d.ts} +60 -4
- package/dist/index-CtXksgqb.d.ts.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +13 -13
- package/dist/{interpolate-CmtjEyRJ.js → interpolate-Cvjy8gpk.js} +3 -3
- package/dist/{interpolate-CmtjEyRJ.js.map → interpolate-Cvjy8gpk.js.map} +1 -1
- package/dist/{login-DxyAERe1.js → login-DthdFNzR.js} +4 -4
- package/dist/{login-DxyAERe1.js.map → login-DthdFNzR.js.map} +1 -1
- package/dist/{mcp-CNUbvbsy.js → mcp-C8XUNC_R.js} +3 -3
- package/dist/{mcp-CNUbvbsy.js.map → mcp-C8XUNC_R.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-fTR19Ga6.js → messages-BBWakTN6.js} +2 -2
- package/dist/{messages-fTR19Ga6.js.map → messages-BBWakTN6.js.map} +1 -1
- package/dist/{presets-D9IbaI40.js → presets-C5E9hokO.js} +3 -2
- package/dist/presets-C5E9hokO.js.map +1 -0
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/{providers-CEzRFYtS.js → providers-CsUyN_FJ.js} +4 -4
- package/dist/{providers-CEzRFYtS.js.map → providers-CsUyN_FJ.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +2 -2
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +2 -1
- package/dist/session/sqlite.js.map +1 -1
- package/dist/{session-kwsNnOmt.js → session-DzfRacU_.js} +3 -2
- package/dist/session-DzfRacU_.js.map +1 -0
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -1
- package/dist/{stats-DgOvY7wd.js → stats-Lc3zL3RM.js} +1 -1
- package/dist/{stats-DgOvY7wd.js.map → stats-Lc3zL3RM.js.map} +1 -1
- package/dist/{tools-BK2vG9UX.js → tools-BavL6n7q.js} +673 -261
- package/dist/tools-BavL6n7q.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +2 -2
- package/dist/{transcript-anchors-DnaBcJej.d.ts → transcript-anchors-BMZRmrYk.d.ts} +182 -73
- package/dist/transcript-anchors-BMZRmrYk.d.ts.map +1 -0
- package/dist/tui.d.ts +27 -5
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +260 -47
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-OzKEOXul.js → turn-operations-DtMApNGT.js} +992 -85
- package/dist/turn-operations-DtMApNGT.js.map +1 -0
- package/dist/{types-IcokUOyC.js → types-C-9h2drI.js} +1 -1
- package/dist/{types-IcokUOyC.js.map → types-C-9h2drI.js.map} +1 -1
- package/dist/types-KukEp-mi.d.ts +253 -0
- package/dist/types-KukEp-mi.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/types.js +3 -3
- package/docs/ARCHITECTURE.md +21 -0
- package/docs/CHAT.md +3 -1
- package/docs/RUN_IN_BACKGROUND.md +612 -0
- package/docs/SKILL.md +59 -0
- package/docs/TUI.md +88 -3
- package/package.json +2 -2
- package/dist/agent-Yu8uhpy-.d.ts.map +0 -1
- package/dist/contexts-BwiHIr2w.js +0 -129
- package/dist/contexts-BwiHIr2w.js.map +0 -1
- package/dist/index-BiO_5Hm4.d.ts.map +0 -1
- package/dist/index-j9tY28ah.d.ts.map +0 -1
- package/dist/presets-D9IbaI40.js.map +0 -1
- package/dist/session-kwsNnOmt.js.map +0 -1
- package/dist/tools-BK2vG9UX.js.map +0 -1
- package/dist/transcript-anchors-DnaBcJej.d.ts.map +0 -1
- package/dist/turn-operations-OzKEOXul.js.map +0 -1
- package/dist/types-Ce78ds4h.d.ts +0 -88
- package/dist/types-Ce78ds4h.d.ts.map +0 -1
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import { a as multiEdit, c as grep, d as resolveOldString, f as styleReplacementForVia, i as readFile$1, l as glob, n as createSpawnTool, o as listFiles,
|
|
2
|
-
import { s as errorMessage } from "./errors-
|
|
3
|
-
import { n as toolResultToText } from "./types-
|
|
4
|
-
import { r as normalizeMcpServers } from "./mcp-
|
|
5
|
-
import { a as discoverSkills } from "./interpolate-
|
|
6
|
-
import { n as formatTokenUsage } from "./stats-
|
|
7
|
-
import { n as definePreset, t as composePresets } from "./presets-
|
|
8
|
-
import { a as writeFileAtomic, i as anthropic, n as openai, r as cerebras, t as openrouter } from "./providers-
|
|
1
|
+
import { H as previewLine, R as fmtTokens, U as shortId, a as multiEdit, c as grep, d as resolveOldString, f as styleReplacementForVia, i as readFile$1, l as glob, n as createSpawnTool, o as listFiles, t as writeFile$1, u as edit, y as shell } from "./tools-BavL6n7q.js";
|
|
2
|
+
import { s as errorMessage } from "./errors-C5VSakmT.js";
|
|
3
|
+
import { n as toolResultToText } from "./types-C-9h2drI.js";
|
|
4
|
+
import { r as normalizeMcpServers } from "./mcp-C8XUNC_R.js";
|
|
5
|
+
import { a as discoverSkills } from "./interpolate-Cvjy8gpk.js";
|
|
6
|
+
import { n as formatTokenUsage } from "./stats-Lc3zL3RM.js";
|
|
7
|
+
import { n as definePreset, t as composePresets } from "./presets-C5E9hokO.js";
|
|
8
|
+
import { a as writeFileAtomic, i as anthropic, n as openai, r as cerebras, t as openrouter } from "./providers-CsUyN_FJ.js";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
11
|
+
import { homedir, tmpdir } from "node:os";
|
|
9
12
|
import { spawn } from "node:child_process";
|
|
13
|
+
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
10
14
|
import { readdir, stat, writeFile } from "node:fs/promises";
|
|
11
|
-
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
12
15
|
import { getModel, getModels } from "@mariozechner/pi-ai";
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
14
|
-
import { homedir } from "node:os";
|
|
15
16
|
import { anthropicOAuthProvider, openaiCodexOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import { createGunzip } from "node:zlib";
|
|
16
19
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
17
20
|
import { jsx } from "react/jsx-runtime";
|
|
21
|
+
//#region \0rolldown/runtime.js
|
|
22
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
23
|
+
//#endregion
|
|
18
24
|
//#region src/chat/agent-prompt.ts
|
|
19
25
|
/**
|
|
20
26
|
* Agent system-prompt fragments — composable doctrine for built-in profiles.
|
|
@@ -1426,6 +1432,801 @@ function shouldAutoCompact(input) {
|
|
|
1426
1432
|
};
|
|
1427
1433
|
}
|
|
1428
1434
|
//#endregion
|
|
1435
|
+
//#region src/chat/auto-update.ts
|
|
1436
|
+
const OPT_OUT_VARS = [
|
|
1437
|
+
"ZIDANE_NO_UPDATE",
|
|
1438
|
+
"NO_UPDATE_NOTIFIER",
|
|
1439
|
+
"CI"
|
|
1440
|
+
];
|
|
1441
|
+
function isOptedOut(env = process.env) {
|
|
1442
|
+
for (const key of OPT_OUT_VARS) {
|
|
1443
|
+
const v = env[key];
|
|
1444
|
+
if (v !== void 0 && v !== "" && v !== "0" && v.toLowerCase() !== "false") return true;
|
|
1445
|
+
}
|
|
1446
|
+
return false;
|
|
1447
|
+
}
|
|
1448
|
+
function parseSemver(input) {
|
|
1449
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?(?:\+[\w.-]+)?$/.exec(input.trim());
|
|
1450
|
+
if (!m) return null;
|
|
1451
|
+
return {
|
|
1452
|
+
major: Number(m[1]),
|
|
1453
|
+
minor: Number(m[2]),
|
|
1454
|
+
patch: Number(m[3]),
|
|
1455
|
+
prerelease: m[4] ? m[4].split(".") : []
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
/** Returns `-1 / 0 / 1`. Unparseable inputs sort as `0` (assume equal). */
|
|
1459
|
+
function compareSemver(a, b) {
|
|
1460
|
+
const A = parseSemver(a);
|
|
1461
|
+
const B = parseSemver(b);
|
|
1462
|
+
if (!A || !B) return 0;
|
|
1463
|
+
for (const k of [
|
|
1464
|
+
"major",
|
|
1465
|
+
"minor",
|
|
1466
|
+
"patch"
|
|
1467
|
+
]) if (A[k] !== B[k]) return A[k] < B[k] ? -1 : 1;
|
|
1468
|
+
if (A.prerelease.length === 0 && B.prerelease.length > 0) return 1;
|
|
1469
|
+
if (A.prerelease.length > 0 && B.prerelease.length === 0) return -1;
|
|
1470
|
+
const len = Math.max(A.prerelease.length, B.prerelease.length);
|
|
1471
|
+
for (let i = 0; i < len; i++) {
|
|
1472
|
+
const ai = A.prerelease[i];
|
|
1473
|
+
const bi = B.prerelease[i];
|
|
1474
|
+
if (ai === void 0) return -1;
|
|
1475
|
+
if (bi === void 0) return 1;
|
|
1476
|
+
const an = /^\d+$/.test(ai) ? Number(ai) : null;
|
|
1477
|
+
const bn = /^\d+$/.test(bi) ? Number(bi) : null;
|
|
1478
|
+
if (an !== null && bn !== null) {
|
|
1479
|
+
if (an !== bn) return an < bn ? -1 : 1;
|
|
1480
|
+
} else if (an !== null) return -1;
|
|
1481
|
+
else if (bn !== null) return 1;
|
|
1482
|
+
else if (ai !== bi) return ai < bi ? -1 : 1;
|
|
1483
|
+
}
|
|
1484
|
+
return 0;
|
|
1485
|
+
}
|
|
1486
|
+
function cacheFilePath(cacheDir, packageName, channel) {
|
|
1487
|
+
return resolve(cacheDir, `update-check-${`${packageName.replaceAll("/", "__")}@${channel}`}.json`);
|
|
1488
|
+
}
|
|
1489
|
+
function readCache(path) {
|
|
1490
|
+
try {
|
|
1491
|
+
const raw = readFileSync(path, "utf8");
|
|
1492
|
+
const parsed = JSON.parse(raw);
|
|
1493
|
+
if (typeof parsed.latest !== "string" || typeof parsed.checkedAt !== "number") return null;
|
|
1494
|
+
return parsed;
|
|
1495
|
+
} catch {
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function writeCache(path, entry) {
|
|
1500
|
+
try {
|
|
1501
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1502
|
+
writeFileSync(path, `${JSON.stringify(entry, null, 2)}\n`);
|
|
1503
|
+
} catch {}
|
|
1504
|
+
}
|
|
1505
|
+
/** Strip a trailing slash so URL assembly is uniform. */
|
|
1506
|
+
function trimSlash(s) {
|
|
1507
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
1508
|
+
}
|
|
1509
|
+
async function fetchLatestRelease(opts) {
|
|
1510
|
+
const url = `${trimSlash(opts.registry)}/${encodeURIComponent(opts.packageName)}/${encodeURIComponent(opts.channel)}`;
|
|
1511
|
+
const headers = {
|
|
1512
|
+
"accept": "application/vnd.npm.install-v1+json, application/json",
|
|
1513
|
+
"user-agent": "zidane-update-check/1"
|
|
1514
|
+
};
|
|
1515
|
+
if (opts.etag) headers["if-none-match"] = opts.etag;
|
|
1516
|
+
const res = await opts.fetcher(url, {
|
|
1517
|
+
headers,
|
|
1518
|
+
signal: opts.signal,
|
|
1519
|
+
redirect: "follow"
|
|
1520
|
+
});
|
|
1521
|
+
if (res.status === 304) throw new NotModifiedError();
|
|
1522
|
+
if (!res.ok) throw new Error(`registry returned ${res.status}`);
|
|
1523
|
+
const body = await res.json();
|
|
1524
|
+
if (!body.version) throw new Error("registry payload missing `version`");
|
|
1525
|
+
return {
|
|
1526
|
+
latest: body.version,
|
|
1527
|
+
etag: res.headers.get("etag")
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
var NotModifiedError = class extends Error {
|
|
1531
|
+
constructor() {
|
|
1532
|
+
super("not-modified");
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
/**
|
|
1536
|
+
* Resolve the latest available version of `packageName`, honoring an
|
|
1537
|
+
* on-disk TTL cache so the warm path costs zero network. Never throws —
|
|
1538
|
+
* registry failures degrade to `{ source: 'error', latest: null }`.
|
|
1539
|
+
*
|
|
1540
|
+
* Designed for boot-path usage: short timeout, swallows errors, opt-outs
|
|
1541
|
+
* for CI / restricted shells. Consumers driving the explicit `upgrade`
|
|
1542
|
+
* subcommand should set `force: true` to ignore env opt-outs.
|
|
1543
|
+
*/
|
|
1544
|
+
async function checkForUpdate(options) {
|
|
1545
|
+
const channel = options.channel ?? "latest";
|
|
1546
|
+
const now = options.now ?? Date.now;
|
|
1547
|
+
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
1548
|
+
const ttl = options.cacheTtlMs ?? 1440 * 60 * 1e3;
|
|
1549
|
+
const timeoutMs = options.timeoutMs ?? 3e3;
|
|
1550
|
+
if (!options.force && isOptedOut()) return skipped(options.currentVersion, channel);
|
|
1551
|
+
const cachePath = options.cacheDir ? cacheFilePath(options.cacheDir, options.packageName, channel) : null;
|
|
1552
|
+
const cached = cachePath ? readCache(cachePath) : null;
|
|
1553
|
+
if (cached && cached.packageName === options.packageName && cached.channel === channel) {
|
|
1554
|
+
if (ttl > 0 && now() - cached.checkedAt < ttl) {
|
|
1555
|
+
const hasUpdate = compareSemver(cached.latest, options.currentVersion) > 0;
|
|
1556
|
+
return {
|
|
1557
|
+
current: options.currentVersion,
|
|
1558
|
+
latest: cached.latest,
|
|
1559
|
+
hasUpdate,
|
|
1560
|
+
source: "cached",
|
|
1561
|
+
checkedAt: cached.checkedAt,
|
|
1562
|
+
channel
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (typeof fetcher !== "function") {
|
|
1567
|
+
if (cached) return {
|
|
1568
|
+
current: options.currentVersion,
|
|
1569
|
+
latest: cached.latest,
|
|
1570
|
+
hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,
|
|
1571
|
+
source: "cached",
|
|
1572
|
+
checkedAt: cached.checkedAt,
|
|
1573
|
+
channel
|
|
1574
|
+
};
|
|
1575
|
+
return skipped(options.currentVersion, channel);
|
|
1576
|
+
}
|
|
1577
|
+
const controller = new AbortController();
|
|
1578
|
+
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("timeout")), timeoutMs);
|
|
1579
|
+
if (options.signal) if (options.signal.aborted) controller.abort(options.signal.reason);
|
|
1580
|
+
else options.signal.addEventListener("abort", () => controller.abort(options.signal.reason), { once: true });
|
|
1581
|
+
try {
|
|
1582
|
+
const release = await fetchLatestRelease({
|
|
1583
|
+
registry: options.registry ?? "https://registry.npmjs.org",
|
|
1584
|
+
packageName: options.packageName,
|
|
1585
|
+
channel,
|
|
1586
|
+
etag: cached?.etag,
|
|
1587
|
+
fetcher,
|
|
1588
|
+
signal: controller.signal
|
|
1589
|
+
});
|
|
1590
|
+
const checkedAt = now();
|
|
1591
|
+
if (cachePath) writeCache(cachePath, {
|
|
1592
|
+
packageName: options.packageName,
|
|
1593
|
+
channel,
|
|
1594
|
+
latest: release.latest,
|
|
1595
|
+
checkedAt,
|
|
1596
|
+
...release.etag ? { etag: release.etag } : {}
|
|
1597
|
+
});
|
|
1598
|
+
return {
|
|
1599
|
+
current: options.currentVersion,
|
|
1600
|
+
latest: release.latest,
|
|
1601
|
+
hasUpdate: compareSemver(release.latest, options.currentVersion) > 0,
|
|
1602
|
+
source: "fresh",
|
|
1603
|
+
checkedAt,
|
|
1604
|
+
channel
|
|
1605
|
+
};
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
if (err instanceof NotModifiedError && cached) {
|
|
1608
|
+
const checkedAt = now();
|
|
1609
|
+
if (cachePath) writeCache(cachePath, {
|
|
1610
|
+
...cached,
|
|
1611
|
+
checkedAt
|
|
1612
|
+
});
|
|
1613
|
+
return {
|
|
1614
|
+
current: options.currentVersion,
|
|
1615
|
+
latest: cached.latest,
|
|
1616
|
+
hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,
|
|
1617
|
+
source: "cached",
|
|
1618
|
+
checkedAt,
|
|
1619
|
+
channel
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
if (cached) return {
|
|
1623
|
+
current: options.currentVersion,
|
|
1624
|
+
latest: cached.latest,
|
|
1625
|
+
hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,
|
|
1626
|
+
source: "cached",
|
|
1627
|
+
checkedAt: cached.checkedAt,
|
|
1628
|
+
channel
|
|
1629
|
+
};
|
|
1630
|
+
return {
|
|
1631
|
+
current: options.currentVersion,
|
|
1632
|
+
latest: null,
|
|
1633
|
+
hasUpdate: false,
|
|
1634
|
+
source: "error",
|
|
1635
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1636
|
+
checkedAt: null,
|
|
1637
|
+
channel
|
|
1638
|
+
};
|
|
1639
|
+
} finally {
|
|
1640
|
+
clearTimeout(timer);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function skipped(current, channel) {
|
|
1644
|
+
return {
|
|
1645
|
+
current,
|
|
1646
|
+
latest: null,
|
|
1647
|
+
hasUpdate: false,
|
|
1648
|
+
source: "skipped",
|
|
1649
|
+
checkedAt: null,
|
|
1650
|
+
channel
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
function normalizePath(path) {
|
|
1654
|
+
try {
|
|
1655
|
+
return realpathSync(path);
|
|
1656
|
+
} catch {
|
|
1657
|
+
return path;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
function detectPackageManager(opts = {}) {
|
|
1661
|
+
const env = opts.env ?? process.env;
|
|
1662
|
+
const home = opts.home ?? homedir();
|
|
1663
|
+
opts.platform ?? process.platform;
|
|
1664
|
+
const channel = opts.channel ?? "latest";
|
|
1665
|
+
const packageName = opts.packageName ?? "zidane-tui";
|
|
1666
|
+
const binaryPath = opts.binaryPath ?? process.execPath;
|
|
1667
|
+
const resolvedBinary = normalizePath(binaryPath);
|
|
1668
|
+
const userAgent = opts.userAgent ?? env.npm_config_user_agent;
|
|
1669
|
+
const target = `${packageName}@${channel}`;
|
|
1670
|
+
const containsSegment = (path, segment) => {
|
|
1671
|
+
const tail = `${segment}${sep}`;
|
|
1672
|
+
return path.includes(tail) || path.endsWith(segment);
|
|
1673
|
+
};
|
|
1674
|
+
const inPath = (segment) => containsSegment(resolvedBinary, segment) || containsSegment(binaryPath, segment);
|
|
1675
|
+
const homeSeg = (rest) => `${home}${sep}${rest}`;
|
|
1676
|
+
if (inPath(homeSeg(".volta"))) return {
|
|
1677
|
+
id: "volta",
|
|
1678
|
+
argv: [
|
|
1679
|
+
"volta",
|
|
1680
|
+
"install",
|
|
1681
|
+
target
|
|
1682
|
+
],
|
|
1683
|
+
note: "Volta-managed install detected — refreshing via `volta install`."
|
|
1684
|
+
};
|
|
1685
|
+
if (inPath(homeSeg(".bun"))) return {
|
|
1686
|
+
id: "bun",
|
|
1687
|
+
argv: [
|
|
1688
|
+
"bun",
|
|
1689
|
+
"add",
|
|
1690
|
+
"--global",
|
|
1691
|
+
target
|
|
1692
|
+
],
|
|
1693
|
+
note: "Bun global install detected."
|
|
1694
|
+
};
|
|
1695
|
+
if (inPath(homeSeg(".local/share/pnpm")) || inPath(homeSeg("Library/pnpm")) || resolvedBinary.includes(`${sep}pnpm${sep}`)) return {
|
|
1696
|
+
id: "pnpm",
|
|
1697
|
+
argv: [
|
|
1698
|
+
"pnpm",
|
|
1699
|
+
"add",
|
|
1700
|
+
"--global",
|
|
1701
|
+
target
|
|
1702
|
+
],
|
|
1703
|
+
note: "pnpm global install detected."
|
|
1704
|
+
};
|
|
1705
|
+
if (inPath(homeSeg(".yarn"))) return {
|
|
1706
|
+
id: "yarn",
|
|
1707
|
+
argv: [
|
|
1708
|
+
"yarn",
|
|
1709
|
+
"global",
|
|
1710
|
+
"add",
|
|
1711
|
+
target
|
|
1712
|
+
],
|
|
1713
|
+
note: "Yarn classic global install detected."
|
|
1714
|
+
};
|
|
1715
|
+
if (userAgent) {
|
|
1716
|
+
const ua = userAgent.toLowerCase();
|
|
1717
|
+
if (ua.startsWith("pnpm/")) return {
|
|
1718
|
+
id: "pnpm",
|
|
1719
|
+
argv: [
|
|
1720
|
+
"pnpm",
|
|
1721
|
+
"add",
|
|
1722
|
+
"--global",
|
|
1723
|
+
target
|
|
1724
|
+
]
|
|
1725
|
+
};
|
|
1726
|
+
if (ua.startsWith("yarn/")) return {
|
|
1727
|
+
id: "yarn",
|
|
1728
|
+
argv: [
|
|
1729
|
+
"yarn",
|
|
1730
|
+
"global",
|
|
1731
|
+
"add",
|
|
1732
|
+
target
|
|
1733
|
+
]
|
|
1734
|
+
};
|
|
1735
|
+
if (ua.startsWith("bun/")) return {
|
|
1736
|
+
id: "bun",
|
|
1737
|
+
argv: [
|
|
1738
|
+
"bun",
|
|
1739
|
+
"add",
|
|
1740
|
+
"--global",
|
|
1741
|
+
target
|
|
1742
|
+
]
|
|
1743
|
+
};
|
|
1744
|
+
if (ua.startsWith("npm/")) return {
|
|
1745
|
+
id: "npm",
|
|
1746
|
+
argv: [
|
|
1747
|
+
"npm",
|
|
1748
|
+
"install",
|
|
1749
|
+
"--global",
|
|
1750
|
+
target
|
|
1751
|
+
]
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
if (env.VOLTA_HOME) return {
|
|
1755
|
+
id: "volta",
|
|
1756
|
+
argv: [
|
|
1757
|
+
"volta",
|
|
1758
|
+
"install",
|
|
1759
|
+
target
|
|
1760
|
+
],
|
|
1761
|
+
note: "$VOLTA_HOME set — using `volta install`. If the install isn't Volta-managed, pass `--package-manager npm`."
|
|
1762
|
+
};
|
|
1763
|
+
if (env.BUN_INSTALL) return {
|
|
1764
|
+
id: "bun",
|
|
1765
|
+
argv: [
|
|
1766
|
+
"bun",
|
|
1767
|
+
"add",
|
|
1768
|
+
"--global",
|
|
1769
|
+
target
|
|
1770
|
+
]
|
|
1771
|
+
};
|
|
1772
|
+
if (env.PNPM_HOME) return {
|
|
1773
|
+
id: "pnpm",
|
|
1774
|
+
argv: [
|
|
1775
|
+
"pnpm",
|
|
1776
|
+
"add",
|
|
1777
|
+
"--global",
|
|
1778
|
+
target
|
|
1779
|
+
]
|
|
1780
|
+
};
|
|
1781
|
+
return {
|
|
1782
|
+
id: "npm",
|
|
1783
|
+
argv: [
|
|
1784
|
+
"npm",
|
|
1785
|
+
"install",
|
|
1786
|
+
"--global",
|
|
1787
|
+
target
|
|
1788
|
+
]
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Re-install the package globally via the host's package manager. Resolves
|
|
1793
|
+
* with the spawned command's exit code, or rejects only when the caller's
|
|
1794
|
+
* `signal` aborts. PM failures surface as a non-zero `exitCode` — the
|
|
1795
|
+
* caller decides whether to retry or fall back to in-place.
|
|
1796
|
+
*/
|
|
1797
|
+
async function performSelfUpdate(options) {
|
|
1798
|
+
const detect = options.detect ?? detectPackageManager;
|
|
1799
|
+
const command = options.packageManager && options.packageManager !== "auto" ? overrideManager(options.packageManager, options.packageName, options.channel ?? "latest") : detect({
|
|
1800
|
+
packageName: options.packageName,
|
|
1801
|
+
channel: options.channel ?? "latest"
|
|
1802
|
+
});
|
|
1803
|
+
if (options.dryRun) return {
|
|
1804
|
+
command,
|
|
1805
|
+
exitCode: null
|
|
1806
|
+
};
|
|
1807
|
+
const useSpawn = options.spawn ?? spawn;
|
|
1808
|
+
return await new Promise((resolveP) => {
|
|
1809
|
+
const [bin, ...args] = command.argv;
|
|
1810
|
+
const child = useSpawn(bin, args, {
|
|
1811
|
+
stdio: options.stdout || options.stderr ? [
|
|
1812
|
+
"ignore",
|
|
1813
|
+
"pipe",
|
|
1814
|
+
"pipe"
|
|
1815
|
+
] : "inherit",
|
|
1816
|
+
env: {
|
|
1817
|
+
...process.env,
|
|
1818
|
+
...options.env
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
if (options.stdout && child.stdout) child.stdout.pipe(options.stdout);
|
|
1822
|
+
if (options.stderr && child.stderr) child.stderr.pipe(options.stderr);
|
|
1823
|
+
child.on("error", (err) => {
|
|
1824
|
+
resolveP({
|
|
1825
|
+
command,
|
|
1826
|
+
exitCode: null,
|
|
1827
|
+
spawnError: err.message
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
child.on("exit", (code) => {
|
|
1831
|
+
resolveP({
|
|
1832
|
+
command,
|
|
1833
|
+
exitCode: code
|
|
1834
|
+
});
|
|
1835
|
+
});
|
|
1836
|
+
if (options.signal) {
|
|
1837
|
+
const onAbort = () => child.kill();
|
|
1838
|
+
if (options.signal.aborted) onAbort();
|
|
1839
|
+
else options.signal.addEventListener("abort", onAbort, { once: true });
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
function overrideManager(id, packageName, channel) {
|
|
1844
|
+
const target = `${packageName}@${channel}`;
|
|
1845
|
+
switch (id) {
|
|
1846
|
+
case "pnpm": return {
|
|
1847
|
+
id,
|
|
1848
|
+
argv: [
|
|
1849
|
+
"pnpm",
|
|
1850
|
+
"add",
|
|
1851
|
+
"--global",
|
|
1852
|
+
target
|
|
1853
|
+
]
|
|
1854
|
+
};
|
|
1855
|
+
case "yarn": return {
|
|
1856
|
+
id,
|
|
1857
|
+
argv: [
|
|
1858
|
+
"yarn",
|
|
1859
|
+
"global",
|
|
1860
|
+
"add",
|
|
1861
|
+
target
|
|
1862
|
+
]
|
|
1863
|
+
};
|
|
1864
|
+
case "bun": return {
|
|
1865
|
+
id,
|
|
1866
|
+
argv: [
|
|
1867
|
+
"bun",
|
|
1868
|
+
"add",
|
|
1869
|
+
"--global",
|
|
1870
|
+
target
|
|
1871
|
+
]
|
|
1872
|
+
};
|
|
1873
|
+
case "volta": return {
|
|
1874
|
+
id,
|
|
1875
|
+
argv: [
|
|
1876
|
+
"volta",
|
|
1877
|
+
"install",
|
|
1878
|
+
target
|
|
1879
|
+
]
|
|
1880
|
+
};
|
|
1881
|
+
default: return {
|
|
1882
|
+
id: "npm",
|
|
1883
|
+
argv: [
|
|
1884
|
+
"npm",
|
|
1885
|
+
"install",
|
|
1886
|
+
"--global",
|
|
1887
|
+
target
|
|
1888
|
+
]
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Replace the running binary with the latest tarball-shipped one. Works on
|
|
1894
|
+
* macOS + Linux (POSIX unlink-while-running); refuses on Windows. Returns
|
|
1895
|
+
* a result object instead of throwing — callers usually want to print
|
|
1896
|
+
* `result.reason` and fall back to a PM-driven update.
|
|
1897
|
+
*/
|
|
1898
|
+
async function performInPlaceSelfUpdate(options) {
|
|
1899
|
+
if (process.platform === "win32") return {
|
|
1900
|
+
status: "refused",
|
|
1901
|
+
reason: "in-place update not supported on Windows; use `zidane upgrade` instead."
|
|
1902
|
+
};
|
|
1903
|
+
const binaryPath = options.binaryPath ?? process.execPath;
|
|
1904
|
+
const resolved = normalizePath(binaryPath);
|
|
1905
|
+
if (options.refuseUnderPrefixes?.some((p) => resolved.startsWith(p))) return {
|
|
1906
|
+
status: "refused",
|
|
1907
|
+
reason: `binary at ${resolved} sits under a managed prefix; use \`zidane upgrade\` to update via your package manager.`
|
|
1908
|
+
};
|
|
1909
|
+
if (resolved.includes(`${homedir()}${sep}.volta${sep}`)) return {
|
|
1910
|
+
status: "refused",
|
|
1911
|
+
reason: "Volta-managed install detected. Use `zidane upgrade` (runs `volta install`) instead."
|
|
1912
|
+
};
|
|
1913
|
+
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
1914
|
+
if (typeof fetcher !== "function") return {
|
|
1915
|
+
status: "failed",
|
|
1916
|
+
reason: "global fetch unavailable; rebuild with Node ≥ 18 or Bun."
|
|
1917
|
+
};
|
|
1918
|
+
const channel = options.channel ?? "latest";
|
|
1919
|
+
const registry = options.registry ?? "https://registry.npmjs.org";
|
|
1920
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
1921
|
+
const controller = new AbortController();
|
|
1922
|
+
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("timeout")), timeoutMs);
|
|
1923
|
+
if (options.signal) if (options.signal.aborted) controller.abort(options.signal.reason);
|
|
1924
|
+
else options.signal.addEventListener("abort", () => controller.abort(options.signal.reason), { once: true });
|
|
1925
|
+
try {
|
|
1926
|
+
const metaRes = await fetcher(`${trimSlash(registry)}/${encodeURIComponent(options.packageName)}/${encodeURIComponent(channel)}`, {
|
|
1927
|
+
headers: {
|
|
1928
|
+
"accept": "application/json",
|
|
1929
|
+
"user-agent": "zidane-update-check/1"
|
|
1930
|
+
},
|
|
1931
|
+
signal: controller.signal
|
|
1932
|
+
});
|
|
1933
|
+
if (!metaRes.ok) return {
|
|
1934
|
+
status: "failed",
|
|
1935
|
+
reason: `registry returned ${metaRes.status} for ${options.packageName}@${channel}`
|
|
1936
|
+
};
|
|
1937
|
+
const meta = await metaRes.json();
|
|
1938
|
+
if (!meta.version || !meta.dist?.tarball) return {
|
|
1939
|
+
status: "failed",
|
|
1940
|
+
reason: `registry payload missing version or tarball for ${options.packageName}@${channel}`
|
|
1941
|
+
};
|
|
1942
|
+
if (!meta.dist.integrity && !meta.dist.shasum) return {
|
|
1943
|
+
status: "failed",
|
|
1944
|
+
reason: `registry payload missing integrity / shasum for ${options.packageName}@${channel}; refusing to overwrite the binary.`
|
|
1945
|
+
};
|
|
1946
|
+
const targetDir = dirname(binaryPath);
|
|
1947
|
+
let stagingDir;
|
|
1948
|
+
try {
|
|
1949
|
+
stagingDir = mkdtempSync(join(targetDir, ".zidane-update-"));
|
|
1950
|
+
} catch {
|
|
1951
|
+
stagingDir = mkdtempSync(join(tmpdir(), "zidane-update-"));
|
|
1952
|
+
}
|
|
1953
|
+
const tarballPath = join(stagingDir, "package.tgz");
|
|
1954
|
+
try {
|
|
1955
|
+
const tgzRes = await fetcher(meta.dist.tarball, {
|
|
1956
|
+
headers: { "user-agent": "zidane-update-check/1" },
|
|
1957
|
+
signal: controller.signal
|
|
1958
|
+
});
|
|
1959
|
+
if (!tgzRes.ok || !tgzRes.body) return {
|
|
1960
|
+
status: "failed",
|
|
1961
|
+
reason: `tarball download failed: ${tgzRes.status}`
|
|
1962
|
+
};
|
|
1963
|
+
const digests = await streamToFile(tgzRes.body, tarballPath);
|
|
1964
|
+
const integrityCheck = verifyIntegrity(meta.dist, digests);
|
|
1965
|
+
if (!integrityCheck.ok) return {
|
|
1966
|
+
status: "failed",
|
|
1967
|
+
reason: `integrity check failed: ${integrityCheck.reason}`
|
|
1968
|
+
};
|
|
1969
|
+
const tarballBinaryPath = options.tarballBinaryPath ?? `bin/${binaryNameFrom(binaryPath)}`;
|
|
1970
|
+
const extractedPath = await extractEntryFromTarball(tarballPath, tarballBinaryPath, stagingDir);
|
|
1971
|
+
if (!extractedPath) return {
|
|
1972
|
+
status: "failed",
|
|
1973
|
+
reason: `tarball did not contain ${tarballBinaryPath}`
|
|
1974
|
+
};
|
|
1975
|
+
if (options.dryRun) return {
|
|
1976
|
+
status: "success",
|
|
1977
|
+
installedVersion: meta.version,
|
|
1978
|
+
binaryPath: extractedPath
|
|
1979
|
+
};
|
|
1980
|
+
chmodSync(extractedPath, 493);
|
|
1981
|
+
const targetForSwap = binaryPath;
|
|
1982
|
+
try {
|
|
1983
|
+
renameSync(extractedPath, targetForSwap);
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
return {
|
|
1986
|
+
status: "failed",
|
|
1987
|
+
reason: `failed to write ${targetForSwap}: ${err instanceof Error ? err.message : String(err)}`
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
return {
|
|
1991
|
+
status: "success",
|
|
1992
|
+
installedVersion: meta.version,
|
|
1993
|
+
binaryPath: targetForSwap
|
|
1994
|
+
};
|
|
1995
|
+
} finally {
|
|
1996
|
+
try {
|
|
1997
|
+
rmSync(stagingDir, {
|
|
1998
|
+
recursive: true,
|
|
1999
|
+
force: true
|
|
2000
|
+
});
|
|
2001
|
+
} catch {}
|
|
2002
|
+
}
|
|
2003
|
+
} catch (err) {
|
|
2004
|
+
return {
|
|
2005
|
+
status: "failed",
|
|
2006
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
2007
|
+
};
|
|
2008
|
+
} finally {
|
|
2009
|
+
clearTimeout(timer);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
function binaryNameFrom(path) {
|
|
2013
|
+
const last = path.split(/[\\/]/).at(-1) ?? "";
|
|
2014
|
+
return last.endsWith(".exe") ? last.slice(0, -4) : last;
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Stream `body` to `path` while computing integrity digests on the fly —
|
|
2018
|
+
* we never load the whole tarball into a Buffer just to hash it. Returns
|
|
2019
|
+
* SHA-1 (hex, for the legacy `dist.shasum`) and SHA-512 (base64, for SRI's
|
|
2020
|
+
* `sha512-<base64>`).
|
|
2021
|
+
*/
|
|
2022
|
+
async function streamToFile(body, path) {
|
|
2023
|
+
const file = createWriteStream(path);
|
|
2024
|
+
const sha1 = createHash("sha1");
|
|
2025
|
+
const sha512 = createHash("sha512");
|
|
2026
|
+
let size = 0;
|
|
2027
|
+
const reader = body.getReader();
|
|
2028
|
+
try {
|
|
2029
|
+
while (true) {
|
|
2030
|
+
const { done, value } = await reader.read();
|
|
2031
|
+
if (done) break;
|
|
2032
|
+
if (value && value.byteLength > 0) {
|
|
2033
|
+
sha1.update(value);
|
|
2034
|
+
sha512.update(value);
|
|
2035
|
+
size += value.byteLength;
|
|
2036
|
+
await new Promise((res, rej) => file.write(value, (err) => err ? rej(err) : res()));
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
} finally {
|
|
2040
|
+
await new Promise((res) => file.end(() => res()));
|
|
2041
|
+
}
|
|
2042
|
+
return {
|
|
2043
|
+
sha1: sha1.digest("hex"),
|
|
2044
|
+
sha512Base64: sha512.digest("base64"),
|
|
2045
|
+
size
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Validate the downloaded tarball's digest against the registry's
|
|
2050
|
+
* `dist.integrity` (SRI: `sha512-<base64>` / `sha384-…` / `sha256-…`) or
|
|
2051
|
+
* the legacy `dist.shasum` (hex SHA-1). Either must match — we don't
|
|
2052
|
+
* accept "neither provided" because that's what `performInPlaceSelfUpdate`
|
|
2053
|
+
* already short-circuited above.
|
|
2054
|
+
*/
|
|
2055
|
+
function verifyIntegrity(dist, digests) {
|
|
2056
|
+
if (dist.integrity) {
|
|
2057
|
+
const entries = dist.integrity.split(/\s+/).filter(Boolean);
|
|
2058
|
+
for (const entry of entries) {
|
|
2059
|
+
const m = /^(sha(?:256|384|512))-(.+)$/.exec(entry);
|
|
2060
|
+
if (!m) continue;
|
|
2061
|
+
const algo = m[1];
|
|
2062
|
+
const expected = m[2];
|
|
2063
|
+
if (algo === "sha512" && digests.sha512Base64 === expected) return { ok: true };
|
|
2064
|
+
if (algo !== "sha512") return {
|
|
2065
|
+
ok: false,
|
|
2066
|
+
reason: `unsupported SRI algorithm \`${algo}\`. Re-run via your package manager.`
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
return {
|
|
2070
|
+
ok: false,
|
|
2071
|
+
reason: "SRI mismatch: tarball does not match the registry's `dist.integrity`."
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
if (dist.shasum) {
|
|
2075
|
+
if (digests.sha1 === dist.shasum.toLowerCase()) return { ok: true };
|
|
2076
|
+
return {
|
|
2077
|
+
ok: false,
|
|
2078
|
+
reason: "SHA-1 mismatch: tarball does not match the registry's `dist.shasum`."
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
return {
|
|
2082
|
+
ok: false,
|
|
2083
|
+
reason: "no integrity field shipped by the registry."
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
async function extractEntryFromTarball(tarballPath, entryRelPath, outDir) {
|
|
2087
|
+
const matchSuffix = `/${entryRelPath}`;
|
|
2088
|
+
const out = join(outDir, "binary.bin");
|
|
2089
|
+
const chunks = [];
|
|
2090
|
+
const gunzip = createGunzip();
|
|
2091
|
+
const input = createReadStream(tarballPath);
|
|
2092
|
+
await new Promise((res, rej) => {
|
|
2093
|
+
gunzip.on("data", (c) => chunks.push(c));
|
|
2094
|
+
gunzip.on("end", () => res());
|
|
2095
|
+
gunzip.on("error", rej);
|
|
2096
|
+
input.on("error", rej);
|
|
2097
|
+
input.pipe(gunzip);
|
|
2098
|
+
});
|
|
2099
|
+
const tar = concatChunks(chunks);
|
|
2100
|
+
let offset = 0;
|
|
2101
|
+
while (offset + 512 <= tar.length) {
|
|
2102
|
+
const header = tar.subarray(offset, offset + 512);
|
|
2103
|
+
if (header.every((b) => b === 0)) break;
|
|
2104
|
+
const name = readNulString(header, 0, 100);
|
|
2105
|
+
const sizeOct = readNulString(header, 124, 12).trim();
|
|
2106
|
+
const size = sizeOct === "" ? 0 : Number.parseInt(sizeOct, 8);
|
|
2107
|
+
const dataStart = offset + 512;
|
|
2108
|
+
const dataEnd = dataStart + size;
|
|
2109
|
+
if (name && (name === entryRelPath || name.endsWith(matchSuffix))) {
|
|
2110
|
+
writeFileSync(out, tar.subarray(dataStart, dataEnd));
|
|
2111
|
+
return out;
|
|
2112
|
+
}
|
|
2113
|
+
offset = dataEnd + (512 - size % 512) % 512;
|
|
2114
|
+
}
|
|
2115
|
+
return null;
|
|
2116
|
+
}
|
|
2117
|
+
function readNulString(buf, start, len) {
|
|
2118
|
+
const end = start + len;
|
|
2119
|
+
let stop = end;
|
|
2120
|
+
for (let i = start; i < end; i++) if (buf[i] === 0) {
|
|
2121
|
+
stop = i;
|
|
2122
|
+
break;
|
|
2123
|
+
}
|
|
2124
|
+
return new TextDecoder("utf8").decode(buf.subarray(start, stop));
|
|
2125
|
+
}
|
|
2126
|
+
function concatChunks(chunks) {
|
|
2127
|
+
let total = 0;
|
|
2128
|
+
for (const c of chunks) total += c.byteLength;
|
|
2129
|
+
const out = new Uint8Array(total);
|
|
2130
|
+
let offset = 0;
|
|
2131
|
+
for (const c of chunks) {
|
|
2132
|
+
out.set(c, offset);
|
|
2133
|
+
offset += c.byteLength;
|
|
2134
|
+
}
|
|
2135
|
+
return out;
|
|
2136
|
+
}
|
|
2137
|
+
function resolvePlatformPackage(opts = {}) {
|
|
2138
|
+
const platform = opts.platform ?? process.platform;
|
|
2139
|
+
const arch = opts.arch ?? process.arch;
|
|
2140
|
+
const libc = opts.libc ?? detectLibc();
|
|
2141
|
+
const prefix = opts.prefix ?? "zidane-tui-";
|
|
2142
|
+
if (platform === "darwin") {
|
|
2143
|
+
if (arch === "arm64") return `${prefix}darwin-arm64`;
|
|
2144
|
+
if (arch === "x64") return `${prefix}darwin-x64`;
|
|
2145
|
+
return null;
|
|
2146
|
+
}
|
|
2147
|
+
if (platform === "linux") {
|
|
2148
|
+
if (libc !== "glibc") return null;
|
|
2149
|
+
if (arch === "arm64") return `${prefix}linux-arm64`;
|
|
2150
|
+
if (arch === "x64") return `${prefix}linux-x64`;
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
2153
|
+
return null;
|
|
2154
|
+
}
|
|
2155
|
+
/** Mirrors `tui/bin/zidane.mjs#detectLibc`. */
|
|
2156
|
+
function detectLibc() {
|
|
2157
|
+
if (process.platform !== "linux") return "glibc";
|
|
2158
|
+
try {
|
|
2159
|
+
const { execSync } = __require("node:child_process");
|
|
2160
|
+
return execSync("ldd --version 2>&1", {
|
|
2161
|
+
encoding: "utf8",
|
|
2162
|
+
stdio: [
|
|
2163
|
+
"ignore",
|
|
2164
|
+
"pipe",
|
|
2165
|
+
"pipe"
|
|
2166
|
+
]
|
|
2167
|
+
}).toLowerCase().includes("musl") ? "musl" : "glibc";
|
|
2168
|
+
} catch {
|
|
2169
|
+
return "musl";
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
//#endregion
|
|
2173
|
+
//#region src/chat/auto-update-hook.ts
|
|
2174
|
+
/**
|
|
2175
|
+
* Returns the current update status, refreshed once per mount (subject to
|
|
2176
|
+
* the on-disk TTL). `null` while the first check is in flight; the
|
|
2177
|
+
* `source: 'cached' | 'fresh'` distinction lets the caller decide whether
|
|
2178
|
+
* to show a discreet chip vs a more prominent banner.
|
|
2179
|
+
*
|
|
2180
|
+
* The hook owns its own AbortController so an unmount cancels any in-flight
|
|
2181
|
+
* registry request cleanly.
|
|
2182
|
+
*/
|
|
2183
|
+
function useUpdateCheck(options) {
|
|
2184
|
+
const [status, setStatus] = useState(null);
|
|
2185
|
+
const optsRef = useRef(options);
|
|
2186
|
+
optsRef.current = options;
|
|
2187
|
+
useEffect(() => {
|
|
2188
|
+
if (!options.enabled || !options.currentVersion) return;
|
|
2189
|
+
const controller = new AbortController();
|
|
2190
|
+
const timer = setTimeout(() => {
|
|
2191
|
+
checkForUpdate({
|
|
2192
|
+
packageName: optsRef.current.packageName,
|
|
2193
|
+
currentVersion: optsRef.current.currentVersion,
|
|
2194
|
+
cacheDir: optsRef.current.cacheDir,
|
|
2195
|
+
registry: optsRef.current.registry,
|
|
2196
|
+
channel: optsRef.current.channel,
|
|
2197
|
+
cacheTtlMs: optsRef.current.cacheTtlMs,
|
|
2198
|
+
signal: controller.signal,
|
|
2199
|
+
now: optsRef.current.now,
|
|
2200
|
+
fetcher: optsRef.current.fetcher
|
|
2201
|
+
}).then((s) => {
|
|
2202
|
+
if (!controller.signal.aborted) setStatus(s);
|
|
2203
|
+
});
|
|
2204
|
+
}, options.delayMs ?? 1500);
|
|
2205
|
+
return () => {
|
|
2206
|
+
clearTimeout(timer);
|
|
2207
|
+
controller.abort();
|
|
2208
|
+
};
|
|
2209
|
+
}, [
|
|
2210
|
+
options.enabled,
|
|
2211
|
+
options.packageName,
|
|
2212
|
+
options.currentVersion
|
|
2213
|
+
]);
|
|
2214
|
+
return status;
|
|
2215
|
+
}
|
|
2216
|
+
function buildUpdateHint(status, options) {
|
|
2217
|
+
if (!status?.hasUpdate || !status.latest) return null;
|
|
2218
|
+
const label = options.showVersion === false ? "update" : `v${stripVPrefix(status.latest)}`;
|
|
2219
|
+
return {
|
|
2220
|
+
key: options.glyph ?? "↑",
|
|
2221
|
+
keyColor: options.color,
|
|
2222
|
+
label,
|
|
2223
|
+
labelColor: options.color
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
function stripVPrefix(v) {
|
|
2227
|
+
return v.startsWith("v") ? v.slice(1) : v;
|
|
2228
|
+
}
|
|
2229
|
+
//#endregion
|
|
1429
2230
|
//#region src/chat/boot-profiler.ts
|
|
1430
2231
|
const enabled = !!process.env.ZIDANE_BOOT_PROFILE;
|
|
1431
2232
|
/**
|
|
@@ -1889,7 +2690,7 @@ function scoreFiles(catalog, query, limit, formatPath) {
|
|
|
1889
2690
|
return scored.slice(0, limit).map(({ entry, display }) => ({
|
|
1890
2691
|
id: display,
|
|
1891
2692
|
label: entry.name,
|
|
1892
|
-
description: parentDir(
|
|
2693
|
+
description: parentDir(entry.path),
|
|
1893
2694
|
insertText: `@${display} `,
|
|
1894
2695
|
data: entry
|
|
1895
2696
|
}));
|
|
@@ -3321,9 +4122,45 @@ function deriveSessionTitle(turns, metadata) {
|
|
|
3321
4122
|
*/
|
|
3322
4123
|
function eventsFromTurns(turns, runs = []) {
|
|
3323
4124
|
const runById = /* @__PURE__ */ new Map();
|
|
3324
|
-
for (const run of runs) runById.set(run.id, run);
|
|
4125
|
+
for (const run of runs) if (!runById.has(run.id)) runById.set(run.id, run);
|
|
4126
|
+
const runStack = [];
|
|
4127
|
+
const pendingToolCallsPerRun = /* @__PURE__ */ new Map();
|
|
4128
|
+
for (const turn of turns) {
|
|
4129
|
+
const rid = turn.runId;
|
|
4130
|
+
if (!rid) continue;
|
|
4131
|
+
const idxInStack = runStack.indexOf(rid);
|
|
4132
|
+
if (idxInStack >= 0) runStack.length = idxInStack + 1;
|
|
4133
|
+
else {
|
|
4134
|
+
const top = runStack[runStack.length - 1];
|
|
4135
|
+
if (!(top !== void 0 && (pendingToolCallsPerRun.get(top)?.size ?? 0) > 0)) runStack.length = 0;
|
|
4136
|
+
runStack.push(rid);
|
|
4137
|
+
if (!runById.has(rid)) {
|
|
4138
|
+
const depth = runStack.length - 1;
|
|
4139
|
+
const parentRunId = depth > 0 ? runStack[depth - 1] : void 0;
|
|
4140
|
+
let inferredPrompt = "";
|
|
4141
|
+
if (turn.role === "user") {
|
|
4142
|
+
for (const block of turn.content) if (block.type === "text" && block.text.trim()) {
|
|
4143
|
+
inferredPrompt = block.text;
|
|
4144
|
+
break;
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
runById.set(rid, {
|
|
4148
|
+
id: rid,
|
|
4149
|
+
startedAt: turn.createdAt,
|
|
4150
|
+
prompt: inferredPrompt,
|
|
4151
|
+
status: "completed",
|
|
4152
|
+
depth,
|
|
4153
|
+
...parentRunId ? { parentRunId } : {}
|
|
4154
|
+
});
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
const pending = pendingToolCallsPerRun.get(rid) ?? /* @__PURE__ */ new Set();
|
|
4158
|
+
for (const block of turn.content) if (block.type === "tool_call") pending.add(block.id);
|
|
4159
|
+
else if (block.type === "tool_result") pending.delete(block.callId);
|
|
4160
|
+
pendingToolCallsPerRun.set(rid, pending);
|
|
4161
|
+
}
|
|
3325
4162
|
const childLabelByRunId = /* @__PURE__ */ new Map();
|
|
3326
|
-
|
|
4163
|
+
[...runById.values()].filter((r) => (r.depth ?? 0) > 0).sort((a, b) => a.startedAt - b.startedAt).forEach((r, i) => childLabelByRunId.set(r.id, `child-${i + 1}`));
|
|
3327
4164
|
const labelFor = (runId) => childLabelByRunId.get(runId) ?? runId;
|
|
3328
4165
|
const toolByCallId = /* @__PURE__ */ new Map();
|
|
3329
4166
|
for (const turn of turns) {
|
|
@@ -3369,7 +4206,7 @@ function eventsFromTurns(turns, runs = []) {
|
|
|
3369
4206
|
});
|
|
3370
4207
|
};
|
|
3371
4208
|
const pushSpawnStart = (run) => {
|
|
3372
|
-
const taskPreview = run.prompt
|
|
4209
|
+
const taskPreview = previewLine(run.prompt, 80);
|
|
3373
4210
|
events.push({
|
|
3374
4211
|
kind: "spawn-start",
|
|
3375
4212
|
text: taskPreview,
|
|
@@ -3399,6 +4236,11 @@ function eventsFromTurns(turns, runs = []) {
|
|
|
3399
4236
|
};
|
|
3400
4237
|
if (turn.role === "user") {
|
|
3401
4238
|
for (const block of turn.content) if (block.type === "text" && block.text.trim()) {
|
|
4239
|
+
const taskEvent = parseTaskNotificationBlock(block.text, tag);
|
|
4240
|
+
if (taskEvent) {
|
|
4241
|
+
events.push(taskEvent);
|
|
4242
|
+
continue;
|
|
4243
|
+
}
|
|
3402
4244
|
if (depth === 0) {
|
|
3403
4245
|
if (lastDepthAtEmission === 0) events.push({
|
|
3404
4246
|
kind: "separator",
|
|
@@ -3474,6 +4316,84 @@ function eventsFromTurns(turns, runs = []) {
|
|
|
3474
4316
|
return events;
|
|
3475
4317
|
}
|
|
3476
4318
|
/** Shared formatter for the `↳ name(args)` line shown on tool calls. */
|
|
4319
|
+
/**
|
|
4320
|
+
* Pattern matching the leading `<task-notification>` shape that the
|
|
4321
|
+
* agent's notification-injection path produces (see
|
|
4322
|
+
* `renderTaskNotificationXml` in `src/agent.ts`). Anchored so a stray
|
|
4323
|
+
* notification-shaped phrase mid-prompt doesn't trigger a false-positive
|
|
4324
|
+
* detection — the model's wire format always starts the user-turn
|
|
4325
|
+
* content block with this exact opening tag.
|
|
4326
|
+
*/
|
|
4327
|
+
const TASK_NOTIFICATION_RE = /^\s*<task-notification>([\s\S]*?)<\/task-notification>\s*$/;
|
|
4328
|
+
/**
|
|
4329
|
+
* Per-field extractors. Compiled once at module load (parsing happens
|
|
4330
|
+
* on every session replay, and a fresh `new RegExp` per pick burns
|
|
4331
|
+
* cycles for no reason).
|
|
4332
|
+
*/
|
|
4333
|
+
const TASK_NOTIFICATION_FIELD_RES = {
|
|
4334
|
+
taskId: /<task-id>([\s\S]*?)<\/task-id>/,
|
|
4335
|
+
status: /<status>([\s\S]*?)<\/status>/,
|
|
4336
|
+
exitCode: /<exit-code>([\s\S]*?)<\/exit-code>/,
|
|
4337
|
+
command: /<command>([\s\S]*?)<\/command>/,
|
|
4338
|
+
outputFile: /<output-file>([\s\S]*?)<\/output-file>/,
|
|
4339
|
+
durationMs: /<duration-ms>([\s\S]*?)<\/duration-ms>/,
|
|
4340
|
+
summary: /<summary>([\s\S]*?)<\/summary>/
|
|
4341
|
+
};
|
|
4342
|
+
/**
|
|
4343
|
+
* Detect a `<task-notification>` text block on replay and convert it
|
|
4344
|
+
* into a structured `task-notification` StreamEvent so the TUI's banner
|
|
4345
|
+
* renderer can paint it cleanly. Returns `null` when the block isn't a
|
|
4346
|
+
* notification (the caller falls back to the normal user-prompt path).
|
|
4347
|
+
*
|
|
4348
|
+
* Reads every field DIRECTLY from its tag — does NOT parse `<summary>`
|
|
4349
|
+
* for the underlying data. The summary is a derived display string,
|
|
4350
|
+
* not a source of truth: changing its format (localization, signal in
|
|
4351
|
+
* the label, whatever) must NEVER break replay. Each field falls back
|
|
4352
|
+
* to a safe default so older transcripts pre-dating a tag addition
|
|
4353
|
+
* still render.
|
|
4354
|
+
*/
|
|
4355
|
+
function parseTaskNotificationBlock(text, tag) {
|
|
4356
|
+
const m = text.match(TASK_NOTIFICATION_RE);
|
|
4357
|
+
if (!m) return null;
|
|
4358
|
+
const body = m[1];
|
|
4359
|
+
const pick = (re) => {
|
|
4360
|
+
const inner = body.match(re);
|
|
4361
|
+
return inner ? unescapeXml(inner[1].trim()) : void 0;
|
|
4362
|
+
};
|
|
4363
|
+
const taskId = pick(TASK_NOTIFICATION_FIELD_RES.taskId) ?? "?";
|
|
4364
|
+
const statusRaw = pick(TASK_NOTIFICATION_FIELD_RES.status) ?? "exited";
|
|
4365
|
+
const status = statusRaw === "killed" ? "killed" : "exited";
|
|
4366
|
+
const exitCode = Number.parseInt(pick(TASK_NOTIFICATION_FIELD_RES.exitCode) ?? "0", 10) || 0;
|
|
4367
|
+
const command = pick(TASK_NOTIFICATION_FIELD_RES.command) ?? "";
|
|
4368
|
+
const outputPath = pick(TASK_NOTIFICATION_FIELD_RES.outputFile) ?? "";
|
|
4369
|
+
const durationMs = Number.parseInt(pick(TASK_NOTIFICATION_FIELD_RES.durationMs) ?? "0", 10) || 0;
|
|
4370
|
+
return {
|
|
4371
|
+
kind: "task-notification",
|
|
4372
|
+
text: pick(TASK_NOTIFICATION_FIELD_RES.summary) ?? `${taskId} ${statusRaw}${exitCode ? ` ${exitCode}` : ""}`,
|
|
4373
|
+
task: {
|
|
4374
|
+
taskId,
|
|
4375
|
+
status,
|
|
4376
|
+
exitCode,
|
|
4377
|
+
outputPath,
|
|
4378
|
+
command,
|
|
4379
|
+
durationMs
|
|
4380
|
+
},
|
|
4381
|
+
turnId: tag.turnId,
|
|
4382
|
+
...tag.childId !== void 0 ? { childId: tag.childId } : {},
|
|
4383
|
+
...tag.depth !== void 0 ? { depth: tag.depth } : {}
|
|
4384
|
+
};
|
|
4385
|
+
}
|
|
4386
|
+
/**
|
|
4387
|
+
* Reverse of `escapeXml` for the small set of entities the writer
|
|
4388
|
+
* emits. Not a full HTML entity decoder — just enough to round-trip
|
|
4389
|
+
* `<` / `>` / `&` / quotes through persistence.
|
|
4390
|
+
*
|
|
4391
|
+
* `&` MUST be replaced last, otherwise `&lt;` becomes `<`
|
|
4392
|
+
* then `<` (data corruption — silent double-decode).
|
|
4393
|
+
*/
|
|
4394
|
+
function unescapeXml(s) {
|
|
4395
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/&/g, "&");
|
|
4396
|
+
}
|
|
3477
4397
|
function toolCallPreview(name, input) {
|
|
3478
4398
|
const args = JSON.stringify(input);
|
|
3479
4399
|
return args && args !== "{}" ? `${name}(${args})` : name;
|
|
@@ -3606,7 +4526,8 @@ const MARGIN_TOP = {
|
|
|
3606
4526
|
"markdown": 1,
|
|
3607
4527
|
"spawn-start": 1,
|
|
3608
4528
|
"spawn-end": 0,
|
|
3609
|
-
"compact-summary": 1
|
|
4529
|
+
"compact-summary": 1,
|
|
4530
|
+
"task-notification": 1
|
|
3610
4531
|
};
|
|
3611
4532
|
const TOOL_KINDS = new Set(["tool", "tool-result"]);
|
|
3612
4533
|
/**
|
|
@@ -3616,20 +4537,28 @@ const TOOL_KINDS = new Set(["tool", "tool-result"]);
|
|
|
3616
4537
|
* - A `tool` / `tool-result` event right after another
|
|
3617
4538
|
* `tool` / `tool-result` collapses to a tight list — call→result
|
|
3618
4539
|
* pairs and back-to-back calls read as one logical block.
|
|
3619
|
-
* -
|
|
3620
|
-
*
|
|
3621
|
-
*
|
|
3622
|
-
*
|
|
3623
|
-
*
|
|
4540
|
+
* - Consecutive `task-notification` banners stack tight — a burst of
|
|
4541
|
+
* background-task completions reads as one log column rather than
|
|
4542
|
+
* scattered banners.
|
|
4543
|
+
*
|
|
4544
|
+
* NB: parent-level events (`depth === 0`) following a subagent block
|
|
4545
|
+
* (`depth > 0`) get their normal default margin. An earlier revision
|
|
4546
|
+
* collapsed this transition to 0 on the assumption that the subagent's
|
|
4547
|
+
* `🌳` end-marker provided enough visual separation — it didn't. A
|
|
4548
|
+
* 2-cell emoji on the same row as the close marker doesn't create a
|
|
4549
|
+
* vertical break, and in hide-subagent-output mode the SubagentBlock
|
|
4550
|
+
* box isn't rendered either, so the parent's follow-up markdown was
|
|
4551
|
+
* glued directly against the close line. Subagents should READ like
|
|
4552
|
+
* tool calls: open with breathing room, close tight inside, and open
|
|
4553
|
+
* a clean gap before the parent's next thought — that's the contract
|
|
4554
|
+
* the tool-call → markdown transition uses, mirrored here.
|
|
3624
4555
|
*
|
|
3625
4556
|
* Renderer-agnostic — TUI uses it as Yoga `marginTop`; a CSS host can
|
|
3626
4557
|
* use the same number as the row's top margin in `em` / `rem`.
|
|
3627
4558
|
*/
|
|
3628
4559
|
function marginTopFor(event, previous) {
|
|
3629
4560
|
if (TOOL_KINDS.has(event.kind) && previous && TOOL_KINDS.has(previous.kind)) return 0;
|
|
3630
|
-
|
|
3631
|
-
const previousDepth = previous?.depth ?? 0;
|
|
3632
|
-
if (eventDepth === 0 && previousDepth > 0) return 0;
|
|
4561
|
+
if (event.kind === "task-notification" && previous?.kind === "task-notification") return 0;
|
|
3633
4562
|
return MARGIN_TOP[event.kind] ?? 0;
|
|
3634
4563
|
}
|
|
3635
4564
|
/**
|
|
@@ -3864,7 +4793,8 @@ function resolveConfig(options) {
|
|
|
3864
4793
|
initialSettings: migrateLegacySettings(initialState.settings ?? {}),
|
|
3865
4794
|
resumeProvider,
|
|
3866
4795
|
initialPicked,
|
|
3867
|
-
keybindings
|
|
4796
|
+
keybindings,
|
|
4797
|
+
autoUpdate: options.autoUpdate ?? null
|
|
3868
4798
|
};
|
|
3869
4799
|
}
|
|
3870
4800
|
/**
|
|
@@ -4963,7 +5893,8 @@ const DEFAULT_SETTINGS = {
|
|
|
4963
5893
|
allowInteraction: true,
|
|
4964
5894
|
smoothStreaming: true,
|
|
4965
5895
|
showTodoIndicator: true,
|
|
4966
|
-
showThrobber: false
|
|
5896
|
+
showThrobber: false,
|
|
5897
|
+
checkForUpdates: true
|
|
4967
5898
|
};
|
|
4968
5899
|
/**
|
|
4969
5900
|
* Hard-clamp a `targetFps` value to a safe range before handing it to
|
|
@@ -5088,6 +6019,11 @@ const SETTINGS_TOGGLES = [
|
|
|
5088
6019
|
key: "showThrobber",
|
|
5089
6020
|
label: "Streaming throbber",
|
|
5090
6021
|
description: "animated gradient glyphs at the transcript tail while the assistant is working"
|
|
6022
|
+
},
|
|
6023
|
+
{
|
|
6024
|
+
key: "checkForUpdates",
|
|
6025
|
+
label: "Check for updates",
|
|
6026
|
+
description: "quietly check npm once a day; footer chip surfaces newer releases. `zidane upgrade` always works regardless."
|
|
5091
6027
|
}
|
|
5092
6028
|
];
|
|
5093
6029
|
const SETTINGS_CHOICES = [
|
|
@@ -5421,51 +6357,6 @@ function toEntries(paths, source, maxFiles) {
|
|
|
5421
6357
|
return out;
|
|
5422
6358
|
}
|
|
5423
6359
|
//#endregion
|
|
5424
|
-
//#region src/chat/format.ts
|
|
5425
|
-
/** Compact token formatter — 12_415 → "12.4k", 1_234_567 → "1.23M". */
|
|
5426
|
-
function fmtTokens(n) {
|
|
5427
|
-
if (n < 1e3) return String(n);
|
|
5428
|
-
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 2 : 1)}k`;
|
|
5429
|
-
return `${(n / 1e6).toFixed(2)}M`;
|
|
5430
|
-
}
|
|
5431
|
-
/** Compact relative-time formatter — "just now / 5m / 3h / 2d". */
|
|
5432
|
-
function ageString(ts, now = Date.now()) {
|
|
5433
|
-
const m = Math.floor((now - ts) / 6e4);
|
|
5434
|
-
if (m < 1) return "just now";
|
|
5435
|
-
if (m < 60) return `${m}m ago`;
|
|
5436
|
-
const h = Math.floor(m / 60);
|
|
5437
|
-
if (h < 24) return `${h}h ago`;
|
|
5438
|
-
return `${Math.floor(h / 24)}d ago`;
|
|
5439
|
-
}
|
|
5440
|
-
/** Six-char short form of a session id for headers and lists. */
|
|
5441
|
-
function shortId(id) {
|
|
5442
|
-
return id.replace(/-/g, "").slice(0, 6);
|
|
5443
|
-
}
|
|
5444
|
-
/**
|
|
5445
|
-
* Compact an absolute path for display: replace the user's `$HOME`
|
|
5446
|
-
* prefix with `~` (so `/Users/yael/Code/zidane` → `~/Code/zidane`),
|
|
5447
|
-
* and optionally left-truncate with an ellipsis when the result
|
|
5448
|
-
* still exceeds `maxWidth` (so the path's *tail* — the part the user
|
|
5449
|
-
* recognizes — stays visible: `…/zidane` rather than `/Users/yaeluil…`).
|
|
5450
|
-
*
|
|
5451
|
-
* `maxWidth` is the maximum *display width* in cells. Omit to skip
|
|
5452
|
-
* truncation. Paths outside `$HOME` are returned verbatim modulo
|
|
5453
|
-
* truncation. The ellipsis (`…`) counts as one cell.
|
|
5454
|
-
*
|
|
5455
|
-
* `home` overrides `os.homedir()` for tests; production callers leave
|
|
5456
|
-
* it undefined and pay the cheap one-syscall lookup per call.
|
|
5457
|
-
*/
|
|
5458
|
-
function compactPath(path, maxWidth, home) {
|
|
5459
|
-
const h = home ?? homedir();
|
|
5460
|
-
let display = path;
|
|
5461
|
-
if (h) {
|
|
5462
|
-
if (path === h) display = "~";
|
|
5463
|
-
else if (path.startsWith(`${h}/`)) display = `~${path.slice(h.length)}`;
|
|
5464
|
-
}
|
|
5465
|
-
if (maxWidth !== void 0 && maxWidth > 1 && display.length > maxWidth) return `…${display.slice(display.length - maxWidth + 1)}`;
|
|
5466
|
-
return display;
|
|
5467
|
-
}
|
|
5468
|
-
//#endregion
|
|
5469
6360
|
//#region src/chat/generate-title.ts
|
|
5470
6361
|
/** Hard cap on the result length. Anything longer is truncated client-side. */
|
|
5471
6362
|
const TITLE_MAX_CHARS = 60;
|
|
@@ -6678,13 +7569,17 @@ async function runOAuthLogin(descriptor, options) {
|
|
|
6678
7569
|
*
|
|
6679
7570
|
* Rule:
|
|
6680
7571
|
*
|
|
6681
|
-
* -
|
|
6682
|
-
*
|
|
6683
|
-
*
|
|
6684
|
-
*
|
|
6685
|
-
*
|
|
6686
|
-
*
|
|
6687
|
-
*
|
|
7572
|
+
* - File at `cwd` itself → `.`.
|
|
7573
|
+
* - File under `cwd` (subtree) → forward-slashed CWD-relative,
|
|
7574
|
+
* no `..`.
|
|
7575
|
+
* - File above `cwd` but still inside the project → CWD-relative
|
|
7576
|
+
* with `..` traversal (e.g. `../EDIT_THIS.md`). Shorter and more
|
|
7577
|
+
* scannable than absolute, and the agent's tools resolve both.
|
|
7578
|
+
* - File outside the project (input was passed absolute) → returned
|
|
7579
|
+
* verbatim. Inputs that arrive as project-root-relative strings
|
|
7580
|
+
* are always inside the project by construction, so this branch
|
|
7581
|
+
* only kicks in when a direct caller hands the function an
|
|
7582
|
+
* absolute target.
|
|
6688
7583
|
*
|
|
6689
7584
|
* Pure, no I/O, deterministic.
|
|
6690
7585
|
*/
|
|
@@ -6695,13 +7590,18 @@ async function runOAuthLogin(descriptor, options) {
|
|
|
6695
7590
|
*
|
|
6696
7591
|
* Inputs:
|
|
6697
7592
|
* - `projectRelativePath` — forward-slashed, no leading slash. Returned
|
|
6698
|
-
* verbatim when it's empty or already absolute (defensive
|
|
7593
|
+
* verbatim when it's empty or already absolute (defensive — the
|
|
7594
|
+
* absolute-input branch is the de-facto "outside the project"
|
|
7595
|
+
* escape; the catalog itself never emits absolute paths).
|
|
6699
7596
|
* - `projectRoot` — absolute path of the discovery anchor.
|
|
6700
7597
|
* - `cwd` — absolute path of `process.cwd()` at the moment of
|
|
6701
7598
|
* insertion.
|
|
6702
7599
|
*
|
|
6703
|
-
* Output is always either an absolute path
|
|
6704
|
-
*
|
|
7600
|
+
* Output is always either an absolute path (only when the input arrived
|
|
7601
|
+
* absolute) OR a CWD-relative path. The relative form may include
|
|
7602
|
+
* `../` segments when the target sits above `cwd` within the project,
|
|
7603
|
+
* which keeps cross-tree references short and scannable
|
|
7604
|
+
* (e.g. `../EDIT_THIS.md` instead of `/Users/.../zidane/EDIT_THIS.md`).
|
|
6705
7605
|
*/
|
|
6706
7606
|
function formatPathForCwd(projectRelativePath, projectRoot, cwd) {
|
|
6707
7607
|
if (projectRelativePath.length === 0) return projectRelativePath;
|
|
@@ -6711,7 +7611,6 @@ function formatPathForCwd(projectRelativePath, projectRoot, cwd) {
|
|
|
6711
7611
|
if (target === here) return ".";
|
|
6712
7612
|
const rel = relative(here, target);
|
|
6713
7613
|
if (rel.length === 0) return ".";
|
|
6714
|
-
if (rel === ".." || rel.startsWith(`..${sep}`) || rel.startsWith("../")) return target;
|
|
6715
7614
|
return sep === "/" ? rel : rel.split(sep).join(posix.sep);
|
|
6716
7615
|
}
|
|
6717
7616
|
//#endregion
|
|
@@ -7872,13 +8771,21 @@ const TOOL_DISPLAY = {
|
|
|
7872
8771
|
}
|
|
7873
8772
|
},
|
|
7874
8773
|
shell: {
|
|
7875
|
-
displayName: "Shell",
|
|
8774
|
+
displayName: (input) => input?.run_in_background === true ? "Shell (background)" : "Shell",
|
|
7876
8775
|
format: (input) => {
|
|
7877
8776
|
const command = stringField(input, "command");
|
|
7878
8777
|
if (!command) return null;
|
|
7879
8778
|
return { target: truncate(command.trim(), 200) };
|
|
7880
8779
|
}
|
|
7881
8780
|
},
|
|
8781
|
+
shell_kill: {
|
|
8782
|
+
displayName: "Kill task",
|
|
8783
|
+
format: (input) => {
|
|
8784
|
+
const taskId = stringField(input, "task_id");
|
|
8785
|
+
if (!taskId) return null;
|
|
8786
|
+
return { target: taskId };
|
|
8787
|
+
}
|
|
8788
|
+
},
|
|
7882
8789
|
edit: {
|
|
7883
8790
|
displayName: "Edit",
|
|
7884
8791
|
format: (input) => {
|
|
@@ -8246,6 +9153,6 @@ function countNeighbors(turnIds, turnId) {
|
|
|
8246
9153
|
};
|
|
8247
9154
|
}
|
|
8248
9155
|
//#endregion
|
|
8249
|
-
export { useMcpAuthDispatch as $,
|
|
9156
|
+
export { useMcpAuthDispatch as $, tryOpenBrowser as $n, selectActiveTodos as $r, loadState as $t, getSafelist as A, DEFAULT_KEYBINDINGS as An, modelsForDescriptor as Ar, resolveChipColor as At, supportsOAuth as B, SKILLS_TRIGGER as Bn, resolveAgentId as Br, useDiscoveryOptional as Bt, resolveSessionExportTarget as C, mergeApprovalAndBodyOutcomes as Cn, anthropicDescriptor as Cr, SETTINGS_CHOICES as Ct, useSafeModeQueue as D, stripEditOutcomesAnnotation as Dn, getContextWindow as Dr, useSettings as Dt, useSafeModeActions as E, rewriteMultiEditHeader as En, effectiveContextWindow as Er, clampFps as Et, suggestSafelistEntry as F, matchesBinding as Fn, BUILTIN_AGENTS as Fr, CATPPUCCIN_MACCHIATO as Ft, defaultMcpsConfigPaths as G, uniqueFilesFromReferences as Gn, TODO_STATUS_GLYPHS as Gr, createStateStore as Gt, filterModelCatalog as H, uniqueSkillNamesFromReferences as Hn, TODOREAD_TOOL as Hr, useConfig as Ht, writeProjects as I, mergeKeybindings as In, DEFAULT_AGENT_ID as Ir, CATPPUCCIN_MOCHA as It, projectUserPaths as J, findActiveTrigger as Jn, getArchivedTodosForRun as Jr, isEditErrorResult as Jt, discoverProjectMcps as K, applyInsert as Kn, TODO_WRITE_COUNTS_METADATA_KEY as Kr, deriveSessionTitle as Kt, splitPromptSegments as L, parseBindingSpec as Ln, DEFAULT_PERSIST_EXCLUDE_TOOLS as Lr, createDiscoverySlot as Lt, matchesSafelistEntry as M, KEYBINDING_DEF_BY_ACTION as Mn, openrouterDescriptor as Mr, VAPORWAVE_THEME as Mt, projectsFilePath as N, ensureKeybindingsFile as Nn, piIdOf as Nr, CATPPUCCIN_FRAPPE as Nt, IMPLICITLY_SAFE_TOOLS as O, summarizeOutcomes as On, getModelInfo as Or, BUILTIN_THEMES as Ot, readProjects as P, keybindingsPath as Pn, BUILD_AGENT as Pr, CATPPUCCIN_LATTE as Pt, McpAuthProvider as Q, buildLinearRamp as Qn, pruneTodosByRun as Qr, listSessionMeta as Qt, formatPathForCwd as R, readKeybindings as Rn, PLAN_AGENT as Rr, DiscoveryProvider as Rt, renderSession as S, maskToOutcomeKinds as Sn, OUTPUT_RESERVE_TOKENS as Sr, DEFAULT_SETTINGS as St, SafeModeProvider as T, resolveApprovalForPayload as Tn, credKeyOf as Tr, SettingsProvider as Tt, indexOfEntry as U, FILES_TRIGGER as Un, TODOS_METADATA_KEY as Ur, resolveConfig as Ut, buildModelCatalog as V, createSkillsCompletionProvider as Vn, singleAgentRegistry as Vr, ConfigProvider as Vt, buildMcpServers as W, createFilesCompletionProvider as Wn, TODOWRITE_TOOL as Wr, EDIT_TOOL_NAMES as Wt, mcpCredentialsPath as X, useCompletion as Xn, isTodoTool as Xr, isVisible as Xt, createFileMcpCredentialStore as Y, mergeReferences as Yn, getTodosForRun as Yr, isTurnHighlighted as Yt, patchMcpCredential as Z, blendHsl as Zn, pickActiveRunId as Zr, lastContextSizeFromTurns as Zt, turnContextSize as _, previewEditPayload as _n, readProviderCredential as _r, truncateTrailing as _t, computeTurnAnchors as a, IDENTITY_PREFIX as ai, titleFromTurns as an, compareSemver as ar, InteractionsProvider as at, defaultSkillScanPaths as b, tokenize as bn, writeCredentials as br, listProjectFiles as bt, formatToolCall as c, PLAN_MODE_DOCTRINE as ci, turnSelectionOwnership as cn, parseSemver as cr, createInteractionTools as ct, useSelectStyle as d, TOKEN_DISCIPLINE_DOCTRINE as di, buildContextualDiff as dn, resolvePlatformPackage as dr, pendingInteractionsFromTurns as dt, setTodosForRun as ei, marginTopFor as en, bootProfileEnabled as er, useMcpAuthState as et, useSurfaces as f, buildBuildSystem as fi, buildUnifiedDiff as fn, shouldAutoCompact as fr, serializeInteractionResponse as ft, finalizeStreamingMarkdownForOwner as g, filetypeFromPath as gn, readCredentials as gr, hintsLength as gt, finalizeStreamingMarkdown as h, extractEditPayload as hn, credentialsPath as hr, clipHintsToWidth as ht, turnAsText as i, DOING_TASKS_DOCTRINE as ii, sumRunCosts as in, checkForUpdate as ir, ASK_USER_TOOL as it, isOnSafelist as j, KEYBINDING_DEFS as jn, openaiDescriptor as jr, resolveTheme as jt, addToSafelist as k, findGitRoot$1 as kn, modelSupportsReasoning as kr, DEFAULT_THEME as kt, ThemeProvider as l, PLAN_MODE_DOCTRINE_NO_PROMPTS as li, updateToolEventOutcomes as ln, performInPlaceSelfUpdate as lr, isInteractionTool as lt, useTheme as m, envSection as mi, computeLineDiff as mn, applyApiKeyEnv as mr, useInteractionsQueue as mt, deleteTurnSafely as n, ACTIONS_WITH_CARE_DOCTRINE as ni, selectableTurnIds as nn, buildUpdateHint as nr, reduceMcpAuth as nt, TOOL_DISPLAY as o, INTERACTION_GUIDANCE as oi, toolCallPreview as on, detectLibc as or, PRESENT_PLAN_TOOL as ot, useSyntaxStyles as p, buildPlanSystem as pi, computeInlineDiff as pn, detectAuth as pr, useInteractionsActions as pt, parseMcpsFile as q, collectReferences as qn, createTodoTools as qr, eventsFromTurns as qt, truncateTurnsAt as r, COMMUNICATION_DOCTRINE as ri, stripSpawnTokensLine as rn, useUpdateCheck as rr, splitMarkdownCodeBlocks as rt, displayNameFor as s, INTERACTION_GUIDANCE_NO_PROMPTS as si, toolResultText as sn, detectPackageManager as sr, buildResumedToolResultsTurn as st, countNeighbors as t, useActiveTodos as ti, saveState as tn, bootTick as tr, getMcpAuthStatus as tt, useColors as u, SUBAGENT_GUIDANCE as ui, applyEditPayload as un, performSelfUpdate as ur, makeRequestInteraction as ut, useStreamBuffer as v, splitLines as vn, removeProviderCredential as vr, cleanTitle as vt, writeSessionExport as w, parseEditOutcomesFromResult as wn, cerebrasDescriptor as wr, SETTINGS_TOGGLES as wt, discoverProjectSkills as x, buildEditOutcomesAnnotation as xn, BUILTIN_PROVIDERS as xr, useEnabledToggleSet as xt, buildSkillsConfig as y, summarizeEditPayload as yn, setProviderCredential as yr, generateSessionTitle as yt, runOAuthLogin as z, stripJsonComments as zn, accentColor as zr, useDiscovery as zt };
|
|
8250
9157
|
|
|
8251
|
-
//# sourceMappingURL=turn-operations-
|
|
9158
|
+
//# sourceMappingURL=turn-operations-DtMApNGT.js.map
|