@cluesmith/codev 3.0.2 → 3.0.5
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/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +44 -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 +2 -117
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/dev.d.ts +20 -0
- package/dist/agent-farm/commands/dev.d.ts.map +1 -0
- package/dist/agent-farm/commands/dev.js +124 -0
- package/dist/agent-farm/commands/dev.js.map +1 -0
- package/dist/agent-farm/commands/setup.d.ts +27 -0
- package/dist/agent-farm/commands/setup.d.ts.map +1 -0
- package/dist/agent-farm/commands/setup.js +55 -0
- package/dist/agent-farm/commands/setup.js.map +1 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts +21 -2
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +42 -4
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +30 -15
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +4 -1
- 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 +15 -8
- package/dist/agent-farm/commands/stop.js.map +1 -1
- package/dist/agent-farm/commands/workspace-add-architect.d.ts +18 -0
- package/dist/agent-farm/commands/workspace-add-architect.d.ts.map +1 -0
- package/dist/agent-farm/commands/workspace-add-architect.js +54 -0
- package/dist/agent-farm/commands/workspace-add-architect.js.map +1 -0
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +54 -1
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/migrate.js +2 -2
- 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 +3 -2
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +7 -2
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js +2 -0
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/builder-lookup.d.ts +26 -0
- package/dist/agent-farm/lib/builder-lookup.d.ts.map +1 -0
- package/dist/agent-farm/lib/builder-lookup.js +103 -0
- package/dist/agent-farm/lib/builder-lookup.js.map +1 -0
- package/dist/agent-farm/servers/tower-instances.d.ts +20 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +260 -16
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-messages.d.ts +10 -1
- package/dist/agent-farm/servers/tower-messages.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-messages.js +137 -7
- package/dist/agent-farm/servers/tower-messages.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +97 -31
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +17 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +78 -29
- 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 -2
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-types.d.ts +18 -6
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
- package/dist/agent-farm/state.d.ts +50 -4
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +118 -12
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +28 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.d.ts +5 -30
- package/dist/agent-farm/utils/agent-names.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.js +5 -54
- package/dist/agent-farm/utils/agent-names.js.map +1 -1
- package/dist/agent-farm/utils/architect-name.d.ts +37 -0
- package/dist/agent-farm/utils/architect-name.d.ts.map +1 -0
- package/dist/agent-farm/utils/architect-name.js +63 -0
- package/dist/agent-farm/utils/architect-name.js.map +1 -0
- package/dist/agent-farm/utils/config.d.ts +19 -0
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +16 -0
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/git.d.ts +29 -0
- package/dist/agent-farm/utils/git.d.ts.map +1 -0
- package/dist/agent-farm/utils/git.js +38 -0
- package/dist/agent-farm/utils/git.js.map +1 -0
- package/dist/agent-farm/utils/shell.d.ts +13 -0
- package/dist/agent-farm/utils/shell.d.ts.map +1 -1
- package/dist/agent-farm/utils/shell.js +28 -0
- package/dist/agent-farm/utils/shell.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +5 -0
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +2 -0
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -0
- package/dist/commands/init.js.map +1 -1
- package/package.json +5 -5
- package/skeleton/protocols/air/builder-prompt.md +6 -0
- package/skeleton/protocols/air/consult-types/impl-review.md +6 -0
- package/skeleton/protocols/air/consult-types/pr-review.md +6 -0
- package/skeleton/protocols/air/prompts/implement.md +6 -0
- package/skeleton/protocols/air/protocol.md +4 -0
- package/skeleton/protocols/aspir/builder-prompt.md +19 -3
- package/skeleton/protocols/aspir/consult-types/plan-review.md +6 -0
- package/skeleton/protocols/aspir/consult-types/spec-review.md +6 -0
- package/skeleton/protocols/aspir/prompts/specify.md +6 -0
- package/skeleton/protocols/aspir/protocol.md +4 -0
- package/skeleton/protocols/bugfix/consult-types/impl-review.md +44 -37
- package/skeleton/protocols/bugfix/consult-types/pr-review.md +65 -40
- package/skeleton/protocols/spir/builder-prompt.md +19 -3
- package/skeleton/protocols/spir/consult-types/plan-review.md +6 -0
- package/skeleton/protocols/spir/consult-types/spec-review.md +6 -0
- package/skeleton/protocols/spir/prompts/specify.md +6 -0
- package/skeleton/protocols/spir/protocol.md +4 -0
|
@@ -9,6 +9,24 @@
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { parseAddress, stripLeadingZeros } from '../utils/agent-names.js';
|
|
11
11
|
import { getWorkspaceTerminals } from './tower-terminals.js';
|
|
12
|
+
import { lookupBuilderSpawningArchitect } from '../state.js';
|
|
13
|
+
import { DEFAULT_ARCHITECT_NAME } from '../utils/architect-name.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Spec 755 error messages — exported as constants so producer and asserter
|
|
16
|
+
// share a single source of truth. Tests assert these texts verbatim; changing
|
|
17
|
+
// the wording without updating both sides will break the build.
|
|
18
|
+
// ============================================================================
|
|
19
|
+
export function legacyBuilderErrorMessage(builderId, registered) {
|
|
20
|
+
const names = registered.length ? registered.join(', ') : '<none>';
|
|
21
|
+
return `legacy builder ${builderId} has no spawning-architect identity and no 'main' architect is registered (registered: ${names})`;
|
|
22
|
+
}
|
|
23
|
+
export function architectGoneErrorMessage(builderId, missingArchitect, registered) {
|
|
24
|
+
const names = registered.length ? registered.join(', ') : '<none>';
|
|
25
|
+
return `builder ${builderId} was spawned by architect ${missingArchitect}, which is no longer registered, and no 'main' architect is registered (registered: ${names})`;
|
|
26
|
+
}
|
|
27
|
+
export function addressSpoofingErrorMessage(builderId) {
|
|
28
|
+
return `builder ${builderId} may only address its own spawning architect`;
|
|
29
|
+
}
|
|
12
30
|
/** Active WebSocket subscribers for the message bus. */
|
|
13
31
|
const messageSubscribers = new Set();
|
|
14
32
|
/**
|
|
@@ -50,11 +68,17 @@ export function getSubscriberCount() {
|
|
|
50
68
|
* - Exact match (case-insensitive) first, then tail match
|
|
51
69
|
* - Multiple tail matches → AMBIGUOUS error
|
|
52
70
|
*
|
|
71
|
+
* Spec 755: when `sender` is supplied and refers to a builder, sender-affinity
|
|
72
|
+
* routing applies to `architect` and `architect:<name>` targets. Non-builder
|
|
73
|
+
* senders see the legacy main-first behavior, unchanged.
|
|
74
|
+
*
|
|
53
75
|
* @param target - Address string: "agent" or "project:agent"
|
|
54
76
|
* @param fallbackWorkspace - Workspace path when no project: prefix is given
|
|
77
|
+
* @param sender - Optional sender identity (a builder ID or 'architect').
|
|
78
|
+
* Enables affinity-aware architect routing per Spec 755.
|
|
55
79
|
* @returns ResolveResult on success, ResolveError on failure
|
|
56
80
|
*/
|
|
57
|
-
export function resolveTarget(target, fallbackWorkspace) {
|
|
81
|
+
export function resolveTarget(target, fallbackWorkspace, sender) {
|
|
58
82
|
const { project, agent } = parseAddress(target);
|
|
59
83
|
// Validate: empty or whitespace-only agent is a malformed address
|
|
60
84
|
if (!agent || !agent.trim()) {
|
|
@@ -63,6 +87,20 @@ export function resolveTarget(target, fallbackWorkspace) {
|
|
|
63
87
|
message: 'Malformed address: agent name is empty.',
|
|
64
88
|
};
|
|
65
89
|
}
|
|
90
|
+
// Spec 755: `architect:<name>` is a per-architect address WITHIN the
|
|
91
|
+
// current workspace, not a `project:agent` cross-workspace address.
|
|
92
|
+
// parseAddress can't distinguish them (the grammar is overloaded), so we
|
|
93
|
+
// intercept here. The resolver below applies the spoofing check when
|
|
94
|
+
// sender is a builder.
|
|
95
|
+
if (project && project.toLowerCase() === 'architect') {
|
|
96
|
+
if (!fallbackWorkspace) {
|
|
97
|
+
return {
|
|
98
|
+
code: 'NO_CONTEXT',
|
|
99
|
+
message: 'Cannot resolve architect:<name> address without workspace context.',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return resolveArchitectByName(agent, fallbackWorkspace, sender);
|
|
103
|
+
}
|
|
66
104
|
// Determine the workspace path
|
|
67
105
|
let workspacePath;
|
|
68
106
|
if (project) {
|
|
@@ -81,7 +119,43 @@ export function resolveTarget(target, fallbackWorkspace) {
|
|
|
81
119
|
workspacePath = fallbackWorkspace;
|
|
82
120
|
}
|
|
83
121
|
// Resolve agent within the workspace
|
|
84
|
-
return resolveAgentInWorkspace(agent, workspacePath);
|
|
122
|
+
return resolveAgentInWorkspace(agent, workspacePath, sender);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Resolve `architect:<name>` to a terminal ID within the given workspace.
|
|
126
|
+
*
|
|
127
|
+
* Spec 755 enforcement when sender is a builder:
|
|
128
|
+
* - If the builder's `spawned_by_architect` matches `<name>`: allowed.
|
|
129
|
+
* - If it doesn't match: rejected with the spoofing error.
|
|
130
|
+
* Non-builder senders may address any architect by name.
|
|
131
|
+
*/
|
|
132
|
+
function resolveArchitectByName(name, workspacePath, sender) {
|
|
133
|
+
const allWorkspaces = getWorkspaceTerminals();
|
|
134
|
+
const entry = allWorkspaces.get(workspacePath);
|
|
135
|
+
if (!entry) {
|
|
136
|
+
return {
|
|
137
|
+
code: 'NOT_FOUND',
|
|
138
|
+
message: `Workspace '${workspacePath}' has no registered terminals.`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Spoofing check: builder senders can only address their own architect.
|
|
142
|
+
if (sender) {
|
|
143
|
+
const spawningArchitect = lookupBuilderSpawningArchitect(sender, workspacePath);
|
|
144
|
+
if (spawningArchitect !== undefined && spawningArchitect !== name) {
|
|
145
|
+
return {
|
|
146
|
+
code: 'NOT_FOUND',
|
|
147
|
+
message: addressSpoofingErrorMessage(sender),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const terminalId = entry.architects.get(name);
|
|
152
|
+
if (!terminalId) {
|
|
153
|
+
return {
|
|
154
|
+
code: 'NOT_FOUND',
|
|
155
|
+
message: `Architect '${name}' not found in workspace '${path.basename(workspacePath)}'.`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { terminalId, workspacePath, agent: name };
|
|
85
159
|
}
|
|
86
160
|
/**
|
|
87
161
|
* Find a workspace path by matching the basename of registered workspace paths.
|
|
@@ -111,9 +185,23 @@ function findWorkspaceByBasename(projectName) {
|
|
|
111
185
|
/**
|
|
112
186
|
* Resolve an agent name to a terminal ID within a specific workspace.
|
|
113
187
|
*
|
|
114
|
-
* Checks architect first
|
|
188
|
+
* Checks architect first (with Spec 755 affinity-aware routing when `sender`
|
|
189
|
+
* is a builder), then builders by exact match, then builders by tail match.
|
|
190
|
+
*
|
|
191
|
+
* Spec 755 architect resolution:
|
|
192
|
+
* - Fast path: single-architect workspace with name 'main' — return that
|
|
193
|
+
* terminal directly. Identical to today's behavior; avoids the state.db
|
|
194
|
+
* read entirely for the common case.
|
|
195
|
+
* - Builder sender, target 'architect': look up the builder's
|
|
196
|
+
* spawnedByArchitect. If that architect is registered, route there.
|
|
197
|
+
* Otherwise fall back to 'main', else error with the spec-mandated
|
|
198
|
+
* legacy-builder / architect-gone messages.
|
|
199
|
+
* - Builder sender, target 'architect:<name>': allowed only if <name>
|
|
200
|
+
* matches the sender's spawnedByArchitect. Otherwise rejected (spoofing).
|
|
201
|
+
* - Non-builder sender (or no sender), target 'architect': route to 'main'
|
|
202
|
+
* (or first registered if 'main' is absent). Unchanged from today.
|
|
115
203
|
*/
|
|
116
|
-
function resolveAgentInWorkspace(agent, workspacePath) {
|
|
204
|
+
function resolveAgentInWorkspace(agent, workspacePath, sender) {
|
|
117
205
|
const allWorkspaces = getWorkspaceTerminals();
|
|
118
206
|
const entry = allWorkspaces.get(workspacePath);
|
|
119
207
|
if (!entry) {
|
|
@@ -122,16 +210,58 @@ function resolveAgentInWorkspace(agent, workspacePath) {
|
|
|
122
210
|
message: `Workspace '${workspacePath}' has no registered terminals.`,
|
|
123
211
|
};
|
|
124
212
|
}
|
|
125
|
-
// Check architect
|
|
213
|
+
// Check architect (Spec 755 affinity-aware path).
|
|
126
214
|
if (agent === 'architect' || agent === 'arch') {
|
|
127
|
-
if (
|
|
215
|
+
if (entry.architects.size === 0) {
|
|
128
216
|
return {
|
|
129
217
|
code: 'NOT_FOUND',
|
|
130
218
|
message: `No architect terminal found in workspace '${path.basename(workspacePath)}'.`,
|
|
131
219
|
};
|
|
132
220
|
}
|
|
133
|
-
|
|
221
|
+
// Single-architect fast path: most workspaces have only 'main'.
|
|
222
|
+
// Identical answer to the full resolution path, but skips the state.db
|
|
223
|
+
// read entirely. Guarantees latency parity for solo-architect users.
|
|
224
|
+
if (entry.architects.size === 1 && entry.architects.has(DEFAULT_ARCHITECT_NAME)) {
|
|
225
|
+
const terminalId = entry.architects.get(DEFAULT_ARCHITECT_NAME);
|
|
226
|
+
return { terminalId, workspacePath, agent: 'architect' };
|
|
227
|
+
}
|
|
228
|
+
// Builder-context detection via state.db row presence. Three-valued
|
|
229
|
+
// result distinguishes "builder with explicit name" / "legacy builder"
|
|
230
|
+
// / "not a builder" — see lookupBuilderSpawningArchitect.
|
|
231
|
+
const spawningArchitect = sender ? lookupBuilderSpawningArchitect(sender, workspacePath) : undefined;
|
|
232
|
+
if (spawningArchitect !== undefined && sender) {
|
|
233
|
+
// Sender is a builder.
|
|
234
|
+
if (spawningArchitect === null) {
|
|
235
|
+
// Legacy builder: row exists, spawnedByArchitect is null. Route to
|
|
236
|
+
// 'main' if present; else fail with the spec's verbatim message.
|
|
237
|
+
const main = entry.architects.get(DEFAULT_ARCHITECT_NAME);
|
|
238
|
+
if (main)
|
|
239
|
+
return { terminalId: main, workspacePath, agent: 'architect' };
|
|
240
|
+
return {
|
|
241
|
+
code: 'NOT_FOUND',
|
|
242
|
+
message: legacyBuilderErrorMessage(sender, [...entry.architects.keys()]),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Builder has an explicit spawning architect.
|
|
246
|
+
const target = entry.architects.get(spawningArchitect);
|
|
247
|
+
if (target)
|
|
248
|
+
return { terminalId: target, workspacePath, agent: 'architect' };
|
|
249
|
+
// Architect-gone fallback: route to 'main' if present; else fail.
|
|
250
|
+
const main = entry.architects.get(DEFAULT_ARCHITECT_NAME);
|
|
251
|
+
if (main)
|
|
252
|
+
return { terminalId: main, workspacePath, agent: 'architect' };
|
|
253
|
+
return {
|
|
254
|
+
code: 'NOT_FOUND',
|
|
255
|
+
message: architectGoneErrorMessage(sender, spawningArchitect, [...entry.architects.keys()]),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Non-builder sender (or no sender): 'main' first, then first registered.
|
|
259
|
+
const terminalId = entry.architects.get(DEFAULT_ARCHITECT_NAME) ?? entry.architects.values().next().value;
|
|
260
|
+
return { terminalId, workspacePath, agent: 'architect' };
|
|
134
261
|
}
|
|
262
|
+
// `architect:<name>` addressing is handled earlier in resolveTarget via
|
|
263
|
+
// resolveArchitectByName. Reaching this point means the agent is a plain
|
|
264
|
+
// workspace-local name (builder/shell), so we fall through to those.
|
|
135
265
|
// Check builders — exact match (case-insensitive)
|
|
136
266
|
for (const [builderId, terminalId] of entry.builders) {
|
|
137
267
|
if (builderId.toLowerCase() === agent) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tower-messages.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"tower-messages.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,gEAAgE;AAChE,+EAA+E;AAE/E,MAAM,UAAU,yBAAyB,CAAC,SAAiB,EAAE,UAAoB;IAC/E,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnE,OAAO,kBAAkB,SAAS,0FAA0F,KAAK,GAAG,CAAC;AACvI,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,SAAiB,EACjB,gBAAwB,EACxB,UAAoB;IAEpB,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnE,OAAO,WAAW,SAAS,6BAA6B,gBAAgB,uFAAuF,KAAK,GAAG,CAAC;AAC1K,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,SAAiB;IAC3D,OAAO,WAAW,SAAS,8CAA8C,CAAC;AAC5E,CAAC;AA2BD,wDAAwD;AACxD,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAqB,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAAa,EAAE,aAAsB;IACjE,kBAAkB,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAa;IAC5C,KAAK,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;QACrC,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;YAClB,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO;QACT,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,kBAAkB,CAAC,IAAI,CAAC;AACjC,CAAC;AAmBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,iBAA0B,EAC1B,MAAe;IAEf,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEhD,kEAAkE;IAClE,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5B,OAAO;YACL,IAAI,EAAE,YAAqB;YAC3B,OAAO,EAAE,yCAAyC;SACnD,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,oEAAoE;IACpE,yEAAyE;IACzE,qEAAqE;IACrE,uBAAuB;IACvB,IAAI,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,WAAW,EAAE,CAAC;QACrD,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,OAAO;gBACL,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,oEAAoE;aAC9E,CAAC;QACJ,CAAC;QACD,OAAO,sBAAsB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;IAClE,CAAC;IAED,+BAA+B;IAC/B,IAAI,aAAqB,CAAC;IAC1B,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,MAAM,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QACpC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,OAAO;gBACL,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,+CAA+C;aACzD,CAAC;QACJ,CAAC;QACD,aAAa,GAAG,iBAAiB,CAAC;IACpC,CAAC;IAED,qCAAqC;IACrC,OAAO,uBAAuB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,sBAAsB,CAC7B,IAAY,EACZ,aAAqB,EACrB,MAAe;IAEf,MAAM,aAAa,GAAG,qBAAqB,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAE/C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,cAAc,aAAa,gCAAgC;SACrE,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,iBAAiB,GAAG,8BAA8B,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QAChF,IAAI,iBAAiB,KAAK,SAAS,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;YAClE,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,2BAA2B,CAAC,MAAM,CAAC;aAC7C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,cAAc,IAAI,6BAA6B,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI;SACzF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,SAAS,uBAAuB,CAC9B,WAAmB;IAEnB,MAAM,aAAa,GAAG,qBAAqB,EAAE,CAAC;IAC9C,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,MAAM,IAAI,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,WAAW,EAAE,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,YAAY,WAAW,6DAA6D;SAC9F,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,YAAY,WAAW,4BAA4B,OAAO,CAAC,MAAM,gBAAgB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SAC/G,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,uBAAuB,CAC9B,KAAa,EACb,aAAqB,EACrB,MAAe;IAEf,MAAM,aAAa,GAAG,qBAAqB,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAE/C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,cAAc,aAAa,gCAAgC;SACrE,CAAC;IACJ,CAAC;IAED,kDAAkD;IAClD,IAAI,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QAC9C,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,6CAA6C,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI;aACvF,CAAC;QACJ,CAAC;QAED,gEAAgE;QAChE,uEAAuE;QACvE,qEAAqE;QACrE,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,sBAAsB,CAAC,EAAE,CAAC;YAChF,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,sBAAsB,CAAE,CAAC;YACjE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAC3D,CAAC;QAED,oEAAoE;QACpE,uEAAuE;QACvE,0DAA0D;QAC1D,MAAM,iBAAiB,GAAG,MAAM,CAAC,CAAC,CAAC,8BAA8B,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAErG,IAAI,iBAAiB,KAAK,SAAS,IAAI,MAAM,EAAE,CAAC;YAC9C,uBAAuB;YACvB,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAC/B,mEAAmE;gBACnE,iEAAiE;gBACjE,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;gBAC1D,IAAI,IAAI;oBAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;gBACzE,OAAO;oBACL,IAAI,EAAE,WAAW;oBACjB,OAAO,EAAE,yBAAyB,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;iBACzE,CAAC;YACJ,CAAC;YACD,8CAA8C;YAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;YACvD,IAAI,MAAM;gBAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;YAC7E,kEAAkE;YAClE,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;YAC1D,IAAI,IAAI;gBAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;YACzE,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;aAC5F,CAAC;QACJ,CAAC;QAED,0EAA0E;QAC1E,MAAM,UAAU,GACd,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC;QAC1F,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAC3D,CAAC;IAED,wEAAwE;IACxE,yEAAyE;IACzE,qEAAqE;IAErE,kDAAkD;IAClD,KAAK,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrD,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE,CAAC;YACtC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QACzD,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,MAAM,aAAa,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7D,MAAM,WAAW,GAAqD,EAAE,CAAC;IAEzE,KAAK,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrD,IAAI,SAAS,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,EAAE,CAAC;YAC1D,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,UAAU;YACrC,aAAa;YACb,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS;SAChC,CAAC;IACJ,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChE,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,UAAU,KAAK,4BAA4B,WAAW,CAAC,MAAM,cAAc,UAAU,sBAAsB;SACrH,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,KAAK,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjD,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE,CAAC;YACpC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,UAAU,KAAK,6BAA6B,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI;KACtF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAqB;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAExC,KAAK,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;QACrC,+EAA+E;QAC/E,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;YACtB,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,EAAE,CAAC,OAAO,KAAK,GAAG,CAAC,aAAa,EAAE,CAAC;gBAC3F,SAAS;YACX,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;YAC/D,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAoC;IACjE,OAAO,MAAM,IAAI,MAAM,CAAC;AAC1B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tower-routes.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAa7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAIxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tower-routes.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAa7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAIxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAoElD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAMtG;AAED,sGAAsG;AACtG,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAMD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IAChD,qBAAqB,EAAE,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjH,YAAY,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAsCD,wBAAsB,aAAa,CACjC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,IAAI,CAAC,CAmFf"}
|
|
@@ -17,7 +17,7 @@ import crypto from 'node:crypto';
|
|
|
17
17
|
import { exec } from 'node:child_process';
|
|
18
18
|
import { promisify } from 'node:util';
|
|
19
19
|
import { homedir, tmpdir } from 'node:os';
|
|
20
|
-
import {
|
|
20
|
+
import { decodeWorkspacePath } from '../lib/tower-client.js';
|
|
21
21
|
import { readCloudConfig } from '../lib/cloud-config.js';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { version } from '../../version.js';
|
|
@@ -32,12 +32,12 @@ import { resolveTarget, broadcastMessage, isResolveError } from './tower-message
|
|
|
32
32
|
import { formatArchitectMessage, formatBuilderMessage } from '../utils/message-format.js';
|
|
33
33
|
import { SendBuffer } from './send-buffer.js';
|
|
34
34
|
import { writeMessageToSession } from './message-write.js';
|
|
35
|
-
import { getKnownWorkspacePaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShellper, stopInstance, } from './tower-instances.js';
|
|
35
|
+
import { getKnownWorkspacePaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShellper, stopInstance, addArchitect, } from './tower-instances.js';
|
|
36
36
|
import { OverviewCache } from './overview.js';
|
|
37
37
|
import { computeAnalytics } from './analytics.js';
|
|
38
38
|
import { getAllTasks, executeTask, getTaskId } from './tower-cron.js';
|
|
39
39
|
import { getGlobalDb } from '../db/index.js';
|
|
40
|
-
import { getWorkspaceTerminals, getTerminalManager, getWorkspaceTerminalsEntry, getNextShellId, saveTerminalSession, isSessionPersistent, deleteTerminalSession, removeTerminalFromRegistry, deleteWorkspaceTerminalSessions, deleteFileTabsForWorkspace, saveFileTab, deleteFileTab,
|
|
40
|
+
import { getWorkspaceTerminals, getTerminalManager, getWorkspaceTerminalsEntry, getNextShellId, saveTerminalSession, isSessionPersistent, deleteTerminalSession, removeTerminalFromRegistry, deleteWorkspaceTerminalSessions, deleteFileTabsForWorkspace, saveFileTab, deleteFileTab, getRehydratedTerminalsEntry, getTerminalSessionById, getActiveShellLabels, updateTerminalLabel, } from './tower-terminals.js';
|
|
41
41
|
const __filename = fileURLToPath(import.meta.url);
|
|
42
42
|
const __dirname = path.dirname(__filename);
|
|
43
43
|
// Singleton cache for overview endpoint (Spec 0126 Phase 4)
|
|
@@ -124,6 +124,11 @@ export async function handleRequest(req, res, ctx) {
|
|
|
124
124
|
if (workspaceApiMatch) {
|
|
125
125
|
return await handleWorkspaceAction(req, res, ctx, workspaceApiMatch);
|
|
126
126
|
}
|
|
127
|
+
// Workspace API: /api/workspaces/:encodedPath/architects (Spec 755 — multi-architect)
|
|
128
|
+
const architectsMatch = url.pathname.match(/^\/api\/workspaces\/([^/]+)\/architects$/);
|
|
129
|
+
if (architectsMatch) {
|
|
130
|
+
return await handleAddArchitect(req, res, architectsMatch);
|
|
131
|
+
}
|
|
127
132
|
// Terminal-specific routes: /api/terminals/:id/* (Spec 0090 Phase 2)
|
|
128
133
|
const terminalRouteMatch = url.pathname.match(/^\/api\/terminals\/([^/]+)(\/.*)?$/);
|
|
129
134
|
if (terminalRouteMatch) {
|
|
@@ -176,6 +181,54 @@ async function handleListWorkspaces(res) {
|
|
|
176
181
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
177
182
|
res.end(JSON.stringify({ workspaces }));
|
|
178
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* POST /api/workspaces/:encodedPath/architects (Spec 755)
|
|
186
|
+
* Body: { name?: string }
|
|
187
|
+
* Adds a named architect terminal to an active workspace.
|
|
188
|
+
* Returns 200 { success: true, name, terminalId } on success,
|
|
189
|
+
* 400 / 404 with { success: false, error } otherwise.
|
|
190
|
+
*/
|
|
191
|
+
async function handleAddArchitect(req, res, match) {
|
|
192
|
+
if (req.method !== 'POST') {
|
|
193
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
194
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const [, encodedPath] = match;
|
|
198
|
+
let workspacePath;
|
|
199
|
+
try {
|
|
200
|
+
workspacePath = decodeWorkspacePath(encodedPath);
|
|
201
|
+
if (!workspacePath || (!workspacePath.startsWith('/') && !/^[A-Za-z]:[\\/]/.test(workspacePath))) {
|
|
202
|
+
throw new Error('Invalid path');
|
|
203
|
+
}
|
|
204
|
+
workspacePath = normalizeWorkspacePath(workspacePath);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
208
|
+
res.end(JSON.stringify({ error: 'Invalid workspace path encoding' }));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
let body;
|
|
212
|
+
try {
|
|
213
|
+
body = (await parseJsonBody(req));
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
217
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const result = await addArchitect(workspacePath, body.name);
|
|
221
|
+
if (result.success) {
|
|
222
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
223
|
+
res.end(JSON.stringify({ success: true, name: result.name, terminalId: result.terminalId }));
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// Distinguish "workspace not active" (404) from validation errors (400).
|
|
227
|
+
const status = result.error?.includes('not running') ? 404 : 400;
|
|
228
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
229
|
+
res.end(JSON.stringify({ success: false, error: result.error }));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
179
232
|
async function handleWorkspaceAction(req, res, ctx, match) {
|
|
180
233
|
const [, encodedPath, action] = match;
|
|
181
234
|
let workspacePath;
|
|
@@ -265,7 +318,11 @@ async function handleTerminalCreate(req, res, ctx) {
|
|
|
265
318
|
const cwd = typeof body.cwd === 'string' ? body.cwd : undefined;
|
|
266
319
|
const env = typeof body.env === 'object' && body.env !== null ? body.env : undefined;
|
|
267
320
|
const label = typeof body.label === 'string' ? body.label : undefined;
|
|
268
|
-
// Optional session persistence via shellper
|
|
321
|
+
// Optional session persistence via shellper.
|
|
322
|
+
// The whitelist below is the gate that keeps `'dev'` (ephemeral dev PTYs)
|
|
323
|
+
// out of SQLite and shellper — the dev process dies with Tower, so
|
|
324
|
+
// persisting a row would point at a non-existent process. Add new
|
|
325
|
+
// persistent terminal kinds here if you ever introduce one.
|
|
269
326
|
const workspacePath = typeof body.workspacePath === 'string' ? body.workspacePath : null;
|
|
270
327
|
const termType = typeof body.type === 'string' && ['builder', 'shell'].includes(body.type) ? body.type : null;
|
|
271
328
|
const roleId = typeof body.roleId === 'string' ? body.roleId : null;
|
|
@@ -531,14 +588,15 @@ async function handleOverview(res, url, workspaceOverride) {
|
|
|
531
588
|
res.end(JSON.stringify({ builders: [], pendingPRs: [], backlog: [] }));
|
|
532
589
|
return;
|
|
533
590
|
}
|
|
534
|
-
// Build set of active builder role_ids (lowercased) from live terminal sessions
|
|
535
|
-
|
|
536
|
-
|
|
591
|
+
// Build set of active builder role_ids (lowercased) from live terminal sessions.
|
|
592
|
+
// Bugfix #718: rehydrate the wsTerminals entry from SQLite + shellper before
|
|
593
|
+
// reading, matching what /api/state has always done. Without this the sidebar
|
|
594
|
+
// diverges from the dashboard after any state-loss event (Tower restart with
|
|
595
|
+
// non-shellper sessions, crashed builders, etc.).
|
|
596
|
+
const entry = await getRehydratedTerminalsEntry(workspaceRoot);
|
|
537
597
|
const activeBuilderRoleIds = new Set();
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
activeBuilderRoleIds.add(key.toLowerCase());
|
|
541
|
-
}
|
|
598
|
+
for (const key of entry.builders.keys()) {
|
|
599
|
+
activeBuilderRoleIds.add(key.toLowerCase());
|
|
542
600
|
}
|
|
543
601
|
const data = await overviewCache.getOverview(workspaceRoot, activeBuilderRoleIds);
|
|
544
602
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -654,8 +712,10 @@ async function handleSend(req, res, ctx) {
|
|
|
654
712
|
const raw = options.raw === true;
|
|
655
713
|
const noEnter = options.noEnter === true;
|
|
656
714
|
const interrupt = options.interrupt === true;
|
|
657
|
-
// Resolve the target address to a terminal ID
|
|
658
|
-
|
|
715
|
+
// Resolve the target address to a terminal ID.
|
|
716
|
+
// Spec 755: pass `from` so architect resolution is sender-affinity-aware
|
|
717
|
+
// when the sender is a builder. Non-builder senders see unchanged behavior.
|
|
718
|
+
const result = resolveTarget(to, workspace, from);
|
|
659
719
|
if (isResolveError(result)) {
|
|
660
720
|
const statusCode = result.code === 'AMBIGUOUS' ? 409
|
|
661
721
|
: result.code === 'NO_CONTEXT' ? 400
|
|
@@ -1110,13 +1170,10 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
1110
1170
|
// Workspace API sub-handlers
|
|
1111
1171
|
// ============================================================================
|
|
1112
1172
|
async function handleWorkspaceState(res, workspacePath) {
|
|
1113
|
-
//
|
|
1114
|
-
//
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
await getTerminalsForWorkspace(workspacePath, proxyUrl);
|
|
1118
|
-
// Now read from the refreshed cache
|
|
1119
|
-
const entry = getWorkspaceTerminalsEntry(workspacePath);
|
|
1173
|
+
// Rehydrate wsTerminals (SQLite sync + shellper reconnect) before reading.
|
|
1174
|
+
// Shared with /api/overview so both endpoints always see a freshly-reconciled
|
|
1175
|
+
// entry — see getRehydratedTerminalsEntry doc.
|
|
1176
|
+
const entry = await getRehydratedTerminalsEntry(workspacePath);
|
|
1120
1177
|
const manager = getTerminalManager();
|
|
1121
1178
|
const state = {
|
|
1122
1179
|
architect: null,
|
|
@@ -1133,15 +1190,18 @@ async function handleWorkspaceState(res, workspacePath) {
|
|
|
1133
1190
|
} })(),
|
|
1134
1191
|
teamEnabled: await hasTeam(path.join(workspacePath, 'codev', 'team')),
|
|
1135
1192
|
};
|
|
1136
|
-
// Add architect if exists
|
|
1137
|
-
|
|
1138
|
-
|
|
1193
|
+
// Add architect if exists.
|
|
1194
|
+
// Spec 755: /api/state preserves the scalar `state.architect` shape.
|
|
1195
|
+
// Prefer 'main', else first registered architect by name.
|
|
1196
|
+
const architectTerminalId = entry.architects.get('main') ?? entry.architects.values().next().value;
|
|
1197
|
+
if (architectTerminalId) {
|
|
1198
|
+
const session = manager.getSession(architectTerminalId);
|
|
1139
1199
|
if (session) {
|
|
1140
1200
|
state.architect = {
|
|
1141
1201
|
port: 0,
|
|
1142
1202
|
pid: session.pid || 0,
|
|
1143
|
-
terminalId:
|
|
1144
|
-
persistent: isSessionPersistent(
|
|
1203
|
+
terminalId: architectTerminalId,
|
|
1204
|
+
persistent: isSessionPersistent(architectTerminalId, session),
|
|
1145
1205
|
};
|
|
1146
1206
|
}
|
|
1147
1207
|
}
|
|
@@ -1504,9 +1564,14 @@ async function handleWorkspaceTabDelete(res, ctx, workspacePath, tabId) {
|
|
|
1504
1564
|
}
|
|
1505
1565
|
}
|
|
1506
1566
|
else if (tabId === 'architect') {
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1567
|
+
// Spec 755: tabId 'architect' targets the architect surfaced by /api/state
|
|
1568
|
+
// — prefer 'main', else the first registered.
|
|
1569
|
+
const name = entry.architects.has('main')
|
|
1570
|
+
? 'main'
|
|
1571
|
+
: entry.architects.keys().next().value;
|
|
1572
|
+
if (name) {
|
|
1573
|
+
terminalId = entry.architects.get(name);
|
|
1574
|
+
entry.architects.delete(name);
|
|
1510
1575
|
}
|
|
1511
1576
|
}
|
|
1512
1577
|
if (terminalId) {
|
|
@@ -1525,9 +1590,10 @@ async function handleWorkspaceTabDelete(res, ctx, workspacePath, tabId) {
|
|
|
1525
1590
|
async function handleWorkspaceStopAll(res, workspacePath) {
|
|
1526
1591
|
const entry = getWorkspaceTerminalsEntry(workspacePath);
|
|
1527
1592
|
const manager = getTerminalManager();
|
|
1528
|
-
// Kill all terminals (disable shellper auto-restart if applicable)
|
|
1529
|
-
|
|
1530
|
-
|
|
1593
|
+
// Kill all terminals (disable shellper auto-restart if applicable).
|
|
1594
|
+
// Spec 755: iterate all named architects, not just the singleton.
|
|
1595
|
+
for (const terminalId of entry.architects.values()) {
|
|
1596
|
+
await killTerminalWithShellper(manager, terminalId);
|
|
1531
1597
|
}
|
|
1532
1598
|
for (const terminalId of entry.shells.values()) {
|
|
1533
1599
|
await killTerminalWithShellper(manager, terminalId);
|