pantheon-opencode 1.5.0 → 1.5.1

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.
@@ -978,8 +978,6 @@ export type DelegationEntry = {
978
978
  updatedAt: number | null
979
979
  timedOut: boolean
980
980
  description: string
981
- /** True while the panel is waiting for pantheon_delegation_read. */
982
- read?: boolean
983
981
  // NOTE: the nested/tree view is BACKLOG. `session.children` returns direct
984
982
  // children only, so no real grandchild rows exist yet; the parentTaskID /
985
983
  // childCount fields + tree-connector helpers were removed as dead code.
@@ -1365,13 +1363,7 @@ export function delegationElapsed(entry: DelegationEntry, now: number): string {
1365
1363
 
1366
1364
  /** The activity labels shown by the animated row. Keeping this pure makes the
1367
1365
  * state machine testable without booting OpenCode's renderer. */
1368
- export type DelegationActivity =
1369
- | 'delegating'
1370
- | 'working'
1371
- | 'reading'
1372
- | 'completed'
1373
- | 'error'
1374
- | 'cancelled'
1366
+ export type DelegationActivity = 'delegating' | 'working' | 'completed' | 'error' | 'cancelled'
1375
1367
 
1376
1368
  export function delegationActivity(entry: DelegationEntry): DelegationActivity {
1377
1369
  if (entry.state === 'completed') return 'completed'
@@ -1382,7 +1374,6 @@ export function delegationActivity(entry: DelegationEntry): DelegationActivity {
1382
1374
  )
1383
1375
  return 'error'
1384
1376
  if (entry.state === 'cancelled') return 'cancelled'
1385
- if (entry.read) return 'reading'
1386
1377
  if (entry.alias.startsWith('live-') && entry.taskID === undefined) return 'delegating'
1387
1378
  return 'working'
1388
1379
  }
@@ -1394,8 +1385,6 @@ export function delegationActivityLabel(entry: DelegationEntry): string {
1394
1385
  return 'DELEGATING'
1395
1386
  case 'working':
1396
1387
  return 'WORKING'
1397
- case 'reading':
1398
- return 'READING RESULT'
1399
1388
  case 'completed':
1400
1389
  return entry.timedOut ? 'DONE (TIMED OUT)' : 'DONE'
1401
1390
  case 'error':
@@ -1560,8 +1549,7 @@ export function mergeChildDelegationSources(
1560
1549
  const incoming = toDelegationEntry(liveEntry)
1561
1550
  // A native task() live part never carries a report alias — it is a
1562
1551
  // children-only row by definition whether it lands on a new row or
1563
- // upgrades an existing
1564
- // children-only one. pantheon_delegate pending parts keep source 'live'.
1552
+ // upgrades an existing children-only one.
1565
1553
  const isNativeLive = liveEntry.tool === 'task' && liveEntry.alias === null
1566
1554
  if (isNativeLive) {
1567
1555
  // Native task() rows carry no report alias — keep the per-call
@@ -1589,12 +1577,12 @@ export function mergeChildDelegationSources(
1589
1577
  if (existing === undefined) continue
1590
1578
  // Any terminal state is authoritative — a child session that went idle
1591
1579
  // correctly derives 'completed' via childStatusToState before the MD
1592
- // report is written. The live tool-part channel stays 'running' until
1593
- // pantheon_delegation_read fires, so allowing it to overwrite a
1594
- // terminal state would flip completed → running (the bug this fixes).
1580
+ // report is written. The live tool-part channel stays 'running' for the
1581
+ // whole call, so allowing it to overwrite a terminal state would flip
1582
+ // completed → running (the bug this fixes).
1595
1583
  //
1596
1584
  // When the existing entry IS terminal, we still absorb live metadata
1597
- // (alias, agent, read) but preserve the terminal state.
1585
+ // (alias, agent) but preserve the terminal state.
1598
1586
  if (existing.state !== 'running') {
1599
1587
  if (liveEntry.alias !== null && existing.alias !== incoming.alias) {
1600
1588
  existing.alias = incoming.alias
@@ -1602,9 +1590,6 @@ export function mergeChildDelegationSources(
1602
1590
  if (incoming.agent !== 'agent' && existing.agent !== incoming.agent) {
1603
1591
  existing.agent = incoming.agent
1604
1592
  }
1605
- if (incoming.read && !existing.read) {
1606
- existing.read = true
1607
- }
1608
1593
  continue
1609
1594
  }
1610
1595
  result[index] = {
@@ -1617,10 +1602,8 @@ export function mergeChildDelegationSources(
1617
1602
  state: incoming.state,
1618
1603
  startedAt: Math.min(existing.startedAt, incoming.startedAt),
1619
1604
  updatedAt: incoming.updatedAt,
1620
- read: incoming.read,
1621
1605
  // A native task() live upgrade must not flip a children-only row to
1622
- // 'live' (that would drop the children-only provenance); pantheon_delegate
1623
- // upgrades still take source 'live'.
1606
+ // 'live' (that would drop the children-only provenance).
1624
1607
  source: isNativeLive ? existing.source : 'live',
1625
1608
  }
1626
1609
  }
@@ -1630,12 +1613,11 @@ export function mergeChildDelegationSources(
1630
1613
  }
1631
1614
 
1632
1615
  /* ─── Live tool-call lifecycle (agent-sidebar pattern) ─────
1633
- * Source of truth for jobs born while opencode is open. The TUI event bus
1616
+ * Refresh trigger for jobs born while opencode is open. The TUI event bus
1634
1617
  * (api.event.on) delivers `message.part.updated` for every part change;
1635
- * we only care about tool parts whose `tool` is pantheon_delegate (job
1636
- * launch), the native `task` subagent tool (same parentID === caller
1637
- * mechanism — its rows render `nat:`), or pantheon_delegation_read
1638
- * (blocking until terminal → closes the entry). Shape-adapted from the
1618
+ * we only care about tool parts whose `tool` is the native `task` subagent
1619
+ * tool (parentID === caller mechanism — its rows render `nat:`); the panel
1620
+ * rows themselves come from the children channel. Shape-adapted from the
1639
1621
  * SDK v2 ToolPart but duck-typed so the pure helpers are testable without
1640
1622
  * the SDK. */
1641
1623
 
@@ -1655,27 +1637,25 @@ export type DelegationToolPart = {
1655
1637
  }
1656
1638
  }
1657
1639
 
1658
- /** One live delegation tracked in-memory, keyed by the delegate callID. */
1640
+ /** One live delegation tracked in-memory, keyed by the task callID. */
1659
1641
  export type LiveDelegationEntry = {
1660
- /** Tool call id of the pantheon_delegate part (stable across events). */
1642
+ /** Tool call id of the task part (stable across events). */
1661
1643
  callID: string
1662
1644
  /** Part id (for message.part.removed cleanup). */
1663
1645
  partID: string
1664
1646
  /** Parent session the delegation was launched from. */
1665
1647
  sessionID: string
1666
- tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task'
1667
- /** Agent name (from the delegate input args). */
1648
+ tool: 'task'
1649
+ /** Agent name (from the task input args). */
1668
1650
  agent: string
1669
1651
  description: string
1670
- /** Known after the delegate tool completes (parsed from its output). */
1652
+ /** Known once the task completes and its output carries a marker. */
1671
1653
  alias: string | null
1672
- /** Child session id (parsed from the delegate output). */
1654
+ /** Child session id (parsed from the task output, when present). */
1673
1655
  taskID: string | null
1674
1656
  state: 'running' | 'completed' | 'error' | 'cancelled'
1675
1657
  startedAt: number
1676
1658
  updatedAt: number | null
1677
- /** True once a pantheon_delegation_read for this job has been observed. */
1678
- read: boolean
1679
1659
  }
1680
1660
 
1681
1661
  /** Result of parsing one tool part into lifecycle-relevant fields. */
@@ -1683,9 +1663,9 @@ export type ParsedDelegationToolPart = {
1683
1663
  callID: string
1684
1664
  partID: string
1685
1665
  sessionID: string
1686
- tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task'
1687
- /** null for read parts (no agent arg — the id targets an existing job). */
1688
- agent: string | null
1666
+ tool: 'task'
1667
+ /** Agent name from the task input args ('agent' fallback when absent). */
1668
+ agent: string
1689
1669
  description: string
1690
1670
  status: 'pending' | 'running' | 'completed' | 'error'
1691
1671
  alias: string | null
@@ -1694,29 +1674,21 @@ export type ParsedDelegationToolPart = {
1694
1674
  endAt: number | null
1695
1675
  }
1696
1676
 
1697
- /** Alias in the delegate output: "Delegated to apollo: [apo-1] (task …)". */
1677
+ /** Alias in the task output: "Delegated to apollo: [apo-1] (task …)". */
1698
1678
  const DELEGATE_ALIAS_PATTERN = /\[([a-z]{2,8}-\d+)\]/i
1699
- /** Child task id in the delegate output: "(task ses_child_9)". */
1679
+ /** Child task id in the task output: "(task ses_child_9)". */
1700
1680
  const DELEGATE_TASKID_PATTERN = /\(task\s+([a-z0-9_]+)\)/i
1701
- /** Plain alias, as passed to pantheon_delegation_read input.id: "apo-1". */
1702
- const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i
1703
1681
 
1704
1682
  /** Extract the tool name + args from a `message.part.updated` part and
1705
1683
  * reduce it to what the panel needs. Returns null for anything that is
1706
- * not a pantheon delegation tool part, the native `task` subagent tool
1707
- * (same parentID === caller mechanism — its children render `nat:`),
1708
- * or is missing its callID. */
1684
+ * not the native `task` subagent tool (parentID === caller mechanism —
1685
+ * its children render `nat:`), or is missing its callID. */
1709
1686
  export function parseDelegationToolPart(
1710
1687
  part: DelegationToolPart,
1711
1688
  now = Date.now(),
1712
1689
  ): ParsedDelegationToolPart | null {
1713
1690
  if (part.type !== 'tool') return null
1714
- if (
1715
- part.tool !== 'pantheon_delegate' &&
1716
- part.tool !== 'pantheon_delegation_read' &&
1717
- part.tool !== 'task'
1718
- )
1719
- return null
1691
+ if (part.tool !== 'task') return null
1720
1692
  const callID = part.callID
1721
1693
  if (callID === undefined || callID === '') return null
1722
1694
  const sessionID = part.sessionID ?? ''
@@ -1728,29 +1700,6 @@ export function parseDelegationToolPart(
1728
1700
  const startedAt = state.time?.start ?? now
1729
1701
  const endAt = state.time?.end ?? null
1730
1702
 
1731
- if (part.tool === 'pantheon_delegation_read') {
1732
- const target = typeof input.id === 'string' ? input.id : null
1733
- let alias: string | null = null
1734
- let taskID: string | null = null
1735
- if (target !== null) {
1736
- if (READ_ALIAS_PATTERN.test(target)) alias = target
1737
- else if (target.startsWith('ses_')) taskID = target // raw child session id
1738
- }
1739
- return {
1740
- callID,
1741
- partID: part.id ?? '',
1742
- sessionID,
1743
- tool: 'pantheon_delegation_read',
1744
- agent: null,
1745
- description: '',
1746
- status,
1747
- alias,
1748
- taskID,
1749
- startedAt,
1750
- endAt,
1751
- }
1752
- }
1753
-
1754
1703
  const agent =
1755
1704
  typeof input.agent === 'string'
1756
1705
  ? input.agent
@@ -1763,9 +1712,9 @@ export function parseDelegationToolPart(
1763
1712
  : typeof input.prompt === 'string'
1764
1713
  ? (input.prompt as string).slice(0, 120)
1765
1714
  : ''
1766
- // The alias/taskID only exist once the delegate tool COMPLETES (they are
1767
- // returned in its output); a running/pending part has neither. Native
1768
- // `task` parts never carry a report alias — the children channel supplies
1715
+ // The alias/taskID only exist once the task COMPLETES (they are
1716
+ // returned in its output); a running/pending part has neither. A native
1717
+ // `task` part never carries a report alias — the children channel supplies
1769
1718
  // the child session id and the row's identity.
1770
1719
  let alias: string | null = null
1771
1720
  let taskID: string | null = null
@@ -1778,7 +1727,7 @@ export function parseDelegationToolPart(
1778
1727
  callID,
1779
1728
  partID: part.id ?? '',
1780
1729
  sessionID,
1781
- tool: part.tool === 'task' ? 'task' : 'pantheon_delegate',
1730
+ tool: 'task',
1782
1731
  agent,
1783
1732
  description,
1784
1733
  status,
@@ -1789,20 +1738,6 @@ export function parseDelegationToolPart(
1789
1738
  }
1790
1739
  }
1791
1740
 
1792
- /** Find a live entry by alias or taskID (read parts resolve by id). */
1793
- function findLiveByTarget(
1794
- map: Map<string, LiveDelegationEntry>,
1795
- alias: string | null,
1796
- taskID: string | null,
1797
- ): LiveDelegationEntry | undefined {
1798
- if (alias === null && taskID === null) return undefined
1799
- for (const entry of map.values()) {
1800
- if (alias !== null && entry.alias === alias) return entry
1801
- if (taskID !== null && entry.taskID === taskID) return entry
1802
- }
1803
- return undefined
1804
- }
1805
-
1806
1741
  /** Apply one tool part to the live map. Returns true when the map changed.
1807
1742
  * Pure w.r.t. I/O — only mutates `map`. */
1808
1743
  export function reduceDelegationToolPart(
@@ -1813,34 +1748,11 @@ export function reduceDelegationToolPart(
1813
1748
  const parsed = parseDelegationToolPart(part, now)
1814
1749
  if (parsed === null) return false
1815
1750
 
1816
- // Read tool: never creates a row — it resolves a delegation by id. It
1817
- // marks the target `read` and (because it blocks until terminal) closes
1818
- // the entry on completed/error with its end timestamp.
1819
- if (parsed.tool === 'pantheon_delegation_read') {
1820
- const target = findLiveByTarget(map, parsed.alias, parsed.taskID)
1821
- if (target === undefined) return false
1822
- let changed = false
1823
- if (!target.read) {
1824
- target.read = true
1825
- changed = true
1826
- }
1827
- if (parsed.status === 'completed' && target.state === 'running') {
1828
- target.state = 'completed'
1829
- target.updatedAt = parsed.endAt ?? now
1830
- changed = true
1831
- } else if (parsed.status === 'error' && target.state === 'running') {
1832
- target.state = 'error'
1833
- target.updatedAt = parsed.endAt ?? now
1834
- changed = true
1835
- }
1836
- return changed
1837
- }
1838
-
1839
- // Delegate tool (pantheon_delegate AND native task): pending/running →
1840
- // job launched (running). A COMPLETED delegate tool only means the job was
1841
- // registered — it stays running and we pick up alias + taskID from the
1842
- // output. Native `task` parts never yield a report alias, so their rows
1843
- // stay identity-less until the children channel resolves them.
1751
+ // Task tool: pending/running → job launched (running). A COMPLETED task
1752
+ // only means the call returned — the child session keeps running, and we
1753
+ // pick up alias + taskID from the output when present. Native `task` parts
1754
+ // never yield a report alias, so their rows stay identity-less until the
1755
+ // children channel resolves them.
1844
1756
  const existing = map.get(parsed.callID)
1845
1757
  if (parsed.status === 'error') {
1846
1758
  if (existing !== undefined && existing.state === 'error' && existing.updatedAt === parsed.endAt)
@@ -1850,14 +1762,13 @@ export function reduceDelegationToolPart(
1850
1762
  partID: parsed.partID,
1851
1763
  sessionID: parsed.sessionID,
1852
1764
  tool: parsed.tool,
1853
- agent: parsed.agent ?? 'agent',
1765
+ agent: parsed.agent,
1854
1766
  description: parsed.description,
1855
1767
  alias: existing?.alias ?? null,
1856
1768
  taskID: existing?.taskID ?? null,
1857
1769
  state: 'error',
1858
1770
  startedAt: existing?.startedAt ?? parsed.startedAt,
1859
1771
  updatedAt: parsed.endAt ?? now,
1860
- read: existing?.read ?? false,
1861
1772
  })
1862
1773
  return true
1863
1774
  }
@@ -1867,14 +1778,13 @@ export function reduceDelegationToolPart(
1867
1778
  partID: parsed.partID,
1868
1779
  sessionID: parsed.sessionID,
1869
1780
  tool: parsed.tool,
1870
- agent: parsed.agent ?? 'agent',
1781
+ agent: parsed.agent,
1871
1782
  description: parsed.description,
1872
1783
  alias: parsed.alias,
1873
1784
  taskID: parsed.taskID,
1874
1785
  state: 'running',
1875
1786
  startedAt: parsed.startedAt,
1876
1787
  updatedAt: null,
1877
- read: false,
1878
1788
  })
1879
1789
  return true
1880
1790
  }
@@ -1919,14 +1829,14 @@ export function removeDelegationEntry(
1919
1829
  return false
1920
1830
  }
1921
1831
 
1922
- /** Collect pantheon delegation + native task tool parts from a session's messages.
1832
+ /** Collect native task tool parts from a session's messages.
1923
1833
  * Messages may carry their parts inline (duck-typed `msg.parts`); when
1924
1834
  * they don't, the optional `getParts(messageID)` callback is used (the TUI
1925
1835
  * SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
1926
- * child session with parentID = caller — the same mechanism as
1927
- * pantheon_delegate — so its parts feed the live-map as the native signal
1928
- * (rows come from the children channel). Pure w.r.t. I/O — used
1929
- * by the mount re-scan to re-seed the live map after compaction/attach. */
1836
+ * child session with parentID = caller, so its parts feed the live-map as
1837
+ * the refresh signal (rows come from the children channel). Pure w.r.t.
1838
+ * I/O — used by the mount re-scan to re-seed the live map after
1839
+ * compaction/attach. */
1930
1840
  export function collectDelegationToolParts(
1931
1841
  messages: readonly { id?: string; parts?: unknown[] }[] | undefined,
1932
1842
  getParts?: (messageID: string) => readonly unknown[] | undefined,
@@ -1942,12 +1852,7 @@ export function collectDelegationToolParts(
1942
1852
  if (!parts) continue
1943
1853
  for (const raw of parts) {
1944
1854
  const part = raw as DelegationToolPart
1945
- if (
1946
- part?.type === 'tool' &&
1947
- (part.tool === 'pantheon_delegate' ||
1948
- part.tool === 'pantheon_delegation_read' ||
1949
- part.tool === 'task')
1950
- ) {
1855
+ if (part?.type === 'tool' && part.tool === 'task') {
1951
1856
  out.push(part)
1952
1857
  }
1953
1858
  }
@@ -1972,7 +1877,7 @@ export function seedLiveDelegationMap(
1972
1877
  }
1973
1878
 
1974
1879
  /** Convert a live entry into the shared display shape. Alias falls back to
1975
- * a `live-<callID>` prefix while the delegate tool has not completed yet. */
1880
+ * a `live-<callID>` prefix while the task call has not completed yet. */
1976
1881
  export function toDelegationEntry(live: LiveDelegationEntry): DelegationEntry {
1977
1882
  return {
1978
1883
  alias: live.alias ?? `live-${live.callID.slice(0, 8)}`,
@@ -1984,7 +1889,6 @@ export function toDelegationEntry(live: LiveDelegationEntry): DelegationEntry {
1984
1889
  updatedAt: live.updatedAt,
1985
1890
  timedOut: false,
1986
1891
  description: live.description,
1987
- read: live.read,
1988
1892
  source: 'live',
1989
1893
  }
1990
1894
  }
@@ -2049,7 +1953,7 @@ export function filterDelegationsToSession(
2049
1953
 
2050
1954
  /* ─── Children channel (primary source, delegations-sidebar pattern) ────
2051
1955
  * `api.client.session.children` returns the child sessions of the current
2052
- * session — every pantheon_delegate spawns one (parentID = caller). The
1956
+ * session — every native task() call spawns one (parentID = caller). The
2053
1957
  * pure helpers here turn those children + the md reports into the display
2054
1958
  * list, and navigate to a child session on click. */
2055
1959
 
@@ -2384,7 +2288,7 @@ export function ceilingDelegationList(
2384
2288
  return { visible, hidden: hiddenActive + hiddenTerminal, hiddenActive, hiddenTerminal }
2385
2289
  }
2386
2290
 
2387
- /** Split a display list into native task() rows vs pantheon_delegate rows.
2291
+ /** Split a display list into native task() rows vs pantheon report rows.
2388
2292
  * Native = source 'children-only' (no delegate report); everything else counts
2389
2293
  * as pantheon. Pure — powers the hooks.log line. */
2390
2294
  export function countDelegationSources(entries: readonly DelegationEntry[]): {
@@ -2417,8 +2321,8 @@ export function formatPanelLogLine(
2417
2321
  * report still renders: description from its title, agent from the child
2418
2322
  * itself (fallback 'agent'), state derived from its status, startedAt from
2419
2323
  * time.created. A report-less child is a NATIVE task() child (every
2420
- * child of the current session — pantheon_delegate OR the native `task()`
2421
- * tool — carries parentID = caller), so it gets source 'children-only', a
2324
+ * child of the current session — the native `task()` tool — carries
2325
+ * parentID = caller), so it gets source 'children-only', a
2422
2326
  * per-child alias 'native-<last4 of the child id>' (one identity per native
2423
2327
  * row) instead of a report alias. The 'task nativa' description fallback
2424
2328
  * keeps the row non-empty when the child carries no title.
@@ -2821,7 +2725,7 @@ function View(props: {
2821
2725
 
2822
2726
  // ── Delegations: session.children (PRIMARY) + md reports (enrichment) ──
2823
2727
  // The panel's source of truth is the SDK child-session API
2824
- // (`api.client.session.children`) — every pantheon_delegate spawns a child
2728
+ // (`api.client.session.children`) — every native task() call spawns a child
2825
2729
  // session with parentID = this session, so children ARE the delegation
2826
2730
  // list (delegations-sidebar pattern). The md channel
2827
2731
  // (.pantheon/delegations) enriches each child with alias/agent/description
@@ -3168,16 +3072,15 @@ const tui: TuiPlugin = (api, _options, _meta) => {
3168
3072
  // Vendored feature (MIT): AI subscription usage gauges.
3169
3073
  void setupUsageBar(api) // async init — never blocks sidebar registration
3170
3074
 
3171
- // ── Live delegation store (legacy tool-part tracker, kept as a REFRESH
3075
+ // ── Live delegation store (tool-part tracker, kept as a REFRESH
3172
3076
  // TRIGGER) ──
3173
3077
  // The Delegations panel's PRIMARY source is `api.client.session.children`
3174
3078
  // (delegations-sidebar pattern) — `message.part.updated` may not fire for
3175
- // pantheon_delegate on runtime 1.18.13 (the "(0)" symptom). The tool-part
3176
- // lifecycle below still tracks pantheon_delegate / pantheon_delegation_read
3177
- // parts and bumps a version signal the View subscribes to: when part
3178
- // events DO arrive, the panel re-fetches children. Fail-open: if the
3179
- // events API is unavailable nothing subscribes and the 1s safety poll
3180
- // keeps the panel working (never crash).
3079
+ // the native task tool on runtime 1.18.13 (the "(0)" symptom). The
3080
+ // tool-part lifecycle below still tracks `task` parts and bumps a version
3081
+ // signal the View subscribes to: when part events DO arrive, the panel
3082
+ // re-fetches children. Fail-open: if the events API is unavailable nothing
3083
+ // subscribes and the 1s safety poll keeps the panel working (never crash).
3181
3084
  const [liveVersion, setLiveVersion] = createSignal(0)
3182
3085
  const liveStore: LiveDelegationStore = {
3183
3086
  map: new Map<string, LiveDelegationEntry>(),