@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.
- package/LICENSE +22 -0
- package/README.md +325 -0
- package/assets/readme/hero.png +0 -0
- package/assets/readme/hero.svg +70 -0
- package/assets/readme/package-cover.png +0 -0
- package/assets/readme/package-cover.svg +66 -0
- package/assets/readme/workflow.png +0 -0
- package/assets/readme/workflow.svg +77 -0
- package/dist/accept-workflow-guidance.d.ts +14 -0
- package/dist/accept-workflow-guidance.js +53 -0
- package/dist/adversarial-review.d.ts +30 -0
- package/dist/adversarial-review.js +107 -0
- package/dist/agent-history.d.ts +20 -0
- package/dist/agent-history.js +122 -0
- package/dist/agent-registry.d.ts +83 -0
- package/dist/agent-registry.js +190 -0
- package/dist/agent.d.ts +364 -0
- package/dist/agent.js +714 -0
- package/dist/builtin-commands.d.ts +19 -0
- package/dist/builtin-commands.js +251 -0
- package/dist/builtin-workflows.d.ts +45 -0
- package/dist/builtin-workflows.js +121 -0
- package/dist/code-review.d.ts +26 -0
- package/dist/code-review.js +181 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +44 -0
- package/dist/deep-research.d.ts +30 -0
- package/dist/deep-research.js +124 -0
- package/dist/display.d.ts +134 -0
- package/dist/display.js +248 -0
- package/dist/effort-command.d.ts +28 -0
- package/dist/effort-command.js +68 -0
- package/dist/enums.d.ts +69 -0
- package/dist/enums.js +78 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.js +140 -0
- package/dist/extension-reload.d.ts +37 -0
- package/dist/extension-reload.js +78 -0
- package/dist/fs-persistence.d.ts +63 -0
- package/dist/fs-persistence.js +102 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +35 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.js +66 -0
- package/dist/model-routing.d.ts +30 -0
- package/dist/model-routing.js +50 -0
- package/dist/model-spec.d.ts +29 -0
- package/dist/model-spec.js +252 -0
- package/dist/model-tier-config.d.ts +133 -0
- package/dist/model-tier-config.js +249 -0
- package/dist/run-persistence.d.ts +180 -0
- package/dist/run-persistence.js +294 -0
- package/dist/saved-commands.d.ts +28 -0
- package/dist/saved-commands.js +100 -0
- package/dist/shared-store.d.ts +98 -0
- package/dist/shared-store.js +212 -0
- package/dist/structured-output.d.ts +19 -0
- package/dist/structured-output.js +30 -0
- package/dist/task-panel.d.ts +61 -0
- package/dist/task-panel.js +422 -0
- package/dist/usage-limit-scheduler.d.ts +145 -0
- package/dist/usage-limit-scheduler.js +368 -0
- package/dist/web-tools.d.ts +20 -0
- package/dist/web-tools.js +120 -0
- package/dist/workflow-authoring-coverage.d.ts +70 -0
- package/dist/workflow-authoring-coverage.js +421 -0
- package/dist/workflow-authoring-reference.d.ts +20 -0
- package/dist/workflow-authoring-reference.js +156 -0
- package/dist/workflow-capability-contract.d.ts +131 -0
- package/dist/workflow-capability-contract.js +604 -0
- package/dist/workflow-commands.d.ts +18 -0
- package/dist/workflow-commands.js +260 -0
- package/dist/workflow-comprehension.d.ts +133 -0
- package/dist/workflow-comprehension.js +1321 -0
- package/dist/workflow-context-measurement.d.ts +72 -0
- package/dist/workflow-context-measurement.js +213 -0
- package/dist/workflow-control-tool.d.ts +30 -0
- package/dist/workflow-control-tool.js +176 -0
- package/dist/workflow-delivery-choice.d.ts +20 -0
- package/dist/workflow-delivery-choice.js +48 -0
- package/dist/workflow-editor.d.ts +93 -0
- package/dist/workflow-editor.js +363 -0
- package/dist/workflow-manager.d.ts +492 -0
- package/dist/workflow-manager.js +1124 -0
- package/dist/workflow-paths.d.ts +22 -0
- package/dist/workflow-paths.js +46 -0
- package/dist/workflow-release-gate.d.ts +39 -0
- package/dist/workflow-release-gate.js +309 -0
- package/dist/workflow-saved.d.ts +38 -0
- package/dist/workflow-saved.js +126 -0
- package/dist/workflow-settings.d.ts +70 -0
- package/dist/workflow-settings.js +131 -0
- package/dist/workflow-tool.d.ts +71 -0
- package/dist/workflow-tool.js +367 -0
- package/dist/workflow-ui.d.ts +182 -0
- package/dist/workflow-ui.js +1587 -0
- package/dist/workflow.d.ts +333 -0
- package/dist/workflow.js +1151 -0
- package/dist/workflows-models-command.d.ts +31 -0
- package/dist/workflows-models-command.js +156 -0
- package/dist/worktree.d.ts +25 -0
- package/dist/worktree.js +61 -0
- package/extensions/workflow.ts +151 -0
- package/package.json +104 -0
- package/skills/workflow-authoring/SKILL.md +30 -0
- package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
- package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
- package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
- package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
- package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
- package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
- package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
- package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
- package/skills/workflow-authoring/examples/structured-output.js +37 -0
- package/skills/workflow-authoring/examples/tournament.js +81 -0
- package/skills/workflow-authoring/examples/validated-gate.js +63 -0
- package/skills/workflow-authoring/references/capabilities.md +41 -0
- package/skills/workflow-authoring/references/capability-details.md +357 -0
- package/skills/workflow-authoring/references/common-helpers.md +4 -0
- package/skills/workflow-authoring/references/debugging.md +27 -0
- package/skills/workflow-authoring/references/focused-recipes.md +11 -0
- package/skills/workflow-authoring/references/helpers.md +9 -0
- package/skills/workflow-authoring/references/lifecycle.md +33 -0
- package/skills/workflow-authoring/references/pattern-selection.md +15 -0
- package/skills/workflow-authoring/references/quality-helpers.md +8 -0
- package/skills/workflow-authoring/references/registry-ownership.md +15 -0
- package/skills/workflow-authoring/references/retry-helper.md +5 -0
- package/skills/workflow-authoring/references/review.md +43 -0
- package/skills/workflow-authoring/references/runtime.md +27 -0
- package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
- package/skills/workflow-authoring/references/versions.md +13 -0
- package/skills/workflow-patterns/SKILL.md +51 -0
- package/src/accept-workflow-guidance.ts +71 -0
- package/src/adversarial-review.ts +120 -0
- package/src/agent-history.ts +157 -0
- package/src/agent-registry.ts +221 -0
- package/src/agent.ts +929 -0
- package/src/builtin-commands.ts +286 -0
- package/src/builtin-workflows.ts +155 -0
- package/src/code-review.ts +183 -0
- package/src/config.ts +55 -0
- package/src/deep-research.ts +135 -0
- package/src/display.ts +367 -0
- package/src/effort-command.ts +87 -0
- package/src/enums.ts +77 -0
- package/src/errors.ts +199 -0
- package/src/extension-reload.ts +100 -0
- package/src/fs-persistence.ts +124 -0
- package/src/index.ts +176 -0
- package/src/logger.ts +88 -0
- package/src/model-routing.ts +73 -0
- package/src/model-spec.ts +309 -0
- package/src/model-tier-config.ts +296 -0
- package/src/run-persistence.ts +484 -0
- package/src/saved-commands.ts +115 -0
- package/src/shared-store.ts +228 -0
- package/src/structured-output.ts +47 -0
- package/src/task-panel.ts +490 -0
- package/src/usage-limit-scheduler.ts +432 -0
- package/src/web-tools.ts +124 -0
- package/src/workflow-authoring-coverage.ts +486 -0
- package/src/workflow-authoring-reference.ts +186 -0
- package/src/workflow-capability-contract.ts +806 -0
- package/src/workflow-commands.ts +287 -0
- package/src/workflow-comprehension.ts +1673 -0
- package/src/workflow-context-measurement.ts +262 -0
- package/src/workflow-control-tool.ts +238 -0
- package/src/workflow-delivery-choice.ts +69 -0
- package/src/workflow-editor.ts +444 -0
- package/src/workflow-manager.ts +1405 -0
- package/src/workflow-paths.ts +63 -0
- package/src/workflow-release-gate.ts +529 -0
- package/src/workflow-saved.ts +180 -0
- package/src/workflow-settings.ts +194 -0
- package/src/workflow-tool.ts +464 -0
- package/src/workflow-ui.ts +1789 -0
- package/src/workflow.ts +1615 -0
- package/src/workflows-models-command.ts +211 -0
- package/src/worktree.ts +76 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standing `/effort` opt-in (pi's answer to CC's ultracode): a session toggle that
|
|
3
|
+
* auto-arms a workflow for substantive interactive messages, with effort-tier
|
|
4
|
+
* guidance nudging fan-out breadth and the hard caps (tokenBudget / maxAgents) the
|
|
5
|
+
* model should set on the workflow tool call.
|
|
6
|
+
*
|
|
7
|
+
* Honest scope: the runtime cannot enforce "reviewer N / loop K" — those live in
|
|
8
|
+
* the script the model writes — so the tiers are guidance plus the model setting
|
|
9
|
+
* the real hard caps (tokenBudget/maxAgents are genuine runtime ceilings). The
|
|
10
|
+
* pre-flight ceiling-confirm dialog (roadmap P1-5 #4) is a downscope point: an
|
|
11
|
+
* `input` hook transforms synchronously and can't await a confirm, so it is left
|
|
12
|
+
* to a follow-up; `/effort` is explicit opt-in, which is the safety valve.
|
|
13
|
+
*/
|
|
14
|
+
export function createEffortState() {
|
|
15
|
+
return { level: "off" };
|
|
16
|
+
}
|
|
17
|
+
const HIGH_DIRECTIVE = "Effort: HIGH. Be thorough — use a few parallel reviewers/perspectives and an adversarial verify pass (see verify()/judgePanel()); set a moderate tokenBudget and maxAgents on the workflow tool call.";
|
|
18
|
+
const ULTRA_DIRECTIVE = "Effort: ULTRA. Be exhaustive — fan out widely (more reviewers/judges, deeper loopUntilDry rounds, a completenessCheck at the end), and prefer the big tier for synthesis. This can spend a lot of tokens quickly, so set explicit caps you're comfortable paying for (a generous but bounded tokenBudget and a high maxAgents) on the workflow tool call rather than leaving them unbounded.";
|
|
19
|
+
/** The extra directive appended to the forced-workflow prompt for an effort level. */
|
|
20
|
+
export function effortDirective(level) {
|
|
21
|
+
if (level === "high")
|
|
22
|
+
return HIGH_DIRECTIVE;
|
|
23
|
+
if (level === "ultra")
|
|
24
|
+
return ULTRA_DIRECTIVE;
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether a message should auto-arm under effort mode: a real interactive request,
|
|
29
|
+
* not a terse acknowledgement or a slash command. (hasTrigger handles the explicit
|
|
30
|
+
* "workflow(s)" keyword separately.)
|
|
31
|
+
*/
|
|
32
|
+
export function isSubstantive(text) {
|
|
33
|
+
const t = text.trim();
|
|
34
|
+
return t.length >= 16 && !t.startsWith("/");
|
|
35
|
+
}
|
|
36
|
+
export function registerEffortCommand(pi, state) {
|
|
37
|
+
pi.registerCommand("effort", {
|
|
38
|
+
description: "Standing workflow effort: off | high | ultra — auto-arms a workflow for substantive messages",
|
|
39
|
+
async handler(args, _ctx) {
|
|
40
|
+
const arg = args.trim().toLowerCase();
|
|
41
|
+
const say = (content) => pi.sendMessage({ customType: "effort", content, display: true });
|
|
42
|
+
if (arg === "off" || arg === "high" || arg === "ultra") {
|
|
43
|
+
state.level = arg;
|
|
44
|
+
await say(arg === "off"
|
|
45
|
+
? "Effort off — messages are no longer auto-armed as workflows."
|
|
46
|
+
: `Effort ${arg} — substantive messages now auto-arm a workflow (${arg === "ultra" ? "exhaustive" : "thorough"} fan-out). Use /effort off to stop.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await say(`Effort is currently "${state.level}". Usage: /effort off | high | ultra`);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
// `/ultracode` — the headline name for the maximal-effort mode (Pi's ultracode):
|
|
53
|
+
// `/ultracode` turns it on, `/ultracode off` turns it off. Alias for /effort ultra.
|
|
54
|
+
pi.registerCommand("ultracode", {
|
|
55
|
+
description: "Ultracode: standing maximal-effort mode (this session only, never persisted) — auto-arms an exhaustive workflow for substantive messages. /ultracode off to stop.",
|
|
56
|
+
async handler(args, _ctx) {
|
|
57
|
+
const arg = args.trim().toLowerCase();
|
|
58
|
+
const say = (content) => pi.sendMessage({ customType: "effort", content, display: true });
|
|
59
|
+
if (arg === "off") {
|
|
60
|
+
state.level = "off";
|
|
61
|
+
await say("Ultracode off — messages are no longer auto-armed as workflows.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
state.level = "ultra";
|
|
65
|
+
await say("Ultracode ON — substantive messages now auto-arm an exhaustive workflow (wide fan-out, big-tier synthesis). Use /ultracode off to stop.");
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
package/dist/enums.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Classifies a workflow capability by its runtime and documentation role. */
|
|
2
|
+
export declare enum CapabilityClassification {
|
|
3
|
+
RUNTIME_GLOBAL = "runtime-global",
|
|
4
|
+
WORKFLOW_TOOL_INPUT = "workflow-tool-input",
|
|
5
|
+
SCRIPT_CONTRACT = "script-contract",
|
|
6
|
+
COMPATIBILITY_BEHAVIOR = "compatibility-behavior",
|
|
7
|
+
INTERNAL_SUBSTRATE = "internal-substrate",
|
|
8
|
+
DYNAMIC_REFERENCE = "dynamic-reference"
|
|
9
|
+
}
|
|
10
|
+
/** Declares whether workflow authors should use a capability. */
|
|
11
|
+
export declare enum CapabilitySupport {
|
|
12
|
+
SUPPORTED = "supported",
|
|
13
|
+
COMPATIBILITY = "compatibility",
|
|
14
|
+
INTERNAL = "internal"
|
|
15
|
+
}
|
|
16
|
+
/** Identifies the model-visible surface responsible for discovery. */
|
|
17
|
+
export declare enum DiscoveryPlacement {
|
|
18
|
+
COMPACT_GUIDANCE = "compact-guidance",
|
|
19
|
+
WORKFLOW_AUTHORING_SKILL = "workflow-authoring-skill",
|
|
20
|
+
NONE = "none"
|
|
21
|
+
}
|
|
22
|
+
/** Names the subsystem that owns a capability's behavior. */
|
|
23
|
+
export declare enum CapabilityOrigin {
|
|
24
|
+
PROJECT = "project",
|
|
25
|
+
TOOL_ADAPTER = "tool-adapter",
|
|
26
|
+
VM_REALM = "vm-realm",
|
|
27
|
+
LIVE_CONFIGURATION = "live-configuration"
|
|
28
|
+
}
|
|
29
|
+
/** Severity carried by capability-alignment diagnostics. */
|
|
30
|
+
export declare enum DiagnosticSeverity {
|
|
31
|
+
ERROR = "error",
|
|
32
|
+
WARNING = "warning",
|
|
33
|
+
INFORMATION = "information"
|
|
34
|
+
}
|
|
35
|
+
/** Optional model-comprehension scenario groups. */
|
|
36
|
+
export declare enum ComprehensionSuite {
|
|
37
|
+
QUICK = "quick",
|
|
38
|
+
FULL = "full",
|
|
39
|
+
COVERAGE = "coverage"
|
|
40
|
+
}
|
|
41
|
+
/** Authoring operation exercised by a comprehension scenario. */
|
|
42
|
+
export declare enum ComprehensionTaskKind {
|
|
43
|
+
WRITE = "write",
|
|
44
|
+
EDIT = "edit",
|
|
45
|
+
REVIEW = "review",
|
|
46
|
+
DEBUG = "debug"
|
|
47
|
+
}
|
|
48
|
+
/** Whether authoring guidance may be optimized against behavioral evidence or must remain frozen. */
|
|
49
|
+
export declare enum WorkflowAuthoringProtection {
|
|
50
|
+
BEHAVIORALLY_COVERED = "behaviorally-covered",
|
|
51
|
+
GUIDANCE_FROZEN = "guidance-frozen"
|
|
52
|
+
}
|
|
53
|
+
/** Machine-readable release-gate failure and warning domains. */
|
|
54
|
+
export declare enum WorkflowReleaseDiagnosticCode {
|
|
55
|
+
INCOMPATIBLE_VERSION = "INCOMPATIBLE_VERSION",
|
|
56
|
+
MISSING_BEHAVIOR_EVIDENCE = "MISSING_BEHAVIOR_EVIDENCE",
|
|
57
|
+
UNRESOLVED_BEHAVIOR_EVIDENCE = "UNRESOLVED_BEHAVIOR_EVIDENCE",
|
|
58
|
+
BROKEN_CONTRACT_REFERENCE = "BROKEN_CONTRACT_REFERENCE",
|
|
59
|
+
MISSING_PACKAGE_RESOURCE = "MISSING_PACKAGE_RESOURCE",
|
|
60
|
+
BROKEN_PACKAGE_LINK = "BROKEN_PACKAGE_LINK",
|
|
61
|
+
STALE_GENERATED_SURFACE = "STALE_GENERATED_SURFACE",
|
|
62
|
+
TOOL_INPUT_MISMATCH = "TOOL_INPUT_MISMATCH",
|
|
63
|
+
RUNTIME_CONSTRAINT_DISAGREEMENT = "RUNTIME_CONSTRAINT_DISAGREEMENT",
|
|
64
|
+
NON_CONTRACTUAL_PROSE_DRIFT = "NON_CONTRACTUAL_PROSE_DRIFT",
|
|
65
|
+
MISSING_AUTHORING_COVERAGE = "MISSING_AUTHORING_COVERAGE",
|
|
66
|
+
UNPROTECTED_AUTHORING_GUIDANCE = "UNPROTECTED_AUTHORING_GUIDANCE",
|
|
67
|
+
PROTECTED_GUIDANCE_DRIFT = "PROTECTED_GUIDANCE_DRIFT",
|
|
68
|
+
UNKNOWN_COMPREHENSION_SCENARIO = "UNKNOWN_COMPREHENSION_SCENARIO"
|
|
69
|
+
}
|
package/dist/enums.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Classifies a workflow capability by its runtime and documentation role. */
|
|
2
|
+
export var CapabilityClassification;
|
|
3
|
+
(function (CapabilityClassification) {
|
|
4
|
+
CapabilityClassification["RUNTIME_GLOBAL"] = "runtime-global";
|
|
5
|
+
CapabilityClassification["WORKFLOW_TOOL_INPUT"] = "workflow-tool-input";
|
|
6
|
+
CapabilityClassification["SCRIPT_CONTRACT"] = "script-contract";
|
|
7
|
+
CapabilityClassification["COMPATIBILITY_BEHAVIOR"] = "compatibility-behavior";
|
|
8
|
+
CapabilityClassification["INTERNAL_SUBSTRATE"] = "internal-substrate";
|
|
9
|
+
CapabilityClassification["DYNAMIC_REFERENCE"] = "dynamic-reference";
|
|
10
|
+
})(CapabilityClassification || (CapabilityClassification = {}));
|
|
11
|
+
/** Declares whether workflow authors should use a capability. */
|
|
12
|
+
export var CapabilitySupport;
|
|
13
|
+
(function (CapabilitySupport) {
|
|
14
|
+
CapabilitySupport["SUPPORTED"] = "supported";
|
|
15
|
+
CapabilitySupport["COMPATIBILITY"] = "compatibility";
|
|
16
|
+
CapabilitySupport["INTERNAL"] = "internal";
|
|
17
|
+
})(CapabilitySupport || (CapabilitySupport = {}));
|
|
18
|
+
/** Identifies the model-visible surface responsible for discovery. */
|
|
19
|
+
export var DiscoveryPlacement;
|
|
20
|
+
(function (DiscoveryPlacement) {
|
|
21
|
+
DiscoveryPlacement["COMPACT_GUIDANCE"] = "compact-guidance";
|
|
22
|
+
DiscoveryPlacement["WORKFLOW_AUTHORING_SKILL"] = "workflow-authoring-skill";
|
|
23
|
+
DiscoveryPlacement["NONE"] = "none";
|
|
24
|
+
})(DiscoveryPlacement || (DiscoveryPlacement = {}));
|
|
25
|
+
/** Names the subsystem that owns a capability's behavior. */
|
|
26
|
+
export var CapabilityOrigin;
|
|
27
|
+
(function (CapabilityOrigin) {
|
|
28
|
+
CapabilityOrigin["PROJECT"] = "project";
|
|
29
|
+
CapabilityOrigin["TOOL_ADAPTER"] = "tool-adapter";
|
|
30
|
+
CapabilityOrigin["VM_REALM"] = "vm-realm";
|
|
31
|
+
CapabilityOrigin["LIVE_CONFIGURATION"] = "live-configuration";
|
|
32
|
+
})(CapabilityOrigin || (CapabilityOrigin = {}));
|
|
33
|
+
/** Severity carried by capability-alignment diagnostics. */
|
|
34
|
+
export var DiagnosticSeverity;
|
|
35
|
+
(function (DiagnosticSeverity) {
|
|
36
|
+
DiagnosticSeverity["ERROR"] = "error";
|
|
37
|
+
DiagnosticSeverity["WARNING"] = "warning";
|
|
38
|
+
DiagnosticSeverity["INFORMATION"] = "information";
|
|
39
|
+
})(DiagnosticSeverity || (DiagnosticSeverity = {}));
|
|
40
|
+
/** Optional model-comprehension scenario groups. */
|
|
41
|
+
export var ComprehensionSuite;
|
|
42
|
+
(function (ComprehensionSuite) {
|
|
43
|
+
ComprehensionSuite["QUICK"] = "quick";
|
|
44
|
+
ComprehensionSuite["FULL"] = "full";
|
|
45
|
+
ComprehensionSuite["COVERAGE"] = "coverage";
|
|
46
|
+
})(ComprehensionSuite || (ComprehensionSuite = {}));
|
|
47
|
+
/** Authoring operation exercised by a comprehension scenario. */
|
|
48
|
+
export var ComprehensionTaskKind;
|
|
49
|
+
(function (ComprehensionTaskKind) {
|
|
50
|
+
ComprehensionTaskKind["WRITE"] = "write";
|
|
51
|
+
ComprehensionTaskKind["EDIT"] = "edit";
|
|
52
|
+
ComprehensionTaskKind["REVIEW"] = "review";
|
|
53
|
+
ComprehensionTaskKind["DEBUG"] = "debug";
|
|
54
|
+
})(ComprehensionTaskKind || (ComprehensionTaskKind = {}));
|
|
55
|
+
/** Whether authoring guidance may be optimized against behavioral evidence or must remain frozen. */
|
|
56
|
+
export var WorkflowAuthoringProtection;
|
|
57
|
+
(function (WorkflowAuthoringProtection) {
|
|
58
|
+
WorkflowAuthoringProtection["BEHAVIORALLY_COVERED"] = "behaviorally-covered";
|
|
59
|
+
WorkflowAuthoringProtection["GUIDANCE_FROZEN"] = "guidance-frozen";
|
|
60
|
+
})(WorkflowAuthoringProtection || (WorkflowAuthoringProtection = {}));
|
|
61
|
+
/** Machine-readable release-gate failure and warning domains. */
|
|
62
|
+
export var WorkflowReleaseDiagnosticCode;
|
|
63
|
+
(function (WorkflowReleaseDiagnosticCode) {
|
|
64
|
+
WorkflowReleaseDiagnosticCode["INCOMPATIBLE_VERSION"] = "INCOMPATIBLE_VERSION";
|
|
65
|
+
WorkflowReleaseDiagnosticCode["MISSING_BEHAVIOR_EVIDENCE"] = "MISSING_BEHAVIOR_EVIDENCE";
|
|
66
|
+
WorkflowReleaseDiagnosticCode["UNRESOLVED_BEHAVIOR_EVIDENCE"] = "UNRESOLVED_BEHAVIOR_EVIDENCE";
|
|
67
|
+
WorkflowReleaseDiagnosticCode["BROKEN_CONTRACT_REFERENCE"] = "BROKEN_CONTRACT_REFERENCE";
|
|
68
|
+
WorkflowReleaseDiagnosticCode["MISSING_PACKAGE_RESOURCE"] = "MISSING_PACKAGE_RESOURCE";
|
|
69
|
+
WorkflowReleaseDiagnosticCode["BROKEN_PACKAGE_LINK"] = "BROKEN_PACKAGE_LINK";
|
|
70
|
+
WorkflowReleaseDiagnosticCode["STALE_GENERATED_SURFACE"] = "STALE_GENERATED_SURFACE";
|
|
71
|
+
WorkflowReleaseDiagnosticCode["TOOL_INPUT_MISMATCH"] = "TOOL_INPUT_MISMATCH";
|
|
72
|
+
WorkflowReleaseDiagnosticCode["RUNTIME_CONSTRAINT_DISAGREEMENT"] = "RUNTIME_CONSTRAINT_DISAGREEMENT";
|
|
73
|
+
WorkflowReleaseDiagnosticCode["NON_CONTRACTUAL_PROSE_DRIFT"] = "NON_CONTRACTUAL_PROSE_DRIFT";
|
|
74
|
+
WorkflowReleaseDiagnosticCode["MISSING_AUTHORING_COVERAGE"] = "MISSING_AUTHORING_COVERAGE";
|
|
75
|
+
WorkflowReleaseDiagnosticCode["UNPROTECTED_AUTHORING_GUIDANCE"] = "UNPROTECTED_AUTHORING_GUIDANCE";
|
|
76
|
+
WorkflowReleaseDiagnosticCode["PROTECTED_GUIDANCE_DRIFT"] = "PROTECTED_GUIDANCE_DRIFT";
|
|
77
|
+
WorkflowReleaseDiagnosticCode["UNKNOWN_COMPREHENSION_SCENARIO"] = "UNKNOWN_COMPREHENSION_SCENARIO";
|
|
78
|
+
})(WorkflowReleaseDiagnosticCode || (WorkflowReleaseDiagnosticCode = {}));
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-specific error types.
|
|
3
|
+
*/
|
|
4
|
+
/** Dependency-neutral diagnostic payload retained by capability contract failures. */
|
|
5
|
+
export interface CapabilityErrorDiagnostic {
|
|
6
|
+
code: string;
|
|
7
|
+
severity: "error" | "warning" | "information";
|
|
8
|
+
subject: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
/** Dependency-neutral skill-loading payload retained by generation failures. */
|
|
12
|
+
export interface ModelGenerationSkillLoadingEvidence {
|
|
13
|
+
discovered: boolean;
|
|
14
|
+
loaded: boolean;
|
|
15
|
+
toolCalls: Array<{
|
|
16
|
+
tool: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
/** Dependency-neutral provider-usage payload retained by generation failures. */
|
|
21
|
+
export interface ModelGenerationTokenUsage {
|
|
22
|
+
input: number;
|
|
23
|
+
output: number;
|
|
24
|
+
total: number;
|
|
25
|
+
cost: number;
|
|
26
|
+
cacheRead: number;
|
|
27
|
+
cacheWrite: number;
|
|
28
|
+
}
|
|
29
|
+
/** Stable runtime and persistence failure codes exposed to callers and UI surfaces. */
|
|
30
|
+
export declare enum WorkflowErrorCode {
|
|
31
|
+
/** Agent exceeded timeout. */
|
|
32
|
+
AGENT_TIMEOUT = "AGENT_TIMEOUT",
|
|
33
|
+
/** Workflow was aborted by user. */
|
|
34
|
+
WORKFLOW_ABORTED = "WORKFLOW_ABORTED",
|
|
35
|
+
/** Agent limit exceeded. */
|
|
36
|
+
AGENT_LIMIT_EXCEEDED = "AGENT_LIMIT_EXCEEDED",
|
|
37
|
+
/** Token budget exhausted. */
|
|
38
|
+
TOKEN_BUDGET_EXHAUSTED = "TOKEN_BUDGET_EXHAUSTED",
|
|
39
|
+
/**
|
|
40
|
+
* The provider's subscription/usage/quota/rate limit was hit. Distinct from the
|
|
41
|
+
* user's self-imposed TOKEN_BUDGET_EXHAUSTED: a provider limit refills on its own,
|
|
42
|
+
* so the run is checkpointed (paused) and replayed by resume() rather than failed.
|
|
43
|
+
*/
|
|
44
|
+
PROVIDER_USAGE_LIMIT = "PROVIDER_USAGE_LIMIT",
|
|
45
|
+
/** Script validation failed. */
|
|
46
|
+
SCRIPT_VALIDATION_ERROR = "SCRIPT_VALIDATION_ERROR",
|
|
47
|
+
/** A schema agent never produced valid structured_output (after repair + extraction). */
|
|
48
|
+
SCHEMA_NONCOMPLIANCE = "SCHEMA_NONCOMPLIANCE",
|
|
49
|
+
/** A non-schema agent completed without any assistant text output. */
|
|
50
|
+
AGENT_EMPTY_OUTPUT = "AGENT_EMPTY_OUTPUT",
|
|
51
|
+
/** Agent execution failed. */
|
|
52
|
+
AGENT_EXECUTION_ERROR = "AGENT_EXECUTION_ERROR",
|
|
53
|
+
/** Run state persistence failed. */
|
|
54
|
+
PERSISTENCE_ERROR = "PERSISTENCE_ERROR",
|
|
55
|
+
/** Unknown error. */
|
|
56
|
+
UNKNOWN = "UNKNOWN"
|
|
57
|
+
}
|
|
58
|
+
/** Classified workflow failure with recoverability and optional agent/provider context. */
|
|
59
|
+
export declare class WorkflowError extends Error {
|
|
60
|
+
readonly code: WorkflowErrorCode;
|
|
61
|
+
readonly recoverable: boolean;
|
|
62
|
+
readonly agentLabel?: string;
|
|
63
|
+
readonly details?: unknown;
|
|
64
|
+
/** For PROVIDER_USAGE_LIMIT: the provider's human reset hint, e.g. "Resets in ~3h" (verbatim). */
|
|
65
|
+
readonly resetHint?: string;
|
|
66
|
+
constructor(message: string, code: WorkflowErrorCode, options?: {
|
|
67
|
+
recoverable?: boolean;
|
|
68
|
+
agentLabel?: string;
|
|
69
|
+
details?: unknown;
|
|
70
|
+
resetHint?: string;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Contract failure that retains every definition or assembly diagnostic. */
|
|
74
|
+
export declare class WorkflowCapabilityContractError extends Error {
|
|
75
|
+
readonly diagnostics: readonly CapabilityErrorDiagnostic[];
|
|
76
|
+
constructor(message: string, diagnostics: readonly CapabilityErrorDiagnostic[]);
|
|
77
|
+
}
|
|
78
|
+
/** Generation failure that retains loading and token evidence for diagnosis. */
|
|
79
|
+
export declare class ModelGenerationError extends Error {
|
|
80
|
+
readonly skillLoadingEvidence: ModelGenerationSkillLoadingEvidence;
|
|
81
|
+
readonly tokenUsage: ModelGenerationTokenUsage;
|
|
82
|
+
constructor(message: string, skillLoadingEvidence: ModelGenerationSkillLoadingEvidence, tokenUsage: ModelGenerationTokenUsage);
|
|
83
|
+
}
|
|
84
|
+
/** Narrow an unknown failure to WorkflowError. */
|
|
85
|
+
export declare function isWorkflowError(error: unknown): error is WorkflowError;
|
|
86
|
+
/** Report whether an unknown failure is a provider usage-limit checkpoint condition. */
|
|
87
|
+
export declare function isProviderUsageLimit(error: unknown): error is WorkflowError;
|
|
88
|
+
/**
|
|
89
|
+
* Detect a provider subscription/usage/quota/rate-limit exhaustion from free-form
|
|
90
|
+
* error text, and extract the provider's human reset hint when present.
|
|
91
|
+
*
|
|
92
|
+
* The pi SDK does NOT throw these — it records them as an assistant message with
|
|
93
|
+
* stopReason "error" and an errorMessage like "Codex usage limit reached (plus
|
|
94
|
+
* plan). Resets in ~3h.". Callers reading message metadata MUST gate on
|
|
95
|
+
* stopReason === "error" before trusting this, so a task whose own output merely
|
|
96
|
+
* mentions "rate limit" is never misclassified. Patterns mirror the SDK's own
|
|
97
|
+
* non-retryable-limit table. Deliberately excludes transient overloaded/5xx
|
|
98
|
+
* errors, which stay recoverable and keep retrying.
|
|
99
|
+
*/
|
|
100
|
+
export declare function classifyProviderLimit(text: string | undefined): {
|
|
101
|
+
matched: boolean;
|
|
102
|
+
resetHint?: string;
|
|
103
|
+
};
|
|
104
|
+
/** Recognize abort-like Error messages without assuming a provider-specific class. */
|
|
105
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
106
|
+
/** Recognize timeout-like errors by name or message. */
|
|
107
|
+
export declare function isTimeoutError(error: unknown): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Wrap an unknown error into a WorkflowError with appropriate classification.
|
|
110
|
+
*/
|
|
111
|
+
export declare function wrapError(error: unknown, context?: {
|
|
112
|
+
agentLabel?: string;
|
|
113
|
+
}): WorkflowError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-specific error types.
|
|
3
|
+
*/
|
|
4
|
+
/** Stable runtime and persistence failure codes exposed to callers and UI surfaces. */
|
|
5
|
+
export var WorkflowErrorCode;
|
|
6
|
+
(function (WorkflowErrorCode) {
|
|
7
|
+
/** Agent exceeded timeout. */
|
|
8
|
+
WorkflowErrorCode["AGENT_TIMEOUT"] = "AGENT_TIMEOUT";
|
|
9
|
+
/** Workflow was aborted by user. */
|
|
10
|
+
WorkflowErrorCode["WORKFLOW_ABORTED"] = "WORKFLOW_ABORTED";
|
|
11
|
+
/** Agent limit exceeded. */
|
|
12
|
+
WorkflowErrorCode["AGENT_LIMIT_EXCEEDED"] = "AGENT_LIMIT_EXCEEDED";
|
|
13
|
+
/** Token budget exhausted. */
|
|
14
|
+
WorkflowErrorCode["TOKEN_BUDGET_EXHAUSTED"] = "TOKEN_BUDGET_EXHAUSTED";
|
|
15
|
+
/**
|
|
16
|
+
* The provider's subscription/usage/quota/rate limit was hit. Distinct from the
|
|
17
|
+
* user's self-imposed TOKEN_BUDGET_EXHAUSTED: a provider limit refills on its own,
|
|
18
|
+
* so the run is checkpointed (paused) and replayed by resume() rather than failed.
|
|
19
|
+
*/
|
|
20
|
+
WorkflowErrorCode["PROVIDER_USAGE_LIMIT"] = "PROVIDER_USAGE_LIMIT";
|
|
21
|
+
/** Script validation failed. */
|
|
22
|
+
WorkflowErrorCode["SCRIPT_VALIDATION_ERROR"] = "SCRIPT_VALIDATION_ERROR";
|
|
23
|
+
/** A schema agent never produced valid structured_output (after repair + extraction). */
|
|
24
|
+
WorkflowErrorCode["SCHEMA_NONCOMPLIANCE"] = "SCHEMA_NONCOMPLIANCE";
|
|
25
|
+
/** A non-schema agent completed without any assistant text output. */
|
|
26
|
+
WorkflowErrorCode["AGENT_EMPTY_OUTPUT"] = "AGENT_EMPTY_OUTPUT";
|
|
27
|
+
/** Agent execution failed. */
|
|
28
|
+
WorkflowErrorCode["AGENT_EXECUTION_ERROR"] = "AGENT_EXECUTION_ERROR";
|
|
29
|
+
/** Run state persistence failed. */
|
|
30
|
+
WorkflowErrorCode["PERSISTENCE_ERROR"] = "PERSISTENCE_ERROR";
|
|
31
|
+
/** Unknown error. */
|
|
32
|
+
WorkflowErrorCode["UNKNOWN"] = "UNKNOWN";
|
|
33
|
+
})(WorkflowErrorCode || (WorkflowErrorCode = {}));
|
|
34
|
+
/** Classified workflow failure with recoverability and optional agent/provider context. */
|
|
35
|
+
export class WorkflowError extends Error {
|
|
36
|
+
code;
|
|
37
|
+
recoverable;
|
|
38
|
+
agentLabel;
|
|
39
|
+
details;
|
|
40
|
+
/** For PROVIDER_USAGE_LIMIT: the provider's human reset hint, e.g. "Resets in ~3h" (verbatim). */
|
|
41
|
+
resetHint;
|
|
42
|
+
constructor(message, code, options = {}) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "WorkflowError";
|
|
45
|
+
this.code = code;
|
|
46
|
+
this.recoverable = options.recoverable ?? false;
|
|
47
|
+
this.agentLabel = options.agentLabel;
|
|
48
|
+
this.details = options.details;
|
|
49
|
+
this.resetHint = options.resetHint;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Contract failure that retains every definition or assembly diagnostic. */
|
|
53
|
+
export class WorkflowCapabilityContractError extends Error {
|
|
54
|
+
diagnostics;
|
|
55
|
+
constructor(message, diagnostics) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = "WorkflowCapabilityContractError";
|
|
58
|
+
this.diagnostics = diagnostics;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Generation failure that retains loading and token evidence for diagnosis. */
|
|
62
|
+
export class ModelGenerationError extends Error {
|
|
63
|
+
skillLoadingEvidence;
|
|
64
|
+
tokenUsage;
|
|
65
|
+
constructor(message, skillLoadingEvidence, tokenUsage) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = "ModelGenerationError";
|
|
68
|
+
this.skillLoadingEvidence = skillLoadingEvidence;
|
|
69
|
+
this.tokenUsage = tokenUsage;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Narrow an unknown failure to WorkflowError. */
|
|
73
|
+
export function isWorkflowError(error) {
|
|
74
|
+
return error instanceof WorkflowError;
|
|
75
|
+
}
|
|
76
|
+
/** Report whether an unknown failure is a provider usage-limit checkpoint condition. */
|
|
77
|
+
export function isProviderUsageLimit(error) {
|
|
78
|
+
return isWorkflowError(error) && error.code === WorkflowErrorCode.PROVIDER_USAGE_LIMIT;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Detect a provider subscription/usage/quota/rate-limit exhaustion from free-form
|
|
82
|
+
* error text, and extract the provider's human reset hint when present.
|
|
83
|
+
*
|
|
84
|
+
* The pi SDK does NOT throw these — it records them as an assistant message with
|
|
85
|
+
* stopReason "error" and an errorMessage like "Codex usage limit reached (plus
|
|
86
|
+
* plan). Resets in ~3h.". Callers reading message metadata MUST gate on
|
|
87
|
+
* stopReason === "error" before trusting this, so a task whose own output merely
|
|
88
|
+
* mentions "rate limit" is never misclassified. Patterns mirror the SDK's own
|
|
89
|
+
* non-retryable-limit table. Deliberately excludes transient overloaded/5xx
|
|
90
|
+
* errors, which stay recoverable and keep retrying.
|
|
91
|
+
*/
|
|
92
|
+
export function classifyProviderLimit(text) {
|
|
93
|
+
if (!text)
|
|
94
|
+
return { matched: false };
|
|
95
|
+
const matched = /usage limit|limit reached|insufficient[_\s]?quota|quota exceeded|exceeded your current quota|out of budget|available balance|\bquota\b|rate.?limit|too many requests|\b429\b|GoUsageLimitError|FreeUsageLimitError|\bbilling\b/i.test(text);
|
|
96
|
+
if (!matched)
|
|
97
|
+
return { matched: false };
|
|
98
|
+
const reset = text.match(/resets?\s+(?:in|at)\s+[^.\n]+/i);
|
|
99
|
+
return { matched: true, resetHint: reset?.[0]?.trim() };
|
|
100
|
+
}
|
|
101
|
+
/** Recognize abort-like Error messages without assuming a provider-specific class. */
|
|
102
|
+
export function isAbortError(error) {
|
|
103
|
+
if (!(error instanceof Error))
|
|
104
|
+
return false;
|
|
105
|
+
return /\babort(?:ed)?\b/i.test(error.message);
|
|
106
|
+
}
|
|
107
|
+
/** Recognize timeout-like errors by name or message. */
|
|
108
|
+
export function isTimeoutError(error) {
|
|
109
|
+
if (!(error instanceof Error))
|
|
110
|
+
return false;
|
|
111
|
+
return /\btimeout\b/i.test(error.message) || error.name === "TimeoutError";
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wrap an unknown error into a WorkflowError with appropriate classification.
|
|
115
|
+
*/
|
|
116
|
+
export function wrapError(error, context) {
|
|
117
|
+
if (isWorkflowError(error))
|
|
118
|
+
return error;
|
|
119
|
+
if (isAbortError(error)) {
|
|
120
|
+
return new WorkflowError(error instanceof Error ? error.message : "Workflow was aborted", WorkflowErrorCode.WORKFLOW_ABORTED, { recoverable: true });
|
|
121
|
+
}
|
|
122
|
+
if (isTimeoutError(error)) {
|
|
123
|
+
return new WorkflowError(error instanceof Error ? error.message : "Agent timed out", WorkflowErrorCode.AGENT_TIMEOUT, { recoverable: true, agentLabel: context?.agentLabel });
|
|
124
|
+
}
|
|
125
|
+
// Defense-in-depth: today the SDK buries provider usage/quota limits in an
|
|
126
|
+
// assistant message (detected in agent.ts), but a future SDK might throw them.
|
|
127
|
+
// Classify a thrown limit here too — recoverable:false so the run checkpoints
|
|
128
|
+
// (paused) instead of being retried into the same wall or silently nulled.
|
|
129
|
+
if (error instanceof Error) {
|
|
130
|
+
const limit = classifyProviderLimit(error.message);
|
|
131
|
+
if (limit.matched) {
|
|
132
|
+
return new WorkflowError(error.message, WorkflowErrorCode.PROVIDER_USAGE_LIMIT, {
|
|
133
|
+
recoverable: false,
|
|
134
|
+
agentLabel: context?.agentLabel,
|
|
135
|
+
resetHint: limit.resetHint,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return new WorkflowError(error instanceof Error ? error.message : String(error), WorkflowErrorCode.AGENT_EXECUTION_ERROR, { recoverable: true, agentLabel: context?.agentLabel, details: error });
|
|
140
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EffortState } from "./effort-command.js";
|
|
2
|
+
import type { WorkflowManager } from "./workflow-manager.js";
|
|
3
|
+
/**
|
|
4
|
+
* Live extension state that Pi may hand from one extension generation to the
|
|
5
|
+
* next during `/reload`. This deliberately stays process-local: run snapshots
|
|
6
|
+
* and journals already provide the durable cold-start path, while the live
|
|
7
|
+
* manager is what owns in-flight promises, abort controllers, and event streams.
|
|
8
|
+
*/
|
|
9
|
+
export declare const WORKFLOW_EXTENSION_VERSION: string;
|
|
10
|
+
export interface WorkflowReloadRuntime {
|
|
11
|
+
cwd: string;
|
|
12
|
+
/** Package version that created this manager. Only an exact match is retained. */
|
|
13
|
+
extensionVersion: string;
|
|
14
|
+
manager: WorkflowManager;
|
|
15
|
+
effort: EffortState;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkflowRuntimeClaim {
|
|
18
|
+
compatible?: WorkflowReloadRuntime;
|
|
19
|
+
versionMismatch?: WorkflowReloadRuntime;
|
|
20
|
+
}
|
|
21
|
+
/** Stage a live runtime immediately before Pi tears down the old extension runner. */
|
|
22
|
+
export declare function handoffWorkflowRuntime(runtime: WorkflowReloadRuntime): void;
|
|
23
|
+
/** Claim a staged runtime from the extension generation that `/reload` just stopped. */
|
|
24
|
+
export declare function takeWorkflowRuntime(cwd: string): WorkflowReloadRuntime | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Claim a staged runtime and compare its package version with this extension
|
|
27
|
+
* generation. Any package update falls back to a fresh manager; only reloads
|
|
28
|
+
* within the exact same installed version retain live workflow state.
|
|
29
|
+
*/
|
|
30
|
+
export declare function claimWorkflowRuntime(cwd: string): WorkflowRuntimeClaim;
|
|
31
|
+
/**
|
|
32
|
+
* Move live runs from a replaced extension version onto the existing journal
|
|
33
|
+
* recovery path before the fresh manager is constructed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function pauseVersionMismatchWorkflowRuntime(runtime: WorkflowReloadRuntime): number;
|
|
36
|
+
/** Test/cleanup helper; identity guard avoids deleting a newer handoff. */
|
|
37
|
+
export declare function discardWorkflowRuntime(cwd: string, runtime?: WorkflowReloadRuntime): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
2
|
+
/**
|
|
3
|
+
* Live extension state that Pi may hand from one extension generation to the
|
|
4
|
+
* next during `/reload`. This deliberately stays process-local: run snapshots
|
|
5
|
+
* and journals already provide the durable cold-start path, while the live
|
|
6
|
+
* manager is what owns in-flight promises, abort controllers, and event streams.
|
|
7
|
+
*/
|
|
8
|
+
export const WORKFLOW_EXTENSION_VERSION = packageJson.version;
|
|
9
|
+
const RELOAD_HANDOFF_KEY = Symbol.for("@quintinshaw/pi-dynamic-workflows:reload-handoffs");
|
|
10
|
+
const RELOAD_HANDOFF_TTL_MS = 30_000;
|
|
11
|
+
function handoffs() {
|
|
12
|
+
const root = globalThis;
|
|
13
|
+
const existing = root[RELOAD_HANDOFF_KEY];
|
|
14
|
+
if (existing)
|
|
15
|
+
return existing;
|
|
16
|
+
const created = new Map();
|
|
17
|
+
root[RELOAD_HANDOFF_KEY] = created;
|
|
18
|
+
return created;
|
|
19
|
+
}
|
|
20
|
+
/** Stage a live runtime immediately before Pi tears down the old extension runner. */
|
|
21
|
+
export function handoffWorkflowRuntime(runtime) {
|
|
22
|
+
const store = handoffs();
|
|
23
|
+
const previous = store.get(runtime.cwd);
|
|
24
|
+
if (previous)
|
|
25
|
+
clearTimeout(previous.timer);
|
|
26
|
+
const entry = {};
|
|
27
|
+
entry.runtime = runtime;
|
|
28
|
+
entry.timer = setTimeout(() => {
|
|
29
|
+
if (store.get(runtime.cwd) === entry)
|
|
30
|
+
store.delete(runtime.cwd);
|
|
31
|
+
}, RELOAD_HANDOFF_TTL_MS);
|
|
32
|
+
entry.timer.unref?.();
|
|
33
|
+
store.set(runtime.cwd, entry);
|
|
34
|
+
}
|
|
35
|
+
/** Claim a staged runtime from the extension generation that `/reload` just stopped. */
|
|
36
|
+
export function takeWorkflowRuntime(cwd) {
|
|
37
|
+
const store = handoffs();
|
|
38
|
+
const entry = store.get(cwd);
|
|
39
|
+
if (!entry)
|
|
40
|
+
return undefined;
|
|
41
|
+
clearTimeout(entry.timer);
|
|
42
|
+
store.delete(cwd);
|
|
43
|
+
return entry.runtime;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Claim a staged runtime and compare its package version with this extension
|
|
47
|
+
* generation. Any package update falls back to a fresh manager; only reloads
|
|
48
|
+
* within the exact same installed version retain live workflow state.
|
|
49
|
+
*/
|
|
50
|
+
export function claimWorkflowRuntime(cwd) {
|
|
51
|
+
const runtime = takeWorkflowRuntime(cwd);
|
|
52
|
+
if (!runtime)
|
|
53
|
+
return {};
|
|
54
|
+
return runtime.extensionVersion === WORKFLOW_EXTENSION_VERSION
|
|
55
|
+
? { compatible: runtime }
|
|
56
|
+
: { versionMismatch: runtime };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Move live runs from a replaced extension version onto the existing journal
|
|
60
|
+
* recovery path before the fresh manager is constructed.
|
|
61
|
+
*/
|
|
62
|
+
export function pauseVersionMismatchWorkflowRuntime(runtime) {
|
|
63
|
+
let paused = 0;
|
|
64
|
+
for (const run of runtime.manager.listRuns()) {
|
|
65
|
+
if (run.status === "running" && runtime.manager.pause(run.runId))
|
|
66
|
+
paused++;
|
|
67
|
+
}
|
|
68
|
+
return paused;
|
|
69
|
+
}
|
|
70
|
+
/** Test/cleanup helper; identity guard avoids deleting a newer handoff. */
|
|
71
|
+
export function discardWorkflowRuntime(cwd, runtime) {
|
|
72
|
+
const store = handoffs();
|
|
73
|
+
const entry = store.get(cwd);
|
|
74
|
+
if (!entry || (runtime && entry.runtime !== runtime))
|
|
75
|
+
return;
|
|
76
|
+
clearTimeout(entry.timer);
|
|
77
|
+
store.delete(cwd);
|
|
78
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filesystem primitives for JSON-backed persistence.
|
|
3
|
+
*
|
|
4
|
+
* Both run-persistence.ts (workflow runs) and workflow-saved.ts (saved
|
|
5
|
+
* workflow commands) persist plain-JSON records to per-record files under a
|
|
6
|
+
* project/user directory, and both need the same three guarantees:
|
|
7
|
+
*
|
|
8
|
+
* 1. Atomic writes with a recovery backup — a crash mid-write must never
|
|
9
|
+
* corrupt the live file, and a later-discovered-truncated primary must
|
|
10
|
+
* still be recoverable from the last good write.
|
|
11
|
+
* 2. Corrupt-file recovery on read — a truncated/corrupt primary falls back
|
|
12
|
+
* to its `.bak` sidecar instead of losing the record.
|
|
13
|
+
* 3. A missing or unreadable directory degrades to "no files" rather than
|
|
14
|
+
* throwing — a listing must never crash because one storage location is
|
|
15
|
+
* temporarily inaccessible (not yet created, deleted mid-race, EACCES).
|
|
16
|
+
*
|
|
17
|
+
* This module is the single implementation of all three; run-persistence.ts
|
|
18
|
+
* and workflow-saved.ts both call into it rather than maintaining parallel
|
|
19
|
+
* copies.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
22
|
+
/** Filesystem operations used by JSON persistence. Exposed for testing. */
|
|
23
|
+
export type PersistenceFsLayer = {
|
|
24
|
+
existsSync: typeof existsSync;
|
|
25
|
+
mkdirSync: typeof mkdirSync;
|
|
26
|
+
readdirSync: typeof readdirSync;
|
|
27
|
+
readFileSync: typeof readFileSync;
|
|
28
|
+
renameSync: typeof renameSync;
|
|
29
|
+
statSync: typeof statSync;
|
|
30
|
+
unlinkSync: typeof unlinkSync;
|
|
31
|
+
writeFileSync: typeof writeFileSync;
|
|
32
|
+
};
|
|
33
|
+
/** The real node:fs implementations. */
|
|
34
|
+
export declare function defaultPersistenceFs(): PersistenceFsLayer;
|
|
35
|
+
/** Merge a partial test override on top of the real node:fs implementations. */
|
|
36
|
+
export declare function resolvePersistenceFs(overrides?: Partial<PersistenceFsLayer>): PersistenceFsLayer;
|
|
37
|
+
/** Ensure `dir` exists (recursive mkdir), idempotent. */
|
|
38
|
+
export declare function ensureDir(fs: PersistenceFsLayer, dir: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Atomically write JSON to `path`: tmp-write + rename (atomic on the same
|
|
41
|
+
* filesystem, so a crash mid-write can't corrupt the live file), then
|
|
42
|
+
* best-effort refresh a `.bak` sidecar from the just-written good state —
|
|
43
|
+
* the recovery fallback readJsonWithBackupRecovery() uses if the primary is
|
|
44
|
+
* later found truncated (e.g. a rename that itself got interrupted by a
|
|
45
|
+
* power loss on a filesystem/OS combination where rename isn't fully atomic).
|
|
46
|
+
*/
|
|
47
|
+
export declare function writeJsonAtomicWithBackup(fs: PersistenceFsLayer, path: string, data: unknown): void;
|
|
48
|
+
/**
|
|
49
|
+
* Read JSON from `path`, falling back to `path.bak` if the primary is
|
|
50
|
+
* missing or fails to parse. Returns null if neither candidate parses.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readJsonWithBackupRecovery<T>(fs: PersistenceFsLayer, path: string): T | null;
|
|
53
|
+
/**
|
|
54
|
+
* List `.json` record files in `dir`. A missing directory (never created
|
|
55
|
+
* yet) or an unreadable one (deleted between the existsSync check and
|
|
56
|
+
* readdirSync, permission-denied, etc.) both degrade to an empty list
|
|
57
|
+
* rather than throwing — callers (run listings, saved-workflow listings)
|
|
58
|
+
* must never crash a navigator/listing because one storage location is
|
|
59
|
+
* temporarily inaccessible.
|
|
60
|
+
*/
|
|
61
|
+
export declare function listJsonFilesSafe(fs: PersistenceFsLayer, dir: string): string[];
|
|
62
|
+
/** Best-effort unlink; ignores missing-file/permission errors, reports whether it deleted anything. */
|
|
63
|
+
export declare function unlinkIfExistsSafe(fs: PersistenceFsLayer, path: string): boolean;
|