@tea-agent/loop-agent 0.43.0-next.14 → 0.43.0-next.15

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 (97) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/worker/console/chat/browser-automation.js +363 -0
  4. package/dist/worker/console/chat/browser-routes.js +36 -14
  5. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +89 -0
  6. package/dist/worker/console/chat/pi-runtime.js +9 -1
  7. package/dist/worker/console/chat/routes.js +6 -1
  8. package/dist/worker/console/chat/session-mode.js +1 -0
  9. package/dist/worker/console/chat/tools.js +4 -0
  10. package/dist/worker/console/server.js +1 -1
  11. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-1TicK2v3.js → abnfDiagram-N423BO3Z-CQE0d0CU.js} +1 -1
  12. package/dist/worker/console/static/assets/{arc-BI-61Wnq.js → arc-BFYYG0Wz.js} +1 -1
  13. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BrGzCbLC.js → architectureDiagram-T3A2C74G-B0YlJL9r.js} +1 -1
  14. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C0E6CBWK.js → blockDiagram-VBNYF7ZC-DN9kD3TO.js} +1 -1
  15. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D6Pnh39e.js → c4Diagram-5PPSVZJV-ChK60XBH.js} +1 -1
  16. package/dist/worker/console/static/assets/channel-BF5AdCat.js +1 -0
  17. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DRhQr5iF.js → chunk-2GRJ4B5K-D4AjoNa-.js} +1 -1
  18. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-MNS7POMD.js → chunk-2Q5K7J3B-B2HEK_D9.js} +1 -1
  19. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DPBI_81g.js → chunk-5RXB4S5H-DXfZkTa0.js} +1 -1
  20. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DduCRUsX.js → chunk-5VM5RSS4-DC1FyC_o.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BQv9BtIQ.js → chunk-6Q2QTUOP-U2E9RJkG.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CzpgRwNH.js → chunk-GF5L2VYU-BeMabzLg.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Dv4_KVRW.js → chunk-JWPE2WC7-CWvf3gKN.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D1qcnmuz.js → chunk-KBJHAD2P-BQE1P4so.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBK8NJN0.js → chunk-RYQCIY6F-DobDWQXh.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DAIeiODy.js → chunk-XXDRQBXY-IQe7Y7EH.js} +1 -1
  27. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BFdnxKyb.js +1 -0
  28. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BFdnxKyb.js +1 -0
  29. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-dnp2bs3S.js → cose-bilkent-JH36ORCC-CyDcJU1F.js} +1 -1
  30. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-O1uTYY7b.js → cynefin-VYW2F7L2-CDt8Uivk.js} +1 -1
  31. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BTDfSvDI.js → cynefinDiagram-MW4NZA55-BmFT7svy.js} +1 -1
  32. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-y_dIbZ9q.js → dagre-VZM6K2ZE-hRf4KDfM.js} +1 -1
  33. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-JH8pTYhg.js → diagram-7IWD3JNH-CaDSdUs-.js} +1 -1
  34. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-nN7RP6Lm.js → diagram-B4RE2ZJO-Dw13MbVk.js} +1 -1
  35. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Dch4i7JV.js → diagram-LBJQPF4R-TLaO5F4A.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-eKV1Psgk.js → diagram-Q27KOJAE-B-YdjvlA.js} +1 -1
  37. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CQDYSqC9.js → diagram-UB23O5K3-DHR7IqG6.js} +1 -1
  38. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dy-884eA.js → ebnfDiagram-BXEA7PRR-C5v-LKCD.js} +1 -1
  39. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DLIrOwXv.js → erDiagram-JOGREHBK-7tPJ992C.js} +1 -1
  40. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DxkodKPO.js → flowDiagram-UKHOOZJN-CdeV10q2.js} +1 -1
  41. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CH_tTUOi.js → ganttDiagram-PKOTCBZU-Nuod51KB.js} +1 -1
  42. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BW962oLZ.js → gitGraphDiagram-DS77QQ5N-CiHmujkA.js} +1 -1
  43. package/dist/worker/console/static/assets/{index-CHRd_RC2.css → index-BXrp_jW4.css} +1 -1
  44. package/dist/worker/console/static/assets/{index-BGEQmIg4.js → index-BnZTkOuj.js} +84 -84
  45. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-B7JBGEiN.js → infoDiagram-6WML65LV-BsbKAEaZ.js} +1 -1
  46. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DExVPf4a.js → ishikawaDiagram-WSZJBQD7-BMg44jAM.js} +1 -1
  47. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-MghtTQAI.js → journeyDiagram-NVQOT4AX-B2HydpC5.js} +1 -1
  48. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CWDeFtqL.js → kanban-definition-27J2QSJJ-Dym8tKBH.js} +1 -1
  49. package/dist/worker/console/static/assets/{linear-B63gdAma.js → linear-C2XLUUy6.js} +1 -1
  50. package/dist/worker/console/static/assets/{mermaid.core-BQZRRvZf.js → mermaid.core-CWKyfsl6.js} +5 -5
  51. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQlHR1EK.js → mindmap-definition-FAOFIHXS-D1kp3mJa.js} +1 -1
  52. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DuCCddHv.js → pegDiagram-VL7TDLO6-yChJn9Q6.js} +1 -1
  53. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BxVL551q.js → pieDiagram-7S7Q4E2Y-BL0gor4I.js} +1 -1
  54. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DBeUEA3G.js → quadrantDiagram-CIZ2JOQS-BQlU54QR.js} +1 -1
  55. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-AJJHzuXc.js → railroadDiagram-AXF67PYL-CFaiPP19.js} +1 -1
  56. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-_bO2feg5.js → requirementDiagram-LRYGKXZP-3ZmKxDyQ.js} +1 -1
  57. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CXw0ti4x.js → sankeyDiagram-W5VNT64P-C6ZDqbFT.js} +1 -1
  58. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Bco8ah7u.js → sequenceDiagram-SI44F4Z6-DdB0hg2W.js} +1 -1
  59. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DypMl6A8.js → sizeCapture-X5ZJPWSS-BdaLiAnv.js} +1 -1
  60. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-C4_BnD96.js → stateDiagram-OKZ733FA-D75bWtSk.js} +1 -1
  61. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBtfTPWx.js +1 -0
  62. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DZTjbssU.js → swimlanes-SLNWSIFB-BPhv358R.js} +2 -2
  63. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cso4j_rK.js +8 -0
  64. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-GdMEc1qP.js → timeline-definition-Z64GVDOM-hrXh6Jie.js} +1 -1
  65. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BrAoL9sU.js → vennDiagram-T6HMQDX7-CxjAfZu8.js} +1 -1
  66. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Cg8QnF-d.js → wardleyDiagram-T6FBY63Y-C4cHKlnQ.js} +1 -1
  67. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-GXQPakaZ.js → xychartDiagram-ELKLHX3M-388ITGyq.js} +1 -1
  68. package/dist/worker/console/static/index.html +2 -2
  69. package/dist/worker/console/workspace-context.js +4 -0
  70. package/package.json +1 -1
  71. package/dist/commands/new-task.js +0 -5
  72. package/dist/commands/status.js +0 -57
  73. package/dist/commands/task-contract.js +0 -269
  74. package/dist/shared/prompts.js +0 -26
  75. package/dist/task/config.js +0 -2
  76. package/dist/task/goal.js +0 -3
  77. package/dist/task/index.js +0 -12
  78. package/dist/task/lifecycle.js +0 -1
  79. package/dist/task/operator/capabilities.js +0 -6
  80. package/dist/task/operator/envelope.js +0 -2
  81. package/dist/task/operator/index.js +0 -5
  82. package/dist/task/operator/registry.js +0 -2
  83. package/dist/task/operator/types.js +0 -1
  84. package/dist/task/paths.js +0 -1
  85. package/dist/task/source-state.js +0 -1
  86. package/dist/task/subagent-guidance.js +0 -1
  87. package/dist/worker/console/chat/sift-bridge.js +0 -1
  88. package/dist/worker/console/index.js +0 -20
  89. package/dist/worker/console/static/assets/channel-D-RWoPS8.js +0 -1
  90. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cb0d0EM1.js +0 -1
  91. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cb0d0EM1.js +0 -1
  92. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CxDH0BMP.js +0 -1
  93. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CkWiKV_L.js +0 -8
  94. package/dist/worker/scheduler/index.js +0 -25
  95. package/dist/workflows/dag/facts.js +0 -4
  96. package/dist/workflows/dag/index.js +0 -6
  97. package/dist/workflows/dynamic/index.js +0 -7
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ - Operator Chat 新增对话驱动的侧边栏浏览器自动化:模型可在工作流 / Pi 会话调用 `browser_navigate` / `browser_search` / `browser_inspect` / `browser_act`,把百度等页面打开到右侧浏览器面板,执行静态搜索、GET 表单填充提交、链接点击并返回结构化页面元素;命令通道按工作区隔离,自动化不读取或修改仓库文件,也不执行页面脚本。
6
+
5
7
  - 修复前端 Plan 无法提交实现步骤的问题:finalize_plan 支持按 AC 绑定实施动作,缺漏在节点内返回修正,避免空计划反复进入设计审查。
