@agentproto/worktree 0.12.0 → 0.13.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/README.md +61 -3
- package/dist/bin/worktree-agent.mjs +3 -3
- package/dist/{chunk-EK5BH2YL.mjs → chunk-3FZWPMLF.mjs} +8 -5
- package/dist/chunk-3FZWPMLF.mjs.map +1 -0
- package/dist/{chunk-EVHLUKC7.mjs → chunk-MYH5XOX6.mjs} +157 -16
- package/dist/chunk-MYH5XOX6.mjs.map +1 -0
- package/dist/{chunk-5SDH2V6M.mjs → chunk-YMDOSMMD.mjs} +4 -4
- package/dist/{chunk-5SDH2V6M.mjs.map → chunk-YMDOSMMD.mjs.map} +1 -1
- package/dist/index.d.ts +97 -1
- package/dist/index.mjs +5 -5
- package/dist/provider/index.mjs +2 -2
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-EK5BH2YL.mjs.map +0 -1
- package/dist/chunk-EVHLUKC7.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -8,9 +8,12 @@ and cleaning up a git worktree — the primitive a `@agentproto/workflow-runtime
|
|
|
8
8
|
|
|
9
9
|
- **`worktree.provision`** — `git worktree add` a new worktree for `repoRoot`
|
|
10
10
|
at `<repoRoot>/../_worktrees/<slug>` on branch `wt/<slug>`, cut from `base`
|
|
11
|
-
(default `origin/main`).
|
|
12
|
-
`
|
|
13
|
-
|
|
11
|
+
(default `origin/main`). Before `depsCmd` runs: `linkPaths` are symlinked in,
|
|
12
|
+
`writeFiles` are written, and `cloneGlobs` (e.g. `node_modules`) are cloned
|
|
13
|
+
in — copy-on-write where the filesystem supports it, else a plain copy,
|
|
14
|
+
never a symlink. Then `depsCmd` runs, then `copyGlobs` (e.g. gitignored
|
|
15
|
+
local secrets) are copied from `repoRoot` into the worktree at the same
|
|
16
|
+
relative path. Finally runs the base tree's `agentproto.json` **setup**
|
|
14
17
|
hooks (unless `runSetup: false`). Returns `{ cwd, branch }`.
|
|
15
18
|
- **`worktree.run-gate`** — run a caller-provided command inside a directory
|
|
16
19
|
and report pass/fail from its exit code.
|
|
@@ -70,6 +73,61 @@ working tree. A feature branch or an agent editing files inside a worktree
|
|
|
70
73
|
therefore **cannot inject** setup/teardown hooks or service commands that run
|
|
71
74
|
on the host; only what a reviewer merged into the base branch executes.
|
|
72
75
|
|
|
76
|
+
## `<repoRoot>/.agentproto/worktree.json` — local, per-machine worktree defaults
|
|
77
|
+
|
|
78
|
+
`agentproto.json` is committed and shared by everyone who clones the repo, so
|
|
79
|
+
it can't hold anything host-specific — a pnpm store path, whether to clone
|
|
80
|
+
`node_modules` at all. `.agentproto/worktree.json` fills that gap: a LOCAL,
|
|
81
|
+
gitignored, host-owned file (same idea as `.agentproto/allowed-commands.json`)
|
|
82
|
+
that one machine can drop to declare its own worktree defaults, without
|
|
83
|
+
committing them:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"cloneGlobs": ["node_modules"],
|
|
88
|
+
"linkPaths": ["../sibling-repo"],
|
|
89
|
+
"copyGlobs": ["envs/**/.env.local"],
|
|
90
|
+
"writeFiles": [
|
|
91
|
+
{ "path": "pnpm-workspace.yaml", "content": "\nvirtualStoreDir: /abs/path/.pnpm-vstores/{slug}\n", "mode": "append" }
|
|
92
|
+
],
|
|
93
|
+
"depsCmd": "pnpm install --prefer-offline"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- **`cloneGlobs`** — glob patterns (relative to `repoRoot`) of gitignored
|
|
98
|
+
dirs/files cloned into the worktree BEFORE `depsCmd` runs. Copy-on-write
|
|
99
|
+
where the filesystem supports it (macOS APFS `cp -c`/clonefile, Linux `cp
|
|
100
|
+
--reflink=auto`), falling back to a plain copy — never a symlink, so
|
|
101
|
+
`depsCmd` can mutate the clone (e.g. `pnpm install` repairing it) without
|
|
102
|
+
touching the source checkout. A directory match is cloned as a whole unit
|
|
103
|
+
(e.g. `node_modules` costs one `readdir`, not a walk of everything inside
|
|
104
|
+
it); `**` is not supported — name each path segment explicitly.
|
|
105
|
+
- **`writeFiles`' `path`/`content`** may use a literal `{slug}` placeholder,
|
|
106
|
+
substituted with the worktree's own slug — e.g. pointing pnpm's
|
|
107
|
+
`virtualStoreDir` at a distinct, collision-free directory per worktree
|
|
108
|
+
(mirrors the real use case this shipped for: `agentik-studio`'s
|
|
109
|
+
`infra/cli/src/commands/wt.ts` `defaultWriteFiles`).
|
|
110
|
+
- **`copyGlobs` / `linkPaths` / `depsCmd`** — same shape and semantics as the
|
|
111
|
+
matching `worktree.provision` inputs / `agentproto.json` fields above.
|
|
112
|
+
|
|
113
|
+
**Precedence** (highest wins): an explicit `worktree.provision` tool input >
|
|
114
|
+
this local file > `agentproto.json`'s committed `worktree.depsCmd`/
|
|
115
|
+
`worktree.linkPaths` (`copyGlobs`/`cloneGlobs`/`writeFiles` have no committed
|
|
116
|
+
equivalent, so for those the chain stops at this file). Same `runSetup` gate
|
|
117
|
+
as the committed config.
|
|
118
|
+
|
|
119
|
+
**Security model**: unlike `agentproto.json`, this file is read straight off
|
|
120
|
+
disk (never `git show`) from `repoRoot` — the SOURCE checkout, never the
|
|
121
|
+
freshly created worktree, which starts with no copy of it (the directory is
|
|
122
|
+
gitignored repo-wide). There is nothing here for the committed-config
|
|
123
|
+
guarantee to defend against: a branch can't smuggle this file in, because it
|
|
124
|
+
can never be committed at all. The trust model is simply "whoever owns this
|
|
125
|
+
machine's filesystem" — same as `allowed-commands.json`.
|
|
126
|
+
|
|
127
|
+
Path-traversal guard: a `cloneGlobs` pattern that could resolve outside
|
|
128
|
+
`repoRoot` (a leading `/`, or any `..` segment) is rejected before any
|
|
129
|
+
filesystem access.
|
|
130
|
+
|
|
73
131
|
### Environment
|
|
74
132
|
|
|
75
133
|
Every hook, script, and service receives:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { connectDaemonAgentSessionHost, worktreeAgentWorkflow } from '../chunk-
|
|
2
|
+
import { connectDaemonAgentSessionHost, worktreeAgentWorkflow } from '../chunk-YMDOSMMD.mjs';
|
|
3
3
|
import '../chunk-E3EM24BS.mjs';
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import '../chunk-
|
|
4
|
+
import '../chunk-MYH5XOX6.mjs';
|
|
5
|
+
import '../chunk-3FZWPMLF.mjs';
|
|
6
6
|
import { runWorkflow } from '@agentproto/workflow-runtime';
|
|
7
7
|
import { resolve } from 'path';
|
|
8
8
|
import { openSync, closeSync, createReadStream, createWriteStream } from 'fs';
|
|
@@ -8,8 +8,8 @@ import { defineTool } from '@agentproto/tool';
|
|
|
8
8
|
|
|
9
9
|
var provisionWorktreeTool = defineTool({
|
|
10
10
|
id: "worktree.provision",
|
|
11
|
-
description: "Create a git worktree for `repoRoot` at a sibling '_worktrees/<slug>' directory (or `dir`, when given), on a new branch 'wt/<slug>' (or `branch`, when given) cut from `base`.
|
|
12
|
-
version: "0.
|
|
11
|
+
description: "Create a git worktree for `repoRoot` at a sibling '_worktrees/<slug>' directory (or `dir`, when given), on a new branch 'wt/<slug>' (or `branch`, when given) cut from `base`. `depsCmd` runs inside the new worktree afterwards (e.g. install deps). `copyGlobs` copies matching files (incl. gitignored, e.g. secrets) from `repoRoot` into the worktree. `cloneGlobs` clones matching dirs/files (e.g. `node_modules`) from `repoRoot` before `depsCmd`, copy-on-write where supported, else a plain copy \u2014 never a symlink. `linkPaths` symlinks gitignored, expensive-to-recreate paths from `repoRoot` before `depsCmd`. `writeFiles` writes/appends generated, worktree-specific config before `depsCmd`; see each field's own description for its `mode` and clobber rules. Each of `depsCmd`/`linkPaths`/`copyGlobs`/`cloneGlobs`/`writeFiles`, when omitted, falls back first to the same-named field in `<repoRoot>/.agentproto/worktree.json` (local, host-owned, gitignored \u2014 read off disk, never a branch), then \u2014 `depsCmd`/`linkPaths` only \u2014 to `worktree.depsCmd`/`worktree.linkPaths` in the base tree's COMMITTED agentproto.json (same `runSetup` gate as the setup hooks below). An explicit input wins over both defaults; the local file wins over the committed one. Local `writeFiles` entries may use a `{slug}` placeholder in `path`/`content`, substituted per call. Also writes a creation-provenance marker into the worktree's private gitdir.",
|
|
12
|
+
version: "0.3.0",
|
|
13
13
|
inputSchema: z.object({
|
|
14
14
|
repoRoot: z.string().describe("Absolute path to the git repository root."),
|
|
15
15
|
base: z.string().optional().describe("Ref the new branch is cut from. Default 'origin/main'."),
|
|
@@ -18,6 +18,9 @@ var provisionWorktreeTool = defineTool({
|
|
|
18
18
|
dir: z.string().optional().describe("Absolute path for the new worktree. Default '<repoRoot>/../_worktrees/<slug>'."),
|
|
19
19
|
depsCmd: z.string().optional().describe("Shell command run inside the worktree after creation, e.g. 'pnpm install --prefer-offline'."),
|
|
20
20
|
copyGlobs: z.array(z.string()).optional().describe("Glob patterns (relative to repoRoot) of gitignored files to copy into the worktree, e.g. 'envs/**/.env.local'."),
|
|
21
|
+
cloneGlobs: z.array(z.string()).optional().describe(
|
|
22
|
+
"Glob patterns (relative to repoRoot) of gitignored dirs/files to clone into the worktree before depsCmd, e.g. 'node_modules'. Copy-on-write where the filesystem supports it, falling back to a plain copy \u2014 never a symlink. Each pattern segment may use '*'/'?'; '**' is not supported (a clone target is a single named entry per level, matched and copied as a whole rather than enumerated file-by-file)."
|
|
23
|
+
),
|
|
21
24
|
linkPaths: z.array(z.string()).optional().describe("Relative paths (dirs or files) symlinked from repoRoot into the worktree before depsCmd, e.g. 'node_modules' or a gitignored sibling workspace repo. Lets the workspace graph resolve without a full reinstall."),
|
|
22
25
|
writeFiles: z.array(
|
|
23
26
|
z.object({
|
|
@@ -26,7 +29,7 @@ var provisionWorktreeTool = defineTool({
|
|
|
26
29
|
mode: z.enum(["create", "append"]).optional().describe("'create' (default): write only if path doesn't already exist. 'append': always append (creating if missing); if git tracks the path, it's marked skip-worktree afterwards so the change never shows as a local modification.")
|
|
27
30
|
})
|
|
28
31
|
).optional().describe("Files written into the worktree before depsCmd runs, e.g. a package-manager config generated for this specific worktree."),
|
|
29
|
-
runSetup: z.boolean().optional().describe("
|
|
32
|
+
runSetup: z.boolean().optional().describe("Apply the declarative worktree lifecycle: `<repoRoot>/.agentproto/worktree.json` (local) and the base tree's agentproto.json (committed) as fallbacks for the inputs above, then the committed config's `worktree.setup` hooks after creation. Default true; a failing setup hook fails provisioning."),
|
|
30
33
|
setupLogPath: z.string().optional().describe("Absolute path to append the FULL, unfiltered stdout+stderr of every `worktree.setup` hook command to (success or failure). Caller-provided \u2014 typically a path under the spawning session's own directory, so the full output outlives a subsequently-reclaimed worktree. Best-effort: a write failure never fails provisioning. When omitted, only a short, noise-filtered tail survives in a failing hook's own error message."),
|
|
31
34
|
retrySetupOnFailure: z.boolean().optional().describe("Re-run a failing `worktree.setup` command exactly once before giving up \u2014 a complement to (never a substitute for) a real root-cause fix, meant for unattended callers with no way to retry themselves short of provisioning an entirely new worktree. Default false: an interactive caller (e.g. `agentproto worktree new`) sees a genuine failure on the first try rather than waiting through an identical rerun.")
|
|
32
35
|
}),
|
|
@@ -160,5 +163,5 @@ var listServicesTool = defineTool({
|
|
|
160
163
|
});
|
|
161
164
|
|
|
162
165
|
export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool };
|
|
163
|
-
//# sourceMappingURL=chunk-
|
|
164
|
-
//# sourceMappingURL=chunk-
|
|
166
|
+
//# sourceMappingURL=chunk-3FZWPMLF.mjs.map
|
|
167
|
+
//# sourceMappingURL=chunk-3FZWPMLF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/provision-worktree.tool.ts","../src/tools/cleanup-worktree.tool.ts","../src/tools/run-gate.tool.ts","../src/tools/run-script.tool.ts","../src/tools/service.schema.ts","../src/tools/start-service.tool.ts","../src/tools/stop-service.tool.ts","../src/tools/list-services.tool.ts"],"names":["defineTool","z"],"mappings":"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","file":"chunk-3FZWPMLF.mjs","sourcesContent":["import { z } from \"zod\"\nimport { defineTool } from \"@agentproto/tool\"\n\n/**\n * AIP-14 contract: create a git worktree off a base ref, on its own branch,\n * optionally installing deps and copying gitignored files (secrets) into it.\n * Agnostic — no hardcoded package manager or env layout; both are inputs.\n */\nexport const provisionWorktreeTool = defineTool({\n id: \"worktree.provision\",\n description:\n \"Create a git worktree for `repoRoot` at a sibling '_worktrees/<slug>' \" +\n \"directory (or `dir`, when given), on a new branch 'wt/<slug>' (or \" +\n \"`branch`, when given) cut from `base`. `depsCmd` runs inside the new \" +\n \"worktree afterwards (e.g. install deps). `copyGlobs` copies matching \" +\n \"files (incl. gitignored, e.g. secrets) from `repoRoot` into the \" +\n \"worktree. `cloneGlobs` clones matching dirs/files (e.g. \" +\n \"`node_modules`) from `repoRoot` before `depsCmd`, copy-on-write where \" +\n \"supported, else a plain copy — never a symlink. `linkPaths` symlinks \" +\n \"gitignored, expensive-to-recreate paths from `repoRoot` before \" +\n \"`depsCmd`. `writeFiles` writes/appends generated, worktree-specific \" +\n \"config before `depsCmd`; see each field's own description for its \" +\n \"`mode` and clobber rules. Each of `depsCmd`/`linkPaths`/`copyGlobs`/\" +\n \"`cloneGlobs`/`writeFiles`, when omitted, falls back first to the \" +\n \"same-named field in `<repoRoot>/.agentproto/worktree.json` (local, \" +\n \"host-owned, gitignored — read off disk, never a branch), then — \" +\n \"`depsCmd`/`linkPaths` only — to `worktree.depsCmd`/`worktree.linkPaths` \" +\n \"in the base tree's COMMITTED agentproto.json (same `runSetup` gate as \" +\n \"the setup hooks below). An explicit input wins over both defaults; the \" +\n \"local file wins over the committed one. Local `writeFiles` entries may \" +\n \"use a `{slug}` placeholder in `path`/`content`, substituted per call. \" +\n \"Also writes a creation-provenance marker into the worktree's private \" +\n \"gitdir.\",\n version: \"0.3.0\",\n inputSchema: z.object({\n repoRoot: z.string().describe(\"Absolute path to the git repository root.\"),\n base: z\n .string()\n .optional()\n .describe(\"Ref the new branch is cut from. Default 'origin/main'.\"),\n slug: z\n .string()\n .regex(/^[a-z0-9][a-z0-9-]*$/, \"slug must be lowercase kebab-case\")\n .describe(\"Short identifier — names both the worktree directory and its branch.\"),\n branch: z\n .string()\n .optional()\n .describe(\"Branch name for the new worktree. Default 'wt/<slug>'.\"),\n dir: z\n .string()\n .optional()\n .describe(\"Absolute path for the new worktree. Default '<repoRoot>/../_worktrees/<slug>'.\"),\n depsCmd: z\n .string()\n .optional()\n .describe(\"Shell command run inside the worktree after creation, e.g. 'pnpm install --prefer-offline'.\"),\n copyGlobs: z\n .array(z.string())\n .optional()\n .describe(\"Glob patterns (relative to repoRoot) of gitignored files to copy into the worktree, e.g. 'envs/**/.env.local'.\"),\n cloneGlobs: z\n .array(z.string())\n .optional()\n .describe(\n \"Glob patterns (relative to repoRoot) of gitignored dirs/files to clone into the worktree before depsCmd, e.g. 'node_modules'. Copy-on-write where the filesystem supports it, falling back to a plain copy — never a symlink. Each pattern segment may use '*'/'?'; '**' is not supported (a clone target is a single named entry per level, matched and copied as a whole rather than enumerated file-by-file).\",\n ),\n linkPaths: z\n .array(z.string())\n .optional()\n .describe(\"Relative paths (dirs or files) symlinked from repoRoot into the worktree before depsCmd, e.g. 'node_modules' or a gitignored sibling workspace repo. Lets the workspace graph resolve without a full reinstall.\"),\n writeFiles: z\n .array(\n z.object({\n path: z.string().describe(\"Path relative to the worktree root.\"),\n content: z.string().describe(\"File content, written or appended verbatim.\"),\n mode: z\n .enum([\"create\", \"append\"])\n .optional()\n .describe(\"'create' (default): write only if path doesn't already exist. 'append': always append (creating if missing); if git tracks the path, it's marked skip-worktree afterwards so the change never shows as a local modification.\"),\n }),\n )\n .optional()\n .describe(\"Files written into the worktree before depsCmd runs, e.g. a package-manager config generated for this specific worktree.\"),\n runSetup: z\n .boolean()\n .optional()\n .describe(\"Apply the declarative worktree lifecycle: `<repoRoot>/.agentproto/worktree.json` (local) and the base tree's agentproto.json (committed) as fallbacks for the inputs above, then the committed config's `worktree.setup` hooks after creation. Default true; a failing setup hook fails provisioning.\"),\n setupLogPath: z\n .string()\n .optional()\n .describe(\"Absolute path to append the FULL, unfiltered stdout+stderr of every `worktree.setup` hook command to (success or failure). Caller-provided — typically a path under the spawning session's own directory, so the full output outlives a subsequently-reclaimed worktree. Best-effort: a write failure never fails provisioning. When omitted, only a short, noise-filtered tail survives in a failing hook's own error message.\"),\n retrySetupOnFailure: z\n .boolean()\n .optional()\n .describe(\"Re-run a failing `worktree.setup` command exactly once before giving up — a complement to (never a substitute for) a real root-cause fix, meant for unattended callers with no way to retry themselves short of provisioning an entirely new worktree. Default false: an interactive caller (e.g. `agentproto worktree new`) sees a genuine failure on the first try rather than waiting through an identical rerun.\"),\n }),\n outputSchema: z.object({\n cwd: z.string().describe(\"Absolute path to the created worktree.\"),\n branch: z.string().describe(\"The branch the worktree was created on.\"),\n }),\n mutates: [\"fs:write\"],\n approval: \"auto\",\n riskLevel: 1,\n})\n","import { z } from \"zod\"\nimport { defineTool } from \"@agentproto/tool\"\n\n/**\n * AIP-14 contract: remove a git worktree, optionally deleting its branch.\n *\n * Guarded by default (PLAN.md §5.2 layer 3): with neither discard flag set,\n * this runs a plain `git worktree remove` — git itself is the final arbiter\n * and refuses on modified or unignored-untracked files, tolerating gitignored\n * trees like `node_modules/`. `discardUntracked` / `discardModified` each\n * authorize exactly one class of destruction git otherwise refuses; only\n * when the flags cover everything actually dirty does `--force` get passed\n * through. This replaces an unconditional `--force` that used to destroy\n * real edits indiscriminately (the incident this contract change exists to\n * prevent) — hence `riskLevel: 2`, up from the old unconditional-force `1`.\n */\nexport const cleanupWorktreeTool = defineTool({\n id: \"worktree.cleanup\",\n description:\n \"Remove a git worktree created by 'worktree.provision'. Refuses if the \" +\n \"worktree has modified tracked files or unignored untracked files, \" +\n \"unless the matching `discardModified` / `discardUntracked` flag is \" +\n \"set — a clean or gitignore-only tree (e.g. installed deps) always \" +\n \"removes with no flags. If `deleteBranch` is true and `branch` is \" +\n \"given, also force-delete that branch (only ever called for merged \" +\n \"branches by this package's own callers).\",\n version: \"0.2.0\",\n inputSchema: z.object({\n repoRoot: z.string().describe(\"Absolute path to the git repository root.\"),\n cwd: z.string().describe(\"Absolute path to the worktree to remove.\"),\n branch: z.string().optional().describe(\"The worktree's branch, for deleteBranch.\"),\n deleteBranch: z.boolean().optional().describe(\"Also force-delete `branch` after removing the worktree.\"),\n base: z.string().optional().describe(\"Ref whose committed agentproto.json supplies teardown hooks. Default 'origin/main'.\"),\n runTeardown: z.boolean().optional().describe(\"Run the `worktree.teardown` hooks before removal. Default true; teardown failures are logged, never blocking.\"),\n discardUntracked: z.boolean().optional().describe(\"Authorize discarding unignored untracked files. Default false: refuse if any are present.\"),\n discardModified: z.boolean().optional().describe(\"Authorize discarding modified tracked files (staged or unstaged). Default false: refuse if any are present.\"),\n }),\n outputSchema: z.object({\n removed: z.literal(true),\n }),\n mutates: [\"fs:write\"],\n approval: \"auto\",\n riskLevel: 2,\n})\n","import { z } from \"zod\"\nimport { defineTool } from \"@agentproto/tool\"\n\n/**\n * AIP-14 contract: run a caller-provided gate/check command inside a\n * directory (typically a provisioned worktree) and report pass/fail.\n * `cmd` is a trusted, developer-authored config value (same trust model as\n * a CI job's `script:` line), run through a shell so it may include flags.\n */\nexport const runGateTool = defineTool({\n id: \"worktree.run-gate\",\n description:\n \"Run a gate command (e.g. 'pnpm test') inside `cwd` and report pass/fail \" +\n \"from its exit code, along with captured stdout/stderr.\",\n version: \"0.1.0\",\n inputSchema: z.object({\n cwd: z.string().describe(\"Directory to run the command in.\"),\n cmd: z.string().describe(\"Shell command to run, e.g. 'pnpm test'.\"),\n }),\n outputSchema: z.object({\n passed: z.boolean(),\n exitCode: z.number().int(),\n stdout: z.string(),\n stderr: z.string(),\n }),\n mutates: [],\n approval: \"auto\",\n riskLevel: 1,\n})\n","import { z } from \"zod\"\nimport { defineTool } from \"@agentproto/tool\"\n\n/**\n * AIP-14 contract: run a one-shot named `scripts.<name>` command from the\n * repo's committed `agentproto.json` inside a worktree, with the\n * `AGENTPROTO_*` context env injected. For long-running services use\n * `worktree.start-service` instead.\n */\nexport const runScriptTool = defineTool({\n id: \"worktree.run-script\",\n description:\n \"Run a declared `scripts.<name>` command (from the base tree's \" +\n \"agentproto.json) inside `worktreePath`, injecting AGENTPROTO_* env, and \" +\n \"report pass/fail with captured stdout/stderr. For a `type: \\\"service\\\"` \" +\n \"script prefer `worktree.start-service`.\",\n version: \"0.1.0\",\n inputSchema: z.object({\n repoRoot: z.string().describe(\"Absolute path to the source git repository root.\"),\n worktreePath: z.string().describe(\"Absolute path to the worktree to run in.\"),\n branch: z.string().describe(\"The worktree's branch name (for AGENTPROTO_BRANCH_NAME).\"),\n script: z.string().describe(\"Name of the script under `scripts` to run.\"),\n base: z.string().optional().describe(\"Ref whose committed agentproto.json is read. Default 'origin/main'.\"),\n }),\n outputSchema: z.object({\n passed: z.boolean(),\n exitCode: z.number().int(),\n stdout: z.string(),\n stderr: z.string(),\n }),\n mutates: [\"fs:write\"],\n approval: \"auto\",\n riskLevel: 1,\n})\n","import { z } from \"zod\"\n\n/** Shared shape of a supervised service's status across the service tools. */\nexport const serviceStatusSchema = z.object({\n name: z.string().describe(\"The declared script name.\"),\n hostname: z.string().describe(\"The *.localhost host the reverse proxy routes to this service.\"),\n port: z.number().int().describe(\"The port the service listens on.\"),\n url: z.string().describe(\"The service's reverse-proxy URL.\"),\n pid: z.number().int().nullable().describe(\"Child process id while running, else null.\"),\n status: z.enum([\"running\", \"exited\"]).describe(\"Whether the service process is up.\"),\n exitCode: z.number().int().nullable().describe(\"Exit code once it has exited, else null.\"),\n startedAt: z.string().nullable().describe(\"ISO timestamp of the last start, else null.\"),\n})\n\nexport type ServiceStatusOutput = z.infer<typeof serviceStatusSchema>\n","import { z } from \"zod\"\nimport { defineTool } from \"@agentproto/tool\"\nimport { serviceStatusSchema } from \"./service.schema.js\"\n\n/**\n * AIP-14 contract: start a declared `type: \"service\"` script for a worktree.\n * Allocates the service's port (declared port if free, else ephemeral),\n * injects AGENTPROTO_PORT/URL + peer env, and registers its hostname with the\n * reverse proxy. Idempotent per worktree+name.\n */\nexport const startServiceTool = defineTool({\n id: \"worktree.start-service\",\n description:\n \"Start a declared `scripts.<name>` service for a worktree as a supervised \" +\n \"long-running child process. Allocates a port, injects AGENTPROTO_PORT / \" +\n \"AGENTPROTO_URL and AGENTPROTO_SERVICE_<PEER>_PORT|URL for siblings, and \" +\n \"registers a `<script>--<branch>--<repo>.localhost` proxy route.\",\n version: \"0.1.0\",\n inputSchema: z.object({\n repoRoot: z.string().describe(\"Absolute path to the source git repository root.\"),\n worktreePath: z.string().describe(\"Absolute path to the worktree.\"),\n branch: z.string().describe(\"The worktree's branch name.\"),\n script: z.string().describe(\"Name of the `type: \\\"service\\\"` script to start.\"),\n base: z.string().optional().describe(\"Ref whose committed agentproto.json is read. Default 'origin/main'.\"),\n proxyPort: z.number().int().min(1).max(65535).optional().describe(\"Reverse-proxy port used to build the service URL.\"),\n }),\n outputSchema: serviceStatusSchema,\n mutates: [\"process:spawn\"],\n approval: \"auto\",\n riskLevel: 1,\n})\n","import { z } from \"zod\"\nimport { defineTool } from \"@agentproto/tool\"\n\n/** AIP-14 contract: stop a running supervised service for a worktree. */\nexport const stopServiceTool = defineTool({\n id: \"worktree.stop-service\",\n description:\n \"Stop a supervised service (SIGTERM) for a worktree and drop its reverse-\" +\n \"proxy route. A no-op (stopped: false) if the service isn't running.\",\n version: \"0.1.0\",\n inputSchema: z.object({\n worktreePath: z.string().describe(\"Absolute path to the worktree.\"),\n script: z.string().describe(\"Name of the service to stop.\"),\n }),\n outputSchema: z.object({\n stopped: z.boolean().describe(\"Whether a running service was actually stopped.\"),\n }),\n mutates: [\"process:spawn\"],\n approval: \"auto\",\n riskLevel: 1,\n})\n","import { z } from \"zod\"\nimport { defineTool } from \"@agentproto/tool\"\nimport { serviceStatusSchema } from \"./service.schema.js\"\n\n/**\n * AIP-14 contract: list every declared service for a worktree with its\n * resolved port / proxy URL and current run status. Resolves (allocating\n * ports for) the worktree's services on first call so declared-but-unstarted\n * services still appear.\n */\nexport const listServicesTool = defineTool({\n id: \"worktree.list-services\",\n description:\n \"List the declared services for a worktree — each with its allocated \" +\n \"port, `*.localhost` proxy URL, and running/exited status.\",\n version: \"0.1.0\",\n inputSchema: z.object({\n repoRoot: z.string().describe(\"Absolute path to the source git repository root.\"),\n worktreePath: z.string().describe(\"Absolute path to the worktree.\"),\n branch: z.string().describe(\"The worktree's branch name.\"),\n base: z.string().optional().describe(\"Ref whose committed agentproto.json is read. Default 'origin/main'.\"),\n proxyPort: z.number().int().min(1).max(65535).optional().describe(\"Reverse-proxy port used to build service URLs.\"),\n }),\n outputSchema: z.object({\n services: z.array(serviceStatusSchema),\n }),\n mutates: [],\n approval: \"auto\",\n riskLevel: 0,\n})\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { provisionWorktreeTool, cleanupWorktreeTool, runGateTool, runScriptTool, startServiceTool, stopServiceTool, listServicesTool } from './chunk-
|
|
1
|
+
import { provisionWorktreeTool, cleanupWorktreeTool, runGateTool, runScriptTool, startServiceTool, stopServiceTool, listServicesTool } from './chunk-3FZWPMLF.mjs';
|
|
2
2
|
import { implementTool, defineDriver } from '@agentproto/driver';
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
|
-
import { lstat, mkdir, symlink, appendFile, writeFile, copyFile, rename, readdir, stat
|
|
4
|
+
import { lstat, mkdir, symlink, appendFile, writeFile, copyFile, readFile, rename, readdir, stat } from 'fs/promises';
|
|
5
5
|
import { resolve, join, dirname, basename, posix, sep } from 'path';
|
|
6
6
|
import { ToolError } from '@agentproto/tool';
|
|
7
7
|
import { spawn } from 'child_process';
|
|
@@ -106,6 +106,53 @@ async function expandGlob(root, pattern) {
|
|
|
106
106
|
const candidates = await walk(root, base);
|
|
107
107
|
return candidates.filter((rel) => regex.test(rel));
|
|
108
108
|
}
|
|
109
|
+
var GlobTraversalError = class extends Error {
|
|
110
|
+
constructor(pattern) {
|
|
111
|
+
super(`glob pattern '${pattern}' escapes the repo root (path traversal)`);
|
|
112
|
+
this.name = "GlobTraversalError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
function assertNoTraversal(pattern) {
|
|
116
|
+
if (pattern.startsWith("/")) throw new GlobTraversalError(pattern);
|
|
117
|
+
if (pattern.split("/").some((seg) => seg === "..")) throw new GlobTraversalError(pattern);
|
|
118
|
+
}
|
|
119
|
+
async function expandCloneGlob(root, pattern) {
|
|
120
|
+
assertNoTraversal(pattern);
|
|
121
|
+
const segments = pattern.split("/").filter((seg) => seg.length > 0);
|
|
122
|
+
if (segments.length === 0) return [];
|
|
123
|
+
if (segments.includes("**")) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`cloneGlobs pattern '${pattern}' uses '**', which is not supported \u2014 name each path segment explicitly so a directory match clones as one unit.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
async function walkMatch(dirRel, idx) {
|
|
129
|
+
const seg = segments[idx];
|
|
130
|
+
const isLast = idx === segments.length - 1;
|
|
131
|
+
const abs = dirRel ? join(root, dirRel) : root;
|
|
132
|
+
let entries;
|
|
133
|
+
try {
|
|
134
|
+
entries = await readdir(abs, { withFileTypes: true });
|
|
135
|
+
} catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const regex = new RegExp(`^${escapeSegment(seg)}$`);
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
if (entry.name === ".git") continue;
|
|
142
|
+
if (!regex.test(entry.name)) continue;
|
|
143
|
+
const rel = dirRel ? posix.join(dirRel, entry.name) : entry.name;
|
|
144
|
+
if (isLast) {
|
|
145
|
+
out.push(rel);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (entry.isDirectory()) {
|
|
149
|
+
out.push(...await walkMatch(rel, idx + 1));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
return walkMatch("", 0);
|
|
155
|
+
}
|
|
109
156
|
var CONFIG_FILENAME = "agentproto.json";
|
|
110
157
|
var hookSchema = z.union([z.string(), z.array(z.string())]);
|
|
111
158
|
var scriptSchema = z.object({
|
|
@@ -182,6 +229,86 @@ async function loadConfigFromBase(repoRoot, base = "origin/main") {
|
|
|
182
229
|
}
|
|
183
230
|
return parseConfig(result.stdout);
|
|
184
231
|
}
|
|
232
|
+
var LOCAL_WORKTREE_CONFIG_REL = ".agentproto/worktree.json";
|
|
233
|
+
var localWriteFileEntrySchema = z.object({
|
|
234
|
+
path: z.string().min(1, "writeFiles[].path must be non-empty"),
|
|
235
|
+
content: z.string(),
|
|
236
|
+
mode: z.enum(["create", "append"]).optional()
|
|
237
|
+
});
|
|
238
|
+
var localWorktreeConfigSchema = z.object({
|
|
239
|
+
copyGlobs: z.array(z.string()).optional(),
|
|
240
|
+
/** Gitignored dirs/files cloned (copy-on-write where the filesystem
|
|
241
|
+
* supports it) from the source checkout into the worktree before
|
|
242
|
+
* `depsCmd` runs — see `clone.ts`. */
|
|
243
|
+
cloneGlobs: z.array(z.string()).optional(),
|
|
244
|
+
writeFiles: z.array(localWriteFileEntrySchema).optional(),
|
|
245
|
+
depsCmd: z.string().optional(),
|
|
246
|
+
linkPaths: z.array(z.string()).optional()
|
|
247
|
+
});
|
|
248
|
+
function parseLocalWorktreeConfig(raw) {
|
|
249
|
+
let json;
|
|
250
|
+
try {
|
|
251
|
+
json = JSON.parse(raw);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
throw new ConfigError(
|
|
254
|
+
`${LOCAL_WORKTREE_CONFIG_REL} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const result = localWorktreeConfigSchema.safeParse(json);
|
|
258
|
+
if (!result.success) {
|
|
259
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
|
|
260
|
+
throw new ConfigError(`${LOCAL_WORKTREE_CONFIG_REL} failed validation: ${issues}`);
|
|
261
|
+
}
|
|
262
|
+
return result.data;
|
|
263
|
+
}
|
|
264
|
+
async function loadLocalWorktreeConfig(workspace) {
|
|
265
|
+
const path = resolve(workspace, LOCAL_WORKTREE_CONFIG_REL);
|
|
266
|
+
let raw;
|
|
267
|
+
try {
|
|
268
|
+
raw = await readFile(path, "utf8");
|
|
269
|
+
} catch (err) {
|
|
270
|
+
if (err.code === "ENOENT") return null;
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
return parseLocalWorktreeConfig(raw);
|
|
274
|
+
}
|
|
275
|
+
var SLUG_PLACEHOLDER = /\{slug\}/g;
|
|
276
|
+
function applySlugPlaceholder(text, slug) {
|
|
277
|
+
return text.replace(SLUG_PLACEHOLDER, slug);
|
|
278
|
+
}
|
|
279
|
+
function resolveLocalWriteFiles(files, slug) {
|
|
280
|
+
return (files ?? []).map((file) => ({
|
|
281
|
+
...file,
|
|
282
|
+
path: applySlugPlaceholder(file.path, slug),
|
|
283
|
+
content: applySlugPlaceholder(file.content, slug)
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
async function cloneEntry(src, dest) {
|
|
287
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
288
|
+
const platform = process.platform;
|
|
289
|
+
const cloneArgs = platform === "darwin" ? ["-Rc", src, dest] : platform === "linux" ? ["-r", "--reflink=auto", src, dest] : null;
|
|
290
|
+
if (cloneArgs) {
|
|
291
|
+
const result = await execArgv("cp", cloneArgs, dirname(dest));
|
|
292
|
+
if (result.exitCode === 0) return;
|
|
293
|
+
}
|
|
294
|
+
const fallback = await execArgv("cp", ["-R", src, dest], dirname(dest));
|
|
295
|
+
if (fallback.exitCode !== 0) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
`clone of '${src}' into '${dest}' failed: ${fallback.stderr || fallback.stdout}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function cloneEntries(repoRoot, cwd, patterns) {
|
|
302
|
+
for (const pattern of patterns) {
|
|
303
|
+
const matches = await expandCloneGlob(repoRoot, pattern);
|
|
304
|
+
for (const rel of matches) {
|
|
305
|
+
const dest = join(cwd, rel);
|
|
306
|
+
const existing = await lstat(dest).catch(() => null);
|
|
307
|
+
if (existing) continue;
|
|
308
|
+
await cloneEntry(join(repoRoot, rel), dest);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
185
312
|
var ENV_VARS = {
|
|
186
313
|
/** Absolute path to the original repo checkout the worktree was cut from. */
|
|
187
314
|
sourceCheckoutPath: "AGENTPROTO_SOURCE_CHECKOUT_PATH",
|
|
@@ -455,8 +582,12 @@ var provisionWorktreeBuiltin = implementTool(
|
|
|
455
582
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
456
583
|
});
|
|
457
584
|
const config = input.runSetup !== false ? await loadConfigFromBase(input.repoRoot, base) : null;
|
|
458
|
-
const
|
|
459
|
-
const
|
|
585
|
+
const localConfig = input.runSetup !== false ? await loadLocalWorktreeConfig(input.repoRoot) : null;
|
|
586
|
+
const linkPaths = input.linkPaths ?? localConfig?.linkPaths ?? config?.worktree?.linkPaths ?? [];
|
|
587
|
+
const depsCmd = input.depsCmd ?? localConfig?.depsCmd ?? config?.worktree?.depsCmd;
|
|
588
|
+
const copyGlobs = input.copyGlobs ?? localConfig?.copyGlobs ?? [];
|
|
589
|
+
const cloneGlobs = input.cloneGlobs ?? localConfig?.cloneGlobs ?? [];
|
|
590
|
+
const writeFiles = input.writeFiles ?? resolveLocalWriteFiles(localConfig?.writeFiles, input.slug);
|
|
460
591
|
for (const rel of linkPaths) {
|
|
461
592
|
const target = resolve(input.repoRoot, rel);
|
|
462
593
|
const dest = join(cwd, rel);
|
|
@@ -465,7 +596,7 @@ var provisionWorktreeBuiltin = implementTool(
|
|
|
465
596
|
await mkdir(dirname(dest), { recursive: true });
|
|
466
597
|
await symlink(target, dest, "dir");
|
|
467
598
|
}
|
|
468
|
-
for (const file of
|
|
599
|
+
for (const file of writeFiles) {
|
|
469
600
|
const dest = join(cwd, file.path);
|
|
470
601
|
await mkdir(dirname(dest), { recursive: true });
|
|
471
602
|
if (file.mode === "append") {
|
|
@@ -478,6 +609,16 @@ var provisionWorktreeBuiltin = implementTool(
|
|
|
478
609
|
await writeFile(dest, file.content);
|
|
479
610
|
}
|
|
480
611
|
}
|
|
612
|
+
if (cloneGlobs.length > 0) {
|
|
613
|
+
try {
|
|
614
|
+
await cloneEntries(input.repoRoot, cwd, cloneGlobs);
|
|
615
|
+
} catch (err) {
|
|
616
|
+
throw new ToolError({
|
|
617
|
+
code: "execution_failed",
|
|
618
|
+
message: `cloneGlobs failed: ${err instanceof Error ? err.message : String(err)}`
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
481
622
|
if (depsCmd) {
|
|
482
623
|
const result = await execShell(depsCmd, cwd);
|
|
483
624
|
if (result.exitCode !== 0) {
|
|
@@ -487,7 +628,7 @@ var provisionWorktreeBuiltin = implementTool(
|
|
|
487
628
|
});
|
|
488
629
|
}
|
|
489
630
|
}
|
|
490
|
-
for (const pattern of
|
|
631
|
+
for (const pattern of copyGlobs) {
|
|
491
632
|
const matches = await expandGlob(input.repoRoot, pattern);
|
|
492
633
|
for (const rel of matches) {
|
|
493
634
|
const dest = join(cwd, rel);
|
|
@@ -600,24 +741,24 @@ function sweepWorktreeTrash(root, options = {}) {
|
|
|
600
741
|
}
|
|
601
742
|
}
|
|
602
743
|
function isPortFree(port) {
|
|
603
|
-
return new Promise((
|
|
744
|
+
return new Promise((resolve6) => {
|
|
604
745
|
const server = createServer();
|
|
605
|
-
server.once("error", () =>
|
|
746
|
+
server.once("error", () => resolve6(false));
|
|
606
747
|
server.once("listening", () => {
|
|
607
|
-
server.close(() =>
|
|
748
|
+
server.close(() => resolve6(true));
|
|
608
749
|
});
|
|
609
750
|
server.listen(port, "127.0.0.1");
|
|
610
751
|
});
|
|
611
752
|
}
|
|
612
753
|
function ephemeralPort() {
|
|
613
|
-
return new Promise((
|
|
754
|
+
return new Promise((resolve6, reject) => {
|
|
614
755
|
const server = createServer();
|
|
615
756
|
server.once("error", reject);
|
|
616
757
|
server.listen(0, "127.0.0.1", () => {
|
|
617
758
|
const address = server.address();
|
|
618
759
|
if (address && typeof address === "object") {
|
|
619
760
|
const port = address.port;
|
|
620
|
-
server.close(() =>
|
|
761
|
+
server.close(() => resolve6(port));
|
|
621
762
|
} else {
|
|
622
763
|
server.close(() => reject(new Error("could not determine an ephemeral port")));
|
|
623
764
|
}
|
|
@@ -752,10 +893,10 @@ var ServiceSupervisor = class _ServiceSupervisor {
|
|
|
752
893
|
this.opts.proxyTable?.delete(t.runtime.hostname);
|
|
753
894
|
const child = t.child;
|
|
754
895
|
if (!child || t.status !== "running") return false;
|
|
755
|
-
return new Promise((
|
|
896
|
+
return new Promise((resolve6) => {
|
|
756
897
|
const done = () => {
|
|
757
898
|
t.status = "exited";
|
|
758
|
-
|
|
899
|
+
resolve6(true);
|
|
759
900
|
};
|
|
760
901
|
child.once("exit", done);
|
|
761
902
|
child.kill("SIGTERM");
|
|
@@ -1420,6 +1561,6 @@ var worktreeProvider = defineDriver({
|
|
|
1420
1561
|
]
|
|
1421
1562
|
});
|
|
1422
1563
|
|
|
1423
|
-
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, HookError, InMemoryVerdictMemoStore, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WORKTREE_TRASH_DIRNAME, WorktreeNotRemovableError, allocatePort, classify, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, normalizeHook, parseConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, removeWorktreeFast, repoLabel, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, sweepWorktreeTrash, worktreeProvider, writeWorktreeMarker };
|
|
1424
|
-
//# sourceMappingURL=chunk-
|
|
1425
|
-
//# sourceMappingURL=chunk-
|
|
1564
|
+
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, GlobTraversalError, HookError, InMemoryVerdictMemoStore, LOCAL_WORKTREE_CONFIG_REL, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WORKTREE_TRASH_DIRNAME, WorktreeNotRemovableError, allocatePort, applySlugPlaceholder, classify, cloneEntries, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandCloneGlob, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, loadLocalWorktreeConfig, normalizeHook, parseConfig, parseLocalWorktreeConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, removeWorktreeFast, repoLabel, resolveLocalWriteFiles, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, sweepWorktreeTrash, worktreeProvider, writeWorktreeMarker };
|
|
1565
|
+
//# sourceMappingURL=chunk-MYH5XOX6.mjs.map
|
|
1566
|
+
//# sourceMappingURL=chunk-MYH5XOX6.mjs.map
|