pantheon-opencode 1.5.2 → 1.6.0-beta.10

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 (146) hide show
  1. package/.pantheon/code-mode/README.md +4 -2
  2. package/.pantheon/code-mode/checkpoint_session.py +138 -102
  3. package/.pantheon/code-mode/compress-inline.py +102 -57
  4. package/.pantheon/code-mode/manifest.json +2 -8
  5. package/AGENTS.global.md +182 -0
  6. package/AGENTS.md +10 -891
  7. package/CHANGELOG.md +343 -0
  8. package/README.md +577 -79
  9. package/README.pt-BR.md +345 -60
  10. package/bin/pantheon-init.mjs +26 -3
  11. package/commands/pantheon-audit.md +7 -1
  12. package/commands/pantheon-consolidate.md +1 -1
  13. package/commands/pantheon-deepwork.md +14 -3
  14. package/commands/pantheon-focus.md +17 -0
  15. package/commands/pantheon-optimize.md +2 -2
  16. package/commands/pantheon-vision.md +61 -12
  17. package/package.json +30 -11
  18. package/plugin.json +2 -2
  19. package/pyproject.toml +11 -16
  20. package/scripts/build-agents-md.mjs +376 -58
  21. package/scripts/changelog-merge.mjs +161 -0
  22. package/scripts/check-plugin-v2-coverage.mjs +213 -0
  23. package/scripts/ci-validate-yaml.py +7 -5
  24. package/scripts/{code_mode_server.py → code_mode.py} +119 -27
  25. package/scripts/cost.mjs +244 -41
  26. package/scripts/doctor.mjs +236 -29
  27. package/scripts/install/agents-md.mjs +4 -4
  28. package/scripts/install/cli-ui.mjs +1 -1
  29. package/scripts/install/config-migration.mjs +319 -6
  30. package/scripts/install/health-check.mjs +26 -37
  31. package/scripts/install/interactive.mjs +7 -7
  32. package/scripts/install/migrate.mjs +1 -1
  33. package/scripts/install/opencode-version.mjs +246 -4
  34. package/scripts/install/opencode.mjs +627 -179
  35. package/scripts/install/plugin.mjs +43 -9
  36. package/scripts/install/shared.mjs +120 -12
  37. package/scripts/install/vision-prompt.mjs +161 -0
  38. package/scripts/install-mcp.mjs +28 -9
  39. package/scripts/{mcp_persistence_server.py → mcp_persistence.py} +392 -78
  40. package/{src/mcp/mcp_resources_server.py → scripts/mcp_resources.py} +2 -2
  41. package/scripts/{memory_mcp_server.py → memory_mcp.py} +372 -198
  42. package/scripts/postinstall.mjs +0 -7
  43. package/scripts/probe-context-rehydrate.mjs +36 -13
  44. package/scripts/probe-pantheon-cost.mjs +71 -15
  45. package/scripts/prune.mjs +2 -2
  46. package/scripts/run-node-tests.mjs +189 -0
  47. package/scripts/sync-artifacts.mjs +26 -13
  48. package/scripts/sync-tui.mjs +24 -3
  49. package/scripts/test-opencode-v2-sandbox.sh +1913 -0
  50. package/scripts/validate-routing.mjs +43 -3
  51. package/scripts/validate-sandbox-install.mjs +138 -0
  52. package/scripts/versioning.mjs +13 -1
  53. package/scripts/vision-status.mjs +682 -0
  54. package/skills-lock.json +15 -16
  55. package/src/agents/aphrodite.md +20 -37
  56. package/src/agents/apollo.md +7 -3
  57. package/src/agents/athena.md +63 -35
  58. package/src/agents/demeter.md +8 -28
  59. package/src/agents/gaia.md +1 -1
  60. package/src/agents/hephaestus.md +10 -21
  61. package/src/agents/hermes.md +15 -38
  62. package/src/agents/iris.md +1 -1
  63. package/src/agents/mnemosyne.md +23 -60
  64. package/src/agents/nyx.md +1 -1
  65. package/src/agents/prometheus.md +16 -20
  66. package/src/agents/talos.md +10 -2
  67. package/src/agents/themis.md +5 -5
  68. package/src/agents/zeus.md +99 -130
  69. package/src/instructions/agent-return-format.instructions.md +18 -3
  70. package/src/instructions/backend-standards.instructions.md +2 -0
  71. package/src/instructions/frontend-standards.instructions.md +3 -1
  72. package/src/instructions/memory-protocol.instructions.md +11 -110
  73. package/src/instructions/zeus-anti-stall.instructions.md +14 -61
  74. package/src/instructions/zeus-communication-rules.instructions.md +0 -1
  75. package/src/instructions/zeus-council-synthesis.instructions.md +32 -260
  76. package/src/instructions/zeus-memory-operations.instructions.md +26 -0
  77. package/src/instructions/zeus-timeout-retry.instructions.md +40 -87
  78. package/src/mcp/{code_mode_server.py → code_mode.py} +119 -27
  79. package/src/mcp/eval_store.py +2 -2
  80. package/src/mcp/mcp_codemap_module.py +559 -129
  81. package/src/mcp/{mcp_persistence_server.py → mcp_persistence.py} +392 -78
  82. package/{scripts/mcp_resources_server.py → src/mcp/mcp_resources.py} +2 -2
  83. package/src/mcp/{memory_mcp_server.py → memory_mcp.py} +373 -250
  84. package/src/mcp/{pantheon_vision_server.py → pantheon_vision.py} +396 -40
  85. package/src/mcp/requirements-mcp.txt +10 -6
  86. package/src/mcp/requirements-vision.txt +4 -2
  87. package/src/pantheon/cost-command.ts +399 -101
  88. package/src/pantheon/delegation-enforce.ts +10 -1
  89. package/src/pantheon/model-command.ts +18 -7
  90. package/src/pantheon/presets.d.mts +0 -8
  91. package/src/pantheon/presets.mjs +5 -32
  92. package/src/pantheon/todo-enforcer.ts +1 -1
  93. package/src/pantheon/v2-bounded-read.ts +640 -0
  94. package/src/pantheon/v2-delegation-enforce.ts +214 -0
  95. package/src/pantheon/v2-execute-after.ts +186 -0
  96. package/src/pantheon/v2-tools.ts +429 -0
  97. package/src/pantheon/v2-unsupported.d.mts +11 -0
  98. package/src/pantheon/v2-unsupported.mjs +90 -0
  99. package/src/pantheon/v2-vision.ts +622 -0
  100. package/src/pantheon/vision-local.ts +531 -0
  101. package/src/pantheon/vision.ts +1161 -116
  102. package/src/plugin-v2.ts +1244 -288
  103. package/src/plugin.ts +12 -9
  104. package/src/plugins/tui/dist/server.d.ts +1 -2
  105. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  106. package/src/plugins/tui/dist/tui-v2.d.ts +154 -0
  107. package/src/plugins/tui/dist/tui-v2.d.ts.map +1 -0
  108. package/src/plugins/tui/dist/tui-v2.js +659 -0
  109. package/src/plugins/tui/dist/tui-v2.js.map +1 -0
  110. package/src/plugins/tui/dist/tui.d.ts +85 -82
  111. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  112. package/src/plugins/tui/dist/tui.js +72 -59
  113. package/src/plugins/tui/index-v2.ts +6 -0
  114. package/src/plugins/tui/package-lock.json +5504 -1341
  115. package/src/plugins/tui/package.json +25 -15
  116. package/src/plugins/tui/src/index-v2.tsx +734 -0
  117. package/src/plugins/tui/src/index.tsx +106 -85
  118. package/src/plugins/tui/src/opencode-v2.d.ts +13 -0
  119. package/src/plugins/tui/tsconfig.build.json +5 -0
  120. package/src/plugins/tui/tsconfig.typecheck.json +5 -0
  121. package/src/plugins/tui/tsdown.config.ts +22 -2
  122. package/src/plugins/tui/tui-v2.ts +2 -0
  123. package/src/plugins/tui/tui.ts +3 -0
  124. package/src/routing.yml +11 -22
  125. package/src/skills/README.md +34 -39
  126. package/src/skills/agent-coordination/SKILL.md +7 -2
  127. package/src/skills/artifact-management/SKILL.md +12 -5
  128. package/src/skills/auto-continue/SKILL.md +54 -95
  129. package/src/skills/code-review-checklist/SKILL.md +5 -5
  130. package/src/skills/context-compression/SKILL.md +26 -879
  131. package/src/skills/session-goal/SKILL.md +14 -5
  132. package/src/skills/spec-driven-development/SKILL.md +3 -1
  133. package/src/skills/tdd-with-agents/SKILL.md +18 -10
  134. package/.pantheon/code-mode/eval-llm-judge.py +0 -255
  135. package/.pantheon/code-mode/eval-monte-carlo.py +0 -368
  136. package/.pantheon/code-mode/eval-run.py +0 -197
  137. package/.pantheon/code-mode/eval-static.py +0 -321
  138. package/.pantheon/code-mode/session-end-save.py +0 -186
  139. package/.pantheon/code-mode/session-end-save.sh +0 -18
  140. package/opencode.json +0 -58
  141. package/scripts/recover-zenodo-deposition.mjs +0 -146
  142. package/scripts/test-opencode-v1-v2-sandbox.sh +0 -862
  143. package/scripts/validate-zenodo-release.mjs +0 -154
  144. package/scripts/zenodo-artifact.mjs +0 -94
  145. package/scripts/zenodo-release-state.mjs +0 -53
  146. package/src/skills/orchestration-workflow/SKILL.md +0 -311
