@retaw106/mmc-pi 0.0.0-stage → 1.2.2
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/README.md +201 -2
- package/dist/historian-calibration-extension.js +62 -0
- package/dist/index-54cgx9gm.js +489 -0
- package/dist/index-65ee50rm.js +16 -0
- package/dist/index-b3eqj1g6.js +51 -0
- package/dist/index-v6scmxvt.js +40 -0
- package/dist/index-wv28nczk.js +464 -0
- package/dist/index-ztb4k1mt.js +5459 -0
- package/dist/index.js +55678 -0
- package/dist/message-time-backfill-h02fzzp8.js +116 -0
- package/dist/read-session-chunk-af3sdznq.js +56 -0
- package/dist/rpc-notifications-jxy30tgz.js +8 -0
- package/dist/safe-notification-target-hn43s7bc.js +9 -0
- package/dist/web/main.js +6322 -0
- package/dist/web/ui/favicon.svg +4 -0
- package/dist/web/ui/index.html +36 -0
- package/dist/web/ui/panel.css +8 -0
- package/dist/web/ui/panel.js +373 -0
- package/package.json +86 -5
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
// ../plugin/src/shared/harness.ts
|
|
2
|
+
var currentHarness = "opencode";
|
|
3
|
+
var harnessLocked = false;
|
|
4
|
+
function setHarness(value) {
|
|
5
|
+
if (harnessLocked && currentHarness !== value) {
|
|
6
|
+
throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
|
|
7
|
+
}
|
|
8
|
+
currentHarness = value;
|
|
9
|
+
harnessLocked = true;
|
|
10
|
+
}
|
|
11
|
+
function getHarness() {
|
|
12
|
+
return currentHarness;
|
|
13
|
+
}
|
|
14
|
+
function harnessOwnsOpenCodeStore(harness = currentHarness) {
|
|
15
|
+
return harness === "opencode" || harness === "opencode2";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ../plugin/src/shared/data-path.ts
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import * as os from "node:os";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
|
|
23
|
+
// ../plugin/src/shared/test-temp-dir.ts
|
|
24
|
+
var STALE_TEMP_DIR_AGE_MS = 60 * 60 * 1000;
|
|
25
|
+
var registeredTempDirs = new Set;
|
|
26
|
+
|
|
27
|
+
// ../plugin/src/shared/data-path.ts
|
|
28
|
+
function getDataDir() {
|
|
29
|
+
return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
|
|
30
|
+
}
|
|
31
|
+
function getMagicContextTempDir(harness = getHarness()) {
|
|
32
|
+
return path.join(os.tmpdir(), harness, "magic-context");
|
|
33
|
+
}
|
|
34
|
+
function getMagicContextLogPath(harness = getHarness()) {
|
|
35
|
+
const envPath = process.env.MMC_LOG_PATH?.trim();
|
|
36
|
+
if (envPath)
|
|
37
|
+
return envPath;
|
|
38
|
+
return path.join(getMagicContextTempDir(harness), "magic-context.log");
|
|
39
|
+
}
|
|
40
|
+
function getProjectMagicContextDir(directory) {
|
|
41
|
+
return path.join(directory, ".mmc", "magic-context");
|
|
42
|
+
}
|
|
43
|
+
var GITIGNORE_GUARD_OPEN = "# >>> mmc:magic-context";
|
|
44
|
+
var GITIGNORE_GUARD_CLOSE = "# <<< mmc:magic-context";
|
|
45
|
+
function ensureMmcArtifactGitignore(directory) {
|
|
46
|
+
try {
|
|
47
|
+
const mmcDir = path.join(directory, ".mmc");
|
|
48
|
+
const gitignorePath = path.join(mmcDir, ".gitignore");
|
|
49
|
+
let existing = "";
|
|
50
|
+
if (existsSync(gitignorePath)) {
|
|
51
|
+
existing = readFileSync(gitignorePath, "utf8");
|
|
52
|
+
if (existing.includes(GITIGNORE_GUARD_OPEN))
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const block = `${GITIGNORE_GUARD_OPEN}
|
|
56
|
+
magic-context/
|
|
57
|
+
${GITIGNORE_GUARD_CLOSE}
|
|
58
|
+
`;
|
|
59
|
+
const needsLeadingNewline = existing.length > 0 && !existing.endsWith(`
|
|
60
|
+
`);
|
|
61
|
+
const next = existing + (needsLeadingNewline ? `
|
|
62
|
+
` : "") + block;
|
|
63
|
+
mkdirSync(mmcDir, { recursive: true });
|
|
64
|
+
writeFileSync(gitignorePath, next, "utf8");
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
function getProjectMagicContextHistorianDir(directory) {
|
|
68
|
+
return path.join(getProjectMagicContextDir(directory), "historian");
|
|
69
|
+
}
|
|
70
|
+
function getOpenCodeStorageDir() {
|
|
71
|
+
return path.join(getDataDir(), "opencode", "storage");
|
|
72
|
+
}
|
|
73
|
+
function getMagicContextStorageResolution() {
|
|
74
|
+
const testDataDir = process.env.MMC_TEST_DATA_DIR?.trim();
|
|
75
|
+
if (testDataDir) {
|
|
76
|
+
const perTestDataHome = process.env.XDG_DATA_HOME?.trim();
|
|
77
|
+
if (perTestDataHome && path.resolve(perTestDataHome) !== path.resolve(testDataDir)) {
|
|
78
|
+
return {
|
|
79
|
+
path: path.join(perTestDataHome, "mmc"),
|
|
80
|
+
source: "test isolation"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
path: path.join(testDataDir, "mmc"),
|
|
85
|
+
source: "test isolation"
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (false) {}
|
|
89
|
+
const explicitStorageDir = process.env.MMC_STORAGE_DIR?.trim();
|
|
90
|
+
if (explicitStorageDir) {
|
|
91
|
+
if (!path.isAbsolute(explicitStorageDir)) {
|
|
92
|
+
throw new Error("MMC_STORAGE_DIR must be an absolute path");
|
|
93
|
+
}
|
|
94
|
+
return { path: explicitStorageDir, source: "environment override" };
|
|
95
|
+
}
|
|
96
|
+
const xdgDataHome = process.env.XDG_DATA_HOME?.trim();
|
|
97
|
+
if (xdgDataHome) {
|
|
98
|
+
return {
|
|
99
|
+
path: path.join(xdgDataHome, "mmc"),
|
|
100
|
+
source: "XDG_DATA_HOME"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
path: path.join(os.homedir(), ".local", "share", "mmc"),
|
|
105
|
+
source: "platform default"
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function getMagicContextStorageDir() {
|
|
109
|
+
return getMagicContextStorageResolution().path;
|
|
110
|
+
}
|
|
111
|
+
function getLegacyOpenCodeMagicContextStorageDir() {
|
|
112
|
+
return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ../plugin/src/shared/logger.ts
|
|
116
|
+
import * as fs from "node:fs";
|
|
117
|
+
import * as path2 from "node:path";
|
|
118
|
+
|
|
119
|
+
// ../plugin/src/shared/redaction.ts
|
|
120
|
+
import { homedir as homedir2, userInfo } from "node:os";
|
|
121
|
+
function escapeRegex(value) {
|
|
122
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
123
|
+
}
|
|
124
|
+
var SECRET_WORDS = [
|
|
125
|
+
"key",
|
|
126
|
+
"token",
|
|
127
|
+
"secret",
|
|
128
|
+
"password",
|
|
129
|
+
"auth",
|
|
130
|
+
"authorization",
|
|
131
|
+
"bearer",
|
|
132
|
+
"credential"
|
|
133
|
+
];
|
|
134
|
+
var SECRET_SEGMENT_PATTERN = new RegExp(`^(?:${SECRET_WORDS.map((w) => `${w}s?`).join("|")})$`, "i");
|
|
135
|
+
var TRAILING_DESCRIPTORS = new Set(["id", "ids", "value", "values", "header", "headers"]);
|
|
136
|
+
function redactionTypeForKey(key) {
|
|
137
|
+
const normalized = key.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, "_");
|
|
138
|
+
const suffix = normalized.split(".").filter(Boolean).at(-1) ?? normalized;
|
|
139
|
+
return suffix || "secret";
|
|
140
|
+
}
|
|
141
|
+
function isNonSecretScalarValue(value) {
|
|
142
|
+
const v = value.trim();
|
|
143
|
+
if (v === "true" || v === "false" || v === "null" || v === "undefined")
|
|
144
|
+
return true;
|
|
145
|
+
return /^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(v);
|
|
146
|
+
}
|
|
147
|
+
var SECRET_QUALIFIERS = new Set([
|
|
148
|
+
"api",
|
|
149
|
+
"access",
|
|
150
|
+
"private",
|
|
151
|
+
"client",
|
|
152
|
+
"auth",
|
|
153
|
+
"authorization",
|
|
154
|
+
"secret",
|
|
155
|
+
"bearer",
|
|
156
|
+
"session",
|
|
157
|
+
"refresh",
|
|
158
|
+
"service",
|
|
159
|
+
"x",
|
|
160
|
+
"openai",
|
|
161
|
+
"anthropic",
|
|
162
|
+
"google",
|
|
163
|
+
"github",
|
|
164
|
+
"huggingface",
|
|
165
|
+
"aws",
|
|
166
|
+
"azure",
|
|
167
|
+
"id"
|
|
168
|
+
]);
|
|
169
|
+
var UNQUALIFIED_SECRET_SEGMENT_PATTERN = /^(?:password|secret|credential|bearer|authorization)s?$/;
|
|
170
|
+
function isSecretKey(key) {
|
|
171
|
+
const segments = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[._-]+/).filter(Boolean);
|
|
172
|
+
if (segments.length === 0)
|
|
173
|
+
return false;
|
|
174
|
+
if (segments.length === 1) {
|
|
175
|
+
const first = segments[0];
|
|
176
|
+
return Boolean(first && SECRET_SEGMENT_PATTERN.test(first));
|
|
177
|
+
}
|
|
178
|
+
for (let i = 0;i < segments.length; i++) {
|
|
179
|
+
const seg = segments[i];
|
|
180
|
+
if (!seg || !SECRET_SEGMENT_PATTERN.test(seg))
|
|
181
|
+
continue;
|
|
182
|
+
let trailingOk = true;
|
|
183
|
+
for (let j = i + 1;j < segments.length; j++) {
|
|
184
|
+
const tail = segments[j];
|
|
185
|
+
if (!tail)
|
|
186
|
+
continue;
|
|
187
|
+
if (TRAILING_DESCRIPTORS.has(tail))
|
|
188
|
+
continue;
|
|
189
|
+
if (SECRET_SEGMENT_PATTERN.test(tail))
|
|
190
|
+
continue;
|
|
191
|
+
trailingOk = false;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
if (!trailingOk)
|
|
195
|
+
continue;
|
|
196
|
+
if (UNQUALIFIED_SECRET_SEGMENT_PATTERN.test(seg))
|
|
197
|
+
return true;
|
|
198
|
+
for (let k = i - 1;k >= 0; k--) {
|
|
199
|
+
const lead = segments[k];
|
|
200
|
+
if (lead && SECRET_QUALIFIERS.has(lead))
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
function sanitizePathString(value) {
|
|
207
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir2();
|
|
208
|
+
const username = userInfo().username;
|
|
209
|
+
let sanitized = value;
|
|
210
|
+
if (home) {
|
|
211
|
+
sanitized = sanitized.replace(new RegExp(escapeRegex(home), "g"), "~");
|
|
212
|
+
}
|
|
213
|
+
sanitized = sanitized.replace(/\/Users\/[^/]+\//g, "/Users/<USER>/");
|
|
214
|
+
sanitized = sanitized.replace(/\/home\/[^/]+\//g, "/home/<USER>/");
|
|
215
|
+
sanitized = sanitized.replace(/C:\\Users\\[^\\]+\\/g, "C:\\Users\\<USER>\\");
|
|
216
|
+
if (username) {
|
|
217
|
+
sanitized = sanitized.replace(new RegExp(escapeRegex(username), "g"), "<USER>");
|
|
218
|
+
}
|
|
219
|
+
return sanitized;
|
|
220
|
+
}
|
|
221
|
+
var SECRET_TEXT_PATTERNS = [
|
|
222
|
+
{
|
|
223
|
+
pattern: /\bsk-ant-(?:api03-)?[A-Za-z0-9_-]{32,}/g,
|
|
224
|
+
replacement: "<ANTHROPIC_API_KEY_REDACTED>"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{12,}/g,
|
|
228
|
+
replacement: "<OPENAI_API_KEY_REDACTED>"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}/g,
|
|
232
|
+
replacement: "<GITHUB_PAT_REDACTED>"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
pattern: /\b(?:gh[opsu]|ghr)_[A-Za-z0-9]{30,}/g,
|
|
236
|
+
replacement: "<GITHUB_TOKEN_REDACTED>"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
pattern: /\bhf_[A-Za-z0-9]{30,}/g,
|
|
240
|
+
replacement: "<HUGGINGFACE_TOKEN_REDACTED>"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
|
|
244
|
+
replacement: "<AWS_ACCESS_KEY_ID_REDACTED>"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
pattern: /\bxox[abprsuvc]-[A-Za-z0-9-]{10,}/g,
|
|
248
|
+
replacement: "<SLACK_TOKEN_REDACTED>"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g,
|
|
252
|
+
replacement: "<GOOGLE_API_KEY_REDACTED>"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
pattern: /\b(Authorization\s*:\s*Bearer\s+)([A-Za-z0-9._~+/=-]{8,})/gi,
|
|
256
|
+
replacement: (_full, prefix) => `${prefix}<REDACTED:bearer>`
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
pattern: /\b((?:Proxy-)?Authorization\s*:\s*(?:Basic|Digest|Token|Negotiate|NTLM)\s+)([A-Za-z0-9._~+/=-]{4,})/gi,
|
|
260
|
+
replacement: (_full, prefix) => `${prefix}<REDACTED:authorization>`
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
pattern: /\b((?:x-)?(?:api-?key|api-?token|auth-?token|access-?token)\s*:\s*)([^\s'"`,;<>]{6,})/gi,
|
|
264
|
+
replacement: (full, prefix, value) => isNonSecretScalarValue(value) ? full : `${prefix}<REDACTED:api_key>`
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
pattern: /\b([a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:)([^\s/@]+)(@)/gi,
|
|
268
|
+
replacement: (_full, prefix, _password, at) => `${prefix}<REDACTED:password>${at}`
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
272
|
+
replacement: "<JWT_REDACTED>"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
pattern: /(["'])([^"']*(?:key|token|secret|password|auth|bearer|credential)[^"']*)\1(\s*:\s*)(["'])([^"']*)\4/gi,
|
|
276
|
+
replacement: (full, quote, key, separator, valueQuote, value) => isNonSecretScalarValue(value) ? full : `${quote}${key}${quote}${separator}${valueQuote}<REDACTED:${redactionTypeForKey(key)}>${valueQuote}`
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
pattern: /\b([A-Za-z0-9_.-]*(?:key|token|secret|password|auth|bearer|credential)[A-Za-z0-9_.-]*)\s*=\s*([^\s'"`]+)/gi,
|
|
280
|
+
replacement: (full, key, value) => isNonSecretScalarValue(value) ? full : `${key}=<REDACTED:${redactionTypeForKey(key)}>`
|
|
281
|
+
}
|
|
282
|
+
];
|
|
283
|
+
function redactSecretText(value) {
|
|
284
|
+
let redacted = value;
|
|
285
|
+
for (const { pattern, replacement } of SECRET_TEXT_PATTERNS) {
|
|
286
|
+
if (typeof replacement === "string") {
|
|
287
|
+
redacted = redacted.replace(pattern, replacement);
|
|
288
|
+
} else {
|
|
289
|
+
redacted = redacted.replace(pattern, replacement);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return redacted;
|
|
293
|
+
}
|
|
294
|
+
function sanitizeDiagnosticText(value) {
|
|
295
|
+
return redactSecretText(sanitizePathString(value));
|
|
296
|
+
}
|
|
297
|
+
function sanitizeConfigValue(value, keyPath = []) {
|
|
298
|
+
if (value === null || typeof value === "number" || typeof value === "boolean")
|
|
299
|
+
return value;
|
|
300
|
+
const key = keyPath.at(-1) ?? "";
|
|
301
|
+
if (key && isSecretKey(key)) {
|
|
302
|
+
return `<REDACTED:${redactionTypeForKey(key)}>`;
|
|
303
|
+
}
|
|
304
|
+
if (typeof value === "string")
|
|
305
|
+
return sanitizeDiagnosticText(value);
|
|
306
|
+
if (Array.isArray(value)) {
|
|
307
|
+
return value.map((entry, index) => sanitizeConfigValue(entry, [...keyPath, String(index)]));
|
|
308
|
+
}
|
|
309
|
+
if (value && typeof value === "object") {
|
|
310
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entry]) => [
|
|
311
|
+
entryKey,
|
|
312
|
+
sanitizeConfigValue(entry, [...keyPath, entryKey])
|
|
313
|
+
]));
|
|
314
|
+
}
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ../plugin/src/shared/logger.ts
|
|
319
|
+
var isTestEnv = false;
|
|
320
|
+
var buffer = [];
|
|
321
|
+
var flushTimer = null;
|
|
322
|
+
var FLUSH_INTERVAL_MS = 500;
|
|
323
|
+
var BUFFER_SIZE_LIMIT = 50;
|
|
324
|
+
var MAX_BUFFERED_BYTES = 1024 * 1024;
|
|
325
|
+
var bufferedBytes = 0;
|
|
326
|
+
var droppedLines = 0;
|
|
327
|
+
function boundBuffer() {
|
|
328
|
+
while (bufferedBytes > MAX_BUFFERED_BYTES && buffer.length > 0) {
|
|
329
|
+
bufferedBytes -= Buffer.byteLength(buffer.shift() ?? "");
|
|
330
|
+
droppedLines++;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
var MAX_LOG_FILE_BYTES = 32 * 1024 * 1024;
|
|
334
|
+
var SIZE_CHECK_INTERVAL_FLUSHES = 64;
|
|
335
|
+
var activeLogFile = null;
|
|
336
|
+
var activeLogSize = null;
|
|
337
|
+
var flushesSinceSizeCheck = 0;
|
|
338
|
+
var swallowedWriteCount = 0;
|
|
339
|
+
var lastErrorMessage = null;
|
|
340
|
+
var lastErrorTime = null;
|
|
341
|
+
function recordSwallowedWrite(error) {
|
|
342
|
+
try {
|
|
343
|
+
swallowedWriteCount++;
|
|
344
|
+
lastErrorMessage = sanitizeDiagnosticText(error instanceof Error ? error.message : String(error));
|
|
345
|
+
lastErrorTime = new Date().toISOString();
|
|
346
|
+
} catch {}
|
|
347
|
+
}
|
|
348
|
+
function ensureDir(filePath) {
|
|
349
|
+
fs.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
350
|
+
}
|
|
351
|
+
function isMissingFile(error) {
|
|
352
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
353
|
+
}
|
|
354
|
+
function getCurrentLogSize(logFile) {
|
|
355
|
+
if (activeLogFile === logFile && activeLogSize !== null && flushesSinceSizeCheck < SIZE_CHECK_INTERVAL_FLUSHES) {
|
|
356
|
+
return activeLogSize;
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
const stat = fs.statSync(logFile);
|
|
360
|
+
if (!stat.isFile()) {
|
|
361
|
+
throw new Error(`Magic Context log path is not a regular file: ${logFile}`);
|
|
362
|
+
}
|
|
363
|
+
fs.chmodSync(logFile, 384);
|
|
364
|
+
activeLogFile = logFile;
|
|
365
|
+
activeLogSize = stat.size;
|
|
366
|
+
flushesSinceSizeCheck = 0;
|
|
367
|
+
return stat.size;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (!isMissingFile(error))
|
|
370
|
+
throw error;
|
|
371
|
+
activeLogFile = logFile;
|
|
372
|
+
activeLogSize = 0;
|
|
373
|
+
flushesSinceSizeCheck = 0;
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function capLogData(data) {
|
|
378
|
+
if (Buffer.byteLength(data) <= MAX_LOG_FILE_BYTES)
|
|
379
|
+
return data;
|
|
380
|
+
let bounded = Buffer.from(data).subarray(0, MAX_LOG_FILE_BYTES).toString("utf8");
|
|
381
|
+
while (Buffer.byteLength(bounded) > MAX_LOG_FILE_BYTES) {
|
|
382
|
+
bounded = bounded.slice(0, -1);
|
|
383
|
+
}
|
|
384
|
+
return bounded;
|
|
385
|
+
}
|
|
386
|
+
function writeBoundedPredecessor(logFile, predecessorPath, size) {
|
|
387
|
+
const predecessorFd = fs.openSync(predecessorPath, "w", 384);
|
|
388
|
+
try {
|
|
389
|
+
fs.fchmodSync(predecessorFd, 384);
|
|
390
|
+
const bytesToCopy = Math.min(size, MAX_LOG_FILE_BYTES);
|
|
391
|
+
const sourceFd = fs.openSync(logFile, "r");
|
|
392
|
+
try {
|
|
393
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, bytesToCopy));
|
|
394
|
+
let remaining = bytesToCopy;
|
|
395
|
+
let position = Math.max(0, size - bytesToCopy);
|
|
396
|
+
while (remaining > 0) {
|
|
397
|
+
const bytesRead = fs.readSync(sourceFd, chunk, 0, Math.min(chunk.length, remaining), position);
|
|
398
|
+
if (bytesRead === 0)
|
|
399
|
+
break;
|
|
400
|
+
fs.writeSync(predecessorFd, chunk, 0, bytesRead);
|
|
401
|
+
remaining -= bytesRead;
|
|
402
|
+
position += bytesRead;
|
|
403
|
+
}
|
|
404
|
+
} finally {
|
|
405
|
+
fs.closeSync(sourceFd);
|
|
406
|
+
}
|
|
407
|
+
} finally {
|
|
408
|
+
fs.closeSync(predecessorFd);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function rotateLogFile(logFile, size) {
|
|
412
|
+
const predecessorPath = `${logFile}.1`;
|
|
413
|
+
writeBoundedPredecessor(logFile, predecessorPath, size);
|
|
414
|
+
fs.truncateSync(logFile, 0);
|
|
415
|
+
activeLogSize = 0;
|
|
416
|
+
flushesSinceSizeCheck = 0;
|
|
417
|
+
}
|
|
418
|
+
function flush() {
|
|
419
|
+
if (flushTimer) {
|
|
420
|
+
clearTimeout(flushTimer);
|
|
421
|
+
flushTimer = null;
|
|
422
|
+
}
|
|
423
|
+
if (buffer.length === 0 && droppedLines === 0)
|
|
424
|
+
return;
|
|
425
|
+
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
|
|
426
|
+
` : "";
|
|
427
|
+
try {
|
|
428
|
+
const data = capLogData(notice + buffer.join(""));
|
|
429
|
+
const logFile = getMagicContextLogPath();
|
|
430
|
+
ensureDir(logFile);
|
|
431
|
+
let currentSize = getCurrentLogSize(logFile);
|
|
432
|
+
const dataSize = Buffer.byteLength(data);
|
|
433
|
+
if (currentSize > 0 && currentSize + dataSize > MAX_LOG_FILE_BYTES) {
|
|
434
|
+
rotateLogFile(logFile, currentSize);
|
|
435
|
+
currentSize = 0;
|
|
436
|
+
}
|
|
437
|
+
fs.appendFileSync(logFile, data, { encoding: "utf8", mode: 384 });
|
|
438
|
+
buffer = [];
|
|
439
|
+
bufferedBytes = 0;
|
|
440
|
+
droppedLines = 0;
|
|
441
|
+
activeLogFile = logFile;
|
|
442
|
+
activeLogSize = currentSize + dataSize;
|
|
443
|
+
flushesSinceSizeCheck++;
|
|
444
|
+
} catch (error) {
|
|
445
|
+
activeLogFile = null;
|
|
446
|
+
activeLogSize = null;
|
|
447
|
+
flushesSinceSizeCheck = 0;
|
|
448
|
+
recordSwallowedWrite(error);
|
|
449
|
+
boundBuffer();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function scheduleFlush() {
|
|
453
|
+
if (flushTimer)
|
|
454
|
+
return;
|
|
455
|
+
flushTimer = setTimeout(() => {
|
|
456
|
+
flushTimer = null;
|
|
457
|
+
flush();
|
|
458
|
+
}, FLUSH_INTERVAL_MS);
|
|
459
|
+
}
|
|
460
|
+
function log(message, data) {
|
|
461
|
+
if (isTestEnv)
|
|
462
|
+
return;
|
|
463
|
+
try {
|
|
464
|
+
const timestamp = new Date().toISOString();
|
|
465
|
+
const serialized = data === undefined ? "" : data instanceof Error ? ` ${sanitizeDiagnosticText(`${data.message}${data.stack ? `
|
|
466
|
+
${data.stack}` : ""}`)}` : ` ${JSON.stringify(sanitizeConfigValue(data))}`;
|
|
467
|
+
const line = `[${timestamp}] ${sanitizeDiagnosticText(message)}${serialized}
|
|
468
|
+
`;
|
|
469
|
+
buffer.push(line);
|
|
470
|
+
bufferedBytes += Buffer.byteLength(line);
|
|
471
|
+
boundBuffer();
|
|
472
|
+
if (buffer.length >= BUFFER_SIZE_LIMIT) {
|
|
473
|
+
flush();
|
|
474
|
+
} else {
|
|
475
|
+
scheduleFlush();
|
|
476
|
+
}
|
|
477
|
+
} catch {}
|
|
478
|
+
}
|
|
479
|
+
function sessionLog(sessionId, message, data) {
|
|
480
|
+
log(`[magic-context][${sessionId}] ${message}`, data);
|
|
481
|
+
}
|
|
482
|
+
function flushLogger() {
|
|
483
|
+
flush();
|
|
484
|
+
}
|
|
485
|
+
if (!isTestEnv) {
|
|
486
|
+
process.on("exit", flush);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export { setHarness, getHarness, harnessOwnsOpenCodeStore, getDataDir, ensureMmcArtifactGitignore, getProjectMagicContextHistorianDir, getMagicContextStorageDir, getLegacyOpenCodeMagicContextStorageDir, sanitizeDiagnosticText, log, sessionLog, flushLogger };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
log
|
|
3
|
+
} from "./index-54cgx9gm.js";
|
|
4
|
+
|
|
5
|
+
// ../plugin/src/shared/write-transaction-timing.ts
|
|
6
|
+
var SLOW_WRITE_TRANSACTION_THRESHOLD_MS = 1000;
|
|
7
|
+
function logSlowWriteTransaction(site, startedAt, thresholdMs = SLOW_WRITE_TRANSACTION_THRESHOLD_MS, completedAtMs = performance.now()) {
|
|
8
|
+
try {
|
|
9
|
+
const durationMs = completedAtMs - startedAt;
|
|
10
|
+
if (durationMs < thresholdMs)
|
|
11
|
+
return;
|
|
12
|
+
log(`[magic-context] slow write transaction: site=${site} held=${durationMs.toFixed(1)}ms`);
|
|
13
|
+
} catch {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { logSlowWriteTransaction };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// ../plugin/src/shared/rpc-notifications.ts
|
|
2
|
+
var queue = [];
|
|
3
|
+
var nextNotificationId = 1;
|
|
4
|
+
var sinks = new Set;
|
|
5
|
+
function notificationMatchesSink(notification, sink) {
|
|
6
|
+
if (notification.sessionId === undefined)
|
|
7
|
+
return true;
|
|
8
|
+
if (sink.sessionId !== undefined)
|
|
9
|
+
return notification.sessionId === sink.sessionId;
|
|
10
|
+
return sink.protocol !== 2;
|
|
11
|
+
}
|
|
12
|
+
function pushNotification2(type, payload, sessionId) {
|
|
13
|
+
const notification = { id: nextNotificationId++, type, payload, sessionId };
|
|
14
|
+
queue.push(notification);
|
|
15
|
+
for (const sink of sinks) {
|
|
16
|
+
if (!notificationMatchesSink(notification, sink))
|
|
17
|
+
continue;
|
|
18
|
+
try {
|
|
19
|
+
sink.send(notification);
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
if (queue.length > 100) {
|
|
23
|
+
const reservedIds = new Set;
|
|
24
|
+
const reservedScopes = new Set;
|
|
25
|
+
for (let i = queue.length - 1;i >= 0 && reservedScopes.size < 25; i -= 1) {
|
|
26
|
+
const candidate = queue[i];
|
|
27
|
+
const scope = candidate.sessionId ?? "\x00global";
|
|
28
|
+
if (reservedScopes.has(scope))
|
|
29
|
+
continue;
|
|
30
|
+
reservedScopes.add(scope);
|
|
31
|
+
reservedIds.add(candidate.id);
|
|
32
|
+
}
|
|
33
|
+
const evictionIndex = queue.findIndex((candidate) => !reservedIds.has(candidate.id));
|
|
34
|
+
queue.splice(evictionIndex >= 0 ? evictionIndex : 0, 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isTuiConnected2(sessionId) {
|
|
38
|
+
if (sinks.size === 0)
|
|
39
|
+
return false;
|
|
40
|
+
if (sessionId === undefined)
|
|
41
|
+
return true;
|
|
42
|
+
for (const sink of sinks) {
|
|
43
|
+
if (sink.sessionId === sessionId)
|
|
44
|
+
return true;
|
|
45
|
+
if (sink.sessionId === undefined && sink.protocol !== 2)
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { pushNotification2, isTuiConnected2 };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
log
|
|
3
|
+
} from "./index-54cgx9gm.js";
|
|
4
|
+
|
|
5
|
+
// ../plugin/src/shared/safe-notification-target.ts
|
|
6
|
+
var DEFAULT_TITLE_RE = /^(New session - |Child session - )\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
7
|
+
function isDefaultSessionTitle2(title) {
|
|
8
|
+
return DEFAULT_TITLE_RE.test(title);
|
|
9
|
+
}
|
|
10
|
+
async function readSessionTitle(client, sessionId) {
|
|
11
|
+
try {
|
|
12
|
+
const c = client;
|
|
13
|
+
if (typeof c.session?.get !== "function")
|
|
14
|
+
return null;
|
|
15
|
+
const raw = await Promise.resolve(c.session.get({ path: { id: sessionId } }));
|
|
16
|
+
const obj = raw;
|
|
17
|
+
const title = obj && typeof obj === "object" ? obj.data?.title ?? obj.title : undefined;
|
|
18
|
+
return typeof title === "string" ? title : null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function waitForSafeNotificationTarget2(client, sessionId, options) {
|
|
24
|
+
const attempts = Math.max(1, options?.attempts ?? 4);
|
|
25
|
+
const delayMs = options?.delayMs ?? 15000;
|
|
26
|
+
for (let attempt = 0;attempt < attempts; attempt += 1) {
|
|
27
|
+
const title = await readSessionTitle(client, sessionId);
|
|
28
|
+
if (title === null)
|
|
29
|
+
return "safe";
|
|
30
|
+
if (!isDefaultSessionTitle2(title))
|
|
31
|
+
return "safe";
|
|
32
|
+
if (attempt < attempts - 1) {
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
log(`[magic-context] notification skipped: session ${sessionId} still has its default title (would suppress title generation); will retry on a later startup`);
|
|
37
|
+
return "skip";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { isDefaultSessionTitle2, waitForSafeNotificationTarget2 };
|