@launch11/srgical 0.0.1
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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/commands/doctor.js +68 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/run-next.js +54 -0
- package/dist/commands/studio.js +7 -0
- package/dist/core/agent.js +164 -0
- package/dist/core/claude.js +271 -0
- package/dist/core/codex.js +257 -0
- package/dist/core/execution-controls.js +87 -0
- package/dist/core/execution-state.js +87 -0
- package/dist/core/local-pack.js +125 -0
- package/dist/core/planning-epochs.js +116 -0
- package/dist/core/planning-pack-state.js +121 -0
- package/dist/core/prompts.js +230 -0
- package/dist/core/studio-session.js +99 -0
- package/dist/core/templates.js +189 -0
- package/dist/core/workspace.js +67 -0
- package/dist/index.js +60 -0
- package/dist/ui/studio.js +704 -0
- package/docs/adr/0001-tech-stack.md +27 -0
- package/docs/distribution.md +129 -0
- package/docs/product-foundation.md +50 -0
- package/docs/testing-strategy.md +96 -0
- package/package.json +64 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.launchStudio = launchStudio;
|
|
7
|
+
exports.formatTrackerSummary = formatTrackerSummary;
|
|
8
|
+
exports.buildStudioHeaderContent = buildStudioHeaderContent;
|
|
9
|
+
exports.formatPlanningPackSummary = formatPlanningPackSummary;
|
|
10
|
+
exports.renderWorkspaceSelectionMessage = renderWorkspaceSelectionMessage;
|
|
11
|
+
exports.resolveStudioWorkspaceInput = resolveStudioWorkspaceInput;
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const blessed_1 = __importDefault(require("blessed"));
|
|
14
|
+
const agent_1 = require("../core/agent");
|
|
15
|
+
const execution_controls_1 = require("../core/execution-controls");
|
|
16
|
+
const execution_state_1 = require("../core/execution-state");
|
|
17
|
+
const planning_pack_state_1 = require("../core/planning-pack-state");
|
|
18
|
+
const studio_session_1 = require("../core/studio-session");
|
|
19
|
+
const workspace_1 = require("../core/workspace");
|
|
20
|
+
const READY_FOOTER = " Enter send message PgUp/PgDn scroll /workspace switch repo /agents tools /write save plan /preview safe preview /run execute next step /quit exit ";
|
|
21
|
+
const ACTIVITY_FRAMES = ["[ ]", "[= ]", "[== ]", "[=== ]", "[ ===]", "[ ==]", "[ =]"];
|
|
22
|
+
async function launchStudio(options = {}) {
|
|
23
|
+
let workspace = (0, workspace_1.resolveWorkspace)(options.workspace);
|
|
24
|
+
let messages = await (0, studio_session_1.loadStudioSession)(workspace);
|
|
25
|
+
const screen = blessed_1.default.screen({
|
|
26
|
+
smartCSR: true,
|
|
27
|
+
fullUnicode: true,
|
|
28
|
+
title: "srgical studio"
|
|
29
|
+
});
|
|
30
|
+
const header = blessed_1.default.box({
|
|
31
|
+
top: 0,
|
|
32
|
+
left: 0,
|
|
33
|
+
width: "100%",
|
|
34
|
+
height: 3,
|
|
35
|
+
tags: true,
|
|
36
|
+
style: {
|
|
37
|
+
fg: "#fff8ef",
|
|
38
|
+
bg: "#1b1a1f"
|
|
39
|
+
},
|
|
40
|
+
content: buildStudioHeaderContent(workspace, null)
|
|
41
|
+
});
|
|
42
|
+
const transcript = blessed_1.default.box({
|
|
43
|
+
top: 3,
|
|
44
|
+
left: 0,
|
|
45
|
+
width: "72%",
|
|
46
|
+
height: "100%-7",
|
|
47
|
+
tags: true,
|
|
48
|
+
scrollable: true,
|
|
49
|
+
alwaysScroll: true,
|
|
50
|
+
keys: true,
|
|
51
|
+
vi: true,
|
|
52
|
+
padding: {
|
|
53
|
+
top: 1,
|
|
54
|
+
right: 2,
|
|
55
|
+
bottom: 1,
|
|
56
|
+
left: 2
|
|
57
|
+
},
|
|
58
|
+
border: {
|
|
59
|
+
type: "line"
|
|
60
|
+
},
|
|
61
|
+
label: " Transcript ",
|
|
62
|
+
style: {
|
|
63
|
+
fg: "#f7efe6",
|
|
64
|
+
bg: "#141318",
|
|
65
|
+
border: {
|
|
66
|
+
fg: "#ff7a59"
|
|
67
|
+
},
|
|
68
|
+
scrollbar: {
|
|
69
|
+
bg: "#ff7a59"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const sidebar = blessed_1.default.box({
|
|
74
|
+
top: 3,
|
|
75
|
+
left: "72%",
|
|
76
|
+
width: "28%",
|
|
77
|
+
height: "100%-7",
|
|
78
|
+
tags: true,
|
|
79
|
+
padding: {
|
|
80
|
+
top: 1,
|
|
81
|
+
right: 1,
|
|
82
|
+
bottom: 1,
|
|
83
|
+
left: 1
|
|
84
|
+
},
|
|
85
|
+
border: {
|
|
86
|
+
type: "line"
|
|
87
|
+
},
|
|
88
|
+
label: " Control Room ",
|
|
89
|
+
style: {
|
|
90
|
+
fg: "#d9fff8",
|
|
91
|
+
bg: "#11161c",
|
|
92
|
+
border: {
|
|
93
|
+
fg: "#4de2c5"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const input = blessed_1.default.textbox({
|
|
98
|
+
bottom: 1,
|
|
99
|
+
left: 0,
|
|
100
|
+
width: "100%",
|
|
101
|
+
height: 3,
|
|
102
|
+
inputOnFocus: true,
|
|
103
|
+
border: {
|
|
104
|
+
type: "line"
|
|
105
|
+
},
|
|
106
|
+
label: " Message / Command ",
|
|
107
|
+
style: {
|
|
108
|
+
fg: "#fff8ef",
|
|
109
|
+
bg: "#1a1112",
|
|
110
|
+
border: {
|
|
111
|
+
fg: "#ffb14a"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
const footer = blessed_1.default.box({
|
|
116
|
+
bottom: 0,
|
|
117
|
+
left: 0,
|
|
118
|
+
width: "100%",
|
|
119
|
+
height: 1,
|
|
120
|
+
tags: true,
|
|
121
|
+
style: {
|
|
122
|
+
fg: "#bfb8c7",
|
|
123
|
+
bg: "#1b1a1f"
|
|
124
|
+
},
|
|
125
|
+
content: READY_FOOTER
|
|
126
|
+
});
|
|
127
|
+
screen.append(header);
|
|
128
|
+
screen.append(transcript);
|
|
129
|
+
screen.append(sidebar);
|
|
130
|
+
screen.append(input);
|
|
131
|
+
screen.append(footer);
|
|
132
|
+
let busy = false;
|
|
133
|
+
let busyMode = null;
|
|
134
|
+
let busyStartedAt = null;
|
|
135
|
+
let activityFrameIndex = 0;
|
|
136
|
+
let activityTimer;
|
|
137
|
+
let latestPackState = null;
|
|
138
|
+
let agentSummary = "checking...";
|
|
139
|
+
let planningPackSummary = "not checked";
|
|
140
|
+
let trackerSummary = "loading...";
|
|
141
|
+
let executionSummary = "never run";
|
|
142
|
+
function setSidebar(status) {
|
|
143
|
+
const planningPaths = (0, workspace_1.getPlanningPackPaths)(workspace);
|
|
144
|
+
sidebar.setContent([
|
|
145
|
+
"{bold}Workspace{/bold}",
|
|
146
|
+
`root: ${workspace}`,
|
|
147
|
+
`plan dir: ${planningPaths.dir}`,
|
|
148
|
+
"",
|
|
149
|
+
"{bold}Agent{/bold}",
|
|
150
|
+
agentSummary,
|
|
151
|
+
"",
|
|
152
|
+
"{bold}Planning Pack{/bold}",
|
|
153
|
+
planningPackSummary,
|
|
154
|
+
"",
|
|
155
|
+
"{bold}Tracker{/bold}",
|
|
156
|
+
trackerSummary,
|
|
157
|
+
"",
|
|
158
|
+
"{bold}Last Run{/bold}",
|
|
159
|
+
executionSummary,
|
|
160
|
+
"",
|
|
161
|
+
"{bold}Commands{/bold}",
|
|
162
|
+
"/workspace [path] inspect or switch planning view",
|
|
163
|
+
"/agents list supported tools and the current session choice",
|
|
164
|
+
"/agent <id> switch the active agent for this workspace",
|
|
165
|
+
"/write put the current plan on disk",
|
|
166
|
+
"/preview inspect the next execution without invoking the active agent",
|
|
167
|
+
"/run execute .srgical/04-next-agent-prompt.md",
|
|
168
|
+
"/help show the workflow and key controls",
|
|
169
|
+
"/quit close the studio",
|
|
170
|
+
"",
|
|
171
|
+
"{bold}State{/bold}",
|
|
172
|
+
status ?? getActivityState()
|
|
173
|
+
].join("\n"));
|
|
174
|
+
}
|
|
175
|
+
function setFooter(status) {
|
|
176
|
+
footer.setContent(status ?? getFooterContent());
|
|
177
|
+
}
|
|
178
|
+
function getActivityState() {
|
|
179
|
+
if (!busy || !busyMode || busyStartedAt === null) {
|
|
180
|
+
return "ready";
|
|
181
|
+
}
|
|
182
|
+
return `${getActivityFrame()} ${describeBusyMode(busyMode)} (${formatElapsed(Date.now() - busyStartedAt)})`;
|
|
183
|
+
}
|
|
184
|
+
function getFooterContent() {
|
|
185
|
+
if (!busy || !busyMode || busyStartedAt === null) {
|
|
186
|
+
return READY_FOOTER;
|
|
187
|
+
}
|
|
188
|
+
return ` ${getActivityFrame()} ${describeBusyMode(busyMode)} elapsed ${formatElapsed(Date.now() - busyStartedAt)} planner and agent calls can take a moment `;
|
|
189
|
+
}
|
|
190
|
+
function getActivityFrame() {
|
|
191
|
+
return ACTIVITY_FRAMES[activityFrameIndex % ACTIVITY_FRAMES.length];
|
|
192
|
+
}
|
|
193
|
+
function startBusy(mode, status) {
|
|
194
|
+
busy = true;
|
|
195
|
+
busyMode = mode;
|
|
196
|
+
busyStartedAt = Date.now();
|
|
197
|
+
activityFrameIndex = 0;
|
|
198
|
+
stopActivityTimer();
|
|
199
|
+
setSidebar(status);
|
|
200
|
+
setFooter(status);
|
|
201
|
+
screen.render();
|
|
202
|
+
activityTimer = setInterval(() => {
|
|
203
|
+
activityFrameIndex += 1;
|
|
204
|
+
setSidebar(status);
|
|
205
|
+
setFooter(status);
|
|
206
|
+
screen.render();
|
|
207
|
+
}, 1000);
|
|
208
|
+
}
|
|
209
|
+
function stopBusy() {
|
|
210
|
+
busy = false;
|
|
211
|
+
busyMode = null;
|
|
212
|
+
busyStartedAt = null;
|
|
213
|
+
activityFrameIndex = 0;
|
|
214
|
+
stopActivityTimer();
|
|
215
|
+
}
|
|
216
|
+
function stopActivityTimer() {
|
|
217
|
+
if (activityTimer) {
|
|
218
|
+
clearInterval(activityTimer);
|
|
219
|
+
activityTimer = undefined;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function renderTranscript() {
|
|
223
|
+
const rendered = messages
|
|
224
|
+
.map((message) => {
|
|
225
|
+
const tone = message.role === "user"
|
|
226
|
+
? "{#ffb14a-fg}YOU{/}"
|
|
227
|
+
: message.role === "assistant"
|
|
228
|
+
? "{#4de2c5-fg}PLANNER{/}"
|
|
229
|
+
: "{#ff7a59-fg}SYSTEM{/}";
|
|
230
|
+
return `${tone}\n${message.content}`;
|
|
231
|
+
})
|
|
232
|
+
.join("\n\n");
|
|
233
|
+
transcript.setContent(rendered);
|
|
234
|
+
transcript.setScrollPerc(100);
|
|
235
|
+
}
|
|
236
|
+
function scrollTranscript(lines) {
|
|
237
|
+
transcript.scroll(lines);
|
|
238
|
+
screen.render();
|
|
239
|
+
}
|
|
240
|
+
async function appendMessage(message) {
|
|
241
|
+
messages.push(message);
|
|
242
|
+
await (0, studio_session_1.saveStudioSession)(workspace, messages);
|
|
243
|
+
}
|
|
244
|
+
async function refreshEnvironment() {
|
|
245
|
+
const [storedAgentId, packState] = await Promise.all([
|
|
246
|
+
(0, studio_session_1.loadStoredActiveAgentId)(workspace),
|
|
247
|
+
(0, planning_pack_state_1.readPlanningPackState)(workspace)
|
|
248
|
+
]);
|
|
249
|
+
let agentState = await (0, agent_1.resolvePrimaryAgent)(workspace);
|
|
250
|
+
if (!storedAgentId) {
|
|
251
|
+
const availableAgents = agentState.statuses.filter((status) => status.available);
|
|
252
|
+
if (availableAgents.length === 1) {
|
|
253
|
+
agentState = await (0, agent_1.selectPrimaryAgent)(workspace, availableAgents[0].id);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
latestPackState = packState;
|
|
257
|
+
header.setContent(buildStudioHeaderContent(workspace, packState));
|
|
258
|
+
agentSummary = formatAgentSummary(agentState.status, agentState.statuses);
|
|
259
|
+
planningPackSummary = formatPlanningPackSummary(workspace, packState);
|
|
260
|
+
trackerSummary = formatTrackerSummary(packState.currentPosition);
|
|
261
|
+
executionSummary = formatExecutionSummary(packState.lastExecution);
|
|
262
|
+
setSidebar();
|
|
263
|
+
setFooter();
|
|
264
|
+
renderTranscript();
|
|
265
|
+
screen.render();
|
|
266
|
+
}
|
|
267
|
+
async function handleSlashCommand(command) {
|
|
268
|
+
if (command === "/quit") {
|
|
269
|
+
screen.destroy();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (command === "/workspace") {
|
|
273
|
+
const packState = latestPackState ?? (await (0, planning_pack_state_1.readPlanningPackState)(workspace));
|
|
274
|
+
await appendMessage({
|
|
275
|
+
role: "system",
|
|
276
|
+
content: renderWorkspaceSelectionMessage(workspace, packState)
|
|
277
|
+
});
|
|
278
|
+
renderTranscript();
|
|
279
|
+
setSidebar();
|
|
280
|
+
setFooter();
|
|
281
|
+
screen.render();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (command.startsWith("/workspace")) {
|
|
285
|
+
const requestedWorkspace = command.slice("/workspace".length).trim();
|
|
286
|
+
if (!requestedWorkspace) {
|
|
287
|
+
const packState = latestPackState ?? (await (0, planning_pack_state_1.readPlanningPackState)(workspace));
|
|
288
|
+
await appendMessage({
|
|
289
|
+
role: "system",
|
|
290
|
+
content: renderWorkspaceSelectionMessage(workspace, packState)
|
|
291
|
+
});
|
|
292
|
+
renderTranscript();
|
|
293
|
+
setSidebar();
|
|
294
|
+
setFooter();
|
|
295
|
+
screen.render();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const nextWorkspace = resolveStudioWorkspaceInput(workspace, requestedWorkspace);
|
|
299
|
+
setSidebar("switching planning view...");
|
|
300
|
+
setFooter(" Switching planning view... ");
|
|
301
|
+
screen.render();
|
|
302
|
+
workspace = nextWorkspace;
|
|
303
|
+
messages = await (0, studio_session_1.loadStudioSession)(workspace);
|
|
304
|
+
await refreshEnvironment();
|
|
305
|
+
await appendMessage({
|
|
306
|
+
role: "system",
|
|
307
|
+
content: [
|
|
308
|
+
`Now looking at ${workspace}.`,
|
|
309
|
+
"",
|
|
310
|
+
renderWorkspaceSelectionMessage(workspace, latestPackState ?? (await (0, planning_pack_state_1.readPlanningPackState)(workspace)))
|
|
311
|
+
].join("\n")
|
|
312
|
+
});
|
|
313
|
+
renderTranscript();
|
|
314
|
+
setSidebar();
|
|
315
|
+
setFooter();
|
|
316
|
+
screen.render();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (command === "/agents") {
|
|
320
|
+
const agentState = await (0, agent_1.resolvePrimaryAgent)(workspace);
|
|
321
|
+
await appendMessage({
|
|
322
|
+
role: "system",
|
|
323
|
+
content: renderAgentSelectionMessage(agentState.status, agentState.statuses)
|
|
324
|
+
});
|
|
325
|
+
renderTranscript();
|
|
326
|
+
setSidebar();
|
|
327
|
+
setFooter();
|
|
328
|
+
screen.render();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (command.startsWith("/agent")) {
|
|
332
|
+
const requestedId = command.slice("/agent".length).trim().toLowerCase();
|
|
333
|
+
if (!requestedId) {
|
|
334
|
+
const agentState = await (0, agent_1.resolvePrimaryAgent)(workspace);
|
|
335
|
+
await appendMessage({
|
|
336
|
+
role: "system",
|
|
337
|
+
content: [
|
|
338
|
+
"Usage: `/agent codex` or `/agent claude`",
|
|
339
|
+
"",
|
|
340
|
+
renderAgentSelectionMessage(agentState.status, agentState.statuses)
|
|
341
|
+
].join("\n")
|
|
342
|
+
});
|
|
343
|
+
renderTranscript();
|
|
344
|
+
setSidebar();
|
|
345
|
+
setFooter();
|
|
346
|
+
screen.render();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
setSidebar("updating active agent...");
|
|
350
|
+
setFooter(" Updating active agent selection... ");
|
|
351
|
+
screen.render();
|
|
352
|
+
try {
|
|
353
|
+
const agentState = await (0, agent_1.selectPrimaryAgent)(workspace, requestedId);
|
|
354
|
+
await appendMessage({
|
|
355
|
+
role: "system",
|
|
356
|
+
content: [
|
|
357
|
+
`Active agent set to ${agentState.status.label} for this workspace session.`,
|
|
358
|
+
"",
|
|
359
|
+
renderAgentSelectionMessage(agentState.status, agentState.statuses)
|
|
360
|
+
].join("\n")
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
await appendMessage({
|
|
365
|
+
role: "system",
|
|
366
|
+
content: `Agent selection failed: ${error instanceof Error ? error.message : String(error)}`
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
await refreshEnvironment();
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (command === "/help") {
|
|
375
|
+
await appendMessage({
|
|
376
|
+
role: "system",
|
|
377
|
+
content: [
|
|
378
|
+
"Workflow:",
|
|
379
|
+
"1. Talk normally to sharpen the plan against the real repo.",
|
|
380
|
+
"2. Run `/workspace <path>` when you want to change which repo and planning directory you are looking at.",
|
|
381
|
+
"3. Run `/agents` to inspect available tools and `/agent <id>` when you want to switch the workspace session.",
|
|
382
|
+
"4. Run `/write` when the shape is ready to put the plan on disk inside `.srgical/`.",
|
|
383
|
+
"5. Run `/preview` to inspect the next execution safely before writes happen.",
|
|
384
|
+
"6. Run `/run` when you want the active agent to execute the next eligible tracker block.",
|
|
385
|
+
"",
|
|
386
|
+
"Controls:",
|
|
387
|
+
"- `Enter` sends the current message.",
|
|
388
|
+
"- `PageUp` and `PageDown` scroll the transcript.",
|
|
389
|
+
"- `/quit` closes the studio."
|
|
390
|
+
].join("\n")
|
|
391
|
+
});
|
|
392
|
+
renderTranscript();
|
|
393
|
+
setSidebar();
|
|
394
|
+
setFooter();
|
|
395
|
+
screen.render();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (command === "/preview") {
|
|
399
|
+
const paths = (0, workspace_1.getPlanningPackPaths)(workspace);
|
|
400
|
+
const packState = await (0, planning_pack_state_1.readPlanningPackState)(workspace);
|
|
401
|
+
if (!packState.packPresent) {
|
|
402
|
+
await appendMessage({
|
|
403
|
+
role: "system",
|
|
404
|
+
content: "Execution preview unavailable: no .srgical planning pack was found yet."
|
|
405
|
+
});
|
|
406
|
+
renderTranscript();
|
|
407
|
+
setSidebar();
|
|
408
|
+
setFooter();
|
|
409
|
+
screen.render();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const prompt = await (0, workspace_1.readText)(paths.nextPrompt);
|
|
413
|
+
await appendMessage({
|
|
414
|
+
role: "system",
|
|
415
|
+
content: (0, execution_controls_1.renderDryRunPreview)(prompt, packState.nextStepSummary, packState.currentPosition.nextRecommended).join("\n")
|
|
416
|
+
});
|
|
417
|
+
renderTranscript();
|
|
418
|
+
setSidebar();
|
|
419
|
+
setFooter();
|
|
420
|
+
screen.render();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (command === "/write") {
|
|
424
|
+
startBusy("pack");
|
|
425
|
+
try {
|
|
426
|
+
const result = await (0, agent_1.writePlanningPack)(workspace, messages);
|
|
427
|
+
await appendMessage({
|
|
428
|
+
role: "system",
|
|
429
|
+
content: `Planning pack updated. Summary:\n${result}`
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
await appendMessage({
|
|
434
|
+
role: "system",
|
|
435
|
+
content: `Pack generation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
stopBusy();
|
|
440
|
+
await refreshEnvironment();
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (command === "/run") {
|
|
445
|
+
const paths = (0, workspace_1.getPlanningPackPaths)(workspace);
|
|
446
|
+
const packState = await (0, planning_pack_state_1.readPlanningPackState)(workspace);
|
|
447
|
+
if (!(0, execution_controls_1.hasQueuedNextStep)(packState.currentPosition.nextRecommended)) {
|
|
448
|
+
await appendMessage({
|
|
449
|
+
role: "system",
|
|
450
|
+
content: (0, execution_controls_1.formatNoQueuedNextStepMessage)("studio")
|
|
451
|
+
});
|
|
452
|
+
renderTranscript();
|
|
453
|
+
setSidebar();
|
|
454
|
+
setFooter();
|
|
455
|
+
screen.render();
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
startBusy("run");
|
|
459
|
+
try {
|
|
460
|
+
const prompt = await (0, workspace_1.readText)(paths.nextPrompt);
|
|
461
|
+
const result = await (0, agent_1.runNextPrompt)(workspace, prompt);
|
|
462
|
+
await (0, execution_state_1.saveExecutionState)(workspace, "success", "studio", result);
|
|
463
|
+
await appendMessage({
|
|
464
|
+
role: "system",
|
|
465
|
+
content: `Execution run finished. ${(0, agent_1.getPrimaryAgentAdapter)().label} summary:\n${result}`
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
470
|
+
await (0, execution_state_1.saveExecutionState)(workspace, "failure", "studio", message);
|
|
471
|
+
const refreshedPackState = await (0, planning_pack_state_1.readPlanningPackState)(workspace);
|
|
472
|
+
await appendMessage({
|
|
473
|
+
role: "system",
|
|
474
|
+
content: (0, execution_controls_1.formatExecutionFailureMessage)(message, refreshedPackState.nextStepSummary, refreshedPackState.currentPosition.nextRecommended, "studio")
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
finally {
|
|
478
|
+
stopBusy();
|
|
479
|
+
await refreshEnvironment();
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
await appendMessage({
|
|
484
|
+
role: "system",
|
|
485
|
+
content: `Unknown command: ${command}`
|
|
486
|
+
});
|
|
487
|
+
renderTranscript();
|
|
488
|
+
setSidebar();
|
|
489
|
+
setFooter();
|
|
490
|
+
screen.render();
|
|
491
|
+
}
|
|
492
|
+
input.on("submit", async (value) => {
|
|
493
|
+
const text = value.trim();
|
|
494
|
+
input.clearValue();
|
|
495
|
+
if (!text || busy) {
|
|
496
|
+
screen.render();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (text.startsWith("/")) {
|
|
500
|
+
await handleSlashCommand(text);
|
|
501
|
+
input.focus();
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
startBusy("planner");
|
|
505
|
+
await appendMessage({
|
|
506
|
+
role: "user",
|
|
507
|
+
content: text
|
|
508
|
+
});
|
|
509
|
+
renderTranscript();
|
|
510
|
+
setSidebar();
|
|
511
|
+
setFooter();
|
|
512
|
+
screen.render();
|
|
513
|
+
try {
|
|
514
|
+
const reply = await (0, agent_1.requestPlannerReply)(workspace, messages);
|
|
515
|
+
await appendMessage({
|
|
516
|
+
role: "assistant",
|
|
517
|
+
content: reply
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
await appendMessage({
|
|
522
|
+
role: "system",
|
|
523
|
+
content: `Planner call failed: ${error instanceof Error ? error.message : String(error)}`
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
stopBusy();
|
|
528
|
+
renderTranscript();
|
|
529
|
+
setSidebar();
|
|
530
|
+
setFooter();
|
|
531
|
+
input.focus();
|
|
532
|
+
screen.render();
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
screen.key(["C-c"], () => {
|
|
536
|
+
stopBusy();
|
|
537
|
+
screen.destroy();
|
|
538
|
+
});
|
|
539
|
+
for (const element of [screen, transcript, input]) {
|
|
540
|
+
element.key(["pageup", "ppage"], () => {
|
|
541
|
+
scrollTranscript(-5);
|
|
542
|
+
});
|
|
543
|
+
element.key(["pagedown", "npage"], () => {
|
|
544
|
+
scrollTranscript(5);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
renderTranscript();
|
|
548
|
+
setSidebar("booting...");
|
|
549
|
+
setFooter(" Starting studio... ");
|
|
550
|
+
screen.render();
|
|
551
|
+
input.focus();
|
|
552
|
+
await refreshEnvironment();
|
|
553
|
+
await ensureFirstRunOrientation();
|
|
554
|
+
async function ensureFirstRunOrientation() {
|
|
555
|
+
if (!latestPackState || !isDefaultStudioSession(messages)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
await appendMessage({
|
|
559
|
+
role: "system",
|
|
560
|
+
content: renderWorkspaceSelectionMessage(workspace, latestPackState)
|
|
561
|
+
});
|
|
562
|
+
renderTranscript();
|
|
563
|
+
setSidebar();
|
|
564
|
+
setFooter();
|
|
565
|
+
screen.render();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function describeBusyMode(mode) {
|
|
569
|
+
switch (mode) {
|
|
570
|
+
case "planner":
|
|
571
|
+
return `waiting on ${(0, agent_1.getPrimaryAgentAdapter)().label} planner`;
|
|
572
|
+
case "pack":
|
|
573
|
+
return `writing planning pack via ${(0, agent_1.getPrimaryAgentAdapter)().label}`;
|
|
574
|
+
case "run":
|
|
575
|
+
return `executing next-agent prompt via ${(0, agent_1.getPrimaryAgentAdapter)().label}`;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function formatElapsed(durationMs) {
|
|
579
|
+
const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
|
|
580
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
581
|
+
const seconds = totalSeconds % 60;
|
|
582
|
+
if (minutes === 0) {
|
|
583
|
+
return `${seconds}s`;
|
|
584
|
+
}
|
|
585
|
+
return `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
|
|
586
|
+
}
|
|
587
|
+
function formatTrackerSummary(currentPosition) {
|
|
588
|
+
if (!currentPosition.lastCompleted && !currentPosition.nextRecommended) {
|
|
589
|
+
return "current position unavailable";
|
|
590
|
+
}
|
|
591
|
+
const lines = [
|
|
592
|
+
`last: ${currentPosition.lastCompleted ?? "unknown"}`,
|
|
593
|
+
`next: ${currentPosition.nextRecommended ?? "none queued"}`
|
|
594
|
+
];
|
|
595
|
+
if (currentPosition.updatedAt) {
|
|
596
|
+
lines.push(`updated: ${currentPosition.updatedAt}`);
|
|
597
|
+
}
|
|
598
|
+
return lines.join("\n");
|
|
599
|
+
}
|
|
600
|
+
function formatExecutionSummary(execution) {
|
|
601
|
+
if (!execution) {
|
|
602
|
+
return "no execution recorded yet";
|
|
603
|
+
}
|
|
604
|
+
const label = execution.status === "success" ? "success" : "failure";
|
|
605
|
+
return `${label} via ${execution.source}\n${execution.updatedAt}\n${execution.summary}`;
|
|
606
|
+
}
|
|
607
|
+
function formatAgentSummary(activeAgent, statuses) {
|
|
608
|
+
return [`active: ${activeAgent.label}`, ...statuses.map((status) => formatAgentStatusLine(status, status.id === activeAgent.id))]
|
|
609
|
+
.join("\n");
|
|
610
|
+
}
|
|
611
|
+
function buildStudioHeaderContent(workspace, packState) {
|
|
612
|
+
const workspaceName = node_path_1.default.basename(workspace) || workspace;
|
|
613
|
+
const packLabel = packState ? formatPlanningPackPill(packState) : "{#ffb14a-fg}PACK STATUS LOADING{/}";
|
|
614
|
+
return ` {bold}SRGICAL STUDIO{/bold} ${workspaceName} ${packLabel}`;
|
|
615
|
+
}
|
|
616
|
+
function formatPlanningPackSummary(workspace, packState) {
|
|
617
|
+
const lines = [
|
|
618
|
+
`state: ${describePlanningPackState(packState)}`,
|
|
619
|
+
`dir: ${(0, workspace_1.getPlanningPackPaths)(workspace).dir}`
|
|
620
|
+
];
|
|
621
|
+
if (!packState.packPresent) {
|
|
622
|
+
lines.push("next: /write will put the plan on disk");
|
|
623
|
+
}
|
|
624
|
+
else if (packState.trackerReadable) {
|
|
625
|
+
lines.push(packState.currentPosition.nextRecommended ? "next: /preview or /run when ready" : "next: plan is written; queue more work when ready");
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
lines.push("next: rewrite or repair the pack before running");
|
|
629
|
+
}
|
|
630
|
+
return lines.join("\n");
|
|
631
|
+
}
|
|
632
|
+
function renderWorkspaceSelectionMessage(workspace, packState) {
|
|
633
|
+
return [
|
|
634
|
+
"Planning view:",
|
|
635
|
+
`- workspace: ${workspace}`,
|
|
636
|
+
`- planning dir: ${(0, workspace_1.getPlanningPackPaths)(workspace).dir}`,
|
|
637
|
+
`- plan status: ${describePlanningPackState(packState)}`,
|
|
638
|
+
"",
|
|
639
|
+
"Use `/workspace <path>` to switch repos.",
|
|
640
|
+
!packState.packPresent
|
|
641
|
+
? "Use `/write` when you want to put the current plan on disk."
|
|
642
|
+
: "Use `/write` when you want to refresh the plan on disk from this transcript."
|
|
643
|
+
].join("\n");
|
|
644
|
+
}
|
|
645
|
+
function renderAgentSelectionMessage(activeAgent, statuses) {
|
|
646
|
+
return [
|
|
647
|
+
`Current active agent: ${activeAgent.label}${activeAgent.available ? "" : " (selected but currently unavailable)"}`,
|
|
648
|
+
"",
|
|
649
|
+
"Detected support:",
|
|
650
|
+
...statuses.map((status) => formatAgentStatusLine(status, status.id === activeAgent.id))
|
|
651
|
+
].join("\n");
|
|
652
|
+
}
|
|
653
|
+
function formatAgentStatusLine(status, selected) {
|
|
654
|
+
const prefix = selected ? "*" : "-";
|
|
655
|
+
const detail = status.available ? `ready (${status.version ?? "version unknown"})` : formatUnavailableAgentDetail(status);
|
|
656
|
+
return `${prefix} ${status.id}: ${detail}`;
|
|
657
|
+
}
|
|
658
|
+
function formatUnavailableAgentDetail(status) {
|
|
659
|
+
const reason = status.error ?? "unknown issue";
|
|
660
|
+
return reason.toLowerCase().includes("install ") ? `on deck (${reason})` : `attention needed (${reason})`;
|
|
661
|
+
}
|
|
662
|
+
function resolveStudioWorkspaceInput(currentWorkspace, requestedWorkspace) {
|
|
663
|
+
const trimmed = requestedWorkspace.trim();
|
|
664
|
+
if (!trimmed) {
|
|
665
|
+
return currentWorkspace;
|
|
666
|
+
}
|
|
667
|
+
const pathModule = shouldUseWindowsPathSemantics(currentWorkspace, trimmed) ? node_path_1.default.win32 : node_path_1.default;
|
|
668
|
+
return pathModule.isAbsolute(trimmed) ? pathModule.resolve(trimmed) : pathModule.resolve(currentWorkspace, trimmed);
|
|
669
|
+
}
|
|
670
|
+
function shouldUseWindowsPathSemantics(currentWorkspace, requestedWorkspace) {
|
|
671
|
+
return (isWindowsAbsolutePath(currentWorkspace) ||
|
|
672
|
+
isWindowsAbsolutePath(requestedWorkspace) ||
|
|
673
|
+
isWindowsUncPath(currentWorkspace) ||
|
|
674
|
+
isWindowsUncPath(requestedWorkspace));
|
|
675
|
+
}
|
|
676
|
+
function isWindowsAbsolutePath(value) {
|
|
677
|
+
return /^[A-Za-z]:[\\/]/.test(value);
|
|
678
|
+
}
|
|
679
|
+
function isWindowsUncPath(value) {
|
|
680
|
+
return /^\\\\[^\\]+\\[^\\]+/.test(value);
|
|
681
|
+
}
|
|
682
|
+
function isDefaultStudioSession(messages) {
|
|
683
|
+
return (messages.length === studio_session_1.DEFAULT_STUDIO_MESSAGES.length &&
|
|
684
|
+
messages.every((message, index) => message.role === studio_session_1.DEFAULT_STUDIO_MESSAGES[index]?.role &&
|
|
685
|
+
message.content === studio_session_1.DEFAULT_STUDIO_MESSAGES[index]?.content));
|
|
686
|
+
}
|
|
687
|
+
function describePlanningPackState(packState) {
|
|
688
|
+
if (!packState.packPresent) {
|
|
689
|
+
return "not written yet";
|
|
690
|
+
}
|
|
691
|
+
if (!packState.trackerReadable) {
|
|
692
|
+
return "written but needs attention";
|
|
693
|
+
}
|
|
694
|
+
return "written to disk";
|
|
695
|
+
}
|
|
696
|
+
function formatPlanningPackPill(packState) {
|
|
697
|
+
if (!packState.packPresent) {
|
|
698
|
+
return "{#ffb14a-fg}PLAN NOT WRITTEN{/}";
|
|
699
|
+
}
|
|
700
|
+
if (!packState.trackerReadable) {
|
|
701
|
+
return "{#ff7a59-fg}PACK NEEDS ATTENTION{/}";
|
|
702
|
+
}
|
|
703
|
+
return "{#4de2c5-fg}PLAN WRITTEN{/}";
|
|
704
|
+
}
|