@retaw106/mmc-oc 2.5.0 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/{context-xx09sh1g.js → context-9q17e52a.js} +553 -214
  2. package/dist/features/magic-context/auto-search-diagnostics.d.ts +27 -0
  3. package/dist/features/magic-context/auto-search-diagnostics.d.ts.map +1 -0
  4. package/dist/features/magic-context/compaction-marker.d.ts +34 -6
  5. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  6. package/dist/features/magic-context/message-index.d.ts +1 -24
  7. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  8. package/dist/features/magic-context/search.d.ts +4 -0
  9. package/dist/features/magic-context/search.d.ts.map +1 -1
  10. package/dist/features/magic-context/session-decision-calibration.d.ts.map +1 -1
  11. package/dist/features/magic-context/session-retention.fixture.d.ts +4 -0
  12. package/dist/features/magic-context/session-retention.fixture.d.ts.map +1 -0
  13. package/dist/features/magic-context/storage-meta-persisted.d.ts +21 -1
  14. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  15. package/dist/features/magic-context/storage-native-replay.d.ts +2 -0
  16. package/dist/features/magic-context/storage-native-replay.d.ts.map +1 -1
  17. package/dist/features/magic-context/storage-session-tables.d.ts +4 -11
  18. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  19. package/dist/features/magic-context/tool-definition-tokens.d.ts +113 -58
  20. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  21. package/dist/features/magic-context/work-metrics.d.ts +5 -3
  22. package/dist/features/magic-context/work-metrics.d.ts.map +1 -1
  23. package/dist/hooks/magic-context/apply-operations.d.ts +32 -0
  24. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/ask-answer-notice.d.ts +25 -0
  26. package/dist/hooks/magic-context/ask-answer-notice.d.ts.map +1 -0
  27. package/dist/hooks/magic-context/auto-search-observation.d.ts +13 -0
  28. package/dist/hooks/magic-context/auto-search-observation.d.ts.map +1 -0
  29. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +5 -5
  31. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/compaction-marker-publication.d.ts +14 -0
  33. package/dist/hooks/magic-context/compaction-marker-publication.d.ts.map +1 -0
  34. package/dist/hooks/magic-context/compaction-off-transition.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +11 -0
  36. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +11 -3
  39. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/historian-history-wait.d.ts +16 -0
  41. package/dist/hooks/magic-context/historian-history-wait.d.ts.map +1 -0
  42. package/dist/hooks/magic-context/history-boundary-repair.d.ts +1 -1
  43. package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts +3 -1
  47. package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/lkg-replay-fit.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/lkg-slot.d.ts +2 -0
  51. package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/read-session-db.d.ts +8 -2
  54. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/read-session-raw.d.ts +20 -0
  56. package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/signed-thinking-fit-snapshot.d.ts +42 -0
  58. package/dist/hooks/magic-context/signed-thinking-fit-snapshot.d.ts.map +1 -0
  59. package/dist/hooks/magic-context/tag-messages.d.ts +2 -0
  60. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/todo-view.d.ts +2 -2
  62. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  63. package/dist/{index-bpjjcdy9.js → index-8rmt6s4f.js} +77 -31
  64. package/dist/{index-mjf0p7yq.js → index-cgjyhwdk.js} +1 -1
  65. package/dist/{index-k25thxq7.js → index-s3ebcy63.js} +1 -1
  66. package/dist/{index-1vxffj5j.js → index-t0zx3tgz.js} +408 -89
  67. package/dist/{index-0d40b4q8.js → index-xrxpbe74.js} +9355 -9215
  68. package/dist/index.js +14 -4
  69. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  70. package/dist/{read-session-chunk-eb3fy45y.js → read-session-chunk-2s8x6xps.js} +2 -2
  71. package/dist/{recomp-orchestrator-mmpy5cb7.js → recomp-orchestrator-gzd1at06.js} +5 -5
  72. package/dist/{send-session-notification-zpqs213d.js → send-session-notification-vdppp5x6.js} +2 -2
  73. package/dist/shared/search-text.d.ts +2 -0
  74. package/dist/shared/search-text.d.ts.map +1 -1
  75. package/dist/shared/tag-transcript.d.ts +2 -0
  76. package/dist/shared/tag-transcript.d.ts.map +1 -1
  77. package/dist/shared/transcript.d.ts +3 -1
  78. package/dist/shared/transcript.d.ts.map +1 -1
  79. package/dist/tools/ctx-expand/render.d.ts +5 -3
  80. package/dist/tools/ctx-expand/render.d.ts.map +1 -1
  81. package/dist/tools/ctx-expand/tools.d.ts +2 -0
  82. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  83. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  84. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  85. package/dist/tools/parameter-descriptions.d.ts +10 -10
  86. package/dist/tools/parameter-descriptions.d.ts.map +1 -1
  87. package/dist/tui.js +2144 -0
  88. package/dist/v2/hooks/context.d.ts +5 -1
  89. package/dist/v2/hooks/context.d.ts.map +1 -1
  90. package/dist/v2/hooks/store.d.ts.map +1 -1
  91. package/dist/v2/message-times.d.ts +17 -0
  92. package/dist/v2/message-times.d.ts.map +1 -0
  93. package/dist/v2/server.js +11211 -10345
  94. package/dist/v2/store-reader.d.ts +14 -4
  95. package/dist/v2/store-reader.d.ts.map +1 -1
  96. package/dist/v2/tui/client.d.ts +57 -0
  97. package/dist/v2/tui/client.d.ts.map +1 -0
  98. package/dist/v2/tui/context-rpc.d.ts +63 -0
  99. package/dist/v2/tui/context-rpc.d.ts.map +1 -0
  100. package/dist/v2/tui/entry.d.ts +8 -0
  101. package/dist/v2/tui/entry.d.ts.map +1 -0
  102. package/dist/v2/tui/notification-socket.d.ts +26 -0
  103. package/dist/v2/tui/notification-socket.d.ts.map +1 -0
  104. package/dist/v2/tui/session-directory.d.ts +22 -0
  105. package/dist/v2/tui/session-directory.d.ts.map +1 -0
  106. package/dist/v2/tui/types.d.ts +110 -0
  107. package/dist/v2/tui/types.d.ts.map +1 -0
  108. package/dist/web/main.js +85 -11
  109. package/dist/{wrapup-orchestrator-jq589pgr.js → wrapup-orchestrator-ghq4y406.js} +5 -5
  110. package/package.json +13 -3
  111. package/src/config/latch-permanence-guard.test.ts +0 -5
  112. package/src/shared/prompt-surface-a1-golden.md +8 -9
  113. package/src/shared/search-text.test.ts +36 -1
  114. package/src/shared/search-text.ts +48 -0
  115. package/src/shared/sqlite-transaction-mode-guard.test.ts +2 -0
  116. package/src/shared/tag-transcript.ts +47 -4
  117. package/src/shared/transcript.ts +1 -1
  118. package/src/shared/write-transaction-attribution.test.ts +1 -6
  119. package/tui.js +4 -0
  120. package/dist/features/magic-context/message-index-sweep.d.ts +0 -11
  121. package/dist/features/magic-context/message-index-sweep.d.ts.map +0 -1
