@zihanw/pi-forge 0.3.1 → 0.4.0-beta.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 +160 -0
- package/PUBLIC_API.md +28 -0
- package/README.md +168 -35
- package/README.zh-CN.md +168 -35
- package/SUBAGENT_ADAPTER_CONTRACT.md +199 -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 +8 -0
- package/dist/forge-config.d.ts.map +1 -0
- package/dist/forge-config.js +40 -0
- package/dist/forge-config.js.map +1 -0
- package/dist/forge-extensions.d.ts +28 -0
- package/dist/forge-extensions.d.ts.map +1 -0
- package/dist/forge-extensions.js +198 -0
- package/dist/forge-extensions.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -349
- 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 +92 -6
- 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/preset-command.d.ts +1 -1
- package/dist/preset-command.d.ts.map +1 -1
- package/dist/preset-command.js +12 -3
- package/dist/preset-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 +215 -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 +30 -0
- package/dist/runtime/subagent-runtime.d.ts.map +1 -0
- package/dist/runtime/subagent-runtime.js +114 -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 +6 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +4 -0
- package/dist/runtime-state.js.map +1 -1
- package/dist/storage.d.ts +7 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +22 -0
- package/dist/storage.js.map +1 -1
- package/dist/subagent/backend-registry.d.ts +75 -0
- package/dist/subagent/backend-registry.d.ts.map +1 -0
- package/dist/subagent/backend-registry.js +463 -0
- package/dist/subagent/backend-registry.js.map +1 -0
- package/dist/subagent/canonical.d.ts +10 -0
- package/dist/subagent/canonical.d.ts.map +1 -0
- package/dist/subagent/canonical.js +52 -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 +11 -0
- package/dist/subagent/contract.d.ts.map +1 -0
- package/dist/subagent/contract.js +11 -0
- package/dist/subagent/contract.js.map +1 -0
- package/dist/subagent/diagnostics.d.ts +3 -0
- package/dist/subagent/diagnostics.d.ts.map +1 -0
- package/dist/subagent/diagnostics.js +5 -0
- package/dist/subagent/diagnostics.js.map +1 -0
- package/dist/subagent/index.d.ts +13 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +13 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/pi-model-runtime.d.ts +8 -0
- package/dist/subagent/pi-model-runtime.d.ts.map +1 -0
- package/dist/subagent/pi-model-runtime.js +22 -0
- package/dist/subagent/pi-model-runtime.js.map +1 -0
- package/dist/subagent/pi-sdk-backend.d.ts +23 -0
- package/dist/subagent/pi-sdk-backend.d.ts.map +1 -0
- package/dist/subagent/pi-sdk-backend.js +383 -0
- package/dist/subagent/pi-sdk-backend.js.map +1 -0
- package/dist/subagent/pi-subprocess-backend.d.ts +72 -0
- package/dist/subagent/pi-subprocess-backend.d.ts.map +1 -0
- package/dist/subagent/pi-subprocess-backend.js +756 -0
- package/dist/subagent/pi-subprocess-backend.js.map +1 -0
- package/dist/subagent/plan.d.ts +14 -0
- package/dist/subagent/plan.d.ts.map +1 -0
- package/dist/subagent/plan.js +160 -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/subprocess-bridge.d.ts +21 -0
- package/dist/subagent/subprocess-bridge.d.ts.map +1 -0
- package/dist/subagent/subprocess-bridge.js +87 -0
- package/dist/subagent/subprocess-bridge.js.map +1 -0
- package/dist/subagent/subprocess-report.d.ts +4 -0
- package/dist/subagent/subprocess-report.d.ts.map +1 -0
- package/dist/subagent/subprocess-report.js +55 -0
- package/dist/subagent/subprocess-report.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 +384 -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 +500 -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 +153 -0
- package/dist/subagent-command.js.map +1 -0
- package/dist/subagent-contract.d.ts +8 -0
- package/dist/subagent-contract.d.ts.map +1 -0
- package/dist/subagent-contract.js +8 -0
- package/dist/subagent-contract.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 +291 -0
- package/dist/subagent-host.js.map +1 -0
- package/dist/subagent-profile-tool.d.ts +26 -0
- package/dist/subagent-profile-tool.d.ts.map +1 -0
- package/dist/subagent-profile-tool.js +93 -0
- package/dist/subagent-profile-tool.js.map +1 -0
- package/dist/subagent-tool.d.ts +50 -0
- package/dist/subagent-tool.d.ts.map +1 -0
- package/dist/subagent-tool.js +385 -0
- package/dist/subagent-tool.js.map +1 -0
- package/dist/web-editor/client/api.d.ts +9 -0
- package/dist/web-editor/client/api.d.ts.map +1 -0
- package/dist/web-editor/client/api.js +26 -0
- package/dist/web-editor/client/api.js.map +1 -0
- package/dist/web-editor/client/dom.d.ts +13 -0
- package/dist/web-editor/client/dom.d.ts.map +1 -0
- package/dist/web-editor/client/dom.js +30 -0
- package/dist/web-editor/client/dom.js.map +1 -0
- package/dist/web-editor/client/inspector.d.ts +22 -0
- package/dist/web-editor/client/inspector.d.ts.map +1 -0
- package/dist/web-editor/client/inspector.js +226 -0
- package/dist/web-editor/client/inspector.js.map +1 -0
- package/dist/web-editor/client/main.d.ts +2 -0
- package/dist/web-editor/client/main.d.ts.map +1 -0
- package/dist/web-editor/client/main.js +1468 -0
- package/dist/web-editor/client/main.js.map +1 -0
- package/dist/web-editor/client/policy-editor.d.ts +16 -0
- package/dist/web-editor/client/policy-editor.d.ts.map +1 -0
- package/dist/web-editor/client/policy-editor.js +330 -0
- package/dist/web-editor/client/policy-editor.js.map +1 -0
- package/dist/web-editor/client/regex-editor.d.ts +19 -0
- package/dist/web-editor/client/regex-editor.d.ts.map +1 -0
- package/dist/web-editor/client/regex-editor.js +281 -0
- package/dist/web-editor/client/regex-editor.js.map +1 -0
- package/dist/web-editor/client/types.d.ts +60 -0
- package/dist/web-editor/client/types.d.ts.map +1 -0
- package/dist/web-editor/client/types.js +2 -0
- package/dist/web-editor/client/types.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/page.d.ts.map +1 -1
- package/dist/web-editor/page.js +6 -3249
- package/dist/web-editor/page.js.map +1 -1
- package/dist/web-editor/server.js +4 -4
- 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 +996 -0
- package/dist/web-editor/styles.js.map +1 -0
- package/dist/web-editor/types.d.ts +8 -8
- package/dist/web-editor/types.d.ts.map +1 -1
- package/dist/web-host.d.ts +4 -4
- package/dist/web-host.d.ts.map +1 -1
- package/dist/web-host.js +15 -9
- package/dist/web-host.js.map +1 -1
- package/examples/custom-system-status-extension/README.md +34 -27
- package/examples/custom-system-status-extension/index.ts +71 -111
- package/examples/custom-system-status-extension/prompt-stack.json +1 -1
- package/package.json +42 -14
- package/src/compiler.ts +0 -578
- package/src/extension-registry.ts +0 -33
- package/src/index.ts +0 -430
- 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 -271
- 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 -36
- 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 -28
- 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/index.ts
DELETED
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
import type { BuildSystemPromptOptions, ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { registerLifecycleHandlers } from "./lifecycle.ts";
|
|
3
|
-
import { chooseDefaultStack, isDisabledPromptStackId, loadPromptStacks } from "./loader.ts";
|
|
4
|
-
import { registerPayloadCommands, registerPayloadRequestHandler, armPayloadIntercept, clearPayloadCapture, webPayloadSnapshot } from "./payload-command.ts";
|
|
5
|
-
import { applyResourcePolicy, hasResourcePolicy } from "./policy.ts";
|
|
6
|
-
import { buildPreview, showText } from "./preview.ts";
|
|
7
|
-
import { registerPresetCommand, selectedActiveId as selectedActiveIdForState } from "./preset-command.ts";
|
|
8
|
-
import { createRuntimeState, STATE_ENTRY_TYPE } from "./runtime-state.ts";
|
|
9
|
-
import type { PromptStack, PromptStackDiagnostic } from "./types.ts";
|
|
10
|
-
import { createWebEditorHost, loadWebEditorSettings, type WebHostRuntime } from "./web-host.ts";
|
|
11
|
-
import { startWebEditorServer, type WebEditorPolicyResource, type WebEditorPolicyResources, type WebEditorServer } from "./web-editor/index.ts";
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
getRegisteredMacros,
|
|
15
|
-
registerMacro,
|
|
16
|
-
type PromptMacroDefinition,
|
|
17
|
-
type PromptMacroRenderContext,
|
|
18
|
-
type PromptMacroRenderer,
|
|
19
|
-
} from "./macro-engine.ts";
|
|
20
|
-
export {
|
|
21
|
-
getRegisteredSlots,
|
|
22
|
-
registerSlot,
|
|
23
|
-
type PromptSlotDefinition,
|
|
24
|
-
type PromptSlotRenderContext,
|
|
25
|
-
type PromptSlotRenderer,
|
|
26
|
-
} from "./slot-renderers.ts";
|
|
27
|
-
export {
|
|
28
|
-
type PromptExtensionArgumentDefinition,
|
|
29
|
-
type PromptExtensionOptionDefinition,
|
|
30
|
-
type PromptExtensionOptionsSchema,
|
|
31
|
-
type PromptExtensionOptionType,
|
|
32
|
-
type PromptRegistryEntry,
|
|
33
|
-
} from "./extension-registry.ts";
|
|
34
|
-
export {
|
|
35
|
-
createVariableAccess,
|
|
36
|
-
promptRenderHelpers,
|
|
37
|
-
type PromptRenderHelpers,
|
|
38
|
-
type PromptVariableAccess,
|
|
39
|
-
type PromptVariableScope,
|
|
40
|
-
type PromptWritableVariableScope,
|
|
41
|
-
} from "./render-helpers.ts";
|
|
42
|
-
|
|
43
|
-
const WEB_EDITOR_GLOBAL_KEY = "__piForgeWebEditor";
|
|
44
|
-
|
|
45
|
-
interface SharedWebEditorState {
|
|
46
|
-
server?: WebEditorServer;
|
|
47
|
-
cwd?: string;
|
|
48
|
-
preferredPort?: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface SharedWebEditorRegistry {
|
|
52
|
-
byCwd: Record<string, SharedWebEditorState | undefined>;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
type PiForgeGlobal = typeof globalThis & {
|
|
56
|
-
__piForgeWebEditor?: SharedWebEditorRegistry;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
function getSharedWebEditorRegistry(): SharedWebEditorRegistry {
|
|
60
|
-
const globalScope = globalThis as PiForgeGlobal;
|
|
61
|
-
globalScope[WEB_EDITOR_GLOBAL_KEY] ??= { byCwd: {} };
|
|
62
|
-
return globalScope[WEB_EDITOR_GLOBAL_KEY];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export default function piForge(pi: ExtensionAPI) {
|
|
66
|
-
const sharedWebEditors = getSharedWebEditorRegistry();
|
|
67
|
-
const state = createRuntimeState();
|
|
68
|
-
let webEditor: WebEditorServer | undefined;
|
|
69
|
-
let webEditorCwd: string | undefined;
|
|
70
|
-
let webEditorPreferredPort: number | undefined;
|
|
71
|
-
let toolPolicyBaseline: string[] | undefined;
|
|
72
|
-
|
|
73
|
-
function activeId(): string | undefined {
|
|
74
|
-
return state.active?.stack.id;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function selectedActiveId(): string | undefined {
|
|
78
|
-
return selectedActiveIdForState(state);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function persistActiveSelection(id: string): void {
|
|
82
|
-
if (id === state.lastPersistedActiveId) return;
|
|
83
|
-
pi.appendEntry(STATE_ENTRY_TYPE, { activeStackId: id });
|
|
84
|
-
state.lastPersistedActiveId = id;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function setActive(id: string | undefined, ctx?: ExtensionContext): boolean {
|
|
88
|
-
if (!id || isDisabledPromptStackId(id)) {
|
|
89
|
-
state.active = undefined;
|
|
90
|
-
if (id) persistActiveSelection("none");
|
|
91
|
-
if (ctx) updateStatus(ctx);
|
|
92
|
-
syncActiveToolPolicy(ctx);
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const found = state.stacks.find((candidate) => candidate.stack.id === id);
|
|
97
|
-
if (!found) return false;
|
|
98
|
-
state.active = found;
|
|
99
|
-
persistActiveSelection(found.stack.id);
|
|
100
|
-
if (ctx) updateStatus(ctx);
|
|
101
|
-
syncActiveToolPolicy(ctx);
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function reloadStacks(ctx: ExtensionContext, preferredId?: string): void {
|
|
106
|
-
if (!ctx.isProjectTrusted()) {
|
|
107
|
-
state.stacks = [];
|
|
108
|
-
state.active = undefined;
|
|
109
|
-
syncActiveToolPolicy(ctx);
|
|
110
|
-
ctx.ui.notify("pi-forge: project is not trusted; prompt stacks are disabled.", "warning");
|
|
111
|
-
updateStatus(ctx);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
state.stacks = loadPromptStacks(ctx.cwd);
|
|
116
|
-
state.active = chooseDefaultStack(state.stacks, preferredId);
|
|
117
|
-
updateStatus(ctx);
|
|
118
|
-
syncActiveToolPolicy(ctx);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function updateStatus(ctx: ExtensionContext): void {
|
|
122
|
-
if (state.active) {
|
|
123
|
-
ctx.ui.setStatus("pi-forge", ctx.ui.theme.fg("accent", "stack:" + state.active.stack.id));
|
|
124
|
-
} else {
|
|
125
|
-
ctx.ui.setStatus("pi-forge", undefined);
|
|
126
|
-
state.latestCompileDiagnostics = [];
|
|
127
|
-
ctx.ui.setStatus("pi-forge-diagnostics", undefined);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function syncActiveToolPolicy(ctx?: ExtensionContext): void {
|
|
132
|
-
const policy = state.active?.stack.tools;
|
|
133
|
-
if (!hasResourcePolicy(policy)) {
|
|
134
|
-
restoreToolPolicy(ctx);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const baseline = toolPolicyBaseline ?? pi.getActiveTools();
|
|
139
|
-
toolPolicyBaseline ??= [...baseline];
|
|
140
|
-
const nextTools = applyResourcePolicy(filterKnownTools(baseline), policy);
|
|
141
|
-
pi.setActiveTools(nextTools);
|
|
142
|
-
if (ctx) {
|
|
143
|
-
const label = nextTools.length > 0 ? `tools:${nextTools.length}` : "tools:none";
|
|
144
|
-
ctx.ui.setStatus("pi-forge-tools", ctx.ui.theme.fg(nextTools.length > 0 ? "accent" : "warning", label));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function restoreToolPolicy(ctx?: ExtensionContext): void {
|
|
149
|
-
if (toolPolicyBaseline) {
|
|
150
|
-
pi.setActiveTools(filterKnownTools(toolPolicyBaseline));
|
|
151
|
-
toolPolicyBaseline = undefined;
|
|
152
|
-
}
|
|
153
|
-
if (ctx) ctx.ui.setStatus("pi-forge-tools", undefined);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function filterKnownTools(names: string[]): string[] {
|
|
157
|
-
const known = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
158
|
-
if (known.size === 0) return names;
|
|
159
|
-
return names.filter((name) => known.has(name));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function notifyActivePreset(ctx: ExtensionContext, detail: string): void {
|
|
163
|
-
if (!state.active) return;
|
|
164
|
-
const errorCount = state.active.diagnostics.filter((d) => d.level === "error").length;
|
|
165
|
-
const warningCount = state.active.diagnostics.filter((d) => d.level === "warning").length;
|
|
166
|
-
const suffix = errorCount || warningCount ? " (" + errorCount + " errors, " + warningCount + " warnings)" : "";
|
|
167
|
-
ctx.ui.notify("pi-forge: active preset " + state.active.stack.id + suffix + " (" + detail + ")", errorCount ? "error" : "info");
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function recordCompileDiagnostics(ctx: ExtensionContext, diagnostics: PromptStackDiagnostic[]): void {
|
|
171
|
-
state.latestCompileDiagnostics = diagnostics;
|
|
172
|
-
const errors = diagnostics.filter((d) => d.level === "error").length;
|
|
173
|
-
const warnings = diagnostics.filter((d) => d.level === "warning").length;
|
|
174
|
-
if (errors || warnings) {
|
|
175
|
-
ctx.ui.setStatus("pi-forge-diagnostics", ctx.ui.theme.fg(errors ? "error" : "warning", `forge:${errors}e/${warnings}w`));
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
ctx.ui.setStatus("pi-forge-diagnostics", undefined);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function webHostRuntime(ctx: ExtensionCommandContext): WebHostRuntime {
|
|
182
|
-
return {
|
|
183
|
-
getStacks: () => state.stacks,
|
|
184
|
-
getActive: () => state.active,
|
|
185
|
-
getActiveId: activeId,
|
|
186
|
-
getSelectedActiveId: selectedActiveId,
|
|
187
|
-
setActive: (id) => setActive(id, ctx),
|
|
188
|
-
reloadStacks: (preferredId) => reloadStacks(ctx, preferredId),
|
|
189
|
-
buildPreview: (target) => buildPreview(ctx, target, state.sessionVariables, previewOptionsForStack(ctx, target.stack)),
|
|
190
|
-
getPolicyResources: () => getPolicyResources(ctx),
|
|
191
|
-
getPayload: () => ({ ok: true, ...webPayloadSnapshot(state) }),
|
|
192
|
-
armPayload: (savePath) => {
|
|
193
|
-
armPayloadIntercept(state, ctx, savePath, "web");
|
|
194
|
-
return { ok: true, ...webPayloadSnapshot(state) };
|
|
195
|
-
},
|
|
196
|
-
clearPayload: () => {
|
|
197
|
-
clearPayloadCapture(state, ctx);
|
|
198
|
-
return { ok: true, ...webPayloadSnapshot(state) };
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function getPolicyResources(ctx: ExtensionCommandContext): WebEditorPolicyResources {
|
|
204
|
-
const options = ctx.getSystemPromptOptions();
|
|
205
|
-
const activeTools = new Set(pi.getActiveTools());
|
|
206
|
-
const snippets = options.toolSnippets ?? {};
|
|
207
|
-
const tools = pi.getAllTools()
|
|
208
|
-
.map((tool) => normalizeToolResource(tool, activeTools, snippets))
|
|
209
|
-
.filter(hasPolicyResourceName)
|
|
210
|
-
.sort(comparePolicyResource);
|
|
211
|
-
const skills = (options.skills ?? [])
|
|
212
|
-
.map(normalizeSkillResource)
|
|
213
|
-
.filter(hasPolicyResourceName)
|
|
214
|
-
.sort(comparePolicyResource);
|
|
215
|
-
return { tools, skills };
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function previewOptionsForStack(ctx: ExtensionCommandContext, stack: PromptStack): BuildSystemPromptOptions {
|
|
219
|
-
const base = ctx.getSystemPromptOptions();
|
|
220
|
-
const baseSelectedTools = Array.isArray(base.selectedTools) ? base.selectedTools : pi.getActiveTools();
|
|
221
|
-
const policyActive = hasResourcePolicy(stack.tools);
|
|
222
|
-
const baselineTools = policyActive ? (toolPolicyBaseline ?? pi.getActiveTools()) : baseSelectedTools;
|
|
223
|
-
const selectedTools = policyActive
|
|
224
|
-
? applyResourcePolicy(filterKnownTools(baselineTools), stack.tools)
|
|
225
|
-
: baseSelectedTools;
|
|
226
|
-
const selectedToolSet = new Set(selectedTools);
|
|
227
|
-
const toolSnippets = filterToolSnippets(base.toolSnippets ?? {}, selectedToolSet);
|
|
228
|
-
const toolInfos = pi.getAllTools();
|
|
229
|
-
for (const tool of toolInfos) {
|
|
230
|
-
const name = stringValue((tool as { name?: unknown }).name);
|
|
231
|
-
if (!name || !selectedToolSet.has(name) || toolSnippets[name]) continue;
|
|
232
|
-
const snippet = stringValue((tool as { promptSnippet?: unknown }).promptSnippet);
|
|
233
|
-
if (snippet) toolSnippets[name] = snippet;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const mappedGuidelines = toolInfos
|
|
237
|
-
.filter((tool) => {
|
|
238
|
-
const name = stringValue((tool as { name?: unknown }).name);
|
|
239
|
-
return !!name && selectedToolSet.has(name);
|
|
240
|
-
})
|
|
241
|
-
.flatMap((tool) => stringArrayValue((tool as { promptGuidelines?: unknown }).promptGuidelines));
|
|
242
|
-
const promptGuidelines = policyActive && !sameStringSet(baseSelectedTools, selectedTools)
|
|
243
|
-
? mappedGuidelines
|
|
244
|
-
: (base.promptGuidelines ?? mappedGuidelines);
|
|
245
|
-
|
|
246
|
-
return { ...base, selectedTools, toolSnippets, promptGuidelines };
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function filterToolSnippets(snippets: Record<string, string | undefined>, selectedTools: Set<string>): Record<string, string> {
|
|
250
|
-
const filtered: Record<string, string> = {};
|
|
251
|
-
for (const [name, snippet] of Object.entries(snippets)) {
|
|
252
|
-
if (selectedTools.has(name) && snippet) filtered[name] = snippet;
|
|
253
|
-
}
|
|
254
|
-
return filtered;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function normalizeToolResource(
|
|
258
|
-
tool: { name?: unknown; description?: unknown; promptSnippet?: unknown; sourceInfo?: unknown },
|
|
259
|
-
activeTools: Set<string>,
|
|
260
|
-
snippets: Record<string, string | undefined>,
|
|
261
|
-
): WebEditorPolicyResource {
|
|
262
|
-
const name = String(tool.name ?? "");
|
|
263
|
-
return {
|
|
264
|
-
name,
|
|
265
|
-
description: stringValue(tool.description) ?? stringValue(tool.promptSnippet) ?? snippets[name],
|
|
266
|
-
source: sourceLabel(tool.sourceInfo),
|
|
267
|
-
active: activeTools.has(name),
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function normalizeSkillResource(skill: { name?: unknown; description?: unknown; filePath?: unknown; disableModelInvocation?: unknown }): WebEditorPolicyResource {
|
|
272
|
-
return {
|
|
273
|
-
name: String(skill.name ?? ""),
|
|
274
|
-
description: stringValue(skill.description),
|
|
275
|
-
source: stringValue(skill.filePath),
|
|
276
|
-
hidden: skill.disableModelInvocation === true,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function stringValue(value: unknown): string | undefined {
|
|
281
|
-
return typeof value === "string" && value.trim() ? value : undefined;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function stringArrayValue(value: unknown): string[] {
|
|
285
|
-
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && !!item.trim()) : [];
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function sourceLabel(value: unknown): string | undefined {
|
|
289
|
-
if (!value || typeof value !== "object") return undefined;
|
|
290
|
-
const source = stringValue((value as { source?: unknown }).source);
|
|
291
|
-
const path = stringValue((value as { path?: unknown }).path);
|
|
292
|
-
if (source && path) return `${source}: ${path}`;
|
|
293
|
-
return source ?? path;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function comparePolicyResource(a: WebEditorPolicyResource, b: WebEditorPolicyResource): number {
|
|
297
|
-
return a.name.localeCompare(b.name);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function hasPolicyResourceName(resource: WebEditorPolicyResource): boolean {
|
|
301
|
-
return !!resource.name.trim();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function sameStringSet(a: string[], b: string[]): boolean {
|
|
305
|
-
if (a.length !== b.length) return false;
|
|
306
|
-
const bSet = new Set(b);
|
|
307
|
-
return a.every((value) => bSet.has(value));
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function sharedWebEditorForCwd(cwd: string): SharedWebEditorState {
|
|
311
|
-
sharedWebEditors.byCwd[cwd] ??= {};
|
|
312
|
-
return sharedWebEditors.byCwd[cwd];
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function syncWebEditorFromShared(cwd: string): void {
|
|
316
|
-
const shared = sharedWebEditorForCwd(cwd);
|
|
317
|
-
webEditor = shared.server;
|
|
318
|
-
webEditorCwd = shared.cwd;
|
|
319
|
-
webEditorPreferredPort = shared.preferredPort;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function rememberWebEditor(server: WebEditorServer, cwd: string, preferredPort: number | undefined): void {
|
|
323
|
-
const shared = sharedWebEditorForCwd(cwd);
|
|
324
|
-
webEditor = server;
|
|
325
|
-
webEditorCwd = cwd;
|
|
326
|
-
webEditorPreferredPort = preferredPort;
|
|
327
|
-
shared.server = server;
|
|
328
|
-
shared.cwd = cwd;
|
|
329
|
-
shared.preferredPort = preferredPort;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function clearWebEditor(server: WebEditorServer): void {
|
|
333
|
-
if (webEditor === server) {
|
|
334
|
-
webEditor = undefined;
|
|
335
|
-
webEditorCwd = undefined;
|
|
336
|
-
webEditorPreferredPort = undefined;
|
|
337
|
-
}
|
|
338
|
-
for (const [cwd, shared] of Object.entries(sharedWebEditors.byCwd)) {
|
|
339
|
-
if (shared?.server === server) delete sharedWebEditors.byCwd[cwd];
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function refreshWebEditorHost(ctx: ExtensionContext): void {
|
|
344
|
-
syncWebEditorFromShared(ctx.cwd);
|
|
345
|
-
if (!webEditor) return;
|
|
346
|
-
const commandCtx = ctx as ExtensionCommandContext;
|
|
347
|
-
webEditor.updateHost(createWebEditorHost(commandCtx, webHostRuntime(commandCtx)));
|
|
348
|
-
rememberWebEditor(webEditor, ctx.cwd, webEditorPreferredPort);
|
|
349
|
-
ctx.ui.setStatus("pi-forge-editor", ctx.ui.theme.fg("accent", `editor:${webEditor.port}`));
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async function openWebEditor(ctx: ExtensionCommandContext, mode: "open" | "restart" = "open"): Promise<void> {
|
|
353
|
-
syncWebEditorFromShared(ctx.cwd);
|
|
354
|
-
const settings = loadWebEditorSettings(ctx);
|
|
355
|
-
for (const warning of settings.warnings) ctx.ui.notify(warning, "warning");
|
|
356
|
-
|
|
357
|
-
if (webEditor && (mode === "restart" || webEditorPreferredPort !== settings.preferredPort)) {
|
|
358
|
-
const server = webEditor;
|
|
359
|
-
await server.close();
|
|
360
|
-
clearWebEditor(server);
|
|
361
|
-
ctx.ui.setStatus("pi-forge-editor", undefined);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (!webEditor) {
|
|
365
|
-
try {
|
|
366
|
-
webEditor = await startWebEditorServer(createWebEditorHost(ctx, webHostRuntime(ctx)), { port: settings.preferredPort });
|
|
367
|
-
} catch (error) {
|
|
368
|
-
if (settings.preferredPort !== undefined) {
|
|
369
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
370
|
-
ctx.ui.notify(`pi-forge: preferred editor port 127.0.0.1:${settings.preferredPort} was unavailable (${detail}); using an available port instead.`, "warning");
|
|
371
|
-
try {
|
|
372
|
-
webEditor = await startWebEditorServer(createWebEditorHost(ctx, webHostRuntime(ctx)));
|
|
373
|
-
} catch (fallbackError) {
|
|
374
|
-
const fallbackDetail = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
375
|
-
ctx.ui.setStatus("pi-forge-editor", undefined);
|
|
376
|
-
ctx.ui.notify(`pi-forge: failed to start stack editor on an available localhost port: ${fallbackDetail}.`, "error");
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
} else {
|
|
380
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
381
|
-
ctx.ui.setStatus("pi-forge-editor", undefined);
|
|
382
|
-
ctx.ui.notify(`pi-forge: failed to start stack editor on an available localhost port: ${detail}.`, "error");
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
rememberWebEditor(webEditor, ctx.cwd, settings.preferredPort);
|
|
387
|
-
ctx.ui.setStatus("pi-forge-editor", ctx.ui.theme.fg("accent", `editor:${webEditor.port}`));
|
|
388
|
-
ctx.ui.notify(`pi-forge: stack editor running at ${webEditor.url}`, "info");
|
|
389
|
-
} else {
|
|
390
|
-
webEditor.updateHost(createWebEditorHost(ctx, webHostRuntime(ctx)));
|
|
391
|
-
rememberWebEditor(webEditor, ctx.cwd, settings.preferredPort);
|
|
392
|
-
ctx.ui.setStatus("pi-forge-editor", ctx.ui.theme.fg("accent", `editor:${webEditor.port}`));
|
|
393
|
-
ctx.ui.notify(`pi-forge: stack editor already running at ${webEditor.url}`, "info");
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
await showText(ctx, "pi-forge stack editor", `Open the local stack editor:\n\n${webEditor.url}\n\nServer bound to 127.0.0.1:${webEditor.port}\nOptional config: ${settings.configPath}\nProject: ${webEditorCwd}`);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
async function stopWebEditor(ctx: ExtensionCommandContext): Promise<void> {
|
|
400
|
-
syncWebEditorFromShared(ctx.cwd);
|
|
401
|
-
if (!webEditor) {
|
|
402
|
-
ctx.ui.notify("pi-forge: stack editor is not running.", "info");
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
const server = webEditor;
|
|
406
|
-
await server.close();
|
|
407
|
-
clearWebEditor(server);
|
|
408
|
-
ctx.ui.setStatus("pi-forge-editor", undefined);
|
|
409
|
-
ctx.ui.notify("pi-forge: stack editor stopped.", "info");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
registerLifecycleHandlers(pi, state, {
|
|
413
|
-
reloadStacks,
|
|
414
|
-
refreshWebEditorHost,
|
|
415
|
-
notifyActivePreset,
|
|
416
|
-
syncActiveToolPolicy,
|
|
417
|
-
activeId,
|
|
418
|
-
persistActiveSelection,
|
|
419
|
-
recordCompileDiagnostics,
|
|
420
|
-
});
|
|
421
|
-
registerPayloadRequestHandler(pi, state, () => state.active);
|
|
422
|
-
registerPayloadCommands(pi, state);
|
|
423
|
-
registerPresetCommand(pi, state, {
|
|
424
|
-
selectedActiveId,
|
|
425
|
-
setActive,
|
|
426
|
-
reloadStacks,
|
|
427
|
-
openWebEditor,
|
|
428
|
-
stopWebEditor,
|
|
429
|
-
});
|
|
430
|
-
}
|
package/src/lifecycle.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
compileMessages,
|
|
4
|
-
compileSystemPrompt,
|
|
5
|
-
createPromptVariableStore,
|
|
6
|
-
getLatestUserMessage,
|
|
7
|
-
markSessionVariablesClean,
|
|
8
|
-
resetTurnVariables,
|
|
9
|
-
} from "./compiler.ts";
|
|
10
|
-
import { applyFinalizeRegexRulesToMessage } from "./regex.ts";
|
|
11
|
-
import { STATE_ENTRY_TYPE, VARIABLE_ENTRY_TYPE, type PiForgeRuntimeState } from "./runtime-state.ts";
|
|
12
|
-
import type { PromptStackDiagnostic, PromptVariableStore, PromptVariableValue } from "./types.ts";
|
|
13
|
-
|
|
14
|
-
export interface LifecycleDeps {
|
|
15
|
-
reloadStacks(ctx: ExtensionContext, preferredId?: string): void;
|
|
16
|
-
refreshWebEditorHost(ctx: ExtensionContext): void;
|
|
17
|
-
notifyActivePreset(ctx: ExtensionContext, detail: string): void;
|
|
18
|
-
syncActiveToolPolicy(ctx?: ExtensionContext): void;
|
|
19
|
-
activeId(): string | undefined;
|
|
20
|
-
persistActiveSelection(id: string): void;
|
|
21
|
-
recordCompileDiagnostics(ctx: ExtensionContext, diagnostics: PromptStackDiagnostic[]): void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function registerLifecycleHandlers(pi: ExtensionAPI, state: PiForgeRuntimeState, deps: LifecycleDeps): void {
|
|
25
|
-
pi.on("session_start", async (event, ctx) => {
|
|
26
|
-
restoreBranchScopedRuntime(ctx, state, deps);
|
|
27
|
-
deps.refreshWebEditorHost(ctx);
|
|
28
|
-
deps.notifyActivePreset(ctx, "after session " + event.reason);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
pi.on("session_tree", async (_event, ctx) => {
|
|
32
|
-
restoreBranchScopedRuntime(ctx, state, deps);
|
|
33
|
-
deps.refreshWebEditorHost(ctx);
|
|
34
|
-
deps.notifyActivePreset(ctx, "after tree navigation");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
pi.on("session_compact", async (_event, ctx) => {
|
|
38
|
-
restoreBranchScopedRuntime(ctx, state, deps);
|
|
39
|
-
deps.refreshWebEditorHost(ctx);
|
|
40
|
-
deps.notifyActivePreset(ctx, "after compaction");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
pi.on("turn_start", async () => {
|
|
44
|
-
deps.syncActiveToolPolicy();
|
|
45
|
-
const id = deps.activeId();
|
|
46
|
-
if (id) deps.persistActiveSelection(id);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
pi.on("before_agent_start", async (event, ctx) => {
|
|
50
|
-
state.currentSystemPromptOptions = event.systemPromptOptions;
|
|
51
|
-
state.currentLatestUserMessage = event.prompt;
|
|
52
|
-
state.currentVariableStore = createPromptVariableStore(state.sessionVariables);
|
|
53
|
-
resetTurnVariables(state.currentVariableStore);
|
|
54
|
-
state.contextRewritePending = true;
|
|
55
|
-
|
|
56
|
-
if (!state.active) return;
|
|
57
|
-
|
|
58
|
-
const result = compileSystemPrompt(
|
|
59
|
-
state.active.stack,
|
|
60
|
-
{ options: event.systemPromptOptions, ctx, latestUserMessage: event.prompt, now: new Date(), variables: state.currentVariableStore },
|
|
61
|
-
event.systemPrompt,
|
|
62
|
-
);
|
|
63
|
-
deps.recordCompileDiagnostics(ctx, result.diagnostics);
|
|
64
|
-
persistVariablesIfDirty(pi, state, state.currentVariableStore);
|
|
65
|
-
|
|
66
|
-
return { systemPrompt: result.systemPrompt };
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
pi.on("context", async (event, ctx) => {
|
|
70
|
-
if (!state.active || !state.currentSystemPromptOptions || !state.contextRewritePending) return;
|
|
71
|
-
|
|
72
|
-
// Rewrite the message layout only for the first provider request of a user-submitted prompt.
|
|
73
|
-
// Tool-result follow-up turns must receive Pi's natural context; otherwise post-history
|
|
74
|
-
// prompt blocks such as COT / {{lastUserMessage}} are re-appended after every tool call
|
|
75
|
-
// and the model restarts its planning instead of continuing from the tool result.
|
|
76
|
-
state.contextRewritePending = false;
|
|
77
|
-
|
|
78
|
-
if (!state.currentVariableStore) state.currentVariableStore = createPromptVariableStore(state.sessionVariables);
|
|
79
|
-
const latestUserMessage = getLatestUserMessage(event.messages) ?? state.currentLatestUserMessage;
|
|
80
|
-
const result = compileMessages(
|
|
81
|
-
state.active.stack,
|
|
82
|
-
{ options: state.currentSystemPromptOptions, ctx, latestUserMessage, now: new Date(), variables: state.currentVariableStore },
|
|
83
|
-
event.messages,
|
|
84
|
-
);
|
|
85
|
-
deps.recordCompileDiagnostics(ctx, [...state.latestCompileDiagnostics, ...result.diagnostics]);
|
|
86
|
-
persistVariablesIfDirty(pi, state, state.currentVariableStore);
|
|
87
|
-
return { messages: result.messages };
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
pi.on("message_end", async (event, ctx) => {
|
|
91
|
-
if (!state.active) return;
|
|
92
|
-
const diagnostics: PromptStackDiagnostic[] = [];
|
|
93
|
-
const message = applyFinalizeRegexRulesToMessage(state.active.stack, event.message, diagnostics);
|
|
94
|
-
if (diagnostics.length > 0) deps.recordCompileDiagnostics(ctx, [...state.latestCompileDiagnostics, ...diagnostics]);
|
|
95
|
-
if (!message) return;
|
|
96
|
-
return { message };
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
pi.on("agent_end", async () => {
|
|
100
|
-
persistVariablesIfDirty(pi, state, state.currentVariableStore);
|
|
101
|
-
state.currentSystemPromptOptions = undefined;
|
|
102
|
-
state.currentLatestUserMessage = undefined;
|
|
103
|
-
state.currentVariableStore = undefined;
|
|
104
|
-
state.contextRewritePending = false;
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function restoreBranchScopedRuntime(ctx: ExtensionContext, state: PiForgeRuntimeState, deps: LifecycleDeps): void {
|
|
109
|
-
state.sessionVariables = getRestoredVariables(ctx);
|
|
110
|
-
state.currentVariableStore = undefined;
|
|
111
|
-
const restoredActiveId = getRestoredActiveId(ctx);
|
|
112
|
-
state.lastPersistedActiveId = restoredActiveId;
|
|
113
|
-
deps.reloadStacks(ctx, restoredActiveId);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function getCurrentBranchEntries(ctx: ExtensionContext): unknown[] {
|
|
117
|
-
const leafId = ctx.sessionManager.getLeafId();
|
|
118
|
-
if (leafId === null) return [];
|
|
119
|
-
const sessionManager = ctx.sessionManager as {
|
|
120
|
-
getBranch?: (fromId?: string) => unknown[];
|
|
121
|
-
getEntries: () => unknown[];
|
|
122
|
-
};
|
|
123
|
-
return sessionManager.getBranch ? sessionManager.getBranch(leafId ?? undefined) : sessionManager.getEntries();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function getRestoredActiveId(ctx: ExtensionContext): string | undefined {
|
|
127
|
-
const entries = getCurrentBranchEntries(ctx);
|
|
128
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
129
|
-
const entry = entries[i] as { type?: string; customType?: string; data?: { activeStackId?: unknown } };
|
|
130
|
-
if (entry.type === "custom" && entry.customType === STATE_ENTRY_TYPE) {
|
|
131
|
-
return typeof entry.data?.activeStackId === "string" ? entry.data.activeStackId : undefined;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function getRestoredVariables(ctx: ExtensionContext): Record<string, PromptVariableValue> {
|
|
138
|
-
const entries = getCurrentBranchEntries(ctx);
|
|
139
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
140
|
-
const entry = entries[i] as { type?: string; customType?: string; data?: { variables?: unknown } };
|
|
141
|
-
if (entry.type !== "custom" || entry.customType !== VARIABLE_ENTRY_TYPE) continue;
|
|
142
|
-
if (!entry.data || typeof entry.data.variables !== "object" || Array.isArray(entry.data.variables)) return {};
|
|
143
|
-
return normalizeVariableRecord(entry.data.variables as Record<string, unknown>);
|
|
144
|
-
}
|
|
145
|
-
return {};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function persistVariablesIfDirty(pi: ExtensionAPI, state: PiForgeRuntimeState, store: PromptVariableStore | undefined): void {
|
|
149
|
-
if (!store?.sessionDirty) return;
|
|
150
|
-
state.sessionVariables = { ...store.session };
|
|
151
|
-
pi.appendEntry(VARIABLE_ENTRY_TYPE, { variables: state.sessionVariables });
|
|
152
|
-
markSessionVariablesClean(store);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function normalizeVariableRecord(value: Record<string, unknown>): Record<string, PromptVariableValue> {
|
|
156
|
-
const result: Record<string, PromptVariableValue> = {};
|
|
157
|
-
for (const [key, raw] of Object.entries(value)) {
|
|
158
|
-
if (isPromptVariableValue(raw)) result[key] = raw;
|
|
159
|
-
}
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function isPromptVariableValue(value: unknown): value is PromptVariableValue {
|
|
164
|
-
if (value === null) return true;
|
|
165
|
-
const type = typeof value;
|
|
166
|
-
if (type === "string" || type === "boolean") return true;
|
|
167
|
-
if (type === "number") return Number.isFinite(value);
|
|
168
|
-
if (Array.isArray(value)) return value.every(isPromptVariableValue);
|
|
169
|
-
if (!value || typeof value !== "object") return false;
|
|
170
|
-
return Object.values(value as Record<string, unknown>).every(isPromptVariableValue);
|
|
171
|
-
}
|