pantheon-opencode 1.5.0-beta.8 → 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.
- package/AGENTS.md +14 -63
- package/CHANGELOG.md +320 -0
- package/README.md +14 -27
- package/README.pt-BR.md +5 -6
- package/commands/pantheon.md +1 -35
- package/package.json +1 -2
- package/plugin.json +1 -1
- package/pyproject.toml +2 -5
- package/scripts/doctor.mjs +32 -3
- package/scripts/install/plugin.mjs +9 -12
- package/scripts/mcp_persistence_server.py +1 -1
- package/scripts/sync-tui.mjs +8 -11
- package/src/agents/apollo.md +1 -0
- package/src/agents/gaia.md +1 -0
- package/src/agents/zeus.md +9 -52
- package/src/instructions/memory-protocol.instructions.md +6 -3
- package/src/instructions/zeus-anti-stall.instructions.md +2 -4
- package/src/instructions/zeus-council-synthesis.instructions.md +3 -28
- package/src/instructions/zeus-timeout-retry.instructions.md +3 -28
- package/src/mcp/mcp_persistence_server.py +1 -1
- package/src/mcp/toon_codec.py +2 -4
- package/src/pantheon/background-job-board.ts +4 -4
- package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
- package/src/pantheon/cost-command.ts +1 -2
- package/src/pantheon/delegation-enforce.ts +59 -14
- package/src/pantheon/goal-loop.ts +1 -1
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/idle-continuation.ts +4 -6
- package/src/pantheon/model-command.ts +1 -2
- package/src/pantheon/native-task-status.ts +2 -2
- package/src/pantheon/presets.d.mts +0 -20
- package/src/pantheon/presets.mjs +0 -64
- package/src/pantheon/session-guard.ts +2 -2
- package/src/pantheon/session-id.ts +30 -0
- package/src/pantheon/task-result-guard.ts +2 -3
- package/src/pantheon/todo-enforcer.ts +3 -3
- package/src/pantheon/tool-context.ts +23 -0
- package/src/pantheon/v2-bridge.ts +1 -4
- package/src/plugin-v2.ts +8 -21
- package/src/plugin.ts +61 -245
- package/src/plugins/pantheon-hooks.ts +1 -1
- package/src/plugins/tui/dist/tui.d.ts +229 -77
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +509 -259
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +2 -2
- package/src/plugins/tui/src/index.tsx +753 -279
- package/src/routing.yml +0 -16
- package/src/skills/context-compression/SKILL.md +1 -1
- package/scripts/generate-preset-docs.mjs +0 -300
- package/scripts/generate-prompts.sh +0 -222
- package/scripts/hash_verify.py +0 -192
- package/scripts/install/component.mjs +0 -24
- package/scripts/install/migrate-config.mjs +0 -302
- package/scripts/test-context-compression.sh +0 -169
- package/src/pantheon/auto-wake.ts +0 -85
- package/src/pantheon/chat-reminders.ts +0 -9
- package/src/pantheon/comment-checker.ts +0 -48
- package/src/pantheon/compaction-assert.ts +0 -160
- package/src/pantheon/compaction-handoff.ts +0 -304
- package/src/pantheon/cost-tracker.ts +0 -108
- package/src/pantheon/delegate-manager.ts +0 -766
- package/src/pantheon/delegation-classifier.ts +0 -307
- package/src/pantheon/delegation-finalize.ts +0 -406
- package/src/pantheon/delegation-notify.ts +0 -212
- package/src/pantheon/delegation-toast.ts +0 -73
- package/src/pantheon/delegation.ts +0 -1337
- package/src/pantheon/dispatch-guard.ts +0 -109
- package/src/pantheon/native-probe.ts +0 -234
- package/src/pantheon/permission-eval.ts +0 -284
- package/src/pantheon/preemptive-compact.ts +0 -76
- package/src/pantheon/provider-capabilities.ts +0 -221
- package/src/pantheon/provider-stream-tracker.ts +0 -182
- package/src/pantheon/retry-policy.ts +0 -212
- package/src/pantheon/step-cap.ts +0 -110
- package/src/pantheon/token-opt.ts +0 -689
- package/src/pantheon/toon-codec.ts +0 -481
- package/src/pantheon/v2-events.ts +0 -148
- package/src/pantheon/v2-hooks.ts +0 -287
- package/src/pantheon/v2-tool-definitions.ts +0 -331
- package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
- package/src/plugins/tui/dist/tui.tsx +0 -2777
|
@@ -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
|
-
}
|