@diegopetrucci/pi-extensions 0.1.57 → 0.1.59
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/.pi-fleet-tested-version +1 -1
- package/extensions/agent-workflow-audit/.pi-fleet-tested-version +1 -1
- package/extensions/agent-workflow-audit/index.ts +8 -1
- package/extensions/agent-workflow-audit/package.json +5 -1
- package/extensions/annotate-git-diff/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-git-diff/package.json +5 -2
- package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-last-message/package.json +5 -2
- package/extensions/brrr/.pi-fleet-tested-version +1 -1
- package/extensions/brrr/README.md +2 -2
- package/extensions/brrr/package.json +5 -1
- package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
- package/extensions/claude-fast/package.json +5 -1
- package/extensions/code-reviewer/.pi-fleet-tested-version +1 -1
- package/extensions/code-reviewer/index.ts +129 -39
- package/extensions/code-reviewer/package.json +5 -1
- package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +20 -5
- package/extensions/confirm-destructive/package.json +5 -1
- package/extensions/context-cap/.pi-fleet-tested-version +1 -1
- package/extensions/context-cap/package.json +5 -1
- package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
- package/extensions/context-inspector/package.json +5 -1
- package/extensions/contrarian/.pi-fleet-tested-version +1 -1
- package/extensions/contrarian/README.md +1 -1
- package/extensions/contrarian/index.ts +199 -40
- package/extensions/contrarian/package.json +5 -1
- package/extensions/dirty-repo-guard/.pi-fleet-tested-version +1 -1
- package/extensions/dirty-repo-guard/README.md +1 -1
- package/extensions/dirty-repo-guard/package.json +5 -1
- package/extensions/dynamic-context-pruning/.pi-fleet-tested-version +1 -1
- package/extensions/dynamic-context-pruning/package.json +4 -1
- package/extensions/git-footer/.pi-fleet-tested-version +1 -1
- package/extensions/git-footer/package.json +5 -1
- package/extensions/gnosis/.pi-fleet-tested-version +1 -1
- package/extensions/gnosis/package.json +5 -1
- package/extensions/illustrations-to-explain-things/.pi-fleet-tested-version +1 -1
- package/extensions/illustrations-to-explain-things/package.json +4 -1
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/01-two-breakpoints.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/02-minimum-loop.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/03-sort-by-purpose.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/04-one-fish-many-uses.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/05-handoff-path.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/06-three-sources.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/07-three-content-jobs.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/08-handoff-copy-toolbox.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/09-common-pits-no-title.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/10-information-well.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/11-idea-press.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/12-content-fermentation.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/13-system-bearing.webp +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/14-trust-bridge.webp +0 -0
- package/extensions/inline-bash/.pi-fleet-tested-version +1 -1
- package/extensions/inline-bash/README.md +1 -1
- package/extensions/inline-bash/package.json +5 -1
- package/extensions/librarian/.pi-fleet-tested-version +1 -1
- package/extensions/librarian/index.ts +393 -107
- package/extensions/librarian/package.json +5 -1
- package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
- package/extensions/minimal-footer/index.ts +2 -5
- package/extensions/minimal-footer/openai-usage.ts +58 -11
- package/extensions/minimal-footer/package.json +5 -1
- package/extensions/notify/.pi-fleet-tested-version +1 -1
- package/extensions/notify/README.md +3 -3
- package/extensions/notify/package.json +5 -1
- package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
- package/extensions/openai-fast/package.json +5 -1
- package/extensions/oracle/.pi-fleet-tested-version +1 -1
- package/extensions/oracle/README.md +1 -1
- package/extensions/oracle/index.ts +200 -41
- package/extensions/oracle/package.json +5 -1
- package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
- package/extensions/permission-gate/README.md +1 -1
- package/extensions/permission-gate/index.ts +453 -2
- package/extensions/permission-gate/package.json +5 -1
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/package.json +5 -1
- package/extensions/review/.pi-fleet-tested-version +1 -1
- package/extensions/review/index.ts +53 -19
- package/extensions/review/package.json +5 -1
- package/extensions/todo/.pi-fleet-tested-version +1 -1
- package/extensions/todo/README.md +1 -1
- package/extensions/todo/package.json +5 -1
- package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
- package/extensions/triage-comments/index.ts +115 -34
- package/extensions/triage-comments/package.json +5 -1
- package/package.json +10 -6
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/01-two-breakpoints.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/02-minimum-loop.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/03-sort-by-purpose.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/04-one-fish-many-uses.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/05-handoff-path.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/06-three-sources.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/07-three-content-jobs.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/08-handoff-copy-toolbox.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/09-common-pits-no-title.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/10-information-well.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/11-idea-press.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/12-content-fermentation.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/13-system-bearing.png +0 -0
- package/extensions/illustrations-to-explain-things/skills/illustrations-to-explain-things/assets/examples/14-trust-bridge.png +0 -0
|
@@ -2,8 +2,8 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { StringEnum } from "@earendil-works/pi-ai";
|
|
6
|
-
import { getAgentDir, getMarkdownTheme, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { StringEnum, type Usage } from "@earendil-works/pi-ai";
|
|
6
|
+
import { getAgentDir, getMarkdownTheme, type AgentToolResult, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
8
8
|
import { Type } from "typebox";
|
|
9
9
|
|
|
@@ -25,7 +25,14 @@ interface UsageStats {
|
|
|
25
25
|
output: number;
|
|
26
26
|
cacheRead: number;
|
|
27
27
|
cacheWrite: number;
|
|
28
|
+
cacheWrite1h?: number;
|
|
29
|
+
reasoning?: number;
|
|
30
|
+
totalTokens: number;
|
|
28
31
|
cost: number;
|
|
32
|
+
costInput: number;
|
|
33
|
+
costOutput: number;
|
|
34
|
+
costCacheRead: number;
|
|
35
|
+
costCacheWrite: number;
|
|
29
36
|
turns: number;
|
|
30
37
|
contextTokens: number;
|
|
31
38
|
}
|
|
@@ -231,7 +238,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
231
238
|
"MiniMaxAI/MiniMax-M2.5",
|
|
232
239
|
"Qwen/Qwen3-Coder-Next",
|
|
233
240
|
],
|
|
234
|
-
"kimi-coding": ["
|
|
241
|
+
"kimi-coding": ["k3", "kimi-for-coding", "kimi-for-coding-highspeed"],
|
|
235
242
|
minimax: ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
236
243
|
"minimax-cn": ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
237
244
|
mistral: [
|
|
@@ -359,17 +366,35 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
359
366
|
"z-ai/glm-5.2",
|
|
360
367
|
"z-ai/glm-5.1",
|
|
361
368
|
],
|
|
369
|
+
"qwen-token-plan": [
|
|
370
|
+
"qwen3.8-max-preview",
|
|
371
|
+
"qwen3.7-max",
|
|
372
|
+
"qwen3.7-plus",
|
|
373
|
+
"qwen3.6-plus",
|
|
374
|
+
"deepseek-v4-pro",
|
|
375
|
+
"glm-5.2",
|
|
376
|
+
"kimi-k2.7-code",
|
|
377
|
+
"minimax-m2.5",
|
|
378
|
+
],
|
|
379
|
+
"qwen-token-plan-cn": [
|
|
380
|
+
"qwen3.8-max-preview",
|
|
381
|
+
"qwen3.7-max",
|
|
382
|
+
"qwen3.7-plus",
|
|
383
|
+
"qwen3.6-plus",
|
|
384
|
+
"deepseek-v4-pro",
|
|
385
|
+
"glm-5.2",
|
|
386
|
+
"kimi-k2.7-code",
|
|
387
|
+
"minimax-m2.5",
|
|
388
|
+
],
|
|
362
389
|
together: [
|
|
363
390
|
"deepseek-ai/DeepSeek-V4-Pro",
|
|
364
391
|
"zai-org/GLM-5.2",
|
|
365
|
-
"zai-org/GLM-5.1",
|
|
366
392
|
"moonshotai/Kimi-K2.7-Code",
|
|
367
393
|
"moonshotai/Kimi-K2.6",
|
|
368
394
|
"Qwen/Qwen3.7-Max",
|
|
369
395
|
"Qwen/Qwen3.6-Plus",
|
|
370
396
|
"MiniMaxAI/MiniMax-M3",
|
|
371
397
|
"MiniMaxAI/MiniMax-M2.7",
|
|
372
|
-
"Qwen/Qwen3.5-397B-A17B",
|
|
373
398
|
"openai/gpt-oss-120b",
|
|
374
399
|
"openai/gpt-oss-20b",
|
|
375
400
|
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
@@ -417,13 +442,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
417
442
|
],
|
|
418
443
|
xai: [
|
|
419
444
|
"grok-4.5",
|
|
420
|
-
"grok-4.20-0309-reasoning",
|
|
421
|
-
"grok-4.20-0309-non-reasoning",
|
|
422
445
|
"grok-4.3",
|
|
423
446
|
"grok-build-0.1",
|
|
424
|
-
"grok-code-fast-1",
|
|
425
|
-
"grok-3-fast",
|
|
426
|
-
"grok-3",
|
|
427
447
|
],
|
|
428
448
|
xiaomi: ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro", "mimo-v2-omni", "mimo-v2-flash"],
|
|
429
449
|
"xiaomi-token-plan-ams": ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro"],
|
|
@@ -434,7 +454,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
434
454
|
"nvidia/nemotron-3-super-120b-a12b",
|
|
435
455
|
"moonshotai/kimi-k2.6",
|
|
436
456
|
"z-ai/glm-5.2",
|
|
437
|
-
"
|
|
457
|
+
"minimaxai/minimax-m3",
|
|
458
|
+
"mistralai/mistral-large-3-675b-instruct-2512",
|
|
438
459
|
"openai/gpt-oss-120b",
|
|
439
460
|
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
|
|
440
461
|
"nvidia/nemotron-3-nano-30b-a3b",
|
|
@@ -501,12 +522,91 @@ function createEmptyUsage(): UsageStats {
|
|
|
501
522
|
output: 0,
|
|
502
523
|
cacheRead: 0,
|
|
503
524
|
cacheWrite: 0,
|
|
525
|
+
totalTokens: 0,
|
|
504
526
|
cost: 0,
|
|
527
|
+
costInput: 0,
|
|
528
|
+
costOutput: 0,
|
|
529
|
+
costCacheRead: 0,
|
|
530
|
+
costCacheWrite: 0,
|
|
505
531
|
turns: 0,
|
|
506
532
|
contextTokens: 0,
|
|
507
533
|
};
|
|
508
534
|
}
|
|
509
535
|
|
|
536
|
+
function cloneUsageStats(stats: UsageStats): UsageStats {
|
|
537
|
+
return { ...stats };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function mergeUsageStats(target: UsageStats, source: UsageStats | undefined): UsageStats {
|
|
541
|
+
if (!source) return target;
|
|
542
|
+
target.input += source.input;
|
|
543
|
+
target.output += source.output;
|
|
544
|
+
target.cacheRead += source.cacheRead;
|
|
545
|
+
target.cacheWrite += source.cacheWrite;
|
|
546
|
+
if (source.cacheWrite1h !== undefined) {
|
|
547
|
+
target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
|
|
548
|
+
}
|
|
549
|
+
if (source.reasoning !== undefined) {
|
|
550
|
+
target.reasoning = (target.reasoning ?? 0) + source.reasoning;
|
|
551
|
+
}
|
|
552
|
+
target.totalTokens += source.totalTokens;
|
|
553
|
+
target.cost += source.cost;
|
|
554
|
+
target.costInput += source.costInput;
|
|
555
|
+
target.costOutput += source.costOutput;
|
|
556
|
+
target.costCacheRead += source.costCacheRead;
|
|
557
|
+
target.costCacheWrite += source.costCacheWrite;
|
|
558
|
+
target.turns += source.turns;
|
|
559
|
+
target.contextTokens = source.contextTokens || target.contextTokens;
|
|
560
|
+
return target;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function usageStatsToToolUsage(stats: UsageStats): Usage {
|
|
564
|
+
return {
|
|
565
|
+
input: stats.input,
|
|
566
|
+
output: stats.output,
|
|
567
|
+
cacheRead: stats.cacheRead,
|
|
568
|
+
cacheWrite: stats.cacheWrite,
|
|
569
|
+
...(stats.cacheWrite1h !== undefined ? { cacheWrite1h: stats.cacheWrite1h } : {}),
|
|
570
|
+
...(stats.reasoning !== undefined ? { reasoning: stats.reasoning } : {}),
|
|
571
|
+
totalTokens: stats.totalTokens,
|
|
572
|
+
cost: {
|
|
573
|
+
input: stats.costInput,
|
|
574
|
+
output: stats.costOutput,
|
|
575
|
+
cacheRead: stats.costCacheRead,
|
|
576
|
+
cacheWrite: stats.costCacheWrite,
|
|
577
|
+
total: stats.cost,
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function addUsageStats(
|
|
583
|
+
target: UsageStats,
|
|
584
|
+
source: Usage | undefined,
|
|
585
|
+
options: { countTurn?: boolean; updateContextTokens?: boolean } = {},
|
|
586
|
+
): void {
|
|
587
|
+
if (!source) return;
|
|
588
|
+
if (options.countTurn) target.turns += 1;
|
|
589
|
+
target.input += source.input || 0;
|
|
590
|
+
target.output += source.output || 0;
|
|
591
|
+
target.cacheRead += source.cacheRead || 0;
|
|
592
|
+
target.cacheWrite += source.cacheWrite || 0;
|
|
593
|
+
if (source.cacheWrite1h !== undefined) {
|
|
594
|
+
target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
|
|
595
|
+
}
|
|
596
|
+
if (source.reasoning !== undefined) {
|
|
597
|
+
target.reasoning = (target.reasoning ?? 0) + source.reasoning;
|
|
598
|
+
}
|
|
599
|
+
target.totalTokens += source.totalTokens || 0;
|
|
600
|
+
target.cost += source.cost?.total || 0;
|
|
601
|
+
target.costInput += source.cost?.input || 0;
|
|
602
|
+
target.costOutput += source.cost?.output || 0;
|
|
603
|
+
target.costCacheRead += source.cost?.cacheRead || 0;
|
|
604
|
+
target.costCacheWrite += source.cost?.cacheWrite || 0;
|
|
605
|
+
if (options.updateContextTokens) {
|
|
606
|
+
target.contextTokens = source.totalTokens || target.contextTokens;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
510
610
|
function getOracleConfigPath(): string {
|
|
511
611
|
return path.join(getAgentDir(), "extensions", ORACLE_CONFIG_FILE);
|
|
512
612
|
}
|
|
@@ -595,6 +695,7 @@ function formatUsage(stats: UsageStats): string {
|
|
|
595
695
|
if (stats.output) parts.push(`↓${formatTokens(stats.output)}`);
|
|
596
696
|
if (stats.cacheRead) parts.push(`R${formatTokens(stats.cacheRead)}`);
|
|
597
697
|
if (stats.cacheWrite) parts.push(`W${formatTokens(stats.cacheWrite)}`);
|
|
698
|
+
if (stats.reasoning) parts.push(`T${formatTokens(stats.reasoning)}`);
|
|
598
699
|
if (stats.cost) parts.push(`$${stats.cost.toFixed(4)}`);
|
|
599
700
|
if (stats.contextTokens) parts.push(`ctx:${formatTokens(stats.contextTokens)}`);
|
|
600
701
|
return parts.join(" ");
|
|
@@ -934,6 +1035,7 @@ async function runOracle(
|
|
|
934
1035
|
signal: AbortSignal | undefined,
|
|
935
1036
|
onUpdate: ((result: { content: Array<{ type: "text"; text: string }>; details: OracleDetails }) => void) | undefined,
|
|
936
1037
|
defaultCwd: string,
|
|
1038
|
+
spawnImpl: typeof spawn = spawn,
|
|
937
1039
|
): Promise<{ ok: true; output: string; details: OracleDetails } | { ok: false; error: string; details: OracleDetails }> {
|
|
938
1040
|
const cwd = params.cwd ?? defaultCwd;
|
|
939
1041
|
const includeBash = params.includeBash ?? false;
|
|
@@ -985,7 +1087,7 @@ async function runOracle(
|
|
|
985
1087
|
let wasAborted = false;
|
|
986
1088
|
|
|
987
1089
|
const exitCode = await new Promise<number>((resolve) => {
|
|
988
|
-
const proc =
|
|
1090
|
+
const proc = spawnImpl(invocation.command, invocation.args, {
|
|
989
1091
|
cwd,
|
|
990
1092
|
shell: false,
|
|
991
1093
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1023,18 +1125,16 @@ async function runOracle(
|
|
|
1023
1125
|
const errorMessageField = event.message.errorMessage;
|
|
1024
1126
|
lastErrorMessage = typeof errorMessageField === "string" ? errorMessageField : undefined;
|
|
1025
1127
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
usage.output += messageUsage.output || 0;
|
|
1031
|
-
usage.cacheRead += messageUsage.cacheRead || 0;
|
|
1032
|
-
usage.cacheWrite += messageUsage.cacheWrite || 0;
|
|
1033
|
-
usage.cost += messageUsage.cost?.total || 0;
|
|
1034
|
-
usage.contextTokens = messageUsage.totalTokens || usage.contextTokens;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1128
|
+
addUsageStats(usage, event.message.usage as Usage | undefined, {
|
|
1129
|
+
countTurn: true,
|
|
1130
|
+
updateContextTokens: true,
|
|
1131
|
+
});
|
|
1037
1132
|
emit();
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (event.type === "compaction_end") {
|
|
1137
|
+
addUsageStats(usage, event.result?.usage as Usage | undefined);
|
|
1038
1138
|
}
|
|
1039
1139
|
};
|
|
1040
1140
|
|
|
@@ -1050,26 +1150,53 @@ async function runOracle(
|
|
|
1050
1150
|
emit();
|
|
1051
1151
|
});
|
|
1052
1152
|
|
|
1153
|
+
let closed = false;
|
|
1154
|
+
let killTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1155
|
+
let abortListener: (() => void) | undefined;
|
|
1156
|
+
|
|
1157
|
+
const childHasExited = () => proc.exitCode !== null || proc.signalCode !== null;
|
|
1158
|
+
const clearEscalation = () => {
|
|
1159
|
+
if (killTimer) {
|
|
1160
|
+
clearTimeout(killTimer);
|
|
1161
|
+
killTimer = undefined;
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
const cleanup = () => {
|
|
1165
|
+
clearEscalation();
|
|
1166
|
+
if (signal && abortListener) {
|
|
1167
|
+
signal.removeEventListener("abort", abortListener);
|
|
1168
|
+
abortListener = undefined;
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
proc.on("exit", () => {
|
|
1173
|
+
clearEscalation();
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1053
1176
|
proc.on("close", (code) => {
|
|
1177
|
+
closed = true;
|
|
1178
|
+
cleanup();
|
|
1054
1179
|
if (buffer.trim()) processLine(buffer);
|
|
1055
1180
|
resolve(code ?? 0);
|
|
1056
1181
|
});
|
|
1057
1182
|
|
|
1058
1183
|
proc.on("error", (error) => {
|
|
1184
|
+
cleanup();
|
|
1059
1185
|
stderr += `${error instanceof Error ? error.message : String(error)}\n`;
|
|
1060
1186
|
resolve(1);
|
|
1061
1187
|
});
|
|
1062
1188
|
|
|
1063
1189
|
if (signal) {
|
|
1064
|
-
|
|
1190
|
+
abortListener = () => {
|
|
1065
1191
|
wasAborted = true;
|
|
1066
1192
|
proc.kill("SIGTERM");
|
|
1067
|
-
|
|
1068
|
-
|
|
1193
|
+
if (closed || childHasExited()) return;
|
|
1194
|
+
killTimer = setTimeout(() => {
|
|
1195
|
+
if (!closed && !childHasExited()) proc.kill("SIGKILL");
|
|
1069
1196
|
}, 5000);
|
|
1070
1197
|
};
|
|
1071
|
-
if (signal.aborted)
|
|
1072
|
-
else signal.addEventListener("abort",
|
|
1198
|
+
if (signal.aborted) abortListener();
|
|
1199
|
+
else signal.addEventListener("abort", abortListener, { once: true });
|
|
1073
1200
|
}
|
|
1074
1201
|
});
|
|
1075
1202
|
|
|
@@ -1078,6 +1205,7 @@ async function runOracle(
|
|
|
1078
1205
|
details.durationMs = Date.now() - startedAt;
|
|
1079
1206
|
|
|
1080
1207
|
if (wasAborted) {
|
|
1208
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1081
1209
|
return { ok: false, error: "Oracle was aborted.", details };
|
|
1082
1210
|
}
|
|
1083
1211
|
|
|
@@ -1087,6 +1215,7 @@ async function runOracle(
|
|
|
1087
1215
|
// so callers can distinguish transient provider errors from a genuinely
|
|
1088
1216
|
// empty response.
|
|
1089
1217
|
if (lastStopReason === "error" || lastStopReason === "aborted") {
|
|
1218
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1090
1219
|
return {
|
|
1091
1220
|
ok: false,
|
|
1092
1221
|
error: formatOracleModelError(lastStopReason, lastErrorMessage),
|
|
@@ -1103,6 +1232,7 @@ async function runOracle(
|
|
|
1103
1232
|
}
|
|
1104
1233
|
|
|
1105
1234
|
if (!finalOutput.trim()) {
|
|
1235
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1106
1236
|
return {
|
|
1107
1237
|
ok: false,
|
|
1108
1238
|
error: details.stderr || "Oracle finished without returning any text.",
|
|
@@ -1123,14 +1253,26 @@ function renderCollapsedText(text: string, lineLimit = COLLAPSED_LINE_LIMIT): st
|
|
|
1123
1253
|
return [...lines.slice(0, lineLimit), `... (${lines.length - lineLimit} more lines)`].join("\n");
|
|
1124
1254
|
}
|
|
1125
1255
|
|
|
1256
|
+
type OracleExtensionDeps = {
|
|
1257
|
+
spawnImpl?: typeof spawn;
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1126
1260
|
export const __test__ = {
|
|
1261
|
+
createOracleExtension,
|
|
1127
1262
|
findAvailableModel,
|
|
1263
|
+
isModelAvailabilityError,
|
|
1128
1264
|
parseModelPreference,
|
|
1265
|
+
runOracle,
|
|
1129
1266
|
resolveThinkingLevel,
|
|
1130
1267
|
selectOracleModel,
|
|
1131
1268
|
};
|
|
1132
1269
|
|
|
1133
1270
|
export default function oracleExtension(pi: ExtensionAPI) {
|
|
1271
|
+
createOracleExtension(pi);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
function createOracleExtension(pi: ExtensionAPI, deps: OracleExtensionDeps = {}) {
|
|
1275
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
1134
1276
|
const activeRuns = new Map<string, OracleUiRun>();
|
|
1135
1277
|
let preferences: OraclePreferences = {};
|
|
1136
1278
|
|
|
@@ -1276,6 +1418,13 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
1276
1418
|
},
|
|
1277
1419
|
});
|
|
1278
1420
|
|
|
1421
|
+
pi.on("tool_result", async (event) => {
|
|
1422
|
+
if (event.toolName !== "oracle") return undefined;
|
|
1423
|
+
const details = event.details as OracleDetails | undefined;
|
|
1424
|
+
if ((details?.exitCode ?? 0) === 0) return undefined;
|
|
1425
|
+
return { isError: true };
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1279
1428
|
pi.registerTool({
|
|
1280
1429
|
name: "oracle",
|
|
1281
1430
|
label: "Oracle",
|
|
@@ -1284,10 +1433,11 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
1284
1433
|
promptSnippet:
|
|
1285
1434
|
"Consult a read-only oracle that auto-selects the strongest reasoning model on the current provider/subscription.",
|
|
1286
1435
|
promptGuidelines: [
|
|
1287
|
-
"Use
|
|
1288
|
-
"Do not use
|
|
1289
|
-
"The oracle is read-only by default
|
|
1290
|
-
"
|
|
1436
|
+
"Use oracle sparingly when you want a second opinion, deeper analysis, code review, debugging help, or a higher-reasoning pass.",
|
|
1437
|
+
"Do not use oracle for routine low-value work; oracle is slower than the main agent.",
|
|
1438
|
+
"The oracle tool is read-only by default and only exposes read, grep, find, and ls unless oracle includeBash is enabled.",
|
|
1439
|
+
"Set oracle includeBash only when the extra bash inspection tool is genuinely useful; keep oracle relying on read, grep, find, and ls otherwise.",
|
|
1440
|
+
"The oracle tool requests xhigh by default for reasoning models; oracle defaults and explicit thinkingLevel overrides are clamped to the effective model-supported level when the model is matched.",
|
|
1291
1441
|
],
|
|
1292
1442
|
parameters: OracleParams,
|
|
1293
1443
|
|
|
@@ -1312,6 +1462,14 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
1312
1462
|
try {
|
|
1313
1463
|
const attempts: OracleSelection[] = [];
|
|
1314
1464
|
const seen = new Set<string>();
|
|
1465
|
+
const aggregateUsage = createEmptyUsage();
|
|
1466
|
+
const finalizeResult = (
|
|
1467
|
+
result: { content: Array<{ type: "text"; text: string }>; details: OracleDetails },
|
|
1468
|
+
): AgentToolResult<OracleDetails> => ({
|
|
1469
|
+
...result,
|
|
1470
|
+
details: { ...result.details, usage: cloneUsageStats(aggregateUsage) },
|
|
1471
|
+
usage: usageStatsToToolUsage(aggregateUsage),
|
|
1472
|
+
});
|
|
1315
1473
|
const pushAttempt = (candidate: OracleSelection | undefined) => {
|
|
1316
1474
|
if (!candidate) return;
|
|
1317
1475
|
const key = `${candidate.provider}/${candidate.modelId}`.toLowerCase();
|
|
@@ -1353,7 +1511,7 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
1353
1511
|
} else {
|
|
1354
1512
|
const selectionResult = await selectOracleModel(ctx, configuredThinkingLevel);
|
|
1355
1513
|
if (!selectionResult.ok) {
|
|
1356
|
-
return {
|
|
1514
|
+
return finalizeResult({
|
|
1357
1515
|
content: [{ type: "text", text: selectionResult.error }],
|
|
1358
1516
|
details: {
|
|
1359
1517
|
modelRef: "",
|
|
@@ -1370,7 +1528,7 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
1370
1528
|
durationMs: 0,
|
|
1371
1529
|
cwd: params.cwd ?? ctx.cwd,
|
|
1372
1530
|
},
|
|
1373
|
-
};
|
|
1531
|
+
});
|
|
1374
1532
|
}
|
|
1375
1533
|
for (const candidate of selectionResult.ordered) pushAttempt(candidate);
|
|
1376
1534
|
}
|
|
@@ -1394,25 +1552,26 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
1394
1552
|
uiRun.selection = attempt;
|
|
1395
1553
|
updateOracleUi(ctx, activeRuns);
|
|
1396
1554
|
|
|
1397
|
-
const result = await runOracle(attempt, { ...params, task }, signal, handleUpdate, ctx.cwd);
|
|
1555
|
+
const result = await runOracle(attempt, { ...params, task }, signal, handleUpdate, ctx.cwd, spawnImpl);
|
|
1556
|
+
mergeUsageStats(aggregateUsage, result.details.usage);
|
|
1398
1557
|
if (result.ok) {
|
|
1399
|
-
return {
|
|
1558
|
+
return finalizeResult({
|
|
1400
1559
|
content: [{ type: "text", text: result.output }],
|
|
1401
1560
|
details: result.details,
|
|
1402
|
-
};
|
|
1561
|
+
});
|
|
1403
1562
|
}
|
|
1404
1563
|
|
|
1405
1564
|
lastErrorResult = result;
|
|
1406
1565
|
const canFallBack = index < attempts.length - 1 && isModelAvailabilityError(result.error);
|
|
1407
1566
|
if (!canFallBack) {
|
|
1408
|
-
return {
|
|
1567
|
+
return finalizeResult({
|
|
1409
1568
|
content: [{ type: "text", text: result.error }],
|
|
1410
1569
|
details: result.details,
|
|
1411
|
-
};
|
|
1570
|
+
});
|
|
1412
1571
|
}
|
|
1413
1572
|
}
|
|
1414
1573
|
|
|
1415
|
-
return {
|
|
1574
|
+
return finalizeResult({
|
|
1416
1575
|
content: [
|
|
1417
1576
|
{ type: "text", text: lastErrorResult?.error ?? "Oracle could not select an available model." },
|
|
1418
1577
|
],
|
|
@@ -1432,7 +1591,7 @@ export default function oracleExtension(pi: ExtensionAPI) {
|
|
|
1432
1591
|
durationMs: 0,
|
|
1433
1592
|
cwd: params.cwd ?? ctx.cwd,
|
|
1434
1593
|
},
|
|
1435
|
-
};
|
|
1594
|
+
});
|
|
1436
1595
|
} finally {
|
|
1437
1596
|
activeRuns.delete(toolCallId);
|
|
1438
1597
|
updateOracleUi(ctx, activeRuns);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-oracle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "An Amp-style oracle extension for pi that consults the strongest reasoning model on your current provider.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -34,5 +34,9 @@
|
|
|
34
34
|
"@earendil-works/pi-coding-agent": "*",
|
|
35
35
|
"@earendil-works/pi-tui": "*",
|
|
36
36
|
"typebox": "*"
|
|
37
|
+
},
|
|
38
|
+
"type": "module",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=22.19.0"
|
|
37
41
|
}
|
|
38
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A small pi extension that prompts for confirmation before running potentially dangerous bash commands or writing to protected paths.
|
|
4
4
|
|
|
5
|
-
This is adapted from the original `permission-gate.ts` example in [`earendil-works/pi
|
|
5
|
+
This is adapted from the original `permission-gate.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/permission-gate.ts) and kept basically the same.
|
|
6
6
|
|
|
7
7
|
## What it checks
|
|
8
8
|
|