@krapht/ork 1.0.0-alpha.17 → 1.0.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{__build_info__-C1Nm0hlJ.mjs → __build_info__-B8TNbFtN.mjs} +2 -2
- package/dist/{attach-EP374oMP.mjs → attach-CU4Pk42S.mjs} +5 -5
- package/dist/{attach-RQx__RzH.mjs → attach-qvfyaQXC.mjs} +1 -1
- package/dist/bin.mjs +35 -35
- package/dist/{brief-Bs3cMQU0.mjs → brief-Bh1FnAHv.mjs} +4 -4
- package/dist/{builtin-files-yYaNTQRU.mjs → builtin-files-dnTP2GaX.mjs} +1 -1
- package/dist/{completion-DIXWlnAl.mjs → completion-CzXUGSVn.mjs} +4 -4
- package/dist/{context-Dq-VM_sq.mjs → context-b4_gPlv3.mjs} +3 -3
- package/dist/{find-k489laBa.mjs → find-P-VV4B47.mjs} +1 -1
- package/dist/{flags-B4ZZWspN.mjs → flags-CqjJ44uW.mjs} +1 -1
- package/dist/{follow-ups-DD8NTPe6.mjs → follow-ups-B_c1DSpA.mjs} +8 -8
- package/dist/{format-DQc9A_ei.mjs → format-BS1qzpkf.mjs} +1 -1
- package/dist/{in-a-run-D_9GlAlS.mjs → in-a-run-DKoMGTNE.mjs} +2 -2
- package/dist/{install-PVlV2QPq.mjs → install-B743Bwzz.mjs} +1 -1
- package/dist/{limits-CHqBO7wb.mjs → limits-DrsKCqaN.mjs} +1 -1
- package/dist/{mcp-D38Ajb0b.mjs → mcp-BMwekw38.mjs} +6 -6
- package/dist/{once-DuUzpzL2.mjs → once-6L8gciJV.mjs} +3 -3
- package/dist/{pause-Bx3ePGrn.mjs → pause-8gnVgICR.mjs} +3 -3
- package/dist/{post-2CaJz_mV.mjs → post-y1qL1Fnj.mjs} +2 -2
- package/dist/{print-n2qetUHc.mjs → print-DLe4MHn-.mjs} +1 -1
- package/dist/{project-records-Cune1l6I.mjs → project-records-CsILxwfI.mjs} +1 -1
- package/dist/{relay-D7RN4m68.mjs → relay-Bvm9JEoN.mjs} +1 -1
- package/dist/{run-OOWQIKMJ.mjs → run-D4yuTmmJ.mjs} +10 -10
- package/dist/{run-BfxdsQJk.mjs → run-y8jI7bAI.mjs} +1 -1
- package/dist/{send-CKth_vgn.mjs → send-BV9eIxQ7.mjs} +2 -2
- package/dist/{src-DmmrX2_C.mjs → src-CVeA4tAw.mjs} +2 -2
- package/dist/{src-DOjTY2ET.mjs → src-DWAcd3Sy.mjs} +6 -6
- package/dist/{src-DLsFsmOD.mjs → src-Frwpx3WF.mjs} +39 -5
- package/dist/{src-BHod38UQ.mjs → src-ygFMh2eP.mjs} +391 -244
- package/dist/{store-DACH768X.mjs → store-B0nGqnPq.mjs} +1 -1
- package/dist/ui/assets/{app-CN3acQ-1.js → app-C4yxmYQR.js} +1 -1
- package/dist/ui/assets/{index-B2uGxA4W.js → index-CcbJWo5C.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CJ-BX12n.mjs → ui-BZ9BhFw8.mjs} +3 -3
- package/dist/{usage-DxZ6JGd4.mjs → usage-BIs4j5Rw.mjs} +4 -4
- package/dist/{validate-BS0nCccW.mjs → validate-Xn2ZbJQC.mjs} +3 -3
- package/dist/{watch-Dp7IB4FR.mjs → watch-9VM3DFTp.mjs} +5 -5
- package/dist/{workflows-Djo1edlK.mjs → workflows-CBYtycIy.mjs} +2 -2
- package/dist/{worktrees-Drof_Nyx.mjs → worktrees-C7vfh7y5.mjs} +4 -4
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $n as FEEDBACK_TARGETS, Bn as countedTokens, It as lowerChild, Lt as programFound, Qn as FEEDBACK_SEVERITIES, Rt as startAt, Ti as AGENT_DRIVERS, Xn as endFor, Yn as END_KINDS, Zn as FEEDBACK_KINDS, _r as MASK, dn as FOLLOW_UP_KINDS, qn as outcomeEnd, vt as parseReport, yr as maskSecrets } from "./src-
|
|
1
|
+
import { $n as FEEDBACK_TARGETS, Bn as countedTokens, It as lowerChild, Lt as programFound, Qn as FEEDBACK_SEVERITIES, Rt as startAt, Ti as AGENT_DRIVERS, Xn as endFor, Yn as END_KINDS, Zn as FEEDBACK_KINDS, _r as MASK, dn as FOLLOW_UP_KINDS, qn as outcomeEnd, vt as parseReport, yr as maskSecrets } from "./src-Frwpx3WF.mjs";
|
|
2
2
|
import { n as holdGuard, r as orphanGuard } from "./orphan-guard-BFKg8VSd.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { existsSync, linkSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { chmodSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
7
7
|
import { homedir, tmpdir } from "node:os";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
@@ -1608,183 +1608,6 @@ const claudeUsage = {
|
|
|
1608
1608
|
dollars: true,
|
|
1609
1609
|
combine: addUsage
|
|
1610
1610
|
};
|
|
1611
|
-
// ../runtime/src/adapters/claude-code.ts
|
|
1612
|
-
/**
|
|
1613
|
-
* Per-request timeout for Ork's MCP server, in ms. The CLI otherwise gives an
|
|
1614
|
-
* HTTP server's request 60 seconds and drops a call silent for 5 minutes, and
|
|
1615
|
-
* `delegate` waits for whole child sessions (the per-server `timeout` raises
|
|
1616
|
-
* both; `MCP_TOOL_TIMEOUT` still caps it).
|
|
1617
|
-
*/
|
|
1618
|
-
const ORK_TOOL_TIMEOUT_MS = 864e5;
|
|
1619
|
-
/** Claude Code's switch for the one-hour prompt cache. */
|
|
1620
|
-
const PROMPT_CACHE_1H = "ENABLE_PROMPT_CACHING_1H";
|
|
1621
|
-
/** Claude Code's switch that turns its auto-memory off. */
|
|
1622
|
-
const NO_AUTO_MEMORY = "CLAUDE_CODE_DISABLE_AUTO_MEMORY";
|
|
1623
|
-
/**
|
|
1624
|
-
* The CLI flag that moves the per-machine sections (working directory, memory
|
|
1625
|
-
* path, git status) out of the system prompt.
|
|
1626
|
-
*/
|
|
1627
|
-
const EXCLUDE_DYNAMIC_SECTIONS = "--exclude-dynamic-system-prompt-sections";
|
|
1628
|
-
/** How long a hook holds a call before it defers it instead. */
|
|
1629
|
-
const HOLD_WINDOW_MS = 54e4;
|
|
1630
|
-
/** The variables a session's prompt cache asks of Claude Code. */
|
|
1631
|
-
const cacheEnv = (spec) => spec.promptCache === "1h" ? { [PROMPT_CACHE_1H]: "1" } : {};
|
|
1632
|
-
/** Whether the session loads no MCP server but Ork's. */
|
|
1633
|
-
const strictMcpOf = (spec) => spec.tools.strictMcp === true || spec.machineClaude !== true;
|
|
1634
|
-
/** The variables that keep the machine's auto-memory out of a session. */
|
|
1635
|
-
const memoryEnv = (spec) => spec.machineClaude === true ? {} : { [NO_AUTO_MEMORY]: "1" };
|
|
1636
|
-
/** Claude Code's default timeout for a Bash call, in milliseconds. */
|
|
1637
|
-
const BASH_TIMEOUT = "BASH_DEFAULT_TIMEOUT_MS";
|
|
1638
|
-
/**
|
|
1639
|
-
* Ten minutes, Claude Code's own most for a Bash call. At its default of two, a
|
|
1640
|
-
* longer command (a typecheck of the workspace) moves to the background, and
|
|
1641
|
-
* the session then polls it with `sleep`, `until` or `Monitor`, which ask.
|
|
1642
|
-
*/
|
|
1643
|
-
const BASH_TIMEOUT_MS = 6e5;
|
|
1644
|
-
/** The variables that let a session's Bash calls run to the end in front. */
|
|
1645
|
-
const bashEnv = () => ({ [BASH_TIMEOUT]: String(BASH_TIMEOUT_MS) });
|
|
1646
|
-
/** Everything a session adds to its driver's environment. */
|
|
1647
|
-
const driverEnv = (spec) => ({
|
|
1648
|
-
...bashEnv(),
|
|
1649
|
-
...cacheEnv(spec),
|
|
1650
|
-
...memoryEnv(spec)
|
|
1651
|
-
});
|
|
1652
|
-
/** A later message goes in at the next step, even mid-turn. */
|
|
1653
|
-
const userLine = (text, later = false) => `${JSON.stringify({
|
|
1654
|
-
type: "user",
|
|
1655
|
-
message: {
|
|
1656
|
-
role: "user",
|
|
1657
|
-
content: text
|
|
1658
|
-
},
|
|
1659
|
-
parent_tool_use_id: null,
|
|
1660
|
-
...later ? { priority: "next" } : {}
|
|
1661
|
-
})}\n`;
|
|
1662
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1663
|
-
/**
|
|
1664
|
-
* The node's `--settings` with Ork's `PreToolUse` HTTP hook added: before every
|
|
1665
|
-
* tool call, pre-approved ones included, the session asks whether its node may
|
|
1666
|
-
* go on. Its `PostToolUse` hook after a Bash call frees the call's slot.
|
|
1667
|
-
*/
|
|
1668
|
-
const withHoldHook = (settings, holdUrl, token) => {
|
|
1669
|
-
const hooks = isRecord(settings?.["hooks"]) ? settings["hooks"] : {};
|
|
1670
|
-
const listed = (event) => {
|
|
1671
|
-
const own = hooks[event];
|
|
1672
|
-
return Array.isArray(own) ? [...own] : [];
|
|
1673
|
-
};
|
|
1674
|
-
const hook = {
|
|
1675
|
-
type: "http",
|
|
1676
|
-
url: holdUrl,
|
|
1677
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
1678
|
-
timeout: 600
|
|
1679
|
-
};
|
|
1680
|
-
return {
|
|
1681
|
-
...settings,
|
|
1682
|
-
hooks: {
|
|
1683
|
-
...hooks,
|
|
1684
|
-
PreToolUse: [...listed("PreToolUse"), {
|
|
1685
|
-
matcher: "*",
|
|
1686
|
-
hooks: [hook]
|
|
1687
|
-
}],
|
|
1688
|
-
PostToolUse: [...listed("PostToolUse"), {
|
|
1689
|
-
matcher: "Bash",
|
|
1690
|
-
hooks: [hook]
|
|
1691
|
-
}]
|
|
1692
|
-
}
|
|
1693
|
-
};
|
|
1694
|
-
};
|
|
1695
|
-
/** The hook's answer: nothing to say for "go", the deferral otherwise. */
|
|
1696
|
-
const hookOutput = (answer) => answer === "go" ? {} : { hookSpecificOutput: {
|
|
1697
|
-
hookEventName: "PreToolUse",
|
|
1698
|
-
permissionDecision: "defer",
|
|
1699
|
-
permissionDecisionReason: "Ork paused this node; it goes on from this call when it continues."
|
|
1700
|
-
} };
|
|
1701
|
-
const claudeHold = {
|
|
1702
|
-
answer: hookOutput,
|
|
1703
|
-
windowMs: HOLD_WINDOW_MS
|
|
1704
|
-
};
|
|
1705
|
-
/**
|
|
1706
|
-
* The `claude -p` arguments for a session spec. The brief goes on stdin;
|
|
1707
|
-
* `allowed` are the Ork tools that need no permission prompt.
|
|
1708
|
-
*/
|
|
1709
|
-
const buildCliArgs = (spec, mcp, allowed) => {
|
|
1710
|
-
const mcpConfig = { mcpServers: {
|
|
1711
|
-
...spec.mcpServers,
|
|
1712
|
-
["ork"]: {
|
|
1713
|
-
type: "http",
|
|
1714
|
-
url: mcp.url,
|
|
1715
|
-
headers: { Authorization: `Bearer ${mcp.token}` },
|
|
1716
|
-
timeout: ORK_TOOL_TIMEOUT_MS
|
|
1717
|
-
}
|
|
1718
|
-
} };
|
|
1719
|
-
const args = [
|
|
1720
|
-
"-p",
|
|
1721
|
-
"--input-format",
|
|
1722
|
-
"stream-json",
|
|
1723
|
-
"--output-format",
|
|
1724
|
-
"stream-json",
|
|
1725
|
-
"--verbose",
|
|
1726
|
-
"--setting-sources",
|
|
1727
|
-
spec.settingSources.join(","),
|
|
1728
|
-
"--append-system-prompt",
|
|
1729
|
-
spec.systemAppend,
|
|
1730
|
-
EXCLUDE_DYNAMIC_SECTIONS,
|
|
1731
|
-
"--permission-mode",
|
|
1732
|
-
spec.permissionMode,
|
|
1733
|
-
"--json-schema",
|
|
1734
|
-
JSON.stringify(spec.reportSchema),
|
|
1735
|
-
"--mcp-config",
|
|
1736
|
-
JSON.stringify(mcpConfig),
|
|
1737
|
-
"--permission-prompt-tool",
|
|
1738
|
-
toolId("permission"),
|
|
1739
|
-
"--allowedTools",
|
|
1740
|
-
[...spec.tools.allow, ...allowed].join(",")
|
|
1741
|
-
];
|
|
1742
|
-
if (spec.tools.only !== void 0) args.push("--tools", spec.tools.only.join(","));
|
|
1743
|
-
if (strictMcpOf(spec)) args.push("--strict-mcp-config");
|
|
1744
|
-
if (spec.agents !== void 0) args.push("--agents", JSON.stringify(spec.agents));
|
|
1745
|
-
if (spec.tools.deny.length > 0) args.push("--disallowedTools", spec.tools.deny.join(","));
|
|
1746
|
-
if (spec.model !== void 0) args.push("--model", spec.model);
|
|
1747
|
-
if (spec.effort !== void 0) args.push("--effort", spec.effort);
|
|
1748
|
-
if (spec.maxTurns !== void 0) args.push("--max-turns", String(spec.maxTurns));
|
|
1749
|
-
if (spec.budgetUsd !== void 0) args.push("--max-budget-usd", String(spec.budgetUsd));
|
|
1750
|
-
if (spec.resume !== void 0) args.push("--resume", spec.resume);
|
|
1751
|
-
if (spec.resume !== void 0 && spec.fork === true) args.push("--fork-session");
|
|
1752
|
-
for (const dir of spec.plugins ?? []) args.push("--plugin-dir", dir);
|
|
1753
|
-
const settings = mcp.holdUrl === void 0 ? spec.settings : withHoldHook(spec.settings, mcp.holdUrl, mcp.token);
|
|
1754
|
-
if (settings !== void 0) args.push("--settings", JSON.stringify(settings));
|
|
1755
|
-
return args;
|
|
1756
|
-
};
|
|
1757
|
-
/** `claude`, as the session runner's strategies. */
|
|
1758
|
-
const claudeCodeAdapter = {
|
|
1759
|
-
driver: "claude-code",
|
|
1760
|
-
command: {
|
|
1761
|
-
bin: "claude",
|
|
1762
|
-
args: (spec, leg) => buildCliArgs(spec, leg.endpoint, leg.allowed),
|
|
1763
|
-
input: userLine
|
|
1764
|
-
},
|
|
1765
|
-
decode: decodeClaudeLine,
|
|
1766
|
-
legDecoder: claudeDecoder,
|
|
1767
|
-
mcp: {
|
|
1768
|
-
kind: "flag",
|
|
1769
|
-
wire: () => Promise.resolve({
|
|
1770
|
-
args: [],
|
|
1771
|
-
env: {},
|
|
1772
|
-
release: () => Promise.resolve()
|
|
1773
|
-
})
|
|
1774
|
-
},
|
|
1775
|
-
permissions: {
|
|
1776
|
-
kind: "callback",
|
|
1777
|
-
denyByDefault: false
|
|
1778
|
-
},
|
|
1779
|
-
usage: claudeUsage,
|
|
1780
|
-
context: {
|
|
1781
|
-
instructions: ["CLAUDE.md", "AGENTS.md"],
|
|
1782
|
-
skills: ".claude/skills",
|
|
1783
|
-
agents: ".claude/agents",
|
|
1784
|
-
env: driverEnv
|
|
1785
|
-
},
|
|
1786
|
-
hold: claudeHold
|
|
1787
|
-
};
|
|
1788
1611
|
// ../runtime/src/lease-ledger.ts
|
|
1789
1612
|
/**
|
|
1790
1613
|
* @file The ledger of Command Code leases (RFC-014, `lease.ts`): one record per
|
|
@@ -1911,6 +1734,280 @@ const cleanStaleLeases = async (options) => {
|
|
|
1911
1734
|
}
|
|
1912
1735
|
return sweep;
|
|
1913
1736
|
};
|
|
1737
|
+
// ../runtime/src/secret-file.ts
|
|
1738
|
+
/**
|
|
1739
|
+
* @file A session's private folder for files that hold a secret, such as a
|
|
1740
|
+
* config carrying Ork's bearer token (RFC-014): a 0700 folder outside the
|
|
1741
|
+
* checkout, under the system's temporary folder, named after the process that
|
|
1742
|
+
* made it, with each file written at mode 0600. The session removes it when
|
|
1743
|
+
* it ends; a folder whose process is gone (a crash) is removed when the next
|
|
1744
|
+
* one is made. Nothing here logs what it writes.
|
|
1745
|
+
*/
|
|
1746
|
+
/** Where sessions' private folders live unless a caller names another place. */
|
|
1747
|
+
const secretRoot = () => path.join(tmpdir(), "ork-sessions");
|
|
1748
|
+
/** Removes the folders under `root` whose process is gone; returns them. */
|
|
1749
|
+
const sweepSecretFolders = (root) => {
|
|
1750
|
+
let names;
|
|
1751
|
+
try {
|
|
1752
|
+
names = readdirSync(root);
|
|
1753
|
+
} catch {
|
|
1754
|
+
return [];
|
|
1755
|
+
}
|
|
1756
|
+
return names.flatMap((name) => {
|
|
1757
|
+
const pid = Number(name.split("-")[0]);
|
|
1758
|
+
if (!Number.isInteger(pid) || alive(pid)) return [];
|
|
1759
|
+
const dir = path.join(root, name);
|
|
1760
|
+
rmSync(dir, {
|
|
1761
|
+
recursive: true,
|
|
1762
|
+
force: true
|
|
1763
|
+
});
|
|
1764
|
+
return [dir];
|
|
1765
|
+
});
|
|
1766
|
+
};
|
|
1767
|
+
/**
|
|
1768
|
+
* Refuses a root that isn't this user's own folder: in a shared temporary
|
|
1769
|
+
* folder another user could have made it first, or put a link there, to read or
|
|
1770
|
+
* swap what a session loads. One of ours that is open to others is closed.
|
|
1771
|
+
*/
|
|
1772
|
+
const holdPrivate = (root) => {
|
|
1773
|
+
const held = lstatSync(root);
|
|
1774
|
+
const uid = process.getuid?.();
|
|
1775
|
+
if (!held.isDirectory() || uid !== void 0 && held.uid !== uid) throw new Error(`${root} is not a folder of this user's; remove it, or set TMPDIR to a private folder`);
|
|
1776
|
+
if ((held.mode & 63) !== 0) chmodSync(root, 448);
|
|
1777
|
+
};
|
|
1778
|
+
/** A new private folder under `root`, after sweeping the stale ones. */
|
|
1779
|
+
const makeSecretFolder = (root = secretRoot()) => {
|
|
1780
|
+
mkdirSync(root, {
|
|
1781
|
+
recursive: true,
|
|
1782
|
+
mode: 448
|
|
1783
|
+
});
|
|
1784
|
+
holdPrivate(root);
|
|
1785
|
+
sweepSecretFolders(root);
|
|
1786
|
+
const dir = mkdtempSync(path.join(root, `${String(process.pid)}-`));
|
|
1787
|
+
return {
|
|
1788
|
+
dir,
|
|
1789
|
+
write: (name, content) => {
|
|
1790
|
+
const file = path.join(dir, name);
|
|
1791
|
+
writeFileSync(file, content, { mode: 384 });
|
|
1792
|
+
return file;
|
|
1793
|
+
},
|
|
1794
|
+
remove: () => {
|
|
1795
|
+
rmSync(dir, {
|
|
1796
|
+
recursive: true,
|
|
1797
|
+
force: true
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
};
|
|
1802
|
+
// ../runtime/src/adapters/claude-code-flags.ts
|
|
1803
|
+
/**
|
|
1804
|
+
* Per-request timeout for Ork's MCP server, in ms. The CLI otherwise gives an
|
|
1805
|
+
* HTTP server's request 60 seconds and drops a call silent for 5 minutes, and
|
|
1806
|
+
* `delegate` waits for whole child sessions (the per-server `timeout` raises
|
|
1807
|
+
* both; `MCP_TOOL_TIMEOUT` still caps it).
|
|
1808
|
+
*/
|
|
1809
|
+
const ORK_TOOL_TIMEOUT_MS = 864e5;
|
|
1810
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1811
|
+
/**
|
|
1812
|
+
* The node's `--settings` with Ork's `PreToolUse` HTTP hook added: before every
|
|
1813
|
+
* tool call, pre-approved ones included, the session asks whether its node may
|
|
1814
|
+
* go on. Its `PostToolUse` hook after a Bash call frees the call's slot.
|
|
1815
|
+
*/
|
|
1816
|
+
const withHoldHook = (settings, holdUrl, token) => {
|
|
1817
|
+
const hooks = isRecord(settings?.["hooks"]) ? settings["hooks"] : {};
|
|
1818
|
+
const listed = (event) => {
|
|
1819
|
+
const own = hooks[event];
|
|
1820
|
+
return Array.isArray(own) ? [...own] : [];
|
|
1821
|
+
};
|
|
1822
|
+
const hook = {
|
|
1823
|
+
type: "http",
|
|
1824
|
+
url: holdUrl,
|
|
1825
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1826
|
+
timeout: 600
|
|
1827
|
+
};
|
|
1828
|
+
return {
|
|
1829
|
+
...settings,
|
|
1830
|
+
hooks: {
|
|
1831
|
+
...hooks,
|
|
1832
|
+
PreToolUse: [...listed("PreToolUse"), {
|
|
1833
|
+
matcher: "*",
|
|
1834
|
+
hooks: [hook]
|
|
1835
|
+
}],
|
|
1836
|
+
PostToolUse: [...listed("PostToolUse"), {
|
|
1837
|
+
matcher: "Bash",
|
|
1838
|
+
hooks: [hook]
|
|
1839
|
+
}]
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
};
|
|
1843
|
+
/** The session's `--mcp-config`: its servers, and Ork's with the token. */
|
|
1844
|
+
const mcpConfigOf = (spec, mcp) => ({ mcpServers: {
|
|
1845
|
+
...spec.mcpServers,
|
|
1846
|
+
["ork"]: {
|
|
1847
|
+
type: "http",
|
|
1848
|
+
url: mcp.url,
|
|
1849
|
+
headers: { Authorization: `Bearer ${mcp.token}` },
|
|
1850
|
+
timeout: ORK_TOOL_TIMEOUT_MS
|
|
1851
|
+
}
|
|
1852
|
+
} });
|
|
1853
|
+
/** The session's `--settings`, with the hold hook (and its token) when held. */
|
|
1854
|
+
const settingsOf = (spec, mcp) => mcp.holdUrl === void 0 ? spec.settings : withHoldHook(spec.settings, mcp.holdUrl, mcp.token);
|
|
1855
|
+
/** The flags whose values carry Ork's token. */
|
|
1856
|
+
const SECRET_FLAGS = /* @__PURE__ */ new Set(["--mcp-config", "--settings"]);
|
|
1857
|
+
/**
|
|
1858
|
+
* `args` without the flags that carry Ork's token: a process's arguments can be
|
|
1859
|
+
* read by other users of the machine, so the session passes them as files.
|
|
1860
|
+
*/
|
|
1861
|
+
const withoutSecretFlags = (args) => args.filter((arg, at) => !SECRET_FLAGS.has(arg) && !SECRET_FLAGS.has(args[at - 1] ?? ""));
|
|
1862
|
+
/**
|
|
1863
|
+
* Writes the session's MCP config and settings into a private folder (0700,
|
|
1864
|
+
* each file 0600) and passes their paths; the folder goes when the session
|
|
1865
|
+
* ends.
|
|
1866
|
+
*/
|
|
1867
|
+
const secretFlags = {
|
|
1868
|
+
kind: "flag",
|
|
1869
|
+
wire: (spec, endpoint) => {
|
|
1870
|
+
const folder = makeSecretFolder();
|
|
1871
|
+
try {
|
|
1872
|
+
const config = JSON.stringify(mcpConfigOf(spec, endpoint));
|
|
1873
|
+
const args = ["--mcp-config", folder.write("mcp.json", config)];
|
|
1874
|
+
const settings = settingsOf(spec, endpoint);
|
|
1875
|
+
if (settings !== void 0) args.push("--settings", folder.write("settings.json", JSON.stringify(settings)));
|
|
1876
|
+
return Promise.resolve({
|
|
1877
|
+
args,
|
|
1878
|
+
env: {},
|
|
1879
|
+
release: () => {
|
|
1880
|
+
folder.remove();
|
|
1881
|
+
return Promise.resolve();
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
folder.remove();
|
|
1886
|
+
throw error;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
// ../runtime/src/adapters/claude-code.ts
|
|
1891
|
+
/** Claude Code's switch for the one-hour prompt cache. */
|
|
1892
|
+
const PROMPT_CACHE_1H = "ENABLE_PROMPT_CACHING_1H";
|
|
1893
|
+
/** Claude Code's switch that turns its auto-memory off. */
|
|
1894
|
+
const NO_AUTO_MEMORY = "CLAUDE_CODE_DISABLE_AUTO_MEMORY";
|
|
1895
|
+
/**
|
|
1896
|
+
* The CLI flag that moves the per-machine sections (working directory, memory
|
|
1897
|
+
* path, git status) out of the system prompt.
|
|
1898
|
+
*/
|
|
1899
|
+
const EXCLUDE_DYNAMIC_SECTIONS = "--exclude-dynamic-system-prompt-sections";
|
|
1900
|
+
/** How long a hook holds a call before it defers it instead. */
|
|
1901
|
+
const HOLD_WINDOW_MS = 54e4;
|
|
1902
|
+
/** The variables a session's prompt cache asks of Claude Code. */
|
|
1903
|
+
const cacheEnv = (spec) => spec.promptCache === "1h" ? { [PROMPT_CACHE_1H]: "1" } : {};
|
|
1904
|
+
/** Whether the session loads no MCP server but Ork's. */
|
|
1905
|
+
const strictMcpOf = (spec) => spec.tools.strictMcp === true || spec.machineClaude !== true;
|
|
1906
|
+
/** The variables that keep the machine's auto-memory out of a session. */
|
|
1907
|
+
const memoryEnv = (spec) => spec.machineClaude === true ? {} : { [NO_AUTO_MEMORY]: "1" };
|
|
1908
|
+
/** Claude Code's default timeout for a Bash call, in milliseconds. */
|
|
1909
|
+
const BASH_TIMEOUT = "BASH_DEFAULT_TIMEOUT_MS";
|
|
1910
|
+
/**
|
|
1911
|
+
* Ten minutes, Claude Code's own most for a Bash call. At its default of two, a
|
|
1912
|
+
* longer command (a typecheck of the workspace) moves to the background, and
|
|
1913
|
+
* the session then polls it with `sleep`, `until` or `Monitor`, which ask.
|
|
1914
|
+
*/
|
|
1915
|
+
const BASH_TIMEOUT_MS = 6e5;
|
|
1916
|
+
/** The variables that let a session's Bash calls run to the end in front. */
|
|
1917
|
+
const bashEnv = () => ({ [BASH_TIMEOUT]: String(BASH_TIMEOUT_MS) });
|
|
1918
|
+
/** Everything a session adds to its driver's environment. */
|
|
1919
|
+
const driverEnv = (spec) => ({
|
|
1920
|
+
...bashEnv(),
|
|
1921
|
+
...cacheEnv(spec),
|
|
1922
|
+
...memoryEnv(spec)
|
|
1923
|
+
});
|
|
1924
|
+
/** A later message goes in at the next step, even mid-turn. */
|
|
1925
|
+
const userLine = (text, later = false) => `${JSON.stringify({
|
|
1926
|
+
type: "user",
|
|
1927
|
+
message: {
|
|
1928
|
+
role: "user",
|
|
1929
|
+
content: text
|
|
1930
|
+
},
|
|
1931
|
+
parent_tool_use_id: null,
|
|
1932
|
+
...later ? { priority: "next" } : {}
|
|
1933
|
+
})}\n`;
|
|
1934
|
+
/** The hook's answer: nothing to say for "go", the deferral otherwise. */
|
|
1935
|
+
const hookOutput = (answer) => answer === "go" ? {} : { hookSpecificOutput: {
|
|
1936
|
+
hookEventName: "PreToolUse",
|
|
1937
|
+
permissionDecision: "defer",
|
|
1938
|
+
permissionDecisionReason: "Ork paused this node; it goes on from this call when it continues."
|
|
1939
|
+
} };
|
|
1940
|
+
const claudeHold = {
|
|
1941
|
+
answer: hookOutput,
|
|
1942
|
+
windowMs: HOLD_WINDOW_MS
|
|
1943
|
+
};
|
|
1944
|
+
/**
|
|
1945
|
+
* The `claude -p` arguments for a session spec. The brief goes on stdin;
|
|
1946
|
+
* `allowed` are the Ork tools that need no permission prompt.
|
|
1947
|
+
*/
|
|
1948
|
+
const buildCliArgs = (spec, mcp, allowed) => {
|
|
1949
|
+
const args = [
|
|
1950
|
+
"-p",
|
|
1951
|
+
"--input-format",
|
|
1952
|
+
"stream-json",
|
|
1953
|
+
"--output-format",
|
|
1954
|
+
"stream-json",
|
|
1955
|
+
"--verbose",
|
|
1956
|
+
"--setting-sources",
|
|
1957
|
+
spec.settingSources.join(","),
|
|
1958
|
+
"--append-system-prompt",
|
|
1959
|
+
spec.systemAppend,
|
|
1960
|
+
EXCLUDE_DYNAMIC_SECTIONS,
|
|
1961
|
+
"--permission-mode",
|
|
1962
|
+
spec.permissionMode,
|
|
1963
|
+
"--json-schema",
|
|
1964
|
+
JSON.stringify(spec.reportSchema),
|
|
1965
|
+
"--mcp-config",
|
|
1966
|
+
JSON.stringify(mcpConfigOf(spec, mcp)),
|
|
1967
|
+
"--permission-prompt-tool",
|
|
1968
|
+
toolId("permission"),
|
|
1969
|
+
"--allowedTools",
|
|
1970
|
+
[...spec.tools.allow, ...allowed].join(",")
|
|
1971
|
+
];
|
|
1972
|
+
if (spec.tools.only !== void 0) args.push("--tools", spec.tools.only.join(","));
|
|
1973
|
+
if (strictMcpOf(spec)) args.push("--strict-mcp-config");
|
|
1974
|
+
if (spec.agents !== void 0) args.push("--agents", JSON.stringify(spec.agents));
|
|
1975
|
+
if (spec.tools.deny.length > 0) args.push("--disallowedTools", spec.tools.deny.join(","));
|
|
1976
|
+
if (spec.model !== void 0) args.push("--model", spec.model);
|
|
1977
|
+
if (spec.effort !== void 0) args.push("--effort", spec.effort);
|
|
1978
|
+
if (spec.maxTurns !== void 0) args.push("--max-turns", String(spec.maxTurns));
|
|
1979
|
+
if (spec.budgetUsd !== void 0) args.push("--max-budget-usd", String(spec.budgetUsd));
|
|
1980
|
+
if (spec.resume !== void 0) args.push("--resume", spec.resume);
|
|
1981
|
+
if (spec.resume !== void 0 && spec.fork === true) args.push("--fork-session");
|
|
1982
|
+
for (const dir of spec.plugins ?? []) args.push("--plugin-dir", dir);
|
|
1983
|
+
const settings = settingsOf(spec, mcp);
|
|
1984
|
+
if (settings !== void 0) args.push("--settings", JSON.stringify(settings));
|
|
1985
|
+
return args;
|
|
1986
|
+
};
|
|
1987
|
+
/** `claude`, as the session runner's strategies. */
|
|
1988
|
+
const claudeCodeAdapter = {
|
|
1989
|
+
driver: "claude-code",
|
|
1990
|
+
command: {
|
|
1991
|
+
bin: "claude",
|
|
1992
|
+
args: (spec, leg) => withoutSecretFlags(buildCliArgs(spec, leg.endpoint, leg.allowed)),
|
|
1993
|
+
input: userLine
|
|
1994
|
+
},
|
|
1995
|
+
decode: decodeClaudeLine,
|
|
1996
|
+
legDecoder: claudeDecoder,
|
|
1997
|
+
mcp: secretFlags,
|
|
1998
|
+
permissions: {
|
|
1999
|
+
kind: "callback",
|
|
2000
|
+
denyByDefault: false
|
|
2001
|
+
},
|
|
2002
|
+
usage: claudeUsage,
|
|
2003
|
+
context: {
|
|
2004
|
+
instructions: ["CLAUDE.md", "AGENTS.md"],
|
|
2005
|
+
skills: ".claude/skills",
|
|
2006
|
+
agents: ".claude/agents",
|
|
2007
|
+
env: driverEnv
|
|
2008
|
+
},
|
|
2009
|
+
hold: claudeHold
|
|
2010
|
+
};
|
|
1914
2011
|
// ../runtime/src/lease.ts
|
|
1915
2012
|
/**
|
|
1916
2013
|
* @file The `lease` kind of `McpWiring` (RFC-014): Command Code reads MCP
|
|
@@ -2250,9 +2347,12 @@ const commandCodeUsage = {
|
|
|
2250
2347
|
* session outside the checkout: its `beforeToolCall` hook blocks every call
|
|
2251
2348
|
* the node did not allow. Ork's own tools (`mcp__ork__*`) always go through.
|
|
2252
2349
|
* Command Code's tool loader passes, and reads inside the worktree need no
|
|
2253
|
-
* rule, as with Claude Code; one reaching outside it
|
|
2254
|
-
* node's, in Claude Code's words
|
|
2255
|
-
* specifier Ork does not read never
|
|
2350
|
+
* rule, as with Claude Code; one reaching outside it (by its real path, links
|
|
2351
|
+
* followed) does. The rules are the node's, in Claude Code's words
|
|
2352
|
+
* (`Bash(git:*)`, `Read`, `mcp__github`); a specifier Ork does not read never
|
|
2353
|
+
* allows a call and always denies one. A shell line is checked command by
|
|
2354
|
+
* command: each must be allowed, and one with a substitution Ork can't read
|
|
2355
|
+
* is allowed only by a plain `Bash` rule.
|
|
2256
2356
|
*/
|
|
2257
2357
|
/** The rules of a session: an `acceptEdits` node may edit without a rule. */
|
|
2258
2358
|
const modRules = (spec) => ({
|
|
@@ -2266,19 +2366,38 @@ const modRules = (spec) => ({
|
|
|
2266
2366
|
* export taking the mod API), so it carries its own matcher.
|
|
2267
2367
|
*/
|
|
2268
2368
|
const modSource = (rules) => `// Ork's rules for one session; written by Ork, removed after it.
|
|
2269
|
-
import {
|
|
2369
|
+
import { realpathSync } from "node:fs";
|
|
2370
|
+
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
2270
2371
|
const RULES = ${JSON.stringify(rules)};
|
|
2271
2372
|
// Command Code's own: loading a deferred tool's schema, Ork's report included.
|
|
2272
2373
|
const META = new Set(["search_tools", "tool_search"]);
|
|
2273
2374
|
// Reads a session makes in its worktree without a rule, as Claude Code does.
|
|
2274
2375
|
const READS = new Set(["read_file", "read_multiple_files", "read_directory", "glob", "grep"]);
|
|
2376
|
+
// A read's paths: every value of a path-like key, and any other string that
|
|
2377
|
+
// looks like a path out (absolute, from the home folder, or climbing with ..).
|
|
2378
|
+
const looksOut = text => isAbsolute(text) || text.startsWith("~") || text.split("/").includes("..");
|
|
2275
2379
|
const pathsOf = input => Object.entries(input ?? {})
|
|
2276
|
-
.
|
|
2277
|
-
.
|
|
2278
|
-
.
|
|
2380
|
+
.flatMap(([key, value]) => (Array.isArray(value) ? value : [value]).map(each => [key, each]))
|
|
2381
|
+
.filter(([key, value]) => typeof value === "string" && (/path|file|dir/i.test(key) || looksOut(value)))
|
|
2382
|
+
.map(([, value]) => value);
|
|
2383
|
+
// Where a path really is: the nearest folder of it that exists, with its links
|
|
2384
|
+
// followed, and the rest of the path on top.
|
|
2385
|
+
const real = at => {
|
|
2386
|
+
const rest = [];
|
|
2387
|
+
for (let here = at; ; here = dirname(here)) {
|
|
2388
|
+
try {
|
|
2389
|
+
return resolve(realpathSync(here), ...rest.reverse());
|
|
2390
|
+
} catch {
|
|
2391
|
+
if (dirname(here) === here) return at;
|
|
2392
|
+
rest.push(basename(here));
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
const ROOT = real(RULES.cwd);
|
|
2279
2397
|
const inside = input => pathsOf(input).every(each => {
|
|
2280
|
-
|
|
2281
|
-
|
|
2398
|
+
if (each.startsWith("~")) return false;
|
|
2399
|
+
const from = relative(ROOT, real(resolve(RULES.cwd, each)));
|
|
2400
|
+
return from === "" || (from !== ".." && !from.startsWith("../") && !isAbsolute(from));
|
|
2282
2401
|
});
|
|
2283
2402
|
const TOOLS = ${JSON.stringify(COMMAND_CODE_TOOLS)};
|
|
2284
2403
|
// A rule's word, as Command Code reads it, covers these of Ork's tool names.
|
|
@@ -2290,6 +2409,80 @@ const split = rule => {
|
|
|
2290
2409
|
? { token: rule.trim() }
|
|
2291
2410
|
: { token: rule.slice(0, at).trim(), spec: rule.slice(at + 1, -1) };
|
|
2292
2411
|
};
|
|
2412
|
+
// The simple commands of a shell line, split at ; & | && || and line breaks
|
|
2413
|
+
// outside quotes. null when Ork can't read it: a command or process
|
|
2414
|
+
// substitution, an unclosed quote, or nothing at all.
|
|
2415
|
+
const TICK = String.fromCharCode(96);
|
|
2416
|
+
const BACKSLASH = String.fromCharCode(92);
|
|
2417
|
+
const shellParts = line => {
|
|
2418
|
+
const parts = [];
|
|
2419
|
+
let part = "";
|
|
2420
|
+
let quote = null;
|
|
2421
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
2422
|
+
const ch = line[i];
|
|
2423
|
+
const next = line[i + 1];
|
|
2424
|
+
if (quote === "'") {
|
|
2425
|
+
part += ch;
|
|
2426
|
+
if (ch === "'") quote = null;
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
if (ch === BACKSLASH) {
|
|
2430
|
+
part += ch + (next ?? "");
|
|
2431
|
+
i += 1;
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
if (ch === TICK || (ch === "$" && next === "(")) return null;
|
|
2435
|
+
if (quote === '"') {
|
|
2436
|
+
part += ch;
|
|
2437
|
+
if (ch === '"') quote = null;
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if ((ch === "<" || ch === ">") && next === "(") return null;
|
|
2441
|
+
if (ch === "'" || ch === '"') {
|
|
2442
|
+
quote = ch;
|
|
2443
|
+
part += ch;
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
// Writing a file is an edit, so output goes only to /dev/null or to
|
|
2447
|
+
// another stream (2>&1); any other target makes the line one Ork can't read.
|
|
2448
|
+
if (ch === ">") {
|
|
2449
|
+
let j = i + 1;
|
|
2450
|
+
if (line[j] === ">" || line[j] === "|") j += 1;
|
|
2451
|
+
if (line[j] === "&") {
|
|
2452
|
+
if (!/[0-9-]/.test(line[j + 1] ?? "")) return null;
|
|
2453
|
+
part += line.slice(i, j + 2);
|
|
2454
|
+
i = j + 1;
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
while (line[j] === " " || line[j] === "\\t") j += 1;
|
|
2458
|
+
let k = j;
|
|
2459
|
+
while (k < line.length && !" \\t;&|<>\\n\\r".includes(line[k])) k += 1;
|
|
2460
|
+
if (line.slice(j, k) !== "/dev/null") return null;
|
|
2461
|
+
part += line.slice(i, k);
|
|
2462
|
+
i = k - 1;
|
|
2463
|
+
continue;
|
|
2464
|
+
}
|
|
2465
|
+
// 2>&1 and &> are redirections, not a command's end.
|
|
2466
|
+
const redirect = ch === "&" && (next === ">" || line[i - 1] === ">" || line[i - 1] === "<");
|
|
2467
|
+
if (!redirect && (ch === ";" || ch === "&" || ch === "|" || ch === "\\n" || ch === "\\r")) {
|
|
2468
|
+
parts.push(part);
|
|
2469
|
+
part = "";
|
|
2470
|
+
continue;
|
|
2471
|
+
}
|
|
2472
|
+
part += ch;
|
|
2473
|
+
}
|
|
2474
|
+
if (quote !== null) return null;
|
|
2475
|
+
parts.push(part);
|
|
2476
|
+
const commands = parts.map(norm).filter(each => each !== "");
|
|
2477
|
+
return commands.length === 0 ? null : commands;
|
|
2478
|
+
};
|
|
2479
|
+
// The calls a rule is checked against: a shell line's simple commands, each
|
|
2480
|
+
// alone; one Ork can't read has no command, so only a plain rule covers it.
|
|
2481
|
+
const callsOf = (name, input) => {
|
|
2482
|
+
if ((TOOLS[name] ?? name) !== "Bash" || typeof input?.command !== "string") return [input];
|
|
2483
|
+
const parts = shellParts(input.command);
|
|
2484
|
+
return parts === null ? [{}] : parts.map(command => ({ ...input, command }));
|
|
2485
|
+
};
|
|
2293
2486
|
const shellMatches = (command, pattern) => {
|
|
2294
2487
|
let p = norm(pattern);
|
|
2295
2488
|
const c = norm(command);
|
|
@@ -2316,14 +2509,21 @@ const matches = (rule, name, input) => {
|
|
|
2316
2509
|
if (ork === "Bash" && typeof input?.command === "string") return shellMatches(input.command, spec);
|
|
2317
2510
|
return null;
|
|
2318
2511
|
};
|
|
2512
|
+
// A deny covers a shell line when it covers any of its commands; an allow,
|
|
2513
|
+
// only when every command is covered by one rule or another.
|
|
2514
|
+
const anyDenied = (name, calls) =>
|
|
2515
|
+
calls.some(call => RULES.deny.some(rule => matches(rule, name, call) !== false));
|
|
2516
|
+
const allCovered = (rules, name, calls) =>
|
|
2517
|
+
calls.every(call => rules.some(rule => matches(rule, name, call) === true));
|
|
2319
2518
|
export const decide = (name, input) => {
|
|
2320
|
-
|
|
2519
|
+
const calls = callsOf(name, input);
|
|
2520
|
+
if (anyDenied(name, calls)) return "denied for this node";
|
|
2321
2521
|
if (name.startsWith("mcp__ork__") || META.has(name)) return undefined;
|
|
2322
|
-
if (RULES.only !== undefined && !RULES.only
|
|
2522
|
+
if (RULES.only !== undefined && !allCovered(RULES.only, name, calls)) {
|
|
2323
2523
|
return "not one of this node's tools";
|
|
2324
2524
|
}
|
|
2325
2525
|
if (READS.has(name) && inside(input)) return undefined;
|
|
2326
|
-
if (RULES.allow
|
|
2526
|
+
if (allCovered(RULES.allow, name, calls)) return undefined;
|
|
2327
2527
|
return "not allowed for this node";
|
|
2328
2528
|
};
|
|
2329
2529
|
export default cc => {
|
|
@@ -2436,59 +2636,6 @@ const commandCodeAdapter = {
|
|
|
2436
2636
|
env: () => ({})
|
|
2437
2637
|
}
|
|
2438
2638
|
};
|
|
2439
|
-
// ../runtime/src/secret-file.ts
|
|
2440
|
-
/**
|
|
2441
|
-
* @file A session's private folder for files that hold a secret, such as a
|
|
2442
|
-
* config carrying Ork's bearer token (RFC-014): a 0700 folder outside the
|
|
2443
|
-
* checkout, under the system's temporary folder, named after the process that
|
|
2444
|
-
* made it, with each file written at mode 0600. The session removes it when
|
|
2445
|
-
* it ends; a folder whose process is gone (a crash) is removed when the next
|
|
2446
|
-
* one is made. Nothing here logs what it writes.
|
|
2447
|
-
*/
|
|
2448
|
-
/** Where sessions' private folders live unless a caller names another place. */
|
|
2449
|
-
const secretRoot = () => path.join(tmpdir(), "ork-sessions");
|
|
2450
|
-
/** Removes the folders under `root` whose process is gone; returns them. */
|
|
2451
|
-
const sweepSecretFolders = (root) => {
|
|
2452
|
-
let names;
|
|
2453
|
-
try {
|
|
2454
|
-
names = readdirSync(root);
|
|
2455
|
-
} catch {
|
|
2456
|
-
return [];
|
|
2457
|
-
}
|
|
2458
|
-
return names.flatMap((name) => {
|
|
2459
|
-
const pid = Number(name.split("-")[0]);
|
|
2460
|
-
if (!Number.isInteger(pid) || alive(pid)) return [];
|
|
2461
|
-
const dir = path.join(root, name);
|
|
2462
|
-
rmSync(dir, {
|
|
2463
|
-
recursive: true,
|
|
2464
|
-
force: true
|
|
2465
|
-
});
|
|
2466
|
-
return [dir];
|
|
2467
|
-
});
|
|
2468
|
-
};
|
|
2469
|
-
/** A new private folder under `root`, after sweeping the stale ones. */
|
|
2470
|
-
const makeSecretFolder = (root = secretRoot()) => {
|
|
2471
|
-
mkdirSync(root, {
|
|
2472
|
-
recursive: true,
|
|
2473
|
-
mode: 448
|
|
2474
|
-
});
|
|
2475
|
-
sweepSecretFolders(root);
|
|
2476
|
-
const dir = mkdtempSync(path.join(root, `${String(process.pid)}-`));
|
|
2477
|
-
return {
|
|
2478
|
-
dir,
|
|
2479
|
-
write: (name, content) => {
|
|
2480
|
-
const file = path.join(dir, name);
|
|
2481
|
-
writeFileSync(file, content, { mode: 384 });
|
|
2482
|
-
return file;
|
|
2483
|
-
},
|
|
2484
|
-
remove: () => {
|
|
2485
|
-
rmSync(dir, {
|
|
2486
|
-
recursive: true,
|
|
2487
|
-
force: true
|
|
2488
|
-
});
|
|
2489
|
-
}
|
|
2490
|
-
};
|
|
2491
|
-
};
|
|
2492
2639
|
/** Claude Code tool names, as OpenCode's permission actions. */
|
|
2493
2640
|
const ACTIONS = {
|
|
2494
2641
|
Read: "read",
|