opencode-usage-coach 0.1.6 → 0.2.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.
@@ -11,6 +11,8 @@ permission:
11
11
  glob: allow
12
12
  grep: allow
13
13
  task: allow
14
+ generate: allow
15
+ grade: allow
14
16
  harness_start: allow
15
17
  task_update: allow
16
18
  harness_done: allow
@@ -32,32 +34,31 @@ Default to the loop only when it genuinely adds value. Do not over-engineer smal
32
34
  ## Harness loop (only for substantive work)
33
35
  The user's message is the task source. If it has multiple distinct parts, decompose into discrete tasks (N); if it is one unit, N = 1.
34
36
 
35
- **Parallel dispatch (independent tasks):** If the decomposed tasks are INDEPENDENT (no data dependency), dispatch them concurrently — issue **multiple `task` calls in the same turn** so they run as parallel subagents. (This is the only reliable parallel path the deterministic script cannot parallelize due to opencode's single-server model.) Cap concurrency by quota coaching: "big tasks OK" up to 3-4 parallel; "moderate/small" → 1-2; STOP → don't dispatch. For DEPENDENT tasks (B needs A's output), run sequentially.
37
+ **Multi-model, 1 terminal:** the work runs via the plugin tools `generate` and `grade`, which use the configured models (generator/grader from harness.config.json) on the same serverso you get e.g. GLM generation + free mimo grading without a second terminal. Do NOT use the `task` tool for harness work; use `generate`/`grade`.
38
+
39
+ **Independent tasks → parallel:** if the decomposed tasks are INDEPENDENT, call multiple `generate` tools in the same turn (parallel sub-sessions). Cap by quota coaching (big-OK → 3-4; throttle → 1-2; STOP → none). DEPENDENT tasks (B needs A) → sequential.
36
40
 
37
41
  1. Call `harness_start(name, N)` to register the run on the panel.
38
- 2. For each task i (1..N) — in parallel batches when independent:
42
+ 2. For each task i (1..N):
39
43
  a. `task_update(i, title, "generating")`.
40
- b. **Generate** — delegate to a subagent via the `task` tool (`subagent_type: "general"`):
41
- prompt: `"Task: {title}. Perform it for real in the current directory (write/edit files, run commands as needed)."`
42
- If a subagent is slow or the task looks too large, split it into smaller subtasks (autonomous decomposition).
44
+ b. **Generate** — call `generate({ prompt: "Task: {title}. Perform it for real in the current directory (write/edit files)." })`. The generator model does the work and returns a summary.
43
45
  c. `task_update(i, title, "grading")`.
44
- d. **Grade** — delegate via the `task` tool:
45
- prompt: `"Evaluate the result against the request's intent and general quality. Output PASS or FAIL on the first line, then the reason.\nRequest: {user request}\nTask: {title}"`
46
+ d. **Grade** — call `grade({ prompt: "Evaluate the result against the request's intent and general quality. Output PASS or FAIL on the first line, then the reason.\\nRequest: {user request}\\nTask: {title}" })`.
46
47
  e. Parse the verdict:
47
- - `PASS` → `task_update(i, title, "completed", score:"PASS")` → next task.
48
- - `FAIL` and revisions < 2 → `task_update(i, title, "revising", revisions:k)` → delegate a revision (`"Apply the grading feedback and improve:\n{grade result}"`)go back to (c) to re-grade.
49
- - `FAIL` and revisions exhausted → `task_update(i, title, "failed", score:"FAIL")` → next task.
48
+ - `PASS` → `task_update(i, title, "completed", score:"PASS")` → next.
49
+ - `FAIL` and revisions < 2 → `task_update(i, title, "revising", revisions:k)` → `generate({ prompt: "Apply the grading feedback and improve:\\n{grade result}\\nTask: {title}" })` → back to (c) re-grade.
50
+ - `FAIL` and revisions exhausted → `task_update(i, title, "failed", score:"FAIL")` → next.
50
51
  3. When all tasks are done → `harness_done()`.
51
52
 
52
53
  ## Rules
53
- - In the loop, **delegate the real work** to subagents via the `task` tool you orchestrate. (Outside the loop, for trivial requests, you may act directly.)
54
+ - In the loop, do NOT do the work yourself call `generate`/`grade` (they run the configured models). You orchestrate. (Outside the loop, for trivial requests, act directly.)
54
55
  - Call `task_update` on every state transition — the sidebar panel reads it for live visibility.
55
56
  - Grading criteria come from the user's request, or sensible defaults; ask the user only if it is truly ambiguous and grading matters.
56
57
  - If the quota coaching injected into your system prompt says **STOP**, immediately `task_update(current, "halted_quota")` and halt the loop.
57
- - If a subagent returns an incomplete result, split the task into smaller subtasks.
58
+ - If `generate` returns an incomplete result, split the task into smaller subtasks.
58
59
  - Be concise. Report only progress summaries to the user.
59
60
 
60
61
  ## Output
61
62
  - Trivial path: just the direct result.
62
- - Loop path: the actual results are the files/changes the subagents leave in the directory; at the end report a brief summary (passed / failed / split counts).
63
+ - Loop path: the actual results are the files/changes the generator leaves in the directory; at the end report a brief summary (passed / failed counts).
63
64
 
package/dist/index.js CHANGED
@@ -54,6 +54,43 @@ function writeHarness(h) {
54
54
  } catch {
55
55
  }
56
56
  }
57
+ function readHarnessCfg(dir) {
58
+ const tryRead = (p) => {
59
+ try {
60
+ if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8"));
61
+ } catch {
62
+ }
63
+ return {};
64
+ };
65
+ return {
66
+ ...tryRead(join(homedir(), ".config", "opencode-usage-coach", "harness.config.json")),
67
+ ...tryRead(join(dir, "harness.config.json"))
68
+ };
69
+ }
70
+ async function runModel(client, model, prompt, directory) {
71
+ try {
72
+ const slash = model.indexOf("/");
73
+ const providerID = slash >= 0 ? model.slice(0, slash) : model;
74
+ const modelID = slash >= 0 ? model.slice(slash + 1) : "";
75
+ const s = await client.session.create({ body: { title: "uc-harness-sub" }, query: { directory } });
76
+ const id = s?.data?.info?.id;
77
+ if (!id) return null;
78
+ const r = await client.session.prompt({
79
+ path: { id },
80
+ body: { model: { providerID, modelID }, parts: [{ type: "text", text: prompt }] }
81
+ });
82
+ const parts = r?.data?.parts ?? [];
83
+ const text = parts.filter((p) => p?.type === "text").map((p) => p?.text ?? "").join("");
84
+ try {
85
+ await client.session.remove?.({ path: { id } });
86
+ } catch {
87
+ }
88
+ return text.trim() || null;
89
+ } catch (e) {
90
+ log(`runModel err (${model}): ${String(e)}`);
91
+ return null;
92
+ }
93
+ }
57
94
  var PROVIDER = process.env.UC_PROVIDER ?? "zai";
58
95
  var num = (e, d) => {
59
96
  try {
@@ -296,6 +333,28 @@ async function UsageCoachPlugin(input) {
296
333
  }
297
334
  return "Harness complete.";
298
335
  }
336
+ }),
337
+ // Per-role model execution (config-driven). Runs a NEW session with the configured model
338
+ // on the same server — enables multi-model (e.g. GLM generate + free mimo grade) in 1 terminal.
339
+ generate: tool({
340
+ description: "Run the GENERATOR model (from harness.config.json) on a prompt. Returns the model's text response. The generator can use tools (write/edit files) in the directory. Use for the 'do the work' step.",
341
+ args: { prompt: tool.schema.string() },
342
+ async execute(args, ctx) {
343
+ const cfg = readHarnessCfg(ctx.directory);
344
+ const model = cfg.generator ?? "zai-coding-plan/glm-5.1";
345
+ const out = await runModel(input.client, model, args.prompt, ctx.directory);
346
+ return out ?? `(generator ${model} produced no text)`;
347
+ }
348
+ }),
349
+ grade: tool({
350
+ description: "Run the GRADER model (from harness.config.json) on a prompt. Returns the model's text response (expect PASS/FAIL on the first line). Use for the 'evaluate the result' step. Falls back to the generator if the grader has no quota.",
351
+ args: { prompt: tool.schema.string() },
352
+ async execute(args, ctx) {
353
+ const cfg = readHarnessCfg(ctx.directory);
354
+ const model = cfg.grader ?? cfg.generator ?? "opencode/mimo-v2.5-free";
355
+ const out = await runModel(input.client, model, args.prompt, ctx.directory);
356
+ return out ?? `(grader ${model} produced no text)`;
357
+ }
299
358
  })
300
359
  }
301
360
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "opencode closed-loop usage coach \u2014 quota SENSE -> coaching DECIDE -> loop ACT + TUI integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",