@astrosheep/pi-context 0.25.2 → 0.26.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/README.md +88 -7
- package/dist/build-info.json +2 -2
- package/dist/extension.js +635 -381
- package/dist/src/context/boot.d.ts +24 -0
- package/dist/src/context/boot.js +33 -24
- package/dist/src/context/budget.d.ts +9 -0
- package/dist/src/context/budget.js +51 -28
- package/dist/src/context/context-window.d.ts +41 -0
- package/dist/src/context/context-window.js +16 -1
- package/dist/src/context/prompts.d.ts +20 -0
- package/dist/src/context/prompts.js +1 -1
- package/dist/src/context/reset-artifacts.d.ts +26 -0
- package/dist/src/context/reset-artifacts.js +18 -17
- package/dist/src/context/reset-lifecycle.d.ts +89 -0
- package/dist/src/context/reset-lifecycle.js +103 -75
- package/dist/src/context/runtime.d.ts +3 -0
- package/dist/src/context/runtime.js +53 -21
- package/dist/src/context/thresholds.d.ts +33 -0
- package/dist/src/context/thresholds.js +1 -1
- package/dist/src/dream/cli.d.ts +10 -0
- package/dist/src/dream/cli.js +1 -1
- package/dist/src/dream/doctor.d.ts +2 -0
- package/dist/src/dream/doctor.js +6 -2
- package/dist/src/dream/gates.d.ts +10 -0
- package/dist/src/dream/git.d.ts +21 -0
- package/dist/src/dream/lock.d.ts +31 -0
- package/dist/src/dream/runner.d.ts +30 -0
- package/dist/src/dream/settings.d.ts +16 -0
- package/dist/src/history/history-tools.d.ts +2 -0
- package/dist/src/history/history.d.ts +57 -0
- package/dist/src/index.d.ts +39 -0
- package/dist/src/index.js +4 -4
- package/dist/src/notes/address.d.ts +26 -0
- package/dist/src/notes/address.js +8 -14
- package/dist/src/notes/constants.d.ts +3 -0
- package/dist/src/notes/constants.js +3 -0
- package/dist/src/notes/context.d.ts +10 -0
- package/dist/src/notes/context.js +33 -0
- package/dist/src/notes/frontmatter.d.ts +46 -0
- package/dist/src/notes/frontmatter.js +10 -5
- package/dist/src/notes/index.d.ts +4 -0
- package/dist/src/notes/index.js +2 -0
- package/dist/src/notes/paths.d.ts +21 -0
- package/dist/src/notes/paths.js +72 -76
- package/dist/src/notes/store.d.ts +94 -0
- package/dist/src/notes/store.js +276 -236
- package/dist/src/pi/notes/adapter.d.ts +12 -0
- package/dist/src/pi/notes/adapter.js +39 -0
- package/dist/src/pi/notes/session-replay.d.ts +16 -0
- package/dist/src/{notes → pi/notes}/session-replay.js +2 -2
- package/dist/src/pi/notes/snapshot.d.ts +33 -0
- package/dist/src/{notes/notes-snapshot.js → pi/notes/snapshot.js} +11 -3
- package/dist/src/pi/notes/tools.d.ts +2 -0
- package/dist/src/{notes → pi/notes}/tools.js +24 -21
- package/dist/src/protocol.d.ts +41 -0
- package/dist/src/protocol.js +4 -6
- package/dist/src/session-reader.d.ts +5 -0
- package/dist/src/settings.d.ts +6 -0
- package/dist/src/text-match.d.ts +5 -0
- package/dist/src/text-match.js +15 -0
- package/dist/src/tool-output.d.ts +97 -0
- package/dist/src/tool-output.js +1 -15
- package/dist/src/tool-schema.d.ts +17 -0
- package/dist/test/agent-loop.test.d.ts +1 -0
- package/dist/test/agent-loop.test.js +387 -20
- package/dist/test/boot.integration.test.d.ts +1 -0
- package/dist/test/boot.integration.test.js +59 -35
- package/dist/test/budget-settings.integration.test.d.ts +1 -0
- package/dist/test/budget-settings.integration.test.js +28 -12
- package/dist/test/doctor.test.d.ts +1 -0
- package/dist/test/doctor.test.js +10 -2
- package/dist/test/dream-skill.test.d.ts +1 -0
- package/dist/test/dream-skill.test.js +69 -0
- package/dist/test/dream.test.d.ts +1 -0
- package/dist/test/helpers/extension-test-environment.d.ts +1 -0
- package/dist/test/helpers/extension-test-environment.js +9 -0
- package/dist/test/helpers/extension.d.ts +104 -0
- package/dist/test/helpers/extension.js +7 -81
- package/dist/test/helpers/notes.d.ts +6 -0
- package/dist/test/helpers/notes.js +13 -0
- package/dist/test/history.integration.test.d.ts +1 -0
- package/dist/test/history.integration.test.js +17 -19
- package/dist/test/notes-library.test.d.ts +1 -0
- package/dist/test/notes-library.test.js +128 -0
- package/dist/test/notes.integration.test.d.ts +1 -0
- package/dist/test/notes.integration.test.js +51 -55
- package/dist/test/notes.test.d.ts +1 -0
- package/dist/test/notes.test.js +162 -34
- package/dist/test/reset-lifecycle.test.d.ts +1 -0
- package/dist/test/reset-lifecycle.test.js +142 -85
- package/docs/architecture.md +8 -8
- package/docs/reset-lifecycle.md +63 -79
- package/package.json +35 -2
- package/playbook.md +33 -32
- package/skills/dream/SKILL.md +12 -0
- package/src/context/boot.ts +44 -25
- package/src/context/budget.ts +52 -27
- package/src/context/context-window.ts +16 -1
- package/src/context/prompts.ts +2 -2
- package/src/context/reset-artifacts.ts +26 -24
- package/src/context/reset-lifecycle.ts +117 -111
- package/src/context/runtime.ts +50 -22
- package/src/context/thresholds.ts +1 -1
- package/src/dream/cli.ts +1 -1
- package/src/dream/doctor.ts +5 -2
- package/src/index.ts +4 -4
- package/src/notes/address.ts +9 -15
- package/src/notes/constants.ts +3 -0
- package/src/notes/context.ts +40 -0
- package/src/notes/frontmatter.ts +18 -12
- package/src/notes/index.ts +22 -0
- package/src/notes/paths.ts +64 -78
- package/src/notes/store.ts +289 -238
- package/src/pi/notes/adapter.ts +44 -0
- package/src/{notes → pi/notes}/session-replay.ts +3 -3
- package/src/{notes/notes-snapshot.ts → pi/notes/snapshot.ts} +13 -4
- package/src/{notes → pi/notes}/tools.ts +25 -23
- package/src/protocol.ts +5 -6
- package/src/text-match.ts +13 -0
- package/src/tool-output.ts +2 -14
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import test from "node:test";
|
|
3
4
|
import { historyFromSession, internal } from "../src/index.js";
|
|
4
|
-
import { listNotes } from "
|
|
5
|
-
import { CONTINUATION_TYPE } from "../src/protocol.js";
|
|
6
|
-
import { appendText, call, commitTurnEndBoundary, context,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
test.afterEach(() => testEnvironment.afterEach());
|
|
10
|
-
test.after(() => testEnvironment.dispose());
|
|
5
|
+
import { listNotes } from "./helpers/notes.js";
|
|
6
|
+
import { CONTINUATION_TYPE, WARNING_TYPE } from "../src/protocol.js";
|
|
7
|
+
import { appendText, call, commitTurnEndBoundary, context, makeExtension, manager, noticesOf, resultJson, resultRead, runContextHook, runContextWithSystemHook, runHandlers, runManualCompact, runCommand, sentOf, } from "./helpers/extension.js";
|
|
8
|
+
import { installExtensionTestHooks } from "./helpers/extension-test-environment.js";
|
|
9
|
+
const testEnvironment = installExtensionTestHooks("pi-context-integration");
|
|
11
10
|
test("custom reset marker removes old provider context while history remains searchable", async () => {
|
|
12
11
|
const sessionManager = manager();
|
|
13
12
|
const captured = makeExtension(sessionManager);
|
|
@@ -21,7 +20,12 @@ test("custom reset marker removes old provider context while history remains sea
|
|
|
21
20
|
const branch = sessionManager.getBranch();
|
|
22
21
|
const marker = branch.find((entry) => entry.type === "custom" && entry.customType === internal.RESET_MARKER_TYPE);
|
|
23
22
|
assert.ok(marker && marker.type === "custom");
|
|
24
|
-
|
|
23
|
+
const checkpoint = branch.find((entry) => entry.type === "compaction");
|
|
24
|
+
assert.ok(checkpoint && checkpoint.type === "compaction", "reset persists a native retain-none checkpoint");
|
|
25
|
+
assert.equal(checkpoint.parentId, toolResultId, "checkpoint follows the completed tool result");
|
|
26
|
+
assert.equal(checkpoint.summary, "");
|
|
27
|
+
assert.equal(checkpoint.firstKeptEntryId, checkpoint.id);
|
|
28
|
+
assert.equal(marker.parentId, checkpoint.id, "marker follows the native checkpoint");
|
|
25
29
|
assert.deepEqual(Object.keys(marker.data), ["windowId"]);
|
|
26
30
|
const projected = await runContextWithSystemHook(captured, ctx, sessionManager.buildSessionContext().messages);
|
|
27
31
|
const providerText = JSON.stringify(projected?.messages ?? []);
|
|
@@ -44,16 +48,16 @@ test("the root boot and reset boot carry durable window identity", async () => {
|
|
|
44
48
|
const ctx = context(sessionManager);
|
|
45
49
|
appendText(sessionManager, "user", "task before reset");
|
|
46
50
|
appendText(sessionManager, "assistant", "working");
|
|
47
|
-
await call(captured, "notes_write", {
|
|
51
|
+
await call(captured, "notes_write", { address: "decisions.md", content: "use terra" }, ctx);
|
|
48
52
|
// Root window: session_start persists the boot block without triggering a turn.
|
|
49
|
-
runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
53
|
+
await runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
50
54
|
assert.equal(captured.sent.length, 1);
|
|
51
55
|
const rootBoot = captured.sent[0];
|
|
52
56
|
assert.equal(rootBoot?.message.customType, internal.BOOT_TYPE);
|
|
53
57
|
assert.equal(rootBoot?.message.display, false, "boot block stays out of the TUI");
|
|
54
58
|
assert.equal(rootBoot?.options?.triggerTurn, false);
|
|
55
59
|
assert.equal(noticesOf(ctx).length, 0, "initial boot is silent");
|
|
56
|
-
runHandlers(captured, "session_start", { reason: "reload" }, ctx);
|
|
60
|
+
await runHandlers(captured, "session_start", { reason: "reload" }, ctx);
|
|
57
61
|
assert.equal(noticesOf(ctx).length, 0, "reload is silent");
|
|
58
62
|
assert.deepEqual(rootBoot?.message.details, { windowId: `pcw:${sessionManager.getSessionId().slice(0, 8)}:root` });
|
|
59
63
|
const rootText = typeof rootBoot?.message.content === "string" ? rootBoot.message.content : "";
|
|
@@ -63,7 +67,7 @@ test("the root boot and reset boot carry durable window identity", async () => {
|
|
|
63
67
|
assert.match(rootText, new RegExp(`First context window id: pcw:${sessionManager.getSessionId().slice(0, 8)}:root`));
|
|
64
68
|
assert.match(rootText, new RegExp(`Current context window id: pcw:${sessionManager.getSessionId().slice(0, 8)}:root`));
|
|
65
69
|
assert.ok(rootText.includes("decisions.md"));
|
|
66
|
-
const decisionsMeta = listNotes(ctx, { scope: "session" }).find((row) => row.path === "decisions.md")?.meta;
|
|
70
|
+
const decisionsMeta = (await listNotes(ctx, { scope: "session" })).find((row) => row.path === "decisions.md")?.meta;
|
|
67
71
|
assert.ok(decisionsMeta);
|
|
68
72
|
assert.match(rootText, /updated \d+s ago\)/, "boot note metadata carries a relative update time");
|
|
69
73
|
assert.ok(rootText.includes(internal.CONTEXT_WINDOW_PROTOCOL_OPEN_TAG));
|
|
@@ -72,8 +76,8 @@ test("the root boot and reset boot carry durable window identity", async () => {
|
|
|
72
76
|
assert.equal(noticesOf(ctx).length, 0, "requesting a reset does not announce success");
|
|
73
77
|
const boundary = await commitTurnEndBoundary(captured, sessionManager, ctx);
|
|
74
78
|
assert.equal(boundary.continue, true);
|
|
75
|
-
runHandlers(captured, "turn_start", {}, ctx);
|
|
76
|
-
runHandlers(captured, "agent_settled", {}, ctx);
|
|
79
|
+
await runHandlers(captured, "turn_start", {}, ctx);
|
|
80
|
+
await runHandlers(captured, "agent_settled", {}, ctx);
|
|
77
81
|
assert.equal(noticesOf(ctx).filter((notice) => notice.message.includes("memory cleared")).length, 1, "the committed reset is announced once");
|
|
78
82
|
const entries = sessionManager.getBranch();
|
|
79
83
|
const marker = entries.find((entry) => entry.type === "custom" && entry.customType === internal.RESET_MARKER_TYPE);
|
|
@@ -85,7 +89,28 @@ test("the root boot and reset boot carry durable window identity", async () => {
|
|
|
85
89
|
const continuation = entries.find((entry) => entry.type === "custom_message" && entry.customType === CONTINUATION_TYPE);
|
|
86
90
|
assert.ok(continuation && continuation.type === "custom_message" && continuation.display === false, "the resumed run is represented by one hidden continuation");
|
|
87
91
|
});
|
|
88
|
-
test("
|
|
92
|
+
test("an aborted async boot repair emits no boot, continuation, or incomplete-notes notice", async () => {
|
|
93
|
+
const notesHome = process.env.PI_NOTES_HOME;
|
|
94
|
+
assert.ok(notesHome);
|
|
95
|
+
rmSync(notesHome, { recursive: true, force: true });
|
|
96
|
+
writeFileSync(notesHome, "not a directory");
|
|
97
|
+
const sessionManager = manager();
|
|
98
|
+
const windowId = "pcw:aborted-boot-repair";
|
|
99
|
+
sessionManager.appendCustomEntry(internal.RESET_MARKER_TYPE, { windowId });
|
|
100
|
+
const captured = makeExtension(sessionManager);
|
|
101
|
+
const ctx = context(sessionManager);
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
ctx.signal = controller.signal;
|
|
104
|
+
const sessionStart = captured.handlers.get("session_start")?.[0];
|
|
105
|
+
assert.ok(sessionStart);
|
|
106
|
+
const pending = sessionStart({ reason: "startup" }, ctx);
|
|
107
|
+
controller.abort();
|
|
108
|
+
await pending;
|
|
109
|
+
assert.equal(captured.sent.length, 0, "aborted snapshot work cannot append the reset boot or continuation");
|
|
110
|
+
assert.equal(noticesOf(ctx).some((notice) => notice.message.includes("notes index incomplete")), false, "aborted snapshot work cannot notify about missing homes");
|
|
111
|
+
assert.equal(sessionManager.getBranch().some((entry) => entry.type === "custom_message" && [internal.BOOT_TYPE, CONTINUATION_TYPE].includes(entry.customType)), false);
|
|
112
|
+
});
|
|
113
|
+
test("a marker tail with only metadata repairs its missing boot and continuation without moving the boundary", async () => {
|
|
89
114
|
const sessionManager = manager(true);
|
|
90
115
|
const windowId = "pcw:metadata-tail";
|
|
91
116
|
const markerId = sessionManager.appendCustomEntry(internal.RESET_MARKER_TYPE, { windowId });
|
|
@@ -93,7 +118,7 @@ test("a marker tail with only metadata repairs its missing boot and continuation
|
|
|
93
118
|
sessionManager.appendThinkingLevelChange("low");
|
|
94
119
|
const captured = makeExtension(sessionManager);
|
|
95
120
|
const ctx = context(sessionManager);
|
|
96
|
-
runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
121
|
+
await runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
97
122
|
const branch = sessionManager.getBranch();
|
|
98
123
|
const markerIndex = branch.findIndex((entry) => entry.id === markerId);
|
|
99
124
|
const bootEntries = branch.filter((entry) => entry.type === "custom_message" && entry.customType === internal.BOOT_TYPE && entry.details && typeof entry.details === "object" && entry.details.windowId === windowId);
|
|
@@ -108,13 +133,13 @@ test("a marker tail with only metadata repairs its missing boot and continuation
|
|
|
108
133
|
assert.equal(captured.sent[1]?.message.customType, CONTINUATION_TYPE);
|
|
109
134
|
assert.equal(noticesOf(ctx).length, 0, "repairing a reset tail is not a new reset");
|
|
110
135
|
});
|
|
111
|
-
test("a bare marker tail repairs the full ordered reset shape", () => {
|
|
136
|
+
test("a bare marker tail repairs the full ordered reset shape", async () => {
|
|
112
137
|
const sessionManager = manager(true);
|
|
113
138
|
const windowId = "pcw:bare-marker";
|
|
114
139
|
const markerId = sessionManager.appendCustomEntry(internal.RESET_MARKER_TYPE, { windowId });
|
|
115
140
|
const captured = makeExtension(sessionManager);
|
|
116
141
|
const ctx = context(sessionManager);
|
|
117
|
-
runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
142
|
+
await runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
118
143
|
assert.equal(captured.sent.length, 2, "a bare marker emits a boot and a continuation");
|
|
119
144
|
const branch = sessionManager.getBranch();
|
|
120
145
|
const markerIndex = branch.findIndex((entry) => entry.id === markerId);
|
|
@@ -123,7 +148,7 @@ test("a bare marker tail repairs the full ordered reset shape", () => {
|
|
|
123
148
|
assert.ok(bootIndex > markerIndex, "the repaired boot follows its marker");
|
|
124
149
|
assert.ok(continuationIndex > bootIndex, "the repaired continuation follows its boot");
|
|
125
150
|
});
|
|
126
|
-
test("a marker tail that already carries a boot repairs only its missing continuation, once", () => {
|
|
151
|
+
test("a marker tail that already carries a boot repairs only its missing continuation, once", async () => {
|
|
127
152
|
const sessionManager = manager(true);
|
|
128
153
|
const windowId = "pcw:missing-continuation";
|
|
129
154
|
const markerId = sessionManager.appendCustomEntry(internal.RESET_MARKER_TYPE, { windowId });
|
|
@@ -131,23 +156,23 @@ test("a marker tail that already carries a boot repairs only its missing continu
|
|
|
131
156
|
sessionManager.appendCustomMessageEntry(internal.BOOT_TYPE, "already-persisted boot", false, { windowId });
|
|
132
157
|
const captured = makeExtension(sessionManager);
|
|
133
158
|
const ctx = context(sessionManager);
|
|
134
|
-
runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
159
|
+
await runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
135
160
|
assert.equal(captured.sent.length, 1, "only the missing continuation is emitted");
|
|
136
161
|
assert.equal(captured.sent[0]?.message.customType, CONTINUATION_TYPE);
|
|
137
162
|
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === internal.BOOT_TYPE).length, 1, "the existing boot is never duplicated");
|
|
138
163
|
assert.ok(sessionManager.getBranch().findIndex((entry) => entry.id === markerId) >= 0);
|
|
139
|
-
runHandlers(captured, "session_start", { reason: "reload" }, ctx);
|
|
164
|
+
await runHandlers(captured, "session_start", { reason: "reload" }, ctx);
|
|
140
165
|
assert.equal(captured.sent.length, 1, "a complete reset tail is idempotent");
|
|
141
166
|
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === CONTINUATION_TYPE).length, 1);
|
|
142
167
|
});
|
|
143
|
-
test("a marker tail followed by real conversation is never repaired", () => {
|
|
168
|
+
test("a marker tail followed by real conversation is never repaired", async () => {
|
|
144
169
|
const sessionManager = manager(true);
|
|
145
170
|
const windowId = "pcw:unsafe-tail";
|
|
146
171
|
sessionManager.appendCustomEntry(internal.RESET_MARKER_TYPE, { windowId });
|
|
147
172
|
appendText(sessionManager, "user", "post-marker work that must not be pushed behind a late boot");
|
|
148
173
|
const captured = makeExtension(sessionManager);
|
|
149
174
|
const ctx = context(sessionManager);
|
|
150
|
-
runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
175
|
+
await runHandlers(captured, "session_start", { reason: "startup" }, ctx);
|
|
151
176
|
assert.equal(captured.sent.length, 0, "an unsafe marker tail emits nothing");
|
|
152
177
|
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === internal.BOOT_TYPE).length, 0, "no boot is appended after real work");
|
|
153
178
|
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === CONTINUATION_TYPE).length, 0, "no continuation is appended after real work");
|
|
@@ -180,37 +205,36 @@ test("wipe_memory uses one turn boundary and never calls ctx.compact", async ()
|
|
|
180
205
|
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom" && entry.customType === internal.RESET_MARKER_TYPE).length, 1);
|
|
181
206
|
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === internal.BOOT_TYPE && entry.details && typeof entry.details === "object" && "windowId" in entry.details).length, 1);
|
|
182
207
|
});
|
|
183
|
-
test("pi-context command toggles future work, /wipe-memory
|
|
208
|
+
test("pi-context command toggles future work, /wipe-memory starts close-out, and /compact remains disabled", async () => {
|
|
184
209
|
const sessionManager = manager();
|
|
185
210
|
appendText(sessionManager, "user", "hello");
|
|
186
211
|
const captured = makeExtension(sessionManager);
|
|
187
212
|
const low = context(sessionManager, undefined, { tokens: 170_000, contextWindow: 200_000, percent: 85 });
|
|
188
213
|
// On by default: session_start persists the root boot block.
|
|
189
|
-
runHandlers(captured, "session_start", { reason: "startup" }, low);
|
|
214
|
+
await runHandlers(captured, "session_start", { reason: "startup" }, low);
|
|
190
215
|
assert.equal(captured.sent.length, 1);
|
|
191
216
|
assert.equal(captured.sent[0]?.message.customType, internal.BOOT_TYPE);
|
|
192
217
|
let notices = await runCommand(captured, "pi-context", "off", low);
|
|
193
218
|
assert.match(notices[0]?.message ?? "", /off/);
|
|
194
219
|
assert.equal(await runContextHook(captured, low), undefined, "no guidance while off");
|
|
195
220
|
assert.equal(sentOf(captured, internal.GUIDANCE_TYPE).length, 0, "no guidance persisted while off");
|
|
196
|
-
runHandlers(captured, "session_start", { reason: "startup" }, low);
|
|
221
|
+
await runHandlers(captured, "session_start", { reason: "startup" }, low);
|
|
197
222
|
assert.equal(captured.sent.length, 1, "no new boot block is persisted while off");
|
|
198
223
|
const offResult = resultJson(await call(captured, "wipe_memory", {}, low));
|
|
199
224
|
assert.match(offResult.error ?? "", /off/, "wipe_memory refuses while off");
|
|
200
225
|
notices = await runCommand(captured, "pi-context", "on", low);
|
|
201
226
|
assert.match(notices[0]?.message ?? "", /on/);
|
|
202
227
|
notices = await runCommand(captured, "wipe-memory", "", low);
|
|
203
|
-
assert.equal(notices.length,
|
|
204
|
-
assert.
|
|
205
|
-
assert.equal(captured.sent
|
|
206
|
-
assert.equal(captured.sent[1]?.
|
|
207
|
-
assert.equal(captured.sent[1]?.message.
|
|
208
|
-
assert.equal(
|
|
209
|
-
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom" && entry.customType === internal.RESET_MARKER_TYPE).length, 1);
|
|
228
|
+
assert.equal(notices.length, 0, "the command begins a model close-out rather than announcing a reset request");
|
|
229
|
+
assert.equal(captured.sent.length, 2, "the command persists one hidden warning after the startup boot");
|
|
230
|
+
assert.equal(captured.sent[1]?.options?.triggerTurn, true, "the shared warning starts an ordinary agent turn");
|
|
231
|
+
assert.equal(captured.sent[1]?.message.customType, WARNING_TYPE);
|
|
232
|
+
assert.equal(captured.sent[1]?.message.display, false);
|
|
233
|
+
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom" && entry.customType === internal.RESET_MARKER_TYPE).length, 0, "no reset is claimed until close-out completes");
|
|
210
234
|
const markerContext = await runManualCompact(captured, low);
|
|
211
|
-
assert.deepEqual(markerContext, { cancel: true }, "/compact is canceled while
|
|
235
|
+
assert.deepEqual(markerContext, { cancel: true }, "/compact is canceled while context windows are enabled");
|
|
212
236
|
const projected = await runContextWithSystemHook(captured, low, sessionManager.buildSessionContext().messages);
|
|
213
|
-
assert.equal(JSON.stringify(projected?.messages ?? []).includes("hello"),
|
|
237
|
+
assert.equal(JSON.stringify(projected?.messages ?? []).includes("hello"), true, "the old window remains active until the close-out commits");
|
|
214
238
|
notices = await runCommand(captured, "pi-context", "maybe", low);
|
|
215
239
|
assert.equal(notices[0]?.type, "error", "unknown argument rejected");
|
|
216
240
|
// Bare command reports current state without changing it.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { internal } from "../src/index.js";
|
|
4
|
-
import { appendText, call, commitTurnEndBoundary, context,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
test.afterEach(() => testEnvironment.afterEach());
|
|
8
|
-
test.after(() => testEnvironment.dispose());
|
|
4
|
+
import { appendText, call, commitTurnEndBoundary, context, makeExtension, manager, noticesOf, resultJson, runContextHook, runHandlers, sentOf, settingsFixture, } from "./helpers/extension.js";
|
|
5
|
+
import { installExtensionTestHooks } from "./helpers/extension-test-environment.js";
|
|
6
|
+
const testEnvironment = installExtensionTestHooks("pi-context-integration");
|
|
9
7
|
test("low-budget guidance and warning persist at turn_end, once per active window", async () => {
|
|
10
8
|
const sessionManager = manager();
|
|
11
9
|
const captured = makeExtension(sessionManager);
|
|
@@ -24,6 +22,23 @@ test("low-budget guidance and warning persist at turn_end, once per active windo
|
|
|
24
22
|
assert.equal(warningBoundary.entries.filter((entry) => entry.type === "custom_message" && entry.customType === internal.WARNING_TYPE).length, 1);
|
|
25
23
|
assert.equal(sessionManager.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === internal.WARNING_TYPE).length, 1);
|
|
26
24
|
});
|
|
25
|
+
test("early guidance stays silent and the committed final warning notifies once", async () => {
|
|
26
|
+
const sm = manager();
|
|
27
|
+
const captured = makeExtension(sm);
|
|
28
|
+
const early = context(sm, undefined, { tokens: 165_000, percent: 82.5, contextWindow: 200_000 });
|
|
29
|
+
assert.equal(await runContextHook(captured, early), undefined, "early guidance is persisted at turn_end, not injected immediately");
|
|
30
|
+
await commitTurnEndBoundary(captured, sm, early);
|
|
31
|
+
await runHandlers(captured, "agent_settled", {}, early);
|
|
32
|
+
assert.equal(noticesOf(early).filter((notice) => notice.message.includes("Context running low") || notice.message.includes("Context almost full")).length, 0, "early model guidance has no UI toast");
|
|
33
|
+
assert.equal(sm.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === internal.GUIDANCE_TYPE).length, 1, "early guidance remains durable for the model");
|
|
34
|
+
const final = context(sm, undefined, { tokens: 181_000, percent: 90.5, contextWindow: 200_000 });
|
|
35
|
+
const warning = await runContextHook(captured, final);
|
|
36
|
+
assert.equal(warning?.messages[0]?.customType, internal.WARNING_TYPE);
|
|
37
|
+
await commitTurnEndBoundary(captured, sm, final);
|
|
38
|
+
await runHandlers(captured, "turn_start", {}, final);
|
|
39
|
+
await runHandlers(captured, "agent_settled", {}, final);
|
|
40
|
+
assert.equal(noticesOf(final).filter((notice) => notice.message === "pi-context: Context almost full; close out the current memory window.").length, 1, "only the committed final warning notifies, exactly once");
|
|
41
|
+
});
|
|
27
42
|
test("the visible countdown ends at the warning line, clamps at zero, and preserves unknown usage", async () => {
|
|
28
43
|
const fixture = settingsFixture({
|
|
29
44
|
reserveTokens: 16_384,
|
|
@@ -31,7 +46,7 @@ test("the visible countdown ends at the warning line, clamps at zero, and preser
|
|
|
31
46
|
});
|
|
32
47
|
const sm = manager();
|
|
33
48
|
const captured = makeExtension(sm);
|
|
34
|
-
runHandlers(captured, "session_tree", {}, context(sm, undefined, undefined, true, fixture.cwd, true));
|
|
49
|
+
await runHandlers(captured, "session_tree", {}, context(sm, undefined, undefined, true, fixture.cwd, true));
|
|
35
50
|
const readBudget = async (tokens, trusted = true) => {
|
|
36
51
|
const ctx = context(sm, undefined, { tokens, contextWindow: 200_000, percent: tokens === null ? null : tokens / 2000 }, true, fixture.cwd, trusted);
|
|
37
52
|
return resultJson(await call(captured, "get_context_remaining", {}, ctx)).remaining_tokens;
|
|
@@ -44,7 +59,7 @@ test("the visible countdown ends at the warning line, clamps at zero, and preser
|
|
|
44
59
|
const absent = context(sm, undefined, undefined, true, fixture.cwd);
|
|
45
60
|
assert.equal(resultJson(await call(captured, "get_context_remaining", {}, absent)).remaining_tokens, null);
|
|
46
61
|
const untrusted = context(sm, undefined, { tokens: 72_563, contextWindow: 200_000, percent: 36.2815 }, true, fixture.cwd, false);
|
|
47
|
-
runHandlers(captured, "session_start", {}, untrusted);
|
|
62
|
+
await runHandlers(captured, "session_start", {}, untrusted);
|
|
48
63
|
assert.equal(await readBudget(72_563, false), 98_765, "session start reloads the global reserve when the project is untrusted");
|
|
49
64
|
});
|
|
50
65
|
test("absent pi-context key or margins reproduce the default reminder threshold at Pi's default reserve", async () => {
|
|
@@ -59,7 +74,7 @@ test("absent pi-context key or margins reproduce the default reminder threshold
|
|
|
59
74
|
const captured = makeExtension(sm);
|
|
60
75
|
// Thresholds are resolved once per session and cached; branch navigation clears the
|
|
61
76
|
// cache without emitting a boot block, so the next read uses this fixture.
|
|
62
|
-
runHandlers(captured, "session_tree", {}, context(sm, undefined, undefined, true, fixture.cwd));
|
|
77
|
+
await runHandlers(captured, "session_tree", {}, context(sm, undefined, undefined, true, fixture.cwd));
|
|
63
78
|
const window = 200_000;
|
|
64
79
|
const at = (remaining) => context(sm, undefined, { tokens: window - remaining, percent: 0, contextWindow: window }, true, fixture.cwd);
|
|
65
80
|
const first = at(40_961);
|
|
@@ -81,7 +96,7 @@ test("project pi-context reminder margin and reserve override global per key", a
|
|
|
81
96
|
// Project reserve wins: reminder = 50000 + 40000 (project margin).
|
|
82
97
|
const sm = manager();
|
|
83
98
|
const captured = makeExtension(sm);
|
|
84
|
-
runHandlers(captured, "session_tree", {}, context(sm, undefined, undefined, true, fixture.cwd));
|
|
99
|
+
await runHandlers(captured, "session_tree", {}, context(sm, undefined, undefined, true, fixture.cwd));
|
|
85
100
|
const window = 300_000;
|
|
86
101
|
const at = (remaining) => context(sm, undefined, { tokens: window - remaining, percent: 0, contextWindow: window }, true, fixture.cwd);
|
|
87
102
|
assert.equal(await runContextHook(captured, at(90_001)), undefined, "nothing injected above the project-derived reminder");
|
|
@@ -96,12 +111,13 @@ test("an invalid reminder margin degrades to its default with one warning and ne
|
|
|
96
111
|
const sm = manager();
|
|
97
112
|
const captured = makeExtension(sm);
|
|
98
113
|
const ctx = context(sm, undefined, undefined, true, fixture.cwd);
|
|
99
|
-
assert.
|
|
114
|
+
await assert.doesNotReject(() => runHandlers(captured, "session_start", { reason: "startup" }, ctx));
|
|
100
115
|
const notices = noticesOf(ctx).filter((notice) => notice.type === "warning");
|
|
101
116
|
assert.equal(notices.length, 1, "one warning for the offending key");
|
|
102
117
|
assert.equal(notices[0]?.type, "warning");
|
|
103
118
|
assert.match(notices[0]?.message ?? "", /reminderMarginTokens/);
|
|
104
|
-
assert.match(notices[0]?.message ?? "", /
|
|
119
|
+
assert.match(notices[0]?.message ?? "", /default reminder margin/);
|
|
120
|
+
assert.equal(/\d+\s*k?\s*(remaining|tokens)/i.test(notices[0]?.message ?? ""), false, "UI notices do not expose token counts");
|
|
105
121
|
const window = 200_000;
|
|
106
122
|
const at = (remaining) => context(sm, undefined, { tokens: window - remaining, percent: 0, contextWindow: window }, true, fixture.cwd);
|
|
107
123
|
// The degraded reminder is Pi's default reserve + default margin = 40960.
|
|
@@ -121,6 +137,6 @@ test("the warning supersedes the early reminder when usage jumps across both thr
|
|
|
121
137
|
assert.deepEqual(warningResult?.messages.map((message) => message.customType), [internal.WARNING_TYPE]);
|
|
122
138
|
await commitTurnEndBoundary(captured, sm, ctx);
|
|
123
139
|
const reloaded = makeExtension(sm);
|
|
124
|
-
runHandlers(reloaded, "context", {}, ctx);
|
|
140
|
+
await runHandlers(reloaded, "context", {}, ctx);
|
|
125
141
|
assert.equal(reloaded.sent.length, 0, "persisted warning also suppresses a late reminder after reload");
|
|
126
142
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test/doctor.test.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { main } from "../src/dream/cli.js";
|
|
3
3
|
import { doctor } from "../src/dream/doctor.js";
|
|
4
|
-
import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
-
const note = "---\norigin: self\nstatus: active\nstale: false\
|
|
8
|
+
const note = "---\norigin: self\nstatus: active\nstale: false\ncreatedAt: 2026-01-01T00:00:00Z\nupdatedAt: 2026-01-01T00:00:00Z\nlastAccessed: 2026-01-01T00:00:00Z\naccessCount: 0\n---\n\n";
|
|
9
|
+
test("doctor identifies legacy metadata as requiring manual migration", (t) => {
|
|
10
|
+
const root = mkdtempSync(join(tmpdir(), "dream-doctor-legacy-"));
|
|
11
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
12
|
+
mkdirSync(join(root, "human"));
|
|
13
|
+
writeFileSync(join(root, "human/legacy.md"), note.replace("createdAt", "created_at"));
|
|
14
|
+
const issues = doctor(root);
|
|
15
|
+
assert.ok(issues.some((issue) => issue.includes("legacy metadata key created_at; manually migrate to createdAt")));
|
|
16
|
+
});
|
|
9
17
|
test("doctor validates without repairing files or running the dreamer", async () => {
|
|
10
18
|
const root = mkdtempSync(join(tmpdir(), "dream-doctor-test-"));
|
|
11
19
|
mkdirSync(join(root, "human"));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { loadSkillsFromDir } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { loadPlaybook } from "../src/dream/runner.js";
|
|
10
|
+
function packageRoot() {
|
|
11
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
while (!existsSync(join(dir, "package.json"))) {
|
|
13
|
+
const parent = dirname(dir);
|
|
14
|
+
if (parent === dir)
|
|
15
|
+
throw new Error("could not locate package root");
|
|
16
|
+
dir = parent;
|
|
17
|
+
}
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
20
|
+
function discoverDreamSkill(skillsDir) {
|
|
21
|
+
const result = loadSkillsFromDir({ dir: skillsDir, source: "path" });
|
|
22
|
+
assert.deepEqual(result.diagnostics, []);
|
|
23
|
+
const skill = result.skills.find((candidate) => candidate.name === "dream");
|
|
24
|
+
assert.ok(skill, "Pi's public skill loader discovers dream");
|
|
25
|
+
assert.ok(skill.description.length > 0);
|
|
26
|
+
return skill;
|
|
27
|
+
}
|
|
28
|
+
test("dream skill is publicly discoverable and resolves the CLI's shared playbook", () => {
|
|
29
|
+
const root = packageRoot();
|
|
30
|
+
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
31
|
+
assert.deepEqual(manifest.pi.skills, ["./skills"]);
|
|
32
|
+
assert.deepEqual(manifest.pi.extensions, ["./dist/extension.js"]);
|
|
33
|
+
assert.equal(manifest.bin.dream, "dist/src/dream/cli.js");
|
|
34
|
+
assert.ok(manifest.files.includes("skills"));
|
|
35
|
+
const skill = discoverDreamSkill(join(root, "skills"));
|
|
36
|
+
const skillPlaybook = resolve(skill.baseDir, "../../playbook.md");
|
|
37
|
+
const packagePlaybook = resolve(root, "playbook.md");
|
|
38
|
+
assert.equal(skillPlaybook, packagePlaybook);
|
|
39
|
+
const playbook = readFileSync(packagePlaybook, "utf8");
|
|
40
|
+
assert.equal(loadPlaybook(skillPlaybook), playbook);
|
|
41
|
+
});
|
|
42
|
+
test("npm tarball keeps the skill's relative playbook path portable after extraction", () => {
|
|
43
|
+
const root = packageRoot();
|
|
44
|
+
const fixture = mkdtempSync(join(tmpdir(), "dream-skill-pack-"));
|
|
45
|
+
try {
|
|
46
|
+
const packed = JSON.parse(execFileSync("npm", ["pack", "--offline", "--ignore-scripts", "--json", "--pack-destination", fixture], {
|
|
47
|
+
cwd: root,
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
50
|
+
}))[0];
|
|
51
|
+
const packedPaths = packed.files.map((file) => file.path);
|
|
52
|
+
assert.ok(packedPaths.includes("skills/dream/SKILL.md"));
|
|
53
|
+
assert.ok(packedPaths.includes("playbook.md"));
|
|
54
|
+
assert.ok(packedPaths.includes("dist/extension.js"));
|
|
55
|
+
assert.ok(packedPaths.includes("dist/src/dream/cli.js"));
|
|
56
|
+
const extracted = join(fixture, "extracted");
|
|
57
|
+
execFileSync("mkdir", ["-p", extracted]);
|
|
58
|
+
execFileSync("tar", ["-xzf", join(fixture, packed.filename), "-C", extracted]);
|
|
59
|
+
const installedRoot = join(extracted, "package");
|
|
60
|
+
const installedSkill = discoverDreamSkill(join(installedRoot, "skills"));
|
|
61
|
+
const referencedPlaybook = resolve(installedSkill.baseDir, "../../playbook.md");
|
|
62
|
+
assert.equal(referencedPlaybook, resolve(installedRoot, "playbook.md"));
|
|
63
|
+
assert.equal(loadPlaybook(referencedPlaybook), readFileSync(referencedPlaybook, "utf8"));
|
|
64
|
+
assert.equal(loadPlaybook(referencedPlaybook), readFileSync(join(root, "playbook.md"), "utf8"));
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
rmSync(fixture, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installExtensionTestHooks(prefix: string): import("./extension.js").ExtensionTestEnvironment;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import { installExtensionTestEnvironment } from "./extension.js";
|
|
3
|
+
export function installExtensionTestHooks(prefix) {
|
|
4
|
+
const environment = installExtensionTestEnvironment(prefix);
|
|
5
|
+
test.beforeEach(() => environment.beforeEach());
|
|
6
|
+
test.afterEach(() => environment.afterEach());
|
|
7
|
+
test.after(() => environment.dispose());
|
|
8
|
+
return environment;
|
|
9
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { type ContextUsage, type ExtensionAPI, type ExtensionContext, type RegisteredCommand, SessionManager, SettingsManager, type SessionBoundaryDraft, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
export type ExtensionTestEnvironment = {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly agentDir: string;
|
|
6
|
+
beforeEach(): void;
|
|
7
|
+
afterEach(): void;
|
|
8
|
+
newNotesRoot(): string;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Install the process-level settings and notes roots needed by one test file.
|
|
13
|
+
* The caller owns the lifecycle hooks; this helper never registers tests or hooks.
|
|
14
|
+
*/
|
|
15
|
+
export declare function installExtensionTestEnvironment(prefix?: string): ExtensionTestEnvironment;
|
|
16
|
+
export type Notice = {
|
|
17
|
+
message: string;
|
|
18
|
+
type?: "info" | "warning" | "error";
|
|
19
|
+
};
|
|
20
|
+
export declare function writeJson(path: string, value: unknown): void;
|
|
21
|
+
export type SettingsFixture = {
|
|
22
|
+
cwd: string;
|
|
23
|
+
agentDir: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Materialize global (agentDir/settings.json) and project (cwd/.pi/settings.json)
|
|
27
|
+
* settings in temp directories, then read them back through the same public
|
|
28
|
+
* SettingsManager.create the extension uses. Never touches the real ~/.pi.
|
|
29
|
+
*/
|
|
30
|
+
export declare function settingsFixture(options?: {
|
|
31
|
+
global?: Record<string, unknown>;
|
|
32
|
+
project?: Record<string, unknown>;
|
|
33
|
+
reserveTokens?: number;
|
|
34
|
+
}): SettingsFixture;
|
|
35
|
+
export type EventHandler = (event: never, ctx: ExtensionContext) => unknown;
|
|
36
|
+
type SendMessageArg = Parameters<ExtensionAPI["sendMessage"]>[0];
|
|
37
|
+
export type SentMessage = {
|
|
38
|
+
message: SendMessageArg;
|
|
39
|
+
options: {
|
|
40
|
+
triggerTurn?: boolean;
|
|
41
|
+
} | undefined;
|
|
42
|
+
};
|
|
43
|
+
export type Captured = {
|
|
44
|
+
tools: Map<string, ToolDefinition>;
|
|
45
|
+
handlers: Map<string, EventHandler[]>;
|
|
46
|
+
commands: Map<string, CommandOptions>;
|
|
47
|
+
sent: SentMessage[];
|
|
48
|
+
contextMessages: unknown[];
|
|
49
|
+
flags: string[];
|
|
50
|
+
};
|
|
51
|
+
export type CommandOptions = Omit<RegisteredCommand, "name" | "sourceInfo">;
|
|
52
|
+
export type CompactionHookResult = {
|
|
53
|
+
cancel: true;
|
|
54
|
+
} | {
|
|
55
|
+
compaction: {
|
|
56
|
+
summary: string;
|
|
57
|
+
firstKeptEntryId: string | null;
|
|
58
|
+
tokensBefore: number;
|
|
59
|
+
details?: unknown;
|
|
60
|
+
};
|
|
61
|
+
} | undefined;
|
|
62
|
+
/** TypeBox's TSchema does not expose `type`/`required` statically; read them structurally. */
|
|
63
|
+
export declare function objectSchema(tool: ToolDefinition | undefined): {
|
|
64
|
+
type?: string;
|
|
65
|
+
required?: string[];
|
|
66
|
+
} | undefined;
|
|
67
|
+
export declare function manager(persisted?: boolean): SessionManager;
|
|
68
|
+
export declare function makeExtension(sessionManager: SessionManager, settingsManager?: SettingsManager): Captured;
|
|
69
|
+
export declare function explicitBoot(ctx: ExtensionContext, currentWindowId: string, previousWindowId: string | undefined): Promise<string>;
|
|
70
|
+
export declare function context(sessionManager: SessionManager, compact?: ExtensionContext["compact"], usage?: ContextUsage, idle?: boolean, cwd?: string, projectTrusted?: boolean, model?: string | {
|
|
71
|
+
provider: string;
|
|
72
|
+
id: string;
|
|
73
|
+
}): ExtensionContext;
|
|
74
|
+
export declare function noticesOf(ctx: ExtensionContext): Notice[];
|
|
75
|
+
export declare function sentOf(captured: Captured, customType: string): SentMessage[];
|
|
76
|
+
export declare function call(captured: Captured, name: string, params: Record<string, unknown>, ctx: ExtensionContext): Promise<AgentToolResult<unknown>>;
|
|
77
|
+
export declare function resultJson<T>(result: AgentToolResult<unknown>): T;
|
|
78
|
+
/** Assert the delivered wire text fits the tool-output budget, header included for raw reads. */
|
|
79
|
+
export declare function assertWithinBudget(result: AgentToolResult<unknown>, message: string): void;
|
|
80
|
+
/** Decoded raw read response using the shared READ WINDOW grammar. */
|
|
81
|
+
export type ReadWindow = {
|
|
82
|
+
header: string;
|
|
83
|
+
content: string;
|
|
84
|
+
offset_chars: number;
|
|
85
|
+
total_chars: number;
|
|
86
|
+
next_offset_chars: number | null;
|
|
87
|
+
details: Record<string, unknown>;
|
|
88
|
+
};
|
|
89
|
+
/** Decode either raw read without including its shared metadata block in the payload. */
|
|
90
|
+
export declare function resultRead(result: AgentToolResult<unknown>): ReadWindow;
|
|
91
|
+
export declare function runManualCompact(captured: Captured, ctx: ExtensionContext): Promise<CompactionHookResult>;
|
|
92
|
+
export declare function runHandlers(captured: Captured, name: string, event: unknown, ctx: ExtensionContext): Promise<void>;
|
|
93
|
+
export declare function runCommand(captured: Captured, name: string, args: string, ctx: ExtensionContext): Promise<Notice[]>;
|
|
94
|
+
export type ContextHookResult = {
|
|
95
|
+
messages: unknown[];
|
|
96
|
+
} | undefined;
|
|
97
|
+
export declare function runContextHook(captured: Captured, ctx: ExtensionContext, eventOverride?: Record<string, unknown>): Promise<ContextHookResult>;
|
|
98
|
+
export declare function runContextWithSystemHook(captured: Captured, ctx: ExtensionContext, messages: unknown[]): Promise<ContextHookResult>;
|
|
99
|
+
export declare function commitTurnEndBoundary(captured: Captured, sessionManager: SessionManager, ctx: ExtensionContext): Promise<{
|
|
100
|
+
entries: SessionBoundaryDraft[];
|
|
101
|
+
continue: boolean;
|
|
102
|
+
}>;
|
|
103
|
+
export declare function appendText(sessionManager: SessionManager, role: "user" | "assistant" | "toolResult", text: string, toolName?: string): string;
|
|
104
|
+
export {};
|