@sorenllm/opencode-forge 0.5.0 → 0.6.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/README.md +36 -9
- package/dist/index.js +31 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -359,8 +359,10 @@ complete.
|
|
|
359
359
|
## Forge subagents (static worker agents in forge.json)
|
|
360
360
|
|
|
361
361
|
Forge subagents are **static, user-authored worker agents**: each forge.json
|
|
362
|
-
entry
|
|
363
|
-
|
|
362
|
+
entry is either **pinned** (a model + a reasoning depth, bound at
|
|
363
|
+
materialization) or an **Auto worker** (no model — it inherits the parent
|
|
364
|
+
session's model at dispatch), materializes as a native `forge-<id>`
|
|
365
|
+
subagent, and is dispatched through the host's **native `task`
|
|
364
366
|
tool** — which means every run is visible in the TUI, expandable and
|
|
365
367
|
monitorable, exactly like any native subagent. There is no dispatch tool, no
|
|
366
368
|
child-session engine, and no configuration invitation anywhere: the session
|
|
@@ -381,17 +383,33 @@ cascade with a single winning source (never merged):
|
|
|
381
383
|
{
|
|
382
384
|
// forge subagents — definitions; see the restart table below for apply timing
|
|
383
385
|
"agents": {
|
|
386
|
+
// PINNED worker: model + thoughtLevel form an ATOMIC PAIR — set BOTH
|
|
384
387
|
"research": {
|
|
385
|
-
"model": "zai-coding-plan/glm-5.3", // exact "provider/model" string
|
|
386
|
-
"thoughtLevel": "low", //
|
|
388
|
+
"model": "zai-coding-plan/glm-5.3", // exact "provider/model" string
|
|
389
|
+
"thoughtLevel": "low", // none/low/medium/high/max or a native level name
|
|
387
390
|
// "prompt": "optional role prompt; built-in research/review defaults exist",
|
|
388
391
|
// "shape": "write", // default readonly denies write/edit/bash
|
|
389
392
|
// "permission": { "bash": "deny" } // optional override; "task" is ALWAYS denied
|
|
390
|
-
}
|
|
393
|
+
},
|
|
394
|
+
// AUTO worker: set NEITHER — inherits the parent session's model at
|
|
395
|
+
// dispatch (snapshot semantics: later primary model switches affect only
|
|
396
|
+
// future dispatches), runs at the provider's default depth
|
|
397
|
+
"scout": {}
|
|
391
398
|
}
|
|
392
399
|
}
|
|
393
400
|
```
|
|
394
401
|
|
|
402
|
+
**The atomic pair — the one rule that matters:**
|
|
403
|
+
|
|
404
|
+
| `model` | `thoughtLevel` | result |
|
|
405
|
+
| --- | --- | --- |
|
|
406
|
+
| set | set | pinned worker (brain + depth bound at materialization) |
|
|
407
|
+
| — | — | **Auto worker** (inherits the parent session's model; provider-default depth) |
|
|
408
|
+
| set | — | ✗ rejected: entry skipped, error finding names the missing `thoughtLevel` |
|
|
409
|
+
| — | set | ✗ rejected: entry skipped, error finding names the missing `model` |
|
|
410
|
+
|
|
411
|
+
A rejected entry costs only itself — its siblings materialize normally.
|
|
412
|
+
|
|
395
413
|
- **The plugin only ever reads this file.** It never creates, writes, or
|
|
396
414
|
migrates it. The old AI-authored onboarding path (seed placeholder +
|
|
397
415
|
error recipe) is gone: an unconfigured host is inert and silent, and the
|
|
@@ -400,13 +418,17 @@ cascade with a single winning source (never merged):
|
|
|
400
418
|
offered to YOU and happens only on your explicit go-ahead.
|
|
401
419
|
- **Parsing is field-level fail-soft** (ZCode-style): a semantically bad
|
|
402
420
|
agent entry is skipped with an error finding while its siblings apply; a
|
|
403
|
-
mistyped optional field is ignored with a warning
|
|
404
|
-
|
|
405
|
-
|
|
421
|
+
mistyped optional field is ignored with a warning (and counts as absent
|
|
422
|
+
for the atomic pair — so a pinned `model` with a typo'd `thoughtLevel`
|
|
423
|
+
skips the whole entry); a syntactically broken file empties the agent set
|
|
424
|
+
with one parse-location error. There is no seed in any failure path.
|
|
425
|
+
Findings surface in the plugin diagnostics log.
|
|
406
426
|
- **`depths` is deprecated**: the array is ignored with a warning finding.
|
|
407
427
|
Pin ONE `thoughtLevel` per agent instead.
|
|
408
428
|
- Each agent materializes as `forge-<id>` — a subagent-mode agent visible in
|
|
409
|
-
the task tool's vocabulary (never hidden), carrying the pinned `model
|
|
429
|
+
the task tool's vocabulary (never hidden), carrying the pinned `model`
|
|
430
|
+
(pinned workers) or NO model key at all (Auto workers — the description
|
|
431
|
+
says "inherits the parent session's model" instead of naming a brain),
|
|
410
432
|
the worker discipline (workspace-relative paths only, verbatim reporting
|
|
411
433
|
of tool refusals, evidence-bearing conclusions) around its role prompt,
|
|
412
434
|
and a deny-style permission: `shape: "readonly"` (the default) denies
|
|
@@ -414,6 +436,11 @@ cascade with a single winning source (never merged):
|
|
|
414
436
|
`permission` map overrides the shape default — and `task` is ALWAYS
|
|
415
437
|
denied (recursive spawning stays physically impossible).
|
|
416
438
|
|
|
439
|
+
> **Upgrading from ≤ 0.5.0 (breaking):** agent entries that pinned `model`
|
|
440
|
+
> without `thoughtLevel` used to be accepted; they are now SKIPPED with an
|
|
441
|
+
> error finding naming the missing half. Fix per entry: add the missing
|
|
442
|
+
> `thoughtLevel`, or delete `model` to make it an Auto worker.
|
|
443
|
+
|
|
417
444
|
**What applies when** — edits take hold per field:
|
|
418
445
|
|
|
419
446
|
| change | needs a host restart? |
|
package/dist/index.js
CHANGED
|
@@ -14893,7 +14893,8 @@ function rolePromptFor(agentId, def) {
|
|
|
14893
14893
|
}
|
|
14894
14894
|
function agentDescriptionFor(agentId, def) {
|
|
14895
14895
|
const shape = def.shape === "write" ? "write" : "readonly";
|
|
14896
|
-
|
|
14896
|
+
const brain = def.model !== undefined ? `pinned ${def.model}` : "auto — inherits the parent session's model at dispatch";
|
|
14897
|
+
return `forge subagent "${agentId}" (${shape}, ${brain}) — dispatch via the task tool with subagent_type "forge-${agentId}".`;
|
|
14897
14898
|
}
|
|
14898
14899
|
function forgeAgentDef(agentId, def) {
|
|
14899
14900
|
const permission = { task: "deny" };
|
|
@@ -14904,10 +14905,9 @@ function forgeAgentDef(agentId, def) {
|
|
|
14904
14905
|
}
|
|
14905
14906
|
Object.assign(permission, def.permission ?? {});
|
|
14906
14907
|
permission.task = "deny";
|
|
14907
|
-
|
|
14908
|
+
const entry = {
|
|
14908
14909
|
description: agentDescriptionFor(agentId, def),
|
|
14909
14910
|
mode: "subagent",
|
|
14910
|
-
model: def.model,
|
|
14911
14911
|
prompt: materializeAgentPrompt({
|
|
14912
14912
|
agent: agentId,
|
|
14913
14913
|
shape,
|
|
@@ -14916,6 +14916,9 @@ function forgeAgentDef(agentId, def) {
|
|
|
14916
14916
|
permission,
|
|
14917
14917
|
tools: { shell: false, bash: false }
|
|
14918
14918
|
};
|
|
14919
|
+
if (def.model !== undefined)
|
|
14920
|
+
entry.model = def.model;
|
|
14921
|
+
return entry;
|
|
14919
14922
|
}
|
|
14920
14923
|
|
|
14921
14924
|
// src/dispatch-depth.ts
|
|
@@ -15250,21 +15253,36 @@ function validateAgentSet(raw) {
|
|
|
15250
15253
|
findings.push({ level: "error", code: "agent-not-object", message: `${label}: definition must be an object — entry skipped` });
|
|
15251
15254
|
continue;
|
|
15252
15255
|
}
|
|
15253
|
-
if (!nonEmptyString(entry.model)) {
|
|
15254
|
-
findings.push({ level: "error", code: "agent-model-missing", message: `${label}.model: a non-empty "provider/model" string is required — entry skipped, siblings still apply` });
|
|
15255
|
-
continue;
|
|
15256
|
-
}
|
|
15257
|
-
const def = { model: entry.model.trim() };
|
|
15258
15256
|
if (entry.depths !== undefined) {
|
|
15259
15257
|
findings.push({ level: "warn", code: "depths-deprecated", message: `${label}.depths is no longer used (static agents pin one thoughtLevel) — ignored; set "thoughtLevel" instead` });
|
|
15260
15258
|
}
|
|
15259
|
+
let model;
|
|
15260
|
+
if (entry.model !== undefined) {
|
|
15261
|
+
if (nonEmptyString(entry.model)) {
|
|
15262
|
+
model = entry.model.trim();
|
|
15263
|
+
} else {
|
|
15264
|
+
findings.push({ level: "warn", code: "model-invalid", message: `${label}.model must be a non-empty string — field ignored (counts as absent for the model/thoughtLevel pair)` });
|
|
15265
|
+
}
|
|
15266
|
+
}
|
|
15267
|
+
let thoughtLevel;
|
|
15261
15268
|
if (entry.thoughtLevel !== undefined) {
|
|
15262
15269
|
if (nonEmptyString(entry.thoughtLevel)) {
|
|
15263
|
-
|
|
15270
|
+
thoughtLevel = entry.thoughtLevel.trim();
|
|
15264
15271
|
} else {
|
|
15265
|
-
findings.push({ level: "warn", code: "thoughtlevel-invalid", message: `${label}.thoughtLevel must be a non-empty string — field ignored
|
|
15272
|
+
findings.push({ level: "warn", code: "thoughtlevel-invalid", message: `${label}.thoughtLevel must be a non-empty string — field ignored (counts as absent for the model/thoughtLevel pair)` });
|
|
15266
15273
|
}
|
|
15267
15274
|
}
|
|
15275
|
+
if (model === undefined !== (thoughtLevel === undefined)) {
|
|
15276
|
+
const presentHalf = model !== undefined ? "model" : "thoughtLevel";
|
|
15277
|
+
const missingHalf = model !== undefined ? "thoughtLevel" : "model";
|
|
15278
|
+
findings.push({ level: "error", code: "agent-half-configured", message: `${label}: model and thoughtLevel form an atomic pair — ${presentHalf} is set but ${missingHalf} is missing; set BOTH to pin the brain and depth, or NEITHER for an Auto worker — entry skipped, siblings still apply` });
|
|
15279
|
+
continue;
|
|
15280
|
+
}
|
|
15281
|
+
const def = {};
|
|
15282
|
+
if (model !== undefined) {
|
|
15283
|
+
def.model = model;
|
|
15284
|
+
def.thoughtLevel = thoughtLevel;
|
|
15285
|
+
}
|
|
15268
15286
|
if (entry.prompt !== undefined) {
|
|
15269
15287
|
if (nonEmptyString(entry.prompt)) {
|
|
15270
15288
|
def.prompt = entry.prompt;
|
|
@@ -16272,7 +16290,7 @@ var CREW_INIT_TEMPLATE = [
|
|
|
16272
16290
|
"",
|
|
16273
16291
|
"2. Or, ONLY if the user explicitly asks you to configure it in this conversation, you may write the file yourself through the normal write path. NEVER write forge.json without that explicit go-ahead.",
|
|
16274
16292
|
"",
|
|
16275
|
-
"Template (JSONC — comments allowed
|
|
16293
|
+
"Template (JSONC — comments allowed): `model` + `thoughtLevel` are an ATOMIC PAIR — set BOTH to pin the brain and depth (model = exact provider/model identity; thoughtLevel = none/low/medium/high/max or a native level name), or NEITHER for an Auto worker that inherits the parent session's model; exactly one of the two is rejected.",
|
|
16276
16294
|
"{template}",
|
|
16277
16295
|
"",
|
|
16278
16296
|
"After the file is saved: NEW agents require a host restart to appear in the task tool; thoughtLevel edits on existing agents apply without restart. Until then, crew stays unavailable."
|
|
@@ -17112,7 +17130,8 @@ async function v2Setup(ctx) {
|
|
|
17112
17130
|
agent.description = v1def.description;
|
|
17113
17131
|
agent.system = v1def.prompt;
|
|
17114
17132
|
agent.mode = v1def.mode;
|
|
17115
|
-
|
|
17133
|
+
if (v1def.model !== undefined)
|
|
17134
|
+
agent.model = v1def.model;
|
|
17116
17135
|
agent.permission = v1def.permission;
|
|
17117
17136
|
});
|
|
17118
17137
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorenllm/opencode-forge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Single general-purpose forge agent for opencode: static worker subagents defined in forge.json (pinned model +
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Single general-purpose forge agent for opencode: static worker subagents defined in forge.json (pinned model + thoughtLevel pair, or an Auto worker inheriting the parent model; dispatched through the native task tool — TUI-visible), a plan harness (file-backed plans with approve/close confirmation gates and a hard draft write-ban), a goal harness (autonomous, host-verified objectives), a /crew orchestration command (declared-plan waves with an evidence-checked close gate), a job supervisor (forge_shell with exit-bound completion, idle/success early return, background jobs with poll/log/kill/handoff), and a builtin-shell hang watchdog.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"exports": {
|