@phnx-labs/agents-cli 1.20.34 → 1.20.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/LICENSE +185 -21
- package/README.md +13 -5
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.js +81 -0
- package/dist/commands/cost.js +17 -12
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -97
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +283 -12
- package/dist/commands/hosts.js +37 -29
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -16
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +196 -91
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +221 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +143 -1
- package/dist/commands/sessions.js +628 -80
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +82 -32
- package/dist/lib/agents.js +30 -9
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +77 -16
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +37 -12
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +183 -23
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +92 -0
- package/dist/lib/daemon.js +222 -18
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +10 -3
- package/dist/lib/events.js +101 -24
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +100 -12
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +135 -10
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +45 -13
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +74 -0
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +51 -7
- package/dist/lib/hosts/ready.js +98 -25
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +22 -0
- package/dist/lib/hosts/tasks.js +29 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +26 -0
- package/dist/lib/picker.js +127 -0
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +61 -10
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +61 -0
- package/dist/lib/session/active.js +196 -17
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +204 -27
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +286 -2
- package/dist/lib/session/pid-registry.d.ts +22 -0
- package/dist/lib/session/pid-registry.js +104 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +153 -0
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +213 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +18 -8
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +1 -7
- package/dist/lib/session/sync/config.js +4 -11
- package/dist/lib/session/types.d.ts +18 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +44 -6
- package/dist/lib/shims.js +143 -57
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +47 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +62 -63
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +8 -0
- package/dist/lib/startup/command-registry.js +17 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +17 -9
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +19 -0
- package/dist/lib/terminal/backends/index.js +31 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +17 -0
- package/dist/lib/terminal/index.js +8 -0
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +30 -3
- package/dist/lib/versions.js +129 -18
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { AgentManager, AgentProcess, EffortLevel } from './agents.js';
|
|
2
|
+
import { type ForEachSpec, type ForEachTeammate } from '../workflows.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a producer's stdout into the item list `expandForEach` fans out over.
|
|
5
|
+
*
|
|
6
|
+
* Two shapes are accepted (issue #343), tried in order:
|
|
7
|
+
* 1. A JSON array — `["a","b","c"]` (each element coerced to a trimmed string).
|
|
8
|
+
* 2. Newline-delimited — one item per line.
|
|
9
|
+
*
|
|
10
|
+
* Empty lines / entries are dropped so a trailing newline or a `[]` never
|
|
11
|
+
* fabricates a phantom teammate. Pure and deterministic — no I/O.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseProducedItems(stdout: string): string[];
|
|
14
|
+
export interface ProduceItemsOptions {
|
|
15
|
+
/** Working directory for the producer command. */
|
|
16
|
+
cwd?: string | null;
|
|
17
|
+
/** Environment for the producer command (defaults to the current process env). */
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
/** Kill the producer after this many ms (default 120_000). */
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve an `itemsRef` (`${step}`-style reference) to a prior step's produced
|
|
23
|
+
* list. When a spec carries `itemsRef` and this resolver returns a list, the
|
|
24
|
+
* producer command is skipped entirely.
|
|
25
|
+
*/
|
|
26
|
+
resolveItemsRef?: (ref: string) => string[] | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a `for_each` spec's item list at runtime (issue #343): either by
|
|
30
|
+
* running its `produce:` shell command and parsing stdout, or by resolving an
|
|
31
|
+
* `itemsRef` to a prior step's list. The resulting items feed `expandForEach` /
|
|
32
|
+
* `runForEach`.
|
|
33
|
+
*
|
|
34
|
+
* `itemsRef` wins when a resolver is supplied and returns a list; otherwise the
|
|
35
|
+
* `produce` command runs. A spec with neither a resolvable ref nor a produce
|
|
36
|
+
* command is a hard error — there is nothing to fan out over.
|
|
37
|
+
*/
|
|
38
|
+
export declare function produceItems(spec: ForEachSpec, opts?: ProduceItemsOptions): Promise<string[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Evaluate a verify panel's `keep_if` gate against its boolean votes (issue
|
|
41
|
+
* #343). A `true` vote is a skeptic confirming the finding should be kept.
|
|
42
|
+
*
|
|
43
|
+
* - `all` — every skeptic must vote keep.
|
|
44
|
+
* - `any` — at least one skeptic votes keep.
|
|
45
|
+
* - `majority` — strictly more than half vote keep (a tie does NOT pass).
|
|
46
|
+
*
|
|
47
|
+
* An empty panel returns false: with no votes there is nothing affirming the
|
|
48
|
+
* item, so the conservative gate drops it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function evaluateKeepIf(votes: boolean[], keepIf: 'majority' | 'all' | 'any'): boolean;
|
|
51
|
+
/** Per-item verdict after tallying its verify panel. */
|
|
52
|
+
export interface ForEachItemVerdict {
|
|
53
|
+
/** The produced item this verdict is for. */
|
|
54
|
+
item: string;
|
|
55
|
+
/** Zero-based index in the (capped) produced list. */
|
|
56
|
+
itemIndex: number;
|
|
57
|
+
/** The stage teammate that handled this item. */
|
|
58
|
+
stageName: string;
|
|
59
|
+
/** Whether the item survives its `keep_if` gate (true when it has no panel). */
|
|
60
|
+
kept: boolean;
|
|
61
|
+
/** The votes that were tallied (empty when the item has no verify panel). */
|
|
62
|
+
votes: boolean[];
|
|
63
|
+
/** The gate applied (undefined when the item has no verify panel). */
|
|
64
|
+
keepIf?: 'majority' | 'all' | 'any';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Tally the verify panels of an expanded `for_each` and gate each item (issue
|
|
68
|
+
* #343). Groups verify teammates by the stage teammate they depend on, reads a
|
|
69
|
+
* boolean vote per verify teammate via `readVote`, and applies `keep_if`.
|
|
70
|
+
*
|
|
71
|
+
* An item with no verify panel is kept unconditionally (there is no gate).
|
|
72
|
+
* Pure and deterministic: `readVote` is the only place runtime state enters, so
|
|
73
|
+
* this is unit-testable with a synthetic vote reader.
|
|
74
|
+
*/
|
|
75
|
+
export declare function tallyForEach(teammates: ForEachTeammate[], readVote: (verify: ForEachTeammate) => boolean): ForEachItemVerdict[];
|
|
76
|
+
export interface RunForEachOptions {
|
|
77
|
+
/** Working directory for the spawned teammates. */
|
|
78
|
+
cwd?: string | null;
|
|
79
|
+
/**
|
|
80
|
+
* Name of the producer teammate the stage teammates should depend on. When
|
|
81
|
+
* set, each stage runs `--after` the producer so it can't start before the
|
|
82
|
+
* list is available.
|
|
83
|
+
*/
|
|
84
|
+
producerName?: string;
|
|
85
|
+
/** Default effort for spawned teammates (per-item overrides live in the spec). */
|
|
86
|
+
effort?: EffortLevel;
|
|
87
|
+
/** Concurrency cap for the wave; falls back to the spec's `concurrency`. */
|
|
88
|
+
concurrency?: number;
|
|
89
|
+
}
|
|
90
|
+
export interface RunForEachResult {
|
|
91
|
+
/** The expanded descriptors (stage + verify), in spawn order. */
|
|
92
|
+
teammates: ForEachTeammate[];
|
|
93
|
+
/** The AgentProcess handles returned by `spawn`, aligned to `teammates`. */
|
|
94
|
+
spawned: AgentProcess[];
|
|
95
|
+
/** Items the producer emitted (pre-cap). */
|
|
96
|
+
producedCount: number;
|
|
97
|
+
/** Items actually fanned out (post-cap). */
|
|
98
|
+
usedCount: number;
|
|
99
|
+
/** How many items the runaway guard dropped. */
|
|
100
|
+
truncated: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Expand a `for_each` spec against a producer's output and stage every
|
|
104
|
+
* resulting teammate into the given team via the dynamic-add path.
|
|
105
|
+
*
|
|
106
|
+
* Returns the descriptors and the spawned handles so a caller can drive the
|
|
107
|
+
* supervisor and later gather results (e.g. to evaluate a `verify` panel's
|
|
108
|
+
* `keep_if` gate — that vote-counting lives downstream, not here).
|
|
109
|
+
*/
|
|
110
|
+
export declare function runForEach(mgr: AgentManager, teamName: string, spec: ForEachSpec, items: string[], opts?: RunForEachOptions): Promise<RunForEachResult>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime wiring for declarative dynamic fan-out (issue #343).
|
|
3
|
+
*
|
|
4
|
+
* The declarative shape (`for_each:` in WORKFLOW.md / routine `run:` blocks) is
|
|
5
|
+
* parsed and expanded by `src/lib/workflows.ts` (`parseForEachBlock`,
|
|
6
|
+
* `expandForEach`). This module is the thin bridge that stages the expanded
|
|
7
|
+
* teammate descriptors into the EXISTING teams substrate — one
|
|
8
|
+
* `AgentManager.spawn` per descriptor, which the DAG supervisor then picks up
|
|
9
|
+
* mid-flight via `rescanFromDisk` / `startReady`
|
|
10
|
+
* (`src/lib/teams/supervisor.ts:98-103`).
|
|
11
|
+
*
|
|
12
|
+
* It deliberately adds NO new orchestration engine: `spawn` with an `--after`
|
|
13
|
+
* chain is the whole mechanism, so cycle detection, wave scheduling, and
|
|
14
|
+
* cross-vendor/rotation dispatch all carry over unchanged.
|
|
15
|
+
*/
|
|
16
|
+
import { execFile } from 'node:child_process';
|
|
17
|
+
import { promisify } from 'node:util';
|
|
18
|
+
import { posixShellPath } from '../platform/exec.js';
|
|
19
|
+
import { expandForEach } from '../workflows.js';
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
/**
|
|
22
|
+
* Parse a producer's stdout into the item list `expandForEach` fans out over.
|
|
23
|
+
*
|
|
24
|
+
* Two shapes are accepted (issue #343), tried in order:
|
|
25
|
+
* 1. A JSON array — `["a","b","c"]` (each element coerced to a trimmed string).
|
|
26
|
+
* 2. Newline-delimited — one item per line.
|
|
27
|
+
*
|
|
28
|
+
* Empty lines / entries are dropped so a trailing newline or a `[]` never
|
|
29
|
+
* fabricates a phantom teammate. Pure and deterministic — no I/O.
|
|
30
|
+
*/
|
|
31
|
+
export function parseProducedItems(stdout) {
|
|
32
|
+
const trimmed = stdout.trim();
|
|
33
|
+
if (trimmed === '')
|
|
34
|
+
return [];
|
|
35
|
+
// JSON array form first — only when it actually looks like one, so a plain
|
|
36
|
+
// line that happens to start with '[' doesn't get swallowed by a parse error.
|
|
37
|
+
if (trimmed.startsWith('[')) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(trimmed);
|
|
40
|
+
if (Array.isArray(parsed)) {
|
|
41
|
+
return parsed
|
|
42
|
+
.map((x) => (x == null ? '' : typeof x === 'string' ? x : String(x)))
|
|
43
|
+
.map((s) => s.trim())
|
|
44
|
+
.filter((s) => s.length > 0);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Not valid JSON — fall through to newline parsing.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Newline-delimited form.
|
|
52
|
+
return trimmed
|
|
53
|
+
.split(/\r?\n/)
|
|
54
|
+
.map((s) => s.trim())
|
|
55
|
+
.filter((s) => s.length > 0);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a `for_each` spec's item list at runtime (issue #343): either by
|
|
59
|
+
* running its `produce:` shell command and parsing stdout, or by resolving an
|
|
60
|
+
* `itemsRef` to a prior step's list. The resulting items feed `expandForEach` /
|
|
61
|
+
* `runForEach`.
|
|
62
|
+
*
|
|
63
|
+
* `itemsRef` wins when a resolver is supplied and returns a list; otherwise the
|
|
64
|
+
* `produce` command runs. A spec with neither a resolvable ref nor a produce
|
|
65
|
+
* command is a hard error — there is nothing to fan out over.
|
|
66
|
+
*/
|
|
67
|
+
export async function produceItems(spec, opts = {}) {
|
|
68
|
+
if (spec.itemsRef && opts.resolveItemsRef) {
|
|
69
|
+
const resolved = opts.resolveItemsRef(spec.itemsRef);
|
|
70
|
+
if (resolved)
|
|
71
|
+
return resolved;
|
|
72
|
+
}
|
|
73
|
+
if (!spec.produce) {
|
|
74
|
+
if (spec.itemsRef) {
|
|
75
|
+
throw new Error(`for_each itemsRef '${spec.itemsRef}' could not be resolved and no produce command is set`);
|
|
76
|
+
}
|
|
77
|
+
throw new Error('for_each has neither a produce command nor a resolvable itemsRef');
|
|
78
|
+
}
|
|
79
|
+
// The producer is a shell command string (e.g. "rg -l 'x' src/"), so it runs
|
|
80
|
+
// under `sh -c` exactly like a teammate command — resolved portably, because
|
|
81
|
+
// /bin/sh does not exist on Windows (there it's Git's sh.exe from PATH).
|
|
82
|
+
// maxBuffer is raised well above the default 1MB so a producer emitting a
|
|
83
|
+
// large list isn't truncated silently mid-stream.
|
|
84
|
+
const { stdout } = await execFileAsync(posixShellPath(), ['-c', spec.produce], {
|
|
85
|
+
cwd: opts.cwd ?? undefined,
|
|
86
|
+
env: opts.env ?? process.env,
|
|
87
|
+
timeout: opts.timeoutMs ?? 120_000,
|
|
88
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
89
|
+
encoding: 'utf-8',
|
|
90
|
+
});
|
|
91
|
+
return parseProducedItems(stdout);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Evaluate a verify panel's `keep_if` gate against its boolean votes (issue
|
|
95
|
+
* #343). A `true` vote is a skeptic confirming the finding should be kept.
|
|
96
|
+
*
|
|
97
|
+
* - `all` — every skeptic must vote keep.
|
|
98
|
+
* - `any` — at least one skeptic votes keep.
|
|
99
|
+
* - `majority` — strictly more than half vote keep (a tie does NOT pass).
|
|
100
|
+
*
|
|
101
|
+
* An empty panel returns false: with no votes there is nothing affirming the
|
|
102
|
+
* item, so the conservative gate drops it.
|
|
103
|
+
*/
|
|
104
|
+
export function evaluateKeepIf(votes, keepIf) {
|
|
105
|
+
const total = votes.length;
|
|
106
|
+
if (total === 0)
|
|
107
|
+
return false;
|
|
108
|
+
const yes = votes.reduce((n, v) => (v ? n + 1 : n), 0);
|
|
109
|
+
switch (keepIf) {
|
|
110
|
+
case 'all':
|
|
111
|
+
return yes === total;
|
|
112
|
+
case 'any':
|
|
113
|
+
return yes >= 1;
|
|
114
|
+
case 'majority':
|
|
115
|
+
return yes * 2 > total;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Tally the verify panels of an expanded `for_each` and gate each item (issue
|
|
120
|
+
* #343). Groups verify teammates by the stage teammate they depend on, reads a
|
|
121
|
+
* boolean vote per verify teammate via `readVote`, and applies `keep_if`.
|
|
122
|
+
*
|
|
123
|
+
* An item with no verify panel is kept unconditionally (there is no gate).
|
|
124
|
+
* Pure and deterministic: `readVote` is the only place runtime state enters, so
|
|
125
|
+
* this is unit-testable with a synthetic vote reader.
|
|
126
|
+
*/
|
|
127
|
+
export function tallyForEach(teammates, readVote) {
|
|
128
|
+
const stages = teammates.filter((t) => t.role === 'stage');
|
|
129
|
+
const verifiersByStage = new Map();
|
|
130
|
+
for (const t of teammates) {
|
|
131
|
+
if (t.role !== 'verify')
|
|
132
|
+
continue;
|
|
133
|
+
const stageName = t.after[0];
|
|
134
|
+
if (!stageName)
|
|
135
|
+
continue;
|
|
136
|
+
const list = verifiersByStage.get(stageName) ?? [];
|
|
137
|
+
list.push(t);
|
|
138
|
+
verifiersByStage.set(stageName, list);
|
|
139
|
+
}
|
|
140
|
+
return stages.map((stage) => {
|
|
141
|
+
const panel = verifiersByStage.get(stage.name) ?? [];
|
|
142
|
+
if (panel.length === 0) {
|
|
143
|
+
return {
|
|
144
|
+
item: stage.item,
|
|
145
|
+
itemIndex: stage.itemIndex,
|
|
146
|
+
stageName: stage.name,
|
|
147
|
+
kept: true,
|
|
148
|
+
votes: [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const keepIf = panel[0].keep_if ?? 'majority';
|
|
152
|
+
const votes = panel.map((v) => readVote(v));
|
|
153
|
+
return {
|
|
154
|
+
item: stage.item,
|
|
155
|
+
itemIndex: stage.itemIndex,
|
|
156
|
+
stageName: stage.name,
|
|
157
|
+
kept: evaluateKeepIf(votes, keepIf),
|
|
158
|
+
votes,
|
|
159
|
+
keepIf,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Expand a `for_each` spec against a producer's output and stage every
|
|
165
|
+
* resulting teammate into the given team via the dynamic-add path.
|
|
166
|
+
*
|
|
167
|
+
* Returns the descriptors and the spawned handles so a caller can drive the
|
|
168
|
+
* supervisor and later gather results (e.g. to evaluate a `verify` panel's
|
|
169
|
+
* `keep_if` gate — that vote-counting lives downstream, not here).
|
|
170
|
+
*/
|
|
171
|
+
export async function runForEach(mgr, teamName, spec, items, opts = {}) {
|
|
172
|
+
const { teammates, producedCount, usedCount, truncated } = expandForEach(spec, items, {
|
|
173
|
+
producerName: opts.producerName,
|
|
174
|
+
});
|
|
175
|
+
const effort = opts.effort ?? 'medium';
|
|
176
|
+
const spawned = [];
|
|
177
|
+
for (const t of teammates) {
|
|
178
|
+
const agent = await mgr.spawn(teamName, t.agentType, t.prompt, opts.cwd ?? null, null, // mode: inherit team default
|
|
179
|
+
effort, null, // parentSessionId
|
|
180
|
+
null, // workspaceDir
|
|
181
|
+
null, // version
|
|
182
|
+
t.name, t.after);
|
|
183
|
+
spawned.push(agent);
|
|
184
|
+
}
|
|
185
|
+
return { teammates, spawned, producedCount, usedCount, truncated };
|
|
186
|
+
}
|
|
@@ -14,4 +14,5 @@ export { type EffortLevel } from './agents.js';
|
|
|
14
14
|
export { collapseEvents, getToolBreakdown, groupAndFlattenEvents, summarizeEvents, getDelta, filterEventsByPriority, getLastTool, getToolUses, getLastMessages, getQuickStatus, getStatusSummary, AgentSummary, PRIORITY, type QuickStatus, } from './summarizer.js';
|
|
15
15
|
export { extractFileOpsFromBash } from './file_ops.js';
|
|
16
16
|
export { debug } from './debug.js';
|
|
17
|
+
export { runForEach, type RunForEachOptions, type RunForEachResult, } from './forEach.js';
|
|
17
18
|
export { createWorktree, removeWorktree, isGitRepo, getGitRoot, hasUncommittedChanges, getWorktreePath, getWorktreeBranch, } from './worktree.js';
|
package/dist/lib/teams/index.js
CHANGED
|
@@ -12,4 +12,5 @@ export { resolveAgentsDir, resolveBaseDir, } from './persistence.js';
|
|
|
12
12
|
export { collapseEvents, getToolBreakdown, groupAndFlattenEvents, summarizeEvents, getDelta, filterEventsByPriority, getLastTool, getToolUses, getLastMessages, getQuickStatus, getStatusSummary, AgentSummary, PRIORITY, } from './summarizer.js';
|
|
13
13
|
export { extractFileOpsFromBash } from './file_ops.js';
|
|
14
14
|
export { debug } from './debug.js';
|
|
15
|
+
export { runForEach, } from './forEach.js';
|
|
15
16
|
export { createWorktree, removeWorktree, isGitRepo, getGitRoot, hasUncommittedChanges, getWorktreePath, getWorktreeBranch, } from './worktree.js';
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CI check result. Shaped after `gh pr checks <pr> --json name,state,link,workflow`,
|
|
3
|
+
* where `state` is one of SUCCESS | FAILURE | PENDING | ERROR | CANCELLED | SKIPPING | ...
|
|
4
|
+
*
|
|
5
|
+
* Note on identity: `gh pr checks` exposes NO stable check-run id — `link` is the
|
|
6
|
+
* per-run URL, which changes every time a workflow re-runs. So dedupe keys off the
|
|
7
|
+
* check NAME (stable across re-runs), not the link (see `checkDedupeKey`). `link`
|
|
8
|
+
* is retained only for the human-facing prompt and for scraping run logs.
|
|
9
|
+
*/
|
|
10
|
+
export interface PrCheck {
|
|
11
|
+
name: string;
|
|
12
|
+
state: string;
|
|
13
|
+
link?: string;
|
|
14
|
+
workflow?: string;
|
|
15
|
+
}
|
|
16
|
+
/** A PR review comment. Shaped after GitHub's `repos/{owner}/{repo}/pulls/{n}/comments`. */
|
|
17
|
+
export interface PrReviewComment {
|
|
18
|
+
id: number;
|
|
19
|
+
body: string;
|
|
20
|
+
user?: string;
|
|
21
|
+
path?: string;
|
|
22
|
+
html_url?: string;
|
|
23
|
+
}
|
|
24
|
+
/** One PR under watch, with its current CI + comment snapshot. */
|
|
25
|
+
export interface PrSnapshot {
|
|
26
|
+
/** The PR URL (https://github.com/{owner}/{repo}/pull/{n}). */
|
|
27
|
+
prUrl: string;
|
|
28
|
+
/**
|
|
29
|
+
* Name of the teammate whose work opened this PR — the `--after` anchor the
|
|
30
|
+
* spawned fix/bugfix teammate links to. Null when the PR can't be traced to a
|
|
31
|
+
* named teammate (the reaction still fires, just without a dependency edge).
|
|
32
|
+
*/
|
|
33
|
+
sourceTeammate: string | null;
|
|
34
|
+
checks: PrCheck[];
|
|
35
|
+
comments: PrReviewComment[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Default cap on how many fix/bugfix waves pr-watch spawns for a single PR before
|
|
39
|
+
* it gives up and escalates to a human. A persistently-RED PR re-runs CI on every
|
|
40
|
+
* follow-up commit; without a cap the loop would spawn teammates without bound
|
|
41
|
+
* (issue #338). Configurable via `--max-waves`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const DEFAULT_MAX_WAVES = 3;
|
|
44
|
+
/** A decision to spawn a follow-up teammate. Pure output of `decidePrActions`. */
|
|
45
|
+
export type PrWatchAction = {
|
|
46
|
+
kind: 'ci-fix';
|
|
47
|
+
prUrl: string;
|
|
48
|
+
sourceTeammate: string | null;
|
|
49
|
+
check: PrCheck;
|
|
50
|
+
/** Idempotency key — record it in `handled` once acted on so it never re-fires. */
|
|
51
|
+
dedupeKey: string;
|
|
52
|
+
/** 1-based wave number for this PR (used to name the spawned teammate uniquely). */
|
|
53
|
+
wave: number;
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'review-fix';
|
|
56
|
+
prUrl: string;
|
|
57
|
+
sourceTeammate: string | null;
|
|
58
|
+
comment: PrReviewComment;
|
|
59
|
+
dedupeKey: string;
|
|
60
|
+
wave: number;
|
|
61
|
+
} | {
|
|
62
|
+
/**
|
|
63
|
+
* The per-PR wave budget is exhausted — stop spawning, escalate to a human.
|
|
64
|
+
* Emitted once per PR (deduped via `needsHumanKey`) instead of an N+1th fix.
|
|
65
|
+
*/
|
|
66
|
+
kind: 'needs-human';
|
|
67
|
+
prUrl: string;
|
|
68
|
+
sourceTeammate: string | null;
|
|
69
|
+
/** What triggered the escalation (e.g. `CI check "test"`). */
|
|
70
|
+
subject: string;
|
|
71
|
+
/** Waves already spent on this PR when the cap was hit. */
|
|
72
|
+
waves: number;
|
|
73
|
+
dedupeKey: string;
|
|
74
|
+
};
|
|
75
|
+
/** The spawnable subset of actions — everything except the `needs-human` escalation. */
|
|
76
|
+
export type PrWatchSpawnAction = Extract<PrWatchAction, {
|
|
77
|
+
kind: 'ci-fix' | 'review-fix';
|
|
78
|
+
}>;
|
|
79
|
+
/** True when a check is in a terminal failing state. */
|
|
80
|
+
export declare function isFailedCheck(check: PrCheck): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Idempotency key for a failed check on a PR — dedupe by check NAME.
|
|
83
|
+
*
|
|
84
|
+
* The name is the ONLY identity that's stable across re-runs: when a fixer pushes
|
|
85
|
+
* a follow-up commit, GitHub creates a fresh workflow run with a new `link` URL but
|
|
86
|
+
* the SAME check name. Keying off `link` (as an earlier draft did) would treat every
|
|
87
|
+
* re-run as a brand-new failure and spawn an unbounded chain of fixers (issue #338).
|
|
88
|
+
* Keying off the name means a re-run of the same check is recognised as the same
|
|
89
|
+
* logical failure; the per-PR wave counter then bounds how many times we retry it.
|
|
90
|
+
*/
|
|
91
|
+
export declare function checkDedupeKey(prUrl: string, check: PrCheck): string;
|
|
92
|
+
/** Idempotency key for a review comment on a PR — dedupe by comment id. */
|
|
93
|
+
export declare function commentDedupeKey(prUrl: string, comment: PrReviewComment): string;
|
|
94
|
+
/** Idempotency key for the one-shot "needs human" escalation on a PR. */
|
|
95
|
+
export declare function needsHumanKey(prUrl: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* The pure heart of pr-watch: given a PR snapshot, the set of dedupe keys already
|
|
98
|
+
* acted on, and how many fix waves each PR has already spent, decide which
|
|
99
|
+
* follow-up teammates to spawn. Emits:
|
|
100
|
+
* - one `ci-fix` action per NEW failed check (dedup by check NAME)
|
|
101
|
+
* - one `review-fix` action per NEW review comment (dedup by comment id)
|
|
102
|
+
* - a single `needs-human` action (per PR) once the wave budget is exhausted,
|
|
103
|
+
* INSTEAD of spawning — so a persistently-failing PR escalates rather than
|
|
104
|
+
* spawning teammates without bound (issue #338).
|
|
105
|
+
*
|
|
106
|
+
* `waves` maps a PR URL to the number of fix/bugfix teammates already spawned for
|
|
107
|
+
* it; once that reaches `maxWaves`, no further spawns are emitted for that PR.
|
|
108
|
+
* Both the CI-fix and comment-routing paths draw from the SAME per-PR budget, so
|
|
109
|
+
* neither can loop unboundedly.
|
|
110
|
+
*
|
|
111
|
+
* No network, no side effects, no reference to state beyond the passed
|
|
112
|
+
* `handled` / `waves` — so the same failure never spawns twice and the caller
|
|
113
|
+
* stays in control of when a wave is "spent".
|
|
114
|
+
*/
|
|
115
|
+
export declare function decidePrActions(snapshot: PrSnapshot, handled: ReadonlySet<string>, waves?: ReadonlyMap<string, number>, maxWaves?: number): PrWatchAction[];
|
|
116
|
+
/**
|
|
117
|
+
* Build the task prompt for a CI-fix teammate. Pure so it's unit-testable: the
|
|
118
|
+
* failing check + the fetched logs are injected as data.
|
|
119
|
+
*/
|
|
120
|
+
export declare function buildCiFixPrompt(action: Extract<PrWatchAction, {
|
|
121
|
+
kind: 'ci-fix';
|
|
122
|
+
}>, logs: string): string;
|
|
123
|
+
/**
|
|
124
|
+
* Build the task prompt for a review-comment bugfix teammate. Pure so it's
|
|
125
|
+
* unit-testable: the review comment is injected as data.
|
|
126
|
+
*/
|
|
127
|
+
export declare function buildReviewFixPrompt(action: Extract<PrWatchAction, {
|
|
128
|
+
kind: 'review-fix';
|
|
129
|
+
}>): string;
|
|
130
|
+
/** Parse an owner/repo/number triple out of a GitHub PR URL. Returns null when it doesn't match. */
|
|
131
|
+
export declare function parsePrUrl(prUrl: string): {
|
|
132
|
+
owner: string;
|
|
133
|
+
repo: string;
|
|
134
|
+
number: number;
|
|
135
|
+
} | null;
|
|
136
|
+
/** Fetch the current CI checks for a PR via `gh pr checks`. Returns [] on any gh error. */
|
|
137
|
+
export declare function fetchPrChecks(prUrl: string): Promise<PrCheck[]>;
|
|
138
|
+
/** Fetch review comments for a PR via `gh api`. Returns [] on any gh error. */
|
|
139
|
+
export declare function fetchPrReviewComments(prUrl: string): Promise<PrReviewComment[]>;
|
|
140
|
+
/** Fetch the failing-run logs for a check via `gh run view --log-failed`. Best-effort, truncated. */
|
|
141
|
+
export declare function fetchCiFailureLogs(check: PrCheck, maxChars?: number): Promise<string>;
|
|
142
|
+
/** Collect a live PR snapshot from `gh`. The poll-mode seam for `runPrWatch`. */
|
|
143
|
+
export declare function pollPrSnapshot(prUrl: string, sourceTeammate: string | null): Promise<PrSnapshot>;
|
|
144
|
+
/** A PR to watch, paired with the teammate that opened it. */
|
|
145
|
+
export interface WatchTarget {
|
|
146
|
+
prUrl: string;
|
|
147
|
+
sourceTeammate: string | null;
|
|
148
|
+
}
|
|
149
|
+
export interface PrWatchDeps {
|
|
150
|
+
/** Discover which PRs to watch this pass (teammates may open PRs mid-run). */
|
|
151
|
+
resolveTargets: () => Promise<WatchTarget[]>;
|
|
152
|
+
/** Snapshot one PR's CI + review comments. Defaults to `pollPrSnapshot`. */
|
|
153
|
+
pollSnapshot?: (prUrl: string, sourceTeammate: string | null) => Promise<PrSnapshot>;
|
|
154
|
+
/** Fetch CI failure logs for a ci-fix action. Defaults to `fetchCiFailureLogs`. */
|
|
155
|
+
fetchLogs?: (check: PrCheck) => Promise<string>;
|
|
156
|
+
/**
|
|
157
|
+
* React to one decided spawn action: spawn the fix/bugfix teammate. The prompt
|
|
158
|
+
* is pre-built with logs/comment injected. Return the spawned teammate label for
|
|
159
|
+
* logging, or null if the spawn was skipped. `needs-human` actions never reach
|
|
160
|
+
* here — they surface as a `needs-human` event instead.
|
|
161
|
+
*/
|
|
162
|
+
react: (action: PrWatchSpawnAction, prompt: string) => Promise<string | null>;
|
|
163
|
+
/**
|
|
164
|
+
* True when a previously-spawned reaction has finished. Once its fixer settles,
|
|
165
|
+
* the dedupe guard for that check clears so a check that is STILL red can spawn
|
|
166
|
+
* its next (budget-bounded) wave; without this, a check would only ever get one
|
|
167
|
+
* fix attempt per pr-watch run. Optional — when omitted, a check gets a single
|
|
168
|
+
* wave until pr-watch restarts.
|
|
169
|
+
*/
|
|
170
|
+
reactionSettled?: (label: string) => Promise<boolean>;
|
|
171
|
+
/** Progress sink — one call per notable event (poll, spawn, escalate, error). */
|
|
172
|
+
onEvent?: (event: PrWatchEvent) => void;
|
|
173
|
+
}
|
|
174
|
+
export type PrWatchEvent = {
|
|
175
|
+
type: 'poll';
|
|
176
|
+
targets: number;
|
|
177
|
+
timestamp: string;
|
|
178
|
+
} | {
|
|
179
|
+
type: 'spawned';
|
|
180
|
+
action: PrWatchSpawnAction;
|
|
181
|
+
label: string | null;
|
|
182
|
+
timestamp: string;
|
|
183
|
+
} | {
|
|
184
|
+
type: 'needs-human';
|
|
185
|
+
prUrl: string;
|
|
186
|
+
subject: string;
|
|
187
|
+
waves: number;
|
|
188
|
+
timestamp: string;
|
|
189
|
+
} | {
|
|
190
|
+
type: 'error';
|
|
191
|
+
prUrl: string;
|
|
192
|
+
message: string;
|
|
193
|
+
timestamp: string;
|
|
194
|
+
};
|
|
195
|
+
export interface PrWatchOptions {
|
|
196
|
+
intervalMs?: number;
|
|
197
|
+
/** Stop after this many polls (0 / undefined = run until signalled). */
|
|
198
|
+
maxPolls?: number;
|
|
199
|
+
/** Seed of already-handled dedupe keys (e.g. restored from disk). */
|
|
200
|
+
handled?: Set<string>;
|
|
201
|
+
/** Per-PR fix-wave counts, carried across restarts so the budget survives a restart. */
|
|
202
|
+
waves?: Map<string, number>;
|
|
203
|
+
/** Cap on fix waves per PR before escalating to a human. Defaults to DEFAULT_MAX_WAVES. */
|
|
204
|
+
maxWaves?: number;
|
|
205
|
+
/** Abort signal — resolves the loop at the next interval boundary. */
|
|
206
|
+
shouldStop?: () => boolean;
|
|
207
|
+
}
|
|
208
|
+
export interface PrWatchResult {
|
|
209
|
+
polls: number;
|
|
210
|
+
spawned: number;
|
|
211
|
+
/** How many PRs escalated to a human (wave budget exhausted). */
|
|
212
|
+
neededHuman: number;
|
|
213
|
+
handled: Set<string>;
|
|
214
|
+
waves: Map<string, number>;
|
|
215
|
+
stoppedBy: 'max-polls' | 'signal';
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Run the pr-watch poll loop. Each pass: retire the dedupe guards of any fixers
|
|
219
|
+
* that have finished (so a still-red check can spawn its next wave), resolve watch
|
|
220
|
+
* targets, snapshot each PR, decide actions against the running `handled` set and
|
|
221
|
+
* per-PR `waves` budget, then react — spawning fresh fixers, or emitting a
|
|
222
|
+
* one-shot `needs-human` event once a PR's wave budget is spent. Every acted-on
|
|
223
|
+
* dedupe key is recorded so a failure/comment never spawns twice within a wave,
|
|
224
|
+
* and `waves` hard-caps the total spawns per PR (issue #338).
|
|
225
|
+
*/
|
|
226
|
+
export declare function runPrWatch(deps: PrWatchDeps, opts?: PrWatchOptions): Promise<PrWatchResult>;
|