pantheon-opencode 1.5.0-beta.2 → 1.5.0-beta.20

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 (107) hide show
  1. package/.pantheon/code-mode/manifest.json +21 -0
  2. package/AGENTS.md +14 -63
  3. package/CHANGELOG.md +1260 -0
  4. package/README.md +162 -23
  5. package/README.pt-BR.md +104 -19
  6. package/bin/pantheon-init.mjs +178 -21
  7. package/commands/pantheon.md +1 -35
  8. package/package.json +23 -15
  9. package/plugin.json +1 -1
  10. package/pyproject.toml +62 -0
  11. package/scripts/code_mode_server.py +431 -55
  12. package/scripts/doctor.mjs +207 -9
  13. package/scripts/install/cli-ui.mjs +17 -11
  14. package/scripts/install/config-migration.mjs +4 -6
  15. package/scripts/install/opencode.mjs +137 -35
  16. package/scripts/install/plugin.mjs +12 -45
  17. package/scripts/install/shared.mjs +43 -2
  18. package/scripts/install/strings.mjs +129 -0
  19. package/scripts/install-mcp.mjs +1 -0
  20. package/scripts/manifest-inventory.mjs +410 -0
  21. package/scripts/mcp_persistence_server.py +536 -164
  22. package/scripts/package-evidence.mjs +242 -0
  23. package/scripts/probe-pantheon-cost.mjs +10 -1
  24. package/scripts/release-beta-version.mjs +56 -60
  25. package/scripts/sync-artifacts.mjs +169 -0
  26. package/scripts/sync-tui.mjs +129 -64
  27. package/scripts/test-opencode-v1-v2-sandbox.sh +79 -161
  28. package/scripts/validation-policy.mjs +7 -7
  29. package/scripts/version-check.mjs +113 -108
  30. package/scripts/versioning.mjs +230 -86
  31. package/src/agents/apollo.md +1 -0
  32. package/src/agents/gaia.md +1 -0
  33. package/src/agents/mnemosyne.md +1 -1
  34. package/src/agents/zeus.md +11 -54
  35. package/src/instructions/memory-protocol.instructions.md +6 -3
  36. package/src/instructions/zeus-anti-stall.instructions.md +2 -4
  37. package/src/instructions/zeus-council-synthesis.instructions.md +3 -28
  38. package/src/instructions/zeus-timeout-retry.instructions.md +3 -28
  39. package/src/mcp/code_mode_server.py +431 -55
  40. package/src/mcp/mcp_persistence_server.py +536 -164
  41. package/src/mcp/toon_codec.py +2 -4
  42. package/src/pantheon/background-job-board.ts +47 -5
  43. package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
  44. package/src/pantheon/cost-command.ts +309 -44
  45. package/src/pantheon/delegation-enforce.ts +59 -14
  46. package/src/pantheon/file-persistence-salvage.ts +64 -0
  47. package/src/pantheon/file-persistence.ts +194 -45
  48. package/src/pantheon/goal-loop.ts +1 -1
  49. package/src/pantheon/goal-store.ts +1 -1
  50. package/src/pantheon/idle-continuation.ts +4 -6
  51. package/src/pantheon/model-command.ts +1 -2
  52. package/src/pantheon/native-task-status.ts +139 -0
  53. package/src/pantheon/presets.d.mts +0 -20
  54. package/src/pantheon/presets.mjs +0 -64
  55. package/src/pantheon/session-guard.ts +2 -2
  56. package/src/pantheon/session-id.ts +30 -0
  57. package/src/pantheon/shared-board.ts +37 -0
  58. package/src/pantheon/task-result-guard.ts +106 -0
  59. package/src/pantheon/todo-enforcer.ts +3 -3
  60. package/src/pantheon/tool-ceiling.ts +159 -0
  61. package/src/pantheon/tool-context.ts +23 -0
  62. package/src/pantheon/v2-bridge.ts +1 -4
  63. package/src/plugin-v2.ts +8 -21
  64. package/src/plugin.ts +83 -251
  65. package/src/plugins/pantheon-hooks.ts +51 -0
  66. package/src/plugins/tui/dist/server.d.ts +1 -1
  67. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  68. package/src/plugins/tui/dist/server.js +1 -1
  69. package/src/plugins/tui/dist/server.js.map +1 -1
  70. package/src/plugins/tui/dist/tui.d.ts +261 -77
  71. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  72. package/src/plugins/tui/dist/tui.js +640 -234
  73. package/src/plugins/tui/dist/tui.js.map +1 -1
  74. package/src/plugins/tui/package-lock.json +2 -2
  75. package/src/plugins/tui/package.json +2 -2
  76. package/src/plugins/tui/src/index.tsx +872 -226
  77. package/src/routing.yml +0 -16
  78. package/src/skills/context-compression/SKILL.md +1 -1
  79. package/scripts/generate-preset-docs.mjs +0 -300
  80. package/scripts/generate-prompts.sh +0 -222
  81. package/scripts/hash_verify.py +0 -192
  82. package/scripts/install/component.mjs +0 -24
  83. package/scripts/install/migrate-config.mjs +0 -302
  84. package/scripts/test-context-compression.sh +0 -169
  85. package/src/pantheon/auto-wake.ts +0 -89
  86. package/src/pantheon/chat-reminders.ts +0 -9
  87. package/src/pantheon/comment-checker.ts +0 -48
  88. package/src/pantheon/compaction-assert.ts +0 -160
  89. package/src/pantheon/cost-tracker.ts +0 -108
  90. package/src/pantheon/delegate-manager.ts +0 -669
  91. package/src/pantheon/delegation-classifier.ts +0 -307
  92. package/src/pantheon/delegation-finalize.ts +0 -374
  93. package/src/pantheon/delegation-notify.ts +0 -212
  94. package/src/pantheon/delegation-toast.ts +0 -73
  95. package/src/pantheon/delegation.ts +0 -1120
  96. package/src/pantheon/dispatch-guard.ts +0 -109
  97. package/src/pantheon/permission-eval.ts +0 -284
  98. package/src/pantheon/preemptive-compact.ts +0 -76
  99. package/src/pantheon/retry-policy.ts +0 -212
  100. package/src/pantheon/step-cap.ts +0 -110
  101. package/src/pantheon/token-opt.ts +0 -781
  102. package/src/pantheon/toon-codec.ts +0 -481
  103. package/src/pantheon/v2-events.ts +0 -131
  104. package/src/pantheon/v2-hooks.ts +0 -287
  105. package/src/pantheon/v2-tool-definitions.ts +0 -331
  106. package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
  107. package/src/plugins/tui/dist/tui.tsx +0 -2605
