@cortexkit/pi-magic-context 0.21.0 → 0.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +885 -600
- package/dist/subagent-entry.js +23 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8163,10 +8163,10 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
8163
8163
|
replacer = null;
|
|
8164
8164
|
indent = EMPTY;
|
|
8165
8165
|
};
|
|
8166
|
-
var
|
|
8166
|
+
var join9 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
|
|
8167
8167
|
var join_content = (inside, value, gap) => {
|
|
8168
8168
|
const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
|
|
8169
|
-
return
|
|
8169
|
+
return join9(comment, inside, gap);
|
|
8170
8170
|
};
|
|
8171
8171
|
var stringify_string = (holder, key, value) => {
|
|
8172
8172
|
const raw = get_raw_string_literal(holder, key);
|
|
@@ -8188,13 +8188,13 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
8188
8188
|
if (i !== 0) {
|
|
8189
8189
|
inside += COMMA;
|
|
8190
8190
|
}
|
|
8191
|
-
const before =
|
|
8191
|
+
const before = join9(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
|
|
8192
8192
|
inside += before || LF + deeper_gap;
|
|
8193
8193
|
inside += stringify(i, value, deeper_gap) || STR_NULL;
|
|
8194
8194
|
inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
|
|
8195
8195
|
after_comma = process_comments(value, AFTER(i), deeper_gap);
|
|
8196
8196
|
}
|
|
8197
|
-
inside +=
|
|
8197
|
+
inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
|
|
8198
8198
|
return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
|
|
8199
8199
|
};
|
|
8200
8200
|
var object_stringify = (value, gap) => {
|
|
@@ -8215,13 +8215,13 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
8215
8215
|
inside += COMMA;
|
|
8216
8216
|
}
|
|
8217
8217
|
first = false;
|
|
8218
|
-
const before =
|
|
8218
|
+
const before = join9(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
|
|
8219
8219
|
inside += before || LF + deeper_gap;
|
|
8220
8220
|
inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
|
|
8221
8221
|
after_comma = process_comments(value, AFTER(key), deeper_gap);
|
|
8222
8222
|
};
|
|
8223
8223
|
keys.forEach(iteratee);
|
|
8224
|
-
inside +=
|
|
8224
|
+
inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
|
|
8225
8225
|
return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
|
|
8226
8226
|
};
|
|
8227
8227
|
function stringify(key, holder, gap) {
|
|
@@ -8342,8 +8342,8 @@ var init_rpc_notifications = __esm(() => {
|
|
|
8342
8342
|
});
|
|
8343
8343
|
|
|
8344
8344
|
// src/index.ts
|
|
8345
|
-
import { createRequire as
|
|
8346
|
-
import { join as
|
|
8345
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
8346
|
+
import { join as join19 } from "node:path";
|
|
8347
8347
|
|
|
8348
8348
|
// ../plugin/src/features/magic-context/memory/project-identity.ts
|
|
8349
8349
|
import { execSync } from "node:child_process";
|
|
@@ -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;
|
|
@@ -145203,9 +145216,22 @@ init_logger();
|
|
|
145203
145216
|
// src/subagent-runner.ts
|
|
145204
145217
|
import * as childProcess from "node:child_process";
|
|
145205
145218
|
import { existsSync as existsSync6 } from "node:fs";
|
|
145206
|
-
import {
|
|
145219
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
145220
|
+
import { dirname as dirname3, join as join8, resolve as resolvePath } from "node:path";
|
|
145207
145221
|
import { createInterface } from "node:readline";
|
|
145208
145222
|
import { fileURLToPath } from "node:url";
|
|
145223
|
+
function resolveBundledPiCli() {
|
|
145224
|
+
try {
|
|
145225
|
+
const require_ = createRequire3(import.meta.url);
|
|
145226
|
+
const pkgJson = require_.resolve("@earendil-works/pi-coding-agent/package.json");
|
|
145227
|
+
const cliPath = join8(dirname3(pkgJson), "dist/cli.js");
|
|
145228
|
+
if (existsSync6(cliPath))
|
|
145229
|
+
return cliPath;
|
|
145230
|
+
return null;
|
|
145231
|
+
} catch {
|
|
145232
|
+
return null;
|
|
145233
|
+
}
|
|
145234
|
+
}
|
|
145209
145235
|
function resolveSubagentEntryPath() {
|
|
145210
145236
|
try {
|
|
145211
145237
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
@@ -145229,7 +145255,16 @@ class PiSubagentRunner {
|
|
|
145229
145255
|
piBinary;
|
|
145230
145256
|
spawnImpl;
|
|
145231
145257
|
constructor(options = {}) {
|
|
145232
|
-
|
|
145258
|
+
if (options.piBinary) {
|
|
145259
|
+
this.piBinary = options.piBinary;
|
|
145260
|
+
} else {
|
|
145261
|
+
const bundled = resolveBundledPiCli();
|
|
145262
|
+
if (bundled) {
|
|
145263
|
+
this.piBinary = bundled;
|
|
145264
|
+
} else {
|
|
145265
|
+
this.piBinary = "pi";
|
|
145266
|
+
}
|
|
145267
|
+
}
|
|
145233
145268
|
this.spawnImpl = options.spawnImpl ?? childProcess.spawn;
|
|
145234
145269
|
}
|
|
145235
145270
|
async run(options) {
|
|
@@ -145455,10 +145490,10 @@ class PiSubagentRunner {
|
|
|
145455
145490
|
});
|
|
145456
145491
|
return;
|
|
145457
145492
|
}
|
|
145458
|
-
if (finalStopReason === "error" || finalStopReason === "aborted") {
|
|
145493
|
+
if (finalStopReason === "error" || finalStopReason === "aborted" || finalStopReason === "length") {
|
|
145459
145494
|
settle({
|
|
145460
145495
|
ok: false,
|
|
145461
|
-
reason: "model_failed",
|
|
145496
|
+
reason: finalStopReason === "length" ? "truncated" : "model_failed",
|
|
145462
145497
|
error: finalErrorMessage ?? `pi assistant stopped with reason "${finalStopReason}"`,
|
|
145463
145498
|
durationMs: Date.now() - startTime,
|
|
145464
145499
|
meta: { stderr: stderr.length > 0 ? stderr : undefined }
|
|
@@ -145580,10 +145615,18 @@ function parsePiEventLine(line) {
|
|
|
145580
145615
|
}
|
|
145581
145616
|
}
|
|
145582
145617
|
function terminateChild(child) {
|
|
145618
|
+
let exited = false;
|
|
145619
|
+
child.once("close", () => {
|
|
145620
|
+
exited = true;
|
|
145621
|
+
});
|
|
145622
|
+
child.once("exit", () => {
|
|
145623
|
+
exited = true;
|
|
145624
|
+
});
|
|
145583
145625
|
child.kill("SIGTERM");
|
|
145584
145626
|
const killHandle = setTimeout(() => {
|
|
145585
|
-
if (!child.
|
|
145627
|
+
if (!exited && child.exitCode === null && child.signalCode === null) {
|
|
145586
145628
|
child.kill("SIGKILL");
|
|
145629
|
+
}
|
|
145587
145630
|
}, 2000);
|
|
145588
145631
|
if (typeof killHandle.unref === "function") {
|
|
145589
145632
|
killHandle.unref();
|
|
@@ -145832,7 +145875,7 @@ function clearStaleEntries(db, maxAgeMs) {
|
|
|
145832
145875
|
init_logger();
|
|
145833
145876
|
// ../plugin/src/features/magic-context/dreamer/runner.ts
|
|
145834
145877
|
import { existsSync as existsSync8 } from "node:fs";
|
|
145835
|
-
import { join as
|
|
145878
|
+
import { join as join11 } from "node:path";
|
|
145836
145879
|
|
|
145837
145880
|
// ../plugin/src/shared/index.ts
|
|
145838
145881
|
init_logger();
|
|
@@ -146127,14 +146170,14 @@ await init_sqlite();
|
|
|
146127
146170
|
|
|
146128
146171
|
// ../plugin/src/features/magic-context/key-files/identify-key-files.ts
|
|
146129
146172
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
146130
|
-
import { join as
|
|
146173
|
+
import { join as join10 } from "node:path";
|
|
146131
146174
|
init_logger();
|
|
146132
146175
|
|
|
146133
146176
|
// ../plugin/src/features/magic-context/key-files/aft-availability.ts
|
|
146134
146177
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
146135
146178
|
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
|
|
146136
146179
|
import { homedir as homedir3 } from "node:os";
|
|
146137
|
-
import { join as
|
|
146180
|
+
import { join as join9 } from "node:path";
|
|
146138
146181
|
var overrideAvailability = null;
|
|
146139
146182
|
function parseConfig(path5) {
|
|
146140
146183
|
if (!existsSync7(path5))
|
|
@@ -146161,10 +146204,10 @@ function hasAftAtKeys(value, keys) {
|
|
|
146161
146204
|
function getAftAvailability() {
|
|
146162
146205
|
const home = process.env.HOME || homedir3();
|
|
146163
146206
|
const opencodePaths = [
|
|
146164
|
-
|
|
146165
|
-
|
|
146207
|
+
join9(home, ".config", "opencode", "opencode.jsonc"),
|
|
146208
|
+
join9(home, ".config", "opencode", "opencode.json")
|
|
146166
146209
|
];
|
|
146167
|
-
const piPaths = [
|
|
146210
|
+
const piPaths = [join9(home, ".pi", "agent", "settings.json")];
|
|
146168
146211
|
const checkedPaths = [...opencodePaths, ...piPaths];
|
|
146169
146212
|
let opencode = false;
|
|
146170
146213
|
for (const path5 of opencodePaths) {
|
|
@@ -146551,7 +146594,7 @@ async function runKeyFilesLlm(args) {
|
|
|
146551
146594
|
});
|
|
146552
146595
|
const messagesResponse = await args.client.session.messages({
|
|
146553
146596
|
path: { id: agentSessionId },
|
|
146554
|
-
query: { directory: args.projectPath }
|
|
146597
|
+
query: { directory: args.projectPath, limit: 50 }
|
|
146555
146598
|
});
|
|
146556
146599
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
146557
146600
|
preferResponseOnMissingData: true
|
|
@@ -146588,7 +146631,7 @@ async function runKeyFilesTask(args) {
|
|
|
146588
146631
|
if (row.staleReason !== null || row.generationConfigHash !== configHash)
|
|
146589
146632
|
return false;
|
|
146590
146633
|
try {
|
|
146591
|
-
return sha256(readFileSync4(
|
|
146634
|
+
return sha256(readFileSync4(join10(projectPath, row.path))) === row.contentHash;
|
|
146592
146635
|
} catch {
|
|
146593
146636
|
return false;
|
|
146594
146637
|
}
|
|
@@ -147659,7 +147702,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
|
|
|
147659
147702
|
});
|
|
147660
147703
|
const messagesResponse = await args.client.session.messages({
|
|
147661
147704
|
path: { id: agentSessionId },
|
|
147662
|
-
query: { directory: args.sessionDirectory }
|
|
147705
|
+
query: { directory: args.sessionDirectory, limit: 50 }
|
|
147663
147706
|
});
|
|
147664
147707
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
147665
147708
|
preferResponseOnMissingData: true
|
|
@@ -147785,7 +147828,7 @@ function logWithStackHead(message, stackHead) {
|
|
|
147785
147828
|
log(message, stackHead ? { stackHead } : undefined);
|
|
147786
147829
|
}
|
|
147787
147830
|
function getOpenCodeDbPath2() {
|
|
147788
|
-
return
|
|
147831
|
+
return join11(getDataDir(), "opencode", "opencode.db");
|
|
147789
147832
|
}
|
|
147790
147833
|
function openOpenCodeDb() {
|
|
147791
147834
|
const dbPath = getOpenCodeDbPath2();
|
|
@@ -147920,8 +147963,8 @@ async function runDream(args) {
|
|
|
147920
147963
|
try {
|
|
147921
147964
|
const docsDir = args.sessionDirectory ?? args.projectIdentity;
|
|
147922
147965
|
const existingDocs = taskName === "maintain-docs" ? {
|
|
147923
|
-
architecture: existsSync8(
|
|
147924
|
-
structure: existsSync8(
|
|
147966
|
+
architecture: existsSync8(join11(docsDir, "ARCHITECTURE.md")),
|
|
147967
|
+
structure: existsSync8(join11(docsDir, "STRUCTURE.md"))
|
|
147925
147968
|
} : undefined;
|
|
147926
147969
|
const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
|
|
147927
147970
|
id: um.id,
|
|
@@ -147965,7 +148008,7 @@ async function runDream(args) {
|
|
|
147965
148008
|
}
|
|
147966
148009
|
const messagesResponse = await args.client.session.messages({
|
|
147967
148010
|
path: { id: agentSessionId },
|
|
147968
|
-
query: { directory: args.sessionDirectory ?? args.projectIdentity }
|
|
148011
|
+
query: { directory: args.sessionDirectory ?? args.projectIdentity, limit: 50 }
|
|
147969
148012
|
});
|
|
147970
148013
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
147971
148014
|
preferResponseOnMissingData: true
|
|
@@ -148288,7 +148331,7 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
|
|
|
148288
148331
|
});
|
|
148289
148332
|
const messagesResponse = await args.client.session.messages({
|
|
148290
148333
|
path: { id: agentSessionId },
|
|
148291
|
-
query: { directory: args.sessionDirectory ?? args.projectIdentity }
|
|
148334
|
+
query: { directory: args.sessionDirectory ?? args.projectIdentity, limit: 50 }
|
|
148292
148335
|
});
|
|
148293
148336
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
148294
148337
|
preferResponseOnMissingData: true
|
|
@@ -163109,7 +163152,7 @@ function getEmbeddingProviderIdentity(config2) {
|
|
|
163109
163152
|
// ../plugin/src/features/magic-context/memory/embedding-local.ts
|
|
163110
163153
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
163111
163154
|
import { open, stat, unlink, writeFile } from "node:fs/promises";
|
|
163112
|
-
import { dirname as dirname4, join as
|
|
163155
|
+
import { dirname as dirname4, join as join12 } from "node:path";
|
|
163113
163156
|
import { pathToFileURL } from "node:url";
|
|
163114
163157
|
init_data_path();
|
|
163115
163158
|
init_logger();
|
|
@@ -163176,7 +163219,7 @@ async function injectWasmOrtForElectron() {
|
|
|
163176
163219
|
const { createRequire: createRequireFn } = await import("node:module");
|
|
163177
163220
|
const requireFn = createRequireFn(import.meta.url);
|
|
163178
163221
|
const pkgPath = requireFn.resolve("onnxruntime-web/package.json");
|
|
163179
|
-
const distDir =
|
|
163222
|
+
const distDir = join12(dirname4(pkgPath), "dist");
|
|
163180
163223
|
const wasmPathsPrefix = `${pathToFileURL(distDir).href}/`;
|
|
163181
163224
|
if (ortWeb.env?.wasm) {
|
|
163182
163225
|
ortWeb.env.wasm.wasmPaths = wasmPathsPrefix;
|
|
@@ -163287,7 +163330,7 @@ class LocalEmbeddingProvider {
|
|
|
163287
163330
|
if (LogLevel && "ERROR" in LogLevel) {
|
|
163288
163331
|
env.logLevel = LogLevel.ERROR;
|
|
163289
163332
|
}
|
|
163290
|
-
const modelCacheDir =
|
|
163333
|
+
const modelCacheDir = join12(getMagicContextStorageDir(), "models");
|
|
163291
163334
|
try {
|
|
163292
163335
|
mkdirSync4(modelCacheDir, { recursive: true });
|
|
163293
163336
|
env.cacheDir = modelCacheDir;
|
|
@@ -163295,7 +163338,7 @@ class LocalEmbeddingProvider {
|
|
|
163295
163338
|
log("[magic-context] could not create model cache dir, using library default");
|
|
163296
163339
|
}
|
|
163297
163340
|
const createPipeline = transformersModule.pipeline;
|
|
163298
|
-
const lockPath =
|
|
163341
|
+
const lockPath = join12(modelCacheDir, ".load.lock");
|
|
163299
163342
|
const releaseLock = await acquireModelLoadLock(lockPath);
|
|
163300
163343
|
const stopHeartbeat = startLockHeartbeat(lockPath);
|
|
163301
163344
|
try {
|
|
@@ -164649,7 +164692,7 @@ function handleUntrustedLoad(db, projectIdentity, directory, detailed) {
|
|
|
164649
164692
|
// src/config/index.ts
|
|
164650
164693
|
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
|
|
164651
164694
|
import { homedir as homedir5 } from "node:os";
|
|
164652
|
-
import { join as
|
|
164695
|
+
import { join as join13 } from "node:path";
|
|
164653
164696
|
|
|
164654
164697
|
// ../plugin/src/config/variable.ts
|
|
164655
164698
|
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
|
|
@@ -164717,12 +164760,12 @@ function substituteConfigVariables(input) {
|
|
|
164717
164760
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
164718
164761
|
var CONFIG_FILE_NAME = "magic-context";
|
|
164719
164762
|
function getProjectConfigPaths(cwd) {
|
|
164720
|
-
const basePath =
|
|
164763
|
+
const basePath = join13(cwd, ".pi", CONFIG_FILE_NAME);
|
|
164721
164764
|
return [`${basePath}.jsonc`, `${basePath}.json`];
|
|
164722
164765
|
}
|
|
164723
164766
|
function getUserConfigPaths() {
|
|
164724
164767
|
const home = process.env.HOME ?? homedir5();
|
|
164725
|
-
const basePath =
|
|
164768
|
+
const basePath = join13(home, ".pi", "agent", CONFIG_FILE_NAME);
|
|
164726
164769
|
return [`${basePath}.jsonc`, `${basePath}.json`];
|
|
164727
164770
|
}
|
|
164728
164771
|
function resolveFirstExisting(paths) {
|
|
@@ -164952,7 +164995,6 @@ function loadPiConfigDetailed(opts = {}) {
|
|
|
164952
164995
|
// src/embedding-bootstrap.ts
|
|
164953
164996
|
async function ensureProjectRegisteredFromPiDirectory(directory, db) {
|
|
164954
164997
|
const projectIdentity = resolveProjectIdentity(directory);
|
|
164955
|
-
invalidateProject(projectIdentity);
|
|
164956
164998
|
const detailed = loadPiConfigDetailed({ cwd: directory });
|
|
164957
164999
|
if (isConfigLoadUntrusted(detailed)) {
|
|
164958
165000
|
handleUntrustedLoad(db, projectIdentity, directory, detailed);
|
|
@@ -164971,6 +165013,7 @@ var sessionsById = new Map;
|
|
|
164971
165013
|
var inFlightDreams = new Set;
|
|
164972
165014
|
var sessionCounter = 0;
|
|
164973
165015
|
var piSubagentRunnerFactory = () => new PiSubagentRunner;
|
|
165016
|
+
var startDreamScheduleTimerFn = startDreamScheduleTimer;
|
|
164974
165017
|
function registerPiDreamerProject(opts) {
|
|
164975
165018
|
if (!opts.config.enabled) {
|
|
164976
165019
|
return;
|
|
@@ -164991,7 +165034,8 @@ function registerPiDreamerProject(opts) {
|
|
|
164991
165034
|
min_reads: opts.config.pin_key_files.min_reads
|
|
164992
165035
|
} : undefined;
|
|
164993
165036
|
let cleanup;
|
|
164994
|
-
|
|
165037
|
+
let cancelled = false;
|
|
165038
|
+
startDreamScheduleTimerFn({
|
|
164995
165039
|
directory: opts.projectDir,
|
|
164996
165040
|
projectIdentity: opts.projectIdentity,
|
|
164997
165041
|
client,
|
|
@@ -165001,6 +165045,10 @@ function registerPiDreamerProject(opts) {
|
|
|
165001
165045
|
gitCommitIndexing: opts.gitCommitIndexing,
|
|
165002
165046
|
ensureRegistered: ensureProjectRegisteredFromPiDirectory
|
|
165003
165047
|
}).then((timerCleanup) => {
|
|
165048
|
+
if (cancelled) {
|
|
165049
|
+
timerCleanup?.();
|
|
165050
|
+
return;
|
|
165051
|
+
}
|
|
165004
165052
|
cleanup = timerCleanup;
|
|
165005
165053
|
});
|
|
165006
165054
|
const runOnce = async () => processDreamQueue({
|
|
@@ -165014,7 +165062,10 @@ function registerPiDreamerProject(opts) {
|
|
|
165014
165062
|
projectIdentity: opts.projectIdentity
|
|
165015
165063
|
});
|
|
165016
165064
|
registeredProjects2.set(opts.projectIdentity, {
|
|
165017
|
-
cleanup: () =>
|
|
165065
|
+
cleanup: () => {
|
|
165066
|
+
cancelled = true;
|
|
165067
|
+
cleanup?.();
|
|
165068
|
+
},
|
|
165018
165069
|
runOnce
|
|
165019
165070
|
});
|
|
165020
165071
|
}
|
|
@@ -165086,6 +165137,7 @@ function createPiDreamerClient(opts) {
|
|
|
165086
165137
|
{ type: "text", text: result.assistantText }
|
|
165087
165138
|
])
|
|
165088
165139
|
];
|
|
165140
|
+
opts.onAdjunctsRefreshNeeded?.(opts.projectIdentity);
|
|
165089
165141
|
} finally {
|
|
165090
165142
|
inFlightDreams.delete(runPromise);
|
|
165091
165143
|
}
|
|
@@ -165189,21 +165241,37 @@ function sendCtxStatusMessage(pi, content, details) {
|
|
|
165189
165241
|
function registerCtxDreamCommand(pi, deps) {
|
|
165190
165242
|
pi.registerCommand("ctx-dream", {
|
|
165191
165243
|
description: "Run a Magic Context dreamer cycle for this project now",
|
|
165192
|
-
handler: async () => {
|
|
165193
|
-
const
|
|
165244
|
+
handler: async (_args, ctx) => {
|
|
165245
|
+
const project = deps.resolveProject?.(ctx) ?? {
|
|
165246
|
+
projectDir: deps.projectDir,
|
|
165247
|
+
projectIdentity: deps.projectIdentity
|
|
165248
|
+
};
|
|
165249
|
+
deps.onProjectSeen?.(project.projectIdentity);
|
|
165250
|
+
if (deps.dreamerEnabled === false) {
|
|
165251
|
+
sendCtxStatusMessage(pi, {
|
|
165252
|
+
title: "/ctx-dream",
|
|
165253
|
+
text: "## /ctx-dream\n\nDreamer is not configured for this project (`dreamer.enabled=false`).",
|
|
165254
|
+
level: "info"
|
|
165255
|
+
}, {
|
|
165256
|
+
projectDir: project.projectDir,
|
|
165257
|
+
projectIdentity: project.projectIdentity
|
|
165258
|
+
});
|
|
165259
|
+
return;
|
|
165260
|
+
}
|
|
165261
|
+
const enqueued = enqueueDream(deps.db, project.projectIdentity, "manual", true);
|
|
165194
165262
|
if (!enqueued) {
|
|
165195
165263
|
sendCtxStatusMessage(pi, {
|
|
165196
165264
|
title: "/ctx-dream",
|
|
165197
165265
|
text: [
|
|
165198
165266
|
"## /ctx-dream",
|
|
165199
165267
|
"",
|
|
165200
|
-
`Dream already queued or running for ${
|
|
165268
|
+
`Dream already queued or running for ${project.projectIdentity}.`
|
|
165201
165269
|
].join(`
|
|
165202
165270
|
`),
|
|
165203
165271
|
level: "info"
|
|
165204
165272
|
}, {
|
|
165205
|
-
projectDir:
|
|
165206
|
-
projectIdentity:
|
|
165273
|
+
projectDir: project.projectDir,
|
|
165274
|
+
projectIdentity: project.projectIdentity,
|
|
165207
165275
|
entry: null
|
|
165208
165276
|
});
|
|
165209
165277
|
return;
|
|
@@ -165213,18 +165281,18 @@ function registerCtxDreamCommand(pi, deps) {
|
|
|
165213
165281
|
text: [
|
|
165214
165282
|
"## /ctx-dream",
|
|
165215
165283
|
"",
|
|
165216
|
-
`Starting dream run #${enqueued.id} for ${
|
|
165217
|
-
`Project directory: ${
|
|
165284
|
+
`Starting dream run #${enqueued.id} for ${project.projectIdentity}…`,
|
|
165285
|
+
`Project directory: ${project.projectDir}`
|
|
165218
165286
|
].join(`
|
|
165219
165287
|
`),
|
|
165220
165288
|
level: "info"
|
|
165221
165289
|
}, {
|
|
165222
|
-
projectDir:
|
|
165223
|
-
projectIdentity:
|
|
165290
|
+
projectDir: project.projectDir,
|
|
165291
|
+
projectIdentity: project.projectIdentity,
|
|
165224
165292
|
entry: enqueued
|
|
165225
165293
|
});
|
|
165226
165294
|
try {
|
|
165227
|
-
const result = await runPiDreamForProject(
|
|
165295
|
+
const result = await runPiDreamForProject(project.projectIdentity);
|
|
165228
165296
|
let summary;
|
|
165229
165297
|
if (!result) {
|
|
165230
165298
|
summary = "Dream queued, but another worker is already processing the queue.";
|
|
@@ -165250,13 +165318,13 @@ function registerCtxDreamCommand(pi, deps) {
|
|
|
165250
165318
|
`),
|
|
165251
165319
|
level: result ? "success" : "info"
|
|
165252
165320
|
}, {
|
|
165253
|
-
projectDir:
|
|
165254
|
-
projectIdentity:
|
|
165321
|
+
projectDir: project.projectDir,
|
|
165322
|
+
projectIdentity: project.projectIdentity,
|
|
165255
165323
|
entry: enqueued
|
|
165256
165324
|
});
|
|
165257
165325
|
} catch (error51) {
|
|
165258
165326
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
165259
|
-
sessionLog(
|
|
165327
|
+
sessionLog(project.projectIdentity, `/ctx-dream failed to drain queue: ${message}`);
|
|
165260
165328
|
sendCtxStatusMessage(pi, {
|
|
165261
165329
|
title: "/ctx-dream",
|
|
165262
165330
|
text: [
|
|
@@ -165268,8 +165336,8 @@ function registerCtxDreamCommand(pi, deps) {
|
|
|
165268
165336
|
`),
|
|
165269
165337
|
level: "error"
|
|
165270
165338
|
}, {
|
|
165271
|
-
projectDir:
|
|
165272
|
-
projectIdentity:
|
|
165339
|
+
projectDir: project.projectDir,
|
|
165340
|
+
projectIdentity: project.projectIdentity,
|
|
165273
165341
|
entry: enqueued
|
|
165274
165342
|
});
|
|
165275
165343
|
}
|
|
@@ -167526,8 +167594,31 @@ function collectUserPromptParts(message) {
|
|
|
167526
167594
|
function hasStackedAugmentation(rawText) {
|
|
167527
167595
|
return rawText.includes("<sidekick-augmentation>") || rawText.includes("<ctx-search-hint>") || rawText.includes("<ctx-search-auto>");
|
|
167528
167596
|
}
|
|
167597
|
+
function stripNestedSystemReminders(text) {
|
|
167598
|
+
const OPEN = "<system-reminder>";
|
|
167599
|
+
const CLOSE = "</system-reminder>";
|
|
167600
|
+
let result = "";
|
|
167601
|
+
let depth = 0;
|
|
167602
|
+
let i = 0;
|
|
167603
|
+
while (i < text.length) {
|
|
167604
|
+
if (text.startsWith(OPEN, i)) {
|
|
167605
|
+
depth += 1;
|
|
167606
|
+
i += OPEN.length;
|
|
167607
|
+
} else if (text.startsWith(CLOSE, i)) {
|
|
167608
|
+
if (depth > 0)
|
|
167609
|
+
depth -= 1;
|
|
167610
|
+
i += CLOSE.length;
|
|
167611
|
+
} else if (depth === 0) {
|
|
167612
|
+
result += text[i];
|
|
167613
|
+
i += 1;
|
|
167614
|
+
} else {
|
|
167615
|
+
i += 1;
|
|
167616
|
+
}
|
|
167617
|
+
}
|
|
167618
|
+
return result;
|
|
167619
|
+
}
|
|
167529
167620
|
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(
|
|
167621
|
+
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
167622
|
`).replace(/\n{3,}/g, `
|
|
167532
167623
|
|
|
167533
167624
|
`).trim();
|
|
@@ -167769,7 +167860,8 @@ var DEDUP_SAFE_TOOLS = new Set([
|
|
|
167769
167860
|
]);
|
|
167770
167861
|
function buildPiToolFingerprints(messages) {
|
|
167771
167862
|
const fingerprints2 = new Map;
|
|
167772
|
-
for (
|
|
167863
|
+
for (let i = 0;i < messages.length; i++) {
|
|
167864
|
+
const message = messages[i];
|
|
167773
167865
|
if (!message || typeof message !== "object")
|
|
167774
167866
|
continue;
|
|
167775
167867
|
const msg = message;
|
|
@@ -167777,6 +167869,7 @@ function buildPiToolFingerprints(messages) {
|
|
|
167777
167869
|
continue;
|
|
167778
167870
|
if (!Array.isArray(msg.content))
|
|
167779
167871
|
continue;
|
|
167872
|
+
const ownerMsgId = typeof msg.timestamp === "number" ? `pi-msg-${i}-${msg.timestamp}-assistant` : `pi-msg-${i}-assistant`;
|
|
167780
167873
|
for (const part of msg.content) {
|
|
167781
167874
|
if (!part || typeof part !== "object")
|
|
167782
167875
|
continue;
|
|
@@ -167799,7 +167892,9 @@ function buildPiToolFingerprints(messages) {
|
|
|
167799
167892
|
} catch {
|
|
167800
167893
|
continue;
|
|
167801
167894
|
}
|
|
167802
|
-
|
|
167895
|
+
const fingerprint = `${ownerMsgId}:${p.name}:${serialized}`;
|
|
167896
|
+
const compositeKey = `${ownerMsgId}\x00${p.id}`;
|
|
167897
|
+
fingerprints2.set(compositeKey, fingerprint);
|
|
167803
167898
|
}
|
|
167804
167899
|
}
|
|
167805
167900
|
return fingerprints2;
|
|
@@ -167917,15 +168012,16 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
|
|
|
167917
168012
|
})();
|
|
167918
168013
|
const toolFingerprints = buildPiToolFingerprints(piMessages);
|
|
167919
168014
|
if (toolFingerprints.size > 0) {
|
|
167920
|
-
const
|
|
168015
|
+
const tagsByCompositeKey = new Map;
|
|
167921
168016
|
for (const tag of tags) {
|
|
167922
168017
|
if (tag.type === "tool" && tag.status === "active" && tag.messageId) {
|
|
167923
|
-
|
|
168018
|
+
const key = tag.toolOwnerMessageId ? `${tag.toolOwnerMessageId}\x00${tag.messageId}` : tag.messageId;
|
|
168019
|
+
tagsByCompositeKey.set(key, tag);
|
|
167924
168020
|
}
|
|
167925
168021
|
}
|
|
167926
168022
|
const fingerprintGroups = new Map;
|
|
167927
|
-
for (const [
|
|
167928
|
-
const tag =
|
|
168023
|
+
for (const [compositeKey, fingerprint] of toolFingerprints) {
|
|
168024
|
+
const tag = tagsByCompositeKey.get(compositeKey);
|
|
167929
168025
|
if (!tag || tag.tagNumber > protectedCutoff)
|
|
167930
168026
|
continue;
|
|
167931
168027
|
const group = fingerprintGroups.get(fingerprint) ?? [];
|
|
@@ -169169,7 +169265,7 @@ function getReducedRecompTokenBudget(currentBudget) {
|
|
|
169169
169265
|
// ../plugin/src/hooks/magic-context/historian-state-file.ts
|
|
169170
169266
|
init_data_path();
|
|
169171
169267
|
import { mkdirSync as mkdirSync5, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
169172
|
-
import { join as
|
|
169268
|
+
import { join as join14 } from "node:path";
|
|
169173
169269
|
var HISTORIAN_STATE_INLINE_THRESHOLD = 30000;
|
|
169174
169270
|
function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
|
|
169175
169271
|
if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
|
|
@@ -169177,7 +169273,7 @@ function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
|
|
|
169177
169273
|
try {
|
|
169178
169274
|
const dir = getProjectMagicContextHistorianDir(directory);
|
|
169179
169275
|
mkdirSync5(dir, { recursive: true });
|
|
169180
|
-
const path5 =
|
|
169276
|
+
const path5 = join14(dir, `state-${sessionId}-${Date.now()}.xml`);
|
|
169181
169277
|
writeFileSync2(path5, existingState, "utf8");
|
|
169182
169278
|
return path5;
|
|
169183
169279
|
} catch {
|
|
@@ -169877,8 +169973,18 @@ function injectSyntheticTodowriteForPi(args) {
|
|
|
169877
169973
|
}
|
|
169878
169974
|
|
|
169879
169975
|
// src/reasoning-replay-pi.ts
|
|
169880
|
-
var
|
|
169976
|
+
var INLINE_THINKING_PATTERNS = [
|
|
169977
|
+
/<thinking>[\s\S]*?<\/thinking>\s*/gi,
|
|
169978
|
+
/<think>[\s\S]*?<\/think>\s*/gi
|
|
169979
|
+
];
|
|
169881
169980
|
var CLEARED = "[cleared]";
|
|
169981
|
+
function stripInlineThinkingMarkup(text) {
|
|
169982
|
+
let cleaned = text;
|
|
169983
|
+
for (const pattern of INLINE_THINKING_PATTERNS) {
|
|
169984
|
+
cleaned = cleaned.replace(pattern, "");
|
|
169985
|
+
}
|
|
169986
|
+
return cleaned;
|
|
169987
|
+
}
|
|
169882
169988
|
function buildMessageIdToMaxTag(targets) {
|
|
169883
169989
|
const out = new Map;
|
|
169884
169990
|
for (const [tagNumber, target] of targets) {
|
|
@@ -169931,6 +170037,51 @@ function clearOldReasoningPi(args) {
|
|
|
169931
170037
|
}
|
|
169932
170038
|
return { cleared, newWatermark };
|
|
169933
170039
|
}
|
|
170040
|
+
function stripInlineThinkingPi(args) {
|
|
170041
|
+
const { messages, messageIdToMaxTag, clearReasoningAge, piMessageStableId } = args;
|
|
170042
|
+
let maxTag = 0;
|
|
170043
|
+
for (const t of messageIdToMaxTag.values())
|
|
170044
|
+
if (t > maxTag)
|
|
170045
|
+
maxTag = t;
|
|
170046
|
+
if (maxTag === 0)
|
|
170047
|
+
return { stripped: 0, newWatermark: 0 };
|
|
170048
|
+
const ageCutoff = maxTag - clearReasoningAge;
|
|
170049
|
+
if (ageCutoff <= 0)
|
|
170050
|
+
return { stripped: 0, newWatermark: 0 };
|
|
170051
|
+
let stripped = 0;
|
|
170052
|
+
let newWatermark = 0;
|
|
170053
|
+
for (let i = 0;i < messages.length; i++) {
|
|
170054
|
+
const raw = messages[i];
|
|
170055
|
+
if (!raw || typeof raw !== "object")
|
|
170056
|
+
continue;
|
|
170057
|
+
const msg = raw;
|
|
170058
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
170059
|
+
continue;
|
|
170060
|
+
const id = piMessageStableId(raw, i);
|
|
170061
|
+
if (!id)
|
|
170062
|
+
continue;
|
|
170063
|
+
const msgTag = messageIdToMaxTag.get(id) ?? 0;
|
|
170064
|
+
if (msgTag === 0 || msgTag > ageCutoff)
|
|
170065
|
+
continue;
|
|
170066
|
+
let strippedThisMessage = false;
|
|
170067
|
+
for (const part of msg.content) {
|
|
170068
|
+
if (part && typeof part === "object" && part.type === "text") {
|
|
170069
|
+
const tp = part;
|
|
170070
|
+
if (typeof tp.text !== "string")
|
|
170071
|
+
continue;
|
|
170072
|
+
const cleaned = stripInlineThinkingMarkup(tp.text);
|
|
170073
|
+
if (cleaned !== tp.text) {
|
|
170074
|
+
tp.text = cleaned;
|
|
170075
|
+
stripped++;
|
|
170076
|
+
strippedThisMessage = true;
|
|
170077
|
+
}
|
|
170078
|
+
}
|
|
170079
|
+
}
|
|
170080
|
+
if (strippedThisMessage && msgTag > newWatermark)
|
|
170081
|
+
newWatermark = msgTag;
|
|
170082
|
+
}
|
|
170083
|
+
return { stripped, newWatermark };
|
|
170084
|
+
}
|
|
169934
170085
|
function replayClearedReasoningPi(args) {
|
|
169935
170086
|
const { db, sessionId, messages, messageIdToMaxTag, piMessageStableId } = args;
|
|
169936
170087
|
const meta3 = getOrCreateSessionMeta(db, sessionId);
|
|
@@ -169988,7 +170139,7 @@ function replayStrippedInlineThinkingPi(args) {
|
|
|
169988
170139
|
const tp = part;
|
|
169989
170140
|
if (typeof tp.text !== "string")
|
|
169990
170141
|
continue;
|
|
169991
|
-
const cleaned = tp.text
|
|
170142
|
+
const cleaned = stripInlineThinkingMarkup(tp.text);
|
|
169992
170143
|
if (cleaned !== tp.text) {
|
|
169993
170144
|
tp.text = cleaned;
|
|
169994
170145
|
stripped++;
|
|
@@ -170079,6 +170230,408 @@ function stripPiDroppedPlaceholderMessages(args) {
|
|
|
170079
170230
|
return { removed, discovered };
|
|
170080
170231
|
}
|
|
170081
170232
|
|
|
170233
|
+
// src/system-prompt.ts
|
|
170234
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
170235
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
170236
|
+
import { join as join16 } from "node:path";
|
|
170237
|
+
|
|
170238
|
+
// ../plugin/src/agents/magic-context-prompt.ts
|
|
170239
|
+
function getToolHistoryGuidance(dropToolStructure) {
|
|
170240
|
+
if (dropToolStructure) {
|
|
170241
|
+
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.`;
|
|
170242
|
+
}
|
|
170243
|
+
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.`;
|
|
170244
|
+
}
|
|
170245
|
+
var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
|
|
170246
|
+
Use \`ctx_reduce\` to manage context size. It supports one operation:
|
|
170247
|
+
- \`drop\`: Remove entirely (best for tool outputs you already acted on).
|
|
170248
|
+
Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
|
|
170249
|
+
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).
|
|
170250
|
+
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.
|
|
170251
|
+
**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:
|
|
170252
|
+
- Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
|
|
170253
|
+
- Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
|
|
170254
|
+
- Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
|
|
170255
|
+
Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
|
|
170256
|
+
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.
|
|
170257
|
+
**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:
|
|
170258
|
+
- Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
|
|
170259
|
+
- Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
|
|
170260
|
+
- Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
|
|
170261
|
+
- Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
|
|
170262
|
+
- Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
|
|
170263
|
+
\`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.
|
|
170264
|
+
${getToolHistoryGuidance(dropToolStructure)}
|
|
170265
|
+
NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
|
|
170266
|
+
NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
|
|
170267
|
+
NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
|
|
170268
|
+
Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
|
|
170269
|
+
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).
|
|
170270
|
+
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.
|
|
170271
|
+
**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:
|
|
170272
|
+
- Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
|
|
170273
|
+
- Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
|
|
170274
|
+
- Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
|
|
170275
|
+
Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
|
|
170276
|
+
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.
|
|
170277
|
+
**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:
|
|
170278
|
+
- Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
|
|
170279
|
+
- Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
|
|
170280
|
+
- Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
|
|
170281
|
+
- Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
|
|
170282
|
+
- Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
|
|
170283
|
+
\`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.
|
|
170284
|
+
${getToolHistoryGuidance(dropToolStructure)}`;
|
|
170285
|
+
var GENERIC_SECTION = `
|
|
170286
|
+
### Reduction Triggers
|
|
170287
|
+
- After reading files or search results you already acted on — drop raw outputs.
|
|
170288
|
+
- After completing a logical step — drop intermediate outputs from that step.
|
|
170289
|
+
- Between major context switches — when moving to a new task area.
|
|
170290
|
+
|
|
170291
|
+
### What to Drop
|
|
170292
|
+
- Large file reads, grep results, and tool outputs you already used.
|
|
170293
|
+
- Large build/test output after you analyzed and acted on it.
|
|
170294
|
+
- Old diagnostic or exploration results that are no longer relevant.
|
|
170295
|
+
|
|
170296
|
+
### What to Keep
|
|
170297
|
+
- ALL user messages and assistant conversation text — these are cheap and compartmentalized automatically.
|
|
170298
|
+
- Your current task requirements and constraints.
|
|
170299
|
+
- Recent errors and unresolved decisions.
|
|
170300
|
+
- Active work context and files being edited.`;
|
|
170301
|
+
var TEMPORAL_AWARENESS_GUIDANCE = `
|
|
170302
|
+
**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.`;
|
|
170303
|
+
var CAVEMAN_COMPRESSION_WARNING = `
|
|
170304
|
+
**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.`;
|
|
170305
|
+
function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, dropToolStructure = true, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false) {
|
|
170306
|
+
const smartNoteGuidance = dreamerEnabled ? `
|
|
170307
|
+
When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
|
|
170308
|
+
The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
|
|
170309
|
+
Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
|
|
170310
|
+
const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
|
|
170311
|
+
const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
|
|
170312
|
+
if (!ctxReduceEnabled) {
|
|
170313
|
+
return `## Magic Context
|
|
170314
|
+
|
|
170315
|
+
${BASE_INTRO_NO_REDUCE(dropToolStructure)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}`;
|
|
170316
|
+
}
|
|
170317
|
+
return `## Magic Context
|
|
170318
|
+
|
|
170319
|
+
${BASE_INTRO(protectedTags, dropToolStructure)}${smartNoteGuidance}${temporalGuidance}
|
|
170320
|
+
${GENERIC_SECTION}
|
|
170321
|
+
|
|
170322
|
+
Prefer many small targeted operations over one large blanket operation. Compress early and often — don't wait for warnings.`;
|
|
170323
|
+
}
|
|
170324
|
+
|
|
170325
|
+
// ../plugin/src/hooks/magic-context/key-files-block.ts
|
|
170326
|
+
import { readFileSync as readFileSync7, realpathSync as realpathSync3 } from "node:fs";
|
|
170327
|
+
import { join as join15, sep as sep2 } from "node:path";
|
|
170328
|
+
init_logger();
|
|
170329
|
+
var cachedKeyFilesBySession = new Map;
|
|
170330
|
+
var staleUpdates = new Map;
|
|
170331
|
+
function staleKey(update) {
|
|
170332
|
+
return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
|
|
170333
|
+
}
|
|
170334
|
+
function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
|
|
170335
|
+
const update = { projectPath, path: path5, generatedAtWitness, staleReason };
|
|
170336
|
+
staleUpdates.set(staleKey(update), update);
|
|
170337
|
+
}
|
|
170338
|
+
function flushStaleUpdates(db) {
|
|
170339
|
+
if (staleUpdates.size === 0)
|
|
170340
|
+
return 0;
|
|
170341
|
+
const updates = [...staleUpdates.values()];
|
|
170342
|
+
staleUpdates.clear();
|
|
170343
|
+
const stmt = db.prepare(`UPDATE project_key_files
|
|
170344
|
+
SET stale_reason = ?1
|
|
170345
|
+
WHERE project_path = ?2
|
|
170346
|
+
AND path = ?3
|
|
170347
|
+
AND generated_at = ?4
|
|
170348
|
+
AND (stale_reason IS NULL OR stale_reason != ?1)`);
|
|
170349
|
+
let changed = 0;
|
|
170350
|
+
for (const update of updates) {
|
|
170351
|
+
try {
|
|
170352
|
+
changed += stmt.run(update.staleReason, update.projectPath, update.path, update.generatedAtWitness).changes;
|
|
170353
|
+
} catch (error51) {
|
|
170354
|
+
log("[key-files] flushStaleUpdates failed:", error51);
|
|
170355
|
+
}
|
|
170356
|
+
}
|
|
170357
|
+
return changed;
|
|
170358
|
+
}
|
|
170359
|
+
function clearKeyFilesCacheForSession(sessionId) {
|
|
170360
|
+
cachedKeyFilesBySession.delete(sessionId);
|
|
170361
|
+
}
|
|
170362
|
+
function isUnderProject(projectPath, absPath) {
|
|
170363
|
+
const root = realpathSync3(projectPath);
|
|
170364
|
+
return absPath.startsWith(root + sep2) || absPath === root;
|
|
170365
|
+
}
|
|
170366
|
+
function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBudget: 1e4 }) {
|
|
170367
|
+
if (!config2.enabled)
|
|
170368
|
+
return null;
|
|
170369
|
+
if (!isAftAvailable())
|
|
170370
|
+
return null;
|
|
170371
|
+
const rows = readCurrentKeyFiles(db, projectPath);
|
|
170372
|
+
if (rows.length === 0)
|
|
170373
|
+
return null;
|
|
170374
|
+
for (const row of rows) {
|
|
170375
|
+
if (row.staleReason !== null)
|
|
170376
|
+
continue;
|
|
170377
|
+
let nextStale = null;
|
|
170378
|
+
let observed = false;
|
|
170379
|
+
try {
|
|
170380
|
+
const absPath = join15(projectPath, row.path);
|
|
170381
|
+
const real = realpathSync3(absPath);
|
|
170382
|
+
if (!isUnderProject(projectPath, real)) {
|
|
170383
|
+
nextStale = "missing";
|
|
170384
|
+
observed = true;
|
|
170385
|
+
} else {
|
|
170386
|
+
const diskHash = sha256(readFileSync7(real));
|
|
170387
|
+
if (diskHash !== row.contentHash)
|
|
170388
|
+
nextStale = "content_drift";
|
|
170389
|
+
observed = true;
|
|
170390
|
+
}
|
|
170391
|
+
} catch (error51) {
|
|
170392
|
+
const code = error51?.code;
|
|
170393
|
+
if (code === "ENOENT" || code === "ELOOP") {
|
|
170394
|
+
nextStale = "missing";
|
|
170395
|
+
observed = true;
|
|
170396
|
+
} else {
|
|
170397
|
+
log(`[key-files] freshness check transient failure: ${row.path}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
170398
|
+
}
|
|
170399
|
+
}
|
|
170400
|
+
if (observed && nextStale !== null) {
|
|
170401
|
+
queueStaleUpdate(row.projectPath, row.path, row.generatedAt, nextStale);
|
|
170402
|
+
}
|
|
170403
|
+
}
|
|
170404
|
+
const blocks = [];
|
|
170405
|
+
let used = 0;
|
|
170406
|
+
for (const row of rows) {
|
|
170407
|
+
if (used + row.localTokenEstimate > config2.tokenBudget)
|
|
170408
|
+
break;
|
|
170409
|
+
blocks.push(` <key-file path="${escapeXmlAttr(row.path)}">
|
|
170410
|
+
${escapeXmlContent(row.content)}
|
|
170411
|
+
</key-file>`);
|
|
170412
|
+
used += row.localTokenEstimate;
|
|
170413
|
+
}
|
|
170414
|
+
if (blocks.length === 0)
|
|
170415
|
+
return null;
|
|
170416
|
+
const rendered = `<key-files>
|
|
170417
|
+
${blocks.join(`
|
|
170418
|
+
`)}
|
|
170419
|
+
</key-files>`;
|
|
170420
|
+
flushStaleUpdates(db);
|
|
170421
|
+
return rendered;
|
|
170422
|
+
}
|
|
170423
|
+
function readVersionedKeyFiles(args) {
|
|
170424
|
+
const config2 = args.config ?? { enabled: true, tokenBudget: 1e4 };
|
|
170425
|
+
if (args.sessionMeta.isSubagent)
|
|
170426
|
+
return null;
|
|
170427
|
+
if (!config2.enabled)
|
|
170428
|
+
return null;
|
|
170429
|
+
if (!isAftAvailable())
|
|
170430
|
+
return null;
|
|
170431
|
+
const projectPath = resolveProjectPath(args.directory ?? args.sessionMeta.sessionId);
|
|
170432
|
+
if (!projectPath)
|
|
170433
|
+
return null;
|
|
170434
|
+
const currentVersion = getKeyFilesVersion(args.db, projectPath);
|
|
170435
|
+
if (args.sessionId) {
|
|
170436
|
+
const cached2 = cachedKeyFilesBySession.get(args.sessionId);
|
|
170437
|
+
if (cached2 && !args.isCacheBusting && cached2.version === currentVersion) {
|
|
170438
|
+
return cached2.value;
|
|
170439
|
+
}
|
|
170440
|
+
}
|
|
170441
|
+
const value = buildKeyFilesBlock(args.db, projectPath, config2);
|
|
170442
|
+
if (args.sessionId) {
|
|
170443
|
+
cachedKeyFilesBySession.set(args.sessionId, { value, version: currentVersion });
|
|
170444
|
+
if (value)
|
|
170445
|
+
sessionLog(args.sessionId, `loaded key-files block (v${currentVersion}, ${value.length} chars)`);
|
|
170446
|
+
}
|
|
170447
|
+
return value;
|
|
170448
|
+
}
|
|
170449
|
+
|
|
170450
|
+
// src/system-prompt.ts
|
|
170451
|
+
init_logger();
|
|
170452
|
+
var PROJECT_DOCS_MARKER = "<project-docs>";
|
|
170453
|
+
var USER_PROFILE_MARKER = "<user-profile>";
|
|
170454
|
+
var KEY_FILES_MARKER = "<key-files>";
|
|
170455
|
+
var MAGIC_CONTEXT_MARKER = "## Magic Context";
|
|
170456
|
+
var stickyDateBySession = new Map;
|
|
170457
|
+
var cachedDocsBySession = new Map;
|
|
170458
|
+
var cachedUserProfileBySession = new Map;
|
|
170459
|
+
var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
|
|
170460
|
+
function readProjectDocs(directory) {
|
|
170461
|
+
const sections = [];
|
|
170462
|
+
for (const filename of DOC_FILES) {
|
|
170463
|
+
const filePath = join16(directory, filename);
|
|
170464
|
+
try {
|
|
170465
|
+
if (existsSync11(filePath)) {
|
|
170466
|
+
const content = readFileSync8(filePath, "utf-8").trim();
|
|
170467
|
+
if (content.length > 0) {
|
|
170468
|
+
sections.push(`<${filename}>
|
|
170469
|
+
${escapeXmlContent(content)}
|
|
170470
|
+
</${filename}>`);
|
|
170471
|
+
}
|
|
170472
|
+
}
|
|
170473
|
+
} catch (error51) {
|
|
170474
|
+
log(`[magic-context-pi] failed to read ${filename}:`, error51);
|
|
170475
|
+
}
|
|
170476
|
+
}
|
|
170477
|
+
if (sections.length === 0)
|
|
170478
|
+
return null;
|
|
170479
|
+
return `${PROJECT_DOCS_MARKER}
|
|
170480
|
+
${sections.join(`
|
|
170481
|
+
|
|
170482
|
+
`)}
|
|
170483
|
+
</project-docs>`;
|
|
170484
|
+
}
|
|
170485
|
+
function buildUserProfileBlock(db) {
|
|
170486
|
+
const memories = getActiveUserMemories(db);
|
|
170487
|
+
if (memories.length === 0)
|
|
170488
|
+
return null;
|
|
170489
|
+
const items = memories.map((m) => `- ${escapeXmlContent(m.content)}`).join(`
|
|
170490
|
+
`);
|
|
170491
|
+
return `${USER_PROFILE_MARKER}
|
|
170492
|
+
${items}
|
|
170493
|
+
</user-profile>`;
|
|
170494
|
+
}
|
|
170495
|
+
function buildMagicContextBlock(opts) {
|
|
170496
|
+
const sections = [];
|
|
170497
|
+
const sessionId = opts.sessionId;
|
|
170498
|
+
const isCacheBusting = opts.isCacheBusting ?? false;
|
|
170499
|
+
const existing = opts.existingSystemPrompt ?? "";
|
|
170500
|
+
if (opts.injectDocs && !existing.includes(PROJECT_DOCS_MARKER)) {
|
|
170501
|
+
const docsBlock = readCachedAdjunct({
|
|
170502
|
+
cache: cachedDocsBySession,
|
|
170503
|
+
sessionId,
|
|
170504
|
+
isCacheBusting,
|
|
170505
|
+
compute: () => readProjectDocs(opts.cwd),
|
|
170506
|
+
describe: "project docs"
|
|
170507
|
+
});
|
|
170508
|
+
if (docsBlock)
|
|
170509
|
+
sections.push(docsBlock);
|
|
170510
|
+
}
|
|
170511
|
+
if (opts.userMemoriesEnabled && !existing.includes(USER_PROFILE_MARKER)) {
|
|
170512
|
+
const profileBlock = readCachedAdjunct({
|
|
170513
|
+
cache: cachedUserProfileBySession,
|
|
170514
|
+
sessionId,
|
|
170515
|
+
isCacheBusting,
|
|
170516
|
+
compute: () => buildUserProfileBlock(opts.db),
|
|
170517
|
+
describe: "user profile"
|
|
170518
|
+
});
|
|
170519
|
+
if (profileBlock)
|
|
170520
|
+
sections.push(profileBlock);
|
|
170521
|
+
}
|
|
170522
|
+
if (opts.pinKeyFilesEnabled && sessionId && !existing.includes(KEY_FILES_MARKER)) {
|
|
170523
|
+
let sessionMeta = null;
|
|
170524
|
+
try {
|
|
170525
|
+
sessionMeta = getOrCreateSessionMeta(opts.db, sessionId);
|
|
170526
|
+
} catch (error51) {
|
|
170527
|
+
sessionLog(sessionId, "Pi key-files session meta load failed:", error51);
|
|
170528
|
+
}
|
|
170529
|
+
const keyFilesBlock = sessionMeta ? readVersionedKeyFiles({
|
|
170530
|
+
db: opts.db,
|
|
170531
|
+
sessionId,
|
|
170532
|
+
sessionMeta,
|
|
170533
|
+
directory: opts.cwd,
|
|
170534
|
+
isCacheBusting,
|
|
170535
|
+
config: {
|
|
170536
|
+
enabled: opts.pinKeyFilesEnabled,
|
|
170537
|
+
tokenBudget: opts.pinKeyFilesTokenBudget ?? 1e4
|
|
170538
|
+
}
|
|
170539
|
+
}) : null;
|
|
170540
|
+
if (keyFilesBlock)
|
|
170541
|
+
sections.push(keyFilesBlock);
|
|
170542
|
+
}
|
|
170543
|
+
const dataBlock = sections.length > 0 ? `<magic-context>
|
|
170544
|
+
${sections.join(`
|
|
170545
|
+
|
|
170546
|
+
`)}
|
|
170547
|
+
</magic-context>` : null;
|
|
170548
|
+
const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
|
|
170549
|
+
if (!includeGuidance) {
|
|
170550
|
+
return dataBlock;
|
|
170551
|
+
}
|
|
170552
|
+
const guidance = buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.dropToolStructure ?? true, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false);
|
|
170553
|
+
if (dataBlock) {
|
|
170554
|
+
return `${guidance}
|
|
170555
|
+
|
|
170556
|
+
${dataBlock}`;
|
|
170557
|
+
}
|
|
170558
|
+
return guidance;
|
|
170559
|
+
}
|
|
170560
|
+
function readCachedAdjunct(args) {
|
|
170561
|
+
const { cache, sessionId, isCacheBusting, compute, describe: describe3 } = args;
|
|
170562
|
+
if (!sessionId) {
|
|
170563
|
+
return compute();
|
|
170564
|
+
}
|
|
170565
|
+
const hasCached = cache.has(sessionId);
|
|
170566
|
+
if (!hasCached || isCacheBusting) {
|
|
170567
|
+
const value = compute();
|
|
170568
|
+
cache.set(sessionId, value);
|
|
170569
|
+
if (value && !hasCached) {
|
|
170570
|
+
sessionLog(sessionId, `loaded ${describe3} (${value.length} chars)`);
|
|
170571
|
+
} else if (value && isCacheBusting) {
|
|
170572
|
+
sessionLog(sessionId, `refreshed ${describe3} (cache-busting pass)`);
|
|
170573
|
+
}
|
|
170574
|
+
return value;
|
|
170575
|
+
}
|
|
170576
|
+
return cache.get(sessionId) ?? null;
|
|
170577
|
+
}
|
|
170578
|
+
var DATE_PATTERN = /Today's date: .+/;
|
|
170579
|
+
function processSystemPromptForCache(args) {
|
|
170580
|
+
const { db, sessionId, systemPrompt, isCacheBusting } = args;
|
|
170581
|
+
let sessionMeta;
|
|
170582
|
+
try {
|
|
170583
|
+
sessionMeta = getOrCreateSessionMeta(db, sessionId);
|
|
170584
|
+
} catch (error51) {
|
|
170585
|
+
sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
|
|
170586
|
+
}
|
|
170587
|
+
const previousHash = sessionMeta?.systemPromptHash ?? "";
|
|
170588
|
+
const isFirstHash = previousHash === "" || previousHash === "0";
|
|
170589
|
+
let frozenPrompt = systemPrompt;
|
|
170590
|
+
const dateMatch = systemPrompt.match(DATE_PATTERN);
|
|
170591
|
+
const liveDate = dateMatch ? dateMatch[0] : null;
|
|
170592
|
+
const stickyDate = stickyDateBySession.get(sessionId);
|
|
170593
|
+
if (liveDate && !stickyDate) {
|
|
170594
|
+
stickyDateBySession.set(sessionId, liveDate);
|
|
170595
|
+
} else if (liveDate && stickyDate && liveDate !== stickyDate) {
|
|
170596
|
+
if (isCacheBusting) {
|
|
170597
|
+
stickyDateBySession.set(sessionId, liveDate);
|
|
170598
|
+
sessionLog(sessionId, `system prompt date updated: ${stickyDate} → ${liveDate} (cache-busting pass)`);
|
|
170599
|
+
} else {
|
|
170600
|
+
frozenPrompt = systemPrompt.replace(DATE_PATTERN, stickyDate);
|
|
170601
|
+
sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
|
|
170602
|
+
}
|
|
170603
|
+
}
|
|
170604
|
+
const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
|
|
170605
|
+
const hashChanged = !isFirstHash && currentHash !== previousHash;
|
|
170606
|
+
if (hashChanged) {
|
|
170607
|
+
sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
|
|
170608
|
+
} else if (isFirstHash) {
|
|
170609
|
+
sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${frozenPrompt.length})`);
|
|
170610
|
+
}
|
|
170611
|
+
const systemPromptTokens = estimateTokens(frozenPrompt);
|
|
170612
|
+
if (sessionMeta) {
|
|
170613
|
+
if (currentHash !== previousHash) {
|
|
170614
|
+
updateSessionMeta(db, sessionId, {
|
|
170615
|
+
systemPromptHash: currentHash,
|
|
170616
|
+
systemPromptTokens
|
|
170617
|
+
});
|
|
170618
|
+
} else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
|
|
170619
|
+
updateSessionMeta(db, sessionId, { systemPromptTokens });
|
|
170620
|
+
}
|
|
170621
|
+
}
|
|
170622
|
+
return {
|
|
170623
|
+
systemPrompt: frozenPrompt,
|
|
170624
|
+
hashChanged,
|
|
170625
|
+
currentHash
|
|
170626
|
+
};
|
|
170627
|
+
}
|
|
170628
|
+
function clearPiSystemPromptSession(sessionId) {
|
|
170629
|
+
stickyDateBySession.delete(sessionId);
|
|
170630
|
+
cachedDocsBySession.delete(sessionId);
|
|
170631
|
+
cachedUserProfileBySession.delete(sessionId);
|
|
170632
|
+
clearKeyFilesCacheForSession(sessionId);
|
|
170633
|
+
}
|
|
170634
|
+
|
|
170082
170635
|
// src/temporal-awareness-pi.ts
|
|
170083
170636
|
function injectPiTemporalMarkers(messages) {
|
|
170084
170637
|
let injected = 0;
|
|
@@ -170639,6 +171192,12 @@ var commitSeenLastPass = new Map;
|
|
|
170639
171192
|
var historyRefreshSessions = new Set;
|
|
170640
171193
|
var systemPromptRefreshSessions = new Set;
|
|
170641
171194
|
var pendingMaterializationSessions = new Set;
|
|
171195
|
+
var deferredHistoryRefreshSessions = new Set;
|
|
171196
|
+
var deferredMaterializationSessions = new Set;
|
|
171197
|
+
var sessionsByProject = new Map;
|
|
171198
|
+
var lastSeenProjectIdentityBySession = new Map;
|
|
171199
|
+
var rawMessageProviderUnregistersBySession = new Map;
|
|
171200
|
+
var lastHeuristicsTurnIdBySession = new Map;
|
|
170642
171201
|
var firstContextPassSeenBySession = new Set;
|
|
170643
171202
|
var recentReduceBySession = new Map;
|
|
170644
171203
|
var liveModelBySession = new Map;
|
|
@@ -170672,6 +171231,50 @@ function signalPiSystemPromptRefresh(sessionId) {
|
|
|
170672
171231
|
function signalPiPendingMaterialization(sessionId) {
|
|
170673
171232
|
pendingMaterializationSessions.add(sessionId);
|
|
170674
171233
|
}
|
|
171234
|
+
function signalPiDeferredHistoryRefresh(sessionId) {
|
|
171235
|
+
deferredHistoryRefreshSessions.add(sessionId);
|
|
171236
|
+
}
|
|
171237
|
+
function signalPiDeferredMaterialization(sessionId) {
|
|
171238
|
+
deferredMaterializationSessions.add(sessionId);
|
|
171239
|
+
}
|
|
171240
|
+
function consumeDeferredHistoryRefresh(sessionId) {
|
|
171241
|
+
const wasSet = deferredHistoryRefreshSessions.has(sessionId);
|
|
171242
|
+
deferredHistoryRefreshSessions.delete(sessionId);
|
|
171243
|
+
return wasSet;
|
|
171244
|
+
}
|
|
171245
|
+
function consumeDeferredMaterialization(sessionId) {
|
|
171246
|
+
const wasSet = deferredMaterializationSessions.has(sessionId);
|
|
171247
|
+
deferredMaterializationSessions.delete(sessionId);
|
|
171248
|
+
return wasSet;
|
|
171249
|
+
}
|
|
171250
|
+
function trackSessionForProject(projectIdentity, sessionId) {
|
|
171251
|
+
let sessions = sessionsByProject.get(projectIdentity);
|
|
171252
|
+
if (!sessions) {
|
|
171253
|
+
sessions = new Set;
|
|
171254
|
+
sessionsByProject.set(projectIdentity, sessions);
|
|
171255
|
+
}
|
|
171256
|
+
sessions.add(sessionId);
|
|
171257
|
+
}
|
|
171258
|
+
function updateSessionProjectTracking(sessionId, projectIdentity) {
|
|
171259
|
+
const prev = lastSeenProjectIdentityBySession.get(sessionId);
|
|
171260
|
+
if (prev && prev !== projectIdentity) {
|
|
171261
|
+
const prevSessions = sessionsByProject.get(prev);
|
|
171262
|
+
prevSessions?.delete(sessionId);
|
|
171263
|
+
if (prevSessions?.size === 0)
|
|
171264
|
+
sessionsByProject.delete(prev);
|
|
171265
|
+
clearPiSystemPromptSession(sessionId);
|
|
171266
|
+
}
|
|
171267
|
+
trackSessionForProject(projectIdentity, sessionId);
|
|
171268
|
+
lastSeenProjectIdentityBySession.set(sessionId, projectIdentity);
|
|
171269
|
+
}
|
|
171270
|
+
function signalPiSystemPromptRefreshForProject(projectIdentity) {
|
|
171271
|
+
const sessions = sessionsByProject.get(projectIdentity);
|
|
171272
|
+
if (!sessions)
|
|
171273
|
+
return;
|
|
171274
|
+
for (const sessionId of sessions) {
|
|
171275
|
+
systemPromptRefreshSessions.add(sessionId);
|
|
171276
|
+
}
|
|
171277
|
+
}
|
|
170675
171278
|
function recordPiLiveModel(sessionId, modelKey) {
|
|
170676
171279
|
liveModelBySession.set(sessionId, modelKey);
|
|
170677
171280
|
}
|
|
@@ -170748,109 +171351,55 @@ function resolveSessionId2(ctx) {
|
|
|
170748
171351
|
return;
|
|
170749
171352
|
}
|
|
170750
171353
|
}
|
|
170751
|
-
function
|
|
171354
|
+
function collectMessageEntryIdsByRef(ctx, messages, sessionId) {
|
|
170752
171355
|
const sm = ctx.sessionManager;
|
|
170753
171356
|
if (typeof sm?.getBranch !== "function")
|
|
170754
|
-
return;
|
|
171357
|
+
return null;
|
|
170755
171358
|
let entries;
|
|
170756
171359
|
try {
|
|
170757
171360
|
entries = sm.getBranch.call(sm);
|
|
170758
|
-
} catch {
|
|
170759
|
-
|
|
171361
|
+
} catch (error51) {
|
|
171362
|
+
sessionLog(sessionId ?? "pi", `collectMessageEntryIdsByRef getBranch failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
171363
|
+
return null;
|
|
170760
171364
|
}
|
|
170761
171365
|
if (!Array.isArray(entries))
|
|
170762
|
-
return;
|
|
170763
|
-
|
|
170764
|
-
|
|
170765
|
-
for (let i = entries.length - 1;i >= 0; i--) {
|
|
170766
|
-
const e = entries[i];
|
|
170767
|
-
if (e && typeof e === "object" && e.type === "compaction") {
|
|
170768
|
-
compactionIndex = i;
|
|
170769
|
-
if (typeof e.firstKeptEntryId === "string") {
|
|
170770
|
-
firstKeptEntryId = e.firstKeptEntryId;
|
|
170771
|
-
}
|
|
170772
|
-
break;
|
|
170773
|
-
}
|
|
170774
|
-
}
|
|
170775
|
-
const ids = [];
|
|
170776
|
-
const isEmitEligible = (entry) => {
|
|
171366
|
+
return null;
|
|
171367
|
+
const entryIdByMsgRef = new Map;
|
|
171368
|
+
for (const entry of entries) {
|
|
170777
171369
|
if (!entry || typeof entry !== "object")
|
|
170778
|
-
|
|
170779
|
-
const
|
|
170780
|
-
|
|
170781
|
-
|
|
170782
|
-
|
|
170783
|
-
|
|
170784
|
-
|
|
170785
|
-
|
|
170786
|
-
|
|
170787
|
-
if (t === "branch_summary") {
|
|
170788
|
-
const summary = entry.summary;
|
|
170789
|
-
return typeof summary === "string" && summary.length > 0;
|
|
170790
|
-
}
|
|
170791
|
-
return false;
|
|
170792
|
-
};
|
|
170793
|
-
if (compactionIndex >= 0) {
|
|
170794
|
-
ids.push(undefined);
|
|
170795
|
-
if (firstKeptEntryId !== undefined) {
|
|
170796
|
-
let foundFirstKept = false;
|
|
170797
|
-
for (let i = 0;i < compactionIndex; i++) {
|
|
170798
|
-
const entry = entries[i];
|
|
170799
|
-
const entryId = entry?.id;
|
|
170800
|
-
if (typeof entryId === "string" && entryId === firstKeptEntryId) {
|
|
170801
|
-
foundFirstKept = true;
|
|
170802
|
-
}
|
|
170803
|
-
if (!foundFirstKept)
|
|
170804
|
-
continue;
|
|
170805
|
-
if (isEmitEligible(entry)) {
|
|
170806
|
-
ids.push(entry.id);
|
|
170807
|
-
}
|
|
170808
|
-
}
|
|
170809
|
-
}
|
|
170810
|
-
for (let i = compactionIndex + 1;i < entries.length; i++) {
|
|
170811
|
-
const entry = entries[i];
|
|
170812
|
-
if (isEmitEligible(entry)) {
|
|
170813
|
-
ids.push(entry.id);
|
|
170814
|
-
}
|
|
170815
|
-
}
|
|
170816
|
-
} else {
|
|
170817
|
-
for (const entry of entries) {
|
|
170818
|
-
if (isEmitEligible(entry)) {
|
|
170819
|
-
ids.push(entry.id);
|
|
170820
|
-
}
|
|
170821
|
-
}
|
|
171370
|
+
continue;
|
|
171371
|
+
const e = entry;
|
|
171372
|
+
if (e.type !== "message")
|
|
171373
|
+
continue;
|
|
171374
|
+
if (typeof e.id !== "string")
|
|
171375
|
+
continue;
|
|
171376
|
+
if (!e.message || typeof e.message !== "object")
|
|
171377
|
+
continue;
|
|
171378
|
+
entryIdByMsgRef.set(e.message, e.id);
|
|
170822
171379
|
}
|
|
170823
|
-
|
|
170824
|
-
|
|
170825
|
-
|
|
170826
|
-
|
|
170827
|
-
if (
|
|
170828
|
-
|
|
170829
|
-
|
|
170830
|
-
|
|
170831
|
-
|
|
170832
|
-
|
|
170833
|
-
|
|
170834
|
-
|
|
170835
|
-
|
|
170836
|
-
|
|
171380
|
+
const result = new Array(messages.length);
|
|
171381
|
+
let resolved = 0;
|
|
171382
|
+
for (let i = 0;i < messages.length; i++) {
|
|
171383
|
+
const msg = messages[i];
|
|
171384
|
+
if (!msg || typeof msg !== "object") {
|
|
171385
|
+
result[i] = undefined;
|
|
171386
|
+
continue;
|
|
171387
|
+
}
|
|
171388
|
+
const id = entryIdByMsgRef.get(msg);
|
|
171389
|
+
if (typeof id === "string") {
|
|
171390
|
+
result[i] = id;
|
|
171391
|
+
resolved += 1;
|
|
171392
|
+
} else {
|
|
171393
|
+
result[i] = undefined;
|
|
170837
171394
|
}
|
|
170838
|
-
return ids.slice(ids.length - expectedLength);
|
|
170839
171395
|
}
|
|
170840
|
-
|
|
170841
|
-
}
|
|
170842
|
-
function collectMessageEntryIdsStrict(ctx, expectedLength, sessionId) {
|
|
170843
|
-
try {
|
|
170844
|
-
return collectMessageEntryIds(ctx, expectedLength, sessionId, true) ?? null;
|
|
170845
|
-
} catch (error51) {
|
|
170846
|
-
sessionLog(sessionId ?? "pi", `collectMessageEntryIdsStrict failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
170847
|
-
return null;
|
|
171396
|
+
if (resolved < messages.length) {
|
|
171397
|
+
log(`[magic-context][pi]${sessionId ? `[${sessionId}]` : ""} ` + `collectMessageEntryIdsByRef: resolved=${resolved}/${messages.length} ` + `(branchEntries=${entries.length}, messageEntries=${entryIdByMsgRef.size}) — ` + `unmapped slots fall through to synthesized ids; boundary lookup still works ` + `for any compartment whose start/end message is among the resolved set`);
|
|
170848
171398
|
}
|
|
171399
|
+
return result;
|
|
170849
171400
|
}
|
|
170850
171401
|
function registerPiContextHandler(pi, options) {
|
|
170851
171402
|
const tagger = createTagger();
|
|
170852
|
-
const projectDirectory = process.cwd();
|
|
170853
|
-
const projectIdentity = resolveProjectIdentity(projectDirectory);
|
|
170854
171403
|
const schedulerConfig = options.scheduler ?? {
|
|
170855
171404
|
executeThresholdPercentage: 65
|
|
170856
171405
|
};
|
|
@@ -170876,14 +171425,19 @@ function registerPiContextHandler(pi, options) {
|
|
|
170876
171425
|
return;
|
|
170877
171426
|
}
|
|
170878
171427
|
sessionIdForError = sessionId;
|
|
171428
|
+
const projectDirectory = ctx.cwd;
|
|
171429
|
+
const projectIdentity = resolveProjectIdentity(projectDirectory);
|
|
171430
|
+
updateSessionProjectTracking(sessionId, projectIdentity);
|
|
170879
171431
|
logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
|
|
170880
171432
|
const rawMessageProvider = {
|
|
170881
171433
|
readMessages: () => readPiSessionMessages(ctx),
|
|
170882
171434
|
readMessageById: (messageId) => readPiSessionMessageById(ctx, messageId)
|
|
170883
171435
|
};
|
|
170884
|
-
|
|
171436
|
+
rawMessageProviderUnregistersBySession.get(sessionId)?.();
|
|
171437
|
+
const unregisterRaw = setRawMessageProvider(sessionId, rawMessageProvider);
|
|
171438
|
+
rawMessageProviderUnregistersBySession.set(sessionId, unregisterRaw);
|
|
170885
171439
|
scheduleReconciliation(options.db, sessionId, readRawSessionMessages);
|
|
170886
|
-
const strictEntryIds =
|
|
171440
|
+
const strictEntryIds = collectMessageEntryIdsByRef(ctx, event.messages, sessionId);
|
|
170887
171441
|
const tLastUser = performance.now();
|
|
170888
171442
|
const latestUser = findLatestUserMessageIdPi(event.messages, buildPiMessageIdByIndex(event.messages, strictEntryIds));
|
|
170889
171443
|
logTransformTiming(sessionId, "findLastUserMessageId", tLastUser);
|
|
@@ -170916,12 +171470,18 @@ function registerPiContextHandler(pi, options) {
|
|
|
170916
171470
|
lastContextPercentage: 0,
|
|
170917
171471
|
lastInputTokens: 0,
|
|
170918
171472
|
observedSafeInputTokens: 0,
|
|
170919
|
-
cacheAlertSent: false
|
|
171473
|
+
cacheAlertSent: false,
|
|
171474
|
+
clearedReasoningThroughTag: 0
|
|
170920
171475
|
});
|
|
171476
|
+
clearHistorianFailureState(options.db, sessionId);
|
|
171477
|
+
clearPersistedReasoningWatermark(options.db, sessionId);
|
|
171478
|
+
clearDetectedContextLimit(options.db, sessionId);
|
|
171479
|
+
clearEmergencyRecovery(options.db, sessionId);
|
|
170921
171480
|
sessionMetaForUsage.lastContextPercentage = 0;
|
|
170922
171481
|
sessionMetaForUsage.lastInputTokens = 0;
|
|
170923
171482
|
sessionMetaForUsage.observedSafeInputTokens = 0;
|
|
170924
171483
|
sessionMetaForUsage.cacheAlertSent = false;
|
|
171484
|
+
sessionMetaForUsage.clearedReasoningThroughTag = 0;
|
|
170925
171485
|
}
|
|
170926
171486
|
let usagePercentage = 0;
|
|
170927
171487
|
let usageInputTokens = 0;
|
|
@@ -171010,7 +171570,7 @@ function registerPiContextHandler(pi, options) {
|
|
|
171010
171570
|
const isCacheBusting = historyRefreshSessions.has(sessionId);
|
|
171011
171571
|
sessionLog(sessionId, `transform: usage=${usagePercentage.toFixed(1)}% (${usageInputTokens} tokens, limit=${usageContextLimit ?? "?"}) decision=${schedulerDecision}${forceMaterialization ? " force=true" : ""}${isEmergency ? " EMERGENCY=true" : ""}${isCacheBusting ? " busting=true" : ""}`);
|
|
171012
171572
|
logTransformTiming(sessionId, "emergencyRecoveryBlock", tEmergencyRecovery);
|
|
171013
|
-
const entryIds =
|
|
171573
|
+
const entryIds = collectMessageEntryIdsByRef(ctx, event.messages, sessionId) ?? undefined;
|
|
171014
171574
|
const result = await runPipeline({
|
|
171015
171575
|
db: options.db,
|
|
171016
171576
|
tagger,
|
|
@@ -171050,11 +171610,25 @@ function registerPiContextHandler(pi, options) {
|
|
|
171050
171610
|
isCacheBusting,
|
|
171051
171611
|
usagePercentage,
|
|
171052
171612
|
usageInputTokens,
|
|
171053
|
-
usageContextLimit
|
|
171613
|
+
usageContextLimit,
|
|
171614
|
+
schedulerDecision
|
|
171054
171615
|
});
|
|
171055
171616
|
}
|
|
171056
171617
|
const tPostTransform = performance.now();
|
|
171057
171618
|
let outputMessages = result.messages;
|
|
171619
|
+
try {
|
|
171620
|
+
const cacheBustingPass = isCacheBusting || result.executedWorkThisPass;
|
|
171621
|
+
outputMessages = applyStickyTurnReminder({
|
|
171622
|
+
sessionId,
|
|
171623
|
+
db: options.db,
|
|
171624
|
+
messages: outputMessages,
|
|
171625
|
+
entryIds: entryIds ?? null,
|
|
171626
|
+
hasRecentReduceCall: recentReduceBySession.has(sessionId),
|
|
171627
|
+
isCacheBustingPass: cacheBustingPass
|
|
171628
|
+
});
|
|
171629
|
+
} catch (err) {
|
|
171630
|
+
sessionLog(sessionId, `sticky turn reminder failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
171631
|
+
}
|
|
171058
171632
|
if (nudgerFn && options.nudge) {
|
|
171059
171633
|
try {
|
|
171060
171634
|
const tNudge = performance.now();
|
|
@@ -171168,7 +171742,7 @@ function maybeFireCompressor(args) {
|
|
|
171168
171742
|
const compressor = historian.compressor;
|
|
171169
171743
|
if (!compressor?.enabled)
|
|
171170
171744
|
return;
|
|
171171
|
-
if (!args.isCacheBusting)
|
|
171745
|
+
if (!args.isCacheBusting && args.schedulerDecision !== "execute")
|
|
171172
171746
|
return;
|
|
171173
171747
|
if (inFlightHistorian.has(sessionId) || inFlightCompressor.has(sessionId)) {
|
|
171174
171748
|
sessionLog(sessionId, "compressor trigger eval: in-flight historian/compressor, skipping");
|
|
@@ -171188,7 +171762,6 @@ function maybeFireCompressor(args) {
|
|
|
171188
171762
|
});
|
|
171189
171763
|
if (!historyBudgetTokens || historyBudgetTokens <= 0)
|
|
171190
171764
|
return;
|
|
171191
|
-
markPiCompressorRun(sessionId);
|
|
171192
171765
|
const runPromise = runPiCompressionPassIfNeeded({
|
|
171193
171766
|
db,
|
|
171194
171767
|
sessionId,
|
|
@@ -171204,10 +171777,14 @@ function maybeFireCompressor(args) {
|
|
|
171204
171777
|
maxCompartmentsPerPass: compressor.maxCompartmentsPerPass,
|
|
171205
171778
|
graceCompartments: compressor.graceCompartments,
|
|
171206
171779
|
onPublished: () => {
|
|
171207
|
-
|
|
171208
|
-
|
|
171780
|
+
signalPiDeferredHistoryRefresh(sessionId);
|
|
171781
|
+
signalPiDeferredMaterialization(sessionId);
|
|
171209
171782
|
historian.onStatusChange?.(ctx, sessionId);
|
|
171210
171783
|
}
|
|
171784
|
+
}).then((didPublish) => {
|
|
171785
|
+
if (didPublish === true) {
|
|
171786
|
+
markPiCompressorRun(sessionId);
|
|
171787
|
+
}
|
|
171211
171788
|
}).catch((err) => {
|
|
171212
171789
|
sessionLog(sessionId, `compressor failed in background: ${err instanceof Error ? err.message : String(err)}`);
|
|
171213
171790
|
}).finally(() => {
|
|
@@ -171262,8 +171839,8 @@ function spawnPiHistorianRun(args) {
|
|
|
171262
171839
|
} catch (err) {
|
|
171263
171840
|
sessionLog(sessionId, `historian: clearEmergencyRecovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
171264
171841
|
}
|
|
171265
|
-
|
|
171266
|
-
|
|
171842
|
+
signalPiDeferredHistoryRefresh(sessionId);
|
|
171843
|
+
signalPiDeferredMaterialization(sessionId);
|
|
171267
171844
|
historian.onStatusChange?.(ctx, sessionId);
|
|
171268
171845
|
}
|
|
171269
171846
|
}).finally(() => {
|
|
@@ -171393,6 +171970,12 @@ async function runPipeline(args) {
|
|
|
171393
171970
|
logTransformTiming(args.sessionId, "injectTemporalMarkers", tTemporal);
|
|
171394
171971
|
}
|
|
171395
171972
|
const transcript = createPiTranscript(args.messages, args.sessionId);
|
|
171973
|
+
const currentTurnId = (() => {
|
|
171974
|
+
const ids = buildPiMessageIdByIndex(args.messages, args.entryIds ?? null);
|
|
171975
|
+
return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
|
|
171976
|
+
})();
|
|
171977
|
+
const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
|
|
171978
|
+
const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
|
|
171396
171979
|
const tTag = performance.now();
|
|
171397
171980
|
const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
|
|
171398
171981
|
skipPrefixInjection: !args.ctxReduceEnabled
|
|
@@ -171413,10 +171996,16 @@ async function runPipeline(args) {
|
|
|
171413
171996
|
sessionLog(args.sessionId, `commit-detect failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
171414
171997
|
}
|
|
171415
171998
|
const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
|
|
171999
|
+
const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
|
|
172000
|
+
const deferredHistoryRefreshWasPending = deferredHistoryRefreshSessions.has(args.sessionId);
|
|
171416
172001
|
const pendingOps = getPendingOps(args.db, args.sessionId);
|
|
171417
|
-
const
|
|
172002
|
+
const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
|
|
172003
|
+
const canConsumeDeferredLate = baseShouldApplyPendingOps || shouldRunHeuristics;
|
|
172004
|
+
const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
|
|
172005
|
+
const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
|
|
172006
|
+
const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
|
|
171418
172007
|
if (shouldApplyPendingOps) {
|
|
171419
|
-
const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
|
|
172008
|
+
const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
|
|
171420
172009
|
sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
|
|
171421
172010
|
try {
|
|
171422
172011
|
const tApplyPending = performance.now();
|
|
@@ -171426,6 +172015,9 @@ async function runPipeline(args) {
|
|
|
171426
172015
|
if (hasPendingMaterializeSignal) {
|
|
171427
172016
|
consumePendingMaterialization(args.sessionId);
|
|
171428
172017
|
}
|
|
172018
|
+
if (deferredMaterialize) {
|
|
172019
|
+
consumeDeferredMaterialization(args.sessionId);
|
|
172020
|
+
}
|
|
171429
172021
|
} catch (err) {
|
|
171430
172022
|
sessionLog(args.sessionId, `pending operations failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
171431
172023
|
throw err;
|
|
@@ -171482,7 +172074,6 @@ async function runPipeline(args) {
|
|
|
171482
172074
|
const tActiveTags = performance.now();
|
|
171483
172075
|
const activeTags = getActiveTagsBySession(args.db, args.sessionId);
|
|
171484
172076
|
logTransformTiming(args.sessionId, "getActiveTagsBySession", tActiveTags, `count=${activeTags.length}`);
|
|
171485
|
-
const shouldRunHeuristics = args.heuristics !== undefined && (args.schedulerDecision === "execute" || args.forceMaterialization === true);
|
|
171486
172077
|
if (shouldRunHeuristics) {
|
|
171487
172078
|
const reason = args.forceMaterialization ? "force_materialization" : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
|
|
171488
172079
|
sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`);
|
|
@@ -171502,6 +172093,9 @@ async function runPipeline(args) {
|
|
|
171502
172093
|
}, activeTags);
|
|
171503
172094
|
heuristicsExecuted = true;
|
|
171504
172095
|
executedWorkThisPass = true;
|
|
172096
|
+
if (currentTurnId !== null) {
|
|
172097
|
+
lastHeuristicsTurnIdBySession.set(args.sessionId, currentTurnId);
|
|
172098
|
+
}
|
|
171505
172099
|
logTransformTiming(args.sessionId, "applyHeuristicCleanup", tHeuristic, `droppedTools=${heuristicsResult.droppedTools} deduplicatedTools=${heuristicsResult.deduplicatedTools} droppedInjections=${heuristicsResult.droppedInjections} compressedTextTags=${heuristicsResult.compressedTextTags}`);
|
|
171506
172100
|
} catch (err) {
|
|
171507
172101
|
sessionLog(args.sessionId, `heuristic cleanup failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -171510,21 +172104,26 @@ async function runPipeline(args) {
|
|
|
171510
172104
|
if (args.reasoningClearing && (args.schedulerDecision === "execute" || args.forceMaterialization === true)) {
|
|
171511
172105
|
try {
|
|
171512
172106
|
const tClearReasoning = performance.now();
|
|
172107
|
+
const meta3 = getOrCreateSessionMeta(args.db, args.sessionId);
|
|
172108
|
+
const prevWatermark = meta3.clearedReasoningThroughTag ?? 0;
|
|
171513
172109
|
const clearOutcome = clearOldReasoningPi({
|
|
171514
172110
|
messages: args.messages,
|
|
171515
172111
|
messageIdToMaxTag,
|
|
171516
172112
|
clearReasoningAge: args.reasoningClearing.clearReasoningAge,
|
|
171517
172113
|
piMessageStableId
|
|
171518
172114
|
});
|
|
171519
|
-
const
|
|
171520
|
-
|
|
171521
|
-
|
|
171522
|
-
|
|
171523
|
-
|
|
171524
|
-
|
|
171525
|
-
|
|
171526
|
-
|
|
171527
|
-
|
|
172115
|
+
const stripOutcome = stripInlineThinkingPi({
|
|
172116
|
+
messages: args.messages,
|
|
172117
|
+
messageIdToMaxTag,
|
|
172118
|
+
clearReasoningAge: args.reasoningClearing.clearReasoningAge,
|
|
172119
|
+
piMessageStableId
|
|
172120
|
+
});
|
|
172121
|
+
const combinedWatermark = Math.max(clearOutcome.newWatermark, stripOutcome.newWatermark);
|
|
172122
|
+
if (combinedWatermark > prevWatermark) {
|
|
172123
|
+
updateSessionMeta(args.db, args.sessionId, {
|
|
172124
|
+
clearedReasoningThroughTag: combinedWatermark
|
|
172125
|
+
});
|
|
172126
|
+
sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearOutcome.cleared} inlineStripped=${stripOutcome.stripped} watermark=${prevWatermark}→${combinedWatermark}`);
|
|
171528
172127
|
}
|
|
171529
172128
|
logTransformTiming(args.sessionId, "clearOldReasoning", tClearReasoning);
|
|
171530
172129
|
logTransformTiming(args.sessionId, "watermarkCleanup", tClearReasoning);
|
|
@@ -171538,10 +172137,13 @@ async function runPipeline(args) {
|
|
|
171538
172137
|
if (args.injection) {
|
|
171539
172138
|
try {
|
|
171540
172139
|
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);
|
|
172140
|
+
injectionResult = injectSessionHistoryIntoPi(args.db, args.sessionId, args.messages, args.isCacheBusting || deferredHistoryRefresh, args.projectIdentity, args.injection.injectionBudgetTokens, args.injection.temporalAwareness, args.entryIds);
|
|
171542
172141
|
if (args.isCacheBusting) {
|
|
171543
172142
|
historyRefreshSessions.delete(args.sessionId);
|
|
171544
172143
|
}
|
|
172144
|
+
if (deferredHistoryRefresh) {
|
|
172145
|
+
consumeDeferredHistoryRefresh(args.sessionId);
|
|
172146
|
+
}
|
|
171545
172147
|
logTransformTiming(args.sessionId, "prepareCompartmentInjection", tInjection);
|
|
171546
172148
|
logTransformTiming(args.sessionId, "compartmentPhase", tInjection);
|
|
171547
172149
|
} catch (err) {
|
|
@@ -171604,6 +172206,29 @@ function applyRollingNudge(args) {
|
|
|
171604
172206
|
return messages;
|
|
171605
172207
|
return injectPiNudge(messages, nudge);
|
|
171606
172208
|
}
|
|
172209
|
+
function applyStickyTurnReminder(args) {
|
|
172210
|
+
const reminder = getPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
172211
|
+
if (!reminder)
|
|
172212
|
+
return args.messages;
|
|
172213
|
+
if (args.hasRecentReduceCall && args.isCacheBustingPass) {
|
|
172214
|
+
clearPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
172215
|
+
return args.messages;
|
|
172216
|
+
}
|
|
172217
|
+
const messageIdByIndex = buildPiMessageIdByIndex(args.messages, args.entryIds);
|
|
172218
|
+
if (reminder.messageId) {
|
|
172219
|
+
const reinjected = appendReminderToUserMessageByIdPi(args.messages, messageIdByIndex, reminder.messageId, reminder.text);
|
|
172220
|
+
if (!reinjected && args.isCacheBustingPass) {
|
|
172221
|
+
clearPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
172222
|
+
}
|
|
172223
|
+
return args.messages;
|
|
172224
|
+
}
|
|
172225
|
+
const latest = findLatestUserMessageIdPi(args.messages, messageIdByIndex);
|
|
172226
|
+
if (latest) {
|
|
172227
|
+
appendReminderToPiUserMessage(args.messages[latest.index], reminder.text);
|
|
172228
|
+
setPersistedStickyTurnReminder(args.db, args.sessionId, reminder.text, latest.messageId);
|
|
172229
|
+
}
|
|
172230
|
+
return args.messages;
|
|
172231
|
+
}
|
|
171607
172232
|
function applyNoteNudges(args) {
|
|
171608
172233
|
const { sessionId, db, messages, projectIdentity, entryIds, isCacheBusting } = args;
|
|
171609
172234
|
const messageIdByIndex = buildPiMessageIdByIndex(messages, entryIds);
|
|
@@ -171743,12 +172368,26 @@ function clearContextHandlerSession(sessionId) {
|
|
|
171743
172368
|
historyRefreshSessions.delete(sessionId);
|
|
171744
172369
|
pendingMaterializationSessions.delete(sessionId);
|
|
171745
172370
|
systemPromptRefreshSessions.delete(sessionId);
|
|
172371
|
+
deferredHistoryRefreshSessions.delete(sessionId);
|
|
172372
|
+
deferredMaterializationSessions.delete(sessionId);
|
|
171746
172373
|
firstContextPassSeenBySession.delete(sessionId);
|
|
171747
172374
|
commitSeenLastPass.delete(sessionId);
|
|
171748
172375
|
recentReduceBySession.delete(sessionId);
|
|
171749
172376
|
liveModelBySession.delete(sessionId);
|
|
171750
172377
|
toolUsageSinceUserTurn.delete(sessionId);
|
|
171751
172378
|
latestUserMessageBySession.delete(sessionId);
|
|
172379
|
+
lastHeuristicsTurnIdBySession.delete(sessionId);
|
|
172380
|
+
lastSeenProjectIdentityBySession.delete(sessionId);
|
|
172381
|
+
for (const [projectIdentity, sessions] of sessionsByProject) {
|
|
172382
|
+
sessions.delete(sessionId);
|
|
172383
|
+
if (sessions.size === 0)
|
|
172384
|
+
sessionsByProject.delete(projectIdentity);
|
|
172385
|
+
}
|
|
172386
|
+
const unregister = rawMessageProviderUnregistersBySession.get(sessionId);
|
|
172387
|
+
if (unregister) {
|
|
172388
|
+
unregister();
|
|
172389
|
+
rawMessageProviderUnregistersBySession.delete(sessionId);
|
|
172390
|
+
}
|
|
171752
172391
|
clearSessionTracking(sessionId);
|
|
171753
172392
|
clearPiCompressorState(sessionId);
|
|
171754
172393
|
}
|
|
@@ -171799,7 +172438,7 @@ init_logger();
|
|
|
171799
172438
|
init_data_path();
|
|
171800
172439
|
init_logger();
|
|
171801
172440
|
await init_sqlite();
|
|
171802
|
-
import { join as
|
|
172441
|
+
import { join as join17 } from "node:path";
|
|
171803
172442
|
var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
171804
172443
|
function randomBase62(length) {
|
|
171805
172444
|
const chars = [];
|
|
@@ -171820,7 +172459,7 @@ function generatePartId(timestampMs, counter = 0n) {
|
|
|
171820
172459
|
return generateId("prt", timestampMs, counter);
|
|
171821
172460
|
}
|
|
171822
172461
|
function getOpenCodeDbPath3() {
|
|
171823
|
-
return
|
|
172462
|
+
return join17(getDataDir(), "opencode", "opencode.db");
|
|
171824
172463
|
}
|
|
171825
172464
|
var cachedWriteDb = null;
|
|
171826
172465
|
var REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
|
|
@@ -172340,7 +172979,7 @@ async function runCompressorPass2(args) {
|
|
|
172340
172979
|
});
|
|
172341
172980
|
const messagesResponse = await client.session.messages({
|
|
172342
172981
|
path: { id: agentSessionId },
|
|
172343
|
-
query: { directory }
|
|
172982
|
+
query: { directory, limit: 50 }
|
|
172344
172983
|
});
|
|
172345
172984
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
172346
172985
|
preferResponseOnMissingData: true
|
|
@@ -172378,7 +173017,7 @@ async function runCompressorPass2(args) {
|
|
|
172378
173017
|
|
|
172379
173018
|
// ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
|
|
172380
173019
|
import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
172381
|
-
import { join as
|
|
173020
|
+
import { join as join18 } from "node:path";
|
|
172382
173021
|
init_data_path();
|
|
172383
173022
|
function historianResponseDumpDir(directory) {
|
|
172384
173023
|
return getProjectMagicContextHistorianDir(directory);
|
|
@@ -172525,7 +173164,7 @@ async function runHistorianPrompt(args) {
|
|
|
172525
173164
|
}
|
|
172526
173165
|
const messagesResponse = await client.session.messages({
|
|
172527
173166
|
path: { id: agentSessionId },
|
|
172528
|
-
query: { directory: sessionDirectory }
|
|
173167
|
+
query: { directory: sessionDirectory, limit: 50 }
|
|
172529
173168
|
});
|
|
172530
173169
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
172531
173170
|
preferResponseOnMissingData: true
|
|
@@ -172632,7 +173271,7 @@ function dumpHistorianResponse(sessionId, directory, label, text) {
|
|
|
172632
173271
|
mkdirSync6(dumpDir, { recursive: true });
|
|
172633
173272
|
const safeSessionId = sanitizeDumpName(sessionId);
|
|
172634
173273
|
const safeLabel = sanitizeDumpName(label);
|
|
172635
|
-
const dumpPath =
|
|
173274
|
+
const dumpPath = join18(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
|
|
172636
173275
|
writeFileSync3(dumpPath, text, "utf8");
|
|
172637
173276
|
sessionLog(sessionId, "compartment agent: historian response dumped", {
|
|
172638
173277
|
label,
|
|
@@ -173838,7 +174477,7 @@ import {
|
|
|
173838
174477
|
// package.json
|
|
173839
174478
|
var package_default = {
|
|
173840
174479
|
name: "@cortexkit/pi-magic-context",
|
|
173841
|
-
version: "0.21.
|
|
174480
|
+
version: "0.21.2",
|
|
173842
174481
|
type: "module",
|
|
173843
174482
|
description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
|
|
173844
174483
|
main: "dist/index.js",
|
|
@@ -174290,6 +174929,8 @@ function registerCtxStatusCommand(pi, deps) {
|
|
|
174290
174929
|
pi.registerCommand("ctx-status", {
|
|
174291
174930
|
description: "Show Magic Context status for the current Pi session",
|
|
174292
174931
|
handler: async (_args, ctx) => {
|
|
174932
|
+
const projectIdentity = deps.resolveProject?.(ctx).projectIdentity ?? deps.projectIdentity;
|
|
174933
|
+
const currentDeps = { ...deps, projectIdentity };
|
|
174293
174934
|
const sessionId = resolveSessionId(ctx);
|
|
174294
174935
|
if (!sessionId) {
|
|
174295
174936
|
sendCtxStatusMessage(pi, {
|
|
@@ -174303,13 +174944,13 @@ No active Pi session is available.`,
|
|
|
174303
174944
|
}
|
|
174304
174945
|
try {
|
|
174305
174946
|
if (ctx.hasUI) {
|
|
174306
|
-
await showStatusDialog(pi, ctx,
|
|
174947
|
+
await showStatusDialog(pi, ctx, currentDeps);
|
|
174307
174948
|
return;
|
|
174308
174949
|
}
|
|
174309
174950
|
const usage = ctx.getContextUsage?.();
|
|
174310
174951
|
const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
174311
|
-
const statusText = executeStatus(
|
|
174312
|
-
const details = buildStatusDetails(
|
|
174952
|
+
const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.nudgeIntervalTokens, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usage?.contextWindow);
|
|
174953
|
+
const details = buildStatusDetails(currentDeps, sessionId);
|
|
174313
174954
|
sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
|
|
174314
174955
|
} catch (error51) {
|
|
174315
174956
|
sendCtxStatusMessage(pi, {
|
|
@@ -174434,405 +175075,6 @@ function stripTagPrefixFromAssistantMessage(message) {
|
|
|
174434
175075
|
return mutated;
|
|
174435
175076
|
}
|
|
174436
175077
|
|
|
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
175078
|
// ../plugin/src/tools/ctx-expand/constants.ts
|
|
174837
175079
|
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
175080
|
var CTX_EXPAND_TOKEN_BUDGET = 15000;
|
|
@@ -178974,6 +179216,7 @@ var ALL_ACTIONS = [
|
|
|
178974
179216
|
"archive"
|
|
178975
179217
|
];
|
|
178976
179218
|
var DREAMER_ONLY_ACTIONS = new Set([
|
|
179219
|
+
"list",
|
|
178977
179220
|
"update",
|
|
178978
179221
|
"merge",
|
|
178979
179222
|
"archive"
|
|
@@ -179062,7 +179305,7 @@ function queueEmbedding(args) {
|
|
|
179062
179305
|
}
|
|
179063
179306
|
function createCtxMemoryTool(deps) {
|
|
179064
179307
|
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
|
|
179308
|
+
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
179309
|
return {
|
|
179067
179310
|
name: "ctx_memory",
|
|
179068
179311
|
label: "Magic Context: Memory",
|
|
@@ -179655,8 +179898,10 @@ function formatResult(result, index) {
|
|
|
179655
179898
|
].join(`
|
|
179656
179899
|
`);
|
|
179657
179900
|
}
|
|
179901
|
+
const expandStart = Math.max(1, result.messageOrdinal - 3);
|
|
179902
|
+
const expandEnd = result.messageOrdinal + 3;
|
|
179658
179903
|
return [
|
|
179659
|
-
`[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} role=${result.role}`,
|
|
179904
|
+
`[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} range=${expandStart}-${expandEnd} role=${result.role}`,
|
|
179660
179905
|
result.content
|
|
179661
179906
|
].join(`
|
|
179662
179907
|
`);
|
|
@@ -179665,7 +179910,11 @@ function formatSearchResults(query, results) {
|
|
|
179665
179910
|
if (results.length === 0) {
|
|
179666
179911
|
return `No results found for "${query}" across memories, git commits, or message history.`;
|
|
179667
179912
|
}
|
|
179668
|
-
const
|
|
179913
|
+
const bodyParts = results.map((result, index) => formatResult(result, index + 1));
|
|
179914
|
+
if (results.some((result) => result.source === "message")) {
|
|
179915
|
+
bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
|
|
179916
|
+
}
|
|
179917
|
+
const body = bodyParts.join(`
|
|
179669
179918
|
|
|
179670
179919
|
`);
|
|
179671
179920
|
return `Found ${results.length} result${results.length === 1 ? "" : "s"} for "${query}":
|
|
@@ -179825,6 +180074,11 @@ function registerMagicContextTools(pi, opts) {
|
|
|
179825
180074
|
|
|
179826
180075
|
// src/index.ts
|
|
179827
180076
|
var PREFIX2 = "[magic-context][pi]";
|
|
180077
|
+
function resolveCurrentProject(ctx) {
|
|
180078
|
+
const projectDir = ctx.cwd;
|
|
180079
|
+
const projectIdentity = resolveProjectIdentity(projectDir);
|
|
180080
|
+
return { projectDir, projectIdentity };
|
|
180081
|
+
}
|
|
179828
180082
|
function persistPiMessageEndModelMeta(args) {
|
|
179829
180083
|
if (!args.message || typeof args.message !== "object")
|
|
179830
180084
|
return;
|
|
@@ -179926,7 +180180,7 @@ async function persistPiPressureFromMessageEnd(args) {
|
|
|
179926
180180
|
}
|
|
179927
180181
|
var PLUGIN_VERSION = (() => {
|
|
179928
180182
|
try {
|
|
179929
|
-
const req =
|
|
180183
|
+
const req = createRequire4(import.meta.url);
|
|
179930
180184
|
return req("../package.json").version;
|
|
179931
180185
|
} catch {
|
|
179932
180186
|
return "0.0.0";
|
|
@@ -180004,7 +180258,7 @@ function resolveDreamerFromConfig(config2) {
|
|
|
180004
180258
|
}
|
|
180005
180259
|
async function src_default2(pi) {
|
|
180006
180260
|
const storageDir = getMagicContextStorageDir();
|
|
180007
|
-
const dbPath =
|
|
180261
|
+
const dbPath = join19(storageDir, "context.db");
|
|
180008
180262
|
let db;
|
|
180009
180263
|
try {
|
|
180010
180264
|
db = openDatabase();
|
|
@@ -180015,6 +180269,7 @@ async function src_default2(pi) {
|
|
|
180015
180269
|
}
|
|
180016
180270
|
const projectDir = process.cwd();
|
|
180017
180271
|
const projectIdentity = resolveProjectIdentity(projectDir);
|
|
180272
|
+
const seenDreamerProjectIdentities = new Set([projectIdentity]);
|
|
180018
180273
|
info(`loaded v${PLUGIN_VERSION} | harness=pi | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
|
|
180019
180274
|
const { config: config2, warnings, loadedFromPaths } = loadPiConfig({
|
|
180020
180275
|
cwd: projectDir
|
|
@@ -180045,7 +180300,10 @@ async function src_default2(pi) {
|
|
|
180045
180300
|
const historianConfig = resolveHistorianFromConfig(config2);
|
|
180046
180301
|
if (historianConfig) {
|
|
180047
180302
|
historianConfig.onStatusChange = (ctx) => {
|
|
180048
|
-
updateStatusLine(ctx, {
|
|
180303
|
+
updateStatusLine(ctx, {
|
|
180304
|
+
db,
|
|
180305
|
+
projectIdentity: resolveCurrentProject(ctx).projectIdentity
|
|
180306
|
+
});
|
|
180049
180307
|
};
|
|
180050
180308
|
}
|
|
180051
180309
|
const nudgeConfig = resolveNudgeFromConfig(config2);
|
|
@@ -180084,12 +180342,17 @@ async function src_default2(pi) {
|
|
|
180084
180342
|
registerCtxStatusCommand(pi, {
|
|
180085
180343
|
db,
|
|
180086
180344
|
projectIdentity,
|
|
180345
|
+
resolveProject: resolveCurrentProject,
|
|
180087
180346
|
protectedTags: config2.protected_tags,
|
|
180088
180347
|
nudgeIntervalTokens: config2.nudge_interval_tokens,
|
|
180089
180348
|
executeThresholdPercentage: config2.execute_threshold_percentage,
|
|
180090
180349
|
historyBudgetPercentage: config2.history_budget_percentage,
|
|
180091
180350
|
commitClusterTrigger: config2.commit_cluster_trigger,
|
|
180092
|
-
executeThresholdTokens: config2.execute_threshold_tokens
|
|
180351
|
+
executeThresholdTokens: config2.execute_threshold_tokens,
|
|
180352
|
+
dreamer: {
|
|
180353
|
+
enabled: config2.dreamer?.enabled ?? false,
|
|
180354
|
+
schedule: config2.dreamer?.schedule
|
|
180355
|
+
}
|
|
180093
180356
|
});
|
|
180094
180357
|
info("registered /ctx-status");
|
|
180095
180358
|
registerStatusLine(pi, { db, projectIdentity });
|
|
@@ -180111,7 +180374,10 @@ async function src_default2(pi) {
|
|
|
180111
180374
|
registerCtxDreamCommand(pi, {
|
|
180112
180375
|
db,
|
|
180113
180376
|
projectDir,
|
|
180114
|
-
projectIdentity
|
|
180377
|
+
projectIdentity,
|
|
180378
|
+
resolveProject: resolveCurrentProject,
|
|
180379
|
+
dreamerEnabled: config2.dreamer?.enabled ?? false,
|
|
180380
|
+
onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
|
|
180115
180381
|
});
|
|
180116
180382
|
info("registered /ctx-dream");
|
|
180117
180383
|
const dreamerConfig = resolveDreamerFromConfig(config2);
|
|
@@ -180123,7 +180389,8 @@ async function src_default2(pi) {
|
|
|
180123
180389
|
config: dreamerConfig,
|
|
180124
180390
|
embeddingConfig: config2.embedding,
|
|
180125
180391
|
memoryEnabled: config2.memory.enabled,
|
|
180126
|
-
gitCommitIndexing: config2.experimental.git_commit_indexing
|
|
180392
|
+
gitCommitIndexing: config2.experimental.git_commit_indexing,
|
|
180393
|
+
onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
|
|
180127
180394
|
});
|
|
180128
180395
|
info(dreamerConfig.enabled ? `registered dreamer (schedule=${dreamerConfig.schedule}, tasks=[${dreamerConfig.tasks.join(",")}])` : "registered dreamer: DISABLED (dreamer.enabled=false)");
|
|
180129
180396
|
} else {
|
|
@@ -180131,6 +180398,8 @@ async function src_default2(pi) {
|
|
|
180131
180398
|
}
|
|
180132
180399
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
180133
180400
|
try {
|
|
180401
|
+
const currentProject = resolveCurrentProject(ctx);
|
|
180402
|
+
seenDreamerProjectIdentities.add(currentProject.projectIdentity);
|
|
180134
180403
|
const sm = ctx.sessionManager;
|
|
180135
180404
|
let sessionId;
|
|
180136
180405
|
if (sm !== undefined) {
|
|
@@ -180143,10 +180412,20 @@ async function src_default2(pi) {
|
|
|
180143
180412
|
} catch {}
|
|
180144
180413
|
}
|
|
180145
180414
|
}
|
|
180415
|
+
if (sessionId) {
|
|
180416
|
+
trackSessionForProject(currentProject.projectIdentity, sessionId);
|
|
180417
|
+
}
|
|
180418
|
+
if (config2.system_prompt_injection?.enabled === false) {
|
|
180419
|
+
return;
|
|
180420
|
+
}
|
|
180421
|
+
const skipSigs = config2.system_prompt_injection?.skip_signatures ?? [];
|
|
180422
|
+
if (skipSigs.some((sig) => sig.length > 0 && event.systemPrompt.includes(sig))) {
|
|
180423
|
+
return;
|
|
180424
|
+
}
|
|
180146
180425
|
const isCacheBusting = sessionId ? hasSystemPromptRefresh(sessionId) : true;
|
|
180147
180426
|
const block = buildMagicContextBlock({
|
|
180148
180427
|
db,
|
|
180149
|
-
cwd:
|
|
180428
|
+
cwd: currentProject.projectDir,
|
|
180150
180429
|
sessionId,
|
|
180151
180430
|
memoryEnabled: config2.memory.enabled,
|
|
180152
180431
|
injectDocs: config2.dreamer?.inject_docs ?? true,
|
|
@@ -180160,7 +180439,8 @@ async function src_default2(pi) {
|
|
|
180160
180439
|
userMemoriesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.user_memories?.enabled ?? false),
|
|
180161
180440
|
pinKeyFilesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.pin_key_files?.enabled ?? false),
|
|
180162
180441
|
pinKeyFilesTokenBudget: config2.dreamer?.pin_key_files?.token_budget ?? 1e4,
|
|
180163
|
-
isCacheBusting
|
|
180442
|
+
isCacheBusting,
|
|
180443
|
+
existingSystemPrompt: event.systemPrompt
|
|
180164
180444
|
});
|
|
180165
180445
|
const composedPrompt = block ? `${event.systemPrompt}
|
|
180166
180446
|
|
|
@@ -180252,10 +180532,13 @@ ${block}` : event.systemPrompt;
|
|
|
180252
180532
|
const sessionId = sm?.getSessionId?.();
|
|
180253
180533
|
if (typeof sessionId !== "string" || sessionId.length === 0)
|
|
180254
180534
|
return;
|
|
180255
|
-
const
|
|
180256
|
-
|
|
180257
|
-
|
|
180258
|
-
scheduleIncrementalIndex(db, sessionId,
|
|
180535
|
+
const endedMsg = event.message;
|
|
180536
|
+
if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
|
|
180537
|
+
const messageId = endedMsg.id;
|
|
180538
|
+
scheduleIncrementalIndex(db, sessionId, messageId, () => {
|
|
180539
|
+
const rawMessages = readPiSessionMessages(ctx);
|
|
180540
|
+
return rawMessages.find((message) => message.id === messageId) ?? null;
|
|
180541
|
+
});
|
|
180259
180542
|
}
|
|
180260
180543
|
persistPiMessageEndModelMeta({
|
|
180261
180544
|
db,
|
|
@@ -180358,7 +180641,9 @@ ${block}` : event.systemPrompt;
|
|
|
180358
180641
|
warn("shutdown: dreamer drain threw:", err5);
|
|
180359
180642
|
}
|
|
180360
180643
|
try {
|
|
180361
|
-
|
|
180644
|
+
for (const identity of seenDreamerProjectIdentities) {
|
|
180645
|
+
unregisterPiDreamerProject({ projectIdentity: identity });
|
|
180646
|
+
}
|
|
180362
180647
|
} catch (err5) {
|
|
180363
180648
|
warn("shutdown: unregisterPiDreamerProject threw:", err5);
|
|
180364
180649
|
}
|