@docyrus/docyrus 0.0.59 → 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.
Files changed (45) hide show
  1. package/README.md +46 -0
  2. package/agent-loader.js +1 -1
  3. package/agent-loader.js.map +2 -2
  4. package/main.js +321 -25
  5. package/main.js.map +2 -2
  6. package/package.json +1 -1
  7. package/resources/browser-tools/browser-click.js +74 -0
  8. package/resources/browser-tools/browser-client.js +236 -0
  9. package/resources/browser-tools/browser-close.js +19 -0
  10. package/resources/browser-tools/browser-console.js +73 -0
  11. package/resources/browser-tools/browser-content.js +36 -75
  12. package/resources/browser-tools/browser-cookies.js +19 -14
  13. package/resources/browser-tools/browser-daemon.js +452 -0
  14. package/resources/browser-tools/browser-devtools.js +62 -0
  15. package/resources/browser-tools/browser-eval.js +16 -22
  16. package/resources/browser-tools/browser-fill.js +70 -0
  17. package/resources/browser-tools/browser-info.js +13 -0
  18. package/resources/browser-tools/browser-nav.js +21 -22
  19. package/resources/browser-tools/browser-network.js +91 -0
  20. package/resources/browser-tools/browser-run-script.js +12 -30
  21. package/resources/browser-tools/browser-screenshot.js +22 -22
  22. package/resources/browser-tools/browser-select.js +59 -0
  23. package/resources/browser-tools/browser-snapshot.js +100 -0
  24. package/resources/browser-tools/browser-start.js +101 -85
  25. package/resources/browser-tools/browser-tabs.js +38 -0
  26. package/resources/browser-tools/browser-wait.js +50 -0
  27. package/resources/pi-agent/extensions/browser-tools.ts +229 -0
  28. package/resources/pi-agent/skills/docyrus-chrome-devtools-cli/SKILL.md +157 -46
  29. package/server-loader.js +580 -232
  30. package/server-loader.js.map +4 -4
  31. package/resources/browser-tools/browser-connect.js +0 -172
  32. package/resources/browser-tools/browser-pick.js +0 -143
  33. package/resources/pi-agent/extensions/docyrus-web-browser.ts +0 -31
  34. package/resources/pi-agent/shared/docyrusWebBrowserProtocol.ts +0 -169
  35. package/resources/pi-agent/skills/agent-browser/SKILL.md +0 -779
  36. package/resources/pi-agent/skills/agent-browser/references/authentication.md +0 -303
  37. package/resources/pi-agent/skills/agent-browser/references/commands.md +0 -295
  38. package/resources/pi-agent/skills/agent-browser/references/profiling.md +0 -120
  39. package/resources/pi-agent/skills/agent-browser/references/proxy-support.md +0 -194
  40. package/resources/pi-agent/skills/agent-browser/references/session-management.md +0 -193
  41. package/resources/pi-agent/skills/agent-browser/references/snapshot-refs.md +0 -219
  42. package/resources/pi-agent/skills/agent-browser/references/video-recording.md +0 -173
  43. package/resources/pi-agent/skills/agent-browser/templates/authenticated-session.sh +0 -105
  44. package/resources/pi-agent/skills/agent-browser/templates/capture-workflow.sh +0 -69
  45. package/resources/pi-agent/skills/agent-browser/templates/form-automation.sh +0 -62
package/server-loader.js CHANGED
@@ -13610,7 +13610,7 @@ __export(graph_exports2, {
13610
13610
  upsertProjectPlanTask: () => upsertProjectPlanTask,
13611
13611
  validateProjectPlanGraph: () => validateProjectPlanGraph
13612
13612
  });
