@zihanw/pi-forge 0.3.2 → 0.4.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 +196 -0
- package/PUBLIC_API.md +5 -0
- package/README.md +93 -478
- package/README.zh-CN.md +88 -461
- package/SUBAGENT_ADAPTER_CONTRACT.md +5 -0
- package/dist/agent-profile.d.ts +71 -0
- package/dist/agent-profile.d.ts.map +1 -0
- package/dist/agent-profile.js +303 -0
- package/dist/agent-profile.js.map +1 -0
- package/dist/forge-config.d.ts +88 -0
- package/dist/forge-config.d.ts.map +1 -0
- package/dist/forge-config.js +290 -0
- package/dist/forge-config.js.map +1 -0
- package/dist/forge-extensions.d.ts.map +1 -1
- package/dist/forge-extensions.js +19 -3
- package/dist/forge-extensions.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -358
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +11 -3
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +90 -4
- package/dist/lifecycle.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +116 -5
- package/dist/loader.js.map +1 -1
- package/dist/payload-capture.d.ts.map +1 -1
- package/dist/payload-capture.js +27 -0
- package/dist/payload-capture.js.map +1 -1
- package/dist/payload-command.d.ts +3 -3
- package/dist/payload-command.d.ts.map +1 -1
- package/dist/payload-command.js.map +1 -1
- package/dist/preview.d.ts +2 -2
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +2 -3
- package/dist/preview.js.map +1 -1
- package/dist/profile-command.d.ts +12 -0
- package/dist/profile-command.d.ts.map +1 -0
- package/dist/profile-command.js +291 -0
- package/dist/profile-command.js.map +1 -0
- package/dist/profile-service.d.ts +103 -0
- package/dist/profile-service.d.ts.map +1 -0
- package/dist/profile-service.js +220 -0
- package/dist/profile-service.js.map +1 -0
- package/dist/runtime/profile-runtime.d.ts +13 -0
- package/dist/runtime/profile-runtime.d.ts.map +1 -0
- package/dist/runtime/profile-runtime.js +47 -0
- package/dist/runtime/profile-runtime.js.map +1 -0
- package/dist/runtime/prompt-stack-runtime.d.ts +22 -0
- package/dist/runtime/prompt-stack-runtime.d.ts.map +1 -0
- package/dist/runtime/prompt-stack-runtime.js +104 -0
- package/dist/runtime/prompt-stack-runtime.js.map +1 -0
- package/dist/runtime/subagent-runtime.d.ts +45 -0
- package/dist/runtime/subagent-runtime.d.ts.map +1 -0
- package/dist/runtime/subagent-runtime.js +335 -0
- package/dist/runtime/subagent-runtime.js.map +1 -0
- package/dist/runtime/tool-policy-runtime.d.ts +15 -0
- package/dist/runtime/tool-policy-runtime.d.ts.map +1 -0
- package/dist/runtime/tool-policy-runtime.js +170 -0
- package/dist/runtime/tool-policy-runtime.js.map +1 -0
- package/dist/runtime/web-editor-runtime.d.ts +9 -0
- package/dist/runtime/web-editor-runtime.d.ts.map +1 -0
- package/dist/runtime/web-editor-runtime.js +131 -0
- package/dist/runtime/web-editor-runtime.js.map +1 -0
- package/dist/runtime-state.d.ts +4 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +2 -0
- package/dist/runtime-state.js.map +1 -1
- package/dist/storage.d.ts +4 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +24 -1
- package/dist/storage.js.map +1 -1
- package/dist/subagent/canonical.d.ts +22 -0
- package/dist/subagent/canonical.d.ts.map +1 -0
- package/dist/subagent/canonical.js +24 -0
- package/dist/subagent/canonical.js.map +1 -0
- package/dist/subagent/context.d.ts +8 -0
- package/dist/subagent/context.d.ts.map +1 -0
- package/dist/subagent/context.js +125 -0
- package/dist/subagent/context.js.map +1 -0
- package/dist/subagent/contract.d.ts +10 -0
- package/dist/subagent/contract.d.ts.map +1 -0
- package/dist/subagent/contract.js +10 -0
- package/dist/subagent/contract.js.map +1 -0
- package/dist/subagent/index.d.ts +14 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +14 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/plan.d.ts +18 -0
- package/dist/subagent/plan.d.ts.map +1 -0
- package/dist/subagent/plan.js +157 -0
- package/dist/subagent/plan.js.map +1 -0
- package/dist/subagent/preflight.d.ts +4 -0
- package/dist/subagent/preflight.d.ts.map +1 -0
- package/dist/subagent/preflight.js +108 -0
- package/dist/subagent/preflight.js.map +1 -0
- package/dist/subagent/request.d.ts +4 -0
- package/dist/subagent/request.d.ts.map +1 -0
- package/dist/subagent/request.js +122 -0
- package/dist/subagent/request.js.map +1 -0
- package/dist/subagent/response.d.ts +8 -0
- package/dist/subagent/response.d.ts.map +1 -0
- package/dist/subagent/response.js +155 -0
- package/dist/subagent/response.js.map +1 -0
- package/dist/subagent/tools.d.ts +4 -0
- package/dist/subagent/tools.d.ts.map +1 -0
- package/dist/subagent/tools.js +42 -0
- package/dist/subagent/tools.js.map +1 -0
- package/dist/subagent/types.d.ts +268 -0
- package/dist/subagent/types.d.ts.map +1 -0
- package/dist/subagent/types.js +3 -0
- package/dist/subagent/types.js.map +1 -0
- package/dist/subagent/validation.d.ts +35 -0
- package/dist/subagent/validation.d.ts.map +1 -0
- package/dist/subagent/validation.js +314 -0
- package/dist/subagent/validation.js.map +1 -0
- package/dist/subagent-command.d.ts +4 -0
- package/dist/subagent-command.d.ts.map +1 -0
- package/dist/subagent-command.js +246 -0
- package/dist/subagent-command.js.map +1 -0
- package/dist/subagent-host.d.ts +44 -0
- package/dist/subagent-host.d.ts.map +1 -0
- package/dist/subagent-host.js +292 -0
- package/dist/subagent-host.js.map +1 -0
- package/dist/subagent-profile-tool.d.ts +49 -0
- package/dist/subagent-profile-tool.d.ts.map +1 -0
- package/dist/subagent-profile-tool.js +124 -0
- package/dist/subagent-profile-tool.js.map +1 -0
- package/dist/subagent-tool.d.ts +53 -0
- package/dist/subagent-tool.d.ts.map +1 -0
- package/dist/subagent-tool.js +456 -0
- package/dist/subagent-tool.js.map +1 -0
- package/dist/web-editor/client-script.d.ts +2 -0
- package/dist/web-editor/client-script.d.ts.map +1 -0
- package/dist/web-editor/client-script.generated.d.ts +2 -0
- package/dist/web-editor/client-script.generated.d.ts.map +1 -0
- package/dist/web-editor/client-script.generated.js +3 -0
- package/dist/web-editor/client-script.generated.js.map +1 -0
- package/dist/web-editor/client-script.js +2 -0
- package/dist/web-editor/client-script.js.map +1 -0
- package/dist/web-editor/client-styles.d.ts +2 -0
- package/dist/web-editor/client-styles.d.ts.map +1 -0
- package/dist/web-editor/client-styles.generated.d.ts +2 -0
- package/dist/web-editor/client-styles.generated.d.ts.map +1 -0
- package/dist/web-editor/client-styles.generated.js +3 -0
- package/dist/web-editor/client-styles.generated.js.map +1 -0
- package/dist/web-editor/client-styles.js +2 -0
- package/dist/web-editor/client-styles.js.map +1 -0
- package/dist/web-editor/page.d.ts +2 -0
- package/dist/web-editor/page.d.ts.map +1 -1
- package/dist/web-editor/page.js +14 -3319
- package/dist/web-editor/page.js.map +1 -1
- package/dist/web-editor/server.d.ts.map +1 -1
- package/dist/web-editor/server.js +148 -0
- package/dist/web-editor/server.js.map +1 -1
- package/dist/web-editor/styles.d.ts +2 -0
- package/dist/web-editor/styles.d.ts.map +1 -0
- package/dist/web-editor/styles.js +1053 -0
- package/dist/web-editor/styles.js.map +1 -0
- package/dist/web-editor/types.d.ts +79 -0
- package/dist/web-editor/types.d.ts.map +1 -1
- package/dist/web-host.d.ts +16 -5
- package/dist/web-host.d.ts.map +1 -1
- package/dist/web-host.js +307 -0
- package/dist/web-host.js.map +1 -1
- package/docs/README.md +41 -0
- package/docs/concepts/agent-profiles.md +60 -0
- package/docs/concepts/prompt-stacks.md +90 -0
- package/docs/design/README.md +17 -0
- package/docs/design/roadmap-0.4-archive.md +216 -0
- package/docs/design/subagents/design-review.md +220 -0
- package/docs/design/subagents/interface-design.md +274 -0
- package/docs/design/subagents/sdk-spike-findings.md +117 -0
- package/docs/development/complexity-review.md +86 -0
- package/docs/development/release.md +31 -0
- package/docs/development/roadmap.md +42 -0
- package/docs/development/setup.md +75 -0
- package/docs/getting-started.md +93 -0
- package/docs/guides/custom-macros-and-slots.md +68 -0
- package/docs/guides/debugging.md +39 -0
- package/docs/guides/delegation.md +99 -0
- package/docs/guides/sillytavern-import.md +47 -0
- package/docs/guides/use-cases.md +65 -0
- package/docs/guides/web-editor.md +75 -0
- package/docs/reference/commands.md +60 -0
- package/docs/reference/configuration.md +64 -0
- package/docs/reference/features.md +279 -0
- package/docs/reference/macros-and-slots.md +82 -0
- package/docs/reference/public-api.md +28 -0
- package/docs/reference/stack-schema.md +167 -0
- package/docs/reference/subagent-adapter.md +204 -0
- package/docs/zh-CN/README.md +37 -0
- package/docs/zh-CN/concepts/agent-profiles.md +44 -0
- package/docs/zh-CN/concepts/prompt-stacks.md +40 -0
- package/docs/zh-CN/getting-started.md +79 -0
- package/docs/zh-CN/guides/delegation.md +66 -0
- package/docs/zh-CN/guides/web-editor.md +45 -0
- package/docs/zh-CN/reference/commands.md +58 -0
- package/package.json +55 -13
- package/src/compiler.ts +0 -578
- package/src/extension-registry.ts +0 -33
- package/src/forge-extensions.ts +0 -223
- package/src/index.ts +0 -445
- package/src/lifecycle.ts +0 -171
- package/src/loader.ts +0 -394
- package/src/macro-engine.ts +0 -358
- package/src/payload-capture.ts +0 -85
- package/src/payload-command.ts +0 -138
- package/src/policy.ts +0 -42
- package/src/preset-command.ts +0 -280
- package/src/preview.ts +0 -226
- package/src/regex.ts +0 -500
- package/src/render-helpers.ts +0 -169
- package/src/runtime-state.ts +0 -40
- package/src/sillytavern-importer/items.ts +0 -98
- package/src/sillytavern-importer/macros.ts +0 -159
- package/src/sillytavern-importer/prompt-order.ts +0 -54
- package/src/sillytavern-importer/regex.ts +0 -270
- package/src/sillytavern-importer/report.ts +0 -202
- package/src/sillytavern-importer/types.ts +0 -120
- package/src/sillytavern-importer.ts +0 -152
- package/src/slot-renderers.ts +0 -414
- package/src/stack-migration.ts +0 -159
- package/src/storage.ts +0 -45
- package/src/types.ts +0 -209
- package/src/web-editor/index.ts +0 -2
- package/src/web-editor/page.ts +0 -3330
- package/src/web-editor/server.ts +0 -294
- package/src/web-editor/types.ts +0 -98
- package/src/web-host.ts +0 -232
package/src/slot-renderers.ts
DELETED
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { applyResourcePolicy } from "./policy.ts";
|
|
4
|
-
import type {
|
|
5
|
-
PromptRuntime,
|
|
6
|
-
PromptStack,
|
|
7
|
-
PromptStackDiagnostic,
|
|
8
|
-
PromptStackSlotFormat,
|
|
9
|
-
PromptStackSlotItem,
|
|
10
|
-
PromptVariableValue,
|
|
11
|
-
} from "./types.ts";
|
|
12
|
-
import {
|
|
13
|
-
collectStaticVariables,
|
|
14
|
-
createVariableAccess,
|
|
15
|
-
promptRenderHelpers,
|
|
16
|
-
selectedToolNames,
|
|
17
|
-
selectedVariableScopes,
|
|
18
|
-
type PromptRenderHelpers,
|
|
19
|
-
type PromptVariableAccess,
|
|
20
|
-
type PromptVariableScope,
|
|
21
|
-
} from "./render-helpers.ts";
|
|
22
|
-
import { assertRegistryName, type PromptExtensionOptionsSchema, type PromptRegistryEntry } from "./extension-registry.ts";
|
|
23
|
-
|
|
24
|
-
export interface PromptSlotRenderContext {
|
|
25
|
-
item: PromptStackSlotItem;
|
|
26
|
-
stack: PromptStack;
|
|
27
|
-
runtime: PromptRuntime;
|
|
28
|
-
diagnostics: PromptStackDiagnostic[];
|
|
29
|
-
options: Record<string, unknown>;
|
|
30
|
-
helpers: PromptRenderHelpers;
|
|
31
|
-
variables: PromptVariableAccess;
|
|
32
|
-
format: (options?: { allowJson?: boolean }) => PromptStackSlotFormat;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type PromptSlotRenderer = (context: PromptSlotRenderContext) => string | undefined;
|
|
36
|
-
|
|
37
|
-
export interface PromptSlotDefinition extends PromptRegistryEntry {
|
|
38
|
-
options?: PromptExtensionOptionsSchema;
|
|
39
|
-
render: PromptSlotRenderer;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface PromptSlotRegistryState {
|
|
43
|
-
slots: Map<string, PromptSlotDefinition>;
|
|
44
|
-
supportedSlots: Set<string>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type PromptSlotGlobal = typeof globalThis & {
|
|
48
|
-
__piForgeSlotRegistry?: PromptSlotRegistryState;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
function slotRegistryState(): PromptSlotRegistryState {
|
|
52
|
-
const globalScope = globalThis as PromptSlotGlobal;
|
|
53
|
-
globalScope.__piForgeSlotRegistry ??= { slots: new Map(), supportedSlots: new Set() };
|
|
54
|
-
return globalScope.__piForgeSlotRegistry;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const SLOT_REGISTRY = slotRegistryState();
|
|
58
|
-
const SLOT_RENDERERS = SLOT_REGISTRY.slots;
|
|
59
|
-
export const SUPPORTED_SLOTS = SLOT_REGISTRY.supportedSlots;
|
|
60
|
-
let registeringBuiltInSlots = true;
|
|
61
|
-
|
|
62
|
-
const FORMAT_OPTION = {
|
|
63
|
-
type: "enum",
|
|
64
|
-
values: ["xml", "plain"],
|
|
65
|
-
default: "xml",
|
|
66
|
-
description: "Render as XML-style wrappers or compact plain text.",
|
|
67
|
-
} as const;
|
|
68
|
-
|
|
69
|
-
const INCLUDE_TIME_OPTION = {
|
|
70
|
-
type: "boolean",
|
|
71
|
-
default: false,
|
|
72
|
-
description: "Include Current time: HH:MM:SS after the current date.",
|
|
73
|
-
} as const;
|
|
74
|
-
|
|
75
|
-
registerSlot({ name: "chat-history", description: "Conversation history insertion point.", render: () => "" });
|
|
76
|
-
registerSlot({
|
|
77
|
-
name: "tools",
|
|
78
|
-
description: "Available tools and prompt snippets.",
|
|
79
|
-
options: {
|
|
80
|
-
format: FORMAT_OPTION,
|
|
81
|
-
onlyWithSnippets: { type: "boolean", default: false, description: "Only render tools that provide prompt snippets." },
|
|
82
|
-
},
|
|
83
|
-
render: renderTools,
|
|
84
|
-
});
|
|
85
|
-
registerSlot({
|
|
86
|
-
name: "tool-guidelines",
|
|
87
|
-
description: "Tool usage guidelines.",
|
|
88
|
-
options: {
|
|
89
|
-
format: FORMAT_OPTION,
|
|
90
|
-
heading: { type: "string", default: "Tool guidelines:", description: "Plain-format heading." },
|
|
91
|
-
includePiDefaultGuidelines: { type: "boolean", default: false, description: "Include Pi's default concise/file-path guideline bullets." },
|
|
92
|
-
piStyle: { type: "boolean", default: false, description: "Use Pi-style wording for default bash guidance." },
|
|
93
|
-
},
|
|
94
|
-
render: renderToolGuidelines,
|
|
95
|
-
});
|
|
96
|
-
registerSlot({
|
|
97
|
-
name: "skills",
|
|
98
|
-
description: "Available Pi skills.",
|
|
99
|
-
options: {
|
|
100
|
-
format: FORMAT_OPTION,
|
|
101
|
-
requireReadTool: { type: "boolean", default: false, description: "Omit the section unless the read tool is active." },
|
|
102
|
-
},
|
|
103
|
-
render: renderSkills,
|
|
104
|
-
});
|
|
105
|
-
registerSlot({ name: "project-context", description: "Project context files.", options: { format: FORMAT_OPTION }, render: renderProjectContext });
|
|
106
|
-
registerSlot({ name: "append-system-prompt", description: "User appended system prompt text.", render: ({ runtime }) => runtime.options.appendSystemPrompt ?? "" });
|
|
107
|
-
registerSlot({ name: "date", description: "Current date.", options: { includeTime: INCLUDE_TIME_OPTION }, render: renderDate });
|
|
108
|
-
registerSlot({ name: "cwd", description: "Current working directory.", render: ({ runtime }) => renderCwd(runtime) });
|
|
109
|
-
registerSlot({ name: "date-cwd", description: "Current date and working directory.", options: { includeTime: INCLUDE_TIME_OPTION }, render: (context) => [renderDate(context), renderCwd(context.runtime)].join("\n") });
|
|
110
|
-
registerSlot({ name: "active-model", description: "Current model provider/id.", render: ({ runtime }) => renderActiveModel(runtime) });
|
|
111
|
-
registerSlot({ name: "pi-docs", description: "Pi documentation guidance.", render: () => renderPiDocsGuidance() });
|
|
112
|
-
registerSlot({
|
|
113
|
-
name: "variables",
|
|
114
|
-
description: "Static, session, and turn variables.",
|
|
115
|
-
options: {
|
|
116
|
-
format: FORMAT_OPTION,
|
|
117
|
-
includeStatic: { type: "boolean", default: true, description: "Include static stack variables." },
|
|
118
|
-
includeSession: { type: "boolean", default: true, description: "Include session variables." },
|
|
119
|
-
includeTurn: { type: "boolean", default: true, description: "Include turn variables." },
|
|
120
|
-
},
|
|
121
|
-
render: renderVariables,
|
|
122
|
-
});
|
|
123
|
-
registeringBuiltInSlots = false;
|
|
124
|
-
|
|
125
|
-
export function renderSlotText(
|
|
126
|
-
item: PromptStackSlotItem,
|
|
127
|
-
stack: PromptStack,
|
|
128
|
-
runtime: PromptRuntime,
|
|
129
|
-
diagnostics: PromptStackDiagnostic[],
|
|
130
|
-
): string {
|
|
131
|
-
const definition = SLOT_RENDERERS.get(item.slot);
|
|
132
|
-
if (!definition) {
|
|
133
|
-
diagnostics.push({ level: "warning", message: `Unsupported slot: ${item.slot}`, itemId: item.id });
|
|
134
|
-
return "";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
return definition.render(createSlotRenderContext(item, stack, runtime, diagnostics)) ?? "";
|
|
139
|
-
} catch (error) {
|
|
140
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
141
|
-
diagnostics.push({ level: "error", message: `Slot "${item.slot}" failed: ${detail}`, itemId: item.id });
|
|
142
|
-
return "";
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function registerSlot(definition: PromptSlotDefinition): () => void {
|
|
147
|
-
assertRegistryName("Slot", definition.name);
|
|
148
|
-
if (SLOT_RENDERERS.has(definition.name)) {
|
|
149
|
-
if (registeringBuiltInSlots) return () => {};
|
|
150
|
-
throw new Error(`Slot is already registered: ${definition.name}`);
|
|
151
|
-
}
|
|
152
|
-
SLOT_RENDERERS.set(definition.name, definition);
|
|
153
|
-
SUPPORTED_SLOTS.add(definition.name);
|
|
154
|
-
return () => {
|
|
155
|
-
if (SLOT_RENDERERS.get(definition.name) === definition) {
|
|
156
|
-
SLOT_RENDERERS.delete(definition.name);
|
|
157
|
-
SUPPORTED_SLOTS.delete(definition.name);
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function getRegisteredSlots(): readonly PromptSlotDefinition[] {
|
|
163
|
-
return [...SLOT_RENDERERS.values()];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function createSlotRenderContext(
|
|
167
|
-
item: PromptStackSlotItem,
|
|
168
|
-
stack: PromptStack,
|
|
169
|
-
runtime: PromptRuntime,
|
|
170
|
-
diagnostics: PromptStackDiagnostic[],
|
|
171
|
-
): PromptSlotRenderContext {
|
|
172
|
-
return {
|
|
173
|
-
item,
|
|
174
|
-
stack,
|
|
175
|
-
runtime,
|
|
176
|
-
diagnostics,
|
|
177
|
-
options: item.options ?? {},
|
|
178
|
-
helpers: promptRenderHelpers,
|
|
179
|
-
variables: createVariableAccess(runtime, stack),
|
|
180
|
-
format: (options) => promptRenderHelpers.slotTextFormat(item, options),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function renderTools({ item, stack, runtime, format, helpers }: PromptSlotRenderContext): string {
|
|
185
|
-
const snippets = runtime.options.toolSnippets ?? {};
|
|
186
|
-
const tools = item.options?.onlyWithSnippets === true
|
|
187
|
-
? selectedToolNames(stack, runtime).filter((name) => !!snippets[name])
|
|
188
|
-
: selectedToolNames(stack, runtime);
|
|
189
|
-
|
|
190
|
-
if (format() === "plain") {
|
|
191
|
-
const lines = ["Available tools:"];
|
|
192
|
-
if (tools.length === 0) {
|
|
193
|
-
lines.push(item.options?.onlyWithSnippets === true ? "(none)" : "- (none)");
|
|
194
|
-
} else {
|
|
195
|
-
for (const name of tools) {
|
|
196
|
-
lines.push(helpers.plainBullet(name, snippets[name] ?? "No prompt snippet provided."));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return lines.join("\n");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const lines = ["<available_tools>"];
|
|
203
|
-
|
|
204
|
-
if (tools.length === 0) {
|
|
205
|
-
lines.push(" (none)");
|
|
206
|
-
} else {
|
|
207
|
-
for (const name of tools) {
|
|
208
|
-
const snippet = snippets[name] ?? "No prompt snippet provided.";
|
|
209
|
-
lines.push(` <tool name=\"${helpers.escapeXml(name)}\">${helpers.escapeXml(snippet)}</tool>`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
lines.push("</available_tools>");
|
|
214
|
-
return lines.join("\n");
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function renderToolGuidelines({ item, stack, runtime, format, helpers }: PromptSlotRenderContext): string {
|
|
218
|
-
const tools = selectedToolNames(stack, runtime);
|
|
219
|
-
const guidelines: string[] = [];
|
|
220
|
-
const seen = new Set<string>();
|
|
221
|
-
const add = (line: string) => {
|
|
222
|
-
const normalized = line.trim();
|
|
223
|
-
if (!normalized || seen.has(normalized)) return;
|
|
224
|
-
seen.add(normalized);
|
|
225
|
-
guidelines.push(normalized);
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
if (tools.includes("bash") && !tools.includes("grep") && !tools.includes("find") && !tools.includes("ls")) {
|
|
229
|
-
add(item.options?.piStyle === true ? "Use bash for file operations like ls, rg, find" : "Use bash for file operations like ls, rg, find.");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
for (const guideline of runtime.options.promptGuidelines ?? []) add(guideline);
|
|
233
|
-
if (item.options?.includePiDefaultGuidelines === true) {
|
|
234
|
-
add("Be concise in your responses");
|
|
235
|
-
add("Show file paths clearly when working with files");
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (guidelines.length === 0) return "";
|
|
239
|
-
if (format() === "plain") {
|
|
240
|
-
const heading = typeof item.options?.heading === "string" ? item.options.heading.trim() : "Tool guidelines:";
|
|
241
|
-
return [
|
|
242
|
-
...(heading ? [heading] : []),
|
|
243
|
-
...guidelines.map((line) => `- ${helpers.plainContinuation(line, " ")}`),
|
|
244
|
-
].join("\n");
|
|
245
|
-
}
|
|
246
|
-
return ["<tool_guidelines>", ...guidelines.map((line) => `- ${line}`), "</tool_guidelines>"].join("\n");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function renderSkills({ item, stack, runtime, format, helpers }: PromptSlotRenderContext): string {
|
|
250
|
-
if (item.options?.requireReadTool === true && !selectedToolNames(stack, runtime).includes("read")) return "";
|
|
251
|
-
const skills = (runtime.options.skills ?? [])
|
|
252
|
-
.filter((skill) => !skill.disableModelInvocation)
|
|
253
|
-
.filter((skill) => applyResourcePolicy([skill.name], stack.skills).length > 0);
|
|
254
|
-
if (skills.length === 0) return "";
|
|
255
|
-
|
|
256
|
-
const lines = [
|
|
257
|
-
"The following skills provide specialized instructions for specific tasks.",
|
|
258
|
-
"Use the read tool to load a skill's file when the task matches its description.",
|
|
259
|
-
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
|
260
|
-
"",
|
|
261
|
-
];
|
|
262
|
-
|
|
263
|
-
if (format() === "plain") {
|
|
264
|
-
lines.push("Available skills:");
|
|
265
|
-
for (const skill of skills) {
|
|
266
|
-
lines.push(helpers.plainBullet(skill.name, skill.description));
|
|
267
|
-
lines.push(` Location: ${helpers.plainContinuation(skill.filePath, " ")}`);
|
|
268
|
-
}
|
|
269
|
-
return lines.join("\n");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
lines.push("<available_skills>");
|
|
273
|
-
for (const skill of skills) {
|
|
274
|
-
lines.push(" <skill>");
|
|
275
|
-
lines.push(` <name>${helpers.escapeXml(skill.name)}</name>`);
|
|
276
|
-
lines.push(` <description>${helpers.escapeXml(skill.description)}</description>`);
|
|
277
|
-
lines.push(` <location>${helpers.escapeXml(skill.filePath)}</location>`);
|
|
278
|
-
lines.push(" </skill>");
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
lines.push("</available_skills>");
|
|
282
|
-
return lines.join("\n");
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function renderProjectContext({ item, runtime, format, helpers }: PromptSlotRenderContext): string {
|
|
286
|
-
const contextFiles = runtime.options.contextFiles ?? [];
|
|
287
|
-
if (contextFiles.length === 0) return "";
|
|
288
|
-
|
|
289
|
-
if (format() === "plain") {
|
|
290
|
-
const lines = ["Project context:", "", "Project-specific instructions and guidelines:", ""];
|
|
291
|
-
for (const file of contextFiles) {
|
|
292
|
-
lines.push(`Path: ${file.path}`);
|
|
293
|
-
lines.push(helpers.indentPlainBlock(file.content, " "), "");
|
|
294
|
-
}
|
|
295
|
-
return lines.join("\n").trimEnd();
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const lines = ["<project_context>", "", "Project-specific instructions and guidelines:", ""];
|
|
299
|
-
for (const file of contextFiles) {
|
|
300
|
-
lines.push(`<project_instructions path=\"${helpers.escapeXml(file.path)}\">`);
|
|
301
|
-
lines.push(file.content);
|
|
302
|
-
lines.push("</project_instructions>", "");
|
|
303
|
-
}
|
|
304
|
-
lines.push("</project_context>");
|
|
305
|
-
return lines.join("\n");
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function renderDate({ item, runtime, helpers }: PromptSlotRenderContext): string {
|
|
309
|
-
const lines = [`Current date: ${helpers.formatDate(runtime.now)}`];
|
|
310
|
-
if (item.options?.includeTime === true) {
|
|
311
|
-
lines.push(`Current time: ${helpers.formatTime(runtime.now)}`);
|
|
312
|
-
}
|
|
313
|
-
return lines.join("\n");
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function renderCwd(runtime: PromptRuntime): string {
|
|
317
|
-
return `Current working directory: ${promptRenderHelpers.normalizePath(runtime.options.cwd)}`;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function renderActiveModel(runtime: PromptRuntime): string {
|
|
321
|
-
const model = runtime.ctx?.model;
|
|
322
|
-
return model ? `Current model: ${model.provider}/${model.id}` : "Current model: (none)";
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function renderPiDocsGuidance(): string {
|
|
326
|
-
const paths = piDocsPaths();
|
|
327
|
-
return [
|
|
328
|
-
"Pi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):",
|
|
329
|
-
`- Main documentation: ${paths.readme}`,
|
|
330
|
-
`- Additional docs: ${paths.docs}`,
|
|
331
|
-
`- Examples: ${paths.examples} (extensions, custom tools, SDK)`,
|
|
332
|
-
"- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory",
|
|
333
|
-
"- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)",
|
|
334
|
-
"- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing",
|
|
335
|
-
"- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)",
|
|
336
|
-
].join("\n");
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function piDocsPaths(): { readme: string; docs: string; examples: string } {
|
|
340
|
-
const fallbackRoot = "@earendil-works/pi-coding-agent";
|
|
341
|
-
try {
|
|
342
|
-
const resolve = (import.meta as unknown as { resolve?: (specifier: string) => string }).resolve;
|
|
343
|
-
if (!resolve) throw new Error("import.meta.resolve unavailable");
|
|
344
|
-
const resolved = resolve("@earendil-works/pi-coding-agent");
|
|
345
|
-
if (!resolved.startsWith("file:")) throw new Error("non-file package resolution");
|
|
346
|
-
const packageRoot = dirname(dirname(fileURLToPath(resolved)));
|
|
347
|
-
return {
|
|
348
|
-
readme: join(packageRoot, "README.md"),
|
|
349
|
-
docs: join(packageRoot, "docs"),
|
|
350
|
-
examples: join(packageRoot, "examples"),
|
|
351
|
-
};
|
|
352
|
-
} catch {
|
|
353
|
-
return {
|
|
354
|
-
readme: `${fallbackRoot}/README.md`,
|
|
355
|
-
docs: `${fallbackRoot}/docs`,
|
|
356
|
-
examples: `${fallbackRoot}/examples`,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function renderVariables({ stack, runtime, options, format, variables, helpers }: PromptSlotRenderContext): string {
|
|
362
|
-
const scopes = selectedVariableScopes(options);
|
|
363
|
-
const store = runtime.variables;
|
|
364
|
-
const grouped: Record<PromptVariableScope, Record<string, PromptVariableValue>> = {
|
|
365
|
-
static: {},
|
|
366
|
-
session: {},
|
|
367
|
-
turn: {},
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
if (scopes.includes("static")) grouped.static = collectStaticVariables(stack);
|
|
371
|
-
if (scopes.includes("session")) grouped.session = { ...(store?.session ?? {}) };
|
|
372
|
-
if (scopes.includes("turn")) grouped.turn = { ...(store?.turn ?? {}) };
|
|
373
|
-
|
|
374
|
-
const hasVariables = Object.values(grouped).some((values) => Object.keys(values).length > 0);
|
|
375
|
-
if (!hasVariables) return "";
|
|
376
|
-
|
|
377
|
-
if (format() === "plain") {
|
|
378
|
-
return renderPlainVariables(grouped, variables, helpers);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const parts: string[] = ["<variables>"];
|
|
382
|
-
|
|
383
|
-
for (const scope of scopes) {
|
|
384
|
-
const entries = Object.entries(grouped[scope]).sort(([a], [b]) => a.localeCompare(b));
|
|
385
|
-
if (entries.length === 0) continue;
|
|
386
|
-
parts.push(` <${scope}>`);
|
|
387
|
-
for (const [name, value] of entries) {
|
|
388
|
-
parts.push(` <var name=\"${helpers.escapeXml(name)}\">${helpers.escapeXml(variables.toPromptText(value))}</var>`);
|
|
389
|
-
}
|
|
390
|
-
parts.push(` </${scope}>`);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
parts.push("</variables>");
|
|
394
|
-
return parts.join("\n");
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function renderPlainVariables(
|
|
398
|
-
grouped: Record<PromptVariableScope, Record<string, PromptVariableValue>>,
|
|
399
|
-
variables: PromptVariableAccess,
|
|
400
|
-
helpers: PromptRenderHelpers,
|
|
401
|
-
): string {
|
|
402
|
-
const parts: string[] = ["Variables:"];
|
|
403
|
-
|
|
404
|
-
for (const scope of ["static", "session", "turn"] as const) {
|
|
405
|
-
const entries = Object.entries(grouped[scope]).sort(([a], [b]) => a.localeCompare(b));
|
|
406
|
-
if (entries.length === 0) continue;
|
|
407
|
-
parts.push(`${scope}:`);
|
|
408
|
-
for (const [name, value] of entries) {
|
|
409
|
-
parts.push(helpers.plainBullet(name, variables.toPromptText(value)));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return parts.join("\n");
|
|
414
|
-
}
|
package/src/stack-migration.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { legacyPromptStacksDir, promptStacksDir } from "./storage.ts";
|
|
4
|
-
|
|
5
|
-
export interface PromptStackMigrationOptions {
|
|
6
|
-
dryRun?: boolean;
|
|
7
|
-
overwrite?: boolean;
|
|
8
|
-
deleteLegacy?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface PromptStackMigrationFile {
|
|
12
|
-
name: string;
|
|
13
|
-
sourcePath: string;
|
|
14
|
-
targetPath: string;
|
|
15
|
-
action: "copy" | "overwrite" | "skip" | "error";
|
|
16
|
-
reason?: string;
|
|
17
|
-
deleteLegacy: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface PromptStackMigrationReport {
|
|
21
|
-
sourceDir: string;
|
|
22
|
-
targetDir: string;
|
|
23
|
-
dryRun: boolean;
|
|
24
|
-
overwrite: boolean;
|
|
25
|
-
deleteLegacy: boolean;
|
|
26
|
-
files: PromptStackMigrationFile[];
|
|
27
|
-
copied: number;
|
|
28
|
-
overwritten: number;
|
|
29
|
-
skipped: number;
|
|
30
|
-
errors: number;
|
|
31
|
-
deletedLegacy: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function migrateLegacyPromptStacks(cwd: string, options: PromptStackMigrationOptions = {}): PromptStackMigrationReport {
|
|
35
|
-
const sourceDir = legacyPromptStacksDir(cwd);
|
|
36
|
-
const targetDir = promptStacksDir(cwd);
|
|
37
|
-
const report: PromptStackMigrationReport = {
|
|
38
|
-
sourceDir,
|
|
39
|
-
targetDir,
|
|
40
|
-
dryRun: options.dryRun === true,
|
|
41
|
-
overwrite: options.overwrite === true,
|
|
42
|
-
deleteLegacy: options.deleteLegacy === true,
|
|
43
|
-
files: [],
|
|
44
|
-
copied: 0,
|
|
45
|
-
overwritten: 0,
|
|
46
|
-
skipped: 0,
|
|
47
|
-
errors: 0,
|
|
48
|
-
deletedLegacy: 0,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if (!existsSync(sourceDir)) return report;
|
|
52
|
-
|
|
53
|
-
let entries: string[];
|
|
54
|
-
try {
|
|
55
|
-
entries = readdirSync(sourceDir).filter((name) => name.endsWith(".json")).sort();
|
|
56
|
-
} catch (error) {
|
|
57
|
-
report.files.push({
|
|
58
|
-
name: "(legacy directory)",
|
|
59
|
-
sourcePath: sourceDir,
|
|
60
|
-
targetPath: targetDir,
|
|
61
|
-
action: "error",
|
|
62
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
63
|
-
deleteLegacy: false,
|
|
64
|
-
});
|
|
65
|
-
report.errors++;
|
|
66
|
-
return report;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
for (const name of entries) {
|
|
70
|
-
const sourcePath = join(sourceDir, name);
|
|
71
|
-
const targetPath = join(targetDir, name);
|
|
72
|
-
const targetExists = existsSync(targetPath);
|
|
73
|
-
if (targetExists && !report.overwrite) {
|
|
74
|
-
report.files.push({
|
|
75
|
-
name,
|
|
76
|
-
sourcePath,
|
|
77
|
-
targetPath,
|
|
78
|
-
action: "skip",
|
|
79
|
-
reason: "target already exists",
|
|
80
|
-
deleteLegacy: false,
|
|
81
|
-
});
|
|
82
|
-
report.skipped++;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const action = targetExists ? "overwrite" : "copy";
|
|
87
|
-
try {
|
|
88
|
-
if (!report.dryRun) {
|
|
89
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
90
|
-
copyFileSync(sourcePath, targetPath);
|
|
91
|
-
if (report.deleteLegacy) {
|
|
92
|
-
unlinkSync(sourcePath);
|
|
93
|
-
report.deletedLegacy++;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (action === "overwrite") report.overwritten++;
|
|
97
|
-
else report.copied++;
|
|
98
|
-
report.files.push({
|
|
99
|
-
name,
|
|
100
|
-
sourcePath,
|
|
101
|
-
targetPath,
|
|
102
|
-
action,
|
|
103
|
-
deleteLegacy: report.deleteLegacy,
|
|
104
|
-
});
|
|
105
|
-
} catch (error) {
|
|
106
|
-
report.files.push({
|
|
107
|
-
name,
|
|
108
|
-
sourcePath,
|
|
109
|
-
targetPath,
|
|
110
|
-
action: "error",
|
|
111
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
112
|
-
deleteLegacy: false,
|
|
113
|
-
});
|
|
114
|
-
report.errors++;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return report;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function renderMigrationReport(report: PromptStackMigrationReport): string {
|
|
122
|
-
const lines = [
|
|
123
|
-
"# Prompt Stack Migration",
|
|
124
|
-
"",
|
|
125
|
-
`Source: ${report.sourceDir}`,
|
|
126
|
-
`Target: ${report.targetDir}`,
|
|
127
|
-
`Mode: ${report.dryRun ? "dry run" : "write"}`,
|
|
128
|
-
`Overwrite existing target files: ${report.overwrite ? "yes" : "no"}`,
|
|
129
|
-
`Delete legacy files after copy: ${report.deleteLegacy ? "yes" : "no"}`,
|
|
130
|
-
"",
|
|
131
|
-
`Copied: ${report.copied}`,
|
|
132
|
-
`Overwritten: ${report.overwritten}`,
|
|
133
|
-
`Skipped: ${report.skipped}`,
|
|
134
|
-
`Errors: ${report.errors}`,
|
|
135
|
-
`Deleted legacy files: ${report.deletedLegacy}`,
|
|
136
|
-
"",
|
|
137
|
-
"## Files",
|
|
138
|
-
"",
|
|
139
|
-
];
|
|
140
|
-
|
|
141
|
-
if (report.files.length === 0) {
|
|
142
|
-
lines.push("No legacy prompt-stack JSON files found.");
|
|
143
|
-
return lines.join("\n");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
for (const file of report.files) {
|
|
147
|
-
const suffix = file.reason ? ` (${file.reason})` : "";
|
|
148
|
-
lines.push(`- ${file.action}: ${file.name}${suffix}`);
|
|
149
|
-
lines.push(` from: ${file.sourcePath}`);
|
|
150
|
-
lines.push(` to: ${file.targetPath}`);
|
|
151
|
-
if (file.deleteLegacy) lines.push(" legacy delete: yes");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!report.deleteLegacy && !report.dryRun && (report.copied || report.overwritten)) {
|
|
155
|
-
lines.push("", "Legacy files were left in place. Re-run with `--delete-legacy` after checking the migrated stacks if you want to remove them.");
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return lines.join("\n");
|
|
159
|
-
}
|
package/src/storage.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
export function promptStacksDir(cwd: string): string {
|
|
5
|
-
return join(cwd, ".pi", "forge", "prompt-stacks");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function forgeDir(cwd: string): string {
|
|
9
|
-
return join(cwd, ".pi", "forge");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function forgeExtensionsDir(cwd: string): string {
|
|
13
|
-
return join(forgeDir(cwd), "extensions");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function globalForgeDir(): string {
|
|
17
|
-
return join(homedir(), ".pi", "forge");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function globalForgeExtensionsDir(): string {
|
|
21
|
-
return join(globalForgeDir(), "extensions");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function legacyPromptStacksDir(cwd: string): string {
|
|
25
|
-
return join(cwd, ".pi", "prompt-stacks");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function promptStackReadDirs(cwd: string): string[] {
|
|
29
|
-
return [promptStacksDir(cwd), legacyPromptStacksDir(cwd)];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function promptStackPath(cwd: string, id: string): string {
|
|
33
|
-
return join(promptStacksDir(cwd), `${id}.json`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function isInsidePromptStackStorage(cwd: string, filePath: string): boolean {
|
|
37
|
-
return promptStackReadDirs(cwd).some((dir) => isInsideDir(dir, filePath));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function isInsideDir(rootDir: string, filePath: string): boolean {
|
|
41
|
-
const root = resolve(rootDir);
|
|
42
|
-
const target = resolve(filePath);
|
|
43
|
-
const rel = relative(root, target);
|
|
44
|
-
return !!rel && !rel.startsWith("..") && !isAbsolute(rel);
|
|
45
|
-
}
|