zidane 7.0.13 → 7.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/{acp-bTPYx7Sd.js → acp-Bw2b_kka.js} +3 -3
  2. package/dist/{acp-bTPYx7Sd.js.map → acp-Bw2b_kka.js.map} +1 -1
  3. package/dist/acp-cli.js +4 -4
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-DRNT6DbC.js → agent-B_uQo4f1.js} +650 -49
  7. package/dist/agent-B_uQo4f1.js.map +1 -0
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/{anthropic-Da39cE-d.js → anthropic-QbHqqTkV.js} +2 -2
  11. package/dist/{anthropic-Da39cE-d.js.map → anthropic-QbHqqTkV.js.map} +1 -1
  12. package/dist/{approvals-Ch6gmO8H.js → approvals-B9aIQJRQ.js} +287 -45
  13. package/dist/approvals-B9aIQJRQ.js.map +1 -0
  14. package/dist/artifacts-CV2hUKnL.js +112 -0
  15. package/dist/artifacts-CV2hUKnL.js.map +1 -0
  16. package/dist/{auth-Ddk-rCdG.js → auth-plw6npda.js} +19 -7
  17. package/dist/auth-plw6npda.js.map +1 -0
  18. package/dist/chat/pure.d.ts +3 -3
  19. package/dist/chat/pure.js +1 -1
  20. package/dist/chat.d.ts +6 -6
  21. package/dist/chat.js +5 -5
  22. package/dist/contexts/daytona.d.ts +1 -1
  23. package/dist/contexts/e2b.d.ts +1 -1
  24. package/dist/daemon-cli.js +1 -1
  25. package/dist/eval.d.ts +1 -1
  26. package/dist/eval.js +1 -1
  27. package/dist/extensions.d.ts +2 -2
  28. package/dist/extensions.js +2 -2
  29. package/dist/headless.d.ts +1 -1
  30. package/dist/headless.js +2 -2
  31. package/dist/{index-DFGECPfm.d.ts → index-Dc_zYpEu.d.ts} +351 -2
  32. package/dist/index-Dc_zYpEu.d.ts.map +1 -0
  33. package/dist/{index-DP7xYXr9.d.ts → index-Df2CH6bn.d.ts} +92 -7
  34. package/dist/index-Df2CH6bn.d.ts.map +1 -0
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +7 -6
  37. package/dist/{ipc-C8MCEu1E.js → ipc-T_pPnHkp.js} +2 -2
  38. package/dist/{ipc-C8MCEu1E.js.map → ipc-T_pPnHkp.js.map} +1 -1
  39. package/dist/ipc.d.ts +54 -6
  40. package/dist/ipc.d.ts.map +1 -1
  41. package/dist/ipc.js +3 -3
  42. package/dist/mcp.d.ts +1 -1
  43. package/dist/{oauth-C4GvY_p1.js → oauth-EIBUuSDK.js} +2 -2
  44. package/dist/{oauth-C4GvY_p1.js.map → oauth-EIBUuSDK.js.map} +1 -1
  45. package/dist/output/stream-json.d.ts +1 -1
  46. package/dist/output/terminal.d.ts +1 -1
  47. package/dist/plugins.d.ts +1 -1
  48. package/dist/presets.d.ts +2 -2
  49. package/dist/presets.js +2 -2
  50. package/dist/providers/anthropic.d.ts +1 -1
  51. package/dist/providers/anthropic.js +1 -1
  52. package/dist/providers/arcee.d.ts +1 -1
  53. package/dist/providers/baseten.d.ts +1 -1
  54. package/dist/providers/cerebras.d.ts +1 -1
  55. package/dist/providers/local.d.ts +1 -1
  56. package/dist/providers/openai-compat.d.ts +1 -1
  57. package/dist/providers/openai.d.ts +1 -1
  58. package/dist/providers/openai.js +1 -1
  59. package/dist/providers/openrouter.d.ts +1 -1
  60. package/dist/providers/xai.d.ts +1 -1
  61. package/dist/providers/xai.js +2 -2
  62. package/dist/{providers-BYgyPb15.js → providers-CoK1rZ1p.js} +2 -2
  63. package/dist/{providers-BYgyPb15.js.map → providers-CoK1rZ1p.js.map} +1 -1
  64. package/dist/providers.d.ts +1 -1
  65. package/dist/providers.js +3 -3
  66. package/dist/restate.d.ts +1 -1
  67. package/dist/restate.js +1 -1
  68. package/dist/{server-CGmy4363.js → server-DXM8ZbGi.js} +64 -6
  69. package/dist/server-DXM8ZbGi.js.map +1 -0
  70. package/dist/session/sqlite.d.ts +1 -1
  71. package/dist/session/sqlite.d.ts.map +1 -1
  72. package/dist/session/sqlite.js +117 -1
  73. package/dist/session/sqlite.js.map +1 -1
  74. package/dist/{session-K3icbPH9.js → session-DPZnncaQ.js} +60 -1
  75. package/dist/session-DPZnncaQ.js.map +1 -0
  76. package/dist/session.d.ts +2 -2
  77. package/dist/session.js +3 -2
  78. package/dist/skills.d.ts +1 -1
  79. package/dist/{tool-formatters-C-s_-u0v.d.ts → tool-formatters-BgdOYAy9.d.ts} +2 -2
  80. package/dist/{tool-formatters-C-s_-u0v.d.ts.map → tool-formatters-BgdOYAy9.d.ts.map} +1 -1
  81. package/dist/tools/fetch-url.d.ts +1 -1
  82. package/dist/tools/web-search.d.ts +1 -1
  83. package/dist/tools.d.ts +2 -2
  84. package/dist/tools.js +2 -2
  85. package/dist/{transcript-anchors-D1ToF-EW.d.ts → transcript-anchors-CtnwuQJM.d.ts} +3 -3
  86. package/dist/transcript-anchors-CtnwuQJM.d.ts.map +1 -0
  87. package/dist/tui.d.ts +3 -3
  88. package/dist/tui.d.ts.map +1 -1
  89. package/dist/tui.js +576 -102
  90. package/dist/tui.js.map +1 -1
  91. package/dist/{turn-operations-CZLsNgw-.js → turn-operations-DkJ1R0p8.js} +4 -3
  92. package/dist/turn-operations-DkJ1R0p8.js.map +1 -0
  93. package/dist/{turn-operations-Bp5-BAki.d.ts → turn-operations-khAvr7KJ.d.ts} +5 -3
  94. package/dist/turn-operations-khAvr7KJ.d.ts.map +1 -0
  95. package/dist/types.d.ts +1 -1
  96. package/dist/{xai-DILdFGzN.js → xai-CazjStOK.js} +3 -3
  97. package/dist/{xai-DILdFGzN.js.map → xai-CazjStOK.js.map} +1 -1
  98. package/docs/EXTENSIONS.md +58 -0
  99. package/docs/IPC.md +9 -0
  100. package/docs/TUI.md +2 -0
  101. package/package.json +1 -1
  102. package/dist/agent-DRNT6DbC.js.map +0 -1
  103. package/dist/approvals-Ch6gmO8H.js.map +0 -1
  104. package/dist/auth-Ddk-rCdG.js.map +0 -1
  105. package/dist/index-DFGECPfm.d.ts.map +0 -1
  106. package/dist/index-DP7xYXr9.d.ts.map +0 -1
  107. package/dist/server-CGmy4363.js.map +0 -1
  108. package/dist/session-K3icbPH9.js.map +0 -1
  109. package/dist/transcript-anchors-D1ToF-EW.d.ts.map +0 -1
  110. package/dist/turn-operations-Bp5-BAki.d.ts.map +0 -1
  111. package/dist/turn-operations-CZLsNgw-.js.map +0 -1
@@ -8,7 +8,8 @@ import { i as isOutputExceededMessage } from "./output-cap-C9zukcCj.js";
8
8
  import { r as createProcessContext, t as resolveDetachedTasksCapability } from "./contexts-BSFQVclW.js";
9
9
  import { n as shellQuote, t as alwaysQuote } from "./shell-quote-BmnhZmdM.js";
10
10
  import { a as AgentProviderError, d as isAbortLikeError, g as toTypedError, i as AgentLengthResumeExhaustedError, n as AgentBudgetExceededError, p as isTransientTransportError, s as AgentToolPairingError, t as AgentAbortedError, u as errorMessage } from "./errors-DKiMrOKq.js";
