@osolmaz/pi-workflows 0.13.1 → 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 +9 -7
- package/dist/builtins/autodoc.workflow.d.ts +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +69 -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 +11 -5
- package/dist/builtins/autoplan.workflow.js +68 -32
- package/dist/builtins/autoplan.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +4 -4
- package/dist/builtins/change-verification.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.js +35 -24
- package/dist/builtins/plain-summary.workflow.js.map +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +25 -23
- package/dist/builtins/sanity-check.workflow.d.ts +3 -3
- package/dist/builtins/sanity-check.workflow.js +19 -22
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- 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 +88 -61
- 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/decision-channels.js +45 -7
- package/dist/extension/decision-channels.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 +462 -76
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +8 -8
- package/dist/extension/recorder.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 +130 -13
- 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/viewer/session-reducer.d.ts +0 -1
- package/dist/viewer/session-reducer.js +2 -24
- package/dist/viewer/session-reducer.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 +303 -36
- 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 +81 -3
- package/dist/workflows/store.js +1563 -50
- 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 +41 -6
- 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 +36 -17
- package/docs/WORKFLOW_COMPOSITION.md +15 -0
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
- package/docs/plans/2026-08-25-autoplan-user-intent-capture-plan.md +107 -0
- 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/session-event-journal.md +2 -3
- package/docs/tui-viewer.md +7 -6
- package/docs/workflows.md +58 -10
- 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/autoplan.workflow.ts +80 -32
- package/src/builtins/catalog.ts +4 -4
- package/src/builtins/plain-summary.workflow.ts +38 -40
- package/src/builtins/sanity-check.workflow.ts +19 -22
- package/src/controllers/index.ts +6 -0
- package/src/controllers/sqlite.ts +132 -72
- 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/decision-channels.ts +47 -7
- package/src/extension/follow-up-coordinator.ts +151 -0
- package/src/extension/index.ts +540 -89
- package/src/extension/recorder.ts +10 -8
- 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 +130 -13
- package/src/viewer/cli.ts +52 -5
- package/src/viewer/render.ts +43 -1
- package/src/viewer/session-reducer.ts +2 -29
- package/src/workflows/composition.ts +19 -0
- package/src/workflows/definition.ts +19 -5
- package/src/workflows/engine.ts +365 -36
- 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 +2268 -121
- package/src/workflows/tool-input.ts +58 -0
- package/src/workflows/types.ts +43 -5
package/docs/workflows.md
CHANGED
|
@@ -85,6 +85,9 @@ type WorkflowNodeContext = {
|
|
|
85
85
|
outputs: Record<string, unknown>; // accepted output per finished node id
|
|
86
86
|
results: Record<string, WorkflowNodeResult>; // full result records, including failures
|
|
87
87
|
state: WorkflowRunState; // the live run state (read-only by convention)
|
|
88
|
+
settings?: unknown; // fixed typed settings for this attempt, when declared
|
|
89
|
+
settingsScopeId?: string;
|
|
90
|
+
settingsChangeNumber?: number;
|
|
88
91
|
signal: AbortSignal; // aborted on node timeout or run cancellation
|
|
89
92
|
};
|
|
90
93
|
```
|
|
@@ -98,6 +101,12 @@ Long-running compute, action, and checkpoint callbacks should observe
|
|
|
98
101
|
steps). When the node times out or the run is cancelled, the engine stops
|
|
99
102
|
waiting immediately, but only cooperative callbacks stop doing work.
|
|
100
103
|
|
|
104
|
+
A workflow can declare typed JSON settings with `workflowSettings()`. Each node
|
|
105
|
+
attempt receives one fixed copy. Authorized changes use RFC 6902 JSON Patch and
|
|
106
|
+
affect only later attempts. Use `settingsRoute()` for a pure route choice that
|
|
107
|
+
must retry when a newer settings change wins before route settlement. See
|
|
108
|
+
[Change workflow settings during a run](2026-08-25-workflow-settings.md).
|
|
109
|
+
|
|
101
110
|
Function actions receive `WorkflowActionContext`, which adds
|
|
102
111
|
`publishUpdate(update)`. Other callbacks keep the read-only node context.
|
|
103
112
|
|
|
@@ -424,6 +433,9 @@ The model sees one `workflow` tool. Its `action` field supports:
|
|
|
424
433
|
- `status` for the active run or a supplied run ID.
|
|
425
434
|
- `pause`, `resume`, and `cancel` for the active run.
|
|
426
435
|
- `answer` with ordinary checkpoint input and an optional run ID. Protected `humanDecision()` gates reject this model-facing action.
|
|
436
|
+
- `change-settings` with an RFC 6902 patch, optional scope ID, and optional expected change number.
|
|
437
|
+
- `queue-follow-up` to save one ordered normal user prompt for after successful completion.
|
|
438
|
+
- `remove-follow-up` to remove an unsent prompt created by the same model source.
|
|
427
439
|
- `update` for a non-completing update from the current agent attempt.
|
|
428
440
|
- `submit` for the current workflow step contract.
|
|
429
441
|
|
|
@@ -432,16 +444,25 @@ workflow prompt then starts a new turn. This keeps the requesting turn outside
|
|
|
432
444
|
the workflow's first attempt and prevents an early missing-submission reminder.
|
|
433
445
|
The normal extension offers all actions. The headless RPC bridge offers only
|
|
434
446
|
`update` and `submit`, so a workflow child cannot recursively control other
|
|
435
|
-
runs.
|
|
447
|
+
runs. Direct `/workflow change-settings`, `queue-follow-up`, and
|
|
448
|
+
`remove-follow-up` commands use verified interactive provenance. Controller
|
|
449
|
+
code can use the matching `ctx.workflows` methods. All surfaces call the same
|
|
450
|
+
SQLite operations.
|
|
451
|
+
|
|
452
|
+
Follow-up prompts stay separate from workflow settings. Successful terminal
|
|
453
|
+
state is saved before delivery. A final presentation settles first. The Pi
|
|
454
|
+
extension then sends prompts in order as normal user messages without
|
|
455
|
+
reactivating the completed run. See [Continue normal work after a workflow
|
|
456
|
+
finishes](2026-08-25-workflow-follow-ups.md).
|
|
436
457
|
|
|
437
458
|
### Built-in plain summary
|
|
438
459
|
|
|
439
460
|
The built-in `plain-summary` workflow turns supplied structured data into one
|
|
440
|
-
|
|
461
|
+
normal assistant message. Its input has `source`, `purpose`, optional
|
|
441
462
|
`mustInclude`, optional `maxChars`, optional `maxSentences`, and `format` set to
|
|
442
|
-
`paragraphs`, `bullets`, or `mixed`. The workflow
|
|
443
|
-
|
|
444
|
-
`assistantMessage()` itself has no default limit.
|
|
463
|
+
`paragraphs`, `bullets`, or `mixed`. The workflow applies no character or
|
|
464
|
+
sentence limit by default. A caller can request either limit with a positive
|
|
465
|
+
integer. `assistantMessage()` itself also has no default limit.
|
|
445
466
|
|
|
446
467
|
The summarizer uses only the supplied source, treats instructions inside that
|
|
447
468
|
source as data, keeps required points, and returns the same text as its
|
|
@@ -452,11 +473,38 @@ before the parent continues.
|
|
|
452
473
|
|
|
453
474
|
### Built-in planning and implementation
|
|
454
475
|
|
|
455
|
-
The built-in `autoplan` workflow
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
476
|
+
The built-in `autoplan` workflow first asks the model to capture everything the
|
|
477
|
+
user has instructed for the intended purpose in the available conversation
|
|
478
|
+
context. Relevant earlier and queued user messages must keep their wording and
|
|
479
|
+
order. The model returns one `originalUserInstructions` string and must not
|
|
480
|
+
summarize, rewrite, explain, label, omit, or add instructions. Validation checks
|
|
481
|
+
only that the string is not empty and preserves the accepted text without
|
|
482
|
+
normalization. Every later Autoplan agent prompt includes this string as the
|
|
483
|
+
authoritative user instructions. Structured run input such as scope,
|
|
484
|
+
constraints, a previous plan, and new evidence remains supplemental. Ready and
|
|
485
|
+
blocked final outputs include the same string for audit and downstream use.
|
|
486
|
+
|
|
487
|
+
Autoplan then records two through four practical candidates, describes the ideal
|
|
488
|
+
separately, chooses one option, records a rejection reason for every other
|
|
489
|
+
explicit option, and writes a detailed plan. It includes `plain-summary` to show
|
|
490
|
+
the chosen plan, its main steps, and the rejected options in one assistant
|
|
491
|
+
message without a character or sentence limit. The detailed records remain in
|
|
492
|
+
the run bundle. See
|
|
493
|
+
[Capture the user's complete intent in Autoplan](plans/2026-08-25-autoplan-user-intent-capture-plan.md)
|
|
494
|
+
for the selected design and implementation plan.
|
|
495
|
+
|
|
496
|
+
The standalone `autodoc` workflow finds an already selected plan, records it in
|
|
497
|
+
canonical documentation, and never devises or implements. It prepares a safe
|
|
498
|
+
workspace only when documentation must change. Program actions run candidate
|
|
499
|
+
checks, compare eligible failures with the base revision in a temporary
|
|
500
|
+
detached worktree, and keep matching baseline failures visible without blocking
|
|
501
|
+
the current change. The built-in `autoimplement` workflow finds a clear existing
|
|
502
|
+
plan from explicit input, conversation context, or referenced canonical
|
|
503
|
+
documents. A missing-plan claim and every other non-exempt blocker enter one
|
|
504
|
+
bounded challenge path. An explicit plan bypasses autodoc only when a
|
|
505
|
+
current-document receipt carries its matching plan digest; otherwise autodoc
|
|
506
|
+
inspects and adopts or updates the canonical documents. Later invalidating
|
|
507
|
+
evidence returns to `autoplan` followed by `autodoc`.
|
|
460
508
|
|
|
461
509
|
The built-in `plan-approval` workflow offers `continue`, `stop`, and exact-text `replan` exits. Its shared policy uses `auto`, `required`, or `skip` mode. Omitted policy defaults to `auto`: ask audience `operator`, then continue with the exact plan after 10 minutes without an answer. Required mode waits for a human. Skip mode creates no decision. Stop and replan always require a human answer.
|
|
462
510
|
|
|
@@ -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.",
|
|
@@ -46,8 +46,13 @@ export type AutoplanSelection = {
|
|
|
46
46
|
blocker?: string;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
export type AutoplanIntent = {
|
|
50
|
+
originalUserInstructions: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
49
53
|
export type AutoplanReady = {
|
|
50
54
|
status: "ready";
|
|
55
|
+
originalUserInstructions: string;
|
|
51
56
|
frame: unknown;
|
|
52
57
|
proposal: AutoplanProposal;
|
|
53
58
|
ideal: AutoplanIdeal;
|
|
@@ -61,6 +66,7 @@ export type AutoplanReady = {
|
|
|
61
66
|
|
|
62
67
|
export type AutoplanBlocked = {
|
|
63
68
|
status: "blocked";
|
|
69
|
+
originalUserInstructions: string;
|
|
64
70
|
frame: unknown;
|
|
65
71
|
proposal: AutoplanProposal;
|
|
66
72
|
ideal: AutoplanIdeal;
|
|
@@ -76,6 +82,25 @@ function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
|
|
76
82
|
return value as Record<string, unknown>;
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function parseIntent(value: unknown): AutoplanIntent {
|
|
86
|
+
const intent = requireRecord(value, "autoplan user intent");
|
|
87
|
+
const instructions = intent.originalUserInstructions;
|
|
88
|
+
if (typeof instructions !== "string" || instructions.trim().length === 0) {
|
|
89
|
+
throw new Error("autoplan originalUserInstructions must be a non-empty string");
|
|
90
|
+
}
|
|
91
|
+
return { originalUserInstructions: instructions };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function originalUserInstructions(outputs: Record<string, unknown>): string {
|
|
95
|
+
return parseIntent(outputs.captureIntent).originalUserInstructions;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function originalInstructionsPrompt(outputs: Record<string, unknown>): string {
|
|
99
|
+
return ["Original user instructions (authoritative):", originalUserInstructions(outputs)].join(
|
|
100
|
+
"\n",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
79
104
|
function requireString(value: unknown, label: string): string {
|
|
80
105
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
81
106
|
throw new Error(`${label} must be a non-empty string`);
|
|
@@ -279,6 +304,7 @@ function summaryInput(
|
|
|
279
304
|
const selected = candidateSummary(proposal, ideal, selection.selectedId);
|
|
280
305
|
return {
|
|
281
306
|
source: {
|
|
307
|
+
originalUserInstructions: originalUserInstructions(outputs),
|
|
282
308
|
status: selection.status,
|
|
283
309
|
selected,
|
|
284
310
|
why: selection.why,
|
|
@@ -307,8 +333,6 @@ function summaryInput(
|
|
|
307
333
|
: []),
|
|
308
334
|
...(blocked ? [selection.blocker as string] : ["The plan is selected for approval"]),
|
|
309
335
|
],
|
|
310
|
-
maxChars: 2_500,
|
|
311
|
-
maxSentences: 12,
|
|
312
336
|
format: "mixed",
|
|
313
337
|
};
|
|
314
338
|
}
|
|
@@ -319,7 +343,7 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
319
343
|
name: "autoplan",
|
|
320
344
|
input: parseInput,
|
|
321
345
|
title: ({ input }) => `autoplan: ${input.problem.slice(0, 60)}`,
|
|
322
|
-
startAt: "
|
|
346
|
+
startAt: "captureIntent",
|
|
323
347
|
maxSteps: 16,
|
|
324
348
|
includes: {
|
|
325
349
|
readySummary: includeWorkflow(plainSummaryWorkflow, {
|
|
@@ -340,18 +364,35 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
340
364
|
},
|
|
341
365
|
},
|
|
342
366
|
nodes: {
|
|
367
|
+
captureIntent: agent({
|
|
368
|
+
statusDetail: "capturing the user's instructions",
|
|
369
|
+
prompt: () =>
|
|
370
|
+
[
|
|
371
|
+
"Read the conversation that came before this workflow step.",
|
|
372
|
+
"Return one text string named originalUserInstructions.",
|
|
373
|
+
"Include everything that the user has instructed for the intended purpose in the given context.",
|
|
374
|
+
"Include relevant earlier or queued user messages that are present in the context.",
|
|
375
|
+
"When several messages contribute, preserve their wording and chronological order in the one text value.",
|
|
376
|
+
"Do not summarize, rewrite, explain, label, omit, or add instructions.",
|
|
377
|
+
"Do not return an array or message objects.",
|
|
378
|
+
].join("\n"),
|
|
379
|
+
expectedOutput: `{ "originalUserInstructions": "all relevant user instructions in one text string" }`,
|
|
380
|
+
validate: parseIntent,
|
|
381
|
+
}),
|
|
343
382
|
frame: agent({
|
|
344
383
|
statusDetail: "framing the problem",
|
|
345
|
-
prompt: ({ input }) => {
|
|
384
|
+
prompt: ({ outputs, input }) => {
|
|
346
385
|
const request = input as AutoplanInput;
|
|
347
386
|
return [
|
|
348
|
-
|
|
349
|
-
`
|
|
387
|
+
originalInstructionsPrompt(outputs),
|
|
388
|
+
`Caller-provided problem description (supplemental): ${request.problem}`,
|
|
389
|
+
`Allowed scope: ${request.scope ?? "infer it conservatively from the request and current project"}.`,
|
|
350
390
|
`Constraints: ${JSON.stringify(request.constraints ?? [])}.`,
|
|
351
391
|
`Previous plan: ${JSON.stringify(request.previousPlan ?? null)}.`,
|
|
352
392
|
`New evidence: ${JSON.stringify(request.newEvidence ?? null)}.`,
|
|
353
|
-
"
|
|
354
|
-
"
|
|
393
|
+
"State the goal and describe what success looks like.",
|
|
394
|
+
"List the systems we may change and the systems we must leave alone. Name the interfaces we control.",
|
|
395
|
+
"Do not assume permission to change an upstream project, an external service, or an unrelated repository.",
|
|
355
396
|
].join("\n");
|
|
356
397
|
},
|
|
357
398
|
expectedOutput: `{ "problem": "concise statement", "success": ["criterion"], "inScope": ["change"], "outOfScope": ["change"], "constraints": ["constraint"], "controlBoundary": "what can change" }`,
|
|
@@ -361,12 +402,13 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
361
402
|
statusDetail: "devising candidate solutions",
|
|
362
403
|
prompt: ({ outputs, input }) =>
|
|
363
404
|
[
|
|
364
|
-
|
|
365
|
-
"
|
|
366
|
-
"
|
|
367
|
-
"
|
|
368
|
-
"
|
|
369
|
-
"
|
|
405
|
+
originalInstructionsPrompt(outputs),
|
|
406
|
+
"Give two to four practical options that fit the allowed scope.",
|
|
407
|
+
"For each option return a stable lowercase id and a short title. Add a plain gist and full solution, explain the reason and trade-offs, and list the parts.",
|
|
408
|
+
"Favor a few reusable parts with clear owners and use interfaces that already exist.",
|
|
409
|
+
"Reject one-off machinery and infrastructure that the task does not need.",
|
|
410
|
+
"If the input includes an earlier plan, keep it as an option or explain why the new evidence rules it out.",
|
|
411
|
+
"Do not change files.",
|
|
370
412
|
`Problem frame: ${JSON.stringify(outputs.frame)}`,
|
|
371
413
|
`Previous plan: ${JSON.stringify((input as AutoplanInput).previousPlan ?? null)}`,
|
|
372
414
|
].join("\n"),
|
|
@@ -378,10 +420,11 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
378
420
|
statusDetail: "describing the ideal end state",
|
|
379
421
|
prompt: ({ outputs, input }) =>
|
|
380
422
|
[
|
|
381
|
-
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
"
|
|
423
|
+
originalInstructionsPrompt(outputs),
|
|
424
|
+
"Describe the best possible end state separately from the practical options.",
|
|
425
|
+
"It may match one option or go beyond the current scope.",
|
|
426
|
+
"List each dependency we do not control. Do not assume that it can change.",
|
|
427
|
+
"State what this end state would improve beyond the practical options.",
|
|
385
428
|
`Problem frame: ${JSON.stringify(outputs.frame)}`,
|
|
386
429
|
`Candidates: ${JSON.stringify(outputs.propose)}`,
|
|
387
430
|
`New evidence: ${JSON.stringify((input as AutoplanInput).newEvidence ?? null)}`,
|
|
@@ -393,14 +436,15 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
393
436
|
statusDetail: "choosing the practical solution",
|
|
394
437
|
prompt: ({ outputs }) =>
|
|
395
438
|
[
|
|
396
|
-
|
|
397
|
-
"
|
|
398
|
-
"
|
|
399
|
-
"
|
|
400
|
-
"
|
|
401
|
-
"
|
|
402
|
-
"
|
|
403
|
-
"
|
|
439
|
+
originalInstructionsPrompt(outputs),
|
|
440
|
+
"Select one option. Do not ask the user to choose.",
|
|
441
|
+
"Select the ideal only when it fits the allowed scope and is ready for production. Its value must justify the added complexity.",
|
|
442
|
+
"Otherwise select the best option we can build now that still moves toward the ideal.",
|
|
443
|
+
"Work outside our control does not by itself make the plan blocked.",
|
|
444
|
+
"Do not require changes to an upstream project or an unrelated repository. Do not require a new service or resource without approval.",
|
|
445
|
+
"When two options solve the problem equally well, choose the simpler one.",
|
|
446
|
+
"Give one specific rejection reason for every option you do not select, including the ideal.",
|
|
447
|
+
"Return blocked only if no option inside the allowed scope can meet the success criteria.",
|
|
404
448
|
`Frame: ${JSON.stringify(outputs.frame)}`,
|
|
405
449
|
`Candidates: ${JSON.stringify(outputs.propose)}`,
|
|
406
450
|
`Ideal candidate id: ideal`,
|
|
@@ -414,12 +458,13 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
414
458
|
statusDetail: "writing the implementation plan",
|
|
415
459
|
prompt: ({ outputs, input }) =>
|
|
416
460
|
[
|
|
417
|
-
|
|
418
|
-
"
|
|
419
|
-
"
|
|
420
|
-
"
|
|
421
|
-
"
|
|
422
|
-
"
|
|
461
|
+
originalInstructionsPrompt(outputs),
|
|
462
|
+
"Turn the selected option into a plan that another engineer can implement.",
|
|
463
|
+
"Keep every step inside the allowed scope and authority.",
|
|
464
|
+
"For each step name the location and exact change before stating the check that proves it works.",
|
|
465
|
+
"Describe contract changes and compatibility boundaries before listing tests. Include rollout, migration, and failure handling only where they apply.",
|
|
466
|
+
"Correct the earlier plan when the new evidence proves it wrong.",
|
|
467
|
+
"Do not change files.",
|
|
423
468
|
`Frame: ${JSON.stringify(outputs.frame)}`,
|
|
424
469
|
`Selection: ${JSON.stringify(outputs.choose)}`,
|
|
425
470
|
`Candidates: ${JSON.stringify(outputs.propose)}`,
|
|
@@ -434,6 +479,7 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
434
479
|
const selection = outputs.choose as AutoplanSelection;
|
|
435
480
|
return {
|
|
436
481
|
status: "blocked",
|
|
482
|
+
originalUserInstructions: originalUserInstructions(outputs),
|
|
437
483
|
frame: outputs.frame,
|
|
438
484
|
proposal: outputs.propose as AutoplanProposal,
|
|
439
485
|
ideal: outputs.ideal as AutoplanIdeal,
|
|
@@ -451,6 +497,7 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
451
497
|
request.previousPlan === undefined ? undefined : digest(request.previousPlan);
|
|
452
498
|
return {
|
|
453
499
|
status: "ready",
|
|
500
|
+
originalUserInstructions: originalUserInstructions(outputs),
|
|
454
501
|
frame: outputs.frame,
|
|
455
502
|
proposal: outputs.propose as AutoplanProposal,
|
|
456
503
|
ideal: outputs.ideal as AutoplanIdeal,
|
|
@@ -465,6 +512,7 @@ export const autoplanWorkflow = defineWorkflow({
|
|
|
465
512
|
}),
|
|
466
513
|
},
|
|
467
514
|
edges: [
|
|
515
|
+
{ from: "captureIntent", to: "frame" },
|
|
468
516
|
{ from: "frame", to: "propose" },
|
|
469
517
|
{ from: "propose", to: "ideal" },
|
|
470
518
|
{ from: "ideal", to: "choose" },
|
package/src/builtins/catalog.ts
CHANGED
|
@@ -8,12 +8,12 @@ import planApprovalWorkflow from "./plan-approval.workflow.js";
|
|
|
8
8
|
import sanityCheckWorkflow from "./sanity-check.workflow.js";
|
|
9
9
|
|
|
10
10
|
export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
|
|
11
|
-
{ id: "plain-summary", revision: "
|
|
12
|
-
{ id: "autoplan", revision: "
|
|
11
|
+
{ id: "plain-summary", revision: "3", definition: plainSummaryWorkflow },
|
|
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
|
-
{ id: "sanity-check", revision: "
|
|
16
|
+
{ id: "sanity-check", revision: "6", definition: sanityCheckWorkflow },
|
|
17
17
|
{
|
|
18
18
|
id: "monitor",
|
|
19
19
|
revision: "11",
|