pi-smart-compact 10.2.0 → 10.3.0
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/ARCHITECTURE.md +30 -8
- package/CHANGELOG.md +74 -0
- package/assets/skills/context-management/SKILL.md +7 -5
- package/dist/app/context-attention.d.ts +9 -8
- package/dist/app/context-attention.d.ts.map +1 -1
- package/dist/app/context-operations.d.ts +1 -0
- package/dist/app/context-operations.d.ts.map +1 -1
- package/dist/app/host-cache-ledger.d.ts +8 -4
- package/dist/app/host-cache-ledger.d.ts.map +1 -1
- package/dist/app/native-compaction.d.ts.map +1 -1
- package/dist/app/register-navigation.d.ts +1 -1
- package/dist/app/register-navigation.d.ts.map +1 -1
- package/dist/app/register-smart-context-tool.d.ts +1 -1
- package/dist/app/register-smart-context-tool.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/domain/tool-semantics.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +193 -80
- package/dist/rtk.js +1 -1
- package/docs/configuration.md +23 -11
- package/docs/evaluation.md +6 -3
- package/docs/guide.md +74 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
|
-
var VERSION = "10.
|
|
8
|
+
var VERSION = "10.3.0";
|
|
9
9
|
var OPTIONAL_COMPONENTS = {
|
|
10
10
|
mnemopi: { name: "@oh-my-pi/pi-mnemopi", version: "18.3.1", enables: "Mnemopi memory store" },
|
|
11
11
|
bun: { name: "bun", version: "1.4.2", enables: "Mnemopi worker runtime when no Bun 1.3.14+ is on PATH" },
|
|
@@ -2157,6 +2157,8 @@ function extractToolPath(args) {
|
|
|
2157
2157
|
return;
|
|
2158
2158
|
}
|
|
2159
2159
|
function isInstructionSource(args) {
|
|
2160
|
+
if (typeof args.qualified_name === "string" && /(?:^|[./])(?:AGENTS|CLAUDE|GEMINI|SKILL|copilot-instructions|skills?)(?:[./]|$)/i.test(args.qualified_name))
|
|
2161
|
+
return true;
|
|
2160
2162
|
return PATH_KEYS.some((key) => {
|
|
2161
2163
|
const source = typeof args[key] === "string" ? args[key].replaceAll("\\", "/") : "";
|
|
2162
2164
|
return /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL|copilot-instructions)\.md$/i.test(source) || /(?:^|\/)skills?\//i.test(source);
|
|
@@ -2176,7 +2178,24 @@ var READ_ONLY_TOOLS = new Set([
|
|
|
2176
2178
|
"read_url_content",
|
|
2177
2179
|
"find_text_in_url_content",
|
|
2178
2180
|
"summarize_url_content",
|
|
2179
|
-
"get_search_content"
|
|
2181
|
+
"get_search_content",
|
|
2182
|
+
"fetch_content",
|
|
2183
|
+
"lens_diagnostics",
|
|
2184
|
+
"effective_config",
|
|
2185
|
+
"ast_grep_search",
|
|
2186
|
+
"search_graph",
|
|
2187
|
+
"trace_path",
|
|
2188
|
+
"get_code_snippet",
|
|
2189
|
+
"get_file_outline",
|
|
2190
|
+
"query_graph",
|
|
2191
|
+
"get_architecture",
|
|
2192
|
+
"search_code",
|
|
2193
|
+
"get_graph_schema",
|
|
2194
|
+
"index_status",
|
|
2195
|
+
"check_index_coverage",
|
|
2196
|
+
"detect_changes",
|
|
2197
|
+
"compare_graphs",
|
|
2198
|
+
"list_projects"
|
|
2180
2199
|
]);
|
|
2181
2200
|
function isReadOnlyResearchTool(toolName, input) {
|
|
2182
2201
|
if (isInstructionSource(input))
|
|
@@ -9304,6 +9323,7 @@ async function attemptNativeCompaction(rc, priorAttempts) {
|
|
|
9304
9323
|
};
|
|
9305
9324
|
}
|
|
9306
9325
|
const prefix = rc.msgs.slice(0, cut);
|
|
9326
|
+
const messages = prefix.map((entry) => entry.message);
|
|
9307
9327
|
const prefixTokens = prefix.reduce((sum, entry) => sum + rc.estimator.message(entry.message), 0);
|
|
9308
9328
|
const totalEstimate = rc.msgs.reduce((sum, entry) => sum + rc.estimator.message(entry.message), 0);
|
|
9309
9329
|
const retainedTailTokens = Math.max(0, totalEstimate - prefixTokens);
|
|
@@ -9337,9 +9357,11 @@ async function attemptNativeCompaction(rc, priorAttempts) {
|
|
|
9337
9357
|
nestedRequests++;
|
|
9338
9358
|
try {
|
|
9339
9359
|
const stream = rc.ctx.modelRegistry.streamSimple(model, {
|
|
9340
|
-
|
|
9341
|
-
messages
|
|
9342
|
-
|
|
9360
|
+
messages,
|
|
9361
|
+
...messages[0]?.role === "system" ? {} : {
|
|
9362
|
+
systemPrompt: rc.ctx.getSystemPrompt(),
|
|
9363
|
+
tools: toolSource()
|
|
9364
|
+
}
|
|
9343
9365
|
}, {
|
|
9344
9366
|
signal: rc.cancellation.signal,
|
|
9345
9367
|
fetch: wrapper.fetch,
|
|
@@ -13232,7 +13254,7 @@ function eligible(event) {
|
|
|
13232
13254
|
if (event.isError || !event.content.length || event.content.some((block) => block.type !== "text") || Array.isArray(event.details) || event.details !== undefined && event.details !== null && !isRecord2(event.details) || isRecord2(event.details) && Object.hasOwn(event.details, ARTIFACT_DETAILS_KEY))
|
|
13233
13255
|
return false;
|
|
13234
13256
|
const tool = normalizeToolName(event.toolName);
|
|
13235
|
-
if (["read", "read_symbol", "read_enclosing", "smart_context"].includes(tool) || !isReadOnlyResearchTool(tool, event.input))
|
|
13257
|
+
if (["read", "read_symbol", "read_enclosing", "get_code_snippet", "smart_context"].includes(tool) || !isReadOnlyResearchTool(tool, event.input))
|
|
13236
13258
|
return false;
|
|
13237
13259
|
return true;
|
|
13238
13260
|
}
|
|
@@ -13310,6 +13332,7 @@ function branchToolArtifacts(branch, ownedReferences) {
|
|
|
13310
13332
|
// src/app/context-operations.ts
|
|
13311
13333
|
var CONTEXT_CONTROL_TYPE = "smart-compact-context";
|
|
13312
13334
|
var CONTEXT_REPORT_TYPE = "smart-compact-rewind";
|
|
13335
|
+
var ATTENTION_CUSTOM_TYPE = "smart-compact-attention";
|
|
13313
13336
|
var MAX_CONTEXT_EDITS = 512;
|
|
13314
13337
|
var MAX_TRIM_EDITS = 32;
|
|
13315
13338
|
var MIN_TRIM_CHARS = 4096;
|
|
@@ -13659,8 +13682,8 @@ function planContextRewind(branch, sessionId, checkpointId, report, readerApi) {
|
|
|
13659
13682
|
if (!state.checkpoint || state.checkpoint.id !== checkpointId) {
|
|
13660
13683
|
throw new Error(state.invalidReason ?? "No matching active checkpoint; create one before research.");
|
|
13661
13684
|
}
|
|
13662
|
-
const
|
|
13663
|
-
const targets =
|
|
13685
|
+
const research = removableResearch(branch);
|
|
13686
|
+
const targets = branch.slice(state.checkpointIndex + 1).filter((entry) => research.has(entry.id) || entry.type === "custom_message" && entry.customType === ATTENTION_CUSTOM_TYPE).map((entry) => entry.id);
|
|
13664
13687
|
if (targets.length > MAX_CONTEXT_EDITS)
|
|
13665
13688
|
throw new Error("Research exceeds the rewind edit limit; compact instead.");
|
|
13666
13689
|
const targetSet = new Set(targets);
|
|
@@ -16808,8 +16831,8 @@ function createSettledAutoTrigger(options = {}) {
|
|
|
16808
16831
|
}
|
|
16809
16832
|
|
|
16810
16833
|
// src/app/context-attention.ts
|
|
16811
|
-
var ATTENTION_CUSTOM_TYPE = "smart-compact-attention";
|
|
16812
16834
|
var CONTEXT_TOOL_NAME = "smart_context";
|
|
16835
|
+
var COMPACTION_TOOL_NAME = "smart_compact";
|
|
16813
16836
|
var LOADER_TOOL_NAME = "smart_tools";
|
|
16814
16837
|
function registerContextAttention(pi, deps) {
|
|
16815
16838
|
let noted = null;
|
|
@@ -16820,12 +16843,12 @@ function registerContextAttention(pi, deps) {
|
|
|
16820
16843
|
pi.on("session_before_switch", clear);
|
|
16821
16844
|
pi.on("session_before_fork", clear);
|
|
16822
16845
|
pi.on("session_tree", clear);
|
|
16823
|
-
pi.on("
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16846
|
+
pi.on("session_compact", clear);
|
|
16847
|
+
pi.on("model_select", clear);
|
|
16848
|
+
pi.on("session_shutdown", clear);
|
|
16849
|
+
const attention = (ctx) => {
|
|
16827
16850
|
const settings = deps.config();
|
|
16828
|
-
if (!settings.
|
|
16851
|
+
if (!settings.contextGuidanceEnabled || settings.toolLoading === "off")
|
|
16829
16852
|
return;
|
|
16830
16853
|
const sessionId = resolveSessionId(ctx);
|
|
16831
16854
|
const pressure = contextPressure(ctx, settings);
|
|
@@ -16837,51 +16860,84 @@ function registerContextAttention(pi, deps) {
|
|
|
16837
16860
|
}
|
|
16838
16861
|
if (noted?.sessionId === sessionId && (noted.band === band || noted.band === "compaction"))
|
|
16839
16862
|
return;
|
|
16840
|
-
if (message.content.some((block) => block.type === "toolCall" && (block.name === NAVIGATION_TOOL_NAME || block.name === CONTEXT_TOOL_NAME)))
|
|
16841
|
-
return;
|
|
16842
|
-
const midTurn = message.stopReason === "toolUse";
|
|
16843
|
-
if (!midTurn && band === "compaction" && settings.autoTrigger)
|
|
16844
|
-
return;
|
|
16845
16863
|
if (!deps.canAgentAct(ctx))
|
|
16846
16864
|
return;
|
|
16847
16865
|
const active = new Set(pi.getActiveTools());
|
|
16848
|
-
const content = renderAttention(band, pressure.percent, settings, ctx, active, deps.reachable);
|
|
16866
|
+
const content = renderAttention(band, pressure.percent, settings, ctx, active, deps.reachable, deps.canCleanup(ctx));
|
|
16849
16867
|
if (!content)
|
|
16850
16868
|
return;
|
|
16851
16869
|
noted = { sessionId, band };
|
|
16852
|
-
|
|
16870
|
+
return { customType: ATTENTION_CUSTOM_TYPE, content, display: true, details: { band, percent: pressure.percent } };
|
|
16871
|
+
};
|
|
16872
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
16873
|
+
const message = attention(ctx);
|
|
16874
|
+
if (message)
|
|
16875
|
+
return { message };
|
|
16876
|
+
});
|
|
16877
|
+
pi.on("message_end", (event, ctx) => {
|
|
16878
|
+
const message = event.message;
|
|
16879
|
+
if (message.role !== "assistant" || message.stopReason !== "toolUse")
|
|
16880
|
+
return;
|
|
16881
|
+
if (message.content.some((block) => block.type === "toolCall" && [NAVIGATION_TOOL_NAME, CONTEXT_TOOL_NAME, COMPACTION_TOOL_NAME].includes(block.name)))
|
|
16882
|
+
return;
|
|
16883
|
+
const note = attention(ctx);
|
|
16884
|
+
if (note)
|
|
16885
|
+
pi.sendMessage(note, { deliverAs: "steer" });
|
|
16853
16886
|
});
|
|
16854
16887
|
}
|
|
16855
|
-
function renderAttention(band, percent, settings, ctx, active, reachable) {
|
|
16888
|
+
function renderAttention(band, percent, settings, ctx, active, reachable, canCleanup) {
|
|
16856
16889
|
const lines = [];
|
|
16857
16890
|
const load = (group, tool) => active.has(tool) ? "" : reachable(group) && active.has(LOADER_TOOL_NAME) ? ` Load it first: ${LOADER_TOOL_NAME} load ${group}.` : null;
|
|
16858
|
-
const navigation = load("navigation", NAVIGATION_TOOL_NAME);
|
|
16891
|
+
const navigation = settings.contextNavigationEnabled ? load("navigation", NAVIGATION_TOOL_NAME) : null;
|
|
16859
16892
|
const history = load("history", CONTEXT_TOOL_NAME);
|
|
16860
|
-
|
|
16861
|
-
|
|
16862
|
-
}
|
|
16893
|
+
let checkpointLabel;
|
|
16894
|
+
let trimmable = 0;
|
|
16863
16895
|
if (history !== null) {
|
|
16864
|
-
const branch = ctx.sessionManager.getBranch();
|
|
16865
|
-
let trimmable = 0;
|
|
16866
|
-
let blocked = false;
|
|
16867
16896
|
try {
|
|
16868
|
-
const
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16897
|
+
const branch = ctx.sessionManager.getBranch();
|
|
16898
|
+
const checkpoint = inspectContext(branch, resolveSessionId(ctx)).checkpoint;
|
|
16899
|
+
checkpointLabel = checkpoint?.label;
|
|
16900
|
+
if (canCleanup && !checkpoint) {
|
|
16901
|
+
const plan = planContextTrim(branch, undefined, { readerApi: ctx.model?.api });
|
|
16902
|
+
if (!plan.blockedReason)
|
|
16903
|
+
trimmable = plan.entries.filter((entry) => entry.type === "context_edit").length;
|
|
16904
|
+
}
|
|
16905
|
+
} catch {}
|
|
16906
|
+
}
|
|
16907
|
+
if (navigation !== null && checkpointLabel === undefined) {
|
|
16908
|
+
lines.push(`- Finishing a unit of work? ${NAVIGATION_TOOL_NAME} anchor with a concise handoff (done / in progress / next step). An anchor alone does not reduce context; cleanup depends on safety and preparation gates.${navigation}`);
|
|
16909
|
+
}
|
|
16910
|
+
if (history !== null) {
|
|
16911
|
+
if (checkpointLabel !== undefined) {
|
|
16912
|
+
const next = canCleanup ? `Finish with ${CONTEXT_TOOL_NAME} rewind(report) before implementation or your final answer. No pressure required; safety checks still apply.` : "Prepared/running compaction or unavailable recovery takes priority. Preserve findings and check status; do not replace the checkpoint or retry in a loop.";
|
|
16913
|
+
lines.push(`- Research checkpoint ${JSON.stringify(checkpointLabel)} is active. ${next}${history}`);
|
|
16914
|
+
} else {
|
|
16875
16915
|
if (trimmable > 0)
|
|
16876
16916
|
lines.push(`- ${CONTEXT_TOOL_NAME} trim: ${trimmable} archived tool output${trimmable === 1 ? "" : "s"} eligible now.${history}`);
|
|
16877
|
-
lines.push(`- Starting a large read-only detour? ${CONTEXT_TOOL_NAME} checkpoint first
|
|
16917
|
+
lines.push(`- Starting a large read-only detour? ${CONTEXT_TOOL_NAME} checkpoint first; rewind(report) afterward before implementation or your final answer. No pressure required; safety checks still apply. Keep findings and evidence in the report.${trimmable > 0 ? "" : history}`);
|
|
16878
16918
|
}
|
|
16879
16919
|
}
|
|
16920
|
+
if (band === "compaction" && !settings.autoTrigger) {
|
|
16921
|
+
const compaction = load("compaction", COMPACTION_TOOL_NAME);
|
|
16922
|
+
if (compaction !== null)
|
|
16923
|
+
lines.push(`- ${COMPACTION_TOOL_NAME} stages a summary (or reports an existing one); the user runs /compact within its staging TTL to apply it. Staging does not shrink context.${compaction}`);
|
|
16924
|
+
}
|
|
16880
16925
|
if (!lines.length)
|
|
16881
16926
|
return null;
|
|
16882
16927
|
const where = percent === null ? "" : ` ${Math.round(percent)}% of the policy window`;
|
|
16883
|
-
|
|
16884
|
-
|
|
16928
|
+
let automatic;
|
|
16929
|
+
if (!settings.autoTrigger)
|
|
16930
|
+
automatic = "Automatic compaction is off here; the user can run /smart-compact.";
|
|
16931
|
+
else if (settings.autoTriggerStrategy === "native-hook")
|
|
16932
|
+
automatic = "Preparation runs when Pi requests compaction; this strategy has no settled trigger.";
|
|
16933
|
+
else if (band === "compaction")
|
|
16934
|
+
automatic = "Automatic compaction is requested when the turn settles if pressure remains.";
|
|
16935
|
+
else {
|
|
16936
|
+
automatic = `Automatic compaction is requested at ${settings.minContextPercent}% when the turn settles.`;
|
|
16937
|
+
if (settings.autoTriggerStrategy === "background")
|
|
16938
|
+
automatic = "Background preparation can start in the cleanup band. " + automatic;
|
|
16939
|
+
}
|
|
16940
|
+
return `Context attention:${where} (${band} band). ${automatic}
|
|
16885
16941
|
${lines.join(`
|
|
16886
16942
|
`)}`;
|
|
16887
16943
|
}
|
|
@@ -16890,19 +16946,26 @@ ${lines.join(`
|
|
|
16890
16946
|
var FOREIGN_REBUILD_WARN_COUNT = 3;
|
|
16891
16947
|
var num = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
16892
16948
|
var tallies = (keys) => Object.fromEntries(keys.map((key) => [key, { count: 0, uncached: 0 }]));
|
|
16893
|
-
|
|
16949
|
+
function cacheLifetimeMs(usage, promptCache) {
|
|
16950
|
+
const tiers = [promptCache?.short, promptCache?.long].filter((value) => value !== undefined);
|
|
16951
|
+
if (!tiers.length || tiers.some((value) => !Number.isFinite(value) || value <= 0 || !Number.isFinite(value * 1000)))
|
|
16952
|
+
return null;
|
|
16953
|
+
return Math.max(...tiers.map((value) => value * 1000), num(usage?.cacheWrite1h) > 0 ? ONE_HOUR_MS : 0);
|
|
16954
|
+
}
|
|
16894
16955
|
function createHostCacheLedger() {
|
|
16895
16956
|
let sessionId = null;
|
|
16896
16957
|
let summary;
|
|
16897
16958
|
let previousAt;
|
|
16898
16959
|
let warmedAt;
|
|
16899
|
-
let lifetimeMs =
|
|
16960
|
+
let lifetimeMs = null;
|
|
16961
|
+
let previousRoute;
|
|
16900
16962
|
let pendingEdit;
|
|
16901
16963
|
const reset = (id) => {
|
|
16902
16964
|
sessionId = id;
|
|
16903
16965
|
previousAt = undefined;
|
|
16904
16966
|
warmedAt = undefined;
|
|
16905
|
-
lifetimeMs =
|
|
16967
|
+
lifetimeMs = null;
|
|
16968
|
+
previousRoute = undefined;
|
|
16906
16969
|
pendingEdit = undefined;
|
|
16907
16970
|
summary = {
|
|
16908
16971
|
sessionId: id,
|
|
@@ -16925,7 +16988,7 @@ function createHostCacheLedger() {
|
|
|
16925
16988
|
noteCacheWarm(at) {
|
|
16926
16989
|
warmedAt = Math.max(warmedAt ?? at, at);
|
|
16927
16990
|
},
|
|
16928
|
-
observe(message, at) {
|
|
16991
|
+
observe(message, at, promptCache) {
|
|
16929
16992
|
const usage = message.usage;
|
|
16930
16993
|
if (!usage || typeof usage.input !== "number" || !Number.isFinite(usage.input))
|
|
16931
16994
|
return null;
|
|
@@ -16946,12 +17009,15 @@ function createHostCacheLedger() {
|
|
|
16946
17009
|
cacheRead,
|
|
16947
17010
|
rebuild: false
|
|
16948
17011
|
};
|
|
16949
|
-
|
|
17012
|
+
const route = JSON.stringify([message.provider, message.model]);
|
|
17013
|
+
const sameRoute = route === previousRoute;
|
|
17014
|
+
const currentLifetime = cacheLifetimeMs(usage, promptCache);
|
|
17015
|
+
if (previousAt !== undefined && sameRoute) {
|
|
16950
17016
|
entry.gapMs = Math.max(0, time - previousAt);
|
|
16951
17017
|
if (uncached >= Math.max(REBUILD_MIN_TOKENS, 0.5 * prompt)) {
|
|
16952
17018
|
entry.rebuild = true;
|
|
16953
17019
|
const alive = Math.max(previousAt, warmedAt ?? previousAt);
|
|
16954
|
-
entry.cause = pendingEdit ? "continuity" : time - alive > lifetimeMs ? "idle-expiry" : "foreign";
|
|
17020
|
+
entry.cause = pendingEdit ? "continuity" : lifetimeMs !== null && currentLifetime !== null && time - alive > Math.max(lifetimeMs, currentLifetime) ? "idle-expiry" : "foreign";
|
|
16955
17021
|
const cause = summary.rebuilds[entry.cause];
|
|
16956
17022
|
cause.count++;
|
|
16957
17023
|
cause.uncached += uncached;
|
|
@@ -16975,8 +17041,8 @@ function createHostCacheLedger() {
|
|
|
16975
17041
|
if (entry.rebuild)
|
|
16976
17042
|
summary.cost.rebuildUncached += num(cost.input) + num(cost.cacheWrite);
|
|
16977
17043
|
}
|
|
16978
|
-
|
|
16979
|
-
|
|
17044
|
+
lifetimeMs = sameRoute && lifetimeMs !== null && currentLifetime !== null ? Math.max(lifetimeMs, currentLifetime) : currentLifetime;
|
|
17045
|
+
previousRoute = route;
|
|
16980
17046
|
previousAt = time;
|
|
16981
17047
|
warmedAt = undefined;
|
|
16982
17048
|
pendingEdit = undefined;
|
|
@@ -16993,7 +17059,7 @@ function formatCacheLedgerSummary(summary) {
|
|
|
16993
17059
|
const readPercent = prompt ? Math.round(summary.cacheRead / prompt * 100) : 0;
|
|
16994
17060
|
const { continuity, "idle-expiry": idle, foreign } = summary.rebuilds;
|
|
16995
17061
|
const total = continuity.count + idle.count + foreign.count;
|
|
16996
|
-
const lines = [`Host prompt cache: ${summary.requests} requests \xB7 ${readPercent}% of prompt tokens read from cache \xB7 ${total} rebuilds`];
|
|
17062
|
+
const lines = [`Host prompt cache: ${summary.requests} requests \xB7 ${readPercent}% of prompt tokens read from cache \xB7 ${total} estimated rebuilds`];
|
|
16997
17063
|
if (!total)
|
|
16998
17064
|
return lines;
|
|
16999
17065
|
const parts = [];
|
|
@@ -17002,10 +17068,10 @@ function formatCacheLedgerSummary(summary) {
|
|
|
17002
17068
|
parts.push(`${continuity.count} after Continuity edits (${kinds}): ${tokens(continuity.uncached)} uncached`);
|
|
17003
17069
|
}
|
|
17004
17070
|
if (idle.count)
|
|
17005
|
-
parts.push(`${idle.count} idle-expiry: ${tokens(idle.uncached)} uncached`);
|
|
17071
|
+
parts.push(`${idle.count} possible idle-expiry: ${tokens(idle.uncached)} uncached`);
|
|
17006
17072
|
if (foreign.count)
|
|
17007
|
-
parts.push(`${foreign.count} without a Continuity edit: ${tokens(foreign.uncached)} uncached`);
|
|
17008
|
-
lines.push("
|
|
17073
|
+
parts.push(`${foreign.count} without a Continuity edit (cause unknown): ${tokens(foreign.uncached)} uncached`);
|
|
17074
|
+
lines.push("Rebuild estimates: " + parts.join(" \xB7 "));
|
|
17009
17075
|
if (summary.cost.total > 0)
|
|
17010
17076
|
lines.push(`Cost (Pi model pricing): $${summary.cost.total.toFixed(4)} total \xB7 $${summary.cost.rebuildUncached.toFixed(4)} uncached input+cache writes on rebuilds`);
|
|
17011
17077
|
return lines;
|
|
@@ -19277,24 +19343,31 @@ async function loadLineage(ctx, options = {}) {
|
|
|
19277
19343
|
// src/app/register-smart-context-tool.ts
|
|
19278
19344
|
var TOOL_NAME = "smart_context";
|
|
19279
19345
|
var MAX_REPORT_CHARS = 8000;
|
|
19346
|
+
var ACTIVE_CHECKPOINT_ERROR = "A checkpoint is already active. Finish it with rewind(report) before starting another.";
|
|
19280
19347
|
function formatDeferredTrim(deferred) {
|
|
19281
19348
|
const tokens = (value) => value >= 1000 ? `${Math.round(value / 1000)}k` : String(value);
|
|
19282
19349
|
const why = deferred.breakEvenRequests === null ? "the model's cache price is unknown" : `it pays back its cache rewrite only after ${Math.ceil(deferred.breakEvenRequests)} requests (limit ${AUTO_TRIM_BREAK_EVEN_REQUESTS})`;
|
|
19283
19350
|
const warming = deferred.warmingStopped ? " Prompt-cache warming was stopped so the cache can go cold." : "";
|
|
19284
19351
|
return `Saves ~${tokens(deferred.savedTokens)} tokens, but ${why}, so it waits for a cold prompt cache.${warming}`;
|
|
19285
19352
|
}
|
|
19286
|
-
function cachedPrefix(branch) {
|
|
19353
|
+
function cachedPrefix(branch, model) {
|
|
19354
|
+
let lifetimeMs = cacheLifetimeMs(undefined, model?.promptCache);
|
|
19355
|
+
if (!model || lifetimeMs === null)
|
|
19356
|
+
return null;
|
|
19287
19357
|
let since;
|
|
19288
19358
|
for (let index = branch.length - 1;index >= 0; index--) {
|
|
19289
19359
|
const entry = branch[index];
|
|
19360
|
+
if (entry.type === "compaction" || entry.type === "context_edit" || entry.type === "branch_summary")
|
|
19361
|
+
break;
|
|
19290
19362
|
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
19291
19363
|
continue;
|
|
19292
19364
|
const message = entry.message;
|
|
19365
|
+
if (message.provider !== model.provider || message.model !== model.id)
|
|
19366
|
+
break;
|
|
19293
19367
|
since ??= message.timestamp;
|
|
19294
|
-
|
|
19295
|
-
return { since, lifetimeMs: cacheLifetimeMs(message.usage) };
|
|
19368
|
+
lifetimeMs = Math.max(lifetimeMs, cacheLifetimeMs(message.usage, model.promptCache) ?? lifetimeMs);
|
|
19296
19369
|
}
|
|
19297
|
-
return since === undefined ? null : { since, lifetimeMs
|
|
19370
|
+
return since === undefined || !Number.isFinite(since) ? null : { since, lifetimeMs };
|
|
19298
19371
|
}
|
|
19299
19372
|
function unchangedSince(branch, leafId) {
|
|
19300
19373
|
const leaf = branch.findIndex((entry) => entry.id === leafId);
|
|
@@ -19334,11 +19407,11 @@ function registerSmartContextTool(pi, options = {}) {
|
|
|
19334
19407
|
pi.registerTool({
|
|
19335
19408
|
name: TOOL_NAME,
|
|
19336
19409
|
label: "Session Context",
|
|
19337
|
-
description: "
|
|
19410
|
+
description: "For bounded read-only research: checkpoint before exploring, then rewind(report) before implementation or your final answer. Rewind replaces safe exploration with your findings; no pressure required, one active checkpoint. status shows gates; plan/trim follow cleanup policy; search/read recover archived evidence (scope=lineage includes parents). Changes apply after the batch; safety guards remain, files/processes are never rolled back.",
|
|
19338
19411
|
parameters: Type4.Object({
|
|
19339
19412
|
action: StringEnum2(["status", "plan", "checkpoint", "rewind", "trim", "read", "search"]),
|
|
19340
19413
|
label: Type4.Optional(Type4.String({ maxLength: 120, description: "Checkpoint label." })),
|
|
19341
|
-
report: Type4.Optional(Type4.String({ minLength: 1, maxLength: MAX_REPORT_CHARS, description: "Rewind: findings, decisions,
|
|
19414
|
+
report: Type4.Optional(Type4.String({ minLength: 1, maxLength: MAX_REPORT_CHARS, description: "Rewind handoff: findings and evidence paths, constraints/decisions, failed attempts, next step." })),
|
|
19342
19415
|
id: Type4.Optional(Type4.String({ minLength: 1, maxLength: 128, description: "Source ID." })),
|
|
19343
19416
|
query: Type4.Optional(Type4.String({ minLength: 1, maxLength: 200, description: "Literal text." })),
|
|
19344
19417
|
line: Type4.Optional(Type4.Integer({ minimum: 1, description: "Read from line." })),
|
|
@@ -19460,7 +19533,7 @@ ${page.text}`);
|
|
|
19460
19533
|
if (options.canAgentMutate?.(ctx) === false) {
|
|
19461
19534
|
throw new Error("Agent-requested context changes are disabled by policy; status, plan, search and read stay available.");
|
|
19462
19535
|
}
|
|
19463
|
-
if (params.action
|
|
19536
|
+
if (params.action === "trim" && config().contextPressureOnly && !contextPressure(ctx, config()).cleanup) {
|
|
19464
19537
|
throw new Error("No context pressure (or usage is unavailable); history left unchanged. Check status. Human commands can request early cleanup.");
|
|
19465
19538
|
}
|
|
19466
19539
|
if (params.action !== "checkpoint" && options.canAutoTrim?.(ctx) === false) {
|
|
@@ -19470,6 +19543,8 @@ ${page.text}`);
|
|
|
19470
19543
|
throw new Error("Context changes are paused while a navigation pivot is pending.");
|
|
19471
19544
|
if (queued)
|
|
19472
19545
|
throw new Error("A context change is already queued; wait for the next turn boundary.");
|
|
19546
|
+
if (params.action === "checkpoint" && state.checkpoint)
|
|
19547
|
+
throw new Error(ACTIVE_CHECKPOINT_ERROR);
|
|
19473
19548
|
if (params.action === "rewind" && !state.checkpoint)
|
|
19474
19549
|
throw new Error(state.invalidReason ?? "No active checkpoint.");
|
|
19475
19550
|
const report = params.report?.trim() ?? "";
|
|
@@ -19492,7 +19567,8 @@ ${page.text}`);
|
|
|
19492
19567
|
text: params.action === "rewind" ? text : text.slice(0, 120),
|
|
19493
19568
|
signal
|
|
19494
19569
|
};
|
|
19495
|
-
|
|
19570
|
+
const next = params.action === "checkpoint" ? "Explore read-only, then call rewind(report) with findings, evidence, constraints and the next step before implementation or your final answer. Do not nest checkpoints. No pressure required." : params.action === "rewind" ? "Stop research here. After the batch, continue the original task from the retained report. Files and processes are not reverted." : "Use status to inspect the committed state.";
|
|
19571
|
+
return reply(`${params.action} queued for the completed tool batch. ${next}`, {
|
|
19496
19572
|
display: { state: "queued", action: params.action, label: text.slice(0, 120) }
|
|
19497
19573
|
});
|
|
19498
19574
|
},
|
|
@@ -19558,7 +19634,7 @@ ${page.text}`);
|
|
|
19558
19634
|
mark = null;
|
|
19559
19635
|
return;
|
|
19560
19636
|
}
|
|
19561
|
-
const prefix = cachedPrefix(branch);
|
|
19637
|
+
const prefix = cachedPrefix(branch, ctx.model);
|
|
19562
19638
|
const since = Math.max(prefix?.since ?? 0, warm?.sessionId === sessionId ? warm.at : 0);
|
|
19563
19639
|
if (!prefix || now() - since <= prefix.lifetimeMs)
|
|
19564
19640
|
return;
|
|
@@ -19579,7 +19655,7 @@ ${page.text}`);
|
|
|
19579
19655
|
return changed ? { messages } : undefined;
|
|
19580
19656
|
};
|
|
19581
19657
|
const vetoWarming = (event, ctx, sessionId) => {
|
|
19582
|
-
if (config().contextPressureOnly || mark?.sessionId !== sessionId || !unchangedSince(ctx.sessionManager.getBranch(), mark.leafId) || thinkingEditReason(ctx.sessionManager.getBranch(), mark.entries, ctx.model?.api))
|
|
19658
|
+
if (config().contextPressureOnly || mark?.sessionId !== sessionId || !cachedPrefix(ctx.sessionManager.getBranch(), ctx.model) || !unchangedSince(ctx.sessionManager.getBranch(), mark.leafId) || thinkingEditReason(ctx.sessionManager.getBranch(), mark.entries, ctx.model?.api))
|
|
19583
19659
|
return false;
|
|
19584
19660
|
const cost = ctx.model?.cost;
|
|
19585
19661
|
const price = cost ? cost.cacheWrite > 0 ? cost.cacheWrite : cost.input : NaN;
|
|
@@ -19613,7 +19689,7 @@ ${page.text}`);
|
|
|
19613
19689
|
}
|
|
19614
19690
|
const settings = config();
|
|
19615
19691
|
const pressure = contextPressure(ctx, settings).cleanup;
|
|
19616
|
-
if (request && !request.manual && request.action
|
|
19692
|
+
if (request && !request.manual && !request.userConfirmed && request.action === "trim" && settings.contextPressureOnly && !pressure) {
|
|
19617
19693
|
return cancelled(event, "Context pressure cleared; history left unchanged.");
|
|
19618
19694
|
}
|
|
19619
19695
|
if (!request) {
|
|
@@ -19647,6 +19723,8 @@ ${page.text}`);
|
|
|
19647
19723
|
let entries;
|
|
19648
19724
|
try {
|
|
19649
19725
|
if (request?.action === "checkpoint") {
|
|
19726
|
+
if (state.checkpoint)
|
|
19727
|
+
throw new Error(ACTIVE_CHECKPOINT_ERROR);
|
|
19650
19728
|
entries = [contextControlEntry({
|
|
19651
19729
|
version: 1,
|
|
19652
19730
|
action: "checkpoint",
|
|
@@ -19669,7 +19747,7 @@ ${page.text}`);
|
|
|
19669
19747
|
if (plan.blockedReason)
|
|
19670
19748
|
return request ? cancelled(event, plan.blockedReason) : undefined;
|
|
19671
19749
|
if (request) {
|
|
19672
|
-
entries = trimEntries(plan, request.manual ? "manual" : "agent");
|
|
19750
|
+
entries = trimEntries(plan, request.manual || request.userConfirmed ? "manual" : "agent");
|
|
19673
19751
|
} else {
|
|
19674
19752
|
mark = null;
|
|
19675
19753
|
if (plan.automatic !== "ready")
|
|
@@ -19709,6 +19787,7 @@ ${page.text}`);
|
|
|
19709
19787
|
return { entries: [...event.entries, ...entries] };
|
|
19710
19788
|
});
|
|
19711
19789
|
const requestTrim = (ctx, anchor) => {
|
|
19790
|
+
const userConfirmed = anchor?.userConfirmed === true;
|
|
19712
19791
|
const sessionId = resolveSessionId(ctx);
|
|
19713
19792
|
if (isUnresolvedSessionId(sessionId)) {
|
|
19714
19793
|
return { state: "unavailable", notice: "Context control needs an identifiable session." };
|
|
@@ -19722,7 +19801,7 @@ ${page.text}`);
|
|
|
19722
19801
|
if (anchor && options.canAutoTrim?.(ctx) === false) {
|
|
19723
19802
|
return { state: "busy", notice: "Cleanup deferred: prepared/running compaction or unavailable recovery tools take priority." };
|
|
19724
19803
|
}
|
|
19725
|
-
if (anchor?.callId && (options.canAgentMutate?.(ctx) === false || config().contextPressureOnly && !contextPressure(ctx, config()).cleanup)) {
|
|
19804
|
+
if (anchor?.callId && (options.canAgentMutate?.(ctx) === false || !userConfirmed && config().contextPressureOnly && !contextPressure(ctx, config()).cleanup)) {
|
|
19726
19805
|
return { state: "unavailable", notice: "Cleanup not requested: no context pressure or agent changes are disabled." };
|
|
19727
19806
|
}
|
|
19728
19807
|
const branch = ctx.sessionManager.getBranch();
|
|
@@ -19731,7 +19810,7 @@ ${page.text}`);
|
|
|
19731
19810
|
return { state: "unavailable", notice: "Anchor origin is no longer on this branch." };
|
|
19732
19811
|
const anchorBoundary = anchor ? lastAnchorBoundary(branch.slice(0, origin + 1)) ?? null : undefined;
|
|
19733
19812
|
const plan = planContextTrim(branch, inspectContext(branch, sessionId).checkpoint?.originId, { anchorBoundary, readerApi: ctx.model?.api });
|
|
19734
|
-
if (!plan.entries.length || anchor?.callId && plan.automatic !== "ready") {
|
|
19813
|
+
if (!plan.entries.length || anchor?.callId && !userConfirmed && plan.automatic !== "ready") {
|
|
19735
19814
|
return { state: "no-eligible", notice: plan.blockedReason ?? (anchor ? "No safe cleanup batch ready; recent turns and protected prefixes stay unchanged." : "No eligible archived output to trim.") };
|
|
19736
19815
|
}
|
|
19737
19816
|
mark = null;
|
|
@@ -19743,17 +19822,18 @@ ${page.text}`);
|
|
|
19743
19822
|
checkpointId: "",
|
|
19744
19823
|
text: "",
|
|
19745
19824
|
manual: !anchor?.callId,
|
|
19825
|
+
userConfirmed,
|
|
19746
19826
|
anchorBoundary,
|
|
19747
19827
|
signal: anchor?.signal
|
|
19748
19828
|
};
|
|
19749
19829
|
return {
|
|
19750
19830
|
state: "queued",
|
|
19751
|
-
notice: anchor?.callId ? "Anchor cleanup queued for the completed tool batch; prior anchor prefixes and recent turns stay protected. Use status to confirm the committed state
|
|
19831
|
+
notice: anchor?.callId ? `${userConfirmed ? "User-confirmed anchor" : "Anchor"} cleanup queued for the completed tool batch; prior anchor prefixes and recent turns stay protected. Use status to confirm the committed state.` : "Manual trim queued. The first next provider request is not yet trimmed; the change applies at the next completed turn boundary. A pending navigation pivot or newer boundary change cancels it."
|
|
19752
19832
|
};
|
|
19753
19833
|
};
|
|
19754
19834
|
return {
|
|
19755
19835
|
requestManualTrim: (ctx) => requestTrim(ctx),
|
|
19756
|
-
requestAnchorTrim: (ctx, originId, callId, signal) => requestTrim(ctx, { originId, callId, signal }),
|
|
19836
|
+
requestAnchorTrim: (ctx, originId, callId, signal, userConfirmed) => requestTrim(ctx, { originId, callId, signal, userConfirmed }),
|
|
19757
19837
|
deferredTrim: deferred
|
|
19758
19838
|
};
|
|
19759
19839
|
}
|
|
@@ -21178,7 +21258,7 @@ var CATEGORIES = [
|
|
|
21178
21258
|
setting: {
|
|
21179
21259
|
id: "contextPressureOnly",
|
|
21180
21260
|
label: "Cleanup timing",
|
|
21181
|
-
description: "Pressure only (default)
|
|
21261
|
+
description: "Pressure only (default) gates automatic cleanup and autonomous agent trim/anchor requests. A host-confirmed anchor can request one early cleanup without changing this setting. Checkpoint/rewind research is pressure-independent. Economic opts into early cleanup. Safety checks always apply.",
|
|
21182
21262
|
values: BOOLEAN_VALUES,
|
|
21183
21263
|
labels: { true: "Pressure only", false: "Economic (opt-in)" }
|
|
21184
21264
|
}
|
|
@@ -24298,6 +24378,11 @@ function registerNavigation(pi, options = {}) {
|
|
|
24298
24378
|
let queued;
|
|
24299
24379
|
let applying;
|
|
24300
24380
|
let timer;
|
|
24381
|
+
let epoch = 0;
|
|
24382
|
+
const agentAllowed = () => {
|
|
24383
|
+
const settings = config();
|
|
24384
|
+
return settings.toolLoading !== "off" && pi.getActiveTools().includes(NAVIGATION_TOOL_NAME);
|
|
24385
|
+
};
|
|
24301
24386
|
const scrub = (text) => {
|
|
24302
24387
|
const settings = config();
|
|
24303
24388
|
return new SecretScrubber(settings.scrubSecrets, settings.scrubPii).scrubText(text).value;
|
|
@@ -24418,7 +24503,7 @@ function registerNavigation(pi, options = {}) {
|
|
|
24418
24503
|
pi.registerTool({
|
|
24419
24504
|
name: NAVIGATION_TOOL_NAME,
|
|
24420
24505
|
label: "Context Navigation",
|
|
24421
|
-
description: "View/recall session anchors.
|
|
24506
|
+
description: "View/recall session anchors. Anchor completed work under pressure. In pressure-only mode, an explicitly user-requested early anchor requires one-time host confirmation. Safe cleanup is queued once, not full compaction; never claim approval or retry a refusal. Pivot needs carryover and ends the turn; files/processes are not rolled back.",
|
|
24422
24507
|
parameters: Type6.Object({
|
|
24423
24508
|
action: StringEnum4(["view", "recall", "anchor", "pivot"]),
|
|
24424
24509
|
target: Type6.Optional(Type6.String({ minLength: 1, maxLength: 128 })),
|
|
@@ -24436,8 +24521,8 @@ function registerNavigation(pi, options = {}) {
|
|
|
24436
24521
|
enabled();
|
|
24437
24522
|
if (signal?.aborted)
|
|
24438
24523
|
throw new Error("Navigation cancelled.");
|
|
24439
|
-
if (
|
|
24440
|
-
throw new Error("
|
|
24524
|
+
if (!agentAllowed())
|
|
24525
|
+
throw new Error("Navigation tool access is disabled or hidden; respect the user's policy and /tools selection.");
|
|
24441
24526
|
if (params.action === "view") {
|
|
24442
24527
|
if (params.target) {
|
|
24443
24528
|
const target = resolveAnchorTarget(ctx.sessionManager, params.target);
|
|
@@ -24462,16 +24547,34 @@ Use Pi's /resume to open a different session.`, {
|
|
|
24462
24547
|
});
|
|
24463
24548
|
}
|
|
24464
24549
|
if (params.action === "anchor") {
|
|
24465
|
-
|
|
24466
|
-
|
|
24550
|
+
const early = config().contextPressureOnly && !contextPressure(ctx, config()).cleanup;
|
|
24551
|
+
if (early && !ctx.hasUI)
|
|
24552
|
+
throw new Error("No context pressure (or usage is unavailable); early anchor requires host confirmation. No anchor or cleanup requested. A human can use /smart-compact.");
|
|
24553
|
+
let data = anchor(ctx, params.name ?? "", params.summary ?? "");
|
|
24554
|
+
let userConfirmed = false;
|
|
24555
|
+
if (early) {
|
|
24556
|
+
if (ctx.hasPendingMessages())
|
|
24557
|
+
throw new Error("Queued user input takes priority; no anchor or cleanup requested.");
|
|
24558
|
+
const approvalEpoch = epoch;
|
|
24559
|
+
const sessionId = resolveSessionId(ctx);
|
|
24560
|
+
const approved = await ctx.ui.confirm("Save anchor and request early cleanup?", `Anchor: ${data.name}
|
|
24561
|
+
|
|
24562
|
+
${data.summary}
|
|
24563
|
+
|
|
24564
|
+
Allow this anchor and one safe cleanup without context pressure? Only eligible old tool outputs may be shortened; recent turns, protected prefixes and safety checks stay. No full compaction or file/process rollback. Cleanup can be blocked by prepared work or safety checks. This does not change your settings or authorize future calls.`, { signal });
|
|
24565
|
+
if (approved !== true || signal?.aborted)
|
|
24566
|
+
throw new Error("Anchor not approved or cancelled; no anchor or cleanup requested. Do not retry without a new user request.");
|
|
24567
|
+
if (approvalEpoch !== epoch || sessionId !== resolveSessionId(ctx) || data.targetId !== ctx.sessionManager.getLeafId() || ctx.hasPendingMessages() || !agentAllowed())
|
|
24568
|
+
throw new Error("Anchor confirmation expired: session, history, pending input or tool permissions changed. Nothing was queued.");
|
|
24569
|
+
data = anchor(ctx, data.name, data.summary);
|
|
24570
|
+
userConfirmed = true;
|
|
24467
24571
|
}
|
|
24468
|
-
const
|
|
24469
|
-
const cleanup = options.onAnchor?.(ctx, data.targetId, callId, signal) ?? "History unchanged; no cleanup controller attached.";
|
|
24572
|
+
const cleanup = options.onAnchor?.(ctx, data.targetId, callId, signal, userConfirmed) ?? "History unchanged; no cleanup controller attached.";
|
|
24470
24573
|
return { content: [{ type: "text", text: `Anchor: ${data.name}
|
|
24471
24574
|
|
|
24472
24575
|
${data.summary}
|
|
24473
24576
|
|
|
24474
|
-
${cleanup}` }], details: { anchor: data } };
|
|
24577
|
+
${cleanup}` }], details: { anchor: data, ...userConfirmed ? { userConfirmed: true } : {} } };
|
|
24475
24578
|
}
|
|
24476
24579
|
const operation = preparePivot(ctx, params.target ?? "", params.carryover ?? "", params.message);
|
|
24477
24580
|
operation.callId = callId;
|
|
@@ -24548,6 +24651,7 @@ ${operation.carryover}` } };
|
|
|
24548
24651
|
footer(ctx);
|
|
24549
24652
|
});
|
|
24550
24653
|
const reset = (_event, ctx) => {
|
|
24654
|
+
epoch++;
|
|
24551
24655
|
cancel();
|
|
24552
24656
|
applying = undefined;
|
|
24553
24657
|
footer(ctx);
|
|
@@ -24556,17 +24660,23 @@ ${operation.carryover}` } };
|
|
|
24556
24660
|
pi.on("session_before_switch", reset);
|
|
24557
24661
|
pi.on("session_before_fork", reset);
|
|
24558
24662
|
pi.on("session_before_compact", (_event, ctx) => {
|
|
24663
|
+
epoch++;
|
|
24559
24664
|
if (queued?.sessionId === resolveSessionId(ctx) || applying?.sessionId === resolveSessionId(ctx))
|
|
24560
24665
|
return { cancel: true };
|
|
24561
24666
|
});
|
|
24562
24667
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
24668
|
+
epoch++;
|
|
24563
24669
|
cancel();
|
|
24564
24670
|
applying = undefined;
|
|
24565
24671
|
ctx.ui.setStatus(STATUS, undefined);
|
|
24566
24672
|
});
|
|
24567
24673
|
pi.on("session_tree", (_event, ctx) => {
|
|
24674
|
+
epoch++;
|
|
24568
24675
|
footer(ctx);
|
|
24569
24676
|
});
|
|
24677
|
+
pi.on("model_select", () => {
|
|
24678
|
+
epoch++;
|
|
24679
|
+
});
|
|
24570
24680
|
return {
|
|
24571
24681
|
isPending: (ctx) => queued?.sessionId === resolveSessionId(ctx) || applying?.sessionId === resolveSessionId(ctx),
|
|
24572
24682
|
refresh,
|
|
@@ -25085,16 +25195,17 @@ function smartCompactExtension(pi) {
|
|
|
25085
25195
|
navigation = registerNavigation(pi, {
|
|
25086
25196
|
config: automaticConfig,
|
|
25087
25197
|
mutationBlocked: (ctx) => isRunning.isSessionActive(resolveSessionId(ctx)) ? "Compaction is running; wait for it to finish." : undefined,
|
|
25088
|
-
onAnchor: (ctx, originId, callId, signal) => smartContext.requestAnchorTrim(ctx, originId, callId, signal).notice,
|
|
25198
|
+
onAnchor: (ctx, originId, callId, signal, userConfirmed) => smartContext.requestAnchorTrim(ctx, originId, callId, signal, userConfirmed).notice,
|
|
25089
25199
|
onContextChange: (ctx) => invalidatePreparation(ctx, "branch"),
|
|
25090
25200
|
onContextEdit: (_ctx, kind) => hostCache.noteContextEdit(kind)
|
|
25091
25201
|
});
|
|
25092
25202
|
registerArtifactOffload(pi, automaticConfig, () => toolExposure.reachable("history"));
|
|
25203
|
+
const canAutoTrim = (ctx) => toolExposure.reachable("history") && !background.hasWork() && !pendingRef.isPresent(resolveSessionId(ctx)) && !isRunning.isSessionActive(resolveSessionId(ctx));
|
|
25093
25204
|
const smartContext = registerSmartContextTool(pi, {
|
|
25094
25205
|
config: automaticConfig,
|
|
25095
25206
|
isPaused: pivotQueued,
|
|
25096
25207
|
canAgentMutate: () => policy.snapshot().agentToolAccess !== "disabled",
|
|
25097
|
-
canAutoTrim
|
|
25208
|
+
canAutoTrim,
|
|
25098
25209
|
onContextChange: (ctx) => invalidatePreparation(ctx, "branch"),
|
|
25099
25210
|
onContextEdit: (_ctx, kind) => hostCache.noteContextEdit(kind),
|
|
25100
25211
|
onCacheWarm: (ctx, at) => {
|
|
@@ -25105,6 +25216,7 @@ function smartCompactExtension(pi) {
|
|
|
25105
25216
|
registerContextAttention(pi, {
|
|
25106
25217
|
config: automaticConfig,
|
|
25107
25218
|
canAgentAct: (ctx) => policy.snapshot().agentToolAccess !== "disabled" && !pivotQueued(ctx) && !isRunning.isSessionActive(resolveSessionId(ctx)),
|
|
25219
|
+
canCleanup: canAutoTrim,
|
|
25108
25220
|
reachable: (group) => toolExposure.reachable(group)
|
|
25109
25221
|
});
|
|
25110
25222
|
registerSmartCompactCommand(pi, {
|
|
@@ -25401,12 +25513,13 @@ function smartCompactExtension(pi) {
|
|
|
25401
25513
|
const sessionId = resolveSessionId(ctx);
|
|
25402
25514
|
if (hostCache.sessionId() !== sessionId)
|
|
25403
25515
|
hostCache.reset(sessionId);
|
|
25404
|
-
|
|
25516
|
+
const promptCache = ctx.model && ctx.model.provider === event.message.provider && ctx.model.id === event.message.model ? ctx.model.promptCache : undefined;
|
|
25517
|
+
if (!hostCache.observe(event.message, undefined, promptCache)?.warn)
|
|
25405
25518
|
return;
|
|
25406
25519
|
const { foreign } = hostCache.summary().rebuilds;
|
|
25407
25520
|
recordIssue({
|
|
25408
25521
|
key: "cache.foreign-rebuilds",
|
|
25409
|
-
message: "Pi
|
|
25522
|
+
message: "Pi reported " + foreign.count + " large uncached prompts this session with no Continuity edit before them (" + foreign.uncached.toLocaleString("en-US") + " uncached prompt tokens). Their cause is unknown; model/tool changes and Pi's built-in compaction can also change the prefix. Home \u203A Readiness & details lists rebuild estimates."
|
|
25410
25523
|
});
|
|
25411
25524
|
});
|
|
25412
25525
|
pi.on("message_end", async (event, ctx) => {
|