@aion0/forge 0.6.1 → 0.8.0

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 (145) hide show
  1. package/.forge/mcp.json +8 -0
  2. package/.forge/worktrees/pipeline-0a33c50d/lib/help-docs/01-settings.md +5 -5
  3. package/.forge/worktrees/pipeline-0a33c50d/lib/help-docs/07-projects.md +1 -1
  4. package/.forge/worktrees/pipeline-2ba01c10/lib/help-docs/01-settings.md +5 -5
  5. package/.forge/worktrees/pipeline-2ba01c10/lib/help-docs/07-projects.md +1 -1
  6. package/.forge/worktrees/pipeline-3156a8b3/lib/help-docs/01-settings.md +5 -5
  7. package/.forge/worktrees/pipeline-3156a8b3/lib/help-docs/07-projects.md +1 -1
  8. package/.forge/worktrees/pipeline-316c6574/lib/help-docs/01-settings.md +5 -5
  9. package/.forge/worktrees/pipeline-316c6574/lib/help-docs/07-projects.md +1 -1
  10. package/.forge/worktrees/pipeline-44a94121/lib/help-docs/01-settings.md +5 -5
  11. package/.forge/worktrees/pipeline-44a94121/lib/help-docs/07-projects.md +1 -1
  12. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +5 -5
  13. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +1 -1
  14. package/.forge/worktrees/pipeline-d1757a50/lib/help-docs/01-settings.md +5 -5
  15. package/.forge/worktrees/pipeline-d1757a50/lib/help-docs/07-projects.md +1 -1
  16. package/.forge/worktrees/pipeline-d59c2fe2/lib/help-docs/01-settings.md +5 -5
  17. package/.forge/worktrees/pipeline-d59c2fe2/lib/help-docs/07-projects.md +1 -1
  18. package/.forge/worktrees/pipeline-d6a6ef23/lib/help-docs/01-settings.md +5 -5
  19. package/.forge/worktrees/pipeline-d6a6ef23/lib/help-docs/07-projects.md +1 -1
  20. package/.forge/worktrees/pipeline-e7f78b7a/lib/help-docs/01-settings.md +5 -5
  21. package/.forge/worktrees/pipeline-e7f78b7a/lib/help-docs/07-projects.md +1 -1
  22. package/.forge/worktrees/pipeline-e97c13c7/lib/help-docs/01-settings.md +5 -5
  23. package/.forge/worktrees/pipeline-e97c13c7/lib/help-docs/07-projects.md +1 -1
  24. package/.forge/worktrees/pipeline-ecd7cb0f/lib/help-docs/01-settings.md +5 -5
  25. package/.forge/worktrees/pipeline-ecd7cb0f/lib/help-docs/07-projects.md +1 -1
  26. package/CLAUDE.md +2 -2
  27. package/RELEASE_NOTES.md +101 -5
  28. package/app/api/auth/check/route.ts +18 -0
  29. package/app/api/browser-bridge/route.ts +70 -0
  30. package/app/api/chat/sessions/[id]/events/route.ts +17 -0
  31. package/app/api/chat/sessions/[id]/fork/route.ts +15 -0
  32. package/app/api/chat/sessions/[id]/messages/route.ts +21 -0
  33. package/app/api/chat/sessions/[id]/route.ts +23 -0
  34. package/app/api/chat/sessions/route.ts +12 -0
  35. package/app/api/chat/temper-ping/route.ts +18 -0
  36. package/app/api/chat-proxy/[...path]/route.ts +83 -0
  37. package/app/api/connector-tool/route.ts +38 -0
  38. package/app/api/connectors/[id]/settings/route.ts +112 -0
  39. package/app/api/connectors/route.ts +108 -0
  40. package/app/api/health/tools/route.ts +14 -0
  41. package/app/api/issue-scanner-gitlab/route.ts +95 -0
  42. package/app/api/jobs/[id]/reset_dedup/route.ts +15 -0
  43. package/app/api/jobs/[id]/route.ts +31 -0
  44. package/app/api/jobs/[id]/run/route.ts +44 -0
  45. package/app/api/jobs/[id]/runs/[runId]/route.ts +15 -0
  46. package/app/api/jobs/[id]/runs/route.ts +15 -0
  47. package/app/api/jobs/preview/route.ts +193 -0
  48. package/app/api/jobs/route.ts +36 -0
  49. package/app/api/notify/test/route.ts +39 -7
  50. package/app/api/pipelines/[id]/route.ts +10 -1
  51. package/app/api/pipelines/route.ts +16 -2
  52. package/app/api/plugins/route.ts +40 -8
  53. package/app/api/project-sessions/route.ts +50 -10
  54. package/app/api/settings/route.ts +13 -0
  55. package/app/chat/page.tsx +531 -0
  56. package/bin/forge-server.mjs +3 -1
  57. package/cli/chat.ts +283 -0
  58. package/cli/jobs.ts +176 -0
  59. package/cli/mw.ts +28 -1
  60. package/cli/worktree.ts +245 -0
  61. package/components/ConnectorsPanel.tsx +275 -0
  62. package/components/Dashboard.tsx +90 -37
  63. package/components/JobsView.tsx +361 -0
  64. package/components/LogViewer.tsx +12 -2
  65. package/components/PipelineView.tsx +275 -56
  66. package/components/PluginsPanel.tsx +3 -1
  67. package/components/SettingsModal.tsx +229 -40
  68. package/components/SkillsPanel.tsx +12 -4
  69. package/components/TerminalLauncher.tsx +3 -1
  70. package/components/WebTerminal.tsx +32 -9
  71. package/components/WorkspaceView.tsx +18 -10
  72. package/docs/Connector-DeclarativeExtract-Handoff.md +471 -0
  73. package/docs/Connector-DeclarativeExtract-Spec.md +364 -0
  74. package/docs/Implementation-Plan-Browser-Agent.md +487 -0
  75. package/docs/Jobs-Design.md +240 -0
  76. package/docs/LOCAL-DEPLOY.md +3 -3
  77. package/docs/RFC-Browser-Connectors.md +509 -0
  78. package/lib/agents/index.ts +44 -6
  79. package/lib/agents/types.ts +1 -1
  80. package/lib/browser-bridge-standalone.ts +317 -0
  81. package/lib/builtin-plugins/github-api.yaml +93 -0
  82. package/lib/builtin-plugins/gitlab.yaml +860 -0
  83. package/lib/builtin-plugins/mantis.probe.js +176 -0
  84. package/lib/builtin-plugins/mantis.yaml +964 -0
  85. package/lib/builtin-plugins/pmdb.yaml +178 -0
  86. package/lib/builtin-plugins/teams.yaml +913 -0
  87. package/lib/chat/__test__/smoke.ts +30 -0
  88. package/lib/chat/agent-loop.ts +523 -0
  89. package/lib/chat/bridge-client.ts +59 -0
  90. package/lib/chat/llm/anthropic.ts +99 -0
  91. package/lib/chat/llm/index.ts +20 -0
  92. package/lib/chat/llm/openai.ts +215 -0
  93. package/lib/chat/llm/types.ts +42 -0
  94. package/lib/chat/local-memory.ts +300 -0
  95. package/lib/chat/memory-store.ts +87 -0
  96. package/lib/chat/memory-tools.ts +157 -0
  97. package/lib/chat/protocols/http.ts +118 -0
  98. package/lib/chat/protocols/shell.ts +101 -0
  99. package/lib/chat/proxy.ts +51 -0
  100. package/lib/chat/session-store.ts +272 -0
  101. package/lib/chat/telegram-bridge.ts +276 -0
  102. package/lib/chat/temper.ts +281 -0
  103. package/lib/chat/tool-dispatcher.ts +190 -0
  104. package/lib/chat/types.ts +50 -0
  105. package/lib/chat-standalone.ts +286 -0
  106. package/lib/crypto.ts +1 -1
  107. package/lib/health.ts +131 -0
  108. package/lib/help-docs/00-overview.md +2 -1
  109. package/lib/help-docs/01-settings.md +46 -25
  110. package/lib/help-docs/07-projects.md +1 -1
  111. package/lib/help-docs/10-troubleshooting.md +10 -2
  112. package/lib/help-docs/16-gitlab-autofix.md +114 -0
  113. package/lib/help-docs/17-connectors.md +322 -0
  114. package/lib/help-docs/18-chrome-mcp.md +134 -0
  115. package/lib/help-docs/19-jobs.md +140 -0
  116. package/lib/help-docs/20-mantis-bug-fix.md +115 -0
  117. package/lib/help-docs/CLAUDE.md +10 -0
  118. package/lib/init.ts +137 -50
  119. package/lib/iso-time.ts +30 -0
  120. package/lib/issue-scanner-gitlab.ts +281 -0
  121. package/lib/jobs/dispatcher.ts +217 -0
  122. package/lib/jobs/scheduler.ts +334 -0
  123. package/lib/jobs/store.ts +319 -0
  124. package/lib/jobs/types.ts +117 -0
  125. package/lib/pipeline-scheduler.ts +1 -6
  126. package/lib/pipeline.ts +790 -10
  127. package/lib/plugins/registry.ts +133 -8
  128. package/lib/plugins/templates.ts +83 -0
  129. package/lib/plugins/types.ts +140 -1
  130. package/lib/session-watcher.ts +36 -10
  131. package/lib/settings.ts +65 -33
  132. package/lib/skills.ts +3 -1
  133. package/lib/task-manager.ts +50 -22
  134. package/lib/telegram-bot.ts +71 -0
  135. package/lib/terminal-standalone.ts +58 -36
  136. package/lib/workspace/orchestrator.ts +1 -0
  137. package/middleware.ts +10 -0
  138. package/package.json +3 -2
  139. package/scripts/bench/README.md +1 -1
  140. package/scripts/bench/tasks/01-text-utils/validator.sh +1 -1
  141. package/scripts/bench/tasks/02-pagination/setup.sh +1 -1
  142. package/scripts/bench/tasks/02-pagination/validator.sh +1 -1
  143. package/scripts/bench/tasks/03-bug-fix/setup.sh +1 -1
  144. package/scripts/bench/tasks/03-bug-fix/validator.sh +1 -1
  145. package/src/core/db/database.ts +21 -12
