@yycholla/pi-dynamic-workflows 3.3.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 (179) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +325 -0
  3. package/assets/readme/hero.png +0 -0
  4. package/assets/readme/hero.svg +70 -0
  5. package/assets/readme/package-cover.png +0 -0
  6. package/assets/readme/package-cover.svg +66 -0
  7. package/assets/readme/workflow.png +0 -0
  8. package/assets/readme/workflow.svg +77 -0
  9. package/dist/accept-workflow-guidance.d.ts +14 -0
  10. package/dist/accept-workflow-guidance.js +53 -0
  11. package/dist/adversarial-review.d.ts +30 -0
  12. package/dist/adversarial-review.js +107 -0
  13. package/dist/agent-history.d.ts +20 -0
  14. package/dist/agent-history.js +122 -0
  15. package/dist/agent-registry.d.ts +83 -0
  16. package/dist/agent-registry.js +190 -0
  17. package/dist/agent.d.ts +364 -0
  18. package/dist/agent.js +714 -0
  19. package/dist/builtin-commands.d.ts +19 -0
  20. package/dist/builtin-commands.js +251 -0
  21. package/dist/builtin-workflows.d.ts +45 -0
  22. package/dist/builtin-workflows.js +121 -0
  23. package/dist/code-review.d.ts +26 -0
  24. package/dist/code-review.js +181 -0
  25. package/dist/config.d.ts +37 -0
  26. package/dist/config.js +44 -0
  27. package/dist/deep-research.d.ts +30 -0
  28. package/dist/deep-research.js +124 -0
  29. package/dist/display.d.ts +134 -0
  30. package/dist/display.js +248 -0
  31. package/dist/effort-command.d.ts +28 -0
  32. package/dist/effort-command.js +68 -0
  33. package/dist/enums.d.ts +69 -0
  34. package/dist/enums.js +78 -0
  35. package/dist/errors.d.ts +113 -0
  36. package/dist/errors.js +140 -0
  37. package/dist/extension-reload.d.ts +37 -0
  38. package/dist/extension-reload.js +78 -0
  39. package/dist/fs-persistence.d.ts +63 -0
  40. package/dist/fs-persistence.js +102 -0
  41. package/dist/index.d.ts +57 -0
  42. package/dist/index.js +35 -0
  43. package/dist/logger.d.ts +21 -0
  44. package/dist/logger.js +66 -0
  45. package/dist/model-routing.d.ts +30 -0
  46. package/dist/model-routing.js +50 -0
  47. package/dist/model-spec.d.ts +29 -0
  48. package/dist/model-spec.js +252 -0
  49. package/dist/model-tier-config.d.ts +133 -0
  50. package/dist/model-tier-config.js +249 -0
  51. package/dist/run-persistence.d.ts +180 -0
  52. package/dist/run-persistence.js +294 -0
  53. package/dist/saved-commands.d.ts +28 -0
  54. package/dist/saved-commands.js +100 -0
  55. package/dist/shared-store.d.ts +98 -0
  56. package/dist/shared-store.js +212 -0
  57. package/dist/structured-output.d.ts +19 -0
  58. package/dist/structured-output.js +30 -0
  59. package/dist/task-panel.d.ts +61 -0
  60. package/dist/task-panel.js +422 -0
  61. package/dist/usage-limit-scheduler.d.ts +145 -0
  62. package/dist/usage-limit-scheduler.js +368 -0
  63. package/dist/web-tools.d.ts +20 -0
  64. package/dist/web-tools.js +120 -0
  65. package/dist/workflow-authoring-coverage.d.ts +70 -0
  66. package/dist/workflow-authoring-coverage.js +421 -0
  67. package/dist/workflow-authoring-reference.d.ts +20 -0
  68. package/dist/workflow-authoring-reference.js +156 -0
  69. package/dist/workflow-capability-contract.d.ts +131 -0
  70. package/dist/workflow-capability-contract.js +604 -0
  71. package/dist/workflow-commands.d.ts +18 -0
  72. package/dist/workflow-commands.js +260 -0
  73. package/dist/workflow-comprehension.d.ts +133 -0
  74. package/dist/workflow-comprehension.js +1321 -0
  75. package/dist/workflow-context-measurement.d.ts +72 -0
  76. package/dist/workflow-context-measurement.js +213 -0
  77. package/dist/workflow-control-tool.d.ts +30 -0
  78. package/dist/workflow-control-tool.js +176 -0
  79. package/dist/workflow-delivery-choice.d.ts +20 -0
  80. package/dist/workflow-delivery-choice.js +48 -0
  81. package/dist/workflow-editor.d.ts +93 -0
  82. package/dist/workflow-editor.js +363 -0
  83. package/dist/workflow-manager.d.ts +492 -0
  84. package/dist/workflow-manager.js +1124 -0
  85. package/dist/workflow-paths.d.ts +22 -0
  86. package/dist/workflow-paths.js +46 -0
  87. package/dist/workflow-release-gate.d.ts +39 -0
  88. package/dist/workflow-release-gate.js +309 -0
  89. package/dist/workflow-saved.d.ts +38 -0
  90. package/dist/workflow-saved.js +126 -0
  91. package/dist/workflow-settings.d.ts +70 -0
  92. package/dist/workflow-settings.js +131 -0
  93. package/dist/workflow-tool.d.ts +71 -0
  94. package/dist/workflow-tool.js +367 -0
  95. package/dist/workflow-ui.d.ts +182 -0
  96. package/dist/workflow-ui.js +1587 -0
  97. package/dist/workflow.d.ts +333 -0
  98. package/dist/workflow.js +1151 -0
  99. package/dist/workflows-models-command.d.ts +31 -0
  100. package/dist/workflows-models-command.js +156 -0
  101. package/dist/worktree.d.ts +25 -0
  102. package/dist/worktree.js +61 -0
  103. package/extensions/workflow.ts +151 -0
  104. package/package.json +104 -0
  105. package/skills/workflow-authoring/SKILL.md +30 -0
  106. package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
  107. package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
  108. package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
  109. package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
  110. package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
  111. package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
  112. package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
  113. package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
  114. package/skills/workflow-authoring/examples/structured-output.js +37 -0
  115. package/skills/workflow-authoring/examples/tournament.js +81 -0
  116. package/skills/workflow-authoring/examples/validated-gate.js +63 -0
  117. package/skills/workflow-authoring/references/capabilities.md +41 -0
  118. package/skills/workflow-authoring/references/capability-details.md +357 -0
  119. package/skills/workflow-authoring/references/common-helpers.md +4 -0
  120. package/skills/workflow-authoring/references/debugging.md +27 -0
  121. package/skills/workflow-authoring/references/focused-recipes.md +11 -0
  122. package/skills/workflow-authoring/references/helpers.md +9 -0
  123. package/skills/workflow-authoring/references/lifecycle.md +33 -0
  124. package/skills/workflow-authoring/references/pattern-selection.md +15 -0
  125. package/skills/workflow-authoring/references/quality-helpers.md +8 -0
  126. package/skills/workflow-authoring/references/registry-ownership.md +15 -0
  127. package/skills/workflow-authoring/references/retry-helper.md +5 -0
  128. package/skills/workflow-authoring/references/review.md +43 -0
  129. package/skills/workflow-authoring/references/runtime.md +27 -0
  130. package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
  131. package/skills/workflow-authoring/references/versions.md +13 -0
  132. package/skills/workflow-patterns/SKILL.md +51 -0
  133. package/src/accept-workflow-guidance.ts +71 -0
  134. package/src/adversarial-review.ts +120 -0
  135. package/src/agent-history.ts +157 -0
  136. package/src/agent-registry.ts +221 -0
  137. package/src/agent.ts +929 -0
  138. package/src/builtin-commands.ts +286 -0
  139. package/src/builtin-workflows.ts +155 -0
  140. package/src/code-review.ts +183 -0
  141. package/src/config.ts +55 -0
  142. package/src/deep-research.ts +135 -0
  143. package/src/display.ts +367 -0
  144. package/src/effort-command.ts +87 -0
  145. package/src/enums.ts +77 -0
  146. package/src/errors.ts +199 -0
  147. package/src/extension-reload.ts +100 -0
  148. package/src/fs-persistence.ts +124 -0
  149. package/src/index.ts +176 -0
  150. package/src/logger.ts +88 -0
  151. package/src/model-routing.ts +73 -0
  152. package/src/model-spec.ts +309 -0
  153. package/src/model-tier-config.ts +296 -0
  154. package/src/run-persistence.ts +484 -0
  155. package/src/saved-commands.ts +115 -0
  156. package/src/shared-store.ts +228 -0
  157. package/src/structured-output.ts +47 -0
  158. package/src/task-panel.ts +490 -0
  159. package/src/usage-limit-scheduler.ts +432 -0
  160. package/src/web-tools.ts +124 -0
  161. package/src/workflow-authoring-coverage.ts +486 -0
  162. package/src/workflow-authoring-reference.ts +186 -0
  163. package/src/workflow-capability-contract.ts +806 -0
  164. package/src/workflow-commands.ts +287 -0
  165. package/src/workflow-comprehension.ts +1673 -0
  166. package/src/workflow-context-measurement.ts +262 -0
  167. package/src/workflow-control-tool.ts +238 -0
  168. package/src/workflow-delivery-choice.ts +69 -0
  169. package/src/workflow-editor.ts +444 -0
  170. package/src/workflow-manager.ts +1405 -0
  171. package/src/workflow-paths.ts +63 -0
  172. package/src/workflow-release-gate.ts +529 -0
  173. package/src/workflow-saved.ts +180 -0
  174. package/src/workflow-settings.ts +194 -0
  175. package/src/workflow-tool.ts +464 -0
  176. package/src/workflow-ui.ts +1789 -0
  177. package/src/workflow.ts +1615 -0
  178. package/src/workflows-models-command.ts +211 -0
  179. package/src/worktree.ts +76 -0
