@henryqw/pi-subagent 0.1.1 → 1.1.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.
- package/README.md +23 -8
- package/extensions/subagent.ts +153 -22
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @henryqw/pi-subagent
|
|
2
2
|
|
|
3
|
-
Delegate one bounded task to one isolated Pi process. Main chooses role and
|
|
3
|
+
Delegate one bounded task to one isolated Pi process. Main chooses role and model class per task.
|
|
4
4
|
|
|
5
5
|
Based on Pi's authoritative [`examples/extensions/subagent`](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/examples/extensions/subagent): child processes use `pi --mode json -p --no-session`.
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ pi install npm:@henryqw/pi-subagent
|
|
|
12
12
|
|
|
13
13
|
## Configure roles
|
|
14
14
|
|
|
15
|
-
Use Pi's existing role Markdown format in package-owned `~/.pi/agent/config/pi-subagent/*.md`.
|
|
15
|
+
Use Pi's existing role Markdown format in package-owned `~/.pi/agent/config/pi-subagent/*.md`. Configure `fast`, `balanced`, and `frontier` model routes with `/subagent`.
|
|
16
16
|
|
|
17
17
|
```markdown
|
|
18
18
|
---
|
|
@@ -36,18 +36,20 @@ Fields:
|
|
|
36
36
|
| --- | --- | --- |
|
|
37
37
|
| `name` | yes | Role selected by Main |
|
|
38
38
|
| `description` | yes | Tells Main when to use role |
|
|
39
|
-
| `tools` |
|
|
39
|
+
| `tools` | no | Omit for Pi effective `defaultTools`; a non-empty list is an exact built-in and extension tool allowlist; use `[]` for none |
|
|
40
40
|
| `extensions` | no | Absolute/user-home paths or package sources passed to Pi `--extension` |
|
|
41
41
|
| `skills` | no | Effective Pi Skill names loaded for role |
|
|
42
42
|
| Markdown body | yes | Role system instructions |
|
|
43
43
|
|
|
44
|
+
Omitted `tools` uses Pi's effective `defaultTools` for built-ins. Tools registered by Role extensions stay active without listing their names. A non-empty `tools` list strictly allowlists both built-in and extension tools; `tools: []` sends `--no-tools`.
|
|
45
|
+
|
|
44
46
|
String lists may also use comma-separated text, matching Pi's example role files. Repository-relative extension paths are rejected: child working directory is delegated project, so relative paths could load untrusted project code. Use absolute paths, `~/...`, or explicit package sources such as `npm:...`.
|
|
45
47
|
|
|
46
48
|
Skill entries use Pi Skill names, normally Skill directory names, not filesystem paths. At delegation time, package resolves names from Main's effective Pi Skill registry and passes matching files to child. Missing or unavailable Skills produce warning and are skipped; they do not block delegation. This preserves Main's trust and Skill collision decisions.
|
|
47
49
|
|
|
48
|
-
Pi's example `agents/` directory contains sample Role files, not another runtime mechanism. This package reuses that Markdown format but ships no presets:
|
|
50
|
+
Pi's example `agents/` directory contains sample Role files, not another runtime mechanism. This package reuses that Markdown format but ships no presets: role capabilities and model routes stay explicit in user config. No nested `agents/` directory is needed because Roles remain Markdown files.
|
|
49
51
|
|
|
50
|
-
Reload Pi after adding or changing role files
|
|
52
|
+
Reload Pi after adding or changing role files or manually editing model config. `/subagent` applies changes immediately.
|
|
51
53
|
|
|
52
54
|
## Execution
|
|
53
55
|
|
|
@@ -55,12 +57,25 @@ Main calls `delegate_task` with:
|
|
|
55
57
|
|
|
56
58
|
- `role`: configured role name
|
|
57
59
|
- `task`: one bounded task
|
|
58
|
-
- `
|
|
59
|
-
|
|
60
|
+
- `modelClass`: optional `fast`, `balanced`, or `frontier`; defaults to configured `balanced`, then Main route
|
|
61
|
+
|
|
62
|
+
Run `/subagent` once per class. Select class, authenticated text model, and supported thinking level. Config lives in `~/.pi/agent/config/pi-subagent.json`:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"models": {
|
|
67
|
+
"fast": { "model": "provider/fast-model", "thinkingLevel": "off" },
|
|
68
|
+
"balanced": { "model": "provider/balanced-model", "thinkingLevel": "medium" },
|
|
69
|
+
"frontier": { "model": "provider/frontier-model", "thinkingLevel": "max" }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Explicit class routes must be configured and available. Omitted `modelClass` falls back to Main when `balanced` route is missing or stale.
|
|
60
75
|
|
|
61
76
|
Each call starts isolated child process. Ambient extensions and skills are disabled. Only role resources load. Child uses delegated working directory and normal Pi project context files, inheriting Main's project approval decision. Abort terminates child process group.
|
|
62
77
|
|
|
63
|
-
|
|
78
|
+
Configured model and thinking level must exist in Main model registry. Invalid role config or explicit model class fails before child starts.
|
|
64
79
|
|
|
65
80
|
Main-visible streaming updates, final output, and errors are capped at 50 KiB of UTF-8 text. Error collection stays bounded while child runs; malformed JSON events above 1 MiB fail delegation. Truncated output ends with exact omitted-byte count.
|
|
66
81
|
|
package/extensions/subagent.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { basename, isAbsolute, join } from "node:path";
|
|
5
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
6
6
|
import { getSupportedThinkingLevels, StringEnum } from "@earendil-works/pi-ai";
|
|
7
7
|
import { type ExtensionAPI, type ExtensionContext, getAgentDir, parseFrontmatter, type Theme } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { type Component, truncateToWidth, type TUI, visibleWidth } from "@earendil-works/pi-tui";
|
|
9
9
|
import { Type } from "typebox";
|
|
10
10
|
|
|
11
11
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
12
|
+
const MODEL_CLASSES = ["fast", "balanced", "frontier"] as const;
|
|
13
|
+
const configPath = () => join(getAgentDir(), "config", "pi-subagent.json");
|
|
12
14
|
const MAX_OUTPUT_BYTES = 50 * 1024;
|
|
13
15
|
const MAX_JSON_EVENT_BYTES = 1024 * 1024;
|
|
14
16
|
const WIDGET_KEY = "subagent-status";
|
|
@@ -18,10 +20,13 @@ const MAX_WIDGET_ROWS = 8;
|
|
|
18
20
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
19
21
|
|
|
20
22
|
type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
23
|
+
type ModelClass = (typeof MODEL_CLASSES)[number];
|
|
24
|
+
type ConfiguredModel = { model: string; thinkingLevel: ThinkingLevel };
|
|
25
|
+
type Config = { models: Partial<Record<ModelClass, ConfiguredModel>> };
|
|
21
26
|
type Role = {
|
|
22
27
|
name: string;
|
|
23
28
|
description: string;
|
|
24
|
-
tools
|
|
29
|
+
tools?: string[];
|
|
25
30
|
extensions: string[];
|
|
26
31
|
skills: string[];
|
|
27
32
|
systemPrompt: string;
|
|
@@ -44,6 +49,62 @@ type WidgetItem = {
|
|
|
44
49
|
removeAt?: number;
|
|
45
50
|
};
|
|
46
51
|
|
|
52
|
+
const defaultConfig = (): Config => ({ models: {} });
|
|
53
|
+
const isModelClass = (value: unknown): value is ModelClass =>
|
|
54
|
+
typeof value === "string" && MODEL_CLASSES.includes(value as ModelClass);
|
|
55
|
+
const isThinkingLevel = (value: unknown): value is ThinkingLevel =>
|
|
56
|
+
typeof value === "string" && THINKING_LEVELS.includes(value as ThinkingLevel);
|
|
57
|
+
const isModelReference = (value: unknown): value is string => {
|
|
58
|
+
if (typeof value !== "string" || value !== value.trim() || value.includes("\0")) return false;
|
|
59
|
+
const separator = value.indexOf("/");
|
|
60
|
+
return separator > 0 && separator < value.length - 1;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function readConfig(): { value: Config; invalid: boolean } {
|
|
64
|
+
try {
|
|
65
|
+
const value = JSON.parse(readFileSync(configPath(), "utf8")) as unknown;
|
|
66
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { value: defaultConfig(), invalid: true };
|
|
67
|
+
const record = value as Record<string, unknown>;
|
|
68
|
+
if (Object.keys(record).some((key) => key !== "models")) return { value: defaultConfig(), invalid: true };
|
|
69
|
+
if (record.models === undefined) return { value: defaultConfig(), invalid: false };
|
|
70
|
+
if (!record.models || typeof record.models !== "object" || Array.isArray(record.models)) {
|
|
71
|
+
return { value: defaultConfig(), invalid: true };
|
|
72
|
+
}
|
|
73
|
+
const modelRecord = record.models as Record<string, unknown>;
|
|
74
|
+
const models: Config["models"] = {};
|
|
75
|
+
let invalid = Object.keys(modelRecord).some((key) => !isModelClass(key));
|
|
76
|
+
for (const modelClass of MODEL_CLASSES) {
|
|
77
|
+
if (!Object.hasOwn(modelRecord, modelClass)) continue;
|
|
78
|
+
const candidate = modelRecord[modelClass];
|
|
79
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
80
|
+
invalid = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const route = candidate as Record<string, unknown>;
|
|
84
|
+
if (isModelReference(route.model) && isThinkingLevel(route.thinkingLevel)) {
|
|
85
|
+
models[modelClass] = { model: route.model, thinkingLevel: route.thinkingLevel };
|
|
86
|
+
} else invalid = true;
|
|
87
|
+
}
|
|
88
|
+
return { value: { models }, invalid };
|
|
89
|
+
} catch (error: unknown) {
|
|
90
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
91
|
+
return { value: defaultConfig(), invalid: false };
|
|
92
|
+
}
|
|
93
|
+
return { value: defaultConfig(), invalid: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function writeConfig(config: Config): void {
|
|
98
|
+
const file = configPath();
|
|
99
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
100
|
+
writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const modelReference = (model: { provider: string; id: string }): string => `${model.provider}/${model.id}`;
|
|
104
|
+
const availableTextModels = (ctx: ExtensionContext) => ctx.modelRegistry
|
|
105
|
+
.getAvailable()
|
|
106
|
+
.filter((model) => model.input.includes("text"));
|
|
107
|
+
|
|
47
108
|
const cleanText = (value: unknown, field: string, file: string): string => {
|
|
48
109
|
if (typeof value !== "string" || !value.trim() || value.includes("\0")) {
|
|
49
110
|
throw new Error(`${file}: ${field} must be non-empty text.`);
|
|
@@ -100,7 +161,7 @@ export function loadRoles(agentDir = getAgentDir()): Role[] {
|
|
|
100
161
|
return {
|
|
101
162
|
name: cleanText(frontmatter.name, "name", file),
|
|
102
163
|
description: cleanText(frontmatter.description, "description", file),
|
|
103
|
-
tools: stringList(frontmatter.tools, "tools", file, true),
|
|
164
|
+
tools: frontmatter.tools === undefined ? undefined : stringList(frontmatter.tools, "tools", file, true),
|
|
104
165
|
extensions: extensionList(frontmatter.extensions, file),
|
|
105
166
|
skills: stringList(frontmatter.skills, "skills", file),
|
|
106
167
|
systemPrompt: cleanText(parsed.body, "system prompt", file),
|
|
@@ -404,9 +465,8 @@ async function runPi(
|
|
|
404
465
|
const Parameters = Type.Object({
|
|
405
466
|
role: Type.String({ description: "Configured Subagent role name" }),
|
|
406
467
|
task: Type.String({ description: "One bounded task with needed context and expected result" }),
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
description: "Thinking level; defaults to Main thinking level",
|
|
468
|
+
modelClass: Type.Optional(StringEnum(MODEL_CLASSES, {
|
|
469
|
+
description: "Classify task complexity: fast for narrow lookups or mechanical edits; balanced for normal bounded work; frontier for ambiguous, cross-cutting, or high-risk reasoning. Defaults to configured balanced, then Main route.",
|
|
410
470
|
})),
|
|
411
471
|
});
|
|
412
472
|
|
|
@@ -439,6 +499,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
439
499
|
let widgetTimer: ReturnType<typeof setInterval> | undefined;
|
|
440
500
|
let spinnerIndex = 0;
|
|
441
501
|
let activeTui: TUI | undefined;
|
|
502
|
+
let config = defaultConfig();
|
|
442
503
|
|
|
443
504
|
const stopWidgetTimer = () => {
|
|
444
505
|
if (!widgetTimer) return;
|
|
@@ -512,7 +573,16 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
512
573
|
requestWidgetRender();
|
|
513
574
|
};
|
|
514
575
|
|
|
515
|
-
|
|
576
|
+
const refreshConfig = (ctx: ExtensionContext) => {
|
|
577
|
+
const loaded = readConfig();
|
|
578
|
+
config = loaded.value;
|
|
579
|
+
if (loaded.invalid) ctx.ui.notify("Invalid pi-subagent config values were ignored.", "warning");
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
pi.on("session_start", (_event, ctx) => {
|
|
583
|
+
ensureWidget(ctx);
|
|
584
|
+
refreshConfig(ctx);
|
|
585
|
+
});
|
|
516
586
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
517
587
|
stopWidgetTimer();
|
|
518
588
|
widgetItems.clear();
|
|
@@ -521,10 +591,52 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
521
591
|
if (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
522
592
|
});
|
|
523
593
|
|
|
594
|
+
pi.registerCommand("subagent", {
|
|
595
|
+
description: "configure fast, balanced, and frontier Subagent model routes",
|
|
596
|
+
handler: async (args, ctx) => {
|
|
597
|
+
if (args.trim()) {
|
|
598
|
+
ctx.ui.notify("Usage: /subagent", "warning");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
refreshConfig(ctx);
|
|
602
|
+
const modelClass = await ctx.ui.select("Subagent model class", [...MODEL_CLASSES]);
|
|
603
|
+
if (!isModelClass(modelClass)) return;
|
|
604
|
+
const models = availableTextModels(ctx);
|
|
605
|
+
if (!models.length) {
|
|
606
|
+
ctx.ui.notify("No authenticated text models are available.", "warning");
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const saved = config.models[modelClass];
|
|
610
|
+
const references = models.map(modelReference).sort();
|
|
611
|
+
const selected = await ctx.ui.select(
|
|
612
|
+
`${modelClass} Subagent model · saved: ${saved?.model ?? "none"}`,
|
|
613
|
+
references,
|
|
614
|
+
);
|
|
615
|
+
const selectedModel = models.find((model) => modelReference(model) === selected);
|
|
616
|
+
if (!selectedModel) return;
|
|
617
|
+
const levels = getSupportedThinkingLevels(selectedModel);
|
|
618
|
+
const thinkingLevel = await ctx.ui.select(
|
|
619
|
+
`${modelClass} Subagent thinking level · saved: ${saved?.thinkingLevel ?? "none"}`,
|
|
620
|
+
levels,
|
|
621
|
+
);
|
|
622
|
+
if (!isThinkingLevel(thinkingLevel) || !levels.some((level) => level === thinkingLevel)) return;
|
|
623
|
+
try {
|
|
624
|
+
const latest = readConfig();
|
|
625
|
+
const route = { model: modelReference(selectedModel), thinkingLevel };
|
|
626
|
+
const next = { ...latest.value, models: { ...latest.value.models, [modelClass]: route } };
|
|
627
|
+
writeConfig(next);
|
|
628
|
+
config = next;
|
|
629
|
+
ctx.ui.notify(`${modelClass} Subagent set to ${route.model} with thinking ${thinkingLevel}.`, "info");
|
|
630
|
+
} catch {
|
|
631
|
+
ctx.ui.notify("Couldn't save pi-subagent model config.", "warning");
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
524
636
|
pi.registerTool({
|
|
525
637
|
name: "delegate_task",
|
|
526
638
|
label: "Subagent",
|
|
527
|
-
description: `Delegate one bounded task to one isolated Pi Subagent. Roles: ${roleSummary()}.
|
|
639
|
+
description: `Delegate one bounded task to one isolated Pi Subagent. Roles: ${roleSummary()}. Choose fast for narrow work, balanced for normal work, or frontier for ambiguous and high-risk work. Request concise conclusions and file/line references; split broad scouting work.`,
|
|
528
640
|
parameters: Parameters,
|
|
529
641
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
530
642
|
const task = cleanText(params.task, "task", "delegate_task");
|
|
@@ -534,16 +646,33 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
534
646
|
throw new Error(`Unknown Subagent role: ${params.role}. Available roles: ${roles.map(({ name }) => name).join(", ") || "none"}.`);
|
|
535
647
|
}
|
|
536
648
|
|
|
537
|
-
|
|
538
|
-
if (!
|
|
539
|
-
|
|
540
|
-
(
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
649
|
+
refreshConfig(ctx);
|
|
650
|
+
if (!ctx.model) throw new Error("delegate_task requires an active Pi model.");
|
|
651
|
+
if (params.modelClass !== undefined && !isModelClass(params.modelClass)) {
|
|
652
|
+
throw new Error("delegate_task modelClass must be fast, balanced, or frontier.");
|
|
653
|
+
}
|
|
654
|
+
const modelClass = params.modelClass ?? "balanced";
|
|
655
|
+
const configuredModel = config.models[modelClass];
|
|
656
|
+
if (params.modelClass && !configuredModel) {
|
|
657
|
+
throw new Error(`No ${modelClass} Subagent model configured; run /subagent.`);
|
|
658
|
+
}
|
|
659
|
+
const selectedModelInfo = configuredModel && availableTextModels(ctx)
|
|
660
|
+
.find((model) => modelReference(model) === configuredModel.model);
|
|
661
|
+
if (params.modelClass && configuredModel && !selectedModelInfo) {
|
|
662
|
+
throw new Error(`Configured ${modelClass} Subagent model is unavailable; run /subagent.`);
|
|
663
|
+
}
|
|
664
|
+
const thinkingAvailable = Boolean(configuredModel && selectedModelInfo
|
|
665
|
+
&& getSupportedThinkingLevels(selectedModelInfo).some((level) => level === configuredModel.thinkingLevel));
|
|
666
|
+
if (params.modelClass && configuredModel && selectedModelInfo && !thinkingAvailable) {
|
|
667
|
+
throw new Error(`Configured ${modelClass} Subagent thinking level is unavailable; run /subagent.`);
|
|
668
|
+
}
|
|
669
|
+
const configuredRoute = thinkingAvailable ? configuredModel : undefined;
|
|
670
|
+
const modelReferenceValue = configuredRoute?.model ?? modelReference(ctx.model);
|
|
671
|
+
const model = configuredRoute ? selectedModelInfo : ctx.model;
|
|
672
|
+
if (!model) throw new Error("Subagent model metadata is unavailable.");
|
|
673
|
+
const thinkingLevel = configuredRoute?.thinkingLevel ?? ctx.thinkingLevel;
|
|
545
674
|
if (thinkingLevel && !getSupportedThinkingLevels(model).includes(thinkingLevel)) {
|
|
546
|
-
throw new Error(`Subagent thinking level ${thinkingLevel} is unavailable for ${
|
|
675
|
+
throw new Error(`Subagent thinking level ${thinkingLevel} is unavailable for ${modelReferenceValue}.`);
|
|
547
676
|
}
|
|
548
677
|
|
|
549
678
|
const resolvedSkills = resolveSkillPaths(pi, role.skills);
|
|
@@ -562,15 +691,17 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
562
691
|
const args = ["--mode", "json", "-p", "--no-session", "--no-extensions", "--no-skills"];
|
|
563
692
|
for (const extension of role.extensions) args.push("--extension", extension);
|
|
564
693
|
for (const skill of resolvedSkills.paths) args.push("--skill", skill);
|
|
565
|
-
if (role.tools
|
|
566
|
-
|
|
567
|
-
|
|
694
|
+
if (role.tools !== undefined) {
|
|
695
|
+
if (role.tools.length) args.push("--tools", role.tools.join(","));
|
|
696
|
+
else args.push("--no-tools");
|
|
697
|
+
}
|
|
698
|
+
args.push("--model", modelReferenceValue);
|
|
568
699
|
if (thinkingLevel) args.push("--thinking", thinkingLevel);
|
|
569
700
|
args.push(ctx.isProjectTrusted() ? "--approve" : "--no-approve");
|
|
570
701
|
args.push("--append-system-prompt", promptPath, `Task: ${task}`);
|
|
571
702
|
|
|
572
703
|
startWidgetItem(toolCallId, role.name, model.id, thinkingLevel, task, ctx);
|
|
573
|
-
const details = { role: role.name, model:
|
|
704
|
+
const details = { role: role.name, model: modelReferenceValue, thinkingLevel };
|
|
574
705
|
const result = await runPi(
|
|
575
706
|
args,
|
|
576
707
|
ctx.cwd,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Delegate one task to an isolated Pi role with explicit extensions and skills.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"pack:check": "npm pack --dry-run"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@earendil-works/pi-ai": "^0.84.
|
|
28
|
-
"@earendil-works/pi-coding-agent": "^0.84.
|
|
29
|
-
"@earendil-works/pi-tui": "^0.84.
|
|
27
|
+
"@earendil-works/pi-ai": "^0.84.2",
|
|
28
|
+
"@earendil-works/pi-coding-agent": "^0.84.2",
|
|
29
|
+
"@earendil-works/pi-tui": "^0.84.2",
|
|
30
30
|
"typebox": "^1.3.7"
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|