@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.
- package/.forge/mcp.json +8 -0
- package/.forge/worktrees/pipeline-0a33c50d/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-0a33c50d/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-2ba01c10/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-2ba01c10/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-3156a8b3/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-3156a8b3/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-316c6574/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-316c6574/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-44a94121/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-44a94121/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-d1757a50/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-d1757a50/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-d59c2fe2/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-d59c2fe2/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-d6a6ef23/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-d6a6ef23/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-e7f78b7a/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-e7f78b7a/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-e97c13c7/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-e97c13c7/lib/help-docs/07-projects.md +1 -1
- package/.forge/worktrees/pipeline-ecd7cb0f/lib/help-docs/01-settings.md +5 -5
- package/.forge/worktrees/pipeline-ecd7cb0f/lib/help-docs/07-projects.md +1 -1
- package/CLAUDE.md +2 -2
- package/RELEASE_NOTES.md +101 -5
- package/app/api/auth/check/route.ts +18 -0
- package/app/api/browser-bridge/route.ts +70 -0
- package/app/api/chat/sessions/[id]/events/route.ts +17 -0
- package/app/api/chat/sessions/[id]/fork/route.ts +15 -0
- package/app/api/chat/sessions/[id]/messages/route.ts +21 -0
- package/app/api/chat/sessions/[id]/route.ts +23 -0
- package/app/api/chat/sessions/route.ts +12 -0
- package/app/api/chat/temper-ping/route.ts +18 -0
- package/app/api/chat-proxy/[...path]/route.ts +83 -0
- package/app/api/connector-tool/route.ts +38 -0
- package/app/api/connectors/[id]/settings/route.ts +112 -0
- package/app/api/connectors/route.ts +108 -0
- package/app/api/health/tools/route.ts +14 -0
- package/app/api/issue-scanner-gitlab/route.ts +95 -0
- package/app/api/jobs/[id]/reset_dedup/route.ts +15 -0
- package/app/api/jobs/[id]/route.ts +31 -0
- package/app/api/jobs/[id]/run/route.ts +44 -0
- package/app/api/jobs/[id]/runs/[runId]/route.ts +15 -0
- package/app/api/jobs/[id]/runs/route.ts +15 -0
- package/app/api/jobs/preview/route.ts +193 -0
- package/app/api/jobs/route.ts +36 -0
- package/app/api/notify/test/route.ts +39 -7
- package/app/api/pipelines/[id]/route.ts +10 -1
- package/app/api/pipelines/route.ts +16 -2
- package/app/api/plugins/route.ts +40 -8
- package/app/api/project-sessions/route.ts +50 -10
- package/app/api/settings/route.ts +13 -0
- package/app/chat/page.tsx +531 -0
- package/bin/forge-server.mjs +3 -1
- package/cli/chat.ts +283 -0
- package/cli/jobs.ts +176 -0
- package/cli/mw.ts +28 -1
- package/cli/worktree.ts +245 -0
- package/components/ConnectorsPanel.tsx +275 -0
- package/components/Dashboard.tsx +90 -37
- package/components/JobsView.tsx +361 -0
- package/components/LogViewer.tsx +12 -2
- package/components/PipelineView.tsx +275 -56
- package/components/PluginsPanel.tsx +3 -1
- package/components/SettingsModal.tsx +229 -40
- package/components/SkillsPanel.tsx +12 -4
- package/components/TerminalLauncher.tsx +3 -1
- package/components/WebTerminal.tsx +32 -9
- package/components/WorkspaceView.tsx +18 -10
- package/docs/Connector-DeclarativeExtract-Handoff.md +471 -0
- package/docs/Connector-DeclarativeExtract-Spec.md +364 -0
- package/docs/Implementation-Plan-Browser-Agent.md +487 -0
- package/docs/Jobs-Design.md +240 -0
- package/docs/LOCAL-DEPLOY.md +3 -3
- package/docs/RFC-Browser-Connectors.md +509 -0
- package/lib/agents/index.ts +44 -6
- package/lib/agents/types.ts +1 -1
- package/lib/browser-bridge-standalone.ts +317 -0
- package/lib/builtin-plugins/github-api.yaml +93 -0
- package/lib/builtin-plugins/gitlab.yaml +860 -0
- package/lib/builtin-plugins/mantis.probe.js +176 -0
- package/lib/builtin-plugins/mantis.yaml +964 -0
- package/lib/builtin-plugins/pmdb.yaml +178 -0
- package/lib/builtin-plugins/teams.yaml +913 -0
- package/lib/chat/__test__/smoke.ts +30 -0
- package/lib/chat/agent-loop.ts +523 -0
- package/lib/chat/bridge-client.ts +59 -0
- package/lib/chat/llm/anthropic.ts +99 -0
- package/lib/chat/llm/index.ts +20 -0
- package/lib/chat/llm/openai.ts +215 -0
- package/lib/chat/llm/types.ts +42 -0
- package/lib/chat/local-memory.ts +300 -0
- package/lib/chat/memory-store.ts +87 -0
- package/lib/chat/memory-tools.ts +157 -0
- package/lib/chat/protocols/http.ts +118 -0
- package/lib/chat/protocols/shell.ts +101 -0
- package/lib/chat/proxy.ts +51 -0
- package/lib/chat/session-store.ts +272 -0
- package/lib/chat/telegram-bridge.ts +276 -0
- package/lib/chat/temper.ts +281 -0
- package/lib/chat/tool-dispatcher.ts +190 -0
- package/lib/chat/types.ts +50 -0
- package/lib/chat-standalone.ts +286 -0
- package/lib/crypto.ts +1 -1
- package/lib/health.ts +131 -0
- package/lib/help-docs/00-overview.md +2 -1
- package/lib/help-docs/01-settings.md +46 -25
- package/lib/help-docs/07-projects.md +1 -1
- package/lib/help-docs/10-troubleshooting.md +10 -2
- package/lib/help-docs/16-gitlab-autofix.md +114 -0
- package/lib/help-docs/17-connectors.md +322 -0
- package/lib/help-docs/18-chrome-mcp.md +134 -0
- package/lib/help-docs/19-jobs.md +140 -0
- package/lib/help-docs/20-mantis-bug-fix.md +115 -0
- package/lib/help-docs/CLAUDE.md +10 -0
- package/lib/init.ts +137 -50
- package/lib/iso-time.ts +30 -0
- package/lib/issue-scanner-gitlab.ts +281 -0
- package/lib/jobs/dispatcher.ts +217 -0
- package/lib/jobs/scheduler.ts +334 -0
- package/lib/jobs/store.ts +319 -0
- package/lib/jobs/types.ts +117 -0
- package/lib/pipeline-scheduler.ts +1 -6
- package/lib/pipeline.ts +790 -10
- package/lib/plugins/registry.ts +133 -8
- package/lib/plugins/templates.ts +83 -0
- package/lib/plugins/types.ts +140 -1
- package/lib/session-watcher.ts +36 -10
- package/lib/settings.ts +65 -33
- package/lib/skills.ts +3 -1
- package/lib/task-manager.ts +50 -22
- package/lib/telegram-bot.ts +71 -0
- package/lib/terminal-standalone.ts +58 -36
- package/lib/workspace/orchestrator.ts +1 -0
- package/middleware.ts +10 -0
- package/package.json +3 -2
- package/scripts/bench/README.md +1 -1
- package/scripts/bench/tasks/01-text-utils/validator.sh +1 -1
- package/scripts/bench/tasks/02-pagination/setup.sh +1 -1
- package/scripts/bench/tasks/02-pagination/validator.sh +1 -1
- package/scripts/bench/tasks/03-bug-fix/setup.sh +1 -1
- package/scripts/bench/tasks/03-bug-fix/validator.sh +1 -1
- 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.
|
package/lib/agents/index.ts
CHANGED
|
@@ -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 || '
|
|
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
|
};
|
package/lib/agents/types.ts
CHANGED
|
@@ -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", "--
|
|
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)
|