opencode-usage-coach 0.2.5 → 0.3.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.
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ function setStateDir(dir) {
25
25
  }
26
26
  var NOOP_HOOKS = {};
27
27
  function log(msg) {
28
- if (DEBUG) try {
28
+ try {
29
29
  appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
30
30
  `);
31
31
  } catch {
@@ -80,50 +80,53 @@ async function runModel(client, model, prompt, directory) {
80
80
  const providerID = slash >= 0 ? model.slice(0, slash) : model;
81
81
  const modelID = slash >= 0 ? model.slice(slash + 1) : "";
82
82
  const s = await client.session.create({ body: { title: "uc-harness-sub" }, query: { directory } });
83
- const id = s?.data?.info?.id;
84
- if (!id) {
85
- log(`runModel(${model}): no session id returned`);
86
- return null;
87
- }
83
+ log(`runModel(${model}): create resp = ${JSON.stringify(s?.data ?? s).slice(0, 500)}`);
84
+ const id = s?.data?.info?.id ?? s?.data?.id ?? s?.id;
85
+ if (!id) return `ERROR: session.create returned no id (response: ${JSON.stringify(s?.data ?? s).slice(0, 200)})`;
88
86
  log(`runModel(${model}): session ${id} created, prompt ${prompt.length} chars`);
89
87
  await client.session.prompt({
90
88
  path: { id },
91
89
  body: { model: { providerID, modelID }, parts: [{ type: "text", text: prompt }] }
92
90
  });
93
- let lastStatus = "";
91
+ log(`runModel(${model}): prompt sent to ${id}`);
92
+ let lastMsgCount = -1;
94
93
  let timedOut = false;
94
+ let text = "";
95
95
  for (let i = 0; i < 600; i++) {
96
96
  await sleep(1e3);
97
- const st = await client.session.status({ path: { id } });
98
- const status = st?.data?.[id]?.status ?? st?.data?.status;
99
- if (status !== lastStatus) {
100
- log(`runModel(${model}): poll ${i}s status="${status}"`);
101
- lastStatus = String(status);
102
- }
103
- if (status === "idle" || status === "completed") break;
104
- if (status === "error" || status === "failed") {
105
- log(`runModel(${model}): sub-session ended with "${status}"`);
106
- break;
97
+ try {
98
+ const msgs = await client.session.messages({ path: { id } });
99
+ const all = msgs?.data ?? msgs ?? [];
100
+ if (all.length !== lastMsgCount) {
101
+ log(`runModel(${model}): poll[${i}] msgs=${all.length}`);
102
+ lastMsgCount = all.length;
103
+ }
104
+ const lastAssistant = all.filter((m) => (m?.info?.role ?? m?.role) === "assistant").pop();
105
+ const parts = lastAssistant?.parts ?? lastAssistant?.info?.parts ?? [];
106
+ const t = parts.filter((p) => p?.type === "text").map((p) => p?.text ?? "").join("").trim();
107
+ if (t) {
108
+ text = t;
109
+ log(`runModel(${model}): got assistant text at poll[${i}] (${t.length} chars)`);
110
+ break;
111
+ }
112
+ } catch (e) {
113
+ log(`runModel(${model}): poll[${i}] messages err: ${String(e).slice(0, 120)}`);
107
114
  }
108
115
  if (i === 599) timedOut = true;
109
116
  }
110
117
  const elapsed = Math.round((Date.now() - t0) / 1e3);
111
- const msgs = await client.session.messages({ path: { id } });
112
- const all = msgs?.data ?? [];
113
- const lastAssistant = all.filter((m) => m?.info?.role === "assistant").pop();
114
- const parts = lastAssistant?.parts ?? [];
115
- const text = parts.filter((p) => p?.type === "text").map((p) => p?.text ?? "").join("");
116
118
  try {
117
119
  await client.session.remove?.({ path: { id } });
118
120
  } catch {
119
121
  }
120
122
  log(`runModel(${model}): done ${elapsed}s, ${text.length} chars${timedOut ? " [TIMEOUT]" : ""}`);
121
- if (timedOut) return `[runModel TIMEOUT after ${elapsed}s \u2014 result may be incomplete]
122
- ${text.trim()}`;
123
- return text.trim() || null;
123
+ if (timedOut) return `[runModel TIMEOUT after ${elapsed}s \u2014 no assistant text appeared]
124
+ ${text}`;
125
+ return text || `ERROR: no assistant text after ${elapsed}s (${lastMsgCount} messages seen)`;
124
126
  } catch (e) {
125
- log(`runModel err (${model}, ${Math.round((Date.now() - t0) / 1e3)}s): ${String(e)}`);
126
- return null;
127
+ const elapsed = Math.round((Date.now() - t0) / 1e3);
128
+ log(`runModel err (${model}, ${elapsed}s): ${String(e)}`);
129
+ return `ERROR: runModel exception after ${elapsed}s: ${String(e)}`;
127
130
  }
128
131
  }
129
132
  var num = (e, d) => {
@@ -380,7 +383,7 @@ async function UsageCoachPlugin(input) {
380
383
  const cfg = readHarnessCfg(ctx.directory);
381
384
  if (!cfg.generator) return 'ERROR: no generator model configured. Set "generator" in harness.config.json (see harness.config.example.json).';
382
385
  const out = await runModel(input.client, cfg.generator, args.prompt, ctx.directory);
383
- return out ?? `(generator ${cfg.generator} produced no text)`;
386
+ return out;
384
387
  }
385
388
  }),
386
389
  generate_batch: tool({
@@ -391,7 +394,7 @@ async function UsageCoachPlugin(input) {
391
394
  if (!cfg.generator) return 'ERROR: no generator model configured. Set "generator" in harness.config.json (see harness.config.example.json).';
392
395
  const results = await Promise.all(args.tasks.map(async (t) => {
393
396
  const out = await runModel(input.client, cfg.generator, t.prompt, ctx.directory);
394
- return `[task ${t.id}] ${out ?? "(no output)"}`;
397
+ return `[task ${t.id}] ${out}`;
395
398
  }));
396
399
  return results.join("\n\n");
397
400
  }
@@ -404,7 +407,8 @@ async function UsageCoachPlugin(input) {
404
407
  const model = cfg.grader ?? cfg.generator;
405
408
  if (!model) return 'FAIL\n(ERROR: no grader/generator model configured. Set "grader" and "generator" in harness.config.json.)';
406
409
  const out = await runModel(input.client, model, args.prompt, ctx.directory);
407
- if (!out) return "FAIL\n(grader produced no output \u2014 treating as FAIL to trigger a revise)";
410
+ if (out.startsWith("ERROR:")) return `FAIL
411
+ (${out})`;
408
412
  const firstLine = out.split("\n").find((l) => l.trim()) ?? "";
409
413
  const f = firstLine.trim();
410
414
  const isPass = /^pass\b/i.test(f);
package/dist/tui.js CHANGED
@@ -173,6 +173,7 @@ function initializeTui(api, disposeRoot) {
173
173
  const hf = join(STATE_DIR, sid, "harness.json");
174
174
  if (existsSync(hf)) h = JSON.parse(readFileSync(hf, "utf8"));
175
175
  }
176
+ if (!h || h.active === false) h = readHarness();
176
177
  } catch {
177
178
  h = null;
178
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.2.5",
3
+ "version": "0.3.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",