@@ -9,7 +9,7 @@ absent/fails.
9
9
  Only the standard library (``json`` + ``re``) — prohibited: external
10
10
  gateway, generative embedding.
11
11
 
12
- Encoding rules (mirrored byte-for-byte by ``src/pantheon/toon-codec.ts``):
12
+ Encoding rules:
13
13
 
14
14
  - Scalars: ``None``/``True``/``False`` → ``null``/``true``/``false``;
15
15
  numbers → shortest form; strings bare when unambiguous, else
@@ -20,8 +20,7 @@ Encoding rules (mirrored byte-for-byte by ``src/pantheon/toon-codec.ts``):
20
20
  - Lists of scalars: ``- item`` lines; empty list → ``[]``.
21
21
  - Lists of dicts with uniform keys: ``@table k1|k2`` header + ``v1|v2``
22
22
  rows (savings are per payload class — ~11% on content-dominated board
23
- records up to ~47% on large tabular checkpoints — see
24
- ``docs/ws3-token-opt-measurements.md`` for the reproducible table).
23
+ records up to ~47% on large tabular checkpoints).
25
24
  - Dict values never need quoting for ``:`` (split is on the FIRST colon);
26
25
  list items / top-level scalars that look like ``key: ...`` are quoted.
27
26
  """
@@ -471,7 +470,6 @@ def toon_size_report(value: Any) -> dict[str, Any]:
471
470
  Reports BOTH chars and tokens: token ratios on tiny payloads suffer
472
471
  ceiling bias (``ceil`` rounds 161→41 vs 143→36), so the char columns are
473
472
  the honest basis and the token columns show what metering will debit.
474
- Per-class numbers: see ``docs/ws3-token-opt-measurements.md``.
475
473
  """
476
474
  compact_json = json.dumps(value, ensure_ascii=False, separators=(",", ":"))
477
475
  toon = toon_encode(value)
@@ -2,7 +2,7 @@
2
2
  * Background Job Board — state machine for tracking background agent jobs.
3
3
  *
4
4
  * Tracks jobs through a state machine (running → completed/error/cancelled → reconciled),