11
- import { E as buildCatalog, L as projectUserPaths, O as installAllowedToolsGate, P as validateResourcePathReal, _ as lastLine, a as interpolateShellCommands, b as resolveSubdirRoot, g as fetchInstallSource, h as defaultWhich, k as createSkillActivationState, m as defaultExec, n as resolveSkills, o as stripShellInterpolations, p as InstallSourceError, v as parseInstallSource, y as resolveNpmVersion } from "./skills-PJyiSRCp.js";
11
+ import { i as ARTIFACT_KIND_PATTERN, l as normalizeArtifactInput, o as ARTIFACT_URI_MAX_CHARS, r as ARTIFACT_KINDS, t as ARTIFACTS_METADATA_KEY } from "./artifacts-CV2hUKnL.js";
12
+ import { E as buildCatalog, L as projectUserPaths, O as installAllowedToolsGate, P as validateResourcePathReal, R as findGitRoot, _ as lastLine, a as interpolateShellCommands, b as resolveSubdirRoot, g as fetchInstallSource, h as defaultWhich, k as createSkillActivationState, m as defaultExec, n as resolveSkills, o as stripShellInterpolations, p as InstallSourceError, v as parseInstallSource, y as resolveNpmVersion } from "./skills-PJyiSRCp.js";
12
13
  import { n as writeFileAtomicAsync, t as writeFileAtomic } from "./atomic-write-B7zr1ddF.js";
13
14
  import { p as resolveStorageDirs, r as loadAgentPlugin } from "./plugins-B531Tz5b.js";
14
15
  import { r as createLogger, t as consoleSink } from "./logger--C1VQjwJ.js";
