@signalridge/pi-subagents 1.10.3 → 1.11.1
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 +53 -0
- package/README.md +34 -11
- package/examples/agent-tool-description.md +1 -1
- package/package.json +9 -8
- package/src/agent-manager.ts +1405 -486
- package/src/agent-runner.ts +538 -268
- package/src/agent-types.ts +6 -0
- package/src/ask-tools.ts +31 -21
- package/src/context-boundary.ts +54 -0
- package/src/context.ts +121 -36
- package/src/custom-agents.ts +19 -4
- package/src/enabled-models.ts +84 -83
- package/src/gate.ts +0 -0
- package/src/index.ts +1118 -861
- package/src/mention-clone.ts +129 -83
- package/src/model-runtime-bridge.ts +68 -0
- package/src/model-scope.ts +8 -3
- package/src/nested-tools.ts +13 -5
- package/src/project-trust.ts +146 -0
- package/src/settings.ts +7 -4
- package/src/skill-loader.ts +22 -10
- package/src/types.ts +6 -4
package/src/settings.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { isManagedAgentTier, MAX_AGENT_TIER_PROFILES } from "@signalridge/pi-sub
|
|
|
12
12
|
// edit index.ts in the same change.
|
|
13
13
|
import { setDefaultToolTimeoutMs } from "./agent-runner.js";
|
|
14
14
|
import { NO_FALLBACK } from "./agent-types.js";
|
|
15
|
+
import { isProjectResource } from "./project-trust.js";
|
|
15
16
|
import type { JoinMode, ThinkingLevel } from "./types.js";
|
|
16
17
|
|
|
17
18
|
/** How a `@handle message` mention is dispatched. See `agentMentions`. */
|
|
@@ -722,11 +723,13 @@ function readSettingsFile(path: string): ReadSettings {
|
|
|
722
723
|
* Load merged settings: global provides defaults, project overrides. Agent-tier
|
|
723
724
|
* profiles are complete replacement units: a project entry never inherits one
|
|
724
725
|
* field from the global entry, and an invalid project entry blocks that global
|
|
725
|
-
* entry instead of silently reviving it.
|
|
726
|
+
* entry instead of silently reviving it. Runtime callers must pass captured trust;
|
|
727
|
+
* the default preserves standalone helper compatibility.
|
|
726
728
|
*/
|
|
727
|
-
export function loadSettings(cwd: string = process.cwd()): SubagentsSettings {
|
|
728
|
-
const
|
|
729
|
-
const
|
|
729
|
+
export function loadSettings(cwd: string = process.cwd(), projectTrusted = true): SubagentsSettings {
|
|
730
|
+
const empty = (): ReadSettings => ({ settings: {}, agentTiers: emptyAgentTiersSource() });
|
|
731
|
+
const global = projectTrusted || !isProjectResource(globalPath(), cwd) ? readSettingsFile(globalPath()) : empty();
|
|
732
|
+
const project = projectTrusted ? readSettingsFile(projectPath(cwd)) : empty();
|
|
730
733
|
const { agentTiers: _globalAgentTiers, ...globalSettings } = global.settings;
|
|
731
734
|
const { agentTiers: _projectAgentTiers, ...projectSettings } = project.settings;
|
|
732
735
|
const agentTiers = mergeAgentTierSources(global.agentTiers, project.agentTiers);
|
package/src/skill-loader.ts
CHANGED
|
@@ -30,37 +30,48 @@ export interface PreloadedSkill {
|
|
|
30
30
|
content: string;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export
|
|
34
|
-
|
|
33
|
+
export interface PreloadSkillsOptions {
|
|
34
|
+
/** Denied/unknown project trust excludes both project roots; user skills remain available. */
|
|
35
|
+
projectTrusted: boolean;
|
|
36
|
+
/** Preserve denied source provenance even through aliases of user skill roots. */
|
|
37
|
+
deniedResource?: (path: string) => boolean;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
function
|
|
40
|
+
export function preloadSkills(skillNames: string[], cwd: string, options: PreloadSkillsOptions): PreloadedSkill[] {
|
|
41
|
+
return skillNames.map((name) => ({ name, content: loadSkillContent(name, cwd, options) }));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadSkillContent(name: string, cwd: string, options: PreloadSkillsOptions): string {
|
|
38
45
|
if (isUnsafeName(name)) {
|
|
39
46
|
return `(Skill "${name}" skipped: name contains path traversal characters)`;
|
|
40
47
|
}
|
|
41
48
|
const roots = [
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
...(options.projectTrusted === true ? [
|
|
50
|
+
join(cwd, ".pi", "skills"), // project — Pi standard
|
|
51
|
+
join(cwd, ".agents", "skills"), // project — Agent Skills spec
|
|
52
|
+
] : []),
|
|
44
53
|
join(getAgentDir(), "skills"), // user — Pi standard
|
|
45
54
|
join(homedir(), ".agents", "skills"), // user — Agent Skills spec
|
|
46
55
|
join(homedir(), ".pi", "skills"), // legacy global, pre-Pi
|
|
47
56
|
];
|
|
48
57
|
for (const root of roots) {
|
|
49
|
-
|
|
58
|
+
if (options.deniedResource?.(root)) continue;
|
|
59
|
+
const content = findInRoot(root, name, options.deniedResource);
|
|
50
60
|
if (content !== undefined) return content;
|
|
51
61
|
}
|
|
52
62
|
return `(Skill "${name}" not found in .pi/skills/, .agents/skills/, or global skill locations)`;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
function findInRoot(root: string, name: string): string | undefined {
|
|
65
|
+
function findInRoot(root: string, name: string, deniedResource?: (path: string) => boolean): string | undefined {
|
|
56
66
|
if (isSymlink(root)) return undefined; // reject symlinked roots entirely
|
|
57
|
-
const
|
|
67
|
+
const flatPath = join(root, `${name}.md`);
|
|
68
|
+
const flat = deniedResource?.(flatPath) ? undefined : safeReadFile(flatPath)?.trim();
|
|
58
69
|
if (flat !== undefined) return flat;
|
|
59
|
-
return findSkillDirectory(root, name);
|
|
70
|
+
return findSkillDirectory(root, name, deniedResource);
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
/** BFS under `root` for a directory named `name` containing `SKILL.md`. Pi-conforming filters. */
|
|
63
|
-
function findSkillDirectory(root: string, name: string): string | undefined {
|
|
74
|
+
function findSkillDirectory(root: string, name: string, deniedResource?: (path: string) => boolean): string | undefined {
|
|
64
75
|
if (!existsSync(root)) return undefined;
|
|
65
76
|
const queue: string[] = [root];
|
|
66
77
|
|
|
@@ -85,6 +96,7 @@ function findSkillDirectory(root: string, name: string): string | undefined {
|
|
|
85
96
|
// Symlinked dirs already filtered by entry.isDirectory() — Dirent uses lstat semantics.
|
|
86
97
|
const path = join(current, entry.name);
|
|
87
98
|
const skillMd = join(path, "SKILL.md");
|
|
99
|
+
if (deniedResource?.(skillMd)) continue;
|
|
88
100
|
const isSkillDir = existsSync(skillMd);
|
|
89
101
|
|
|
90
102
|
if (isSkillDir) {
|
package/src/types.ts
CHANGED
|
@@ -42,10 +42,12 @@ export interface AgentConfig {
|
|
|
42
42
|
*/
|
|
43
43
|
color?: string;
|
|
44
44
|
/**
|
|
45
|
-
* Tools whose
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* Tools whose first call needs human approval for the child session
|
|
46
|
+
* (`ask_tools:`), including resumed turns of this in-memory child. A reopened
|
|
47
|
+
* child must ask again. The third answer between
|
|
48
|
+
* `tools:` and `disallowed_tools:` for tools that are usually fine and
|
|
49
|
+
* occasionally not. Approval comes from the human, never from a model —
|
|
50
|
+
* see `ask-tools.ts`.
|
|
49
51
|
*/
|
|
50
52
|
askTools?: string[];
|
|
51
53
|
/**
|