pi-posthorse 0.10.11 → 0.10.13
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/package.json +1 -1
- package/src/note-evidence.ts +5 -3
- package/src/note-guards.ts +15 -65
- package/src/note-provenance.ts +1 -9
- package/src/note-store.ts +1 -22
- package/src/notes.ts +1 -1
- package/src/recovery-note-touches.ts +12 -2
- package/src/recovery-notes.ts +18 -4
- package/src/rollover.ts +32 -16
package/package.json
CHANGED
package/src/note-evidence.ts
CHANGED
|
@@ -156,9 +156,11 @@ export function scopeRecoveryTouches(
|
|
|
156
156
|
if (address !== undefined && sameScope(address, recovery.binding)) {
|
|
157
157
|
return touch;
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
// This session's own file-tool effects keep their recorded address and kind in any scope.
|
|
160
|
+
if (touch.owner !== undefined && sameOwner(touch.owner, recovery.binding.owner)) {
|
|
161
|
+
return touch;
|
|
162
|
+
}
|
|
163
|
+
if (owned.has(touch.notePath)) {
|
|
162
164
|
return {
|
|
163
165
|
...touch,
|
|
164
166
|
absPath: notePath(recovery.binding, touch.notePath),
|
package/src/note-guards.ts
CHANGED
|
@@ -5,10 +5,7 @@ import { withFileMutationQueue, type ExtensionAPI } from "@earendil-works/pi-cod
|
|
|
5
5
|
import {
|
|
6
6
|
addressOf,
|
|
7
7
|
canonicalPath,
|
|
8
|
-
inside,
|
|
9
|
-
notePath,
|
|
10
8
|
sameScope,
|
|
11
|
-
scopeDirectory,
|
|
12
9
|
type NoteAddress,
|
|
13
10
|
type NoteTouchRecord,
|
|
14
11
|
} from "./note-scopes.ts";
|
|
@@ -68,48 +65,21 @@ function existingEdit(tool: string, operation: NoteOperation): boolean {
|
|
|
68
65
|
(tool === "write_files" && (operation.action === "write" || operation.action === "clear"))
|
|
69
66
|
);
|
|
70
67
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
operation: NoteOperation,
|
|
74
|
-
captured: CapturedCall,
|
|
75
|
-
): Promise<string> {
|
|
76
|
-
const { session, tool, call } = captured;
|
|
68
|
+
/** An edit of an inherited note in this session's scope applies to its private copy. */
|
|
69
|
+
async function prepareOperation(operation: NoteOperation, captured: CapturedCall): Promise<void> {
|
|
77
70
|
if (operation.action === "read") {
|
|
78
|
-
return
|
|
71
|
+
return;
|
|
79
72
|
}
|
|
80
|
-
return existingEdit(tool, operation) &&
|
|
81
|
-
session.scopes.some((scope) => sameScope(scope, address))
|
|
82
|
-
? session.guardReference(address.path, call)
|
|
83
|
-
: `The foreign note was not changed. This session writes ${notePath(session.binding, address.path)}. Retry explicitly there; creation semantics were not changed.`;
|
|
84
|
-
}
|
|
85
|
-
async function guardOperation(
|
|
86
|
-
operation: NoteOperation,
|
|
87
|
-
captured: CapturedCall,
|
|
88
|
-
): Promise<string | undefined> {
|
|
89
73
|
const { session, call, tool, cwd } = captured;
|
|
90
74
|
const path = toolPath(tool, operation.path, cwd);
|
|
91
75
|
const address = await noteAddress(path, session);
|
|
92
|
-
if (address === undefined) {
|
|
93
|
-
return
|
|
94
|
-
session.scopes.some((scope) =>
|
|
95
|
-
inside(canonicalPath(join(scope.root, ".forks")), canonicalPath(path)),
|
|
96
|
-
)
|
|
97
|
-
? `Managed notes metadata is not editable through file tools: ${path}. Use notes for this session’s checkpoint.`
|
|
98
|
-
: undefined;
|
|
99
|
-
}
|
|
100
|
-
if (!sameScope(address, session.binding)) {
|
|
101
|
-
return guardForeign(address, operation, captured);
|
|
76
|
+
if (address === undefined || !sameScope(address, session.binding)) {
|
|
77
|
+
return;
|
|
102
78
|
}
|
|
103
79
|
if (existingEdit(tool, operation) && !session.owns(address.path)) {
|
|
104
|
-
|
|
105
|
-
if (copied === "adopted") {
|
|
106
|
-
return `Private copy created at ${path}; no requested edit ran. Read this copy, then retry the edit.`;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (operation.action !== "read") {
|
|
110
|
-
await session.prepareWrite();
|
|
80
|
+
await withFileMutationQueue(path, () => session.adopt(address.path, call));
|
|
111
81
|
}
|
|
112
|
-
|
|
82
|
+
await session.prepareWrite();
|
|
113
83
|
}
|
|
114
84
|
async function recordedFiles(
|
|
115
85
|
operations: readonly NoteOperation[],
|
|
@@ -148,42 +118,22 @@ async function recordedFiles(
|
|
|
148
118
|
export function registerNoteGuards(pi: ExtensionAPI, store: NotesStore): void {
|
|
149
119
|
const calls = new Map<string, CapturedCall>();
|
|
150
120
|
pi.on("tool_call", async (event, ctx) => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
store
|
|
157
|
-
.roots()
|
|
158
|
-
.some((root) =>
|
|
159
|
-
inside(
|
|
160
|
-
root,
|
|
161
|
-
canonicalPath(toolPath(event.toolName, operation.path, ctx.cwd)),
|
|
162
|
-
),
|
|
163
|
-
),
|
|
164
|
-
)
|
|
165
|
-
) {
|
|
166
|
-
throw error;
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
});
|
|
170
|
-
if (session === undefined) {
|
|
121
|
+
let session: SessionNotes;
|
|
122
|
+
try {
|
|
123
|
+
session = await store.get(ctx);
|
|
124
|
+
} catch {
|
|
125
|
+
// Failed notes initialization skips bookkeeping; the notes tool reports the error itself.
|
|
171
126
|
return;
|
|
172
127
|
}
|
|
173
128
|
const call = session.startCall(event.toolCallId, event.parentToolCallId);
|
|
174
129
|
const captured = { session, call, cwd: ctx.cwd, tool: event.toolName };
|
|
175
130
|
calls.set(event.toolCallId, captured);
|
|
176
|
-
if (event.toolName === "notes"
|
|
131
|
+
if (event.toolName === "notes") {
|
|
177
132
|
return;
|
|
178
133
|
}
|
|
179
|
-
for (const operation of
|
|
180
|
-
|
|
181
|
-
const reason = await guardOperation(operation, captured);
|
|
182
|
-
if (reason !== undefined) {
|
|
183
|
-
return { block: true, reason };
|
|
184
|
-
}
|
|
134
|
+
for (const operation of noteOperations(event.toolName, event.input)) {
|
|
135
|
+
await prepareOperation(operation, captured);
|
|
185
136
|
}
|
|
186
|
-
return;
|
|
187
137
|
});
|
|
188
138
|
pi.on("tool_result", async (event) => {
|
|
189
139
|
const captured = calls.get(event.toolCallId);
|
package/src/note-provenance.ts
CHANGED
|
@@ -195,11 +195,6 @@ function seedCandidates(
|
|
|
195
195
|
retained: new Set(),
|
|
196
196
|
directories: new Set(scopes.map(scopeDirectory)),
|
|
197
197
|
});
|
|
198
|
-
if (scope === undefined && evidence.touches.length > 0) {
|
|
199
|
-
throw new Error(
|
|
200
|
-
"Incomplete notes snapshot: the source notes root is missing or ambiguous.",
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
198
|
const selected = touchedCandidates(evidence, scopes, allEntries);
|
|
204
199
|
for (const binding of bindings) {
|
|
205
200
|
for (const seed of seedsOf(binding, allEntries)) {
|
|
@@ -232,6 +227,7 @@ async function forkBinding(
|
|
|
232
227
|
);
|
|
233
228
|
for (const [index, candidate] of candidates.entries()) {
|
|
234
229
|
// Resolve against the immediate parent's files, not a historical ancestor's version.
|
|
230
|
+
// A missing source note is not copied; a known source scope still resolves it later.
|
|
235
231
|
const address = addresses[index];
|
|
236
232
|
if (address !== undefined) {
|
|
237
233
|
files.push({
|
|
@@ -242,10 +238,6 @@ async function forkBinding(
|
|
|
242
238
|
});
|
|
243
239
|
} else if (candidate.cleared) {
|
|
244
240
|
absent.push(candidate.path);
|
|
245
|
-
} else {
|
|
246
|
-
throw new Error(
|
|
247
|
-
`Incomplete notes snapshot: source note ${candidate.path} is unavailable.`,
|
|
248
|
-
);
|
|
249
241
|
}
|
|
250
242
|
}
|
|
251
243
|
return snapshot(
|
package/src/note-store.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
withFileMutationQueue,
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
type ExtensionContext,
|
|
7
|
-
} from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
4
|
import type { EntryLike, ProjectedEntry } from "./contracts.ts";
|
|
9
5
|
import { notesDir } from "./files.ts";
|
|
10
6
|
import { adoptNote, ensureManifest, readManifest, saveAdoption } from "./note-snapshots.ts";
|
|
@@ -219,13 +215,6 @@ export class SessionNotes {
|
|
|
219
215
|
async prepareWrite(): Promise<void> {
|
|
220
216
|
await ensureManifest(this.binding);
|
|
221
217
|
}
|
|
222
|
-
async guardReference(path: string, call: NoteCall): Promise<string> {
|
|
223
|
-
const destination = notePath(this.binding, path);
|
|
224
|
-
const copied = await withFileMutationQueue(destination, () => this.adopt(path, call));
|
|
225
|
-
return copied === "adopted"
|
|
226
|
-
? `Private copy created at ${destination}; the source was not edited. Read the private copy, then retry the edit on that path.`
|
|
227
|
-
: `This session writes only ${this.directory}. Read ${destination}, then retry there; the foreign path was not edited.`;
|
|
228
|
-
}
|
|
229
218
|
}
|
|
230
219
|
function sessionKey(ctx: ExtensionContext): string {
|
|
231
220
|
const header = ctx.sessionManager.getHeader();
|
|
@@ -236,7 +225,6 @@ function sessionKey(ctx: ExtensionContext): string {
|
|
|
236
225
|
}
|
|
237
226
|
export type NotesStore = {
|
|
238
227
|
readonly get: (ctx: ExtensionContext) => Promise<SessionNotes>;
|
|
239
|
-
readonly roots: () => readonly string[];
|
|
240
228
|
readonly recovery: (ctx: ExtensionContext) => NotesRecovery | undefined;
|
|
241
229
|
};
|
|
242
230
|
type SessionState =
|
|
@@ -244,7 +232,6 @@ type SessionState =
|
|
|
244
232
|
| { readonly kind: "ready"; readonly value: SessionNotes };
|
|
245
233
|
export function createNotesStore(pi: ExtensionAPI): NotesStore {
|
|
246
234
|
const sessions = new Map<string, SessionState>();
|
|
247
|
-
const roots = new Set<string>();
|
|
248
235
|
const get = (ctx: ExtensionContext): Promise<SessionNotes> => {
|
|
249
236
|
const key = sessionKey(ctx);
|
|
250
237
|
const existing = sessions.get(key);
|
|
@@ -258,19 +245,12 @@ export function createNotesStore(pi: ExtensionAPI): NotesStore {
|
|
|
258
245
|
const root =
|
|
259
246
|
bindings.findLast((binding) => header !== null && sameOwner(binding.owner, header))
|
|
260
247
|
?.root ?? canonicalPath(notesDir(ctx.cwd));
|
|
261
|
-
roots.add(root);
|
|
262
|
-
for (const binding of bindings) {
|
|
263
|
-
roots.add(binding.root);
|
|
264
|
-
}
|
|
265
248
|
const initialized = loadNotesBinding(ctx.sessionManager, root)
|
|
266
249
|
.then(({ binding, source, created }) => {
|
|
267
250
|
if (created) {
|
|
268
251
|
pi.appendEntry(NOTES_BINDING, binding);
|
|
269
252
|
}
|
|
270
253
|
const session = new SessionNotes(pi, binding, ctx.sessionManager, source);
|
|
271
|
-
for (const scope of session.scopes) {
|
|
272
|
-
roots.add(scope.root);
|
|
273
|
-
}
|
|
274
254
|
sessions.set(key, { kind: "ready", value: session });
|
|
275
255
|
return session;
|
|
276
256
|
})
|
|
@@ -283,7 +263,6 @@ export function createNotesStore(pi: ExtensionAPI): NotesStore {
|
|
|
283
263
|
};
|
|
284
264
|
return {
|
|
285
265
|
get,
|
|
286
|
-
roots: () => [...roots],
|
|
287
266
|
recovery: (ctx: ExtensionContext) => {
|
|
288
267
|
const state = sessions.get(sessionKey(ctx));
|
|
289
268
|
return state?.kind === "ready" ? state.value.recovery : undefined;
|
package/src/notes.ts
CHANGED
|
@@ -295,7 +295,7 @@ export function registerNotes(
|
|
|
295
295
|
label: "Notes",
|
|
296
296
|
...toolCards("notes"),
|
|
297
297
|
description:
|
|
298
|
-
"Durable notes across Git worktrees and context resets
|
|
298
|
+
"Durable task notes shared across Git worktrees and context resets; forks get private copies. Ops: list (newest first), read, search (case-insensitive), write (replace; empty clears), append (one record). list/search also cover other sessions' note scopes and show their selectors; pass scope to read, list, or search one of them. list/read/search are paged; continue with the returned offset. A read of a current note returns its file path.",
|
|
299
299
|
promptSnippet: "save and recall durable state with private notes for forks",
|
|
300
300
|
parameters: Type.Object({
|
|
301
301
|
op: Type.Union(
|
|
@@ -77,8 +77,11 @@ function matchMarkerPath(slash: string): string | undefined {
|
|
|
77
77
|
return /\/\.pi\/(?:agent\/)?notes\/[^/]+\/[^/]+\/[^/]+\/(.+)$/u.exec(slash)?.[1];
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function absoluteSlashPath(slash: string): boolean {
|
|
81
|
+
return slash.startsWith("/") || /^[A-Za-z]:\//u.test(slash);
|
|
82
|
+
}
|
|
80
83
|
function unscopedNoteTarget(trimmed: string, slash: string): NoteTarget | undefined {
|
|
81
|
-
if (
|
|
84
|
+
if (absoluteSlashPath(slash)) {
|
|
82
85
|
const base = normalizeRelPath(slash.slice(slash.lastIndexOf("/") + 1));
|
|
83
86
|
return base === "" ? undefined : { notePath: base, absPath: trimmed };
|
|
84
87
|
}
|
|
@@ -98,7 +101,14 @@ function resolveNoteTarget(
|
|
|
98
101
|
const matched = matchNoteDirectory(slash, [...knownDirs])?.path ?? matchMarkerPath(slash);
|
|
99
102
|
if (matched !== undefined) {
|
|
100
103
|
const rel = matched.startsWith(".forks/") ? "" : normalizeRelPath(matched);
|
|
101
|
-
|
|
104
|
+
if (rel === "") {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
if (absoluteSlashPath(slash)) {
|
|
108
|
+
return { notePath: rel, absPath: trimmed };
|
|
109
|
+
}
|
|
110
|
+
// The journal does not record a file tool's working directory, so its relative path has no known base.
|
|
111
|
+
return fromNotesTool ? { notePath: rel } : undefined;
|
|
102
112
|
}
|
|
103
113
|
return fromNotesTool ? unscopedNoteTarget(trimmed, slash) : undefined;
|
|
104
114
|
}
|
package/src/recovery-notes.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import type { EntryLike, ProjectedEntry } from "./contracts.ts";
|
|
2
3
|
import { sessionNoteTouches, scopeRecoveryTouches, type NotesRecovery } from "./note-evidence.ts";
|
|
4
|
+
import { scopeDirectory } from "./note-scopes.ts";
|
|
3
5
|
import type { NoteTarget, NoteTouch } from "./recovery-note-touches.ts";
|
|
4
6
|
|
|
5
7
|
const MAX_WRITTEN_NOTES = 4;
|
|
@@ -77,7 +79,7 @@ function applyTouch(
|
|
|
77
79
|
}
|
|
78
80
|
function formatNoteLabel(note: TrackedNote): string {
|
|
79
81
|
if (note.referenceScope !== undefined) {
|
|
80
|
-
return `${note.notePath} (
|
|
82
|
+
return `${note.notePath} (another notes scope: ${note.absPath ?? `${note.referenceScope}/${note.notePath}`})`;
|
|
81
83
|
}
|
|
82
84
|
return note.absPath !== undefined && note.absPath !== note.notePath
|
|
83
85
|
? `${note.notePath} (${note.absPath})`
|
|
@@ -116,14 +118,22 @@ function formatReadNoteLine(note: ReadNote, span: RetainedSpan | undefined): str
|
|
|
116
118
|
function formatNotesAction(
|
|
117
119
|
primaryWritten: WrittenNote | undefined,
|
|
118
120
|
span: RetainedSpan | undefined,
|
|
121
|
+
notesDirectory: string | undefined,
|
|
119
122
|
): string {
|
|
120
123
|
if (primaryWritten === undefined) {
|
|
121
124
|
return "Read the referenced note again if its context is still needed, and keep one current-state note for this task.";
|
|
122
125
|
}
|
|
126
|
+
// notes read resolves a bare name in this session's own scope, so a note elsewhere is named by its path.
|
|
127
|
+
const elsewhere =
|
|
128
|
+
primaryWritten.absPath !== undefined &&
|
|
129
|
+
notesDirectory !== undefined &&
|
|
130
|
+
primaryWritten.absPath !== join(notesDirectory, primaryWritten.notePath);
|
|
131
|
+
const target = elsewhere ? primaryWritten.absPath : primaryWritten.notePath;
|
|
123
132
|
if (span !== undefined && primaryWritten.proof.kind === "full") {
|
|
124
|
-
return `Your latest full write to ${
|
|
133
|
+
return `Your latest full write to ${target} is already visible above at entry ${primaryWritten.proof.entryId}; do not rewrite it unless new findings after entry ${span.lastId} require it.`;
|
|
125
134
|
}
|
|
126
|
-
|
|
135
|
+
const how = elsewhere ? "read on its path" : "notes read or read on its path";
|
|
136
|
+
return `Read ${target} (${how}) before continuing to restore the saved goal, progress, decisions, and next steps, then keep that note current as work progresses.`;
|
|
127
137
|
}
|
|
128
138
|
function trackedNotes(
|
|
129
139
|
touches: readonly NoteTouch[],
|
|
@@ -176,7 +186,11 @@ export function renderSessionNotes(
|
|
|
176
186
|
...readOnly.map((note) => formatReadNoteLine(note, span)),
|
|
177
187
|
];
|
|
178
188
|
const header = "Task notes in this session:";
|
|
179
|
-
const action = formatNotesAction(
|
|
189
|
+
const action = formatNotesAction(
|
|
190
|
+
written[0],
|
|
191
|
+
span,
|
|
192
|
+
options.notes === undefined ? defaultDir : scopeDirectory(options.notes.binding),
|
|
193
|
+
);
|
|
180
194
|
const selected: string[] = [];
|
|
181
195
|
for (const line of lines) {
|
|
182
196
|
if ([header, ...selected, line, action].join("\n").length <= maxChars) {
|
package/src/rollover.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BoundaryResult,
|
|
3
|
-
|
|
3
|
+
ContextWithSystemEvent,
|
|
4
4
|
ExtensionAPI,
|
|
5
5
|
ExtensionContext,
|
|
6
6
|
SessionBeforeCompactEvent,
|
|
@@ -113,25 +113,41 @@ function staleReminderPredicate(
|
|
|
113
113
|
isReminderType(message.customType) &&
|
|
114
114
|
(!enabled || !reminderMatches(message.details, fingerprint));
|
|
115
115
|
}
|
|
116
|
+
function retainedTailEnd(
|
|
117
|
+
messages: ContextWithSystemEvent["messages"],
|
|
118
|
+
start: number,
|
|
119
|
+
count: number,
|
|
120
|
+
): number | undefined {
|
|
121
|
+
let remaining = count;
|
|
122
|
+
for (let index = start; index < messages.length; index += 1) {
|
|
123
|
+
if (messages[index]?.role !== "system") {
|
|
124
|
+
remaining -= 1;
|
|
125
|
+
}
|
|
126
|
+
if (remaining === 0) {
|
|
127
|
+
return index + 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
116
132
|
function chronologicalRetainedTail(
|
|
117
|
-
messages:
|
|
133
|
+
messages: ContextWithSystemEvent["messages"],
|
|
118
134
|
ctx: ExtensionContext,
|
|
119
135
|
stale: StaleReminderPredicate,
|
|
120
|
-
):
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
first?.role === "compactionSummary" &&
|
|
125
|
-
hasRetainedRolloverMarker(first.summary)
|
|
126
|
-
) {
|
|
136
|
+
): ContextWithSystemEvent["messages"] {
|
|
137
|
+
const summaryIndex = messages.findIndex((message) => message.role !== "system");
|
|
138
|
+
const first = messages[summaryIndex];
|
|
139
|
+
if (first?.role === "compactionSummary" && hasRetainedRolloverMarker(first.summary)) {
|
|
127
140
|
const count = countRetainedMessages(ctx, stale);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
const end = count > 0 ? retainedTailEnd(messages, summaryIndex + 1, count) : undefined;
|
|
142
|
+
if (end !== undefined) {
|
|
143
|
+
const prefix = messages.slice(0, summaryIndex);
|
|
144
|
+
const retainedSlice = messages.slice(summaryIndex + 1, end);
|
|
145
|
+
const afterSlice = messages.slice(end);
|
|
146
|
+
if (retainedSlice.find((message) => message.role !== "system")?.role !== "assistant") {
|
|
147
|
+
return [...prefix, ...retainedSlice, first, ...afterSlice];
|
|
133
148
|
}
|
|
134
149
|
return [
|
|
150
|
+
...prefix,
|
|
135
151
|
{
|
|
136
152
|
role: "custom",
|
|
137
153
|
customType: "posthorse-retained-tail",
|
|
@@ -152,7 +168,7 @@ function registerReminderFilter(
|
|
|
152
168
|
policy: SnapshotReader,
|
|
153
169
|
reminders: ReminderLookup,
|
|
154
170
|
): void {
|
|
155
|
-
pi.on("
|
|
171
|
+
pi.on("context_with_system", (event, ctx) => {
|
|
156
172
|
const stale = staleReminderPredicate(event.messages, ctx, policy, reminders);
|
|
157
173
|
const filtered = event.messages.some(stale)
|
|
158
174
|
? event.messages.filter((message) => !stale(message))
|
|
@@ -411,8 +427,8 @@ export function registerRollover(
|
|
|
411
427
|
pi.on("session_tree", () => {
|
|
412
428
|
reminders.reset();
|
|
413
429
|
});
|
|
414
|
-
registerGuidance(pi, policy);
|
|
415
430
|
registerReminderFilter(pi, policy, reminders);
|
|
431
|
+
registerGuidance(pi, policy);
|
|
416
432
|
registerAutomaticRollover(pi, policy, activeToolTokens, resources.notes);
|
|
417
433
|
pi.on("turn_end", (event, ctx) => {
|
|
418
434
|
const summary = resetRequests.take(event.toolResults);
|