pi-workflow-engine 0.10.3 → 0.12.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/.pi/extensions/pi-workflow-engine/index.ts +418 -213
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +11 -4
- package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +147 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-limits.ts +35 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +399 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-retry.ts +116 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +91 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +154 -530
- package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +256 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-session-providers.ts +50 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +368 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +87 -39
- package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +89 -0
- package/.pi/extensions/pi-workflow-engine/src/background-workflow-tool.ts +75 -0
- package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +492 -0
- package/.pi/extensions/pi-workflow-engine/src/budget.ts +10 -3
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +55 -3
- package/.pi/extensions/pi-workflow-engine/src/command-completions.ts +36 -0
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +230 -61
- package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +38 -55
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +70 -17
- package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +59 -13
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +123 -63
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +357 -75
- package/.pi/extensions/pi-workflow-engine/src/filesystem-error.ts +4 -0
- package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +66 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +138 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-fingerprint.ts +15 -0
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +10 -5
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +156 -66
- package/.pi/extensions/pi-workflow-engine/src/model-profile-command.ts +167 -0
- package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +310 -0
- package/.pi/extensions/pi-workflow-engine/src/options.ts +96 -6
- package/.pi/extensions/pi-workflow-engine/src/pi-compat.ts +32 -0
- package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +203 -39
- package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +50 -0
- package/.pi/extensions/pi-workflow-engine/src/progress.ts +74 -122
- package/.pi/extensions/pi-workflow-engine/src/provider-usage-limit.ts +189 -0
- package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +48 -0
- package/.pi/extensions/pi-workflow-engine/src/research-contract.ts +89 -0
- package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +289 -0
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +701 -0
- package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +49 -0
- package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +75 -16
- package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +83 -17
- package/.pi/extensions/pi-workflow-engine/src/review/review-budget.ts +45 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +117 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +14 -66
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +38 -12
- package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +42 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +18 -22
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +128 -56
- package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +153 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +294 -0
- package/.pi/extensions/pi-workflow-engine/src/review-diff-target.ts +130 -0
- package/.pi/extensions/pi-workflow-engine/src/session-identity.ts +6 -0
- package/.pi/extensions/pi-workflow-engine/src/structured-output.ts +32 -0
- package/.pi/extensions/pi-workflow-engine/src/tool-capabilities.ts +44 -0
- package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +150 -0
- package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +401 -0
- package/.pi/extensions/pi-workflow-engine/src/types.ts +87 -25
- package/.pi/extensions/pi-workflow-engine/src/ui/dynamax-editor-decoration.ts +220 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +26 -6
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +88 -46
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +7 -70
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-viewer-layout.ts +51 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +12 -67
- package/.pi/extensions/pi-workflow-engine/src/unknown-error.ts +8 -0
- package/.pi/extensions/pi-workflow-engine/src/usage.ts +124 -16
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +23 -3
- package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +121 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +19 -1
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +72 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +354 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +162 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +632 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +172 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-usage-limit-scheduler.ts +80 -0
- package/.pi/extensions/pi-workflow-engine/src/workflows.ts +30 -3
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +321 -43
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +62 -64
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +17 -17
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +15 -16
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +15 -15
- package/.pi/extensions/pi-workflow-engine/workflows/research.ts +170 -0
- package/README.md +82 -207
- package/USAGE.md +295 -21
- package/package.json +9 -9
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import type
|
|
4
|
+
import { Key, type KeyId } from "@earendil-works/pi-tui";
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_DYNAMAX_INSPECTOR_SHORTCUT = "ctrl+shift+m" satisfies KeyId;
|
|
7
|
+
export const DEFAULT_REVIEW_RESULTS_SHORTCUT = "ctrl+shift+r" satisfies KeyId;
|
|
7
8
|
|
|
8
9
|
const CONFIG_FILE_NAME = "pi-workflow-engine.json";
|
|
9
10
|
|
|
10
11
|
export interface DynamaxShortcuts {
|
|
11
12
|
inspector: KeyId | null;
|
|
13
|
+
results: KeyId | null;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
interface DynamaxShortcutConfig {
|
|
15
17
|
inspector?: unknown;
|
|
18
|
+
results?: unknown;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export function dynamaxShortcutsConfigPath(agentDir: string = getAgentDir()): string {
|
|
@@ -25,9 +28,15 @@ export function resolveDynamaxShortcuts(configPath: string = dynamaxShortcutsCon
|
|
|
25
28
|
const config = readShortcutConfig(configPath);
|
|
26
29
|
if (!config) return defaultDynamaxShortcuts();
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
inspector: shortcutFromConfig(config.inspector, DEFAULT_DYNAMAX_INSPECTOR_SHORTCUT),
|
|
31
|
+
const shortcuts = {
|
|
32
|
+
inspector: shortcutFromConfig("inspector", config.inspector, DEFAULT_DYNAMAX_INSPECTOR_SHORTCUT, configPath),
|
|
33
|
+
results: shortcutFromConfig("results", config.results, DEFAULT_REVIEW_RESULTS_SHORTCUT, configPath),
|
|
30
34
|
};
|
|
35
|
+
if (shortcuts.inspector && shortcuts.results && shortcutIdentity(shortcuts.inspector) === shortcutIdentity(shortcuts.results)) {
|
|
36
|
+
console.warn(`Duplicate pi-workflow-engine shortcut ${shortcuts.inspector} in ${configPath}; disabling the results shortcut.`);
|
|
37
|
+
return { inspector: shortcuts.inspector, results: null };
|
|
38
|
+
}
|
|
39
|
+
return shortcuts;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
function readShortcutConfig(configPath: string): DynamaxShortcutConfig | null {
|
|
@@ -41,7 +50,7 @@ function readShortcutConfig(configPath: string): DynamaxShortcutConfig | null {
|
|
|
41
50
|
|
|
42
51
|
const shortcuts = isRecord(parsed) ? parsed.shortcuts : undefined;
|
|
43
52
|
if (shortcuts === undefined) return {};
|
|
44
|
-
if (!isRecord(shortcuts)
|
|
53
|
+
if (!isRecord(shortcuts)) {
|
|
45
54
|
warnUsingDefaults("Invalid", configPath);
|
|
46
55
|
return null;
|
|
47
56
|
}
|
|
@@ -49,21 +58,58 @@ function readShortcutConfig(configPath: string): DynamaxShortcutConfig | null {
|
|
|
49
58
|
return shortcuts;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
function
|
|
53
|
-
|
|
61
|
+
function shortcutFromConfig(
|
|
62
|
+
name: keyof DynamaxShortcutConfig,
|
|
63
|
+
configured: unknown,
|
|
64
|
+
fallback: KeyId,
|
|
65
|
+
configPath: string,
|
|
66
|
+
): KeyId | null {
|
|
67
|
+
if (configured === null) return null;
|
|
68
|
+
if (configured === undefined) return fallback;
|
|
69
|
+
if (typeof configured === "string") {
|
|
70
|
+
const normalized = configured.trim();
|
|
71
|
+
if (isKeyId(normalized)) return normalized;
|
|
72
|
+
}
|
|
73
|
+
console.warn(`Invalid ${name} shortcut in ${configPath}; using ${fallback}.`);
|
|
74
|
+
return fallback;
|
|
54
75
|
}
|
|
55
76
|
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
const MODIFIERS = new Set(["ctrl", "shift", "alt", "super"]);
|
|
78
|
+
const NAMED_BASE_KEYS = new Set<string>(Object.values(Key).flatMap((value) => (typeof value === "string" ? [String(value)] : [])));
|
|
79
|
+
|
|
80
|
+
function isKeyId(value: string): value is KeyId {
|
|
81
|
+
return parseKeyId(value) !== undefined;
|
|
58
82
|
}
|
|
59
83
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
function parseKeyId(value: string): { readonly modifiers: readonly string[]; readonly base: string } | undefined {
|
|
85
|
+
const seen = new Set<string>();
|
|
86
|
+
let base = value;
|
|
87
|
+
while (true) {
|
|
88
|
+
const separator = base.indexOf("+");
|
|
89
|
+
if (separator < 0) break;
|
|
90
|
+
const modifier = base.slice(0, separator);
|
|
91
|
+
if (!MODIFIERS.has(modifier) || seen.has(modifier)) break;
|
|
92
|
+
seen.add(modifier);
|
|
93
|
+
base = base.slice(separator + 1);
|
|
94
|
+
}
|
|
95
|
+
return isBaseKey(base) ? { modifiers: [...seen], base } : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isBaseKey(value: string): boolean {
|
|
99
|
+
return /^[a-z0-9]$/.test(value) || NAMED_BASE_KEYS.has(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function shortcutIdentity(shortcut: KeyId): string {
|
|
103
|
+
const parsed = parseKeyId(shortcut);
|
|
104
|
+
if (!parsed) return shortcut;
|
|
105
|
+
let base = parsed.base;
|
|
106
|
+
if (base === "esc") base = "escape";
|
|
107
|
+
else if (base === "return") base = "enter";
|
|
108
|
+
return `${[...parsed.modifiers].sort().join("+")}+${base}`;
|
|
63
109
|
}
|
|
64
110
|
|
|
65
111
|
function defaultDynamaxShortcuts(): DynamaxShortcuts {
|
|
66
|
-
return { inspector: DEFAULT_DYNAMAX_INSPECTOR_SHORTCUT };
|
|
112
|
+
return { inspector: DEFAULT_DYNAMAX_INSPECTOR_SHORTCUT, results: DEFAULT_REVIEW_RESULTS_SHORTCUT };
|
|
67
113
|
}
|
|
68
114
|
|
|
69
115
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -71,5 +117,5 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
function warnUsingDefaults(reason: "Could not read" | "Invalid", configPath: string): void {
|
|
74
|
-
console.warn(`${reason} pi-workflow-engine config at ${configPath}; using default
|
|
120
|
+
console.warn(`${reason} pi-workflow-engine config at ${configPath}; using default workflow shortcuts.`);
|
|
75
121
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type
|
|
3
|
-
import type { KeyId } from "@earendil-works/pi-tui";
|
|
2
|
+
import { CustomEditor, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { EditorComponent, KeyId } from "@earendil-works/pi-tui";
|
|
4
|
+
import { completeCurrentArgument } from "./command-completions.ts";
|
|
4
5
|
import { resolveDynamaxShortcuts, type DynamaxShortcuts } from "./dynamax-shortcuts.ts";
|
|
6
|
+
import { sessionKey } from "./session-identity.ts";
|
|
7
|
+
import { unknownErrorMessage } from "./unknown-error.ts";
|
|
8
|
+
import {
|
|
9
|
+
decorateDynamaxEditor,
|
|
10
|
+
resolveDynamaxEffect,
|
|
11
|
+
type DynamaxAnimationScheduler,
|
|
12
|
+
type DynamaxEditorDecoration,
|
|
13
|
+
type DynamaxEffect,
|
|
14
|
+
} from "./ui/dynamax-editor-decoration.ts";
|
|
5
15
|
|
|
6
16
|
export interface DynamaxState {
|
|
7
17
|
sticky: boolean;
|
|
@@ -15,13 +25,43 @@ export interface DynamaxRuntime {
|
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
export type DynamaxRuntimeStore = Map<string, DynamaxRuntime>;
|
|
28
|
+
|
|
18
29
|
export interface DynamaxRegistrationOptions {
|
|
19
30
|
openInspector?: (ctx: ExtensionContext) => Promise<void> | void;
|
|
31
|
+
effect?: DynamaxEffect;
|
|
32
|
+
animationScheduler?: DynamaxAnimationScheduler;
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
export const DYNAMAX_TOKEN_PATTERN = /(^|[^A-Za-z0-9_])dynamax([^A-Za-z0-9_]|$)/i;
|
|
23
36
|
export const DYNAMAX_STATUS_KEY = "dynamax";
|
|
24
|
-
|
|
37
|
+
|
|
38
|
+
type EditorFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorComponent"]>>;
|
|
39
|
+
|
|
40
|
+
interface DynamaxEditorInstallation {
|
|
41
|
+
factory: EditorFactory;
|
|
42
|
+
previousFactory: EditorFactory | undefined;
|
|
43
|
+
decorations: Set<DynamaxEditorDecoration>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function disposeDynamaxDecorations(decorations: Set<DynamaxEditorDecoration>): void {
|
|
47
|
+
for (const decoration of decorations) decoration.dispose();
|
|
48
|
+
decorations.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DYNAMAX_ACTION_COMPLETIONS = [
|
|
52
|
+
{ value: "on", description: "Keep Dynamax enabled for this session" },
|
|
53
|
+
{ value: "off", description: "Disable sticky and pending Dynamax modes" },
|
|
54
|
+
{ value: "status", description: "Show the current Dynamax state" },
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
export const ADAPTIVE_WORKFLOW_GUIDANCE = `
|
|
58
|
+
Adaptive multi-pass workflows are optional. Use a simple single-pass fan-out when it is sufficient. When the first pass may expose gaps, conflicts, weak claims, or missing evidence:
|
|
59
|
+
- run the bounded first-pass agents;
|
|
60
|
+
- give their surviving results to a structured gap-analysis agent so the LLM decides what needs follow-up;
|
|
61
|
+
- put a hard maxItems bound on LLM-authored task arrays and defensively slice them before fan-out;
|
|
62
|
+
- use ordinary TypeScript conditionals or bounded loops to launch follow-up agents only when gaps exist;
|
|
63
|
+
- synthesize the first-pass and follow-up results together.
|
|
64
|
+
Do not generate a second pass when the first pass is sufficient, and do not invent iteration, quorum, graph, reduction, or retry primitives for this pattern.`;
|
|
25
65
|
|
|
26
66
|
export const DYNAMAX_REMINDER = `
|
|
27
67
|
## dynamax workflow opt-in
|
|
@@ -31,9 +71,11 @@ The user has opted into dynamax multi-agent orchestration. The workflow tool is
|
|
|
31
71
|
Inline workflow rules:
|
|
32
72
|
- Use the injected Type object for schemas, for example Type.Object({ ok: Type.Boolean() }); do not import typebox.
|
|
33
73
|
- Do not use import statements or dynamic import() in inline workflow scripts.
|
|
34
|
-
- Set
|
|
74
|
+
- Set profile to "small", "medium", or "big" on each agent() call so routing remains explicit; use model/thinkingLevel only for an intentional override.
|
|
35
75
|
- Subagents receive no skills by default. Add \`skills: ["skill-name"]\` per agent only when that stage should load that skill; grant the smallest useful set.
|
|
36
76
|
- Provide exactly one of workflow.name or workflow.script.
|
|
77
|
+
|
|
78
|
+
${ADAPTIVE_WORKFLOW_GUIDANCE}
|
|
37
79
|
`;
|
|
38
80
|
|
|
39
81
|
const DYNAMAX_CONTEXT_CUSTOM_TYPE = "workflow-dynamax-reminder";
|
|
@@ -46,12 +88,8 @@ export function createDynamaxRuntime(): DynamaxRuntime {
|
|
|
46
88
|
return { state: createDynamaxState() };
|
|
47
89
|
}
|
|
48
90
|
|
|
49
|
-
export function dynamaxSessionKey(ctx: Pick<ExtensionContext, "sessionManager">): string {
|
|
50
|
-
return ctx.sessionManager.getSessionFile() ?? ctx.sessionManager.getSessionId();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
91
|
export function getDynamaxRuntime(store: DynamaxRuntimeStore, ctx: Pick<ExtensionContext, "sessionManager">): DynamaxRuntime {
|
|
54
|
-
const key =
|
|
92
|
+
const key = sessionKey(ctx);
|
|
55
93
|
const existing = store.get(key);
|
|
56
94
|
if (existing) return existing;
|
|
57
95
|
const runtime = createDynamaxRuntime();
|
|
@@ -67,12 +105,8 @@ export function markDynamaxOneShot(state: DynamaxState): void {
|
|
|
67
105
|
state.oneShotPending = true;
|
|
68
106
|
}
|
|
69
107
|
|
|
70
|
-
export function
|
|
71
|
-
state.sticky =
|
|
72
|
-
if (!sticky) {
|
|
73
|
-
state.oneShotPending = false;
|
|
74
|
-
state.turnActive = false;
|
|
75
|
-
}
|
|
108
|
+
export function enableDynamaxSticky(state: DynamaxState): void {
|
|
109
|
+
state.sticky = true;
|
|
76
110
|
}
|
|
77
111
|
|
|
78
112
|
export function clearDynamax(state: DynamaxState): void {
|
|
@@ -81,12 +115,6 @@ export function clearDynamax(state: DynamaxState): void {
|
|
|
81
115
|
state.turnActive = false;
|
|
82
116
|
}
|
|
83
117
|
|
|
84
|
-
export function consumeDynamaxOneShot(state: DynamaxState): boolean {
|
|
85
|
-
const pending = state.oneShotPending;
|
|
86
|
-
state.oneShotPending = false;
|
|
87
|
-
return pending;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
118
|
export function isDynamaxActive(state: DynamaxState): boolean {
|
|
91
119
|
return state.sticky || state.oneShotPending || state.turnActive;
|
|
92
120
|
}
|
|
@@ -128,24 +156,85 @@ export function registerDynamax(
|
|
|
128
156
|
options: DynamaxRegistrationOptions = {},
|
|
129
157
|
): void {
|
|
130
158
|
const runtimes: DynamaxRuntimeStore = new Map();
|
|
159
|
+
const effect = options.effect ?? resolveDynamaxEffect();
|
|
160
|
+
let editorInstallation: DynamaxEditorInstallation | undefined;
|
|
131
161
|
const openInspector =
|
|
132
162
|
options.openInspector ??
|
|
133
163
|
((ctx: ExtensionContext): void => {
|
|
134
164
|
ctx.ui.notify("No workflow inspector is available yet", "warning");
|
|
135
165
|
});
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
166
|
+
const installEditor = (ctx: ExtensionContext): void => {
|
|
167
|
+
if (ctx.mode !== "tui" || effect === "off") return;
|
|
168
|
+
const previousFactory = ctx.ui.getEditorComponent();
|
|
169
|
+
if (editorInstallation && editorInstallation.factory === previousFactory) return;
|
|
170
|
+
|
|
171
|
+
const decorations = new Set<DynamaxEditorDecoration>();
|
|
172
|
+
const installation: DynamaxEditorInstallation = {
|
|
173
|
+
previousFactory,
|
|
174
|
+
decorations,
|
|
175
|
+
factory: (tui, theme, keybindings) => {
|
|
176
|
+
const decorate = (editor: EditorComponent): EditorComponent => {
|
|
177
|
+
const decoration = decorateDynamaxEditor(editor, () => tui.requestRender(), {
|
|
178
|
+
effect,
|
|
179
|
+
scheduler: options.animationScheduler,
|
|
180
|
+
isActive: () => editorInstallation === installation && ctx.ui.getEditorComponent() === installation.factory,
|
|
181
|
+
});
|
|
182
|
+
decorations.add(decoration);
|
|
183
|
+
return decoration.editor;
|
|
184
|
+
};
|
|
185
|
+
if (!previousFactory) return decorate(new CustomEditor(tui, theme, keybindings));
|
|
186
|
+
|
|
187
|
+
let editor: EditorComponent;
|
|
188
|
+
try {
|
|
189
|
+
editor = previousFactory(tui, theme, keybindings);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
ctx.ui.notify(
|
|
192
|
+
`Dynamax could not compose the existing custom editor (${unknownErrorMessage(error)}); using pi's stock-compatible CustomEditor so highlighting stays enabled`,
|
|
193
|
+
"warning",
|
|
194
|
+
);
|
|
195
|
+
installation.previousFactory = undefined;
|
|
196
|
+
return decorate(new CustomEditor(tui, theme, keybindings));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
return decorate(editor);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
ctx.ui.notify(
|
|
203
|
+
`Dynamax could not decorate the existing custom editor (${unknownErrorMessage(error)}); using pi's stock-compatible CustomEditor so highlighting stays enabled`,
|
|
204
|
+
"warning",
|
|
205
|
+
);
|
|
206
|
+
return decorate(new CustomEditor(tui, theme, keybindings));
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
ctx.ui.setEditorComponent(installation.factory);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
disposeDynamaxDecorations(decorations);
|
|
215
|
+
ctx.ui.notify(`Dynamax highlighting could not be installed: ${unknownErrorMessage(error)}`, "error");
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
editorInstallation = installation;
|
|
219
|
+
};
|
|
220
|
+
const uninstallEditor = (ctx: ExtensionContext): void => {
|
|
221
|
+
const installation = editorInstallation;
|
|
222
|
+
if (!installation) return;
|
|
223
|
+
editorInstallation = undefined;
|
|
224
|
+
disposeDynamaxDecorations(installation.decorations);
|
|
225
|
+
if (ctx.mode === "tui" && ctx.ui.getEditorComponent() === installation.factory) {
|
|
226
|
+
ctx.ui.setEditorComponent(installation.previousFactory);
|
|
140
227
|
}
|
|
141
228
|
};
|
|
142
229
|
|
|
143
230
|
pi.on("session_start", (_event, ctx) => {
|
|
231
|
+
installEditor(ctx);
|
|
144
232
|
updateDynamaxSurfaces(ctx, getDynamaxRuntime(runtimes, ctx), shortcuts);
|
|
145
233
|
});
|
|
146
234
|
|
|
147
235
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
148
|
-
|
|
236
|
+
uninstallEditor(ctx);
|
|
237
|
+
clearDynamaxSurfaces(ctx);
|
|
149
238
|
});
|
|
150
239
|
|
|
151
240
|
pi.on("input", (event, ctx) => {
|
|
@@ -166,26 +255,6 @@ export function registerDynamax(
|
|
|
166
255
|
return { systemPrompt };
|
|
167
256
|
});
|
|
168
257
|
|
|
169
|
-
pi.on("agent_start", (_event, ctx) => {
|
|
170
|
-
refreshIfVisible(ctx);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
pi.on("message_start", (_event, ctx) => {
|
|
174
|
-
refreshIfVisible(ctx);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
pi.on("message_update", (_event, ctx) => {
|
|
178
|
-
refreshIfVisible(ctx);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
pi.on("message_end", (_event, ctx) => {
|
|
182
|
-
refreshIfVisible(ctx);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
pi.on("turn_end", (_event, ctx) => {
|
|
186
|
-
refreshIfVisible(ctx);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
258
|
pi.on("agent_end", (_event, ctx) => {
|
|
190
259
|
const runtime = getDynamaxRuntime(runtimes, ctx);
|
|
191
260
|
runtime.runningWorkflow = undefined;
|
|
@@ -194,23 +263,17 @@ export function registerDynamax(
|
|
|
194
263
|
});
|
|
195
264
|
|
|
196
265
|
pi.on("tool_execution_start", (event, ctx) => {
|
|
266
|
+
if (event.toolName !== "workflow") return undefined;
|
|
197
267
|
const runtime = getDynamaxRuntime(runtimes, ctx);
|
|
198
|
-
|
|
199
|
-
runtime.runningWorkflow = workflowLabel(event.args);
|
|
200
|
-
}
|
|
268
|
+
runtime.runningWorkflow = workflowLabel(event.args);
|
|
201
269
|
updateDynamaxSurfaces(ctx, runtime, shortcuts);
|
|
202
270
|
return undefined;
|
|
203
271
|
});
|
|
204
272
|
|
|
205
|
-
pi.on("tool_execution_update", (_event, ctx) => {
|
|
206
|
-
refreshIfVisible(ctx);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
273
|
pi.on("tool_execution_end", (event, ctx) => {
|
|
274
|
+
if (event.toolName !== "workflow") return undefined;
|
|
210
275
|
const runtime = getDynamaxRuntime(runtimes, ctx);
|
|
211
|
-
|
|
212
|
-
runtime.runningWorkflow = undefined;
|
|
213
|
-
}
|
|
276
|
+
runtime.runningWorkflow = undefined;
|
|
214
277
|
updateDynamaxSurfaces(ctx, runtime, shortcuts);
|
|
215
278
|
return undefined;
|
|
216
279
|
});
|
|
@@ -233,6 +296,7 @@ export function registerDynamax(
|
|
|
233
296
|
|
|
234
297
|
pi.registerCommand("workflow:dynamax", {
|
|
235
298
|
description: "Toggle Dynamax workflow orchestration: /workflow:dynamax [on|off|status]",
|
|
299
|
+
getArgumentCompletions: (argumentPrefix) => completeCurrentArgument(argumentPrefix, DYNAMAX_ACTION_COMPLETIONS),
|
|
236
300
|
handler: async (args, ctx) => {
|
|
237
301
|
const runtime = getDynamaxRuntime(runtimes, ctx);
|
|
238
302
|
const action = args.trim().toLowerCase();
|
|
@@ -241,7 +305,7 @@ export function registerDynamax(
|
|
|
241
305
|
return;
|
|
242
306
|
}
|
|
243
307
|
if (action === "on") {
|
|
244
|
-
|
|
308
|
+
enableDynamaxSticky(runtime.state);
|
|
245
309
|
updateDynamaxSurfaces(ctx, runtime, shortcuts);
|
|
246
310
|
ctx.ui.notify("Dynamax workflow orchestration is on for this session", "info");
|
|
247
311
|
return;
|
|
@@ -263,10 +327,8 @@ export function registerDynamax(
|
|
|
263
327
|
|
|
264
328
|
export function updateDynamaxSurfaces(ctx: Pick<ExtensionContext, "hasUI" | "ui">, runtime: DynamaxRuntime, shortcuts: DynamaxShortcuts): void {
|
|
265
329
|
if (!ctx.hasUI) return;
|
|
266
|
-
ctx.ui.setWidget(DYNAMAX_WIDGET_KEY, undefined);
|
|
267
|
-
ctx.ui.setStatus(DYNAMAX_WIDGET_KEY, undefined);
|
|
268
330
|
if (!isDynamaxActive(runtime.state) && !runtime.runningWorkflow) {
|
|
269
|
-
clearDynamaxSurfaces(ctx
|
|
331
|
+
clearDynamaxSurfaces(ctx);
|
|
270
332
|
return;
|
|
271
333
|
}
|
|
272
334
|
|
|
@@ -274,11 +336,9 @@ export function updateDynamaxSurfaces(ctx: Pick<ExtensionContext, "hasUI" | "ui"
|
|
|
274
336
|
ctx.ui.setStatus(DYNAMAX_STATUS_KEY, status);
|
|
275
337
|
}
|
|
276
338
|
|
|
277
|
-
export function clearDynamaxSurfaces(ctx: Pick<ExtensionContext, "hasUI" | "ui"
|
|
339
|
+
export function clearDynamaxSurfaces(ctx: Pick<ExtensionContext, "hasUI" | "ui">): void {
|
|
278
340
|
if (!ctx.hasUI) return;
|
|
279
|
-
ctx.ui.setWidget(DYNAMAX_WIDGET_KEY, undefined);
|
|
280
341
|
ctx.ui.setStatus(DYNAMAX_STATUS_KEY, undefined);
|
|
281
|
-
ctx.ui.setStatus(DYNAMAX_WIDGET_KEY, undefined);
|
|
282
342
|
}
|
|
283
343
|
|
|
284
344
|
function workflowLabel(args: unknown): string {
|