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,11 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type {
|
|
2
|
+
CreateAgentSessionServicesOptions,
|
|
3
|
+
ResourceDiagnostic,
|
|
4
|
+
ResourceLoader,
|
|
5
|
+
Skill,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { logicalWorkspacePath, type ReplayWorkspaceRoots } from "./replay-path-identity.ts";
|
|
9
|
+
import { captureTreeFingerprint } from "./tree-fingerprint.ts";
|
|
10
|
+
import type { ResolvedSkillIdentity } from "./resume-context.ts";
|
|
2
11
|
|
|
3
|
-
|
|
4
|
-
export type AgentSkillRequestSource = "explicit" | "prompt";
|
|
5
|
-
|
|
6
|
-
export interface AgentSkillRequest {
|
|
12
|
+
interface AgentSkillRequest {
|
|
7
13
|
readonly selectors: readonly string[];
|
|
8
|
-
readonly source: AgentSkillRequestSource;
|
|
9
14
|
readonly strict: boolean;
|
|
10
15
|
}
|
|
11
16
|
|
|
@@ -14,13 +19,28 @@ export interface AgentSkillResolution {
|
|
|
14
19
|
readonly unmatched: readonly string[];
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
type AgentSkillResourceLoaderOptions = Pick<
|
|
23
|
+
NonNullable<CreateAgentSessionServicesOptions["resourceLoaderOptions"]>,
|
|
24
|
+
"noSkills" | "skillsOverride"
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
interface PreparedAgentSkillResources {
|
|
28
|
+
readonly resourceLoaderOptions: AgentSkillResourceLoaderOptions;
|
|
29
|
+
resolve(resourceLoader: Pick<ResourceLoader, "getSkills">): readonly Skill[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type AgentSkillIdentityCapture =
|
|
33
|
+
| { readonly kind: "verified"; readonly skills: readonly ResolvedSkillIdentity[] }
|
|
34
|
+
| { readonly kind: "unverifiable"; readonly reason: string };
|
|
35
|
+
|
|
36
|
+
export interface AgentSkillIdentityOptions extends ReplayWorkspaceRoots {
|
|
37
|
+
readonly signal?: AbortSignal;
|
|
22
38
|
}
|
|
23
39
|
|
|
40
|
+
const SKILL_FINGERPRINT_EXCLUSIONS = new Set([".git", "node_modules"]);
|
|
41
|
+
const SKILL_FINGERPRINT_MAX_BYTES = 8 << 20;
|
|
42
|
+
const SKILL_FINGERPRINT_MAX_FILES = 1024;
|
|
43
|
+
|
|
24
44
|
const SLASH_SKILL_PATTERN = /\/skill:([a-z0-9][a-z0-9-]{0,63})/gi;
|
|
25
45
|
const SKILLS_FIELD_PATTERN = /(?<!\/)\bskills?\s*[:=]\s*([^\n.;]+)/gi;
|
|
26
46
|
const VERB_SKILL_LIST_PATTERN = /\b(?:include|use|enable|load|provide|allow|grant|with)\s+(?:the\s+|this\s+|these\s+)?skills?\s+([^\n.;]+)/gi;
|
|
@@ -36,9 +56,9 @@ const PURPOSE_BOUNDARY_PATTERN = /\s+\b(?:for|to|when|while|so|because|in\s+orde
|
|
|
36
56
|
*/
|
|
37
57
|
export function resolveAgentSkillRequest(prompt: string, explicitSkills: unknown): AgentSkillRequest {
|
|
38
58
|
if (explicitSkills !== undefined) {
|
|
39
|
-
return { selectors: normalizeExplicitSkillSelectors(explicitSkills),
|
|
59
|
+
return { selectors: normalizeExplicitSkillSelectors(explicitSkills), strict: true };
|
|
40
60
|
}
|
|
41
|
-
return { selectors: extractSkillSelectorsFromText(prompt),
|
|
61
|
+
return { selectors: extractSkillSelectorsFromText(prompt), strict: false };
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
export function extractSkillSelectorsFromText(text: string): string[] {
|
|
@@ -83,19 +103,21 @@ export function selectAgentSkills(skills: readonly Skill[], selectors: readonly
|
|
|
83
103
|
return { selected: [...selected.values()], unmatched };
|
|
84
104
|
}
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Prepare the skill-selection hooks consumed by pi's session-service factory.
|
|
108
|
+
* The returned resolver is scoped to one resource loader and must not be reused
|
|
109
|
+
* across concurrently-created session services.
|
|
110
|
+
*/
|
|
111
|
+
export function prepareAgentSkillResources(options: {
|
|
88
112
|
readonly prompt: string;
|
|
89
113
|
readonly skills: unknown;
|
|
90
114
|
readonly log?: (message: string) => void;
|
|
91
|
-
}):
|
|
115
|
+
}): PreparedAgentSkillResources {
|
|
92
116
|
const request = resolveAgentSkillRequest(options.prompt, options.skills);
|
|
93
117
|
let resolution: AgentSkillResolution = { selected: [], unmatched: [] };
|
|
94
118
|
let availableSkillNames: readonly string[] = [];
|
|
95
119
|
|
|
96
|
-
const
|
|
97
|
-
cwd: options.cwd,
|
|
98
|
-
agentDir: getAgentDir(),
|
|
120
|
+
const resourceLoaderOptions: AgentSkillResourceLoaderOptions = {
|
|
99
121
|
noSkills: request.selectors.length === 0,
|
|
100
122
|
skillsOverride:
|
|
101
123
|
request.selectors.length === 0
|
|
@@ -105,30 +127,56 @@ export async function createAgentSkillResourceLoader(options: {
|
|
|
105
127
|
resolution = selectAgentSkills(current.skills, request.selectors);
|
|
106
128
|
return { skills: [...resolution.selected], diagnostics: current.diagnostics };
|
|
107
129
|
},
|
|
108
|
-
}
|
|
109
|
-
await loader.reload();
|
|
110
|
-
const { diagnostics } = loader.getSkills();
|
|
111
|
-
logSkillDiagnostics(options.log, diagnostics, request.selectors.length > 0);
|
|
112
|
-
|
|
113
|
-
if (request.strict && resolution.unmatched.length > 0) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
`Unknown subagent skill${resolution.unmatched.length === 1 ? "" : "s"}: ${resolution.unmatched.join(", ")}. Available: ${
|
|
116
|
-
availableSkillNames.join(", ") || "(none)"
|
|
117
|
-
}.`,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
130
|
+
};
|
|
120
131
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
132
|
+
return {
|
|
133
|
+
resourceLoaderOptions,
|
|
134
|
+
resolve(resourceLoader) {
|
|
135
|
+
const { diagnostics } = resourceLoader.getSkills();
|
|
136
|
+
logSkillDiagnostics(options.log, diagnostics, request.selectors.length > 0);
|
|
137
|
+
|
|
138
|
+
if (request.strict && resolution.unmatched.length > 0) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Unknown subagent skill${resolution.unmatched.length === 1 ? "" : "s"}: ${resolution.unmatched.join(", ")}. Available: ${
|
|
141
|
+
availableSkillNames.join(", ") || "(none)"
|
|
142
|
+
}.`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
124
145
|
|
|
125
|
-
|
|
146
|
+
if (!request.strict && resolution.unmatched.length > 0) {
|
|
147
|
+
options.log?.(`Ignoring unrecognized subagent skill mention${resolution.unmatched.length === 1 ? "" : "s"}: ${resolution.unmatched.join(", ")}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return resolution.selected;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
126
153
|
}
|
|
127
154
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
155
|
+
/** Capture the exact selected skill directories before journal lookup. */
|
|
156
|
+
export async function captureAgentSkillIdentities(
|
|
157
|
+
skills: readonly Skill[],
|
|
158
|
+
options: AgentSkillIdentityOptions,
|
|
159
|
+
): Promise<AgentSkillIdentityCapture> {
|
|
160
|
+
const identities: ResolvedSkillIdentity[] = [];
|
|
161
|
+
for (const skill of skills) {
|
|
162
|
+
const root = resolve(dirname(skill.filePath));
|
|
163
|
+
const capture = await captureTreeFingerprint({
|
|
164
|
+
root,
|
|
165
|
+
excludedRelativePaths: SKILL_FINGERPRINT_EXCLUSIONS,
|
|
166
|
+
maxBytes: SKILL_FINGERPRINT_MAX_BYTES,
|
|
167
|
+
maxFiles: SKILL_FINGERPRINT_MAX_FILES,
|
|
168
|
+
signal: options.signal,
|
|
169
|
+
});
|
|
170
|
+
if (capture.kind === "unverifiable") {
|
|
171
|
+
return { kind: "unverifiable", reason: `skill ${skill.name} could not be fingerprinted: ${capture.reason}` };
|
|
172
|
+
}
|
|
173
|
+
identities.push({
|
|
174
|
+
name: skill.name,
|
|
175
|
+
path: logicalWorkspacePath(root, options) ?? `external:${skill.name}`,
|
|
176
|
+
fingerprint: capture.fingerprint,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return { kind: "verified", skills: identities };
|
|
132
180
|
}
|
|
133
181
|
|
|
134
182
|
function collectPatternMatches(text: string, pattern: RegExp, selectors: string[], captureIndex: number): void {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { AgentExecutionOptions, AgentProgress } from "./agent-runner-types.ts";
|
|
2
|
+
import { unknownErrorMessage } from "./unknown-error.ts";
|
|
3
|
+
import type { WorktreeRegistry } from "./worktree.ts";
|
|
4
|
+
|
|
5
|
+
interface AgentWorkspaceBase {
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
wrapResult(result: unknown): Promise<unknown>;
|
|
8
|
+
dispose(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SharedAgentWorkspace extends AgentWorkspaceBase {
|
|
12
|
+
readonly kind: "shared";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IsolatedAgentWorkspace extends AgentWorkspaceBase {
|
|
16
|
+
readonly kind: "isolated";
|
|
17
|
+
readonly baselineOid: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AgentWorkspace = SharedAgentWorkspace | IsolatedAgentWorkspace;
|
|
21
|
+
|
|
22
|
+
export interface AgentWorkspaceContext {
|
|
23
|
+
readonly cwd: string;
|
|
24
|
+
readonly worktrees: Pick<WorktreeRegistry, "probe" | "add" | "capturePatch" | "remove">;
|
|
25
|
+
readonly signal: AbortSignal | undefined;
|
|
26
|
+
readonly progress: Pick<AgentProgress, "log">;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function createAgentWorkspace(
|
|
30
|
+
rc: AgentWorkspaceContext,
|
|
31
|
+
opts: AgentExecutionOptions,
|
|
32
|
+
label: string,
|
|
33
|
+
): Promise<AgentWorkspace> {
|
|
34
|
+
if (opts.isolation !== "worktree") {
|
|
35
|
+
return {
|
|
36
|
+
kind: "shared",
|
|
37
|
+
cwd: rc.cwd,
|
|
38
|
+
async wrapResult(result) {
|
|
39
|
+
return result;
|
|
40
|
+
},
|
|
41
|
+
async dispose() {},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const probe = await rc.worktrees.probe(rc.signal);
|
|
46
|
+
if (!probe.ok) {
|
|
47
|
+
throw new Error(`Failed to check git worktree availability for isolated agent: ${probe.error ?? "unknown git error"}`);
|
|
48
|
+
}
|
|
49
|
+
if (!probe.inside) {
|
|
50
|
+
throw new Error("Agent requested worktree isolation, but the workflow cwd is not inside a git work tree.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const added = await rc.worktrees.add(rc.signal, opts.worktreeBaseline);
|
|
54
|
+
if ("error" in added) throw new Error(`Failed to create isolated worktree: ${added.error}`);
|
|
55
|
+
const worktreePath = added.path;
|
|
56
|
+
rc.progress.log(`${label}: using isolated worktree ${worktreePath}`);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
kind: "isolated",
|
|
60
|
+
cwd: worktreePath,
|
|
61
|
+
baselineOid: added.baselineOid,
|
|
62
|
+
async wrapResult(result) {
|
|
63
|
+
const patch = await rc.worktrees.capturePatch(worktreePath, added.baselineOid, rc.signal);
|
|
64
|
+
if ("error" in patch) throw new Error(`Failed to capture isolated worktree patch: ${patch.error}`);
|
|
65
|
+
return { result, patch: patch.patch, changed: patch.changed };
|
|
66
|
+
},
|
|
67
|
+
async dispose() {
|
|
68
|
+
const removed = await rc.worktrees.remove(worktreePath);
|
|
69
|
+
if (!removed.ok) {
|
|
70
|
+
rc.progress.log(
|
|
71
|
+
`${label}: failed to remove isolated worktree (${removed.error ?? (removed.stderr.trim() || "unknown error")})`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function disposeAgentWorkspace(
|
|
79
|
+
rc: Pick<AgentWorkspaceContext, "progress">,
|
|
80
|
+
label: string,
|
|
81
|
+
workspace: AgentWorkspace | undefined,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
if (!workspace) return;
|
|
84
|
+
try {
|
|
85
|
+
await workspace.dispose();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
rc.progress.log(`${label}: failed to dispose isolated workspace (${unknownErrorMessage(error)})`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { BackgroundWorkflowCoordinator, backgroundOrigin } from "./background-workflows.ts";
|
|
3
|
+
import { createWorkflowRunId } from "./journal.ts";
|
|
4
|
+
import type { ResolvedWorkflowRunOptions } from "./options.ts";
|
|
5
|
+
import { unknownErrorMessage } from "./unknown-error.ts";
|
|
6
|
+
|
|
7
|
+
export interface BackgroundWorkflowToolResult {
|
|
8
|
+
readonly content: Array<{ readonly type: "text"; readonly text: string }>;
|
|
9
|
+
readonly details: Readonly<Record<string, unknown>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BackgroundWorkflowToolStartInput {
|
|
13
|
+
readonly coordinator: BackgroundWorkflowCoordinator;
|
|
14
|
+
readonly ctx: ExtensionContext;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly options: ResolvedWorkflowRunOptions;
|
|
17
|
+
readonly execute: (ctx: ExtensionContext, options: ResolvedWorkflowRunOptions) => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function backgroundUnavailableResult(mode: ExtensionContext["mode"]): BackgroundWorkflowToolResult | undefined {
|
|
21
|
+
if (mode !== "print" && mode !== "json") return undefined;
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: `Background workflows are unavailable in ${mode} mode because pi exits after the prompt. Run synchronously or use TUI/RPC mode.`,
|
|
26
|
+
}],
|
|
27
|
+
details: { error: "background_unavailable", mode },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function startBackgroundWorkflowTool(
|
|
32
|
+
input: BackgroundWorkflowToolStartInput,
|
|
33
|
+
): Promise<BackgroundWorkflowToolResult> {
|
|
34
|
+
const runId = createWorkflowRunId();
|
|
35
|
+
const backgroundOptions: ResolvedWorkflowRunOptions = {
|
|
36
|
+
...input.options,
|
|
37
|
+
inspect: false,
|
|
38
|
+
resultViewer: "skip",
|
|
39
|
+
runId,
|
|
40
|
+
background: backgroundOrigin(input.ctx),
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
await input.coordinator.start({
|
|
44
|
+
ctx: input.ctx,
|
|
45
|
+
runId,
|
|
46
|
+
name: input.name,
|
|
47
|
+
run: async (signal, onStarted) => {
|
|
48
|
+
await input.execute(
|
|
49
|
+
{ ...input.ctx, signal },
|
|
50
|
+
{
|
|
51
|
+
...backgroundOptions,
|
|
52
|
+
signal,
|
|
53
|
+
onRunMetadata(metadata) {
|
|
54
|
+
onStarted();
|
|
55
|
+
return backgroundOptions.onRunMetadata?.(metadata);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const message = unknownErrorMessage(error);
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: `Background workflow did not start: ${message}` }],
|
|
65
|
+
details: { error: "background_start_failed", message, runId },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `Background workflow "${input.name}" started.\nRun ID: ${runId}\nDurable record: .pi/.workflow-runs/${runId}.run.json`,
|
|
72
|
+
}],
|
|
73
|
+
details: { background: true, state: "running", name: input.name, runId },
|
|
74
|
+
};
|
|
75
|
+
}
|