@cortexkit/opencode-magic-context 0.31.2 → 0.31.4
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/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +16 -12
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +13 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify-prompt.d.ts +2 -2
- package/dist/features/magic-context/dreamer/classify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts +8 -0
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts +1 -0
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/manifest-parser.d.ts +7 -0
- package/dist/features/magic-context/dreamer/manifest-parser.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +2 -3
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +8 -0
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/promote-primers.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +5 -0
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts +2 -2
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +4 -0
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +12 -0
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/verification-paths.d.ts +16 -0
- package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +6 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts +1 -0
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -2
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +3 -2
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/schedule.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +17 -6
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/types.d.ts +6 -1
- package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +8 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/workspaces.d.ts +8 -0
- package/dist/features/magic-context/workspaces.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts +2 -0
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +1 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/todo-view.d.ts +27 -2
- package/dist/hooks/magic-context/todo-view.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-message-helpers.d.ts +6 -3
- package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts +1 -0
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/index.js +1046 -333
- package/dist/shared/assistant-message-extractor.d.ts +1 -0
- package/dist/shared/assistant-message-extractor.d.ts.map +1 -1
- package/dist/shared/redaction.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +21 -16
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +3 -1
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/types.d.ts +1 -1
- package/dist/tools/ctx-search/types.d.ts.map +1 -1
- package/dist/tui/data/notification-socket.d.ts +1 -0
- package/dist/tui/data/notification-socket.d.ts.map +1 -1
- package/dist/tui-compiled/badge-contrast.d.ts +46 -0
- package/dist/tui-compiled/badge-contrast.d.ts.map +1 -0
- package/dist/tui-compiled/data/context-db.d.ts +45 -0
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -0
- package/dist/tui-compiled/data/notification-socket.d.ts +40 -0
- package/dist/tui-compiled/data/notification-socket.d.ts.map +1 -0
- package/package.json +7 -4
- package/src/shared/assistant-message-extractor.ts +20 -0
- package/src/shared/redaction.ts +1 -0
- package/src/shared/rpc-client.test.ts +164 -5
- package/src/shared/rpc-notifications.ts +73 -21
- package/src/shared/rpc-server.ts +69 -18
- package/src/shared/rpc-utils.ts +12 -2
- package/src/tui/data/notification-socket.test.ts +131 -0
- package/src/tui/data/notification-socket.ts +81 -16
- package/src/tui/entry.mjs +36 -0
- package/src/tui-compiled/badge-contrast.ts +84 -0
- package/src/tui-compiled/data/context-db.ts +353 -0
- package/src/tui-compiled/data/notification-socket.ts +294 -0
- package/src/tui-compiled/index.tsx +1204 -0
- package/src/tui-compiled/slots/sidebar-content.tsx +1052 -0
- package/src/tui-compiled/types/opencode-plugin-tui.d.ts +232 -0
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
2
|
+
import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
3
|
+
import { effect as _$effect } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
4
|
+
import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
5
|
+
import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
6
|
+
import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
7
|
+
import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
8
|
+
import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
9
|
+
/** @jsxImportSource @opentui/solid */
|
|
10
|
+
import { Show, createEffect, createMemo, createSignal, on, onCleanup } from "opentui:runtime-module:solid-js";
|
|
11
|
+
import packageJson from "../../../package.json";
|
|
12
|
+
import { badgeTextColor } from "../badge-contrast";
|
|
13
|
+
import { loadSidebarSnapshot } from "../data/context-db";
|
|
14
|
+
import { formatThresholdPercent } from "../../shared/format-threshold";
|
|
15
|
+
import { computeEffectiveOrder, DEFAULT_SLOT_ORDER, PLUGIN_KEY, queueTuiPreferenceUpdate, readTuiPreferencesFile, readTuiPreferencesFileSync, resolveMagicContextPrefs, watchTuiPreferences } from "../../shared/tui-preferences";
|
|
16
|
+
|
|
17
|
+
// Module-level hook so the upgrade/recomp dialog can kick the sidebar into its
|
|
18
|
+
// fast recomp self-poll the INSTANT the user confirms — without waiting for a
|
|
19
|
+
// parent-session message event (the RPC upgrade/recomp call fires none). The
|
|
20
|
+
// mounted SidebarContent registers its refresh here.
|
|
21
|
+
let activeRecompPollKick = null;
|
|
22
|
+
export function kickRecompProgressRefresh() {
|
|
23
|
+
activeRecompPollKick?.();
|
|
24
|
+
}
|
|
25
|
+
const SINGLE_BORDER = {
|
|
26
|
+
type: "single"
|
|
27
|
+
};
|
|
28
|
+
const REFRESH_DEBOUNCE_MS = 150;
|
|
29
|
+
// The TUI may unmount and remount sidebar_content when the user switches views
|
|
30
|
+
// (main -> subagent -> main). A remount re-runs the component body, so a signal
|
|
31
|
+
// created inside the component would reset to its seed. The controller lives in
|
|
32
|
+
// the slot-factory closure (plugin/process lifetime) and owns the durable
|
|
33
|
+
// prefs/collapse signals plus the single shared file watcher, so collapse state
|
|
34
|
+
// and live pref reloads survive remounts. No Solid effects/memos here — those
|
|
35
|
+
// need an owner; the poll-interval effect stays inside the component.
|
|
36
|
+
function createSidebarController(initialPrefs) {
|
|
37
|
+
const [prefs, setPrefs] = createSignal(initialPrefs);
|
|
38
|
+
const seedCollapsed = initialPrefs.rememberCollapsed && initialPrefs.collapsed != null ? initialPrefs.collapsed : initialPrefs.startCollapsed;
|
|
39
|
+
const [collapsed, setCollapsed] = createSignal(seedCollapsed);
|
|
40
|
+
let lastPersistedCollapsed = initialPrefs.collapsed;
|
|
41
|
+
let lastApplied = JSON.stringify(initialPrefs);
|
|
42
|
+
|
|
43
|
+
// Watcher lives for the process lifetime — intentionally never disposed.
|
|
44
|
+
// Collapse echo guard: lastPersistedCollapsed advances only once our own
|
|
45
|
+
// write lands, so a watcher echo of the value we just wrote is rejected by
|
|
46
|
+
// the `!==` check and cannot revert a user click.
|
|
47
|
+
watchTuiPreferences(() => {
|
|
48
|
+
void (async () => {
|
|
49
|
+
const next = resolveMagicContextPrefs(await readTuiPreferencesFile());
|
|
50
|
+
const serialized = JSON.stringify(next);
|
|
51
|
+
if (serialized === lastApplied) return;
|
|
52
|
+
lastApplied = serialized;
|
|
53
|
+
setPrefs(next);
|
|
54
|
+
if (next.rememberCollapsed && next.collapsed != null && next.collapsed !== lastPersistedCollapsed) {
|
|
55
|
+
lastPersistedCollapsed = next.collapsed;
|
|
56
|
+
setCollapsed(next.collapsed);
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
});
|
|
60
|
+
function toggleCollapsed() {
|
|
61
|
+
const next = !collapsed();
|
|
62
|
+
setCollapsed(next);
|
|
63
|
+
if (prefs().rememberCollapsed) {
|
|
64
|
+
void queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], next).then(() => {
|
|
65
|
+
lastPersistedCollapsed = next;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
prefs,
|
|
71
|
+
collapsed,
|
|
72
|
+
toggleCollapsed
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function compactTokens(value) {
|
|
76
|
+
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
77
|
+
if (value >= 1_000) return `${(value / 1_000).toFixed(0)}K`;
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
function relativeTime(ms) {
|
|
81
|
+
const diff = Date.now() - ms;
|
|
82
|
+
if (diff < 60_000) return "just now";
|
|
83
|
+
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
|
|
84
|
+
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
|
|
85
|
+
return `${Math.floor(diff / 86_400_000)}d ago`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Text progress bar, e.g. [██████░░░░] for the recomp/upgrade live indicator.
|
|
89
|
+
function progressBar(fraction, width = 14) {
|
|
90
|
+
const clamped = Math.max(0, Math.min(1, fraction));
|
|
91
|
+
const filled = Math.round(clamped * width);
|
|
92
|
+
return `[${"█".repeat(filled)}${"░".repeat(width - filled)}]`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Token breakdown segment colors (hardcoded hex values)
|
|
96
|
+
const COLORS = {
|
|
97
|
+
// Cool / structured — injected by the plugin into message[0]
|
|
98
|
+
system: "#c084fc",
|
|
99
|
+
// Purple
|
|
100
|
+
docs: "#22d3ee",
|
|
101
|
+
// Cyan — <project-docs>
|
|
102
|
+
compartments: "#60a5fa",
|
|
103
|
+
// Blue
|
|
104
|
+
facts: "#fbbf24",
|
|
105
|
+
// Yellow/orange
|
|
106
|
+
memories: "#34d399",
|
|
107
|
+
// Green
|
|
108
|
+
profile: "#a3e635",
|
|
109
|
+
// Lime — <user-profile>
|
|
110
|
+
// Warm / user-facing — regular chat and tool traffic. Grouped visually
|
|
111
|
+
// by hue family so the user reads them as a related block.
|
|
112
|
+
conversation: "#f87171",
|
|
113
|
+
// Red
|
|
114
|
+
toolCalls: "#fb923c",
|
|
115
|
+
// Orange
|
|
116
|
+
toolDefs: "#f472b6" // Pink
|
|
117
|
+
};
|
|
118
|
+
// Segmented token breakdown bar with legend
|
|
119
|
+
const TokenBreakdown = props => {
|
|
120
|
+
// The bar is rendered as a flex row of colored boxes, each with
|
|
121
|
+
// flexGrow=tokens and flexBasis=0. opentui distributes the parent
|
|
122
|
+
// container's full width proportionally, so the bar always fills the
|
|
123
|
+
// sidebar regardless of terminal size. No hardcoded width is needed —
|
|
124
|
+
// this fixes both the over-wide bar that wrapped onto a second line on
|
|
125
|
+
// narrow sidebars (issue #90) and the under-wide bar that left empty
|
|
126
|
+
// space on the right on wide sidebars.
|
|
127
|
+
const segments = createMemo(() => {
|
|
128
|
+
const s = props.snapshot;
|
|
129
|
+
const total = s.inputTokens || 1;
|
|
130
|
+
const result = [];
|
|
131
|
+
|
|
132
|
+
// System Prompt (purple)
|
|
133
|
+
if (s.systemPromptTokens > 0) {
|
|
134
|
+
result.push({
|
|
135
|
+
key: "sys",
|
|
136
|
+
tokens: s.systemPromptTokens,
|
|
137
|
+
color: COLORS.system,
|
|
138
|
+
label: "System"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Docs (cyan) — injected <project-docs> block (ARCHITECTURE/STRUCTURE)
|
|
143
|
+
if (s.docsTokens > 0) {
|
|
144
|
+
result.push({
|
|
145
|
+
key: "docs",
|
|
146
|
+
tokens: s.docsTokens,
|
|
147
|
+
color: COLORS.docs,
|
|
148
|
+
label: "Docs"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Compartments (blue)
|
|
153
|
+
if (s.compartmentTokens > 0) {
|
|
154
|
+
result.push({
|
|
155
|
+
key: "comp",
|
|
156
|
+
tokens: s.compartmentTokens,
|
|
157
|
+
color: COLORS.compartments,
|
|
158
|
+
label: "Compartments"
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Facts (yellow/orange)
|
|
163
|
+
if (s.factTokens > 0) {
|
|
164
|
+
result.push({
|
|
165
|
+
key: "fact",
|
|
166
|
+
tokens: s.factTokens,
|
|
167
|
+
color: COLORS.facts,
|
|
168
|
+
label: "Facts"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Memories (green)
|
|
173
|
+
if (s.memoryTokens > 0) {
|
|
174
|
+
result.push({
|
|
175
|
+
key: "mem",
|
|
176
|
+
tokens: s.memoryTokens,
|
|
177
|
+
color: COLORS.memories,
|
|
178
|
+
label: "Memories"
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// User Profile (lime) — injected <user-profile> block (promoted user memories)
|
|
183
|
+
if (s.profileTokens > 0) {
|
|
184
|
+
result.push({
|
|
185
|
+
key: "profile",
|
|
186
|
+
tokens: s.profileTokens,
|
|
187
|
+
color: COLORS.profile,
|
|
188
|
+
label: "User Profile"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Conversation = real user/assistant text/reasoning/images
|
|
193
|
+
// (excludes injected session-history and excludes tool call I/O).
|
|
194
|
+
//
|
|
195
|
+
// Always show this row even when conversationTokens === 0. The
|
|
196
|
+
// calibrator's residual-distribution math (tokenizer-calibration.ts)
|
|
197
|
+
// can round it down to zero when toolCallsLocal massively dominates
|
|
198
|
+
// conversationLocal — that's a calibration artifact, not a real
|
|
199
|
+
// "zero conversation". Suppressing the row leaves the legend looking
|
|
200
|
+
// truncated, which is more confusing than showing a 0% line. The
|
|
201
|
+
// segment is also skipped in the bar at 0 width because the segment
|
|
202
|
+
// builder uses `Math.max(1, ...)` only when tokens > 0 (see
|
|
203
|
+
// segmentWidths), so the visual bar stays correct either way.
|
|
204
|
+
result.push({
|
|
205
|
+
key: "conv",
|
|
206
|
+
tokens: s.conversationTokens,
|
|
207
|
+
color: COLORS.conversation,
|
|
208
|
+
label: "Conversation"
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Tool Calls = tool_use/tool_result/tool/tool-invocation parts in messages
|
|
212
|
+
// (actionable — users can reduce via ctx_reduce)
|
|
213
|
+
if (s.toolCallTokens > 0) {
|
|
214
|
+
result.push({
|
|
215
|
+
key: "tool-calls",
|
|
216
|
+
tokens: s.toolCallTokens,
|
|
217
|
+
color: COLORS.toolCalls,
|
|
218
|
+
label: "Tool Calls"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Tool Definitions = measured description + JSON-schema parameters for
|
|
223
|
+
// each tool OpenCode sends in the `tools` request parameter, populated
|
|
224
|
+
// by the `tool.definition` plugin hook keyed by {provider, model, agent}.
|
|
225
|
+
// Zero until the first turn measures the active agent's tool set.
|
|
226
|
+
if (s.toolDefinitionTokens > 0) {
|
|
227
|
+
result.push({
|
|
228
|
+
key: "tool-defs",
|
|
229
|
+
tokens: s.toolDefinitionTokens,
|
|
230
|
+
color: COLORS.toolDefs,
|
|
231
|
+
label: "Tool Defs"
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
});
|
|
236
|
+
const totalTokens = createMemo(() => props.snapshot.inputTokens || 1);
|
|
237
|
+
|
|
238
|
+
// Render-time segments for the bar. Zero-token segments are filtered out
|
|
239
|
+
// entirely (no flex weight, no rendered box) so they don't claim any
|
|
240
|
+
// width. Non-zero segments still get a Math.max(1, ...) floor on
|
|
241
|
+
// flexGrow so very small contributions remain visible as a thin sliver.
|
|
242
|
+
// The legend rows below show every segment (including zeros) for table
|
|
243
|
+
// stability — only the bar prunes them.
|
|
244
|
+
const barSegments = createMemo(() => segments().filter(seg => seg.tokens > 0));
|
|
245
|
+
return (() => {
|
|
246
|
+
var _el$ = _$createElement("box"),
|
|
247
|
+
_el$2 = _$createElement("box");
|
|
248
|
+
_$insertNode(_el$, _el$2);
|
|
249
|
+
_$setProp(_el$, "width", "100%");
|
|
250
|
+
_$setProp(_el$, "flexDirection", "column");
|
|
251
|
+
_$setProp(_el$2, "width", "100%");
|
|
252
|
+
_$setProp(_el$2, "flexDirection", "row");
|
|
253
|
+
_$setProp(_el$2, "height", 1);
|
|
254
|
+
_$insert(_el$2, () => barSegments().map(seg => (() => {
|
|
255
|
+
var _el$3 = _$createElement("box");
|
|
256
|
+
_$setProp(_el$3, "flexBasis", 0);
|
|
257
|
+
_$setProp(_el$3, "height", 1);
|
|
258
|
+
_$effect(_p$ => {
|
|
259
|
+
var _v$ = seg.key,
|
|
260
|
+
_v$2 = Math.max(1, seg.tokens),
|
|
261
|
+
_v$3 = seg.color;
|
|
262
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "key", _v$, _p$.e));
|
|
263
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$3, "flexGrow", _v$2, _p$.t));
|
|
264
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$3, "backgroundColor", _v$3, _p$.a));
|
|
265
|
+
return _p$;
|
|
266
|
+
}, {
|
|
267
|
+
e: undefined,
|
|
268
|
+
t: undefined,
|
|
269
|
+
a: undefined
|
|
270
|
+
});
|
|
271
|
+
return _el$3;
|
|
272
|
+
})()));
|
|
273
|
+
_$insert(_el$, (() => {
|
|
274
|
+
var _c$ = _$memo(() => !!!props.collapsed);
|
|
275
|
+
return () => _c$() && (() => {
|
|
276
|
+
var _el$4 = _$createElement("box");
|
|
277
|
+
_$setProp(_el$4, "flexDirection", "column");
|
|
278
|
+
_$setProp(_el$4, "marginTop", 0);
|
|
279
|
+
_$insert(_el$4, () => segments().map(seg => {
|
|
280
|
+
const pct = (seg.tokens / totalTokens() * 100).toFixed(0);
|
|
281
|
+
return (() => {
|
|
282
|
+
var _el$5 = _$createElement("box"),
|
|
283
|
+
_el$6 = _$createElement("text"),
|
|
284
|
+
_el$7 = _$createElement("text"),
|
|
285
|
+
_el$8 = _$createTextNode(` (`),
|
|
286
|
+
_el$9 = _$createTextNode(`%)`);
|
|
287
|
+
_$insertNode(_el$5, _el$6);
|
|
288
|
+
_$insertNode(_el$5, _el$7);
|
|
289
|
+
_$setProp(_el$5, "width", "100%");
|
|
290
|
+
_$setProp(_el$5, "flexDirection", "row");
|
|
291
|
+
_$setProp(_el$5, "justifyContent", "space-between");
|
|
292
|
+
_$insert(_el$6, () => seg.label);
|
|
293
|
+
_$insertNode(_el$7, _el$8);
|
|
294
|
+
_$insertNode(_el$7, _el$9);
|
|
295
|
+
_$insert(_el$7, () => compactTokens(seg.tokens), _el$8);
|
|
296
|
+
_$insert(_el$7, pct, _el$9);
|
|
297
|
+
_$effect(_p$ => {
|
|
298
|
+
var _v$4 = seg.key,
|
|
299
|
+
_v$5 = seg.color,
|
|
300
|
+
_v$6 = props.theme.textMuted;
|
|
301
|
+
_v$4 !== _p$.e && (_p$.e = _$setProp(_el$5, "key", _v$4, _p$.e));
|
|
302
|
+
_v$5 !== _p$.t && (_p$.t = _$setProp(_el$6, "fg", _v$5, _p$.t));
|
|
303
|
+
_v$6 !== _p$.a && (_p$.a = _$setProp(_el$7, "fg", _v$6, _p$.a));
|
|
304
|
+
return _p$;
|
|
305
|
+
}, {
|
|
306
|
+
e: undefined,
|
|
307
|
+
t: undefined,
|
|
308
|
+
a: undefined
|
|
309
|
+
});
|
|
310
|
+
return _el$5;
|
|
311
|
+
})();
|
|
312
|
+
}));
|
|
313
|
+
return _el$4;
|
|
314
|
+
})();
|
|
315
|
+
})(), null);
|
|
316
|
+
return _el$;
|
|
317
|
+
})();
|
|
318
|
+
};
|
|
319
|
+
const StatRow = props => {
|
|
320
|
+
const fg = createMemo(() => {
|
|
321
|
+
if (props.warning) return props.theme.warning;
|
|
322
|
+
if (props.accent) return props.theme.accent;
|
|
323
|
+
if (props.dim) return props.theme.textMuted;
|
|
324
|
+
return props.theme.text;
|
|
325
|
+
});
|
|
326
|
+
return (() => {
|
|
327
|
+
var _el$0 = _$createElement("box"),
|
|
328
|
+
_el$1 = _$createElement("text"),
|
|
329
|
+
_el$10 = _$createElement("text"),
|
|
330
|
+
_el$11 = _$createElement("b");
|
|
331
|
+
_$insertNode(_el$0, _el$1);
|
|
332
|
+
_$insertNode(_el$0, _el$10);
|
|
333
|
+
_$setProp(_el$0, "width", "100%");
|
|
334
|
+
_$setProp(_el$0, "flexDirection", "row");
|
|
335
|
+
_$setProp(_el$0, "justifyContent", "space-between");
|
|
336
|
+
_$insert(_el$1, () => props.label);
|
|
337
|
+
_$insertNode(_el$10, _el$11);
|
|
338
|
+
_$insert(_el$11, () => props.value);
|
|
339
|
+
_$effect(_p$ => {
|
|
340
|
+
var _v$7 = props.theme.textMuted,
|
|
341
|
+
_v$8 = fg();
|
|
342
|
+
_v$7 !== _p$.e && (_p$.e = _$setProp(_el$1, "fg", _v$7, _p$.e));
|
|
343
|
+
_v$8 !== _p$.t && (_p$.t = _$setProp(_el$10, "fg", _v$8, _p$.t));
|
|
344
|
+
return _p$;
|
|
345
|
+
}, {
|
|
346
|
+
e: undefined,
|
|
347
|
+
t: undefined
|
|
348
|
+
});
|
|
349
|
+
return _el$0;
|
|
350
|
+
})();
|
|
351
|
+
};
|
|
352
|
+
const SectionHeader = props => (() => {
|
|
353
|
+
var _el$12 = _$createElement("box"),
|
|
354
|
+
_el$13 = _$createElement("text"),
|
|
355
|
+
_el$14 = _$createElement("b");
|
|
356
|
+
_$insertNode(_el$12, _el$13);
|
|
357
|
+
_$setProp(_el$12, "width", "100%");
|
|
358
|
+
_$setProp(_el$12, "marginTop", 1);
|
|
359
|
+
_$insertNode(_el$13, _el$14);
|
|
360
|
+
_$insert(_el$14, () => props.title);
|
|
361
|
+
_$effect(_$p => _$setProp(_el$13, "fg", props.theme.text, _$p));
|
|
362
|
+
return _el$12;
|
|
363
|
+
})();
|
|
364
|
+
|
|
365
|
+
// Live recomp / session-upgrade progress. Renders while an upgrade runs (and
|
|
366
|
+
// briefly after it finishes) so a multi-minute rebuild is visible instead of a
|
|
367
|
+
// single missed toast (dogfood 2026-05-30).
|
|
368
|
+
const RecompProgressSection = props => {
|
|
369
|
+
// CRITICAL: read `props.progress` reactively on every access — do NOT
|
|
370
|
+
// destructure it into a local `const p = props.progress` at creation time.
|
|
371
|
+
// The parent keeps THIS component instance mounted as the phase advances
|
|
372
|
+
// (recomp → migration → done), so a frozen `p` would render the
|
|
373
|
+
// creation-time phase forever — the sidebar stuck on "upgrading / Running
|
|
374
|
+
// historian (pass 1)…" even though the upgrade finished. Each accessor below
|
|
375
|
+
// tracks the parent signal so the label/bar/note update live (root cause of
|
|
376
|
+
// the dogfood 2026-05-30 "recomp upgrading stays" freeze).
|
|
377
|
+
const phase = () => props.progress.phase;
|
|
378
|
+
const fraction = () => props.progress.totalMessages > 0 ? props.progress.processedMessages / props.progress.totalMessages : 0;
|
|
379
|
+
const pct = () => Math.round(fraction() * 100);
|
|
380
|
+
|
|
381
|
+
// "Recomp" vs "Upgrade" vs "Embed" wording follows the flow that started this
|
|
382
|
+
// run, so a plain /ctx-recomp never renders as an "Upgrade" (dogfood 2026-06-04).
|
|
383
|
+
const verb = () => props.progress.kind === "upgrade" ? "Upgrade" : props.progress.kind === "embed" ? "Embed" : props.progress.kind === "wrapup" ? "Wrapup" : "Recomp";
|
|
384
|
+
const activeText = () => props.progress.kind === "upgrade" ? "upgrading ⟳" : props.progress.kind === "embed" ? "embedding ⟳" : props.progress.kind === "wrapup" ? "wrapping ⟳" : "comparting ⟳";
|
|
385
|
+
const label = createMemo(() => {
|
|
386
|
+
switch (props.progress.phase) {
|
|
387
|
+
case "recomp":
|
|
388
|
+
return {
|
|
389
|
+
text: activeText(),
|
|
390
|
+
color: props.theme.warning
|
|
391
|
+
};
|
|
392
|
+
case "migration":
|
|
393
|
+
return {
|
|
394
|
+
text: "Migrating memories ⟳",
|
|
395
|
+
color: props.theme.warning
|
|
396
|
+
};
|
|
397
|
+
case "done":
|
|
398
|
+
return {
|
|
399
|
+
text: `✓ ${verb()} complete`,
|
|
400
|
+
color: props.theme.success ?? props.theme.accent
|
|
401
|
+
};
|
|
402
|
+
case "skipped":
|
|
403
|
+
// Neutral terse status next to the bold verb header; the full,
|
|
404
|
+
// self-contained reason (lease-busy "try again shortly" vs a
|
|
405
|
+
// partial-stall "run /ctx-embed start again") renders on its own
|
|
406
|
+
// line below. Don't re-prepend verb here (it's already the bold
|
|
407
|
+
// header — doing so read as "EmbedEmbed"), and don't hardcode
|
|
408
|
+
// "retry shortly" (wrong for a partial stall).
|
|
409
|
+
return {
|
|
410
|
+
text: "stopped",
|
|
411
|
+
color: props.theme.textMuted
|
|
412
|
+
};
|
|
413
|
+
case "failed":
|
|
414
|
+
return {
|
|
415
|
+
text: `✗ ${verb()} failed`,
|
|
416
|
+
color: props.theme.error
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
return [(() => {
|
|
421
|
+
var _el$15 = _$createElement("box"),
|
|
422
|
+
_el$16 = _$createElement("text"),
|
|
423
|
+
_el$17 = _$createElement("b"),
|
|
424
|
+
_el$18 = _$createElement("text");
|
|
425
|
+
_$insertNode(_el$15, _el$16);
|
|
426
|
+
_$insertNode(_el$15, _el$18);
|
|
427
|
+
_$setProp(_el$15, "width", "100%");
|
|
428
|
+
_$setProp(_el$15, "marginTop", 1);
|
|
429
|
+
_$setProp(_el$15, "flexDirection", "row");
|
|
430
|
+
_$setProp(_el$15, "justifyContent", "space-between");
|
|
431
|
+
_$insertNode(_el$16, _el$17);
|
|
432
|
+
_$insert(_el$17, verb);
|
|
433
|
+
_$insert(_el$18, () => label().text);
|
|
434
|
+
_$effect(_p$ => {
|
|
435
|
+
var _v$9 = props.theme.text,
|
|
436
|
+
_v$0 = label().color;
|
|
437
|
+
_v$9 !== _p$.e && (_p$.e = _$setProp(_el$16, "fg", _v$9, _p$.e));
|
|
438
|
+
_v$0 !== _p$.t && (_p$.t = _$setProp(_el$18, "fg", _v$0, _p$.t));
|
|
439
|
+
return _p$;
|
|
440
|
+
}, {
|
|
441
|
+
e: undefined,
|
|
442
|
+
t: undefined
|
|
443
|
+
});
|
|
444
|
+
return _el$15;
|
|
445
|
+
})(), _$memo(() => _$memo(() => !!(phase() === "recomp" && props.progress.totalMessages > 0))() && (() => {
|
|
446
|
+
var _el$19 = _$createElement("box"),
|
|
447
|
+
_el$20 = _$createElement("text"),
|
|
448
|
+
_el$21 = _$createElement("text"),
|
|
449
|
+
_el$22 = _$createTextNode(`%`);
|
|
450
|
+
_$insertNode(_el$19, _el$20);
|
|
451
|
+
_$insertNode(_el$19, _el$21);
|
|
452
|
+
_$setProp(_el$19, "width", "100%");
|
|
453
|
+
_$setProp(_el$19, "flexDirection", "row");
|
|
454
|
+
_$setProp(_el$19, "justifyContent", "space-between");
|
|
455
|
+
_$insert(_el$20, () => progressBar(fraction()));
|
|
456
|
+
_$insertNode(_el$21, _el$22);
|
|
457
|
+
_$insert(_el$21, pct, _el$22);
|
|
458
|
+
_$effect(_p$ => {
|
|
459
|
+
var _v$1 = props.theme.accent,
|
|
460
|
+
_v$10 = props.theme.textMuted;
|
|
461
|
+
_v$1 !== _p$.e && (_p$.e = _$setProp(_el$20, "fg", _v$1, _p$.e));
|
|
462
|
+
_v$10 !== _p$.t && (_p$.t = _$setProp(_el$21, "fg", _v$10, _p$.t));
|
|
463
|
+
return _p$;
|
|
464
|
+
}, {
|
|
465
|
+
e: undefined,
|
|
466
|
+
t: undefined
|
|
467
|
+
});
|
|
468
|
+
return _el$19;
|
|
469
|
+
})()), _$memo(() => _$memo(() => !!((phase() === "recomp" || phase() === "migration") && props.progress.note))() && (() => {
|
|
470
|
+
var _el$23 = _$createElement("text");
|
|
471
|
+
_$insert(_el$23, () => props.progress.note);
|
|
472
|
+
_$effect(_$p => _$setProp(_el$23, "fg", props.theme.textMuted, _$p));
|
|
473
|
+
return _el$23;
|
|
474
|
+
})()), _$memo(() => _$memo(() => !!(phase() === "recomp" && props.progress.kind !== "embed"))() && _$createComponent(StatRow, {
|
|
475
|
+
get theme() {
|
|
476
|
+
return props.theme;
|
|
477
|
+
},
|
|
478
|
+
label: "Compartments",
|
|
479
|
+
get value() {
|
|
480
|
+
return `${props.progress.compartmentsCreated} (${props.progress.passCount} pass${props.progress.passCount === 1 ? "" : "es"})`;
|
|
481
|
+
},
|
|
482
|
+
dim: true
|
|
483
|
+
})), _$memo(() => _$memo(() => !!(phase() === "recomp" && props.progress.kind === "embed"))() && _$createComponent(StatRow, {
|
|
484
|
+
get theme() {
|
|
485
|
+
return props.theme;
|
|
486
|
+
},
|
|
487
|
+
label: "Compartments",
|
|
488
|
+
get value() {
|
|
489
|
+
return `${props.progress.processedMessages}/${props.progress.totalMessages} embedded`;
|
|
490
|
+
},
|
|
491
|
+
dim: true
|
|
492
|
+
})), _$memo(() => _$memo(() => !!((phase() === "failed" || phase() === "skipped") && props.progress.message))() && (() => {
|
|
493
|
+
var _el$24 = _$createElement("text");
|
|
494
|
+
_$insert(_el$24, () => props.progress.message);
|
|
495
|
+
_$effect(_$p => _$setProp(_el$24, "fg", props.theme.textMuted, _$p));
|
|
496
|
+
return _el$24;
|
|
497
|
+
})())];
|
|
498
|
+
};
|
|
499
|
+
const SidebarContent = props => {
|
|
500
|
+
const [snapshot, setSnapshot] = createSignal(null);
|
|
501
|
+
// Collapse state + section visibility prefs live in the controller (plugin
|
|
502
|
+
// closure), so they survive view-switch remounts and persist across restarts
|
|
503
|
+
// via ~/.config/opencode/tui-preferences.jsonc. Read reactively.
|
|
504
|
+
const collapsed = props.controller.collapsed;
|
|
505
|
+
const sections = () => props.controller.prefs().sections;
|
|
506
|
+
const headerLabel = () => props.controller.prefs().header.label;
|
|
507
|
+
let refreshTimer;
|
|
508
|
+
// Self-sustaining poll while a recomp/upgrade is running. Recomp work
|
|
509
|
+
// happens in CHILD sessions whose message events are filtered out of the
|
|
510
|
+
// subscription below, so without this the progress bar would freeze until
|
|
511
|
+
// the next parent-session message. Active only during recomp/migration;
|
|
512
|
+
// stops itself once the phase goes terminal/absent (dogfood 2026-05-30).
|
|
513
|
+
let recompPollTimer;
|
|
514
|
+
const RECOMP_POLL_MS = 1200;
|
|
515
|
+
// Robust recomp poll state. The loop MUST survive a failed/slow snapshot
|
|
516
|
+
// fetch — the server is busy doing the historian LLM call during a recomp,
|
|
517
|
+
// so a poll can reject or return a stale (pre-recomp) cached snapshot. The
|
|
518
|
+
// OLD loop reattached the next timer only inside `.then()`, so any rejection
|
|
519
|
+
// killed it and the bar froze mid-pass (dogfood 2026-05-30). This version
|
|
520
|
+
// reschedules on BOTH success and failure, keyed on `recompActive`, and only
|
|
521
|
+
// stops on a terminal phase, a bounded "never started" probe window, or the
|
|
522
|
+
// entry vanishing after we'd seen it active.
|
|
523
|
+
let recompActive = false;
|
|
524
|
+
let recompSawPhase = false;
|
|
525
|
+
let recompPollCount = 0;
|
|
526
|
+
let recompConsecutiveAbsent = 0;
|
|
527
|
+
const RECOMP_PROBE_MAX = 12; // ~15s for the server's "Starting…" to land
|
|
528
|
+
// After we've SEEN an active phase, a momentarily absent snapshot is almost
|
|
529
|
+
// always transient — the server's sticky cache serves a pre-recomp snapshot
|
|
530
|
+
// (no recompProgress) during the token-quiet recomp window, or a concurrent
|
|
531
|
+
// BEGIN-IMMEDIATE publish makes the snapshot DB read throw → bare empty. The
|
|
532
|
+
// entry is held until terminal + a 30s grace, so we keep polling through many
|
|
533
|
+
// absents and only give up after a long run of them (entry truly gone but we
|
|
534
|
+
// somehow missed "done"). This was the freeze: the old logic stopped on the
|
|
535
|
+
// FIRST absent-after-active (dogfood 2026-05-30).
|
|
536
|
+
const RECOMP_ABSENT_GIVEUP = 40; // ~48s of continuous absence → stop
|
|
537
|
+
const RECOMP_MAX_POLLS = 1500; // ~30min absolute safety cap
|
|
538
|
+
|
|
539
|
+
const refresh = () => {
|
|
540
|
+
const sid = props.sessionID();
|
|
541
|
+
if (!sid) return;
|
|
542
|
+
const directory = props.api.state.path.directory ?? "";
|
|
543
|
+
void loadSidebarSnapshot(sid, directory).then(data => {
|
|
544
|
+
// Guard against a session switch while this load was in flight:
|
|
545
|
+
// painting session A's snapshot into the now-active session B shows
|
|
546
|
+
// the wrong session's numbers until B's own refresh resolves.
|
|
547
|
+
if (props.sessionID() !== sid) return;
|
|
548
|
+
setSnapshot(data);
|
|
549
|
+
try {
|
|
550
|
+
props.api.renderer.requestRender();
|
|
551
|
+
} catch {
|
|
552
|
+
// Ignore render errors
|
|
553
|
+
}
|
|
554
|
+
// If a recomp/upgrade is running (detected via any refresh, e.g.
|
|
555
|
+
// a /ctx-recomp command not started from the dialog), make sure
|
|
556
|
+
// the dedicated poll loop is running.
|
|
557
|
+
const phase = data?.recompProgress?.phase;
|
|
558
|
+
if ((phase === "recomp" || phase === "migration") && !recompActive) {
|
|
559
|
+
kickRecompPoll();
|
|
560
|
+
}
|
|
561
|
+
}).catch(() => {
|
|
562
|
+
// one-shot refresh failure is non-fatal; the recomp loop (if any)
|
|
563
|
+
// has its own resilient retry.
|
|
564
|
+
});
|
|
565
|
+
};
|
|
566
|
+
const scheduleRefresh = () => {
|
|
567
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
568
|
+
refreshTimer = setTimeout(() => {
|
|
569
|
+
refreshTimer = undefined;
|
|
570
|
+
refresh();
|
|
571
|
+
}, REFRESH_DEBOUNCE_MS);
|
|
572
|
+
};
|
|
573
|
+
const scheduleRecompTick = () => {
|
|
574
|
+
if (!recompActive) return;
|
|
575
|
+
if (recompPollTimer) clearTimeout(recompPollTimer);
|
|
576
|
+
recompPollTimer = setTimeout(recompTick, RECOMP_POLL_MS);
|
|
577
|
+
};
|
|
578
|
+
function recompTick() {
|
|
579
|
+
if (!recompActive) return;
|
|
580
|
+
recompPollCount += 1;
|
|
581
|
+
if (recompPollCount > RECOMP_MAX_POLLS) {
|
|
582
|
+
recompActive = false;
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const sid = props.sessionID();
|
|
586
|
+
if (!sid) {
|
|
587
|
+
recompActive = false;
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const directory = props.api.state.path.directory ?? "";
|
|
591
|
+
void loadSidebarSnapshot(sid, directory).then(data => {
|
|
592
|
+
const phase = data?.recompProgress?.phase;
|
|
593
|
+
// While a recomp is known-active, a transient snapshot that lost
|
|
594
|
+
// recompProgress (sticky cache / busy-DB empty) must NOT wipe the
|
|
595
|
+
// visible bar — carry the last good progress forward so it stays
|
|
596
|
+
// stable until a real update or the terminal state lands.
|
|
597
|
+
const prevProgress = snapshot()?.recompProgress;
|
|
598
|
+
const merged = !phase && recompSawPhase && prevProgress ? {
|
|
599
|
+
...data,
|
|
600
|
+
recompProgress: prevProgress
|
|
601
|
+
} : data;
|
|
602
|
+
setSnapshot(merged);
|
|
603
|
+
try {
|
|
604
|
+
props.api.renderer.requestRender();
|
|
605
|
+
} catch {
|
|
606
|
+
// ignore render errors
|
|
607
|
+
}
|
|
608
|
+
if (phase === "recomp" || phase === "migration") {
|
|
609
|
+
recompSawPhase = true;
|
|
610
|
+
recompConsecutiveAbsent = 0;
|
|
611
|
+
scheduleRecompTick();
|
|
612
|
+
} else if (phase === "done" || phase === "failed" || phase === "skipped") {
|
|
613
|
+
// Terminal state rendered — stop. The server keeps "done"/
|
|
614
|
+
// "skipped" for a grace window and "failed" until the next run,
|
|
615
|
+
// so the outcome stays visible without further polling.
|
|
616
|
+
recompActive = false;
|
|
617
|
+
} else {
|
|
618
|
+
// Phase absent this poll.
|
|
619
|
+
recompConsecutiveAbsent += 1;
|
|
620
|
+
if (!recompSawPhase) {
|
|
621
|
+
// Still waiting for the server's first "Starting…".
|
|
622
|
+
if (recompPollCount < RECOMP_PROBE_MAX) scheduleRecompTick();else {
|
|
623
|
+
recompActive = false;
|
|
624
|
+
}
|
|
625
|
+
} else if (recompConsecutiveAbsent < RECOMP_ABSENT_GIVEUP) {
|
|
626
|
+
// Seen it active — absent is almost certainly the sticky
|
|
627
|
+
// cache / a transient snapshot read. Keep polling so we
|
|
628
|
+
// still catch the terminal state. DON'T overwrite the
|
|
629
|
+
// last good progress snapshot with this transient empty.
|
|
630
|
+
scheduleRecompTick();
|
|
631
|
+
} else {
|
|
632
|
+
// Long continuous absence — the entry is genuinely gone.
|
|
633
|
+
recompActive = false;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}).catch(err => {
|
|
637
|
+
// CRITICAL: a failed/slow fetch must NOT kill the loop — keep
|
|
638
|
+
// polling while active so we still catch the terminal state.
|
|
639
|
+
scheduleRecompTick();
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Kick the resilient recomp poll loop on dialog confirm (or when a refresh
|
|
644
|
+
// first detects an active recomp). The server emits an immediate "Starting…"
|
|
645
|
+
// entry; the probe window covers the brief RPC race before it lands.
|
|
646
|
+
function kickRecompPoll() {
|
|
647
|
+
if (recompActive) return; // already running
|
|
648
|
+
recompActive = true;
|
|
649
|
+
recompSawPhase = false;
|
|
650
|
+
recompPollCount = 0;
|
|
651
|
+
recompConsecutiveAbsent = 0;
|
|
652
|
+
recompTick();
|
|
653
|
+
}
|
|
654
|
+
activeRecompPollKick = kickRecompPoll;
|
|
655
|
+
onCleanup(() => {
|
|
656
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
657
|
+
if (recompPollTimer) clearTimeout(recompPollTimer);
|
|
658
|
+
recompActive = false;
|
|
659
|
+
if (activeRecompPollKick === kickRecompPoll) activeRecompPollKick = null;
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Refresh on session change
|
|
663
|
+
createEffect(on(props.sessionID, () => {
|
|
664
|
+
refresh();
|
|
665
|
+
}));
|
|
666
|
+
|
|
667
|
+
// Subscribe to events for live updates
|
|
668
|
+
createEffect(on(props.sessionID, sessionID => {
|
|
669
|
+
const unsubs = [props.api.event.on("message.updated", event => {
|
|
670
|
+
if (event.properties.info.sessionID !== sessionID) return;
|
|
671
|
+
scheduleRefresh();
|
|
672
|
+
}), props.api.event.on("session.updated", event => {
|
|
673
|
+
if (event.properties.info.id !== sessionID) return;
|
|
674
|
+
scheduleRefresh();
|
|
675
|
+
}), props.api.event.on("message.removed", event => {
|
|
676
|
+
if (event.properties.sessionID !== sessionID) return;
|
|
677
|
+
scheduleRefresh();
|
|
678
|
+
})];
|
|
679
|
+
onCleanup(() => {
|
|
680
|
+
for (const unsub of unsubs) unsub();
|
|
681
|
+
});
|
|
682
|
+
}, {
|
|
683
|
+
defer: false
|
|
684
|
+
}));
|
|
685
|
+
const s = createMemo(() => snapshot());
|
|
686
|
+
const contextSummaryColor = createMemo(() => {
|
|
687
|
+
const usage = s()?.usagePercentage ?? 0;
|
|
688
|
+
if (usage >= 80) return props.theme.error;
|
|
689
|
+
if (usage >= 65) return props.theme.warning;
|
|
690
|
+
return props.theme.accent;
|
|
691
|
+
});
|
|
692
|
+
return (() => {
|
|
693
|
+
var _el$25 = _$createElement("box"),
|
|
694
|
+
_el$26 = _$createElement("box"),
|
|
695
|
+
_el$27 = _$createElement("box"),
|
|
696
|
+
_el$28 = _$createElement("text"),
|
|
697
|
+
_el$29 = _$createElement("b"),
|
|
698
|
+
_el$30 = _$createElement("text"),
|
|
699
|
+
_el$31 = _$createTextNode(`v`);
|
|
700
|
+
_$insertNode(_el$25, _el$26);
|
|
701
|
+
_$setProp(_el$25, "width", "100%");
|
|
702
|
+
_$setProp(_el$25, "flexDirection", "column");
|
|
703
|
+
_$setProp(_el$25, "border", SINGLE_BORDER);
|
|
704
|
+
_$setProp(_el$25, "paddingTop", 1);
|
|
705
|
+
_$setProp(_el$25, "paddingBottom", 1);
|
|
706
|
+
_$setProp(_el$25, "paddingLeft", 1);
|
|
707
|
+
_$setProp(_el$25, "paddingRight", 1);
|
|
708
|
+
_$insertNode(_el$26, _el$27);
|
|
709
|
+
_$insertNode(_el$26, _el$30);
|
|
710
|
+
_$setProp(_el$26, "flexDirection", "row");
|
|
711
|
+
_$setProp(_el$26, "justifyContent", "space-between");
|
|
712
|
+
_$setProp(_el$26, "alignItems", "center");
|
|
713
|
+
_$setProp(_el$26, "onMouseDown", () => props.controller.toggleCollapsed());
|
|
714
|
+
_$insertNode(_el$27, _el$28);
|
|
715
|
+
_$setProp(_el$27, "paddingLeft", 1);
|
|
716
|
+
_$setProp(_el$27, "paddingRight", 1);
|
|
717
|
+
_$insertNode(_el$28, _el$29);
|
|
718
|
+
_$insert(_el$29, () => collapsed() ? "▶ " : "▼ ", null);
|
|
719
|
+
_$insert(_el$29, headerLabel, null);
|
|
720
|
+
_$insertNode(_el$30, _el$31);
|
|
721
|
+
_$insert(_el$30, () => packageJson.version, null);
|
|
722
|
+
_$insert(_el$25, (() => {
|
|
723
|
+
var _c$2 = _$memo(() => !!(s() && s().inputTokens > 0));
|
|
724
|
+
return () => _c$2() && (() => {
|
|
725
|
+
var _el$32 = _$createElement("box");
|
|
726
|
+
_$setProp(_el$32, "flexDirection", "column");
|
|
727
|
+
_$insert(_el$32, (() => {
|
|
728
|
+
var _c$5 = _$memo(() => (s()?.contextLimit ?? 0) > 0);
|
|
729
|
+
return () => _c$5() && (() => {
|
|
730
|
+
var _el$33 = _$createElement("box"),
|
|
731
|
+
_el$34 = _$createElement("text"),
|
|
732
|
+
_el$35 = _$createElement("b"),
|
|
733
|
+
_el$36 = _$createTextNode(`%`),
|
|
734
|
+
_el$37 = _$createTextNode(` / `),
|
|
735
|
+
_el$38 = _$createTextNode(`%`),
|
|
736
|
+
_el$39 = _$createElement("text"),
|
|
737
|
+
_el$40 = _$createTextNode(` / `);
|
|
738
|
+
_$insertNode(_el$33, _el$34);
|
|
739
|
+
_$insertNode(_el$33, _el$39);
|
|
740
|
+
_$setProp(_el$33, "width", "100%");
|
|
741
|
+
_$setProp(_el$33, "flexDirection", "row");
|
|
742
|
+
_$setProp(_el$33, "justifyContent", "space-between");
|
|
743
|
+
_$insertNode(_el$34, _el$35);
|
|
744
|
+
_$insertNode(_el$34, _el$37);
|
|
745
|
+
_$insertNode(_el$34, _el$38);
|
|
746
|
+
_$insertNode(_el$35, _el$36);
|
|
747
|
+
_$insert(_el$35, () => s().usagePercentage.toFixed(1), _el$36);
|
|
748
|
+
_$insert(_el$34, () => formatThresholdPercent(s().executeThreshold), _el$38);
|
|
749
|
+
_$insertNode(_el$39, _el$40);
|
|
750
|
+
_$insert(_el$39, () => compactTokens(s().inputTokens), _el$40);
|
|
751
|
+
_$insert(_el$39, () => compactTokens(s().contextLimit), null);
|
|
752
|
+
_$effect(_p$ => {
|
|
753
|
+
var _v$15 = contextSummaryColor(),
|
|
754
|
+
_v$16 = contextSummaryColor();
|
|
755
|
+
_v$15 !== _p$.e && (_p$.e = _$setProp(_el$34, "fg", _v$15, _p$.e));
|
|
756
|
+
_v$16 !== _p$.t && (_p$.t = _$setProp(_el$39, "fg", _v$16, _p$.t));
|
|
757
|
+
return _p$;
|
|
758
|
+
}, {
|
|
759
|
+
e: undefined,
|
|
760
|
+
t: undefined
|
|
761
|
+
});
|
|
762
|
+
return _el$33;
|
|
763
|
+
})();
|
|
764
|
+
})(), null);
|
|
765
|
+
_$insert(_el$32, _$createComponent(TokenBreakdown, {
|
|
766
|
+
get theme() {
|
|
767
|
+
return props.theme;
|
|
768
|
+
},
|
|
769
|
+
get snapshot() {
|
|
770
|
+
return s();
|
|
771
|
+
},
|
|
772
|
+
get collapsed() {
|
|
773
|
+
return collapsed();
|
|
774
|
+
}
|
|
775
|
+
}), null);
|
|
776
|
+
_$effect(_$p => _$setProp(_el$32, "marginTop", collapsed() ? 0 : 1, _$p));
|
|
777
|
+
return _el$32;
|
|
778
|
+
})();
|
|
779
|
+
})(), null);
|
|
780
|
+
_$insert(_el$25, (() => {
|
|
781
|
+
var _c$3 = _$memo(() => !!collapsed());
|
|
782
|
+
return () => _c$3() && (() => {
|
|
783
|
+
var _el$41 = _$createElement("box"),
|
|
784
|
+
_el$42 = _$createElement("box"),
|
|
785
|
+
_el$43 = _$createElement("text"),
|
|
786
|
+
_el$45 = _$createElement("box"),
|
|
787
|
+
_el$46 = _$createElement("text"),
|
|
788
|
+
_el$48 = _$createElement("text"),
|
|
789
|
+
_el$49 = _$createElement("box"),
|
|
790
|
+
_el$50 = _$createElement("text"),
|
|
791
|
+
_el$52 = _$createElement("text"),
|
|
792
|
+
_el$53 = _$createTextNode(`C:`),
|
|
793
|
+
_el$54 = _$createTextNode(` Q:`),
|
|
794
|
+
_el$55 = _$createTextNode(` N:`);
|
|
795
|
+
_$insertNode(_el$41, _el$42);
|
|
796
|
+
_$insertNode(_el$41, _el$45);
|
|
797
|
+
_$insertNode(_el$41, _el$49);
|
|
798
|
+
_$setProp(_el$41, "width", "100%");
|
|
799
|
+
_$setProp(_el$41, "flexDirection", "column");
|
|
800
|
+
_$insertNode(_el$42, _el$43);
|
|
801
|
+
_$setProp(_el$42, "width", "100%");
|
|
802
|
+
_$setProp(_el$42, "flexDirection", "row");
|
|
803
|
+
_$setProp(_el$42, "justifyContent", "space-between");
|
|
804
|
+
_$insertNode(_el$43, _$createTextNode(`Historian`));
|
|
805
|
+
_$insert(_el$42, (() => {
|
|
806
|
+
var _c$6 = _$memo(() => !!s()?.historianRunning);
|
|
807
|
+
return () => _c$6() ? (() => {
|
|
808
|
+
var _el$56 = _$createElement("text");
|
|
809
|
+
_$insertNode(_el$56, _$createTextNode(`comparting ⟳`));
|
|
810
|
+
_$effect(_$p => _$setProp(_el$56, "fg", props.theme.warning, _$p));
|
|
811
|
+
return _el$56;
|
|
812
|
+
})() : (() => {
|
|
813
|
+
var _el$58 = _$createElement("text");
|
|
814
|
+
_$insertNode(_el$58, _$createTextNode(`idle`));
|
|
815
|
+
_$effect(_$p => _$setProp(_el$58, "fg", props.theme.textMuted, _$p));
|
|
816
|
+
return _el$58;
|
|
817
|
+
})();
|
|
818
|
+
})(), null);
|
|
819
|
+
_$insertNode(_el$45, _el$46);
|
|
820
|
+
_$insertNode(_el$45, _el$48);
|
|
821
|
+
_$setProp(_el$45, "width", "100%");
|
|
822
|
+
_$setProp(_el$45, "flexDirection", "row");
|
|
823
|
+
_$setProp(_el$45, "justifyContent", "space-between");
|
|
824
|
+
_$insertNode(_el$46, _$createTextNode(`Memories`));
|
|
825
|
+
_$insert(_el$48, (() => {
|
|
826
|
+
var _c$7 = _$memo(() => (s()?.memoryBlockCount ?? 0) > 0);
|
|
827
|
+
return () => _c$7() ? `${s().memoryBlockCount}/${s()?.memoryCount ?? 0}` : String(s()?.memoryCount ?? 0);
|
|
828
|
+
})());
|
|
829
|
+
_$insertNode(_el$49, _el$50);
|
|
830
|
+
_$insertNode(_el$49, _el$52);
|
|
831
|
+
_$setProp(_el$49, "width", "100%");
|
|
832
|
+
_$setProp(_el$49, "flexDirection", "row");
|
|
833
|
+
_$setProp(_el$49, "justifyContent", "space-between");
|
|
834
|
+
_$insertNode(_el$50, _$createTextNode(`Status`));
|
|
835
|
+
_$insertNode(_el$52, _el$53);
|
|
836
|
+
_$insertNode(_el$52, _el$54);
|
|
837
|
+
_$insertNode(_el$52, _el$55);
|
|
838
|
+
_$insert(_el$52, () => s()?.compartmentCount ?? 0, _el$54);
|
|
839
|
+
_$insert(_el$52, () => s()?.pendingOpsCount ?? 0, _el$55);
|
|
840
|
+
_$insert(_el$52, () => s()?.sessionNoteCount ?? 0, null);
|
|
841
|
+
_$insert(_el$41, _$createComponent(Show, {
|
|
842
|
+
get when() {
|
|
843
|
+
return s()?.recompProgress;
|
|
844
|
+
},
|
|
845
|
+
children: progress => _$createComponent(RecompProgressSection, {
|
|
846
|
+
get theme() {
|
|
847
|
+
return props.theme;
|
|
848
|
+
},
|
|
849
|
+
get progress() {
|
|
850
|
+
return progress();
|
|
851
|
+
}
|
|
852
|
+
})
|
|
853
|
+
}), null);
|
|
854
|
+
_$effect(_p$ => {
|
|
855
|
+
var _v$17 = props.theme.textMuted,
|
|
856
|
+
_v$18 = props.theme.textMuted,
|
|
857
|
+
_v$19 = props.theme.textMuted,
|
|
858
|
+
_v$20 = props.theme.textMuted,
|
|
859
|
+
_v$21 = props.theme.textMuted;
|
|
860
|
+
_v$17 !== _p$.e && (_p$.e = _$setProp(_el$43, "fg", _v$17, _p$.e));
|
|
861
|
+
_v$18 !== _p$.t && (_p$.t = _$setProp(_el$46, "fg", _v$18, _p$.t));
|
|
862
|
+
_v$19 !== _p$.a && (_p$.a = _$setProp(_el$48, "fg", _v$19, _p$.a));
|
|
863
|
+
_v$20 !== _p$.o && (_p$.o = _$setProp(_el$50, "fg", _v$20, _p$.o));
|
|
864
|
+
_v$21 !== _p$.i && (_p$.i = _$setProp(_el$52, "fg", _v$21, _p$.i));
|
|
865
|
+
return _p$;
|
|
866
|
+
}, {
|
|
867
|
+
e: undefined,
|
|
868
|
+
t: undefined,
|
|
869
|
+
a: undefined,
|
|
870
|
+
o: undefined,
|
|
871
|
+
i: undefined
|
|
872
|
+
});
|
|
873
|
+
return _el$41;
|
|
874
|
+
})();
|
|
875
|
+
})(), null);
|
|
876
|
+
_$insert(_el$25, (() => {
|
|
877
|
+
var _c$4 = _$memo(() => !!!collapsed());
|
|
878
|
+
return () => _c$4() && [_$memo(() => _$memo(() => !!sections().historian)() && [(() => {
|
|
879
|
+
var _el$60 = _$createElement("box"),
|
|
880
|
+
_el$61 = _$createElement("text"),
|
|
881
|
+
_el$62 = _$createElement("b");
|
|
882
|
+
_$insertNode(_el$60, _el$61);
|
|
883
|
+
_$setProp(_el$60, "width", "100%");
|
|
884
|
+
_$setProp(_el$60, "marginTop", 1);
|
|
885
|
+
_$setProp(_el$60, "flexDirection", "row");
|
|
886
|
+
_$setProp(_el$60, "justifyContent", "space-between");
|
|
887
|
+
_$insertNode(_el$61, _el$62);
|
|
888
|
+
_$insertNode(_el$62, _$createTextNode(`Historian`));
|
|
889
|
+
_$insert(_el$60, (() => {
|
|
890
|
+
var _c$8 = _$memo(() => !!s()?.historianRunning);
|
|
891
|
+
return () => _c$8() ? (() => {
|
|
892
|
+
var _el$64 = _$createElement("text");
|
|
893
|
+
_$insertNode(_el$64, _$createTextNode(`comparting ⟳`));
|
|
894
|
+
_$effect(_$p => _$setProp(_el$64, "fg", props.theme.warning, _$p));
|
|
895
|
+
return _el$64;
|
|
896
|
+
})() : (() => {
|
|
897
|
+
var _el$66 = _$createElement("text");
|
|
898
|
+
_$insertNode(_el$66, _$createTextNode(`idle`));
|
|
899
|
+
_$effect(_$p => _$setProp(_el$66, "fg", props.theme.textMuted, _$p));
|
|
900
|
+
return _el$66;
|
|
901
|
+
})();
|
|
902
|
+
})(), null);
|
|
903
|
+
_$effect(_$p => _$setProp(_el$61, "fg", props.theme.text, _$p));
|
|
904
|
+
return _el$60;
|
|
905
|
+
})(), _$createComponent(StatRow, {
|
|
906
|
+
get theme() {
|
|
907
|
+
return props.theme;
|
|
908
|
+
},
|
|
909
|
+
label: "Compartments",
|
|
910
|
+
get value() {
|
|
911
|
+
return String(s()?.compartmentCount ?? 0);
|
|
912
|
+
}
|
|
913
|
+
}), _$createComponent(Show, {
|
|
914
|
+
get when() {
|
|
915
|
+
return s()?.recompProgress;
|
|
916
|
+
},
|
|
917
|
+
children: progress => _$createComponent(RecompProgressSection, {
|
|
918
|
+
get theme() {
|
|
919
|
+
return props.theme;
|
|
920
|
+
},
|
|
921
|
+
get progress() {
|
|
922
|
+
return progress();
|
|
923
|
+
}
|
|
924
|
+
})
|
|
925
|
+
})]), _$memo(() => _$memo(() => !!sections().memory)() && [_$createComponent(SectionHeader, {
|
|
926
|
+
get theme() {
|
|
927
|
+
return props.theme;
|
|
928
|
+
},
|
|
929
|
+
title: "Memory"
|
|
930
|
+
}), _$createComponent(StatRow, {
|
|
931
|
+
get theme() {
|
|
932
|
+
return props.theme;
|
|
933
|
+
},
|
|
934
|
+
label: "Memories",
|
|
935
|
+
get value() {
|
|
936
|
+
return String(s()?.memoryCount ?? 0);
|
|
937
|
+
},
|
|
938
|
+
accent: true
|
|
939
|
+
}), _$memo(() => _$memo(() => (s()?.memoryBlockCount ?? 0) > 0)() && _$createComponent(StatRow, {
|
|
940
|
+
get theme() {
|
|
941
|
+
return props.theme;
|
|
942
|
+
},
|
|
943
|
+
label: "Injected",
|
|
944
|
+
get value() {
|
|
945
|
+
return String(s().memoryBlockCount);
|
|
946
|
+
},
|
|
947
|
+
dim: true
|
|
948
|
+
}))]), _$memo(() => _$memo(() => !!(sections().status && ((s()?.pendingOpsCount ?? 0) > 0 || (s()?.sessionNoteCount ?? 0) > 0 || (s()?.readySmartNoteCount ?? 0) > 0)))() && [_$createComponent(SectionHeader, {
|
|
949
|
+
get theme() {
|
|
950
|
+
return props.theme;
|
|
951
|
+
},
|
|
952
|
+
title: "Status"
|
|
953
|
+
}), _$memo(() => _$memo(() => (s()?.pendingOpsCount ?? 0) > 0)() && _$createComponent(StatRow, {
|
|
954
|
+
get theme() {
|
|
955
|
+
return props.theme;
|
|
956
|
+
},
|
|
957
|
+
label: "Queue",
|
|
958
|
+
get value() {
|
|
959
|
+
return `${s().pendingOpsCount} pending`;
|
|
960
|
+
},
|
|
961
|
+
warning: true
|
|
962
|
+
})), _$memo(() => _$memo(() => (s()?.sessionNoteCount ?? 0) > 0)() && _$createComponent(StatRow, {
|
|
963
|
+
get theme() {
|
|
964
|
+
return props.theme;
|
|
965
|
+
},
|
|
966
|
+
label: "Notes",
|
|
967
|
+
get value() {
|
|
968
|
+
return String(s().sessionNoteCount);
|
|
969
|
+
}
|
|
970
|
+
})), _$memo(() => _$memo(() => (s()?.readySmartNoteCount ?? 0) > 0)() && _$createComponent(StatRow, {
|
|
971
|
+
get theme() {
|
|
972
|
+
return props.theme;
|
|
973
|
+
},
|
|
974
|
+
label: "Smart Notes",
|
|
975
|
+
get value() {
|
|
976
|
+
return `${s().readySmartNoteCount} ready`;
|
|
977
|
+
},
|
|
978
|
+
accent: true
|
|
979
|
+
}))]), _$memo(() => _$memo(() => !!(sections().dreamer && s()?.lastDreamerRunAt))() && [_$createComponent(SectionHeader, {
|
|
980
|
+
get theme() {
|
|
981
|
+
return props.theme;
|
|
982
|
+
},
|
|
983
|
+
title: "Dreamer"
|
|
984
|
+
}), _$createComponent(StatRow, {
|
|
985
|
+
get theme() {
|
|
986
|
+
return props.theme;
|
|
987
|
+
},
|
|
988
|
+
label: "Last run",
|
|
989
|
+
get value() {
|
|
990
|
+
return relativeTime(s().lastDreamerRunAt);
|
|
991
|
+
},
|
|
992
|
+
dim: true
|
|
993
|
+
})]), _$memo(() => _$memo(() => !!(sections().stats && s()?.totalInputTokens != null))() && [_$createComponent(SectionHeader, {
|
|
994
|
+
get theme() {
|
|
995
|
+
return props.theme;
|
|
996
|
+
},
|
|
997
|
+
title: "Stats"
|
|
998
|
+
}), _$createComponent(StatRow, {
|
|
999
|
+
get theme() {
|
|
1000
|
+
return props.theme;
|
|
1001
|
+
},
|
|
1002
|
+
label: "Total tokens",
|
|
1003
|
+
get value() {
|
|
1004
|
+
return compactTokens(s().totalInputTokens ?? 0);
|
|
1005
|
+
},
|
|
1006
|
+
dim: true
|
|
1007
|
+
})])];
|
|
1008
|
+
})(), null);
|
|
1009
|
+
_$effect(_p$ => {
|
|
1010
|
+
var _v$11 = props.theme.borderActive,
|
|
1011
|
+
_v$12 = props.theme.accent,
|
|
1012
|
+
_v$13 = badgeTextColor(props.theme.accent, props.theme.background),
|
|
1013
|
+
_v$14 = props.theme.textMuted;
|
|
1014
|
+
_v$11 !== _p$.e && (_p$.e = _$setProp(_el$25, "borderColor", _v$11, _p$.e));
|
|
1015
|
+
_v$12 !== _p$.t && (_p$.t = _$setProp(_el$27, "backgroundColor", _v$12, _p$.t));
|
|
1016
|
+
_v$13 !== _p$.a && (_p$.a = _$setProp(_el$28, "fg", _v$13, _p$.a));
|
|
1017
|
+
_v$14 !== _p$.o && (_p$.o = _$setProp(_el$30, "fg", _v$14, _p$.o));
|
|
1018
|
+
return _p$;
|
|
1019
|
+
}, {
|
|
1020
|
+
e: undefined,
|
|
1021
|
+
t: undefined,
|
|
1022
|
+
a: undefined,
|
|
1023
|
+
o: undefined
|
|
1024
|
+
});
|
|
1025
|
+
return _el$25;
|
|
1026
|
+
})();
|
|
1027
|
+
};
|
|
1028
|
+
export function createSidebarContentSlot(api) {
|
|
1029
|
+
// Seed synchronously at slot construction so the sidebar renders at its
|
|
1030
|
+
// final collapse state + order on the first paint (no async flicker). The
|
|
1031
|
+
// controller lives here in the factory closure for the plugin lifetime, so
|
|
1032
|
+
// collapse state and live pref reloads survive sidebar_content remounts.
|
|
1033
|
+
const seedRoot = readTuiPreferencesFileSync();
|
|
1034
|
+
const controller = createSidebarController(resolveMagicContextPrefs(seedRoot));
|
|
1035
|
+
const effectiveOrder = computeEffectiveOrder(seedRoot, PLUGIN_KEY, DEFAULT_SLOT_ORDER);
|
|
1036
|
+
return {
|
|
1037
|
+
order: effectiveOrder,
|
|
1038
|
+
slots: {
|
|
1039
|
+
sidebar_content: (ctx, value) => {
|
|
1040
|
+
const theme = createMemo(() => ctx.theme.current);
|
|
1041
|
+
return _$createComponent(SidebarContent, {
|
|
1042
|
+
api: api,
|
|
1043
|
+
sessionID: () => value.session_id,
|
|
1044
|
+
get theme() {
|
|
1045
|
+
return theme();
|
|
1046
|
+
},
|
|
1047
|
+
controller: controller
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
}
|