@tokenfactory/acc-runner 0.27.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anthropic-auth.d.ts +53 -0
- package/dist/anthropic-auth.d.ts.map +1 -0
- package/dist/anthropic-auth.js +89 -0
- package/dist/anthropic-auth.js.map +1 -0
- package/dist/bin-resolve.d.ts +16 -0
- package/dist/bin-resolve.d.ts.map +1 -0
- package/dist/bin-resolve.js +35 -0
- package/dist/bin-resolve.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +128 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +121 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +396 -0
- package/dist/config.js.map +1 -0
- package/dist/cost-pricing.d.ts +130 -0
- package/dist/cost-pricing.d.ts.map +1 -0
- package/dist/cost-pricing.js +191 -0
- package/dist/cost-pricing.js.map +1 -0
- package/dist/doctor.d.ts +49 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +776 -0
- package/dist/doctor.js.map +1 -0
- package/dist/failure-classifier.d.ts +112 -0
- package/dist/failure-classifier.d.ts.map +1 -0
- package/dist/failure-classifier.js +353 -0
- package/dist/failure-classifier.js.map +1 -0
- package/dist/gh.d.ts +22 -0
- package/dist/gh.d.ts.map +1 -0
- package/dist/gh.js +48 -0
- package/dist/gh.js.map +1 -0
- package/dist/git.d.ts +50 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +127 -0
- package/dist/git.js.map +1 -0
- package/dist/github-client.d.ts +133 -0
- package/dist/github-client.d.ts.map +1 -0
- package/dist/github-client.js +234 -0
- package/dist/github-client.js.map +1 -0
- package/dist/keychain.d.ts +21 -0
- package/dist/keychain.d.ts.map +1 -0
- package/dist/keychain.js +45 -0
- package/dist/keychain.js.map +1 -0
- package/dist/login.d.ts +12 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +133 -0
- package/dist/login.js.map +1 -0
- package/dist/logout.d.ts +2 -0
- package/dist/logout.d.ts.map +1 -0
- package/dist/logout.js +31 -0
- package/dist/logout.js.map +1 -0
- package/dist/mcp-spawn.d.ts +30 -0
- package/dist/mcp-spawn.d.ts.map +1 -0
- package/dist/mcp-spawn.js +145 -0
- package/dist/mcp-spawn.js.map +1 -0
- package/dist/messaging.d.ts +49 -0
- package/dist/messaging.d.ts.map +1 -0
- package/dist/messaging.js +36 -0
- package/dist/messaging.js.map +1 -0
- package/dist/pkg-version.d.ts +3 -0
- package/dist/pkg-version.d.ts.map +1 -0
- package/dist/pkg-version.js +20 -0
- package/dist/pkg-version.js.map +1 -0
- package/dist/profiles/designer-prompt.d.ts +18 -0
- package/dist/profiles/designer-prompt.d.ts.map +1 -0
- package/dist/profiles/designer-prompt.js +172 -0
- package/dist/profiles/designer-prompt.js.map +1 -0
- package/dist/profiles/developer-prompt.d.ts +24 -0
- package/dist/profiles/developer-prompt.d.ts.map +1 -0
- package/dist/profiles/developer-prompt.js +24 -0
- package/dist/profiles/developer-prompt.js.map +1 -0
- package/dist/profiles/manager-prompt.d.ts +34 -0
- package/dist/profiles/manager-prompt.d.ts.map +1 -0
- package/dist/profiles/manager-prompt.js +93 -0
- package/dist/profiles/manager-prompt.js.map +1 -0
- package/dist/profiles/tester-prompt.d.ts +28 -0
- package/dist/profiles/tester-prompt.d.ts.map +1 -0
- package/dist/profiles/tester-prompt.js +165 -0
- package/dist/profiles/tester-prompt.js.map +1 -0
- package/dist/prompt.d.ts +38 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +78 -0
- package/dist/prompt.js.map +1 -0
- package/dist/runtime/cache-dir.d.ts +10 -0
- package/dist/runtime/cache-dir.d.ts.map +1 -0
- package/dist/runtime/cache-dir.js +42 -0
- package/dist/runtime/cache-dir.js.map +1 -0
- package/dist/runtime/conflict-resolver.d.ts +65 -0
- package/dist/runtime/conflict-resolver.d.ts.map +1 -0
- package/dist/runtime/conflict-resolver.js +477 -0
- package/dist/runtime/conflict-resolver.js.map +1 -0
- package/dist/runtime/expand-args.d.ts +28 -0
- package/dist/runtime/expand-args.d.ts.map +1 -0
- package/dist/runtime/expand-args.js +50 -0
- package/dist/runtime/expand-args.js.map +1 -0
- package/dist/runtime/locks.d.ts +21 -0
- package/dist/runtime/locks.d.ts.map +1 -0
- package/dist/runtime/locks.js +103 -0
- package/dist/runtime/locks.js.map +1 -0
- package/dist/runtime/provision-mutex.d.ts +37 -0
- package/dist/runtime/provision-mutex.d.ts.map +1 -0
- package/dist/runtime/provision-mutex.js +67 -0
- package/dist/runtime/provision-mutex.js.map +1 -0
- package/dist/runtime/quarantine.d.ts +30 -0
- package/dist/runtime/quarantine.d.ts.map +1 -0
- package/dist/runtime/quarantine.js +53 -0
- package/dist/runtime/quarantine.js.map +1 -0
- package/dist/runtime/resolution-integrity.d.ts +86 -0
- package/dist/runtime/resolution-integrity.d.ts.map +1 -0
- package/dist/runtime/resolution-integrity.js +248 -0
- package/dist/runtime/resolution-integrity.js.map +1 -0
- package/dist/runtime/reviewer.d.ts +81 -0
- package/dist/runtime/reviewer.d.ts.map +1 -0
- package/dist/runtime/reviewer.js +374 -0
- package/dist/runtime/reviewer.js.map +1 -0
- package/dist/runtime/rework.d.ts +48 -0
- package/dist/runtime/rework.d.ts.map +1 -0
- package/dist/runtime/rework.js +136 -0
- package/dist/runtime/rework.js.map +1 -0
- package/dist/runtime/singleton.d.ts +66 -0
- package/dist/runtime/singleton.d.ts.map +1 -0
- package/dist/runtime/singleton.js +227 -0
- package/dist/runtime/singleton.js.map +1 -0
- package/dist/runtime/version-drift.d.ts +31 -0
- package/dist/runtime/version-drift.d.ts.map +1 -0
- package/dist/runtime/version-drift.js +114 -0
- package/dist/runtime/version-drift.js.map +1 -0
- package/dist/runtime/worktree.d.ts +74 -0
- package/dist/runtime/worktree.d.ts.map +1 -0
- package/dist/runtime/worktree.js +211 -0
- package/dist/runtime/worktree.js.map +1 -0
- package/dist/secrets/inject.d.ts +70 -0
- package/dist/secrets/inject.d.ts.map +1 -0
- package/dist/secrets/inject.js +102 -0
- package/dist/secrets/inject.js.map +1 -0
- package/dist/supabase.d.ts +4 -0
- package/dist/supabase.d.ts.map +1 -0
- package/dist/supabase.js +36 -0
- package/dist/supabase.js.map +1 -0
- package/dist/task-runner.d.ts +313 -0
- package/dist/task-runner.d.ts.map +1 -0
- package/dist/task-runner.js +1766 -0
- package/dist/task-runner.js.map +1 -0
- package/dist/token-provider.d.ts +50 -0
- package/dist/token-provider.d.ts.map +1 -0
- package/dist/token-provider.js +177 -0
- package/dist/token-provider.js.map +1 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/version-check.d.ts +17 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +56 -0
- package/dist/version-check.js.map +1 -0
- package/dist/watch.d.ts +296 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +1570 -0
- package/dist/watch.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export declare function workDir(): string;
|
|
2
|
+
export interface PreparedWorktree {
|
|
3
|
+
path: string;
|
|
4
|
+
/**
|
|
5
|
+
* v0.12-RESUME — true when a pre-existing registered worktree on
|
|
6
|
+
* the expected branch was reused instead of force-recreated. The
|
|
7
|
+
* caller logs this so an operator can audit how often the resume
|
|
8
|
+
* path actually fires vs. force-recreate (which is the path taken
|
|
9
|
+
* on every fresh task and on every stale/mismatched leftover).
|
|
10
|
+
* Optional so v0.11-F-era test stubs that omit the flag remain
|
|
11
|
+
* valid; absent or false both mean "fresh-recreate path".
|
|
12
|
+
*/
|
|
13
|
+
resumed?: boolean;
|
|
14
|
+
cleanup(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export type GitExec = (args: string[], cwd: string) => Promise<void>;
|
|
17
|
+
export type WorktreeInspector = (repoPath: string, taskId: string) => Promise<WorktreeInspection | null>;
|
|
18
|
+
export interface PrepareWorktreeOptions {
|
|
19
|
+
repoPath: string;
|
|
20
|
+
/**
|
|
21
|
+
* v0.41-B: optional mirror clone path used exclusively for `git
|
|
22
|
+
* worktree` sub-commands (add/remove/prune). `git fetch` still runs
|
|
23
|
+
* against `repoPath` (which has credentials + the full ref history),
|
|
24
|
+
* so when this is set the runner reads from the operator's clone and
|
|
25
|
+
* writes worktree registrations against a separate path — sidestepping
|
|
26
|
+
* the virtiofs lock conflicts that arise when another process (e.g.
|
|
27
|
+
* Cowork) holds `repoPath` open. Undefined preserves v0.40 byte-for-
|
|
28
|
+
* byte behaviour: every git op runs against `repoPath`.
|
|
29
|
+
*/
|
|
30
|
+
worktreeRepoPath?: string;
|
|
31
|
+
taskId: string;
|
|
32
|
+
branch: string;
|
|
33
|
+
baseBranch: string;
|
|
34
|
+
git?: GitExec;
|
|
35
|
+
/**
|
|
36
|
+
* v0.12-RESUME — override worktree-state inspection for tests.
|
|
37
|
+
* Defaults to the real `inspectExistingWorktree` which shells out
|
|
38
|
+
* via `execa`. Tests inject a stub so the resume-vs-fresh branch is
|
|
39
|
+
* exercisable without standing up a real git repo on disk.
|
|
40
|
+
*/
|
|
41
|
+
inspect?: WorktreeInspector;
|
|
42
|
+
}
|
|
43
|
+
export declare function worktreePath(taskId: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* v0.12-RESUME — capture the state of `worktreePath(taskId)` so the
|
|
46
|
+
* caller can decide between resume (reuse the dir + branch) and
|
|
47
|
+
* force-recreate (the legacy v0.11-F behaviour). Returns null when
|
|
48
|
+
* the directory does not exist.
|
|
49
|
+
*
|
|
50
|
+
* `dirty` is set when `git status --porcelain` inside the worktree
|
|
51
|
+
* lists at least one entry — meaning the prior (crashed) runner left
|
|
52
|
+
* uncommitted edits Claude can pick up from. `branch` is the result
|
|
53
|
+
* of `git rev-parse --abbrev-ref HEAD` — when it matches the
|
|
54
|
+
* task's expected branch the worktree is a valid resume candidate.
|
|
55
|
+
*
|
|
56
|
+
* Best-effort: failures (corrupt worktree, missing .git/worktrees
|
|
57
|
+
* entry, etc.) return `registered: false, branch: null` so the
|
|
58
|
+
* caller falls back to force-recreate rather than crashing.
|
|
59
|
+
*/
|
|
60
|
+
export interface WorktreeInspection {
|
|
61
|
+
path: string;
|
|
62
|
+
registered: boolean;
|
|
63
|
+
branch: string | null;
|
|
64
|
+
dirty: boolean;
|
|
65
|
+
}
|
|
66
|
+
export declare function inspectExistingWorktree(repoPath: string, taskId: string): Promise<WorktreeInspection | null>;
|
|
67
|
+
export declare function prepareTaskWorktree(opts: PrepareWorktreeOptions): Promise<PreparedWorktree>;
|
|
68
|
+
export interface OrphanWorktree {
|
|
69
|
+
path: string;
|
|
70
|
+
taskId: string;
|
|
71
|
+
}
|
|
72
|
+
/** Doctor helper: dirs under workDir() not registered in `git worktree list`. */
|
|
73
|
+
export declare function listOrphanWorktrees(repoPath: string): Promise<OrphanWorktree[]>;
|
|
74
|
+
//# sourceMappingURL=worktree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/runtime/worktree.ts"],"names":[],"mappings":"AAwCA,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AACrE,MAAM,MAAM,iBAAiB,GAAG,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;AAExC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAMD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAyBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAgDpC;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CA8D3B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,iFAAiF;AACjF,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,EAAE,CAAC,CA+B3B"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-task isolated `git worktree` at `~/.cache/acc-runner/work/<task_id>/`
|
|
3
|
+
* so parallel runners on the same machine don't race on the shared
|
|
4
|
+
* clone's branch checkout. Cleaned up on every task completion path.
|
|
5
|
+
*
|
|
6
|
+
* v0.19-T64 — start-point contract. A FRESH task branch MUST be cut from
|
|
7
|
+
* the LATEST integration tip, not a stale local ref. `git fetch` is driven
|
|
8
|
+
* by the caller (task-runner.ts), which fetches origin and passes the
|
|
9
|
+
* freshly-fetched remote-tracking ref (`origin/<integration>`) as
|
|
10
|
+
* `baseBranch`. T-63-1 shipped a PR that conflicted against the real
|
|
11
|
+
* integration because a 13-commit-stale LOCAL ref was used as the base;
|
|
12
|
+
* forking from `origin/<integration>` is the fix. On a caller-side fetch
|
|
13
|
+
* failure the caller falls back to the local ref and emits
|
|
14
|
+
* `runner.stale_base_fallback` — the worktree still starts from whatever
|
|
15
|
+
* `baseBranch` it is handed, so this module never aborts for staleness.
|
|
16
|
+
*
|
|
17
|
+
* v0.12-RESUME — when a prior runner crashed mid-task and the
|
|
18
|
+
* v0.12 stale-running sweep returned the task to queued, a fresh
|
|
19
|
+
* runner that picks the same task back up will find a leftover
|
|
20
|
+
* worktree directory + git registration. The pre-v0.12 behaviour
|
|
21
|
+
* was to force-remove and recreate, throwing away the dead
|
|
22
|
+
* runner's in-progress edits. We now detect this case
|
|
23
|
+
* (`inspectExistingWorktree`), preserve the directory when it is
|
|
24
|
+
* still a valid registration on the expected task branch, and
|
|
25
|
+
* mark the returned `PreparedWorktree` as `resumed: true` so the
|
|
26
|
+
* task-runner can log the resume and Claude picks up from the
|
|
27
|
+
* partially-committed state instead of restarting from baseBranch.
|
|
28
|
+
*/
|
|
29
|
+
import fs from "node:fs/promises";
|
|
30
|
+
import path from "node:path";
|
|
31
|
+
import { execa } from "execa";
|
|
32
|
+
import { buildEnv } from "../bin-resolve.js";
|
|
33
|
+
import { cacheDir } from "./cache-dir.js";
|
|
34
|
+
// Lazy so tests can override ACC_RUNNER_CACHE_DIR / HOME between cases.
|
|
35
|
+
//
|
|
36
|
+
// v1.00-B: resolves through `cacheDir()` (honours ACC_RUNNER_CACHE_DIR)
|
|
37
|
+
// rather than a hardcoded ~/.cache path, so two ephemeral runners with
|
|
38
|
+
// distinct cache dirs get isolated `work/` trees and never race on each
|
|
39
|
+
// other's per-task worktrees.
|
|
40
|
+
export function workDir() {
|
|
41
|
+
return path.join(cacheDir(), "work");
|
|
42
|
+
}
|
|
43
|
+
function defaultGitExec(args, cwd) {
|
|
44
|
+
return execa("git", args, { cwd, env: buildEnv() }).then(() => undefined);
|
|
45
|
+
}
|
|
46
|
+
export function worktreePath(taskId) {
|
|
47
|
+
return path.join(workDir(), taskId);
|
|
48
|
+
}
|
|
49
|
+
async function pathExists(p) {
|
|
50
|
+
try {
|
|
51
|
+
await fs.access(p);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function removeWorktree(repoPath, target, git) {
|
|
59
|
+
if (await pathExists(target)) {
|
|
60
|
+
// Best-effort: deregister, then nuke residue. Partial cleanup
|
|
61
|
+
// shouldn't fail the surrounding task lifecycle.
|
|
62
|
+
try {
|
|
63
|
+
await git(["worktree", "remove", "--force", target], repoPath);
|
|
64
|
+
}
|
|
65
|
+
catch { /* noop */ }
|
|
66
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
await git(["worktree", "prune"], repoPath);
|
|
70
|
+
}
|
|
71
|
+
catch { /* noop */ }
|
|
72
|
+
}
|
|
73
|
+
export async function inspectExistingWorktree(repoPath, taskId) {
|
|
74
|
+
const target = worktreePath(taskId);
|
|
75
|
+
if (!(await pathExists(target)))
|
|
76
|
+
return null;
|
|
77
|
+
// `git worktree list --porcelain` runs from the *parent* clone so
|
|
78
|
+
// the registration check sees the canonical list. A target dir
|
|
79
|
+
// that exists on disk but is absent here is an orphan — the
|
|
80
|
+
// caller will treat it as not-registered and force-recreate.
|
|
81
|
+
let registered = false;
|
|
82
|
+
try {
|
|
83
|
+
const { stdout } = await execa("git", ["worktree", "list", "--porcelain"], { cwd: repoPath, env: buildEnv() });
|
|
84
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
85
|
+
const m = /^worktree (.+)$/.exec(line);
|
|
86
|
+
if (m && path.resolve(m[1]) === path.resolve(target)) {
|
|
87
|
+
registered = true;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
registered = false;
|
|
94
|
+
}
|
|
95
|
+
let branch = null;
|
|
96
|
+
try {
|
|
97
|
+
const { stdout } = await execa("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: target, env: buildEnv() });
|
|
98
|
+
branch = stdout.trim() || null;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
branch = null;
|
|
102
|
+
}
|
|
103
|
+
let dirty = false;
|
|
104
|
+
try {
|
|
105
|
+
const { stdout } = await execa("git", ["status", "--porcelain"], { cwd: target, env: buildEnv() });
|
|
106
|
+
dirty = stdout.trim().length > 0;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
dirty = false;
|
|
110
|
+
}
|
|
111
|
+
return { path: target, registered, branch, dirty };
|
|
112
|
+
}
|
|
113
|
+
export async function prepareTaskWorktree(opts) {
|
|
114
|
+
const git = opts.git ?? defaultGitExec;
|
|
115
|
+
await fs.mkdir(workDir(), { recursive: true });
|
|
116
|
+
const target = worktreePath(opts.taskId);
|
|
117
|
+
// v0.41-B: every `git worktree` sub-command runs against this base,
|
|
118
|
+
// which is the mirror clone when configured and the operator's
|
|
119
|
+
// working clone otherwise. `git fetch` (driven by the caller) stays
|
|
120
|
+
// on opts.repoPath so credentials and ref history come from the
|
|
121
|
+
// working clone; only worktree registration/removal/prune relocate.
|
|
122
|
+
const worktreeBase = opts.worktreeRepoPath ?? opts.repoPath;
|
|
123
|
+
// v0.12-RESUME — if a leftover worktree is registered on the
|
|
124
|
+
// expected branch, preserve it so Claude can pick up where the
|
|
125
|
+
// dead runner left off. Otherwise (orphan dir, wrong branch,
|
|
126
|
+
// corrupt state) fall through to the v0.11-F force-recreate
|
|
127
|
+
// path so the new task starts from a known baseBranch tip.
|
|
128
|
+
//
|
|
129
|
+
// We only enter the resume path when the inspector reports
|
|
130
|
+
// registered=true on the expected branch. The real inspector
|
|
131
|
+
// shells to `git worktree list --porcelain`; tests inject a stub.
|
|
132
|
+
// When `opts.repoPath` is not a real git checkout (test fixtures
|
|
133
|
+
// like "/repo"), the default inspector swallows the `git` error
|
|
134
|
+
// and returns registered=false, preserving v0.11-F's force-
|
|
135
|
+
// recreate semantics for the existing test suite.
|
|
136
|
+
const inspect = opts.inspect ?? inspectExistingWorktree;
|
|
137
|
+
const existing = await inspect(worktreeBase, opts.taskId);
|
|
138
|
+
if (existing && existing.registered && existing.branch === opts.branch) {
|
|
139
|
+
let cleaned = false;
|
|
140
|
+
return {
|
|
141
|
+
path: target,
|
|
142
|
+
resumed: true,
|
|
143
|
+
async cleanup() {
|
|
144
|
+
if (cleaned)
|
|
145
|
+
return;
|
|
146
|
+
cleaned = true;
|
|
147
|
+
await removeWorktree(worktreeBase, target, git);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
await removeWorktree(worktreeBase, target, git);
|
|
152
|
+
// `-B <branch> <path> <baseBranch>` force-creates the task branch at
|
|
153
|
+
// baseBranch's tip, overwriting a stale ref from a prior crashed run.
|
|
154
|
+
// v0.19 T-64-1: for a FRESH task the caller passes the freshly-fetched
|
|
155
|
+
// origin tip (`origin/<integration>`) as baseBranch — NOT a stale local
|
|
156
|
+
// ref — so the branch starts at the latest integration HEAD. The resume
|
|
157
|
+
// path above returns before reaching here, so a resumed partial branch
|
|
158
|
+
// is never reset to this base.
|
|
159
|
+
await git(["worktree", "add", "-B", opts.branch, target, opts.baseBranch], worktreeBase);
|
|
160
|
+
let cleaned = false;
|
|
161
|
+
return {
|
|
162
|
+
path: target,
|
|
163
|
+
resumed: false,
|
|
164
|
+
async cleanup() {
|
|
165
|
+
if (cleaned)
|
|
166
|
+
return;
|
|
167
|
+
cleaned = true;
|
|
168
|
+
await removeWorktree(worktreeBase, target, git);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Doctor helper: dirs under workDir() not registered in `git worktree list`. */
|
|
173
|
+
export async function listOrphanWorktrees(repoPath) {
|
|
174
|
+
let entries;
|
|
175
|
+
try {
|
|
176
|
+
entries = await fs.readdir(workDir());
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
if (err.code === "ENOENT")
|
|
180
|
+
return [];
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
const registered = new Set();
|
|
184
|
+
try {
|
|
185
|
+
const { stdout } = await execa("git", ["worktree", "list", "--porcelain"], { cwd: repoPath, env: buildEnv() });
|
|
186
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
187
|
+
const m = /^worktree (.+)$/.exec(line);
|
|
188
|
+
if (m)
|
|
189
|
+
registered.add(path.resolve(m[1]));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Without a usable repo we can't distinguish; surface everything.
|
|
194
|
+
}
|
|
195
|
+
const orphans = [];
|
|
196
|
+
for (const name of entries) {
|
|
197
|
+
const p = path.join(workDir(), name);
|
|
198
|
+
try {
|
|
199
|
+
const stat = await fs.stat(p);
|
|
200
|
+
if (!stat.isDirectory())
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (!registered.has(p))
|
|
207
|
+
orphans.push({ path: p, taskId: name });
|
|
208
|
+
}
|
|
209
|
+
return orphans;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=worktree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/runtime/worktree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runner-side `{{secret:NAME}}` placeholder expander.
|
|
3
|
+
*
|
|
4
|
+
* The runner never holds the service-role key (which would let it pull
|
|
5
|
+
* arbitrary plaintext from the vault). Instead, name→id resolution and
|
|
6
|
+
* decryption run server-side behind the user's JWT, and the runner POSTs
|
|
7
|
+
* to /api/runner/expand-secret for each unique name encountered in a
|
|
8
|
+
* tool-call argument tree.
|
|
9
|
+
*
|
|
10
|
+
* The plaintext flow is:
|
|
11
|
+
*
|
|
12
|
+
* 1. Runner extracts `{{secret:NAME}}` placeholders from the args.
|
|
13
|
+
* 2. For each unique NAME, runner POSTs `/api/runner/expand-secret`
|
|
14
|
+
* with `{ name, agent_id, tool_name }`. The endpoint uses
|
|
15
|
+
* `getAdminClient()` to call acc.resolve_secret_id_by_name (v0.7-D)
|
|
16
|
+
* then acc.fetch_secret_for_runtime (v0.7-B, frozen).
|
|
17
|
+
* 3. Plaintext is held in memory ONLY for the duration of the tool
|
|
18
|
+
* call. We never log it, never serialize it, never copy it to the
|
|
19
|
+
* DB. The pre-expansion args (still containing the literal
|
|
20
|
+
* `{{secret:…}}`) are what gets logged to telemetry.
|
|
21
|
+
* 4. Caller hands the expanded result to MCP stdio.
|
|
22
|
+
*
|
|
23
|
+
* Telemetry tripwire test: a fake secret named `TRIPWIRE_VALUE` with
|
|
24
|
+
* plaintext `tripwire-do-not-log-me` must NOT appear in any captured
|
|
25
|
+
* telemetry row. See packages/acc-runner/tests/expand-args.test.ts.
|
|
26
|
+
*
|
|
27
|
+
* Deviations from planning/v0.7-D-chat-encryption-and-visibility.md:
|
|
28
|
+
* - The planning doc imagined `import { supabaseAdmin } from '../db'`
|
|
29
|
+
* — a service-role client at the runner. That doesn't exist (and
|
|
30
|
+
* shouldn't; the runner is end-user-deployed). The /api/runner/
|
|
31
|
+
* expand-secret endpoint is the actual seam.
|
|
32
|
+
* - The planning doc's single fetch_secret_for_runtime call is split
|
|
33
|
+
* into resolve_secret_id_by_name → fetch_secret_for_runtime because
|
|
34
|
+
* v0.7-B's frozen contract takes a uuid, not a name.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SECRET_PATTERN: RegExp;
|
|
37
|
+
/**
|
|
38
|
+
* Fetcher signature. Production wires this to an HTTP call against
|
|
39
|
+
* /api/runner/expand-secret; tests pass a stub.
|
|
40
|
+
*
|
|
41
|
+
* MUST throw on any failure (missing secret, locked vault, denied
|
|
42
|
+
* grant, rate-limit). MUST NOT include any secret-derived data in the
|
|
43
|
+
* error message.
|
|
44
|
+
*/
|
|
45
|
+
export type SecretFetcher = (name: string, agentId: string, toolName?: string) => Promise<string>;
|
|
46
|
+
export interface ExpandResult {
|
|
47
|
+
expanded: string;
|
|
48
|
+
consumedSecrets: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Expand `{{secret:NAME}}` placeholders in a single string. Returns the
|
|
52
|
+
* expanded string plus the list of secret names consumed (for telemetry
|
|
53
|
+
* of NAMES, never plaintexts). Throws if any required secret is
|
|
54
|
+
* unavailable — partial expansion is never returned (atomic).
|
|
55
|
+
*/
|
|
56
|
+
export declare function expandSecretsInString(raw: string, agentId: string, fetcher: SecretFetcher, toolName?: string): Promise<ExpandResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Build an HTTP-backed SecretFetcher pointing at the deployed
|
|
59
|
+
* /api/runner/expand-secret endpoint. The runner wires this at startup
|
|
60
|
+
* once it has its user JWT and config.
|
|
61
|
+
*
|
|
62
|
+
* The endpoint MUST NOT echo the plaintext into any error path; this
|
|
63
|
+
* helper trusts that contract.
|
|
64
|
+
*/
|
|
65
|
+
export declare function makeHttpSecretFetcher(opts: {
|
|
66
|
+
publicUrl: string;
|
|
67
|
+
jwt: string;
|
|
68
|
+
fetchImpl?: typeof fetch;
|
|
69
|
+
}): SecretFetcher;
|
|
70
|
+
//# sourceMappingURL=inject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../../src/secrets/inject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,eAAO,MAAM,cAAc,QAAsC,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAElG,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,CA4BvB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,aAAa,CAwBhB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runner-side `{{secret:NAME}}` placeholder expander.
|
|
3
|
+
*
|
|
4
|
+
* The runner never holds the service-role key (which would let it pull
|
|
5
|
+
* arbitrary plaintext from the vault). Instead, name→id resolution and
|
|
6
|
+
* decryption run server-side behind the user's JWT, and the runner POSTs
|
|
7
|
+
* to /api/runner/expand-secret for each unique name encountered in a
|
|
8
|
+
* tool-call argument tree.
|
|
9
|
+
*
|
|
10
|
+
* The plaintext flow is:
|
|
11
|
+
*
|
|
12
|
+
* 1. Runner extracts `{{secret:NAME}}` placeholders from the args.
|
|
13
|
+
* 2. For each unique NAME, runner POSTs `/api/runner/expand-secret`
|
|
14
|
+
* with `{ name, agent_id, tool_name }`. The endpoint uses
|
|
15
|
+
* `getAdminClient()` to call acc.resolve_secret_id_by_name (v0.7-D)
|
|
16
|
+
* then acc.fetch_secret_for_runtime (v0.7-B, frozen).
|
|
17
|
+
* 3. Plaintext is held in memory ONLY for the duration of the tool
|
|
18
|
+
* call. We never log it, never serialize it, never copy it to the
|
|
19
|
+
* DB. The pre-expansion args (still containing the literal
|
|
20
|
+
* `{{secret:…}}`) are what gets logged to telemetry.
|
|
21
|
+
* 4. Caller hands the expanded result to MCP stdio.
|
|
22
|
+
*
|
|
23
|
+
* Telemetry tripwire test: a fake secret named `TRIPWIRE_VALUE` with
|
|
24
|
+
* plaintext `tripwire-do-not-log-me` must NOT appear in any captured
|
|
25
|
+
* telemetry row. See packages/acc-runner/tests/expand-args.test.ts.
|
|
26
|
+
*
|
|
27
|
+
* Deviations from planning/v0.7-D-chat-encryption-and-visibility.md:
|
|
28
|
+
* - The planning doc imagined `import { supabaseAdmin } from '../db'`
|
|
29
|
+
* — a service-role client at the runner. That doesn't exist (and
|
|
30
|
+
* shouldn't; the runner is end-user-deployed). The /api/runner/
|
|
31
|
+
* expand-secret endpoint is the actual seam.
|
|
32
|
+
* - The planning doc's single fetch_secret_for_runtime call is split
|
|
33
|
+
* into resolve_secret_id_by_name → fetch_secret_for_runtime because
|
|
34
|
+
* v0.7-B's frozen contract takes a uuid, not a name.
|
|
35
|
+
*/
|
|
36
|
+
export const SECRET_PATTERN = /\{\{secret:([A-Z][A-Z0-9_]*)\}\}/g;
|
|
37
|
+
/**
|
|
38
|
+
* Expand `{{secret:NAME}}` placeholders in a single string. Returns the
|
|
39
|
+
* expanded string plus the list of secret names consumed (for telemetry
|
|
40
|
+
* of NAMES, never plaintexts). Throws if any required secret is
|
|
41
|
+
* unavailable — partial expansion is never returned (atomic).
|
|
42
|
+
*/
|
|
43
|
+
export async function expandSecretsInString(raw, agentId, fetcher, toolName) {
|
|
44
|
+
const matches = [...raw.matchAll(SECRET_PATTERN)];
|
|
45
|
+
if (matches.length === 0) {
|
|
46
|
+
return { expanded: raw, consumedSecrets: [] };
|
|
47
|
+
}
|
|
48
|
+
const names = [...new Set(matches.map((m) => m[1]))];
|
|
49
|
+
const plaintexts = new Map();
|
|
50
|
+
for (const name of names) {
|
|
51
|
+
try {
|
|
52
|
+
const plaintext = await fetcher(name, agentId, toolName);
|
|
53
|
+
plaintexts.set(name, plaintext);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
throw new Error(`secret_unavailable: ${name}: ${message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const expanded = raw.replace(SECRET_PATTERN, (_match, name) => {
|
|
61
|
+
const value = plaintexts.get(name);
|
|
62
|
+
if (value === undefined) {
|
|
63
|
+
throw new Error(`secret_unavailable: ${name}: missing after fetch`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
});
|
|
67
|
+
return { expanded, consumedSecrets: names };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build an HTTP-backed SecretFetcher pointing at the deployed
|
|
71
|
+
* /api/runner/expand-secret endpoint. The runner wires this at startup
|
|
72
|
+
* once it has its user JWT and config.
|
|
73
|
+
*
|
|
74
|
+
* The endpoint MUST NOT echo the plaintext into any error path; this
|
|
75
|
+
* helper trusts that contract.
|
|
76
|
+
*/
|
|
77
|
+
export function makeHttpSecretFetcher(opts) {
|
|
78
|
+
const f = opts.fetchImpl ?? fetch;
|
|
79
|
+
const endpoint = `${opts.publicUrl.replace(/\/$/, "")}/api/runner/expand-secret`;
|
|
80
|
+
return async (name, agentId, toolName) => {
|
|
81
|
+
const res = await f(endpoint, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
Authorization: `Bearer ${opts.jwt}`,
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({ name, agent_id: agentId, tool_name: toolName ?? null }),
|
|
88
|
+
});
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
// Read body for the error reason, but the endpoint contract is
|
|
91
|
+
// that the body NEVER contains plaintext.
|
|
92
|
+
const text = await res.text().catch(() => "");
|
|
93
|
+
throw new Error(`expand-secret HTTP ${res.status}: ${text || res.statusText}`);
|
|
94
|
+
}
|
|
95
|
+
const data = (await res.json());
|
|
96
|
+
if (typeof data.plaintext !== "string") {
|
|
97
|
+
throw new Error("expand-secret response missing plaintext");
|
|
98
|
+
}
|
|
99
|
+
return data.plaintext;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=inject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject.js","sourceRoot":"","sources":["../../src/secrets/inject.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RunnerConfig } from "./config.js";
|
|
2
|
+
export declare function createRunnerClient(cfg: RunnerConfig, jwt: string): import("@supabase/supabase-js").SupabaseClient<any, "public", "acc", any, any>;
|
|
3
|
+
export type RunnerSupabaseClient = ReturnType<typeof createRunnerClient>;
|
|
4
|
+
//# sourceMappingURL=supabase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../src/supabase.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,kFAoBhE;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
package/dist/supabase.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase client factory for the CLI. Authenticates with the user's
|
|
3
|
+
* stored JWT (loaded from keychain) so RLS / acc.is_member() resolve
|
|
4
|
+
* correctly on every RPC.
|
|
5
|
+
*
|
|
6
|
+
* Realtime transport is pinned to the `ws` package. Node 20+ has a
|
|
7
|
+
* native WebSocket global, but Supabase's runtime detector still warns
|
|
8
|
+
* "Node.js 20 detected without native WebSocket support" because it
|
|
9
|
+
* looks for a Node-API-shaped constructor, not the WHATWG one. Passing
|
|
10
|
+
* `ws` explicitly silences the warning and is the same transport every
|
|
11
|
+
* server-side Supabase consumer uses.
|
|
12
|
+
*/
|
|
13
|
+
import { createClient } from "@supabase/supabase-js";
|
|
14
|
+
import WebSocket from "ws";
|
|
15
|
+
export function createRunnerClient(cfg, jwt) {
|
|
16
|
+
return createClient(cfg.supabaseUrl, cfg.supabaseAnonKey, {
|
|
17
|
+
db: { schema: "acc" },
|
|
18
|
+
auth: {
|
|
19
|
+
persistSession: false,
|
|
20
|
+
autoRefreshToken: false,
|
|
21
|
+
detectSessionInUrl: false,
|
|
22
|
+
},
|
|
23
|
+
global: {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${jwt}`,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
realtime: {
|
|
29
|
+
// any-allowed: ws's type matches Node, not the browser WebSocket
|
|
30
|
+
// interface Supabase's option signature is declared against.
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
transport: WebSocket,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=supabase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase.js","sourceRoot":"","sources":["../src/supabase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,SAAS,MAAM,IAAI,CAAC;AAG3B,MAAM,UAAU,kBAAkB,CAAC,GAAiB,EAAE,GAAW;IAC/D,OAAO,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,eAAe,EAAE;QACxD,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QACrB,IAAI,EAAE;YACJ,cAAc,EAAE,KAAK;YACrB,gBAAgB,EAAE,KAAK;YACvB,kBAAkB,EAAE,KAAK;SAC1B;QACD,MAAM,EAAE;YACN,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,GAAG,EAAE;aAC/B;SACF;QACD,QAAQ,EAAE;YACR,iEAAiE;YACjE,6DAA6D;YAC7D,8DAA8D;YAC9D,SAAS,EAAE,SAAgB;SAC5B;KACF,CAAC,CAAC;AACL,CAAC"}
|