@@ -17,11 +18,11 @@ import { n as reconcileImageMediaType } from "./media-sniff-UKijEaiQ.js";
17
18
  import { f as wrapDiscoveredMcpTools, i as deriveMcpToolMeta, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-BIWa57eY.js";
18
19
  import { appendStaticSection, renderSystemForWire } from "./prompt.js";
19
20
  import { a as detectTurnInterruption, c as ensureToolResultPairing, l as filterUnresolvedToolUses, m as resolveContentRefsInMessages, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, p as remintDuplicateToolCallIds, s as ensureEndsWithUserMessage } from "./messages-21XfHxyF.js";
20
- import { a as getExtensionSessionState, c as setExtensionSessionState, l as withExtensionSession, n as currentExtensionSession, r as deleteExtensionSessionState, s as resumeChainRunIds } from "./session-K3icbPH9.js";
21
+ import { a as getExtensionSessionState, c as setExtensionSessionState, l as withExtensionSession, n as currentExtensionSession, r as deleteExtensionSessionState, s as resumeChainRunIds } from "./session-DPZnncaQ.js";
21
22
  import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
22
23
  import { a as settleWithinTimeout, i as resolveTimeoutMs, n as abortableSleep, o as withInactivityTimeout, r as linkAbortSignal, s as withTimeout$1, t as OperationTimeoutError } from "./timeout-CjOG2mea.js";
23
24
  import { createRequire } from "node:module";
24
- import { basename, delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
25
+ import { basename, delimiter, dirname, isAbsolute, join, posix, resolve, sep } from "node:path";
25
26
  import { createHooks } from "hookable";
26
27
  import { Buffer } from "node:buffer";
27
28
  import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
@@ -1466,6 +1467,352 @@ function shouldAutoCompact(input) {
1466
1467
  };
1467
1468
  }
1468
1469
  //#endregion
1470
+ //#region src/extensions/artifact-detect.ts
1471
+ /**
1472
+ * `undefined` when the registration has no `detect`. The hook never
1473
+ * touches `result` / `isError`; a throwing detector emits nothing.
1474
+ * Entries default `kind` to the registration's kind; `validateMeta`
1475
+ * applies to entries of that kind only and drops `meta`, never the entry.
1476
+ */
1477
+ function createArtifactDetectHook(registration, options = {}) {
1478
+ const detect = registration.detect;
1479
+ if (!detect) return void 0;
1480
+ const { validateMeta, kind } = registration;
1481
+ return async (transformCtx) => {
1482
+ let detected;
1483
+ try {
1484
+ detected = await detect({
1485
+ tool: transformCtx.name,
1486
+ input: transformCtx.input,
1487
+ result: transformCtx.result,
1488
+ isError: transformCtx.isError
1489
+ });
1490
+ } catch (err) {
1491
+ options.warn?.("artifact detect threw; nothing emitted", {
1492
+ kind,
1493
+ tool: transformCtx.name,
1494
+ error: String(err)
1495
+ });
1496
+ return;
1497
+ }
1498
+ if (!detected) return;
1499
+ const entries = Array.isArray(detected) ? detected : [detected];
1500
+ const emitted = [];
1501
+ for (const entry of entries) {
1502
+ const normalized = normalizeArtifactInput({
1503
+ ...entry,
1504
+ kind: entry.kind ?? kind
1505
+ });
1506
+ if (!normalized) continue;
1507
+ if (normalized.meta !== void 0 && validateMeta && normalized.kind === kind) {
1508
+ let ok = false;
1509
+ try {
1510
+ ok = validateMeta(normalized.meta);
1511
+ } catch {
1512
+ ok = false;
1513
+ }
1514
+ if (!ok) {
1515
+ options.warn?.("artifact meta rejected by validateMeta; kept artifact without meta", {
1516
+ kind,
1517
+ uri: normalized.uri
1518
+ });
1519
+ delete normalized.meta;
1520
+ }
1521
+ }
1522
+ emitted.push(normalized);
1523
+ }
1524
+ if (emitted.length === 0) return;
1525
+ const existing = transformCtx.metadata[ARTIFACTS_METADATA_KEY];
1526
+ const list = Array.isArray(existing) ? existing : [];
1527
+ transformCtx.metadata[ARTIFACTS_METADATA_KEY] = [...list, ...emitted];
1528
+ };
1529
+ }
1530
+ //#endregion
1531
+ //#region src/extensions/builtin-artifact-kinds.ts
1532
+ const GITHUB_PR_URL = /https:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/(\d+)/g;
1533
+ const GITLAB_MR_URL = /https?:\/\/[\w.-]+\/([\w./-]+?)\/-\/merge_requests\/(\d+)/g;
1534
+ const GIT_COMMIT_LINE = /^\[([^\s\]]+)(?: \(root-commit\))? ([0-9a-f]{7,40})\] (.*)$/m;
1535
+ /** Don't re-resolve an open PR more often than this. */
1536
+ const PR_RESOLVE_TTL_MS = 5 * 6e4;
1537
+ function resultText(result) {
1538
+ if (typeof result === "string") return result;
1539
+ return result.map((block) => block.type === "text" ? block.text : "").join("\n");
1540
+ }
1541
+ function shellCommand(tool, input) {
1542
+ if (tool !== "shell") return void 0;
1543
+ return typeof input.command === "string" ? input.command : void 0;
1544
+ }
1545
+ /** Split a shell line into rough tokens — enough to read flags off `git` invocations. */
1546
+ function tokens(command) {
1547
+ return command.split(/\s+/).filter(Boolean);
1548
+ }
1549
+ const pullRequestKind = {
1550
+ kind: ARTIFACT_KINDS.pullRequest,
1551
+ label: "Pull request",
1552
+ detect({ tool, input, result, isError }) {
1553
+ if (isError) return void 0;
1554
+ const command = shellCommand(tool, input);
1555
+ const viaCli = command !== void 0 && (/\bgh\s+pr\s+create\b/.test(command) || /\bglab\s+mr\s+create\b/.test(command));
1556
+ const viaTool = /create_pull_request$/i.test(tool) || /create_merge_request$/i.test(tool);
1557
+ if (!viaCli && !viaTool) return void 0;
1558
+ const text = resultText(result);
1559
+ const out = [];
1560
+ for (const m of text.matchAll(GITHUB_PR_URL)) {
1561
+ const [url, owner, repo, number] = m;
1562
+ out.push({
1563
+ uri: url,
1564
+ label: `${owner}/${repo}#${number}`,
1565
+ meta: {
1566
+ provider: "github",
1567
+ owner,
1568
+ repo,
1569
+ number: Number(number)
1570
+ }
1571
+ });
1572
+ }
1573
+ for (const m of text.matchAll(GITLAB_MR_URL)) {
1574
+ const [url, project, number] = m;
1575
+ out.push({
1576
+ uri: url,
1577
+ label: `${project}!${number}`,
1578
+ meta: {
1579
+ provider: "gitlab",
1580
+ project,
1581
+ number: Number(number)
1582
+ }
1583
+ });
1584
+ }
1585
+ return out.length > 0 ? out : void 0;
1586
+ },
1587
+ validateMeta: (meta) => typeof meta === "object" && meta !== null && !Array.isArray(meta) && typeof meta.number === "number",
1588
+ render(artifact) {
1589
+ const meta = metaRecord(artifact);
1590
+ const state = typeof meta.state === "string" ? meta.state : void 0;
1591
+ return {
1592
+ title: artifact.label,
1593
+ ...typeof meta.title === "string" ? { subtitle: meta.title } : {},
1594
+ href: artifact.uri,
1595
+ ...state ? { status: state } : {}
1596
+ };
1597
+ },
1598
+ /**
1599
+ * GitHub only (GitLab needs per-host auth). Unauthenticated when no
1600
+ * `GITHUB_TOKEN` / `GH_TOKEN` is in the environment — public repos work,
1601
+ * private ones (and the 60/h anonymous limit) degrade to `undefined`, i.e.
1602
+ * "no refresh", never an error. Bounded by a 4 s timeout.
1603
+ *
1604
+ * Rate-limit hygiene: terminal states (`merged` / `closed`) never change,
1605
+ * and a fresh `resolvedAt` (within {@link PR_RESOLVE_TTL_MS}) is reused, so
1606
+ * reopening the modal and arrowing through a dozen PRs doesn't spend a
1607
+ * dozen anonymous API calls each time.
1608
+ */
1609
+ async resolve(artifact) {
1610
+ const meta = metaRecord(artifact);
1611
+ if (meta.provider !== "github" || typeof meta.owner !== "string" || typeof meta.repo !== "string" || typeof meta.number !== "number") return void 0;
1612
+ if (meta.state === "merged" || meta.state === "closed") return void 0;
1613
+ if (typeof meta.resolvedAt === "number" && Date.now() - meta.resolvedAt < PR_RESOLVE_TTL_MS) return void 0;
1614
+ const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
1615
+ const controller = new AbortController();
1616
+ const timer = setTimeout(() => controller.abort(), 4e3);
1617
+ try {
1618
+ const res = await fetch(`https://api.github.com/repos/${meta.owner}/${meta.repo}/pulls/${meta.number}`, {
1619
+ headers: {
1620
+ "Accept": "application/vnd.github+json",
1621
+ "User-Agent": "zidane",
1622
+ ...token ? { Authorization: `Bearer ${token}` } : {}
1623
+ },
1624
+ signal: controller.signal
1625
+ });
1626
+ if (!res.ok) return void 0;
1627
+ const body = await res.json();
1628
+ const state = body.merged ? "merged" : body.draft ? "draft" : typeof body.state === "string" ? body.state : void 0;
1629
+ if (!state) return void 0;
1630
+ return { meta: {
1631
+ ...meta,
1632
+ state,
1633
+ ...typeof body.title === "string" ? { title: body.title } : {},
1634
+ resolvedAt: Date.now()
1635
+ } };
1636
+ } catch {
1637
+ return;
1638
+ } finally {
1639
+ clearTimeout(timer);
1640
+ }
1641
+ }
1642
+ };
1643
+ const commitKind = {
1644
+ kind: ARTIFACT_KINDS.commit,
1645
+ label: "Commit",
1646
+ detect({ tool, input, result, isError }) {
1647
+ if (isError) return void 0;
1648
+ const command = shellCommand(tool, input);
1649
+ if (!command || !/\bgit\s+commit\b/.test(command)) return void 0;
1650
+ const m = GIT_COMMIT_LINE.exec(resultText(result));
1651
+ if (!m) return void 0;
1652
+ const [, branch, sha, message] = m;
1653
+ return {
1654
+ uri: `git:commit:${sha}`,
1655
+ label: message.trim() || sha,
1656
+ meta: {
1657
+ sha,
1658
+ branch,
1659
+ message: message.trim()
1660
+ }
1661
+ };
1662
+ },
1663
+ render(artifact) {
1664
+ const meta = metaRecord(artifact);
1665
+ const sha = typeof meta.sha === "string" ? meta.sha.slice(0, 7) : void 0;
1666
+ const branch = typeof meta.branch === "string" ? meta.branch : void 0;
1667
+ return {
1668
+ title: artifact.label,
1669
+ ...sha || branch ? { subtitle: [sha, branch].filter(Boolean).join(" · ") } : {}
1670
+ };
1671
+ }
1672
+ };
1673
+ const branchKind = {
1674
+ kind: ARTIFACT_KINDS.branch,
1675
+ label: "Branch",
1676
+ detect({ tool, input, isError }) {
1677
+ if (isError) return void 0;
1678
+ const command = shellCommand(tool, input);
1679
+ if (!command) return void 0;
1680
+ const names = /* @__PURE__ */ new Set();
1681
+ for (const segment of command.split(/&&|;|\|\|/)) {
1682
+ const t = tokens(segment);
1683
+ const git = t.indexOf("git");
1684
+ if (git === -1) continue;
1685
+ const sub = t[git + 1];
1686
+ if (sub === "checkout" || sub === "switch") {
1687
+ const flag = t.findIndex((x, i) => i > git + 1 && /^-[bc]$/i.test(x));
1688
+ const name = flag !== -1 ? t[flag + 1] : void 0;
1689
+ if (name && !name.startsWith("-")) names.add(name);
1690
+ } else if (sub === "push") {
1691
+ const flag = t.findIndex((x, i) => i > git + 1 && (x === "-u" || x === "--set-upstream"));
1692
+ const name = flag !== -1 ? t[flag + 2] : void 0;
1693
+ if (name && !name.startsWith("-") && !name.includes(":") && name !== "HEAD" && name !== "@") names.add(name);
1694
+ }
1695
+ }
1696
+ if (names.size === 0) return void 0;
1697
+ return [...names].map((name) => ({
1698
+ uri: `git:branch:${name}`,
1699
+ label: name,
1700
+ meta: { name }
1701
+ }));
1702
+ },
1703
+ render: (artifact) => ({ title: artifact.label })
1704
+ };
1705
+ const fileKind = {
1706
+ kind: ARTIFACT_KINDS.file,
1707
+ label: "File",
1708
+ detect({ tool, input, result, isError }) {
1709
+ if (isError || tool !== "write_file") return void 0;
1710
+ const path = typeof input.path === "string" ? input.path : void 0;
1711
+ if (!path || !resultText(result).startsWith(`Created ${path} (`)) return void 0;
1712
+ return {
1713
+ uri: `file:${path}`,
1714
+ label: path
1715
+ };
1716
+ },
1717
+ render(artifact) {
1718
+ const path = artifact.uri.startsWith("file://") ? artifact.uri.slice(7) : artifact.uri;
1719
+ return {
1720
+ title: artifact.label,
1721
+ ...path !== artifact.label ? { subtitle: path } : {},
1722
+ href: path
1723
+ };
1724
+ }
1725
+ };
1726
+ /**
1727
+ * Not a kind of its own: the producer behind the `record_artifact` tool.
1728
+ * Reads the tool INPUT (the tool has no store access) and emits it as-is;
1729
+ * `kind` is whatever the model said, so a custom `deployment` lands under
1730
+ * `deployment` and renders via the fallback until an extension registers
1731
+ * it. Only fires on a successful call — a rejected input never records.
1732
+ */
1733
+ const recordArtifactKind = {
1734
+ kind: "record_artifact",
1735
+ label: "Recorded",
1736
+ detect({ tool, input, result, isError }) {
1737
+ if (isError || tool !== "record_artifact") return void 0;
1738
+ if (!resultText(result).startsWith("Recorded ")) return void 0;
1739
+ const kind = typeof input.kind === "string" ? input.kind : void 0;
1740
+ const uri = typeof input.uri === "string" ? input.uri : void 0;
1741
+ if (!kind || !uri) return void 0;
1742
+ return {
1743
+ kind,
1744
+ uri,
1745
+ ...typeof input.label === "string" ? { label: input.label } : { label: uri },
1746
+ ...input.meta !== void 0 && input.meta !== null && typeof input.meta === "object" ? { meta: input.meta } : {}
1747
+ };
1748
+ }
1749
+ };
1750
+ const BUILTIN_ARTIFACT_KINDS = [
1751
+ pullRequestKind,
1752
+ branchKind,
1753
+ commitKind,
1754
+ fileKind
1755
+ ];
1756
+ /** Every built-in detector: the kinds plus the `record_artifact` producer. */
1757
+ const BUILTIN_ARTIFACT_DETECTORS = [...BUILTIN_ARTIFACT_KINDS, recordArtifactKind];
1758
+ /**
1759
+ * Resolve the registration for a stored artifact's kind: the active
1760
+ * registry wins (an extension may refine a built-in's presentation), then
1761
+ * built-ins, then `undefined` — callers fall back to `label` + `uri`.
1762
+ */
1763
+ function resolveArtifactKind(kind, registry) {
1764
+ const fromRegistry = registry?.artifactKinds.find((c) => c.value.kind === kind)?.value;
1765
+ if (fromRegistry) return fromRegistry;
1766
+ return BUILTIN_ARTIFACT_KINDS.find((k) => k.kind === kind);
1767
+ }
1768
+ /**
1769
+ * Row presentation with the guaranteed fallback. A `render` that throws
1770
+ * or returns nothing degrades to `label` + `uri` — a stored artifact is
1771
+ * never invisible because its kind's extension is gone or buggy.
1772
+ */
1773
+ function renderArtifactRow(artifact, registry) {
1774
+ const registration = resolveArtifactKind(artifact.kind, registry);
1775
+ if (registration?.render) try {
1776
+ const row = registration.render(artifact);
1777
+ if (row && typeof row.title === "string" && row.title) return row;
1778
+ } catch {}
1779
+ return {
1780
+ title: artifact.label,
1781
+ ...artifact.uri !== artifact.label ? { subtitle: artifact.uri } : {},
1782
+ .../^(?:https?:\/\/|file:\/\/)/.test(artifact.uri) ? { href: artifact.uri.replace(/^file:\/\//, "") } : {}
1783
+ };
1784
+ }
1785
+ /**
1786
+ * Lazy refresh: run the kind's `resolve()` and return the artifact with
1787
+ * the patch applied (`updatedAt` bumped), or `undefined` when the kind has
1788
+ * no resolver, the resolver declined, or it threw. Callers persist the
1789
+ * result through their store / RPC — this never writes.
1790
+ */
1791
+ async function refreshArtifact(artifact, registry) {
1792
+ const registration = resolveArtifactKind(artifact.kind, registry);
1793
+ if (!registration?.resolve) return void 0;
1794
+ try {
1795
+ const patch = await registration.resolve(artifact);
1796
+ if (!patch || patch.label === void 0 && patch.meta === void 0) return void 0;
1797
+ return {
1798
+ ...artifact,
1799
+ ...patch.label !== void 0 ? { label: patch.label } : {},
1800
+ ...patch.meta !== void 0 ? { meta: patch.meta } : {},
1801
+ updatedAt: Date.now()
1802
+ };
1803
+ } catch {
1804
+ return;
1805
+ }
1806
+ }
1807
+ /** Human label for a kind (`Pull request`), falling back to the raw kind string. */
1808
+ function artifactKindLabel(kind, registry) {
1809
+ return resolveArtifactKind(kind, registry)?.label ?? kind;
1810
+ }
1811
+ function metaRecord(artifact) {
1812
+ const meta = artifact.meta;
1813
+ return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
1814
+ }
1815
+ //#endregion
1469
1816
  //#region src/extensions/bus.ts
1470
1817
  /** Serialized-payload cap, enforced at emit AND at the daemon trust boundary. */
1471
1818
  const EXTENSION_BUS_MAX_PAYLOAD_BYTES = 64 * 1024;
@@ -2426,7 +2773,7 @@ const KEYBINDING_DEFS = [
2426
2773
  action: "openEffortPicker",
2427
2774
  default: "ctrl+l",
2428
2775
  label: "options",
2429
- description: "open the model-options picker — reasoning effort + feature toggles (e.g. fast mode)",
2776
+ description: "open the model options picker: reasoning effort and feature toggles such as fast mode",
2430
2777
  group: "Global"
2431
2778
  },
2432
2779
  {
@@ -2436,6 +2783,13 @@ const KEYBINDING_DEFS = [
2436
2783
  description: "open the active run's todo list (the agent's `todowrite` checkpoints)",
2437
2784
  group: "Global"
2438
2785
  },
2786
+ {
2787
+ action: "openArtifacts",
2788
+ default: "ctrl+shift+a",
2789
+ label: "artifacts",
2790
+ description: "open the session's artifacts (pull requests, branches, commits, files). Same as /artifacts",
2791
+ group: "Global"
2792
+ },
2439
2793
  {
2440
2794
  action: "openContextPanel",
2441
2795
  default: "ctrl+shift+g",
@@ -2445,7 +2799,7 @@ const KEYBINDING_DEFS = [
2445
2799
  },
2446
2800
  {
2447
2801
  action: "openMainSession",
2448
- default: "ctrl+u",
2802
+ default: "ctrl+shift+m",
2449
2803
  label: "Jump to main session",
2450
2804
  description: "jump to (or lazily create) the pinned \"Main\" orchestration session for the current directory; requires the \"Main session (orchestration)\" and \"Use daemon runtime\" settings",
2451
2805
  group: "Global"
@@ -2461,7 +2815,7 @@ const KEYBINDING_DEFS = [
2461
2815
  action: "openKeybindings",
2462
2816
  default: "ctrl+y",
2463
2817
  label: "keybindings",
2464
- description: "open the keybindings panel — list every shortcut and jump to the keybindings.json file",
2818
+ description: "open the keybindings panel: every shortcut, and a jump to keybindings.json",
2465
2819
  group: "Global"
2466
2820
  },
2467
2821
  {
@@ -2524,7 +2878,7 @@ const KEYBINDING_DEFS = [
2524
2878
  action: "dropQueuedMessage",
2525
2879
  default: "backspace",
2526
2880
  label: "drop",
2527
- description: "remove the selected queued message — exits back to the prompt when the queue empties (the big \"remove\" key works everywhere: backspace on Win/Linux, the laptop key labeled \"delete\" on Mac, which sends backspace)",
2881
+ description: "remove the selected queued message. Returns to the prompt when the queue empties",
2528
2882
  group: "Message queue"
2529
2883
  },
2530
2884
  {
@@ -2924,7 +3278,7 @@ function isBareCharExtensionBinding(spec) {
2924
3278
  */
2925
3279
  function renderDefaultFile() {
2926
3280
  const lines = [];
2927
- lines.push("// Zidane keybindings — edit values below to customize.");
3281
+ lines.push("// Zidane keybindings. Edit the values below to customize.");
2928
3282
  lines.push("//");
2929
3283
  lines.push("// Format:");
2930
3284
  lines.push("// \"<action>\": \"<modifier>+<modifier>+<key>\"");
@@ -2999,6 +3353,78 @@ function stripJsonComments(input) {
2999
3353
  return out;
3000
3354
  }
3001
3355
  //#endregion
3356
+ //#region src/tools/record-artifact.ts
3357
+ const RECORD_ARTIFACT_TOOL = "record_artifact";
3358
+ const recordArtifact = {
3359
+ isConcurrencySafe: true,
3360
+ spec: {
3361
+ name: RECORD_ARTIFACT_TOOL,
3362
+ description: [
3363
+ "Record an artifact this session produced so it is tracked on the session (visible via /artifacts).",
3364
+ "Pull requests from `gh pr create`, commits, branches and files created with `write_file` are detected automatically — call this only for outputs the automatic detection cannot see: a PR opened from a background shell task, a deployment, an issue, a release, a file created by a script.",
3365
+ "Idempotent per (kind, uri): recording the same artifact again refreshes its label/meta."
3366
+ ].join("\n"),
3367
+ inputSchema: {
3368
+ type: "object",
3369
+ properties: {
3370
+ kind: {
3371
+ type: "string",
3372
+ description: "Artifact kind: `pull_request`, `branch`, `commit`, `file`, or a custom kind such as `deployment` / `issue` (`[\\w.:-]+`)."
3373
+ },
3374
+ uri: {
3375
+ type: "string",
3376
+ description: "Canonical locator: a URL (`https://github.com/o/r/pull/12`), `file:<path>` (relative paths resolve against the working directory), `git:commit:<sha>`, `git:branch:<name>`."
3377
+ },
3378
+ label: {
3379
+ type: "string",
3380
+ description: "Short human label. Defaults to the uri."
3381
+ },
3382
+ meta: {
3383
+ type: "object",
3384
+ description: "Optional small JSON payload (≤ 16 KB) — e.g. `{ \"number\": 12, \"state\": \"open\" }`."
3385
+ }
3386
+ },
3387
+ required: ["kind", "uri"],
3388
+ additionalProperties: false
3389
+ }
3390
+ },
3391
+ async execute(input) {
3392
+ const kind = typeof input.kind === "string" ? input.kind.trim() : "";
3393
+ const uri = typeof input.uri === "string" ? input.uri.trim() : "";
3394
+ if (!kind || !ARTIFACT_KIND_PATTERN.test(kind)) return `record_artifact error: kind must match [\\w.:-]+ (got ${JSON.stringify(input.kind)}).`;
3395
+ if (!uri) return "record_artifact error: uri is required.";
3396
+ if (uri.length > 2048) return `record_artifact error: uri exceeds ${ARTIFACT_URI_MAX_CHARS} characters.`;
3397
+ const label = typeof input.label === "string" && input.label.trim() ? input.label.trim() : uri;
3398
+ const clipped = label.length > 512 ? `${label.slice(0, 511)}…` : label;
3399
+ return `Recorded ${kind} artifact: ${clipped}${clipped !== uri ? ` (${uri})` : ""}`;
3400
+ }
3401
+ };
3402
+ //#endregion
3403
+ //#region src/presets/artifacts.ts
3404
+ /**
3405
+ * Built-in artifact detection — records pull requests, branches, commits
3406
+ * and created files a session produces (see `session/artifacts.ts`).
3407
+ *
3408
+ * Opt-in by composition, not baked into `basic`: SDK consumers who never
3409
+ * spread this preset see zero behavior change, while the TUI / daemon
3410
+ * hosts compose it under the profile preset so every chat session gets
3411
+ * detection. Extension-registered kinds ride the extension registry's
3412
+ * hooks instead; both paths share `createArtifactDetectHook`.
3413
+ *
3414
+ * Also contributes the `record_artifact` tool — the explicit path for
3415
+ * outputs detection can't see (background-shell PRs, deployments, issues).
3416
+ */
3417
+ const detectHooks = BUILTIN_ARTIFACT_DETECTORS.map((kind) => createArtifactDetectHook(kind, { warn: (message, fields) => {
3418
+ if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/artifacts] ${message} ${JSON.stringify(fields)}\n`);
3419
+ } })).filter((hook) => hook !== void 0);
3420
+ var artifacts_default = definePreset({
3421
+ tools: { [recordArtifact.spec.name]: recordArtifact },
3422
+ hooks: {
3423
+ "tool:transform": detectHooks,
3424
+ "child:tool:transform": detectHooks
3425
+ }
3426
+ });
3427
+ //#endregion
3002
3428
  //#region src/tools/path-suggest.ts
3003
3429
  function entryPath$2(entry) {
3004
3430
  return typeof entry === "string" ? entry : entry.path;
@@ -5620,7 +6046,8 @@ const BUBBLED_EVENTS = [
5620
6046
  "background:start",
5621
6047
  "background:exit",
5622
6048
  "background:reassign",
5623
- "turn:after"
6049
+ "turn:after",
6050
+ "artifact:recorded"
5624
6051
  ];
5625
6052
  const BUBBLED_MUTABLE_EVENTS = [
5626
6053
  "content-ref:resolve",
@@ -5644,7 +6071,8 @@ const CHILD_EVENT_NAME = {
5644
6071
  "background:start": "child:background:start",
5645
6072
  "background:exit": "child:background:exit",
5646
6073
  "background:reassign": "child:background:reassign",
5647
- "turn:after": "child:turn:after"
6074
+ "turn:after": "child:turn:after",
6075
+ "artifact:recorded": "child:artifact:recorded"
5648
6076
  };
5649
6077
  const CHILD_MUTABLE_EVENT_NAME = {
5650
6078
  "content-ref:resolve": "content-ref:resolve",
@@ -6832,6 +7260,49 @@ function createExtensionHostRuntimeBridge() {
6832
7260
  };
6833
7261
  }
6834
7262
  /**
7263
+ * Build the `ctx.artifacts` facade over a host-runtime bridge. Read-only;
7264
+ * every call re-resolves the scope (session switches swap it). Detached,
7265
+ * or a store without artifact support, answers `[]` — the same honest
7266
+ * degradation as `ctx.sessions`.
7267
+ */
7268
+ function createArtifactsSurface(resolve) {
7269
+ return {
7270
+ list: async (query) => {
7271
+ const scope = resolve();
7272
+ if (!scope?.store.listArtifacts) return [];
7273
+ const mode = query?.scope ?? "session";
7274
+ const base = {
7275
+ ...query?.kind !== void 0 ? { kind: query.kind } : {},
7276
+ ...query?.since !== void 0 ? { since: query.since } : {},
7277
+ ...query?.limit !== void 0 ? { limit: query.limit } : {}
7278
+ };
7279
+ if (mode === "session") {
7280
+ if (!scope.sessionId) return [];
7281
+ return scope.store.listArtifacts({
7282
+ ...base,
7283
+ sessionId: scope.sessionId
7284
+ });
7285
+ }
7286
+ if (mode === "project") {
7287
+ if (!scope.projectRoot) return [];
7288
+ return scope.store.listArtifacts({
7289
+ ...base,
7290
+ projectRoot: scope.projectRoot
7291
+ });
7292
+ }
7293
+ return scope.store.listArtifacts(base);
7294
+ },
7295
+ find: async (kind, uri) => {
7296
+ const scope = resolve();
7297
+ if (!scope?.store.listArtifacts) return [];
7298
+ return scope.store.listArtifacts({
7299
+ kind,
7300
+ uri
7301
+ });
7302
+ }
7303
+ };
7304
+ }
7305
+ /**
6835
7306
  * Build the `ctx.workspace` facade over a host-runtime bridge. Every call
6836
7307
  * re-resolves the live pair (see {@link ExtensionWorkspacePair}); detached
6837
7308
  * calls reject with a message naming the guard.
@@ -6997,40 +7468,7 @@ async function buildExtensionRegistry(loaded, options = {}) {
6997
7468
  const isDisabled = disabled.has(entry.name);
6998
7469
  const logger = createLogger(sink, { extension: entry.name });
6999
7470
  const gate = { open: true };
7000
- const perExtSinks = {
7001
- tools: createRecord(),
7002
- hooks: {},
7003
- mcpServers: [],
7004
- skills: [],
7005
- plugins: [],
7006
- systemFragments: [],
7007
- contextBreakdownSections: [],
7008
- contextPanelSections: [],
7009
- toolResultMetadata: [],
7010
- runTransforms: [],
7011
- behaviorTransforms: [],
7012
- providerMiddlewares: [],
7013
- toolCatalogTransforms: [],
7014
- mcpConfigTransforms: [],
7015
- skillPolicies: [],
7016
- promptTransforms: [],
7017
- completionProviders: [],
7018
- inlineAutocompleteProviders: [],
7019
- commands: [],
7020
- themes: createRecord(),
7021
- agents: createRecord(),
7022
- providers: createRecord(),
7023
- subagents: createRecord(),
7024
- subagentOverrides: createRecord(),
7025
- configCategories: createRecord(),
7026
- configItems: createRecord(),
7027
- keybindings: [],
7028
- footerHints: [],
7029
- modals: [],
7030
- renderers: [],
7031
- toolRenderers: [],
7032
- guiPanels: []
7033
- };
7471
+ const perExtSinks = createEmptySinks();
7034
7472
  const abort = new AbortController();
7035
7473
  const hudWriter = isDisabled ? void 0 : options.hud?.claim(entry.name);
7036
7474
  if (hudWriter) hudWriters.push(hudWriter);
@@ -7172,6 +7610,7 @@ function captureContributions(perExtSinks) {
7172
7610
  contextBreakdownSections: perExtSinks.contextBreakdownSections,
7173
7611
  contextPanelSections: perExtSinks.contextPanelSections,
7174
7612
  toolResultMetadata: perExtSinks.toolResultMetadata,
7613
+ artifactKinds: perExtSinks.artifactKinds,
7175
7614
  runTransforms: perExtSinks.runTransforms,
7176
7615
  behaviorTransforms: perExtSinks.behaviorTransforms,
7177
7616
  providerMiddlewares: perExtSinks.providerMiddlewares,
@@ -7194,7 +7633,8 @@ function captureContributions(perExtSinks) {
7194
7633
  footerHints: perExtSinks.footerHints,
7195
7634
  modals: perExtSinks.modals,
7196
7635
  renderers: perExtSinks.renderers,
7197
- toolRenderers: perExtSinks.toolRenderers
7636
+ toolRenderers: perExtSinks.toolRenderers,
7637
+ artifactRenderers: perExtSinks.artifactRenderers
7198
7638
  },
7199
7639
  gui: { panels: perExtSinks.guiPanels }
7200
7640
  };
@@ -7250,6 +7690,8 @@ function mergeContributions(active, inactive = [], options = {}) {
7250
7690
  const contextBreakdownSections = [];
7251
7691
  const contextPanelSections = [];
7252
7692
  const toolResultMetadata = [];
7693
+ const artifactKinds = [];
7694
+ const seenArtifactKinds = /* @__PURE__ */ new Set();
7253
7695
  const runTransforms = [];
7254
7696
  const behaviorTransforms = [];
7255
7697
  const providerMiddlewares = [];
@@ -7276,6 +7718,8 @@ function mergeContributions(active, inactive = [], options = {}) {
7276
7718
  const renderers = [];
7277
7719
  const seenRendererIds = /* @__PURE__ */ new Set();
7278
7720
  const toolRenderers = [];
7721
+ const artifactRenderers = [];
7722
+ const seenArtifactRendererKinds = /* @__PURE__ */ new Set();
7279
7723
  const guiPanels = [];
7280
7724
  for (const entry of active) {
7281
7725
  const c = entry.contributions;
@@ -7293,6 +7737,11 @@ function mergeContributions(active, inactive = [], options = {}) {
7293
7737
  contextBreakdownSections.push(...c.contextBreakdownSections);
7294
7738
  contextPanelSections.push(...c.contextPanelSections);
7295
7739
  toolResultMetadata.push(...c.toolResultMetadata);
7740
+ for (const contribution of c.artifactKinds) {
7741
+ if (seenArtifactKinds.has(contribution.value.kind)) continue;
7742
+ seenArtifactKinds.add(contribution.value.kind);
7743
+ artifactKinds.push(contribution);
7744
+ }
7296
7745
  runTransforms.push(...c.runTransforms);
7297
7746
  behaviorTransforms.push(...c.behaviorTransforms);
7298
7747
  providerMiddlewares.push(...c.providerMiddlewares);
@@ -7343,6 +7792,11 @@ function mergeContributions(active, inactive = [], options = {}) {
7343
7792
  renderers.push(renderer);
7344
7793
  }
7345
7794
  toolRenderers.push(...c.tui.toolRenderers);
7795
+ for (const renderer of c.tui.artifactRenderers) {
7796
+ if (seenArtifactRendererKinds.has(renderer.kind)) continue;
7797
+ seenArtifactRendererKinds.add(renderer.kind);
7798
+ artifactRenderers.push(renderer);
7799
+ }
7346
7800
  guiPanels.push(...c.gui.panels);
7347
7801
  }
7348
7802
  for (const entry of inactive) {
@@ -7367,6 +7821,7 @@ function mergeContributions(active, inactive = [], options = {}) {
7367
7821
  contextBreakdownSections,
7368
7822
  contextPanelSections,
7369
7823
  toolResultMetadata,
7824
+ artifactKinds,
7370
7825
  runTransforms,
7371
7826
  behaviorTransforms,
7372
7827
  providerMiddlewares,
@@ -7389,7 +7844,8 @@ function mergeContributions(active, inactive = [], options = {}) {
7389
7844
  footerHints,
7390
7845
  modals,
7391
7846
  renderers,
7392
- toolRenderers
7847
+ toolRenderers,
7848
+ artifactRenderers
7393
7849
  },
7394
7850
  guiContributions: { panels: guiPanels },
7395
7851
  dispose: options.dispose ?? (async () => {})
@@ -7541,6 +7997,45 @@ function partitionExtensionCatalog(loaded, inactive) {
7541
7997
  allNames: [...toggleable.map((e) => e.name), ...errored.map((e) => e.name)]
7542
7998
  };
7543
7999
  }
8000
+ /** Fresh, empty per-extension sinks (one set per `setup` call). */
8001
+ function createEmptySinks() {
8002
+ return {
8003
+ tools: createRecord(),
8004
+ hooks: {},
8005
+ mcpServers: [],
8006
+ skills: [],
8007
+ plugins: [],
8008
+ systemFragments: [],
8009
+ contextBreakdownSections: [],
8010
+ contextPanelSections: [],
8011
+ toolResultMetadata: [],
8012
+ artifactKinds: [],
8013
+ runTransforms: [],
8014
+ behaviorTransforms: [],
8015
+ providerMiddlewares: [],
8016
+ toolCatalogTransforms: [],
8017
+ mcpConfigTransforms: [],
8018
+ skillPolicies: [],
8019
+ promptTransforms: [],
8020
+ completionProviders: [],
8021
+ inlineAutocompleteProviders: [],
8022
+ commands: [],
8023
+ themes: createRecord(),
8024
+ agents: createRecord(),
8025
+ providers: createRecord(),
8026
+ subagents: createRecord(),
8027
+ subagentOverrides: createRecord(),
8028
+ configCategories: createRecord(),
8029
+ configItems: createRecord(),
8030
+ keybindings: [],
8031
+ footerHints: [],
8032
+ modals: [],
8033
+ renderers: [],
8034
+ toolRenderers: [],
8035
+ artifactRenderers: [],
8036
+ guiPanels: []
8037
+ };
8038
+ }
7544
8039
  function makeContext(opts) {
7545
8040
  const { definition, source, dir, logger, gate, signal, sinks, readSettings, paths, hudWriter, promptBridge, hostRuntime, busChannel, disabled = false } = opts;
7546
8041
  function guardOpen(method) {
@@ -7824,6 +8319,32 @@ function makeContext(opts) {
7824
8319
  }
7825
8320
  sinks.toolResultMetadata.push(ordered(registration));
7826
8321
  };
8322
+ const registerArtifactKind = (registration) => {
8323
+ if (!guardOpen("registerArtifactKind")) return;
8324
+ if (!registration || typeof registration.kind !== "string" || !ARTIFACT_KIND_PATTERN.test(registration.kind)) {
8325
+ logger.warn("registerArtifactKind: kind must match [\\w.:-]+", { kind: String(registration?.kind) });
8326
+ return;
8327
+ }
8328
+ if (typeof registration.label !== "string" || !registration.label) {
8329
+ logger.warn("registerArtifactKind: missing label", { kind: registration.kind });
8330
+ return;
8331
+ }
8332
+ for (const fn of [
8333
+ "detect",
8334
+ "validateMeta",
8335
+ "render",
8336
+ "resolve"
8337
+ ]) if (registration[fn] !== void 0 && typeof registration[fn] !== "function") {
8338
+ logger.warn(`registerArtifactKind: ${fn} must be a function`, { kind: registration.kind });
8339
+ return;
8340
+ }
8341
+ sinks.artifactKinds.push(ordered(registration));
8342
+ const hook = createArtifactDetectHook(registration, { warn: (message, fields) => logger.warn(message, fields) });
8343
+ if (hook) {
8344
+ (sinks.hooks["tool:transform"] ??= []).push(hook);
8345
+ (sinks.hooks["child:tool:transform"] ??= []).push(hook);
8346
+ }
8347
+ };
7827
8348
  const registerRunTransform = (transform) => {
7828
8349
  if (!guardOpen("registerRunTransform")) return;
7829
8350
  if (typeof transform !== "function") {
@@ -8109,6 +8630,21 @@ function makeContext(opts) {
8109
8630
  owner: definition.name
8110
8631
  });
8111
8632
  };
8633
+ const registerArtifactRenderer = (registration) => {
8634
+ if (!guardOpen("tui.registerArtifactRenderer")) return;
8635
+ if (!registration || typeof registration.kind !== "string" || !registration.kind) {
8636
+ logger.warn("tui.registerArtifactRenderer: missing kind");
8637
+ return;
8638
+ }
8639
+ if (typeof registration.renderDetail !== "function") {
8640
+ logger.warn("tui.registerArtifactRenderer: renderDetail must be a function", { kind: registration.kind });
8641
+ return;
8642
+ }
8643
+ sinks.artifactRenderers.push({
8644
+ ...registration,
8645
+ owner: definition.name
8646
+ });
8647
+ };
8112
8648
  const registerPanel = (panel) => {
8113
8649
  if (!guardOpen("gui.registerPanel")) return;
8114
8650
  if (!panel || typeof panel.id !== "string") {
@@ -8144,6 +8680,8 @@ function makeContext(opts) {
8144
8680
  registerContextBreakdownSection,
8145
8681
  registerContextPanelSection,
8146
8682
  registerToolResultMetadata,
8683
+ registerArtifactKind,
8684
+ artifacts: createArtifactsSurface(() => hostRuntime?.current()?.artifacts?.() ?? null),
8147
8685
  registerRunTransform,
8148
8686
  registerBehaviorTransform,
8149
8687
  registerProviderMiddleware,
@@ -8164,6 +8702,7 @@ function makeContext(opts) {
8164
8702
  registerModal,
8165
8703
  registerRenderer,
8166
8704
  registerToolRenderer,
8705
+ registerArtifactRenderer,
8167
8706
  notify: (text, opts) => {
8168
8707
  hudWriter?.notify(text, opts);
8169
8708
  },
@@ -8794,6 +9333,7 @@ const EMPTY_EXTENSION_REGISTRY = Object.freeze({
8794
9333
  contextBreakdownSections: Object.freeze([]),
8795
9334
  contextPanelSections: Object.freeze([]),
8796
9335
  toolResultMetadata: Object.freeze([]),
9336
+ artifactKinds: Object.freeze([]),
8797
9337
  runTransforms: Object.freeze([]),
8798
9338
  behaviorTransforms: Object.freeze([]),
8799
9339
  providerMiddlewares: Object.freeze([]),
@@ -8816,7 +9356,8 @@ const EMPTY_EXTENSION_REGISTRY = Object.freeze({
8816
9356
  footerHints: Object.freeze([]),
8817
9357
  modals: Object.freeze([]),
8818
9358
  renderers: Object.freeze([]),
8819
- toolRenderers: Object.freeze([])
9359
+ toolRenderers: Object.freeze([]),
9360
+ artifactRenderers: Object.freeze([])
8820
9361
  }),
8821
9362
  guiContributions: Object.freeze({ panels: Object.freeze([]) }),
8822
9363
  dispose: async () => {}
@@ -13798,6 +14339,12 @@ async function emitToolResult(ctx, params) {
13798
14339
  const sanitizedMetadata = sanitizeToolResultMetadata(transformCtx.metadata);
13799
14340
  const metadata = sanitizedMetadata.metadata;
13800
14341
  if (sanitizedMetadata.warnings.length > 0 && process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/loop] sanitized tool metadata for ${name}/${callId}: ${sanitizedMetadata.warnings.join("; ")}\n`);
14342
+ await recordArtifactsFromMetadata(ctx, {
14343
+ turnId,
14344
+ callId,
14345
+ name,
14346
+ metadata: transformCtx.metadata
14347
+ });
13801
14348
  const finalOutcome = isError && outcome && outcome !== "failed" ? "failed" : outcome;
13802
14349
  const threshold = ctx.behavior.persistThreshold;
13803
14350
  const persistDir = ctx.behavior.persistDir;
@@ -13834,6 +14381,58 @@ async function emitToolResult(ctx, params) {
13834
14381
  ...metadata ? { metadata } : {}
13835
14382
  };
13836
14383
  }
14384
+ /** Hard bound on artifacts a single tool result may emit; the rest are dropped. */
14385
+ const MAX_ARTIFACTS_PER_TOOL_RESULT = 50;
14386
+ async function recordArtifactsFromMetadata(ctx, params) {
14387
+ const raw = params.metadata?.[ARTIFACTS_METADATA_KEY];
14388
+ if (!Array.isArray(raw) || raw.length === 0 || !ctx.session) return;
14389
+ const inputs = [];
14390
+ for (const entry of raw.slice(0, MAX_ARTIFACTS_PER_TOOL_RESULT)) {
14391
+ const normalized = normalizeArtifactInput(entry);
14392
+ if (normalized) inputs.push(canonicalizeFileArtifact(normalized, ctx));
14393
+ }
14394
+ for (const input of inputs) try {
14395
+ const artifact = await ctx.session.addArtifact(input, {
14396
+ runId: ctx.runId,
14397
+ turnId: params.turnId
14398
+ });
14399
+ if (artifact) await ctx.hooks.callHook("artifact:recorded", {
14400
+ runId: ctx.runId,
14401
+ turnId: params.turnId,
14402
+ callId: params.callId,
14403
+ name: params.name,
14404
+ artifact
14405
+ });
14406
+ } catch (err) {
14407
+ if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/loop] artifact record failed for ${params.name}/${params.callId}: ${String(err)}\n`);
14408
+ }
14409
+ }
14410
+ /**
14411
+ * `file:<path>` → `file://<abs path>`, resolved against the execution cwd
14412
+ * (detectors only see the model's input, which is usually relative). The
14413
+ * artifact's `projectRoot` is the git root the file actually landed in —
14414
+ * a worktree or sibling checkout differs from the session's root — but
14415
+ * only when the execution runs on the host: under docker / remote the
14416
+ * path isn't on this filesystem, so we keep the session default.
14417
+ */
14418
+ function canonicalizeFileArtifact(input, ctx) {
14419
+ if (input.kind !== ARTIFACT_KINDS.file) return input;
14420
+ let path = input.uri;
14421
+ if (path.startsWith("file://")) path = path.slice(7);
14422
+ else if (path.startsWith("file:")) path = path.slice(5);
14423
+ if (!path) return input;
14424
+ const local = ctx.handle.type === "process";
14425
+ const abs = local ? resolve(ctx.handle.cwd, path) : posix.resolve(ctx.handle.cwd, path);
14426
+ const out = {
14427
+ ...input,
14428
+ uri: `file://${abs}`
14429
+ };
14430
+ if (!out.projectRoot && local) {
14431
+ const root = findGitRoot(dirname(abs));
14432
+ if (root) out.projectRoot = root;
14433
+ }
14434
+ return out;
14435
+ }
13837
14436
  /** Default cap on in-flight tools per turn. Mirrors Claude Code's `CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY`. */
13838
14437
  const DEFAULT_MAX_CONCURRENT_TOOLS = 10;
13839
14438
  /** Canonical name of the shell tool — referenced for cascade-cancel semantics. */
@@ -14975,6 +15574,8 @@ const HOOK_EVENT_SET = /* @__PURE__ */ new Set([
14975
15574
  "tool:transform",
14976
15575
  "tool:unknown",
14977
15576
  "tool-state:commit-failed",
15577
+ "artifact:recorded",
15578
+ "child:artifact:recorded",
14978
15579
  "validation:reject",
14979
15580
  "validation:coerce",
14980
15581
  "context:transform",
@@ -17216,6 +17817,6 @@ function createAgent(rawOptions) {
17216
17817
  };
17217
17818
  }
17218
17819
  //#endregion
17219
- export { installExtensionFromSpec as $, groupBindings as $t, selectFilesFromSession as A, EXTENSION_BUS_MAX_PAYLOAD_BYTES as An, createSkillsReadTool as At, buildUpToCompactPrompt as B, stableStringify as Bn, matchModelEntries as Bt, resolveLegacySessionDir as C, coerceExtensionConfigInput as Cn, waitTask as Ct, resolveTasksDir as D, resolveExtensionConfigValue as Dn, reserveChildLabel as Dt, resolveSessionDir as E, resolveExtensionConfig as En, isSpawnTool as Et, TRAILER as F, effectiveContextWindow as Fn, readFile$1 as Ft, summaryToTurn as G, readStateKey as Gn, edit as Gt, anchorPreviewFor as H, hashContent as Hn, createInteractionTool as Ht, buildCompactPrompt as I, shouldAutoCompact as In, looksBinary as It, CompactPromptTooLongError as J, KEYBINDING_DEFS as Jt, truncateHeadForPtlRetry as K, resolveReadStateMap as Kn, validateToolArgs as Kt, buildFromCompactPrompt as L, defaultRepeatGuardNormalize as Ln, multiEdit as Lt, compactConversation as M, serializedPayloadBytes as Mn, createShellTool as Mt, BASE_INSTRUCTIONS as N, AUTO_COMPACT_MIN_GROWTH_FRACTION as Nn, shell as Nt, buildPostCompactAttachments as O, sanitizeExtensionConfigActionPatch as On, createSkillsUseTool as Ot, NO_TOOLS_PREAMBLE as P, OUTPUT_RESERVE_TOKENS as Pn, tailTruncate as Pt, fetchExtensionSource as Q, formatBindingForDisplay as Qt, buildFullCompactPrompt as R, defaultRepeatGuardTracked as Rn, createModelSearchTool as Rt, resolveLegacyPersistDir as S, clampExtensionConfigNumber as Sn, WAIT_TASK_TIMED_OUT_PREFIX as St, resolvePersistDir as T, isValidExtensionConfigValue as Tn, createSpawnTool as Tt, sliceForCompaction as U, hydrateReadStateFromSession as Un, grep as Ut, ANCHOR_PREVIEW_MAX_CHARS as V, getReadState as Vn, listFiles as Vt, stripImagesFromTurns as W, hydrateReadStateFromSessionAsync as Wn, glob as Wt, createExtensionPromptBridge as X, KEYBINDING_KEY_COL_WIDTH as Xt, EMPTY_EXTENSION_REGISTRY as Y, KEYBINDING_DEF_BY_ACTION as Yt, ExtensionInstallError as Z, ensureKeybindingsFile as Zt, buildPersistedStub as _, parseExtensionScanPathEnv as _n, composePresets as _t, stagedToolState as a, readKeybindingsRaw as an, createExtensionHud as at, migrateSessionArtifactDirs as b, setDebugLogSink as bn, basic_default as bt, INTERRUPT_MESSAGE_FOR_TOOL_USE as c, ALTERNATE_CRED_KEY_SUFFIX as cn, buildExtensionRegistry as ct, TOOL_USE_SKIPPED_MESSAGE as d, registerExtensionHostModules as dn, filterExtensionRegistry as dt, keybindingsPath as en, listExtensionCandidates as et, applyCompactSummaryCutoff as f, ENTRY_CANDIDATES as fn, filterRegistryPlugins as ft, PERSISTENCE_PREVIEW_BYTES as g, isExtensionDefinition as gn, createWorkspaceSurface as gt, PERSISTED_STUB_PREFIX as h, inlineLoadedExtension as hn, createExtensionHostRuntimeBridge as ht, isToolStateOps as i, readKeybindings as in, updateInstalledExtension as it, selectRecentFiles as j, createExtensionBus as jn, shellKill as jt, selectFilesFromReadState as k, toWireConfigSchema as kn, createSkillsRunScriptTool as kt, SHELL_CASCADE_CANCEL_MESSAGE as l, alternateCredKey as ln, composeExtensionPreset as lt, renderStaleReadNotice as m, discoverExtensions as mn, partitionExtensionCatalog as mt, DEFAULT_MAX_VALUE_BYTES as n, mergeKeybindings as nn, readExtensionInstallProvenance as nt, normalizeProviders as o, resolveExtensionKeybindings as on, discoverAndBuildExtensionRegistry as ot, collectNewlyStaleReadPaths as p, defaultExtensionScanPaths as pn, mergeContributions as pt, CompactInvalidInputError as q, DEFAULT_KEYBINDINGS as qt, createToolStateController as r, parseBindingSpec as rn, removeInstalledExtension as rt, resolveModelTargets as s, stripJsonComments as sn, buildExtensionPreset as st, createAgent as t, matchesBinding as tn, parseExtensionInstallSource as tt, TOOL_USE_CANCELLED_MESSAGE as u, isReservedCredKey as un, disposeExtensionRegistry as ut, cleanupPersistedSession as v, scanExtensionDirectories as vn, definePreset as vt, resolveMcpWarningsDir as w, isValidExtensionConfigItem as wn, createToolSearchTool as wt, rehomePersistedStubs as x, defineExtension as xn, writeFile$1 as xt, maybePersistToolResult as y, createDebugLog as yn, basicTools as yt, buildTailCompactPrompt as z, normalizeShellCommand as zn, formatModelLine as zt };
17820
+ export { installExtensionFromSpec as $, normalizeShellCommand as $n, KEYBINDING_DEF_BY_ACTION as $t, selectFilesFromSession as A, resolveExtensionConfig as An, createSkillsRunScriptTool as At, buildUpToCompactPrompt as B, commitKind as Bn, formatModelLine as Bt, resolveLegacySessionDir as C, createDebugLog as Cn, WAIT_TASK_TIMED_OUT_PREFIX as Ct, resolveTasksDir as D, coerceExtensionConfigInput as Dn, isSpawnTool as Dt, resolveSessionDir as E, clampExtensionConfigNumber as En, createSpawnTool as Et, TRAILER as F, createExtensionBus as Fn, tailTruncate as Ft, summaryToTurn as G, resolveArtifactKind as Gn, glob as Gt, anchorPreviewFor as H, pullRequestKind as Hn, listFiles as Ht, buildCompactPrompt as I, serializedPayloadBytes as In, readFile$1 as It, CompactPromptTooLongError as J, OUTPUT_RESERVE_TOKENS as Jn, artifacts_default as Jt, truncateHeadForPtlRetry as K, createArtifactDetectHook as Kn, edit as Kt, buildFromCompactPrompt as L, BUILTIN_ARTIFACT_KINDS as Ln, looksBinary as Lt, compactConversation as M, sanitizeExtensionConfigActionPatch as Mn, shellKill as Mt, BASE_INSTRUCTIONS as N, toWireConfigSchema as Nn, createShellTool as Nt, buildPostCompactAttachments as O, isValidExtensionConfigItem as On, reserveChildLabel as Ot, NO_TOOLS_PREAMBLE as P, EXTENSION_BUS_MAX_PAYLOAD_BYTES as Pn, shell as Pt, fetchExtensionSource as Q, defaultRepeatGuardTracked as Qn, KEYBINDING_DEFS as Qt, buildFullCompactPrompt as R, artifactKindLabel as Rn, multiEdit as Rt, resolveLegacyPersistDir as S, scanExtensionDirectories as Sn, writeFile$1 as St, resolvePersistDir as T, defineExtension as Tn, createToolSearchTool as Tt, sliceForCompaction as U, refreshArtifact as Un, createInteractionTool as Ut, ANCHOR_PREVIEW_MAX_CHARS as V, fileKind as Vn, matchModelEntries as Vt, stripImagesFromTurns as W, renderArtifactRow as Wn, grep as Wt, createExtensionPromptBridge as X, shouldAutoCompact as Xn, recordArtifact as Xt, EMPTY_EXTENSION_REGISTRY as Y, effectiveContextWindow as Yn, RECORD_ARTIFACT_TOOL as Yt, ExtensionInstallError as Z, defaultRepeatGuardNormalize as Zn, DEFAULT_KEYBINDINGS as Zt, buildPersistedStub as _, defaultExtensionScanPaths as _n, createWorkspaceSurface as _t, stagedToolState as a, matchesBinding as an, readStateKey as ar, createExtensionHud as at, migrateSessionArtifactDirs as b, isExtensionDefinition as bn, basicTools as bt, INTERRUPT_MESSAGE_FOR_TOOL_USE as c, readKeybindings as cn, buildExtensionRegistry as ct, TOOL_USE_SKIPPED_MESSAGE as d, stripJsonComments as dn, filterExtensionRegistry as dt, KEYBINDING_KEY_COL_WIDTH as en, stableStringify as er, listExtensionCandidates as et, applyCompactSummaryCutoff as f, ALTERNATE_CRED_KEY_SUFFIX as fn, filterRegistryPlugins as ft, PERSISTENCE_PREVIEW_BYTES as g, ENTRY_CANDIDATES as gn, createExtensionHostRuntimeBridge as gt, PERSISTED_STUB_PREFIX as h, registerExtensionHostModules as hn, createArtifactsSurface as ht, isToolStateOps as i, keybindingsPath as in, hydrateReadStateFromSessionAsync as ir, updateInstalledExtension as it, selectRecentFiles as j, resolveExtensionConfigValue as jn, createSkillsReadTool as jt, selectFilesFromReadState as k, isValidExtensionConfigValue as kn, createSkillsUseTool as kt, SHELL_CASCADE_CANCEL_MESSAGE as l, readKeybindingsRaw as ln, composeExtensionPreset as lt, renderStaleReadNotice as m, isReservedCredKey as mn, partitionExtensionCatalog as mt, DEFAULT_MAX_VALUE_BYTES as n, formatBindingForDisplay as nn, hashContent as nr, readExtensionInstallProvenance as nt, normalizeProviders as o, mergeKeybindings as on, resolveReadStateMap as or, discoverAndBuildExtensionRegistry as ot, collectNewlyStaleReadPaths as p, alternateCredKey as pn, mergeContributions as pt, CompactInvalidInputError as q, AUTO_COMPACT_MIN_GROWTH_FRACTION as qn, validateToolArgs as qt, createToolStateController as r, groupBindings as rn, hydrateReadStateFromSession as rr, removeInstalledExtension as rt, resolveModelTargets as s, parseBindingSpec as sn, buildExtensionPreset as st, createAgent as t, ensureKeybindingsFile as tn, getReadState as tr, parseExtensionInstallSource as tt, TOOL_USE_CANCELLED_MESSAGE as u, resolveExtensionKeybindings as un, disposeExtensionRegistry as ut, cleanupPersistedSession as v, discoverExtensions as vn, composePresets as vt, resolveMcpWarningsDir as w, setDebugLogSink as wn, waitTask as wt, rehomePersistedStubs as x, parseExtensionScanPathEnv as xn, basic_default as xt, maybePersistToolResult as y, inlineLoadedExtension as yn, definePreset as yt, buildTailCompactPrompt as z, branchKind as zn, createModelSearchTool as zt };
17220
17821
 
17221
- //# sourceMappingURL=agent-DRNT6DbC.js.map
17822
+ //# sourceMappingURL=agent-B_uQo4f1.js.map