@cortexkit/pi-magic-context 0.21.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +790 -475
- package/dist/subagent-entry.js +23 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -143698,6 +143698,13 @@ function getDefaultHistorianFailureState() {
|
|
|
143698
143698
|
lastFailureAt: null
|
|
143699
143699
|
};
|
|
143700
143700
|
}
|
|
143701
|
+
function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
|
|
143702
|
+
ensureSessionMetaRow(db, sessionId);
|
|
143703
|
+
db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
|
|
143704
|
+
}
|
|
143705
|
+
function clearPersistedReasoningWatermark(db, sessionId) {
|
|
143706
|
+
setPersistedReasoningWatermark(db, sessionId, 0);
|
|
143707
|
+
}
|
|
143701
143708
|
function getPersistedStickyTurnReminder(db, sessionId) {
|
|
143702
143709
|
const result = db.prepare("SELECT sticky_turn_reminder_text, sticky_turn_reminder_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
143703
143710
|
if (!isPersistedStickyTurnReminderRow(result)) {
|
|
@@ -143939,6 +143946,12 @@ function clearEmergencyRecovery(db, sessionId) {
|
|
|
143939
143946
|
db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
|
|
143940
143947
|
})();
|
|
143941
143948
|
}
|
|
143949
|
+
function clearDetectedContextLimit(db, sessionId) {
|
|
143950
|
+
db.transaction(() => {
|
|
143951
|
+
ensureSessionMetaRow(db, sessionId);
|
|
143952
|
+
db.prepare("UPDATE session_meta SET detected_context_limit = 0 WHERE session_id = ?").run(sessionId);
|
|
143953
|
+
})();
|
|
143954
|
+
}
|
|
143942
143955
|
function getPersistedCompactionMarkerState(db, sessionId) {
|
|
143943
143956
|
const row = db.prepare("SELECT compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
143944
143957
|
const raw = row?.compaction_marker_state;
|
|
@@ -145455,10 +145468,10 @@ class PiSubagentRunner {
|
|
|
145455
145468
|
});
|
|
145456
145469
|
return;
|
|
145457
145470
|
}
|
|
145458
|
-
if (finalStopReason === "error" || finalStopReason === "aborted") {
|
|
145471
|
+
if (finalStopReason === "error" || finalStopReason === "aborted" || finalStopReason === "length") {
|
|
145459
145472
|
settle({
|
|
145460
145473
|
ok: false,
|
|
145461
|
-
reason: "model_failed",
|
|
145474
|
+
reason: finalStopReason === "length" ? "truncated" : "model_failed",
|
|
145462
145475
|
error: finalErrorMessage ?? `pi assistant stopped with reason "${finalStopReason}"`,
|
|
145463
145476
|
durationMs: Date.now() - startTime,
|
|
145464
145477
|
meta: { stderr: stderr.length > 0 ? stderr : undefined }
|
|
@@ -145580,10 +145593,18 @@ function parsePiEventLine(line) {
|
|
|
145580
145593
|
}
|
|
145581
145594
|
}
|
|
145582
145595
|
function terminateChild(child) {
|
|
145596
|
+
let exited = false;
|
|
145597
|
+
child.once("close", () => {
|
|
145598
|
+
exited = true;
|
|
145599
|
+
});
|
|
145600
|
+
child.once("exit", () => {
|
|
145601
|
+
exited = true;
|
|
145602
|
+
});
|
|
145583
145603
|
child.kill("SIGTERM");
|
|
145584
145604
|
const killHandle = setTimeout(() => {
|
|
145585
|
-
if (!child.
|
|
145605
|
+
if (!exited && child.exitCode === null && child.signalCode === null) {
|
|
145586
145606
|
child.kill("SIGKILL");
|
|
145607
|
+
}
|
|
145587
145608
|
}, 2000);
|
|
145588
145609
|
if (typeof killHandle.unref === "function") {
|
|
145589
145610
|
killHandle.unref();
|
|
@@ -164952,7 +164973,6 @@ function loadPiConfigDetailed(opts = {}) {
|
|
|
164952
164973
|
// src/embedding-bootstrap.ts
|
|
164953
164974
|
async function ensureProjectRegisteredFromPiDirectory(directory, db) {
|
|
164954
164975
|
const projectIdentity = resolveProjectIdentity(directory);
|
|
164955
|
-
invalidateProject(projectIdentity);
|
|
164956
164976
|
const detailed = loadPiConfigDetailed({ cwd: directory });
|
|
164957
164977
|
if (isConfigLoadUntrusted(detailed)) {
|
|
164958
164978
|
handleUntrustedLoad(db, projectIdentity, directory, detailed);
|
|
@@ -164971,6 +164991,7 @@ var sessionsById = new Map;
|
|
|
164971
164991
|
var inFlightDreams = new Set;
|
|
164972
164992
|
var sessionCounter = 0;
|
|
164973
164993
|
var piSubagentRunnerFactory = () => new PiSubagentRunner;
|
|
164994
|
+
var startDreamScheduleTimerFn = startDreamScheduleTimer;
|
|
164974
164995
|
function registerPiDreamerProject(opts) {
|
|
164975
164996
|
if (!opts.config.enabled) {
|
|
164976
164997
|
return;
|
|
@@ -164991,7 +165012,8 @@ function registerPiDreamerProject(opts) {
|
|
|
164991
165012
|
min_reads: opts.config.pin_key_files.min_reads
|
|
164992
165013
|
} : undefined;
|
|
164993
165014
|
let cleanup;
|
|
164994
|
-
|
|
165015
|
+
let cancelled = false;
|
|
165016
|
+
startDreamScheduleTimerFn({
|
|
164995
165017
|
directory: opts.projectDir,
|
|
164996
165018
|
projectIdentity: opts.projectIdentity,
|
|
164997
165019
|
client,
|
|
@@ -165001,6 +165023,10 @@ function registerPiDreamerProject(opts) {
|
|
|
165001
165023
|
gitCommitIndexing: opts.gitCommitIndexing,
|
|
165002
165024
|
ensureRegistered: ensureProjectRegisteredFromPiDirectory
|
|
165003
165025
|
}).then((timerCleanup) => {
|
|
165026
|
+
if (cancelled) {
|
|
165027
|
+
timerCleanup?.();
|
|
165028
|
+
return;
|
|
165029
|
+
}
|
|
165004
165030
|
cleanup = timerCleanup;
|
|
165005
165031
|
});
|
|
165006
165032
|
const runOnce = async () => processDreamQueue({
|
|
@@ -165014,7 +165040,10 @@ function registerPiDreamerProject(opts) {
|
|
|
165014
165040
|
projectIdentity: opts.projectIdentity
|
|
165015
165041
|
});
|
|
165016
165042
|
registeredProjects2.set(opts.projectIdentity, {
|
|
165017
|
-
cleanup: () =>
|
|
165043
|
+
cleanup: () => {
|
|
165044
|
+
cancelled = true;
|
|
165045
|
+
cleanup?.();
|
|
165046
|
+
},
|
|
165018
165047
|
runOnce
|
|
165019
165048
|
});
|
|
165020
165049
|
}
|
|
@@ -165086,6 +165115,7 @@ function createPiDreamerClient(opts) {
|
|
|
165086
165115
|
{ type: "text", text: result.assistantText }
|
|
165087
165116
|
])
|
|
165088
165117
|
];
|
|
165118
|
+
opts.onAdjunctsRefreshNeeded?.(opts.projectIdentity);
|
|
165089
165119
|
} finally {
|
|
165090
165120
|
inFlightDreams.delete(runPromise);
|
|
165091
165121
|
}
|
|
@@ -165189,21 +165219,37 @@ function sendCtxStatusMessage(pi, content, details) {
|
|
|
165189
165219
|
function registerCtxDreamCommand(pi, deps) {
|
|
165190
165220
|
pi.registerCommand("ctx-dream", {
|
|
165191
165221
|
description: "Run a Magic Context dreamer cycle for this project now",
|
|
165192
|
-
handler: async () => {
|
|
165193
|
-
const
|
|
165222
|
+
handler: async (_args, ctx) => {
|
|
165223
|
+
const project = deps.resolveProject?.(ctx) ?? {
|
|
165224
|
+
projectDir: deps.projectDir,
|
|
165225
|
+
projectIdentity: deps.projectIdentity
|
|
165226
|
+
};
|
|
165227
|
+
deps.onProjectSeen?.(project.projectIdentity);
|
|
165228
|
+
if (deps.dreamerEnabled === false) {
|
|
165229
|
+
sendCtxStatusMessage(pi, {
|
|
165230
|
+
title: "/ctx-dream",
|
|
165231
|
+
text: "## /ctx-dream\n\nDreamer is not configured for this project (`dreamer.enabled=false`).",
|
|
165232
|
+
level: "info"
|
|
165233
|
+
}, {
|
|
165234
|
+
projectDir: project.projectDir,
|
|
165235
|
+
projectIdentity: project.projectIdentity
|
|
165236
|
+
});
|
|
165237
|
+
return;
|
|
165238
|
+
}
|
|
165239
|
+
const enqueued = enqueueDream(deps.db, project.projectIdentity, "manual", true);
|
|
165194
165240
|
if (!enqueued) {
|
|
165195
165241
|
sendCtxStatusMessage(pi, {
|
|
165196
165242
|
title: "/ctx-dream",
|
|
165197
165243
|
text: [
|
|
165198
165244
|
"## /ctx-dream",
|
|
165199
165245
|
"",
|
|
165200
|
-
`Dream already queued or running for ${
|
|
165246
|
+
`Dream already queued or running for ${project.projectIdentity}.`
|
|
165201
165247
|
].join(`
|
|
165202
165248
|
`),
|
|
165203
165249
|
level: "info"
|
|
165204
165250
|
}, {
|
|
165205
|
-
projectDir:
|
|
165206
|
-
projectIdentity:
|
|
165251
|
+
projectDir: project.projectDir,
|
|
165252
|
+
projectIdentity: project.projectIdentity,
|
|
165207
165253
|
entry: null
|
|
165208
165254
|
});
|
|
165209
165255
|
return;
|
|
@@ -165213,18 +165259,18 @@ function registerCtxDreamCommand(pi, deps) {
|
|
|
165213
165259
|
text: [
|
|
165214
165260
|
"## /ctx-dream",
|
|
165215
165261
|
"",
|
|
165216
|
-
`Starting dream run #${enqueued.id} for ${
|
|
165217
|
-
`Project directory: ${
|
|
165262
|
+
`Starting dream run #${enqueued.id} for ${project.projectIdentity}…`,
|
|
165263
|
+
`Project directory: ${project.projectDir}`
|
|
165218
165264
|
].join(`
|
|
165219
165265
|
`),
|
|
165220
165266
|
level: "info"
|
|
165221
165267
|
}, {
|
|
165222
|
-
projectDir:
|
|
165223
|
-
projectIdentity:
|
|
165268
|
+
projectDir: project.projectDir,
|
|
165269
|
+
projectIdentity: project.projectIdentity,
|
|
165224
165270
|
entry: enqueued
|
|
165225
165271
|
});
|
|
165226
165272
|
try {
|
|
165227
|
-
const result = await runPiDreamForProject(
|
|
165273
|
+
const result = await runPiDreamForProject(project.projectIdentity);
|
|
165228
165274
|
let summary;
|
|
165229
165275
|
if (!result) {
|
|
165230
165276
|
summary = "Dream queued, but another worker is already processing the queue.";
|
|
@@ -165250,13 +165296,13 @@ function registerCtxDreamCommand(pi, deps) {
|
|
|
165250
165296
|
`),
|
|
165251
165297
|
level: result ? "success" : "info"
|
|
165252
165298
|
}, {
|
|
165253
|
-
projectDir:
|
|
165254
|
-
projectIdentity:
|
|
165299
|
+
projectDir: project.projectDir,
|
|
165300
|
+
projectIdentity: project.projectIdentity,
|
|
165255
165301
|
entry: enqueued
|
|
165256
165302
|
});
|
|
165257
165303
|
} catch (error51) {
|
|
165258
165304
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
165259
|
-
sessionLog(
|
|
165305
|
+
sessionLog(project.projectIdentity, `/ctx-dream failed to drain queue: ${message}`);
|
|
165260
165306
|
sendCtxStatusMessage(pi, {
|
|
165261
165307
|
title: "/ctx-dream",
|
|
165262
165308
|
text: [
|
|
@@ -165268,8 +165314,8 @@ function registerCtxDreamCommand(pi, deps) {
|
|
|
165268
165314
|
`),
|
|
165269
165315
|
level: "error"
|
|
165270
165316
|
}, {
|
|
165271
|
-
projectDir:
|
|
165272
|
-
projectIdentity:
|
|
165317
|
+
projectDir: project.projectDir,
|
|
165318
|
+
projectIdentity: project.projectIdentity,
|
|
165273
165319
|
entry: enqueued
|
|
165274
165320
|
});
|
|
165275
165321
|
}
|
|
@@ -167526,8 +167572,31 @@ function collectUserPromptParts(message) {
|
|
|
167526
167572
|
function hasStackedAugmentation(rawText) {
|
|
167527
167573
|
return rawText.includes("<sidekick-augmentation>") || rawText.includes("<ctx-search-hint>") || rawText.includes("<ctx-search-auto>");
|
|
167528
167574
|
}
|
|
167575
|
+
function stripNestedSystemReminders(text) {
|
|
167576
|
+
const OPEN = "<system-reminder>";
|
|
167577
|
+
const CLOSE = "</system-reminder>";
|
|
167578
|
+
let result = "";
|
|
167579
|
+
let depth = 0;
|
|
167580
|
+
let i = 0;
|
|
167581
|
+
while (i < text.length) {
|
|
167582
|
+
if (text.startsWith(OPEN, i)) {
|
|
167583
|
+
depth += 1;
|
|
167584
|
+
i += OPEN.length;
|
|
167585
|
+
} else if (text.startsWith(CLOSE, i)) {
|
|
167586
|
+
if (depth > 0)
|
|
167587
|
+
depth -= 1;
|
|
167588
|
+
i += CLOSE.length;
|
|
167589
|
+
} else if (depth === 0) {
|
|
167590
|
+
result += text[i];
|
|
167591
|
+
i += 1;
|
|
167592
|
+
} else {
|
|
167593
|
+
i += 1;
|
|
167594
|
+
}
|
|
167595
|
+
}
|
|
167596
|
+
return result;
|
|
167597
|
+
}
|
|
167529
167598
|
function extractUserPromptText(message) {
|
|
167530
|
-
return collectUserPromptParts(message).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(
|
|
167599
|
+
return stripNestedSystemReminders(collectUserPromptParts(message)).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<!--\s*ALFONSO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
|
|
167531
167600
|
`).replace(/\n{3,}/g, `
|
|
167532
167601
|
|
|
167533
167602
|
`).trim();
|
|
@@ -167769,7 +167838,8 @@ var DEDUP_SAFE_TOOLS = new Set([
|
|
|
167769
167838
|
]);
|
|
167770
167839
|
function buildPiToolFingerprints(messages) {
|
|
167771
167840
|
const fingerprints2 = new Map;
|
|
167772
|
-
for (
|
|
167841
|
+
for (let i = 0;i < messages.length; i++) {
|
|
167842
|
+
const message = messages[i];
|
|
167773
167843
|
if (!message || typeof message !== "object")
|
|
167774
167844
|
continue;
|
|
167775
167845
|
const msg = message;
|
|
@@ -167777,6 +167847,7 @@ function buildPiToolFingerprints(messages) {
|
|
|
167777
167847
|
continue;
|
|
167778
167848
|
if (!Array.isArray(msg.content))
|
|
167779
167849
|
continue;
|
|
167850
|
+
const ownerMsgId = typeof msg.timestamp === "number" ? `pi-msg-${i}-${msg.timestamp}-assistant` : `pi-msg-${i}-assistant`;
|
|
167780
167851
|
for (const part of msg.content) {
|
|
167781
167852
|
if (!part || typeof part !== "object")
|
|
167782
167853
|
continue;
|
|
@@ -167799,7 +167870,9 @@ function buildPiToolFingerprints(messages) {
|
|
|
167799
167870
|
} catch {
|
|
167800
167871
|
continue;
|
|
167801
167872
|
}
|
|
167802
|
-
|
|
167873
|
+
const fingerprint = `${ownerMsgId}:${p.name}:${serialized}`;
|
|
167874
|
+
const compositeKey = `${ownerMsgId}\x00${p.id}`;
|
|
167875
|
+
fingerprints2.set(compositeKey, fingerprint);
|
|
167803
167876
|
}
|
|
167804
167877
|
}
|
|
167805
167878
|
return fingerprints2;
|
|
@@ -167917,15 +167990,16 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
|
|
|
167917
167990
|
})();
|
|
167918
167991
|
const toolFingerprints = buildPiToolFingerprints(piMessages);
|
|
167919
167992
|
if (toolFingerprints.size > 0) {
|
|
167920
|
-
const
|
|
167993
|
+
const tagsByCompositeKey = new Map;
|
|
167921
167994
|
for (const tag of tags) {
|
|
167922
167995
|
if (tag.type === "tool" && tag.status === "active" && tag.messageId) {
|
|
167923
|
-
|
|
167996
|
+
const key = tag.toolOwnerMessageId ? `${tag.toolOwnerMessageId}\x00${tag.messageId}` : tag.messageId;
|
|
167997
|
+
tagsByCompositeKey.set(key, tag);
|
|
167924
167998
|
}
|
|
167925
167999
|
}
|
|
167926
168000
|
const fingerprintGroups = new Map;
|
|
167927
|
-
for (const [
|
|
167928
|
-
const tag =
|
|
168001
|
+
for (const [compositeKey, fingerprint] of toolFingerprints) {
|
|
168002
|
+
const tag = tagsByCompositeKey.get(compositeKey);
|
|
167929
168003
|
if (!tag || tag.tagNumber > protectedCutoff)
|
|
167930
168004
|
continue;
|
|
167931
168005
|
const group = fingerprintGroups.get(fingerprint) ?? [];
|
|
@@ -169877,8 +169951,18 @@ function injectSyntheticTodowriteForPi(args) {
|
|
|
169877
169951
|
}
|
|
169878
169952
|
|
|
169879
169953
|
// src/reasoning-replay-pi.ts
|
|
169880
|
-
var
|
|
169954
|
+
var INLINE_THINKING_PATTERNS = [
|
|
169955
|
+
/<thinking>[\s\S]*?<\/thinking>\s*/gi,
|
|
169956
|
+
/<think>[\s\S]*?<\/think>\s*/gi
|
|
169957
|
+
];
|
|
169881
169958
|
var CLEARED = "[cleared]";
|
|
169959
|
+
function stripInlineThinkingMarkup(text) {
|
|
169960
|
+
let cleaned = text;
|
|
169961
|
+
for (const pattern of INLINE_THINKING_PATTERNS) {
|
|
169962
|
+
cleaned = cleaned.replace(pattern, "");
|
|
169963
|
+
}
|
|
169964
|
+
return cleaned;
|
|
169965
|
+
}
|
|
169882
169966
|
function buildMessageIdToMaxTag(targets) {
|
|
169883
169967
|
const out = new Map;
|
|
169884
169968
|
for (const [tagNumber, target] of targets) {
|
|
@@ -169931,6 +170015,51 @@ function clearOldReasoningPi(args) {
|
|
|
169931
170015
|
}
|
|
169932
170016
|
return { cleared, newWatermark };
|
|
169933
170017
|
}
|
|
170018
|
+
function stripInlineThinkingPi(args) {
|
|
170019
|
+
const { messages, messageIdToMaxTag, clearReasoningAge, piMessageStableId } = args;
|
|
170020
|
+
let maxTag = 0;
|
|
170021
|
+
for (const t of messageIdToMaxTag.values())
|
|
170022
|
+
if (t > maxTag)
|
|
170023
|
+
maxTag = t;
|
|
170024
|
+
if (maxTag === 0)
|
|
170025
|
+
return { stripped: 0, newWatermark: 0 };
|
|
170026
|
+
const ageCutoff = maxTag - clearReasoningAge;
|
|
170027
|
+
if (ageCutoff <= 0)
|
|
170028
|
+
return { stripped: 0, newWatermark: 0 };
|
|
170029
|
+
let stripped = 0;
|
|
170030
|
+
let newWatermark = 0;
|
|
170031
|
+
for (let i = 0;i < messages.length; i++) {
|
|
170032
|
+
const raw = messages[i];
|
|
170033
|
+
if (!raw || typeof raw !== "object")
|
|
170034
|
+
continue;
|
|
170035
|
+
const msg = raw;
|
|
170036
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
170037
|
+
continue;
|
|
170038
|
+
const id = piMessageStableId(raw, i);
|
|
170039
|
+
if (!id)
|
|
170040
|
+
continue;
|
|
170041
|
+
const msgTag = messageIdToMaxTag.get(id) ?? 0;
|
|
170042
|
+
if (msgTag === 0 || msgTag > ageCutoff)
|
|
170043
|
+
continue;
|
|
170044
|
+
let strippedThisMessage = false;
|
|
170045
|
+
for (const part of msg.content) {
|
|
170046
|
+
if (part && typeof part === "object" && part.type === "text") {
|
|
170047
|
+
const tp = part;
|
|
170048
|
+
if (typeof tp.text !== "string")
|
|
170049
|
+
continue;
|
|
170050
|
+
const cleaned = stripInlineThinkingMarkup(tp.text);
|
|
170051
|
+
if (cleaned !== tp.text) {
|
|
170052
|
+
tp.text = cleaned;
|
|
170053
|
+
stripped++;
|
|
170054
|
+
strippedThisMessage = true;
|
|
170055
|
+
}
|
|
170056
|
+
}
|
|
170057
|
+
}
|
|
170058
|
+
if (strippedThisMessage && msgTag > newWatermark)
|
|
170059
|
+
newWatermark = msgTag;
|
|
170060
|
+
}
|
|
170061
|
+
return { stripped, newWatermark };
|
|
170062
|
+
}
|
|
169934
170063
|
function replayClearedReasoningPi(args) {
|
|
169935
170064
|
const { db, sessionId, messages, messageIdToMaxTag, piMessageStableId } = args;
|
|
169936
170065
|
const meta3 = getOrCreateSessionMeta(db, sessionId);
|
|
@@ -169988,7 +170117,7 @@ function replayStrippedInlineThinkingPi(args) {
|
|
|
169988
170117
|
const tp = part;
|
|
169989
170118
|
if (typeof tp.text !== "string")
|
|
169990
170119
|
continue;
|
|
169991
|
-
const cleaned = tp.text
|
|
170120
|
+
const cleaned = stripInlineThinkingMarkup(tp.text);
|
|
169992
170121
|
if (cleaned !== tp.text) {
|
|
169993
170122
|
tp.text = cleaned;
|
|
169994
170123
|
stripped++;
|
|
@@ -170079,6 +170208,408 @@ function stripPiDroppedPlaceholderMessages(args) {
|
|
|
170079
170208
|
return { removed, discovered };
|
|
170080
170209
|
}
|
|
170081
170210
|
|
|
170211
|
+
// src/system-prompt.ts
|
|
170212
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
170213
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
170214
|
+
import { join as join15 } from "node:path";
|
|
170215
|
+
|
|
170216
|
+
// ../plugin/src/agents/magic-context-prompt.ts
|
|
170217
|
+
function getToolHistoryGuidance(dropToolStructure) {
|
|
170218
|
+
if (dropToolStructure) {
|
|
170219
|
+
return `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
|
|
170220
|
+
}
|
|
170221
|
+
return `Older tool calls in your conversation show truncated inputs and [truncated] outputs — this is normal context management, not a pattern to follow. The original tool calls executed successfully with full inputs and produced real outputs that were later cleaned up to save context. ALWAYS use real tool calls with complete arguments; never copy truncated patterns like "filePa...[truncated]" into your tool inputs. If you need to re-read a file or re-run a command, make a fresh tool call.`;
|
|
170222
|
+
}
|
|
170223
|
+
var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
|
|
170224
|
+
Use \`ctx_reduce\` to manage context size. It supports one operation:
|
|
170225
|
+
- \`drop\`: Remove entirely (best for tool outputs you already acted on).
|
|
170226
|
+
Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
|
|
170227
|
+
Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
|
|
170228
|
+
Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
|
|
170229
|
+
**Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
|
|
170230
|
+
- Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
|
|
170231
|
+
- Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
|
|
170232
|
+
- Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
|
|
170233
|
+
Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
|
|
170234
|
+
Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
|
|
170235
|
+
**Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
|
|
170236
|
+
- Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
|
|
170237
|
+
- Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
|
|
170238
|
+
- Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
|
|
170239
|
+
- Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
|
|
170240
|
+
- Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
|
|
170241
|
+
\`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
|
|
170242
|
+
${getToolHistoryGuidance(dropToolStructure)}
|
|
170243
|
+
NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
|
|
170244
|
+
NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
|
|
170245
|
+
NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
|
|
170246
|
+
Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
|
|
170247
|
+
var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
|
|
170248
|
+
Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
|
|
170249
|
+
**Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
|
|
170250
|
+
- Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
|
|
170251
|
+
- Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
|
|
170252
|
+
- Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
|
|
170253
|
+
Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
|
|
170254
|
+
Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
|
|
170255
|
+
**Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
|
|
170256
|
+
- Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
|
|
170257
|
+
- Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
|
|
170258
|
+
- Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
|
|
170259
|
+
- Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
|
|
170260
|
+
- Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
|
|
170261
|
+
\`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
|
|
170262
|
+
${getToolHistoryGuidance(dropToolStructure)}`;
|
|
170263
|
+
var GENERIC_SECTION = `
|
|
170264
|
+
### Reduction Triggers
|
|
170265
|
+
- After reading files or search results you already acted on — drop raw outputs.
|
|
170266
|
+
- After completing a logical step — drop intermediate outputs from that step.
|
|
170267
|
+
- Between major context switches — when moving to a new task area.
|
|
170268
|
+
|
|
170269
|
+
### What to Drop
|
|
170270
|
+
- Large file reads, grep results, and tool outputs you already used.
|
|
170271
|
+
- Large build/test output after you analyzed and acted on it.
|
|
170272
|
+
- Old diagnostic or exploration results that are no longer relevant.
|
|
170273
|
+
|
|
170274
|
+
### What to Keep
|
|
170275
|
+
- ALL user messages and assistant conversation text — these are cheap and compartmentalized automatically.
|
|
170276
|
+
- Your current task requirements and constraints.
|
|
170277
|
+
- Recent errors and unresolved decisions.
|
|
170278
|
+
- Active work context and files being edited.`;
|
|
170279
|
+
var TEMPORAL_AWARENESS_GUIDANCE = `
|
|
170280
|
+
**Temporal awareness**: User messages may be preceded by HTML comments like \`<!-- +12m -->\`, \`<!-- +2h 15m -->\`, or \`<!-- +3d 4h -->\` indicating time elapsed since the previous message's completion. Compartments in \`<session-history>\` carry \`start-date\` and \`end-date\` attributes (YYYY-MM-DD) showing real-time boundaries. Use these when reasoning about workflow pacing, log durations, build times, or how long ago something happened.`;
|
|
170281
|
+
var CAVEMAN_COMPRESSION_WARNING = `
|
|
170282
|
+
**BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
|
|
170283
|
+
function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, dropToolStructure = true, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false) {
|
|
170284
|
+
const smartNoteGuidance = dreamerEnabled ? `
|
|
170285
|
+
When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
|
|
170286
|
+
The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
|
|
170287
|
+
Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
|
|
170288
|
+
const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
|
|
170289
|
+
const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
|
|
170290
|
+
if (!ctxReduceEnabled) {
|
|
170291
|
+
return `## Magic Context
|
|
170292
|
+
|
|
170293
|
+
${BASE_INTRO_NO_REDUCE(dropToolStructure)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}`;
|
|
170294
|
+
}
|
|
170295
|
+
return `## Magic Context
|
|
170296
|
+
|
|
170297
|
+
${BASE_INTRO(protectedTags, dropToolStructure)}${smartNoteGuidance}${temporalGuidance}
|
|
170298
|
+
${GENERIC_SECTION}
|
|
170299
|
+
|
|
170300
|
+
Prefer many small targeted operations over one large blanket operation. Compress early and often — don't wait for warnings.`;
|
|
170301
|
+
}
|
|
170302
|
+
|
|
170303
|
+
// ../plugin/src/hooks/magic-context/key-files-block.ts
|
|
170304
|
+
import { readFileSync as readFileSync7, realpathSync as realpathSync3 } from "node:fs";
|
|
170305
|
+
import { join as join14, sep as sep2 } from "node:path";
|
|
170306
|
+
init_logger();
|
|
170307
|
+
var cachedKeyFilesBySession = new Map;
|
|
170308
|
+
var staleUpdates = new Map;
|
|
170309
|
+
function staleKey(update) {
|
|
170310
|
+
return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
|
|
170311
|
+
}
|
|
170312
|
+
function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
|
|
170313
|
+
const update = { projectPath, path: path5, generatedAtWitness, staleReason };
|
|
170314
|
+
staleUpdates.set(staleKey(update), update);
|
|
170315
|
+
}
|
|
170316
|
+
function flushStaleUpdates(db) {
|
|
170317
|
+
if (staleUpdates.size === 0)
|
|
170318
|
+
return 0;
|
|
170319
|
+
const updates = [...staleUpdates.values()];
|
|
170320
|
+
staleUpdates.clear();
|
|
170321
|
+
const stmt = db.prepare(`UPDATE project_key_files
|
|
170322
|
+
SET stale_reason = ?1
|
|
170323
|
+
WHERE project_path = ?2
|
|
170324
|
+
AND path = ?3
|
|
170325
|
+
AND generated_at = ?4
|
|
170326
|
+
AND (stale_reason IS NULL OR stale_reason != ?1)`);
|
|
170327
|
+
let changed = 0;
|
|
170328
|
+
for (const update of updates) {
|
|
170329
|
+
try {
|
|
170330
|
+
changed += stmt.run(update.staleReason, update.projectPath, update.path, update.generatedAtWitness).changes;
|
|
170331
|
+
} catch (error51) {
|
|
170332
|
+
log("[key-files] flushStaleUpdates failed:", error51);
|
|
170333
|
+
}
|
|
170334
|
+
}
|
|
170335
|
+
return changed;
|
|
170336
|
+
}
|
|
170337
|
+
function clearKeyFilesCacheForSession(sessionId) {
|
|
170338
|
+
cachedKeyFilesBySession.delete(sessionId);
|
|
170339
|
+
}
|
|
170340
|
+
function isUnderProject(projectPath, absPath) {
|
|
170341
|
+
const root = realpathSync3(projectPath);
|
|
170342
|
+
return absPath.startsWith(root + sep2) || absPath === root;
|
|
170343
|
+
}
|
|
170344
|
+
function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBudget: 1e4 }) {
|
|
170345
|
+
if (!config2.enabled)
|
|
170346
|
+
return null;
|
|
170347
|
+
if (!isAftAvailable())
|
|
170348
|
+
return null;
|
|
170349
|
+
const rows = readCurrentKeyFiles(db, projectPath);
|
|
170350
|
+
if (rows.length === 0)
|
|
170351
|
+
return null;
|
|
170352
|
+
for (const row of rows) {
|
|
170353
|
+
if (row.staleReason !== null)
|
|
170354
|
+
continue;
|
|
170355
|
+
let nextStale = null;
|
|
170356
|
+
let observed = false;
|
|
170357
|
+
try {
|
|
170358
|
+
const absPath = join14(projectPath, row.path);
|
|
170359
|
+
const real = realpathSync3(absPath);
|
|
170360
|
+
if (!isUnderProject(projectPath, real)) {
|
|
170361
|
+
nextStale = "missing";
|
|
170362
|
+
observed = true;
|
|
170363
|
+
} else {
|
|
170364
|
+
const diskHash = sha256(readFileSync7(real));
|
|
170365
|
+
if (diskHash !== row.contentHash)
|
|
170366
|
+
nextStale = "content_drift";
|
|
170367
|
+
observed = true;
|
|
170368
|
+
}
|
|
170369
|
+
} catch (error51) {
|
|
170370
|
+
const code = error51?.code;
|
|
170371
|
+
if (code === "ENOENT" || code === "ELOOP") {
|
|
170372
|
+
nextStale = "missing";
|
|
170373
|
+
observed = true;
|
|
170374
|
+
} else {
|
|
170375
|
+
log(`[key-files] freshness check transient failure: ${row.path}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
170376
|
+
}
|
|
170377
|
+
}
|
|
170378
|
+
if (observed && nextStale !== null) {
|
|
170379
|
+
queueStaleUpdate(row.projectPath, row.path, row.generatedAt, nextStale);
|
|
170380
|
+
}
|
|
170381
|
+
}
|
|
170382
|
+
const blocks = [];
|
|
170383
|
+
let used = 0;
|
|
170384
|
+
for (const row of rows) {
|
|
170385
|
+
if (used + row.localTokenEstimate > config2.tokenBudget)
|
|
170386
|
+
break;
|
|
170387
|
+
blocks.push(` <key-file path="${escapeXmlAttr(row.path)}">
|
|
170388
|
+
${escapeXmlContent(row.content)}
|
|
170389
|
+
</key-file>`);
|
|
170390
|
+
used += row.localTokenEstimate;
|
|
170391
|
+
}
|
|
170392
|
+
if (blocks.length === 0)
|
|
170393
|
+
return null;
|
|
170394
|
+
const rendered = `<key-files>
|
|
170395
|
+
${blocks.join(`
|
|
170396
|
+
`)}
|
|
170397
|
+
</key-files>`;
|
|
170398
|
+
flushStaleUpdates(db);
|
|
170399
|
+
return rendered;
|
|
170400
|
+
}
|
|
170401
|
+
function readVersionedKeyFiles(args) {
|
|
170402
|
+
const config2 = args.config ?? { enabled: true, tokenBudget: 1e4 };
|
|
170403
|
+
if (args.sessionMeta.isSubagent)
|
|
170404
|
+
return null;
|
|
170405
|
+
if (!config2.enabled)
|
|
170406
|
+
return null;
|
|
170407
|
+
if (!isAftAvailable())
|
|
170408
|
+
return null;
|
|
170409
|
+
const projectPath = resolveProjectPath(args.directory ?? args.sessionMeta.sessionId);
|
|
170410
|
+
if (!projectPath)
|
|
170411
|
+
return null;
|
|
170412
|
+
const currentVersion = getKeyFilesVersion(args.db, projectPath);
|
|
170413
|
+
if (args.sessionId) {
|
|
170414
|
+
const cached2 = cachedKeyFilesBySession.get(args.sessionId);
|
|
170415
|
+
if (cached2 && !args.isCacheBusting && cached2.version === currentVersion) {
|
|
170416
|
+
return cached2.value;
|
|
170417
|
+
}
|
|
170418
|
+
}
|
|
170419
|
+
const value = buildKeyFilesBlock(args.db, projectPath, config2);
|
|
170420
|
+
if (args.sessionId) {
|
|
170421
|
+
cachedKeyFilesBySession.set(args.sessionId, { value, version: currentVersion });
|
|
170422
|
+
if (value)
|
|
170423
|
+
sessionLog(args.sessionId, `loaded key-files block (v${currentVersion}, ${value.length} chars)`);
|
|
170424
|
+
}
|
|
170425
|
+
return value;
|
|
170426
|
+
}
|
|
170427
|
+
|
|
170428
|
+
// src/system-prompt.ts
|
|
170429
|
+
init_logger();
|
|
170430
|
+
var PROJECT_DOCS_MARKER = "<project-docs>";
|
|
170431
|
+
var USER_PROFILE_MARKER = "<user-profile>";
|
|
170432
|
+
var KEY_FILES_MARKER = "<key-files>";
|
|
170433
|
+
var MAGIC_CONTEXT_MARKER = "## Magic Context";
|
|
170434
|
+
var stickyDateBySession = new Map;
|
|
170435
|
+
var cachedDocsBySession = new Map;
|
|
170436
|
+
var cachedUserProfileBySession = new Map;
|
|
170437
|
+
var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
|
|
170438
|
+
function readProjectDocs(directory) {
|
|
170439
|
+
const sections = [];
|
|
170440
|
+
for (const filename of DOC_FILES) {
|
|
170441
|
+
const filePath = join15(directory, filename);
|
|
170442
|
+
try {
|
|
170443
|
+
if (existsSync11(filePath)) {
|
|
170444
|
+
const content = readFileSync8(filePath, "utf-8").trim();
|
|
170445
|
+
if (content.length > 0) {
|
|
170446
|
+
sections.push(`<${filename}>
|
|
170447
|
+
${escapeXmlContent(content)}
|
|
170448
|
+
</${filename}>`);
|
|
170449
|
+
}
|
|
170450
|
+
}
|
|
170451
|
+
} catch (error51) {
|
|
170452
|
+
log(`[magic-context-pi] failed to read ${filename}:`, error51);
|
|
170453
|
+
}
|
|
170454
|
+
}
|
|
170455
|
+
if (sections.length === 0)
|
|
170456
|
+
return null;
|
|
170457
|
+
return `${PROJECT_DOCS_MARKER}
|
|
170458
|
+
${sections.join(`
|
|
170459
|
+
|
|
170460
|
+
`)}
|
|
170461
|
+
</project-docs>`;
|
|
170462
|
+
}
|
|
170463
|
+
function buildUserProfileBlock(db) {
|
|
170464
|
+
const memories = getActiveUserMemories(db);
|
|
170465
|
+
if (memories.length === 0)
|
|
170466
|
+
return null;
|
|
170467
|
+
const items = memories.map((m) => `- ${escapeXmlContent(m.content)}`).join(`
|
|
170468
|
+
`);
|
|
170469
|
+
return `${USER_PROFILE_MARKER}
|
|
170470
|
+
${items}
|
|
170471
|
+
</user-profile>`;
|
|
170472
|
+
}
|
|
170473
|
+
function buildMagicContextBlock(opts) {
|
|
170474
|
+
const sections = [];
|
|
170475
|
+
const sessionId = opts.sessionId;
|
|
170476
|
+
const isCacheBusting = opts.isCacheBusting ?? false;
|
|
170477
|
+
const existing = opts.existingSystemPrompt ?? "";
|
|
170478
|
+
if (opts.injectDocs && !existing.includes(PROJECT_DOCS_MARKER)) {
|
|
170479
|
+
const docsBlock = readCachedAdjunct({
|
|
170480
|
+
cache: cachedDocsBySession,
|
|
170481
|
+
sessionId,
|
|
170482
|
+
isCacheBusting,
|
|
170483
|
+
compute: () => readProjectDocs(opts.cwd),
|
|
170484
|
+
describe: "project docs"
|
|
170485
|
+
});
|
|
170486
|
+
if (docsBlock)
|
|
170487
|
+
sections.push(docsBlock);
|
|
170488
|
+
}
|
|
170489
|
+
if (opts.userMemoriesEnabled && !existing.includes(USER_PROFILE_MARKER)) {
|
|
170490
|
+
const profileBlock = readCachedAdjunct({
|
|
170491
|
+
cache: cachedUserProfileBySession,
|
|
170492
|
+
sessionId,
|
|
170493
|
+
isCacheBusting,
|
|
170494
|
+
compute: () => buildUserProfileBlock(opts.db),
|
|
170495
|
+
describe: "user profile"
|
|
170496
|
+
});
|
|
170497
|
+
if (profileBlock)
|
|
170498
|
+
sections.push(profileBlock);
|
|
170499
|
+
}
|
|
170500
|
+
if (opts.pinKeyFilesEnabled && sessionId && !existing.includes(KEY_FILES_MARKER)) {
|
|
170501
|
+
let sessionMeta = null;
|
|
170502
|
+
try {
|
|
170503
|
+
sessionMeta = getOrCreateSessionMeta(opts.db, sessionId);
|
|
170504
|
+
} catch (error51) {
|
|
170505
|
+
sessionLog(sessionId, "Pi key-files session meta load failed:", error51);
|
|
170506
|
+
}
|
|
170507
|
+
const keyFilesBlock = sessionMeta ? readVersionedKeyFiles({
|
|
170508
|
+
db: opts.db,
|
|
170509
|
+
sessionId,
|
|
170510
|
+
sessionMeta,
|
|
170511
|
+
directory: opts.cwd,
|
|
170512
|
+
isCacheBusting,
|
|
170513
|
+
config: {
|
|
170514
|
+
enabled: opts.pinKeyFilesEnabled,
|
|
170515
|
+
tokenBudget: opts.pinKeyFilesTokenBudget ?? 1e4
|
|
170516
|
+
}
|
|
170517
|
+
}) : null;
|
|
170518
|
+
if (keyFilesBlock)
|
|
170519
|
+
sections.push(keyFilesBlock);
|
|
170520
|
+
}
|
|
170521
|
+
const dataBlock = sections.length > 0 ? `<magic-context>
|
|
170522
|
+
${sections.join(`
|
|
170523
|
+
|
|
170524
|
+
`)}
|
|
170525
|
+
</magic-context>` : null;
|
|
170526
|
+
const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
|
|
170527
|
+
if (!includeGuidance) {
|
|
170528
|
+
return dataBlock;
|
|
170529
|
+
}
|
|
170530
|
+
const guidance = buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.dropToolStructure ?? true, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false);
|
|
170531
|
+
if (dataBlock) {
|
|
170532
|
+
return `${guidance}
|
|
170533
|
+
|
|
170534
|
+
${dataBlock}`;
|
|
170535
|
+
}
|
|
170536
|
+
return guidance;
|
|
170537
|
+
}
|
|
170538
|
+
function readCachedAdjunct(args) {
|
|
170539
|
+
const { cache, sessionId, isCacheBusting, compute, describe: describe3 } = args;
|
|
170540
|
+
if (!sessionId) {
|
|
170541
|
+
return compute();
|
|
170542
|
+
}
|
|
170543
|
+
const hasCached = cache.has(sessionId);
|
|
170544
|
+
if (!hasCached || isCacheBusting) {
|
|
170545
|
+
const value = compute();
|
|
170546
|
+
cache.set(sessionId, value);
|
|
170547
|
+
if (value && !hasCached) {
|
|
170548
|
+
sessionLog(sessionId, `loaded ${describe3} (${value.length} chars)`);
|
|
170549
|
+
} else if (value && isCacheBusting) {
|
|
170550
|
+
sessionLog(sessionId, `refreshed ${describe3} (cache-busting pass)`);
|
|
170551
|
+
}
|
|
170552
|
+
return value;
|
|
170553
|
+
}
|
|
170554
|
+
return cache.get(sessionId) ?? null;
|
|
170555
|
+
}
|
|
170556
|
+
var DATE_PATTERN = /Today's date: .+/;
|
|
170557
|
+
function processSystemPromptForCache(args) {
|
|
170558
|
+
const { db, sessionId, systemPrompt, isCacheBusting } = args;
|
|
170559
|
+
let sessionMeta;
|
|
170560
|
+
try {
|
|
170561
|
+
sessionMeta = getOrCreateSessionMeta(db, sessionId);
|
|
170562
|
+
} catch (error51) {
|
|
170563
|
+
sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
|
|
170564
|
+
}
|
|
170565
|
+
const previousHash = sessionMeta?.systemPromptHash ?? "";
|
|
170566
|
+
const isFirstHash = previousHash === "" || previousHash === "0";
|
|
170567
|
+
let frozenPrompt = systemPrompt;
|
|
170568
|
+
const dateMatch = systemPrompt.match(DATE_PATTERN);
|
|
170569
|
+
const liveDate = dateMatch ? dateMatch[0] : null;
|
|
170570
|
+
const stickyDate = stickyDateBySession.get(sessionId);
|
|
170571
|
+
if (liveDate && !stickyDate) {
|
|
170572
|
+
stickyDateBySession.set(sessionId, liveDate);
|
|
170573
|
+
} else if (liveDate && stickyDate && liveDate !== stickyDate) {
|
|
170574
|
+
if (isCacheBusting) {
|
|
170575
|
+
stickyDateBySession.set(sessionId, liveDate);
|
|
170576
|
+
sessionLog(sessionId, `system prompt date updated: ${stickyDate} → ${liveDate} (cache-busting pass)`);
|
|
170577
|
+
} else {
|
|
170578
|
+
frozenPrompt = systemPrompt.replace(DATE_PATTERN, stickyDate);
|
|
170579
|
+
sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
|
|
170580
|
+
}
|
|
170581
|
+
}
|
|
170582
|
+
const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
|
|
170583
|
+
const hashChanged = !isFirstHash && currentHash !== previousHash;
|
|
170584
|
+
if (hashChanged) {
|
|
170585
|
+
sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
|
|
170586
|
+
} else if (isFirstHash) {
|
|
170587
|
+
sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${frozenPrompt.length})`);
|
|
170588
|
+
}
|
|
170589
|
+
const systemPromptTokens = estimateTokens(frozenPrompt);
|
|
170590
|
+
if (sessionMeta) {
|
|
170591
|
+
if (currentHash !== previousHash) {
|
|
170592
|
+
updateSessionMeta(db, sessionId, {
|
|
170593
|
+
systemPromptHash: currentHash,
|
|
170594
|
+
systemPromptTokens
|
|
170595
|
+
});
|
|
170596
|
+
} else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
|
|
170597
|
+
updateSessionMeta(db, sessionId, { systemPromptTokens });
|
|
170598
|
+
}
|
|
170599
|
+
}
|
|
170600
|
+
return {
|
|
170601
|
+
systemPrompt: frozenPrompt,
|
|
170602
|
+
hashChanged,
|
|
170603
|
+
currentHash
|
|
170604
|
+
};
|
|
170605
|
+
}
|
|
170606
|
+
function clearPiSystemPromptSession(sessionId) {
|
|
170607
|
+
stickyDateBySession.delete(sessionId);
|
|
170608
|
+
cachedDocsBySession.delete(sessionId);
|
|
170609
|
+
cachedUserProfileBySession.delete(sessionId);
|
|
170610
|
+
clearKeyFilesCacheForSession(sessionId);
|
|
170611
|
+
}
|
|
170612
|
+
|
|
170082
170613
|
// src/temporal-awareness-pi.ts
|
|
170083
170614
|
function injectPiTemporalMarkers(messages) {
|
|
170084
170615
|
let injected = 0;
|
|
@@ -170639,6 +171170,12 @@ var commitSeenLastPass = new Map;
|
|
|
170639
171170
|
var historyRefreshSessions = new Set;
|
|
170640
171171
|
var systemPromptRefreshSessions = new Set;
|
|
170641
171172
|
var pendingMaterializationSessions = new Set;
|
|
171173
|
+
var deferredHistoryRefreshSessions = new Set;
|
|
171174
|
+
var deferredMaterializationSessions = new Set;
|
|
171175
|
+
var sessionsByProject = new Map;
|
|
171176
|
+
var lastSeenProjectIdentityBySession = new Map;
|
|
171177
|
+
var rawMessageProviderUnregistersBySession = new Map;
|
|
171178
|
+
var lastHeuristicsTurnIdBySession = new Map;
|
|
170642
171179
|
var firstContextPassSeenBySession = new Set;
|
|
170643
171180
|
var recentReduceBySession = new Map;
|
|
170644
171181
|
var liveModelBySession = new Map;
|
|
@@ -170672,6 +171209,50 @@ function signalPiSystemPromptRefresh(sessionId) {
|
|
|
170672
171209
|
function signalPiPendingMaterialization(sessionId) {
|
|
170673
171210
|
pendingMaterializationSessions.add(sessionId);
|
|
170674
171211
|
}
|
|
171212
|
+
function signalPiDeferredHistoryRefresh(sessionId) {
|
|
171213
|
+
deferredHistoryRefreshSessions.add(sessionId);
|
|
171214
|
+
}
|
|
171215
|
+
function signalPiDeferredMaterialization(sessionId) {
|
|
171216
|
+
deferredMaterializationSessions.add(sessionId);
|
|
171217
|
+
}
|
|
171218
|
+
function consumeDeferredHistoryRefresh(sessionId) {
|
|
171219
|
+
const wasSet = deferredHistoryRefreshSessions.has(sessionId);
|
|
171220
|
+
deferredHistoryRefreshSessions.delete(sessionId);
|
|
171221
|
+
return wasSet;
|
|
171222
|
+
}
|
|
171223
|
+
function consumeDeferredMaterialization(sessionId) {
|
|
171224
|
+
const wasSet = deferredMaterializationSessions.has(sessionId);
|
|
171225
|
+
deferredMaterializationSessions.delete(sessionId);
|
|
171226
|
+
return wasSet;
|
|
171227
|
+
}
|
|
171228
|
+
function trackSessionForProject(projectIdentity, sessionId) {
|
|
171229
|
+
let sessions = sessionsByProject.get(projectIdentity);
|
|
171230
|
+
if (!sessions) {
|
|
171231
|
+
sessions = new Set;
|
|
171232
|
+
sessionsByProject.set(projectIdentity, sessions);
|
|
171233
|
+
}
|
|
171234
|
+
sessions.add(sessionId);
|
|
171235
|
+
}
|
|
171236
|
+
function updateSessionProjectTracking(sessionId, projectIdentity) {
|
|
171237
|
+
const prev = lastSeenProjectIdentityBySession.get(sessionId);
|
|
171238
|
+
if (prev && prev !== projectIdentity) {
|
|
171239
|
+
const prevSessions = sessionsByProject.get(prev);
|
|
171240
|
+
prevSessions?.delete(sessionId);
|
|
171241
|
+
if (prevSessions?.size === 0)
|
|
171242
|
+
sessionsByProject.delete(prev);
|
|
171243
|
+
clearPiSystemPromptSession(sessionId);
|
|
171244
|
+
}
|
|
171245
|
+
trackSessionForProject(projectIdentity, sessionId);
|
|
171246
|
+
lastSeenProjectIdentityBySession.set(sessionId, projectIdentity);
|
|
171247
|
+
}
|
|
171248
|
+
function signalPiSystemPromptRefreshForProject(projectIdentity) {
|
|
171249
|
+
const sessions = sessionsByProject.get(projectIdentity);
|
|
171250
|
+
if (!sessions)
|
|
171251
|
+
return;
|
|
171252
|
+
for (const sessionId of sessions) {
|
|
171253
|
+
systemPromptRefreshSessions.add(sessionId);
|
|
171254
|
+
}
|
|
171255
|
+
}
|
|
170675
171256
|
function recordPiLiveModel(sessionId, modelKey) {
|
|
170676
171257
|
liveModelBySession.set(sessionId, modelKey);
|
|
170677
171258
|
}
|
|
@@ -170849,8 +171430,6 @@ function collectMessageEntryIdsStrict(ctx, expectedLength, sessionId) {
|
|
|
170849
171430
|
}
|
|
170850
171431
|
function registerPiContextHandler(pi, options) {
|
|
170851
171432
|
const tagger = createTagger();
|
|
170852
|
-
const projectDirectory = process.cwd();
|
|
170853
|
-
const projectIdentity = resolveProjectIdentity(projectDirectory);
|
|
170854
171433
|
const schedulerConfig = options.scheduler ?? {
|
|
170855
171434
|
executeThresholdPercentage: 65
|
|
170856
171435
|
};
|
|
@@ -170876,12 +171455,17 @@ function registerPiContextHandler(pi, options) {
|
|
|
170876
171455
|
return;
|
|
170877
171456
|
}
|
|
170878
171457
|
sessionIdForError = sessionId;
|
|
171458
|
+
const projectDirectory = ctx.cwd;
|
|
171459
|
+
const projectIdentity = resolveProjectIdentity(projectDirectory);
|
|
171460
|
+
updateSessionProjectTracking(sessionId, projectIdentity);
|
|
170879
171461
|
logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
|
|
170880
171462
|
const rawMessageProvider = {
|
|
170881
171463
|
readMessages: () => readPiSessionMessages(ctx),
|
|
170882
171464
|
readMessageById: (messageId) => readPiSessionMessageById(ctx, messageId)
|
|
170883
171465
|
};
|
|
170884
|
-
|
|
171466
|
+
rawMessageProviderUnregistersBySession.get(sessionId)?.();
|
|
171467
|
+
const unregisterRaw = setRawMessageProvider(sessionId, rawMessageProvider);
|
|
171468
|
+
rawMessageProviderUnregistersBySession.set(sessionId, unregisterRaw);
|
|
170885
171469
|
scheduleReconciliation(options.db, sessionId, readRawSessionMessages);
|
|
170886
171470
|
const strictEntryIds = collectMessageEntryIdsStrict(ctx, event.messages.length, sessionId);
|
|
170887
171471
|
const tLastUser = performance.now();
|
|
@@ -170916,12 +171500,18 @@ function registerPiContextHandler(pi, options) {
|
|
|
170916
171500
|
lastContextPercentage: 0,
|
|
170917
171501
|
lastInputTokens: 0,
|
|
170918
171502
|
observedSafeInputTokens: 0,
|
|
170919
|
-
cacheAlertSent: false
|
|
171503
|
+
cacheAlertSent: false,
|
|
171504
|
+
clearedReasoningThroughTag: 0
|
|
170920
171505
|
});
|
|
171506
|
+
clearHistorianFailureState(options.db, sessionId);
|
|
171507
|
+
clearPersistedReasoningWatermark(options.db, sessionId);
|
|
171508
|
+
clearDetectedContextLimit(options.db, sessionId);
|
|
171509
|
+
clearEmergencyRecovery(options.db, sessionId);
|
|
170921
171510
|
sessionMetaForUsage.lastContextPercentage = 0;
|
|
170922
171511
|
sessionMetaForUsage.lastInputTokens = 0;
|
|
170923
171512
|
sessionMetaForUsage.observedSafeInputTokens = 0;
|
|
170924
171513
|
sessionMetaForUsage.cacheAlertSent = false;
|
|
171514
|
+
sessionMetaForUsage.clearedReasoningThroughTag = 0;
|
|
170925
171515
|
}
|
|
170926
171516
|
let usagePercentage = 0;
|
|
170927
171517
|
let usageInputTokens = 0;
|
|
@@ -171050,11 +171640,25 @@ function registerPiContextHandler(pi, options) {
|
|
|
171050
171640
|
isCacheBusting,
|
|
171051
171641
|
usagePercentage,
|
|
171052
171642
|
usageInputTokens,
|
|
171053
|
-
usageContextLimit
|
|
171643
|
+
usageContextLimit,
|
|
171644
|
+
schedulerDecision
|
|
171054
171645
|
});
|
|
171055
171646
|
}
|
|
171056
171647
|
const tPostTransform = performance.now();
|
|
171057
171648
|
let outputMessages = result.messages;
|
|
171649
|
+
try {
|
|
171650
|
+
const cacheBustingPass = isCacheBusting || result.executedWorkThisPass;
|
|
171651
|
+
outputMessages = applyStickyTurnReminder({
|
|
171652
|
+
sessionId,
|
|
171653
|
+
db: options.db,
|
|
171654
|
+
messages: outputMessages,
|
|
171655
|
+
entryIds: entryIds ?? null,
|
|
171656
|
+
hasRecentReduceCall: recentReduceBySession.has(sessionId),
|
|
171657
|
+
isCacheBustingPass: cacheBustingPass
|
|
171658
|
+
});
|
|
171659
|
+
} catch (err) {
|
|
171660
|
+
sessionLog(sessionId, `sticky turn reminder failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
171661
|
+
}
|
|
171058
171662
|
if (nudgerFn && options.nudge) {
|
|
171059
171663
|
try {
|
|
171060
171664
|
const tNudge = performance.now();
|
|
@@ -171168,7 +171772,7 @@ function maybeFireCompressor(args) {
|
|
|
171168
171772
|
const compressor = historian.compressor;
|
|
171169
171773
|
if (!compressor?.enabled)
|
|
171170
171774
|
return;
|
|
171171
|
-
if (!args.isCacheBusting)
|
|
171775
|
+
if (!args.isCacheBusting && args.schedulerDecision !== "execute")
|
|
171172
171776
|
return;
|
|
171173
171777
|
if (inFlightHistorian.has(sessionId) || inFlightCompressor.has(sessionId)) {
|
|
171174
171778
|
sessionLog(sessionId, "compressor trigger eval: in-flight historian/compressor, skipping");
|
|
@@ -171188,7 +171792,6 @@ function maybeFireCompressor(args) {
|
|
|
171188
171792
|
});
|
|
171189
171793
|
if (!historyBudgetTokens || historyBudgetTokens <= 0)
|
|
171190
171794
|
return;
|
|
171191
|
-
markPiCompressorRun(sessionId);
|
|
171192
171795
|
const runPromise = runPiCompressionPassIfNeeded({
|
|
171193
171796
|
db,
|
|
171194
171797
|
sessionId,
|
|
@@ -171204,10 +171807,14 @@ function maybeFireCompressor(args) {
|
|
|
171204
171807
|
maxCompartmentsPerPass: compressor.maxCompartmentsPerPass,
|
|
171205
171808
|
graceCompartments: compressor.graceCompartments,
|
|
171206
171809
|
onPublished: () => {
|
|
171207
|
-
|
|
171208
|
-
|
|
171810
|
+
signalPiDeferredHistoryRefresh(sessionId);
|
|
171811
|
+
signalPiDeferredMaterialization(sessionId);
|
|
171209
171812
|
historian.onStatusChange?.(ctx, sessionId);
|
|
171210
171813
|
}
|
|
171814
|
+
}).then((didPublish) => {
|
|
171815
|
+
if (didPublish === true) {
|
|
171816
|
+
markPiCompressorRun(sessionId);
|
|
171817
|
+
}
|
|
171211
171818
|
}).catch((err) => {
|
|
171212
171819
|
sessionLog(sessionId, `compressor failed in background: ${err instanceof Error ? err.message : String(err)}`);
|
|
171213
171820
|
}).finally(() => {
|
|
@@ -171262,8 +171869,8 @@ function spawnPiHistorianRun(args) {
|
|
|
171262
171869
|
} catch (err) {
|
|
171263
171870
|
sessionLog(sessionId, `historian: clearEmergencyRecovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
171264
171871
|
}
|
|
171265
|
-
|
|
171266
|
-
|
|
171872
|
+
signalPiDeferredHistoryRefresh(sessionId);
|
|
171873
|
+
signalPiDeferredMaterialization(sessionId);
|
|
171267
171874
|
historian.onStatusChange?.(ctx, sessionId);
|
|
171268
171875
|
}
|
|
171269
171876
|
}).finally(() => {
|
|
@@ -171393,6 +172000,12 @@ async function runPipeline(args) {
|
|
|
171393
172000
|
logTransformTiming(args.sessionId, "injectTemporalMarkers", tTemporal);
|
|
171394
172001
|
}
|
|
171395
172002
|
const transcript = createPiTranscript(args.messages, args.sessionId);
|
|
172003
|
+
const currentTurnId = (() => {
|
|
172004
|
+
const ids = buildPiMessageIdByIndex(args.messages, args.entryIds ?? null);
|
|
172005
|
+
return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
|
|
172006
|
+
})();
|
|
172007
|
+
const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
|
|
172008
|
+
const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
|
|
171396
172009
|
const tTag = performance.now();
|
|
171397
172010
|
const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
|
|
171398
172011
|
skipPrefixInjection: !args.ctxReduceEnabled
|
|
@@ -171413,10 +172026,16 @@ async function runPipeline(args) {
|
|
|
171413
172026
|
sessionLog(args.sessionId, `commit-detect failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
171414
172027
|
}
|
|
171415
172028
|
const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
|
|
172029
|
+
const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
|
|
172030
|
+
const deferredHistoryRefreshWasPending = deferredHistoryRefreshSessions.has(args.sessionId);
|
|
171416
172031
|
const pendingOps = getPendingOps(args.db, args.sessionId);
|
|
171417
|
-
const
|
|
172032
|
+
const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
|
|
172033
|
+
const canConsumeDeferredLate = baseShouldApplyPendingOps || shouldRunHeuristics;
|
|
172034
|
+
const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
|
|
172035
|
+
const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
|
|
172036
|
+
const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
|
|
171418
172037
|
if (shouldApplyPendingOps) {
|
|
171419
|
-
const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
|
|
172038
|
+
const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
|
|
171420
172039
|
sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
|
|
171421
172040
|
try {
|
|
171422
172041
|
const tApplyPending = performance.now();
|
|
@@ -171426,6 +172045,9 @@ async function runPipeline(args) {
|
|
|
171426
172045
|
if (hasPendingMaterializeSignal) {
|
|
171427
172046
|
consumePendingMaterialization(args.sessionId);
|
|
171428
172047
|
}
|
|
172048
|
+
if (deferredMaterialize) {
|
|
172049
|
+
consumeDeferredMaterialization(args.sessionId);
|
|
172050
|
+
}
|
|
171429
172051
|
} catch (err) {
|
|
171430
172052
|
sessionLog(args.sessionId, `pending operations failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
171431
172053
|
throw err;
|
|
@@ -171482,7 +172104,6 @@ async function runPipeline(args) {
|
|
|
171482
172104
|
const tActiveTags = performance.now();
|
|
171483
172105
|
const activeTags = getActiveTagsBySession(args.db, args.sessionId);
|
|
171484
172106
|
logTransformTiming(args.sessionId, "getActiveTagsBySession", tActiveTags, `count=${activeTags.length}`);
|
|
171485
|
-
const shouldRunHeuristics = args.heuristics !== undefined && (args.schedulerDecision === "execute" || args.forceMaterialization === true);
|
|
171486
172107
|
if (shouldRunHeuristics) {
|
|
171487
172108
|
const reason = args.forceMaterialization ? "force_materialization" : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
|
|
171488
172109
|
sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`);
|
|
@@ -171502,6 +172123,9 @@ async function runPipeline(args) {
|
|
|
171502
172123
|
}, activeTags);
|
|
171503
172124
|
heuristicsExecuted = true;
|
|
171504
172125
|
executedWorkThisPass = true;
|
|
172126
|
+
if (currentTurnId !== null) {
|
|
172127
|
+
lastHeuristicsTurnIdBySession.set(args.sessionId, currentTurnId);
|
|
172128
|
+
}
|
|
171505
172129
|
logTransformTiming(args.sessionId, "applyHeuristicCleanup", tHeuristic, `droppedTools=${heuristicsResult.droppedTools} deduplicatedTools=${heuristicsResult.deduplicatedTools} droppedInjections=${heuristicsResult.droppedInjections} compressedTextTags=${heuristicsResult.compressedTextTags}`);
|
|
171506
172130
|
} catch (err) {
|
|
171507
172131
|
sessionLog(args.sessionId, `heuristic cleanup failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -171510,21 +172134,26 @@ async function runPipeline(args) {
|
|
|
171510
172134
|
if (args.reasoningClearing && (args.schedulerDecision === "execute" || args.forceMaterialization === true)) {
|
|
171511
172135
|
try {
|
|
171512
172136
|
const tClearReasoning = performance.now();
|
|
172137
|
+
const meta3 = getOrCreateSessionMeta(args.db, args.sessionId);
|
|
172138
|
+
const prevWatermark = meta3.clearedReasoningThroughTag ?? 0;
|
|
171513
172139
|
const clearOutcome = clearOldReasoningPi({
|
|
171514
172140
|
messages: args.messages,
|
|
171515
172141
|
messageIdToMaxTag,
|
|
171516
172142
|
clearReasoningAge: args.reasoningClearing.clearReasoningAge,
|
|
171517
172143
|
piMessageStableId
|
|
171518
172144
|
});
|
|
171519
|
-
const
|
|
171520
|
-
|
|
171521
|
-
|
|
171522
|
-
|
|
171523
|
-
|
|
171524
|
-
|
|
171525
|
-
|
|
171526
|
-
|
|
171527
|
-
|
|
172145
|
+
const stripOutcome = stripInlineThinkingPi({
|
|
172146
|
+
messages: args.messages,
|
|
172147
|
+
messageIdToMaxTag,
|
|
172148
|
+
clearReasoningAge: args.reasoningClearing.clearReasoningAge,
|
|
172149
|
+
piMessageStableId
|
|
172150
|
+
});
|
|
172151
|
+
const combinedWatermark = Math.max(clearOutcome.newWatermark, stripOutcome.newWatermark);
|
|
172152
|
+
if (combinedWatermark > prevWatermark) {
|
|
172153
|
+
updateSessionMeta(args.db, args.sessionId, {
|
|
172154
|
+
clearedReasoningThroughTag: combinedWatermark
|
|
172155
|
+
});
|
|
172156
|
+
sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearOutcome.cleared} inlineStripped=${stripOutcome.stripped} watermark=${prevWatermark}→${combinedWatermark}`);
|
|
171528
172157
|
}
|
|
171529
172158
|
logTransformTiming(args.sessionId, "clearOldReasoning", tClearReasoning);
|
|
171530
172159
|
logTransformTiming(args.sessionId, "watermarkCleanup", tClearReasoning);
|
|
@@ -171538,10 +172167,13 @@ async function runPipeline(args) {
|
|
|
171538
172167
|
if (args.injection) {
|
|
171539
172168
|
try {
|
|
171540
172169
|
const tInjection = performance.now();
|
|
171541
|
-
injectionResult = injectSessionHistoryIntoPi(args.db, args.sessionId, args.messages, args.isCacheBusting, args.projectIdentity, args.injection.injectionBudgetTokens, args.injection.temporalAwareness, args.entryIds);
|
|
172170
|
+
injectionResult = injectSessionHistoryIntoPi(args.db, args.sessionId, args.messages, args.isCacheBusting || deferredHistoryRefresh, args.projectIdentity, args.injection.injectionBudgetTokens, args.injection.temporalAwareness, args.entryIds);
|
|
171542
172171
|
if (args.isCacheBusting) {
|
|
171543
172172
|
historyRefreshSessions.delete(args.sessionId);
|
|
171544
172173
|
}
|
|
172174
|
+
if (deferredHistoryRefresh) {
|
|
172175
|
+
consumeDeferredHistoryRefresh(args.sessionId);
|
|
172176
|
+
}
|
|
171545
172177
|
logTransformTiming(args.sessionId, "prepareCompartmentInjection", tInjection);
|
|
171546
172178
|
logTransformTiming(args.sessionId, "compartmentPhase", tInjection);
|
|
171547
172179
|
} catch (err) {
|
|
@@ -171604,6 +172236,29 @@ function applyRollingNudge(args) {
|
|
|
171604
172236
|
return messages;
|
|
171605
172237
|
return injectPiNudge(messages, nudge);
|
|
171606
172238
|
}
|
|
172239
|
+
function applyStickyTurnReminder(args) {
|
|
172240
|
+
const reminder = getPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
172241
|
+
if (!reminder)
|
|
172242
|
+
return args.messages;
|
|
172243
|
+
if (args.hasRecentReduceCall && args.isCacheBustingPass) {
|
|
172244
|
+
clearPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
172245
|
+
return args.messages;
|
|
172246
|
+
}
|
|
172247
|
+
const messageIdByIndex = buildPiMessageIdByIndex(args.messages, args.entryIds);
|
|
172248
|
+
if (reminder.messageId) {
|
|
172249
|
+
const reinjected = appendReminderToUserMessageByIdPi(args.messages, messageIdByIndex, reminder.messageId, reminder.text);
|
|
172250
|
+
if (!reinjected && args.isCacheBustingPass) {
|
|
172251
|
+
clearPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
172252
|
+
}
|
|
172253
|
+
return args.messages;
|
|
172254
|
+
}
|
|
172255
|
+
const latest = findLatestUserMessageIdPi(args.messages, messageIdByIndex);
|
|
172256
|
+
if (latest) {
|
|
172257
|
+
appendReminderToPiUserMessage(args.messages[latest.index], reminder.text);
|
|
172258
|
+
setPersistedStickyTurnReminder(args.db, args.sessionId, reminder.text, latest.messageId);
|
|
172259
|
+
}
|
|
172260
|
+
return args.messages;
|
|
172261
|
+
}
|
|
171607
172262
|
function applyNoteNudges(args) {
|
|
171608
172263
|
const { sessionId, db, messages, projectIdentity, entryIds, isCacheBusting } = args;
|
|
171609
172264
|
const messageIdByIndex = buildPiMessageIdByIndex(messages, entryIds);
|
|
@@ -171743,12 +172398,26 @@ function clearContextHandlerSession(sessionId) {
|
|
|
171743
172398
|
historyRefreshSessions.delete(sessionId);
|
|
171744
172399
|
pendingMaterializationSessions.delete(sessionId);
|
|
171745
172400
|
systemPromptRefreshSessions.delete(sessionId);
|
|
172401
|
+
deferredHistoryRefreshSessions.delete(sessionId);
|
|
172402
|
+
deferredMaterializationSessions.delete(sessionId);
|
|
171746
172403
|
firstContextPassSeenBySession.delete(sessionId);
|
|
171747
172404
|
commitSeenLastPass.delete(sessionId);
|
|
171748
172405
|
recentReduceBySession.delete(sessionId);
|
|
171749
172406
|
liveModelBySession.delete(sessionId);
|
|
171750
172407
|
toolUsageSinceUserTurn.delete(sessionId);
|
|
171751
172408
|
latestUserMessageBySession.delete(sessionId);
|
|
172409
|
+
lastHeuristicsTurnIdBySession.delete(sessionId);
|
|
172410
|
+
lastSeenProjectIdentityBySession.delete(sessionId);
|
|
172411
|
+
for (const [projectIdentity, sessions] of sessionsByProject) {
|
|
172412
|
+
sessions.delete(sessionId);
|
|
172413
|
+
if (sessions.size === 0)
|
|
172414
|
+
sessionsByProject.delete(projectIdentity);
|
|
172415
|
+
}
|
|
172416
|
+
const unregister = rawMessageProviderUnregistersBySession.get(sessionId);
|
|
172417
|
+
if (unregister) {
|
|
172418
|
+
unregister();
|
|
172419
|
+
rawMessageProviderUnregistersBySession.delete(sessionId);
|
|
172420
|
+
}
|
|
171752
172421
|
clearSessionTracking(sessionId);
|
|
171753
172422
|
clearPiCompressorState(sessionId);
|
|
171754
172423
|
}
|
|
@@ -171799,7 +172468,7 @@ init_logger();
|
|
|
171799
172468
|
init_data_path();
|
|
171800
172469
|
init_logger();
|
|
171801
172470
|
await init_sqlite();
|
|
171802
|
-
import { join as
|
|
172471
|
+
import { join as join16 } from "node:path";
|
|
171803
172472
|
var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
171804
172473
|
function randomBase62(length) {
|
|
171805
172474
|
const chars = [];
|
|
@@ -171820,7 +172489,7 @@ function generatePartId(timestampMs, counter = 0n) {
|
|
|
171820
172489
|
return generateId("prt", timestampMs, counter);
|
|
171821
172490
|
}
|
|
171822
172491
|
function getOpenCodeDbPath3() {
|
|
171823
|
-
return
|
|
172492
|
+
return join16(getDataDir(), "opencode", "opencode.db");
|
|
171824
172493
|
}
|
|
171825
172494
|
var cachedWriteDb = null;
|
|
171826
172495
|
var REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
|
|
@@ -172378,7 +173047,7 @@ async function runCompressorPass2(args) {
|
|
|
172378
173047
|
|
|
172379
173048
|
// ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
|
|
172380
173049
|
import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
172381
|
-
import { join as
|
|
173050
|
+
import { join as join17 } from "node:path";
|
|
172382
173051
|
init_data_path();
|
|
172383
173052
|
function historianResponseDumpDir(directory) {
|
|
172384
173053
|
return getProjectMagicContextHistorianDir(directory);
|
|
@@ -172632,7 +173301,7 @@ function dumpHistorianResponse(sessionId, directory, label, text) {
|
|
|
172632
173301
|
mkdirSync6(dumpDir, { recursive: true });
|
|
172633
173302
|
const safeSessionId = sanitizeDumpName(sessionId);
|
|
172634
173303
|
const safeLabel = sanitizeDumpName(label);
|
|
172635
|
-
const dumpPath =
|
|
173304
|
+
const dumpPath = join17(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
|
|
172636
173305
|
writeFileSync3(dumpPath, text, "utf8");
|
|
172637
173306
|
sessionLog(sessionId, "compartment agent: historian response dumped", {
|
|
172638
173307
|
label,
|
|
@@ -173838,7 +174507,7 @@ import {
|
|
|
173838
174507
|
// package.json
|
|
173839
174508
|
var package_default = {
|
|
173840
174509
|
name: "@cortexkit/pi-magic-context",
|
|
173841
|
-
version: "0.21.
|
|
174510
|
+
version: "0.21.1",
|
|
173842
174511
|
type: "module",
|
|
173843
174512
|
description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
|
|
173844
174513
|
main: "dist/index.js",
|
|
@@ -174290,6 +174959,8 @@ function registerCtxStatusCommand(pi, deps) {
|
|
|
174290
174959
|
pi.registerCommand("ctx-status", {
|
|
174291
174960
|
description: "Show Magic Context status for the current Pi session",
|
|
174292
174961
|
handler: async (_args, ctx) => {
|
|
174962
|
+
const projectIdentity = deps.resolveProject?.(ctx).projectIdentity ?? deps.projectIdentity;
|
|
174963
|
+
const currentDeps = { ...deps, projectIdentity };
|
|
174293
174964
|
const sessionId = resolveSessionId(ctx);
|
|
174294
174965
|
if (!sessionId) {
|
|
174295
174966
|
sendCtxStatusMessage(pi, {
|
|
@@ -174303,13 +174974,13 @@ No active Pi session is available.`,
|
|
|
174303
174974
|
}
|
|
174304
174975
|
try {
|
|
174305
174976
|
if (ctx.hasUI) {
|
|
174306
|
-
await showStatusDialog(pi, ctx,
|
|
174977
|
+
await showStatusDialog(pi, ctx, currentDeps);
|
|
174307
174978
|
return;
|
|
174308
174979
|
}
|
|
174309
174980
|
const usage = ctx.getContextUsage?.();
|
|
174310
174981
|
const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
174311
|
-
const statusText = executeStatus(
|
|
174312
|
-
const details = buildStatusDetails(
|
|
174982
|
+
const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.nudgeIntervalTokens, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usage?.contextWindow);
|
|
174983
|
+
const details = buildStatusDetails(currentDeps, sessionId);
|
|
174313
174984
|
sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
|
|
174314
174985
|
} catch (error51) {
|
|
174315
174986
|
sendCtxStatusMessage(pi, {
|
|
@@ -174434,405 +175105,6 @@ function stripTagPrefixFromAssistantMessage(message) {
|
|
|
174434
175105
|
return mutated;
|
|
174435
175106
|
}
|
|
174436
175107
|
|
|
174437
|
-
// src/system-prompt.ts
|
|
174438
|
-
import { createHash as createHash8 } from "node:crypto";
|
|
174439
|
-
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
174440
|
-
import { join as join17 } from "node:path";
|
|
174441
|
-
|
|
174442
|
-
// ../plugin/src/agents/magic-context-prompt.ts
|
|
174443
|
-
function getToolHistoryGuidance(dropToolStructure) {
|
|
174444
|
-
if (dropToolStructure) {
|
|
174445
|
-
return `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
|
|
174446
|
-
}
|
|
174447
|
-
return `Older tool calls in your conversation show truncated inputs and [truncated] outputs — this is normal context management, not a pattern to follow. The original tool calls executed successfully with full inputs and produced real outputs that were later cleaned up to save context. ALWAYS use real tool calls with complete arguments; never copy truncated patterns like "filePa...[truncated]" into your tool inputs. If you need to re-read a file or re-run a command, make a fresh tool call.`;
|
|
174448
|
-
}
|
|
174449
|
-
var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
|
|
174450
|
-
Use \`ctx_reduce\` to manage context size. It supports one operation:
|
|
174451
|
-
- \`drop\`: Remove entirely (best for tool outputs you already acted on).
|
|
174452
|
-
Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
|
|
174453
|
-
Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
|
|
174454
|
-
Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
|
|
174455
|
-
**Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
|
|
174456
|
-
- Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
|
|
174457
|
-
- Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
|
|
174458
|
-
- Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
|
|
174459
|
-
Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
|
|
174460
|
-
Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
|
|
174461
|
-
**Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
|
|
174462
|
-
- Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
|
|
174463
|
-
- Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
|
|
174464
|
-
- Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
|
|
174465
|
-
- Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
|
|
174466
|
-
- Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
|
|
174467
|
-
\`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
|
|
174468
|
-
${getToolHistoryGuidance(dropToolStructure)}
|
|
174469
|
-
NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
|
|
174470
|
-
NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
|
|
174471
|
-
NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
|
|
174472
|
-
Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
|
|
174473
|
-
var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
|
|
174474
|
-
Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
|
|
174475
|
-
**Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
|
|
174476
|
-
- Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
|
|
174477
|
-
- Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
|
|
174478
|
-
- Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
|
|
174479
|
-
Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
|
|
174480
|
-
Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
|
|
174481
|
-
**Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
|
|
174482
|
-
- Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
|
|
174483
|
-
- Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
|
|
174484
|
-
- Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
|
|
174485
|
-
- Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
|
|
174486
|
-
- Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
|
|
174487
|
-
\`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
|
|
174488
|
-
${getToolHistoryGuidance(dropToolStructure)}`;
|
|
174489
|
-
var GENERIC_SECTION = `
|
|
174490
|
-
### Reduction Triggers
|
|
174491
|
-
- After reading files or search results you already acted on — drop raw outputs.
|
|
174492
|
-
- After completing a logical step — drop intermediate outputs from that step.
|
|
174493
|
-
- Between major context switches — when moving to a new task area.
|
|
174494
|
-
|
|
174495
|
-
### What to Drop
|
|
174496
|
-
- Large file reads, grep results, and tool outputs you already used.
|
|
174497
|
-
- Large build/test output after you analyzed and acted on it.
|
|
174498
|
-
- Old diagnostic or exploration results that are no longer relevant.
|
|
174499
|
-
|
|
174500
|
-
### What to Keep
|
|
174501
|
-
- ALL user messages and assistant conversation text — these are cheap and compartmentalized automatically.
|
|
174502
|
-
- Your current task requirements and constraints.
|
|
174503
|
-
- Recent errors and unresolved decisions.
|
|
174504
|
-
- Active work context and files being edited.`;
|
|
174505
|
-
var TEMPORAL_AWARENESS_GUIDANCE = `
|
|
174506
|
-
**Temporal awareness**: User messages may be preceded by HTML comments like \`<!-- +12m -->\`, \`<!-- +2h 15m -->\`, or \`<!-- +3d 4h -->\` indicating time elapsed since the previous message's completion. Compartments in \`<session-history>\` carry \`start-date\` and \`end-date\` attributes (YYYY-MM-DD) showing real-time boundaries. Use these when reasoning about workflow pacing, log durations, build times, or how long ago something happened.`;
|
|
174507
|
-
var CAVEMAN_COMPRESSION_WARNING = `
|
|
174508
|
-
**BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
|
|
174509
|
-
function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, dropToolStructure = true, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false) {
|
|
174510
|
-
const smartNoteGuidance = dreamerEnabled ? `
|
|
174511
|
-
When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
|
|
174512
|
-
The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
|
|
174513
|
-
Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
|
|
174514
|
-
const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
|
|
174515
|
-
const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
|
|
174516
|
-
if (!ctxReduceEnabled) {
|
|
174517
|
-
return `## Magic Context
|
|
174518
|
-
|
|
174519
|
-
${BASE_INTRO_NO_REDUCE(dropToolStructure)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}`;
|
|
174520
|
-
}
|
|
174521
|
-
return `## Magic Context
|
|
174522
|
-
|
|
174523
|
-
${BASE_INTRO(protectedTags, dropToolStructure)}${smartNoteGuidance}${temporalGuidance}
|
|
174524
|
-
${GENERIC_SECTION}
|
|
174525
|
-
|
|
174526
|
-
Prefer many small targeted operations over one large blanket operation. Compress early and often — don't wait for warnings.`;
|
|
174527
|
-
}
|
|
174528
|
-
|
|
174529
|
-
// ../plugin/src/hooks/magic-context/key-files-block.ts
|
|
174530
|
-
import { readFileSync as readFileSync7, realpathSync as realpathSync3 } from "node:fs";
|
|
174531
|
-
import { join as join16, sep as sep2 } from "node:path";
|
|
174532
|
-
init_logger();
|
|
174533
|
-
var cachedKeyFilesBySession = new Map;
|
|
174534
|
-
var staleUpdates = new Map;
|
|
174535
|
-
function staleKey(update) {
|
|
174536
|
-
return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
|
|
174537
|
-
}
|
|
174538
|
-
function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
|
|
174539
|
-
const update = { projectPath, path: path5, generatedAtWitness, staleReason };
|
|
174540
|
-
staleUpdates.set(staleKey(update), update);
|
|
174541
|
-
}
|
|
174542
|
-
function flushStaleUpdates(db) {
|
|
174543
|
-
if (staleUpdates.size === 0)
|
|
174544
|
-
return 0;
|
|
174545
|
-
const updates = [...staleUpdates.values()];
|
|
174546
|
-
staleUpdates.clear();
|
|
174547
|
-
const stmt = db.prepare(`UPDATE project_key_files
|
|
174548
|
-
SET stale_reason = ?1
|
|
174549
|
-
WHERE project_path = ?2
|
|
174550
|
-
AND path = ?3
|
|
174551
|
-
AND generated_at = ?4
|
|
174552
|
-
AND (stale_reason IS NULL OR stale_reason != ?1)`);
|
|
174553
|
-
let changed = 0;
|
|
174554
|
-
for (const update of updates) {
|
|
174555
|
-
try {
|
|
174556
|
-
changed += stmt.run(update.staleReason, update.projectPath, update.path, update.generatedAtWitness).changes;
|
|
174557
|
-
} catch (error51) {
|
|
174558
|
-
log("[key-files] flushStaleUpdates failed:", error51);
|
|
174559
|
-
}
|
|
174560
|
-
}
|
|
174561
|
-
return changed;
|
|
174562
|
-
}
|
|
174563
|
-
function clearKeyFilesCacheForSession(sessionId) {
|
|
174564
|
-
cachedKeyFilesBySession.delete(sessionId);
|
|
174565
|
-
}
|
|
174566
|
-
function isUnderProject(projectPath, absPath) {
|
|
174567
|
-
const root = realpathSync3(projectPath);
|
|
174568
|
-
return absPath.startsWith(root + sep2) || absPath === root;
|
|
174569
|
-
}
|
|
174570
|
-
function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBudget: 1e4 }) {
|
|
174571
|
-
if (!config2.enabled)
|
|
174572
|
-
return null;
|
|
174573
|
-
if (!isAftAvailable())
|
|
174574
|
-
return null;
|
|
174575
|
-
const rows = readCurrentKeyFiles(db, projectPath);
|
|
174576
|
-
if (rows.length === 0)
|
|
174577
|
-
return null;
|
|
174578
|
-
for (const row of rows) {
|
|
174579
|
-
if (row.staleReason !== null)
|
|
174580
|
-
continue;
|
|
174581
|
-
let nextStale = null;
|
|
174582
|
-
let observed = false;
|
|
174583
|
-
try {
|
|
174584
|
-
const absPath = join16(projectPath, row.path);
|
|
174585
|
-
const real = realpathSync3(absPath);
|
|
174586
|
-
if (!isUnderProject(projectPath, real)) {
|
|
174587
|
-
nextStale = "missing";
|
|
174588
|
-
observed = true;
|
|
174589
|
-
} else {
|
|
174590
|
-
const diskHash = sha256(readFileSync7(real));
|
|
174591
|
-
if (diskHash !== row.contentHash)
|
|
174592
|
-
nextStale = "content_drift";
|
|
174593
|
-
observed = true;
|
|
174594
|
-
}
|
|
174595
|
-
} catch (error51) {
|
|
174596
|
-
const code = error51?.code;
|
|
174597
|
-
if (code === "ENOENT" || code === "ELOOP") {
|
|
174598
|
-
nextStale = "missing";
|
|
174599
|
-
observed = true;
|
|
174600
|
-
} else {
|
|
174601
|
-
log(`[key-files] freshness check transient failure: ${row.path}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
174602
|
-
}
|
|
174603
|
-
}
|
|
174604
|
-
if (observed && nextStale !== null) {
|
|
174605
|
-
queueStaleUpdate(row.projectPath, row.path, row.generatedAt, nextStale);
|
|
174606
|
-
}
|
|
174607
|
-
}
|
|
174608
|
-
const blocks = [];
|
|
174609
|
-
let used = 0;
|
|
174610
|
-
for (const row of rows) {
|
|
174611
|
-
if (used + row.localTokenEstimate > config2.tokenBudget)
|
|
174612
|
-
break;
|
|
174613
|
-
blocks.push(` <key-file path="${escapeXmlAttr(row.path)}">
|
|
174614
|
-
${escapeXmlContent(row.content)}
|
|
174615
|
-
</key-file>`);
|
|
174616
|
-
used += row.localTokenEstimate;
|
|
174617
|
-
}
|
|
174618
|
-
if (blocks.length === 0)
|
|
174619
|
-
return null;
|
|
174620
|
-
const rendered = `<key-files>
|
|
174621
|
-
${blocks.join(`
|
|
174622
|
-
`)}
|
|
174623
|
-
</key-files>`;
|
|
174624
|
-
flushStaleUpdates(db);
|
|
174625
|
-
return rendered;
|
|
174626
|
-
}
|
|
174627
|
-
function readVersionedKeyFiles(args) {
|
|
174628
|
-
const config2 = args.config ?? { enabled: true, tokenBudget: 1e4 };
|
|
174629
|
-
if (args.sessionMeta.isSubagent)
|
|
174630
|
-
return null;
|
|
174631
|
-
if (!config2.enabled)
|
|
174632
|
-
return null;
|
|
174633
|
-
if (!isAftAvailable())
|
|
174634
|
-
return null;
|
|
174635
|
-
const projectPath = resolveProjectPath(args.directory ?? args.sessionMeta.sessionId);
|
|
174636
|
-
if (!projectPath)
|
|
174637
|
-
return null;
|
|
174638
|
-
const currentVersion = getKeyFilesVersion(args.db, projectPath);
|
|
174639
|
-
if (args.sessionId) {
|
|
174640
|
-
const cached2 = cachedKeyFilesBySession.get(args.sessionId);
|
|
174641
|
-
if (cached2 && !args.isCacheBusting && cached2.version === currentVersion) {
|
|
174642
|
-
return cached2.value;
|
|
174643
|
-
}
|
|
174644
|
-
}
|
|
174645
|
-
const value = buildKeyFilesBlock(args.db, projectPath, config2);
|
|
174646
|
-
if (args.sessionId) {
|
|
174647
|
-
cachedKeyFilesBySession.set(args.sessionId, { value, version: currentVersion });
|
|
174648
|
-
if (value)
|
|
174649
|
-
sessionLog(args.sessionId, `loaded key-files block (v${currentVersion}, ${value.length} chars)`);
|
|
174650
|
-
}
|
|
174651
|
-
return value;
|
|
174652
|
-
}
|
|
174653
|
-
|
|
174654
|
-
// src/system-prompt.ts
|
|
174655
|
-
init_logger();
|
|
174656
|
-
var PROJECT_DOCS_MARKER = "<project-docs>";
|
|
174657
|
-
var USER_PROFILE_MARKER = "<user-profile>";
|
|
174658
|
-
var stickyDateBySession = new Map;
|
|
174659
|
-
var cachedDocsBySession = new Map;
|
|
174660
|
-
var cachedUserProfileBySession = new Map;
|
|
174661
|
-
var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
|
|
174662
|
-
function readProjectDocs(directory) {
|
|
174663
|
-
const sections = [];
|
|
174664
|
-
for (const filename of DOC_FILES) {
|
|
174665
|
-
const filePath = join17(directory, filename);
|
|
174666
|
-
try {
|
|
174667
|
-
if (existsSync11(filePath)) {
|
|
174668
|
-
const content = readFileSync8(filePath, "utf-8").trim();
|
|
174669
|
-
if (content.length > 0) {
|
|
174670
|
-
sections.push(`<${filename}>
|
|
174671
|
-
${content}
|
|
174672
|
-
</${filename}>`);
|
|
174673
|
-
}
|
|
174674
|
-
}
|
|
174675
|
-
} catch (error51) {
|
|
174676
|
-
log(`[magic-context-pi] failed to read ${filename}:`, error51);
|
|
174677
|
-
}
|
|
174678
|
-
}
|
|
174679
|
-
if (sections.length === 0)
|
|
174680
|
-
return null;
|
|
174681
|
-
return `${PROJECT_DOCS_MARKER}
|
|
174682
|
-
${sections.join(`
|
|
174683
|
-
|
|
174684
|
-
`)}
|
|
174685
|
-
</project-docs>`;
|
|
174686
|
-
}
|
|
174687
|
-
function buildUserProfileBlock(db) {
|
|
174688
|
-
const memories = getActiveUserMemories(db);
|
|
174689
|
-
if (memories.length === 0)
|
|
174690
|
-
return null;
|
|
174691
|
-
const items = memories.map((m) => `- ${m.content}`).join(`
|
|
174692
|
-
`);
|
|
174693
|
-
return `${USER_PROFILE_MARKER}
|
|
174694
|
-
${items}
|
|
174695
|
-
</user-profile>`;
|
|
174696
|
-
}
|
|
174697
|
-
function buildMagicContextBlock(opts) {
|
|
174698
|
-
const sections = [];
|
|
174699
|
-
const sessionId = opts.sessionId;
|
|
174700
|
-
const isCacheBusting = opts.isCacheBusting ?? false;
|
|
174701
|
-
if (opts.injectDocs) {
|
|
174702
|
-
const docsBlock = readCachedAdjunct({
|
|
174703
|
-
cache: cachedDocsBySession,
|
|
174704
|
-
sessionId,
|
|
174705
|
-
isCacheBusting,
|
|
174706
|
-
compute: () => readProjectDocs(opts.cwd),
|
|
174707
|
-
describe: "project docs"
|
|
174708
|
-
});
|
|
174709
|
-
if (docsBlock)
|
|
174710
|
-
sections.push(docsBlock);
|
|
174711
|
-
}
|
|
174712
|
-
if (opts.userMemoriesEnabled) {
|
|
174713
|
-
const profileBlock = readCachedAdjunct({
|
|
174714
|
-
cache: cachedUserProfileBySession,
|
|
174715
|
-
sessionId,
|
|
174716
|
-
isCacheBusting,
|
|
174717
|
-
compute: () => buildUserProfileBlock(opts.db),
|
|
174718
|
-
describe: "user profile"
|
|
174719
|
-
});
|
|
174720
|
-
if (profileBlock)
|
|
174721
|
-
sections.push(profileBlock);
|
|
174722
|
-
}
|
|
174723
|
-
if (opts.pinKeyFilesEnabled && sessionId) {
|
|
174724
|
-
let sessionMeta = null;
|
|
174725
|
-
try {
|
|
174726
|
-
sessionMeta = getOrCreateSessionMeta(opts.db, sessionId);
|
|
174727
|
-
} catch (error51) {
|
|
174728
|
-
sessionLog(sessionId, "Pi key-files session meta load failed:", error51);
|
|
174729
|
-
}
|
|
174730
|
-
const keyFilesBlock = sessionMeta ? readVersionedKeyFiles({
|
|
174731
|
-
db: opts.db,
|
|
174732
|
-
sessionId,
|
|
174733
|
-
sessionMeta,
|
|
174734
|
-
directory: opts.cwd,
|
|
174735
|
-
isCacheBusting,
|
|
174736
|
-
config: {
|
|
174737
|
-
enabled: opts.pinKeyFilesEnabled,
|
|
174738
|
-
tokenBudget: opts.pinKeyFilesTokenBudget ?? 1e4
|
|
174739
|
-
}
|
|
174740
|
-
}) : null;
|
|
174741
|
-
if (keyFilesBlock)
|
|
174742
|
-
sections.push(keyFilesBlock);
|
|
174743
|
-
}
|
|
174744
|
-
const dataBlock = sections.length > 0 ? `<magic-context>
|
|
174745
|
-
${sections.join(`
|
|
174746
|
-
|
|
174747
|
-
`)}
|
|
174748
|
-
</magic-context>` : null;
|
|
174749
|
-
const includeGuidance = opts.includeGuidance ?? true;
|
|
174750
|
-
if (!includeGuidance) {
|
|
174751
|
-
return dataBlock;
|
|
174752
|
-
}
|
|
174753
|
-
const guidance = buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.dropToolStructure ?? true, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false);
|
|
174754
|
-
if (dataBlock) {
|
|
174755
|
-
return `${guidance}
|
|
174756
|
-
|
|
174757
|
-
${dataBlock}`;
|
|
174758
|
-
}
|
|
174759
|
-
return guidance;
|
|
174760
|
-
}
|
|
174761
|
-
function readCachedAdjunct(args) {
|
|
174762
|
-
const { cache, sessionId, isCacheBusting, compute, describe: describe3 } = args;
|
|
174763
|
-
if (!sessionId) {
|
|
174764
|
-
return compute();
|
|
174765
|
-
}
|
|
174766
|
-
const hasCached = cache.has(sessionId);
|
|
174767
|
-
if (!hasCached || isCacheBusting) {
|
|
174768
|
-
const value = compute();
|
|
174769
|
-
cache.set(sessionId, value);
|
|
174770
|
-
if (value && !hasCached) {
|
|
174771
|
-
sessionLog(sessionId, `loaded ${describe3} (${value.length} chars)`);
|
|
174772
|
-
} else if (value && isCacheBusting) {
|
|
174773
|
-
sessionLog(sessionId, `refreshed ${describe3} (cache-busting pass)`);
|
|
174774
|
-
}
|
|
174775
|
-
return value;
|
|
174776
|
-
}
|
|
174777
|
-
return cache.get(sessionId) ?? null;
|
|
174778
|
-
}
|
|
174779
|
-
var DATE_PATTERN = /Today's date: .+/;
|
|
174780
|
-
function processSystemPromptForCache(args) {
|
|
174781
|
-
const { db, sessionId, systemPrompt, isCacheBusting } = args;
|
|
174782
|
-
let sessionMeta;
|
|
174783
|
-
try {
|
|
174784
|
-
sessionMeta = getOrCreateSessionMeta(db, sessionId);
|
|
174785
|
-
} catch (error51) {
|
|
174786
|
-
sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
|
|
174787
|
-
}
|
|
174788
|
-
const previousHash = sessionMeta?.systemPromptHash ?? "";
|
|
174789
|
-
const isFirstHash = previousHash === "" || previousHash === "0";
|
|
174790
|
-
let frozenPrompt = systemPrompt;
|
|
174791
|
-
const dateMatch = systemPrompt.match(DATE_PATTERN);
|
|
174792
|
-
const liveDate = dateMatch ? dateMatch[0] : null;
|
|
174793
|
-
const stickyDate = stickyDateBySession.get(sessionId);
|
|
174794
|
-
if (liveDate && !stickyDate) {
|
|
174795
|
-
stickyDateBySession.set(sessionId, liveDate);
|
|
174796
|
-
} else if (liveDate && stickyDate && liveDate !== stickyDate) {
|
|
174797
|
-
if (isCacheBusting) {
|
|
174798
|
-
stickyDateBySession.set(sessionId, liveDate);
|
|
174799
|
-
sessionLog(sessionId, `system prompt date updated: ${stickyDate} → ${liveDate} (cache-busting pass)`);
|
|
174800
|
-
} else {
|
|
174801
|
-
frozenPrompt = systemPrompt.replace(DATE_PATTERN, stickyDate);
|
|
174802
|
-
sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
|
|
174803
|
-
}
|
|
174804
|
-
}
|
|
174805
|
-
const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
|
|
174806
|
-
const hashChanged = !isFirstHash && currentHash !== previousHash;
|
|
174807
|
-
if (hashChanged) {
|
|
174808
|
-
sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
|
|
174809
|
-
} else if (isFirstHash) {
|
|
174810
|
-
sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${frozenPrompt.length})`);
|
|
174811
|
-
}
|
|
174812
|
-
const systemPromptTokens = estimateTokens(frozenPrompt);
|
|
174813
|
-
if (sessionMeta) {
|
|
174814
|
-
if (currentHash !== previousHash) {
|
|
174815
|
-
updateSessionMeta(db, sessionId, {
|
|
174816
|
-
systemPromptHash: currentHash,
|
|
174817
|
-
systemPromptTokens
|
|
174818
|
-
});
|
|
174819
|
-
} else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
|
|
174820
|
-
updateSessionMeta(db, sessionId, { systemPromptTokens });
|
|
174821
|
-
}
|
|
174822
|
-
}
|
|
174823
|
-
return {
|
|
174824
|
-
systemPrompt: frozenPrompt,
|
|
174825
|
-
hashChanged,
|
|
174826
|
-
currentHash
|
|
174827
|
-
};
|
|
174828
|
-
}
|
|
174829
|
-
function clearPiSystemPromptSession(sessionId) {
|
|
174830
|
-
stickyDateBySession.delete(sessionId);
|
|
174831
|
-
cachedDocsBySession.delete(sessionId);
|
|
174832
|
-
cachedUserProfileBySession.delete(sessionId);
|
|
174833
|
-
clearKeyFilesCacheForSession(sessionId);
|
|
174834
|
-
}
|
|
174835
|
-
|
|
174836
175108
|
// ../plugin/src/tools/ctx-expand/constants.ts
|
|
174837
175109
|
var CTX_EXPAND_DESCRIPTION = "Decompress a compartment range to see the original conversation transcript. " + 'Use start/end from <compartment start="N" end="M"> attributes. ' + "Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.";
|
|
174838
175110
|
var CTX_EXPAND_TOKEN_BUDGET = 15000;
|
|
@@ -178974,6 +179246,7 @@ var ALL_ACTIONS = [
|
|
|
178974
179246
|
"archive"
|
|
178975
179247
|
];
|
|
178976
179248
|
var DREAMER_ONLY_ACTIONS = new Set([
|
|
179249
|
+
"list",
|
|
178977
179250
|
"update",
|
|
178978
179251
|
"merge",
|
|
178979
179252
|
"archive"
|
|
@@ -179062,7 +179335,7 @@ function queueEmbedding(args) {
|
|
|
179062
179335
|
}
|
|
179063
179336
|
function createCtxMemoryTool(deps) {
|
|
179064
179337
|
const dreamerAllowed = deps.allowDreamerActions === true;
|
|
179065
|
-
const description = dreamerAllowed ? "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete, list, update, merge, archive." : "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete
|
|
179338
|
+
const description = dreamerAllowed ? "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete, list, update, merge, archive." : "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete.";
|
|
179066
179339
|
return {
|
|
179067
179340
|
name: "ctx_memory",
|
|
179068
179341
|
label: "Magic Context: Memory",
|
|
@@ -179655,8 +179928,10 @@ function formatResult(result, index) {
|
|
|
179655
179928
|
].join(`
|
|
179656
179929
|
`);
|
|
179657
179930
|
}
|
|
179931
|
+
const expandStart = Math.max(1, result.messageOrdinal - 3);
|
|
179932
|
+
const expandEnd = result.messageOrdinal + 3;
|
|
179658
179933
|
return [
|
|
179659
|
-
`[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} role=${result.role}`,
|
|
179934
|
+
`[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} range=${expandStart}-${expandEnd} role=${result.role}`,
|
|
179660
179935
|
result.content
|
|
179661
179936
|
].join(`
|
|
179662
179937
|
`);
|
|
@@ -179665,7 +179940,11 @@ function formatSearchResults(query, results) {
|
|
|
179665
179940
|
if (results.length === 0) {
|
|
179666
179941
|
return `No results found for "${query}" across memories, git commits, or message history.`;
|
|
179667
179942
|
}
|
|
179668
|
-
const
|
|
179943
|
+
const bodyParts = results.map((result, index) => formatResult(result, index + 1));
|
|
179944
|
+
if (results.some((result) => result.source === "message")) {
|
|
179945
|
+
bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
|
|
179946
|
+
}
|
|
179947
|
+
const body = bodyParts.join(`
|
|
179669
179948
|
|
|
179670
179949
|
`);
|
|
179671
179950
|
return `Found ${results.length} result${results.length === 1 ? "" : "s"} for "${query}":
|
|
@@ -179825,6 +180104,11 @@ function registerMagicContextTools(pi, opts) {
|
|
|
179825
180104
|
|
|
179826
180105
|
// src/index.ts
|
|
179827
180106
|
var PREFIX2 = "[magic-context][pi]";
|
|
180107
|
+
function resolveCurrentProject(ctx) {
|
|
180108
|
+
const projectDir = ctx.cwd;
|
|
180109
|
+
const projectIdentity = resolveProjectIdentity(projectDir);
|
|
180110
|
+
return { projectDir, projectIdentity };
|
|
180111
|
+
}
|
|
179828
180112
|
function persistPiMessageEndModelMeta(args) {
|
|
179829
180113
|
if (!args.message || typeof args.message !== "object")
|
|
179830
180114
|
return;
|
|
@@ -180015,6 +180299,7 @@ async function src_default2(pi) {
|
|
|
180015
180299
|
}
|
|
180016
180300
|
const projectDir = process.cwd();
|
|
180017
180301
|
const projectIdentity = resolveProjectIdentity(projectDir);
|
|
180302
|
+
const seenDreamerProjectIdentities = new Set([projectIdentity]);
|
|
180018
180303
|
info(`loaded v${PLUGIN_VERSION} | harness=pi | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
|
|
180019
180304
|
const { config: config2, warnings, loadedFromPaths } = loadPiConfig({
|
|
180020
180305
|
cwd: projectDir
|
|
@@ -180045,7 +180330,10 @@ async function src_default2(pi) {
|
|
|
180045
180330
|
const historianConfig = resolveHistorianFromConfig(config2);
|
|
180046
180331
|
if (historianConfig) {
|
|
180047
180332
|
historianConfig.onStatusChange = (ctx) => {
|
|
180048
|
-
updateStatusLine(ctx, {
|
|
180333
|
+
updateStatusLine(ctx, {
|
|
180334
|
+
db,
|
|
180335
|
+
projectIdentity: resolveCurrentProject(ctx).projectIdentity
|
|
180336
|
+
});
|
|
180049
180337
|
};
|
|
180050
180338
|
}
|
|
180051
180339
|
const nudgeConfig = resolveNudgeFromConfig(config2);
|
|
@@ -180084,12 +180372,17 @@ async function src_default2(pi) {
|
|
|
180084
180372
|
registerCtxStatusCommand(pi, {
|
|
180085
180373
|
db,
|
|
180086
180374
|
projectIdentity,
|
|
180375
|
+
resolveProject: resolveCurrentProject,
|
|
180087
180376
|
protectedTags: config2.protected_tags,
|
|
180088
180377
|
nudgeIntervalTokens: config2.nudge_interval_tokens,
|
|
180089
180378
|
executeThresholdPercentage: config2.execute_threshold_percentage,
|
|
180090
180379
|
historyBudgetPercentage: config2.history_budget_percentage,
|
|
180091
180380
|
commitClusterTrigger: config2.commit_cluster_trigger,
|
|
180092
|
-
executeThresholdTokens: config2.execute_threshold_tokens
|
|
180381
|
+
executeThresholdTokens: config2.execute_threshold_tokens,
|
|
180382
|
+
dreamer: {
|
|
180383
|
+
enabled: config2.dreamer?.enabled ?? false,
|
|
180384
|
+
schedule: config2.dreamer?.schedule
|
|
180385
|
+
}
|
|
180093
180386
|
});
|
|
180094
180387
|
info("registered /ctx-status");
|
|
180095
180388
|
registerStatusLine(pi, { db, projectIdentity });
|
|
@@ -180111,7 +180404,10 @@ async function src_default2(pi) {
|
|
|
180111
180404
|
registerCtxDreamCommand(pi, {
|
|
180112
180405
|
db,
|
|
180113
180406
|
projectDir,
|
|
180114
|
-
projectIdentity
|
|
180407
|
+
projectIdentity,
|
|
180408
|
+
resolveProject: resolveCurrentProject,
|
|
180409
|
+
dreamerEnabled: config2.dreamer?.enabled ?? false,
|
|
180410
|
+
onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
|
|
180115
180411
|
});
|
|
180116
180412
|
info("registered /ctx-dream");
|
|
180117
180413
|
const dreamerConfig = resolveDreamerFromConfig(config2);
|
|
@@ -180123,7 +180419,8 @@ async function src_default2(pi) {
|
|
|
180123
180419
|
config: dreamerConfig,
|
|
180124
180420
|
embeddingConfig: config2.embedding,
|
|
180125
180421
|
memoryEnabled: config2.memory.enabled,
|
|
180126
|
-
gitCommitIndexing: config2.experimental.git_commit_indexing
|
|
180422
|
+
gitCommitIndexing: config2.experimental.git_commit_indexing,
|
|
180423
|
+
onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
|
|
180127
180424
|
});
|
|
180128
180425
|
info(dreamerConfig.enabled ? `registered dreamer (schedule=${dreamerConfig.schedule}, tasks=[${dreamerConfig.tasks.join(",")}])` : "registered dreamer: DISABLED (dreamer.enabled=false)");
|
|
180129
180426
|
} else {
|
|
@@ -180131,6 +180428,8 @@ async function src_default2(pi) {
|
|
|
180131
180428
|
}
|
|
180132
180429
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
180133
180430
|
try {
|
|
180431
|
+
const currentProject = resolveCurrentProject(ctx);
|
|
180432
|
+
seenDreamerProjectIdentities.add(currentProject.projectIdentity);
|
|
180134
180433
|
const sm = ctx.sessionManager;
|
|
180135
180434
|
let sessionId;
|
|
180136
180435
|
if (sm !== undefined) {
|
|
@@ -180143,10 +180442,20 @@ async function src_default2(pi) {
|
|
|
180143
180442
|
} catch {}
|
|
180144
180443
|
}
|
|
180145
180444
|
}
|
|
180445
|
+
if (sessionId) {
|
|
180446
|
+
trackSessionForProject(currentProject.projectIdentity, sessionId);
|
|
180447
|
+
}
|
|
180448
|
+
if (config2.system_prompt_injection?.enabled === false) {
|
|
180449
|
+
return;
|
|
180450
|
+
}
|
|
180451
|
+
const skipSigs = config2.system_prompt_injection?.skip_signatures ?? [];
|
|
180452
|
+
if (skipSigs.some((sig) => sig.length > 0 && event.systemPrompt.includes(sig))) {
|
|
180453
|
+
return;
|
|
180454
|
+
}
|
|
180146
180455
|
const isCacheBusting = sessionId ? hasSystemPromptRefresh(sessionId) : true;
|
|
180147
180456
|
const block = buildMagicContextBlock({
|
|
180148
180457
|
db,
|
|
180149
|
-
cwd:
|
|
180458
|
+
cwd: currentProject.projectDir,
|
|
180150
180459
|
sessionId,
|
|
180151
180460
|
memoryEnabled: config2.memory.enabled,
|
|
180152
180461
|
injectDocs: config2.dreamer?.inject_docs ?? true,
|
|
@@ -180160,7 +180469,8 @@ async function src_default2(pi) {
|
|
|
180160
180469
|
userMemoriesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.user_memories?.enabled ?? false),
|
|
180161
180470
|
pinKeyFilesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.pin_key_files?.enabled ?? false),
|
|
180162
180471
|
pinKeyFilesTokenBudget: config2.dreamer?.pin_key_files?.token_budget ?? 1e4,
|
|
180163
|
-
isCacheBusting
|
|
180472
|
+
isCacheBusting,
|
|
180473
|
+
existingSystemPrompt: event.systemPrompt
|
|
180164
180474
|
});
|
|
180165
180475
|
const composedPrompt = block ? `${event.systemPrompt}
|
|
180166
180476
|
|
|
@@ -180252,10 +180562,13 @@ ${block}` : event.systemPrompt;
|
|
|
180252
180562
|
const sessionId = sm?.getSessionId?.();
|
|
180253
180563
|
if (typeof sessionId !== "string" || sessionId.length === 0)
|
|
180254
180564
|
return;
|
|
180255
|
-
const
|
|
180256
|
-
|
|
180257
|
-
|
|
180258
|
-
scheduleIncrementalIndex(db, sessionId,
|
|
180565
|
+
const endedMsg = event.message;
|
|
180566
|
+
if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
|
|
180567
|
+
const messageId = endedMsg.id;
|
|
180568
|
+
scheduleIncrementalIndex(db, sessionId, messageId, () => {
|
|
180569
|
+
const rawMessages = readPiSessionMessages(ctx);
|
|
180570
|
+
return rawMessages.find((message) => message.id === messageId) ?? null;
|
|
180571
|
+
});
|
|
180259
180572
|
}
|
|
180260
180573
|
persistPiMessageEndModelMeta({
|
|
180261
180574
|
db,
|
|
@@ -180358,7 +180671,9 @@ ${block}` : event.systemPrompt;
|
|
|
180358
180671
|
warn("shutdown: dreamer drain threw:", err5);
|
|
180359
180672
|
}
|
|
180360
180673
|
try {
|
|
180361
|
-
|
|
180674
|
+
for (const identity of seenDreamerProjectIdentities) {
|
|
180675
|
+
unregisterPiDreamerProject({ projectIdentity: identity });
|
|
180676
|
+
}
|
|
180362
180677
|
} catch (err5) {
|
|
180363
180678
|
warn("shutdown: unregisterPiDreamerProject threw:", err5);
|
|
180364
180679
|
}
|