pantheon-opencode 1.5.0-beta.13 → 1.5.0-beta.15

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 (42) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
  4. package/pyproject.toml +2 -5
  5. package/src/agents/zeus.md +4 -26
  6. package/src/pantheon/background-job-board.ts +4 -4
  7. package/src/pantheon/delegate-manager.ts +95 -10
  8. package/src/pantheon/delegation-classifier.ts +3 -4
  9. package/src/pantheon/delegation-finalize.ts +69 -7
  10. package/src/pantheon/delegation.ts +16 -4
  11. package/src/pantheon/presets.d.mts +0 -20
  12. package/src/pantheon/presets.mjs +0 -64
  13. package/src/pantheon/toon-codec.ts +1 -1
  14. package/src/plugin-v2.ts +4 -6
  15. package/src/plugin.ts +6 -6
  16. package/src/plugins/pantheon-hooks.ts +1 -1
  17. package/src/plugins/tui/dist/tui.d.ts +57 -40
  18. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  19. package/src/plugins/tui/dist/tui.js +145 -54
  20. package/src/plugins/tui/package-lock.json +2 -2
  21. package/src/plugins/tui/package.json +1 -1
  22. package/src/plugins/tui/src/index.tsx +245 -83
  23. package/src/routing.yml +0 -16
  24. package/scripts/hash_verify.py +0 -192
  25. package/scripts/test-context-compression.sh +0 -169
  26. package/src/pantheon/auto-wake.ts +0 -85
  27. package/src/pantheon/chat-reminders.ts +0 -9
  28. package/src/pantheon/comment-checker.ts +0 -48
  29. package/src/pantheon/compaction-assert.ts +0 -160
  30. package/src/pantheon/compaction-handoff.ts +0 -304
  31. package/src/pantheon/cost-tracker.ts +0 -108
  32. package/src/pantheon/dispatch-guard.ts +0 -109
  33. package/src/pantheon/permission-eval.ts +0 -284
  34. package/src/pantheon/preemptive-compact.ts +0 -76
  35. package/src/pantheon/provider-capabilities.ts +0 -221
  36. package/src/pantheon/provider-stream-tracker.ts +0 -182
  37. package/src/pantheon/retry-policy.ts +0 -212
  38. package/src/pantheon/token-opt.ts +0 -689
  39. package/src/pantheon/v2-events.ts +0 -148
  40. package/src/pantheon/v2-hooks.ts +0 -287
  41. package/src/pantheon/v2-tool-definitions.ts +0 -331
  42. package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
