@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,364 @@
1
+ # Connector — Declarative Extraction Spec
2
+
3
+ > **Goal**: Adding a new browser-side connector should be a Forge-only change.
4
+ > No `pnpm build && release` of the browser extension required.
5
+ >
6
+ > **Today**: Forge ships the manifest (schema), Extension ships a hand-written
7
+ > handler (`mantis.ts`, eventually `gitlab.ts`, …). Adding a new connector
8
+ > means coordinated changes in two repos.
9
+ >
10
+ > **Target**: Manifest carries the extraction script. Extension is a generic
11
+ > runner that knows nothing about specific connectors.
12
+
13
+ ---
14
+
15
+ ## 1. Architecture before / after
16
+
17
+ ### Today
18
+ ```
19
+ Forge Extension
20
+ ───── ─────────
21
+ mantis.yaml ──────────────▶ list of tool schemas
22
+ (schema only) │
23
+ │ LLM calls mantis.list_my_bugs
24
+
25
+ mantis.ts (hand-written, ~300 LOC)
26
+ ├─ findMantisTab() ◀── Mantis-specific
27
+ ├─ ensureOnListPage() ◀── Mantis URLs hardcoded
28
+ ├─ extractBugList() ◀── #bug_list selector hardcoded
29
+ └─ Forge column quirks ◀── per-instance hardcoded
30
+
31
+
32
+ chrome.scripting.executeScript → result
33
+ ```
34
+ **Adding GitLab**: write `gitlab.ts` in extension, ship new extension version.
35
+
36
+ ### Target
37
+ ```
38
+ Forge Extension
39
+ ───── ─────────
40
+ mantis.yaml src/lib/connectors/runner.ts
41
+ tools: (≤ 100 LOC, generic)
42
+ list_my_bugs:
43
+ page: { url, on_target, ... } │ LLM calls mantis.list_my_bugs
44
+ script: | ──────────────────────▶│
45
+ const list = document.qs(...) │
46
+ return { bugs, ... } ▼
47
+ runConnectorTool(plugin, tool, args)
48
+ ├─ acquireTab(host_match, url)
49
+ ├─ waitForLoad
50
+ ├─ detectLoginRedirect
51
+ └─ executeScript(script, args) → result
52
+ ```
53
+ **Adding GitLab**: drop `gitlab.yaml` in Forge with `page` + `script` per tool. Done.
54
+
55
+ ---
56
+
57
+ ## 2. Schema change to plugin YAML
58
+
59
+ A connector plugin's `tools` map gets two new keys per tool:
60
+ `page` (where to extract from) and `script` (the in-page function body).
61
+
62
+ ```yaml
63
+ id: mantis
64
+ name: MantisBT
65
+ icon: "🐞"
66
+ version: "0.2.0" # bump on schema change
67
+ category: connector
68
+ mode: browser-side
69
+
70
+ settings:
71
+ base_url:
72
+ type: string
73
+ label: Mantis base URL
74
+ required: true
75
+
76
+ # plugin-level: how to find / open an authenticated tab on this host
77
+ host_match: "{base_url}/*" # Chrome match pattern, used to find existing tabs
78
+ login_redirect: "/login_page.php" # substring; if URL contains this after nav, abort
79
+
80
+ tools:
81
+ list_my_bugs:
82
+ description: List bugs assigned to me.
83
+ parameters:
84
+ status:
85
+ type: select
86
+ options: ["open", "closed", "all"]
87
+ default: "open"
88
+ limit:
89
+ type: number
90
+ default: 50
91
+
92
+ page:
93
+ url: "{base_url}/view_all_bug_page.php"
94
+ on_target: "/view_all_bug_page.php" # substring match; if current URL has this, don't navigate
95
+
96
+ # Function body. Receives `args` (the LLM's parsed parameters).
97
+ # Returns a JSON-serializable value. Top-level Web APIs only.
98
+ # NO closures over Forge / extension globals — this runs in the user's tab.
99
+ script: |
100
+ const { status, limit } = args;
101
+ const list = document.querySelector('#bug_list');
102
+ if (!list) return { bugs: [], total: 0, _error: '#bug_list not found' };
103
+ // ... ports the body of mantis.ts/extractBugList() ...
104
+ return { bugs, total, _page: location.href };
105
+ ```
106
+
107
+ ### Field reference
108
+
109
+ | Field | Level | Required | Notes |
110
+ |---|---|---|---|
111
+ | `host_match` | plugin | yes (browser-side) | Used by extension to find/open tabs. Chrome match pattern with `{settings.*}` substitution. |
112
+ | `login_redirect` | plugin | optional | Substring. If the tab URL contains this after navigation, treat as "user not logged in". |
113
+ | `page.url` | tool | yes | Template URL to navigate to. `{base_url}` etc. expanded from settings. |
114
+ | `page.on_target` | tool | optional | If current tab URL contains this substring, skip the navigation step (current page is fine). |
115
+ | `script` | tool | yes | Function body. Implicit param: `args`. Must return a JSON-serializable value. |
116
+
117
+ ### Multi-step tools (defer)
118
+
119
+ `add_comment`-style tools that POST need form interaction (fill input + click submit + wait for redirect). For v1 we only spec read-only extraction. Destructive tools stay marked `destructive: true` but **`script` is allowed to call `fetch()` from within the tab context** (same-origin, cookies auto-attached) for cases where the rendered HTML form is too brittle. That's a per-connector decision the script author makes.
120
+
121
+ ---
122
+
123
+ ## 3. HTTP API change — `GET /api/connectors`
124
+
125
+ Today's response (per `app/api/connectors/route.ts`):
126
+ ```json
127
+ {
128
+ "connectors": [
129
+ {
130
+ "plugin_id": "mantis",
131
+ "name": "MantisBT",
132
+ "mode": "browser-side",
133
+ "installed": true,
134
+ "entries": [
135
+ {
136
+ "id": "mantis",
137
+ "host_permissions": ["..."],
138
+ "tools": {
139
+ "list_my_bugs": {
140
+ "description": "...",
141
+ "parameters": {...},
142
+ "returns": "..."
143
+ }
144
+ },
145
+ "settings": {...}
146
+ }
147
+ ]
148
+ }
149
+ ]
150
+ }
151
+ ```
152
+
153
+ After this change, **`tools[name]` must additionally include `page` and `script`** (and the plugin level needs `host_match` + `login_redirect`):
154
+
155
+ ```json
156
+ {
157
+ "connectors": [
158
+ {
159
+ "plugin_id": "mantis",
160
+ "name": "MantisBT",
161
+ "mode": "browser-side",
162
+ "installed": true,
163
+ "host_match": "https://mantis.acme.com/*",
164
+ "login_redirect": "/login_page.php",
165
+ "entries": [
166
+ {
167
+ "id": "mantis",
168
+ "tools": {
169
+ "list_my_bugs": {
170
+ "description": "...",
171
+ "parameters": {...},
172
+ "page": {
173
+ "url": "https://mantis.acme.com/view_all_bug_page.php",
174
+ "on_target": "/view_all_bug_page.php"
175
+ },
176
+ "script": "const { status, limit } = args; ... return { bugs, total };"
177
+ }
178
+ },
179
+ "settings": {...}
180
+ }
181
+ ]
182
+ }
183
+ ]
184
+ }
185
+ ```
186
+
187
+ Note `{base_url}` / `{settings.base_url}` is **expanded server-side** using the user's saved settings before the response goes out — extension shouldn't need to know template syntax. (If the user hasn't saved settings yet, omit the tool from the listing or return `null` for `page.url`.)
188
+
189
+ ---
190
+
191
+ ## 4. Template substitution rules
192
+
193
+ Anywhere `page.url`, `host_match`, `login_redirect` appear, expand `{<key>}` tokens:
194
+
195
+ - `{base_url}` → `settings.base_url` (with trailing slashes stripped)
196
+ - `{settings.<name>}` → `settings.<name>` (verbose form)
197
+ - Unknown tokens → leave as literal `{foo}` (don't error — gives author a way to ship a static URL with curly braces)
198
+
199
+ Do this in Forge before returning the manifest. Avoids re-implementing on the extension side.
200
+
201
+ ---
202
+
203
+ ## 5. Script execution contract (what the extension will do)
204
+
205
+ The extension will:
206
+ 1. Take `tool.script` as a string
207
+ 2. Find or open a tab matching `host_match`, navigating to `page.url` unless `page.on_target` says current URL is fine
208
+ 3. Detect `login_redirect` substring in tab URL → return `{ error: 'login required', loginRequired: true }`
209
+ 4. Run via `chrome.scripting.executeScript`:
210
+ ```js
211
+ // executes IN THE USER'S TAB (Mantis page context)
212
+ func: function(scriptText, args) {
213
+ try {
214
+ const fn = new Function('args', scriptText);
215
+ return { ok: true, value: fn(args) };
216
+ } catch (err) {
217
+ return { ok: false, error: err.message };
218
+ }
219
+ }
220
+ ```
221
+ 5. Return `result.value` to the LLM as the tool result
222
+
223
+ **Script authors should assume**:
224
+ - `document`, `fetch`, `URL`, etc. are available
225
+ - Same-origin cookies are attached to `fetch()` calls
226
+ - No access to `chrome.*` APIs (running in page context)
227
+ - No closures over Forge/extension code — script is a self-contained function body
228
+ - Return value must be JSON-serializable (no DOM nodes, no functions)
229
+ - Errors should be caught and returned as `{ _error: '...' }` shape; thrown errors are also caught by the runner
230
+
231
+ ---
232
+
233
+ ## 6. Security considerations
234
+
235
+ This change moves trust: extension now executes arbitrary JS shipped by Forge. Constraints:
236
+
237
+ | Risk | Mitigation |
238
+ |---|---|
239
+ | Compromised Forge serves malicious script | Forge is already a trusted authenticated peer (`X-Forge-Token`). User explicitly chose to connect. Same trust level as `npm install`. |
240
+ | Script reads from / writes to sites it shouldn't | Run only in the tab matching `host_match`; never inject into arbitrary tabs. |
241
+ | `new Function` blocked by page CSP | Most internal corp tools have permissive CSPs. Strict-CSP sites (github.com, banks) will fail; document as a known limitation. For those, write a server-side connector instead (REST with token). |
242
+ | Script exfiltrates page content | Already inherent to the "scrape user's page" design — script could always do this. Extension UI should log connector tool calls so user can audit. |
243
+
244
+ **Recommendation for v1**: log every script run to extension console with `{plugin_id, tool, tab_url, script_length}`. Add a "Connector audit log" panel in Settings later.
245
+
246
+ ---
247
+
248
+ ## 7. Concrete: full migration of `mantis.yaml`
249
+
250
+ The current `mantis.ts` in extension has these page-script blocks:
251
+
252
+ - `extractBugList(args)` — for `list_my_bugs`, runs on `/view_all_bug_page.php`
253
+ - `extractBugDetail(args)` — for `get_bug`, runs on `/bug_view_page.php?bug_id=N`
254
+
255
+ These two functions get **copy-pasted into the YAML's `script` fields** (minus type annotations — script is plain JS). Their function bodies are already self-contained, so the port is mechanical:
256
+
257
+ ```yaml
258
+ id: mantis
259
+ name: MantisBT
260
+ version: "0.2.0"
261
+ category: connector
262
+ mode: browser-side
263
+
264
+ settings:
265
+ base_url: { type: string, required: true, label: "Mantis base URL" }
266
+ default_project: { type: string, label: "Default project" }
267
+
268
+ host_match: "{base_url}/*"
269
+ login_redirect: "/login_page.php"
270
+
271
+ tools:
272
+ list_my_bugs:
273
+ description: |
274
+ List bugs assigned to the current user.
275
+ parameters:
276
+ status:
277
+ type: select
278
+ options: ["open", "closed", "all"]
279
+ default: "open"
280
+ limit:
281
+ type: number
282
+ default: 50
283
+ page:
284
+ url: "{base_url}/view_all_bug_page.php"
285
+ on_target: "/view_all_bug_page.php"
286
+ script: |
287
+ // Body of current extractBugList in src/lib/connectors/mantis.ts
288
+ // (paste lines 69–148 of mantis.ts here, drop the function signature)
289
+ const list = document.querySelector('#bug_list');
290
+ if (!list) return { bugs: [], total: 0, _error: '#bug_list not found' };
291
+ // ... etc
292
+ return { bugs: filtered.slice(0, args.limit), total: filtered.length };
293
+
294
+ get_bug:
295
+ description: |
296
+ Get full details of a single bug.
297
+ parameters:
298
+ bug_id:
299
+ type: number
300
+ required: true
301
+ page:
302
+ # Note: bug_id is in args, so we use it in the URL via template
303
+ # extension expands `{args.bug_id}` at run-time (this is the one case
304
+ # where args, not settings, drives the URL — special-case in spec).
305
+ url: "{base_url}/bug_view_page.php?bug_id={args.bug_id}"
306
+ on_target: "/bug_view_page.php"
307
+ script: |
308
+ // Body of current extractBugDetail
309
+ ...
310
+ ```
311
+
312
+ > ⚠ **`{args.*}` substitution in URLs is the one extra template feature** —
313
+ > needed for tools that target a specific record by ID. Forge needs to do
314
+ > two passes: settings-time substitution at API response time, then args-time
315
+ > substitution inside the extension when actually executing the tool. (Or
316
+ > Forge can leave `{args.*}` literals unexpanded; the extension finishes
317
+ > them.) **Recommendation**: leave `{args.*}` for the extension to expand
318
+ > from the LLM's input; Forge only expands `{settings.*}` / `{base_url}`.
319
+
320
+ The `mantis.probe.js` file stays as a dev tool; it's not shipped to extension at runtime.
321
+
322
+ ---
323
+
324
+ ## 8. Migration order
325
+
326
+ To avoid breaking the extension during the change:
327
+
328
+ 1. **Forge: add the schema fields, no behavior change**
329
+ - Extend manifest parser to accept `page` + `script` + `host_match` + `login_redirect`. Tools missing them are still valid (old shape).
330
+ - Update `/api/connectors` to emit the new fields when present, omit when absent.
331
+ - Mantis manifest gains `page` + `script` for both tools. Bump version `0.1.1` → `0.2.0`.
332
+ - Verify with curl: `/api/connectors/mantis` returns the new shape, fields populated.
333
+
334
+ 2. **Extension: write the generic runner alongside the existing per-connector handler**
335
+ - New file `src/lib/connectors/runner.ts` implementing `runConnectorTool(connector, toolName, input)`.
336
+ - Update `build-tools.ts` so a tool is "executable" if its manifest has `script` (not "if `hasHandler(plugin_id)`").
337
+ - Routing: if tool has `script` → runner; else (fallback) → old `dispatchConnector(plugin_id, …)` for backward compat.
338
+
339
+ 3. **Cut over**
340
+ - Once the runner works for `mantis.list_my_bugs` end-to-end, delete `src/lib/connectors/mantis.ts` and the `mantis` entry in `registry.ts`.
341
+ - Remove the fallback branch in routing.
342
+
343
+ 4. **Add second connector to prove the loop**
344
+ - Pick the simplest GitLab page (e.g. "my issues") and write the YAML. Confirm extension picks it up with zero extension-side changes.
345
+
346
+ ---
347
+
348
+ ## 9. Acceptance criteria
349
+
350
+ A connector is "shippable via Forge-only changes" iff:
351
+
352
+ - ✅ Adding a new `<name>.yaml` in `lib/builtin-plugins/` makes the tool visible in extension on next Connectors-tab refresh.
353
+ - ✅ Saving connector settings (`base_url`) makes the tool callable by the LLM.
354
+ - ✅ LLM calls succeed end-to-end without rebuilding/reloading the extension binary.
355
+ - ✅ Existing `mantis` connector continues to work throughout the migration (no flag day).
356
+ - ✅ Bumping a connector's `version` and changing only the YAML's `script` body propagates to all extension users on next refresh — no extension release needed.
357
+
358
+ ---
359
+
360
+ ## 10. Open questions for Forge side
361
+
362
+ - [ ] Where does the YAML's `script` body actually live in a multi-line YAML? Confirm Forge's YAML parser preserves it verbatim (it should — block scalar `|` is the right syntax).
363
+ - [ ] For users on `installed=false`, omit `page.url` / `script` from the response, or include them so the marketplace UI can preview? (Recommendation: include, but with `{settings.base_url}` left literal, so consumer can preview.)
364
+ - [ ] Probe scripts (`mantis.probe.js`) — keep as devtool only, or also serve via `/api/connectors/<id>/probe` so the extension can re-run probe on a "selectors broken" report? (Recommendation: defer — solve when selectors actually break.)