@@ -0,0 +1,659 @@
1
+ import { createComponent, createElement, createTextNode, effect, insert, insertNode, memo, setProp } from "@opentui/solid";
2
+ import { readFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js";
6
+ import { Plugin } from "@opencode/plugin/tui";
7
+ //#region src/index-v2.tsx
8
+ /** @jsxImportSource @opentui/solid */
9
+ const SINGLETON = Symbol.for("pantheon.tui.v2.loaded");
10
+ /** How often the sidebar re-reads .pantheon/active-preset.json (V1 parity) so
11
+ * `set-tier` changes made while opencode is open show up within ~30s. */
12
+ const PRESET_REFRESH_MS = 3e4;
13
+ /** Running delegations rendered before the "… +N more" line (V1 parity). */
14
+ const DELEGATIONS_VISIBLE_CEILING = 8;
15
+ /** Running-row spinner cadence — matches V1's animation tick. */
16
+ const SPINNER_INTERVAL_MS = 80;
17
+ /** Bounded warm-up: sync at most this many sessions at plugin load. */
18
+ const WARMUP_SESSION_LIMIT = 24;
19
+ /** Sidebar body shown while the host has not published a session yet. */
20
+ const SIDEBAR_WAITING_LABEL = "Aguardando sessão…";
21
+ /** Delegate-completion toast title. */
22
+ const TOAST_TITLE = "Pantheon";
23
+ /** One event name the host publishes on `data.on` — the OpenCode V2 union. */
24
+ /** V2 events that refresh the sidebar. V1 also listened to `session.updated`,
25
+ * but that name is absent from the OpenCode V2 event union (verified against
26
+ * `@opencode/client`'s `OpenCodeEvent`), so the valid events that carry the
27
+ * same state changes are used instead: `session.renamed` /
28
+ * `session.metadata.updated` / `session.moved` (the mutations `session.updated`
29
+ * used to stand for) and `session.idle` (a child finishing). `satisfies` keeps
30
+ * the literals while making a name outside the union a type error (surfaced by
31
+ * `tsc -p tsconfig.build.json`), and the sidebar test asserts the same names
32
+ * against the installed union at runtime. */
33
+ const SIDEBAR_REFRESH_EVENTS = [
34
+ "session.created",
35
+ "session.renamed",
36
+ "session.metadata.updated",
37
+ "session.moved",
38
+ "session.idle",
39
+ "session.status",
40
+ "session.deleted"
41
+ ];
42
+ /** Read .pantheon/active-preset.json — first existing candidate wins; malformed
43
+ * JSON or a `preset` that is missing, empty or "none" → null (no fall-through
44
+ * to lower candidates). */
45
+ async function readActivePresetFile(cwd) {
46
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
47
+ const candidates = [
48
+ join(cwd, ".pantheon", "active-preset.json"),
49
+ join(xdgConfig, "opencode", ".pantheon", "active-preset.json"),
50
+ join(homedir(), ".opencode", ".pantheon", "active-preset.json")
51
+ ];
52
+ for (const candidate of candidates) {
53
+ let raw;
54
+ try {
55
+ raw = await readFile(candidate, "utf8");
56
+ } catch {
57
+ continue;
58
+ }
59
+ try {
60
+ const parsed = JSON.parse(raw);
61
+ const name = parsed && typeof parsed === "object" ? parsed.preset : void 0;
62
+ if (typeof name !== "string" || name.length === 0 || name === "none") return null;
63
+ return {
64
+ name,
65
+ source: "file"
66
+ };
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /** Env leg of resolution: PANTHEON_MODEL_PRESET set and !== 'none' wins. */
74
+ function presetFromEnv(env) {
75
+ const name = env.PANTHEON_MODEL_PRESET;
76
+ if (name !== void 0 && name !== "" && name !== "none") return {
77
+ name,
78
+ source: "env"
79
+ };
80
+ return {
81
+ name: null,
82
+ source: null
83
+ };
84
+ }
85
+ /** Resolve the active preset for the sidebar: env > file > default. */
86
+ async function resolvePresetForTui(env, cwd) {
87
+ const envPreset = presetFromEnv(env);
88
+ if (envPreset.source === "env") return envPreset;
89
+ return await readActivePresetFile(cwd) ?? {
90
+ name: null,
91
+ source: null
92
+ };
93
+ }
94
+ /** `SessionInfo.id` is always a `ses…` handle. Guarded so a placeholder or a
95
+ * foreign id never reaches the status/navigate APIs. */
96
+ function isValidSessionID(id) {
97
+ return typeof id === "string" && id.startsWith("ses");
98
+ }
99
+ function numberOrNull(value) {
100
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
101
+ }
102
+ /** Grace window for a child that has not started yet. V2's `session.status()`
103
+ * returns `idle` both for "created but not started" and for "finished", so an
104
+ * idle/unknown/absent child whose own last activity (`time.updated`, falling
105
+ * back to `time.created`) is this recent still reads as running — otherwise a
106
+ * just-spawned child renders a premature ✓. Trade-off (V1 parity: it had the
107
+ * same window for its absent statuses): a child that finished within the window
108
+ * and reports no `outcome` reads as running until the window closes, at which
109
+ * point it turns terminal and its completion toast fires. A child that reports
110
+ * a terminal `outcome` is never held back by the window. */
111
+ const DELEGATION_CHILD_STATUS_GRACE_MS = 6e4;
112
+ /** One delegated child job as the sidebar renders it. */
113
+ /** Map the host's live session status to a display state. V2 exposes only
114
+ * `"idle" | "running"` through the TUI context (the client union adds `retry`
115
+ * and `busy`); `error`/`failed` are handled defensively for host drift.
116
+ * `idle`, `retry` and unknown/absent statuses are terminal only once the
117
+ * child's own activity is older than the grace window — within it the child
118
+ * reads as running (see `DELEGATION_CHILD_STATUS_GRACE_MS`). An interruption is
119
+ * not part of this union: it arrives as a terminal `outcome`. Pure. */
120
+ function delegationStateFromStatus(status, time, now = Date.now(), graceMs = DELEGATION_CHILD_STATUS_GRACE_MS) {
121
+ switch (status) {
122
+ case "running":
123
+ case "busy": return "running";
124
+ case "error":
125
+ case "failed": return "failed";
126
+ default: {
127
+ const lastActivity = Math.max(time?.updated ?? 0, time?.created ?? 0);
128
+ if (lastActivity > 0 && now - lastActivity < graceMs) return "running";
129
+ return "completed";
130
+ }
131
+ }
132
+ }
133
+ /** Map a V2 child `outcome` to a display state. `interrupted` (user abort,
134
+ * shutdown, superseded, inactivity) is distinct from both success and failure,
135
+ * so an aborted child never renders the ✓ of a completed one. Returns null for
136
+ * an absent/unrecognised outcome so the live status decides. Pure. */
137
+ function delegationStateFromOutcome(outcome) {
138
+ switch (outcome) {
139
+ case "succeeded": return "completed";
140
+ case "failed": return "failed";
141
+ case "interrupted": return "interrupted";
142
+ default: return null;
143
+ }
144
+ }
145
+ /** Build a delegation row from a child session + its live status. A terminal
146
+ * `outcome` wins when present (the only V2 primitive that surfaces a
147
+ * delegation failure/interruption once the status is back to idle); otherwise
148
+ * the live status decides, with the absent/idle grace window applied. Pure. */
149
+ function toDelegationView(child, status, now = Date.now()) {
150
+ const id = typeof child?.id === "string" ? child.id : "";
151
+ const title = typeof child?.title === "string" && child.title !== "" ? child.title : "";
152
+ const agent = typeof child?.agent === "string" && child.agent !== "" ? child.agent : "";
153
+ return {
154
+ id,
155
+ label: title !== "" ? title : agent !== "" ? agent : id.slice(0, 8),
156
+ state: delegationStateFromOutcome(child?.outcome) ?? delegationStateFromStatus(status, child?.time, now),
157
+ startedAt: numberOrNull(child?.time?.created),
158
+ updatedAt: numberOrNull(child?.time?.updated)
159
+ };
160
+ }
161
+ const DELEGATION_TONES = {
162
+ running: "warning",
163
+ completed: "success",
164
+ failed: "error",
165
+ interrupted: "warning"
166
+ };
167
+ /** Whole-row tone: red = failed, green = terminal, yellow = in flight. The
168
+ * glyph stays a redundant channel so the state reads in monochrome. Pure. */
169
+ function delegationStateTone(state) {
170
+ return DELEGATION_TONES[state];
171
+ }
172
+ const SPINNER_FRAMES = [
173
+ "⠋",
174
+ "⠙",
175
+ "⠹",
176
+ "⠸",
177
+ "⠼",
178
+ "⠴",
179
+ "⠦",
180
+ "⠧",
181
+ "⠇",
182
+ "⠏"
183
+ ];
184
+ /** Spinner glyph for a frame counter (incremented every ~80ms while any job is
185
+ * running). Pure. */
186
+ function spinnerFrame(frame) {
187
+ const index = Math.abs(Math.floor(frame)) % SPINNER_FRAMES.length;
188
+ return SPINNER_FRAMES[index] ?? SPINNER_FRAMES[0];
189
+ }
190
+ /** Row marker (`<glyph> `): the animated spinner while running, ✓ when done,
191
+ * ⊘ when interrupted, ✕ on failure. Pure. */
192
+ function delegationRowMarker(state, frame = 0) {
193
+ if (state === "running") return `${spinnerFrame(frame)} `;
194
+ if (state === "failed") return "✕ ";
195
+ if (state === "interrupted") return "⊘ ";
196
+ return "✓ ";
197
+ }
198
+ /** Compact single-unit elapsed label: "12s"/"3m"/"1h"/"2d". Pure. */
199
+ function fmtElapsed(ms) {
200
+ const total = Math.max(0, Math.floor(ms / 1e3));
201
+ const days = Math.floor(total / 86400);
202
+ const hours = Math.floor(total % 86400 / 3600);
203
+ const minutes = Math.floor(total % 3600 / 60);
204
+ const seconds = total % 60;
205
+ if (days > 0) return `${days}d`;
206
+ if (hours > 0) return `${hours}h`;
207
+ if (minutes > 0) return `${minutes}m`;
208
+ return `${seconds}s`;
209
+ }
210
+ /** Elapsed label for one row: a running job ticks `now - startedAt`; a terminal
211
+ * one is fixed at `updatedAt - startedAt` (em dash when a timestamp is
212
+ * missing). Pure. */
213
+ function delegationElapsed(view, now) {
214
+ if (view.state === "running") return view.startedAt === null ? "—" : fmtElapsed(now - view.startedAt);
215
+ if (view.startedAt !== null && view.updatedAt !== null) return fmtElapsed(view.updatedAt - view.startedAt);
216
+ return "—";
217
+ }
218
+ /** Live rows first (a running job never falls behind the cap), then by most
219
+ * recent activity. Pure. */
220
+ function orderDelegations(views) {
221
+ return [...views].sort((a, b) => {
222
+ const activeA = a.state === "running" ? 1 : 0;
223
+ const activeB = b.state === "running" ? 1 : 0;
224
+ if (activeA !== activeB) return activeB - activeA;
225
+ return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
226
+ });
227
+ }
228
+ /** Cap the panel: at most `maxVisible` rows, the remainder collapsing into the
229
+ * "… +N more" line. Pure. */
230
+ function ceilingDelegations(views, maxVisible = 8) {
231
+ const limit = Math.max(0, maxVisible);
232
+ const visible = views.slice(0, limit);
233
+ return {
234
+ visible,
235
+ hidden: Math.max(0, views.length - visible.length)
236
+ };
237
+ }
238
+ /** Session ids to eagerly sync at load (deduped, bounded). Pure. */
239
+ function collectWarmupTargets(sessions, limit = WARMUP_SESSION_LIMIT) {
240
+ const ids = [];
241
+ for (const session of sessions) {
242
+ if (!isValidSessionID(session?.id) || ids.includes(session.id)) continue;
243
+ ids.push(session.id);
244
+ if (ids.length >= limit) break;
245
+ }
246
+ return ids;
247
+ }
248
+ /** Body a sidebar instance renders when the host has not published a real
249
+ * session id yet. OpenCode V2 scopes the `sidebar.content` slot to a session
250
+ * (`SlotMap["sidebar.content"] = { sessionID: string }`), so before the first
251
+ * prompt the slot may not mount at all — see the `home.footer.status`
252
+ * contribution for the pre-prompt degradation. Pure. */
253
+ function sidebarPlaceholder(sessionID) {
254
+ return isValidSessionID(sessionID) ? null : SIDEBAR_WAITING_LABEL;
255
+ }
256
+ /** Navigate to a session through the V2 router. Fail-open: an absent router or
257
+ * a removed session must never break the sidebar. Returns whether a valid
258
+ * navigation was dispatched. Pure. */
259
+ function navigateToSession(router, sessionID) {
260
+ if (typeof router?.navigate !== "function" || !isValidSessionID(sessionID)) return false;
261
+ try {
262
+ router.navigate({
263
+ type: "session",
264
+ sessionID
265
+ });
266
+ return true;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+ /** Diff two delegation observations: entries that were `running` and are no
272
+ * longer running count as completions, fired once per transition. Returns the
273
+ * next snapshot so callers can keep a single tracker. First observation never
274
+ * reports a completion (no toast spam on mount). Pure. */
275
+ function detectDelegationCompletions(previous, current) {
276
+ const snapshot = /* @__PURE__ */ new Map();
277
+ const completed = [];
278
+ for (const entry of current) {
279
+ snapshot.set(entry.id, entry.state);
280
+ if (previous.get(entry.id) === "running" && entry.state !== "running") completed.push(entry);
281
+ }
282
+ return {
283
+ completed,
284
+ snapshot
285
+ };
286
+ }
287
+ function resolveLocation(context) {
288
+ try {
289
+ return context?.location ?? context?.data?.location?.default();
290
+ } catch {
291
+ return;
292
+ }
293
+ }
294
+ function resolveSidebarCwd(context) {
295
+ const location = resolveLocation(context);
296
+ if (location && typeof location.directory === "string" && location.directory !== "") return location.directory;
297
+ return process.cwd();
298
+ }
299
+ function sessionStatusOf(context, sessionID) {
300
+ try {
301
+ const status = context?.data?.session?.status?.(sessionID);
302
+ return typeof status === "string" ? status : void 0;
303
+ } catch {
304
+ return;
305
+ }
306
+ }
307
+ /** Populate the cached data at load so the sidebar has content before the first
308
+ * prompt (Known Issue guard). Fire-and-forget: a missing API or a failed sync
309
+ * degrades silently and the sidebar still renders its static header. */
310
+ function warmUpSidebarData(context, location) {
311
+ try {
312
+ Promise.resolve(context?.data?.location?.vcs?.sync?.(location)).catch(() => {});
313
+ } catch {}
314
+ let listed = [];
315
+ try {
316
+ const result = context?.data?.session?.list?.();
317
+ if (Array.isArray(result)) listed = result;
318
+ } catch {}
319
+ for (const id of collectWarmupTargets(listed)) try {
320
+ Promise.resolve(context?.data?.session?.sync?.(id)).catch(() => {});
321
+ } catch {}
322
+ }
323
+ function showDelegationToast(context, done) {
324
+ const variant = done.state === "failed" ? "error" : done.state === "interrupted" ? "warning" : "success";
325
+ const verb = done.state === "failed" ? "falhou" : done.state === "interrupted" ? "interrompida" : "concluída";
326
+ try {
327
+ context?.ui?.toast?.show?.({
328
+ title: TOAST_TITLE,
329
+ message: `${done.label} ${verb}`,
330
+ variant,
331
+ sessionID: done.id
332
+ });
333
+ } catch {}
334
+ }
335
+ /** Active preset signal with mount + 30s refresh. Shared by the sidebar and the
336
+ * home-screen status contribution. */
337
+ function useActivePreset(context) {
338
+ const [preset, setPreset] = createSignal(presetFromEnv(process.env));
339
+ onMount(() => {
340
+ const cwd = resolveSidebarCwd(context);
341
+ let cancelled = false;
342
+ const refresh = async () => {
343
+ try {
344
+ const info = await resolvePresetForTui(process.env, cwd);
345
+ if (!cancelled) setPreset(info);
346
+ } catch {}
347
+ };
348
+ refresh();
349
+ const timer = setInterval(() => void refresh(), PRESET_REFRESH_MS);
350
+ onCleanup(() => {
351
+ cancelled = true;
352
+ clearInterval(timer);
353
+ });
354
+ });
355
+ return preset;
356
+ }
357
+ /** Reactive state the sidebar view renders. Accessors keep the view reactive
358
+ * while the model itself stays free of JSX, so its effects are exercisable
359
+ * without a TUI renderer. */
360
+ /** Reactive wiring behind the sidebar: refresh subscription, warm syncs, the
361
+ * running-only spinner and the completion-toast effect. `context` stays loosely
362
+ * typed (the host API only exists at runtime) but the refresh event names are
363
+ * narrowed to the V2 union at the subscription site. */
364
+ function createSidebarModel(context, sessionID) {
365
+ const [revision, setRevision] = createSignal(0);
366
+ const [frame, setFrame] = createSignal(0);
367
+ const location = resolveLocation(context);
368
+ const preset = useActivePreset(context);
369
+ let observedStates = /* @__PURE__ */ new Map();
370
+ const refresh = () => setRevision((value) => value + 1);
371
+ const activeSessionID = () => {
372
+ const id = sessionID();
373
+ return isValidSessionID(id) ? id : "";
374
+ };
375
+ const session = createMemo(() => {
376
+ revision();
377
+ const id = activeSessionID();
378
+ if (id === "") return void 0;
379
+ try {
380
+ return context.data.session.get(id);
381
+ } catch {
382
+ return;
383
+ }
384
+ });
385
+ const branch = createMemo(() => {
386
+ revision();
387
+ try {
388
+ return context.data.location.vcs.info(location)?.branch?.current ?? null;
389
+ } catch {
390
+ return null;
391
+ }
392
+ });
393
+ const allDelegations = createMemo(() => {
394
+ revision();
395
+ const id = activeSessionID();
396
+ if (id === "") return [];
397
+ let family = [];
398
+ try {
399
+ const result = context.data.session.family?.(id);
400
+ if (Array.isArray(result)) family = result;
401
+ } catch {
402
+ return [];
403
+ }
404
+ const now = Date.now();
405
+ const views = [];
406
+ for (const childID of family) {
407
+ if (childID === id || !isValidSessionID(childID)) continue;
408
+ const child = context.data.session.get(childID);
409
+ if (child?.parentID !== id) continue;
410
+ views.push(toDelegationView(child, sessionStatusOf(context, childID), now));
411
+ }
412
+ return orderDelegations(views);
413
+ });
414
+ const delegations = createMemo(() => allDelegations().filter((view) => view.state === "running"));
415
+ const ceiling = createMemo(() => ceilingDelegations(delegations(), 8));
416
+ const hasRunning = createMemo(() => delegations().length > 0);
417
+ onMount(() => {
418
+ const stop = [];
419
+ const data = context?.data;
420
+ if (typeof data?.on === "function") for (const type of SIDEBAR_REFRESH_EVENTS) try {
421
+ const unsubscribe = data.on(type, refresh);
422
+ if (typeof unsubscribe === "function") stop.push(unsubscribe);
423
+ } catch {}
424
+ const id = activeSessionID();
425
+ if (id !== "") try {
426
+ Promise.resolve(context.data.session.sync(id)).then(refresh);
427
+ } catch {}
428
+ try {
429
+ Promise.resolve(context.data.location.vcs.sync(location)).then(refresh);
430
+ } catch {}
431
+ createEffect(() => {
432
+ if (!hasRunning()) return;
433
+ const spinner = setInterval(() => setFrame((value) => value + 1), SPINNER_INTERVAL_MS);
434
+ onCleanup(() => clearInterval(spinner));
435
+ });
436
+ onCleanup(() => {
437
+ for (const unsubscribe of stop) if (typeof unsubscribe === "function") unsubscribe();
438
+ });
439
+ });
440
+ createEffect(() => {
441
+ const views = allDelegations();
442
+ if (activeSessionID() === "") {
443
+ observedStates = /* @__PURE__ */ new Map();
444
+ return;
445
+ }
446
+ const { completed, snapshot } = detectDelegationCompletions(observedStates, views.map((view) => ({
447
+ id: view.id,
448
+ label: view.label,
449
+ state: view.state
450
+ })));
451
+ observedStates = snapshot;
452
+ for (const done of completed) showDelegationToast(context, done);
453
+ });
454
+ return {
455
+ activeSessionID,
456
+ session,
457
+ branch,
458
+ delegations,
459
+ ceiling,
460
+ hasRunning,
461
+ frame,
462
+ preset
463
+ };
464
+ }
465
+ function Sidebar(props) {
466
+ const { context } = props;
467
+ const model = createSidebarModel(context, () => props.sessionID);
468
+ const text = () => context.theme.text?.base;
469
+ const muted = () => context.theme.text?.muted ?? text();
470
+ const tone = (value) => context.theme?.text?.feedback?.[value]?.base ?? text();
471
+ const open = (sessionID) => () => navigateToSession(context.ui?.router, sessionID);
472
+ return (() => {
473
+ var _el$ = createElement("box"), _el$2 = createElement("text"), _el$3 = createElement("text");
474
+ insertNode(_el$, _el$2);
475
+ insertNode(_el$, _el$3);
476
+ setProp(_el$, "flexDirection", "column");
477
+ setProp(_el$, "width", "100%");
478
+ setProp(_el$, "padding", 1);
479
+ setProp(_el$2, "attributes", 1);
480
+ insert(_el$2, `Pantheon v1.6.0-beta.10`);
481
+ insertNode(_el$3, createTextNode(`OpenCode V2 · plugin TUI ativo`));
482
+ insert(_el$, createComponent(Show, {
483
+ get when() {
484
+ return model.branch();
485
+ },
486
+ children: (value) => (() => {
487
+ var _el$8 = createElement("text");
488
+ insert(_el$8, () => `Branch: ${value()}`);
489
+ effect((_$p) => setProp(_el$8, "fg", muted(), _$p));
490
+ return _el$8;
491
+ })()
492
+ }), null);
493
+ insert(_el$, createComponent(Show, {
494
+ get when() {
495
+ return model.preset().name;
496
+ },
497
+ get fallback() {
498
+ return (() => {
499
+ var _el$9 = createElement("box"), _el$0 = createElement("text");
500
+ insertNode(_el$9, _el$0);
501
+ setProp(_el$9, "flexDirection", "row");
502
+ insertNode(_el$0, createTextNode(`Preset: default`));
503
+ effect((_$p) => setProp(_el$0, "fg", muted(), _$p));
504
+ return _el$9;
505
+ })();
506
+ },
507
+ children: (name) => (() => {
508
+ var _el$10 = createElement("box"), _el$11 = createElement("text"), _el$13 = createElement("text"), _el$14 = createElement("text");
509
+ insertNode(_el$10, _el$11);
510
+ insertNode(_el$10, _el$13);
511
+ insertNode(_el$10, _el$14);
512
+ setProp(_el$10, "flexDirection", "row");
513
+ insertNode(_el$11, createTextNode(`⚡ Preset: `));
514
+ insert(_el$13, name);
515
+ insert(_el$14, () => ` (${model.preset().source ?? ""})`);
516
+ effect((_p$) => {
517
+ var _v$3 = muted(), _v$4 = text(), _v$5 = muted();
518
+ _v$3 !== _p$.e && (_p$.e = setProp(_el$11, "fg", _v$3, _p$.e));
519
+ _v$4 !== _p$.t && (_p$.t = setProp(_el$13, "fg", _v$4, _p$.t));
520
+ _v$5 !== _p$.a && (_p$.a = setProp(_el$14, "fg", _v$5, _p$.a));
521
+ return _p$;
522
+ }, {
523
+ e: void 0,
524
+ t: void 0,
525
+ a: void 0
526
+ });
527
+ return _el$10;
528
+ })()
529
+ }), null);
530
+ insert(_el$, createComponent(Show, {
531
+ get when() {
532
+ return memo(() => model.activeSessionID() !== "")() && model.session();
533
+ },
534
+ get fallback() {
535
+ return (() => {
536
+ var _el$15 = createElement("text");
537
+ insert(_el$15, () => sidebarPlaceholder(props.sessionID));
538
+ effect((_$p) => setProp(_el$15, "fg", muted(), _$p));
539
+ return _el$15;
540
+ })();
541
+ },
542
+ children: (current) => [(() => {
543
+ var _el$16 = createElement("text");
544
+ insert(_el$16, () => `Sessão: ${current().title || current().id}`);
545
+ effect((_$p) => setProp(_el$16, "fg", text(), _$p));
546
+ return _el$16;
547
+ })(), createComponent(Show, {
548
+ get when() {
549
+ return current().agent;
550
+ },
551
+ get children() {
552
+ var _el$17 = createElement("text");
553
+ insert(_el$17, () => `Agente: ${current().agent}`);
554
+ effect((_$p) => setProp(_el$17, "fg", muted(), _$p));
555
+ return _el$17;
556
+ }
557
+ })]
558
+ }), null);
559
+ insert(_el$, createComponent(Show, {
560
+ get when() {
561
+ return model.delegations().length > 0;
562
+ },
563
+ get children() {
564
+ return [
565
+ (() => {
566
+ var _el$5 = createElement("text");
567
+ insertNode(_el$5, createTextNode(`Delegações`));
568
+ setProp(_el$5, "attributes", 1);
569
+ effect((_$p) => setProp(_el$5, "fg", text(), _$p));
570
+ return _el$5;
571
+ })(),
572
+ createComponent(For, {
573
+ get each() {
574
+ return model.ceiling().visible;
575
+ },
576
+ children: (view) => (() => {
577
+ var _el$18 = createElement("box"), _el$19 = createElement("text");
578
+ insertNode(_el$18, _el$19);
579
+ insert(_el$19, () => `${delegationRowMarker(view.state, model.frame())}${view.label} ${delegationElapsed(view, Date.now())}`);
580
+ effect((_p$) => {
581
+ var _v$6 = open(view.id), _v$7 = tone(delegationStateTone(view.state));
582
+ _v$6 !== _p$.e && (_p$.e = setProp(_el$18, "onMouseDown", _v$6, _p$.e));
583
+ _v$7 !== _p$.t && (_p$.t = setProp(_el$19, "fg", _v$7, _p$.t));
584
+ return _p$;
585
+ }, {
586
+ e: void 0,
587
+ t: void 0
588
+ });
589
+ return _el$18;
590
+ })()
591
+ }),
592
+ createComponent(Show, {
593
+ get when() {
594
+ return model.ceiling().hidden > 0;
595
+ },
596
+ get children() {
597
+ var _el$7 = createElement("text");
598
+ insert(_el$7, () => `… +${model.ceiling().hidden} more`);
599
+ effect((_$p) => setProp(_el$7, "fg", muted(), _$p));
600
+ return _el$7;
601
+ }
602
+ })
603
+ ];
604
+ }
605
+ }), null);
606
+ effect((_p$) => {
607
+ var _v$ = text(), _v$2 = muted();
608
+ _v$ !== _p$.e && (_p$.e = setProp(_el$2, "fg", _v$, _p$.e));
609
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$2, _p$.t));
610
+ return _p$;
611
+ }, {
612
+ e: void 0,
613
+ t: void 0
614
+ });
615
+ return _el$;
616
+ })();
617
+ }
618
+ /** Home-screen status contribution. The sidebar slot is session-scoped, so
619
+ * before the first prompt the sidebar may not mount at all (Known Issue) —
620
+ * this keeps a compact Pantheon line visible on the home footer meanwhile. */
621
+ function HomeStatus(props) {
622
+ const preset = useActivePreset(props.context);
623
+ const muted = () => props.context.theme?.text?.muted ?? props.context.theme?.text?.base;
624
+ return (() => {
625
+ var _el$20 = createElement("text");
626
+ insert(_el$20, () => `Pantheon v1.6.0-beta.10${preset().name ? ` ⚡ ${preset().name}` : ""}`);
627
+ effect((_$p) => setProp(_el$20, "fg", muted(), _$p));
628
+ return _el$20;
629
+ })();
630
+ }
631
+ const plugin = Plugin.define({
632
+ id: "pantheon.tui",
633
+ setup(context) {
634
+ const globalState = globalThis;
635
+ if (globalState[SINGLETON]) return;
636
+ globalState[SINGLETON] = true;
637
+ warmUpSidebarData(context, resolveLocation(context));
638
+ const unregisterSidebar = context.ui.slot({
639
+ append: "sidebar.content",
640
+ render: ({ sessionID }) => createComponent(Sidebar, {
641
+ context,
642
+ sessionID
643
+ })
644
+ });
645
+ const unregisterHome = context.ui.slot({
646
+ append: "home.footer.status",
647
+ render: () => createComponent(HomeStatus, { context })
648
+ });
649
+ return () => {
650
+ if (typeof unregisterSidebar === "function") unregisterSidebar();
651
+ if (typeof unregisterHome === "function") unregisterHome();
652
+ delete globalState[SINGLETON];
653
+ };
654
+ }
655
+ });
656
+ //#endregion
657
+ export { DELEGATIONS_VISIBLE_CEILING, DELEGATION_CHILD_STATUS_GRACE_MS, SIDEBAR_REFRESH_EVENTS, SIDEBAR_WAITING_LABEL, ceilingDelegations, collectWarmupTargets, createSidebarModel, plugin as default, delegationElapsed, delegationRowMarker, delegationStateFromOutcome, delegationStateFromStatus, delegationStateTone, detectDelegationCompletions, fmtElapsed, isValidSessionID, navigateToSession, orderDelegations, presetFromEnv, resolvePresetForTui, sidebarPlaceholder, spinnerFrame, toDelegationView };
658
+
659
+ //# sourceMappingURL=tui-v2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-v2.js","names":[],"sources":[],"mappings":""}