6
8
 
7
9
  - 修复前端最终审查反复猜错范围 ID:明确 checkpoint 参数和补交步骤,收尾正确识别 checkpoint 之后的审批结论。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.43.0-next.14",
4
- "gitSha": "31d48ba3bc6eecc584ff9fe66f78c095dcf0ca2e",
5
- "builtAt": "2026-09-11T02:21:26.141Z"
3
+ "version": "0.43.0-next.15",
4
+ "gitSha": "216714a351e09e72203f04c7143d880cc22e17ca",
5
+ "builtAt": "2026-09-11T05:20:15.894Z"
6
6
  }
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Server-side implementation for the model-callable sidebar browser tools.
3
+ *
4
+ * Navigation is delivered to the visible BrowserPanel through a small pending
5
+ * command queue. Page inspection uses the same http(s) URL loaded by that
6
+ * panel and parses the server-visible HTML. Static GET forms are submitted as
7
+ * real navigations; dynamic scripts and POST forms are intentionally refused.
8
+ */
9
+ import { normalizeBrowserUrl } from "./browser-policy.js";
10
+ export class BrowserAutomationService {
11
+ fetchImpl;
12
+ pending = [];
13
+ sequence = 0;
14
+ constructor(fetchImpl = fetch) {
15
+ this.fetchImpl = fetchImpl;
16
+ }
17
+ async navigate(rawUrl, options) {
18
+ const normalized = normalizeBrowserUrl(rawUrl);
19
+ if (normalized.kind !== "ok") {
20
+ return {
21
+ ok: false,
22
+ error: normalized.kind === "invalid"
23
+ ? "invalid browser url"
24
+ : "browser urls must use http(s)",
25
+ };
26
+ }
27
+ let snapshot;
28
+ const snapshotSource = rewriteBaiduSearchUrlForSnapshot(normalized.url);
29
+ if (options?.includeSnapshot) {
30
+ try {
31
+ snapshot = await this.inspect(snapshotSource);
32
+ }
33
+ catch {
34
+ // Navigation itself can still succeed when the panel cannot
35
+ // fetch the page server-side; the iframe remains the fallback.
36
+ }
37
+ }
38
+ const displayUrl = snapshotSource === normalized.url ? normalized.url : snapshotSource;
39
+ const command = {
40
+ kind: "navigate",
41
+ id: this.nextId(),
42
+ url: displayUrl,
43
+ ...(snapshot ? { snapshot } : {}),
44
+ createdAt: new Date().toISOString(),
45
+ };
46
+ this.pending = [command];
47
+ return { ok: true, command };
48
+ }
49
+ reload() {
50
+ const command = {
51
+ kind: "reload",
52
+ id: this.nextId(),
53
+ createdAt: new Date().toISOString(),
54
+ };
55
+ this.pending = [command];
56
+ return { ok: true, command };
57
+ }
58
+ take() {
59
+ const commands = this.pending;
60
+ this.pending = [];
61
+ return commands;
62
+ }
63
+ async inspect(rawUrl) {
64
+ const normalized = normalizeBrowserUrl(rawUrl);
65
+ if (normalized.kind !== "ok")
66
+ throw new BrowserAutomationError("INVALID_URL", "invalid browser url");
67
+ const controller = new AbortController();
68
+ const timer = setTimeout(() => controller.abort(), 12_000);
69
+ try {
70
+ const response = await this.fetchImpl(normalized.url, {
71
+ redirect: "follow",
72
+ signal: controller.signal,
73
+ headers: { accept: "text/html,application/xhtml+xml" },
74
+ });
75
+ const contentType = response.headers.get("content-type") ?? "";
76
+ if (!contentType.includes("html")) {
77
+ throw new BrowserAutomationError("NOT_HTML", "the browser page did not return an HTML document");
78
+ }
79
+ const html = await response.text();
80
+ if (html.length > 3_000_000) {
81
+ throw new BrowserAutomationError("PAGE_TOO_LARGE", "page exceeds 3MB");
82
+ }
83
+ return parseBrowserPage(html, response.url || normalized.url);
84
+ }
85
+ catch (error) {
86
+ if (error instanceof BrowserAutomationError)
87
+ throw error;
88
+ throw new BrowserAutomationError("FETCH_FAILED", "could not load the page", {
89
+ cause: error,
90
+ });
91
+ }
92
+ finally {
93
+ clearTimeout(timer);
94
+ }
95
+ }
96
+ async search(rawQuery, rawUrl) {
97
+ const query = rawQuery.trim();
98
+ if (!query)
99
+ throw new BrowserAutomationError("EMPTY_QUERY", "search query is empty");
100
+ let target = rawUrl?.trim() ? rawUrl.trim() : "https://m.baidu.com/s?wd=";
101
+ const normalized = normalizeBrowserUrl(target);
102
+ if (normalized.kind !== "ok") {
103
+ throw new BrowserAutomationError("INVALID_URL", "search engine url must use http(s)");
104
+ }
105
+ const url = new URL(normalized.url);
106
+ url.searchParams.set(url.searchParams.has("q") ? "q" : "wd", query);
107
+ const result = await this.navigate(url.href, { includeSnapshot: true });
108
+ if (!result.ok)
109
+ throw new BrowserAutomationError("INVALID_URL", result.error);
110
+ return result.command.kind === "navigate" && result.command.snapshot
111
+ ? result.command.snapshot
112
+ : await this.inspect(url.href);
113
+ }
114
+ async act(rawUrl, request) {
115
+ const snapshot = await this.inspect(rawUrl);
116
+ const element = selectBrowserElement(snapshot, request);
117
+ if (!element) {
118
+ throw new BrowserAutomationError("ELEMENT_NOT_FOUND", "selector did not match a clickable element or form field");
119
+ }
120
+ if (request.type === "fill") {
121
+ if (element.tag !== "input" && element.tag !== "textarea") {
122
+ throw new BrowserAutomationError("NOT_FILLABLE", "matched element is not fillable");
123
+ }
124
+ const submitted = this.submitForm(snapshot, element, request.value);
125
+ const submittedSnapshot = await this.inspect(submitted.url);
126
+ const navigation = await this.navigate(submitted.url, { includeSnapshot: true });
127
+ if (!navigation.ok)
128
+ throw new BrowserAutomationError("INVALID_URL", navigation.error);
129
+ return {
130
+ ...submittedSnapshot,
131
+ action: { type: "fill", selector: request.selector, value: request.value, url: submitted.url },
132
+ };
133
+ }
134
+ const formSubmit = element.clickable && element.formIndex !== undefined && !element.href
135
+ ? this.submitForm(snapshot, element)
136
+ : undefined;
137
+ const targetUrl = formSubmit?.url ?? element.href;
138
+ if (targetUrl === undefined) {
139
+ throw new BrowserAutomationError("NOT_CLICKABLE", "matched element has no safe click target");
140
+ }
141
+ const navigation = await this.navigate(targetUrl, { includeSnapshot: true });
142
+ if (!navigation.ok)
143
+ throw new BrowserAutomationError("INVALID_URL", navigation.error);
144
+ return {
145
+ ...snapshot,
146
+ action: {
147
+ type: "click",
148
+ selector: request.selector,
149
+ url: targetUrl,
150
+ },
151
+ };
152
+ }
153
+ submitForm(snapshot, target, valueOverride) {
154
+ if (target.formIndex === undefined) {
155
+ throw new BrowserAutomationError("FORM_NOT_FOUND", "matched field is not inside a form");
156
+ }
157
+ if ((target.formMethod ?? "get") !== "get") {
158
+ throw new BrowserAutomationError("FORM_METHOD_NOT_SUPPORTED", "only GET forms can be automated without browser scripts");
159
+ }
160
+ const url = new URL(target.formAction ?? snapshot.url);
161
+ for (const element of snapshot.elements) {
162
+ if (element.formIndex !== target.formIndex || !element.name)
163
+ continue;
164
+ if (element.tag === "button")
165
+ continue;
166
+ if (element.tag === "input" && ["submit", "button", "reset", "image"].includes(element.type ?? "")) {
167
+ continue;
168
+ }
169
+ const value = element.selector === target.selector && valueOverride !== undefined
170
+ ? valueOverride
171
+ : element.value ?? "";
172
+ url.searchParams.set(element.name, value);
173
+ }
174
+ return { url: url.href };
175
+ }
176
+ nextId() {
177
+ this.sequence += 1;
178
+ return `browser-${Date.now().toString(36)}-${this.sequence}`;
179
+ }
180
+ }
181
+ export class BrowserAutomationError extends Error {
182
+ code;
183
+ constructor(code, message, options) {
184
+ super(message, options);
185
+ this.code = code;
186
+ this.name = "BrowserAutomationError";
187
+ }
188
+ }
189
+ export function rewriteBaiduSearchUrlForSnapshot(rawUrl) {
190
+ try {
191
+ const url = new URL(rawUrl);
192
+ if (url.protocol !== "https:" || url.hostname !== "www.baidu.com")
193
+ return rawUrl;
194
+ if (url.pathname.replace(/\/+$/, "") !== "/s")
195
+ return rawUrl;
196
+ const query = url.searchParams.get("wd") ?? url.searchParams.get("word");
197
+ if (query === null)
198
+ return rawUrl;
199
+ const mobile = new URL("https://m.baidu.com/s");
200
+ mobile.searchParams.set("wd", query);
201
+ return mobile.href;
202
+ }
203
+ catch {
204
+ return rawUrl;
205
+ }
206
+ }
207
+ const SKIP_TAGS = new Set(["script", "style", "template", "noscript"]);
208
+ const INTERACTIVE_TAGS = new Set(["a", "button", "input", "select", "textarea"]);
209
+ export function parseBrowserPage(html, baseUrl) {
210
+ const state = { snapshot: { url: baseUrl, title: "", elements: [] }, nextFormIndex: 0 };
211
+ const tokenizer = /<!--[\s\S]*?-->|<\/?([a-zA-Z][a-zA-Z0-9:-]*)([^>]*)>/g;
212
+ const openTags = [];
213
+ let cursor = 0;
214
+ let match;
215
+ while ((match = tokenizer.exec(html)) !== null) {
216
+ const text = decodeEntities(html.slice(cursor, match.index)).replace(/\s+/g, " ").trim();
217
+ const parent = openTags[openTags.length - 1];
218
+ if (parent && !SKIP_TAGS.has(parent.tag))
219
+ appendText(parent, text);
220
+ cursor = match.index + match[0].length;
221
+ if (match[0].startsWith("<!--"))
222
+ continue;
223
+ const tag = match[1]?.toLowerCase() ?? "";
224
+ const attrs = parseAttributes(match[2] ?? "");
225
+ if (match[0].startsWith("</")) {
226
+ const index = findOpenTag(openTags, tag);
227
+ if (index >= 0) {
228
+ const closed = openTags[index];
229
+ openTags.length = index;
230
+ closeElement(state, closed, baseUrl);
231
+ }
232
+ continue;
233
+ }
234
+ const parentForForm = openTags[openTags.length - 1];
235
+ let form = parentForForm?.form;
236
+ if (tag === "form" && !form) {
237
+ form = {
238
+ index: state.nextFormIndex,
239
+ action: attrs.action,
240
+ method: attrs.method?.toLowerCase(),
241
+ };
242
+ state.nextFormIndex += 1;
243
+ }
244
+ const element = { tag, attrs, text: "", form };
245
+ if (/\/>$/.test(match[0]) || isVoidTag(tag)) {
246
+ closeElement(state, element, baseUrl);
247
+ }
248
+ else {
249
+ openTags.push(element);
250
+ }
251
+ }
252
+ if (state.snapshot.elements.length > 300)
253
+ state.snapshot.elements.length = 300;
254
+ return state.snapshot;
255
+ }
256
+ function parseAttributes(raw) {
257
+ const attrs = {};
258
+ const pattern = /([:@a-zA-Z_#][^\s"'<>`=]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g;
259
+ let match;
260
+ while ((match = pattern.exec(raw)) !== null) {
261
+ attrs[match[1]?.toLowerCase() ?? ""] = decodeEntities(match[2] ?? match[3] ?? match[4] ?? "");
262
+ }
263
+ return attrs;
264
+ }
265
+ function isVoidTag(tag) {
266
+ return tag === "input" || tag === "img" || tag === "br" || tag === "hr" || tag === "meta" || tag === "link";
267
+ }
268
+ function findOpenTag(stack, tag) {
269
+ for (let index = stack.length - 1; index >= 0; index -= 1) {
270
+ if (stack[index]?.tag === tag)
271
+ return index;
272
+ }
273
+ return -1;
274
+ }
275
+ function appendText(target, text) {
276
+ target.text = target.text ? `${target.text} ${text}` : text;
277
+ }
278
+ function closeElement(state, element, baseUrl) {
279
+ const { tag, attrs, text } = element;
280
+ if (tag === "title") {
281
+ state.snapshot.title = text.slice(0, 300);
282
+ return;
283
+ }
284
+ if (!INTERACTIVE_TAGS.has(tag))
285
+ return;
286
+ let href;
287
+ if (tag === "a" && attrs.href && !/^(javascript|mailto|tel):/i.test(attrs.href.trim())) {
288
+ try {
289
+ href = new URL(attrs.href, baseUrl).href;
290
+ }
291
+ catch {
292
+ href = undefined;
293
+ }
294
+ }
295
+ const id = attrs.id;
296
+ const name = attrs.name;
297
+ const selector = buildSelector(tag, id, name, attrs.class, state.snapshot.elements.length);
298
+ const rawText = text || attrs.value || attrs.placeholder || attrs["aria-label"] || attrs.title || "";
299
+ state.snapshot.elements.push({
300
+ tag,
301
+ ...(id ? { id } : {}),
302
+ text: rawText.replace(/\s+/g, " ").trim().slice(0, 240),
303
+ ...(href ? { href } : {}),
304
+ ...(name ? { name } : {}),
305
+ ...(attrs.type ? { type: attrs.type } : {}),
306
+ ...(attrs.value ? { value: attrs.value } : {}),
307
+ ...(attrs.placeholder ? { placeholder: attrs.placeholder } : {}),
308
+ ...(element.form ? { formIndex: element.form.index } : {}),
309
+ ...(element.form ? {
310
+ formAction: element.form.action === undefined
311
+ ? baseUrl
312
+ : new URL(element.form.action, baseUrl).href,
313
+ formMethod: element.form.method ?? "get",
314
+ } : {}),
315
+ selector,
316
+ clickable: tag === "a" || tag === "button" || (tag === "input" && ["submit", "button", "radio", "checkbox"].includes(attrs.type ?? "")),
317
+ });
318
+ }
319
+ function buildSelector(tag, id, name, className, index) {
320
+ if (id)
321
+ return `#${escapeSelectorValue(id)}`;
322
+ if (name)
323
+ return `${tag}[name="${escapeSelectorValue(name)}"]`;
324
+ if (className?.trim()) {
325
+ const firstClass = className.trim().split(/\s+/)[0];
326
+ if (firstClass)
327
+ return `${tag}.${escapeSelectorValue(firstClass)}`;
328
+ }
329
+ return `${tag}:nth-of-type(${index + 1})`;
330
+ }
331
+ function escapeSelectorValue(value) {
332
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
333
+ }
334
+ export function selectBrowserElement(snapshot, request) {
335
+ const selector = request.selector.trim();
336
+ if (!selector)
337
+ return undefined;
338
+ return snapshot.elements.find((element) => element.selector === selector)
339
+ ?? snapshot.elements.find((element) => element.id === selector.slice(1))
340
+ ?? snapshot.elements.find((element) => element.name === selector)
341
+ ?? snapshot.elements.find((element) => element.text.toLowerCase().includes(selector.toLowerCase()) && selector.length >= 2);
342
+ }
343
+ const ENTITY_MAP = {
344
+ amp: "&",
345
+ lt: "<",
346
+ gt: ">",
347
+ quot: "\"",
348
+ apos: "'",
349
+ nbsp: " ",
350
+ };
351
+ function decodeEntities(value) {
352
+ return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, entity) => {
353
+ if (entity.startsWith("#x") || entity.startsWith("#X")) {
354
+ const code = Number.parseInt(entity.slice(2), 16);
355
+ return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
356
+ }
357
+ if (entity.startsWith("#")) {
358
+ const code = Number.parseInt(entity.slice(1), 10);
359
+ return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
360
+ }
361
+ return ENTITY_MAP[entity.toLowerCase()] ?? whole;
362
+ });
363
+ }
@@ -6,7 +6,7 @@
6
6
  * caller), restricted to http(s) URLs, with a hard timeout. Local addresses