package/dist/tui.js ADDED
@@ -0,0 +1,2144 @@
1
+ var __esm = (fn, res, err) => () => {
2
+ if (fn)
3
+ try {
4
+ res = fn(fn = 0);
5
+ } catch (e) {
6
+ err = [e];
7
+ }
8
+ if (err)
9
+ throw err[0];
10
+ return res;
11
+ };
12
+
13
+ // src/config/agent-disable.ts
14
+ var COMPACTION_ENABLED_PATH;
15
+ var init_agent_disable = __esm(() => {
16
+ COMPACTION_ENABLED_PATH = `compaction${"."}enabled`;
17
+ });
18
+
19
+ // src/shared/harness.ts
20
+ function getHarness() {
21
+ return currentHarness;
22
+ }
23
+ var currentHarness = "opencode";
24
+
25
+ // src/shared/test-temp-dir.ts
26
+ var STALE_TEMP_DIR_AGE_MS, registeredTempDirs;
27
+ var init_test_temp_dir = __esm(() => {
28
+ STALE_TEMP_DIR_AGE_MS = 60 * 60 * 1000;
29
+ registeredTempDirs = new Set;
30
+ });
31
+
32
+ // src/shared/data-path.ts
33
+ import * as os from "node:os";
34
+ import * as path from "node:path";
35
+ function getMagicContextTempDir(harness = getHarness()) {
36
+ return path.join(os.tmpdir(), harness, "magic-context");
37
+ }
38
+ function getMagicContextLogPath(harness = getHarness()) {
39
+ const envPath = process.env.MMC_LOG_PATH?.trim();
40
+ if (envPath)
41
+ return envPath;
42
+ return path.join(getMagicContextTempDir(harness), "magic-context.log");
43
+ }
44
+ function getMagicContextStorageResolution() {
45
+ const testDataDir = process.env.MMC_TEST_DATA_DIR?.trim();
46
+ if (testDataDir) {
47
+ const perTestDataHome = process.env.XDG_DATA_HOME?.trim();
48
+ if (perTestDataHome && path.resolve(perTestDataHome) !== path.resolve(testDataDir)) {
49
+ return {
50
+ path: path.join(perTestDataHome, "mmc"),
51
+ source: "test isolation"
52
+ };
53
+ }
54
+ return {
55
+ path: path.join(testDataDir, "mmc"),
56
+ source: "test isolation"
57
+ };
58
+ }
59
+ if (false) {}
60
+ const explicitStorageDir = process.env.MMC_STORAGE_DIR?.trim();
61
+ if (explicitStorageDir) {
62
+ if (!path.isAbsolute(explicitStorageDir)) {
63
+ throw new Error("MMC_STORAGE_DIR must be an absolute path");
64
+ }
65
+ return { path: explicitStorageDir, source: "environment override" };
66
+ }
67
+ const xdgDataHome = process.env.XDG_DATA_HOME?.trim();
68
+ if (xdgDataHome) {
69
+ return {
70
+ path: path.join(xdgDataHome, "mmc"),
71
+ source: "XDG_DATA_HOME"
72
+ };
73
+ }
74
+ return {
75
+ path: path.join(os.homedir(), ".local", "share", "mmc"),
76
+ source: "platform default"
77
+ };
78
+ }
79
+ function getMagicContextStorageDir() {
80
+ return getMagicContextStorageResolution().path;
81
+ }
82
+ var init_data_path = __esm(() => {
83
+ init_test_temp_dir();
84
+ });
85
+
86
+ // src/shared/redaction.ts
87
+ import { homedir as homedir2, userInfo } from "node:os";
88
+ function escapeRegex(value) {
89
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
90
+ }
91
+ function redactionTypeForKey(key) {
92
+ const normalized = key.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, "_");
93
+ const suffix = normalized.split(".").filter(Boolean).at(-1) ?? normalized;
94
+ return suffix || "secret";
95
+ }
96
+ function isNonSecretScalarValue(value) {
97
+ const v = value.trim();
98
+ if (v === "true" || v === "false" || v === "null" || v === "undefined")
99
+ return true;
100
+ return /^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(v);
101
+ }
102
+ function isSecretKey(key) {
103
+ const segments = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[._-]+/).filter(Boolean);
104
+ if (segments.length === 0)
105
+ return false;
106
+ if (segments.length === 1) {
107
+ const first = segments[0];
108
+ return Boolean(first && SECRET_SEGMENT_PATTERN.test(first));
109
+ }
110
+ for (let i = 0;i < segments.length; i++) {
111
+ const seg = segments[i];
112
+ if (!seg || !SECRET_SEGMENT_PATTERN.test(seg))
113
+ continue;
114
+ let trailingOk = true;
115
+ for (let j = i + 1;j < segments.length; j++) {
116
+ const tail = segments[j];
117
+ if (!tail)
118
+ continue;
119
+ if (TRAILING_DESCRIPTORS.has(tail))
120
+ continue;
121
+ if (SECRET_SEGMENT_PATTERN.test(tail))
122
+ continue;
123
+ trailingOk = false;
124
+ break;
125
+ }
126
+ if (!trailingOk)
127
+ continue;
128
+ if (UNQUALIFIED_SECRET_SEGMENT_PATTERN.test(seg))
129
+ return true;
130
+ for (let k = i - 1;k >= 0; k--) {
131
+ const lead = segments[k];
132
+ if (lead && SECRET_QUALIFIERS.has(lead))
133
+ return true;
134
+ }
135
+ }
136
+ return false;
137
+ }
138
+ function sanitizePathString(value) {
139
+ const home = process.env.HOME || process.env.USERPROFILE || homedir2();
140
+ const username = userInfo().username;
141
+ let sanitized = value;
142
+ if (home) {
143
+ sanitized = sanitized.replace(new RegExp(escapeRegex(home), "g"), "~");
144
+ }
145
+ sanitized = sanitized.replace(/\/Users\/[^/]+\//g, "/Users/<USER>/");
146
+ sanitized = sanitized.replace(/\/home\/[^/]+\//g, "/home/<USER>/");
147
+ sanitized = sanitized.replace(/C:\\Users\\[^\\]+\\/g, "C:\\Users\\<USER>\\");
148
+ if (username) {
149
+ sanitized = sanitized.replace(new RegExp(escapeRegex(username), "g"), "<USER>");
150
+ }
151
+ return sanitized;
152
+ }
153
+ function redactSecretText(value) {
154
+ let redacted = value;
155
+ for (const { pattern, replacement } of SECRET_TEXT_PATTERNS) {
156
+ if (typeof replacement === "string") {
157
+ redacted = redacted.replace(pattern, replacement);
158
+ } else {
159
+ redacted = redacted.replace(pattern, replacement);
160
+ }
161
+ }
162
+ return redacted;
163
+ }
164
+ function sanitizeDiagnosticText(value) {
165
+ return redactSecretText(sanitizePathString(value));
166
+ }
167
+ function sanitizeConfigValue(value, keyPath = []) {
168
+ if (value === null || typeof value === "number" || typeof value === "boolean")
169
+ return value;
170
+ const key = keyPath.at(-1) ?? "";
171
+ if (key && isSecretKey(key)) {
172
+ return `<REDACTED:${redactionTypeForKey(key)}>`;
173
+ }
174
+ if (typeof value === "string")
175
+ return sanitizeDiagnosticText(value);
176
+ if (Array.isArray(value)) {
177
+ return value.map((entry, index) => sanitizeConfigValue(entry, [...keyPath, String(index)]));
178
+ }
179
+ if (value && typeof value === "object") {
180
+ return Object.fromEntries(Object.entries(value).map(([entryKey, entry]) => [
181
+ entryKey,
182
+ sanitizeConfigValue(entry, [...keyPath, entryKey])
183
+ ]));
184
+ }
185
+ return value;
186
+ }
187
+ var SECRET_WORDS, SECRET_SEGMENT_PATTERN, TRAILING_DESCRIPTORS, SECRET_QUALIFIERS, UNQUALIFIED_SECRET_SEGMENT_PATTERN, SECRET_TEXT_PATTERNS;
188
+ var init_redaction = __esm(() => {
189
+ SECRET_WORDS = [
190
+ "key",
191
+ "token",
192
+ "secret",
193
+ "password",
194
+ "auth",
195
+ "authorization",
196
+ "bearer",
197
+ "credential"
198
+ ];
199
+ SECRET_SEGMENT_PATTERN = new RegExp(`^(?:${SECRET_WORDS.map((w) => `${w}s?`).join("|")})$`, "i");
200
+ TRAILING_DESCRIPTORS = new Set(["id", "ids", "value", "values", "header", "headers"]);
201
+ SECRET_QUALIFIERS = new Set([
202
+ "api",
203
+ "access",
204
+ "private",
205
+ "client",
206
+ "auth",
207
+ "authorization",
208
+ "secret",
209
+ "bearer",
210
+ "session",
211
+ "refresh",
212
+ "service",
213
+ "x",
214
+ "openai",
215
+ "anthropic",
216
+ "google",
217
+ "github",
218
+ "huggingface",
219
+ "aws",
220
+ "azure",
221
+ "id"
222
+ ]);
223
+ UNQUALIFIED_SECRET_SEGMENT_PATTERN = /^(?:password|secret|credential|bearer|authorization)s?$/;
224
+ SECRET_TEXT_PATTERNS = [
225
+ {
226
+ pattern: /\bsk-ant-(?:api03-)?[A-Za-z0-9_-]{32,}/g,
227
+ replacement: "<ANTHROPIC_API_KEY_REDACTED>"
228
+ },
229
+ {
230
+ pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{12,}/g,
231
+ replacement: "<OPENAI_API_KEY_REDACTED>"
232
+ },
233
+ {
234
+ pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}/g,
235
+ replacement: "<GITHUB_PAT_REDACTED>"
236
+ },
237
+ {
238
+ pattern: /\b(?:gh[opsu]|ghr)_[A-Za-z0-9]{30,}/g,
239
+ replacement: "<GITHUB_TOKEN_REDACTED>"
240
+ },
241
+ {
242
+ pattern: /\bhf_[A-Za-z0-9]{30,}/g,
243
+ replacement: "<HUGGINGFACE_TOKEN_REDACTED>"
244
+ },
245
+ {
246
+ pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
247
+ replacement: "<AWS_ACCESS_KEY_ID_REDACTED>"
248
+ },
249
+ {
250
+ pattern: /\bxox[abprsuvc]-[A-Za-z0-9-]{10,}/g,
251
+ replacement: "<SLACK_TOKEN_REDACTED>"
252
+ },
253
+ {
254
+ pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g,
255
+ replacement: "<GOOGLE_API_KEY_REDACTED>"
256
+ },
257
+ {
258
+ pattern: /\b(Authorization\s*:\s*Bearer\s+)([A-Za-z0-9._~+/=-]{8,})/gi,
259
+ replacement: (_full, prefix) => `${prefix}<REDACTED:bearer>`
260
+ },
261
+ {
262
+ pattern: /\b((?:Proxy-)?Authorization\s*:\s*(?:Basic|Digest|Token|Negotiate|NTLM)\s+)([A-Za-z0-9._~+/=-]{4,})/gi,
263
+ replacement: (_full, prefix) => `${prefix}<REDACTED:authorization>`
264
+ },
265
+ {
266
+ pattern: /\b((?:x-)?(?:api-?key|api-?token|auth-?token|access-?token)\s*:\s*)([^\s'"`,;<>]{6,})/gi,
267
+ replacement: (full, prefix, value) => isNonSecretScalarValue(value) ? full : `${prefix}<REDACTED:api_key>`
268
+ },
269
+ {
270
+ pattern: /\b([a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:)([^\s/@]+)(@)/gi,
271
+ replacement: (_full, prefix, _password, at) => `${prefix}<REDACTED:password>${at}`
272
+ },
273
+ {
274
+ pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
275
+ replacement: "<JWT_REDACTED>"
276
+ },
277
+ {
278
+ pattern: /(["'])([^"']*(?:key|token|secret|password|auth|bearer|credential)[^"']*)\1(\s*:\s*)(["'])([^"']*)\4/gi,
279
+ replacement: (full, quote, key, separator, valueQuote, value) => isNonSecretScalarValue(value) ? full : `${quote}${key}${quote}${separator}${valueQuote}<REDACTED:${redactionTypeForKey(key)}>${valueQuote}`
280
+ },
281
+ {
282
+ pattern: /\b([A-Za-z0-9_.-]*(?:key|token|secret|password|auth|bearer|credential)[A-Za-z0-9_.-]*)\s*=\s*([^\s'"`]+)/gi,
283
+ replacement: (full, key, value) => isNonSecretScalarValue(value) ? full : `${key}=<REDACTED:${redactionTypeForKey(key)}>`
284
+ }
285
+ ];
286
+ });
287
+
288
+ // src/shared/logger.ts
289
+ import * as fs from "node:fs";
290
+ import * as path2 from "node:path";
291
+ function boundBuffer() {
292
+ while (bufferedBytes > MAX_BUFFERED_BYTES && buffer.length > 0) {
293
+ bufferedBytes -= Buffer.byteLength(buffer.shift() ?? "");
294
+ droppedLines++;
295
+ }
296
+ }
297
+ function recordSwallowedWrite(error) {
298
+ try {
299
+ swallowedWriteCount++;
300
+ lastErrorMessage = sanitizeDiagnosticText(error instanceof Error ? error.message : String(error));
301
+ lastErrorTime = new Date().toISOString();
302
+ } catch {}
303
+ }
304
+ function ensureDir(filePath) {
305
+ fs.mkdirSync(path2.dirname(filePath), { recursive: true });
306
+ }
307
+ function isMissingFile(error) {
308
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
309
+ }
310
+ function getCurrentLogSize(logFile) {
311
+ if (activeLogFile === logFile && activeLogSize !== null && flushesSinceSizeCheck < SIZE_CHECK_INTERVAL_FLUSHES) {
312
+ return activeLogSize;
313
+ }
314
+ try {
315
+ const stat = fs.statSync(logFile);
316
+ if (!stat.isFile()) {
317
+ throw new Error(`Magic Context log path is not a regular file: ${logFile}`);
318
+ }
319
+ fs.chmodSync(logFile, 384);
320
+ activeLogFile = logFile;
321
+ activeLogSize = stat.size;
322
+ flushesSinceSizeCheck = 0;
323
+ return stat.size;
324
+ } catch (error) {
325
+ if (!isMissingFile(error))
326
+ throw error;
327
+ activeLogFile = logFile;
328
+ activeLogSize = 0;
329
+ flushesSinceSizeCheck = 0;
330
+ return 0;
331
+ }
332
+ }
333
+ function capLogData(data) {
334
+ if (Buffer.byteLength(data) <= MAX_LOG_FILE_BYTES)
335
+ return data;
336
+ let bounded = Buffer.from(data).subarray(0, MAX_LOG_FILE_BYTES).toString("utf8");
337
+ while (Buffer.byteLength(bounded) > MAX_LOG_FILE_BYTES) {
338
+ bounded = bounded.slice(0, -1);
339
+ }
340
+ return bounded;
341
+ }
342
+ function writeBoundedPredecessor(logFile, predecessorPath, size) {
343
+ const predecessorFd = fs.openSync(predecessorPath, "w", 384);
344
+ try {
345
+ fs.fchmodSync(predecessorFd, 384);
346
+ const bytesToCopy = Math.min(size, MAX_LOG_FILE_BYTES);
347
+ const sourceFd = fs.openSync(logFile, "r");
348
+ try {
349
+ const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, bytesToCopy));
350
+ let remaining = bytesToCopy;
351
+ let position = Math.max(0, size - bytesToCopy);
352
+ while (remaining > 0) {
353
+ const bytesRead = fs.readSync(sourceFd, chunk, 0, Math.min(chunk.length, remaining), position);
354
+ if (bytesRead === 0)
355
+ break;
356
+ fs.writeSync(predecessorFd, chunk, 0, bytesRead);
357
+ remaining -= bytesRead;
358
+ position += bytesRead;
359
+ }
360
+ } finally {
361
+ fs.closeSync(sourceFd);
362
+ }
363
+ } finally {
364
+ fs.closeSync(predecessorFd);
365
+ }
366
+ }
367
+ function rotateLogFile(logFile, size) {
368
+ const predecessorPath = `${logFile}.1`;
369
+ writeBoundedPredecessor(logFile, predecessorPath, size);
370
+ fs.truncateSync(logFile, 0);
371
+ activeLogSize = 0;
372
+ flushesSinceSizeCheck = 0;
373
+ }
374
+ function flush() {
375
+ if (flushTimer) {
376
+ clearTimeout(flushTimer);
377
+ flushTimer = null;
378
+ }
379
+ if (buffer.length === 0 && droppedLines === 0)
380
+ return;
381
+ const notice = droppedLines ? `[${new Date().toISOString()}] [magic-context][global] logger dropped ${droppedLines} ${droppedLines === 1 ? "line" : "lines"}: buffer exceeded ${MAX_BUFFERED_BYTES} bytes while writes were pending
382
+ ` : "";
383
+ try {
384
+ const data = capLogData(notice + buffer.join(""));
385
+ const logFile = getMagicContextLogPath();
386
+ ensureDir(logFile);
387
+ let currentSize = getCurrentLogSize(logFile);
388
+ const dataSize = Buffer.byteLength(data);
389
+ if (currentSize > 0 && currentSize + dataSize > MAX_LOG_FILE_BYTES) {
390
+ flushesSinceSizeCheck = SIZE_CHECK_INTERVAL_FLUSHES;
391
+ currentSize = getCurrentLogSize(logFile);
392
+ if (currentSize > 0 && currentSize + dataSize > MAX_LOG_FILE_BYTES) {
393
+ rotateLogFile(logFile, currentSize);
394
+ currentSize = 0;
395
+ }
396
+ }
397
+ fs.appendFileSync(logFile, data, { encoding: "utf8", mode: 384 });
398
+ buffer = [];
399
+ bufferedBytes = 0;
400
+ droppedLines = 0;
401
+ activeLogFile = logFile;
402
+ activeLogSize = currentSize + dataSize;
403
+ flushesSinceSizeCheck++;
404
+ } catch (error) {
405
+ activeLogFile = null;
406
+ activeLogSize = null;
407
+ flushesSinceSizeCheck = 0;
408
+ recordSwallowedWrite(error);
409
+ boundBuffer();
410
+ }
411
+ }
412
+ function scheduleFlush() {
413
+ if (flushTimer)
414
+ return;
415
+ flushTimer = setTimeout(() => {
416
+ flushTimer = null;
417
+ flush();
418
+ }, FLUSH_INTERVAL_MS);
419
+ }
420
+ function log(message, data) {
421
+ if (isTestEnv)
422
+ return;
423
+ try {
424
+ const timestamp = new Date().toISOString();
425
+ const serialized = data === undefined ? "" : data instanceof Error ? ` ${sanitizeDiagnosticText(`${data.message}${data.stack ? `
426
+ ${data.stack}` : ""}`)}` : ` ${JSON.stringify(sanitizeConfigValue(data))}`;
427
+ const line = `[${timestamp}] ${sanitizeDiagnosticText(message)}${serialized}
428
+ `;
429
+ buffer.push(line);
430
+ bufferedBytes += Buffer.byteLength(line);
431
+ boundBuffer();
432
+ if (buffer.length >= BUFFER_SIZE_LIMIT) {
433
+ flush();
434
+ } else {
435
+ scheduleFlush();
436
+ }
437
+ } catch {}
438
+ }
439
+ function flushLogger() {
440
+ flush();
441
+ }
442
+ var isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50, MAX_BUFFERED_BYTES, bufferedBytes = 0, droppedLines = 0, MAX_LOG_FILE_BYTES, SIZE_CHECK_INTERVAL_FLUSHES = 64, activeLogFile = null, activeLogSize = null, flushesSinceSizeCheck = 0, swallowedWriteCount = 0, lastErrorMessage = null, lastErrorTime = null;
443
+ var init_logger = __esm(() => {
444
+ init_data_path();
445
+ init_redaction();
446
+ buffer = [];
447
+ MAX_BUFFERED_BYTES = 1024 * 1024;
448
+ MAX_LOG_FILE_BYTES = 32 * 1024 * 1024;
449
+ if (!isTestEnv) {
450
+ process.on("exit", flush);
451
+ }
452
+ });
453
+
454
+ // src/shared/plugin-package-version.ts
455
+ import { existsSync, readFileSync } from "node:fs";
456
+ import { dirname as dirname2, join as join2 } from "node:path";
457
+ import { fileURLToPath } from "node:url";
458
+ function pluginPackageVersion(moduleUrl = import.meta.url) {
459
+ const useCache = moduleUrl === import.meta.url;
460
+ if (useCache && cachedVersion !== undefined)
461
+ return cachedVersion;
462
+ let found = null;
463
+ try {
464
+ let directory = dirname2(fileURLToPath(moduleUrl));
465
+ for (let depth = 0;depth < 10; depth++) {
466
+ const candidate = join2(directory, "package.json");
467
+ if (existsSync(candidate)) {
468
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
469
+ if (typeof parsed === "object" && parsed !== null && "name" in parsed && parsed.name === PACKAGE_NAME && "version" in parsed && typeof parsed.version === "string") {
470
+ found = parsed.version;
471
+ break;
472
+ }
473
+ }
474
+ const parent = dirname2(directory);
475
+ if (parent === directory)
476
+ break;
477
+ directory = parent;
478
+ }
479
+ } catch {}
480
+ if (useCache)
481
+ cachedVersion = found;
482
+ return found;
483
+ }
484
+ var PACKAGE_NAME = "@retaw106/mmc-oc", cachedVersion;
485
+ var init_plugin_package_version = () => {};
486
+
487
+ // src/shared/harness-provider-map.ts
488
+ var init_harness_provider_map = () => {};
489
+
490
+ // src/shared/prompt-surface.ts
491
+ var init_prompt_surface = __esm(() => {
492
+ init_harness_provider_map();
493
+ });
494
+
495
+ // src/shared/model-cache-ttl.ts
496
+ var init_model_cache_ttl = __esm(() => {
497
+ init_harness_provider_map();
498
+ init_prompt_surface();
499
+ });
500
+
501
+ // src/shared/cache-ttl-display.ts
502
+ var init_cache_ttl_display = __esm(() => {
503
+ init_model_cache_ttl();
504
+ });
505
+
506
+ // src/shared/config-diagnostics.ts
507
+ var claimedFailures;
508
+ var init_config_diagnostics = __esm(() => {
509
+ claimedFailures = new Set;
510
+ });
511
+ // src/shared/user-facing-codes.ts
512
+ function renderUserFacingFailure(key, style = "markdown") {
513
+ const failure = USER_FACING_FAILURES[key];
514
+ const action = style === "plain" ? failure.action.replaceAll("`", "") : failure.action;
515
+ return `${failure.sentence} ${action} (${failure.code})`;
516
+ }
517
+ var USER_FACING_FAILURES;
518
+ var init_user_facing_codes = __esm(() => {
519
+ USER_FACING_FAILURES = {
520
+ historian_unavailable: {
521
+ code: "MC-H01",
522
+ sentence: "History compression could not finish this turn.",
523
+ action: "It will retry automatically."
524
+ },
525
+ hidden_cleanup_unbound: {
526
+ code: "MC-H02",
527
+ sentence: "Some finished background sessions could not be removed from this host.",
528
+ action: "They are removed automatically the next time Magic Context runs inside a registered OpenCode service."
529
+ },
530
+ historian_saved_history_misaligned: {
531
+ code: "MC-H03",
532
+ sentence: "History compression is paused because this session's saved summaries no longer line up with its messages.",
533
+ action: "Run /mmc-recomp to rebuild them."
534
+ },
535
+ history_boundary_unresolved: {
536
+ code: "MC-H04",
537
+ sentence: "This request was not sent: Magic Context could not safely place the history summary boundary in the loaded conversation, and the untrimmed request is larger than the model's context window.",
538
+ action: "Reopen this session in its current project directory, then run /mmc-recomp to rebuild the history summary. A model with a larger context window can also help."
539
+ },
540
+ history_over_window_unmanaged: {
541
+ code: "MC-H06",
542
+ sentence: "This request was not sent: this session's history is larger than the model's context window, and Magic Context does not have a summary of it yet to send in its place.",
543
+ action: "Send your message again once history compression has caught up, or switch to a model with a larger context window."
544
+ },
545
+ frozen_history_over_window: {
546
+ code: "MC-H07",
547
+ sentence: "This request was not sent: the conversation is larger than the context window the provider reported, and the compressed history Magic Context has ready does not fit either.",
548
+ action: "Run /mmc-flush to compress it now, then send your message again."
549
+ },
550
+ historian_window_too_small: {
551
+ code: "MC-H05",
552
+ sentence: "History compression is paused because the history model's context window is too small for its instructions.",
553
+ action: "Set historian.model in magic-context.jsonc to a model with a larger context window."
554
+ },
555
+ recomp_unavailable: {
556
+ code: "MC-R01",
557
+ sentence: "History compression could not be rebuilt.",
558
+ action: "Run /mmc-recomp again."
559
+ },
560
+ status_unavailable: {
561
+ code: "MC-S01",
562
+ sentence: "Magic Context status is temporarily unavailable.",
563
+ action: "Retry /mmc-status in a moment."
564
+ },
565
+ transform_update_failed: {
566
+ code: "MC-S02",
567
+ sentence: "The last context update did not finish.",
568
+ action: "Send another message to retry."
569
+ },
570
+ transform_pass_degraded: {
571
+ code: "MC-S06",
572
+ sentence: "This request was not sent: Magic Context could not finish preparing it, and without that preparation it could be far larger than the previous request.",
573
+ action: "Send your message again."
574
+ },
575
+ configuration_warning: {
576
+ code: "MC-S03",
577
+ sentence: "Some configuration settings could not be applied.",
578
+ action: "Fix the configuration warning shown in /mmc-status, then restart."
579
+ },
580
+ status_log_unavailable: {
581
+ code: "MC-S04",
582
+ sentence: "Some diagnostic details could not be saved.",
583
+ action: "Retry /mmc-status in a moment."
584
+ },
585
+ memory_writes_paused: {
586
+ code: "MC-C01",
587
+ sentence: "Memory writes are paused while the engine syncs.",
588
+ action: "Retry in a moment."
589
+ },
590
+ memory_access_unavailable: {
591
+ code: "MC-C02",
592
+ sentence: "Memory access is temporarily unavailable.",
593
+ action: "Retry in a moment."
594
+ },
595
+ note_changes_paused: {
596
+ code: "MC-C03",
597
+ sentence: "Note changes are paused while the engine syncs.",
598
+ action: "Retry in a moment."
599
+ },
600
+ note_access_unavailable: {
601
+ code: "MC-C04",
602
+ sentence: "Notes are temporarily unavailable.",
603
+ action: "Retry in a moment."
604
+ },
605
+ context_cleanup_paused: {
606
+ code: "MC-C05",
607
+ sentence: "Context cleanup is paused while the engine syncs.",
608
+ action: "Retry in a moment."
609
+ },
610
+ partial_history_unavailable: {
611
+ code: "MC-C06",
612
+ sentence: "Partial history compression is not available in the current mode.",
613
+ action: "Run /mmc-recomp without a range."
614
+ },
615
+ history_compression_paused: {
616
+ code: "MC-C09",
617
+ sentence: "History compression is paused while the engine syncs.",
618
+ action: "Retry in a moment."
619
+ },
620
+ history_compression_needs_message: {
621
+ code: "MC-C12",
622
+ sentence: "History compression has not seen this session since Magic Context reconnected.",
623
+ action: "Send a message in this session first, then run /mmc-wrapup again."
624
+ },
625
+ context_service_unavailable: {
626
+ code: "MC-C10",
627
+ sentence: "Magic Context is temporarily unavailable.",
628
+ action: "Retry in a moment."
629
+ },
630
+ compaction_marker_missing: {
631
+ code: "MC-C11",
632
+ sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
633
+ action: "It is retried on every message; if this persists, run `/mmc-flush`."
634
+ },
635
+ memory_mirror_stalled: {
636
+ code: "MC-M01",
637
+ sentence: "Memory synchronization stopped before the host mirror caught up.",
638
+ action: "Send another message to resume it, or run `ck doctor drain-authority`."
639
+ },
640
+ memory_authority_mismatch: {
641
+ code: "MC-M02",
642
+ sentence: "Memory authority is inconsistent between the host and module.",
643
+ action: "Run `ck doctor drain-authority` before changing Rust mode."
644
+ }
645
+ };
646
+ });
647
+
648
+ // src/shared/status-summary.ts
649
+ function statusWarningsFromDetail(detail) {
650
+ const warnings = [];
651
+ if (detail.lastTransformError)
652
+ warnings.push("transform_update_failed");
653
+ if ((detail.historianFailureCount ?? 0) > 0)
654
+ warnings.push("historian_unavailable");
655
+ if ((detail.configParseFailures?.length ?? 0) > 0)
656
+ warnings.push("configuration_warning");
657
+ if ((detail.loggerDiagnostics?.swallowedWriteCount ?? 0) > 0) {
658
+ warnings.push("status_log_unavailable");
659
+ }
660
+ if (detail.memoryMirror?.stalled)
661
+ warnings.push("memory_mirror_stalled");
662
+ if (detail.compactionMarker?.code)
663
+ warnings.push("compaction_marker_missing");
664
+ if (detail.memoryAuthorityMismatch)
665
+ warnings.push("memory_authority_mismatch");
666
+ warnings.push(...detail.hostLimitations ?? []);
667
+ return [...new Set(warnings)];
668
+ }
669
+ var init_status_summary = __esm(() => {
670
+ init_cache_ttl_display();
671
+ init_user_facing_codes();
672
+ });
673
+
674
+ // src/shared/status-view-check.ts
675
+ function isRecord(value) {
676
+ return typeof value === "object" && value !== null && !Array.isArray(value);
677
+ }
678
+ function isNumber(value) {
679
+ return typeof value === "number" && !Number.isNaN(value);
680
+ }
681
+ function isFiniteNumber(value) {
682
+ return typeof value === "number" && Number.isFinite(value);
683
+ }
684
+ function isString(value) {
685
+ return typeof value === "string";
686
+ }
687
+ function isBoolean(value) {
688
+ return typeof value === "boolean";
689
+ }
690
+ function isUserFacingFailureKey(value) {
691
+ return typeof value === "string" && Object.hasOwn(USER_FACING_FAILURES, value);
692
+ }
693
+
694
+ class OptionalFields {
695
+ raw;
696
+ ignored = [];
697
+ constructor(raw) {
698
+ this.raw = raw;
699
+ }
700
+ read(field, accept) {
701
+ const value = this.raw[field];
702
+ if (value === undefined)
703
+ return;
704
+ if (accept(value))
705
+ return value;
706
+ this.ignored.push(field);
707
+ return;
708
+ }
709
+ readArray(field, accept) {
710
+ const value = this.raw[field];
711
+ if (value === undefined)
712
+ return;
713
+ if (!Array.isArray(value)) {
714
+ this.ignored.push(field);
715
+ return;
716
+ }
717
+ const kept = value.filter(accept);
718
+ if (kept.length !== value.length)
719
+ this.ignored.push(field);
720
+ return kept;
721
+ }
722
+ }
723
+ function isWindowGeometry(value) {
724
+ if (!isRecord(value) || !isNumber(value.usableSoft) || !isRecord(value.derivation))
725
+ return false;
726
+ const derivation = value.derivation;
727
+ return isNumber(derivation.window) && isNumber(derivation.reserve) && isString(derivation.reserveSource);
728
+ }
729
+ function isTailHygiene(value) {
730
+ return isRecord(value) && isNumber(value.u) && isNumber(value.t) && isNumber(value.severity) && isBoolean(value.evaluable);
731
+ }
732
+ function isCompactionMarker(value) {
733
+ return isRecord(value) && (value.code === null || value.code === "MC-C11") && isNumber(value.attempts) && (value.lastError === null || isString(value.lastError));
734
+ }
735
+ function isConfigParseFailure(value) {
736
+ return isRecord(value);
737
+ }
738
+ function isConfigReloadFailure(value) {
739
+ return isRecord(value) && isString(value.path) && isString(value.message);
740
+ }
741
+ function isUnconfirmedMigrationHolders(value) {
742
+ return isRecord(value) && Array.isArray(value.pids) && value.pids.every(isNumber) && isNumber(value.fromVersion) && isNumber(value.toVersion);
743
+ }
744
+ function isNullableNumber(value) {
745
+ return value === null || isNumber(value);
746
+ }
747
+ function isNullableString(value) {
748
+ return value === null || isString(value);
749
+ }
750
+ function isCacheTtlSource(value) {
751
+ return isString(value);
752
+ }
753
+ function checkStatusViewSource(candidate) {
754
+ if (!isRecord(candidate))
755
+ return { ok: false, fields: ["(the payload is not an object)"] };
756
+ const missing = [];
757
+ const numbers = {};
758
+ for (const field of REQUIRED_NUMBER_FIELDS) {
759
+ const value = candidate[field];
760
+ if (isNumber(value))
761
+ numbers[field] = value;
762
+ else
763
+ missing.push(field);
764
+ }
765
+ for (const field of REQUIRED_BOOLEAN_FIELDS) {
766
+ if (!isBoolean(candidate[field]))
767
+ missing.push(field);
768
+ }
769
+ const cacheTtl = isString(candidate.cacheTtl) ? candidate.cacheTtl : undefined;
770
+ if (cacheTtl === undefined)
771
+ missing.push("cacheTtl");
772
+ if (missing.length > 0 || cacheTtl === undefined)
773
+ return { ok: false, fields: missing };
774
+ const optional = new OptionalFields(candidate);
775
+ const source = {
776
+ ...numbers,
777
+ isSubagent: candidate.isSubagent === true,
778
+ cacheExpired: candidate.cacheExpired === true,
779
+ cacheTtl,
780
+ compressionBudget: optional.read("compressionBudget", isNullableNumber) ?? null,
781
+ compressionUsage: optional.read("compressionUsage", isNullableString) ?? null,
782
+ executeThresholdClamped: optional.read("executeThresholdClamped", isBoolean),
783
+ windowGeometry: optional.read("windowGeometry", isWindowGeometry),
784
+ tailHygiene: optional.read("tailHygiene", isTailHygiene),
785
+ tagCountsAuthoritative: optional.read("tagCountsAuthoritative", isBoolean),
786
+ compactionMarker: optional.read("compactionMarker", isCompactionMarker),
787
+ cacheTtlSource: optional.read("cacheTtlSource", isCacheTtlSource),
788
+ cacheTtlModelKey: optional.read("cacheTtlModelKey", isString),
789
+ cacheNeverExpires: optional.read("cacheNeverExpires", isBoolean),
790
+ sessionNoteCount: optional.read("sessionNoteCount", isNumber),
791
+ archivedCompartmentCount: optional.read("archivedCompartmentCount", isNumber),
792
+ configParseFailures: optional.readArray("configParseFailures", isConfigParseFailure),
793
+ configGeneration: optional.read("configGeneration", isNumber),
794
+ configAdoptedAt: optional.read("configAdoptedAt", isNumber),
795
+ configReloadFailure: optional.read("configReloadFailure", isConfigReloadFailure),
796
+ compaction_enabled: optional.read("compaction_enabled", isBoolean),
797
+ compactionEnabled: optional.read("compactionEnabled", isBoolean),
798
+ warnings: optional.readArray("warnings", isUserFacingFailureKey),
799
+ hiddenVariantWarnings: optional.readArray("hiddenVariantWarnings", isString),
800
+ unconfirmedMigrationHolders: optional.read("unconfirmedMigrationHolders", isUnconfirmedMigrationHolders)
801
+ };
802
+ return { ok: true, source, ignoredFields: optional.ignored };
803
+ }
804
+ function statusRpcFailure(message) {
805
+ return { state: "unavailable", reason: { kind: "rpc_error", message }, versions: null };
806
+ }
807
+ function isRecompProgress(value) {
808
+ return isRecord(value) && isString(value.phase) && (value.kind === undefined || isString(value.kind)) && isNumber(value.processedMessages) && isNumber(value.totalMessages) && isNumber(value.passCount) && isNumber(value.compartmentsCreated) && (value.message === undefined || isString(value.message)) && (value.note === undefined || isString(value.note));
809
+ }
810
+ function warningInputFrom(raw, optional) {
811
+ return {
812
+ lastTransformError: optional.read("lastTransformError", isNullableString) ?? null,
813
+ historianFailureCount: optional.read("historianFailureCount", isNumber),
814
+ configParseFailures: optional.readArray("configParseFailures", isConfigParseFailure),
815
+ loggerDiagnostics: isRecord(raw.loggerDiagnostics) && isNumber(raw.loggerDiagnostics.swallowedWriteCount) ? { swallowedWriteCount: raw.loggerDiagnostics.swallowedWriteCount } : undefined,
816
+ memoryMirror: isRecord(raw.memoryMirror) ? { stalled: raw.memoryMirror.stalled === true } : undefined,
817
+ compactionMarker: optional.read("compactionMarker", isCompactionMarker),
818
+ memoryAuthorityMismatch: raw.memoryAuthorityMismatch === true,
819
+ hostLimitations: optional.readArray("hostLimitations", isUserFacingFailureKey)
820
+ };
821
+ }
822
+ function dialogContextLimit(raw) {
823
+ const { contextLimit, usagePercentage, inputTokens } = raw;
824
+ if (!isNumber(contextLimit))
825
+ return contextLimit;
826
+ if (contextLimit > 0)
827
+ return contextLimit;
828
+ if (isFiniteNumber(usagePercentage) && usagePercentage > 0 && isFiniteNumber(inputTokens)) {
829
+ return Math.round(inputTokens / (usagePercentage / 100));
830
+ }
831
+ return 0;
832
+ }
833
+ function checkStatusDetailPayload(raw, uiVersion) {
834
+ if (!isRecord(raw)) {
835
+ return {
836
+ state: "unavailable",
837
+ reason: { kind: "malformed", fields: ["(the payload is not an object)"] },
838
+ versions: null
839
+ };
840
+ }
841
+ const versions = {
842
+ server: isString(raw.pluginVersion) ? raw.pluginVersion : null,
843
+ ui: uiVersion
844
+ };
845
+ if (isString(raw.error)) {
846
+ return {
847
+ state: "unavailable",
848
+ reason: { kind: "rpc_error", message: raw.error },
849
+ versions: versions.server === null ? null : versions
850
+ };
851
+ }
852
+ if (raw.disabled === true) {
853
+ return {
854
+ state: "unavailable",
855
+ reason: {
856
+ kind: "not_tracked",
857
+ cause: raw.paused === true ? "identity_paused" : "home_directory"
858
+ },
859
+ versions
860
+ };
861
+ }
862
+ const extrasFields = new OptionalFields(raw);
863
+ const warnings = statusWarningsFromDetail(warningInputFrom(raw, extrasFields));
864
+ const checked = checkStatusViewSource({
865
+ ...raw,
866
+ contextLimit: dialogContextLimit(raw),
867
+ warnings
868
+ });
869
+ if (!checked.ok) {
870
+ return {
871
+ state: "unavailable",
872
+ reason: { kind: "malformed", fields: checked.fields },
873
+ versions
874
+ };
875
+ }
876
+ const extras = {
877
+ recompProgress: extrasFields.read("recompProgress", (value) => value === null || isRecompProgress(value)) ?? null,
878
+ hostBackendsModuleSide: extrasFields.read("hostBackendsModuleSide", isBoolean) ?? false,
879
+ historianRunning: extrasFields.read("historianRunning", isBoolean) ?? false,
880
+ lastTransformError: extrasFields.read("lastTransformError", isNullableString) ?? null
881
+ };
882
+ return {
883
+ state: "ready",
884
+ source: checked.source,
885
+ ignoredFields: [...new Set([...checked.ignoredFields, ...extrasFields.ignored])],
886
+ versions,
887
+ extras
888
+ };
889
+ }
890
+ function statusVersionNotice(versions) {
891
+ if (!versions)
892
+ return null;
893
+ if (versions.server === versions.ui)
894
+ return null;
895
+ if (versions.server === null) {
896
+ return `An older Magic Context server (one too old to report its version) is still running, while this UI is ${versions.ui}: quit all OpenCode processes, then start OpenCode again`;
897
+ }
898
+ if (compareVersions(versions.server, versions.ui) < 0) {
899
+ return `An older Magic Context (${versions.server}) server is still running, while this UI is ${versions.ui}: quit all OpenCode processes, then start OpenCode again`;
900
+ }
901
+ return `Magic Context server is ${versions.server}, this UI is ${versions.ui}: restart OpenCode`;
902
+ }
903
+ function compareVersions(left, right) {
904
+ const parts = (version) => version.split(/[-+]/, 1)[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
905
+ const a = parts(left);
906
+ const b = parts(right);
907
+ for (let index = 0;index < Math.max(a.length, b.length); index++) {
908
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
909
+ if (difference !== 0)
910
+ return difference;
911
+ }
912
+ return 0;
913
+ }
914
+ var REQUIRED_NUMBER_FIELDS, REQUIRED_BOOLEAN_FIELDS;
915
+ var init_status_view_check = __esm(() => {
916
+ init_status_summary();
917
+ init_user_facing_codes();
918
+ REQUIRED_NUMBER_FIELDS = [
919
+ "usagePercentage",
920
+ "inputTokens",
921
+ "contextLimit",
922
+ "executeThreshold",
923
+ "systemPromptTokens",
924
+ "compartmentTokens",
925
+ "compartmentCount",
926
+ "factTokens",
927
+ "memoryTokens",
928
+ "memoryBlockCount",
929
+ "conversationTokens",
930
+ "toolCallTokens",
931
+ "toolDefinitionTokens",
932
+ "activeTags",
933
+ "droppedTags",
934
+ "totalTags",
935
+ "activeBytes",
936
+ "lastNudgeTokens",
937
+ "pendingOpsCount",
938
+ "protectedTagCount",
939
+ "lastResponseTime",
940
+ "cacheRemainingMs",
941
+ "historyBlockTokens",
942
+ "memoryCount"
943
+ ];
944
+ REQUIRED_BOOLEAN_FIELDS = [
945
+ "isSubagent",
946
+ "cacheExpired"
947
+ ];
948
+ });
949
+ // src/shared/window-geometry.ts
950
+ var geometryClampLogSeen;
951
+ var init_window_geometry = __esm(() => {
952
+ init_logger();
953
+ geometryClampLogSeen = new Set;
954
+ });
955
+
956
+ // src/shared/status-view.ts
957
+ function unavailableLabel(reason) {
958
+ if (reason.kind === "rpc_error")
959
+ return "server did not answer";
960
+ if (reason.kind === "not_tracked") {
961
+ return reason.cause === "home_directory" ? "home directory" : "memory paused";
962
+ }
963
+ if (reason.kind === "malformed")
964
+ return "incomplete status data";
965
+ return "view error";
966
+ }
967
+ function unavailableExplanation(reason, versionNotice) {
968
+ const persists = "If this persists after a restart, report it with magic-context.log attached.";
969
+ if (reason.kind === "rpc_error") {
970
+ return [
971
+ `The Magic Context server did not return status: ${reason.message}`,
972
+ renderUserFacingFailure("status_unavailable", "plain")
973
+ ];
974
+ }
975
+ if (reason.kind === "not_tracked" && reason.cause === "home_directory") {
976
+ return [
977
+ "This is your home directory, so Magic Context keeps no project history or memory here.",
978
+ "Set allow_home_project in magic-context.jsonc to opt in."
979
+ ];
980
+ }
981
+ if (reason.kind === "not_tracked") {
982
+ return [
983
+ "Magic Context could not work out a project identity for this directory, so its memory features are paused.",
984
+ 'magic-context.log names the cause (search for "memory features paused"), for example a git command that failed.'
985
+ ];
986
+ }
987
+ if (reason.kind === "malformed") {
988
+ return [
989
+ `The status reply is missing, or has unexpected values for: ${reason.fields.join(", ")}.`,
990
+ versionNotice ? "The version difference above is the likely cause." : persists
991
+ ];
992
+ }
993
+ return [`The status view could not be drawn: ${reason.message}`, persists];
994
+ }
995
+ function buildUnavailableStatusView(reason, versions, options) {
996
+ const label = unavailableLabel(reason);
997
+ const versionNotice = statusVersionNotice(versions);
998
+ const rows = [{ label: "Reason", value: label, tone: "warning" }];
999
+ if (versions) {
1000
+ rows.push({ label: "Server", value: versions.server ?? "not reported", tone: "muted" }, { label: "UI", value: versions.ui, tone: "muted" });
1001
+ }
1002
+ return {
1003
+ title: "⚡ Magic Context Status",
1004
+ version: `v${options.version}`,
1005
+ headline: {
1006
+ left: { text: "Status unavailable", tone: "error" },
1007
+ right: { text: label, tone: "muted" }
1008
+ },
1009
+ windowLine: null,
1010
+ bar: [],
1011
+ breakdown: [],
1012
+ hygiene: null,
1013
+ sections: [{ title: "Status unavailable", labelWidth: 8, rows }],
1014
+ warnings: [
1015
+ ...versionNotice ? [{ text: versionNotice, tone: "error" }] : [],
1016
+ ...unavailableExplanation(reason, versionNotice).map((text) => ({
1017
+ text,
1018
+ tone: "warning"
1019
+ }))
1020
+ ],
1021
+ footer: "Esc to close"
1022
+ };
1023
+ }
1024
+ var init_status_view = __esm(() => {
1025
+ init_cache_ttl_display();
1026
+ init_config_diagnostics();
1027
+ init_status_view_check();
1028
+ init_user_facing_codes();
1029
+ init_window_geometry();
1030
+ });
1031
+
1032
+ // src/shared/pi-executable.ts
1033
+ var PI_IMAGE_NAMES;
1034
+ var init_pi_executable = __esm(() => {
1035
+ PI_IMAGE_NAMES = new Set(["pi", "pi.cmd", "omp", "oh-my-pi"]);
1036
+ });
1037
+
1038
+ // src/shared/project-directory-key.ts
1039
+ import { realpathSync } from "node:fs";
1040
+ import path3 from "node:path";
1041
+ function projectDirectoryKey(directory) {
1042
+ const slashed = directory.replaceAll("\\", "/").replace(/^\/\/\?\/UNC\//i, "//").replace(/^\/\/\?\//, "");
1043
+ if (/^[a-z]:\//i.test(slashed) || slashed.startsWith("//")) {
1044
+ return path3.win32.normalize(slashed).replaceAll("\\", "/").replace(/\/$/, "").toLowerCase();
1045
+ }
1046
+ return path3.resolve(directory);
1047
+ }
1048
+ function canonicalProjectDirectory(directory) {
1049
+ let resolved = directory;
1050
+ try {
1051
+ resolved = realpathForCanonicalDirectory(directory);
1052
+ } catch {}
1053
+ return projectDirectoryKey(resolved);
1054
+ }
1055
+ var realpathForCanonicalDirectory = (directory) => realpathSync.native(directory);
1056
+ var init_project_directory_key = () => {};
1057
+
1058
+ // src/shared/rpc-utils.ts
1059
+ import { execFile, execFileSync } from "node:child_process";
1060
+ import { createHash } from "node:crypto";
1061
+ import { join as join3 } from "node:path";
1062
+ function projectHash(directory) {
1063
+ return createHash("sha256").update(canonicalProjectDirectory(directory)).digest("hex").slice(0, 16);
1064
+ }
1065
+ function legacyProjectHash(directory) {
1066
+ const normalized = directory.replace(/\/+$/, "");
1067
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
1068
+ }
1069
+ function rpcPortDir(storageDir, directory) {
1070
+ return join3(storageDir, "rpc", projectHash(directory));
1071
+ }
1072
+ function legacyRpcPortFilePath(storageDir, directory) {
1073
+ return join3(storageDir, "rpc", legacyProjectHash(directory), "port");
1074
+ }
1075
+ function isPidAlive(pid) {
1076
+ if (!Number.isInteger(pid) || pid <= 0)
1077
+ return "dead";
1078
+ if (rpcIdentityPlatform === "win32")
1079
+ return readWindowsProcess(pid).state;
1080
+ try {
1081
+ rpcIdentityProcessKill(pid, 0);
1082
+ return "alive";
1083
+ } catch (error) {
1084
+ return error.code === "ESRCH" ? "dead" : "inconclusive";
1085
+ }
1086
+ }
1087
+ function parseCsvLine(line) {
1088
+ const fields = [];
1089
+ let field = "";
1090
+ let quoted = false;
1091
+ for (let index = 0;index < line.length; index += 1) {
1092
+ const character = line[index];
1093
+ if (character === '"') {
1094
+ if (quoted && line[index + 1] === '"') {
1095
+ field += '"';
1096
+ index += 1;
1097
+ } else {
1098
+ quoted = !quoted;
1099
+ }
1100
+ } else if (character === "," && !quoted) {
1101
+ fields.push(field);
1102
+ field = "";
1103
+ } else {
1104
+ field += character;
1105
+ }
1106
+ }
1107
+ if (quoted)
1108
+ return null;
1109
+ fields.push(field);
1110
+ return fields;
1111
+ }
1112
+ function parseTasklistOutput(output) {
1113
+ const entries = [];
1114
+ for (const rawLine of output.split(/\r?\n/)) {
1115
+ const line = rawLine.trim();
1116
+ if (!line)
1117
+ continue;
1118
+ const fields = parseCsvLine(line);
1119
+ if (!fields)
1120
+ continue;
1121
+ if (fields[1]?.trim().toLowerCase() === "pid") {
1122
+ continue;
1123
+ }
1124
+ const pid = Number(fields[1]);
1125
+ if (!Number.isInteger(pid) || pid <= 0 || !fields[0])
1126
+ continue;
1127
+ entries.push({ pid, command: fields[0] });
1128
+ }
1129
+ return entries;
1130
+ }
1131
+ function readWindowsProcess(pid) {
1132
+ try {
1133
+ const output = rpcIdentityExecFileSync("tasklist", ["/FO", "CSV", "/NH", "/FI", `PID eq ${pid}`], {
1134
+ encoding: "utf8",
1135
+ timeout: PS_PROBE_TIMEOUT_MS,
1136
+ stdio: ["ignore", "pipe", "pipe"],
1137
+ windowsHide: true
1138
+ });
1139
+ const entries = parseTasklistOutput(String(output));
1140
+ if (entries === null)
1141
+ return { state: "inconclusive" };
1142
+ const process2 = entries.find((entry) => entry.pid === pid);
1143
+ return process2 ? { state: "alive", command: process2.command } : { state: "dead" };
1144
+ } catch {
1145
+ return { state: "inconclusive" };
1146
+ }
1147
+ }
1148
+ function parseRpcPortFile(content, fallbackPid = 0) {
1149
+ const trimmed = content.trim();
1150
+ if (!trimmed)
1151
+ return null;
1152
+ if (trimmed.startsWith("{")) {
1153
+ try {
1154
+ const parsed = JSON.parse(trimmed);
1155
+ const port = Number(parsed.port);
1156
+ const pid = Number(parsed.pid);
1157
+ const startedAt = Number(parsed.started_at);
1158
+ if (!isValidPort(port) || !Number.isInteger(pid) || pid <= 0)
1159
+ return null;
1160
+ return {
1161
+ port,
1162
+ pid,
1163
+ started_at: Number.isFinite(startedAt) ? startedAt : 0,
1164
+ kind: typeof parsed.kind === "string" ? parsed.kind : undefined,
1165
+ harness: typeof parsed.harness === "string" ? parsed.harness : undefined,
1166
+ token: typeof parsed.token === "string" ? parsed.token : undefined,
1167
+ instance_id: typeof parsed.instance_id === "string" ? parsed.instance_id : undefined
1168
+ };
1169
+ } catch {
1170
+ return null;
1171
+ }
1172
+ }
1173
+ const port = Number.parseInt(trimmed, 10);
1174
+ if (!isValidPort(port))
1175
+ return null;
1176
+ return { port, pid: fallbackPid, started_at: 0 };
1177
+ }
1178
+ function isValidPort(port) {
1179
+ return Number.isInteger(port) && port > 0 && port <= 65535;
1180
+ }
1181
+ var ownRpcServerInstanceIds, PS_PROBE_TIMEOUT_MS = 1000, PROCESS_LIST_MAX_BUFFER_BYTES, rpcIdentityExecFileSync, rpcIdentityProcessKill, rpcIdentityPlatform;
1182
+ var init_rpc_utils = __esm(() => {
1183
+ init_logger();
1184
+ init_pi_executable();
1185
+ init_project_directory_key();
1186
+ ownRpcServerInstanceIds = new Set;
1187
+ PROCESS_LIST_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
1188
+ rpcIdentityExecFileSync = execFileSync;
1189
+ rpcIdentityProcessKill = process.kill;
1190
+ rpcIdentityPlatform = process.platform;
1191
+ });
1192
+
1193
+ // src/shared/rpc-client.ts
1194
+ import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
1195
+ import { join as join4 } from "node:path";
1196
+
1197
+ class MagicContextRpcClient {
1198
+ port = null;
1199
+ token = null;
1200
+ instanceId = null;
1201
+ portDir;
1202
+ legacyPortFilePath;
1203
+ healthChecked = false;
1204
+ constructor(storageDir, directory) {
1205
+ this.portDir = rpcPortDir(storageDir, directory);
1206
+ this.legacyPortFilePath = legacyRpcPortFilePath(storageDir, directory);
1207
+ }
1208
+ async call(method, params = {}) {
1209
+ let lastError = null;
1210
+ for (let attempt = 0;attempt < MAX_RERESOLVE_ATTEMPTS; attempt++) {
1211
+ const port = await this.resolvePort();
1212
+ if (!port) {
1213
+ lastError = new Error("Magic Context RPC server not available");
1214
+ this.reset();
1215
+ continue;
1216
+ }
1217
+ try {
1218
+ const response = await this.fetchWithTimeout(`http://127.0.0.1:${port}/rpc/${method}`, {
1219
+ method: "POST",
1220
+ headers: {
1221
+ "Content-Type": "application/json",
1222
+ ...this.token ? { Authorization: `Bearer ${this.token}` } : {}
1223
+ },
1224
+ body: JSON.stringify(params)
1225
+ });
1226
+ if (!response.ok) {
1227
+ const text = await response.text();
1228
+ const error = new Error(`RPC ${method} failed (${response.status}): ${text}`);
1229
+ if (response.status === 401 || response.status >= 500) {
1230
+ lastError = error;
1231
+ this.reset();
1232
+ continue;
1233
+ }
1234
+ error[NON_RETRYABLE_RPC_ERROR] = true;
1235
+ throw error;
1236
+ }
1237
+ return await response.json();
1238
+ } catch (err) {
1239
+ if (isNonRetryableRpcError(err)) {
1240
+ throw err;
1241
+ }
1242
+ lastError = err;
1243
+ this.reset();
1244
+ }
1245
+ }
1246
+ if (lastError instanceof Error) {
1247
+ throw lastError;
1248
+ }
1249
+ throw new Error("Magic Context RPC server not available");
1250
+ }
1251
+ async isAvailable() {
1252
+ try {
1253
+ const port = await this.resolvePort();
1254
+ return port !== null;
1255
+ } catch {
1256
+ return false;
1257
+ }
1258
+ }
1259
+ async resolveEndpoint() {
1260
+ try {
1261
+ const port = await this.resolvePort(1);
1262
+ if (port === null)
1263
+ return null;
1264
+ return { port, token: this.token, instanceId: this.instanceId };
1265
+ } catch {
1266
+ return null;
1267
+ }
1268
+ }
1269
+ async resolvePort(maxAttempts = MAX_RETRIES) {
1270
+ if (this.port && this.healthChecked) {
1271
+ return this.port;
1272
+ }
1273
+ for (let attempt = 0;attempt < maxAttempts; attempt++) {
1274
+ for (const record of this.readPortFiles()) {
1275
+ if (!await this.healthCheck(record))
1276
+ continue;
1277
+ this.port = record.port;
1278
+ this.token = record.token ?? null;
1279
+ this.instanceId = record.instance_id ?? null;
1280
+ this.healthChecked = true;
1281
+ return record.port;
1282
+ }
1283
+ this.reset();
1284
+ if (attempt < maxAttempts - 1) {
1285
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
1286
+ }
1287
+ }
1288
+ return null;
1289
+ }
1290
+ readPortFiles() {
1291
+ const records = [];
1292
+ try {
1293
+ for (const entry of readdirSync(this.portDir)) {
1294
+ if (!entry.startsWith("port-") || !entry.endsWith(".json"))
1295
+ continue;
1296
+ const record = parseRpcPortFile(readFileSync2(join4(this.portDir, entry), "utf-8"));
1297
+ if (!record || isPidAlive(record.pid) === "dead")
1298
+ continue;
1299
+ records.push(record);
1300
+ }
1301
+ } catch {}
1302
+ try {
1303
+ const legacy = parseRpcPortFile(readFileSync2(this.legacyPortFilePath, "utf-8"));
1304
+ if (legacy && (!legacy.pid || isPidAlive(legacy.pid) !== "dead"))
1305
+ records.push(legacy);
1306
+ } catch {}
1307
+ records.sort((a, b) => {
1308
+ const aLocal = a.pid === process.pid ? 1 : 0;
1309
+ const bLocal = b.pid === process.pid ? 1 : 0;
1310
+ return bLocal - aLocal || b.started_at - a.started_at;
1311
+ });
1312
+ return records;
1313
+ }
1314
+ async healthCheck(record) {
1315
+ try {
1316
+ const response = await this.fetchWithTimeout(`http://127.0.0.1:${record.port}/health`, {
1317
+ method: "GET"
1318
+ });
1319
+ if (!response.ok)
1320
+ return false;
1321
+ const body = await response.json();
1322
+ if (body.pid !== record.pid)
1323
+ return false;
1324
+ return body.instance_id === undefined || record.instance_id === undefined || body.instance_id === record.instance_id;
1325
+ } catch {
1326
+ return false;
1327
+ }
1328
+ }
1329
+ async fetchWithTimeout(url, options) {
1330
+ const controller = new AbortController;
1331
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
1332
+ try {
1333
+ return await fetch(url, { ...options, signal: controller.signal });
1334
+ } finally {
1335
+ clearTimeout(timeout);
1336
+ }
1337
+ }
1338
+ reset() {
1339
+ this.port = null;
1340
+ this.token = null;
1341
+ this.instanceId = null;
1342
+ this.healthChecked = false;
1343
+ }
1344
+ }
1345
+ function isNonRetryableRpcError(err) {
1346
+ return typeof err === "object" && err !== null && NON_RETRYABLE_RPC_ERROR in err;
1347
+ }
1348
+ var MAX_RETRIES = 10, RETRY_DELAY_MS = 500, REQUEST_TIMEOUT_MS = 5000, MAX_RERESOLVE_ATTEMPTS = 3, NON_RETRYABLE_RPC_ERROR;
1349
+ var init_rpc_client = __esm(() => {
1350
+ init_rpc_utils();
1351
+ NON_RETRYABLE_RPC_ERROR = Symbol("nonRetryableRpcError");
1352
+ });
1353
+
1354
+ // src/v2/tui/context-rpc.ts
1355
+ function initRpcClient(directory) {
1356
+ const storageDir = getMagicContextStorageDir();
1357
+ rpcGeneration += 1;
1358
+ rpcClient = new MagicContextRpcClient(storageDir, directory);
1359
+ rpcClientDirectory = directory;
1360
+ resetSessionDirectoryClients();
1361
+ }
1362
+ function resetSessionDirectoryClients() {
1363
+ for (const client of sessionDirectoryClients.values())
1364
+ client.reset();
1365
+ sessionDirectoryClients.clear();
1366
+ }
1367
+ function clientForDirectory(directory) {
1368
+ if (!rpcClient)
1369
+ return null;
1370
+ if (!directory || directory === rpcClientDirectory)
1371
+ return rpcClient;
1372
+ const existing = sessionDirectoryClients.get(directory);
1373
+ if (existing)
1374
+ return existing;
1375
+ if (sessionDirectoryClients.size >= MAX_SESSION_DIRECTORY_CLIENTS) {
1376
+ const oldest = sessionDirectoryClients.keys().next().value;
1377
+ if (oldest !== undefined) {
1378
+ sessionDirectoryClients.get(oldest)?.reset();
1379
+ sessionDirectoryClients.delete(oldest);
1380
+ }
1381
+ }
1382
+ const client = new MagicContextRpcClient(getMagicContextStorageDir(), directory);
1383
+ sessionDirectoryClients.set(directory, client);
1384
+ return client;
1385
+ }
1386
+ async function resolveNotificationTarget(sessionDirectory) {
1387
+ const startup = rpcClient;
1388
+ if (!startup)
1389
+ return null;
1390
+ const directory = sessionDirectory ?? "";
1391
+ const client = clientForDirectory(directory) ?? startup;
1392
+ const endpoint = await client.resolveEndpoint();
1393
+ if (endpoint) {
1394
+ return {
1395
+ client,
1396
+ directory: client === startup ? rpcClientDirectory ?? "" : directory,
1397
+ endpoint
1398
+ };
1399
+ }
1400
+ if (client === startup)
1401
+ return null;
1402
+ const startupEndpoint = await startup.resolveEndpoint();
1403
+ return startupEndpoint ? { client: startup, directory: rpcClientDirectory ?? "", endpoint: startupEndpoint } : null;
1404
+ }
1405
+ function notificationDirectoryFor(sessionDirectory) {
1406
+ const directory = sessionDirectory ?? "";
1407
+ if (!directory || directory === rpcClientDirectory)
1408
+ return rpcClientDirectory ?? "";
1409
+ return directory;
1410
+ }
1411
+ async function callSessionRpc(directory, method, params) {
1412
+ const client = clientForDirectory(directory);
1413
+ if (!client)
1414
+ throw new Error("RPC client is not initialized");
1415
+ return client.call(method, params);
1416
+ }
1417
+ function getRpcGeneration() {
1418
+ return rpcGeneration;
1419
+ }
1420
+ function closeRpc() {
1421
+ rpcGeneration += 1;
1422
+ rpcClient?.reset();
1423
+ rpcClient = null;
1424
+ rpcClientDirectory = null;
1425
+ resetSessionDirectoryClients();
1426
+ }
1427
+ async function loadStatusDetail(sessionId, directory, modelKey) {
1428
+ if (!rpcClient)
1429
+ return statusRpcFailure("RPC client is not initialized");
1430
+ try {
1431
+ const reply = await callSessionRpc(directory, "status-detail", {
1432
+ sessionId,
1433
+ directory,
1434
+ modelKey
1435
+ });
1436
+ return checkStatusDetailPayload(reply, pluginPackageVersion() ?? "unknown");
1437
+ } catch (error) {
1438
+ return statusRpcFailure(error instanceof Error ? error.message : String(error));
1439
+ }
1440
+ }
1441
+ async function requestRecomp(sessionId, directory) {
1442
+ if (!rpcClient)
1443
+ return false;
1444
+ try {
1445
+ const result = await callSessionRpc(directory ?? "", "recomp", {
1446
+ sessionId
1447
+ });
1448
+ return result.ok ?? false;
1449
+ } catch {
1450
+ return false;
1451
+ }
1452
+ }
1453
+ async function callCommandRpc(method, params) {
1454
+ if (!rpcClient)
1455
+ return { ok: false };
1456
+ try {
1457
+ const result = await rpcClient.call(method, params);
1458
+ return result.ok === true ? result : { ok: false, error: result.error };
1459
+ } catch (error) {
1460
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
1461
+ }
1462
+ }
1463
+ async function requestFlush(sessionId) {
1464
+ return callCommandRpc("flush", { sessionId });
1465
+ }
1466
+ async function requestWrapup(sessionId, messagesToKeep) {
1467
+ return callCommandRpc("wrapup", { sessionId, messagesToKeep });
1468
+ }
1469
+ async function requestMaintenance(sessionId) {
1470
+ return callCommandRpc("maintenance", { sessionId, subcommand: "run" });
1471
+ }
1472
+ var rpcClient = null, rpcClientDirectory = null, rpcGeneration = 0, sessionDirectoryClients, MAX_SESSION_DIRECTORY_CLIENTS = 16;
1473
+ var init_context_rpc = __esm(() => {
1474
+ init_data_path();
1475
+ init_plugin_package_version();
1476
+ init_rpc_client();
1477
+ init_status_view_check();
1478
+ sessionDirectoryClients = new Map;
1479
+ });
1480
+
1481
+ // src/v2/tui/notification-socket.ts
1482
+ function logResolveFailure() {
1483
+ resolveFailureCount += 1;
1484
+ if (resolveFailureCount === 1 || resolveFailureCount % 10 === 0) {
1485
+ log(`[tui] notification socket: RPC endpoint not found (attempt ${resolveFailureCount}); retrying`);
1486
+ }
1487
+ }
1488
+ function startNotificationSocket(options) {
1489
+ opts = options;
1490
+ closed = false;
1491
+ if (!socket && inFlightAttemptId === null)
1492
+ connect();
1493
+ if (!sessionWatchTimer) {
1494
+ sessionWatchTimer = setInterval(watchSession, SESSION_WATCH_MS);
1495
+ }
1496
+ }
1497
+ function stopNotificationSocket() {
1498
+ closed = true;
1499
+ nextAttemptId += 1;
1500
+ inFlightAttemptId = null;
1501
+ if (reconnectTimer) {
1502
+ clearTimeout(reconnectTimer);
1503
+ reconnectTimer = undefined;
1504
+ }
1505
+ if (sessionWatchTimer) {
1506
+ clearInterval(sessionWatchTimer);
1507
+ sessionWatchTimer = undefined;
1508
+ }
1509
+ try {
1510
+ socket?.close();
1511
+ } catch {}
1512
+ socket = null;
1513
+ opts = null;
1514
+ activeToken = null;
1515
+ helloedSession = null;
1516
+ connectedDirectory = null;
1517
+ lastRetargetAt = 0;
1518
+ reconnectAttempt = 0;
1519
+ activeInstanceId = null;
1520
+ notificationProtocolMode = null;
1521
+ bufferedNotifications.length = 0;
1522
+ lastHandledIdByCursor.clear();
1523
+ handledNotificationIds.clear();
1524
+ handledNotificationIdOrder.length = 0;
1525
+ legacyUnconsumedIdsByCursor.clear();
1526
+ legacyConsumedIdsByCursor.clear();
1527
+ notificationHandlingChain = Promise.resolve();
1528
+ }
1529
+ function scheduleReconnect() {
1530
+ if (closed)
1531
+ return;
1532
+ if (reconnectTimer)
1533
+ return;
1534
+ const delay = Math.min(RECONNECT_BASE_MS * 2 ** reconnectAttempt, RECONNECT_MAX_MS);
1535
+ reconnectAttempt += 1;
1536
+ reconnectTimer = setTimeout(() => {
1537
+ reconnectTimer = undefined;
1538
+ connect();
1539
+ }, delay);
1540
+ }
1541
+ async function connect() {
1542
+ if (closed || socket || inFlightAttemptId !== null)
1543
+ return;
1544
+ const attemptId = ++nextAttemptId;
1545
+ const rpcGeneration = getRpcGeneration();
1546
+ inFlightAttemptId = attemptId;
1547
+ const target = await resolveNotificationTarget(opts?.getSessionDirectory?.() ?? null);
1548
+ if (closed || inFlightAttemptId !== attemptId || getRpcGeneration() !== rpcGeneration) {
1549
+ return;
1550
+ }
1551
+ if (target)
1552
+ resolveFailureCount = 0;
1553
+ inFlightAttemptId = null;
1554
+ if (!target) {
1555
+ logResolveFailure();
1556
+ scheduleReconnect();
1557
+ return;
1558
+ }
1559
+ const { client, endpoint } = target;
1560
+ let ws;
1561
+ try {
1562
+ ws = new WebSocket(`ws://127.0.0.1:${endpoint.port}/ws`, {
1563
+ headers: endpoint.token ? { Authorization: `Bearer ${endpoint.token}` } : {}
1564
+ });
1565
+ } catch (error) {
1566
+ log(`[tui] notification socket: WebSocket constructor failed: ${String(error)}`);
1567
+ client.reset();
1568
+ scheduleReconnect();
1569
+ return;
1570
+ }
1571
+ if (closed || getRpcGeneration() !== rpcGeneration || socket) {
1572
+ ws.close();
1573
+ return;
1574
+ }
1575
+ connectGeneration = rpcGeneration;
1576
+ connectedDirectory = target.directory;
1577
+ activeToken = endpoint.token;
1578
+ notificationProtocolMode = null;
1579
+ bufferedNotifications.length = 0;
1580
+ switchNotificationEpoch(endpoint.instanceId ?? LEGACY_INSTANCE_ID);
1581
+ socket = ws;
1582
+ ws.addEventListener("open", () => {
1583
+ if (socket !== ws || getRpcGeneration() !== connectGeneration) {
1584
+ ws.close();
1585
+ return;
1586
+ }
1587
+ reconnectAttempt = 0;
1588
+ log(`[tui] notification socket connected (ws://127.0.0.1:${endpoint.port}, directory=${target.directory || "startup"})`);
1589
+ sendHello(ws, endpoint.token);
1590
+ });
1591
+ ws.addEventListener("message", (event) => {
1592
+ if (socket !== ws)
1593
+ return;
1594
+ handleSocketMessage(ws, String(event.data), endpoint.token);
1595
+ });
1596
+ const onDown = () => {
1597
+ if (socket !== ws)
1598
+ return;
1599
+ client.reset();
1600
+ socket = null;
1601
+ activeToken = null;
1602
+ helloedSession = null;
1603
+ notificationProtocolMode = null;
1604
+ bufferedNotifications.length = 0;
1605
+ scheduleReconnect();
1606
+ };
1607
+ ws.addEventListener("close", onDown);
1608
+ ws.addEventListener("error", onDown);
1609
+ }
1610
+ function sendHello(ws, token) {
1611
+ const sessionId = opts?.getSessionId() ?? undefined;
1612
+ helloedSession = sessionId ?? null;
1613
+ ws.send(JSON.stringify({
1614
+ type: "hello",
1615
+ protocol: 2,
1616
+ instanceId: activeInstanceId,
1617
+ token: token ?? "",
1618
+ sessionId,
1619
+ lastReceivedId: cursorForKey(cursorKeyForSession(sessionId)),
1620
+ globalLastReceivedId: cursorForKey(cursorKeyForSession(undefined))
1621
+ }));
1622
+ }
1623
+ function handleSocketMessage(ws, raw, token) {
1624
+ let msg;
1625
+ try {
1626
+ msg = JSON.parse(raw);
1627
+ } catch {
1628
+ return;
1629
+ }
1630
+ if (msg.type === "hello-ack") {
1631
+ if (typeof msg.instanceId === "string") {
1632
+ notificationProtocolMode = "v2";
1633
+ if (msg.instanceId !== activeInstanceId) {
1634
+ switchNotificationEpoch(msg.instanceId);
1635
+ sendHello(ws, token);
1636
+ }
1637
+ } else {
1638
+ notificationProtocolMode = "legacy";
1639
+ switchNotificationEpoch(LEGACY_INSTANCE_ID);
1640
+ }
1641
+ flushBufferedNotifications(ws);
1642
+ return;
1643
+ }
1644
+ if (msg.type === "notification" && msg.notification) {
1645
+ if (notificationProtocolMode === null) {
1646
+ bufferedNotifications.push(msg.notification);
1647
+ } else {
1648
+ queueNotification(ws, msg.notification);
1649
+ }
1650
+ return;
1651
+ }
1652
+ if (msg.type === "error") {
1653
+ try {
1654
+ ws.close();
1655
+ } catch {}
1656
+ }
1657
+ }
1658
+ function flushBufferedNotifications(ws) {
1659
+ const pending = bufferedNotifications.splice(0);
1660
+ for (const notification of pending)
1661
+ queueNotification(ws, notification);
1662
+ }
1663
+ function queueNotification(ws, notification) {
1664
+ const deliveryInstanceId = activeInstanceId ?? LEGACY_INSTANCE_ID;
1665
+ const deliveryMode = notificationProtocolMode;
1666
+ if (deliveryMode === null)
1667
+ return;
1668
+ notificationHandlingChain = notificationHandlingChain.then(() => handleNotification(ws, notification, deliveryInstanceId, deliveryMode)).catch(() => {});
1669
+ }
1670
+ async function handleNotification(ws, notification, deliveryInstanceId, deliveryMode) {
1671
+ if (socket !== ws || getRpcGeneration() !== connectGeneration || activeInstanceId !== deliveryInstanceId || notificationProtocolMode !== deliveryMode) {
1672
+ return;
1673
+ }
1674
+ const active = opts?.getSessionId() ?? null;
1675
+ if (notification.sessionId !== undefined && notification.sessionId !== active)
1676
+ return;
1677
+ if (deliveryMode === "legacy")
1678
+ markLegacyUnconsumed(notification);
1679
+ if (handledNotificationIds.has(notificationDedupKey(notification.id, deliveryInstanceId))) {
1680
+ if (deliveryMode === "legacy")
1681
+ markLegacyConsumed(notification);
1682
+ sendAck(ws, notification, deliveryMode);
1683
+ return;
1684
+ }
1685
+ let consumed = false;
1686
+ try {
1687
+ consumed = await Promise.resolve(opts?.onNotification(notification) ?? false);
1688
+ } catch {
1689
+ consumed = false;
1690
+ }
1691
+ if (socket !== ws || getRpcGeneration() !== connectGeneration || activeInstanceId !== deliveryInstanceId || notificationProtocolMode !== deliveryMode) {
1692
+ return;
1693
+ }
1694
+ if (consumed) {
1695
+ rememberHandledId(notification.id, deliveryInstanceId);
1696
+ if (deliveryMode === "legacy") {
1697
+ markLegacyConsumed(notification);
1698
+ } else {
1699
+ advanceCursor(notificationCursorKey(notification), notification.id);
1700
+ }
1701
+ sendAck(ws, notification, deliveryMode);
1702
+ }
1703
+ }
1704
+ function cursorKeyForSession(sessionId) {
1705
+ const scope = sessionId ? `${SESSION_CURSOR_PREFIX}${sessionId}` : GLOBAL_CURSOR_KEY;
1706
+ return `${activeInstanceId ?? LEGACY_INSTANCE_ID}:${scope}`;
1707
+ }
1708
+ function notificationCursorKey(notification) {
1709
+ return cursorKeyForSession(notification.sessionId);
1710
+ }
1711
+ function cursorForKey(key) {
1712
+ return lastHandledIdByCursor.get(key) ?? 0;
1713
+ }
1714
+ function advanceCursor(key, id) {
1715
+ if (id > cursorForKey(key))
1716
+ lastHandledIdByCursor.set(key, id);
1717
+ }
1718
+ function idsForCursor(map, key) {
1719
+ let ids = map.get(key);
1720
+ if (!ids) {
1721
+ ids = new Set;
1722
+ map.set(key, ids);
1723
+ }
1724
+ return ids;
1725
+ }
1726
+ function markLegacyUnconsumed(notification) {
1727
+ const key = notificationCursorKey(notification);
1728
+ if (idsForCursor(legacyConsumedIdsByCursor, key).has(notification.id))
1729
+ return;
1730
+ idsForCursor(legacyUnconsumedIdsByCursor, key).add(notification.id);
1731
+ }
1732
+ function markLegacyConsumed(notification) {
1733
+ const key = notificationCursorKey(notification);
1734
+ idsForCursor(legacyUnconsumedIdsByCursor, key).delete(notification.id);
1735
+ const consumedIds = idsForCursor(legacyConsumedIdsByCursor, key);
1736
+ consumedIds.add(notification.id);
1737
+ let safeCursor = Math.max(cursorForKey(key), ...consumedIds);
1738
+ const unconsumedIds = legacyUnconsumedIdsByCursor.get(key);
1739
+ if (unconsumedIds && unconsumedIds.size > 0) {
1740
+ safeCursor = Math.min(safeCursor, Math.min(...unconsumedIds) - 1);
1741
+ }
1742
+ advanceCursor(key, safeCursor);
1743
+ for (const id of consumedIds) {
1744
+ if (id <= cursorForKey(key))
1745
+ consumedIds.delete(id);
1746
+ }
1747
+ }
1748
+ function notificationDedupKey(id, instanceId = activeInstanceId ?? LEGACY_INSTANCE_ID) {
1749
+ return `${instanceId}:${id}`;
1750
+ }
1751
+ function rememberHandledId(id, instanceId) {
1752
+ const key = notificationDedupKey(id, instanceId);
1753
+ if (handledNotificationIds.has(key))
1754
+ return;
1755
+ handledNotificationIds.add(key);
1756
+ handledNotificationIdOrder.push(key);
1757
+ while (handledNotificationIdOrder.length > MAX_DEDUPED_NOTIFICATION_IDS) {
1758
+ const evicted = handledNotificationIdOrder.shift();
1759
+ if (evicted !== undefined)
1760
+ handledNotificationIds.delete(evicted);
1761
+ }
1762
+ }
1763
+ function switchNotificationEpoch(instanceId) {
1764
+ if (activeInstanceId === instanceId)
1765
+ return;
1766
+ activeInstanceId = instanceId;
1767
+ lastHandledIdByCursor.clear();
1768
+ handledNotificationIds.clear();
1769
+ handledNotificationIdOrder.length = 0;
1770
+ legacyUnconsumedIdsByCursor.clear();
1771
+ legacyConsumedIdsByCursor.clear();
1772
+ }
1773
+ function sendAck(ws, notification, mode) {
1774
+ try {
1775
+ if (mode === "legacy") {
1776
+ const cursor = cursorForKey(notificationCursorKey(notification));
1777
+ ws.send(JSON.stringify({
1778
+ type: "ack",
1779
+ cursor,
1780
+ ...notification.sessionId ? { sessionId: notification.sessionId } : { ackScope: "global" }
1781
+ }));
1782
+ return;
1783
+ }
1784
+ ws.send(JSON.stringify({ type: "ack", ids: [notification.id] }));
1785
+ } catch {}
1786
+ }
1787
+ function watchSession() {
1788
+ if (closed || !socket || socket.readyState !== WebSocket.OPEN)
1789
+ return;
1790
+ const wanted = notificationDirectoryFor(opts?.getSessionDirectory?.() ?? null);
1791
+ if (wanted !== connectedDirectory && Date.now() - lastRetargetAt >= RETARGET_MIN_INTERVAL_MS) {
1792
+ lastRetargetAt = Date.now();
1793
+ const previous = socket;
1794
+ socket = null;
1795
+ activeToken = null;
1796
+ helloedSession = null;
1797
+ connectedDirectory = null;
1798
+ notificationProtocolMode = null;
1799
+ bufferedNotifications.length = 0;
1800
+ try {
1801
+ previous.close();
1802
+ } catch {}
1803
+ connect();
1804
+ return;
1805
+ }
1806
+ const current = opts?.getSessionId() ?? null;
1807
+ if (current === helloedSession)
1808
+ return;
1809
+ sendHello(socket, activeToken);
1810
+ }
1811
+ var resolveFailureCount = 0, RECONNECT_BASE_MS = 500, RECONNECT_MAX_MS = 1e4, SESSION_WATCH_MS = 1000, socket = null, reconnectTimer, sessionWatchTimer, reconnectAttempt = 0, closed = false, helloedSession = null, connectedDirectory = null, lastRetargetAt = 0, RETARGET_MIN_INTERVAL_MS = 1e4, opts = null, activeToken = null, connectGeneration = 0, nextAttemptId = 0, inFlightAttemptId = null, GLOBAL_CURSOR_KEY = "global", SESSION_CURSOR_PREFIX = "session:", MAX_DEDUPED_NOTIFICATION_IDS = 500, LEGACY_INSTANCE_ID = "legacy", activeInstanceId = null, notificationProtocolMode = null, bufferedNotifications, lastHandledIdByCursor, handledNotificationIds, handledNotificationIdOrder, legacyUnconsumedIdsByCursor, legacyConsumedIdsByCursor, notificationHandlingChain;
1812
+ var init_notification_socket = __esm(() => {
1813
+ init_logger();
1814
+ init_context_rpc();
1815
+ bufferedNotifications = [];
1816
+ lastHandledIdByCursor = new Map;
1817
+ handledNotificationIds = new Set;
1818
+ handledNotificationIdOrder = [];
1819
+ legacyUnconsumedIdsByCursor = new Map;
1820
+ legacyConsumedIdsByCursor = new Map;
1821
+ notificationHandlingChain = Promise.resolve();
1822
+ });
1823
+
1824
+ // src/v2/tui/session-directory.ts
1825
+ function directoryForSession(sessionDirectory, startupDirectory) {
1826
+ return typeof sessionDirectory === "string" && sessionDirectory.length > 0 ? sessionDirectory : startupDirectory;
1827
+ }
1828
+ function sessionDirectory(context, sessionID) {
1829
+ try {
1830
+ return context.data.session?.get(sessionID)?.location?.directory;
1831
+ } catch {
1832
+ return;
1833
+ }
1834
+ }
1835
+
1836
+ // src/v2/tui/client.ts
1837
+ function compactTokens(value) {
1838
+ if (value >= 1e6)
1839
+ return `${(value / 1e6).toFixed(1)}M`;
1840
+ if (value >= 1000)
1841
+ return `${Math.round(value / 1000)}K`;
1842
+ return String(value);
1843
+ }
1844
+ function statusText(detail) {
1845
+ const context = detail.contextLimit > 0 ? `${detail.usagePercentage.toFixed(1)}% (${compactTokens(detail.inputTokens)}/${compactTokens(detail.contextLimit)} tokens)` : `${compactTokens(detail.inputTokens)} tokens`;
1846
+ return [
1847
+ ...detail.compaction_enabled === false ? [
1848
+ `Compaction: disabled (${COMPACTION_ENABLED_PATH}: false) — native compaction owns the context window.`
1849
+ ] : [],
1850
+ `Context: ${context}`,
1851
+ `Historian: ${detail.historianRunning ? "running" : "idle"}`,
1852
+ `Compartments: ${detail.compartmentCount}`,
1853
+ `Memories: ${detail.memoryBlockCount} injected / ${detail.memoryCount} stored`,
1854
+ `Pending reductions: ${detail.pendingOpsCount}`,
1855
+ `Harness: opencode2`,
1856
+ ...detail.configGeneration === undefined ? [] : [
1857
+ `Config generation: ${detail.configGeneration} (adopted ${detail.configAdoptedAt ? new Date(detail.configAdoptedAt).toLocaleString() : "unknown"})`
1858
+ ],
1859
+ ...detail.configReloadFailure ? [
1860
+ `Config reload failed ${detail.configReloadFailure.path}: ${detail.configReloadFailure.message}`
1861
+ ] : [],
1862
+ ...detail.lastTransformError ? [`Warning: ${detail.lastTransformError}`] : []
1863
+ ].join(`
1864
+ `);
1865
+ }
1866
+ function statusTextFor(status) {
1867
+ if (status.state === "unavailable") {
1868
+ const view = buildUnavailableStatusView(status.reason, status.versions, {
1869
+ version: pluginPackageVersion() ?? "unknown"
1870
+ });
1871
+ return [
1872
+ `${view.headline.left.text}: ${view.headline.right.text}`,
1873
+ ...view.sections.flatMap((section) => section.rows.map((row) => `${row.label}: ${row.value}`)),
1874
+ ...view.warnings.map((warning) => warning.text)
1875
+ ].join(`
1876
+ `);
1877
+ }
1878
+ const notice = statusVersionNotice(status.versions);
1879
+ const text = statusText({
1880
+ ...status.source,
1881
+ historianRunning: status.extras.historianRunning,
1882
+ lastTransformError: status.extras.lastTransformError
1883
+ });
1884
+ return notice ? `${notice}
1885
+ ${text}` : text;
1886
+ }
1887
+ function currentSessionID(context) {
1888
+ const route = context.ui.router.current();
1889
+ return route.type === "session" && route.sessionID ? route.sessionID : null;
1890
+ }
1891
+ function createCommandRunner(context) {
1892
+ return async (title, call, pending) => {
1893
+ const target = currentSessionID(context);
1894
+ if (!target) {
1895
+ context.ui.toast.show({ message: "No active session", variant: "warning" });
1896
+ return false;
1897
+ }
1898
+ const result = await call(target);
1899
+ if (!result.ok) {
1900
+ context.ui.toast.show({
1901
+ message: result.error ?? `${title} request failed`,
1902
+ variant: "error"
1903
+ });
1904
+ return false;
1905
+ }
1906
+ if (result.started) {
1907
+ context.ui.toast.show({ message: pending ?? `${title} started`, variant: "info" });
1908
+ return true;
1909
+ }
1910
+ if (currentSessionID(context) !== target)
1911
+ return false;
1912
+ await context.ui.dialog.alert({ title, message: result.message ?? "" });
1913
+ return true;
1914
+ };
1915
+ }
1916
+ function buildKeymapLayer(context, hooks) {
1917
+ return {
1918
+ mode: "global",
1919
+ commands: [
1920
+ {
1921
+ id: "magic-context.status",
1922
+ title: "Magic Context: Status",
1923
+ group: "Magic Context",
1924
+ palette: true,
1925
+ slash: { name: "mmc-status" },
1926
+ run: async () => {
1927
+ await hooks.showStatus();
1928
+ }
1929
+ },
1930
+ {
1931
+ id: "magic-context.recomp",
1932
+ title: "Magic Context: Recomp",
1933
+ group: "Magic Context",
1934
+ palette: true,
1935
+ slash: { name: "mmc-recomp" },
1936
+ run: async () => {
1937
+ await hooks.showRecomp();
1938
+ }
1939
+ },
1940
+ {
1941
+ id: "magic-context.flush",
1942
+ title: "Magic Context: Flush",
1943
+ group: "Magic Context",
1944
+ palette: true,
1945
+ slash: { name: "mmc-flush" },
1946
+ run: async () => {
1947
+ await hooks.runCommand("Flush", requestFlush);
1948
+ }
1949
+ },
1950
+ {
1951
+ id: "magic-context.wrapup",
1952
+ title: "Magic Context: Wrapup",
1953
+ group: "Magic Context",
1954
+ palette: true,
1955
+ slash: { name: "mmc-wrapup", arguments: true },
1956
+ run: async (input) => {
1957
+ const argument = input?.trim() ?? "";
1958
+ if (argument !== "" && !/^\d+$/.test(argument)) {
1959
+ context.ui.toast.show({
1960
+ message: "Usage: /mmc-wrapup [messages_to_keep] where messages_to_keep is a positive integer",
1961
+ variant: "warning"
1962
+ });
1963
+ return;
1964
+ }
1965
+ const messagesToKeep = argument === "" ? 20 : Number.parseInt(argument, 10);
1966
+ if (messagesToKeep <= 0) {
1967
+ context.ui.toast.show({
1968
+ message: "messages_to_keep must be a positive integer",
1969
+ variant: "warning"
1970
+ });
1971
+ return;
1972
+ }
1973
+ await hooks.runCommand("Wrapup", (sessionID) => requestWrapup(sessionID, messagesToKeep), "Wrapup started; the summary appears when it finishes");
1974
+ }
1975
+ },
1976
+ {
1977
+ id: "magic-context.maintain",
1978
+ title: "Magic Context: Memory maintenance",
1979
+ group: "Magic Context",
1980
+ palette: true,
1981
+ slash: { name: "mmc-memory-maintain" },
1982
+ run: async () => {
1983
+ await hooks.runCommand("Memory maintenance", requestMaintenance, "Memory maintenance started");
1984
+ }
1985
+ }
1986
+ ]
1987
+ };
1988
+ }
1989
+ function createNotificationHandler(context, hooks) {
1990
+ return async (notification) => {
1991
+ const target = notification.sessionId ?? currentSessionID(context);
1992
+ if (notification.sessionId && notification.sessionId !== currentSessionID(context))
1993
+ return false;
1994
+ if (notification.type === "toast") {
1995
+ const payload = notification.payload;
1996
+ context.ui.toast.show({
1997
+ title: typeof payload.title === "string" ? payload.title : undefined,
1998
+ message: String(payload.message ?? ""),
1999
+ variant: payload.variant === "success" || payload.variant === "warning" || payload.variant === "error" ? payload.variant : "info",
2000
+ duration: typeof payload.duration === "number" ? payload.duration : undefined
2001
+ });
2002
+ return true;
2003
+ }
2004
+ if (notification.type !== "action")
2005
+ return false;
2006
+ if (notification.payload.action === "show-status-dialog") {
2007
+ return hooks.showStatus(target);
2008
+ }
2009
+ if (notification.payload.action === "show-recomp-dialog")
2010
+ return hooks.showRecomp(target);
2011
+ if (notification.payload.action === "refresh-sidebar")
2012
+ return false;
2013
+ if (notification.payload.action === "show-result-dialog") {
2014
+ await context.ui.dialog.alert({
2015
+ title: String(notification.payload.title ?? "Magic Context"),
2016
+ message: String(notification.payload.message ?? "")
2017
+ });
2018
+ return true;
2019
+ }
2020
+ return false;
2021
+ };
2022
+ }
2023
+ function createTextClient(context, directoryOf) {
2024
+ const showStatus = async (target = currentSessionID(context)) => {
2025
+ if (!target) {
2026
+ context.ui.toast.show({ message: "No active session", variant: "warning" });
2027
+ return false;
2028
+ }
2029
+ const result = await loadStatusDetail(target, directoryOf(target));
2030
+ if (currentSessionID(context) !== target)
2031
+ return false;
2032
+ await context.ui.dialog.alert({
2033
+ title: "Magic Context status",
2034
+ message: statusTextFor(result)
2035
+ });
2036
+ return true;
2037
+ };
2038
+ const showRecomp = async (target = currentSessionID(context)) => {
2039
+ if (!target) {
2040
+ context.ui.toast.show({ message: "No active session", variant: "warning" });
2041
+ return false;
2042
+ }
2043
+ const confirmed = await context.ui.dialog.confirm({
2044
+ title: "Recomp confirmation",
2045
+ message: "Recomp rebuilds compressed history and can consume significant tokens.",
2046
+ label: { confirm: "Run recomp", cancel: "Cancel" }
2047
+ });
2048
+ if (!confirmed)
2049
+ return true;
2050
+ const requested = await requestRecomp(target, directoryOf(target));
2051
+ context.ui.toast.show({
2052
+ message: requested ? "Recomp requested; historian will start shortly" : "Recomp request failed",
2053
+ variant: requested ? "info" : "error"
2054
+ });
2055
+ return requested;
2056
+ };
2057
+ const handleNotification = createNotificationHandler(context, { showStatus, showRecomp });
2058
+ const runCommand = createCommandRunner(context);
2059
+ let keymapLayerRegistered = false;
2060
+ let keymapGapLogged = false;
2061
+ const registerKeymapLayer = () => {
2062
+ if (keymapLayerRegistered)
2063
+ return true;
2064
+ try {
2065
+ context.keymap.layer(() => buildKeymapLayer(context, { showStatus, showRecomp, runCommand }));
2066
+ keymapLayerRegistered = true;
2067
+ const registered = `registered slash commands: ${buildKeymapLayer(context, {
2068
+ showStatus,
2069
+ showRecomp,
2070
+ runCommand
2071
+ }).commands.map((command) => command.slash.name).join(" ")}`;
2072
+ log(`[magic-context] ${registered}`);
2073
+ flushLogger();
2074
+ return true;
2075
+ } catch (error) {
2076
+ if (!(error instanceof Error) || error.message !== "Keymap.Provider is missing")
2077
+ throw error;
2078
+ return false;
2079
+ }
2080
+ };
2081
+ let unregisterKeymapSlot;
2082
+ if (!registerKeymapLayer()) {
2083
+ unregisterKeymapSlot = context.ui.slot({
2084
+ append: "app",
2085
+ render: () => {
2086
+ let registered = false;
2087
+ try {
2088
+ registered = registerKeymapLayer();
2089
+ } catch (error) {
2090
+ log("[magic-context] keymap.layer registration failed", error);
2091
+ }
2092
+ if (!registered && !keymapGapLogged) {
2093
+ keymapGapLogged = true;
2094
+ log("[magic-context] OpenCode 2 keymap.layer is unavailable; /mmc-status, /mmc-recomp, /mmc-flush, /mmc-wrapup and /mmc-memory-maintain were not registered");
2095
+ }
2096
+ return null;
2097
+ }
2098
+ });
2099
+ }
2100
+ startNotificationSocket({
2101
+ getSessionId: () => currentSessionID(context),
2102
+ getSessionDirectory: () => {
2103
+ const sessionID = currentSessionID(context);
2104
+ return sessionID ? directoryOf(sessionID) : null;
2105
+ },
2106
+ onNotification: handleNotification
2107
+ });
2108
+ return {
2109
+ dispose: () => {
2110
+ unregisterKeymapSlot?.();
2111
+ stopNotificationSocket();
2112
+ closeRpc();
2113
+ }
2114
+ };
2115
+ }
2116
+ async function setup(context) {
2117
+ const directory = context.location?.directory ?? context.data.location.default().directory;
2118
+ initRpcClient(directory);
2119
+ const directoryOf = (sessionID) => directoryForSession(sessionDirectory(context, sessionID), directory);
2120
+ const client = createTextClient(context, directoryOf);
2121
+ log("[magic-context] @retaw106/mmc-oc v2 text TUI setup");
2122
+ return () => {
2123
+ client.dispose();
2124
+ };
2125
+ }
2126
+ var init_client = __esm(() => {
2127
+ init_agent_disable();
2128
+ init_logger();
2129
+ init_plugin_package_version();
2130
+ init_status_view();
2131
+ init_status_view_check();
2132
+ init_context_rpc();
2133
+ init_notification_socket();
2134
+ });
2135
+
2136
+ // src/v2/tui/entry.ts
2137
+ async function setup2(context) {
2138
+ const client = await Promise.resolve().then(() => (init_client(), {}));
2139
+ return setup(context);
2140
+ }
2141
+ var entry_default = { id: "mmc-oc-tui", setup: setup2 };
2142
+ export {
2143
+ entry_default as default
2144
+ };