@docyrus/docyrus 0.0.60 → 0.0.62
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 +7 -4
- package/main.js.map +2 -2
- package/package.json +1 -1
- package/resources/pi-agent/extensions/browser-tools.ts +229 -0
- package/server-loader.js +563 -3
- package/server-loader.js.map +4 -4
package/server-loader.js
CHANGED
|
@@ -43986,6 +43986,199 @@ async function listAllTools(params) {
|
|
|
43986
43986
|
return tools;
|
|
43987
43987
|
}
|
|
43988
43988
|
|
|
43989
|
+
// src/server/browserToolSchemas.ts
|
|
43990
|
+
var BROWSER_TOOL_PREFIX = "docyrus_browser_";
|
|
43991
|
+
var BROWSER_TOOL_SCHEMAS = [
|
|
43992
|
+
{
|
|
43993
|
+
name: "docyrus_browser_navigate",
|
|
43994
|
+
description: "Navigate the preview browser to a URL. Use --reload to force reload after navigation.",
|
|
43995
|
+
source: "built-in",
|
|
43996
|
+
inputSchema: {
|
|
43997
|
+
type: "object",
|
|
43998
|
+
properties: {
|
|
43999
|
+
url: { type: "string", description: "URL to navigate to" },
|
|
44000
|
+
reload: { type: "boolean", description: "Force reload after navigation" }
|
|
44001
|
+
},
|
|
44002
|
+
required: ["url"]
|
|
44003
|
+
}
|
|
44004
|
+
},
|
|
44005
|
+
{
|
|
44006
|
+
name: "docyrus_browser_wait",
|
|
44007
|
+
description: "Wait for a condition before proceeding: network idle, CSS selector appearance, URL pattern match, or fixed delay.",
|
|
44008
|
+
source: "built-in",
|
|
44009
|
+
inputSchema: {
|
|
44010
|
+
type: "object",
|
|
44011
|
+
properties: {
|
|
44012
|
+
idle: { type: "boolean", description: "Wait for network idle" },
|
|
44013
|
+
selector: { type: "string", description: "Wait for CSS selector to appear" },
|
|
44014
|
+
url: { type: "string", description: "Wait for URL to match glob pattern" },
|
|
44015
|
+
ms: { type: "number", description: "Wait for fixed milliseconds" },
|
|
44016
|
+
timeout: { type: "number", description: "Maximum wait time in ms (default: 15000)" }
|
|
44017
|
+
}
|
|
44018
|
+
}
|
|
44019
|
+
},
|
|
44020
|
+
{
|
|
44021
|
+
name: "docyrus_browser_snapshot",
|
|
44022
|
+
description: "Get a compact snapshot of interactive page elements with refs (@e1, @e2, ...) for use in click/fill/select. Returns tag, text, type, name, value, placeholder, href for each element.",
|
|
44023
|
+
source: "built-in",
|
|
44024
|
+
inputSchema: {
|
|
44025
|
+
type: "object",
|
|
44026
|
+
properties: {
|
|
44027
|
+
all: { type: "boolean", description: "Include all elements, not just interactive ones" },
|
|
44028
|
+
selector: { type: "string", description: "Scope snapshot to a CSS selector subtree" }
|
|
44029
|
+
}
|
|
44030
|
+
}
|
|
44031
|
+
},
|
|
44032
|
+
{
|
|
44033
|
+
name: "docyrus_browser_click",
|
|
44034
|
+
description: "Click an element by snapshot ref (@e1), CSS selector, or x,y coordinates. Coordinate clicks pass through iframes and shadow DOM at the compositor level.",
|
|
44035
|
+
source: "built-in",
|
|
44036
|
+
inputSchema: {
|
|
44037
|
+
type: "object",
|
|
44038
|
+
properties: {
|
|
44039
|
+
target: { type: "string", description: "Snapshot ref (@e1), CSS selector, or x coordinate" },
|
|
44040
|
+
x: { type: "number", description: "X coordinate (when using coordinate mode)" },
|
|
44041
|
+
y: { type: "number", description: "Y coordinate (when using coordinate mode)" },
|
|
44042
|
+
timeout: { type: "number", description: "Timeout in ms (default: 5000)" }
|
|
44043
|
+
},
|
|
44044
|
+
required: ["target"]
|
|
44045
|
+
}
|
|
44046
|
+
},
|
|
44047
|
+
{
|
|
44048
|
+
name: "docyrus_browser_fill",
|
|
44049
|
+
description: "Clear and type a value into an input or textarea by snapshot ref or CSS selector.",
|
|
44050
|
+
source: "built-in",
|
|
44051
|
+
inputSchema: {
|
|
44052
|
+
type: "object",
|
|
44053
|
+
properties: {
|
|
44054
|
+
target: { type: "string", description: "Snapshot ref (@e1) or CSS selector" },
|
|
44055
|
+
value: { type: "string", description: "Value to type into the element" },
|
|
44056
|
+
timeout: { type: "number", description: "Timeout in ms (default: 5000)" }
|
|
44057
|
+
},
|
|
44058
|
+
required: ["target", "value"]
|
|
44059
|
+
}
|
|
44060
|
+
},
|
|
44061
|
+
{
|
|
44062
|
+
name: "docyrus_browser_select",
|
|
44063
|
+
description: "Select a dropdown option by snapshot ref or CSS selector. Matches by option text or value.",
|
|
44064
|
+
source: "built-in",
|
|
44065
|
+
inputSchema: {
|
|
44066
|
+
type: "object",
|
|
44067
|
+
properties: {
|
|
44068
|
+
target: { type: "string", description: "Snapshot ref (@e1) or CSS selector" },
|
|
44069
|
+
value: { type: "string", description: "Option text or value to select" }
|
|
44070
|
+
},
|
|
44071
|
+
required: ["target", "value"]
|
|
44072
|
+
}
|
|
44073
|
+
},
|
|
44074
|
+
{
|
|
44075
|
+
name: "docyrus_browser_eval",
|
|
44076
|
+
description: "Evaluate JavaScript in the preview page and return the result. Supports expressions and multi-statement code with async/await.",
|
|
44077
|
+
source: "built-in",
|
|
44078
|
+
inputSchema: {
|
|
44079
|
+
type: "object",
|
|
44080
|
+
properties: {
|
|
44081
|
+
code: { type: "string", description: "JavaScript code to evaluate" }
|
|
44082
|
+
},
|
|
44083
|
+
required: ["code"]
|
|
44084
|
+
}
|
|
44085
|
+
},
|
|
44086
|
+
{
|
|
44087
|
+
name: "docyrus_browser_screenshot",
|
|
44088
|
+
description: "Capture a screenshot of the preview browser. Returns base64-encoded PNG image.",
|
|
44089
|
+
source: "built-in",
|
|
44090
|
+
inputSchema: {
|
|
44091
|
+
type: "object",
|
|
44092
|
+
properties: {
|
|
44093
|
+
full: { type: "boolean", description: "Capture full page instead of just the viewport" }
|
|
44094
|
+
}
|
|
44095
|
+
}
|
|
44096
|
+
},
|
|
44097
|
+
{
|
|
44098
|
+
name: "docyrus_browser_console",
|
|
44099
|
+
description: "Read captured console messages (log, warn, error) from the preview page. Run early in the session to start capturing.",
|
|
44100
|
+
source: "built-in",
|
|
44101
|
+
inputSchema: {
|
|
44102
|
+
type: "object",
|
|
44103
|
+
properties: {
|
|
44104
|
+
level: { type: "string", description: "Filter by level: log, warn, error, info, debug" }
|
|
44105
|
+
}
|
|
44106
|
+
}
|
|
44107
|
+
},
|
|
44108
|
+
{
|
|
44109
|
+
name: "docyrus_browser_network",
|
|
44110
|
+
description: "Inspect captured network requests from the preview page. Filter by HTTP method, status code, or URL.",
|
|
44111
|
+
source: "built-in",
|
|
44112
|
+
inputSchema: {
|
|
44113
|
+
type: "object",
|
|
44114
|
+
properties: {
|
|
44115
|
+
method: { type: "string", description: "Filter by HTTP method (GET, POST, etc.)" },
|
|
44116
|
+
status: { type: "string", description: "Filter by status code or pattern (200, 4xx, 5xx)" },
|
|
44117
|
+
url: { type: "string", description: "Filter by URL substring" }
|
|
44118
|
+
}
|
|
44119
|
+
}
|
|
44120
|
+
},
|
|
44121
|
+
{
|
|
44122
|
+
name: "docyrus_browser_cookies",
|
|
44123
|
+
description: "List cookies for the preview page, optionally filtered by name or domain.",
|
|
44124
|
+
source: "built-in",
|
|
44125
|
+
inputSchema: {
|
|
44126
|
+
type: "object",
|
|
44127
|
+
properties: {
|
|
44128
|
+
name: { type: "string", description: "Filter cookies by exact name" },
|
|
44129
|
+
domain: { type: "string", description: "Filter cookies by domain (substring match)" }
|
|
44130
|
+
}
|
|
44131
|
+
}
|
|
44132
|
+
},
|
|
44133
|
+
{
|
|
44134
|
+
name: "docyrus_browser_content",
|
|
44135
|
+
description: "Extract readable markdown content from the current preview page using Readability.",
|
|
44136
|
+
source: "built-in",
|
|
44137
|
+
inputSchema: {
|
|
44138
|
+
type: "object",
|
|
44139
|
+
properties: {}
|
|
44140
|
+
}
|
|
44141
|
+
},
|
|
44142
|
+
{
|
|
44143
|
+
name: "docyrus_browser_info",
|
|
44144
|
+
description: "Get current preview page info: URL, title, viewport dimensions, scroll position, page size, and ready state.",
|
|
44145
|
+
source: "built-in",
|
|
44146
|
+
inputSchema: {
|
|
44147
|
+
type: "object",
|
|
44148
|
+
properties: {}
|
|
44149
|
+
}
|
|
44150
|
+
},
|
|
44151
|
+
{
|
|
44152
|
+
name: "docyrus_browser_devtools",
|
|
44153
|
+
description: "Read @docyrus/devtools runtime diagnostics from the preview page: API errors, usage issues, and console entries.",
|
|
44154
|
+
source: "built-in",
|
|
44155
|
+
inputSchema: {
|
|
44156
|
+
type: "object",
|
|
44157
|
+
properties: {
|
|
44158
|
+
subcommand: {
|
|
44159
|
+
type: "string",
|
|
44160
|
+
enum: ["state", "errors", "issues", "console"],
|
|
44161
|
+
description: "What to read: state (full), errors (API errors), issues (usage issues), console (entries)"
|
|
44162
|
+
},
|
|
44163
|
+
level: { type: "string", description: "Filter console entries by level (for console subcommand)" }
|
|
44164
|
+
},
|
|
44165
|
+
required: ["subcommand"]
|
|
44166
|
+
}
|
|
44167
|
+
},
|
|
44168
|
+
{
|
|
44169
|
+
name: "docyrus_browser_open",
|
|
44170
|
+
description: "Open an external website in a new browser window for automation. Returns a webviewId for targeting subsequent commands.",
|
|
44171
|
+
source: "built-in",
|
|
44172
|
+
inputSchema: {
|
|
44173
|
+
type: "object",
|
|
44174
|
+
properties: {
|
|
44175
|
+
url: { type: "string", description: "URL to open in the new browser window" }
|
|
44176
|
+
},
|
|
44177
|
+
required: ["url"]
|
|
44178
|
+
}
|
|
44179
|
+
}
|
|
44180
|
+
];
|
|
44181
|
+
|
|
43989
44182
|
// src/server/sessionConfig.ts
|
|
43990
44183
|
var import_node_fs12 = require("node:fs");
|
|
43991
44184
|
var path5 = __toESM(require("node:path"));
|
|
@@ -44345,6 +44538,107 @@ function resolveSafePath(cwd, requestPath) {
|
|
|
44345
44538
|
}
|
|
44346
44539
|
return resolved;
|
|
44347
44540
|
}
|
|
44541
|
+
var ALLOWED_UPLOAD_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
44542
|
+
// Text / code
|
|
44543
|
+
".txt",
|
|
44544
|
+
".md",
|
|
44545
|
+
".markdown",
|
|
44546
|
+
".json",
|
|
44547
|
+
".jsonl",
|
|
44548
|
+
".csv",
|
|
44549
|
+
".tsv",
|
|
44550
|
+
".xml",
|
|
44551
|
+
".yaml",
|
|
44552
|
+
".yml",
|
|
44553
|
+
".toml",
|
|
44554
|
+
".ini",
|
|
44555
|
+
".cfg",
|
|
44556
|
+
".conf",
|
|
44557
|
+
".log",
|
|
44558
|
+
".rtf",
|
|
44559
|
+
// Documents
|
|
44560
|
+
".pdf",
|
|
44561
|
+
".doc",
|
|
44562
|
+
".docx",
|
|
44563
|
+
".xls",
|
|
44564
|
+
".xlsx",
|
|
44565
|
+
".ppt",
|
|
44566
|
+
".pptx",
|
|
44567
|
+
".odt",
|
|
44568
|
+
".ods",
|
|
44569
|
+
".odp",
|
|
44570
|
+
".pages",
|
|
44571
|
+
".numbers",
|
|
44572
|
+
".key",
|
|
44573
|
+
// Images
|
|
44574
|
+
".png",
|
|
44575
|
+
".jpg",
|
|
44576
|
+
".jpeg",
|
|
44577
|
+
".gif",
|
|
44578
|
+
".bmp",
|
|
44579
|
+
".webp",
|
|
44580
|
+
".svg",
|
|
44581
|
+
".ico",
|
|
44582
|
+
".tiff",
|
|
44583
|
+
".tif",
|
|
44584
|
+
".heic",
|
|
44585
|
+
".heif",
|
|
44586
|
+
".avif",
|
|
44587
|
+
// Audio
|
|
44588
|
+
".mp3",
|
|
44589
|
+
".wav",
|
|
44590
|
+
".ogg",
|
|
44591
|
+
".flac",
|
|
44592
|
+
".aac",
|
|
44593
|
+
".m4a",
|
|
44594
|
+
".wma",
|
|
44595
|
+
".opus",
|
|
44596
|
+
// Video
|
|
44597
|
+
".mp4",
|
|
44598
|
+
".webm",
|
|
44599
|
+
".mov",
|
|
44600
|
+
".avi",
|
|
44601
|
+
".mkv",
|
|
44602
|
+
".m4v",
|
|
44603
|
+
".wmv",
|
|
44604
|
+
// Archives
|
|
44605
|
+
".zip",
|
|
44606
|
+
".tar",
|
|
44607
|
+
".gz",
|
|
44608
|
+
".7z",
|
|
44609
|
+
".rar",
|
|
44610
|
+
// Fonts
|
|
44611
|
+
".woff",
|
|
44612
|
+
".woff2",
|
|
44613
|
+
".ttf",
|
|
44614
|
+
".otf",
|
|
44615
|
+
".eot"
|
|
44616
|
+
]);
|
|
44617
|
+
function sanitizeUploadPath(filePath) {
|
|
44618
|
+
const cleaned = filePath.replace(/[\x00-\x1f\x7f]/g, "");
|
|
44619
|
+
const normalized = cleaned.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
44620
|
+
if (!normalized || normalized === "/") {
|
|
44621
|
+
throw new Error("Invalid upload path");
|
|
44622
|
+
}
|
|
44623
|
+
const ext = normalized.includes(".") ? `.${normalized.split(".").pop().toLowerCase()}` : "";
|
|
44624
|
+
if (!ext || !ALLOWED_UPLOAD_EXTENSIONS.has(ext)) {
|
|
44625
|
+
throw new Error(`File type '${ext || "(none)"}' is not allowed. Allowed: text, images, audio, video, and document files.`);
|
|
44626
|
+
}
|
|
44627
|
+
return normalized;
|
|
44628
|
+
}
|
|
44629
|
+
async function assertNotSymlink(filePath) {
|
|
44630
|
+
try {
|
|
44631
|
+
const stats = await (0, import_promises18.lstat)(filePath);
|
|
44632
|
+
if (stats.isSymbolicLink()) {
|
|
44633
|
+
throw new Error("Cannot write to symbolic link");
|
|
44634
|
+
}
|
|
44635
|
+
} catch (error48) {
|
|
44636
|
+
if (error48 instanceof Error && "code" in error48 && error48.code === "ENOENT") {
|
|
44637
|
+
return;
|
|
44638
|
+
}
|
|
44639
|
+
throw error48;
|
|
44640
|
+
}
|
|
44641
|
+
}
|
|
44348
44642
|
function globToRegex(pattern) {
|
|
44349
44643
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\0/g, ".*").replace(/\?/g, "[^/]");
|
|
44350
44644
|
return new RegExp(`^${escaped}$`);
|
|
@@ -44425,6 +44719,7 @@ async function walkFiles(params) {
|
|
|
44425
44719
|
}
|
|
44426
44720
|
async function createAgentServer(params) {
|
|
44427
44721
|
const { port, sessionManager, modelRegistry, authRuntime, context, onCreateSession, onResumeSession, authToken } = params;
|
|
44722
|
+
const extensionUIBridge = params.extensionUIBridge;
|
|
44428
44723
|
let activeSession = params.session;
|
|
44429
44724
|
let sessionMode = "normal";
|
|
44430
44725
|
const pendingAskUserRequests = /* @__PURE__ */ new Map();
|
|
@@ -44541,6 +44836,16 @@ async function createAgentServer(params) {
|
|
|
44541
44836
|
}
|
|
44542
44837
|
writeChunk({ type: "start" });
|
|
44543
44838
|
writeChunk({ type: "start-step" });
|
|
44839
|
+
let extensionUICleanup;
|
|
44840
|
+
if (extensionUIBridge) {
|
|
44841
|
+
extensionUIBridge.setRequestHandler((request) => {
|
|
44842
|
+
writeChunk(request);
|
|
44843
|
+
});
|
|
44844
|
+
extensionUICleanup = () => {
|
|
44845
|
+
extensionUIBridge.setRequestHandler(() => {
|
|
44846
|
+
});
|
|
44847
|
+
};
|
|
44848
|
+
}
|
|
44544
44849
|
const bridge = createEventBridge({
|
|
44545
44850
|
messageId,
|
|
44546
44851
|
onChunk: writeChunk,
|
|
@@ -44548,11 +44853,13 @@ async function createAgentServer(params) {
|
|
|
44548
44853
|
pendingAskUserRequests.set(sessionId, { toolCallId, request });
|
|
44549
44854
|
},
|
|
44550
44855
|
onDone: () => {
|
|
44856
|
+
extensionUICleanup?.();
|
|
44551
44857
|
writeChunk({ type: "finish-step" });
|
|
44552
44858
|
writeChunk({ type: "finish" });
|
|
44553
44859
|
controller.close();
|
|
44554
44860
|
},
|
|
44555
44861
|
onError: (errorText) => {
|
|
44862
|
+
extensionUICleanup?.();
|
|
44556
44863
|
writeChunk({ type: "error", errorText });
|
|
44557
44864
|
writeChunk({ type: "finish-step" });
|
|
44558
44865
|
writeChunk({ type: "finish" });
|
|
@@ -44723,7 +45030,8 @@ async function createAgentServer(params) {
|
|
|
44723
45030
|
agentDir: context.agentDir,
|
|
44724
45031
|
version: context.version,
|
|
44725
45032
|
sessionDir: context.sessionDir,
|
|
44726
|
-
thinkingLevel: context.thinkingLevel
|
|
45033
|
+
thinkingLevel: context.thinkingLevel,
|
|
45034
|
+
desktop: context.desktop
|
|
44727
45035
|
});
|
|
44728
45036
|
});
|
|
44729
45037
|
app.get("/api/models", (c) => {
|
|
@@ -45412,6 +45720,59 @@ async function createAgentServer(params) {
|
|
|
45412
45720
|
return c.json({ error: message }, 400);
|
|
45413
45721
|
}
|
|
45414
45722
|
});
|
|
45723
|
+
app.post("/api/fs/upload", async (c) => {
|
|
45724
|
+
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
|
|
45725
|
+
const contentLength = parseInt(c.req.header("content-length") || "0", 10);
|
|
45726
|
+
if (contentLength > MAX_UPLOAD_SIZE) {
|
|
45727
|
+
return c.json({ error: `File too large. Maximum upload size is ${MAX_UPLOAD_SIZE / 1024 / 1024} MB` }, 413);
|
|
45728
|
+
}
|
|
45729
|
+
const contentType = c.req.header("content-type") || "";
|
|
45730
|
+
if (contentType.includes("multipart/form-data")) {
|
|
45731
|
+
const formData = await c.req.formData();
|
|
45732
|
+
const file2 = formData.get("file");
|
|
45733
|
+
const targetPath2 = formData.get("path");
|
|
45734
|
+
if (!file2 || !(file2 instanceof File)) {
|
|
45735
|
+
return c.json({ error: "Missing required form field: file" }, 400);
|
|
45736
|
+
}
|
|
45737
|
+
if (file2.size > MAX_UPLOAD_SIZE) {
|
|
45738
|
+
return c.json({ error: `File too large (${(file2.size / 1024 / 1024).toFixed(1)} MB). Maximum is ${MAX_UPLOAD_SIZE / 1024 / 1024} MB` }, 413);
|
|
45739
|
+
}
|
|
45740
|
+
const destPath = typeof targetPath2 === "string" && targetPath2 ? targetPath2 : file2.name;
|
|
45741
|
+
try {
|
|
45742
|
+
const sanitized = sanitizeUploadPath(destPath);
|
|
45743
|
+
const resolved = resolveSafePath(context.cwd, sanitized);
|
|
45744
|
+
await assertNotSymlink(resolved);
|
|
45745
|
+
await (0, import_promises18.mkdir)((0, import_node_path22.join)(resolved, ".."), { recursive: true });
|
|
45746
|
+
const buffer = Buffer.from(await file2.arrayBuffer());
|
|
45747
|
+
await (0, import_promises18.writeFile)(resolved, buffer);
|
|
45748
|
+
const fileStat = await (0, import_promises18.stat)(resolved);
|
|
45749
|
+
return c.json({ ok: true, path: sanitized, name: file2.name, size: fileStat.size, type: file2.type });
|
|
45750
|
+
} catch (error48) {
|
|
45751
|
+
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
45752
|
+
return c.json({ error: message }, 400);
|
|
45753
|
+
}
|
|
45754
|
+
}
|
|
45755
|
+
const targetPath = c.req.query("path") || c.req.header("x-file-path");
|
|
45756
|
+
if (!targetPath) {
|
|
45757
|
+
return c.json({ error: "Missing target path. Use multipart form field 'path', query param 'path', or header 'X-File-Path'" }, 400);
|
|
45758
|
+
}
|
|
45759
|
+
try {
|
|
45760
|
+
const sanitized = sanitizeUploadPath(targetPath);
|
|
45761
|
+
const resolved = resolveSafePath(context.cwd, sanitized);
|
|
45762
|
+
await assertNotSymlink(resolved);
|
|
45763
|
+
await (0, import_promises18.mkdir)((0, import_node_path22.join)(resolved, ".."), { recursive: true });
|
|
45764
|
+
const buffer = Buffer.from(await c.req.arrayBuffer());
|
|
45765
|
+
if (buffer.length > MAX_UPLOAD_SIZE) {
|
|
45766
|
+
return c.json({ error: `File too large (${(buffer.length / 1024 / 1024).toFixed(1)} MB). Maximum is ${MAX_UPLOAD_SIZE / 1024 / 1024} MB` }, 413);
|
|
45767
|
+
}
|
|
45768
|
+
await (0, import_promises18.writeFile)(resolved, buffer);
|
|
45769
|
+
const fileStat = await (0, import_promises18.stat)(resolved);
|
|
45770
|
+
return c.json({ ok: true, path: sanitized, size: fileStat.size });
|
|
45771
|
+
} catch (error48) {
|
|
45772
|
+
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
45773
|
+
return c.json({ error: message }, 400);
|
|
45774
|
+
}
|
|
45775
|
+
});
|
|
45415
45776
|
app.post("/api/fs/mkdir", async (c) => {
|
|
45416
45777
|
const body2 = await c.req.json();
|
|
45417
45778
|
if (!body2.path) {
|
|
@@ -46064,6 +46425,20 @@ async function createAgentServer(params) {
|
|
|
46064
46425
|
return c.json({ error: message }, 500);
|
|
46065
46426
|
}
|
|
46066
46427
|
});
|
|
46428
|
+
app.post("/api/extension-ui-response", async (c) => {
|
|
46429
|
+
if (!extensionUIBridge) {
|
|
46430
|
+
return c.json({ error: "Extension UI bridge not available" }, 404);
|
|
46431
|
+
}
|
|
46432
|
+
const body2 = await c.req.json();
|
|
46433
|
+
if (!body2.id) {
|
|
46434
|
+
return c.json({ error: "Missing required field: id" }, 400);
|
|
46435
|
+
}
|
|
46436
|
+
const handled = extensionUIBridge.handleResponse(body2);
|
|
46437
|
+
if (!handled) {
|
|
46438
|
+
return c.json({ error: `No pending extension UI request for id: ${body2.id}` }, 404);
|
|
46439
|
+
}
|
|
46440
|
+
return c.json({ ok: true });
|
|
46441
|
+
});
|
|
46067
46442
|
app.get("/api/tools", async (c) => {
|
|
46068
46443
|
try {
|
|
46069
46444
|
const tools = await listAllTools({
|
|
@@ -46071,11 +46446,15 @@ async function createAgentServer(params) {
|
|
|
46071
46446
|
agentDir: context.agentDir,
|
|
46072
46447
|
cwd: context.cwd
|
|
46073
46448
|
});
|
|
46449
|
+
if (context.desktop) {
|
|
46450
|
+
tools.push(...BROWSER_TOOL_SCHEMAS);
|
|
46451
|
+
}
|
|
46074
46452
|
const builtInCount = tools.filter((t) => t.source === "built-in").length;
|
|
46075
46453
|
const mcpCount = tools.filter((t) => t.source !== "built-in").length;
|
|
46076
46454
|
return c.json({
|
|
46077
46455
|
tools,
|
|
46078
|
-
summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length }
|
|
46456
|
+
summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length },
|
|
46457
|
+
clientSideToolPrefixes: context.desktop ? [BROWSER_TOOL_PREFIX] : []
|
|
46079
46458
|
});
|
|
46080
46459
|
} catch (error48) {
|
|
46081
46460
|
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
@@ -46267,6 +46646,8 @@ async function createAgentServer(params) {
|
|
|
46267
46646
|
process.stderr.write(` GET /api/fs/read \u2014 read file contents
|
|
46268
46647
|
`);
|
|
46269
46648
|
process.stderr.write(` POST /api/fs/write \u2014 write file
|
|
46649
|
+
`);
|
|
46650
|
+
process.stderr.write(` POST /api/fs/upload \u2014 upload file (multipart or raw)
|
|
46270
46651
|
`);
|
|
46271
46652
|
process.stderr.write(` POST /api/fs/mkdir \u2014 create directory
|
|
46272
46653
|
`);
|
|
@@ -46298,6 +46679,12 @@ async function createAgentServer(params) {
|
|
|
46298
46679
|
`);
|
|
46299
46680
|
process.stderr.write(` GET /api/tools \u2014 list all tools
|
|
46300
46681
|
`);
|
|
46682
|
+
if (context.desktop) {
|
|
46683
|
+
process.stderr.write(` POST /api/extension-ui-response \u2014 submit extension UI response
|
|
46684
|
+
`);
|
|
46685
|
+
process.stderr.write(` Browser tools: docyrus_browser_* (client-side via extension_ui)
|
|
46686
|
+
`);
|
|
46687
|
+
}
|
|
46301
46688
|
process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
|
|
46302
46689
|
`);
|
|
46303
46690
|
process.stderr.write(` WS /api/terminal \u2014 PTY terminal (WebSocket)
|
|
@@ -46493,6 +46880,163 @@ function createServerSessionAdapter(params) {
|
|
|
46493
46880
|
};
|
|
46494
46881
|
}
|
|
46495
46882
|
|
|
46883
|
+
// src/server/extensionUIBridge.ts
|
|
46884
|
+
var import_node_crypto7 = require("node:crypto");
|
|
46885
|
+
var ExtensionUIBridge = class {
|
|
46886
|
+
pending = /* @__PURE__ */ new Map();
|
|
46887
|
+
requestHandler;
|
|
46888
|
+
defaultTimeout;
|
|
46889
|
+
constructor(options) {
|
|
46890
|
+
this.requestHandler = options.onRequest;
|
|
46891
|
+
this.defaultTimeout = options.defaultTimeout ?? 12e4;
|
|
46892
|
+
}
|
|
46893
|
+
/**
|
|
46894
|
+
* Update the request handler. Used to wire into the active chat stream.
|
|
46895
|
+
*/
|
|
46896
|
+
setRequestHandler(handler2) {
|
|
46897
|
+
this.requestHandler = handler2;
|
|
46898
|
+
}
|
|
46899
|
+
/**
|
|
46900
|
+
* Handle an incoming extension_ui_response from the client.
|
|
46901
|
+
* Returns true if the response matched a pending request.
|
|
46902
|
+
*/
|
|
46903
|
+
handleResponse(response) {
|
|
46904
|
+
const entry = this.pending.get(response.id);
|
|
46905
|
+
if (!entry) {
|
|
46906
|
+
return false;
|
|
46907
|
+
}
|
|
46908
|
+
if (entry.timer) {
|
|
46909
|
+
clearTimeout(entry.timer);
|
|
46910
|
+
}
|
|
46911
|
+
this.pending.delete(response.id);
|
|
46912
|
+
entry.resolve(response);
|
|
46913
|
+
return true;
|
|
46914
|
+
}
|
|
46915
|
+
/**
|
|
46916
|
+
* Check if there are any pending requests.
|
|
46917
|
+
*/
|
|
46918
|
+
hasPending() {
|
|
46919
|
+
return this.pending.size > 0;
|
|
46920
|
+
}
|
|
46921
|
+
/**
|
|
46922
|
+
* Create a dialog promise that sends a request and waits for a response.
|
|
46923
|
+
*/
|
|
46924
|
+
createDialogPromise(request, defaultValue, parseResponse, opts) {
|
|
46925
|
+
if (opts?.signal?.aborted) {
|
|
46926
|
+
return Promise.resolve(defaultValue);
|
|
46927
|
+
}
|
|
46928
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
46929
|
+
const timeout = opts?.timeout ?? this.defaultTimeout;
|
|
46930
|
+
return new Promise((resolve4, reject) => {
|
|
46931
|
+
const cleanup = () => {
|
|
46932
|
+
if (timer) {
|
|
46933
|
+
clearTimeout(timer);
|
|
46934
|
+
}
|
|
46935
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
46936
|
+
this.pending.delete(id);
|
|
46937
|
+
};
|
|
46938
|
+
const onAbort = () => {
|
|
46939
|
+
cleanup();
|
|
46940
|
+
resolve4(defaultValue);
|
|
46941
|
+
};
|
|
46942
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
46943
|
+
const timer = timeout > 0 ? setTimeout(() => {
|
|
46944
|
+
cleanup();
|
|
46945
|
+
resolve4(defaultValue);
|
|
46946
|
+
}, timeout) : void 0;
|
|
46947
|
+
this.pending.set(id, {
|
|
46948
|
+
resolve: (response) => {
|
|
46949
|
+
cleanup();
|
|
46950
|
+
resolve4(parseResponse(response));
|
|
46951
|
+
},
|
|
46952
|
+
reject: (error48) => {
|
|
46953
|
+
cleanup();
|
|
46954
|
+
reject(error48);
|
|
46955
|
+
},
|
|
46956
|
+
timer
|
|
46957
|
+
});
|
|
46958
|
+
this.requestHandler({ type: "extension_ui_request", id, ...request });
|
|
46959
|
+
});
|
|
46960
|
+
}
|
|
46961
|
+
/**
|
|
46962
|
+
* Build an ExtensionUIContext compatible with pi agent's extension system.
|
|
46963
|
+
*/
|
|
46964
|
+
createUIContext() {
|
|
46965
|
+
return {
|
|
46966
|
+
select: (title, options, opts) => this.createDialogPromise(
|
|
46967
|
+
{ method: "select", title, options, timeout: opts?.timeout },
|
|
46968
|
+
void 0,
|
|
46969
|
+
(r) => "cancelled" in r && r.cancelled ? void 0 : r.value,
|
|
46970
|
+
opts
|
|
46971
|
+
),
|
|
46972
|
+
confirm: (title, message, opts) => this.createDialogPromise(
|
|
46973
|
+
{ method: "confirm", title, message, timeout: opts?.timeout },
|
|
46974
|
+
false,
|
|
46975
|
+
(r) => "cancelled" in r && r.cancelled ? false : r.confirmed ?? false,
|
|
46976
|
+
opts
|
|
46977
|
+
),
|
|
46978
|
+
input: (title, placeholder, opts) => this.createDialogPromise(
|
|
46979
|
+
{ method: "input", title, placeholder, timeout: opts?.timeout },
|
|
46980
|
+
void 0,
|
|
46981
|
+
(r) => "cancelled" in r && r.cancelled ? void 0 : r.value,
|
|
46982
|
+
opts
|
|
46983
|
+
),
|
|
46984
|
+
editor: (title, prefill, opts) => this.createDialogPromise(
|
|
46985
|
+
{ method: "editor", title, placeholder: prefill },
|
|
46986
|
+
void 0,
|
|
46987
|
+
(r) => "cancelled" in r && r.cancelled ? void 0 : r.value,
|
|
46988
|
+
opts
|
|
46989
|
+
),
|
|
46990
|
+
notify: (message, notifyType) => {
|
|
46991
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
46992
|
+
this.requestHandler({ type: "extension_ui_request", id, method: "notify", message, placeholder: notifyType });
|
|
46993
|
+
},
|
|
46994
|
+
setStatus: (statusKey, statusText) => {
|
|
46995
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
46996
|
+
this.requestHandler({ type: "extension_ui_request", id, method: "setStatus", title: statusKey, message: statusText });
|
|
46997
|
+
},
|
|
46998
|
+
setWidget: (widgetKey, widgetLines) => {
|
|
46999
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
47000
|
+
this.requestHandler({ type: "extension_ui_request", id, method: "setWidget", title: widgetKey, options: widgetLines });
|
|
47001
|
+
},
|
|
47002
|
+
setTitle: (title) => {
|
|
47003
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
47004
|
+
this.requestHandler({ type: "extension_ui_request", id, method: "setTitle", title });
|
|
47005
|
+
},
|
|
47006
|
+
setEditorText: (text3) => {
|
|
47007
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
47008
|
+
this.requestHandler({ type: "extension_ui_request", id, method: "set_editor_text", message: text3 });
|
|
47009
|
+
},
|
|
47010
|
+
// No-op methods for server mode (not applicable without TUI)
|
|
47011
|
+
setWorkingMessage: () => {
|
|
47012
|
+
},
|
|
47013
|
+
setHiddenThinkingLabel: () => {
|
|
47014
|
+
},
|
|
47015
|
+
setFooter: () => {
|
|
47016
|
+
},
|
|
47017
|
+
setHeader: () => {
|
|
47018
|
+
},
|
|
47019
|
+
onTerminalInput: () => () => {
|
|
47020
|
+
},
|
|
47021
|
+
custom: async () => void 0,
|
|
47022
|
+
pasteToEditor: () => {
|
|
47023
|
+
},
|
|
47024
|
+
getEditorText: () => "",
|
|
47025
|
+
setEditorComponent: () => {
|
|
47026
|
+
},
|
|
47027
|
+
get theme() {
|
|
47028
|
+
return { name: "default" };
|
|
47029
|
+
},
|
|
47030
|
+
getAllThemes: () => [],
|
|
47031
|
+
getTheme: () => void 0,
|
|
47032
|
+
setTheme: () => ({ success: false, error: "UI not available in server mode" }),
|
|
47033
|
+
getToolsExpanded: () => false,
|
|
47034
|
+
setToolsExpanded: () => {
|
|
47035
|
+
}
|
|
47036
|
+
};
|
|
47037
|
+
}
|
|
47038
|
+
};
|
|
47039
|
+
|
|
46496
47040
|
// src/services/spinner.ts
|
|
46497
47041
|
var import_picocolors = __toESM(require_picocolors());
|
|
46498
47042
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -46707,8 +47251,17 @@ async function main() {
|
|
|
46707
47251
|
`
|
|
46708
47252
|
);
|
|
46709
47253
|
}
|
|
47254
|
+
const extensionUIBridge = new ExtensionUIBridge({
|
|
47255
|
+
onRequest: () => {
|
|
47256
|
+
}
|
|
47257
|
+
// Will be wired up by agentServer after the chat stream is created
|
|
47258
|
+
});
|
|
47259
|
+
await session.bindExtensions({
|
|
47260
|
+
uiContext: extensionUIBridge.createUIContext()
|
|
47261
|
+
});
|
|
46710
47262
|
await createAgentServer({
|
|
46711
47263
|
session: createServerSessionAdapter({ session, extensionsResult }),
|
|
47264
|
+
extensionUIBridge,
|
|
46712
47265
|
authToken: request.auth,
|
|
46713
47266
|
port: request.port,
|
|
46714
47267
|
sessionManager: {
|
|
@@ -46728,7 +47281,8 @@ async function main() {
|
|
|
46728
47281
|
agentDir,
|
|
46729
47282
|
version: version2,
|
|
46730
47283
|
sessionDir: request.sessionDir ?? null,
|
|
46731
|
-
thinkingLevel: request.thinking ?? null
|
|
47284
|
+
thinkingLevel: request.thinking ?? null,
|
|
47285
|
+
desktop: request.desktop === true
|
|
46732
47286
|
},
|
|
46733
47287
|
onCreateSession: async () => {
|
|
46734
47288
|
const { session: freshSession, extensionsResult: freshExtensionsResult } = await pi.createAgentSession({
|
|
@@ -46747,6 +47301,9 @@ async function main() {
|
|
|
46747
47301
|
session: freshSession,
|
|
46748
47302
|
modelRegistry
|
|
46749
47303
|
});
|
|
47304
|
+
await freshSession.bindExtensions({
|
|
47305
|
+
uiContext: extensionUIBridge.createUIContext()
|
|
47306
|
+
});
|
|
46750
47307
|
return createServerSessionAdapter({
|
|
46751
47308
|
session: freshSession,
|
|
46752
47309
|
extensionsResult: freshExtensionsResult
|
|
@@ -46779,6 +47336,9 @@ async function main() {
|
|
|
46779
47336
|
session: resumedSession,
|
|
46780
47337
|
modelRegistry
|
|
46781
47338
|
});
|
|
47339
|
+
await resumedSession.bindExtensions({
|
|
47340
|
+
uiContext: extensionUIBridge.createUIContext()
|
|
47341
|
+
});
|
|
46782
47342
|
return createServerSessionAdapter({
|
|
46783
47343
|
session: resumedSession,
|
|
46784
47344
|
extensionsResult: resumedExtensionsResult
|