7
7
  * are allowed — the panel routinely browses local dev servers.
8
8
  */
9
- import { embeddabilityOf, extractFrameAncestors, } from "./browser-policy.js";
9
+ import { embeddabilityOf, extractFrameAncestors, normalizeBrowserUrl, } from "./browser-policy.js";
10
10
  function workspaceIdOf(req) {
11
11
  try {
12
12
  return new URL(req.url ?? "/", "http://localhost").searchParams.get("workspaceId") ?? undefined;
@@ -52,24 +52,20 @@ export async function handleBrowserRequest(req, res, deps, pathname) {
52
52
  }
53
53
  const url = new URL(req.url ?? "/", "http://localhost");
54
54
  const raw = url.searchParams.get("url") ?? "";
55
- let parsed;
56
- try {
57
- parsed = new URL(raw);
58
- }
59
- catch {
60
- sendJson(res, 400, {
61
- ok: false,
62
- error: { code: "INVALID_URL", message: "invalid url" },
63
- });
64
- return true;
65
- }
66
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
55
+ const normalized = normalizeBrowserUrl(raw);
56
+ if (normalized.kind !== "ok") {
67
57
  sendJson(res, 400, {
68
58
  ok: false,
69
- error: { code: "BAD_SCHEME", message: "only http/https urls can be probed" },
59
+ error: {
60
+ code: normalized.kind === "blocked" ? "BAD_SCHEME" : "INVALID_URL",
61
+ message: normalized.kind === "blocked"
62
+ ? "only http/https urls can be probed"
63
+ : "invalid url",
64
+ },
70
65
  });
71
66
  return true;
72
67
  }
68
+ const parsed = new URL(normalized.url);
73
69
  const controller = new AbortController();
74
70
  const timer = setTimeout(() => controller.abort(), 8000);
75
71
  try {
@@ -116,6 +112,32 @@ export async function handleBrowserRequest(req, res, deps, pathname) {
116
112
  clearTimeout(timer);
117
113
  }
118
114
  }
115
+ if (method === "GET" && sub === "commands" && parts.length === 1) {
116
+ const ws = workspaceIdOf(req);
117
+ if (!ws) {
118
+ sendJson(res, 400, {
119
+ ok: false,
120
+ error: { code: "WORKSPACE_REQUIRED", message: "workspaceId is required" },
121
+ });
122
+ return true;
123
+ }
124
+ if (deps.requireWorkspace) {
125
+ const scope = deps.requireWorkspace(req);
126
+ if (!scope.ok) {
127
+ sendJson(res, 403, {
128
+ ok: false,
129
+ error: { code: "WORKSPACE_DENIED", message: "workspace scope mismatch" },
130
+ });
131
+ return true;
132
+ }
133
+ }
134
+ if (!deps.automation) {
135
+ sendJson(res, 200, { ok: true, data: { commands: [] } });
136
+ return true;
137
+ }
138
+ sendJson(res, 200, { ok: true, data: { commands: deps.automation.take() } });
139
+ return true;
140
+ }
119
141
  sendJson(res, 404, {
120
142
  ok: false,
121
143
  error: { code: "NOT_FOUND", message: `unknown browser route: ${method} ${pathname}` },
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Session-independent browser tools shared by workflow and Pi chat modes.
3
+ * Navigation is visible in the Console sidebar; inspection is server-side and
4
+ * bounded to avoid exposing raw credentials or arbitrary page scripts.
5
+ */
6
+ const BROWSER_TOOLS_DESCRIPTION = "Operate the Console sidebar browser. `browser_navigate` shows a URL in the panel; `browser_search` opens a search URL and returns elements; `browser_inspect` returns title/links/form controls; `browser_act` fills/submits a static GET form or clicks a static link. For natural-language search requests, prefer `browser_search` over navigating to a homepage and simulating its form. It never edits the workspace and cannot execute browser scripts. Dynamic-page and POST-form operations are out of scope.";
7
+ export async function buildBrowserTools(options) {
8
+ const [{ Type }, { defineTool }] = await Promise.all([
9
+ import("typebox"),
10
+ import("@earendil-works/pi-coding-agent"),
11
+ ]);
12
+ const automation = options.automation;
13
+ const urlParameter = Type.String({
14
+ description: "Absolute http(s) URL. Prefer URLs/element selectors returned by prior browser tools.",
15
+ });
16
+ async function execute(operation) {
17
+ try {
18
+ const result = await operation();
19
+ return {
20
+ content: [{ type: "text", text: JSON.stringify(result) }],
21
+ details: result,
22
+ };
23
+ }
24
+ catch (error) {
25
+ const payload = {
26
+ error: error instanceof Error ? error.message : String(error),
27
+ code: error instanceof Error && "code" in error
28
+ ? String(error.code)
29
+ : "BROWSER_OPERATION_FAILED",
30
+ };
31
+ return {
32
+ content: [{ type: "text", text: JSON.stringify(payload) }],
33
+ details: payload,
34
+ };
35
+ }
36
+ }
37
+ return [
38
+ defineTool({
39
+ name: "browser_navigate",
40
+ label: "Sidebar browser navigate",
41
+ description: BROWSER_TOOLS_DESCRIPTION,
42
+ promptSnippet: BROWSER_TOOLS_DESCRIPTION,
43
+ parameters: Type.Object({ url: urlParameter }, { additionalProperties: false }),
44
+ async execute(_toolCallId, params) {
45
+ return execute(() => automation.navigate(params.url, { includeSnapshot: true }));
46
+ },
47
+ }),
48
+ defineTool({
49
+ name: "browser_search",
50
+ label: "Sidebar browser search",
51
+ description: BROWSER_TOOLS_DESCRIPTION,
52
+ promptSnippet: BROWSER_TOOLS_DESCRIPTION,
53
+ parameters: Type.Object({
54
+ query: Type.String({ description: "Search query." }),
55
+ engineUrl: Type.Optional(urlParameter),
56
+ }, { additionalProperties: false }),
57
+ async execute(_toolCallId, params) {
58
+ return execute(() => automation.search(params.query, params.engineUrl));
59
+ },
60
+ }),
61
+ defineTool({
62
+ name: "browser_inspect",
63
+ label: "Sidebar browser inspect",
64
+ description: BROWSER_TOOLS_DESCRIPTION,
65
+ promptSnippet: BROWSER_TOOLS_DESCRIPTION,
66
+ parameters: Type.Object({ url: urlParameter }, { additionalProperties: false }),
67
+ async execute(_toolCallId, params) {
68
+ return execute(() => automation.inspect(params.url));
69
+ },
70
+ }),
71
+ defineTool({
72
+ name: "browser_act",
73
+ label: "Sidebar browser act",
74
+ description: BROWSER_TOOLS_DESCRIPTION,
75
+ promptSnippet: BROWSER_TOOLS_DESCRIPTION,
76
+ parameters: Type.Object({
77
+ url: urlParameter,
78
+ type: Type.Union([Type.Literal("click"), Type.Literal("fill")]),
79
+ selector: Type.String({ description: "Selector returned by browser_inspect." }),
80
+ value: Type.Optional(Type.String()),
81
+ }, { additionalProperties: false }),
82
+ async execute(_toolCallId, params) {
83
+ return execute(() => automation.act(params.url, params.type === "click"
84
+ ? { type: "click", selector: params.selector }
85
+ : { type: "fill", selector: params.selector, value: params.value ?? "" }));
86
+ },
87
+ }),
88
+ ];
89
+ }
@@ -43,12 +43,14 @@ import { incrementCounter as incrementPressureCounter } from "../../../shared/pi
43
43
  import { resolveDefaultChatModel, resolveLowChatModel, } from "./model-resolver.js";
44
44
  import { filterActiveInterviewTools } from "../interview/tools.js";
45
45
  import { redactRuntimeText } from "./runtime-context.js";
46
+ import { BrowserAutomationService } from "./browser-automation.js";
46
47
  import { buildTodoContinuationContext, } from "./todos.js";
47
48
  import { buildPersistentTerminalTools } from "./terminal-tools.js";
48
49
  import { buildScmTools } from "./scm-tools.js";
49
50
  import { buildGoalContinuationContext, } from "./goals.js";
50
51
  import { injectWizardLocalHeaders, } from "./sdd-data-alignment.js";
51
52
  import { buildCustomOperatorTools } from "./pi-runtime/custom-tools/operator-tools.js";
53
+ import { buildBrowserTools } from "./pi-runtime/custom-tools/browser-tools.js";
52
54
  import { buildAskUserQuestionTool } from "./pi-runtime/custom-tools/ask-user-question.js";
53
55
  import { buildTodoWriteTool } from "./pi-runtime/custom-tools/todo-write.js";
54
56
  import { buildGoalTools } from "./pi-runtime/custom-tools/goal-tools.js";
@@ -78,7 +80,7 @@ import { createDefaultPiSdkBindings, safeGetAgentDir, } from "./pi-runtime/sdk-b
78
80
  */
79
81
  export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
80
82
  "You are General Operator Chat.",
81
- "Use operator tools first (dagReport, dagRerunPlan, dagRerun). Pi read/write/edit/bash/grep/find/ls are available; obey repository AGENTS.md. apply_patch, full-tools, shell and coding-chat are denied. Prefer safe-read/safe-grep for probes.",
83
+ "Use operator tools first (dagReport, dagRerunPlan, dagRerun). Pi read/write/edit/bash/grep/find/ls are available; obey repository AGENTS.md. apply_patch, full-tools, shell and coding-chat are denied. Prefer safe-read/safe-grep for probes. Browser_* drives the visible Browser panel only (static http(s), no workspace edits/scripts).",
82
84
  "No harness and asked to init: `loop-agent init instructions --repo-root .`, then `loop-agent init --repo-root . --profile full --merge`; verify with doctor, inspect and docs audit. Updates: `loop-agent init upgrade --repo-root . --json` until stable; `loop-agent init check-update` is read-only.",
83
85
  "Long DAGs: prepareDagExecution → runDag → operationId. Never foreground-Bash task advance, tail/head pipes or nohup/Start-Process/start; do not end on accepted/queued/running or an operationId — NOT completion. Supervise operationGet/status/dagReport/dagDoctor/operationWait at 60s → 90s → 120s → 180s; reset to 60s on state change; 30-60s re-checks when stall suspected. operationWait MUST use afterSeq=previous nextSeq (recommendedNextCall). Heartbeats: not meaningful progress. Run id ≠ task id.",
84
86
  "Failure: terminal runSummary first. With progress wait; else dagRerunPlan(fromNode=primaryFailure.nodeId); eligible → dagRerun: reruns the failed subgraph, keeps prior successes. Ineligible: Worker → workerTaskRetry; standalone → standaloneTaskRerun after contract adopt (regenerates the whole task, not node continue). Do not call dagResume; do not re-advance a run-failed task. Pre-run identical state twice: stop and report; never loop identical bash/read/grep. Approve only bounded writeSet inside allowedPaths, outside forbiddenPaths, with structured verification.",
@@ -415,10 +417,12 @@ export class ConsolePiRuntime {
415
417
  exploreCustomToolsPromise;
416
418
  exploreOrchestrator;
417
419
  bindings;
420
+ browserAutomation;
418
421
  constructor(options) {
419
422
  this.options = options;
420
423
  this.loader = createOperatorChatResourceLoader();
421
424
  this.bindings = options.bindings ?? createDefaultPiSdkBindings();
425
+ this.browserAutomation = options.browserAutomation ?? new BrowserAutomationService();
422
426
  this.exploreOrchestrator = new ExploreOrchestrator({
423
427
  cwd: this.options.cwd,
424
428
  bindings: this.bindings,
@@ -972,6 +976,9 @@ export class ConsolePiRuntime {
972
976
  : Promise.resolve([]),
973
977
  this.loadExploreCustomTools(),
974
978
  ]);
979
+ const browserTools = await buildBrowserTools({
980
+ automation: this.browserAutomation,
981
+ });
975
982
  const questionTool = this.options.questionRegistry
976
983
  ? await buildAskUserQuestionTool({
977
984
  sessionId,
@@ -1030,6 +1037,7 @@ export class ConsolePiRuntime {
1030
1037
  ...goalTools,
1031
1038
  ...terminalTools,
1032
1039
  ...scmTools,
1040
+ ...browserTools,
1033
1041
  ...(agentTool ? [agentTool] : []),
1034
1042
  ];
1035
1043
  }
@@ -4280,7 +4280,12 @@ async function dispatchChatRequest(req, res, deps, pathname, workspaceScope) {
4280
4280
  }
4281
4281
  }
4282
4282
  // Browser probe API (read-only; explains iframe refusals to the panel).
4283
- if (await handleBrowserRequest(req, res, {}, pathname)) {
4283
+ if (await handleBrowserRequest(req, res, {
4284
+ automation: deps.browserAutomation,
4285
+ requireWorkspace: () => workspaceScope
4286
+ ? { ok: true, workspaceId: workspaceScope }
4287
+ : { ok: false },
4288
+ }, pathname)) {
4284
4289
  return true;
4285
4290
  }
4286
4291
  if (!pathname.startsWith("/api/operator/v1/chat/") &&
@@ -74,6 +74,7 @@ export const PI_CHAT_SYSTEM_PROMPT_BASE = [
74
74
  "Use read/write/edit/bash/grep/find/ls to inspect and change the workspace. apply_patch, full-tools, shell and coding-chat are denied.",
75
75
  "Agent (Explore) is a built-in read-only subagent for path-uncertain codebase search. Do not use it for a known single-file read. Prefer safe-read/safe-grep for secret-safe probes.",
76
76
  "ask_user_question pauses for a real user answer. todo_write tracks the current work and is restored after compression; it is not a workflow auto-continue signal. The persistent terminal tool is available.",
77
+ "Sidebar browser tools (browser_navigate/search/inspect/act) drive the visible Browser panel for user-requested web research and static automation; they never edit workspace files or execute page scripts.",
77
78
  "Goal tools (get_goal / create_goal / update_goal) track the session's durable cross-turn objective. Create a Goal only when the user explicitly asks to start one; iterate in bounded rounds until complete or blocked.",
78
79
  "This session keeps Console compression (L0/L1/L2, sift, strict checkpoint) and existing Aimax / SDD access. Do not create tasks, run DAGs, or start requirement interviews from this session.",
79
80
  "Obey repository AGENTS.md, skills and extensions when they apply. Choosing Pi mode is not extra write authorization and does not bypass Console access control or output scrubbing.",
@@ -25,6 +25,10 @@ export const OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS = Object.freeze([
25
25
  OPERATOR_CHAT_AGENT_TOOL_NAME,
26
26
  "terminal",
27
27
  "scm",
28
+ "browser_navigate",
29
+ "browser_search",
30
+ "browser_inspect",
31
+ "browser_act",
28
32
  ]);
29
33
  /**
30
34
  * Actions the model MAY invoke. Derived from the registry but filtered by the
@@ -651,7 +651,7 @@ export async function createConsoleServer(options) {
651
651
  }
652
652
  resolution.context.actionContext.readiness =
653
653
  resolution.context.getReadiness();
654
- await dispatchChat(req, res, resolution.context.chatRouteDeps, pathname, undefined);
654
+ await dispatchChat(req, res, resolution.context.chatRouteDeps, pathname, resolution.context.workspaceId);
655
655
  }
656
656
  async function handleOperatorDispatch(req, res, pathname, url, method) {
657
657
  const { handleConsoleOperatorRequest } = await import("./routes.js");
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-BQv9BtIQ.js";import{p as f}from"./chunk-JWPE2WC7-Dv4_KVRW.js";import{_ as n,l as o}from"./mermaid.core-BQZRRvZf.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-O1uTYY7b.js";import"./index-BGEQmIg4.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-U2E9RJkG.js";import{p as f}from"./chunk-JWPE2WC7-CWvf3gKN.js";import{_ as n,l as o}from"./mermaid.core-CWKyfsl6.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-CDt8Uivk.js";import"./index-BnZTkOuj.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};