@howaboua/pi-notes-compaction 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/PCC-INTEGRATION.md +100 -0
- package/README.md +42 -2
- package/changelog.ts +337 -0
- package/package.json +50 -5
- package/src/bridge.ts +156 -0
- package/src/fresh-notes.ts +132 -0
- package/src/history.ts +198 -0
- package/src/index.ts +274 -0
- package/src/lifecycle.ts +538 -0
- package/src/notes.ts +440 -0
- package/src/settings.ts +48 -0
- package/src/store.ts +240 -0
- package/src/tools.ts +554 -0
- package/src/windows.ts +282 -0
package/src/tools.ts
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import type {
|
|
3
|
+
AgentToolResult,
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionContext,
|
|
6
|
+
ToolDefinition,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { Type } from "typebox";
|
|
9
|
+
import type {
|
|
10
|
+
CachedResponse,
|
|
11
|
+
LookupQuery,
|
|
12
|
+
SerializedCachedResponse,
|
|
13
|
+
SharedNotesDetails,
|
|
14
|
+
} from "./bridge.js";
|
|
15
|
+
import { queryKey } from "./bridge.js";
|
|
16
|
+
import { useHistory } from "./history.js";
|
|
17
|
+
import type { NotesLifecycle } from "./lifecycle.js";
|
|
18
|
+
import {
|
|
19
|
+
CACHE_RECEIPT,
|
|
20
|
+
collectNotes,
|
|
21
|
+
normalizeNoteParams,
|
|
22
|
+
sessionRef,
|
|
23
|
+
sourceSessionIds,
|
|
24
|
+
useNotes,
|
|
25
|
+
} from "./notes.js";
|
|
26
|
+
import type { NotesStore } from "./store.js";
|
|
27
|
+
|
|
28
|
+
const NOTES_ACTIONS = [
|
|
29
|
+
"help",
|
|
30
|
+
"list_files_by_prefix",
|
|
31
|
+
"read_file",
|
|
32
|
+
"search_contents",
|
|
33
|
+
"append_to_file",
|
|
34
|
+
"write_file",
|
|
35
|
+
] as const;
|
|
36
|
+
const HISTORY_ACTIONS = [
|
|
37
|
+
"help",
|
|
38
|
+
"list_windows",
|
|
39
|
+
"list_items",
|
|
40
|
+
"read_item",
|
|
41
|
+
"search_contents",
|
|
42
|
+
] as const;
|
|
43
|
+
export const NOTES_USAGE = "await tools.notes() // help";
|
|
44
|
+
export const HISTORY_USAGE = "await tools.history() // help";
|
|
45
|
+
const STRING = Type.Optional(Type.String());
|
|
46
|
+
const INTEGER = Type.Optional(Type.Integer({ minimum: 1 }));
|
|
47
|
+
const NOTES_SCHEMA = Type.Object(
|
|
48
|
+
{
|
|
49
|
+
action: Type.Optional(StringEnum(NOTES_ACTIONS)),
|
|
50
|
+
path: STRING,
|
|
51
|
+
text: STRING,
|
|
52
|
+
prefix: STRING,
|
|
53
|
+
query: STRING,
|
|
54
|
+
path_prefix: STRING,
|
|
55
|
+
start_line: INTEGER,
|
|
56
|
+
stop_line: INTEGER,
|
|
57
|
+
max_results: INTEGER,
|
|
58
|
+
max_files: INTEGER,
|
|
59
|
+
max_matches_per_file: INTEGER,
|
|
60
|
+
file_order: Type.Optional(StringEnum(["ascending", "descending"])),
|
|
61
|
+
file_order_by: Type.Optional(
|
|
62
|
+
StringEnum(["name", "created_at", "updated_at"]),
|
|
63
|
+
),
|
|
64
|
+
recent_file_first: Type.Optional(Type.Boolean()),
|
|
65
|
+
},
|
|
66
|
+
{ additionalProperties: false },
|
|
67
|
+
);
|
|
68
|
+
const HISTORY_SCHEMA = Type.Object(
|
|
69
|
+
{
|
|
70
|
+
action: Type.Optional(StringEnum(HISTORY_ACTIONS)),
|
|
71
|
+
agent_name: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
72
|
+
item_id: STRING,
|
|
73
|
+
window_id: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
74
|
+
query: STRING,
|
|
75
|
+
limit: INTEGER,
|
|
76
|
+
limit_chars: INTEGER,
|
|
77
|
+
max_chars_per_item: INTEGER,
|
|
78
|
+
offset_chars: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
79
|
+
recent_first: Type.Optional(Type.Boolean()),
|
|
80
|
+
role: Type.Optional(
|
|
81
|
+
Type.Union([
|
|
82
|
+
StringEnum(["user", "assistant", "tool", "system", "developer"]),
|
|
83
|
+
Type.Null(),
|
|
84
|
+
]),
|
|
85
|
+
),
|
|
86
|
+
tool_name: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
87
|
+
tool_namespace: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
88
|
+
},
|
|
89
|
+
{ additionalProperties: false },
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const FIELDS: Record<string, readonly string[]> = {
|
|
93
|
+
list_files_by_prefix: [
|
|
94
|
+
"prefix",
|
|
95
|
+
"file_order",
|
|
96
|
+
"file_order_by",
|
|
97
|
+
"max_results",
|
|
98
|
+
],
|
|
99
|
+
read_file: ["path", "start_line", "stop_line"],
|
|
100
|
+
search_notes: [
|
|
101
|
+
"query",
|
|
102
|
+
"path_prefix",
|
|
103
|
+
"max_files",
|
|
104
|
+
"max_matches_per_file",
|
|
105
|
+
"recent_file_first",
|
|
106
|
+
],
|
|
107
|
+
append_to_file: ["path", "text"],
|
|
108
|
+
write_file: ["path", "text"],
|
|
109
|
+
list_windows: ["agent_name", "limit", "recent_first"],
|
|
110
|
+
list_items: [
|
|
111
|
+
"agent_name",
|
|
112
|
+
"window_id",
|
|
113
|
+
"role",
|
|
114
|
+
"tool_name",
|
|
115
|
+
"tool_namespace",
|
|
116
|
+
"limit",
|
|
117
|
+
"max_chars_per_item",
|
|
118
|
+
"recent_first",
|
|
119
|
+
],
|
|
120
|
+
read_item: [
|
|
121
|
+
"agent_name",
|
|
122
|
+
"window_id",
|
|
123
|
+
"item_id",
|
|
124
|
+
"offset_chars",
|
|
125
|
+
"limit_chars",
|
|
126
|
+
],
|
|
127
|
+
search_history: [
|
|
128
|
+
"agent_name",
|
|
129
|
+
"window_id",
|
|
130
|
+
"role",
|
|
131
|
+
"tool_name",
|
|
132
|
+
"tool_namespace",
|
|
133
|
+
"query",
|
|
134
|
+
"limit",
|
|
135
|
+
"recent_first",
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
function relevant(
|
|
139
|
+
namespace: LookupQuery["namespace"],
|
|
140
|
+
input: unknown,
|
|
141
|
+
): Record<string, unknown> {
|
|
142
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
143
|
+
throw new Error("Supply an action object");
|
|
144
|
+
const params = input as Record<string, unknown>;
|
|
145
|
+
const action = params["action"];
|
|
146
|
+
const key =
|
|
147
|
+
action === "search_contents" ? `search_${namespace}` : String(action);
|
|
148
|
+
const fields = FIELDS[key];
|
|
149
|
+
if (!fields) throw new Error("Unknown action");
|
|
150
|
+
return Object.fromEntries(
|
|
151
|
+
Object.entries(params).filter(
|
|
152
|
+
([key]) => key === "action" || fields.includes(key),
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function result(payload: unknown): AgentToolResult<unknown> {
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
160
|
+
details: {},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function discovery(
|
|
165
|
+
namespace: LookupQuery["namespace"],
|
|
166
|
+
input: unknown,
|
|
167
|
+
): AgentToolResult<unknown> | undefined {
|
|
168
|
+
const params = input === undefined ? {} : input;
|
|
169
|
+
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
170
|
+
throw new Error("Supply an action object, or omit arguments for help");
|
|
171
|
+
const entries = Object.entries(params);
|
|
172
|
+
if (
|
|
173
|
+
!entries.length ||
|
|
174
|
+
(params as Record<string, unknown>)["action"] === "help"
|
|
175
|
+
) {
|
|
176
|
+
if (entries.some(([key]) => key !== "action"))
|
|
177
|
+
throw new Error("Help accepts only action: help");
|
|
178
|
+
const actions = namespace === "notes" ? NOTES_ACTIONS : HISTORY_ACTIONS;
|
|
179
|
+
return result({
|
|
180
|
+
actions: Object.fromEntries(
|
|
181
|
+
actions.map((action) => [
|
|
182
|
+
action,
|
|
183
|
+
action === "help"
|
|
184
|
+
? ""
|
|
185
|
+
: FIELDS[
|
|
186
|
+
action === "search_contents" ? `search_${namespace}` : action
|
|
187
|
+
]!.map((field) =>
|
|
188
|
+
["path", "text", "query", "item_id"].includes(field) ||
|
|
189
|
+
(action === "read_item" && field === "window_id")
|
|
190
|
+
? field
|
|
191
|
+
: `${field}?`,
|
|
192
|
+
).join(" "),
|
|
193
|
+
]),
|
|
194
|
+
),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function hybrid(
|
|
201
|
+
pi: ExtensionAPI,
|
|
202
|
+
store: NotesStore,
|
|
203
|
+
lifecycle: NotesLifecycle,
|
|
204
|
+
query: LookupQuery,
|
|
205
|
+
local: Record<string, unknown>,
|
|
206
|
+
callId: string,
|
|
207
|
+
ctx: ExtensionContext,
|
|
208
|
+
signal?: AbortSignal,
|
|
209
|
+
shared = false,
|
|
210
|
+
): Promise<AgentToolResult<unknown>> {
|
|
211
|
+
let responses: readonly CachedResponse[] = store.cached(
|
|
212
|
+
sourceSessionIds(ctx),
|
|
213
|
+
query,
|
|
214
|
+
);
|
|
215
|
+
let state = responses.length ? "cache" : "unavailable";
|
|
216
|
+
let lookupFailed = false;
|
|
217
|
+
const bridge = lifecycle.bridge;
|
|
218
|
+
if (bridge?.lookup) {
|
|
219
|
+
let fetched: readonly CachedResponse[] | undefined;
|
|
220
|
+
try {
|
|
221
|
+
fetched = await bridge.lookup(query, ctx, signal);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (signal?.aborted) throw error;
|
|
224
|
+
lookupFailed = true;
|
|
225
|
+
}
|
|
226
|
+
if (signal?.aborted) throw new Error("Lookup cancelled");
|
|
227
|
+
if (fetched) {
|
|
228
|
+
try {
|
|
229
|
+
store.cache(sessionRef(ctx), fetched);
|
|
230
|
+
} catch {
|
|
231
|
+
throw new Error(
|
|
232
|
+
"Remote results could not be retained locally. Retry this lookup when local storage is available.",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (fetched.length)
|
|
236
|
+
pi.appendEntry(CACHE_RECEIPT, {
|
|
237
|
+
protocol: 1,
|
|
238
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
239
|
+
});
|
|
240
|
+
const exact = fetched.filter(
|
|
241
|
+
(response) => queryKey(response.query) === queryKey(query),
|
|
242
|
+
);
|
|
243
|
+
if (exact.length) {
|
|
244
|
+
responses = exact;
|
|
245
|
+
state = "live";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const canProject =
|
|
250
|
+
shared ||
|
|
251
|
+
(Boolean(bridge?.projectResult) && bridge?.canProject?.(ctx) !== false);
|
|
252
|
+
if (responses.length && !canProject) state = "incompatible";
|
|
253
|
+
const remote = {
|
|
254
|
+
state,
|
|
255
|
+
...(lookupFailed
|
|
256
|
+
? {
|
|
257
|
+
lookup: "failed",
|
|
258
|
+
recovery: "Retry this lookup when remote access is available",
|
|
259
|
+
}
|
|
260
|
+
: {}),
|
|
261
|
+
coverage: responses.length
|
|
262
|
+
? responses.map((response) => ({
|
|
263
|
+
source: response.source,
|
|
264
|
+
fetched_at: response.fetchedAt,
|
|
265
|
+
coverage: response.coverage,
|
|
266
|
+
}))
|
|
267
|
+
: "No cached response for this exact query",
|
|
268
|
+
...(state === "cache"
|
|
269
|
+
? { scope: "Exact cached query only, not a complete offline archive" }
|
|
270
|
+
: {}),
|
|
271
|
+
...(state === "incompatible"
|
|
272
|
+
? {
|
|
273
|
+
recovery:
|
|
274
|
+
"Cached encrypted contents are unavailable on this model. Local notes remain available",
|
|
275
|
+
}
|
|
276
|
+
: {}),
|
|
277
|
+
};
|
|
278
|
+
const output = result({ source: "local", local, remote });
|
|
279
|
+
if (shared)
|
|
280
|
+
return {
|
|
281
|
+
...output,
|
|
282
|
+
details: {
|
|
283
|
+
codexHistoryNotes: {},
|
|
284
|
+
externalNotesResponses: responses.map(
|
|
285
|
+
({ bytes, ...response }): SerializedCachedResponse => ({
|
|
286
|
+
...response,
|
|
287
|
+
bytesBase64: Buffer.from(bytes).toString("base64"),
|
|
288
|
+
}),
|
|
289
|
+
),
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
if (responses.length && canProject && bridge?.projectResult) {
|
|
293
|
+
try {
|
|
294
|
+
return await bridge.projectResult(output, responses, callId, ctx, signal);
|
|
295
|
+
} catch {
|
|
296
|
+
throw new Error(
|
|
297
|
+
"Remote contents were cached, but delivery failed. Retry this lookup on a compatible model.",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return output;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function createQueryExecutor(
|
|
305
|
+
pi: ExtensionAPI,
|
|
306
|
+
store: NotesStore,
|
|
307
|
+
lifecycle: NotesLifecycle,
|
|
308
|
+
shared = false,
|
|
309
|
+
) {
|
|
310
|
+
const assertActive = () => {
|
|
311
|
+
if (!lifecycle.active)
|
|
312
|
+
throw new Error("Notes continuity is unavailable in this session");
|
|
313
|
+
};
|
|
314
|
+
async function notes(
|
|
315
|
+
callId: string,
|
|
316
|
+
input: unknown,
|
|
317
|
+
signal: AbortSignal | undefined,
|
|
318
|
+
ctx: ExtensionContext,
|
|
319
|
+
): Promise<AgentToolResult<unknown>> {
|
|
320
|
+
const help = discovery("notes", input);
|
|
321
|
+
if (help) return help;
|
|
322
|
+
assertActive();
|
|
323
|
+
const raw = relevant("notes", input);
|
|
324
|
+
const routed =
|
|
325
|
+
!shared &&
|
|
326
|
+
(await lifecycle.bridge?.route?.(
|
|
327
|
+
{ namespace: "notes", params: raw },
|
|
328
|
+
callId,
|
|
329
|
+
ctx,
|
|
330
|
+
signal,
|
|
331
|
+
));
|
|
332
|
+
if (routed) return routed;
|
|
333
|
+
const params = relevant(
|
|
334
|
+
"notes",
|
|
335
|
+
normalizeNoteParams(raw, lifecycle.agent(ctx)),
|
|
336
|
+
);
|
|
337
|
+
if (signal?.aborted) throw new Error("Note operation cancelled");
|
|
338
|
+
const local = useNotes(
|
|
339
|
+
pi,
|
|
340
|
+
store,
|
|
341
|
+
params,
|
|
342
|
+
ctx,
|
|
343
|
+
lifecycle.agent(ctx),
|
|
344
|
+
lifecycle.windows.current?.currentWindowId ?? "",
|
|
345
|
+
lifecycle.runId,
|
|
346
|
+
shared,
|
|
347
|
+
);
|
|
348
|
+
if (
|
|
349
|
+
params["action"] === "write_file" ||
|
|
350
|
+
params["action"] === "append_to_file"
|
|
351
|
+
)
|
|
352
|
+
return {
|
|
353
|
+
...result(local),
|
|
354
|
+
details: {
|
|
355
|
+
...(shared
|
|
356
|
+
? { codexHistoryNotes: {}, notesCompactionRouted: true }
|
|
357
|
+
: {
|
|
358
|
+
notesCompaction: {
|
|
359
|
+
protocol: 1,
|
|
360
|
+
saved: true,
|
|
361
|
+
runId: lifecycle.runId,
|
|
362
|
+
},
|
|
363
|
+
}),
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
const notes = collectNotes(store, ctx.sessionManager.getBranch());
|
|
367
|
+
if (
|
|
368
|
+
params["action"] === "read_file" &&
|
|
369
|
+
notes.get(params["path"] as string)?.mode === "replace"
|
|
370
|
+
)
|
|
371
|
+
return result({
|
|
372
|
+
source: "local",
|
|
373
|
+
local,
|
|
374
|
+
remote: { state: "superseded_by_local_replacement" },
|
|
375
|
+
});
|
|
376
|
+
const replacements = [...notes.values()]
|
|
377
|
+
.filter((note) => note.mode === "replace")
|
|
378
|
+
.map((note) => `${lifecycle.agent(ctx)}/notes/${note.path}`);
|
|
379
|
+
const remoteParams = { ...params };
|
|
380
|
+
for (const field of ["path", "prefix", "path_prefix"])
|
|
381
|
+
if (typeof remoteParams[field] === "string")
|
|
382
|
+
remoteParams[field] =
|
|
383
|
+
`${lifecycle.agent(ctx)}/notes${remoteParams[field] ? `/${remoteParams[field]}` : ""}`;
|
|
384
|
+
return hybrid(
|
|
385
|
+
pi,
|
|
386
|
+
store,
|
|
387
|
+
lifecycle,
|
|
388
|
+
{ namespace: "notes", params: remoteParams },
|
|
389
|
+
{
|
|
390
|
+
...local,
|
|
391
|
+
...(replacements.length
|
|
392
|
+
? {
|
|
393
|
+
local_replacements_authoritative: replacements.slice(0, 100),
|
|
394
|
+
replacements_truncated: replacements.length > 100,
|
|
395
|
+
}
|
|
396
|
+
: {}),
|
|
397
|
+
},
|
|
398
|
+
callId,
|
|
399
|
+
ctx,
|
|
400
|
+
signal,
|
|
401
|
+
shared,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
async function history(
|
|
405
|
+
callId: string,
|
|
406
|
+
input: unknown,
|
|
407
|
+
signal: AbortSignal | undefined,
|
|
408
|
+
ctx: ExtensionContext,
|
|
409
|
+
): Promise<AgentToolResult<unknown>> {
|
|
410
|
+
const help = discovery("history", input);
|
|
411
|
+
if (help) return help;
|
|
412
|
+
assertActive();
|
|
413
|
+
const params = relevant("history", input);
|
|
414
|
+
const query = { namespace: "history", params } as const;
|
|
415
|
+
const routed =
|
|
416
|
+
!shared && (await lifecycle.bridge?.route?.(query, callId, ctx, signal));
|
|
417
|
+
if (routed) return routed;
|
|
418
|
+
if (params["agent_name"] && params["agent_name"] !== lifecycle.agent(ctx))
|
|
419
|
+
throw new Error(
|
|
420
|
+
"That agent's history is not available here. Omit agent_name to search this session.",
|
|
421
|
+
);
|
|
422
|
+
return hybrid(
|
|
423
|
+
pi,
|
|
424
|
+
store,
|
|
425
|
+
lifecycle,
|
|
426
|
+
query,
|
|
427
|
+
useHistory(
|
|
428
|
+
ctx.sessionManager.getBranch(),
|
|
429
|
+
params,
|
|
430
|
+
ctx.sessionManager.getSessionId(),
|
|
431
|
+
),
|
|
432
|
+
callId,
|
|
433
|
+
ctx,
|
|
434
|
+
signal,
|
|
435
|
+
shared,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return { notes, history };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function createSharedExecutor(
|
|
442
|
+
pi: ExtensionAPI,
|
|
443
|
+
store: NotesStore,
|
|
444
|
+
lifecycle: NotesLifecycle,
|
|
445
|
+
) {
|
|
446
|
+
const queries = createQueryExecutor(pi, store, lifecycle, true);
|
|
447
|
+
return async (
|
|
448
|
+
query: LookupQuery,
|
|
449
|
+
callId: string,
|
|
450
|
+
ctx: ExtensionContext,
|
|
451
|
+
signal?: AbortSignal,
|
|
452
|
+
): Promise<AgentToolResult<SharedNotesDetails>> => {
|
|
453
|
+
const output = await queries[query.namespace](
|
|
454
|
+
callId,
|
|
455
|
+
query.params,
|
|
456
|
+
signal,
|
|
457
|
+
ctx,
|
|
458
|
+
);
|
|
459
|
+
return {
|
|
460
|
+
...output,
|
|
461
|
+
details: { codexHistoryNotes: {}, ...(output.details as object) },
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function createTools(
|
|
467
|
+
pi: ExtensionAPI,
|
|
468
|
+
store: NotesStore,
|
|
469
|
+
lifecycle: NotesLifecycle,
|
|
470
|
+
): ToolDefinition[] {
|
|
471
|
+
const assertActive = () => {
|
|
472
|
+
if (!lifecycle.active)
|
|
473
|
+
throw new Error("Notes continuity is unavailable in this session");
|
|
474
|
+
};
|
|
475
|
+
const queries = createQueryExecutor(pi, store, lifecycle);
|
|
476
|
+
return [
|
|
477
|
+
{
|
|
478
|
+
name: "notes",
|
|
479
|
+
label: "notes",
|
|
480
|
+
description:
|
|
481
|
+
"Cross-window notes. write_file replaces; append_to_file adds. Virtual paths: relative uses current agent; cross-agent uses <agent>/notes[/path].",
|
|
482
|
+
parameters: NOTES_SCHEMA,
|
|
483
|
+
executionMode: "sequential",
|
|
484
|
+
prepareArguments(args) {
|
|
485
|
+
if (args === undefined) return {};
|
|
486
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
487
|
+
return args;
|
|
488
|
+
const params = { ...args } as Record<string, unknown>;
|
|
489
|
+
if (
|
|
490
|
+
params["action"] === "list_files_by_prefix" &&
|
|
491
|
+
params["max_files"] !== undefined
|
|
492
|
+
) {
|
|
493
|
+
params["max_results"] ??= params["max_files"];
|
|
494
|
+
delete params["max_files"];
|
|
495
|
+
}
|
|
496
|
+
return params;
|
|
497
|
+
},
|
|
498
|
+
execute: (callId, input, signal, _update, ctx) =>
|
|
499
|
+
queries.notes(callId, input, signal, ctx),
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
name: "history",
|
|
503
|
+
label: "history",
|
|
504
|
+
description:
|
|
505
|
+
"Prior-window detail. Pass IDs unchanged. Search, never browse",
|
|
506
|
+
parameters: HISTORY_SCHEMA,
|
|
507
|
+
prepareArguments(args) {
|
|
508
|
+
return args === undefined ? {} : args;
|
|
509
|
+
},
|
|
510
|
+
execute: (callId, input, signal, _update, ctx) =>
|
|
511
|
+
queries.history(callId, input, signal, ctx),
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "new_context",
|
|
515
|
+
label: "new_context",
|
|
516
|
+
description: "Start a new context window",
|
|
517
|
+
parameters: Type.Object({}, { additionalProperties: false }),
|
|
518
|
+
exposure: "model-only",
|
|
519
|
+
executionMode: "sequential",
|
|
520
|
+
async execute(_id, _input, signal, _update, ctx) {
|
|
521
|
+
assertActive();
|
|
522
|
+
if (signal?.aborted) throw new Error("Rollover cancelled");
|
|
523
|
+
const started = lifecycle.requestRollover(ctx);
|
|
524
|
+
if (started) ctx.abort();
|
|
525
|
+
return {
|
|
526
|
+
...result({
|
|
527
|
+
started,
|
|
528
|
+
message: started
|
|
529
|
+
? lifecycle.normalCompaction
|
|
530
|
+
? "Normal compaction will run before the new window opens"
|
|
531
|
+
: "A new window will continue from your notes after this run settles"
|
|
532
|
+
: "A checkpoint or rollover is already pending. Save notes and finish your response",
|
|
533
|
+
}),
|
|
534
|
+
...(started ? { terminate: true } : {}),
|
|
535
|
+
};
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: "get_context_remaining",
|
|
540
|
+
label: "get_context_remaining",
|
|
541
|
+
description: "Remaining context tokens",
|
|
542
|
+
parameters: Type.Object({}, { additionalProperties: false }),
|
|
543
|
+
async execute(_id, _input, _signal, _update, ctx) {
|
|
544
|
+
assertActive();
|
|
545
|
+
const remaining = lifecycle.windows.remaining(ctx);
|
|
546
|
+
return result({
|
|
547
|
+
...remaining,
|
|
548
|
+
remainingTokens: remaining.remainingTokens ?? null,
|
|
549
|
+
remainingPercent: remaining.remainingPercent ?? null,
|
|
550
|
+
});
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
];
|
|
554
|
+
}
|