@sorenllm/opencode-forge 0.19.0 → 0.20.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 +75 -18
- package/dist/index.js +228 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -566,6 +566,11 @@ out of a materialized id.
|
|
|
566
566
|
// dispatches are blocked until the field is added). Invalid values
|
|
567
567
|
// degrade to a warning + the directory-derived namespace.
|
|
568
568
|
// "pool": "aa",
|
|
569
|
+
// OPTIONAL pool-level shared prompt segment — written ONCE at the top of
|
|
570
|
+
// the file, inherited by EVERY agent this file materializes (project
|
|
571
|
+
// conventions, repo layout, standing rules). A plain string, no
|
|
572
|
+
// interpolation. See "Pool-level shared prompt segment" below.
|
|
573
|
+
// "shared": "Project X conventions: use bun; cite file:line evidence.",
|
|
569
574
|
"agents": {
|
|
570
575
|
// PINNED worker: model + thoughtLevel form an ATOMIC PAIR — set BOTH
|
|
571
576
|
"research": {
|
|
@@ -612,6 +617,26 @@ A rejected entry costs only itself — its siblings materialize normally.
|
|
|
612
617
|
Findings surface in the plugin diagnostics log.
|
|
613
618
|
- **`depths` is deprecated**: the array is ignored with a warning finding.
|
|
614
619
|
Pin ONE `thoughtLevel` per agent instead.
|
|
620
|
+
- **Pool-level `shared` prompt segment**: an optional top-level `"shared"`
|
|
621
|
+
string — the pool's common base, written once and inherited by EVERY agent
|
|
622
|
+
the file materializes (no per-agent opt-out; an agent that must escape it
|
|
623
|
+
gets its own pool file). The layering contract is a strict prepend: the
|
|
624
|
+
materialized role prompt = `shared` + a blank line + the EXISTING
|
|
625
|
+
three-layer chain (explicit `prompt` > built-in `research`/`review`
|
|
626
|
+
defaults > generic one-liner), byte-for-byte unchanged beneath it —
|
|
627
|
+
`shared` never suppresses and never overrides anything, and an explicit
|
|
628
|
+
per-agent prompt keeps its priority over role defaults. Validation is
|
|
629
|
+
field-level fail-soft: a non-string or whitespace-only `shared` records
|
|
630
|
+
exactly ONE warn finding (`shared-invalid`, naming the file) and is
|
|
631
|
+
ignored — every agent of the pool still materializes with the unshared
|
|
632
|
+
chain. Scope is PER FILE: agents from two different pool files each get
|
|
633
|
+
their OWN file's `shared` (no cross-pool bleed), and the GLOBAL fallback
|
|
634
|
+
file's `shared` inherits to its family normally — the rule that ignores
|
|
635
|
+
the global file's `pool` namespace field does not extend to `shared`
|
|
636
|
+
(content is not namespace). Hot-apply rides the existing mtime cache:
|
|
637
|
+
editing `shared` re-materializes at the next config hook exactly like any
|
|
638
|
+
pool edit. With no `shared` declared anywhere, materialized prompts are
|
|
639
|
+
byte-identical to the pre-`shared` build.
|
|
615
640
|
- Each agent materializes as `forge-<ns>-<id>` — a subagent-mode agent
|
|
616
641
|
visible in the task tool's vocabulary (never hidden), whose description
|
|
617
642
|
names its **pool provenance** (namespace + origin forge.json path +
|
|
@@ -720,7 +745,7 @@ Covered WRITE-CLASS indicators:
|
|
|
720
745
|
| `mutating-cmdlet` | `Set-Content`, `Out-File`, `New-Item`, `Remove-Item`, `Move-Item`, `Copy-Item`, `Add-Content`, `Clear-Content` |
|
|
721
746
|
| `git-mutation` | `git add/commit/push/reset/checkout/rebase/merge/clean/rm` |
|
|
722
747
|
| `pkg-mutation` | `npm publish`, `npm install` (incl. `npm i`/`ci`/`add`), `bun add`/`install`, `pip`/`pip3 install` |
|
|
723
|
-
| `workspace-write` | output-target flags (`--out`, `--output`, `--out-file`/`--outfile`/`--outFile`, `--dest`, `--destination`, `-o`) whose parsed value is a workspace-relative path, or an absolute path under the session workspace |
|
|
748
|
+
| `workspace-write` | output-target flags (`--out`, `--output`, `--out-file`/`--outfile`/`--outFile`, `--dest`, `--destination`, `-o`) whose parsed value is a workspace-relative path, or an absolute path under the session workspace — the short `-o` counts only when the head binary is a known `-o` writer (`gcc`, `g++`, `cc`, `c++`, `clang`, `clang++`, `ld`, `rustc`, `go`, `curl`, `sort`, `psql`, `dotnet`); on every other binary it flows |
|
|
724
749
|
|
|
725
750
|
**Honest positioning — this is belt-in-depth, NOT a sandbox.** A command
|
|
726
751
|
that fails to classify is treated as READ-CLASS (fail-open on the channel,
|
|
@@ -729,7 +754,9 @@ the same posture as every refusal belt). The known bypass surface, verbatim:
|
|
|
729
754
|
no redirect is indirection the classifier does not model), **`Start-Process`
|
|
730
755
|
writers** (spawning a writer process is explicitly out of scope), and
|
|
731
756
|
**encoded payloads** (`powershell -EncodedPayload …`, base64 blobs) are NOT
|
|
732
|
-
covered; they flow on the readonly channel. The
|
|
757
|
+
covered; they flow on the readonly channel. The short `-o` on a writer
|
|
758
|
+
binary outside the known set flows (fail-open, disclosed). The seal narrows
|
|
759
|
+
the exec hole
|
|
733
760
|
from "completely open" to "explicitly constructed indirection" — a true OS
|
|
734
761
|
level seal (write-time redirection, shadow copies, containers, win32+POSIX
|
|
735
762
|
dual implementations) was evaluated and rejected as a disproportionate
|
|
@@ -834,22 +861,52 @@ requires a plan. The flow:
|
|
|
834
861
|
(arm=true) folding the declared subtasks into criteria/checks; the arm
|
|
835
862
|
dialog is the user's gate;
|
|
836
863
|
- **standby** — no call; the crew waits until armed or abandoned.
|
|
837
|
-
3. **Execute in waves** (armed crews only) of parallel native `task` calls
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
4. **
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
864
|
+
3. **Execute in waves** (armed crews only) of parallel native `task` calls
|
|
865
|
+
(results return in-turn; the next wave launches only after the previous
|
|
866
|
+
wave's results). GUI / computer-use subtasks are mutually exclusive within
|
|
867
|
+
a wave (the desktop is a singleton; non-GUI subtasks still parallelize
|
|
868
|
+
freely), and GUI walkthroughs use the `computer` tool rather than DIY
|
|
869
|
+
shell screenshot pipelines.
|
|
870
|
+
4. **Auto-advance on idle** (armed crews only): when the session goes idle
|
|
871
|
+
between waves and no live active goal governs it, the plugin's
|
|
872
|
+
idle-continuation engine sends a `[forge:crew-continue]` brief (objective
|
|
873
|
+
+ declared subtask titles + wave discipline) after the shared debounce —
|
|
874
|
+
waves advance autonomously until `crew_close`. A PENDING crew is never
|
|
875
|
+
auto-continued (that pause is the user's decision seam), a goal-governed
|
|
876
|
+
session belongs to the goal engine exclusively, and a non-forge speaker is
|
|
877
|
+
never continued. The engine keeps its OWN activity counter (task
|
|
878
|
+
dispatches and write-family calls count); after 3 consecutive
|
|
879
|
+
activity-free continuation turns it STOPS with one bounded console
|
|
880
|
+
disclosure — the crew itself stays registered (no auto-abandon, no
|
|
881
|
+
auto-close; nudge, close, or re-shard), and repeated transport failures
|
|
882
|
+
stop it the same way. A fresh `crew_begin` clears the stall; no disk
|
|
883
|
+
state is added (the crew record is unchanged).
|
|
884
|
+
5. **Missing role?** Run the subtask through a native task call anyway and
|
|
885
|
+
tell the user — suggest configuring the missing role.
|
|
886
|
+
6. **Native fallback lane** (crew-native-fallback): when the pool offers no
|
|
887
|
+
forge worker fitting a subtask, or `forge-*` dispatch is blocked by a
|
|
888
|
+
degraded (non-compliant) pool file, the armed crew dispatches the subtask
|
|
889
|
+
through the NATIVE task channel (e.g. the `general` subagent) with the
|
|
890
|
+
SAME relay contract as a forge dispatch — the change/artifact path for
|
|
891
|
+
authoritative guides, the scoped task prompt, and the evidence demands.
|
|
892
|
+
The fallback is recorded, never silent: the crew record's wave notes name
|
|
893
|
+
it, and the subtask's `crew_close` evidence statement says it ran native
|
|
894
|
+
(free text — the close gate's cross-check is unchanged; titles, verdicts
|
|
895
|
+
and evidence statements govern exactly as before, no new field). The
|
|
896
|
+
mid-crew compliance refusal names this exit while the pool is repaired —
|
|
897
|
+
the belt gates `forge-*` dispatch only. No guard changed: the
|
|
898
|
+
registration pre-flight still refuses a non-compliant pool outright
|
|
899
|
+
(D3), cross-pool refusals keep their copy, the pending belt stands, and
|
|
900
|
+
native targets were never refused for forge-family sessions and still
|
|
901
|
+
are not.
|
|
902
|
+
7. **Verify** each subtask against its acceptance-evidence statement;
|
|
903
|
+
retry a failure at most once.
|
|
904
|
+
8. **Close** with `crew_close` — a hard, ask-gated gate cross-checking the
|
|
905
|
+
report against the DECLARED plan: every declared subtask needs a
|
|
906
|
+
PASS/FAIL verdict with evidence; an undeclared subtask in the report
|
|
907
|
+
refuses the close (fold discoveries into an existing verdict, or discard
|
|
908
|
+
and re-crew); a FAIL needs both failure reports.
|
|
909
|
+
9. **Re-shard exit**: `crew_close {abandon: true, reason}` abandons the crew
|
|
853
910
|
— ask-gated like the close, no verdict requirements, usable from PENDING
|
|
854
911
|
(standby cancel) and EXECUTING (wrong decomposition) alike. The session is
|
|
855
912
|
immediately free for a fresh `crew_begin` with the corrected plan.
|
package/dist/index.js
CHANGED
|
@@ -15048,10 +15048,11 @@ var ROLE_PROMPTS = {
|
|
|
15048
15048
|
research: "You are a research worker: search, read, and run read-only checks to answer the assigned question. Report findings as a compact brief with sources and file:line evidence; state plainly what could not be verified.",
|
|
15049
15049
|
review: "You are a review worker: read the diff or files under review, run read-only checks/tests, and report findings as a prioritized list, each with file:line evidence and a concrete fix suggestion. End with a single-line verdict."
|
|
15050
15050
|
};
|
|
15051
|
-
function rolePromptFor(agentId, def) {
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15051
|
+
function rolePromptFor(agentId, def, shared) {
|
|
15052
|
+
const chain = def?.prompt && def.prompt.trim().length > 0 ? def.prompt : ROLE_PROMPTS[agentId] ?? `You are a ${agentId} worker. Execute the assigned task exactly as scoped.`;
|
|
15053
|
+
return shared === undefined ? chain : `${shared}
|
|
15054
|
+
|
|
15055
|
+
${chain}`;
|
|
15055
15056
|
}
|
|
15056
15057
|
function poolProvenanceLine(provenance) {
|
|
15057
15058
|
if ("global" in provenance) {
|
|
@@ -15065,7 +15066,7 @@ function agentDescriptionFor(agentId, def, provenance) {
|
|
|
15065
15066
|
const base = `forge subagent "${agentId}" (${shape}, ${brain}) — dispatch via the task tool with subagent_type "forge-${agentId}".`;
|
|
15066
15067
|
return provenance === undefined ? base : `${base} ${poolProvenanceLine(provenance)}`;
|
|
15067
15068
|
}
|
|
15068
|
-
function forgeAgentDef(agentId, def, provenance) {
|
|
15069
|
+
function forgeAgentDef(agentId, def, provenance, sharedSeg) {
|
|
15069
15070
|
const permission = { task: "deny" };
|
|
15070
15071
|
const shape = def.shape ?? "write";
|
|
15071
15072
|
if (shape === "readonly") {
|
|
@@ -15080,7 +15081,7 @@ function forgeAgentDef(agentId, def, provenance) {
|
|
|
15080
15081
|
prompt: materializeAgentPrompt({
|
|
15081
15082
|
agent: agentId,
|
|
15082
15083
|
shape,
|
|
15083
|
-
role: rolePromptFor(agentId, def)
|
|
15084
|
+
role: rolePromptFor(agentId, def, sharedSeg)
|
|
15084
15085
|
}),
|
|
15085
15086
|
permission,
|
|
15086
15087
|
tools: { shell: false, bash: false }
|
|
@@ -15142,12 +15143,27 @@ var WRITE_TARGET_FLAGS = [
|
|
|
15142
15143
|
"--destination",
|
|
15143
15144
|
"-o"
|
|
15144
15145
|
];
|
|
15146
|
+
var O_FLAG_WRITER_BINARIES = [
|
|
15147
|
+
"gcc",
|
|
15148
|
+
"g++",
|
|
15149
|
+
"cc",
|
|
15150
|
+
"c++",
|
|
15151
|
+
"clang",
|
|
15152
|
+
"clang++",
|
|
15153
|
+
"ld",
|
|
15154
|
+
"rustc",
|
|
15155
|
+
"go",
|
|
15156
|
+
"curl",
|
|
15157
|
+
"sort",
|
|
15158
|
+
"psql",
|
|
15159
|
+
"dotnet"
|
|
15160
|
+
];
|
|
15145
15161
|
var EXEC_SEAL_INDICATORS = [
|
|
15146
15162
|
{ id: REDIRECT_INDICATOR, covers: "`>` `>>` and fd forms (`2>` `*>`) — bash and the PowerShell family share the operators" },
|
|
15147
15163
|
{ id: "mutating-cmdlet", covers: MUTATING_CMDLETS.join(", ") },
|
|
15148
15164
|
{ id: "git-mutation", covers: `git ${GIT_MUTATING_SUBCOMMANDS.join("/")}` },
|
|
15149
15165
|
{ id: "pkg-mutation", covers: PKG_MUTATING_COMMANDS.join(", ") + " (with common aliases: npm i/ci/add, bun install, pip3 install)" },
|
|
15150
|
-
{ id: "workspace-write", covers: `output-target flags ${WRITE_TARGET_FLAGS.join(" ")} whose parsed value is a workspace-relative path (or an absolute path under the provided workspaceRoot)` }
|
|
15166
|
+
{ id: "workspace-write", covers: `output-target flags ${WRITE_TARGET_FLAGS.join(" ")} whose parsed value is a workspace-relative path (or an absolute path under the provided workspaceRoot); the short -o counts only when the head binary is a known -o writer (${O_FLAG_WRITER_BINARIES.join(", ")})` }
|
|
15151
15167
|
];
|
|
15152
15168
|
function stripQuoted(cmd) {
|
|
15153
15169
|
let out = "";
|
|
@@ -15166,6 +15182,57 @@ function stripQuoted(cmd) {
|
|
|
15166
15182
|
}
|
|
15167
15183
|
return out;
|
|
15168
15184
|
}
|
|
15185
|
+
function tokenizeCommand(cmd) {
|
|
15186
|
+
const tokens = [];
|
|
15187
|
+
let text = "";
|
|
15188
|
+
let quoted = false;
|
|
15189
|
+
let mode = null;
|
|
15190
|
+
let sawAny = false;
|
|
15191
|
+
const flush = () => {
|
|
15192
|
+
if (sawAny)
|
|
15193
|
+
tokens.push({ text, quoted });
|
|
15194
|
+
text = "";
|
|
15195
|
+
quoted = false;
|
|
15196
|
+
sawAny = false;
|
|
15197
|
+
};
|
|
15198
|
+
for (const ch of cmd) {
|
|
15199
|
+
if (mode) {
|
|
15200
|
+
if (ch === mode) {
|
|
15201
|
+
mode = null;
|
|
15202
|
+
continue;
|
|
15203
|
+
}
|
|
15204
|
+
text += ch;
|
|
15205
|
+
sawAny = true;
|
|
15206
|
+
continue;
|
|
15207
|
+
}
|
|
15208
|
+
if (ch === '"' || ch === "'") {
|
|
15209
|
+
mode = ch;
|
|
15210
|
+
quoted = true;
|
|
15211
|
+
sawAny = true;
|
|
15212
|
+
continue;
|
|
15213
|
+
}
|
|
15214
|
+
if (ch === " " || ch === "\t" || ch === "\r" || ch === `
|
|
15215
|
+
`) {
|
|
15216
|
+
flush();
|
|
15217
|
+
continue;
|
|
15218
|
+
}
|
|
15219
|
+
text += ch;
|
|
15220
|
+
sawAny = true;
|
|
15221
|
+
}
|
|
15222
|
+
flush();
|
|
15223
|
+
return tokens;
|
|
15224
|
+
}
|
|
15225
|
+
function resolveHeadBinary(tokens) {
|
|
15226
|
+
let i = tokens.length > 0 && !tokens[0].quoted && tokens[0].text === "&" ? 1 : 0;
|
|
15227
|
+
const head = tokens[i];
|
|
15228
|
+
if (!head)
|
|
15229
|
+
return "";
|
|
15230
|
+
const parts = head.text.split(/[\\/]/);
|
|
15231
|
+
let bin = parts[parts.length - 1].toLowerCase();
|
|
15232
|
+
if (bin.endsWith(".exe"))
|
|
15233
|
+
bin = bin.slice(0, -4);
|
|
15234
|
+
return bin;
|
|
15235
|
+
}
|
|
15169
15236
|
function redirectMatch(cmd) {
|
|
15170
15237
|
const bare = stripQuoted(cmd).replace(/\r?\n/g, " ");
|
|
15171
15238
|
const m = /(?:^|[^>&])>{1,2}(?!&)[^\s;&|]*/.exec(bare);
|
|
@@ -15214,25 +15281,33 @@ function insideWorkspace(value, workspaceRoot) {
|
|
|
15214
15281
|
return norm(value).startsWith(`${norm(workspaceRoot)}/`) || norm(value) === norm(workspaceRoot);
|
|
15215
15282
|
}
|
|
15216
15283
|
function workspaceWriteMatch(cmd, workspaceRoot) {
|
|
15217
|
-
const tokens =
|
|
15284
|
+
const tokens = tokenizeCommand(cmd);
|
|
15285
|
+
const headBinary = resolveHeadBinary(tokens);
|
|
15286
|
+
const headIsWriter = O_FLAG_WRITER_BINARIES.includes(headBinary);
|
|
15218
15287
|
for (let i = 0;i < tokens.length; i++) {
|
|
15219
15288
|
const tok = tokens[i];
|
|
15289
|
+
if (tok.quoted)
|
|
15290
|
+
continue;
|
|
15220
15291
|
let flag = "";
|
|
15221
|
-
let
|
|
15222
|
-
const
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15292
|
+
let valueText;
|
|
15293
|
+
const text = tok.text;
|
|
15294
|
+
const eq = text.indexOf("=");
|
|
15295
|
+
if (eq > 0 && text.startsWith("--")) {
|
|
15296
|
+
flag = text.slice(0, eq);
|
|
15297
|
+
valueText = text.slice(eq + 1);
|
|
15298
|
+
} else if (WRITE_TARGET_FLAGS.includes(text)) {
|
|
15299
|
+
if (text === "-o" && !headIsWriter)
|
|
15300
|
+
continue;
|
|
15301
|
+
flag = text;
|
|
15302
|
+
const next = tokens[i + 1];
|
|
15303
|
+
valueText = next?.text;
|
|
15304
|
+
}
|
|
15305
|
+
if (!flag || valueText === undefined)
|
|
15231
15306
|
continue;
|
|
15232
|
-
if (
|
|
15307
|
+
if (valueText.startsWith("-"))
|
|
15233
15308
|
continue;
|
|
15234
|
-
if (pathLike(
|
|
15235
|
-
return { detail: `${flag} ${
|
|
15309
|
+
if (pathLike(valueText) && insideWorkspace(valueText, workspaceRoot)) {
|
|
15310
|
+
return { detail: `${flag} ${valueText}` };
|
|
15236
15311
|
}
|
|
15237
15312
|
}
|
|
15238
15313
|
return null;
|
|
@@ -15857,6 +15932,7 @@ function createPoolResolver(deps) {
|
|
|
15857
15932
|
parsed = {
|
|
15858
15933
|
agents: {},
|
|
15859
15934
|
declaredPool: null,
|
|
15935
|
+
shared: null,
|
|
15860
15936
|
findings: [
|
|
15861
15937
|
{
|
|
15862
15938
|
level: "error",
|
|
@@ -15866,11 +15942,20 @@ function createPoolResolver(deps) {
|
|
|
15866
15942
|
]
|
|
15867
15943
|
};
|
|
15868
15944
|
} else if (doc2.config !== undefined && !isRecord(doc2.config)) {
|
|
15869
|
-
parsed = { agents: {}, declaredPool: null, findings: [{ level: "error", code: "config-not-object", message: `${path}: top level must be an object — this pool contributes no agents` }] };
|
|
15945
|
+
parsed = { agents: {}, declaredPool: null, shared: null, findings: [{ level: "error", code: "config-not-object", message: `${path}: top level must be an object — this pool contributes no agents` }] };
|
|
15870
15946
|
} else {
|
|
15871
15947
|
const top = doc2.config === undefined ? undefined : doc2.config;
|
|
15872
15948
|
const validated = validateAgentSet(top === undefined ? undefined : top.agents);
|
|
15873
|
-
|
|
15949
|
+
const sharedRaw = top === undefined ? undefined : top.shared;
|
|
15950
|
+
let shared = null;
|
|
15951
|
+
if (sharedRaw !== undefined && sharedRaw !== null) {
|
|
15952
|
+
if (typeof sharedRaw === "string" && sharedRaw.trim().length > 0) {
|
|
15953
|
+
shared = sharedRaw.trim();
|
|
15954
|
+
} else {
|
|
15955
|
+
validated.findings.push({ level: "warn", code: "shared-invalid", message: `${path}: the top-level "shared" field must be a non-empty string — ignored` });
|
|
15956
|
+
}
|
|
15957
|
+
}
|
|
15958
|
+
parsed = { agents: validated.agents, declaredPool: top === undefined ? null : top.pool, shared, findings: validated.findings };
|
|
15874
15959
|
}
|
|
15875
15960
|
fileCache.set(path, { mtimeMs: st.mtimeMs, size: st.size, parsed });
|
|
15876
15961
|
return parsed;
|
|
@@ -15925,7 +16010,7 @@ function createPoolResolver(deps) {
|
|
|
15925
16010
|
}
|
|
15926
16011
|
}
|
|
15927
16012
|
}
|
|
15928
|
-
families.push({ file: file2, parsed, ns: "", declaredPool });
|
|
16013
|
+
families.push({ file: file2, parsed, ns: "", declaredPool, shared: parsed?.shared ?? null });
|
|
15929
16014
|
}
|
|
15930
16015
|
const allDeclared = new Set;
|
|
15931
16016
|
for (const f of families)
|
|
@@ -15958,10 +16043,10 @@ function createPoolResolver(deps) {
|
|
|
15958
16043
|
findings.push({ level: "error", code: "materialized-id-collision", message: `materialized agent forge-${matId} from ${f.file} collides with the one from ${existing.file} — the earlier pool in resolution order wins, this agent is skipped` });
|
|
15959
16044
|
continue;
|
|
15960
16045
|
}
|
|
15961
|
-
agents[matId] = { materializedId: matId, def: own[rawId], ns: f.ns, file: f.file };
|
|
16046
|
+
agents[matId] = { materializedId: matId, def: own[rawId], ns: f.ns, file: f.file, shared: f.shared };
|
|
15962
16047
|
ids.push(matId);
|
|
15963
16048
|
}
|
|
15964
|
-
origins.push({ file: f.file, ns: f.ns, declaredPool: f.declaredPool, materializedIds: ids });
|
|
16049
|
+
origins.push({ file: f.file, ns: f.ns, declaredPool: f.declaredPool, shared: f.shared, materializedIds: ids });
|
|
15965
16050
|
}
|
|
15966
16051
|
return { families: origins, agents, findings };
|
|
15967
16052
|
}
|
|
@@ -16363,6 +16448,8 @@ function crewPoolComplianceRefusal(anchor) {
|
|
|
16363
16448
|
const suggested = fam?.ns ?? sanitizeNamespace(basename2(dirname4(dirname4(poolFile))));
|
|
16364
16449
|
return `[forge:crew] Pool compliance refused: this session's workspace pool file (${poolFile}) is non-compliant — ${verdict.violation}. ` + `Minimal fix: add or repair the top-level "pool": "${suggested}" field (a [a-z0-9-] string, 1-24 chars — REQUIRED for crew-governed dispatch). ` + `The gate lifts hot-apply: the next resolve after the repair admits registration/dispatch; no host restart.`;
|
|
16365
16450
|
}
|
|
16451
|
+
var CREW_NATIVE_FALLBACK_RULE = "Native fallback: when the pool offers no forge worker fitting a subtask (or forge-* dispatch is blocked by a degraded pool), dispatch the native general subagent with the same relay contract (artifact path, scoped prompt, evidence demands) and RECORD the fallback (wave notes + the subtask's close evidence says it ran native).";
|
|
16452
|
+
var CREW_COMPLIANCE_NATIVE_EXIT = " Native exit: while the pool is being repaired, the affected subtask may dispatch through the NATIVE task channel with the same relay contract — this compliance belt gates forge-* dispatch only, never the crew's existence.";
|
|
16366
16453
|
var crews = new Map;
|
|
16367
16454
|
function crewRecordPathFor(worktree, objective) {
|
|
16368
16455
|
const dir = join6(worktree, ".opencode", "crew");
|
|
@@ -16412,7 +16499,7 @@ function crewPendingChoiceLines() {
|
|
|
16412
16499
|
return [
|
|
16413
16500
|
"[forge:crew] The crew is PENDING — execution has NOT started. Present the execution-mode choice to the user and END YOUR TURN (every task dispatch is refused while the crew pends):",
|
|
16414
16501
|
`RELAY FIRST: tell the user the crew record path (it leads this output) alongside this choice — the record is on disk from the registration moment, and the user must hear it.`,
|
|
16415
|
-
` 1. supervised waves NOW — when the user says go, call crew_begin {execution: "waves"} and run the wave discipline;`,
|
|
16502
|
+
` 1. supervised waves NOW — when the user says go, call crew_begin {execution: "waves"} and run the wave discipline; waves auto-advance on session idle until crew_close (the engine stops after a stall — nudge or re-shard);`,
|
|
16416
16503
|
` 2. convert to a goal contract — call crew_begin {execution: "goal"} (the crew ends as a conversion record; no extra dialog here), then draft goal_write with arm=true folding the declared subtasks into criteria/checks — the arm dialog is the user's gate;`,
|
|
16417
16504
|
` 3. standby — no call; the crew waits until armed or abandoned.`
|
|
16418
16505
|
];
|
|
@@ -17405,8 +17492,8 @@ function crewOrchestrationTemplate(resolution) {
|
|
|
17405
17492
|
"You are entering CREW ORCHESTRATION discipline for this session (single subject: do not switch agents). The macro contract above the crew may come from an approved plan, a spec change, or inline text — the discipline is identical in all three cases and never requires a plan. Follow it exactly:",
|
|
17406
17493
|
"",
|
|
17407
17494
|
'1. REGISTER: call `crew_begin` with the objective AND the declared subtask plan (subtasks[]: one {title} per subtask, optionally naming the intended forge-* agent). When the macro contract descends from a plan artifact, pass its path as the optional `lineage` argument (e.g. lineage: ".opencode/plan/2026-10-01-my-plan.md") — the descent is recorded as a first-class line in the crew record header; lineage is never inferred. Reconnaissance BEFORE registration is free (task calls included). If crew_begin refuses (a crew is already active here, or a plan draft is active), STOP and tell the user why — never start a second crew. A wrong plan is fixed by abandoning and re-crewing (crew_close {abandon: true, reason}), never by improvising outside the declared plan.',
|
|
17408
|
-
`2. THE PAUSE: registration enters a PENDING crew — execution has NOT started. STOP and present the user the three execution-mode choices (supervised waves now / convert to a goal contract / standby), then END YOUR TURN. Arm per the user's choice: waves → crew_begin {execution: "waves"}; conversion → crew_begin {execution: "goal"} followed by goal_write (arm=true — its dialog is the user's gate) folding the declared subtasks into criteria/checks; standby → no call. EXCEPTION: while a live active goal governs this session, register with execution: "waves" in the SAME call — the crew is born armed (the goal contract already authorized orchestration) and crew_close / abandon stay ask-free under that goal.`,
|
|
17409
|
-
"3. WAVES, NEVER FLOODS (armed crews only): execute subtasks in waves of parallel native `task` calls (subagent_type = the matching forge-* agent; a wave's results return within the calling turn). Launch the next wave ONLY after the previous wave's results are in. GUI / computer-use subtasks are mutually exclusive within a wave (the desktop is a singleton resource — at most ONE in flight; non-GUI subtasks still parallelize freely), and GUI walkthroughs use the `computer` tool rather than DIY shell screenshot pipelines.",
|
|
17495
|
+
`2. THE PAUSE: registration enters a PENDING crew — execution has NOT started. STOP and present the user the three execution-mode choices (supervised waves now / convert to a goal contract / standby), then END YOUR TURN. Arm per the user's choice: waves → crew_begin {execution: "waves"} (waves then auto-advance on session idle until crew_close — the engine stops after a stall, nudge or re-shard); conversion → crew_begin {execution: "goal"} followed by goal_write (arm=true — its dialog is the user's gate) folding the declared subtasks into criteria/checks; standby → no call. EXCEPTION: while a live active goal governs this session, register with execution: "waves" in the SAME call — the crew is born armed (the goal contract already authorized orchestration) and crew_close / abandon stay ask-free under that goal.`,
|
|
17496
|
+
"3. WAVES, NEVER FLOODS (armed crews only): execute subtasks in waves of parallel native `task` calls (subagent_type = the matching forge-* agent; a wave's results return within the calling turn). Launch the next wave ONLY after the previous wave's results are in. GUI / computer-use subtasks are mutually exclusive within a wave (the desktop is a singleton resource — at most ONE in flight; non-GUI subtasks still parallelize freely), and GUI walkthroughs use the `computer` tool rather than DIY shell screenshot pipelines. Native fallback: when the pool offers no forge worker fitting a subtask (or forge-* dispatch is blocked by a degraded pool), dispatch the native general subagent with the same relay contract (artifact path, scoped prompt, evidence demands) and RECORD the fallback (wave notes + the subtask's close evidence says it ran native).",
|
|
17410
17497
|
"4. CONTRACT RELAY (lineage-descended crews): when the macro contract descends from an on-disk artifact (the lineage names a plan artifact, a spec change directory, or a goal contract), every subtask's dispatch prompt MUST explicitly carry that artifact's path — or the exact slice the subtask works from (a delta file path, a quoted criteria section) — so the dispatched worker reads the contract itself, never your paraphrase of it. A large artifact is relayed by its exact slice; it is NEVER dropped for being too long to inline. Every close-report evidence statement MUST name the contract (or slice) the subtask worked against.",
|
|
17411
17498
|
"5. MISSING ROLE: when a subtask needs a role no configured forge-* agent matches, run that subtask through a native task call anyway AND tell the user about the gap — suggest configuring the missing role in forge.json.",
|
|
17412
17499
|
"6. VERIFY: judge each subtask strictly against its acceptance-evidence statement from the task result text. A result that does not meet the statement is a failure even if the worker sounded confident.",
|
|
@@ -17500,9 +17587,11 @@ var crewBeginTool = tool({
|
|
|
17500
17587
|
title: `crew armed: ${crew.objective.slice(0, 60)}`,
|
|
17501
17588
|
output: [
|
|
17502
17589
|
`[forge:crew] Crew ARMED for supervised waves: ${crew.objective}`,
|
|
17590
|
+
`Waves auto-advance on session idle until crew_close (the engine stops after a stall — nudge or re-shard).`,
|
|
17503
17591
|
`Declared plan (${crew.subtasks.length}):`,
|
|
17504
17592
|
...crew.subtasks.map((s, i) => ` ${i + 1}. ${s.title}${s.agent ? ` → ${s.agent}` : ""}`),
|
|
17505
|
-
"Proceed with the wave discipline: waves of parallel native task calls (next wave after the previous results) → per-subtask evidence verdicts → at most one retry → crew_close with the full report covering every declared subtask."
|
|
17593
|
+
"Proceed with the wave discipline: waves of parallel native task calls (next wave after the previous results) → per-subtask evidence verdicts → at most one retry → crew_close with the full report covering every declared subtask.",
|
|
17594
|
+
CREW_NATIVE_FALLBACK_RULE
|
|
17506
17595
|
].join(`
|
|
17507
17596
|
`)
|
|
17508
17597
|
};
|
|
@@ -17554,6 +17643,7 @@ var crewBeginTool = tool({
|
|
|
17554
17643
|
recordNote = "Crew record could not be written (fail-soft; the crew still runs).";
|
|
17555
17644
|
}
|
|
17556
17645
|
}
|
|
17646
|
+
crewEngineForgetSession(context.sessionID);
|
|
17557
17647
|
crews.set(context.sessionID, { objective: objectiveText, startedAt: nowIso(), subtasks, mode: governed ? "executing" : "pending", recordPath });
|
|
17558
17648
|
appendCrewRecord(recordPath, governed ? "registered (born armed under a governing goal)" : "registered (pending)", subtasks.map((s, i) => `${i + 1}. ${s.title}${s.agent ? ` → ${s.agent}` : ""}`));
|
|
17559
17649
|
let disclosure = "";
|
|
@@ -17591,7 +17681,7 @@ var crewBeginTool = tool({
|
|
|
17591
17681
|
...disclosure ? [disclosure] : []
|
|
17592
17682
|
];
|
|
17593
17683
|
if (governed) {
|
|
17594
|
-
lines.push("Goal-delegated orchestration: the governing goal contract already authorized this crew — run the wave discipline NOW (crew gates stay internal to the loop; crew_close and abandon are ask-free under this goal), and re-shard via crew_close {abandon: true, reason} + a fresh one-call crew_begin when the decomposition proves wrong.", "Wave discipline: waves of parallel native task calls (next wave after the previous results) → per-subtask evidence verdicts → at most one retry → crew_close with the full report covering every declared subtask.");
|
|
17684
|
+
lines.push("Goal-delegated orchestration: the governing goal contract already authorized this crew — run the wave discipline NOW (crew gates stay internal to the loop; crew_close and abandon are ask-free under this goal), and re-shard via crew_close {abandon: true, reason} + a fresh one-call crew_begin when the decomposition proves wrong.", "This crew is driven by the goal engine's own continuations (no separate crew briefs); standalone waves auto-advance on session idle until crew_close (the engine stops after a stall — nudge or re-shard).", "Wave discipline: waves of parallel native task calls (next wave after the previous results) → per-subtask evidence verdicts → at most one retry → crew_close with the full report covering every declared subtask.", CREW_NATIVE_FALLBACK_RULE);
|
|
17595
17685
|
} else {
|
|
17596
17686
|
lines.push(...crewPendingChoiceLines());
|
|
17597
17687
|
}
|
|
@@ -17731,6 +17821,91 @@ function engineForgetAll() {
|
|
|
17731
17821
|
for (const id of [...idleTimers.keys()])
|
|
17732
17822
|
engineForgetSession(id);
|
|
17733
17823
|
}
|
|
17824
|
+
var CREW_NO_PROGRESS_LIMIT = 3;
|
|
17825
|
+
var crewPendingTurn = new Set;
|
|
17826
|
+
var crewTurnActivity = new Map;
|
|
17827
|
+
var crewNoProgress = new Map;
|
|
17828
|
+
var crewStalled = new Set;
|
|
17829
|
+
function crewEngineForgetSession(sessionID) {
|
|
17830
|
+
crewPendingTurn.delete(sessionID);
|
|
17831
|
+
crewTurnActivity.delete(sessionID);
|
|
17832
|
+
crewNoProgress.delete(sessionID);
|
|
17833
|
+
crewStalled.delete(sessionID);
|
|
17834
|
+
}
|
|
17835
|
+
function crewContinueBrief(crew) {
|
|
17836
|
+
return [
|
|
17837
|
+
`[forge:crew-continue] The crew "${crew.objective}" is armed (waves) — continue the wave discipline.`,
|
|
17838
|
+
`Declared plan (every subtask needs a verdict at close):`,
|
|
17839
|
+
...crew.subtasks.map((s, i) => `${i + 1}. ${s.title}`),
|
|
17840
|
+
`Next: dispatch the next wave (parallel task calls — forge-* workers from this session's pool, or the native general subagent when no forge worker fits), collect per-subtask evidence verdicts, at most one retry per subtask, then crew_close with the full report. Stalled or wrong decomposition? crew_close {abandon: true, reason} to re-shard.`
|
|
17841
|
+
].join(`
|
|
17842
|
+
`);
|
|
17843
|
+
}
|
|
17844
|
+
async function continueCrewIfEligible(client, state, sessionID) {
|
|
17845
|
+
const crew = crews.get(sessionID);
|
|
17846
|
+
if (!crew || crew.mode !== "executing") {
|
|
17847
|
+
goalProbe(`crew skip: no executing crew session=${sessionID}`);
|
|
17848
|
+
return;
|
|
17849
|
+
}
|
|
17850
|
+
if (crewStalled.has(sessionID)) {
|
|
17851
|
+
goalProbe(`crew skip: stalled session=${sessionID}`);
|
|
17852
|
+
return;
|
|
17853
|
+
}
|
|
17854
|
+
if (!sessionIsForgeFamily(sessionID)) {
|
|
17855
|
+
goalProbe(`crew skip: agent switched to non-forge before send session=${sessionID}`);
|
|
17856
|
+
return;
|
|
17857
|
+
}
|
|
17858
|
+
if (crewPendingTurn.has(sessionID)) {
|
|
17859
|
+
crewPendingTurn.delete(sessionID);
|
|
17860
|
+
const act = crewTurnActivity.get(sessionID);
|
|
17861
|
+
if (act && (act.dispatches > 0 || act.writes > 0)) {
|
|
17862
|
+
crewNoProgress.delete(sessionID);
|
|
17863
|
+
} else {
|
|
17864
|
+
const n = (crewNoProgress.get(sessionID) ?? 0) + 1;
|
|
17865
|
+
crewNoProgress.set(sessionID, n);
|
|
17866
|
+
goalProbe(`crew no-progress session=${sessionID} streak=${n}`);
|
|
17867
|
+
if (n >= CREW_NO_PROGRESS_LIMIT) {
|
|
17868
|
+
crewStalled.add(sessionID);
|
|
17869
|
+
crewTurnActivity.delete(sessionID);
|
|
17870
|
+
console.warn(`[forge:crew-continue] stalled: crew "${crew.objective.slice(0, 80)}" produced ${n} activity-free continuation turns — the engine stops continuing it (the crew stays registered; nudge the session, crew_close the report, or crew_close {abandon: true, reason} to re-shard).`);
|
|
17871
|
+
return;
|
|
17872
|
+
}
|
|
17873
|
+
}
|
|
17874
|
+
}
|
|
17875
|
+
if (typeof client.session.status === "function") {
|
|
17876
|
+
try {
|
|
17877
|
+
const st = await client.session.status({ path: { id: sessionID } });
|
|
17878
|
+
const t = st?.[sessionID]?.type;
|
|
17879
|
+
if (t && t !== "idle") {
|
|
17880
|
+
goalProbe(`crew skip: session busy again session=${sessionID} status=${t}`);
|
|
17881
|
+
return;
|
|
17882
|
+
}
|
|
17883
|
+
} catch (err) {
|
|
17884
|
+
goalProbe(`crew skip: status check failed session=${sessionID} err=${String(err)}`);
|
|
17885
|
+
return;
|
|
17886
|
+
}
|
|
17887
|
+
}
|
|
17888
|
+
try {
|
|
17889
|
+
await client.session.prompt({
|
|
17890
|
+
path: { id: sessionID },
|
|
17891
|
+
body: { parts: [{ type: "text", text: crewContinueBrief(crew) }] }
|
|
17892
|
+
});
|
|
17893
|
+
transportFails.delete(sessionID);
|
|
17894
|
+
crewPendingTurn.add(sessionID);
|
|
17895
|
+
crewTurnActivity.set(sessionID, { dispatches: 0, writes: 0 });
|
|
17896
|
+
goalProbe(`crew continued session=${sessionID} objective="${crew.objective.slice(0, 60)}"`);
|
|
17897
|
+
} catch (err) {
|
|
17898
|
+
const n = (transportFails.get(sessionID) ?? 0) + 1;
|
|
17899
|
+
transportFails.set(sessionID, n);
|
|
17900
|
+
goalProbe(`crew transport failure session=${sessionID} count=${n} err=${String(err)}`);
|
|
17901
|
+
if (n >= TRANSPORT_FAILURE_LIMIT) {
|
|
17902
|
+
crewStalled.add(sessionID);
|
|
17903
|
+
crewPendingTurn.delete(sessionID);
|
|
17904
|
+
crewTurnActivity.delete(sessionID);
|
|
17905
|
+
console.warn(`[forge:crew-continue] stopped: ${n} consecutive delivery failures for crew "${crew.objective.slice(0, 80)}" — the engine stops continuing it (the crew stays registered; nudge the session or crew_close when back).`);
|
|
17906
|
+
}
|
|
17907
|
+
}
|
|
17908
|
+
}
|
|
17734
17909
|
function goalBriefText(state, goal) {
|
|
17735
17910
|
const d = goal.doc;
|
|
17736
17911
|
return [
|
|
@@ -17743,7 +17918,7 @@ ${d.checks.map((c, i) => `${i + 1}. ${c.kind === "shell" ? `shell \`${c.cmd}\``
|
|
|
17743
17918
|
`)}`,
|
|
17744
17919
|
d.constraints.trim() ? `Constraints: ${d.constraints.trim()}` : "",
|
|
17745
17920
|
`Budget: ${d.turnsUsed}/${d.maxTurns} turns used. Work the criteria now; call goal_check when you believe they hold, then goal_complete (it re-runs every check itself and asks the user). If genuinely blocked, call goal_pause with the blocker — do not spin.`,
|
|
17746
|
-
`Crew layer: you own it under this goal — when parallel decomposition serves a criterion, register crews with crew_begin {objective, subtasks, execution: "waves"} in ONE call (born armed; no pause mid-loop), dispatch forge-* waves, and re-shard a wrong decomposition via crew_close {abandon: true, reason} plus a fresh registration. Crew gates stay internal to this loop.`,
|
|
17921
|
+
`Crew layer: you own it under this goal — when parallel decomposition serves a criterion, register crews with crew_begin {objective, subtasks, execution: "waves"} in ONE call (born armed; no pause mid-loop), dispatch forge-* waves, and re-shard a wrong decomposition via crew_close {abandon: true, reason} plus a fresh registration. Crew gates stay internal to this loop (your goal turns drive the crew — no separate crew idle briefs fire under this goal; standalone waves auto-advance on session idle until crew_close).`,
|
|
17747
17922
|
`Goal file: ${relFrom(state.worktree, goal.path)}`
|
|
17748
17923
|
].filter((s) => s.length > 0).join(`
|
|
17749
17924
|
`);
|
|
@@ -17817,12 +17992,9 @@ async function continueIfEligible(client, sessionID) {
|
|
|
17817
17992
|
}
|
|
17818
17993
|
}
|
|
17819
17994
|
const goal = resolveLiveGoal(state);
|
|
17820
|
-
if (!goal || goal.doc.status !== "active") {
|
|
17821
|
-
goalProbe(`
|
|
17822
|
-
|
|
17823
|
-
}
|
|
17824
|
-
if (!goal.doc.session || goal.doc.session !== sessionID) {
|
|
17825
|
-
goalProbe(`skip: not goal owner session=${sessionID} owner=${goal.doc.session || "(none)"}`);
|
|
17995
|
+
if (!goal || goal.doc.status !== "active" || !goal.doc.session || goal.doc.session !== sessionID) {
|
|
17996
|
+
goalProbe(`no live active goal owned by session=${sessionID} — crew branch`);
|
|
17997
|
+
await continueCrewIfEligible(client, state, sessionID);
|
|
17826
17998
|
return;
|
|
17827
17999
|
}
|
|
17828
18000
|
if (!sessionIsForgeFamily(sessionID)) {
|
|
@@ -18045,7 +18217,7 @@ var server = async (input, options) => {
|
|
|
18045
18217
|
if (agentSection[id] !== undefined)
|
|
18046
18218
|
continue;
|
|
18047
18219
|
const provenance = foldPath(agent.file) === foldPath(globalMaterializationPath) ? { global: true } : { ns: agent.ns, file: agent.file };
|
|
18048
|
-
const entry = forgeAgentDef(matId, agent.def, provenance);
|
|
18220
|
+
const entry = forgeAgentDef(matId, agent.def, provenance, agent.shared ?? undefined);
|
|
18049
18221
|
agentSection[id] = entry;
|
|
18050
18222
|
materializedSnapshot[id] = entry;
|
|
18051
18223
|
}
|
|
@@ -18162,7 +18334,7 @@ var server = async (input, options) => {
|
|
|
18162
18334
|
if (anchor) {
|
|
18163
18335
|
const complianceRefusal = crewPoolComplianceRefusal(anchor);
|
|
18164
18336
|
if (complianceRefusal)
|
|
18165
|
-
throw new Error(complianceRefusal);
|
|
18337
|
+
throw new Error(complianceRefusal + CREW_COMPLIANCE_NATIVE_EXIT);
|
|
18166
18338
|
}
|
|
18167
18339
|
}
|
|
18168
18340
|
}
|
|
@@ -18380,6 +18552,8 @@ var server = async (input, options) => {
|
|
|
18380
18552
|
sealFindings.delete(info.id);
|
|
18381
18553
|
sealUnknownNotes.delete(info.id);
|
|
18382
18554
|
poolGateUnknownNotes.delete(info.id);
|
|
18555
|
+
crews.delete(info.id);
|
|
18556
|
+
crewEngineForgetSession(info.id);
|
|
18383
18557
|
}
|
|
18384
18558
|
}
|
|
18385
18559
|
},
|
|
@@ -18412,12 +18586,19 @@ var server = async (input, options) => {
|
|
|
18412
18586
|
if (typeof input2.tool !== "string")
|
|
18413
18587
|
return;
|
|
18414
18588
|
const act = turnActivity.get(input2.sessionID);
|
|
18415
|
-
if (
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18589
|
+
if (act) {
|
|
18590
|
+
if (isWriteTool(input2.tool) || input2.tool === "plan_tick")
|
|
18591
|
+
act.writes++;
|
|
18592
|
+
if (input2.tool === "goal_check")
|
|
18593
|
+
act.checks++;
|
|
18594
|
+
}
|
|
18595
|
+
const crewAct = crewTurnActivity.get(input2.sessionID);
|
|
18596
|
+
if (crewAct) {
|
|
18597
|
+
if (input2.tool === "task")
|
|
18598
|
+
crewAct.dispatches++;
|
|
18599
|
+
if (isWriteTool(input2.tool))
|
|
18600
|
+
crewAct.writes++;
|
|
18601
|
+
}
|
|
18421
18602
|
},
|
|
18422
18603
|
"experimental.chat.system.transform": async (input2, output) => {
|
|
18423
18604
|
if (!input2.sessionID)
|
|
@@ -18506,7 +18687,7 @@ async function v2Setup(ctx) {
|
|
|
18506
18687
|
if (draft.get(id) !== undefined)
|
|
18507
18688
|
continue;
|
|
18508
18689
|
const provenance = foldPath(agent.file) === foldPath(v2GlobalPath) ? { global: true } : { ns: agent.ns, file: agent.file };
|
|
18509
|
-
const v1def = forgeAgentDef(matId, agent.def, provenance);
|
|
18690
|
+
const v1def = forgeAgentDef(matId, agent.def, provenance, agent.shared ?? undefined);
|
|
18510
18691
|
draft.update(id, (agentDraft) => {
|
|
18511
18692
|
agentDraft.description = v1def.description;
|
|
18512
18693
|
agentDraft.system = v1def.prompt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorenllm/opencode-forge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
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",
|