@cleocode/goal 0.0.0-stage → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +319 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -2
  4. package/cli/ocg.ts +149 -0
  5. package/dist/cli.js +8708 -0
  6. package/dist/tui.d.ts +2 -0
  7. package/dist/tui.js +1920 -0
  8. package/package.json +79 -4
  9. package/rpc.ts +2 -0
  10. package/server.ts +3 -0
  11. package/skills/write-goal/SKILL.md +211 -0
  12. package/skills/write-goal/assets/goal.template.yaml +38 -0
  13. package/skills/write-goal/references/examples.md +283 -0
  14. package/skills/write-goal/references/goal-schema.md +53 -0
  15. package/skills/write-goal/references/interview.md +36 -0
  16. package/src/cli/install.ts +125 -0
  17. package/src/cli/status.ts +116 -0
  18. package/src/contract/parse.ts +454 -0
  19. package/src/contract/render.ts +134 -0
  20. package/src/contract/types.ts +158 -0
  21. package/src/engine/amendment.ts +51 -0
  22. package/src/engine/amendments.ts +72 -0
  23. package/src/engine/cleo-bridge.ts +339 -0
  24. package/src/engine/cleo-link.ts +150 -0
  25. package/src/engine/continue-work.ts +131 -0
  26. package/src/engine/decisions.ts +44 -0
  27. package/src/engine/dogfood-record.ts +278 -0
  28. package/src/engine/handoff.ts +102 -0
  29. package/src/engine/interrupt-policy.ts +45 -0
  30. package/src/engine/inventory.ts +70 -0
  31. package/src/engine/launches.ts +82 -0
  32. package/src/engine/loop-stop.ts +50 -0
  33. package/src/engine/loop.ts +109 -0
  34. package/src/engine/next-work.ts +82 -0
  35. package/src/engine/owner-intent.ts +71 -0
  36. package/src/engine/owner-reviews.ts +178 -0
  37. package/src/engine/peer-announce.ts +67 -0
  38. package/src/engine/peer-transport.ts +78 -0
  39. package/src/engine/policy.ts +42 -0
  40. package/src/engine/registry.ts +119 -0
  41. package/src/engine/similarity.ts +65 -0
  42. package/src/engine/stale-decisions.ts +68 -0
  43. package/src/engine/state.ts +185 -0
  44. package/src/engine/store.ts +263 -0
  45. package/src/engine/tick-record.ts +129 -0
  46. package/src/engine/tick.ts +207 -0
  47. package/src/rpc.ts +182 -0
  48. package/src/server/app.ts +4320 -0
  49. package/src/server/help.ts +50 -0
  50. package/src/server/index.ts +23 -0
  51. package/src/server/intent.ts +22 -0
  52. package/src/server/messages.ts +228 -0
  53. package/src/summary/audit.ts +76 -0
  54. package/src/summary/build.ts +314 -0
  55. package/src/tui/dashboard.ts +322 -0
  56. package/src/tui/decision-form.ts +12 -0
  57. package/src/tui/format.ts +127 -0
  58. package/src/tui/index.tsx +718 -0
  59. package/src/tui/manager.ts +94 -0
  60. package/src/tui/navigation.ts +11 -0
  61. package/src/tui/pointer-select.ts +20 -0
  62. package/src/tui/tracer.ts +51 -0
  63. package/src/util/git.ts +102 -0
  64. package/src/util/ids.ts +20 -0
  65. package/src/util/shell.ts +42 -0
  66. package/src/verify/artifact.ts +95 -0
  67. package/src/verify/pipeline.ts +200 -0
  68. package/tui.tsx +2 -0
