tarsk 0.3.36 → 0.3.38
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/index.js
CHANGED
|
@@ -96,7 +96,7 @@ import { cors } from "hono/cors";
|
|
|
96
96
|
import { serve } from "@hono/node-server";
|
|
97
97
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
98
98
|
import path3 from "path";
|
|
99
|
-
import
|
|
99
|
+
import open3 from "open";
|
|
100
100
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
101
101
|
|
|
102
102
|
// src/managers/project-manager.ts
|
|
@@ -1908,12 +1908,7 @@ class MetadataManager {
|
|
|
1908
1908
|
|
|
1909
1909
|
// src/managers/pi-executor.ts
|
|
1910
1910
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
1911
|
-
import {
|
|
1912
|
-
getModel
|
|
1913
|
-
} from "@mariozechner/pi-ai";
|
|
1914
|
-
import { resolve, dirname as dirname5, isAbsolute as isAbsolute2 } from "path";
|
|
1915
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
1916
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1911
|
+
import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
|
|
1917
1912
|
|
|
1918
1913
|
// src/tools/bash.ts
|
|
1919
1914
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
@@ -2563,6 +2558,38 @@ function resolveReadPath(filePath, cwd) {
|
|
|
2563
2558
|
return resolved;
|
|
2564
2559
|
}
|
|
2565
2560
|
|
|
2561
|
+
// src/tools/tool-helpers.ts
|
|
2562
|
+
async function withAbortSignal(signal, operation) {
|
|
2563
|
+
return new Promise((resolve, reject) => {
|
|
2564
|
+
if (signal?.aborted) {
|
|
2565
|
+
reject(new Error("Operation aborted"));
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
let aborted = false;
|
|
2569
|
+
const onAbort = () => {
|
|
2570
|
+
aborted = true;
|
|
2571
|
+
reject(new Error("Operation aborted"));
|
|
2572
|
+
};
|
|
2573
|
+
if (signal) {
|
|
2574
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2575
|
+
}
|
|
2576
|
+
const cleanup = () => {
|
|
2577
|
+
if (signal)
|
|
2578
|
+
signal.removeEventListener("abort", onAbort);
|
|
2579
|
+
};
|
|
2580
|
+
const abortCheck = () => aborted;
|
|
2581
|
+
operation(abortCheck).then((result) => {
|
|
2582
|
+
cleanup();
|
|
2583
|
+
if (!aborted)
|
|
2584
|
+
resolve(result);
|
|
2585
|
+
}).catch((error) => {
|
|
2586
|
+
cleanup();
|
|
2587
|
+
if (!aborted)
|
|
2588
|
+
reject(error);
|
|
2589
|
+
});
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2566
2593
|
// src/tools/edit.ts
|
|
2567
2594
|
var editSchema = Type2.Object({
|
|
2568
2595
|
path: Type2.String({ description: "Path to the file to edit (relative or absolute)" }),
|
|
@@ -2584,83 +2611,49 @@ function createEditTool(cwd, options) {
|
|
|
2584
2611
|
execute: async (_toolCallId, { path, oldText, newText }, signal) => {
|
|
2585
2612
|
const absolutePath = resolveToCwd(path, cwd);
|
|
2586
2613
|
validatePathWithinCwd(absolutePath, cwd);
|
|
2587
|
-
return
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2614
|
+
return withAbortSignal(signal, async (isAborted) => {
|
|
2615
|
+
try {
|
|
2616
|
+
await ops.access(absolutePath);
|
|
2617
|
+
} catch {
|
|
2618
|
+
throw new Error(`File not found: ${path}`);
|
|
2591
2619
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2620
|
+
if (isAborted())
|
|
2621
|
+
return { content: [], details: undefined };
|
|
2622
|
+
const buffer = await ops.readFile(absolutePath);
|
|
2623
|
+
const rawContent = buffer.toString("utf-8");
|
|
2624
|
+
if (isAborted())
|
|
2625
|
+
return { content: [], details: undefined };
|
|
2626
|
+
const { bom, text: content } = stripBom(rawContent);
|
|
2627
|
+
const originalEnding = detectLineEnding(content);
|
|
2628
|
+
const normalizedContent = normalizeToLF(content);
|
|
2629
|
+
const normalizedOldText = normalizeToLF(oldText);
|
|
2630
|
+
const normalizedNewText = normalizeToLF(newText);
|
|
2631
|
+
const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
|
|
2632
|
+
if (!matchResult.found) {
|
|
2633
|
+
throw new Error(`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`);
|
|
2634
|
+
}
|
|
2635
|
+
const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
|
|
2636
|
+
const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
|
|
2637
|
+
const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
|
|
2638
|
+
if (occurrences > 1) {
|
|
2639
|
+
throw new Error(`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`);
|
|
2640
|
+
}
|
|
2641
|
+
if (isAborted())
|
|
2642
|
+
return { content: [], details: undefined };
|
|
2643
|
+
const baseContent = matchResult.contentForReplacement;
|
|
2644
|
+
const newContent = baseContent.substring(0, matchResult.index) + normalizedNewText + baseContent.substring(matchResult.index + matchResult.matchLength);
|
|
2645
|
+
if (baseContent === newContent) {
|
|
2646
|
+
throw new Error(`No changes made to ${path}. The replacement produced identical content.`);
|
|
2647
|
+
}
|
|
2648
|
+
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
2649
|
+
await ops.writeFile(absolutePath, finalContent);
|
|
2650
|
+
if (isAborted())
|
|
2651
|
+
return { content: [], details: undefined };
|
|
2652
|
+
const diffResult = generateDiffString(baseContent, newContent);
|
|
2653
|
+
return {
|
|
2654
|
+
content: [{ type: "text", text: `Successfully replaced text in ${path}.` }],
|
|
2655
|
+
details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }
|
|
2596
2656
|
};
|
|
2597
|
-
if (signal)
|
|
2598
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
2599
|
-
(async () => {
|
|
2600
|
-
try {
|
|
2601
|
-
try {
|
|
2602
|
-
await ops.access(absolutePath);
|
|
2603
|
-
} catch {
|
|
2604
|
-
if (signal)
|
|
2605
|
-
signal.removeEventListener("abort", onAbort);
|
|
2606
|
-
reject(new Error(`File not found: ${path}`));
|
|
2607
|
-
return;
|
|
2608
|
-
}
|
|
2609
|
-
if (aborted)
|
|
2610
|
-
return;
|
|
2611
|
-
const buffer = await ops.readFile(absolutePath);
|
|
2612
|
-
const rawContent = buffer.toString("utf-8");
|
|
2613
|
-
if (aborted)
|
|
2614
|
-
return;
|
|
2615
|
-
const { bom, text: content } = stripBom(rawContent);
|
|
2616
|
-
const originalEnding = detectLineEnding(content);
|
|
2617
|
-
const normalizedContent = normalizeToLF(content);
|
|
2618
|
-
const normalizedOldText = normalizeToLF(oldText);
|
|
2619
|
-
const normalizedNewText = normalizeToLF(newText);
|
|
2620
|
-
const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
|
|
2621
|
-
if (!matchResult.found) {
|
|
2622
|
-
if (signal)
|
|
2623
|
-
signal.removeEventListener("abort", onAbort);
|
|
2624
|
-
reject(new Error(`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`));
|
|
2625
|
-
return;
|
|
2626
|
-
}
|
|
2627
|
-
const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
|
|
2628
|
-
const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
|
|
2629
|
-
const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
|
|
2630
|
-
if (occurrences > 1) {
|
|
2631
|
-
if (signal)
|
|
2632
|
-
signal.removeEventListener("abort", onAbort);
|
|
2633
|
-
reject(new Error(`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`));
|
|
2634
|
-
return;
|
|
2635
|
-
}
|
|
2636
|
-
if (aborted)
|
|
2637
|
-
return;
|
|
2638
|
-
const baseContent = matchResult.contentForReplacement;
|
|
2639
|
-
const newContent = baseContent.substring(0, matchResult.index) + normalizedNewText + baseContent.substring(matchResult.index + matchResult.matchLength);
|
|
2640
|
-
if (baseContent === newContent) {
|
|
2641
|
-
if (signal)
|
|
2642
|
-
signal.removeEventListener("abort", onAbort);
|
|
2643
|
-
reject(new Error(`No changes made to ${path}. The replacement produced identical content.`));
|
|
2644
|
-
return;
|
|
2645
|
-
}
|
|
2646
|
-
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
2647
|
-
await ops.writeFile(absolutePath, finalContent);
|
|
2648
|
-
if (aborted)
|
|
2649
|
-
return;
|
|
2650
|
-
if (signal)
|
|
2651
|
-
signal.removeEventListener("abort", onAbort);
|
|
2652
|
-
const diffResult = generateDiffString(baseContent, newContent);
|
|
2653
|
-
resolve({
|
|
2654
|
-
content: [{ type: "text", text: `Successfully replaced text in ${path}.` }],
|
|
2655
|
-
details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }
|
|
2656
|
-
});
|
|
2657
|
-
} catch (error) {
|
|
2658
|
-
if (signal)
|
|
2659
|
-
signal.removeEventListener("abort", onAbort);
|
|
2660
|
-
if (!aborted)
|
|
2661
|
-
reject(error);
|
|
2662
|
-
}
|
|
2663
|
-
})();
|
|
2664
2657
|
});
|
|
2665
2658
|
}
|
|
2666
2659
|
};
|
|
@@ -3147,87 +3140,64 @@ function createReadTool(cwd, options) {
|
|
|
3147
3140
|
execute: async (_toolCallId, { path: path3, offset, limit }, signal) => {
|
|
3148
3141
|
const absolutePath = resolveReadPath(path3, cwd);
|
|
3149
3142
|
validatePathWithinCwd(absolutePath, cwd);
|
|
3150
|
-
return
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
return;
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
const
|
|
3157
|
-
aborted = true;
|
|
3158
|
-
reject(new Error("Operation aborted"));
|
|
3159
|
-
};
|
|
3160
|
-
if (signal) {
|
|
3161
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
3162
|
-
}
|
|
3163
|
-
(async () => {
|
|
3164
|
-
try {
|
|
3165
|
-
await ops.access(absolutePath);
|
|
3166
|
-
if (aborted)
|
|
3167
|
-
return;
|
|
3168
|
-
const buffer = await ops.readFile(absolutePath);
|
|
3169
|
-
const textContent = buffer.toString("utf-8");
|
|
3170
|
-
const allLines = textContent.split(`
|
|
3143
|
+
return withAbortSignal(signal, async (isAborted) => {
|
|
3144
|
+
await ops.access(absolutePath);
|
|
3145
|
+
if (isAborted())
|
|
3146
|
+
return { content: [], details: undefined };
|
|
3147
|
+
const buffer = await ops.readFile(absolutePath);
|
|
3148
|
+
const textContent = buffer.toString("utf-8");
|
|
3149
|
+
const allLines = textContent.split(`
|
|
3171
3150
|
`);
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3151
|
+
const totalFileLines = allLines.length;
|
|
3152
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
3153
|
+
const startLineDisplay = startLine + 1;
|
|
3154
|
+
if (startLine >= allLines.length) {
|
|
3155
|
+
throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
|
|
3156
|
+
}
|
|
3157
|
+
let selectedContent;
|
|
3158
|
+
let userLimitedLines;
|
|
3159
|
+
if (limit !== undefined) {
|
|
3160
|
+
const endLine = Math.min(startLine + limit, allLines.length);
|
|
3161
|
+
selectedContent = allLines.slice(startLine, endLine).join(`
|
|
3183
3162
|
`);
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3163
|
+
userLimitedLines = endLine - startLine;
|
|
3164
|
+
} else {
|
|
3165
|
+
selectedContent = allLines.slice(startLine).join(`
|
|
3187
3166
|
`);
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3167
|
+
}
|
|
3168
|
+
const truncation = truncateHead(selectedContent);
|
|
3169
|
+
let outputText;
|
|
3170
|
+
let details;
|
|
3171
|
+
if (truncation.firstLineExceedsLimit) {
|
|
3172
|
+
outputText = `[Line ${startLineDisplay} is ${formatSize(Buffer.byteLength(allLines[startLine], "utf-8"))}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path3} | head -c ${DEFAULT_MAX_BYTES}]`;
|
|
3173
|
+
details = { truncation };
|
|
3174
|
+
} else if (truncation.truncated) {
|
|
3175
|
+
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
3176
|
+
const nextOffset = endLineDisplay + 1;
|
|
3177
|
+
outputText = truncation.content;
|
|
3178
|
+
if (truncation.truncatedBy === "lines") {
|
|
3179
|
+
outputText += `
|
|
3201
3180
|
|
|
3202
3181
|
[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;
|
|
3203
|
-
|
|
3204
|
-
|
|
3182
|
+
} else {
|
|
3183
|
+
outputText += `
|
|
3205
3184
|
|
|
3206
3185
|
[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3186
|
+
}
|
|
3187
|
+
details = { truncation };
|
|
3188
|
+
} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
3189
|
+
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
3190
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
3191
|
+
outputText = truncation.content;
|
|
3192
|
+
outputText += `
|
|
3214
3193
|
|
|
3215
3194
|
[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
signal.removeEventListener("abort", onAbort);
|
|
3223
|
-
resolve({ content: [{ type: "text", text: outputText }], details });
|
|
3224
|
-
} catch (error) {
|
|
3225
|
-
if (signal)
|
|
3226
|
-
signal.removeEventListener("abort", onAbort);
|
|
3227
|
-
if (!aborted)
|
|
3228
|
-
reject(error);
|
|
3229
|
-
}
|
|
3230
|
-
})();
|
|
3195
|
+
} else {
|
|
3196
|
+
outputText = truncation.content;
|
|
3197
|
+
}
|
|
3198
|
+
if (isAborted())
|
|
3199
|
+
return { content: [], details: undefined };
|
|
3200
|
+
return { content: [{ type: "text", text: outputText }], details };
|
|
3231
3201
|
});
|
|
3232
3202
|
}
|
|
3233
3203
|
};
|
|
@@ -3296,14 +3266,239 @@ function createWriteTool(cwd, options) {
|
|
|
3296
3266
|
}
|
|
3297
3267
|
var writeTool = createWriteTool(process.cwd());
|
|
3298
3268
|
|
|
3269
|
+
// src/tools/skill-tool.ts
|
|
3270
|
+
import { readdir } from "fs/promises";
|
|
3271
|
+
import { join as join7, extname } from "path";
|
|
3272
|
+
import { existsSync as existsSync6, statSync as statSync3 } from "fs";
|
|
3273
|
+
import { Type as Type8 } from "@sinclair/typebox";
|
|
3274
|
+
import { spawn as spawn7 } from "child_process";
|
|
3275
|
+
function getInterpreter(scriptPath) {
|
|
3276
|
+
const ext = extname(scriptPath);
|
|
3277
|
+
switch (ext) {
|
|
3278
|
+
case ".sh":
|
|
3279
|
+
case ".bash":
|
|
3280
|
+
return { command: "bash", args: [scriptPath] };
|
|
3281
|
+
case ".py":
|
|
3282
|
+
return { command: "python3", args: [scriptPath] };
|
|
3283
|
+
case ".js":
|
|
3284
|
+
return { command: "node", args: [scriptPath] };
|
|
3285
|
+
case ".ts":
|
|
3286
|
+
return { command: "bun", args: ["run", scriptPath] };
|
|
3287
|
+
default:
|
|
3288
|
+
try {
|
|
3289
|
+
const stats = statSync3(scriptPath);
|
|
3290
|
+
if (stats.mode & 73) {
|
|
3291
|
+
return { command: scriptPath, args: [] };
|
|
3292
|
+
}
|
|
3293
|
+
} catch {}
|
|
3294
|
+
return null;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
async function executeScript(scriptPath, args, cwd, timeout = 30) {
|
|
3298
|
+
const interpreter = getInterpreter(scriptPath);
|
|
3299
|
+
if (!interpreter) {
|
|
3300
|
+
throw new Error(`Unsupported script type: ${scriptPath}`);
|
|
3301
|
+
}
|
|
3302
|
+
return new Promise((resolve, reject) => {
|
|
3303
|
+
const child = spawn7(interpreter.command, [...interpreter.args, ...args], {
|
|
3304
|
+
cwd,
|
|
3305
|
+
timeout: timeout * 1000
|
|
3306
|
+
});
|
|
3307
|
+
let stdout = "";
|
|
3308
|
+
let stderr = "";
|
|
3309
|
+
let timedOut = false;
|
|
3310
|
+
if (child.stdout) {
|
|
3311
|
+
child.stdout.on("data", (data) => {
|
|
3312
|
+
stdout += data.toString();
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
if (child.stderr) {
|
|
3316
|
+
child.stderr.on("data", (data) => {
|
|
3317
|
+
stderr += data.toString();
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
const timeoutHandle = setTimeout(() => {
|
|
3321
|
+
timedOut = true;
|
|
3322
|
+
child.kill("SIGTERM");
|
|
3323
|
+
}, timeout * 1000);
|
|
3324
|
+
child.on("error", (err) => {
|
|
3325
|
+
clearTimeout(timeoutHandle);
|
|
3326
|
+
reject(err);
|
|
3327
|
+
});
|
|
3328
|
+
child.on("close", (code) => {
|
|
3329
|
+
clearTimeout(timeoutHandle);
|
|
3330
|
+
if (timedOut) {
|
|
3331
|
+
reject(new Error(`Script execution timed out after ${timeout}s`));
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
resolve({ stdout, stderr, exitCode: code });
|
|
3335
|
+
});
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
var skillScriptSchema = Type8.Object({
|
|
3339
|
+
skillName: Type8.String({ description: "Name of the skill" }),
|
|
3340
|
+
scriptName: Type8.String({ description: "Name of the script to execute (without path)" }),
|
|
3341
|
+
args: Type8.Optional(Type8.Array(Type8.String(), { description: "Arguments to pass to the script" })),
|
|
3342
|
+
timeout: Type8.Optional(Type8.Number({ description: "Timeout in seconds (default: 30)" }))
|
|
3343
|
+
});
|
|
3344
|
+
function createSkillScriptTool(skills, cwd) {
|
|
3345
|
+
return {
|
|
3346
|
+
name: "execute_skill_script",
|
|
3347
|
+
description: "Execute a script from an activated skill. Skills provide executable scripts in their scripts/ directory.",
|
|
3348
|
+
input_schema: skillScriptSchema,
|
|
3349
|
+
execute: async (input) => {
|
|
3350
|
+
const { skillName, scriptName, args = [], timeout = 30 } = input;
|
|
3351
|
+
const skill = skills.find((s) => s.name === skillName);
|
|
3352
|
+
if (!skill) {
|
|
3353
|
+
return {
|
|
3354
|
+
error: `Skill not found: ${skillName}. Available skills: ${skills.map((s) => s.name).join(", ")}`
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
const scriptsDir = join7(skill.skillPath, "scripts");
|
|
3358
|
+
if (!existsSync6(scriptsDir)) {
|
|
3359
|
+
return {
|
|
3360
|
+
error: `Skill '${skillName}' has no scripts directory`
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3363
|
+
const scriptPath = join7(scriptsDir, scriptName);
|
|
3364
|
+
if (!existsSync6(scriptPath)) {
|
|
3365
|
+
try {
|
|
3366
|
+
const availableScripts = await readdir(scriptsDir);
|
|
3367
|
+
return {
|
|
3368
|
+
error: `Script '${scriptName}' not found in skill '${skillName}'. Available scripts: ${availableScripts.join(", ")}`
|
|
3369
|
+
};
|
|
3370
|
+
} catch {
|
|
3371
|
+
return {
|
|
3372
|
+
error: `Script '${scriptName}' not found in skill '${skillName}'`
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
try {
|
|
3377
|
+
const result = await executeScript(scriptPath, args, cwd, timeout);
|
|
3378
|
+
let output = "";
|
|
3379
|
+
if (result.stdout) {
|
|
3380
|
+
output += `STDOUT:
|
|
3381
|
+
${result.stdout}
|
|
3382
|
+
`;
|
|
3383
|
+
}
|
|
3384
|
+
if (result.stderr) {
|
|
3385
|
+
output += `STDERR:
|
|
3386
|
+
${result.stderr}
|
|
3387
|
+
`;
|
|
3388
|
+
}
|
|
3389
|
+
output += `Exit code: ${result.exitCode}
|
|
3390
|
+
`;
|
|
3391
|
+
return output;
|
|
3392
|
+
} catch (error) {
|
|
3393
|
+
return {
|
|
3394
|
+
error: `Failed to execute script: ${error instanceof Error ? error.message : String(error)}`
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// src/tools/skill-reference-tool.ts
|
|
3402
|
+
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
3403
|
+
import { join as join8, normalize, relative as relative3 } from "path";
|
|
3404
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3405
|
+
import { Type as Type9 } from "@sinclair/typebox";
|
|
3406
|
+
function isPathSafe(basePath, requestedPath) {
|
|
3407
|
+
const normalized = normalize(requestedPath);
|
|
3408
|
+
const fullPath = join8(basePath, normalized);
|
|
3409
|
+
const relativePath = relative3(basePath, fullPath);
|
|
3410
|
+
return !relativePath.startsWith("..") && !relativePath.startsWith("/");
|
|
3411
|
+
}
|
|
3412
|
+
var skillReferenceSchema = Type9.Object({
|
|
3413
|
+
skillName: Type9.String({ description: "Name of the skill" }),
|
|
3414
|
+
referencePath: Type9.String({
|
|
3415
|
+
description: `Path to the reference file relative to the skill's references/ directory (e.g., "api-reference.md" or "guides/setup.md")`
|
|
3416
|
+
})
|
|
3417
|
+
});
|
|
3418
|
+
function createSkillReferenceTool(skills) {
|
|
3419
|
+
return {
|
|
3420
|
+
name: "read_skill_reference",
|
|
3421
|
+
description: "Read a reference file from an activated skill. Skills provide additional documentation in their references/ directory.",
|
|
3422
|
+
input_schema: skillReferenceSchema,
|
|
3423
|
+
execute: async (input) => {
|
|
3424
|
+
const { skillName, referencePath } = input;
|
|
3425
|
+
const skill = skills.find((s) => s.name === skillName);
|
|
3426
|
+
if (!skill) {
|
|
3427
|
+
return {
|
|
3428
|
+
error: `Skill not found: ${skillName}. Available skills: ${skills.map((s) => s.name).join(", ")}`
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
const referencesDir = join8(skill.skillPath, "references");
|
|
3432
|
+
if (!existsSync7(referencesDir)) {
|
|
3433
|
+
return {
|
|
3434
|
+
error: `Skill '${skillName}' has no references directory`
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
if (!isPathSafe(referencesDir, referencePath)) {
|
|
3438
|
+
return {
|
|
3439
|
+
error: `Invalid reference path: ${referencePath}. Path must be within the skill's references directory.`
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
const fullPath = join8(referencesDir, referencePath);
|
|
3443
|
+
if (!existsSync7(fullPath)) {
|
|
3444
|
+
try {
|
|
3445
|
+
const availableRefs = await listReferencesRecursive(referencesDir);
|
|
3446
|
+
return {
|
|
3447
|
+
error: `Reference file '${referencePath}' not found in skill '${skillName}'. Available references:
|
|
3448
|
+
${availableRefs.join(`
|
|
3449
|
+
`)}`
|
|
3450
|
+
};
|
|
3451
|
+
} catch {
|
|
3452
|
+
return {
|
|
3453
|
+
error: `Reference file '${referencePath}' not found in skill '${skillName}'`
|
|
3454
|
+
};
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
try {
|
|
3458
|
+
const content = await readFile2(fullPath, "utf-8");
|
|
3459
|
+
return content;
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
return {
|
|
3462
|
+
error: `Failed to read reference file: ${error instanceof Error ? error.message : String(error)}`
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
async function listReferencesRecursive(dir, prefix = "") {
|
|
3469
|
+
const files = [];
|
|
3470
|
+
try {
|
|
3471
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
3472
|
+
for (const entry of entries) {
|
|
3473
|
+
const relativePath = prefix ? join8(prefix, entry.name) : entry.name;
|
|
3474
|
+
if (entry.isDirectory()) {
|
|
3475
|
+
const subFiles = await listReferencesRecursive(join8(dir, entry.name), relativePath);
|
|
3476
|
+
files.push(...subFiles);
|
|
3477
|
+
} else {
|
|
3478
|
+
files.push(relativePath);
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
} catch (error) {
|
|
3482
|
+
console.error(`Failed to list references in ${dir}:`, error);
|
|
3483
|
+
}
|
|
3484
|
+
return files;
|
|
3485
|
+
}
|
|
3299
3486
|
// src/tools/index.ts
|
|
3300
3487
|
function createCodingTools(cwd, options) {
|
|
3301
|
-
|
|
3488
|
+
const baseTools = [
|
|
3302
3489
|
createReadTool(cwd, options?.read),
|
|
3303
3490
|
createBashTool(cwd, options?.bash),
|
|
3304
3491
|
createEditTool(cwd),
|
|
3305
3492
|
createWriteTool(cwd)
|
|
3306
3493
|
];
|
|
3494
|
+
if (options?.skills && options.skills.length > 0) {
|
|
3495
|
+
const skillTools = [
|
|
3496
|
+
createSkillScriptTool(options.skills, cwd),
|
|
3497
|
+
createSkillReferenceTool(options.skills)
|
|
3498
|
+
];
|
|
3499
|
+
return [...baseTools, ...skillTools];
|
|
3500
|
+
}
|
|
3501
|
+
return baseTools;
|
|
3307
3502
|
}
|
|
3308
3503
|
|
|
3309
3504
|
// src/provider.ts
|
|
@@ -3493,15 +3688,16 @@ var PROVIDERS = [
|
|
|
3493
3688
|
];
|
|
3494
3689
|
|
|
3495
3690
|
// src/paths.ts
|
|
3496
|
-
import { join as
|
|
3691
|
+
import { join as join9 } from "path";
|
|
3497
3692
|
import { homedir as homedir2 } from "os";
|
|
3498
|
-
var APP_SUPPORT_DIR =
|
|
3499
|
-
var DATA_DIR =
|
|
3693
|
+
var APP_SUPPORT_DIR = join9(homedir2(), "Library", "Application Support", "Tarsk");
|
|
3694
|
+
var DATA_DIR = join9(APP_SUPPORT_DIR, "data");
|
|
3500
3695
|
function getDataDir() {
|
|
3501
3696
|
return DATA_DIR;
|
|
3502
3697
|
}
|
|
3503
3698
|
|
|
3504
|
-
// src/managers/pi-
|
|
3699
|
+
// src/managers/pi-model-resolver.ts
|
|
3700
|
+
import { getModel } from "@mariozechner/pi-ai";
|
|
3505
3701
|
var PROVIDER_NAME_TO_PI = {
|
|
3506
3702
|
anthropic: "anthropic",
|
|
3507
3703
|
openai: "openai",
|
|
@@ -3549,32 +3745,188 @@ function resolveModel(providerName, modelId, providerConfig) {
|
|
|
3549
3745
|
maxTokens: 16384
|
|
3550
3746
|
};
|
|
3551
3747
|
}
|
|
3748
|
+
|
|
3749
|
+
// src/managers/pi-event-transformer.ts
|
|
3552
3750
|
function extractTextFromAssistantMessage(msg) {
|
|
3553
3751
|
return msg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
3554
3752
|
}
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3753
|
+
|
|
3754
|
+
class EventQueue {
|
|
3755
|
+
queue = [];
|
|
3756
|
+
resolver = null;
|
|
3757
|
+
finalContent = "";
|
|
3758
|
+
errorOccurred = false;
|
|
3759
|
+
handlePiEvent(event) {
|
|
3760
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
3761
|
+
const delta = event.assistantMessageEvent.delta;
|
|
3762
|
+
this.finalContent += delta;
|
|
3763
|
+
this.push({
|
|
3764
|
+
type: "message",
|
|
3765
|
+
role: "assistant",
|
|
3766
|
+
content: delta
|
|
3767
|
+
});
|
|
3768
|
+
} else if (event.type === "tool_execution_start") {
|
|
3769
|
+
this.push({
|
|
3770
|
+
type: "message",
|
|
3771
|
+
role: "tool",
|
|
3772
|
+
content: JSON.stringify([
|
|
3773
|
+
{
|
|
3774
|
+
type: "tool_use",
|
|
3775
|
+
name: event.toolName,
|
|
3776
|
+
id: event.toolCallId,
|
|
3777
|
+
input: event.args
|
|
3778
|
+
}
|
|
3779
|
+
])
|
|
3780
|
+
});
|
|
3781
|
+
} else if (event.type === "tool_execution_end") {
|
|
3782
|
+
const resultContent = event.result?.content?.filter((b) => b.type === "text").map((b) => b.text || "").join("") || JSON.stringify(event.result);
|
|
3783
|
+
this.push({
|
|
3784
|
+
type: "message",
|
|
3785
|
+
role: "tool",
|
|
3786
|
+
content: JSON.stringify([
|
|
3787
|
+
{
|
|
3788
|
+
type: "tool-result",
|
|
3789
|
+
toolCallId: event.toolCallId,
|
|
3790
|
+
toolName: event.toolName,
|
|
3791
|
+
content: resultContent,
|
|
3792
|
+
isError: event.isError
|
|
3793
|
+
}
|
|
3794
|
+
])
|
|
3795
|
+
});
|
|
3796
|
+
} else if (event.type === "agent_end") {
|
|
3797
|
+
const messages = event.messages;
|
|
3798
|
+
const lastAssistant = [...messages].reverse().find((m) => ("role" in m) && m.role === "assistant");
|
|
3799
|
+
if (lastAssistant) {
|
|
3800
|
+
const extracted = extractTextFromAssistantMessage(lastAssistant);
|
|
3801
|
+
if (extracted && extracted !== this.finalContent) {
|
|
3802
|
+
this.finalContent = extracted;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
this.notifyResolver();
|
|
3560
3807
|
}
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3808
|
+
push(event) {
|
|
3809
|
+
this.queue.push(event);
|
|
3810
|
+
}
|
|
3811
|
+
shift() {
|
|
3812
|
+
return this.queue.shift();
|
|
3813
|
+
}
|
|
3814
|
+
get length() {
|
|
3815
|
+
return this.queue.length;
|
|
3816
|
+
}
|
|
3817
|
+
setResolver(resolver) {
|
|
3818
|
+
this.resolver = resolver;
|
|
3819
|
+
}
|
|
3820
|
+
clearResolver() {
|
|
3821
|
+
this.resolver = null;
|
|
3822
|
+
}
|
|
3823
|
+
notifyResolver() {
|
|
3824
|
+
if (this.resolver) {
|
|
3825
|
+
this.resolver();
|
|
3826
|
+
this.resolver = null;
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
notify() {
|
|
3830
|
+
this.notifyResolver();
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
// src/managers/pi-prompt-loader.ts
|
|
3835
|
+
import { resolve, dirname as dirname5 } from "path";
|
|
3836
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
3837
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3838
|
+
var DEFAULT_PROMPT = "You are a helpful coding assistant. You have access to read, bash, edit, and write tools. Use them to explore and modify the codebase as needed.";
|
|
3839
|
+
function loadAgentSystemPrompt(skills) {
|
|
3840
|
+
let basePrompt;
|
|
3565
3841
|
try {
|
|
3566
3842
|
const __filename2 = fileURLToPath2(import.meta.url);
|
|
3567
3843
|
const __dirname3 = dirname5(__filename2);
|
|
3568
3844
|
const agentsPath = resolve(__dirname3, "../../agents.md");
|
|
3569
3845
|
const content = readFileSync3(agentsPath, "utf-8");
|
|
3570
3846
|
console.log("[ai] Successfully loaded agents.md for system prompt");
|
|
3571
|
-
|
|
3847
|
+
basePrompt = content.trim();
|
|
3572
3848
|
} catch (error) {
|
|
3573
|
-
|
|
3574
|
-
|
|
3849
|
+
const isFileNotFound = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
3850
|
+
if (isFileNotFound) {
|
|
3851
|
+
console.log("[ai] agents.md not found, using default system prompt");
|
|
3852
|
+
} else {
|
|
3853
|
+
console.warn("[ai] Error loading agents.md, using default prompt:", error instanceof Error ? error.message : String(error));
|
|
3854
|
+
}
|
|
3855
|
+
basePrompt = DEFAULT_PROMPT;
|
|
3856
|
+
}
|
|
3857
|
+
if (!skills || skills.length === 0) {
|
|
3858
|
+
return basePrompt;
|
|
3859
|
+
}
|
|
3860
|
+
let skillsSection = `
|
|
3861
|
+
|
|
3862
|
+
# Available Skills
|
|
3863
|
+
|
|
3864
|
+
`;
|
|
3865
|
+
skillsSection += `The following skills are available for this session. Use them to enhance your capabilities:
|
|
3866
|
+
|
|
3867
|
+
`;
|
|
3868
|
+
for (const skill of skills) {
|
|
3869
|
+
skillsSection += `## ${skill.name}
|
|
3870
|
+
|
|
3871
|
+
`;
|
|
3872
|
+
skillsSection += `**Description**: ${skill.description}
|
|
3873
|
+
|
|
3874
|
+
`;
|
|
3875
|
+
if (skill.compatibility) {
|
|
3876
|
+
skillsSection += `**Compatibility**: ${skill.compatibility}
|
|
3877
|
+
|
|
3878
|
+
`;
|
|
3879
|
+
}
|
|
3880
|
+
skillsSection += skill.instructions;
|
|
3881
|
+
skillsSection += `
|
|
3882
|
+
|
|
3883
|
+
---
|
|
3884
|
+
|
|
3885
|
+
`;
|
|
3886
|
+
}
|
|
3887
|
+
console.log(`[ai] Injected ${skills.length} skill(s) into system prompt: ${skills.map((s) => s.name).join(", ")}`);
|
|
3888
|
+
return basePrompt + skillsSection;
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
// src/managers/pi-error-utils.ts
|
|
3892
|
+
function getErrorCode(error) {
|
|
3893
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
3894
|
+
const code = error.code;
|
|
3895
|
+
if (typeof code === "string")
|
|
3896
|
+
return code;
|
|
3897
|
+
}
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3900
|
+
function formatErrorEvent(error) {
|
|
3901
|
+
let errCode;
|
|
3902
|
+
let errMessage;
|
|
3903
|
+
let errStack;
|
|
3904
|
+
let errDetails;
|
|
3905
|
+
if (error instanceof Error) {
|
|
3906
|
+
errMessage = error.message || "Unknown error";
|
|
3907
|
+
errStack = error.stack;
|
|
3908
|
+
errCode = getErrorCode(error);
|
|
3909
|
+
} else if (typeof error === "object" && error !== null) {
|
|
3910
|
+
errMessage = error.message ? String(error.message) : JSON.stringify(error);
|
|
3911
|
+
errCode = getErrorCode(error);
|
|
3912
|
+
errDetails = error;
|
|
3913
|
+
} else if (typeof error === "string") {
|
|
3914
|
+
errMessage = error;
|
|
3915
|
+
} else {
|
|
3916
|
+
errMessage = String(error);
|
|
3575
3917
|
}
|
|
3918
|
+
return {
|
|
3919
|
+
type: "error",
|
|
3920
|
+
content: errMessage,
|
|
3921
|
+
error: {
|
|
3922
|
+
code: errCode || "EXECUTION_ERROR",
|
|
3923
|
+
message: errMessage || "An error occurred during execution",
|
|
3924
|
+
details: { stack: errStack, originalError: errDetails }
|
|
3925
|
+
}
|
|
3926
|
+
};
|
|
3576
3927
|
}
|
|
3577
3928
|
|
|
3929
|
+
// src/managers/pi-executor.ts
|
|
3578
3930
|
class PiExecutorImpl {
|
|
3579
3931
|
metadataManager;
|
|
3580
3932
|
constructor(metadataManager) {
|
|
@@ -3591,7 +3943,7 @@ class PiExecutorImpl {
|
|
|
3591
3943
|
throw new Error("No model specified in execution context");
|
|
3592
3944
|
}
|
|
3593
3945
|
model = model.replace(`${providerName.toLowerCase()}/`, "");
|
|
3594
|
-
const cwd = isAbsolute2(context.threadPath) ? context.threadPath :
|
|
3946
|
+
const cwd = isAbsolute2(context.threadPath) ? context.threadPath : resolve2(getDataDir(), context.threadPath);
|
|
3595
3947
|
console.log("[ai] Execution context:", {
|
|
3596
3948
|
model,
|
|
3597
3949
|
cwd,
|
|
@@ -3619,9 +3971,9 @@ class PiExecutorImpl {
|
|
|
3619
3971
|
api: resolvedModel.api,
|
|
3620
3972
|
provider: resolvedModel.provider
|
|
3621
3973
|
});
|
|
3622
|
-
const tools = createCodingTools(cwd);
|
|
3623
|
-
const systemPrompt = loadAgentSystemPrompt();
|
|
3624
|
-
console.log("[ai] System prompt loaded from agents.md");
|
|
3974
|
+
const tools = createCodingTools(cwd, { skills: context.skills });
|
|
3975
|
+
const systemPrompt = loadAgentSystemPrompt(context.skills);
|
|
3976
|
+
console.log("[ai] System prompt loaded from agents.md", context.skills && context.skills.length > 0 ? `with ${context.skills.length} skill(s)` : "");
|
|
3625
3977
|
const agent = new Agent({
|
|
3626
3978
|
initialState: {
|
|
3627
3979
|
systemPrompt,
|
|
@@ -3630,74 +3982,19 @@ class PiExecutorImpl {
|
|
|
3630
3982
|
},
|
|
3631
3983
|
getApiKey: async () => apiKey
|
|
3632
3984
|
});
|
|
3633
|
-
const
|
|
3634
|
-
let resolver = null;
|
|
3985
|
+
const eventQueue = new EventQueue;
|
|
3635
3986
|
let done = false;
|
|
3636
|
-
let finalContent = "";
|
|
3637
|
-
let errorOccurred = false;
|
|
3638
3987
|
const unsubscribe = agent.subscribe((event) => {
|
|
3639
|
-
|
|
3640
|
-
const delta = event.assistantMessageEvent.delta;
|
|
3641
|
-
finalContent += delta;
|
|
3642
|
-
queue.push({
|
|
3643
|
-
type: "message",
|
|
3644
|
-
role: "assistant",
|
|
3645
|
-
content: delta
|
|
3646
|
-
});
|
|
3647
|
-
} else if (event.type === "tool_execution_start") {
|
|
3648
|
-
queue.push({
|
|
3649
|
-
type: "message",
|
|
3650
|
-
role: "tool",
|
|
3651
|
-
content: JSON.stringify([
|
|
3652
|
-
{
|
|
3653
|
-
type: "tool_use",
|
|
3654
|
-
name: event.toolName,
|
|
3655
|
-
id: event.toolCallId,
|
|
3656
|
-
input: event.args
|
|
3657
|
-
}
|
|
3658
|
-
])
|
|
3659
|
-
});
|
|
3660
|
-
} else if (event.type === "tool_execution_end") {
|
|
3661
|
-
const resultContent = event.result?.content?.filter((b) => b.type === "text").map((b) => b.text || "").join("") || JSON.stringify(event.result);
|
|
3662
|
-
queue.push({
|
|
3663
|
-
type: "message",
|
|
3664
|
-
role: "tool",
|
|
3665
|
-
content: JSON.stringify([
|
|
3666
|
-
{
|
|
3667
|
-
type: "tool-result",
|
|
3668
|
-
toolCallId: event.toolCallId,
|
|
3669
|
-
toolName: event.toolName,
|
|
3670
|
-
content: resultContent,
|
|
3671
|
-
isError: event.isError
|
|
3672
|
-
}
|
|
3673
|
-
])
|
|
3674
|
-
});
|
|
3675
|
-
} else if (event.type === "agent_end") {
|
|
3676
|
-
const messages = event.messages;
|
|
3677
|
-
const lastAssistant = [...messages].reverse().find((m) => ("role" in m) && m.role === "assistant");
|
|
3678
|
-
if (lastAssistant) {
|
|
3679
|
-
const extracted = extractTextFromAssistantMessage(lastAssistant);
|
|
3680
|
-
if (extracted && extracted !== finalContent) {
|
|
3681
|
-
finalContent = extracted;
|
|
3682
|
-
}
|
|
3683
|
-
}
|
|
3684
|
-
}
|
|
3685
|
-
if (resolver) {
|
|
3686
|
-
resolver();
|
|
3687
|
-
resolver = null;
|
|
3688
|
-
}
|
|
3988
|
+
eventQueue.handlePiEvent(event);
|
|
3689
3989
|
});
|
|
3690
3990
|
const promptDone = agent.prompt(userPrompt).then(() => {
|
|
3691
3991
|
done = true;
|
|
3692
|
-
|
|
3693
|
-
resolver();
|
|
3694
|
-
resolver = null;
|
|
3695
|
-
}
|
|
3992
|
+
eventQueue.notify();
|
|
3696
3993
|
}).catch((err) => {
|
|
3697
|
-
if (!errorOccurred) {
|
|
3698
|
-
errorOccurred = true;
|
|
3994
|
+
if (!eventQueue.errorOccurred) {
|
|
3995
|
+
eventQueue.errorOccurred = true;
|
|
3699
3996
|
const errMessage = err instanceof Error ? err.message : String(err);
|
|
3700
|
-
|
|
3997
|
+
eventQueue.push({
|
|
3701
3998
|
type: "error",
|
|
3702
3999
|
content: errMessage,
|
|
3703
4000
|
error: {
|
|
@@ -3710,68 +4007,40 @@ class PiExecutorImpl {
|
|
|
3710
4007
|
});
|
|
3711
4008
|
}
|
|
3712
4009
|
done = true;
|
|
3713
|
-
|
|
3714
|
-
resolver();
|
|
3715
|
-
resolver = null;
|
|
3716
|
-
}
|
|
4010
|
+
eventQueue.notify();
|
|
3717
4011
|
});
|
|
3718
4012
|
try {
|
|
3719
|
-
while (!done ||
|
|
3720
|
-
if (
|
|
3721
|
-
yield
|
|
4013
|
+
while (!done || eventQueue.length > 0) {
|
|
4014
|
+
if (eventQueue.length > 0) {
|
|
4015
|
+
yield eventQueue.shift();
|
|
3722
4016
|
} else {
|
|
3723
|
-
await new Promise((
|
|
3724
|
-
|
|
4017
|
+
await new Promise((resolve3) => {
|
|
4018
|
+
eventQueue.setResolver(resolve3);
|
|
3725
4019
|
});
|
|
3726
4020
|
}
|
|
3727
4021
|
}
|
|
3728
4022
|
await promptDone;
|
|
3729
|
-
if (!errorOccurred && finalContent) {
|
|
4023
|
+
if (!eventQueue.errorOccurred && eventQueue.finalContent) {
|
|
3730
4024
|
yield {
|
|
3731
4025
|
type: "result",
|
|
3732
|
-
content: finalContent
|
|
4026
|
+
content: eventQueue.finalContent
|
|
3733
4027
|
};
|
|
3734
4028
|
}
|
|
3735
4029
|
} finally {
|
|
3736
4030
|
unsubscribe();
|
|
3737
4031
|
agent.abort();
|
|
4032
|
+
eventQueue.clearResolver();
|
|
3738
4033
|
}
|
|
3739
4034
|
} catch (error) {
|
|
3740
4035
|
console.error("[ai] Error during execution:", error);
|
|
3741
|
-
|
|
3742
|
-
let errMessage;
|
|
3743
|
-
let errStack;
|
|
3744
|
-
let errDetails;
|
|
3745
|
-
if (error instanceof Error) {
|
|
3746
|
-
errMessage = error.message || "Unknown error";
|
|
3747
|
-
errStack = error.stack;
|
|
3748
|
-
errCode = getErrorCode(error);
|
|
3749
|
-
} else if (typeof error === "object" && error !== null) {
|
|
3750
|
-
errMessage = error.message ? String(error.message) : JSON.stringify(error);
|
|
3751
|
-
errCode = getErrorCode(error);
|
|
3752
|
-
errDetails = error;
|
|
3753
|
-
} else if (typeof error === "string") {
|
|
3754
|
-
errMessage = error;
|
|
3755
|
-
} else {
|
|
3756
|
-
errMessage = String(error);
|
|
3757
|
-
}
|
|
3758
|
-
console.error("[ai] Error details:", { errCode, errMessage, errStack });
|
|
3759
|
-
yield {
|
|
3760
|
-
type: "error",
|
|
3761
|
-
content: errMessage,
|
|
3762
|
-
error: {
|
|
3763
|
-
code: errCode || "EXECUTION_ERROR",
|
|
3764
|
-
message: errMessage || "An error occurred during execution",
|
|
3765
|
-
details: { stack: errStack, originalError: errDetails }
|
|
3766
|
-
}
|
|
3767
|
-
};
|
|
4036
|
+
yield formatErrorEvent(error);
|
|
3768
4037
|
}
|
|
3769
4038
|
}
|
|
3770
4039
|
}
|
|
3771
4040
|
|
|
3772
4041
|
// src/managers/conversation-manager.ts
|
|
3773
4042
|
import { promises as fs2 } from "fs";
|
|
3774
|
-
import { join as
|
|
4043
|
+
import { join as join10, dirname as dirname6 } from "path";
|
|
3775
4044
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3776
4045
|
var CONVERSATION_FILE = "conversation-history.json";
|
|
3777
4046
|
|
|
@@ -3860,8 +4129,8 @@ class ConversationManagerImpl {
|
|
|
3860
4129
|
}
|
|
3861
4130
|
getConversationFilePath(threadPath) {
|
|
3862
4131
|
const threadId = threadPath.split(/[\\/]/).pop() || "unknown";
|
|
3863
|
-
const conversationsDir =
|
|
3864
|
-
return
|
|
4132
|
+
const conversationsDir = join10(this.metadataDir, "conversations");
|
|
4133
|
+
return join10(conversationsDir, threadId, CONVERSATION_FILE);
|
|
3865
4134
|
}
|
|
3866
4135
|
}
|
|
3867
4136
|
|
|
@@ -3908,6 +4177,9 @@ function streamAsyncGenerator(c, generator, options) {
|
|
|
3908
4177
|
});
|
|
3909
4178
|
}
|
|
3910
4179
|
|
|
4180
|
+
// src/routes/projects.ts
|
|
4181
|
+
init_dist();
|
|
4182
|
+
|
|
3911
4183
|
// src/lib/response-builder.ts
|
|
3912
4184
|
class ResponseBuilder {
|
|
3913
4185
|
static error(code, message, statusCode, details) {
|
|
@@ -3927,8 +4199,17 @@ class ResponseBuilder {
|
|
|
3927
4199
|
}
|
|
3928
4200
|
}
|
|
3929
4201
|
|
|
4202
|
+
// src/lib/route-helpers.ts
|
|
4203
|
+
function errorResponse(c, code, message, statusCode, details) {
|
|
4204
|
+
const { response, statusCode: status } = ResponseBuilder.error(code, message, statusCode, details);
|
|
4205
|
+
return c.json(response, status);
|
|
4206
|
+
}
|
|
4207
|
+
function successResponse(c, data, statusCode = 200) {
|
|
4208
|
+
const { response, statusCode: status } = ResponseBuilder.success(data, statusCode);
|
|
4209
|
+
return c.json(response, status);
|
|
4210
|
+
}
|
|
4211
|
+
|
|
3930
4212
|
// src/routes/projects.ts
|
|
3931
|
-
init_dist();
|
|
3932
4213
|
function createProjectRoutes(projectManager, threadManager) {
|
|
3933
4214
|
const router = new Hono;
|
|
3934
4215
|
router.post("/", async (c) => {
|
|
@@ -3936,14 +4217,12 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
3936
4217
|
const body = await c.req.json();
|
|
3937
4218
|
const { gitUrl, commitMethod } = body;
|
|
3938
4219
|
if (!gitUrl || typeof gitUrl !== "string") {
|
|
3939
|
-
|
|
3940
|
-
return c.json(response, statusCode);
|
|
4220
|
+
return errorResponse(c, "INVALID_REQUEST", "gitUrl is required and must be a string", 400);
|
|
3941
4221
|
}
|
|
3942
4222
|
const method = isValidCommitMethod(commitMethod) ? commitMethod : CommitMethods.PullRequest;
|
|
3943
4223
|
return streamAsyncGenerator(c, projectManager.createProject(gitUrl, method));
|
|
3944
4224
|
} catch (error) {
|
|
3945
|
-
|
|
3946
|
-
return c.json(response, statusCode);
|
|
4225
|
+
return errorResponse(c, "REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
|
|
3947
4226
|
}
|
|
3948
4227
|
});
|
|
3949
4228
|
router.get("/", async (c) => {
|
|
@@ -3972,46 +4251,38 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
3972
4251
|
}));
|
|
3973
4252
|
return c.json(expandedProjects);
|
|
3974
4253
|
} catch (error) {
|
|
3975
|
-
|
|
3976
|
-
return c.json(response, statusCode);
|
|
4254
|
+
return errorResponse(c, "LIST_PROJECTS_ERROR", "Failed to list projects", 500, error instanceof Error ? error.message : String(error));
|
|
3977
4255
|
}
|
|
3978
4256
|
});
|
|
3979
4257
|
router.get("/:id", async (c) => {
|
|
3980
4258
|
try {
|
|
3981
4259
|
const projectId = c.req.param("id");
|
|
3982
4260
|
if (!projectId) {
|
|
3983
|
-
|
|
3984
|
-
return c.json(response, statusCode);
|
|
4261
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
3985
4262
|
}
|
|
3986
4263
|
const project = await projectManager.getProject(projectId);
|
|
3987
4264
|
if (!project) {
|
|
3988
|
-
|
|
3989
|
-
return c.json(response, statusCode);
|
|
4265
|
+
return errorResponse(c, "PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
|
|
3990
4266
|
}
|
|
3991
4267
|
return c.json(project);
|
|
3992
4268
|
} catch (error) {
|
|
3993
|
-
|
|
3994
|
-
return c.json(response, statusCode);
|
|
4269
|
+
return errorResponse(c, "GET_PROJECT_ERROR", "Failed to get project", 500, error instanceof Error ? error.message : String(error));
|
|
3995
4270
|
}
|
|
3996
4271
|
});
|
|
3997
4272
|
router.delete("/:id", async (c) => {
|
|
3998
4273
|
try {
|
|
3999
4274
|
const projectId = c.req.param("id");
|
|
4000
4275
|
if (!projectId) {
|
|
4001
|
-
|
|
4002
|
-
return c.json(response2, statusCode2);
|
|
4276
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4003
4277
|
}
|
|
4004
4278
|
await projectManager.deleteProject(projectId);
|
|
4005
|
-
|
|
4006
|
-
return c.json(response, statusCode);
|
|
4279
|
+
return successResponse(c, { success: true, message: "Project deleted successfully" });
|
|
4007
4280
|
} catch (error) {
|
|
4008
4281
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4009
4282
|
if (errorMessage.includes("not found")) {
|
|
4010
|
-
|
|
4011
|
-
return c.json(response2, statusCode2);
|
|
4283
|
+
return errorResponse(c, "PROJECT_NOT_FOUND", errorMessage, 404);
|
|
4012
4284
|
}
|
|
4013
|
-
|
|
4014
|
-
return c.json(response, statusCode);
|
|
4285
|
+
return errorResponse(c, "DELETE_PROJECT_ERROR", "Failed to delete project", 500, errorMessage);
|
|
4015
4286
|
}
|
|
4016
4287
|
});
|
|
4017
4288
|
router.post("/:id/open", async (c) => {
|
|
@@ -4021,31 +4292,25 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
4021
4292
|
const { program } = body;
|
|
4022
4293
|
console.log("[POST /api/projects/:id/open] projectId=%s program=%s", projectId, program);
|
|
4023
4294
|
if (!projectId) {
|
|
4024
|
-
|
|
4025
|
-
return c.json(response2, statusCode2);
|
|
4295
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4026
4296
|
}
|
|
4027
4297
|
if (!program) {
|
|
4028
|
-
|
|
4029
|
-
return c.json(response2, statusCode2);
|
|
4298
|
+
return errorResponse(c, "INVALID_REQUEST", "Program is required", 400);
|
|
4030
4299
|
}
|
|
4031
4300
|
if (!AVAILABLE_PROGRAMS.includes(program)) {
|
|
4032
|
-
|
|
4033
|
-
return c.json(response2, statusCode2);
|
|
4301
|
+
return errorResponse(c, "INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS.join(", ")}`, 400);
|
|
4034
4302
|
}
|
|
4035
4303
|
const project = await projectManager.getProject(projectId);
|
|
4036
4304
|
if (!project) {
|
|
4037
|
-
|
|
4038
|
-
return c.json(response2, statusCode2);
|
|
4305
|
+
return errorResponse(c, "PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
|
|
4039
4306
|
}
|
|
4040
4307
|
console.log("[POST /api/projects/:id/open] Calling projectManager.openWith");
|
|
4041
4308
|
await projectManager.openWith(projectId, program);
|
|
4042
4309
|
console.log("[POST /api/projects/:id/open] openWith completed successfully");
|
|
4043
|
-
|
|
4044
|
-
return c.json(response, statusCode);
|
|
4310
|
+
return successResponse(c, { success: true, message: `Project opened in ${program}` });
|
|
4045
4311
|
} catch (error) {
|
|
4046
4312
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4047
|
-
|
|
4048
|
-
return c.json(response, statusCode);
|
|
4313
|
+
return errorResponse(c, "OPEN_PROGRAM_ERROR", "Failed to open project", 500, errorMessage);
|
|
4049
4314
|
}
|
|
4050
4315
|
});
|
|
4051
4316
|
router.put("/:id", async (c) => {
|
|
@@ -4054,22 +4319,18 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
4054
4319
|
const body = await c.req.json();
|
|
4055
4320
|
const { program, setupScript, name, runCommand, commitMethod, runNow } = body;
|
|
4056
4321
|
if (!projectId) {
|
|
4057
|
-
|
|
4058
|
-
return c.json(response2, statusCode2);
|
|
4322
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4059
4323
|
}
|
|
4060
4324
|
const project = await projectManager.getProject(projectId);
|
|
4061
4325
|
if (!project) {
|
|
4062
|
-
|
|
4063
|
-
return c.json(response2, statusCode2);
|
|
4326
|
+
return errorResponse(c, "PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
|
|
4064
4327
|
}
|
|
4065
4328
|
if (program !== undefined) {
|
|
4066
4329
|
if (!program) {
|
|
4067
|
-
|
|
4068
|
-
return c.json(response2, statusCode2);
|
|
4330
|
+
return errorResponse(c, "INVALID_REQUEST", "Program cannot be empty", 400);
|
|
4069
4331
|
}
|
|
4070
4332
|
if (!AVAILABLE_PROGRAMS.includes(program)) {
|
|
4071
|
-
|
|
4072
|
-
return c.json(response2, statusCode2);
|
|
4333
|
+
return errorResponse(c, "INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS.join(", ")}`, 400);
|
|
4073
4334
|
}
|
|
4074
4335
|
await projectManager.updateOpenWith(projectId, program);
|
|
4075
4336
|
}
|
|
@@ -4087,24 +4348,20 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
4087
4348
|
if (commitMethod !== undefined) {
|
|
4088
4349
|
if (!isValidCommitMethod(commitMethod)) {
|
|
4089
4350
|
const validMethods = Object.values(CommitMethods).join('", "');
|
|
4090
|
-
|
|
4091
|
-
return c.json(response2, statusCode2);
|
|
4351
|
+
return errorResponse(c, "INVALID_REQUEST", `Commit method must be one of: "${validMethods}"`, 400);
|
|
4092
4352
|
}
|
|
4093
4353
|
await projectManager.updateCommitMethod(projectId, commitMethod);
|
|
4094
4354
|
}
|
|
4095
4355
|
if (name !== undefined) {
|
|
4096
4356
|
if (!name || !name.trim()) {
|
|
4097
|
-
|
|
4098
|
-
return c.json(response2, statusCode2);
|
|
4357
|
+
return errorResponse(c, "INVALID_REQUEST", "Project name is required", 400);
|
|
4099
4358
|
}
|
|
4100
4359
|
await projectManager.updateProjectName(projectId, name);
|
|
4101
4360
|
}
|
|
4102
|
-
|
|
4103
|
-
return c.json(response, statusCode);
|
|
4361
|
+
return successResponse(c, { success: true, message: "Project updated" });
|
|
4104
4362
|
} catch (error) {
|
|
4105
4363
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4106
|
-
|
|
4107
|
-
return c.json(response, statusCode);
|
|
4364
|
+
return errorResponse(c, "UPDATE_PROJECT_ERROR", "Failed to update project", 500, errorMessage);
|
|
4108
4365
|
}
|
|
4109
4366
|
});
|
|
4110
4367
|
router.post("/:id/commands", async (c) => {
|
|
@@ -4112,15 +4369,12 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
4112
4369
|
const projectId = c.req.param("id");
|
|
4113
4370
|
const command = await c.req.json();
|
|
4114
4371
|
if (!projectId) {
|
|
4115
|
-
|
|
4116
|
-
return c.json(response2, statusCode2);
|
|
4372
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4117
4373
|
}
|
|
4118
4374
|
await projectManager.saveCommand(projectId, command);
|
|
4119
|
-
|
|
4120
|
-
return c.json(response, statusCode);
|
|
4375
|
+
return successResponse(c, { success: true, message: "Command saved" });
|
|
4121
4376
|
} catch (error) {
|
|
4122
|
-
|
|
4123
|
-
return c.json(response, statusCode);
|
|
4377
|
+
return errorResponse(c, "SAVE_COMMAND_ERROR", "Failed to save command", 500, error instanceof Error ? error.message : String(error));
|
|
4124
4378
|
}
|
|
4125
4379
|
});
|
|
4126
4380
|
router.delete("/:id/commands/:commandId", async (c) => {
|
|
@@ -4128,15 +4382,12 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
4128
4382
|
const projectId = c.req.param("id");
|
|
4129
4383
|
const commandId = c.req.param("commandId");
|
|
4130
4384
|
if (!projectId || !commandId) {
|
|
4131
|
-
|
|
4132
|
-
return c.json(response2, statusCode2);
|
|
4385
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID and Command ID are required", 400);
|
|
4133
4386
|
}
|
|
4134
4387
|
await projectManager.deleteCommand(projectId, commandId);
|
|
4135
|
-
|
|
4136
|
-
return c.json(response, statusCode);
|
|
4388
|
+
return successResponse(c, { success: true, message: "Command deleted" });
|
|
4137
4389
|
} catch (error) {
|
|
4138
|
-
|
|
4139
|
-
return c.json(response, statusCode);
|
|
4390
|
+
return errorResponse(c, "DELETE_COMMAND_ERROR", "Failed to delete command", 500, error instanceof Error ? error.message : String(error));
|
|
4140
4391
|
}
|
|
4141
4392
|
});
|
|
4142
4393
|
router.post("/:projectId/threads/:threadId/commands/run", async (c) => {
|
|
@@ -4144,71 +4395,58 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
4144
4395
|
const threadId = c.req.param("threadId");
|
|
4145
4396
|
const { commandLine, cwd } = await c.req.json();
|
|
4146
4397
|
if (!threadId || !commandLine) {
|
|
4147
|
-
|
|
4148
|
-
return c.json(response, statusCode);
|
|
4398
|
+
return errorResponse(c, "INVALID_REQUEST", "Thread ID and commandLine are required", 400);
|
|
4149
4399
|
}
|
|
4150
4400
|
return streamAsyncGenerator(c, projectManager.runCommand(threadId, commandLine, cwd));
|
|
4151
4401
|
} catch (error) {
|
|
4152
|
-
|
|
4153
|
-
return c.json(response, statusCode);
|
|
4402
|
+
return errorResponse(c, "RUN_COMMAND_ERROR", "Failed to run command", 500, error instanceof Error ? error.message : String(error));
|
|
4154
4403
|
}
|
|
4155
4404
|
});
|
|
4156
4405
|
router.post("/:id/run", async (c) => {
|
|
4157
4406
|
try {
|
|
4158
4407
|
const projectId = c.req.param("id");
|
|
4159
4408
|
if (!projectId) {
|
|
4160
|
-
|
|
4161
|
-
return c.json(response, statusCode);
|
|
4409
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4162
4410
|
}
|
|
4163
4411
|
return streamAsyncGenerator(c, projectManager.startRunningProcess(projectId));
|
|
4164
4412
|
} catch (error) {
|
|
4165
|
-
|
|
4166
|
-
return c.json(response, statusCode);
|
|
4413
|
+
return errorResponse(c, "START_PROCESS_ERROR", "Failed to start dev server", 500, error instanceof Error ? error.message : String(error));
|
|
4167
4414
|
}
|
|
4168
4415
|
});
|
|
4169
4416
|
router.post("/:id/run-suggest", async (c) => {
|
|
4170
4417
|
try {
|
|
4171
4418
|
const projectId = c.req.param("id");
|
|
4172
4419
|
if (!projectId) {
|
|
4173
|
-
|
|
4174
|
-
return c.json(response2, statusCode2);
|
|
4420
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4175
4421
|
}
|
|
4176
4422
|
const runCommand = await projectManager.suggestRunCommand(projectId);
|
|
4177
|
-
|
|
4178
|
-
return c.json(response, statusCode);
|
|
4423
|
+
return successResponse(c, { runCommand });
|
|
4179
4424
|
} catch (error) {
|
|
4180
|
-
|
|
4181
|
-
return c.json(response, statusCode);
|
|
4425
|
+
return errorResponse(c, "SUGGEST_RUN_COMMAND_ERROR", "Failed to suggest run command", 500, error instanceof Error ? error.message : String(error));
|
|
4182
4426
|
}
|
|
4183
4427
|
});
|
|
4184
4428
|
router.post("/:id/stop", async (c) => {
|
|
4185
4429
|
try {
|
|
4186
4430
|
const projectId = c.req.param("id");
|
|
4187
4431
|
if (!projectId) {
|
|
4188
|
-
|
|
4189
|
-
return c.json(response2, statusCode2);
|
|
4432
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4190
4433
|
}
|
|
4191
4434
|
await projectManager.stopRunningProcess(projectId);
|
|
4192
|
-
|
|
4193
|
-
return c.json(response, statusCode);
|
|
4435
|
+
return successResponse(c, { success: true, message: "Dev server stopped" });
|
|
4194
4436
|
} catch (error) {
|
|
4195
|
-
|
|
4196
|
-
return c.json(response, statusCode);
|
|
4437
|
+
return errorResponse(c, "STOP_PROCESS_ERROR", "Failed to stop dev server", 500, error instanceof Error ? error.message : String(error));
|
|
4197
4438
|
}
|
|
4198
4439
|
});
|
|
4199
4440
|
router.get("/:id/running", async (c) => {
|
|
4200
4441
|
try {
|
|
4201
4442
|
const projectId = c.req.param("id");
|
|
4202
4443
|
if (!projectId) {
|
|
4203
|
-
|
|
4204
|
-
return c.json(response2, statusCode2);
|
|
4444
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
4205
4445
|
}
|
|
4206
4446
|
const isRunning = await projectManager.isProcessRunning(projectId);
|
|
4207
|
-
|
|
4208
|
-
return c.json(response, statusCode);
|
|
4447
|
+
return successResponse(c, { isRunning });
|
|
4209
4448
|
} catch (error) {
|
|
4210
|
-
|
|
4211
|
-
return c.json(response, statusCode);
|
|
4449
|
+
return errorResponse(c, "CHECK_PROCESS_ERROR", "Failed to check process status", 500, error instanceof Error ? error.message : String(error));
|
|
4212
4450
|
}
|
|
4213
4451
|
});
|
|
4214
4452
|
return router;
|
|
@@ -4328,6 +4566,7 @@ function extractAssistantContent(events, fallback) {
|
|
|
4328
4566
|
}
|
|
4329
4567
|
|
|
4330
4568
|
// src/routes/threads.ts
|
|
4569
|
+
import open2 from "open";
|
|
4331
4570
|
var buildStructuredContentFromEvents = (events, fallbackContent) => {
|
|
4332
4571
|
if (!events || events.length === 0) {
|
|
4333
4572
|
return fallbackContent;
|
|
@@ -4378,98 +4617,81 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
|
4378
4617
|
const body = await c.req.json();
|
|
4379
4618
|
const { projectId, title } = body;
|
|
4380
4619
|
if (!projectId || typeof projectId !== "string") {
|
|
4381
|
-
|
|
4382
|
-
return c.json(response, statusCode);
|
|
4620
|
+
return errorResponse(c, "INVALID_REQUEST", "projectId is required and must be a string", 400);
|
|
4383
4621
|
}
|
|
4384
4622
|
if (title !== undefined && typeof title !== "string") {
|
|
4385
|
-
|
|
4386
|
-
return c.json(response, statusCode);
|
|
4623
|
+
return errorResponse(c, "INVALID_REQUEST", "title must be a string if provided", 400);
|
|
4387
4624
|
}
|
|
4388
4625
|
if (title && title.trim() !== "") {
|
|
4389
4626
|
const nameError = validateThreadName(title);
|
|
4390
4627
|
if (nameError) {
|
|
4391
|
-
|
|
4392
|
-
return c.json(response, statusCode);
|
|
4628
|
+
return errorResponse(c, "INVALID_REQUEST", nameError, 400);
|
|
4393
4629
|
}
|
|
4394
4630
|
const existingThreads = await threadManager.listThreads(projectId);
|
|
4395
4631
|
const sanitizedNewName = gitManager.sanitizeBranchName(title);
|
|
4396
4632
|
const existingSanitized = existingThreads.map((t) => gitManager.sanitizeBranchName(t.title));
|
|
4397
4633
|
if (existingSanitized.includes(sanitizedNewName)) {
|
|
4398
|
-
|
|
4399
|
-
return c.json(response, statusCode);
|
|
4634
|
+
return errorResponse(c, "DUPLICATE_BRANCH", `A thread with branch name "${sanitizedNewName}" already exists`, 409);
|
|
4400
4635
|
}
|
|
4401
4636
|
}
|
|
4402
4637
|
return streamAsyncGenerator(c, threadManager.createThread(projectId, title));
|
|
4403
4638
|
} catch (error) {
|
|
4404
|
-
|
|
4405
|
-
return c.json(response, statusCode);
|
|
4639
|
+
return errorResponse(c, "REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
|
|
4406
4640
|
}
|
|
4407
4641
|
});
|
|
4408
4642
|
router.get("/", async (c) => {
|
|
4409
4643
|
try {
|
|
4410
4644
|
const projectId = c.req.query("projectId");
|
|
4411
4645
|
if (!projectId) {
|
|
4412
|
-
|
|
4413
|
-
return c.json(response, statusCode);
|
|
4646
|
+
return errorResponse(c, "INVALID_REQUEST", "projectId query parameter is required", 400);
|
|
4414
4647
|
}
|
|
4415
4648
|
const threads = await threadManager.listThreads(projectId);
|
|
4416
4649
|
return c.json(threads);
|
|
4417
4650
|
} catch (error) {
|
|
4418
|
-
|
|
4419
|
-
return c.json(response, statusCode);
|
|
4651
|
+
return errorResponse(c, "LIST_THREADS_ERROR", "Failed to list threads", 500, error instanceof Error ? error.message : String(error));
|
|
4420
4652
|
}
|
|
4421
4653
|
});
|
|
4422
4654
|
router.delete("/:id", async (c) => {
|
|
4423
4655
|
try {
|
|
4424
4656
|
const threadId = c.req.param("id");
|
|
4425
4657
|
if (!threadId) {
|
|
4426
|
-
|
|
4427
|
-
return c.json(response2, statusCode2);
|
|
4658
|
+
return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
|
|
4428
4659
|
}
|
|
4429
4660
|
await threadManager.deleteThread(threadId);
|
|
4430
|
-
|
|
4431
|
-
return c.json(response, statusCode);
|
|
4661
|
+
return successResponse(c, { success: true, message: "Thread deleted successfully" });
|
|
4432
4662
|
} catch (error) {
|
|
4433
4663
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4434
4664
|
if (errorMessage.includes("not found")) {
|
|
4435
|
-
|
|
4436
|
-
return c.json(response2, statusCode2);
|
|
4665
|
+
return errorResponse(c, "THREAD_NOT_FOUND", errorMessage, 404);
|
|
4437
4666
|
}
|
|
4438
|
-
|
|
4439
|
-
return c.json(response, statusCode);
|
|
4667
|
+
return errorResponse(c, "DELETE_THREAD_ERROR", "Failed to delete thread", 500, errorMessage);
|
|
4440
4668
|
}
|
|
4441
4669
|
});
|
|
4442
4670
|
router.post("/:id/select", async (c) => {
|
|
4443
4671
|
try {
|
|
4444
4672
|
const threadId = c.req.param("id");
|
|
4445
4673
|
if (!threadId) {
|
|
4446
|
-
|
|
4447
|
-
return c.json(response2, statusCode2);
|
|
4674
|
+
return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
|
|
4448
4675
|
}
|
|
4449
4676
|
const thread = await threadManager.getThread(threadId);
|
|
4450
4677
|
if (!thread) {
|
|
4451
|
-
|
|
4452
|
-
return c.json(response2, statusCode2);
|
|
4678
|
+
return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
|
|
4453
4679
|
}
|
|
4454
4680
|
await threadManager.selectThread(threadId);
|
|
4455
|
-
|
|
4456
|
-
return c.json(response, statusCode);
|
|
4681
|
+
return successResponse(c, { success: true, message: "Thread selected successfully", threadId });
|
|
4457
4682
|
} catch (error) {
|
|
4458
|
-
|
|
4459
|
-
return c.json(response, statusCode);
|
|
4683
|
+
return errorResponse(c, "SELECT_THREAD_ERROR", "Failed to select thread", 500, error instanceof Error ? error.message : String(error));
|
|
4460
4684
|
}
|
|
4461
4685
|
});
|
|
4462
4686
|
router.get("/:id/messages", async (c) => {
|
|
4463
4687
|
try {
|
|
4464
4688
|
const threadId = c.req.param("id");
|
|
4465
4689
|
if (!threadId) {
|
|
4466
|
-
|
|
4467
|
-
return c.json(response, statusCode);
|
|
4690
|
+
return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
|
|
4468
4691
|
}
|
|
4469
4692
|
const thread = await threadManager.getThread(threadId);
|
|
4470
4693
|
if (!thread) {
|
|
4471
|
-
|
|
4472
|
-
return c.json(response, statusCode);
|
|
4694
|
+
return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
|
|
4473
4695
|
}
|
|
4474
4696
|
const history = await conversationManager.getConversationHistory(thread.path);
|
|
4475
4697
|
if (!history || history.messages.length === 0) {
|
|
@@ -4504,22 +4726,19 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
|
4504
4726
|
});
|
|
4505
4727
|
return c.json(messages);
|
|
4506
4728
|
} catch (error) {
|
|
4507
|
-
|
|
4508
|
-
return c.json(response, statusCode);
|
|
4729
|
+
return errorResponse(c, "GET_THREAD_MESSAGES_ERROR", "Failed to load thread messages", 500, error instanceof Error ? error.message : String(error));
|
|
4509
4730
|
}
|
|
4510
4731
|
});
|
|
4511
4732
|
router.get("/:id/files", async (c) => {
|
|
4512
4733
|
try {
|
|
4513
4734
|
const threadId = c.req.param("id");
|
|
4514
4735
|
if (!threadId) {
|
|
4515
|
-
|
|
4516
|
-
return c.json(response, statusCode);
|
|
4736
|
+
return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
|
|
4517
4737
|
}
|
|
4518
4738
|
const files = await threadManager.listFiles(threadId);
|
|
4519
4739
|
return c.json(files);
|
|
4520
4740
|
} catch (error) {
|
|
4521
|
-
|
|
4522
|
-
return c.json(response, statusCode);
|
|
4741
|
+
return errorResponse(c, "LIST_THREAD_FILES_ERROR", "Failed to list thread files", 500, error instanceof Error ? error.message : String(error));
|
|
4523
4742
|
}
|
|
4524
4743
|
});
|
|
4525
4744
|
router.patch("/:id", async (c) => {
|
|
@@ -4527,62 +4746,62 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
|
4527
4746
|
const threadId = c.req.param("id");
|
|
4528
4747
|
const body = await c.req.json();
|
|
4529
4748
|
if (!threadId) {
|
|
4530
|
-
|
|
4531
|
-
return c.json(response2, statusCode2);
|
|
4749
|
+
return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
|
|
4532
4750
|
}
|
|
4533
4751
|
await threadManager.updateThread(threadId, body);
|
|
4534
|
-
|
|
4535
|
-
return c.json(response, statusCode);
|
|
4752
|
+
return successResponse(c, { success: true, message: "Thread updated successfully" });
|
|
4536
4753
|
} catch (error) {
|
|
4537
4754
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4538
|
-
|
|
4539
|
-
return c.json(response, statusCode);
|
|
4755
|
+
return errorResponse(c, "UPDATE_THREAD_ERROR", "Failed to update thread", 500, errorMessage);
|
|
4540
4756
|
}
|
|
4541
4757
|
});
|
|
4758
|
+
const OPEN_APP_NAMES = {
|
|
4759
|
+
"VS Code": ["Visual Studio Code", "code"],
|
|
4760
|
+
Cursor: ["Cursor", "cursor"],
|
|
4761
|
+
Windsurf: ["Windsurf", "windsurf"],
|
|
4762
|
+
Xcode: "Xcode",
|
|
4763
|
+
"Android Studio": ["Android Studio", "studio"],
|
|
4764
|
+
Kiro: ["Kiro", "kiro"]
|
|
4765
|
+
};
|
|
4542
4766
|
router.post("/:id/open", async (c) => {
|
|
4543
4767
|
try {
|
|
4544
4768
|
const threadId = c.req.param("id");
|
|
4545
4769
|
const body = await c.req.json();
|
|
4546
4770
|
const { program } = body;
|
|
4547
4771
|
if (!threadId) {
|
|
4548
|
-
|
|
4549
|
-
return c.json(response2, statusCode2);
|
|
4772
|
+
return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
|
|
4550
4773
|
}
|
|
4551
4774
|
if (!program) {
|
|
4552
|
-
|
|
4553
|
-
return c.json(response2, statusCode2);
|
|
4775
|
+
return errorResponse(c, "INVALID_REQUEST", "Program is required", 400);
|
|
4554
4776
|
}
|
|
4555
4777
|
const { AVAILABLE_PROGRAMS: AVAILABLE_PROGRAMS2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
4556
4778
|
if (!AVAILABLE_PROGRAMS2.includes(program)) {
|
|
4557
|
-
|
|
4558
|
-
return c.json(response2, statusCode2);
|
|
4779
|
+
return errorResponse(c, "INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS2.join(", ")}`, 400);
|
|
4559
4780
|
}
|
|
4560
4781
|
const thread = await threadManager.getThread(threadId);
|
|
4561
4782
|
if (!thread) {
|
|
4562
|
-
|
|
4563
|
-
return c.json(response2, statusCode2);
|
|
4783
|
+
return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
|
|
4564
4784
|
}
|
|
4565
|
-
const { spawn: spawn7 } = await import("child_process");
|
|
4566
4785
|
const threadPath = thread.path;
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
}
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4786
|
+
const appNames = OPEN_APP_NAMES[program];
|
|
4787
|
+
if (!appNames) {
|
|
4788
|
+
return errorResponse(c, "OPEN_PROGRAM_ERROR", `Unsupported program: ${program}`, 500);
|
|
4789
|
+
}
|
|
4790
|
+
console.log("[POST /api/threads/:id/open] threadId=%s program=%s threadPath=%s", threadId, program, threadPath);
|
|
4791
|
+
try {
|
|
4792
|
+
await open2(threadPath, {
|
|
4793
|
+
app: { name: appNames }
|
|
4794
|
+
});
|
|
4795
|
+
console.log("[POST /api/threads/:id/open] Successfully opened thread in %s", program);
|
|
4796
|
+
} catch (openError) {
|
|
4797
|
+
const message = openError instanceof Error ? openError.message : String(openError);
|
|
4798
|
+
console.error("[POST /api/threads/:id/open] Failed to open thread:", message);
|
|
4799
|
+
return errorResponse(c, "OPEN_PROGRAM_ERROR", `Failed to open ${program}: ${message}`, 500);
|
|
4800
|
+
}
|
|
4801
|
+
return successResponse(c, { success: true, message: `Thread opened in ${program}` });
|
|
4582
4802
|
} catch (error) {
|
|
4583
4803
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4584
|
-
|
|
4585
|
-
return c.json(response, statusCode);
|
|
4804
|
+
return errorResponse(c, "OPEN_PROGRAM_ERROR", "Failed to open thread", 500, errorMessage);
|
|
4586
4805
|
}
|
|
4587
4806
|
});
|
|
4588
4807
|
return router;
|
|
@@ -4591,9 +4810,274 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
|
4591
4810
|
// src/routes/chat.ts
|
|
4592
4811
|
import { Hono as Hono3 } from "hono";
|
|
4593
4812
|
|
|
4813
|
+
// src/managers/skill-manager.ts
|
|
4814
|
+
import { readdir as readdir3, readFile as readFile3 } from "fs/promises";
|
|
4815
|
+
import { join as join11 } from "path";
|
|
4816
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4817
|
+
import { homedir as homedir3 } from "os";
|
|
4818
|
+
function parseFrontmatter(markdown) {
|
|
4819
|
+
const lines = markdown.split(`
|
|
4820
|
+
`);
|
|
4821
|
+
if (lines[0]?.trim() !== "---") {
|
|
4822
|
+
return {
|
|
4823
|
+
metadata: {},
|
|
4824
|
+
content: markdown.trim()
|
|
4825
|
+
};
|
|
4826
|
+
}
|
|
4827
|
+
let endIndex = -1;
|
|
4828
|
+
for (let i = 1;i < lines.length; i++) {
|
|
4829
|
+
if (lines[i]?.trim() === "---") {
|
|
4830
|
+
endIndex = i;
|
|
4831
|
+
break;
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
if (endIndex === -1) {
|
|
4835
|
+
return {
|
|
4836
|
+
metadata: {},
|
|
4837
|
+
content: markdown.trim()
|
|
4838
|
+
};
|
|
4839
|
+
}
|
|
4840
|
+
const frontmatterLines = lines.slice(1, endIndex);
|
|
4841
|
+
const metadata = {};
|
|
4842
|
+
for (const line of frontmatterLines) {
|
|
4843
|
+
const colonIndex = line.indexOf(":");
|
|
4844
|
+
if (colonIndex === -1)
|
|
4845
|
+
continue;
|
|
4846
|
+
const key = line.slice(0, colonIndex).trim();
|
|
4847
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
4848
|
+
if (key === "name") {
|
|
4849
|
+
metadata.name = value;
|
|
4850
|
+
} else if (key === "description") {
|
|
4851
|
+
metadata.description = value;
|
|
4852
|
+
} else if (key === "license") {
|
|
4853
|
+
metadata.license = value;
|
|
4854
|
+
} else if (key === "compatibility") {
|
|
4855
|
+
metadata.compatibility = value;
|
|
4856
|
+
} else if (key === "allowed-tools") {
|
|
4857
|
+
metadata.allowedTools = value;
|
|
4858
|
+
} else if (key === "metadata") {}
|
|
4859
|
+
}
|
|
4860
|
+
const content = lines.slice(endIndex + 1).join(`
|
|
4861
|
+
`).trim();
|
|
4862
|
+
return { metadata, content };
|
|
4863
|
+
}
|
|
4864
|
+
function getGlobalSkillsDir() {
|
|
4865
|
+
return join11(homedir3(), ".tarsk", "skills");
|
|
4866
|
+
}
|
|
4867
|
+
function getProjectSkillsDir(threadPath) {
|
|
4868
|
+
return join11(threadPath, ".tarsk", "skills");
|
|
4869
|
+
}
|
|
4870
|
+
function validateSkillName(name) {
|
|
4871
|
+
if (!name || name.length === 0 || name.length > 64) {
|
|
4872
|
+
return false;
|
|
4873
|
+
}
|
|
4874
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
4875
|
+
return false;
|
|
4876
|
+
}
|
|
4877
|
+
if (name.startsWith("-") || name.endsWith("-")) {
|
|
4878
|
+
return false;
|
|
4879
|
+
}
|
|
4880
|
+
if (name.includes("--")) {
|
|
4881
|
+
return false;
|
|
4882
|
+
}
|
|
4883
|
+
return true;
|
|
4884
|
+
}
|
|
4885
|
+
function validateDescription(description) {
|
|
4886
|
+
return description && description.length > 0 && description.length <= 1024;
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
class SkillManager {
|
|
4890
|
+
async loadSkills(threadPath) {
|
|
4891
|
+
const skills = new Map;
|
|
4892
|
+
const globalDir = getGlobalSkillsDir();
|
|
4893
|
+
if (existsSync8(globalDir)) {
|
|
4894
|
+
const globalSkills = await this.loadSkillsFromDir(globalDir, "global");
|
|
4895
|
+
for (const skill of globalSkills) {
|
|
4896
|
+
skills.set(skill.name, skill);
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
const projectDir = getProjectSkillsDir(threadPath);
|
|
4900
|
+
if (existsSync8(projectDir)) {
|
|
4901
|
+
const projectSkills = await this.loadSkillsFromDir(projectDir, "project");
|
|
4902
|
+
for (const skill of projectSkills) {
|
|
4903
|
+
skills.set(skill.name, skill);
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
return Array.from(skills.values());
|
|
4907
|
+
}
|
|
4908
|
+
async loadSkillsFromDir(dir, scope) {
|
|
4909
|
+
const skills = [];
|
|
4910
|
+
try {
|
|
4911
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
4912
|
+
for (const entry of entries) {
|
|
4913
|
+
if (!entry.isDirectory())
|
|
4914
|
+
continue;
|
|
4915
|
+
const skillDirName = entry.name;
|
|
4916
|
+
const skillPath = join11(dir, skillDirName);
|
|
4917
|
+
const skillFilePath = join11(skillPath, "SKILL.md");
|
|
4918
|
+
if (!existsSync8(skillFilePath)) {
|
|
4919
|
+
console.warn(`Skipping skill directory ${skillDirName}: SKILL.md not found`);
|
|
4920
|
+
continue;
|
|
4921
|
+
}
|
|
4922
|
+
try {
|
|
4923
|
+
const fileContent = await readFile3(skillFilePath, "utf-8");
|
|
4924
|
+
const { metadata, content } = parseFrontmatter(fileContent);
|
|
4925
|
+
if (!metadata.name) {
|
|
4926
|
+
console.warn(`Skipping skill in ${skillDirName}: missing 'name' in frontmatter`);
|
|
4927
|
+
continue;
|
|
4928
|
+
}
|
|
4929
|
+
if (!metadata.description) {
|
|
4930
|
+
console.warn(`Skipping skill in ${skillDirName}: missing 'description' in frontmatter`);
|
|
4931
|
+
continue;
|
|
4932
|
+
}
|
|
4933
|
+
if (!validateSkillName(metadata.name)) {
|
|
4934
|
+
console.warn(`Skipping skill in ${skillDirName}: invalid name format '${metadata.name}'. ` + `Name must be 1-64 chars, lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens.`);
|
|
4935
|
+
continue;
|
|
4936
|
+
}
|
|
4937
|
+
if (!validateDescription(metadata.description)) {
|
|
4938
|
+
console.warn(`Skipping skill in ${skillDirName}: description must be 1-1024 characters`);
|
|
4939
|
+
continue;
|
|
4940
|
+
}
|
|
4941
|
+
if (metadata.name !== skillDirName) {
|
|
4942
|
+
console.warn(`Skipping skill in ${skillDirName}: directory name must match frontmatter name '${metadata.name}'`);
|
|
4943
|
+
continue;
|
|
4944
|
+
}
|
|
4945
|
+
const skill = {
|
|
4946
|
+
name: metadata.name,
|
|
4947
|
+
description: metadata.description,
|
|
4948
|
+
scope,
|
|
4949
|
+
skillPath,
|
|
4950
|
+
instructions: content
|
|
4951
|
+
};
|
|
4952
|
+
if (metadata.license) {
|
|
4953
|
+
skill.license = metadata.license;
|
|
4954
|
+
}
|
|
4955
|
+
if (metadata.compatibility) {
|
|
4956
|
+
skill.compatibility = metadata.compatibility;
|
|
4957
|
+
}
|
|
4958
|
+
if (metadata.metadata) {
|
|
4959
|
+
skill.metadata = metadata.metadata;
|
|
4960
|
+
}
|
|
4961
|
+
if (metadata.allowedTools) {
|
|
4962
|
+
skill.allowedTools = metadata.allowedTools;
|
|
4963
|
+
}
|
|
4964
|
+
skills.push(skill);
|
|
4965
|
+
} catch (error) {
|
|
4966
|
+
console.error(`Failed to load skill from ${skillDirName}:`, error);
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
} catch (error) {
|
|
4970
|
+
console.error(`Failed to load skills from ${dir}:`, error);
|
|
4971
|
+
}
|
|
4972
|
+
return skills;
|
|
4973
|
+
}
|
|
4974
|
+
async getSkill(name, threadPath) {
|
|
4975
|
+
const skills = await this.loadSkills(threadPath);
|
|
4976
|
+
return skills.find((skill) => skill.name === name) || null;
|
|
4977
|
+
}
|
|
4978
|
+
validateSkillName(name) {
|
|
4979
|
+
return validateSkillName(name);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
// src/managers/skill-activation.ts
|
|
4984
|
+
var STOP_WORDS = new Set([
|
|
4985
|
+
"a",
|
|
4986
|
+
"an",
|
|
4987
|
+
"and",
|
|
4988
|
+
"are",
|
|
4989
|
+
"as",
|
|
4990
|
+
"at",
|
|
4991
|
+
"be",
|
|
4992
|
+
"by",
|
|
4993
|
+
"for",
|
|
4994
|
+
"from",
|
|
4995
|
+
"has",
|
|
4996
|
+
"he",
|
|
4997
|
+
"in",
|
|
4998
|
+
"is",
|
|
4999
|
+
"it",
|
|
5000
|
+
"its",
|
|
5001
|
+
"of",
|
|
5002
|
+
"on",
|
|
5003
|
+
"that",
|
|
5004
|
+
"the",
|
|
5005
|
+
"to",
|
|
5006
|
+
"was",
|
|
5007
|
+
"will",
|
|
5008
|
+
"with",
|
|
5009
|
+
"when",
|
|
5010
|
+
"where",
|
|
5011
|
+
"which",
|
|
5012
|
+
"who",
|
|
5013
|
+
"use",
|
|
5014
|
+
"used",
|
|
5015
|
+
"using",
|
|
5016
|
+
"this",
|
|
5017
|
+
"can",
|
|
5018
|
+
"do",
|
|
5019
|
+
"does",
|
|
5020
|
+
"how",
|
|
5021
|
+
"what",
|
|
5022
|
+
"i",
|
|
5023
|
+
"you",
|
|
5024
|
+
"we",
|
|
5025
|
+
"they",
|
|
5026
|
+
"them",
|
|
5027
|
+
"their",
|
|
5028
|
+
"my",
|
|
5029
|
+
"your",
|
|
5030
|
+
"our"
|
|
5031
|
+
]);
|
|
5032
|
+
function extractKeywords(text) {
|
|
5033
|
+
const keywords = new Set;
|
|
5034
|
+
const words = text.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length >= 3);
|
|
5035
|
+
for (const word of words) {
|
|
5036
|
+
if (!STOP_WORDS.has(word)) {
|
|
5037
|
+
keywords.add(word);
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
return keywords;
|
|
5041
|
+
}
|
|
5042
|
+
function calculateRelevanceScore(taskKeywords, skillDescription) {
|
|
5043
|
+
const skillKeywords = extractKeywords(skillDescription);
|
|
5044
|
+
if (skillKeywords.size === 0 || taskKeywords.size === 0) {
|
|
5045
|
+
return 0;
|
|
5046
|
+
}
|
|
5047
|
+
let matches = 0;
|
|
5048
|
+
for (const keyword of taskKeywords) {
|
|
5049
|
+
if (skillKeywords.has(keyword)) {
|
|
5050
|
+
matches++;
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
5053
|
+
return matches / taskKeywords.size;
|
|
5054
|
+
}
|
|
5055
|
+
function autoDiscoverSkills(allSkills, taskDescription, relevanceThreshold = 0.1, maxSkills = 5) {
|
|
5056
|
+
const taskKeywords = extractKeywords(taskDescription);
|
|
5057
|
+
const scoredSkills = allSkills.map((skill) => ({
|
|
5058
|
+
skill,
|
|
5059
|
+
score: calculateRelevanceScore(taskKeywords, skill.description)
|
|
5060
|
+
}));
|
|
5061
|
+
const relevantSkills = scoredSkills.filter(({ score }) => score >= relevanceThreshold).sort((a, b) => b.score - a.score).slice(0, maxSkills).map(({ skill }) => skill);
|
|
5062
|
+
return relevantSkills;
|
|
5063
|
+
}
|
|
5064
|
+
async function activateSkills(allSkills, taskDescription, thread, options) {
|
|
5065
|
+
const { relevanceThreshold = 0.1, maxSkills = 5 } = options || {};
|
|
5066
|
+
if (thread?.enabledSkills && thread.enabledSkills.length > 0) {
|
|
5067
|
+
const enabledSet = new Set(thread.enabledSkills);
|
|
5068
|
+
return allSkills.filter((skill) => enabledSet.has(skill.name));
|
|
5069
|
+
}
|
|
5070
|
+
const autoDiscovered = autoDiscoverSkills(allSkills, taskDescription, relevanceThreshold, maxSkills);
|
|
5071
|
+
if (thread?.disabledSkills && thread.disabledSkills.length > 0) {
|
|
5072
|
+
const disabledSet = new Set(thread.disabledSkills);
|
|
5073
|
+
return autoDiscovered.filter((skill) => !disabledSet.has(skill.name));
|
|
5074
|
+
}
|
|
5075
|
+
return autoDiscovered;
|
|
5076
|
+
}
|
|
5077
|
+
|
|
4594
5078
|
// src/utils.ts
|
|
4595
5079
|
function delay(ms) {
|
|
4596
|
-
return new Promise((
|
|
5080
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
4597
5081
|
}
|
|
4598
5082
|
|
|
4599
5083
|
// src/routes/chat.ts
|
|
@@ -4611,34 +5095,29 @@ function createChatRoutes(threadManager, agentExecutor, conversationManager, pro
|
|
|
4611
5095
|
}
|
|
4612
5096
|
}
|
|
4613
5097
|
if (!threadId || typeof threadId !== "string") {
|
|
4614
|
-
|
|
4615
|
-
return c.json(response, statusCode);
|
|
5098
|
+
return errorResponse(c, "INVALID_REQUEST", "threadId is required and must be a string", 400);
|
|
4616
5099
|
}
|
|
4617
5100
|
if (!content || typeof content !== "string") {
|
|
4618
|
-
|
|
4619
|
-
return c.json(response, statusCode);
|
|
5101
|
+
return errorResponse(c, "INVALID_REQUEST", "content is required and must be a string", 400);
|
|
4620
5102
|
}
|
|
4621
5103
|
if (!model || typeof model !== "string") {
|
|
4622
|
-
|
|
4623
|
-
return c.json(response, statusCode);
|
|
5104
|
+
return errorResponse(c, "INVALID_REQUEST", "model is required and must be a string", 400);
|
|
4624
5105
|
}
|
|
5106
|
+
console.log(`[chat] User message (thread: ${threadId}): ${content}`);
|
|
4625
5107
|
const thread = await threadManager.getThread(threadId);
|
|
4626
5108
|
if (!thread) {
|
|
4627
|
-
|
|
4628
|
-
return c.json(response, statusCode);
|
|
5109
|
+
return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
|
|
4629
5110
|
}
|
|
4630
5111
|
const threadPath = thread.path;
|
|
4631
5112
|
if (content.trim().toLowerCase() === "replay") {
|
|
4632
5113
|
console.log("[ChatRoute] Replay request detected");
|
|
4633
5114
|
const lastMessages = await conversationManager.getLastMessages(threadPath, 1);
|
|
4634
5115
|
if (lastMessages.length === 0) {
|
|
4635
|
-
|
|
4636
|
-
return c.json(response2, statusCode);
|
|
5116
|
+
return errorResponse(c, "NO_HISTORY", "No previous conversation found to replay", 404);
|
|
4637
5117
|
}
|
|
4638
5118
|
const lastMessage = lastMessages[0];
|
|
4639
5119
|
if (!lastMessage.response) {
|
|
4640
|
-
|
|
4641
|
-
return c.json(response2, statusCode);
|
|
5120
|
+
return errorResponse(c, "NO_RESPONSE", "Previous message has no completed response to replay", 404);
|
|
4642
5121
|
}
|
|
4643
5122
|
console.log("[ChatRoute] Replaying message:", {
|
|
4644
5123
|
messageId: lastMessage.id,
|
|
@@ -4669,13 +5148,20 @@ function createChatRoutes(threadManager, agentExecutor, conversationManager, pro
|
|
|
4669
5148
|
}
|
|
4670
5149
|
return streamAsyncGenerator(c, replayGenerator());
|
|
4671
5150
|
}
|
|
5151
|
+
const skillManager = new SkillManager;
|
|
5152
|
+
const allSkills = await skillManager.loadSkills(threadPath);
|
|
5153
|
+
const activatedSkills = await activateSkills(allSkills, content, thread);
|
|
5154
|
+
if (activatedSkills.length > 0) {
|
|
5155
|
+
console.log("[ChatRoute] Activated skills:", activatedSkills.map((s) => s.name).join(", "));
|
|
5156
|
+
}
|
|
4672
5157
|
const context = {
|
|
4673
5158
|
threadId,
|
|
4674
5159
|
threadPath,
|
|
4675
5160
|
model,
|
|
4676
5161
|
provider,
|
|
4677
5162
|
attachments,
|
|
4678
|
-
planMode
|
|
5163
|
+
planMode,
|
|
5164
|
+
skills: activatedSkills
|
|
4679
5165
|
};
|
|
4680
5166
|
console.log("[ChatRoute] Execution context:", {
|
|
4681
5167
|
threadId,
|
|
@@ -4701,6 +5187,7 @@ User: ${content}` : content;
|
|
|
4701
5187
|
capturedEvents.push(event);
|
|
4702
5188
|
if (event.type === "message" && event.content) {
|
|
4703
5189
|
fullContent += event.content;
|
|
5190
|
+
console.log(`[chat] Assistant (role: ${event.role ?? "assistant"}): ${event.content}`);
|
|
4704
5191
|
}
|
|
4705
5192
|
if (event.type === "message" && typeof event.content === "string" && (event.role === "tool" || isToolLikeContent(event.content))) {
|
|
4706
5193
|
yield { type: "thinking", content: event.content };
|
|
@@ -4738,30 +5225,29 @@ User: ${content}` : content;
|
|
|
4738
5225
|
processingStateManager.clearProcessing(threadId);
|
|
4739
5226
|
}
|
|
4740
5227
|
}
|
|
4741
|
-
|
|
5228
|
+
async function* withCompleteSignal() {
|
|
5229
|
+
yield* chatExecutionGenerator();
|
|
5230
|
+
yield { type: "complete" };
|
|
5231
|
+
}
|
|
5232
|
+
return streamAsyncGenerator(c, withCompleteSignal());
|
|
4742
5233
|
} catch (error) {
|
|
4743
|
-
|
|
4744
|
-
return c.json(response, statusCode);
|
|
5234
|
+
return errorResponse(c, "REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
|
|
4745
5235
|
}
|
|
4746
5236
|
});
|
|
4747
5237
|
router.delete("/:threadId", async (c) => {
|
|
4748
5238
|
try {
|
|
4749
5239
|
const threadId = c.req.param("threadId");
|
|
4750
5240
|
if (!threadId || typeof threadId !== "string") {
|
|
4751
|
-
|
|
4752
|
-
return c.json(response2, statusCode2);
|
|
5241
|
+
return errorResponse(c, "INVALID_REQUEST", "threadId is required and must be a string", 400);
|
|
4753
5242
|
}
|
|
4754
5243
|
const thread = await threadManager.getThread(threadId);
|
|
4755
5244
|
if (!thread) {
|
|
4756
|
-
|
|
4757
|
-
return c.json(response2, statusCode2);
|
|
5245
|
+
return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
|
|
4758
5246
|
}
|
|
4759
5247
|
await conversationManager.clearChatHistory(thread.path);
|
|
4760
|
-
|
|
4761
|
-
return c.json(response, statusCode);
|
|
5248
|
+
return successResponse(c, { message: "Chat history cleared" }, 200);
|
|
4762
5249
|
} catch (error) {
|
|
4763
|
-
|
|
4764
|
-
return c.json(response, statusCode);
|
|
5250
|
+
return errorResponse(c, "CLEAR_CHAT_ERROR", "Failed to clear chat history", 500, error instanceof Error ? error.message : String(error));
|
|
4765
5251
|
}
|
|
4766
5252
|
});
|
|
4767
5253
|
return router;
|
|
@@ -4934,9 +5420,9 @@ async function getAIHubMixCredits(apiKey) {
|
|
|
4934
5420
|
|
|
4935
5421
|
// src/utils/env-manager.ts
|
|
4936
5422
|
import { promises as fs3 } from "fs";
|
|
4937
|
-
import { join as
|
|
5423
|
+
import { join as join12 } from "path";
|
|
4938
5424
|
async function updateEnvFile(keyNames) {
|
|
4939
|
-
const envPath =
|
|
5425
|
+
const envPath = join12(process.cwd(), ".env");
|
|
4940
5426
|
let content = "";
|
|
4941
5427
|
try {
|
|
4942
5428
|
content = await fs3.readFile(envPath, "utf-8");
|
|
@@ -4967,7 +5453,7 @@ async function updateEnvFile(keyNames) {
|
|
|
4967
5453
|
`, "utf-8");
|
|
4968
5454
|
}
|
|
4969
5455
|
async function readEnvFile() {
|
|
4970
|
-
const envPath =
|
|
5456
|
+
const envPath = join12(process.cwd(), ".env");
|
|
4971
5457
|
const envMap = {};
|
|
4972
5458
|
try {
|
|
4973
5459
|
const content = await fs3.readFile(envPath, "utf-8");
|
|
@@ -5083,32 +5569,6 @@ import { Hono as Hono5 } from "hono";
|
|
|
5083
5569
|
|
|
5084
5570
|
// src/provider-data.ts
|
|
5085
5571
|
var PROVIDER_DATA = [
|
|
5086
|
-
{
|
|
5087
|
-
id: "aihubmix",
|
|
5088
|
-
models: [
|
|
5089
|
-
"DeepSeek-R1",
|
|
5090
|
-
"DeepSeek-V3",
|
|
5091
|
-
"claude-3-5-sonnet-20241022",
|
|
5092
|
-
"claude-3-7-sonnet-20250219",
|
|
5093
|
-
"claude-opus-4-1",
|
|
5094
|
-
"claude-opus-4-20250514",
|
|
5095
|
-
"claude-sonnet-4-20250514",
|
|
5096
|
-
"claude-sonnet-4-5",
|
|
5097
|
-
"gemini-2.5-flash",
|
|
5098
|
-
"gemini-2.5-flash-lite",
|
|
5099
|
-
"gemini-2.5-pro",
|
|
5100
|
-
"glm-4.6",
|
|
5101
|
-
"gpt-4",
|
|
5102
|
-
"gpt-4.1",
|
|
5103
|
-
"gpt-4o",
|
|
5104
|
-
"gpt-5",
|
|
5105
|
-
"gpt-5-mini",
|
|
5106
|
-
"kimi-k2-thinking",
|
|
5107
|
-
"kimi-k2-turbo-preview",
|
|
5108
|
-
"o3-mini",
|
|
5109
|
-
"o4-mini"
|
|
5110
|
-
]
|
|
5111
|
-
},
|
|
5112
5572
|
{
|
|
5113
5573
|
id: "anthropic",
|
|
5114
5574
|
models: [
|
|
@@ -5585,6 +6045,163 @@ async function getModelInfoForProvider(provider, modelIds, apiKey) {
|
|
|
5585
6045
|
return result;
|
|
5586
6046
|
}
|
|
5587
6047
|
|
|
6048
|
+
// src/generated-data.ts
|
|
6049
|
+
var GENERATED_DATA = [
|
|
6050
|
+
{
|
|
6051
|
+
id: "aihubmix",
|
|
6052
|
+
models: [
|
|
6053
|
+
"BAAI/bge-large-en-v1.5",
|
|
6054
|
+
"BAAI/bge-large-zh-v1.5",
|
|
6055
|
+
"DeepSeek-R1",
|
|
6056
|
+
"DeepSeek-V3",
|
|
6057
|
+
"DeepSeek-V3-Fast",
|
|
6058
|
+
"DeepSeek-V3.1-Fast",
|
|
6059
|
+
"DeepSeek-V3.1-Terminus",
|
|
6060
|
+
"DeepSeek-V3.1-Think",
|
|
6061
|
+
"DeepSeek-V3.2-Exp",
|
|
6062
|
+
"DeepSeek-V3.2-Exp-Think",
|
|
6063
|
+
"ERNIE-X1.1-Preview",
|
|
6064
|
+
"Kimi-K2-0905",
|
|
6065
|
+
"Qwen/QwQ-32B",
|
|
6066
|
+
"Qwen/Qwen2.5-VL-32B-Instruct",
|
|
6067
|
+
"baidu/ERNIE-4.5-300B-A47B",
|
|
6068
|
+
"bge-large-en",
|
|
6069
|
+
"bge-large-zh",
|
|
6070
|
+
"cc-MiniMax-M2",
|
|
6071
|
+
"cc-deepseek-v3.1",
|
|
6072
|
+
"cc-ernie-4.5-300b-a47b",
|
|
6073
|
+
"cc-kimi-k2-instruct",
|
|
6074
|
+
"cc-kimi-k2-instruct-0905",
|
|
6075
|
+
"cc-minimax-m2",
|
|
6076
|
+
"cc-minimax-m2.1",
|
|
6077
|
+
"cc-minimax-m2.5",
|
|
6078
|
+
"claude-3-5-haiku",
|
|
6079
|
+
"claude-3-5-sonnet",
|
|
6080
|
+
"claude-3-7-sonnet",
|
|
6081
|
+
"claude-haiku-4-5",
|
|
6082
|
+
"claude-opus-4-0",
|
|
6083
|
+
"claude-opus-4-1",
|
|
6084
|
+
"claude-opus-4-5",
|
|
6085
|
+
"claude-opus-4-5-think",
|
|
6086
|
+
"claude-opus-4-6",
|
|
6087
|
+
"claude-opus-4-6-think",
|
|
6088
|
+
"claude-sonnet-4-0",
|
|
6089
|
+
"claude-sonnet-4-5",
|
|
6090
|
+
"claude-sonnet-4-5-think",
|
|
6091
|
+
"claude-sonnet-4-6",
|
|
6092
|
+
"claude-sonnet-4-6-think",
|
|
6093
|
+
"coding-glm-4.6",
|
|
6094
|
+
"coding-glm-4.6-free",
|
|
6095
|
+
"coding-glm-4.7",
|
|
6096
|
+
"coding-glm-4.7-free",
|
|
6097
|
+
"coding-glm-5",
|
|
6098
|
+
"coding-glm-5-free",
|
|
6099
|
+
"coding-minimax-m2",
|
|
6100
|
+
"coding-minimax-m2-free",
|
|
6101
|
+
"coding-minimax-m2.1",
|
|
6102
|
+
"coding-minimax-m2.5",
|
|
6103
|
+
"deepseek-v3.2",
|
|
6104
|
+
"deepseek-v3.2-think",
|
|
6105
|
+
"doubao-seed-1-8",
|
|
6106
|
+
"doubao-seed-2-0-code-preview",
|
|
6107
|
+
"doubao-seed-2-0-lite",
|
|
6108
|
+
"doubao-seed-2-0-mini",
|
|
6109
|
+
"doubao-seed-2-0-pro",
|
|
6110
|
+
"ernie-4.5",
|
|
6111
|
+
"ernie-4.5-0.3b",
|
|
6112
|
+
"ernie-4.5-turbo-128k-preview",
|
|
6113
|
+
"ernie-4.5-turbo-latest",
|
|
6114
|
+
"ernie-4.5-turbo-vl",
|
|
6115
|
+
"ernie-irag-edit",
|
|
6116
|
+
"ernie-x1-turbo",
|
|
6117
|
+
"gemini-2.0-flash",
|
|
6118
|
+
"gemini-2.0-flash-exp-search",
|
|
6119
|
+
"gemini-2.0-flash-free",
|
|
6120
|
+
"gemini-2.5-flash",
|
|
6121
|
+
"gemini-2.5-flash-lite",
|
|
6122
|
+
"gemini-2.5-flash-lite-preview-09-2025",
|
|
6123
|
+
"gemini-2.5-flash-nothink",
|
|
6124
|
+
"gemini-2.5-flash-preview-05-20-nothink",
|
|
6125
|
+
"gemini-2.5-flash-preview-05-20-search",
|
|
6126
|
+
"gemini-2.5-flash-preview-09-2025",
|
|
6127
|
+
"gemini-2.5-flash-search",
|
|
6128
|
+
"gemini-2.5-pro",
|
|
6129
|
+
"gemini-2.5-pro-preview-03-25",
|
|
6130
|
+
"gemini-2.5-pro-preview-03-25-search",
|
|
6131
|
+
"gemini-2.5-pro-preview-06-05",
|
|
6132
|
+
"gemini-2.5-pro-preview-06-05-search",
|
|
6133
|
+
"gemini-2.5-pro-search",
|
|
6134
|
+
"gemini-3-flash-preview",
|
|
6135
|
+
"gemini-3-flash-preview-free",
|
|
6136
|
+
"gemini-3-flash-preview-search",
|
|
6137
|
+
"gemini-3-pro-preview",
|
|
6138
|
+
"gemini-3-pro-preview-search",
|
|
6139
|
+
"gemini-3.1-pro-preview",
|
|
6140
|
+
"gemini-3.1-pro-preview-customtools",
|
|
6141
|
+
"gemini-3.1-pro-preview-search",
|
|
6142
|
+
"glm-4.6",
|
|
6143
|
+
"glm-4.7",
|
|
6144
|
+
"glm-4.7-flash-free",
|
|
6145
|
+
"gpt-4.1",
|
|
6146
|
+
"gpt-4.1-free",
|
|
6147
|
+
"gpt-4.1-mini",
|
|
6148
|
+
"gpt-4.1-mini-free",
|
|
6149
|
+
"gpt-4.1-nano",
|
|
6150
|
+
"gpt-4.1-nano-free",
|
|
6151
|
+
"gpt-4o",
|
|
6152
|
+
"gpt-4o-free",
|
|
6153
|
+
"gpt-5",
|
|
6154
|
+
"gpt-5-codex",
|
|
6155
|
+
"gpt-5-mini",
|
|
6156
|
+
"gpt-5-nano",
|
|
6157
|
+
"gpt-5-pro",
|
|
6158
|
+
"gpt-5.1",
|
|
6159
|
+
"gpt-5.2",
|
|
6160
|
+
"gpt-5.2-high",
|
|
6161
|
+
"gpt-5.2-low",
|
|
6162
|
+
"gpt-5.2-pro",
|
|
6163
|
+
"grok-4-1-fast-non-reasoning",
|
|
6164
|
+
"grok-4-1-fast-reasoning",
|
|
6165
|
+
"grok-code-fast-1",
|
|
6166
|
+
"inclusionAI/Ling-1T",
|
|
6167
|
+
"inclusionAI/Ling-flash-2.0",
|
|
6168
|
+
"inclusionAI/Ling-mini-2.0",
|
|
6169
|
+
"inclusionAI/Ring-1T",
|
|
6170
|
+
"inclusionAI/Ring-flash-2.0",
|
|
6171
|
+
"kimi-for-coding-free",
|
|
6172
|
+
"kimi-k2-0711",
|
|
6173
|
+
"kimi-k2-thinking",
|
|
6174
|
+
"kimi-k2-turbo-preview",
|
|
6175
|
+
"llama-4-maverick",
|
|
6176
|
+
"llama-4-scout",
|
|
6177
|
+
"o3",
|
|
6178
|
+
"o3-pro",
|
|
6179
|
+
"qwen3-235b-a22b",
|
|
6180
|
+
"qwen3-235b-a22b-instruct-2507",
|
|
6181
|
+
"qwen3-235b-a22b-thinking-2507",
|
|
6182
|
+
"qwen3-coder-30b-a3b-instruct",
|
|
6183
|
+
"qwen3-coder-480b-a35b-instruct",
|
|
6184
|
+
"qwen3-coder-flash",
|
|
6185
|
+
"qwen3-coder-next",
|
|
6186
|
+
"qwen3-coder-plus",
|
|
6187
|
+
"qwen3-coder-plus-2025-07-22",
|
|
6188
|
+
"qwen3-max",
|
|
6189
|
+
"qwen3-max-preview",
|
|
6190
|
+
"qwen3-next-80b-a3b-instruct",
|
|
6191
|
+
"qwen3-next-80b-a3b-thinking",
|
|
6192
|
+
"qwen3-vl-235b-a22b-instruct",
|
|
6193
|
+
"qwen3-vl-235b-a22b-thinking",
|
|
6194
|
+
"qwen3-vl-30b-a3b-instruct",
|
|
6195
|
+
"qwen3-vl-30b-a3b-thinking",
|
|
6196
|
+
"qwen3-vl-flash",
|
|
6197
|
+
"qwen3-vl-flash-2026-01-22",
|
|
6198
|
+
"qwen3-vl-plus",
|
|
6199
|
+
"qwen3.5-397b-a17b",
|
|
6200
|
+
"qwen3.5-plus"
|
|
6201
|
+
]
|
|
6202
|
+
}
|
|
6203
|
+
];
|
|
6204
|
+
|
|
5588
6205
|
// src/managers/model-manager.ts
|
|
5589
6206
|
class ModelManager {
|
|
5590
6207
|
metadataManager;
|
|
@@ -5593,7 +6210,10 @@ class ModelManager {
|
|
|
5593
6210
|
}
|
|
5594
6211
|
async getAvailableModels(provider) {
|
|
5595
6212
|
const providerId = provider.toLowerCase();
|
|
5596
|
-
|
|
6213
|
+
let providerData = PROVIDER_DATA.find((p) => p.id === providerId);
|
|
6214
|
+
if (!providerData) {
|
|
6215
|
+
providerData = GENERATED_DATA.find((p) => p.id === providerId);
|
|
6216
|
+
}
|
|
5597
6217
|
if (!providerData) {
|
|
5598
6218
|
throw new Error(`Provider "${provider}" is not supported`);
|
|
5599
6219
|
}
|
|
@@ -5753,10 +6373,10 @@ function createModelRoutes(metadataManager) {
|
|
|
5753
6373
|
|
|
5754
6374
|
// src/routes/git.ts
|
|
5755
6375
|
import { Hono as Hono6 } from "hono";
|
|
5756
|
-
import { spawn as
|
|
5757
|
-
import { existsSync as
|
|
5758
|
-
import { join as
|
|
5759
|
-
import { isAbsolute as isAbsolute3, normalize, resolve as
|
|
6376
|
+
import { spawn as spawn8 } from "child_process";
|
|
6377
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
6378
|
+
import { join as join13 } from "path";
|
|
6379
|
+
import { isAbsolute as isAbsolute3, normalize as normalize2, resolve as resolve3 } from "path";
|
|
5760
6380
|
import {
|
|
5761
6381
|
completeSimple,
|
|
5762
6382
|
getModel as getModel2
|
|
@@ -5881,12 +6501,12 @@ Generate only the commit message, nothing else:`;
|
|
|
5881
6501
|
}
|
|
5882
6502
|
}
|
|
5883
6503
|
function resolveThreadPath(repoPath) {
|
|
5884
|
-
const base = isAbsolute3(repoPath) ? repoPath :
|
|
5885
|
-
return
|
|
6504
|
+
const base = isAbsolute3(repoPath) ? repoPath : resolve3(getDataDir(), repoPath);
|
|
6505
|
+
return normalize2(base);
|
|
5886
6506
|
}
|
|
5887
6507
|
function getGitRoot(cwd) {
|
|
5888
6508
|
return new Promise((resolveRoot, reject) => {
|
|
5889
|
-
const proc =
|
|
6509
|
+
const proc = spawn8("git", ["rev-parse", "--show-toplevel"], { cwd });
|
|
5890
6510
|
let out = "";
|
|
5891
6511
|
let err = "";
|
|
5892
6512
|
proc.stdout.on("data", (d) => {
|
|
@@ -5909,8 +6529,8 @@ function createGitRoutes(metadataManager) {
|
|
|
5909
6529
|
const router = new Hono6;
|
|
5910
6530
|
router.get("/username", async (c) => {
|
|
5911
6531
|
try {
|
|
5912
|
-
const name = await new Promise((
|
|
5913
|
-
const proc =
|
|
6532
|
+
const name = await new Promise((resolve4, reject) => {
|
|
6533
|
+
const proc = spawn8("git", ["config", "user.name"]);
|
|
5914
6534
|
let out = "";
|
|
5915
6535
|
let err = "";
|
|
5916
6536
|
proc.stdout.on("data", (d) => {
|
|
@@ -5921,7 +6541,7 @@ function createGitRoutes(metadataManager) {
|
|
|
5921
6541
|
});
|
|
5922
6542
|
proc.on("close", (code) => {
|
|
5923
6543
|
if (code === 0) {
|
|
5924
|
-
|
|
6544
|
+
resolve4(out.trim());
|
|
5925
6545
|
} else {
|
|
5926
6546
|
reject(new Error(err.trim() || `git exited ${code}`));
|
|
5927
6547
|
}
|
|
@@ -5945,7 +6565,7 @@ function createGitRoutes(metadataManager) {
|
|
|
5945
6565
|
return c.json({ error: "Thread path not found" }, 404);
|
|
5946
6566
|
}
|
|
5947
6567
|
const absolutePath = resolveThreadPath(repoPath);
|
|
5948
|
-
if (!
|
|
6568
|
+
if (!existsSync9(absolutePath)) {
|
|
5949
6569
|
return c.json({
|
|
5950
6570
|
error: `Thread repo path does not exist: ${absolutePath}. Check that the project folder is present.`
|
|
5951
6571
|
}, 400);
|
|
@@ -5959,8 +6579,8 @@ function createGitRoutes(metadataManager) {
|
|
|
5959
6579
|
error: `Path is not a git repository: ${absolutePath}. ${msg}`
|
|
5960
6580
|
}, 400);
|
|
5961
6581
|
}
|
|
5962
|
-
const { hasChanges, changedFilesCount } = await new Promise((
|
|
5963
|
-
const proc =
|
|
6582
|
+
const { hasChanges, changedFilesCount } = await new Promise((resolve4) => {
|
|
6583
|
+
const proc = spawn8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: gitRoot });
|
|
5964
6584
|
let out = "";
|
|
5965
6585
|
proc.stdout.on("data", (d) => {
|
|
5966
6586
|
out += d.toString();
|
|
@@ -5968,34 +6588,34 @@ function createGitRoutes(metadataManager) {
|
|
|
5968
6588
|
proc.on("close", () => {
|
|
5969
6589
|
const lines = out.trim().split(`
|
|
5970
6590
|
`).filter((line) => line.length > 0);
|
|
5971
|
-
|
|
6591
|
+
resolve4({
|
|
5972
6592
|
hasChanges: lines.length > 0,
|
|
5973
6593
|
changedFilesCount: lines.length
|
|
5974
6594
|
});
|
|
5975
6595
|
});
|
|
5976
|
-
proc.on("error", () =>
|
|
6596
|
+
proc.on("error", () => resolve4({ hasChanges: false, changedFilesCount: 0 }));
|
|
5977
6597
|
});
|
|
5978
|
-
const currentBranch = await new Promise((
|
|
5979
|
-
const proc =
|
|
6598
|
+
const currentBranch = await new Promise((resolve4) => {
|
|
6599
|
+
const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
|
|
5980
6600
|
let out = "";
|
|
5981
6601
|
proc.stdout.on("data", (d) => {
|
|
5982
6602
|
out += d.toString();
|
|
5983
6603
|
});
|
|
5984
6604
|
proc.on("close", () => {
|
|
5985
|
-
|
|
6605
|
+
resolve4(out.trim());
|
|
5986
6606
|
});
|
|
5987
|
-
proc.on("error", () =>
|
|
6607
|
+
proc.on("error", () => resolve4(""));
|
|
5988
6608
|
});
|
|
5989
|
-
const hasUnpushedCommits = await new Promise((
|
|
5990
|
-
const proc =
|
|
6609
|
+
const hasUnpushedCommits = await new Promise((resolve4) => {
|
|
6610
|
+
const proc = spawn8("git", ["log", `origin/${currentBranch}..HEAD`, "--oneline"], { cwd: gitRoot });
|
|
5991
6611
|
let out = "";
|
|
5992
6612
|
proc.stdout.on("data", (d) => {
|
|
5993
6613
|
out += d.toString();
|
|
5994
6614
|
});
|
|
5995
6615
|
proc.on("close", () => {
|
|
5996
|
-
|
|
6616
|
+
resolve4(out.trim().length > 0);
|
|
5997
6617
|
});
|
|
5998
|
-
proc.on("error", () =>
|
|
6618
|
+
proc.on("error", () => resolve4(false));
|
|
5999
6619
|
});
|
|
6000
6620
|
return c.json({
|
|
6001
6621
|
hasChanges,
|
|
@@ -6025,14 +6645,14 @@ function createGitRoutes(metadataManager) {
|
|
|
6025
6645
|
} catch {
|
|
6026
6646
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
6027
6647
|
}
|
|
6028
|
-
const statusOutput = await new Promise((
|
|
6029
|
-
const proc =
|
|
6648
|
+
const statusOutput = await new Promise((resolve4) => {
|
|
6649
|
+
const proc = spawn8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: gitRoot });
|
|
6030
6650
|
let out = "";
|
|
6031
6651
|
proc.stdout.on("data", (d) => {
|
|
6032
6652
|
out += d.toString();
|
|
6033
6653
|
});
|
|
6034
|
-
proc.on("close", () =>
|
|
6035
|
-
proc.on("error", () =>
|
|
6654
|
+
proc.on("close", () => resolve4(out));
|
|
6655
|
+
proc.on("error", () => resolve4(""));
|
|
6036
6656
|
});
|
|
6037
6657
|
const changedFiles = [];
|
|
6038
6658
|
const statusLines = statusOutput.trim().split(`
|
|
@@ -6066,51 +6686,51 @@ function createGitRoutes(metadataManager) {
|
|
|
6066
6686
|
let newContent = "";
|
|
6067
6687
|
const hunks = [];
|
|
6068
6688
|
if (file.status === "deleted") {
|
|
6069
|
-
const content = await new Promise((
|
|
6070
|
-
const proc =
|
|
6689
|
+
const content = await new Promise((resolve4) => {
|
|
6690
|
+
const proc = spawn8("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
|
|
6071
6691
|
let out = "";
|
|
6072
6692
|
proc.stdout.on("data", (d) => {
|
|
6073
6693
|
out += d.toString();
|
|
6074
6694
|
});
|
|
6075
|
-
proc.on("close", () =>
|
|
6076
|
-
proc.on("error", () =>
|
|
6695
|
+
proc.on("close", () => resolve4(out));
|
|
6696
|
+
proc.on("error", () => resolve4(""));
|
|
6077
6697
|
});
|
|
6078
6698
|
oldContent = content;
|
|
6079
6699
|
} else if (file.status === "added" && file.path.startsWith("(new)")) {
|
|
6080
6700
|
const fs4 = await import("fs");
|
|
6081
6701
|
try {
|
|
6082
|
-
newContent = fs4.readFileSync(
|
|
6702
|
+
newContent = fs4.readFileSync(resolve3(gitRoot, file.path.replace("(new) ", "")), "utf-8");
|
|
6083
6703
|
} catch {
|
|
6084
6704
|
newContent = "";
|
|
6085
6705
|
}
|
|
6086
6706
|
} else {
|
|
6087
|
-
const diffOutput = await new Promise((
|
|
6088
|
-
const proc =
|
|
6707
|
+
const diffOutput = await new Promise((resolve4) => {
|
|
6708
|
+
const proc = spawn8("git", file.status === "added" ? ["diff", "--cached", "--", file.path] : ["diff", "HEAD", "--", file.path], { cwd: gitRoot });
|
|
6089
6709
|
let out = "";
|
|
6090
6710
|
proc.stdout.on("data", (d) => {
|
|
6091
6711
|
out += d.toString();
|
|
6092
6712
|
});
|
|
6093
|
-
proc.on("close", () =>
|
|
6094
|
-
proc.on("error", () =>
|
|
6713
|
+
proc.on("close", () => resolve4(out));
|
|
6714
|
+
proc.on("error", () => resolve4(""));
|
|
6095
6715
|
});
|
|
6096
6716
|
const lines = diffOutput.split(`
|
|
6097
6717
|
`);
|
|
6098
6718
|
let currentHunk = null;
|
|
6099
6719
|
let oldLineNum = 0;
|
|
6100
6720
|
let newLineNum = 0;
|
|
6101
|
-
const oldContentOutput = await new Promise((
|
|
6102
|
-
const proc =
|
|
6721
|
+
const oldContentOutput = await new Promise((resolve4) => {
|
|
6722
|
+
const proc = spawn8("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
|
|
6103
6723
|
let out = "";
|
|
6104
6724
|
proc.stdout.on("data", (d) => {
|
|
6105
6725
|
out += d.toString();
|
|
6106
6726
|
});
|
|
6107
|
-
proc.on("close", () =>
|
|
6108
|
-
proc.on("error", () =>
|
|
6727
|
+
proc.on("close", () => resolve4(out));
|
|
6728
|
+
proc.on("error", () => resolve4(""));
|
|
6109
6729
|
});
|
|
6110
6730
|
oldContent = oldContentOutput;
|
|
6111
6731
|
const fs4 = await import("fs");
|
|
6112
6732
|
try {
|
|
6113
|
-
newContent = fs4.readFileSync(
|
|
6733
|
+
newContent = fs4.readFileSync(resolve3(gitRoot, file.path), "utf-8");
|
|
6114
6734
|
} catch {
|
|
6115
6735
|
newContent = "";
|
|
6116
6736
|
}
|
|
@@ -6193,7 +6813,7 @@ function createGitRoutes(metadataManager) {
|
|
|
6193
6813
|
const absolutePath = resolveThreadPath(repoPath);
|
|
6194
6814
|
process.stdout.write(`[generate-commit-message] resolved path: ${absolutePath}
|
|
6195
6815
|
`);
|
|
6196
|
-
if (!
|
|
6816
|
+
if (!existsSync9(absolutePath)) {
|
|
6197
6817
|
process.stdout.write(`[generate-commit-message] path does not exist: ${absolutePath}
|
|
6198
6818
|
`);
|
|
6199
6819
|
return c.json({
|
|
@@ -6217,7 +6837,7 @@ function createGitRoutes(metadataManager) {
|
|
|
6217
6837
|
const runUnstagedDiff = () => {
|
|
6218
6838
|
process.stdout.write(`[generate-commit-message] using unstaged diff (git diff)
|
|
6219
6839
|
`);
|
|
6220
|
-
const proc2 =
|
|
6840
|
+
const proc2 = spawn8("git", ["diff"], { cwd: gitRoot });
|
|
6221
6841
|
let out2 = "";
|
|
6222
6842
|
proc2.stdout.on("data", (d) => {
|
|
6223
6843
|
out2 += d.toString();
|
|
@@ -6227,7 +6847,7 @@ function createGitRoutes(metadataManager) {
|
|
|
6227
6847
|
});
|
|
6228
6848
|
proc2.on("error", reject);
|
|
6229
6849
|
};
|
|
6230
|
-
const proc =
|
|
6850
|
+
const proc = spawn8("git", ["diff", "--cached"], { cwd: gitRoot });
|
|
6231
6851
|
let out = "";
|
|
6232
6852
|
let err = "";
|
|
6233
6853
|
proc.stdout.on("data", (d) => {
|
|
@@ -6262,7 +6882,7 @@ function createGitRoutes(metadataManager) {
|
|
|
6262
6882
|
});
|
|
6263
6883
|
if (!diff.trim()) {
|
|
6264
6884
|
const statusOut = await new Promise((resolveStatus) => {
|
|
6265
|
-
const proc =
|
|
6885
|
+
const proc = spawn8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: gitRoot });
|
|
6266
6886
|
let out = "";
|
|
6267
6887
|
proc.stdout.on("data", (d) => {
|
|
6268
6888
|
out += d.toString();
|
|
@@ -6286,11 +6906,11 @@ function createGitRoutes(metadataManager) {
|
|
|
6286
6906
|
const parts = [];
|
|
6287
6907
|
const maxFileSize = 1e5;
|
|
6288
6908
|
for (const relPath of untrackedPaths) {
|
|
6289
|
-
const fullPath =
|
|
6290
|
-
if (!
|
|
6909
|
+
const fullPath = join13(gitRoot, relPath);
|
|
6910
|
+
if (!existsSync9(fullPath))
|
|
6291
6911
|
continue;
|
|
6292
6912
|
try {
|
|
6293
|
-
if (
|
|
6913
|
+
if (statSync4(fullPath).isDirectory())
|
|
6294
6914
|
continue;
|
|
6295
6915
|
const content = readFileSync4(fullPath, "utf-8");
|
|
6296
6916
|
const safeContent = content.length > maxFileSize ? content.slice(0, maxFileSize) + `
|
|
@@ -6353,21 +6973,21 @@ new file mode 100644
|
|
|
6353
6973
|
} catch {
|
|
6354
6974
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
6355
6975
|
}
|
|
6356
|
-
await new Promise((
|
|
6357
|
-
const proc =
|
|
6976
|
+
await new Promise((resolve4, reject) => {
|
|
6977
|
+
const proc = spawn8("git", ["add", "-A"], { cwd: gitRoot });
|
|
6358
6978
|
proc.on("close", (code) => {
|
|
6359
6979
|
if (code === 0)
|
|
6360
|
-
|
|
6980
|
+
resolve4();
|
|
6361
6981
|
else
|
|
6362
6982
|
reject(new Error("Failed to stage changes"));
|
|
6363
6983
|
});
|
|
6364
6984
|
proc.on("error", reject);
|
|
6365
6985
|
});
|
|
6366
|
-
await new Promise((
|
|
6367
|
-
const proc =
|
|
6986
|
+
await new Promise((resolve4, reject) => {
|
|
6987
|
+
const proc = spawn8("git", ["commit", "-m", message], { cwd: gitRoot });
|
|
6368
6988
|
proc.on("close", (code) => {
|
|
6369
6989
|
if (code === 0)
|
|
6370
|
-
|
|
6990
|
+
resolve4();
|
|
6371
6991
|
else
|
|
6372
6992
|
reject(new Error("Failed to commit changes"));
|
|
6373
6993
|
});
|
|
@@ -6397,24 +7017,24 @@ new file mode 100644
|
|
|
6397
7017
|
} catch {
|
|
6398
7018
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
6399
7019
|
}
|
|
6400
|
-
const currentBranch = await new Promise((
|
|
6401
|
-
const proc =
|
|
7020
|
+
const currentBranch = await new Promise((resolve4, reject) => {
|
|
7021
|
+
const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
|
|
6402
7022
|
let out = "";
|
|
6403
7023
|
proc.stdout.on("data", (d) => {
|
|
6404
7024
|
out += d.toString();
|
|
6405
7025
|
});
|
|
6406
|
-
proc.on("close", () =>
|
|
7026
|
+
proc.on("close", () => resolve4(out.trim()));
|
|
6407
7027
|
proc.on("error", reject);
|
|
6408
7028
|
});
|
|
6409
|
-
await new Promise((
|
|
6410
|
-
const proc =
|
|
7029
|
+
await new Promise((resolve4, reject) => {
|
|
7030
|
+
const proc = spawn8("git", ["push", "-u", "origin", currentBranch], { cwd: gitRoot });
|
|
6411
7031
|
let err = "";
|
|
6412
7032
|
proc.stderr.on("data", (d) => {
|
|
6413
7033
|
err += d.toString();
|
|
6414
7034
|
});
|
|
6415
7035
|
proc.on("close", (code) => {
|
|
6416
7036
|
if (code === 0)
|
|
6417
|
-
|
|
7037
|
+
resolve4();
|
|
6418
7038
|
else
|
|
6419
7039
|
reject(new Error(err || "Failed to push changes"));
|
|
6420
7040
|
});
|
|
@@ -6444,15 +7064,15 @@ new file mode 100644
|
|
|
6444
7064
|
} catch {
|
|
6445
7065
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
6446
7066
|
}
|
|
6447
|
-
await new Promise((
|
|
6448
|
-
const proc =
|
|
7067
|
+
await new Promise((resolve4, reject) => {
|
|
7068
|
+
const proc = spawn8("git", ["fetch", "origin"], { cwd: gitRoot });
|
|
6449
7069
|
let err = "";
|
|
6450
7070
|
proc.stderr.on("data", (d) => {
|
|
6451
7071
|
err += d.toString();
|
|
6452
7072
|
});
|
|
6453
7073
|
proc.on("close", (code) => {
|
|
6454
7074
|
if (code === 0)
|
|
6455
|
-
|
|
7075
|
+
resolve4();
|
|
6456
7076
|
else
|
|
6457
7077
|
reject(new Error(err || "Failed to fetch from origin"));
|
|
6458
7078
|
});
|
|
@@ -6486,7 +7106,7 @@ new file mode 100644
|
|
|
6486
7106
|
}
|
|
6487
7107
|
const diff = await new Promise((resolveDiff, reject) => {
|
|
6488
7108
|
const runPlainDiff = () => {
|
|
6489
|
-
const proc2 =
|
|
7109
|
+
const proc2 = spawn8("git", ["diff"], { cwd: gitRoot });
|
|
6490
7110
|
let out2 = "";
|
|
6491
7111
|
proc2.stdout.on("data", (d) => {
|
|
6492
7112
|
out2 += d.toString();
|
|
@@ -6494,7 +7114,7 @@ new file mode 100644
|
|
|
6494
7114
|
proc2.on("close", () => resolveDiff(out2));
|
|
6495
7115
|
proc2.on("error", reject);
|
|
6496
7116
|
};
|
|
6497
|
-
const proc =
|
|
7117
|
+
const proc = spawn8("git", ["diff", "HEAD"], { cwd: gitRoot });
|
|
6498
7118
|
let out = "";
|
|
6499
7119
|
let err = "";
|
|
6500
7120
|
proc.stdout.on("data", (d) => {
|
|
@@ -6578,8 +7198,8 @@ DESCRIPTION: <description here>`;
|
|
|
6578
7198
|
} catch {
|
|
6579
7199
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
6580
7200
|
}
|
|
6581
|
-
const commits = await new Promise((
|
|
6582
|
-
const proc =
|
|
7201
|
+
const commits = await new Promise((resolve4, reject) => {
|
|
7202
|
+
const proc = spawn8("git", ["log", "--oneline", "-n", limit.toString(), "--format=%H|%s|%an|%ai"], { cwd: gitRoot });
|
|
6583
7203
|
let out = "";
|
|
6584
7204
|
let err = "";
|
|
6585
7205
|
proc.stdout.on("data", (d) => {
|
|
@@ -6601,7 +7221,7 @@ DESCRIPTION: <description here>`;
|
|
|
6601
7221
|
date: parts[3] || ""
|
|
6602
7222
|
};
|
|
6603
7223
|
});
|
|
6604
|
-
|
|
7224
|
+
resolve4(parsed);
|
|
6605
7225
|
} else {
|
|
6606
7226
|
reject(new Error(err || "Failed to get git log"));
|
|
6607
7227
|
}
|
|
@@ -6634,18 +7254,18 @@ DESCRIPTION: <description here>`;
|
|
|
6634
7254
|
} catch {
|
|
6635
7255
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
6636
7256
|
}
|
|
6637
|
-
const currentBranch = await new Promise((
|
|
6638
|
-
const proc =
|
|
7257
|
+
const currentBranch = await new Promise((resolve4, reject) => {
|
|
7258
|
+
const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
|
|
6639
7259
|
let out = "";
|
|
6640
7260
|
proc.stdout.on("data", (d) => {
|
|
6641
7261
|
out += d.toString();
|
|
6642
7262
|
});
|
|
6643
|
-
proc.on("close", () =>
|
|
7263
|
+
proc.on("close", () => resolve4(out.trim()));
|
|
6644
7264
|
proc.on("error", reject);
|
|
6645
7265
|
});
|
|
6646
|
-
const prUrl = await new Promise((
|
|
7266
|
+
const prUrl = await new Promise((resolve4, reject) => {
|
|
6647
7267
|
const args = ["pr", "create", "--title", title || currentBranch, "--body", description || ""];
|
|
6648
|
-
const proc =
|
|
7268
|
+
const proc = spawn8("gh", args, { cwd: gitRoot });
|
|
6649
7269
|
let out = "";
|
|
6650
7270
|
let err = "";
|
|
6651
7271
|
proc.stdout.on("data", (d) => {
|
|
@@ -6657,7 +7277,7 @@ DESCRIPTION: <description here>`;
|
|
|
6657
7277
|
proc.on("close", (code) => {
|
|
6658
7278
|
if (code === 0) {
|
|
6659
7279
|
const urlMatch = out.match(/https:\/\/[^\s]+/);
|
|
6660
|
-
|
|
7280
|
+
resolve4(urlMatch ? urlMatch[0] : out.trim());
|
|
6661
7281
|
} else {
|
|
6662
7282
|
reject(new Error(err || "Failed to create PR"));
|
|
6663
7283
|
}
|
|
@@ -6681,84 +7301,69 @@ function createRunRoutes(projectManager) {
|
|
|
6681
7301
|
try {
|
|
6682
7302
|
const projectId = c.req.param("id");
|
|
6683
7303
|
if (!projectId) {
|
|
6684
|
-
|
|
6685
|
-
return c.json(response2, statusCode2);
|
|
7304
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
6686
7305
|
}
|
|
6687
7306
|
const runCommand = await projectManager.suggestRunCommand(projectId);
|
|
6688
|
-
|
|
6689
|
-
return c.json(response, statusCode);
|
|
7307
|
+
return successResponse(c, { runCommand });
|
|
6690
7308
|
} catch (error) {
|
|
6691
7309
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6692
|
-
|
|
6693
|
-
return c.json(response, statusCode);
|
|
7310
|
+
return errorResponse(c, "SUGGEST_RUN_COMMAND_ERROR", "Failed to suggest run command", 500, errorMessage);
|
|
6694
7311
|
}
|
|
6695
7312
|
});
|
|
6696
7313
|
router.post("/:id/run", async (c) => {
|
|
6697
7314
|
try {
|
|
6698
7315
|
const projectId = c.req.param("id");
|
|
6699
7316
|
if (!projectId) {
|
|
6700
|
-
|
|
6701
|
-
return c.json(response, statusCode);
|
|
7317
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
6702
7318
|
}
|
|
6703
7319
|
return streamAsyncGenerator(c, projectManager.startRunningProcess(projectId));
|
|
6704
7320
|
} catch (error) {
|
|
6705
7321
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6706
|
-
|
|
6707
|
-
return c.json(response, statusCode);
|
|
7322
|
+
return errorResponse(c, "RUN_PROCESS_ERROR", "Failed to start process", 500, errorMessage);
|
|
6708
7323
|
}
|
|
6709
7324
|
});
|
|
6710
7325
|
router.post("/:id/stop", async (c) => {
|
|
6711
7326
|
try {
|
|
6712
7327
|
const projectId = c.req.param("id");
|
|
6713
7328
|
if (!projectId) {
|
|
6714
|
-
|
|
6715
|
-
return c.json(response2, statusCode2);
|
|
7329
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
6716
7330
|
}
|
|
6717
7331
|
const stopped = await projectManager.stopRunningProcess(projectId);
|
|
6718
7332
|
if (!stopped) {
|
|
6719
|
-
|
|
6720
|
-
return c.json(response2, statusCode2);
|
|
7333
|
+
return errorResponse(c, "NO_PROCESS_RUNNING", "No process running for this project", 400);
|
|
6721
7334
|
}
|
|
6722
|
-
|
|
6723
|
-
return c.json(response, statusCode);
|
|
7335
|
+
return successResponse(c, { success: true, message: "Process stopped" });
|
|
6724
7336
|
} catch (error) {
|
|
6725
7337
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6726
|
-
|
|
6727
|
-
return c.json(response, statusCode);
|
|
7338
|
+
return errorResponse(c, "STOP_PROCESS_ERROR", "Failed to stop process", 500, errorMessage);
|
|
6728
7339
|
}
|
|
6729
7340
|
});
|
|
6730
7341
|
router.get("/:id/running", async (c) => {
|
|
6731
7342
|
try {
|
|
6732
7343
|
const projectId = c.req.param("id");
|
|
6733
7344
|
if (!projectId) {
|
|
6734
|
-
|
|
6735
|
-
return c.json(response2, statusCode2);
|
|
7345
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
6736
7346
|
}
|
|
6737
7347
|
const isRunning = await projectManager.isProcessRunning(projectId);
|
|
6738
|
-
|
|
6739
|
-
return c.json(response, statusCode);
|
|
7348
|
+
return successResponse(c, { isRunning });
|
|
6740
7349
|
} catch (error) {
|
|
6741
7350
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6742
|
-
|
|
6743
|
-
return c.json(response, statusCode);
|
|
7351
|
+
return errorResponse(c, "CHECK_RUNNING_ERROR", "Failed to check running status", 500, errorMessage);
|
|
6744
7352
|
}
|
|
6745
7353
|
});
|
|
6746
7354
|
router.put("/:id/run-command", async (c) => {
|
|
6747
7355
|
try {
|
|
6748
7356
|
const projectId = c.req.param("id");
|
|
6749
7357
|
if (!projectId) {
|
|
6750
|
-
|
|
6751
|
-
return c.json(response2, statusCode2);
|
|
7358
|
+
return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
|
|
6752
7359
|
}
|
|
6753
7360
|
const body = await c.req.json();
|
|
6754
7361
|
const { runCommand } = body;
|
|
6755
7362
|
await projectManager.updateRunCommand(projectId, runCommand || null);
|
|
6756
|
-
|
|
6757
|
-
return c.json(response, statusCode);
|
|
7363
|
+
return successResponse(c, { success: true, message: "Run command updated" });
|
|
6758
7364
|
} catch (error) {
|
|
6759
7365
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6760
|
-
|
|
6761
|
-
return c.json(response, statusCode);
|
|
7366
|
+
return errorResponse(c, "UPDATE_RUN_COMMAND_ERROR", "Failed to update run command", 500, errorMessage);
|
|
6762
7367
|
}
|
|
6763
7368
|
});
|
|
6764
7369
|
return router;
|
|
@@ -6766,7 +7371,7 @@ function createRunRoutes(projectManager) {
|
|
|
6766
7371
|
|
|
6767
7372
|
// src/routes/onboarding.ts
|
|
6768
7373
|
import { Hono as Hono8 } from "hono";
|
|
6769
|
-
import { spawn as
|
|
7374
|
+
import { spawn as spawn9 } from "child_process";
|
|
6770
7375
|
function createOnboardingRoutes(metadataManager) {
|
|
6771
7376
|
const router = new Hono8;
|
|
6772
7377
|
router.get("/status", async (c) => {
|
|
@@ -6780,17 +7385,17 @@ function createOnboardingRoutes(metadataManager) {
|
|
|
6780
7385
|
});
|
|
6781
7386
|
router.get("/git-check", async (c) => {
|
|
6782
7387
|
try {
|
|
6783
|
-
const gitInstalled = await new Promise((
|
|
6784
|
-
const proc =
|
|
7388
|
+
const gitInstalled = await new Promise((resolve4) => {
|
|
7389
|
+
const proc = spawn9("git", ["--version"]);
|
|
6785
7390
|
let _err = "";
|
|
6786
7391
|
proc.stderr.on("data", (d) => {
|
|
6787
7392
|
_err += d.toString();
|
|
6788
7393
|
});
|
|
6789
7394
|
proc.on("close", (code) => {
|
|
6790
|
-
|
|
7395
|
+
resolve4(code === 0);
|
|
6791
7396
|
});
|
|
6792
7397
|
proc.on("error", () => {
|
|
6793
|
-
|
|
7398
|
+
resolve4(false);
|
|
6794
7399
|
});
|
|
6795
7400
|
});
|
|
6796
7401
|
return c.json({ gitInstalled });
|
|
@@ -6858,6 +7463,357 @@ function createScaffoldRoutes(projectManager) {
|
|
|
6858
7463
|
return router;
|
|
6859
7464
|
}
|
|
6860
7465
|
|
|
7466
|
+
// src/managers/slash-command-manager.ts
|
|
7467
|
+
import { readdir as readdir4, readFile as readFile4, mkdir as mkdir2, writeFile, unlink } from "fs/promises";
|
|
7468
|
+
import { join as join14, basename, extname as extname2 } from "path";
|
|
7469
|
+
import { existsSync as existsSync10 } from "fs";
|
|
7470
|
+
import { homedir as homedir4 } from "os";
|
|
7471
|
+
function slugify(filename) {
|
|
7472
|
+
return filename.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
7473
|
+
}
|
|
7474
|
+
function parseFrontmatter2(markdown) {
|
|
7475
|
+
const lines = markdown.split(`
|
|
7476
|
+
`);
|
|
7477
|
+
if (lines[0]?.trim() !== "---") {
|
|
7478
|
+
return {
|
|
7479
|
+
metadata: {},
|
|
7480
|
+
content: markdown.trim()
|
|
7481
|
+
};
|
|
7482
|
+
}
|
|
7483
|
+
let endIndex = -1;
|
|
7484
|
+
for (let i = 1;i < lines.length; i++) {
|
|
7485
|
+
if (lines[i]?.trim() === "---") {
|
|
7486
|
+
endIndex = i;
|
|
7487
|
+
break;
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
if (endIndex === -1) {
|
|
7491
|
+
return {
|
|
7492
|
+
metadata: {},
|
|
7493
|
+
content: markdown.trim()
|
|
7494
|
+
};
|
|
7495
|
+
}
|
|
7496
|
+
const frontmatterLines = lines.slice(1, endIndex);
|
|
7497
|
+
const metadata = {};
|
|
7498
|
+
for (const line of frontmatterLines) {
|
|
7499
|
+
const colonIndex = line.indexOf(":");
|
|
7500
|
+
if (colonIndex === -1)
|
|
7501
|
+
continue;
|
|
7502
|
+
const key = line.slice(0, colonIndex).trim();
|
|
7503
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
7504
|
+
if (key === "description") {
|
|
7505
|
+
metadata.description = value;
|
|
7506
|
+
} else if (key === "argument-hint") {
|
|
7507
|
+
metadata.argumentHint = value;
|
|
7508
|
+
} else if (key === "mode") {
|
|
7509
|
+
metadata.mode = value;
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
const content = lines.slice(endIndex + 1).join(`
|
|
7513
|
+
`).trim();
|
|
7514
|
+
return { metadata, content };
|
|
7515
|
+
}
|
|
7516
|
+
function getGlobalCommandsDir() {
|
|
7517
|
+
return join14(homedir4(), ".tarsk", "commands");
|
|
7518
|
+
}
|
|
7519
|
+
function getProjectCommandsDir(threadPath) {
|
|
7520
|
+
return join14(threadPath, ".tarsk", "commands");
|
|
7521
|
+
}
|
|
7522
|
+
|
|
7523
|
+
class SlashCommandManager {
|
|
7524
|
+
static BUILT_IN_COMMANDS = new Set(["init"]);
|
|
7525
|
+
async loadCommands(threadPath) {
|
|
7526
|
+
const commands = new Map;
|
|
7527
|
+
const globalDir = getGlobalCommandsDir();
|
|
7528
|
+
if (existsSync10(globalDir)) {
|
|
7529
|
+
const globalCommands = await this.loadCommandsFromDir(globalDir, "global");
|
|
7530
|
+
for (const cmd of globalCommands) {
|
|
7531
|
+
commands.set(cmd.name, cmd);
|
|
7532
|
+
}
|
|
7533
|
+
}
|
|
7534
|
+
const projectDir = getProjectCommandsDir(threadPath);
|
|
7535
|
+
if (existsSync10(projectDir)) {
|
|
7536
|
+
const projectCommands = await this.loadCommandsFromDir(projectDir, "project");
|
|
7537
|
+
for (const cmd of projectCommands) {
|
|
7538
|
+
if (!SlashCommandManager.BUILT_IN_COMMANDS.has(cmd.name)) {
|
|
7539
|
+
commands.set(cmd.name, cmd);
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
}
|
|
7543
|
+
return Array.from(commands.values());
|
|
7544
|
+
}
|
|
7545
|
+
async loadCommandsFromDir(dir, scope) {
|
|
7546
|
+
const commands = [];
|
|
7547
|
+
try {
|
|
7548
|
+
const files = await readdir4(dir);
|
|
7549
|
+
for (const file of files) {
|
|
7550
|
+
if (!file.endsWith(".md"))
|
|
7551
|
+
continue;
|
|
7552
|
+
const filePath = join14(dir, file);
|
|
7553
|
+
const fileContent = await readFile4(filePath, "utf-8");
|
|
7554
|
+
const { metadata, content } = parseFrontmatter2(fileContent);
|
|
7555
|
+
const nameWithoutExt = basename(file, extname2(file));
|
|
7556
|
+
const commandName = slugify(nameWithoutExt);
|
|
7557
|
+
if (!commandName)
|
|
7558
|
+
continue;
|
|
7559
|
+
commands.push({
|
|
7560
|
+
name: commandName,
|
|
7561
|
+
content,
|
|
7562
|
+
metadata,
|
|
7563
|
+
scope,
|
|
7564
|
+
filePath
|
|
7565
|
+
});
|
|
7566
|
+
}
|
|
7567
|
+
} catch (error) {
|
|
7568
|
+
console.error(`Failed to load commands from ${dir}:`, error);
|
|
7569
|
+
}
|
|
7570
|
+
return commands;
|
|
7571
|
+
}
|
|
7572
|
+
async createCommand(name, content, metadata, scope, threadPath) {
|
|
7573
|
+
if (SlashCommandManager.BUILT_IN_COMMANDS.has(name)) {
|
|
7574
|
+
throw new Error(`Cannot create built-in command: ${name}`);
|
|
7575
|
+
}
|
|
7576
|
+
const dir = scope === "global" ? getGlobalCommandsDir() : threadPath ? getProjectCommandsDir(threadPath) : null;
|
|
7577
|
+
if (!dir) {
|
|
7578
|
+
throw new Error("threadPath required for project-scoped commands");
|
|
7579
|
+
}
|
|
7580
|
+
if (!existsSync10(dir)) {
|
|
7581
|
+
await mkdir2(dir, { recursive: true });
|
|
7582
|
+
}
|
|
7583
|
+
const filename = `${name}.md`;
|
|
7584
|
+
const filePath = join14(dir, filename);
|
|
7585
|
+
if (existsSync10(filePath)) {
|
|
7586
|
+
throw new Error(`Command already exists: ${name}`);
|
|
7587
|
+
}
|
|
7588
|
+
let markdown = "";
|
|
7589
|
+
if (metadata.description || metadata.argumentHint || metadata.mode) {
|
|
7590
|
+
markdown += `---
|
|
7591
|
+
`;
|
|
7592
|
+
if (metadata.description) {
|
|
7593
|
+
markdown += `description: ${metadata.description}
|
|
7594
|
+
`;
|
|
7595
|
+
}
|
|
7596
|
+
if (metadata.argumentHint) {
|
|
7597
|
+
markdown += `argument-hint: ${metadata.argumentHint}
|
|
7598
|
+
`;
|
|
7599
|
+
}
|
|
7600
|
+
if (metadata.mode) {
|
|
7601
|
+
markdown += `mode: ${metadata.mode}
|
|
7602
|
+
`;
|
|
7603
|
+
}
|
|
7604
|
+
markdown += `---
|
|
7605
|
+
|
|
7606
|
+
`;
|
|
7607
|
+
}
|
|
7608
|
+
markdown += content;
|
|
7609
|
+
await writeFile(filePath, markdown, "utf-8");
|
|
7610
|
+
return {
|
|
7611
|
+
name,
|
|
7612
|
+
content,
|
|
7613
|
+
metadata,
|
|
7614
|
+
scope,
|
|
7615
|
+
filePath
|
|
7616
|
+
};
|
|
7617
|
+
}
|
|
7618
|
+
async updateCommand(filePath, content, metadata) {
|
|
7619
|
+
if (!existsSync10(filePath)) {
|
|
7620
|
+
throw new Error(`Command file not found: ${filePath}`);
|
|
7621
|
+
}
|
|
7622
|
+
let markdown = "";
|
|
7623
|
+
if (metadata.description || metadata.argumentHint || metadata.mode) {
|
|
7624
|
+
markdown += `---
|
|
7625
|
+
`;
|
|
7626
|
+
if (metadata.description) {
|
|
7627
|
+
markdown += `description: ${metadata.description}
|
|
7628
|
+
`;
|
|
7629
|
+
}
|
|
7630
|
+
if (metadata.argumentHint) {
|
|
7631
|
+
markdown += `argument-hint: ${metadata.argumentHint}
|
|
7632
|
+
`;
|
|
7633
|
+
}
|
|
7634
|
+
if (metadata.mode) {
|
|
7635
|
+
markdown += `mode: ${metadata.mode}
|
|
7636
|
+
`;
|
|
7637
|
+
}
|
|
7638
|
+
markdown += `---
|
|
7639
|
+
|
|
7640
|
+
`;
|
|
7641
|
+
}
|
|
7642
|
+
markdown += content;
|
|
7643
|
+
await writeFile(filePath, markdown, "utf-8");
|
|
7644
|
+
}
|
|
7645
|
+
async deleteCommand(filePath) {
|
|
7646
|
+
if (!existsSync10(filePath)) {
|
|
7647
|
+
throw new Error(`Command file not found: ${filePath}`);
|
|
7648
|
+
}
|
|
7649
|
+
await unlink(filePath);
|
|
7650
|
+
}
|
|
7651
|
+
async getCommand(name, threadPath) {
|
|
7652
|
+
const commands = await this.loadCommands(threadPath);
|
|
7653
|
+
return commands.find((cmd) => cmd.name === name) || null;
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
|
|
7657
|
+
// src/routes/slash-commands.ts
|
|
7658
|
+
var slashCommandManager = new SlashCommandManager;
|
|
7659
|
+
var skillManager = new SkillManager;
|
|
7660
|
+
function createSlashCommandRoutes(router, threadManager) {
|
|
7661
|
+
router.get("/api/slash-commands", async (c) => {
|
|
7662
|
+
const threadId = c.req.query("threadId");
|
|
7663
|
+
if (!threadId) {
|
|
7664
|
+
return c.json({
|
|
7665
|
+
error: {
|
|
7666
|
+
code: "MISSING_THREAD_ID",
|
|
7667
|
+
message: "threadId query parameter is required",
|
|
7668
|
+
timestamp: new Date().toISOString()
|
|
7669
|
+
}
|
|
7670
|
+
}, 400);
|
|
7671
|
+
}
|
|
7672
|
+
try {
|
|
7673
|
+
const thread = await threadManager.getThread(threadId);
|
|
7674
|
+
if (!thread) {
|
|
7675
|
+
return c.json({
|
|
7676
|
+
error: {
|
|
7677
|
+
code: "THREAD_NOT_FOUND",
|
|
7678
|
+
message: `Thread not found: ${threadId}`,
|
|
7679
|
+
timestamp: new Date().toISOString()
|
|
7680
|
+
}
|
|
7681
|
+
}, 404);
|
|
7682
|
+
}
|
|
7683
|
+
const commands = await slashCommandManager.loadCommands(thread.path);
|
|
7684
|
+
const skills = await skillManager.loadSkills(thread.path);
|
|
7685
|
+
const response = {
|
|
7686
|
+
commands,
|
|
7687
|
+
skills
|
|
7688
|
+
};
|
|
7689
|
+
return c.json(response);
|
|
7690
|
+
} catch (error) {
|
|
7691
|
+
console.error("Failed to load slash commands:", error);
|
|
7692
|
+
return c.json({
|
|
7693
|
+
error: {
|
|
7694
|
+
code: "LOAD_FAILED",
|
|
7695
|
+
message: error instanceof Error ? error.message : "Failed to load slash commands",
|
|
7696
|
+
details: error,
|
|
7697
|
+
timestamp: new Date().toISOString()
|
|
7698
|
+
}
|
|
7699
|
+
}, 500);
|
|
7700
|
+
}
|
|
7701
|
+
});
|
|
7702
|
+
router.post("/api/slash-commands", async (c) => {
|
|
7703
|
+
try {
|
|
7704
|
+
const body = await c.req.json();
|
|
7705
|
+
const { name, content, metadata, scope, threadId } = body;
|
|
7706
|
+
if (!name || !content) {
|
|
7707
|
+
return c.json({
|
|
7708
|
+
error: {
|
|
7709
|
+
code: "MISSING_FIELDS",
|
|
7710
|
+
message: "name and content are required",
|
|
7711
|
+
timestamp: new Date().toISOString()
|
|
7712
|
+
}
|
|
7713
|
+
}, 400);
|
|
7714
|
+
}
|
|
7715
|
+
if (scope !== "global" && scope !== "project") {
|
|
7716
|
+
return c.json({
|
|
7717
|
+
error: {
|
|
7718
|
+
code: "INVALID_SCOPE",
|
|
7719
|
+
message: 'scope must be either "global" or "project"',
|
|
7720
|
+
timestamp: new Date().toISOString()
|
|
7721
|
+
}
|
|
7722
|
+
}, 400);
|
|
7723
|
+
}
|
|
7724
|
+
let threadPath;
|
|
7725
|
+
if (scope === "project") {
|
|
7726
|
+
if (!threadId) {
|
|
7727
|
+
return c.json({
|
|
7728
|
+
error: {
|
|
7729
|
+
code: "MISSING_THREAD_ID",
|
|
7730
|
+
message: "threadId is required for project-scoped commands",
|
|
7731
|
+
timestamp: new Date().toISOString()
|
|
7732
|
+
}
|
|
7733
|
+
}, 400);
|
|
7734
|
+
}
|
|
7735
|
+
const thread = await threadManager.getThread(threadId);
|
|
7736
|
+
if (!thread) {
|
|
7737
|
+
return c.json({
|
|
7738
|
+
error: {
|
|
7739
|
+
code: "THREAD_NOT_FOUND",
|
|
7740
|
+
message: `Thread not found: ${threadId}`,
|
|
7741
|
+
timestamp: new Date().toISOString()
|
|
7742
|
+
}
|
|
7743
|
+
}, 404);
|
|
7744
|
+
}
|
|
7745
|
+
threadPath = thread.path;
|
|
7746
|
+
}
|
|
7747
|
+
const command = await slashCommandManager.createCommand(name, content, metadata || {}, scope, threadPath);
|
|
7748
|
+
return c.json(command, 201);
|
|
7749
|
+
} catch (error) {
|
|
7750
|
+
console.error("Failed to create slash command:", error);
|
|
7751
|
+
return c.json({
|
|
7752
|
+
error: {
|
|
7753
|
+
code: "CREATE_FAILED",
|
|
7754
|
+
message: error instanceof Error ? error.message : "Failed to create slash command",
|
|
7755
|
+
details: error,
|
|
7756
|
+
timestamp: new Date().toISOString()
|
|
7757
|
+
}
|
|
7758
|
+
}, 500);
|
|
7759
|
+
}
|
|
7760
|
+
});
|
|
7761
|
+
router.put("/api/slash-commands", async (c) => {
|
|
7762
|
+
try {
|
|
7763
|
+
const body = await c.req.json();
|
|
7764
|
+
const { filePath, content, metadata } = body;
|
|
7765
|
+
if (!filePath || !content) {
|
|
7766
|
+
return c.json({
|
|
7767
|
+
error: {
|
|
7768
|
+
code: "MISSING_FIELDS",
|
|
7769
|
+
message: "filePath and content are required",
|
|
7770
|
+
timestamp: new Date().toISOString()
|
|
7771
|
+
}
|
|
7772
|
+
}, 400);
|
|
7773
|
+
}
|
|
7774
|
+
await slashCommandManager.updateCommand(filePath, content, metadata || {});
|
|
7775
|
+
return c.json({ success: true });
|
|
7776
|
+
} catch (error) {
|
|
7777
|
+
console.error("Failed to update slash command:", error);
|
|
7778
|
+
return c.json({
|
|
7779
|
+
error: {
|
|
7780
|
+
code: "UPDATE_FAILED",
|
|
7781
|
+
message: error instanceof Error ? error.message : "Failed to update slash command",
|
|
7782
|
+
details: error,
|
|
7783
|
+
timestamp: new Date().toISOString()
|
|
7784
|
+
}
|
|
7785
|
+
}, 500);
|
|
7786
|
+
}
|
|
7787
|
+
});
|
|
7788
|
+
router.delete("/api/slash-commands", async (c) => {
|
|
7789
|
+
try {
|
|
7790
|
+
const body = await c.req.json();
|
|
7791
|
+
const { filePath } = body;
|
|
7792
|
+
if (!filePath) {
|
|
7793
|
+
return c.json({
|
|
7794
|
+
error: {
|
|
7795
|
+
code: "MISSING_FILE_PATH",
|
|
7796
|
+
message: "filePath is required",
|
|
7797
|
+
timestamp: new Date().toISOString()
|
|
7798
|
+
}
|
|
7799
|
+
}, 400);
|
|
7800
|
+
}
|
|
7801
|
+
await slashCommandManager.deleteCommand(filePath);
|
|
7802
|
+
return c.json({ success: true });
|
|
7803
|
+
} catch (error) {
|
|
7804
|
+
console.error("Failed to delete slash command:", error);
|
|
7805
|
+
return c.json({
|
|
7806
|
+
error: {
|
|
7807
|
+
code: "DELETE_FAILED",
|
|
7808
|
+
message: error instanceof Error ? error.message : "Failed to delete slash command",
|
|
7809
|
+
details: error,
|
|
7810
|
+
timestamp: new Date().toISOString()
|
|
7811
|
+
}
|
|
7812
|
+
}, 500);
|
|
7813
|
+
}
|
|
7814
|
+
});
|
|
7815
|
+
}
|
|
7816
|
+
|
|
6861
7817
|
// src/index.ts
|
|
6862
7818
|
init_dist();
|
|
6863
7819
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
@@ -6905,6 +7861,7 @@ app.route("/api/models", createModelRoutes(metadataManager));
|
|
|
6905
7861
|
app.route("/api/git", createGitRoutes(metadataManager));
|
|
6906
7862
|
app.route("/api/onboarding", createOnboardingRoutes(metadataManager));
|
|
6907
7863
|
app.route("/api/scaffold", createScaffoldRoutes(projectManager));
|
|
7864
|
+
createSlashCommandRoutes(app, threadManager);
|
|
6908
7865
|
var publicDir = path3.join(__dirname3, "public");
|
|
6909
7866
|
var staticRoot = path3.relative(process.cwd(), publicDir);
|
|
6910
7867
|
app.use("/*", async (c, next) => {
|
|
@@ -6922,14 +7879,14 @@ app.get("*", async (c, next) => {
|
|
|
6922
7879
|
})(c, next);
|
|
6923
7880
|
});
|
|
6924
7881
|
app.all("*", (c) => {
|
|
6925
|
-
const
|
|
7882
|
+
const errorResponse2 = {
|
|
6926
7883
|
error: {
|
|
6927
7884
|
code: "NOT_FOUND",
|
|
6928
7885
|
message: `Route not found: ${c.req.method} ${c.req.path}`,
|
|
6929
7886
|
timestamp: new Date().toISOString()
|
|
6930
7887
|
}
|
|
6931
7888
|
};
|
|
6932
|
-
return c.json(
|
|
7889
|
+
return c.json(errorResponse2, 404);
|
|
6933
7890
|
});
|
|
6934
7891
|
var port = isDebug ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
|
|
6935
7892
|
var url = `http://localhost:${port}`;
|
|
@@ -6941,6 +7898,6 @@ serve({
|
|
|
6941
7898
|
}, () => {
|
|
6942
7899
|
const isDevelopment = process.env.MODE === "development";
|
|
6943
7900
|
if (shouldOpenBrowser || !isDevelopment) {
|
|
6944
|
-
|
|
7901
|
+
open3(url).catch(() => {});
|
|
6945
7902
|
}
|
|
6946
7903
|
});
|