spec-cat 0.1.12 → 0.1.13
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{CG1pdqEM.js → ASOk6VwA.js} +1 -1
- package/.output/public/_nuxt/B24bRLVA.js +1 -0
- package/.output/public/_nuxt/{DZfSmUhR.js → B3SzeM5g.js} +1 -1
- package/.output/public/_nuxt/{wF8_ZCpc.js → CPqjerP3.js} +1 -1
- package/.output/public/_nuxt/D3Ay6Tay.js +126 -0
- package/.output/public/_nuxt/{BGX7vZ2E.js → DN_vgzh9.js} +1 -1
- package/.output/public/_nuxt/{CJ5aKHH_.js → Djrn6aq0.js} +2 -2
- package/.output/public/_nuxt/{BgAW4X6A.js → DqCsQSnP.js} +1 -1
- package/.output/public/_nuxt/{Bo0qwvIG.js → afuUTAOf.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/a94a9679-4171-4043-b507-a48b9a35f087.json +1 -0
- package/.output/public/_nuxt/{BRNtaPwh.js → dYG8t45S.js} +1 -1
- package/.output/public/_nuxt/entry.qjEvbHpP.css +1 -0
- package/.output/server/chunks/_/chat.mjs +8 -2
- package/.output/server/chunks/_/chat.mjs.map +1 -1
- package/.output/server/chunks/_/codexProvider.mjs +62 -102
- package/.output/server/chunks/_/codexProvider.mjs.map +1 -1
- package/.output/server/chunks/_/providerProcessError.mjs.map +1 -1
- package/.output/server/chunks/_/validateWorktree.mjs +1 -1
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/client.precomputed.mjs.map +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +813 -795
- package/.output/server/chunks/routes/_ws.mjs +346 -256
- package/.output/server/chunks/routes/_ws.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/finalize.post.mjs +8 -6
- package/.output/server/chunks/routes/api/chat/finalize.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/generate-commit-message.post.mjs +1 -1
- package/.output/server/chunks/routes/api/chat/preview.post.mjs +1 -1
- package/.output/server/chunks/routes/api/chat/preview.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/chat/rebase.post.mjs +1 -1
- package/.output/server/chunks/routes/api/chat/worktree.post.mjs +3 -3
- package/.output/server/chunks/routes/api/chat/worktree.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/archives/_archiveId/restore.post.mjs +3 -7
- package/.output/server/chunks/routes/api/conversations/archives/_archiveId/restore.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/update.post.mjs +59 -0
- package/.output/server/chunks/routes/api/conversations/update.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/rebase/continue-sync.post.mjs +1 -1
- package/.output/server/chunks/routes/api/rebase/continue.post.mjs +8 -6
- package/.output/server/chunks/routes/api/rebase/continue.post.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/package.json +1 -1
- package/.output/public/_nuxt/CQT7IgVF.js +0 -1
- package/.output/public/_nuxt/CWTghksz.js +0 -128
- package/.output/public/_nuxt/builds/meta/83cad684-1ef2-41bf-8031-41fb1a7c2996.json +0 -1
- package/.output/public/_nuxt/entry.CA9G_QPQ.css +0 -1
|
@@ -26,7 +26,7 @@ import 'node:module';
|
|
|
26
26
|
import 'node:url';
|
|
27
27
|
|
|
28
28
|
const execAsync = promisify(exec);
|
|
29
|
-
const WORKTREE_PREFIX = "/tmp/
|
|
29
|
+
const WORKTREE_PREFIX = "/tmp/sc-";
|
|
30
30
|
async function ensureChatWorktree(projectDir, worktreePath, knownBranch) {
|
|
31
31
|
if (!worktreePath.startsWith(WORKTREE_PREFIX)) {
|
|
32
32
|
return { recovered: false };
|
|
@@ -42,7 +42,7 @@ async function ensureChatWorktree(projectDir, worktreePath, knownBranch) {
|
|
|
42
42
|
if (!convId) {
|
|
43
43
|
return { recovered: false, error: `Cannot derive branch name from worktree path: ${worktreePath}` };
|
|
44
44
|
}
|
|
45
|
-
branchName = `
|
|
45
|
+
branchName = `sc/${convId}`;
|
|
46
46
|
}
|
|
47
47
|
try {
|
|
48
48
|
await execAsync("git worktree prune", { cwd: projectDir });
|
|
@@ -99,9 +99,304 @@ async function loadSpecContext(projectDir, featureId) {
|
|
|
99
99
|
return sections.join("\n");
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function extractSessionId(message) {
|
|
103
|
+
const eventType = typeof message.type === "string" ? message.type.toLowerCase() : "";
|
|
104
|
+
const subtype = typeof message.subtype === "string" ? message.subtype.toLowerCase() : "";
|
|
105
|
+
const isErrorLike = eventType.includes("error") || eventType.includes("failed") || subtype.startsWith("error");
|
|
106
|
+
if (isErrorLike) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const sessionIdKeys = ["session_id", "sessionId", "conversation_id", "conversationId", "thread_id", "threadId"];
|
|
110
|
+
for (const key of sessionIdKeys) {
|
|
111
|
+
const value = message[key];
|
|
112
|
+
if (typeof value === "string" && value.length > 0) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const response = message.response;
|
|
117
|
+
if (response && typeof response === "object" && !Array.isArray(response)) {
|
|
118
|
+
const responseObj = response;
|
|
119
|
+
for (const key of sessionIdKeys) {
|
|
120
|
+
const value = responseObj[key];
|
|
121
|
+
if (typeof value === "string" && value.length > 0) {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function normalizeTurnResultSubtype(value, isError) {
|
|
129
|
+
if (isError) return "error";
|
|
130
|
+
const raw = typeof value === "string" ? value.toLowerCase() : "";
|
|
131
|
+
if (raw === "success") return "success";
|
|
132
|
+
if (raw === "max_turns" || raw === "error_max_turns") return "max_turns";
|
|
133
|
+
if (raw.startsWith("error")) return "error";
|
|
134
|
+
return "success";
|
|
135
|
+
}
|
|
136
|
+
function transformClaudeEvent(event) {
|
|
137
|
+
var _a;
|
|
138
|
+
const sessionId = extractSessionId(event) || void 0;
|
|
139
|
+
const events = [];
|
|
140
|
+
if (event.type === "stream_event" && event.event && typeof event.event === "object") {
|
|
141
|
+
const streamEvent = event.event;
|
|
142
|
+
if (streamEvent.type === "content_block_start" && streamEvent.content_block && typeof streamEvent.content_block === "object") {
|
|
143
|
+
const block = streamEvent.content_block;
|
|
144
|
+
const blockType = block.type;
|
|
145
|
+
const normalizedBlockType = blockType === "server_tool_use" ? "tool_use" : blockType;
|
|
146
|
+
events.push({
|
|
147
|
+
type: "block_start",
|
|
148
|
+
sessionId,
|
|
149
|
+
blockId: block.id || `blk-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
|
|
150
|
+
blockType: normalizedBlockType,
|
|
151
|
+
index: streamEvent.index,
|
|
152
|
+
name: block.name,
|
|
153
|
+
toolUseId: block.id,
|
|
154
|
+
text: block.text,
|
|
155
|
+
thinking: block.thinking
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (streamEvent.type === "content_block_delta" && streamEvent.delta && typeof streamEvent.delta === "object") {
|
|
159
|
+
const delta = streamEvent.delta;
|
|
160
|
+
events.push({
|
|
161
|
+
type: "block_delta",
|
|
162
|
+
sessionId,
|
|
163
|
+
blockId: "",
|
|
164
|
+
// Client matches by index/type if ID is missing in delta
|
|
165
|
+
index: streamEvent.index,
|
|
166
|
+
text: delta.text,
|
|
167
|
+
thinking: delta.thinking,
|
|
168
|
+
partialJson: delta.partial_json
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (streamEvent.type === "content_block_stop") {
|
|
172
|
+
events.push({
|
|
173
|
+
type: "block_end",
|
|
174
|
+
sessionId,
|
|
175
|
+
blockId: "",
|
|
176
|
+
index: streamEvent.index
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (event.type === "tool_result") {
|
|
181
|
+
events.push({
|
|
182
|
+
type: "tool_result",
|
|
183
|
+
sessionId,
|
|
184
|
+
toolUseId: event.tool_use_id,
|
|
185
|
+
content: event.content,
|
|
186
|
+
isError: !!event.is_error
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (event.type === "permission_request" && event.permission && typeof event.permission === "object") {
|
|
190
|
+
const perm = event.permission;
|
|
191
|
+
events.push({
|
|
192
|
+
type: "permission_request",
|
|
193
|
+
sessionId,
|
|
194
|
+
tool: perm.tool || "Permission",
|
|
195
|
+
description: perm.description,
|
|
196
|
+
input: perm
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (event.type === "result") {
|
|
200
|
+
const usage = event.usage;
|
|
201
|
+
const isError = Boolean(event.is_error);
|
|
202
|
+
events.push({
|
|
203
|
+
type: "turn_result",
|
|
204
|
+
sessionId,
|
|
205
|
+
subtype: normalizeTurnResultSubtype(event.subtype, isError),
|
|
206
|
+
totalCostUsd: event.total_cost_usd,
|
|
207
|
+
durationMs: event.duration_ms,
|
|
208
|
+
numTurns: event.num_turns,
|
|
209
|
+
usage: usage ? {
|
|
210
|
+
inputTokens: usage.input_tokens || 0,
|
|
211
|
+
outputTokens: usage.output_tokens || 0,
|
|
212
|
+
cacheCreationInputTokens: usage.cache_creation_input_tokens || 0,
|
|
213
|
+
cacheReadInputTokens: usage.cache_read_input_tokens || 0
|
|
214
|
+
} : void 0
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (event.type === "system" && event.subtype === "init") {
|
|
218
|
+
events.push({
|
|
219
|
+
type: "session_init",
|
|
220
|
+
sessionId,
|
|
221
|
+
model: event.model,
|
|
222
|
+
tools: event.tools || [],
|
|
223
|
+
permissionMode: event.permissionMode || "",
|
|
224
|
+
cwd: event.cwd || ""
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (event.type === "user" && (event.message || event.content)) {
|
|
228
|
+
const content = ((_a = event.message) == null ? void 0 : _a.content) || event.content;
|
|
229
|
+
if (Array.isArray(content)) {
|
|
230
|
+
for (const block of content) {
|
|
231
|
+
if ((block == null ? void 0 : block.type) === "tool_result" && (block == null ? void 0 : block.is_error) && typeof block.content === "string") {
|
|
232
|
+
const errorContent = block.content;
|
|
233
|
+
if (errorContent.includes("requested permissions") || errorContent.includes("haven't granted")) {
|
|
234
|
+
const tools = parseToolsFromError(errorContent);
|
|
235
|
+
events.push({
|
|
236
|
+
type: "permission_request",
|
|
237
|
+
sessionId,
|
|
238
|
+
tool: tools[0] || "Permission",
|
|
239
|
+
tools,
|
|
240
|
+
description: errorContent
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return events;
|
|
248
|
+
}
|
|
249
|
+
function transformCodexEvent(event) {
|
|
250
|
+
const type = event.type;
|
|
251
|
+
const canonicalTypes = [
|
|
252
|
+
"session_init",
|
|
253
|
+
"block_start",
|
|
254
|
+
"block_delta",
|
|
255
|
+
"block_end",
|
|
256
|
+
"tool_result",
|
|
257
|
+
"permission_request",
|
|
258
|
+
"turn_result",
|
|
259
|
+
"error",
|
|
260
|
+
"done"
|
|
261
|
+
];
|
|
262
|
+
if (canonicalTypes.includes(type)) {
|
|
263
|
+
return [event];
|
|
264
|
+
}
|
|
265
|
+
return transformClaudeEvent(event);
|
|
266
|
+
}
|
|
267
|
+
function isRenderableEvent(event) {
|
|
268
|
+
switch (event.type) {
|
|
269
|
+
case "block_start":
|
|
270
|
+
return ["text", "thinking", "tool_use"].includes(event.blockType);
|
|
271
|
+
case "block_delta":
|
|
272
|
+
return !!(event.text || event.thinking || event.partialJson);
|
|
273
|
+
case "tool_result":
|
|
274
|
+
case "permission_request":
|
|
275
|
+
return true;
|
|
276
|
+
case "turn_result":
|
|
277
|
+
return event.subtype !== "success";
|
|
278
|
+
// Errors in result are renderable
|
|
279
|
+
default:
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function checkForPermissionRequest(event, approvedTools, providerId) {
|
|
284
|
+
if (event.type === "permission_request") {
|
|
285
|
+
return event;
|
|
286
|
+
}
|
|
287
|
+
if (event.type === "block_start" && event.blockType === "tool_use" && providerId === "codex") {
|
|
288
|
+
const normalizedTool = normalizeToolName(event.name || "");
|
|
289
|
+
if (normalizedTool && codexToolNeedsAskApproval(normalizedTool) && !approvedTools.has(normalizedTool)) {
|
|
290
|
+
return {
|
|
291
|
+
type: "permission_request",
|
|
292
|
+
sessionId: event.sessionId,
|
|
293
|
+
tool: normalizedTool,
|
|
294
|
+
tools: [normalizedTool],
|
|
295
|
+
description: `Permission required: ${normalizedTool}`
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (event.type === "tool_result" && event.isError) {
|
|
300
|
+
if (isPermissionRequestText(event.content)) {
|
|
301
|
+
const tools = parseToolsFromError(event.content);
|
|
302
|
+
return {
|
|
303
|
+
type: "permission_request",
|
|
304
|
+
sessionId: event.sessionId,
|
|
305
|
+
tool: tools[0] || "Permission",
|
|
306
|
+
tools,
|
|
307
|
+
description: event.content
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
function normalizeToolName(tool) {
|
|
314
|
+
if (!tool) return "";
|
|
315
|
+
const trimmed = tool.trim();
|
|
316
|
+
if (!trimmed) return "";
|
|
317
|
+
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
318
|
+
}
|
|
319
|
+
function isPermissionRequestText(text) {
|
|
320
|
+
if (!text) return false;
|
|
321
|
+
return /permission required|approval required|requires approval|requested permissions|haven't granted|hasn't granted|not approved|approval policy|permission denied|operation not permitted|read-only file system|cannot touch/i.test(text);
|
|
322
|
+
}
|
|
323
|
+
function parseToolsFromError(errorContent) {
|
|
324
|
+
const lowerContent = errorContent.toLowerCase();
|
|
325
|
+
const tools = [];
|
|
326
|
+
const toolNameMatch = errorContent.match(/(?:use the |Permission Required: )(\w+)(?: tool)?/i);
|
|
327
|
+
if (toolNameMatch) {
|
|
328
|
+
const toolName = toolNameMatch[1];
|
|
329
|
+
const normalized = toolName.charAt(0).toUpperCase() + toolName.slice(1).toLowerCase();
|
|
330
|
+
if (["Read", "Write", "Edit", "Bash", "Glob", "Grep", "Webfetch", "Websearch"].includes(normalized)) {
|
|
331
|
+
const tool = normalized === "Webfetch" ? "WebFetch" : normalized === "Websearch" ? "WebSearch" : normalized;
|
|
332
|
+
return [tool];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (lowerContent.includes("write to") || lowerContent.includes("write ")) {
|
|
336
|
+
tools.push("Write", "Edit");
|
|
337
|
+
}
|
|
338
|
+
if (lowerContent.includes("edit ") && !tools.includes("Edit")) {
|
|
339
|
+
tools.push("Edit");
|
|
340
|
+
}
|
|
341
|
+
if (lowerContent.includes("read ")) {
|
|
342
|
+
tools.push("Read");
|
|
343
|
+
}
|
|
344
|
+
if (lowerContent.includes("run ") || lowerContent.includes("execute") || lowerContent.includes("bash")) {
|
|
345
|
+
tools.push("Bash");
|
|
346
|
+
}
|
|
347
|
+
if (lowerContent.includes("glob")) {
|
|
348
|
+
tools.push("Glob");
|
|
349
|
+
}
|
|
350
|
+
if (lowerContent.includes("grep")) {
|
|
351
|
+
tools.push("Grep");
|
|
352
|
+
}
|
|
353
|
+
if (lowerContent.includes("fetch") || lowerContent.includes("webfetch")) {
|
|
354
|
+
tools.push("WebFetch");
|
|
355
|
+
}
|
|
356
|
+
if (lowerContent.includes("websearch")) {
|
|
357
|
+
tools.push("WebSearch");
|
|
358
|
+
}
|
|
359
|
+
return tools.length > 0 ? tools : ["Write", "Edit"];
|
|
360
|
+
}
|
|
361
|
+
function normalizeTools(tools) {
|
|
362
|
+
const seen = /* @__PURE__ */ new Set();
|
|
363
|
+
for (const tool of tools) {
|
|
364
|
+
const normalized = normalizeToolName(tool);
|
|
365
|
+
if (normalized) {
|
|
366
|
+
seen.add(normalized);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return Array.from(seen);
|
|
370
|
+
}
|
|
371
|
+
function codexToolNeedsAskApproval(tool) {
|
|
372
|
+
const normalized = normalizeToolName(tool);
|
|
373
|
+
if (!normalized) return false;
|
|
374
|
+
if (normalized === "Read" || normalized === "Glob" || normalized === "Grep" || normalized === "WebSearch") {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
function extractPermissionRequestFromProcessOutput(nonJsonOutput) {
|
|
380
|
+
if (!Array.isArray(nonJsonOutput) || nonJsonOutput.length === 0) return null;
|
|
381
|
+
const description = nonJsonOutput.join("\n");
|
|
382
|
+
if (!isPermissionRequestText(description)) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
const tools = parseToolsFromError(description);
|
|
386
|
+
return { tools, description };
|
|
387
|
+
}
|
|
388
|
+
|
|
102
389
|
const peerStates = /* @__PURE__ */ new Map();
|
|
103
390
|
const MAX_ATTACHMENT_COUNT = 4;
|
|
104
391
|
const MAX_ATTACHMENT_SIZE_BYTES = 5 * 1024 * 1024;
|
|
392
|
+
const SPECKIT_AUTONOMY_DIRECTIVE = [
|
|
393
|
+
"Speckit Execution Mode (MANDATORY):",
|
|
394
|
+
"- Do not ask the user for confirmation, follow-up, or permission to proceed.",
|
|
395
|
+
'- Do not end with questions like "Would you like me to..." or "Shall I...".',
|
|
396
|
+
"- For remediation and traceability gaps, directly edit the relevant spec files now (spec.md, plan.md, tasks.md) when writable.",
|
|
397
|
+
"- Prefer concrete file edits over recommendations; provide a brief change summary after edits are complete.",
|
|
398
|
+
"- Only stop without edits if blocked by a hard constraint (missing files, permission failure), and report the blocker explicitly."
|
|
399
|
+
].join("\n");
|
|
105
400
|
function getPeerState(peerId) {
|
|
106
401
|
let state = peerStates.get(peerId);
|
|
107
402
|
if (!state) {
|
|
@@ -117,48 +412,15 @@ function getPeerState(peerId) {
|
|
|
117
412
|
}
|
|
118
413
|
return state;
|
|
119
414
|
}
|
|
415
|
+
function isSpeckitCommand(message) {
|
|
416
|
+
return message.trim().startsWith("/speckit.");
|
|
417
|
+
}
|
|
120
418
|
function killProc(proc) {
|
|
121
419
|
try {
|
|
122
420
|
proc.kill();
|
|
123
421
|
} catch {
|
|
124
422
|
}
|
|
125
423
|
}
|
|
126
|
-
function hasRenderableProviderContent(message) {
|
|
127
|
-
const type = typeof message.type === "string" ? message.type : "";
|
|
128
|
-
if (type === "stream_event") {
|
|
129
|
-
const event = message.event;
|
|
130
|
-
if (event && typeof event === "object") {
|
|
131
|
-
const streamEvent = event;
|
|
132
|
-
if (streamEvent.type === "content_block_start") {
|
|
133
|
-
const contentBlock = streamEvent.content_block;
|
|
134
|
-
if (contentBlock && typeof contentBlock === "object") {
|
|
135
|
-
const block = contentBlock;
|
|
136
|
-
const blockType = typeof block.type === "string" ? block.type : "";
|
|
137
|
-
if (blockType === "text" || blockType === "thinking" || blockType === "tool_use" || blockType === "server_tool_use") {
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (streamEvent.type === "content_block_delta") {
|
|
143
|
-
const delta = streamEvent.delta;
|
|
144
|
-
if (delta && typeof delta === "object") {
|
|
145
|
-
const d = delta;
|
|
146
|
-
if (typeof d.text === "string" || typeof d.thinking === "string" || typeof d.partial_json === "string") {
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (type === "tool_result" || type === "permission_request") {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
if (type === "result") {
|
|
157
|
-
const subtype = typeof message.subtype === "string" ? message.subtype : "";
|
|
158
|
-
return subtype.startsWith("error");
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
424
|
function normalizeImageAttachments(attachments) {
|
|
163
425
|
if (!Array.isArray(attachments)) return [];
|
|
164
426
|
return attachments.slice(0, MAX_ATTACHMENT_COUNT).map((item) => {
|
|
@@ -196,22 +458,21 @@ function buildProviderMessage(baseMessage, attachments) {
|
|
|
196
458
|
}
|
|
197
459
|
function sendAssistantText(peer, text, sessionId) {
|
|
198
460
|
peer.send(JSON.stringify({
|
|
199
|
-
type: "
|
|
200
|
-
|
|
201
|
-
type: "
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
461
|
+
type: "ui_event",
|
|
462
|
+
event: {
|
|
463
|
+
type: "block_start",
|
|
464
|
+
sessionId: sessionId || void 0,
|
|
465
|
+
blockId: `blk-${Date.now()}`,
|
|
466
|
+
blockType: "text",
|
|
467
|
+
text
|
|
207
468
|
}
|
|
208
469
|
}));
|
|
209
470
|
peer.send(JSON.stringify({
|
|
210
|
-
type: "
|
|
211
|
-
|
|
212
|
-
type: "
|
|
213
|
-
|
|
214
|
-
|
|
471
|
+
type: "ui_event",
|
|
472
|
+
event: {
|
|
473
|
+
type: "block_end",
|
|
474
|
+
sessionId: sessionId || void 0,
|
|
475
|
+
blockId: ""
|
|
215
476
|
}
|
|
216
477
|
}));
|
|
217
478
|
}
|
|
@@ -340,8 +601,8 @@ async function handleChatMessage(peer, msg) {
|
|
|
340
601
|
}));
|
|
341
602
|
return;
|
|
342
603
|
}
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
604
|
+
const speckitCommand = isSpeckitCommand(msg.message);
|
|
605
|
+
if (speckitCommand) {
|
|
345
606
|
console.log("[WS] Speckit command detected - auto-resetting context for peer:", peer.id);
|
|
346
607
|
clearProviderSession(state);
|
|
347
608
|
} else {
|
|
@@ -353,9 +614,9 @@ async function handleChatMessage(peer, msg) {
|
|
|
353
614
|
}
|
|
354
615
|
state.pendingMessage = msg;
|
|
355
616
|
state.pendingTools = [];
|
|
356
|
-
if (!
|
|
617
|
+
if (!speckitCommand && msg.sessionId) {
|
|
357
618
|
state.providerSessionId = msg.sessionId;
|
|
358
|
-
} else if (!
|
|
619
|
+
} else if (!speckitCommand) {
|
|
359
620
|
state.approvedTools.clear();
|
|
360
621
|
state.providerSessionId = null;
|
|
361
622
|
}
|
|
@@ -366,7 +627,7 @@ async function handleChatMessage(peer, msg) {
|
|
|
366
627
|
attachmentCount: attachments.length,
|
|
367
628
|
providerId: msg.providerId,
|
|
368
629
|
providerModelKey: msg.providerModelKey,
|
|
369
|
-
isSpeckitCommand
|
|
630
|
+
isSpeckitCommand: speckitCommand
|
|
370
631
|
});
|
|
371
632
|
runProvider(peer, state, msg);
|
|
372
633
|
}
|
|
@@ -404,7 +665,7 @@ async function runProvider(peer, state, msg, isRetry = false, forceEphemeral = f
|
|
|
404
665
|
return;
|
|
405
666
|
}
|
|
406
667
|
}
|
|
407
|
-
if (workingDirectory.startsWith("/tmp/
|
|
668
|
+
if (workingDirectory.startsWith("/tmp/sc-") && !existsSync(workingDirectory)) {
|
|
408
669
|
const result = await ensureChatWorktree(projectDir, workingDirectory, msg.worktreeBranch);
|
|
409
670
|
if (result.recovered) {
|
|
410
671
|
peer.send(JSON.stringify({ type: "worktree_recovered" }));
|
|
@@ -420,6 +681,7 @@ async function runProvider(peer, state, msg, isRetry = false, forceEphemeral = f
|
|
|
420
681
|
const usedResumeFlag = !isRetry && !!state.providerSessionId;
|
|
421
682
|
const resumeSessionId = usedResumeFlag ? state.providerSessionId : void 0;
|
|
422
683
|
let systemPrompt;
|
|
684
|
+
const speckitCommand = isSpeckitCommand(msg.message);
|
|
423
685
|
if (msg.featureId && !usedResumeFlag) {
|
|
424
686
|
try {
|
|
425
687
|
const specContext = await loadSpecContext(projectDir, msg.featureId);
|
|
@@ -430,6 +692,11 @@ async function runProvider(peer, state, msg, isRetry = false, forceEphemeral = f
|
|
|
430
692
|
console.error("[WS] Failed to load spec context:", error);
|
|
431
693
|
}
|
|
432
694
|
}
|
|
695
|
+
if (speckitCommand && !usedResumeFlag) {
|
|
696
|
+
systemPrompt = systemPrompt ? `${systemPrompt}
|
|
697
|
+
|
|
698
|
+
${SPECKIT_AUTONOMY_DIRECTIVE}` : SPECKIT_AUTONOMY_DIRECTIVE;
|
|
699
|
+
}
|
|
433
700
|
console.log("[WS] Running provider stream:", selection.providerId, selection.modelKey, isRetry ? "(retry)" : "");
|
|
434
701
|
const generation = state.procGeneration;
|
|
435
702
|
let permissionRequested = false;
|
|
@@ -450,85 +717,33 @@ async function runProvider(peer, state, msg, isRetry = false, forceEphemeral = f
|
|
|
450
717
|
},
|
|
451
718
|
{
|
|
452
719
|
onProviderJson(parsed) {
|
|
453
|
-
var _a
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
emittedRenderableContent = true;
|
|
460
|
-
}
|
|
461
|
-
if (selection.providerId === "codex" && (mode === "ask" || mode === "plan") && !permissionRequested) {
|
|
462
|
-
const requestedTool = extractToolUseNameFromProviderJson(parsed);
|
|
463
|
-
const normalizedTool = normalizeToolName(requestedTool || "");
|
|
464
|
-
if (normalizedTool && codexToolNeedsAskApproval(normalizedTool) && !state.approvedTools.has(normalizedTool)) {
|
|
465
|
-
permissionRequested = true;
|
|
466
|
-
state.pendingTools = [normalizedTool];
|
|
467
|
-
peer.send(JSON.stringify({
|
|
468
|
-
type: "permission_request",
|
|
469
|
-
tool: normalizedTool,
|
|
470
|
-
tools: state.pendingTools,
|
|
471
|
-
description: `Permission required: ${normalizedTool}`
|
|
472
|
-
}));
|
|
473
|
-
(_a = state.proc) == null ? void 0 : _a.kill();
|
|
474
|
-
return;
|
|
720
|
+
var _a;
|
|
721
|
+
const adapter = selection.providerId === "claude" ? transformClaudeEvent : transformCodexEvent;
|
|
722
|
+
const events = adapter(parsed);
|
|
723
|
+
for (const event of events) {
|
|
724
|
+
if (event.sessionId) {
|
|
725
|
+
state.providerSessionId = event.sessionId;
|
|
475
726
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
permissionRequested = true;
|
|
479
|
-
const permission = parsed.permission && typeof parsed.permission === "object" ? parsed.permission : null;
|
|
480
|
-
const tool = permission && typeof permission.tool === "string" ? permission.tool : "Permission";
|
|
481
|
-
const normalizedTool = normalizeToolName(tool);
|
|
482
|
-
const description = permission && typeof permission.description === "string" ? permission.description : "Permission required to continue execution.";
|
|
483
|
-
state.pendingTools = normalizedTool ? [normalizedTool] : [];
|
|
484
|
-
peer.send(JSON.stringify({
|
|
485
|
-
type: "permission_request",
|
|
486
|
-
tool: normalizedTool || "Permission",
|
|
487
|
-
tools: state.pendingTools,
|
|
488
|
-
description
|
|
489
|
-
}));
|
|
490
|
-
(_b = state.proc) == null ? void 0 : _b.kill();
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
if ((mode === "ask" || mode === "plan") && !permissionRequested) {
|
|
494
|
-
const inferred = extractPermissionRequestFromToolResult(parsed);
|
|
495
|
-
if (inferred) {
|
|
496
|
-
permissionRequested = true;
|
|
497
|
-
state.pendingTools = normalizeTools(inferred.tools);
|
|
498
|
-
peer.send(JSON.stringify({
|
|
499
|
-
type: "permission_request",
|
|
500
|
-
tool: state.pendingTools[0] || "Permission",
|
|
501
|
-
tools: state.pendingTools,
|
|
502
|
-
description: inferred.description
|
|
503
|
-
}));
|
|
504
|
-
(_c = state.proc) == null ? void 0 : _c.kill();
|
|
505
|
-
return;
|
|
727
|
+
if (isRenderableEvent(event)) {
|
|
728
|
+
emittedRenderableContent = true;
|
|
506
729
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
tool: state.pendingTools[0],
|
|
521
|
-
tools: state.pendingTools,
|
|
522
|
-
description: errorContent
|
|
523
|
-
}));
|
|
524
|
-
(_e = state.proc) == null ? void 0 : _e.kill();
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
730
|
+
if ((mode === "ask" || mode === "plan") && !permissionRequested) {
|
|
731
|
+
const permRequest = checkForPermissionRequest(event, state.approvedTools, selection.providerId);
|
|
732
|
+
if (permRequest) {
|
|
733
|
+
permissionRequested = true;
|
|
734
|
+
state.pendingTools = permRequest.tools || [permRequest.tool];
|
|
735
|
+
peer.send(JSON.stringify({
|
|
736
|
+
type: "permission_request",
|
|
737
|
+
tool: permRequest.tool,
|
|
738
|
+
tools: state.pendingTools,
|
|
739
|
+
description: permRequest.description || `Permission required: ${permRequest.tool}`
|
|
740
|
+
}));
|
|
741
|
+
(_a = state.proc) == null ? void 0 : _a.kill();
|
|
742
|
+
return;
|
|
528
743
|
}
|
|
529
744
|
}
|
|
745
|
+
peer.send(JSON.stringify({ type: "ui_event", event }));
|
|
530
746
|
}
|
|
531
|
-
peer.send(JSON.stringify({ type: "provider_json", data: parsed }));
|
|
532
747
|
},
|
|
533
748
|
onClose({ exitCode, signal, nonJsonOutput }) {
|
|
534
749
|
if (state.procGeneration !== generation) {
|
|
@@ -651,131 +866,6 @@ ${summary}` : "Provider completed without returning visible response content.";
|
|
|
651
866
|
state.proc = null;
|
|
652
867
|
}
|
|
653
868
|
}
|
|
654
|
-
function extractSessionId(message) {
|
|
655
|
-
const eventType = typeof message.type === "string" ? message.type.toLowerCase() : "";
|
|
656
|
-
const subtype = typeof message.subtype === "string" ? message.subtype.toLowerCase() : "";
|
|
657
|
-
const isErrorLike = eventType.includes("error") || eventType.includes("failed") || subtype.startsWith("error");
|
|
658
|
-
if (isErrorLike) {
|
|
659
|
-
return null;
|
|
660
|
-
}
|
|
661
|
-
const sessionIdKeys = ["session_id", "sessionId", "conversation_id", "conversationId", "thread_id", "threadId"];
|
|
662
|
-
for (const key of sessionIdKeys) {
|
|
663
|
-
const value = message[key];
|
|
664
|
-
if (typeof value === "string" && value.length > 0) {
|
|
665
|
-
return value;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
const response = message.response;
|
|
669
|
-
if (response && typeof response === "object" && !Array.isArray(response)) {
|
|
670
|
-
const responseObj = response;
|
|
671
|
-
for (const key of sessionIdKeys) {
|
|
672
|
-
const value = responseObj[key];
|
|
673
|
-
if (typeof value === "string" && value.length > 0) {
|
|
674
|
-
return value;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
return null;
|
|
679
|
-
}
|
|
680
|
-
function parseToolsFromError(errorContent) {
|
|
681
|
-
const lowerContent = errorContent.toLowerCase();
|
|
682
|
-
const tools = [];
|
|
683
|
-
const toolNameMatch = errorContent.match(/(?:use the |Permission Required: )(\w+)(?: tool)?/i);
|
|
684
|
-
if (toolNameMatch) {
|
|
685
|
-
const toolName = toolNameMatch[1];
|
|
686
|
-
const normalized = toolName.charAt(0).toUpperCase() + toolName.slice(1).toLowerCase();
|
|
687
|
-
if (["Read", "Write", "Edit", "Bash", "Glob", "Grep", "Webfetch", "Websearch"].includes(normalized)) {
|
|
688
|
-
const tool = normalized === "Webfetch" ? "WebFetch" : normalized === "Websearch" ? "WebSearch" : normalized;
|
|
689
|
-
return [tool];
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
if (lowerContent.includes("write to") || lowerContent.includes("write ")) {
|
|
693
|
-
tools.push("Write", "Edit");
|
|
694
|
-
}
|
|
695
|
-
if (lowerContent.includes("edit ") && !tools.includes("Edit")) {
|
|
696
|
-
tools.push("Edit");
|
|
697
|
-
}
|
|
698
|
-
if (lowerContent.includes("read ")) {
|
|
699
|
-
tools.push("Read");
|
|
700
|
-
}
|
|
701
|
-
if (lowerContent.includes("run ") || lowerContent.includes("execute") || lowerContent.includes("bash")) {
|
|
702
|
-
tools.push("Bash");
|
|
703
|
-
}
|
|
704
|
-
if (lowerContent.includes("glob")) {
|
|
705
|
-
tools.push("Glob");
|
|
706
|
-
}
|
|
707
|
-
if (lowerContent.includes("grep")) {
|
|
708
|
-
tools.push("Grep");
|
|
709
|
-
}
|
|
710
|
-
if (lowerContent.includes("fetch") || lowerContent.includes("webfetch")) {
|
|
711
|
-
tools.push("WebFetch");
|
|
712
|
-
}
|
|
713
|
-
if (lowerContent.includes("websearch")) {
|
|
714
|
-
tools.push("WebSearch");
|
|
715
|
-
}
|
|
716
|
-
return tools.length > 0 ? tools : ["Write", "Edit"];
|
|
717
|
-
}
|
|
718
|
-
function extractToolUseNameFromProviderJson(parsed) {
|
|
719
|
-
if (parsed.type !== "stream_event") return null;
|
|
720
|
-
const event = parsed.event;
|
|
721
|
-
if (!event || typeof event !== "object" || Array.isArray(event)) return null;
|
|
722
|
-
const eventObj = event;
|
|
723
|
-
if (eventObj.type !== "content_block_start") return null;
|
|
724
|
-
const contentBlock = eventObj.content_block;
|
|
725
|
-
if (!contentBlock || typeof contentBlock !== "object" || Array.isArray(contentBlock)) return null;
|
|
726
|
-
const blockObj = contentBlock;
|
|
727
|
-
const blockType = typeof blockObj.type === "string" ? blockObj.type : "";
|
|
728
|
-
if (blockType !== "tool_use" && blockType !== "server_tool_use") return null;
|
|
729
|
-
return typeof blockObj.name === "string" && blockObj.name.length > 0 ? blockObj.name : null;
|
|
730
|
-
}
|
|
731
|
-
function normalizeToolName(tool) {
|
|
732
|
-
if (!tool) return "";
|
|
733
|
-
const trimmed = tool.trim();
|
|
734
|
-
if (!trimmed) return "";
|
|
735
|
-
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
736
|
-
}
|
|
737
|
-
function normalizeTools(tools) {
|
|
738
|
-
const seen = /* @__PURE__ */ new Set();
|
|
739
|
-
for (const tool of tools) {
|
|
740
|
-
const normalized = normalizeToolName(tool);
|
|
741
|
-
if (normalized) {
|
|
742
|
-
seen.add(normalized);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
return Array.from(seen);
|
|
746
|
-
}
|
|
747
|
-
function codexToolNeedsAskApproval(tool) {
|
|
748
|
-
const normalized = normalizeToolName(tool);
|
|
749
|
-
if (!normalized) return false;
|
|
750
|
-
if (normalized === "Read" || normalized === "Glob" || normalized === "Grep" || normalized === "WebSearch") {
|
|
751
|
-
return false;
|
|
752
|
-
}
|
|
753
|
-
return true;
|
|
754
|
-
}
|
|
755
|
-
function isPermissionRequestText(text) {
|
|
756
|
-
if (!text) return false;
|
|
757
|
-
return /permission required|approval required|requires approval|requested permissions|haven't granted|hasn't granted|not approved|approval policy|permission denied|operation not permitted|read-only file system|cannot touch/i.test(text);
|
|
758
|
-
}
|
|
759
|
-
function extractPermissionRequestFromToolResult(parsed) {
|
|
760
|
-
if (parsed.type !== "tool_result") return null;
|
|
761
|
-
if (parsed.is_error !== true) return null;
|
|
762
|
-
if (typeof parsed.content !== "string") return null;
|
|
763
|
-
const description = parsed.content;
|
|
764
|
-
if (!isPermissionRequestText(description)) {
|
|
765
|
-
return null;
|
|
766
|
-
}
|
|
767
|
-
const tools = parseToolsFromError(description);
|
|
768
|
-
return { tools, description };
|
|
769
|
-
}
|
|
770
|
-
function extractPermissionRequestFromProcessOutput(nonJsonOutput) {
|
|
771
|
-
if (!Array.isArray(nonJsonOutput) || nonJsonOutput.length === 0) return null;
|
|
772
|
-
const description = nonJsonOutput.join("\n");
|
|
773
|
-
if (!isPermissionRequestText(description)) {
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
const tools = parseToolsFromError(description);
|
|
777
|
-
return { tools, description };
|
|
778
|
-
}
|
|
779
869
|
|
|
780
870
|
export { _ws as default };
|
|
781
871
|
//# sourceMappingURL=_ws.mjs.map
|