pantheon-opencode 1.5.0-beta.9 → 1.5.0

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 (82) hide show
  1. package/AGENTS.md +14 -63
  2. package/CHANGELOG.md +304 -0
  3. package/README.md +14 -27
  4. package/README.pt-BR.md +5 -6
  5. package/commands/pantheon.md +1 -35
  6. package/package.json +1 -2
  7. package/plugin.json +1 -1
  8. package/pyproject.toml +2 -5
  9. package/scripts/doctor.mjs +32 -3
  10. package/scripts/install/plugin.mjs +9 -12
  11. package/scripts/mcp_persistence_server.py +1 -1
  12. package/scripts/sync-tui.mjs +8 -11
  13. package/src/agents/apollo.md +1 -0
  14. package/src/agents/gaia.md +1 -0
  15. package/src/agents/zeus.md +9 -52
  16. package/src/instructions/memory-protocol.instructions.md +6 -3
  17. package/src/instructions/zeus-anti-stall.instructions.md +2 -4
  18. package/src/instructions/zeus-council-synthesis.instructions.md +3 -28
  19. package/src/instructions/zeus-timeout-retry.instructions.md +3 -28
  20. package/src/mcp/mcp_persistence_server.py +1 -1
  21. package/src/mcp/toon_codec.py +2 -4
  22. package/src/pantheon/background-job-board.ts +4 -4
  23. package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
  24. package/src/pantheon/cost-command.ts +1 -2
  25. package/src/pantheon/delegation-enforce.ts +59 -14
  26. package/src/pantheon/goal-loop.ts +1 -1
  27. package/src/pantheon/goal-store.ts +1 -1
  28. package/src/pantheon/idle-continuation.ts +4 -6
  29. package/src/pantheon/model-command.ts +1 -2
  30. package/src/pantheon/native-task-status.ts +2 -2
  31. package/src/pantheon/presets.d.mts +0 -20
  32. package/src/pantheon/presets.mjs +0 -64
  33. package/src/pantheon/session-guard.ts +2 -2
  34. package/src/pantheon/session-id.ts +30 -0
  35. package/src/pantheon/task-result-guard.ts +2 -3
  36. package/src/pantheon/todo-enforcer.ts +3 -3
  37. package/src/pantheon/tool-context.ts +23 -0
  38. package/src/pantheon/v2-bridge.ts +1 -4
  39. package/src/plugin-v2.ts +8 -21
  40. package/src/plugin.ts +61 -245
  41. package/src/plugins/pantheon-hooks.ts +1 -1
  42. package/src/plugins/tui/dist/tui.d.ts +212 -81
  43. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  44. package/src/plugins/tui/dist/tui.js +475 -254
  45. package/src/plugins/tui/package-lock.json +2 -2
  46. package/src/plugins/tui/package.json +2 -2
  47. package/src/plugins/tui/src/index.tsx +667 -269
  48. package/src/routing.yml +0 -16
  49. package/src/skills/context-compression/SKILL.md +1 -1
  50. package/scripts/generate-preset-docs.mjs +0 -300
  51. package/scripts/generate-prompts.sh +0 -222
  52. package/scripts/hash_verify.py +0 -192
  53. package/scripts/install/component.mjs +0 -24
  54. package/scripts/install/migrate-config.mjs +0 -302
  55. package/scripts/test-context-compression.sh +0 -169
  56. package/src/pantheon/auto-wake.ts +0 -85
  57. package/src/pantheon/chat-reminders.ts +0 -9
  58. package/src/pantheon/comment-checker.ts +0 -48
  59. package/src/pantheon/compaction-assert.ts +0 -160
  60. package/src/pantheon/compaction-handoff.ts +0 -304
  61. package/src/pantheon/cost-tracker.ts +0 -108
  62. package/src/pantheon/delegate-manager.ts +0 -766
  63. package/src/pantheon/delegation-classifier.ts +0 -307
  64. package/src/pantheon/delegation-finalize.ts +0 -406
  65. package/src/pantheon/delegation-notify.ts +0 -212
  66. package/src/pantheon/delegation-toast.ts +0 -73
  67. package/src/pantheon/delegation.ts +0 -1337
  68. package/src/pantheon/dispatch-guard.ts +0 -109
  69. package/src/pantheon/native-probe.ts +0 -234
  70. package/src/pantheon/permission-eval.ts +0 -284
  71. package/src/pantheon/preemptive-compact.ts +0 -76
  72. package/src/pantheon/provider-capabilities.ts +0 -221
  73. package/src/pantheon/provider-stream-tracker.ts +0 -182
  74. package/src/pantheon/retry-policy.ts +0 -212
  75. package/src/pantheon/step-cap.ts +0 -110
  76. package/src/pantheon/token-opt.ts +0 -689
  77. package/src/pantheon/toon-codec.ts +0 -481
  78. package/src/pantheon/v2-events.ts +0 -148
  79. package/src/pantheon/v2-hooks.ts +0 -287
  80. package/src/pantheon/v2-tool-definitions.ts +0 -331
  81. package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
  82. package/src/plugins/tui/dist/tui.tsx +0 -2853
