@herbertgao/sol-pi 0.1.0 → 0.3.0

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.
@@ -17,9 +17,11 @@ SoL-Pi imports only public package exports:
17
17
 
18
18
  The built-in edit/write definitions capture their working directory, so SoL-Pi caches one definition per `ctx.cwd`. Its own per-file queue surrounds the built-in mutation and follow-up command. It does not nest Pi's built-in mutation queue.
19
19
 
20
+ Action Fusion decodes `file://` targets with Node's `fileURLToPath()` before resolving the queue and hash-check path. This keeps file URLs, including percent-encoded filenames and Pi's optional `@` prefix, aligned with the file handled by the built-in mutation tool.
21
+
20
22
  The queue covers only fused operations registered by this SoL-Pi instance. External processes, direct built-in-tool calls outside the replacement, and unrelated extensions are not globally locked. SoL-Pi hashes the target immediately before launching `then_run` and skips the command if it observes an intervening content change.
21
23
 
22
- When another extension already owns `write`, Action Fusion leaves that tool in place and only registers its `edit` replacement. When `pi-automode` exposes `automode_inspect`, Action Fusion registers neither mutation replacement: nested `then_run` cannot re-enter Pi's public `tool_call` permission pipeline, so disabling the fusion is the safe fallback.
24
+ When another extension already owns `write`, Action Fusion leaves that tool in place and only registers its `edit` replacement.
23
25
 
24
26
  ## ObservationPack
25
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/sol-pi",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Token-efficient context and tool extensions for Pi, maintained from NVlabs/SoL-Pi.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -71,6 +71,6 @@
71
71
  "version": "0.1.0",
72
72
  "reviewedVersion": "0.1.0",
73
73
  "repository": "https://github.com/NVlabs/SoL-Pi",
74
- "commit": "8f8c13916c97fb54c32008e41c05421f9b69ac3b"
74
+ "commit": "22277b7e0c3c46ba1259a6687f31fe39ade421a5"
75
75
  }
76
76
  }
@@ -6,6 +6,7 @@
6
6
  import { realpath } from "node:fs/promises";
7
7
  import { homedir } from "node:os";
