pantheon-opencode 1.6.0-beta.6 → 1.6.0-beta.7

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.
Files changed (58) hide show
  1. package/AGENTS.md +27 -820
  2. package/CHANGELOG.md +116 -0
  3. package/README.md +132 -39
  4. package/README.pt-BR.md +47 -23
  5. package/package.json +6 -1
  6. package/plugin.json +1 -1
  7. package/pyproject.toml +3 -1
  8. package/scripts/build-agents-md.mjs +162 -28
  9. package/scripts/doctor.mjs +104 -22
  10. package/scripts/install/agents-md.mjs +2 -2
  11. package/scripts/install/config-migration.mjs +154 -7
  12. package/scripts/install/opencode.mjs +202 -40
  13. package/scripts/install/shared.mjs +115 -7
  14. package/scripts/install/strings.mjs +8 -0
  15. package/scripts/probe-pantheon-cost.mjs +16 -3
  16. package/scripts/sync-artifacts.mjs +2 -2
  17. package/scripts/test-opencode-v2-sandbox.sh +820 -86
  18. package/skills-lock.json +6 -5
  19. package/src/agents/aphrodite.md +17 -28
  20. package/src/agents/demeter.md +8 -18
  21. package/src/agents/hephaestus.md +10 -11
  22. package/src/agents/hermes.md +15 -30
  23. package/src/agents/prometheus.md +16 -10
  24. package/src/agents/talos.md +4 -1
  25. package/src/agents/themis.md +4 -3
  26. package/src/agents/zeus.md +16 -280
  27. package/src/instructions/agent-return-format.instructions.md +4 -50
  28. package/src/instructions/backend-standards.instructions.md +2 -0
  29. package/src/instructions/frontend-standards.instructions.md +3 -1
  30. package/src/instructions/memory-protocol.instructions.md +12 -121
  31. package/src/instructions/zeus-anti-stall.instructions.md +8 -111
  32. package/src/instructions/zeus-communication-rules.instructions.md +0 -1
  33. package/src/instructions/zeus-memory-operations.instructions.md +26 -0
  34. package/src/instructions/zeus-timeout-retry.instructions.md +9 -130
  35. package/src/pantheon/secret-scanner.ts +489 -0
  36. package/src/pantheon/v2-delegation-enforce.ts +86 -0
  37. package/src/pantheon/v2-execute-after.ts +186 -0
  38. package/src/pantheon/v2-unsupported.mjs +0 -23
  39. package/src/plugin-v2.ts +254 -102
  40. package/src/plugins/pantheon-hooks.ts +82 -35
  41. package/src/plugins/tui/README.md +14 -0
  42. package/src/plugins/tui/dist/tui.d.ts +11 -8
  43. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  44. package/src/plugins/tui/dist/tui.js +102 -62
  45. package/src/plugins/tui/package-lock.json +30 -33
  46. package/src/plugins/tui/package.json +5 -1
  47. package/src/plugins/tui/src/index.tsx +163 -107
  48. package/src/plugins/tui/tsdown.config.ts +17 -0
  49. package/src/routing.yml +12 -9
  50. package/src/skills/README.md +9 -4
  51. package/src/skills/agent-coordination/SKILL.md +13 -83
  52. package/src/skills/artifact-management/SKILL.md +12 -5
  53. package/src/skills/auto-continue/SKILL.md +37 -27
  54. package/src/skills/code-review-checklist/SKILL.md +5 -5
  55. package/src/skills/council-synthesis/SKILL.md +29 -0
  56. package/src/skills/tdd-with-agents/SKILL.md +18 -10
  57. package/scripts/hooks/scan-secrets.sh +0 -97
  58. package/src/instructions/zeus-council-synthesis.instructions.md +0 -299
