@henryqw/pi-subagent 24.0.1 → 25.0.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/CONTEXT.md +1 -1
- package/README.md +3 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -8
- package/dist/mcp-role.d.ts +7 -5
- package/dist/mcp-role.js +56 -8
- package/docs/orchestration.md +1 -1
- package/extensions/role-mcp.ts +6 -10
- package/package.json +7 -7
package/CONTEXT.md
CHANGED
|
@@ -62,7 +62,7 @@ Own Role-configured Pi delegation through one `delegate_task` surface:
|
|
|
62
62
|
### Resource and launch policy
|
|
63
63
|
|
|
64
64
|
- Ambient child extensions and Skills stay disabled. Only Role/caller resources plus required internal adapters load.
|
|
65
|
-
- Every Role requires `tools`, `extensions`, and `skills` arrays. Omitted or empty `mcps` denies MCP access. Direct `pi-mcp-adapter` loading is rejected because it bypasses the allowlist.
|
|
65
|
+
- Every Role requires `tools`, `extensions`, and `skills` arrays. `extensions` may name Pi built-in extensions as `builtin:<name>`. `mcps` names servers from the global `~/.pi/agent/mcp.json`; their tools load in the child with direct exposure. Omitted or empty `mcps` denies MCP access. Direct `pi-mcp-adapter` loading is rejected because it bypasses the allowlist.
|
|
66
66
|
- Selected extensions are trusted executable bundles, not a sandbox. All tools and lifecycle behavior they register load together.
|
|
67
67
|
- Role Skill names resolve through Main's effective Pi registry. Missing Roles, Skills, tools, MCP servers, routes, models, or thinking levels fail before the first productive turn.
|
|
68
68
|
- Route precedence is call model class, then Role default, then registered Model Task assignment/default. A direct model replaces only the route model and must support its thinking level.
|
package/README.md
CHANGED
|
@@ -13,18 +13,11 @@ pi install npm:@henryqw/pi-subagent
|
|
|
13
13
|
|
|
14
14
|
Run `/task-models` and configure the `fast`, `balanced`, `frontier`, and `fav` routes you use.
|
|
15
15
|
|
|
16
|
-
Install `pi-mcp-adapter` only when a Role declares an MCP server allowlist:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
pi install npm:pi-mcp-adapter
|
|
20
|
-
```
|
|
21
|
-
|
|
22
16
|
## Works with
|
|
23
17
|
|
|
24
18
|
| Package | Relationship | Purpose |
|
|
25
19
|
| --- | --- | --- |
|
|
26
20
|
| [`@henryqw/pi-task-models`](https://pi.henry.wang/extensions/pi-task-models) | Required | Provides the configured model routes for delegated Roles. |
|
|
27
|
-
| [`pi-mcp-adapter`](https://github.com/nicobailon/pi-mcp-adapter) | Required | Loads the MCP servers named in a Role's `mcps` allowlist, only for Roles that declare one. |
|
|
28
21
|
|
|
29
22
|
## Use
|
|
30
23
|
|
|
@@ -181,14 +174,14 @@ Role Markdown lives in `~/.pi/agent/config/pi-subagent/` and requires frontmatte
|
|
|
181
174
|
| `name`, `description` | Required non-empty text without terminal control characters |
|
|
182
175
|
| `modelClass` | Optional `fast`, `balanced`, `frontier`, or `fav` default |
|
|
183
176
|
| `tools` | Required array of base tool names; `[]` selects none |
|
|
184
|
-
| `extensions` | Required array of trusted absolute paths
|
|
177
|
+
| `extensions` | Required array of trusted absolute paths, supported package sources, or Pi built-in extensions such as `builtin:codemode` |
|
|
185
178
|
| `skills` | Required array of effective Pi Skill names |
|
|
186
|
-
| `mcps` | Optional exact
|
|
179
|
+
| `mcps` | Optional exact server names from `~/.pi/agent/mcp.json` in Pi's native `mcpServers` format; omitted or `[]` denies MCP access |
|
|
187
180
|
| body | Required system instructions |
|
|
188
181
|
|
|
189
182
|
Roles describe responsibility and capabilities. They do not choose isolation; each request does. A same-named user Role overrides a built-in Role. The package ships `implementer`, `reviewer`, and `scout`.
|
|
190
183
|
|
|
191
|
-
Children
|
|
184
|
+
Children run with `--no-extensions`, so a Role must list the extension that registers its route model's provider, and only declared resources plus required internal policy adapters load. `builtin:<name>` entries load Pi built-in extensions (`builtin:codemode`, `builtin:tool-search`, `builtin:llama.cpp`) explicitly; `builtin:mcp` is rejected because it would connect every configured server and bypass `mcps`. The servers named in `mcps` come from the global `~/.pi/agent/mcp.json` only; their tools load in the child with direct exposure and are declared to the model as `mcp__<server>__<tool>` once the server connects. Missing Skills, tools, MCP servers, Roles, or routes fail before productive work starts. Main-only delegation and recovery tools plus `ask_question` are excluded from children.
|
|
192
185
|
|
|
193
186
|
## API
|
|
194
187
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type AvailableModel, type ModelTask, type ProfileName, type ResolvedTaskRoute, type ThinkingLevel } from "@henryqw/pi-task-models";
|
|
3
3
|
export { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "./display-text.ts";
|
|
4
|
-
export {
|
|
4
|
+
export { loadRoleMcpConfig, parseRoleMcpAllowlist } from "./mcp-role.ts";
|
|
5
5
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, type EphemeralSubagentActivityEvent, type EphemeralSubagentErrorCode, type EphemeralSubagentExecutionBudget, type EphemeralSubagentExecutor, type EphemeralSubagentExecutorOptions, type EphemeralSubagentResult, type EphemeralSubagentRunInput, type EphemeralSubagentTimeout, } from "./ephemeral.ts";
|
|
6
6
|
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, type WorktreeDirtyInspection, type WorktreeInfo, type WorktreePayload, } from "./worktree.ts";
|
|
7
7
|
export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, type PreparedReviewEvidence, type PrepareExactReviewEvidenceInput, } from "./review-evidence.ts";
|
package/dist/index.js
CHANGED
|
@@ -4,10 +4,10 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { DefaultPackageManager, getAgentDir, parseFrontmatter, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { extensionConfigDir } from "@henryqw/pi-config-store";
|
|
6
6
|
import { hasDisplayControlCharacters } from "./display-text.js";
|
|
7
|
-
import {
|
|
7
|
+
import { loadRoleMcpConfig, parseRoleMcpAllowlist } from "./mcp-role.js";
|
|
8
8
|
import { loadTaskModelsConfig, modelReference, orderedProfileRoutes, PROFILE_NAMES, resolveConfiguredTaskRoute, resolveTaskModelRoute, } from "@henryqw/pi-task-models";
|
|
9
9
|
export { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "./display-text.js";
|
|
10
|
-
export {
|
|
10
|
+
export { loadRoleMcpConfig, parseRoleMcpAllowlist } from "./mcp-role.js";
|
|
11
11
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, } from "./ephemeral.js";
|
|
12
12
|
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, } from "./worktree.js";
|
|
13
13
|
export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, } from "./review-evidence.js";
|
|
@@ -66,12 +66,25 @@ const stringList = (value, field, source) => {
|
|
|
66
66
|
}
|
|
67
67
|
return value.map((item) => item.trim());
|
|
68
68
|
};
|
|
69
|
+
// Pi 0.99 built-in extensions (`builtin:<name>`, see Pi's settings docs); Pi exports no list of them.
|
|
70
|
+
// `builtin:mcp` is excluded: it connects every server in mcp.json and would bypass the Role `mcps` allowlist.
|
|
71
|
+
const BUILTIN_EXTENSION_NAMES = ["codemode", "tool-search", "llama.cpp"];
|
|
72
|
+
const BUILTIN_EXTENSION_PREFIX = "builtin:";
|
|
69
73
|
function validateExtension(extension, source) {
|
|
70
74
|
const value = cleanText(extension, "extension", source);
|
|
75
|
+
if (value.startsWith(BUILTIN_EXTENSION_PREFIX)) {
|
|
76
|
+
if (value === `${BUILTIN_EXTENSION_PREFIX}mcp`) {
|
|
77
|
+
throw new Error(`${source} must select MCP servers with mcps instead of loading builtin:mcp directly.`);
|
|
78
|
+
}
|
|
79
|
+
if (!BUILTIN_EXTENSION_NAMES.includes(value.slice(BUILTIN_EXTENSION_PREFIX.length))) {
|
|
80
|
+
throw new Error(`${source}: unknown built-in extension ${value}; use ${BUILTIN_EXTENSION_NAMES.map((name) => `${BUILTIN_EXTENSION_PREFIX}${name}`).join(", ")}.`);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
71
84
|
const packageSource = /^(?:npm|git|github|https?|ssh):/.test(value);
|
|
72
85
|
const userPath = isAbsolute(value) || value.startsWith("~/") || value.startsWith("~\\") || value.startsWith("file://");
|
|
73
86
|
if (!packageSource && !userPath) {
|
|
74
|
-
throw new Error(`${source}: extensions entries must be absolute paths
|
|
87
|
+
throw new Error(`${source}: extensions entries must be absolute paths, package sources, or builtin:<name>.`);
|
|
75
88
|
}
|
|
76
89
|
return value;
|
|
77
90
|
}
|
|
@@ -231,7 +244,9 @@ export async function resolveRolePackageResources(role, ctx) {
|
|
|
231
244
|
const resolved = await packageManager(ctx).resolveExtensionSources([...sources]);
|
|
232
245
|
const resourceGroups = [resolved.extensions, resolved.skills, resolved.prompts, resolved.themes]
|
|
233
246
|
.map((resources) => resources.filter((resource) => resource.enabled));
|
|
234
|
-
|
|
247
|
+
// Built-in extensions resolve to their `builtin:<name>` path with the bare source `builtin`.
|
|
248
|
+
const resolvedSources = new Set(resourceGroups.flat()
|
|
249
|
+
.map((resource) => resource.metadata.source === "builtin" ? resource.path : resource.metadata.source));
|
|
235
250
|
const missing = sources.filter((source) => !resolvedSources.has(source));
|
|
236
251
|
if (missing.length)
|
|
237
252
|
throw new Error(`Role extension sources resolved no resources: ${missing.join(", ")}.`);
|
|
@@ -317,6 +332,9 @@ export function prepareRoleLaunch(pi, ctx, input) {
|
|
|
317
332
|
? prepareRoleLaunchFromSkills(ctx, input, resolveRoleSkills(pi, input.role))
|
|
318
333
|
: prepareResolvedRoleLaunch(pi, ctx, input);
|
|
319
334
|
assertNoMissingRoleSkills(input.role, prepared);
|
|
335
|
+
// Validate the mcp.json the child reads: its environment inherits Main's, with launch overrides on top.
|
|
336
|
+
if (input.role.mcps?.length)
|
|
337
|
+
loadRoleMcpConfig(prepared.env.PI_CODING_AGENT_DIR ?? getAgentDir(), input.role.mcps);
|
|
320
338
|
return { ...prepared, role };
|
|
321
339
|
}
|
|
322
340
|
/** Resolve and prepare a configured Role with its package-owned resources. */
|
|
@@ -328,10 +346,8 @@ export async function resolveConfiguredRoleLaunch(pi, ctx, input) {
|
|
|
328
346
|
if (matches.length !== 1)
|
|
329
347
|
throw new Error(`Required configured Role ${roleName} is missing or ambiguous.`);
|
|
330
348
|
const role = matches[0];
|
|
331
|
-
if (role.mcps?.length)
|
|
332
|
-
|
|
333
|
-
selectRoleMcpConfig(loadMcpConfig(join(getAgentDir(), "mcp.json"), ctx.cwd), role.mcps);
|
|
334
|
-
}
|
|
349
|
+
if (role.mcps?.length)
|
|
350
|
+
loadRoleMcpConfig(getAgentDir(), role.mcps);
|
|
335
351
|
const resources = await resolveRolePackageResources(role, ctx);
|
|
336
352
|
const effectiveRole = { ...role, extensions: resources.extensions };
|
|
337
353
|
const namedSkills = resolveRoleSkills(pi, effectiveRole);
|
package/dist/mcp-role.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
mcpServers: Record<string, unknown>;
|
|
3
|
-
settings?: Record<string, unknown>;
|
|
4
|
-
}
|
|
1
|
+
import type { LoadedMcpConfig } from "@earendil-works/pi-coding-agent";
|
|
5
2
|
export declare function parseRoleMcpAllowlist(value: unknown): string[];
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Select the Role's allowlisted servers from the global `mcp.json` in Pi's native `mcpServers`
|
|
5
|
+
* shape; the project `.pi/mcp.json` is ignored. A `--no-extensions` child has no codemode tool, so
|
|
6
|
+
* every selected server is forced to direct exposure and codemode is never activated.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadRoleMcpConfig(agentDir: string, allowlist: readonly string[]): LoadedMcpConfig;
|
package/dist/mcp-role.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
export function parseRoleMcpAllowlist(value) {
|
|
2
4
|
if (typeof value !== "string")
|
|
3
5
|
throw new Error("The Role MCP policy flag must contain a JSON array of MCP server names.");
|
|
@@ -17,14 +19,60 @@ export function parseRoleMcpAllowlist(value) {
|
|
|
17
19
|
throw new Error("The Role MCP policy flag contains duplicate MCP server names.");
|
|
18
20
|
return names;
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
const isStringRecord = (value) => isRecord(value) && Object.values(value).every((item) => typeof item === "string");
|
|
26
|
+
/** Documented optional fields of an `mcpServers` entry and their shapes (see Pi's docs/mcp.md). */
|
|
27
|
+
const FIELD_CHECKS = {
|
|
28
|
+
args: [(value) => Array.isArray(value) && value.every((item) => typeof item === "string"), "an array of strings"],
|
|
29
|
+
env: [isStringRecord, "an object of strings"],
|
|
30
|
+
headers: [isStringRecord, "an object of strings"],
|
|
31
|
+
cwd: [(value) => typeof value === "string", "a string"],
|
|
32
|
+
timeout: [(value) => typeof value === "number" && Number.isFinite(value) && value > 0, "a positive number"],
|
|
33
|
+
enabled: [(value) => typeof value === "boolean", "a boolean"],
|
|
34
|
+
oauth: [isRecord, "an object"],
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Select the Role's allowlisted servers from the global `mcp.json` in Pi's native `mcpServers`
|
|
38
|
+
* shape; the project `.pi/mcp.json` is ignored. A `--no-extensions` child has no codemode tool, so
|
|
39
|
+
* every selected server is forced to direct exposure and codemode is never activated.
|
|
40
|
+
*/
|
|
41
|
+
export function loadRoleMcpConfig(agentDir, allowlist) {
|
|
42
|
+
const path = join(agentDir, "mcp.json");
|
|
43
|
+
let parsed = {};
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
49
|
+
throw new Error(`${path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers))) {
|
|
53
|
+
throw new Error(`${path}: expected an object with an "mcpServers" object.`);
|
|
54
|
+
}
|
|
55
|
+
const configured = parsed.mcpServers ?? {};
|
|
56
|
+
const missing = allowlist.filter((name) => !Object.hasOwn(configured, name));
|
|
22
57
|
if (missing.length)
|
|
23
58
|
throw new Error(`Role MCP servers are not configured: ${missing.join(", ")}.`);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
59
|
+
const servers = allowlist.map((name) => {
|
|
60
|
+
const value = configured[name];
|
|
61
|
+
if (!isRecord(value))
|
|
62
|
+
throw new Error(`${path}: MCP server "${name}" must be an object.`);
|
|
63
|
+
if (value.enabled === false)
|
|
64
|
+
throw new Error(`${path}: Role MCP server "${name}" is disabled.`);
|
|
65
|
+
const stdio = typeof value.command === "string" && value.command.trim() !== "" && (value.type === undefined || value.type === "stdio");
|
|
66
|
+
const http = typeof value.url === "string" && value.url.trim() !== "" && (value.type === undefined || value.type === "http" || value.type === "streamable-http");
|
|
67
|
+
if (stdio === http) {
|
|
68
|
+
throw new Error(`${path}: MCP server "${name}" needs either a "command" (stdio) or a "url" (http or streamable-http).`);
|
|
69
|
+
}
|
|
70
|
+
for (const [field, [valid, shape]] of Object.entries(FIELD_CHECKS)) {
|
|
71
|
+
if (value[field] !== undefined && !valid(value[field]))
|
|
72
|
+
throw new Error(`${path}: MCP server "${name}" field "${field}" must be ${shape}.`);
|
|
73
|
+
}
|
|
74
|
+
const { toolExposure: _toolExposure, ...config } = value;
|
|
75
|
+
return { name, config: { ...config, exposure: "direct" }, source: path, scope: "global" };
|
|
76
|
+
});
|
|
77
|
+
return { servers, autoEnableCodemode: false, errors: [] };
|
|
30
78
|
}
|
package/docs/orchestration.md
CHANGED
|
@@ -139,7 +139,7 @@ Every child launch:
|
|
|
139
139
|
5. excludes Main-only delegation/recovery tools and `ask_question`; and
|
|
140
140
|
6. prepends the delegated-subagent identity contract.
|
|
141
141
|
|
|
142
|
-
An empty `tools` list adds no base tools, though selected extension tools can still activate. Omitted or empty `mcps` denies MCP access. Loading `pi-mcp-adapter` directly is rejected because it bypasses the allowlist.
|
|
142
|
+
An empty `tools` list adds no base tools, though selected extension tools can still activate. Children run with `--no-extensions`, so a Role must list the extension that registers its route model's provider; `builtin:<name>` entries load Pi built-in extensions. `mcps` names servers from the global `~/.pi/agent/mcp.json` in Pi's native format, and their tools load in the child with direct exposure. Omitted or empty `mcps` denies MCP access. Loading `pi-mcp-adapter` directly is rejected because it bypasses the allowlist.
|
|
143
143
|
|
|
144
144
|
Isolated Herdr Role launches receive the same turn, token, and child-runtime budget metadata as ephemeral launches. Retained workers therefore keep one non-refilling session budget across follow-ups rather than receiving a new request budget.
|
|
145
145
|
|
package/extensions/role-mcp.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { createMcpAdapter } from "pi-mcp-adapter";
|
|
4
|
-
import { loadMcpConfig } from "pi-mcp-adapter/config";
|
|
5
|
-
import { roleMcpAllowlistFromArgv, ROLE_MCP_POLICY_FLAG, selectRoleMcpConfig } from "@henryqw/pi-subagent";
|
|
1
|
+
import { createMcpExtension, getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { loadRoleMcpConfig, roleMcpAllowlistFromArgv, ROLE_MCP_POLICY_FLAG } from "@henryqw/pi-subagent";
|
|
6
3
|
|
|
7
|
-
export default function roleMcp(pi: ExtensionAPI): void {
|
|
4
|
+
export default function roleMcp(pi: ExtensionAPI): void | Promise<void> {
|
|
8
5
|
pi.registerFlag(ROLE_MCP_POLICY_FLAG, {
|
|
9
6
|
description: "Internal Pi Subagent Role MCP policy",
|
|
10
7
|
type: "string",
|
|
11
8
|
});
|
|
12
|
-
// Pi binds extension flag values after factories load.
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
createMcpAdapter({ config })(pi);
|
|
9
|
+
// Pi binds extension flag values after factories load. Throwing here exits the child non-zero.
|
|
10
|
+
const config = loadRoleMcpConfig(getAgentDir(), roleMcpAllowlistFromArgv(process.argv));
|
|
11
|
+
return createMcpExtension({ loadConfig: () => config })(pi);
|
|
16
12
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "25.0.0",
|
|
4
4
|
"description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"pack:check": "npm pack --dry-run"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@earendil-works/pi-ai": ">=0.
|
|
44
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
45
|
-
"@earendil-works/pi-tui": ">=0.
|
|
43
|
+
"@earendil-works/pi-ai": ">=0.99.1 <0.100.0",
|
|
44
|
+
"@earendil-works/pi-coding-agent": ">=0.99.1 <0.100.0",
|
|
45
|
+
"@earendil-works/pi-tui": ">=0.99.1 <0.100.0",
|
|
46
46
|
"typebox": "^1.3.15"
|
|
47
47
|
},
|
|
48
48
|
"repository": {
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"proper-lockfile": "^4.1.2"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@earendil-works/pi-ai": "0.
|
|
78
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
79
|
-
"@earendil-works/pi-tui": "0.
|
|
77
|
+
"@earendil-works/pi-ai": "0.99.1",
|
|
78
|
+
"@earendil-works/pi-coding-agent": "0.99.1",
|
|
79
|
+
"@earendil-works/pi-tui": "0.99.1",
|
|
80
80
|
"@types/proper-lockfile": "^4.1.4"
|
|
81
81
|
}
|
|
82
82
|
}
|