@cluesmith/codev 3.0.7 → 3.1.2
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-DUQ8fmjX.js → index-CM6A5r59.js} +39 -39
- package/dashboard-dist/assets/index-CM6A5r59.js.map +1 -0
- package/dashboard-dist/assets/index-Cl5tt1AP.css +32 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +14 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/attach.d.ts.map +1 -1
- package/dist/agent-farm/commands/attach.js +4 -1
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +4 -2
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +13 -2
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +54 -15
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +43 -14
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/stop.d.ts.map +1 -1
- package/dist/agent-farm/commands/stop.js +14 -8
- package/dist/agent-farm/commands/stop.js.map +1 -1
- package/dist/agent-farm/commands/workspace-remove-architect.d.ts +16 -0
- package/dist/agent-farm/commands/workspace-remove-architect.d.ts.map +1 -0
- package/dist/agent-farm/commands/workspace-remove-architect.js +40 -0
- package/dist/agent-farm/commands/workspace-remove-architect.js.map +1 -0
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +125 -2
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/migrate.d.ts +11 -2
- package/dist/agent-farm/db/migrate.d.ts.map +1 -1
- package/dist/agent-farm/db/migrate.js +24 -5
- package/dist/agent-farm/db/migrate.js.map +1 -1
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +15 -2
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +1 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts +26 -0
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +31 -12
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts +25 -0
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +314 -49
- 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 +241 -6
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +8 -0
- 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 +76 -12
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-types.d.ts +24 -0
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
- package/dist/agent-farm/servers/worktree-config-watcher.d.ts +36 -0
- package/dist/agent-farm/servers/worktree-config-watcher.d.ts.map +1 -0
- package/dist/agent-farm/servers/worktree-config-watcher.js +80 -0
- package/dist/agent-farm/servers/worktree-config-watcher.js.map +1 -0
- package/dist/agent-farm/state.d.ts +59 -16
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +137 -34
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +23 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/architect-name.d.ts +5 -0
- package/dist/agent-farm/utils/architect-name.d.ts.map +1 -1
- package/dist/agent-farm/utils/architect-name.js +8 -0
- package/dist/agent-farm/utils/architect-name.js.map +1 -1
- package/dist/agent-farm/utils/config.d.ts +2 -12
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +20 -0
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/consult/index.d.ts +4 -0
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +160 -31
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/porch/artifacts.d.ts +32 -0
- package/dist/commands/porch/artifacts.d.ts.map +1 -1
- package/dist/commands/porch/artifacts.js +129 -0
- package/dist/commands/porch/artifacts.js.map +1 -1
- package/dist/lib/config.d.ts +17 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +30 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/default-branch.d.ts +11 -0
- package/dist/lib/default-branch.d.ts.map +1 -0
- package/dist/lib/default-branch.js +23 -0
- package/dist/lib/default-branch.js.map +1 -0
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +5 -0
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/shellper-client.d.ts +15 -0
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +37 -0
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +1 -0
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +8 -0
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +12 -0
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/package.json +3 -3
- package/scripts/forge/github/pr-search.sh +2 -2
- package/skeleton/protocols/air/consult-types/pr-review.md +5 -1
- package/skeleton/protocols/aspir/builder-prompt.md +3 -3
- package/skeleton/protocols/aspir/consult-types/pr-review.md +5 -1
- package/skeleton/protocols/bugfix/consult-types/pr-review.md +2 -1
- package/skeleton/protocols/maintain/consult-types/pr-review.md +5 -1
- package/skeleton/protocols/pir/builder-prompt.md +2 -2
- package/skeleton/protocols/pir/consult-types/pr-review.md +3 -2
- package/skeleton/protocols/pir/prompts/implement.md +15 -7
- package/skeleton/protocols/pir/prompts/review.md +27 -21
- package/skeleton/protocols/pir/protocol.json +1 -1
- package/skeleton/protocols/pir/protocol.md +4 -6
- package/skeleton/protocols/spir/builder-prompt.md +3 -3
- package/skeleton/protocols/spir/consult-types/pr-review.md +5 -1
- package/skeleton/roles/builder.md +21 -0
- package/skeleton/templates/AGENTS.md +20 -0
- package/skeleton/templates/CLAUDE.md +20 -0
- package/dashboard-dist/assets/index-0yEACH2F.css +0 -32
- package/dashboard-dist/assets/index-DUQ8fmjX.js.map +0 -1
|
@@ -22,17 +22,20 @@ import { readCloudConfig } from '../lib/cloud-config.js';
|
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { version } from '../../version.js';
|
|
24
24
|
const execAsync = promisify(exec);
|
|
25
|
+
import { getBuilders, setArchitectByName } from '../state.js';
|
|
25
26
|
import { DEFAULT_COLS, defaultSessionOptions } from '../../terminal/index.js';
|
|
26
27
|
import { parseJsonBody, isRequestAllowed } from '../utils/server-utils.js';
|
|
27
28
|
import { isRateLimited, normalizeWorkspacePath, getLanguageForExt, getMimeTypeForFile, serveStaticFile, } from './tower-utils.js';
|
|
28
29
|
import { handleTunnelEndpoint } from './tower-tunnel.js';
|
|
30
|
+
import { getWorktreeConfig } from '../utils/config.js';
|
|
31
|
+
import { ensureWorktreeConfigWatcher } from './worktree-config-watcher.js';
|
|
29
32
|
import { hasTeam, loadTeamMembers, loadMessages } from '../../lib/team.js';
|
|
30
33
|
import { fetchTeamGitHubData } from '../../lib/team-github.js';
|
|
31
34
|
import { resolveTarget, broadcastMessage, isResolveError } from './tower-messages.js';
|
|
32
35
|
import { formatArchitectMessage, formatBuilderMessage } from '../utils/message-format.js';
|
|
33
36
|
import { SendBuffer } from './send-buffer.js';
|
|
34
37
|
import { writeMessageToSession } from './message-write.js';
|
|
35
|
-
import { getKnownWorkspacePaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShellper, stopInstance, addArchitect, } from './tower-instances.js';
|
|
38
|
+
import { getKnownWorkspacePaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShellper, stopInstance, addArchitect, removeArchitect, } from './tower-instances.js';
|
|
36
39
|
import { OverviewCache } from './overview.js';
|
|
37
40
|
import { fetchIssue } from '../../lib/github.js';
|
|
38
41
|
import { computeAnalytics } from './analytics.js';
|
|
@@ -66,8 +69,9 @@ const ROUTES = {
|
|
|
66
69
|
'POST /api/terminals': (req, res, _url, ctx) => handleTerminalCreate(req, res, ctx),
|
|
67
70
|
'GET /api/terminals': (_req, res) => handleTerminalList(res),
|
|
68
71
|
'GET /api/status': (_req, res) => handleStatus(res),
|
|
69
|
-
'GET /api/overview': (_req, res, url) => handleOverview(res, url),
|
|
72
|
+
'GET /api/overview': (_req, res, url, ctx) => handleOverview(res, url, undefined, ctx),
|
|
70
73
|
'GET /api/issue': (_req, res, url) => handleIssueView(res, url),
|
|
74
|
+
'GET /api/worktree-config': (_req, res, url) => handleWorktreeConfigView(res, url),
|
|
71
75
|
'GET /api/analytics': (_req, res, url) => handleAnalytics(res, url),
|
|
72
76
|
'POST /api/overview/refresh': (_req, res, _url, ctx) => handleOverviewRefresh(res, ctx),
|
|
73
77
|
'GET /api/events': (req, res, _url, ctx) => handleSSEEvents(req, res, ctx),
|
|
@@ -129,7 +133,12 @@ export async function handleRequest(req, res, ctx) {
|
|
|
129
133
|
// Workspace API: /api/workspaces/:encodedPath/architects (Spec 755 — multi-architect)
|
|
130
134
|
const architectsMatch = url.pathname.match(/^\/api\/workspaces\/([^/]+)\/architects$/);
|
|
131
135
|
if (architectsMatch) {
|
|
132
|
-
return await handleAddArchitect(req, res, architectsMatch);
|
|
136
|
+
return await handleAddArchitect(req, res, architectsMatch, ctx);
|
|
137
|
+
}
|
|
138
|
+
// Workspace API: DELETE /api/workspaces/:encodedPath/architects/:name (Spec 786)
|
|
139
|
+
const architectRemoveMatch = url.pathname.match(/^\/api\/workspaces\/([^/]+)\/architects\/([^/]+)$/);
|
|
140
|
+
if (architectRemoveMatch) {
|
|
141
|
+
return await handleRemoveArchitect(req, res, architectRemoveMatch, ctx);
|
|
133
142
|
}
|
|
134
143
|
// Terminal-specific routes: /api/terminals/:id/* (Spec 0090 Phase 2)
|
|
135
144
|
const terminalRouteMatch = url.pathname.match(/^\/api\/terminals\/([^/]+)(\/.*)?$/);
|
|
@@ -190,7 +199,7 @@ async function handleListWorkspaces(res) {
|
|
|
190
199
|
* Returns 200 { success: true, name, terminalId } on success,
|
|
191
200
|
* 400 / 404 with { success: false, error } otherwise.
|
|
192
201
|
*/
|
|
193
|
-
async function handleAddArchitect(req, res, match) {
|
|
202
|
+
async function handleAddArchitect(req, res, match, ctx) {
|
|
194
203
|
if (req.method !== 'POST') {
|
|
195
204
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
196
205
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
@@ -221,6 +230,16 @@ async function handleAddArchitect(req, res, match) {
|
|
|
221
230
|
}
|
|
222
231
|
const result = await addArchitect(workspacePath, body.name);
|
|
223
232
|
if (result.success) {
|
|
233
|
+
// Spec 823: emit an `architects-updated` SSE event so VSCode's
|
|
234
|
+
// WorkspaceProvider tree refreshes when the add happens via the CLI
|
|
235
|
+
// (today the tree only refreshes when add is triggered from within
|
|
236
|
+
// VSCode itself). Mirrors `worktree-config-updated`'s broadcast shape.
|
|
237
|
+
ctx.broadcastNotification({
|
|
238
|
+
type: 'architects-updated',
|
|
239
|
+
title: 'Architects updated',
|
|
240
|
+
body: JSON.stringify({ workspace: workspacePath }),
|
|
241
|
+
workspace: workspacePath,
|
|
242
|
+
});
|
|
224
243
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
225
244
|
res.end(JSON.stringify({ success: true, name: result.name, terminalId: result.terminalId }));
|
|
226
245
|
}
|
|
@@ -231,6 +250,59 @@ async function handleAddArchitect(req, res, match) {
|
|
|
231
250
|
res.end(JSON.stringify({ success: false, error: result.error }));
|
|
232
251
|
}
|
|
233
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* DELETE /api/workspaces/:encodedPath/architects/:name (Spec 786)
|
|
255
|
+
*
|
|
256
|
+
* Removes a named sibling architect from an active workspace. Refuses to
|
|
257
|
+
* remove `main` (returns 400). Returns 404 when the workspace isn't active or
|
|
258
|
+
* the named architect isn't registered.
|
|
259
|
+
*
|
|
260
|
+
* Removing an architect with in-flight builders is allowed (per OQ-A) — the
|
|
261
|
+
* builders fall back to `main` via the existing `tower-messages.ts:336` chain.
|
|
262
|
+
*/
|
|
263
|
+
async function handleRemoveArchitect(req, res, match, ctx) {
|
|
264
|
+
if (req.method !== 'DELETE') {
|
|
265
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
266
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const [, encodedPath, encodedName] = match;
|
|
270
|
+
let workspacePath;
|
|
271
|
+
try {
|
|
272
|
+
workspacePath = decodeWorkspacePath(encodedPath);
|
|
273
|
+
if (!workspacePath || (!workspacePath.startsWith('/') && !/^[A-Za-z]:[\\/]/.test(workspacePath))) {
|
|
274
|
+
throw new Error('Invalid path');
|
|
275
|
+
}
|
|
276
|
+
workspacePath = normalizeWorkspacePath(workspacePath);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
280
|
+
res.end(JSON.stringify({ error: 'Invalid workspace path encoding' }));
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const name = decodeURIComponent(encodedName);
|
|
284
|
+
const result = await removeArchitect(workspacePath, name);
|
|
285
|
+
if (result.success) {
|
|
286
|
+
// Spec 823: emit `architects-updated` so VSCode's WorkspaceProvider
|
|
287
|
+
// refreshes when remove happens via CLI (the dashboard polls and
|
|
288
|
+
// doesn't need an explicit event; VSCode subscribes to this notification).
|
|
289
|
+
ctx.broadcastNotification({
|
|
290
|
+
type: 'architects-updated',
|
|
291
|
+
title: 'Architects updated',
|
|
292
|
+
body: JSON.stringify({ workspace: workspacePath }),
|
|
293
|
+
workspace: workspacePath,
|
|
294
|
+
});
|
|
295
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
296
|
+
res.end(JSON.stringify({ success: true }));
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// Distinguish "not registered" / "not running" (404) from validation
|
|
300
|
+
// errors like "Cannot remove main" (400).
|
|
301
|
+
const status = result.error?.includes('not running') || result.error?.includes('not found') ? 404 : 400;
|
|
302
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
303
|
+
res.end(JSON.stringify({ success: false, error: result.error }));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
234
306
|
async function handleWorkspaceAction(req, res, ctx, match) {
|
|
235
307
|
const [, encodedPath, action] = match;
|
|
236
308
|
let workspacePath;
|
|
@@ -578,7 +650,7 @@ async function handleStatus(res) {
|
|
|
578
650
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
579
651
|
res.end(JSON.stringify({ instances }));
|
|
580
652
|
}
|
|
581
|
-
async function handleOverview(res, url, workspaceOverride) {
|
|
653
|
+
async function handleOverview(res, url, workspaceOverride, ctx) {
|
|
582
654
|
// Accept workspace from: explicit override (workspace-scoped route), ?workspace= param, or first known path.
|
|
583
655
|
let workspaceRoot = workspaceOverride || url.searchParams.get('workspace');
|
|
584
656
|
if (!workspaceRoot) {
|
|
@@ -601,6 +673,29 @@ async function handleOverview(res, url, workspaceOverride) {
|
|
|
601
673
|
activeBuilderRoleIds.add(key.toLowerCase());
|
|
602
674
|
}
|
|
603
675
|
const data = await overviewCache.getOverview(workspaceRoot, activeBuilderRoleIds);
|
|
676
|
+
// Enrich each builder with `lastDataAt` — the wall-clock time Tower
|
|
677
|
+
// last received a DATA frame from that builder's shellper. The UI uses
|
|
678
|
+
// this to flag builders silent past a threshold as "waiting for input"
|
|
679
|
+
// (separate from formal porch gates, which `blocked` already covers).
|
|
680
|
+
//
|
|
681
|
+
// Pattern mirrors /api/state's builder loop (around line 1572): iterate
|
|
682
|
+
// `entry.builders` (Map<roleId, PtySession ID>), look up the session,
|
|
683
|
+
// do the work. Discovery already stamps `roleId` on each builder, so
|
|
684
|
+
// matching the runtime registry back to the discovery list is a single
|
|
685
|
+
// `find`. Builders with no attached session keep `lastDataAt = null`
|
|
686
|
+
// (the discover-time default).
|
|
687
|
+
const terminalManager = getTerminalManager();
|
|
688
|
+
for (const [builderId, terminalId] of entry.builders) {
|
|
689
|
+
const ptySession = terminalManager.getSession(terminalId);
|
|
690
|
+
if (!ptySession) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const builder = data.builders.find(b => b.roleId === builderId.toLowerCase());
|
|
694
|
+
if (!builder) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
builder.lastDataAt = new Date(ptySession.lastDataAt).toISOString();
|
|
698
|
+
}
|
|
604
699
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
605
700
|
res.end(JSON.stringify(data));
|
|
606
701
|
}
|
|
@@ -628,6 +723,42 @@ async function handleIssueView(res, url) {
|
|
|
628
723
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
629
724
|
res.end(JSON.stringify(issue));
|
|
630
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* GET /api/worktree-config — returns the canonical `ResolvedWorktreeConfig`
|
|
728
|
+
* for the requested workspace (defaults / cache / global / project /
|
|
729
|
+
* project-local, deep-merged per `lib/config.ts:loadConfig`). This is
|
|
730
|
+
* the single source of truth for any client that needs to act on
|
|
731
|
+
* worktree config (currently the VSCode extension's "Open Dev URL"
|
|
732
|
+
* surface; the dashboard is welcome to use it too).
|
|
733
|
+
*
|
|
734
|
+
* Side effect: lazily installs a directory watcher on the workspace's
|
|
735
|
+
* `.codev/` so any subsequent edit to `config.json` /
|
|
736
|
+
* `config.local.json` fans out a `worktree-config-updated` SSE event
|
|
737
|
+
* — clients refetch via this same endpoint and re-render.
|
|
738
|
+
*/
|
|
739
|
+
function handleWorktreeConfigView(res, url) {
|
|
740
|
+
let workspaceRoot = url.searchParams.get('workspace');
|
|
741
|
+
if (!workspaceRoot) {
|
|
742
|
+
const knownPaths = getKnownWorkspacePaths();
|
|
743
|
+
workspaceRoot = knownPaths.find(p => !p.includes('/.builders/')) || null;
|
|
744
|
+
}
|
|
745
|
+
if (!workspaceRoot) {
|
|
746
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
747
|
+
res.end(JSON.stringify({ error: 'Missing workspace' }));
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
const config = getWorktreeConfig(workspaceRoot);
|
|
752
|
+
ensureWorktreeConfigWatcher(workspaceRoot);
|
|
753
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
754
|
+
res.end(JSON.stringify(config));
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
758
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
759
|
+
res.end(JSON.stringify({ error: `Failed to resolve worktree config: ${message}` }));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
631
762
|
function handleOverviewRefresh(res, ctx) {
|
|
632
763
|
overviewCache.invalidate();
|
|
633
764
|
// Bugfix #388: Broadcast SSE event so all connected dashboard clients
|
|
@@ -1082,6 +1213,36 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
1082
1213
|
if (req.method === 'POST' && apiPath === 'stop') {
|
|
1083
1214
|
return handleWorkspaceStopAll(res, workspacePath);
|
|
1084
1215
|
}
|
|
1216
|
+
// DELETE /api/architects/:name - Remove a sibling architect (Spec 786 Phase 4)
|
|
1217
|
+
// Workspace-scoped variant of /api/workspaces/:encoded/architects/:name —
|
|
1218
|
+
// the workspace path comes from the /workspace/<base64>/ URL prefix already
|
|
1219
|
+
// resolved above. Used by the dashboard's close-button → confirmation
|
|
1220
|
+
// modal flow.
|
|
1221
|
+
const archDeleteMatch = apiPath.match(/^architects\/([^/]+)$/);
|
|
1222
|
+
if (req.method === 'DELETE' && archDeleteMatch) {
|
|
1223
|
+
const name = decodeURIComponent(archDeleteMatch[1]);
|
|
1224
|
+
const result = await removeArchitect(workspacePath, name);
|
|
1225
|
+
if (result.success) {
|
|
1226
|
+
// Spec 823 Phase 4 (iter-1 Codex): emit architects-updated from
|
|
1227
|
+
// every successful remove path, not just the /api/workspaces/
|
|
1228
|
+
// route, so the VSCode tree refreshes when the dashboard's
|
|
1229
|
+
// close-button → confirmation modal triggers the remove.
|
|
1230
|
+
ctx.broadcastNotification({
|
|
1231
|
+
type: 'architects-updated',
|
|
1232
|
+
title: 'Architects updated',
|
|
1233
|
+
body: JSON.stringify({ workspace: workspacePath }),
|
|
1234
|
+
workspace: workspacePath,
|
|
1235
|
+
});
|
|
1236
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1237
|
+
res.end(JSON.stringify({ success: true }));
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
const status = result.error?.includes('not running') || result.error?.includes('not found') ? 404 : 400;
|
|
1241
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
1242
|
+
res.end(JSON.stringify({ success: false, error: result.error }));
|
|
1243
|
+
}
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1085
1246
|
// GET /api/files - Return workspace directory tree for file browser (Spec 0092)
|
|
1086
1247
|
if (req.method === 'GET' && apiPath === 'files') {
|
|
1087
1248
|
return handleWorkspaceFiles(res, url, workspacePath);
|
|
@@ -1156,7 +1317,7 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
1156
1317
|
}
|
|
1157
1318
|
// GET /api/overview - Work view overview data (Spec 0126 Phase 4)
|
|
1158
1319
|
if (req.method === 'GET' && apiPath === 'overview') {
|
|
1159
|
-
return handleOverview(res, url, workspacePath);
|
|
1320
|
+
return handleOverview(res, url, workspacePath, ctx);
|
|
1160
1321
|
}
|
|
1161
1322
|
// POST /api/overview/refresh - Invalidate overview cache (Spec 0126 Phase 4)
|
|
1162
1323
|
if (req.method === 'POST' && apiPath === 'overview/refresh') {
|
|
@@ -1260,6 +1421,23 @@ async function handleWorkspaceState(res, workspacePath) {
|
|
|
1260
1421
|
});
|
|
1261
1422
|
}
|
|
1262
1423
|
}
|
|
1424
|
+
// Spec 786 Phase 4: build a lookup from builder id → spawned_by_architect so
|
|
1425
|
+
// the dashboard's remove-architect confirmation modal can show which builders
|
|
1426
|
+
// would lose their spawning architect (informational; per OQ-A removal
|
|
1427
|
+
// proceeds regardless and they fall back to `main` routing). The data lives
|
|
1428
|
+
// in `state.db.builders.spawned_by_architect` but the in-memory cache used by
|
|
1429
|
+
// /api/state doesn't carry it — we read it explicitly here. Single query
|
|
1430
|
+
// amortised across all builders rather than per-builder lookups.
|
|
1431
|
+
const spawnedByMap = new Map();
|
|
1432
|
+
try {
|
|
1433
|
+
for (const b of getBuilders()) {
|
|
1434
|
+
spawnedByMap.set(b.id, b.spawnedByArchitect ?? null);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
catch {
|
|
1438
|
+
// DB unavailable — modal degrades to "no in-flight builders" display.
|
|
1439
|
+
// Acceptable since the modal text is informational per OQ-A.
|
|
1440
|
+
}
|
|
1263
1441
|
// Add builders from refreshed cache
|
|
1264
1442
|
for (const [builderId, terminalId] of entry.builders) {
|
|
1265
1443
|
const session = manager.getSession(terminalId);
|
|
@@ -1276,6 +1454,10 @@ async function handleWorkspaceState(res, workspacePath) {
|
|
|
1276
1454
|
type: 'spec',
|
|
1277
1455
|
terminalId,
|
|
1278
1456
|
persistent: isSessionPersistent(terminalId, session),
|
|
1457
|
+
// Spec 786 Phase 4: surface spawning architect to the dashboard so the
|
|
1458
|
+
// remove-architect modal can show affected builders. May be undefined
|
|
1459
|
+
// when the builder row isn't in state.db (e.g. ephemeral test builders).
|
|
1460
|
+
spawnedByArchitect: spawnedByMap.get(builderId) ?? null,
|
|
1279
1461
|
});
|
|
1280
1462
|
}
|
|
1281
1463
|
}
|
|
@@ -1614,6 +1796,36 @@ async function handleWorkspaceTabDelete(res, ctx, workspacePath, tabId) {
|
|
|
1614
1796
|
entry.architects.delete(name);
|
|
1615
1797
|
}
|
|
1616
1798
|
}
|
|
1799
|
+
else if (tabId.startsWith('architect:')) {
|
|
1800
|
+
// Spec 786 Phase 4 / PR iter-1 review fix: sibling architect tabs (Spec
|
|
1801
|
+
// 761 ids `architect:<name>`) are closable from the mobile TabBar, which
|
|
1802
|
+
// dispatches `DELETE /api/tabs/<tabId>`. Route the sibling close through
|
|
1803
|
+
// `removeArchitect()` so the full lifecycle (kills PTY, deletes state.db
|
|
1804
|
+
// row, suppresses cascaded delete via intentional-stop flag) runs the
|
|
1805
|
+
// same way as the desktop close button + CLI.
|
|
1806
|
+
const name = tabId.slice('architect:'.length);
|
|
1807
|
+
const result = await removeArchitect(workspacePath, name);
|
|
1808
|
+
if (result.success) {
|
|
1809
|
+
// Spec 823 Phase 4 (iter-1 Codex): emit architects-updated so the
|
|
1810
|
+
// VSCode tree refreshes when the remove originates from the mobile
|
|
1811
|
+
// TabBar close (which doesn't trigger the dashboard's add/remove
|
|
1812
|
+
// SSE event from within VSCode).
|
|
1813
|
+
ctx.broadcastNotification({
|
|
1814
|
+
type: 'architects-updated',
|
|
1815
|
+
title: 'Architects updated',
|
|
1816
|
+
body: JSON.stringify({ workspace: workspacePath }),
|
|
1817
|
+
workspace: workspacePath,
|
|
1818
|
+
});
|
|
1819
|
+
res.writeHead(204);
|
|
1820
|
+
res.end();
|
|
1821
|
+
}
|
|
1822
|
+
else {
|
|
1823
|
+
const status = result.error?.includes('not found') || result.error?.includes('not running') ? 404 : 400;
|
|
1824
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
1825
|
+
res.end(JSON.stringify({ success: false, error: result.error }));
|
|
1826
|
+
}
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1617
1829
|
if (terminalId) {
|
|
1618
1830
|
// Disable shellper auto-restart if applicable, then kill the PtySession
|
|
1619
1831
|
await killTerminalWithShellper(manager, terminalId);
|
|
@@ -1630,6 +1842,29 @@ async function handleWorkspaceTabDelete(res, ctx, workspacePath, tabId) {
|
|
|
1630
1842
|
async function handleWorkspaceStopAll(res, workspacePath) {
|
|
1631
1843
|
const entry = getWorkspaceTerminalsEntry(workspacePath);
|
|
1632
1844
|
const manager = getTerminalManager();
|
|
1845
|
+
// Spec 786 PR iter-2 race-fix (Codex): explicitly delete every architect's
|
|
1846
|
+
// `state.db.architect` row BEFORE killing or clearing the registry. The
|
|
1847
|
+
// cascaded architect exit handlers in tower-instances.ts (lines 452-...,
|
|
1848
|
+
// 501-..., etc.) and tower-terminals.ts work by scanning
|
|
1849
|
+
// `currentEntry.architects` to recover the architect name from a dead
|
|
1850
|
+
// terminal id. But stop-all clears that registry synchronously after the
|
|
1851
|
+
// kills, before node-pty's async 'exit' events fire — so by the time the
|
|
1852
|
+
// exit handlers look up the name, it's already gone, and they silently
|
|
1853
|
+
// skip the row deletion. Result: stale `state.db.architect` rows survive
|
|
1854
|
+
// what's supposed to be a full wipe, and `launchInstance` reconciliation
|
|
1855
|
+
// re-spawns them on the next workspace start.
|
|
1856
|
+
//
|
|
1857
|
+
// The intentional-stop flag isn't the right tool here either — stop-all
|
|
1858
|
+
// explicitly wants the rows gone. Pre-emptive deletion makes it
|
|
1859
|
+
// explicit and order-independent: even if the exit handler somehow runs
|
|
1860
|
+
// first, `setArchitectByName(name, null)` is idempotent.
|
|
1861
|
+
for (const name of entry.architects.keys()) {
|
|
1862
|
+
try {
|
|
1863
|
+
// Bugfix #826: scoped by workspace_path.
|
|
1864
|
+
setArchitectByName(workspacePath, name, null);
|
|
1865
|
+
}
|
|
1866
|
+
catch { /* best-effort cleanup */ }
|
|
1867
|
+
}
|
|
1633
1868
|
// Kill all terminals (disable shellper auto-restart if applicable).
|
|
1634
1869
|
// Spec 755: iterate all named architects, not just the singleton.
|
|
1635
1870
|
for (const terminalId of entry.architects.values()) {
|