@@ -0,0 +1,186 @@
1
+ /**
2
+ * V2 `execute.after` parity adapter — reproduces the V1 post-tool chain on the
3
+ * V2 tool-event surface.
4
+ *
5
+ * V1 (`src/plugin.ts`) wires a single `tool.execute.after` handler that runs,
6
+ * in order:
7
+ * 1. task-result-guard — an empty native `task()` result becomes an explicit
8
+ * error so the parent LLM cannot read silence as success;
9
+ * 2. context-sandbox — oversized read/grep/glob/webfetch output is truncated
10
+ * with a marker + metadata;
11
+ * 3. read-enhancer — a `read` result gains per-line hashline tags
12
+ * (`N#TAG|content`) so `hashline_edit` refs resolve.
13
+ *
14
+ * On a V2-only install (`opencode.json` `plugins` declares only `plugin-v2`),
15
+ * `src/plugin.ts` is NOT loaded, so that chain does not exist. `plugin-v2.ts`
16
+ * registered an `execute.after` hook but with an empty body, so the chain was
17
+ * dead on V2. This module rebuilds it against the REAL V2 event shape.
18
+ *
19
+ * The V2 event (opencode 2.0.x `ToolHooks["execute.after"]`) is ONE object:
20
+ *
21
+ * { tool, sessionID, agent, messageID, id, input } & (
22
+ * { status: 'completed'; result: Tool.Result } |
23
+ * { status: 'error'; error: Tool.Error } )
24
+ *
25
+ * where `Tool.Result = { output?, content?, metadata? }`. This adapter only
26
+ * acts on `status === 'completed'`; an ERROR event is left untouched — V1's own
27
+ * behaviour on a tool error was never verified, so this module does not claim
28
+ * after-on-error semantics.
29
+ *
30
+ * Fail-safe: every path is wrapped so an unexpected shape can never break the
31
+ * tool result the host is about to surface.
32
+ *
33
+ * @module pantheon/v2-execute-after
34
+ */
35
+
36
+ import {
37
+ type ContextSandboxConfig,
38
+ createContextSandbox,
39
+ resolveSandboxConfig,
40
+ DEFAULT_CONFIG as SANDBOX_DEFAULT_CONFIG,
41
+ } from './context-sandbox.ts'
42
+ import { createReadEnhancer } from './hashline/read-enhancer.ts'
43
+ import { createTaskResultGuard } from './task-result-guard.ts'
44
+
45
+ /** The `Tool.Result` fields this adapter reads and writes back. */
46
+ export interface V2ToolResult {
47
+ output?: unknown
48
+ content?: unknown
49
+ metadata?: unknown
50
+ }
51
+
52
+ /** The completed, non-error `execute.after` event as the 2.0.x host dispatches it. */
53
+ export interface V2ExecuteAfterEvent {
54
+ tool?: unknown
55
+ sessionID?: unknown
56
+ id?: unknown
57
+ input?: unknown
58
+ status?: unknown
59
+ result?: unknown
60
+ }
61
+
62
+ /** Options for {@link createV2ExecuteAfter}. */
63
+ export interface V2ExecuteAfterOptions {
64
+ /**
65
+ * Sandbox config, normally derived from `ctx.options.context_sandbox` via
66
+ * {@link resolveSandboxConfig}. Defaults to the shared defaults.
67
+ */
68
+ sandbox?: ContextSandboxConfig
69
+ }
70
+
71
+ /** Which `Tool.Result` field carries the (mutable) text this adapter enhances. */
72
+ type TextField = 'output' | 'content' | 'content-array'
73
+
74
+ /**
75
+ * Extract the mutable text from a `Tool.Result`.
76
+ *
77
+ * Preference order mirrors what a session renders: a declared string `output`
78
+ * first, then a string `content`, then the concatenation of `content` text
79
+ * parts. A `content` array containing non-text (file) parts is deliberately NOT
80
+ * enhanced — replacing it would corrupt the structured payload, so it is
81
+ * reported as unextractable and the whole event becomes a no-op.
82
+ *
83
+ * @returns `{ text, field }` or `null` when there is nothing to enhance.
84
+ */
85
+ function extractText(result: V2ToolResult): { text: string; field: TextField } | null {
86
+ if (typeof result.output === 'string') return { text: result.output, field: 'output' }
87
+ if (typeof result.content === 'string') return { text: result.content, field: 'content' }
88
+ if (Array.isArray(result.content)) {
89
+ const parts = result.content
90
+ const allText = parts.every(
91
+ (part) => typeof part === 'string' || (part as { type?: unknown })?.type === 'text',
92
+ )
93
+ if (!allText || parts.length === 0) return null
94
+ const text = parts
95
+ .map((part) => (typeof part === 'string' ? part : ((part as { text?: string }).text ?? '')))
96
+ .join('\n')
97
+ return { text, field: 'content-array' }
98
+ }
99
+ return null
100
+ }
101
+
102
+ /**
103
+ * Write enhanced text back into the SAME field it came from, preserving the
104
+ * result's other fields.
105
+ */
106
+ function writeText(result: V2ToolResult, field: TextField, text: string): void {
107
+ if (field === 'output') {
108
+ result.output = text
109
+ } else if (field === 'content') {
110
+ result.content = text
111
+ } else {
112
+ result.content = [{ type: 'text', text }]
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Build the V2 `execute.after` handler that runs the V1 parity chain.
118
+ *
119
+ * @param options - see {@link V2ExecuteAfterOptions}
120
+ * @returns a handler suitable for `ctx.tool.hook('execute.after', …)`
121
+ */
122
+ export function createV2ExecuteAfter(
123
+ options: V2ExecuteAfterOptions = {},
124
+ ): (event: unknown) => Promise<void> {
125
+ const taskResultGuard = createTaskResultGuard()
126
+ const sandbox = createContextSandbox(options.sandbox ?? SANDBOX_DEFAULT_CONFIG)
127
+ const readEnhancer = createReadEnhancer()
128
+
129
+ return async function v2ExecuteAfter(event: unknown): Promise<void> {
130
+ try {
131
+ if (event === null || typeof event !== 'object' || Array.isArray(event)) return
132
+ const payload = event as V2ExecuteAfterEvent
133
+ // Only a completed result has a result to augment; an error event is left
134
+ // untouched (no after-on-error semantics are claimed).
135
+ if (payload.status !== 'completed') return
136
+
137
+ const result = payload.result
138
+ if (result === null || typeof result !== 'object' || Array.isArray(result)) return
139
+
140
+ const tool = typeof payload.tool === 'string' ? payload.tool : ''
141
+ const sessionID = typeof payload.sessionID === 'string' ? payload.sessionID : ''
142
+ const callID = typeof payload.id === 'string' ? payload.id : ''
143
+
144
+ const extracted = extractText(result as V2ToolResult)
145
+ // A `task` result may carry no text at all — that IS the empty-result
146
+ // failure the guard exists to surface, so synthesize an empty string and
147
+ // let the guard replace it. Other tools with no text are a no-op.
148
+ if (extracted === null && tool !== 'task') return
149
+ const original = extracted === null ? { text: '', field: 'output' as TextField } : extracted
150
+
151
+ const input = { tool, sessionID, callID, args: payload.input }
152
+ const output: {
153
+ output: string
154
+ metadata?: Record<string, unknown>
155
+ } = { output: original.text }
156
+
157
+ await taskResultGuard(input, output)
158
+ await sandbox(input, output)
159
+ await readEnhancer(input, output)
160
+
161
+ if (output.output !== original.text) {
162
+ writeText(result as V2ToolResult, original.field, output.output)
163
+ }
164
+ // Surface sandbox metadata onto the result (merge, never replace). It is
165
+ // only present when the sandbox actually truncated something.
166
+ if (output.metadata && Object.keys(output.metadata).length > 0) {
167
+ ;(result as V2ToolResult).metadata = {
168
+ ...((result as V2ToolResult).metadata as Record<string, unknown> | undefined),
169
+ ...output.metadata,
170
+ }
171
+ }
172
+ } catch {
173
+ // Fail-safe: an unexpected shape must never break the tool result.
174
+ }
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Resolve a sandbox config from a V2 plugin's `ctx.options.context_sandbox`.
180
+ * Exposed so `plugin-v2.ts` resolves it the SAME way V1's config hook does,
181
+ * rather than silently constructing defaults.
182
+ */
183
+ export function resolveV2SandboxConfig(options: unknown): ContextSandboxConfig {
184
+ const raw = (options as { context_sandbox?: unknown } | null | undefined)?.context_sandbox
185
+ return resolveSandboxConfig(raw)
186
+ }
@@ -74,29 +74,6 @@ export const V2_UNSUPPORTED_FEATURE_SEED = Object.freeze([
74
74
  // pantheon_goal_create/get/update are therefore absent from the V2 surface
75
75
  // rather than registered as non-functional placeholders.
76
76
  'goal-tools',
77
- // Adapter limitation, not a host gap — TWO separate reasons, which are easy
78
- // to conflate and were once stated wrongly (see the v2EnforcementGuard
79
- // comment in src/plugin-v2.ts for the long form):
80
- // - MISSING WIRING: V2 keeps no session→agent map (the host puts the agent
81
- // on the execute.before event), so there is no `getSessionAgent` to pass.
82
- // The guard would then call isDelegationAllowed(undefined, target) → false
83
- // and throw "caller agent is unavailable" on EVERY task() call.
84
- // - UNWIRED HIERARCHY: V2 seeds no session→parent map anywhere, and an
85
- // unseeded SessionHierarchyRegistry gates exactly two things — the depth-2
86
- // child deny and the root-session gate. Unseeded, isChild is always false,
87
- // so neither can be trusted. UNWIRED, not impossible: the V2
88
- // `session.created` event carries `properties.info: Session` with
89
- // `parentID?`, the same field V1 seeds from on the same event, and one
90
- // line in onSessionCreated would seed live sessions. V1's OTHER source —
91
- // the client.session.list() startup seed for sessions predating plugin
92
- // load — has no V2 equivalent at all, because V2's PluginContext exposes
93
- // no `client`. Whether a live 2.0.22 host populates info.parentID on that
94
- // event is UNVERIFIED: the canary records Object.keys(ctx), not payloads.
95
- // Note the hierarchy is NOT what would deny: isRoot reports `true` for an
96
- // unknown session while unseeded, which PASSES the root gate.
97
- // Read-only depth-2 still holds via the blocked-tool list in the guard, so
98
- // this marker records an unenforced matrix, not an unenforced depth limit.
99
- 'delegation-matrix',
100
77
  ])
101
78
 
102
79
  /**