@astrosheep/pi-context 0.25.1 → 0.25.2

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.
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.25.1",
3
- "sourceHash": "d9c843b30fa0f1426f40b6a054277e757d64b58ede6cb590aada253f34b75a81"
2
+ "version": "0.25.2",
3
+ "sourceHash": "e240c1946d0f2b6d7b1d83ed658c348c7daed5fce5a374844215f1eebddcb25f"
4
4
  }
package/dist/extension.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // <define:__PI_CONTEXT_BUILD__>
2
- var define_PI_CONTEXT_BUILD_default = { version: "0.25.1", sourceHash: "d9c843b30fa0f1426f40b6a054277e757d64b58ede6cb590aada253f34b75a81" };
2
+ var define_PI_CONTEXT_BUILD_default = { version: "0.25.2", sourceHash: "e240c1946d0f2b6d7b1d83ed658c348c7daed5fce5a374844215f1eebddcb25f" };
3
3
 
4
4
  // src/index.ts
5
5
  import { VERSION as VERSION2 } from "@earendil-works/pi-coding-agent";
@@ -1389,6 +1389,7 @@ function registerBudget(pi, isEnabled, settingsManager) {
1389
1389
  const invalidateThresholds = () => {
1390
1390
  cachedPolicy = void 0;
1391
1391
  };
1392
+ const formatRemaining = (remaining) => `${Math.max(0, Math.ceil(remaining / 1e3))}k`;
1392
1393
  let pendingGuidance;
1393
1394
  let pendingWarning;
1394
1395
  let pendingNotices = [];
@@ -1396,7 +1397,7 @@ function registerBudget(pi, isEnabled, settingsManager) {
1396
1397
  const windowId = currentWindowId(ctx);
1397
1398
  for (const notice of pendingNotices) {
1398
1399
  if (notice.windowId !== windowId || !hasWindowMessage(ctx, notice.customType)) continue;
1399
- ctx.ui.notify(notice.customType === WARNING_TYPE ? "pi-context: context budget critical \u2014 final checkpoint warning recorded for the model." : "pi-context: context budget low \u2014 checkpoint reminder recorded for the model, kept out of the chat view.", "warning");
1400
+ ctx.ui.notify(notice.customType === WARNING_TYPE ? `pi-context: Context almost full \u2014 ${formatRemaining(notice.remaining)} remaining` : `pi-context: Context running low \u2014 ${formatRemaining(notice.remaining)} remaining`, "warning");
1400
1401
  }
1401
1402
  pendingNotices = [];
1402
1403
  };
@@ -1418,7 +1419,7 @@ function registerBudget(pi, isEnabled, settingsManager) {
1418
1419
  clearStaged();
1419
1420
  const windowId = currentWindowId(ctx);
1420
1421
  const drafts = staged.filter((draft) => draft !== void 0 && draft.windowId === windowId);
1421
- pendingNotices = drafts.map(({ windowId: windowId2, customType }) => ({ windowId: windowId2, customType }));
1422
+ pendingNotices = drafts.map(({ windowId: windowId2, customType, remaining }) => ({ windowId: windowId2, customType, remaining }));
1422
1423
  return drafts.map((draft) => ({
1423
1424
  type: "custom_message",
1424
1425
  customType: draft.customType,
@@ -1433,7 +1434,6 @@ function registerBudget(pi, isEnabled, settingsManager) {
1433
1434
  pi.on("session_tree", resetForTransition);
1434
1435
  pi.on("model_select", resetForTransition);
1435
1436
  pi.on("session_shutdown", resetForTransition);
1436
- pi.on("turn_start", (_event, ctx) => notifyCommittedReminders(ctx));
1437
1437
  pi.on("agent_settled", (_event, ctx) => {
1438
1438
  notifyCommittedReminders(ctx);
1439
1439
  clearStaged();
@@ -1450,7 +1450,7 @@ function registerBudget(pi, isEnabled, settingsManager) {
1450
1450
  const content = `${GUIDANCE_OPEN_TAG}
1451
1451
  ${WARNING_PROMPT}
1452
1452
  ${GUIDANCE_CLOSE_TAG}`;
1453
- pendingWarning = { windowId, content };
1453
+ pendingWarning = { windowId, content, remaining };
1454
1454
  const warningMessage = {
1455
1455
  role: "custom",
1456
1456
  customType: WARNING_TYPE,
@@ -1463,7 +1463,7 @@ ${GUIDANCE_CLOSE_TAG}`;
1463
1463
  if (hasWindowMessage(ctx, GUIDANCE_TYPE) || pendingGuidance?.windowId === windowId) return void 0;
1464
1464
  if (remaining <= reminder) {
1465
1465
  const left = Math.max(0, remaining - warning);
1466
- pendingGuidance = { windowId, content: tokenBudgetGuidance(left) };
1466
+ pendingGuidance = { windowId, content: tokenBudgetGuidance(left), remaining };
1467
1467
  }
1468
1468
  return void 0;
1469
1469
  });
@@ -40,6 +40,7 @@ export function registerBudget(pi, isEnabled, settingsManager) {
40
40
  return usage !== undefined && usage.tokens !== null && usage.contextWindow - usage.tokens <= thresholdsFor(ctx).reserve;
41
41
  };
42
42
  const invalidateThresholds = () => { cachedPolicy = undefined; };
43
+ const formatRemaining = (remaining) => `${Math.max(0, Math.ceil(remaining / 1000))}k`;
43
44
  let pendingGuidance;
44
45
  let pendingWarning;
45
46
  let pendingNotices = [];
@@ -49,8 +50,8 @@ export function registerBudget(pi, isEnabled, settingsManager) {
49
50
  if (notice.windowId !== windowId || !hasWindowMessage(ctx, notice.customType))
50
51
  continue;
51
52
  ctx.ui.notify(notice.customType === WARNING_TYPE
52
- ? "pi-context: context budget criticalfinal checkpoint warning recorded for the model."
53
- : "pi-context: context budget low — checkpoint reminder recorded for the model, kept out of the chat view.", "warning");
53
+ ? `pi-context: Context almost full${formatRemaining(notice.remaining)} remaining`
54
+ : `pi-context: Context running low — ${formatRemaining(notice.remaining)} remaining`, "warning");
54
55
  }
55
56
  pendingNotices = [];
56
57
  };
@@ -72,7 +73,7 @@ export function registerBudget(pi, isEnabled, settingsManager) {
72
73
  clearStaged();
73
74
  const windowId = currentWindowId(ctx);
74
75
  const drafts = staged.filter((draft) => draft !== undefined && draft.windowId === windowId);
75
- pendingNotices = drafts.map(({ windowId, customType }) => ({ windowId, customType }));
76
+ pendingNotices = drafts.map(({ windowId, customType, remaining }) => ({ windowId, customType, remaining }));
76
77
  return drafts.map((draft) => ({
77
78
  type: "custom_message",
78
79
  customType: draft.customType,
@@ -88,7 +89,6 @@ export function registerBudget(pi, isEnabled, settingsManager) {
88
89
  // lifecycle point that must discard an uncommitted draft before the next prompt.
89
90
  // UI notices follow committed reminders. Aborted requests can retry their drafts
90
91
  // without showing the same low-budget notification twice.
91
- pi.on("turn_start", (_event, ctx) => notifyCommittedReminders(ctx));
92
92
  pi.on("agent_settled", (_event, ctx) => {
93
93
  notifyCommittedReminders(ctx);
94
94
  clearStaged();
@@ -109,7 +109,7 @@ export function registerBudget(pi, isEnabled, settingsManager) {
109
109
  // A not-yet-committed shallow reminder is superseded by the final warning.
110
110
  pendingGuidance = undefined;
111
111
  const content = `${GUIDANCE_OPEN_TAG}\n${WARNING_PROMPT}\n${GUIDANCE_CLOSE_TAG}`;
112
- pendingWarning = { windowId, content };
112
+ pendingWarning = { windowId, content, remaining };
113
113
  const warningMessage = {
114
114
  role: "custom",
115
115
  customType: WARNING_TYPE,
@@ -125,7 +125,7 @@ export function registerBudget(pi, isEnabled, settingsManager) {
125
125
  // Persist at turn_end, before any reset drafts. A queued sendMessage could
126
126
  // otherwise cross the marker and leak the old window's reminder forward.
127
127
  const left = Math.max(0, remaining - warning);
128
- pendingGuidance = { windowId, content: tokenBudgetGuidance(left) };
128
+ pendingGuidance = { windowId, content: tokenBudgetGuidance(left), remaining };
129
129
  }
130
130
  return undefined;
131
131
  });
@@ -59,6 +59,7 @@ async function openFixture(options) {
59
59
  const streamSignals = [];
60
60
  const events = [];
61
61
  const notices = [];
62
+ let budgetNotices = 0;
62
63
  let session;
63
64
  const loader = new DefaultResourceLoader({
64
65
  cwd: dir,
@@ -120,16 +121,14 @@ async function openFixture(options) {
120
121
  };
121
122
  await session.bindExtensions({
122
123
  uiContext: {
123
- notify(message) {
124
+ notify(message, type) {
124
125
  if (message.startsWith("pi-context: memory cleared · ")) {
125
126
  const windowId = message.split(" · ")[1];
126
127
  assert.ok(sessionManager.getBranch().some((entry) => entry.type === "custom" && entry.customType === RESET_MARKER_TYPE && entry.data?.windowId === windowId), "notification follows the reset marker commit");
127
128
  assert.ok(sessionManager.getBranch().some((entry) => entry.type === "custom_message" && entry.customType === BOOT_TYPE && entry.details?.windowId === windowId), "notification follows the reset boot commit");
128
129
  }
129
- if (message.includes("context budget low") || message.includes("context budget critical")) {
130
- const customType = message.includes("context budget critical") ? WARNING_TYPE : GUIDANCE_TYPE;
131
- assert.ok(sessionManager.getBranch().some((entry) => entry.type === "custom_message" && entry.customType === customType), "budget notifications follow the reminder commit");
132
- }
130
+ if (type === "warning" && sessionManager.getBranch().some((entry) => entry.type === "custom_message" && (entry.customType === GUIDANCE_TYPE || entry.customType === WARNING_TYPE)))
131
+ budgetNotices++;
133
132
  notices.push(message);
134
133
  },
135
134
  },
@@ -145,6 +144,7 @@ async function openFixture(options) {
145
144
  streamSignals,
146
145
  events,
147
146
  notices,
147
+ budgetNotices: () => budgetNotices,
148
148
  close: () => {
149
149
  session.dispose();
150
150
  if (managesEnvironment) {
@@ -221,22 +221,22 @@ test("real AgentSession: aborted low-budget requests notify only after a retry c
221
221
  });
222
222
  try {
223
223
  const reminders = () => fixture.sessionManager.getBranch().filter((entry) => entry.type === "custom_message" && entry.customType === GUIDANCE_TYPE);
224
- const notices = () => fixture.notices.filter((message) => message.includes("context budget low"));
224
+ const notices = () => fixture.budgetNotices();
225
225
  await fixture.session.prompt("Establish usage below the reminder line.");
226
226
  await fixture.session.waitForIdle();
227
227
  assert.equal(reminders().length, 0);
228
228
  await fixture.session.prompt("Abort this low-budget request.");
229
229
  await fixture.session.waitForIdle();
230
230
  assert.equal(reminders().length, 0, "an aborted turn does not commit its reminder");
231
- assert.equal(notices().length, 0, "an uncommitted reminder never notifies");
231
+ assert.equal(notices(), 0, "an uncommitted reminder never notifies");
232
232
  await fixture.session.prompt("Retry successfully.");
233
233
  await fixture.session.waitForIdle();
234
234
  assert.equal(reminders().length, 1);
235
- assert.equal(notices().length, 1, "the committed retry notifies once");
235
+ assert.equal(notices(), 1, "the committed retry notifies once");
236
236
  await fixture.session.prompt("Continue in the same window.");
237
237
  await fixture.session.waitForIdle();
238
238
  assert.equal(reminders().length, 1);
239
- assert.equal(notices().length, 1, "later turns cannot repeat the notice");
239
+ assert.equal(notices(), 1, "later turns cannot repeat the notice");
240
240
  }
241
241
  finally {
242
242
  fixture.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/pi-context",
3
- "version": "0.25.1",
3
+ "version": "0.25.2",
4
4
  "type": "module",
5
5
  "description": "Codex-style context windows for Pi: durable reset windows, session history tools, and persistent notes.",
6
6
  "license": "MIT",
@@ -38,16 +38,17 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
38
38
  return usage !== undefined && usage.tokens !== null && usage.contextWindow - usage.tokens <= thresholdsFor(ctx).reserve;
39
39
  };
40
40
  const invalidateThresholds = () => { cachedPolicy = undefined; };
41
- let pendingGuidance: { windowId: string; content: string } | undefined;
42
- let pendingWarning: { windowId: string; content: string } | undefined;
43
- let pendingNotices: Array<{ windowId: string; customType: string }> = [];
41
+ const formatRemaining = (remaining: number): string => `${Math.max(0, Math.ceil(remaining / 1000))}k`;
42
+ let pendingGuidance: { windowId: string; content: string; remaining: number } | undefined;
43
+ let pendingWarning: { windowId: string; content: string; remaining: number } | undefined;
44
+ let pendingNotices: Array<{ windowId: string; customType: string; remaining: number }> = [];
44
45
  const notifyCommittedReminders = (ctx: ExtensionContext) => {
45
46
  const windowId = currentWindowId(ctx);
46
47
  for (const notice of pendingNotices) {
47
48
  if (notice.windowId !== windowId || !hasWindowMessage(ctx, notice.customType)) continue;
48
49
  ctx.ui.notify(notice.customType === WARNING_TYPE
49
- ? "pi-context: context budget criticalfinal checkpoint warning recorded for the model."
50
- : "pi-context: context budget low — checkpoint reminder recorded for the model, kept out of the chat view.", "warning");
50
+ ? `pi-context: Context almost full${formatRemaining(notice.remaining)} remaining`
51
+ : `pi-context: Context running low — ${formatRemaining(notice.remaining)} remaining`, "warning");
51
52
  }
52
53
  pendingNotices = [];
53
54
  };
@@ -71,7 +72,7 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
71
72
  clearStaged();
72
73
  const windowId = currentWindowId(ctx);
73
74
  const drafts = staged.filter((draft): draft is NonNullable<typeof draft> => draft !== undefined && draft.windowId === windowId);
74
- pendingNotices = drafts.map(({ windowId, customType }) => ({ windowId, customType }));
75
+ pendingNotices = drafts.map(({ windowId, customType, remaining }) => ({ windowId, customType, remaining }));
75
76
  return drafts.map((draft) => ({
76
77
  type: "custom_message" as const,
77
78
  customType: draft.customType,
@@ -88,7 +89,6 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
88
89
  // lifecycle point that must discard an uncommitted draft before the next prompt.
89
90
  // UI notices follow committed reminders. Aborted requests can retry their drafts
90
91
  // without showing the same low-budget notification twice.
91
- pi.on("turn_start", (_event, ctx) => notifyCommittedReminders(ctx));
92
92
  pi.on("agent_settled", (_event, ctx) => {
93
93
  notifyCommittedReminders(ctx);
94
94
  clearStaged();
@@ -106,7 +106,7 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
106
106
  // A not-yet-committed shallow reminder is superseded by the final warning.
107
107
  pendingGuidance = undefined;
108
108
  const content = `${GUIDANCE_OPEN_TAG}\n${WARNING_PROMPT}\n${GUIDANCE_CLOSE_TAG}`;
109
- pendingWarning = { windowId, content };
109
+ pendingWarning = { windowId, content, remaining };
110
110
  const warningMessage = {
111
111
  role: "custom" as const,
112
112
  customType: WARNING_TYPE,
@@ -121,7 +121,7 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
121
121
  // Persist at turn_end, before any reset drafts. A queued sendMessage could
122
122
  // otherwise cross the marker and leak the old window's reminder forward.
123
123
  const left = Math.max(0, remaining - warning);
124
- pendingGuidance = { windowId, content: tokenBudgetGuidance(left) };
124
+ pendingGuidance = { windowId, content: tokenBudgetGuidance(left), remaining };
125
125
  }
126
126
  return undefined;
127
127
  });