@wolfx/opencode-magic-context 0.21.8-patch.1 → 0.21.8-patch.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +14 -5
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +2 -0
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/text-complete.d.ts +39 -1
- package/dist/hooks/magic-context/text-complete.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1019 -1613
- package/dist/plugin/event.d.ts +0 -3
- package/dist/plugin/event.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/hooks/auto-update-checker/cache.d.ts +0 -23
- package/dist/hooks/auto-update-checker/cache.d.ts.map +0 -1
- package/dist/hooks/auto-update-checker/checker.d.ts +0 -13
- package/dist/hooks/auto-update-checker/checker.d.ts.map +0 -1
- package/dist/hooks/auto-update-checker/constants.d.ts +0 -10
- package/dist/hooks/auto-update-checker/constants.d.ts.map +0 -1
- package/dist/hooks/auto-update-checker/index.d.ts +0 -40
- package/dist/hooks/auto-update-checker/index.d.ts.map +0 -1
- package/dist/hooks/auto-update-checker/types.d.ts +0 -50
- package/dist/hooks/auto-update-checker/types.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -14924,7 +14924,6 @@ var init_magic_context = __esm(() => {
|
|
|
14924
14924
|
});
|
|
14925
14925
|
MagicContextConfigSchema = exports_external.object({
|
|
14926
14926
|
enabled: exports_external.boolean().default(true),
|
|
14927
|
-
auto_update: exports_external.boolean().optional(),
|
|
14928
14927
|
ctx_reduce_enabled: exports_external.boolean().default(true),
|
|
14929
14928
|
historian: HistorianConfigSchema,
|
|
14930
14929
|
dreamer: DreamerConfigSchema.optional(),
|
|
@@ -14986,6 +14985,10 @@ var init_magic_context = __esm(() => {
|
|
|
14986
14985
|
score_threshold: exports_external.number().min(0.3).max(0.95).default(0.6),
|
|
14987
14986
|
min_prompt_chars: exports_external.number().min(5).max(500).default(20)
|
|
14988
14987
|
}).default({ enabled: false, score_threshold: 0.6, min_prompt_chars: 20 }),
|
|
14988
|
+
text_complete_strip: exports_external.object({
|
|
14989
|
+
enabled: exports_external.boolean().default(true),
|
|
14990
|
+
patterns: exports_external.array(exports_external.string()).optional()
|
|
14991
|
+
}).optional(),
|
|
14989
14992
|
caveman_text_compression: exports_external.object({
|
|
14990
14993
|
enabled: exports_external.boolean().default(false),
|
|
14991
14994
|
min_chars: exports_external.number().min(100).max(1e4).default(500)
|
|
@@ -152939,6 +152942,907 @@ var init_subagent_token_capture = __esm(() => {
|
|
|
152939
152942
|
init_logger();
|
|
152940
152943
|
});
|
|
152941
152944
|
|
|
152945
|
+
// src/hooks/magic-context/compartment-prompt.ts
|
|
152946
|
+
function buildHistorianEditorPrompt(draft) {
|
|
152947
|
+
return [
|
|
152948
|
+
"This is a historian draft. Clean it up following the rules in your system prompt.",
|
|
152949
|
+
"",
|
|
152950
|
+
"<draft>",
|
|
152951
|
+
draft,
|
|
152952
|
+
"</draft>",
|
|
152953
|
+
"",
|
|
152954
|
+
"Return the cleaned draft as valid XML matching the original structure."
|
|
152955
|
+
].join(`
|
|
152956
|
+
`);
|
|
152957
|
+
}
|
|
152958
|
+
function buildCompressorPrompt(compartments, currentTokens, targetTokens, outputDepth, outputCount) {
|
|
152959
|
+
const lines = [];
|
|
152960
|
+
const densityLabel = outputDepth === 1 ? "MERGE ONLY" : outputDepth === 2 ? "LITE TIGHTEN" : outputDepth === 3 ? "FULL CONDENSE" : "ULTRA TELEGRAPH";
|
|
152961
|
+
const resolvedOutputCount = outputCount ?? Math.max(1, Math.ceil(compartments.length / 2));
|
|
152962
|
+
lines.push(`Density target: LEVEL ${outputDepth} (${densityLabel}). See system prompt for level rules.`);
|
|
152963
|
+
lines.push(`Input: ${compartments.length} compartments, ~${currentTokens} tokens. Target output: exactly ${resolvedOutputCount} compartments, ~${targetTokens} tokens total.`);
|
|
152964
|
+
lines.push("");
|
|
152965
|
+
if (outputDepth === 1) {
|
|
152966
|
+
lines.push("Merge only. Preserve narrative and all U: lines. Drop only genuine duplicate sentences spanning compartments.");
|
|
152967
|
+
} else if (outputDepth === 2) {
|
|
152968
|
+
lines.push("Merge + drop filler words and hedging. Keep grammar, keep U: lines verbatim.");
|
|
152969
|
+
} else if (outputDepth === 3) {
|
|
152970
|
+
lines.push("Merge into single-paragraph compartments. Drop articles and weak auxiliaries. Keep only IRREPLACEABLE U: lines.");
|
|
152971
|
+
} else {
|
|
152972
|
+
lines.push("Merge into telegraphic fragments with symbol connectives (→ + // |). U: lines only if truly irreplaceable.");
|
|
152973
|
+
}
|
|
152974
|
+
lines.push("");
|
|
152975
|
+
lines.push("Preserved literally at all levels: commit hashes, file paths, URLs, code spans.");
|
|
152976
|
+
lines.push("");
|
|
152977
|
+
for (const c of compartments) {
|
|
152978
|
+
lines.push(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr(c.title)}">`);
|
|
152979
|
+
lines.push(escapeXmlContent(c.content));
|
|
152980
|
+
lines.push("</compartment>");
|
|
152981
|
+
lines.push("");
|
|
152982
|
+
}
|
|
152983
|
+
lines.push("Return merged compartments as XML.");
|
|
152984
|
+
return lines.join(`
|
|
152985
|
+
`);
|
|
152986
|
+
}
|
|
152987
|
+
function buildCompartmentAgentPrompt(existingState, inputSource, options) {
|
|
152988
|
+
const existingStateBlock = options?.stateFilePath ? `Read the existing session state from this file before proceeding:
|
|
152989
|
+
${options.stateFilePath}
|
|
152990
|
+
|
|
152991
|
+
The file contains the full XML existing state (compartments, facts, memories). Read it first, then process the new messages below.` : existingState;
|
|
152992
|
+
return [
|
|
152993
|
+
"Existing state (read-only context for continuity and fact normalization — do NOT re-emit these compartments):",
|
|
152994
|
+
existingStateBlock,
|
|
152995
|
+
"",
|
|
152996
|
+
"<new_messages>",
|
|
152997
|
+
inputSource,
|
|
152998
|
+
"</new_messages>",
|
|
152999
|
+
"",
|
|
153000
|
+
"Instructions:",
|
|
153001
|
+
"- Return ONLY new compartments for the messages inside <new_messages>, plus the full normalized fact list.",
|
|
153002
|
+
"- Use the exact absolute raw ordinals from the input ranges for every compartment start/end and for <unprocessed_from>.",
|
|
153003
|
+
"- Rewrite every fact into terse, present-tense operational form. Merge semantic duplicates within each category.",
|
|
153004
|
+
"- Drop any session fact already covered by a project memory in the existing state.",
|
|
153005
|
+
"- Do not preserve prior narrative wording verbatim; if a fact is already canonical and still correct, keep or lightly normalize it.",
|
|
153006
|
+
"- Drop obsolete or task-local facts.",
|
|
153007
|
+
...options?.userMemoriesEnabled ? [
|
|
153008
|
+
"- Also emit <user_observations> with universal behavioral observations about the user (see system prompt for rules)."
|
|
153009
|
+
] : []
|
|
153010
|
+
].join(`
|
|
153011
|
+
`);
|
|
153012
|
+
}
|
|
153013
|
+
var COMPARTMENT_AGENT_SYSTEM_PROMPT = `You condense long AI coding sessions into two outputs:
|
|
153014
|
+
|
|
153015
|
+
1. compartments: completed logical work units
|
|
153016
|
+
2. facts: persistent cross-cutting information for future work
|
|
153017
|
+
|
|
153018
|
+
Compartment rules:
|
|
153019
|
+
- A compartment is one contiguous completed work unit: investigation, fix, refactor, docs update, feature, or decision.
|
|
153020
|
+
- Start a new compartment only when the work clearly pivots to a different objective.
|
|
153021
|
+
- If one broad effort contains multiple completed sub-pivots with distinct outcomes, prefer multiple smaller compartments over one umbrella compartment with many U: lines.
|
|
153022
|
+
- Do not create compartments for magic-context commands or tool-only noise.
|
|
153023
|
+
- If the input ends mid-topic, leave it out and report its first message index in <unprocessed_from>.
|
|
153024
|
+
- All compartment start/end ordinals and <unprocessed_from> must use the absolute raw message numbers shown in the input. Never renumber relative to this chunk.
|
|
153025
|
+
- Every displayed raw message ordinal in the input MUST appear in exactly one compartment. Gaps between compartments are invalid. When a displayed block is pure tool noise (e.g. a long "TC: ..." run with no narrative text), do NOT skip it — extend the preceding compartment's \`end\` to absorb the range, or include it inside the current compartment if the block falls within an ongoing work unit. Never create a dedicated compartment just to cover a tool-only run.
|
|
153026
|
+
- Only emit NEW compartments for the new messages. Do not re-emit existing compartments from the existing state.
|
|
153027
|
+
- Write comprehensive, detailed compartments. Include file paths, function names, commit hashes, config keys, and values when they matter.
|
|
153028
|
+
- Do not list every changed file. Do not narrate tool calls. Do not preserve dead-end exploration beyond a brief clause when needed.
|
|
153029
|
+
|
|
153030
|
+
# Construction order (MANDATORY)
|
|
153031
|
+
|
|
153032
|
+
For each compartment, build in this exact order:
|
|
153033
|
+
|
|
153034
|
+
1. Write the narrative summary first — what was done, why, and the outcome. This is 1–4 sentences covering the work unit completely.
|
|
153035
|
+
2. Re-read your narrative. Ask: does the summary already convey all important decisions and constraints from this work unit?
|
|
153036
|
+
3. If yes, the compartment is DONE with zero U: lines. Move on.
|
|
153037
|
+
4. If no, identify the specific signal the narrative cannot capture. Add U: lines ONLY for those signals.
|
|
153038
|
+
5. Before writing each U: line, run the CROSS-COMPARTMENT CHECK (see below).
|
|
153039
|
+
|
|
153040
|
+
Zero U: lines in a compartment is normal and expected. Most compartments should have 0–2 U: lines. Compartments with 3–5 are rare and must be justified by genuinely distinct durable signals.
|
|
153041
|
+
|
|
153042
|
+
# DROP rules (check these first — if any match, drop without exception)
|
|
153043
|
+
|
|
153044
|
+
- Questions in ANY form: "should I X?", "what about Y?", "do you think Z?", "isn't it better to A?", "why don't we B?", "any ideas?" — the resolved answer belongs in narrative only. If it feels important to keep the question, you are wrong: keep the answer in narrative.
|
|
153045
|
+
- Agreements and acknowledgments: "yes", "okay", "sure", "thanks", "go ahead", "looks good", "perfect", "I agree", "sounds good", "great".
|
|
153046
|
+
- Pure pacing and sequencing: "let's start", "continue", "let's do all", "now we can X", "let's commit", "first do A then B", "before that", "in the meantime".
|
|
153047
|
+
- Tactical observations: "I just noticed X", "we recently did Y", "I'm seeing Z right now", "this seems wrong".
|
|
153048
|
+
- Debugging status: "context is at 78%", "I'm restarting", "the last build failed".
|
|
153049
|
+
- Dogfooding/restart loops: "I restarted, can you check?", "okay we should have updated versions now", "let me try again".
|
|
153050
|
+
- Pasted error output or logs as U: line — capture the underlying problem in narrative, not the raw paste.
|
|
153051
|
+
- Examples and illustrations: "mine was when an agent wants to see X" — convert the underlying intent into a directive or drop.
|
|
153052
|
+
- Hype with embedded directive: ALL-CAPS pleas, "PLEASE PLEASE PLEASE just do X" — extract only the underlying directive into narrative; drop the hype.
|
|
153053
|
+
- Social signals, banter, emoji-only, enthusiasm.
|
|
153054
|
+
- Deferred ideas: "for later", "we can do X later", "another idea for the future".
|
|
153055
|
+
- Mid-process status: "running Y", "checking Z".
|
|
153056
|
+
- Superseded drafts once a later message gives the final decision.
|
|
153057
|
+
- Standing workflow rules ("always run lint before push") — these belong in WORKFLOW_RULES facts, not U: lines.
|
|
153058
|
+
|
|
153059
|
+
# Wording rule (default: verbatim)
|
|
153060
|
+
|
|
153061
|
+
By default, U: lines use the user's actual wording. The user's exact phrasing often carries negotiation context, emphasis, or technical specificity that paraphrase loses.
|
|
153062
|
+
|
|
153063
|
+
Paraphrase ONLY in these cases:
|
|
153064
|
+
- **Strip agreement prefixes**: "Yes X", "Okay X", "Sure X" → keep only the substantive part of X, in the user's original wording.
|
|
153065
|
+
- **Split compound directives**: If one message contains two distinct durable directives, split into two U: lines — each preserving the user's wording for its part.
|
|
153066
|
+
- **Drop conversational noise, keep core**: If a message wraps a directive in exploratory phrasing ("so I was thinking, maybe... but actually..."), drop the exploration and keep the core directive in the user's remaining words. Don't invent new phrasing.
|
|
153067
|
+
|
|
153068
|
+
NEVER:
|
|
153069
|
+
- Rewrite a clear user directive into a formal constraint statement. ("We need tool count at ~8" stays as-is; do NOT convert to "Tool count must be capped at 8.")
|
|
153070
|
+
- Synthesize a directive from multiple messages into one canonical statement. If the signal needs synthesis, it belongs in narrative, not a U: line.
|
|
153071
|
+
- Add technical specificity the user didn't state (file paths, function names, constant names). Canonical technical specificity belongs in narrative or facts, not in U: lines attributed to the user.
|
|
153072
|
+
|
|
153073
|
+
Good example:
|
|
153074
|
+
Original user message: "Yes let's do this. But we need to also make sure that we limit by message count as some sessions have quite a lot of messages."
|
|
153075
|
+
Correct U: line: "We need to also make sure that we limit by message count as some sessions have quite a lot of messages."
|
|
153076
|
+
(Stripped agreement prefix; kept the user's actual wording.)
|
|
153077
|
+
|
|
153078
|
+
Bad example (do not do this):
|
|
153079
|
+
Incorrect U: line: "Cap session history retrieval at a maximum message count to prevent memory issues on large sessions."
|
|
153080
|
+
(Rewrote the user directive into formal language and invented specificity.)
|
|
153081
|
+
|
|
153082
|
+
# KEEP rules (U: line survives only if ALL pass)
|
|
153083
|
+
|
|
153084
|
+
1. DURABLE: The signal matters after the immediate turn.
|
|
153085
|
+
2. SPECIFIC: Concrete goal, hard constraint, design decision, rejection, rationale, threshold, source-of-truth correction, or future-work directive.
|
|
153086
|
+
3. OUTCOME-BACKED: This compartment's narrative clearly states what was done, decided, or changed because of the message.
|
|
153087
|
+
4. NON-REDUNDANT: Not captured by another U: line (see CROSS-COMPARTMENT CHECK), by a fact, or by the narrative.
|
|
153088
|
+
5. IRREPLACEABLE: The user's wording adds signal that narrative paraphrase cannot preserve. If the same information could appear as narrative without losing meaning, it should.
|
|
153089
|
+
|
|
153090
|
+
Categories of KEEP:
|
|
153091
|
+
- Hard gates, thresholds, config defaults, percentages, byte sizes with concrete values.
|
|
153092
|
+
- Accepted designs and explicit decisions.
|
|
153093
|
+
- Rejections and negative constraints: "X is wrong because Y", "we should NOT do Z".
|
|
153094
|
+
- Source-of-truth corrections: "follow the code, not the README".
|
|
153095
|
+
- Implementation pivots stated in future tense: "instead of X let's do Y", "switch to Z".
|
|
153096
|
+
- Durable rationale that explains WHY an approach was chosen.
|
|
153097
|
+
- Specific feature requirements stated as durable goals.
|
|
153098
|
+
|
|
153099
|
+
# PIVOT vs OBSERVATION test
|
|
153100
|
+
|
|
153101
|
+
A pivot is FUTURE-TENSE and changes the plan: "instead of X, let's do Y", "switch this to Z", "actually, let's not do A".
|
|
153102
|
+
An observation is PAST-TENSE or PRESENT-TENSE and reports state: "we recently did X", "I just noticed Y", "this is broken right now".
|
|
153103
|
+
Observations may frame narrative context but are NOT pivots and NOT durable. Drop them as U: lines.
|
|
153104
|
+
|
|
153105
|
+
# CROSS-COMPARTMENT CHECK (forward-looking)
|
|
153106
|
+
|
|
153107
|
+
Before writing ANY U: line in the current compartment:
|
|
153108
|
+
1. Scan U: lines you have ALREADY written in previous compartments in this response.
|
|
153109
|
+
2. If any prior U: line expresses the same intent, decision, constraint, or rationale — even in different words — do NOT write the new U: line.
|
|
153110
|
+
3. Let the narrative in the current compartment carry the signal instead.
|
|
153111
|
+
|
|
153112
|
+
This is a forward operation: you only need to check what you already wrote, not revisit past compartments.
|
|
153113
|
+
|
|
153114
|
+
Examples of same-intent pairs to collapse:
|
|
153115
|
+
- "X shouldn't cause cache bust" + "X must not bust cache by itself" → keep only the first, in its original compartment.
|
|
153116
|
+
- "Let's use monorepo" + "Yes, monorepo is the right call" → keep only the first.
|
|
153117
|
+
- "Add logging" + "We need logs here too" → keep only the first.
|
|
153118
|
+
|
|
153119
|
+
Never keep two U: lines for the same underlying directive across compartments.
|
|
153120
|
+
|
|
153121
|
+
# Budget
|
|
153122
|
+
|
|
153123
|
+
- HARD LIMIT: 3–5 U: lines per compartment. 0–2 is typical.
|
|
153124
|
+
- If you have more than 5 candidate U: lines in one compartment, that is a signal to split into two compartments at a natural pivot, not to stuff more.
|
|
153125
|
+
- Every U: line must be immediately followed by 1–3 sentences describing the outcome, decision, or effect. Never stack two U: lines without intervening outcome text.
|
|
153126
|
+
|
|
153127
|
+
# Example: CORRECT preservation (narrative-first, verbatim U: line)
|
|
153128
|
+
|
|
153129
|
+
<compartment start="50" end="120" title="Built the auth layer">
|
|
153130
|
+
Implemented JWT auth with hard 60-minute exp claim and refresh-token rotation. Chose Bearer tokens over cookies after finding cookie-based auth broke the SPA flow. Added session_expiry config (read-only at runtime). Commits: a3f891, b22c4e.
|
|
153131
|
+
U: We need session expiry capped at 1 hour, no exceptions
|
|
153132
|
+
Hardcoded the 60-minute cap at the JWT-issuer layer so runtime overrides cannot extend it.
|
|
153133
|
+
</compartment>
|
|
153134
|
+
|
|
153135
|
+
Notice: only one U: line, kept verbatim from the user's actual message. The cookie-to-Bearer pivot is narrative because paraphrase captures the signal fully.
|
|
153136
|
+
|
|
153137
|
+
# Example: OVER-PRESERVATION (avoid)
|
|
153138
|
+
|
|
153139
|
+
<compartment start="200" end="350" title="Refactored data layer">
|
|
153140
|
+
U: Okay let's start on the data layer
|
|
153141
|
+
U: What about transactions?
|
|
153142
|
+
U: Yes that approach looks good
|
|
153143
|
+
U: Actually wait, maybe we need write-ahead logging
|
|
153144
|
+
U: I just noticed the previous commit broke a test
|
|
153145
|
+
U: Let's commit and ship it
|
|
153146
|
+
Refactored data layer with WAL mode and connection pooling.
|
|
153147
|
+
</compartment>
|
|
153148
|
+
|
|
153149
|
+
Problems: pacing, question, agreement, observation, pacing again. Only one message carries signal, and even that is narrative-capturable.
|
|
153150
|
+
|
|
153151
|
+
# CORRECT version of the above
|
|
153152
|
+
|
|
153153
|
+
<compartment start="200" end="350" title="Refactored data layer">
|
|
153154
|
+
Refactored data layer to use WAL mode plus connection pooling. Chose WAL over plain connections for concurrent read performance under sustained write load.
|
|
153155
|
+
</compartment>
|
|
153156
|
+
|
|
153157
|
+
Zero U: lines. The pivot to WAL is clear in narrative.
|
|
153158
|
+
|
|
153159
|
+
Fact rules:
|
|
153160
|
+
- Facts are editable state, not append-only notes. Rewrite, normalize, deduplicate, or drop existing facts whenever needed.
|
|
153161
|
+
- Before emitting any fact, check all existing facts in the same category for semantic duplicates. If two facts describe the same decision, constraint, or default with different wording, merge them into one canonical statement. Never emit two facts that could be answered by the same question.
|
|
153162
|
+
- When project memories are provided as read-only reference, drop any session fact that is already covered by a project memory. Project memories are the canonical cross-session source; session facts must not duplicate them.
|
|
153163
|
+
- Facts must be durable and actionable after the conversation ends.
|
|
153164
|
+
- A fact is either a stable invariant/default or a reusable operating rule. If it mainly explains what happened, it belongs in a compartment, not a fact.
|
|
153165
|
+
- Facts belong only in these categories when relevant: WORKFLOW_RULES, ARCHITECTURE_DECISIONS, CONSTRAINTS, CONFIG_DEFAULTS, KNOWN_ISSUES, ENVIRONMENT, NAMING, USER_PREFERENCES, USER_DIRECTIVES.
|
|
153166
|
+
- Keep only high-signal facts. Omit greetings, acknowledgements, temporary status, one-off sequencing, branch-local tactics, and task-local cleanup notes.
|
|
153167
|
+
- When a user message carries durable goals, constraints, preferences, or decision rationale, add a USER_DIRECTIVES fact when future agents should follow it after the session is compacted.
|
|
153168
|
+
- Do not turn task-local details into facts.
|
|
153169
|
+
- Do not keep stale facts. Rewrite or drop them even if the new input only implies they are obsolete.
|
|
153170
|
+
- Keep existing ARCHITECTURE_DECISIONS and CONSTRAINTS facts when they are still valid and uncontradicted; rewrite them into canonical form instead of dropping them.
|
|
153171
|
+
- Facts must be present tense and operational. Do not use chronology or provenance wording such as: initially, currently, remained, previously, later, then, was implemented, we changed, used to.
|
|
153172
|
+
- One fact bullet must contain exactly one rule/default/constraint/preference. If a candidate fact mixes history with guidance, keep the guidance and drop the history.
|
|
153173
|
+
- Durability test: a future agent should still act correctly on the fact next session, after merge/restart, without rereading the conversation.
|
|
153174
|
+
- Category guide:
|
|
153175
|
+
- WORKFLOW_RULES: standing repeatable process only. Prefer Do/When form: When <condition>, <action>. Do not store one-off branch strategy or task-specific sequencing unless it is standing policy.
|
|
153176
|
+
- ARCHITECTURE_DECISIONS: stable design choice. Use: <component> uses <choice> because <reason>.
|
|
153177
|
+
- CONSTRAINTS: hard must/must-not rule or invariant. Use: <thing> must/must not <action> because <reason>.
|
|
153178
|
+
- CONFIG_DEFAULTS: stable default only. Use: <key>=<value>.
|
|
153179
|
+
- KNOWN_ISSUES: unresolved recurring problem only. Do not store solved-issue stories.
|
|
153180
|
+
- ENVIRONMENT: stable setup fact that affects future work.
|
|
153181
|
+
- NAMING: canonical term choice. Use: Use <term>; avoid <term>.
|
|
153182
|
+
- USER_PREFERENCES: durable user preference. Prefer Do/When form.
|
|
153183
|
+
- USER_DIRECTIVES: durable user-stated goal, constraint, preference, or rationale. Keep the user's wording when it carries meaning, but narrow it to 1-3 sentences and remove filler.
|
|
153184
|
+
- Fact dedup examples:
|
|
153185
|
+
- These are DUPLICATES (merge into one): "Plugin config uses layered JSONC files" and "AFT plugin config uses layered JSONC files at ~/.config/opencode/aft.jsonc and <project>/.opencode/aft.jsonc, with project values deep-merging over user values." → keep the longer, more specific version only.
|
|
153186
|
+
- These are NOT duplicates (keep both): "AFT uses 1-based line numbers" and "AFT converts to LSP 0-based UTF-16 at the protocol boundary" → different aspects of the same system.
|
|
153187
|
+
- Fact rewrite examples:
|
|
153188
|
+
- Bad ARCHITECTURE_DECISIONS: The new tool-heavy \`ctx_reduce\` reminder was initially implemented as a hidden instruction appended to the latest user message in \`transform\`.
|
|
153189
|
+
- Good ARCHITECTURE_DECISIONS: \`ctx_reduce\` turn reminders are injected into the latest user message in \`transform\`.
|
|
153190
|
+
- Bad WORKFLOW_RULES: Current local workflow remained feat -> integrate -> build for code changes.
|
|
153191
|
+
- Good WORKFLOW_RULES (only if this is standing policy): For magic-context changes, commit on \`feat/magic-context\`, cherry-pick to \`integrate/athena-magic-context\`, run \`bun run build\` on integrate, then return to \`feat/magic-context\`.
|
|
153192
|
+
|
|
153193
|
+
Input notes:
|
|
153194
|
+
- [N] or [N-M] is a stable raw OpenCode message range.
|
|
153195
|
+
- U: means user.
|
|
153196
|
+
- A: means assistant.
|
|
153197
|
+
- TC: means tool call — a compact summary of what the agent did (e.g., "TC: Fix lint errors", "TC: read(src/index.ts)", "TC: grep(ctx_memory)"). TC lines appear when there is no text describing the action. Use them to understand what happened between text blocks, but do not copy them verbatim into compartments — incorporate their meaning into the narrative.
|
|
153198
|
+
- commits: ... on an assistant block lists commit hashes mentioned in that work unit; keep the relevant ones in the compartment summary when they matter.
|
|
153199
|
+
|
|
153200
|
+
Output valid XML only in this shape:
|
|
153201
|
+
<output>
|
|
153202
|
+
<compartments>
|
|
153203
|
+
<compartment start="FIRST" end="LAST" title="short title">
|
|
153204
|
+
U: Verbatim high-signal user message
|
|
153205
|
+
Summary text describing what was done and why.
|
|
153206
|
+
U: Another high-signal user message if applicable
|
|
153207
|
+
More summary text.
|
|
153208
|
+
</compartment>
|
|
153209
|
+
</compartments>
|
|
153210
|
+
<facts>
|
|
153211
|
+
<WORKFLOW_RULES>
|
|
153212
|
+
* Fact text
|
|
153213
|
+
</WORKFLOW_RULES>
|
|
153214
|
+
</facts>
|
|
153215
|
+
<meta>
|
|
153216
|
+
<messages_processed>FIRST-LAST</messages_processed>
|
|
153217
|
+
<unprocessed_from>INDEX</unprocessed_from>
|
|
153218
|
+
</meta>
|
|
153219
|
+
</output>
|
|
153220
|
+
|
|
153221
|
+
Omit empty fact categories. Compartments must be ordered, contiguous for the ranges they cover, and non-overlapping.`, HISTORIAN_EDITOR_SYSTEM_PROMPT = `You are an editor refining a historian draft. The draft was produced by a first-pass historian and may contain noise — low-signal U: lines, redundant quotes across compartments, and weak preservation decisions.
|
|
153222
|
+
|
|
153223
|
+
Your job is to clean the draft without changing its structure:
|
|
153224
|
+
|
|
153225
|
+
1. DROP low-signal U: lines:
|
|
153226
|
+
- Questions in any form — resolved decision goes in narrative only.
|
|
153227
|
+
- Pacing/agreement: "let's go", "yes", "okay", "sounds good", "I agree".
|
|
153228
|
+
- Pasted error output, debugging status, mid-process observations.
|
|
153229
|
+
- Tactical micro-direction: "now look at X", "first check Y".
|
|
153230
|
+
|
|
153231
|
+
2. DROP cross-compartment duplicates:
|
|
153232
|
+
- Scan U: lines across ALL compartments in the draft.
|
|
153233
|
+
- If two U: lines express the same intent/decision, keep only ONE — in the compartment where the outcome is actually described.
|
|
153234
|
+
|
|
153235
|
+
3. STRIP agreement prefixes:
|
|
153236
|
+
- "Yes we should X" → keep only the directive content, or drop entirely if nothing substantive remains after "Yes".
|
|
153237
|
+
|
|
153238
|
+
4. PREFER verbatim over paraphrase:
|
|
153239
|
+
- If the draft rephrased a user directive into formal constraint language, restore the user's wording if available.
|
|
153240
|
+
- Do not invent technical specificity (file paths, function names, constants) the user did not state.
|
|
153241
|
+
|
|
153242
|
+
5. FOLD into narrative when possible:
|
|
153243
|
+
- If a U: line's signal is already captured in the surrounding narrative, drop the U: line.
|
|
153244
|
+
- Narrative should not need the U: line to be understood.
|
|
153245
|
+
|
|
153246
|
+
6. KEEP as U: lines ONLY:
|
|
153247
|
+
- Hard constraints with concrete values (thresholds, byte sizes, timeouts).
|
|
153248
|
+
- Explicit rejections ("X is wrong because Y", "NOT Z").
|
|
153249
|
+
- Implementation pivots in future-tense ("instead of A, do B").
|
|
153250
|
+
- Source-of-truth corrections.
|
|
153251
|
+
|
|
153252
|
+
Do NOT change:
|
|
153253
|
+
- Compartment titles, ranges, or ordering.
|
|
153254
|
+
- Narrative summary text unless it directly references a U: line you dropped (in which case integrate the signal into the narrative).
|
|
153255
|
+
- Facts — leave the facts section untouched.
|
|
153256
|
+
- <meta> section — leave messages_processed and unprocessed_from exactly as the draft has them.
|
|
153257
|
+
|
|
153258
|
+
Output the cleaned version as valid XML matching the original structure. Preserve all XML tags, compartment ranges, meta, and facts.`, USER_OBSERVATIONS_APPENDIX = `
|
|
153259
|
+
|
|
153260
|
+
User observation rules (EXPERIMENTAL):
|
|
153261
|
+
- After outputting compartments and facts, also output a <user_observations> section.
|
|
153262
|
+
- User observations capture UNIVERSAL behavioral patterns about the human user — not project-specific or technical.
|
|
153263
|
+
- Good observations: communication preferences, review focus areas, expertise level, decision-making patterns, frustration triggers, working style.
|
|
153264
|
+
- Bad observations (DO NOT emit): project-specific preferences, framework choices, coding language preferences, one-off moods, task-local frustration.
|
|
153265
|
+
- Each observation must be a single concise sentence in present tense.
|
|
153266
|
+
- Only emit observations you have strong evidence for from the conversation. Do not speculate.
|
|
153267
|
+
- Emit 0-5 observations per run. Zero is fine if nothing stands out.
|
|
153268
|
+
- The output shape inside <output> gains an additional section:
|
|
153269
|
+
<user_observations>
|
|
153270
|
+
* User prefers terse communication and dislikes verbose explanations.
|
|
153271
|
+
* User is technically deep — understands cache invalidation, SQLite internals, and prompt engineering.
|
|
153272
|
+
</user_observations>
|
|
153273
|
+
If no observations, omit the <user_observations> section entirely.`;
|
|
153274
|
+
var init_compartment_prompt = __esm(() => {
|
|
153275
|
+
init_compartment_storage();
|
|
153276
|
+
});
|
|
153277
|
+
|
|
153278
|
+
// src/shared/opencode-config-dir.ts
|
|
153279
|
+
import { homedir as homedir4 } from "node:os";
|
|
153280
|
+
import { join as join8, resolve as resolve3 } from "node:path";
|
|
153281
|
+
function getCliConfigDir() {
|
|
153282
|
+
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
153283
|
+
if (envConfigDir) {
|
|
153284
|
+
return resolve3(envConfigDir);
|
|
153285
|
+
}
|
|
153286
|
+
if (process.platform === "win32") {
|
|
153287
|
+
return join8(homedir4(), ".config", "opencode");
|
|
153288
|
+
}
|
|
153289
|
+
return join8(process.env.XDG_CONFIG_HOME || join8(homedir4(), ".config"), "opencode");
|
|
153290
|
+
}
|
|
153291
|
+
function getOpenCodeConfigDir(_options) {
|
|
153292
|
+
return getCliConfigDir();
|
|
153293
|
+
}
|
|
153294
|
+
function getOpenCodeConfigPaths(options) {
|
|
153295
|
+
const configDir = getOpenCodeConfigDir(options);
|
|
153296
|
+
return {
|
|
153297
|
+
configDir,
|
|
153298
|
+
configJson: join8(configDir, "opencode.json"),
|
|
153299
|
+
configJsonc: join8(configDir, "opencode.jsonc"),
|
|
153300
|
+
packageJson: join8(configDir, "package.json"),
|
|
153301
|
+
omoConfig: join8(configDir, "magic-context.jsonc")
|
|
153302
|
+
};
|
|
153303
|
+
}
|
|
153304
|
+
var init_opencode_config_dir = () => {};
|
|
153305
|
+
|
|
153306
|
+
// src/shared/conflict-detector.ts
|
|
153307
|
+
import { join as join9 } from "node:path";
|
|
153308
|
+
function detectConflicts(directory) {
|
|
153309
|
+
const conflicts = {
|
|
153310
|
+
compactionAuto: false,
|
|
153311
|
+
compactionPrune: false,
|
|
153312
|
+
dcpPlugin: false,
|
|
153313
|
+
omoPreemptiveCompaction: false,
|
|
153314
|
+
omoContextWindowMonitor: false,
|
|
153315
|
+
omoAnthropicRecovery: false
|
|
153316
|
+
};
|
|
153317
|
+
const reasons = [];
|
|
153318
|
+
const compactionResult = checkCompaction(directory);
|
|
153319
|
+
if (compactionResult.auto) {
|
|
153320
|
+
conflicts.compactionAuto = true;
|
|
153321
|
+
reasons.push("OpenCode auto-compaction is enabled (compaction.auto=true)");
|
|
153322
|
+
}
|
|
153323
|
+
if (compactionResult.prune) {
|
|
153324
|
+
conflicts.compactionPrune = true;
|
|
153325
|
+
reasons.push("OpenCode prune is enabled (compaction.prune=true)");
|
|
153326
|
+
}
|
|
153327
|
+
const dcpFound = checkDcpPlugin(directory);
|
|
153328
|
+
if (dcpFound) {
|
|
153329
|
+
conflicts.dcpPlugin = true;
|
|
153330
|
+
reasons.push("opencode-dcp plugin is installed — it conflicts with Magic Context's context management");
|
|
153331
|
+
}
|
|
153332
|
+
const omoResult = checkOmoHooks(directory);
|
|
153333
|
+
if (omoResult.preemptiveCompaction) {
|
|
153334
|
+
conflicts.omoPreemptiveCompaction = true;
|
|
153335
|
+
reasons.push("oh-my-opencode preemptive-compaction hook is active — it triggers compaction that conflicts with historian");
|
|
153336
|
+
}
|
|
153337
|
+
if (omoResult.contextWindowMonitor) {
|
|
153338
|
+
conflicts.omoContextWindowMonitor = true;
|
|
153339
|
+
reasons.push("oh-my-opencode context-window-monitor hook is active — it injects usage warnings that overlap with Magic Context nudges");
|
|
153340
|
+
}
|
|
153341
|
+
if (omoResult.anthropicRecovery) {
|
|
153342
|
+
conflicts.omoAnthropicRecovery = true;
|
|
153343
|
+
reasons.push("oh-my-opencode anthropic-context-window-limit-recovery hook is active — it triggers emergency compaction that bypasses historian");
|
|
153344
|
+
}
|
|
153345
|
+
return {
|
|
153346
|
+
hasConflict: reasons.length > 0,
|
|
153347
|
+
reasons,
|
|
153348
|
+
conflicts
|
|
153349
|
+
};
|
|
153350
|
+
}
|
|
153351
|
+
function checkCompaction(directory) {
|
|
153352
|
+
if (process.env.OPENCODE_DISABLE_AUTOCOMPACT) {
|
|
153353
|
+
return { auto: false, prune: false };
|
|
153354
|
+
}
|
|
153355
|
+
const projectResult = readProjectCompaction(directory);
|
|
153356
|
+
if (projectResult.resolved)
|
|
153357
|
+
return projectResult;
|
|
153358
|
+
const userResult = readUserCompaction();
|
|
153359
|
+
if (userResult.resolved)
|
|
153360
|
+
return userResult;
|
|
153361
|
+
return { auto: true, prune: false };
|
|
153362
|
+
}
|
|
153363
|
+
function readProjectCompaction(directory) {
|
|
153364
|
+
const dotOcJsonc = join9(directory, ".opencode", "opencode.jsonc");
|
|
153365
|
+
const dotOcJson = join9(directory, ".opencode", "opencode.json");
|
|
153366
|
+
const dotOcConfig = readJsoncFile(dotOcJsonc) ?? readJsoncFile(dotOcJson);
|
|
153367
|
+
if (dotOcConfig?.compaction) {
|
|
153368
|
+
const c = dotOcConfig.compaction;
|
|
153369
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
153370
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
153371
|
+
}
|
|
153372
|
+
}
|
|
153373
|
+
const rootJsonc = join9(directory, "opencode.jsonc");
|
|
153374
|
+
const rootJson = join9(directory, "opencode.json");
|
|
153375
|
+
const rootConfig = readJsoncFile(rootJsonc) ?? readJsoncFile(rootJson);
|
|
153376
|
+
if (rootConfig?.compaction) {
|
|
153377
|
+
const c = rootConfig.compaction;
|
|
153378
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
153379
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
153380
|
+
}
|
|
153381
|
+
}
|
|
153382
|
+
return { auto: false, prune: false, resolved: false };
|
|
153383
|
+
}
|
|
153384
|
+
function readUserCompaction() {
|
|
153385
|
+
try {
|
|
153386
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
153387
|
+
const config2 = readJsoncFile(paths.configJsonc) ?? readJsoncFile(paths.configJson);
|
|
153388
|
+
if (config2?.compaction) {
|
|
153389
|
+
const c = config2.compaction;
|
|
153390
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
153391
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
153392
|
+
}
|
|
153393
|
+
}
|
|
153394
|
+
} catch {}
|
|
153395
|
+
return { auto: false, prune: false, resolved: false };
|
|
153396
|
+
}
|
|
153397
|
+
function checkDcpPlugin(directory) {
|
|
153398
|
+
const plugins = collectPluginEntries(directory);
|
|
153399
|
+
return plugins.some((p) => matchesPackageName(p, DCP_PACKAGE_NAMES));
|
|
153400
|
+
}
|
|
153401
|
+
function matchesPackageName(entry, canonicalNames) {
|
|
153402
|
+
if (entry.startsWith("file:") || entry.startsWith("http:") || entry.startsWith("https:") || entry.startsWith("/") || entry.startsWith("./") || entry.startsWith("../")) {
|
|
153403
|
+
return false;
|
|
153404
|
+
}
|
|
153405
|
+
const lastAt = entry.lastIndexOf("@");
|
|
153406
|
+
const nameOnly = lastAt > 0 ? entry.slice(0, lastAt) : entry;
|
|
153407
|
+
return canonicalNames.has(nameOnly);
|
|
153408
|
+
}
|
|
153409
|
+
function extractPluginName(entry) {
|
|
153410
|
+
if (typeof entry === "string")
|
|
153411
|
+
return entry;
|
|
153412
|
+
if (Array.isArray(entry) && typeof entry[0] === "string")
|
|
153413
|
+
return entry[0];
|
|
153414
|
+
return null;
|
|
153415
|
+
}
|
|
153416
|
+
function collectPluginEntries(directory) {
|
|
153417
|
+
const plugins = [];
|
|
153418
|
+
const pushFrom = (entries) => {
|
|
153419
|
+
if (!entries)
|
|
153420
|
+
return;
|
|
153421
|
+
for (const entry of entries) {
|
|
153422
|
+
const name2 = extractPluginName(entry);
|
|
153423
|
+
if (name2)
|
|
153424
|
+
plugins.push(name2);
|
|
153425
|
+
}
|
|
153426
|
+
};
|
|
153427
|
+
for (const configPath of [
|
|
153428
|
+
join9(directory, ".opencode", "opencode.jsonc"),
|
|
153429
|
+
join9(directory, ".opencode", "opencode.json"),
|
|
153430
|
+
join9(directory, "opencode.jsonc"),
|
|
153431
|
+
join9(directory, "opencode.json")
|
|
153432
|
+
]) {
|
|
153433
|
+
const config2 = readJsoncFile(configPath);
|
|
153434
|
+
pushFrom(config2?.plugin);
|
|
153435
|
+
}
|
|
153436
|
+
try {
|
|
153437
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
153438
|
+
for (const configPath of [paths.configJsonc, paths.configJson]) {
|
|
153439
|
+
const config2 = readJsoncFile(configPath);
|
|
153440
|
+
pushFrom(config2?.plugin);
|
|
153441
|
+
}
|
|
153442
|
+
} catch {}
|
|
153443
|
+
return plugins;
|
|
153444
|
+
}
|
|
153445
|
+
function checkOmoHooks(directory) {
|
|
153446
|
+
const result = {
|
|
153447
|
+
preemptiveCompaction: false,
|
|
153448
|
+
contextWindowMonitor: false,
|
|
153449
|
+
anthropicRecovery: false
|
|
153450
|
+
};
|
|
153451
|
+
const plugins = collectPluginEntries(directory);
|
|
153452
|
+
const hasOmo = plugins.some((p) => matchesPackageName(p, OMO_PACKAGE_NAMES));
|
|
153453
|
+
if (!hasOmo)
|
|
153454
|
+
return result;
|
|
153455
|
+
const disabledHooks = readOmoDisabledHooks(directory);
|
|
153456
|
+
result.preemptiveCompaction = !disabledHooks.has("preemptive-compaction");
|
|
153457
|
+
result.contextWindowMonitor = !disabledHooks.has("context-window-monitor");
|
|
153458
|
+
result.anthropicRecovery = !disabledHooks.has("anthropic-context-window-limit-recovery");
|
|
153459
|
+
return result;
|
|
153460
|
+
}
|
|
153461
|
+
function readOmoDisabledHooks(directory) {
|
|
153462
|
+
const disabled = new Set;
|
|
153463
|
+
const configNames = [
|
|
153464
|
+
"oh-my-opencode.jsonc",
|
|
153465
|
+
"oh-my-opencode.json",
|
|
153466
|
+
"oh-my-openagent.jsonc",
|
|
153467
|
+
"oh-my-openagent.json"
|
|
153468
|
+
];
|
|
153469
|
+
try {
|
|
153470
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
153471
|
+
for (const name2 of configNames) {
|
|
153472
|
+
const configPath = join9(paths.configDir, name2);
|
|
153473
|
+
const config2 = readJsoncFile(configPath);
|
|
153474
|
+
if (config2?.disabled_hooks) {
|
|
153475
|
+
for (const hook of config2.disabled_hooks) {
|
|
153476
|
+
disabled.add(hook);
|
|
153477
|
+
}
|
|
153478
|
+
}
|
|
153479
|
+
}
|
|
153480
|
+
} catch {}
|
|
153481
|
+
for (const name2 of configNames) {
|
|
153482
|
+
const config2 = readJsoncFile(join9(directory, name2));
|
|
153483
|
+
if (config2?.disabled_hooks) {
|
|
153484
|
+
for (const hook of config2.disabled_hooks) {
|
|
153485
|
+
disabled.add(hook);
|
|
153486
|
+
}
|
|
153487
|
+
}
|
|
153488
|
+
}
|
|
153489
|
+
return disabled;
|
|
153490
|
+
}
|
|
153491
|
+
function formatConflictShort(result) {
|
|
153492
|
+
if (!result.hasConflict)
|
|
153493
|
+
return "";
|
|
153494
|
+
const lines = [
|
|
153495
|
+
"⚠️ Magic Context is disabled due to conflicting configuration:",
|
|
153496
|
+
"",
|
|
153497
|
+
...result.reasons.map((r) => `• ${r}`),
|
|
153498
|
+
"",
|
|
153499
|
+
"Fix: run `npx @wolfx/opencode-magic-context@latest doctor`"
|
|
153500
|
+
];
|
|
153501
|
+
return lines.join(`
|
|
153502
|
+
`);
|
|
153503
|
+
}
|
|
153504
|
+
var DCP_PACKAGE_NAMES, OMO_PACKAGE_NAMES;
|
|
153505
|
+
var init_conflict_detector = __esm(() => {
|
|
153506
|
+
init_jsonc_parser();
|
|
153507
|
+
init_opencode_config_dir();
|
|
153508
|
+
DCP_PACKAGE_NAMES = new Set(["@tarquinen/opencode-dcp"]);
|
|
153509
|
+
OMO_PACKAGE_NAMES = new Set(["oh-my-opencode", "oh-my-openagent"]);
|
|
153510
|
+
});
|
|
153511
|
+
|
|
153512
|
+
// src/plugin/conflict-warning-hook.ts
|
|
153513
|
+
var exports_conflict_warning_hook = {};
|
|
153514
|
+
__export(exports_conflict_warning_hook, {
|
|
153515
|
+
sendTuiSetupNotification: () => sendTuiSetupNotification,
|
|
153516
|
+
sendStartupAnnouncement: () => sendStartupAnnouncement,
|
|
153517
|
+
sendConflictWarning: () => sendConflictWarning,
|
|
153518
|
+
cleanupConflictWarnings: () => cleanupConflictWarnings
|
|
153519
|
+
});
|
|
153520
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
|
|
153521
|
+
import { homedir as homedir5, platform } from "node:os";
|
|
153522
|
+
import { join as join10 } from "node:path";
|
|
153523
|
+
function getDesktopStatePath() {
|
|
153524
|
+
const os2 = platform();
|
|
153525
|
+
const home = homedir5();
|
|
153526
|
+
if (os2 === "darwin") {
|
|
153527
|
+
return join10(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
|
|
153528
|
+
}
|
|
153529
|
+
if (os2 === "linux") {
|
|
153530
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join10(home, ".config");
|
|
153531
|
+
return join10(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
|
|
153532
|
+
}
|
|
153533
|
+
if (os2 === "win32") {
|
|
153534
|
+
const appData = process.env.APPDATA || join10(home, "AppData", "Roaming");
|
|
153535
|
+
return join10(appData, "ai.opencode.desktop", "opencode.global.dat");
|
|
153536
|
+
}
|
|
153537
|
+
return null;
|
|
153538
|
+
}
|
|
153539
|
+
function readDesktopState(directory) {
|
|
153540
|
+
const statePath = getDesktopStatePath();
|
|
153541
|
+
if (!statePath || !existsSync8(statePath)) {
|
|
153542
|
+
log(`[magic-context] conflict-warning: Desktop state file not found at ${statePath}`);
|
|
153543
|
+
return { sessionId: null, sidecarUrl: null };
|
|
153544
|
+
}
|
|
153545
|
+
try {
|
|
153546
|
+
const raw = readFileSync5(statePath, "utf-8");
|
|
153547
|
+
const state = JSON.parse(raw);
|
|
153548
|
+
let sidecarUrl = null;
|
|
153549
|
+
const serverStr = state.server;
|
|
153550
|
+
if (typeof serverStr === "string") {
|
|
153551
|
+
try {
|
|
153552
|
+
const serverState = JSON.parse(serverStr);
|
|
153553
|
+
if (typeof serverState.currentSidecarUrl === "string") {
|
|
153554
|
+
sidecarUrl = serverState.currentSidecarUrl;
|
|
153555
|
+
}
|
|
153556
|
+
} catch {}
|
|
153557
|
+
}
|
|
153558
|
+
let sessionId = null;
|
|
153559
|
+
const layoutPage = state["layout.page"];
|
|
153560
|
+
if (typeof layoutPage === "string") {
|
|
153561
|
+
const parsed = JSON.parse(layoutPage);
|
|
153562
|
+
const lastProjectSession = parsed.lastProjectSession;
|
|
153563
|
+
if (lastProjectSession) {
|
|
153564
|
+
const entry = lastProjectSession[directory];
|
|
153565
|
+
sessionId = entry?.id ?? null;
|
|
153566
|
+
}
|
|
153567
|
+
}
|
|
153568
|
+
return { sessionId, sidecarUrl };
|
|
153569
|
+
} catch (error51) {
|
|
153570
|
+
log(`[magic-context] conflict-warning: failed to read Desktop state: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
153571
|
+
return { sessionId: null, sidecarUrl: null };
|
|
153572
|
+
}
|
|
153573
|
+
}
|
|
153574
|
+
function getDesktopState(directory) {
|
|
153575
|
+
let cached2 = cachedDesktopStateByDir.get(directory);
|
|
153576
|
+
if (!cached2) {
|
|
153577
|
+
cached2 = readDesktopState(directory);
|
|
153578
|
+
cachedDesktopStateByDir.set(directory, cached2);
|
|
153579
|
+
}
|
|
153580
|
+
return cached2;
|
|
153581
|
+
}
|
|
153582
|
+
async function deleteMessage(serverUrl, sessionId, messageId) {
|
|
153583
|
+
const auth = getServerAuth();
|
|
153584
|
+
const url2 = `${serverUrl}/session/${encodeURIComponent(sessionId)}/message/${encodeURIComponent(messageId)}`;
|
|
153585
|
+
try {
|
|
153586
|
+
const response = await fetch(url2, {
|
|
153587
|
+
method: "DELETE",
|
|
153588
|
+
headers: auth ? { Authorization: auth } : {},
|
|
153589
|
+
signal: AbortSignal.timeout(1e4)
|
|
153590
|
+
});
|
|
153591
|
+
if (!response.ok) {
|
|
153592
|
+
log(`[magic-context] conflict-warning: DELETE failed status=${response.status} url=${url2}`);
|
|
153593
|
+
return false;
|
|
153594
|
+
}
|
|
153595
|
+
return true;
|
|
153596
|
+
} catch (error51) {
|
|
153597
|
+
log(`[magic-context] conflict-warning: DELETE error (url=${serverUrl}): ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
153598
|
+
return false;
|
|
153599
|
+
}
|
|
153600
|
+
}
|
|
153601
|
+
function getServerAuth() {
|
|
153602
|
+
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
153603
|
+
if (!password)
|
|
153604
|
+
return;
|
|
153605
|
+
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
|
|
153606
|
+
return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
|
|
153607
|
+
}
|
|
153608
|
+
async function getSessionMessages(client, sessionId) {
|
|
153609
|
+
try {
|
|
153610
|
+
const c = client;
|
|
153611
|
+
if (typeof c.session?.messages === "function") {
|
|
153612
|
+
const result = await c.session.messages({
|
|
153613
|
+
path: { id: sessionId },
|
|
153614
|
+
query: { limit: 50 }
|
|
153615
|
+
});
|
|
153616
|
+
return result?.data ?? [];
|
|
153617
|
+
}
|
|
153618
|
+
} catch (error51) {
|
|
153619
|
+
log(`[magic-context] conflict-warning: failed to read messages: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
153620
|
+
}
|
|
153621
|
+
return [];
|
|
153622
|
+
}
|
|
153623
|
+
async function sendConflictWarning(client, directory, conflictResult) {
|
|
153624
|
+
const { sessionId } = getDesktopState(directory);
|
|
153625
|
+
if (!sessionId) {
|
|
153626
|
+
log("[magic-context] conflict-warning: could not find active session for Desktop warning");
|
|
153627
|
+
return;
|
|
153628
|
+
}
|
|
153629
|
+
const warningText = formatConflictShort(conflictResult);
|
|
153630
|
+
log(`[magic-context] sending conflict warning to session ${sessionId}: ${conflictResult.reasons.join(", ")}`);
|
|
153631
|
+
try {
|
|
153632
|
+
const c = client;
|
|
153633
|
+
const promptInput = {
|
|
153634
|
+
path: { id: sessionId },
|
|
153635
|
+
body: {
|
|
153636
|
+
noReply: true,
|
|
153637
|
+
parts: [
|
|
153638
|
+
{
|
|
153639
|
+
type: "text",
|
|
153640
|
+
text: warningText,
|
|
153641
|
+
ignored: true
|
|
153642
|
+
}
|
|
153643
|
+
]
|
|
153644
|
+
}
|
|
153645
|
+
};
|
|
153646
|
+
if (typeof c.session?.prompt === "function") {
|
|
153647
|
+
await Promise.resolve(c.session.prompt(promptInput));
|
|
153648
|
+
} else if (typeof c.session?.promptAsync === "function") {
|
|
153649
|
+
await c.session.promptAsync(promptInput);
|
|
153650
|
+
} else {
|
|
153651
|
+
log("[magic-context] conflict-warning: session prompt API unavailable");
|
|
153652
|
+
}
|
|
153653
|
+
} catch (error51) {
|
|
153654
|
+
log(`[magic-context] conflict-warning: failed to send: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
153655
|
+
}
|
|
153656
|
+
}
|
|
153657
|
+
async function cleanupConflictWarnings(client, directory, serverUrl) {
|
|
153658
|
+
const { sessionId } = getDesktopState(directory);
|
|
153659
|
+
if (!sessionId) {
|
|
153660
|
+
log("[magic-context] cleanup: no active Desktop session found");
|
|
153661
|
+
return;
|
|
153662
|
+
}
|
|
153663
|
+
const messages = await getSessionMessages(client, sessionId);
|
|
153664
|
+
if (messages.length === 0)
|
|
153665
|
+
return;
|
|
153666
|
+
const warningMessageIds = [];
|
|
153667
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
153668
|
+
const msg = messages[i];
|
|
153669
|
+
const msgId = msg.info?.id;
|
|
153670
|
+
const msgRole = msg.info?.role;
|
|
153671
|
+
if (!msgId || msgRole !== "user")
|
|
153672
|
+
break;
|
|
153673
|
+
const parts = msg.parts ?? [];
|
|
153674
|
+
const isWarning = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(CONFLICT_WARNING_MARKER));
|
|
153675
|
+
if (isWarning) {
|
|
153676
|
+
warningMessageIds.push(msgId);
|
|
153677
|
+
} else {
|
|
153678
|
+
break;
|
|
153679
|
+
}
|
|
153680
|
+
}
|
|
153681
|
+
if (warningMessageIds.length === 0) {
|
|
153682
|
+
await cleanupEnabledMessages(messages, serverUrl, sessionId);
|
|
153683
|
+
return;
|
|
153684
|
+
}
|
|
153685
|
+
if (!serverUrl) {
|
|
153686
|
+
log("[magic-context] cleanup: no serverUrl provided, cannot delete messages");
|
|
153687
|
+
return;
|
|
153688
|
+
}
|
|
153689
|
+
log(`[magic-context] cleaning up ${warningMessageIds.length} conflict warning message(s) from session ${sessionId}`);
|
|
153690
|
+
for (const messageId of warningMessageIds) {
|
|
153691
|
+
const ok = await deleteMessage(serverUrl, sessionId, messageId);
|
|
153692
|
+
if (ok) {
|
|
153693
|
+
log(`[magic-context] deleted conflict warning message ${messageId}`);
|
|
153694
|
+
}
|
|
153695
|
+
}
|
|
153696
|
+
const enabledText = `${ENABLED_MARKER}. Enjoy! ✨`;
|
|
153697
|
+
try {
|
|
153698
|
+
const c = client;
|
|
153699
|
+
const promptInput = {
|
|
153700
|
+
path: { id: sessionId },
|
|
153701
|
+
body: {
|
|
153702
|
+
noReply: true,
|
|
153703
|
+
parts: [{ type: "text", text: enabledText, ignored: true }]
|
|
153704
|
+
}
|
|
153705
|
+
};
|
|
153706
|
+
if (typeof c.session?.prompt === "function") {
|
|
153707
|
+
await Promise.resolve(c.session.prompt(promptInput));
|
|
153708
|
+
} else if (typeof c.session?.promptAsync === "function") {
|
|
153709
|
+
await c.session.promptAsync(promptInput);
|
|
153710
|
+
}
|
|
153711
|
+
} catch {}
|
|
153712
|
+
setTimeout(async () => {
|
|
153713
|
+
try {
|
|
153714
|
+
const freshMessages = await getSessionMessages(client, sessionId);
|
|
153715
|
+
for (let i = freshMessages.length - 1;i >= 0; i--) {
|
|
153716
|
+
const msg = freshMessages[i];
|
|
153717
|
+
const msgId = msg.info?.id;
|
|
153718
|
+
const msgRole = msg.info?.role;
|
|
153719
|
+
if (!msgId || msgRole !== "user")
|
|
153720
|
+
break;
|
|
153721
|
+
const parts = msg.parts ?? [];
|
|
153722
|
+
const isEnabled = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(ENABLED_MARKER));
|
|
153723
|
+
if (isEnabled) {
|
|
153724
|
+
await deleteMessage(serverUrl, sessionId, msgId);
|
|
153725
|
+
} else {
|
|
153726
|
+
break;
|
|
153727
|
+
}
|
|
153728
|
+
}
|
|
153729
|
+
} catch {}
|
|
153730
|
+
}, 1000);
|
|
153731
|
+
}
|
|
153732
|
+
async function cleanupEnabledMessages(messages, serverUrl, sessionId) {
|
|
153733
|
+
if (!serverUrl)
|
|
153734
|
+
return;
|
|
153735
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
153736
|
+
const msg = messages[i];
|
|
153737
|
+
const msgId = msg.info?.id;
|
|
153738
|
+
const msgRole = msg.info?.role;
|
|
153739
|
+
if (!msgId || msgRole !== "user")
|
|
153740
|
+
break;
|
|
153741
|
+
const parts = msg.parts ?? [];
|
|
153742
|
+
const isEnabled = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(ENABLED_MARKER));
|
|
153743
|
+
if (isEnabled) {
|
|
153744
|
+
await deleteMessage(serverUrl, sessionId, msgId);
|
|
153745
|
+
} else {
|
|
153746
|
+
break;
|
|
153747
|
+
}
|
|
153748
|
+
}
|
|
153749
|
+
}
|
|
153750
|
+
async function sendTuiSetupNotification(client, directory, serverUrl) {
|
|
153751
|
+
const { sessionId } = getDesktopState(directory);
|
|
153752
|
+
if (!sessionId)
|
|
153753
|
+
return;
|
|
153754
|
+
const text = [
|
|
153755
|
+
`${TUI_SETUP_MARKER}`,
|
|
153756
|
+
"",
|
|
153757
|
+
"Magic Context added its TUI plugin to your tui.json.",
|
|
153758
|
+
"Restart OpenCode to see the sidebar with live context breakdown,",
|
|
153759
|
+
"token usage, historian status, memory counts, and more."
|
|
153760
|
+
].join(`
|
|
153761
|
+
`);
|
|
153762
|
+
try {
|
|
153763
|
+
const c = client;
|
|
153764
|
+
const promptInput = {
|
|
153765
|
+
path: { id: sessionId },
|
|
153766
|
+
body: {
|
|
153767
|
+
noReply: true,
|
|
153768
|
+
parts: [{ type: "text", text, ignored: true }]
|
|
153769
|
+
}
|
|
153770
|
+
};
|
|
153771
|
+
if (typeof c.session?.prompt === "function") {
|
|
153772
|
+
await Promise.resolve(c.session.prompt(promptInput));
|
|
153773
|
+
} else if (typeof c.session?.promptAsync === "function") {
|
|
153774
|
+
await c.session.promptAsync(promptInput);
|
|
153775
|
+
}
|
|
153776
|
+
} catch {
|
|
153777
|
+
return;
|
|
153778
|
+
}
|
|
153779
|
+
if (!serverUrl)
|
|
153780
|
+
return;
|
|
153781
|
+
setTimeout(async () => {
|
|
153782
|
+
try {
|
|
153783
|
+
const msgs = await getSessionMessages(client, sessionId);
|
|
153784
|
+
for (let i = msgs.length - 1;i >= 0; i--) {
|
|
153785
|
+
const msg = msgs[i];
|
|
153786
|
+
const msgId = msg.info?.id;
|
|
153787
|
+
if (!msgId || msg.info?.role !== "user")
|
|
153788
|
+
break;
|
|
153789
|
+
const parts = msg.parts ?? [];
|
|
153790
|
+
const isTuiSetup = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(TUI_SETUP_MARKER));
|
|
153791
|
+
if (isTuiSetup) {
|
|
153792
|
+
await deleteMessage(serverUrl, sessionId, msgId);
|
|
153793
|
+
} else {
|
|
153794
|
+
break;
|
|
153795
|
+
}
|
|
153796
|
+
}
|
|
153797
|
+
} catch {}
|
|
153798
|
+
}, 1000);
|
|
153799
|
+
}
|
|
153800
|
+
async function sendStartupAnnouncement(client, directory, version2, features, footer, markSeen) {
|
|
153801
|
+
if (!version2 || features.length === 0)
|
|
153802
|
+
return;
|
|
153803
|
+
const { sessionId } = getDesktopState(directory);
|
|
153804
|
+
if (!sessionId) {
|
|
153805
|
+
return;
|
|
153806
|
+
}
|
|
153807
|
+
const bullets = features.map((line) => ` • ${line}`).join(`
|
|
153808
|
+
`);
|
|
153809
|
+
const sections = [`${ANNOUNCEMENT_MARKER} v${version2}:`, "", bullets];
|
|
153810
|
+
if (footer && footer.trim().length > 0) {
|
|
153811
|
+
sections.push("", footer);
|
|
153812
|
+
}
|
|
153813
|
+
const text = sections.join(`
|
|
153814
|
+
`);
|
|
153815
|
+
log(`[magic-context] sending startup announcement for v${version2} to session ${sessionId}`);
|
|
153816
|
+
try {
|
|
153817
|
+
const c = client;
|
|
153818
|
+
const promptInput = {
|
|
153819
|
+
path: { id: sessionId },
|
|
153820
|
+
body: {
|
|
153821
|
+
noReply: true,
|
|
153822
|
+
parts: [{ type: "text", text, ignored: true }]
|
|
153823
|
+
}
|
|
153824
|
+
};
|
|
153825
|
+
if (typeof c.session?.prompt === "function") {
|
|
153826
|
+
await Promise.resolve(c.session.prompt(promptInput));
|
|
153827
|
+
} else if (typeof c.session?.promptAsync === "function") {
|
|
153828
|
+
await c.session.promptAsync(promptInput);
|
|
153829
|
+
} else {
|
|
153830
|
+
log("[magic-context] announcement: session prompt API unavailable");
|
|
153831
|
+
return;
|
|
153832
|
+
}
|
|
153833
|
+
} catch (error51) {
|
|
153834
|
+
log(`[magic-context] announcement: failed to send: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
153835
|
+
return;
|
|
153836
|
+
}
|
|
153837
|
+
markSeen(version2);
|
|
153838
|
+
}
|
|
153839
|
+
var CONFLICT_WARNING_MARKER = "⚠️ Magic Context is disabled due to conflicting configuration:", ENABLED_MARKER = "✨ Magic Context is now enabled", TUI_SETUP_MARKER = "\uD83D\uDCCA Magic Context sidebar configured", ANNOUNCEMENT_MARKER = "✨ Magic Context — what's new in", cachedDesktopStateByDir;
|
|
153840
|
+
var init_conflict_warning_hook = __esm(() => {
|
|
153841
|
+
init_conflict_detector();
|
|
153842
|
+
init_logger();
|
|
153843
|
+
cachedDesktopStateByDir = new Map;
|
|
153844
|
+
});
|
|
153845
|
+
|
|
152942
153846
|
// ../../node_modules/.bun/esprima@4.0.1/node_modules/esprima/dist/esprima.js
|
|
152943
153847
|
var require_esprima = __commonJS((exports, module) => {
|
|
152944
153848
|
(function webpackUniversalModuleDefinition(root, factory) {
|
|
@@ -160546,10 +161450,10 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
160546
161450
|
replacer = null;
|
|
160547
161451
|
indent = EMPTY;
|
|
160548
161452
|
};
|
|
160549
|
-
var
|
|
161453
|
+
var join11 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
|
|
160550
161454
|
var join_content = (inside, value, gap) => {
|
|
160551
161455
|
const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
|
|
160552
|
-
return
|
|
161456
|
+
return join11(comment, inside, gap);
|
|
160553
161457
|
};
|
|
160554
161458
|
var stringify_string = (holder, key, value) => {
|
|
160555
161459
|
const raw = get_raw_string_literal(holder, key);
|
|
@@ -160571,13 +161475,13 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
160571
161475
|
if (i !== 0) {
|
|
160572
161476
|
inside += COMMA;
|
|
160573
161477
|
}
|
|
160574
|
-
const before =
|
|
161478
|
+
const before = join11(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
|
|
160575
161479
|
inside += before || LF + deeper_gap;
|
|
160576
161480
|
inside += stringify(i, value, deeper_gap) || STR_NULL;
|
|
160577
161481
|
inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
|
|
160578
161482
|
after_comma = process_comments(value, AFTER(i), deeper_gap);
|
|
160579
161483
|
}
|
|
160580
|
-
inside +=
|
|
161484
|
+
inside += join11(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
|
|
160581
161485
|
return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
|
|
160582
161486
|
};
|
|
160583
161487
|
var object_stringify = (value, gap) => {
|
|
@@ -160598,13 +161502,13 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
160598
161502
|
inside += COMMA;
|
|
160599
161503
|
}
|
|
160600
161504
|
first = false;
|
|
160601
|
-
const before =
|
|
161505
|
+
const before = join11(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
|
|
160602
161506
|
inside += before || LF + deeper_gap;
|
|
160603
161507
|
inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
|
|
160604
161508
|
after_comma = process_comments(value, AFTER(key), deeper_gap);
|
|
160605
161509
|
};
|
|
160606
161510
|
keys.forEach(iteratee);
|
|
160607
|
-
inside +=
|
|
161511
|
+
inside += join11(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
|
|
160608
161512
|
return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
|
|
160609
161513
|
};
|
|
160610
161514
|
function stringify(key, holder, gap) {
|
|
@@ -160696,907 +161600,6 @@ var require_src2 = __commonJS((exports, module) => {
|
|
|
160696
161600
|
};
|
|
160697
161601
|
});
|
|
160698
161602
|
|
|
160699
|
-
// src/hooks/magic-context/compartment-prompt.ts
|
|
160700
|
-
function buildHistorianEditorPrompt(draft) {
|
|
160701
|
-
return [
|
|
160702
|
-
"This is a historian draft. Clean it up following the rules in your system prompt.",
|
|
160703
|
-
"",
|
|
160704
|
-
"<draft>",
|
|
160705
|
-
draft,
|
|
160706
|
-
"</draft>",
|
|
160707
|
-
"",
|
|
160708
|
-
"Return the cleaned draft as valid XML matching the original structure."
|
|
160709
|
-
].join(`
|
|
160710
|
-
`);
|
|
160711
|
-
}
|
|
160712
|
-
function buildCompressorPrompt(compartments, currentTokens, targetTokens, outputDepth, outputCount) {
|
|
160713
|
-
const lines = [];
|
|
160714
|
-
const densityLabel = outputDepth === 1 ? "MERGE ONLY" : outputDepth === 2 ? "LITE TIGHTEN" : outputDepth === 3 ? "FULL CONDENSE" : "ULTRA TELEGRAPH";
|
|
160715
|
-
const resolvedOutputCount = outputCount ?? Math.max(1, Math.ceil(compartments.length / 2));
|
|
160716
|
-
lines.push(`Density target: LEVEL ${outputDepth} (${densityLabel}). See system prompt for level rules.`);
|
|
160717
|
-
lines.push(`Input: ${compartments.length} compartments, ~${currentTokens} tokens. Target output: exactly ${resolvedOutputCount} compartments, ~${targetTokens} tokens total.`);
|
|
160718
|
-
lines.push("");
|
|
160719
|
-
if (outputDepth === 1) {
|
|
160720
|
-
lines.push("Merge only. Preserve narrative and all U: lines. Drop only genuine duplicate sentences spanning compartments.");
|
|
160721
|
-
} else if (outputDepth === 2) {
|
|
160722
|
-
lines.push("Merge + drop filler words and hedging. Keep grammar, keep U: lines verbatim.");
|
|
160723
|
-
} else if (outputDepth === 3) {
|
|
160724
|
-
lines.push("Merge into single-paragraph compartments. Drop articles and weak auxiliaries. Keep only IRREPLACEABLE U: lines.");
|
|
160725
|
-
} else {
|
|
160726
|
-
lines.push("Merge into telegraphic fragments with symbol connectives (→ + // |). U: lines only if truly irreplaceable.");
|
|
160727
|
-
}
|
|
160728
|
-
lines.push("");
|
|
160729
|
-
lines.push("Preserved literally at all levels: commit hashes, file paths, URLs, code spans.");
|
|
160730
|
-
lines.push("");
|
|
160731
|
-
for (const c of compartments) {
|
|
160732
|
-
lines.push(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr(c.title)}">`);
|
|
160733
|
-
lines.push(escapeXmlContent(c.content));
|
|
160734
|
-
lines.push("</compartment>");
|
|
160735
|
-
lines.push("");
|
|
160736
|
-
}
|
|
160737
|
-
lines.push("Return merged compartments as XML.");
|
|
160738
|
-
return lines.join(`
|
|
160739
|
-
`);
|
|
160740
|
-
}
|
|
160741
|
-
function buildCompartmentAgentPrompt(existingState, inputSource, options) {
|
|
160742
|
-
const existingStateBlock = options?.stateFilePath ? `Read the existing session state from this file before proceeding:
|
|
160743
|
-
${options.stateFilePath}
|
|
160744
|
-
|
|
160745
|
-
The file contains the full XML existing state (compartments, facts, memories). Read it first, then process the new messages below.` : existingState;
|
|
160746
|
-
return [
|
|
160747
|
-
"Existing state (read-only context for continuity and fact normalization — do NOT re-emit these compartments):",
|
|
160748
|
-
existingStateBlock,
|
|
160749
|
-
"",
|
|
160750
|
-
"<new_messages>",
|
|
160751
|
-
inputSource,
|
|
160752
|
-
"</new_messages>",
|
|
160753
|
-
"",
|
|
160754
|
-
"Instructions:",
|
|
160755
|
-
"- Return ONLY new compartments for the messages inside <new_messages>, plus the full normalized fact list.",
|
|
160756
|
-
"- Use the exact absolute raw ordinals from the input ranges for every compartment start/end and for <unprocessed_from>.",
|
|
160757
|
-
"- Rewrite every fact into terse, present-tense operational form. Merge semantic duplicates within each category.",
|
|
160758
|
-
"- Drop any session fact already covered by a project memory in the existing state.",
|
|
160759
|
-
"- Do not preserve prior narrative wording verbatim; if a fact is already canonical and still correct, keep or lightly normalize it.",
|
|
160760
|
-
"- Drop obsolete or task-local facts.",
|
|
160761
|
-
...options?.userMemoriesEnabled ? [
|
|
160762
|
-
"- Also emit <user_observations> with universal behavioral observations about the user (see system prompt for rules)."
|
|
160763
|
-
] : []
|
|
160764
|
-
].join(`
|
|
160765
|
-
`);
|
|
160766
|
-
}
|
|
160767
|
-
var COMPARTMENT_AGENT_SYSTEM_PROMPT = `You condense long AI coding sessions into two outputs:
|
|
160768
|
-
|
|
160769
|
-
1. compartments: completed logical work units
|
|
160770
|
-
2. facts: persistent cross-cutting information for future work
|
|
160771
|
-
|
|
160772
|
-
Compartment rules:
|
|
160773
|
-
- A compartment is one contiguous completed work unit: investigation, fix, refactor, docs update, feature, or decision.
|
|
160774
|
-
- Start a new compartment only when the work clearly pivots to a different objective.
|
|
160775
|
-
- If one broad effort contains multiple completed sub-pivots with distinct outcomes, prefer multiple smaller compartments over one umbrella compartment with many U: lines.
|
|
160776
|
-
- Do not create compartments for magic-context commands or tool-only noise.
|
|
160777
|
-
- If the input ends mid-topic, leave it out and report its first message index in <unprocessed_from>.
|
|
160778
|
-
- All compartment start/end ordinals and <unprocessed_from> must use the absolute raw message numbers shown in the input. Never renumber relative to this chunk.
|
|
160779
|
-
- Every displayed raw message ordinal in the input MUST appear in exactly one compartment. Gaps between compartments are invalid. When a displayed block is pure tool noise (e.g. a long "TC: ..." run with no narrative text), do NOT skip it — extend the preceding compartment's \`end\` to absorb the range, or include it inside the current compartment if the block falls within an ongoing work unit. Never create a dedicated compartment just to cover a tool-only run.
|
|
160780
|
-
- Only emit NEW compartments for the new messages. Do not re-emit existing compartments from the existing state.
|
|
160781
|
-
- Write comprehensive, detailed compartments. Include file paths, function names, commit hashes, config keys, and values when they matter.
|
|
160782
|
-
- Do not list every changed file. Do not narrate tool calls. Do not preserve dead-end exploration beyond a brief clause when needed.
|
|
160783
|
-
|
|
160784
|
-
# Construction order (MANDATORY)
|
|
160785
|
-
|
|
160786
|
-
For each compartment, build in this exact order:
|
|
160787
|
-
|
|
160788
|
-
1. Write the narrative summary first — what was done, why, and the outcome. This is 1–4 sentences covering the work unit completely.
|
|
160789
|
-
2. Re-read your narrative. Ask: does the summary already convey all important decisions and constraints from this work unit?
|
|
160790
|
-
3. If yes, the compartment is DONE with zero U: lines. Move on.
|
|
160791
|
-
4. If no, identify the specific signal the narrative cannot capture. Add U: lines ONLY for those signals.
|
|
160792
|
-
5. Before writing each U: line, run the CROSS-COMPARTMENT CHECK (see below).
|
|
160793
|
-
|
|
160794
|
-
Zero U: lines in a compartment is normal and expected. Most compartments should have 0–2 U: lines. Compartments with 3–5 are rare and must be justified by genuinely distinct durable signals.
|
|
160795
|
-
|
|
160796
|
-
# DROP rules (check these first — if any match, drop without exception)
|
|
160797
|
-
|
|
160798
|
-
- Questions in ANY form: "should I X?", "what about Y?", "do you think Z?", "isn't it better to A?", "why don't we B?", "any ideas?" — the resolved answer belongs in narrative only. If it feels important to keep the question, you are wrong: keep the answer in narrative.
|
|
160799
|
-
- Agreements and acknowledgments: "yes", "okay", "sure", "thanks", "go ahead", "looks good", "perfect", "I agree", "sounds good", "great".
|
|
160800
|
-
- Pure pacing and sequencing: "let's start", "continue", "let's do all", "now we can X", "let's commit", "first do A then B", "before that", "in the meantime".
|
|
160801
|
-
- Tactical observations: "I just noticed X", "we recently did Y", "I'm seeing Z right now", "this seems wrong".
|
|
160802
|
-
- Debugging status: "context is at 78%", "I'm restarting", "the last build failed".
|
|
160803
|
-
- Dogfooding/restart loops: "I restarted, can you check?", "okay we should have updated versions now", "let me try again".
|
|
160804
|
-
- Pasted error output or logs as U: line — capture the underlying problem in narrative, not the raw paste.
|
|
160805
|
-
- Examples and illustrations: "mine was when an agent wants to see X" — convert the underlying intent into a directive or drop.
|
|
160806
|
-
- Hype with embedded directive: ALL-CAPS pleas, "PLEASE PLEASE PLEASE just do X" — extract only the underlying directive into narrative; drop the hype.
|
|
160807
|
-
- Social signals, banter, emoji-only, enthusiasm.
|
|
160808
|
-
- Deferred ideas: "for later", "we can do X later", "another idea for the future".
|
|
160809
|
-
- Mid-process status: "running Y", "checking Z".
|
|
160810
|
-
- Superseded drafts once a later message gives the final decision.
|
|
160811
|
-
- Standing workflow rules ("always run lint before push") — these belong in WORKFLOW_RULES facts, not U: lines.
|
|
160812
|
-
|
|
160813
|
-
# Wording rule (default: verbatim)
|
|
160814
|
-
|
|
160815
|
-
By default, U: lines use the user's actual wording. The user's exact phrasing often carries negotiation context, emphasis, or technical specificity that paraphrase loses.
|
|
160816
|
-
|
|
160817
|
-
Paraphrase ONLY in these cases:
|
|
160818
|
-
- **Strip agreement prefixes**: "Yes X", "Okay X", "Sure X" → keep only the substantive part of X, in the user's original wording.
|
|
160819
|
-
- **Split compound directives**: If one message contains two distinct durable directives, split into two U: lines — each preserving the user's wording for its part.
|
|
160820
|
-
- **Drop conversational noise, keep core**: If a message wraps a directive in exploratory phrasing ("so I was thinking, maybe... but actually..."), drop the exploration and keep the core directive in the user's remaining words. Don't invent new phrasing.
|
|
160821
|
-
|
|
160822
|
-
NEVER:
|
|
160823
|
-
- Rewrite a clear user directive into a formal constraint statement. ("We need tool count at ~8" stays as-is; do NOT convert to "Tool count must be capped at 8.")
|
|
160824
|
-
- Synthesize a directive from multiple messages into one canonical statement. If the signal needs synthesis, it belongs in narrative, not a U: line.
|
|
160825
|
-
- Add technical specificity the user didn't state (file paths, function names, constant names). Canonical technical specificity belongs in narrative or facts, not in U: lines attributed to the user.
|
|
160826
|
-
|
|
160827
|
-
Good example:
|
|
160828
|
-
Original user message: "Yes let's do this. But we need to also make sure that we limit by message count as some sessions have quite a lot of messages."
|
|
160829
|
-
Correct U: line: "We need to also make sure that we limit by message count as some sessions have quite a lot of messages."
|
|
160830
|
-
(Stripped agreement prefix; kept the user's actual wording.)
|
|
160831
|
-
|
|
160832
|
-
Bad example (do not do this):
|
|
160833
|
-
Incorrect U: line: "Cap session history retrieval at a maximum message count to prevent memory issues on large sessions."
|
|
160834
|
-
(Rewrote the user directive into formal language and invented specificity.)
|
|
160835
|
-
|
|
160836
|
-
# KEEP rules (U: line survives only if ALL pass)
|
|
160837
|
-
|
|
160838
|
-
1. DURABLE: The signal matters after the immediate turn.
|
|
160839
|
-
2. SPECIFIC: Concrete goal, hard constraint, design decision, rejection, rationale, threshold, source-of-truth correction, or future-work directive.
|
|
160840
|
-
3. OUTCOME-BACKED: This compartment's narrative clearly states what was done, decided, or changed because of the message.
|
|
160841
|
-
4. NON-REDUNDANT: Not captured by another U: line (see CROSS-COMPARTMENT CHECK), by a fact, or by the narrative.
|
|
160842
|
-
5. IRREPLACEABLE: The user's wording adds signal that narrative paraphrase cannot preserve. If the same information could appear as narrative without losing meaning, it should.
|
|
160843
|
-
|
|
160844
|
-
Categories of KEEP:
|
|
160845
|
-
- Hard gates, thresholds, config defaults, percentages, byte sizes with concrete values.
|
|
160846
|
-
- Accepted designs and explicit decisions.
|
|
160847
|
-
- Rejections and negative constraints: "X is wrong because Y", "we should NOT do Z".
|
|
160848
|
-
- Source-of-truth corrections: "follow the code, not the README".
|
|
160849
|
-
- Implementation pivots stated in future tense: "instead of X let's do Y", "switch to Z".
|
|
160850
|
-
- Durable rationale that explains WHY an approach was chosen.
|
|
160851
|
-
- Specific feature requirements stated as durable goals.
|
|
160852
|
-
|
|
160853
|
-
# PIVOT vs OBSERVATION test
|
|
160854
|
-
|
|
160855
|
-
A pivot is FUTURE-TENSE and changes the plan: "instead of X, let's do Y", "switch this to Z", "actually, let's not do A".
|
|
160856
|
-
An observation is PAST-TENSE or PRESENT-TENSE and reports state: "we recently did X", "I just noticed Y", "this is broken right now".
|
|
160857
|
-
Observations may frame narrative context but are NOT pivots and NOT durable. Drop them as U: lines.
|
|
160858
|
-
|
|
160859
|
-
# CROSS-COMPARTMENT CHECK (forward-looking)
|
|
160860
|
-
|
|
160861
|
-
Before writing ANY U: line in the current compartment:
|
|
160862
|
-
1. Scan U: lines you have ALREADY written in previous compartments in this response.
|
|
160863
|
-
2. If any prior U: line expresses the same intent, decision, constraint, or rationale — even in different words — do NOT write the new U: line.
|
|
160864
|
-
3. Let the narrative in the current compartment carry the signal instead.
|
|
160865
|
-
|
|
160866
|
-
This is a forward operation: you only need to check what you already wrote, not revisit past compartments.
|
|
160867
|
-
|
|
160868
|
-
Examples of same-intent pairs to collapse:
|
|
160869
|
-
- "X shouldn't cause cache bust" + "X must not bust cache by itself" → keep only the first, in its original compartment.
|
|
160870
|
-
- "Let's use monorepo" + "Yes, monorepo is the right call" → keep only the first.
|
|
160871
|
-
- "Add logging" + "We need logs here too" → keep only the first.
|
|
160872
|
-
|
|
160873
|
-
Never keep two U: lines for the same underlying directive across compartments.
|
|
160874
|
-
|
|
160875
|
-
# Budget
|
|
160876
|
-
|
|
160877
|
-
- HARD LIMIT: 3–5 U: lines per compartment. 0–2 is typical.
|
|
160878
|
-
- If you have more than 5 candidate U: lines in one compartment, that is a signal to split into two compartments at a natural pivot, not to stuff more.
|
|
160879
|
-
- Every U: line must be immediately followed by 1–3 sentences describing the outcome, decision, or effect. Never stack two U: lines without intervening outcome text.
|
|
160880
|
-
|
|
160881
|
-
# Example: CORRECT preservation (narrative-first, verbatim U: line)
|
|
160882
|
-
|
|
160883
|
-
<compartment start="50" end="120" title="Built the auth layer">
|
|
160884
|
-
Implemented JWT auth with hard 60-minute exp claim and refresh-token rotation. Chose Bearer tokens over cookies after finding cookie-based auth broke the SPA flow. Added session_expiry config (read-only at runtime). Commits: a3f891, b22c4e.
|
|
160885
|
-
U: We need session expiry capped at 1 hour, no exceptions
|
|
160886
|
-
Hardcoded the 60-minute cap at the JWT-issuer layer so runtime overrides cannot extend it.
|
|
160887
|
-
</compartment>
|
|
160888
|
-
|
|
160889
|
-
Notice: only one U: line, kept verbatim from the user's actual message. The cookie-to-Bearer pivot is narrative because paraphrase captures the signal fully.
|
|
160890
|
-
|
|
160891
|
-
# Example: OVER-PRESERVATION (avoid)
|
|
160892
|
-
|
|
160893
|
-
<compartment start="200" end="350" title="Refactored data layer">
|
|
160894
|
-
U: Okay let's start on the data layer
|
|
160895
|
-
U: What about transactions?
|
|
160896
|
-
U: Yes that approach looks good
|
|
160897
|
-
U: Actually wait, maybe we need write-ahead logging
|
|
160898
|
-
U: I just noticed the previous commit broke a test
|
|
160899
|
-
U: Let's commit and ship it
|
|
160900
|
-
Refactored data layer with WAL mode and connection pooling.
|
|
160901
|
-
</compartment>
|
|
160902
|
-
|
|
160903
|
-
Problems: pacing, question, agreement, observation, pacing again. Only one message carries signal, and even that is narrative-capturable.
|
|
160904
|
-
|
|
160905
|
-
# CORRECT version of the above
|
|
160906
|
-
|
|
160907
|
-
<compartment start="200" end="350" title="Refactored data layer">
|
|
160908
|
-
Refactored data layer to use WAL mode plus connection pooling. Chose WAL over plain connections for concurrent read performance under sustained write load.
|
|
160909
|
-
</compartment>
|
|
160910
|
-
|
|
160911
|
-
Zero U: lines. The pivot to WAL is clear in narrative.
|
|
160912
|
-
|
|
160913
|
-
Fact rules:
|
|
160914
|
-
- Facts are editable state, not append-only notes. Rewrite, normalize, deduplicate, or drop existing facts whenever needed.
|
|
160915
|
-
- Before emitting any fact, check all existing facts in the same category for semantic duplicates. If two facts describe the same decision, constraint, or default with different wording, merge them into one canonical statement. Never emit two facts that could be answered by the same question.
|
|
160916
|
-
- When project memories are provided as read-only reference, drop any session fact that is already covered by a project memory. Project memories are the canonical cross-session source; session facts must not duplicate them.
|
|
160917
|
-
- Facts must be durable and actionable after the conversation ends.
|
|
160918
|
-
- A fact is either a stable invariant/default or a reusable operating rule. If it mainly explains what happened, it belongs in a compartment, not a fact.
|
|
160919
|
-
- Facts belong only in these categories when relevant: WORKFLOW_RULES, ARCHITECTURE_DECISIONS, CONSTRAINTS, CONFIG_DEFAULTS, KNOWN_ISSUES, ENVIRONMENT, NAMING, USER_PREFERENCES, USER_DIRECTIVES.
|
|
160920
|
-
- Keep only high-signal facts. Omit greetings, acknowledgements, temporary status, one-off sequencing, branch-local tactics, and task-local cleanup notes.
|
|
160921
|
-
- When a user message carries durable goals, constraints, preferences, or decision rationale, add a USER_DIRECTIVES fact when future agents should follow it after the session is compacted.
|
|
160922
|
-
- Do not turn task-local details into facts.
|
|
160923
|
-
- Do not keep stale facts. Rewrite or drop them even if the new input only implies they are obsolete.
|
|
160924
|
-
- Keep existing ARCHITECTURE_DECISIONS and CONSTRAINTS facts when they are still valid and uncontradicted; rewrite them into canonical form instead of dropping them.
|
|
160925
|
-
- Facts must be present tense and operational. Do not use chronology or provenance wording such as: initially, currently, remained, previously, later, then, was implemented, we changed, used to.
|
|
160926
|
-
- One fact bullet must contain exactly one rule/default/constraint/preference. If a candidate fact mixes history with guidance, keep the guidance and drop the history.
|
|
160927
|
-
- Durability test: a future agent should still act correctly on the fact next session, after merge/restart, without rereading the conversation.
|
|
160928
|
-
- Category guide:
|
|
160929
|
-
- WORKFLOW_RULES: standing repeatable process only. Prefer Do/When form: When <condition>, <action>. Do not store one-off branch strategy or task-specific sequencing unless it is standing policy.
|
|
160930
|
-
- ARCHITECTURE_DECISIONS: stable design choice. Use: <component> uses <choice> because <reason>.
|
|
160931
|
-
- CONSTRAINTS: hard must/must-not rule or invariant. Use: <thing> must/must not <action> because <reason>.
|
|
160932
|
-
- CONFIG_DEFAULTS: stable default only. Use: <key>=<value>.
|
|
160933
|
-
- KNOWN_ISSUES: unresolved recurring problem only. Do not store solved-issue stories.
|
|
160934
|
-
- ENVIRONMENT: stable setup fact that affects future work.
|
|
160935
|
-
- NAMING: canonical term choice. Use: Use <term>; avoid <term>.
|
|
160936
|
-
- USER_PREFERENCES: durable user preference. Prefer Do/When form.
|
|
160937
|
-
- USER_DIRECTIVES: durable user-stated goal, constraint, preference, or rationale. Keep the user's wording when it carries meaning, but narrow it to 1-3 sentences and remove filler.
|
|
160938
|
-
- Fact dedup examples:
|
|
160939
|
-
- These are DUPLICATES (merge into one): "Plugin config uses layered JSONC files" and "AFT plugin config uses layered JSONC files at ~/.config/opencode/aft.jsonc and <project>/.opencode/aft.jsonc, with project values deep-merging over user values." → keep the longer, more specific version only.
|
|
160940
|
-
- These are NOT duplicates (keep both): "AFT uses 1-based line numbers" and "AFT converts to LSP 0-based UTF-16 at the protocol boundary" → different aspects of the same system.
|
|
160941
|
-
- Fact rewrite examples:
|
|
160942
|
-
- Bad ARCHITECTURE_DECISIONS: The new tool-heavy \`ctx_reduce\` reminder was initially implemented as a hidden instruction appended to the latest user message in \`transform\`.
|
|
160943
|
-
- Good ARCHITECTURE_DECISIONS: \`ctx_reduce\` turn reminders are injected into the latest user message in \`transform\`.
|
|
160944
|
-
- Bad WORKFLOW_RULES: Current local workflow remained feat -> integrate -> build for code changes.
|
|
160945
|
-
- Good WORKFLOW_RULES (only if this is standing policy): For magic-context changes, commit on \`feat/magic-context\`, cherry-pick to \`integrate/athena-magic-context\`, run \`bun run build\` on integrate, then return to \`feat/magic-context\`.
|
|
160946
|
-
|
|
160947
|
-
Input notes:
|
|
160948
|
-
- [N] or [N-M] is a stable raw OpenCode message range.
|
|
160949
|
-
- U: means user.
|
|
160950
|
-
- A: means assistant.
|
|
160951
|
-
- TC: means tool call — a compact summary of what the agent did (e.g., "TC: Fix lint errors", "TC: read(src/index.ts)", "TC: grep(ctx_memory)"). TC lines appear when there is no text describing the action. Use them to understand what happened between text blocks, but do not copy them verbatim into compartments — incorporate their meaning into the narrative.
|
|
160952
|
-
- commits: ... on an assistant block lists commit hashes mentioned in that work unit; keep the relevant ones in the compartment summary when they matter.
|
|
160953
|
-
|
|
160954
|
-
Output valid XML only in this shape:
|
|
160955
|
-
<output>
|
|
160956
|
-
<compartments>
|
|
160957
|
-
<compartment start="FIRST" end="LAST" title="short title">
|
|
160958
|
-
U: Verbatim high-signal user message
|
|
160959
|
-
Summary text describing what was done and why.
|
|
160960
|
-
U: Another high-signal user message if applicable
|
|
160961
|
-
More summary text.
|
|
160962
|
-
</compartment>
|
|
160963
|
-
</compartments>
|
|
160964
|
-
<facts>
|
|
160965
|
-
<WORKFLOW_RULES>
|
|
160966
|
-
* Fact text
|
|
160967
|
-
</WORKFLOW_RULES>
|
|
160968
|
-
</facts>
|
|
160969
|
-
<meta>
|
|
160970
|
-
<messages_processed>FIRST-LAST</messages_processed>
|
|
160971
|
-
<unprocessed_from>INDEX</unprocessed_from>
|
|
160972
|
-
</meta>
|
|
160973
|
-
</output>
|
|
160974
|
-
|
|
160975
|
-
Omit empty fact categories. Compartments must be ordered, contiguous for the ranges they cover, and non-overlapping.`, HISTORIAN_EDITOR_SYSTEM_PROMPT = `You are an editor refining a historian draft. The draft was produced by a first-pass historian and may contain noise — low-signal U: lines, redundant quotes across compartments, and weak preservation decisions.
|
|
160976
|
-
|
|
160977
|
-
Your job is to clean the draft without changing its structure:
|
|
160978
|
-
|
|
160979
|
-
1. DROP low-signal U: lines:
|
|
160980
|
-
- Questions in any form — resolved decision goes in narrative only.
|
|
160981
|
-
- Pacing/agreement: "let's go", "yes", "okay", "sounds good", "I agree".
|
|
160982
|
-
- Pasted error output, debugging status, mid-process observations.
|
|
160983
|
-
- Tactical micro-direction: "now look at X", "first check Y".
|
|
160984
|
-
|
|
160985
|
-
2. DROP cross-compartment duplicates:
|
|
160986
|
-
- Scan U: lines across ALL compartments in the draft.
|
|
160987
|
-
- If two U: lines express the same intent/decision, keep only ONE — in the compartment where the outcome is actually described.
|
|
160988
|
-
|
|
160989
|
-
3. STRIP agreement prefixes:
|
|
160990
|
-
- "Yes we should X" → keep only the directive content, or drop entirely if nothing substantive remains after "Yes".
|
|
160991
|
-
|
|
160992
|
-
4. PREFER verbatim over paraphrase:
|
|
160993
|
-
- If the draft rephrased a user directive into formal constraint language, restore the user's wording if available.
|
|
160994
|
-
- Do not invent technical specificity (file paths, function names, constants) the user did not state.
|
|
160995
|
-
|
|
160996
|
-
5. FOLD into narrative when possible:
|
|
160997
|
-
- If a U: line's signal is already captured in the surrounding narrative, drop the U: line.
|
|
160998
|
-
- Narrative should not need the U: line to be understood.
|
|
160999
|
-
|
|
161000
|
-
6. KEEP as U: lines ONLY:
|
|
161001
|
-
- Hard constraints with concrete values (thresholds, byte sizes, timeouts).
|
|
161002
|
-
- Explicit rejections ("X is wrong because Y", "NOT Z").
|
|
161003
|
-
- Implementation pivots in future-tense ("instead of A, do B").
|
|
161004
|
-
- Source-of-truth corrections.
|
|
161005
|
-
|
|
161006
|
-
Do NOT change:
|
|
161007
|
-
- Compartment titles, ranges, or ordering.
|
|
161008
|
-
- Narrative summary text unless it directly references a U: line you dropped (in which case integrate the signal into the narrative).
|
|
161009
|
-
- Facts — leave the facts section untouched.
|
|
161010
|
-
- <meta> section — leave messages_processed and unprocessed_from exactly as the draft has them.
|
|
161011
|
-
|
|
161012
|
-
Output the cleaned version as valid XML matching the original structure. Preserve all XML tags, compartment ranges, meta, and facts.`, USER_OBSERVATIONS_APPENDIX = `
|
|
161013
|
-
|
|
161014
|
-
User observation rules (EXPERIMENTAL):
|
|
161015
|
-
- After outputting compartments and facts, also output a <user_observations> section.
|
|
161016
|
-
- User observations capture UNIVERSAL behavioral patterns about the human user — not project-specific or technical.
|
|
161017
|
-
- Good observations: communication preferences, review focus areas, expertise level, decision-making patterns, frustration triggers, working style.
|
|
161018
|
-
- Bad observations (DO NOT emit): project-specific preferences, framework choices, coding language preferences, one-off moods, task-local frustration.
|
|
161019
|
-
- Each observation must be a single concise sentence in present tense.
|
|
161020
|
-
- Only emit observations you have strong evidence for from the conversation. Do not speculate.
|
|
161021
|
-
- Emit 0-5 observations per run. Zero is fine if nothing stands out.
|
|
161022
|
-
- The output shape inside <output> gains an additional section:
|
|
161023
|
-
<user_observations>
|
|
161024
|
-
* User prefers terse communication and dislikes verbose explanations.
|
|
161025
|
-
* User is technically deep — understands cache invalidation, SQLite internals, and prompt engineering.
|
|
161026
|
-
</user_observations>
|
|
161027
|
-
If no observations, omit the <user_observations> section entirely.`;
|
|
161028
|
-
var init_compartment_prompt = __esm(() => {
|
|
161029
|
-
init_compartment_storage();
|
|
161030
|
-
});
|
|
161031
|
-
|
|
161032
|
-
// src/shared/opencode-config-dir.ts
|
|
161033
|
-
import { homedir as homedir6 } from "node:os";
|
|
161034
|
-
import { join as join12, resolve as resolve4 } from "node:path";
|
|
161035
|
-
function getCliConfigDir() {
|
|
161036
|
-
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
161037
|
-
if (envConfigDir) {
|
|
161038
|
-
return resolve4(envConfigDir);
|
|
161039
|
-
}
|
|
161040
|
-
if (process.platform === "win32") {
|
|
161041
|
-
return join12(homedir6(), ".config", "opencode");
|
|
161042
|
-
}
|
|
161043
|
-
return join12(process.env.XDG_CONFIG_HOME || join12(homedir6(), ".config"), "opencode");
|
|
161044
|
-
}
|
|
161045
|
-
function getOpenCodeConfigDir(_options) {
|
|
161046
|
-
return getCliConfigDir();
|
|
161047
|
-
}
|
|
161048
|
-
function getOpenCodeConfigPaths(options) {
|
|
161049
|
-
const configDir = getOpenCodeConfigDir(options);
|
|
161050
|
-
return {
|
|
161051
|
-
configDir,
|
|
161052
|
-
configJson: join12(configDir, "opencode.json"),
|
|
161053
|
-
configJsonc: join12(configDir, "opencode.jsonc"),
|
|
161054
|
-
packageJson: join12(configDir, "package.json"),
|
|
161055
|
-
omoConfig: join12(configDir, "magic-context.jsonc")
|
|
161056
|
-
};
|
|
161057
|
-
}
|
|
161058
|
-
var init_opencode_config_dir = () => {};
|
|
161059
|
-
|
|
161060
|
-
// src/shared/conflict-detector.ts
|
|
161061
|
-
import { join as join13 } from "node:path";
|
|
161062
|
-
function detectConflicts(directory) {
|
|
161063
|
-
const conflicts = {
|
|
161064
|
-
compactionAuto: false,
|
|
161065
|
-
compactionPrune: false,
|
|
161066
|
-
dcpPlugin: false,
|
|
161067
|
-
omoPreemptiveCompaction: false,
|
|
161068
|
-
omoContextWindowMonitor: false,
|
|
161069
|
-
omoAnthropicRecovery: false
|
|
161070
|
-
};
|
|
161071
|
-
const reasons = [];
|
|
161072
|
-
const compactionResult = checkCompaction(directory);
|
|
161073
|
-
if (compactionResult.auto) {
|
|
161074
|
-
conflicts.compactionAuto = true;
|
|
161075
|
-
reasons.push("OpenCode auto-compaction is enabled (compaction.auto=true)");
|
|
161076
|
-
}
|
|
161077
|
-
if (compactionResult.prune) {
|
|
161078
|
-
conflicts.compactionPrune = true;
|
|
161079
|
-
reasons.push("OpenCode prune is enabled (compaction.prune=true)");
|
|
161080
|
-
}
|
|
161081
|
-
const dcpFound = checkDcpPlugin(directory);
|
|
161082
|
-
if (dcpFound) {
|
|
161083
|
-
conflicts.dcpPlugin = true;
|
|
161084
|
-
reasons.push("opencode-dcp plugin is installed — it conflicts with Magic Context's context management");
|
|
161085
|
-
}
|
|
161086
|
-
const omoResult = checkOmoHooks(directory);
|
|
161087
|
-
if (omoResult.preemptiveCompaction) {
|
|
161088
|
-
conflicts.omoPreemptiveCompaction = true;
|
|
161089
|
-
reasons.push("oh-my-opencode preemptive-compaction hook is active — it triggers compaction that conflicts with historian");
|
|
161090
|
-
}
|
|
161091
|
-
if (omoResult.contextWindowMonitor) {
|
|
161092
|
-
conflicts.omoContextWindowMonitor = true;
|
|
161093
|
-
reasons.push("oh-my-opencode context-window-monitor hook is active — it injects usage warnings that overlap with Magic Context nudges");
|
|
161094
|
-
}
|
|
161095
|
-
if (omoResult.anthropicRecovery) {
|
|
161096
|
-
conflicts.omoAnthropicRecovery = true;
|
|
161097
|
-
reasons.push("oh-my-opencode anthropic-context-window-limit-recovery hook is active — it triggers emergency compaction that bypasses historian");
|
|
161098
|
-
}
|
|
161099
|
-
return {
|
|
161100
|
-
hasConflict: reasons.length > 0,
|
|
161101
|
-
reasons,
|
|
161102
|
-
conflicts
|
|
161103
|
-
};
|
|
161104
|
-
}
|
|
161105
|
-
function checkCompaction(directory) {
|
|
161106
|
-
if (process.env.OPENCODE_DISABLE_AUTOCOMPACT) {
|
|
161107
|
-
return { auto: false, prune: false };
|
|
161108
|
-
}
|
|
161109
|
-
const projectResult = readProjectCompaction(directory);
|
|
161110
|
-
if (projectResult.resolved)
|
|
161111
|
-
return projectResult;
|
|
161112
|
-
const userResult = readUserCompaction();
|
|
161113
|
-
if (userResult.resolved)
|
|
161114
|
-
return userResult;
|
|
161115
|
-
return { auto: true, prune: false };
|
|
161116
|
-
}
|
|
161117
|
-
function readProjectCompaction(directory) {
|
|
161118
|
-
const dotOcJsonc = join13(directory, ".opencode", "opencode.jsonc");
|
|
161119
|
-
const dotOcJson = join13(directory, ".opencode", "opencode.json");
|
|
161120
|
-
const dotOcConfig = readJsoncFile(dotOcJsonc) ?? readJsoncFile(dotOcJson);
|
|
161121
|
-
if (dotOcConfig?.compaction) {
|
|
161122
|
-
const c = dotOcConfig.compaction;
|
|
161123
|
-
if (c.auto !== undefined || c.prune !== undefined) {
|
|
161124
|
-
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
161125
|
-
}
|
|
161126
|
-
}
|
|
161127
|
-
const rootJsonc = join13(directory, "opencode.jsonc");
|
|
161128
|
-
const rootJson = join13(directory, "opencode.json");
|
|
161129
|
-
const rootConfig = readJsoncFile(rootJsonc) ?? readJsoncFile(rootJson);
|
|
161130
|
-
if (rootConfig?.compaction) {
|
|
161131
|
-
const c = rootConfig.compaction;
|
|
161132
|
-
if (c.auto !== undefined || c.prune !== undefined) {
|
|
161133
|
-
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
161134
|
-
}
|
|
161135
|
-
}
|
|
161136
|
-
return { auto: false, prune: false, resolved: false };
|
|
161137
|
-
}
|
|
161138
|
-
function readUserCompaction() {
|
|
161139
|
-
try {
|
|
161140
|
-
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
161141
|
-
const config2 = readJsoncFile(paths.configJsonc) ?? readJsoncFile(paths.configJson);
|
|
161142
|
-
if (config2?.compaction) {
|
|
161143
|
-
const c = config2.compaction;
|
|
161144
|
-
if (c.auto !== undefined || c.prune !== undefined) {
|
|
161145
|
-
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
161146
|
-
}
|
|
161147
|
-
}
|
|
161148
|
-
} catch {}
|
|
161149
|
-
return { auto: false, prune: false, resolved: false };
|
|
161150
|
-
}
|
|
161151
|
-
function checkDcpPlugin(directory) {
|
|
161152
|
-
const plugins = collectPluginEntries(directory);
|
|
161153
|
-
return plugins.some((p) => matchesPackageName(p, DCP_PACKAGE_NAMES));
|
|
161154
|
-
}
|
|
161155
|
-
function matchesPackageName(entry, canonicalNames) {
|
|
161156
|
-
if (entry.startsWith("file:") || entry.startsWith("http:") || entry.startsWith("https:") || entry.startsWith("/") || entry.startsWith("./") || entry.startsWith("../")) {
|
|
161157
|
-
return false;
|
|
161158
|
-
}
|
|
161159
|
-
const lastAt = entry.lastIndexOf("@");
|
|
161160
|
-
const nameOnly = lastAt > 0 ? entry.slice(0, lastAt) : entry;
|
|
161161
|
-
return canonicalNames.has(nameOnly);
|
|
161162
|
-
}
|
|
161163
|
-
function extractPluginName(entry) {
|
|
161164
|
-
if (typeof entry === "string")
|
|
161165
|
-
return entry;
|
|
161166
|
-
if (Array.isArray(entry) && typeof entry[0] === "string")
|
|
161167
|
-
return entry[0];
|
|
161168
|
-
return null;
|
|
161169
|
-
}
|
|
161170
|
-
function collectPluginEntries(directory) {
|
|
161171
|
-
const plugins = [];
|
|
161172
|
-
const pushFrom = (entries) => {
|
|
161173
|
-
if (!entries)
|
|
161174
|
-
return;
|
|
161175
|
-
for (const entry of entries) {
|
|
161176
|
-
const name2 = extractPluginName(entry);
|
|
161177
|
-
if (name2)
|
|
161178
|
-
plugins.push(name2);
|
|
161179
|
-
}
|
|
161180
|
-
};
|
|
161181
|
-
for (const configPath of [
|
|
161182
|
-
join13(directory, ".opencode", "opencode.jsonc"),
|
|
161183
|
-
join13(directory, ".opencode", "opencode.json"),
|
|
161184
|
-
join13(directory, "opencode.jsonc"),
|
|
161185
|
-
join13(directory, "opencode.json")
|
|
161186
|
-
]) {
|
|
161187
|
-
const config2 = readJsoncFile(configPath);
|
|
161188
|
-
pushFrom(config2?.plugin);
|
|
161189
|
-
}
|
|
161190
|
-
try {
|
|
161191
|
-
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
161192
|
-
for (const configPath of [paths.configJsonc, paths.configJson]) {
|
|
161193
|
-
const config2 = readJsoncFile(configPath);
|
|
161194
|
-
pushFrom(config2?.plugin);
|
|
161195
|
-
}
|
|
161196
|
-
} catch {}
|
|
161197
|
-
return plugins;
|
|
161198
|
-
}
|
|
161199
|
-
function checkOmoHooks(directory) {
|
|
161200
|
-
const result = {
|
|
161201
|
-
preemptiveCompaction: false,
|
|
161202
|
-
contextWindowMonitor: false,
|
|
161203
|
-
anthropicRecovery: false
|
|
161204
|
-
};
|
|
161205
|
-
const plugins = collectPluginEntries(directory);
|
|
161206
|
-
const hasOmo = plugins.some((p) => matchesPackageName(p, OMO_PACKAGE_NAMES));
|
|
161207
|
-
if (!hasOmo)
|
|
161208
|
-
return result;
|
|
161209
|
-
const disabledHooks = readOmoDisabledHooks(directory);
|
|
161210
|
-
result.preemptiveCompaction = !disabledHooks.has("preemptive-compaction");
|
|
161211
|
-
result.contextWindowMonitor = !disabledHooks.has("context-window-monitor");
|
|
161212
|
-
result.anthropicRecovery = !disabledHooks.has("anthropic-context-window-limit-recovery");
|
|
161213
|
-
return result;
|
|
161214
|
-
}
|
|
161215
|
-
function readOmoDisabledHooks(directory) {
|
|
161216
|
-
const disabled = new Set;
|
|
161217
|
-
const configNames = [
|
|
161218
|
-
"oh-my-opencode.jsonc",
|
|
161219
|
-
"oh-my-opencode.json",
|
|
161220
|
-
"oh-my-openagent.jsonc",
|
|
161221
|
-
"oh-my-openagent.json"
|
|
161222
|
-
];
|
|
161223
|
-
try {
|
|
161224
|
-
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
161225
|
-
for (const name2 of configNames) {
|
|
161226
|
-
const configPath = join13(paths.configDir, name2);
|
|
161227
|
-
const config2 = readJsoncFile(configPath);
|
|
161228
|
-
if (config2?.disabled_hooks) {
|
|
161229
|
-
for (const hook of config2.disabled_hooks) {
|
|
161230
|
-
disabled.add(hook);
|
|
161231
|
-
}
|
|
161232
|
-
}
|
|
161233
|
-
}
|
|
161234
|
-
} catch {}
|
|
161235
|
-
for (const name2 of configNames) {
|
|
161236
|
-
const config2 = readJsoncFile(join13(directory, name2));
|
|
161237
|
-
if (config2?.disabled_hooks) {
|
|
161238
|
-
for (const hook of config2.disabled_hooks) {
|
|
161239
|
-
disabled.add(hook);
|
|
161240
|
-
}
|
|
161241
|
-
}
|
|
161242
|
-
}
|
|
161243
|
-
return disabled;
|
|
161244
|
-
}
|
|
161245
|
-
function formatConflictShort(result) {
|
|
161246
|
-
if (!result.hasConflict)
|
|
161247
|
-
return "";
|
|
161248
|
-
const lines = [
|
|
161249
|
-
"⚠️ Magic Context is disabled due to conflicting configuration:",
|
|
161250
|
-
"",
|
|
161251
|
-
...result.reasons.map((r) => `• ${r}`),
|
|
161252
|
-
"",
|
|
161253
|
-
"Fix: run `npx @wolfx/opencode-magic-context@latest doctor`"
|
|
161254
|
-
];
|
|
161255
|
-
return lines.join(`
|
|
161256
|
-
`);
|
|
161257
|
-
}
|
|
161258
|
-
var DCP_PACKAGE_NAMES, OMO_PACKAGE_NAMES;
|
|
161259
|
-
var init_conflict_detector = __esm(() => {
|
|
161260
|
-
init_jsonc_parser();
|
|
161261
|
-
init_opencode_config_dir();
|
|
161262
|
-
DCP_PACKAGE_NAMES = new Set(["@tarquinen/opencode-dcp"]);
|
|
161263
|
-
OMO_PACKAGE_NAMES = new Set(["oh-my-opencode", "oh-my-openagent"]);
|
|
161264
|
-
});
|
|
161265
|
-
|
|
161266
|
-
// src/plugin/conflict-warning-hook.ts
|
|
161267
|
-
var exports_conflict_warning_hook = {};
|
|
161268
|
-
__export(exports_conflict_warning_hook, {
|
|
161269
|
-
sendTuiSetupNotification: () => sendTuiSetupNotification,
|
|
161270
|
-
sendStartupAnnouncement: () => sendStartupAnnouncement,
|
|
161271
|
-
sendConflictWarning: () => sendConflictWarning,
|
|
161272
|
-
cleanupConflictWarnings: () => cleanupConflictWarnings
|
|
161273
|
-
});
|
|
161274
|
-
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
161275
|
-
import { homedir as homedir7, platform as platform2 } from "node:os";
|
|
161276
|
-
import { join as join14 } from "node:path";
|
|
161277
|
-
function getDesktopStatePath() {
|
|
161278
|
-
const os2 = platform2();
|
|
161279
|
-
const home = homedir7();
|
|
161280
|
-
if (os2 === "darwin") {
|
|
161281
|
-
return join14(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
|
|
161282
|
-
}
|
|
161283
|
-
if (os2 === "linux") {
|
|
161284
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME || join14(home, ".config");
|
|
161285
|
-
return join14(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
|
|
161286
|
-
}
|
|
161287
|
-
if (os2 === "win32") {
|
|
161288
|
-
const appData = process.env.APPDATA || join14(home, "AppData", "Roaming");
|
|
161289
|
-
return join14(appData, "ai.opencode.desktop", "opencode.global.dat");
|
|
161290
|
-
}
|
|
161291
|
-
return null;
|
|
161292
|
-
}
|
|
161293
|
-
function readDesktopState(directory) {
|
|
161294
|
-
const statePath = getDesktopStatePath();
|
|
161295
|
-
if (!statePath || !existsSync11(statePath)) {
|
|
161296
|
-
log(`[magic-context] conflict-warning: Desktop state file not found at ${statePath}`);
|
|
161297
|
-
return { sessionId: null, sidecarUrl: null };
|
|
161298
|
-
}
|
|
161299
|
-
try {
|
|
161300
|
-
const raw = readFileSync8(statePath, "utf-8");
|
|
161301
|
-
const state = JSON.parse(raw);
|
|
161302
|
-
let sidecarUrl = null;
|
|
161303
|
-
const serverStr = state.server;
|
|
161304
|
-
if (typeof serverStr === "string") {
|
|
161305
|
-
try {
|
|
161306
|
-
const serverState = JSON.parse(serverStr);
|
|
161307
|
-
if (typeof serverState.currentSidecarUrl === "string") {
|
|
161308
|
-
sidecarUrl = serverState.currentSidecarUrl;
|
|
161309
|
-
}
|
|
161310
|
-
} catch {}
|
|
161311
|
-
}
|
|
161312
|
-
let sessionId = null;
|
|
161313
|
-
const layoutPage = state["layout.page"];
|
|
161314
|
-
if (typeof layoutPage === "string") {
|
|
161315
|
-
const parsed = JSON.parse(layoutPage);
|
|
161316
|
-
const lastProjectSession = parsed.lastProjectSession;
|
|
161317
|
-
if (lastProjectSession) {
|
|
161318
|
-
const entry = lastProjectSession[directory];
|
|
161319
|
-
sessionId = entry?.id ?? null;
|
|
161320
|
-
}
|
|
161321
|
-
}
|
|
161322
|
-
return { sessionId, sidecarUrl };
|
|
161323
|
-
} catch (error51) {
|
|
161324
|
-
log(`[magic-context] conflict-warning: failed to read Desktop state: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
161325
|
-
return { sessionId: null, sidecarUrl: null };
|
|
161326
|
-
}
|
|
161327
|
-
}
|
|
161328
|
-
function getDesktopState(directory) {
|
|
161329
|
-
let cached2 = cachedDesktopStateByDir.get(directory);
|
|
161330
|
-
if (!cached2) {
|
|
161331
|
-
cached2 = readDesktopState(directory);
|
|
161332
|
-
cachedDesktopStateByDir.set(directory, cached2);
|
|
161333
|
-
}
|
|
161334
|
-
return cached2;
|
|
161335
|
-
}
|
|
161336
|
-
async function deleteMessage(serverUrl, sessionId, messageId) {
|
|
161337
|
-
const auth = getServerAuth();
|
|
161338
|
-
const url2 = `${serverUrl}/session/${encodeURIComponent(sessionId)}/message/${encodeURIComponent(messageId)}`;
|
|
161339
|
-
try {
|
|
161340
|
-
const response = await fetch(url2, {
|
|
161341
|
-
method: "DELETE",
|
|
161342
|
-
headers: auth ? { Authorization: auth } : {},
|
|
161343
|
-
signal: AbortSignal.timeout(1e4)
|
|
161344
|
-
});
|
|
161345
|
-
if (!response.ok) {
|
|
161346
|
-
log(`[magic-context] conflict-warning: DELETE failed status=${response.status} url=${url2}`);
|
|
161347
|
-
return false;
|
|
161348
|
-
}
|
|
161349
|
-
return true;
|
|
161350
|
-
} catch (error51) {
|
|
161351
|
-
log(`[magic-context] conflict-warning: DELETE error (url=${serverUrl}): ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
161352
|
-
return false;
|
|
161353
|
-
}
|
|
161354
|
-
}
|
|
161355
|
-
function getServerAuth() {
|
|
161356
|
-
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
161357
|
-
if (!password)
|
|
161358
|
-
return;
|
|
161359
|
-
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
|
|
161360
|
-
return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
|
|
161361
|
-
}
|
|
161362
|
-
async function getSessionMessages(client, sessionId) {
|
|
161363
|
-
try {
|
|
161364
|
-
const c = client;
|
|
161365
|
-
if (typeof c.session?.messages === "function") {
|
|
161366
|
-
const result = await c.session.messages({
|
|
161367
|
-
path: { id: sessionId },
|
|
161368
|
-
query: { limit: 50 }
|
|
161369
|
-
});
|
|
161370
|
-
return result?.data ?? [];
|
|
161371
|
-
}
|
|
161372
|
-
} catch (error51) {
|
|
161373
|
-
log(`[magic-context] conflict-warning: failed to read messages: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
161374
|
-
}
|
|
161375
|
-
return [];
|
|
161376
|
-
}
|
|
161377
|
-
async function sendConflictWarning(client, directory, conflictResult) {
|
|
161378
|
-
const { sessionId } = getDesktopState(directory);
|
|
161379
|
-
if (!sessionId) {
|
|
161380
|
-
log("[magic-context] conflict-warning: could not find active session for Desktop warning");
|
|
161381
|
-
return;
|
|
161382
|
-
}
|
|
161383
|
-
const warningText = formatConflictShort(conflictResult);
|
|
161384
|
-
log(`[magic-context] sending conflict warning to session ${sessionId}: ${conflictResult.reasons.join(", ")}`);
|
|
161385
|
-
try {
|
|
161386
|
-
const c = client;
|
|
161387
|
-
const promptInput = {
|
|
161388
|
-
path: { id: sessionId },
|
|
161389
|
-
body: {
|
|
161390
|
-
noReply: true,
|
|
161391
|
-
parts: [
|
|
161392
|
-
{
|
|
161393
|
-
type: "text",
|
|
161394
|
-
text: warningText,
|
|
161395
|
-
ignored: true
|
|
161396
|
-
}
|
|
161397
|
-
]
|
|
161398
|
-
}
|
|
161399
|
-
};
|
|
161400
|
-
if (typeof c.session?.prompt === "function") {
|
|
161401
|
-
await Promise.resolve(c.session.prompt(promptInput));
|
|
161402
|
-
} else if (typeof c.session?.promptAsync === "function") {
|
|
161403
|
-
await c.session.promptAsync(promptInput);
|
|
161404
|
-
} else {
|
|
161405
|
-
log("[magic-context] conflict-warning: session prompt API unavailable");
|
|
161406
|
-
}
|
|
161407
|
-
} catch (error51) {
|
|
161408
|
-
log(`[magic-context] conflict-warning: failed to send: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
161409
|
-
}
|
|
161410
|
-
}
|
|
161411
|
-
async function cleanupConflictWarnings(client, directory, serverUrl) {
|
|
161412
|
-
const { sessionId } = getDesktopState(directory);
|
|
161413
|
-
if (!sessionId) {
|
|
161414
|
-
log("[magic-context] cleanup: no active Desktop session found");
|
|
161415
|
-
return;
|
|
161416
|
-
}
|
|
161417
|
-
const messages = await getSessionMessages(client, sessionId);
|
|
161418
|
-
if (messages.length === 0)
|
|
161419
|
-
return;
|
|
161420
|
-
const warningMessageIds = [];
|
|
161421
|
-
for (let i = messages.length - 1;i >= 0; i--) {
|
|
161422
|
-
const msg = messages[i];
|
|
161423
|
-
const msgId = msg.info?.id;
|
|
161424
|
-
const msgRole = msg.info?.role;
|
|
161425
|
-
if (!msgId || msgRole !== "user")
|
|
161426
|
-
break;
|
|
161427
|
-
const parts = msg.parts ?? [];
|
|
161428
|
-
const isWarning = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(CONFLICT_WARNING_MARKER));
|
|
161429
|
-
if (isWarning) {
|
|
161430
|
-
warningMessageIds.push(msgId);
|
|
161431
|
-
} else {
|
|
161432
|
-
break;
|
|
161433
|
-
}
|
|
161434
|
-
}
|
|
161435
|
-
if (warningMessageIds.length === 0) {
|
|
161436
|
-
await cleanupEnabledMessages(messages, serverUrl, sessionId);
|
|
161437
|
-
return;
|
|
161438
|
-
}
|
|
161439
|
-
if (!serverUrl) {
|
|
161440
|
-
log("[magic-context] cleanup: no serverUrl provided, cannot delete messages");
|
|
161441
|
-
return;
|
|
161442
|
-
}
|
|
161443
|
-
log(`[magic-context] cleaning up ${warningMessageIds.length} conflict warning message(s) from session ${sessionId}`);
|
|
161444
|
-
for (const messageId of warningMessageIds) {
|
|
161445
|
-
const ok = await deleteMessage(serverUrl, sessionId, messageId);
|
|
161446
|
-
if (ok) {
|
|
161447
|
-
log(`[magic-context] deleted conflict warning message ${messageId}`);
|
|
161448
|
-
}
|
|
161449
|
-
}
|
|
161450
|
-
const enabledText = `${ENABLED_MARKER}. Enjoy! ✨`;
|
|
161451
|
-
try {
|
|
161452
|
-
const c = client;
|
|
161453
|
-
const promptInput = {
|
|
161454
|
-
path: { id: sessionId },
|
|
161455
|
-
body: {
|
|
161456
|
-
noReply: true,
|
|
161457
|
-
parts: [{ type: "text", text: enabledText, ignored: true }]
|
|
161458
|
-
}
|
|
161459
|
-
};
|
|
161460
|
-
if (typeof c.session?.prompt === "function") {
|
|
161461
|
-
await Promise.resolve(c.session.prompt(promptInput));
|
|
161462
|
-
} else if (typeof c.session?.promptAsync === "function") {
|
|
161463
|
-
await c.session.promptAsync(promptInput);
|
|
161464
|
-
}
|
|
161465
|
-
} catch {}
|
|
161466
|
-
setTimeout(async () => {
|
|
161467
|
-
try {
|
|
161468
|
-
const freshMessages = await getSessionMessages(client, sessionId);
|
|
161469
|
-
for (let i = freshMessages.length - 1;i >= 0; i--) {
|
|
161470
|
-
const msg = freshMessages[i];
|
|
161471
|
-
const msgId = msg.info?.id;
|
|
161472
|
-
const msgRole = msg.info?.role;
|
|
161473
|
-
if (!msgId || msgRole !== "user")
|
|
161474
|
-
break;
|
|
161475
|
-
const parts = msg.parts ?? [];
|
|
161476
|
-
const isEnabled = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(ENABLED_MARKER));
|
|
161477
|
-
if (isEnabled) {
|
|
161478
|
-
await deleteMessage(serverUrl, sessionId, msgId);
|
|
161479
|
-
} else {
|
|
161480
|
-
break;
|
|
161481
|
-
}
|
|
161482
|
-
}
|
|
161483
|
-
} catch {}
|
|
161484
|
-
}, 1000);
|
|
161485
|
-
}
|
|
161486
|
-
async function cleanupEnabledMessages(messages, serverUrl, sessionId) {
|
|
161487
|
-
if (!serverUrl)
|
|
161488
|
-
return;
|
|
161489
|
-
for (let i = messages.length - 1;i >= 0; i--) {
|
|
161490
|
-
const msg = messages[i];
|
|
161491
|
-
const msgId = msg.info?.id;
|
|
161492
|
-
const msgRole = msg.info?.role;
|
|
161493
|
-
if (!msgId || msgRole !== "user")
|
|
161494
|
-
break;
|
|
161495
|
-
const parts = msg.parts ?? [];
|
|
161496
|
-
const isEnabled = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(ENABLED_MARKER));
|
|
161497
|
-
if (isEnabled) {
|
|
161498
|
-
await deleteMessage(serverUrl, sessionId, msgId);
|
|
161499
|
-
} else {
|
|
161500
|
-
break;
|
|
161501
|
-
}
|
|
161502
|
-
}
|
|
161503
|
-
}
|
|
161504
|
-
async function sendTuiSetupNotification(client, directory, serverUrl) {
|
|
161505
|
-
const { sessionId } = getDesktopState(directory);
|
|
161506
|
-
if (!sessionId)
|
|
161507
|
-
return;
|
|
161508
|
-
const text = [
|
|
161509
|
-
`${TUI_SETUP_MARKER}`,
|
|
161510
|
-
"",
|
|
161511
|
-
"Magic Context added its TUI plugin to your tui.json.",
|
|
161512
|
-
"Restart OpenCode to see the sidebar with live context breakdown,",
|
|
161513
|
-
"token usage, historian status, memory counts, and more."
|
|
161514
|
-
].join(`
|
|
161515
|
-
`);
|
|
161516
|
-
try {
|
|
161517
|
-
const c = client;
|
|
161518
|
-
const promptInput = {
|
|
161519
|
-
path: { id: sessionId },
|
|
161520
|
-
body: {
|
|
161521
|
-
noReply: true,
|
|
161522
|
-
parts: [{ type: "text", text, ignored: true }]
|
|
161523
|
-
}
|
|
161524
|
-
};
|
|
161525
|
-
if (typeof c.session?.prompt === "function") {
|
|
161526
|
-
await Promise.resolve(c.session.prompt(promptInput));
|
|
161527
|
-
} else if (typeof c.session?.promptAsync === "function") {
|
|
161528
|
-
await c.session.promptAsync(promptInput);
|
|
161529
|
-
}
|
|
161530
|
-
} catch {
|
|
161531
|
-
return;
|
|
161532
|
-
}
|
|
161533
|
-
if (!serverUrl)
|
|
161534
|
-
return;
|
|
161535
|
-
setTimeout(async () => {
|
|
161536
|
-
try {
|
|
161537
|
-
const msgs = await getSessionMessages(client, sessionId);
|
|
161538
|
-
for (let i = msgs.length - 1;i >= 0; i--) {
|
|
161539
|
-
const msg = msgs[i];
|
|
161540
|
-
const msgId = msg.info?.id;
|
|
161541
|
-
if (!msgId || msg.info?.role !== "user")
|
|
161542
|
-
break;
|
|
161543
|
-
const parts = msg.parts ?? [];
|
|
161544
|
-
const isTuiSetup = parts.length > 0 && parts.every((p) => p.ignored === true && p.type === "text" && typeof p.text === "string" && p.text.startsWith(TUI_SETUP_MARKER));
|
|
161545
|
-
if (isTuiSetup) {
|
|
161546
|
-
await deleteMessage(serverUrl, sessionId, msgId);
|
|
161547
|
-
} else {
|
|
161548
|
-
break;
|
|
161549
|
-
}
|
|
161550
|
-
}
|
|
161551
|
-
} catch {}
|
|
161552
|
-
}, 1000);
|
|
161553
|
-
}
|
|
161554
|
-
async function sendStartupAnnouncement(client, directory, version2, features, footer, markSeen) {
|
|
161555
|
-
if (!version2 || features.length === 0)
|
|
161556
|
-
return;
|
|
161557
|
-
const { sessionId } = getDesktopState(directory);
|
|
161558
|
-
if (!sessionId) {
|
|
161559
|
-
return;
|
|
161560
|
-
}
|
|
161561
|
-
const bullets = features.map((line) => ` • ${line}`).join(`
|
|
161562
|
-
`);
|
|
161563
|
-
const sections = [`${ANNOUNCEMENT_MARKER} v${version2}:`, "", bullets];
|
|
161564
|
-
if (footer && footer.trim().length > 0) {
|
|
161565
|
-
sections.push("", footer);
|
|
161566
|
-
}
|
|
161567
|
-
const text = sections.join(`
|
|
161568
|
-
`);
|
|
161569
|
-
log(`[magic-context] sending startup announcement for v${version2} to session ${sessionId}`);
|
|
161570
|
-
try {
|
|
161571
|
-
const c = client;
|
|
161572
|
-
const promptInput = {
|
|
161573
|
-
path: { id: sessionId },
|
|
161574
|
-
body: {
|
|
161575
|
-
noReply: true,
|
|
161576
|
-
parts: [{ type: "text", text, ignored: true }]
|
|
161577
|
-
}
|
|
161578
|
-
};
|
|
161579
|
-
if (typeof c.session?.prompt === "function") {
|
|
161580
|
-
await Promise.resolve(c.session.prompt(promptInput));
|
|
161581
|
-
} else if (typeof c.session?.promptAsync === "function") {
|
|
161582
|
-
await c.session.promptAsync(promptInput);
|
|
161583
|
-
} else {
|
|
161584
|
-
log("[magic-context] announcement: session prompt API unavailable");
|
|
161585
|
-
return;
|
|
161586
|
-
}
|
|
161587
|
-
} catch (error51) {
|
|
161588
|
-
log(`[magic-context] announcement: failed to send: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
161589
|
-
return;
|
|
161590
|
-
}
|
|
161591
|
-
markSeen(version2);
|
|
161592
|
-
}
|
|
161593
|
-
var CONFLICT_WARNING_MARKER = "⚠️ Magic Context is disabled due to conflicting configuration:", ENABLED_MARKER = "✨ Magic Context is now enabled", TUI_SETUP_MARKER = "\uD83D\uDCCA Magic Context sidebar configured", ANNOUNCEMENT_MARKER = "✨ Magic Context — what's new in", cachedDesktopStateByDir;
|
|
161594
|
-
var init_conflict_warning_hook = __esm(() => {
|
|
161595
|
-
init_conflict_detector();
|
|
161596
|
-
init_logger();
|
|
161597
|
-
cachedDesktopStateByDir = new Map;
|
|
161598
|
-
});
|
|
161599
|
-
|
|
161600
161603
|
// src/features/magic-context/memory/storage-memory-embeddings.ts
|
|
161601
161604
|
function isEmbeddingBlob(value) {
|
|
161602
161605
|
return value instanceof Uint8Array || value instanceof ArrayBuffer;
|
|
@@ -162190,9 +162193,9 @@ var init_embedding_identity = __esm(() => {
|
|
|
162190
162193
|
});
|
|
162191
162194
|
|
|
162192
162195
|
// src/features/magic-context/memory/embedding-local.ts
|
|
162193
|
-
import { mkdirSync as
|
|
162196
|
+
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
162194
162197
|
import { open, stat, unlink, writeFile } from "node:fs/promises";
|
|
162195
|
-
import { dirname as
|
|
162198
|
+
import { dirname as dirname4, join as join14 } from "node:path";
|
|
162196
162199
|
import { pathToFileURL } from "node:url";
|
|
162197
162200
|
async function acquireModelLoadLock(lockPath) {
|
|
162198
162201
|
const waitStart = Date.now();
|
|
@@ -162230,7 +162233,7 @@ async function acquireModelLoadLock(lockPath) {
|
|
|
162230
162233
|
log("[magic-context] embedding-load lock wait exceeded, proceeding without lock");
|
|
162231
162234
|
return async () => {};
|
|
162232
162235
|
}
|
|
162233
|
-
await new Promise((
|
|
162236
|
+
await new Promise((resolve5) => setTimeout(resolve5, LOCK_POLL_MS));
|
|
162234
162237
|
}
|
|
162235
162238
|
}
|
|
162236
162239
|
}
|
|
@@ -162254,7 +162257,7 @@ async function injectWasmOrtForElectron() {
|
|
|
162254
162257
|
const { createRequire: createRequireFn } = await import("node:module");
|
|
162255
162258
|
const requireFn = createRequireFn(import.meta.url);
|
|
162256
162259
|
const pkgPath = requireFn.resolve("onnxruntime-web/package.json");
|
|
162257
|
-
const distDir =
|
|
162260
|
+
const distDir = join14(dirname4(pkgPath), "dist");
|
|
162258
162261
|
const wasmPathsPrefix = `${pathToFileURL(distDir).href}/`;
|
|
162259
162262
|
if (ortWeb.env?.wasm) {
|
|
162260
162263
|
ortWeb.env.wasm.wasmPaths = wasmPathsPrefix;
|
|
@@ -162365,15 +162368,15 @@ class LocalEmbeddingProvider {
|
|
|
162365
162368
|
if (LogLevel && "ERROR" in LogLevel) {
|
|
162366
162369
|
env.logLevel = LogLevel.ERROR;
|
|
162367
162370
|
}
|
|
162368
|
-
const modelCacheDir =
|
|
162371
|
+
const modelCacheDir = join14(getMagicContextStorageDir(), "models");
|
|
162369
162372
|
try {
|
|
162370
|
-
|
|
162373
|
+
mkdirSync4(modelCacheDir, { recursive: true });
|
|
162371
162374
|
env.cacheDir = modelCacheDir;
|
|
162372
162375
|
} catch {
|
|
162373
162376
|
log("[magic-context] could not create model cache dir, using library default");
|
|
162374
162377
|
}
|
|
162375
162378
|
const createPipeline = transformersModule.pipeline;
|
|
162376
|
-
const lockPath =
|
|
162379
|
+
const lockPath = join14(modelCacheDir, ".load.lock");
|
|
162377
162380
|
const releaseLock = await acquireModelLoadLock(lockPath);
|
|
162378
162381
|
const stopHeartbeat = startLockHeartbeat(lockPath);
|
|
162379
162382
|
try {
|
|
@@ -162399,7 +162402,7 @@ class LocalEmbeddingProvider {
|
|
|
162399
162402
|
}
|
|
162400
162403
|
const delayMs = 300 * attempt + Math.floor(Math.random() * 200);
|
|
162401
162404
|
log(`[magic-context] embedding model load attempt ${attempt}/${MAX_ATTEMPTS} failed transiently, retrying in ${delayMs}ms`);
|
|
162402
|
-
await new Promise((
|
|
162405
|
+
await new Promise((resolve5) => setTimeout(resolve5, delayMs));
|
|
162403
162406
|
}
|
|
162404
162407
|
}
|
|
162405
162408
|
if (this.pipeline) {
|
|
@@ -162427,8 +162430,8 @@ class LocalEmbeddingProvider {
|
|
|
162427
162430
|
if (this.inFlight === 0) {
|
|
162428
162431
|
return Promise.resolve();
|
|
162429
162432
|
}
|
|
162430
|
-
return new Promise((
|
|
162431
|
-
this.inFlightWaiters.push(
|
|
162433
|
+
return new Promise((resolve5) => {
|
|
162434
|
+
this.inFlightWaiters.push(resolve5);
|
|
162432
162435
|
});
|
|
162433
162436
|
}
|
|
162434
162437
|
finishInFlight() {
|
|
@@ -163212,9 +163215,9 @@ var init_storage_memory_fts = __esm(() => {
|
|
|
163212
163215
|
|
|
163213
163216
|
// src/shared/models-dev-cache.ts
|
|
163214
163217
|
import { createHash as createHash7 } from "node:crypto";
|
|
163215
|
-
import { existsSync as
|
|
163216
|
-
import { homedir as
|
|
163217
|
-
import { join as
|
|
163218
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
163219
|
+
import { homedir as homedir7, platform as platform2 } from "node:os";
|
|
163220
|
+
import { join as join15 } from "node:path";
|
|
163218
163221
|
function hashFast(input) {
|
|
163219
163222
|
return createHash7("sha1").update(input).digest("hex");
|
|
163220
163223
|
}
|
|
@@ -163225,16 +163228,16 @@ function getModelsJsonPath() {
|
|
|
163225
163228
|
const cacheBase = getCacheDir();
|
|
163226
163229
|
const source = process.env.OPENCODE_MODELS_URL?.trim();
|
|
163227
163230
|
const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
|
|
163228
|
-
return
|
|
163231
|
+
return join15(cacheBase, "opencode", filename);
|
|
163229
163232
|
}
|
|
163230
163233
|
function getOpencodeConfigPath() {
|
|
163231
163234
|
const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
163232
|
-
const configDir = envDir ? envDir :
|
|
163233
|
-
const jsonc =
|
|
163234
|
-
if (
|
|
163235
|
+
const configDir = envDir ? envDir : platform2() === "win32" ? join15(homedir7(), ".config", "opencode") : join15(process.env.XDG_CONFIG_HOME || join15(homedir7(), ".config"), "opencode");
|
|
163236
|
+
const jsonc = join15(configDir, "opencode.jsonc");
|
|
163237
|
+
if (existsSync11(jsonc))
|
|
163235
163238
|
return jsonc;
|
|
163236
|
-
const json2 =
|
|
163237
|
-
if (
|
|
163239
|
+
const json2 = join15(configDir, "opencode.json");
|
|
163240
|
+
if (existsSync11(json2))
|
|
163238
163241
|
return json2;
|
|
163239
163242
|
return null;
|
|
163240
163243
|
}
|
|
@@ -163266,9 +163269,9 @@ function loadModelsDevMetadataFromFile() {
|
|
|
163266
163269
|
const modelsJsonPath = getModelsJsonPath();
|
|
163267
163270
|
let fileFound = false;
|
|
163268
163271
|
try {
|
|
163269
|
-
if (
|
|
163272
|
+
if (existsSync11(modelsJsonPath)) {
|
|
163270
163273
|
fileFound = true;
|
|
163271
|
-
const raw =
|
|
163274
|
+
const raw = readFileSync8(modelsJsonPath, "utf-8");
|
|
163272
163275
|
const data = JSON.parse(raw);
|
|
163273
163276
|
for (const [providerId, provider2] of Object.entries(data)) {
|
|
163274
163277
|
if (!provider2?.models || typeof provider2.models !== "object")
|
|
@@ -163283,8 +163286,8 @@ function loadModelsDevMetadataFromFile() {
|
|
|
163283
163286
|
}
|
|
163284
163287
|
try {
|
|
163285
163288
|
const configPath = getOpencodeConfigPath();
|
|
163286
|
-
if (configPath &&
|
|
163287
|
-
const config2 = parseJsonc(
|
|
163289
|
+
if (configPath && existsSync11(configPath)) {
|
|
163290
|
+
const config2 = parseJsonc(readFileSync8(configPath, "utf-8"));
|
|
163288
163291
|
if (config2.provider && typeof config2.provider === "object") {
|
|
163289
163292
|
for (const [providerId, provider2] of Object.entries(config2.provider)) {
|
|
163290
163293
|
if (!provider2?.models || typeof provider2.models !== "object")
|
|
@@ -163382,7 +163385,7 @@ var init_rpc_notifications = __esm(() => {
|
|
|
163382
163385
|
});
|
|
163383
163386
|
|
|
163384
163387
|
// src/features/magic-context/compaction-marker.ts
|
|
163385
|
-
import { join as
|
|
163388
|
+
import { join as join16 } from "node:path";
|
|
163386
163389
|
function randomBase62(length) {
|
|
163387
163390
|
const chars = [];
|
|
163388
163391
|
for (let i = 0;i < length; i++) {
|
|
@@ -163402,7 +163405,7 @@ function generatePartId(timestampMs, counter = 0n) {
|
|
|
163402
163405
|
return generateId("prt", timestampMs, counter);
|
|
163403
163406
|
}
|
|
163404
163407
|
function getOpenCodeDbPath3() {
|
|
163405
|
-
return
|
|
163408
|
+
return join16(getDataDir(), "opencode", "opencode.db");
|
|
163406
163409
|
}
|
|
163407
163410
|
function isOpenCodeSchemaCompatible(db, dbPath) {
|
|
163408
163411
|
if (cachedSchemaCompatible?.path === dbPath) {
|
|
@@ -163544,7 +163547,7 @@ var init_compaction_marker = __esm(async () => {
|
|
|
163544
163547
|
});
|
|
163545
163548
|
|
|
163546
163549
|
// src/hooks/magic-context/compaction-marker-manager.ts
|
|
163547
|
-
import { join as
|
|
163550
|
+
import { join as join17 } from "node:path";
|
|
163548
163551
|
function validatePendingTarget(db, sessionId, pending) {
|
|
163549
163552
|
const ocMessage = getOpenCodeMessageById(sessionId, pending.endMessageId);
|
|
163550
163553
|
if (!ocMessage) {
|
|
@@ -163651,7 +163654,7 @@ function removeCompactionMarkerForSession(db, sessionId) {
|
|
|
163651
163654
|
}
|
|
163652
163655
|
}
|
|
163653
163656
|
function checkCompactionMarkerConsistency(db) {
|
|
163654
|
-
const opencodeDbPath =
|
|
163657
|
+
const opencodeDbPath = join17(getDataDir(), "opencode", "opencode.db");
|
|
163655
163658
|
let opencodeDb;
|
|
163656
163659
|
try {
|
|
163657
163660
|
opencodeDb = new Database(opencodeDbPath, { readonly: true });
|
|
@@ -163934,8 +163937,8 @@ var init_compartment_runner_validation = __esm(async () => {
|
|
|
163934
163937
|
});
|
|
163935
163938
|
|
|
163936
163939
|
// src/hooks/magic-context/compartment-runner-historian.ts
|
|
163937
|
-
import { mkdirSync as
|
|
163938
|
-
import { join as
|
|
163940
|
+
import { mkdirSync as mkdirSync5, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
163941
|
+
import { join as join18 } from "node:path";
|
|
163939
163942
|
function historianResponseDumpDir(directory) {
|
|
163940
163943
|
return getProjectMagicContextHistorianDir(directory);
|
|
163941
163944
|
}
|
|
@@ -164198,8 +164201,8 @@ function isTransientHistorianPromptError(message) {
|
|
|
164198
164201
|
].some((token) => normalized.includes(token));
|
|
164199
164202
|
}
|
|
164200
164203
|
function sleep(ms) {
|
|
164201
|
-
return new Promise((
|
|
164202
|
-
setTimeout(
|
|
164204
|
+
return new Promise((resolve5) => {
|
|
164205
|
+
setTimeout(resolve5, ms);
|
|
164203
164206
|
});
|
|
164204
164207
|
}
|
|
164205
164208
|
function cleanupHistorianDump(sessionId, dumpPath) {
|
|
@@ -164217,11 +164220,11 @@ function cleanupHistorianDump(sessionId, dumpPath) {
|
|
|
164217
164220
|
function dumpHistorianResponse(sessionId, directory, label, text) {
|
|
164218
164221
|
try {
|
|
164219
164222
|
const dumpDir = historianResponseDumpDir(directory);
|
|
164220
|
-
|
|
164223
|
+
mkdirSync5(dumpDir, { recursive: true });
|
|
164221
164224
|
const safeSessionId = sanitizeDumpName(sessionId);
|
|
164222
164225
|
const safeLabel = sanitizeDumpName(label);
|
|
164223
|
-
const dumpPath =
|
|
164224
|
-
|
|
164226
|
+
const dumpPath = join18(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
|
|
164227
|
+
writeFileSync2(dumpPath, text, "utf8");
|
|
164225
164228
|
sessionLog(sessionId, "compartment agent: historian response dumped", {
|
|
164226
164229
|
label,
|
|
164227
164230
|
dumpPath
|
|
@@ -165592,16 +165595,16 @@ var init_caveman = __esm(() => {
|
|
|
165592
165595
|
});
|
|
165593
165596
|
|
|
165594
165597
|
// src/hooks/magic-context/historian-state-file.ts
|
|
165595
|
-
import { mkdirSync as
|
|
165596
|
-
import { join as
|
|
165598
|
+
import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
165599
|
+
import { join as join19 } from "node:path";
|
|
165597
165600
|
function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
|
|
165598
165601
|
if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
|
|
165599
165602
|
return;
|
|
165600
165603
|
try {
|
|
165601
165604
|
const dir = getProjectMagicContextHistorianDir(directory);
|
|
165602
|
-
|
|
165603
|
-
const path5 =
|
|
165604
|
-
|
|
165605
|
+
mkdirSync6(dir, { recursive: true });
|
|
165606
|
+
const path5 = join19(dir, `state-${sessionId}-${Date.now()}.xml`);
|
|
165607
|
+
writeFileSync3(path5, existingState, "utf8");
|
|
165605
165608
|
return path5;
|
|
165606
165609
|
} catch {
|
|
165607
165610
|
return;
|
|
@@ -167181,9 +167184,6 @@ function deepMergeRawConfig(base, override) {
|
|
|
167181
167184
|
}
|
|
167182
167185
|
return result;
|
|
167183
167186
|
}
|
|
167184
|
-
function getProjectUserOnlyFields(config2) {
|
|
167185
|
-
return "auto_update" in config2 ? ["auto_update"] : [];
|
|
167186
|
-
}
|
|
167187
167187
|
function redactConfigValue(value) {
|
|
167188
167188
|
if (value === undefined)
|
|
167189
167189
|
return "<missing>";
|
|
@@ -167330,15 +167330,7 @@ function loadPluginConfig(directory) {
|
|
|
167330
167330
|
}
|
|
167331
167331
|
if (projectLoaded) {
|
|
167332
167332
|
allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
|
|
167333
|
-
|
|
167334
|
-
const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
|
|
167335
|
-
if (strippedUserOnlyFields.length > 0) {
|
|
167336
|
-
for (const key of strippedUserOnlyFields) {
|
|
167337
|
-
delete projectRaw[key];
|
|
167338
|
-
}
|
|
167339
|
-
allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
|
|
167340
|
-
}
|
|
167341
|
-
mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
|
|
167333
|
+
mergedRaw = deepMergeRawConfig(mergedRaw, projectLoaded.config);
|
|
167342
167334
|
}
|
|
167343
167335
|
const config2 = parsePluginConfig(mergedRaw);
|
|
167344
167336
|
if (config2.configWarnings?.length) {
|
|
@@ -167411,15 +167403,7 @@ function loadPluginConfigDetailed(directory) {
|
|
|
167411
167403
|
}
|
|
167412
167404
|
if (projectLoaded) {
|
|
167413
167405
|
allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
|
|
167414
|
-
|
|
167415
|
-
const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
|
|
167416
|
-
if (strippedUserOnlyFields.length > 0) {
|
|
167417
|
-
for (const key of strippedUserOnlyFields) {
|
|
167418
|
-
delete projectRaw[key];
|
|
167419
|
-
}
|
|
167420
|
-
allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
|
|
167421
|
-
}
|
|
167422
|
-
mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
|
|
167406
|
+
mergedRaw = deepMergeRawConfig(mergedRaw, projectLoaded.config);
|
|
167423
167407
|
}
|
|
167424
167408
|
const recoveredTopLevelKeys = [];
|
|
167425
167409
|
const config2 = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
|
|
@@ -167957,587 +167941,6 @@ async function runSidekick(deps) {
|
|
|
167957
167941
|
|
|
167958
167942
|
// src/index.ts
|
|
167959
167943
|
init_tool_definition_tokens();
|
|
167960
|
-
|
|
167961
|
-
// src/hooks/auto-update-checker/index.ts
|
|
167962
|
-
init_logger();
|
|
167963
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
167964
|
-
import { dirname as dirname6, join as join11 } from "node:path";
|
|
167965
|
-
|
|
167966
|
-
// src/hooks/auto-update-checker/cache.ts
|
|
167967
|
-
init_logger();
|
|
167968
|
-
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
167969
|
-
import { spawn } from "node:child_process";
|
|
167970
|
-
import { existsSync as existsSync9, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
167971
|
-
import { basename, dirname as dirname5, join as join10 } from "node:path";
|
|
167972
|
-
|
|
167973
|
-
// src/hooks/auto-update-checker/checker.ts
|
|
167974
|
-
init_logger();
|
|
167975
|
-
var import_comment_json = __toESM(require_src2(), 1);
|
|
167976
|
-
import { existsSync as existsSync8, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
167977
|
-
import { homedir as homedir5 } from "node:os";
|
|
167978
|
-
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
|
|
167979
|
-
import { fileURLToPath } from "node:url";
|
|
167980
|
-
|
|
167981
|
-
// src/hooks/auto-update-checker/constants.ts
|
|
167982
|
-
import { homedir as homedir4, platform } from "node:os";
|
|
167983
|
-
import { join as join8 } from "node:path";
|
|
167984
|
-
var PACKAGE_NAME = "@wolfx/opencode-magic-context";
|
|
167985
|
-
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
167986
|
-
var NPM_FETCH_TIMEOUT = 1e4;
|
|
167987
|
-
function getOpenCodeCacheRoot() {
|
|
167988
|
-
if (platform() === "win32") {
|
|
167989
|
-
return join8(process.env.LOCALAPPDATA ?? homedir4(), "opencode");
|
|
167990
|
-
}
|
|
167991
|
-
return join8(homedir4(), ".cache", "opencode");
|
|
167992
|
-
}
|
|
167993
|
-
function getOpenCodeConfigRoot() {
|
|
167994
|
-
if (platform() === "win32") {
|
|
167995
|
-
return join8(process.env.APPDATA ?? join8(homedir4(), "AppData", "Roaming"), "opencode");
|
|
167996
|
-
}
|
|
167997
|
-
return join8(process.env.XDG_CONFIG_HOME ?? join8(homedir4(), ".config"), "opencode");
|
|
167998
|
-
}
|
|
167999
|
-
var CACHE_DIR = join8(getOpenCodeCacheRoot(), "packages");
|
|
168000
|
-
var USER_OPENCODE_CONFIG = join8(getOpenCodeConfigRoot(), "opencode.json");
|
|
168001
|
-
var USER_OPENCODE_CONFIG_JSONC = join8(getOpenCodeConfigRoot(), "opencode.jsonc");
|
|
168002
|
-
|
|
168003
|
-
// src/hooks/auto-update-checker/types.ts
|
|
168004
|
-
init_zod();
|
|
168005
|
-
var NpmPackageEnvelopeSchema = exports_external.object({
|
|
168006
|
-
"dist-tags": exports_external.record(exports_external.string(), exports_external.string()).optional().default({})
|
|
168007
|
-
});
|
|
168008
|
-
var OpencodePluginTupleSchema = exports_external.tuple([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]);
|
|
168009
|
-
var OpencodeConfigSchema = exports_external.object({
|
|
168010
|
-
plugin: exports_external.array(exports_external.union([exports_external.string(), OpencodePluginTupleSchema])).optional()
|
|
168011
|
-
});
|
|
168012
|
-
var PackageJsonSchema = exports_external.object({
|
|
168013
|
-
name: exports_external.string().optional(),
|
|
168014
|
-
version: exports_external.string().optional(),
|
|
168015
|
-
dependencies: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
168016
|
-
}).passthrough();
|
|
168017
|
-
|
|
168018
|
-
// src/hooks/auto-update-checker/checker.ts
|
|
168019
|
-
function warn(message) {
|
|
168020
|
-
log(`WARN: ${message}`);
|
|
168021
|
-
}
|
|
168022
|
-
function isString(value) {
|
|
168023
|
-
return typeof value === "string";
|
|
168024
|
-
}
|
|
168025
|
-
function pluginSpecifier(entry) {
|
|
168026
|
-
return typeof entry === "string" ? entry : entry[0];
|
|
168027
|
-
}
|
|
168028
|
-
function getPluginEntries(config2) {
|
|
168029
|
-
const parsed = OpencodeConfigSchema.safeParse(config2);
|
|
168030
|
-
if (!parsed.success)
|
|
168031
|
-
return [];
|
|
168032
|
-
return (parsed.data.plugin ?? []).map(pluginSpecifier).filter(isString);
|
|
168033
|
-
}
|
|
168034
|
-
function parseJsonConfig(content) {
|
|
168035
|
-
try {
|
|
168036
|
-
return import_comment_json.parse(content);
|
|
168037
|
-
} catch (err) {
|
|
168038
|
-
warn(`[auto-update-checker] Failed to parse OpenCode config: ${String(err)}`);
|
|
168039
|
-
return null;
|
|
168040
|
-
}
|
|
168041
|
-
}
|
|
168042
|
-
function isPrereleaseVersion(version2) {
|
|
168043
|
-
return version2.includes("-");
|
|
168044
|
-
}
|
|
168045
|
-
function isDistTag(version2) {
|
|
168046
|
-
return !/^\d/.test(version2);
|
|
168047
|
-
}
|
|
168048
|
-
function extractChannel(version2) {
|
|
168049
|
-
if (!version2)
|
|
168050
|
-
return "latest";
|
|
168051
|
-
if (isDistTag(version2))
|
|
168052
|
-
return version2;
|
|
168053
|
-
if (isPrereleaseVersion(version2)) {
|
|
168054
|
-
const prereleasePart = version2.split("-")[1];
|
|
168055
|
-
const channelMatch = prereleasePart?.match(/^(alpha|beta|rc|canary|next)/);
|
|
168056
|
-
if (channelMatch?.[1])
|
|
168057
|
-
return channelMatch[1];
|
|
168058
|
-
}
|
|
168059
|
-
return "latest";
|
|
168060
|
-
}
|
|
168061
|
-
function getConfigPaths(directory) {
|
|
168062
|
-
return [
|
|
168063
|
-
join9(directory, ".opencode", "opencode.json"),
|
|
168064
|
-
join9(directory, ".opencode", "opencode.jsonc"),
|
|
168065
|
-
USER_OPENCODE_CONFIG,
|
|
168066
|
-
USER_OPENCODE_CONFIG_JSONC
|
|
168067
|
-
];
|
|
168068
|
-
}
|
|
168069
|
-
function resolvePathPluginSpec(spec, configPath) {
|
|
168070
|
-
if (spec.startsWith("file://")) {
|
|
168071
|
-
try {
|
|
168072
|
-
return fileURLToPath(spec);
|
|
168073
|
-
} catch {
|
|
168074
|
-
return spec.replace(/^file:\/\//, "");
|
|
168075
|
-
}
|
|
168076
|
-
}
|
|
168077
|
-
if (isAbsolute2(spec) || /^[A-Za-z]:[\\/]/.test(spec))
|
|
168078
|
-
return spec;
|
|
168079
|
-
return resolve3(dirname4(configPath), spec);
|
|
168080
|
-
}
|
|
168081
|
-
function findPackageJsonUp(startPath) {
|
|
168082
|
-
try {
|
|
168083
|
-
const stat = statSync(startPath);
|
|
168084
|
-
let dir = stat.isDirectory() ? startPath : dirname4(startPath);
|
|
168085
|
-
for (let i = 0;i < 10; i++) {
|
|
168086
|
-
const pkgPath = join9(dir, "package.json");
|
|
168087
|
-
if (existsSync8(pkgPath)) {
|
|
168088
|
-
try {
|
|
168089
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
|
|
168090
|
-
if (pkg.success && pkg.data.name === PACKAGE_NAME)
|
|
168091
|
-
return pkgPath;
|
|
168092
|
-
} catch {}
|
|
168093
|
-
}
|
|
168094
|
-
const parent = dirname4(dir);
|
|
168095
|
-
if (parent === dir)
|
|
168096
|
-
break;
|
|
168097
|
-
dir = parent;
|
|
168098
|
-
}
|
|
168099
|
-
} catch {}
|
|
168100
|
-
return null;
|
|
168101
|
-
}
|
|
168102
|
-
function getLocalDevPath(directory) {
|
|
168103
|
-
for (const configPath of getConfigPaths(directory)) {
|
|
168104
|
-
try {
|
|
168105
|
-
if (!existsSync8(configPath))
|
|
168106
|
-
continue;
|
|
168107
|
-
const rawConfig = parseJsonConfig(readFileSync5(configPath, "utf-8"));
|
|
168108
|
-
const plugins = getPluginEntries(rawConfig);
|
|
168109
|
-
for (const entry of plugins) {
|
|
168110
|
-
if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
|
|
168111
|
-
continue;
|
|
168112
|
-
if (entry.startsWith("file://") || entry.startsWith(".") || isAbsolute2(entry)) {
|
|
168113
|
-
const localPath = resolvePathPluginSpec(entry, configPath);
|
|
168114
|
-
const pkgPath = findPackageJsonUp(localPath);
|
|
168115
|
-
if (!pkgPath)
|
|
168116
|
-
continue;
|
|
168117
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
|
|
168118
|
-
if (pkg.success && pkg.data.name === PACKAGE_NAME)
|
|
168119
|
-
return localPath;
|
|
168120
|
-
}
|
|
168121
|
-
}
|
|
168122
|
-
} catch {}
|
|
168123
|
-
}
|
|
168124
|
-
return null;
|
|
168125
|
-
}
|
|
168126
|
-
function getLocalDevVersion(directory) {
|
|
168127
|
-
const localPath = getLocalDevPath(directory);
|
|
168128
|
-
if (!localPath)
|
|
168129
|
-
return null;
|
|
168130
|
-
try {
|
|
168131
|
-
const pkgPath = findPackageJsonUp(localPath);
|
|
168132
|
-
if (!pkgPath)
|
|
168133
|
-
return null;
|
|
168134
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
|
|
168135
|
-
return pkg.success ? pkg.data.version ?? null : null;
|
|
168136
|
-
} catch {
|
|
168137
|
-
return null;
|
|
168138
|
-
}
|
|
168139
|
-
}
|
|
168140
|
-
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
168141
|
-
try {
|
|
168142
|
-
return findPackageJsonUp(dirname4(fileURLToPath(currentModuleUrl)));
|
|
168143
|
-
} catch (err) {
|
|
168144
|
-
warn(`[auto-update-checker] Failed to resolve runtime package path: ${String(err)}`);
|
|
168145
|
-
return null;
|
|
168146
|
-
}
|
|
168147
|
-
}
|
|
168148
|
-
function findPluginEntry(directory) {
|
|
168149
|
-
for (const configPath of getConfigPaths(directory)) {
|
|
168150
|
-
try {
|
|
168151
|
-
if (!existsSync8(configPath))
|
|
168152
|
-
continue;
|
|
168153
|
-
const rawConfig = parseJsonConfig(readFileSync5(configPath, "utf-8"));
|
|
168154
|
-
const plugins = getPluginEntries(rawConfig);
|
|
168155
|
-
for (const entry of plugins) {
|
|
168156
|
-
if (entry === PACKAGE_NAME) {
|
|
168157
|
-
return { entry, isPinned: false, pinnedVersion: null, configPath };
|
|
168158
|
-
}
|
|
168159
|
-
if (entry.startsWith(`${PACKAGE_NAME}@`)) {
|
|
168160
|
-
const pinnedVersion = entry.slice(PACKAGE_NAME.length + 1);
|
|
168161
|
-
const isPinned = pinnedVersion !== "latest";
|
|
168162
|
-
return {
|
|
168163
|
-
entry,
|
|
168164
|
-
isPinned,
|
|
168165
|
-
pinnedVersion: isPinned ? pinnedVersion : null,
|
|
168166
|
-
configPath
|
|
168167
|
-
};
|
|
168168
|
-
}
|
|
168169
|
-
}
|
|
168170
|
-
} catch {}
|
|
168171
|
-
}
|
|
168172
|
-
return null;
|
|
168173
|
-
}
|
|
168174
|
-
var cachedPackageVersion = null;
|
|
168175
|
-
function getSpecCachePackageJsonPath(spec) {
|
|
168176
|
-
return join9(CACHE_DIR, spec, "node_modules", PACKAGE_NAME, "package.json");
|
|
168177
|
-
}
|
|
168178
|
-
function getCachedVersion(spec) {
|
|
168179
|
-
if (!spec && cachedPackageVersion)
|
|
168180
|
-
return cachedPackageVersion;
|
|
168181
|
-
const candidates = [
|
|
168182
|
-
getCurrentRuntimePackageJsonPath(),
|
|
168183
|
-
spec ? getSpecCachePackageJsonPath(spec) : null,
|
|
168184
|
-
getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
|
|
168185
|
-
join9(homedir5(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
|
|
168186
|
-
].filter(isString);
|
|
168187
|
-
for (const packageJsonPath of candidates) {
|
|
168188
|
-
try {
|
|
168189
|
-
if (!existsSync8(packageJsonPath))
|
|
168190
|
-
continue;
|
|
168191
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(packageJsonPath, "utf-8")));
|
|
168192
|
-
if (pkg.success && pkg.data.version) {
|
|
168193
|
-
if (!spec)
|
|
168194
|
-
cachedPackageVersion = pkg.data.version;
|
|
168195
|
-
return pkg.data.version;
|
|
168196
|
-
}
|
|
168197
|
-
} catch {}
|
|
168198
|
-
}
|
|
168199
|
-
return null;
|
|
168200
|
-
}
|
|
168201
|
-
function buildRegistryUrl(registryUrl) {
|
|
168202
|
-
return `${registryUrl.replace(/\/+$/, "")}/${encodeURIComponent(PACKAGE_NAME).replace("%2F", "/")}`;
|
|
168203
|
-
}
|
|
168204
|
-
async function getLatestVersion(channel = "latest", options = {}) {
|
|
168205
|
-
const controller = new AbortController;
|
|
168206
|
-
const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs ?? NPM_FETCH_TIMEOUT);
|
|
168207
|
-
const abortHandler = () => controller.abort();
|
|
168208
|
-
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
168209
|
-
try {
|
|
168210
|
-
if (options.signal?.aborted)
|
|
168211
|
-
return null;
|
|
168212
|
-
const response = await fetch(buildRegistryUrl(options.registryUrl ?? NPM_REGISTRY_URL), {
|
|
168213
|
-
signal: controller.signal,
|
|
168214
|
-
headers: { Accept: "application/json" }
|
|
168215
|
-
});
|
|
168216
|
-
if (!response.ok)
|
|
168217
|
-
return null;
|
|
168218
|
-
const data = NpmPackageEnvelopeSchema.safeParse(await response.json());
|
|
168219
|
-
if (!data.success)
|
|
168220
|
-
return null;
|
|
168221
|
-
return data.data["dist-tags"][channel] ?? data.data["dist-tags"].latest ?? null;
|
|
168222
|
-
} catch {
|
|
168223
|
-
return null;
|
|
168224
|
-
} finally {
|
|
168225
|
-
options.signal?.removeEventListener("abort", abortHandler);
|
|
168226
|
-
clearTimeout(timeoutId);
|
|
168227
|
-
}
|
|
168228
|
-
}
|
|
168229
|
-
|
|
168230
|
-
// src/hooks/auto-update-checker/cache.ts
|
|
168231
|
-
function warn2(message) {
|
|
168232
|
-
log(`WARN: ${message}`);
|
|
168233
|
-
}
|
|
168234
|
-
function stripPackageNameFromPath(pathValue, packageName) {
|
|
168235
|
-
let current = pathValue;
|
|
168236
|
-
for (const segment of [...packageName.split("/")].reverse()) {
|
|
168237
|
-
if (basename(current) !== segment)
|
|
168238
|
-
return null;
|
|
168239
|
-
current = dirname5(current);
|
|
168240
|
-
}
|
|
168241
|
-
return current;
|
|
168242
|
-
}
|
|
168243
|
-
function removeFromPackageLock(installDir, packageName) {
|
|
168244
|
-
const lockPath = join10(installDir, "package-lock.json");
|
|
168245
|
-
if (!existsSync9(lockPath))
|
|
168246
|
-
return false;
|
|
168247
|
-
try {
|
|
168248
|
-
const lock = import_comment_json2.parse(readFileSync6(lockPath, "utf-8"));
|
|
168249
|
-
let modified = false;
|
|
168250
|
-
if (lock.packages) {
|
|
168251
|
-
const key = `node_modules/${packageName}`;
|
|
168252
|
-
if (lock.packages[key] !== undefined) {
|
|
168253
|
-
delete lock.packages[key];
|
|
168254
|
-
modified = true;
|
|
168255
|
-
}
|
|
168256
|
-
}
|
|
168257
|
-
if (lock.dependencies?.[packageName]) {
|
|
168258
|
-
delete lock.dependencies[packageName];
|
|
168259
|
-
modified = true;
|
|
168260
|
-
}
|
|
168261
|
-
if (modified) {
|
|
168262
|
-
writeFileSync3(lockPath, JSON.stringify(lock, null, 2));
|
|
168263
|
-
log(`[auto-update-checker] Removed from package-lock.json: ${packageName}`);
|
|
168264
|
-
}
|
|
168265
|
-
return modified;
|
|
168266
|
-
} catch {
|
|
168267
|
-
return false;
|
|
168268
|
-
}
|
|
168269
|
-
}
|
|
168270
|
-
function ensureDependencyVersion(packageJsonPath, packageName, version2) {
|
|
168271
|
-
if (!existsSync9(packageJsonPath))
|
|
168272
|
-
return false;
|
|
168273
|
-
try {
|
|
168274
|
-
const raw = import_comment_json2.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
168275
|
-
const pkgJson = PackageJsonSchema.safeParse(raw);
|
|
168276
|
-
if (!pkgJson.success)
|
|
168277
|
-
return false;
|
|
168278
|
-
const nextPackageJson = { ...pkgJson.data };
|
|
168279
|
-
const dependencies = { ...nextPackageJson.dependencies ?? {} };
|
|
168280
|
-
if (dependencies[packageName] === version2)
|
|
168281
|
-
return true;
|
|
168282
|
-
dependencies[packageName] = version2;
|
|
168283
|
-
nextPackageJson.dependencies = dependencies;
|
|
168284
|
-
writeFileSync3(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
|
|
168285
|
-
log(`[auto-update-checker] Updated dependency in package.json: ${packageName} → ${version2}`);
|
|
168286
|
-
return true;
|
|
168287
|
-
} catch (err) {
|
|
168288
|
-
warn2(`[auto-update-checker] Failed to update package.json dependency: ${String(err)}`);
|
|
168289
|
-
return false;
|
|
168290
|
-
}
|
|
168291
|
-
}
|
|
168292
|
-
function removeInstalledPackage(installDir, packageName) {
|
|
168293
|
-
const packageDir = join10(installDir, "node_modules", packageName);
|
|
168294
|
-
if (!existsSync9(packageDir))
|
|
168295
|
-
return false;
|
|
168296
|
-
rmSync(packageDir, { recursive: true, force: true });
|
|
168297
|
-
log(`[auto-update-checker] Package removed: ${packageDir}`);
|
|
168298
|
-
return true;
|
|
168299
|
-
}
|
|
168300
|
-
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
168301
|
-
if (runtimePackageJsonPath) {
|
|
168302
|
-
const packageDir = dirname5(runtimePackageJsonPath);
|
|
168303
|
-
const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
|
|
168304
|
-
if (nodeModulesDir && basename(nodeModulesDir) === "node_modules") {
|
|
168305
|
-
const installDir = dirname5(nodeModulesDir);
|
|
168306
|
-
const packageJsonPath = join10(installDir, "package.json");
|
|
168307
|
-
if (!existsSync9(packageJsonPath)) {
|
|
168308
|
-
try {
|
|
168309
|
-
writeFileSync3(packageJsonPath, `${JSON.stringify({ private: true, dependencies: {} }, null, 2)}
|
|
168310
|
-
`);
|
|
168311
|
-
log(`[auto-update-checker] Seeded missing package.json at ${packageJsonPath} (issue #73)`);
|
|
168312
|
-
} catch (err) {
|
|
168313
|
-
warn2(`[auto-update-checker] Could not seed package.json at ${packageJsonPath}: ${String(err)}`);
|
|
168314
|
-
return null;
|
|
168315
|
-
}
|
|
168316
|
-
}
|
|
168317
|
-
return { installDir, packageJsonPath };
|
|
168318
|
-
}
|
|
168319
|
-
return null;
|
|
168320
|
-
}
|
|
168321
|
-
const legacyPackageJsonPath = join10(dirname5(CACHE_DIR), "package.json");
|
|
168322
|
-
if (existsSync9(legacyPackageJsonPath)) {
|
|
168323
|
-
return { installDir: dirname5(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
|
|
168324
|
-
}
|
|
168325
|
-
return null;
|
|
168326
|
-
}
|
|
168327
|
-
function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
168328
|
-
try {
|
|
168329
|
-
const installContext = resolveInstallContext(runtimePackageJsonPath);
|
|
168330
|
-
if (!installContext) {
|
|
168331
|
-
warn2("[auto-update-checker] No install context found for auto-update");
|
|
168332
|
-
return null;
|
|
168333
|
-
}
|
|
168334
|
-
if (!ensureDependencyVersion(installContext.packageJsonPath, packageName, version2))
|
|
168335
|
-
return null;
|
|
168336
|
-
const packageRemoved = removeInstalledPackage(installContext.installDir, packageName);
|
|
168337
|
-
const lockRemoved = removeFromPackageLock(installContext.installDir, packageName);
|
|
168338
|
-
if (!packageRemoved && !lockRemoved) {
|
|
168339
|
-
log(`[auto-update-checker] No cached package artifacts removed for ${packageName}; continuing with updated dependency spec`);
|
|
168340
|
-
}
|
|
168341
|
-
return installContext.installDir;
|
|
168342
|
-
} catch (err) {
|
|
168343
|
-
warn2(`[auto-update-checker] Failed to prepare package update: ${String(err)}`);
|
|
168344
|
-
return null;
|
|
168345
|
-
}
|
|
168346
|
-
}
|
|
168347
|
-
async function runNpmInstallSafe(installDir, options = {}) {
|
|
168348
|
-
let timeout = null;
|
|
168349
|
-
try {
|
|
168350
|
-
if (options.signal?.aborted)
|
|
168351
|
-
return false;
|
|
168352
|
-
const proc = spawn("npm", ["install", "--no-audit", "--no-fund", "--no-progress"], {
|
|
168353
|
-
cwd: installDir,
|
|
168354
|
-
stdio: "pipe"
|
|
168355
|
-
});
|
|
168356
|
-
const abortProcess = () => {
|
|
168357
|
-
try {
|
|
168358
|
-
proc.kill();
|
|
168359
|
-
} catch {}
|
|
168360
|
-
};
|
|
168361
|
-
options.signal?.addEventListener("abort", abortProcess, { once: true });
|
|
168362
|
-
const exitPromise = new Promise((resolveExit) => {
|
|
168363
|
-
proc.on("error", () => resolveExit(false));
|
|
168364
|
-
proc.on("exit", (code) => resolveExit(code === 0));
|
|
168365
|
-
});
|
|
168366
|
-
const timeoutPromise = new Promise((resolveTimeout) => {
|
|
168367
|
-
timeout = setTimeout(() => resolveTimeout("timeout"), options.timeoutMs ?? 60000);
|
|
168368
|
-
});
|
|
168369
|
-
const result = await Promise.race([exitPromise, timeoutPromise]);
|
|
168370
|
-
options.signal?.removeEventListener("abort", abortProcess);
|
|
168371
|
-
if (result === "timeout" || options.signal?.aborted) {
|
|
168372
|
-
abortProcess();
|
|
168373
|
-
return false;
|
|
168374
|
-
}
|
|
168375
|
-
return result;
|
|
168376
|
-
} catch (err) {
|
|
168377
|
-
warn2(`[auto-update-checker] npm install error: ${String(err)}`);
|
|
168378
|
-
return false;
|
|
168379
|
-
} finally {
|
|
168380
|
-
if (timeout)
|
|
168381
|
-
clearTimeout(timeout);
|
|
168382
|
-
}
|
|
168383
|
-
}
|
|
168384
|
-
|
|
168385
|
-
// src/hooks/auto-update-checker/index.ts
|
|
168386
|
-
var DEFAULT_CHECK_INTERVAL_MS = 60 * 60 * 1000;
|
|
168387
|
-
var DEFAULT_INIT_DELAY_MS = 5000;
|
|
168388
|
-
var TIMESTAMP_FILENAME = "last-update-check.json";
|
|
168389
|
-
function warn3(message) {
|
|
168390
|
-
log(`WARN: ${message}`);
|
|
168391
|
-
}
|
|
168392
|
-
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
168393
|
-
const {
|
|
168394
|
-
enabled = true,
|
|
168395
|
-
showStartupToast = true,
|
|
168396
|
-
autoUpdate = true,
|
|
168397
|
-
npmRegistryUrl = NPM_REGISTRY_URL,
|
|
168398
|
-
fetchTimeoutMs = NPM_FETCH_TIMEOUT,
|
|
168399
|
-
signal = new AbortController().signal,
|
|
168400
|
-
storageDir = null,
|
|
168401
|
-
checkIntervalMs = DEFAULT_CHECK_INTERVAL_MS,
|
|
168402
|
-
initDelayMs = DEFAULT_INIT_DELAY_MS
|
|
168403
|
-
} = options;
|
|
168404
|
-
if (!enabled) {
|
|
168405
|
-
return async (_input) => {};
|
|
168406
|
-
}
|
|
168407
|
-
const initTimer = setTimeout(() => {
|
|
168408
|
-
maybeRunCheck(ctx, {
|
|
168409
|
-
showStartupToast,
|
|
168410
|
-
autoUpdate,
|
|
168411
|
-
npmRegistryUrl,
|
|
168412
|
-
fetchTimeoutMs,
|
|
168413
|
-
signal,
|
|
168414
|
-
storageDir,
|
|
168415
|
-
checkIntervalMs,
|
|
168416
|
-
initDelayMs
|
|
168417
|
-
}).catch((err) => {
|
|
168418
|
-
warn3(`[auto-update-checker] Background update check failed: ${String(err)}`);
|
|
168419
|
-
});
|
|
168420
|
-
}, initDelayMs);
|
|
168421
|
-
if (typeof initTimer === "object" && initTimer !== null && "unref" in initTimer) {
|
|
168422
|
-
initTimer.unref();
|
|
168423
|
-
}
|
|
168424
|
-
signal.addEventListener("abort", () => {
|
|
168425
|
-
clearTimeout(initTimer);
|
|
168426
|
-
}, { once: true });
|
|
168427
|
-
return async (_input) => {};
|
|
168428
|
-
}
|
|
168429
|
-
async function maybeRunCheck(ctx, options) {
|
|
168430
|
-
if (options.signal.aborted)
|
|
168431
|
-
return;
|
|
168432
|
-
if (!claimCheckSlot(options.storageDir, options.checkIntervalMs)) {
|
|
168433
|
-
log("[auto-update-checker] Skipping check (another instance ran one recently)");
|
|
168434
|
-
return;
|
|
168435
|
-
}
|
|
168436
|
-
await runStartupCheck(ctx, options);
|
|
168437
|
-
}
|
|
168438
|
-
function claimCheckSlot(storageDir, intervalMs) {
|
|
168439
|
-
if (!storageDir)
|
|
168440
|
-
return true;
|
|
168441
|
-
try {
|
|
168442
|
-
const file2 = join11(storageDir, TIMESTAMP_FILENAME);
|
|
168443
|
-
if (existsSync10(file2)) {
|
|
168444
|
-
try {
|
|
168445
|
-
const raw = JSON.parse(readFileSync7(file2, "utf-8"));
|
|
168446
|
-
const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
|
|
168447
|
-
if (Number.isFinite(last) && Date.now() - last < intervalMs) {
|
|
168448
|
-
return false;
|
|
168449
|
-
}
|
|
168450
|
-
} catch {}
|
|
168451
|
-
}
|
|
168452
|
-
mkdirSync4(dirname6(file2), { recursive: true });
|
|
168453
|
-
const tmp = `${file2}.tmp.${process.pid}`;
|
|
168454
|
-
writeFileSync4(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
|
|
168455
|
-
renameSync(tmp, file2);
|
|
168456
|
-
return true;
|
|
168457
|
-
} catch (err) {
|
|
168458
|
-
warn3(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
|
|
168459
|
-
return true;
|
|
168460
|
-
}
|
|
168461
|
-
}
|
|
168462
|
-
async function runStartupCheck(ctx, options) {
|
|
168463
|
-
if (options.signal.aborted)
|
|
168464
|
-
return;
|
|
168465
|
-
const cachedVersion = getCachedVersion();
|
|
168466
|
-
const localDevVersion = getLocalDevVersion(ctx.directory);
|
|
168467
|
-
const displayVersion = localDevVersion ?? cachedVersion;
|
|
168468
|
-
if (localDevVersion) {
|
|
168469
|
-
if (options.showStartupToast) {
|
|
168470
|
-
showToast(ctx, `Magic Context ${displayVersion} (dev)`, "Running in local development mode.", "info");
|
|
168471
|
-
}
|
|
168472
|
-
log("[auto-update-checker] Local development mode");
|
|
168473
|
-
return;
|
|
168474
|
-
}
|
|
168475
|
-
if (options.showStartupToast) {
|
|
168476
|
-
showToast(ctx, `Magic Context ${displayVersion ?? "unknown"}`, "@wolfx/opencode-magic-context is active.", "info");
|
|
168477
|
-
}
|
|
168478
|
-
await runBackgroundUpdateCheck(ctx, options);
|
|
168479
|
-
}
|
|
168480
|
-
async function runBackgroundUpdateCheck(ctx, options) {
|
|
168481
|
-
if (options.signal.aborted)
|
|
168482
|
-
return;
|
|
168483
|
-
const pluginInfo = findPluginEntry(ctx.directory);
|
|
168484
|
-
if (!pluginInfo) {
|
|
168485
|
-
log("[auto-update-checker] Plugin not found in config");
|
|
168486
|
-
return;
|
|
168487
|
-
}
|
|
168488
|
-
const cachedVersion = getCachedVersion(pluginInfo.entry);
|
|
168489
|
-
const currentVersion = cachedVersion ?? pluginInfo.pinnedVersion;
|
|
168490
|
-
if (!currentVersion) {
|
|
168491
|
-
log("[auto-update-checker] No version found (cached or pinned)");
|
|
168492
|
-
return;
|
|
168493
|
-
}
|
|
168494
|
-
const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion);
|
|
168495
|
-
const latestVersion = await getLatestVersion(channel, {
|
|
168496
|
-
registryUrl: options.npmRegistryUrl,
|
|
168497
|
-
timeoutMs: options.fetchTimeoutMs,
|
|
168498
|
-
signal: options.signal
|
|
168499
|
-
});
|
|
168500
|
-
if (!latestVersion) {
|
|
168501
|
-
warn3(`[auto-update-checker] Failed to fetch latest version for channel: ${channel}`);
|
|
168502
|
-
showToast(ctx, "Magic Context update check failed", "Could not check npm for @wolfx/opencode-magic-context updates. Continuing with the cached version.", "warning", 8000);
|
|
168503
|
-
return;
|
|
168504
|
-
}
|
|
168505
|
-
if (currentVersion === latestVersion) {
|
|
168506
|
-
log(`[auto-update-checker] Already on latest version for channel: ${channel}`);
|
|
168507
|
-
return;
|
|
168508
|
-
}
|
|
168509
|
-
log(`[auto-update-checker] Update available (${channel}): ${currentVersion} → ${latestVersion}`);
|
|
168510
|
-
if (pluginInfo.isPinned) {
|
|
168511
|
-
showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. Version is pinned; update your OpenCode plugin config to upgrade.`, "info", 8000);
|
|
168512
|
-
log("[auto-update-checker] Version is pinned; skipping auto-update");
|
|
168513
|
-
return;
|
|
168514
|
-
}
|
|
168515
|
-
if (!options.autoUpdate) {
|
|
168516
|
-
showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. Auto-update is disabled.`, "info", 8000);
|
|
168517
|
-
log("[auto-update-checker] Auto-update disabled, notification only");
|
|
168518
|
-
return;
|
|
168519
|
-
}
|
|
168520
|
-
const installDir = preparePackageUpdate(latestVersion, PACKAGE_NAME);
|
|
168521
|
-
if (!installDir) {
|
|
168522
|
-
showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. Auto-update could not prepare the active install.`, "warning", 8000);
|
|
168523
|
-
warn3("[auto-update-checker] Failed to prepare install root for auto-update");
|
|
168524
|
-
return;
|
|
168525
|
-
}
|
|
168526
|
-
const installSuccess = await runNpmInstallSafe(installDir, { signal: options.signal });
|
|
168527
|
-
if (installSuccess) {
|
|
168528
|
-
showToast(ctx, "Magic Context Updated!", `v${currentVersion} → v${latestVersion}
|
|
168529
|
-
Restart OpenCode to apply.`, "success", 8000);
|
|
168530
|
-
log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
|
|
168531
|
-
return;
|
|
168532
|
-
}
|
|
168533
|
-
showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
|
|
168534
|
-
warn3("[auto-update-checker] npm install failed; update not installed");
|
|
168535
|
-
}
|
|
168536
|
-
function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
168537
|
-
ctx.client.tui.showToast({ body: { title, message, variant, duration: duration3 } }).catch(() => {});
|
|
168538
|
-
}
|
|
168539
|
-
|
|
168540
|
-
// src/index.ts
|
|
168541
167944
|
init_compartment_prompt();
|
|
168542
167945
|
|
|
168543
167946
|
// src/hooks/magic-context/live-session-state.ts
|
|
@@ -168739,27 +168142,27 @@ init_assistant_message_extractor();
|
|
|
168739
168142
|
init_data_path();
|
|
168740
168143
|
init_logger();
|
|
168741
168144
|
await init_sqlite();
|
|
168742
|
-
import { existsSync as
|
|
168743
|
-
import { join as
|
|
168145
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
168146
|
+
import { join as join13 } from "node:path";
|
|
168744
168147
|
|
|
168745
168148
|
// src/features/magic-context/key-files/identify-key-files.ts
|
|
168746
168149
|
init_read_session_formatting();
|
|
168747
168150
|
init_shared();
|
|
168748
168151
|
init_assistant_message_extractor();
|
|
168749
168152
|
init_logger();
|
|
168750
|
-
import { readFileSync as
|
|
168751
|
-
import { join as
|
|
168153
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
168154
|
+
import { join as join12 } from "node:path";
|
|
168752
168155
|
|
|
168753
168156
|
// src/features/magic-context/key-files/aft-availability.ts
|
|
168754
|
-
var
|
|
168755
|
-
import { existsSync as
|
|
168756
|
-
import { homedir as
|
|
168757
|
-
import { join as
|
|
168157
|
+
var import_comment_json = __toESM(require_src2(), 1);
|
|
168158
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
168159
|
+
import { homedir as homedir6 } from "node:os";
|
|
168160
|
+
import { join as join11 } from "node:path";
|
|
168758
168161
|
var overrideAvailability = null;
|
|
168759
168162
|
function parseConfig(path5) {
|
|
168760
|
-
if (!
|
|
168163
|
+
if (!existsSync9(path5))
|
|
168761
168164
|
return null;
|
|
168762
|
-
return
|
|
168165
|
+
return import_comment_json.parse(readFileSync6(path5, "utf-8"));
|
|
168763
168166
|
}
|
|
168764
168167
|
function entryMatchesAft(entry) {
|
|
168765
168168
|
const value = Array.isArray(entry) ? entry[0] : entry;
|
|
@@ -168779,12 +168182,12 @@ function hasAftAtKeys(value, keys) {
|
|
|
168779
168182
|
return false;
|
|
168780
168183
|
}
|
|
168781
168184
|
function getAftAvailability() {
|
|
168782
|
-
const home = process.env.HOME ||
|
|
168185
|
+
const home = process.env.HOME || homedir6();
|
|
168783
168186
|
const opencodePaths = [
|
|
168784
|
-
|
|
168785
|
-
|
|
168187
|
+
join11(home, ".config", "opencode", "opencode.jsonc"),
|
|
168188
|
+
join11(home, ".config", "opencode", "opencode.json")
|
|
168786
168189
|
];
|
|
168787
|
-
const piPaths = [
|
|
168190
|
+
const piPaths = [join11(home, ".pi", "agent", "settings.json")];
|
|
168788
168191
|
const checkedPaths = [...opencodePaths, ...piPaths];
|
|
168789
168192
|
let opencode = false;
|
|
168790
168193
|
for (const path5 of opencodePaths) {
|
|
@@ -168828,7 +168231,7 @@ init_project_key_files();
|
|
|
168828
168231
|
|
|
168829
168232
|
// src/features/magic-context/key-files/read-history.ts
|
|
168830
168233
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
168831
|
-
import { relative, resolve as
|
|
168234
|
+
import { relative, resolve as resolve4 } from "node:path";
|
|
168832
168235
|
function toMs(value) {
|
|
168833
168236
|
if (typeof value === "number" && Number.isFinite(value))
|
|
168834
168237
|
return value;
|
|
@@ -168865,7 +168268,7 @@ function coalesceRanges(ranges) {
|
|
|
168865
168268
|
}
|
|
168866
168269
|
function normalizeProjectRelativePath(projectPath, filePath) {
|
|
168867
168270
|
const root = realpathSync2(projectPath);
|
|
168868
|
-
const abs = filePath.startsWith("/") ?
|
|
168271
|
+
const abs = filePath.startsWith("/") ? resolve4(filePath) : resolve4(root, filePath);
|
|
168869
168272
|
let real = abs;
|
|
168870
168273
|
try {
|
|
168871
168274
|
real = realpathSync2(abs);
|
|
@@ -169251,7 +168654,7 @@ async function runKeyFilesTask(args) {
|
|
|
169251
168654
|
if (row.staleReason !== null || row.generationConfigHash !== configHash)
|
|
169252
168655
|
return false;
|
|
169253
168656
|
try {
|
|
169254
|
-
return sha256(
|
|
168657
|
+
return sha256(readFileSync7(join12(projectPath, row.path))) === row.contentHash;
|
|
169255
168658
|
} catch {
|
|
169256
168659
|
return false;
|
|
169257
168660
|
}
|
|
@@ -169558,11 +168961,11 @@ function logWithStackHead(message, stackHead) {
|
|
|
169558
168961
|
log(message, stackHead ? { stackHead } : undefined);
|
|
169559
168962
|
}
|
|
169560
168963
|
function getOpenCodeDbPath2() {
|
|
169561
|
-
return
|
|
168964
|
+
return join13(getDataDir(), "opencode", "opencode.db");
|
|
169562
168965
|
}
|
|
169563
168966
|
function openOpenCodeDb() {
|
|
169564
168967
|
const dbPath = getOpenCodeDbPath2();
|
|
169565
|
-
if (!
|
|
168968
|
+
if (!existsSync10(dbPath)) {
|
|
169566
168969
|
log(`[key-files] OpenCode DB not found at ${dbPath} — skipping`);
|
|
169567
168970
|
return null;
|
|
169568
168971
|
}
|
|
@@ -169711,8 +169114,8 @@ async function runDream(args) {
|
|
|
169711
169114
|
try {
|
|
169712
169115
|
const docsDir = args.sessionDirectory ?? args.projectIdentity;
|
|
169713
169116
|
const existingDocs = taskName === "maintain-docs" ? {
|
|
169714
|
-
architecture:
|
|
169715
|
-
structure:
|
|
169117
|
+
architecture: existsSync10(join13(docsDir, "ARCHITECTURE.md")),
|
|
169118
|
+
structure: existsSync10(join13(docsDir, "STRUCTURE.md"))
|
|
169716
169119
|
} : undefined;
|
|
169717
169120
|
const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
|
|
169718
169121
|
id: um.id,
|
|
@@ -170968,7 +170371,6 @@ async function ensureProjectRegisteredFromOpenCodeDirectory(directory, db) {
|
|
|
170968
170371
|
// src/plugin/event.ts
|
|
170969
170372
|
function createEventHandler(args) {
|
|
170970
170373
|
return async (input) => {
|
|
170971
|
-
await args.autoUpdateChecker?.(input);
|
|
170972
170374
|
await args.magicContext?.event?.(input);
|
|
170973
170375
|
};
|
|
170974
170376
|
}
|
|
@@ -173181,7 +172583,7 @@ async function runCompartmentPhase(args) {
|
|
|
173181
172583
|
async function awaitCompartmentRun(activeRun, reason) {
|
|
173182
172584
|
sessionLog(args.sessionId, reason);
|
|
173183
172585
|
const timeoutMs = args.historianTimeoutMs ?? 120000;
|
|
173184
|
-
const timeout = new Promise((
|
|
172586
|
+
const timeout = new Promise((resolve5) => setTimeout(() => resolve5("timeout"), timeoutMs));
|
|
173185
172587
|
const result = await Promise.race([activeRun.promise.then(() => "done"), timeout]);
|
|
173186
172588
|
if (result === "timeout") {
|
|
173187
172589
|
sessionLog(args.sessionId, `transform: compartment await timed out after ${timeoutMs}ms — proceeding without waiting`);
|
|
@@ -174632,10 +174034,10 @@ var AUTO_SEARCH_TIMEOUT_MS = 3000;
|
|
|
174632
174034
|
async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
|
|
174633
174035
|
const controller = new AbortController;
|
|
174634
174036
|
let timer;
|
|
174635
|
-
const timeoutPromise = new Promise((
|
|
174037
|
+
const timeoutPromise = new Promise((resolve5) => {
|
|
174636
174038
|
timer = setTimeout(() => {
|
|
174637
174039
|
controller.abort();
|
|
174638
|
-
|
|
174040
|
+
resolve5(null);
|
|
174639
174041
|
}, timeoutMs);
|
|
174640
174042
|
});
|
|
174641
174043
|
try {
|
|
@@ -176697,11 +176099,24 @@ function estimateProjectedPercentage(db, sessionId, contextUsage, preloadedTags)
|
|
|
176697
176099
|
await init_read_session_db();
|
|
176698
176100
|
|
|
176699
176101
|
// src/hooks/magic-context/text-complete.ts
|
|
176700
|
-
var
|
|
176701
|
-
|
|
176702
|
-
|
|
176102
|
+
var DEFAULT_PATTERNS = [
|
|
176103
|
+
/^(\u00a7\d+\u00a7\s*)+/,
|
|
176104
|
+
/\u00a7/g
|
|
176105
|
+
];
|
|
176106
|
+
function createTextCompleteHandler(stripConfig) {
|
|
176107
|
+
const isEnabled = stripConfig?.enabled ?? true;
|
|
176108
|
+
let patterns;
|
|
176109
|
+
if (!isEnabled) {
|
|
176110
|
+
patterns = [];
|
|
176111
|
+
} else if (stripConfig?.patterns && stripConfig.patterns.length > 0) {
|
|
176112
|
+
patterns = stripConfig.patterns.map((src) => new RegExp(src));
|
|
176113
|
+
} else {
|
|
176114
|
+
patterns = DEFAULT_PATTERNS;
|
|
176115
|
+
}
|
|
176703
176116
|
return async (_input, output) => {
|
|
176704
|
-
|
|
176117
|
+
for (const regex of patterns) {
|
|
176118
|
+
output.text = output.text.replace(regex, "");
|
|
176119
|
+
}
|
|
176705
176120
|
};
|
|
176706
176121
|
}
|
|
176707
176122
|
|
|
@@ -176924,8 +176339,8 @@ init_send_session_notification();
|
|
|
176924
176339
|
|
|
176925
176340
|
// src/hooks/magic-context/system-prompt-hash.ts
|
|
176926
176341
|
import { createHash as createHash9 } from "node:crypto";
|
|
176927
|
-
import { existsSync as
|
|
176928
|
-
import { join as
|
|
176342
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
176343
|
+
import { join as join21 } from "node:path";
|
|
176929
176344
|
|
|
176930
176345
|
// src/agents/magic-context-prompt.ts
|
|
176931
176346
|
function getToolHistoryGuidance(dropToolStructure) {
|
|
@@ -177021,8 +176436,8 @@ await init_storage();
|
|
|
177021
176436
|
|
|
177022
176437
|
// src/hooks/magic-context/key-files-block.ts
|
|
177023
176438
|
init_compartment_storage();
|
|
177024
|
-
import { readFileSync as
|
|
177025
|
-
import { join as
|
|
176439
|
+
import { readFileSync as readFileSync9, realpathSync as realpathSync3 } from "node:fs";
|
|
176440
|
+
import { join as join20, sep as sep2 } from "node:path";
|
|
177026
176441
|
init_project_key_files();
|
|
177027
176442
|
init_logger();
|
|
177028
176443
|
var cachedKeyFilesBySession = new Map;
|
|
@@ -177076,13 +176491,13 @@ function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBud
|
|
|
177076
176491
|
let nextStale = null;
|
|
177077
176492
|
let observed = false;
|
|
177078
176493
|
try {
|
|
177079
|
-
const absPath =
|
|
176494
|
+
const absPath = join20(projectPath, row.path);
|
|
177080
176495
|
const real = realpathSync3(absPath);
|
|
177081
176496
|
if (!isUnderProject(projectPath, real)) {
|
|
177082
176497
|
nextStale = "missing";
|
|
177083
176498
|
observed = true;
|
|
177084
176499
|
} else {
|
|
177085
|
-
const diskHash = sha256(
|
|
176500
|
+
const diskHash = sha256(readFileSync9(real));
|
|
177086
176501
|
if (diskHash !== row.contentHash)
|
|
177087
176502
|
nextStale = "content_drift";
|
|
177088
176503
|
observed = true;
|
|
@@ -177166,10 +176581,10 @@ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
|
|
|
177166
176581
|
function readProjectDocs(directory) {
|
|
177167
176582
|
const sections = [];
|
|
177168
176583
|
for (const filename of DOC_FILES) {
|
|
177169
|
-
const filePath =
|
|
176584
|
+
const filePath = join21(directory, filename);
|
|
177170
176585
|
try {
|
|
177171
|
-
if (
|
|
177172
|
-
const content =
|
|
176586
|
+
if (existsSync12(filePath)) {
|
|
176587
|
+
const content = readFileSync10(filePath, "utf-8").trim();
|
|
177173
176588
|
if (content.length > 0) {
|
|
177174
176589
|
sections.push(`<${filename}>
|
|
177175
176590
|
${escapeXmlContent(content)}
|
|
@@ -177676,7 +177091,7 @@ function createMagicContextHook(deps) {
|
|
|
177676
177091
|
return {
|
|
177677
177092
|
"experimental.chat.messages.transform": transform2,
|
|
177678
177093
|
"experimental.chat.system.transform": systemPromptHashHandler,
|
|
177679
|
-
"experimental.text.complete": createTextCompleteHandler(),
|
|
177094
|
+
"experimental.text.complete": createTextCompleteHandler(deps.config.experimental?.text_complete_strip),
|
|
177680
177095
|
"chat.message": createChatMessageHook({
|
|
177681
177096
|
db,
|
|
177682
177097
|
toolUsageSinceUserTurn,
|
|
@@ -179392,28 +178807,28 @@ init_models_dev_cache();
|
|
|
179392
178807
|
// src/shared/rpc-server.ts
|
|
179393
178808
|
init_logger();
|
|
179394
178809
|
import {
|
|
179395
|
-
mkdirSync as
|
|
178810
|
+
mkdirSync as mkdirSync8,
|
|
179396
178811
|
readdirSync,
|
|
179397
|
-
readFileSync as
|
|
179398
|
-
renameSync
|
|
178812
|
+
readFileSync as readFileSync12,
|
|
178813
|
+
renameSync,
|
|
179399
178814
|
unlinkSync as unlinkSync3,
|
|
179400
|
-
writeFileSync as
|
|
178815
|
+
writeFileSync as writeFileSync5
|
|
179401
178816
|
} from "node:fs";
|
|
179402
178817
|
import { createServer } from "node:http";
|
|
179403
|
-
import { dirname as
|
|
178818
|
+
import { dirname as dirname5 } from "node:path";
|
|
179404
178819
|
|
|
179405
178820
|
// src/shared/rpc-utils.ts
|
|
179406
178821
|
import { createHash as createHash10 } from "node:crypto";
|
|
179407
|
-
import { join as
|
|
178822
|
+
import { join as join23 } from "node:path";
|
|
179408
178823
|
function projectHash(directory) {
|
|
179409
178824
|
const normalized = directory.replace(/\/+$/, "");
|
|
179410
178825
|
return createHash10("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
179411
178826
|
}
|
|
179412
178827
|
function rpcPortDir(storageDir, directory) {
|
|
179413
|
-
return
|
|
178828
|
+
return join23(storageDir, "rpc", projectHash(directory));
|
|
179414
178829
|
}
|
|
179415
178830
|
function rpcPortFilePath(storageDir, directory, pid = process.pid) {
|
|
179416
|
-
return
|
|
178831
|
+
return join23(rpcPortDir(storageDir, directory), `port-${pid}.json`);
|
|
179417
178832
|
}
|
|
179418
178833
|
function isPidAlive(pid) {
|
|
179419
178834
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -179471,7 +178886,7 @@ class MagicContextRpcServer {
|
|
|
179471
178886
|
this.handlers.set(method, handler);
|
|
179472
178887
|
}
|
|
179473
178888
|
async start() {
|
|
179474
|
-
return new Promise((
|
|
178889
|
+
return new Promise((resolve5, reject) => {
|
|
179475
178890
|
const server = createServer((req, res) => this.dispatch(req, res));
|
|
179476
178891
|
server.on("error", (err) => {
|
|
179477
178892
|
log(`[rpc] server error: ${err.message}`);
|
|
@@ -179487,20 +178902,20 @@ class MagicContextRpcServer {
|
|
|
179487
178902
|
this.server = server;
|
|
179488
178903
|
try {
|
|
179489
178904
|
this.warnIfOtherLiveInstance();
|
|
179490
|
-
const dir =
|
|
179491
|
-
|
|
178905
|
+
const dir = dirname5(this.portFilePath);
|
|
178906
|
+
mkdirSync8(dir, { recursive: true });
|
|
179492
178907
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
179493
|
-
|
|
178908
|
+
writeFileSync5(tmpPath, JSON.stringify({
|
|
179494
178909
|
port: this.port,
|
|
179495
178910
|
pid: process.pid,
|
|
179496
178911
|
started_at: this.startedAt
|
|
179497
178912
|
}), "utf-8");
|
|
179498
|
-
|
|
178913
|
+
renameSync(tmpPath, this.portFilePath);
|
|
179499
178914
|
log(`[rpc] server listening on 127.0.0.1:${this.port}`);
|
|
179500
178915
|
} catch (err) {
|
|
179501
178916
|
log(`[rpc] failed to write port file: ${err}`);
|
|
179502
178917
|
}
|
|
179503
|
-
|
|
178918
|
+
resolve5(this.port);
|
|
179504
178919
|
});
|
|
179505
178920
|
server.unref();
|
|
179506
178921
|
});
|
|
@@ -179510,7 +178925,7 @@ class MagicContextRpcServer {
|
|
|
179510
178925
|
for (const entry of readdirSync(this.portDir)) {
|
|
179511
178926
|
if (!entry.startsWith("port-") || !entry.endsWith(".json"))
|
|
179512
178927
|
continue;
|
|
179513
|
-
const record2 = parseRpcPortFile(
|
|
178928
|
+
const record2 = parseRpcPortFile(readFileSync12(`${this.portDir}/${entry}`, "utf-8"));
|
|
179514
178929
|
if (!record2 || record2.pid === process.pid || !isPidAlive(record2.pid))
|
|
179515
178930
|
continue;
|
|
179516
178931
|
log(`[rpc] another Magic Context RPC server is active for this project (pid ${record2.pid}, port ${record2.port}); starting separate instance on a new port`);
|
|
@@ -179582,10 +178997,6 @@ class MagicContextRpcServer {
|
|
|
179582
178997
|
// src/index.ts
|
|
179583
178998
|
var plugin = async (ctx) => {
|
|
179584
178999
|
const pluginConfig = loadPluginConfig(ctx.directory);
|
|
179585
|
-
const autoUpdateAbort = new AbortController;
|
|
179586
|
-
process.once("exit", () => {
|
|
179587
|
-
autoUpdateAbort.abort();
|
|
179588
|
-
});
|
|
179589
179000
|
if (pluginConfig.configWarnings?.length) {
|
|
179590
179001
|
for (const w of pluginConfig.configWarnings) {
|
|
179591
179002
|
log(`[magic-context] config warning: ${w}`);
|
|
@@ -179699,12 +179110,7 @@ var plugin = async (ctx) => {
|
|
|
179699
179110
|
return {
|
|
179700
179111
|
tool: tools5,
|
|
179701
179112
|
event: createEventHandler({
|
|
179702
|
-
magicContext: hooks.magicContext
|
|
179703
|
-
autoUpdateChecker: createAutoUpdateCheckerHook(ctx, {
|
|
179704
|
-
autoUpdate: pluginConfig.auto_update !== false,
|
|
179705
|
-
signal: autoUpdateAbort.signal,
|
|
179706
|
-
storageDir
|
|
179707
|
-
})
|
|
179113
|
+
magicContext: hooks.magicContext
|
|
179708
179114
|
}),
|
|
179709
179115
|
"experimental.chat.messages.transform": createMessagesTransformHandler({
|
|
179710
179116
|
magicContext: hooks.magicContext
|