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.
Files changed (88) hide show
  1. package/.pi/extensions/pi-workflow-engine/index.ts +418 -213
  2. package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +11 -4
  3. package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +147 -0
  4. package/.pi/extensions/pi-workflow-engine/src/agent-limits.ts +35 -0
  5. package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +399 -0
  6. package/.pi/extensions/pi-workflow-engine/src/agent-retry.ts +116 -0
  7. package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +91 -0
  8. package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +154 -530
  9. package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +256 -0
  10. package/.pi/extensions/pi-workflow-engine/src/agent-session-providers.ts +50 -0
  11. package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +368 -0
  12. package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +87 -39
  13. package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +89 -0
  14. package/.pi/extensions/pi-workflow-engine/src/background-workflow-tool.ts +75 -0
  15. package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +492 -0
  16. package/.pi/extensions/pi-workflow-engine/src/budget.ts +10 -3
  17. package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +55 -3
  18. package/.pi/extensions/pi-workflow-engine/src/command-completions.ts +36 -0
  19. package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +230 -61
  20. package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +38 -55
  21. package/.pi/extensions/pi-workflow-engine/src/discovery.ts +70 -17
  22. package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +59 -13
  23. package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +123 -63
  24. package/.pi/extensions/pi-workflow-engine/src/engine.ts +357 -75
  25. package/.pi/extensions/pi-workflow-engine/src/filesystem-error.ts +4 -0
  26. package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +66 -0
  27. package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +138 -0
  28. package/.pi/extensions/pi-workflow-engine/src/identity-fingerprint.ts +15 -0
  29. package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +10 -5
  30. package/.pi/extensions/pi-workflow-engine/src/journal.ts +156 -66
  31. package/.pi/extensions/pi-workflow-engine/src/model-profile-command.ts +167 -0
  32. package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +310 -0
  33. package/.pi/extensions/pi-workflow-engine/src/options.ts +96 -6
  34. package/.pi/extensions/pi-workflow-engine/src/pi-compat.ts +32 -0
  35. package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +203 -39
  36. package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +50 -0
  37. package/.pi/extensions/pi-workflow-engine/src/progress.ts +74 -122
  38. package/.pi/extensions/pi-workflow-engine/src/provider-usage-limit.ts +189 -0
  39. package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +48 -0
  40. package/.pi/extensions/pi-workflow-engine/src/research-contract.ts +89 -0
  41. package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +289 -0
  42. package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +701 -0
  43. package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +49 -0
  44. package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +75 -16
  45. package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +83 -17
  46. package/.pi/extensions/pi-workflow-engine/src/review/review-budget.ts +45 -0
  47. package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +117 -0
  48. package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +14 -66
  49. package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +38 -12
  50. package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +42 -0
  51. package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +18 -22
  52. package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +128 -56
  53. package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +153 -0
  54. package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +294 -0
  55. package/.pi/extensions/pi-workflow-engine/src/review-diff-target.ts +130 -0
  56. package/.pi/extensions/pi-workflow-engine/src/session-identity.ts +6 -0
  57. package/.pi/extensions/pi-workflow-engine/src/structured-output.ts +32 -0
  58. package/.pi/extensions/pi-workflow-engine/src/tool-capabilities.ts +44 -0
  59. package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +150 -0
  60. package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +401 -0
  61. package/.pi/extensions/pi-workflow-engine/src/types.ts +87 -25
  62. package/.pi/extensions/pi-workflow-engine/src/ui/dynamax-editor-decoration.ts +220 -0
  63. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +26 -6
  64. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +88 -46
  65. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +7 -70
  66. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-viewer-layout.ts +51 -0
  67. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +12 -67
  68. package/.pi/extensions/pi-workflow-engine/src/unknown-error.ts +8 -0
  69. package/.pi/extensions/pi-workflow-engine/src/usage.ts +124 -16
  70. package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +23 -3
  71. package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +121 -0
  72. package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +19 -1
  73. package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +72 -0
  74. package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +354 -0
  75. package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +162 -0
  76. package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +632 -0
  77. package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +172 -0
  78. package/.pi/extensions/pi-workflow-engine/src/workflow-usage-limit-scheduler.ts +80 -0
  79. package/.pi/extensions/pi-workflow-engine/src/workflows.ts +30 -3
  80. package/.pi/extensions/pi-workflow-engine/src/worktree.ts +321 -43
  81. package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +62 -64
  82. package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +17 -17
  83. package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +15 -16
  84. package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +15 -15
  85. package/.pi/extensions/pi-workflow-engine/workflows/research.ts +170 -0
  86. package/README.md +82 -207
  87. package/USAGE.md +295 -21
  88. package/package.json +9 -9
@@ -1,11 +1,16 @@
1
- import { DefaultResourceLoader, getAgentDir, type ResourceDiagnostic, type Skill } from "@earendil-works/pi-coding-agent";
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
- export type AgentSkillOption = readonly string[] | undefined;
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
- export interface AgentSkillResourceSetup {
18
- readonly resourceLoader: DefaultResourceLoader;
19
- readonly selectedSkills: readonly Skill[];
20
- readonly unmatchedSelectors: readonly string[];
21
- readonly diagnostics: readonly ResourceDiagnostic[];
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), source: "explicit", strict: true };
59
+ return { selectors: normalizeExplicitSkillSelectors(explicitSkills), strict: true };
40
60
  }
41
- return { selectors: extractSkillSelectorsFromText(prompt), source: "prompt", strict: false };
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
- export async function createAgentSkillResourceLoader(options: {
87
- readonly cwd: string;
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
- }): Promise<AgentSkillResourceSetup> {
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 loader = new DefaultResourceLoader({
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
- if (!request.strict && resolution.unmatched.length > 0) {
122
- options.log?.(`Ignoring unrecognized subagent skill mention${resolution.unmatched.length === 1 ? "" : "s"}: ${resolution.unmatched.join(", ")}`);
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
- return { resourceLoader: loader, selectedSkills: resolution.selected, unmatchedSelectors: resolution.unmatched, diagnostics };
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
- export function appendSkillReminder(prompt: string, skills: readonly Pick<Skill, "name" | "filePath">[]): string {
129
- if (skills.length === 0) return prompt;
130
- const skillList = skills.map((skill) => `${skill.name} (${skill.filePath})`).join(", ");
131
- return `${prompt}\n\nWorkflow subagent skills enabled: ${skillList}. If you rely on an enabled skill, first read its SKILL.md with the read tool and follow its instructions. No other skills are available in this subagent.`;
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
+ }