@letrquan/book 0.3.0 → 0.4.0-beta.0
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/CHANGELOG.md +1024 -0
- package/README.md +6 -3
- package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
- package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
- package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
- package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
- package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
- package/dist/index.js +657 -754
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
- package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
- package/dist/job-runner.js +10 -3
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.js +13 -4
- package/dist/job-supervisor.js.map +1 -1
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
- package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
- package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
- package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
- package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
- package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +876 -64
- package/dist/sdk.js +57 -37
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +5 -5
- package/dist/app-GE4OHJOC.js.map +0 -1
- package/dist/chunk-224FJZLH.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-5YHTKWJN.js.map +0 -1
- package/dist/chunk-62QL5QUP.js.map +0 -1
- package/dist/chunk-67UZEWWF.js.map +0 -1
- package/dist/chunk-6OQQOMZR.js +0 -785
- package/dist/chunk-6OQQOMZR.js.map +0 -1
- package/dist/chunk-AQEQX4FF.js.map +0 -1
- package/dist/chunk-C7ADUNPR.js +0 -273
- package/dist/chunk-C7ADUNPR.js.map +0 -1
- package/dist/chunk-EFJ2NH6E.js.map +0 -1
- package/dist/chunk-IME7R2UI.js.map +0 -1
- package/dist/chunk-J62VZYOB.js +0 -1339
- package/dist/chunk-J62VZYOB.js.map +0 -1
- package/dist/chunk-JTPOI3KU.js.map +0 -1
- package/dist/chunk-QYBD22H5.js.map +0 -1
- package/dist/chunk-RFJ6Q6LE.js.map +0 -1
- package/dist/chunk-S4JMID4G.js +0 -259
- package/dist/chunk-S4JMID4G.js.map +0 -1
- package/dist/chunk-TLX3H2CL.js.map +0 -1
- package/dist/chunk-VPNLCMVR.js.map +0 -1
- package/dist/chunk-W3XFHEF4.js.map +0 -1
- package/dist/chunk-X4NJT436.js.map +0 -1
- package/dist/chunk-X66I2NQO.js +0 -137
- package/dist/chunk-X66I2NQO.js.map +0 -1
- package/dist/chunk-Y2VHN7TC.js.map +0 -1
- package/dist/chunk-YC6W4EJH.js.map +0 -1
- package/dist/context-CBOBDY5Q.js +0 -38
- package/dist/loader-S77OVTHH.js +0 -18
- package/dist/memory-store-DZUT5HUH.js +0 -75
- package/dist/scrollback-35ME2GFX.js.map +0 -1
- package/dist/settings-loader-JUNPGAAM.js +0 -35
- package/dist/shell-selection-CDHGJ4GC.js +0 -17
- /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
- /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
- /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
- /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
- /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
- /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
- /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
- /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
|
@@ -5,16 +5,21 @@ import {
|
|
|
5
5
|
} from "./chunk-J4F2QLM2.js";
|
|
6
6
|
import {
|
|
7
7
|
discoverSkills
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DNFQTSMJ.js";
|
|
9
9
|
import {
|
|
10
10
|
discoverCommands
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AJZWLKLQ.js";
|
|
12
12
|
import "./chunk-KXAYUIKP.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-SWPDKLNM.js";
|
|
14
14
|
import "./chunk-2AAOF5B5.js";
|
|
15
15
|
import "./chunk-XHAO3K7I.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-SBTU5BEC.js";
|
|
17
|
+
import "./chunk-YIL6HTJX.js";
|
|
18
|
+
import "./chunk-EEX6QNXV.js";
|
|
19
|
+
import "./chunk-WV3O5PGX.js";
|
|
20
|
+
import "./chunk-HFB74CLM.js";
|
|
21
|
+
import "./chunk-REASC2QG.js";
|
|
22
|
+
import "./chunk-IDYWN37T.js";
|
|
18
23
|
import "./chunk-ZPSZZUWF.js";
|
|
19
24
|
|
|
20
25
|
// src/tui/interactive-assets.ts
|
|
@@ -32,4 +37,4 @@ function loadInteractiveAssets(config) {
|
|
|
32
37
|
export {
|
|
33
38
|
loadInteractiveAssets
|
|
34
39
|
};
|
|
35
|
-
//# sourceMappingURL=interactive-assets-
|
|
40
|
+
//# sourceMappingURL=interactive-assets-73AQO5WP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tui/interactive-assets.ts"],"sourcesContent":["import { discoverCommands } from '../commands/loader.js';\nimport { discoverSkills, type Skill } from '../skills.js';\nimport type { SlashCommand } from '../types/commands.js';\nimport type { AgentConfig } from '../types/runtime.js';\nimport { DEFAULT_THEME_NAME, listCustomThemes, resolveTheme, type ResolvedTheme } from './theme.js';\n\nexport interface InteractiveAssets {\n commands: SlashCommand[];\n skills: Skill[];\n customThemes: string[];\n initialTheme: ResolvedTheme | null;\n}\n\n/** Load filesystem-backed UI metadata before React begins rendering. */\nexport function loadInteractiveAssets(\n config: Pick<AgentConfig, 'workspace' | 'settings'>,\n): InteractiveAssets {\n return {\n commands: discoverCommands(config.workspace),\n skills: discoverSkills(config.workspace, config.settings.skills.overrides, {\n executionOverrides: config.settings.skills.execution,\n enabled: config.settings.skills.enabled,\n }),\n customThemes: listCustomThemes(config.workspace),\n initialTheme: resolveTheme(config.workspace, config.settings.theme ?? DEFAULT_THEME_NAME),\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/tui/interactive-assets.ts"],"sourcesContent":["import { discoverCommands } from '../commands/loader.js';\nimport { discoverSkills, type Skill } from '../skills.js';\nimport type { SlashCommand } from '../types/commands.js';\nimport type { AgentConfig } from '../types/runtime.js';\nimport { DEFAULT_THEME_NAME, listCustomThemes, resolveTheme, type ResolvedTheme } from './theme.js';\n\nexport interface InteractiveAssets {\n commands: SlashCommand[];\n skills: Skill[];\n customThemes: string[];\n initialTheme: ResolvedTheme | null;\n}\n\n/** Load filesystem-backed UI metadata before React begins rendering. */\nexport function loadInteractiveAssets(\n config: Pick<AgentConfig, 'workspace' | 'settings'>,\n): InteractiveAssets {\n return {\n commands: discoverCommands(config.workspace),\n skills: discoverSkills(config.workspace, config.settings.skills.overrides, {\n executionOverrides: config.settings.skills.execution,\n enabled: config.settings.skills.enabled,\n }),\n customThemes: listCustomThemes(config.workspace),\n initialTheme: resolveTheme(config.workspace, config.settings.theme ?? DEFAULT_THEME_NAME),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAcO,SAAS,sBACd,QACmB;AACnB,SAAO;AAAA,IACL,UAAU,iBAAiB,OAAO,SAAS;AAAA,IAC3C,QAAQ,eAAe,OAAO,WAAW,OAAO,SAAS,OAAO,WAAW;AAAA,MACzE,oBAAoB,OAAO,SAAS,OAAO;AAAA,MAC3C,SAAS,OAAO,SAAS,OAAO;AAAA,IAClC,CAAC;AAAA,IACD,cAAc,iBAAiB,OAAO,SAAS;AAAA,IAC/C,cAAc,aAAa,OAAO,WAAW,OAAO,SAAS,SAAS,kBAAkB;AAAA,EAC1F;AACF;","names":[]}
|
package/dist/job-runner.js
CHANGED
|
@@ -2,19 +2,25 @@ import {
|
|
|
2
2
|
loadPersistentShellSpec,
|
|
3
3
|
readJsonFile,
|
|
4
4
|
writeJsonAtomic
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-JGF5HLSI.js";
|
|
6
6
|
import {
|
|
7
7
|
TERMINATE_GRACE_MS,
|
|
8
8
|
signalProcessGroup,
|
|
9
9
|
terminateProcessTree,
|
|
10
10
|
waitForProcessClose,
|
|
11
11
|
waitForProcessGroupExit
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-32WBCUAN.js";
|
|
13
13
|
import "./chunk-RT5SATF7.js";
|
|
14
14
|
import {
|
|
15
15
|
buildChildEnv
|
|
16
16
|
} from "./chunk-XHAO3K7I.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-SBTU5BEC.js";
|
|
18
|
+
import "./chunk-YIL6HTJX.js";
|
|
19
|
+
import "./chunk-EEX6QNXV.js";
|
|
20
|
+
import "./chunk-WV3O5PGX.js";
|
|
21
|
+
import "./chunk-HFB74CLM.js";
|
|
22
|
+
import "./chunk-REASC2QG.js";
|
|
23
|
+
import "./chunk-IDYWN37T.js";
|
|
18
24
|
import "./chunk-ZPSZZUWF.js";
|
|
19
25
|
|
|
20
26
|
// src/job-runner.ts
|
|
@@ -62,6 +68,7 @@ var state = {
|
|
|
62
68
|
status: "starting",
|
|
63
69
|
notify: spec.notify,
|
|
64
70
|
sandboxed: spec.sandboxed,
|
|
71
|
+
gitDirReadOnly: spec.gitDirReadOnly,
|
|
65
72
|
runnerPid: process.pid,
|
|
66
73
|
tokenHash: spec.tokenHash,
|
|
67
74
|
startedAt,
|
package/dist/job-runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/job-runner.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport { buildChildEnv } from './child-env.js';\nimport {\n appendFileSync,\n existsSync,\n readFileSync,\n rmSync,\n statSync,\n truncateSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, extname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n loadPersistentShellSpec,\n readJsonFile,\n writeJsonAtomic,\n type PersistentShellSpec,\n type PersistentShellState,\n} from './jobs/persistent-store.js';\nimport {\n signalProcessGroup,\n terminateProcessTree,\n waitForProcessClose,\n waitForProcessGroupExit,\n TERMINATE_GRACE_MS,\n} from './jobs/process-tree.js';\n\nconst specPath = process.argv[2];\nif (!specPath) {\n process.exitCode = 2;\n throw new Error('Missing persistent shell specification path.');\n}\nconst loaded = loadPersistentShellSpec(specPath);\nif ('error' in loaded) {\n process.exitCode = 2;\n throw new Error(loaded.error);\n}\nconst spec: PersistentShellSpec = loaded.spec;\n/**\n * How long a contended record write may block before it counts as failed. The first record and\n * the terminal record wait up to a second for Windows to release the file: the job cannot start\n * without the first, and the manager waits on the second to call a stop complete. Every other\n * write (the heartbeat, the child pid, `stopping`, a log rotation) is repeated by the next\n * heartbeat, so it gives up almost at once instead of stalling the runner's timers.\n */\nconst RECORD_RENAME_RETRY_BUDGET_MS = 1_000;\nconst BEST_EFFORT_RENAME_RETRY_BUDGET_MS = 50;\n/** A terminal record that still failed to write is retried on a timer this often, this many times. */\nconst TERMINAL_RECORD_RETRY_MS = 250;\nconst TERMINAL_RECORD_ATTEMPTS = 20;\n\nlet child: ChildProcess | undefined;\nlet terminal = false;\nlet terminationInFlight = false;\n// These timers are assigned after the child is wired so startup failures can still call finish().\nlet heartbeat: NodeJS.Timeout | undefined;\nlet controlPoll: NodeJS.Timeout | undefined;\nlet deadline: NodeJS.Timeout | undefined;\nconst startedAt = Date.now();\nlet state: PersistentShellState = {\n version: 1,\n revision: 1,\n id: spec.id,\n command: spec.command,\n title: spec.title,\n workdir: spec.workdir,\n status: 'starting',\n notify: spec.notify,\n sandboxed: spec.sandboxed,\n runnerPid: process.pid,\n tokenHash: spec.tokenHash,\n startedAt,\n heartbeatAt: startedAt,\n outputRotationSequence: 0,\n truncatedBytes: 0,\n timeoutMs: spec.timeoutMs,\n deadlineAt: spec.timeoutMs ? startedAt + spec.timeoutMs : undefined,\n parentSessionId: spec.parentSessionId,\n rootRunId: spec.rootRunId,\n parentRunId: spec.parentRunId,\n outputPath: spec.outputPath,\n controlPath: spec.controlPath,\n completionSequence: 0,\n completionDeliveredSequence: 0,\n completionAcknowledgedSequence: 0,\n};\n\nfunction persist(budgetMs = RECORD_RENAME_RETRY_BUDGET_MS): void {\n state = { ...state, revision: state.revision + 1, heartbeatAt: Date.now() };\n writeJsonAtomic(spec.recordPath, state, { renameRetryBudgetMs: budgetMs });\n}\n\n/**\n * A non-terminal write: the heartbeat, the switch to `stopping`, a log rotation. One that fails\n * must not end the runner, or its job would keep running with nothing left to stop it; the next\n * heartbeat writes the same state again. The failure is counted on the record, which that next\n * write carries, rather than noted in the job's log: the log is the command's own output, and the\n * model reads it through BashOutput.\n */\nfunction persistBestEffort(what: string): void {\n try {\n persist(BEST_EFFORT_RENAME_RETRY_BUDGET_MS);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException | undefined)?.code ?? 'unknown error';\n state = {\n ...state,\n recordWriteFailures: (state.recordWriteFailures ?? 0) + 1,\n lastRecordWriteError: `${what} write failed (${code})`,\n };\n }\n}\n\nfunction appendBounded(data: unknown): void {\n const chunk = Buffer.isBuffer(data) ? data : Buffer.from(String(data));\n if (chunk.length === 0) return;\n try {\n appendFileSync(spec.outputPath, chunk);\n const size = statSync(spec.outputPath).size;\n if (size <= spec.maxLogBytes) return;\n const retained = readFileSync(spec.outputPath).subarray(-spec.maxLogBytes);\n const discarded = size - retained.length;\n truncateSync(spec.outputPath, 0);\n writeFileSync(spec.outputPath, retained);\n state = {\n ...state,\n outputRotationSequence: (state.outputRotationSequence ?? 0) + 1,\n truncatedBytes: (state.truncatedBytes ?? 0) + discarded,\n };\n persistBestEffort('log rotation');\n } catch {\n // Output and its retention are best effort; lifecycle state remains authoritative.\n }\n}\n\n/**\n * End the job's tree. Off Windows the first SIGTERM goes to the supervisor alone, because the\n * supervisor leads the job's own process group and passes the signal on to that group exactly once:\n * signalling the group from here as well would hand the command a second SIGTERM, which a command\n * that has installed its own handler or is midway through a graceful shutdown sees as two stops\n * rather than one. A supervisor that has already exited forwards nothing, so the signal falls\n * through to the group itself. From there the escalation is this process's own, exactly as before,\n * because the supervisor no longer passes SIGKILL along. Windows has no signal for a group, so\n * `taskkill /T /F` still walks the tree from the supervisor there.\n */\nasync function terminateTree(): Promise<boolean> {\n const proc = child;\n const pid = proc?.pid;\n if (!proc || pid === undefined) return true;\n if (process.platform === 'win32') {\n return terminateProcessTree(proc, pid, (timeoutMs) => waitForProcessClose(proc, timeoutMs));\n }\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n signalProcessGroup(proc, pid, 'SIGTERM');\n }\n if (await waitForProcessGroupExit(pid, TERMINATE_GRACE_MS)) return true;\n signalProcessGroup(proc, pid, 'SIGKILL');\n return waitForProcessGroupExit(pid, TERMINATE_GRACE_MS);\n}\n\nfunction finish(\n status: 'exited' | 'failed' | 'killed' | 'timed_out',\n code?: number | null,\n signal?: NodeJS.Signals | string | null,\n stopReason?: string,\n): void {\n if (terminal) return;\n terminal = true;\n // Stop capturing output at terminality: once the record reads terminal, the\n // manager may dismiss the job and delete its files, so a straggling stream\n // chunk must not re-create the log after that point.\n child?.stdout?.off('data', appendBounded);\n child?.stderr?.off('data', appendBounded);\n state = {\n ...state,\n status,\n exitCode: code,\n signal,\n stopReason,\n finishedAt: Date.now(),\n completionSequence: state.completionSequence + 1,\n };\n // Remove the runner-owned control/spec files BEFORE persisting the terminal\n // record. The manager treats an observable terminal record as \"stop\n // complete\" and immediately expects these files to be gone, so the removals\n // must happen-before the rename that publishes the terminal state — the old\n // order left a window where a preempted runner had published \"killed\" while\n // the spec file still existed.\n try {\n rmSync(spec.controlPath, { force: true });\n rmSync(specPath, { force: true });\n } catch {\n // Best effort: a leaked control/spec file is cleaned up by the manager's\n // lost-job reconciliation or dismissal, whereas skipping the terminal\n // persist below would strand the job as \"stopping\" forever.\n }\n if (heartbeat) clearInterval(heartbeat);\n if (controlPoll) clearInterval(controlPoll);\n if (deadline) clearTimeout(deadline);\n publishTerminalRecord(1);\n}\n\n/**\n * Write the terminal record, then exit. It is the one write the manager waits on to call a stop\n * complete, so a write that fails even after the retry budget is tried again on a timer rather\n * than ending the runner with its job still recorded as running. If every attempt fails, the\n * runner says at the end of the job's log what really happened, exits nonzero, and the manager's\n * heartbeat-staleness check reports the job lost.\n */\nfunction publishTerminalRecord(attempt: number): void {\n try {\n persist();\n } catch (error) {\n if (attempt < TERMINAL_RECORD_ATTEMPTS) {\n setTimeout(() => publishTerminalRecord(attempt + 1), TERMINAL_RECORD_RETRY_MS);\n return;\n }\n reportUnrecordedEnd(error);\n process.exitCode = 1;\n setTimeout(() => process.exit(1), 10).unref();\n return;\n }\n setTimeout(() => process.exit(), 10).unref();\n}\n\n/**\n * Every attempt at the terminal record failed, so the manager will find a stale heartbeat and\n * call the job lost. Say what really happened at the end of the job's log, the one place left\n * that someone reads. The log is safe to write: the record never turned terminal, so the\n * manager cannot have dismissed the job and deleted it.\n */\nfunction reportUnrecordedEnd(error: unknown): void {\n const code = (error as NodeJS.ErrnoException | undefined)?.code ?? 'unknown error';\n const outcome =\n state.exitCode === undefined || state.exitCode === null\n ? state.status\n : `${state.status}, exit code ${state.exitCode}`;\n try {\n appendFileSync(\n spec.outputPath,\n `[runner: the job ended (${outcome}) but its record could not be written (${code}); Book will report it as lost]\\n`,\n );\n } catch {\n // There is nowhere left to say it.\n }\n}\n\n/** The supervisor built beside this file: `.js` in dist, `.ts` when run from source through tsx. */\nfunction supervisorPath(): string {\n const self = fileURLToPath(import.meta.url);\n return join(dirname(self), `job-supervisor${extname(self)}`);\n}\n\nfunction failStartup(what: string, error: unknown): never {\n const reason = error instanceof Error ? error.message : String(error);\n process.stderr.write(`Persistent background runner could not ${what}: ${reason}\\n`);\n process.exit(1);\n}\n\n/**\n * The env the supervisor is started with. `NODE_OPTIONS` is meant for the command the user asked\n * for, not for the supervisor standing between them: left in place it makes the supervisor open the\n * inspector and write \"Debugger listening...\" into the job's log, which the runner appends to the\n * command's own output. It is therefore taken out here and carried beside it, under a name of this\n * runner's own, which the supervisor puts back for the command alone.\n *\n * This process inherited Book's environment, so a `NODE_ENV` Book defaulted for its own renderer\n * travels the whole chain: it is not the job's, and a persistent job outlives Book to be the one\n * that most certainly must not carry it.\n */\nfunction supervisorEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = buildChildEnv(process.env, spec.env);\n const nodeOptions = env.NODE_OPTIONS;\n delete env.NODE_OPTIONS;\n if (nodeOptions !== undefined) env.BOOK_SUPERVISED_NODE_OPTIONS = nodeOptions;\n return env;\n}\n\n/**\n * Create the log, write the first record, then start the command under its supervisor. The first\n * record says `starting`, and the job counts as started once the second, carrying the command's\n * pid, has landed: `start()` returns on the first record whose status is not `starting`, so a\n * record that claimed otherwise would report a persistent job with nothing to stop. That first\n * write is therefore the one that cannot be best effort — without it the manager gives up, forgets\n * the job and deletes its files while the command runs on with nothing left to stop it. So until\n * it has landed, a failure ends the runner before anything runs; a failure on the second ends the\n * command first, and both say why on stderr, which `start()` reports.\n */\nfunction startRunner(): void {\n try {\n writeFileSync(spec.outputPath, '', { encoding: 'utf8', mode: 0o600 });\n } catch (error) {\n failStartup('create its job log', error);\n }\n try {\n persist();\n } catch (error) {\n failStartup('write its job record', error);\n }\n let proc: ChildProcess;\n try {\n // `process.execArgv` carries tsx's loader when this runner itself runs from source.\n proc = spawn(process.execPath, [...process.execArgv, supervisorPath(), specPath], {\n cwd: spec.workdir,\n env: supervisorEnv(),\n detached: true,\n stdio: ['pipe', 'pipe', 'pipe'],\n windowsHide: true,\n });\n } catch (error) {\n appendBounded(error instanceof Error ? `${error.message}\\n` : `${String(error)}\\n`);\n process.exitCode = 1;\n finish('failed');\n return;\n }\n child = proc;\n // The supervisor's stdin is its lifeline to this process: never written and never closed, it\n // reaches end-of-file only when this process is gone. See job-supervisor.ts.\n proc.stdin?.on('error', () => {});\n proc.stdout?.on('data', appendBounded);\n proc.stderr?.on('data', appendBounded);\n proc.on('error', (error) => {\n appendBounded(`${error.message}\\n`);\n if (state.status !== 'stopping') finish('failed');\n });\n proc.on('close', (code, signal) => {\n if (terminal || state.status === 'stopping') return;\n finish(code === 0 ? 'exited' : 'failed', code, signal);\n });\n state = { ...state, status: 'running', childPid: proc.pid };\n try {\n persist();\n } catch (error) {\n // The manager calls the job started only once it reads this record. Without it, it would\n // give up, forget the job and delete its files while the command ran on: end the command\n // first, then say why.\n terminal = true;\n void terminateTree().finally(() => failStartup('write its job record', error));\n return;\n }\n heartbeat = setInterval(() => persistBestEffort('heartbeat'), 1_000);\n controlPoll = setInterval(() => {\n if (!existsSync(spec.controlPath) || terminal) return;\n const control = readJsonFile<{ token?: string; action?: string; reason?: string }>(\n spec.controlPath,\n );\n if (control?.token !== spec.token || control.action !== 'stop') return;\n void requestTermination('killed', control.reason ?? 'requested');\n }, 250);\n deadline = spec.timeoutMs\n ? setTimeout(() => {\n void requestTermination('timed_out', 'timeout');\n }, spec.timeoutMs)\n : undefined;\n}\n\nasync function requestTermination(status: 'killed' | 'timed_out', reason: string): Promise<void> {\n if (terminal || terminationInFlight) return;\n terminationInFlight = true;\n state = { ...state, status: 'stopping', stopReason: reason };\n persistBestEffort('stopping state');\n try {\n if (await terminateTree()) {\n finish(status, child?.exitCode, child?.signalCode, reason);\n } else {\n appendBounded('[termination requested; process is still running; retrying]\\n');\n const retry = setTimeout(() => void requestTermination(status, reason), 500);\n retry.unref();\n }\n } finally {\n terminationInFlight = false;\n }\n}\n\nstartRunner();\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/job-runner.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport { buildChildEnv } from './child-env.js';\nimport {\n appendFileSync,\n existsSync,\n readFileSync,\n rmSync,\n statSync,\n truncateSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, extname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n loadPersistentShellSpec,\n readJsonFile,\n writeJsonAtomic,\n type PersistentShellSpec,\n type PersistentShellState,\n} from './jobs/persistent-store.js';\nimport {\n signalProcessGroup,\n terminateProcessTree,\n waitForProcessClose,\n waitForProcessGroupExit,\n TERMINATE_GRACE_MS,\n} from './jobs/process-tree.js';\n\nconst specPath = process.argv[2];\nif (!specPath) {\n process.exitCode = 2;\n throw new Error('Missing persistent shell specification path.');\n}\nconst loaded = loadPersistentShellSpec(specPath);\nif ('error' in loaded) {\n process.exitCode = 2;\n throw new Error(loaded.error);\n}\nconst spec: PersistentShellSpec = loaded.spec;\n/**\n * How long a contended record write may block before it counts as failed. The first record and\n * the terminal record wait up to a second for Windows to release the file: the job cannot start\n * without the first, and the manager waits on the second to call a stop complete. Every other\n * write (the heartbeat, the child pid, `stopping`, a log rotation) is repeated by the next\n * heartbeat, so it gives up almost at once instead of stalling the runner's timers.\n */\nconst RECORD_RENAME_RETRY_BUDGET_MS = 1_000;\nconst BEST_EFFORT_RENAME_RETRY_BUDGET_MS = 50;\n/** A terminal record that still failed to write is retried on a timer this often, this many times. */\nconst TERMINAL_RECORD_RETRY_MS = 250;\nconst TERMINAL_RECORD_ATTEMPTS = 20;\n\nlet child: ChildProcess | undefined;\nlet terminal = false;\nlet terminationInFlight = false;\n// These timers are assigned after the child is wired so startup failures can still call finish().\nlet heartbeat: NodeJS.Timeout | undefined;\nlet controlPoll: NodeJS.Timeout | undefined;\nlet deadline: NodeJS.Timeout | undefined;\nconst startedAt = Date.now();\nlet state: PersistentShellState = {\n version: 1,\n revision: 1,\n id: spec.id,\n command: spec.command,\n title: spec.title,\n workdir: spec.workdir,\n status: 'starting',\n notify: spec.notify,\n sandboxed: spec.sandboxed,\n gitDirReadOnly: spec.gitDirReadOnly,\n runnerPid: process.pid,\n tokenHash: spec.tokenHash,\n startedAt,\n heartbeatAt: startedAt,\n outputRotationSequence: 0,\n truncatedBytes: 0,\n timeoutMs: spec.timeoutMs,\n deadlineAt: spec.timeoutMs ? startedAt + spec.timeoutMs : undefined,\n parentSessionId: spec.parentSessionId,\n rootRunId: spec.rootRunId,\n parentRunId: spec.parentRunId,\n outputPath: spec.outputPath,\n controlPath: spec.controlPath,\n completionSequence: 0,\n completionDeliveredSequence: 0,\n completionAcknowledgedSequence: 0,\n};\n\nfunction persist(budgetMs = RECORD_RENAME_RETRY_BUDGET_MS): void {\n state = { ...state, revision: state.revision + 1, heartbeatAt: Date.now() };\n writeJsonAtomic(spec.recordPath, state, { renameRetryBudgetMs: budgetMs });\n}\n\n/**\n * A non-terminal write: the heartbeat, the switch to `stopping`, a log rotation. One that fails\n * must not end the runner, or its job would keep running with nothing left to stop it; the next\n * heartbeat writes the same state again. The failure is counted on the record, which that next\n * write carries, rather than noted in the job's log: the log is the command's own output, and the\n * model reads it through BashOutput.\n */\nfunction persistBestEffort(what: string): void {\n try {\n persist(BEST_EFFORT_RENAME_RETRY_BUDGET_MS);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException | undefined)?.code ?? 'unknown error';\n state = {\n ...state,\n recordWriteFailures: (state.recordWriteFailures ?? 0) + 1,\n lastRecordWriteError: `${what} write failed (${code})`,\n };\n }\n}\n\nfunction appendBounded(data: unknown): void {\n const chunk = Buffer.isBuffer(data) ? data : Buffer.from(String(data));\n if (chunk.length === 0) return;\n try {\n appendFileSync(spec.outputPath, chunk);\n const size = statSync(spec.outputPath).size;\n if (size <= spec.maxLogBytes) return;\n const retained = readFileSync(spec.outputPath).subarray(-spec.maxLogBytes);\n const discarded = size - retained.length;\n truncateSync(spec.outputPath, 0);\n writeFileSync(spec.outputPath, retained);\n state = {\n ...state,\n outputRotationSequence: (state.outputRotationSequence ?? 0) + 1,\n truncatedBytes: (state.truncatedBytes ?? 0) + discarded,\n };\n persistBestEffort('log rotation');\n } catch {\n // Output and its retention are best effort; lifecycle state remains authoritative.\n }\n}\n\n/**\n * End the job's tree. Off Windows the first SIGTERM goes to the supervisor alone, because the\n * supervisor leads the job's own process group and passes the signal on to that group exactly once:\n * signalling the group from here as well would hand the command a second SIGTERM, which a command\n * that has installed its own handler or is midway through a graceful shutdown sees as two stops\n * rather than one. A supervisor that has already exited forwards nothing, so the signal falls\n * through to the group itself. From there the escalation is this process's own, exactly as before,\n * because the supervisor no longer passes SIGKILL along. Windows has no signal for a group, so\n * `taskkill /T /F` still walks the tree from the supervisor there.\n */\nasync function terminateTree(): Promise<boolean> {\n const proc = child;\n const pid = proc?.pid;\n if (!proc || pid === undefined) return true;\n if (process.platform === 'win32') {\n return terminateProcessTree(proc, pid, (timeoutMs) => waitForProcessClose(proc, timeoutMs));\n }\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n signalProcessGroup(proc, pid, 'SIGTERM');\n }\n if (await waitForProcessGroupExit(pid, TERMINATE_GRACE_MS)) return true;\n signalProcessGroup(proc, pid, 'SIGKILL');\n return waitForProcessGroupExit(pid, TERMINATE_GRACE_MS);\n}\n\nfunction finish(\n status: 'exited' | 'failed' | 'killed' | 'timed_out',\n code?: number | null,\n signal?: NodeJS.Signals | string | null,\n stopReason?: string,\n): void {\n if (terminal) return;\n terminal = true;\n // Stop capturing output at terminality: once the record reads terminal, the\n // manager may dismiss the job and delete its files, so a straggling stream\n // chunk must not re-create the log after that point.\n child?.stdout?.off('data', appendBounded);\n child?.stderr?.off('data', appendBounded);\n state = {\n ...state,\n status,\n exitCode: code,\n signal,\n stopReason,\n finishedAt: Date.now(),\n completionSequence: state.completionSequence + 1,\n };\n // Remove the runner-owned control/spec files BEFORE persisting the terminal\n // record. The manager treats an observable terminal record as \"stop\n // complete\" and immediately expects these files to be gone, so the removals\n // must happen-before the rename that publishes the terminal state — the old\n // order left a window where a preempted runner had published \"killed\" while\n // the spec file still existed.\n try {\n rmSync(spec.controlPath, { force: true });\n rmSync(specPath, { force: true });\n } catch {\n // Best effort: a leaked control/spec file is cleaned up by the manager's\n // lost-job reconciliation or dismissal, whereas skipping the terminal\n // persist below would strand the job as \"stopping\" forever.\n }\n if (heartbeat) clearInterval(heartbeat);\n if (controlPoll) clearInterval(controlPoll);\n if (deadline) clearTimeout(deadline);\n publishTerminalRecord(1);\n}\n\n/**\n * Write the terminal record, then exit. It is the one write the manager waits on to call a stop\n * complete, so a write that fails even after the retry budget is tried again on a timer rather\n * than ending the runner with its job still recorded as running. If every attempt fails, the\n * runner says at the end of the job's log what really happened, exits nonzero, and the manager's\n * heartbeat-staleness check reports the job lost.\n */\nfunction publishTerminalRecord(attempt: number): void {\n try {\n persist();\n } catch (error) {\n if (attempt < TERMINAL_RECORD_ATTEMPTS) {\n setTimeout(() => publishTerminalRecord(attempt + 1), TERMINAL_RECORD_RETRY_MS);\n return;\n }\n reportUnrecordedEnd(error);\n process.exitCode = 1;\n setTimeout(() => process.exit(1), 10).unref();\n return;\n }\n setTimeout(() => process.exit(), 10).unref();\n}\n\n/**\n * Every attempt at the terminal record failed, so the manager will find a stale heartbeat and\n * call the job lost. Say what really happened at the end of the job's log, the one place left\n * that someone reads. The log is safe to write: the record never turned terminal, so the\n * manager cannot have dismissed the job and deleted it.\n */\nfunction reportUnrecordedEnd(error: unknown): void {\n const code = (error as NodeJS.ErrnoException | undefined)?.code ?? 'unknown error';\n const outcome =\n state.exitCode === undefined || state.exitCode === null\n ? state.status\n : `${state.status}, exit code ${state.exitCode}`;\n try {\n appendFileSync(\n spec.outputPath,\n `[runner: the job ended (${outcome}) but its record could not be written (${code}); Book will report it as lost]\\n`,\n );\n } catch {\n // There is nowhere left to say it.\n }\n}\n\n/** The supervisor built beside this file: `.js` in dist, `.ts` when run from source through tsx. */\nfunction supervisorPath(): string {\n const self = fileURLToPath(import.meta.url);\n return join(dirname(self), `job-supervisor${extname(self)}`);\n}\n\nfunction failStartup(what: string, error: unknown): never {\n const reason = error instanceof Error ? error.message : String(error);\n process.stderr.write(`Persistent background runner could not ${what}: ${reason}\\n`);\n process.exit(1);\n}\n\n/**\n * The env the supervisor is started with. `NODE_OPTIONS` is meant for the command the user asked\n * for, not for the supervisor standing between them: left in place it makes the supervisor open the\n * inspector and write \"Debugger listening...\" into the job's log, which the runner appends to the\n * command's own output. It is therefore taken out here and carried beside it, under a name of this\n * runner's own, which the supervisor puts back for the command alone.\n *\n * This process inherited Book's environment, so a `NODE_ENV` Book defaulted for its own renderer\n * travels the whole chain: it is not the job's, and a persistent job outlives Book to be the one\n * that most certainly must not carry it.\n */\nfunction supervisorEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = buildChildEnv(process.env, spec.env);\n const nodeOptions = env.NODE_OPTIONS;\n delete env.NODE_OPTIONS;\n if (nodeOptions !== undefined) env.BOOK_SUPERVISED_NODE_OPTIONS = nodeOptions;\n return env;\n}\n\n/**\n * Create the log, write the first record, then start the command under its supervisor. The first\n * record says `starting`, and the job counts as started once the second, carrying the command's\n * pid, has landed: `start()` returns on the first record whose status is not `starting`, so a\n * record that claimed otherwise would report a persistent job with nothing to stop. That first\n * write is therefore the one that cannot be best effort — without it the manager gives up, forgets\n * the job and deletes its files while the command runs on with nothing left to stop it. So until\n * it has landed, a failure ends the runner before anything runs; a failure on the second ends the\n * command first, and both say why on stderr, which `start()` reports.\n */\nfunction startRunner(): void {\n try {\n writeFileSync(spec.outputPath, '', { encoding: 'utf8', mode: 0o600 });\n } catch (error) {\n failStartup('create its job log', error);\n }\n try {\n persist();\n } catch (error) {\n failStartup('write its job record', error);\n }\n let proc: ChildProcess;\n try {\n // `process.execArgv` carries tsx's loader when this runner itself runs from source.\n proc = spawn(process.execPath, [...process.execArgv, supervisorPath(), specPath], {\n cwd: spec.workdir,\n env: supervisorEnv(),\n detached: true,\n stdio: ['pipe', 'pipe', 'pipe'],\n windowsHide: true,\n });\n } catch (error) {\n appendBounded(error instanceof Error ? `${error.message}\\n` : `${String(error)}\\n`);\n process.exitCode = 1;\n finish('failed');\n return;\n }\n child = proc;\n // The supervisor's stdin is its lifeline to this process: never written and never closed, it\n // reaches end-of-file only when this process is gone. See job-supervisor.ts.\n proc.stdin?.on('error', () => {});\n proc.stdout?.on('data', appendBounded);\n proc.stderr?.on('data', appendBounded);\n proc.on('error', (error) => {\n appendBounded(`${error.message}\\n`);\n if (state.status !== 'stopping') finish('failed');\n });\n proc.on('close', (code, signal) => {\n if (terminal || state.status === 'stopping') return;\n finish(code === 0 ? 'exited' : 'failed', code, signal);\n });\n state = { ...state, status: 'running', childPid: proc.pid };\n try {\n persist();\n } catch (error) {\n // The manager calls the job started only once it reads this record. Without it, it would\n // give up, forget the job and delete its files while the command ran on: end the command\n // first, then say why.\n terminal = true;\n void terminateTree().finally(() => failStartup('write its job record', error));\n return;\n }\n heartbeat = setInterval(() => persistBestEffort('heartbeat'), 1_000);\n controlPoll = setInterval(() => {\n if (!existsSync(spec.controlPath) || terminal) return;\n const control = readJsonFile<{ token?: string; action?: string; reason?: string }>(\n spec.controlPath,\n );\n if (control?.token !== spec.token || control.action !== 'stop') return;\n void requestTermination('killed', control.reason ?? 'requested');\n }, 250);\n deadline = spec.timeoutMs\n ? setTimeout(() => {\n void requestTermination('timed_out', 'timeout');\n }, spec.timeoutMs)\n : undefined;\n}\n\nasync function requestTermination(status: 'killed' | 'timed_out', reason: string): Promise<void> {\n if (terminal || terminationInFlight) return;\n terminationInFlight = true;\n state = { ...state, status: 'stopping', stopReason: reason };\n persistBestEffort('stopping state');\n try {\n if (await terminateTree()) {\n finish(status, child?.exitCode, child?.signalCode, reason);\n } else {\n appendBounded('[termination requested; process is still running; retrying]\\n');\n const retry = setTimeout(() => void requestTermination(status, reason), 500);\n retry.unref();\n }\n } finally {\n terminationInFlight = false;\n }\n}\n\nstartRunner();\n"],"mappings":"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","names":[]}
|
package/dist/job-supervisor.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
spawnCommandExecution
|
|
3
|
+
} from "./chunk-W7SLYS3M.js";
|
|
1
4
|
import {
|
|
2
5
|
loadPersistentShellSpec
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JGF5HLSI.js";
|
|
4
7
|
import {
|
|
5
8
|
TERMINATE_GRACE_MS,
|
|
6
9
|
runTaskkill
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-32WBCUAN.js";
|
|
8
11
|
import "./chunk-RT5SATF7.js";
|
|
9
12
|
import {
|
|
10
13
|
buildChildEnv
|
|
11
14
|
} from "./chunk-XHAO3K7I.js";
|
|
12
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-SBTU5BEC.js";
|
|
16
|
+
import "./chunk-YIL6HTJX.js";
|
|
17
|
+
import "./chunk-EEX6QNXV.js";
|
|
18
|
+
import "./chunk-WV3O5PGX.js";
|
|
19
|
+
import "./chunk-HFB74CLM.js";
|
|
20
|
+
import "./chunk-REASC2QG.js";
|
|
21
|
+
import "./chunk-IDYWN37T.js";
|
|
13
22
|
import "./chunk-ZPSZZUWF.js";
|
|
14
23
|
|
|
15
24
|
// src/job-supervisor.ts
|
|
@@ -45,7 +54,7 @@ var options = {
|
|
|
45
54
|
windowsHide: true,
|
|
46
55
|
env: commandEnv()
|
|
47
56
|
};
|
|
48
|
-
var command = spec.exec ?
|
|
57
|
+
var command = spec.exec ? spawnCommandExecution(spec.exec, options) : spawn(spec.effectiveCommand, { ...options, shell: true });
|
|
49
58
|
var lifelineLost = false;
|
|
50
59
|
command.on("error", (error) => {
|
|
51
60
|
process.stderr.write(`${error.message}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/job-supervisor.ts"],"sourcesContent":["/**\n * The supervisor that keeps a persistent background job's command from outliving its runner.\n *\n * The detached job runner is the only thing that stops a command, so when the runner died any\n * other way than through a stop request (a crash, Task Manager, `kill -9`), the command kept\n * running with nothing left to stop it. On Windows the runner's death did end the shell wrapper,\n * which Node puts in a job object that dies with its parent, but not the process the wrapper had\n * started. Elsewhere the command ran in its own process group, and nothing ended it at all.\n *\n * The runner now starts this process detached, so that it outlives the runner: outside the\n * runner's job object on Windows, as the leader of its own process group elsewhere. This process\n * starts the command. Its stdin is a pipe the runner holds open and never writes, a lifeline: when\n * the runner goes away, for any reason, the operating system closes the pipe, and this process\n * ends the command's whole tree and then itself. The pipe needs no polling and cannot be fooled by\n * a reused pid. The command writes straight into the runner's own pipes, and this process exits\n * with the command's exit code, so the runner records what it always recorded. The pid Book reports\n * for the job is this process's; off Windows a signal sent to it is passed to the whole job.\n *\n * Usage, from the runner: node job-supervisor.js <spec-path>\n */\nimport { spawn, type SpawnOptions } from 'node:child_process';\nimport { constants } from 'node:os';\nimport { buildChildEnv } from './child-env.js';\nimport { loadPersistentShellSpec } from './jobs/persistent-store.js';\nimport { runTaskkill, TERMINATE_GRACE_MS } from './jobs/process-tree.js';\n\n/** Signals this process can die of just as the command did: Node gives them no meaning of its own. */\nconst RERAISABLE_SIGNALS: ReadonlySet<NodeJS.Signals> = new Set([\n 'SIGTERM',\n 'SIGINT',\n 'SIGHUP',\n 'SIGKILL',\n]);\n\nconst [specPath] = process.argv.slice(2);\nif (!specPath) {\n process.stderr.write('Missing persistent shell specification path.\\n');\n process.exit(2);\n}\n// The same checks the runner applies: this process starts the command, so a spec it must not\n// execute is exactly as unsafe here as it is there.\nconst loaded = loadPersistentShellSpec(specPath);\nif ('error' in loaded) {\n process.stderr.write(`${loaded.error}\\n`);\n process.exit(2);\n}\nconst spec = loaded.spec;\n\n/**\n * The env the command is started with. This process was started without `NODE_OPTIONS`, so that the\n * options the user asked for apply to their command rather than opening an inspector here and\n * writing about it into the job's log; the runner carries them beside it for exactly that reason.\n *\n * A `NODE_ENV` Book defaulted for its own renderer is dropped here too, the last link of the\n * chain: the runner has already removed its own, and this process reads what it inherited.\n */\nfunction commandEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = buildChildEnv(process.env);\n const nodeOptions = env.BOOK_SUPERVISED_NODE_OPTIONS;\n delete env.BOOK_SUPERVISED_NODE_OPTIONS;\n if (nodeOptions !== undefined) env.NODE_OPTIONS = nodeOptions;\n return env;\n}\n\nconst options: SpawnOptions = {\n stdio: ['ignore', 'inherit', 'inherit'],\n windowsHide: true,\n env: commandEnv(),\n};\n// Sandboxed specs carry an argv; only unsandboxed ones go through a shell.\nconst command = spec.exec\n ? spawn(spec.exec.file, spec.exec.args, { ...options, shell: false })\n : spawn(spec.effectiveCommand, { ...options, shell: true });\n\n/** Set once the runner is gone and this process has begun ending the command's tree. */\nlet lifelineLost = false;\n\ncommand.on('error', (error) => {\n process.stderr.write(`${error.message}\\n`);\n if (!lifelineLost) process.exit(1);\n});\n\ncommand.on('exit', (code, signal) => {\n // Once the runner is gone, this process exits when the tree has been ended, not before.\n if (lifelineLost) return;\n if (signal && process.platform !== 'win32') {\n if (RERAISABLE_SIGNALS.has(signal)) {\n // Die of the command's signal, so the runner records it the way it did before this process\n // stood between them.\n // Drop the forwarding handler, or it would swallow the re-raised signal.\n process.removeAllListeners(signal);\n process.kill(process.pid, signal);\n setTimeout(() => process.exit(1), 100);\n return;\n }\n // A signal Node gives a meaning of its own cannot be re-raised: SIGUSR1 would open the\n // inspector and write \"Debugger listening...\" into the job's log, and SIGSEGV, SIGBUS, SIGABRT\n // and SIGQUIT would dump this process's core. Report the command's death the way a shell does.\n process.exit(128 + (constants.signals[signal] ?? 0));\n }\n process.exit(code ?? 1);\n});\n\n/**\n * Signals sent to this process, the pid Book reports for the job, are meant for the job: pass each\n * to the whole group, as the runner's own stop does, and exit with the command. Forwarded once per\n * kind, because the forwarded signal reaches this process too. Windows has no such signals; there\n * the runner's stop goes through taskkill.\n */\nconst FORWARDED_SIGNALS: readonly NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];\nconst forwarded = new Set<NodeJS.Signals>();\nif (process.platform !== 'win32') {\n for (const kind of FORWARDED_SIGNALS) {\n process.on(kind, () => {\n if (lifelineLost || forwarded.has(kind)) return;\n forwarded.add(kind);\n try {\n process.kill(-process.pid, kind);\n } catch {\n // The group has already exited.\n }\n });\n }\n}\n\n/** How long one `taskkill` may take, and how many times it is tried before the tree is given up on. */\nconst TASKKILL_TIMEOUT_MS = 10_000;\nconst TASKKILL_ATTEMPTS = 3;\n\n/**\n * `taskkill /T /F` on the command's tree, retried until one of them actually runs. A single attempt\n * that fails or times out leaves the command's descendants running, and here there is nobody to\n * escalate afterwards: this process is about to exit and the runner it served is already gone.\n */\nasync function killCommandTree(pid: number): Promise<void> {\n for (let attempt = 1; attempt <= TASKKILL_ATTEMPTS; attempt++) {\n if (await runTaskkill(pid, TASKKILL_TIMEOUT_MS)) return;\n }\n}\n\n/** The runner is gone: end the command's whole tree, then exit. */\nfunction endCommandTree(): void {\n if (lifelineLost) return;\n lifelineLost = true;\n if (process.platform === 'win32') {\n if (command.pid === undefined) process.exit(1);\n // taskkill walks the tree from the command's root, which is still alive because this process\n // holds it. Exit only when the tree kill is done: as a child of this process, it would die with\n // it, taking the walks it had left undone with it.\n void killCommandTree(command.pid).finally(() => process.exit(1));\n return;\n }\n // This process leads the command's process group, and the group signal below reaches it too. Its\n // own SIGTERM is already spared, by the forwarding handler above returning now that the lifeline\n // is lost; that is what lets the escalation below run, and it is why nothing else has to spare it\n // a second time.\n try {\n process.kill(-process.pid, 'SIGTERM');\n } catch {\n // The group has already exited.\n }\n // The group includes this process, so waitForProcessGroupExit could never report it empty:\n // escalate on the grace alone. SIGKILL ends this process too.\n setTimeout(() => {\n try {\n process.kill(-process.pid, 'SIGKILL');\n } catch {\n // The group has already exited.\n }\n process.exit(1);\n }, TERMINATE_GRACE_MS);\n}\n\nprocess.stdin.on('end', endCommandTree);\nprocess.stdin.on('error', endCommandTree);\nprocess.stdin.resume();\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/job-supervisor.ts"],"sourcesContent":["/**\n * The supervisor that keeps a persistent background job's command from outliving its runner.\n *\n * The detached job runner is the only thing that stops a command, so when the runner died any\n * other way than through a stop request (a crash, Task Manager, `kill -9`), the command kept\n * running with nothing left to stop it. On Windows the runner's death did end the shell wrapper,\n * which Node puts in a job object that dies with its parent, but not the process the wrapper had\n * started. Elsewhere the command ran in its own process group, and nothing ended it at all.\n *\n * The runner now starts this process detached, so that it outlives the runner: outside the\n * runner's job object on Windows, as the leader of its own process group elsewhere. This process\n * starts the command. Its stdin is a pipe the runner holds open and never writes, a lifeline: when\n * the runner goes away, for any reason, the operating system closes the pipe, and this process\n * ends the command's whole tree and then itself. The pipe needs no polling and cannot be fooled by\n * a reused pid. The command writes straight into the runner's own pipes, and this process exits\n * with the command's exit code, so the runner records what it always recorded. The pid Book reports\n * for the job is this process's; off Windows a signal sent to it is passed to the whole job.\n *\n * Usage, from the runner: node job-supervisor.js <spec-path>\n */\nimport { spawn, type SpawnOptions } from 'node:child_process';\nimport { constants } from 'node:os';\nimport { buildChildEnv } from './child-env.js';\nimport { loadPersistentShellSpec } from './jobs/persistent-store.js';\nimport { runTaskkill, TERMINATE_GRACE_MS } from './jobs/process-tree.js';\nimport { spawnCommandExecution } from './shell-selection.js';\n\n/** Signals this process can die of just as the command did: Node gives them no meaning of its own. */\nconst RERAISABLE_SIGNALS: ReadonlySet<NodeJS.Signals> = new Set([\n 'SIGTERM',\n 'SIGINT',\n 'SIGHUP',\n 'SIGKILL',\n]);\n\nconst [specPath] = process.argv.slice(2);\nif (!specPath) {\n process.stderr.write('Missing persistent shell specification path.\\n');\n process.exit(2);\n}\n// The same checks the runner applies: this process starts the command, so a spec it must not\n// execute is exactly as unsafe here as it is there.\nconst loaded = loadPersistentShellSpec(specPath);\nif ('error' in loaded) {\n process.stderr.write(`${loaded.error}\\n`);\n process.exit(2);\n}\nconst spec = loaded.spec;\n\n/**\n * The env the command is started with. This process was started without `NODE_OPTIONS`, so that the\n * options the user asked for apply to their command rather than opening an inspector here and\n * writing about it into the job's log; the runner carries them beside it for exactly that reason.\n *\n * A `NODE_ENV` Book defaulted for its own renderer is dropped here too, the last link of the\n * chain: the runner has already removed its own, and this process reads what it inherited.\n */\nfunction commandEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = buildChildEnv(process.env);\n const nodeOptions = env.BOOK_SUPERVISED_NODE_OPTIONS;\n delete env.BOOK_SUPERVISED_NODE_OPTIONS;\n if (nodeOptions !== undefined) env.NODE_OPTIONS = nodeOptions;\n return env;\n}\n\nconst options: SpawnOptions = {\n stdio: ['ignore', 'inherit', 'inherit'],\n windowsHide: true,\n env: commandEnv(),\n};\n// Sandboxed specs and a session shell spawned as argv carry an `exec`; the rest go through a shell.\n// `exec.env` (on Windows, the command in `BOOK_BASH_COMMAND` for Git Bash or any POSIX-named sh)\n// reaches this spawn alone, never the runner's environment or this process's.\nconst command = spec.exec\n ? spawnCommandExecution(spec.exec, options)\n : spawn(spec.effectiveCommand, { ...options, shell: true });\n\n/** Set once the runner is gone and this process has begun ending the command's tree. */\nlet lifelineLost = false;\n\ncommand.on('error', (error) => {\n process.stderr.write(`${error.message}\\n`);\n if (!lifelineLost) process.exit(1);\n});\n\ncommand.on('exit', (code, signal) => {\n // Once the runner is gone, this process exits when the tree has been ended, not before.\n if (lifelineLost) return;\n if (signal && process.platform !== 'win32') {\n if (RERAISABLE_SIGNALS.has(signal)) {\n // Die of the command's signal, so the runner records it the way it did before this process\n // stood between them.\n // Drop the forwarding handler, or it would swallow the re-raised signal.\n process.removeAllListeners(signal);\n process.kill(process.pid, signal);\n setTimeout(() => process.exit(1), 100);\n return;\n }\n // A signal Node gives a meaning of its own cannot be re-raised: SIGUSR1 would open the\n // inspector and write \"Debugger listening...\" into the job's log, and SIGSEGV, SIGBUS, SIGABRT\n // and SIGQUIT would dump this process's core. Report the command's death the way a shell does.\n process.exit(128 + (constants.signals[signal] ?? 0));\n }\n process.exit(code ?? 1);\n});\n\n/**\n * Signals sent to this process, the pid Book reports for the job, are meant for the job: pass each\n * to the whole group, as the runner's own stop does, and exit with the command. Forwarded once per\n * kind, because the forwarded signal reaches this process too. Windows has no such signals; there\n * the runner's stop goes through taskkill.\n */\nconst FORWARDED_SIGNALS: readonly NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];\nconst forwarded = new Set<NodeJS.Signals>();\nif (process.platform !== 'win32') {\n for (const kind of FORWARDED_SIGNALS) {\n process.on(kind, () => {\n if (lifelineLost || forwarded.has(kind)) return;\n forwarded.add(kind);\n try {\n process.kill(-process.pid, kind);\n } catch {\n // The group has already exited.\n }\n });\n }\n}\n\n/** How long one `taskkill` may take, and how many times it is tried before the tree is given up on. */\nconst TASKKILL_TIMEOUT_MS = 10_000;\nconst TASKKILL_ATTEMPTS = 3;\n\n/**\n * `taskkill /T /F` on the command's tree, retried until one of them actually runs. A single attempt\n * that fails or times out leaves the command's descendants running, and here there is nobody to\n * escalate afterwards: this process is about to exit and the runner it served is already gone.\n */\nasync function killCommandTree(pid: number): Promise<void> {\n for (let attempt = 1; attempt <= TASKKILL_ATTEMPTS; attempt++) {\n if (await runTaskkill(pid, TASKKILL_TIMEOUT_MS)) return;\n }\n}\n\n/** The runner is gone: end the command's whole tree, then exit. */\nfunction endCommandTree(): void {\n if (lifelineLost) return;\n lifelineLost = true;\n if (process.platform === 'win32') {\n if (command.pid === undefined) process.exit(1);\n // taskkill walks the tree from the command's root, which is still alive because this process\n // holds it. Exit only when the tree kill is done: as a child of this process, it would die with\n // it, taking the walks it had left undone with it.\n void killCommandTree(command.pid).finally(() => process.exit(1));\n return;\n }\n // This process leads the command's process group, and the group signal below reaches it too. Its\n // own SIGTERM is already spared, by the forwarding handler above returning now that the lifeline\n // is lost; that is what lets the escalation below run, and it is why nothing else has to spare it\n // a second time.\n try {\n process.kill(-process.pid, 'SIGTERM');\n } catch {\n // The group has already exited.\n }\n // The group includes this process, so waitForProcessGroupExit could never report it empty:\n // escalate on the grace alone. SIGKILL ends this process too.\n setTimeout(() => {\n try {\n process.kill(-process.pid, 'SIGKILL');\n } catch {\n // The group has already exited.\n }\n process.exit(1);\n }, TERMINATE_GRACE_MS);\n}\n\nprocess.stdin.on('end', endCommandTree);\nprocess.stdin.on('error', endCommandTree);\nprocess.stdin.resume();\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectSkippedCommandNotices,
|
|
3
|
+
discoverCommands,
|
|
4
|
+
generateCommandListing,
|
|
5
|
+
inspectCommands,
|
|
6
|
+
resolveCommandBody
|
|
7
|
+
} from "./chunk-AJZWLKLQ.js";
|
|
8
|
+
import "./chunk-KXAYUIKP.js";
|
|
9
|
+
import "./chunk-SWPDKLNM.js";
|
|
10
|
+
import "./chunk-2AAOF5B5.js";
|
|
11
|
+
import "./chunk-XHAO3K7I.js";
|
|
12
|
+
import "./chunk-SBTU5BEC.js";
|
|
13
|
+
import "./chunk-YIL6HTJX.js";
|
|
14
|
+
import "./chunk-EEX6QNXV.js";
|
|
15
|
+
import "./chunk-WV3O5PGX.js";
|
|
16
|
+
import "./chunk-HFB74CLM.js";
|
|
17
|
+
import "./chunk-REASC2QG.js";
|
|
18
|
+
import "./chunk-IDYWN37T.js";
|
|
19
|
+
import "./chunk-ZPSZZUWF.js";
|
|
20
|
+
export {
|
|
21
|
+
collectSkippedCommandNotices,
|
|
22
|
+
discoverCommands,
|
|
23
|
+
generateCommandListing,
|
|
24
|
+
inspectCommands,
|
|
25
|
+
resolveCommandBody
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=loader-DQNPDKOX.js.map
|
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
mcpServersToRecord,
|
|
5
5
|
partitionMcpServersByApproval,
|
|
6
6
|
persistMcpProjectServerChoice
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-PTDC7ZUP.js";
|
|
8
|
+
import "./chunk-HFB74CLM.js";
|
|
9
|
+
import "./chunk-REASC2QG.js";
|
|
10
|
+
import "./chunk-IDYWN37T.js";
|
|
10
11
|
import "./chunk-ZPSZZUWF.js";
|
|
11
12
|
export {
|
|
12
13
|
evaluateMcpServerApproval,
|
|
@@ -15,4 +16,4 @@ export {
|
|
|
15
16
|
partitionMcpServersByApproval,
|
|
16
17
|
persistMcpProjectServerChoice
|
|
17
18
|
};
|
|
18
|
-
//# sourceMappingURL=mcp-approvals-
|
|
19
|
+
//# sourceMappingURL=mcp-approvals-27LJYADQ.js.map
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
isValidMcpServerName,
|
|
5
5
|
loadMcpConfig,
|
|
6
6
|
resolveMcpServerList
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MEOB2DZA.js";
|
|
8
|
+
import "./chunk-EEX6QNXV.js";
|
|
8
9
|
import "./chunk-ZPSZZUWF.js";
|
|
9
10
|
export {
|
|
10
11
|
consoleMcpDiagnosticSink,
|
|
@@ -13,4 +14,4 @@ export {
|
|
|
13
14
|
loadMcpConfig,
|
|
14
15
|
resolveMcpServerList
|
|
15
16
|
};
|
|
16
|
-
//# sourceMappingURL=mcp-config-
|
|
17
|
+
//# sourceMappingURL=mcp-config-GXSODNCM.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MAX_INDEX_LINES,
|
|
3
|
+
MAX_INDEX_BYTES,
|
|
4
|
+
deleteMemoryEntry,
|
|
5
|
+
findGitRoot,
|
|
6
|
+
findLegacyMemoryBackup,
|
|
7
|
+
getMemoryHealth,
|
|
8
|
+
getNewestMemoryWriteTime,
|
|
9
|
+
getProjectMemoryDir,
|
|
10
|
+
legacyBackupNotice,
|
|
11
|
+
listMemoryFiles,
|
|
12
|
+
loadMemoryContext,
|
|
13
|
+
memoryFileForSlug,
|
|
14
|
+
memoryWriteScope,
|
|
15
|
+
migrateLegacyMemoryDir,
|
|
16
|
+
migrateLegacyMemoryFormat,
|
|
17
|
+
sanitizeMemorySlug,
|
|
18
|
+
shortenAtWord,
|
|
19
|
+
slugifyWorkspace
|
|
20
|
+
} from "./chunk-EFXVSQ5J.js";
|
|
21
|
+
import "./chunk-KXAYUIKP.js";
|
|
22
|
+
import "./chunk-SBTU5BEC.js";
|
|
23
|
+
import "./chunk-OJBMDM5H.js";
|
|
24
|
+
import "./chunk-YIL6HTJX.js";
|
|
25
|
+
import "./chunk-EEX6QNXV.js";
|
|
26
|
+
import "./chunk-WV3O5PGX.js";
|
|
27
|
+
import "./chunk-HFB74CLM.js";
|
|
28
|
+
import "./chunk-REASC2QG.js";
|
|
29
|
+
import "./chunk-IDYWN37T.js";
|
|
30
|
+
import "./chunk-ZPSZZUWF.js";
|
|
31
|
+
export {
|
|
32
|
+
DEFAULT_MAX_INDEX_LINES,
|
|
33
|
+
MAX_INDEX_BYTES,
|
|
34
|
+
deleteMemoryEntry,
|
|
35
|
+
findGitRoot,
|
|
36
|
+
findLegacyMemoryBackup,
|
|
37
|
+
getMemoryHealth,
|
|
38
|
+
getNewestMemoryWriteTime,
|
|
39
|
+
getProjectMemoryDir,
|
|
40
|
+
legacyBackupNotice,
|
|
41
|
+
listMemoryFiles,
|
|
42
|
+
loadMemoryContext,
|
|
43
|
+
memoryFileForSlug,
|
|
44
|
+
memoryWriteScope,
|
|
45
|
+
migrateLegacyMemoryDir,
|
|
46
|
+
migrateLegacyMemoryFormat,
|
|
47
|
+
sanitizeMemorySlug,
|
|
48
|
+
shortenAtWord,
|
|
49
|
+
slugifyWorkspace
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=memory-store-I6Z54VUN.js.map
|
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
ratchetModelWindow,
|
|
12
12
|
readModelWindowStore,
|
|
13
13
|
writeModelWindowStore
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-62EEAUID.js";
|
|
15
|
+
import "./chunk-REASC2QG.js";
|
|
16
|
+
import "./chunk-IDYWN37T.js";
|
|
16
17
|
import "./chunk-ZPSZZUWF.js";
|
|
17
18
|
export {
|
|
18
19
|
FileModelWindowStore,
|
|
@@ -28,4 +29,4 @@ export {
|
|
|
28
29
|
readModelWindowStore,
|
|
29
30
|
writeModelWindowStore
|
|
30
31
|
};
|
|
31
|
-
//# sourceMappingURL=model-window-store-
|
|
32
|
+
//# sourceMappingURL=model-window-store-6VKXXTPJ.js.map
|
|
@@ -2,13 +2,15 @@ import {
|
|
|
2
2
|
chatCompletionStream,
|
|
3
3
|
convertTools,
|
|
4
4
|
parseCompatibleUsage
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-AHC7OU75.js";
|
|
6
6
|
import "./chunk-2AAOF5B5.js";
|
|
7
7
|
import "./chunk-RT5SATF7.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-OJBMDM5H.js";
|
|
9
|
+
import "./chunk-IDYWN37T.js";
|
|
10
|
+
import "./chunk-ZPSZZUWF.js";
|
|
9
11
|
export {
|
|
10
12
|
chatCompletionStream,
|
|
11
13
|
convertTools,
|
|
12
14
|
parseCompatibleUsage
|
|
13
15
|
};
|
|
14
|
-
//# sourceMappingURL=openai-compatible-
|
|
16
|
+
//# sourceMappingURL=openai-compatible-IPUNBJ53.js.map
|
|
@@ -2,13 +2,14 @@ import {
|
|
|
2
2
|
evaluateProjectAllowRule,
|
|
3
3
|
partitionProjectAllowRules,
|
|
4
4
|
persistProjectAllowRuleChoice
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-MV5PQNIA.js";
|
|
6
|
+
import "./chunk-HFB74CLM.js";
|
|
7
|
+
import "./chunk-REASC2QG.js";
|
|
8
|
+
import "./chunk-IDYWN37T.js";
|
|
8
9
|
import "./chunk-ZPSZZUWF.js";
|
|
9
10
|
export {
|
|
10
11
|
evaluateProjectAllowRule,
|
|
11
12
|
partitionProjectAllowRules,
|
|
12
13
|
persistProjectAllowRuleChoice
|
|
13
14
|
};
|
|
14
|
-
//# sourceMappingURL=permission-approvals-
|
|
15
|
+
//# sourceMappingURL=permission-approvals-Q5IMFEZI.js.map
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
evaluatePermission,
|
|
8
8
|
evaluatePermissionDetail,
|
|
9
9
|
hasAdjudicationPolicy,
|
|
10
|
+
hasBashAdjudicationPolicy,
|
|
11
|
+
memoryTargetSpellings,
|
|
12
|
+
missingRuleReason,
|
|
10
13
|
parseRule,
|
|
11
14
|
permissionReasonOf,
|
|
12
15
|
permissionResultOf,
|
|
@@ -15,14 +18,25 @@ import {
|
|
|
15
18
|
permissionRuleMatchesCall,
|
|
16
19
|
permissionRuleOf,
|
|
17
20
|
primaryArgForRule
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
23
|
-
import "./chunk-
|
|
24
|
-
import "./chunk-
|
|
25
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-N2G4Q7ME.js";
|
|
22
|
+
import "./chunk-XJRJVXJR.js";
|
|
23
|
+
import "./chunk-UCFJ43OS.js";
|
|
24
|
+
import "./chunk-HOZXKOE3.js";
|
|
25
|
+
import "./chunk-EFXVSQ5J.js";
|
|
26
|
+
import "./chunk-62EEAUID.js";
|
|
27
|
+
import "./chunk-KXAYUIKP.js";
|
|
28
|
+
import "./chunk-W7SLYS3M.js";
|
|
29
|
+
import "./chunk-SBTU5BEC.js";
|
|
30
|
+
import "./chunk-4DKHLZRI.js";
|
|
31
|
+
import "./chunk-OJBMDM5H.js";
|
|
32
|
+
import "./chunk-YIL6HTJX.js";
|
|
33
|
+
import "./chunk-EEX6QNXV.js";
|
|
34
|
+
import "./chunk-MV5PQNIA.js";
|
|
35
|
+
import "./chunk-B5FC3MYZ.js";
|
|
36
|
+
import "./chunk-WV3O5PGX.js";
|
|
37
|
+
import "./chunk-HFB74CLM.js";
|
|
38
|
+
import "./chunk-REASC2QG.js";
|
|
39
|
+
import "./chunk-IDYWN37T.js";
|
|
26
40
|
import "./chunk-ZPSZZUWF.js";
|
|
27
41
|
export {
|
|
28
42
|
ALWAYS_ALLOWED_TOOLS,
|
|
@@ -33,6 +47,9 @@ export {
|
|
|
33
47
|
evaluatePermission,
|
|
34
48
|
evaluatePermissionDetail,
|
|
35
49
|
hasAdjudicationPolicy,
|
|
50
|
+
hasBashAdjudicationPolicy,
|
|
51
|
+
memoryTargetSpellings,
|
|
52
|
+
missingRuleReason,
|
|
36
53
|
parseRule,
|
|
37
54
|
permissionReasonOf,
|
|
38
55
|
permissionResultOf,
|
|
@@ -42,4 +59,4 @@ export {
|
|
|
42
59
|
permissionRuleOf,
|
|
43
60
|
primaryArgForRule
|
|
44
61
|
};
|
|
45
|
-
//# sourceMappingURL=permissions-
|
|
62
|
+
//# sourceMappingURL=permissions-7Y35X6HZ.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LEGACY_BASE_URL_ID,
|
|
3
|
+
MODEL_SELECTION_KEYS,
|
|
4
|
+
PROVIDER_CONNECTION_KEYS,
|
|
5
|
+
WorkspaceProviderRefusalError,
|
|
6
|
+
createWorkspaceProviderGate,
|
|
7
|
+
declaredProviderIds,
|
|
8
|
+
describeApiKeyDeclaration,
|
|
9
|
+
describeDroppedOverride,
|
|
10
|
+
describeLegacyBaseUrl,
|
|
11
|
+
describeOrigin,
|
|
12
|
+
describeReservedProvider,
|
|
13
|
+
describeUnaffectedSelection,
|
|
14
|
+
describeWithheldSelection,
|
|
15
|
+
describeWorkspaceProvider,
|
|
16
|
+
droppedProviderOverrides,
|
|
17
|
+
emptyWorkspaceProviderPartition,
|
|
18
|
+
environmentModelSelections,
|
|
19
|
+
evaluateLegacyBaseUrl,
|
|
20
|
+
legacyBaseUrlFingerprint,
|
|
21
|
+
modelSelectionsOf,
|
|
22
|
+
partitionWorkspaceProviders,
|
|
23
|
+
persistWorkspaceProviderChoice,
|
|
24
|
+
profileModelKey,
|
|
25
|
+
profileOfModelKey,
|
|
26
|
+
providerIdsOf,
|
|
27
|
+
providerPrefixOf,
|
|
28
|
+
unaffectedModelSelections,
|
|
29
|
+
withEnvironmentSelections,
|
|
30
|
+
withheldModelSelections,
|
|
31
|
+
withheldProviderIds,
|
|
32
|
+
withoutWorkspaceProviderOverrides,
|
|
33
|
+
workspaceProviderFingerprint,
|
|
34
|
+
workspaceProviderRefusal
|
|
35
|
+
} from "./chunk-B5FC3MYZ.js";
|
|
36
|
+
import "./chunk-WV3O5PGX.js";
|
|
37
|
+
import "./chunk-HFB74CLM.js";
|
|
38
|
+
import "./chunk-REASC2QG.js";
|
|
39
|
+
import "./chunk-IDYWN37T.js";
|
|
40
|
+
import "./chunk-ZPSZZUWF.js";
|
|
41
|
+
export {
|
|
42
|
+
LEGACY_BASE_URL_ID,
|
|
43
|
+
MODEL_SELECTION_KEYS,
|
|
44
|
+
PROVIDER_CONNECTION_KEYS,
|
|
45
|
+
WorkspaceProviderRefusalError,
|
|
46
|
+
createWorkspaceProviderGate,
|
|
47
|
+
declaredProviderIds,
|
|
48
|
+
describeApiKeyDeclaration,
|
|
49
|
+
describeDroppedOverride,
|
|
50
|
+
describeLegacyBaseUrl,
|
|
51
|
+
describeOrigin,
|
|
52
|
+
describeReservedProvider,
|
|
53
|
+
describeUnaffectedSelection,
|
|
54
|
+
describeWithheldSelection,
|
|
55
|
+
describeWorkspaceProvider,
|
|
56
|
+
droppedProviderOverrides,
|
|
57
|
+
emptyWorkspaceProviderPartition,
|
|
58
|
+
environmentModelSelections,
|
|
59
|
+
evaluateLegacyBaseUrl,
|
|
60
|
+
legacyBaseUrlFingerprint,
|
|
61
|
+
modelSelectionsOf,
|
|
62
|
+
partitionWorkspaceProviders,
|
|
63
|
+
persistWorkspaceProviderChoice,
|
|
64
|
+
profileModelKey,
|
|
65
|
+
profileOfModelKey,
|
|
66
|
+
providerIdsOf,
|
|
67
|
+
providerPrefixOf,
|
|
68
|
+
unaffectedModelSelections,
|
|
69
|
+
withEnvironmentSelections,
|
|
70
|
+
withheldModelSelections,
|
|
71
|
+
withheldProviderIds,
|
|
72
|
+
withoutWorkspaceProviderOverrides,
|
|
73
|
+
workspaceProviderFingerprint,
|
|
74
|
+
workspaceProviderRefusal
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=provider-approvals-5AGH3RVT.js.map
|
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
unbindablePaths,
|
|
16
16
|
unsandboxedRefusalMessage,
|
|
17
17
|
withGitConfigReadOnlyNotice
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
21
|
-
import "./chunk-
|
|
18
|
+
} from "./chunk-XJRJVXJR.js";
|
|
19
|
+
import "./chunk-OJBMDM5H.js";
|
|
20
|
+
import "./chunk-YIL6HTJX.js";
|
|
21
|
+
import "./chunk-EEX6QNXV.js";
|
|
22
|
+
import "./chunk-IDYWN37T.js";
|
|
22
23
|
import "./chunk-ZPSZZUWF.js";
|
|
23
24
|
export {
|
|
24
25
|
buildSandboxExecution,
|
|
@@ -38,4 +39,4 @@ export {
|
|
|
38
39
|
unsandboxedRefusalMessage,
|
|
39
40
|
withGitConfigReadOnlyNotice
|
|
40
41
|
};
|
|
41
|
-
//# sourceMappingURL=sandbox-
|
|
42
|
+
//# sourceMappingURL=sandbox-N2RY72GN.js.map
|