agentweaver 0.1.16 → 0.1.18
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 +148 -27
- package/dist/artifacts.js +114 -3
- package/dist/doctor/checks/executors.js +2 -2
- package/dist/flow-state.js +138 -1
- package/dist/index.js +421 -82
- package/dist/interactive/controller.js +305 -36
- package/dist/interactive/ink/index.js +24 -3
- package/dist/interactive/state.js +1 -0
- package/dist/interactive/tree.js +2 -2
- package/dist/interactive/web/index.js +179 -0
- package/dist/interactive/web/protocol.js +154 -0
- package/dist/interactive/web/server.js +575 -0
- package/dist/interactive/web/static/app.js +709 -0
- package/dist/interactive/web/static/index.html +77 -0
- package/dist/interactive/web/static/styles.css +2 -0
- package/dist/interactive/web/static/styles.input.css +469 -0
- package/dist/pipeline/auto-flow.js +9 -6
- package/dist/pipeline/context.js +6 -5
- package/dist/pipeline/declarative-flows.js +39 -20
- package/dist/pipeline/flow-catalog.js +40 -14
- package/dist/pipeline/flow-specs/auto-common-guided.json +313 -0
- package/dist/pipeline/flow-specs/auto-common.json +4 -1
- package/dist/pipeline/flow-specs/auto-golang.json +27 -1
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +15 -1
- package/dist/pipeline/flow-specs/design-review.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +3 -1
- package/dist/pipeline/flow-specs/plan.json +8 -2
- package/dist/pipeline/flow-specs/playbook-init.json +199 -0
- package/dist/pipeline/flow-specs/review/review-fix.json +3 -1
- package/dist/pipeline/flow-specs/review/review-loop.json +4 -0
- package/dist/pipeline/flow-specs/review/review.json +2 -0
- package/dist/pipeline/launch-profile-config.js +30 -18
- package/dist/pipeline/node-contract.js +1 -0
- package/dist/pipeline/node-registry.js +119 -5
- package/dist/pipeline/nodes/flow-run-node.js +200 -173
- package/dist/pipeline/nodes/llm-prompt-node.js +15 -33
- package/dist/pipeline/nodes/playbook-ensure-node.js +115 -0
- package/dist/pipeline/nodes/playbook-inventory-node.js +51 -0
- package/dist/pipeline/nodes/playbook-questions-form-node.js +166 -0
- package/dist/pipeline/nodes/playbook-write-node.js +243 -0
- package/dist/pipeline/nodes/project-guidance-node.js +69 -0
- package/dist/pipeline/plugin-loader.js +389 -0
- package/dist/pipeline/plugin-types.js +1 -0
- package/dist/pipeline/prompt-registry.js +4 -1
- package/dist/pipeline/prompt-runtime.js +6 -2
- package/dist/pipeline/registry.js +71 -4
- package/dist/pipeline/spec-compiler.js +1 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-types.js +19 -0
- package/dist/pipeline/spec-validator.js +6 -0
- package/dist/pipeline/value-resolver.js +41 -2
- package/dist/playbook/practice-candidates.js +12 -0
- package/dist/playbook/repo-inventory.js +208 -0
- package/dist/plugin-sdk.js +1 -0
- package/dist/prompts.js +31 -0
- package/dist/runtime/artifact-registry.js +3 -0
- package/dist/runtime/execution-routing.js +25 -19
- package/dist/runtime/interactive-execution-routing.js +66 -57
- package/dist/runtime/playbook.js +485 -0
- package/dist/runtime/project-guidance.js +339 -0
- package/dist/structured-artifact-schema-registry.js +8 -0
- package/dist/structured-artifact-schemas.json +235 -0
- package/dist/structured-artifacts.js +7 -1
- package/docs/declarative-workflows.md +565 -0
- package/docs/example/.flows/examples/claude-example.json +50 -0
- package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/examples/.flows/claude-example.json +50 -0
- package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/features.md +77 -0
- package/docs/playbook.md +327 -0
- package/docs/plugin-sdk.md +731 -0
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -1,45 +1,64 @@
|
|
|
1
1
|
# AgentWeaver
|
|
2
2
|
|
|
3
|
-
`AgentWeaver` is a TypeScript/Node.js CLI for
|
|
3
|
+
`AgentWeaver` is a TypeScript/Node.js CLI for engineering durable workflows around coding agents.
|
|
4
4
|
|
|
5
|
-
It is built
|
|
5
|
+
It is built for teams that want agent work to behave less like one-off prompting and more like an inspectable engineering system: explicit workflows, durable artifacts, repeatable review gates, resumable execution, and repository-local guidance that evolves with the codebase.
|
|
6
6
|
|
|
7
7
|
Typical usage looks like:
|
|
8
8
|
|
|
9
9
|
`plan -> implement -> run-go-linter-loop -> run-go-tests-loop -> review -> review-fix`
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Planning-heavy work can use:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`plan -> design-review -> implement -> review-loop`
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The important part is not the exact chain. The point is that AgentWeaver lets you model, operate, and evolve the harness around the agent.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- Fetches GitLab merge request diff and review data into reusable artifacts
|
|
19
|
-
- Runs Codex-, OpenCode-, and process-backed stages through a common pipeline runtime
|
|
20
|
-
- Persists artifacts and compact flow execution state under the current project scope
|
|
21
|
-
- Supports both operator-driven work in a TUI and end-to-end automation flows
|
|
22
|
-
- Resumes interrupted declarative flows when required artifacts and launch profile still match
|
|
17
|
+
## Key Features
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
See [docs/features.md](docs/features.md) for the expanded feature overview.
|
|
20
|
+
|
|
21
|
+
- **Declarative agent workflows**: flows are JSON specs with phases, steps, prompt bindings, params, expectations, and post-step actions. Workflow design stays declarative while runtime behavior lives in typed nodes and executors.
|
|
22
|
+
- **Repository-local project playbook**: stable project conventions live under `.agentweaver/playbook/` as versioned rules, examples, and templates. Guided flows select relevant guidance before planning, implementation, review, and repair so repeated agent runs inherit the same project knowledge.
|
|
23
|
+
- **Artifact-first execution**: each stage produces structured JSON and human-readable markdown artifacts on disk. Artifacts are the contract between stages, which makes runs inspectable, reviewable, and restartable.
|
|
24
|
+
- **Planning and design-review gates**: planning flows produce design, implementation plan, and QA plan artifacts. `design-review` critiques those artifacts before coding starts, and `auto-common` can iterate through `plan-revise` before implementation.
|
|
25
|
+
- **Review and repair loops**: review flows produce structured findings with severities. Repair flows can select blockers and critical findings, apply targeted fixes, and run follow-up checks.
|
|
26
|
+
- **Resumable automation**: long-running flows persist compact execution state, support resume/continue/restart semantics, and can restart from selected phases when the artifacts and launch profile are compatible.
|
|
27
|
+
- **Multiple execution backends**: Codex, OpenCode, shell/process checks, Jira, GitLab, Git commit, and Telegram notification integrations run through a common executor model.
|
|
28
|
+
- **Interactive TUI and direct CLI**: the same workflow model works in an operator-driven terminal UI, direct CLI commands, and non-interactive automation.
|
|
29
|
+
- **Custom flows**: built-in flows can be extended with global or project-local flow specs without changing AgentWeaver source code.
|
|
30
|
+
- **Plugin SDK**: local plugins can add public-SDK-compatible nodes and executors, with manifest validation, version checks, and documented entrypoint rules.
|
|
31
|
+
- **Operational diagnostics**: `doctor` checks system readiness, executor configuration, flow specs, node versions, and runtime environment shape before workflows fail mid-run.
|
|
32
|
+
|
|
33
|
+
## Why Harness Engineering
|
|
25
34
|
|
|
26
35
|
AgentWeaver is not positioned as a thin wrapper around one agent call. It is meant for harness engineering:
|
|
27
36
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
37
|
+
- The workflow is explicit instead of hidden in a long prompt.
|
|
38
|
+
- The intermediate decisions are persisted instead of disappearing in chat history.
|
|
39
|
+
- The agent receives project guidance from the repository instead of relying on memory or copy-pasted instructions.
|
|
40
|
+
- Review, repair, checks, and restart behavior are first-class parts of the workflow.
|
|
41
|
+
- The same model works in local CLI use, interactive operation, and automation.
|
|
32
42
|
|
|
33
43
|
In practice, this means you can treat an agent workflow like an engineered system: versioned, inspectable, repeatable, and debuggable.
|
|
34
44
|
|
|
35
45
|
## Core Concepts
|
|
36
46
|
|
|
37
|
-
- `flow spec`: declarative JSON under `src/pipeline/flow-specs
|
|
47
|
+
- `flow spec`: declarative JSON under `src/pipeline/flow-specs/`, global `~/.agentweaver/.flows/`, or project-local `.agentweaver/.flows/`
|
|
38
48
|
- `node`: reusable runtime unit from `src/pipeline/nodes/`
|
|
39
49
|
- `executor`: integration layer for Jira, Codex, OpenCode, GitLab, shell/process execution, Telegram notifications, and related actions
|
|
40
50
|
- `scope`: isolated workspace key for artifacts and flow state; usually based on Jira task, otherwise derived from git context
|
|
41
51
|
- `artifact`: file produced or consumed by flows, used as the stable contract between stages
|
|
42
52
|
- `flow state`: compact persisted execution metadata used for resume/restart in long-running flows such as `auto-golang`
|
|
53
|
+
- `project playbook`: local `.agentweaver/playbook/` directory with `manifest.yaml`, practices, examples, and templates; the format is described in [docs/playbook.md](docs/playbook.md)
|
|
54
|
+
|
|
55
|
+
## Launch Semantics
|
|
56
|
+
|
|
57
|
+
- `resume` only resumes a genuinely interrupted run and uses the saved execution state without rebuilding already completed steps
|
|
58
|
+
- `continue` is intended for completed iterative cycles and starts the next iteration from the latest valid artifacts without deleting historical artifacts
|
|
59
|
+
- `restart` is treated as a new run: the current active attempt is archived under `.agentweaver/scopes/<scope>/.artifacts/restart-archives/attempt-XXXX`, then a new active attempt is created
|
|
60
|
+
- For ambiguous launches, the operator must choose the action explicitly: by confirmation in interactive mode, or with `--resume`, `--continue`, or `--restart` in non-interactive mode
|
|
61
|
+
- This contract applies to `auto-common`, `auto-simple`, `auto-golang`, `instant-task`, `review-loop`, `run-go-linter-loop`, and `run-go-tests-loop`
|
|
43
62
|
|
|
44
63
|
## Declarative Workflow Model
|
|
45
64
|
|
|
@@ -54,6 +73,8 @@ The center of the system is the declarative flow spec:
|
|
|
54
73
|
|
|
55
74
|
This keeps workflow design in JSON while keeping implementation details in typed runtime code.
|
|
56
75
|
|
|
76
|
+
The full flow-spec reference now lives in [docs/declarative-workflows.md](docs/declarative-workflows.md).
|
|
77
|
+
|
|
57
78
|
## Repository Layout
|
|
58
79
|
|
|
59
80
|
- `src/index.ts` — CLI entrypoint, interactive mode bootstrap, and top-level orchestration
|
|
@@ -101,6 +122,30 @@ There are also built-in nested/helper flows that are loaded declaratively but ar
|
|
|
101
122
|
- `opencode` CLI if you use OpenCode-backed stages
|
|
102
123
|
- access to Jira and/or GitLab when the selected flow needs them
|
|
103
124
|
|
|
125
|
+
## Web UI
|
|
126
|
+
|
|
127
|
+
The `agentweaver web [--no-open] [--host <host>|--listen-all] [<jira-browse-url|jira-issue-key>]` command starts interactive mode through the Web UI. By default, the server binds to `127.0.0.1`, asks the operating system for a random port, and prints the final address as `AgentWeaver Web UI: http://127.0.0.1:<port>/`.
|
|
128
|
+
|
|
129
|
+
To open the Web UI from another machine on a trusted network, configure Web UI credentials first:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
export AGENTWEAVER_WEB_USERNAME=operator
|
|
133
|
+
export AGENTWEAVER_WEB_PASSWORD='choose-a-strong-password'
|
|
134
|
+
agentweaver web --listen-all --no-open
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
External binding requires both `AGENTWEAVER_WEB_USERNAME` and `AGENTWEAVER_WEB_PASSWORD`. This applies to `agentweaver web --listen-all`, `agentweaver web --host 0.0.0.0`, `agentweaver web --host ::`, explicit non-loopback IP addresses such as `192.168.1.10` or `2001:db8::1`, and any hostname other than `localhost`. In this mode, the server listens on the requested interface; connect to the IP address or hostname of the machine running AgentWeaver and the assigned port.
|
|
138
|
+
|
|
139
|
+
The default localhost bindings, including `127.0.0.1`, `::1`, and `localhost`, remain no-auth by default. If Web UI credentials are configured, the same Basic auth check also protects localhost Web UI requests.
|
|
140
|
+
|
|
141
|
+
Web UI authentication uses HTTP Basic auth. Over plain HTTP, use it only on trusted networks because credentials are not encrypted in transit. For untrusted networks, put AgentWeaver behind TLS termination or an equivalent reverse proxy.
|
|
142
|
+
|
|
143
|
+
By default, AgentWeaver tries to open the browser after the server starts successfully and the URL is printed. For CI, tests, and manual smoke checks, use `agentweaver web --no-open` or the `AGENTWEAVER_WEB_NO_OPEN=1` environment variable; the `--no-open` flag is supported only after the `web` command.
|
|
144
|
+
|
|
145
|
+
The Web UI serves the operator console from the same local process, including `/`, `/static/app.js`, and `/static/styles.css`. Live browser interaction uses WebSocket on `/__agentweaver/ws`. Bounded checks can use `GET /__agentweaver/health`, and shutdown is available through `POST /__agentweaver/exit` or `SIGINT`/`SIGTERM`.
|
|
146
|
+
|
|
147
|
+
Web UI state is process-local: it exists only while the AgentWeaver process is running and is not shared with other AgentWeaver processes. The Web UI is intended to match the interactive operator workflow for flow selection, launch confirmation, routing and user-input forms, progress and logs, and interrupt handling.
|
|
148
|
+
|
|
104
149
|
## Installation
|
|
105
150
|
|
|
106
151
|
Local development:
|
|
@@ -118,6 +163,34 @@ node dist/index.js --help
|
|
|
118
163
|
|
|
119
164
|
Global install after publishing:
|
|
120
165
|
|
|
166
|
+
## Plugin SDK
|
|
167
|
+
|
|
168
|
+
AgentWeaver supports local plugins and custom declarative flows from both global and project-local `.agentweaver` directories.
|
|
169
|
+
|
|
170
|
+
Plugin authors must use only the public SDK subpath: `agentweaver/plugin-sdk`.
|
|
171
|
+
The package root `agentweaver`, internal paths such as `agentweaver/dist/*` and `agentweaver/src/*`, and repository-relative source imports are not part of the supported SDK contract.
|
|
172
|
+
|
|
173
|
+
Supported plugin manifest locations are:
|
|
174
|
+
|
|
175
|
+
- `~/.agentweaver/.plugins/<plugin-id>/plugin.json`
|
|
176
|
+
- `.agentweaver/.plugins/<plugin-id>/plugin.json`
|
|
177
|
+
|
|
178
|
+
The plugin directory name and manifest `id` must match exactly.
|
|
179
|
+
|
|
180
|
+
Use the dedicated guide at [docs/plugin-sdk.md](docs/plugin-sdk.md) for:
|
|
181
|
+
|
|
182
|
+
- the executor versus node architecture
|
|
183
|
+
- manifest and entrypoint rules
|
|
184
|
+
- optional routing metadata for plugin LLM executors
|
|
185
|
+
- runtime context APIs available to plugin code
|
|
186
|
+
- global and project-local flow wiring under `~/.agentweaver/.flows/` and `.agentweaver/.flows/`
|
|
187
|
+
- compatibility, testing, troubleshooting, and a complete end-to-end walkthrough
|
|
188
|
+
|
|
189
|
+
Repository reference examples live under `docs/examples/`, for example:
|
|
190
|
+
|
|
191
|
+
- `docs/examples/.plugins/claude-example-plugin/`
|
|
192
|
+
- `docs/examples/.flows/claude-example.json`
|
|
193
|
+
|
|
121
194
|
```bash
|
|
122
195
|
npm install -g agentweaver
|
|
123
196
|
agentweaver --help
|
|
@@ -183,7 +256,7 @@ OPENCODE_MODEL=minimax-coding-plan/MiniMax-M2.7
|
|
|
183
256
|
|
|
184
257
|
The full-screen TUI is not a cosmetic wrapper. It is the operator console for the harness:
|
|
185
258
|
|
|
186
|
-
- browse built-in and project-local workflows
|
|
259
|
+
- browse built-in, global, and project-local workflows
|
|
187
260
|
- launch flows in the current scope
|
|
188
261
|
- inspect progress by phase and step
|
|
189
262
|
- follow activity, prompts, summaries, and statuses
|
|
@@ -259,7 +332,7 @@ Notes:
|
|
|
259
332
|
- `--verbose` streams child process stdout/stderr in direct CLI mode
|
|
260
333
|
- `--prompt <text>` appends extra instructions to the prompt
|
|
261
334
|
- `--scope <name>` is supported by scope-flexible flows such as `implement`, `review`, `review-fix`, `review-loop`, `run-go-tests-loop`, `run-go-linter-loop`, `gitlab-review`, and `gitlab-diff-review`
|
|
262
|
-
- `--md-lang <en|ru>`
|
|
335
|
+
- `--md-lang <en|ru>` applies only to generated workflow markdown artifacts, not repository source files or committed documentation
|
|
263
336
|
- `--force` only affects interactive mode: it skips loading cached summary-pane content on startup so Jira-backed flows that regenerate summary artifacts can repopulate it during the run
|
|
264
337
|
- Jira-backed flows ask for Jira input interactively when it is omitted
|
|
265
338
|
- `task-describe` can also work from manual task description input without Jira
|
|
@@ -300,6 +373,7 @@ Artifacts and flow state are stored under the current project scope. In practice
|
|
|
300
373
|
- Jira-backed runs usually use the Jira issue key as scope
|
|
301
374
|
- non-Jira runs can fall back to a git-derived scope
|
|
302
375
|
- `--scope <name>` lets you override the default for supported commands
|
|
376
|
+
- interactive and web sessions automatically switch the branch-derived scope after the git branch changes, unless the session was started with an explicit Jira argument or `--scope`
|
|
303
377
|
|
|
304
378
|
The runtime uses artifacts as the contract between stages, including markdown outputs and structured JSON files validated against schemas.
|
|
305
379
|
|
|
@@ -332,27 +406,31 @@ Current layout:
|
|
|
332
406
|
Flow discovery behavior:
|
|
333
407
|
|
|
334
408
|
- built-in flows are loaded from `src/pipeline/flow-specs/`
|
|
335
|
-
-
|
|
336
|
-
-
|
|
337
|
-
-
|
|
338
|
-
-
|
|
409
|
+
- global custom flows are loaded from `~/.agentweaver/.flows/`
|
|
410
|
+
- project-local custom flows are loaded from `.agentweaver/.flows/`
|
|
411
|
+
- all discovered flow specs are validated at load time
|
|
412
|
+
- duplicate flow ids fail fast across built-in, global, and project-local sources
|
|
413
|
+
- custom flows are shown separately in the UI as global and project-local groups
|
|
339
414
|
|
|
340
|
-
##
|
|
415
|
+
## Custom Flows
|
|
341
416
|
|
|
342
|
-
You can add
|
|
417
|
+
You can add custom flow specs under either:
|
|
343
418
|
|
|
344
419
|
```bash
|
|
420
|
+
~/.agentweaver/.flows/**/*.json
|
|
345
421
|
.agentweaver/.flows/**/*.json
|
|
346
422
|
```
|
|
347
423
|
|
|
348
|
-
|
|
424
|
+
Custom flows:
|
|
349
425
|
|
|
350
426
|
- are discovered recursively
|
|
351
427
|
- get their flow id from the relative path without `.json`
|
|
352
428
|
- share the same validator and runtime as built-in flows
|
|
353
429
|
- cannot conflict with an existing built-in or other discovered flow id
|
|
354
430
|
|
|
355
|
-
|
|
431
|
+
Use the global directory for reusable personal flows and plugins across repositories, and the project-local directory for repo-specific wiring.
|
|
432
|
+
|
|
433
|
+
Nested `flow-run` steps can reference built-in, global, or project-local specs by file name, as long as the name resolves unambiguously.
|
|
356
434
|
|
|
357
435
|
## Development
|
|
358
436
|
|
|
@@ -387,7 +465,50 @@ Recommended smoke checks:
|
|
|
387
465
|
node dist/index.js --help
|
|
388
466
|
node dist/index.js auto-golang --help-phases
|
|
389
467
|
node dist/index.js auto-common --help-phases
|
|
468
|
+
node dist/index.js auto-common-guided --help-phases
|
|
390
469
|
node dist/index.js plan --dry DEMO-1234
|
|
391
470
|
node dist/index.js implement --dry DEMO-1234
|
|
392
471
|
node dist/index.js review --dry DEMO-1234
|
|
393
472
|
```
|
|
473
|
+
|
|
474
|
+
## Guided Project Guidance
|
|
475
|
+
|
|
476
|
+
The project playbook is AgentWeaver's way to turn project-specific conventions into durable agent context. Instead of repeating the same instructions in every prompt, a repository can keep stable rules, examples, and templates under `.agentweaver/playbook/`. Guided flows validate that material, select the parts relevant to the current task and phase, and pass compact guidance into the model before planning, implementation, review, and repair.
|
|
477
|
+
|
|
478
|
+
Typical playbook content includes:
|
|
479
|
+
|
|
480
|
+
- engineering rules such as required test locations, documentation language, or runtime validation boundaries
|
|
481
|
+
- examples that should be opened only when relevant, instead of pasted into every prompt
|
|
482
|
+
- templates for recurring artifact shapes or implementation notes
|
|
483
|
+
- repository context that should remain visible across tasks without overriding task-specific inputs
|
|
484
|
+
|
|
485
|
+
The guided flow is `auto-common-guided`. It first runs the same Jira fetch and task normalization steps as `auto-common`, then validates `.agentweaver/playbook/manifest.yaml` and generates compact project guidance before the `plan`, `design-review`, `implement`, `review`, and `repair/review-fix` phases. JSON artifacts remain English and machine-readable; workflow markdown artifacts are generated in the workflow-selected language. The markdown language setting does not apply to repository source files, committed documentation, or playbook rules.
|
|
486
|
+
|
|
487
|
+
The guidance is intentionally phase-aware. A rule can apply only to `plan`, `implement`, `review`, or another supported phase; it can also target languages, frameworks, glob patterns, and keywords. AgentWeaver writes both a structured `project-guidance/v1` JSON artifact and a derivative markdown file, then passes their paths into the phase prompt as supplemental project-local context.
|
|
488
|
+
|
|
489
|
+
Initialize or refresh the playbook with:
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
agentweaver playbook-init
|
|
493
|
+
agentweaver playbook-init --accept-playbook-draft
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Use the guided flow with:
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
agentweaver auto-common-guided --help-phases
|
|
500
|
+
agentweaver auto-common-guided --accept-playbook-draft DEMO-1234
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
The workflow does not read old `playbook.json` or `playbook.md` files as fallbacks. In non-interactive runs, a missing manifest stops the workflow before planning and reports the required action: run `agentweaver playbook-init --accept-playbook-draft` first, or rerun `agentweaver auto-common-guided --accept-playbook-draft <jira>`. The `--accept-playbook-draft` flag explicitly accepts the generated playbook without interactive review and allows AgentWeaver to write the manifest-based layout. An invalid manifest stops the guided phase before the LLM prompt.
|
|
504
|
+
|
|
505
|
+
To inspect whether playbook guidance participated in a run, check the generated artifacts:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
find .agentweaver/scopes -name 'project-guidance-*'
|
|
509
|
+
rg -n "Project Guidance|practice\\." .agentweaver/scopes
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Keep `.agentweaver/playbook/` in Git even when other AgentWeaver runtime state is ignored. The playbook format and maintenance workflow are documented in [docs/playbook.md](docs/playbook.md).
|
|
513
|
+
|
|
514
|
+
Current limitations: skills integration is not available yet; the playbook generator must rely on repository evidence and clarification answers; guided prompts receive compact context and open full examples only when they are directly relevant to the current phase.
|
package/dist/artifacts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { TaskRunnerError } from "./errors.js";
|
|
@@ -163,6 +163,20 @@ export function taskContextFile(taskKey, iteration) {
|
|
|
163
163
|
export function taskContextJsonFile(taskKey, iteration) {
|
|
164
164
|
return versionedJsonArtifactFile(taskKey, "task-context", iteration);
|
|
165
165
|
}
|
|
166
|
+
export function projectGuidanceArtifactStem(phase) {
|
|
167
|
+
switch (phase) {
|
|
168
|
+
case "repair/review-fix":
|
|
169
|
+
return "project-guidance-repair-review-fix";
|
|
170
|
+
default:
|
|
171
|
+
return `project-guidance-${phase}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function projectGuidanceFile(taskKey, phase, iteration) {
|
|
175
|
+
return versionedMarkdownArtifactFile(taskKey, projectGuidanceArtifactStem(phase), iteration);
|
|
176
|
+
}
|
|
177
|
+
export function projectGuidanceJsonFile(taskKey, phase, iteration) {
|
|
178
|
+
return versionedJsonArtifactFile(taskKey, projectGuidanceArtifactStem(phase), iteration);
|
|
179
|
+
}
|
|
166
180
|
export function taskDescribeInputJsonFile(taskKey) {
|
|
167
181
|
return taskArtifactsFile(taskKey, `task-describe-input-${taskKey}.json`);
|
|
168
182
|
}
|
|
@@ -175,6 +189,33 @@ export function gitStatusJsonFile(taskKey) {
|
|
|
175
189
|
export function gitDiffFile(taskKey) {
|
|
176
190
|
return taskWorkspaceFile(taskKey, `git-diff-${taskKey}.txt`);
|
|
177
191
|
}
|
|
192
|
+
export function repoInventoryFile(taskKey) {
|
|
193
|
+
return taskWorkspaceFile(taskKey, `repo-inventory-${taskKey}.md`);
|
|
194
|
+
}
|
|
195
|
+
export function repoInventoryJsonFile(taskKey) {
|
|
196
|
+
return taskArtifactsFile(taskKey, `repo-inventory-${taskKey}.json`);
|
|
197
|
+
}
|
|
198
|
+
export function practiceCandidatesFile(taskKey) {
|
|
199
|
+
return taskWorkspaceFile(taskKey, `practice-candidates-${taskKey}.md`);
|
|
200
|
+
}
|
|
201
|
+
export function practiceCandidatesJsonFile(taskKey) {
|
|
202
|
+
return taskArtifactsFile(taskKey, `practice-candidates-${taskKey}.json`);
|
|
203
|
+
}
|
|
204
|
+
export function playbookQuestionsJsonFile(taskKey) {
|
|
205
|
+
return taskArtifactsFile(taskKey, `playbook-questions-${taskKey}.json`);
|
|
206
|
+
}
|
|
207
|
+
export function playbookAnswersJsonFile(taskKey) {
|
|
208
|
+
return taskArtifactsFile(taskKey, `playbook-answers-${taskKey}.json`);
|
|
209
|
+
}
|
|
210
|
+
export function playbookDraftFile(taskKey) {
|
|
211
|
+
return taskWorkspaceFile(taskKey, `playbook-draft-${taskKey}.md`);
|
|
212
|
+
}
|
|
213
|
+
export function playbookDraftJsonFile(taskKey) {
|
|
214
|
+
return taskArtifactsFile(taskKey, `playbook-draft-${taskKey}.json`);
|
|
215
|
+
}
|
|
216
|
+
export function playbookWriteResultJsonFile(taskKey) {
|
|
217
|
+
return taskArtifactsFile(taskKey, `playbook-write-result-${taskKey}.json`);
|
|
218
|
+
}
|
|
178
219
|
export function gitCommitMessageJsonFile(taskKey) {
|
|
179
220
|
return taskArtifactsFile(taskKey, `git-commit-message-${taskKey}.json`);
|
|
180
221
|
}
|
|
@@ -214,8 +255,78 @@ export function gitlabDiffReviewInputJsonFile(taskKey) {
|
|
|
214
255
|
export function flowStateFile(scopeKey, flowId) {
|
|
215
256
|
return scopeArtifactsFile(scopeKey, `.agentweaver-flow-state-${encodeURIComponent(flowId)}.json`);
|
|
216
257
|
}
|
|
217
|
-
export function
|
|
218
|
-
return
|
|
258
|
+
export function restartArchivesDir(scopeKey) {
|
|
259
|
+
return scopeArtifactsFile(scopeKey, "restart-archives");
|
|
260
|
+
}
|
|
261
|
+
function nextRestartArchiveName(scopeKey) {
|
|
262
|
+
const archiveRoot = restartArchivesDir(scopeKey);
|
|
263
|
+
if (!existsSync(archiveRoot)) {
|
|
264
|
+
return "attempt-0001";
|
|
265
|
+
}
|
|
266
|
+
const attemptNumbers = readdirSync(archiveRoot, { withFileTypes: true })
|
|
267
|
+
.filter((entry) => entry.isDirectory())
|
|
268
|
+
.map((entry) => /^attempt-(\d{4})$/.exec(entry.name)?.[1] ?? null)
|
|
269
|
+
.filter((value) => value !== null)
|
|
270
|
+
.map((value) => Number.parseInt(value, 10));
|
|
271
|
+
const nextNumber = (attemptNumbers.length === 0 ? 0 : Math.max(...attemptNumbers)) + 1;
|
|
272
|
+
return `attempt-${String(nextNumber).padStart(4, "0")}`;
|
|
273
|
+
}
|
|
274
|
+
export function archiveActiveAttempt(scopeKey) {
|
|
275
|
+
const workspaceDir = scopeWorkspaceDir(scopeKey);
|
|
276
|
+
if (!existsSync(workspaceDir)) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const workspaceEntries = readdirSync(workspaceDir, { withFileTypes: true })
|
|
280
|
+
.filter((entry) => entry.name !== ".artifacts");
|
|
281
|
+
const artifactEntries = readdirSync(scopeArtifactsDir(scopeKey), { withFileTypes: true })
|
|
282
|
+
.filter((entry) => entry.name !== "restart-archives");
|
|
283
|
+
if (workspaceEntries.length === 0 && artifactEntries.length === 0) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const archiveRoot = restartArchivesDir(scopeKey);
|
|
287
|
+
mkdirSync(archiveRoot, { recursive: true });
|
|
288
|
+
const archiveDir = path.join(archiveRoot, nextRestartArchiveName(scopeKey));
|
|
289
|
+
const workspaceArchiveDir = path.join(archiveDir, "workspace");
|
|
290
|
+
const artifactsArchiveDir = path.join(archiveDir, "artifacts");
|
|
291
|
+
mkdirSync(workspaceArchiveDir, { recursive: true });
|
|
292
|
+
mkdirSync(artifactsArchiveDir, { recursive: true });
|
|
293
|
+
try {
|
|
294
|
+
for (const entry of workspaceEntries) {
|
|
295
|
+
cpSync(path.join(workspaceDir, entry.name), path.join(workspaceArchiveDir, entry.name), {
|
|
296
|
+
recursive: true,
|
|
297
|
+
errorOnExist: true,
|
|
298
|
+
force: false,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
for (const entry of artifactEntries) {
|
|
302
|
+
cpSync(path.join(scopeArtifactsDir(scopeKey), entry.name), path.join(artifactsArchiveDir, entry.name), {
|
|
303
|
+
recursive: true,
|
|
304
|
+
errorOnExist: true,
|
|
305
|
+
force: false,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
rmSync(archiveDir, { recursive: true, force: true });
|
|
311
|
+
throw new TaskRunnerError(`Failed to archive active attempt for restart: ${error.message}`);
|
|
312
|
+
}
|
|
313
|
+
for (const entry of workspaceEntries) {
|
|
314
|
+
rmSync(path.join(workspaceDir, entry.name), { recursive: true, force: true });
|
|
315
|
+
}
|
|
316
|
+
for (const entry of artifactEntries) {
|
|
317
|
+
rmSync(path.join(scopeArtifactsDir(scopeKey), entry.name), { recursive: true, force: true });
|
|
318
|
+
}
|
|
319
|
+
return archiveDir;
|
|
320
|
+
}
|
|
321
|
+
export function planArtifacts(taskKey, iteration) {
|
|
322
|
+
return [
|
|
323
|
+
designFile(taskKey, iteration),
|
|
324
|
+
designJsonFile(taskKey, iteration),
|
|
325
|
+
planFile(taskKey, iteration),
|
|
326
|
+
planJsonFile(taskKey, iteration),
|
|
327
|
+
qaFile(taskKey, iteration),
|
|
328
|
+
qaJsonFile(taskKey, iteration),
|
|
329
|
+
];
|
|
219
330
|
}
|
|
220
331
|
export function bugAnalyzeArtifacts(taskKey) {
|
|
221
332
|
return [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { DoctorStatus } from "../types.js";
|
|
3
3
|
import { CATEGORY } from "./category.js";
|
|
4
|
-
import {
|
|
4
|
+
import { allowedModelsForExecutor } from "../../pipeline/launch-profile-config.js";
|
|
5
5
|
import { findCmdPath, isExecutable } from "../../runtime/command-resolution.js";
|
|
6
6
|
function getEnvVarName(executorId) {
|
|
7
7
|
return executorId === "codex" ? "CODEX_BIN" : "OPENCODE_BIN";
|
|
@@ -72,7 +72,7 @@ function checkExecutor(executorId) {
|
|
|
72
72
|
if (versionOutput === null) {
|
|
73
73
|
return createResult(executorId, DoctorStatus.Fail, `${executorId} --version check failed`, `${executorId} --version did not produce expected output`, `path: ${resolution.path}, source: ${resolution.source}`, resolution, null);
|
|
74
74
|
}
|
|
75
|
-
const allowedModels =
|
|
75
|
+
const allowedModels = allowedModelsForExecutor(executorId);
|
|
76
76
|
const modelWarnings = [];
|
|
77
77
|
for (const model of allowedModels) {
|
|
78
78
|
const modelResult = spawnSync(resolution.path, ["--model", model, "--version"], { encoding: "utf8", stdio: "pipe" });
|
package/dist/flow-state.js
CHANGED
|
@@ -4,7 +4,25 @@ import { ensureScopeWorkspaceDir, flowStateFile } from "./artifacts.js";
|
|
|
4
4
|
import { TaskRunnerError } from "./errors.js";
|
|
5
5
|
import { isFlowRunResumeEnvelope } from "./pipeline/flow-run-resume.js";
|
|
6
6
|
import { resolveStoredExecutionRoutingSnapshot, singleLaunchProfileExecutionRouting } from "./runtime/execution-routing.js";
|
|
7
|
-
const FLOW_STATE_SCHEMA_VERSION =
|
|
7
|
+
const FLOW_STATE_SCHEMA_VERSION = 3;
|
|
8
|
+
const CONTINUABLE_FLOW_KINDS = new Set([
|
|
9
|
+
"design-review-loop-flow",
|
|
10
|
+
"review-loop-flow",
|
|
11
|
+
"review-project-loop-flow",
|
|
12
|
+
"run-go-linter-loop-flow",
|
|
13
|
+
"run-go-tests-loop-flow",
|
|
14
|
+
]);
|
|
15
|
+
const CONTINUABLE_PARENT_FLOW_IDS = new Set([
|
|
16
|
+
"auto-common",
|
|
17
|
+
"auto-simple",
|
|
18
|
+
"auto-golang",
|
|
19
|
+
"instant-task",
|
|
20
|
+
]);
|
|
21
|
+
const CONTINUABLE_DIRECT_FLOW_IDS = new Set([
|
|
22
|
+
"review-loop",
|
|
23
|
+
"run-go-linter-loop",
|
|
24
|
+
"run-go-tests-loop",
|
|
25
|
+
]);
|
|
8
26
|
function nowIso8601() {
|
|
9
27
|
return new Date().toISOString();
|
|
10
28
|
}
|
|
@@ -45,6 +63,7 @@ export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, la
|
|
|
45
63
|
ensurePublicationRunId(executionState);
|
|
46
64
|
const effectiveExecutionRouting = executionRouting ?? (launchProfile ? singleLaunchProfileExecutionRouting(launchProfile) : undefined);
|
|
47
65
|
const effectiveLaunchProfile = launchProfile ?? effectiveExecutionRouting?.defaultRoute;
|
|
66
|
+
const continuation = inferContinuationMetadata(flowId, executionState);
|
|
48
67
|
return {
|
|
49
68
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
50
69
|
flowId,
|
|
@@ -56,6 +75,7 @@ export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, la
|
|
|
56
75
|
...(effectiveLaunchProfile ? { launchProfile: effectiveLaunchProfile } : {}),
|
|
57
76
|
...(effectiveExecutionRouting ? { executionRouting: effectiveExecutionRouting, routingFingerprint: effectiveExecutionRouting.fingerprint } : {}),
|
|
58
77
|
...(selectedRoutingPreset ? { selectedRoutingPreset } : {}),
|
|
78
|
+
continuation,
|
|
59
79
|
executionState: stripExecutionStatePayload(executionState),
|
|
60
80
|
};
|
|
61
81
|
}
|
|
@@ -66,6 +86,43 @@ function upgradeFlowRunStateV1(state) {
|
|
|
66
86
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
67
87
|
...(executionRouting ? { executionRouting, routingFingerprint: executionRouting.fingerprint } : {}),
|
|
68
88
|
...(executionRouting ? { selectedRoutingPreset: { kind: "custom", label: "Legacy launch profile" } } : {}),
|
|
89
|
+
continuation: {
|
|
90
|
+
continueEligible: false,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function upgradeFlowRunStateV2(state) {
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
98
|
+
continuation: {
|
|
99
|
+
continueEligible: false,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function parseTerminationLocation(terminationReason) {
|
|
104
|
+
if (typeof terminationReason !== "string") {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
const match = /^Stopped by ([^:]+):(.+)$/.exec(terminationReason.trim());
|
|
108
|
+
if (!match) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
const stopPhaseId = match[1];
|
|
112
|
+
const stopStepId = match[2];
|
|
113
|
+
return {
|
|
114
|
+
...(stopPhaseId ? { stopPhaseId } : {}),
|
|
115
|
+
...(stopStepId ? { stopStepId } : {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function inferContinuationMetadata(flowId, executionState) {
|
|
119
|
+
const stopLocation = parseTerminationLocation(executionState.terminationReason);
|
|
120
|
+
const continueEligible = CONTINUABLE_FLOW_KINDS.has(executionState.flowKind)
|
|
121
|
+
|| (CONTINUABLE_PARENT_FLOW_IDS.has(flowId) && Boolean(stopLocation.stopPhaseId && stopLocation.stopStepId));
|
|
122
|
+
return {
|
|
123
|
+
continueEligible,
|
|
124
|
+
...(stopLocation.stopPhaseId ? { stopPhaseId: stopLocation.stopPhaseId } : {}),
|
|
125
|
+
...(stopLocation.stopStepId ? { stopStepId: stopLocation.stopStepId } : {}),
|
|
69
126
|
};
|
|
70
127
|
}
|
|
71
128
|
function normalizeFlowRunState(raw, flowId, filePath) {
|
|
@@ -77,6 +134,9 @@ function normalizeFlowRunState(raw, flowId, filePath) {
|
|
|
77
134
|
if (schemaVersion === 1) {
|
|
78
135
|
state = upgradeFlowRunStateV1(raw);
|
|
79
136
|
}
|
|
137
|
+
else if (schemaVersion === 2) {
|
|
138
|
+
state = upgradeFlowRunStateV2(raw);
|
|
139
|
+
}
|
|
80
140
|
else if (schemaVersion === FLOW_STATE_SCHEMA_VERSION) {
|
|
81
141
|
state = raw;
|
|
82
142
|
}
|
|
@@ -97,6 +157,11 @@ function normalizeFlowRunState(raw, flowId, filePath) {
|
|
|
97
157
|
state.executionRouting = executionRouting;
|
|
98
158
|
state.routingFingerprint = executionRouting.fingerprint;
|
|
99
159
|
}
|
|
160
|
+
const inferredContinuation = inferContinuationMetadata(state.flowId, state.executionState);
|
|
161
|
+
state.continuation = {
|
|
162
|
+
...inferredContinuation,
|
|
163
|
+
continueEligible: inferredContinuation.continueEligible && state.continuation?.continueEligible !== false,
|
|
164
|
+
};
|
|
100
165
|
return state;
|
|
101
166
|
}
|
|
102
167
|
export function loadFlowRunState(scopeKey, flowId) {
|
|
@@ -125,6 +190,7 @@ export function saveFlowRunState(state) {
|
|
|
125
190
|
state.executionRouting = singleLaunchProfileExecutionRouting(state.launchProfile);
|
|
126
191
|
state.routingFingerprint = state.executionRouting.fingerprint;
|
|
127
192
|
}
|
|
193
|
+
state.continuation = inferContinuationMetadata(state.flowId, state.executionState);
|
|
128
194
|
ensureScopeWorkspaceDir(state.scopeKey);
|
|
129
195
|
writeFileSync(flowStateFile(state.scopeKey, state.flowId), `${JSON.stringify({
|
|
130
196
|
...state,
|
|
@@ -154,6 +220,53 @@ export function hasResumableFlowState(state) {
|
|
|
154
220
|
}
|
|
155
221
|
return state.executionState.phases.some((phase) => phase.steps.some((step) => step.status === "done" || step.status === "running"));
|
|
156
222
|
}
|
|
223
|
+
function hasContinuableFlowState(state) {
|
|
224
|
+
if (!state) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (!state.executionState.terminated && state.status !== "completed") {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return state.continuation?.continueEligible === true;
|
|
231
|
+
}
|
|
232
|
+
export function classifyFlowLaunchAvailability(state) {
|
|
233
|
+
if (!state) {
|
|
234
|
+
return {
|
|
235
|
+
hasExistingState: false,
|
|
236
|
+
requiresExplicitChoice: false,
|
|
237
|
+
resume: { available: false, reason: "No saved state found." },
|
|
238
|
+
continue: { available: false, reason: "No saved state found." },
|
|
239
|
+
restart: { available: true, reason: "Start a fresh attempt." },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const resumeAvailable = hasResumableFlowState(state);
|
|
243
|
+
const continueAvailable = hasContinuableFlowState(state);
|
|
244
|
+
const availability = {
|
|
245
|
+
hasExistingState: true,
|
|
246
|
+
requiresExplicitChoice: resumeAvailable || continueAvailable,
|
|
247
|
+
resume: resumeAvailable
|
|
248
|
+
? { available: true, reason: "Continue the interrupted execution state." }
|
|
249
|
+
: {
|
|
250
|
+
available: false,
|
|
251
|
+
reason: state.executionState.terminated || state.status === "completed"
|
|
252
|
+
? "The saved run already terminated and cannot be resumed."
|
|
253
|
+
: "The saved state is not resumable.",
|
|
254
|
+
},
|
|
255
|
+
continue: continueAvailable
|
|
256
|
+
? { available: true, reason: "Start the next iteration from the latest active artifacts." }
|
|
257
|
+
: {
|
|
258
|
+
available: false,
|
|
259
|
+
reason: state.schemaVersion < FLOW_STATE_SCHEMA_VERSION
|
|
260
|
+
? "Legacy flow state lacks safe continuation metadata."
|
|
261
|
+
: "The saved run does not expose a continuable loop boundary.",
|
|
262
|
+
},
|
|
263
|
+
restart: {
|
|
264
|
+
available: true,
|
|
265
|
+
reason: "Archive the active attempt and start a fresh run.",
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
return availability;
|
|
269
|
+
}
|
|
157
270
|
function normalizeStepState(step) {
|
|
158
271
|
if (step.status !== "running") {
|
|
159
272
|
return step;
|
|
@@ -237,3 +350,27 @@ export function prepareFlowStateForResume(state) {
|
|
|
237
350
|
delete state.executionState.terminationReason;
|
|
238
351
|
return state;
|
|
239
352
|
}
|
|
353
|
+
export function prepareFlowStateForContinue(state, orderedPhases) {
|
|
354
|
+
state.status = "pending";
|
|
355
|
+
state.lastError = null;
|
|
356
|
+
state.currentStep = null;
|
|
357
|
+
const flowKind = state.executionState.flowKind;
|
|
358
|
+
if (CONTINUABLE_FLOW_KINDS.has(flowKind) || CONTINUABLE_DIRECT_FLOW_IDS.has(state.flowId)) {
|
|
359
|
+
state.executionState = {
|
|
360
|
+
...state.executionState,
|
|
361
|
+
publicationRunId: randomUUID(),
|
|
362
|
+
terminated: false,
|
|
363
|
+
phases: orderedPhases.map(createPendingPhaseState),
|
|
364
|
+
};
|
|
365
|
+
delete state.executionState.terminationReason;
|
|
366
|
+
delete state.executionState.terminationOutcome;
|
|
367
|
+
return state;
|
|
368
|
+
}
|
|
369
|
+
const targetPhaseId = state.continuation?.stopPhaseId ?? parseTerminationLocation(state.executionState.terminationReason).stopPhaseId;
|
|
370
|
+
if (!targetPhaseId) {
|
|
371
|
+
throw new TaskRunnerError("Continue is impossible because the stop phase could not be determined safely. Use restart.");
|
|
372
|
+
}
|
|
373
|
+
rewindFlowRunStateToPhase(state, orderedPhases, targetPhaseId);
|
|
374
|
+
state.executionState.publicationRunId = randomUUID();
|
|
375
|
+
return state;
|
|
376
|
+
}
|