8
8
  import { basename, dirname, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
9
10
 
10
11
  const queueTails = new Map<string, Promise<void>>();
11
12
 
@@ -14,7 +15,9 @@ function stripToolPathPrefix(filePath: string): string {
14
15
  }
15
16
 
16
17
  export function resolveToolPath(cwd: string, filePath: string): string {
17
- const expanded = stripToolPathPrefix(filePath);
18
+ const stripped = stripToolPathPrefix(filePath);
19
+ // Pi accepts file URLs; the queue and hash guard must use the same target.
20
+ const expanded = stripped.startsWith("file://") ? fileURLToPath(stripped) : stripped;
18
21
  if (expanded === "~") return homedir();
19
22
  if (expanded.startsWith("~/")) return resolve(homedir(), expanded.slice(2));
20
23
  return resolve(cwd, expanded);
@@ -80,11 +80,6 @@ export function createActionFusionExtension(options: ActionFusionOptions = {}):
80
80
  const baseWrite = memoizeByCwd((cwd: string) => createWriteToolDefinition(cwd, options.writeOptions));
81
81
 
82
82
  return (pi: ExtensionAPI) => {
83
- // then_run executes Bash inside the mutation tool, so it cannot pass through
84
- // Pi's public tool_call permission handlers. Leave it to automode instead of
85
- // creating a nested shell escape hatch.
86
- if (hasExternalToolOwner(pi, "automode_inspect")) return;
87
-
88
83
  const editTemplate = baseEdit(process.cwd());
89
84
  const writeTemplate = baseWrite(process.cwd());
90
85
 
@@ -218,9 +218,8 @@ export function decideCompaction(input: {
218
218
  carriedDebtTokens: input.carriedDebtTokens,
219
219
  cacheDebtRepaymentTokens: input.cacheDebtRepaymentTokens,
220
220
  compact,
221
- reason: !compressible
222
- ? "non_positive_saving"
223
- : windowProtection
221
+ reason: compressible
222
+ ? windowProtection
224
223
  ? "window_protection"
225
224
  : economic
226
225
  ? "economic"
@@ -232,6 +231,7 @@ export function decideCompaction(input: {
232
231
  ? "deferred_subsequent_margin"
233
232
  : !firstCompaction && baseEconomic && !carriedDebtGateOpen
234
233
  ? "deferred_carried_debt"
235
- : "deferred_economic",
234
+ : "deferred_economic"
235
+ : "non_positive_saving",
236
236
  };
237
237
  }
@@ -2,12 +2,13 @@
2
2
  * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
- import type { AgentMessage, AgentToolResult } from "@earendil-works/pi-agent-core";
5
+ import type { AgentMessage, AgentToolResult, ThinkingLevel } from "@earendil-works/pi-agent-core";
6
6
  import {
7
7
  buildSessionContext,
8
8
  estimateTokens,
9
9
  findCutPoint,
10
10
  sessionEntryToContextMessages,
11
+ type ExtensionAPI,
11
12
  type ExtensionContext,
12
13
  type ExtensionFactory,
13
14
  type SessionEntry,
@@ -165,6 +166,37 @@ function validPositiveInteger(value: unknown): value is number {
165
166
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
166
167
  }
167
168
 
169
+ type CompactionThinkingOverride = {
170
+ readonly previous: ThinkingLevel;
171
+ readonly applied: ThinkingLevel;
172
+ };
173
+
174
+ // Native summaries share their output cap with reasoning; reserve the cap for summary text.
175
+ function lowerCompactionThinking(
176
+ pi: ExtensionAPI,
177
+ reasoning: boolean,
178
+ ): CompactionThinkingOverride | undefined {
179
+ if (!reasoning) return;
180
+ try {
181
+ const previous = pi.getThinkingLevel();
182
+ if (previous !== "high" && previous !== "xhigh" && previous !== "max") return;
183
+ pi.setThinkingLevel("medium");
184
+ const applied = pi.getThinkingLevel();
185
+ return applied === previous ? undefined : { previous, applied };
186
+ } catch {
187
+ return;
188
+ }
189
+ }
190
+
191
+ function restoreCompactionThinking(pi: ExtensionAPI, override: CompactionThinkingOverride | undefined): void {
192
+ if (!override) return;
193
+ try {
194
+ if (pi.getThinkingLevel() === override.applied) pi.setThinkingLevel(override.previous);
195
+ } catch {
196
+ // A compaction result must not be masked by a best-effort state restore.
197
+ }
198
+ }
199
+
168
200
  function releaseParentContinuation(continuation: PendingContinuation | undefined): void {
169
201
  if (continuation) setTimeout(continuation.resolve, 0);
170
202
  }
@@ -182,6 +214,7 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
182
214
  let activeDebt: CacheDebt | undefined;
183
215
  let nextContinuation: PendingContinuation | undefined;
184
216
  let compactionInFlight = false;
217
+ let compactionThinkingOverride: CompactionThinkingOverride | undefined;
185
218
 
186
219
  const releaseContinuation = (): void => {
187
220
  const continuation = nextContinuation;
@@ -349,6 +382,7 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
349
382
  let compactionError: Error | undefined;
350
383
  try {
351
384
  compactionInFlight = true;
385
+ compactionThinkingOverride = lowerCompactionThinking(pi, context.model?.reasoning === true);
352
386
  await new Promise<void>((resolve) => {
353
387
  let finished = false;
354
388
  const finish = (): void => {
@@ -382,6 +416,8 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
382
416
  },
383
417
  });
384
418
  });
419
+ restoreCompactionThinking(pi, compactionThinkingOverride);
420
+ compactionThinkingOverride = undefined;
385
421
  compactionInFlight = false;
386
422
  if (
387
423
  compactionError &&
@@ -422,6 +458,8 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
422
458
  await continuation.promise;
423
459
  }
424
460
  } finally {
461
+ restoreCompactionThinking(pi, compactionThinkingOverride);
462
+ compactionThinkingOverride = undefined;
425
463
  compactionInFlight = false;
426
464
  activeDebt = undefined;
427
465
  releaseParentContinuation(parentContinuation);
@@ -445,6 +483,8 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
445
483
  });
446
484
 
447
485
  pi.on("session_shutdown", () => {
486
+ restoreCompactionThinking(pi, compactionThinkingOverride);
487
+ compactionThinkingOverride = undefined;
448
488
  releaseContinuation();
449
489
  pendingBoundary = undefined;
450
490
  selected = undefined;
@@ -180,7 +180,9 @@ export function recordCompaction(
180
180
  return {
181
181
  ...state,
182
182
  epoch: state.epoch + 1,
183
- plan: [],
183
+ // Keep the plan so the post-compaction rebuild does not reclassify
184
+ // already-completed steps as a new boundary.
185
+ plan: [...state.plan],
184
186
  pendingProgress: [],
185
187
  lastContextTokens: null,
186
188
  positiveContextDeltaTotal: 0,