@sorenllm/opencode-forge 0.11.0 → 0.11.2

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 CHANGED
@@ -428,6 +428,12 @@ workspace-local file that the host was not launched on is NOT silently
428
428
  ignored: `/crew` names it and tells you to relaunch there (or fold the
429
429
  agents into the global layer).
430
430
 
431
+ **Id naming**: use plain role words (`research`, `coder`, …) — the plugin
432
+ materializes every id as `forge-<id>` itself. A `forge-`-prefixed id in the
433
+ file would double up (`forge-coder` → agent `forge-forge-coder`); the loader
434
+ auto-strips the prefix (all repetitions) with a warning so existing files
435
+ keep working, but rename the entries to silence it.
436
+
431
437
  ```jsonc
432
438
  {
433
439
  // forge subagents — definitions; see the restart table below for apply timing
@@ -436,7 +442,7 @@ agents into the global layer).
436
442
  "research": {
437
443
  "model": "zai-coding-plan/glm-5.3", // exact "provider/model" string
438
444
  "thoughtLevel": "low", // none/low/medium/high/max or a native level name
439
- // "prompt": "optional role prompt; built-in research/review defaults exist",
445
+ // "prompt": "short role description — explicit prompts fully override the built-ins (only research/review ids have them; any other id without a prompt gets a generic one-liner)",
440
446
  // "shape": "write", // default readonly denies write/edit/bash
441
447
  // "permission": { "bash": "deny" } // optional override; "task" is ALWAYS denied
442
448
  },
package/dist/index.js CHANGED
@@ -15358,12 +15358,21 @@ function validateAgentSet(raw) {
15358
15358
  findings.push({ level: "error", code: "agents-not-object", message: 'forge.json "agents" must be an object mapping agent ids to definitions — the agent set is empty' });
15359
15359
  return { agents, findings };
15360
15360
  }
15361
- for (const [id, entry] of Object.entries(raw)) {
15362
- const label = `agents.${id}`;
15361
+ for (const [rawId, entry] of Object.entries(raw)) {
15362
+ let label = `agents.${rawId}`;
15363
+ let id = rawId;
15364
+ if (rawId.startsWith("forge-")) {
15365
+ id = rawId.replace(/^(forge-)+/, "");
15366
+ findings.push({ level: "warn", code: "agent-id-forge-prefix", message: `${label}: the forge- prefix is added automatically at materialization — stripped to "${id}"; rename the entry to silence this warning` });
15367
+ }
15363
15368
  if (!AGENT_ID_RE.test(id)) {
15364
15369
  findings.push({ level: "error", code: "agent-id-invalid", message: `${label}: agent id must match [a-z0-9-] — entry skipped` });
15365
15370
  continue;
15366
15371
  }
15372
+ if (agents[id] !== undefined) {
15373
+ findings.push({ level: "error", code: "agent-id-collision", message: `${label}: normalizes to "${id}" which is already defined — entry skipped (first definition wins)` });
15374
+ continue;
15375
+ }
15367
15376
  if (!isRecord(entry)) {
15368
15377
  findings.push({ level: "error", code: "agent-not-object", message: `${label}: definition must be an object — entry skipped` });
15369
15378
  continue;
@@ -16491,9 +16500,9 @@ var CREW_INIT_TEMPLATE = [
16491
16500
  "1. The user configures it themselves: create either layer — MERGED, the project layer overrides the global one per agent id (the project file is the nearest .opencode/forge.json walking up from the workspace root):",
16492
16501
  "{paths}",
16493
16502
  "",
16494
- "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.",
16503
+ "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. When you configure it: name ids as PLAIN role words WITHOUT the `forge-` prefix — the plugin materializes every id as `forge-<id>` automatically (a `forge-`-prefixed id doubles up as `forge-forge-coder` and is auto-stripped with a warning). Give EVERY agent a SHORT prompt derived from the role the user asked for — one or two sentences describing its job (the user trims or extends from there); write a long prompt ONLY when the user explicitly asks for that agent.",
16495
16504
  "",
16496
- "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.",
16505
+ "Template (JSONC — comments allowed): ids are plain role words (research, coder, ...) — the plugin adds the `forge-` prefix at materialization; `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. Optional per agent: `prompt` — a short role description; an explicit prompt FULLY overrides the built-in role (only the ids `research` and `review` carry built-ins; any other id without a prompt gets a generic one-liner and will not know its job).",
16497
16506
  "{template}",