@@ -1,148 +0,0 @@
1
- /**
2
- * V2 Event Handlers — Pantheon event subscriptions for the V2 plugin API.
3
- *
4
- * Subscribes to session lifecycle events (created, idle, error, compacted)
5
- * and routes them to the appropriate subsystem:
6
- * - session.created → seed session hierarchy + root sessions
7
- * - session.idle → delegation finalize / goal loop / todo enforcer
8
- * - session.error → delegation finalize as error
9
- * - session.compacted → todo preserve / compaction context
10
- *
11
- * These handlers are PURE — they depend only on structural interfaces,
12
- * not on the V1 PluginInput or V2 PluginContext.
13
- *
14
- * @module pantheon/v2-events
15
- */
16
-
17
- import type { BackgroundJobBoard } from './background-job-board.ts'
18
- import type { FinalizeInput } from './delegation-finalize.ts'
19
- import type { DelegationEventDeps, DelegationEventLike } from './delegation-notify.ts'
20
- import { handleDelegationEvent } from './delegation-notify.ts'
21
- import type { GoalLoopLike, TodoEnforcerLike } from './idle-continuation.ts'
22
- import { createIdleDispatcher, type IdleDispatcher } from './idle-continuation.ts'
23
- import { createPantheonLogger } from './logger.ts'
24
-
25
- const log = createPantheonLogger({ module: 'pantheon-v2-events' })
26
-
27
- // ─── Types ───────────────────────────────────────────────────────────────
28
-
29
- /**
30
- * Structural session info from session.created events.
31
- */
32
- export interface SessionInfo {
33
- id: string
34
- parentID?: string
35
- }
36
-
37
- /**
38
- * Structural event from the V2 event stream.
39
- */
40
- export interface V2SessionEvent {
41
- type: string
42
- properties?: Record<string, unknown>
43
- }
44
-
45
- /**
46
- * Dependencies for the V2 event dispatcher.
47
- */
48
- export interface V2EventDeps {
49
- board: BackgroundJobBoard
50
- finalize: (childSessionID: string, opts: FinalizeInput) => Promise<unknown>
51
- goalLoop: GoalLoopLike
52
- todoEnforcer: TodoEnforcerLike
53
- /** Callback to register a session in the hierarchy. */
54
- registerSession?: (info: SessionInfo) => void
55
- /** Callback to add a session as root. */
56
- addRootSession?: (sessionID: string) => void
57
- }
58
-
59
- /**
60
- * V2 Event Dispatcher — routes session events to subsystems.
61
- */
62
- export interface V2EventDispatcher {
63
- /** Process a session event. Returns true if handled. */
64
- handleEvent(event: V2SessionEvent): Promise<boolean>
65
- /** Cleanup subscriptions. */
66
- dispose(): void
67
- }
68
-
69
- // ─── Implementation ──────────────────────────────────────────────────────
70
-
71
- /**
72
- * Create the V2 event dispatcher. Routes session lifecycle events to
73
- * delegation finalize, goal loop, and todo enforcer.
74
- *
75
- * Usage:
76
- * ```ts
77
- * const dispatcher = createV2EventDispatcher(deps)
78
- * // In event subscription loop:
79
- * for await (const event of ctx.event.subscribe(...)) {
80
- * await dispatcher.handleEvent(event)
81
- * }
82
- * ```
83
- */
84
- export function createV2EventDispatcher(deps: V2EventDeps): V2EventDispatcher {
85
- const delegationDeps: DelegationEventDeps = {
86
- board: deps.board,
87
- finalize: deps.finalize,
88
- }
89
- const idleDispatcher: IdleDispatcher = createIdleDispatcher({
90
- goalLoop: deps.goalLoop,
91
- todoEnforcer: deps.todoEnforcer,
92
- })
93
-
94
- return {
95
- handleEvent: async (event: V2SessionEvent): Promise<boolean> => {
96
- try {
97
- // session.created → seed hierarchy + board put-if-absent for native children
98
- if (event.type === 'session.created') {
99
- const info = event.properties?.info as SessionInfo | undefined
100
- if (info) {
101
- deps.registerSession?.(info)
102
- if (info.parentID === undefined) {
103
- deps.addRootSession?.(info.id)
104
- } else {
105
- // Native task(background=true) child: register on the board
106
- // atomically (skip if V1 idle hook or re-dispatch already claimed it).
107
- try {
108
- await deps.board.registerLaunchIfAbsent({
109
- taskID: info.id,
110
- parentSessionID: info.parentID,
111
- agent: 'native',
112
- description: `native child ${info.id}`,
113
- })
114
- } catch (err) {
115
- // The per-agent concurrency limit can refuse the launch. Do not
116
- // abort the rest of the handler: this child is simply not
117
- // mirrored on the board, while finalize/idle dispatch for every
118
- // other event must still run.
119
- log.warn(`[Pantheon V2] native child ${info.id} not mirrored on board:`, err)
120
- }
121
- }
122
- }
123
- }
124
-
125
- // Delegate to handleDelegationEvent (covers idle + error for board children)
126
- const delegated = await handleDelegationEvent(event as DelegationEventLike, delegationDeps)
127
-
128
- // Non-board idle → idle dispatcher (goal loop / todo enforcer)
129
- if (!delegated && event.type === 'session.idle') {
130
- const sessionID = event.properties?.sessionID as string | undefined
131
- if (sessionID) {
132
- await idleDispatcher.onIdle(sessionID)
133
- }
134
- }
135
-
136
- return delegated
137
- } catch (err) {
138
- log.error('[Pantheon V2] Event handling failed:', err)
139
- return false
140
- }
141
- },
142
-
143
- dispose: () => {
144
- // No persistent subscriptions to clean up — the event stream
145
- // lifecycle is managed by the caller.
146
- },
147
- }
148
- }
@@ -1,287 +0,0 @@
1
- /**
2
- * V2 Session Hooks — Pantheon session hooks for the V2 plugin API.
3
- *
4
- * Provides two hook types:
5
- * - `"prompt"` hook: Vision message interception (paste-and-ask). Intercepts
6
- * user messages with pasted images and replaces them with text instructions
7
- * or native vision descriptions.
8
- * - `"context"` hook: Inject routing policy, active goals, and pending todos
9
- * into the system context before each LLM call.
10
- *
11
- * These handlers are PURE — they depend only on structural interfaces.
12
- *
13
- * @module pantheon/v2-hooks
14
- */
15
-
16
- import type { BackgroundJobBoard } from './background-job-board.ts'
17
- import { buildCompactionContext } from './delegation-compaction.ts'
18
- import type { GoalStore } from './goal-loop.ts'
19
- import { createPantheonLogger } from './logger.ts'
20
- import type { TodoEnforcer } from './todo-enforcer.ts'
21
-
22
- const log = createPantheonLogger({ module: 'pantheon-v2-hooks' })
23
-
24
- // ─── Types ───────────────────────────────────────────────────────────────
25
-
26
- /**
27
- * Structural context event passed to the "context" hook.
28
- * V2 API: `ctx.session.hook("context", (event) => { ... })`
29
- */
30
- export interface V2ContextHookEvent {
31
- sessionID: string
32
- // Unknown: host beta may send string, string[], SystemPart objects
33
- // ({type,text}), mixed arrays, or non-array. Narrow internally (fail-open).
34
- system: unknown
35
- generation: {
36
- temperature?: number
37
- }
38
- }
39
-
40
- /**
41
- * Structural prompt event passed to the "prompt" hook.
42
- * V2 API: `ctx.session.hook("prompt", (event) => { ... })`
43
- */
44
- export interface V2PromptHookEvent {
45
- sessionID: string
46
- message: unknown
47
- }
48
-
49
- /**
50
- * Dependencies for session hooks.
51
- */
52
- export interface V2HookDeps {
53
- board: BackgroundJobBoard
54
- goalStore: GoalStore
55
- todoEnforcer: TodoEnforcer
56
- }
57
-
58
- // ─── Context Hook ────────────────────────────────────────────────────────
59
-
60
- const POLICY_MARKER = '<!-- pantheon-v2-policy -->'
61
- const ROUTING_POLICY =
62
- `${POLICY_MARKER}\nFollow Pantheon routing policy: delegate implementation work ` +
63
- `to the named specialist and do not claim work was performed without verification.`
64
-
65
- /** Default max compaction items (matching routing.yml background_delegation.max_compaction_items). */
66
- const DEFAULT_MAX_COMPACTION_ITEMS = 10
67
-
68
- /**
69
- * Extract comparable text from a system entry: plain string or
70
- * SystemPart-like object ({type,text}). Returns null for junk (null/number).
71
- * Runtime sniffing: pinned SDK (@opencode-ai/plugin 1.18.18) only knows
72
- * string/string[] — host beta sends objects. Never throws (fail-open).
73
- */
74
- function systemEntryText(entry: unknown): string | null {
75
- try {
76
- if (typeof entry === 'string') return entry
77
- if (
78
- typeof entry === 'object' &&
79
- entry !== null &&
80
- typeof (entry as { text?: unknown }).text === 'string'
81
- ) {
82
- return (entry as { text: string }).text
83
- }
84
- return null
85
- } catch {
86
- return null
87
- }
88
- }
89
-
90
- function hasPolicyMarker(arr: unknown[]): boolean {
91
- try {
92
- return arr.some((s) => {
93
- const t = systemEntryText(s)
94
- return t?.includes(POLICY_MARKER) ?? false
95
- })
96
- } catch {
97
- return false
98
- }
99
- }
100
-
101
- function usesObjectShape(arr: unknown[]): boolean {
102
- try {
103
- return arr.some(
104
- (s) =>
105
- typeof s === 'object' && s !== null && typeof (s as { text?: unknown }).text === 'string',
106
- )
107
- } catch {
108
- return false
109
- }
110
- }
111
-
112
- function toSystemEntry(text: string, useObject: boolean): string | { type: 'text'; text: string } {
113
- return useObject ? { type: 'text', text } : text
114
- }
115
-
116
- /**
117
- * Context hook handler — injects Pantheon policy, routing, and compaction
118
- * state into the system context before each LLM call.
119
- *
120
- * Registered via: `ctx.session.hook("context", handler)`
121
- */
122
- export function createV2ContextHookHandler(deps: V2HookDeps) {
123
- return async (event: V2ContextHookEvent): Promise<void> => {
124
- try {
125
- // Beta 19192 hardening (defense-in-depth) + SystemPart sniffing: the
126
- // host may send string[], SystemPart objects ({type,text}), mixed
127
- // arrays, a single string, or a non-array system. Preserve the string
128
- // path behavior; inject object shape when the array contains objects
129
- // (else host schema validation fails on system[N]). Never throw.
130
- const rawSystem: unknown = (event as { system?: unknown } | null | undefined)?.system
131
- let system: unknown[]
132
- if (typeof rawSystem === 'string') {
133
- system = rawSystem.includes(POLICY_MARKER) ? [rawSystem] : [rawSystem, ROUTING_POLICY]
134
- ;(event as { system?: unknown }).system = system
135
- } else if (Array.isArray(rawSystem)) {
136
- // 1. Inject routing policy if not already present (string OU .text)
137
- if (!hasPolicyMarker(rawSystem)) {
138
- rawSystem.push(toSystemEntry(ROUTING_POLICY, usesObjectShape(rawSystem)))
139
- }
140
- system = rawSystem
141
- } else {
142
- // Non-string, non-array system: ignore silently (fail-open).
143
- return
144
- }
145
-
146
- // 2. Inject compaction context (active goals, pending todos, delegations)
147
- // Convert blocks to the same shape as the host array (object vs string).
148
- const blocks = await buildCompactionContext(deps.board, {
149
- sessionID: event.sessionID,
150
- maxItems: DEFAULT_MAX_COMPACTION_ITEMS,
151
- goals: {
152
- enabled: true,
153
- list: (sessionID: string) => deps.goalStore.list(sessionID),
154
- },
155
- todos: {
156
- enabled: true,
157
- list: (sessionID: string) => deps.todoEnforcer.listPendingTodos(sessionID),
158
- },
159
- })
160
- if (blocks.length > 0) {
161
- const useObject = usesObjectShape(system)
162
- for (const b of blocks) {
163
- system.push(useObject ? { type: 'text' as const, text: b } : b)
164
- }
165
- }
166
-
167
- // 3. Apply conservative temperature for routing decisions
168
- const generation = (event as { generation?: { temperature?: number } }).generation
169
- if (generation != null && generation.temperature === undefined) {
170
- generation.temperature = 0.2
171
- }
172
- } catch (err) {
173
- log.warn('[Pantheon V2] Context hook failed:', err)
174
- // Fail-open: never break the session
175
- }
176
- }
177
- }
178
-
179
- // ─── Prompt Hook ─────────────────────────────────────────────────────────
180
-
181
- /**
182
- * Prompt hook handler — intercepts user messages for vision processing.
183
- *
184
- * Registered via: `ctx.session.hook("prompt", handler)`
185
- *
186
- * Note: The full vision handler is complex (file I/O, provider API calls).
187
- * This is a minimal adapter that delegates to the V1 vision handler
188
- * if available. Without vision infrastructure, it's a no-op.
189
- */
190
- export function createV2PromptHookHandler(_deps: V2HookDeps) {
191
- return async (event: V2PromptHookEvent): Promise<void> => {
192
- // Vision interception is a complex subsystem (see src/pantheon/vision.ts).
193
- // The V2 prompt hook delegates to the V1 vision handler via the plugin's
194
- // integration point. Without V1 client access, this is a graceful no-op.
195
- //
196
- // TODO: Wire full vision handler when V2 provides SDK client access.
197
- void event
198
- }
199
- }
200
-
201
- // ─── Tool Hooks ──────────────────────────────────────────────────────────
202
-
203
- /**
204
- * Tool execute.before hook — enforces read-only restrictions, command
205
- * normalization, and todo preservation.
206
- *
207
- * Registered via: `ctx.tool.hook("execute.before", handler)`
208
- */
209
- export interface V2ToolBeforeEvent {
210
- tool: string
211
- sessionID: string
212
- callID: string
213
- args?: unknown
214
- }
215
-
216
- export function createV2ToolBeforeHookHandler(
217
- enforcementGuard: (
218
- input: { tool: string; sessionID: string; callID: string },
219
- output?: { args?: unknown },
220
- ) => void,
221
- commandNormalizer: (
222
- input: { tool: string; sessionID: string; callID: string },
223
- output?: { args?: unknown },
224
- ) => Promise<void>,
225
- ) {
226
- return async (event: V2ToolBeforeEvent): Promise<void> => {
227
- try {
228
- enforcementGuard(
229
- { tool: event.tool, sessionID: event.sessionID, callID: event.callID },
230
- { args: event.args },
231
- )
232
- await commandNormalizer(
233
- { tool: event.tool, sessionID: event.sessionID, callID: event.callID },
234
- { args: event.args },
235
- )
236
- } catch (err) {
237
- log.warn('[Pantheon V2] Tool before-hook failed:', err)
238
- }
239
- }
240
- }
241
-
242
- /**
243
- * Tool execute.after hook — augments read output with hashline tags
244
- * and applies context sandbox truncation.
245
- *
246
- * Registered via: `ctx.tool.hook("execute.after", handler)`
247
- */
248
- export interface V2ToolAfterEvent {
249
- tool: string
250
- sessionID: string
251
- callID: string
252
- args?: unknown
253
- // The host may send a non-string output (shape drift on beta). Keep this
254
- // honest as unknown and narrow with typeof at the use site — never assume.
255
- output: unknown
256
- }
257
-
258
- export function createV2ToolAfterHookHandler(
259
- readEnhancer: (
260
- input: { tool: string; sessionID: string; callID: string; args?: unknown },
261
- output: { output: string },
262
- ) => Promise<void>,
263
- sandboxHandler: (
264
- input: { tool: string; sessionID: string; callID: string; args?: unknown },
265
- output: { output: string },
266
- ) => Promise<void>,
267
- ) {
268
- return async (event: V2ToolAfterEvent): Promise<void> => {
269
- try {
270
- const input = {
271
- tool: event.tool,
272
- sessionID: event.sessionID,
273
- callID: event.callID,
274
- args: event.args,
275
- }
276
- // Narrow the honest unknown: only strings flow through the enhancers.
277
- // Non-string output is left untouched (fail-open, preserve host shape).
278
- if (typeof event.output !== 'string') return
279
- const output = { output: event.output }
280
- await sandboxHandler(input, output)
281
- await readEnhancer(input, output)
282
- event.output = output.output
283
- } catch (err) {
284
- log.warn('[Pantheon V2] Tool after-hook failed:', err)
285
- }
286
- }
287
- }