@@ -1,406 +0,0 @@
1
- /**
2
- * Zeus Delegate With Retry (Wave 4, empty-mode fix) — helper for Zeus waves.
3
- *
4
- * Opencode 1.18.x cannot intercept `task()` completion via hooks, so the
5
- * `dispatch-guard` lib is NOT wired in the plugin. Zeus waves MUST use this
6
- * helper manually: it encapsulates
7
- *
8
- * delegate → waitForTerminal → classify via createDispatchGuard
9
- * → maybeRetry 1x on empty-mode1/mode2 → escalate
10
- *
11
- * Retry cap is HARD at 1 (never 2x). The guard is stateful per helper
12
- * instance — one retry budget total. Fail-open on missing APIs is NOT
13
- * applicable here: a failed delegate throws/log+escalate so the wave can
14
- * decide (a) retry with different agent, (b) simplify, (c) manual.
15
- *
16
- * Two usage shapes:
17
- * 1. High-level: `zeusDelegateWithRetry({ board, client, sessionID, agent, prompt })`
18
- * — creates child session, registers board, promptAsync, waitForTerminal,
19
- * reads report, classifies, retries once if empty, escalates if still empty.
20
- * 2. Low-level: `createZeusRetryHelper()` — wraps a generic
21
- * `() => Promise<DispatchResultLike>` pair via `executeWithRetry`.
22
- *
23
- * The high-level helper is the one Zeus waves should import:
24
- *
25
- * ```ts
26
- * import { zeusDelegateWithRetry } from './pantheon/zeus-delegate-with-retry.ts'
27
- * const { report, alias, taskID, retried, classification } =
28
- * await zeusDelegateWithRetry({ board, client, sessionID, agent: 'apollo', prompt })
29
- * ```
30
- *
31
- * If the result is empty after one retry, the helper THROWS with an
32
- * `EscalationError` — the wave must catch and escalate to the user (see
33
- * `src/agents/zeus.md` "Waves with Retry").
34
- *
35
- * Pure TypeScript — no direct SDK import. Board/client are structural.
36
- *
37
- * @module zeus-delegate-with-retry
38
- */
39
-
40
- import type { BackgroundJobBoard } from './background-job-board.ts'
41
- import type { DelegationClient } from './delegation-finalize.ts'
42
- import { DELEGATION_DEFAULTS, readDelegationReport } from './delegation-finalize.ts'
43
- import {
44
- createDispatchGuard,
45
- type DispatchClassification,
46
- type DispatchGuard,
47
- type DispatchGuardOptions,
48
- type DispatchResultLike,
49
- } from './dispatch-guard.ts'
50
- import { createPantheonLogger } from './logger.ts'
51
- import {
52
- type ProviderCooldownTracker,
53
- type RetryPolicy,
54
- RetryPolicyEngine,
55
- } from './retry-policy.ts'
56
- import { safeSessionPath } from './session-guard.ts'
57
-
58
- const log = createPantheonLogger({ module: 'pantheon-zeus-retry' })
59
-
60
- // ─── Types ───────────────────────────────────────────────────────────────
61
-
62
- export interface ZeusDelegateWithRetryOptions {
63
- /** Board singleton (same instance the plugin uses). */
64
- board: BackgroundJobBoard
65
- /** Delegation client (adapted SDK client, e.g. adaptDelegationClient). */
66
- client: DelegationClient
67
- /** Parent session that delegates (must be a valid ses_ id). */
68
- sessionID: string
69
- /** Target agent (e.g. "apollo", "hermes"). */
70
- agent: string
71
- /** Task prompt delivered to the background agent. */
72
- prompt: string
73
- /** Human-readable description shown on the job board. */
74
- description?: string
75
- /** Explicit model for the child session (provider/model). */
76
- model?: { id: string; providerID: string }
77
- /** Timeout for board.waitForTerminal (ms). Default: DELEGATION_DEFAULTS.readTimeoutMs */
78
- readTimeoutMs?: number
79
- /** Retry on empty results. Default: true */
80
- retryOnEmpty?: boolean
81
- /** R1: per-error-type retry policy (routing.yml `retry_policy`). */
82
- retryPolicy?: RetryPolicy
83
- /** R1: provider cooldown tracker (routing.yml `cooldown`). */
84
- cooldown?: ProviderCooldownTracker
85
- /** R1: provider name for cooldown tracking (default "default"). */
86
- provider?: string
87
- /** Injectable logger. Default: file log (PANTHEON_HOOKS_LOG gate). */
88
- logger?: { warn: (message: string) => void; info?: (message: string) => void }
89
- /** Output dir for delegation reports. Default: DELEGATION_DEFAULTS.outputDir */
90
- outputDir?: string
91
- }
92
-
93
- export interface ZeusDelegateResult {
94
- /** Job alias (e.g. "apo-1"). */
95
- alias: string
96
- /** Child session id (= board task id). */
97
- taskID: string
98
- /** Report markdown (with Agent Activity section when sampled). */
99
- report: string
100
- /** Classification of the final result. */
101
- classification: DispatchClassification
102
- /** True when a retry was fired (cap 1). */
103
- retried: boolean
104
- }
105
-
106
- /**
107
- * Thrown when a delegate returns empty even after one retry — the wave must
108
- * escalate (try different agent, simplify scope, or manual intervention).
109
- */
110
- export class ZeusEscalationError extends Error {
111
- constructor(
112
- public readonly agent: string,
113
- public readonly prompt: string,
114
- public readonly classification: DispatchClassification,
115
- ) {
116
- super(
117
- `Zeus delegate escalate: agent "${agent}" returned ${classification} even after retry (prompt: "${prompt.slice(0, 80)}") — escalate to user: (a) try different agent, (b) simplify scope, (c) manual`,
118
- )
119
- this.name = 'ZeusEscalationError'
120
- }
121
- }
122
-
123
- // ─── Low-level helper — wraps any async delegate+read pair ───────────────
124
-
125
- /**
126
- * Low-level retry helper. Zeus waves that already have a `delegate+read`
127
- * pair can use this directly without the board/client machinery:
128
- *
129
- * ```ts
130
- * const guard = createZeusRetryHelper({ logger })
131
- * const out = await guard.executeWithRetry(
132
- * () => firstRead(),
133
- * () => secondRead(),
134
- * )
135
- * if (out.escalate) throw new ZeusEscalationError(...)
136
- * ```
137
- */
138
- export interface ZeusRetryHelper {
139
- guard: DispatchGuard
140
- /**
141
- * Classify `first` and, if empty and budget allows, run `redispatch` once.
142
- * Returns the final result plus escalation flag (true when still empty after
143
- * the one allowed retry).
144
- */
145
- executeWithRetry(
146
- first: DispatchResultLike,
147
- redispatch: () => Promise<DispatchResultLike>,
148
- ): Promise<{
149
- result: DispatchResultLike
150
- classification: DispatchClassification
151
- retried: boolean
152
- escalate: boolean
153
- }>
154
- }
155
-
156
- export interface ZeusRetryHelperOptions extends DispatchGuardOptions {
157
- /**
158
- * R1: per-error-type retry policy. When provided, error-type retries with
159
- * exponential backoff apply (auth → 0, rate_limit → N, ...); when absent
160
- * the legacy hard-cap-1 empty-result retry applies.
161
- */
162
- retryPolicy?: RetryPolicy
163
- /** R1: provider cooldown tracker (shared across helpers for global cooldown). */
164
- cooldown?: ProviderCooldownTracker
165
- /** R1: provider name for cooldown tracking (default "default"). */
166
- provider?: string
167
- /** R1: injectable sleep for tests (default: setTimeout promise). */
168
- sleep?: (ms: number) => Promise<void>
169
- }
170
-
171
- export function createZeusRetryHelper(options?: ZeusRetryHelperOptions): ZeusRetryHelper {
172
- const guard = createDispatchGuard(options)
173
- const engine =
174
- options?.retryPolicy !== undefined
175
- ? new RetryPolicyEngine(options.retryPolicy, options.cooldown)
176
- : undefined
177
- const provider = options?.provider ?? 'default'
178
- const sleep =
179
- options?.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)))
180
- return {
181
- guard,
182
- async executeWithRetry(first, redispatch) {
183
- if (engine === undefined) {
184
- // Legacy path (unchanged): hard cap 1 on empty results.
185
- const out = await guard.maybeRetry(first, redispatch)
186
- const escalate = out.classification !== 'content'
187
- return {
188
- result: out.result,
189
- classification: out.classification,
190
- retried: out.retried,
191
- escalate,
192
- }
193
- }
194
- // R1 path: classify the failure, apply the per-error-type policy with
195
- // exponential backoff, and track provider cooldown. A content result
196
- // records success (resets the provider's failure counter); an
197
- // exhausted budget records failure (feeds the cooldown tracker).
198
- let result = first
199
- let attemptsUsed = 0
200
- let retried = false
201
- let classification = guard.classifyResult(result)
202
- let lastError: unknown =
203
- result.error ??
204
- (classification === 'content' ? undefined : new Error('empty dispatch result'))
205
- for (;;) {
206
- if (classification === 'content') {
207
- engine.recordSuccess(provider)
208
- return { result, classification, retried, escalate: false }
209
- }
210
- lastError = result.error ?? lastError
211
- const decision = engine.decide(provider, lastError, attemptsUsed)
212
- if (!decision.shouldRetry) {
213
- engine.recordFailure(provider, lastError)
214
- return { result, classification, retried, escalate: true }
215
- }
216
- retried = true
217
- attemptsUsed += 1
218
- await sleep(decision.delayMs)
219
- try {
220
- result = await redispatch()
221
- lastError = undefined
222
- } catch (err: unknown) {
223
- lastError = err
224
- }
225
- classification = guard.classifyResult(result)
226
- }
227
- },
228
- }
229
- }
230
-
231
- // ─── High-level helper — full delegate → wait → read → retry → escalate ───
232
-
233
- /**
234
- * High-level helper Zeus waves MUST use (see `src/agents/zeus.md`).
235
- * Delegates a background agent as a child session, waits for terminal,
236
- * classifies via `createDispatchGuard`, retries ONCE on empty-mode1/mode2,
237
- * and escalates (throws `ZeusEscalationError`) if still empty.
238
- *
239
- * Not wired automatically in the plugin — opencode 1.18.x cannot intercept
240
- * task completion via hooks, so wiring is manual-orchestration only.
241
- */
242
- export async function zeusDelegateWithRetry(
243
- opts: ZeusDelegateWithRetryOptions,
244
- ): Promise<ZeusDelegateResult> {
245
- const logger = opts.logger ?? log
246
- const warn = logger.warn.bind(logger)
247
- const board = opts.board
248
- const client = opts.client
249
- const sessionID = opts.sessionID
250
-
251
- const sessionPath = safeSessionPath(sessionID)
252
- if (!sessionPath) {
253
- warn(`zeusDelegateWithRetry: invalid sessionID "${sessionID}" — aborting`)
254
- throw new Error(`invalid sessionID: ${sessionID}`)
255
- }
256
-
257
- const readTimeoutMs = opts.readTimeoutMs ?? DELEGATION_DEFAULTS.readTimeoutMs
258
- const outputDir = opts.outputDir ?? DELEGATION_DEFAULTS.outputDir
259
- const retryHelper = createZeusRetryHelper({
260
- retryOnEmpty: opts.retryOnEmpty ?? true,
261
- logger,
262
- ...(opts.retryPolicy !== undefined ? { retryPolicy: opts.retryPolicy } : {}),
263
- ...(opts.cooldown !== undefined ? { cooldown: opts.cooldown } : {}),
264
- ...(opts.provider !== undefined ? { provider: opts.provider } : {}),
265
- })
266
-
267
- // One delegate+read attempt. Factored so maybeRetry can invoke a second
268
- // attempt with a fresh child session + board job.
269
- async function runOnce(): Promise<{
270
- alias: string
271
- taskID: string
272
- report: string
273
- dispatchResult: DispatchResultLike
274
- }> {
275
- // 1. Create child session (parentID = caller). When a model is resolved,
276
- // the same model is forwarded to session.create and promptAsync; otherwise
277
- // both requests omit it so the host keeps native model inheritance.
278
- let childID: string
279
- try {
280
- const created = await client.session.create({
281
- body: {
282
- parentID: sessionID,
283
- title: opts.description ?? opts.prompt.slice(0, 80),
284
- ...(opts.model !== undefined ? { model: opts.model } : {}),
285
- },
286
- })
287
- childID = created.id
288
- } catch (err: unknown) {
289
- const reason = err instanceof Error ? err.message : String(err)
290
- warn(`zeusDelegateWithRetry: session.create failed: ${reason}`)
291
- throw new Error(`delegate session.create failed: ${reason}`)
292
- }
293
-
294
- const childPath = safeSessionPath(childID)
295
- if (!childPath) {
296
- warn(`zeusDelegateWithRetry: child sessionID invalid "${childID}"`)
297
- throw new Error(`invalid child sessionID: ${childID}`)
298
- }
299
-
300
- // 2. Register on board (taskID == child session id)
301
- const job = await board.registerLaunch({
302
- taskID: childID,
303
- parentSessionID: sessionID,
304
- agent: opts.agent,
305
- description: opts.description ?? opts.prompt,
306
- objective: opts.prompt,
307
- })
308
-
309
- // 3. Fire-and-forget promptAsync on the child. Completion is observed via
310
- // board.waitForTerminal, not via noReply — the spike refuted noReply.
311
- // A promptAsync failure is logged but does not abort the wait — the board
312
- // timeout will finalize the job.
313
- const promptBody = {
314
- agent: opts.agent,
315
- ...(opts.model !== undefined ? { model: opts.model } : {}),
316
- parts: [{ type: 'text' as const, text: opts.prompt }],
317
- }
318
- void client.session
319
- .promptAsync({
320
- path: childPath.path,
321
- body: promptBody,
322
- })
323
- .catch((err: unknown) => {
324
- const reason = err instanceof Error ? err.message : String(err)
325
- warn(`zeusDelegateWithRetry: promptAsync failed for ${childID}: ${reason}`)
326
- })
327
-
328
- // 4. Wait for terminal state
329
- let terminal: Awaited<ReturnType<BackgroundJobBoard['waitForTerminal']>>
330
- try {
331
- terminal = await board.waitForTerminal(childID, readTimeoutMs)
332
- } catch {
333
- // Timed out waiting — treat as empty so the guard retries once
334
- return {
335
- alias: job.alias,
336
- taskID: childID,
337
- report: '',
338
- dispatchResult: { content: '' },
339
- }
340
- }
341
-
342
- // 5. Read report markdown
343
- const report = (await readDelegationReport(outputDir, terminal)) ?? ''
344
-
345
- // DispatchResultLike for classification: content = report markdown.
346
- // Tokens are unavailable from the board/report path → mode1 when empty.
347
- const dispatchResult: DispatchResultLike = { content: report }
348
-
349
- return { alias: job.alias, taskID: childID, report, dispatchResult }
350
- }
351
-
352
- // First attempt
353
- const first = await runOnce()
354
-
355
- // Classify + maybe retry once — capture second run's metadata via closure
356
- let secondRun: typeof first | undefined
357
- const out = await retryHelper.executeWithRetry(first.dispatchResult, async () => {
358
- const second = await runOnce()
359
- secondRun = second
360
- return second.dispatchResult
361
- })
362
-
363
- // When retried, return second run's report or escalate
364
- if (out.retried && secondRun !== undefined) {
365
- if (out.classification === 'content') {
366
- return {
367
- alias: secondRun.alias,
368
- taskID: secondRun.taskID,
369
- report: secondRun.report,
370
- classification: out.classification,
371
- retried: true,
372
- }
373
- }
374
- // Still empty after retry → escalate
375
- throw new ZeusEscalationError(opts.agent, opts.prompt, out.classification)
376
- }
377
-
378
- // No retry, or first was content
379
- if (out.classification !== 'content') {
380
- // Empty and retry budget exhausted (or retryOnEmpty:false) → escalate
381
- // But if we already retried and failed we threw above; this path is for
382
- // the case where maybeRetry did NOT retry (budget exhausted or disabled)
383
- // and the first result was empty. Per spec, one retry is allowed — if
384
- // maybeRetry didn't retry, we should NOT auto-escalate here unless the
385
- // guard's budget was used. Instead, treat as final empty that still
386
- // requires escalation when retried==false but classification empty and
387
- // retryOnEmpty was true but guard had no budget left? The guard already
388
- // enforces cap 1, so after one retry escalate is required. For the
389
- // first-attempt-empty with no retry (retryOnEmpty false), we still escalate
390
- // so the wave knows to handle it.
391
- if (out.retried) {
392
- throw new ZeusEscalationError(opts.agent, opts.prompt, out.classification)
393
- }
394
- // No retry happened — still empty. Escalate so the wave doesn't silently
395
- // use an empty report.
396
- throw new ZeusEscalationError(opts.agent, opts.prompt, out.classification)
397
- }
398
-
399
- return {
400
- alias: first.alias,
401
- taskID: first.taskID,
402
- report: first.report,
403
- classification: out.classification,
404
- retried: out.retried,
405
- }
406
- }