@cortexkit/opencode-magic-context 0.24.0 → 0.24.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.
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts +10 -0
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts +14 -0
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +10 -1
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts +23 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +7 -2
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.js +111 -67
- package/dist/shared/tui-preferences.d.ts +32 -0
- package/dist/shared/tui-preferences.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/shared/tui-preferences.test.ts +210 -0
- package/src/shared/tui-preferences.ts +303 -0
- package/src/tui/slots/sidebar-content.tsx +99 -10
package/dist/index.js
CHANGED
|
@@ -154606,15 +154606,22 @@ function ownerMessageIdForTagRow(row) {
|
|
|
154606
154606
|
}
|
|
154607
154607
|
return row.message_id.replace(CONTENT_ID_SUFFIX, "");
|
|
154608
154608
|
}
|
|
154609
|
-
function getActiveTagTokenAggregate(db, sessionId) {
|
|
154610
|
-
const
|
|
154609
|
+
function getActiveTagTokenAggregate(db, sessionId, protectedTags = 0) {
|
|
154610
|
+
const toolOutputExpr = protectedTags > 0 ? `COALESCE(SUM(CASE WHEN type = 'tool' AND tag_number < (
|
|
154611
|
+
SELECT tag_number FROM tags
|
|
154612
|
+
WHERE session_id = ? AND status = 'active'
|
|
154613
|
+
ORDER BY tag_number DESC LIMIT 1 OFFSET ?
|
|
154614
|
+
) THEN COALESCE(token_count, 0) ELSE 0 END), 0)` : `COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)`;
|
|
154615
|
+
const sql = `SELECT
|
|
154611
154616
|
COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
|
|
154612
154617
|
+ COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
|
|
154613
154618
|
COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
|
|
154614
|
-
|
|
154619
|
+
${toolOutputExpr} AS tool_output,
|
|
154615
154620
|
COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
|
|
154616
154621
|
FROM tags
|
|
154617
|
-
WHERE session_id = ? AND status = 'active'
|
|
154622
|
+
WHERE session_id = ? AND status = 'active'`;
|
|
154623
|
+
const params = protectedTags > 0 ? [sessionId, protectedTags - 1, sessionId] : [sessionId];
|
|
154624
|
+
const row = db.prepare(sql).get(...params);
|
|
154618
154625
|
return {
|
|
154619
154626
|
conversation: row?.conversation ?? 0,
|
|
154620
154627
|
toolCall: row?.tool_call ?? 0,
|
|
@@ -165369,9 +165376,10 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
165369
165376
|
if (lines.length === 0 || endOrdinal < startOrdinal)
|
|
165370
165377
|
return [];
|
|
165371
165378
|
const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
|
|
165379
|
+
const effectiveMax = Math.max(1, Math.floor(normalizedMax * CHUNK_WINDOW_SAFETY_RATIO));
|
|
165372
165380
|
const fullText = lines.join(`
|
|
165373
165381
|
`);
|
|
165374
|
-
if (estimateTokens(fullText) <=
|
|
165382
|
+
if (estimateTokens(fullText) <= effectiveMax) {
|
|
165375
165383
|
return [
|
|
165376
165384
|
{
|
|
165377
165385
|
windowIndex: 0,
|
|
@@ -165409,7 +165417,7 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
165409
165417
|
const lineStart = range?.start ?? startOrdinal;
|
|
165410
165418
|
const lineEnd = range?.end ?? lineStart;
|
|
165411
165419
|
const lineTokens = estimateTokens(line);
|
|
165412
|
-
if (currentLines.length > 0 && currentTokens + lineTokens >
|
|
165420
|
+
if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
|
|
165413
165421
|
flush2();
|
|
165414
165422
|
}
|
|
165415
165423
|
if (currentLines.length === 0) {
|
|
@@ -165564,7 +165572,7 @@ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId)
|
|
|
165564
165572
|
)`).get(projectPath, sessionId, projectPath, modelId);
|
|
165565
165573
|
return typeof row?.n === "number" ? row.n : 0;
|
|
165566
165574
|
}
|
|
165567
|
-
var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
|
|
165575
|
+
var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, CHUNK_WINDOW_SAFETY_RATIO = 0.9, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
|
|
165568
165576
|
var init_compartment_chunk_embedding = __esm(() => {
|
|
165569
165577
|
init_read_session_formatting();
|
|
165570
165578
|
loadFtsRowsStatements = new WeakMap;
|
|
@@ -166032,6 +166040,13 @@ var init_embedding_ssrf = __esm(() => {
|
|
|
166032
166040
|
function normalizeEndpoint3(endpoint) {
|
|
166033
166041
|
return endpoint?.trim().replace(/\/+$/, "") ?? "";
|
|
166034
166042
|
}
|
|
166043
|
+
function embeddingModelsMatch(served, requested) {
|
|
166044
|
+
const a = served.trim().toLowerCase();
|
|
166045
|
+
const b = requested.trim().toLowerCase();
|
|
166046
|
+
if (a.length === 0 || b.length === 0)
|
|
166047
|
+
return true;
|
|
166048
|
+
return a === b || a.includes(b) || b.includes(a);
|
|
166049
|
+
}
|
|
166035
166050
|
|
|
166036
166051
|
class OpenAICompatibleEmbeddingProvider {
|
|
166037
166052
|
modelId;
|
|
@@ -166045,6 +166060,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
166045
166060
|
failureTimes = [];
|
|
166046
166061
|
circuitOpenUntil = 0;
|
|
166047
166062
|
openLogged = false;
|
|
166063
|
+
modelMismatchLogged = false;
|
|
166048
166064
|
halfOpenProbeInFlight = false;
|
|
166049
166065
|
constructor(options) {
|
|
166050
166066
|
this.endpoint = normalizeEndpoint3(options.endpoint);
|
|
@@ -166143,6 +166159,15 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
166143
166159
|
this.recordFailure(isProbe);
|
|
166144
166160
|
return Array.from({ length: texts.length }, () => null);
|
|
166145
166161
|
}
|
|
166162
|
+
const servedModel = typeof body.model === "string" ? body.model : "";
|
|
166163
|
+
if (this.model && servedModel && !embeddingModelsMatch(servedModel, this.model)) {
|
|
166164
|
+
if (!this.modelMismatchLogged) {
|
|
166165
|
+
log(`[magic-context] embedding endpoint served a DIFFERENT model than requested — refusing the substituted vectors (they have the wrong dimensions/space). requested="${this.model}" served="${servedModel}". The endpoint likely substituted a loaded model; load/select "${this.model}" on the endpoint, or set embedding.model to the served model.`);
|
|
166166
|
+
this.modelMismatchLogged = true;
|
|
166167
|
+
}
|
|
166168
|
+
this.recordFailure(isProbe);
|
|
166169
|
+
return Array.from({ length: texts.length }, () => null);
|
|
166170
|
+
}
|
|
166146
166171
|
const items = Array.isArray(body.data) ? body.data : [];
|
|
166147
166172
|
const results = Array.from({ length: texts.length }, (_, index) => {
|
|
166148
166173
|
const embedding = items[index]?.embedding;
|
|
@@ -166629,7 +166654,7 @@ function getChunkEmbeddingModelId(config2, providerIdentity) {
|
|
|
166629
166654
|
}
|
|
166630
166655
|
const chunkIdentity = {
|
|
166631
166656
|
providerIdentity,
|
|
166632
|
-
chunkerVersion:
|
|
166657
|
+
chunkerVersion: 2,
|
|
166633
166658
|
maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
|
|
166634
166659
|
truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
|
|
166635
166660
|
};
|
|
@@ -169268,7 +169293,7 @@ ${prepared.block}
|
|
|
169268
169293
|
if (!firstMessage || !textPart || isDroppedPlaceholder(textPart.text)) {
|
|
169269
169294
|
messages.unshift({
|
|
169270
169295
|
info: { role: "user", sessionID: sessionId },
|
|
169271
|
-
parts: [{ type: "text", text: historyBlock }]
|
|
169296
|
+
parts: [{ type: "text", text: historyBlock, synthetic: true }]
|
|
169272
169297
|
});
|
|
169273
169298
|
} else {
|
|
169274
169299
|
textPart.text = `${historyBlock}
|
|
@@ -170158,10 +170183,16 @@ function softRefreshCachedM1(options) {
|
|
|
170158
170183
|
function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
|
|
170159
170184
|
messages.unshift({
|
|
170160
170185
|
info: { role: "user", sessionID: sessionId },
|
|
170161
|
-
parts: [
|
|
170186
|
+
parts: [
|
|
170187
|
+
{
|
|
170188
|
+
type: "text",
|
|
170189
|
+
text: m0Text.length > 0 ? m0Text : M0_EMPTY_BODY,
|
|
170190
|
+
synthetic: true
|
|
170191
|
+
}
|
|
170192
|
+
]
|
|
170162
170193
|
}, {
|
|
170163
170194
|
info: { role: "user", sessionID: sessionId },
|
|
170164
|
-
parts: [{ type: "text", text: m1Text }]
|
|
170195
|
+
parts: [{ type: "text", text: m1Text, synthetic: true }]
|
|
170165
170196
|
});
|
|
170166
170197
|
}
|
|
170167
170198
|
function renderFreshM0NonPersisted(options) {
|
|
@@ -177079,6 +177110,11 @@ async function runManagedRecomp(ctx, sessionId, options) {
|
|
|
177079
177110
|
try {
|
|
177080
177111
|
const message = await executeContextRecomp(buildRecompDeps(ctx, sessionId), options);
|
|
177081
177112
|
const terminalPhase = isRecompSkip(message) ? "skipped" : isRecompFailure(message) ? "failed" : "done";
|
|
177113
|
+
if (terminalPhase === "done") {
|
|
177114
|
+
try {
|
|
177115
|
+
clearEmergencyRecovery(ctx.db, sessionId);
|
|
177116
|
+
} catch {}
|
|
177117
|
+
}
|
|
177082
177118
|
setRecompTerminal(ctx.liveSessionState, sessionId, terminalPhase, extractRecompReason(message));
|
|
177083
177119
|
return message;
|
|
177084
177120
|
} catch (error51) {
|
|
@@ -177177,6 +177213,7 @@ var RECOMP_DONE_GRACE_MS = 30000;
|
|
|
177177
177213
|
var init_recomp_orchestrator = __esm(async () => {
|
|
177178
177214
|
init_compartment_storage();
|
|
177179
177215
|
init_project_identity2();
|
|
177216
|
+
init_storage_meta_persisted();
|
|
177180
177217
|
await __promiseAll([
|
|
177181
177218
|
init_memory_migration(),
|
|
177182
177219
|
init_compartment_runner()
|
|
@@ -183375,8 +183412,8 @@ var CHANNEL1_SENTINEL = "<system-reminder>";
|
|
|
183375
183412
|
var TOKENS_PER_BYTE = 0.25;
|
|
183376
183413
|
var CHANNEL1_FLOOR_TOKENS = 1e4;
|
|
183377
183414
|
var CHANNEL1_REFIRE_FLOOR_TOKENS = 1e4;
|
|
183378
|
-
function channel1RefireTokens(
|
|
183379
|
-
const scaled = Math.round(0.05 * Math.max(0,
|
|
183415
|
+
function channel1RefireTokens(workingWindowTokens) {
|
|
183416
|
+
const scaled = Math.round(0.05 * Math.max(0, workingWindowTokens));
|
|
183380
183417
|
return Math.max(CHANNEL1_REFIRE_FLOOR_TOKENS, scaled);
|
|
183381
183418
|
}
|
|
183382
183419
|
var S_GENTLE = 0.2;
|
|
@@ -183446,7 +183483,7 @@ function computeTailTokenEstimate(messages) {
|
|
|
183446
183483
|
};
|
|
183447
183484
|
}
|
|
183448
183485
|
function decideChannel1(input) {
|
|
183449
|
-
const { undroppedTokens, pressure,
|
|
183486
|
+
const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
|
|
183450
183487
|
const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
|
|
183451
183488
|
const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
|
|
183452
183489
|
const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
|
|
@@ -183461,7 +183498,7 @@ function decideChannel1(input) {
|
|
|
183461
183498
|
return quiet();
|
|
183462
183499
|
if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
|
|
183463
183500
|
return quiet();
|
|
183464
|
-
const budget =
|
|
183501
|
+
const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
|
|
183465
183502
|
const severity = undroppedTokens / budget * pressure;
|
|
183466
183503
|
if (severity < S_GENTLE)
|
|
183467
183504
|
return quiet();
|
|
@@ -183473,7 +183510,7 @@ function decideChannel1(input) {
|
|
|
183473
183510
|
else
|
|
183474
183511
|
level = "gentle";
|
|
183475
183512
|
if (lastLevel === "") {
|
|
183476
|
-
if (undroppedTokens < lastNudge + channel1RefireTokens(
|
|
183513
|
+
if (undroppedTokens < lastNudge + channel1RefireTokens(workingWindowTokens)) {
|
|
183477
183514
|
return quiet();
|
|
183478
183515
|
}
|
|
183479
183516
|
} else if (LEVEL_RANK[level] <= LEVEL_RANK[lastLevel]) {
|
|
@@ -183518,13 +183555,13 @@ function buildChannel1Reminder(level, undroppedTokens) {
|
|
|
183518
183555
|
let body;
|
|
183519
183556
|
switch (level) {
|
|
183520
183557
|
case "gentle":
|
|
183521
|
-
body = `You have ~${amount} tokens of tool output you have not reduced. ` + `
|
|
183558
|
+
body = `You have ~${amount} tokens of tool output you have not reduced. ` + `When you are done with earlier outputs, dropping them with ctx_reduce keeps context lean.`;
|
|
183522
183559
|
break;
|
|
183523
183560
|
case "firm":
|
|
183524
|
-
body = `~${amount} tokens of unreduced tool output
|
|
183561
|
+
body = `~${amount} tokens of unreduced tool output has built up. ` + `At your next natural stopping point, consider dropping what you have already processed with ctx_reduce.`;
|
|
183525
183562
|
break;
|
|
183526
183563
|
case "urgent":
|
|
183527
|
-
body = `~${amount} tokens of unreduced tool output remain
|
|
183564
|
+
body = `~${amount} tokens of unreduced tool output remain, and a large span of this session will be comparted before long. ` + `Consider dropping spent outputs with ctx_reduce so the archived span is the part that matters.`;
|
|
183528
183565
|
break;
|
|
183529
183566
|
}
|
|
183530
183567
|
return `
|
|
@@ -185112,8 +185149,55 @@ function appendReminderToUserMessage(message, reminder) {
|
|
|
185112
185149
|
}
|
|
185113
185150
|
|
|
185114
185151
|
// src/hooks/magic-context/apply-operations.ts
|
|
185115
|
-
init_tag_part_guards();
|
|
185116
185152
|
await init_storage();
|
|
185153
|
+
|
|
185154
|
+
// src/hooks/magic-context/system-injection-stripper.ts
|
|
185155
|
+
var SYSTEM_INJECTION_MARKERS = [
|
|
185156
|
+
"<!-- OMO_INTERNAL_INITIATOR -->",
|
|
185157
|
+
"[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
|
|
185158
|
+
"[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
|
|
185159
|
+
"[Category+Skill Reminder]",
|
|
185160
|
+
"[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
|
|
185161
|
+
"[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
|
|
185162
|
+
"[EMERGENCY CONTEXT WINDOW WARNING]",
|
|
185163
|
+
"Unstable background agent appears idle",
|
|
185164
|
+
"**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
|
|
185165
|
+
];
|
|
185166
|
+
var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
185167
|
+
var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
|
|
185168
|
+
function stripSystemInjection(text) {
|
|
185169
|
+
let hasInjection = false;
|
|
185170
|
+
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
185171
|
+
if (text.includes(marker)) {
|
|
185172
|
+
hasInjection = true;
|
|
185173
|
+
break;
|
|
185174
|
+
}
|
|
185175
|
+
}
|
|
185176
|
+
if (SYSTEM_REMINDER_REGEX.test(text))
|
|
185177
|
+
hasInjection = true;
|
|
185178
|
+
SYSTEM_REMINDER_REGEX.lastIndex = 0;
|
|
185179
|
+
if (!hasInjection)
|
|
185180
|
+
return null;
|
|
185181
|
+
let cleaned = text;
|
|
185182
|
+
cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
|
|
185183
|
+
cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
|
|
185184
|
+
cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
|
|
185185
|
+
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
185186
|
+
if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
|
|
185187
|
+
continue;
|
|
185188
|
+
const idx = cleaned.indexOf(marker);
|
|
185189
|
+
if (idx === -1)
|
|
185190
|
+
continue;
|
|
185191
|
+
const blockEnd = cleaned.indexOf(`
|
|
185192
|
+
|
|
185193
|
+
`, idx + marker.length);
|
|
185194
|
+
cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
|
|
185195
|
+
}
|
|
185196
|
+
return cleaned.trim();
|
|
185197
|
+
}
|
|
185198
|
+
|
|
185199
|
+
// src/hooks/magic-context/apply-operations.ts
|
|
185200
|
+
init_tag_part_guards();
|
|
185117
185201
|
var USER_DROP_PREVIEW_CHARS = 250;
|
|
185118
185202
|
var RECENT_TOOL_SKELETON_WINDOW = 20;
|
|
185119
185203
|
function buildReplacementContent(tagId, target) {
|
|
@@ -185122,6 +185206,10 @@ function buildReplacementContent(tagId, target) {
|
|
|
185122
185206
|
return `[dropped §${tagId}§]`;
|
|
185123
185207
|
}
|
|
185124
185208
|
const currentContent = target.getContent?.() ?? "";
|
|
185209
|
+
const strippedInjection = stripSystemInjection(currentContent);
|
|
185210
|
+
if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
|
|
185211
|
+
return `[dropped §${tagId}§]`;
|
|
185212
|
+
}
|
|
185125
185213
|
const originalText = stripTagPrefix(currentContent);
|
|
185126
185214
|
if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
|
|
185127
185215
|
return `[truncated §${tagId}§]
|
|
@@ -186828,51 +186916,6 @@ function planEmergencyDrop(input) {
|
|
|
186828
186916
|
};
|
|
186829
186917
|
}
|
|
186830
186918
|
|
|
186831
|
-
// src/hooks/magic-context/system-injection-stripper.ts
|
|
186832
|
-
var SYSTEM_INJECTION_MARKERS = [
|
|
186833
|
-
"<!-- OMO_INTERNAL_INITIATOR -->",
|
|
186834
|
-
"[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
|
|
186835
|
-
"[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
|
|
186836
|
-
"[Category+Skill Reminder]",
|
|
186837
|
-
"[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
|
|
186838
|
-
"[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
|
|
186839
|
-
"[EMERGENCY CONTEXT WINDOW WARNING]",
|
|
186840
|
-
"Unstable background agent appears idle",
|
|
186841
|
-
"**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
|
|
186842
|
-
];
|
|
186843
|
-
var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
186844
|
-
var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
|
|
186845
|
-
function stripSystemInjection(text) {
|
|
186846
|
-
let hasInjection = false;
|
|
186847
|
-
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
186848
|
-
if (text.includes(marker)) {
|
|
186849
|
-
hasInjection = true;
|
|
186850
|
-
break;
|
|
186851
|
-
}
|
|
186852
|
-
}
|
|
186853
|
-
if (SYSTEM_REMINDER_REGEX.test(text))
|
|
186854
|
-
hasInjection = true;
|
|
186855
|
-
SYSTEM_REMINDER_REGEX.lastIndex = 0;
|
|
186856
|
-
if (!hasInjection)
|
|
186857
|
-
return null;
|
|
186858
|
-
let cleaned = text;
|
|
186859
|
-
cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
|
|
186860
|
-
cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
|
|
186861
|
-
cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
|
|
186862
|
-
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
186863
|
-
if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
|
|
186864
|
-
continue;
|
|
186865
|
-
const idx = cleaned.indexOf(marker);
|
|
186866
|
-
if (idx === -1)
|
|
186867
|
-
continue;
|
|
186868
|
-
const blockEnd = cleaned.indexOf(`
|
|
186869
|
-
|
|
186870
|
-
`, idx + marker.length);
|
|
186871
|
-
cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
|
|
186872
|
-
}
|
|
186873
|
-
return cleaned.trim();
|
|
186874
|
-
}
|
|
186875
|
-
|
|
186876
186919
|
// src/hooks/magic-context/heuristic-cleanup.ts
|
|
186877
186920
|
init_tag_part_guards();
|
|
186878
186921
|
var DEDUP_SAFE_TOOLS = new Set([
|
|
@@ -188336,7 +188379,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
|
|
|
188336
188379
|
let tailToolTokens;
|
|
188337
188380
|
let liveTailTokens;
|
|
188338
188381
|
try {
|
|
188339
|
-
const agg = getActiveTagTokenAggregate(db, sessionId);
|
|
188382
|
+
const agg = getActiveTagTokenAggregate(db, sessionId, deps.protectedTags);
|
|
188340
188383
|
tailToolTokens = agg.toolOutput;
|
|
188341
188384
|
liveTailTokens = agg.conversation + agg.toolCall;
|
|
188342
188385
|
} catch {
|
|
@@ -188947,10 +188990,11 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
|
|
|
188947
188990
|
contextLimit: state.contextLimit,
|
|
188948
188991
|
executeThresholdPercentage: state.executeThresholdPercentage
|
|
188949
188992
|
});
|
|
188993
|
+
const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
|
|
188950
188994
|
const decision = decideChannel1({
|
|
188951
188995
|
undroppedTokens,
|
|
188952
188996
|
pressure,
|
|
188953
|
-
|
|
188997
|
+
workingWindowTokens,
|
|
188954
188998
|
lastNudgeUndropped: getLastNudgeUndropped(args.db, sessionId),
|
|
188955
188999
|
lastNudgeLevel: getLastNudgeLevel(args.db, sessionId),
|
|
188956
189000
|
hasRecentReduce: false
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const TUI_PREFS_FILE_ENV = "OPENCODE_TUI_PREFERENCES_FILE";
|
|
2
|
+
export declare function getTuiPreferencesFile(): string;
|
|
3
|
+
export declare function readTuiPreferencesFile(): Promise<Record<string, unknown>>;
|
|
4
|
+
export declare function readTuiPreferencesFileSync(): Record<string, unknown>;
|
|
5
|
+
export declare const PLUGIN_KEY = "magic-context";
|
|
6
|
+
export declare const DEFAULT_SLOT_ORDER = 200;
|
|
7
|
+
export interface MagicContextTuiPrefs {
|
|
8
|
+
forceToTop: boolean;
|
|
9
|
+
order: number;
|
|
10
|
+
startCollapsed: boolean;
|
|
11
|
+
rememberCollapsed: boolean;
|
|
12
|
+
collapsed: boolean | null;
|
|
13
|
+
header: {
|
|
14
|
+
label: string;
|
|
15
|
+
};
|
|
16
|
+
sections: {
|
|
17
|
+
historian: boolean;
|
|
18
|
+
memory: boolean;
|
|
19
|
+
status: boolean;
|
|
20
|
+
dreamer: boolean;
|
|
21
|
+
stats: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export type TuiSections = MagicContextTuiPrefs["sections"];
|
|
25
|
+
export declare const DEFAULT_PREFS: MagicContextTuiPrefs;
|
|
26
|
+
export declare function resolveMagicContextPrefs(root: Record<string, unknown>): MagicContextTuiPrefs;
|
|
27
|
+
export declare function computeEffectiveOrder(root: Record<string, unknown>, pluginKey: string, defaultOrder: number): number;
|
|
28
|
+
type JsonValue = string | number | boolean | null;
|
|
29
|
+
export declare function queueTuiPreferenceUpdate(pluginKey: string, path: string[], value: JsonValue): Promise<void>;
|
|
30
|
+
export declare function watchTuiPreferences(onChange: () => void): () => void;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=tui-preferences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-preferences.d.ts","sourceRoot":"","sources":["../../src/shared/tui-preferences.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,kBAAkB,kCAAkC,CAAC;AAGlE,wBAAgB,qBAAqB,IAAI,MAAM,CAO9C;AAQD,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAS/E;AAMD,wBAAgB,0BAA0B,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASpE;AAED,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAC1C,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,MAAM,WAAW,oBAAoB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,OAAO,CAAC;IAE3B,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,EAAE;QACN,SAAS,EAAE,OAAO,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,EAAE,OAAO,CAAC;KAClB,CAAC;CACL;AAED,MAAM,MAAM,WAAW,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;AAE3D,eAAO,MAAM,aAAa,EAAE,oBAc3B,CAAC;AAmBF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,oBAAoB,CAyB5F;AAgBD,wBAAgB,qBAAqB,CACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACrB,MAAM,CAOR;AASD,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AA0DlD,wBAAgB,wBAAwB,CACpC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,SAAS,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAeD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAuCpE"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { parse } from "comment-json";
|
|
6
|
+
import {
|
|
7
|
+
computeEffectiveOrder,
|
|
8
|
+
DEFAULT_PREFS,
|
|
9
|
+
DEFAULT_SLOT_ORDER,
|
|
10
|
+
getTuiPreferencesFile,
|
|
11
|
+
PLUGIN_KEY,
|
|
12
|
+
queueTuiPreferenceUpdate,
|
|
13
|
+
readTuiPreferencesFile,
|
|
14
|
+
resolveMagicContextPrefs,
|
|
15
|
+
TUI_PREFS_FILE_ENV,
|
|
16
|
+
} from "./tui-preferences";
|
|
17
|
+
|
|
18
|
+
let dir: string;
|
|
19
|
+
let file: string;
|
|
20
|
+
const savedEnv: Record<string, string | undefined> = {};
|
|
21
|
+
const ENV_KEYS = [TUI_PREFS_FILE_ENV, "OPENCODE_CONFIG_DIR", "XDG_CONFIG_HOME"];
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
for (const key of ENV_KEYS) savedEnv[key] = process.env[key];
|
|
25
|
+
dir = await mkdtemp(join(tmpdir(), "mc-tui-prefs-test-"));
|
|
26
|
+
file = join(dir, "tui-preferences.jsonc");
|
|
27
|
+
process.env[TUI_PREFS_FILE_ENV] = file;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
for (const key of ENV_KEYS) {
|
|
32
|
+
if (savedEnv[key] === undefined) delete process.env[key];
|
|
33
|
+
else process.env[key] = savedEnv[key];
|
|
34
|
+
}
|
|
35
|
+
await rm(dir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("getTuiPreferencesFile", () => {
|
|
39
|
+
test("env override wins", () => {
|
|
40
|
+
expect(getTuiPreferencesFile()).toBe(file);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("falls back to OPENCODE_CONFIG_DIR then XDG then ~/.config", () => {
|
|
44
|
+
delete process.env[TUI_PREFS_FILE_ENV];
|
|
45
|
+
process.env.OPENCODE_CONFIG_DIR = "/tmp/cfgdir";
|
|
46
|
+
expect(getTuiPreferencesFile()).toBe("/tmp/cfgdir/tui-preferences.jsonc");
|
|
47
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
48
|
+
process.env.XDG_CONFIG_HOME = "/tmp/xdg";
|
|
49
|
+
expect(getTuiPreferencesFile()).toBe("/tmp/xdg/opencode/tui-preferences.jsonc");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("readTuiPreferencesFile (tolerant)", () => {
|
|
54
|
+
test("missing file → {}", async () => {
|
|
55
|
+
expect(await readTuiPreferencesFile()).toEqual({});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("malformed JSON → {}", async () => {
|
|
59
|
+
await writeFile(file, "{ this is not json ", "utf8");
|
|
60
|
+
expect(await readTuiPreferencesFile()).toEqual({});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("non-object root → {}", async () => {
|
|
64
|
+
await writeFile(file, "[1, 2, 3]", "utf8");
|
|
65
|
+
expect(await readTuiPreferencesFile()).toEqual({});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("jsonc with comments + trailing comma parses", async () => {
|
|
69
|
+
await writeFile(
|
|
70
|
+
file,
|
|
71
|
+
`{
|
|
72
|
+
// a comment
|
|
73
|
+
"magic-context": { "order": 205, },
|
|
74
|
+
}`,
|
|
75
|
+
"utf8",
|
|
76
|
+
);
|
|
77
|
+
const root = await readTuiPreferencesFile();
|
|
78
|
+
expect(resolveMagicContextPrefs(root).order).toBe(205);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("resolveMagicContextPrefs (per-key validation)", () => {
|
|
83
|
+
test("missing key → full defaults clone", () => {
|
|
84
|
+
expect(resolveMagicContextPrefs({})).toEqual(DEFAULT_PREFS);
|
|
85
|
+
// clone, not the shared object
|
|
86
|
+
expect(resolveMagicContextPrefs({})).not.toBe(DEFAULT_PREFS);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("one bad value never poisons the rest", () => {
|
|
90
|
+
const prefs = resolveMagicContextPrefs({
|
|
91
|
+
"magic-context": {
|
|
92
|
+
order: "nope",
|
|
93
|
+
rememberCollapsed: 1,
|
|
94
|
+
collapsed: true,
|
|
95
|
+
sections: { historian: false, memory: "bad" },
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
expect(prefs.order).toBe(DEFAULT_SLOT_ORDER); // bad → default
|
|
99
|
+
expect(prefs.rememberCollapsed).toBe(true); // bad → default true
|
|
100
|
+
expect(prefs.collapsed).toBe(true); // valid bool preserved
|
|
101
|
+
expect(prefs.sections.historian).toBe(false); // valid bool preserved
|
|
102
|
+
expect(prefs.sections.memory).toBe(true); // bad → default true
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("order clamps to -10000..10000", () => {
|
|
106
|
+
expect(resolveMagicContextPrefs({ "magic-context": { order: 99999 } }).order).toBe(10000);
|
|
107
|
+
expect(resolveMagicContextPrefs({ "magic-context": { order: -99999 } }).order).toBe(-10000);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("collapsed non-boolean → null (seed from startCollapsed)", () => {
|
|
111
|
+
expect(resolveMagicContextPrefs({ "magic-context": {} }).collapsed).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("header label clamps length, empty → default", () => {
|
|
115
|
+
expect(
|
|
116
|
+
resolveMagicContextPrefs({ "magic-context": { header: { label: "" } } }).header.label,
|
|
117
|
+
).toBe(DEFAULT_PREFS.header.label);
|
|
118
|
+
expect(
|
|
119
|
+
resolveMagicContextPrefs({
|
|
120
|
+
"magic-context": { header: { label: "x".repeat(50) } },
|
|
121
|
+
}).header.label.length,
|
|
122
|
+
).toBe(24);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("computeEffectiveOrder (cross-plugin convention)", () => {
|
|
127
|
+
test("default when key missing", () => {
|
|
128
|
+
expect(computeEffectiveOrder({}, PLUGIN_KEY, DEFAULT_SLOT_ORDER)).toBe(DEFAULT_SLOT_ORDER);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("explicit order clamped", () => {
|
|
132
|
+
expect(computeEffectiveOrder({ "magic-context": { order: 250 } }, PLUGIN_KEY, 200)).toBe(
|
|
133
|
+
250,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("forceToTop sorts below FORCE_TOP_BASE by key position", () => {
|
|
138
|
+
const root = { aft: { forceToTop: true }, "magic-context": { forceToTop: true } };
|
|
139
|
+
expect(computeEffectiveOrder(root, "aft", 200)).toBe(-100000 + 0);
|
|
140
|
+
expect(computeEffectiveOrder(root, "magic-context", 200)).toBe(-100000 + 1);
|
|
141
|
+
// forced always beats any manual order (clamped band is strictly above)
|
|
142
|
+
expect(computeEffectiveOrder(root, "aft", 200)).toBeLessThan(-10000);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("write path — comment-json full round-trip", () => {
|
|
147
|
+
test("persists a nested key and reads back", async () => {
|
|
148
|
+
await queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], true);
|
|
149
|
+
const prefs = resolveMagicContextPrefs(await readTuiPreferencesFile());
|
|
150
|
+
expect(prefs.collapsed).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("seeds the file from the template when absent", async () => {
|
|
154
|
+
await queueTuiPreferenceUpdate(PLUGIN_KEY, ["order"], 205);
|
|
155
|
+
const text = await readFile(file, "utf8");
|
|
156
|
+
expect(text).toContain("Shared preferences for OpenCode TUI plugins");
|
|
157
|
+
expect(resolveMagicContextPrefs(await readTuiPreferencesFile()).order).toBe(205);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("INTEROP: a sibling plugin's values AND comments survive MC writing only its key", async () => {
|
|
161
|
+
// A shared file owned partly by anthropic-auth, with comments and an
|
|
162
|
+
// appearance block MC knows nothing about. MC must touch ONLY its key.
|
|
163
|
+
await writeFile(
|
|
164
|
+
file,
|
|
165
|
+
`{
|
|
166
|
+
// anthropic-auth section — DO NOT lose this BLOCK comment
|
|
167
|
+
"anthropic-auth": {
|
|
168
|
+
"order": 160,
|
|
169
|
+
"header": { "label": "CLAUDE" },
|
|
170
|
+
// bar appearance knobs MC has no schema for
|
|
171
|
+
"appearance": { "barWidth": 10, "barFilledChar": "#" },
|
|
172
|
+
"pollMs": 2000 // INLINE trailing comment — must survive too
|
|
173
|
+
},
|
|
174
|
+
"magic-context": { "order": 200 }
|
|
175
|
+
}
|
|
176
|
+
`,
|
|
177
|
+
"utf8",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
await queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], true);
|
|
181
|
+
|
|
182
|
+
const text = await readFile(file, "utf8");
|
|
183
|
+
// sibling comments preserved — BOTH block and inline trailing
|
|
184
|
+
// (comment-json round-trips both faithfully; enforce the guarantee).
|
|
185
|
+
expect(text).toContain("anthropic-auth section — DO NOT lose this BLOCK comment");
|
|
186
|
+
expect(text).toContain("bar appearance knobs MC has no schema for");
|
|
187
|
+
expect(text).toContain("INLINE trailing comment — must survive too");
|
|
188
|
+
|
|
189
|
+
// sibling VALUES intact (incl. nested keys MC has no schema for)
|
|
190
|
+
const root = parse(text) as Record<string, Record<string, unknown>>;
|
|
191
|
+
const aa = root["anthropic-auth"] as Record<string, unknown>;
|
|
192
|
+
expect(aa.order).toBe(160);
|
|
193
|
+
expect((aa.header as Record<string, unknown>).label).toBe("CLAUDE");
|
|
194
|
+
const appearance = aa.appearance as Record<string, unknown>;
|
|
195
|
+
expect(appearance.barWidth).toBe(10);
|
|
196
|
+
expect(appearance.barFilledChar).toBe("#");
|
|
197
|
+
|
|
198
|
+
// MC's own change landed
|
|
199
|
+
expect(resolveMagicContextPrefs(root).collapsed).toBe(true);
|
|
200
|
+
expect(resolveMagicContextPrefs(root).order).toBe(200);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("malformed existing file → write is a no-op, sibling content untouched", async () => {
|
|
204
|
+
const broken = `{ "anthropic-auth": { "order": 160 } broken `;
|
|
205
|
+
await writeFile(file, broken, "utf8");
|
|
206
|
+
await queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], true);
|
|
207
|
+
// unchanged — we never clobber a file we can't safely parse
|
|
208
|
+
expect(await readFile(file, "utf8")).toBe(broken);
|
|
209
|
+
});
|
|
210
|
+
});
|