@aexol/spectral 0.9.183 → 0.9.185
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/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +8 -7
- package/dist/memory/branch.d.ts +13 -0
- package/dist/memory/branch.d.ts.map +1 -1
- package/dist/memory/branch.js +100 -0
- package/dist/memory/config.d.ts +2 -12
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +2 -20
- package/dist/memory/hooks/compaction-hook.d.ts.map +1 -1
- package/dist/memory/hooks/compaction-hook.js +43 -1
- package/dist/memory/hooks/observer-trigger.d.ts.map +1 -1
- package/dist/memory/hooks/observer-trigger.js +9 -3
- package/dist/memory/index.d.ts +13 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +17 -26
- package/dist/memory/project-observation-memory.d.ts +16 -0
- package/dist/memory/project-observation-memory.d.ts.map +1 -0
- package/dist/memory/project-observation-memory.js +27 -0
- package/dist/memory/project-observations-store.d.ts +8 -0
- package/dist/memory/project-observations-store.d.ts.map +1 -1
- package/dist/memory/project-observations-store.js +5 -0
- package/dist/memory/prompts.d.ts +2 -2
- package/dist/memory/prompts.d.ts.map +1 -1
- package/dist/memory/prompts.js +1 -1
- package/dist/memory/session-memory.d.ts +21 -0
- package/dist/memory/session-memory.d.ts.map +1 -0
- package/dist/memory/session-memory.js +33 -0
- package/dist/memory/tool-output-compressor.d.ts +0 -1
- package/dist/memory/tool-output-compressor.d.ts.map +1 -1
- package/dist/memory/tool-output-compressor.js +0 -1
- package/dist/memory/tools/compact-context.d.ts +31 -10
- package/dist/memory/tools/compact-context.d.ts.map +1 -1
- package/dist/memory/tools/compact-context.js +193 -246
- package/dist/memory/tools/read-project-observations.d.ts.map +1 -1
- package/dist/memory/tools/read-project-observations.js +6 -3
- package/dist/memory/tools/recall-observation.d.ts.map +1 -1
- package/dist/memory/tools/recall-observation.js +18 -4
- package/dist/memory/tools/receive-agent-observations.d.ts.map +1 -1
- package/dist/memory/tools/receive-agent-observations.js +4 -2
- package/dist/memory/tools/share-project-observation.d.ts.map +1 -1
- package/dist/memory/tools/share-project-observation.js +5 -3
- package/dist/memory/tools/write-project-observation.d.ts.map +1 -1
- package/dist/memory/tools/write-project-observation.js +16 -3
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts +11 -0
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/compaction.js +13 -0
- package/dist/server/handlers/settings.d.ts +0 -6
- package/dist/server/handlers/settings.d.ts.map +1 -1
- package/dist/server/handlers/settings.js +0 -21
- package/dist/server/inter-agent-broker.d.ts +12 -7
- package/dist/server/inter-agent-broker.d.ts.map +1 -1
- package/dist/server/inter-agent-broker.js +12 -7
- package/dist/server/session-stream.d.ts +1 -3
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +2 -5
- package/dist/server/storage.d.ts +14 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +43 -0
- package/dist/server/wire.d.ts +0 -4
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -4,9 +4,7 @@ import { defineTool } from "../../sdk/coding-agent/index.js";
|
|
|
4
4
|
import { buildSessionContext } from "../../sdk/coding-agent/core/session-manager.js";
|
|
5
5
|
import { rawTokensSinceLastCompaction } from "../branch.js";
|
|
6
6
|
import { debugLog, withDebugLogContext } from "../debug-log.js";
|
|
7
|
-
import { DEFAULTS } from "../config.js";
|
|
8
7
|
export const COMPACT_CONTEXT_TOOL_NAME = "compact_context";
|
|
9
|
-
const DCP_NUDGE_HEADING = "## DCP-lite context hygiene";
|
|
10
8
|
const MAX_COMPACT_CONTEXT_TEXT_CHARS = 240;
|
|
11
9
|
const MAX_CUSTOM_INSTRUCTIONS_CHARS = 640;
|
|
12
10
|
const DEFAULT_COMPACT_CONTEXT_SCOPE = "closed_phase";
|
|
@@ -61,11 +59,9 @@ function getState(runtime) {
|
|
|
61
59
|
if (!state) {
|
|
62
60
|
state = {
|
|
63
61
|
request: null,
|
|
64
|
-
|
|
65
|
-
lastNudgeTurn: 0,
|
|
62
|
+
turnCount: 0,
|
|
66
63
|
lastAgentDrivenRequestTurn: null,
|
|
67
|
-
|
|
68
|
-
lastInRunNudgeRequest: 0,
|
|
64
|
+
autoPruneFrozen: null,
|
|
69
65
|
};
|
|
70
66
|
stateByRuntime.set(runtime, state);
|
|
71
67
|
}
|
|
@@ -100,136 +96,6 @@ function buildCustomInstructions(params) {
|
|
|
100
96
|
function formatTokenCount(tokens) {
|
|
101
97
|
return Math.round(tokens).toLocaleString();
|
|
102
98
|
}
|
|
103
|
-
function formatSignals(rawTokens, contextTokens, percent, providerRequests) {
|
|
104
|
-
const parts = [`~${rawTokens.toLocaleString()} raw tokens since last compaction`];
|
|
105
|
-
if (typeof contextTokens === "number" && Number.isFinite(contextTokens)) {
|
|
106
|
-
parts.push(`~${formatTokenCount(contextTokens)} context tokens in the next request`);
|
|
107
|
-
}
|
|
108
|
-
if (typeof percent === "number" && Number.isFinite(percent)) {
|
|
109
|
-
parts.push(`${Math.round(percent)}% context window used`);
|
|
110
|
-
}
|
|
111
|
-
if (providerRequests !== undefined) {
|
|
112
|
-
parts.push(`${formatTokenCount(providerRequests)} provider requests since the last user prompt`);
|
|
113
|
-
}
|
|
114
|
-
return parts.join(", ");
|
|
115
|
-
}
|
|
116
|
-
function buildDcpNudge(rawTokens, contextTokens, percent, policy, providerRequests) {
|
|
117
|
-
return [
|
|
118
|
-
"",
|
|
119
|
-
DCP_NUDGE_HEADING,
|
|
120
|
-
`Context is accumulating (${formatSignals(rawTokens, contextTokens, percent, providerRequests)}).`,
|
|
121
|
-
"Use `compact_context` once when a safe compaction scope is available. When the agent is still working mid-run, the default is virtual `request_time` pruning that shrinks the next provider request without mutating session history; when the phase is closed or the agent is idle, persisted `after_turn` compaction runs after this run for cache-friendly future requests.",
|
|
122
|
-
"If the user explicitly asks to run/trigger `compact_context`, DCP, DCP-lite, context pruning, or context compression, call `compact_context` in the next step with the safest allowed scope; do not run shell tests, explain it away, or claim DCP was activated without a tool result. If guards reject the call, report that rejection.",
|
|
123
|
-
"When the current topic/phase is done, the user changes threads, or old tool output is no longer needed, call `compact_context({ scope: \"closed_phase\", focus: \"short topic/focus\", phaseComplete: true })` once and continue normally.",
|
|
124
|
-
`If the phase remains active/open, use \`compact_context({ scope: "older_history", keepRecentTokens: ${policy.agentDriven.olderHistoryKeepRecentTokens} })\` to prune older stale history in-flight while a recent raw suffix stays available for the next step.`,
|
|
125
|
-
"Prefer `mode: \"request_time\"` while the phase is active and context is growing (prunes the next request virtually); use `mode: \"after_turn\"` once the phase is closed (persists compaction after the run).",
|
|
126
|
-
].join("\n");
|
|
127
|
-
}
|
|
128
|
-
function isRecord(value) {
|
|
129
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
130
|
-
}
|
|
131
|
-
function contentContainsDcpNudge(content) {
|
|
132
|
-
if (typeof content === "string")
|
|
133
|
-
return content.includes(DCP_NUDGE_HEADING);
|
|
134
|
-
if (!Array.isArray(content))
|
|
135
|
-
return false;
|
|
136
|
-
return content.some((part) => {
|
|
137
|
-
if (typeof part === "string")
|
|
138
|
-
return part.includes(DCP_NUDGE_HEADING);
|
|
139
|
-
return isRecord(part) && typeof part.text === "string" && part.text.includes(DCP_NUDGE_HEADING);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
function payloadContainsDcpNudge(payload) {
|
|
143
|
-
if (!isRecord(payload))
|
|
144
|
-
return false;
|
|
145
|
-
if (contentContainsDcpNudge(payload.system))
|
|
146
|
-
return true;
|
|
147
|
-
const messages = payload.messages;
|
|
148
|
-
if (!Array.isArray(messages))
|
|
149
|
-
return false;
|
|
150
|
-
return messages.some((message) => isRecord(message) && contentContainsDcpNudge(message.content));
|
|
151
|
-
}
|
|
152
|
-
function appendNudgeToContent(content, nudge) {
|
|
153
|
-
if (typeof content === "string")
|
|
154
|
-
return `${content}${nudge}`;
|
|
155
|
-
if (Array.isArray(content))
|
|
156
|
-
return [...content, { type: "text", text: nudge.trimStart() }];
|
|
157
|
-
return nudge.trimStart();
|
|
158
|
-
}
|
|
159
|
-
function injectDcpNudgeIntoAnthropicPayload(payload, nudge) {
|
|
160
|
-
if (!("system" in payload))
|
|
161
|
-
return undefined;
|
|
162
|
-
const system = payload.system;
|
|
163
|
-
if (typeof system === "string")
|
|
164
|
-
return { ...payload, system: `${system}${nudge}` };
|
|
165
|
-
if (Array.isArray(system))
|
|
166
|
-
return { ...payload, system: [...system, { type: "text", text: nudge.trimStart() }] };
|
|
167
|
-
if (system === undefined || system === null)
|
|
168
|
-
return { ...payload, system: [{ type: "text", text: nudge.trimStart() }] };
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
function injectDcpNudgeIntoOpenAiPayload(payload, nudge) {
|
|
172
|
-
const messages = payload.messages;
|
|
173
|
-
if (!Array.isArray(messages))
|
|
174
|
-
return undefined;
|
|
175
|
-
const targetIndex = messages.findIndex((message) => {
|
|
176
|
-
if (!isRecord(message))
|
|
177
|
-
return false;
|
|
178
|
-
return message.role === "system" || message.role === "developer";
|
|
179
|
-
});
|
|
180
|
-
if (targetIndex === -1) {
|
|
181
|
-
return { ...payload, messages: [{ role: "system", content: nudge.trimStart() }, ...messages] };
|
|
182
|
-
}
|
|
183
|
-
const targetMessage = messages[targetIndex];
|
|
184
|
-
if (!isRecord(targetMessage))
|
|
185
|
-
return undefined;
|
|
186
|
-
const nextMessages = [...messages];
|
|
187
|
-
nextMessages[targetIndex] = {
|
|
188
|
-
...targetMessage,
|
|
189
|
-
content: appendNudgeToContent(targetMessage.content, nudge),
|
|
190
|
-
};
|
|
191
|
-
return { ...payload, messages: nextMessages };
|
|
192
|
-
}
|
|
193
|
-
function injectDcpNudgeIntoProviderPayload(payload, nudge) {
|
|
194
|
-
if (!isRecord(payload) || payloadContainsDcpNudge(payload))
|
|
195
|
-
return undefined;
|
|
196
|
-
return injectDcpNudgeIntoAnthropicPayload(payload, nudge) ?? injectDcpNudgeIntoOpenAiPayload(payload, nudge);
|
|
197
|
-
}
|
|
198
|
-
function describeProviderPayload(payload) {
|
|
199
|
-
if (!isRecord(payload))
|
|
200
|
-
return typeof payload;
|
|
201
|
-
const keys = Object.keys(payload).sort();
|
|
202
|
-
if (Array.isArray(payload.messages))
|
|
203
|
-
return `messages:${keys.join(",")}`;
|
|
204
|
-
if (Array.isArray(payload.system))
|
|
205
|
-
return `anthropic:${keys.join(",")}`;
|
|
206
|
-
return `object:${keys.join(",")}`;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Creates an invocation-local provider payload hook for isolated agent loops.
|
|
210
|
-
* The values are snapshotted at invocation start; no Runtime, SessionManager, or
|
|
211
|
-
* extension state is retained by the returned hook.
|
|
212
|
-
*/
|
|
213
|
-
export function createIsolatedDcpNudgePayloadHook(options = {}) {
|
|
214
|
-
let providerRequestCount = 0;
|
|
215
|
-
let lastNudgeRequest = 0;
|
|
216
|
-
const config = options.config ?? DEFAULTS;
|
|
217
|
-
const policy = options.policy ?? DEFAULT_COMPACTION_POLICY;
|
|
218
|
-
return (payload) => {
|
|
219
|
-
providerRequestCount += 1;
|
|
220
|
-
const frequency = config.dcpInRunNudgeFrequency;
|
|
221
|
-
const hasSignal = providerRequestCount >= config.dcpInRunNudgeIterationThreshold;
|
|
222
|
-
const frequencyReady = lastNudgeRequest === 0 || providerRequestCount - lastNudgeRequest >= frequency;
|
|
223
|
-
if (!hasSignal || !frequencyReady || payloadContainsDcpNudge(payload))
|
|
224
|
-
return undefined;
|
|
225
|
-
const nudge = buildDcpNudge(0, undefined, undefined, policy, providerRequestCount);
|
|
226
|
-
const nextPayload = injectDcpNudgeIntoProviderPayload(payload, nudge);
|
|
227
|
-
if (nextPayload === undefined)
|
|
228
|
-
return undefined;
|
|
229
|
-
lastNudgeRequest = providerRequestCount;
|
|
230
|
-
return nextPayload;
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
99
|
function rawTokensForContext(ctx) {
|
|
234
100
|
const entries = ctx.sessionManager.getBranch();
|
|
235
101
|
return rawTokensSinceLastCompaction(entries);
|
|
@@ -342,7 +208,7 @@ function decideAutoCompaction(ctx, runtime, state, policy, rawTokens, contextTok
|
|
|
342
208
|
cooldownActive: remainingCooldownTurns(state, policy) > 0,
|
|
343
209
|
hasSufficientMaterial: rawTokens >= requestSettings.minRawTokens,
|
|
344
210
|
contextRatioReached: isContextAtOrAboveRatio(percent, policy.autoOlderHistory.contextLimitRatio),
|
|
345
|
-
absoluteThresholdReached: isContextAtOrAboveTokenLimit(contextTokens, runtime.config.
|
|
211
|
+
absoluteThresholdReached: isContextAtOrAboveTokenLimit(contextTokens, runtime.config.autoCompactionContextTokens),
|
|
346
212
|
rawThresholdReached: rawTokens >= runtime.config.compactionThresholdTokens,
|
|
347
213
|
iterationReached: iterationSignal,
|
|
348
214
|
});
|
|
@@ -352,7 +218,7 @@ function maybeScheduleAutoOlderHistoryCompaction(ctx, runtime, state, policy, ra
|
|
|
352
218
|
if (decision.mode === "none")
|
|
353
219
|
return false;
|
|
354
220
|
const requestSettings = getAutoOlderHistoryRequestSettings(policy);
|
|
355
|
-
state.lastAgentDrivenRequestTurn = state.
|
|
221
|
+
state.lastAgentDrivenRequestTurn = state.turnCount;
|
|
356
222
|
state.request = { source: "auto", mode: decision.mode, ...requestSettings };
|
|
357
223
|
if (decision.mode === "request_time")
|
|
358
224
|
notifyRequestTimeActive(ctx, state.request, rawTokens);
|
|
@@ -363,7 +229,7 @@ function maybeScheduleAutoOlderHistoryCompaction(ctx, runtime, state, policy, ra
|
|
|
363
229
|
function remainingCooldownTurns(state, policy) {
|
|
364
230
|
if (state.lastAgentDrivenRequestTurn === null)
|
|
365
231
|
return 0;
|
|
366
|
-
const elapsed = Math.max(0, state.
|
|
232
|
+
const elapsed = Math.max(0, state.turnCount - state.lastAgentDrivenRequestTurn);
|
|
367
233
|
return Math.max(0, policy.agentDriven.cooldownTurns - elapsed);
|
|
368
234
|
}
|
|
369
235
|
function getRequestTimeCompactionSettings(request, policy) {
|
|
@@ -376,12 +242,31 @@ function getRequestTimeCompactionSettings(request, policy) {
|
|
|
376
242
|
function getBranchEntries(ctx) {
|
|
377
243
|
return ctx.sessionManager.getBranch();
|
|
378
244
|
}
|
|
379
|
-
function
|
|
245
|
+
function getAutoPruneBranchId(ctx) {
|
|
246
|
+
try {
|
|
247
|
+
return ctx.sessionManager.getSessionId();
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return "unknown-session";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function autoPruneIdentityMatches(frozen, branchId, currentMessages) {
|
|
254
|
+
if (frozen.branchId !== branchId)
|
|
255
|
+
return false;
|
|
256
|
+
if (frozen.frozenAtBoundaryEnd > currentMessages.length)
|
|
257
|
+
return false;
|
|
258
|
+
for (let index = 0; index < frozen.frozenAtBoundaryEnd; index += 1) {
|
|
259
|
+
if (currentMessages[index] !== frozen.leadingMessageRefs[index])
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
function createVirtualCompactionEntry(pathEntries, result, timestamp) {
|
|
380
265
|
return {
|
|
381
266
|
type: "compaction",
|
|
382
267
|
id: REQUEST_TIME_VIRTUAL_COMPACTION_ID,
|
|
383
268
|
parentId: pathEntries[pathEntries.length - 1]?.id ?? null,
|
|
384
|
-
timestamp: pathEntries[pathEntries.length - 1]?.timestamp ?? new Date().toISOString(),
|
|
269
|
+
timestamp: timestamp ?? pathEntries[pathEntries.length - 1]?.timestamp ?? new Date().toISOString(),
|
|
385
270
|
summary: result.summary,
|
|
386
271
|
firstKeptEntryId: result.firstKeptEntryId,
|
|
387
272
|
tokensBefore: result.tokensBefore,
|
|
@@ -389,7 +274,7 @@ function createVirtualCompactionEntry(pathEntries, result) {
|
|
|
389
274
|
fromHook: false,
|
|
390
275
|
};
|
|
391
276
|
}
|
|
392
|
-
function buildRequestTimePrunedMessages(ctx, request, policy, currentMessages, onFailure) {
|
|
277
|
+
function buildRequestTimePrunedMessages(ctx, request, policy, currentMessages, onFailure, frozen) {
|
|
393
278
|
// During an active provider request the event payload is the source of truth.
|
|
394
279
|
// The session branch can lag behind it (notably while a tool result or the
|
|
395
280
|
// current assistant turn is still being appended), which used to make the
|
|
@@ -404,13 +289,24 @@ function buildRequestTimePrunedMessages(ctx, request, policy, currentMessages, o
|
|
|
404
289
|
timestamp: new Date(index).toISOString(),
|
|
405
290
|
message,
|
|
406
291
|
}));
|
|
407
|
-
const fromCurrentMessages = buildRequestTimePrunedMessagesFromEntries(currentEntries, request, policy, currentMessages, onFailure);
|
|
292
|
+
const fromCurrentMessages = buildRequestTimePrunedMessagesFromEntries(currentEntries, request, policy, currentMessages, onFailure, frozen);
|
|
408
293
|
if (fromCurrentMessages)
|
|
409
294
|
return fromCurrentMessages;
|
|
410
|
-
return buildRequestTimePrunedMessagesFromEntries(getBranchEntries(ctx), request, policy, currentMessages, onFailure);
|
|
295
|
+
return buildRequestTimePrunedMessagesFromEntries(getBranchEntries(ctx), request, policy, currentMessages, onFailure, frozen);
|
|
411
296
|
}
|
|
412
297
|
/** Request-time DCP adapter for isolated loops: it borrows only pure policy helpers. */
|
|
413
298
|
export function buildIsolatedRequestTimePrunedMessages(currentMessages, policy = DEFAULT_COMPACTION_POLICY, keepRecentTokens) {
|
|
299
|
+
return buildIsolatedRequestTimePrunedMessagesResult(currentMessages, policy, keepRecentTokens).messages;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Stateful per-run variant of {@link buildIsolatedRequestTimePrunedMessages}.
|
|
303
|
+
*
|
|
304
|
+
* The subagent loop owns the returned `frozen` value across provider requests.
|
|
305
|
+
* On the first prune we freeze the deterministic summary + cut, then reuse that
|
|
306
|
+
* exact prefix until the leading message references change (which can only
|
|
307
|
+
* happen at the end of the isolated run).
|
|
308
|
+
*/
|
|
309
|
+
export function buildIsolatedRequestTimePrunedMessagesResult(currentMessages, policy = DEFAULT_COMPACTION_POLICY, keepRecentTokens, frozen) {
|
|
414
310
|
const syntheticEntries = currentMessages.map((message, index) => ({
|
|
415
311
|
type: "message",
|
|
416
312
|
id: `isolated-${index}`,
|
|
@@ -418,18 +314,89 @@ export function buildIsolatedRequestTimePrunedMessages(currentMessages, policy =
|
|
|
418
314
|
timestamp: new Date(index).toISOString(),
|
|
419
315
|
message,
|
|
420
316
|
}));
|
|
421
|
-
|
|
317
|
+
const request = {
|
|
318
|
+
source: "auto",
|
|
319
|
+
mode: "request_time",
|
|
320
|
+
scope: "older_history",
|
|
321
|
+
keepRecentTokens,
|
|
322
|
+
phaseBoundary: false,
|
|
323
|
+
minRawTokens: 0,
|
|
324
|
+
pinLatestUserMessage: true,
|
|
325
|
+
};
|
|
326
|
+
const branchId = "isolated-subagent-run";
|
|
327
|
+
if (frozen && autoPruneIdentityMatches(frozen, branchId, currentMessages)) {
|
|
328
|
+
const reused = buildRequestTimePrunedMessagesFromEntries(syntheticEntries, request, policy, currentMessages, undefined, frozen);
|
|
329
|
+
if (reused)
|
|
330
|
+
return { messages: reused.messages, frozen };
|
|
331
|
+
}
|
|
332
|
+
const pruned = buildRequestTimePrunedMessagesFromEntries(syntheticEntries, request, policy, currentMessages);
|
|
333
|
+
if (!pruned || typeof pruned.anchorEntryId !== "string" || typeof pruned.summary !== "string") {
|
|
334
|
+
return { messages: currentMessages, frozen: null };
|
|
335
|
+
}
|
|
336
|
+
const nextFrozen = {
|
|
337
|
+
branchId,
|
|
338
|
+
anchorEntryId: pruned.anchorEntryId,
|
|
339
|
+
summary: pruned.summary,
|
|
340
|
+
frozenAtBoundaryEnd: currentMessages.length,
|
|
341
|
+
rawTokensAtFreeze: rawTokensForCurrentMessages(currentMessages),
|
|
342
|
+
tokensBefore: pruned.tokensBefore ?? 0,
|
|
343
|
+
timestamp: pruned.timestamp ?? new Date().toISOString(),
|
|
344
|
+
leadingMessageRefs: currentMessages.slice(),
|
|
345
|
+
};
|
|
346
|
+
return { messages: pruned.messages, frozen: nextFrozen };
|
|
422
347
|
}
|
|
423
|
-
function buildRequestTimePrunedMessagesFromEntries(pathEntries, request, policy, currentMessages, onFailure) {
|
|
348
|
+
function buildRequestTimePrunedMessagesFromEntries(pathEntries, request, policy, currentMessages, onFailure, frozen) {
|
|
424
349
|
const settings = getRequestTimeCompactionSettings(request, policy);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
350
|
+
let summary;
|
|
351
|
+
let firstKeptEntryId;
|
|
352
|
+
let tokensBefore;
|
|
353
|
+
let details;
|
|
354
|
+
let tokensRemoved;
|
|
355
|
+
if (frozen) {
|
|
356
|
+
// Reuse the frozen cut. We still run prepareCompaction with an explicit
|
|
357
|
+
// anchor so the fixed boundary is validated against the current path,
|
|
358
|
+
// but we intentionally do NOT regenerate the summary (that would change
|
|
359
|
+
// the prompt-cache prefix on every request).
|
|
360
|
+
const preparation = prepareCompaction(pathEntries, settings, {
|
|
361
|
+
phaseBoundary: request.phaseBoundary,
|
|
362
|
+
pinLatestUserMessage: request.pinLatestUserMessage,
|
|
363
|
+
anchorEntryId: frozen.anchorEntryId,
|
|
364
|
+
});
|
|
365
|
+
if (!preparation || preparation.firstKeptEntryId !== frozen.anchorEntryId) {
|
|
366
|
+
onFailure?.(`frozen_anchor_not_found:${frozen.anchorEntryId}`);
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
summary = frozen.summary;
|
|
370
|
+
firstKeptEntryId = frozen.anchorEntryId;
|
|
371
|
+
tokensBefore = frozen.tokensBefore;
|
|
372
|
+
details = undefined;
|
|
373
|
+
tokensRemoved = 0;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
const preparation = prepareCompaction(pathEntries, settings, {
|
|
377
|
+
phaseBoundary: request.phaseBoundary,
|
|
378
|
+
pinLatestUserMessage: request.pinLatestUserMessage,
|
|
379
|
+
});
|
|
380
|
+
if (!preparation) {
|
|
381
|
+
onFailure?.("prepare_compaction_returned_undefined");
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
const result = compactDcpLite(preparation);
|
|
385
|
+
summary = result.summary;
|
|
386
|
+
firstKeptEntryId = result.firstKeptEntryId;
|
|
387
|
+
tokensBefore = result.tokensBefore;
|
|
388
|
+
details = result.details;
|
|
389
|
+
tokensRemoved = Math.max(0, result.tokensRemoved ?? 0);
|
|
429
390
|
}
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
391
|
+
const virtualTimestamp = frozen?.timestamp ??
|
|
392
|
+
pathEntries[pathEntries.length - 1]?.timestamp ??
|
|
393
|
+
new Date().toISOString();
|
|
394
|
+
const virtualCompaction = createVirtualCompactionEntry(pathEntries, {
|
|
395
|
+
summary,
|
|
396
|
+
firstKeptEntryId,
|
|
397
|
+
tokensBefore,
|
|
398
|
+
details,
|
|
399
|
+
}, virtualTimestamp);
|
|
433
400
|
const virtualContext = buildSessionContext([...pathEntries, virtualCompaction]);
|
|
434
401
|
if (virtualContext.messages.length === 0) {
|
|
435
402
|
onFailure?.("virtual_context_has_no_messages");
|
|
@@ -441,12 +408,21 @@ function buildRequestTimePrunedMessagesFromEntries(pathEntries, request, policy,
|
|
|
441
408
|
const originalTokens = currentMessages.reduce((total, message) => total + estimateTokens(message), 0);
|
|
442
409
|
const prunedTokens = virtualContext.messages.reduce((total, message) => total + estimateTokens(message), 0);
|
|
443
410
|
if (prunedTokens >= originalTokens) {
|
|
444
|
-
onFailure?.(tokensRemoved <= 0
|
|
411
|
+
onFailure?.(!frozen && tokensRemoved <= 0
|
|
445
412
|
? `summary_removed_zero_tokens:${originalTokens}->${prunedTokens}`
|
|
446
413
|
: `pruned_context_not_smaller:${originalTokens}->${prunedTokens}`);
|
|
447
414
|
return undefined;
|
|
448
415
|
}
|
|
449
|
-
|
|
416
|
+
const effectiveTokensRemoved = frozen
|
|
417
|
+
? Math.max(0, originalTokens - prunedTokens)
|
|
418
|
+
: tokensRemoved;
|
|
419
|
+
return {
|
|
420
|
+
messages: virtualContext.messages,
|
|
421
|
+
tokensRemoved: effectiveTokensRemoved,
|
|
422
|
+
originalTokens,
|
|
423
|
+
prunedTokens,
|
|
424
|
+
...(frozen ? {} : { anchorEntryId: firstKeptEntryId, summary, tokensBefore, timestamp: virtualTimestamp }),
|
|
425
|
+
};
|
|
450
426
|
}
|
|
451
427
|
function notifyDcp(ctx, message, level, telemetry) {
|
|
452
428
|
const notify = ctx.ui?.notify;
|
|
@@ -581,7 +557,7 @@ function registerAgentCompactionTool(ext, runtime, state) {
|
|
|
581
557
|
};
|
|
582
558
|
}
|
|
583
559
|
const customInstructions = buildCustomInstructions(params);
|
|
584
|
-
state.lastAgentDrivenRequestTurn = state.
|
|
560
|
+
state.lastAgentDrivenRequestTurn = state.turnCount;
|
|
585
561
|
state.request = { source: "tool", mode, customInstructions, ...requestSettings };
|
|
586
562
|
if (mode === "request_time")
|
|
587
563
|
notifyRequestTimeActive(ctx, state.request, rawTokens);
|
|
@@ -625,9 +601,7 @@ export function registerCompactContextTool(ext, runtime) {
|
|
|
625
601
|
const policy = getCompactionPolicy(ctx);
|
|
626
602
|
if (!policy.agentDriven.enabled)
|
|
627
603
|
return;
|
|
628
|
-
state.
|
|
629
|
-
state.lastInRunNudgeRequest = 0;
|
|
630
|
-
state.nudgeTurnCount += 1;
|
|
604
|
+
state.turnCount += 1;
|
|
631
605
|
let rawTokens = 0;
|
|
632
606
|
try {
|
|
633
607
|
rawTokens = rawTokensForContext(ctx);
|
|
@@ -638,21 +612,7 @@ export function registerCompactContextTool(ext, runtime) {
|
|
|
638
612
|
const usage = ctx.getContextUsage();
|
|
639
613
|
const contextTokens = usage?.tokens;
|
|
640
614
|
const percent = usage?.percent;
|
|
641
|
-
const minRawTokens = runtime.config.dcpNudgeMinRawTokens;
|
|
642
|
-
const minContextTokens = runtime.config.dcpNudgeMinContextTokens;
|
|
643
|
-
const minPercent = runtime.config.dcpNudgeContextPercent;
|
|
644
|
-
const turnInterval = runtime.config.dcpNudgeTurnInterval;
|
|
645
|
-
const turnsSinceNudge = state.nudgeTurnCount - state.lastNudgeTurn;
|
|
646
|
-
const rawSignal = rawTokens >= minRawTokens;
|
|
647
|
-
const absoluteContextSignal = isContextAtOrAboveTokenLimit(contextTokens, minContextTokens);
|
|
648
|
-
const percentSignal = typeof percent === "number" && Number.isFinite(percent) && percent >= minPercent;
|
|
649
|
-
const intervalSignal = turnsSinceNudge >= turnInterval && rawTokens >= Math.floor(minRawTokens / 2);
|
|
650
615
|
maybeScheduleAutoOlderHistoryCompaction(ctx, runtime, state, policy, rawTokens, contextTokens, percent);
|
|
651
|
-
if (!rawSignal && !absoluteContextSignal && !percentSignal && !intervalSignal)
|
|
652
|
-
return;
|
|
653
|
-
state.lastNudgeTurn = state.nudgeTurnCount;
|
|
654
|
-
const nudge = buildDcpNudge(rawTokens, contextTokens, percent, policy);
|
|
655
|
-
return { systemPrompt: `${_event.systemPrompt}${nudge}` };
|
|
656
616
|
}));
|
|
657
617
|
ext.on("context", withDebug((_event, ctx) => {
|
|
658
618
|
runtime.ensureConfig(ctx.cwd);
|
|
@@ -722,6 +682,53 @@ export function registerCompactContextTool(ext, runtime) {
|
|
|
722
682
|
const autoUsage = ctx.getContextUsage();
|
|
723
683
|
const autoContextTokens = autoUsage?.tokens;
|
|
724
684
|
const autoPercent = autoUsage?.percent;
|
|
685
|
+
const autoBranchId = getAutoPruneBranchId(ctx);
|
|
686
|
+
const autoHysteresisTokens = autoSettings.keepRecentTokens
|
|
687
|
+
? autoSettings.keepRecentTokens * 2
|
|
688
|
+
: 0;
|
|
689
|
+
// 2a. Frozen cut reuse. Once the auto path has pruned, keep reusing the
|
|
690
|
+
// exact summary + anchor so the provider prompt prefix stays byte
|
|
691
|
+
// stable across requests. Only invalidate on identity mismatch or
|
|
692
|
+
// when raw growth exceeds the hysteresis threshold.
|
|
693
|
+
let frozen = state.autoPruneFrozen ?? null;
|
|
694
|
+
if (frozen && isMidRun) {
|
|
695
|
+
const identityMatches = autoPruneIdentityMatches(frozen, autoBranchId, _event.messages);
|
|
696
|
+
const hysteresisExceeded = autoHysteresisTokens > 0 &&
|
|
697
|
+
autoRawTokens - frozen.rawTokensAtFreeze > autoHysteresisTokens;
|
|
698
|
+
if (!identityMatches || hysteresisExceeded) {
|
|
699
|
+
state.autoPruneFrozen = null;
|
|
700
|
+
frozen = null;
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
const frozenRequest = { source: "auto", mode: "request_time", ...autoSettings };
|
|
704
|
+
let frozenFailure;
|
|
705
|
+
const frozenPruned = buildRequestTimePrunedMessages(ctx, frozenRequest, policy, _event.messages, (reason) => {
|
|
706
|
+
frozenFailure = reason;
|
|
707
|
+
}, frozen);
|
|
708
|
+
if (frozenPruned) {
|
|
709
|
+
debugLog("compact_context.request_time.auto_frozen_reuse", {
|
|
710
|
+
rawTokens: autoRawTokens,
|
|
711
|
+
contextTokens: autoContextTokens ?? null,
|
|
712
|
+
percent: autoPercent ?? null,
|
|
713
|
+
originalTokens: frozenPruned.originalTokens,
|
|
714
|
+
prunedTokens: frozenPruned.prunedTokens,
|
|
715
|
+
tokensRemoved: frozenPruned.tokensRemoved,
|
|
716
|
+
anchorEntryId: frozen.anchorEntryId,
|
|
717
|
+
});
|
|
718
|
+
notifyRequestTimeApplied(ctx, frozenPruned, frozenRequest);
|
|
719
|
+
return { messages: frozenPruned.messages };
|
|
720
|
+
}
|
|
721
|
+
debugLog("compact_context.request_time.auto_frozen_invalid", {
|
|
722
|
+
rawTokens: autoRawTokens,
|
|
723
|
+
contextTokens: autoContextTokens ?? null,
|
|
724
|
+
percent: autoPercent ?? null,
|
|
725
|
+
reason: frozenFailure ?? "unknown",
|
|
726
|
+
anchorEntryId: frozen.anchorEntryId,
|
|
727
|
+
});
|
|
728
|
+
state.autoPruneFrozen = null;
|
|
729
|
+
frozen = null;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
725
732
|
const autoDecision = decideAutoCompaction(ctx, runtime, state, policy, autoRawTokens, autoContextTokens, autoPercent, false, isMidRun);
|
|
726
733
|
if (autoDecision.mode !== "request_time")
|
|
727
734
|
return;
|
|
@@ -748,84 +755,24 @@ export function registerCompactContextTool(ext, runtime) {
|
|
|
748
755
|
originalTokens: autoPruned.originalTokens,
|
|
749
756
|
prunedTokens: autoPruned.prunedTokens,
|
|
750
757
|
tokensRemoved: autoPruned.tokensRemoved,
|
|
758
|
+
frozen: false,
|
|
751
759
|
});
|
|
760
|
+
if (typeof autoPruned.anchorEntryId === "string" && typeof autoPruned.summary === "string") {
|
|
761
|
+
state.autoPruneFrozen = {
|
|
762
|
+
branchId: autoBranchId,
|
|
763
|
+
anchorEntryId: autoPruned.anchorEntryId,
|
|
764
|
+
summary: autoPruned.summary,
|
|
765
|
+
frozenAtBoundaryEnd: _event.messages.length,
|
|
766
|
+
rawTokensAtFreeze: autoRawTokens,
|
|
767
|
+
tokensBefore: autoPruned.tokensBefore ?? 0,
|
|
768
|
+
timestamp: autoPruned.timestamp ?? new Date().toISOString(),
|
|
769
|
+
leadingMessageRefs: _event.messages.slice(),
|
|
770
|
+
};
|
|
771
|
+
}
|
|
752
772
|
notifyRequestTimeApplied(ctx, autoPruned, autoRequest);
|
|
753
773
|
return { messages: autoPruned.messages };
|
|
754
774
|
}));
|
|
755
|
-
ext.on("before_provider_request", withDebug((_event, ctx) => {
|
|
756
|
-
runtime.ensureConfig(ctx.cwd);
|
|
757
|
-
if (runtime.config.passive === true)
|
|
758
|
-
return;
|
|
759
|
-
const policy = getCompactionPolicy(ctx);
|
|
760
|
-
if (!policy.agentDriven.enabled)
|
|
761
|
-
return;
|
|
762
|
-
state.providerRequestCount += 1;
|
|
763
|
-
const rawTokens = safeRawTokensForContext(ctx);
|
|
764
|
-
const usage = ctx.getContextUsage();
|
|
765
|
-
const contextTokens = usage?.tokens;
|
|
766
|
-
const percent = usage?.percent;
|
|
767
|
-
const minRawTokens = runtime.config.dcpNudgeMinRawTokens;
|
|
768
|
-
const minContextTokens = runtime.config.dcpNudgeMinContextTokens;
|
|
769
|
-
const minPercent = runtime.config.dcpNudgeContextPercent;
|
|
770
|
-
const iterationThreshold = runtime.config.dcpInRunNudgeIterationThreshold;
|
|
771
|
-
const frequency = runtime.config.dcpInRunNudgeFrequency;
|
|
772
|
-
const rawSignal = rawTokens >= minRawTokens;
|
|
773
|
-
const absoluteContextSignal = isContextAtOrAboveTokenLimit(contextTokens, minContextTokens);
|
|
774
|
-
const percentSignal = typeof percent === "number" && Number.isFinite(percent) && percent >= minPercent;
|
|
775
|
-
const iterationSignal = state.providerRequestCount >= iterationThreshold;
|
|
776
|
-
const frequencyReady = state.lastInRunNudgeRequest === 0 ||
|
|
777
|
-
state.providerRequestCount - state.lastInRunNudgeRequest >= frequency;
|
|
778
|
-
const signalEligible = rawSignal || absoluteContextSignal || percentSignal || iterationSignal;
|
|
779
|
-
const payloadShape = describeProviderPayload(_event.payload);
|
|
780
|
-
const alreadyContainsNudge = payloadContainsDcpNudge(_event.payload);
|
|
781
|
-
debugLog("compact_context.before_provider_request", {
|
|
782
|
-
providerRequestCount: state.providerRequestCount,
|
|
783
|
-
rawTokens,
|
|
784
|
-
contextTokens: contextTokens ?? null,
|
|
785
|
-
percent: percent ?? null,
|
|
786
|
-
rawSignal,
|
|
787
|
-
absoluteContextSignal,
|
|
788
|
-
percentSignal,
|
|
789
|
-
iterationSignal,
|
|
790
|
-
frequencyReady,
|
|
791
|
-
signalEligible,
|
|
792
|
-
payloadShape,
|
|
793
|
-
alreadyContainsNudge,
|
|
794
|
-
});
|
|
795
|
-
// NOTE: maybeScheduleAutoOlderHistoryCompaction is intentionally NOT called
|
|
796
|
-
// here. This event fires AFTER the context event within the same provider
|
|
797
|
-
// request cycle, so any state.request set here would only be visible to
|
|
798
|
-
// the context event on the NEXT request — a one-request lag that lets the
|
|
799
|
-
// oversized request through unpruned. Auto request_time pruning is now
|
|
800
|
-
// decided inline in the context event handler (no lag).
|
|
801
|
-
if (!signalEligible || !frequencyReady) {
|
|
802
|
-
debugLog("compact_context.nudge_skipped", {
|
|
803
|
-
reason: !signalEligible ? "no_signal" : "frequency_cooldown",
|
|
804
|
-
payloadShape,
|
|
805
|
-
});
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
if (alreadyContainsNudge) {
|
|
809
|
-
state.lastInRunNudgeRequest = state.providerRequestCount;
|
|
810
|
-
debugLog("compact_context.nudge_skipped", { reason: "already_present", payloadShape });
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
const nudge = buildDcpNudge(rawTokens, contextTokens, percent, policy, state.providerRequestCount);
|
|
814
|
-
const payload = injectDcpNudgeIntoProviderPayload(_event.payload, nudge);
|
|
815
|
-
if (payload === undefined) {
|
|
816
|
-
debugLog("compact_context.nudge_skipped", { reason: "unsupported_payload_shape", payloadShape });
|
|
817
|
-
return;
|
|
818
|
-
}
|
|
819
|
-
state.lastInRunNudgeRequest = state.providerRequestCount;
|
|
820
|
-
debugLog("compact_context.nudge_injected", {
|
|
821
|
-
providerRequestCount: state.providerRequestCount,
|
|
822
|
-
payloadShape,
|
|
823
|
-
});
|
|
824
|
-
return payload;
|
|
825
|
-
}));
|
|
826
775
|
ext.on("agent_end", withDebug((_event, ctx) => {
|
|
827
|
-
state.providerRequestCount = 0;
|
|
828
|
-
state.lastInRunNudgeRequest = 0;
|
|
829
776
|
const request = state.request;
|
|
830
777
|
if (!request || request.mode !== "after_turn")
|
|
831
778
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-project-observations.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/read-project-observations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGhF,eAAO,MAAM,mCAAmC,8BAA8B,CAAC;AAE/E,eAAO,MAAM,2BAA2B;;
|
|
1
|
+
{"version":3,"file":"read-project-observations.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/read-project-observations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAGhF,eAAO,MAAM,mCAAmC,8BAA8B,CAAC;AAE/E,eAAO,MAAM,2BAA2B;;iFAwFtC,CAAC;AAEH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE3E"}
|
|
@@ -5,13 +5,16 @@ export const READ_PROJECT_OBSERVATIONS_TOOL_NAME = "read_project_observations";
|
|
|
5
5
|
export const readProjectObservationsTool = defineTool({
|
|
6
6
|
name: READ_PROJECT_OBSERVATIONS_TOOL_NAME,
|
|
7
7
|
label: "Search project observations",
|
|
8
|
-
description: "Search
|
|
8
|
+
description: "Search past observations across sessions in this project, stored per-machine " +
|
|
9
|
+
"and per-working-directory (cwd). " +
|
|
9
10
|
"Use this when you need to recall project conventions, past decisions, " +
|
|
10
11
|
"file locations, or user preferences before editing or creating files. " +
|
|
11
12
|
"Uses simple substring matching — be specific with your query terms. " +
|
|
12
|
-
"Returns up to 20 most recent matching observations with relevance tags."
|
|
13
|
-
|
|
13
|
+
"Returns up to 20 most recent matching observations with relevance tags. " +
|
|
14
|
+
"Results are local to this machine and cwd; they are NOT synced across machines or teams.",
|
|
15
|
+
promptSnippet: "Use read_project_observations(<query>) to search past observations across sessions in this local project working directory.",
|
|
14
16
|
promptGuidelines: [
|
|
17
|
+
"Results are per-machine + per-cwd and cross-session; they are NOT shared across machines or teams.",
|
|
15
18
|
"Use read_project_observations before creating new files to find conventions for that file kind.",
|
|
16
19
|
"Use read_project_observations before editing a file to find past context about that file or its directory.",
|
|
17
20
|
"Be specific: search for file paths, technology names, or pattern keywords.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recall-observation.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/recall-observation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEhF,OAAO,EAGN,KAAK,uBAAuB,EAE5B,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"recall-observation.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/recall-observation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEhF,OAAO,EAGN,KAAK,uBAAuB,EAE5B,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEvE,eAAO,MAAM,4BAA4B,WAAW,CAAC;AAIrD,KAAK,2BAA2B,GAC7B,IAAI,GACJ,SAAS,GACT,YAAY,GACZ,WAAW,GACX,WAAW,GACX,oBAAoB,GACpB,eAAe,CAAC;AAEnB,KAAK,kBAAkB,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;AAChG,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,SAAS,GAAG,0BAA0B,GAAG,QAAQ,CAAC,GAAG;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAExI,MAAM,MAAM,wBAAwB,GAAG;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACpC,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC1C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,WAAW,EAAE,kBAAkB,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;IAC3C,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,KAAK,6CAA6C,GAAG;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,KAAK,4CAA4C,GAAG;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,QAAQ,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IAC1C,MAAM,EAAE,2BAA2B,CAAC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,wBAAwB,EAAE,6BAA6B,EAAE,CAAC;IAC1D,YAAY,EAAE,6BAA6B,EAAE,CAAC;IAC9C,OAAO,EAAE,6BAA6B,EAAE,CAAC;IACzC,aAAa,EAAE,wBAAwB,EAAE,CAAC;IAC1C,iCAAiC,EAAE,6CAA6C,EAAE,CAAC;IACnF,+BAA+B,EAAE,4CAA4C,EAAE,CAAC;IAChF,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAuUF,eAAO,MAAM,qBAAqB;;iFAqDhC,CAAC;AAEH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE1D"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "../../sdk/ai/index.js";
|
|
2
2
|
import { defineTool } from "../../sdk/coding-agent/index.js";
|
|
3
3
|
import { recallMemorySources, } from "../branch.js";
|
|
4
|
+
import { getProjectObsStore } from "../project-observations-store.js";
|
|
4
5
|
import { renderRecallSourceEntries, renderRecallSourceEntry } from "../serialize.js";
|
|
5
6
|
import { estimateEntryTokens } from "../tokens.js";
|
|
6
7
|
export const RECALL_OBSERVATION_TOOL_NAME = "recall";
|
|
@@ -315,7 +316,8 @@ function renderFoundResult(result) {
|
|
|
315
316
|
export const recallObservationTool = defineTool({
|
|
316
317
|
name: RECALL_OBSERVATION_TOOL_NAME,
|
|
317
318
|
label: "Recall memory evidence",
|
|
318
|
-
description: "Recover exact evidence and source context behind a compacted observational-memory observation or reflection id
|
|
319
|
+
description: "Recover exact evidence and source context behind a compacted observational-memory observation or reflection id " +
|
|
320
|
+
"from the current branch or an earlier session in the same working directory. " +
|
|
319
321
|
"Use when compressed memory is important and original source context is needed before acting.",
|
|
320
322
|
promptSnippet: "Use recall(<id>) to recover exact source context behind compacted memory observations/reflections when precision matters.",
|
|
321
323
|
promptGuidelines: [
|
|
@@ -340,10 +342,22 @@ export const recallObservationTool = defineTool({
|
|
|
340
342
|
return textResult(message, emptyDetails("invalid_id", memoryId, message));
|
|
341
343
|
}
|
|
342
344
|
const branchEntries = ctx.sessionManager.getBranch();
|
|
343
|
-
|
|
345
|
+
let result = recallMemorySources(branchEntries, memoryId);
|
|
344
346
|
if (result.status === "not_found") {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
+
const store = getProjectObsStore();
|
|
348
|
+
const projectId = store?.getProjectByCwd(ctx.cwd);
|
|
349
|
+
const archiveEntries = projectId ? store?.getProjectRecallSource?.(projectId, memoryId) : null;
|
|
350
|
+
if (archiveEntries && archiveEntries.length > 0) {
|
|
351
|
+
const archivedResult = recallMemorySources(archiveEntries, memoryId);
|
|
352
|
+
if (archivedResult.status === "found") {
|
|
353
|
+
result = archivedResult;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (result.status === "not_found") {
|
|
357
|
+
const message = `No observation or reflection with id ${memoryId} was found on the current branch ` +
|
|
358
|
+
`or in this project's cross-session memory.`;
|
|
359
|
+
return textResult(message, emptyDetails("not_found", memoryId, message));
|
|
360
|
+
}
|
|
347
361
|
}
|
|
348
362
|
return renderFoundResult(result);
|
|
349
363
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receive-agent-observations.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/receive-agent-observations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAIhF,eAAO,MAAM,oCAAoC,+BAA+B,CAAC;AAEjF,MAAM,WAAW,uBAAuB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,aAAa,GAAG,YAAY,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,4BAA4B;;;
|
|
1
|
+
{"version":3,"file":"receive-agent-observations.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/receive-agent-observations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAIhF,eAAO,MAAM,oCAAoC,+BAA+B,CAAC;AAEjF,MAAM,WAAW,uBAAuB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,aAAa,GAAG,YAAY,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,4BAA4B;;;iFAqEvC,CAAC;AAEH,wBAAgB,oCAAoC,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE5E"}
|