opencode-usage-coach 0.2.1 → 0.2.3
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/dist/index.js +49 -21
- package/dist/tui.js +5 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync, writeFileSync, appendFileSync, readFileSync, existsSync } fr
|
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
4
|
import { createHash } from "crypto";
|
|
5
5
|
import { homedir } from "os";
|
|
6
|
-
import { join, resolve } from "path";
|
|
6
|
+
import { join, resolve, dirname } from "path";
|
|
7
7
|
import { tool } from "@opencode-ai/plugin";
|
|
8
8
|
var PLUGIN_NAME = "opencode-usage-coach";
|
|
9
9
|
var DEBUG = process.env.UC_DEBUG === "1";
|
|
@@ -38,19 +38,24 @@ function writeState(c) {
|
|
|
38
38
|
} catch {
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function harnessFile(sessionID) {
|
|
42
|
+
return join(STATE_DIR, sessionID || "_default", "harness.json");
|
|
43
|
+
}
|
|
44
|
+
function readHarness(sessionID) {
|
|
42
45
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
const f = harnessFile(sessionID);
|
|
47
|
+
if (!existsSync(f)) return null;
|
|
48
|
+
return JSON.parse(readFileSync(f, "utf8"));
|
|
45
49
|
} catch {
|
|
46
50
|
return null;
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
|
-
function writeHarness(h) {
|
|
53
|
+
function writeHarness(sessionID, h) {
|
|
50
54
|
try {
|
|
51
|
-
|
|
55
|
+
const f = harnessFile(sessionID);
|
|
56
|
+
mkdirSync(dirname(f), { recursive: true });
|
|
52
57
|
h.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
53
|
-
writeFileSync(
|
|
58
|
+
writeFileSync(f, JSON.stringify(h, null, 2));
|
|
54
59
|
} catch {
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -68,6 +73,7 @@ function readHarnessCfg(dir) {
|
|
|
68
73
|
};
|
|
69
74
|
}
|
|
70
75
|
async function runModel(client, model, prompt, directory) {
|
|
76
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
71
77
|
try {
|
|
72
78
|
const slash = model.indexOf("/");
|
|
73
79
|
const providerID = slash >= 0 ? model.slice(0, slash) : model;
|
|
@@ -75,11 +81,20 @@ async function runModel(client, model, prompt, directory) {
|
|
|
75
81
|
const s = await client.session.create({ body: { title: "uc-harness-sub" }, query: { directory } });
|
|
76
82
|
const id = s?.data?.info?.id;
|
|
77
83
|
if (!id) return null;
|
|
78
|
-
|
|
84
|
+
await client.session.prompt({
|
|
79
85
|
path: { id },
|
|
80
86
|
body: { model: { providerID, modelID }, parts: [{ type: "text", text: prompt }] }
|
|
81
87
|
});
|
|
82
|
-
|
|
88
|
+
for (let i = 0; i < 600; i++) {
|
|
89
|
+
await sleep(1e3);
|
|
90
|
+
const st = await client.session.status({ path: { id } });
|
|
91
|
+
const status = st?.data?.[id]?.status ?? st?.data?.status;
|
|
92
|
+
if (status === "idle" || status === "completed" || !status) break;
|
|
93
|
+
}
|
|
94
|
+
const msgs = await client.session.messages({ path: { id } });
|
|
95
|
+
const all = msgs?.data ?? [];
|
|
96
|
+
const lastAssistant = all.filter((m) => m?.info?.role === "assistant").pop();
|
|
97
|
+
const parts = lastAssistant?.parts ?? [];
|
|
83
98
|
const text = parts.filter((p) => p?.type === "text").map((p) => p?.text ?? "").join("");
|
|
84
99
|
try {
|
|
85
100
|
await client.session.remove?.({ path: { id } });
|
|
@@ -298,8 +313,8 @@ async function UsageCoachPlugin(input) {
|
|
|
298
313
|
harness_start: tool({
|
|
299
314
|
description: "Start the harness: register the total task count on the panel. Call once when the harness loop begins.",
|
|
300
315
|
args: { name: tool.schema.string(), total: tool.schema.number() },
|
|
301
|
-
async execute(args) {
|
|
302
|
-
writeHarness({ name: args.name, total: args.total, current: 0, tasks: [], usage: {}, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
316
|
+
async execute(args, ctx) {
|
|
317
|
+
writeHarness(ctx.sessionID, { name: args.name, total: args.total, current: 0, tasks: [], usage: {}, active: true, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
303
318
|
return `Harness '${args.name}' started (${args.total} tasks). Now report each task's status via task_update and run the loop.`;
|
|
304
319
|
}
|
|
305
320
|
}),
|
|
@@ -313,31 +328,31 @@ async function UsageCoachPlugin(input) {
|
|
|
313
328
|
score: tool.schema.string().optional().describe("PASS | FAIL"),
|
|
314
329
|
model: tool.schema.string().optional()
|
|
315
330
|
},
|
|
316
|
-
async execute(args) {
|
|
317
|
-
const h = readHarness() ?? { name: "batch", total: 0, current: 0, tasks: [], usage: {} };
|
|
331
|
+
async execute(args, ctx) {
|
|
332
|
+
const h = readHarness(ctx.sessionID) ?? { name: "batch", total: 0, current: 0, tasks: [], usage: {}, active: true };
|
|
318
333
|
h.tasks = h.tasks.filter((x) => x.id !== args.id);
|
|
319
334
|
h.tasks.push({ id: args.id, title: args.title, status: args.status, model: args.model ?? "", revisions: args.revisions ?? 0, score: args.score ?? null });
|
|
320
335
|
if (args.id > h.current) h.current = args.id;
|
|
321
|
-
writeHarness(h);
|
|
336
|
+
writeHarness(ctx.sessionID, h);
|
|
322
337
|
return `task ${args.id} -> ${args.status}${args.score ? ` (${args.score})` : ""}`;
|
|
323
338
|
}
|
|
324
339
|
}),
|
|
325
340
|
harness_done: tool({
|
|
326
341
|
description: "Mark the harness as complete \u2014 call when the loop ends.",
|
|
327
342
|
args: {},
|
|
328
|
-
async execute() {
|
|
329
|
-
const h = readHarness();
|
|
343
|
+
async execute(_args, ctx) {
|
|
344
|
+
const h = readHarness(ctx.sessionID);
|
|
330
345
|
if (h) {
|
|
331
346
|
h.current = h.total;
|
|
332
|
-
|
|
347
|
+
h.active = false;
|
|
348
|
+
writeHarness(ctx.sessionID, h);
|
|
333
349
|
}
|
|
334
350
|
return "Harness complete.";
|
|
335
351
|
}
|
|
336
352
|
}),
|
|
337
|
-
// Per-role model execution (config-driven
|
|
338
|
-
// on the same server — enables multi-model (e.g. GLM generate + free mimo grade) in 1 terminal.
|
|
353
|
+
// Per-role model execution (config-driven, same server, no deadlock).
|
|
339
354
|
generate: tool({
|
|
340
|
-
description: "Run the GENERATOR model
|
|
355
|
+
description: "Run the GENERATOR model on a prompt. The generator can use tools (write/edit files). Returns the model's text response.",
|
|
341
356
|
args: { prompt: tool.schema.string() },
|
|
342
357
|
async execute(args, ctx) {
|
|
343
358
|
const cfg = readHarnessCfg(ctx.directory);
|
|
@@ -346,8 +361,21 @@ async function UsageCoachPlugin(input) {
|
|
|
346
361
|
return out ?? `(generator ${model} produced no text)`;
|
|
347
362
|
}
|
|
348
363
|
}),
|
|
364
|
+
generate_batch: tool({
|
|
365
|
+
description: "Run the GENERATOR model on MULTIPLE tasks in PARALLEL. Pass an array of {id, prompt}. Returns all results at once. Use for INDEPENDENT tasks (much faster than sequential generate calls).",
|
|
366
|
+
args: { tasks: tool.schema.array(tool.schema.object({ id: tool.schema.number(), prompt: tool.schema.string() })) },
|
|
367
|
+
async execute(args, ctx) {
|
|
368
|
+
const cfg = readHarnessCfg(ctx.directory);
|
|
369
|
+
const model = cfg.generator ?? "zai-coding-plan/glm-5.1";
|
|
370
|
+
const results = await Promise.all(args.tasks.map(async (t) => {
|
|
371
|
+
const out = await runModel(input.client, model, t.prompt, ctx.directory);
|
|
372
|
+
return `[task ${t.id}] ${out ?? "(no output)"}`;
|
|
373
|
+
}));
|
|
374
|
+
return results.join("\n\n");
|
|
375
|
+
}
|
|
376
|
+
}),
|
|
349
377
|
grade: tool({
|
|
350
|
-
description: "Run the GRADER model
|
|
378
|
+
description: "Run the GRADER model on a prompt. Returns PASS/FAIL on the first line. Falls back to generator if grader quota is out.",
|
|
351
379
|
args: { prompt: tool.schema.string() },
|
|
352
380
|
async execute(args, ctx) {
|
|
353
381
|
const cfg = readHarnessCfg(ctx.directory);
|
package/dist/tui.js
CHANGED
|
@@ -132,7 +132,11 @@ function initializeTui(api, disposeRoot) {
|
|
|
132
132
|
}
|
|
133
133
|
let h = null;
|
|
134
134
|
try {
|
|
135
|
-
|
|
135
|
+
const sid = ctx.session_id ?? "";
|
|
136
|
+
if (sid) {
|
|
137
|
+
const hf = join(STATE_DIR, sid, "harness.json");
|
|
138
|
+
if (existsSync(hf)) h = JSON.parse(readFileSync(hf, "utf8"));
|
|
139
|
+
}
|
|
136
140
|
} catch {
|
|
137
141
|
h = null;
|
|
138
142
|
}
|
package/package.json
CHANGED