omegon 0.6.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.
- package/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure data builders for the dashboard overlay.
|
|
3
|
+
*
|
|
4
|
+
* Separated from overlay.ts so they can be tested without pi-tui dependency.
|
|
5
|
+
* All rendering/theme concerns are abstracted via the ThemeFn callback.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { sharedState } from "../shared-state.ts";
|
|
9
|
+
import type { CleaveState, DesignAssessmentResult, DesignSpecBindingState, DesignTreeDashboardState, OpenSpecDashboardState } from "./types.ts";
|
|
10
|
+
import type { ProviderRoutingPolicy } from "../lib/model-routing.ts";
|
|
11
|
+
import {
|
|
12
|
+
getDashboardFileUri,
|
|
13
|
+
getOpenSpecArtifactUri,
|
|
14
|
+
linkDashboardFile,
|
|
15
|
+
linkOpenSpecArtifact,
|
|
16
|
+
linkOpenSpecChange,
|
|
17
|
+
} from "./uri-helper.ts";
|
|
18
|
+
|
|
19
|
+
// ── Tab definitions ─────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export type TabId = "design" | "openspec" | "cleave" | "system";
|
|
22
|
+
|
|
23
|
+
export interface Tab {
|
|
24
|
+
id: TabId;
|
|
25
|
+
label: string;
|
|
26
|
+
shortcut: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const TABS: Tab[] = [
|
|
30
|
+
{ id: "design", label: "Design Tree", shortcut: "1" },
|
|
31
|
+
{ id: "openspec", label: "Implementation", shortcut: "2" },
|
|
32
|
+
{ id: "cleave", label: "Cleave", shortcut: "3" },
|
|
33
|
+
{ id: "system", label: "System", shortcut: "4" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// ── Item model for navigable lists ──────────────────────────────
|
|
37
|
+
|
|
38
|
+
/** Theme coloring function — (colorName, text) → styled string */
|
|
39
|
+
export type ThemeFn = (color: string, text: string) => string;
|
|
40
|
+
|
|
41
|
+
export interface ListItem {
|
|
42
|
+
key: string;
|
|
43
|
+
depth: number;
|
|
44
|
+
expandable: boolean;
|
|
45
|
+
openUri?: string;
|
|
46
|
+
lines: (th: ThemeFn, width: number) => string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Maximum content lines before the footer hint row. */
|
|
50
|
+
export const MAX_CONTENT_LINES = 30;
|
|
51
|
+
|
|
52
|
+
// ── Status icon helper ──────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
const STATUS_ICONS: Record<string, { color: string; icon: string }> = {
|
|
55
|
+
decided: { color: "success", icon: "●" },
|
|
56
|
+
exploring: { color: "accent", icon: "◐" },
|
|
57
|
+
seed: { color: "muted", icon: "◌" },
|
|
58
|
+
blocked: { color: "error", icon: "✕" },
|
|
59
|
+
deferred: { color: "warning", icon: "◑" },
|
|
60
|
+
implementing: { color: "warning", icon: "⟳" },
|
|
61
|
+
implemented: { color: "success", icon: "✓" },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function statusIcon(status: string, th: ThemeFn): string {
|
|
65
|
+
const entry = STATUS_ICONS[status];
|
|
66
|
+
if (entry) return th(entry.color, entry.icon);
|
|
67
|
+
return th("dim", "○");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Design spec badge helper ────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Returns a colored badge character based on spec binding state and last
|
|
74
|
+
* assessment outcome.
|
|
75
|
+
*
|
|
76
|
+
* ✓ active spec + passed assessment (success)
|
|
77
|
+
* ✦ active spec + no assessment yet, or needs spec (warning)
|
|
78
|
+
* ◐ active spec + ambiguous/reopen assessment (accent)
|
|
79
|
+
* ● archived spec (muted)
|
|
80
|
+
* '' no binding (seed/unlinked node)
|
|
81
|
+
*/
|
|
82
|
+
export function designSpecBadge(
|
|
83
|
+
binding: DesignSpecBindingState | undefined,
|
|
84
|
+
assessmentResult: DesignAssessmentResult | null | undefined,
|
|
85
|
+
th: ThemeFn,
|
|
86
|
+
): string {
|
|
87
|
+
if (!binding || binding.missing) return "";
|
|
88
|
+
if (binding.archived) return th("muted", "●");
|
|
89
|
+
if (!binding.active) return "";
|
|
90
|
+
// active binding
|
|
91
|
+
if (!assessmentResult) return th("warning", "✦");
|
|
92
|
+
switch (assessmentResult.outcome) {
|
|
93
|
+
case "pass": return th("success", "✓");
|
|
94
|
+
case "reopen": return th("accent", "◐");
|
|
95
|
+
case "ambiguous": return th("accent", "◐");
|
|
96
|
+
default: return th("warning", "✦");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Data Builders ───────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export function buildDesignItems(
|
|
103
|
+
dt: DesignTreeDashboardState | undefined,
|
|
104
|
+
expandedKeys: Set<string>,
|
|
105
|
+
): ListItem[] {
|
|
106
|
+
if (!dt) return [];
|
|
107
|
+
|
|
108
|
+
const items: ListItem[] = [];
|
|
109
|
+
|
|
110
|
+
// Pipeline funnel row (collapsible — expand to see per-bucket detail lines)
|
|
111
|
+
if (dt.designPipeline) {
|
|
112
|
+
const p = dt.designPipeline;
|
|
113
|
+
const pKey = "dt-pipeline";
|
|
114
|
+
const isExpanded = expandedKeys.has(pKey);
|
|
115
|
+
items.push({
|
|
116
|
+
key: pKey,
|
|
117
|
+
depth: 0,
|
|
118
|
+
expandable: true,
|
|
119
|
+
lines: (th) => {
|
|
120
|
+
const parts: string[] = [];
|
|
121
|
+
if (p.designing > 0) parts.push(th("accent", `${p.designing} designing`));
|
|
122
|
+
if (p.decided > 0) parts.push(th("success", `${p.decided} decided`));
|
|
123
|
+
if (p.implementing > 0) parts.push(th("warning", `${p.implementing} implementing`));
|
|
124
|
+
if (p.done > 0) parts.push(th("success", `${p.done} done`));
|
|
125
|
+
const expandHint = isExpanded ? th("dim", " ▾") : th("dim", " ▸");
|
|
126
|
+
return [th("dim", "→ ") + (parts.join(th("dim", " · ")) || th("dim", "empty pipeline")) + expandHint];
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (isExpanded) {
|
|
131
|
+
// Detail sub-rows for each non-zero bucket
|
|
132
|
+
const buckets: Array<{ label: string; count: number; color: string }> = [
|
|
133
|
+
{ label: "designing", count: p.designing, color: "accent" },
|
|
134
|
+
{ label: "decided", count: p.decided, color: "success" },
|
|
135
|
+
{ label: "implementing", count: p.implementing, color: "warning" },
|
|
136
|
+
{ label: "done", count: p.done, color: "success" },
|
|
137
|
+
];
|
|
138
|
+
for (const b of buckets) {
|
|
139
|
+
if (b.count === 0) continue;
|
|
140
|
+
items.push({
|
|
141
|
+
key: `dt-pipeline-${b.label}`,
|
|
142
|
+
depth: 1,
|
|
143
|
+
expandable: false,
|
|
144
|
+
lines: (th) => [th(b.color, `${b.count} ${b.label}`)],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (p.needsSpec > 0) {
|
|
148
|
+
items.push({
|
|
149
|
+
key: "dt-pipeline-needs-spec",
|
|
150
|
+
depth: 1,
|
|
151
|
+
expandable: false,
|
|
152
|
+
lines: (th) => [th("warning", `✦ ${p.needsSpec} need spec`)],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Summary
|
|
159
|
+
items.push({
|
|
160
|
+
key: "dt-summary",
|
|
161
|
+
depth: 0,
|
|
162
|
+
expandable: false,
|
|
163
|
+
lines: (th) => {
|
|
164
|
+
const parts: string[] = [];
|
|
165
|
+
if (dt.decidedCount > 0) parts.push(th("success", `${dt.decidedCount} decided`));
|
|
166
|
+
if (dt.exploringCount > 0) parts.push(th("accent", `${dt.exploringCount} exploring`));
|
|
167
|
+
if (dt.blockedCount > 0) parts.push(th("error", `${dt.blockedCount} blocked`));
|
|
168
|
+
if (dt.openQuestionCount > 0) parts.push(th("warning", `${dt.openQuestionCount} open questions`));
|
|
169
|
+
return [parts.join(" · ") || th("dim", "empty")];
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Focused node
|
|
174
|
+
const focused = dt.focusedNode;
|
|
175
|
+
if (focused) {
|
|
176
|
+
const focusedKey = `dt-focused-${focused.id}`;
|
|
177
|
+
const hasQuestions = focused.questions.length > 0;
|
|
178
|
+
items.push({
|
|
179
|
+
key: focusedKey,
|
|
180
|
+
depth: 0,
|
|
181
|
+
expandable: hasQuestions,
|
|
182
|
+
openUri: getDashboardFileUri(focused.filePath),
|
|
183
|
+
lines: (th) => {
|
|
184
|
+
const icon = statusIcon(focused.status, th);
|
|
185
|
+
const linkedTitle = linkDashboardFile(focused.title, focused.filePath);
|
|
186
|
+
const label = th("accent", " (focused)");
|
|
187
|
+
// TODO(types-and-emission): DesignTreeFocusedNode lacks designSpec/assessmentResult fields.
|
|
188
|
+
// Once the sibling task adds those fields, call designSpecBadge here for the focused node.
|
|
189
|
+
return [`${icon} ${linkedTitle}${label}`];
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (hasQuestions && expandedKeys.has(focusedKey)) {
|
|
194
|
+
for (let qi = 0; qi < focused.questions.length; qi++) {
|
|
195
|
+
items.push({
|
|
196
|
+
key: `dt-q-${focused.id}-${qi}`,
|
|
197
|
+
depth: 1,
|
|
198
|
+
expandable: false,
|
|
199
|
+
lines: (th) => [th("warning", `? ${focused.questions[qi]}`)],
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// All nodes list
|
|
206
|
+
if (dt.nodes && dt.nodes.length > 0) {
|
|
207
|
+
for (const node of dt.nodes) {
|
|
208
|
+
// Skip focused node — already shown above
|
|
209
|
+
if (focused && node.id === focused.id) continue;
|
|
210
|
+
|
|
211
|
+
items.push({
|
|
212
|
+
key: `dt-node-${node.id}`,
|
|
213
|
+
depth: 0,
|
|
214
|
+
expandable: false,
|
|
215
|
+
openUri: getDashboardFileUri(node.filePath),
|
|
216
|
+
lines: (th) => {
|
|
217
|
+
const icon = statusIcon(node.status, th);
|
|
218
|
+
const badge = designSpecBadge(node.designSpec, node.assessmentResult, th);
|
|
219
|
+
// spacer: "icon badge title" when badge present, "icon title" when absent
|
|
220
|
+
const spacer = badge ? ` ${badge} ` : " ";
|
|
221
|
+
const linkedTitle = linkDashboardFile(node.title, node.filePath);
|
|
222
|
+
const qLabel = node.questionCount > 0
|
|
223
|
+
? th("warning", ` (${node.questionCount}?)`)
|
|
224
|
+
: "";
|
|
225
|
+
const linkSuffix = node.openspecChange
|
|
226
|
+
? th("dim", " &")
|
|
227
|
+
: "";
|
|
228
|
+
return [`${icon}${spacer}${linkedTitle}${qLabel}${linkSuffix}`];
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
} else if (!focused) {
|
|
233
|
+
// Seed hint when no nodes at all
|
|
234
|
+
items.push({
|
|
235
|
+
key: "dt-empty",
|
|
236
|
+
depth: 0,
|
|
237
|
+
expandable: false,
|
|
238
|
+
lines: (th) => [th("muted", "No design nodes — use /design new <id> <title>")],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return items;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function buildOpenSpecItems(
|
|
246
|
+
os: OpenSpecDashboardState | undefined,
|
|
247
|
+
expandedKeys: Set<string>,
|
|
248
|
+
): ListItem[] {
|
|
249
|
+
if (!os || os.changes.length === 0) return [];
|
|
250
|
+
|
|
251
|
+
const items: ListItem[] = [];
|
|
252
|
+
|
|
253
|
+
items.push({
|
|
254
|
+
key: "os-summary",
|
|
255
|
+
depth: 0,
|
|
256
|
+
expandable: false,
|
|
257
|
+
lines: (th) => [th("dim", `${os.changes.length} active change${os.changes.length > 1 ? "s" : ""}`)],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
for (const change of os.changes) {
|
|
261
|
+
const done = change.tasksTotal > 0 && change.tasksDone >= change.tasksTotal;
|
|
262
|
+
const hasDetails = change.stage !== undefined || change.tasksTotal > 0;
|
|
263
|
+
const key = `os-change-${change.name}`;
|
|
264
|
+
|
|
265
|
+
// Check if any design node links to this change (for & suffix)
|
|
266
|
+
const linkedToDesign = sharedState.designTree?.nodes?.some(
|
|
267
|
+
(n) => n.openspecChange === change.name,
|
|
268
|
+
) ?? false;
|
|
269
|
+
|
|
270
|
+
items.push({
|
|
271
|
+
key,
|
|
272
|
+
depth: 0,
|
|
273
|
+
expandable: hasDetails,
|
|
274
|
+
openUri: getOpenSpecArtifactUri(change.path, "proposal"),
|
|
275
|
+
lines: (th) => {
|
|
276
|
+
const icon = done ? th("success", "✓") : th("dim", "◦");
|
|
277
|
+
const linkedName = linkOpenSpecChange(change.name, change.path);
|
|
278
|
+
const progress = change.tasksTotal > 0
|
|
279
|
+
? th(done ? "success" : "dim", ` ${change.tasksDone}/${change.tasksTotal}`)
|
|
280
|
+
: "";
|
|
281
|
+
const designLink = linkedToDesign ? th("dim", " &") : "";
|
|
282
|
+
return [`${icon} ${linkedName}${progress}${designLink}`];
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (hasDetails && expandedKeys.has(key)) {
|
|
287
|
+
if (change.stage) {
|
|
288
|
+
items.push({
|
|
289
|
+
key: `os-stage-${change.name}`,
|
|
290
|
+
depth: 1,
|
|
291
|
+
expandable: false,
|
|
292
|
+
lines: (th) => [th("dim", `stage: ${change.stage}`)],
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const artifactOrder: Array<"proposal" | "design" | "tasks"> = ["proposal", "design", "tasks"];
|
|
297
|
+
for (const artifact of artifactOrder) {
|
|
298
|
+
if (!change.artifacts?.includes(artifact)) continue;
|
|
299
|
+
items.push({
|
|
300
|
+
key: `os-artifact-${change.name}-${artifact}`,
|
|
301
|
+
depth: 1,
|
|
302
|
+
expandable: false,
|
|
303
|
+
openUri: getOpenSpecArtifactUri(change.path, artifact),
|
|
304
|
+
lines: (th) => {
|
|
305
|
+
const linked = linkOpenSpecArtifact(artifact, change.path, artifact);
|
|
306
|
+
return [th("dim", "file: ") + linked];
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (change.tasksTotal > 0) {
|
|
312
|
+
const pct = Math.round((change.tasksDone / change.tasksTotal) * 100);
|
|
313
|
+
items.push({
|
|
314
|
+
key: `os-progress-${change.name}`,
|
|
315
|
+
depth: 1,
|
|
316
|
+
expandable: false,
|
|
317
|
+
lines: (th) => {
|
|
318
|
+
const barW = 20;
|
|
319
|
+
const filled = Math.round((change.tasksDone / change.tasksTotal) * barW);
|
|
320
|
+
const bar = th("success", "█".repeat(filled)) + th("dim", "░".repeat(barW - filled));
|
|
321
|
+
return [`${bar} ${pct}%`];
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return items;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function buildCleaveItems(
|
|
332
|
+
cl: CleaveState | undefined,
|
|
333
|
+
expandedKeys: Set<string>,
|
|
334
|
+
): ListItem[] {
|
|
335
|
+
if (!cl) return [];
|
|
336
|
+
|
|
337
|
+
const items: ListItem[] = [];
|
|
338
|
+
|
|
339
|
+
const statusColorMap: Record<string, string> = {
|
|
340
|
+
done: "success", failed: "error", idle: "dim",
|
|
341
|
+
};
|
|
342
|
+
const color = statusColorMap[cl.status] ?? "warning";
|
|
343
|
+
|
|
344
|
+
items.push({
|
|
345
|
+
key: "cl-status",
|
|
346
|
+
depth: 0,
|
|
347
|
+
expandable: false,
|
|
348
|
+
lines: (th) => {
|
|
349
|
+
const runLabel = cl.runId ? th("dim", ` (${cl.runId})`) : "";
|
|
350
|
+
return [th(color, cl.status) + runLabel];
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (cl.children && cl.children.length > 0) {
|
|
355
|
+
const doneCount = cl.children.filter((c) => c.status === "done").length;
|
|
356
|
+
const failCount = cl.children.filter((c) => c.status === "failed").length;
|
|
357
|
+
const runCount = cl.children.filter((c) => c.status === "running").length;
|
|
358
|
+
|
|
359
|
+
items.push({
|
|
360
|
+
key: "cl-summary",
|
|
361
|
+
depth: 0,
|
|
362
|
+
expandable: false,
|
|
363
|
+
lines: (th) => {
|
|
364
|
+
const parts: string[] = [];
|
|
365
|
+
parts.push(`${cl.children!.length} children`);
|
|
366
|
+
if (doneCount > 0) parts.push(th("success", `${doneCount} ✓`));
|
|
367
|
+
if (runCount > 0) parts.push(th("warning", `${runCount} ⟳`));
|
|
368
|
+
if (failCount > 0) parts.push(th("error", `${failCount} ✕`));
|
|
369
|
+
return [parts.join(" ")];
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
for (const child of cl.children) {
|
|
374
|
+
const key = `cl-child-${child.label}`;
|
|
375
|
+
const isRunning = child.status === "running";
|
|
376
|
+
const hasDoneElapsed = child.elapsed !== undefined && !isRunning;
|
|
377
|
+
// Running children: compute live elapsed from startedAt; done: use stored elapsed
|
|
378
|
+
const liveElapsedSec = isRunning && child.startedAt
|
|
379
|
+
? Math.round((Date.now() - child.startedAt) / 1000)
|
|
380
|
+
: (child.elapsed ?? 0);
|
|
381
|
+
const fmtSecs = (secs: number) => {
|
|
382
|
+
const m = Math.floor(secs / 60);
|
|
383
|
+
const s = secs % 60;
|
|
384
|
+
return m > 0 ? `${m}m ${s}s` : `${s}s`;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
items.push({
|
|
388
|
+
key,
|
|
389
|
+
depth: 0,
|
|
390
|
+
expandable: hasDoneElapsed,
|
|
391
|
+
lines: (th) => {
|
|
392
|
+
const icon = child.status === "done" ? th("success", "✓")
|
|
393
|
+
: child.status === "failed" ? th("error", "✕")
|
|
394
|
+
: child.status === "running" ? th("warning", "⟳")
|
|
395
|
+
: th("dim", "○");
|
|
396
|
+
const elapsedBadge = isRunning
|
|
397
|
+
? th("dim", ` ${fmtSecs(liveElapsedSec)}`)
|
|
398
|
+
: "";
|
|
399
|
+
return [`${icon} ${child.label}${elapsedBadge}`];
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Running: show last 3 ring-buffer lines (falls back to lastLine)
|
|
404
|
+
const activityLines = child.recentLines?.slice(-3) ?? (child.lastLine ? [child.lastLine] : []);
|
|
405
|
+
if (isRunning && activityLines.length > 0) {
|
|
406
|
+
items.push({
|
|
407
|
+
key: `cl-activity-${child.label}`,
|
|
408
|
+
depth: 1,
|
|
409
|
+
expandable: false,
|
|
410
|
+
lines: (th) => activityLines.map(l => th("dim", l.slice(0, 72))),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Done: show elapsed when expanded
|
|
415
|
+
if (hasDoneElapsed && expandedKeys.has(key)) {
|
|
416
|
+
items.push({
|
|
417
|
+
key: `cl-elapsed-${child.label}`,
|
|
418
|
+
depth: 1,
|
|
419
|
+
expandable: false,
|
|
420
|
+
lines: (th) => [th("dim", `elapsed: ${fmtSecs(child.elapsed!)}`)],
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return items;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ── System / Config tab ─────────────────────────────────────────
|
|
430
|
+
|
|
431
|
+
/** Omegon env vars surfaced in the System tab */
|
|
432
|
+
const PI_ENV_VARS: Array<{ key: string; description: string }> = [
|
|
433
|
+
{ key: "PI_CHILD", description: "Non-empty when running as cleave child" },
|
|
434
|
+
{ key: "PI_OFFLINE", description: "Force offline/local-only mode" },
|
|
435
|
+
{ key: "PI_SKIP_VERSION_CHECK", description: "Disable GitHub release polling" },
|
|
436
|
+
{ key: "PI_DEBUG", description: "Enable verbose debug logging" },
|
|
437
|
+
{ key: "PI_LOG_LEVEL", description: "Log verbosity (debug|info|warn|error)" },
|
|
438
|
+
{ key: "PI_PROVIDER", description: "Override active provider (anthropic|openai|local)" },
|
|
439
|
+
{ key: "PI_MODEL", description: "Override active model ID" },
|
|
440
|
+
{ key: "ANTHROPIC_API_KEY", description: "Anthropic API key" },
|
|
441
|
+
{ key: "OPENAI_API_KEY", description: "OpenAI API key" },
|
|
442
|
+
{ key: "OLLAMA_HOST", description: "Ollama server URL (default: localhost:11434)" },
|
|
443
|
+
];
|
|
444
|
+
|
|
445
|
+
function fmtBool(val: boolean, th: ThemeFn): string {
|
|
446
|
+
return val ? th("success", "yes") : th("dim", "no");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function fmtValue(val: string | undefined, th: ThemeFn): string {
|
|
450
|
+
if (!val) return th("dim", "(unset)");
|
|
451
|
+
if (val.length > 40) return th("muted", val.slice(0, 37) + "…");
|
|
452
|
+
return th("accent", val);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function routingPolicyItems(policy: ProviderRoutingPolicy, expandedKeys: Set<string>): ListItem[] {
|
|
456
|
+
const key = "sys-routing";
|
|
457
|
+
const items: ListItem[] = [];
|
|
458
|
+
|
|
459
|
+
items.push({
|
|
460
|
+
key,
|
|
461
|
+
depth: 0,
|
|
462
|
+
expandable: true,
|
|
463
|
+
lines: (th) => [th("accent", "Routing Policy")],
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (!expandedKeys.has(key)) return items;
|
|
467
|
+
|
|
468
|
+
items.push({
|
|
469
|
+
key: "sys-routing-order",
|
|
470
|
+
depth: 1,
|
|
471
|
+
expandable: false,
|
|
472
|
+
lines: (th) => [th("dim", "provider order: ") + th("muted", policy.providerOrder.join(" → "))],
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
if (policy.avoidProviders.length > 0) {
|
|
476
|
+
items.push({
|
|
477
|
+
key: "sys-routing-avoid",
|
|
478
|
+
depth: 1,
|
|
479
|
+
expandable: false,
|
|
480
|
+
lines: (th) => [th("dim", "avoid: ") + th("warning", policy.avoidProviders.join(", "))],
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
items.push({
|
|
485
|
+
key: "sys-routing-cheap",
|
|
486
|
+
depth: 1,
|
|
487
|
+
expandable: false,
|
|
488
|
+
lines: (th) => [th("dim", "cheap cloud over local: ") + fmtBool(policy.cheapCloudPreferredOverLocal, th)],
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
items.push({
|
|
492
|
+
key: "sys-routing-preflight",
|
|
493
|
+
depth: 1,
|
|
494
|
+
expandable: false,
|
|
495
|
+
lines: (th) => [th("dim", "preflight for large runs: ") + fmtBool(policy.requirePreflightForLargeRuns, th)],
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (policy.notes) {
|
|
499
|
+
items.push({
|
|
500
|
+
key: "sys-routing-notes",
|
|
501
|
+
depth: 1,
|
|
502
|
+
expandable: false,
|
|
503
|
+
lines: (th) => [th("dim", "notes: ") + th("muted", policy.notes!)],
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return items;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function effortItems(effort: any | undefined, expandedKeys: Set<string>): ListItem[] {
|
|
511
|
+
if (!effort) return [];
|
|
512
|
+
const key = "sys-effort";
|
|
513
|
+
const items: ListItem[] = [];
|
|
514
|
+
|
|
515
|
+
items.push({
|
|
516
|
+
key,
|
|
517
|
+
depth: 0,
|
|
518
|
+
expandable: true,
|
|
519
|
+
lines: (th) => {
|
|
520
|
+
const name = effort.name ?? `Level ${effort.level ?? "?"}`;
|
|
521
|
+
const glyphs: Record<number, string> = { 1:"α", 2:"β", 3:"γ", 4:"δ", 5:"ε", 6:"ζ", 7:"ω" };
|
|
522
|
+
const glyph = glyphs[effort.level as number] ?? "·";
|
|
523
|
+
return [th("accent", "Effort Tier") + th("dim", ": ") + th("accent", glyph) + th("dim", " ") + th("muted", name)];
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
if (!expandedKeys.has(key)) return items;
|
|
528
|
+
|
|
529
|
+
const fields: Array<[string, string]> = [
|
|
530
|
+
["level", String(effort.level ?? "?")],
|
|
531
|
+
["driver", effort.driverModel ?? "?"],
|
|
532
|
+
["extract", effort.extractionModel ?? "?"],
|
|
533
|
+
["thinking", effort.thinkingLevel ?? "?"],
|
|
534
|
+
];
|
|
535
|
+
for (const [label, val] of fields) {
|
|
536
|
+
items.push({
|
|
537
|
+
key: `sys-effort-${label}`,
|
|
538
|
+
depth: 1,
|
|
539
|
+
expandable: false,
|
|
540
|
+
lines: (th) => [th("dim", `${label}: `) + th("muted", val)],
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return items;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function recoveryItems(recovery: any | undefined, expandedKeys: Set<string>): ListItem[] {
|
|
548
|
+
if (!recovery) return [];
|
|
549
|
+
const key = "sys-recovery";
|
|
550
|
+
const items: ListItem[] = [];
|
|
551
|
+
|
|
552
|
+
const color = recovery.escalated ? "error" : recovery.action === "retry" ? "warning" : "dim";
|
|
553
|
+
|
|
554
|
+
items.push({
|
|
555
|
+
key,
|
|
556
|
+
depth: 0,
|
|
557
|
+
expandable: true,
|
|
558
|
+
lines: (th) => [th("accent", "Last Recovery Event") + th("dim", ": ") + th(color, recovery.action ?? "?")],
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
if (!expandedKeys.has(key)) return items;
|
|
562
|
+
|
|
563
|
+
items.push({
|
|
564
|
+
key: "sys-recovery-provider",
|
|
565
|
+
depth: 1,
|
|
566
|
+
expandable: false,
|
|
567
|
+
lines: (th) => [th("dim", "provider: ") + th("muted", `${recovery.provider}/${recovery.modelId}`)],
|
|
568
|
+
});
|
|
569
|
+
items.push({
|
|
570
|
+
key: "sys-recovery-class",
|
|
571
|
+
depth: 1,
|
|
572
|
+
expandable: false,
|
|
573
|
+
lines: (th) => [th("dim", "class: ") + th("muted", recovery.classification ?? "?")],
|
|
574
|
+
});
|
|
575
|
+
if (recovery.summary) {
|
|
576
|
+
items.push({
|
|
577
|
+
key: "sys-recovery-summary",
|
|
578
|
+
depth: 1,
|
|
579
|
+
expandable: false,
|
|
580
|
+
lines: (th) => [th("dim", "summary: ") + th("muted", recovery.summary)],
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return items;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function memoryItems(expandedKeys: Set<string>): ListItem[] {
|
|
588
|
+
const key = "sys-memory";
|
|
589
|
+
const items: ListItem[] = [];
|
|
590
|
+
const metrics = sharedState.lastMemoryInjection;
|
|
591
|
+
|
|
592
|
+
items.push({
|
|
593
|
+
key,
|
|
594
|
+
depth: 0,
|
|
595
|
+
expandable: !!metrics,
|
|
596
|
+
lines: (th) => {
|
|
597
|
+
const est = sharedState.memoryTokenEstimate;
|
|
598
|
+
const label = est > 0 ? th("muted", `~${est.toLocaleString()} tokens`) : th("dim", "no injection yet");
|
|
599
|
+
return [th("accent", "Memory Injection") + th("dim", ": ") + label];
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
if (!metrics || !expandedKeys.has(key)) return items;
|
|
604
|
+
|
|
605
|
+
const mFields: Array<[string, string]> = [
|
|
606
|
+
["project facts", String(metrics.projectFactCount ?? "?")],
|
|
607
|
+
["global facts", String(metrics.globalFactCount ?? "?")],
|
|
608
|
+
["working memory", String(metrics.workingMemoryFactCount ?? "?")],
|
|
609
|
+
["episodes", String(metrics.episodeCount ?? "?")],
|
|
610
|
+
["tokens ~", String(metrics.estimatedTokens ?? "?")],
|
|
611
|
+
];
|
|
612
|
+
for (const [label, val] of mFields) {
|
|
613
|
+
items.push({
|
|
614
|
+
key: `sys-mem-${label}`,
|
|
615
|
+
depth: 1,
|
|
616
|
+
expandable: false,
|
|
617
|
+
lines: (th) => [th("dim", `${label}: `) + th("muted", val)],
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return items;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function buildSystemItems(expandedKeys: Set<string>): ListItem[] {
|
|
625
|
+
const items: ListItem[] = [];
|
|
626
|
+
|
|
627
|
+
// ── Section: Environment Variables ──────────────────────────
|
|
628
|
+
const envKey = "sys-env";
|
|
629
|
+
items.push({
|
|
630
|
+
key: envKey,
|
|
631
|
+
depth: 0,
|
|
632
|
+
expandable: true,
|
|
633
|
+
lines: (th) => [th("accent", "Environment Variables")],
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
if (expandedKeys.has(envKey)) {
|
|
637
|
+
for (const { key, description } of PI_ENV_VARS) {
|
|
638
|
+
const raw = process.env[key];
|
|
639
|
+
// Mask key values to avoid leaking secrets
|
|
640
|
+
const masked = key.endsWith("_KEY") && raw
|
|
641
|
+
? raw.slice(0, 4) + "…" + raw.slice(-4)
|
|
642
|
+
: raw;
|
|
643
|
+
items.push({
|
|
644
|
+
key: `sys-env-${key}`,
|
|
645
|
+
depth: 1,
|
|
646
|
+
expandable: false,
|
|
647
|
+
lines: (th) => {
|
|
648
|
+
const valStr = fmtValue(masked, th);
|
|
649
|
+
return [`${th("dim", key + ": ")}${valStr} ${th("muted", description)}`];
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// ── Section: Routing Policy ──────────────────────────────────
|
|
656
|
+
if (sharedState.routingPolicy) {
|
|
657
|
+
items.push(...routingPolicyItems(sharedState.routingPolicy, expandedKeys));
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// ── Section: Effort Tier ─────────────────────────────────────
|
|
661
|
+
items.push(...effortItems(sharedState.effort, expandedKeys));
|
|
662
|
+
|
|
663
|
+
// ── Section: Memory Injection ────────────────────────────────
|
|
664
|
+
items.push(...memoryItems(expandedKeys));
|
|
665
|
+
|
|
666
|
+
// ── Section: Last Recovery Event ────────────────────────────
|
|
667
|
+
items.push(...recoveryItems(sharedState.recovery, expandedKeys));
|
|
668
|
+
|
|
669
|
+
// ── Section: Dashboard Mode ──────────────────────────────────
|
|
670
|
+
items.push({
|
|
671
|
+
key: "sys-mode",
|
|
672
|
+
depth: 0,
|
|
673
|
+
expandable: false,
|
|
674
|
+
lines: (th) => [
|
|
675
|
+
th("dim", "dashboard mode: ") + th("muted", sharedState.dashboardMode ?? "compact") +
|
|
676
|
+
th("dim", " turns: ") + th("muted", String(sharedState.dashboardTurns ?? 0))
|
|
677
|
+
],
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
return items;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// ── State management ────────────────────────────────────────────
|
|
684
|
+
|
|
685
|
+
export function rebuildItems(
|
|
686
|
+
activeTab: TabId,
|
|
687
|
+
expandedKeys: Set<string>,
|
|
688
|
+
): ListItem[] {
|
|
689
|
+
switch (activeTab) {
|
|
690
|
+
case "design":
|
|
691
|
+
return buildDesignItems(sharedState.designTree, expandedKeys);
|
|
692
|
+
case "openspec":
|
|
693
|
+
return buildOpenSpecItems(sharedState.openspec, expandedKeys);
|
|
694
|
+
case "cleave":
|
|
695
|
+
return buildCleaveItems(sharedState.cleave, expandedKeys);
|
|
696
|
+
case "system":
|
|
697
|
+
return buildSystemItems(expandedKeys);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export function clampIndex(selectedIndex: number, itemCount: number): number {
|
|
702
|
+
if (itemCount === 0) return 0;
|
|
703
|
+
if (selectedIndex >= itemCount) return itemCount - 1;
|
|
704
|
+
return selectedIndex;
|
|
705
|
+
}
|