@oh-my-pi/pi-coding-agent 14.6.3 → 14.6.4
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 +24 -0
- package/package.json +7 -7
- package/src/config/settings-schema.ts +25 -0
- package/src/edit/modes/hashline.ts +191 -2
- package/src/hindsight/backend.ts +85 -324
- package/src/hindsight/client.ts +153 -0
- package/src/hindsight/config.ts +10 -0
- package/src/hindsight/content.ts +9 -4
- package/src/hindsight/index.ts +2 -0
- package/src/hindsight/mental-models.ts +382 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +469 -0
- package/src/memory-backend/types.ts +14 -4
- package/src/modes/controllers/command-controller.ts +263 -4
- package/src/prompts/tools/hashline.md +1 -0
- package/src/sdk.ts +10 -1
- package/src/session/agent-session.ts +44 -1
- package/src/slash-commands/builtin-registry.ts +10 -0
- package/src/task/executor.ts +3 -0
- package/src/task/index.ts +2 -0
- package/src/tools/hindsight-recall.ts +1 -3
- package/src/tools/hindsight-reflect.ts +1 -3
- package/src/tools/hindsight-retain.ts +6 -9
- package/src/tools/index.ts +3 -0
- package/src/hindsight/retain-queue.ts +0 -166
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global, debounced batch queue for tool-initiated `retain` calls.
|
|
3
|
-
*
|
|
4
|
-
* The `retain` tool used to block on a single-item HTTP round trip per
|
|
5
|
-
* invocation. Now it pushes onto a per-session queue and returns immediately;
|
|
6
|
-
* a flush fires when:
|
|
7
|
-
* 1. the queue reaches `FLUSH_BATCH_SIZE`, or
|
|
8
|
-
* 2. `FLUSH_INTERVAL_MS` elapses since the queue first became non-empty.
|
|
9
|
-
*
|
|
10
|
-
* On batch failure we surface a UI-only notice via `session.emitNotice` —
|
|
11
|
-
* a single yellow "Hindsight: memory retention failed …" line in the TUI.
|
|
12
|
-
* The LLM is NOT told; the agent already received "Memory queued" and has
|
|
13
|
-
* moved on. This is purely so the user knows their facts didn't persist.
|
|
14
|
-
*
|
|
15
|
-
* Auto-retain (`retainSession` in backend.ts) is intentionally NOT routed
|
|
16
|
-
* through this queue — it submits a full transcript as one large item and
|
|
17
|
-
* already runs `async: true` server-side.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
21
|
-
import { getHindsightSessionState, type HindsightSessionState } from "./backend";
|
|
22
|
-
import { ensureBankMission } from "./bank";
|
|
23
|
-
import type { MemoryItemInput } from "./client";
|
|
24
|
-
|
|
25
|
-
const FLUSH_BATCH_SIZE = 16;
|
|
26
|
-
const FLUSH_INTERVAL_MS = 5_000;
|
|
27
|
-
|
|
28
|
-
interface PendingItem {
|
|
29
|
-
content: string;
|
|
30
|
-
context?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface SessionQueue {
|
|
34
|
-
items: PendingItem[];
|
|
35
|
-
timer?: NodeJS.Timeout;
|
|
36
|
-
/** Currently in-flight flush; subsequent flushes await it before running. */
|
|
37
|
-
flushing?: Promise<void>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const QUEUES = new Map<string, SessionQueue>();
|
|
41
|
-
|
|
42
|
-
/** Push a memory item onto the session's retain queue. Returns immediately. */
|
|
43
|
-
export function enqueueRetain(sessionId: string, content: string, context?: string): void {
|
|
44
|
-
const queue = QUEUES.get(sessionId) ?? createQueue(sessionId);
|
|
45
|
-
queue.items.push({ content, context });
|
|
46
|
-
|
|
47
|
-
if (queue.items.length >= FLUSH_BATCH_SIZE) {
|
|
48
|
-
void flushSessionQueue(sessionId);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (!queue.timer) {
|
|
52
|
-
queue.timer = setTimeout(() => {
|
|
53
|
-
void flushSessionQueue(sessionId);
|
|
54
|
-
}, FLUSH_INTERVAL_MS);
|
|
55
|
-
// Don't pin the event loop alive just for a pending retain flush.
|
|
56
|
-
queue.timer.unref?.();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Flush a single session's queue. Safe to call when empty or already in flight. */
|
|
61
|
-
export async function flushSessionQueue(sessionId: string): Promise<void> {
|
|
62
|
-
const queue = QUEUES.get(sessionId);
|
|
63
|
-
if (!queue) return;
|
|
64
|
-
|
|
65
|
-
if (queue.timer) {
|
|
66
|
-
clearTimeout(queue.timer);
|
|
67
|
-
queue.timer = undefined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (queue.flushing) {
|
|
71
|
-
// Coalesce: wait for the in-flight flush, then drain anything that
|
|
72
|
-
// landed after it started so we don't strand items.
|
|
73
|
-
await queue.flushing;
|
|
74
|
-
if (queue.items.length > 0) {
|
|
75
|
-
await flushSessionQueue(sessionId);
|
|
76
|
-
}
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (queue.items.length === 0) {
|
|
81
|
-
QUEUES.delete(sessionId);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const items = queue.items.splice(0);
|
|
86
|
-
const flushPromise = doFlush(sessionId, items);
|
|
87
|
-
queue.flushing = flushPromise;
|
|
88
|
-
try {
|
|
89
|
-
await flushPromise;
|
|
90
|
-
} finally {
|
|
91
|
-
queue.flushing = undefined;
|
|
92
|
-
if (queue.items.length === 0 && !queue.timer) {
|
|
93
|
-
QUEUES.delete(sessionId);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Flush every pending session queue. Called from `clear`/`enqueue` backend hooks. */
|
|
99
|
-
export async function flushAllRetainQueues(): Promise<void> {
|
|
100
|
-
const ids = [...QUEUES.keys()];
|
|
101
|
-
await Promise.all(ids.map(id => flushSessionQueue(id)));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Test helper: clear timers and pending items without triggering flushes. */
|
|
105
|
-
export function clearRetainQueueForTest(): void {
|
|
106
|
-
for (const queue of QUEUES.values()) {
|
|
107
|
-
if (queue.timer) clearTimeout(queue.timer);
|
|
108
|
-
}
|
|
109
|
-
QUEUES.clear();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Test helper: peek at queued count for a session. */
|
|
113
|
-
export function getRetainQueueDepthForTest(sessionId: string): number {
|
|
114
|
-
return QUEUES.get(sessionId)?.items.length ?? 0;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function doFlush(sessionId: string, items: PendingItem[]): Promise<void> {
|
|
118
|
-
const state = getHindsightSessionState(sessionId);
|
|
119
|
-
if (!state) {
|
|
120
|
-
// Session went away before we could flush. We can't notify anyone, so
|
|
121
|
-
// log and drop — these are best-effort facts, not transactional writes.
|
|
122
|
-
logger.warn("Hindsight retain queue: session vanished, dropping batch", {
|
|
123
|
-
sessionId,
|
|
124
|
-
items: items.length,
|
|
125
|
-
});
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
await ensureBankMission(state.client, state.bankId, state.config, state.missionsSet);
|
|
131
|
-
const batch: MemoryItemInput[] = items.map(item => ({
|
|
132
|
-
content: item.content,
|
|
133
|
-
context: item.context ?? state.config.retainContext,
|
|
134
|
-
metadata: { session_id: sessionId },
|
|
135
|
-
tags: state.retainTags,
|
|
136
|
-
}));
|
|
137
|
-
await state.client.retainBatch(state.bankId, batch, { async: true });
|
|
138
|
-
if (state.config.debug) {
|
|
139
|
-
logger.debug("Hindsight retain queue: batch flushed", {
|
|
140
|
-
sessionId,
|
|
141
|
-
bankId: state.bankId,
|
|
142
|
-
items: items.length,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
} catch (err) {
|
|
146
|
-
const errorText = err instanceof Error ? err.message : String(err);
|
|
147
|
-
logger.warn("Hindsight retain queue: batch flush failed", {
|
|
148
|
-
sessionId,
|
|
149
|
-
bankId: state.bankId,
|
|
150
|
-
items: items.length,
|
|
151
|
-
error: errorText,
|
|
152
|
-
});
|
|
153
|
-
notifyRetainFailure(state, items.length, errorText);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function notifyRetainFailure(state: HindsightSessionState, count: number, errorText: string): void {
|
|
158
|
-
const noun = count === 1 ? "memory" : "memories";
|
|
159
|
-
state.session.emitNotice("warning", `Memory retention failed for ${count} ${noun}: ${errorText}`, "Hindsight");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function createQueue(sessionId: string): SessionQueue {
|
|
163
|
-
const queue: SessionQueue = { items: [] };
|
|
164
|
-
QUEUES.set(sessionId, queue);
|
|
165
|
-
return queue;
|
|
166
|
-
}
|