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.
Files changed (74) hide show
  1. package/README.md +148 -27
  2. package/dist/artifacts.js +114 -3
  3. package/dist/doctor/checks/executors.js +2 -2
  4. package/dist/flow-state.js +138 -1
  5. package/dist/index.js +421 -82
  6. package/dist/interactive/controller.js +305 -36
  7. package/dist/interactive/ink/index.js +24 -3
  8. package/dist/interactive/state.js +1 -0
  9. package/dist/interactive/tree.js +2 -2
  10. package/dist/interactive/web/index.js +179 -0
  11. package/dist/interactive/web/protocol.js +154 -0
  12. package/dist/interactive/web/server.js +575 -0
  13. package/dist/interactive/web/static/app.js +709 -0
  14. package/dist/interactive/web/static/index.html +77 -0
  15. package/dist/interactive/web/static/styles.css +2 -0
  16. package/dist/interactive/web/static/styles.input.css +469 -0
  17. package/dist/pipeline/auto-flow.js +9 -6
  18. package/dist/pipeline/context.js +6 -5
  19. package/dist/pipeline/declarative-flows.js +39 -20
  20. package/dist/pipeline/flow-catalog.js +40 -14
  21. package/dist/pipeline/flow-specs/auto-common-guided.json +313 -0
  22. package/dist/pipeline/flow-specs/auto-common.json +4 -1
  23. package/dist/pipeline/flow-specs/auto-golang.json +27 -1
  24. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +15 -1
  25. package/dist/pipeline/flow-specs/design-review.json +2 -0
  26. package/dist/pipeline/flow-specs/implement.json +3 -1
  27. package/dist/pipeline/flow-specs/plan.json +8 -2
  28. package/dist/pipeline/flow-specs/playbook-init.json +199 -0
  29. package/dist/pipeline/flow-specs/review/review-fix.json +3 -1
  30. package/dist/pipeline/flow-specs/review/review-loop.json +4 -0
  31. package/dist/pipeline/flow-specs/review/review.json +2 -0
  32. package/dist/pipeline/launch-profile-config.js +30 -18
  33. package/dist/pipeline/node-contract.js +1 -0
  34. package/dist/pipeline/node-registry.js +119 -5
  35. package/dist/pipeline/nodes/flow-run-node.js +200 -173
  36. package/dist/pipeline/nodes/llm-prompt-node.js +15 -33
  37. package/dist/pipeline/nodes/playbook-ensure-node.js +115 -0
  38. package/dist/pipeline/nodes/playbook-inventory-node.js +51 -0
  39. package/dist/pipeline/nodes/playbook-questions-form-node.js +166 -0
  40. package/dist/pipeline/nodes/playbook-write-node.js +243 -0
  41. package/dist/pipeline/nodes/project-guidance-node.js +69 -0
  42. package/dist/pipeline/plugin-loader.js +389 -0
  43. package/dist/pipeline/plugin-types.js +1 -0
  44. package/dist/pipeline/prompt-registry.js +4 -1
  45. package/dist/pipeline/prompt-runtime.js +6 -2
  46. package/dist/pipeline/registry.js +71 -4
  47. package/dist/pipeline/spec-compiler.js +1 -0
  48. package/dist/pipeline/spec-loader.js +14 -0
  49. package/dist/pipeline/spec-types.js +19 -0
  50. package/dist/pipeline/spec-validator.js +6 -0
  51. package/dist/pipeline/value-resolver.js +41 -2
  52. package/dist/playbook/practice-candidates.js +12 -0
  53. package/dist/playbook/repo-inventory.js +208 -0
  54. package/dist/plugin-sdk.js +1 -0
  55. package/dist/prompts.js +31 -0
  56. package/dist/runtime/artifact-registry.js +3 -0
  57. package/dist/runtime/execution-routing.js +25 -19
  58. package/dist/runtime/interactive-execution-routing.js +66 -57
  59. package/dist/runtime/playbook.js +485 -0
  60. package/dist/runtime/project-guidance.js +339 -0
  61. package/dist/structured-artifact-schema-registry.js +8 -0
  62. package/dist/structured-artifact-schemas.json +235 -0
  63. package/dist/structured-artifacts.js +7 -1
  64. package/docs/declarative-workflows.md +565 -0
  65. package/docs/example/.flows/examples/claude-example.json +50 -0
  66. package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
  67. package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
  68. package/docs/examples/.flows/claude-example.json +50 -0
  69. package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
  70. package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
  71. package/docs/features.md +77 -0
  72. package/docs/playbook.md +327 -0
  73. package/docs/plugin-sdk.md +731 -0
  74. 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 harness engineering around coding agents.
3
+ `AgentWeaver` is a TypeScript/Node.js CLI for engineering durable workflows around coding agents.
4
4
 
5
- It is built around declarative workflow specs. A flow describes phases and steps in JSON, runtime nodes implement behavior in TypeScript, and artifacts on disk make runs resumable, inspectable, and operationally manageable from the TUI.
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
- The important part is not that exact chain. The point is that AgentWeaver lets you design, operate, and evolve durable agent harnesses instead of accumulating one-off prompts and shell glue.
11
+ Planning-heavy work can use:
12
12
 
13
- For planning-heavy work, a typical path can now include `plan -> design-review -> implement`, where `design-review` critiques planning artifacts before coding starts.
13
+ `plan -> design-review -> implement -> review-loop`
14
14
 
15
- ## What It Does
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
- - Fetches Jira issue context by issue key or browse URL
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
- ## Harness Engineering Focus
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
- - workflows are modeled explicitly as phases, steps, prompts, params, expectations, and artifacts
29
- - execution logic is isolated into reusable nodes and executors instead of being embedded in ad-hoc scripts
30
- - artifacts on disk are the contract between stages, which makes runs reviewable and restartable
31
- - the same workflow model can be used in direct CLI mode, interactive TUI mode, and resumable automation flows
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/` or project-local `.agentweaver/.flows/`
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>` currently applies to `plan`
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
- - project-local flows are loaded from `.agentweaver/.flows/`
336
- - both built-in and project-local flow specs are validated at load time
337
- - duplicate flow ids fail fast
338
- - project-local flows are shown separately in the UI
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
- ## Project-Local Flows
415
+ ## Custom Flows
341
416
 
342
- You can add project-specific flow specs under:
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
- Project-local flows:
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
- Nested `flow-run` steps can reference built-in or project-local specs by file name, as long as the name resolves unambiguously.
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 planArtifacts(taskKey) {
218
- return [designFile(taskKey), designJsonFile(taskKey), planFile(taskKey), planJsonFile(taskKey), qaFile(taskKey), qaJsonFile(taskKey)];
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 { ALLOWED_MODELS_BY_EXECUTOR } from "../../pipeline/launch-profile-config.js";
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 = ALLOWED_MODELS_BY_EXECUTOR[executorId];
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" });
@@ -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 = 2;
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
+ }