package/dist/tui.js ADDED
@@ -0,0 +1,1920 @@
1
+ // @bun
2
+ // src/tui/index.tsx
3
+ import { use as _$use } from "@opentui/solid";
4
+ import { memo as _$memo } from "@opentui/solid";
5
+ import { createComponent as _$createComponent } from "@opentui/solid";
6
+ import { effect as _$effect } from "@opentui/solid";
7
+ import { insert as _$insert } from "@opentui/solid";
8
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
9
+ import { insertNode as _$insertNode } from "@opentui/solid";
10
+ import { setProp as _$setProp } from "@opentui/solid";
11
+ import { createElement as _$createElement } from "@opentui/solid";
12
+ import { createEffect, createSignal, For, onCleanup, Show, untrack } from "solid-js";
13
+ import { createStore, reconcile } from "solid-js/store";
14
+
15
+ // src/rpc.ts
16
+ var anyObject = { type: "object", additionalProperties: true };
17
+ var targetObject = { type: "object", properties: { key: { type: "string", minLength: 1, maxLength: 256 }, lock: { type: "string", minLength: 64, maxLength: 64 }, runId: { type: "string", minLength: 1, maxLength: 128 }, sessionID: { type: "string", minLength: 1, maxLength: 256 } }, required: ["key", "lock"], additionalProperties: false };
18
+ var GoalRpc = {
19
+ id: "opencode-goal",
20
+ methods: {
21
+ provenance: {
22
+ input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 } }, additionalProperties: false },
23
+ output: anyObject
24
+ },
25
+ snapshot: {
26
+ input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 } }, required: ["sessionID"], additionalProperties: false },
27
+ output: anyObject
28
+ },
29
+ list: {
30
+ input: { type: "object", properties: {}, additionalProperties: false },
31
+ output: anyObject
32
+ },
33
+ details: {
34
+ input: { type: "object", properties: { key: { type: "string", minLength: 1, maxLength: 256 } }, required: ["key"], additionalProperties: false },
35
+ output: anyObject
36
+ },
37
+ review: {
38
+ input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 }, decisionId: { type: "string", minLength: 1, maxLength: 256 } }, required: ["sessionID"], additionalProperties: false },
39
+ output: anyObject
40
+ },
41
+ manage: {
42
+ input: { type: "object", properties: { sessionID: { type: "string", minLength: 1, maxLength: 256 }, action: { type: "string", enum: ["pause", "resume", "verify", "attach", "abort", "archive"] }, target: targetObject }, required: ["sessionID", "action", "target"], additionalProperties: false },
43
+ output: anyObject
44
+ },
45
+ act: {
46
+ input: {
47
+ type: "object",
48
+ properties: {
49
+ sessionID: { type: "string", minLength: 1, maxLength: 256 },
50
+ action: { type: "string", enum: ["pause", "resume", "abort", "verify", "approve", "reject", "amend", "archive", "attach", "start", "start-next", "decompose"] },
51
+ arg: { type: "string", maxLength: 2000 },
52
+ decisionId: { type: "string", minLength: 1, maxLength: 256 }
53
+ },
54
+ required: ["sessionID", "action"],
55
+ additionalProperties: false
56
+ },
57
+ output: anyObject
58
+ }
59
+ },
60
+ events: {
61
+ handoff: { schema: { type: "object", properties: { fromSessionID: { type: "string" }, toSessionID: { type: "string" }, slug: { type: "string" }, runId: { type: "string" }, fallback: { type: "string" } }, required: ["fromSessionID", "toSessionID", "slug", "runId"], additionalProperties: false } },
62
+ updated: { schema: { type: "object", properties: { sessionID: { type: "string" }, view: anyObject }, required: ["sessionID"], additionalProperties: true } },
63
+ notice: { schema: { type: "object", properties: { sessionID: { type: "string" }, level: { type: "string" }, text: { type: "string" }, attention: { type: "string" } }, required: ["text"], additionalProperties: true } },
64
+ summary: { schema: { type: "object", properties: { sessionID: { type: "string" }, slug: { type: "string" }, status: { type: "string" }, headline: { type: "string" }, title: { type: "string" }, digest: { type: "string" }, text: { type: "string" } }, required: ["sessionID", "text"], additionalProperties: true } },
65
+ decision: {
66
+ schema: {
67
+ type: "object",
68
+ properties: {
69
+ sessionID: { type: "string" },
70
+ slug: { type: "string" },
71
+ kind: { type: "string", enum: ["needs_review", "paused-after-verdict", "blocked", "budget_limited", "amend-proposed", "supersede-ack", "complete", "start-picker", "owner-operation"] },
72
+ title: { type: "string" },
73
+ message: { type: "string" },
74
+ question: { type: "string" },
75
+ expiresAt: { type: "number" },
76
+ choices: { type: "array", items: { type: "object", properties: { label: { type: "string" }, act: { type: "string" }, arg: { type: "string" }, run: { type: "string" } }, required: ["label"], additionalProperties: true } }
77
+ },
78
+ required: ["sessionID", "kind", "message", "choices"],
79
+ additionalProperties: true
80
+ }
81
+ },
82
+ "decision.resolved": {
83
+ schema: {
84
+ type: "object",
85
+ properties: {
86
+ sessionID: { type: "string" },
87
+ kind: { type: "string" },
88
+ decisionId: { type: "string" },
89
+ via: { type: "string", enum: ["form", "dialog", "form-guidance", "amendment-invalidated"] }
90
+ },
91
+ required: ["sessionID", "kind", "via"],
92
+ additionalProperties: true
93
+ }
94
+ },
95
+ "decision.failed": {
96
+ schema: { type: "object", properties: { sessionID: { type: "string" }, decisionId: { type: "string" }, message: { type: "string" } }, required: ["sessionID", "decisionId", "message"], additionalProperties: false }
97
+ }
98
+ }
99
+ };
100
+
101
+ // src/tui/format.ts
102
+ var STATUS = {
103
+ running: { icon: "\u25B6", label: "running", tone: "success" },
104
+ waiting: { icon: "\u25F7", label: "waiting", tone: "info" },
105
+ verifying: { icon: "\u25D0", label: "verifying", tone: "info" },
106
+ paused: { icon: "\u23F8", label: "paused", tone: "warning" },
107
+ blocked: { icon: "\u26D4", label: "blocked", tone: "error" },
108
+ needs_review: { icon: "\u2691", label: "needs review", tone: "warning" },
109
+ budget_limited: { icon: "\u2298", label: "budget used", tone: "warning" },
110
+ complete: { icon: "\u2713", label: "complete", tone: "success" },
111
+ failed: { icon: "\u2717", label: "failed", tone: "error" },
112
+ aborted: { icon: "\u25A0", label: "aborted", tone: "muted" },
113
+ superseded: { icon: "\u21E2", label: "superseded", tone: "muted" }
114
+ };
115
+ var statusOf = (s) => STATUS[s] ?? { icon: "\xB7", label: s, tone: "muted" };
116
+ var fit = (text, width) => {
117
+ const flat = text.replace(/\s+/g, " ").trim();
118
+ return flat.length <= width ? flat : `${flat.slice(0, Math.max(1, width - 1))}\u2026`;
119
+ };
120
+ function fmtDuration(ms) {
121
+ const s = Math.max(0, Math.floor(ms / 1000));
122
+ if (s < 60)
123
+ return `${s}s`;
124
+ const m = Math.floor(s / 60);
125
+ if (m < 60)
126
+ return `${m}m`;
127
+ const h = Math.floor(m / 60);
128
+ return `${h}h${m % 60 ? ` ${String(m % 60).padStart(2, "0")}m` : ""}`;
129
+ }
130
+ var fmtTokens = (n) => n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
131
+ var elapsed = (view, now) => view.activeMs + (view.activeSince !== null ? Math.max(0, now - view.activeSince) : 0);
132
+ function bar(done, total, width = 10) {
133
+ if (total <= 0)
134
+ return "";
135
+ const filled = Math.round(done / total * width);
136
+ return "\u25A0".repeat(filled) + "\u25A1".repeat(width - filled);
137
+ }
138
+ var proven = (view) => view.criteria.filter((c) => c.status === "pass").length;
139
+ function usageParts(view, now) {
140
+ const limit = (name) => view.budget.find((b) => b.name === name)?.limit;
141
+ const time = `turn ${view.turn}${limit("turns") ? `/${limit("turns")}` : ""} \xB7 ${fmtDuration(elapsed(view, now))}${limit("wall") ? `/${fmtDuration(limit("wall"))}` : ""}`;
142
+ const spend = [];
143
+ if (view.usage.tokens || limit("tokens"))
144
+ spend.push(`${fmtTokens(view.usage.tokens)}${limit("tokens") ? `/${fmtTokens(limit("tokens"))}` : ""} tok`);
145
+ if (view.usage.cost || limit("cost"))
146
+ spend.push(`$${view.usage.cost.toFixed(2)}${limit("cost") ? `/$${limit("cost")}` : ""}`);
147
+ return { time, ...spend.length ? { spend: spend.join(" \xB7 ") } : {} };
148
+ }
149
+ var usageLine = (view, now) => {
150
+ const u = usageParts(view, now);
151
+ return u.spend ? `${u.time} \xB7 ${u.spend}` : u.time;
152
+ };
153
+ function pillText(view, now) {
154
+ const st = statusOf(view.status);
155
+ const steps = view.steps ?? [];
156
+ const plan = steps.length ? `${steps.filter((s) => s.status === "done").length}/${steps.length} plan \xB7 ` : "";
157
+ return `\u25CE ${st.icon} ${plan}${proven(view)}/${view.criteria.length} proven \xB7 t${view.turn} \xB7 ${fmtDuration(elapsed(view, now))}`;
158
+ }
159
+ var RESUME = "/goal resume";
160
+ function endStateCopy(status, slug) {
161
+ switch (status) {
162
+ case "complete":
163
+ return { text: `\u25CE \u2713 complete \u2014 the loop has stopped. Follow-ups: the Goal panel (F9) and .opencode/goals/${slug}/`, tone: "success" };
164
+ case "failed":
165
+ return { text: `\u25CE \u2717 failed \u2014 the loop has stopped. What happened: the Goal panel (F9) and .opencode/goals/${slug}/`, tone: "error" };
166
+ case "aborted":
167
+ return { text: `\u25CE \u25A0 aborted \u2014 the loop has stopped. History stays in .opencode/goals/${slug}/`, tone: "muted" };
168
+ default:
169
+ return;
170
+ }
171
+ }
172
+ function bannerText(view) {
173
+ const st = statusOf(view.status);
174
+ const terminal = endStateCopy(view.status, view.slug);
175
+ if (terminal && view.status !== "running")
176
+ return terminal;
177
+ if (view.awaitingUser && ["running", "waiting", "verifying"].includes(view.status))
178
+ return { text: "\u25CE The goal is waiting for your answer.", tone: "warning" };
179
+ switch (view.status) {
180
+ case "paused":
181
+ return { text: `\u25CE ${st.icon} Goal paused \u2014 ${view.reason ?? ""} \xB7 ${RESUME}`, tone: "warning" };
182
+ case "blocked":
183
+ return { text: `\u25CE ${st.icon} Goal blocked \u2014 ${view.blocker?.reason ?? view.reason ?? ""} \xB7 fix it, then ${RESUME}`, tone: "error" };
184
+ case "needs_review":
185
+ return { text: `\u25CE ${st.icon} Goal needs review \u2014 ${view.reason ?? ""} \xB7 /goal approve <id> or ${RESUME}`, tone: "warning" };
186
+ case "budget_limited":
187
+ return { text: `\u25CE ${st.icon} Goal stopped at its budget \u2014 raise budget in goal.yaml and /goal start, or /goal abort`, tone: "warning" };
188
+ default:
189
+ return;
190
+ }
191
+ }
192
+ function statusText(view, now) {
193
+ const st = statusOf(view.status);
194
+ const lines = [`${view.title} \u2014 ${st.icon} ${st.label}${view.reason ? ` (${view.reason})` : ""}`, `Outcome: ${view.outcome}`, usageLine(view, now), ""];
195
+ for (const c of view.criteria)
196
+ lines.push(`${c.status === "pass" ? "\u2713" : c.status === "fail" ? "\u2717" : c.status === "claimed" ? "\u25D0" : "\xB7"} ${c.id}${c.invariant ? " (invariant)" : ""} ${c.statement}${c.detail && c.status !== "pass" ? `
197
+ ${c.detail.split(`
198
+ `)[0]}` : ""}`);
199
+ if (view.steps.length) {
200
+ lines.push("");
201
+ for (const s of view.steps)
202
+ lines.push(`${s.status === "done" ? "\u2713" : s.status === "active" ? "\u25B8" : "\xB7"} ${s.id} ${s.title}`);
203
+ }
204
+ if (view.progress)
205
+ lines.push("", `Last progress: ${view.progress.note}${view.progress.next ? ` \u2192 ${view.progress.next}` : ""}`);
206
+ if (view.verdict)
207
+ lines.push("", `Last verdict (turn ${view.verdict.turn}): ${view.verdict.passed ? "passed" : "failed"}`, ...view.verdict.lines.map((l) => ` ${l.split(`
208
+ `)[0]}`));
209
+ return lines.join(`
210
+ `);
211
+ }
212
+
213
+ // src/tui/tracer.ts
214
+ var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
215
+ var fmtCountdown = (ms) => {
216
+ const s = Math.max(0, Math.ceil(ms / 1000));
217
+ if (s < 60)
218
+ return `${s}s`;
219
+ const m = Math.floor(s / 60);
220
+ return `${m}m ${String(s % 60).padStart(2, "0")}s`;
221
+ };
222
+ var spinFrame = (now) => SPINNER[Math.floor(now / 120) % SPINNER.length];
223
+ function tracerLines(view, now, width = 40) {
224
+ const a = view.activity;
225
+ if (!["running", "waiting", "verifying"].includes(view.status))
226
+ return [];
227
+ if (!a) {
228
+ return [{ text: fit(`\u25CE goal ${view.status} \xB7 turn ${view.turn}`, width), tone: "muted" }];
229
+ }
230
+ const elapsed = Math.max(0, Math.floor((now - a.since) / 1000));
231
+ const elapsedText = `${fmtDuration(elapsed * 1000)}`;
232
+ const left = a.until ? Math.max(0, a.until - now) : undefined;
233
+ const leftText = left !== undefined ? fmtCountdown(left) : undefined;
234
+ switch (a.kind) {
235
+ case "verifier-child":
236
+ return [{ text: fit(`${spinFrame(now)} verifier child running \u2014 ${elapsedText}${a.detail ? ` \xB7 ${a.detail}` : ""}`, width), tone: "info" }];
237
+ case "verifying":
238
+ return [{ text: fit(`${spinFrame(now)} verifying the claim \u2014 ${elapsedText}${a.detail ? ` \xB7 ${a.detail}` : " (host checks, then the verifier child)"}`, width), tone: "info" }];
239
+ case "waiting":
240
+ return [{ text: fit(`\u25F7 waiting \u2014 ${leftText ?? elapsedText}${left !== undefined ? " left" : ""}${a.detail ? ` \xB7 ${a.detail}` : ""}`, width), tone: "info" }];
241
+ case "cooldown":
242
+ return [{ text: fit(`\xB7 cooldown \u2014 next turn in ${leftText ?? "\u2026"}`, width), tone: "muted" }];
243
+ case "turn":
244
+ default:
245
+ return [{ text: fit(`${spinFrame(now)} turn ${view.turn} in progress \u2014 ${elapsedText}${a.detail ? ` \xB7 ${a.detail}` : ""}`, width), tone: "success" }];
246
+ }
247
+ }
248
+
249
+ // src/tui/dashboard.ts
250
+ var DASHBOARD_TABS = ["now", "progress", "decisions", "goals"];
251
+ var STATUS2 = {
252
+ running: { icon: "\u25B6", label: "running", tone: "success" },
253
+ waiting: { icon: "\u25F7", label: "waiting", tone: "info" },
254
+ verifying: { icon: "\u25D0", label: "verifying", tone: "info" },
255
+ paused: { icon: "\u23F8", label: "paused", tone: "warning" },
256
+ blocked: { icon: "\u26D4", label: "blocked", tone: "error" },
257
+ needs_review: { icon: "\u2691", label: "needs review", tone: "warning" },
258
+ budget_limited: { icon: "\u2298", label: "budget used", tone: "warning" },
259
+ complete: { icon: "\u2713", label: "complete", tone: "success" },
260
+ failed: { icon: "\u2717", label: "failed", tone: "error" },
261
+ aborted: { icon: "\u25A0", label: "aborted", tone: "muted" },
262
+ superseded: { icon: "\u21E2", label: "superseded", tone: "muted" }
263
+ };
264
+ var statusOf2 = (s) => STATUS2[s] ?? { icon: "\xB7", label: s, tone: "muted" };
265
+ var provenCount = (view) => view.criteria.filter((c) => c.status === "pass").length;
266
+ function cardCompactLines(view, now, width = 40) {
267
+ const st = statusOf2(view.status);
268
+ const lines = [];
269
+ const head = "\u25CE Goal";
270
+ const right = `${st.icon} ${st.label}`;
271
+ lines.push({ text: `${head}${" ".repeat(Math.max(1, width - head.length - right.length))}${right}`, tone: st.tone, bold: true });
272
+ if (view.actionRequired)
273
+ lines.push({ text: fit(`\u2691 ${view.actionRequired}`, width), tone: "error", bold: true, tab: "decisions" });
274
+ lines.push({ text: fit(view.title, width), tone: "base", bold: true, tab: "now" });
275
+ const total = view.criteria.length;
276
+ lines.push({ text: `criteria ${bar(provenCount(view), total)} ${provenCount(view)}/${total}`, tone: provenCount(view) === total && total ? "success" : "muted" });
277
+ const focus = [...view.criteria].sort((a, b) => a.status === "fail" ? -1 : b.status === "fail" ? 1 : a.status === "pass" ? 1 : b.status === "pass" ? -1 : 0).slice(0, 3);
278
+ for (const c of focus) {
279
+ const mark = c.status === "pass" ? "\u2713" : c.status === "fail" ? "\u2717" : "\xB7";
280
+ const tone = c.status === "pass" ? "success" : c.status === "fail" ? "error" : "muted";
281
+ lines.push({ text: fit(`${mark} ${c.id} ${c.statement}`, width), tone, tab: "progress" });
282
+ }
283
+ if (view.steps.length) {
284
+ const current = view.steps.find((s) => s.status === "active") ?? view.steps.find((s) => s.status !== "done");
285
+ if (current)
286
+ lines.push({ text: fit(`\u25B8 ${current.id} ${current.title}`, width), tone: "base", tab: "now" });
287
+ }
288
+ const tokens = view.usage.tokens ? ` \xB7 ${fmtTokens(view.usage.tokens)} tok` : "";
289
+ lines.push({ text: fit(`turn ${view.turn}${tokens}`, width), tone: view.budgetRatio >= 0.8 ? "warning" : "muted" });
290
+ const stopped = endStateCopy(view.status, view.slug);
291
+ if (stopped)
292
+ lines.push({ text: fit(`\u25A0 loop stopped \u2014 follow-ups: .opencode/goals/${view.slug}/`, width), tone: stopped.tone });
293
+ else if (view.progress)
294
+ lines.push({ text: fit(`\u21BB ${view.progress.note}`, width), tone: "muted", tab: "now" });
295
+ const tabs = [...new Set(lines.filter((l) => l.tab && l.text.includes("\u2026")).map((l) => l.tab))];
296
+ const affordance = tabs.length ? `\u21B3 F9 \u2192 ${TAB_LABEL[tabs[0]]}${tabs.length > 1 ? ` (+${tabs.length - 1})` : ""}` : "\u21B3 full card \u2014 F9";
297
+ lines.push({ text: fit(affordance, width), tone: "info" });
298
+ return lines.slice(0, 12);
299
+ }
300
+ var wrap = (text, width) => {
301
+ const flat = text.replace(/\s+/g, " ").trim();
302
+ if (!flat)
303
+ return [];
304
+ if (flat.length <= width)
305
+ return [flat];
306
+ const out = [];
307
+ let line = "";
308
+ for (const word of flat.split(" ")) {
309
+ if (!line.length)
310
+ line = word;
311
+ else if (line.length + 1 + word.length <= width)
312
+ line += ` ${word}`;
313
+ else {
314
+ out.push(line);
315
+ line = word;
316
+ }
317
+ }
318
+ if (line.length)
319
+ out.push(line);
320
+ return out.flatMap((l) => l.length <= width ? [l] : l.match(new RegExp(`.{1,${width}}`, "g")) ?? [l]);
321
+ };
322
+ var TAB_LABEL = { now: "Now", progress: "Progress", decisions: "Decisions", goals: "Goals" };
323
+ function cardAffordanceTab(view, now, width = 40) {
324
+ const order = ["decisions", "progress", "now"];
325
+ const truncated = new Set(cardCompactLines(view, now, width).filter((l) => l.tab && l.text.includes("\u2026")).map((l) => l.tab));
326
+ return order.find((t) => truncated.has(t));
327
+ }
328
+ var humanKinds = new Set(["verdict", "approve", "reject", "amend-proposed", "amended", "blocked", "complete", "summary", "flag", "attached", "paused", "resumed", "aborted", "budget_limited", "superseded-by", "approve ", "reject "]);
329
+ function quickActRows(view) {
330
+ const rows = [];
331
+ if (["running", "waiting", "verifying"].includes(view.status)) {
332
+ rows.push({ label: "Pause the goal", description: "stop the loop; /goal resume picks it up", act: "pause" });
333
+ rows.push({ label: "Verify now", description: "run the host checks + verifier immediately", act: "verify" });
334
+ } else if (view.status === "paused") {
335
+ rows.push({ label: "Resume the goal", description: view.reason ?? "continue the loop", act: "resume" });
336
+ rows.push({ label: "Verify now", description: "run the host checks + verifier immediately", act: "verify" });
337
+ } else if (view.status === "blocked") {
338
+ rows.push({ label: "Resolved \u2014 resume", description: view.blocker?.reason ?? "the blocker is fixed", act: "resume" });
339
+ rows.push({ label: "Abort the goal", description: "stop for good; history stays", act: "abort" });
340
+ } else if (view.status === "needs_review") {
341
+ const unproven = view.criteria.filter((c) => !c.invariant && c.status !== "pass");
342
+ for (const c of unproven.slice(0, 3))
343
+ rows.push({ label: `Approve ${c.id} (final)`, description: c.statement, act: "approve", arg: c.id });
344
+ rows.push({ label: "Verify now", description: "run the host checks + verifier immediately", act: "verify" });
345
+ } else if (view.status === "budget_limited") {
346
+ rows.push({ label: "Abort the goal", description: "stop at the budget", act: "abort" });
347
+ } else if (isTerminalStatus(view.status)) {
348
+ rows.push({ label: "Archive the goal", description: "demote to goals-archive/ (history intact)", act: "archive" });
349
+ rows.push({ label: "Start the next goal", description: "open the write-goal interview", act: "start-next" });
350
+ }
351
+ rows.push({ label: "Pause/Resume/Verify are also one keystroke away", description: "/goal pause \xB7 /goal resume \xB7 /goal verify \u2014 or the palette (ctrl+p \u2192 Goal)" });
352
+ return rows;
353
+ }
354
+ var TERMINAL_STATUSES = new Set(["complete", "failed", "aborted", "superseded"]);
355
+ var isTerminalStatus = (s) => TERMINAL_STATUSES.has(s);
356
+ var humanizedEvents = (view, n) => {
357
+ const relevant = view.timeline.filter((e) => humanKinds.has(e.kind) || /^(owner|verdict|goal complete|summary|blocked|amended|flag)/i.test(e.text));
358
+ return relevant.slice(-n).reverse();
359
+ };
360
+ function decisionRows(view) {
361
+ const rows = [];
362
+ if (view.actionRequired) {
363
+ if (view.status === "needs_review") {
364
+ const unproven = view.criteria.filter((c) => !c.invariant && c.status !== "pass");
365
+ for (const c of unproven)
366
+ rows.push({ label: `Approve ${c.id} (final)`, description: c.statement, act: "approve", arg: c.id });
367
+ rows.push({ label: "Reject a criterion", description: "/goal reject <C#> <why>", act: "reject" });
368
+ } else if (view.status === "blocked") {
369
+ rows.push({ label: "Resolved \u2014 resume", description: view.blocker?.reason ?? "the blocker is fixed", act: "resume" });
370
+ rows.push({ label: "Abort the goal", description: "stop for good; history stays", act: "abort" });
371
+ } else if (view.status === "budget_limited") {
372
+ rows.push({ label: "Raise budget and amend", description: "Describe the budget change in the Composer; review the agent's staged diff" });
373
+ rows.push({ label: "Abort the goal", description: "stop at the budget", act: "abort" });
374
+ }
375
+ }
376
+ if (view.status === "paused")
377
+ rows.push({ label: "Resume the goal", description: view.reason ?? "", act: "resume" });
378
+ if (view.status === "complete")
379
+ rows.push({ label: "Archive the goal", description: "demote to goals-archive/ (history intact)", act: "archive" });
380
+ if (view.amendments)
381
+ rows.push({ label: "Confirm proposed amendment", description: "/goal amend confirm", act: "amend", arg: "confirm" });
382
+ return rows;
383
+ }
384
+ function dialogDigest(title, text, maxLines = 14, maxCols = 100) {
385
+ const lines = text.split(`
386
+ `).map((l) => l.length > maxCols ? `${l.slice(0, maxCols - 1)}\u2026` : l);
387
+ const head = lines.slice(0, maxLines);
388
+ const rest = lines.length - head.length;
389
+ return [title, ...head, ...rest > 0 ? [`\u2026 +${rest} more lines \u2014 full text: the Goal panel (F9) or .opencode/goals/ evidence`] : []].join(`
390
+ `);
391
+ }
392
+ function dashboardLines(view, tab, now, width = 46, goals = []) {
393
+ const st = statusOf2(view.status);
394
+ const lines = [];
395
+ const emit = (text, tone, bold = false) => {
396
+ for (const part of wrap(text, width))
397
+ lines.push({ text: part, tone, ...bold ? { bold: true } : {} });
398
+ };
399
+ const rule = () => lines.push({ text: "\u2500".repeat(Math.max(8, Math.min(width, 40))), tone: "muted" });
400
+ const tabsHeader = DASHBOARD_TABS.map((t) => t === tab ? `[${TAB_LABEL[t]}]` : ` ${TAB_LABEL[t]} `).join("");
401
+ lines.push({ text: fit(`\u25CE GOAL ${st.icon} ${st.label}`, width), tone: st.tone, bold: true });
402
+ lines.push({ text: fit(tabsHeader, width), tone: "base", bold: true });
403
+ rule();
404
+ lines.push({ text: "", tone: "base" });
405
+ if (tab === "now") {
406
+ emit(view.title, "base", true);
407
+ emit(view.outcome, "muted");
408
+ lines.push(...tracerLines(view, now, width));
409
+ const current = view.steps.find((s) => s.status === "active") ?? view.steps.find((s) => s.status !== "done");
410
+ if (current)
411
+ emit(`\u25B8 step ${current.id} ${current.title}`, "base");
412
+ if (view.progress?.next)
413
+ emit(`next: ${view.progress.next}`, "muted");
414
+ if (view.actionRequired)
415
+ emit(`\u2691 ${view.actionRequired}`, "error", true);
416
+ else if (["running", "waiting", "verifying"].includes(view.status))
417
+ emit("\u2713 nothing needed from you \u2014 the loop is working", "success");
418
+ const events = humanizedEvents(view, 3);
419
+ if (events.length) {
420
+ lines.push({ text: "recent", tone: "base", bold: true });
421
+ rule();
422
+ for (const e of events)
423
+ emit(`\xB7 ${e.text}`, "muted");
424
+ }
425
+ } else if (tab === "progress") {
426
+ const groups = [
427
+ ["Failed", view.criteria.filter((c) => c.status === "fail"), "error"],
428
+ ["In progress", view.criteria.filter((c) => c.status !== "pass" && c.status !== "fail"), "muted"],
429
+ ["Proven", view.criteria.filter((c) => c.status === "pass"), "success"]
430
+ ];
431
+ emit(`criteria ${bar(provenCount(view), view.criteria.length)} ${provenCount(view)}/${view.criteria.length}`, "base", true);
432
+ for (const [label, items, tone] of groups) {
433
+ if (!items.length)
434
+ continue;
435
+ lines.push({ text: label, tone, bold: true });
436
+ rule();
437
+ for (const c of items)
438
+ emit(`${c.invariant ? "I" : "C"} ${c.id} \u2014 ${c.statement}${c.by ? ` (${c.by})` : ""}`, tone);
439
+ }
440
+ if (view.steps.length) {
441
+ lines.push({ text: "plan", tone: "base", bold: true });
442
+ rule();
443
+ for (const s of view.steps)
444
+ emit(`S ${s.id} ${s.status === "done" ? "\u2713" : s.status === "active" ? "\u25B8" : "\xB7"} ${s.title}`, s.status === "active" ? "info" : s.status === "done" ? "success" : "muted");
445
+ }
446
+ if (view.verdict) {
447
+ lines.push({ text: `last verdict \u2014 ${view.verdict.passed ? "passed" : "failed"}`, tone: view.verdict.passed ? "success" : "warning", bold: true });
448
+ for (const l of view.verdict.lines.slice(0, 3))
449
+ emit(l.split(`
450
+ `)[0], view.verdict.passed ? "success" : "warning");
451
+ }
452
+ } else if (tab === "decisions") {
453
+ lines.push({ text: "act on a row (\u2191\u2193 \xB7 enter)", tone: "base", bold: true });
454
+ lines.push({ text: "", tone: "base" });
455
+ const history = humanizedEvents(view, 6);
456
+ if (history.length) {
457
+ lines.push({ text: "history", tone: "base", bold: true });
458
+ rule();
459
+ for (const e of history)
460
+ emit(`\xB7 ${e.text}`, "muted");
461
+ }
462
+ } else {
463
+ const others = goals.filter((g) => g.slug !== view.slug);
464
+ emit(`this goal: ${view.title} (${st.label})`, "base", true);
465
+ if (others.length) {
466
+ lines.push({ text: "other goals", tone: "base", bold: true });
467
+ rule();
468
+ for (const g of others)
469
+ emit(`\xB7 ${g.slug} \u2014 ${g.title} (${g.status})${g.attachable ? " \xB7 attachable" : ""}`, g.terminal ? "muted" : "info");
470
+ } else
471
+ lines.push({ text: "no other goals in this project", tone: "muted" });
472
+ if (view.steps.length) {
473
+ lines.push({ text: "upcoming steps", tone: "base", bold: true });
474
+ rule();
475
+ for (const s of view.steps.filter((s) => s.status !== "done"))
476
+ emit(`S ${s.id} \xB7 ${s.title}`, "muted");
477
+ }
478
+ }
479
+ lines.push({ text: "", tone: "base" });
480
+ lines.push({ text: fit("C criterion \xB7 I invariant \xB7 S plan step", width), tone: "muted" });
481
+ lines.push({ text: fit("\u2191\u2193/tab/enter to act", width), tone: "muted" });
482
+ return lines;
483
+ }
484
+
485
+ // src/contract/types.ts
486
+ var HOST_KINDS = ["command", "file", "contains", "absent", "diff"];
487
+ var CHECK_KINDS = [...HOST_KINDS, "verifier", "human"];
488
+ var priorityWeight = (p) => p === "high" ? 3 : p === "medium" ? 2 : p === "low" ? 1 : 0;
489
+
490
+ // src/tui/manager.ts
491
+ var terminal = new Set(["complete", "failed", "aborted", "superseded"]);
492
+ var goalKey = (goal) => goal.key ?? `${goal.archived ? "archive" : "live"}:${goal.slug}:${goal.runId ?? "draft"}`;
493
+ var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
494
+ var columns = (text) => Bun.stringWidth(text, { ambiguousIsNarrow: false });
495
+ function fitColumns(text, width) {
496
+ const flat = text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").replace(/\s+/g, " ").trim();
497
+ const limit = Math.max(1, Math.floor(width));
498
+ if (columns(flat) <= limit)
499
+ return flat;
500
+ const suffix = columns("\u2026") <= limit ? "\u2026" : ".";
501
+ let out = "";
502
+ for (const { segment } of graphemes.segment(flat)) {
503
+ if (columns(out + segment + suffix) > limit)
504
+ break;
505
+ out += segment;
506
+ }
507
+ return out + suffix;
508
+ }
509
+ var targetOf = (goal) => ({ key: goalKey(goal), lock: goal.lock ?? "", ...goal.runId ? { runId: goal.runId } : {}, ...goal.sessionID ? { sessionID: goal.sessionID } : {} });
510
+ function launchStatusText(launch, current) {
511
+ if (!launch || launch.status === "succeeded")
512
+ return;
513
+ if (current?.runId && current.sessionID === launch.sessionID && current.slug === launch.slug && current.lock === launch.lock && !terminal.has(current.status))
514
+ return `Goal ${current.slug} \xB7 ${current.status} \xB7 last launch attempt ${launch.status}; existing run unchanged`;
515
+ if (launch.status === "executing")
516
+ return `Starting ${launch.slug} \xB7 ${launch.activity?.detail ?? "owner-approved admission"} \xB7 no run yet`;
517
+ if (launch.status === "pending" || launch.status === "interrupted")
518
+ return `Launch review ${launch.slug} \xB7 pending in Composer \xB7 nothing started`;
519
+ return `Launch ${launch.slug} \xB7 ${launch.status} \xB7 ${launch.result ?? "nothing started; inspect the review"}`;
520
+ }
521
+ function managerActions(goal, actorSessionID, actorHasGoal) {
522
+ const rows = [];
523
+ if (goal.sessionID && goal.sessionState === "present")
524
+ rows.push({ label: "Open goal session", description: "Deliberately jump after viewing these details", action: "jump" });
525
+ if (goal.valid !== false && !goal.archived && goal.runId) {
526
+ if (["running", "waiting", "verifying"].includes(goal.status))
527
+ rows.push({ label: "Pause selected goal", description: "Stop only this goal's loop", action: "pause" });
528
+ if (["paused", "blocked", "needs_review"].includes(goal.status))
529
+ rows.push({ label: "Resume selected goal", description: "Continue this stopped run", action: "resume" });
530
+ if (!terminal.has(goal.status) && goal.status !== "verifying")
531
+ rows.push({ label: "Verify selected goal", description: "Run its declared host checks and verifier", action: "verify" });
532
+ if (goal.attachable && !actorHasGoal && goal.sessionID !== actorSessionID)
533
+ rows.push({ label: "Attach selected goal here", description: "Move its ownership; preserve history and progress", action: "attach" });
534
+ if (!terminal.has(goal.status))
535
+ rows.push({ label: "Abort selected goal", description: "Requires a native confirmation; preserves history", action: "abort" });
536
+ else
537
+ rows.push({ label: "Archive selected goal", description: "Requires a native confirmation; preserves history", action: "archive" });
538
+ }
539
+ rows.push({ label: "Back to all goals", description: "Collapse details without changing sessions", action: "back" });
540
+ return rows;
541
+ }
542
+ function orderedGoals(goals, sessionID) {
543
+ const here = (g) => !g.archived ? g.sessionID === sessionID ? 2 : g.handoff?.fromSessionID === sessionID || !g.runId && g.launch?.sessionID === sessionID ? 1 : 0 : 0;
544
+ return [...goals].sort((a, b) => here(b) - here(a) || priorityWeight(b.priority) - priorityWeight(a.priority) || (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
545
+ }
546
+ function sidebarCards(goals, sessionID, width = 40, maxLines = 12) {
547
+ const active = orderedGoals(goals.filter((g) => !g.archived && !g.terminal && !terminal.has(g.status)), sessionID);
548
+ const capacity = Math.max(1, Math.floor((maxLines - 2) / 5));
549
+ const visible = active.slice(0, capacity);
550
+ return {
551
+ total: active.length,
552
+ hidden: Math.max(0, active.length - visible.length),
553
+ cards: visible.map((goal) => {
554
+ const here = goal.sessionID === sessionID;
555
+ const where = !goal.sessionID ? goal.launch?.sessionID === sessionID ? "author here" : "draft" : here ? "here" : "elsewhere";
556
+ const tone = goal.status === "blocked" ? "error" : ["running", "waiting", "verifying"].includes(goal.status) ? "success" : "warning";
557
+ const steps = goal.steps ?? [];
558
+ const plan = steps.length ? `${steps.filter((s) => s.status === "done").length}/${steps.length} plan \xB7 ` : "";
559
+ const handoffPointer = goal.handoff?.fromSessionID === sessionID && goal.sessionID ? `${fitColumns(goal.slug, Math.max(1, width - 17))} \xB7 ses\u2026${goal.sessionID.slice(-8)}` : undefined;
560
+ return { key: goalKey(goal), goal, tone, title: fitColumns(goal.title, Math.max(1, width - 2)), description: fitColumns(`${goal.status}${goal.launch?.status === "executing" ? " \xB7 starting" : goal.launch?.status === "expired" ? " \xB7 review expired" : ""} \xB7 ${where}`, Math.max(1, width - 2)), progress: fitColumns(`${goal.handoff?.fromSessionID === sessionID ? "from here \xB7 " : ""}${plan}${goal.proven}/${goal.total} proven \xB7 ${goal.agent ?? "agent unknown"}`, width), handoffPointer: handoffPointer ? fitColumns(handoffPointer, width) : undefined };
561
+ }),
562
+ pointer: fitColumns(active.length > visible.length ? `+${active.length - visible.length} ${active.length - visible.length === 1 ? "goal" : "goals"} \u2014 F9 manager` : "F9 goals manager", width)
563
+ };
564
+ }
565
+
566
+ // src/tui/navigation.ts
567
+ function openGoalSession(ui, sessionID) {
568
+ try {
569
+ if (ui.tabs?.enabled() && ui.tabs.open(sessionID) && ui.tabs.focus(sessionID))
570
+ return "tab";
571
+ } catch {}
572
+ ui.router.navigate({ type: "session", sessionID });
573
+ return "route";
574
+ }
575
+
576
+ // src/tui/pointer-select.ts
577
+ function pointerSelect(control, event, rowHeight) {
578
+ if (event.button !== 0 || event.isDragging)
579
+ return;
580
+ const row = Math.floor((event.y - control.y) / rowHeight);
581
+ const visible = Math.max(1, Math.floor(control.height / rowHeight));
582
+ if (row < 0 || row >= visible || event.x < control.x || event.x >= control.x + control.width)
583
+ return;
584
+ const first = Math.max(0, Math.min(control.getSelectedIndex() - Math.floor(visible / 2), control.options.length - visible));
585
+ const index = first + row;
586
+ if (index >= control.options.length)
587
+ return;
588
+ event.stopPropagation();
589
+ event.preventDefault();
590
+ control.focus();
591
+ control.setSelectedIndex(index);
592
+ control.selectCurrent();
593
+ }
594
+
595
+ // src/tui/index.tsx
596
+ var SIDEBAR_WIDTH = 40;
597
+ var PANEL_NAME = "goal-dashboard";
598
+ var tui_default = {
599
+ id: "opencode-goal",
600
+ async setup(context) {
601
+ const rpc = context.client.rpc(GoalRpc);
602
+ const [views, setViews] = createStore({});
603
+ const [launches, setLaunches] = createStore({});
604
+ const currentLaunch = (sessionID) => [...launches[sessionID] ?? []].sort((a, b) => Number(b.status === "executing") - Number(a.status === "executing") || (b.updatedAt ?? 0) - (a.updatedAt ?? 0))[0];
605
+ const [now, setNow] = createSignal(Date.now());
606
+ const timer = setInterval(() => setNow(Date.now()), 1000);
607
+ const requested = new Set;
608
+ const [goalsList, setGoalsList] = createSignal([]);
609
+ const [goalsState, setGoalsState] = createSignal("loading");
610
+ const [selectedGoalKey, setSelectedGoalKey] = createSignal();
611
+ const [selectedDetails, setSelectedDetails] = createSignal();
612
+ const [managerNotice, setManagerNotice] = createSignal();
613
+ let detailRequest = 0;
614
+ let goalsFetchedAt = 0;
615
+ let goalsRequested = false;
616
+ let goalsInFlight = false;
617
+ let goalsRefreshAgain = false;
618
+ let goalsDirectory;
619
+ let disposed = false;
620
+ let focusManagerSection;
621
+ const mountedPanels = new Map;
622
+ const currentMountedPanel = () => {
623
+ const route = context.ui.router.current();
624
+ return route.type === "session" ? mountedPanels.get(route.sessionID) : undefined;
625
+ };
626
+ const locationOf = (sessionID) => (sessionID ? context.data.session.get(sessionID)?.location : undefined) ?? context.location ?? context.data.location?.default();
627
+ const ensureGoals = async (force = false) => {
628
+ goalsRequested = true;
629
+ const route = context.ui.router.current();
630
+ const location = locationOf(route.type === "session" ? route.sessionID : undefined);
631
+ const directory = location?.directory;
632
+ if (goalsDirectory !== directory) {
633
+ goalsDirectory = directory;
634
+ goalsFetchedAt = 0;
635
+ setGoalsList([]);
636
+ setGoalsState("loading");
637
+ setSelectedGoalKey(undefined);
638
+ setSelectedDetails(undefined);
639
+ }
640
+ if (goalsInFlight) {
641
+ if (force)
642
+ goalsRefreshAgain = true;
643
+ return;
644
+ }
645
+ if (!force && Date.now() - goalsFetchedAt < 4000)
646
+ return;
647
+ goalsInFlight = true;
648
+ goalsFetchedAt = Date.now();
649
+ try {
650
+ const listed = await rpc.list({}, location ? {
651
+ location
652
+ } : undefined);
653
+ if (disposed || goalsDirectory !== directory)
654
+ return;
655
+ if (!Array.isArray(listed?.goals))
656
+ throw new Error("Goal inventory missing");
657
+ setGoalsList(listed.goals);
658
+ setGoalsState("ready");
659
+ } catch {
660
+ if (!disposed && goalsDirectory === directory)
661
+ setGoalsState("error");
662
+ } finally {
663
+ goalsInFlight = false;
664
+ if (!disposed && goalsRefreshAgain) {
665
+ goalsRefreshAgain = false;
666
+ ensureGoals(true);
667
+ }
668
+ }
669
+ };
670
+ const STALE_MS = 4000;
671
+ const fetchedAt = new Map;
672
+ const fetchView = async (sessionID) => {
673
+ fetchedAt.set(sessionID, Date.now());
674
+ try {
675
+ const location = locationOf(sessionID);
676
+ const result = await rpc.snapshot({
677
+ sessionID
678
+ }, location ? {
679
+ location
680
+ } : undefined);
681
+ setViews(sessionID, reconcile(result?.view ?? null));
682
+ if (Array.isArray(result?.launches))
683
+ setLaunches(sessionID, reconcile(result.launches));
684
+ } catch {}
685
+ };
686
+ const ensure = (sessionID) => {
687
+ if (requested.has(sessionID))
688
+ return;
689
+ requested.add(sessionID);
690
+ fetchView(sessionID);
691
+ };
692
+ const offUpdated = rpc.events.on("updated", (event) => {
693
+ const data = event.data ?? {}, {
694
+ sessionID,
695
+ view,
696
+ launch
697
+ } = data;
698
+ if (typeof sessionID === "string") {
699
+ if ("view" in data)
700
+ setViews(sessionID, reconcile(view ?? null));
701
+ if (launch && typeof launch.decisionId === "string")
702
+ setLaunches(sessionID, reconcile([...(launches[sessionID] ?? []).filter((p) => p.decisionId !== launch.decisionId), launch]));
703
+ }
704
+ if (goalsRequested)
705
+ ensureGoals(true);
706
+ });
707
+ const offNotice = rpc.events.on("notice", (event) => {
708
+ const data = event.data ?? {};
709
+ const current = context.ui.router.current();
710
+ const here = current.type === "session" && current.sessionID === data.sessionID;
711
+ if (data.panel && here) {
712
+ const view = views[data.sessionID];
713
+ context.ui.dialog.alert({
714
+ title: "Goal",
715
+ message: view ? statusText(view, Date.now()) : String(data.text)
716
+ });
717
+ } else {
718
+ context.ui.toast.show({
719
+ title: "Goal",
720
+ message: String(data.text ?? ""),
721
+ variant: data.level ?? "info",
722
+ duration: data.level === "error" ? 8000 : 5000,
723
+ ...data.sessionID ? {
724
+ sessionID: data.sessionID
725
+ } : {}
726
+ });
727
+ }
728
+ if (data.attention)
729
+ context.attention.notify({
730
+ title: "Goal",
731
+ message: String(data.text ?? ""),
732
+ notification: {
733
+ when: "blurred"
734
+ },
735
+ sound: {
736
+ name: data.attention,
737
+ when: "blurred"
738
+ }
739
+ });
740
+ });
741
+ const openDecisions = new Map;
742
+ const pendingSummaries = new Map;
743
+ const summaryTitle = (data) => {
744
+ const label = String(data.status ?? "summary").replace(/_/g, " ");
745
+ return data.status === "complete" ? `\u2713 Goal complete \u2014 ${data.title ?? label}` : `Goal ${label}`;
746
+ };
747
+ const summaryBody = (data) => String(data.digest ?? dialogDigest(String(data.headline ?? "summary"), String(data.text ?? "")));
748
+ const flushSummary = (sessionID) => {
749
+ const queued = pendingSummaries.get(sessionID);
750
+ pendingSummaries.delete(sessionID);
751
+ if (!queued)
752
+ return;
753
+ context.ui.dialog.alert({
754
+ title: summaryTitle(queued),
755
+ message: summaryBody(queued)
756
+ });
757
+ };
758
+ const offSummary = rpc.events.on("summary", (event) => {
759
+ const data = event.data ?? {};
760
+ const current = context.ui.router.current();
761
+ if (current.type !== "session" || current.sessionID !== data.sessionID) {
762
+ context.ui.toast.show({
763
+ title: "Goal summary",
764
+ message: String(data.headline ?? data.text ?? ""),
765
+ variant: "info",
766
+ ...data.sessionID ? {
767
+ sessionID: data.sessionID
768
+ } : {}
769
+ });
770
+ return;
771
+ }
772
+ if (openDecisions.has(data.sessionID)) {
773
+ pendingSummaries.set(data.sessionID, {
774
+ headline: String(data.headline ?? "summary"),
775
+ text: String(data.text ?? data.headline ?? ""),
776
+ status: String(data.status ?? ""),
777
+ title: data.title ? String(data.title) : undefined,
778
+ digest: data.digest ? String(data.digest) : undefined
779
+ });
780
+ context.ui.toast.show({
781
+ title: "Goal summary queued",
782
+ message: String(data.headline ?? "the full summary opens after the decision"),
783
+ variant: "info",
784
+ ...data.sessionID ? {
785
+ sessionID: data.sessionID
786
+ } : {}
787
+ });
788
+ return;
789
+ }
790
+ context.ui.dialog.alert({
791
+ title: summaryTitle(data),
792
+ message: summaryBody(data)
793
+ });
794
+ });
795
+ const offData = context.data.listen(({
796
+ details
797
+ }) => {
798
+ if (details?.type === "server.connected") {
799
+ for (const id of requested)
800
+ fetchView(id);
801
+ if (goalsRequested)
802
+ ensureGoals(true);
803
+ }
804
+ });
805
+ const offHandoff = rpc.events.on("handoff", (event) => {
806
+ const data = event.data ?? {};
807
+ const route = context.ui.router.current();
808
+ if (typeof data.toSessionID !== "string" || typeof data.fromSessionID !== "string")
809
+ return;
810
+ if (route.type === "session" && route.sessionID === data.fromSessionID)
811
+ openGoalSession(context.ui, data.toSessionID);
812
+ else
813
+ context.ui.toast.show({
814
+ title: "Goal running elsewhere",
815
+ message: `${data.slug} is running in ${data.toSessionID}; original history is preserved.`,
816
+ variant: "info"
817
+ });
818
+ ensureGoals(true);
819
+ });
820
+ const refreshIfStale = (sessionID) => {
821
+ if (Date.now() - (fetchedAt.get(sessionID) ?? 0) <= STALE_MS)
822
+ return;
823
+ fetchedAt.set(sessionID, Date.now());
824
+ fetchView(sessionID);
825
+ };
826
+ const ticker = setInterval(() => {
827
+ for (const id of requested)
828
+ refreshIfStale(id);
829
+ if (goalsRequested)
830
+ ensureGoals();
831
+ }, STALE_MS);
832
+ let panelFocusPending = false;
833
+ const openPanel = () => {
834
+ panelFocusPending = true;
835
+ const id = currentSession();
836
+ const v = id ? views[id] : undefined;
837
+ const target = v ? cardAffordanceTab(v, now(), SIDEBAR_WIDTH) : undefined;
838
+ if (target)
839
+ setTab(target);
840
+ context.ui.panel.open(PANEL_NAME);
841
+ };
842
+ const togglePanel = () => {
843
+ const mounted = currentMountedPanel();
844
+ if (mounted || context.ui.panel.current()?.name === PANEL_NAME) {
845
+ if (mounted)
846
+ mounted.close();
847
+ else
848
+ context.ui.panel.close();
849
+ } else
850
+ openPanel();
851
+ };
852
+ const [tab, setTab] = createSignal("now");
853
+ const expandGoal = (goal) => {
854
+ setManagerNotice(undefined);
855
+ openPanel();
856
+ setSelectedGoalKey(goalKey(goal));
857
+ setSelectedDetails(goal);
858
+ setTab("goals");
859
+ ensureGoals(true);
860
+ fetchDetails(goal);
861
+ };
862
+ const fetchDetails = async (goal) => {
863
+ const ticket = ++detailRequest;
864
+ const id = currentSession(), location = id ? locationOf(id) : undefined;
865
+ try {
866
+ const result = await rpc.details({
867
+ key: goalKey(goal)
868
+ }, location ? {
869
+ location
870
+ } : undefined);
871
+ if (!disposed && ticket === detailRequest && selectedGoalKey() === goalKey(goal) && result?.goal)
872
+ setSelectedDetails(result.goal);
873
+ } catch {}
874
+ };
875
+ const selectedGoal = () => {
876
+ const row = goalsList().find((g) => goalKey(g) === selectedGoalKey());
877
+ const detail = selectedDetails();
878
+ return row && detail && goalKey(detail) === goalKey(row) && detail.lock === row.lock && detail.sessionID === row.sessionID ? {
879
+ ...detail,
880
+ ...row,
881
+ details: detail.details
882
+ } : row;
883
+ };
884
+ const manageSelected = async (row) => {
885
+ const goal = selectedGoal(), sessionID = currentSession();
886
+ if (row.action === "back") {
887
+ setSelectedGoalKey(undefined);
888
+ setSelectedDetails(undefined);
889
+ setManagerNotice(undefined);
890
+ return;
891
+ }
892
+ if (!goal || !sessionID) {
893
+ setManagerNotice({
894
+ message: "The selected goal or active session is unavailable. Reopen the manager with F9.",
895
+ tone: "warning"
896
+ });
897
+ return;
898
+ }
899
+ if (row.action === "jump") {
900
+ if (!goal.sessionID || goal.sessionState !== "present") {
901
+ setManagerNotice({
902
+ message: "This goal's session is not available for navigation.",
903
+ tone: "warning"
904
+ });
905
+ return;
906
+ }
907
+ try {
908
+ if (goal.sessionID === sessionID) {
909
+ const panel = currentMountedPanel();
910
+ if (panel)
911
+ panel.close();
912
+ else
913
+ context.ui.panel.close();
914
+ } else
915
+ openGoalSession(context.ui, goal.sessionID);
916
+ } catch (error) {
917
+ setManagerNotice({
918
+ message: `Navigation unavailable: ${String(error)}`,
919
+ tone: "warning"
920
+ });
921
+ }
922
+ return;
923
+ }
924
+ const location = locationOf(sessionID);
925
+ try {
926
+ const result = await rpc.manage({
927
+ sessionID,
928
+ action: row.action,
929
+ target: targetOf(goal)
930
+ }, location ? {
931
+ location
932
+ } : undefined);
933
+ setManagerNotice({
934
+ message: String(result.message),
935
+ tone: result.pending ? "muted" : result.ok ? "success" : "warning"
936
+ });
937
+ ensureGoals(true);
938
+ if (!result.pending)
939
+ fetchDetails(goal);
940
+ } catch (error) {
941
+ setManagerNotice({
942
+ message: `Operation unavailable: ${String(error)}`,
943
+ tone: "warning"
944
+ });
945
+ }
946
+ };
947
+ const color = (tone) => {
948
+ const t = context.theme;
949
+ if (tone === "base")
950
+ return t.text.base;
951
+ if (tone === "muted")
952
+ return t.text.muted;
953
+ return t.text.feedback?.[tone]?.base ?? t.text.base;
954
+ };
955
+ const disposers = [];
956
+ disposers.push(context.ui.slot({
957
+ append: "sidebar.content",
958
+ render: (input) => {
959
+ ensure(input.sessionID);
960
+ refreshIfStale(input.sessionID);
961
+ ensureGoals();
962
+ const width = typeof input.width === "number" ? input.width : SIDEBAR_WIDTH;
963
+ const model = () => sidebarCards(goalsList(), input.sessionID, width);
964
+ return (() => {
965
+ var _el$ = _$createElement("box"), _el$5 = _$createElement("select");
966
+ _$insertNode(_el$, _el$5);
967
+ _$setProp(_el$, "id", "goal-sidebar");
968
+ _$setProp(_el$, "flexDirection", "column");
969
+ _$setProp(_el$, "maxHeight", 12);
970
+ _$insert(_el$, _$createComponent(Show, {
971
+ get when() {
972
+ return model().total;
973
+ },
974
+ get children() {
975
+ return [(() => {
976
+ var _el$2 = _$createElement("text"), _el$3 = _$createTextNode(`Goals (`), _el$4 = _$createTextNode(`)`);
977
+ _$insertNode(_el$2, _el$3);
978
+ _$insertNode(_el$2, _el$4);
979
+ _$setProp(_el$2, "height", 1);
980
+ _$setProp(_el$2, "flexShrink", 0);
981
+ _$setProp(_el$2, "wrapMode", "none");
982
+ _$setProp(_el$2, "truncate", true);
983
+ _$insert(_el$2, () => model().total, _el$4);
984
+ _$effect((_$p) => _$setProp(_el$2, "fg", color("base"), _$p));
985
+ return _el$2;
986
+ })(), _$createComponent(For, {
987
+ get each() {
988
+ return model().cards.map((c) => c.key);
989
+ },
990
+ children: (key) => {
991
+ const card = () => model().cards.find((c) => c.key === key);
992
+ const expand = () => {
993
+ const current = card();
994
+ if (current)
995
+ expandGoal(current.goal);
996
+ };
997
+ return (() => {
998
+ var _el$6 = _$createElement("box"), _el$7 = _$createElement("select"), _el$8 = _$createElement("text");
999
+ _$insertNode(_el$6, _el$7);
1000
+ _$insertNode(_el$6, _el$8);
1001
+ _$setProp(_el$6, "flexDirection", "column");
1002
+ _$setProp(_el$6, "marginTop", 1);
1003
+ _$setProp(_el$6, "flexShrink", 0);
1004
+ _$setProp(_el$7, "id", `goal-card:${key}`);
1005
+ _$setProp(_el$7, "height", 2);
1006
+ _$setProp(_el$7, "flexShrink", 0);
1007
+ _$setProp(_el$7, "showDescription", true);
1008
+ _$setProp(_el$7, "showSelectionIndicator", false);
1009
+ _$setProp(_el$7, "onSelect", expand);
1010
+ _$setProp(_el$7, "onMouseUp", expand);
1011
+ _$setProp(_el$8, "height", 1);
1012
+ _$setProp(_el$8, "flexShrink", 0);
1013
+ _$setProp(_el$8, "wrapMode", "none");
1014
+ _$setProp(_el$8, "truncate", true);
1015
+ _$insert(_el$8, () => card()?.progress ?? "");
1016
+ _$insert(_el$6, _$createComponent(Show, {
1017
+ get when() {
1018
+ return card()?.handoffPointer;
1019
+ },
1020
+ children: (pointer) => (() => {
1021
+ var _el$9 = _$createElement("text");
1022
+ _$setProp(_el$9, "id", `goal-handoff-pointer:${key}`);
1023
+ _$setProp(_el$9, "height", 1);
1024
+ _$setProp(_el$9, "flexShrink", 0);
1025
+ _$setProp(_el$9, "wrapMode", "none");
1026
+ _$setProp(_el$9, "truncate", true);
1027
+ _$insert(_el$9, pointer);
1028
+ _$effect((_$p) => _$setProp(_el$9, "fg", color("muted"), _$p));
1029
+ return _el$9;
1030
+ })()
1031
+ }), null);
1032
+ _$effect((_p$) => {
1033
+ var _v$ = [{
1034
+ name: card()?.title ?? "",
1035
+ description: card()?.description ?? "",
1036
+ value: key
1037
+ }], _v$2 = color("base"), _v$3 = color(card()?.tone ?? "muted"), _v$4 = color("muted");
1038
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$7, "options", _v$, _p$.e));
1039
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, "textColor", _v$2, _p$.t));
1040
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$7, "descriptionColor", _v$3, _p$.a));
1041
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$8, "fg", _v$4, _p$.o));
1042
+ return _p$;
1043
+ }, {
1044
+ e: undefined,
1045
+ t: undefined,
1046
+ a: undefined,
1047
+ o: undefined
1048
+ });
1049
+ return _el$6;
1050
+ })();
1051
+ }
1052
+ })];
1053
+ }
1054
+ }), _el$5);
1055
+ _$setProp(_el$5, "id", "goal-sidebar-manager");
1056
+ _$setProp(_el$5, "height", 1);
1057
+ _$setProp(_el$5, "flexShrink", 0);
1058
+ _$setProp(_el$5, "showDescription", false);
1059
+ _$setProp(_el$5, "onSelect", () => {
1060
+ openPanel();
1061
+ setTab("goals");
1062
+ ensureGoals(true);
1063
+ });
1064
+ _$setProp(_el$5, "onMouseUp", () => {
1065
+ openPanel();
1066
+ setTab("goals");
1067
+ ensureGoals(true);
1068
+ });
1069
+ _$effect((_$p) => _$setProp(_el$5, "options", [{
1070
+ name: goalsState() === "error" ? "Retry goals \u2014 F9 manager" : model().pointer,
1071
+ description: "Open the project Goals manager",
1072
+ value: "manager"
1073
+ }], _$p));
1074
+ return _el$;
1075
+ })();
1076
+ }
1077
+ }));
1078
+ disposers.push(context.ui.slot({
1079
+ append: "prompt.footer.status",
1080
+ render: (input) => {
1081
+ if (input.sessionID)
1082
+ ensure(input.sessionID);
1083
+ return _$createComponent(Show, {
1084
+ get when() {
1085
+ return _$memo(() => !!input.sessionID)() ? views[input.sessionID] : undefined;
1086
+ },
1087
+ children: (view) => (() => {
1088
+ var _el$0 = _$createElement("text");
1089
+ _$insert(_el$0, () => pillText(view(), now()));
1090
+ _$effect((_$p) => _$setProp(_el$0, "fg", color(view().status === "running" ? "success" : "muted"), _$p));
1091
+ return _el$0;
1092
+ })()
1093
+ });
1094
+ }
1095
+ }));
1096
+ disposers.push(context.ui.slot({
1097
+ append: "session.composer.top",
1098
+ render: (input) => {
1099
+ ensure(input.sessionID);
1100
+ const tracer = () => {
1101
+ const view = views[input.sessionID];
1102
+ return view ? tracerLines(view, now(), 80) : [];
1103
+ };
1104
+ const banner = () => {
1105
+ const view = views[input.sessionID];
1106
+ return view ? bannerText(view) : undefined;
1107
+ };
1108
+ return (() => {
1109
+ var _el$1 = _$createElement("box");
1110
+ _$setProp(_el$1, "flexDirection", "column");
1111
+ _$setProp(_el$1, "paddingLeft", 1);
1112
+ _$setProp(_el$1, "paddingRight", 1);
1113
+ _$insert(_el$1, _$createComponent(Show, {
1114
+ get when() {
1115
+ return launchStatusText(currentLaunch(input.sessionID), views[input.sessionID]);
1116
+ },
1117
+ children: (line) => (() => {
1118
+ var _el$10 = _$createElement("text");
1119
+ _$setProp(_el$10, "id", "goal-launch-admission");
1120
+ _$setProp(_el$10, "wrapMode", "none");
1121
+ _$setProp(_el$10, "truncate", true);
1122
+ _$insert(_el$10, line);
1123
+ _$effect((_$p) => _$setProp(_el$10, "fg", color(currentLaunch(input.sessionID)?.status === "executing" ? "muted" : "warning"), _$p));
1124
+ return _el$10;
1125
+ })()
1126
+ }), null);
1127
+ _$insert(_el$1, _$createComponent(For, {
1128
+ get each() {
1129
+ return tracer();
1130
+ },
1131
+ children: (line) => (() => {
1132
+ var _el$11 = _$createElement("text");
1133
+ _$setProp(_el$11, "wrapMode", "none");
1134
+ _$setProp(_el$11, "truncate", true);
1135
+ _$insert(_el$11, () => line.text);
1136
+ _$effect((_$p) => _$setProp(_el$11, "fg", color(line.tone), _$p));
1137
+ return _el$11;
1138
+ })()
1139
+ }), null);
1140
+ _$insert(_el$1, _$createComponent(Show, {
1141
+ get when() {
1142
+ return banner();
1143
+ },
1144
+ children: (b) => (() => {
1145
+ var _el$12 = _$createElement("text");
1146
+ _$setProp(_el$12, "wrapMode", "none");
1147
+ _$setProp(_el$12, "truncate", true);
1148
+ _$insert(_el$12, () => b().text);
1149
+ _$effect((_$p) => _$setProp(_el$12, "fg", color(b().tone), _$p));
1150
+ return _el$12;
1151
+ })()
1152
+ }), null);
1153
+ return _el$1;
1154
+ })();
1155
+ }
1156
+ }));
1157
+ disposers.push(context.ui.slot({
1158
+ append: "session.panel",
1159
+ render: (input) => {
1160
+ if (input.name !== PANEL_NAME)
1161
+ return null;
1162
+ const mount = {
1163
+ close: () => {
1164
+ if (typeof input.close === "function")
1165
+ input.close();
1166
+ else
1167
+ context.ui.panel.close();
1168
+ }
1169
+ };
1170
+ mountedPanels.set(input.sessionID, mount);
1171
+ onCleanup(() => {
1172
+ if (mountedPanels.get(input.sessionID) === mount)
1173
+ mountedPanels.delete(input.sessionID);
1174
+ });
1175
+ ensure(input.sessionID);
1176
+ ensureGoals();
1177
+ const view = () => views[input.sessionID];
1178
+ if (!view() && !selectedGoalKey())
1179
+ setTab("goals");
1180
+ const width = () => typeof input.width === "number" ? input.width : 46;
1181
+ if (input.name === PANEL_NAME && panelFocusPending && typeof input.focus === "function") {
1182
+ panelFocusPending = false;
1183
+ try {
1184
+ input.focus();
1185
+ } catch {}
1186
+ }
1187
+ const rows = () => view() && tab() === "decisions" ? [...decisionRows(view()), ...quickActRows(view())] : [];
1188
+ const managerRows = () => orderedGoals(goalsList(), input.sessionID);
1189
+ const actions = () => {
1190
+ const g = selectedGoal();
1191
+ return g ? managerActions(g, input.sessionID, !!view() && !["complete", "failed", "aborted", "superseded"].includes(view().status)) : [];
1192
+ };
1193
+ const focusControl = (control) => {
1194
+ const scheduled = setTimeout(() => {
1195
+ try {
1196
+ input.focus?.();
1197
+ control.focus();
1198
+ } catch {}
1199
+ }, 0);
1200
+ onCleanup(() => clearTimeout(scheduled));
1201
+ };
1202
+ let managerControl, actionsControl, detailsControl, closeControl, lastFocusedControl;
1203
+ let resizeFocusTimer;
1204
+ onCleanup(() => clearTimeout(resizeFocusTimer));
1205
+ const rememberFocus = (control) => {
1206
+ control.on?.("focused", () => {
1207
+ lastFocusedControl = control;
1208
+ });
1209
+ };
1210
+ const restoreAfterResize = () => {
1211
+ if (tab() !== "goals" || mountedPanels.get(input.sessionID) !== mount || !lastFocusedControl)
1212
+ return;
1213
+ const route = context.ui.router.current();
1214
+ if (route.type !== "session" || route.sessionID !== input.sessionID)
1215
+ return;
1216
+ clearTimeout(resizeFocusTimer);
1217
+ resizeFocusTimer = setTimeout(() => {
1218
+ if (mountedPanels.get(input.sessionID) !== mount || (context.data.session.form?.list(input.sessionID, locationOf(input.sessionID))?.length ?? 0) > 0)
1219
+ return;
1220
+ input.focus?.();
1221
+ lastFocusedControl?.focus();
1222
+ }, 0);
1223
+ };
1224
+ context.renderer?.on?.("resize", restoreAfterResize);
1225
+ onCleanup(() => context.renderer?.off?.("resize", restoreAfterResize));
1226
+ createEffect(() => {
1227
+ if (input.focused === true && tab() === "goals")
1228
+ untrack(() => focusControl(selectedGoal() ? actionsControl : managerControl));
1229
+ });
1230
+ const cycleSection = () => {
1231
+ const controls = [managerControl, detailsControl, actionsControl, closeControl].filter((c) => c && c.visible !== false);
1232
+ if (!controls.length)
1233
+ return;
1234
+ const at = controls.findIndex((c) => c.focused);
1235
+ input.focus?.();
1236
+ controls[(at + 1) % controls.length].focus();
1237
+ };
1238
+ focusManagerSection = cycleSection;
1239
+ onCleanup(() => {
1240
+ if (focusManagerSection === cycleSection)
1241
+ focusManagerSection = undefined;
1242
+ });
1243
+ return _$createComponent(Show, {
1244
+ get when() {
1245
+ return tab() !== "goals";
1246
+ },
1247
+ get fallback() {
1248
+ return (() => {
1249
+ var _el$13 = _$createElement("box"), _el$14 = _$createElement("text"), _el$15 = _$createElement("b"), _el$17 = _$createElement("select"), _el$18 = _$createElement("text");
1250
+ _$insertNode(_el$13, _el$14);
1251
+ _$insertNode(_el$13, _el$17);
1252
+ _$insertNode(_el$13, _el$18);
1253
+ _$setProp(_el$13, "id", "goal-manager");
1254
+ _$setProp(_el$13, "flexDirection", "column");
1255
+ _$setProp(_el$13, "paddingLeft", 1);
1256
+ _$setProp(_el$13, "paddingRight", 1);
1257
+ _$setProp(_el$13, "height", "100%");
1258
+ _$setProp(_el$13, "minHeight", 0);
1259
+ _$setProp(_el$13, "overflow", "hidden");
1260
+ _$insertNode(_el$14, _el$15);
1261
+ _$setProp(_el$14, "height", 1);
1262
+ _$setProp(_el$14, "flexShrink", 0);
1263
+ _$setProp(_el$14, "wrapMode", "none");
1264
+ _$setProp(_el$14, "truncate", true);
1265
+ _$insertNode(_el$15, _$createTextNode(`Goals manager \xB7 this project`));
1266
+ _$use((control) => {
1267
+ closeControl = control;
1268
+ rememberFocus(control);
1269
+ }, _el$17);
1270
+ _$setProp(_el$17, "id", "goal-manager-close");
1271
+ _$setProp(_el$17, "height", 1);
1272
+ _$setProp(_el$17, "flexShrink", 0);
1273
+ _$setProp(_el$17, "options", [{
1274
+ name: "Close manager \xB7 F9: return to sidebar",
1275
+ description: "Return to this session's basic sidebar without changing the goal",
1276
+ value: "close"
1277
+ }]);
1278
+ _$setProp(_el$17, "showDescription", false);
1279
+ _$setProp(_el$17, "onSelect", () => {
1280
+ if (typeof input.close === "function")
1281
+ input.close();
1282
+ else
1283
+ context.ui.panel.close();
1284
+ });
1285
+ _$setProp(_el$17, "onMouseUp", () => {
1286
+ if (typeof input.close === "function")
1287
+ input.close();
1288
+ else
1289
+ context.ui.panel.close();
1290
+ });
1291
+ _$setProp(_el$18, "height", 1);
1292
+ _$setProp(_el$18, "flexShrink", 0);
1293
+ _$setProp(_el$18, "wrapMode", "none");
1294
+ _$setProp(_el$18, "truncate", true);
1295
+ _$insert(_el$18, () => fitColumns("Tab: focus \xB7 \u2191\u2193: select \xB7 Enter: activate", width() - 2));
1296
+ _$insert(_el$13, _$createComponent(Show, {
1297
+ get when() {
1298
+ return goalsState() === "loading";
1299
+ },
1300
+ get children() {
1301
+ var _el$19 = _$createElement("text");
1302
+ _$insertNode(_el$19, _$createTextNode(`Loading project goals\u2026`));
1303
+ _$effect((_$p) => _$setProp(_el$19, "fg", color("muted"), _$p));
1304
+ return _el$19;
1305
+ }
1306
+ }), null);
1307
+ _$insert(_el$13, _$createComponent(Show, {
1308
+ get when() {
1309
+ return goalsState() === "error";
1310
+ },
1311
+ get children() {
1312
+ var _el$21 = _$createElement("text");
1313
+ _$insertNode(_el$21, _$createTextNode(`Inventory unavailable; F9 retries.`));
1314
+ _$effect((_$p) => _$setProp(_el$21, "fg", color("error"), _$p));
1315
+ return _el$21;
1316
+ }
1317
+ }), null);
1318
+ _$insert(_el$13, _$createComponent(Show, {
1319
+ get when() {
1320
+ return managerRows().length;
1321
+ },
1322
+ get fallback() {
1323
+ return _$createComponent(Show, {
1324
+ get when() {
1325
+ return goalsState() === "ready";
1326
+ },
1327
+ get children() {
1328
+ var _el$24 = _$createElement("text");
1329
+ _$insertNode(_el$24, _$createTextNode(`No goals \u2014 /goal starts the interview.`));
1330
+ _$effect((_$p) => _$setProp(_el$24, "fg", color("muted"), _$p));
1331
+ return _el$24;
1332
+ }
1333
+ });
1334
+ },
1335
+ get children() {
1336
+ var _el$23 = _$createElement("select");
1337
+ _$use((control) => {
1338
+ managerControl = control;
1339
+ rememberFocus(control);
1340
+ if (!selectedGoal())
1341
+ focusControl(control);
1342
+ }, _el$23);
1343
+ _$setProp(_el$23, "id", "goal-manager-list");
1344
+ _$setProp(_el$23, "flexShrink", 0);
1345
+ _$setProp(_el$23, "showDescription", true);
1346
+ _$setProp(_el$23, "onMouseDown", (event) => {
1347
+ event.stopPropagation();
1348
+ event.preventDefault();
1349
+ });
1350
+ _$setProp(_el$23, "onMouseUp", (event) => {
1351
+ input.focus?.();
1352
+ pointerSelect(managerControl, event, 2);
1353
+ });
1354
+ _$setProp(_el$23, "onSelect", (_index, option) => {
1355
+ const goal = managerRows().find((g) => goalKey(g) === option?.value);
1356
+ if (goal) {
1357
+ setSelectedGoalKey(goalKey(goal));
1358
+ setSelectedDetails(goal);
1359
+ fetchDetails(goal);
1360
+ }
1361
+ });
1362
+ _$effect((_p$) => {
1363
+ var _v$5 = selectedGoal() ? 2 : Math.min(8, Math.max(2, managerRows().length * 2)), _v$6 = managerRows().map((g) => ({
1364
+ name: fitColumns(`${g.title}${g.archived ? " [archived]" : ""}`, width() - 4),
1365
+ description: fitColumns(`${g.status}${g.launch?.status === "executing" ? " \xB7 starting" : g.launch?.status === "expired" ? " \xB7 review expired" : ""} \xB7 ${g.proven}/${g.total} proven`, width() - 4),
1366
+ value: goalKey(g)
1367
+ })), _v$7 = !selectedGoal();
1368
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$23, "height", _v$5, _p$.e));
1369
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$23, "options", _v$6, _p$.t));
1370
+ _v$7 !== _p$.a && (_p$.a = _$setProp(_el$23, "focused", _v$7, _p$.a));
1371
+ return _p$;
1372
+ }, {
1373
+ e: undefined,
1374
+ t: undefined,
1375
+ a: undefined
1376
+ });
1377
+ return _el$23;
1378
+ }
1379
+ }), null);
1380
+ _$insert(_el$13, _$createComponent(Show, {
1381
+ get when() {
1382
+ return selectedGoal();
1383
+ },
1384
+ children: (goal) => (() => {
1385
+ var _el$26 = _$createElement("box"), _el$27 = _$createElement("text"), _el$28 = _$createElement("text"), _el$30 = _$createElement("text"), _el$32 = _$createElement("scrollbox"), _el$33 = _$createElement("text"), _el$34 = _$createElement("b"), _el$36 = _$createElement("text"), _el$37 = _$createElement("text"), _el$38 = _$createElement("text"), _el$42 = _$createElement("text"), _el$43 = _$createElement("b"), _el$45 = _$createElement("select");
1386
+ _$insertNode(_el$26, _el$27);
1387
+ _$insertNode(_el$26, _el$28);
1388
+ _$insertNode(_el$26, _el$30);
1389
+ _$insertNode(_el$26, _el$32);
1390
+ _$insertNode(_el$26, _el$42);
1391
+ _$insertNode(_el$26, _el$45);
1392
+ _$setProp(_el$26, "id", "goal-manager-details");
1393
+ _$setProp(_el$26, "flexDirection", "column");
1394
+ _$setProp(_el$26, "flexGrow", 1);
1395
+ _$setProp(_el$26, "flexShrink", 1);
1396
+ _$setProp(_el$26, "minHeight", 0);
1397
+ _$setProp(_el$26, "marginTop", 1);
1398
+ _$setProp(_el$27, "height", 1);
1399
+ _$setProp(_el$27, "flexShrink", 0);
1400
+ _$setProp(_el$27, "wrapMode", "none");
1401
+ _$setProp(_el$27, "truncate", true);
1402
+ _$insert(_el$27, () => fitColumns(`${goal().status} \xB7 ${goal().proven}/${goal().total} host-proven \xB7 ${goal().agent ?? "agent unknown"}`, width() - 2));
1403
+ _$setProp(_el$28, "height", 1);
1404
+ _$setProp(_el$28, "flexShrink", 0);
1405
+ _$setProp(_el$28, "wrapMode", "none");
1406
+ _$setProp(_el$28, "truncate", true);
1407
+ _$insert(_el$28, () => fitColumns(`Session: ${goal().sessionTitle ?? goal().sessionID ?? "unassigned"}`, width() - 2));
1408
+ _$insert(_el$26, _$createComponent(Show, {
1409
+ get when() {
1410
+ return managerNotice();
1411
+ },
1412
+ children: (notice) => (() => {
1413
+ var _el$46 = _$createElement("text");
1414
+ _$setProp(_el$46, "id", "goal-manager-feedback");
1415
+ _$setProp(_el$46, "height", 1);
1416
+ _$setProp(_el$46, "flexShrink", 0);
1417
+ _$setProp(_el$46, "wrapMode", "none");
1418
+ _$setProp(_el$46, "truncate", true);
1419
+ _$insert(_el$46, () => fitColumns(notice().message, width() - 2));
1420
+ _$effect((_$p) => _$setProp(_el$46, "fg", color(notice().tone), _$p));
1421
+ return _el$46;
1422
+ })()
1423
+ }), _el$30);
1424
+ _$insert(_el$26, _$createComponent(Show, {
1425
+ get when() {
1426
+ return goal().steps?.length;
1427
+ },
1428
+ get children() {
1429
+ var _el$29 = _$createElement("text");
1430
+ _$setProp(_el$29, "height", 1);
1431
+ _$setProp(_el$29, "flexShrink", 0);
1432
+ _$setProp(_el$29, "wrapMode", "none");
1433
+ _$setProp(_el$29, "truncate", true);
1434
+ _$insert(_el$29, () => fitColumns(`Plan: ${goal().steps?.filter((s) => s.status === "done").length}/${goal().steps?.length} done \xB7 ${goal().steps?.find((s) => s.status === "active")?.title ?? "no active step"}`, width() - 2));
1435
+ _$effect((_$p) => _$setProp(_el$29, "fg", color("muted"), _$p));
1436
+ return _el$29;
1437
+ }
1438
+ }), _el$30);
1439
+ _$insertNode(_el$30, _$createTextNode(`Details \xB7 Tab to focus \xB7 wheel/PgUp/PgDn scroll`));
1440
+ _$setProp(_el$30, "height", 1);
1441
+ _$setProp(_el$30, "flexShrink", 0);
1442
+ _$setProp(_el$30, "wrapMode", "none");
1443
+ _$setProp(_el$30, "truncate", true);
1444
+ _$insertNode(_el$32, _el$33);
1445
+ _$insertNode(_el$32, _el$36);
1446
+ _$insertNode(_el$32, _el$37);
1447
+ _$insertNode(_el$32, _el$38);
1448
+ _$use((control) => {
1449
+ detailsControl = control;
1450
+ rememberFocus(control);
1451
+ }, _el$32);
1452
+ _$setProp(_el$32, "id", "goal-manager-detail-text");
1453
+ _$setProp(_el$32, "flexGrow", 1);
1454
+ _$setProp(_el$32, "flexShrink", 1);
1455
+ _$setProp(_el$32, "minHeight", 1);
1456
+ _$setProp(_el$32, "scrollX", false);
1457
+ _$setProp(_el$32, "scrollY", true);
1458
+ _$setProp(_el$32, "viewportOptions", {
1459
+ overflow: "hidden"
1460
+ });
1461
+ _$setProp(_el$32, "contentOptions", {
1462
+ flexDirection: "column"
1463
+ });
1464
+ _$setProp(_el$32, "onMouseDown", () => {
1465
+ input.focus?.();
1466
+ detailsControl?.focus();
1467
+ });
1468
+ _$insertNode(_el$33, _el$34);
1469
+ _$insertNode(_el$34, _$createTextNode(`Outcome`));
1470
+ _$setProp(_el$36, "wrapMode", "word");
1471
+ _$insert(_el$36, () => `Goal: ${goal().slug}`);
1472
+ _$setProp(_el$37, "wrapMode", "word");
1473
+ _$insert(_el$37, () => goal().outcome ?? goal().title);
1474
+ _$setProp(_el$38, "wrapMode", "word");
1475
+ _$insert(_el$38, () => `Session ID: ${goal().sessionID ?? "unassigned"}`);
1476
+ _$insert(_el$32, _$createComponent(Show, {
1477
+ get when() {
1478
+ return launchStatusText(goal().launch, goal());
1479
+ },
1480
+ children: (line) => (() => {
1481
+ var _el$47 = _$createElement("text");
1482
+ _$setProp(_el$47, "wrapMode", "word");
1483
+ _$insert(_el$47, line);
1484
+ _$effect((_$p) => _$setProp(_el$47, "fg", color("warning"), _$p));
1485
+ return _el$47;
1486
+ })()
1487
+ }), null);
1488
+ _$insert(_el$32, _$createComponent(Show, {
1489
+ get when() {
1490
+ return goal().details?.criteria?.length;
1491
+ },
1492
+ get children() {
1493
+ var _el$39 = _$createElement("text"), _el$40 = _$createElement("b");
1494
+ _$insertNode(_el$39, _el$40);
1495
+ _$insertNode(_el$40, _$createTextNode(`Evidence`));
1496
+ _$effect((_$p) => _$setProp(_el$39, "fg", color("base"), _$p));
1497
+ return _el$39;
1498
+ }
1499
+ }), null);
1500
+ _$insert(_el$32, _$createComponent(For, {
1501
+ get each() {
1502
+ return goal().details?.criteria ?? [];
1503
+ },
1504
+ children: (criterion) => (() => {
1505
+ var _el$48 = _$createElement("text");
1506
+ _$setProp(_el$48, "wrapMode", "word");
1507
+ _$insert(_el$48, () => `${criterion.id} [${criterion.status}] ${criterion.statement}${criterion.by ? ` (${criterion.by})` : ""}`);
1508
+ _$effect((_$p) => _$setProp(_el$48, "fg", color(criterion.status === "fail" ? "error" : "muted"), _$p));
1509
+ return _el$48;
1510
+ })()
1511
+ }), null);
1512
+ _$insert(_el$32, _$createComponent(For, {
1513
+ get each() {
1514
+ return goal().issues ?? [];
1515
+ },
1516
+ children: (issue) => (() => {
1517
+ var _el$49 = _$createElement("text");
1518
+ _$setProp(_el$49, "wrapMode", "word");
1519
+ _$insert(_el$49, issue);
1520
+ _$effect((_$p) => _$setProp(_el$49, "fg", color("error"), _$p));
1521
+ return _el$49;
1522
+ })()
1523
+ }), null);
1524
+ _$insertNode(_el$42, _el$43);
1525
+ _$setProp(_el$42, "height", 1);
1526
+ _$setProp(_el$42, "flexShrink", 0);
1527
+ _$insertNode(_el$43, _$createTextNode(`Actions \xB7 \u2191\u2193 and Enter, or click`));
1528
+ _$use((control) => {
1529
+ actionsControl = control;
1530
+ rememberFocus(control);
1531
+ focusControl(control);
1532
+ }, _el$45);
1533
+ _$setProp(_el$45, "id", "goal-manager-actions");
1534
+ _$setProp(_el$45, "flexShrink", 0);
1535
+ _$setProp(_el$45, "showDescription", false);
1536
+ _$setProp(_el$45, "focused", true);
1537
+ _$setProp(_el$45, "onMouseDown", (event) => {
1538
+ event.stopPropagation();
1539
+ event.preventDefault();
1540
+ });
1541
+ _$setProp(_el$45, "onMouseUp", (event) => {
1542
+ input.focus?.();
1543
+ pointerSelect(actionsControl, event, 1);
1544
+ });
1545
+ _$setProp(_el$45, "onSelect", (_index, option) => {
1546
+ if (option?.value)
1547
+ manageSelected(option.value);
1548
+ });
1549
+ _$effect((_p$) => {
1550
+ var _v$0 = color("base"), _v$1 = color("muted"), _v$10 = color("muted"), _v$11 = color("base"), _v$12 = color("muted"), _v$13 = color("base"), _v$14 = color("muted"), _v$15 = color("base"), _v$16 = Math.min(8, actions().length), _v$17 = actions().map((r) => ({
1551
+ name: r.label,
1552
+ description: r.description,
1553
+ value: r
1554
+ }));
1555
+ _v$0 !== _p$.e && (_p$.e = _$setProp(_el$27, "fg", _v$0, _p$.e));
1556
+ _v$1 !== _p$.t && (_p$.t = _$setProp(_el$28, "fg", _v$1, _p$.t));
1557
+ _v$10 !== _p$.a && (_p$.a = _$setProp(_el$30, "fg", _v$10, _p$.a));
1558
+ _v$11 !== _p$.o && (_p$.o = _$setProp(_el$33, "fg", _v$11, _p$.o));
1559
+ _v$12 !== _p$.i && (_p$.i = _$setProp(_el$36, "fg", _v$12, _p$.i));
1560
+ _v$13 !== _p$.n && (_p$.n = _$setProp(_el$37, "fg", _v$13, _p$.n));
1561
+ _v$14 !== _p$.s && (_p$.s = _$setProp(_el$38, "fg", _v$14, _p$.s));
1562
+ _v$15 !== _p$.h && (_p$.h = _$setProp(_el$42, "fg", _v$15, _p$.h));
1563
+ _v$16 !== _p$.r && (_p$.r = _$setProp(_el$45, "height", _v$16, _p$.r));
1564
+ _v$17 !== _p$.d && (_p$.d = _$setProp(_el$45, "options", _v$17, _p$.d));
1565
+ return _p$;
1566
+ }, {
1567
+ e: undefined,
1568
+ t: undefined,
1569
+ a: undefined,
1570
+ o: undefined,
1571
+ i: undefined,
1572
+ n: undefined,
1573
+ s: undefined,
1574
+ h: undefined,
1575
+ r: undefined,
1576
+ d: undefined
1577
+ });
1578
+ return _el$26;
1579
+ })()
1580
+ }), null);
1581
+ _$effect((_p$) => {
1582
+ var _v$8 = color("base"), _v$9 = color("muted");
1583
+ _v$8 !== _p$.e && (_p$.e = _$setProp(_el$14, "fg", _v$8, _p$.e));
1584
+ _v$9 !== _p$.t && (_p$.t = _$setProp(_el$18, "fg", _v$9, _p$.t));
1585
+ return _p$;
1586
+ }, {
1587
+ e: undefined,
1588
+ t: undefined
1589
+ });
1590
+ return _el$13;
1591
+ })();
1592
+ },
1593
+ get children() {
1594
+ return _$createComponent(Show, {
1595
+ get when() {
1596
+ return view();
1597
+ },
1598
+ children: (v) => (() => {
1599
+ var _el$50 = _$createElement("box");
1600
+ _$setProp(_el$50, "flexDirection", "column");
1601
+ _$setProp(_el$50, "paddingLeft", 1);
1602
+ _$setProp(_el$50, "paddingRight", 1);
1603
+ _$insert(_el$50, _$createComponent(For, {
1604
+ get each() {
1605
+ return dashboardLines(v(), tab(), now(), width(), goalsList());
1606
+ },
1607
+ children: (line) => (() => {
1608
+ var _el$52 = _$createElement("text");
1609
+ _$setProp(_el$52, "wrapMode", "none");
1610
+ _$setProp(_el$52, "truncate", true);
1611
+ _$insert(_el$52, (() => {
1612
+ var _c$ = _$memo(() => !!line.bold);
1613
+ return () => _c$() ? (() => {
1614
+ var _el$53 = _$createElement("b");
1615
+ _$insert(_el$53, () => line.text);
1616
+ return _el$53;
1617
+ })() : line.text;
1618
+ })());
1619
+ _$effect((_$p) => _$setProp(_el$52, "fg", color(line.tone), _$p));
1620
+ return _el$52;
1621
+ })()
1622
+ }), null);
1623
+ _$insert(_el$50, _$createComponent(Show, {
1624
+ get when() {
1625
+ return rows().length;
1626
+ },
1627
+ get children() {
1628
+ var _el$51 = _$createElement("select");
1629
+ _$setProp(_el$51, "showDescription", true);
1630
+ _$setProp(_el$51, "focused", true);
1631
+ _$setProp(_el$51, "onSelect", (_index, option) => {
1632
+ const row = option?.value;
1633
+ if (row?.act)
1634
+ act(row.act, row.arg);
1635
+ else
1636
+ context.ui.toast.show({
1637
+ title: "Goal",
1638
+ message: `Run: ${row?.description ?? row?.label ?? ""}`,
1639
+ variant: "info"
1640
+ });
1641
+ });
1642
+ _$effect((_$p) => _$setProp(_el$51, "options", rows().map((r) => ({
1643
+ name: r.label,
1644
+ description: r.description,
1645
+ value: r
1646
+ })), _$p));
1647
+ return _el$51;
1648
+ }
1649
+ }), null);
1650
+ return _el$50;
1651
+ })()
1652
+ });
1653
+ }
1654
+ });
1655
+ }
1656
+ }));
1657
+ const currentSession = () => {
1658
+ const route = context.ui.router.current();
1659
+ return route.type === "session" ? route.sessionID : undefined;
1660
+ };
1661
+ const act = async (action, arg, decision) => {
1662
+ const sessionID = decision?.sessionID ?? currentSession();
1663
+ if (!sessionID)
1664
+ return;
1665
+ const location = locationOf(sessionID);
1666
+ try {
1667
+ const result = await rpc.act({
1668
+ sessionID,
1669
+ action,
1670
+ ...arg ? {
1671
+ arg
1672
+ } : {},
1673
+ ...decision ? {
1674
+ decisionId: decision.decisionId
1675
+ } : {}
1676
+ }, location ? {
1677
+ location
1678
+ } : undefined);
1679
+ context.ui.toast.show({
1680
+ title: "Goal",
1681
+ message: result.message,
1682
+ variant: result.ok ? "success" : "warning"
1683
+ });
1684
+ } catch (error) {
1685
+ context.ui.toast.show({
1686
+ title: "Goal",
1687
+ message: `Goal plugin unavailable: ${String(error)}`,
1688
+ variant: "error"
1689
+ });
1690
+ }
1691
+ };
1692
+ const hasGoal = () => {
1693
+ const id = currentSession();
1694
+ return !!(id && views[id]);
1695
+ };
1696
+ const openDecisionIdentity = new Map;
1697
+ const offDecision = rpc.events.on("decision", (event) => {
1698
+ const data = event.data ?? {};
1699
+ const choices = data.choices ?? [];
1700
+ const route = context.ui.router.current();
1701
+ if (choices.some((c) => c.act) && route.type === "session" && route.sessionID === data.sessionID) {
1702
+ const mounted = mountedPanels.get(data.sessionID);
1703
+ if (mounted)
1704
+ mounted.close();
1705
+ else if (context.ui.panel?.current?.()?.name === PANEL_NAME)
1706
+ context.ui.panel.close();
1707
+ }
1708
+ if (typeof data.sessionID === "string" && typeof data.decisionId === "string" && choices.some((c) => c.act)) {
1709
+ openDecisions.set(data.sessionID, 1);
1710
+ openDecisionIdentity.set(data.sessionID, data.decisionId);
1711
+ }
1712
+ context.ui.toast.show({
1713
+ title: "Goal decision",
1714
+ message: choices.some((c) => c.act) ? "Owner decision pending in this session's Composer. No operation executed." : String(data.message ?? "Goal guidance is available in the manager."),
1715
+ variant: "info",
1716
+ ...data.sessionID ? {
1717
+ sessionID: data.sessionID
1718
+ } : {}
1719
+ });
1720
+ });
1721
+ const offResolved = rpc.events.on("decision.resolved", (event) => {
1722
+ const data = event.data ?? {};
1723
+ if (data.message)
1724
+ context.ui.toast.show({
1725
+ title: "Goal decision",
1726
+ message: data.message,
1727
+ variant: "success"
1728
+ });
1729
+ if (openDecisionIdentity.get(data.sessionID) !== data.decisionId)
1730
+ return;
1731
+ openDecisions.delete(data.sessionID);
1732
+ openDecisionIdentity.delete(data.sessionID);
1733
+ flushSummary(data.sessionID);
1734
+ });
1735
+ const offFailed = rpc.events.on("decision.failed", (event) => {
1736
+ const data = event.data ?? {};
1737
+ context.ui.toast.show({
1738
+ title: "Goal decision",
1739
+ message: String(data.message ?? "Owner operation did not execute; inspect the goal and request a new review."),
1740
+ variant: "warning",
1741
+ duration: 8000
1742
+ });
1743
+ if (openDecisionIdentity.get(data.sessionID) === data.decisionId) {
1744
+ openDecisions.delete(data.sessionID);
1745
+ openDecisionIdentity.delete(data.sessionID);
1746
+ flushSummary(data.sessionID);
1747
+ }
1748
+ });
1749
+ disposers.push(context.ui.slot({
1750
+ append: "app",
1751
+ render: () => {
1752
+ context.keymap.layer(() => ({
1753
+ mode: "global",
1754
+ commands: [{
1755
+ id: "goal.attach",
1756
+ title: "Goal: Manage goals",
1757
+ group: "Goal",
1758
+ palette: true,
1759
+ enabled: () => true,
1760
+ run: () => {
1761
+ setTab("goals");
1762
+ openPanel();
1763
+ }
1764
+ }, {
1765
+ id: "goal.panel",
1766
+ title: "Goal: Toggle dashboard",
1767
+ group: "Goal",
1768
+ palette: true,
1769
+ bind: "f9",
1770
+ enabled: () => true,
1771
+ run: () => togglePanel()
1772
+ }, {
1773
+ id: "goal.dashboard.tab",
1774
+ title: "Goal: Next dashboard tab",
1775
+ group: "Goal",
1776
+ palette: true,
1777
+ bind: "tab",
1778
+ enabled: () => hasGoal() || !!currentMountedPanel() || context.ui.panel.current()?.name === PANEL_NAME,
1779
+ run: () => {
1780
+ if (tab() === "goals" && (currentMountedPanel() || context.ui.panel.current()?.name === PANEL_NAME) && focusManagerSection) {
1781
+ focusManagerSection();
1782
+ return;
1783
+ }
1784
+ const order = DASHBOARD_TABS;
1785
+ setTab(order[(order.indexOf(tab()) + 1) % order.length]);
1786
+ }
1787
+ }, {
1788
+ id: "goal.status",
1789
+ title: "Goal: Show status",
1790
+ group: "Goal",
1791
+ palette: true,
1792
+ enabled: hasGoal,
1793
+ run: () => {
1794
+ const view = views[currentSession()];
1795
+ if (view)
1796
+ context.ui.dialog.alert({
1797
+ title: "Goal",
1798
+ message: statusText(view, Date.now())
1799
+ });
1800
+ }
1801
+ }, {
1802
+ id: "goal.pause",
1803
+ title: "Goal: Pause",
1804
+ group: "Goal",
1805
+ palette: true,
1806
+ enabled: hasGoal,
1807
+ run: () => act("pause")
1808
+ }, {
1809
+ id: "goal.resume",
1810
+ title: "Goal: Resume",
1811
+ group: "Goal",
1812
+ palette: true,
1813
+ enabled: hasGoal,
1814
+ run: () => act("resume")
1815
+ }, {
1816
+ id: "goal.verify",
1817
+ title: "Goal: Verify now",
1818
+ group: "Goal",
1819
+ palette: true,
1820
+ enabled: hasGoal,
1821
+ run: () => act("verify")
1822
+ }, {
1823
+ id: "goal.approve",
1824
+ title: "Goal: Review in Composer",
1825
+ group: "Goal",
1826
+ palette: true,
1827
+ enabled: () => !!currentSession(),
1828
+ run: async () => {
1829
+ const sessionID = currentSession();
1830
+ if (!sessionID)
1831
+ return;
1832
+ const location = locationOf(sessionID);
1833
+ try {
1834
+ const result = await rpc.review({
1835
+ sessionID
1836
+ }, location ? {
1837
+ location
1838
+ } : undefined);
1839
+ context.ui.toast.show({
1840
+ title: "Goal review",
1841
+ message: result.message,
1842
+ variant: result.ok ? "info" : "warning"
1843
+ });
1844
+ } catch (error) {
1845
+ context.ui.toast.show({
1846
+ title: "Goal review",
1847
+ message: String(error),
1848
+ variant: "warning"
1849
+ });
1850
+ }
1851
+ }
1852
+ }, {
1853
+ id: "goal.abort",
1854
+ title: "Goal: Abort",
1855
+ group: "Goal",
1856
+ palette: true,
1857
+ enabled: hasGoal,
1858
+ run: async () => {
1859
+ const sessionID = currentSession();
1860
+ if (!sessionID)
1861
+ return;
1862
+ const location = locationOf(sessionID);
1863
+ try {
1864
+ const listed = await rpc.list({}, location ? {
1865
+ location
1866
+ } : undefined);
1867
+ const goal = listed?.goals?.find((g) => g.sessionID === sessionID && g.runId === views[sessionID]?.runId && !g.archived);
1868
+ if (!goal) {
1869
+ context.ui.toast.show({
1870
+ title: "Goal",
1871
+ message: "Current target changed; inspect its details before aborting.",
1872
+ variant: "warning"
1873
+ });
1874
+ return;
1875
+ }
1876
+ const result = await rpc.manage({
1877
+ sessionID,
1878
+ action: "abort",
1879
+ target: targetOf(goal)
1880
+ }, location ? {
1881
+ location
1882
+ } : undefined);
1883
+ context.ui.toast.show({
1884
+ title: "Goal",
1885
+ message: result.message,
1886
+ variant: result.ok ? "info" : "warning"
1887
+ });
1888
+ } catch (error) {
1889
+ context.ui.toast.show({
1890
+ title: "Goal",
1891
+ message: String(error),
1892
+ variant: "warning"
1893
+ });
1894
+ }
1895
+ }
1896
+ }]
1897
+ }));
1898
+ return null;
1899
+ }
1900
+ }));
1901
+ return () => {
1902
+ disposed = true;
1903
+ offHandoff();
1904
+ clearInterval(timer);
1905
+ clearInterval(ticker);
1906
+ offUpdated?.();
1907
+ offNotice?.();
1908
+ offSummary?.();
1909
+ offDecision?.();
1910
+ offResolved?.();
1911
+ offFailed?.();
1912
+ offData?.();
1913
+ for (const dispose of disposers.splice(0))
1914
+ dispose();
1915
+ };
1916
+ }
1917
+ };
1918
+ export {
1919
+ tui_default as default
1920
+ };