opencode-usage-coach 0.2.1 → 0.2.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/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 readHarness() {
41
+ function harnessFile(sessionID) {
42
+ return join(STATE_DIR, sessionID || "_default", "harness.json");
43
+ }
44
+ function readHarness(sessionID) {
42
45
  try {
43
- if (!existsSync(HARNESS_FILE)) return null;
44
- return JSON.parse(readFileSync(HARNESS_FILE, "utf8"));
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
- mkdirSync(STATE_DIR, { recursive: true });
55
+ const f = harnessFile(sessionID);
56
+ mkdirSync(dirname(f), { recursive: true });
52
57
  h.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
53
- writeFileSync(HARNESS_FILE, JSON.stringify(h, null, 2));
58
+ writeFileSync(f, JSON.stringify(h, null, 2));
54
59
  } catch {
55
60
  }
56
61
  }
@@ -298,8 +303,8 @@ async function UsageCoachPlugin(input) {
298
303
  harness_start: tool({
299
304
  description: "Start the harness: register the total task count on the panel. Call once when the harness loop begins.",
300
305
  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() });
306
+ async execute(args, ctx) {
307
+ writeHarness(ctx.sessionID, { name: args.name, total: args.total, current: 0, tasks: [], usage: {}, active: true, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
303
308
  return `Harness '${args.name}' started (${args.total} tasks). Now report each task's status via task_update and run the loop.`;
304
309
  }
305
310
  }),
@@ -313,31 +318,31 @@ async function UsageCoachPlugin(input) {
313
318
  score: tool.schema.string().optional().describe("PASS | FAIL"),
314
319
  model: tool.schema.string().optional()
315
320
  },
316
- async execute(args) {
317
- const h = readHarness() ?? { name: "batch", total: 0, current: 0, tasks: [], usage: {} };
321
+ async execute(args, ctx) {
322
+ const h = readHarness(ctx.sessionID) ?? { name: "batch", total: 0, current: 0, tasks: [], usage: {}, active: true };
318
323
  h.tasks = h.tasks.filter((x) => x.id !== args.id);
319
324
  h.tasks.push({ id: args.id, title: args.title, status: args.status, model: args.model ?? "", revisions: args.revisions ?? 0, score: args.score ?? null });
320
325
  if (args.id > h.current) h.current = args.id;
321
- writeHarness(h);
326
+ writeHarness(ctx.sessionID, h);
322
327
  return `task ${args.id} -> ${args.status}${args.score ? ` (${args.score})` : ""}`;
323
328
  }
324
329
  }),
325
330
  harness_done: tool({
326
331
  description: "Mark the harness as complete \u2014 call when the loop ends.",
327
332
  args: {},
328
- async execute() {
329
- const h = readHarness();
333
+ async execute(_args, ctx) {
334
+ const h = readHarness(ctx.sessionID);
330
335
  if (h) {
331
336
  h.current = h.total;
332
- writeHarness(h);
337
+ h.active = false;
338
+ writeHarness(ctx.sessionID, h);
333
339
  }
334
340
  return "Harness complete.";
335
341
  }
336
342
  }),
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.
343
+ // Per-role model execution (config-driven, same server, no deadlock).
339
344
  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.",
345
+ description: "Run the GENERATOR model on a prompt. The generator can use tools (write/edit files). Returns the model's text response.",
341
346
  args: { prompt: tool.schema.string() },
342
347
  async execute(args, ctx) {
343
348
  const cfg = readHarnessCfg(ctx.directory);
@@ -346,8 +351,21 @@ async function UsageCoachPlugin(input) {
346
351
  return out ?? `(generator ${model} produced no text)`;
347
352
  }
348
353
  }),
354
+ generate_batch: tool({
355
+ 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).",
356
+ args: { tasks: tool.schema.array(tool.schema.object({ id: tool.schema.number(), prompt: tool.schema.string() })) },
357
+ async execute(args, ctx) {
358
+ const cfg = readHarnessCfg(ctx.directory);
359
+ const model = cfg.generator ?? "zai-coding-plan/glm-5.1";
360
+ const results = await Promise.all(args.tasks.map(async (t) => {
361
+ const out = await runModel(input.client, model, t.prompt, ctx.directory);
362
+ return `[task ${t.id}] ${out ?? "(no output)"}`;
363
+ }));
364
+ return results.join("\n\n");
365
+ }
366
+ }),
349
367
  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.",
368
+ 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
369
  args: { prompt: tool.schema.string() },
352
370
  async execute(args, ctx) {
353
371
  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
- h = getHarness();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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",