@agentproto/worktree 0.2.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/bin/worktree-agent.mjs +198 -0
- package/dist/bin/worktree-agent.mjs.map +1 -0
- package/dist/chunk-BBAH3VLQ.mjs +170 -0
- package/dist/chunk-BBAH3VLQ.mjs.map +1 -0
- package/dist/chunk-FRVPZTQN.mjs +67 -0
- package/dist/chunk-FRVPZTQN.mjs.map +1 -0
- package/dist/chunk-P7FOHOQ3.mjs +176 -0
- package/dist/chunk-P7FOHOQ3.mjs.map +1 -0
- package/dist/chunk-PSFICTF7.mjs +3 -0
- package/dist/chunk-PSFICTF7.mjs.map +1 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -0
- package/dist/provider/index.d.ts +10 -0
- package/dist/provider/index.mjs +4 -0
- package/dist/provider/index.mjs.map +1 -0
- package/dist/tools/index.d.ts +46 -0
- package/dist/tools/index.mjs +4 -0
- package/dist/tools/index.mjs.map +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jeremy André and agentproto contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @agentproto/worktree
|
|
2
|
+
|
|
3
|
+
AIP-14 TOOL contracts + a builtin AIP-30 PROVIDER for provisioning, gating,
|
|
4
|
+
and cleaning up a git worktree — the primitive a `@agentproto/workflow-runtime`
|
|
5
|
+
`AgentStep` binds its `cwd` to for a "launch an agent in a worktree" workflow.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
- **`worktree.provision`** — `git worktree add` a new worktree for `repoRoot`
|
|
10
|
+
at `<repoRoot>/../_worktrees/<slug>` on branch `wt/<slug>`, cut from `base`
|
|
11
|
+
(default `origin/main`). Optionally runs `depsCmd` inside it, and copies
|
|
12
|
+
`copyGlobs` (e.g. gitignored local secrets) from `repoRoot` into it at the
|
|
13
|
+
same relative path. Returns `{ cwd, branch }`.
|
|
14
|
+
- **`worktree.run-gate`** — run a caller-provided command inside a directory
|
|
15
|
+
and report pass/fail from its exit code.
|
|
16
|
+
- **`worktree.cleanup`** — `git worktree remove` (+ optional `git branch -D`).
|
|
17
|
+
|
|
18
|
+
All three are agnostic: no hardcoded package manager, env layout, or gate
|
|
19
|
+
command — everything is an input.
|
|
20
|
+
|
|
21
|
+
## `worktreeAgentWorkflow`
|
|
22
|
+
|
|
23
|
+
This package also exports the `RuntimeWorkflow` def that chains the three
|
|
24
|
+
tools above around an `AgentStep`: provision → agent (`cwd` bound to the
|
|
25
|
+
provisioned worktree) → gate → on pass, human approval → cleanup. On gate
|
|
26
|
+
failure the worktree is left in place for inspection.
|
|
27
|
+
|
|
28
|
+
## `worktree-agent` CLI
|
|
29
|
+
|
|
30
|
+
A `bin` runs that workflow end-to-end against a real agentproto daemon (the
|
|
31
|
+
coding agent is a real, supervisable `agent_start` session — not a bare
|
|
32
|
+
subprocess):
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
worktree-agent run \
|
|
36
|
+
--repo <abs repo root> --slug <id> --task "<prompt>" --gate "<check cmd>" \
|
|
37
|
+
[--base origin/main] [--adapter claude-code] [--deps-cmd "pnpm install --prefer-offline"] \
|
|
38
|
+
[--copy-glob <glob>]... [--no-cleanup] [--yes]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
It connects to the daemon's MCP endpoint (`http://127.0.0.1:18790/mcp`, or
|
|
42
|
+
`AGENTPROTO_MCP_URL`) and fails loudly if it can't reach one. The approval
|
|
43
|
+
step reads a y/n answer from `/dev/tty`; `--yes` auto-approves, and a
|
|
44
|
+
non-interactive run (no TTY) defaults to NOT approving — the worktree is left
|
|
45
|
+
in place rather than silently cleaned up.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { connectDaemonAgentSessionHost, worktreeAgentWorkflow } from '../chunk-P7FOHOQ3.mjs';
|
|
3
|
+
import '../chunk-PSFICTF7.mjs';
|
|
4
|
+
import '../chunk-BBAH3VLQ.mjs';
|
|
5
|
+
import '../chunk-FRVPZTQN.mjs';
|
|
6
|
+
import { runWorkflow } from '@agentproto/workflow-runtime';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { openSync, closeSync, createReadStream, createWriteStream } from 'fs';
|
|
9
|
+
import { createInterface } from 'readline';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @agentproto/worktree v0.1.0
|
|
13
|
+
* git-worktree provision / gate / cleanup TOOL contracts + builtin PROVIDER.
|
|
14
|
+
*/
|
|
15
|
+
var USAGE = `usage: worktree-agent run --repo <abs repo root> --slug <id> --task "<prompt>" --gate "<check cmd>"
|
|
16
|
+
[--base <ref>] [--adapter <slug>] [--deps-cmd "<cmd>"] [--copy-glob <glob>]...
|
|
17
|
+
[--link <path>]... [--no-cleanup] [--yes]
|
|
18
|
+
|
|
19
|
+
--repo Absolute (or cwd-relative) path to the git repository root.
|
|
20
|
+
--slug Worktree/branch identifier, e.g. 'fix-flaky-test'.
|
|
21
|
+
--task The prompt sent to the coding agent.
|
|
22
|
+
--gate Command run inside the worktree to check the agent's work.
|
|
23
|
+
--base Ref to cut the worktree branch from. Default 'origin/main'.
|
|
24
|
+
--adapter Agent adapter slug. Default 'claude-code'.
|
|
25
|
+
--deps-cmd Command to install deps inside the worktree.
|
|
26
|
+
--copy-glob Gitignored glob to copy into the worktree (repeatable).
|
|
27
|
+
--link Gitignored dir/file to symlink from the host repo into the worktree
|
|
28
|
+
before deps (node_modules, sibling workspace repos); repeatable.
|
|
29
|
+
--no-cleanup Keep the worktree's branch after cleanup (still removes the worktree dir).
|
|
30
|
+
--yes Auto-approve the cleanup step instead of prompting.
|
|
31
|
+
`;
|
|
32
|
+
var CliUsageError = class extends Error {
|
|
33
|
+
};
|
|
34
|
+
function parseArgs(argv) {
|
|
35
|
+
const [cmd, ...rest] = argv;
|
|
36
|
+
if (cmd !== "run") {
|
|
37
|
+
throw new CliUsageError(cmd ? `unknown command '${cmd}'
|
|
38
|
+
|
|
39
|
+
${USAGE}` : USAGE);
|
|
40
|
+
}
|
|
41
|
+
let repoRoot;
|
|
42
|
+
let slug;
|
|
43
|
+
let task;
|
|
44
|
+
let gateCmd;
|
|
45
|
+
let base;
|
|
46
|
+
let adapter;
|
|
47
|
+
let depsCmd;
|
|
48
|
+
const copyGlobs = [];
|
|
49
|
+
const linkPaths = [];
|
|
50
|
+
let deleteBranch = true;
|
|
51
|
+
let yes = false;
|
|
52
|
+
function nextValue(flag, i) {
|
|
53
|
+
const value = rest[i];
|
|
54
|
+
if (value === void 0) throw new CliUsageError(`flag '${flag}' requires a value
|
|
55
|
+
|
|
56
|
+
${USAGE}`);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < rest.length; i++) {
|
|
60
|
+
const arg = rest[i];
|
|
61
|
+
switch (arg) {
|
|
62
|
+
case "--repo":
|
|
63
|
+
repoRoot = nextValue(arg, ++i);
|
|
64
|
+
break;
|
|
65
|
+
case "--slug":
|
|
66
|
+
slug = nextValue(arg, ++i);
|
|
67
|
+
break;
|
|
68
|
+
case "--task":
|
|
69
|
+
task = nextValue(arg, ++i);
|
|
70
|
+
break;
|
|
71
|
+
case "--gate":
|
|
72
|
+
gateCmd = nextValue(arg, ++i);
|
|
73
|
+
break;
|
|
74
|
+
case "--base":
|
|
75
|
+
base = nextValue(arg, ++i);
|
|
76
|
+
break;
|
|
77
|
+
case "--adapter":
|
|
78
|
+
adapter = nextValue(arg, ++i);
|
|
79
|
+
break;
|
|
80
|
+
case "--deps-cmd":
|
|
81
|
+
depsCmd = nextValue(arg, ++i);
|
|
82
|
+
break;
|
|
83
|
+
case "--copy-glob":
|
|
84
|
+
copyGlobs.push(nextValue(arg, ++i));
|
|
85
|
+
break;
|
|
86
|
+
case "--link":
|
|
87
|
+
linkPaths.push(nextValue(arg, ++i));
|
|
88
|
+
break;
|
|
89
|
+
case "--no-cleanup":
|
|
90
|
+
deleteBranch = false;
|
|
91
|
+
break;
|
|
92
|
+
case "--yes":
|
|
93
|
+
yes = true;
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
throw new CliUsageError(`unrecognized flag '${arg}'
|
|
97
|
+
|
|
98
|
+
${USAGE}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const missing = [
|
|
102
|
+
!repoRoot && "--repo",
|
|
103
|
+
!slug && "--slug",
|
|
104
|
+
!task && "--task",
|
|
105
|
+
!gateCmd && "--gate"
|
|
106
|
+
].filter((f) => typeof f === "string");
|
|
107
|
+
if (missing.length > 0) {
|
|
108
|
+
throw new CliUsageError(`missing required flag(s): ${missing.join(", ")}
|
|
109
|
+
|
|
110
|
+
${USAGE}`);
|
|
111
|
+
}
|
|
112
|
+
const input = {
|
|
113
|
+
repoRoot: resolve(repoRoot),
|
|
114
|
+
slug,
|
|
115
|
+
task,
|
|
116
|
+
gateCmd,
|
|
117
|
+
deleteBranch,
|
|
118
|
+
...base !== void 0 ? { base } : {},
|
|
119
|
+
...adapter !== void 0 ? { adapter } : {},
|
|
120
|
+
...depsCmd !== void 0 ? { depsCmd } : {},
|
|
121
|
+
...copyGlobs.length > 0 ? { copyGlobs } : {},
|
|
122
|
+
...linkPaths.length > 0 ? { linkPaths } : {}
|
|
123
|
+
};
|
|
124
|
+
return { input, yes };
|
|
125
|
+
}
|
|
126
|
+
function hasTty() {
|
|
127
|
+
try {
|
|
128
|
+
const fd = openSync("/dev/tty", "r+");
|
|
129
|
+
closeSync(fd);
|
|
130
|
+
return true;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function askTty(prompt) {
|
|
136
|
+
const input = createReadStream("/dev/tty");
|
|
137
|
+
const output = createWriteStream("/dev/tty");
|
|
138
|
+
const rl = createInterface({ input, output });
|
|
139
|
+
try {
|
|
140
|
+
return await new Promise((resolvePromise) => rl.question(prompt, resolvePromise));
|
|
141
|
+
} finally {
|
|
142
|
+
rl.close();
|
|
143
|
+
input.destroy();
|
|
144
|
+
output.destroy();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function makeApprove(opts, deps = {}) {
|
|
148
|
+
const hasTtyFn = deps.hasTty ?? hasTty;
|
|
149
|
+
const askFn = deps.ask ?? askTty;
|
|
150
|
+
return async (req) => {
|
|
151
|
+
if (opts.yes) {
|
|
152
|
+
process.stderr.write(`${req.prompt} \u2014 auto-approved (--yes)
|
|
153
|
+
`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (!hasTtyFn()) {
|
|
157
|
+
process.stderr.write(`${req.prompt} \u2014 no TTY attached, not approving (worktree left in place)
|
|
158
|
+
`);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
const answer = await askFn(`${req.prompt} [y/N] `);
|
|
162
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/bin/worktree-agent.ts
|
|
167
|
+
async function main() {
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = parseArgs(process.argv.slice(2));
|
|
171
|
+
} catch (err) {
|
|
172
|
+
if (!(err instanceof CliUsageError)) throw err;
|
|
173
|
+
process.stderr.write(`${err.message}
|
|
174
|
+
`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
const { input, yes } = parsed;
|
|
178
|
+
const host = await connectDaemonAgentSessionHost();
|
|
179
|
+
try {
|
|
180
|
+
const { output } = await runWorkflow({
|
|
181
|
+
workflow: worktreeAgentWorkflow,
|
|
182
|
+
input,
|
|
183
|
+
agents: host,
|
|
184
|
+
approve: makeApprove({ yes })
|
|
185
|
+
});
|
|
186
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}
|
|
187
|
+
`);
|
|
188
|
+
} finally {
|
|
189
|
+
await host.close();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
main().catch((err) => {
|
|
193
|
+
process.stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}
|
|
194
|
+
`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
});
|
|
197
|
+
//# sourceMappingURL=worktree-agent.mjs.map
|
|
198
|
+
//# sourceMappingURL=worktree-agent.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/bin/args.ts","../../src/bin/approve.ts","../../src/bin/worktree-agent.ts"],"names":[],"mappings":"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","file":"worktree-agent.mjs","sourcesContent":["import { resolve } from \"node:path\"\nimport type { WorktreeAgentInput } from \"../workflow.js\"\n\nexport const USAGE = `usage: worktree-agent run --repo <abs repo root> --slug <id> --task \"<prompt>\" --gate \"<check cmd>\"\n [--base <ref>] [--adapter <slug>] [--deps-cmd \"<cmd>\"] [--copy-glob <glob>]...\n [--link <path>]... [--no-cleanup] [--yes]\n\n --repo Absolute (or cwd-relative) path to the git repository root.\n --slug Worktree/branch identifier, e.g. 'fix-flaky-test'.\n --task The prompt sent to the coding agent.\n --gate Command run inside the worktree to check the agent's work.\n --base Ref to cut the worktree branch from. Default 'origin/main'.\n --adapter Agent adapter slug. Default 'claude-code'.\n --deps-cmd Command to install deps inside the worktree.\n --copy-glob Gitignored glob to copy into the worktree (repeatable).\n --link Gitignored dir/file to symlink from the host repo into the worktree\n before deps (node_modules, sibling workspace repos); repeatable.\n --no-cleanup Keep the worktree's branch after cleanup (still removes the worktree dir).\n --yes Auto-approve the cleanup step instead of prompting.\n`\n\nexport class CliUsageError extends Error {}\n\nexport interface ParsedCli {\n input: WorktreeAgentInput\n yes: boolean\n}\n\n/** Parse `worktree-agent run <flags>` into a `WorktreeAgentInput` + CLI-only options. */\nexport function parseArgs(argv: readonly string[]): ParsedCli {\n const [cmd, ...rest] = argv\n if (cmd !== \"run\") {\n throw new CliUsageError(cmd ? `unknown command '${cmd}'\\n\\n${USAGE}` : USAGE)\n }\n\n let repoRoot: string | undefined\n let slug: string | undefined\n let task: string | undefined\n let gateCmd: string | undefined\n let base: string | undefined\n let adapter: string | undefined\n let depsCmd: string | undefined\n const copyGlobs: string[] = []\n const linkPaths: string[] = []\n let deleteBranch = true\n let yes = false\n\n function nextValue(flag: string, i: number): string {\n const value = rest[i]\n if (value === undefined) throw new CliUsageError(`flag '${flag}' requires a value\\n\\n${USAGE}`)\n return value\n }\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n switch (arg) {\n case \"--repo\":\n repoRoot = nextValue(arg, ++i)\n break\n case \"--slug\":\n slug = nextValue(arg, ++i)\n break\n case \"--task\":\n task = nextValue(arg, ++i)\n break\n case \"--gate\":\n gateCmd = nextValue(arg, ++i)\n break\n case \"--base\":\n base = nextValue(arg, ++i)\n break\n case \"--adapter\":\n adapter = nextValue(arg, ++i)\n break\n case \"--deps-cmd\":\n depsCmd = nextValue(arg, ++i)\n break\n case \"--copy-glob\":\n copyGlobs.push(nextValue(arg, ++i))\n break\n case \"--link\":\n linkPaths.push(nextValue(arg, ++i))\n break\n case \"--no-cleanup\":\n deleteBranch = false\n break\n case \"--yes\":\n yes = true\n break\n default:\n throw new CliUsageError(`unrecognized flag '${arg}'\\n\\n${USAGE}`)\n }\n }\n\n const missing = [\n !repoRoot && \"--repo\",\n !slug && \"--slug\",\n !task && \"--task\",\n !gateCmd && \"--gate\",\n ].filter((f): f is string => typeof f === \"string\")\n if (missing.length > 0) {\n throw new CliUsageError(`missing required flag(s): ${missing.join(\", \")}\\n\\n${USAGE}`)\n }\n\n const input: WorktreeAgentInput = {\n repoRoot: resolve(repoRoot!),\n slug: slug!,\n task: task!,\n gateCmd: gateCmd!,\n deleteBranch,\n ...(base !== undefined ? { base } : {}),\n ...(adapter !== undefined ? { adapter } : {}),\n ...(depsCmd !== undefined ? { depsCmd } : {}),\n ...(copyGlobs.length > 0 ? { copyGlobs } : {}),\n ...(linkPaths.length > 0 ? { linkPaths } : {}),\n }\n return { input, yes }\n}\n","import { closeSync, createReadStream, createWriteStream, openSync } from \"node:fs\"\nimport { createInterface } from \"node:readline\"\nimport type { ApprovalRequest } from \"@agentproto/workflow-runtime\"\n\n/** True when `/dev/tty` can be opened — i.e. a real terminal is attached. */\nexport function hasTty(): boolean {\n try {\n const fd = openSync(\"/dev/tty\", \"r+\")\n closeSync(fd)\n return true\n } catch {\n return false\n }\n}\n\n/** Prompt on `/dev/tty` (not stdin/stdout, which may be piping the workflow's own IO). */\nexport async function askTty(prompt: string): Promise<string> {\n const input = createReadStream(\"/dev/tty\")\n const output = createWriteStream(\"/dev/tty\")\n const rl = createInterface({ input, output })\n try {\n return await new Promise<string>((resolvePromise) => rl.question(prompt, resolvePromise))\n } finally {\n rl.close()\n input.destroy()\n output.destroy()\n }\n}\n\nexport interface ApproveDeps {\n hasTty?: () => boolean\n ask?: (prompt: string) => Promise<string>\n}\n\n/**\n * Build the workflow's `approve` callback: `--yes` auto-approves; otherwise a\n * y/n prompt on `/dev/tty`; with no TTY attached (non-interactive), don't\n * approve — the worktree is left in place rather than silently cleaned up.\n */\nexport function makeApprove(\n opts: { yes: boolean },\n deps: ApproveDeps = {},\n): (req: ApprovalRequest) => Promise<boolean> {\n const hasTtyFn = deps.hasTty ?? hasTty\n const askFn = deps.ask ?? askTty\n return async (req: ApprovalRequest): Promise<boolean> => {\n if (opts.yes) {\n process.stderr.write(`${req.prompt} — auto-approved (--yes)\\n`)\n return true\n }\n if (!hasTtyFn()) {\n process.stderr.write(`${req.prompt} — no TTY attached, not approving (worktree left in place)\\n`)\n return false\n }\n const answer = await askFn(`${req.prompt} [y/N] `)\n return /^y(es)?$/i.test(answer.trim())\n }\n}\n","#!/usr/bin/env node\n/**\n * `worktree-agent run` — runs `worktreeAgentWorkflow` end-to-end: provision a\n * git worktree, run a coding agent in it as a real daemon session (`agent_start`\n * against the agentproto daemon's MCP endpoint), gate the result, ask a human\n * to approve cleanup, then clean up (or leave it in place on gate failure /\n * rejection).\n */\nimport { runWorkflow } from \"@agentproto/workflow-runtime\"\nimport { worktreeAgentWorkflow } from \"../workflow.js\"\nimport { connectDaemonAgentSessionHost } from \"../agent-session-host.js\"\nimport { parseArgs, CliUsageError } from \"./args.js\"\nimport { makeApprove } from \"./approve.js\"\n\nasync function main(): Promise<void> {\n let parsed: ReturnType<typeof parseArgs>\n try {\n parsed = parseArgs(process.argv.slice(2))\n } catch (err) {\n if (!(err instanceof CliUsageError)) throw err\n process.stderr.write(`${err.message}\\n`)\n process.exit(1)\n }\n const { input, yes } = parsed\n\n const host = await connectDaemonAgentSessionHost()\n try {\n const { output } = await runWorkflow({\n workflow: worktreeAgentWorkflow,\n input,\n agents: host,\n approve: makeApprove({ yes }),\n })\n process.stdout.write(`${JSON.stringify(output, null, 2)}\\n`)\n } finally {\n await host.close()\n }\n}\n\nmain().catch((err: unknown) => {\n process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\\n`)\n process.exit(1)\n})\n"]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { provisionWorktreeTool, cleanupWorktreeTool, runGateTool } from './chunk-FRVPZTQN.mjs';
|
|
2
|
+
import { implementTool, defineDriver } from '@agentproto/driver';
|
|
3
|
+
import { lstat, mkdir, symlink, copyFile, readdir } from 'fs/promises';
|
|
4
|
+
import { resolve, join, dirname, posix } from 'path';
|
|
5
|
+
import { ToolError } from '@agentproto/tool';
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @agentproto/worktree v0.1.0
|
|
10
|
+
* git-worktree provision / gate / cleanup TOOL contracts + builtin PROVIDER.
|
|
11
|
+
*/
|
|
12
|
+
function execArgv(command, args, cwd) {
|
|
13
|
+
return new Promise((resolvePromise, reject) => {
|
|
14
|
+
const child = spawn(command, args, { cwd, shell: false, env: process.env });
|
|
15
|
+
let stdout = "";
|
|
16
|
+
let stderr = "";
|
|
17
|
+
child.stdout?.on("data", (d) => stdout += d.toString("utf8"));
|
|
18
|
+
child.stderr?.on("data", (d) => stderr += d.toString("utf8"));
|
|
19
|
+
child.on("error", reject);
|
|
20
|
+
child.on("close", (code) => resolvePromise({ exitCode: code ?? -1, stdout, stderr }));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function execShell(cmd, cwd) {
|
|
24
|
+
return new Promise((resolvePromise, reject) => {
|
|
25
|
+
const child = spawn(cmd, { cwd, shell: true, env: process.env });
|
|
26
|
+
let stdout = "";
|
|
27
|
+
let stderr = "";
|
|
28
|
+
child.stdout?.on("data", (d) => stdout += d.toString("utf8"));
|
|
29
|
+
child.stderr?.on("data", (d) => stderr += d.toString("utf8"));
|
|
30
|
+
child.on("error", reject);
|
|
31
|
+
child.on("close", (code) => resolvePromise({ exitCode: code ?? -1, stdout, stderr }));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function execGit(repoRoot, args) {
|
|
35
|
+
const result = await execArgv("git", ["-C", repoRoot, ...args], repoRoot);
|
|
36
|
+
if (result.exitCode !== 0) {
|
|
37
|
+
throw new Error(`git ${args.join(" ")} failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function escapeSegment(seg) {
|
|
42
|
+
let out = "";
|
|
43
|
+
for (const ch of seg) {
|
|
44
|
+
if (ch === "*") out += "[^/]*";
|
|
45
|
+
else if (ch === "?") out += "[^/]";
|
|
46
|
+
else out += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function globToRegExp(pattern) {
|
|
51
|
+
const segments = pattern.split("/");
|
|
52
|
+
let out = "^";
|
|
53
|
+
segments.forEach((seg, i) => {
|
|
54
|
+
const isLast = i === segments.length - 1;
|
|
55
|
+
if (seg === "**") {
|
|
56
|
+
out += isLast ? ".*" : "(?:[^/]+/)*";
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
out += escapeSegment(seg);
|
|
60
|
+
if (!isLast) out += "/";
|
|
61
|
+
});
|
|
62
|
+
return new RegExp(out + "$");
|
|
63
|
+
}
|
|
64
|
+
function globBaseDir(pattern) {
|
|
65
|
+
const segments = pattern.split("/");
|
|
66
|
+
const literal = [];
|
|
67
|
+
for (const seg of segments) {
|
|
68
|
+
if (seg.includes("*") || seg.includes("?")) break;
|
|
69
|
+
literal.push(seg);
|
|
70
|
+
}
|
|
71
|
+
if (literal.length === segments.length) literal.pop();
|
|
72
|
+
return literal.join("/");
|
|
73
|
+
}
|
|
74
|
+
async function walk(root, dirRel) {
|
|
75
|
+
const abs = dirRel ? join(root, dirRel) : root;
|
|
76
|
+
let entries;
|
|
77
|
+
try {
|
|
78
|
+
entries = await readdir(abs, { withFileTypes: true });
|
|
79
|
+
} catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
85
|
+
const rel = dirRel ? posix.join(dirRel, entry.name) : entry.name;
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
const children = await walk(root, rel);
|
|
88
|
+
for (const child of children) out.push(child);
|
|
89
|
+
} else if (entry.isFile()) {
|
|
90
|
+
out.push(rel);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
async function expandGlob(root, pattern) {
|
|
96
|
+
const regex = globToRegExp(pattern);
|
|
97
|
+
const base = globBaseDir(pattern);
|
|
98
|
+
const candidates = await walk(root, base);
|
|
99
|
+
return candidates.filter((rel) => regex.test(rel));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/provider/bodies/provision-worktree.body.ts
|
|
103
|
+
var provisionWorktreeBuiltin = implementTool(
|
|
104
|
+
provisionWorktreeTool,
|
|
105
|
+
async ({ input }) => {
|
|
106
|
+
const base = input.base ?? "origin/main";
|
|
107
|
+
const branch = `wt/${input.slug}`;
|
|
108
|
+
const cwd = resolve(input.repoRoot, "..", "_worktrees", input.slug);
|
|
109
|
+
await execGit(input.repoRoot, ["worktree", "add", "-b", branch, cwd, base]);
|
|
110
|
+
for (const rel of input.linkPaths ?? []) {
|
|
111
|
+
const target = resolve(input.repoRoot, rel);
|
|
112
|
+
const dest = join(cwd, rel);
|
|
113
|
+
const existing = await lstat(dest).catch(() => null);
|
|
114
|
+
if (existing) continue;
|
|
115
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
116
|
+
await symlink(target, dest, "dir");
|
|
117
|
+
}
|
|
118
|
+
if (input.depsCmd) {
|
|
119
|
+
const result = await execShell(input.depsCmd, cwd);
|
|
120
|
+
if (result.exitCode !== 0) {
|
|
121
|
+
throw new ToolError({
|
|
122
|
+
code: "execution_failed",
|
|
123
|
+
message: `depsCmd '${input.depsCmd}' failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const pattern of input.copyGlobs ?? []) {
|
|
128
|
+
const matches = await expandGlob(input.repoRoot, pattern);
|
|
129
|
+
for (const rel of matches) {
|
|
130
|
+
const dest = join(cwd, rel);
|
|
131
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
132
|
+
await copyFile(join(input.repoRoot, rel), dest);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { cwd, branch };
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
var cleanupWorktreeBuiltin = implementTool(
|
|
139
|
+
cleanupWorktreeTool,
|
|
140
|
+
async ({ input }) => {
|
|
141
|
+
await execGit(input.repoRoot, ["worktree", "remove", "--force", input.cwd]);
|
|
142
|
+
if (input.deleteBranch && input.branch) {
|
|
143
|
+
await execGit(input.repoRoot, ["branch", "-D", input.branch]);
|
|
144
|
+
}
|
|
145
|
+
return { removed: true };
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
var runGateBuiltin = implementTool(runGateTool, async ({ input }) => {
|
|
149
|
+
const result = await execShell(input.cmd, input.cwd);
|
|
150
|
+
return { passed: result.exitCode === 0, ...result };
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// src/provider/worktree-provider.ts
|
|
154
|
+
var worktreeProvider = defineDriver({
|
|
155
|
+
id: "worktree-builtin",
|
|
156
|
+
name: "Git Worktree (built-in)",
|
|
157
|
+
description: "In-process provision / gate / cleanup of git worktrees via direct git and shell invocations. No sandboxing \u2014 the host owns the filesystem and process boundary.",
|
|
158
|
+
version: "0.1.0",
|
|
159
|
+
kind: "builtin",
|
|
160
|
+
implements: [
|
|
161
|
+
{ tool: "worktree.provision", version: "0.1.0" },
|
|
162
|
+
{ tool: "worktree.cleanup", version: "0.1.0" },
|
|
163
|
+
{ tool: "worktree.run-gate", version: "0.1.0" }
|
|
164
|
+
],
|
|
165
|
+
implementations: [provisionWorktreeBuiltin, cleanupWorktreeBuiltin, runGateBuiltin]
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
export { execArgv, execGit, execShell, expandGlob, globToRegExp, worktreeProvider };
|
|
169
|
+
//# sourceMappingURL=chunk-BBAH3VLQ.mjs.map
|
|
170
|
+
//# sourceMappingURL=chunk-BBAH3VLQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/exec.ts","../src/glob.ts","../src/provider/bodies/provision-worktree.body.ts","../src/provider/bodies/cleanup-worktree.body.ts","../src/provider/bodies/run-gate.body.ts","../src/provider/worktree-provider.ts"],"names":["join","implementTool"],"mappings":"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","file":"chunk-BBAH3VLQ.mjs","sourcesContent":["import { spawn } from \"node:child_process\"\n\nexport interface ExecResult {\n exitCode: number\n stdout: string\n stderr: string\n}\n\n/** Run an argv (no shell — args pass through verbatim, no injection risk). */\nexport function execArgv(\n command: string,\n args: readonly string[],\n cwd: string,\n): Promise<ExecResult> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(command, args, { cwd, shell: false, env: process.env })\n let stdout = \"\"\n let stderr = \"\"\n child.stdout?.on(\"data\", (d) => (stdout += d.toString(\"utf8\")))\n child.stderr?.on(\"data\", (d) => (stderr += d.toString(\"utf8\")))\n child.on(\"error\", reject)\n child.on(\"close\", (code) => resolvePromise({ exitCode: code ?? -1, stdout, stderr }))\n })\n}\n\n/**\n * Run a single command string through a shell. Callers pass `depsCmd` /\n * `gateCmd` as a trusted, developer-authored config value (the same trust\n * model as a CI job's `script:` line) — not untrusted end-user input.\n */\nexport function execShell(cmd: string, cwd: string): Promise<ExecResult> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(cmd, { cwd, shell: true, env: process.env })\n let stdout = \"\"\n let stderr = \"\"\n child.stdout?.on(\"data\", (d) => (stdout += d.toString(\"utf8\")))\n child.stderr?.on(\"data\", (d) => (stderr += d.toString(\"utf8\")))\n child.on(\"error\", reject)\n child.on(\"close\", (code) => resolvePromise({ exitCode: code ?? -1, stdout, stderr }))\n })\n}\n\nexport async function execGit(repoRoot: string, args: readonly string[]): Promise<ExecResult> {\n const result = await execArgv(\"git\", [\"-C\", repoRoot, ...args], repoRoot)\n if (result.exitCode !== 0) {\n throw new Error(`git ${args.join(\" \")} failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`)\n }\n return result\n}\n","import { readdir } from \"node:fs/promises\"\nimport { join, posix } from \"node:path\"\n\nfunction escapeSegment(seg: string): string {\n let out = \"\"\n for (const ch of seg) {\n if (ch === \"*\") out += \"[^/]*\"\n else if (ch === \"?\") out += \"[^/]\"\n else out += ch.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n }\n return out\n}\n\n/** Compile a `/`-separated glob (supporting `*`, `?`, `**`) into an anchored regex. */\nexport function globToRegExp(pattern: string): RegExp {\n const segments = pattern.split(\"/\")\n let out = \"^\"\n segments.forEach((seg, i) => {\n const isLast = i === segments.length - 1\n if (seg === \"**\") {\n out += isLast ? \".*\" : \"(?:[^/]+/)*\"\n return\n }\n out += escapeSegment(seg)\n if (!isLast) out += \"/\"\n })\n return new RegExp(out + \"$\")\n}\n\n/** The literal (non-wildcard) directory prefix of a glob — where to start walking. */\nfunction globBaseDir(pattern: string): string {\n const segments = pattern.split(\"/\")\n const literal: string[] = []\n for (const seg of segments) {\n if (seg.includes(\"*\") || seg.includes(\"?\")) break\n literal.push(seg)\n }\n // Drop the last literal segment if it's the pattern's final segment (a filename),\n // not a directory to walk into.\n if (literal.length === segments.length) literal.pop()\n return literal.join(\"/\")\n}\n\nasync function walk(root: string, dirRel: string): Promise<string[]> {\n const abs = dirRel ? join(root, dirRel) : root\n let entries\n try {\n entries = await readdir(abs, { withFileTypes: true })\n } catch {\n return []\n }\n const out: string[] = []\n for (const entry of entries) {\n if (entry.name === \".git\" || entry.name === \"node_modules\") continue\n const rel = dirRel ? posix.join(dirRel, entry.name) : entry.name\n // entry.isDirectory() is false for symlinked dirs (Dirent reports the link's own\n // type, not the target's) — keep it that way so we never follow symlinks.\n if (entry.isDirectory()) {\n const children = await walk(root, rel)\n for (const child of children) out.push(child)\n } else if (entry.isFile()) {\n out.push(rel)\n }\n }\n return out\n}\n\n/** Expand a glob against `root`, returning root-relative posix paths of matching files. */\nexport async function expandGlob(root: string, pattern: string): Promise<string[]> {\n const regex = globToRegExp(pattern)\n const base = globBaseDir(pattern)\n const candidates = await walk(root, base)\n return candidates.filter((rel) => regex.test(rel))\n}\n","import { mkdir, copyFile, symlink, lstat } from \"node:fs/promises\"\nimport { dirname, join, resolve } from \"node:path\"\nimport { implementTool } from \"@agentproto/driver\"\nimport { ToolError } from \"@agentproto/tool\"\nimport { provisionWorktreeTool } from \"../../tools/provision-worktree.tool.js\"\nimport { execGit, execShell } from \"../../exec.js\"\nimport { expandGlob } from \"../../glob.js\"\n\nexport const provisionWorktreeBuiltin = implementTool(\n provisionWorktreeTool,\n async ({ input }) => {\n const base = input.base ?? \"origin/main\"\n const branch = `wt/${input.slug}`\n const cwd = resolve(input.repoRoot, \"..\", \"_worktrees\", input.slug)\n\n await execGit(input.repoRoot, [\"worktree\", \"add\", \"-b\", branch, cwd, base])\n\n // Symlink gitignored, expensive-to-recreate trees from the host repo into\n // the worktree BEFORE depsCmd, so a workspace whose graph spans gitignored\n // dirs (sibling repos, node_modules) resolves without a full reinstall.\n for (const rel of input.linkPaths ?? []) {\n const target = resolve(input.repoRoot, rel)\n const dest = join(cwd, rel)\n // A fresh worktree shouldn't already carry a gitignored path; if it does\n // (a tracked dir, or a re-run), leave it untouched rather than clobber.\n const existing = await lstat(dest).catch(() => null)\n if (existing) continue\n await mkdir(dirname(dest), { recursive: true })\n await symlink(target, dest, \"dir\")\n }\n\n if (input.depsCmd) {\n const result = await execShell(input.depsCmd, cwd)\n if (result.exitCode !== 0) {\n throw new ToolError({\n code: \"execution_failed\",\n message: `depsCmd '${input.depsCmd}' failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`,\n })\n }\n }\n\n for (const pattern of input.copyGlobs ?? []) {\n const matches = await expandGlob(input.repoRoot, pattern)\n for (const rel of matches) {\n const dest = join(cwd, rel)\n await mkdir(dirname(dest), { recursive: true })\n await copyFile(join(input.repoRoot, rel), dest)\n }\n }\n\n return { cwd, branch }\n },\n)\n","import { implementTool } from \"@agentproto/driver\"\nimport { cleanupWorktreeTool } from \"../../tools/cleanup-worktree.tool.js\"\nimport { execGit } from \"../../exec.js\"\n\nexport const cleanupWorktreeBuiltin = implementTool(\n cleanupWorktreeTool,\n async ({ input }) => {\n // --force: a provisioned worktree almost always carries untracked files\n // by the time cleanup runs (installed deps, copied secrets, agent output)\n // — plain `worktree remove` refuses those. Cleanup's whole job is to\n // tear the directory down regardless.\n await execGit(input.repoRoot, [\"worktree\", \"remove\", \"--force\", input.cwd])\n if (input.deleteBranch && input.branch) {\n await execGit(input.repoRoot, [\"branch\", \"-D\", input.branch])\n }\n return { removed: true as const }\n },\n)\n","import { implementTool } from \"@agentproto/driver\"\nimport { runGateTool } from \"../../tools/run-gate.tool.js\"\nimport { execShell } from \"../../exec.js\"\n\nexport const runGateBuiltin = implementTool(runGateTool, async ({ input }) => {\n const result = await execShell(input.cmd, input.cwd)\n return { passed: result.exitCode === 0, ...result }\n})\n","import { defineDriver } from \"@agentproto/driver\"\nimport { provisionWorktreeBuiltin } from \"./bodies/provision-worktree.body.js\"\nimport { cleanupWorktreeBuiltin } from \"./bodies/cleanup-worktree.body.js\"\nimport { runGateBuiltin } from \"./bodies/run-gate.body.js\"\n\n/**\n * AIP-30 PROVIDER for the three `worktree.*` TOOL contracts. `kind: \"builtin\"`\n * — runs in-process, shelling out to `git` / the caller's commands directly.\n * No subprocess-DRIVER indirection, no network hop.\n */\nexport const worktreeProvider = defineDriver({\n id: \"worktree-builtin\",\n name: \"Git Worktree (built-in)\",\n description:\n \"In-process provision / gate / cleanup of git worktrees via direct git \" +\n \"and shell invocations. No sandboxing — the host owns the filesystem \" +\n \"and process boundary.\",\n version: \"0.1.0\",\n kind: \"builtin\",\n implements: [\n { tool: \"worktree.provision\", version: \"0.1.0\" },\n { tool: \"worktree.cleanup\", version: \"0.1.0\" },\n { tool: \"worktree.run-gate\", version: \"0.1.0\" },\n ],\n implementations: [provisionWorktreeBuiltin, cleanupWorktreeBuiltin, runGateBuiltin],\n})\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineTool } from '@agentproto/tool';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @agentproto/worktree v0.1.0
|
|
6
|
+
* git-worktree provision / gate / cleanup TOOL contracts + builtin PROVIDER.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var provisionWorktreeTool = defineTool({
|
|
10
|
+
id: "worktree.provision",
|
|
11
|
+
description: "Create a git worktree for `repoRoot` at a sibling '_worktrees/<slug>' directory, on a new branch 'wt/<slug>' cut from `base`. If `depsCmd` is given, it runs inside the new worktree afterwards (e.g. install deps). If `copyGlobs` is given, matching files under `repoRoot` (including gitignored ones, e.g. local secrets) are copied into the worktree at the same relative path. If `linkPaths` is given, each is symlinked from `repoRoot` into the worktree before `depsCmd` runs \u2014 for gitignored, expensive-to-recreate trees a fresh worktree lacks (node_modules, sibling workspace repos) so the workspace graph resolves without a full reinstall.",
|
|
12
|
+
version: "0.1.0",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
repoRoot: z.string().describe("Absolute path to the git repository root."),
|
|
15
|
+
base: z.string().optional().describe("Ref the new branch is cut from. Default 'origin/main'."),
|
|
16
|
+
slug: z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "slug must be lowercase kebab-case").describe("Short identifier \u2014 names both the worktree directory and its branch."),
|
|
17
|
+
depsCmd: z.string().optional().describe("Shell command run inside the worktree after creation, e.g. 'pnpm install --prefer-offline'."),
|
|
18
|
+
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'."),
|
|
19
|
+
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.")
|
|
20
|
+
}),
|
|
21
|
+
outputSchema: z.object({
|
|
22
|
+
cwd: z.string().describe("Absolute path to the created worktree."),
|
|
23
|
+
branch: z.string().describe("The branch the worktree was created on.")
|
|
24
|
+
}),
|
|
25
|
+
mutates: ["fs:write"],
|
|
26
|
+
approval: "auto",
|
|
27
|
+
riskLevel: 1
|
|
28
|
+
});
|
|
29
|
+
var cleanupWorktreeTool = defineTool({
|
|
30
|
+
id: "worktree.cleanup",
|
|
31
|
+
description: "Remove a git worktree created by 'worktree.provision' (force-removed, since installed deps / copied secrets / agent output leave it with untracked files). If `deleteBranch` is true and `branch` is given, also force-delete that branch.",
|
|
32
|
+
version: "0.1.0",
|
|
33
|
+
inputSchema: z.object({
|
|
34
|
+
repoRoot: z.string().describe("Absolute path to the git repository root."),
|
|
35
|
+
cwd: z.string().describe("Absolute path to the worktree to remove."),
|
|
36
|
+
branch: z.string().optional().describe("The worktree's branch, for deleteBranch."),
|
|
37
|
+
deleteBranch: z.boolean().optional().describe("Also force-delete `branch` after removing the worktree.")
|
|
38
|
+
}),
|
|
39
|
+
outputSchema: z.object({
|
|
40
|
+
removed: z.literal(true)
|
|
41
|
+
}),
|
|
42
|
+
mutates: ["fs:write"],
|
|
43
|
+
approval: "auto",
|
|
44
|
+
riskLevel: 1
|
|
45
|
+
});
|
|
46
|
+
var runGateTool = defineTool({
|
|
47
|
+
id: "worktree.run-gate",
|
|
48
|
+
description: "Run a gate command (e.g. 'pnpm test') inside `cwd` and report pass/fail from its exit code, along with captured stdout/stderr.",
|
|
49
|
+
version: "0.1.0",
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
cwd: z.string().describe("Directory to run the command in."),
|
|
52
|
+
cmd: z.string().describe("Shell command to run, e.g. 'pnpm test'.")
|
|
53
|
+
}),
|
|
54
|
+
outputSchema: z.object({
|
|
55
|
+
passed: z.boolean(),
|
|
56
|
+
exitCode: z.number().int(),
|
|
57
|
+
stdout: z.string(),
|
|
58
|
+
stderr: z.string()
|
|
59
|
+
}),
|
|
60
|
+
mutates: [],
|
|
61
|
+
approval: "auto",
|
|
62
|
+
riskLevel: 1
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export { cleanupWorktreeTool, provisionWorktreeTool, runGateTool };
|
|
66
|
+
//# sourceMappingURL=chunk-FRVPZTQN.mjs.map
|
|
67
|
+
//# sourceMappingURL=chunk-FRVPZTQN.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"],"names":["defineTool","z"],"mappings":"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","file":"chunk-FRVPZTQN.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, on a new branch 'wt/<slug>' cut from `base`. If `depsCmd` is \" +\n \"given, it runs inside the new worktree afterwards (e.g. install deps). \" +\n \"If `copyGlobs` is given, matching files under `repoRoot` (including \" +\n \"gitignored ones, e.g. local secrets) are copied into the worktree at \" +\n \"the same relative path. If `linkPaths` is given, each is symlinked from \" +\n \"`repoRoot` into the worktree before `depsCmd` runs — for gitignored, \" +\n \"expensive-to-recreate trees a fresh worktree lacks (node_modules, \" +\n \"sibling workspace repos) so the workspace graph resolves without a full \" +\n \"reinstall.\",\n version: \"0.1.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 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 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 }),\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/** AIP-14 contract: remove a git worktree, optionally deleting its branch. */\nexport const cleanupWorktreeTool = defineTool({\n id: \"worktree.cleanup\",\n description:\n \"Remove a git worktree created by 'worktree.provision' (force-removed, \" +\n \"since installed deps / copied secrets / agent output leave it with \" +\n \"untracked files). If `deleteBranch` is true and `branch` is given, also \" +\n \"force-delete that branch.\",\n version: \"0.1.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 }),\n outputSchema: z.object({\n removed: z.literal(true),\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: 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"]}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { worktreeProvider } from './chunk-BBAH3VLQ.mjs';
|
|
2
|
+
import { provisionWorktreeTool, runGateTool, cleanupWorktreeTool } from './chunk-FRVPZTQN.mjs';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { resolveMcpUrl, HarnessClient } from '@agentproto/harness';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @agentproto/worktree v0.1.0
|
|
8
|
+
* git-worktree provision / gate / cleanup TOOL contracts + builtin PROVIDER.
|
|
9
|
+
*/
|
|
10
|
+
var worktreeAgentInputSchema = z.object({
|
|
11
|
+
repoRoot: z.string().describe("Absolute path to the git repository root."),
|
|
12
|
+
slug: z.string().describe("Worktree/branch identifier, e.g. 'fix-flaky-test'."),
|
|
13
|
+
base: z.string().optional().describe("Ref to cut the worktree branch from. Default 'origin/main'."),
|
|
14
|
+
depsCmd: z.string().optional().describe("Command to install deps inside the worktree, e.g. 'pnpm install --prefer-offline'."),
|
|
15
|
+
copyGlobs: z.array(z.string()).optional().describe("Gitignored files to copy into the worktree, e.g. secrets."),
|
|
16
|
+
linkPaths: z.array(z.string()).optional().describe("Gitignored dirs/files symlinked from the host repo into the worktree before depsCmd (node_modules, sibling workspace repos) so the graph resolves without a full reinstall."),
|
|
17
|
+
task: z.string().describe("The prompt sent to the coding agent."),
|
|
18
|
+
adapter: z.string().optional().describe("Agent adapter slug. Default 'claude-code'."),
|
|
19
|
+
gateCmd: z.string().describe("Command run inside the worktree to check the agent's work, e.g. 'pnpm test'."),
|
|
20
|
+
deleteBranch: z.boolean().optional().describe("Delete the worktree's branch on cleanup. Default true.")
|
|
21
|
+
});
|
|
22
|
+
var candidates = [worktreeProvider];
|
|
23
|
+
function input(b) {
|
|
24
|
+
return b.input;
|
|
25
|
+
}
|
|
26
|
+
var worktreeAgentWorkflow = {
|
|
27
|
+
id: "worktree-agent",
|
|
28
|
+
description: "Launch a coding agent inside a fresh git worktree, gate its work with a check command, get a human ack, then clean up. On gate failure the worktree is left in place for inspection instead of being cleaned up.",
|
|
29
|
+
steps: [
|
|
30
|
+
{
|
|
31
|
+
kind: "tool",
|
|
32
|
+
id: "provision",
|
|
33
|
+
tool: provisionWorktreeTool,
|
|
34
|
+
candidates,
|
|
35
|
+
input: (b) => {
|
|
36
|
+
const i = input(b);
|
|
37
|
+
return {
|
|
38
|
+
repoRoot: i.repoRoot,
|
|
39
|
+
slug: i.slug,
|
|
40
|
+
base: i.base,
|
|
41
|
+
depsCmd: i.depsCmd,
|
|
42
|
+
copyGlobs: i.copyGlobs,
|
|
43
|
+
linkPaths: i.linkPaths
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
kind: "agent",
|
|
49
|
+
id: "code",
|
|
50
|
+
adapter: (b) => input(b).adapter ?? "claude-code",
|
|
51
|
+
cwd: (b) => b.steps.provision.cwd,
|
|
52
|
+
prompt: (b) => input(b).task
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
kind: "tool",
|
|
56
|
+
id: "gate",
|
|
57
|
+
tool: runGateTool,
|
|
58
|
+
candidates,
|
|
59
|
+
input: (b) => ({
|
|
60
|
+
cwd: b.steps.provision.cwd,
|
|
61
|
+
cmd: input(b).gateCmd
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
kind: "branch",
|
|
66
|
+
id: "route",
|
|
67
|
+
cond: (b) => b.steps.gate.passed,
|
|
68
|
+
then: [
|
|
69
|
+
{
|
|
70
|
+
kind: "approval",
|
|
71
|
+
id: "approval",
|
|
72
|
+
prompt: () => "Gate passed. Approve worktree cleanup?",
|
|
73
|
+
onApprove: [
|
|
74
|
+
{
|
|
75
|
+
kind: "tool",
|
|
76
|
+
id: "cleanup",
|
|
77
|
+
tool: cleanupWorktreeTool,
|
|
78
|
+
candidates,
|
|
79
|
+
input: (b) => ({
|
|
80
|
+
repoRoot: input(b).repoRoot,
|
|
81
|
+
cwd: b.steps.provision.cwd,
|
|
82
|
+
branch: b.steps.provision.branch,
|
|
83
|
+
deleteBranch: input(b).deleteBranch ?? true
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
onReject: [
|
|
88
|
+
{
|
|
89
|
+
kind: "transform",
|
|
90
|
+
id: "held",
|
|
91
|
+
compute: () => ({ removed: false, reason: "cleanup_rejected" })
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
otherwise: [
|
|
97
|
+
{
|
|
98
|
+
kind: "transform",
|
|
99
|
+
id: "gate-failed",
|
|
100
|
+
compute: () => ({ removed: false, reason: "gate_failed" })
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
output: (b) => ({
|
|
106
|
+
cwd: b.steps.provision.cwd,
|
|
107
|
+
branch: b.steps.provision.branch,
|
|
108
|
+
gate: b.steps.gate,
|
|
109
|
+
cleanup: b.steps.cleanup ?? b.steps.held ?? b.steps["gate-failed"]
|
|
110
|
+
})
|
|
111
|
+
};
|
|
112
|
+
var MAX_POLL_MS = 49e3;
|
|
113
|
+
async function connectDaemonAgentSessionHost(opts) {
|
|
114
|
+
const url = resolveMcpUrl(opts);
|
|
115
|
+
let client;
|
|
116
|
+
try {
|
|
117
|
+
client = await HarnessClient.connect({ url });
|
|
118
|
+
} catch (err) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`worktree-agent: could not reach the agentproto daemon's MCP endpoint at ${url}. Start the daemon (\`agentproto daemon start\`), or point at a running one via AGENTPROTO_MCP_URL, before running this command.
|
|
121
|
+
(${err instanceof Error ? err.message : String(err)})`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return makeDaemonAgentSessionHost(client);
|
|
125
|
+
}
|
|
126
|
+
function makeDaemonAgentSessionHost(client) {
|
|
127
|
+
const sessionByStepId = /* @__PURE__ */ new Map();
|
|
128
|
+
return {
|
|
129
|
+
async spawn(adapter, opts) {
|
|
130
|
+
const desc = await client.start({
|
|
131
|
+
adapter,
|
|
132
|
+
...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
|
|
133
|
+
...opts.workspaceSlug !== void 0 ? { workspaceSlug: opts.workspaceSlug } : {},
|
|
134
|
+
...opts.stepId !== void 0 ? { label: opts.stepId } : {}
|
|
135
|
+
});
|
|
136
|
+
if (opts.stepId) sessionByStepId.set(opts.stepId, desc.id);
|
|
137
|
+
return desc.id;
|
|
138
|
+
},
|
|
139
|
+
async sendPromptAndWait(sessionId, prompt) {
|
|
140
|
+
const waitPromise = waitForSettled(client, sessionId);
|
|
141
|
+
await client.prompt(sessionId, prompt);
|
|
142
|
+
await waitPromise;
|
|
143
|
+
},
|
|
144
|
+
resolveByLabel(stepId) {
|
|
145
|
+
return sessionByStepId.get(stepId);
|
|
146
|
+
},
|
|
147
|
+
async start(args) {
|
|
148
|
+
return client.start(args);
|
|
149
|
+
},
|
|
150
|
+
async prompt(sessionId, prompt, opts) {
|
|
151
|
+
await client.prompt(sessionId, prompt, opts);
|
|
152
|
+
},
|
|
153
|
+
async output(sessionId, lastN) {
|
|
154
|
+
return client.output(sessionId, lastN);
|
|
155
|
+
},
|
|
156
|
+
async kill(sessionId) {
|
|
157
|
+
await client.kill(sessionId);
|
|
158
|
+
},
|
|
159
|
+
async waitForAny(sessionIds, opts) {
|
|
160
|
+
return client.waitForAny(sessionIds, opts);
|
|
161
|
+
},
|
|
162
|
+
async close() {
|
|
163
|
+
await client.close();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function waitForSettled(client, sessionId) {
|
|
168
|
+
for (; ; ) {
|
|
169
|
+
const result = await client.waitForAny([sessionId], { event: "any", timeoutMs: MAX_POLL_MS });
|
|
170
|
+
if (!result.timedOut) return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow };
|
|
175
|
+
//# sourceMappingURL=chunk-P7FOHOQ3.mjs.map
|
|
176
|
+
//# sourceMappingURL=chunk-P7FOHOQ3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflow.ts","../src/agent-session-host.ts"],"names":[],"mappings":"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","file":"chunk-P7FOHOQ3.mjs","sourcesContent":["import { z } from \"zod\"\nimport type { Bindings, RuntimeWorkflow } from \"@agentproto/workflow-runtime\"\nimport { provisionWorktreeTool, cleanupWorktreeTool, runGateTool } from \"./tools/index.js\"\nimport { worktreeProvider } from \"./provider/index.js\"\n\n/**\n * Input to the worktree-agent workflow: launch a coding agent inside a\n * fresh git worktree, gate its work, and — on approval — clean up.\n */\nexport const worktreeAgentInputSchema = z.object({\n repoRoot: z.string().describe(\"Absolute path to the git repository root.\"),\n slug: z.string().describe(\"Worktree/branch identifier, e.g. 'fix-flaky-test'.\"),\n base: z.string().optional().describe(\"Ref to cut the worktree branch from. Default 'origin/main'.\"),\n depsCmd: z.string().optional().describe(\"Command to install deps inside the worktree, e.g. 'pnpm install --prefer-offline'.\"),\n copyGlobs: z.array(z.string()).optional().describe(\"Gitignored files to copy into the worktree, e.g. secrets.\"),\n linkPaths: z.array(z.string()).optional().describe(\"Gitignored dirs/files symlinked from the host repo into the worktree before depsCmd (node_modules, sibling workspace repos) so the graph resolves without a full reinstall.\"),\n task: z.string().describe(\"The prompt sent to the coding agent.\"),\n adapter: z.string().optional().describe(\"Agent adapter slug. Default 'claude-code'.\"),\n gateCmd: z.string().describe(\"Command run inside the worktree to check the agent's work, e.g. 'pnpm test'.\"),\n deleteBranch: z.boolean().optional().describe(\"Delete the worktree's branch on cleanup. Default true.\"),\n})\n\nexport type WorktreeAgentInput = z.infer<typeof worktreeAgentInputSchema>\n\nconst candidates = [worktreeProvider]\n\nfunction input(b: { input: unknown }): WorktreeAgentInput {\n return b.input as WorktreeAgentInput\n}\n\n/**\n * provision (tool) → code (AgentStep, cwd bound to provision's output) →\n * gate (tool) → on pass: approval → cleanup (tool); on fail: leave the\n * worktree in place for inspection.\n */\nexport const worktreeAgentWorkflow: RuntimeWorkflow = {\n id: \"worktree-agent\",\n description:\n \"Launch a coding agent inside a fresh git worktree, gate its work with a \" +\n \"check command, get a human ack, then clean up. On gate failure the \" +\n \"worktree is left in place for inspection instead of being cleaned up.\",\n steps: [\n {\n kind: \"tool\",\n id: \"provision\",\n tool: provisionWorktreeTool,\n candidates,\n input: (b: Bindings) => {\n const i = input(b)\n return {\n repoRoot: i.repoRoot,\n slug: i.slug,\n base: i.base,\n depsCmd: i.depsCmd,\n copyGlobs: i.copyGlobs,\n linkPaths: i.linkPaths,\n }\n },\n },\n {\n kind: \"agent\",\n id: \"code\",\n adapter: (b: Bindings) => input(b).adapter ?? \"claude-code\",\n cwd: (b: Bindings) => (b.steps.provision as { cwd: string }).cwd,\n prompt: (b: Bindings) => input(b).task,\n },\n {\n kind: \"tool\",\n id: \"gate\",\n tool: runGateTool,\n candidates,\n input: (b: Bindings) => ({\n cwd: (b.steps.provision as { cwd: string }).cwd,\n cmd: input(b).gateCmd,\n }),\n },\n {\n kind: \"branch\",\n id: \"route\",\n cond: (b: Bindings) => (b.steps.gate as { passed: boolean }).passed,\n then: [\n {\n kind: \"approval\",\n id: \"approval\",\n prompt: () => \"Gate passed. Approve worktree cleanup?\",\n onApprove: [\n {\n kind: \"tool\",\n id: \"cleanup\",\n tool: cleanupWorktreeTool,\n candidates,\n input: (b: Bindings) => ({\n repoRoot: input(b).repoRoot,\n cwd: (b.steps.provision as { cwd: string }).cwd,\n branch: (b.steps.provision as { branch: string }).branch,\n deleteBranch: input(b).deleteBranch ?? true,\n }),\n },\n ],\n onReject: [\n {\n kind: \"transform\",\n id: \"held\",\n compute: () => ({ removed: false, reason: \"cleanup_rejected\" as const }),\n },\n ],\n },\n ],\n otherwise: [\n {\n kind: \"transform\",\n id: \"gate-failed\",\n compute: () => ({ removed: false, reason: \"gate_failed\" as const }),\n },\n ],\n },\n ],\n output: (b: Bindings) => ({\n cwd: (b.steps.provision as { cwd: string }).cwd,\n branch: (b.steps.provision as { branch: string }).branch,\n gate: b.steps.gate,\n cleanup: b.steps.cleanup ?? b.steps.held ?? b.steps[\"gate-failed\"],\n }),\n}\n","import {\n HarnessClient,\n resolveMcpUrl,\n type ConnectHarnessOptions,\n type SessionDescriptor,\n type StartAgentArgs,\n type TurnEvent,\n type TurnResult,\n} from \"@agentproto/harness\"\nimport type { AgentSessionHost } from \"@agentproto/workflow-runtime\"\n\n/** `session_monitor`'s max accepted long-poll window (`orchestration-tools.ts`). */\nconst MAX_POLL_MS = 49_000\n\n/** The subset of `HarnessClient` the host drives — narrowed for fake-client tests. */\nexport type DaemonClient = Pick<\n HarnessClient,\n \"start\" | \"prompt\" | \"output\" | \"kill\" | \"waitForAny\" | \"close\"\n>\n\nexport interface DaemonAgentSessionHost extends AgentSessionHost {\n close(): Promise<void>\n /** Full `agent_start`, returning the raw descriptor and forwarding every\n * `StartAgentArgs` field (`mcpServers`, `model`, `effort`, `label`, …) —\n * unlike `spawn()` above (the narrow AgentStep-shaped convenience\n * wrapper), which only takes `cwd`/`workspaceSlug`/`stepId`. Consumers\n * that need finer per-turn control than the AgentStep contract provides\n * (e.g. `@agentproto/sandbox`'s sandbox agent-session proxy) use this\n * plus `prompt`/`output`/`kill`/`waitForAny` directly instead of\n * `spawn`/`sendPromptAndWait`. */\n start(args: StartAgentArgs): Promise<SessionDescriptor>\n /** Send a follow-up turn — `opts.interrupt` cancels an in-flight turn and\n * redirects the session onto this prompt (mirrors `agent_prompt`). */\n prompt(sessionId: string, prompt: string, opts?: { interrupt?: boolean }): Promise<void>\n /** Tail the ring buffer (`agent_output`). */\n output(sessionId: string, lastN?: number): Promise<string>\n /** SIGTERM the session (`agent_kill`). */\n kill(sessionId: string): Promise<void>\n /** Multiplexed long-poll (`session_monitor`) — see `HarnessClient.waitForAny`. */\n waitForAny(sessionIds: string[], opts?: { timeoutMs?: number; event?: TurnEvent }): Promise<TurnResult>\n}\n\n/**\n * Connect to the agentproto daemon's MCP endpoint and return a real,\n * supervisable `AgentSessionHost` — every `AgentStep` becomes an `agent_start`\n * daemon session rather than a bare subprocess. Fails loudly (no silent\n * fallback) if the daemon isn't reachable.\n */\nexport async function connectDaemonAgentSessionHost(\n opts?: ConnectHarnessOptions,\n): Promise<DaemonAgentSessionHost> {\n const url = resolveMcpUrl(opts)\n let client: HarnessClient\n try {\n client = await HarnessClient.connect({ url })\n } catch (err) {\n throw new Error(\n `worktree-agent: could not reach the agentproto daemon's MCP endpoint at ${url}. ` +\n \"Start the daemon (`agentproto daemon start`), or point at a running one via \" +\n `AGENTPROTO_MCP_URL, before running this command.\\n` +\n `(${err instanceof Error ? err.message : String(err)})`,\n )\n }\n return makeDaemonAgentSessionHost(client)\n}\n\n/** Build the host over an already-connected client (exported for tests with a fake client). */\nexport function makeDaemonAgentSessionHost(client: DaemonClient): DaemonAgentSessionHost {\n const sessionByStepId = new Map<string, string>()\n\n return {\n async spawn(adapter, opts): Promise<string> {\n const desc = await client.start({\n adapter,\n ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),\n ...(opts.workspaceSlug !== undefined ? { workspaceSlug: opts.workspaceSlug } : {}),\n ...(opts.stepId !== undefined ? { label: opts.stepId } : {}),\n })\n if (opts.stepId) sessionByStepId.set(opts.stepId, desc.id)\n return desc.id\n },\n\n async sendPromptAndWait(sessionId, prompt): Promise<void> {\n // Subscribe before sending: an extremely fast turn could otherwise settle\n // before the wait call subscribes (same race `harness`'s `ask()` avoids).\n const waitPromise = waitForSettled(client, sessionId)\n await client.prompt(sessionId, prompt)\n await waitPromise\n },\n\n resolveByLabel(stepId): string | undefined {\n return sessionByStepId.get(stepId)\n },\n\n async start(args): Promise<SessionDescriptor> {\n return client.start(args)\n },\n\n async prompt(sessionId, prompt, opts): Promise<void> {\n await client.prompt(sessionId, prompt, opts)\n },\n\n async output(sessionId, lastN): Promise<string> {\n return client.output(sessionId, lastN)\n },\n\n async kill(sessionId): Promise<void> {\n await client.kill(sessionId)\n },\n\n async waitForAny(sessionIds, opts): Promise<TurnResult> {\n return client.waitForAny(sessionIds, opts)\n },\n\n async close(): Promise<void> {\n await client.close()\n },\n }\n}\n\n/**\n * `session_monitor` long-polls cap at 49s; loop past timeouts for longer\n * turns. A clean timeout comes back as `{ timedOut: true, sessionIds }` —\n * there is no `event: \"timeout\"` on the wire, `event` is only ever set on a\n * real match. Keep polling while `timedOut` is true; stop once it's\n * falsy/absent (a real turn-end/awaiting-input/exited match).\n */\nasync function waitForSettled(client: DaemonClient, sessionId: string): Promise<void> {\n for (;;) {\n const result = await client.waitForAny([sessionId], { event: \"any\", timeoutMs: MAX_POLL_MS })\n if (!result.timedOut) return\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-PSFICTF7.mjs"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export { cleanupWorktreeTool, provisionWorktreeTool, runGateTool } from './tools/index.js';
|
|
2
|
+
export { worktreeProvider } from './provider/index.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { RuntimeWorkflow, AgentSessionHost } from '@agentproto/workflow-runtime';
|
|
5
|
+
import { StartAgentArgs, SessionDescriptor, TurnEvent, TurnResult, HarnessClient, ConnectHarnessOptions } from '@agentproto/harness';
|
|
6
|
+
import '@agentproto/tool';
|
|
7
|
+
import '@agentproto/driver';
|
|
8
|
+
|
|
9
|
+
interface ExecResult {
|
|
10
|
+
exitCode: number;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
}
|
|
14
|
+
/** Run an argv (no shell — args pass through verbatim, no injection risk). */
|
|
15
|
+
declare function execArgv(command: string, args: readonly string[], cwd: string): Promise<ExecResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Run a single command string through a shell. Callers pass `depsCmd` /
|
|
18
|
+
* `gateCmd` as a trusted, developer-authored config value (the same trust
|
|
19
|
+
* model as a CI job's `script:` line) — not untrusted end-user input.
|
|
20
|
+
*/
|
|
21
|
+
declare function execShell(cmd: string, cwd: string): Promise<ExecResult>;
|
|
22
|
+
declare function execGit(repoRoot: string, args: readonly string[]): Promise<ExecResult>;
|
|
23
|
+
|
|
24
|
+
/** Compile a `/`-separated glob (supporting `*`, `?`, `**`) into an anchored regex. */
|
|
25
|
+
declare function globToRegExp(pattern: string): RegExp;
|
|
26
|
+
/** Expand a glob against `root`, returning root-relative posix paths of matching files. */
|
|
27
|
+
declare function expandGlob(root: string, pattern: string): Promise<string[]>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Input to the worktree-agent workflow: launch a coding agent inside a
|
|
31
|
+
* fresh git worktree, gate its work, and — on approval — clean up.
|
|
32
|
+
*/
|
|
33
|
+
declare const worktreeAgentInputSchema: z.ZodObject<{
|
|
34
|
+
repoRoot: z.ZodString;
|
|
35
|
+
slug: z.ZodString;
|
|
36
|
+
base: z.ZodOptional<z.ZodString>;
|
|
37
|
+
depsCmd: z.ZodOptional<z.ZodString>;
|
|
38
|
+
copyGlobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
39
|
+
linkPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
40
|
+
task: z.ZodString;
|
|
41
|
+
adapter: z.ZodOptional<z.ZodString>;
|
|
42
|
+
gateCmd: z.ZodString;
|
|
43
|
+
deleteBranch: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
type WorktreeAgentInput = z.infer<typeof worktreeAgentInputSchema>;
|
|
46
|
+
/**
|
|
47
|
+
* provision (tool) → code (AgentStep, cwd bound to provision's output) →
|
|
48
|
+
* gate (tool) → on pass: approval → cleanup (tool); on fail: leave the
|
|
49
|
+
* worktree in place for inspection.
|
|
50
|
+
*/
|
|
51
|
+
declare const worktreeAgentWorkflow: RuntimeWorkflow;
|
|
52
|
+
|
|
53
|
+
/** The subset of `HarnessClient` the host drives — narrowed for fake-client tests. */
|
|
54
|
+
type DaemonClient = Pick<HarnessClient, "start" | "prompt" | "output" | "kill" | "waitForAny" | "close">;
|
|
55
|
+
interface DaemonAgentSessionHost extends AgentSessionHost {
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
/** Full `agent_start`, returning the raw descriptor and forwarding every
|
|
58
|
+
* `StartAgentArgs` field (`mcpServers`, `model`, `effort`, `label`, …) —
|
|
59
|
+
* unlike `spawn()` above (the narrow AgentStep-shaped convenience
|
|
60
|
+
* wrapper), which only takes `cwd`/`workspaceSlug`/`stepId`. Consumers
|
|
61
|
+
* that need finer per-turn control than the AgentStep contract provides
|
|
62
|
+
* (e.g. `@agentproto/sandbox`'s sandbox agent-session proxy) use this
|
|
63
|
+
* plus `prompt`/`output`/`kill`/`waitForAny` directly instead of
|
|
64
|
+
* `spawn`/`sendPromptAndWait`. */
|
|
65
|
+
start(args: StartAgentArgs): Promise<SessionDescriptor>;
|
|
66
|
+
/** Send a follow-up turn — `opts.interrupt` cancels an in-flight turn and
|
|
67
|
+
* redirects the session onto this prompt (mirrors `agent_prompt`). */
|
|
68
|
+
prompt(sessionId: string, prompt: string, opts?: {
|
|
69
|
+
interrupt?: boolean;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
/** Tail the ring buffer (`agent_output`). */
|
|
72
|
+
output(sessionId: string, lastN?: number): Promise<string>;
|
|
73
|
+
/** SIGTERM the session (`agent_kill`). */
|
|
74
|
+
kill(sessionId: string): Promise<void>;
|
|
75
|
+
/** Multiplexed long-poll (`session_monitor`) — see `HarnessClient.waitForAny`. */
|
|
76
|
+
waitForAny(sessionIds: string[], opts?: {
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
event?: TurnEvent;
|
|
79
|
+
}): Promise<TurnResult>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Connect to the agentproto daemon's MCP endpoint and return a real,
|
|
83
|
+
* supervisable `AgentSessionHost` — every `AgentStep` becomes an `agent_start`
|
|
84
|
+
* daemon session rather than a bare subprocess. Fails loudly (no silent
|
|
85
|
+
* fallback) if the daemon isn't reachable.
|
|
86
|
+
*/
|
|
87
|
+
declare function connectDaemonAgentSessionHost(opts?: ConnectHarnessOptions): Promise<DaemonAgentSessionHost>;
|
|
88
|
+
/** Build the host over an already-connected client (exported for tests with a fake client). */
|
|
89
|
+
declare function makeDaemonAgentSessionHost(client: DaemonClient): DaemonAgentSessionHost;
|
|
90
|
+
|
|
91
|
+
export { type DaemonAgentSessionHost, type DaemonClient, type ExecResult, type WorktreeAgentInput, connectDaemonAgentSessionHost, execArgv, execGit, execShell, expandGlob, globToRegExp, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-P7FOHOQ3.mjs';
|
|
2
|
+
import './chunk-PSFICTF7.mjs';
|
|
3
|
+
export { execArgv, execGit, execShell, expandGlob, globToRegExp, worktreeProvider } from './chunk-BBAH3VLQ.mjs';
|
|
4
|
+
export { cleanupWorktreeTool, provisionWorktreeTool, runGateTool } from './chunk-FRVPZTQN.mjs';
|
|
5
|
+
//# sourceMappingURL=index.mjs.map
|
|
6
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as _agentproto_driver from '@agentproto/driver';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AIP-30 PROVIDER for the three `worktree.*` TOOL contracts. `kind: "builtin"`
|
|
5
|
+
* — runs in-process, shelling out to `git` / the caller's commands directly.
|
|
6
|
+
* No subprocess-DRIVER indirection, no network hop.
|
|
7
|
+
*/
|
|
8
|
+
declare const worktreeProvider: _agentproto_driver.DriverHandle;
|
|
9
|
+
|
|
10
|
+
export { worktreeProvider };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as _agentproto_tool from '@agentproto/tool';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AIP-14 contract: create a git worktree off a base ref, on its own branch,
|
|
5
|
+
* optionally installing deps and copying gitignored files (secrets) into it.
|
|
6
|
+
* Agnostic — no hardcoded package manager or env layout; both are inputs.
|
|
7
|
+
*/
|
|
8
|
+
declare const provisionWorktreeTool: _agentproto_tool.ToolHandle<{
|
|
9
|
+
repoRoot: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
base?: string | undefined;
|
|
12
|
+
depsCmd?: string | undefined;
|
|
13
|
+
copyGlobs?: string[] | undefined;
|
|
14
|
+
linkPaths?: string[] | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
cwd: string;
|
|
17
|
+
branch: string;
|
|
18
|
+
}, _agentproto_tool.ToolContext>;
|
|
19
|
+
|
|
20
|
+
/** AIP-14 contract: remove a git worktree, optionally deleting its branch. */
|
|
21
|
+
declare const cleanupWorktreeTool: _agentproto_tool.ToolHandle<{
|
|
22
|
+
repoRoot: string;
|
|
23
|
+
cwd: string;
|
|
24
|
+
branch?: string | undefined;
|
|
25
|
+
deleteBranch?: boolean | undefined;
|
|
26
|
+
}, {
|
|
27
|
+
removed: true;
|
|
28
|
+
}, _agentproto_tool.ToolContext>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* AIP-14 contract: run a caller-provided gate/check command inside a
|
|
32
|
+
* directory (typically a provisioned worktree) and report pass/fail.
|
|
33
|
+
* `cmd` is a trusted, developer-authored config value (same trust model as
|
|
34
|
+
* a CI job's `script:` line), run through a shell so it may include flags.
|
|
35
|
+
*/
|
|
36
|
+
declare const runGateTool: _agentproto_tool.ToolHandle<{
|
|
37
|
+
cwd: string;
|
|
38
|
+
cmd: string;
|
|
39
|
+
}, {
|
|
40
|
+
passed: boolean;
|
|
41
|
+
exitCode: number;
|
|
42
|
+
stdout: string;
|
|
43
|
+
stderr: string;
|
|
44
|
+
}, _agentproto_tool.ToolContext>;
|
|
45
|
+
|
|
46
|
+
export { cleanupWorktreeTool, provisionWorktreeTool, runGateTool };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentproto/worktree",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "AIP-14 TOOL contracts + builtin AIP-30 PROVIDER for provisioning, gating, and cleaning up a git worktree — the primitive an AgentStep binds its `cwd` to for a launch-agent-in-a-worktree workflow.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agentproto",
|
|
7
|
+
"aip-14",
|
|
8
|
+
"aip-30",
|
|
9
|
+
"worktree",
|
|
10
|
+
"git",
|
|
11
|
+
"workflow",
|
|
12
|
+
"open-standard",
|
|
13
|
+
"agentic"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://agentproto.sh",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/agentproto/ts",
|
|
19
|
+
"directory": "packages/worktree"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/agentproto/ts/issues"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "dist/index.mjs",
|
|
27
|
+
"module": "dist/index.mjs",
|
|
28
|
+
"types": "dist/index.d.ts",
|
|
29
|
+
"bin": {
|
|
30
|
+
"worktree-agent": "dist/bin/worktree-agent.mjs"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.mjs",
|
|
36
|
+
"default": "./dist/index.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./tools": {
|
|
39
|
+
"types": "./dist/tools/index.d.ts",
|
|
40
|
+
"import": "./dist/tools/index.mjs",
|
|
41
|
+
"default": "./dist/tools/index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./provider": {
|
|
44
|
+
"types": "./dist/provider/index.d.ts",
|
|
45
|
+
"import": "./dist/provider/index.mjs",
|
|
46
|
+
"default": "./dist/provider/index.mjs"
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
60
|
+
"zod": "^4.4.3",
|
|
61
|
+
"@agentproto/harness": "0.2.0",
|
|
62
|
+
"@agentproto/driver": "0.1.2",
|
|
63
|
+
"@agentproto/tool": "0.2.0",
|
|
64
|
+
"@agentproto/workflow-runtime": "0.3.0"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/node": "^25.6.2",
|
|
68
|
+
"tsup": "^8.5.1",
|
|
69
|
+
"typescript": "^5.9.3",
|
|
70
|
+
"vitest": "^3.2.4",
|
|
71
|
+
"@agentproto/tooling": "0.1.0-alpha.0"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"dev": "tsup --watch",
|
|
75
|
+
"build": "tsup",
|
|
76
|
+
"clean": "rm -rf dist",
|
|
77
|
+
"check-types": "tsc --noEmit",
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"test:watch": "vitest"
|
|
80
|
+
}
|
|
81
|
+
}
|