13613
- function isRecord3(value2) {
13613
+ function isRecord2(value2) {
13614
13614
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
13615
13615
  }
13616
13616
  function isNonEmptyString2(value2) {
@@ -13721,14 +13721,14 @@ function migrateV1ToV2(v1) {
13721
13721
  return { version: 2, projectVersion: "0.1.0", phases, features, tasks };
13722
13722
  }
13723
13723
  function parseV1Graph(rawValue) {
13724
- const sections = Array.isArray(rawValue.sections) ? rawValue.sections.filter((value2) => isRecord3(value2)).map((value2) => ({
13724
+ const sections = Array.isArray(rawValue.sections) ? rawValue.sections.filter((value2) => isRecord2(value2)).map((value2) => ({
13725
13725
  id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
13726
13726
  title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
13727
13727
  slug: isNonEmptyString2(value2.slug) ? value2.slug.trim() : "",
13728
13728
  summary: typeof value2.summary === "string" ? value2.summary.trim() : "",
13729
13729
  ...typeof value2.order === "number" ? { order: value2.order } : {}
13730
13730
  })).filter((value2) => value2.id.length > 0) : [];
13731
- const features = Array.isArray(rawValue.features) ? rawValue.features.filter((value2) => isRecord3(value2)).map((value2) => ({
13731
+ const features = Array.isArray(rawValue.features) ? rawValue.features.filter((value2) => isRecord2(value2)).map((value2) => ({
13732
13732
  id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
13733
13733
  title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
13734
13734
  slug: isNonEmptyString2(value2.slug) ? value2.slug.trim() : "",
@@ -13736,7 +13736,7 @@ function parseV1Graph(rawValue) {
13736
13736
  sectionId: isNonEmptyString2(value2.sectionId) ? value2.sectionId.trim() : "",
13737
13737
  ...typeof value2.order === "number" ? { order: value2.order } : {}
13738
13738
  })).filter((value2) => value2.id.length > 0) : [];
13739
- const tasks = Array.isArray(rawValue.tasks) ? rawValue.tasks.filter((value2) => isRecord3(value2)).map((value2) => ({
13739
+ const tasks = Array.isArray(rawValue.tasks) ? rawValue.tasks.filter((value2) => isRecord2(value2)).map((value2) => ({
13740
13740
  id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
13741
13741
  title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
13742
13742
  summary: typeof value2.summary === "string" ? value2.summary.trim() : "",
@@ -13752,7 +13752,7 @@ function parseV1Graph(rawValue) {
13752
13752
  }
13753
13753
  function parseV2Graph(rawValue) {
13754
13754
  const projectVersion = isNonEmptyString2(rawValue.projectVersion) ? rawValue.projectVersion.trim() : "0.1.0";
13755
- const phases = Array.isArray(rawValue.phases) ? rawValue.phases.filter((value2) => isRecord3(value2)).map((value2) => ({
13755
+ const phases = Array.isArray(rawValue.phases) ? rawValue.phases.filter((value2) => isRecord2(value2)).map((value2) => ({
13756
13756
  id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
13757
13757
  title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
13758
13758
  slug: isNonEmptyString2(value2.slug) ? value2.slug.trim() : "",
@@ -13760,7 +13760,7 @@ function parseV2Graph(rawValue) {
13760
13760
  ...isNonEmptyString2(value2.projectVersion) ? { projectVersion: value2.projectVersion.trim() } : {},
13761
13761
  ...typeof value2.order === "number" ? { order: value2.order } : {}
13762
13762
  })).filter((value2) => value2.id.length > 0) : [];
13763
- const features = Array.isArray(rawValue.features) ? rawValue.features.filter((value2) => isRecord3(value2)).map((value2) => ({
13763
+ const features = Array.isArray(rawValue.features) ? rawValue.features.filter((value2) => isRecord2(value2)).map((value2) => ({
13764
13764
  id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
13765
13765
  title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
13766
13766
  slug: isNonEmptyString2(value2.slug) ? value2.slug.trim() : "",
@@ -13770,7 +13770,7 @@ function parseV2Graph(rawValue) {
13770
13770
  ...isNonEmptyString2(value2.projectVersion) ? { projectVersion: value2.projectVersion.trim() } : {},
13771
13771
  ...typeof value2.order === "number" ? { order: value2.order } : {}
13772
13772
  })).filter((value2) => value2.id.length > 0) : [];
13773
- const tasks = Array.isArray(rawValue.tasks) ? rawValue.tasks.filter((value2) => isRecord3(value2)).map((value2) => ({
13773
+ const tasks = Array.isArray(rawValue.tasks) ? rawValue.tasks.filter((value2) => isRecord2(value2)).map((value2) => ({
13774
13774
  id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
13775
13775
  title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
13776
13776
  summary: typeof value2.summary === "string" ? value2.summary.trim() : "",
@@ -13785,7 +13785,7 @@ function parseV2Graph(rawValue) {
13785
13785
  return sortGraph({ version: 2, projectVersion, phases, features, tasks });
13786
13786
  }
13787
13787
  function parseProjectPlanGraph(rawValue) {
13788
- if (!isRecord3(rawValue)) {
13788
+ if (!isRecord2(rawValue)) {
13789
13789
  return createEmptyProjectPlanGraph();
13790
13790
  }
13791
13791
  if (rawValue.version === 2) {
@@ -18724,7 +18724,7 @@ var AgentEnvStore = class {
18724
18724
  // src/agent/packagedExtensions.ts
18725
18725
  var import_node_fs = require("node:fs");
18726
18726
  var import_node_path2 = require("node:path");
18727
- var SERVER_ONLY_PACKAGED_EXTENSION_NAMES = /* @__PURE__ */ new Set(["docyrus-web-browser", "server-auto-commit"]);
18727
+ var SERVER_ONLY_PACKAGED_EXTENSION_NAMES = /* @__PURE__ */ new Set(["server-auto-commit"]);
18728
18728
  function isPackagedExtensionEntry(entryName, isDirectory) {
18729
18729
  return isDirectory || entryName.endsWith(".ts") || entryName.endsWith(".js");
18730
18730
  }
@@ -35486,122 +35486,9 @@ function parseAskUserResponseFromToolOutput(output) {
35486
35486
  return normalizeAskUserResponse(output);
35487
35487
  }
35488
35488
 
35489
- // resources/pi-agent/shared/docyrusWebBrowserProtocol.ts
35490
- var DOCYRUS_WEB_BROWSER_TAG = "docyrus_web_browser";
35491
- var DOCYRUS_WEB_BROWSER_OPEN = `<${DOCYRUS_WEB_BROWSER_TAG}>`;
35492
- var DOCYRUS_WEB_BROWSER_CLOSE = `</${DOCYRUS_WEB_BROWSER_TAG}>`;
35493
- var DOCYRUS_WEB_BROWSER_RESULT_TAG = "docyrus_web_browser_result";
35494
- var DOCYRUS_WEB_BROWSER_RESULT_OPEN = `<${DOCYRUS_WEB_BROWSER_RESULT_TAG}>`;
35495
- var DOCYRUS_WEB_BROWSER_RESULT_CLOSE = `</${DOCYRUS_WEB_BROWSER_RESULT_TAG}>`;
35496
- var WEB_PREVIEW_CONTEXT_TOOL = "web_preview_context";
35497
- var WEB_PREVIEW_PLAYWRIGHT_TOOL = "web_preview_playwright";
35498
- var DOCYRUS_WEB_BROWSER_TOOL_NAMES = [
35499
- WEB_PREVIEW_CONTEXT_TOOL,
35500
- WEB_PREVIEW_PLAYWRIGHT_TOOL
35501
- ];
35502
- var DOCYRUS_WEB_BROWSER_CLIENT_TOOLS = [
35503
- {
35504
- name: WEB_PREVIEW_CONTEXT_TOOL,
35505
- description: "Inspect the current Docyrus web preview state before automation. Prefer this first when preview availability or bridge state is unknown.",
35506
- inputSchema: {
35507
- type: "object",
35508
- properties: {
35509
- includeSnapshot: { type: "boolean" }
35510
- },
35511
- additionalProperties: false
35512
- }
35513
- },
35514
- {
35515
- name: WEB_PREVIEW_PLAYWRIGHT_TOOL,
35516
- description: "Run Playwright-style actions against the Docyrus web preview. Prefer structured steps over raw scripts.",
35517
- inputSchema: {
35518
- type: "object",
35519
- properties: {
35520
- script: { type: "string" },
35521
- steps: {
35522
- type: "array",
35523
- items: {
35524
- type: "object",
35525
- properties: {
35526
- action: { type: "string" },
35527
- selector: { type: "string" },
35528
- url: { type: "string" },
35529
- value: { type: "string" },
35530
- values: {
35531
- type: "array",
35532
- items: { type: "string" }
35533
- },
35534
- key: { type: "string" },
35535
- attribute: { type: "string" },
35536
- timeoutMs: { type: "number" },
35537
- state: { type: "string" }
35538
- },
35539
- additionalProperties: true
35540
- }
35541
- },
35542
- timeoutMs: { type: "number" },
35543
- stopOnError: { type: "boolean" }
35544
- },
35545
- additionalProperties: true
35546
- }
35547
- }
35548
- ];
35549
- function hashString2(value2) {
35550
- let hash2 = 0;
35551
- for (let index2 = 0; index2 < value2.length; index2 += 1) {
35552
- hash2 = (hash2 << 5) - hash2 + value2.charCodeAt(index2);
35553
- hash2 |= 0;
35554
- }
35555
- return Math.abs(hash2).toString(36);
35556
- }
35557
- function isRecord2(value2) {
35558
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
35559
- }
35560
- function isDocyrusWebBrowserToolName(value2) {
35561
- return DOCYRUS_WEB_BROWSER_TOOL_NAMES.some((toolName) => toolName === value2);
35562
- }
35563
- function normalizeDocyrusWebBrowserToolRequest(value2) {
35564
- if (!isRecord2(value2) || !isDocyrusWebBrowserToolName(value2.tool)) {
35565
- return void 0;
35566
- }
35567
- return {
35568
- tool: value2.tool,
35569
- input: isRecord2(value2.input) ? value2.input : void 0
35570
- };
35571
- }
35572
- function createDocyrusWebBrowserToolCallId(request) {
35573
- return `docyrus_web_browser_${hashString2(JSON.stringify(request))}`;
35574
- }
35575
- function parseDocyrusWebBrowserRequestFromText(text3) {
35576
- const trimmed = text3.trim();
35577
- if (!trimmed.startsWith(DOCYRUS_WEB_BROWSER_OPEN) || !trimmed.endsWith(DOCYRUS_WEB_BROWSER_CLOSE)) {
35578
- return void 0;
35579
- }
35580
- const body2 = trimmed.slice(DOCYRUS_WEB_BROWSER_OPEN.length, trimmed.length - DOCYRUS_WEB_BROWSER_CLOSE.length).trim();
35581
- if (!body2) {
35582
- return void 0;
35583
- }
35584
- try {
35585
- return normalizeDocyrusWebBrowserToolRequest(JSON.parse(body2));
35586
- } catch {
35587
- return void 0;
35588
- }
35589
- }
35590
- function formatDocyrusWebBrowserToolResponsePrompt(response) {
35591
- return [
35592
- "The docyrus-web-browser client tool returned a result.",
35593
- "",
35594
- `${DOCYRUS_WEB_BROWSER_RESULT_OPEN}`,
35595
- JSON.stringify(response, null, 2),
35596
- `${DOCYRUS_WEB_BROWSER_RESULT_CLOSE}`,
35597
- "",
35598
- "Continue the task using this result. If the tool reported an availability or bridge blocker, explain that blocker exactly and do not claim success."
35599
- ].join("\n");
35600
- }
35601
-
35602
35489
  // src/server/eventBridge.ts
35603
35490
  function createEventBridge(params) {
35604
- const { messageId, onChunk, onDone, onError, onAskUser, onDocyrusWebBrowserTool } = params;
35491
+ const { messageId, onChunk, onDone, onError, onAskUser } = params;
35605
35492
  const activeToolCalls = /* @__PURE__ */ new Map();
35606
35493
  let activeTextBuffer = null;
35607
35494
  function flushBufferedTextToStream(close2) {
@@ -35637,25 +35524,8 @@ function createEventBridge(params) {
35637
35524
  });
35638
35525
  return true;
35639
35526
  }
35640
- function maybeEmitDocyrusWebBrowserTool(text3) {
35641
- const request = parseDocyrusWebBrowserRequestFromText(text3);
35642
- if (!request) {
35643
- return false;
35644
- }
35645
- const toolCallId = createDocyrusWebBrowserToolCallId(request);
35646
- onDocyrusWebBrowserTool?.({ toolCallId, request });
35647
- onChunk({ type: "tool-input-start", toolCallId, toolName: request.tool, dynamic: true });
35648
- onChunk({
35649
- type: "tool-input-available",
35650
- toolCallId,
35651
- toolName: request.tool,
35652
- input: request.input ?? {},
35653
- dynamic: true
35654
- });
35655
- return true;
35656
- }
35657
35527
  function shouldKeepBufferingDynamicToolText(text3) {
35658
- const dynamicToolTags = ["<ask_user>", "<docyrus_web_browser>"];
35528
+ const dynamicToolTags = ["<ask_user>"];
35659
35529
  return dynamicToolTags.some((tag) => tag.startsWith(text3) || text3.startsWith(tag));
35660
35530
  }
35661
35531
  function handleEvent(event) {
@@ -35712,7 +35582,7 @@ function createEventBridge(params) {
35712
35582
  activeTextBuffer = null;
35713
35583
  break;
35714
35584
  }
35715
- if (!maybeEmitAskUser(activeTextBuffer.text) && !maybeEmitDocyrusWebBrowserTool(activeTextBuffer.text)) {
35585
+ if (!maybeEmitAskUser(activeTextBuffer.text)) {
35716
35586
  flushBufferedTextToStream(true);
35717
35587
  } else {
35718
35588
  activeTextBuffer = null;
@@ -44061,7 +43931,7 @@ var BUILT_IN_TOOLS = {
44061
43931
  ]
44062
43932
  };
44063
43933
  async function listAllTools(params) {
44064
- const { profile, agentDir, cwd, includeDocyrusWebBrowser } = params;
43934
+ const { profile, agentDir, cwd } = params;
44065
43935
  const tools = [];
44066
43936
  const builtIn = BUILT_IN_TOOLS[profile] ?? BUILT_IN_TOOLS.coder;
44067
43937
  for (const tool of builtIn) {
@@ -44071,16 +43941,6 @@ async function listAllTools(params) {
44071
43941
  source: "built-in"
44072
43942
  });
44073
43943
  }
44074
- if (includeDocyrusWebBrowser) {
44075
- for (const tool of DOCYRUS_WEB_BROWSER_CLIENT_TOOLS) {
44076
- tools.push({
44077
- name: tool.name,
44078
- description: tool.description,
44079
- source: "built-in",
44080
- inputSchema: tool.inputSchema
44081
- });
44082
- }
44083
- }
44084
43944
  let config2;
44085
43945
  let cache;
44086
43946
  try {
@@ -44126,6 +43986,199 @@ async function listAllTools(params) {
44126
43986
  return tools;
44127
43987
  }
44128
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
+
44129
44182
  // src/server/sessionConfig.ts
44130
44183
  var import_node_fs12 = require("node:fs");
44131
44184
  var path5 = __toESM(require("node:path"));
@@ -44201,45 +44254,6 @@ function extractAskUserToolResponse(messages) {
44201
44254
  }
44202
44255
  return void 0;
44203
44256
  }
44204
- function extractDocyrusWebBrowserToolResponse(messages) {
44205
- const message = getLastAssistantMessage(messages);
44206
- if (!message) {
44207
- return void 0;
44208
- }
44209
- for (let partIndex = message.parts.length - 1; partIndex >= 0; partIndex -= 1) {
44210
- const part = message.parts[partIndex];
44211
- if (part.type === "dynamic-tool" && isDocyrusWebBrowserToolName(part.toolName) && typeof part.toolCallId === "string" && (part.state === "output-available" || part.state === "output-error")) {
44212
- const errorText = typeof part.errorText === "string" ? part.errorText : typeof part.output === "string" && part.state === "output-error" ? part.output : void 0;
44213
- return {
44214
- toolCallId: part.toolCallId,
44215
- toolName: part.toolName,
44216
- output: part.state === "output-available" ? part.output : void 0,
44217
- errorText,
44218
- isError: part.state === "output-error"
44219
- };
44220
- }
44221
- }
44222
- return void 0;
44223
- }
44224
- function extractDocyrusWebBrowserToolRequest(messages, toolCallId) {
44225
- const message = getLastAssistantMessage(messages);
44226
- if (!message) {
44227
- return void 0;
44228
- }
44229
- for (let partIndex = message.parts.length - 1; partIndex >= 0; partIndex -= 1) {
44230
- const part = message.parts[partIndex];
44231
- if (part.type === "dynamic-tool" && isDocyrusWebBrowserToolName(part.toolName) && part.toolCallId === toolCallId) {
44232
- const request = normalizeDocyrusWebBrowserToolRequest({
44233
- tool: part.toolName,
44234
- input: part.input
44235
- });
44236
- if (request) {
44237
- return request;
44238
- }
44239
- }
44240
- }
44241
- return void 0;
44242
- }
44243
44257
  function normalizeSlashCommands(commands) {
44244
44258
  const seen = /* @__PURE__ */ new Set();
44245
44259
  return commands.filter((command) => typeof command.name === "string" && command.name.trim().length > 0).sort((left, right) => left.name.localeCompare(right.name)).filter((command) => {
@@ -44473,17 +44487,6 @@ function convertSessionEntriesToUIMessages(entries) {
44473
44487
  });
44474
44488
  continue;
44475
44489
  }
44476
- const docyrusWebBrowserRequest = parseDocyrusWebBrowserRequestFromText(block.text);
44477
- if (docyrusWebBrowserRequest) {
44478
- parts2.push({
44479
- type: "dynamic-tool",
44480
- toolCallId: createDocyrusWebBrowserToolCallId(docyrusWebBrowserRequest),
44481
- toolName: docyrusWebBrowserRequest.tool,
44482
- state: "input-available",
44483
- input: docyrusWebBrowserRequest.input ?? {}
44484
- });
44485
- continue;
44486
- }
44487
44490
  parts2.push({ type: "text", text: block.text });
44488
44491
  } else if (block.type === "thinking" && typeof block.thinking === "string") {
44489
44492
  parts2.push({ type: "reasoning", text: block.thinking, state: "complete" });
@@ -44535,6 +44538,107 @@ function resolveSafePath(cwd, requestPath) {
44535
44538
  }
44536
44539
  return resolved;
44537
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
+ }
44538
44642
  function globToRegex(pattern) {
44539
44643
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\0/g, ".*").replace(/\?/g, "[^/]");
44540
44644
  return new RegExp(`^${escaped}$`);
@@ -44615,10 +44719,10 @@ async function walkFiles(params) {
44615
44719
  }
44616
44720
  async function createAgentServer(params) {
44617
44721
  const { port, sessionManager, modelRegistry, authRuntime, context, onCreateSession, onResumeSession, authToken } = params;
44722
+ const extensionUIBridge = params.extensionUIBridge;
44618
44723
  let activeSession = params.session;
44619
44724
  let sessionMode = "normal";
44620
44725
  const pendingAskUserRequests = /* @__PURE__ */ new Map();
44621
- const pendingDocyrusWebBrowserRequests = /* @__PURE__ */ new Map();
44622
44726
  const oauthFlowManager = new OAuthFlowManager();
44623
44727
  const app = new Hono2();
44624
44728
  const settingsRootPath = resolveServerSettingsRootPath(context.agentDir);
@@ -44679,33 +44783,18 @@ async function createAgentServer(params) {
44679
44783
  }
44680
44784
  const sessionId = body2.sessionId?.trim() || activeSession.id?.trim() || "active";
44681
44785
  const askUserResponse = extractAskUserToolResponse(messages);
44682
- const docyrusWebBrowserResponse = extractDocyrusWebBrowserToolResponse(messages);
44683
44786
  const pendingAskUser = pendingAskUserRequests.get(sessionId);
44684
- const pendingDocyrusWebBrowser = pendingDocyrusWebBrowserRequests.get(sessionId);
44685
- const pendingDocyrusWebBrowserRequest = docyrusWebBrowserResponse ? pendingDocyrusWebBrowser?.get(docyrusWebBrowserResponse.toolCallId) ?? extractDocyrusWebBrowserToolRequest(messages, docyrusWebBrowserResponse.toolCallId) : void 0;
44686
- const userMessage = askUserResponse ? pendingAskUser && pendingAskUser.toolCallId === askUserResponse.toolCallId ? formatAskUserResponsePrompt(askUserResponse.response) : void 0 : docyrusWebBrowserResponse ? pendingDocyrusWebBrowserRequest ? formatDocyrusWebBrowserToolResponsePrompt({
44687
- tool: docyrusWebBrowserResponse.toolName,
44688
- request: pendingDocyrusWebBrowserRequest.input,
44689
- status: docyrusWebBrowserResponse.isError ? "error" : "success",
44690
- output: docyrusWebBrowserResponse.output,
44691
- errorText: docyrusWebBrowserResponse.errorText
44692
- }) : void 0 : extractLastUserText(messages);
44787
+ const userMessage = askUserResponse ? pendingAskUser && pendingAskUser.toolCallId === askUserResponse.toolCallId ? formatAskUserResponsePrompt(askUserResponse.response) : void 0 : extractLastUserText(messages);
44693
44788
  if (!userMessage) {
44694
44789
  return c.json({
44695
- error: askUserResponse ? "No matching pending ask_user request found" : docyrusWebBrowserResponse ? "No matching pending docyrus-web-browser request found" : "No user message found"
44790
+ error: askUserResponse ? "No matching pending ask_user request found" : "No user message found"
44696
44791
  }, 400);
44697
44792
  }
44698
44793
  if (askUserResponse) {
44699
44794
  pendingAskUserRequests.delete(sessionId);
44700
44795
  }
44701
- if (docyrusWebBrowserResponse && pendingDocyrusWebBrowser) {
44702
- pendingDocyrusWebBrowser.delete(docyrusWebBrowserResponse.toolCallId);
44703
- if (pendingDocyrusWebBrowser.size === 0) {
44704
- pendingDocyrusWebBrowserRequests.delete(sessionId);
44705
- }
44706
- }
44707
44796
  let promptText = userMessage;
44708
- if (!askUserResponse && !docyrusWebBrowserResponse) {
44797
+ if (!askUserResponse) {
44709
44798
  const modeCmd = parseModeSlashCommand(userMessage);
44710
44799
  if (modeCmd) {
44711
44800
  if (activeSession.isStreaming) {
@@ -44747,23 +44836,30 @@ async function createAgentServer(params) {
44747
44836
  }
44748
44837
  writeChunk({ type: "start" });
44749
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
+ }
44750
44849
  const bridge = createEventBridge({
44751
44850
  messageId,
44752
44851
  onChunk: writeChunk,
44753
44852
  onAskUser: ({ toolCallId, request }) => {
44754
44853
  pendingAskUserRequests.set(sessionId, { toolCallId, request });
44755
44854
  },
44756
- onDocyrusWebBrowserTool: ({ toolCallId, request }) => {
44757
- const pendingForSession = pendingDocyrusWebBrowserRequests.get(sessionId) ?? /* @__PURE__ */ new Map();
44758
- pendingForSession.set(toolCallId, request);
44759
- pendingDocyrusWebBrowserRequests.set(sessionId, pendingForSession);
44760
- },
44761
44855
  onDone: () => {
44856
+ extensionUICleanup?.();
44762
44857
  writeChunk({ type: "finish-step" });
44763
44858
  writeChunk({ type: "finish" });
44764
44859
  controller.close();
44765
44860
  },
44766
44861
  onError: (errorText) => {
44862
+ extensionUICleanup?.();
44767
44863
  writeChunk({ type: "error", errorText });
44768
44864
  writeChunk({ type: "finish-step" });
44769
44865
  writeChunk({ type: "finish" });
@@ -44934,7 +45030,8 @@ async function createAgentServer(params) {
44934
45030
  agentDir: context.agentDir,
44935
45031
  version: context.version,
44936
45032
  sessionDir: context.sessionDir,
44937
- thinkingLevel: context.thinkingLevel
45033
+ thinkingLevel: context.thinkingLevel,
45034
+ desktop: context.desktop
44938
45035
  });
44939
45036
  });
44940
45037
  app.get("/api/models", (c) => {
@@ -45623,6 +45720,59 @@ async function createAgentServer(params) {
45623
45720
  return c.json({ error: message }, 400);
45624
45721
  }
45625
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
+ });
45626
45776
  app.post("/api/fs/mkdir", async (c) => {
45627
45777
  const body2 = await c.req.json();
45628
45778
  if (!body2.path) {
@@ -46275,19 +46425,36 @@ async function createAgentServer(params) {
46275
46425
  return c.json({ error: message }, 500);
46276
46426
  }
46277
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
+ });
46278
46442
  app.get("/api/tools", async (c) => {
46279
46443
  try {
46280
46444
  const tools = await listAllTools({
46281
46445
  profile: context.profile,
46282
46446
  agentDir: context.agentDir,
46283
- cwd: context.cwd,
46284
- includeDocyrusWebBrowser: true
46447
+ cwd: context.cwd
46285
46448
  });
46449
+ if (context.desktop) {
46450
+ tools.push(...BROWSER_TOOL_SCHEMAS);
46451
+ }
46286
46452
  const builtInCount = tools.filter((t) => t.source === "built-in").length;
46287
46453
  const mcpCount = tools.filter((t) => t.source !== "built-in").length;
46288
46454
  return c.json({
46289
46455
  tools,
46290
- summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length }
46456
+ summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length },
46457
+ clientSideToolPrefixes: context.desktop ? [BROWSER_TOOL_PREFIX] : []
46291
46458
  });
46292
46459
  } catch (error48) {
46293
46460
  const message = error48 instanceof Error ? error48.message : String(error48);
@@ -46479,6 +46646,8 @@ async function createAgentServer(params) {
46479
46646
  process.stderr.write(` GET /api/fs/read \u2014 read file contents
46480
46647
  `);
46481
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)
46482
46651
  `);
46483
46652
  process.stderr.write(` POST /api/fs/mkdir \u2014 create directory
46484
46653
  `);
@@ -46510,6 +46679,12 @@ async function createAgentServer(params) {
46510
46679
  `);
46511
46680
  process.stderr.write(` GET /api/tools \u2014 list all tools
46512
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
+ }
46513
46688
  process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
46514
46689
  `);
46515
46690
  process.stderr.write(` WS /api/terminal \u2014 PTY terminal (WebSocket)
@@ -46705,6 +46880,163 @@ function createServerSessionAdapter(params) {
46705
46880
  };
46706
46881
  }
46707
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
+
46708
47040
  // src/services/spinner.ts
46709
47041
  var import_picocolors = __toESM(require_picocolors());
46710
47042
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -46919,8 +47251,17 @@ async function main() {
46919
47251
  `
46920
47252
  );
46921
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
+ });
46922
47262
  await createAgentServer({
46923
47263
  session: createServerSessionAdapter({ session, extensionsResult }),
47264
+ extensionUIBridge,
46924
47265
  authToken: request.auth,
46925
47266
  port: request.port,
46926
47267
  sessionManager: {
@@ -46940,7 +47281,8 @@ async function main() {
46940
47281
  agentDir,
46941
47282
  version: version2,
46942
47283
  sessionDir: request.sessionDir ?? null,
46943
- thinkingLevel: request.thinking ?? null
47284
+ thinkingLevel: request.thinking ?? null,
47285
+ desktop: request.desktop === true
46944
47286
  },
46945
47287
  onCreateSession: async () => {
46946
47288
  const { session: freshSession, extensionsResult: freshExtensionsResult } = await pi.createAgentSession({
@@ -46959,6 +47301,9 @@ async function main() {
46959
47301
  session: freshSession,
46960
47302
  modelRegistry
46961
47303
  });
47304
+ await freshSession.bindExtensions({
47305
+ uiContext: extensionUIBridge.createUIContext()
47306
+ });
46962
47307
  return createServerSessionAdapter({
46963
47308
  session: freshSession,
46964
47309
  extensionsResult: freshExtensionsResult
@@ -46991,6 +47336,9 @@ async function main() {
46991
47336
  session: resumedSession,
46992
47337
  modelRegistry
46993
47338
  });
47339
+ await resumedSession.bindExtensions({
47340
+ uiContext: extensionUIBridge.createUIContext()
47341
+ });
46994
47342
  return createServerSessionAdapter({
46995
47343
  session: resumedSession,
46996
47344
  extensionsResult: resumedExtensionsResult