opencode-usage-coach 0.1.7 → 0.2.1
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 +11 -10
- package/agents/usage-coach-harness.md +14 -13
- package/dist/index.js +59 -0
- package/dist/tui.js +0 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,15 +13,16 @@ the loop** — and ships a harness agent mode + a deterministic orchestrator.
|
|
|
13
13
|
- Senses z.ai quota windows via the `codexbar` CLI.
|
|
14
14
|
- On STOP threshold: blocks tool calls (`tool.execute.before` throws) → the agent self-stops.
|
|
15
15
|
- Injects coaching (how to use right now) into the system prompt (double defense).
|
|
16
|
-
- Surfaces a sidebar panel: quota meters + harness task states +
|
|
16
|
+
- Surfaces a sidebar panel: per-provider quota meters + harness task states + coaching.
|
|
17
17
|
- Defensive: any plugin error never breaks opencode.
|
|
18
18
|
|
|
19
|
-
**Harness (
|
|
20
|
-
-
|
|
21
|
-
unclear → clarify; substantive → generate
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
**Harness (agent mode, 1 terminal):**
|
|
20
|
+
- The `usage-coach-harness` agent triages each request — trivial → do directly;
|
|
21
|
+
unclear → clarify; substantive → generate→grade→revise→advance.
|
|
22
|
+
- Multi-model via plugin tools `generate`/`grade`: run the configured generator/grader model
|
|
23
|
+
(from `harness.config.json`) in a new session on the same server — no second terminal.
|
|
24
|
+
e.g. GLM generation + free mimo grading in one opencode session.
|
|
25
|
+
- Reports progress via `task_update` → the sidebar panel shows live task states.
|
|
25
26
|
|
|
26
27
|
## Requirements
|
|
27
28
|
- opencode (tested on 1.17.13) with a z.ai coding-plan provider configured.
|
|
@@ -118,8 +119,8 @@ opencode's bundled solid); TUI plugins must be compiled + loaded via `tui.json`
|
|
|
118
119
|
`codexbar` must be called via `spawn` (the `$` BunShell leaks output to the TUI).
|
|
119
120
|
|
|
120
121
|
## Status
|
|
121
|
-
- ✅ M0–M2 quota guardian + TUI panel
|
|
122
|
-
- ✅ M3 harness: agent mode (triage)
|
|
123
|
-
-
|
|
122
|
+
- ✅ M0–M2 quota guardian + TUI panel (per-provider coach view, colors, collapsible Alt+H)
|
|
123
|
+
- ✅ M3 harness: agent mode (triage) with generate/grade model-specific tools (1 terminal, multi-model)
|
|
124
|
+
- ✅ M4 npm packaging (`opencode plugin install opencode-usage-coach`)
|
|
124
125
|
|
|
125
126
|
License: MIT.
|
|
@@ -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
|
-
**
|
|
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 server — so 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)
|
|
42
|
+
2. For each task i (1..N):
|
|
39
43
|
a. `task_update(i, title, "generating")`.
|
|
40
|
-
b. **Generate** —
|
|
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** —
|
|
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
|
|
48
|
-
- `FAIL` and revisions < 2 → `task_update(i, title, "revising", revisions:k)` →
|
|
49
|
-
- `FAIL` and revisions exhausted → `task_update(i, title, "failed", score:"FAIL")` → next
|
|
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,
|
|
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
|
|
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
|
|
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/dist/tui.js
CHANGED
|
@@ -379,17 +379,6 @@ function initializeTui(api, disposeRoot) {
|
|
|
379
379
|
return _el$72;
|
|
380
380
|
})();
|
|
381
381
|
}
|
|
382
|
-
},
|
|
383
|
-
home_footer(ctx) {
|
|
384
|
-
try {
|
|
385
|
-
return panel(ctx);
|
|
386
|
-
} catch {
|
|
387
|
-
return (() => {
|
|
388
|
-
var _el$74 = _$createElement("text");
|
|
389
|
-
_$insertNode(_el$74, _$createTextNode(`usage-coach`));
|
|
390
|
-
return _el$74;
|
|
391
|
-
})();
|
|
392
|
-
}
|
|
393
382
|
}
|
|
394
383
|
}
|
|
395
384
|
});
|
package/package.json
CHANGED