@cluesmith/codev 3.2.3 → 3.2.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/dashboard-dist/assets/index-BZ1x7x1X.js.map +1 -1
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +31 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/attach.js +2 -2
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +2 -2
- package/dist/agent-farm/commands/send.js +3 -3
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/setup.d.ts +4 -1
- package/dist/agent-farm/commands/setup.d.ts.map +1 -1
- package/dist/agent-farm/commands/setup.js +8 -4
- package/dist/agent-farm/commands/setup.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +11 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +64 -16
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +26 -2
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/tower-sweep-husks.d.ts +6 -0
- package/dist/agent-farm/commands/tower-sweep-husks.d.ts.map +1 -0
- package/dist/agent-farm/commands/tower-sweep-husks.js +77 -0
- package/dist/agent-farm/commands/tower-sweep-husks.js.map +1 -0
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +34 -0
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/commands/whoami.d.ts +65 -0
- package/dist/agent-farm/commands/whoami.d.ts.map +1 -0
- package/dist/agent-farm/commands/whoami.js +196 -0
- package/dist/agent-farm/commands/whoami.js.map +1 -0
- package/dist/agent-farm/lib/tower-client.d.ts +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/servers/architect-session-holder.d.ts +133 -0
- package/dist/agent-farm/servers/architect-session-holder.d.ts.map +1 -0
- package/dist/agent-farm/servers/architect-session-holder.js +268 -0
- package/dist/agent-farm/servers/architect-session-holder.js.map +1 -0
- package/dist/agent-farm/servers/process-census.d.ts +41 -0
- package/dist/agent-farm/servers/process-census.d.ts.map +1 -0
- package/dist/agent-farm/servers/process-census.js +62 -0
- package/dist/agent-farm/servers/process-census.js.map +1 -0
- package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
- package/dist/agent-farm/servers/send-buffer.js +13 -0
- package/dist/agent-farm/servers/send-buffer.js.map +1 -1
- package/dist/agent-farm/servers/session-log-sweep.d.ts +93 -0
- package/dist/agent-farm/servers/session-log-sweep.d.ts.map +1 -0
- package/dist/agent-farm/servers/session-log-sweep.js +165 -0
- package/dist/agent-farm/servers/session-log-sweep.js.map +1 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.d.ts +87 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.d.ts.map +1 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.js +129 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.js.map +1 -0
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +52 -5
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +147 -4
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +95 -1
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +38 -5
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +74 -5
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/state.d.ts +6 -1
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +5 -2
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/commands/adopt.d.ts +3 -2
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +7 -6
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/agy-auth-cache.d.ts +97 -0
- package/dist/commands/consult/agy-auth-cache.d.ts.map +1 -0
- package/dist/commands/consult/agy-auth-cache.js +320 -0
- package/dist/commands/consult/agy-auth-cache.js.map +1 -0
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +59 -0
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts +28 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +187 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +3 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +11 -8
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/update.d.ts +4 -4
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +9 -11
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/default-branch.d.ts +1 -1
- package/dist/lib/default-branch.js +1 -1
- package/dist/lib/gitignore.d.ts +25 -2
- package/dist/lib/gitignore.d.ts.map +1 -1
- package/dist/lib/gitignore.js +70 -0
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/migration-backup-audit.d.ts +64 -0
- package/dist/lib/migration-backup-audit.d.ts.map +1 -0
- package/dist/lib/migration-backup-audit.js +145 -0
- package/dist/lib/migration-backup-audit.js.map +1 -0
- package/dist/lib/protocol-drift-audit.d.ts +118 -0
- package/dist/lib/protocol-drift-audit.d.ts.map +1 -0
- package/dist/lib/protocol-drift-audit.js +217 -0
- package/dist/lib/protocol-drift-audit.js.map +1 -0
- package/dist/lib/scaffold.d.ts +10 -3
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +29 -26
- package/dist/lib/scaffold.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts +7 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +16 -0
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +25 -4
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +83 -11
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +76 -0
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +252 -44
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/shellper-client.d.ts +19 -4
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +70 -8
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +20 -5
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +40 -2
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js +72 -5
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/package.json +7 -7
- package/skeleton/.claude/skills/afx/SKILL.md +13 -0
- package/skeleton/.claude/skills/arch-init/SKILL.md +122 -0
- package/skeleton/.claude/skills/codev/SKILL.md +2 -2
- package/skeleton/.codex/skills/afx/SKILL.md +157 -0
- package/skeleton/.codex/skills/arch-init/SKILL.md +122 -0
- package/skeleton/.codex/skills/codev/SKILL.md +59 -0
- package/skeleton/.codex/skills/consult/SKILL.md +96 -0
- package/skeleton/.codex/skills/generate-image/SKILL.md +45 -0
- package/skeleton/.codex/skills/porch/SKILL.md +60 -0
- package/skeleton/.codex/skills/update-arch-docs/SKILL.md +132 -0
- package/skeleton/resources/commands/agent-farm.md +89 -0
- package/skeleton/resources/commands/codev.md +5 -0
- package/skeleton/resources/commands/consult.md +26 -0
- package/skeleton/roles/architect.md +11 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whoami command — report this terminal's agent identity (Spec 1134).
|
|
3
|
+
*
|
|
4
|
+
* Answers "who am I, from Tower/global.db's perspective?" for any terminal:
|
|
5
|
+
* builders resolve from their worktree cwd against global.db, architects from
|
|
6
|
+
* the Tower-injected CODEV_ARCHITECT_NAME env var. Identity precedence is
|
|
7
|
+
* fixed by the spec: builder-worktree cwd match → CODEV_ARCHITECT_NAME →
|
|
8
|
+
* unknown (exit non-zero). There is deliberately NO implicit fallback to
|
|
9
|
+
* 'main' — an unverified identity misroutes downstream consumers (issue
|
|
10
|
+
* #1094), so unknown must fail loud.
|
|
11
|
+
*
|
|
12
|
+
* Read-only invariant: this command never opens global.db read-write. Builder
|
|
13
|
+
* resolution reuses detectCurrentBuilderId() (its own read-only connection);
|
|
14
|
+
* all whoami-specific queries share one read-only connection opened here.
|
|
15
|
+
*
|
|
16
|
+
* The identity helpers are imported from ./send.js rather than relocated:
|
|
17
|
+
* they are already exported and tested there, and whoami is only their second
|
|
18
|
+
* consumer (extraction deferred until a third appears).
|
|
19
|
+
*/
|
|
20
|
+
import { basename } from 'node:path';
|
|
21
|
+
import Database from 'better-sqlite3';
|
|
22
|
+
import { detectCurrentBuilderId, detectWorkspaceRoot, BuilderIdResolutionError } from './send.js';
|
|
23
|
+
import { lookupBuilderSpawningArchitect } from '../state.js';
|
|
24
|
+
import { getGlobalDbPath } from '../db/index.js';
|
|
25
|
+
import { normalizeWorkspacePath } from '../utils/workspace-path.js';
|
|
26
|
+
/** Thrown when no identity signal resolves. Message is the user-facing text. */
|
|
27
|
+
export class WhoamiError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'WhoamiError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Open global.db read-only, or return null when it is missing/unopenable.
|
|
35
|
+
* Null degrades gracefully: the informational lookups (workspace display
|
|
36
|
+
* name, spawning architect, architect-row cross-check) are skipped. Identity
|
|
37
|
+
* itself never depends on this handle — the builder path already verified
|
|
38
|
+
* against the db inside detectCurrentBuilderId(), and the architect path is
|
|
39
|
+
* env-based.
|
|
40
|
+
*/
|
|
41
|
+
function openReadonlyGlobalDb() {
|
|
42
|
+
try {
|
|
43
|
+
return new Database(getGlobalDbPath(), { readonly: true });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Display name for the workspace: the `known_workspaces` registry entry when
|
|
51
|
+
* present, else the directory basename. Informational context only — the
|
|
52
|
+
* spec's fail-loud rule applies to type/name, not this field.
|
|
53
|
+
*/
|
|
54
|
+
function workspaceDisplayName(workspaceRoot, db) {
|
|
55
|
+
const root = workspaceRoot ?? process.cwd();
|
|
56
|
+
if (db) {
|
|
57
|
+
try {
|
|
58
|
+
const row = db
|
|
59
|
+
.prepare('SELECT name FROM known_workspaces WHERE workspace_path = ?')
|
|
60
|
+
.get(normalizeWorkspacePath(root));
|
|
61
|
+
if (row?.name)
|
|
62
|
+
return row.name;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Table missing or query failure — informational field, fall through.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return basename(root);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the current terminal's identity per the spec precedence.
|
|
72
|
+
*
|
|
73
|
+
* Throws `BuilderIdResolutionError` when cwd IS a builder worktree whose
|
|
74
|
+
* identity cannot be verified (no fallthrough to the env check — #1094), and
|
|
75
|
+
* `WhoamiError` when no identity signal resolves at all.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveIdentity(env = process.env) {
|
|
78
|
+
// 1. Builder-worktree cwd match. A BuilderIdResolutionError propagates
|
|
79
|
+
// verbatim: inside a confirmed worktree, an unverifiable identity is an
|
|
80
|
+
// error, never a reason to consult the env var.
|
|
81
|
+
const builderId = detectCurrentBuilderId();
|
|
82
|
+
const workspaceRoot = detectWorkspaceRoot();
|
|
83
|
+
const db = openReadonlyGlobalDb();
|
|
84
|
+
try {
|
|
85
|
+
if (builderId) {
|
|
86
|
+
const identity = {
|
|
87
|
+
type: 'builder',
|
|
88
|
+
workspace: workspaceDisplayName(workspaceRoot, db),
|
|
89
|
+
name: builderId,
|
|
90
|
+
};
|
|
91
|
+
if (db) {
|
|
92
|
+
try {
|
|
93
|
+
const spawnedBy = lookupBuilderSpawningArchitect(builderId, workspaceRoot ?? undefined, db);
|
|
94
|
+
if (spawnedBy)
|
|
95
|
+
identity.architect = spawnedBy;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Enrichment only — the identity is already verified; a lookup
|
|
99
|
+
// failure means "not recorded", not "unknown identity".
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return identity;
|
|
103
|
+
}
|
|
104
|
+
// 2. Tower-injected architect env var. Read directly, NOT via
|
|
105
|
+
// currentArchitectName() — its default of 'main' is exactly the
|
|
106
|
+
// implicit fallback whoami must not have.
|
|
107
|
+
const architectName = env.CODEV_ARCHITECT_NAME?.trim();
|
|
108
|
+
if (architectName) {
|
|
109
|
+
const identity = {
|
|
110
|
+
type: 'architect',
|
|
111
|
+
workspace: workspaceDisplayName(workspaceRoot, db),
|
|
112
|
+
name: architectName,
|
|
113
|
+
};
|
|
114
|
+
// Best-effort cross-check against the architect table (diagnostics
|
|
115
|
+
// only): a missing row is a warning, never a failure — rows are
|
|
116
|
+
// legitimately absent in the crash-recovery scenarios whoami serves.
|
|
117
|
+
if (db && workspaceRoot) {
|
|
118
|
+
try {
|
|
119
|
+
const row = db
|
|
120
|
+
.prepare('SELECT 1 AS present FROM architect WHERE workspace_path = ? AND id = ?')
|
|
121
|
+
.get(normalizeWorkspacePath(workspaceRoot), architectName);
|
|
122
|
+
if (!row)
|
|
123
|
+
identity.rowMissing = true;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Swallowed by design — this check never affects output or exit code.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return identity;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
db?.close();
|
|
134
|
+
}
|
|
135
|
+
// 3. Fail loud. No implicit 'main'.
|
|
136
|
+
throw new WhoamiError('Cannot determine agent identity: the current directory is not inside a ' +
|
|
137
|
+
'.builders/<id>/ worktree and CODEV_ARCHITECT_NAME is not set. ' +
|
|
138
|
+
'Likely causes: a plain shell, a terminal not started by Tower, or a ' +
|
|
139
|
+
'pre-#786 architect terminal. Refusing to guess (issue #1094) — ' +
|
|
140
|
+
'identity must come from a verified signal, never a default.');
|
|
141
|
+
}
|
|
142
|
+
/** Format an identity as the human-readable `key: value` lines. */
|
|
143
|
+
export function formatIdentity(identity) {
|
|
144
|
+
const lines = [`workspace: ${identity.workspace}`, `type: ${identity.type}`, `name: ${identity.name}`];
|
|
145
|
+
if (identity.type === 'builder' && identity.architect) {
|
|
146
|
+
lines.push(`architect: ${identity.architect}`);
|
|
147
|
+
}
|
|
148
|
+
return lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
/** Format an identity as the JSON payload (`architect` omitted when unknown). */
|
|
151
|
+
export function identityToJson(identity) {
|
|
152
|
+
const payload = {
|
|
153
|
+
workspace: identity.workspace,
|
|
154
|
+
type: identity.type,
|
|
155
|
+
name: identity.name,
|
|
156
|
+
};
|
|
157
|
+
if (identity.type === 'builder' && identity.architect) {
|
|
158
|
+
payload.architect = identity.architect;
|
|
159
|
+
}
|
|
160
|
+
return payload;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* `afx whoami` — print this terminal's identity.
|
|
164
|
+
*
|
|
165
|
+
* Success: identity to stdout (text or --json), exit 0.
|
|
166
|
+
* Unknown identity: explanation to stderr (always), `{ "error": ... }` to
|
|
167
|
+
* stdout under --json, exit 1 via process.exitCode (streams flush).
|
|
168
|
+
*/
|
|
169
|
+
export async function whoami(options = {}) {
|
|
170
|
+
let identity;
|
|
171
|
+
try {
|
|
172
|
+
identity = resolveIdentity();
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (!(err instanceof WhoamiError) && !(err instanceof BuilderIdResolutionError))
|
|
176
|
+
throw err;
|
|
177
|
+
process.stderr.write(`${err.message}\n`);
|
|
178
|
+
if (options.json) {
|
|
179
|
+
process.stdout.write(`${JSON.stringify({ error: err.message })}\n`);
|
|
180
|
+
}
|
|
181
|
+
process.exitCode = 1;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (identity.type === 'architect' && identity.rowMissing) {
|
|
185
|
+
process.stderr.write(`warning: CODEV_ARCHITECT_NAME='${identity.name}' but no matching architect row ` +
|
|
186
|
+
`in global.db for this workspace (Tower may not have registered this terminal, ` +
|
|
187
|
+
`or it predates a crash). Identity is reported from the env signal.\n`);
|
|
188
|
+
}
|
|
189
|
+
if (options.json) {
|
|
190
|
+
process.stdout.write(`${JSON.stringify(identityToJson(identity))}\n`);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
process.stdout.write(`${formatIdentity(identity)}\n`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../../src/agent-farm/commands/whoami.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* All implementation lives in packages/core/src/tower-client.ts.
|
|
5
5
|
* This file preserves the import path for existing consumers.
|
|
6
6
|
*/
|
|
7
|
-
export { TowerClient, getTowerClient, type TowerClientOptions, type TowerWorkspace, type TowerWorkspaceStatus, type TowerHealth, type TowerTunnelStatus, type TowerStatus, type TowerTerminal, } from '@cluesmith/codev-core/tower-client';
|
|
7
|
+
export { TowerClient, getTowerClient, type TowerClientOptions, type TowerWorkspace, type TowerWorkspaceStatus, type TowerHealth, type TowerTunnelStatus, type TowerStatus, type TowerTerminal, type HuskCandidate, type HuskPreview, type HuskSweepResult, } from '@cluesmith/codev-core/tower-client';
|
|
8
8
|
export { encodeWorkspacePath, decodeWorkspacePath } from '@cluesmith/codev-core/workspace';
|
|
9
9
|
export { DEFAULT_TOWER_PORT, AGENT_FARM_DIR } from '@cluesmith/codev-core/constants';
|
|
10
10
|
//# sourceMappingURL=tower-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tower-client.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/lib/tower-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,WAAW,EACX,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"tower-client.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/lib/tower-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,WAAW,EACX,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tower-client.js","sourceRoot":"","sources":["../../../src/agent-farm/lib/tower-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,WAAW,EACX,cAAc,
|
|
1
|
+
{"version":3,"file":"tower-client.js","sourceRoot":"","sources":["../../../src/agent-farm/lib/tower-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,WAAW,EACX,cAAc,GAWf,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1224: detect and reclaim live processes that hold an architect's
|
|
3
|
+
* conversation session id, so an architect launch never bakes a colliding
|
|
4
|
+
* `claude --resume <id>` into a permanent crash loop.
|
|
5
|
+
*
|
|
6
|
+
* Two holder classes were observed after a workspace restart (forensic timeline
|
|
7
|
+
* on the issue):
|
|
8
|
+
* 1. OUR OWN superseded shellper for the same architect identity — a remnant
|
|
9
|
+
* from before the restart, or a previous crash-looping instance. Its claude
|
|
10
|
+
* child dies within seconds of every respawn, so the child is absent most
|
|
11
|
+
* of the time; the durable evidence is the *shellper parent's* argv, which
|
|
12
|
+
* carries the session id in its JSON config (`"--session-id","<id>"`).
|
|
13
|
+
* 2. A FOREIGN process — e.g. an interactive `claude` the user started by hand
|
|
14
|
+
* on their own tty (holder case in incident 3). This must NEVER be touched:
|
|
15
|
+
* it is not ours to kill.
|
|
16
|
+
*
|
|
17
|
+
* The policy: reclaim (kill + resume) only OUR OWN superseded shellper, proven
|
|
18
|
+
* by shellper-main.js + matching session id + matching cwd + matching
|
|
19
|
+
* CODEV_ARCHITECT_NAME. Anything else that holds the id is foreign → mint fresh.
|
|
20
|
+
*/
|
|
21
|
+
export interface ProcessEntry {
|
|
22
|
+
pid: number;
|
|
23
|
+
/** The full joined argv (as `ps ... -o args=` reports it). */
|
|
24
|
+
cmdline: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The session-flag needle forms a live holder's argv can carry the id in:
|
|
28
|
+
* - `--session-id <id>` / `--resume <id>` — a claude CHILD's shell argv
|
|
29
|
+
* - `--session-id=<id>` / `--resume=<id>` — the `=`-joined shell variant
|
|
30
|
+
* - `"--session-id","<id>"` / `"--resume","<id>"` — the shellper PARENT's
|
|
31
|
+
* JSON config argv (Issue #1224: catches a remnant shellper whose child is
|
|
32
|
+
* dead between crash-loop respawns).
|
|
33
|
+
*/
|
|
34
|
+
export declare function sessionIdNeedles(sessionId: string): string[];
|
|
35
|
+
/** True when a process's argv references `sessionId` as a session-flag argument. */
|
|
36
|
+
export declare function cmdlineHoldsSession(cmdline: string, sessionId: string): boolean;
|
|
37
|
+
export interface ArchitectIdentity {
|
|
38
|
+
/** Absolute workspace path the shellper runs in (`cwd` in its config). */
|
|
39
|
+
workspacePath: string;
|
|
40
|
+
/** The architect name (shellper config env `CODEV_ARCHITECT_NAME`). */
|
|
41
|
+
architectName: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* True when `cmdline` is one of OUR OWN shellper processes for `identity`:
|
|
45
|
+
* a `shellper-main.js` whose JSON config carries the matching workspace cwd and
|
|
46
|
+
* `CODEV_ARCHITECT_NAME`. The cwd match tolerates symlink variants (macOS
|
|
47
|
+
* `/tmp` vs `/private/tmp`), mirroring verifySessionOwnership.
|
|
48
|
+
*
|
|
49
|
+
* This is the airtight-identity gate before any kill: if any field cannot be
|
|
50
|
+
* positively confirmed, this returns false and the caller must not reclaim.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isOwnArchitectShellper(cmdline: string, identity: ArchitectIdentity): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Snapshot every running process as {pid, cmdline}. `ps -ww` prevents argv
|
|
55
|
+
* truncation (the shellper config blob is large) on both BSD and coreutils.
|
|
56
|
+
* Throws on `ps` failure; callers decide how to degrade.
|
|
57
|
+
*/
|
|
58
|
+
export declare function listProcessEntries(): ProcessEntry[];
|
|
59
|
+
export interface SessionHolderClassification {
|
|
60
|
+
/** PIDs of OUR OWN superseded shellpers holding this session (safe to reap). */
|
|
61
|
+
reclaimable: number[];
|
|
62
|
+
/** True when some holder is NOT positively ours — must not be touched. */
|
|
63
|
+
foreign: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Classify every live holder of `sessionId` into reclaimable-own vs foreign.
|
|
67
|
+
* A holder is reclaimable only when it is one of our own architect shellpers
|
|
68
|
+
* (identity-matched); any other holder (a bare `claude`, a shellper for a
|
|
69
|
+
* different identity, an unparseable match) is foreign.
|
|
70
|
+
*
|
|
71
|
+
* On `ps` failure returns `{reclaimable: [], foreign: false}` — no positive
|
|
72
|
+
* evidence, so the caller resumes as it would have before this guard existed.
|
|
73
|
+
*/
|
|
74
|
+
export declare function classifyArchitectSessionHolder(opts: {
|
|
75
|
+
sessionId: string;
|
|
76
|
+
identity: ArchitectIdentity;
|
|
77
|
+
/** Exclude this pid (e.g. Tower's own) from consideration. */
|
|
78
|
+
selfPid?: number;
|
|
79
|
+
/** Test seam: override the process snapshot. */
|
|
80
|
+
list?: () => ProcessEntry[];
|
|
81
|
+
}): SessionHolderClassification;
|
|
82
|
+
/**
|
|
83
|
+
* Find PIDs of OUR OWN architect shellpers for `identity`, regardless of which
|
|
84
|
+
* session they hold. Used by remove-architect to reap a live-process-without-a-
|
|
85
|
+
* registry-row zombie (Issue #1224 symptom B) — there is no stored session id to
|
|
86
|
+
* key on, so identity (shellper-main.js + cwd + CODEV_ARCHITECT_NAME) is the key.
|
|
87
|
+
*/
|
|
88
|
+
export declare function findOwnArchitectShellpers(opts: {
|
|
89
|
+
identity: ArchitectIdentity;
|
|
90
|
+
selfPid?: number;
|
|
91
|
+
list?: () => ProcessEntry[];
|
|
92
|
+
}): number[];
|
|
93
|
+
/**
|
|
94
|
+
* Reap the given superseded-shellper PIDs: SIGTERM the process group, poll for
|
|
95
|
+
* death, then SIGKILL any survivor. Resolves once every pid is gone (or the
|
|
96
|
+
* SIGKILL grace elapses). The wait matters: the killed shellper's child must
|
|
97
|
+
* release the session lock before the caller's fresh `claude --resume` runs,
|
|
98
|
+
* else the collision recurs.
|
|
99
|
+
*
|
|
100
|
+
* Seams (`isAlive`, `kill`, `wait`) keep it unit-testable without real signals.
|
|
101
|
+
*/
|
|
102
|
+
export declare function reapShellpers(pids: number[], opts?: {
|
|
103
|
+
isAlive?: (pid: number) => boolean;
|
|
104
|
+
kill?: (pid: number, signal: NodeJS.Signals) => void;
|
|
105
|
+
wait?: (ms: number) => Promise<void>;
|
|
106
|
+
graceMs?: number;
|
|
107
|
+
killGraceMs?: number;
|
|
108
|
+
pollMs?: number;
|
|
109
|
+
}): Promise<void>;
|
|
110
|
+
export type ArchitectLogger = (level: 'INFO' | 'WARN' | 'ERROR', message: string) => void;
|
|
111
|
+
/**
|
|
112
|
+
* Reconcile a stored architect session id against the live process table before
|
|
113
|
+
* launch, resolving whether the caller may resume it.
|
|
114
|
+
*
|
|
115
|
+
* - A FOREIGN holder → `{ foreignHolder: true }`: the caller must mint fresh
|
|
116
|
+
* (the session is genuinely in use by something we must not touch).
|
|
117
|
+
* - Only OUR OWN superseded shellper(s) hold it → reap them and return
|
|
118
|
+
* `{ foreignHolder: false }`: the session is now free, resume it (mint-or-
|
|
119
|
+
* RECLAIM, preserving the conversation instead of abandoning it).
|
|
120
|
+
* - No holder → `{ foreignHolder: false }`: resume as usual.
|
|
121
|
+
*/
|
|
122
|
+
export declare function reconcileArchitectSessionHolder(opts: {
|
|
123
|
+
sessionId: string;
|
|
124
|
+
identity: ArchitectIdentity;
|
|
125
|
+
selfPid?: number;
|
|
126
|
+
log?: ArchitectLogger;
|
|
127
|
+
list?: () => ProcessEntry[];
|
|
128
|
+
reap?: (pids: number[]) => Promise<void>;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
foreignHolder: boolean;
|
|
131
|
+
reclaimedPids: number[];
|
|
132
|
+
}>;
|
|
133
|
+
//# sourceMappingURL=architect-session-holder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"architect-session-holder.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/architect-session-holder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAM5D;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAG/E;AAsBD,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAc5F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,EAAE,CAgBnD;AAED,MAAM,WAAW,2BAA2B;IAC1C,gFAAgF;IAChF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,YAAY,EAAE,CAAC;CAC7B,GAAG,2BAA2B,CAqB9B;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,YAAY,EAAE,CAAC;CAC7B,GAAG,MAAM,EAAE,CAcX;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;IACL,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;IACrD,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1F;;;;;;;;;;GAUG;AACH,wBAAsB,+BAA+B,CAAC,IAAI,EAAE;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,YAAY,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,GAAG,OAAO,CAAC;IAAE,aAAa,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA4B/D"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1224: detect and reclaim live processes that hold an architect's
|
|
3
|
+
* conversation session id, so an architect launch never bakes a colliding
|
|
4
|
+
* `claude --resume <id>` into a permanent crash loop.
|
|
5
|
+
*
|
|
6
|
+
* Two holder classes were observed after a workspace restart (forensic timeline
|
|
7
|
+
* on the issue):
|
|
8
|
+
* 1. OUR OWN superseded shellper for the same architect identity — a remnant
|
|
9
|
+
* from before the restart, or a previous crash-looping instance. Its claude
|
|
10
|
+
* child dies within seconds of every respawn, so the child is absent most
|
|
11
|
+
* of the time; the durable evidence is the *shellper parent's* argv, which
|
|
12
|
+
* carries the session id in its JSON config (`"--session-id","<id>"`).
|
|
13
|
+
* 2. A FOREIGN process — e.g. an interactive `claude` the user started by hand
|
|
14
|
+
* on their own tty (holder case in incident 3). This must NEVER be touched:
|
|
15
|
+
* it is not ours to kill.
|
|
16
|
+
*
|
|
17
|
+
* The policy: reclaim (kill + resume) only OUR OWN superseded shellper, proven
|
|
18
|
+
* by shellper-main.js + matching session id + matching cwd + matching
|
|
19
|
+
* CODEV_ARCHITECT_NAME. Anything else that holds the id is foreign → mint fresh.
|
|
20
|
+
*/
|
|
21
|
+
import { execFileSync } from 'node:child_process';
|
|
22
|
+
import { realpathSync } from 'node:fs';
|
|
23
|
+
/** Marker identifying a Codev shellper process in a `ps` command line. */
|
|
24
|
+
const SHELLPER_MARKER = 'shellper-main.js';
|
|
25
|
+
/**
|
|
26
|
+
* The session-flag needle forms a live holder's argv can carry the id in:
|
|
27
|
+
* - `--session-id <id>` / `--resume <id>` — a claude CHILD's shell argv
|
|
28
|
+
* - `--session-id=<id>` / `--resume=<id>` — the `=`-joined shell variant
|
|
29
|
+
* - `"--session-id","<id>"` / `"--resume","<id>"` — the shellper PARENT's
|
|
30
|
+
* JSON config argv (Issue #1224: catches a remnant shellper whose child is
|
|
31
|
+
* dead between crash-loop respawns).
|
|
32
|
+
*/
|
|
33
|
+
export function sessionIdNeedles(sessionId) {
|
|
34
|
+
return ['--session-id', '--resume'].flatMap((flag) => [
|
|
35
|
+
`${flag} ${sessionId}`,
|
|
36
|
+
`${flag}=${sessionId}`,
|
|
37
|
+
`"${flag}","${sessionId}"`,
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
/** True when a process's argv references `sessionId` as a session-flag argument. */
|
|
41
|
+
export function cmdlineHoldsSession(cmdline, sessionId) {
|
|
42
|
+
const needles = sessionIdNeedles(sessionId);
|
|
43
|
+
return needles.some((needle) => cmdline.includes(needle));
|
|
44
|
+
}
|
|
45
|
+
/** Canonicalize a path for comparison; fall back to the input when realpath fails. */
|
|
46
|
+
function realpathOrSelf(p) {
|
|
47
|
+
try {
|
|
48
|
+
return realpathSync(p);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract a `"key":"value"` string field from a shellper's JSON config as it
|
|
56
|
+
* appears verbatim in the `ps` argv. Regex rather than JSON.parse: the config
|
|
57
|
+
* embeds the full process env, so the blob is large and a partial/edge `ps`
|
|
58
|
+
* line must degrade to "field absent" rather than throw.
|
|
59
|
+
*/
|
|
60
|
+
function extractJsonStringField(cmdline, key) {
|
|
61
|
+
const m = cmdline.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
|
|
62
|
+
return m ? m[1] : null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* True when `cmdline` is one of OUR OWN shellper processes for `identity`:
|
|
66
|
+
* a `shellper-main.js` whose JSON config carries the matching workspace cwd and
|
|
67
|
+
* `CODEV_ARCHITECT_NAME`. The cwd match tolerates symlink variants (macOS
|
|
68
|
+
* `/tmp` vs `/private/tmp`), mirroring verifySessionOwnership.
|
|
69
|
+
*
|
|
70
|
+
* This is the airtight-identity gate before any kill: if any field cannot be
|
|
71
|
+
* positively confirmed, this returns false and the caller must not reclaim.
|
|
72
|
+
*/
|
|
73
|
+
export function isOwnArchitectShellper(cmdline, identity) {
|
|
74
|
+
if (!cmdline.includes(SHELLPER_MARKER))
|
|
75
|
+
return false;
|
|
76
|
+
const cfgName = extractJsonStringField(cmdline, 'CODEV_ARCHITECT_NAME');
|
|
77
|
+
if (cfgName !== identity.architectName)
|
|
78
|
+
return false;
|
|
79
|
+
const cfgCwd = extractJsonStringField(cmdline, 'cwd');
|
|
80
|
+
if (!cfgCwd)
|
|
81
|
+
return false;
|
|
82
|
+
const wanted = new Set([identity.workspacePath, realpathOrSelf(identity.workspacePath)]);
|
|
83
|
+
const got = new Set([cfgCwd, realpathOrSelf(cfgCwd)]);
|
|
84
|
+
const cwdMatches = [...got].some((g) => wanted.has(g));
|
|
85
|
+
if (!cwdMatches)
|
|
86
|
+
return false;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Snapshot every running process as {pid, cmdline}. `ps -ww` prevents argv
|
|
91
|
+
* truncation (the shellper config blob is large) on both BSD and coreutils.
|
|
92
|
+
* Throws on `ps` failure; callers decide how to degrade.
|
|
93
|
+
*/
|
|
94
|
+
export function listProcessEntries() {
|
|
95
|
+
const out = execFileSync('ps', ['-ww', '-eo', 'pid=,args='], {
|
|
96
|
+
encoding: 'utf-8',
|
|
97
|
+
timeout: 5000,
|
|
98
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
99
|
+
});
|
|
100
|
+
const entries = [];
|
|
101
|
+
for (const line of out.split('\n')) {
|
|
102
|
+
const trimmed = line.trimStart();
|
|
103
|
+
const sp = trimmed.indexOf(' ');
|
|
104
|
+
if (sp <= 0)
|
|
105
|
+
continue;
|
|
106
|
+
const pid = parseInt(trimmed.slice(0, sp), 10);
|
|
107
|
+
if (Number.isNaN(pid) || pid <= 0)
|
|
108
|
+
continue;
|
|
109
|
+
entries.push({ pid, cmdline: trimmed.slice(sp + 1) });
|
|
110
|
+
}
|
|
111
|
+
return entries;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Classify every live holder of `sessionId` into reclaimable-own vs foreign.
|
|
115
|
+
* A holder is reclaimable only when it is one of our own architect shellpers
|
|
116
|
+
* (identity-matched); any other holder (a bare `claude`, a shellper for a
|
|
117
|
+
* different identity, an unparseable match) is foreign.
|
|
118
|
+
*
|
|
119
|
+
* On `ps` failure returns `{reclaimable: [], foreign: false}` — no positive
|
|
120
|
+
* evidence, so the caller resumes as it would have before this guard existed.
|
|
121
|
+
*/
|
|
122
|
+
export function classifyArchitectSessionHolder(opts) {
|
|
123
|
+
const list = opts.list ?? listProcessEntries;
|
|
124
|
+
let entries;
|
|
125
|
+
try {
|
|
126
|
+
entries = list();
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return { reclaimable: [], foreign: false };
|
|
130
|
+
}
|
|
131
|
+
const reclaimable = [];
|
|
132
|
+
let foreign = false;
|
|
133
|
+
for (const { pid, cmdline } of entries) {
|
|
134
|
+
if (opts.selfPid !== undefined && pid === opts.selfPid)
|
|
135
|
+
continue;
|
|
136
|
+
if (!cmdlineHoldsSession(cmdline, opts.sessionId))
|
|
137
|
+
continue;
|
|
138
|
+
if (isOwnArchitectShellper(cmdline, opts.identity)) {
|
|
139
|
+
reclaimable.push(pid);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
foreign = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { reclaimable, foreign };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Find PIDs of OUR OWN architect shellpers for `identity`, regardless of which
|
|
149
|
+
* session they hold. Used by remove-architect to reap a live-process-without-a-
|
|
150
|
+
* registry-row zombie (Issue #1224 symptom B) — there is no stored session id to
|
|
151
|
+
* key on, so identity (shellper-main.js + cwd + CODEV_ARCHITECT_NAME) is the key.
|
|
152
|
+
*/
|
|
153
|
+
export function findOwnArchitectShellpers(opts) {
|
|
154
|
+
const list = opts.list ?? listProcessEntries;
|
|
155
|
+
let entries;
|
|
156
|
+
try {
|
|
157
|
+
entries = list();
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
const pids = [];
|
|
163
|
+
for (const { pid, cmdline } of entries) {
|
|
164
|
+
if (opts.selfPid !== undefined && pid === opts.selfPid)
|
|
165
|
+
continue;
|
|
166
|
+
if (isOwnArchitectShellper(cmdline, opts.identity))
|
|
167
|
+
pids.push(pid);
|
|
168
|
+
}
|
|
169
|
+
return pids;
|
|
170
|
+
}
|
|
171
|
+
/** Whether a pid is still alive (signal 0 probe). */
|
|
172
|
+
function pidAlive(pid) {
|
|
173
|
+
try {
|
|
174
|
+
process.kill(pid, 0);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
// ESRCH → gone; EPERM → alive but not ours to signal (treat as alive).
|
|
179
|
+
return err.code === 'EPERM';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Kill a shellper's whole process group (leader is detached), best-effort. */
|
|
183
|
+
function killProcessGroup(pid, signal) {
|
|
184
|
+
try {
|
|
185
|
+
process.kill(-pid, signal);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Group gone or not permitted — fall back to the single pid.
|
|
189
|
+
try {
|
|
190
|
+
process.kill(pid, signal);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
/* already dead */
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
198
|
+
/**
|
|
199
|
+
* Reap the given superseded-shellper PIDs: SIGTERM the process group, poll for
|
|
200
|
+
* death, then SIGKILL any survivor. Resolves once every pid is gone (or the
|
|
201
|
+
* SIGKILL grace elapses). The wait matters: the killed shellper's child must
|
|
202
|
+
* release the session lock before the caller's fresh `claude --resume` runs,
|
|
203
|
+
* else the collision recurs.
|
|
204
|
+
*
|
|
205
|
+
* Seams (`isAlive`, `kill`, `wait`) keep it unit-testable without real signals.
|
|
206
|
+
*/
|
|
207
|
+
export async function reapShellpers(pids, opts) {
|
|
208
|
+
if (pids.length === 0)
|
|
209
|
+
return;
|
|
210
|
+
const isAlive = opts?.isAlive ?? pidAlive;
|
|
211
|
+
const kill = opts?.kill ?? killProcessGroup;
|
|
212
|
+
const wait = opts?.wait ?? sleep;
|
|
213
|
+
const graceMs = opts?.graceMs ?? 3000;
|
|
214
|
+
const killGraceMs = opts?.killGraceMs ?? 2000;
|
|
215
|
+
const pollMs = opts?.pollMs ?? 100;
|
|
216
|
+
const pollUntilDead = async (deadlineMs) => {
|
|
217
|
+
let waited = 0;
|
|
218
|
+
while (waited < deadlineMs && pids.some((pid) => isAlive(pid))) {
|
|
219
|
+
await wait(pollMs);
|
|
220
|
+
waited += pollMs;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
for (const pid of pids)
|
|
224
|
+
kill(pid, 'SIGTERM');
|
|
225
|
+
await pollUntilDead(graceMs);
|
|
226
|
+
const survivors = pids.filter((pid) => isAlive(pid));
|
|
227
|
+
if (survivors.length > 0) {
|
|
228
|
+
for (const pid of survivors)
|
|
229
|
+
kill(pid, 'SIGKILL');
|
|
230
|
+
// Confirm SIGKILL actually took effect before returning: the caller resumes
|
|
231
|
+
// `claude --resume <id>` next, and a holder still in the kernel's exit path
|
|
232
|
+
// has not yet released the session lock — returning early would re-open the
|
|
233
|
+
// very collision race this reap exists to close (codex review, Issue #1224).
|
|
234
|
+
await pollUntilDead(killGraceMs);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Reconcile a stored architect session id against the live process table before
|
|
239
|
+
* launch, resolving whether the caller may resume it.
|
|
240
|
+
*
|
|
241
|
+
* - A FOREIGN holder → `{ foreignHolder: true }`: the caller must mint fresh
|
|
242
|
+
* (the session is genuinely in use by something we must not touch).
|
|
243
|
+
* - Only OUR OWN superseded shellper(s) hold it → reap them and return
|
|
244
|
+
* `{ foreignHolder: false }`: the session is now free, resume it (mint-or-
|
|
245
|
+
* RECLAIM, preserving the conversation instead of abandoning it).
|
|
246
|
+
* - No holder → `{ foreignHolder: false }`: resume as usual.
|
|
247
|
+
*/
|
|
248
|
+
export async function reconcileArchitectSessionHolder(opts) {
|
|
249
|
+
const { reclaimable, foreign } = classifyArchitectSessionHolder({
|
|
250
|
+
sessionId: opts.sessionId,
|
|
251
|
+
identity: opts.identity,
|
|
252
|
+
selfPid: opts.selfPid,
|
|
253
|
+
list: opts.list,
|
|
254
|
+
});
|
|
255
|
+
const shortId = opts.sessionId.slice(0, 8);
|
|
256
|
+
if (foreign) {
|
|
257
|
+
opts.log?.('WARN', `Architect '${opts.identity.architectName}' session ${shortId}… is held by a foreign process; minting a fresh session (holder left untouched) in ${opts.identity.workspacePath}`);
|
|
258
|
+
return { foreignHolder: true, reclaimedPids: [] };
|
|
259
|
+
}
|
|
260
|
+
if (reclaimable.length === 0) {
|
|
261
|
+
return { foreignHolder: false, reclaimedPids: [] };
|
|
262
|
+
}
|
|
263
|
+
opts.log?.('WARN', `Architect '${opts.identity.architectName}' session ${shortId}… is held by our own superseded shellper(s) [${reclaimable.join(', ')}]; reaping to reclaim the conversation in ${opts.identity.workspacePath}`);
|
|
264
|
+
const reap = opts.reap ?? ((pids) => reapShellpers(pids));
|
|
265
|
+
await reap(reclaimable);
|
|
266
|
+
return { foreignHolder: false, reclaimedPids: reclaimable };
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=architect-session-holder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"architect-session-holder.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/architect-session-holder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,0EAA0E;AAC1E,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAQ3C;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACpD,GAAG,IAAI,IAAI,SAAS,EAAE;QACtB,GAAG,IAAI,IAAI,SAAS,EAAE;QACtB,IAAI,IAAI,MAAM,SAAS,GAAG;KAC3B,CAAC,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,SAAiB;IACpE,MAAM,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,sFAAsF;AACtF,SAAS,cAAc,CAAC,CAAS;IAC/B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,sBAAsB,CAAC,OAAe,EAAE,GAAW;IAC1D,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,qBAAqB,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzB,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,QAA2B;IACjF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;QAAE,OAAO,KAAK,CAAC;IAErD,MAAM,OAAO,GAAG,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,CAAC,CAAC;IACxE,IAAI,OAAO,KAAK,QAAQ,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC;IAErD,MAAM,MAAM,GAAG,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IACzF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAE9B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE;QAC3D,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;KAC5B,CAAC,CAAC;IACH,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QACjC,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACtB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAAE,SAAS;QAC5C,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,8BAA8B,CAAC,IAO9C;IACC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,kBAAkB,CAAC;IAC7C,IAAI,OAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,EAAE,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,CAAC,OAAO;YAAE,SAAS;QACjE,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;YAAE,SAAS;QAC5D,IAAI,sBAAsB,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnD,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAIzC;IACC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,kBAAkB,CAAC;IAC7C,IAAI,OAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,EAAE,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,CAAC,OAAO;YAAE,SAAS;QACjE,IAAI,sBAAsB,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,qDAAqD;AACrD,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,uEAAuE;QACvE,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAS,gBAAgB,CAAC,GAAW,EAAE,MAAsB;IAC3D,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;QAC7D,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAc,EACd,IAOC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC9B,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,QAAQ,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,IAAI,gBAAgB,CAAC;IAC5C,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,IAAI,KAAK,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC;IACtC,MAAM,WAAW,GAAG,IAAI,EAAE,WAAW,IAAI,IAAI,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,GAAG,CAAC;IAEnC,MAAM,aAAa,GAAG,KAAK,EAAE,UAAkB,EAAiB,EAAE;QAChE,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,MAAM,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;YACnB,MAAM,IAAI,MAAM,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,GAAG,IAAI,SAAS;YAAE,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAClD,4EAA4E;QAC5E,4EAA4E;QAC5E,4EAA4E;QAC5E,6EAA6E;QAC7E,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAID;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAAC,IAOrD;IACC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,8BAA8B,CAAC;QAC9D,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;KAChB,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3C,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC,GAAG,EAAE,CACR,MAAM,EACN,cAAc,IAAI,CAAC,QAAQ,CAAC,aAAa,aAAa,OAAO,sFAAsF,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CACjL,CAAC;QACF,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;IACpD,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CACR,MAAM,EACN,cAAc,IAAI,CAAC,QAAQ,CAAC,aAAa,aAAa,OAAO,gDAAgD,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,6CAA6C,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAC9M,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAc,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IACpE,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC;IACxB,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;AAC9D,CAAC"}
|