opencode-usage-coach 0.6.2 → 0.7.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/README.md +0 -1
- package/dist/index.js +155 -183
- package/dist/tui.js +186 -149
- package/package.json +2 -8
package/README.md
CHANGED
|
@@ -155,7 +155,6 @@ Place in the **work directory**. Each role runs on its model, so per-model quota
|
|
|
155
155
|
| `UC_HARNESS_AGENT` | `Usage-Coach-Harness` | comma-separated agent modes allowed to use harness tools + receive quota coaching (case-insensitive; must match the agent id, e.g. `usage-coach-harness` from `agents/usage-coach-harness.md`) |
|
|
156
156
|
| `UC_WORM_MAX_AGE_DAYS` | 180 | domain DB worm (GC): drop nodes not accessed in N days (~6 months) |
|
|
157
157
|
| `UC_WORM_MAX_NODES` | 100000 | domain DB worm (GC): cap node count, evict oldest-accessed beyond this |
|
|
158
|
-
| `UC_DOMAIN_TIMEOUT_MS` | 5000 | domain DB query timeout (ms) — a slow/hung query resolves to a safe fallback instead of blocking the plugin |
|
|
159
158
|
|
|
160
159
|
## Agent-mode scoping
|
|
161
160
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { mkdirSync, writeFileSync, appendFileSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
2
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
4
|
import { createHash } from "crypto";
|
|
5
5
|
import { homedir } from "os";
|
|
@@ -7,193 +7,123 @@ import { join as join2, resolve, dirname } from "path";
|
|
|
7
7
|
import { tool } from "@opencode-ai/plugin";
|
|
8
8
|
|
|
9
9
|
// src/domain.ts
|
|
10
|
-
import {
|
|
10
|
+
import { mkdirSync, appendFileSync, readFileSync, existsSync, writeFileSync } from "fs";
|
|
11
11
|
import { join } from "path";
|
|
12
|
-
import { existsSync, readFileSync } from "fs";
|
|
13
|
-
var QUERY_TIMEOUT_MS = (() => {
|
|
14
|
-
try {
|
|
15
|
-
const v = Number(process.env.UC_DOMAIN_TIMEOUT_MS);
|
|
16
|
-
return Number.isFinite(v) && v > 0 ? v : 5e3;
|
|
17
|
-
} catch {
|
|
18
|
-
return 5e3;
|
|
19
|
-
}
|
|
20
|
-
})();
|
|
21
12
|
var BASE_DIR = "";
|
|
22
|
-
var DB_PATH = "";
|
|
23
|
-
var db = null;
|
|
24
|
-
var conn = null;
|
|
25
|
-
var schemaReady = false;
|
|
26
|
-
var migrated = false;
|
|
27
13
|
function initDomain(stateDir) {
|
|
28
14
|
BASE_DIR = stateDir;
|
|
29
|
-
DB_PATH = join(BASE_DIR, "domain.ladybug");
|
|
30
|
-
db = null;
|
|
31
|
-
conn = null;
|
|
32
|
-
schemaReady = false;
|
|
33
|
-
migrated = false;
|
|
34
15
|
}
|
|
35
|
-
|
|
36
|
-
|
|
16
|
+
var nodesFile = () => join(BASE_DIR, "nodes.ndjson");
|
|
17
|
+
var edgesFile = () => join(BASE_DIR, "edges.ndjson");
|
|
18
|
+
function readNdjson(path) {
|
|
19
|
+
try {
|
|
20
|
+
if (!existsSync(path)) return [];
|
|
21
|
+
return readFileSync(path, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
37
25
|
}
|
|
38
|
-
function
|
|
39
|
-
return
|
|
26
|
+
function readNodes() {
|
|
27
|
+
return readNdjson(nodesFile());
|
|
40
28
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
})
|
|
46
|
-
return Promise.race([op().catch(() => fallback), timeout]).finally(() => {
|
|
47
|
-
if (timer) clearTimeout(timer);
|
|
48
|
-
});
|
|
29
|
+
function readEdges() {
|
|
30
|
+
return readNdjson(edgesFile());
|
|
31
|
+
}
|
|
32
|
+
function uid(prefix) {
|
|
33
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
49
34
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
35
|
+
function addDomainNode(node) {
|
|
36
|
+
const full = { ...node, id: uid("node"), ts: (/* @__PURE__ */ new Date()).toISOString() };
|
|
52
37
|
try {
|
|
53
|
-
|
|
38
|
+
mkdirSync(BASE_DIR, { recursive: true });
|
|
39
|
+
appendFileSync(nodesFile(), JSON.stringify(full) + "\n");
|
|
54
40
|
} catch {
|
|
55
41
|
}
|
|
56
|
-
return
|
|
57
|
-
id: r.id,
|
|
58
|
-
type: r.type,
|
|
59
|
-
name: r.name,
|
|
60
|
-
props,
|
|
61
|
-
source: r.source ?? "",
|
|
62
|
-
confidence: r.confidence ?? 0,
|
|
63
|
-
ts: r.ts,
|
|
64
|
-
lastAccessed: r.lastAccessed || void 0,
|
|
65
|
-
accessCount: r.accessCount ?? void 0
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
var NODE_COLS = "n.id AS id, n.type AS type, n.name AS name, n.props AS props, n.source AS source, n.confidence AS confidence, n.ts AS ts, n.lastAccessed AS lastAccessed, n.accessCount AS accessCount";
|
|
69
|
-
async function ready() {
|
|
70
|
-
if (!conn) {
|
|
71
|
-
db = new Database(DB_PATH);
|
|
72
|
-
conn = new Connection(db);
|
|
73
|
-
}
|
|
74
|
-
if (!schemaReady) {
|
|
75
|
-
try {
|
|
76
|
-
await conn.query("CREATE NODE TABLE DomainNode(id STRING, type STRING, name STRING, props STRING, source STRING, confidence DOUBLE, lastAccessed STRING, accessCount INT64 DEFAULT 0, ts STRING, PRIMARY KEY(id))");
|
|
77
|
-
} catch {
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
await conn.query("CREATE REL TABLE Related(FROM DomainNode TO DomainNode, rel STRING, note STRING, ts STRING)");
|
|
81
|
-
} catch {
|
|
82
|
-
}
|
|
83
|
-
schemaReady = true;
|
|
84
|
-
}
|
|
85
|
-
if (!migrated) {
|
|
86
|
-
migrated = true;
|
|
87
|
-
await migrateFromNdjson();
|
|
88
|
-
}
|
|
89
|
-
return conn;
|
|
42
|
+
return full.id;
|
|
90
43
|
}
|
|
91
|
-
|
|
92
|
-
if (!conn) return;
|
|
93
|
-
const nf = join(BASE_DIR, "nodes.ndjson");
|
|
94
|
-
const ef = join(BASE_DIR, "edges.ndjson");
|
|
95
|
-
if (!existsSync(nf)) return;
|
|
44
|
+
function writeNodes(nodes) {
|
|
96
45
|
try {
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const readNdjson = (p) => {
|
|
101
|
-
try {
|
|
102
|
-
return readFileSync(p, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
103
|
-
} catch {
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
for (const n of readNdjson(nf)) {
|
|
108
|
-
await conn.query(`CREATE (n:DomainNode {id:'${esc(n.id)}',type:'${esc(n.type)}',name:'${esc(n.name)}',props:'${esc(JSON.stringify(n.props ?? {}))}',source:'${esc(n.source ?? "")}',confidence:${Number(n.confidence ?? 0)},ts:'${esc(n.ts ?? "")}',lastAccessed:'${esc(n.lastAccessed ?? "")}',accessCount:${Number(n.accessCount ?? 0)}})`);
|
|
109
|
-
}
|
|
110
|
-
if (existsSync(ef)) {
|
|
111
|
-
for (const e of readNdjson(ef)) {
|
|
112
|
-
await conn.query(`MATCH (a:DomainNode {id:'${esc(e.from)}'}), (b:DomainNode {id:'${esc(e.to)}'}) CREATE (a)-[:Related {rel:'${esc(e.rel)}',note:'${esc(e.note ?? "")}',ts:'${esc(e.ts ?? "")}'}]->(b)`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
46
|
+
mkdirSync(BASE_DIR, { recursive: true });
|
|
47
|
+
const lines = nodes.map((n) => JSON.stringify(n));
|
|
48
|
+
writeFileSync(nodesFile(), lines.length ? lines.join("\n") + "\n" : "");
|
|
115
49
|
} catch {
|
|
116
50
|
}
|
|
117
51
|
}
|
|
118
|
-
async function _readNodes() {
|
|
119
|
-
const c = await ready();
|
|
120
|
-
const r = await c.query(`MATCH (n:DomainNode) RETURN ${NODE_COLS}`);
|
|
121
|
-
return (await r.getAll()).map(parseNode);
|
|
122
|
-
}
|
|
123
|
-
async function _addDomainNode(node) {
|
|
124
|
-
const c = await ready();
|
|
125
|
-
const id = uid("node");
|
|
126
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
127
|
-
await c.query(`CREATE (n:DomainNode {id:'${esc(id)}',type:'${esc(node.type)}',name:'${esc(node.name)}',props:'${esc(JSON.stringify(node.props ?? {}))}',source:'${esc(node.source ?? "")}',confidence:${Number(node.confidence ?? 0)},ts:'${esc(now)}',lastAccessed:'',accessCount:0})`);
|
|
128
|
-
return id;
|
|
129
|
-
}
|
|
130
52
|
function queryDomain(keywords) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
if (matched.length) await _touchNodes(new Set(matched.map((n) => n.id)));
|
|
141
|
-
let edges = [];
|
|
142
|
-
if (matched.length) {
|
|
143
|
-
const ids = matched.map((n) => `'${esc(n.id)}'`).join(",");
|
|
144
|
-
const er = await c.query(`MATCH (a:DomainNode)-[r:Related]->(b:DomainNode) WHERE a.id IN [${ids}] OR b.id IN [${ids}] RETURN a.id AS from, b.id AS to, r.rel AS rel, r.note AS note, r.ts AS ts`);
|
|
145
|
-
edges = await er.getAll();
|
|
146
|
-
}
|
|
53
|
+
const lc = keywords.map((k) => k.toLowerCase());
|
|
54
|
+
const nodes = readNodes();
|
|
55
|
+
const matched = nodes.filter((n) => {
|
|
56
|
+
const hay = (n.name + " " + JSON.stringify(n.props)).toLowerCase();
|
|
57
|
+
return lc.some((k) => k && hay.includes(k));
|
|
58
|
+
});
|
|
59
|
+
if (matched.length) touchNodes(new Set(matched.map((n) => n.id)));
|
|
60
|
+
const ids = new Set(matched.map((n) => n.id));
|
|
61
|
+
const edges = readEdges().filter((e) => ids.has(e.from) || ids.has(e.to));
|
|
147
62
|
return { nodes: matched, edges };
|
|
148
63
|
}
|
|
149
|
-
|
|
64
|
+
function touchNodes(ids) {
|
|
150
65
|
if (ids.size === 0) return;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const ageMs = maxAgeDays * 864e5;
|
|
165
|
-
const lastTs = (n) => {
|
|
166
|
-
const la = n.lastAccessed ? new Date(n.lastAccessed).getTime() : 0;
|
|
167
|
-
return la || new Date(n.ts).getTime();
|
|
168
|
-
};
|
|
169
|
-
let kept = all.filter((n) => now - lastTs(n) < ageMs);
|
|
170
|
-
if (kept.length > maxNodes) {
|
|
171
|
-
kept.sort((a, b) => lastTs(b) - lastTs(a));
|
|
172
|
-
kept = kept.slice(0, maxNodes);
|
|
66
|
+
try {
|
|
67
|
+
const nodes = readNodes();
|
|
68
|
+
let changed = false;
|
|
69
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
70
|
+
for (const n of nodes) {
|
|
71
|
+
if (ids.has(n.id)) {
|
|
72
|
+
n.lastAccessed = now;
|
|
73
|
+
n.accessCount = (n.accessCount ?? 0) + 1;
|
|
74
|
+
changed = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (changed) writeNodes(nodes);
|
|
78
|
+
} catch {
|
|
173
79
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
80
|
+
}
|
|
81
|
+
function evictStale(maxAgeDays = 30, maxNodes = 1e3) {
|
|
82
|
+
try {
|
|
83
|
+
const nodes = readNodes();
|
|
84
|
+
if (nodes.length === 0) return { removed: 0, kept: 0 };
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
const ageMs = maxAgeDays * 864e5;
|
|
87
|
+
const lastTs = (n) => new Date(n.lastAccessed ?? n.ts).getTime();
|
|
88
|
+
let kept = nodes.filter((n) => now - lastTs(n) < ageMs);
|
|
89
|
+
if (kept.length > maxNodes) {
|
|
90
|
+
kept.sort((a, b) => lastTs(b) - lastTs(a));
|
|
91
|
+
kept = kept.slice(0, maxNodes);
|
|
92
|
+
}
|
|
93
|
+
const removed = nodes.length - kept.length;
|
|
94
|
+
if (removed > 0) writeNodes(kept);
|
|
95
|
+
return { removed, kept: kept.length };
|
|
96
|
+
} catch {
|
|
97
|
+
return { removed: 0, kept: 0 };
|
|
178
98
|
}
|
|
179
|
-
return { removed: toRemove.length, kept: kept.length };
|
|
180
99
|
}
|
|
181
100
|
function saveInvestigationResult(keywords, result, source) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
101
|
+
try {
|
|
102
|
+
return addDomainNode({
|
|
103
|
+
type: "fact",
|
|
104
|
+
name: keywords.join(" "),
|
|
105
|
+
props: { result },
|
|
106
|
+
source: source || "investigation",
|
|
107
|
+
confidence: 0.7
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
192
112
|
}
|
|
193
113
|
|
|
194
114
|
// src/index.ts
|
|
195
115
|
var PLUGIN_NAME = "opencode-usage-coach";
|
|
196
116
|
var TTL_MS = Number(process.env.UC_TTL_MS ?? 6e4);
|
|
117
|
+
var PIPE_LOG = join2(homedir(), ".cache", "opencode-usage-coach", "pipeline.log");
|
|
118
|
+
function pipeLog(msg) {
|
|
119
|
+
try {
|
|
120
|
+
mkdirSync2(dirname(PIPE_LOG), { recursive: true });
|
|
121
|
+
appendFileSync2(PIPE_LOG, `[SERVER] ${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
122
|
+
`);
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
pipeLog(`MODULE LOADED | node=${process.version} | pid=${process.pid}`);
|
|
197
127
|
var STATE_DIR = join2(homedir(), ".cache", "opencode-usage-coach");
|
|
198
128
|
var STATE_FILE = join2(STATE_DIR, "state.json");
|
|
199
129
|
var LOG_FILE = join2(STATE_DIR, "coach.log");
|
|
@@ -210,15 +140,15 @@ function setStateDir(dir) {
|
|
|
210
140
|
var NOOP_HOOKS = {};
|
|
211
141
|
function log(msg) {
|
|
212
142
|
try {
|
|
213
|
-
|
|
143
|
+
appendFileSync2(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
214
144
|
`);
|
|
215
145
|
} catch {
|
|
216
146
|
}
|
|
217
147
|
}
|
|
218
148
|
function writeState(c) {
|
|
219
149
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
150
|
+
mkdirSync2(STATE_DIR, { recursive: true });
|
|
151
|
+
writeFileSync2(STATE_FILE, JSON.stringify({ ...c, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
222
152
|
} catch {
|
|
223
153
|
}
|
|
224
154
|
}
|
|
@@ -269,9 +199,9 @@ function readHarness(sessionID) {
|
|
|
269
199
|
function writeHarness(sessionID, h) {
|
|
270
200
|
try {
|
|
271
201
|
const f = harnessFile(sessionID);
|
|
272
|
-
|
|
202
|
+
mkdirSync2(dirname(f), { recursive: true });
|
|
273
203
|
h.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
274
|
-
|
|
204
|
+
writeFileSync2(f, JSON.stringify(h, null, 2));
|
|
275
205
|
} catch {
|
|
276
206
|
}
|
|
277
207
|
}
|
|
@@ -451,14 +381,44 @@ function coach(q, lighter) {
|
|
|
451
381
|
return { decision: "GO", advice: `Comfortable \u2014 weekly ${wk}% \xB7 5h ${h5}% \xB7 monthly ${mo}%. proceed. 5h window ${h5R}.`, weekly: wk, monthly: mo, fiveHour: h5 };
|
|
452
382
|
}
|
|
453
383
|
var agentCache = /* @__PURE__ */ new Map();
|
|
384
|
+
var currentModel = "";
|
|
385
|
+
var currentProvider = "";
|
|
386
|
+
var currentAgent = "";
|
|
387
|
+
var modelChanged = false;
|
|
388
|
+
function isFreeModel(model, provider) {
|
|
389
|
+
if (!model && !provider) return false;
|
|
390
|
+
if (provider === "opencode") return true;
|
|
391
|
+
if (model.toLowerCase().includes("free")) return true;
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
function providerToCodexbar(provider) {
|
|
395
|
+
if (!provider) return "";
|
|
396
|
+
return provider.split("-")[0];
|
|
397
|
+
}
|
|
454
398
|
async function resolveAgent(client, sessionID) {
|
|
455
399
|
if (!sessionID) return "";
|
|
456
400
|
const hit = agentCache.get(sessionID);
|
|
457
|
-
if (hit && Date.now() - hit.ts < 6e4)
|
|
401
|
+
if (hit && Date.now() - hit.ts < 6e4) {
|
|
402
|
+
currentModel = hit.model;
|
|
403
|
+
currentProvider = hit.provider;
|
|
404
|
+
return hit.agent;
|
|
405
|
+
}
|
|
458
406
|
try {
|
|
459
407
|
const s = await client.session.get({ path: { id: sessionID } });
|
|
460
|
-
|
|
461
|
-
|
|
408
|
+
log(`resolveAgent raw session: ${JSON.stringify(s?.data?.info ?? s?.data ?? s?.info ?? s).slice(0, 500)}`);
|
|
409
|
+
const info = s?.data?.info ?? s?.data ?? s?.info ?? s;
|
|
410
|
+
const agent = String(info?.agent ?? "");
|
|
411
|
+
const rawModel = info?.model;
|
|
412
|
+
const model = typeof rawModel === "string" ? rawModel : rawModel?.id ?? rawModel?.modelID ?? rawModel?.name ?? "";
|
|
413
|
+
const rawProvider = info?.providerID ?? info?.provider ?? (typeof rawModel === "object" ? rawModel?.providerID ?? rawModel?.provider : "");
|
|
414
|
+
const provider = typeof rawProvider === "string" ? rawProvider : rawProvider?.id ?? "";
|
|
415
|
+
if (currentModel && model && currentModel !== model) {
|
|
416
|
+
log(`MODEL CHANGED: ${currentModel} \u2192 ${model} (provider: ${currentProvider} \u2192 ${provider})`);
|
|
417
|
+
modelChanged = true;
|
|
418
|
+
}
|
|
419
|
+
agentCache.set(sessionID, { agent, model, provider, ts: Date.now() });
|
|
420
|
+
currentModel = model;
|
|
421
|
+
currentProvider = provider;
|
|
462
422
|
return agent;
|
|
463
423
|
} catch (e) {
|
|
464
424
|
log(`resolveAgent err: ${String(e)}`);
|
|
@@ -483,9 +443,19 @@ async function UsageCoachPlugin(input) {
|
|
|
483
443
|
const refreshBackground = () => {
|
|
484
444
|
try {
|
|
485
445
|
if (refreshing) return;
|
|
486
|
-
if (last && Date.now() - lastFetchedAt < TTL_MS) return;
|
|
446
|
+
if (last && !modelChanged && Date.now() - lastFetchedAt < TTL_MS) return;
|
|
487
447
|
refreshing = true;
|
|
488
|
-
|
|
448
|
+
modelChanged = false;
|
|
449
|
+
if (isFreeModel(currentModel, currentProvider)) {
|
|
450
|
+
last = { decision: "GO", advice: `${currentModel || currentProvider || "free model"} \u2014 no quota limit.`, weekly: -1, monthly: -1, fiveHour: -1, model: currentModel, provider: currentProvider, isFree: true };
|
|
451
|
+
lastFetchedAt = Date.now();
|
|
452
|
+
writeState({ ...last, providers: [], model: currentModel, provider: currentProvider, isFree: true, agent: currentAgent, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
453
|
+
log(`FREE | model=${currentModel} provider=${currentProvider}`);
|
|
454
|
+
refreshing = false;
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const activeProvider = providerToCodexbar(currentProvider) || PROVIDER;
|
|
458
|
+
fetchQuota(activeProvider).then(async (q) => {
|
|
489
459
|
try {
|
|
490
460
|
last = coach(q, LIGHTER);
|
|
491
461
|
lastFetchedAt = Date.now();
|
|
@@ -498,7 +468,7 @@ async function UsageCoachPlugin(input) {
|
|
|
498
468
|
const p0 = providers[0];
|
|
499
469
|
last = { ...last, weekly: p0.weekly, fiveHour: p0.fiveHour, monthly: p0.weekly >= 0 ? 0 : -1, advice: p0.advice, decision: p0.weekly >= STOP_WK ? "STOP" : p0.weekly >= THR_WK ? "THROTTLE" : "GO" };
|
|
500
470
|
}
|
|
501
|
-
writeState({ ...last, providers, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
471
|
+
writeState({ ...last, providers, model: currentModel, provider: currentProvider, isFree: false, agent: currentAgent, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
502
472
|
log(`${last.decision} | weekly=${last.weekly}% 5h=${last.fiveHour}% | providers=${providers.length}`);
|
|
503
473
|
} catch (e) {
|
|
504
474
|
log(`refresh-in-then err: ${String(e)}`);
|
|
@@ -526,7 +496,7 @@ async function UsageCoachPlugin(input) {
|
|
|
526
496
|
if (event.type === "session.created" || event.type === "session.idle") refreshBackground();
|
|
527
497
|
if (event.type === "session.idle") {
|
|
528
498
|
try {
|
|
529
|
-
const r =
|
|
499
|
+
const r = evictStale(WORM_MAX_AGE_DAYS, WORM_MAX_NODES);
|
|
530
500
|
if (r.removed) log(`evictStale: removed ${r.removed}, kept ${r.kept} (maxAge=${WORM_MAX_AGE_DAYS}d, maxNodes=${WORM_MAX_NODES})`);
|
|
531
501
|
} catch (e) {
|
|
532
502
|
log(`evictStale err: ${String(e)}`);
|
|
@@ -536,13 +506,14 @@ async function UsageCoachPlugin(input) {
|
|
|
536
506
|
log(`event err: ${String(e)}`);
|
|
537
507
|
}
|
|
538
508
|
},
|
|
539
|
-
// ACT(1)
|
|
540
|
-
//
|
|
541
|
-
// are NEVER gated, in ANY mode — they don't consume model quota.
|
|
509
|
+
// ACT(1) — model/agent detection on EVERY tool call (cached 60s, negligible overhead).
|
|
510
|
+
// Then hard-gate harness tools by agent mode + quota STOP.
|
|
542
511
|
"tool.execute.before": async (_input) => {
|
|
512
|
+
const agent = await resolveAgent(input.client, _input.sessionID);
|
|
513
|
+
currentAgent = agent;
|
|
514
|
+
refreshBackground();
|
|
543
515
|
const harnessTools = ["generate", "generate_batch", "grade", "investigate", "verify_diagnosis", "generalize", "harness_start", "task_update", "harness_done", "record_failure"];
|
|
544
516
|
if (!harnessTools.includes(_input.tool)) return;
|
|
545
|
-
const agent = await resolveAgent(input.client, _input.sessionID);
|
|
546
517
|
if (!isHarnessAgent(agent)) {
|
|
547
518
|
throw new Error(`[${PLUGIN_NAME}] '${_input.tool}' is restricted to agent mode ${JSON.stringify(HARNESS_AGENTS)} (current: ${JSON.stringify(agent || "unknown")}). Switch to that agent mode to use it.`);
|
|
548
519
|
}
|
|
@@ -562,6 +533,7 @@ async function UsageCoachPlugin(input) {
|
|
|
562
533
|
try {
|
|
563
534
|
if (_input.sessionID) {
|
|
564
535
|
const agent = await resolveAgent(input.client, _input.sessionID);
|
|
536
|
+
refreshBackground();
|
|
565
537
|
if (!isHarnessAgent(agent)) return;
|
|
566
538
|
}
|
|
567
539
|
const c = current();
|
|
@@ -651,8 +623,8 @@ Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns.
|
|
|
651
623
|
async execute(args, _ctx) {
|
|
652
624
|
const rec = { ts: (/* @__PURE__ */ new Date()).toISOString(), task: args.task, prompt: args.prompt, gradeResult: args.gradeResult, model: args.model, revisions: args.revisions };
|
|
653
625
|
try {
|
|
654
|
-
|
|
655
|
-
|
|
626
|
+
mkdirSync2(STATE_DIR, { recursive: true });
|
|
627
|
+
appendFileSync2(failuresFile(), JSON.stringify(rec) + "\n");
|
|
656
628
|
} catch (e) {
|
|
657
629
|
log(`record_failure err: ${String(e)}`);
|
|
658
630
|
}
|
|
@@ -676,7 +648,7 @@ Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns.
|
|
|
676
648
|
try {
|
|
677
649
|
keywords = extractKeywords(`${args.task} ${args.gradeResult}`);
|
|
678
650
|
if (keywords.length) {
|
|
679
|
-
const { nodes, edges } =
|
|
651
|
+
const { nodes, edges } = queryDomain(keywords);
|
|
680
652
|
if (nodes && nodes.length || edges && edges.length) {
|
|
681
653
|
domainEmpty = false;
|
|
682
654
|
domainPrefix = `Known facts from domain DB: ${JSON.stringify({ nodes, edges })}. Use these if relevant.
|
|
@@ -700,7 +672,7 @@ evidence: <file/line or specific quote>`;
|
|
|
700
672
|
const out = await runModel(input.client, cfg.generator, domainPrefix + rcaPrompt, ctx.directory);
|
|
701
673
|
if (domainEmpty && keywords.length) {
|
|
702
674
|
try {
|
|
703
|
-
|
|
675
|
+
saveInvestigationResult(keywords, out, "investigate");
|
|
704
676
|
} catch (e) {
|
|
705
677
|
log(`investigate save err: ${String(e)}`);
|
|
706
678
|
}
|
|
@@ -756,8 +728,8 @@ Keep it concrete and actionable.`;
|
|
|
756
728
|
const rule = out;
|
|
757
729
|
try {
|
|
758
730
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
759
|
-
|
|
760
|
-
|
|
731
|
+
mkdirSync2(STATE_DIR, { recursive: true });
|
|
732
|
+
appendFileSync2(rulesFile(), `## Rule (${date})
|
|
761
733
|
${rule}
|
|
762
734
|
Origin: ${args.task}
|
|
763
735
|
|
|
@@ -796,7 +768,7 @@ ${rules}
|
|
|
796
768
|
try {
|
|
797
769
|
keywords = extractKeywords(args.prompt);
|
|
798
770
|
if (keywords.length) {
|
|
799
|
-
const { nodes, edges } =
|
|
771
|
+
const { nodes, edges } = queryDomain(keywords);
|
|
800
772
|
if (nodes && nodes.length || edges && edges.length) {
|
|
801
773
|
domainEmpty = false;
|
|
802
774
|
prefix = `Known facts from domain DB: ${JSON.stringify({ nodes, edges })}. Use these if relevant.
|
|
@@ -812,7 +784,7 @@ ${rules}
|
|
|
812
784
|
const out = await runModel(input.client, model, prefix + args.prompt, ctx.directory);
|
|
813
785
|
if (domainEmpty && keywords.length) {
|
|
814
786
|
try {
|
|
815
|
-
|
|
787
|
+
saveInvestigationResult(keywords, out, "generate");
|
|
816
788
|
} catch (e) {
|
|
817
789
|
log(`generate save err: ${String(e)}`);
|
|
818
790
|
}
|
package/dist/tui.js
CHANGED
|
@@ -202,152 +202,189 @@ function initializeTui(api, disposeRoot) {
|
|
|
202
202
|
h = null;
|
|
203
203
|
}
|
|
204
204
|
const nodes = [];
|
|
205
|
+
const HARNESS_AGENTS = ["usage-coach-harness"];
|
|
206
|
+
if (s && s.agent && !HARNESS_AGENTS.includes(s.agent)) {
|
|
207
|
+
return _$createElement("box");
|
|
208
|
+
}
|
|
205
209
|
if (s) {
|
|
206
210
|
const dKey = s.decision === "GO" ? "success" : s.decision === "THROTTLE" ? "warning" : "error";
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
var
|
|
219
|
-
_$
|
|
220
|
-
_$
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
211
|
+
const modelShort = s.model ? s.model.split("/").pop() ?? s.model : "";
|
|
212
|
+
if (s.isFree) {
|
|
213
|
+
nodes.push((() => {
|
|
214
|
+
var _el$5 = _$createElement("box"), _el$6 = _$createElement("text"), _el$8 = _$createElement("text"), _el$9 = _$createTextNode(` `);
|
|
215
|
+
_$insertNode(_el$5, _el$6);
|
|
216
|
+
_$insertNode(_el$5, _el$8);
|
|
217
|
+
_$setProp(_el$5, "flexDirection", "row");
|
|
218
|
+
_$insertNode(_el$6, _$createTextNode(`usage-coach [free]`));
|
|
219
|
+
_$insertNode(_el$8, _el$9);
|
|
220
|
+
_$insert(_el$8, modelShort, null);
|
|
221
|
+
_$effect((_p$) => {
|
|
222
|
+
var _v$ = st(dKey), _v$2 = st("textMuted");
|
|
223
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$6, "style", _v$, _p$.e));
|
|
224
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$8, "style", _v$2, _p$.t));
|
|
225
|
+
return _p$;
|
|
226
|
+
}, {
|
|
227
|
+
e: void 0,
|
|
228
|
+
t: void 0
|
|
229
|
+
});
|
|
230
|
+
return _el$5;
|
|
231
|
+
})());
|
|
232
|
+
} else {
|
|
233
|
+
if (modelShort) {
|
|
224
234
|
nodes.push((() => {
|
|
225
|
-
var _el$
|
|
226
|
-
_$insertNode(_el$
|
|
227
|
-
_$insertNode(_el$
|
|
228
|
-
_$
|
|
229
|
-
_$insertNode(_el$
|
|
230
|
-
_$
|
|
231
|
-
_$
|
|
232
|
-
_$insert(_el$10, () => barFill(p.fiveHour));
|
|
233
|
-
_$insert(_el$11, () => barEmpty(p.fiveHour));
|
|
235
|
+
var _el$0 = _$createElement("box"), _el$1 = _$createElement("text"), _el$10 = _$createTextNode(`usage-coach [`), _el$11 = _$createTextNode(`]`), _el$12 = _$createElement("text"), _el$13 = _$createTextNode(` `);
|
|
236
|
+
_$insertNode(_el$0, _el$1);
|
|
237
|
+
_$insertNode(_el$0, _el$12);
|
|
238
|
+
_$setProp(_el$0, "flexDirection", "row");
|
|
239
|
+
_$insertNode(_el$1, _el$10);
|
|
240
|
+
_$insertNode(_el$1, _el$11);
|
|
241
|
+
_$insert(_el$1, () => TAG[s.decision], _el$11);
|
|
234
242
|
_$insertNode(_el$12, _el$13);
|
|
235
|
-
_$
|
|
236
|
-
_$insert(_el$12, () => p.fiveHour, _el$14);
|
|
237
|
-
_$insert(_el$12, () => p.fiveHourReset, null);
|
|
243
|
+
_$insert(_el$12, modelShort, null);
|
|
238
244
|
_$effect((_p$) => {
|
|
239
|
-
var _v$ = st(
|
|
240
|
-
_v$ !== _p$.e && (_p$.e = _$setProp(_el$
|
|
241
|
-
_v$
|
|
245
|
+
var _v$3 = st(dKey), _v$4 = st("textMuted");
|
|
246
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$1, "style", _v$3, _p$.e));
|
|
247
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$12, "style", _v$4, _p$.t));
|
|
242
248
|
return _p$;
|
|
243
249
|
}, {
|
|
244
250
|
e: void 0,
|
|
245
251
|
t: void 0
|
|
246
252
|
});
|
|
247
|
-
return _el$
|
|
253
|
+
return _el$0;
|
|
254
|
+
})());
|
|
255
|
+
} else {
|
|
256
|
+
nodes.push((() => {
|
|
257
|
+
var _el$14 = _$createElement("text"), _el$15 = _$createTextNode(`usage-coach [`), _el$16 = _$createTextNode(`]`);
|
|
258
|
+
_$insertNode(_el$14, _el$15);
|
|
259
|
+
_$insertNode(_el$14, _el$16);
|
|
260
|
+
_$insert(_el$14, () => TAG[s.decision], _el$16);
|
|
261
|
+
_$effect((_$p) => _$setProp(_el$14, "style", st(dKey), _$p));
|
|
262
|
+
return _el$14;
|
|
248
263
|
})());
|
|
264
|
+
}
|
|
265
|
+
if (s.providers && s.providers.length > 0) {
|
|
266
|
+
for (const p of s.providers) {
|
|
267
|
+
nodes.push((() => {
|
|
268
|
+
var _el$17 = _$createElement("box"), _el$18 = _$createElement("text"), _el$20 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createElement("text"), _el$23 = _$createTextNode(` `), _el$24 = _$createTextNode(`% `);
|
|
269
|
+
_$insertNode(_el$17, _el$18);
|
|
270
|
+
_$insertNode(_el$17, _el$20);
|
|
271
|
+
_$insertNode(_el$17, _el$21);
|
|
272
|
+
_$insertNode(_el$17, _el$22);
|
|
273
|
+
_$setProp(_el$17, "flexDirection", "row");
|
|
274
|
+
_$insertNode(_el$18, _$createTextNode(` 5h `));
|
|
275
|
+
_$insert(_el$20, () => barFill(p.fiveHour));
|
|
276
|
+
_$insert(_el$21, () => barEmpty(p.fiveHour));
|
|
277
|
+
_$insertNode(_el$22, _el$23);
|
|
278
|
+
_$insertNode(_el$22, _el$24);
|
|
279
|
+
_$insert(_el$22, () => p.fiveHour, _el$24);
|
|
280
|
+
_$insert(_el$22, () => p.fiveHourReset, null);
|
|
281
|
+
_$effect((_p$) => {
|
|
282
|
+
var _v$5 = st("text"), _v$6 = st("text");
|
|
283
|
+
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$20, "style", _v$5, _p$.e));
|
|
284
|
+
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$21, "style", _v$6, _p$.t));
|
|
285
|
+
return _p$;
|
|
286
|
+
}, {
|
|
287
|
+
e: void 0,
|
|
288
|
+
t: void 0
|
|
289
|
+
});
|
|
290
|
+
return _el$17;
|
|
291
|
+
})());
|
|
292
|
+
nodes.push((() => {
|
|
293
|
+
var _el$25 = _$createElement("box"), _el$26 = _$createElement("text"), _el$28 = _$createElement("text"), _el$29 = _$createElement("text"), _el$30 = _$createElement("text"), _el$31 = _$createTextNode(` `), _el$32 = _$createTextNode(`% `);
|
|
294
|
+
_$insertNode(_el$25, _el$26);
|
|
295
|
+
_$insertNode(_el$25, _el$28);
|
|
296
|
+
_$insertNode(_el$25, _el$29);
|
|
297
|
+
_$insertNode(_el$25, _el$30);
|
|
298
|
+
_$setProp(_el$25, "flexDirection", "row");
|
|
299
|
+
_$insertNode(_el$26, _$createTextNode(` 1w `));
|
|
300
|
+
_$insert(_el$28, () => barFill(p.weekly));
|
|
301
|
+
_$insert(_el$29, () => barEmpty(p.weekly));
|
|
302
|
+
_$insertNode(_el$30, _el$31);
|
|
303
|
+
_$insertNode(_el$30, _el$32);
|
|
304
|
+
_$insert(_el$30, () => p.weekly, _el$32);
|
|
305
|
+
_$insert(_el$30, () => p.weeklyReset, null);
|
|
306
|
+
_$effect((_p$) => {
|
|
307
|
+
var _v$7 = st("text"), _v$8 = st("text");
|
|
308
|
+
_v$7 !== _p$.e && (_p$.e = _$setProp(_el$28, "style", _v$7, _p$.e));
|
|
309
|
+
_v$8 !== _p$.t && (_p$.t = _$setProp(_el$29, "style", _v$8, _p$.t));
|
|
310
|
+
return _p$;
|
|
311
|
+
}, {
|
|
312
|
+
e: void 0,
|
|
313
|
+
t: void 0
|
|
314
|
+
});
|
|
315
|
+
return _el$25;
|
|
316
|
+
})());
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
249
319
|
nodes.push((() => {
|
|
250
|
-
var _el$
|
|
251
|
-
_$insertNode(_el$
|
|
252
|
-
_$insertNode(_el$
|
|
253
|
-
_$insertNode(_el$
|
|
254
|
-
_$insertNode(_el$
|
|
255
|
-
_$setProp(_el$
|
|
256
|
-
_$insertNode(_el$
|
|
257
|
-
_$insert(_el$
|
|
258
|
-
_$insert(_el$
|
|
259
|
-
_$insertNode(_el$
|
|
260
|
-
_$insertNode(_el$20, _el$22);
|
|
261
|
-
_$insert(_el$20, () => p.weekly, _el$22);
|
|
262
|
-
_$insert(_el$20, () => p.weeklyReset, null);
|
|
320
|
+
var _el$33 = _$createElement("box"), _el$34 = _$createElement("text"), _el$36 = _$createElement("text"), _el$37 = _$createElement("text"), _el$38 = _$createElement("text");
|
|
321
|
+
_$insertNode(_el$33, _el$34);
|
|
322
|
+
_$insertNode(_el$33, _el$36);
|
|
323
|
+
_$insertNode(_el$33, _el$37);
|
|
324
|
+
_$insertNode(_el$33, _el$38);
|
|
325
|
+
_$setProp(_el$33, "flexDirection", "row");
|
|
326
|
+
_$insertNode(_el$34, _$createTextNode(` 5h `));
|
|
327
|
+
_$insert(_el$36, () => barFill(s.fiveHour));
|
|
328
|
+
_$insert(_el$37, () => barEmpty(s.fiveHour));
|
|
329
|
+
_$insertNode(_el$38, _$createTextNode(` 0%`));
|
|
263
330
|
_$effect((_p$) => {
|
|
264
|
-
var _v$
|
|
265
|
-
_v$
|
|
266
|
-
_v$
|
|
331
|
+
var _v$9 = st("text"), _v$0 = st("text");
|
|
332
|
+
_v$9 !== _p$.e && (_p$.e = _$setProp(_el$36, "style", _v$9, _p$.e));
|
|
333
|
+
_v$0 !== _p$.t && (_p$.t = _$setProp(_el$37, "style", _v$0, _p$.t));
|
|
267
334
|
return _p$;
|
|
268
335
|
}, {
|
|
269
336
|
e: void 0,
|
|
270
337
|
t: void 0
|
|
271
338
|
});
|
|
272
|
-
return _el$
|
|
339
|
+
return _el$33;
|
|
273
340
|
})());
|
|
274
341
|
nodes.push((() => {
|
|
275
|
-
var _el$
|
|
276
|
-
_$insertNode(_el$
|
|
277
|
-
_$
|
|
278
|
-
_$
|
|
279
|
-
|
|
342
|
+
var _el$40 = _$createElement("box"), _el$41 = _$createElement("text"), _el$43 = _$createElement("text"), _el$44 = _$createElement("text"), _el$45 = _$createElement("text");
|
|
343
|
+
_$insertNode(_el$40, _el$41);
|
|
344
|
+
_$insertNode(_el$40, _el$43);
|
|
345
|
+
_$insertNode(_el$40, _el$44);
|
|
346
|
+
_$insertNode(_el$40, _el$45);
|
|
347
|
+
_$setProp(_el$40, "flexDirection", "row");
|
|
348
|
+
_$insertNode(_el$41, _$createTextNode(` 1w `));
|
|
349
|
+
_$insert(_el$43, () => barFill(s.weekly));
|
|
350
|
+
_$insert(_el$44, () => barEmpty(s.weekly));
|
|
351
|
+
_$insertNode(_el$45, _$createTextNode(` 0%`));
|
|
352
|
+
_$effect((_p$) => {
|
|
353
|
+
var _v$1 = st("text"), _v$10 = st("text");
|
|
354
|
+
_v$1 !== _p$.e && (_p$.e = _$setProp(_el$43, "style", _v$1, _p$.e));
|
|
355
|
+
_v$10 !== _p$.t && (_p$.t = _$setProp(_el$44, "style", _v$10, _p$.t));
|
|
356
|
+
return _p$;
|
|
357
|
+
}, {
|
|
358
|
+
e: void 0,
|
|
359
|
+
t: void 0
|
|
360
|
+
});
|
|
361
|
+
return _el$40;
|
|
280
362
|
})());
|
|
281
363
|
}
|
|
282
|
-
} else {
|
|
283
|
-
nodes.push((() => {
|
|
284
|
-
var _el$27 = _$createElement("box"), _el$28 = _$createElement("text"), _el$30 = _$createElement("text"), _el$31 = _$createElement("text"), _el$32 = _$createElement("text");
|
|
285
|
-
_$insertNode(_el$27, _el$28);
|
|
286
|
-
_$insertNode(_el$27, _el$30);
|
|
287
|
-
_$insertNode(_el$27, _el$31);
|
|
288
|
-
_$insertNode(_el$27, _el$32);
|
|
289
|
-
_$setProp(_el$27, "flexDirection", "row");
|
|
290
|
-
_$insertNode(_el$28, _$createTextNode(` 5h `));
|
|
291
|
-
_$insert(_el$30, () => barFill(s.fiveHour));
|
|
292
|
-
_$insert(_el$31, () => barEmpty(s.fiveHour));
|
|
293
|
-
_$insertNode(_el$32, _$createTextNode(` 0%`));
|
|
294
|
-
_$effect((_p$) => {
|
|
295
|
-
var _v$5 = st("text"), _v$6 = st("text");
|
|
296
|
-
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$30, "style", _v$5, _p$.e));
|
|
297
|
-
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$31, "style", _v$6, _p$.t));
|
|
298
|
-
return _p$;
|
|
299
|
-
}, {
|
|
300
|
-
e: void 0,
|
|
301
|
-
t: void 0
|
|
302
|
-
});
|
|
303
|
-
return _el$27;
|
|
304
|
-
})());
|
|
305
|
-
nodes.push((() => {
|
|
306
|
-
var _el$34 = _$createElement("box"), _el$35 = _$createElement("text"), _el$37 = _$createElement("text"), _el$38 = _$createElement("text"), _el$39 = _$createElement("text");
|
|
307
|
-
_$insertNode(_el$34, _el$35);
|
|
308
|
-
_$insertNode(_el$34, _el$37);
|
|
309
|
-
_$insertNode(_el$34, _el$38);
|
|
310
|
-
_$insertNode(_el$34, _el$39);
|
|
311
|
-
_$setProp(_el$34, "flexDirection", "row");
|
|
312
|
-
_$insertNode(_el$35, _$createTextNode(` 1w `));
|
|
313
|
-
_$insert(_el$37, () => barFill(s.weekly));
|
|
314
|
-
_$insert(_el$38, () => barEmpty(s.weekly));
|
|
315
|
-
_$insertNode(_el$39, _$createTextNode(` 0%`));
|
|
316
|
-
_$effect((_p$) => {
|
|
317
|
-
var _v$7 = st("text"), _v$8 = st("text");
|
|
318
|
-
_v$7 !== _p$.e && (_p$.e = _$setProp(_el$37, "style", _v$7, _p$.e));
|
|
319
|
-
_v$8 !== _p$.t && (_p$.t = _$setProp(_el$38, "style", _v$8, _p$.t));
|
|
320
|
-
return _p$;
|
|
321
|
-
}, {
|
|
322
|
-
e: void 0,
|
|
323
|
-
t: void 0
|
|
324
|
-
});
|
|
325
|
-
return _el$34;
|
|
326
|
-
})());
|
|
327
364
|
}
|
|
328
365
|
} else {
|
|
329
366
|
nodes.push((() => {
|
|
330
|
-
var _el$
|
|
331
|
-
_$insertNode(_el$
|
|
332
|
-
return _el$
|
|
367
|
+
var _el$47 = _$createElement("text");
|
|
368
|
+
_$insertNode(_el$47, _$createTextNode(`usage-coach: ...`));
|
|
369
|
+
return _el$47;
|
|
333
370
|
})());
|
|
334
371
|
}
|
|
335
372
|
if (h && h.active !== false && h.tasks.length > 0) {
|
|
336
373
|
nodes.push((() => {
|
|
337
|
-
var _el$
|
|
338
|
-
_$insertNode(_el$
|
|
339
|
-
return _el$
|
|
374
|
+
var _el$49 = _$createElement("text");
|
|
375
|
+
_$insertNode(_el$49, _$createTextNode(` `));
|
|
376
|
+
return _el$49;
|
|
340
377
|
})());
|
|
341
378
|
nodes.push((() => {
|
|
342
|
-
var _el$
|
|
343
|
-
_$insertNode(_el$
|
|
344
|
-
_$insertNode(_el$
|
|
345
|
-
_$insertNode(_el$
|
|
346
|
-
_$insert(_el$
|
|
347
|
-
_$insert(_el$
|
|
348
|
-
_$insert(_el$
|
|
349
|
-
_$effect((_$p) => _$setProp(_el$
|
|
350
|
-
return _el$
|
|
379
|
+
var _el$51 = _$createElement("text"), _el$52 = _$createTextNode(`harness: `), _el$53 = _$createTextNode(` `), _el$54 = _$createTextNode(`/`);
|
|
380
|
+
_$insertNode(_el$51, _el$52);
|
|
381
|
+
_$insertNode(_el$51, _el$53);
|
|
382
|
+
_$insertNode(_el$51, _el$54);
|
|
383
|
+
_$insert(_el$51, () => h.name, _el$53);
|
|
384
|
+
_$insert(_el$51, () => h.current, _el$54);
|
|
385
|
+
_$insert(_el$51, () => h.total, null);
|
|
386
|
+
_$effect((_$p) => _$setProp(_el$51, "style", st("textMuted"), _$p));
|
|
387
|
+
return _el$51;
|
|
351
388
|
})());
|
|
352
389
|
for (const t of h.tasks) {
|
|
353
390
|
const sKey = statusKey[t.status] ?? "text";
|
|
@@ -357,18 +394,18 @@ function initializeTui(api, disposeRoot) {
|
|
|
357
394
|
const elapsed = t.startedAt ? Math.max(0, Math.round((Date.now() - new Date(t.startedAt).getTime()) / 1e3)) : 0;
|
|
358
395
|
const elapsedStr = t.status === "completed" || t.status === "failed" ? "" : elapsed > 0 ? ` ${elapsed}s` : "";
|
|
359
396
|
nodes.push((() => {
|
|
360
|
-
var _el$
|
|
361
|
-
_$insertNode(_el$
|
|
362
|
-
_$insertNode(_el$
|
|
363
|
-
_$insertNode(_el$
|
|
364
|
-
_$insert(_el$
|
|
365
|
-
_$insert(_el$
|
|
366
|
-
_$insert(_el$
|
|
367
|
-
_$insert(_el$
|
|
368
|
-
_$insert(_el$
|
|
369
|
-
_$insert(_el$
|
|
370
|
-
_$effect((_$p) => _$setProp(_el$
|
|
371
|
-
return _el$
|
|
397
|
+
var _el$55 = _$createElement("text"), _el$56 = _$createTextNode(` \u25CF `), _el$57 = _$createTextNode(` `), _el$58 = _$createTextNode(` `);
|
|
398
|
+
_$insertNode(_el$55, _el$56);
|
|
399
|
+
_$insertNode(_el$55, _el$57);
|
|
400
|
+
_$insertNode(_el$55, _el$58);
|
|
401
|
+
_$insert(_el$55, () => t.id, _el$57);
|
|
402
|
+
_$insert(_el$55, mdl, _el$57);
|
|
403
|
+
_$insert(_el$55, lbl, _el$58);
|
|
404
|
+
_$insert(_el$55, rev, _el$58);
|
|
405
|
+
_$insert(_el$55, elapsedStr, _el$58);
|
|
406
|
+
_$insert(_el$55, () => t.title, null);
|
|
407
|
+
_$effect((_$p) => _$setProp(_el$55, "style", st(sKey), _$p));
|
|
408
|
+
return _el$55;
|
|
372
409
|
})());
|
|
373
410
|
const pv = t.model ? (t.model.split("/")[0] ?? "").split("-")[0] : "";
|
|
374
411
|
const provCoach = pv ? s?.providers?.find((p) => p.id === pv || pv && p.id.startsWith(pv) || pv && pv.startsWith(p.id)) : s?.providers?.[0];
|
|
@@ -376,35 +413,35 @@ function initializeTui(api, disposeRoot) {
|
|
|
376
413
|
const pct = rawPct < 0 ? 0 : rawPct;
|
|
377
414
|
const pctLabel = rawPct < 0 ? "n/a" : `${rawPct}%`;
|
|
378
415
|
nodes.push((() => {
|
|
379
|
-
var _el$
|
|
380
|
-
_$insertNode(_el$
|
|
381
|
-
_$insertNode(_el$
|
|
382
|
-
_$insertNode(_el$
|
|
383
|
-
_$insertNode(_el$
|
|
384
|
-
_$setProp(_el$
|
|
385
|
-
_$insertNode(_el$
|
|
386
|
-
_$insert(_el$
|
|
387
|
-
_$insert(_el$
|
|
388
|
-
_$insertNode(_el$
|
|
389
|
-
_$insert(_el$
|
|
416
|
+
var _el$59 = _$createElement("box"), _el$60 = _$createElement("text"), _el$62 = _$createElement("text"), _el$63 = _$createElement("text"), _el$64 = _$createElement("text"), _el$65 = _$createTextNode(` `);
|
|
417
|
+
_$insertNode(_el$59, _el$60);
|
|
418
|
+
_$insertNode(_el$59, _el$62);
|
|
419
|
+
_$insertNode(_el$59, _el$63);
|
|
420
|
+
_$insertNode(_el$59, _el$64);
|
|
421
|
+
_$setProp(_el$59, "flexDirection", "row");
|
|
422
|
+
_$insertNode(_el$60, _$createTextNode(` 5h `));
|
|
423
|
+
_$insert(_el$62, () => barFill(pct));
|
|
424
|
+
_$insert(_el$63, () => barEmpty(pct));
|
|
425
|
+
_$insertNode(_el$64, _el$65);
|
|
426
|
+
_$insert(_el$64, pctLabel, null);
|
|
390
427
|
_$effect((_p$) => {
|
|
391
|
-
var _v$
|
|
392
|
-
_v$
|
|
393
|
-
_v$
|
|
428
|
+
var _v$11 = st("text"), _v$12 = st("text");
|
|
429
|
+
_v$11 !== _p$.e && (_p$.e = _$setProp(_el$62, "style", _v$11, _p$.e));
|
|
430
|
+
_v$12 !== _p$.t && (_p$.t = _$setProp(_el$63, "style", _v$12, _p$.t));
|
|
394
431
|
return _p$;
|
|
395
432
|
}, {
|
|
396
433
|
e: void 0,
|
|
397
434
|
t: void 0
|
|
398
435
|
});
|
|
399
|
-
return _el$
|
|
436
|
+
return _el$59;
|
|
400
437
|
})());
|
|
401
438
|
}
|
|
402
439
|
}
|
|
403
440
|
return (() => {
|
|
404
|
-
var _el$
|
|
405
|
-
_$setProp(_el$
|
|
406
|
-
_$insert(_el$
|
|
407
|
-
return _el$
|
|
441
|
+
var _el$66 = _$createElement("box");
|
|
442
|
+
_$setProp(_el$66, "flexDirection", "column");
|
|
443
|
+
_$insert(_el$66, nodes);
|
|
444
|
+
return _el$66;
|
|
408
445
|
})();
|
|
409
446
|
};
|
|
410
447
|
tlog("registering slots");
|
|
@@ -419,9 +456,9 @@ function initializeTui(api, disposeRoot) {
|
|
|
419
456
|
} catch (e) {
|
|
420
457
|
tlog(`sidebar_footer err: ${String(e)}`);
|
|
421
458
|
result = (() => {
|
|
422
|
-
var _el$
|
|
423
|
-
_$insertNode(_el$
|
|
424
|
-
return _el$
|
|
459
|
+
var _el$67 = _$createElement("text");
|
|
460
|
+
_$insertNode(_el$67, _$createTextNode(`usage-coach`));
|
|
461
|
+
return _el$67;
|
|
425
462
|
})();
|
|
426
463
|
}
|
|
427
464
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-usage-coach",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "opencode closed-loop usage coach — quota SENSE -> coaching DECIDE -> loop ACT + TUI integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -58,11 +58,5 @@
|
|
|
58
58
|
"tsup": "^8.5",
|
|
59
59
|
"typescript": "^5",
|
|
60
60
|
"typescript-eslint": "^8.63.0"
|
|
61
|
-
}
|
|
62
|
-
"dependencies": {
|
|
63
|
-
"@ladybugdb/core": "^0.18.0"
|
|
64
|
-
},
|
|
65
|
-
"trustedDependencies": [
|
|
66
|
-
"@ladybugdb/core"
|
|
67
|
-
]
|
|
61
|
+
}
|
|
68
62
|
}
|