@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
|
}
|
|
@@ -247,7 +254,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
247
254
|
"MiniMaxAI/MiniMax-M2.5",
|
|
248
255
|
"Qwen/Qwen3-Coder-Next",
|
|
249
256
|
],
|
|
250
|
-
"kimi-coding": ["
|
|
257
|
+
"kimi-coding": ["k3", "kimi-for-coding", "kimi-for-coding-highspeed"],
|
|
251
258
|
minimax: ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
252
259
|
"minimax-cn": ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
|
|
253
260
|
mistral: [
|
|
@@ -375,17 +382,35 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
375
382
|
"z-ai/glm-5.2",
|
|
376
383
|
"z-ai/glm-5.1",
|
|
377
384
|
],
|
|
385
|
+
"qwen-token-plan": [
|
|
386
|
+
"qwen3.8-max-preview",
|
|
387
|
+
"qwen3.7-max",
|
|
388
|
+
"qwen3.7-plus",
|
|
389
|
+
"qwen3.6-plus",
|
|
390
|
+
"deepseek-v4-pro",
|
|
391
|
+
"glm-5.2",
|
|
392
|
+
"kimi-k2.7-code",
|
|
393
|
+
"minimax-m2.5",
|
|
394
|
+
],
|
|
395
|
+
"qwen-token-plan-cn": [
|
|
396
|
+
"qwen3.8-max-preview",
|
|
397
|
+
"qwen3.7-max",
|
|
398
|
+
"qwen3.7-plus",
|
|
399
|
+
"qwen3.6-plus",
|
|
400
|
+
"deepseek-v4-pro",
|
|
401
|
+
"glm-5.2",
|
|
402
|
+
"kimi-k2.7-code",
|
|
403
|
+
"minimax-m2.5",
|
|
404
|
+
],
|
|
378
405
|
together: [
|
|
379
406
|
"deepseek-ai/DeepSeek-V4-Pro",
|
|
380
407
|
"zai-org/GLM-5.2",
|
|
381
|
-
"zai-org/GLM-5.1",
|
|
382
408
|
"moonshotai/Kimi-K2.7-Code",
|
|
383
409
|
"moonshotai/Kimi-K2.6",
|
|
384
410
|
"Qwen/Qwen3.7-Max",
|
|
385
411
|
"Qwen/Qwen3.6-Plus",
|
|
386
412
|
"MiniMaxAI/MiniMax-M3",
|
|
387
413
|
"MiniMaxAI/MiniMax-M2.7",
|
|
388
|
-
"Qwen/Qwen3.5-397B-A17B",
|
|
389
414
|
"openai/gpt-oss-120b",
|
|
390
415
|
"openai/gpt-oss-20b",
|
|
391
416
|
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
@@ -433,13 +458,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
433
458
|
],
|
|
434
459
|
xai: [
|
|
435
460
|
"grok-4.5",
|
|
436
|
-
"grok-4.20-0309-reasoning",
|
|
437
|
-
"grok-4.20-0309-non-reasoning",
|
|
438
461
|
"grok-4.3",
|
|
439
462
|
"grok-build-0.1",
|
|
440
|
-
"grok-code-fast-1",
|
|
441
|
-
"grok-3-fast",
|
|
442
|
-
"grok-3",
|
|
443
463
|
],
|
|
444
464
|
xiaomi: ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro", "mimo-v2-omni", "mimo-v2-flash"],
|
|
445
465
|
"xiaomi-token-plan-ams": ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro"],
|
|
@@ -450,7 +470,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
450
470
|
"nvidia/nemotron-3-super-120b-a12b",
|
|
451
471
|
"moonshotai/kimi-k2.6",
|
|
452
472
|
"z-ai/glm-5.2",
|
|
453
|
-
"
|
|
473
|
+
"minimaxai/minimax-m3",
|
|
474
|
+
"mistralai/mistral-large-3-675b-instruct-2512",
|
|
454
475
|
"openai/gpt-oss-120b",
|
|
455
476
|
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
|
|
456
477
|
"nvidia/nemotron-3-nano-30b-a3b",
|
|
@@ -523,12 +544,91 @@ function createEmptyUsage(): UsageStats {
|
|
|
523
544
|
output: 0,
|
|
524
545
|
cacheRead: 0,
|
|
525
546
|
cacheWrite: 0,
|
|
547
|
+
totalTokens: 0,
|
|
526
548
|
cost: 0,
|
|
549
|
+
costInput: 0,
|
|
550
|
+
costOutput: 0,
|
|
551
|
+
costCacheRead: 0,
|
|
552
|
+
costCacheWrite: 0,
|
|
527
553
|
turns: 0,
|
|
528
554
|
contextTokens: 0,
|
|
529
555
|
};
|
|
530
556
|
}
|
|
531
557
|
|
|
558
|
+
function cloneUsageStats(stats: UsageStats): UsageStats {
|
|
559
|
+
return { ...stats };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function mergeUsageStats(target: UsageStats, source: UsageStats | undefined): UsageStats {
|
|
563
|
+
if (!source) return target;
|
|
564
|
+
target.input += source.input;
|
|
565
|
+
target.output += source.output;
|
|
566
|
+
target.cacheRead += source.cacheRead;
|
|
567
|
+
target.cacheWrite += source.cacheWrite;
|
|
568
|
+
if (source.cacheWrite1h !== undefined) {
|
|
569
|
+
target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
|
|
570
|
+
}
|
|
571
|
+
if (source.reasoning !== undefined) {
|
|
572
|
+
target.reasoning = (target.reasoning ?? 0) + source.reasoning;
|
|
573
|
+
}
|
|
574
|
+
target.totalTokens += source.totalTokens;
|
|
575
|
+
target.cost += source.cost;
|
|
576
|
+
target.costInput += source.costInput;
|
|
577
|
+
target.costOutput += source.costOutput;
|
|
578
|
+
target.costCacheRead += source.costCacheRead;
|
|
579
|
+
target.costCacheWrite += source.costCacheWrite;
|
|
580
|
+
target.turns += source.turns;
|
|
581
|
+
target.contextTokens = source.contextTokens || target.contextTokens;
|
|
582
|
+
return target;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function usageStatsToToolUsage(stats: UsageStats): Usage {
|
|
586
|
+
return {
|
|
587
|
+
input: stats.input,
|
|
588
|
+
output: stats.output,
|
|
589
|
+
cacheRead: stats.cacheRead,
|
|
590
|
+
cacheWrite: stats.cacheWrite,
|
|
591
|
+
...(stats.cacheWrite1h !== undefined ? { cacheWrite1h: stats.cacheWrite1h } : {}),
|
|
592
|
+
...(stats.reasoning !== undefined ? { reasoning: stats.reasoning } : {}),
|
|
593
|
+
totalTokens: stats.totalTokens,
|
|
594
|
+
cost: {
|
|
595
|
+
input: stats.costInput,
|
|
596
|
+
output: stats.costOutput,
|
|
597
|
+
cacheRead: stats.costCacheRead,
|
|
598
|
+
cacheWrite: stats.costCacheWrite,
|
|
599
|
+
total: stats.cost,
|
|
600
|
+
},
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function addUsageStats(
|
|
605
|
+
target: UsageStats,
|
|
606
|
+
source: Usage | undefined,
|
|
607
|
+
options: { countTurn?: boolean; updateContextTokens?: boolean } = {},
|
|
608
|
+
): void {
|
|
609
|
+
if (!source) return;
|
|
610
|
+
if (options.countTurn) target.turns += 1;
|
|
611
|
+
target.input += source.input || 0;
|
|
612
|
+
target.output += source.output || 0;
|
|
613
|
+
target.cacheRead += source.cacheRead || 0;
|
|
614
|
+
target.cacheWrite += source.cacheWrite || 0;
|
|
615
|
+
if (source.cacheWrite1h !== undefined) {
|
|
616
|
+
target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
|
|
617
|
+
}
|
|
618
|
+
if (source.reasoning !== undefined) {
|
|
619
|
+
target.reasoning = (target.reasoning ?? 0) + source.reasoning;
|
|
620
|
+
}
|
|
621
|
+
target.totalTokens += source.totalTokens || 0;
|
|
622
|
+
target.cost += source.cost?.total || 0;
|
|
623
|
+
target.costInput += source.cost?.input || 0;
|
|
624
|
+
target.costOutput += source.cost?.output || 0;
|
|
625
|
+
target.costCacheRead += source.cost?.cacheRead || 0;
|
|
626
|
+
target.costCacheWrite += source.cost?.cacheWrite || 0;
|
|
627
|
+
if (options.updateContextTokens) {
|
|
628
|
+
target.contextTokens = source.totalTokens || target.contextTokens;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
532
632
|
function getContrarianConfigPath(): string {
|
|
533
633
|
return path.join(getAgentDir(), "extensions", CONTRARIAN_CONFIG_FILE);
|
|
534
634
|
}
|
|
@@ -617,6 +717,7 @@ function formatUsage(stats: UsageStats): string {
|
|
|
617
717
|
if (stats.output) parts.push(`↓${formatTokens(stats.output)}`);
|
|
618
718
|
if (stats.cacheRead) parts.push(`R${formatTokens(stats.cacheRead)}`);
|
|
619
719
|
if (stats.cacheWrite) parts.push(`W${formatTokens(stats.cacheWrite)}`);
|
|
720
|
+
if (stats.reasoning) parts.push(`T${formatTokens(stats.reasoning)}`);
|
|
620
721
|
if (stats.cost) parts.push(`$${stats.cost.toFixed(4)}`);
|
|
621
722
|
if (stats.contextTokens) parts.push(`ctx:${formatTokens(stats.contextTokens)}`);
|
|
622
723
|
return parts.join(" ");
|
|
@@ -1020,6 +1121,7 @@ async function runContrarian(
|
|
|
1020
1121
|
signal: AbortSignal | undefined,
|
|
1021
1122
|
onUpdate: ((result: { content: Array<{ type: "text"; text: string }>; details: ContrarianDetails }) => void) | undefined,
|
|
1022
1123
|
defaultCwd: string,
|
|
1124
|
+
spawnImpl: typeof spawn = spawn,
|
|
1023
1125
|
): Promise<{ ok: true; output: string; details: ContrarianDetails } | { ok: false; error: string; details: ContrarianDetails }> {
|
|
1024
1126
|
const cwd = params.cwd ?? defaultCwd;
|
|
1025
1127
|
const includeBash = params.includeBash ?? false;
|
|
@@ -1071,7 +1173,7 @@ async function runContrarian(
|
|
|
1071
1173
|
let wasAborted = false;
|
|
1072
1174
|
|
|
1073
1175
|
const exitCode = await new Promise<number>((resolve) => {
|
|
1074
|
-
const proc =
|
|
1176
|
+
const proc = spawnImpl(invocation.command, invocation.args, {
|
|
1075
1177
|
cwd,
|
|
1076
1178
|
shell: false,
|
|
1077
1179
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1109,18 +1211,16 @@ async function runContrarian(
|
|
|
1109
1211
|
const errorMessageField = event.message.errorMessage;
|
|
1110
1212
|
lastErrorMessage = typeof errorMessageField === "string" ? errorMessageField : undefined;
|
|
1111
1213
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
usage.output += messageUsage.output || 0;
|
|
1117
|
-
usage.cacheRead += messageUsage.cacheRead || 0;
|
|
1118
|
-
usage.cacheWrite += messageUsage.cacheWrite || 0;
|
|
1119
|
-
usage.cost += messageUsage.cost?.total || 0;
|
|
1120
|
-
usage.contextTokens = messageUsage.totalTokens || usage.contextTokens;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1214
|
+
addUsageStats(usage, event.message.usage as Usage | undefined, {
|
|
1215
|
+
countTurn: true,
|
|
1216
|
+
updateContextTokens: true,
|
|
1217
|
+
});
|
|
1123
1218
|
emit();
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
if (event.type === "compaction_end") {
|
|
1223
|
+
addUsageStats(usage, event.result?.usage as Usage | undefined);
|
|
1124
1224
|
}
|
|
1125
1225
|
};
|
|
1126
1226
|
|
|
@@ -1136,26 +1236,53 @@ async function runContrarian(
|
|
|
1136
1236
|
emit();
|
|
1137
1237
|
});
|
|
1138
1238
|
|
|
1239
|
+
let closed = false;
|
|
1240
|
+
let killTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1241
|
+
let abortListener: (() => void) | undefined;
|
|
1242
|
+
|
|
1243
|
+
const childHasExited = () => proc.exitCode !== null || proc.signalCode !== null;
|
|
1244
|
+
const clearEscalation = () => {
|
|
1245
|
+
if (killTimer) {
|
|
1246
|
+
clearTimeout(killTimer);
|
|
1247
|
+
killTimer = undefined;
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
const cleanup = () => {
|
|
1251
|
+
clearEscalation();
|
|
1252
|
+
if (signal && abortListener) {
|
|
1253
|
+
signal.removeEventListener("abort", abortListener);
|
|
1254
|
+
abortListener = undefined;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
proc.on("exit", () => {
|
|
1259
|
+
clearEscalation();
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1139
1262
|
proc.on("close", (code) => {
|
|
1263
|
+
closed = true;
|
|
1264
|
+
cleanup();
|
|
1140
1265
|
if (buffer.trim()) processLine(buffer);
|
|
1141
1266
|
resolve(code ?? 0);
|
|
1142
1267
|
});
|
|
1143
1268
|
|
|
1144
1269
|
proc.on("error", (error) => {
|
|
1270
|
+
cleanup();
|
|
1145
1271
|
stderr += `${error instanceof Error ? error.message : String(error)}\n`;
|
|
1146
1272
|
resolve(1);
|
|
1147
1273
|
});
|
|
1148
1274
|
|
|
1149
1275
|
if (signal) {
|
|
1150
|
-
|
|
1276
|
+
abortListener = () => {
|
|
1151
1277
|
wasAborted = true;
|
|
1152
1278
|
proc.kill("SIGTERM");
|
|
1153
|
-
|
|
1154
|
-
|
|
1279
|
+
if (closed || childHasExited()) return;
|
|
1280
|
+
killTimer = setTimeout(() => {
|
|
1281
|
+
if (!closed && !childHasExited()) proc.kill("SIGKILL");
|
|
1155
1282
|
}, 5000);
|
|
1156
1283
|
};
|
|
1157
|
-
if (signal.aborted)
|
|
1158
|
-
else signal.addEventListener("abort",
|
|
1284
|
+
if (signal.aborted) abortListener();
|
|
1285
|
+
else signal.addEventListener("abort", abortListener, { once: true });
|
|
1159
1286
|
}
|
|
1160
1287
|
});
|
|
1161
1288
|
|
|
@@ -1164,6 +1291,7 @@ async function runContrarian(
|
|
|
1164
1291
|
details.durationMs = Date.now() - startedAt;
|
|
1165
1292
|
|
|
1166
1293
|
if (wasAborted) {
|
|
1294
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1167
1295
|
return { ok: false, error: "Contrarian was aborted.", details };
|
|
1168
1296
|
}
|
|
1169
1297
|
|
|
@@ -1173,6 +1301,7 @@ async function runContrarian(
|
|
|
1173
1301
|
// so callers can distinguish transient provider errors from a genuinely
|
|
1174
1302
|
// empty response.
|
|
1175
1303
|
if (lastStopReason === "error" || lastStopReason === "aborted") {
|
|
1304
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1176
1305
|
return {
|
|
1177
1306
|
ok: false,
|
|
1178
1307
|
error: formatContrarianModelError(lastStopReason, lastErrorMessage),
|
|
@@ -1189,6 +1318,7 @@ async function runContrarian(
|
|
|
1189
1318
|
}
|
|
1190
1319
|
|
|
1191
1320
|
if (!finalOutput.trim()) {
|
|
1321
|
+
if (details.exitCode === 0) details.exitCode = 1;
|
|
1192
1322
|
return {
|
|
1193
1323
|
ok: false,
|
|
1194
1324
|
error: details.stderr || "Contrarian finished without returning any text.",
|
|
@@ -1209,14 +1339,26 @@ function renderCollapsedText(text: string, lineLimit = COLLAPSED_LINE_LIMIT): st
|
|
|
1209
1339
|
return [...lines.slice(0, lineLimit), `... (${lines.length - lineLimit} more lines)`].join("\n");
|
|
1210
1340
|
}
|
|
1211
1341
|
|
|
1342
|
+
type ContrarianExtensionDeps = {
|
|
1343
|
+
spawnImpl?: typeof spawn;
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1212
1346
|
export const __test__ = {
|
|
1347
|
+
createContrarianExtension,
|
|
1213
1348
|
findAvailableModel,
|
|
1349
|
+
isModelAvailabilityError,
|
|
1214
1350
|
parseModelPreference,
|
|
1351
|
+
runContrarian,
|
|
1215
1352
|
resolveThinkingLevel,
|
|
1216
1353
|
selectContrarianModel,
|
|
1217
1354
|
};
|
|
1218
1355
|
|
|
1219
1356
|
export default function contrarianExtension(pi: ExtensionAPI) {
|
|
1357
|
+
createContrarianExtension(pi);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function createContrarianExtension(pi: ExtensionAPI, deps: ContrarianExtensionDeps = {}) {
|
|
1361
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
1220
1362
|
const activeRuns = new Map<string, ContrarianUiRun>();
|
|
1221
1363
|
let preferences: ContrarianPreferences = {};
|
|
1222
1364
|
|
|
@@ -1362,6 +1504,13 @@ export default function contrarianExtension(pi: ExtensionAPI) {
|
|
|
1362
1504
|
},
|
|
1363
1505
|
});
|
|
1364
1506
|
|
|
1507
|
+
pi.on("tool_result", async (event) => {
|
|
1508
|
+
if (event.toolName !== "contrarian") return undefined;
|
|
1509
|
+
const details = event.details as ContrarianDetails | undefined;
|
|
1510
|
+
if ((details?.exitCode ?? 0) === 0) return undefined;
|
|
1511
|
+
return { isError: true };
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1365
1514
|
pi.registerTool({
|
|
1366
1515
|
name: "contrarian",
|
|
1367
1516
|
label: "Contrarian",
|
|
@@ -1372,9 +1521,10 @@ export default function contrarianExtension(pi: ExtensionAPI) {
|
|
|
1372
1521
|
promptGuidelines: [
|
|
1373
1522
|
"Use contrarian when you need to stress-test a proposal, implementation plan, design, assumption, bug hypothesis, review conclusion, or product direction.",
|
|
1374
1523
|
"Ask contrarian to steelman the strongest opposing case and separate confirmed objections, plausible concerns, and unresolved unknowns.",
|
|
1375
|
-
"Do not use
|
|
1376
|
-
"The contrarian is read-only by default
|
|
1377
|
-
"
|
|
1524
|
+
"Do not use contrarian for routine low-value work; contrarian is slower than the main agent.",
|
|
1525
|
+
"The contrarian tool is read-only by default and only exposes read, grep, find, and ls unless contrarian includeBash is enabled.",
|
|
1526
|
+
"Set contrarian includeBash only when the extra bash inspection tool is genuinely useful; keep contrarian relying on read, grep, find, and ls otherwise.",
|
|
1527
|
+
"The contrarian tool requests high by default for reasoning models; contrarian defaults and explicit thinkingLevel overrides are clamped to the effective model-supported level when the model is matched.",
|
|
1378
1528
|
],
|
|
1379
1529
|
parameters: ContrarianParams,
|
|
1380
1530
|
|
|
@@ -1399,6 +1549,14 @@ export default function contrarianExtension(pi: ExtensionAPI) {
|
|
|
1399
1549
|
try {
|
|
1400
1550
|
const attempts: ContrarianSelection[] = [];
|
|
1401
1551
|
const seen = new Set<string>();
|
|
1552
|
+
const aggregateUsage = createEmptyUsage();
|
|
1553
|
+
const finalizeResult = (
|
|
1554
|
+
result: { content: Array<{ type: "text"; text: string }>; details: ContrarianDetails },
|
|
1555
|
+
): AgentToolResult<ContrarianDetails> => ({
|
|
1556
|
+
...result,
|
|
1557
|
+
details: { ...result.details, usage: cloneUsageStats(aggregateUsage) },
|
|
1558
|
+
usage: usageStatsToToolUsage(aggregateUsage),
|
|
1559
|
+
});
|
|
1402
1560
|
const pushAttempt = (candidate: ContrarianSelection | undefined) => {
|
|
1403
1561
|
if (!candidate) return;
|
|
1404
1562
|
const key = `${candidate.provider}/${candidate.modelId}`.toLowerCase();
|
|
@@ -1440,7 +1598,7 @@ export default function contrarianExtension(pi: ExtensionAPI) {
|
|
|
1440
1598
|
} else {
|
|
1441
1599
|
const selectionResult = await selectContrarianModel(ctx, configuredThinkingLevel);
|
|
1442
1600
|
if (!selectionResult.ok) {
|
|
1443
|
-
return {
|
|
1601
|
+
return finalizeResult({
|
|
1444
1602
|
content: [{ type: "text", text: selectionResult.error }],
|
|
1445
1603
|
details: {
|
|
1446
1604
|
modelRef: "",
|
|
@@ -1457,7 +1615,7 @@ export default function contrarianExtension(pi: ExtensionAPI) {
|
|
|
1457
1615
|
durationMs: 0,
|
|
1458
1616
|
cwd: params.cwd ?? ctx.cwd,
|
|
1459
1617
|
},
|
|
1460
|
-
};
|
|
1618
|
+
});
|
|
1461
1619
|
}
|
|
1462
1620
|
for (const candidate of selectionResult.ordered) pushAttempt(candidate);
|
|
1463
1621
|
}
|
|
@@ -1481,25 +1639,26 @@ export default function contrarianExtension(pi: ExtensionAPI) {
|
|
|
1481
1639
|
uiRun.selection = attempt;
|
|
1482
1640
|
updateContrarianUi(ctx, activeRuns);
|
|
1483
1641
|
|
|
1484
|
-
const result = await runContrarian(attempt, { ...params, task }, signal, handleUpdate, ctx.cwd);
|
|
1642
|
+
const result = await runContrarian(attempt, { ...params, task }, signal, handleUpdate, ctx.cwd, spawnImpl);
|
|
1643
|
+
mergeUsageStats(aggregateUsage, result.details.usage);
|
|
1485
1644
|
if (result.ok) {
|
|
1486
|
-
return {
|
|
1645
|
+
return finalizeResult({
|
|
1487
1646
|
content: [{ type: "text", text: result.output }],
|
|
1488
1647
|
details: result.details,
|
|
1489
|
-
};
|
|
1648
|
+
});
|
|
1490
1649
|
}
|
|
1491
1650
|
|
|
1492
1651
|
lastErrorResult = result;
|
|
1493
1652
|
const canFallBack = index < attempts.length - 1 && isModelAvailabilityError(result.error);
|
|
1494
1653
|
if (!canFallBack) {
|
|
1495
|
-
return {
|
|
1654
|
+
return finalizeResult({
|
|
1496
1655
|
content: [{ type: "text", text: result.error }],
|
|
1497
1656
|
details: result.details,
|
|
1498
|
-
};
|
|
1657
|
+
});
|
|
1499
1658
|
}
|
|
1500
1659
|
}
|
|
1501
1660
|
|
|
1502
|
-
return {
|
|
1661
|
+
return finalizeResult({
|
|
1503
1662
|
content: [
|
|
1504
1663
|
{ type: "text", text: lastErrorResult?.error ?? "Contrarian could not select an available model." },
|
|
1505
1664
|
],
|
|
@@ -1519,7 +1678,7 @@ export default function contrarianExtension(pi: ExtensionAPI) {
|
|
|
1519
1678
|
durationMs: 0,
|
|
1520
1679
|
cwd: params.cwd ?? ctx.cwd,
|
|
1521
1680
|
},
|
|
1522
|
-
};
|
|
1681
|
+
});
|
|
1523
1682
|
} finally {
|
|
1524
1683
|
activeRuns.delete(toolCallId);
|
|
1525
1684
|
updateContrarianUi(ctx, activeRuns);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-contrarian",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A read-only contrarian subagent extension for pi that stress-tests plans, designs, assumptions, and conclusions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -33,5 +33,9 @@
|
|
|
33
33
|
"@earendil-works/pi-coding-agent": "*",
|
|
34
34
|
"@earendil-works/pi-tui": "*",
|
|
35
35
|
"typebox": "*"
|
|
36
|
+
},
|
|
37
|
+
"type": "module",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.19.0"
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A small pi extension that prompts before session changes when the current git repo has uncommitted changes.
|
|
4
4
|
|
|
5
|
-
This is copied from the original `dirty-repo-guard.ts` example in [`earendil-works/pi
|
|
5
|
+
This is copied from the original `dirty-repo-guard.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/dirty-repo-guard.ts) and kept basically the same.
|
|
6
6
|
|
|
7
7
|
## What it checks
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-dirty-repo-guard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A pi extension that prompts before session changes when the current git repo has uncommitted changes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,5 +29,9 @@
|
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@earendil-works/pi-coding-agent": "*"
|
|
32
|
+
},
|
|
33
|
+
"type": "module",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.19.0"
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-dynamic-context-pruning",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A pi extension that dynamically prunes stale context to keep sessions lean.",
|
|
6
6
|
"keywords": [
|
|
@@ -30,5 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@earendil-works/pi-coding-agent": "*"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.19.0"
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-git-footer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A TLH-style git status add-on for pi's built-in footer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -31,5 +31,9 @@
|
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@earendil-works/pi-coding-agent": "*",
|
|
33
33
|
"@earendil-works/pi-tui": "*"
|
|
34
|
+
},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.19.0"
|
|
34
38
|
}
|
|
35
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-gnosis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A pi extension that exposes the gnosis repo-local knowledge base CLI as an agent tool.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -33,5 +33,9 @@
|
|
|
33
33
|
"@earendil-works/pi-ai": "*",
|
|
34
34
|
"@earendil-works/pi-coding-agent": "*",
|
|
35
35
|
"typebox": "*"
|
|
36
|
+
},
|
|
37
|
+
"type": "module",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.19.0"
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-illustrations-to-explain-things",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A pi skill for generating clean, absurd Xiaohei-style article illustrations, shot lists, and visual metaphors.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -27,5 +27,8 @@
|
|
|
27
27
|
"skills": [
|
|
28
28
|
"skills"
|
|
29
29
|
]
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22.19.0"
|
|
30
33
|
}
|
|
31
34
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A pi extension that expands inline bash commands in user prompts before they are sent to the agent.
|
|
4
4
|
|
|
5
|
-
This started from the original `inline-bash.ts` example in [`earendil-works/pi
|
|
5
|
+
This started from the original `inline-bash.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/inline-bash.ts), with small packaging and safety tweaks.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-inline-bash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A pi extension that expands inline bash commands in user prompts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -30,5 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@earendil-works/pi-coding-agent": "*"
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.19.0"
|
|
33
37
|
}
|
|
34
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.81.1
|