@@ -0,0 +1,509 @@
1
+ # RFC: Browser Connectors
2
+
3
+ > **Status**: Draft — design only, no implementation.
4
+ > **Owner**: aion0
5
+ > **Date**: 2026-05-16
6
+ > **Related**:
7
+ > - `PRD.md` (Forge product PRD)
8
+ > - `roadmap-multi-agent-workflow.md`
9
+ > - `../temper` (memory service used by enterprise agents)
10
+
11
+ ## Problem
12
+
13
+ Enterprise agents need access to dozens of internal web systems (JIRA,
14
+ Mantis, Confluence, Teams, internal CRM, monitoring dashboards). Two
15
+ existing approaches both fail:
16
+
17
+ 1. **OAuth / API tokens per system** — IT申请慢、credentials 难管、
18
+ refresh tokens 复杂、有些遗留系统压根没 API。
19
+ 2. **Anthropic Computer Use 视觉路径** — 截图 + vision LLM 推坐标,慢、
20
+ 不稳、token 消耗大、出错难调试。
21
+
22
+ We propose a **third path**: a Chrome browser extension that runs as the
23
+ user's already-authenticated session, executes semantic actions via
24
+ per-site **connectors** that extract data from the rendered DOM (using
25
+ the extension's Chrome MCP capabilities), and exposes them as MCP tools
26
+ to Forge / any MCP client.
27
+
28
+ **Connectors do not call REST APIs.** They navigate tabs and parse the
29
+ rendered HTML, inheriting the user's UI session implicitly through
30
+ cookies. Token-based API access is an opt-in escape hatch, not the
31
+ default — see "Execution Model" below for the rationale.
32
+
33
+ ## Goals
34
+
35
+ - **Zero credential setup** — re-use the user's existing browser sessions.
36
+ - **Reliable per-site integration** — structured tool calls (not vision).
37
+ - **Forge-orchestratable** — agents in Forge pipelines/crafts can call
38
+ browser connectors like any other MCP tool.
39
+ - **Composable** — connectors are independent packages, easy to publish
40
+ and version.
41
+
42
+ ## Non-Goals
43
+
44
+ - Building a general-purpose web automation framework (not Selenium).
45
+ - Replacing Computer Use entirely — vision-based fallback is a separate
46
+ Tier 3 below.
47
+ - Synchronous chat UI in the browser — that's a separate component
48
+ layered on top (see "Out of Scope" below).
49
+
50
+ ## High-level Architecture
51
+
52
+ ```
53
+ ┌──────────────────────────────────────────────┐
54
+ │ Browser (Chrome / Edge with Chromium) │
55
+ │ ┌────────────────────────────────────────┐ │
56
+ │ │ Forge Browser Extension (MV3) │ │
57
+ │ │ • background service worker │ │
58
+ │ │ • content scripts (per-domain) │ │
59
+ │ │ • connectors (built-in + downloaded) │ │
60
+ │ └─────────────────┬──────────────────────┘ │
61
+ └───────────────────┼──────────────────────────┘
62
+ │ WebSocket (loopback, paired)
63
+
64
+ ┌──────────────────────────────────────────────┐
65
+ │ Forge installation │
66
+ │ ┌────────────────────────────────────────┐ │
67
+ │ │ browser-bridge-standalone (NEW) │ │
68
+ │ │ port 8407 │ │
69
+ │ │ • WebSocket server → extension │ │
70
+ │ │ • MCP server (exposes connector tools)│ │
71
+ │ │ • pairing token store │ │
72
+ │ │ • health monitor │ │
73
+ │ └────────────────────────────────────────┘ │
74
+ └──────────────────────────────────────────────┘
75
+
76
+ │ MCP / SSE
77
+
78
+ Forge agents / pipelines / crafts
79
+ (and Claude Desktop, Cursor, etc.)
80
+ ```
81
+
82
+ ### Why a bridge process
83
+
84
+ Chrome MV3 service workers can't keep long-lived TCP servers or expose
85
+ SSE endpoints. The bridge is a small Node process inside Forge that:
86
+
87
+ 1. Holds the persistent MCP server (port 8407)
88
+ 2. Maintains the WebSocket connection to the extension
89
+ 3. Routes MCP tool calls into extension actions
90
+ 4. Tracks connector health + paired devices
91
+
92
+ The bridge is one of Forge's standalone services (alongside
93
+ terminal/workspace/telegram). Stops when Forge stops; auto-detaches
94
+ in background mode (same SIGHUP fix already in `forge-server.mjs`).
95
+
96
+ ## Connector Spec
97
+
98
+ A **connector** is a JavaScript module that:
99
+ - Declares which URLs it can handle (`matches`)
100
+ - Exposes a set of MCP tools (`tools`)
101
+ - Each tool is an async function that runs in the extension's content
102
+ script context
103
+
104
+ ### Manifest
105
+
106
+ ```ts
107
+ import { defineBrowserConnector } from '@forge/browser-connector-sdk';
108
+
109
+ export default defineBrowserConnector({
110
+ id: 'mantis',
111
+ name: 'MantisBT',
112
+ version: '0.1.0',
113
+ matches: ['https://mantis.mycompany.com/*'],
114
+ description: 'Search + create + update Mantis bug tickets',
115
+
116
+ // Hint to the orchestrator whether opening a tab is required when
117
+ // none of the user's tabs match `matches`.
118
+ requiresActiveTab: true,
119
+
120
+ // Optional: health probe — bridge calls this periodically; failure
121
+ // raises an alert. Should return quickly + use minimal quota.
122
+ healthCheck: async (ctx) => {
123
+ const r = await ctx.fetch('/api/rest/users/me');
124
+ return r.ok;
125
+ },
126
+
127
+ tools: {
128
+ 'mantis.list_my_bugs': {
129
+ description: 'List bugs assigned to the current user',
130
+ parameters: {
131
+ status: { type: 'string', enum: ['open', 'closed', 'all'], default: 'open' },
132
+ limit: { type: 'number', default: 50 },
133
+ },
134
+ handler: async (args, ctx) => {
135
+ // ctx.fetch attaches cookies automatically (credentials: include)
136
+ const r = await ctx.fetch(
137
+ `/api/rest/issues?handler_id=me&status_id=${args.status === 'open' ? '!=resolved' : ''}&page_size=${args.limit}`,
138
+ );
139
+ const data = await r.json();
140
+ return { bugs: data.issues, total: data.total };
141
+ },
142
+ },
143
+ 'mantis.add_comment': {
144
+ description: 'Add a comment to a bug',
145
+ parameters: {
146
+ bug_id: { type: 'integer', required: true },
147
+ text: { type: 'string', required: true },
148
+ },
149
+ // destructive: true → bridge requires user confirmation before exec
150
+ destructive: true,
151
+ handler: async (args, ctx) => {
152
+ const r = await ctx.fetch(`/api/rest/issues/${args.bug_id}/notes`, {
153
+ method: 'POST',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({ text: args.text }),
156
+ });
157
+ return { ok: r.ok };
158
+ },
159
+ },
160
+ },
161
+ });
162
+ ```
163
+
164
+ ### Packaging in Forge
165
+
166
+ The `defineBrowserConnector(...)` module above is the **runtime spec** for a
167
+ single connector. It's shipped inside a **Forge plugin** — the same plugin
168
+ system used for `gmail`, `jenkins`, etc. Two rules:
169
+
170
+ **1. Default 1:1 — one connector per plugin.**
171
+
172
+ Marketplace UX is cleanest when "install Mantis" = one entry. Each plugin
173
+ owns its `host_permissions`, version, and auth lifecycle independently.
174
+ A connector outage doesn't drag down unrelated plugins.
175
+
176
+ **2. Escape hatch: 1:N for same-vendor suites with shared auth.**
177
+
178
+ Atlassian (Jira + Confluence + Bitbucket), Google Workspace (Gmail + Drive +
179
+ Calendar), and Microsoft 365 (Teams + Outlook + SharePoint) share OAuth /
180
+ SSO across products. Forcing them into separate plugins triples the auth
181
+ dance. The plugin manifest therefore allows multiple `connectors:` entries:
182
+
183
+ ```yaml
184
+ # plugin.yaml
185
+ name: atlassian-suite
186
+ category: connector
187
+ mode: hybrid
188
+ auth: oauth2-atlassian # shared across all connectors below
189
+ connectors:
190
+ - id: jira
191
+ host_permissions: ["*://*.atlassian.net/*"]
192
+ tools: [...]
193
+ - id: confluence
194
+ host_permissions: ["*://*.atlassian.net/wiki/*"]
195
+ tools: [...]
196
+ ```
197
+
198
+ This is the **minority case**. All 5 MVP defaults (gmail / teams / jira /
199
+ github / gitlab) are 1 plugin = 1 connector.
200
+
201
+ **3. `category: connector` marks the plugin as a connector in marketplace.**
202
+
203
+ Forge plugins serve different purposes (pipeline nodes, MCP servers, tools,
204
+ connectors). The plugin manifest gets a top-level `category` field so the
205
+ marketplace and extension can filter for connectors specifically:
206
+
207
+ ```yaml
208
+ category: connector # | pipeline-node | mcp-server | tool | other
209
+ ```
210
+
211
+ Only `category: connector` plugins appear in the extension's "Connectors"
212
+ panel. See the plugin-mode ADR in `Implementation-Plan-Browser-Agent.md`
213
+ for the full plugin schema changes.
214
+
215
+ **Three-layer concept (don't conflate):**
216
+
217
+ | Layer | Unit | Example |
218
+ |---|---|---|
219
+ | Plugin | Install unit (marketplace entry) | `mantis`, `atlassian-suite` |
220
+ | Connector | Per-system adapter | `mantis`, `jira`, `confluence` |
221
+ | Capability | A method on a connector | `mantis.list_my_bugs`, `jira.get_issue` |
222
+
223
+ GitLab exposes issues, MRs, wiki, and CI — that's **one connector with many
224
+ capabilities**, not multiple connectors.
225
+
226
+ ### `ctx` API (passed to handlers)
227
+
228
+ DOM-centric. Handlers navigate, wait, query, and parse. A `fetch` is
229
+ still provided for the rare cases where an XHR endpoint of the rendered
230
+ app is the cleanest path (still inside the tab's origin, still using
231
+ cookies — but **not** for documented REST APIs that demand tokens).
232
+
233
+ ```ts
234
+ interface ConnectorContext {
235
+ /** Navigate the active tab to a path within the connector's origin. */
236
+ navigate(path: string, opts?: { waitFor?: string; timeout?: number }): Promise<void>;
237
+
238
+ /** Query the DOM. Returns null if not found. */
239
+ $(selector: string): Element | null;
240
+ $$(selector: string): Element[];
241
+
242
+ /** Wait for selector with timeout (most pages aren't ready synchronously). */
243
+ waitFor(selector: string, opts?: { timeout?: number }): Promise<Element>;
244
+
245
+ /**
246
+ * Same-origin fetch with cookies attached. Use sparingly — only for XHR
247
+ * endpoints the rendered page itself calls (i.e. cookie-authenticated).
248
+ * Do NOT use for documented REST APIs that require Authorization headers;
249
+ * those need a Tier 3 escape hatch (user-provided token).
250
+ */
251
+ fetch(input: string | URL, init?: RequestInit): Promise<Response>;
252
+
253
+ /** Take a screenshot of the current viewport (only for Tier 2 vision fallback). */
254
+ screenshot(): Promise<string>; // base64 PNG
255
+
256
+ /** Structured logging visible in the bridge dashboard. */
257
+ log(...args: any[]): void;
258
+
259
+ /** Throw this to signal "auth needed" — bridge surfaces a toast + pauses pipeline. */
260
+ AuthExpiredError: typeof Error;
261
+ }
262
+ ```
263
+
264
+ ## Execution Model: DOM Extraction First (Decision 2026-05-16)
265
+
266
+ **Browser-side connectors extract data from the rendered DOM of the user's
267
+ active tab, via the extension's Chrome MCP / scripting capabilities. They
268
+ do NOT call site REST APIs.**
269
+
270
+ This is a deliberate design choice, not a fallback.
271
+
272
+ ### Why DOM, not API?
273
+
274
+ The whole point of routing through a browser extension is to **reuse the
275
+ user's existing logged-in browser session for free**. Many enterprise
276
+ systems make this trade-off-laden:
277
+
278
+ | System | REST API auth | DOM auth |
279
+ |---|---|---|
280
+ | **MantisBT** | Requires API token (`Authorization` header). Cookies don't work on `/api/rest/*`. | PHPSESSID cookie authenticates rendered pages. ✅ |
281
+ | **GitLab** | Requires PAT (cookies don't work on `/api/v4/*` for browsers due to CSRF). | Session cookie authenticates UI pages. ✅ |
282
+ | **JIRA Server** | Requires basic auth or PAT. | Session cookie OK on rendered pages. ✅ |
283
+ | **Teams / Outlook** | Requires MSAL bearer token. | Session OK on rendered pages. ✅ |
284
+
285
+ If we used REST APIs, every connector would need the user to mint a token,
286
+ copy it into a settings page, rotate it on expiry, and deal with scope-
287
+ limited tokens that don't grant the same permissions as their UI session.
288
+ That defeats the value proposition of the extension entirely — the user
289
+ might as well configure server-side plugins with PATs.
290
+
291
+ **DOM extraction inherits the user's UI session, no token management,
292
+ ever.** The cost is paid in robustness against site redesigns — accepted.
293
+
294
+ ### Execution path
295
+
296
+ ```
297
+ LLM in extension → tool call (e.g. mantis.list_my_bugs)
298
+
299
+
300
+ extension's connector runtime:
301
+ 1. find tab matching connector's host_permissions (or open one)
302
+ 2. navigate to the appropriate URL inside that tab
303
+ 3. use Chrome MCP / scripting / page DOM to extract structured data
304
+ 4. return the data to the LLM
305
+ ```
306
+
307
+ No `fetch('/api/rest/...')`. No `Authorization` headers. Cookies are
308
+ implicit — the tab is already authenticated because the user logged in
309
+ through normal browsing.
310
+
311
+ ### Fallback tiers (in declining preference)
312
+
313
+ | Tier | Mechanism | When |
314
+ |---|---|---|
315
+ | **1 (primary)** | DOM extraction via Chrome MCP — selectors, structure parsing | Always try first |
316
+ | **2 (fallback)** | Screenshot → vision LLM | Site redesigned / selector broken |
317
+ | **3 (escape)** | Optional REST API + user-provided token | Only if user explicitly enables it in connector settings |
318
+
319
+ Tier 3 is **off by default**. We don't ship token-based fallbacks because
320
+ the friction of token management is exactly what the extension model
321
+ exists to avoid. A connector author MAY surface an optional `api_token`
322
+ setting if their site simply cannot be scraped reliably; users who want
323
+ to use it accept the trade-off explicitly.
324
+
325
+ ### What this means for connector authors
326
+
327
+ - Tools execute via DOM, not HTTP. Implementation goes in the extension,
328
+ not in REST clients.
329
+ - Tool descriptions in the manifest should describe **observable
330
+ behavior** ("list bugs assigned to me"), not the URL path or API
331
+ surface used to obtain them.
332
+ - The connector should declare which page(s) it needs open
333
+ (`host_permissions`) so the extension can route or open tabs accordingly.
334
+ - Selector breakage = expected maintenance burden. Bake stable selectors
335
+ into the connector code (e.g., `data-testid`, schema.org microdata)
336
+ and version-pin them.
337
+
338
+ ## Connector Lifecycle
339
+
340
+ ### Discovery
341
+ - Built-in connectors ship in the extension package
342
+ - Remote connectors fetched from a GitHub registry
343
+ (`forge-browser-connectors` — mirrors the `forge-crafts` model)
344
+ - Extension popup → "Marketplace" → install / update / remove
345
+
346
+ ### Activation
347
+ - When a tab navigates to a URL matching some connector's `matches`,
348
+ the extension auto-injects that connector's content script
349
+ - Bridge is notified, MCP tools are advertised dynamically
350
+ - "Connector X became available in tab Y" → Forge sees the new tools
351
+
352
+ ### Health monitoring
353
+ - Bridge runs `healthCheck()` on each active connector every 10 min
354
+ - Failures show in `Settings → Browser → Connectors` (red badge)
355
+ - Optional: notify the user via Forge UI / Telegram bot
356
+
357
+ ### Auth expiry
358
+ - Handler throws `AuthExpiredError` on 401 / login-redirect
359
+ - Bridge pauses any in-flight pipeline using that tool
360
+ - Notifies the user ("Please re-login to Mantis to resume")
361
+ - Resumes automatically when the next health check passes
362
+
363
+ ## Connector Examples (DOM Extraction)
364
+
365
+ ### MantisBT
366
+ - **Approach**: navigate the active tab to `my_view_page.php` /
367
+ `view_all_bug_page.php` / `view.php?id=<n>` and parse the rendered
368
+ HTML via Chrome MCP. PHP-rendered, structure hasn't changed in years.
369
+ - **Why not REST?** `/api/rest/*` requires a Mantis API token; cookies
370
+ don't authenticate against it. Defeats the "logged-in session for free"
371
+ model.
372
+ - **Estimated uptime**: 90%+. Self-hosted instances often theme the UI,
373
+ but core selectors (`#buglist`, `.column-id`, `.bug-status`) are stable.
374
+
375
+ ### GitLab (browser)
376
+ - **Approach**: navigate to `/-/issues/?assignee_username=me` /
377
+ `/-/merge_requests/` / `/-/profile` pages and parse the rendered DOM.
378
+ - **Why not REST?** `/api/v4/*` requires a PAT — same problem as Mantis.
379
+ - **Selectors**: GitLab uses `data-testid` and `data-qa-*` attributes
380
+ heavily — relatively stable across versions.
381
+
382
+ ### Microsoft Teams Web
383
+ - **Approach**: DOM scrape `[data-tid='message-content']` from rendered
384
+ messages. Microsoft's internal test-id attributes are surprisingly
385
+ stable across releases.
386
+ - Avoid extracting MSAL tokens — fragile, key format changes 1-2× per
387
+ year, and surfacing them ourselves is a security smell.
388
+
389
+ ### Confluence / JIRA Server (browser)
390
+ - **Approach**: DOM scrape `#main-content` and structured fields. The
391
+ rendered UI carries everything an agent needs (title, body, comments,
392
+ status), and the session cookie keeps working.
393
+
394
+ ### Internal homemade dashboards
395
+ - **Approach**: stable selectors with `data-testid` if available, else
396
+ semantic class names. Pin selector versions; expect maintenance when
397
+ the dashboard ships a redesign.
398
+
399
+ ### When DOM extraction genuinely cannot work
400
+
401
+ Some sites are pure SPAs that render via canvas, virtualize lists
402
+ aggressively, or require many sequential interactions to expose data.
403
+ For these, the connector author may surface an optional `api_token`
404
+ setting and fall back to REST. This is **opt-in per user**, not a
405
+ default code path. See "Tier 3 (escape)" in the execution model above.
406
+
407
+ ## Security
408
+
409
+ ### Trust boundaries
410
+ - Extension runs with `host_permissions` for each connector's `matches`
411
+ - Bridge ↔ extension uses a **per-installation pairing token** (random
412
+ 256-bit, generated by Forge, copied into extension on first run)
413
+ - Connector code is **not arbitrary** — must come from the marketplace,
414
+ signed by maintainer's GPG (sketched, full design TBD)
415
+
416
+ ### Destructive actions
417
+ - Connector spec marks tools as `destructive: true` (writes, deletes,
418
+ sends messages)
419
+ - Bridge presents an interactive confirmation to the user before
420
+ executing them — at least the first time per session per tool
421
+ - Audit log retains request + response for every destructive call
422
+
423
+ ### Data flow
424
+ - All connector responses pass through the bridge → Forge agent context
425
+ - **No connector data is ever sent to external LLMs without explicit
426
+ agent invocation** (i.e., the connector doesn't auto-train the agent;
427
+ the agent decides what to do with the result)
428
+
429
+ ### Compliance
430
+ - Some sites' ToS prohibit automated access. For internal company
431
+ systems and CRMs, this is a non-issue (you have a license). For
432
+ SaaS like Teams, we operate as "the user" — gray area; document
433
+ expectations in the extension's first-run consent.
434
+
435
+ ## Open Questions
436
+
437
+ 1. **Pairing token rotation** — should it be revocable per-device?
438
+ How is it rotated? Web UI?
439
+ 2. **Remote Forge** — extension currently assumes loopback bridge.
440
+ Should it support connecting to a Forge behind Cloudflare tunnel
441
+ so a phone can drive desktop's browser? Probably not v1.
442
+ 3. **Off-screen tab handling** — if a connector needs an active tab
443
+ but the user closed it, do we auto-reopen + wait, or error out?
444
+ Default: error, with optional auto-reopen flag.
445
+ 4. **Vision tier provider** — Anthropic Computer Use API vs OpenAI
446
+ gpt-4o-vision vs local model? Probably configurable.
447
+ 5. **Connector marketplace governance** — review process for accepting
448
+ new connectors? Mirrors forge-crafts but stricter (these run inside
449
+ the user's session).
450
+ 6. **Smith folding** — should `@aion0/smith-engine` (pi-coding-agent
451
+ loop + tempr memory client) be embedded in the extension as the
452
+ default chat backend? Or stay as a separate optional process?
453
+ See "Mutual-enhancement boundaries" in the agent strategy doc.
454
+
455
+ ## Implementation Phases
456
+
457
+ ### Phase 0 — Pairing + bridge skeleton (week 1)
458
+ - `lib/browser-bridge-standalone.ts` in Forge, port 8407
459
+ - Pairing UI in Forge Settings → Browser
460
+ - Minimal MV3 extension shell, popup with "Connect to Forge" button
461
+ - WebSocket handshake + ping/pong
462
+
463
+ ### Phase 1 — First connector + MCP wiring (week 2)
464
+ - Connector SDK package (`@forge/browser-connector-sdk`)
465
+ - Single connector: Mantis (assume internal install)
466
+ - MCP tools auto-registered to Forge from bridge
467
+ - End-to-end smoke: Forge agent calls `mantis.list_my_bugs` → result
468
+
469
+ ### Phase 2 — Tier 2 + monitoring (week 3)
470
+ - DOM scraping fallback in Mantis connector
471
+ - Health checks every 10 min, dashboard in Forge
472
+ - Auth expiry detection + user notification
473
+
474
+ ### Phase 3 — Multi-connector + marketplace (week 4-5)
475
+ - 2-3 more connectors (pick highest-value internal systems)
476
+ - `forge-browser-connectors` GitHub registry
477
+ - One-click install from extension marketplace
478
+ - Versioning + update detection
479
+
480
+ ### Phase 4 — Vision fallback + Computer Use bridge (later)
481
+ - Tier 3 vision LLM integration
482
+ - Configurable provider
483
+ - Reserved for known-bad sites only
484
+
485
+ ### Phase 5 — Smith integration / chat UI (separate RFC)
486
+ - Decide whether extension hosts chat UI directly
487
+ - If yes: temper-as-memory + pi-coding-agent loop in extension
488
+ - Probably one quarter out
489
+
490
+ ## Out of Scope (for this RFC)
491
+
492
+ - **Chat UI in extension popup/sidepanel** — separate design needed;
493
+ this RFC is purely about connector mechanics.
494
+ - **Mobile** — same design works in principle but each browser's
495
+ extension API differs; Phase ∞.
496
+ - **Cross-browser** (Safari, Firefox) — eventually yes, but Chrome
497
+ + Edge (Chromium) cover ~95% of enterprise.
498
+
499
+ ## References
500
+
501
+ - `RFC-…` (placeholder for future chat-UI / agent-loop RFC)
502
+ - MCP spec: <https://spec.modelcontextprotocol.io>
503
+ - pi-coding-agent: <https://github.com/earendil-works/pi>
504
+ - Anthropic Computer Use: <https://docs.anthropic.com/en/docs/build-with-claude/computer-use>
505
+ - Chrome MV3 extension docs: <https://developer.chrome.com/docs/extensions/mv3/intro>
506
+
507
+ ## Changelog
508
+
509
+ - **2026-05-16**: initial draft.
@@ -3,6 +3,7 @@
3
3
  * Agents coexist (not mutually exclusive). Each entry point can select any agent.
4
4
  */
5
5
 
6
+ import { execFileSync } from 'node:child_process';
6
7
  import { loadSettings } from '../settings';
7
8
  import type { AgentAdapter, AgentConfig, AgentId } from './types';
8
9
  import { createClaudeAdapter, detectClaude } from './claude-adapter';
@@ -13,6 +14,38 @@ export type { AgentAdapter, AgentConfig, AgentId } from './types';
13
14
  // Module-level cache
14
15
  const adapterCache = new Map<AgentId, AgentAdapter>();
15
16
 
17
+ /** Probe the installed codex CLI's `--help` and return whichever
18
+ * "skip all approvals / sandbox" flag it actually accepts.
19
+ *
20
+ * - Modern codex: `--dangerously-bypass-approvals-and-sandbox`
21
+ * - Legacy codex: `--full-auto`
22
+ *
23
+ * If neither shows up, default to the modern flag so users can still override
24
+ * the value in settings.yaml if a future CLI renames it again.
25
+ */
26
+ const codexSkipFlagCache = new Map<string, string>();
27
+ export function probeCodexSkipFlag(binary = 'codex'): string {
28
+ const cached = codexSkipFlagCache.get(binary);
29
+ if (cached) return cached;
30
+
31
+ let help = '';
32
+ try {
33
+ help = execFileSync(binary, ['--help'], { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
34
+ } catch {
35
+ // `--help` might exit non-zero on some versions — also try without args.
36
+ try { help = execFileSync(binary, [], { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }); } catch {}
37
+ }
38
+
39
+ const flag = help.includes('--dangerously-bypass-approvals-and-sandbox')
40
+ ? '--dangerously-bypass-approvals-and-sandbox'
41
+ : help.includes('--full-auto')
42
+ ? '--full-auto'
43
+ : '--dangerously-bypass-approvals-and-sandbox';
44
+
45
+ codexSkipFlagCache.set(binary, flag);
46
+ return flag;
47
+ }
48
+
16
49
  /** Get all configured agents */
17
50
  export function listAgents(): AgentConfig[] {
18
51
  const settings = loadSettings();
@@ -30,7 +63,7 @@ export function listAgents(): AgentConfig[] {
30
63
  const codex = detectAgent('codex', 'OpenAI Codex', codexConfig?.path || 'codex', ['exec']);
31
64
  if (codex) {
32
65
  codex.capabilities.requiresTTY = false; // exec subcommand is non-interactive
33
- agents.push({ ...codex, enabled: codexConfig?.enabled !== false, detected: true, skipPermissionsFlag: codexConfig?.skipPermissionsFlag || '--full-auto', cliType: 'codex' } as any);
66
+ agents.push({ ...codex, enabled: codexConfig?.enabled !== false, detected: true, skipPermissionsFlag: codexConfig?.skipPermissionsFlag || probeCodexSkipFlag(codexConfig?.path || 'codex'), cliType: 'codex' } as any);
34
67
  }
35
68
 
36
69
  // Aider
@@ -193,6 +226,7 @@ export interface TerminalLaunchInfo {
193
226
  cliType: string; // claude-code, codex, aider, generic
194
227
  supportsSession: boolean; // has session files to resume
195
228
  resumeFlag: string; // -c, --resume, etc.
229
+ skipPermissionsFlag?: string; // --dangerously-skip-permissions (claude), version-detected for codex, --yes (aider)
196
230
  env?: Record<string, string>; // profile env vars to export
197
231
  model?: string; // profile model override (--model flag)
198
232
  }
@@ -208,13 +242,16 @@ export function resolveTerminalLaunch(agentId?: string): TerminalLaunchInfo {
208
242
  || (agentId === 'codex' ? 'codex' : agentId === 'aider' ? 'aider' : 'claude-code');
209
243
 
210
244
  // Determine CLI command and capabilities from cliType
211
- const cliMap: Record<string, { cmd: string; session: boolean; resume: string }> = {
212
- 'claude-code': { cmd: 'claude', session: true, resume: '--resume' },
213
- 'codex': { cmd: 'codex', session: false, resume: '' },
214
- 'aider': { cmd: 'aider', session: false, resume: '' },
215
- 'generic': { cmd: agentCfg.path || agentId || 'claude', session: false, resume: '' },
245
+ const cliMap: Record<string, { cmd: string; session: boolean; resume: string; skip: string }> = {
246
+ 'claude-code': { cmd: 'claude', session: true, resume: '--resume', skip: '--dangerously-skip-permissions' },
247
+ 'codex': { cmd: 'codex', session: false, resume: '', skip: '' },
248
+ 'aider': { cmd: 'aider', session: false, resume: '', skip: '--yes' },
249
+ 'generic': { cmd: agentCfg.path || agentId || 'claude', session: false, resume: '', skip: '' },
216
250
  };
217
251
  const cli = cliMap[cliType] || cliMap['claude-code'];
252
+ const skipPermissionsFlag = agentCfg.skipPermissionsFlag
253
+ || (cliType === 'codex' ? probeCodexSkipFlag(agentCfg.path || 'codex') : cli.skip)
254
+ || undefined;
218
255
 
219
256
  // Resolve env/model: either from this agent's own profile fields, or from linked profile
220
257
  let env: Record<string, string> | undefined;
@@ -239,6 +276,7 @@ export function resolveTerminalLaunch(agentId?: string): TerminalLaunchInfo {
239
276
  cliType,
240
277
  supportsSession: cli.session,
241
278
  resumeFlag: agentCfg.resumeFlag || cli.resume,
279
+ skipPermissionsFlag,
242
280
  env,
243
281
  model,
244
282
  };
@@ -22,7 +22,7 @@ export interface AgentConfig {
22
22
  flags?: string[]; // extra CLI flags
23
23
  capabilities: AgentCapabilities;
24
24
  version?: string;
25
- skipPermissionsFlag?: string; // e.g., "--dangerously-skip-permissions", "--full-auto"
25
+ skipPermissionsFlag?: string; // e.g., "--dangerously-skip-permissions" (claude), "--dangerously-bypass-approvals-and-sandbox" (codex), "--yes" (aider)
26
26
  // Profile fields
27
27
  base?: string; // base agent ID — makes this a profile
28
28
  isProfile?: boolean; // true if this is a profile (not a base agent)