@docyrus/docyrus 0.0.60 → 0.0.63

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/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();
@@ -44534,13 +44829,41 @@ async function createAgentServer(params) {
44534
44829
  const encoder = new TextEncoder();
44535
44830
  const stream = new ReadableStream({
44536
44831
  start(controller) {
44832
+ let closed = false;
44537
44833
  function writeChunk(chunk) {
44538
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
44834
+ if (closed) {
44835
+ return;
44836
+ }
44837
+ try {
44838
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
44539
44839
 
44540
44840
  `));
44841
+ } catch {
44842
+ closed = true;
44843
+ }
44844
+ }
44845
+ function closeController() {
44846
+ if (closed) {
44847
+ return;
44848
+ }
44849
+ closed = true;
44850
+ try {
44851
+ controller.close();
44852
+ } catch {
44853
+ }
44541
44854
  }
44542
44855
  writeChunk({ type: "start" });
44543
44856
  writeChunk({ type: "start-step" });
44857
+ let extensionUICleanup;
44858
+ if (extensionUIBridge) {
44859
+ extensionUIBridge.setRequestHandler((request) => {
44860
+ writeChunk(request);
44861
+ });
44862
+ extensionUICleanup = () => {
44863
+ extensionUIBridge.setRequestHandler(() => {
44864
+ });
44865
+ };
44866
+ }
44544
44867
  const bridge = createEventBridge({
44545
44868
  messageId,
44546
44869
  onChunk: writeChunk,
@@ -44548,15 +44871,17 @@ async function createAgentServer(params) {
44548
44871
  pendingAskUserRequests.set(sessionId, { toolCallId, request });
44549
44872
  },
44550
44873
  onDone: () => {
44874
+ extensionUICleanup?.();
44551
44875
  writeChunk({ type: "finish-step" });
44552
44876
  writeChunk({ type: "finish" });
44553
- controller.close();
44877
+ closeController();
44554
44878
  },
44555
44879
  onError: (errorText) => {
44880
+ extensionUICleanup?.();
44556
44881
  writeChunk({ type: "error", errorText });
44557
44882
  writeChunk({ type: "finish-step" });
44558
44883
  writeChunk({ type: "finish" });
44559
- controller.close();
44884
+ closeController();
44560
44885
  }
44561
44886
  });
44562
44887
  const unsubscribe = activeSession.subscribe((event) => {
@@ -44568,17 +44893,19 @@ async function createAgentServer(params) {
44568
44893
  activeSession.prompt(promptText).then(() => {
44569
44894
  if (!activeSession.isStreaming) {
44570
44895
  unsubscribe();
44896
+ extensionUICleanup?.();
44571
44897
  writeChunk({ type: "finish-step" });
44572
44898
  writeChunk({ type: "finish" });
44573
- controller.close();
44899
+ closeController();
44574
44900
  }
44575
44901
  }).catch((error48) => {
44576
44902
  const errorMessage = error48 instanceof Error ? error48.message : String(error48);
44903
+ extensionUICleanup?.();
44577
44904
  writeChunk({ type: "error", errorText: errorMessage });
44578
44905
  writeChunk({ type: "finish-step" });
44579
44906
  writeChunk({ type: "finish" });
44580
44907
  unsubscribe();
44581
- controller.close();
44908
+ closeController();
44582
44909
  });
44583
44910
  }
44584
44911
  });
@@ -44723,7 +45050,8 @@ async function createAgentServer(params) {
44723
45050
  agentDir: context.agentDir,
44724
45051
  version: context.version,
44725
45052
  sessionDir: context.sessionDir,
44726
- thinkingLevel: context.thinkingLevel
45053
+ thinkingLevel: context.thinkingLevel,
45054
+ desktop: context.desktop
44727
45055
  });
44728
45056
  });
44729
45057
  app.get("/api/models", (c) => {
@@ -45412,6 +45740,59 @@ async function createAgentServer(params) {
45412
45740
  return c.json({ error: message }, 400);
45413
45741
  }
45414
45742
  });
45743
+ app.post("/api/fs/upload", async (c) => {
45744
+ const MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
45745
+ const contentLength = parseInt(c.req.header("content-length") || "0", 10);
45746
+ if (contentLength > MAX_UPLOAD_SIZE) {
45747
+ return c.json({ error: `File too large. Maximum upload size is ${MAX_UPLOAD_SIZE / 1024 / 1024} MB` }, 413);
45748
+ }
45749
+ const contentType = c.req.header("content-type") || "";
45750
+ if (contentType.includes("multipart/form-data")) {
45751
+ const formData = await c.req.formData();
45752
+ const file2 = formData.get("file");
45753
+ const targetPath2 = formData.get("path");
45754
+ if (!file2 || !(file2 instanceof File)) {
45755
+ return c.json({ error: "Missing required form field: file" }, 400);
45756
+ }
45757
+ if (file2.size > MAX_UPLOAD_SIZE) {
45758
+ return c.json({ error: `File too large (${(file2.size / 1024 / 1024).toFixed(1)} MB). Maximum is ${MAX_UPLOAD_SIZE / 1024 / 1024} MB` }, 413);
45759
+ }
45760
+ const destPath = typeof targetPath2 === "string" && targetPath2 ? targetPath2 : file2.name;
45761
+ try {
45762
+ const sanitized = sanitizeUploadPath(destPath);
45763
+ const resolved = resolveSafePath(context.cwd, sanitized);
45764
+ await assertNotSymlink(resolved);
45765
+ await (0, import_promises18.mkdir)((0, import_node_path22.join)(resolved, ".."), { recursive: true });
45766
+ const buffer = Buffer.from(await file2.arrayBuffer());
45767
+ await (0, import_promises18.writeFile)(resolved, buffer);
45768
+ const fileStat = await (0, import_promises18.stat)(resolved);
45769
+ return c.json({ ok: true, path: sanitized, name: file2.name, size: fileStat.size, type: file2.type });
45770
+ } catch (error48) {
45771
+ const message = error48 instanceof Error ? error48.message : String(error48);
45772
+ return c.json({ error: message }, 400);
45773
+ }
45774
+ }
45775
+ const targetPath = c.req.query("path") || c.req.header("x-file-path");
45776
+ if (!targetPath) {
45777
+ return c.json({ error: "Missing target path. Use multipart form field 'path', query param 'path', or header 'X-File-Path'" }, 400);
45778
+ }
45779
+ try {
45780
+ const sanitized = sanitizeUploadPath(targetPath);
45781
+ const resolved = resolveSafePath(context.cwd, sanitized);
45782
+ await assertNotSymlink(resolved);
45783
+ await (0, import_promises18.mkdir)((0, import_node_path22.join)(resolved, ".."), { recursive: true });
45784
+ const buffer = Buffer.from(await c.req.arrayBuffer());
45785
+ if (buffer.length > MAX_UPLOAD_SIZE) {
45786
+ return c.json({ error: `File too large (${(buffer.length / 1024 / 1024).toFixed(1)} MB). Maximum is ${MAX_UPLOAD_SIZE / 1024 / 1024} MB` }, 413);
45787
+ }
45788
+ await (0, import_promises18.writeFile)(resolved, buffer);
45789
+ const fileStat = await (0, import_promises18.stat)(resolved);
45790
+ return c.json({ ok: true, path: sanitized, size: fileStat.size });
45791
+ } catch (error48) {
45792
+ const message = error48 instanceof Error ? error48.message : String(error48);
45793
+ return c.json({ error: message }, 400);
45794
+ }
45795
+ });
45415
45796
  app.post("/api/fs/mkdir", async (c) => {
45416
45797
  const body2 = await c.req.json();
45417
45798
  if (!body2.path) {
@@ -46064,6 +46445,20 @@ async function createAgentServer(params) {
46064
46445
  return c.json({ error: message }, 500);
46065
46446
  }
46066
46447
  });
46448
+ app.post("/api/extension-ui-response", async (c) => {
46449
+ if (!extensionUIBridge) {
46450
+ return c.json({ error: "Extension UI bridge not available" }, 404);
46451
+ }
46452
+ const body2 = await c.req.json();
46453
+ if (!body2.id) {
46454
+ return c.json({ error: "Missing required field: id" }, 400);
46455
+ }
46456
+ const handled = extensionUIBridge.handleResponse(body2);
46457
+ if (!handled) {
46458
+ return c.json({ error: `No pending extension UI request for id: ${body2.id}` }, 404);
46459
+ }
46460
+ return c.json({ ok: true });
46461
+ });
46067
46462
  app.get("/api/tools", async (c) => {
46068
46463
  try {
46069
46464
  const tools = await listAllTools({
@@ -46071,11 +46466,15 @@ async function createAgentServer(params) {
46071
46466
  agentDir: context.agentDir,
46072
46467
  cwd: context.cwd
46073
46468
  });
46469
+ if (context.desktop) {
46470
+ tools.push(...BROWSER_TOOL_SCHEMAS);
46471
+ }
46074
46472
  const builtInCount = tools.filter((t) => t.source === "built-in").length;
46075
46473
  const mcpCount = tools.filter((t) => t.source !== "built-in").length;
46076
46474
  return c.json({
46077
46475
  tools,
46078
- summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length }
46476
+ summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length },
46477
+ clientSideToolPrefixes: context.desktop ? [BROWSER_TOOL_PREFIX] : []
46079
46478
  });
46080
46479
  } catch (error48) {
46081
46480
  const message = error48 instanceof Error ? error48.message : String(error48);
@@ -46267,6 +46666,8 @@ async function createAgentServer(params) {
46267
46666
  process.stderr.write(` GET /api/fs/read \u2014 read file contents
46268
46667
  `);
46269
46668
  process.stderr.write(` POST /api/fs/write \u2014 write file
46669
+ `);
46670
+ process.stderr.write(` POST /api/fs/upload \u2014 upload file (multipart or raw)
46270
46671
  `);
46271
46672
  process.stderr.write(` POST /api/fs/mkdir \u2014 create directory
46272
46673
  `);
@@ -46298,6 +46699,12 @@ async function createAgentServer(params) {
46298
46699
  `);
46299
46700
  process.stderr.write(` GET /api/tools \u2014 list all tools
46300
46701
  `);
46702
+ if (context.desktop) {
46703
+ process.stderr.write(` POST /api/extension-ui-response \u2014 submit extension UI response
46704
+ `);
46705
+ process.stderr.write(` Browser tools: docyrus_browser_* (client-side via extension_ui)
46706
+ `);
46707
+ }
46301
46708
  process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
46302
46709
  `);
46303
46710
  process.stderr.write(` WS /api/terminal \u2014 PTY terminal (WebSocket)
@@ -46493,6 +46900,163 @@ function createServerSessionAdapter(params) {
46493
46900
  };
46494
46901
  }
46495
46902
 
46903
+ // src/server/extensionUIBridge.ts
46904
+ var import_node_crypto7 = require("node:crypto");
46905
+ var ExtensionUIBridge = class {
46906
+ pending = /* @__PURE__ */ new Map();
46907
+ requestHandler;
46908
+ defaultTimeout;
46909
+ constructor(options) {
46910
+ this.requestHandler = options.onRequest;
46911
+ this.defaultTimeout = options.defaultTimeout ?? 12e4;
46912
+ }
46913
+ /**
46914
+ * Update the request handler. Used to wire into the active chat stream.
46915
+ */
46916
+ setRequestHandler(handler2) {
46917
+ this.requestHandler = handler2;
46918
+ }
46919
+ /**
46920
+ * Handle an incoming extension_ui_response from the client.
46921
+ * Returns true if the response matched a pending request.
46922
+ */
46923
+ handleResponse(response) {
46924
+ const entry = this.pending.get(response.id);
46925
+ if (!entry) {
46926
+ return false;
46927
+ }
46928
+ if (entry.timer) {
46929
+ clearTimeout(entry.timer);
46930
+ }
46931
+ this.pending.delete(response.id);
46932
+ entry.resolve(response);
46933
+ return true;
46934
+ }
46935
+ /**
46936
+ * Check if there are any pending requests.
46937
+ */
46938
+ hasPending() {
46939
+ return this.pending.size > 0;
46940
+ }
46941
+ /**
46942
+ * Create a dialog promise that sends a request and waits for a response.
46943
+ */
46944
+ createDialogPromise(request, defaultValue, parseResponse, opts) {
46945
+ if (opts?.signal?.aborted) {
46946
+ return Promise.resolve(defaultValue);
46947
+ }
46948
+ const id = (0, import_node_crypto7.randomUUID)();
46949
+ const timeout = opts?.timeout ?? this.defaultTimeout;
46950
+ return new Promise((resolve4, reject) => {
46951
+ const cleanup = () => {
46952
+ if (timer) {
46953
+ clearTimeout(timer);
46954
+ }
46955
+ opts?.signal?.removeEventListener("abort", onAbort);
46956
+ this.pending.delete(id);
46957
+ };
46958
+ const onAbort = () => {
46959
+ cleanup();
46960
+ resolve4(defaultValue);
46961
+ };
46962
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
46963
+ const timer = timeout > 0 ? setTimeout(() => {
46964
+ cleanup();
46965
+ resolve4(defaultValue);
46966
+ }, timeout) : void 0;
46967
+ this.pending.set(id, {
46968
+ resolve: (response) => {
46969
+ cleanup();
46970
+ resolve4(parseResponse(response));
46971
+ },
46972
+ reject: (error48) => {
46973
+ cleanup();
46974
+ reject(error48);
46975
+ },
46976
+ timer
46977
+ });
46978
+ this.requestHandler({ type: "extension_ui_request", id, ...request });
46979
+ });
46980
+ }
46981
+ /**
46982
+ * Build an ExtensionUIContext compatible with pi agent's extension system.
46983
+ */
46984
+ createUIContext() {
46985
+ return {
46986
+ select: (title, options, opts) => this.createDialogPromise(
46987
+ { method: "select", title, options, timeout: opts?.timeout },
46988
+ void 0,
46989
+ (r) => "cancelled" in r && r.cancelled ? void 0 : r.value,
46990
+ opts
46991
+ ),
46992
+ confirm: (title, message, opts) => this.createDialogPromise(
46993
+ { method: "confirm", title, message, timeout: opts?.timeout },
46994
+ false,
46995
+ (r) => "cancelled" in r && r.cancelled ? false : r.confirmed ?? false,
46996
+ opts
46997
+ ),
46998
+ input: (title, placeholder, opts) => this.createDialogPromise(
46999
+ { method: "input", title, placeholder, timeout: opts?.timeout },
47000
+ void 0,
47001
+ (r) => "cancelled" in r && r.cancelled ? void 0 : r.value,
47002
+ opts
47003
+ ),
47004
+ editor: (title, prefill, opts) => this.createDialogPromise(
47005
+ { method: "editor", title, placeholder: prefill },
47006
+ void 0,
47007
+ (r) => "cancelled" in r && r.cancelled ? void 0 : r.value,
47008
+ opts
47009
+ ),
47010
+ notify: (message, notifyType) => {
47011
+ const id = (0, import_node_crypto7.randomUUID)();
47012
+ this.requestHandler({ type: "extension_ui_request", id, method: "notify", message, placeholder: notifyType });
47013
+ },
47014
+ setStatus: (statusKey, statusText) => {
47015
+ const id = (0, import_node_crypto7.randomUUID)();
47016
+ this.requestHandler({ type: "extension_ui_request", id, method: "setStatus", title: statusKey, message: statusText });
47017
+ },
47018
+ setWidget: (widgetKey, widgetLines) => {
47019
+ const id = (0, import_node_crypto7.randomUUID)();
47020
+ this.requestHandler({ type: "extension_ui_request", id, method: "setWidget", title: widgetKey, options: widgetLines });
47021
+ },
47022
+ setTitle: (title) => {
47023
+ const id = (0, import_node_crypto7.randomUUID)();
47024
+ this.requestHandler({ type: "extension_ui_request", id, method: "setTitle", title });
47025
+ },
47026
+ setEditorText: (text3) => {
47027
+ const id = (0, import_node_crypto7.randomUUID)();
47028
+ this.requestHandler({ type: "extension_ui_request", id, method: "set_editor_text", message: text3 });
47029
+ },
47030
+ // No-op methods for server mode (not applicable without TUI)
47031
+ setWorkingMessage: () => {
47032
+ },
47033
+ setHiddenThinkingLabel: () => {
47034
+ },
47035
+ setFooter: () => {
47036
+ },
47037
+ setHeader: () => {
47038
+ },
47039
+ onTerminalInput: () => () => {
47040
+ },
47041
+ custom: async () => void 0,
47042
+ pasteToEditor: () => {
47043
+ },
47044
+ getEditorText: () => "",
47045
+ setEditorComponent: () => {
47046
+ },
47047
+ get theme() {
47048
+ return { name: "default" };
47049
+ },
47050
+ getAllThemes: () => [],
47051
+ getTheme: () => void 0,
47052
+ setTheme: () => ({ success: false, error: "UI not available in server mode" }),
47053
+ getToolsExpanded: () => false,
47054
+ setToolsExpanded: () => {
47055
+ }
47056
+ };
47057
+ }
47058
+ };
47059
+
46496
47060
  // src/services/spinner.ts
46497
47061
  var import_picocolors = __toESM(require_picocolors());
46498
47062
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -46707,8 +47271,17 @@ async function main() {
46707
47271
  `
46708
47272
  );
46709
47273
  }
47274
+ const extensionUIBridge = new ExtensionUIBridge({
47275
+ onRequest: () => {
47276
+ }
47277
+ // Will be wired up by agentServer after the chat stream is created
47278
+ });
47279
+ await session.bindExtensions({
47280
+ uiContext: extensionUIBridge.createUIContext()
47281
+ });
46710
47282
  await createAgentServer({
46711
47283
  session: createServerSessionAdapter({ session, extensionsResult }),
47284
+ extensionUIBridge,
46712
47285
  authToken: request.auth,
46713
47286
  port: request.port,
46714
47287
  sessionManager: {
@@ -46728,7 +47301,8 @@ async function main() {
46728
47301
  agentDir,
46729
47302
  version: version2,
46730
47303
  sessionDir: request.sessionDir ?? null,
46731
- thinkingLevel: request.thinking ?? null
47304
+ thinkingLevel: request.thinking ?? null,
47305
+ desktop: request.desktop === true
46732
47306
  },
46733
47307
  onCreateSession: async () => {
46734
47308
  const { session: freshSession, extensionsResult: freshExtensionsResult } = await pi.createAgentSession({
@@ -46747,6 +47321,9 @@ async function main() {
46747
47321
  session: freshSession,
46748
47322
  modelRegistry
46749
47323
  });
47324
+ await freshSession.bindExtensions({
47325
+ uiContext: extensionUIBridge.createUIContext()
47326
+ });
46750
47327
  return createServerSessionAdapter({
46751
47328
  session: freshSession,
46752
47329
  extensionsResult: freshExtensionsResult
@@ -46779,6 +47356,9 @@ async function main() {
46779
47356
  session: resumedSession,
46780
47357
  modelRegistry
46781
47358
  });
47359
+ await resumedSession.bindExtensions({
47360
+ uiContext: extensionUIBridge.createUIContext()
47361
+ });
46782
47362
  return createServerSessionAdapter({
46783
47363
  session: resumedSession,
46784
47364
  extensionsResult: resumedExtensionsResult