@docyrus/docyrus 0.0.23 → 0.0.25
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/main.js +113 -45
- package/main.js.map +3 -3
- package/package.json +1 -1
- package/resources/pi-agent/skills/diffity-diff/SKILL.md +28 -0
- package/resources/pi-agent/skills/diffity-resolve/SKILL.md +69 -0
- package/resources/pi-agent/skills/diffity-review/SKILL.md +175 -0
- package/server-loader.js +421 -24
- package/server-loader.js.map +2 -2
package/server-loader.js
CHANGED
|
@@ -3092,6 +3092,85 @@ async function waitForIdle(session, timeoutMs = 3e4) {
|
|
|
3092
3092
|
}
|
|
3093
3093
|
});
|
|
3094
3094
|
}
|
|
3095
|
+
function convertSessionEntriesToUIMessages(entries) {
|
|
3096
|
+
const messages = [];
|
|
3097
|
+
const toolResults = /* @__PURE__ */ new Map();
|
|
3098
|
+
for (const entry of entries) {
|
|
3099
|
+
const rec = entry;
|
|
3100
|
+
if (rec.type !== "message") {
|
|
3101
|
+
continue;
|
|
3102
|
+
}
|
|
3103
|
+
const msg = rec.message;
|
|
3104
|
+
if (!msg || msg.role !== "toolResult") {
|
|
3105
|
+
continue;
|
|
3106
|
+
}
|
|
3107
|
+
const toolCallId = msg.toolCallId;
|
|
3108
|
+
if (toolCallId) {
|
|
3109
|
+
const content = msg.content;
|
|
3110
|
+
const textParts = Array.isArray(content) ? content.filter((b) => b.type === "text").map((b) => b.text).join("\n") : typeof content === "string" ? content : "";
|
|
3111
|
+
toolResults.set(toolCallId, {
|
|
3112
|
+
output: textParts,
|
|
3113
|
+
isError: msg.isError ?? false
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
for (const entry of entries) {
|
|
3118
|
+
const rec = entry;
|
|
3119
|
+
if (rec.type !== "message") {
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
const msg = rec.message;
|
|
3123
|
+
if (!msg) {
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
const entryId = rec.id ?? `entry_${messages.length}`;
|
|
3127
|
+
if (msg.role === "user") {
|
|
3128
|
+
const content = msg.content;
|
|
3129
|
+
let text = "";
|
|
3130
|
+
if (typeof content === "string") {
|
|
3131
|
+
text = content;
|
|
3132
|
+
} else if (Array.isArray(content)) {
|
|
3133
|
+
text = content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
3134
|
+
}
|
|
3135
|
+
if (text) {
|
|
3136
|
+
messages.push({ id: entryId, role: "user", parts: [{ type: "text", text }] });
|
|
3137
|
+
}
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3140
|
+
if (msg.role === "assistant") {
|
|
3141
|
+
const content = msg.content;
|
|
3142
|
+
if (!Array.isArray(content)) {
|
|
3143
|
+
continue;
|
|
3144
|
+
}
|
|
3145
|
+
const parts = [];
|
|
3146
|
+
for (const block of content) {
|
|
3147
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
3148
|
+
parts.push({ type: "text", text: block.text });
|
|
3149
|
+
} else if (block.type === "thinking" && typeof block.thinking === "string") {
|
|
3150
|
+
parts.push({ type: "reasoning", text: block.thinking, state: "complete" });
|
|
3151
|
+
} else if (block.type === "toolCall") {
|
|
3152
|
+
const toolCallId = block.id;
|
|
3153
|
+
const toolName = block.name;
|
|
3154
|
+
const input = block.arguments ?? {};
|
|
3155
|
+
const result = toolResults.get(toolCallId);
|
|
3156
|
+
parts.push({
|
|
3157
|
+
type: "dynamic-tool",
|
|
3158
|
+
toolCallId,
|
|
3159
|
+
toolName,
|
|
3160
|
+
input,
|
|
3161
|
+
state: result ? result.isError ? "output-error" : "output-available" : "input-available",
|
|
3162
|
+
...result && !result.isError ? { output: result.output } : {},
|
|
3163
|
+
...result && result.isError ? { errorText: String(result.output) } : {}
|
|
3164
|
+
});
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
if (parts.length > 0) {
|
|
3168
|
+
messages.push({ id: entryId, role: "assistant", parts });
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return messages;
|
|
3173
|
+
}
|
|
3095
3174
|
var FS_IGNORE = /* @__PURE__ */ new Set([
|
|
3096
3175
|
"node_modules",
|
|
3097
3176
|
".git",
|
|
@@ -3135,13 +3214,19 @@ async function buildTree(params) {
|
|
|
3135
3214
|
entries.sort((a, b) => {
|
|
3136
3215
|
const aIsDir = a.isDirectory();
|
|
3137
3216
|
const bIsDir = b.isDirectory();
|
|
3138
|
-
if (aIsDir !== bIsDir)
|
|
3217
|
+
if (aIsDir !== bIsDir) {
|
|
3218
|
+
return aIsDir ? -1 : 1;
|
|
3219
|
+
}
|
|
3139
3220
|
return a.name.localeCompare(b.name);
|
|
3140
3221
|
});
|
|
3141
3222
|
const result = [];
|
|
3142
3223
|
for (const entry of entries) {
|
|
3143
|
-
if (counter.value >= maxFiles)
|
|
3144
|
-
|
|
3224
|
+
if (counter.value >= maxFiles) {
|
|
3225
|
+
break;
|
|
3226
|
+
}
|
|
3227
|
+
if (ignore.has(entry.name)) {
|
|
3228
|
+
continue;
|
|
3229
|
+
}
|
|
3145
3230
|
const fullPath = (0, import_node_path2.join)(dir, entry.name);
|
|
3146
3231
|
const relativePath = (0, import_node_path2.relative)(cwd, fullPath);
|
|
3147
3232
|
if (entry.isDirectory()) {
|
|
@@ -3164,7 +3249,9 @@ async function buildTree(params) {
|
|
|
3164
3249
|
}
|
|
3165
3250
|
async function walkFiles(params) {
|
|
3166
3251
|
const { dir, cwd, pattern, ignore, maxResults, results } = params;
|
|
3167
|
-
if (results.length >= maxResults)
|
|
3252
|
+
if (results.length >= maxResults) {
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3168
3255
|
let entries;
|
|
3169
3256
|
try {
|
|
3170
3257
|
entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
|
|
@@ -3172,8 +3259,12 @@ async function walkFiles(params) {
|
|
|
3172
3259
|
return;
|
|
3173
3260
|
}
|
|
3174
3261
|
for (const entry of entries) {
|
|
3175
|
-
if (results.length >= maxResults)
|
|
3176
|
-
|
|
3262
|
+
if (results.length >= maxResults) {
|
|
3263
|
+
break;
|
|
3264
|
+
}
|
|
3265
|
+
if (ignore.has(entry.name)) {
|
|
3266
|
+
continue;
|
|
3267
|
+
}
|
|
3177
3268
|
const fullPath = (0, import_node_path2.join)(dir, entry.name);
|
|
3178
3269
|
const relativePath = (0, import_node_path2.relative)(cwd, fullPath);
|
|
3179
3270
|
if (entry.isDirectory()) {
|
|
@@ -3184,7 +3275,7 @@ async function walkFiles(params) {
|
|
|
3184
3275
|
}
|
|
3185
3276
|
}
|
|
3186
3277
|
async function createAgentServer(params) {
|
|
3187
|
-
const { port, sessionManager, modelRegistry, context, onResumeSession } = params;
|
|
3278
|
+
const { port, sessionManager, modelRegistry, context, onCreateSession, onResumeSession } = params;
|
|
3188
3279
|
let activeSession = params.session;
|
|
3189
3280
|
const app = new Hono2();
|
|
3190
3281
|
app.use("/*", cors({ origin: "*" }));
|
|
@@ -3279,6 +3370,31 @@ async function createAgentServer(params) {
|
|
|
3279
3370
|
return c.json({ error: message }, 500);
|
|
3280
3371
|
}
|
|
3281
3372
|
});
|
|
3373
|
+
app.post("/api/sessions", async (c) => {
|
|
3374
|
+
try {
|
|
3375
|
+
if (activeSession.isStreaming) {
|
|
3376
|
+
await activeSession.abort();
|
|
3377
|
+
await waitForIdle(activeSession);
|
|
3378
|
+
}
|
|
3379
|
+
activeSession = await onCreateSession();
|
|
3380
|
+
const sessionId = activeSession.id?.trim();
|
|
3381
|
+
if (!sessionId) {
|
|
3382
|
+
return c.json({ error: "Created session is missing an id" }, 500);
|
|
3383
|
+
}
|
|
3384
|
+
const sessions = await sessionManager.list();
|
|
3385
|
+
const match2 = sessions.find((session) => session.id === sessionId);
|
|
3386
|
+
return c.json({
|
|
3387
|
+
ok: true,
|
|
3388
|
+
sessionId,
|
|
3389
|
+
sessionName: match2?.name ?? null,
|
|
3390
|
+
cwd: match2?.cwd ?? context.cwd,
|
|
3391
|
+
model: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null
|
|
3392
|
+
});
|
|
3393
|
+
} catch (error) {
|
|
3394
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3395
|
+
return c.json({ error: message }, 500);
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3282
3398
|
app.get("/api/sessions/:sessionId/messages", async (c) => {
|
|
3283
3399
|
const sessionId = c.req.param("sessionId");
|
|
3284
3400
|
try {
|
|
@@ -3289,14 +3405,15 @@ async function createAgentServer(params) {
|
|
|
3289
3405
|
}
|
|
3290
3406
|
const opened = sessionManager.open(match2.path);
|
|
3291
3407
|
const sessionContext = opened.buildSessionContext();
|
|
3292
|
-
const
|
|
3408
|
+
const rawEntries = opened.getBranch();
|
|
3409
|
+
const messages = convertSessionEntriesToUIMessages(rawEntries);
|
|
3293
3410
|
return c.json({
|
|
3294
3411
|
sessionId: opened.getSessionId(),
|
|
3295
3412
|
sessionName: opened.getSessionName() ?? null,
|
|
3296
3413
|
cwd: opened.getCwd(),
|
|
3297
3414
|
model: sessionContext.model ?? null,
|
|
3298
3415
|
thinkingLevel: sessionContext.thinkingLevel ?? null,
|
|
3299
|
-
|
|
3416
|
+
messages
|
|
3300
3417
|
});
|
|
3301
3418
|
} catch (error) {
|
|
3302
3419
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3466,6 +3583,42 @@ async function createAgentServer(params) {
|
|
|
3466
3583
|
return c.json({ error: message }, status);
|
|
3467
3584
|
}
|
|
3468
3585
|
});
|
|
3586
|
+
const CLI_EXEC = process.env.DOCYRUS_CLI_EXECUTABLE || process.execPath;
|
|
3587
|
+
const CLI_ENTRY = process.env.DOCYRUS_CLI_ENTRY || (0, import_node_path2.resolve)(process.argv[1] ? (0, import_node_path2.join)(process.argv[1], "..") : __dirname, "main.js");
|
|
3588
|
+
const CLI_SCOPE = process.env.DOCYRUS_CLI_SCOPE;
|
|
3589
|
+
const CLI_TIMEOUT_MS = 3e4;
|
|
3590
|
+
function runCliCommand(args) {
|
|
3591
|
+
return new Promise((resolveResult) => {
|
|
3592
|
+
const scopeArgs = CLI_SCOPE ? ["--scope", CLI_SCOPE] : [];
|
|
3593
|
+
const proc = (0, import_node_child_process.spawn)(CLI_EXEC, [CLI_ENTRY, ...scopeArgs, ...args, "--json"], {
|
|
3594
|
+
cwd: context.cwd,
|
|
3595
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3596
|
+
timeout: CLI_TIMEOUT_MS
|
|
3597
|
+
});
|
|
3598
|
+
let stdout = "";
|
|
3599
|
+
let stderr = "";
|
|
3600
|
+
proc.stdout?.on("data", (chunk) => {
|
|
3601
|
+
stdout += chunk.toString();
|
|
3602
|
+
});
|
|
3603
|
+
proc.stderr?.on("data", (chunk) => {
|
|
3604
|
+
stderr += chunk.toString();
|
|
3605
|
+
});
|
|
3606
|
+
proc.on("close", (code) => {
|
|
3607
|
+
if (code !== 0) {
|
|
3608
|
+
resolveResult({ ok: false, error: stderr.trim() || `exit code ${code}` });
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
try {
|
|
3612
|
+
resolveResult({ ok: true, data: JSON.parse(stdout) });
|
|
3613
|
+
} catch {
|
|
3614
|
+
resolveResult({ ok: true, data: stdout.trim() });
|
|
3615
|
+
}
|
|
3616
|
+
});
|
|
3617
|
+
proc.on("error", (err) => {
|
|
3618
|
+
resolveResult({ ok: false, error: err.message });
|
|
3619
|
+
});
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3469
3622
|
let devProcess = null;
|
|
3470
3623
|
let devUrl = null;
|
|
3471
3624
|
const DEV_URL_PATTERN = /https?:\/\/(?:localhost|127\.0\.0\.1):\d+/;
|
|
@@ -3479,13 +3632,104 @@ async function createAgentServer(params) {
|
|
|
3479
3632
|
return null;
|
|
3480
3633
|
}
|
|
3481
3634
|
}
|
|
3635
|
+
async function detectDevPort(cwd) {
|
|
3636
|
+
try {
|
|
3637
|
+
const pkg = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, "package.json"), "utf-8"));
|
|
3638
|
+
const devScript = pkg.scripts?.dev;
|
|
3639
|
+
if (devScript) {
|
|
3640
|
+
const portFlag = devScript.match(/--port\s+(\d+)/);
|
|
3641
|
+
if (portFlag) {
|
|
3642
|
+
return Number(portFlag[1]);
|
|
3643
|
+
}
|
|
3644
|
+
const dashP = devScript.match(/(?:^|\s)-p\s+(\d+)/);
|
|
3645
|
+
if (dashP) {
|
|
3646
|
+
return Number(dashP[1]);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
} catch {
|
|
3650
|
+
}
|
|
3651
|
+
for (const name of ["vite.config.ts", "vite.config.mts", "vite.config.js"]) {
|
|
3652
|
+
try {
|
|
3653
|
+
const content = await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, name), "utf-8");
|
|
3654
|
+
const portMatch = content.match(/port\s*:\s*(\d+)/);
|
|
3655
|
+
if (portMatch) {
|
|
3656
|
+
return Number(portMatch[1]);
|
|
3657
|
+
}
|
|
3658
|
+
} catch {
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
for (const name of ["next.config.ts", "next.config.mts", "next.config.js", "next.config.mjs"]) {
|
|
3662
|
+
try {
|
|
3663
|
+
await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, name));
|
|
3664
|
+
return 3e3;
|
|
3665
|
+
} catch {
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
try {
|
|
3669
|
+
await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, "angular.json"));
|
|
3670
|
+
return 4200;
|
|
3671
|
+
} catch {
|
|
3672
|
+
}
|
|
3673
|
+
for (const name of ["nuxt.config.ts", "nuxt.config.js"]) {
|
|
3674
|
+
try {
|
|
3675
|
+
await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, name));
|
|
3676
|
+
return 3e3;
|
|
3677
|
+
} catch {
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
return null;
|
|
3681
|
+
}
|
|
3682
|
+
let cachedProjectInfo = null;
|
|
3683
|
+
async function getProjectInfo() {
|
|
3684
|
+
if (cachedProjectInfo) {
|
|
3685
|
+
return cachedProjectInfo;
|
|
3686
|
+
}
|
|
3687
|
+
const cwd = context.cwd;
|
|
3688
|
+
let repo = null;
|
|
3689
|
+
let packageName = null;
|
|
3690
|
+
let packageVersion = null;
|
|
3691
|
+
try {
|
|
3692
|
+
repo = await new Promise((res, rej) => {
|
|
3693
|
+
(0, import_node_child_process.execFile)("git", ["remote", "get-url", "origin"], { cwd }, (err, stdout) => {
|
|
3694
|
+
if (err) {
|
|
3695
|
+
return rej(err);
|
|
3696
|
+
}
|
|
3697
|
+
const url = stdout.trim();
|
|
3698
|
+
const match2 = url.match(/\/([^/]+?)(?:\.git)?$/);
|
|
3699
|
+
res(match2 ? match2[1] : url);
|
|
3700
|
+
});
|
|
3701
|
+
});
|
|
3702
|
+
} catch {
|
|
3703
|
+
}
|
|
3704
|
+
try {
|
|
3705
|
+
const pkg = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, "package.json"), "utf-8"));
|
|
3706
|
+
packageName = pkg.name ?? null;
|
|
3707
|
+
packageVersion = pkg.version ?? null;
|
|
3708
|
+
} catch {
|
|
3709
|
+
}
|
|
3710
|
+
cachedProjectInfo = {
|
|
3711
|
+
path: cwd,
|
|
3712
|
+
folder: (0, import_node_path2.basename)(cwd),
|
|
3713
|
+
repo,
|
|
3714
|
+
packageName,
|
|
3715
|
+
packageVersion
|
|
3716
|
+
};
|
|
3717
|
+
return cachedProjectInfo;
|
|
3718
|
+
}
|
|
3482
3719
|
app.get("/api/env/status", async (c) => {
|
|
3720
|
+
const [authResult, project] = await Promise.all([
|
|
3721
|
+
runCliCommand(["auth", "who"]),
|
|
3722
|
+
getProjectInfo()
|
|
3723
|
+
]);
|
|
3724
|
+
const env = authResult.ok ? authResult.data : null;
|
|
3483
3725
|
if (devProcess && devProcess.exitCode === null && devUrl) {
|
|
3484
3726
|
const httpStatus = await probeUrl(devUrl);
|
|
3485
3727
|
return c.json({
|
|
3486
3728
|
status: httpStatus !== null ? "running" : "starting",
|
|
3487
3729
|
url: devUrl,
|
|
3488
3730
|
managed: true,
|
|
3731
|
+
project,
|
|
3732
|
+
env,
|
|
3489
3733
|
...httpStatus !== null ? { httpStatus } : {}
|
|
3490
3734
|
});
|
|
3491
3735
|
}
|
|
@@ -3494,17 +3738,33 @@ async function createAgentServer(params) {
|
|
|
3494
3738
|
devProcess = null;
|
|
3495
3739
|
const stoppedUrl = devUrl;
|
|
3496
3740
|
devUrl = null;
|
|
3497
|
-
return c.json({ status: "stopped", url: stoppedUrl, exitCode, managed: true });
|
|
3741
|
+
return c.json({ status: "stopped", url: stoppedUrl, exitCode, managed: true, project, env });
|
|
3498
3742
|
}
|
|
3499
|
-
const
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3743
|
+
const explicitUrl = c.req.query("url");
|
|
3744
|
+
const explicitPort = c.req.query("port");
|
|
3745
|
+
let probeTarget = null;
|
|
3746
|
+
if (explicitUrl) {
|
|
3747
|
+
probeTarget = explicitUrl;
|
|
3748
|
+
} else if (explicitPort) {
|
|
3749
|
+
probeTarget = `http://localhost:${explicitPort}`;
|
|
3750
|
+
} else {
|
|
3751
|
+
const detected = await detectDevPort(context.cwd);
|
|
3752
|
+
if (detected) {
|
|
3753
|
+
probeTarget = `http://localhost:${detected}`;
|
|
3505
3754
|
}
|
|
3506
3755
|
}
|
|
3507
|
-
|
|
3756
|
+
if (probeTarget) {
|
|
3757
|
+
const httpStatus = await probeUrl(probeTarget);
|
|
3758
|
+
return c.json({
|
|
3759
|
+
status: httpStatus !== null ? "running" : "stopped",
|
|
3760
|
+
url: probeTarget,
|
|
3761
|
+
httpStatus: httpStatus ?? void 0,
|
|
3762
|
+
managed: false,
|
|
3763
|
+
project,
|
|
3764
|
+
env
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
return c.json({ status: "stopped", url: null, managed: false, project, env });
|
|
3508
3768
|
});
|
|
3509
3769
|
app.post("/api/env/serve", (c) => {
|
|
3510
3770
|
if (devProcess && devProcess.exitCode === null) {
|
|
@@ -3521,7 +3781,9 @@ async function createAgentServer(params) {
|
|
|
3521
3781
|
let stderr = "";
|
|
3522
3782
|
let resolved = false;
|
|
3523
3783
|
function settle(response) {
|
|
3524
|
-
if (resolved)
|
|
3784
|
+
if (resolved) {
|
|
3785
|
+
return;
|
|
3786
|
+
}
|
|
3525
3787
|
resolved = true;
|
|
3526
3788
|
resolveResponse(response);
|
|
3527
3789
|
}
|
|
@@ -3562,13 +3824,103 @@ async function createAgentServer(params) {
|
|
|
3562
3824
|
devUrl = null;
|
|
3563
3825
|
return c.json({ status: "stopped", url: stoppedUrl, pid });
|
|
3564
3826
|
});
|
|
3827
|
+
const BOOLEAN_CLI_FLAGS = /* @__PURE__ */ new Set(["json", "verbose", "global", "noAuth", "expand", "i"]);
|
|
3828
|
+
function buildCliArgs(pathSegments, query, body) {
|
|
3829
|
+
const args = [...pathSegments, "--json"];
|
|
3830
|
+
for (const [key, value] of Object.entries(query)) {
|
|
3831
|
+
if (key === "json") {
|
|
3832
|
+
continue;
|
|
3833
|
+
}
|
|
3834
|
+
const flag = key.length === 1 ? `-${key}` : `--${key}`;
|
|
3835
|
+
if (BOOLEAN_CLI_FLAGS.has(key) || value === "" || value === "true") {
|
|
3836
|
+
args.push(flag);
|
|
3837
|
+
} else {
|
|
3838
|
+
args.push(flag, value);
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
if (body) {
|
|
3842
|
+
if (body.data !== void 0) {
|
|
3843
|
+
args.push("--data", typeof body.data === "string" ? body.data : JSON.stringify(body.data));
|
|
3844
|
+
}
|
|
3845
|
+
if (typeof body.fromFile === "string") {
|
|
3846
|
+
args.push("--from-file", body.fromFile);
|
|
3847
|
+
}
|
|
3848
|
+
for (const [key, value] of Object.entries(body)) {
|
|
3849
|
+
if (key === "data" || key === "fromFile") {
|
|
3850
|
+
continue;
|
|
3851
|
+
}
|
|
3852
|
+
const flag = key.length === 1 ? `-${key}` : `--${key}`;
|
|
3853
|
+
if (typeof value === "boolean") {
|
|
3854
|
+
if (value) {
|
|
3855
|
+
args.push(flag);
|
|
3856
|
+
}
|
|
3857
|
+
} else if (value !== void 0 && value !== null) {
|
|
3858
|
+
args.push(flag, String(value));
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
return args;
|
|
3863
|
+
}
|
|
3864
|
+
app.all("/api/cli/*", async (c) => {
|
|
3865
|
+
const pathSegments = c.req.path.replace(/^\/api\/cli\/?/, "").split("/").filter(Boolean);
|
|
3866
|
+
if (pathSegments.length === 0) {
|
|
3867
|
+
return c.json({ error: "No command specified. Usage: /api/cli/<command>/[subcommand]/[args...]" }, 400);
|
|
3868
|
+
}
|
|
3869
|
+
const query = {};
|
|
3870
|
+
for (const [key, value] of Object.entries(c.req.query())) {
|
|
3871
|
+
if (typeof value === "string") {
|
|
3872
|
+
query[key] = value;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
let body;
|
|
3876
|
+
if (c.req.method === "POST" || c.req.method === "PUT") {
|
|
3877
|
+
try {
|
|
3878
|
+
body = await c.req.json();
|
|
3879
|
+
} catch {
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
const cliArgs = buildCliArgs(pathSegments, query, body);
|
|
3883
|
+
return new Promise((resolveResponse) => {
|
|
3884
|
+
const scopeArgs = CLI_SCOPE ? ["--scope", CLI_SCOPE] : [];
|
|
3885
|
+
const proc = (0, import_node_child_process.spawn)(CLI_EXEC, [CLI_ENTRY, ...scopeArgs, ...cliArgs], {
|
|
3886
|
+
cwd: context.cwd,
|
|
3887
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3888
|
+
timeout: CLI_TIMEOUT_MS
|
|
3889
|
+
});
|
|
3890
|
+
let stdout = "";
|
|
3891
|
+
let stderr = "";
|
|
3892
|
+
proc.stdout?.on("data", (chunk) => {
|
|
3893
|
+
stdout += chunk.toString();
|
|
3894
|
+
});
|
|
3895
|
+
proc.stderr?.on("data", (chunk) => {
|
|
3896
|
+
stderr += chunk.toString();
|
|
3897
|
+
});
|
|
3898
|
+
proc.on("close", (code) => {
|
|
3899
|
+
if (code !== 0) {
|
|
3900
|
+
resolveResponse(c.json({
|
|
3901
|
+
error: stderr.trim() || `Command exited with code ${code}`,
|
|
3902
|
+
command: `docyrus ${cliArgs.join(" ")}`,
|
|
3903
|
+
exitCode: code
|
|
3904
|
+
}, 500));
|
|
3905
|
+
return;
|
|
3906
|
+
}
|
|
3907
|
+
try {
|
|
3908
|
+
const parsed = JSON.parse(stdout);
|
|
3909
|
+
resolveResponse(c.json(parsed));
|
|
3910
|
+
} catch {
|
|
3911
|
+
resolveResponse(c.json({ output: stdout.trim(), command: `docyrus ${cliArgs.join(" ")}` }));
|
|
3912
|
+
}
|
|
3913
|
+
});
|
|
3914
|
+
proc.on("error", (err) => {
|
|
3915
|
+
resolveResponse(c.json({ error: err.message, command: `docyrus ${cliArgs.join(" ")}` }, 500));
|
|
3916
|
+
});
|
|
3917
|
+
});
|
|
3918
|
+
});
|
|
3565
3919
|
const { serve: serve2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
port
|
|
3569
|
-
}, (info) => {
|
|
3920
|
+
const MAX_PORT_RETRIES = 10;
|
|
3921
|
+
function printBanner(actualPort) {
|
|
3570
3922
|
process.stderr.write(`
|
|
3571
|
-
Agent server listening on http://localhost:${
|
|
3923
|
+
Agent server listening on http://localhost:${actualPort}
|
|
3572
3924
|
|
|
3573
3925
|
`);
|
|
3574
3926
|
process.stderr.write(` POST /api/chat \u2014 send chat messages (SSE UIMessage stream)
|
|
@@ -3578,6 +3930,8 @@ async function createAgentServer(params) {
|
|
|
3578
3930
|
process.stderr.write(` GET /api/status \u2014 session status
|
|
3579
3931
|
`);
|
|
3580
3932
|
process.stderr.write(` GET /api/sessions \u2014 list sessions
|
|
3933
|
+
`);
|
|
3934
|
+
process.stderr.write(` POST /api/sessions \u2014 create a new session
|
|
3581
3935
|
`);
|
|
3582
3936
|
process.stderr.write(` GET /api/sessions/:sessionId/messages \u2014 session messages
|
|
3583
3937
|
`);
|
|
@@ -3606,9 +3960,37 @@ async function createAgentServer(params) {
|
|
|
3606
3960
|
process.stderr.write(` POST /api/env/serve \u2014 start dev server (pnpm dev)
|
|
3607
3961
|
`);
|
|
3608
3962
|
process.stderr.write(` POST /api/env/stop \u2014 stop dev server
|
|
3963
|
+
`);
|
|
3964
|
+
process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
|
|
3609
3965
|
|
|
3610
3966
|
`);
|
|
3611
|
-
}
|
|
3967
|
+
}
|
|
3968
|
+
for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
|
|
3969
|
+
const candidatePort = port + attempt;
|
|
3970
|
+
try {
|
|
3971
|
+
await new Promise((resolveStart, rejectStart) => {
|
|
3972
|
+
const server = serve2({ fetch: app.fetch, port: candidatePort }, () => {
|
|
3973
|
+
printBanner(candidatePort);
|
|
3974
|
+
resolveStart();
|
|
3975
|
+
});
|
|
3976
|
+
server.on("error", (err) => {
|
|
3977
|
+
if (err.code === "EADDRINUSE") {
|
|
3978
|
+
process.stderr.write(` Port ${candidatePort} in use, trying ${candidatePort + 1}...
|
|
3979
|
+
`);
|
|
3980
|
+
rejectStart(err);
|
|
3981
|
+
} else {
|
|
3982
|
+
throw err;
|
|
3983
|
+
}
|
|
3984
|
+
});
|
|
3985
|
+
});
|
|
3986
|
+
return;
|
|
3987
|
+
} catch (err) {
|
|
3988
|
+
const isAddrInUse = err instanceof Error && "code" in err && err.code === "EADDRINUSE";
|
|
3989
|
+
if (!isAddrInUse || attempt === MAX_PORT_RETRIES - 1) {
|
|
3990
|
+
throw new Error(`All ports ${port}\u2013${port + MAX_PORT_RETRIES - 1} are in use`);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3612
3994
|
}
|
|
3613
3995
|
|
|
3614
3996
|
// src/server/server-loader.ts
|
|
@@ -3808,6 +4190,21 @@ Or create ${modelsJsonPath}`
|
|
|
3808
4190
|
sessionDir: request.sessionDir ?? null,
|
|
3809
4191
|
thinkingLevel: request.thinking ?? null
|
|
3810
4192
|
},
|
|
4193
|
+
onCreateSession: async () => {
|
|
4194
|
+
const { session: freshSession } = await pi.createAgentSession({
|
|
4195
|
+
cwd,
|
|
4196
|
+
agentDir,
|
|
4197
|
+
authStorage,
|
|
4198
|
+
modelRegistry,
|
|
4199
|
+
resourceLoader,
|
|
4200
|
+
settingsManager,
|
|
4201
|
+
sessionManager,
|
|
4202
|
+
tools: buildTools(request.profile, cwd, pi),
|
|
4203
|
+
model: requestedModel,
|
|
4204
|
+
thinkingLevel: request.thinking
|
|
4205
|
+
});
|
|
4206
|
+
return freshSession;
|
|
4207
|
+
},
|
|
3811
4208
|
onResumeSession: async (sessionId) => {
|
|
3812
4209
|
const sessions = await pi.SessionManager.list(cwd, request.sessionDir);
|
|
3813
4210
|
const match2 = sessions.find((s) => s.id === sessionId);
|