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.
- package/.pantheon/code-mode/manifest.json +21 -0
- package/AGENTS.md +14 -63
- package/CHANGELOG.md +1260 -0
- package/README.md +162 -23
- package/README.pt-BR.md +104 -19
- package/bin/pantheon-init.mjs +178 -21
- package/commands/pantheon.md +1 -35
- package/package.json +23 -15
- package/plugin.json +1 -1
- package/pyproject.toml +62 -0
- package/scripts/code_mode_server.py +431 -55
- package/scripts/doctor.mjs +207 -9
- package/scripts/install/cli-ui.mjs +17 -11
- package/scripts/install/config-migration.mjs +4 -6
- package/scripts/install/opencode.mjs +137 -35
- package/scripts/install/plugin.mjs +12 -45
- package/scripts/install/shared.mjs +43 -2
- package/scripts/install/strings.mjs +129 -0
- package/scripts/install-mcp.mjs +1 -0
- package/scripts/manifest-inventory.mjs +410 -0
- package/scripts/mcp_persistence_server.py +536 -164
- package/scripts/package-evidence.mjs +242 -0
- package/scripts/probe-pantheon-cost.mjs +10 -1
- package/scripts/release-beta-version.mjs +56 -60
- package/scripts/sync-artifacts.mjs +169 -0
- package/scripts/sync-tui.mjs +129 -64
- package/scripts/test-opencode-v1-v2-sandbox.sh +79 -161
- package/scripts/validation-policy.mjs +7 -7
- package/scripts/version-check.mjs +113 -108
- package/scripts/versioning.mjs +230 -86
- package/src/agents/apollo.md +1 -0
- package/src/agents/gaia.md +1 -0
- package/src/agents/mnemosyne.md +1 -1
- package/src/agents/zeus.md +11 -54
- 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/code_mode_server.py +431 -55
- package/src/mcp/mcp_persistence_server.py +536 -164
- package/src/mcp/toon_codec.py +2 -4
- package/src/pantheon/background-job-board.ts +47 -5
- package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
- package/src/pantheon/cost-command.ts +309 -44
- package/src/pantheon/delegation-enforce.ts +59 -14
- package/src/pantheon/file-persistence-salvage.ts +64 -0
- package/src/pantheon/file-persistence.ts +194 -45
- 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 +139 -0
- 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/shared-board.ts +37 -0
- package/src/pantheon/task-result-guard.ts +106 -0
- package/src/pantheon/todo-enforcer.ts +3 -3
- package/src/pantheon/tool-ceiling.ts +159 -0
- 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 +83 -251
- package/src/plugins/pantheon-hooks.ts +51 -0
- package/src/plugins/tui/dist/server.d.ts +1 -1
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/server.js +1 -1
- package/src/plugins/tui/dist/server.js.map +1 -1
- package/src/plugins/tui/dist/tui.d.ts +261 -77
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +640 -234
- package/src/plugins/tui/dist/tui.js.map +1 -1
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +2 -2
- package/src/plugins/tui/src/index.tsx +872 -226
- 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 -89
- 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/cost-tracker.ts +0 -108
- package/src/pantheon/delegate-manager.ts +0 -669
- package/src/pantheon/delegation-classifier.ts +0 -307
- package/src/pantheon/delegation-finalize.ts +0 -374
- package/src/pantheon/delegation-notify.ts +0 -212
- package/src/pantheon/delegation-toast.ts +0 -73
- package/src/pantheon/delegation.ts +0 -1120
- package/src/pantheon/dispatch-guard.ts +0 -109
- package/src/pantheon/permission-eval.ts +0 -284
- package/src/pantheon/preemptive-compact.ts +0 -76
- package/src/pantheon/retry-policy.ts +0 -212
- package/src/pantheon/step-cap.ts +0 -110
- package/src/pantheon/token-opt.ts +0 -781
- package/src/pantheon/toon-codec.ts +0 -481
- package/src/pantheon/v2-events.ts +0 -131
- 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 -2605
package/src/mcp/toon_codec.py
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
3
|
-
*
|
|
4
|
-
* compacted session does not lose
|
|
5
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
218
|
-
*
|
|
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
|
|
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
|
}
|