pantheon-opencode 1.6.0-beta.3 → 1.6.0-beta.4
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/CHANGELOG.md +22 -0
- package/README.md +19 -7
- package/README.pt-BR.md +17 -7
- package/package.json +12 -10
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/code_mode_server.py +1 -1
- package/scripts/cost.mjs +244 -41
- package/scripts/install/agents-md.mjs +2 -2
- package/scripts/install/health-check.mjs +9 -2
- package/scripts/install/shared.mjs +5 -5
- package/scripts/mcp_persistence_server.py +1 -1
- package/scripts/mcp_resources_server.py +1 -1
- package/scripts/memory_mcp_server.py +1 -1
- package/scripts/probe-context-rehydrate.mjs +6 -0
- package/scripts/probe-pantheon-cost.mjs +71 -15
- package/scripts/validate-routing.mjs +3 -3
- package/src/mcp/code_mode_server.py +1 -1
- package/src/mcp/mcp_persistence_server.py +1 -1
- package/src/mcp/mcp_resources_server.py +1 -1
- package/src/mcp/memory_mcp_server.py +1 -1
- package/src/mcp/pantheon_vision_server.py +1 -1
- package/src/mcp/requirements-mcp.txt +6 -4
- package/src/mcp/requirements-vision.txt +2 -2
- package/src/pantheon/cost-command.ts +399 -101
- package/src/pantheon/presets.mjs +3 -3
- package/src/plugin-v2.ts +131 -58
- package/src/plugins/tui/dist/server.d.ts +1 -2
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.d.ts +74 -74
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +17 -20
- package/src/plugins/tui/package-lock.json +364 -317
- package/src/plugins/tui/package.json +8 -5
- package/src/plugins/tui/tsconfig.build.json +5 -0
- package/src/plugins/tui/tsdown.config.ts +3 -0
package/src/plugin-v2.ts
CHANGED
|
@@ -26,21 +26,44 @@
|
|
|
26
26
|
* `execute` MUST resolve to a value carrying `output`. The host enforces the
|
|
27
27
|
* two as a pair — see `V2ToolDraft` for the exact contract.
|
|
28
28
|
*
|
|
29
|
-
* The
|
|
30
|
-
* ctx.agent / ctx.command / ctx.model / ctx.reference / ctx.skill /
|
|
31
|
-
* ctx.tool / ctx.event / ctx.permission / ctx.session
|
|
29
|
+
* ## The four domains the SDK types do not carry
|
|
32
30
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
31
|
+
* `@opencode-ai/plugin` 1.18.33 types `PluginContext` as exactly:
|
|
32
|
+
* options · agent · aisdk · catalog · command · integration · plugin ·
|
|
33
|
+
* reference · skill
|
|
34
|
+
* — no `tool`, `event`, `permission` or `session`. 1.18.34 (`latest`) is the
|
|
35
|
+
* same, so following the stable SDK line does not resolve this: it is a
|
|
36
|
+
* STRUCTURAL DECLARATION task, not a pin reconciliation.
|
|
37
|
+
*
|
|
38
|
+
* A live opencode 2.0.22 host DOES have all four, with the shapes this module
|
|
39
|
+
* uses. Measured by `tests/canary/plugin-v2-tool-canary.test.mjs`, which loads
|
|
40
|
+
* this file through the real loader and records `Object.keys(ctx)` from inside
|
|
41
|
+
* the host:
|
|
42
|
+
*
|
|
43
|
+
* agent aisdk app command event experimental generate integration location
|
|
44
|
+
* mcp model options permission plugin provider reference rpc session shell
|
|
45
|
+
* skill storage tool vcs websearch worktree
|
|
46
|
+
*
|
|
47
|
+
* ctx.tool.transform function
|
|
48
|
+
* ctx.tool.hook function
|
|
49
|
+
* ctx.tool.list function
|
|
50
|
+
* ctx.event.subscribe function
|
|
51
|
+
* ctx.permission.hook function
|
|
52
|
+
* ctx.session.hook function
|
|
53
|
+
* ctx.catalog absent
|
|
54
|
+
*
|
|
55
|
+
* `HostPluginContext` below declares exactly the minimum of that, and
|
|
56
|
+
* `hostDomains` is the SINGLE documented cast that reaches them — replacing the
|
|
57
|
+
* six repeated `as unknown as Record<string, unknown>` casts this file carried
|
|
58
|
+
* (Phases 2, 3, 4, 5, 6 and 7 — every phase except Phase 1, which reads the
|
|
59
|
+
* SDK-typed domains directly), each of which type-checked nothing at all. The
|
|
60
|
+
* per-domain definition checks stay: `registerV2Tools` still returns `false`
|
|
61
|
+
* and marks `tool-transform` when `ctx.tool.transform` is missing, and likewise
|
|
62
|
+
* for the others. The cast is not a licence to assume: the canary above is what
|
|
63
|
+
* proves the domains exist on a real host, and it fails if they stop existing.
|
|
64
|
+
*
|
|
65
|
+
* If the SDK ever exposes these four, replacing `hostDomains` with the real
|
|
66
|
+
* type is a one-line change. Do NOT make that change while 1.18.x lacks them.
|
|
44
67
|
*
|
|
45
68
|
* V2_UNSUPPORTED_FEATURES distinguishes adapter limitations from observed
|
|
46
69
|
* host-absent APIs; setup handles optional registrations on a best-effort basis.
|
|
@@ -82,15 +105,19 @@ import { type HookPayload, runHook } from './plugins/hook-runner.ts'
|
|
|
82
105
|
*/
|
|
83
106
|
export const V2_UNSUPPORTED_FEATURES: string[] = [
|
|
84
107
|
'legacy-hooks',
|
|
85
|
-
// `catalog`
|
|
108
|
+
// `ctx.catalog` is the one domain a live 2.0.22 host does NOT have: it is
|
|
109
|
+
// absent from `Object.keys(ctx)`, measured in
|
|
110
|
+
// tests/canary/plugin-v2-tool-canary.test.mjs. (`catalog` IS in the 1.18.33
|
|
111
|
+
// SDK's PluginContext, so the SDK types it and the host does not provide it.)
|
|
86
112
|
'catalog-transform',
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
113
|
+
// Adapter support, NOT host availability: 2.0.22 exposes ctx.integration and
|
|
114
|
+
// ctx.skill with callable transforms, and Pantheon deliberately registers
|
|
115
|
+
// neither. These entries record what this plugin does not implement.
|
|
90
116
|
'integration-transform',
|
|
91
|
-
// The inspected SkillEditor shape has no `source()` helper
|
|
92
|
-
// directory source. This is narrower
|
|
93
|
-
// host availability or callable
|
|
117
|
+
// The inspected SkillEditor shape has no `source()` helper — i.e. no
|
|
118
|
+
// `SkillEditor.source()` — for adding a directory source. This is narrower
|
|
119
|
+
// than (and distinct from) ctx.skill's host availability or callable
|
|
120
|
+
// transform, which a live 2.0.22 host does provide.
|
|
94
121
|
'skill-transform',
|
|
95
122
|
// Adapter limitation, not a host gap: the goal loop needs a GoalStore, a
|
|
96
123
|
// GoalLoopClient and a BackgroundJobBoard, none of which the V2
|
|
@@ -189,6 +216,68 @@ function markUnsupported(feature: string): void {
|
|
|
189
216
|
}
|
|
190
217
|
}
|
|
191
218
|
|
|
219
|
+
// ─── Host context domains the SDK types do not carry ────────────────────
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The minimal shape of `ctx.tool` this module uses, as a live opencode 2.0.22
|
|
223
|
+
* host provides it. `transform` registers the Pantheon tools, `hook` installs
|
|
224
|
+
* the `execute.before`/`execute.after` guards, `list` is the host-built
|
|
225
|
+
* descriptor dump the canary asserts on.
|
|
226
|
+
*/
|
|
227
|
+
interface V2ToolDomain {
|
|
228
|
+
transform?: (cb: (draft: V2ToolDraft) => void) => Promise<unknown>
|
|
229
|
+
hook?: (name: string, handler: (event: unknown) => void | Promise<void>) => Promise<unknown>
|
|
230
|
+
list?: () => Promise<unknown>
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The minimal shape of `ctx.event`: one async-iterable subscription. */
|
|
234
|
+
interface V2EventDomain {
|
|
235
|
+
subscribe?: (opts?: { signal?: AbortSignal }) => AsyncIterable<V2SessionEvent>
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The minimal shape of `ctx.permission`.
|
|
240
|
+
*
|
|
241
|
+
* Only a hook name and a handler: the plugin registers `evaluate` as a
|
|
242
|
+
* registration point and deliberately runs no V2-side permission logic, so no
|
|
243
|
+
* richer payload type is claimed here.
|
|
244
|
+
*/
|
|
245
|
+
interface V2PermissionDomain {
|
|
246
|
+
hook?: (name: string, handler: (event: unknown) => void | Promise<void>) => Promise<unknown>
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The minimal shape of `ctx.session`.
|
|
251
|
+
*
|
|
252
|
+
* Only `hook(name, handler)`. The host accepts any string and silently drops an
|
|
253
|
+
* unknown one — which is why the hook names below were a live defect once and
|
|
254
|
+
* why the host-backed hook canary exists.
|
|
255
|
+
*/
|
|
256
|
+
interface V2SessionDomain {
|
|
257
|
+
hook?: (name: string, handler: (event: unknown) => void | Promise<void>) => Promise<unknown>
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** `PluginContext` plus the four domains 1.18.x omits. See the module header. */
|
|
261
|
+
interface HostPluginContext extends PluginContext {
|
|
262
|
+
tool?: V2ToolDomain
|
|
263
|
+
event?: V2EventDomain
|
|
264
|
+
permission?: V2PermissionDomain
|
|
265
|
+
session?: V2SessionDomain
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* THE cast to the host's context. Every optional member stays optional and every
|
|
270
|
+
* caller re-checks before use, so this narrows nothing away: it replaces five
|
|
271
|
+
* anonymous `Record<string, unknown>` casts with one named, documented,
|
|
272
|
+
* type-checked boundary.
|
|
273
|
+
*
|
|
274
|
+
* When the SDK ships these four domains, delete this function and use
|
|
275
|
+
* `PluginContext` directly.
|
|
276
|
+
*/
|
|
277
|
+
function hostDomains(context: PluginContext): HostPluginContext {
|
|
278
|
+
return context as HostPluginContext
|
|
279
|
+
}
|
|
280
|
+
|
|
192
281
|
// ─── V1 Bridge Integration ──────────────────────────────────────────────
|
|
193
282
|
|
|
194
283
|
/**
|
|
@@ -334,9 +423,7 @@ function transformReferences(draft: ReferenceDraft): void {
|
|
|
334
423
|
* Returns true if tools were registered, false if the API is unavailable.
|
|
335
424
|
*/
|
|
336
425
|
async function registerV2Tools(context: PluginContext): Promise<boolean> {
|
|
337
|
-
const toolCtx = (context
|
|
338
|
-
| { transform?: (cb: (draft: V2ToolDraft) => void) => Promise<unknown> }
|
|
339
|
-
| undefined
|
|
426
|
+
const toolCtx = hostDomains(context).tool
|
|
340
427
|
|
|
341
428
|
if (!toolCtx?.transform) {
|
|
342
429
|
return false
|
|
@@ -387,9 +474,7 @@ async function registerV2Tools(context: PluginContext): Promise<boolean> {
|
|
|
387
474
|
* Returns a cleanup function, or undefined if the API is unavailable.
|
|
388
475
|
*/
|
|
389
476
|
function subscribeV2Events(context: PluginContext): (() => void) | undefined {
|
|
390
|
-
const eventCtx = (context
|
|
391
|
-
| { subscribe?: (opts?: { signal?: AbortSignal }) => AsyncIterable<V2SessionEvent> }
|
|
392
|
-
| undefined
|
|
477
|
+
const eventCtx = hostDomains(context).event
|
|
393
478
|
|
|
394
479
|
if (!eventCtx?.subscribe) {
|
|
395
480
|
return undefined
|
|
@@ -505,11 +590,7 @@ async function onSessionCompacted(_event: V2SessionEvent): Promise<void> {}
|
|
|
505
590
|
* Returns true if any hooks were registered.
|
|
506
591
|
*/
|
|
507
592
|
async function registerV2SessionHooks(context: PluginContext): Promise<boolean> {
|
|
508
|
-
const sessionCtx = (context
|
|
509
|
-
| {
|
|
510
|
-
hook?: (name: string, handler: (event: unknown) => void | Promise<void>) => Promise<unknown>
|
|
511
|
-
}
|
|
512
|
-
| undefined
|
|
593
|
+
const sessionCtx = hostDomains(context).session
|
|
513
594
|
|
|
514
595
|
if (!sessionCtx?.hook) {
|
|
515
596
|
return false
|
|
@@ -600,16 +681,21 @@ function maskSecret(text: string): string {
|
|
|
600
681
|
/**
|
|
601
682
|
* Message thrown when scan-secrets.sh exits 2 (a high-confidence token match).
|
|
602
683
|
*
|
|
603
|
-
* This is the only deliberate throw on the V2 surface
|
|
604
|
-
* message (src/plugins/pantheon-hooks.ts `SECRET_BLOCK_MESSAGE`). opencode's
|
|
684
|
+
* This is the only deliberate throw on the V2 surface. opencode's
|
|
605
685
|
* Plugin.trigger has no catch around hook invocations, so the rejection
|
|
606
686
|
* propagates and fails the tool's execute promise BEFORE the tool body runs —
|
|
607
|
-
* the same hard block V1
|
|
687
|
+
* the same hard block V1 has (src/plugins/pantheon-hooks.ts
|
|
688
|
+
* `SECRET_BLOCK_MESSAGE`), which was inert on a V2-only install because
|
|
608
689
|
* pantheon-hooks.ts is never loaded there (opencode.json `plugins` declares only
|
|
609
690
|
* `src/plugin-v2`).
|
|
691
|
+
*
|
|
692
|
+
* English, like every other message this module emits: this string surfaces in
|
|
693
|
+
* the user's session as the tool error, so it is read by whoever hit the block,
|
|
694
|
+
* not by the code that raised it. A Portuguese release note elsewhere in the
|
|
695
|
+
* repo is not a reason for the runtime message to be Portuguese too.
|
|
610
696
|
*/
|
|
611
697
|
const SECRET_BLOCK_MESSAGE =
|
|
612
|
-
'[plugin-v2]
|
|
698
|
+
'[plugin-v2] Blocked: high-confidence secret detected in tool input — see .pantheon/logs/hooks.log'
|
|
613
699
|
|
|
614
700
|
/**
|
|
615
701
|
* Append one masked, ISO-stamped line to .pantheon/logs/hooks.log under the
|
|
@@ -740,11 +826,7 @@ function adaptV2ExecuteBeforeEvent(event: unknown): {
|
|
|
740
826
|
* Returns true if any hooks were registered.
|
|
741
827
|
*/
|
|
742
828
|
async function registerV2ToolHooks(context: PluginContext): Promise<boolean> {
|
|
743
|
-
const toolCtx = (context
|
|
744
|
-
| {
|
|
745
|
-
hook?: (name: string, handler: (event: unknown) => void | Promise<void>) => Promise<unknown>
|
|
746
|
-
}
|
|
747
|
-
| undefined
|
|
829
|
+
const toolCtx = hostDomains(context).tool
|
|
748
830
|
|
|
749
831
|
if (!toolCtx?.hook) {
|
|
750
832
|
return false
|
|
@@ -842,11 +924,7 @@ async function registerV2ToolHooks(context: PluginContext): Promise<boolean> {
|
|
|
842
924
|
* Returns true if the hook was registered.
|
|
843
925
|
*/
|
|
844
926
|
async function registerV2PermissionHook(context: PluginContext): Promise<boolean> {
|
|
845
|
-
const permCtx = (context
|
|
846
|
-
| {
|
|
847
|
-
hook?: (name: string, handler: (event: unknown) => void | Promise<void>) => Promise<unknown>
|
|
848
|
-
}
|
|
849
|
-
| undefined
|
|
927
|
+
const permCtx = hostDomains(context).permission
|
|
850
928
|
|
|
851
929
|
if (!permCtx?.hook) {
|
|
852
930
|
markUnsupported('permission-hook')
|
|
@@ -920,10 +998,12 @@ export const plugin = define({
|
|
|
920
998
|
//
|
|
921
999
|
// Pantheon's Phase 1/config transform registrations cover agent, command,
|
|
922
1000
|
// and reference; ctx.tool.transform is attempted separately in Phase 2.
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
//
|
|
926
|
-
//
|
|
1001
|
+
// ctx.integration and ctx.skill are present on a live 2.0.22 host and neither
|
|
1002
|
+
// is registered here — that is a Pantheon scope choice, not a host gap.
|
|
1003
|
+
// ctx.catalog is the one domain the host does not provide.
|
|
1004
|
+
// Phase 1 registers against the SDK-typed `context.agent` / `.command` /
|
|
1005
|
+
// `.reference` directly; the four domains the SDK omits are reached only
|
|
1006
|
+
// through `hostDomains` (see the module header).
|
|
927
1007
|
const phase1Transforms: Array<{ feature: string; register: () => Promise<unknown> }> = [
|
|
928
1008
|
{ feature: 'agent-transform', register: () => context.agent.transform(transformAgents) },
|
|
929
1009
|
{
|
|
@@ -976,14 +1056,7 @@ export const plugin = define({
|
|
|
976
1056
|
await registerV2PermissionHook(context).catch(() => false)
|
|
977
1057
|
|
|
978
1058
|
// ─── Phase 7: V2 Compaction Hook (best-effort) ───────────────────
|
|
979
|
-
const compactionCtx = (context
|
|
980
|
-
| {
|
|
981
|
-
hook?: (
|
|
982
|
-
name: string,
|
|
983
|
-
handler: (event: unknown) => void | Promise<void>,
|
|
984
|
-
) => Promise<unknown>
|
|
985
|
-
}
|
|
986
|
-
| undefined
|
|
1059
|
+
const compactionCtx = hostDomains(context).session
|
|
987
1060
|
if (compactionCtx?.hook) {
|
|
988
1061
|
try {
|
|
989
1062
|
// 2.0.16 `SessionHooks` key is `compaction` (the migration doc maps
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* This stub satisfies the loader contract; pantheon-tui is TUI-only and has
|
|
12
12
|
* no background server work of its own.
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
export default function server(): {};
|
|
15
15
|
//#endregion
|
|
16
|
-
export { server as default };
|
|
17
16
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;;;;;;;;;;wBAYwB"}
|