pantheon-opencode 1.6.0-beta.7 → 1.6.0-beta.9
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 +61 -94
- package/README.md +81 -103
- package/README.pt-BR.md +19 -37
- package/commands/pantheon-audit.md +7 -1
- package/commands/pantheon-consolidate.md +1 -1
- package/commands/pantheon-focus.md +17 -0
- 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 +174 -77
- package/scripts/install/plugin.mjs +43 -9
- package/scripts/install/strings.mjs +0 -8
- package/scripts/probe-pantheon-cost.mjs +3 -16
- package/scripts/sync-artifacts.mjs +1 -0
- package/scripts/sync-tui.mjs +24 -3
- package/scripts/test-opencode-v2-sandbox.sh +203 -786
- package/scripts/validate-sandbox-install.mjs +138 -0
- package/skills-lock.json +4 -5
- 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 +18 -53
- 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-v2.d.ts +9 -0
- package/src/plugins/tui/dist/tui-v2.d.ts.map +1 -0
- package/src/plugins/tui/dist/tui-v2.js +151 -0
- package/src/plugins/tui/dist/tui-v2.js.map +1 -0
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +29 -53
- package/src/plugins/tui/index-v2.ts +6 -0
- package/src/plugins/tui/package-lock.json +5411 -1292
- package/src/plugins/tui/package.json +10 -7
- package/src/plugins/tui/src/index-v2.tsx +108 -0
- package/src/plugins/tui/src/index.tsx +28 -63
- package/src/plugins/tui/src/opencode-v2.d.ts +13 -0
- package/src/plugins/tui/tsdown.config.ts +2 -2
- package/src/plugins/tui/tui-v2.ts +2 -0
- package/src/plugins/tui/tui.ts +3 -0
- 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/skills/session-goal/SKILL.md +14 -5
- 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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-v2.d.ts","names":[],"sources":["../src/index-v2.tsx"],"mappings":";;cAuFM;;EAEW,MAAA"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createComponent, createElement, createTextNode, effect, insert, insertNode, setProp } from "@opentui/solid";
|
|
2
|
+
import { For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js";
|
|
3
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
4
|
+
//#region src/index-v2.tsx
|
|
5
|
+
/** @jsxImportSource @opentui/solid */
|
|
6
|
+
const SINGLETON = Symbol.for("pantheon.tui.v2.loaded");
|
|
7
|
+
function Sidebar(props) {
|
|
8
|
+
const { context } = props;
|
|
9
|
+
const [revision, setRevision] = createSignal(0);
|
|
10
|
+
const location = context.location ?? context.data.location.default();
|
|
11
|
+
const session = createMemo(() => {
|
|
12
|
+
revision();
|
|
13
|
+
return props.sessionID ? context.data.session.get(props.sessionID) : void 0;
|
|
14
|
+
});
|
|
15
|
+
const branch = createMemo(() => {
|
|
16
|
+
revision();
|
|
17
|
+
return context.data.location.vcs.info(location)?.branch?.current ?? null;
|
|
18
|
+
});
|
|
19
|
+
const delegations = createMemo(() => {
|
|
20
|
+
revision();
|
|
21
|
+
const id = props.sessionID;
|
|
22
|
+
if (!id) return [];
|
|
23
|
+
return (context.data.session.family(id) ?? []).filter((childID) => childID !== id).map((childID) => context.data.session.get(childID)).filter((child) => child?.parentID === id).slice(0, 6);
|
|
24
|
+
});
|
|
25
|
+
onMount(() => {
|
|
26
|
+
const stop = [];
|
|
27
|
+
for (const type of [
|
|
28
|
+
"session.created",
|
|
29
|
+
"session.updated",
|
|
30
|
+
"session.status",
|
|
31
|
+
"session.deleted"
|
|
32
|
+
]) try {
|
|
33
|
+
stop.push(context.data.on(type, () => setRevision((value) => value + 1)));
|
|
34
|
+
} catch {}
|
|
35
|
+
if (props.sessionID) context.data.session.sync(props.sessionID).then(() => setRevision((value) => value + 1));
|
|
36
|
+
context.data.location.vcs.sync(location).then(() => setRevision((value) => value + 1));
|
|
37
|
+
onCleanup(() => {
|
|
38
|
+
for (const unsubscribe of stop) unsubscribe();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
const text = () => context.theme.text?.base;
|
|
42
|
+
const muted = () => context.theme.text?.muted ?? text();
|
|
43
|
+
return (() => {
|
|
44
|
+
var _el$ = createElement("box"), _el$2 = createElement("text"), _el$3 = createElement("text");
|
|
45
|
+
insertNode(_el$, _el$2);
|
|
46
|
+
insertNode(_el$, _el$3);
|
|
47
|
+
setProp(_el$, "flexDirection", "column");
|
|
48
|
+
setProp(_el$, "width", "100%");
|
|
49
|
+
setProp(_el$, "padding", 1);
|
|
50
|
+
setProp(_el$2, "attributes", 1);
|
|
51
|
+
insert(_el$2, `Pantheon v1.6.0-beta.9`);
|
|
52
|
+
insertNode(_el$3, createTextNode(`OpenCode V2 · plugin TUI ativo`));
|
|
53
|
+
insert(_el$, createComponent(Show, {
|
|
54
|
+
get when() {
|
|
55
|
+
return branch();
|
|
56
|
+
},
|
|
57
|
+
children: (value) => (() => {
|
|
58
|
+
var _el$7 = createElement("text");
|
|
59
|
+
insert(_el$7, () => `Branch: ${value()}`);
|
|
60
|
+
effect((_$p) => setProp(_el$7, "fg", muted(), _$p));
|
|
61
|
+
return _el$7;
|
|
62
|
+
})()
|
|
63
|
+
}), null);
|
|
64
|
+
insert(_el$, createComponent(Show, {
|
|
65
|
+
get when() {
|
|
66
|
+
return session();
|
|
67
|
+
},
|
|
68
|
+
get fallback() {
|
|
69
|
+
return (() => {
|
|
70
|
+
var _el$8 = createElement("text");
|
|
71
|
+
insertNode(_el$8, createTextNode(`Nenhuma sessão selecionada`));
|
|
72
|
+
effect((_$p) => setProp(_el$8, "fg", muted(), _$p));
|
|
73
|
+
return _el$8;
|
|
74
|
+
})();
|
|
75
|
+
},
|
|
76
|
+
children: (current) => [(() => {
|
|
77
|
+
var _el$0 = createElement("text");
|
|
78
|
+
insert(_el$0, () => `Sessão: ${current().title || current().id}`);
|
|
79
|
+
effect((_$p) => setProp(_el$0, "fg", text(), _$p));
|
|
80
|
+
return _el$0;
|
|
81
|
+
})(), createComponent(Show, {
|
|
82
|
+
get when() {
|
|
83
|
+
return current().agent;
|
|
84
|
+
},
|
|
85
|
+
get children() {
|
|
86
|
+
var _el$1 = createElement("text");
|
|
87
|
+
insert(_el$1, () => `Agente: ${current().agent}`);
|
|
88
|
+
effect((_$p) => setProp(_el$1, "fg", muted(), _$p));
|
|
89
|
+
return _el$1;
|
|
90
|
+
}
|
|
91
|
+
})]
|
|
92
|
+
}), null);
|
|
93
|
+
insert(_el$, createComponent(Show, {
|
|
94
|
+
get when() {
|
|
95
|
+
return delegations().length > 0;
|
|
96
|
+
},
|
|
97
|
+
get children() {
|
|
98
|
+
return [(() => {
|
|
99
|
+
var _el$5 = createElement("text");
|
|
100
|
+
insertNode(_el$5, createTextNode(`Delegações`));
|
|
101
|
+
setProp(_el$5, "attributes", 1);
|
|
102
|
+
effect((_$p) => setProp(_el$5, "fg", text(), _$p));
|
|
103
|
+
return _el$5;
|
|
104
|
+
})(), createComponent(For, {
|
|
105
|
+
get each() {
|
|
106
|
+
return delegations();
|
|
107
|
+
},
|
|
108
|
+
children: (child) => (() => {
|
|
109
|
+
var _el$10 = createElement("text");
|
|
110
|
+
insert(_el$10, () => `· ${child.title || child.id}`);
|
|
111
|
+
effect((_$p) => setProp(_el$10, "fg", muted(), _$p));
|
|
112
|
+
return _el$10;
|
|
113
|
+
})()
|
|
114
|
+
})];
|
|
115
|
+
}
|
|
116
|
+
}), null);
|
|
117
|
+
effect((_p$) => {
|
|
118
|
+
var _v$ = text(), _v$2 = muted();
|
|
119
|
+
_v$ !== _p$.e && (_p$.e = setProp(_el$2, "fg", _v$, _p$.e));
|
|
120
|
+
_v$2 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$2, _p$.t));
|
|
121
|
+
return _p$;
|
|
122
|
+
}, {
|
|
123
|
+
e: void 0,
|
|
124
|
+
t: void 0
|
|
125
|
+
});
|
|
126
|
+
return _el$;
|
|
127
|
+
})();
|
|
128
|
+
}
|
|
129
|
+
const plugin = Plugin.define({
|
|
130
|
+
id: "pantheon.tui",
|
|
131
|
+
setup(context) {
|
|
132
|
+
const globalState = globalThis;
|
|
133
|
+
if (globalState[SINGLETON]) return;
|
|
134
|
+
globalState[SINGLETON] = true;
|
|
135
|
+
const unregister = context.ui.slot({
|
|
136
|
+
append: "sidebar.content",
|
|
137
|
+
render: ({ sessionID }) => createComponent(Sidebar, {
|
|
138
|
+
context,
|
|
139
|
+
sessionID
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
return () => {
|
|
143
|
+
if (typeof unregister === "function") unregister();
|
|
144
|
+
delete globalState[SINGLETON];
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
//#endregion
|
|
149
|
+
export { plugin as default };
|
|
150
|
+
|
|
151
|
+
//# sourceMappingURL=tui-v2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-v2.js","names":[],"sources":[],"mappings":""}
|
|
@@ -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":"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"}
|
|
@@ -10,9 +10,9 @@ import { For, Show, createMemo, createResource, createSignal, onCleanup, onMount
|
|
|
10
10
|
/**
|
|
11
11
|
* pantheon-tui — Pantheon TUI plugin for opencode.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* V1 bundle: `tsdown` emits `dist/tui.js`, re-exported by root `tui.ts`.
|
|
14
|
+
* OpenCode V2 uses the separate `src/index-v2.tsx` entry and native CLI plugin
|
|
15
|
+
* API. No raw TSX implementation is shipped. Because the bundle inlines relative
|
|
16
16
|
* imports, this entry may be split into modules without breaking the loader.
|
|
17
17
|
*
|
|
18
18
|
* Slots:
|
|
@@ -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.9";
|
|
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(() => {
|