@cluesmith/codev 3.1.2 → 3.1.4
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/bin/afx.js +8 -4
- package/dashboard-dist/assets/{index-CM6A5r59.js → index-DSENHqFj.js} +2 -2
- package/dashboard-dist/assets/{index-CM6A5r59.js.map → index-DSENHqFj.js.map} +1 -1
- package/dashboard-dist/index.html +1 -1
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +28 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +8 -2
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +30 -7
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts +8 -0
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +21 -3
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/workspace-recover.d.ts +74 -0
- package/dist/agent-farm/commands/workspace-recover.d.ts.map +1 -0
- package/dist/agent-farm/commands/workspace-recover.js +307 -0
- package/dist/agent-farm/commands/workspace-recover.js.map +1 -0
- package/dist/agent-farm/servers/overview.d.ts +37 -0
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +38 -0
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +57 -1
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/utils/claude-session-discovery.d.ts +19 -0
- package/dist/agent-farm/utils/claude-session-discovery.d.ts.map +1 -0
- package/dist/agent-farm/utils/claude-session-discovery.js +64 -0
- package/dist/agent-farm/utils/claude-session-discovery.js.map +1 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -32
- package/dist/cli.js.map +1 -1
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +29 -1
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +59 -9
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts +25 -1
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +43 -3
- package/dist/commands/porch/protocol.js.map +1 -1
- package/dist/commands/porch/state.d.ts +17 -0
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +47 -0
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/porch/types.d.ts +34 -0
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/package.json +3 -4
- package/skeleton/protocols/aspir/protocol.json +5 -5
- package/skeleton/protocols/spir/protocol.json +5 -5
- package/bin/af.js +0 -8
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ProjectState } from '../../commands/porch/types.js';
|
|
2
|
+
import type { DbTerminalSession } from '../servers/tower-types.js';
|
|
3
|
+
export interface WorkspaceRecoverOptions {
|
|
4
|
+
apply?: boolean;
|
|
5
|
+
maxAge?: number;
|
|
6
|
+
includeStale?: boolean;
|
|
7
|
+
yes?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export type IneligibleReason = 'terminal' | 'unsupported_protocol' | 'worktree_missing' | 'shellper_alive' | 'stale';
|
|
10
|
+
export type EligibilityResult = {
|
|
11
|
+
eligible: true;
|
|
12
|
+
} | {
|
|
13
|
+
eligible: false;
|
|
14
|
+
reason: IneligibleReason;
|
|
15
|
+
};
|
|
16
|
+
export interface EligibilityInputs {
|
|
17
|
+
state: ProjectState;
|
|
18
|
+
builderInfo: BuilderInfo | null;
|
|
19
|
+
sessions: DbTerminalSession[];
|
|
20
|
+
worktreeExists: boolean;
|
|
21
|
+
ageDays: number;
|
|
22
|
+
maxAgeDays: number;
|
|
23
|
+
includeStale: boolean;
|
|
24
|
+
isProcessAlive: (pid: number) => boolean;
|
|
25
|
+
socketExists: (socket: string) => boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pure predicate — no I/O. All filesystem and process probes happen in the
|
|
29
|
+
* caller and are passed in via `isProcessAlive` and `socketExists`. This keeps
|
|
30
|
+
* the predicate trivially unit-testable.
|
|
31
|
+
*
|
|
32
|
+
* On terminal_sessions and the "no row" case:
|
|
33
|
+
* Tower's reconciliation deletes terminal_sessions rows whose shellper can't
|
|
34
|
+
* be reconnected (tower-terminals.ts:485-711). That runs on Tower startup,
|
|
35
|
+
* which is exactly what happens between a machine reboot and the user
|
|
36
|
+
* invoking `workspace recover`. By the time recovery runs, the rows for
|
|
37
|
+
* dead builders have already been pruned. So "no matching session row"
|
|
38
|
+
* is the COMMON case for any builder that needs revival — not a reason to
|
|
39
|
+
* skip. The row is only useful here as a positive "still alive" signal.
|
|
40
|
+
*
|
|
41
|
+
* Predicate order (cheap structural checks first):
|
|
42
|
+
* 1. terminal phase
|
|
43
|
+
* 2. unsupported protocol family
|
|
44
|
+
* 3. worktree missing on disk
|
|
45
|
+
* 4. any matching session row is alive → known still running, leave alone
|
|
46
|
+
* 5. stale (older than maxAge, unless includeStale)
|
|
47
|
+
* 6. otherwise → revive
|
|
48
|
+
*/
|
|
49
|
+
export declare function evaluateEligibility(inputs: EligibilityInputs): EligibilityResult;
|
|
50
|
+
export interface BuilderInfo {
|
|
51
|
+
builderId: string;
|
|
52
|
+
issueArg: string;
|
|
53
|
+
cliProtocol: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Derive the inputs needed to invoke `afx spawn <issueArg> --resume --protocol <cliProtocol>`
|
|
57
|
+
* and the SQLite `role_id` to look up the builder's terminal session.
|
|
58
|
+
*
|
|
59
|
+
* Returns null for protocols that cannot be cleanly resumed via the spawn CLI
|
|
60
|
+
* (experiment/maintain/task/legacy spider/etc) — callers should skip those
|
|
61
|
+
* projects with an `unsupported_protocol` reason.
|
|
62
|
+
*/
|
|
63
|
+
export declare function deriveBuilderInfo(state: ProjectState): BuilderInfo | null;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the builder's worktree path on disk, handling both the Spec-653
|
|
66
|
+
* ID-only layout and the legacy title-suffixed form.
|
|
67
|
+
*
|
|
68
|
+
* Returns null when the project's protocol isn't revivable (no defined worktree
|
|
69
|
+
* naming) or when no matching directory exists.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveWorktreePath(buildersDir: string, state: ProjectState): string | null;
|
|
72
|
+
export declare function formatRelativeAge(iso: string): string;
|
|
73
|
+
export declare function workspaceRecover(options?: WorkspaceRecoverOptions): Promise<void>;
|
|
74
|
+
//# sourceMappingURL=workspace-recover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-recover.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/commands/workspace-recover.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAcnE,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,sBAAsB,GACtB,kBAAkB,GAClB,gBAAgB,GAChB,OAAO,CAAC;AAEZ,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAClB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAuBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,CAyBhF;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,GAAG,IAAI,CAiBzE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAkB3F;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAcrD;AA6DD,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,CAoH3F"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// workspace recover — revive builders whose shellper died (e.g. machine reboot).
|
|
2
|
+
// Issue #829. Dry-run by default; --apply actually respawns.
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { getConfig } from '../utils/index.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { buildAgentName, stripLeadingZeros } from '../utils/agent-names.js';
|
|
10
|
+
import { processExists, getTerminalSessionsForWorkspace } from '../servers/tower-terminals.js';
|
|
11
|
+
import { closeGlobalDb } from '../db/index.js';
|
|
12
|
+
import { listAllProjects } from '../../commands/porch/state.js';
|
|
13
|
+
import { confirm } from '../../lib/cli-prompts.js';
|
|
14
|
+
const TERMINAL_PHASES = new Set(['verified', 'complete']);
|
|
15
|
+
const DEFAULT_MAX_AGE_DAYS = 7;
|
|
16
|
+
// Protocols that `afx spawn <id> --resume --protocol <name>` can revive cleanly:
|
|
17
|
+
// issue-driven families with a positional issue arg and a stable worktree layout.
|
|
18
|
+
// `experiment` / `maintain` / `task` / bare protocol-mode builders can't be
|
|
19
|
+
// resumed by ID through spawn.ts and are skipped with `unsupported_protocol`.
|
|
20
|
+
// (Legacy `spider` is also excluded — it was retired long ago, and any stray
|
|
21
|
+
// project still carrying that protocol value should be treated as unsupported.)
|
|
22
|
+
const REVIVABLE_PROTOCOLS = new Set(['spir', 'aspir', 'air', 'pir', 'bugfix']);
|
|
23
|
+
/**
|
|
24
|
+
* Liveness rule per row (Gemini cmap finding):
|
|
25
|
+
* - shellper_pid known + alive → ALIVE
|
|
26
|
+
* - shellper_pid known + dead → DEAD (socket is ignored; the OS doesn't clean
|
|
27
|
+
* ~/.codev/run/ on reboot, so a stale socket
|
|
28
|
+
* would otherwise falsely mark the row alive
|
|
29
|
+
* and defeat the primary recovery use case)
|
|
30
|
+
* - shellper_pid null + socket file exists → ALIVE (legacy / pre-PID rows)
|
|
31
|
+
* - shellper_pid null + no socket → DEAD
|
|
32
|
+
*/
|
|
33
|
+
function isSessionAlive(session, isProcessAlive, socketExists) {
|
|
34
|
+
if (session.shellper_pid !== null) {
|
|
35
|
+
return isProcessAlive(session.shellper_pid);
|
|
36
|
+
}
|
|
37
|
+
return session.shellper_socket !== null && socketExists(session.shellper_socket);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pure predicate — no I/O. All filesystem and process probes happen in the
|
|
41
|
+
* caller and are passed in via `isProcessAlive` and `socketExists`. This keeps
|
|
42
|
+
* the predicate trivially unit-testable.
|
|
43
|
+
*
|
|
44
|
+
* On terminal_sessions and the "no row" case:
|
|
45
|
+
* Tower's reconciliation deletes terminal_sessions rows whose shellper can't
|
|
46
|
+
* be reconnected (tower-terminals.ts:485-711). That runs on Tower startup,
|
|
47
|
+
* which is exactly what happens between a machine reboot and the user
|
|
48
|
+
* invoking `workspace recover`. By the time recovery runs, the rows for
|
|
49
|
+
* dead builders have already been pruned. So "no matching session row"
|
|
50
|
+
* is the COMMON case for any builder that needs revival — not a reason to
|
|
51
|
+
* skip. The row is only useful here as a positive "still alive" signal.
|
|
52
|
+
*
|
|
53
|
+
* Predicate order (cheap structural checks first):
|
|
54
|
+
* 1. terminal phase
|
|
55
|
+
* 2. unsupported protocol family
|
|
56
|
+
* 3. worktree missing on disk
|
|
57
|
+
* 4. any matching session row is alive → known still running, leave alone
|
|
58
|
+
* 5. stale (older than maxAge, unless includeStale)
|
|
59
|
+
* 6. otherwise → revive
|
|
60
|
+
*/
|
|
61
|
+
export function evaluateEligibility(inputs) {
|
|
62
|
+
const { state, builderInfo, sessions, worktreeExists, ageDays, maxAgeDays, includeStale, isProcessAlive, socketExists, } = inputs;
|
|
63
|
+
if (TERMINAL_PHASES.has(state.phase)) {
|
|
64
|
+
return { eligible: false, reason: 'terminal' };
|
|
65
|
+
}
|
|
66
|
+
if (builderInfo === null) {
|
|
67
|
+
return { eligible: false, reason: 'unsupported_protocol' };
|
|
68
|
+
}
|
|
69
|
+
if (!worktreeExists) {
|
|
70
|
+
return { eligible: false, reason: 'worktree_missing' };
|
|
71
|
+
}
|
|
72
|
+
// If ANY matching session looks alive, treat the builder as alive. Duplicates
|
|
73
|
+
// can occur when a prior recovery left a dead row behind (terminal_sessions
|
|
74
|
+
// has no UNIQUE constraint on role_id) — the cautious read is "alive."
|
|
75
|
+
if (sessions.some(s => isSessionAlive(s, isProcessAlive, socketExists))) {
|
|
76
|
+
return { eligible: false, reason: 'shellper_alive' };
|
|
77
|
+
}
|
|
78
|
+
if (!includeStale && ageDays > maxAgeDays) {
|
|
79
|
+
return { eligible: false, reason: 'stale' };
|
|
80
|
+
}
|
|
81
|
+
return { eligible: true };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Derive the inputs needed to invoke `afx spawn <issueArg> --resume --protocol <cliProtocol>`
|
|
85
|
+
* and the SQLite `role_id` to look up the builder's terminal session.
|
|
86
|
+
*
|
|
87
|
+
* Returns null for protocols that cannot be cleanly resumed via the spawn CLI
|
|
88
|
+
* (experiment/maintain/task/legacy spider/etc) — callers should skip those
|
|
89
|
+
* projects with an `unsupported_protocol` reason.
|
|
90
|
+
*/
|
|
91
|
+
export function deriveBuilderInfo(state) {
|
|
92
|
+
if (!REVIVABLE_PROTOCOLS.has(state.protocol)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (state.protocol === 'bugfix') {
|
|
96
|
+
const numericId = state.id.replace(/^bugfix-/, '');
|
|
97
|
+
return {
|
|
98
|
+
builderId: buildAgentName('bugfix', numericId),
|
|
99
|
+
issueArg: numericId,
|
|
100
|
+
cliProtocol: 'bugfix',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
builderId: buildAgentName('spec', state.id, state.protocol),
|
|
105
|
+
issueArg: stripLeadingZeros(state.id),
|
|
106
|
+
cliProtocol: state.protocol,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the builder's worktree path on disk, handling both the Spec-653
|
|
111
|
+
* ID-only layout and the legacy title-suffixed form.
|
|
112
|
+
*
|
|
113
|
+
* Returns null when the project's protocol isn't revivable (no defined worktree
|
|
114
|
+
* naming) or when no matching directory exists.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveWorktreePath(buildersDir, state) {
|
|
117
|
+
const info = deriveBuilderInfo(state);
|
|
118
|
+
if (info === null)
|
|
119
|
+
return null;
|
|
120
|
+
const idOnlyName = `${info.cliProtocol}-${info.issueArg}`;
|
|
121
|
+
const idOnlyPath = join(buildersDir, idOnlyName);
|
|
122
|
+
if (existsSync(idOnlyPath) && existsSync(join(idOnlyPath, '.git'))) {
|
|
123
|
+
return idOnlyPath;
|
|
124
|
+
}
|
|
125
|
+
if (!existsSync(buildersDir))
|
|
126
|
+
return null;
|
|
127
|
+
const prefix = `${info.cliProtocol}-${info.issueArg}-`;
|
|
128
|
+
for (const entry of readdirSync(buildersDir, { withFileTypes: true })) {
|
|
129
|
+
if (!entry.isDirectory() || !entry.name.startsWith(prefix))
|
|
130
|
+
continue;
|
|
131
|
+
const candidate = join(buildersDir, entry.name);
|
|
132
|
+
if (existsSync(join(candidate, '.git')))
|
|
133
|
+
return candidate;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
export function formatRelativeAge(iso) {
|
|
138
|
+
const ms = Date.now() - Date.parse(iso);
|
|
139
|
+
if (Number.isNaN(ms) || ms < 0)
|
|
140
|
+
return '—';
|
|
141
|
+
const minutes = Math.floor(ms / 60_000);
|
|
142
|
+
if (minutes < 60)
|
|
143
|
+
return `${minutes}m ago`;
|
|
144
|
+
const hours = Math.floor(minutes / 60);
|
|
145
|
+
if (hours < 24)
|
|
146
|
+
return `${hours}h ago`;
|
|
147
|
+
// Round UP rather than down so the label aligns with --max-age semantics:
|
|
148
|
+
// anything strictly older than 24h prints as "2d ago" (not "1d ago"), so
|
|
149
|
+
// a row labelled "1d ago" is actually within --max-age 1 (≤ 24h exact).
|
|
150
|
+
// Use ms rather than the floored `hours` to preserve sub-hour precision —
|
|
151
|
+
// 24h + 1s must ceil to 2 days, not 1.
|
|
152
|
+
const days = Math.ceil(ms / 86_400_000);
|
|
153
|
+
return `${days}d ago`;
|
|
154
|
+
}
|
|
155
|
+
function reasonLabel(reason) {
|
|
156
|
+
switch (reason) {
|
|
157
|
+
case 'terminal': return 'terminal';
|
|
158
|
+
case 'unsupported_protocol': return 'unsupported protocol';
|
|
159
|
+
case 'worktree_missing': return 'worktree missing';
|
|
160
|
+
case 'shellper_alive': return 'shellper alive';
|
|
161
|
+
case 'stale': return 'stale';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function printPreview(rows) {
|
|
165
|
+
const widths = [6, 9, 12, 14, 10, 20];
|
|
166
|
+
logger.row(['ID', 'PROTOCOL', 'PHASE', 'UPDATED', 'STATUS', 'REASON'], widths);
|
|
167
|
+
logger.row(['─'.repeat(6), '─'.repeat(9), '─'.repeat(12), '─'.repeat(14), '─'.repeat(10), '─'.repeat(20)], widths);
|
|
168
|
+
for (const row of rows) {
|
|
169
|
+
const status = row.eligibility.eligible
|
|
170
|
+
? chalk.green('revive')
|
|
171
|
+
: chalk.gray('skip');
|
|
172
|
+
const reason = row.eligibility.eligible ? '—' : reasonLabel(row.eligibility.reason);
|
|
173
|
+
logger.row([
|
|
174
|
+
row.state.id,
|
|
175
|
+
row.state.protocol,
|
|
176
|
+
row.state.phase,
|
|
177
|
+
formatRelativeAge(row.state.updated_at),
|
|
178
|
+
status,
|
|
179
|
+
reason,
|
|
180
|
+
], widths);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function respawnBuilder(info) {
|
|
184
|
+
// Use the current node binary and CLI entry point so the respawn invocation
|
|
185
|
+
// matches the install method this command was started under (npm global,
|
|
186
|
+
// npx, dev script, etc.) instead of relying on PATH lookup of 'afx'.
|
|
187
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
188
|
+
const child = spawn(process.execPath, [process.argv[1], 'spawn', info.issueArg, '--resume', '--protocol', info.cliProtocol], { stdio: 'inherit' });
|
|
189
|
+
child.on('error', rejectPromise);
|
|
190
|
+
child.on('exit', (code) => {
|
|
191
|
+
if (code === 0)
|
|
192
|
+
resolvePromise();
|
|
193
|
+
else
|
|
194
|
+
rejectPromise(new Error(`afx spawn exited with code ${code}`));
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
export async function workspaceRecover(options = {}) {
|
|
199
|
+
const config = getConfig();
|
|
200
|
+
const maxAgeDays = options.maxAge ?? DEFAULT_MAX_AGE_DAYS;
|
|
201
|
+
const includeStale = options.includeStale ?? false;
|
|
202
|
+
const apply = options.apply ?? false;
|
|
203
|
+
logger.header(`Workspace Recover${apply ? '' : ' (dry-run)'}`);
|
|
204
|
+
logger.kv('Workspace', config.workspaceRoot);
|
|
205
|
+
if (!includeStale)
|
|
206
|
+
logger.kv('Max age', `${maxAgeDays} day(s)`);
|
|
207
|
+
logger.blank();
|
|
208
|
+
const projects = listAllProjects(config.workspaceRoot, {
|
|
209
|
+
onParseError: (statusPath, err) => {
|
|
210
|
+
logger.debug(`Skipped unparseable ${statusPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
if (projects.length === 0) {
|
|
214
|
+
logger.info('No porch projects found.');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
let sessions;
|
|
218
|
+
try {
|
|
219
|
+
sessions = getTerminalSessionsForWorkspace(config.workspaceRoot);
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
closeGlobalDb();
|
|
223
|
+
}
|
|
224
|
+
// role_id has no UNIQUE constraint in the schema, so collect every matching
|
|
225
|
+
// row per builder id rather than collapsing to last-write-wins.
|
|
226
|
+
const sessionsByRoleId = new Map();
|
|
227
|
+
for (const s of sessions) {
|
|
228
|
+
if (s.type !== 'builder' || !s.role_id)
|
|
229
|
+
continue;
|
|
230
|
+
const bucket = sessionsByRoleId.get(s.role_id);
|
|
231
|
+
if (bucket)
|
|
232
|
+
bucket.push(s);
|
|
233
|
+
else
|
|
234
|
+
sessionsByRoleId.set(s.role_id, [s]);
|
|
235
|
+
}
|
|
236
|
+
const allRows = projects.map(({ state }) => {
|
|
237
|
+
const builderInfo = deriveBuilderInfo(state);
|
|
238
|
+
const matchingSessions = builderInfo ? sessionsByRoleId.get(builderInfo.builderId) ?? [] : [];
|
|
239
|
+
const worktreePath = resolveWorktreePath(config.buildersDir, state);
|
|
240
|
+
const ageDays = (Date.now() - Date.parse(state.updated_at)) / 86_400_000;
|
|
241
|
+
const eligibility = evaluateEligibility({
|
|
242
|
+
state, builderInfo,
|
|
243
|
+
sessions: matchingSessions,
|
|
244
|
+
worktreeExists: worktreePath !== null,
|
|
245
|
+
ageDays, maxAgeDays, includeStale,
|
|
246
|
+
isProcessAlive: processExists,
|
|
247
|
+
socketExists: existsSync,
|
|
248
|
+
});
|
|
249
|
+
return { state, builderInfo, worktreePath, eligibility, ageDays };
|
|
250
|
+
});
|
|
251
|
+
// By default the preview hides projects beyond the recency window — for a
|
|
252
|
+
// large workspace the stale tail dominates the table and obscures the few
|
|
253
|
+
// rows the operator actually cares about. --include-stale shows everything.
|
|
254
|
+
const visibleRows = includeStale
|
|
255
|
+
? allRows
|
|
256
|
+
: allRows.filter(r => r.ageDays <= maxAgeDays);
|
|
257
|
+
const hiddenStaleCount = allRows.length - visibleRows.length;
|
|
258
|
+
printPreview(visibleRows);
|
|
259
|
+
if (hiddenStaleCount > 0) {
|
|
260
|
+
logger.blank();
|
|
261
|
+
logger.info(`${hiddenStaleCount} project(s) older than ${maxAgeDays} day(s) hidden. Pass --include-stale to show them.`);
|
|
262
|
+
}
|
|
263
|
+
const eligible = allRows.filter((r) => r.eligibility.eligible && r.builderInfo !== null);
|
|
264
|
+
logger.blank();
|
|
265
|
+
if (hiddenStaleCount > 0) {
|
|
266
|
+
logger.kv('Eligible', `${eligible.length} / ${visibleRows.length} visible (${allRows.length} scanned)`);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
logger.kv('Eligible', `${eligible.length} / ${allRows.length}`);
|
|
270
|
+
}
|
|
271
|
+
if (eligible.length === 0) {
|
|
272
|
+
logger.info(apply ? 'Nothing to revive.' : 'Nothing would be revived.');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!apply) {
|
|
276
|
+
logger.info(`Run with --apply to respawn ${eligible.length} builder(s).`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!options.yes) {
|
|
280
|
+
const proceed = await confirm(`Proceed to respawn ${eligible.length} builder(s)?`, false);
|
|
281
|
+
if (!proceed) {
|
|
282
|
+
logger.info('Aborted.');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
let succeeded = 0;
|
|
287
|
+
let failed = 0;
|
|
288
|
+
for (const row of eligible) {
|
|
289
|
+
logger.blank();
|
|
290
|
+
logger.info(`Respawning ${row.builderInfo.builderId} (issue ${row.builderInfo.issueArg}, ${row.builderInfo.cliProtocol})...`);
|
|
291
|
+
try {
|
|
292
|
+
await respawnBuilder(row.builderInfo);
|
|
293
|
+
succeeded++;
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
failed++;
|
|
297
|
+
logger.error(`Failed to respawn ${row.builderInfo.builderId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
logger.blank();
|
|
301
|
+
logger.kv('Respawned', String(succeeded));
|
|
302
|
+
if (failed > 0) {
|
|
303
|
+
logger.kv('Failed', String(failed));
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=workspace-recover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-recover.js","sourceRoot":"","sources":["../../../src/agent-farm/commands/workspace-recover.ts"],"names":[],"mappings":"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"}
|
|
@@ -60,6 +60,18 @@ export interface BuilderOverview {
|
|
|
60
60
|
* an inline attribution tag when the workspace hosts more than one architect.
|
|
61
61
|
*/
|
|
62
62
|
spawnedByArchitect: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Canonical "PR is waiting on a human reviewer" signal (Issue #872). True the
|
|
65
|
+
* moment porch transitions out of the CMAP-emitting state for the PR-creating
|
|
66
|
+
* phase, for ALL bundled protocols. Computed from `pr_ready_for_human` in
|
|
67
|
+
* status.yaml when present; otherwise falls back to the v3.1.3 derived logic
|
|
68
|
+
* (`blocked === 'PR review'` || BUGFIX `phase === 'verified'`) so in-flight
|
|
69
|
+
* projects from before this field shipped continue to surface correctly.
|
|
70
|
+
*
|
|
71
|
+
* Consumers (dashboard NeedsAttentionList, VSCode tree) gate on this flag
|
|
72
|
+
* instead of deriving from the protocol-specific gate shape themselves.
|
|
73
|
+
*/
|
|
74
|
+
prReady: boolean;
|
|
63
75
|
}
|
|
64
76
|
export interface PROverview {
|
|
65
77
|
id: string;
|
|
@@ -121,6 +133,13 @@ interface ParsedStatus {
|
|
|
121
133
|
gateApprovedAt: Record<string, string>;
|
|
122
134
|
planPhases: PlanPhase[];
|
|
123
135
|
startedAt: string;
|
|
136
|
+
/**
|
|
137
|
+
* Mirror of porch's `pr_ready_for_human` status.yaml field (Issue #872).
|
|
138
|
+
* `null` when the field is absent in the yaml (legacy state from before
|
|
139
|
+
* the field shipped) so `derivePrReady` can choose between the explicit
|
|
140
|
+
* value and the v3.1.3 fallback derivation.
|
|
141
|
+
*/
|
|
142
|
+
prReadyForHuman: boolean | null;
|
|
124
143
|
}
|
|
125
144
|
/**
|
|
126
145
|
* Parse a porch status.yaml file into structured data.
|
|
@@ -153,6 +172,24 @@ export declare function detectBlockedGate(parsed: ParsedStatus): string | null;
|
|
|
153
172
|
* Keep this list in sync with `detectBlocked`'s `gateLabels` keys.
|
|
154
173
|
*/
|
|
155
174
|
export declare function detectBlockedSince(parsed: ParsedStatus): string | null;
|
|
175
|
+
/**
|
|
176
|
+
* Compute the canonical "PR ready for human review" signal for a builder
|
|
177
|
+
* (Issue #872). Prefers porch's explicit `pr_ready_for_human` field in
|
|
178
|
+
* status.yaml when present; otherwise falls back to the v3.1.3 derivation so
|
|
179
|
+
* builders whose state.yaml pre-dates the field continue to surface.
|
|
180
|
+
*
|
|
181
|
+
* Fallback derivation:
|
|
182
|
+
* - `blocked === 'PR review'` — protocols with a `pr` gate (AIR/SPIR/ASPIR/PIR)
|
|
183
|
+
* that reached the human-bottleneck state under v3.1.3 logic.
|
|
184
|
+
* - BUGFIX `phase === 'verified'` — the v3.1.3 gap this canonical signal
|
|
185
|
+
* closes. BUGFIX has no `pr` gate, so its post-CMAP state is `verified`
|
|
186
|
+
* with no gate-pending signal; the fallback adds this case so in-flight
|
|
187
|
+
* BUGFIX builders aren't dropped from Needs Attention.
|
|
188
|
+
*
|
|
189
|
+
* Once porch has written `pr_ready_for_human` to status.yaml the fallback no
|
|
190
|
+
* longer fires for that project — the explicit field is authoritative.
|
|
191
|
+
*/
|
|
192
|
+
export declare function derivePrReady(parsed: ParsedStatus): boolean;
|
|
156
193
|
/**
|
|
157
194
|
* Compute total idle time (ms) from gate wait periods.
|
|
158
195
|
* Includes completed gate waits (requested_at → approved_at) and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/overview.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,KAAK,EAAW,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQvE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/overview.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,7 @@ export function parseStatusYaml(content) {
|
|
|
27
27
|
gateApprovedAt: {},
|
|
28
28
|
planPhases: [],
|
|
29
29
|
startedAt: '',
|
|
30
|
+
prReadyForHuman: null,
|
|
30
31
|
};
|
|
31
32
|
const lines = content.split('\n');
|
|
32
33
|
let section = 'none';
|
|
@@ -70,6 +71,12 @@ export function parseStatusYaml(content) {
|
|
|
70
71
|
section = 'none';
|
|
71
72
|
continue;
|
|
72
73
|
}
|
|
74
|
+
const prReadyMatch = line.match(/^pr_ready_for_human:\s*(true|false)\s*$/);
|
|
75
|
+
if (prReadyMatch) {
|
|
76
|
+
result.prReadyForHuman = prReadyMatch[1] === 'true';
|
|
77
|
+
section = 'none';
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
73
80
|
// Section headers
|
|
74
81
|
if (/^gates:\s*$/.test(line)) {
|
|
75
82
|
if (currentPlanPhase) {
|
|
@@ -285,6 +292,34 @@ export function detectBlockedSince(parsed) {
|
|
|
285
292
|
}
|
|
286
293
|
return null;
|
|
287
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Compute the canonical "PR ready for human review" signal for a builder
|
|
297
|
+
* (Issue #872). Prefers porch's explicit `pr_ready_for_human` field in
|
|
298
|
+
* status.yaml when present; otherwise falls back to the v3.1.3 derivation so
|
|
299
|
+
* builders whose state.yaml pre-dates the field continue to surface.
|
|
300
|
+
*
|
|
301
|
+
* Fallback derivation:
|
|
302
|
+
* - `blocked === 'PR review'` — protocols with a `pr` gate (AIR/SPIR/ASPIR/PIR)
|
|
303
|
+
* that reached the human-bottleneck state under v3.1.3 logic.
|
|
304
|
+
* - BUGFIX `phase === 'verified'` — the v3.1.3 gap this canonical signal
|
|
305
|
+
* closes. BUGFIX has no `pr` gate, so its post-CMAP state is `verified`
|
|
306
|
+
* with no gate-pending signal; the fallback adds this case so in-flight
|
|
307
|
+
* BUGFIX builders aren't dropped from Needs Attention.
|
|
308
|
+
*
|
|
309
|
+
* Once porch has written `pr_ready_for_human` to status.yaml the fallback no
|
|
310
|
+
* longer fires for that project — the explicit field is authoritative.
|
|
311
|
+
*/
|
|
312
|
+
export function derivePrReady(parsed) {
|
|
313
|
+
if (parsed.prReadyForHuman !== null)
|
|
314
|
+
return parsed.prReadyForHuman;
|
|
315
|
+
// Fallback: v3.1.3 logic + BUGFIX case.
|
|
316
|
+
const prGatePending = parsed.gates['pr'] === 'pending' && !!parsed.gateRequestedAt['pr'];
|
|
317
|
+
if (prGatePending)
|
|
318
|
+
return true;
|
|
319
|
+
if (parsed.protocol === 'bugfix' && parsed.phase === 'verified')
|
|
320
|
+
return true;
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
288
323
|
/**
|
|
289
324
|
* Compute total idle time (ms) from gate wait periods.
|
|
290
325
|
* Includes completed gate waits (requested_at → approved_at) and
|
|
@@ -450,6 +485,7 @@ export function discoverBuilders(workspaceRoot) {
|
|
|
450
485
|
idleMs: 0,
|
|
451
486
|
lastDataAt: null,
|
|
452
487
|
spawnedByArchitect: null,
|
|
488
|
+
prReady: false,
|
|
453
489
|
});
|
|
454
490
|
continue;
|
|
455
491
|
}
|
|
@@ -503,6 +539,7 @@ export function discoverBuilders(workspaceRoot) {
|
|
|
503
539
|
idleMs: computeIdleMs(parsed),
|
|
504
540
|
lastDataAt: null,
|
|
505
541
|
spawnedByArchitect: null,
|
|
542
|
+
prReady: derivePrReady(parsed),
|
|
506
543
|
});
|
|
507
544
|
found = true;
|
|
508
545
|
break;
|
|
@@ -535,6 +572,7 @@ export function discoverBuilders(workspaceRoot) {
|
|
|
535
572
|
idleMs: 0,
|
|
536
573
|
lastDataAt: null,
|
|
537
574
|
spawnedByArchitect: null,
|
|
575
|
+
prReady: false,
|
|
538
576
|
});
|
|
539
577
|
}
|
|
540
578
|
}
|