16498
16507
  "",
16499
16508
  "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."
@@ -16545,8 +16554,9 @@ var crewBeginTool = tool({
16545
16554
  const lines = [
16546
16555
  "[forge:crew] CREW IS NOT INITIALIZED on this host: no forge subagents are materialized. Crew orchestration is unavailable until the user sets it up.",
16547
16556
  `Two layers, MERGED (the project layer overrides the global one per agent id): ${paths.project} (nearest .opencode/forge.json walking up from the workspace) OR ${paths.global}.`,
16548
- 'Template (JSONC): {"agents": {"research": {"model": "provider/model", "thoughtLevel": "low"}}}',
16549
- "The user may configure it themselves, or ask you to write the file — only on their explicit go-ahead. Newly added agents need a host restart; thoughtLevel edits apply without one."
16557
+ 'Template (JSONC): {"agents": {"research": {"model": "provider/model", "thoughtLevel": "low", "prompt": "short role description"}}}',
16558
+ "Agent ids are plain role words WITHOUT the `forge-` prefix — the plugin materializes each id as `forge-<id>` (a prefixed id is auto-stripped with a warning).",
16559
+ "The user may configure it themselves, or ask you to write the file — only on their explicit go-ahead. When you write it, give every agent a SHORT prompt from the role the user asked for (one or two sentences; long only on explicit request) — without a prompt, custom ids get a generic one-liner and will not know their job. Newly added agents need a host restart; thoughtLevel edits apply without one."
16550
16560
  ];
16551
16561
  if (sessionLoaded && Object.keys(sessionLoaded.agents).length > 0 && sessionProject) {
16552
16562
  lines.push(`NOTE: this session's workspace DOES have a forge.json (${sessionProject}) with usable agents — but this host instance was launched from ${hostAnchorDir || "a different directory"}, so they are not in the task vocabulary. Relaunch opencode in that workspace, or add the agents to the global layer.`);
@@ -17050,7 +17060,7 @@ var server = async (input, options) => {
17050
17060
  if (!existingCrew || String(existingCrew.template ?? "").startsWith("(forge crew")) {
17051
17061
  const agentIds = Object.keys(loaded.agents);
17052
17062
  const template = agentIds.length > 0 ? crewOrchestrationTemplate(agentIds) : CREW_INIT_TEMPLATE.replaceAll("{paths}", ` - ${forgeConfigPaths({ projectDir: forgeLoaderDir ?? undefined, homeDir: process.env.FORGE_TEST_FORGE_HOME ?? homedir() }).project}
17053
- - ${forgeConfigPaths({ projectDir: forgeLoaderDir ?? undefined, homeDir: process.env.FORGE_TEST_FORGE_HOME ?? homedir() }).global}`).replaceAll("{template}", JSON.stringify({ agents: { research: { model: "provider/model", thoughtLevel: "low" } } }, null, 2).split(`
17063
+ - ${forgeConfigPaths({ projectDir: forgeLoaderDir ?? undefined, homeDir: process.env.FORGE_TEST_FORGE_HOME ?? homedir() }).global}`).replaceAll("{template}", JSON.stringify({ agents: { research: { model: "provider/model", thoughtLevel: "low", prompt: "short role description — what this agent does" } } }, null, 2).split(`
17054
17064
  `).map((l) => " " + l).join(`
17055
17065
  `));
17056
17066
  const description = agentIds.length > 0 ? "forge crew orchestration: register a declared subtask plan, execute it in waves of parallel native task calls, close with an evidence-checked report" : "forge crew orchestration — NOT INITIALIZED: no forge subagents configured; invoking it shows the one-time setup guidance";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sorenllm/opencode-forge",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
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",