@osolmaz/pi-workflows 0.13.2 → 0.13.3
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/README.md +5 -3
- package/dist/builtins/autodoc.workflow.d.ts +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +65 -61
- package/dist/builtins/autoimplement.workflow.js +47 -8
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/autoplan.workflow.d.ts +5 -5
- package/dist/builtins/catalog.js +1 -1
- package/dist/builtins/change-verification.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.d.ts +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +23 -23
- package/dist/builtins/sanity-check.workflow.d.ts +3 -3
- package/dist/builtins/workspace-preparation.workflow.d.ts +1 -1
- package/dist/controllers/index.d.ts +2 -2
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.js +85 -23
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/types.d.ts +34 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +5 -1
- package/dist/controllers/workflow-engine-scheduler.js +83 -1
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
- package/dist/controllers/workflows.d.ts +8 -1
- package/dist/controllers/workflows.js +36 -0
- package/dist/controllers/workflows.js.map +1 -1
- package/dist/extension/controller-host.d.ts +2 -1
- package/dist/extension/controller-host.js +1 -1
- package/dist/extension/controller-host.js.map +1 -1
- package/dist/extension/follow-up-coordinator.d.ts +27 -0
- package/dist/extension/follow-up-coordinator.js +131 -0
- package/dist/extension/follow-up-coordinator.js.map +1 -0
- package/dist/extension/index.d.ts +9 -0
- package/dist/extension/index.js +460 -75
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -3
- package/dist/extension/session-events.js +11 -10
- package/dist/extension/session-events.js.map +1 -1
- package/dist/extension/widget.js +9 -0
- package/dist/extension/widget.js.map +1 -1
- package/dist/state/database.d.ts +2 -1
- package/dist/state/database.js +11 -13
- package/dist/state/database.js.map +1 -1
- package/dist/state/json.js +6 -1
- package/dist/state/json.js.map +1 -1
- package/dist/state/mutation.d.ts +1 -1
- package/dist/state/mutation.js.map +1 -1
- package/dist/state/prune.d.ts +18 -0
- package/dist/state/prune.js +373 -0
- package/dist/state/prune.js.map +1 -0
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +116 -18
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/cli.d.ts +3 -1
- package/dist/viewer/cli.js +55 -4
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +19 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/workflows/composition.d.ts +2 -0
- package/dist/workflows/composition.js +15 -0
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +6 -3
- package/dist/workflows/definition.js +3 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +6 -1
- package/dist/workflows/engine.js +301 -35
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +1 -0
- package/dist/workflows/human-decision.js +25 -9
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -1
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/json-patch.d.ts +47 -0
- package/dist/workflows/json-patch.js +298 -0
- package/dist/workflows/json-patch.js.map +1 -0
- package/dist/workflows/schema.js +29 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/settings.d.ts +128 -0
- package/dist/workflows/settings.js +199 -0
- package/dist/workflows/settings.js.map +1 -0
- package/dist/workflows/store.d.ts +75 -2
- package/dist/workflows/store.js +1349 -89
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +22 -0
- package/dist/workflows/tool-input.js +43 -0
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +40 -5
- package/docs/2026-08-25-workflow-follow-ups.md +132 -0
- package/docs/2026-08-25-workflow-settings.md +169 -0
- package/docs/DEFERRED_TURNS.md +6 -0
- package/docs/DESIGN_PHILOSOPHY.md +2 -0
- package/docs/SQLITE_STATE.md +31 -18
- package/docs/WORKFLOW_COMPOSITION.md +15 -0
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +532 -0
- package/docs/plans/2026-08-25-workflow-run-storage-plan.md +67 -0
- package/docs/tui-viewer.md +7 -6
- package/docs/workflows.md +22 -1
- package/examples/workflows/live-settings.workflow.ts +93 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/src/builtins/autoimplement.workflow.ts +56 -8
- package/src/builtins/catalog.ts +1 -1
- package/src/controllers/index.ts +6 -0
- package/src/controllers/sqlite.ts +129 -35
- package/src/controllers/types.ts +40 -0
- package/src/controllers/workflow-engine-scheduler.ts +103 -1
- package/src/controllers/workflows.ts +68 -0
- package/src/extension/controller-host.ts +6 -1
- package/src/extension/follow-up-coordinator.ts +151 -0
- package/src/extension/index.ts +538 -88
- package/src/extension/session-events.ts +15 -13
- package/src/extension/widget.ts +8 -0
- package/src/state/database.ts +12 -12
- package/src/state/json.ts +6 -1
- package/src/state/mutation.ts +4 -1
- package/src/state/prune.ts +502 -0
- package/src/state/schema.ts +116 -18
- package/src/viewer/cli.ts +52 -5
- package/src/viewer/render.ts +43 -1
- package/src/workflows/composition.ts +19 -0
- package/src/workflows/definition.ts +19 -5
- package/src/workflows/engine.ts +363 -35
- package/src/workflows/human-decision.ts +41 -11
- package/src/workflows/index.ts +43 -0
- package/src/workflows/json-patch.ts +375 -0
- package/src/workflows/schema.ts +31 -1
- package/src/workflows/settings.ts +430 -0
- package/src/workflows/store.ts +1977 -184
- package/src/workflows/tool-input.ts +58 -0
- package/src/workflows/types.ts +43 -4
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
agent,
|
|
3
|
+
allowSettingsPath,
|
|
4
|
+
compute,
|
|
5
|
+
defineWorkflow,
|
|
6
|
+
settingsRoute,
|
|
7
|
+
workflowSettings,
|
|
8
|
+
} from "@osolmaz/pi-workflows";
|
|
9
|
+
|
|
10
|
+
type Settings = {
|
|
11
|
+
instructions: string[];
|
|
12
|
+
route: "normal" | "careful";
|
|
13
|
+
variables: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function parseSettings(value: unknown): Settings {
|
|
17
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
18
|
+
throw new Error("settings must be an object");
|
|
19
|
+
}
|
|
20
|
+
const settings = value as Partial<Settings>;
|
|
21
|
+
if (
|
|
22
|
+
!Array.isArray(settings.instructions) ||
|
|
23
|
+
!settings.instructions.every((item) => typeof item === "string") ||
|
|
24
|
+
(settings.route !== "normal" && settings.route !== "careful") ||
|
|
25
|
+
settings.variables === null ||
|
|
26
|
+
typeof settings.variables !== "object" ||
|
|
27
|
+
Array.isArray(settings.variables)
|
|
28
|
+
) {
|
|
29
|
+
throw new Error("settings are invalid");
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
instructions: [...settings.instructions],
|
|
33
|
+
route: settings.route,
|
|
34
|
+
variables: { ...settings.variables },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default defineWorkflow({
|
|
39
|
+
name: "live-settings",
|
|
40
|
+
settings: workflowSettings<Settings>({
|
|
41
|
+
initial: { instructions: [], route: "normal", variables: {} },
|
|
42
|
+
parse: parseSettings,
|
|
43
|
+
description: "Instructions, variables, and the future route for this example.",
|
|
44
|
+
paths: [
|
|
45
|
+
allowSettingsPath("/instructions", {
|
|
46
|
+
read: ["session", "human"],
|
|
47
|
+
add: ["session", "human"],
|
|
48
|
+
remove: ["session", "human"],
|
|
49
|
+
replace: ["session", "human"],
|
|
50
|
+
}),
|
|
51
|
+
allowSettingsPath("/route", {
|
|
52
|
+
read: ["session", "human"],
|
|
53
|
+
replace: ["session", "human"],
|
|
54
|
+
}),
|
|
55
|
+
allowSettingsPath("/variables", {
|
|
56
|
+
read: ["session", "human"],
|
|
57
|
+
add: ["session", "human"],
|
|
58
|
+
remove: ["session", "human"],
|
|
59
|
+
replace: ["session", "human"],
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
}),
|
|
63
|
+
startAt: "prepare",
|
|
64
|
+
nodes: {
|
|
65
|
+
prepare: agent({
|
|
66
|
+
prompt: ({ settings }) =>
|
|
67
|
+
[
|
|
68
|
+
"Prepare the requested work.",
|
|
69
|
+
`Current instructions and variables: ${JSON.stringify(settings)}`,
|
|
70
|
+
"The user can change future settings or queue several post-completion prompts while this step runs.",
|
|
71
|
+
].join("\n"),
|
|
72
|
+
expectedOutput: `{ "prepared": "summary" }`,
|
|
73
|
+
}),
|
|
74
|
+
choose: settingsRoute({
|
|
75
|
+
run: ({ settings }) => ({ route: (settings as Settings).route }),
|
|
76
|
+
}),
|
|
77
|
+
normal: compute({
|
|
78
|
+
run: ({ settings }) => ({ mode: "normal", settings }),
|
|
79
|
+
}),
|
|
80
|
+
careful: compute({
|
|
81
|
+
run: ({ settings }) => ({ mode: "careful", settings }),
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
edges: [
|
|
85
|
+
{ from: "prepare", to: "choose" },
|
|
86
|
+
{
|
|
87
|
+
from: "choose",
|
|
88
|
+
switch: { on: "$.route", cases: { normal: "normal", careful: "careful" } },
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
presentationPrompt:
|
|
92
|
+
"Explain which route and settings were used. Any queued follow-up prompts run only after this response settles.",
|
|
93
|
+
});
|
package/herdr-plugin.toml
CHANGED
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
includedResult,
|
|
14
14
|
} from "../workflows/definition.js";
|
|
15
15
|
import { digest } from "../workflows/human-decision.js";
|
|
16
|
+
import { allowSettingsPath, workflowSettings } from "../workflows/settings.js";
|
|
16
17
|
import type { WorkflowActionContext, WorkflowNodeContext } from "../workflows/types.js";
|
|
17
18
|
import autodocWorkflow, { type AutodocInput } from "./autodoc.workflow.js";
|
|
18
19
|
import {
|
|
@@ -40,6 +41,11 @@ import workspacePreparationWorkflow, {
|
|
|
40
41
|
type WorkspacePreparationInput,
|
|
41
42
|
} from "./workspace-preparation.workflow.js";
|
|
42
43
|
|
|
44
|
+
export type AutoimplementSettings = {
|
|
45
|
+
merge: boolean;
|
|
46
|
+
addedInstructions: string[];
|
|
47
|
+
};
|
|
48
|
+
|
|
43
49
|
export type AutoimplementInput = {
|
|
44
50
|
task: string;
|
|
45
51
|
plan?: unknown;
|
|
@@ -577,6 +583,27 @@ function parseInput(value: unknown): AutoimplementInput {
|
|
|
577
583
|
};
|
|
578
584
|
}
|
|
579
585
|
|
|
586
|
+
function parseAutoimplementSettings(value: unknown): AutoimplementSettings {
|
|
587
|
+
const settings = requireRecord(value, "autoimplement settings");
|
|
588
|
+
if (typeof settings.merge !== "boolean") {
|
|
589
|
+
throw new Error("autoimplement settings merge must be a boolean");
|
|
590
|
+
}
|
|
591
|
+
if (
|
|
592
|
+
!Array.isArray(settings.addedInstructions) ||
|
|
593
|
+
settings.addedInstructions.some((item) => typeof item !== "string")
|
|
594
|
+
) {
|
|
595
|
+
throw new Error("autoimplement settings addedInstructions must be an array of strings");
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
merge: settings.merge,
|
|
599
|
+
addedInstructions: [...settings.addedInstructions] as string[],
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function autoimplementSettings(context: WorkflowNodeContext): AutoimplementSettings {
|
|
604
|
+
return parseAutoimplementSettings(context.settings);
|
|
605
|
+
}
|
|
606
|
+
|
|
580
607
|
function parseExistingPlan(value: unknown): ExistingPlanDiscovery {
|
|
581
608
|
const result = requireRecord(value, "existing plan discovery");
|
|
582
609
|
if (result.route !== "found" && result.route !== "blocked") {
|
|
@@ -790,7 +817,7 @@ function createBlockerClaim(context: WorkflowNodeContext): BlockerClaim {
|
|
|
790
817
|
unrelatedFailures: Array.isArray(result.unrelatedFailures) ? result.unrelatedFailures : [],
|
|
791
818
|
recoveryAttempts: Array.isArray(result.repairAttempts) ? result.repairAttempts : [],
|
|
792
819
|
alternativesChecked: [],
|
|
793
|
-
authorityFact: `scope=${(context.input as AutoimplementInput).scope ?? "unspecified"}; merge=${(context
|
|
820
|
+
authorityFact: `scope=${(context.input as AutoimplementInput).scope ?? "unspecified"}; merge=${autoimplementSettings(context).merge}`,
|
|
794
821
|
};
|
|
795
822
|
}
|
|
796
823
|
|
|
@@ -919,8 +946,8 @@ function parseDeliveryResult(
|
|
|
919
946
|
if (result.status !== "completed" && result.status !== "blocked") {
|
|
920
947
|
throw new Error("delivery status must be completed or blocked");
|
|
921
948
|
}
|
|
922
|
-
const
|
|
923
|
-
if (
|
|
949
|
+
const settings = autoimplementSettings(context);
|
|
950
|
+
if (settings.merge !== true && result.merged === true) {
|
|
924
951
|
throw new Error("delivery cannot merge without explicit merge: true");
|
|
925
952
|
}
|
|
926
953
|
if (result.status === "blocked") return result;
|
|
@@ -960,7 +987,7 @@ function parseDeliveryResult(
|
|
|
960
987
|
}
|
|
961
988
|
actual.set(repository.repository, repository);
|
|
962
989
|
}
|
|
963
|
-
const mergeExpected =
|
|
990
|
+
const mergeExpected = settings.merge === true;
|
|
964
991
|
for (const expected of published) {
|
|
965
992
|
const repository = actual.get(path.resolve(expected.repository));
|
|
966
993
|
if (repository === undefined || repository.pr !== expected.pr) {
|
|
@@ -1298,6 +1325,28 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1298
1325
|
contractId: "pi-workflows.autoimplement.v1",
|
|
1299
1326
|
name: "autoimplement",
|
|
1300
1327
|
input: parseInput,
|
|
1328
|
+
settings: workflowSettings<AutoimplementSettings, AutoimplementInput>({
|
|
1329
|
+
initial: (input) => ({ merge: input.merge === true, addedInstructions: [] }),
|
|
1330
|
+
parse: parseAutoimplementSettings,
|
|
1331
|
+
description: "Future merge behavior and instructions added during this run.",
|
|
1332
|
+
paths: [
|
|
1333
|
+
allowSettingsPath("/merge", {
|
|
1334
|
+
read: ["session", "human"],
|
|
1335
|
+
replace: ["session", "human"],
|
|
1336
|
+
}),
|
|
1337
|
+
allowSettingsPath("/addedInstructions", {
|
|
1338
|
+
read: ["session", "human"],
|
|
1339
|
+
add: ["session", "human"],
|
|
1340
|
+
remove: ["session", "human"],
|
|
1341
|
+
replace: ["session", "human"],
|
|
1342
|
+
}),
|
|
1343
|
+
],
|
|
1344
|
+
validateChange: ({ before, after, actor }) => {
|
|
1345
|
+
if (actor.type === "session" && before.merge === false && after.merge === true) {
|
|
1346
|
+
throw new Error("A model workflow step cannot grant merge authority");
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
}),
|
|
1301
1350
|
title: ({ input }) => `autoimplement: ${input.task.slice(0, 60)}`,
|
|
1302
1351
|
presentationPrompt:
|
|
1303
1352
|
"Summarize what was implemented, the review rounds by severity, the CI result, the PR or merge result, and any remaining limitation. Include exact validation commands.",
|
|
@@ -1616,7 +1665,7 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1616
1665
|
`Current result and claimed blocker: ${JSON.stringify(latestBlockerClaim(context))}`,
|
|
1617
1666
|
`Authorized scope: ${request.scope ?? request.repository ?? "the current repository and task"}`,
|
|
1618
1667
|
`Constraints and authority: ${JSON.stringify(request.constraints ?? [])}`,
|
|
1619
|
-
`Merge
|
|
1668
|
+
`Merge currently allowed: ${autoimplementSettings(context).merge === true}`,
|
|
1620
1669
|
`Previous blocker challenges: ${JSON.stringify(blockerChallenges(context))}`,
|
|
1621
1670
|
`Recent workflow attempts: ${JSON.stringify(recentWorkflowAttempts(context))}`,
|
|
1622
1671
|
].join("\n");
|
|
@@ -1925,10 +1974,9 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1925
1974
|
timeoutMs: 30 * 60_000,
|
|
1926
1975
|
statusDetail: "finalizing PRs",
|
|
1927
1976
|
prompt: (context) => {
|
|
1928
|
-
const request = context.input as AutoimplementInput;
|
|
1929
1977
|
return [
|
|
1930
|
-
|
|
1931
|
-
? "Leave every verified PR ready without merging because
|
|
1978
|
+
autoimplementSettings(context).merge === false
|
|
1979
|
+
? "Leave every verified PR ready without merging because current workflow settings disable merge."
|
|
1932
1980
|
: "Handle verified PRs one at a time and merge each unless repository policy or explicit user instructions prohibit it.",
|
|
1933
1981
|
"Before each mutation, inspect the current PR head, merge state, and existing final report. Do not merge an already merged expected head or post a duplicate report.",
|
|
1934
1982
|
"Use each repository's required merge method.",
|
package/src/builtins/catalog.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
|
|
|
11
11
|
{ id: "plain-summary", revision: "3", definition: plainSummaryWorkflow },
|
|
12
12
|
{ id: "autoplan", revision: "5", definition: autoplanWorkflow },
|
|
13
13
|
{ id: "autodoc", revision: "2", definition: autodocWorkflow },
|
|
14
|
-
{ id: "autoimplement", revision: "
|
|
14
|
+
{ id: "autoimplement", revision: "11", definition: autoimplementWorkflow },
|
|
15
15
|
{ id: "plan-approval", revision: "4", definition: planApprovalWorkflow },
|
|
16
16
|
{ id: "sanity-check", revision: "6", definition: sanityCheckWorkflow },
|
|
17
17
|
{
|
package/src/controllers/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ export {
|
|
|
49
49
|
} from "./workflow-engine-scheduler.js";
|
|
50
50
|
export {
|
|
51
51
|
ControllerWorkflowCoordinator,
|
|
52
|
+
type ControllerWorkflowControlRequest,
|
|
52
53
|
type ControllerWorkflowScheduler,
|
|
53
54
|
type WorkflowSchedulerRequest,
|
|
54
55
|
type WorkflowSchedulerResult,
|
|
@@ -64,6 +65,11 @@ export type {
|
|
|
64
65
|
ControllerConditionStatus,
|
|
65
66
|
ControllerDefinition,
|
|
66
67
|
ControllerEffects,
|
|
68
|
+
ControllerFollowUpResult,
|
|
69
|
+
ControllerQueueFollowUpRequest,
|
|
70
|
+
ControllerRemoveFollowUpRequest,
|
|
71
|
+
ControllerSettingsChangeRequest,
|
|
72
|
+
ControllerSettingsChangeResult,
|
|
67
73
|
ControllerEvent,
|
|
68
74
|
ControllerQueueClaim,
|
|
69
75
|
ControllerResource,
|
|
@@ -155,9 +155,8 @@ type RunRow = {
|
|
|
155
155
|
workflowName: string;
|
|
156
156
|
workflowRef: string;
|
|
157
157
|
runStatus: string;
|
|
158
|
-
workflowSourceHash: Buffer;
|
|
159
|
-
workflowSourcesHash: Buffer | null;
|
|
160
158
|
definitionDigest: Buffer;
|
|
159
|
+
definitionHash: Buffer;
|
|
161
160
|
inputHash: Buffer;
|
|
162
161
|
launchOptionsHash: Buffer;
|
|
163
162
|
status: WorkflowRunLaunchStatus;
|
|
@@ -176,6 +175,12 @@ type RunRow = {
|
|
|
176
175
|
ownerId: string | null;
|
|
177
176
|
claimExpiresAt: number | null;
|
|
178
177
|
};
|
|
178
|
+
type RunSourceIdentityRow = {
|
|
179
|
+
mountPath: string;
|
|
180
|
+
sourceType: "builtin" | "file";
|
|
181
|
+
sourceRef: string;
|
|
182
|
+
sourceRevision: string;
|
|
183
|
+
};
|
|
179
184
|
|
|
180
185
|
type EffectRow = {
|
|
181
186
|
effectId: string;
|
|
@@ -1049,9 +1054,6 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1049
1054
|
const resourceId = resourceIdFor("run", options.runId);
|
|
1050
1055
|
const definitionHash = this.state.putJson(options.definitionSnapshot, now);
|
|
1051
1056
|
const queuedSource = queuedWorkflowSource(options.workflowSource);
|
|
1052
|
-
const sourceHash = this.state.putJson(queuedSource.root, now);
|
|
1053
|
-
const mountedSourcesHash =
|
|
1054
|
-
queuedSource.mounted.length === 0 ? null : this.state.putJson(queuedSource.mounted, now);
|
|
1055
1057
|
const inputHash = this.state.putJson(options.input ?? null, now);
|
|
1056
1058
|
const launchHash = this.state.putJson(options.launchOptions ?? {}, now);
|
|
1057
1059
|
this.state.connection
|
|
@@ -1071,15 +1073,13 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1071
1073
|
this.state.connection
|
|
1072
1074
|
.prepare("INSERT INTO leases(resource_id, generation) VALUES (?, 0)")
|
|
1073
1075
|
.run(resourceId);
|
|
1074
|
-
const source = sourceParts(options.workflowSourceRef, options.definitionDigest);
|
|
1075
1076
|
this.state.connection
|
|
1076
1077
|
.prepare(
|
|
1077
1078
|
`INSERT INTO runs(
|
|
1078
1079
|
run_id, resource_id, project_id, parent_run_id, definition_digest,
|
|
1079
|
-
workflow_ref,
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', 0, ?, ?, ?, ?)`,
|
|
1080
|
+
workflow_ref, launch_options_hash, status, paused,
|
|
1081
|
+
input_hash, created_at, updated_at
|
|
1082
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'queued', 0, ?, ?, ?)`,
|
|
1083
1083
|
)
|
|
1084
1084
|
.run(
|
|
1085
1085
|
options.runId,
|
|
@@ -1088,16 +1088,12 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1088
1088
|
options.parentRunId ?? null,
|
|
1089
1089
|
definitionDigest,
|
|
1090
1090
|
options.workflowSourceRef,
|
|
1091
|
-
sourceHash,
|
|
1092
1091
|
launchHash,
|
|
1093
|
-
source.type,
|
|
1094
|
-
source.ref,
|
|
1095
|
-
source.revision,
|
|
1096
1092
|
inputHash,
|
|
1097
|
-
mountedSourcesHash,
|
|
1098
1093
|
now,
|
|
1099
1094
|
now,
|
|
1100
1095
|
);
|
|
1096
|
+
insertQueuedRunSources(this.state, options.runId, queuedSource);
|
|
1101
1097
|
this.state.connection
|
|
1102
1098
|
.prepare(
|
|
1103
1099
|
`INSERT INTO run_bindings(run_id, origin_session_id, execution_mode, created_at)
|
|
@@ -2238,11 +2234,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2238
2234
|
runId: row.runId,
|
|
2239
2235
|
workflowName: row.workflowName,
|
|
2240
2236
|
workflowSourceRef: row.workflowRef,
|
|
2241
|
-
workflowSource:
|
|
2242
|
-
root: this.state.readJson(row.workflowSourceHash),
|
|
2243
|
-
mounted:
|
|
2244
|
-
row.workflowSourcesHash === null ? [] : this.state.readJson(row.workflowSourcesHash),
|
|
2245
|
-
},
|
|
2237
|
+
workflowSource: readQueuedRunSources(this.state, row),
|
|
2246
2238
|
initialized: row.runStatus !== "queued",
|
|
2247
2239
|
definitionDigest: `sha256:${row.definitionDigest.toString("hex")}`,
|
|
2248
2240
|
input: this.state.readJson(row.inputHash),
|
|
@@ -2469,8 +2461,6 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2469
2461
|
.prepare(
|
|
2470
2462
|
`UPDATE runs
|
|
2471
2463
|
SET status = ?, paused = 0, status_detail = NULL, error_hash = ?,
|
|
2472
|
-
current_node = NULL, current_attempt_id = NULL,
|
|
2473
|
-
current_node_started_at = NULL, waiting_on = NULL,
|
|
2474
2464
|
updated_at = ?, finished_at = ?
|
|
2475
2465
|
WHERE run_id = ?`,
|
|
2476
2466
|
)
|
|
@@ -2776,9 +2766,8 @@ function workflowSelect(clause: string): string {
|
|
|
2776
2766
|
function workflowRunSelect(clause: string): string {
|
|
2777
2767
|
return `SELECT r.run_id AS runId, r.resource_id AS resourceId,
|
|
2778
2768
|
d.workflow_name AS workflowName, r.workflow_ref AS workflowRef, r.status AS runStatus,
|
|
2779
|
-
r.
|
|
2780
|
-
r.
|
|
2781
|
-
r.definition_digest AS definitionDigest, r.input_hash AS inputHash,
|
|
2769
|
+
r.definition_digest AS definitionDigest, d.definition_hash AS definitionHash,
|
|
2770
|
+
r.input_hash AS inputHash,
|
|
2782
2771
|
r.launch_options_hash AS launchOptionsHash,
|
|
2783
2772
|
q.status, q.available_at AS availableAt, q.affinity_runner_id AS affinityRunnerId,
|
|
2784
2773
|
q.consecutive_errors AS consecutiveErrors, q.error_code AS errorCode,
|
|
@@ -2886,6 +2875,9 @@ function isLeaseRow(value: unknown): value is LeaseRow {
|
|
|
2886
2875
|
function isRunRow(value: unknown): value is RunRow {
|
|
2887
2876
|
return isRecord(value);
|
|
2888
2877
|
}
|
|
2878
|
+
function isRunSourceIdentityRow(value: unknown): value is RunSourceIdentityRow {
|
|
2879
|
+
return isRecord(value);
|
|
2880
|
+
}
|
|
2889
2881
|
function isEffectRow(value: unknown): value is EffectRow {
|
|
2890
2882
|
return isRecord(value);
|
|
2891
2883
|
}
|
|
@@ -2958,24 +2950,126 @@ function digestBuffer(value: string): Buffer {
|
|
|
2958
2950
|
if (!/^[a-f0-9]{64}$/i.test(hex)) throw new Error("Expected a SHA-256 digest");
|
|
2959
2951
|
return Buffer.from(hex, "hex");
|
|
2960
2952
|
}
|
|
2961
|
-
|
|
2962
|
-
|
|
2953
|
+
type QueuedSource = {
|
|
2954
|
+
root:
|
|
2955
|
+
| { kind: "builtin"; id: string; revision: string }
|
|
2956
|
+
| { kind: "file"; path: string; hash: string };
|
|
2957
|
+
mounted: Array<{
|
|
2958
|
+
mountPath: string[];
|
|
2959
|
+
workflowName: string;
|
|
2960
|
+
source:
|
|
2961
|
+
| { kind: "builtin"; id: string; revision: string }
|
|
2962
|
+
| { kind: "file"; path: string; hash: string };
|
|
2963
|
+
}>;
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2966
|
+
function queuedWorkflowSource(value: unknown): QueuedSource {
|
|
2967
|
+
if (isWorkflowSource(value)) {
|
|
2963
2968
|
return { root: value, mounted: [] };
|
|
2964
2969
|
}
|
|
2965
|
-
if (
|
|
2970
|
+
if (
|
|
2971
|
+
isRecord(value) &&
|
|
2972
|
+
isWorkflowSource(value.root) &&
|
|
2973
|
+
Array.isArray(value.mounted) &&
|
|
2974
|
+
value.mounted.every(isMountedWorkflowSource)
|
|
2975
|
+
) {
|
|
2966
2976
|
return { root: value.root, mounted: value.mounted };
|
|
2967
2977
|
}
|
|
2968
2978
|
throw new Error("Stored workflow source identity is invalid");
|
|
2969
2979
|
}
|
|
2970
2980
|
|
|
2971
|
-
function
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2981
|
+
function isWorkflowSource(value: unknown): value is QueuedSource["root"] {
|
|
2982
|
+
return (
|
|
2983
|
+
isRecord(value) &&
|
|
2984
|
+
((value.kind === "builtin" &&
|
|
2985
|
+
typeof value.id === "string" &&
|
|
2986
|
+
typeof value.revision === "string") ||
|
|
2987
|
+
(value.kind === "file" && typeof value.path === "string" && typeof value.hash === "string"))
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
function isMountedWorkflowSource(value: unknown): value is QueuedSource["mounted"][number] {
|
|
2992
|
+
return (
|
|
2993
|
+
isRecord(value) &&
|
|
2994
|
+
Array.isArray(value.mountPath) &&
|
|
2995
|
+
value.mountPath.every((part) => typeof part === "string") &&
|
|
2996
|
+
typeof value.workflowName === "string" &&
|
|
2997
|
+
isWorkflowSource(value.source)
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
function insertQueuedRunSources(state: StateDatabase, runId: string, value: QueuedSource): void {
|
|
3002
|
+
const insert = state.connection.prepare(
|
|
3003
|
+
`INSERT INTO run_sources(run_id, mount_path, source_type, source_ref, source_revision)
|
|
3004
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
3005
|
+
);
|
|
3006
|
+
const root = queuedSourceParts(value.root);
|
|
3007
|
+
insert.run(runId, "", root.type, root.ref, root.revision);
|
|
3008
|
+
for (const mounted of value.mounted) {
|
|
3009
|
+
const source = queuedSourceParts(mounted.source);
|
|
3010
|
+
insert.run(runId, mounted.mountPath.join("/"), source.type, source.ref, source.revision);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
function queuedSourceParts(source: QueuedSource["root"]): {
|
|
3015
|
+
type: "builtin" | "file";
|
|
3016
|
+
ref: string;
|
|
3017
|
+
revision: string;
|
|
3018
|
+
} {
|
|
3019
|
+
return source.kind === "builtin"
|
|
3020
|
+
? { type: "builtin", ref: source.id, revision: source.revision }
|
|
3021
|
+
: { type: "file", ref: source.path, revision: source.hash };
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
function readQueuedRunSources(state: StateDatabase, run: RunRow): QueuedSource {
|
|
3025
|
+
const rows = state.connection
|
|
3026
|
+
.prepare(
|
|
3027
|
+
`SELECT mount_path AS mountPath, source_type AS sourceType,
|
|
3028
|
+
source_ref AS sourceRef, source_revision AS sourceRevision
|
|
3029
|
+
FROM run_sources WHERE run_id = ? ORDER BY mount_path`,
|
|
3030
|
+
)
|
|
3031
|
+
.all(run.runId)
|
|
3032
|
+
.filter(isRunSourceIdentityRow);
|
|
3033
|
+
const rootRow = rows.find((row) => row.mountPath === "");
|
|
3034
|
+
if (rootRow === undefined) throw new Error(`Workflow run source is missing: ${run.runId}`);
|
|
3035
|
+
const snapshot = state.readJson(run.definitionHash);
|
|
3036
|
+
const mounts =
|
|
3037
|
+
isRecord(snapshot) &&
|
|
3038
|
+
isRecord(snapshot.composition) &&
|
|
3039
|
+
Array.isArray(snapshot.composition.mounts)
|
|
3040
|
+
? snapshot.composition.mounts
|
|
3041
|
+
: [];
|
|
3042
|
+
const names = new Map(
|
|
3043
|
+
mounts.flatMap((mount) => {
|
|
3044
|
+
if (
|
|
3045
|
+
!isRecord(mount) ||
|
|
3046
|
+
!Array.isArray(mount.mountPath) ||
|
|
3047
|
+
!mount.mountPath.every((part) => typeof part === "string") ||
|
|
3048
|
+
typeof mount.workflowName !== "string"
|
|
3049
|
+
) {
|
|
3050
|
+
return [];
|
|
3051
|
+
}
|
|
3052
|
+
return [[mount.mountPath.join("/"), mount.workflowName] as const];
|
|
3053
|
+
}),
|
|
3054
|
+
);
|
|
3055
|
+
return {
|
|
3056
|
+
root: rowToSource(rootRow),
|
|
3057
|
+
mounted: rows
|
|
3058
|
+
.filter((row) => row.mountPath !== "")
|
|
3059
|
+
.map((row) => ({
|
|
3060
|
+
mountPath: row.mountPath.split("/"),
|
|
3061
|
+
workflowName: names.get(row.mountPath) ?? row.mountPath,
|
|
3062
|
+
source: rowToSource(row),
|
|
3063
|
+
})),
|
|
3064
|
+
};
|
|
2978
3065
|
}
|
|
3066
|
+
|
|
3067
|
+
function rowToSource(row: RunSourceIdentityRow): QueuedSource["root"] {
|
|
3068
|
+
return row.sourceType === "builtin"
|
|
3069
|
+
? { kind: "builtin", id: row.sourceRef, revision: row.sourceRevision }
|
|
3070
|
+
: { kind: "file", path: row.sourceRef, hash: row.sourceRevision };
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2979
3073
|
function effectStatus(value: EffectRecord["state"]): string {
|
|
2980
3074
|
return value === "indeterminate" ? "ambiguous" : value;
|
|
2981
3075
|
}
|
package/src/controllers/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { JsonPatch } from "../workflows/json-patch.js";
|
|
2
|
+
|
|
1
3
|
export type MaybePromise<T> = T | Promise<T>;
|
|
2
4
|
export type JsonPrimitive = string | number | boolean | null;
|
|
3
5
|
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
|
@@ -131,8 +133,46 @@ export type ChildWorkflowRecord = ChildWorkflowReference & {
|
|
|
131
133
|
error?: string;
|
|
132
134
|
};
|
|
133
135
|
|
|
136
|
+
export type ControllerSettingsChangeRequest = {
|
|
137
|
+
requestKey: string;
|
|
138
|
+
runId: string;
|
|
139
|
+
scopeId?: string;
|
|
140
|
+
expectedChangeNumber?: number;
|
|
141
|
+
patch: JsonPatch;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export type ControllerSettingsChangeResult = {
|
|
145
|
+
runId: string;
|
|
146
|
+
scopeId: string;
|
|
147
|
+
changeNumber: number;
|
|
148
|
+
adopted: boolean;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type ControllerQueueFollowUpRequest = {
|
|
152
|
+
requestKey: string;
|
|
153
|
+
runId: string;
|
|
154
|
+
prompt: string;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export type ControllerFollowUpResult = {
|
|
158
|
+
runId: string;
|
|
159
|
+
followUpId: string;
|
|
160
|
+
order: number;
|
|
161
|
+
state: string;
|
|
162
|
+
adopted: boolean;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export type ControllerRemoveFollowUpRequest = {
|
|
166
|
+
requestKey: string;
|
|
167
|
+
runId: string;
|
|
168
|
+
followUpId: string;
|
|
169
|
+
};
|
|
170
|
+
|
|
134
171
|
export interface ControllerWorkflows {
|
|
135
172
|
ensure(request: ChildWorkflowRequest): Promise<ChildWorkflowRecord>;
|
|
173
|
+
changeSettings(request: ControllerSettingsChangeRequest): Promise<ControllerSettingsChangeResult>;
|
|
174
|
+
queueFollowUp(request: ControllerQueueFollowUpRequest): Promise<ControllerFollowUpResult>;
|
|
175
|
+
removeFollowUp(request: ControllerRemoveFollowUpRequest): Promise<ControllerFollowUpResult>;
|
|
136
176
|
}
|
|
137
177
|
|
|
138
178
|
export type ReconcileContext<TStatus> = {
|