@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.19.1-fork.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 +135 -0
- package/dist/src/agent-execution.js +114 -87
- package/dist/src/background-widget.d.ts +61 -0
- package/dist/src/background-widget.js +23 -16
- package/dist/src/decoders.js +28 -1
- package/dist/src/execution.d.ts +4 -2
- package/dist/src/execution.js +43 -2
- package/dist/src/host-delivery.js +1 -0
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-recovery.d.ts +5 -2
- package/dist/src/host-recovery.js +3 -3
- package/dist/src/host-view.d.ts +21 -1
- package/dist/src/host-view.js +138 -47
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -60
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +1346 -622
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +326 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/settings.d.ts +26 -0
- package/dist/src/settings.js +277 -0
- package/dist/src/trajectory.d.ts +39 -4
- package/dist/src/trajectory.js +82 -5
- package/dist/src/types.d.ts +30 -3
- package/dist/src/types.js +1 -1
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +40 -0
- package/dist/src/validation.d.ts +10 -17
- package/dist/src/validation.js +29 -458
- package/dist/starter/index.js +0 -2
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +0 -2
- package/dist/subagents/index.js +1344 -798
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +49 -29
- package/dist/subagents/src/contracts.js +18 -7
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +15 -7
- package/dist/subagents/src/manager.js +32 -13
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +24 -2
- package/dist/subagents/src/view.js +107 -80
- package/dist/trajectory/assets/index.html +370 -232
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +445 -277
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +370 -232
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +151 -60
- package/dist/trajectory/src/server.js +138 -11
- package/dist/trajectory/src/server.js.map +2 -2
- package/package.json +7 -6
- package/skills/pi-extensible-workflows/SKILL.md +2 -0
- package/src/agent-execution.ts +110 -80
- package/src/background-widget.ts +26 -19
- package/src/decoders.ts +22 -1
- package/src/execution.ts +41 -4
- package/src/host-delivery.ts +1 -0
- package/src/host-navigator.ts +58 -14
- package/src/host-recovery.ts +8 -7
- package/src/host-view.ts +116 -42
- package/src/host.ts +223 -55
- package/src/index.ts +3 -1
- package/src/paths.ts +3 -0
- package/src/registry.ts +3 -2
- package/src/roles.ts +327 -0
- package/src/script-tools.ts +76 -0
- package/src/settings.ts +224 -0
- package/src/trajectory.ts +97 -9
- package/src/types.ts +12 -5
- package/src/utils.ts +36 -1
- package/src/validation.ts +30 -356
- package/starter/review-loop.ts +0 -2
- package/subagents/README.md +3 -3
- package/subagents/src/contracts.ts +22 -7
- package/subagents/src/index.ts +18 -7
- package/subagents/src/manager.ts +31 -13
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +108 -76
- package/trajectory/src/assets/index.html +370 -232
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +124 -47
- package/trajectory/src/server.ts +114 -14
package/dist/src/paths.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare function safePart(value: string): string;
|
|
|
5
5
|
* guessing at the identity of an object that does not exist.
|
|
6
6
|
*/
|
|
7
7
|
export declare function canonicalPath(path: string): string;
|
|
8
|
+
/** Pi names built-in extensions `builtin:<name>` instead of by a path; other extensions are files. */
|
|
9
|
+
export declare function extensionIdentity(path: string): string;
|
|
8
10
|
export declare function sameFilesystemPath(left: string, right: string): boolean;
|
|
9
11
|
export declare function workflowProjectSettingsPath(cwd: string): string;
|
|
10
12
|
export declare function projectStorageKey(cwd: string): string;
|
package/dist/src/paths.js
CHANGED
|
@@ -20,6 +20,8 @@ export function canonicalPath(path) {
|
|
|
20
20
|
return absolute;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
/** Pi names built-in extensions `builtin:<name>` instead of by a path; other extensions are files. */
|
|
24
|
+
export function extensionIdentity(path) { return path.startsWith("builtin:") ? path : canonicalPath(path); }
|
|
23
25
|
export function sameFilesystemPath(left, right) { return canonicalPath(left) === canonicalPath(right); }
|
|
24
26
|
export function workflowProjectSettingsPath(cwd) { return join(cwd, ".pi", "pi-extensible-workflows", "settings.json"); }
|
|
25
27
|
export function projectStorageKey(cwd) {
|
package/dist/src/registry.js
CHANGED
|
@@ -3,10 +3,11 @@ import { basename, dirname, isAbsolute, join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { Value } from "typebox/value";
|
|
5
5
|
import { byPriorityThenName, deepFreeze, errorCode, errorText, fail, jsonValue, MODEL_ALIAS_NAME, object, validWorkflowExtensionNamespace } from "./utils.js";
|
|
6
|
-
import { loadSettings, resolveWorkflowSettings
|
|
6
|
+
import { loadSettings, resolveWorkflowSettings } from "./settings.js";
|
|
7
|
+
import { validateSchema } from "./utils.js";
|
|
7
8
|
import { canonicalPath } from "./paths.js";
|
|
8
9
|
import { isCorePackageName } from "./package-identity.js";
|
|
9
|
-
const RESERVED_GLOBALS = new Set(["agent", "shell", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
|
|
10
|
+
const RESERVED_GLOBALS = new Set(["agent", "shell", "tools", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
|
|
10
11
|
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
11
12
|
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
12
13
|
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { AgentDefinition, AgentResourceSelectorSources, ContextFileScope, ModelSpec, WorkflowExtensionSettings, WorkflowRoleDirectoryRegistration } from "./types.js";
|
|
2
|
+
export declare function parseRoleMarkdown(content: string, strict?: boolean, rolePath?: string): AgentDefinition;
|
|
3
|
+
export declare function workflowRoleDirectories(agentDir?: string): readonly string[];
|
|
4
|
+
export type WorkflowRoleDirectoryInput = string | WorkflowRoleDirectoryRegistration;
|
|
5
|
+
export interface RoleDiscoveryOptions {
|
|
6
|
+
cwd: string;
|
|
7
|
+
agentDir?: string;
|
|
8
|
+
projectTrusted?: boolean;
|
|
9
|
+
extensionRoleDirectories?: readonly WorkflowRoleDirectoryInput[];
|
|
10
|
+
}
|
|
11
|
+
export interface RoleResourceCandidates {
|
|
12
|
+
skills?: readonly string[];
|
|
13
|
+
extensions?: readonly string[];
|
|
14
|
+
tools?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface RoleResolutionOptions extends RoleDiscoveryOptions {
|
|
17
|
+
definition?: AgentDefinition;
|
|
18
|
+
definitions?: Readonly<Record<string, AgentDefinition>>;
|
|
19
|
+
model?: string;
|
|
20
|
+
modelOverride?: ModelSpec;
|
|
21
|
+
rootModel?: ModelSpec;
|
|
22
|
+
rootTools?: ReadonlySet<string> | readonly string[];
|
|
23
|
+
inheritedTools?: readonly string[];
|
|
24
|
+
availableModels?: ReadonlySet<string>;
|
|
25
|
+
knownModels?: ReadonlySet<string>;
|
|
26
|
+
modelAliases?: Readonly<Record<string, string>>;
|
|
27
|
+
blockedAliases?: ReadonlySet<string>;
|
|
28
|
+
blockedAliasTargets?: Readonly<Record<string, string>>;
|
|
29
|
+
settingsPath?: string;
|
|
30
|
+
selectorSources?: AgentResourceSelectorSources;
|
|
31
|
+
resources?: RoleResourceCandidates;
|
|
32
|
+
tools?: readonly string[];
|
|
33
|
+
skills?: readonly string[];
|
|
34
|
+
extensions?: readonly string[];
|
|
35
|
+
contextFiles?: readonly ContextFileScope[];
|
|
36
|
+
effectiveTools?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
export interface ResolvedRole {
|
|
39
|
+
name?: string;
|
|
40
|
+
definition?: AgentDefinition;
|
|
41
|
+
model?: ModelSpec;
|
|
42
|
+
requestedModel?: string;
|
|
43
|
+
tools?: readonly string[];
|
|
44
|
+
prompt: string;
|
|
45
|
+
overrideSystemPrompt: boolean;
|
|
46
|
+
systemPrompt: {
|
|
47
|
+
mode: "override" | "append";
|
|
48
|
+
text: string;
|
|
49
|
+
};
|
|
50
|
+
contextFiles?: readonly ContextFileScope[];
|
|
51
|
+
extensionSettings?: Readonly<WorkflowExtensionSettings>;
|
|
52
|
+
selectorSources: AgentResourceSelectorSources;
|
|
53
|
+
selectorLayers: {
|
|
54
|
+
skills: readonly (readonly string[] | undefined)[];
|
|
55
|
+
extensions: readonly (readonly string[] | undefined)[];
|
|
56
|
+
tools: readonly (readonly string[] | undefined)[];
|
|
57
|
+
};
|
|
58
|
+
selectedSkills?: readonly string[];
|
|
59
|
+
selectedExtensions?: readonly string[];
|
|
60
|
+
unmatchedSkills?: readonly string[];
|
|
61
|
+
unmatchedExtensions?: readonly string[];
|
|
62
|
+
unmatchedTools?: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
export declare function loadProjectAgentDefinitions(cwd: string): Readonly<Record<string, AgentDefinition>>;
|
|
65
|
+
export declare function discoverRoles(input: RoleDiscoveryOptions): Readonly<Record<string, AgentDefinition>>;
|
|
66
|
+
export declare function loadRole(name: string, input: RoleDiscoveryOptions): AgentDefinition;
|
|
67
|
+
export declare function loadAgentDefinitions(cwd: string, agentDir?: string, projectTrusted?: boolean, extensionRoleDirectories?: readonly WorkflowRoleDirectoryInput[]): Readonly<Record<string, AgentDefinition>>;
|
|
68
|
+
export declare function canonicalExtensionSelector(selector: string, base?: string): string;
|
|
69
|
+
/** Without a supplied root/tool/resource boundary, this API reports selector layers but does not infer available resources; tools and model remain undefined when no candidates are supplied. When selector sources are supplied, model aliases default to none and blocked aliases are not applied unless provided. */
|
|
70
|
+
export declare function resolveRole(name: string | undefined, options: RoleResolutionOptions): ResolvedRole;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
3
|
+
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { registeredWorkflowRoleDirectoryRegistrations } from "./registry.js";
|
|
5
|
+
import { resolveWorkflowSettings, validateContextFileScopes, validateSelectorList, validateWorkflowExtensionSettings, workflowSettingsPath } from "./settings.js";
|
|
6
|
+
import { assertModelThinking, deepFreeze, object, errorText, fail, isNodeError, modelAliasName, modelCapability, resourcePatternHasMagic, resolveModelReference, selectResourcesByLayers, unmatchedResourcePatterns } from "./utils.js";
|
|
7
|
+
import { canonicalPath, extensionIdentity } from "./paths.js";
|
|
8
|
+
const ROLE_DIRECTORY = "pi-extensible-workflows";
|
|
9
|
+
// Preserve the role-file base when a discovered definition is resolved again through the public API.
|
|
10
|
+
const roleDefinitionPaths = new WeakMap();
|
|
11
|
+
export function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
12
|
+
if (!strict) {
|
|
13
|
+
if (!content.startsWith("---\n"))
|
|
14
|
+
return { prompt: content };
|
|
15
|
+
const end = content.indexOf("\n---", 4);
|
|
16
|
+
if (end < 0)
|
|
17
|
+
return { prompt: content };
|
|
18
|
+
const meta = {};
|
|
19
|
+
for (const line of content.slice(4, end).split("\n")) {
|
|
20
|
+
const match = /^(model|tools|skills|extensions|description|overrideSystemPrompt|override_system_prompt|is_system_prompt|contextFiles|extensionSettings|disabledAgentResources|thinking)\s*:\s*(.+)$/.exec(line.trim());
|
|
21
|
+
if (match?.[1] === "disabledAgentResources")
|
|
22
|
+
fail("INVALID_METADATA", "disabledAgentResources is no longer supported; use skills, extensions, and tools selectors");
|
|
23
|
+
if (match?.[1] === "thinking")
|
|
24
|
+
fail("INVALID_METADATA", "Role thinking is not supported; put it on model as provider/model:thinking");
|
|
25
|
+
if (match?.[1] && match[2])
|
|
26
|
+
meta[match[1]] = match[2].trim();
|
|
27
|
+
}
|
|
28
|
+
const unquote = (v) => v.replace(/^['"]|['"]$/g, "");
|
|
29
|
+
const parseList = (value) => value === undefined ? undefined : value.replace(/^\[|\]$/g, "").split(",").map((entry) => unquote(entry.trim())).filter(Boolean);
|
|
30
|
+
const tools = parseList(meta.tools);
|
|
31
|
+
const skills = parseList(meta.skills);
|
|
32
|
+
const extensions = parseList(meta.extensions);
|
|
33
|
+
let extensionSettings;
|
|
34
|
+
if (meta.extensionSettings !== undefined) {
|
|
35
|
+
try {
|
|
36
|
+
extensionSettings = JSON.parse(meta.extensionSettings);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
fail("INVALID_METADATA", `Invalid role extensionSettings: ${errorText(error)}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const rolePathValue = rolePath ?? "<role>";
|
|
43
|
+
const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
|
|
44
|
+
const definition = { prompt: content.slice(end + 4).replace(/^\n/, "") };
|
|
45
|
+
if (meta.model) {
|
|
46
|
+
const model = unquote(meta.model);
|
|
47
|
+
assertModelThinking(model, "Role model");
|
|
48
|
+
definition.model = model;
|
|
49
|
+
}
|
|
50
|
+
if (meta.description)
|
|
51
|
+
definition.description = unquote(meta.description);
|
|
52
|
+
if (tools)
|
|
53
|
+
definition.tools = tools;
|
|
54
|
+
if (skills)
|
|
55
|
+
definition.skills = skills;
|
|
56
|
+
if (extensions)
|
|
57
|
+
definition.extensions = extensions;
|
|
58
|
+
if (normalizedExtensionSettings !== undefined)
|
|
59
|
+
definition.extensionSettings = normalizedExtensionSettings;
|
|
60
|
+
const overrideSystemPrompt = meta.overrideSystemPrompt ?? meta.override_system_prompt ?? meta.is_system_prompt;
|
|
61
|
+
const contextFiles = meta.contextFiles ? meta.contextFiles.replace(/^\[|\]$/g, "").split(",").map((scope) => unquote(scope.trim())).filter(Boolean) : undefined;
|
|
62
|
+
const normalizedContextFiles = validateContextFileScopes(contextFiles, "role");
|
|
63
|
+
if (overrideSystemPrompt)
|
|
64
|
+
definition.overrideSystemPrompt = overrideSystemPrompt === "true";
|
|
65
|
+
if (normalizedContextFiles)
|
|
66
|
+
definition.contextFiles = normalizedContextFiles;
|
|
67
|
+
return definition;
|
|
68
|
+
}
|
|
69
|
+
const normalized = content.replace(/\r\n?/g, "\n");
|
|
70
|
+
if (normalized.startsWith("---\n") && normalized.indexOf("\n---", 3) < 0)
|
|
71
|
+
fail("INVALID_METADATA", "Role frontmatter is missing its closing delimiter");
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = parseFrontmatter(content);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
fail("INVALID_METADATA", `Invalid role frontmatter: ${errorText(error)}`);
|
|
78
|
+
}
|
|
79
|
+
if (!object(parsed.frontmatter))
|
|
80
|
+
fail("INVALID_METADATA", "Role frontmatter must be an object");
|
|
81
|
+
const { model, tools, skills, extensions, extensionSettings, description, contextFiles } = parsed.frontmatter;
|
|
82
|
+
if (Object.prototype.hasOwnProperty.call(parsed.frontmatter, "disabledAgentResources"))
|
|
83
|
+
fail("INVALID_METADATA", "disabledAgentResources is no longer supported; use skills, extensions, and tools selectors");
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(parsed.frontmatter, "thinking"))
|
|
85
|
+
fail("INVALID_METADATA", "Role thinking is not supported; put it on model as provider/model:thinking");
|
|
86
|
+
const overrideSystemPrompt = parsed.frontmatter.overrideSystemPrompt ?? parsed.frontmatter.override_system_prompt ?? parsed.frontmatter.is_system_prompt;
|
|
87
|
+
if (model !== undefined && (typeof model !== "string" || model.trim() === ""))
|
|
88
|
+
fail("INVALID_METADATA", "Role model must be a non-empty string");
|
|
89
|
+
if (description !== undefined && (typeof description !== "string" || description.trim() === "" || description.length > 1024 || /[\r\n]/.test(description)))
|
|
90
|
+
fail("INVALID_METADATA", "Role description must be a non-empty single-line string of at most 1024 characters");
|
|
91
|
+
if (overrideSystemPrompt !== undefined && typeof overrideSystemPrompt !== "boolean")
|
|
92
|
+
fail("INVALID_METADATA", "Role overrideSystemPrompt must be a boolean");
|
|
93
|
+
const normalizedContextFiles = validateContextFileScopes(contextFiles, rolePath ?? "<role>");
|
|
94
|
+
const rolePathValue = rolePath ?? "<role>";
|
|
95
|
+
const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
|
|
96
|
+
const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
|
|
97
|
+
const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
|
|
98
|
+
const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
|
|
99
|
+
const normalizedDescription = typeof description === "string" ? description.trim() : undefined;
|
|
100
|
+
const normalizedModel = typeof model === "string" ? model.trim() : undefined;
|
|
101
|
+
if (normalizedModel !== undefined)
|
|
102
|
+
assertModelThinking(normalizedModel, "Role model");
|
|
103
|
+
const definition = { prompt: parsed.body };
|
|
104
|
+
if (normalizedDescription !== undefined)
|
|
105
|
+
definition.description = normalizedDescription;
|
|
106
|
+
if (normalizedModel !== undefined)
|
|
107
|
+
definition.model = normalizedModel;
|
|
108
|
+
if (normalizedTools !== undefined)
|
|
109
|
+
definition.tools = normalizedTools;
|
|
110
|
+
if (normalizedSkills !== undefined)
|
|
111
|
+
definition.skills = normalizedSkills;
|
|
112
|
+
if (normalizedExtensions !== undefined)
|
|
113
|
+
definition.extensions = normalizedExtensions;
|
|
114
|
+
if (normalizedExtensionSettings !== undefined)
|
|
115
|
+
definition.extensionSettings = normalizedExtensionSettings;
|
|
116
|
+
if (typeof overrideSystemPrompt === "boolean")
|
|
117
|
+
definition.overrideSystemPrompt = overrideSystemPrompt;
|
|
118
|
+
if (normalizedContextFiles !== undefined)
|
|
119
|
+
definition.contextFiles = normalizedContextFiles;
|
|
120
|
+
return definition;
|
|
121
|
+
}
|
|
122
|
+
export function workflowRoleDirectories(agentDir = getAgentDir()) {
|
|
123
|
+
return [join(agentDir, ROLE_DIRECTORY, "roles")];
|
|
124
|
+
}
|
|
125
|
+
function projectRoleDirectories(root) {
|
|
126
|
+
return [join(root, ROLE_DIRECTORY, "roles")];
|
|
127
|
+
}
|
|
128
|
+
function roleDirectorySources(dirs) {
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
return dirs.flatMap((value) => {
|
|
131
|
+
const source = typeof value === "string" ? { path: value } : { path: value.path, extension: value.extension, ...(value.builtin === true ? { builtin: true } : {}) };
|
|
132
|
+
if (seen.has(source.path))
|
|
133
|
+
return [];
|
|
134
|
+
seen.add(source.path);
|
|
135
|
+
return [source];
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function roleDirectoryLabel(source, extension = true) {
|
|
139
|
+
return source.extension ? `Extension "${source.extension.headline}" (${source.extension.version})` : extension ? "Registered workflow extension" : "Standard workflow";
|
|
140
|
+
}
|
|
141
|
+
function isRoleFile(dir, entry) {
|
|
142
|
+
if (extname(entry.name) !== ".md")
|
|
143
|
+
return false;
|
|
144
|
+
if (entry.isFile())
|
|
145
|
+
return true;
|
|
146
|
+
if (!entry.isSymbolicLink())
|
|
147
|
+
return false;
|
|
148
|
+
try {
|
|
149
|
+
return statSync(join(dir, entry.name)).isFile();
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (isNodeError(error, "ENOENT"))
|
|
153
|
+
return false;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function scanRoleFiles(dirs, extension) {
|
|
158
|
+
const files = [];
|
|
159
|
+
for (const source of roleDirectorySources(dirs)) {
|
|
160
|
+
let entries;
|
|
161
|
+
try {
|
|
162
|
+
entries = readdirSync(source.path, { withFileTypes: true });
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (!extension && isNodeError(error, "ENOENT"))
|
|
166
|
+
continue;
|
|
167
|
+
fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
|
|
168
|
+
}
|
|
169
|
+
for (const entry of entries)
|
|
170
|
+
if (isRoleFile(source.path, entry))
|
|
171
|
+
files.push({ name: basename(entry.name, ".md"), path: join(source.path, entry.name), source });
|
|
172
|
+
}
|
|
173
|
+
return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
|
|
174
|
+
}
|
|
175
|
+
function roleEntries(dirs, extension) {
|
|
176
|
+
const files = scanRoleFiles(dirs, extension);
|
|
177
|
+
const starterFiles = files.filter(({ source }) => source.builtin === true);
|
|
178
|
+
const regularFiles = files.filter(({ source }) => source.builtin !== true);
|
|
179
|
+
if (extension) {
|
|
180
|
+
const byName = new Map();
|
|
181
|
+
for (const file of regularFiles)
|
|
182
|
+
byName.set(file.name, [...(byName.get(file.name) ?? []), file]);
|
|
183
|
+
for (const [name, matches] of byName)
|
|
184
|
+
if (matches.length > 1)
|
|
185
|
+
fail("INVALID_METADATA", `Duplicate extension role "${name}": ${matches.map(({ path, source }) => `${roleDirectoryLabel(source)} role directory "${source.path}" (${path})`).join("; ")}`);
|
|
186
|
+
}
|
|
187
|
+
const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
|
|
188
|
+
try {
|
|
189
|
+
const definition = parseRoleMarkdown(readFileSync(path, "utf8"), true, path);
|
|
190
|
+
roleDefinitionPaths.set(definition, path);
|
|
191
|
+
return [name, { name, path, source, definition }];
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (extension)
|
|
195
|
+
fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}));
|
|
199
|
+
return extension ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
|
|
200
|
+
}
|
|
201
|
+
function discoveryInput(input) {
|
|
202
|
+
return { cwd: input.cwd, agentDir: input.agentDir ?? getAgentDir(), projectTrusted: input.projectTrusted ?? true, extensionRoleDirectories: input.extensionRoleDirectories ?? registeredWorkflowRoleDirectoryRegistrations() };
|
|
203
|
+
}
|
|
204
|
+
function discoveredRoleEntries(input) {
|
|
205
|
+
const resolved = discoveryInput(input);
|
|
206
|
+
return {
|
|
207
|
+
...roleEntries(resolved.extensionRoleDirectories, true),
|
|
208
|
+
...roleEntries(workflowRoleDirectories(resolved.agentDir), false),
|
|
209
|
+
...(resolved.projectTrusted ? roleEntries(projectRoleDirectories(join(resolved.cwd, ".pi")), false) : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export function loadProjectAgentDefinitions(cwd) {
|
|
213
|
+
return deepFreeze(Object.fromEntries(Object.entries(roleEntries(projectRoleDirectories(join(cwd, ".pi")), false)).map(([name, entry]) => [name, entry.definition])));
|
|
214
|
+
}
|
|
215
|
+
export function discoverRoles(input) {
|
|
216
|
+
return deepFreeze(Object.fromEntries(Object.entries(discoveredRoleEntries(input)).map(([name, entry]) => [name, entry.definition])));
|
|
217
|
+
}
|
|
218
|
+
export function loadRole(name, input) {
|
|
219
|
+
const entry = discoveredRoleEntries(input)[name];
|
|
220
|
+
if (!entry)
|
|
221
|
+
fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
|
|
222
|
+
return entry.definition;
|
|
223
|
+
}
|
|
224
|
+
export function loadAgentDefinitions(cwd, agentDir = getAgentDir(), projectTrusted = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
|
|
225
|
+
return discoverRoles({ cwd, agentDir, projectTrusted, extensionRoleDirectories });
|
|
226
|
+
}
|
|
227
|
+
export function canonicalExtensionSelector(selector, base = process.cwd()) {
|
|
228
|
+
const negated = selector.startsWith("!");
|
|
229
|
+
const body = negated ? selector.slice(1) : selector;
|
|
230
|
+
// Built-in extensions are named `builtin:<name>`, not by a path.
|
|
231
|
+
if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:"))
|
|
232
|
+
return selector;
|
|
233
|
+
const resolved = resolve(base, body);
|
|
234
|
+
if (resourcePatternHasMagic(body))
|
|
235
|
+
return `${negated ? "!" : ""}${resolved}`;
|
|
236
|
+
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
237
|
+
}
|
|
238
|
+
function selectorsFromSettings(settings) {
|
|
239
|
+
return { ...(settings.skills === undefined ? {} : { skills: settings.skills }), ...(settings.extensions === undefined ? {} : { extensions: settings.extensions }), ...(settings.tools === undefined ? {} : { tools: settings.tools }) };
|
|
240
|
+
}
|
|
241
|
+
function rootToolNames(value) {
|
|
242
|
+
return value === undefined ? undefined : value instanceof Set ? [...value] : [...value];
|
|
243
|
+
}
|
|
244
|
+
function roleDefinitionFor(name, options) {
|
|
245
|
+
if (name === undefined)
|
|
246
|
+
return {};
|
|
247
|
+
if (options.definition !== undefined) {
|
|
248
|
+
const path = roleDefinitionPaths.get(options.definition);
|
|
249
|
+
return { definition: options.definition, ...(path === undefined ? {} : { path }) };
|
|
250
|
+
}
|
|
251
|
+
if (options.definitions?.[name] !== undefined) {
|
|
252
|
+
const definition = options.definitions[name];
|
|
253
|
+
const path = roleDefinitionPaths.get(definition);
|
|
254
|
+
return { definition, ...(path === undefined ? {} : { path }) };
|
|
255
|
+
}
|
|
256
|
+
const entries = discoveredRoleEntries(options);
|
|
257
|
+
const entry = entries[name];
|
|
258
|
+
if (!entry)
|
|
259
|
+
fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
|
|
260
|
+
return entry;
|
|
261
|
+
}
|
|
262
|
+
/** Without a supplied root/tool/resource boundary, this API reports selector layers but does not infer available resources; tools and model remain undefined when no candidates are supplied. When selector sources are supplied, model aliases default to none and blocked aliases are not applied unless provided. */
|
|
263
|
+
export function resolveRole(name, options) {
|
|
264
|
+
const { definition, path } = roleDefinitionFor(name, options);
|
|
265
|
+
const settings = options.selectorSources === undefined ? resolveWorkflowSettings(options.cwd, options.projectTrusted ?? true, options.settingsPath ?? workflowSettingsPath(options.agentDir ?? getAgentDir())) : undefined;
|
|
266
|
+
const baseSources = options.selectorSources ?? { global: selectorsFromSettings(settings?.global ?? {}), project: selectorsFromSettings(settings?.project ?? {}) };
|
|
267
|
+
const canonicalSources = (sources) => ({
|
|
268
|
+
...sources,
|
|
269
|
+
global: { ...sources.global, ...(sources.global.extensions === undefined ? {} : { extensions: sources.global.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }) },
|
|
270
|
+
project: { ...sources.project, ...(sources.project.extensions === undefined ? {} : { extensions: sources.project.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }) },
|
|
271
|
+
...(sources.role === undefined ? {} : { role: { ...sources.role, ...(sources.role.extensions === undefined ? {} : { extensions: sources.role.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }) } }),
|
|
272
|
+
...(sources.call === undefined ? {} : { call: { ...sources.call, ...(sources.call.extensions === undefined ? {} : { extensions: sources.call.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }) } }),
|
|
273
|
+
});
|
|
274
|
+
const roleLayer = definition?.skills !== undefined || definition?.extensions !== undefined || definition?.tools !== undefined ? { ...(definition.skills === undefined ? {} : { skills: definition.skills }), ...(definition.extensions === undefined ? {} : { extensions: definition.extensions.map((selector) => canonicalExtensionSelector(selector, path ? dirname(path) : options.cwd)) }), ...(definition.tools === undefined ? {} : { tools: definition.tools }) } : baseSources.role;
|
|
275
|
+
const callLayer = options.skills !== undefined || options.extensions !== undefined || options.tools !== undefined ? { ...(options.skills === undefined ? {} : { skills: options.skills }), ...(options.extensions === undefined ? {} : { extensions: options.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }), ...(options.tools === undefined ? {} : { tools: options.tools }) } : baseSources.call;
|
|
276
|
+
const selectorSources = canonicalSources({ global: baseSources.global, project: baseSources.project, ...(roleLayer === undefined ? {} : { role: roleLayer }), ...(callLayer === undefined ? {} : { call: callLayer }) });
|
|
277
|
+
const callSelectors = selectorSources.call;
|
|
278
|
+
const roleSelectors = selectorSources.role;
|
|
279
|
+
const selectorLayers = {
|
|
280
|
+
skills: [selectorSources.global.skills, selectorSources.project.skills, roleSelectors?.skills, callSelectors?.skills],
|
|
281
|
+
extensions: [selectorSources.global.extensions, selectorSources.project.extensions, roleSelectors?.extensions, callSelectors?.extensions],
|
|
282
|
+
tools: [selectorSources.global.tools, selectorSources.project.tools, roleSelectors?.tools, callSelectors?.tools],
|
|
283
|
+
};
|
|
284
|
+
const resources = options.resources ?? {};
|
|
285
|
+
const candidateTools = options.inheritedTools ?? rootToolNames(options.rootTools) ?? resources.tools;
|
|
286
|
+
const tools = options.effectiveTools ?? (candidateTools === undefined ? undefined : selectResourcesByLayers(selectorLayers.tools, candidateTools));
|
|
287
|
+
const rootTools = rootToolNames(options.rootTools);
|
|
288
|
+
if (rootTools !== undefined) {
|
|
289
|
+
const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools?.includes(tool));
|
|
290
|
+
if (outsideEffectiveTool)
|
|
291
|
+
fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
|
|
292
|
+
const outsideCallTool = (options.tools ?? []).find((tool) => !tool.startsWith("!") && !resourcePatternHasMagic(tool) && !candidateTools?.includes(tool));
|
|
293
|
+
if (outsideCallTool)
|
|
294
|
+
fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideCallTool}`);
|
|
295
|
+
const outsideRootTool = tools?.find((tool) => !rootTools.includes(tool));
|
|
296
|
+
if (outsideRootTool)
|
|
297
|
+
fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideRootTool}`);
|
|
298
|
+
}
|
|
299
|
+
const selectedSkills = resources.skills === undefined ? undefined : selectResourcesByLayers(selectorLayers.skills, resources.skills);
|
|
300
|
+
const extensionResources = resources.extensions?.map((extension) => extensionIdentity(extension));
|
|
301
|
+
const selectedExtensions = extensionResources === undefined ? undefined : selectResourcesByLayers(selectorLayers.extensions, extensionResources);
|
|
302
|
+
const unmatchedSkills = resources.skills === undefined ? undefined : unmatchedResourcePatterns(selectorLayers.skills.flatMap((layer) => layer ?? []), resources.skills);
|
|
303
|
+
const unmatchedExtensions = extensionResources === undefined ? undefined : unmatchedResourcePatterns(selectorLayers.extensions.flatMap((layer) => layer ?? []), extensionResources);
|
|
304
|
+
const unmatchedTools = candidateTools === undefined ? undefined : unmatchedResourcePatterns(selectorLayers.tools.flatMap((layer) => layer ?? []), candidateTools);
|
|
305
|
+
const aliases = options.modelAliases ?? settings?.effective.modelAliases ?? {};
|
|
306
|
+
const requestedModel = options.model ?? (definition?.model && definition.thinking && !definition.model.includes(":") ? `${definition.model}:${definition.thinking}` : definition?.model);
|
|
307
|
+
const blockedAlias = requestedModel?.split(":", 1)[0];
|
|
308
|
+
if (requestedModel !== undefined && blockedAlias && options.blockedAliases?.has(blockedAlias) && !modelAliasName(requestedModel, aliases)) {
|
|
309
|
+
const target = options.blockedAliasTargets?.[blockedAlias];
|
|
310
|
+
fail("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
|
|
311
|
+
}
|
|
312
|
+
let model = options.modelOverride;
|
|
313
|
+
if (model === undefined && requestedModel !== undefined) {
|
|
314
|
+
assertModelThinking(requestedModel);
|
|
315
|
+
model = resolveModelReference(requestedModel, aliases, options.knownModels ?? options.availableModels, options.settingsPath ?? settings?.sources.modelAliases);
|
|
316
|
+
}
|
|
317
|
+
if (model === undefined)
|
|
318
|
+
model = options.rootModel;
|
|
319
|
+
const availableModels = options.knownModels ?? options.availableModels ?? (options.rootModel === undefined ? undefined : new Set([modelCapability(options.rootModel)]));
|
|
320
|
+
if (model !== undefined && availableModels !== undefined && !availableModels.has(modelCapability(model)))
|
|
321
|
+
fail("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
|
|
322
|
+
const prompt = definition?.prompt ?? "";
|
|
323
|
+
const overrideSystemPrompt = definition?.overrideSystemPrompt === true;
|
|
324
|
+
const contextFiles = options.contextFiles ?? definition?.contextFiles;
|
|
325
|
+
return { ...(name === undefined ? {} : { name }), ...(definition === undefined ? {} : { definition }), ...(model === undefined ? {} : { model }), ...(modelAliasName(requestedModel ?? "", aliases) && requestedModel ? { requestedModel } : {}), ...(tools === undefined ? {} : { tools }), prompt, overrideSystemPrompt, systemPrompt: { mode: overrideSystemPrompt ? "override" : "append", text: prompt }, ...(contextFiles === undefined ? {} : { contextFiles: [...contextFiles] }), ...(definition?.extensionSettings === undefined ? {} : { extensionSettings: definition.extensionSettings }), selectorSources, selectorLayers, ...(selectedSkills === undefined ? {} : { selectedSkills }), ...(selectedExtensions === undefined ? {} : { selectedExtensions }), ...(unmatchedSkills === undefined ? {} : { unmatchedSkills }), ...(unmatchedExtensions === undefined ? {} : { unmatchedExtensions }), ...(unmatchedTools === undefined ? {} : { unmatchedTools }) };
|
|
326
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ExtensionToolContext, ToolLoadout, ToolLoadoutChanges } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type JsonValue } from "./types.js";
|
|
3
|
+
type ScriptTool = ExtensionToolContext["tools"][number];
|
|
4
|
+
type ScriptToolOutcome = Awaited<ReturnType<ExtensionToolContext["executeTool"]>>;
|
|
5
|
+
/** Workflow and subagent tools stay out of scripts, so a run cannot launch or control runs. Codemode is model-only and never callable. */
|
|
6
|
+
export declare function scriptToolExcluded(name: string): boolean;
|
|
7
|
+
/** The identifier scripts use for a tool, the same as Pi codemode's, so its `tools.<id>(args)` lines apply to workflow scripts. */
|
|
8
|
+
export declare function scriptToolIdentifier(name: string): string;
|
|
9
|
+
/** The context of a Pi tool call, whose `executeTool()` runs session tools; absent for commands, session events, and headless launches. */
|
|
10
|
+
export declare function scriptToolContext(ctx: unknown): ExtensionToolContext | undefined;
|
|
11
|
+
export declare function scriptToolIdentifiers(context: ExtensionToolContext | undefined): string[];
|
|
12
|
+
export declare function scriptTool(context: ExtensionToolContext, identifier: string): ScriptTool | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* What a script call resolves to, as in codemode: the structured content of a tool with an output
|
|
15
|
+
* schema, also for error results that carry one, otherwise its text; other failures throw. The value
|
|
16
|
+
* is normalized through JSON and size-checked before it is journaled, so a replay returns the same value.
|
|
17
|
+
*/
|
|
18
|
+
export declare function scriptToolValue(tool: ScriptTool, outcome: ScriptToolOutcome): JsonValue;
|
|
19
|
+
/** Fails a launch whose script names a tool this session cannot call, before any agent runs. */
|
|
20
|
+
export declare function validateScriptToolReferences(script: string, context: ExtensionToolContext | undefined): void;
|
|
21
|
+
/**
|
|
22
|
+
* Tells the model what `tools.<id>(args)` resolves to for declared tools with an output schema; the
|
|
23
|
+
* workflow description says that the others resolve to their text. With codemode active its own
|
|
24
|
+
* identical `Codemode:` lines say this, and pi keeps only one description per tool, so this defers.
|
|
25
|
+
*/
|
|
26
|
+
export declare function prepareScriptToolLoadout(loadout: ToolLoadout): ToolLoadoutChanges | undefined;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { WorkflowError } from "./types.js";
|
|
2
|
+
import { errorText, jsonValue, object } from "./utils.js";
|
|
3
|
+
import { RPC_LIMIT_BYTES } from "./execution.js";
|
|
4
|
+
import { scriptToolReferences } from "./validation.js";
|
|
5
|
+
/** Workflow and subagent tools stay out of scripts, so a run cannot launch or control runs. Codemode is model-only and never callable. */
|
|
6
|
+
export function scriptToolExcluded(name) {
|
|
7
|
+
return name === "workflow" || name.startsWith("workflow_") || name.startsWith("subagents_");
|
|
8
|
+
}
|
|
9
|
+
/** The identifier scripts use for a tool, the same as Pi codemode's, so its `tools.<id>(args)` lines apply to workflow scripts. */
|
|
10
|
+
export function scriptToolIdentifier(name) {
|
|
11
|
+
let identifier = "";
|
|
12
|
+
for (const char of name)
|
|
13
|
+
identifier += (identifier === "" ? /^[A-Za-z_$]$/ : /^[A-Za-z0-9_$]$/).test(char) ? char : "_";
|
|
14
|
+
return identifier || "_";
|
|
15
|
+
}
|
|
16
|
+
/** The context of a Pi tool call, whose `executeTool()` runs session tools; absent for commands, session events, and headless launches. */
|
|
17
|
+
export function scriptToolContext(ctx) {
|
|
18
|
+
return object(ctx) && typeof ctx.executeTool === "function" && Array.isArray(ctx.tools) ? ctx : undefined;
|
|
19
|
+
}
|
|
20
|
+
export function scriptToolIdentifiers(context) {
|
|
21
|
+
return (context?.tools ?? []).filter(({ name }) => !scriptToolExcluded(name)).map(({ name }) => scriptToolIdentifier(name));
|
|
22
|
+
}
|
|
23
|
+
// NOTE: a launch rejects identifiers that several tools share; a later tool set may still collide, and the first match wins.
|
|
24
|
+
export function scriptTool(context, identifier) {
|
|
25
|
+
return context.tools.find(({ name }) => !scriptToolExcluded(name) && scriptToolIdentifier(name) === identifier);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* What a script call resolves to, as in codemode: the structured content of a tool with an output
|
|
29
|
+
* schema, also for error results that carry one, otherwise its text; other failures throw. The value
|
|
30
|
+
* is normalized through JSON and size-checked before it is journaled, so a replay returns the same value.
|
|
31
|
+
*/
|
|
32
|
+
export function scriptToolValue(tool, outcome) {
|
|
33
|
+
const structured = tool.outputSchema === undefined ? undefined : outcome.result.structuredContent;
|
|
34
|
+
const text = outcome.result.content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n");
|
|
35
|
+
if (structured === undefined && outcome.isError)
|
|
36
|
+
throw new WorkflowError("TOOL_FAILED", `${tool.name}: ${text || "failed"}`);
|
|
37
|
+
let value;
|
|
38
|
+
try {
|
|
39
|
+
value = JSON.parse(JSON.stringify(structured ?? text));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new WorkflowError("TOOL_FAILED", `${tool.name} returned a result that is not JSON: ${errorText(error)}`);
|
|
43
|
+
}
|
|
44
|
+
if (!jsonValue(value))
|
|
45
|
+
throw new WorkflowError("TOOL_FAILED", `${tool.name} returned a result that is not JSON`);
|
|
46
|
+
if (Buffer.byteLength(JSON.stringify(value)) > RPC_LIMIT_BYTES)
|
|
47
|
+
throw new WorkflowError("TOOL_FAILED", `${tool.name} returned more than the 10 MB workflow RPC limit`);
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/** Fails a launch whose script names a tool this session cannot call, before any agent runs. */
|
|
51
|
+
export function validateScriptToolReferences(script, context) {
|
|
52
|
+
const references = scriptToolReferences(script);
|
|
53
|
+
if (!references?.length)
|
|
54
|
+
return;
|
|
55
|
+
if (!context)
|
|
56
|
+
throw new WorkflowError("UNKNOWN_TOOL", `Workflow scripts can call session tools only when launched by the workflow tool; this launch has none for ${references.map((name) => `tools.${name}`).join(", ")}`);
|
|
57
|
+
const identifiers = scriptToolIdentifiers(context);
|
|
58
|
+
const unknown = references.filter((name) => !identifiers.includes(name));
|
|
59
|
+
if (unknown.length)
|
|
60
|
+
throw new WorkflowError("UNKNOWN_TOOL", `Workflow script calls tools this session cannot call: ${unknown.map((name) => `tools.${name}`).join(", ")}`);
|
|
61
|
+
const ambiguous = references.filter((name) => identifiers.indexOf(name) !== identifiers.lastIndexOf(name));
|
|
62
|
+
if (ambiguous.length)
|
|
63
|
+
throw new WorkflowError("UNKNOWN_TOOL", `Workflow script calls tools whose names collide after normalization: ${ambiguous.map((name) => `tools.${name}`).join(", ")}`);
|
|
64
|
+
}
|
|
65
|
+
function describeScriptOutput(schema) {
|
|
66
|
+
if (!object(schema) || schema.type !== "object" || !object(schema.properties))
|
|
67
|
+
return schema === undefined ? undefined : "a JSON value";
|
|
68
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
69
|
+
return `\`{ ${Object.keys(schema.properties).map((name) => required.has(name) ? name : `${name}?`).join(", ")} }\``;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Tells the model what `tools.<id>(args)` resolves to for declared tools with an output schema; the
|
|
73
|
+
* workflow description says that the others resolve to their text. With codemode active its own
|
|
74
|
+
* identical `Codemode:` lines say this, and pi keeps only one description per tool, so this defers.
|
|
75
|
+
*/
|
|
76
|
+
export function prepareScriptToolLoadout(loadout) {
|
|
77
|
+
if (loadout.declared.some(({ name }) => name === "codemode"))
|
|
78
|
+
return undefined;
|
|
79
|
+
const callable = new Set(loadout.callable.map(({ name }) => name));
|
|
80
|
+
const descriptions = {};
|
|
81
|
+
for (const tool of loadout.declared) {
|
|
82
|
+
const output = callable.has(tool.name) && !scriptToolExcluded(tool.name) ? describeScriptOutput(tool.outputSchema) : undefined;
|
|
83
|
+
if (output)
|
|
84
|
+
descriptions[tool.name] = `${tool.description.trim()}\n\nWorkflow scripts: \`tools.${scriptToolIdentifier(tool.name)}(args)\` resolves to ${output}.`;
|
|
85
|
+
}
|
|
86
|
+
return { descriptions };
|
|
87
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ToolAnnotations } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AgentResourcePolicy, ContextFileScope, WorkflowExtensionSettings, WorkflowSettings, WorkflowSettingsOverrides, WorkflowSettingsResolution } from "./types.js";
|
|
3
|
+
export { workflowProjectSettingsPath } from "./paths.js";
|
|
4
|
+
export declare const DEFAULT_SETTINGS: Readonly<WorkflowSettings>;
|
|
5
|
+
export declare function workflowSettingsPath(agentDir?: string): string;
|
|
6
|
+
export declare function validateSelectorList(value: unknown, path: string, kind: "skills" | "extensions" | "tools", errorCode?: "INVALID_SETTINGS" | "INVALID_METADATA", normalizeExtensions?: boolean): readonly string[] | undefined;
|
|
7
|
+
export declare function validateContextFileScopes(value: unknown, rolePath: string): readonly ContextFileScope[] | undefined;
|
|
8
|
+
export declare function validateWorkflowExtensionSettings(value: unknown, settingsPath: string, errorCode?: "INVALID_SETTINGS" | "INVALID_METADATA"): WorkflowExtensionSettings | undefined;
|
|
9
|
+
export declare function loadSettings(path?: string): Readonly<WorkflowSettings>;
|
|
10
|
+
/**
|
|
11
|
+
* Exposure and annotations of a workflow or subagent tool. Under the global `codemodeTools` setting,
|
|
12
|
+
* `model-only` keeps the model's direct access and stops codemode scripts from calling the tool.
|
|
13
|
+
* Tools are registered once at load, so the setting is global and applies after a reload.
|
|
14
|
+
* Read-only tools carry `readOnlyHint` so permission extensions need not confirm them.
|
|
15
|
+
*/
|
|
16
|
+
export declare function workflowToolExposure(name: string, codemodeTools: WorkflowSettings["codemodeTools"]): {
|
|
17
|
+
exposure?: "model-only";
|
|
18
|
+
annotations?: ToolAnnotations;
|
|
19
|
+
};
|
|
20
|
+
/** The `codemodeTools` setting at load time. Invalid settings keep the default; launches report them. */
|
|
21
|
+
export declare function loadCodemodeToolsSetting(agentDir?: string): WorkflowSettings["codemodeTools"];
|
|
22
|
+
export declare function loadSettingsOverrides(path: string): Readonly<WorkflowSettingsOverrides>;
|
|
23
|
+
export declare function resolveWorkflowSettings(cwd: string, projectTrusted: boolean, globalSettingsPath?: string): WorkflowSettingsResolution;
|
|
24
|
+
export declare function validateModelAliasAvailability(aliases: Readonly<Record<string, string>>, names: readonly string[], availableModels: ReadonlySet<string>, knownModels: ReadonlySet<string>, settingsPath?: string): void;
|
|
25
|
+
export declare function resolveAgentResourcePolicy(cwd: string, projectTrusted: boolean, globalSettingsPath?: string): AgentResourcePolicy;
|
|
26
|
+
export declare function saveModelAliases(path?: string, aliases?: Readonly<Record<string, string>>): void;
|