@wolfx/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 +95 -9
package/dist/index.js
CHANGED
|
@@ -154609,15 +154609,22 @@ function ownerMessageIdForTagRow(row) {
|
|
|
154609
154609
|
}
|
|
154610
154610
|
return row.message_id.replace(CONTENT_ID_SUFFIX, "");
|
|
154611
154611
|
}
|
|
154612
|
-
function getActiveTagTokenAggregate(db, sessionId) {
|
|
154613
|
-
const
|
|
154612
|
+
function getActiveTagTokenAggregate(db, sessionId, protectedTags = 0) {
|
|
154613
|
+
const toolOutputExpr = protectedTags > 0 ? `COALESCE(SUM(CASE WHEN type = 'tool' AND tag_number < (
|
|
154614
|
+
SELECT tag_number FROM tags
|
|
154615
|
+
WHERE session_id = ? AND status = 'active'
|
|
154616
|
+
ORDER BY tag_number DESC LIMIT 1 OFFSET ?
|
|
154617
|
+
) THEN COALESCE(token_count, 0) ELSE 0 END), 0)` : `COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)`;
|
|
154618
|
+
const sql = `SELECT
|
|
154614
154619
|
COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
|
|
154615
154620
|
+ COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
|
|
154616
154621
|
COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
|
|
154617
|
-
|
|
154622
|
+
${toolOutputExpr} AS tool_output,
|
|
154618
154623
|
COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
|
|
154619
154624
|
FROM tags
|
|
154620
|
-
WHERE session_id = ? AND status = 'active'
|
|
154625
|
+
WHERE session_id = ? AND status = 'active'`;
|
|
154626
|
+
const params = protectedTags > 0 ? [sessionId, protectedTags - 1, sessionId] : [sessionId];
|
|
154627
|
+
const row = db.prepare(sql).get(...params);
|
|
154621
154628
|
return {
|
|
154622
154629
|
conversation: row?.conversation ?? 0,
|
|
154623
154630
|
toolCall: row?.tool_call ?? 0,
|
|
@@ -165372,9 +165379,10 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
165372
165379
|
if (lines.length === 0 || endOrdinal < startOrdinal)
|
|
165373
165380
|
return [];
|
|
165374
165381
|
const normalizedMax = normalizeCompartmentChunkMaxInputTokens(maxInputTokens);
|
|
165382
|
+
const effectiveMax = Math.max(1, Math.floor(normalizedMax * CHUNK_WINDOW_SAFETY_RATIO));
|
|
165375
165383
|
const fullText = lines.join(`
|
|
165376
165384
|
`);
|
|
165377
|
-
if (estimateTokens(fullText) <=
|
|
165385
|
+
if (estimateTokens(fullText) <= effectiveMax) {
|
|
165378
165386
|
return [
|
|
165379
165387
|
{
|
|
165380
165388
|
windowIndex: 0,
|
|
@@ -165412,7 +165420,7 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
165412
165420
|
const lineStart = range?.start ?? startOrdinal;
|
|
165413
165421
|
const lineEnd = range?.end ?? lineStart;
|
|
165414
165422
|
const lineTokens = estimateTokens(line);
|
|
165415
|
-
if (currentLines.length > 0 && currentTokens + lineTokens >
|
|
165423
|
+
if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
|
|
165416
165424
|
flush2();
|
|
165417
165425
|
}
|
|
165418
165426
|
if (currentLines.length === 0) {
|
|
@@ -165567,7 +165575,7 @@ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId)
|
|
|
165567
165575
|
)`).get(projectPath, sessionId, projectPath, modelId);
|
|
165568
165576
|
return typeof row?.n === "number" ? row.n : 0;
|
|
165569
165577
|
}
|
|
165570
|
-
var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
|
|
165578
|
+
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;
|
|
165571
165579
|
var init_compartment_chunk_embedding = __esm(() => {
|
|
165572
165580
|
init_read_session_formatting();
|
|
165573
165581
|
loadFtsRowsStatements = new WeakMap;
|
|
@@ -166035,6 +166043,13 @@ var init_embedding_ssrf = __esm(() => {
|
|
|
166035
166043
|
function normalizeEndpoint3(endpoint) {
|
|
166036
166044
|
return endpoint?.trim().replace(/\/+$/, "") ?? "";
|
|
166037
166045
|
}
|
|
166046
|
+
function embeddingModelsMatch(served, requested) {
|
|
166047
|
+
const a = served.trim().toLowerCase();
|
|
166048
|
+
const b = requested.trim().toLowerCase();
|
|
166049
|
+
if (a.length === 0 || b.length === 0)
|
|
166050
|
+
return true;
|
|
166051
|
+
return a === b || a.includes(b) || b.includes(a);
|
|
166052
|
+
}
|
|
166038
166053
|
|
|
166039
166054
|
class OpenAICompatibleEmbeddingProvider {
|
|
166040
166055
|
modelId;
|
|
@@ -166048,6 +166063,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
166048
166063
|
failureTimes = [];
|
|
166049
166064
|
circuitOpenUntil = 0;
|
|
166050
166065
|
openLogged = false;
|
|
166066
|
+
modelMismatchLogged = false;
|
|
166051
166067
|
halfOpenProbeInFlight = false;
|
|
166052
166068
|
constructor(options) {
|
|
166053
166069
|
this.endpoint = normalizeEndpoint3(options.endpoint);
|
|
@@ -166146,6 +166162,15 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
166146
166162
|
this.recordFailure(isProbe);
|
|
166147
166163
|
return Array.from({ length: texts.length }, () => null);
|
|
166148
166164
|
}
|
|
166165
|
+
const servedModel = typeof body.model === "string" ? body.model : "";
|
|
166166
|
+
if (this.model && servedModel && !embeddingModelsMatch(servedModel, this.model)) {
|
|
166167
|
+
if (!this.modelMismatchLogged) {
|
|
166168
|
+
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.`);
|
|
166169
|
+
this.modelMismatchLogged = true;
|
|
166170
|
+
}
|
|
166171
|
+
this.recordFailure(isProbe);
|
|
166172
|
+
return Array.from({ length: texts.length }, () => null);
|
|
166173
|
+
}
|
|
166149
166174
|
const items = Array.isArray(body.data) ? body.data : [];
|
|
166150
166175
|
const results = Array.from({ length: texts.length }, (_, index) => {
|
|
166151
166176
|
const embedding = items[index]?.embedding;
|
|
@@ -166632,7 +166657,7 @@ function getChunkEmbeddingModelId(config2, providerIdentity) {
|
|
|
166632
166657
|
}
|
|
166633
166658
|
const chunkIdentity = {
|
|
166634
166659
|
providerIdentity,
|
|
166635
|
-
chunkerVersion:
|
|
166660
|
+
chunkerVersion: 2,
|
|
166636
166661
|
maxInputTokens: normalizeCompartmentChunkMaxInputTokens("max_input_tokens" in config2 ? config2.max_input_tokens : undefined),
|
|
166637
166662
|
truncate: config2.provider === "openai-compatible" ? config2.truncate ?? "" : ""
|
|
166638
166663
|
};
|
|
@@ -169271,7 +169296,7 @@ ${prepared.block}
|
|
|
169271
169296
|
if (!firstMessage || !textPart || isDroppedPlaceholder(textPart.text)) {
|
|
169272
169297
|
messages.unshift({
|
|
169273
169298
|
info: { role: "user", sessionID: sessionId },
|
|
169274
|
-
parts: [{ type: "text", text: historyBlock }]
|
|
169299
|
+
parts: [{ type: "text", text: historyBlock, synthetic: true }]
|
|
169275
169300
|
});
|
|
169276
169301
|
} else {
|
|
169277
169302
|
textPart.text = `${historyBlock}
|
|
@@ -170161,10 +170186,16 @@ function softRefreshCachedM1(options) {
|
|
|
170161
170186
|
function prependM0M1Messages(sessionId, messages, m0Text, m1Text) {
|
|
170162
170187
|
messages.unshift({
|
|
170163
170188
|
info: { role: "user", sessionID: sessionId },
|
|
170164
|
-
parts: [
|
|
170189
|
+
parts: [
|
|
170190
|
+
{
|
|
170191
|
+
type: "text",
|
|
170192
|
+
text: m0Text.length > 0 ? m0Text : M0_EMPTY_BODY,
|
|
170193
|
+
synthetic: true
|
|
170194
|
+
}
|
|
170195
|
+
]
|
|
170165
170196
|
}, {
|
|
170166
170197
|
info: { role: "user", sessionID: sessionId },
|
|
170167
|
-
parts: [{ type: "text", text: m1Text }]
|
|
170198
|
+
parts: [{ type: "text", text: m1Text, synthetic: true }]
|
|
170168
170199
|
});
|
|
170169
170200
|
}
|
|
170170
170201
|
function renderFreshM0NonPersisted(options) {
|
|
@@ -177054,6 +177085,11 @@ async function runManagedRecomp(ctx, sessionId, options) {
|
|
|
177054
177085
|
try {
|
|
177055
177086
|
const message = await executeContextRecomp(buildRecompDeps(ctx, sessionId), options);
|
|
177056
177087
|
const terminalPhase = isRecompSkip(message) ? "skipped" : isRecompFailure(message) ? "failed" : "done";
|
|
177088
|
+
if (terminalPhase === "done") {
|
|
177089
|
+
try {
|
|
177090
|
+
clearEmergencyRecovery(ctx.db, sessionId);
|
|
177091
|
+
} catch {}
|
|
177092
|
+
}
|
|
177057
177093
|
setRecompTerminal(ctx.liveSessionState, sessionId, terminalPhase, extractRecompReason(message));
|
|
177058
177094
|
return message;
|
|
177059
177095
|
} catch (error51) {
|
|
@@ -177152,6 +177188,7 @@ var RECOMP_DONE_GRACE_MS = 30000;
|
|
|
177152
177188
|
var init_recomp_orchestrator = __esm(async () => {
|
|
177153
177189
|
init_compartment_storage();
|
|
177154
177190
|
init_project_identity2();
|
|
177191
|
+
init_storage_meta_persisted();
|
|
177155
177192
|
await __promiseAll([
|
|
177156
177193
|
init_memory_migration(),
|
|
177157
177194
|
init_compartment_runner()
|
|
@@ -182686,8 +182723,8 @@ var CHANNEL1_SENTINEL = "<system-reminder>";
|
|
|
182686
182723
|
var TOKENS_PER_BYTE = 0.25;
|
|
182687
182724
|
var CHANNEL1_FLOOR_TOKENS = 1e4;
|
|
182688
182725
|
var CHANNEL1_REFIRE_FLOOR_TOKENS = 1e4;
|
|
182689
|
-
function channel1RefireTokens(
|
|
182690
|
-
const scaled = Math.round(0.05 * Math.max(0,
|
|
182726
|
+
function channel1RefireTokens(workingWindowTokens) {
|
|
182727
|
+
const scaled = Math.round(0.05 * Math.max(0, workingWindowTokens));
|
|
182691
182728
|
return Math.max(CHANNEL1_REFIRE_FLOOR_TOKENS, scaled);
|
|
182692
182729
|
}
|
|
182693
182730
|
var S_GENTLE = 0.2;
|
|
@@ -182757,7 +182794,7 @@ function computeTailTokenEstimate(messages) {
|
|
|
182757
182794
|
};
|
|
182758
182795
|
}
|
|
182759
182796
|
function decideChannel1(input) {
|
|
182760
|
-
const { undroppedTokens, pressure,
|
|
182797
|
+
const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
|
|
182761
182798
|
const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
|
|
182762
182799
|
const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
|
|
182763
182800
|
const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
|
|
@@ -182772,7 +182809,7 @@ function decideChannel1(input) {
|
|
|
182772
182809
|
return quiet();
|
|
182773
182810
|
if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
|
|
182774
182811
|
return quiet();
|
|
182775
|
-
const budget =
|
|
182812
|
+
const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
|
|
182776
182813
|
const severity = undroppedTokens / budget * pressure;
|
|
182777
182814
|
if (severity < S_GENTLE)
|
|
182778
182815
|
return quiet();
|
|
@@ -182784,7 +182821,7 @@ function decideChannel1(input) {
|
|
|
182784
182821
|
else
|
|
182785
182822
|
level = "gentle";
|
|
182786
182823
|
if (lastLevel === "") {
|
|
182787
|
-
if (undroppedTokens < lastNudge + channel1RefireTokens(
|
|
182824
|
+
if (undroppedTokens < lastNudge + channel1RefireTokens(workingWindowTokens)) {
|
|
182788
182825
|
return quiet();
|
|
182789
182826
|
}
|
|
182790
182827
|
} else if (LEVEL_RANK[level] <= LEVEL_RANK[lastLevel]) {
|
|
@@ -182829,13 +182866,13 @@ function buildChannel1Reminder(level, undroppedTokens) {
|
|
|
182829
182866
|
let body;
|
|
182830
182867
|
switch (level) {
|
|
182831
182868
|
case "gentle":
|
|
182832
|
-
body = `You have ~${amount} tokens of tool output you have not reduced. ` + `
|
|
182869
|
+
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.`;
|
|
182833
182870
|
break;
|
|
182834
182871
|
case "firm":
|
|
182835
|
-
body = `~${amount} tokens of unreduced tool output
|
|
182872
|
+
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.`;
|
|
182836
182873
|
break;
|
|
182837
182874
|
case "urgent":
|
|
182838
|
-
body = `~${amount} tokens of unreduced tool output remain
|
|
182875
|
+
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.`;
|
|
182839
182876
|
break;
|
|
182840
182877
|
}
|
|
182841
182878
|
return `
|
|
@@ -184423,8 +184460,55 @@ function appendReminderToUserMessage(message, reminder) {
|
|
|
184423
184460
|
}
|
|
184424
184461
|
|
|
184425
184462
|
// src/hooks/magic-context/apply-operations.ts
|
|
184426
|
-
init_tag_part_guards();
|
|
184427
184463
|
await init_storage();
|
|
184464
|
+
|
|
184465
|
+
// src/hooks/magic-context/system-injection-stripper.ts
|
|
184466
|
+
var SYSTEM_INJECTION_MARKERS = [
|
|
184467
|
+
"<!-- OMO_INTERNAL_INITIATOR -->",
|
|
184468
|
+
"[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
|
|
184469
|
+
"[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
|
|
184470
|
+
"[Category+Skill Reminder]",
|
|
184471
|
+
"[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
|
|
184472
|
+
"[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
|
|
184473
|
+
"[EMERGENCY CONTEXT WINDOW WARNING]",
|
|
184474
|
+
"Unstable background agent appears idle",
|
|
184475
|
+
"**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
|
|
184476
|
+
];
|
|
184477
|
+
var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
184478
|
+
var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
|
|
184479
|
+
function stripSystemInjection(text) {
|
|
184480
|
+
let hasInjection = false;
|
|
184481
|
+
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
184482
|
+
if (text.includes(marker)) {
|
|
184483
|
+
hasInjection = true;
|
|
184484
|
+
break;
|
|
184485
|
+
}
|
|
184486
|
+
}
|
|
184487
|
+
if (SYSTEM_REMINDER_REGEX.test(text))
|
|
184488
|
+
hasInjection = true;
|
|
184489
|
+
SYSTEM_REMINDER_REGEX.lastIndex = 0;
|
|
184490
|
+
if (!hasInjection)
|
|
184491
|
+
return null;
|
|
184492
|
+
let cleaned = text;
|
|
184493
|
+
cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
|
|
184494
|
+
cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
|
|
184495
|
+
cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
|
|
184496
|
+
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
184497
|
+
if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
|
|
184498
|
+
continue;
|
|
184499
|
+
const idx = cleaned.indexOf(marker);
|
|
184500
|
+
if (idx === -1)
|
|
184501
|
+
continue;
|
|
184502
|
+
const blockEnd = cleaned.indexOf(`
|
|
184503
|
+
|
|
184504
|
+
`, idx + marker.length);
|
|
184505
|
+
cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
|
|
184506
|
+
}
|
|
184507
|
+
return cleaned.trim();
|
|
184508
|
+
}
|
|
184509
|
+
|
|
184510
|
+
// src/hooks/magic-context/apply-operations.ts
|
|
184511
|
+
init_tag_part_guards();
|
|
184428
184512
|
var USER_DROP_PREVIEW_CHARS = 250;
|
|
184429
184513
|
var RECENT_TOOL_SKELETON_WINDOW = 20;
|
|
184430
184514
|
function buildReplacementContent(tagId, target) {
|
|
@@ -184433,6 +184517,10 @@ function buildReplacementContent(tagId, target) {
|
|
|
184433
184517
|
return `[dropped §${tagId}§]`;
|
|
184434
184518
|
}
|
|
184435
184519
|
const currentContent = target.getContent?.() ?? "";
|
|
184520
|
+
const strippedInjection = stripSystemInjection(currentContent);
|
|
184521
|
+
if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
|
|
184522
|
+
return `[dropped §${tagId}§]`;
|
|
184523
|
+
}
|
|
184436
184524
|
const originalText = stripTagPrefix(currentContent);
|
|
184437
184525
|
if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
|
|
184438
184526
|
return `[truncated §${tagId}§]
|
|
@@ -186139,51 +186227,6 @@ function planEmergencyDrop(input) {
|
|
|
186139
186227
|
};
|
|
186140
186228
|
}
|
|
186141
186229
|
|
|
186142
|
-
// src/hooks/magic-context/system-injection-stripper.ts
|
|
186143
|
-
var SYSTEM_INJECTION_MARKERS = [
|
|
186144
|
-
"<!-- OMO_INTERNAL_INITIATOR -->",
|
|
186145
|
-
"[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
|
|
186146
|
-
"[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
|
|
186147
|
-
"[Category+Skill Reminder]",
|
|
186148
|
-
"[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
|
|
186149
|
-
"[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
|
|
186150
|
-
"[EMERGENCY CONTEXT WINDOW WARNING]",
|
|
186151
|
-
"Unstable background agent appears idle",
|
|
186152
|
-
"**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
|
|
186153
|
-
];
|
|
186154
|
-
var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
186155
|
-
var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
|
|
186156
|
-
function stripSystemInjection(text) {
|
|
186157
|
-
let hasInjection = false;
|
|
186158
|
-
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
186159
|
-
if (text.includes(marker)) {
|
|
186160
|
-
hasInjection = true;
|
|
186161
|
-
break;
|
|
186162
|
-
}
|
|
186163
|
-
}
|
|
186164
|
-
if (SYSTEM_REMINDER_REGEX.test(text))
|
|
186165
|
-
hasInjection = true;
|
|
186166
|
-
SYSTEM_REMINDER_REGEX.lastIndex = 0;
|
|
186167
|
-
if (!hasInjection)
|
|
186168
|
-
return null;
|
|
186169
|
-
let cleaned = text;
|
|
186170
|
-
cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
|
|
186171
|
-
cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
|
|
186172
|
-
cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
|
|
186173
|
-
for (const marker of SYSTEM_INJECTION_MARKERS) {
|
|
186174
|
-
if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
|
|
186175
|
-
continue;
|
|
186176
|
-
const idx = cleaned.indexOf(marker);
|
|
186177
|
-
if (idx === -1)
|
|
186178
|
-
continue;
|
|
186179
|
-
const blockEnd = cleaned.indexOf(`
|
|
186180
|
-
|
|
186181
|
-
`, idx + marker.length);
|
|
186182
|
-
cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
|
|
186183
|
-
}
|
|
186184
|
-
return cleaned.trim();
|
|
186185
|
-
}
|
|
186186
|
-
|
|
186187
186230
|
// src/hooks/magic-context/heuristic-cleanup.ts
|
|
186188
186231
|
init_tag_part_guards();
|
|
186189
186232
|
var DEDUP_SAFE_TOOLS = new Set([
|
|
@@ -187647,7 +187690,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
|
|
|
187647
187690
|
let tailToolTokens;
|
|
187648
187691
|
let liveTailTokens;
|
|
187649
187692
|
try {
|
|
187650
|
-
const agg = getActiveTagTokenAggregate(db, sessionId);
|
|
187693
|
+
const agg = getActiveTagTokenAggregate(db, sessionId, deps.protectedTags);
|
|
187651
187694
|
tailToolTokens = agg.toolOutput;
|
|
187652
187695
|
liveTailTokens = agg.conversation + agg.toolCall;
|
|
187653
187696
|
} catch {
|
|
@@ -188282,10 +188325,11 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
|
|
|
188282
188325
|
contextLimit: state.contextLimit,
|
|
188283
188326
|
executeThresholdPercentage: state.executeThresholdPercentage
|
|
188284
188327
|
});
|
|
188328
|
+
const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
|
|
188285
188329
|
const decision = decideChannel1({
|
|
188286
188330
|
undroppedTokens,
|
|
188287
188331
|
pressure,
|
|
188288
|
-
|
|
188332
|
+
workingWindowTokens,
|
|
188289
188333
|
lastNudgeUndropped: getLastNudgeUndropped(args.db, sessionId),
|
|
188290
188334
|
lastNudgeLevel: getLastNudgeLevel(args.db, sessionId),
|
|
188291
188335
|
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
|
+
});
|