pi-subagents 0.27.0 → 0.29.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/CHANGELOG.md +27 -0
- package/README.md +16 -15
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +3 -6
- package/src/agents/agent-management.ts +10 -6
- package/src/agents/agent-selection.ts +2 -0
- package/src/agents/agents.ts +303 -6
- package/src/agents/chain-serializer.ts +4 -9
- package/src/extension/doctor.ts +4 -3
- package/src/extension/fanout-child.ts +0 -1
- package/src/extension/index.ts +1 -4
- package/src/extension/schemas.ts +31 -28
- package/src/intercom/intercom-bridge.ts +11 -1
- package/src/runs/background/async-execution.ts +20 -7
- package/src/runs/background/run-status.ts +1 -7
- package/src/runs/background/subagent-runner.ts +73 -146
- package/src/runs/foreground/chain-execution.ts +61 -13
- package/src/runs/foreground/execution.ts +28 -172
- package/src/runs/foreground/subagent-executor.ts +25 -40
- package/src/runs/shared/acceptance.ts +605 -22
- package/src/runs/shared/completion-guard.ts +3 -26
- package/src/runs/shared/model-fallback.ts +38 -0
- package/src/runs/shared/parallel-utils.ts +6 -8
- package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
- package/src/shared/atomic-json.ts +68 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +8 -32
- package/src/shared/utils.ts +2 -1
- package/src/tui/render.ts +1 -11
- package/src/runs/shared/acceptance-contract.ts +0 -291
- package/src/runs/shared/acceptance-evaluation.ts +0 -221
- package/src/runs/shared/acceptance-finalization.ts +0 -161
- package/src/runs/shared/acceptance-reports.ts +0 -127
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.29.0] - 2026-06-19
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added package-provided agent and chain discovery from installed Pi packages and package settings, including read-only management behavior, package source counts in doctor output, nested-cwd project package discovery, and package definitions that remain below user/project overrides. Thanks to Fabian Jocks (@iamfj) for #278.
|
|
9
|
+
- Added `PI_SUBAGENT_EXTRA_AGENT_DIRS` and `PI_INTERCOM_EXTENSION_DIR` overrides so bundled agents and `pi-intercom` can be loaded from read-only package locations. Thanks to David Barroso (@dbarrosop) for #288.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Show captured output from failed foreground subagents instead of returning only the failure summary. Thanks to Jürgen Schmied (@jschmied) for #277.
|
|
13
|
+
- Preserve nested fanout child subagent history when building child prompts. Thanks to James Wood (@jamesjwood) for the original #270 fix.
|
|
14
|
+
- Retry Windows atomic JSON renames on transient `EPERM`, `EBUSY`, and `EACCES` failures. Thanks to Wings Butterfly (@wings1848) for #269.
|
|
15
|
+
- Inherit the parent session model for subagents instead of falling back to global settings, including foreground, chain, async chain, async single, and resume/revive paths. Thanks to Rogerio Saulo (@rsaulo) for #266 and Nicolas Marchildon (@elecnix) for the original #283 fix.
|
|
16
|
+
- Avoid duplicate `subagent` tool registration in fanout-authorized child processes. Thanks to Aleksei Gurianov (@Guria) for #279.
|
|
17
|
+
- Hardened the parallel intercom integration test fixture after Windows CI exposed nondeterministic failure ordering.
|
|
18
|
+
|
|
19
|
+
## [0.28.0] - 2026-06-03
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Added foreground-only `timeoutMs`/`maxRuntimeMs` for single, parallel, and chain subagent runs. Timed-out children are soft-interrupted, keep completed sibling/prior results, and return `timedOut: true` with a stable timeout message.
|
|
23
|
+
- Added per-agent `maxExecutionTimeMs` and `maxTokens` resource limits. Foreground and async children stop with a clear `resourceLimitExceeded` result when the configured runtime or observed token budget is reached.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Strengthened tool and skill guidance so writer subagents launched from plans, specs, issues, or broad fixes proactively use structured `acceptance` instead of burying validation requirements only in task prose.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Removed a provider-unfriendly required-only subschema from the public `acceptance` tool schema so Kimi models served through OpenCode Go can load the `subagent` tool, while keeping runtime validation for empty acceptance contracts.
|
|
30
|
+
- Clarified acceptance-report prompts so required evidence like `diff-summary` must be copied into structured JSON fields such as `diffSummary`, not only described in visible prose.
|
|
31
|
+
|
|
5
32
|
## [0.27.0] - 2026-05-30
|
|
6
33
|
|
|
7
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -358,10 +358,11 @@ Agent locations, lowest to highest priority:
|
|
|
358
358
|
| Scope | Path |
|
|
359
359
|
|-------|------|
|
|
360
360
|
| Builtin | `~/.pi/agent/extensions/subagent/agents/` |
|
|
361
|
+
| Installed package | `package.json` `pi-subagents.agents` or `pi.subagents.agents` |
|
|
361
362
|
| User | `~/.pi/agent/agents/**/*.md` |
|
|
362
363
|
| Project | `.pi/agents/**/*.md` |
|
|
363
364
|
|
|
364
|
-
Project discovery also reads legacy `.agents/**/*.md` files. Nested subdirectories are discovered recursively. `.chain.md` files do not define agents. If both `.agents/` and `.pi/agents/` define the same parsed runtime agent name, `.pi/agents/` wins. Use `agentScope: "user" | "project" | "both"` to control discovery; `both` is the default and project definitions win runtime-name collisions.
|
|
365
|
+
Project discovery also reads legacy `.agents/**/*.md` files. Nested subdirectories are discovered recursively. `.chain.md` files do not define agents. Installed Pi packages can expose agent directories from either `{"pi-subagents":{"agents":["./agents"]}}` or `{"pi":{"subagents":{"agents":["./agents"]}}}` in their package manifest. Package agents load above builtins and below user/project agents. If both `.agents/` and `.pi/agents/` define the same parsed runtime agent name, `.pi/agents/` wins. Use `agentScope: "user" | "project" | "both"` to control discovery; `both` is the default and project definitions win runtime-name collisions.
|
|
365
366
|
|
|
366
367
|
Builtin agents load at the lowest priority, so a user or project agent with the same name overrides them. They do not pin a provider model; they inherit your current Pi default model unless you set `subagents.agentOverrides.<name>.model`. `oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `worker` is the implementation agent for normal tasks and approved oracle handoffs.
|
|
367
368
|
|
|
@@ -496,10 +497,11 @@ Chains are reusable workflows stored separately from agent files. Use `.chain.md
|
|
|
496
497
|
|
|
497
498
|
| Scope | Path |
|
|
498
499
|
|-------|------|
|
|
500
|
+
| Installed package | `package.json` `pi-subagents.chains` or `pi.subagents.chains` |
|
|
499
501
|
| User | `~/.pi/agent/chains/**/*.chain.md`, `~/.pi/agent/chains/**/*.chain.json` |
|
|
500
502
|
| Project | `.pi/chains/**/*.chain.md`, `.pi/chains/**/*.chain.json` |
|
|
501
503
|
|
|
502
|
-
Nested subdirectories are discovered recursively. If both `.chain.md` and `.chain.json` define the same parsed runtime chain name in the same scope, `.chain.json` wins. If user and project scopes define the same parsed runtime chain name, the project chain wins. Chains support the same optional `package` frontmatter as agents; `name: review-flow` plus `package: code-analysis` runs as `code-analysis.review-flow`.
|
|
504
|
+
Nested subdirectories are discovered recursively. Installed Pi packages can expose chain directories from either `{"pi-subagents":{"chains":["./chains"]}}` or `{"pi":{"subagents":{"chains":["./chains"]}}}` in their package manifest. Package chains load below user/project chains. If both `.chain.md` and `.chain.json` define the same parsed runtime chain name in the same scope, `.chain.json` wins. If user and project scopes define the same parsed runtime chain name, the project chain wins. Chains support the same optional `package` frontmatter as agents; `name: review-flow` plus `package: code-analysis` runs as `code-analysis.review-flow`.
|
|
503
505
|
|
|
504
506
|
Example:
|
|
505
507
|
|
|
@@ -794,7 +796,7 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
794
796
|
| `tasks` | array | - | Top-level parallel tasks. Supports `agent`, `task`, `cwd`, `count`, `output`, `outputMode`, `reads`, `progress`, `skill`, `model`, and `acceptance`. |
|
|
795
797
|
| `concurrency` | number | config or `4` | Top-level parallel concurrency. |
|
|
796
798
|
| `worktree` | boolean | false | Create isolated git worktrees for parallel tasks. |
|
|
797
|
-
| `chain` | array | - | Sequential, static parallel, and dynamic fanout chain steps.
|
|
799
|
+
| `chain` | array | - | Sequential, static parallel, and dynamic fanout chain steps. Steps and chain parallel tasks support `phase`, `label`, `as`, `outputSchema`, and `acceptance` in addition to the usual execution fields. Dynamic fanout uses `expand`, one child `parallel` template, and `collect`. |
|
|
798
800
|
| `context` | `fresh \| fork` | agent default or `fresh` | `fork` creates real branched sessions from the parent leaf. Packaged `planner`, `worker`, and `oracle` default to `fork`. |
|
|
799
801
|
| `chainDir` | string | temp chain dir | Persistent directory for chain artifacts. |
|
|
800
802
|
| `clarify` | boolean | true for chains | Show TUI preview/edit flow. |
|
|
@@ -806,7 +808,7 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
806
808
|
| `includeProgress` | boolean | false | Include full progress in result. |
|
|
807
809
|
| `share` | boolean | false | Upload session export to GitHub Gist. |
|
|
808
810
|
| `sessionDir` | string | derived | Override session log directory. |
|
|
809
|
-
| `acceptance` | object |
|
|
811
|
+
| `acceptance` | string/object/false | inferred | Override the run's inferred acceptance gates. Use `"auto"`, `"attested"`, `"checked"`, `"verified"`, `"reviewed"`, or `{ level: "none", reason: "..." }`. |
|
|
810
812
|
|
|
811
813
|
`context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. It never silently downgrades to `fresh`. In multi-agent runs, if any requested agent has `defaultContext: fork` and the launch omits `context`, the whole invocation uses forked context; pass `context: "fresh"` when you intentionally want a fresh run.
|
|
812
814
|
|
|
@@ -989,34 +991,33 @@ Async runs write:
|
|
|
989
991
|
|
|
990
992
|
## Acceptance Gates
|
|
991
993
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
If you are coming from Codex Goals, `acceptance` is the subagent equivalent for one delegated run. When a user says `/goal`, “goal”, “active goal”, “continue until evidence says done”, or “verify against a goal”, translate that into an acceptance contract: `criteria` are the target, `evidence` and `verify` are proof, `stopRules` are constraints, and `maxFinalizationTurns` is the bounded loop budget.
|
|
994
|
+
Every run resolves an effective acceptance policy. Callers may omit `acceptance` for the inferred default, or set it on single runs, top-level parallel task items, chain steps, static parallel tasks, and dynamic fanout templates.
|
|
995
995
|
|
|
996
996
|
```ts
|
|
997
997
|
{
|
|
998
998
|
agent: "worker",
|
|
999
999
|
task: "Implement the fix",
|
|
1000
1000
|
acceptance: {
|
|
1001
|
+
level: "verified",
|
|
1001
1002
|
criteria: ["Patch the bug without widening scope"],
|
|
1002
1003
|
evidence: ["changed-files", "tests-added", "commands-run", "residual-risks", "no-staged-files"],
|
|
1003
|
-
verify: [{ id: "focused", command: "npm test", timeoutMs: 120000 }]
|
|
1004
|
-
maxFinalizationTurns: 3
|
|
1004
|
+
verify: [{ id: "focused", command: "npm test", timeoutMs: 120000 }]
|
|
1005
1005
|
}
|
|
1006
1006
|
}
|
|
1007
1007
|
```
|
|
1008
1008
|
|
|
1009
|
-
|
|
1009
|
+
Accepted levels are `auto`, `none`, `attested`, `checked`, `verified`, and `reviewed`. `acceptance: "auto"` is the default. Read-only reviewer/scout tasks infer lightweight attestation, normal writer tasks infer checked evidence, and async/risky/dynamic writer contexts infer a reviewed gate. To disable gates, prefer `{ level: "none", reason: "..." }`.
|
|
1010
1010
|
|
|
1011
|
-
|
|
1011
|
+
Acceptance provenance is stored separately from child prose:
|
|
1012
1012
|
|
|
1013
|
-
- `
|
|
1014
|
-
- `
|
|
1013
|
+
- `claimed`: child finished but did not provide structured evidence.
|
|
1014
|
+
- `attested`: child returned a structured acceptance report.
|
|
1015
|
+
- `checked`: runtime structural checks passed, such as required evidence and no staged files.
|
|
1015
1016
|
- `verified`: configured runtime verification commands passed. Child-reported command success does not count.
|
|
1016
1017
|
- `reviewed`: an independent reviewer result is present.
|
|
1017
|
-
- `rejected`: attestation, structural checks, verification,
|
|
1018
|
+
- `rejected`: attestation, structural checks, verification, or review failed.
|
|
1018
1019
|
|
|
1019
|
-
|
|
1020
|
+
For `attested` or stricter levels, the child prompt includes a standardized acceptance section and asks for a fenced `acceptance-report` JSON block. Explicit failed gates fail the run. Inferred gates are persisted for observability without breaking older calls that omit `acceptance`.
|
|
1020
1021
|
|
|
1021
1022
|
## Live progress
|
|
1022
1023
|
|
package/package.json
CHANGED
|
@@ -692,9 +692,7 @@ clarify → validation contract → planner → async worker → parallel async
|
|
|
692
692
|
|
|
693
693
|
The validation contract defines acceptance before code is written: expected behavior, acceptance checks, commands or user flows to exercise, and evidence the worker should return. Keep it lightweight for small tasks, but make it explicit enough that reviewers and validators are checking the intended outcome rather than the worker’s own assumptions.
|
|
694
694
|
|
|
695
|
-
Use the structured `acceptance` field when the run should carry an explicit acceptance contract. If omitted,
|
|
696
|
-
|
|
697
|
-
Goal-style requests map to `acceptance`. If the user says `/goal`, “goal”, “active goal”, “continue until evidence says done”, or “verify against a goal” for a subagent run, create an explicit run-scoped acceptance contract: `criteria` for the target, `evidence` and `verify` for proof, `stopRules` for constraints, and `maxFinalizationTurns` for the bounded loop budget.
|
|
695
|
+
Use the structured `acceptance` field when the run should carry an explicit acceptance contract. If omitted, subagents infer an effective acceptance policy from role, mode, and risk. Use `level: "checked"` for ordinary writer evidence gates, `level: "verified"` when the runtime should run explicit validation commands, and `level: "reviewed"` only when an independent reviewer result is expected. Do not call a run reviewed just because the worker says it is done; reviewed means a reviewer gate returned a result. Child-reported command success is evidence, not runtime verification.
|
|
698
696
|
|
|
699
697
|
The first `worker` implements the approved plan. The parent continues with independent inspection or validation prep while it runs, not parallel edits to the same worktree. When the async worker completes, treat its handoff as the transition into review, not as final completion, unless the user explicitly asked for worker-only work, review-only output, or to stop after implementation. Parallel reviewers inspect the resulting diff from fresh context. Validators check behavior with the best available evidence: commands, tests, browser/CLI interaction, screenshots, logs, or manual reproduction notes. The final `worker` applies synthesized review fixes in forked context, then the parent looks over the final diff before completing. The parent may launch these steps as an initial async chain when the workflow is already clear, or as follow-up subagent runs after each async completion. Initial chains should pass `async: true` so the main chat is unblocked; avoid `clarify: true` unless the user asked for foreground clarification. Do not stop after parallel review unless the user explicitly asked for review-only output or the review surfaced a decision that needs approval first.
|
|
700
698
|
|
|
@@ -723,9 +721,8 @@ subagent({
|
|
|
723
721
|
agent: "worker",
|
|
724
722
|
task: "Implement the approved feature.\n\nClarified requirements:\n- ...\n\nPlan: see ~/Documents/docs/...-plan.md\n\nValidation contract:\n- ...\n\nReturn a handoff with changed files, what was implemented, what was left undone, commands run with exit codes, validation evidence, surprises/new risks, and decisions needing parent approval.",
|
|
725
723
|
acceptance: {
|
|
726
|
-
|
|
727
|
-
evidence: ["changed-files", "tests-added", "commands-run", "residual-risks", "no-staged-files"]
|
|
728
|
-
maxFinalizationTurns: 3
|
|
724
|
+
level: "checked",
|
|
725
|
+
evidence: ["changed-files", "tests-added", "commands-run", "residual-risks", "no-staged-files"]
|
|
729
726
|
},
|
|
730
727
|
async: true
|
|
731
728
|
})
|
|
@@ -78,8 +78,8 @@ function parsePackageConfig(value: unknown): { packageName?: string; error?: str
|
|
|
78
78
|
return parsePackageName(value, "config.package");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function allAgents(d: { builtin: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }): AgentConfig[] {
|
|
82
|
-
return [...d.builtin, ...d.user, ...d.project];
|
|
81
|
+
function allAgents(d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }): AgentConfig[] {
|
|
82
|
+
return [...d.builtin, ...d.package, ...d.user, ...d.project];
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function availableNames(cwd: string, kind: "agent" | "chain"): string[] {
|
|
@@ -116,6 +116,10 @@ function nameExistsInScope(cwd: string, scope: ManagementScope, name: string, ex
|
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
function isMutableSource(source: AgentSource): source is ManagementScope {
|
|
120
|
+
return source === "user" || source === "project";
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
function unknownChainAgents(cwd: string, steps: ChainStepConfig[]): string[] {
|
|
120
124
|
const d = discoverAgentsAll(cwd);
|
|
121
125
|
const known = new Set(allAgents(d).map((a) => a.name));
|
|
@@ -327,10 +331,10 @@ function resolveTarget<T extends { source: AgentSource; filePath: string }>(
|
|
|
327
331
|
cwd: string,
|
|
328
332
|
scopeHint?: string,
|
|
329
333
|
): T | AgentToolResult<Details> {
|
|
330
|
-
const mutable = matches.filter((m) => m.source
|
|
334
|
+
const mutable = matches.filter((m): m is T & { source: ManagementScope } => isMutableSource(m.source));
|
|
331
335
|
if (mutable.length === 0) {
|
|
332
336
|
if (matches.length > 0) {
|
|
333
|
-
return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' is
|
|
337
|
+
return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' is read-only and cannot be modified. Create a same-named ${kind} in user or project scope to override it.`, true);
|
|
334
338
|
}
|
|
335
339
|
const available = availableNames(cwd, kind);
|
|
336
340
|
return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' not found. Available: ${available.join(", ") || "none"}.`, true);
|
|
@@ -442,9 +446,9 @@ function formatChainDetail(chain: ChainConfig): string {
|
|
|
442
446
|
export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
443
447
|
const scope = normalizeListScope(params.agentScope) ?? "both";
|
|
444
448
|
const d = discoverAgentsAll(ctx.cwd);
|
|
445
|
-
const scopedAgents = allAgents(d).filter((a) => scope === "both" || a.source === "builtin" || a.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
449
|
+
const scopedAgents = allAgents(d).filter((a) => scope === "both" || a.source === "builtin" || a.source === "package" || a.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
446
450
|
const agents = scopedAgents.filter((a) => !a.disabled);
|
|
447
|
-
const chains = d.chains.filter((c) => scope === "both" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
451
|
+
const chains = d.chains.filter((c) => scope === "both" || c.source === "package" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
448
452
|
const diagnostics = d.chainDiagnostics.filter((entry) => scope === "both" || entry.source === scope);
|
|
449
453
|
const lines = [
|
|
450
454
|
"Executable agents:",
|
|
@@ -5,10 +5,12 @@ export function mergeAgentsForScope(
|
|
|
5
5
|
userAgents: AgentConfig[],
|
|
6
6
|
projectAgents: AgentConfig[],
|
|
7
7
|
builtinAgents: AgentConfig[] = [],
|
|
8
|
+
packageAgents: AgentConfig[] = [],
|
|
8
9
|
): AgentConfig[] {
|
|
9
10
|
const agentMap = new Map<string, AgentConfig>();
|
|
10
11
|
|
|
11
12
|
for (const agent of builtinAgents) agentMap.set(agent.name, agent);
|
|
13
|
+
for (const agent of packageAgents) agentMap.set(agent.name, agent);
|
|
12
14
|
|
|
13
15
|
if (scope === "both") {
|
|
14
16
|
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
package/src/agents/agents.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Agent discovery and configuration
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
5
6
|
import * as fs from "node:fs";
|
|
6
7
|
import * as os from "node:os";
|
|
7
8
|
import * as path from "node:path";
|
|
@@ -17,7 +18,7 @@ export { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./
|
|
|
17
18
|
|
|
18
19
|
export type AgentScope = "user" | "project" | "both";
|
|
19
20
|
|
|
20
|
-
export type AgentSource = "builtin" | "user" | "project";
|
|
21
|
+
export type AgentSource = "builtin" | "package" | "user" | "project";
|
|
21
22
|
type SystemPromptMode = "append" | "replace";
|
|
22
23
|
export type AgentDefaultContext = "fresh" | "fork";
|
|
23
24
|
|
|
@@ -141,7 +142,7 @@ export interface ChainConfig {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
export interface ChainDiscoveryDiagnostic {
|
|
144
|
-
source:
|
|
145
|
+
source: AgentSource;
|
|
145
146
|
filePath: string;
|
|
146
147
|
error: string;
|
|
147
148
|
}
|
|
@@ -155,6 +156,259 @@ function getUserChainDir(): string {
|
|
|
155
156
|
return path.join(getAgentDir(), "chains");
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
interface PackageSubagentPaths {
|
|
160
|
+
agents: string[];
|
|
161
|
+
chains: string[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let cachedGlobalNpmRoot: string | null = null;
|
|
165
|
+
|
|
166
|
+
function readJsonFileBestEffort(filePath: string): unknown {
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
169
|
+
} catch {
|
|
170
|
+
// Installed package scans are opportunistic; bad third-party manifests
|
|
171
|
+
// should not break local agent discovery.
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function readOptionalJsonFile(filePath: string): unknown {
|
|
177
|
+
try {
|
|
178
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
181
|
+
? (error as { code?: unknown }).code
|
|
182
|
+
: undefined;
|
|
183
|
+
if (code === "ENOENT") return null;
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isSafePackagePath(value: string): boolean {
|
|
189
|
+
return value.length > 0
|
|
190
|
+
&& !path.isAbsolute(value)
|
|
191
|
+
&& value.split(/[\\/]/).every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseNpmPackageName(source: string): string | undefined {
|
|
195
|
+
const spec = source.slice(4).trim();
|
|
196
|
+
if (!spec) return undefined;
|
|
197
|
+
const match = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/);
|
|
198
|
+
const packageName = match?.[1] ?? spec;
|
|
199
|
+
return isSafePackagePath(packageName) ? packageName : undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function stripGitRef(repoPath: string): string {
|
|
203
|
+
const atIndex = repoPath.indexOf("@");
|
|
204
|
+
const hashIndex = repoPath.indexOf("#");
|
|
205
|
+
const refIndex = [atIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
206
|
+
return refIndex === undefined ? repoPath : repoPath.slice(0, refIndex);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function parseGitPackagePath(source: string): { host: string; repoPath: string } | undefined {
|
|
210
|
+
const spec = source.slice(4).trim();
|
|
211
|
+
if (!spec) return undefined;
|
|
212
|
+
|
|
213
|
+
let host = "";
|
|
214
|
+
let repoPath = "";
|
|
215
|
+
const scpLike = spec.match(/^git@([^:]+):(.+)$/);
|
|
216
|
+
if (scpLike) {
|
|
217
|
+
host = scpLike[1] ?? "";
|
|
218
|
+
repoPath = scpLike[2] ?? "";
|
|
219
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(spec)) {
|
|
220
|
+
try {
|
|
221
|
+
const url = new URL(spec);
|
|
222
|
+
host = url.hostname;
|
|
223
|
+
repoPath = url.pathname.replace(/^\/+/, "");
|
|
224
|
+
} catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
const slashIndex = spec.indexOf("/");
|
|
229
|
+
if (slashIndex < 0) return undefined;
|
|
230
|
+
host = spec.slice(0, slashIndex);
|
|
231
|
+
repoPath = spec.slice(slashIndex + 1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const normalizedPath = stripGitRef(repoPath).replace(/\.git$/, "").replace(/^\/+/, "");
|
|
235
|
+
if (!host || !isSafePackagePath(host) || !isSafePackagePath(normalizedPath) || normalizedPath.split(/[\\/]/).length < 2) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
return { host, repoPath: normalizedPath };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function resolveSettingsPackageRoot(source: string, baseDir: string): string | undefined {
|
|
242
|
+
const trimmed = source.trim();
|
|
243
|
+
if (!trimmed) return undefined;
|
|
244
|
+
if (trimmed.startsWith("git:")) {
|
|
245
|
+
const parsed = parseGitPackagePath(trimmed);
|
|
246
|
+
return parsed ? path.join(baseDir, "git", parsed.host, parsed.repoPath) : undefined;
|
|
247
|
+
}
|
|
248
|
+
if (trimmed.startsWith("npm:")) {
|
|
249
|
+
const packageName = parseNpmPackageName(trimmed);
|
|
250
|
+
return packageName ? path.join(baseDir, "npm", "node_modules", packageName) : undefined;
|
|
251
|
+
}
|
|
252
|
+
const normalized = trimmed.startsWith("file:") ? trimmed.slice(5) : trimmed;
|
|
253
|
+
if (normalized === "~") return os.homedir();
|
|
254
|
+
if (normalized.startsWith("~/")) return path.join(os.homedir(), normalized.slice(2));
|
|
255
|
+
if (path.isAbsolute(normalized)) return normalized;
|
|
256
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("./") || normalized.startsWith("../")) {
|
|
257
|
+
return path.resolve(baseDir, normalized);
|
|
258
|
+
}
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function getGlobalNpmRoot(): string | null {
|
|
263
|
+
if (cachedGlobalNpmRoot !== null) return cachedGlobalNpmRoot;
|
|
264
|
+
try {
|
|
265
|
+
cachedGlobalNpmRoot = fs.realpathSync(execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim());
|
|
266
|
+
return cachedGlobalNpmRoot;
|
|
267
|
+
} catch {
|
|
268
|
+
cachedGlobalNpmRoot = "";
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function stringArray(value: unknown): string[] {
|
|
274
|
+
if (!Array.isArray(value)) return [];
|
|
275
|
+
return value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function extractSubagentPathsFromPackageRoot(packageRoot: string): PackageSubagentPaths {
|
|
279
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
280
|
+
const pkg = readJsonFileBestEffort(packageJsonPath);
|
|
281
|
+
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) return { agents: [], chains: [] };
|
|
282
|
+
|
|
283
|
+
const roots: Record<string, unknown>[] = [];
|
|
284
|
+
const piSubagents = (pkg as { "pi-subagents"?: unknown })["pi-subagents"];
|
|
285
|
+
if (piSubagents && typeof piSubagents === "object" && !Array.isArray(piSubagents)) {
|
|
286
|
+
roots.push(piSubagents as Record<string, unknown>);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const pi = (pkg as { pi?: unknown }).pi;
|
|
290
|
+
if (pi && typeof pi === "object" && !Array.isArray(pi)) {
|
|
291
|
+
const subagents = (pi as { subagents?: unknown }).subagents;
|
|
292
|
+
if (subagents && typeof subagents === "object" && !Array.isArray(subagents)) {
|
|
293
|
+
roots.push(subagents as Record<string, unknown>);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const agents: string[] = [];
|
|
298
|
+
const chains: string[] = [];
|
|
299
|
+
for (const root of roots) {
|
|
300
|
+
for (const entry of stringArray(root.agents)) agents.push(path.resolve(packageRoot, entry));
|
|
301
|
+
for (const entry of stringArray(root.chains)) chains.push(path.resolve(packageRoot, entry));
|
|
302
|
+
}
|
|
303
|
+
return { agents, chains };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function collectPackageRootsFromNodeModules(nodeModulesDir: string): string[] {
|
|
307
|
+
const roots: string[] = [];
|
|
308
|
+
if (!fs.existsSync(nodeModulesDir)) return roots;
|
|
309
|
+
|
|
310
|
+
let entries: fs.Dirent[];
|
|
311
|
+
try {
|
|
312
|
+
entries = fs.readdirSync(nodeModulesDir, { withFileTypes: true });
|
|
313
|
+
} catch {
|
|
314
|
+
return roots;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
if (entry.name.startsWith(".")) continue;
|
|
319
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
320
|
+
|
|
321
|
+
if (entry.name.startsWith("@")) {
|
|
322
|
+
const scopeDir = path.join(nodeModulesDir, entry.name);
|
|
323
|
+
let scopeEntries: fs.Dirent[];
|
|
324
|
+
try {
|
|
325
|
+
scopeEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
|
|
326
|
+
} catch {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
for (const scopeEntry of scopeEntries) {
|
|
330
|
+
if (scopeEntry.name.startsWith(".")) continue;
|
|
331
|
+
if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink()) continue;
|
|
332
|
+
roots.push(path.join(scopeDir, scopeEntry.name));
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
roots.push(path.join(nodeModulesDir, entry.name));
|
|
338
|
+
}
|
|
339
|
+
return roots;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function collectSettingsPackageRoots(settingsFile: string, baseDir: string): string[] {
|
|
343
|
+
const settings = readOptionalJsonFile(settingsFile);
|
|
344
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) return [];
|
|
345
|
+
const packages = (settings as { packages?: unknown }).packages;
|
|
346
|
+
if (!Array.isArray(packages)) return [];
|
|
347
|
+
|
|
348
|
+
const roots: string[] = [];
|
|
349
|
+
for (const entry of packages) {
|
|
350
|
+
const packageSource = typeof entry === "string"
|
|
351
|
+
? entry
|
|
352
|
+
: typeof entry === "object" && entry !== null && typeof (entry as { source?: unknown }).source === "string"
|
|
353
|
+
? (entry as { source: string }).source
|
|
354
|
+
: undefined;
|
|
355
|
+
if (!packageSource) continue;
|
|
356
|
+
const packageRoot = resolveSettingsPackageRoot(packageSource, baseDir);
|
|
357
|
+
if (packageRoot) roots.push(packageRoot);
|
|
358
|
+
}
|
|
359
|
+
return roots;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function collectPackageSubagentPaths(cwd: string, options: { includeUser: boolean; includeProject: boolean } = { includeUser: true, includeProject: true }): PackageSubagentPaths {
|
|
363
|
+
const agentDir = getAgentDir();
|
|
364
|
+
const projectRoot = findNearestProjectRoot(cwd) ?? cwd;
|
|
365
|
+
const packageRoots = [
|
|
366
|
+
projectRoot,
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
if (options.includeProject) {
|
|
370
|
+
packageRoots.push(
|
|
371
|
+
...collectPackageRootsFromNodeModules(path.join(projectRoot, ".pi", "npm", "node_modules")),
|
|
372
|
+
...collectSettingsPackageRoots(path.join(projectRoot, ".pi", "settings.json"), path.join(projectRoot, ".pi")),
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (options.includeUser) {
|
|
377
|
+
packageRoots.push(
|
|
378
|
+
...collectPackageRootsFromNodeModules(path.join(agentDir, "npm", "node_modules")),
|
|
379
|
+
...collectSettingsPackageRoots(path.join(agentDir, "settings.json"), agentDir),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (options.includeUser) {
|
|
384
|
+
const globalRoot = getGlobalNpmRoot();
|
|
385
|
+
if (globalRoot) packageRoots.push(...collectPackageRootsFromNodeModules(globalRoot));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const seenRoots = new Set<string>();
|
|
389
|
+
const seenAgents = new Set<string>();
|
|
390
|
+
const seenChains = new Set<string>();
|
|
391
|
+
const agents: string[] = [];
|
|
392
|
+
const chains: string[] = [];
|
|
393
|
+
for (const packageRoot of packageRoots) {
|
|
394
|
+
const resolvedRoot = path.resolve(packageRoot);
|
|
395
|
+
if (seenRoots.has(resolvedRoot)) continue;
|
|
396
|
+
seenRoots.add(resolvedRoot);
|
|
397
|
+
const paths = extractSubagentPathsFromPackageRoot(resolvedRoot);
|
|
398
|
+
for (const agentDir of paths.agents) {
|
|
399
|
+
if (seenAgents.has(agentDir)) continue;
|
|
400
|
+
seenAgents.add(agentDir);
|
|
401
|
+
agents.push(agentDir);
|
|
402
|
+
}
|
|
403
|
+
for (const chainDir of paths.chains) {
|
|
404
|
+
if (seenChains.has(chainDir)) continue;
|
|
405
|
+
seenChains.add(chainDir);
|
|
406
|
+
chains.push(chainDir);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return { agents, chains };
|
|
410
|
+
}
|
|
411
|
+
|
|
158
412
|
function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpDirectTools?: string[] } {
|
|
159
413
|
const mcpDirectTools: string[] = [];
|
|
160
414
|
const tools: string[] = [];
|
|
@@ -706,7 +960,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
706
960
|
return agents;
|
|
707
961
|
}
|
|
708
962
|
|
|
709
|
-
function loadChainsFromDir(dir: string, source:
|
|
963
|
+
function loadChainsFromDir(dir: string, source: AgentSource): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[] } {
|
|
710
964
|
const chains = new Map<string, ChainConfig>();
|
|
711
965
|
const diagnostics: ChainDiscoveryDiagnostic[] = [];
|
|
712
966
|
|
|
@@ -768,6 +1022,22 @@ function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; pref
|
|
|
768
1022
|
}
|
|
769
1023
|
const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
|
|
770
1024
|
|
|
1025
|
+
export const EXTRA_AGENT_DIRS_ENV = "PI_SUBAGENT_EXTRA_AGENT_DIRS";
|
|
1026
|
+
|
|
1027
|
+
// Additional read-only directories to scan for agent definitions, supplied by the
|
|
1028
|
+
// launcher via PI_SUBAGENT_EXTRA_AGENT_DIRS (PATH-style, split on os/path delimiter).
|
|
1029
|
+
// Lets a hermetic wrapper (e.g. a Nix-store install) expose bundled agents without
|
|
1030
|
+
// copying or symlinking them into the writable agent dir. Loaded as "user" source,
|
|
1031
|
+
// at lower precedence than agents the user placed in their own agent dir.
|
|
1032
|
+
function extraUserAgentDirs(): string[] {
|
|
1033
|
+
const raw = process.env[EXTRA_AGENT_DIRS_ENV];
|
|
1034
|
+
if (!raw) return [];
|
|
1035
|
+
return raw
|
|
1036
|
+
.split(path.delimiter)
|
|
1037
|
+
.map((dir) => dir.trim())
|
|
1038
|
+
.filter((dir) => dir.length > 0);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
771
1041
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
772
1042
|
const userDirOld = path.join(getAgentDir(), "agents");
|
|
773
1043
|
const userDirNew = path.join(os.homedir(), ".agents");
|
|
@@ -776,6 +1046,10 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
776
1046
|
const projectSettingsPath = getProjectAgentSettingsPath(cwd);
|
|
777
1047
|
const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
|
|
778
1048
|
const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
|
|
1049
|
+
const packageSubagentPaths = collectPackageSubagentPaths(cwd, {
|
|
1050
|
+
includeUser: scope !== "project",
|
|
1051
|
+
includeProject: scope !== "user",
|
|
1052
|
+
});
|
|
779
1053
|
|
|
780
1054
|
const builtinAgents = applyBuiltinOverrides(
|
|
781
1055
|
loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
|
|
@@ -785,12 +1059,14 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
785
1059
|
projectSettingsPath,
|
|
786
1060
|
);
|
|
787
1061
|
|
|
1062
|
+
const userAgentsExtra = scope === "project" ? [] : extraUserAgentDirs().flatMap((dir) => loadAgentsFromDir(dir, "user"));
|
|
788
1063
|
const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
|
|
789
1064
|
const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
|
|
790
|
-
const userAgents = [...userAgentsOld, ...userAgentsNew];
|
|
1065
|
+
const userAgents = [...userAgentsExtra, ...userAgentsOld, ...userAgentsNew];
|
|
791
1066
|
|
|
792
1067
|
const projectAgents = scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project"));
|
|
793
|
-
const
|
|
1068
|
+
const packageAgents = packageSubagentPaths.agents.flatMap((dir) => loadAgentsFromDir(dir, "package"));
|
|
1069
|
+
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents, packageAgents)
|
|
794
1070
|
.filter((agent) => agent.disabled !== true);
|
|
795
1071
|
|
|
796
1072
|
return { agents, projectAgentsDir };
|
|
@@ -798,6 +1074,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
798
1074
|
|
|
799
1075
|
export function discoverAgentsAll(cwd: string): {
|
|
800
1076
|
builtin: AgentConfig[];
|
|
1077
|
+
package: AgentConfig[];
|
|
801
1078
|
user: AgentConfig[];
|
|
802
1079
|
project: AgentConfig[];
|
|
803
1080
|
chains: ChainConfig[];
|
|
@@ -818,6 +1095,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
818
1095
|
const projectSettingsPath = getProjectAgentSettingsPath(cwd);
|
|
819
1096
|
const userSettings = readSubagentSettings(userSettingsPath);
|
|
820
1097
|
const projectSettings = readSubagentSettings(projectSettingsPath);
|
|
1098
|
+
const packageSubagentPaths = collectPackageSubagentPaths(cwd);
|
|
821
1099
|
|
|
822
1100
|
const builtin = applyBuiltinOverrides(
|
|
823
1101
|
loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
|
|
@@ -827,9 +1105,17 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
827
1105
|
projectSettingsPath,
|
|
828
1106
|
);
|
|
829
1107
|
const user = [
|
|
1108
|
+
...extraUserAgentDirs().flatMap((dir) => loadAgentsFromDir(dir, "user")),
|
|
830
1109
|
...loadAgentsFromDir(userDirOld, "user"),
|
|
831
1110
|
...loadAgentsFromDir(userDirNew, "user"),
|
|
832
1111
|
];
|
|
1112
|
+
const packageMap = new Map<string, AgentConfig>();
|
|
1113
|
+
for (const dir of packageSubagentPaths.agents) {
|
|
1114
|
+
for (const agent of loadAgentsFromDir(dir, "package")) {
|
|
1115
|
+
if (!packageMap.has(agent.name)) packageMap.set(agent.name, agent);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const packageAgents = Array.from(packageMap.values());
|
|
833
1119
|
const projectMap = new Map<string, AgentConfig>();
|
|
834
1120
|
for (const dir of projectDirs) {
|
|
835
1121
|
for (const agent of loadAgentsFromDir(dir, "project")) {
|
|
@@ -839,6 +1125,15 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
839
1125
|
const project = Array.from(projectMap.values());
|
|
840
1126
|
|
|
841
1127
|
const chainMap = new Map<string, ChainConfig>();
|
|
1128
|
+
const packageChainDiagnostics: ChainDiscoveryDiagnostic[] = [];
|
|
1129
|
+
const packageChainMap = new Map<string, ChainConfig>();
|
|
1130
|
+
for (const dir of packageSubagentPaths.chains) {
|
|
1131
|
+
const loaded = loadChainsFromDir(dir, "package");
|
|
1132
|
+
packageChainDiagnostics.push(...loaded.diagnostics);
|
|
1133
|
+
for (const chain of loaded.chains) {
|
|
1134
|
+
if (!packageChainMap.has(chain.name)) packageChainMap.set(chain.name, chain);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
842
1137
|
const projectChainDiagnostics: ChainDiscoveryDiagnostic[] = [];
|
|
843
1138
|
for (const dir of projectChainDirs) {
|
|
844
1139
|
const loaded = loadChainsFromDir(dir, "project");
|
|
@@ -849,15 +1144,17 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
849
1144
|
}
|
|
850
1145
|
const userChains = loadChainsFromDir(userChainDir, "user");
|
|
851
1146
|
const chains = [
|
|
1147
|
+
...Array.from(packageChainMap.values()),
|
|
852
1148
|
...userChains.chains,
|
|
853
1149
|
...Array.from(chainMap.values()),
|
|
854
1150
|
];
|
|
855
1151
|
const chainDiagnostics = [
|
|
1152
|
+
...packageChainDiagnostics,
|
|
856
1153
|
...userChains.diagnostics,
|
|
857
1154
|
...projectChainDiagnostics,
|
|
858
1155
|
];
|
|
859
1156
|
|
|
860
1157
|
const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
|
861
1158
|
|
|
862
|
-
return { builtin, user, project, chains, chainDiagnostics, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
|
|
1159
|
+
return { builtin, package: packageAgents, user, project, chains, chainDiagnostics, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
|
|
863
1160
|
}
|