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.
Files changed (82) hide show
  1. package/AGENTS.md +14 -63
  2. package/CHANGELOG.md +320 -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 +229 -77
  43. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  44. package/src/plugins/tui/dist/tui.js +509 -259
  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 +753 -279
  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 -2777
@@ -21,9 +21,8 @@ import { homedir } from 'node:os'
21
21
  import { delimiter, dirname, join } from 'node:path'
22
22
  import { fileURLToPath } from 'node:url'
23
23
  import { z } from 'zod'
24
-
25
- import type { ToolContextLike } from './delegation.ts'
26
24
  import type { NativeTaskStatus } from './native-task-status.ts'
25
+ import type { ToolContextLike } from './tool-context.ts'
27
26
 
28
27
  const __filename = fileURLToPath(import.meta.url)
29
28
  const __dirname = dirname(__filename)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Read-Only Enforcement (Phase 4) — deny mutating tools inside sessions that
3
- * were delegated as read-only (advisory `read_only: true` on
4
- * pantheon_delegate, or the agent ∈ `readOnlyAgents` from routing.yml).
3
+ * were delegated as read-only (an explicit read-only flag, or the agent ∈
4
+ * `readOnlyAgents` from routing.yml).
5
5
  *
6
6
  * OpenCode hook: `tool.execute.before`. The hook shape is
7
7
  * `(input, output) => Promise<void>`; THROWING from the hook denies the tool
@@ -22,8 +22,10 @@
22
22
  * fail-closed when the runtime matrix is wired: missing agent identity or
23
23
  * target metadata is denied rather than inferred.
24
24
  *
25
- * The registry is populated by delegation.ts at delegate time: the CHILD
26
- * session is registered when the delegate call is read-only.
25
+ * The registry is populated by the plugin's `chat.params` hook — the only
26
+ * place the SDK exposes the active agent: when the session's agent is
27
+ * read-only (apollo, gaia — routing.yml `background_delegation.read_only_agents`),
28
+ * the session is registered; `session.deleted` unregisters it.
27
29
  *
28
30
  * @module delegation-enforce
29
31
  */
