@sorenllm/opencode-forge 0.3.2 → 0.3.3

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 (3) hide show
  1. package/README.md +140 -2
  2. package/dist/index.js +2196 -202
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -114,8 +114,8 @@ and the plugin's command of that name is not registered.
114
114
  (the hide was runtime-only). Your `.opencode/plan/` and `.opencode/goal/`
115
115
  files are yours; delete them yourself if you want.
116
116
  5. Optional runtime debris: delete `<tmp>/opencode-forge/` (job logs, the
117
- job registry ledger, and the watchdog ledger — the file ledger table
118
- above lists everything).
117
+ job registry ledger, the watchdog ledger, and the dispatch ledger — the
118
+ file ledger table above lists everything).
119
119
 
120
120
  ## File ledger
121
121
 
@@ -130,6 +130,8 @@ What this plugin touches, exhaustively:
130
130
  | `<tmp>/opencode-forge/jobs/ledger.jsonl` | job registry ledger (bounded: 1 MB reset, 200 entries) | runtime debris — delete freely, also after uninstall |
131
131
  | `<tmp>/opencode-forge/jobs/registry.json` | persistent survivor registry (bounded: 100 entries) | runtime debris — after uninstall, kill any still-running `survive` jobs yourself first |
132
132
  | `<tmp>/opencode-forge/watchdog/log.jsonl` | watchdog interventions ledger (bounded: 200 entries, oldest rotated) | runtime debris — delete freely, also after uninstall |
133
+ | `<tmp>/opencode-forge/dispatch/ledger.jsonl` | dispatch ledger: resolved identity/depth, outcomes, tokens/cost, timeouts, lost-on-exit, crew summaries (bounded, capped rotation) | runtime debris — delete freely, also after uninstall |
134
+ | `<project>/.opencode/forge.json`, `~/.config/opencode/forge.json` | your dispatch agent definitions (JSONC). **User data — the plugin only reads it, never writes or migrates it** | yours — version the project one, keep the global one out of sync tools if it holds machine-specific models |
133
135
  | `~/.cache/opencode/packages/...` | installed package copy | written by the `opencode plugin` installer, not the plugin |
134
136
  | `~/.config/opencode/opencode.json` | `plugin` array entry | written by the installer |
135
137
 
