pantheon-opencode 1.6.0-beta.7 → 1.6.0-beta.8
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/AGENTS.md +58 -15
- package/CHANGELOG.md +37 -100
- package/README.md +78 -102
- package/README.pt-BR.md +19 -37
- package/package.json +1 -2
- package/plugin.json +2 -2
- package/pyproject.toml +1 -1
- package/scripts/build-agents-md.mjs +2 -3
- package/scripts/doctor.mjs +22 -104
- package/scripts/hooks/scan-secrets.sh +97 -0
- package/scripts/install/config-migration.mjs +9 -42
- package/scripts/install/interactive.mjs +1 -1
- package/scripts/install/opencode.mjs +114 -43
- package/scripts/install/strings.mjs +0 -8
- package/scripts/probe-pantheon-cost.mjs +3 -16
- package/scripts/test-opencode-v2-sandbox.sh +203 -786
- package/scripts/validate-sandbox-install.mjs +138 -0
- package/skills-lock.json +3 -4
- package/src/agents/aphrodite.md +2 -9
- package/src/agents/demeter.md +0 -9
- package/src/agents/gaia.md +1 -0
- package/src/agents/hephaestus.md +0 -9
- package/src/agents/hermes.md +2 -9
- package/src/agents/mnemosyne.md +14 -42
- package/src/agents/prometheus.md +0 -9
- package/src/agents/themis.md +0 -1
- package/src/agents/zeus.md +180 -19
- package/src/instructions/agent-return-format.instructions.md +48 -3
- package/src/instructions/memory-protocol.instructions.md +8 -9
- package/src/instructions/zeus-anti-stall.instructions.md +54 -6
- package/src/instructions/zeus-council-synthesis.instructions.md +71 -0
- package/src/instructions/zeus-memory-operations.instructions.md +4 -4
- package/src/instructions/zeus-timeout-retry.instructions.md +81 -7
- package/src/pantheon/v2-delegation-enforce.ts +2 -1
- package/src/plugin-v2.ts +251 -168
- package/src/plugins/pantheon-hooks.ts +35 -82
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +26 -50
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +1 -1
- package/src/plugins/tui/src/index.tsx +25 -60
- package/src/routing.yml +4 -2
- package/src/skills/README.md +7 -12
- package/src/skills/agent-coordination/SKILL.md +83 -13
- package/src/skills/auto-continue/SKILL.md +13 -14
- package/src/skills/context-compression/SKILL.md +26 -879
- package/src/pantheon/secret-scanner.ts +0 -489
- package/src/plugins/tui/README.md +0 -14
- package/src/skills/council-synthesis/SKILL.md +0 -29
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Re-created per Council Synthesis 2026-08-05 (P0): the previous hook plugin
|
|
5
5
|
* was removed on 24-25/07 because it used the Bun Shell `$` from the plugin
|
|
6
6
|
* API, whose interface has NO `.timeout()` method — every hook crashed with
|
|
7
|
-
* `TypeError: $.quiet().nothrow().timeout is not a function`. The
|
|
7
|
+
* `TypeError: $.quiet().nothrow().timeout is not a function`. The 10 functional
|
|
8
8
|
* scripts in scripts/hooks/ (gitleaks covers only staged files, NOT tool
|
|
9
9
|
* calls) were left orphaned. This plugin wires them back into the opencode
|
|
10
10
|
* runtime via `node:child_process` (version-proof — see hook-runner.ts).
|
|
11
11
|
*
|
|
12
|
-
* Hook mapping
|
|
13
|
-
*
|
|
12
|
+
* Hook mapping (Claude Code protocol payload {tool_name, tool_input,
|
|
13
|
+
* agent_id, session_id} delivered on the script stdin):
|
|
14
|
+
* - tool.execute.before → validate-talos-scope.sh, scan-secrets.sh,
|
|
14
15
|
* validate-tool-safety.sh + on-subagent-delegation-start.sh (only when the
|
|
15
16
|
* tool is a delegation tool such as `task`, to keep the audit log honest)
|
|
16
17
|
* - tool.execute.after → format-multi-language.sh, log-session-start.sh +
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
* ({tool, sessionID, callID}). P0-4 (2026-08-06): a `chat.params` hook
|
|
23
24
|
* records the active agent per session (sessionAgent map), so payloadFor()
|
|
24
25
|
* now resolves the real agent_id — validate-talos-scope.sh enforces the
|
|
25
|
-
* Talos file-count boundary; the agent-agnostic
|
|
26
|
+
* Talos file-count boundary; the agent-agnostic scan-secrets and
|
|
26
27
|
* validate-tool-safety protections remain fully active.
|
|
27
28
|
* - tool.execute.before CAN hard-deny — the old "cannot hard-deny" claim
|
|
28
29
|
* below this line is OUTDATED for 1.18.x (verified 2026-08-06 on the
|
|
@@ -33,15 +34,15 @@
|
|
|
33
34
|
* the tool body runs (the docs' .env-protection example relies on this).
|
|
34
35
|
* The permission system remains the primary deny path, but a thrown
|
|
35
36
|
* hook error is now a supported hard block (hybrid blocking, Fix 2
|
|
36
|
-
* 2026-08-06): high-confidence token matches
|
|
37
|
-
* reportFailure (non-TUI channels) and THEN throws
|
|
37
|
+
* 2026-08-06): scan-secrets.sh exits 2 on high-confidence token matches
|
|
38
|
+
* → the plugin logs via reportFailure (non-TUI channels) and THEN throws
|
|
38
39
|
* the DELIBERATE block error — the ONLY throw in this plugin. Exit 1
|
|
39
40
|
* (low-confidence header/KEY names) stays advisory: toast + logs, no
|
|
40
41
|
* block.
|
|
41
42
|
* - Logging policy (P0 fix 2026-08-06): NON-ZERO exit → NEVER console.
|
|
42
43
|
* `console.error` in a plugin writes to the process stderr, which the
|
|
43
44
|
* opencode TUI renders directly into the terminal — that polluted the
|
|
44
|
-
* chat with
|
|
45
|
+
* chat with `[pantheon-hooks:scan-secrets.sh] exit 1: [SECRET SCAN]...`
|
|
45
46
|
* spam on every tool call. Non-zero exits now go to THREE non-TUI channels:
|
|
46
47
|
* 1. client.tui.showToast() — one short, deduped TUI toast (per
|
|
47
48
|
* (script, exit code, masked match) per session) so the user gets a
|
|
@@ -59,8 +60,8 @@
|
|
|
59
60
|
* - Hooks NEVER throw: every body is try/catch'd and only logs. All three
|
|
60
61
|
* reporting channels (toast, app.log, file append) are individually
|
|
61
62
|
* try/catch'd so a failure in one can never take down the session.
|
|
62
|
-
* EXCEPTION (Fix 2, 2026-08-06): the deliberate high-
|
|
63
|
-
* block — when
|
|
63
|
+
* EXCEPTION (Fix 2, 2026-08-06): the deliberate scan-secrets high-
|
|
64
|
+
* confidence block — when scan-secrets.sh exits 2, the hook awaits
|
|
64
65
|
* reportFailure FIRST (app.log + hooks.log) and only then throws. The
|
|
65
66
|
* throw is deliberately NOT swallowed by the surrounding try/catch
|
|
66
67
|
* (handled via a blockError variable thrown after it), because that
|
|
@@ -176,7 +177,6 @@ import { appendFile, mkdir } from 'node:fs/promises'
|
|
|
176
177
|
import { join } from 'node:path'
|
|
177
178
|
import type { Plugin, PluginInput } from '@opencode-ai/plugin'
|
|
178
179
|
import { pantheonPluginOnce } from '../pantheon/plugin-once.ts'
|
|
179
|
-
import { scanSecretPayload } from '../pantheon/secret-scanner.ts'
|
|
180
180
|
import { getSharedBoard } from '../pantheon/shared-board.ts'
|
|
181
181
|
import { type HookPayload, type HookResult, runHook } from './hook-runner.ts'
|
|
182
182
|
|
|
@@ -320,7 +320,8 @@ const sessionAgent = new Map<string, { agent: string; at: number }>()
|
|
|
320
320
|
const RISKY_PATH_RE = /migration|schema\.sql|alembic|models\/|auth|password|token|secret/i
|
|
321
321
|
|
|
322
322
|
/**
|
|
323
|
-
* Fix 1 (2026-08-06): high-confidence secret token formats
|
|
323
|
+
* Fix 1 (2026-08-06): high-confidence secret token formats — mirrors
|
|
324
|
+
* scripts/hooks/scan-secrets.sh HIGH_CONFIDENCE_PATTERNS. Used by maskSecret()
|
|
324
325
|
* to REDACT secret VALUES from any plugin log line: the talos-guard must
|
|
325
326
|
* never write a raw credential to hooks.log (a real `sk-bf-...` token leaked
|
|
326
327
|
* in plain text there on 2026-08-06). Header/KEY names (the Bifrost header alone,
|
|
@@ -340,8 +341,8 @@ function maskSecret(text: string): string {
|
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
/**
|
|
343
|
-
* Fix 2 (2026-08-06): message thrown
|
|
344
|
-
* This is the ONLY deliberate throw in the
|
|
344
|
+
* Fix 2 (2026-08-06): message thrown when scan-secrets.sh exits 2
|
|
345
|
+
* (HIGH_CONFIDENCE token match). This is the ONLY deliberate throw in the
|
|
345
346
|
* plugin — it is thrown AFTER reportFailure so the log channels always write
|
|
346
347
|
* first, and it is NOT swallowed by the hook's try/catch: opencode's
|
|
347
348
|
* Plugin.trigger has no catch around hook invocations, so the rejection
|
|
@@ -1092,8 +1093,9 @@ function flushIdleReminders(ctx: HookContext): void {
|
|
|
1092
1093
|
* (echo routed to structured log + hooks.log, never the TUI).
|
|
1093
1094
|
* No console.error / console.log anywhere in this path — the TUI stays clean.
|
|
1094
1095
|
*
|
|
1095
|
-
* Returns the underlying HookResult (or null on unexpected failure)
|
|
1096
|
-
*
|
|
1096
|
+
* Returns the underlying HookResult (or null on unexpected failure) so the
|
|
1097
|
+
* tool.execute.before handler can inspect scan-secrets.sh's exit code for the
|
|
1098
|
+
* Fix-2 hybrid block (code 2 = high-confidence token → throw after logging).
|
|
1097
1099
|
*/
|
|
1098
1100
|
async function safeRun(
|
|
1099
1101
|
ctx: HookContext,
|
|
@@ -1175,26 +1177,15 @@ const plugin: Plugin = async ({ client, directory }) => {
|
|
|
1175
1177
|
// catch: opencode propagates the rejection to fail the tool call.
|
|
1176
1178
|
let blockError: Error | null = null
|
|
1177
1179
|
try {
|
|
1178
|
-
const hooks = ['validate-talos-scope.sh', 'validate-tool-safety.sh']
|
|
1180
|
+
const hooks = ['validate-talos-scope.sh', 'scan-secrets.sh', 'validate-tool-safety.sh']
|
|
1179
1181
|
// P0-2: the delegation record must exist BEFORE the payload is built
|
|
1180
1182
|
// so the start hook receives the delegation_id the stop hook joins on.
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
// Validate and scan before touching delegation args or launching any
|
|
1184
|
-
// hook subprocess. The shared scanner catches malformed serialization
|
|
1185
|
-
// and returns a fixed fail-closed result.
|
|
1186
|
-
const scan = scanSecretPayload({ ...payload, tool_input: args })
|
|
1187
|
-
if (scan.status === 'invalid') {
|
|
1188
|
-
blockError = new Error(
|
|
1189
|
-
'[pantheon-hooks] Bloqueado: o scanner de segredos não validou a entrada da ferramenta (fail-closed)',
|
|
1190
|
-
)
|
|
1191
|
-
} else if (scan.status === 'block') {
|
|
1192
|
-
blockError = new Error(SECRET_BLOCK_MESSAGE)
|
|
1193
|
-
}
|
|
1194
|
-
if (blockError === null && DELEGATION_TOOL_RE.test(input.tool)) {
|
|
1183
|
+
let payload = payloadFor(input, output?.args ?? {})
|
|
1184
|
+
if (DELEGATION_TOOL_RE.test(input.tool)) {
|
|
1195
1185
|
hooks.push('on-subagent-delegation-start.sh')
|
|
1196
1186
|
// tool.execute.before carries args on `output` (typed); `input.args`
|
|
1197
1187
|
// also exists at runtime — read from whichever is present.
|
|
1188
|
+
const args = output?.args ?? (input as { args?: unknown }).args
|
|
1198
1189
|
const start: DelegationStart = {
|
|
1199
1190
|
delegation_id: crypto.randomUUID(),
|
|
1200
1191
|
agent: delegationAgent(input.tool, args),
|
|
@@ -1231,7 +1222,7 @@ const plugin: Plugin = async ({ client, directory }) => {
|
|
|
1231
1222
|
const isWriteIntent =
|
|
1232
1223
|
WRITE_INTENT_TOOLS.has(input.tool) ||
|
|
1233
1224
|
(input.tool === 'bash' && BASH_WRITE_INTENT_RE.test(bashCommand))
|
|
1234
|
-
if (
|
|
1225
|
+
if (isWriteIntent) {
|
|
1235
1226
|
try {
|
|
1236
1227
|
const values: string[] = []
|
|
1237
1228
|
const scan = (v: unknown): void => {
|
|
@@ -1282,63 +1273,25 @@ const plugin: Plugin = async ({ client, directory }) => {
|
|
|
1282
1273
|
// Advisory only — a failure here must never take down the hook.
|
|
1283
1274
|
}
|
|
1284
1275
|
}
|
|
1285
|
-
//
|
|
1286
|
-
//
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
} else if (scan.status === 'block') {
|
|
1298
|
-
for (const finding of scan.findings) {
|
|
1299
|
-
await reportFailure(
|
|
1300
|
-
ctx,
|
|
1301
|
-
`[SECRET SCAN] High-confidence secret detected: ${finding.masked} (pattern: ${finding.pattern})`,
|
|
1302
|
-
{ script: 'secret-scan', tool: input.tool, pattern: finding.pattern },
|
|
1303
|
-
'error',
|
|
1304
|
-
)
|
|
1305
|
-
}
|
|
1276
|
+
// Fix 2 hybrid blocking: safeRun returns each script's HookResult;
|
|
1277
|
+
// scan-secrets.sh exits 2 on HIGH_CONFIDENCE token matches
|
|
1278
|
+
// (sk-bf-*, AKIA..., ghp_..., glpat-..., JWT, bearer...) → BLOCK the
|
|
1279
|
+
// tool call. Exit 1 (low-confidence header/KEY names) stays advisory
|
|
1280
|
+
// — safeRun already logged + toasted it, no block. The toast for
|
|
1281
|
+
// code 1/2 fires inside safeRun BEFORE we reach the throw, and the
|
|
1282
|
+
// reportFailure below is awaited first — log channels always write.
|
|
1283
|
+
const results = await Promise.all(
|
|
1284
|
+
hooks.map(async (script) => ({ script, result: await safeRun(ctx, script, payload) })),
|
|
1285
|
+
)
|
|
1286
|
+
const scan = results.find((r) => r.script === 'scan-secrets.sh')
|
|
1287
|
+
if (scan?.result?.code === 2) {
|
|
1306
1288
|
await reportFailure(
|
|
1307
1289
|
ctx,
|
|
1308
1290
|
SECRET_BLOCK_MESSAGE,
|
|
1309
|
-
{ script: '
|
|
1291
|
+
{ script: 'scan-secrets', tool: input.tool, blocked: true, code: 2 },
|
|
1310
1292
|
'error',
|
|
1311
1293
|
)
|
|
1312
|
-
void notifyToast(ctx, {
|
|
1313
|
-
title: 'Pantheon Hook',
|
|
1314
|
-
message: '⚠️ High-confidence secret detected — tool call blocked',
|
|
1315
|
-
variant: 'error',
|
|
1316
|
-
duration: 4000,
|
|
1317
|
-
category: 'errors',
|
|
1318
|
-
dedupeKey: `secret-scan|block|${input.sessionID}`,
|
|
1319
|
-
})
|
|
1320
1294
|
blockError = new Error(SECRET_BLOCK_MESSAGE)
|
|
1321
|
-
} else if (scan.status === 'advisory') {
|
|
1322
|
-
for (const finding of scan.findings) {
|
|
1323
|
-
await reportFailure(
|
|
1324
|
-
ctx,
|
|
1325
|
-
`[SECRET SCAN] Potential secret detected (low confidence): ${finding.masked} (pattern: ${finding.pattern})`,
|
|
1326
|
-
{ script: 'secret-scan', tool: input.tool, pattern: finding.pattern },
|
|
1327
|
-
'warn',
|
|
1328
|
-
)
|
|
1329
|
-
}
|
|
1330
|
-
void notifyToast(ctx, {
|
|
1331
|
-
title: 'Pantheon Hook',
|
|
1332
|
-
message: '⚠️ Possible secret detected — see log',
|
|
1333
|
-
variant: 'warning',
|
|
1334
|
-
duration: 4000,
|
|
1335
|
-
category: 'errors',
|
|
1336
|
-
dedupeKey: `secret-scan|advisory|${input.sessionID}`,
|
|
1337
|
-
})
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
if (blockError === null) {
|
|
1341
|
-
await Promise.all(hooks.map(async (script) => safeRun(ctx, script, payload)))
|
|
1342
1295
|
}
|
|
1343
1296
|
} catch {
|
|
1344
1297
|
// Never let a hook take down the opencode session — except the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;YAi7BY;;EAEV;;EAEA;;;;EAIA;;EAEA;EACA;;EAUA;;EAEA;EACA;EACA;;;;;;EAUA;;;;;;;;;;;wBAsBc,wBACd,aACA,oBACA,qBACC;;;;;;wBAkHmB,sBAAsB,cAAc,QAAQ;;;;;;;;;wBAyC5C,yBAAyB,eAAe,QAAQ;;;;;;;wBAUtD,oBACd;EAAS;EAAoB;eAC7B;;;;wBAUc,sBACd;EAAS;EAAoB;eAC7B;;;;;;wBAUc,YAAY;;;wBAMZ,WAAW;;;;wBAOX,yBAAyB,GAAG,iBAAiB,GAAG;;;;;wBAahD,oBACd,cAAc,mBACd,oBACA,cACA;EACG,QAAQ;EAAmB,QAAQ;;;;;wBAmCxB,sBACd,cAAc,mBACd,sBACA,cACA,4BACC;;qBAMU;;;;qBAKA;;;qBAIA;qBACA;;;;;;;;;;qBAWA;;;;;;;;qBASA;;;;;qBAMA;;;;;;;;;wBAUG,mBACd,OAAO,iBACP,aACA,uBACC;;;wBAWa,WAAW;;;;wBAeX,kBAAkB,OAAO,iBAAiB;;;YAQ9C;wBAEI,mBAAmB,OAAO,kBAAkB;wBAa5C,wBAAwB,OAAO;;;;wBAqB/B,uBAAuB;;;;;;;YAW3B,yBAAyB;;;;;YAMzB;;;;wBAKI,oBAAoB,OAAO,yBAAyB;YAuBxD;EACV;EACA;EACA;;;;;wBAsBc,oBACd;EACE;EACA;EACA;EACA;IAAU;IAAiB,QAAQ;;GAErC;EACG;EAAmB,UAAU;;;wBAYlB,kBACd,KAAK,YAAY,eACjB,mBACA,UAAU;;wBAWI,sBACd,KAAK,YAAY,eACjB,+BACA,cACA,iBACC;;;;;;;wBAca,4BACd,mBAAmB,mBACnB,eAAe,wBACd;;YAiGS;EACV;EACA;EACA;EACA;EACA;EACA;IACE;IACA,QAAQ;IACR;IACA;IACA;MAAS;MAAgB;;;;;YAKjB;;EAEV;;EAEA;;EAEA;EACA;;EAEA;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;;YAIU;EACV;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;wBAYc,wBACd,MAAM,oBACN,eACC;;;wBAsDa,yBACd,KAAK,YAAY,sBACjB,MAAM,oBACN;;;wBAwEc,sBACd,KAAK,YAAY,sBACjB;;;;;;;;;wBAoBc,2BACd;EAAqB;EAAa;iBAClC,YAAY,uDACX;;;;;wBAwBa,sBACd,KAAK,YAAY,sBACjB,gBAAgB,sBAChB;;;wBAWc,kBAAkB,MAAM,sBAAsB;;;;;;wBAoB9C,uBACd,eAAe,uBACf,aAAa,oBACZ;;;;;;;;;;;;;;;;;wBA0Ca,2BACd,kBAAkB,mBAClB,6CACC;;;;;;;;;wBAyCa,iBAAiB,cAAc;;;;YAOnC;;EAEV;EACA;;IAEE;MAAU;;;IAEV;MAAU;QAAY;QAAe,SAAS;;;;;;;;;;wBASlC,wBAAwB,SAAS;;;;;;;;;;;;wBAuBjC,gBAAgB;EAAgB;;;;;;;wBAUhC,kBAAkB;EAAkC;;;YAKxD;;EAEV;;EAEA;;;EAGA;;;EAGA;EACA;IACE;IACA;;;;;;;;;;;;;;;;;;;;wBAqBY,mBACd,4BACA;EAAS;EAAkB;GAC3B,cACA;;;;;;;YA6BU;;;;;wBAMI,oBAAoB,OAAO,yBAAyB;;;qBAmBvD,uBAAuB,SAAS,OAAO;wBAOpC,mBAAmB,QAAQ;;;;wBAO3B,oBAAoB,OAAO,wBAAwB;;;;;wBAUnD,sBAAsB,OAAO;;;qBAShC;wBAEG,sBAAsB,kBAAkB;;;qBAO3C;;;;wBAoBG,8BACd,cACA;;;;qBAUW;wBAEG,wBACd,OAAO,iBACP,aACA;;;;wBAUc,wBAAwB,OAAO,iBAAiB;;;;qBAYnD;;;;;wBAMG,uBAAuB,kBAAkB;;;;wBAyBzC,sBACd,cAAc,mBACd,qBACA;EACG,SAAS;EAAmB;EAAgB;EAAsB;;;;;wBAevD,uBAAuB,kBAAkB;EACvD;EACA;EACA;;;;;wBAUc,mBACd,kBACA,kBACA,gBACA,YACA;wBA8Cc,4BACd,mBAAmB,mCACnB,aAAa,mBACb,cACA,2BACC;;;;;;wBA4Da,4BACd;EAEM,YAAY,cAAc,SAAS,mCAAmC;eAG5E;;wBAmBc,+BACd;EAEM,YAAY,cAAc,SAAS,mCAAmC;eAG5E;;;;YAuLU;EACV,KAAK,YAAY;;EAEjB,YAAY;;EAEZ;;;wBAIc,6BAA6B;;wBAe7B,6BACd,KAAK,cACL,WAAW;cAggBP,QAAQ;EAAoB;EAAY,aAAa"}
|
|
@@ -167,7 +167,7 @@ async function detectVersion(api) {
|
|
|
167
167
|
if (ver) return ver;
|
|
168
168
|
}
|
|
169
169
|
} catch {}
|
|
170
|
-
return "1.6.0-beta.
|
|
170
|
+
return "1.6.0-beta.8";
|
|
171
171
|
}
|
|
172
172
|
/**
|
|
173
173
|
* usage-bar — AI subscription usage gauge for the opencode TUI.
|
|
@@ -509,22 +509,12 @@ async function loadConfig(configPath) {
|
|
|
509
509
|
return defaultConfig();
|
|
510
510
|
}
|
|
511
511
|
}
|
|
512
|
-
/** Vendored usage-bar plugin init: reads config, seeds the kv cache,
|
|
513
|
-
* the
|
|
512
|
+
/** Vendored usage-bar plugin init: reads config, seeds the kv cache, starts
|
|
513
|
+
* the poll loop and registers the `app_bottom` slot. Skips everything (and
|
|
514
514
|
* registers nothing) when no provider is enabled — mirrors the standalone
|
|
515
515
|
* plugin's early return. */
|
|
516
516
|
async function setupUsageBar(api) {
|
|
517
|
-
let disposed = false;
|
|
518
|
-
let clockInterval;
|
|
519
|
-
const pollTimeouts = /* @__PURE__ */ new Set();
|
|
520
|
-
api.lifecycle.onDispose(() => {
|
|
521
|
-
disposed = true;
|
|
522
|
-
if (clockInterval !== void 0) clearInterval(clockInterval);
|
|
523
|
-
for (const timeout of pollTimeouts) clearTimeout(timeout);
|
|
524
|
-
pollTimeouts.clear();
|
|
525
|
-
});
|
|
526
517
|
const config = await loadConfig(api.state.path?.config);
|
|
527
|
-
if (disposed) return;
|
|
528
518
|
if (api.state.path?.state) opencodeAuthFile = join(api.state.path.state, "auth.json");
|
|
529
519
|
const enabled = providers.filter((p) => config.providers[p.id].enabled);
|
|
530
520
|
if (enabled.length === 0) return;
|
|
@@ -536,6 +526,28 @@ async function setupUsageBar(api) {
|
|
|
536
526
|
const [byProvider, setByProvider] = createSignal(seed);
|
|
537
527
|
const [byStatus, setByStatus] = createSignal({});
|
|
538
528
|
const [now, setNow] = createSignal(Date.now());
|
|
529
|
+
setInterval(() => setNow(Date.now()), 1e3);
|
|
530
|
+
for (const p of enabled) {
|
|
531
|
+
const cfg = config.providers[p.id];
|
|
532
|
+
const poll = async () => {
|
|
533
|
+
const statusP = config.showStatus && p.statusUrl ? fetchStatus(p.statusUrl) : null;
|
|
534
|
+
const [all, status] = await Promise.all([p.fetchUsage(cfg), statusP]);
|
|
535
|
+
if (all) {
|
|
536
|
+
const windows = all.filter((w) => cfg.show[w.category] && w.resetsAt > Date.now());
|
|
537
|
+
setByProvider((prev) => ({
|
|
538
|
+
...prev,
|
|
539
|
+
[p.id]: windows
|
|
540
|
+
}));
|
|
541
|
+
api.kv.set(`usage-bar.${p.id}.windows`, windows);
|
|
542
|
+
}
|
|
543
|
+
if (status !== null) setByStatus((prev) => ({
|
|
544
|
+
...prev,
|
|
545
|
+
[p.id]: status
|
|
546
|
+
}));
|
|
547
|
+
setTimeout(poll, all ? POLL_MS : POLL_MS * 3);
|
|
548
|
+
};
|
|
549
|
+
poll();
|
|
550
|
+
}
|
|
539
551
|
api.slots.register({
|
|
540
552
|
order: 60,
|
|
541
553
|
slots: { app_bottom() {
|
|
@@ -684,42 +696,6 @@ async function setupUsageBar(api) {
|
|
|
684
696
|
});
|
|
685
697
|
} }
|
|
686
698
|
});
|
|
687
|
-
clockInterval = setInterval(() => setNow(Date.now()), 1e3);
|
|
688
|
-
const schedulePoll = (run, delay) => {
|
|
689
|
-
if (disposed) return;
|
|
690
|
-
const timeout = setTimeout(() => {
|
|
691
|
-
pollTimeouts.delete(timeout);
|
|
692
|
-
if (!disposed) run();
|
|
693
|
-
}, delay);
|
|
694
|
-
pollTimeouts.add(timeout);
|
|
695
|
-
};
|
|
696
|
-
for (const p of enabled) {
|
|
697
|
-
const cfg = config.providers[p.id];
|
|
698
|
-
const poll = async () => {
|
|
699
|
-
if (disposed) return;
|
|
700
|
-
const statusP = config.showStatus && p.statusUrl ? fetchStatus(p.statusUrl) : null;
|
|
701
|
-
const [all, status] = await Promise.all([p.fetchUsage(cfg), statusP]);
|
|
702
|
-
if (disposed) return;
|
|
703
|
-
if (all) {
|
|
704
|
-
const windows = all.filter((w) => cfg.show[w.category] && w.resetsAt > Date.now());
|
|
705
|
-
setByProvider((prev) => ({
|
|
706
|
-
...prev,
|
|
707
|
-
[p.id]: windows
|
|
708
|
-
}));
|
|
709
|
-
api.kv.set(`usage-bar.${p.id}.windows`, windows);
|
|
710
|
-
}
|
|
711
|
-
if (status !== null) setByStatus((prev) => ({
|
|
712
|
-
...prev,
|
|
713
|
-
[p.id]: status
|
|
714
|
-
}));
|
|
715
|
-
schedulePoll(runPoll, all ? POLL_MS : POLL_MS * 3);
|
|
716
|
-
};
|
|
717
|
-
const runPoll = () => {
|
|
718
|
-
if (disposed) return;
|
|
719
|
-
poll().catch(() => schedulePoll(runPoll, POLL_MS * 3));
|
|
720
|
-
};
|
|
721
|
-
runPoll();
|
|
722
|
-
}
|
|
723
699
|
}
|
|
724
700
|
/** True for `\s` characters (space, tab, newline, CR) — plain char checks so
|
|
725
701
|
* the parser stays regex-free (CodeQL flagged the old `\s*`/`\s+` + `(.+)`
|
|
@@ -2232,7 +2208,7 @@ const tui = (api, _options, _meta) => {
|
|
|
2232
2208
|
if (pantheonPluginOnce("pantheon:tui")) return;
|
|
2233
2209
|
const [version, setVersion] = createSignal(null);
|
|
2234
2210
|
detectVersion(api).then((detected) => setVersion(detected)).catch(() => setVersion(null));
|
|
2235
|
-
setupUsageBar(api)
|
|
2211
|
+
setupUsageBar(api);
|
|
2236
2212
|
const liveStore = createLiveDelegationStore();
|
|
2237
2213
|
const unsubscribeLive = registerLiveDelegationEvents(api, liveStore);
|
|
2238
2214
|
api.lifecycle.onDispose(() => {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
3
|
+
"version": "1.6.0-beta.8",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pantheon-tui",
|
|
9
|
-
"version": "1.6.0-beta.
|
|
9
|
+
"version": "1.6.0-beta.8",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@opencode-ai/plugin": "1.18.33",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.6.0-beta.
|
|
4
|
+
"version": "1.6.0-beta.8",
|
|
5
5
|
"description": "Pantheon TUI plugin — sidebar (version, preset, sessions, real-time delegations) + AI subscription usage gauges incl. OpenCode Go/Zen dollar limits (vendored, MIT)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|
|
@@ -740,27 +740,12 @@ async function loadConfig(configPath: string | undefined): Promise<UsageBarConfi
|
|
|
740
740
|
}
|
|
741
741
|
}
|
|
742
742
|
|
|
743
|
-
/** Vendored usage-bar plugin init: reads config, seeds the kv cache,
|
|
744
|
-
* the
|
|
743
|
+
/** Vendored usage-bar plugin init: reads config, seeds the kv cache, starts
|
|
744
|
+
* the poll loop and registers the `app_bottom` slot. Skips everything (and
|
|
745
745
|
* registers nothing) when no provider is enabled — mirrors the standalone
|
|
746
746
|
* plugin's early return. */
|
|
747
747
|
async function setupUsageBar(api: TuiPluginApi) {
|
|
748
|
-
let disposed = false
|
|
749
|
-
let clockInterval: ReturnType<typeof setInterval> | undefined
|
|
750
|
-
const pollTimeouts = new Set<ReturnType<typeof setTimeout>>()
|
|
751
|
-
|
|
752
|
-
// This optional feature owns timers independently from the sidebar. A slow
|
|
753
|
-
// config read may finish after shutdown, so register cleanup before awaiting
|
|
754
|
-
// anything and check the disposed flag at every async boundary.
|
|
755
|
-
api.lifecycle.onDispose(() => {
|
|
756
|
-
disposed = true
|
|
757
|
-
if (clockInterval !== undefined) clearInterval(clockInterval)
|
|
758
|
-
for (const timeout of pollTimeouts) clearTimeout(timeout)
|
|
759
|
-
pollTimeouts.clear()
|
|
760
|
-
})
|
|
761
|
-
|
|
762
748
|
const config = await loadConfig(api.state.path?.config)
|
|
763
|
-
if (disposed) return
|
|
764
749
|
if (api.state.path?.state) opencodeAuthFile = join(api.state.path.state, 'auth.json')
|
|
765
750
|
|
|
766
751
|
const enabled = providers.filter((p) => config.providers[p.id].enabled)
|
|
@@ -777,6 +762,28 @@ async function setupUsageBar(api: TuiPluginApi) {
|
|
|
777
762
|
const [byStatus, setByStatus] = createSignal<Record<string, StatusIndicator>>({})
|
|
778
763
|
const [now, setNow] = createSignal(Date.now())
|
|
779
764
|
|
|
765
|
+
setInterval(() => setNow(Date.now()), 1_000)
|
|
766
|
+
|
|
767
|
+
for (const p of enabled) {
|
|
768
|
+
const cfg = config.providers[p.id]
|
|
769
|
+
const poll = async () => {
|
|
770
|
+
// Fetch usage and status concurrently; status pages are CDN-backed and
|
|
771
|
+
// never throttle, so we poll them on the same cadence as usage.
|
|
772
|
+
const statusP = config.showStatus && p.statusUrl ? fetchStatus(p.statusUrl) : null
|
|
773
|
+
const [all, status] = await Promise.all([p.fetchUsage(cfg), statusP])
|
|
774
|
+
if (all) {
|
|
775
|
+
const windows = all.filter((w) => cfg.show[w.category] && w.resetsAt > Date.now())
|
|
776
|
+
setByProvider((prev) => ({ ...prev, [p.id]: windows }))
|
|
777
|
+
api.kv.set(`usage-bar.${p.id}.windows`, windows)
|
|
778
|
+
}
|
|
779
|
+
// `null` means the status fetch failed — keep the last known indicator.
|
|
780
|
+
if (status !== null) setByStatus((prev) => ({ ...prev, [p.id]: status }))
|
|
781
|
+
// Back off when the usage fetch failed (e.g. 429 — these endpoints throttle).
|
|
782
|
+
setTimeout(poll, all ? POLL_MS : POLL_MS * 3)
|
|
783
|
+
}
|
|
784
|
+
void poll()
|
|
785
|
+
}
|
|
786
|
+
|
|
780
787
|
api.slots.register({
|
|
781
788
|
order: 60,
|
|
782
789
|
slots: {
|
|
@@ -864,46 +871,6 @@ async function setupUsageBar(api: TuiPluginApi) {
|
|
|
864
871
|
},
|
|
865
872
|
},
|
|
866
873
|
})
|
|
867
|
-
|
|
868
|
-
// Start background work only once the optional UI slot was accepted. If
|
|
869
|
-
// registration fails, the outer fail-open handler can suppress it without
|
|
870
|
-
// leaving a timer or provider request running in the background.
|
|
871
|
-
clockInterval = setInterval(() => setNow(Date.now()), 1_000)
|
|
872
|
-
|
|
873
|
-
const schedulePoll = (run: () => void, delay: number) => {
|
|
874
|
-
if (disposed) return
|
|
875
|
-
const timeout = setTimeout(() => {
|
|
876
|
-
pollTimeouts.delete(timeout)
|
|
877
|
-
if (!disposed) run()
|
|
878
|
-
}, delay)
|
|
879
|
-
pollTimeouts.add(timeout)
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
for (const p of enabled) {
|
|
883
|
-
const cfg = config.providers[p.id]
|
|
884
|
-
const poll = async () => {
|
|
885
|
-
if (disposed) return
|
|
886
|
-
// Fetch usage and status concurrently; status pages are CDN-backed and
|
|
887
|
-
// never throttle, so we poll them on the same cadence as usage.
|
|
888
|
-
const statusP = config.showStatus && p.statusUrl ? fetchStatus(p.statusUrl) : null
|
|
889
|
-
const [all, status] = await Promise.all([p.fetchUsage(cfg), statusP])
|
|
890
|
-
if (disposed) return
|
|
891
|
-
if (all) {
|
|
892
|
-
const windows = all.filter((w) => cfg.show[w.category] && w.resetsAt > Date.now())
|
|
893
|
-
setByProvider((prev) => ({ ...prev, [p.id]: windows }))
|
|
894
|
-
api.kv.set(`usage-bar.${p.id}.windows`, windows)
|
|
895
|
-
}
|
|
896
|
-
// `null` means the status fetch failed — keep the last known indicator.
|
|
897
|
-
if (status !== null) setByStatus((prev) => ({ ...prev, [p.id]: status }))
|
|
898
|
-
// Back off when the usage fetch failed (e.g. 429 — these endpoints throttle).
|
|
899
|
-
schedulePoll(runPoll, all ? POLL_MS : POLL_MS * 3)
|
|
900
|
-
}
|
|
901
|
-
const runPoll = () => {
|
|
902
|
-
if (disposed) return
|
|
903
|
-
void poll().catch(() => schedulePoll(runPoll, POLL_MS * 3))
|
|
904
|
-
}
|
|
905
|
-
runPoll()
|
|
906
|
-
}
|
|
907
874
|
}
|
|
908
875
|
|
|
909
876
|
/* ─── Delegations (real-time panel) ────────────────────────
|
|
@@ -3167,9 +3134,7 @@ const tui: TuiPlugin = (api, _options, _meta) => {
|
|
|
3167
3134
|
.catch(() => setVersion(null))
|
|
3168
3135
|
|
|
3169
3136
|
// Vendored feature (MIT): AI subscription usage gauges.
|
|
3170
|
-
|
|
3171
|
-
// never turn an otherwise healthy TUI startup into an unhandled rejection.
|
|
3172
|
-
void setupUsageBar(api).catch(() => undefined)
|
|
3137
|
+
void setupUsageBar(api) // async init — never blocks sidebar registration
|
|
3173
3138
|
|
|
3174
3139
|
// ── Live delegation store (tool-part tracker + sidebar invalidation) ──
|
|
3175
3140
|
// The Delegations panel's PRIMARY source is `api.client.session.children`
|
package/src/routing.yml
CHANGED
|
@@ -133,7 +133,7 @@ agents:
|
|
|
133
133
|
- artifact-management
|
|
134
134
|
- context-compression
|
|
135
135
|
- auto-continue
|
|
136
|
-
-
|
|
136
|
+
- incremental-implementation
|
|
137
137
|
athena:
|
|
138
138
|
role: Strategic planner
|
|
139
139
|
reasoning_effort: high
|
|
@@ -188,6 +188,8 @@ agents:
|
|
|
188
188
|
- pantheon_code_mode
|
|
189
189
|
skills:
|
|
190
190
|
- tdd-with-agents
|
|
191
|
+
- file-prompts
|
|
192
|
+
- streaming-patterns
|
|
191
193
|
- git-workflow-and-versioning
|
|
192
194
|
- incremental-implementation
|
|
193
195
|
aphrodite:
|
|
@@ -209,7 +211,7 @@ agents:
|
|
|
209
211
|
skills:
|
|
210
212
|
- tdd-with-agents
|
|
211
213
|
- visual-review-pipeline
|
|
212
|
-
-
|
|
214
|
+
- file-prompts
|
|
213
215
|
demeter:
|
|
214
216
|
role: Database specialist
|
|
215
217
|
reasoning_effort: medium
|
package/src/skills/README.md
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
Skills are reference modules loaded on demand by agents. Each skill provides domain-specific knowledge — architecture patterns, security checklists, optimization strategies, and implementation guides. Agents load skills when their task matches the skill's description, keeping context focused and token-efficient.
|
|
6
6
|
|
|
7
|
-
There are **
|
|
7
|
+
There are **20 skills** divided into **5 domains**.
|
|
8
8
|
|
|
9
9
|
## Skills by Domain
|
|
10
10
|
|
|
11
11
|
### Domain 1: Orchestration & Workflow
|
|
12
12
|
|
|
13
13
|
1. **agent-coordination** — `agent-coordination/SKILL.md`
|
|
14
|
-
-
|
|
14
|
+
- Multi-agent orchestration with model routing, category delegation, and sprint management.
|
|
15
15
|
- Used by: Zeus
|
|
16
16
|
|
|
17
17
|
2. **artifact-management** — `artifact-management/SKILL.md`
|
|
@@ -23,8 +23,8 @@ There are **21 skills** divided into **5 domains**.
|
|
|
23
23
|
- Used by: Zeus, Apollo, Hephaestus, Nyx, Gaia
|
|
24
24
|
|
|
25
25
|
4. **context-compression** — `context-compression/SKILL.md`
|
|
26
|
-
-
|
|
27
|
-
- Used by: Zeus, Mnemosyne
|
|
26
|
+
- Native compaction guidance and explicit checkpoint rules; no automatic compression MCP calls.
|
|
27
|
+
- Used by: Zeus, Mnemosyne
|
|
28
28
|
|
|
29
29
|
5. **memory-bank** — `memory-bank/SKILL.md`
|
|
30
30
|
- Memory bank rules, structure, and optimization — complete guide to Pantheon memory management.
|
|
@@ -34,10 +34,6 @@ There are **21 skills** divided into **5 domains**.
|
|
|
34
34
|
- Pin session objectives to prevent scope creep across long multi-agent sessions.
|
|
35
35
|
- Used by: Zeus, Mnemosyne
|
|
36
36
|
|
|
37
|
-
- **council-synthesis** — `council-synthesis/SKILL.md`
|
|
38
|
-
- Full structured council workflow, loaded only for `/pantheon` or a material multi-perspective decision.
|
|
39
|
-
- Used by: Zeus
|
|
40
|
-
|
|
41
37
|
### Domain 2: Development Tools
|
|
42
38
|
|
|
43
39
|
7. **clonedeps** — `clonedeps/SKILL.md`
|
|
@@ -50,7 +46,7 @@ There are **21 skills** divided into **5 domains**.
|
|
|
50
46
|
|
|
51
47
|
9. **incremental-implementation** — `incremental-implementation/SKILL.md`
|
|
52
48
|
- Implement in thin vertical slices — one commit per task, testable, rollback-safe.
|
|
53
|
-
- Used by: Hermes, Aphrodite, Demeter, Prometheus, Talos
|
|
49
|
+
- Used by: Zeus, Hermes, Aphrodite, Demeter, Prometheus, Talos
|
|
54
50
|
|
|
55
51
|
10. **loop-engineering** — `loop-engineering/SKILL.md`
|
|
56
52
|
- Iterative refinement pattern for complex problems — solve, review, improve, repeat in controlled cycles.
|
|
@@ -128,12 +124,12 @@ The `SKILL.md` file contains YAML frontmatter with `name`, `description`, and pl
|
|
|
128
124
|
| 1 | agent-coordination | Orchestration & Workflow | Zeus |
|
|
129
125
|
| 2 | artifact-management | Orchestration & Workflow | Zeus, Mnemosyne, Iris |
|
|
130
126
|
| 3 | auto-continue | Orchestration & Workflow | Zeus, Apollo, Hephaestus, Nyx, Gaia |
|
|
131
|
-
| 4 | context-compression | Orchestration & Workflow | Zeus, Mnemosyne
|
|
127
|
+
| 4 | context-compression | Orchestration & Workflow | Zeus, Mnemosyne |
|
|
132
128
|
| 5 | memory-bank | Orchestration & Workflow | Mnemosyne, Athena |
|
|
133
129
|
| 6 | session-goal | Orchestration & Workflow | Zeus, Mnemosyne |
|
|
134
130
|
| 7 | clonedeps | Development Tools | Hermes, Hephaestus |
|
|
135
131
|
| 8 | git-workflow-and-versioning | Development Tools | Hermes, Prometheus, Iris |
|
|
136
|
-
| 9 | incremental-implementation | Development Tools | Hermes, Aphrodite, Demeter, Prometheus, Talos |
|
|
132
|
+
| 9 | incremental-implementation | Development Tools | Zeus, Hermes, Aphrodite, Demeter, Prometheus, Talos |
|
|
137
133
|
| 10 | loop-engineering | Development Tools | All agents |
|
|
138
134
|
| 11 | reflect | Development Tools | All agents |
|
|
139
135
|
| 12 | simplify | Development Tools | All agents |
|
|
@@ -145,7 +141,6 @@ The `SKILL.md` file contains YAML frontmatter with `name`, `description`, and pl
|
|
|
145
141
|
| 18 | spec-driven-development | Planning | Athena |
|
|
146
142
|
| 19 | verification-planning | Planning | Athena, Zeus |
|
|
147
143
|
| 20 | visual-review-pipeline | Frontend Development | Aphrodite |
|
|
148
|
-
| 21 | council-synthesis | Orchestration & Workflow | Zeus |
|
|
149
144
|
|
|
150
145
|
---
|
|
151
146
|
|