@@ -0,0 +1,72 @@
1
+ /** Package-relative generated context-measurement artifact. */
2
+ export declare const WORKFLOW_CONTEXT_MEASUREMENT_PATH = "docs/workflow-context-surfaces.json";
3
+ /** Canonical task profiles used for stable byte-based on-demand context measurements. */
4
+ export declare const WORKFLOW_AUTHORING_PROFILES: readonly [{
5
+ readonly name: "write";
6
+ readonly files: readonly ["skills/workflow-authoring/SKILL.md", "skills/workflow-authoring/references/runtime.md", "skills/workflow-authoring/references/pattern-selection.md", "skills/workflow-authoring/references/focused-recipes.md", "skills/workflow-authoring/examples/fan-out-and-synthesize.js", "skills/workflow-authoring/examples/structured-output.js"];
7
+ }, {
8
+ readonly name: "edit";
9
+ readonly files: readonly ["skills/workflow-authoring/SKILL.md", "skills/workflow-authoring/references/runtime.md", "skills/workflow-authoring/references/lifecycle.md", "skills/workflow-authoring/references/focused-recipes.md", "skills/workflow-authoring/examples/phased-budgets.js", "skills/workflow-authoring/examples/saved-nested-workflows.js"];
10
+ }, {
11
+ readonly name: "review";
12
+ readonly files: readonly ["skills/workflow-authoring/SKILL.md", "skills/workflow-authoring/references/runtime.md", "skills/workflow-authoring/references/review.md", "skills/workflow-authoring/references/quality-helpers.md", "skills/workflow-authoring/examples/adversarial-verification.js"];
13
+ }, {
14
+ readonly name: "debug";
15
+ readonly files: readonly ["skills/workflow-authoring/SKILL.md", "skills/workflow-authoring/references/runtime.md", "skills/workflow-authoring/references/debugging.md", "skills/workflow-authoring/references/specialized-helpers.md", "skills/workflow-authoring/examples/validated-gate.js"];
16
+ }, {
17
+ readonly name: "loop";
18
+ readonly files: readonly ["skills/workflow-authoring/SKILL.md", "skills/workflow-authoring/references/runtime.md", "skills/workflow-authoring/references/pattern-selection.md", "skills/workflow-authoring/references/lifecycle.md", "skills/workflow-authoring/references/focused-recipes.md", "skills/workflow-authoring/examples/loop-until-done.js", "skills/workflow-authoring/examples/structured-output.js"];
19
+ }, {
20
+ readonly name: "retry";
21
+ readonly files: readonly ["skills/workflow-authoring/SKILL.md", "skills/workflow-authoring/references/runtime.md", "skills/workflow-authoring/references/retry-helper.md", "skills/workflow-authoring/references/focused-recipes.md", "skills/workflow-authoring/examples/bounded-semantic-retry.js", "skills/workflow-authoring/examples/structured-output.js"];
22
+ }];
23
+ interface ByteSurface {
24
+ serialization: string;
25
+ bytes: number;
26
+ }
27
+ /** One registered skill's always-on discovery-entry byte cost. */
28
+ export interface SkillDiscoverySurface extends ByteSurface {
29
+ root: string;
30
+ }
31
+ /** Versioned byte measurements for always-on, discovery, corpus, and representative authoring surfaces. */
32
+ export interface WorkflowContextMeasurement {
33
+ formatVersion: 3;
34
+ encoding: "utf8";
35
+ sources: ["src/workflow-tool.ts", "skills/workflow-authoring", "package.json#pi.skills"];
36
+ surfaces: {
37
+ permanentWorkflowPrompt: ByteSurface;
38
+ providerVisibleWorkflowToolDefinition: ByteSurface;
39
+ /**
40
+ * Every skill this package registers (package.json's `pi.skills` — read
41
+ * from disk, not hardcoded here) contributes an always-on discovery entry
42
+ * (name + description) the model sees regardless of whether the skill is
43
+ * ever loaded. This sums all of them, not just workflow-authoring, so a
44
+ * new skill's always-on cost can't silently go untracked.
45
+ */
46
+ registeredSkillsDiscovery: ByteSurface & {
47
+ skills: SkillDiscoverySurface[];
48
+ };
49
+ ordinaryWorkflowOwnedAlwaysOn: ByteSurface;
50
+ workflowAuthoringSkillCorpus: ByteSurface & {
51
+ files: number;
52
+ };
53
+ representativeAuthoringProfiles: {
54
+ serialization: "sum of UTF-8 bytes for each profile's declared package-relative files";
55
+ medianBytes: number;
56
+ profiles: Array<{
57
+ name: string;
58
+ files: string[];
59
+ bytes: number;
60
+ }>;
61
+ };
62
+ };
63
+ }
64
+ /** Measures permanent, discovery, corpus, and canonical on-demand workflow context surfaces. */
65
+ export declare function measureWorkflowContextSurfaces(root?: string): WorkflowContextMeasurement;
66
+ /** Render the current measurement as deterministic formatted JSON. */
67
+ export declare function renderWorkflowContextMeasurement(): string;
68
+ /** Write the generated measurement under root and return the measured values. */
69
+ export declare function writeWorkflowContextMeasurement(root: string): WorkflowContextMeasurement;
70
+ /** Report whether committed or supplied measurement JSON matches current package bytes. */
71
+ export declare function checkWorkflowContextMeasurement(root: string, actual?: string): boolean;
72
+ export {};
@@ -0,0 +1,213 @@
1
+ import { readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { createWorkflowTool } from "./workflow-tool.js";
4
+ /** Package-relative generated context-measurement artifact. */
5
+ export const WORKFLOW_CONTEXT_MEASUREMENT_PATH = "docs/workflow-context-surfaces.json";
6
+ const ROOT = join(import.meta.dirname, "..");
7
+ const SKILL_ROOT = "skills/workflow-authoring";
8
+ const SKILL_PATH = `${SKILL_ROOT}/SKILL.md`;
9
+ /** Canonical task profiles used for stable byte-based on-demand context measurements. */
10
+ export const WORKFLOW_AUTHORING_PROFILES = [
11
+ {
12
+ name: "write",
13
+ files: [
14
+ SKILL_PATH,
15
+ `${SKILL_ROOT}/references/runtime.md`,
16
+ `${SKILL_ROOT}/references/pattern-selection.md`,
17
+ `${SKILL_ROOT}/references/focused-recipes.md`,
18
+ `${SKILL_ROOT}/examples/fan-out-and-synthesize.js`,
19
+ `${SKILL_ROOT}/examples/structured-output.js`,
20
+ ],
21
+ },
22
+ {
23
+ name: "edit",
24
+ files: [
25
+ SKILL_PATH,
26
+ `${SKILL_ROOT}/references/runtime.md`,
27
+ `${SKILL_ROOT}/references/lifecycle.md`,
28
+ `${SKILL_ROOT}/references/focused-recipes.md`,
29
+ `${SKILL_ROOT}/examples/phased-budgets.js`,
30
+ `${SKILL_ROOT}/examples/saved-nested-workflows.js`,
31
+ ],
32
+ },
33
+ {
34
+ name: "review",
35
+ files: [
36
+ SKILL_PATH,
37
+ `${SKILL_ROOT}/references/runtime.md`,
38
+ `${SKILL_ROOT}/references/review.md`,
39
+ `${SKILL_ROOT}/references/quality-helpers.md`,
40
+ `${SKILL_ROOT}/examples/adversarial-verification.js`,
41
+ ],
42
+ },
43
+ {
44
+ name: "debug",
45
+ files: [
46
+ SKILL_PATH,
47
+ `${SKILL_ROOT}/references/runtime.md`,
48
+ `${SKILL_ROOT}/references/debugging.md`,
49
+ `${SKILL_ROOT}/references/specialized-helpers.md`,
50
+ `${SKILL_ROOT}/examples/validated-gate.js`,
51
+ ],
52
+ },
53
+ {
54
+ name: "loop",
55
+ files: [
56
+ SKILL_PATH,
57
+ `${SKILL_ROOT}/references/runtime.md`,
58
+ `${SKILL_ROOT}/references/pattern-selection.md`,
59
+ `${SKILL_ROOT}/references/lifecycle.md`,
60
+ `${SKILL_ROOT}/references/focused-recipes.md`,
61
+ `${SKILL_ROOT}/examples/loop-until-done.js`,
62
+ `${SKILL_ROOT}/examples/structured-output.js`,
63
+ ],
64
+ },
65
+ {
66
+ name: "retry",
67
+ files: [
68
+ SKILL_PATH,
69
+ `${SKILL_ROOT}/references/runtime.md`,
70
+ `${SKILL_ROOT}/references/retry-helper.md`,
71
+ `${SKILL_ROOT}/references/focused-recipes.md`,
72
+ `${SKILL_ROOT}/examples/bounded-semantic-retry.js`,
73
+ `${SKILL_ROOT}/examples/structured-output.js`,
74
+ ],
75
+ },
76
+ ];
77
+ function bytes(value) {
78
+ return Buffer.byteLength(value, "utf8");
79
+ }
80
+ function fileBytes(root, path) {
81
+ return bytes(readFileSync(join(root, path), "utf8"));
82
+ }
83
+ function skillFiles(root) {
84
+ const absoluteRoot = join(root, SKILL_ROOT);
85
+ const pending = [absoluteRoot];
86
+ const files = [];
87
+ while (pending.length > 0) {
88
+ const directory = pending.pop();
89
+ if (!directory)
90
+ continue;
91
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
92
+ const absolute = join(directory, entry.name);
93
+ if (entry.isDirectory())
94
+ pending.push(absolute);
95
+ else if (entry.isFile())
96
+ files.push(relative(root, absolute).replaceAll("\\", "/"));
97
+ }
98
+ }
99
+ return files.sort();
100
+ }
101
+ /**
102
+ * Every skill root this package registers, read from package.json's
103
+ * `pi.skills` array rather than hardcoded — so a newly added skill is picked
104
+ * up automatically instead of silently missing from the always-on tally.
105
+ */
106
+ function registeredSkillRoots(root) {
107
+ const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
108
+ const skills = manifest.pi?.skills;
109
+ if (!Array.isArray(skills) || skills.length === 0 || !skills.every((s) => typeof s === "string")) {
110
+ throw new Error("package.json must declare a non-empty pi.skills array of skill root paths");
111
+ }
112
+ return skills;
113
+ }
114
+ function skillDiscoveryEntry(root, skillRoot) {
115
+ const skillPath = `${skillRoot}/SKILL.md`;
116
+ const skill = readFileSync(join(root, skillPath), "utf8");
117
+ const name = /^name:\s*(.+)$/m.exec(skill)?.[1]?.trim();
118
+ const description = /^description:\s*(.+)$/m.exec(skill)?.[1]?.trim();
119
+ if (!name || !description)
120
+ throw new Error(`${skillPath} must declare name and description`);
121
+ return [
122
+ "<skill>",
123
+ ` <name>${name}</name>`,
124
+ ` <description>${description}</description>`,
125
+ ` <location>${skillPath}</location>`,
126
+ "</skill>",
127
+ ].join("\n");
128
+ }
129
+ function median(values) {
130
+ const sorted = [...values].sort((left, right) => left - right);
131
+ const middle = sorted.length / 2;
132
+ return sorted.length % 2 === 0
133
+ ? ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2
134
+ : (sorted[Math.floor(middle)] ?? 0);
135
+ }
136
+ /** Measures permanent, discovery, corpus, and canonical on-demand workflow context surfaces. */
137
+ export function measureWorkflowContextSurfaces(root = ROOT) {
138
+ const tool = createWorkflowTool();
139
+ const permanentWorkflowPrompt = [
140
+ `- workflow: ${tool.promptSnippet}`,
141
+ ...(tool.promptGuidelines ?? []).map((guideline) => `- ${guideline}`),
142
+ ].join("\n");
143
+ const providerVisibleWorkflowToolDefinition = JSON.stringify({
144
+ name: tool.name,
145
+ description: tool.description,
146
+ parameters: tool.parameters,
147
+ });
148
+ const skillRoots = registeredSkillRoots(root);
149
+ const skillDiscoverySurfaces = skillRoots.map((skillRoot) => ({
150
+ root: skillRoot,
151
+ serialization: "UTF-8 bytes of normalized Pi skill XML with package-relative location",
152
+ bytes: bytes(skillDiscoveryEntry(root, skillRoot)),
153
+ }));
154
+ const registeredSkillsDiscoveryBytes = skillDiscoverySurfaces.reduce((sum, surface) => sum + surface.bytes, 0);
155
+ const corpusFiles = skillFiles(root);
156
+ const corpusBytes = corpusFiles.reduce((sum, path) => sum + fileBytes(root, path), 0);
157
+ const profiles = WORKFLOW_AUTHORING_PROFILES.map((profile) => ({
158
+ name: profile.name,
159
+ files: [...profile.files],
160
+ bytes: profile.files.reduce((sum, path) => sum + fileBytes(root, path), 0),
161
+ }));
162
+ const promptBytes = bytes(permanentWorkflowPrompt);
163
+ const toolBytes = bytes(providerVisibleWorkflowToolDefinition);
164
+ return {
165
+ formatVersion: 3,
166
+ encoding: "utf8",
167
+ sources: ["src/workflow-tool.ts", "skills/workflow-authoring", "package.json#pi.skills"],
168
+ surfaces: {
169
+ permanentWorkflowPrompt: {
170
+ serialization: "UTF-8 bytes of LF-joined Pi prompt lines",
171
+ bytes: promptBytes,
172
+ },
173
+ providerVisibleWorkflowToolDefinition: {
174
+ serialization: "UTF-8 bytes of JSON.stringify({ name, description, parameters })",
175
+ bytes: toolBytes,
176
+ },
177
+ registeredSkillsDiscovery: {
178
+ serialization: "sum of UTF-8 bytes of normalized Pi skill XML (name + description + location) across every root in package.json's pi.skills",
179
+ bytes: registeredSkillsDiscoveryBytes,
180
+ skills: skillDiscoverySurfaces,
181
+ },
182
+ ordinaryWorkflowOwnedAlwaysOn: {
183
+ serialization: "sum of permanent prompt, provider-visible tool definition, and every registered skill's discovery bytes",
184
+ bytes: promptBytes + toolBytes + registeredSkillsDiscoveryBytes,
185
+ },
186
+ workflowAuthoringSkillCorpus: {
187
+ serialization: "sum of UTF-8 bytes for every file under skills/workflow-authoring",
188
+ files: corpusFiles.length,
189
+ bytes: corpusBytes,
190
+ },
191
+ representativeAuthoringProfiles: {
192
+ serialization: "sum of UTF-8 bytes for each profile's declared package-relative files",
193
+ medianBytes: median(profiles.map(({ bytes: profileBytes }) => profileBytes)),
194
+ profiles,
195
+ },
196
+ },
197
+ };
198
+ }
199
+ /** Render the current measurement as deterministic formatted JSON. */
200
+ export function renderWorkflowContextMeasurement() {
201
+ return `${JSON.stringify(measureWorkflowContextSurfaces(), null, 2)}\n`;
202
+ }
203
+ /** Write the generated measurement under root and return the measured values. */
204
+ export function writeWorkflowContextMeasurement(root) {
205
+ const measurement = measureWorkflowContextSurfaces(root);
206
+ writeFileSync(join(root, WORKFLOW_CONTEXT_MEASUREMENT_PATH), `${JSON.stringify(measurement, null, 2)}\n`);
207
+ return measurement;
208
+ }
209
+ /** Report whether committed or supplied measurement JSON matches current package bytes. */
210
+ export function checkWorkflowContextMeasurement(root, actual) {
211
+ const committed = actual ?? readFileSync(join(root, WORKFLOW_CONTEXT_MEASUREMENT_PATH), "utf8");
212
+ return committed === `${JSON.stringify(measureWorkflowContextSurfaces(root), null, 2)}\n`;
213
+ }
@@ -0,0 +1,30 @@
1
+ import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import { type Static, Type } from "typebox";
3
+ import type { RunStatus } from "./run-persistence.js";
4
+ import type { WorkflowManager } from "./workflow-manager.js";
5
+ declare const workflowControlSchema: Type.TObject<{
6
+ action: Type.TUnion<[Type.TLiteral<"list">, Type.TLiteral<"status">, Type.TLiteral<"pause">, Type.TLiteral<"resume">, Type.TLiteral<"stop">]>;
7
+ runId: Type.TOptional<Type.TString>;
8
+ }>;
9
+ export type WorkflowControlInput = Static<typeof workflowControlSchema>;
10
+ export interface WorkflowControlToolOptions {
11
+ manager: WorkflowManager;
12
+ }
13
+ export interface WorkflowControlRunDetails {
14
+ runId: string;
15
+ workflowName: string;
16
+ status: RunStatus;
17
+ phase: string | null;
18
+ counts: {
19
+ total: number;
20
+ done: number;
21
+ running: number;
22
+ queued: number;
23
+ error: number;
24
+ skipped: number;
25
+ };
26
+ activeLabels: string[];
27
+ tokenTotal: number;
28
+ }
29
+ export declare function createWorkflowControlTool(options: WorkflowControlToolOptions): ToolDefinition<typeof workflowControlSchema, Record<string, unknown>>;
30
+ export {};
@@ -0,0 +1,176 @@
1
+ import { defineTool } from "@earendil-works/pi-coding-agent";
2
+ import { Type } from "typebox";
3
+ import { aggregateAgentUsage, tokenFigures } from "./display.js";
4
+ // A tool's top-level parameter schema must be a JSON Schema object (`type:
5
+ // "object"`). A discriminated Type.Union of two objects serializes to a
6
+ // top-level `anyOf` with no `type`, which strict providers (e.g. DeepSeek)
7
+ // reject with "schema must be type object, got type: null". So the schema is a
8
+ // single object: `action` is the full set of verbs and `runId` is optional at
9
+ // the schema level. The per-action requirement (runId is mandatory for every
10
+ // action except `list`, and `list` takes no runId) is enforced at runtime in
11
+ // normalizeInput() and guarded again in execute().
12
+ const workflowControlSchema = Type.Object({
13
+ action: Type.Union([
14
+ Type.Literal("list"),
15
+ Type.Literal("status"),
16
+ Type.Literal("pause"),
17
+ Type.Literal("resume"),
18
+ Type.Literal("stop"),
19
+ ], { description: "list = all runs (no runId); status/pause/resume/stop act on one run and require runId." }),
20
+ runId: Type.Optional(Type.String({
21
+ minLength: 1,
22
+ description: "Canonical workflow run ID. Required for status, pause, resume, and stop; omit for list.",
23
+ })),
24
+ }, { additionalProperties: false });
25
+ export function createWorkflowControlTool(options) {
26
+ const manager = options.manager;
27
+ return defineTool({
28
+ name: "workflow_control",
29
+ label: "Workflow Control",
30
+ description: "List and inspect workflow runs, or pause, resume, and stop them without asking the user to run slash commands.",
31
+ promptSnippet: "Inspect and manage workflow runs directly by canonical run ID.",
32
+ promptGuidelines: [
33
+ "Use workflow_control for workflow lifecycle management; do not ask the user to type /workflows when this tool can perform the action.",
34
+ "Use stop to terminate or quit a run. Closing the navigator does not stop a run.",
35
+ ],
36
+ parameters: workflowControlSchema,
37
+ prepareArguments: normalizeInput,
38
+ async execute(_toolCallId, params) {
39
+ if (params.action === "list") {
40
+ const runs = manager.listRuns();
41
+ const summaries = runs.map((run) => summarizeRun(run, manager.getSnapshot(run.runId)));
42
+ return result(summaries.length
43
+ ? `action=list result=ok runs=${summaries.length}\n${summaries.map(formatRun).join("\n")}`
44
+ : "action=list result=ok runs=0", { action: "list", result: "ok", runs: summaries });
45
+ }
46
+ // runId is optional in the schema (see workflowControlSchema) but required
47
+ // for every non-list action; normalizeInput already enforces this, and this
48
+ // guard both narrows the type and returns a structured error if a model
49
+ // somehow calls a run action without one.
50
+ if (!params.runId)
51
+ return controlError(params.action, "", "runId is required for this action", ["list"]);
52
+ const run = findRun(manager, params.runId);
53
+ if (!run)
54
+ return controlError(params.action, params.runId, "run not found", ["list"]);
55
+ try {
56
+ switch (params.action) {
57
+ case "status": {
58
+ const summary = summarizeRun(run, manager.getSnapshot(run.runId));
59
+ return result(`action=status result=ok ${formatRun(summary)}`, {
60
+ action: "status",
61
+ result: "ok",
62
+ run: summary,
63
+ });
64
+ }
65
+ case "pause":
66
+ if (!manager.pause(run.runId))
67
+ return invalidTransition("pause", run);
68
+ return actionSuccess("pause", "paused", currentSummary(manager, run));
69
+ case "resume":
70
+ if (!(await manager.resume(run.runId)))
71
+ return invalidTransition("resume", run);
72
+ return actionSuccess("resume", "resumed", currentSummary(manager, run));
73
+ case "stop":
74
+ if (!manager.stop(run.runId))
75
+ return invalidTransition("stop", run);
76
+ return actionSuccess("stop", "stopped", currentSummary(manager, run));
77
+ }
78
+ }
79
+ catch (err) {
80
+ // A transient persistence I/O error (or any unexpected throw from the
81
+ // manager) shouldn't surface as a raw stack trace to the model — report
82
+ // it via the tool's normal structured error shape instead.
83
+ const message = err instanceof Error ? err.message : String(err);
84
+ return controlError(params.action, run.runId, message, allowedActions(run.status));
85
+ }
86
+ },
87
+ });
88
+ }
89
+ function normalizeInput(value) {
90
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
91
+ throw new Error("workflow_control requires an object argument");
92
+ }
93
+ const input = value;
94
+ const actions = new Set(["list", "status", "pause", "resume", "stop"]);
95
+ if (typeof input.action !== "string" || !actions.has(input.action)) {
96
+ throw new Error("workflow_control requires action: list|status|pause|resume|stop");
97
+ }
98
+ const allowedKeys = input.action === "list" ? new Set(["action"]) : new Set(["action", "runId"]);
99
+ const extraKey = Object.keys(input).find((key) => !allowedKeys.has(key));
100
+ if (extraKey)
101
+ throw new Error(`workflow_control action "${input.action}" does not accept ${extraKey}`);
102
+ if (input.action !== "list" && (typeof input.runId !== "string" || !input.runId.trim())) {
103
+ throw new Error(`workflow_control action "${input.action}" requires runId`);
104
+ }
105
+ return input;
106
+ }
107
+ function result(text, details) {
108
+ return { content: [{ type: "text", text }], details };
109
+ }
110
+ function findRun(manager, runId) {
111
+ return manager.listRuns().find((candidate) => candidate.runId === runId);
112
+ }
113
+ function currentSummary(manager, fallback) {
114
+ const current = findRun(manager, fallback.runId) ?? fallback;
115
+ return summarizeRun(current, manager.getSnapshot(current.runId));
116
+ }
117
+ function actionSuccess(action, actionResult, run) {
118
+ return result(`action=${action} result=${actionResult} ${formatRun(run)}`, {
119
+ action,
120
+ result: actionResult,
121
+ run,
122
+ });
123
+ }
124
+ function invalidTransition(action, run) {
125
+ return controlError(action, run.runId, `cannot ${action} run with status ${run.status}`, allowedActions(run.status));
126
+ }
127
+ function controlError(action, runId, message, allowed) {
128
+ return result(`action=${action} result=error runId=${runId} error=${message} allowed=${allowed.join(",") || "none"}`, { action, result: "error", runId, error: message, allowedActions: allowed });
129
+ }
130
+ function allowedActions(status) {
131
+ switch (status) {
132
+ case "running":
133
+ return ["status", "pause", "stop"];
134
+ case "paused":
135
+ return ["status", "resume", "stop"];
136
+ case "failed":
137
+ case "pending":
138
+ return ["status", "resume"];
139
+ case "completed":
140
+ case "aborted":
141
+ return ["status"];
142
+ }
143
+ }
144
+ function summarizeRun(run, live) {
145
+ const agents = live?.agents ?? run.agents;
146
+ const counts = countAgents(agents);
147
+ const liveUsage = tokenFigures(live?.tokenUsage);
148
+ const persistedUsage = tokenFigures(run.tokenUsage);
149
+ const agentUsage = aggregateAgentUsage(agents);
150
+ return {
151
+ runId: run.runId,
152
+ workflowName: live?.name ?? run.workflowName,
153
+ status: run.status,
154
+ phase: live?.currentPhase ?? run.currentPhase ?? null,
155
+ counts,
156
+ activeLabels: agents.filter((agent) => agent.status === "running").map((agent) => agent.label),
157
+ tokenTotal: Math.max(liveUsage.fresh + liveUsage.cacheRead, persistedUsage.fresh + persistedUsage.cacheRead, agentUsage.fresh + agentUsage.cacheRead),
158
+ };
159
+ }
160
+ function countAgents(agents) {
161
+ return {
162
+ total: agents.length,
163
+ done: agents.filter((agent) => agent.status === "done").length,
164
+ running: agents.filter((agent) => agent.status === "running").length,
165
+ queued: agents.filter((agent) => agent.status === "queued").length,
166
+ error: agents.filter((agent) => agent.status === "error").length,
167
+ skipped: agents.filter((agent) => agent.status === "skipped").length,
168
+ };
169
+ }
170
+ function formatRun(run) {
171
+ const active = run.activeLabels.join(",") || "-";
172
+ return `runId=${run.runId} name=${quote(run.workflowName)} status=${run.status} phase=${quote(run.phase ?? "-")} total=${run.counts.total} done=${run.counts.done} running=${run.counts.running} queued=${run.counts.queued} error=${run.counts.error} skipped=${run.counts.skipped} active=${quote(active)} tokens=${run.tokenTotal}`;
173
+ }
174
+ function quote(value) {
175
+ return JSON.stringify(value);
176
+ }
@@ -0,0 +1,20 @@
1
+ /** One model-facing timing decision for invoking the workflow tool. */
2
+ export interface WorkflowDeliveryChoiceScenario {
3
+ id: string;
4
+ prompt: string;
5
+ expectedBackground: boolean;
6
+ }
7
+ /** Pure scoring result for one captured workflow tool invocation. */
8
+ export interface WorkflowDeliveryChoiceEvaluation {
9
+ passed: boolean;
10
+ resolvedBackground: boolean | null;
11
+ assertions: Array<{
12
+ name: string;
13
+ passed: boolean;
14
+ details: string;
15
+ }>;
16
+ }
17
+ /** Focused #89 scenarios for default background delivery versus same-turn use. */
18
+ export declare const WORKFLOW_DELIVERY_CHOICE_SCENARIOS: readonly WorkflowDeliveryChoiceScenario[];
19
+ /** Score captured workflow arguments without executing the submitted workflow. */
20
+ export declare function evaluateWorkflowDeliveryChoice(scenario: WorkflowDeliveryChoiceScenario, value: unknown): WorkflowDeliveryChoiceEvaluation;
@@ -0,0 +1,48 @@
1
+ /** Focused #89 scenarios for default background delivery versus same-turn use. */
2
+ export const WORKFLOW_DELIVERY_CHOICE_SCENARIOS = [
3
+ {
4
+ id: "background-delivery",
5
+ prompt: "Run a workflow to audit the repository from several independent angles. I do not need the result in this turn; let it be delivered back later so I can keep working.",
6
+ expectedBackground: true,
7
+ },
8
+ {
9
+ id: "inline-result",
10
+ prompt: "Run a workflow to compare the two proposed designs, then use its result in your answer in this same turn. I am waiting for the result before you respond.",
11
+ expectedBackground: false,
12
+ },
13
+ ];
14
+ /** Score captured workflow arguments without executing the submitted workflow. */
15
+ export function evaluateWorkflowDeliveryChoice(scenario, value) {
16
+ const input = isRecord(value) ? value : null;
17
+ const hasScript = typeof input?.script === "string" && input.script.trim().length > 0;
18
+ const validBackground = input !== null && (input.background === undefined || typeof input.background === "boolean");
19
+ const resolvedBackground = validBackground ? (typeof input.background === "boolean" ? input.background : true) : null;
20
+ const timingMatches = resolvedBackground === scenario.expectedBackground;
21
+ const assertions = [
22
+ {
23
+ name: "workflow:called-with-script",
24
+ passed: hasScript,
25
+ details: "the model must invoke the real workflow tool shape with a nonblank script",
26
+ },
27
+ {
28
+ name: "background:valid-type",
29
+ passed: validBackground,
30
+ details: "background must be omitted for its true default or supplied as a boolean",
31
+ },
32
+ {
33
+ name: "background:matches-user-timing",
34
+ passed: timingMatches,
35
+ details: scenario.expectedBackground
36
+ ? "later delivery should use the default background run"
37
+ : "same-turn use should pass background: false",
38
+ },
39
+ ];
40
+ return {
41
+ passed: assertions.every(({ passed }) => passed),
42
+ resolvedBackground,
43
+ assertions,
44
+ };
45
+ }
46
+ function isRecord(value) {
47
+ return typeof value === "object" && value !== null && !Array.isArray(value);
48
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * "Workflows mode" keyword trigger: while the submitted message contains the
3
+ * bounded word `workflow`/`workflows` (or a configured custom trigger word),
4
+ * the message is transformed at submit time to instruct Pi to actually run the
5
+ * workflow tool. Detection is purely textual (`event.text` on the `input`
6
+ * hook) — it does not depend on, or own, the host's editor component.
7
+ */
8
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
+ import { type EffortState } from "./effort-command.js";
10
+ import { type WorkflowSettingsStore } from "./workflow-settings.js";
11
+ export declare function hasTrigger(text: string, triggerWord?: string): boolean;
12
+ export declare function endsWithTrigger(textBeforeCursor: string, triggerWord?: string): boolean;
13
+ /** Shared, mutable view of whether "workflows mode" is currently armed. */
14
+ export interface WorkflowModeState {
15
+ active: boolean;
16
+ keywordTriggerEnabled: boolean;
17
+ keywordTriggerWord?: string;
18
+ suppressedKeywordText?: string;
19
+ }
20
+ export interface InstallWorkflowKeywordArmingOptions {
21
+ settingsStore?: WorkflowSettingsStore;
22
+ }
23
+ /**
24
+ * Why a turn was armed. This is stated truthfully in the banner so the model
25
+ * isn't told "the trigger word you typed" on a path where no word was typed:
26
+ * - "keyword": the user typed the configured workflow trigger word.
27
+ * - "effort": standing `/effort` armed this turn (no workflow word was typed).
28
+ */
29
+ export type ArmReason = "keyword" | "effort";
30
+ /**
31
+ * Appended to the effort-path directive: standing `/effort` arms on every
32
+ * substantive message, so the model must be told it can decline the workflow on
33
+ * a conversational or trivial turn (mirrors "solo only on conversational turns").
34
+ */
35
+ export declare const EFFORT_CONVERSATIONAL_ESCAPE = "This turn was armed by standing effort mode, not by an explicit workflow request: if it is conversational or trivial, skip the workflow and just respond directly.";
36
+ /**
37
+ * The directive appended to a submitted message when workflows mode is ARMED by a
38
+ * HEURISTIC path — the keyword trigger or standing `/effort`. (The explicit
39
+ * `/workflows run` command uses {@link buildForcedWorkflowPrompt} instead.)
40
+ *
41
+ * This authorizes — it does not force. Arming is a confirmed opt-in signal that
42
+ * lifts the always-on "do not call the tool" gate for THIS message; the model
43
+ * still decides whether the message is actually a request to do work (→ call the
44
+ * `workflow` tool) or just talk about workflows (→ answer directly). The old
45
+ * "You MUST / the ONLY acceptable action / Do NOT answer directly" forcing text
46
+ * caused two bugs: it over-triggered on messages that merely mention workflows
47
+ * (#88), and — by commanding the model to emit nothing but one `workflow` call
48
+ * and not talk — it produced a bare background run that ends the turn and leaves
49
+ * the user at an idle prompt (#89).
50
+ *
51
+ * The banner therefore (1) LEADS with the decision boundary (question/trivial →
52
+ * answer directly; a real decomposable request → call `workflow`) rather than
53
+ * leading with "call the tool"; (2) states the truthful opt-in `reason` for THIS
54
+ * path (no "the word you typed" on the effort path, where none was); and (3)
55
+ * carries the #89 background/deliver-back reassurance so an ending turn reads as
56
+ * expected. The how-to mechanics are NOT here — they live in the tool's static
57
+ * `description` (see createWorkflowTool), visible whenever the model looks at the
58
+ * tool, so they aren't re-injected per armed turn (#65).
59
+ *
60
+ * `extraDirective` (e.g. an effort-tier nudge + EFFORT_CONVERSATIONAL_ESCAPE) is
61
+ * appended when present.
62
+ */
63
+ export declare function buildArmedWorkflowPrompt(text: string, opts?: {
64
+ reason?: ArmReason;
65
+ extraDirective?: string;
66
+ }): string;
67
+ /**
68
+ * The directive for the explicit `/workflows run <prompt>` command. Unlike the
69
+ * heuristic {@link buildArmedWorkflowPrompt}, `/workflows run` is a maximal-intent
70
+ * command — the user typed a command whose whole purpose is to execute a workflow
71
+ * now — so it does NOT get the "if it's a question, just answer" escape. It still
72
+ * avoids the old MUST/ONLY forcing language (which caused #88/#89) and still
73
+ * carries the #89 background/deliver-back reassurance so an ending turn reads as
74
+ * expected. `extraDirective` (e.g. a standing effort-tier nudge) is appended.
75
+ */
76
+ export declare function buildForcedWorkflowPrompt(text: string, extraDirective?: string): string;
77
+ /** The exact name of the workflow tool that workflows mode forces. */
78
+ export declare const WORKFLOW_TOOL_NAME = "workflow";
79
+ export declare function registerWorkflowTriggerCommand(pi: ExtensionAPI, state: WorkflowModeState, settingsStore?: WorkflowSettingsStore): void;
80
+ /**
81
+ * Register the bottom progress-panel preference command:
82
+ * - `/workflows-progress compact|detailed|status` — switch (or report) the panel mode.
83
+ * - `/workflows-progress max <1-1000>` — cap agents shown per phase in detailed mode.
84
+ * Both persist via `settingsStore` and take effect on the next live run (the panel
85
+ * live-reads its settings), so no session restart is needed.
86
+ */
87
+ export declare function registerWorkflowProgressCommands(pi: ExtensionAPI, settingsStore?: WorkflowSettingsStore): void;
88
+ /**
89
+ * Install the keyword-trigger arming hook (submit-time detection + prompt
90
+ * rewrite) and the related trigger/progress commands. Call once (e.g. in
91
+ * `session_start`).
92
+ */
93
+ export declare function installWorkflowKeywordArming(pi: ExtensionAPI, effort?: EffortState, options?: InstallWorkflowKeywordArmingOptions): WorkflowModeState;