@@ -317,6 +319,142 @@ These are runtime debris, not data — with ONE caveat: **if you uninstall with
317
319
  /F /T`, or just reboot); deleting the directory afterwards is safe and
318
320
  complete.
319
321
 
322
+ ## Worker dispatch (scoped subagents on a chosen model + depth)
323
+
324
+ `forge_dispatch {prompt, agent, depth}` spawns a child session that runs one
325
+ scoped task on the agent's pinned model at the requested reasoning depth. The
326
+ result is an honest report: the actual model/depth used, real token counts, a
327
+ self-computed cost (`null` when the model is unpriced), the
328
+ `depthTranslation` disclosure (`canonical high → native XHigh`, or
329
+ `verbatim`), and the worker's concluding report.
330
+
331
+ ### forge.json — the dispatch configuration
332
+
333
+ Agents live in a dedicated `forge.json` (JSONC — comments allowed), resolved
334
+ by a three-level cascade with a single winning source (never merged):
335
+
336
+ 1. `<project>/.opencode/forge.json` — project-level, versionable, team-shared
337
+ 2. `~/.config/opencode/forge.json` — global
338
+ 3. the built-in seed — `research {depths: [low, medium]}` and
339
+ `review {depths: [medium, high, max]}`, both pinned to the placeholder
340
+ `Local/GPT Luna` (an identity that never resolves: an unconfigured install
341
+ fails every dispatch with the configuration recipe below)
342
+
343
+ ```jsonc
344
+ {
345
+ // forge dispatch agents — save the file; changes apply on the next dispatch, no restart
346
+ "agents": {
347
+ "research": {
348
+ "model": "zai-coding-plan/glm-5.3", // exact "provider/model" string
349
+ "depths": ["low", "medium"], // first entry is the default
350
+ // "prompt": "optional role prompt riding inside the discipline wrapper",
351
+ // "shape": "write", // default readonly denies mutating tools
352
+ // "permission": { "bash": "deny" } // optional override; "task" is always denied
353
+ }
354
+ }
355
+ }
356
+ ```
357
+
358
+ - **The plugin only ever reads this file.** It never creates, writes, or
359
+ migrates it. Unconfigured? The first `forge_dispatch` error carries a
360
+ machine-actionable recipe (detected identities, both file paths, the
361
+ template, a verify dispatch) — or just ask your session AI to configure
362
+ dispatch; `forge_dispatch_config` shows the exact live state.
363
+ - **Hot-apply**: the file is re-read (mtime-cached) before every dispatch and
364
+ on every `forge_dispatch_config` call — edits take effect on the next
365
+ dispatch, no host restart. One boundary: newly added agents materialize
366
+ their hidden `forge-<agent>` entry only on the next config reload; the
367
+ dispatch itself still works (the body names the agent), but a brand-new
368
+ agent's first dispatch may need a host reload if the host validates agent
369
+ names on session create.
370
+ - Each agent materializes as a hidden `mode: subagent` agent `forge-<agent>`
371
+ carrying the role prompt (explicit `prompt` > built-in research/review
372
+ defaults > generic worker) and a deny-style permission: `shape: "readonly"`
373
+ (the default) denies `write`/`edit`/`bash`, `shape: "write"` allows them,
374
+ an explicit `permission` map overrides the shape default — and `task` is
375
+ ALWAYS denied (recursive dispatch stays physically impossible). Agents
376
+ never carry a `model` field; the brain is bound per dispatch.
377
+
378
+ ### The depth metalanguage
379
+
380
+ `depth` comes from the canonical five-word set `none | low | medium | high |
381
+ max`, or the model's **native level names verbatim** (escape hatch — e.g.
382
+ Qwen's `XHigh`). Translation to the provider's native parameter follows three
383
+ hard rules:
384
+
385
+ - **verbatim-first**: a meta word the model natively offers passes as-is
386
+ - **no interpolation**: a meta word with no counterpart errors listing both
387
+ vocabularies — never a nearest guess
388
+ - **full disclosure**: the report always shows `canonical → native`
389
+
390
+ Per family: effort-style providers (OpenAI-compatible) receive
391
+ `reasoningEffort = <word>`; budget-style (Anthropic-style) receive a
392
+ published thinking tier — `low → 8192`, `medium → 16384`, `high → 24576`,
393
+ `max → 32768` budget tokens, `none → thinking off`; toggle-style (zai/GLM)
394
+ receive thinking on/off (that shape has no level slot — disclosed honestly).
395
+ A provider whose option shape the plugin does not know gets nothing injected,
396
+ disclosed as `not injected (unknown provider shape)`. The provider is the
397
+ final judge of every depth word: its raw errors flow back unedited.
398
+
399
+ ### Legacy inline roster (advanced)
400
+
401
+ The pre-forge.json inline form still works when configured (the plugin
402
+ options `dispatch.roster` / `dispatch.tiers`): tier ids it defines keep their
403
+ exact-match resolution, one-retry-excluding-a-failed-identity semantics, and
404
+ startup dead-key warnings. Legacy tier ids win over forge.json agents of the
405
+ same name while inline options exist. Zero-config auto-generation of roster
406
+ entries was removed (configured ≠ usable) — dispatch without configuration is
407
+ the seed + recipe path above.
408
+
409
+ ### Background dispatch and wake briefs
410
+
411
+ `forge_dispatch` takes `background: true`: it returns
412
+ `{dispatchId, agent, requested, resolved, depth, queuedAt}` immediately and
413
+ the full result arrives later as a coalesced `[forge:dispatch-complete]`
414
+ brief — debounced, never interrupting an active turn (delivery waits for an
415
+ idle), each terminal delivered exactly once, and merged into a single
416
+ combined re-prompt when a goal continuation fires on the same idle.
417
+ `forge_dispatch_list` recovers in-flight and recent terminal state (with
418
+ dispatchIds) after context compaction; `forge_dispatch_kill {dispatchId}`
419
+ stops polling, suppresses the brief, and ledgers `killed`. On host exit,
420
+ in-flight dispatches are ledgered `lost-on-exit` — sessions are host memory
421
+ objects, there are no orphan processes and no survive semantics.
422
+
423
+ ### Crew workflow (`/crew`)
424
+
425
+ `/crew <objective>` registers a crew: decompose the objective into
426
+ evidence-checked subtasks, dispatch them as background waves paced on
427
+ completion briefs (**never past the concurrency cap — launch the next batch
428
+ only as briefs free capacity**), at most ONE retry per failed subtask, then
429
+ `crew_close` with the full report. The close is a hard gate: every subtask
430
+ needs a PASS/FAIL verdict with evidence, a FAIL needs both failure reports,
431
+ and the report is cross-checked against the dispatch ledger — a dispatched
432
+ subtask missing from the report refuses the close (refusals never bother you
433
+ with a dialog). A valid close asks once, appends a crew summary to the
434
+ ledger, and ends the crew. Crew state is in-memory by design: a host restart
435
+ ends it honestly and the ledger keeps the history.
436
+
437
+ ### Known pitfalls
438
+
439
+ - **Keyless endpoints × readonly agents**: free/keyless providers can return
440
+ a deterministic EMPTY response for any toolset-reduced (readonly) agent —
441
+ 0 tokens, no parts. The plugin detects this and fails the dispatch honestly
442
+ (`empty-response`, never counted as success); use a `shape: "write"` agent
443
+ or a keyed provider for readonly work on such endpoints.
444
+ - **`opencode run` and background**: a finished run-mode session cannot be
445
+ re-prompted, so completion briefs have nowhere to land — completions are
446
+ ledger-only (read them via `forge_dispatch_list`). Background mode targets
447
+ live TUI sessions; prefer sync under `opencode run`.
448
+ - **Host tool ceiling (~262s observed on 1.18.32)**: the host may kill a
449
+ sync tool call that exceeds roughly 262 seconds even though the plugin's
450
+ own deadline is higher. Keep `timeoutMs` at or below ~240000 for
451
+ sync-heavy workflows; a killed call still leaves the child session
452
+ reclaimable via `forge_dispatch_list` / the host API.
453
+ - **Child permission asks**: a worker that hits a permission ask surfaces the
454
+ ask on the CHILD's session in the TUI. An unanswered ask stalls the child
455
+ until the dispatch deadline — scope worker prompts to avoid permission
456
+ boundaries (readonly tiers and workspace-relative paths help).
457
+
320
458
  ## Hang watchdog (a stuck builtin shell unblocks itself)
321
459
 
322
460
  `forge_shell` is structurally immune to the stdio-EOF hang, but the builtin