pi-sage 0.2.13 → 0.2.14

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.
@@ -7,14 +7,20 @@ import {
7
7
  isHardCostCapExceeded,
8
8
  makeBlockedResult
9
9
  } from "./policy.js";
10
- import { isSageRunnerPolicyError, runSageSingleShot } from "./runner.js";
10
+ import {
11
+ isSageRunnerPolicyError,
12
+ runSageSingleShot,
13
+ type SageRunnerInput,
14
+ type SageRunnerOutput
15
+ } from "./runner.js";
11
16
  import {
12
17
  getSettingsPathForScope,
13
18
  loadSettings,
14
19
  mergeSettings,
15
20
  saveSettings,
16
21
  type SageSettings,
17
- type SettingsScope
22
+ type SettingsScope,
23
+ type ToolPolicySettings
18
24
  } from "./settings.js";
19
25
  import { checkVolumeCaps, getDisallowedCustomTools, resolveToolPolicy } from "./tool-policy.js";
20
26
  import type {
@@ -232,10 +238,8 @@ export default function registerSageExtension(pi: ExtensionAPI): void {
232
238
  });
233
239
  }
234
240
 
235
- const toolPolicy = resolveToolPolicy(settings.toolPolicy);
236
-
237
241
  try {
238
- const runnerResult = await runSageSingleShot({
242
+ const runnerInput: Omit<SageRunnerInput, "toolPolicy"> = {
239
243
  cwd: ctx.cwd,
240
244
  model: resolvedModel,
241
245
  reasoningLevel: settings.reasoningLevel,
@@ -246,11 +250,13 @@ export default function registerSageExtension(pi: ExtensionAPI): void {
246
250
  evidence: input.evidence,
247
251
  objective: input.objective,
248
252
  urgency: input.urgency,
249
- toolPolicy: settings.toolPolicy,
250
253
  signal
251
- });
254
+ };
255
+
256
+ const invocation = await runSageWithFallback(runnerInput, settings.toolPolicy);
257
+ const toolPolicy = resolveToolPolicy(invocation.toolPolicyUsed);
252
258
 
253
- const volume = checkVolumeCaps(runnerResult.toolUsage, toolPolicy);
259
+ const volume = checkVolumeCaps(invocation.result.toolUsage, toolPolicy);
254
260
  if (!volume.ok) {
255
261
  return makeBlockedResult({
256
262
  mode,
@@ -266,21 +272,25 @@ export default function registerSageExtension(pi: ExtensionAPI): void {
266
272
  budgetState.callsThisTurn += 1;
267
273
  budgetState.sessionCalls += 1;
268
274
  if (mode === "autonomous") budgetState.lastAutoTurn = budgetState.currentTurn;
269
- if (runnerResult.usage?.costTotal) budgetState.sessionCostTotal += runnerResult.usage.costTotal;
275
+ if (invocation.result.usage?.costTotal) budgetState.sessionCostTotal += invocation.result.usage.costTotal;
276
+
277
+ const contextReason = invocation.fallbackReason
278
+ ? "eligible; fallback to read-only-lite after blocked bash command"
279
+ : "eligible";
270
280
 
271
281
  return {
272
- content: [{ type: "text", text: runnerResult.text }],
282
+ content: [{ type: "text", text: invocation.result.text }],
273
283
  details: {
274
284
  model: resolvedModel,
275
285
  reasoningLevel: settings.reasoningLevel,
276
- latencyMs: runnerResult.latencyMs,
277
- stopReason: runnerResult.stopReason,
278
- usage: runnerResult.usage,
279
- toolUsage: runnerResult.toolUsage,
286
+ latencyMs: invocation.result.latencyMs,
287
+ stopReason: invocation.result.stopReason,
288
+ usage: invocation.result.usage,
289
+ toolUsage: invocation.result.toolUsage,
280
290
  policy: {
281
291
  mode,
282
292
  allowedByContext: true,
283
- contextReason: "eligible",
293
+ contextReason,
284
294
  allowedByBudget: true,
285
295
  budgetReason: softBudget.reason
286
296
  }
@@ -353,6 +363,32 @@ function formatRunnerPolicyReason(blockCode: BlockCode, reason: string): string
353
363
  return reason;
354
364
  }
355
365
 
366
+ async function runSageWithFallback(
367
+ runnerInput: Omit<SageRunnerInput, "toolPolicy">,
368
+ toolPolicy: ToolPolicySettings
369
+ ): Promise<{ result: SageRunnerOutput; toolPolicyUsed: ToolPolicySettings; fallbackReason?: string }> {
370
+ try {
371
+ const result = await runSageSingleShot({ ...runnerInput, toolPolicy });
372
+ return { result, toolPolicyUsed: toolPolicy };
373
+ } catch (error) {
374
+ if (
375
+ isSageRunnerPolicyError(error) &&
376
+ error.blockCode === "tool-disallowed" &&
377
+ toolPolicy.profile === "git-review-readonly"
378
+ ) {
379
+ const fallbackPolicy: ToolPolicySettings = {
380
+ ...toolPolicy,
381
+ profile: "read-only-lite"
382
+ };
383
+
384
+ const result = await runSageSingleShot({ ...runnerInput, toolPolicy: fallbackPolicy });
385
+ return { result, toolPolicyUsed: fallbackPolicy, fallbackReason: error.message };
386
+ }
387
+
388
+ throw error;
389
+ }
390
+ }
391
+
356
392
  function buildCallerContext(lastInputSource: InputSource | undefined, hasUI: boolean): CallerContext | null {
357
393
  if (!lastInputSource) return null;
358
394
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sage",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Interactive-only advisory Sage extension for Pi",