@@ -147,7 +149,6 @@ export function isDelegationAllowed(
147
149
  function targetFromTool(tool: string, args: unknown): string | undefined {
148
150
  if (args === null || typeof args !== 'object') return undefined
149
151
  const record = args as Record<string, unknown>
150
- if (tool === 'pantheon_delegate') return normalizeDelegationAgent(record.agent)
151
152
  if (tool === 'task') {
152
153
  return normalizeDelegationAgent(record.subagent_type ?? record.subagentType)
153
154
  }
@@ -236,9 +237,10 @@ export function zeusReadGuard(tool: string, args: unknown, agent: string | undef
236
237
  // ─── Registry ──────────────────────────────────────────────────────────
237
238
 
238
239
  /**
239
- * Read-only session registry. `createDelegationTools` registers child
240
- * sessions here; the plugin's `tool.execute.before` guard consults it.
241
- * Process-wide singleton for plugin wiring + factory for test isolation.
240
+ * Read-only session registry. The plugin's `chat.params` hook registers
241
+ * sessions whose active agent is read-only (see {@link syncReadOnlySession});
242
+ * the `tool.execute.before` guard consults it. Process-wide singleton for
243
+ * plugin wiring + factory for test isolation.
242
244
  */
243
245
  export class ReadOnlySessionRegistry {
244
246
  private readonly sessions = new Map<string, ReadOnlyEntry>()
@@ -279,6 +281,50 @@ export class ReadOnlySessionRegistry {
279
281
  /** Process-wide registry wired into the plugin. */
280
282
  export const readOnlyRegistry = new ReadOnlySessionRegistry()
281
283
 
284
+ // ─── Read-Only Agent Policy ────────────────────────────────────────────
285
+
286
+ /**
287
+ * Agents that must stay investigation-only. Mirrors
288
+ * `background_delegation.read_only_agents` in routing.yml — the parity is
289
+ * enforced by tests/pantheon/delegation-enforce.test.ts so the two cannot
290
+ * drift silently.
291
+ */
292
+ export const READ_ONLY_AGENTS: ReadonlySet<string> = new Set(['apollo', 'gaia'])
293
+
294
+ /**
295
+ * Whether an agent identity belongs to the read-only set. Identity is never
296
+ * inferred from prompts or tool names — only the normalized agent name counts.
297
+ */
298
+ export function isReadOnlyAgent(
299
+ agent: unknown,
300
+ readOnlyAgents: ReadonlySet<string> = READ_ONLY_AGENTS,
301
+ ): boolean {
302
+ const normalized = normalizeDelegationAgent(agent)
303
+ return normalized !== undefined && readOnlyAgents.has(normalized)
304
+ }
305
+
306
+ /**
307
+ * Sync the read-only registry with a session's active agent. Called from the
308
+ * plugin's `chat.params` hook: a read-only agent registers the session;
309
+ * anything else (unknown/absent/non-read-only) unregisters it, which also
310
+ * covers an in-session agent switch. Returns whether the session is now
311
+ * registered.
312
+ */
313
+ export function syncReadOnlySession(
314
+ registry: ReadOnlySessionRegistry,
315
+ sessionID: string,
316
+ agent: unknown,
317
+ readOnlyAgents: ReadonlySet<string> = READ_ONLY_AGENTS,
318
+ ): boolean {
319
+ const normalized = normalizeDelegationAgent(agent)
320
+ if (normalized === undefined || !readOnlyAgents.has(normalized)) {
321
+ registry.unregister(sessionID)
322
+ return false
323
+ }
324
+ registry.register(sessionID, { agent: normalized })
325
+ return true
326
+ }
327
+
282
328
  // ─── Guard ─────────────────────────────────────────────────────────────
283
329
 
284
330
  /**
@@ -291,19 +337,18 @@ export function createEnforcementGuard(input: {
291
337
  options?: EnforcementGuardOptions
292
338
  }): ToolExecuteBeforeHandler {
293
339
  const blockedTools = input.options?.blockedTools ?? DEFAULT_BLOCKED_TOOLS
294
- const prefix = input.options?.messagePrefix ?? 'pantheon_delegate guard'
340
+ const prefix = input.options?.messagePrefix ?? 'pantheon delegation guard'
295
341
 
296
342
  return async (hook: ToolExecuteBeforeInput, output?: ToolExecuteBeforeOutput): Promise<void> => {
297
- const isDelegationTool = hook.tool === 'task' || hook.tool === 'pantheon_delegate'
343
+ const isDelegationTool = hook.tool === 'task'
298
344
  if (isDelegationTool && input.options?.isChildSession?.(hook.sessionID) === true) {
299
345
  throw new Error(
300
346
  `${prefix}: session ${hook.sessionID} is a child session — delegation tools are denied ` +
301
347
  'to prevent nested delegation',
302
348
  )
303
349
  }
304
- // pantheon_delegate has the authoritative ToolContext.agent at execution
305
- // time, so its matrix check lives in delegation.ts. The hook only has the
306
- // session-scoped agent learned from chat.params for native task().
350
+ // The hook only has the session-scoped agent learned from chat.params,
351
+ // so the runtime matrix check for native task() lives here.
307
352
  if (
308
353
  hook.tool === 'task' &&
309
354
  input.options?.isRootSession !== undefined &&
@@ -336,7 +381,7 @@ export function createEnforcementGuard(input: {
336
381
  `This session was delegated for investigation only (read-only agent); it cannot ` +
337
382
  `modify files, run commands, or spawn subagents. ` +
338
383
  `If a write result is needed, dispatch a write-capable agent ` +
339
- `(e.g. hermes, aphrodite) with pantheon_delegate instead.`,
384
+ `(e.g. hermes, aphrodite) with native task() instead.`,
340
385
  )
341
386
  }
342
387
  }
@@ -17,11 +17,11 @@
17
17
  import { z } from 'zod'
18
18
 
19
19
  import type { BackgroundJobBoard } from './background-job-board.ts'
20
- import type { ToolContextLike } from './delegation.ts'
21
20
  import { GoalStore } from './goal-store.ts'
22
21
  import { createPantheonLogger } from './logger.ts'
23
22
  import { safeSessionPath } from './session-guard.ts'
24
23
  import type { TodoEnforcerMessage } from './todo-enforcer.ts'
24
+ import type { ToolContextLike } from './tool-context.ts'
25
25
 
26
26
  // Silence-by-default TUI policy (pantheon-hooks L42-58): warn → hooks.log,
27
27
  // console echo opt-in via PANTHEON_HOOKS_LOG=1. `deps.logger` stays for tests.
@@ -15,7 +15,7 @@
15
15
  import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
16
16
  import { join } from 'node:path'
17
17
 
18
- import { assertSafeParentSessionID } from './delegation-finalize.ts'
18
+ import { assertSafeParentSessionID } from './session-id.ts'
19
19
 
20
20
  export type GoalStatus = 'pending' | 'in_progress' | 'done'
21
21
 
@@ -5,9 +5,8 @@
5
5
  * Priority: an ACTIVE GOAL owns the idle — `GoalLoop.onIdle` continues the
6
6
  * goal. Without an active goal, the TODO enforcer gets the idle (it
7
7
  * self-guards on its own enabled flag + guards). Board-child sessions never
8
- * reach the dispatcher — the plugin's event hook routes those to the
9
- * delegation finalize path first (`handleDelegationEvent` returns true) and
10
- * only calls the dispatcher on the `!delegated` branch.
8
+ * reach the dispatcher — the plugin's event hook owns board transitions and
9
+ * only calls the dispatcher for non-board sessions.
11
10
  *
12
11
  * Independently testable — the loop and enforcer are structural interfaces;
13
12
  * the real GoalLoop/TodoEnforcer satisfy them.
@@ -34,9 +33,8 @@ export interface IdleDispatcher {
34
33
  }
35
34
 
36
35
  /**
37
- * Build the idle router. The plugin event hook calls this only on the
38
- * `!delegated` branch of `handleDelegationEvent`, so board children never
39
- * arrive here.
36
+ * Build the idle router. The plugin event hook calls this only for non-board
37
+ * sessions, so board children never arrive here.
40
38
  */
41
39
  export function createIdleDispatcher(deps: {
42
40
  goalLoop: GoalLoopLike
@@ -23,8 +23,6 @@ import { lstat, mkdir, open, rename, unlink } from 'node:fs/promises'
23
23
  import { homedir } from 'node:os'
24
24
  import { basename, dirname, join, resolve } from 'node:path'
25
25
  import { z } from 'zod'
26
-
27
- import type { ToolContextLike } from './delegation.ts'
28
26
  import {
29
27
  capabilityEntry,
30
28
  hasVision,
@@ -32,6 +30,7 @@ import {
32
30
  normalizeCapability,
33
31
  resolveActivePreset,
34
32
  } from './presets.mjs'
33
+ import type { ToolContextLike } from './tool-context.ts'
35
34
 
36
35
  export type ModelScope = 'project' | 'global'
37
36
  export type ModelAction = 'status' | 'show' | 'set' | 'reset'
@@ -2,8 +2,8 @@
2
2
  * Native Task Status — classifies BackgroundJobRecord results from native
3
3
  * task() delegation into actionable status codes.
4
4
  *
5
- * Pure module — zero external dependencies. Consumed by native-probe.ts
6
- * and the delegation manager to decide retry/skip/escalate strategy.
5
+ * Pure module — zero external dependencies. Consumed by the tool-ceiling and
6
+ * cost layers to decide retry/skip/escalate strategy.
7
7
  *
8
8
  * @module native-task-status
9
9
  */
@@ -104,26 +104,6 @@ export interface RoutingRetryPolicyOptions {
104
104
  logger?: { warn?: (msg: string) => void }
105
105
  }
106
106
 
107
- /**
108
- * R1: per-error-type retry policy from routing.yml `retry_policy`
109
- * (auth/rate_limit/timeout/other → max retries). Fail-open → null.
110
- */
111
- export declare function loadRoutingRetryPolicy(
112
- options?: RoutingRetryPolicyOptions,
113
- ): Record<string, number> | null
114
-
115
- export interface RoutingCooldownResult {
116
- allowed_fails: number
117
- cooldown_time_seconds: number
118
- }
119
-
120
- /**
121
- * R1: provider cooldown config from routing.yml `cooldown`. Fail-open → null.
122
- */
123
- export declare function loadRoutingCooldown(
124
- options?: RoutingRetryPolicyOptions,
125
- ): RoutingCooldownResult | null
126
-
127
107
  /**
128
108
  * R4: per-agent step caps from routing.yml `agents.<name>.max_steps`.
129
109
  * Fail-open → {} (no agent capped).
@@ -213,70 +213,6 @@ function loadRoutingYaml(routingPath) {
213
213
  return routing && typeof routing === 'object' ? routing : null
214
214
  }
215
215
 
216
- const RETRY_ERROR_TYPES = new Set(['auth', 'rate_limit', 'timeout', 'other'])
217
-
218
- /**
219
- * Load the R1 per-error-type retry policy from routing.yml (`retry_policy:`).
220
- * Fail-open: missing/unparseable config yields null (the caller falls back
221
- * to DEFAULT_RETRY_POLICY) and warns via the optional logger.
222
- *
223
- * @param {object} [opts]
224
- * @param {string} [opts.routingPath]
225
- * @param {{warn?: Function}} [opts.logger]
226
- * @returns {Record<string, number>|null} error-type → max retries
227
- */
228
- export function loadRoutingRetryPolicy({ routingPath, logger = console } = {}) {
229
- try {
230
- const routing = loadRoutingYaml(routingPath)
231
- const policy = routing?.retry_policy
232
- if (!policy || typeof policy !== 'object' || Array.isArray(policy)) return null
233
- const out = {}
234
- for (const [type, max] of Object.entries(policy)) {
235
- if (RETRY_ERROR_TYPES.has(type) && Number.isSafeInteger(max) && max >= 0) {
236
- out[type] = max
237
- }
238
- }
239
- return Object.keys(out).length > 0 ? out : null
240
- } catch (err) {
241
- const reason = err instanceof Error ? err.message : String(err)
242
- logger.warn?.(`presets: routing.yml retry_policy unavailable (${reason})`)
243
- return null
244
- }
245
- }
246
-
247
- /**
248
- * Load the R1 provider cooldown config from routing.yml (`cooldown:`).
249
- * Fail-open: missing/unparseable config yields null (caller falls back to
250
- * DEFAULT_COOLDOWN).
251
- *
252
- * @param {object} [opts]
253
- * @param {string} [opts.routingPath]
254
- * @param {{warn?: Function}} [opts.logger]
255
- * @returns {{allowed_fails: number, cooldown_time_seconds: number}|null}
256
- */
257
- export function loadRoutingCooldown({ routingPath, logger = console } = {}) {
258
- try {
259
- const routing = loadRoutingYaml(routingPath)
260
- const cooldown = routing?.cooldown
261
- if (!cooldown || typeof cooldown !== 'object' || Array.isArray(cooldown)) return null
262
- const allowedFails = cooldown.allowed_fails
263
- const cooldownTimeSeconds = cooldown.cooldown_time_seconds
264
- if (
265
- !Number.isSafeInteger(allowedFails) ||
266
- allowedFails < 1 ||
267
- !Number.isSafeInteger(cooldownTimeSeconds) ||
268
- cooldownTimeSeconds < 1
269
- ) {
270
- return null
271
- }
272
- return { allowed_fails: allowedFails, cooldown_time_seconds: cooldownTimeSeconds }
273
- } catch (err) {
274
- const reason = err instanceof Error ? err.message : String(err)
275
- logger.warn?.(`presets: routing.yml cooldown unavailable (${reason})`)
276
- return null
277
- }
278
- }
279
-
280
216
  /**
281
217
  * Load the R4 per-agent step caps from routing.yml
282
218
  * (`agents.<name>.max_steps`). Fail-open: missing config yields {}.
@@ -10,8 +10,8 @@
10
10
  * never throw, never send the placeholder).
11
11
  *
12
12
  * Mirrors the TUI's `src/plugins/tui/src/index.tsx` contract verbatim so both
13
- * sides agree on what "valid" means. The TUI file remains self-contained
14
- * (dist/tui.tsx is a raw copy, no relative imports), so its local copy is kept
13
+ * sides agree on what "valid" means. The TUI plugin ships as a bundle
14
+ * (dist/tui.js) without the src/pantheon tree, so its local copy is kept
15
15
  * in sync — this module is the shared source for `src/pantheon/*` and
16
16
  * `src/plugin.ts`.
17
17
  *
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Session ID safety — reject session IDs that could escape a per-session
3
+ * output directory.
4
+ *
5
+ * Shared helper for the file-backed stores that remain after the delegate
6
+ * removal (goal-store and friends) — it no longer lives alongside the
7
+ * deleted delegation module.
8
+ *
9
+ * `sessionID` is embedded in a path
10
+ * `<dir>/<sessionID>/file.json` — `path.join` does NOT strip `..`, so a
11
+ * traversing ID would write outside the sandbox. Call this BEFORE any path
12
+ * construction.
13
+ *
14
+ * @module session-id
15
+ */
16
+
17
+ /**
18
+ * Reject session IDs that could escape the target directory.
19
+ *
20
+ * @throws {Error} When the ID contains `..`, `/`, or `\`.
21
+ */
22
+ export function assertSafeParentSessionID(parentSessionID: string): void {
23
+ if (
24
+ parentSessionID.includes('..') ||
25
+ parentSessionID.includes('/') ||
26
+ parentSessionID.includes('\\')
27
+ ) {
28
+ throw new Error(`Invalid parentSessionID: ${parentSessionID}`)
29
+ }
30
+ }
@@ -58,14 +58,13 @@ export interface TaskResultGuardOptions {
58
58
 
59
59
  /**
60
60
  * The error message injected when a task() result is empty.
61
- * Matches the existing delegation-finalize.ts contract:
62
- * "Child session produced no assistant or tool output".
61
+ * Keeps the historical "no assistant or tool output" contract text.
63
62
  */
64
63
  export const EMPTY_TASK_ERROR =
65
64
  'ERROR: task() returned empty output — child session produced no assistant or tool output. ' +
66
65
  'This typically means the provider rejected the request (admission control / rate limit) ' +
67
66
  'or the child session crashed before producing any response. ' +
68
- 'Consider using pantheon_delegate (clean context) instead of task() for large payloads.'
67
+ 'Consider splitting the work into a smaller task() or retrying the child session.'
69
68
 
70
69
  // ─── Implementation ────────────────────────────────────────────────────
71
70
 
@@ -2,9 +2,9 @@
2
2
  * TODO Enforcer (Wave 1) — keeps root/non-board sessions working on their
3
3
  * todo list when the session goes idle.
4
4
  *
5
- * The plugin's `event` hook dispatches `session.idle`: board-child sessions go
6
- * to the delegation finalize path (`handleDelegationEvent`), everything else
7
- * (roots, non-board sessions) goes to `TodoEnforcer.onIdle`. The enforcer
5
+ * The plugin's `event` hook dispatches `session.idle`: board-child sessions
6
+ * are owned by the board transition path, everything else (roots, non-board
7
+ * sessions) goes to `TodoEnforcer.onIdle`. The enforcer
8
8
  * re-injects a continuation prompt when the session went idle with incomplete
9
9
  * todos, so the agent keeps working on its task list instead of stopping.
10
10
  *
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Tool Context — the structural view of the context opencode passes to a
3
+ * tool's `execute()`.
4
+ *
5
+ * Extracted from `delegation.ts` (Fase 2 of the delegate removal) so the
6
+ * surviving structural tools (cost-command, model-command, goal-loop) no
7
+ * longer depend on the delegation module. Pure type — zero runtime code.
8
+ *
9
+ * @module tool-context
10
+ */
11
+
12
+ /** Structural view of the tool context opencode passes to execute(). */
13
+ export interface ToolContextLike {
14
+ sessionID: string
15
+ directory?: string
16
+ worktree?: string
17
+ /**
18
+ * The OpenCode SDK's ToolContext declares this as required. It remains
19
+ * optional here because the structural test/embedding surface can be
20
+ * supplied by older hosts; enforcement must skip when it is absent.
21
+ */
22
+ agent?: string
23
+ }
@@ -17,7 +17,6 @@
17
17
  */
18
18
 
19
19
  import type { BackgroundJobBoard } from './background-job-board.ts'
20
- import type { DelegationClient } from './delegation-finalize.ts'
21
20
  import type { GoalStore } from './goal-store.ts'
22
21
  import type { TodoEnforcer } from './todo-enforcer.ts'
23
22
 
@@ -57,8 +56,6 @@ export interface VisionHandler {
57
56
  export interface PantheonV2Bridge {
58
57
  /** Background job board — tracks running/completed/cancelled delegated jobs. */
59
58
  board?: BackgroundJobBoard
60
- /** Structural delegation client — creates child sessions and sends prompts. */
61
- delegationClient?: DelegationClient
62
59
  /** Goal store — file-based persistence for full-auto goal loop. */
63
60
  goalStore?: GoalStore
64
61
  /** Todo enforcer — idle continuation when todos are incomplete. */
@@ -85,7 +82,7 @@ export const BRIDGE_OPTIONS_KEY = '__pantheonV1Bridge' as const
85
82
  * @example
86
83
  * ```ts
87
84
  * // In V1 plugin setup (plugin.ts):
88
- * const bridge = createV2Bridge({ board, delegationClient, goalStore, ... })
85
+ * const bridge = createV2Bridge({ board, goalStore, ... })
89
86
  * // Then pass bridge to V2 context via options
90
87
  * ```
91
88
  */
package/src/plugin-v2.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Pantheon V2 Plugin — full orchestration plugin for OpenCode V2.
3
3
  *
4
4
  * Registers:
5
- * - 9 orchestration tools via V2 tool.transform (with V1 bridge fallback)
5
+ * - 6 orchestration tools via V2 tool.transform (with V1 bridge fallback)
6
6
  * - 4 event subscriptions (session.created, idle, error, compacted)
7
7
  * - Session hooks (prompt, context) and tool hooks (execute.before/after)
8
8
  * - Permission hooks for custom authorization
@@ -408,11 +408,11 @@ async function onSessionIdle(_event: V2SessionEvent): Promise<void> {
408
408
  }
409
409
 
410
410
  async function onSessionError(_event: V2SessionEvent): Promise<void> {
411
- // Error handling — delegation finalize as error.
411
+ // Error handling — board transition to error.
412
412
  const bridge = resolveBridge()
413
413
  if (bridge?.board != null) {
414
- // Bridge available: delegation error handling is done by V1 event hook.
415
- // V2 only logs — V1 finalizeDelegation handles the board transition.
414
+ // Bridge available: error handling is done by the V1 event hook.
415
+ // V2 only logs — V1 owns the board transition.
416
416
  }
417
417
  }
418
418
 
@@ -640,9 +640,6 @@ function createV2ToolDefinitionsFromContext(_context: PluginContext): V2ToolDef[
640
640
  'infrastructure through the V2 bridge (src/pantheon/v2-bridge.ts).'
641
641
 
642
642
  const toolNames = [
643
- 'pantheon_delegate',
644
- 'pantheon_delegation_read',
645
- 'pantheon_delegation_list',
646
643
  'hashline_edit',
647
644
  'pantheon_goal_create',
648
645
  'pantheon_goal_get',
@@ -652,14 +649,6 @@ function createV2ToolDefinitionsFromContext(_context: PluginContext): V2ToolDef[
652
649
  ]
653
650
 
654
651
  const toolDescriptions: Record<string, string> = {
655
- pantheon_delegate:
656
- 'Dispatch a background agent as a child session and register it on the job board. ' +
657
- 'Returns the readable alias (e.g. "apo-1"); read the result with pantheon_delegation_read.',
658
- pantheon_delegation_read:
659
- 'Block until a background delegation finishes (completed/error/cancelled), then return ' +
660
- 'its report markdown (with a trailing agent-activity section) and mark the job reconciled.',
661
- pantheon_delegation_list:
662
- 'List background delegations for the current session, with [unread] for finished jobs.',
663
652
  hashline_edit:
664
653
  'Edit a file anchored by hashline refs (LINE#TAG) instead of raw line numbers. ' +
665
654
  'Ops: replace, append, prepend, delete. ALL refs validated against ORIGINAL file first.',
@@ -681,12 +670,10 @@ function createV2ToolDefinitionsFromContext(_context: PluginContext): V2ToolDef[
681
670
  name,
682
671
  description: toolDescriptions[name] ?? `Pantheon tool: ${name}`,
683
672
  input: { type: 'object' as const, properties: {} },
684
- // FOLLOW-UP: connect the real wrappers from
685
- // src/pantheon/v2-tool-definitions.ts (createV2ToolDefinitions with
686
- // costCommand/goalTools) once V1 infrastructure is resolvable in the V2
687
- // standalone context. The before/after factories in v2-hooks.ts:216-282
688
- // are likewise unwired. Until then, placeholders resolve to the object
689
- // shape so the beta host never sees a bare string.
673
+ // FOLLOW-UP: wire the real tool wrappers (costCommand/goalTools) once V1
674
+ // infrastructure is resolvable in the V2 standalone context. Until then,
675
+ // placeholders resolve to the object shape so the beta host never sees a
676
+ // bare string.
690
677
  execute: async (_input: Record<string, unknown>, _context: unknown): Promise<V2ToolResult> => {
691
678
  return { output: `${name}: ${v2UnavailableMessage}` }
692
679
  },