5
- * supports alias generation per session, persistence via adapter, auto-wake signal files,
5
+ * supports alias generation per session, persistence via adapter, wake signal files,
6
6
  * and terminal state listeners (persist-before-notify write-ahead-log pattern).
7
7
  *
8
8
  * Pure TypeScript — zero external dependencies beyond Node.js builtins (fs/path).
@@ -123,7 +123,7 @@ export interface BoardOptions {
123
123
  /** Maximum completed jobs kept per agent for reuse (default: 3). */
124
124
  maxReusablePerAgent?: number
125
125
  /**
126
- * Directory for auto-wake signal files.
126
+ * Directory for wake signal files.
127
127
  * When set, a `.signal.json` file is atomically written on every terminal transition.
128
128
  * When null or undefined, no signal files are written.
129
129
  */
@@ -175,7 +175,7 @@ function getAgentPrefix(agent: string): string {
175
175
  * - Strict state machine with validated transitions
176
176
  * - Per-session, per-agent alias generation (apo-1, her-2, …)
177
177
  * - Optional persistence via injected PersistenceAdapter
178
- * - Optional auto-wake signal files on terminal transitions
178
+ * - Optional wake signal files on terminal transitions
179
179
  * - Terminal state listeners with persist-before-notify guarantee
180
180
  * - expire/prune helpers for session lifecycle management
181
181
  */
@@ -222,15 +222,28 @@ export class BackgroundJobBoard {
222
222
  async recoverRunningJobs(): Promise<void> {
223
223
  if (!this.persistence) return
224
224
  const records = await this.persistence.loadAllJobs()
225
+ const recoveredIDs: string[] = []
225
226
  for (const record of records) {
226
227
  if (record.state === 'running') {
227
228
  record.state = 'error'
228
229
  record.lastStatusError = 'Process restarted — job marked as errored'
229
230
  record.updatedAt = Date.now()
231
+ record.completedAt = Date.now()
230
232
  record.totalErrors++
233
+ record.terminalUnreconciled = true
234
+ recoveredIDs.push(record.taskID)
231
235
  }
232
236
  this.jobs.set(record.taskID, record)
233
237
  }
238
+ // Persist + signal + notify for each recovered job (write-ahead-log pattern)
239
+ for (const taskID of recoveredIDs) {
240
+ const job = this.jobs.get(taskID)
241
+ if (job) {
242
+ await this.persistRecord(job)
243
+ await this.writeSignal(job)
244
+ this.notifyTerminal(job.taskID)
245
+ }
246
+ }
234
247
  }
235
248
 
236
249
  // ─── Core Operations ────────────────────────────────────────────────
@@ -240,6 +253,16 @@ export class BackgroundJobBoard {
240
253
  * The job starts in the `running` state with a generated alias.
241
254
  */
242
255
  async registerLaunch(input: LaunchInput): Promise<BackgroundJobRecord> {
256
+ // Atomic concurrency check: validate BEFORE any mutation or await.
257
+ // JavaScript's single-threaded event loop guarantees this check + set
258
+ // cannot be interleaved with another registerLaunch call.
259
+ if (!this.canDispatch(input.agent)) {
260
+ throw new Error(
261
+ `Concurrency limit reached for agent "${input.agent}" ` +
262
+ `(${this.getRunningCount(input.agent)} running, max ${this.options.maxConcurrentPerAgent})`,
263
+ )
264
+ }
265
+
243
266
  const prefix = getAgentPrefix(input.agent)
244
267
  let sessionCounters = this.aliasCounters.get(input.parentSessionID)
245
268
  if (!sessionCounters) {
@@ -272,6 +295,20 @@ export class BackgroundJobBoard {
272
295
  return record
273
296
  }
274
297
 
298
+ /**
299
+ * Register a launch only when taskID has not already been claimed.
300
+ *
301
+ * The map check and set happen before the first await, so JavaScript's
302
+ * single-threaded event loop makes this critical section atomic even when
303
+ * two event handlers call it concurrently. The existing record is returned
304
+ * for the losing caller.
305
+ */
306
+ async registerLaunchIfAbsent(input: LaunchInput): Promise<BackgroundJobRecord> {
307
+ const existing = this.jobs.get(input.taskID)
308
+ if (existing !== undefined) return existing
309
+ return this.registerLaunch(input)
310
+ }
311
+
275
312
  /**
276
313
  * Update a job's state to a terminal state.
277
314
  *
@@ -420,7 +457,12 @@ export class BackgroundJobBoard {
420
457
  job.terminalUnreconciled = false
421
458
 
422
459
  await this.persistRecord(job)
423
- await this.writeSignal(job)
460
+ // P1-1: reconcile is an acknowledgment, NOT a new terminal transition
461
+ // (onTerminal does not re-fire). Re-writing a `.signal.json` here leaked
462
+ // orphan signals with `state:"reconciled"` after the finalize→reconcile
463
+ // cycle deleted them. Instead, remove any still-pending wake signal so
464
+ // reconcile leaves no `<alias>.signal.json` behind.
465
+ await this.deleteSignal(job.alias)
424
466
 
425
467
  return job
426
468
  }
@@ -610,7 +652,7 @@ export class BackgroundJobBoard {
610
652
  }
611
653
 
612
654
  /**
613
- * Delete the auto-wake signal file for an alias (`<alias>.signal.json`).
655
+ * Delete the wake signal file for an alias (`<alias>.signal.json`).
614
656
  * No-op (false) when no signalDir is configured or the file is absent.
615
657
  * The delegate manager calls this on the spot right after auto-reconcile.
616
658
  */
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Compaction Context (Phase 4 + release-134 Phase 2) — build the context
3
- * blocks injected by the `experimental.session.compacting` hook so a
4
- * compacted session does not lose its working state: in-flight background
5
- * delegations, active goals and pending todos.
2
+ * Compaction Context (Phase 4 + release-134 Phase 2, slimmed in Fase 2 of the
3
+ * delegate removal) — build the context blocks injected by the
4
+ * `experimental.session.compacting` hook so a compacted session does not lose
5
+ * its working state: active goals and pending todos.
6
6
  *
7
7
  * Returns a Promise<string[]> (empty when there is nothing to preserve).
8
8
  * Section order is stable:
@@ -17,23 +17,10 @@
17
17
  * `opts.todos`: " [id] description — status". Omitted when the todo
18
18
  * source is absent, disabled, unscoped (no sessionID), fails, or has
19
19
  * no pending todos.
20
- * - "Background Delegations (running):" — every running job launched by
21
- * the session (active work is never capped): alias, agent, started ISO,
22
- * truncated prompt.
23
- * - "Background Delegations (finished, unread):" — terminal-unreconciled
24
- * jobs, newest first, capped at `maxItems` (default 10, matching
25
- * routing.yml background_delegation.max_compaction_items).
26
20
  *
27
- * Reconciled jobs are excluded — they have been read already.
28
- *
29
- * The block style mirrors board.formatForPrompt (" [alias] description —
30
- * STATUS") so compaction context reads consistently with the system prompt,
31
- * but with the extra id/agent/started detail the compaction hook needs.
32
- *
33
- * @module delegation-compaction
21
+ * @module compaction-context
34
22
  */
35
23
 
36
- import type { BackgroundJobBoard, BackgroundJobRecord } from './background-job-board.ts'
37
24
  import type { Goal } from './goal-store.ts'
38
25
  import { createPantheonLogger } from './logger.ts'
39
26
  import type { TodoLike } from './todo-enforcer.ts'
@@ -72,56 +59,14 @@ export interface CompactionTodoSource {
72
59
  }
73
60
 
74
61
  export interface CompactionContextOptions {
75
- /** Scope to jobs launched by this session; omit for all jobs. */
62
+ /** Scope to this session; omit to skip the per-session sections. */
76
63
  sessionID?: string
77
- /** Cap on unread terminal jobs kept (default 10). */
78
- maxItems?: number
79
64
  /** Goal source for <mission_context> (optional — omitted when absent). */
80
65
  goals?: CompactionGoalSource
81
66
  /** Todo source for <todo_context> (optional — omitted when absent). */
82
67
  todos?: CompactionTodoSource
83
68
  }
84
69
 
85
- /** Prompt truncation length for a context line. */
86
- const PROMPT_MAX_LEN = 100
87
-
88
- // ─── Formatting ────────────────────────────────────────────────────────
89
-
90
- /** Collapse whitespace and truncate a prompt to PROMPT_MAX_LEN chars. */
91
- function truncatePrompt(text: string): string {
92
- const flat = text.trim().replace(/\s+/g, ' ')
93
- if (flat.length <= PROMPT_MAX_LEN) return flat
94
- return `${flat.slice(0, PROMPT_MAX_LEN)}…`
95
- }
96
-
97
- /** One-line terminal status label, mirroring formatForPrompt. */
98
- function terminalLabel(state: BackgroundJobRecord['state']): string {
99
- switch (state) {
100
- case 'completed':
101
- return 'OK'
102
- case 'error':
103
- return 'ERR'
104
- case 'cancelled':
105
- return 'CAN'
106
- default:
107
- return state.toUpperCase()
108
- }
109
- }
110
-
111
- function runningLine(job: BackgroundJobRecord): string {
112
- return (
113
- ` [${job.alias}] ${job.agent} — ${truncatePrompt(job.description)} — ` +
114
- `RUNNING since ${new Date(job.launchedAt).toISOString()}`
115
- )
116
- }
117
-
118
- function unreadTerminalLine(job: BackgroundJobRecord): string {
119
- return (
120
- ` [${job.alias}] ${job.agent} — ${truncatePrompt(job.description)} — ` +
121
- `${terminalLabel(job.state)} ${new Date(job.updatedAt).toISOString()} [unread]`
122
- )
123
- }
124
-
125
70
  // ─── Sections ──────────────────────────────────────────────────────────
126
71
 
127
72
  /**
@@ -180,58 +125,23 @@ async function todoContextBlock(opts: CompactionContextOptions): Promise<string
180
125
  return `<todo_context>\n${lines.join('\n')}`
181
126
  }
182
127
 
183
- /**
184
- * `<delegation_context>` — the existing delegation blocks, byte-for-byte
185
- * unchanged from Phase 4 (running, then unread terminal ≤ maxItems).
186
- */
187
- function delegationBlocks(board: BackgroundJobBoard, opts: CompactionContextOptions): string[] {
188
- const maxItems = opts.maxItems ?? 10
189
- const jobs = opts.sessionID !== undefined ? board.list(opts.sessionID) : board.list()
190
-
191
- const running = jobs
192
- .filter((j) => j.state === 'running')
193
- .sort((a, b) => a.launchedAt - b.launchedAt)
194
- const unreadTerminal = jobs
195
- .filter((j) => j.terminalUnreconciled)
196
- .sort((a, b) => b.updatedAt - a.updatedAt)
197
- .slice(0, maxItems)
198
-
199
- const blocks: string[] = []
200
- if (running.length > 0) {
201
- blocks.push(`Background Delegations (running):\n${running.map(runningLine).join('\n')}`)
202
- }
203
- if (unreadTerminal.length > 0) {
204
- blocks.push(
205
- `Background Delegations (finished, unread):\n${unreadTerminal
206
- .map(unreadTerminalLine)
207
- .join('\n')}`,
208
- )
209
- }
210
- return blocks
211
- }
212
-
213
128
  // ─── Build ─────────────────────────────────────────────────────────────
214
129
 
215
130
  /**
216
131
  * Build the compaction context blocks for a session: preservation
217
- * directive, active goals, pending todos, and in-flight background
218
- * delegations. Returns an empty array when there is nothing to preserve
219
- * (caller skips the hook injection entirely).
132
+ * directive, active goals, and pending todos. Returns an empty array when
133
+ * there is nothing to preserve (caller skips the hook injection entirely).
220
134
  */
221
135
  export async function buildCompactionContext(
222
- board: BackgroundJobBoard,
223
136
  opts: CompactionContextOptions = {},
224
137
  ): Promise<string[]> {
225
138
  const [mission, todo] = await Promise.all([missionContextBlock(opts), todoContextBlock(opts)])
226
- const delegation = delegationBlocks(board, opts)
227
139
 
228
140
  // Totally-empty state → nothing to preserve; a lone directive is noise.
229
- if (mission === undefined && todo === undefined && delegation.length === 0) return []
141
+ if (mission === undefined && todo === undefined) return []
230
142
 
231
- const blocks: string[] = []
232
- blocks.push(`<pantheon-context directive>\n${PANTHEON_COMPACTION_DIRECTIVE}`)
143
+ const blocks: string[] = [`<pantheon-context directive>\n${PANTHEON_COMPACTION_DIRECTIVE}`]
233
144
  if (mission !== undefined) blocks.push(mission)
234
145
  if (todo !== undefined) blocks.push(todo)
235
- blocks.push(...delegation)
236
146
  return blocks
237
147
  }