@cluesmith/codev 2.0.3 → 2.0.7
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-BblS3DWL.js +135 -0
- package/dashboard/dist/assets/index-BblS3DWL.js.map +1 -0
- package/dashboard/dist/assets/index-Cr9PyjqX.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 +54 -61
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/architect.d.ts +5 -5
- package/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +37 -20
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/attach.d.ts +19 -0
- package/dist/agent-farm/commands/attach.d.ts.map +1 -1
- package/dist/agent-farm/commands/attach.js +169 -29
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts +12 -0
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +108 -7
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +22 -2
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +97 -178
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.d.ts +3 -9
- package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.js +14 -53
- package/dist/agent-farm/commands/spawn-roles.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +11 -18
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +35 -22
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts +8 -6
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +207 -89
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +2 -6
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +5 -35
- 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 +2 -6
- package/dist/agent-farm/commands/stop.js.map +1 -1
- package/dist/agent-farm/commands/tower-cloud.d.ts +2 -9
- package/dist/agent-farm/commands/tower-cloud.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower-cloud.js +12 -47
- package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +6 -23
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +52 -2
- package/dist/agent-farm/db/index.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 +1 -1
- package/dist/agent-farm/lib/cloud-config.d.ts +1 -0
- package/dist/agent-farm/lib/cloud-config.d.ts.map +1 -1
- package/dist/agent-farm/lib/cloud-config.js +2 -2
- package/dist/agent-farm/lib/cloud-config.js.map +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts +65 -6
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +57 -2
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts +157 -0
- package/dist/agent-farm/servers/overview.d.ts.map +1 -0
- package/dist/agent-farm/servers/overview.js +625 -0
- package/dist/agent-farm/servers/overview.js.map +1 -0
- package/dist/agent-farm/servers/tower-instances.d.ts +1 -3
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +12 -14
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-messages.d.ts +87 -0
- package/dist/agent-farm/servers/tower-messages.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-messages.js +202 -0
- package/dist/agent-farm/servers/tower-messages.js.map +1 -0
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +182 -34
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +30 -6
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +9 -3
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +129 -84
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +3 -19
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-types.d.ts +0 -2
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +27 -5
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/types.d.ts +4 -5
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.d.ts +85 -0
- package/dist/agent-farm/utils/agent-names.d.ts.map +1 -0
- package/dist/agent-farm/utils/agent-names.js +140 -0
- package/dist/agent-farm/utils/agent-names.js.map +1 -0
- package/dist/agent-farm/utils/display.d.ts +8 -0
- package/dist/agent-farm/utils/display.d.ts.map +1 -0
- package/dist/agent-farm/utils/display.js +26 -0
- package/dist/agent-farm/utils/display.js.map +1 -0
- package/dist/agent-farm/utils/message-format.d.ts +17 -0
- package/dist/agent-farm/utils/message-format.d.ts.map +1 -0
- package/dist/agent-farm/utils/message-format.js +41 -0
- package/dist/agent-farm/utils/message-format.js.map +1 -0
- package/dist/agent-farm/utils/notifications.d.ts.map +1 -1
- package/dist/agent-farm/utils/notifications.js +7 -16
- package/dist/agent-farm/utils/notifications.js.map +1 -1
- package/dist/agent-farm/utils/server-utils.d.ts +4 -0
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +20 -0
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/agent-farm/utils/shell.d.ts +5 -0
- package/dist/agent-farm/utils/shell.d.ts.map +1 -1
- package/dist/agent-farm/utils/shell.js +15 -11
- package/dist/agent-farm/utils/shell.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +46 -15
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +1 -13
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts +34 -9
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +617 -263
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/metrics.d.ts +90 -0
- package/dist/commands/consult/metrics.d.ts.map +1 -0
- package/dist/commands/consult/metrics.js +203 -0
- package/dist/commands/consult/metrics.js.map +1 -0
- package/dist/commands/consult/stats.d.ts +18 -0
- package/dist/commands/consult/stats.d.ts.map +1 -0
- package/dist/commands/consult/stats.js +150 -0
- package/dist/commands/consult/stats.js.map +1 -0
- package/dist/commands/consult/usage-extractor.d.ts +41 -0
- package/dist/commands/consult/usage-extractor.d.ts.map +1 -0
- package/dist/commands/consult/usage-extractor.js +122 -0
- package/dist/commands/consult/usage-extractor.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +5 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -13
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +13 -12
- 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 +57 -77
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/porch/plan.d.ts.map +1 -1
- package/dist/commands/porch/plan.js +17 -2
- package/dist/commands/porch/plan.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts +10 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +56 -29
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/commands/porch/protocol.js +2 -2
- package/dist/commands/porch/state.d.ts +13 -0
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +49 -2
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +0 -10
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/github.d.ts +82 -0
- package/dist/lib/github.d.ts.map +1 -0
- package/dist/lib/github.js +181 -0
- package/dist/lib/github.js.map +1 -0
- package/dist/lib/scaffold.d.ts +0 -21
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +0 -57
- package/dist/lib/scaffold.js.map +1 -1
- package/dist/terminal/index.d.ts +16 -0
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +14 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +8 -5
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.js +4 -4
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +64 -0
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +299 -10
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/shellper-client.d.ts +2 -1
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +4 -2
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-main.js +33 -4
- package/dist/terminal/shellper-main.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +24 -7
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +139 -36
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +1 -0
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/package.json +4 -1
- package/skeleton/.claude/skills/af/SKILL.md +10 -10
- package/skeleton/.claude/skills/consult/SKILL.md +55 -38
- package/skeleton/.claude/skills/porch/SKILL.md +53 -0
- package/skeleton/DEPENDENCIES.md +2 -2
- package/skeleton/builders.md +8 -19
- package/skeleton/maintain/.gitkeep +1 -1
- package/skeleton/porch/prompts/specify.md +1 -1
- package/skeleton/protocol-schema.json +1 -1
- package/skeleton/protocols/bugfix/prompts/pr.md +18 -7
- package/skeleton/protocols/bugfix/protocol.json +1 -1
- package/skeleton/protocols/experiment/protocol.md +17 -17
- package/skeleton/protocols/maintain/consult-types/impl-review.md +72 -0
- package/skeleton/protocols/maintain/consult-types/pr-review.md +72 -0
- package/skeleton/protocols/maintain/prompts/audit.md +2 -2
- package/skeleton/protocols/maintain/prompts/sync.md +1 -1
- package/skeleton/protocols/maintain/prompts/verify.md +1 -1
- package/skeleton/protocols/maintain/protocol.json +4 -4
- package/skeleton/protocols/maintain/protocol.md +11 -12
- package/skeleton/protocols/maintain/templates/maintenance-run.md +2 -2
- package/skeleton/protocols/protocol-schema.json +1 -1
- package/skeleton/protocols/spir/consult-types/impl-review.md +72 -0
- package/skeleton/protocols/spir/consult-types/phase-review.md +72 -0
- package/skeleton/protocols/spir/consult-types/pr-review.md +72 -0
- package/skeleton/protocols/spir/prompts/plan.md +4 -4
- package/skeleton/protocols/spir/prompts/review.md +8 -8
- package/skeleton/protocols/spir/prompts/specify.md +6 -6
- package/skeleton/protocols/spir/protocol.json +16 -16
- package/skeleton/protocols/spir/protocol.md +8 -8
- package/skeleton/protocols/spir/templates/review.md +2 -2
- package/skeleton/protocols/tick/consult-types/impl-review.md +72 -0
- package/skeleton/protocols/tick/consult-types/plan-review.md +59 -0
- package/skeleton/protocols/tick/consult-types/pr-review.md +72 -0
- package/skeleton/protocols/tick/consult-types/spec-review.md +55 -0
- package/skeleton/protocols/tick/protocol.json +2 -7
- package/skeleton/protocols/tick/protocol.md +31 -31
- package/skeleton/resources/commands/agent-farm.md +21 -19
- package/skeleton/resources/commands/codev.md +0 -36
- package/skeleton/resources/commands/consult.md +88 -234
- package/skeleton/resources/commands/overview.md +6 -7
- package/skeleton/resources/spikes.md +3 -3
- package/skeleton/resources/workflow-reference.md +28 -28
- package/skeleton/roles/architect.md +34 -38
- package/skeleton/roles/builder.md +14 -14
- package/skeleton/roles/consultant.md +6 -0
- package/skeleton/templates/AGENTS.md +6 -6
- package/skeleton/templates/CLAUDE.md +6 -6
- package/skeleton/templates/cheatsheet.md +22 -18
- package/skeleton/templates/lifecycle.md +9 -9
- package/skeleton/templates/pr-overview.md +5 -5
- package/templates/open.html +6 -3
- package/templates/tower.html +1 -41
- package/dashboard/dist/assets/index-4n9zpWLY.css +0 -32
- package/dashboard/dist/assets/index-UsH9ixz1.js +0 -136
- package/dashboard/dist/assets/index-UsH9ixz1.js.map +0 -1
- package/dist/agent-farm/commands/consult.d.ts +0 -15
- package/dist/agent-farm/commands/consult.d.ts.map +0 -1
- package/dist/agent-farm/commands/consult.js +0 -39
- package/dist/agent-farm/commands/consult.js.map +0 -1
- package/dist/agent-farm/utils/gate-status.d.ts +0 -16
- package/dist/agent-farm/utils/gate-status.d.ts.map +0 -1
- package/dist/agent-farm/utils/gate-status.js +0 -79
- package/dist/agent-farm/utils/gate-status.js.map +0 -1
- package/skeleton/templates/projectlist-archive.md +0 -21
- package/skeleton/templates/projectlist.md +0 -147
- /package/skeleton/{consult-types → protocols/bugfix/consult-types}/impl-review.md +0 -0
- /package/skeleton/{consult-types/pr-ready.md → protocols/bugfix/consult-types/pr-review.md} +0 -0
- /package/skeleton/{consult-types → protocols/spir/consult-types}/plan-review.md +0 -0
- /package/skeleton/{consult-types → protocols/spir/consult-types}/spec-review.md +0 -0
|
@@ -14,38 +14,40 @@
|
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import crypto from 'node:crypto';
|
|
17
|
-
import {
|
|
17
|
+
import { exec } from 'node:child_process';
|
|
18
|
+
import { promisify } from 'node:util';
|
|
18
19
|
import { homedir, tmpdir } from 'node:os';
|
|
20
|
+
import { encodeWorkspacePath, decodeWorkspacePath } from '../lib/tower-client.js';
|
|
19
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
+
const execAsync = promisify(exec);
|
|
23
|
+
import { DEFAULT_COLS, defaultSessionOptions } from '../../terminal/index.js';
|
|
20
24
|
import { parseJsonBody, isRequestAllowed } from '../utils/server-utils.js';
|
|
21
25
|
import { isRateLimited, normalizeWorkspacePath, getLanguageForExt, getMimeTypeForFile, serveStaticFile, } from './tower-utils.js';
|
|
22
26
|
import { handleTunnelEndpoint } from './tower-tunnel.js';
|
|
27
|
+
import { resolveTarget, broadcastMessage, isResolveError } from './tower-messages.js';
|
|
28
|
+
import { formatArchitectMessage, formatBuilderMessage } from '../utils/message-format.js';
|
|
23
29
|
import { getKnownWorkspacePaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShellper, stopInstance, } from './tower-instances.js';
|
|
24
|
-
import {
|
|
30
|
+
import { OverviewCache } from './overview.js';
|
|
31
|
+
import { getWorkspaceTerminals, getTerminalManager, getWorkspaceTerminalsEntry, getNextShellId, saveTerminalSession, isSessionPersistent, deleteTerminalSession, removeTerminalFromRegistry, deleteWorkspaceTerminalSessions, saveFileTab, deleteFileTab, getTerminalsForWorkspace, } from './tower-terminals.js';
|
|
25
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
33
|
const __dirname = path.dirname(__filename);
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
// ============================================================================
|
|
30
|
-
async function readBody(req) {
|
|
31
|
-
return new Promise((resolve) => {
|
|
32
|
-
let data = '';
|
|
33
|
-
req.on('data', (chunk) => data += chunk.toString());
|
|
34
|
-
req.on('end', () => resolve(data));
|
|
35
|
-
});
|
|
36
|
-
}
|
|
34
|
+
// Singleton cache for overview endpoint (Spec 0126 Phase 4)
|
|
35
|
+
const overviewCache = new OverviewCache();
|
|
37
36
|
const ROUTES = {
|
|
38
37
|
'GET /health': (_req, res) => handleHealthCheck(res),
|
|
39
38
|
'GET /api/workspaces': (_req, res) => handleListWorkspaces(res),
|
|
40
39
|
'POST /api/terminals': (req, res, _url, ctx) => handleTerminalCreate(req, res, ctx),
|
|
41
40
|
'GET /api/terminals': (_req, res) => handleTerminalList(res),
|
|
42
41
|
'GET /api/status': (_req, res) => handleStatus(res),
|
|
42
|
+
'GET /api/overview': (_req, res, url) => handleOverview(res, url),
|
|
43
|
+
'POST /api/overview/refresh': (_req, res, _url, ctx) => handleOverviewRefresh(res, ctx),
|
|
43
44
|
'GET /api/events': (req, res, _url, ctx) => handleSSEEvents(req, res, ctx),
|
|
44
45
|
'POST /api/notify': (req, res, _url, ctx) => handleNotify(req, res, ctx),
|
|
45
46
|
'GET /api/browse': (_req, res, url) => handleBrowse(res, url),
|
|
46
47
|
'POST /api/create': (req, res, _url, ctx) => handleCreateWorkspace(req, res, ctx),
|
|
47
48
|
'POST /api/launch': (req, res) => handleLaunchInstance(req, res),
|
|
48
49
|
'POST /api/stop': (req, res) => handleStopInstance(req, res),
|
|
50
|
+
'POST /api/send': (req, res, _url, ctx) => handleSend(req, res, ctx),
|
|
49
51
|
'GET /': (_req, res, _url, ctx) => handleDashboard(res, ctx),
|
|
50
52
|
'GET /index.html': (_req, res, _url, ctx) => handleDashboard(res, ctx),
|
|
51
53
|
};
|
|
@@ -145,7 +147,7 @@ async function handleWorkspaceAction(req, res, ctx, match) {
|
|
|
145
147
|
const [, encodedPath, action] = match;
|
|
146
148
|
let workspacePath;
|
|
147
149
|
try {
|
|
148
|
-
workspacePath =
|
|
150
|
+
workspacePath = decodeWorkspacePath(encodedPath);
|
|
149
151
|
if (!workspacePath || (!workspacePath.startsWith('/') && !/^[A-Za-z]:[\\/]/.test(workspacePath))) {
|
|
150
152
|
throw new Error('Invalid path');
|
|
151
153
|
}
|
|
@@ -171,7 +173,6 @@ async function handleWorkspaceAction(req, res, ctx, match) {
|
|
|
171
173
|
name: instance.workspaceName,
|
|
172
174
|
active: instance.running,
|
|
173
175
|
terminals: instance.terminals,
|
|
174
|
-
gateStatus: instance.gateStatus,
|
|
175
176
|
}));
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
@@ -244,9 +245,8 @@ async function handleTerminalCreate(req, res, ctx) {
|
|
|
244
245
|
args: args || [],
|
|
245
246
|
cwd,
|
|
246
247
|
env: sessionEnv,
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
restartOnExit: false,
|
|
248
|
+
...defaultSessionOptions(),
|
|
249
|
+
cols: cols || DEFAULT_COLS,
|
|
250
250
|
});
|
|
251
251
|
const replayData = client.getReplayData() ?? Buffer.alloc(0);
|
|
252
252
|
const shellperInfo = shellperManager.getSessionInfo(sessionId);
|
|
@@ -333,6 +333,9 @@ async function handleTerminalRoutes(req, res, url, match) {
|
|
|
333
333
|
}
|
|
334
334
|
// TICK-001: Delete from SQLite
|
|
335
335
|
deleteTerminalSession(terminalId);
|
|
336
|
+
// Bugfix #290: Also remove from in-memory registry so dashboard
|
|
337
|
+
// stops showing tabs for cleaned-up builders
|
|
338
|
+
removeTerminalFromRegistry(terminalId);
|
|
336
339
|
res.writeHead(204);
|
|
337
340
|
res.end();
|
|
338
341
|
return;
|
|
@@ -406,6 +409,41 @@ async function handleStatus(res) {
|
|
|
406
409
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
407
410
|
res.end(JSON.stringify({ instances }));
|
|
408
411
|
}
|
|
412
|
+
async function handleOverview(res, url, workspaceOverride) {
|
|
413
|
+
// Accept workspace from: explicit override (workspace-scoped route), ?workspace= param, or first known path.
|
|
414
|
+
let workspaceRoot = workspaceOverride || url.searchParams.get('workspace');
|
|
415
|
+
if (!workspaceRoot) {
|
|
416
|
+
const knownPaths = getKnownWorkspacePaths();
|
|
417
|
+
workspaceRoot = knownPaths.find(p => !p.includes('/.builders/')) || null;
|
|
418
|
+
}
|
|
419
|
+
if (!workspaceRoot) {
|
|
420
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
421
|
+
res.end(JSON.stringify({ builders: [], pendingPRs: [], backlog: [] }));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
// Build set of active builder role_ids (lowercased) from live terminal sessions
|
|
425
|
+
const wsTerminals = getWorkspaceTerminals();
|
|
426
|
+
const entry = wsTerminals.get(normalizeWorkspacePath(workspaceRoot));
|
|
427
|
+
const activeBuilderRoleIds = new Set();
|
|
428
|
+
if (entry) {
|
|
429
|
+
for (const key of entry.builders.keys()) {
|
|
430
|
+
activeBuilderRoleIds.add(key.toLowerCase());
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const data = await overviewCache.getOverview(workspaceRoot, activeBuilderRoleIds);
|
|
434
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
435
|
+
res.end(JSON.stringify(data));
|
|
436
|
+
}
|
|
437
|
+
function handleOverviewRefresh(res, ctx) {
|
|
438
|
+
overviewCache.invalidate();
|
|
439
|
+
// Bugfix #388: Broadcast SSE event so all connected dashboard clients
|
|
440
|
+
// immediately re-fetch instead of waiting for the next poll cycle.
|
|
441
|
+
if (ctx) {
|
|
442
|
+
ctx.broadcastNotification({ type: 'overview-changed', title: 'Overview updated', body: 'Cache invalidated' });
|
|
443
|
+
}
|
|
444
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
445
|
+
res.end(JSON.stringify({ ok: true }));
|
|
446
|
+
}
|
|
409
447
|
function handleSSEEvents(req, res, ctx) {
|
|
410
448
|
const clientId = crypto.randomBytes(8).toString('hex');
|
|
411
449
|
res.writeHead(200, {
|
|
@@ -446,6 +484,107 @@ async function handleNotify(req, res, ctx) {
|
|
|
446
484
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
447
485
|
res.end(JSON.stringify({ success: true }));
|
|
448
486
|
}
|
|
487
|
+
// ============================================================================
|
|
488
|
+
// POST /api/send — send a message to a resolved agent terminal
|
|
489
|
+
// ============================================================================
|
|
490
|
+
async function handleSend(req, res, ctx) {
|
|
491
|
+
const body = await parseJsonBody(req);
|
|
492
|
+
// Validate required fields
|
|
493
|
+
const to = typeof body.to === 'string' ? body.to.trim() : '';
|
|
494
|
+
const message = typeof body.message === 'string' ? body.message.trim() : '';
|
|
495
|
+
if (!to) {
|
|
496
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
497
|
+
res.end(JSON.stringify({ error: 'INVALID_PARAMS', message: 'Missing or empty "to" field' }));
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (!message) {
|
|
501
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
502
|
+
res.end(JSON.stringify({ error: 'INVALID_PARAMS', message: 'Missing or empty "message" field' }));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
// Optional fields
|
|
506
|
+
const from = typeof body.from === 'string' ? body.from : undefined;
|
|
507
|
+
const workspace = typeof body.workspace === 'string' ? body.workspace : undefined;
|
|
508
|
+
const fromWorkspace = typeof body.fromWorkspace === 'string' ? body.fromWorkspace : undefined;
|
|
509
|
+
const options = typeof body.options === 'object' && body.options !== null
|
|
510
|
+
? body.options
|
|
511
|
+
: {};
|
|
512
|
+
const raw = options.raw === true;
|
|
513
|
+
const noEnter = options.noEnter === true;
|
|
514
|
+
const interrupt = options.interrupt === true;
|
|
515
|
+
// Resolve the target address to a terminal ID
|
|
516
|
+
const result = resolveTarget(to, workspace);
|
|
517
|
+
if (isResolveError(result)) {
|
|
518
|
+
const statusCode = result.code === 'AMBIGUOUS' ? 409
|
|
519
|
+
: result.code === 'NO_CONTEXT' ? 400
|
|
520
|
+
: 404;
|
|
521
|
+
// Map NO_CONTEXT to INVALID_PARAMS per plan's error contract
|
|
522
|
+
const errorCode = result.code === 'NO_CONTEXT' ? 'INVALID_PARAMS' : result.code;
|
|
523
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
524
|
+
res.end(JSON.stringify({ error: errorCode, message: result.message }));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
// Get the terminal session
|
|
528
|
+
const manager = getTerminalManager();
|
|
529
|
+
const session = manager.getSession(result.terminalId);
|
|
530
|
+
if (!session) {
|
|
531
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
532
|
+
res.end(JSON.stringify({
|
|
533
|
+
error: 'NOT_FOUND',
|
|
534
|
+
message: `Terminal session ${result.terminalId} not found (agent '${result.agent}' resolved but terminal is gone).`,
|
|
535
|
+
}));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
// Format the message based on sender/target
|
|
539
|
+
const isArchitectTarget = result.agent === 'architect';
|
|
540
|
+
let formattedMessage;
|
|
541
|
+
if (isArchitectTarget && from) {
|
|
542
|
+
// Builder → Architect
|
|
543
|
+
formattedMessage = formatBuilderMessage(from, message, undefined, raw);
|
|
544
|
+
}
|
|
545
|
+
else if (!isArchitectTarget) {
|
|
546
|
+
// Architect → Builder (or any → builder)
|
|
547
|
+
formattedMessage = formatArchitectMessage(message, undefined, raw);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
// Unknown sender to architect — use raw
|
|
551
|
+
formattedMessage = raw ? message : formatArchitectMessage(message, undefined, false);
|
|
552
|
+
}
|
|
553
|
+
// Optionally interrupt first
|
|
554
|
+
if (interrupt) {
|
|
555
|
+
session.write('\x03'); // Ctrl+C
|
|
556
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
557
|
+
}
|
|
558
|
+
// Write the message to the terminal
|
|
559
|
+
session.write(formattedMessage);
|
|
560
|
+
// Send Enter to submit (unless noEnter)
|
|
561
|
+
if (!noEnter) {
|
|
562
|
+
session.write('\r');
|
|
563
|
+
}
|
|
564
|
+
// Broadcast structured message to WebSocket subscribers
|
|
565
|
+
const senderWorkspace = fromWorkspace ?? workspace ?? 'unknown';
|
|
566
|
+
broadcastMessage({
|
|
567
|
+
type: 'message',
|
|
568
|
+
from: {
|
|
569
|
+
project: path.basename(senderWorkspace),
|
|
570
|
+
agent: from ?? 'unknown',
|
|
571
|
+
},
|
|
572
|
+
to: {
|
|
573
|
+
project: path.basename(result.workspacePath),
|
|
574
|
+
agent: result.agent,
|
|
575
|
+
},
|
|
576
|
+
content: message,
|
|
577
|
+
metadata: { raw, source: 'api' },
|
|
578
|
+
timestamp: new Date().toISOString(),
|
|
579
|
+
});
|
|
580
|
+
ctx.log('INFO', `Message sent: ${from ?? 'unknown'} → ${result.agent} (terminal ${result.terminalId.slice(0, 8)}...)`);
|
|
581
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
582
|
+
res.end(JSON.stringify({
|
|
583
|
+
ok: true,
|
|
584
|
+
terminalId: result.terminalId,
|
|
585
|
+
resolvedTo: result.agent,
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
449
588
|
async function handleBrowse(res, url) {
|
|
450
589
|
const inputPath = url.searchParams.get('path') || '';
|
|
451
590
|
try {
|
|
@@ -493,9 +632,8 @@ async function handleCreateWorkspace(req, res, ctx) {
|
|
|
493
632
|
}
|
|
494
633
|
try {
|
|
495
634
|
// Run codev init (it creates the directory)
|
|
496
|
-
|
|
635
|
+
await execAsync(`codev init --yes "${workspaceName}"`, {
|
|
497
636
|
cwd: expandedParent,
|
|
498
|
-
stdio: 'pipe',
|
|
499
637
|
timeout: 60000,
|
|
500
638
|
});
|
|
501
639
|
// Launch the instance
|
|
@@ -593,7 +731,7 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
593
731
|
// Decode Base64URL (RFC 4648)
|
|
594
732
|
let workspacePath;
|
|
595
733
|
try {
|
|
596
|
-
workspacePath =
|
|
734
|
+
workspacePath = decodeWorkspacePath(encodedPath);
|
|
597
735
|
// Support both POSIX (/) and Windows (C:\) paths
|
|
598
736
|
if (!workspacePath || (!workspacePath.startsWith('/') && !/^[A-Za-z]:[\\/]/.test(workspacePath))) {
|
|
599
737
|
throw new Error('Invalid workspace path');
|
|
@@ -618,7 +756,7 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
618
756
|
await handleTunnelEndpoint(req, res, tunnelSub);
|
|
619
757
|
return;
|
|
620
758
|
}
|
|
621
|
-
// GET /file?path=<relative-path> — Read workspace file by path
|
|
759
|
+
// GET /file?path=<relative-path> — Read workspace file by path
|
|
622
760
|
if (req.method === 'GET' && subPath === 'file' && url.searchParams.has('path')) {
|
|
623
761
|
const relPath = url.searchParams.get('path');
|
|
624
762
|
const fullPath = path.resolve(workspacePath, relPath);
|
|
@@ -771,6 +909,18 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
771
909
|
});
|
|
772
910
|
return;
|
|
773
911
|
}
|
|
912
|
+
// GET /api/overview - Work view overview data (Spec 0126 Phase 4)
|
|
913
|
+
if (req.method === 'GET' && apiPath === 'overview') {
|
|
914
|
+
return handleOverview(res, url, workspacePath);
|
|
915
|
+
}
|
|
916
|
+
// POST /api/overview/refresh - Invalidate overview cache (Spec 0126 Phase 4)
|
|
917
|
+
if (req.method === 'POST' && apiPath === 'overview/refresh') {
|
|
918
|
+
return handleOverviewRefresh(res, ctx);
|
|
919
|
+
}
|
|
920
|
+
// GET /api/events - SSE push notifications (Bugfix #388)
|
|
921
|
+
if (req.method === 'GET' && apiPath === 'events') {
|
|
922
|
+
return handleSSEEvents(req, res, ctx);
|
|
923
|
+
}
|
|
774
924
|
// Unhandled API route
|
|
775
925
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
776
926
|
res.end(JSON.stringify({ error: 'API endpoint not found', path: apiPath }));
|
|
@@ -799,9 +949,9 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
799
949
|
async function handleWorkspaceState(res, workspacePath) {
|
|
800
950
|
// Refresh cache via getTerminalsForWorkspace (handles SQLite sync
|
|
801
951
|
// and shellper reconnection in one place)
|
|
802
|
-
const encodedPath =
|
|
952
|
+
const encodedPath = encodeWorkspacePath(workspacePath);
|
|
803
953
|
const proxyUrl = `/workspace/${encodedPath}/`;
|
|
804
|
-
|
|
954
|
+
await getTerminalsForWorkspace(workspacePath, proxyUrl);
|
|
805
955
|
// Now read from the refreshed cache
|
|
806
956
|
const entry = getWorkspaceTerminalsEntry(workspacePath);
|
|
807
957
|
const manager = getTerminalManager();
|
|
@@ -811,7 +961,6 @@ async function handleWorkspaceState(res, workspacePath) {
|
|
|
811
961
|
utils: [],
|
|
812
962
|
annotations: [],
|
|
813
963
|
workspaceName: path.basename(workspacePath),
|
|
814
|
-
gateStatus,
|
|
815
964
|
};
|
|
816
965
|
// Add architect if exists
|
|
817
966
|
if (entry.architect) {
|
|
@@ -845,7 +994,7 @@ async function handleWorkspaceState(res, workspacePath) {
|
|
|
845
994
|
if (session) {
|
|
846
995
|
state.builders.push({
|
|
847
996
|
id: builderId,
|
|
848
|
-
name:
|
|
997
|
+
name: builderId,
|
|
849
998
|
port: 0,
|
|
850
999
|
pid: session.pid || 0,
|
|
851
1000
|
status: 'running',
|
|
@@ -891,9 +1040,7 @@ async function handleWorkspaceShellCreate(res, ctx, workspacePath) {
|
|
|
891
1040
|
args: shellArgs,
|
|
892
1041
|
cwd: workspacePath,
|
|
893
1042
|
env: shellEnv,
|
|
894
|
-
|
|
895
|
-
rows: 50,
|
|
896
|
-
restartOnExit: false,
|
|
1043
|
+
...defaultSessionOptions(),
|
|
897
1044
|
});
|
|
898
1045
|
const replayData = client.getReplayData() ?? Buffer.alloc(0);
|
|
899
1046
|
const shellperInfo = shellperManager.getSessionInfo(sessionId);
|
|
@@ -954,8 +1101,10 @@ async function handleWorkspaceShellCreate(res, ctx, workspacePath) {
|
|
|
954
1101
|
}
|
|
955
1102
|
async function handleWorkspaceFileTabCreate(req, res, ctx, workspacePath) {
|
|
956
1103
|
try {
|
|
957
|
-
const body = await
|
|
958
|
-
const
|
|
1104
|
+
const body = await parseJsonBody(req);
|
|
1105
|
+
const filePath = body.path;
|
|
1106
|
+
const line = body.line;
|
|
1107
|
+
const terminalId = body.terminalId;
|
|
959
1108
|
if (!filePath || typeof filePath !== 'string') {
|
|
960
1109
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
961
1110
|
res.end(JSON.stringify({ error: 'Missing path parameter' }));
|
|
@@ -1116,8 +1265,7 @@ async function handleWorkspaceFileSave(req, res, ctx, workspacePath, tabId) {
|
|
|
1116
1265
|
return;
|
|
1117
1266
|
}
|
|
1118
1267
|
try {
|
|
1119
|
-
const
|
|
1120
|
-
const { content } = JSON.parse(body || '{}');
|
|
1268
|
+
const { content } = await parseJsonBody(req);
|
|
1121
1269
|
if (typeof content !== 'string') {
|
|
1122
1270
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1123
1271
|
res.end(JSON.stringify({ error: 'Missing content parameter' }));
|
|
@@ -1241,10 +1389,10 @@ function handleWorkspaceFiles(res, url, workspacePath) {
|
|
|
1241
1389
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1242
1390
|
res.end(JSON.stringify(tree));
|
|
1243
1391
|
}
|
|
1244
|
-
function handleWorkspaceGitStatus(res, ctx, workspacePath) {
|
|
1392
|
+
async function handleWorkspaceGitStatus(res, ctx, workspacePath) {
|
|
1245
1393
|
try {
|
|
1246
1394
|
// Get git status in porcelain format for parsing
|
|
1247
|
-
const result =
|
|
1395
|
+
const { stdout: result } = await execAsync('git status --porcelain', {
|
|
1248
1396
|
cwd: workspacePath,
|
|
1249
1397
|
encoding: 'utf-8',
|
|
1250
1398
|
timeout: 5000,
|