@jslee124/forge 0.3.0 → 0.3.1

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 (35) hide show
  1. package/dist/index.js +1177 -90
  2. package/package.json +2 -1
  3. package/resources/docs/en/ARCHITECTURE.md +519 -0
  4. package/resources/docs/en/AUTHENTICATION.md +224 -0
  5. package/resources/docs/en/CLI_UI.md +266 -0
  6. package/resources/docs/en/CONFIGURATION.md +263 -0
  7. package/resources/docs/en/CONTEXT_MANAGEMENT.md +692 -0
  8. package/resources/docs/en/GETTING_STARTED.md +241 -0
  9. package/resources/docs/en/PLUGINS.md +622 -0
  10. package/resources/docs/en/PRODUCT.md +157 -0
  11. package/resources/docs/en/PROJECT_CONTEXT.md +225 -0
  12. package/resources/docs/en/RELEASING.md +94 -0
  13. package/resources/docs/en/SECURITY.md +272 -0
  14. package/resources/docs/en/SESSIONS.md +134 -0
  15. package/resources/docs/en/TROUBLESHOOTING.md +256 -0
  16. package/resources/docs/index.json +24334 -0
  17. package/resources/docs/zh-CN/ARCHITECTURE.md +174 -0
  18. package/resources/docs/zh-CN/AUTHENTICATION.md +96 -0
  19. package/resources/docs/zh-CN/CLI_UI.md +112 -0
  20. package/resources/docs/zh-CN/CONFIGURATION.md +221 -0
  21. package/resources/docs/zh-CN/CONTEXT_MANAGEMENT.md +200 -0
  22. package/resources/docs/zh-CN/GETTING_STARTED.md +193 -0
  23. package/resources/docs/zh-CN/PLUGINS.md +286 -0
  24. package/resources/docs/zh-CN/PRODUCT.md +86 -0
  25. package/resources/docs/zh-CN/PROJECT_CONTEXT.md +130 -0
  26. package/resources/docs/zh-CN/RELEASING.md +86 -0
  27. package/resources/docs/zh-CN/SECURITY.md +92 -0
  28. package/resources/docs/zh-CN/SESSIONS.md +69 -0
  29. package/resources/docs/zh-CN/TROUBLESHOOTING.md +185 -0
  30. package/resources/skills/forge-plugin-creator/SKILL.md +70 -0
  31. package/resources/skills/forge-plugin-creator/references/plugin-api.md +36 -0
  32. package/resources/skills/forge-plugin-creator/templates/index.mjs +30 -0
  33. package/resources/skills/forge-plugin-creator/templates/plugin.json +8 -0
  34. package/resources/skills/forge-plugin-creator/templates/plugin.test-template.ts +14 -0
  35. package/resources/skills/forge-product-help/SKILL.md +16 -0
