pi-ui-extend 1.0.7 → 1.0.8
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.
|
@@ -809,6 +809,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
809
809
|
await saveDcpState(ctx, state)
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
+
const anchorsBeforeFinalization = state.nudgeAnchors.length
|
|
812
813
|
if (state.manualMode) {
|
|
813
814
|
state.nudgeAnchors = state.nudgeAnchors.filter((anchor) =>
|
|
814
815
|
anchor.type === "context-strong" || anchor.type === "context-soft",
|
|
@@ -817,6 +818,9 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
817
818
|
applyAnchoredNudges(prunedMessages, state, (anchor) =>
|
|
818
819
|
appendConcreteNudgeGuidance(baseNudgeText(anchor.type), candidate, messageCandidates, state),
|
|
819
820
|
)
|
|
821
|
+
if (state.nudgeAnchors.length !== anchorsBeforeFinalization) {
|
|
822
|
+
await saveDcpState(ctx, state)
|
|
823
|
+
}
|
|
820
824
|
|
|
821
825
|
return finishContext("complete", prunedMessages, {
|
|
822
826
|
candidate,
|
|
@@ -51,7 +51,6 @@ function isRealAnchorCandidate(msg: any): boolean {
|
|
|
51
51
|
const role = msg?.role ?? "";
|
|
52
52
|
if (role !== "user" && role !== "assistant") return false;
|
|
53
53
|
const text = messageText(msg);
|
|
54
|
-
if (text.includes("<dcp-system-reminder>")) return false;
|
|
55
54
|
if (extractBlockId(text) !== undefined) return false;
|
|
56
55
|
return true;
|
|
57
56
|
}
|
|
@@ -94,6 +93,12 @@ function anchorMatchesMessage(anchor: DcpNudgeAnchor, msg: any, index: number):
|
|
|
94
93
|
return msg?.timestamp === anchor.anchorTimestamp;
|
|
95
94
|
}
|
|
96
95
|
|
|
96
|
+
function isNewerAnchor(candidate: DcpNudgeAnchor, current: DcpNudgeAnchor): boolean {
|
|
97
|
+
if (candidate.updatedAt !== current.updatedAt) return candidate.updatedAt > current.updatedAt;
|
|
98
|
+
if (candidate.createdAt !== current.createdAt) return candidate.createdAt > current.createdAt;
|
|
99
|
+
return candidate.id > current.id;
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
function appendTextToMessage(msg: any, text: string): void {
|
|
98
103
|
const suffix = `\n\n${text}`;
|
|
99
104
|
if (typeof msg.content === "string") {
|
|
@@ -205,15 +210,27 @@ export function upsertNudgeAnchor(
|
|
|
205
210
|
options: { contextPercent?: number } = {},
|
|
206
211
|
): { anchor: DcpNudgeAnchor | null; created: boolean; updated: boolean } {
|
|
207
212
|
const target = findAnchorMessage(messages);
|
|
208
|
-
if (!target)
|
|
213
|
+
if (!target) {
|
|
214
|
+
// The caller will append one synthetic tail reminder. Drop persisted
|
|
215
|
+
// anchors first so applyAnchoredNudges cannot render a second reminder.
|
|
216
|
+
state.nudgeAnchors = [];
|
|
217
|
+
state.lastNudge = undefined;
|
|
218
|
+
return { anchor: null, created: false, updated: false };
|
|
219
|
+
}
|
|
209
220
|
|
|
210
221
|
const key = `${target.stableId}|${target.timestamp}`;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
let existing: DcpNudgeAnchor | null = null;
|
|
223
|
+
for (const anchor of state.nudgeAnchors) {
|
|
224
|
+
const anchorKey = `${anchor.anchorStableId ?? ""}|${anchor.anchorTimestamp}`;
|
|
225
|
+
if (anchorKey !== key) continue;
|
|
226
|
+
if (!existing || isNewerAnchor(anchor, existing)) existing = anchor;
|
|
227
|
+
}
|
|
214
228
|
|
|
215
229
|
const now = Date.now();
|
|
216
230
|
if (existing) {
|
|
231
|
+
// Older sidecars may contain several anchors. Keep only the anchor for the
|
|
232
|
+
// current target so every subsequent context pass has singleton state.
|
|
233
|
+
state.nudgeAnchors = [existing];
|
|
217
234
|
const shouldUpgrade = typePriority(type) > typePriority(existing.type);
|
|
218
235
|
if (shouldUpgrade) existing.type = type;
|
|
219
236
|
existing.updatedAt = now;
|
|
@@ -240,7 +257,9 @@ export function upsertNudgeAnchor(
|
|
|
240
257
|
createdAt: now,
|
|
241
258
|
updatedAt: now,
|
|
242
259
|
};
|
|
243
|
-
|
|
260
|
+
// A nudge follows the latest useful message. Replacing the previous anchor
|
|
261
|
+
// prevents one full reminder from accumulating per turn/assistant response.
|
|
262
|
+
state.nudgeAnchors = [anchor];
|
|
244
263
|
state.lastNudge = {
|
|
245
264
|
type,
|
|
246
265
|
anchorId: anchor.id,
|
|
@@ -259,15 +278,19 @@ export function applyAnchoredNudges(
|
|
|
259
278
|
): void {
|
|
260
279
|
if (state.nudgeAnchors.length === 0) return;
|
|
261
280
|
|
|
262
|
-
|
|
281
|
+
let selected: { anchor: DcpNudgeAnchor; index: number } | null = null;
|
|
263
282
|
for (const anchor of state.nudgeAnchors) {
|
|
264
283
|
const index = messages.findIndex((msg, messageIndex) => anchorMatchesMessage(anchor, msg, messageIndex));
|
|
265
284
|
if (index === -1) continue;
|
|
266
|
-
|
|
267
|
-
|
|
285
|
+
if (!selected || isNewerAnchor(anchor, selected.anchor)) {
|
|
286
|
+
selected = { anchor, index };
|
|
287
|
+
}
|
|
268
288
|
}
|
|
269
289
|
|
|
270
|
-
state
|
|
290
|
+
// Defensive migration for persisted pre-singleton state: render only the
|
|
291
|
+
// newest valid anchor and discard every stale predecessor.
|
|
292
|
+
state.nudgeAnchors = selected ? [selected.anchor] : [];
|
|
293
|
+
if (selected) appendTextToMessage(messages[selected.index], render(selected.anchor));
|
|
271
294
|
}
|
|
272
295
|
|
|
273
296
|
export function clearDcpNudgeAnchors(state: DcpState): number {
|
package/package.json
CHANGED