@@ -0,0 +1,622 @@
1
+ > Forge can create plugins. Ask the connected model to build one for your use
2
+ > case and point it to this document.
3
+
4
+ # Plugin authoring guide
5
+
6
+ 简体中文 · Documentation index
7
+
8
+ Forge 0.3.1 works without plugins. A plugin is an optional in-process JavaScript
9
+ module that can register model-callable tools and explicit local commands,
10
+ contribute instructions, observe immutable run events, or make policy stricter.
11
+
12
+ This page is both a human guide and the contract a coding model should follow
13
+ when writing a Forge plugin. The checked-in implementation is authoritative:
14
+ `packages/plugin-api/src/types.ts`,
15
+ `schema.ts`, and
16
+ `host.ts`.
17
+
18
+ ## Contents
19
+
20
+ - [Quick start](#quick-start)
21
+ - [Locations, enablement, and trust](#locations-enablement-and-trust)
22
+ - [Plugin anatomy](#plugin-anatomy)
23
+ - [Manifest version 1](#manifest-version-1)
24
+ - [Activation and API reference](#activation-and-api-reference)
25
+ - [Custom tools](#custom-tools)
26
+ - [Other extension points](#other-extension-points)
27
+ - [Discovery and run lifecycle](#discovery-and-run-lifecycle)
28
+ - [Portable Skills are different](#portable-skills-are-different)
29
+ - [Testing a plugin](#testing-a-plugin)
30
+ - [Web tools example](#web-tools-example)
31
+ - [Instructions for a model author](#instructions-for-a-model-author)
32
+ - [Security boundary](#security-boundary)
33
+ - [Deliberate limitations](#deliberate-limitations)
34
+
35
+ ## Quick start
36
+
37
+ Create a user plugin at `$FORGE_HOME/plugins/count-text/` (normally
38
+ `~/.forge/plugins/count-text/`):
39
+
40
+ ```text
41
+ count-text/
42
+ |-- plugin.json
43
+ `-- index.mjs
44
+ ```
45
+
46
+ `plugin.json`:
47
+
48
+ ```json
49
+ {
50
+ "schemaVersion": 1,
51
+ "apiVersion": "1",
52
+ "name": "count-text",
53
+ "version": "1.0.0",
54
+ "entry": "./index.mjs",
55
+ "capabilities": ["tools:register"]
56
+ }
57
+ ```
58
+
59
+ `index.mjs`:
60
+
61
+ ```js
62
+ export default function activate(api) {
63
+ const inputSchema = api.z
64
+ .object({ text: api.z.string().max(10000) })
65
+ .strict();
66
+
67
+ api.registerTool({
68
+ name: "count_text",
69
+ description: "Count Unicode characters in supplied text.",
70
+ risk: "read",
71
+ inputSchema,
72
+ execute: async (input) => {
73
+ const parsed = inputSchema.safeParse(input);
74
+ if (!parsed.success) {
75
+ return {
76
+ ok: false,
77
+ error: {
78
+ code: "invalid_input",
79
+ message: "Invalid input for count_text.",
80
+ retryable: false
81
+ }
82
+ };
83
+ }
84
+ return {
85
+ ok: true,
86
+ output: { characters: Array.from(parsed.data.text).length },
87
+ truncated: false
88
+ };
89
+ }
90
+ });
91
+ }
92
+ ```
93
+
94
+ Enable it in the user-level `$FORGE_HOME/config.json`:
95
+
96
+ ```json
97
+ {
98
+ "schemaVersion": 1,
99
+ "plugins": {
100
+ "enabled": ["count-text"]
101
+ }
102
+ }
103
+ ```
104
+
105
+ Then verify discovery and start Forge:
106
+
107
+ ```bash
108
+ forge plugins list
109
+ forge
110
+ ```
111
+
112
+ The blue startup frame lists enabled user plugins, trusted or skipped project
113
+ plugins, and discovered built-in, user, and project Skills. This listing reads manifests and Skill
114
+ metadata only; it does not import plugin entry files early.
115
+
116
+ ## Locations, enablement, and trust
117
+
118
+ Forge supports two scopes:
119
+
120
+ | Scope | Location | How it becomes loadable |
121
+ | --- | --- | --- |
122
+ | User | `$FORGE_HOME/plugins/<name>/` | Add the name to user `plugins.enabled` |
123
+ | Project | `<workspace>/.forge/plugins/<name>/` | Run `forge plugins trust` for the canonical workspace |
124
+
125
+ Project configuration cannot set `plugins.enabled`. User configuration cannot
126
+ silently trust project code. Project trust is stored outside the repository in
127
+ `$FORGE_HOME/plugin-trust.json` and is keyed by the canonical workspace path.
128
+
129
+ ```bash
130
+ forge plugins list
131
+ forge plugins trust
132
+ forge plugins trust --yes # explicit non-interactive decision
133
+ forge plugins untrust
134
+ ```
135
+
136
+ The same decision is available inside an interactive Forge session. Enter
137
+ `/plugins`, review the project plugin versions and capabilities, press `t`, and
138
+ confirm with `y`. A trusted workspace can be revoked from the same panel with
139
+ `u`. The header updates immediately, and newly trusted plugins load on the next
140
+ native Forge Engine task without restarting the TUI.
141
+
142
+ Discovery is intentionally shallow. Forge does not scan arbitrary ancestors,
143
+ nested plugin directories, or `node_modules`, and it does not install packages
144
+ or run lifecycle scripts. Starting in a repository subdirectory still resolves
145
+ the same workspace root and trust record.
146
+
147
+ ## Plugin anatomy
148
+
149
+ A dependency-free plugin needs only a strict manifest and a JavaScript entry:
150
+
151
+ ```text
152
+ my-plugin/
153
+ |-- plugin.json # declarative metadata, read before trust/import
154
+ |-- index.mjs # activation function
155
+ |-- README.md # recommended setup and safety notes
156
+ `-- test/ # optional plugin-owned tests
157
+ ```
158
+
159
+ The entry may import sibling `.js`/`.mjs` files. Forge does not provide a
160
+ dependency installer, so a shared plugin that imports third-party packages must
161
+ document how the user installs them and must not rely on package-manager hooks
162
+ running automatically.
163
+
164
+ ## Manifest version 1
165
+
166
+ Every manifest is strict: unknown keys are rejected.
167
+
168
+ | Field | Required value |
169
+ | --- | --- |
170
+ | `schemaVersion` | Number `1` |
171
+ | `apiVersion` | String `"1"` |
172
+ | `name` | Lowercase kebab-case, 1–64 characters, equal to directory name |
173
+ | `version` | Non-empty plugin version string |
174
+ | `entry` | Relative `.js`, `.mjs`, or `.cjs` path that stays inside the plugin directory |
175
+ | `capabilities` | Array of the capabilities the entry intends to use |
176
+
177
+ Supported capabilities:
178
+
179
+ | Capability | Meaning |
180
+ | --- | --- |
181
+ | `tools:register` | Call `api.registerTool()` |
182
+ | `commands:register` | Call `api.registerCommand()` |
183
+ | `prompt:contribute` | Call `api.contributePrompt()` |
184
+ | `subagents:register` | Call `api.registerSubagent()` to declare a host-run child role |
185
+ | `events:observe` | Call `api.observeRunEvents()` |
186
+ | `policy:restrict` | Call `api.restrictPolicy()` |
187
+ | `network:access` | Declare that a registered `network`-risk tool performs external I/O |
188
+
189
+ Forge rejects an unsupported API version before import. It also rejects use of
190
+ a registration method that was not declared. A `network`-risk tool additionally
191
+ requires `network:access`.
192
+
193
+ Capabilities are review and API gates, not an operating-system sandbox. Trusted
194
+ JavaScript can still call Node.js directly, including during activation.
195
+
196
+ ## Activation and API reference
197
+
198
+ The entry exports either `default` or a named `activate` function. It may be
199
+ synchronous or asynchronous; Forge awaits it before starting the run.
200
+
201
+ ```js
202
+ export async function activate(api) {
203
+ // register extension points here
204
+ }
205
+ ```
206
+
207
+ The frozen `api` object contains:
208
+
209
+ | Member | Purpose |
210
+ | --- | --- |
211
+ | `api.apiVersion` | Current plugin API version, currently `"1"` |
212
+ | `api.z` | Forge's Zod instance for input schemas; no plugin dependency needed |
213
+ | `api.registerTool(tool)` | Register a model-callable tool |
214
+ | `api.registerCommand(command)` | Register an explicit local command |
215
+ | `api.registerSubagent(definition)` | Register an isolated, host-managed child role as a model tool |
216
+ | `api.contributePrompt(hook)` | Add bounded instructions to a run |
217
+ | `api.observeRunEvents(observer)` | Observe immutable event snapshots |
218
+ | `api.restrictPolicy(hook)` | Change an effective decision only to `confirm` or `deny` |
219
+
220
+ Registration names must be unique across built-ins and loaded plugins. Treat
221
+ activation as setup: do not perform surprising writes, network requests, or
222
+ long-running work merely because Forge starts.
223
+
224
+ ## Custom tools
225
+
226
+ `api.registerTool()` receives the provider-neutral `ForgeTool` contract:
227
+
228
+ ```ts
229
+ interface ForgeTool {
230
+ name: string;
231
+ description: string;
232
+ inputSchema: z.ZodType;
233
+ risk: "read" | "write" | "process" | "network" | "model";
234
+ execute(input: unknown, context: ToolContext): Promise<ToolResult>;
235
+ }
236
+ ```
237
+
238
+ Tool names use lower snake case and must match
239
+ `^[a-z][a-z0-9_]{0,63}$`. Descriptions and schema descriptions are sent to the
240
+ model, so say exactly when the tool should be called and keep inputs bounded.
241
+
242
+ ### Risk selection
243
+
244
+ | Risk | Use for | Default policy |
245
+ | --- | --- | --- |
246
+ | `read` | Workspace-bounded, side-effect-free inspection | Allow |
247
+ | `write` | Workspace file changes | Confirm first write in `safe`; allow in `workspace-write` |
248
+ | `process` | Starting any child process | Confirm every call |
249
+ | `network` | Sending data to or fetching data from an external service | Confirm every call |
250
+ | `model` | Starting an additional delegated model run | Confirm every call |
251
+
252
+ Do not label a network or process action as `read` merely because it does not
253
+ modify the repository. The risk describes the external effect, not only the
254
+ shape of the returned data.
255
+
256
+ Every plugin tool call follows the same runtime path as a built-in tool:
257
+
258
+ ```text
259
+ model proposal
260
+ -> schema validation
261
+ -> core policy
262
+ -> stricter plugin policy hooks
263
+ -> approval when required
264
+ -> execute
265
+ -> structured RunEvents
266
+ -> redacted trace and observers
267
+ -> tool result returned to model
268
+ ```
269
+
270
+ The `context` passed to `execute` contains the canonical workspace root/current
271
+ directory, the run's `AbortSignal`, and strict limits:
272
+
273
+ ```ts
274
+ interface ToolContext {
275
+ workspace: { root: string; cwd: string };
276
+ signal: AbortSignal;
277
+ limits: {
278
+ maxOutputBytes: number;
279
+ maxEntries: number;
280
+ commandTimeoutMs?: number;
281
+ };
282
+ }
283
+ ```
284
+
285
+ Honor cancellation promptly and let configured limits win over plugin defaults.
286
+ Successful and failed results are explicit:
287
+
288
+ ```js
289
+ return {
290
+ ok: true,
291
+ output: { value: "bounded JSON-serializable data" },
292
+ truncated: false
293
+ };
294
+
295
+ return {
296
+ ok: false,
297
+ error: {
298
+ code: "invalid_input",
299
+ message: "Explain a safe corrective action without secrets.",
300
+ retryable: false
301
+ }
302
+ };
303
+ ```
304
+
305
+ Use an existing Forge error code from `@forge/core` (`invalid_input`,
306
+ `cancelled`, `io_error`, `output_limit`, `timed_out`, and so on). Never include
307
+ credentials, authorization headers, or private response bodies in errors.
308
+
309
+ ## Other extension points
310
+
311
+ ### Subagents
312
+
313
+ `api.registerSubagent()` is declarative: the plugin defines a role, generated
314
+ parent-tool name, instructions, allowed child tools, and tight limits. It never
315
+ receives credentials or a callable model/runtime object.
316
+
317
+ ```js
318
+ api.registerSubagent({
319
+ name: "code-reviewer",
320
+ toolName: "delegate_code_review",
321
+ description: "Delegate a focused read-only review.",
322
+ instructions: "Report concrete correctness and security findings.",
323
+ tools: ["list_files", "read_file", "search"],
324
+ limits: { maxModelSteps: 4, maxToolCalls: 8 }
325
+ });
326
+ ```
327
+
328
+ Declare `subagents:register`. Role names are kebab-case; generated tool names
329
+ are lower snake case and share the normal built-in/plugin name namespace.
330
+ Instructions are required and capped at 16 KiB. At most 32 unique, already
331
+ registered non-subagent tools may be selected. Per-child limits are capped at
332
+ 8 model steps and 20 tool calls.
333
+
334
+ The generated tool accepts `{ task: string }`, has `model` risk, and requires
335
+ approval for every delegation. Forge creates a fresh adapter and isolated
336
+ conversation, inherits project instructions, workspace, context settings,
337
+ cancellation, approval channel, and the effective core-plus-plugin policy, and
338
+ exposes only the declared tools. Subagent tools are never included in child
339
+ tool sets, preventing recursive delegation.
340
+
341
+ One parent run may start at most four children. All children also share budgets
342
+ equal to the configured parent `maxSteps` and `maxToolCalls`; a plugin's limit
343
+ can only reduce those ceilings. Results are bounded by `maxToolOutputBytes`.
344
+ When tracing is enabled, each child gets a separate run trace whose envelopes
345
+ carry `parentRunId` and `subagentName`; the parent tool result carries the child
346
+ `runId`, status, step/tool counts, and final text.
347
+
348
+ See `examples/plugins/code-review-subagent`.
349
+
350
+ ### Commands
351
+
352
+ Commands are explicit trusted-code entry points, not model tool calls:
353
+
354
+ ```js
355
+ api.registerCommand({
356
+ name: "hello",
357
+ description: "Print a local greeting.",
358
+ execute: async ({ args, write }) => {
359
+ write(`hello ${args.join(" ") || "world"}\n`);
360
+ return 0;
361
+ }
362
+ });
363
+ ```
364
+
365
+ Run them with `forge plugins run hello [args...]`. The context exposes `cwd`,
366
+ `workspaceRoot`, `args`, `signal`, `write`, and `writeError`. Commands bypass
367
+ model-tool approval because the user invokes them directly; they still execute
368
+ with the full privileges of the trusted plugin.
369
+
370
+ ### Prompt contributions
371
+
372
+ `api.contributePrompt(hook)` receives an immutable snapshot containing the
373
+ current prompt, canonical workspace root, and working directory. A returned
374
+ string is limited to 32 KiB, labelled with its manifest path, added to the
375
+ effective instruction context, and included in instruction provenance.
376
+
377
+ Return `undefined` when no contribution is needed. Treat the user prompt as
378
+ untrusted data and do not use a prompt hook as a hidden command runner.
379
+
380
+ ### Run-event observers
381
+
382
+ `api.observeRunEvents(observer)` receives a deeply frozen structured clone of
383
+ each `RunEvent`. Observers cannot mutate runtime history. Observer failures
384
+ produce warnings without replacing the run result or trace. Events are redacted
385
+ for configured secrets before observers receive them.
386
+
387
+ ### Policy restrictions
388
+
389
+ `api.restrictPolicy(hook)` receives a frozen snapshot of the tool, call, and
390
+ validated input. It may return only:
391
+
392
+ ```js
393
+ { kind: "confirm", reason: "..." }
394
+ { kind: "deny", reason: "..." }
395
+ undefined
396
+ ```
397
+
398
+ Forge combines decisions using `deny > confirm > allow`. A plugin cannot turn a
399
+ core confirmation or denial into an allow. See
400
+ `examples/plugins/stricter-policy`.
401
+
402
+ ## Discovery and run lifecycle
403
+
404
+ The native Forge Engine performs these steps for each prompt:
405
+
406
+ 1. Load and validate configuration.
407
+ 2. Load bounded user/project instructions.
408
+ 3. Discover bounded built-in, user, and project Skill metadata, resolve
409
+ collisions, and preserve explicit `$skill-name` selection.
410
+ 4. Discover plugin manifests.
411
+ 5. Exclude disabled user plugins and untrusted project plugins.
412
+ 6. Resolve each entry inside its plugin directory and import it.
413
+ 7. Activate plugins and validate registrations/capabilities/name conflicts.
414
+ 8. Collect bounded prompt contributions.
415
+ 9. Build the model request and combined tool registry.
416
+ 10. Run all proposed tools through policy, approval, execution, events, and
417
+ traces.
418
+
419
+ The interactive startup frame stops after metadata discovery for display. The
420
+ actual entry import/activation still happens only when the Forge Engine starts a
421
+ run. The Codex Engine is a separate runtime owned by Codex App Server and does
422
+ not load Forge plugins.
423
+
424
+ ## Portable Skills are different
425
+
426
+ Forge discovers Markdown Skills at:
427
+
428
+ ```text
429
+ <installed-package>/resources/skills/<skill-name>/SKILL.md
430
+ $FORGE_HOME/skills/<skill-name>/SKILL.md
431
+ <workspace-root>/.agents/skills/<skill-name>/SKILL.md
432
+ ```
433
+
434
+ A Skill is guidance, not executable plugin code. Discovery never executes it.
435
+ Each `SKILL.md` starts with bounded YAML frontmatter containing a kebab-case
436
+ `name` matching its directory and a task-oriented `description`.
437
+ `disable-model-invocation: true` makes that Skill explicit-only.
438
+
439
+ The first model request contains only the bounded catalog fields `id`, `name`,
440
+ `description`, and `source`; Skill bodies are loaded lazily through the
441
+ host-owned `load_skill` read tool. That tool accepts only registered opaque IDs,
442
+ limits and deduplicates loads, revalidates canonical paths and file identities,
443
+ and rejects symlinks or files outside the registered root. Name collisions use
444
+ `project > user > builtin`; an explicit `$skill-name` overrides automatic
445
+ routing. Selection, load, rejection, and truncation are trace events visible in
446
+ `forge inspect`.
447
+
448
+ Scripts or actions described by a Skill still require normal model tool calls,
449
+ policy, approval, and trace events. Selecting or loading a project Skill does
450
+ not trust project plugin code and does not widen `read_file` beyond the active
451
+ workspace.
452
+
453
+ ## Testing a plugin
454
+
455
+ Plugin tests must not require a paid model or a real external service. Use fake
456
+ transport responses and the real host/policy boundary where possible.
457
+
458
+ Recommended loop:
459
+
460
+ ```bash
461
+ forge plugins list
462
+ pnpm typecheck
463
+ pnpm test
464
+ pnpm check
465
+ ```
466
+
467
+ For a project plugin, inspect the manifest before trust, then make the decision
468
+ explicit:
469
+
470
+ ```bash
471
+ forge plugins list
472
+ forge plugins trust
473
+ forge run "Use my plugin to perform its smallest safe task"
474
+ forge plugins untrust
475
+ ```
476
+
477
+ Alternatively, use `/plugins` in the TUI for the same explicit review,
478
+ confirmation, and revocation flow.
479
+
480
+ For a user plugin under test, point `FORGE_HOME` at a temporary directory and
481
+ enable only that plugin. A robust plugin test should cover:
482
+
483
+ - Manifest discovery without importing the entry.
484
+ - Host activation and registered names/capabilities.
485
+ - Invalid inputs and cancellation.
486
+ - Output/entry/timeout limits and `truncated` accuracy.
487
+ - Model/network/process/write policy decisions where relevant.
488
+ - Redaction: secrets never occur in a result, event, error, or test snapshot.
489
+ - Recoverable provider failures without live network calls.
490
+
491
+ ## Web tools example
492
+
493
+ `examples/plugins/web-tools` is a complete,
494
+ dependency-free test of the current plugin system. It registers:
495
+
496
+ - `web_search`: Brave Search when `BRAVE_SEARCH_API_KEY` exists, otherwise
497
+ DuckDuckGo's non-JavaScript HTML search.
498
+ - `web_fetch`: extracts bounded readable text from public HTTP(S) pages.
499
+
500
+ The Brave path follows the official
501
+ [Web Search API](https://api-dashboard.search.brave.com/api-reference/web/search/get).
502
+ The key-free fallback uses DuckDuckGo's documented
503
+ [non-JavaScript search](https://duckduckgo.com/duckduckgo-help-pages/features/non-javascript).
504
+
505
+ Install it as a user plugin:
506
+
507
+ ```bash
508
+ mkdir -p "${FORGE_HOME:-$HOME/.forge}/plugins"
509
+ cp -R examples/plugins/web-tools "${FORGE_HOME:-$HOME/.forge}/plugins/web-tools"
510
+ ```
511
+
512
+ Then add `"web-tools"` to `plugins.enabled` and restart Forge. Its own
513
+ `README.md` documents search-provider
514
+ selection and implemented controls.
515
+
516
+ The example treats web access as external I/O, so both tools use `network` risk
517
+ and require approval on every call. Forge's shared HTTP transport honors
518
+ `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` (including lowercase aliases), so
519
+ plugins using the global `fetch` work with ordinary HTTP(S) proxy setups.
520
+ `web_fetch` validates every initial and redirect URL, blocks local hostnames and
521
+ private IP literals, and resolves direct destinations before connection. For a
522
+ proxied destination, the explicitly configured proxy owns DNS resolution;
523
+ `NO_PROXY` destinations retain direct-DNS validation. It also restricts ports
524
+ and MIME types and bounds redirects, time, downloads, characters, entries, and
525
+ serialized output. These controls reduce accidental SSRF and runaway output;
526
+ they are not a network sandbox, and a configured proxy is part of the trust
527
+ boundary.
528
+
529
+ ## MCP, to-dos, and subagents
530
+
531
+ The examples make the current extension boundary concrete:
532
+
533
+ | Capability | Current plugin API | Example / limitation |
534
+ | --- | --- | --- |
535
+ | MCP server tools | Yes, with protocol and lifecycle limits | `mcp-stdio` registers approved `process`-risk list/call bridge tools for one configured stdio server. |
536
+ | Lightweight to-dos | Yes | `todos` registers an in-memory tool and bounded prompt contribution. Persistence and a custom TUI panel are not available. |
537
+ | Host-managed subagents | Yes | `code-review-subagent` declares a read-only child role; Forge owns its adapter, policy, budgets, cancellation, and linked trace. |
538
+
539
+ The MCP example intentionally targets session-based, newline-delimited stdio
540
+ revision `2025-11-25`; it is evidence that a plugin can bridge MCP tools, not a
541
+ claim that Forge has complete MCP host support. Streamable HTTP, current
542
+ handshake-free protocol support, server reuse, prompts/resources/roots,
543
+ sampling, tasks, and lifecycle disposal need a first-class host or expanded
544
+ plugin contract.
545
+
546
+ Subagents currently inherit the active parent model; plugins cannot select a
547
+ different provider/model, pass parent conversation history, persist a child as
548
+ an independently resumable session, stream child deltas into a dedicated TUI
549
+ panel, or enable nested delegation. Those remain deliberate host limitations,
550
+ not behaviors plugins should emulate with direct provider calls or recursive
551
+ CLI spawning.
552
+
553
+ ## Instructions for a model author
554
+
555
+ When asked to write a Forge plugin, follow this sequence:
556
+
557
+ 1. Read this entire document and inspect the current plugin types/schema/host.
558
+ 2. Decide user versus project scope from the user's intent; never record trust
559
+ or edit user configuration unless requested.
560
+ 3. Choose the smallest manifest capability set. A network tool needs both
561
+ `tools:register` and `network:access`.
562
+ 4. Use plain ESM JavaScript and `api.z` unless a dependency is genuinely
563
+ necessary. Forge does not compile TypeScript plugin entries or install deps.
564
+ 5. Validate inside `execute` even though the runtime validates first; direct
565
+ tests and future callers should receive a structured failure.
566
+ 6. Choose the honest risk. Bound every input and every output, honor
567
+ `context.signal`, and use `context.limits` as the upper authority.
568
+ 7. Keep activation free of surprising side effects. Do work only after an
569
+ explicit command or approved tool call.
570
+ 8. Add deterministic tests with fake I/O. Exercise discovery and activation
571
+ through `loadPluginHost` when changing Forge itself.
572
+ 9. Run formatting, type checks, targeted tests, and the full suite.
573
+ 10. Document setup, required environment variables, data sent externally,
574
+ safety controls, and known limitations without claiming sandboxing.
575
+
576
+ Before handing off, verify:
577
+
578
+ - Directory name equals manifest `name`.
579
+ - Manifest/API versions are exactly supported.
580
+ - Entry stays inside the plugin directory and exports an activation function.
581
+ - All used APIs, subagents, and `network` tools declare their capabilities.
582
+ - Tool/command names do not collide and descriptions are model-readable.
583
+ - Errors are actionable and contain no secrets.
584
+ - Output sizes are measured after JSON serialization when that matters.
585
+ - Project code is not executed before explicit trust.
586
+ - Documentation distinguishes discovery, enablement, trust, activation, and
587
+ tool-call approval.
588
+
589
+ ## Security boundary
590
+
591
+ Loading a plugin executes local code with the full privileges of the Forge
592
+ process. It may import Node.js modules, read arbitrary files, start processes,
593
+ or use the network without going through a registered tool. Only install or
594
+ trust code you have reviewed.
595
+
596
+ Forge enforces safety at its supported API boundaries:
597
+
598
+ - Project entries are not imported before canonical workspace trust.
599
+ - Manifest/API/capability/name/schema contracts are validated.
600
+ - Model-called plugin tools follow core policy and approval.
601
+ - `model`, `network`, `process`, and applicable write actions require confirmation.
602
+ - Policy hooks can only make decisions stricter.
603
+ - Prompt contributions and Skill content are bounded and attributed.
604
+ - Observer input is cloned, frozen, and secret-redacted.
605
+
606
+ Those properties do not isolate a malicious trusted entry. Strong isolation
607
+ would require a restricted process or OS sandbox.
608
+
609
+ ## Deliberate limitations
610
+
611
+ Forge 0.3.1 has no plugin installer, dependency resolver, package registry, hot
612
+ reload, TypeScript entry compilation, custom interactive UI, provider
613
+ registration, isolated plugin process, or enforceable filesystem/network
614
+ capabilities. Plugin commands run only through `forge plugins run`; they do not
615
+ become interactive slash commands. These gaps are explicit so plugin authors
616
+ can target the implemented contract instead of guessing at future APIs.
617
+
618
+ ## Skills and product documentation are resources
619
+
620
+ Skills are non-executable, untrusted instruction resources discovered from built-in, user, and project scopes. `forge plugins list` reports executable plugins; `forge resources list` reports Skill source, invocation status, collision shadowing, and diagnostics. The interactive equivalents are `/plugins` and `/resources`.
621
+
622
+ The built-in `forge-product-help` Skill requires a documentation lookup before implementation-specific product answers. `search_forge_docs` and `read_forge_doc` use a version-matched packaged allowlist and stable `forge-doc:<version>:<locale>:<document>#<section>` references. They reject filesystem paths and do not inherit `read_file` access.