opencode-plugin-flow 6.2.0 → 6.3.1
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/CHANGELOG.md +46 -0
- package/README.md +3 -3
- package/dist/index.js +209 -68
- package/dist/index.js.map +8 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,52 @@ One short entry per release, written for users deciding whether to upgrade.
|
|
|
6
6
|
|
|
7
7
|
No changes yet.
|
|
8
8
|
|
|
9
|
+
## [6.3.1] - 2026-07-21
|
|
10
|
+
|
|
11
|
+
Read-only retry lore closes the recovery and documentation gaps in 6.3.0:
|
|
12
|
+
|
|
13
|
+
- While Session v5 remains active, every caller with tool access now receives an
|
|
14
|
+
exact accepted completion through the read-only replay path before a new
|
|
15
|
+
reviewer submission is considered. Reviewer retries no longer cancel
|
|
16
|
+
validation capture or write session state.
|
|
17
|
+
- Replay documentation now states the active Session v5 boundary, and release
|
|
18
|
+
language accurately describes secret avoidance as manager guidance rather
|
|
19
|
+
than runtime filtering or redaction.
|
|
20
|
+
- The documentation contract keeps the rolling `Unreleased` changelog heading
|
|
21
|
+
without requiring its contents to remain `No changes yet.`
|
|
22
|
+
|
|
23
|
+
Install or update:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
opencode plugin opencode-plugin-flow@6.3.1 --global --force
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## [6.3.0] - 2026-07-21
|
|
30
|
+
|
|
31
|
+
Reviewer-owned submission lore strengthens independent review without turning
|
|
32
|
+
Flow back into a heavy orchestration framework:
|
|
33
|
+
|
|
34
|
+
- The reserved reviewer now submits its own result through
|
|
35
|
+
`flow_feature_complete`. The host authorizes new completions by agent identity,
|
|
36
|
+
while exact previously accepted requests remain read-only replays for active
|
|
37
|
+
Session v5 workflows.
|
|
38
|
+
- Pending-review recovery is source-aware: current assignments can be
|
|
39
|
+
redispatched after interruption, stale source routes to reset and full
|
|
40
|
+
revalidation, and fingerprint failures fail closed with repair guidance.
|
|
41
|
+
- Final reviewers receive the approved plan targets and validation intent,
|
|
42
|
+
completed feature IDs, and only assignment-linked evidence. Broad validation
|
|
43
|
+
is treated as a coverage claim; manager guidance warns against putting secrets
|
|
44
|
+
in durable commands, and raw output is reduced to completeness plus a digest.
|
|
45
|
+
- Closed-world permission tests and the pinned OpenCode host smoke cover every
|
|
46
|
+
registered Flow tool. Documentation distinguishes lifecycle mutation from
|
|
47
|
+
fail-closed quarantine maintenance.
|
|
48
|
+
|
|
49
|
+
Install or update:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
opencode plugin opencode-plugin-flow@6.3.0 --global --force
|
|
53
|
+
```
|
|
54
|
+
|
|
9
55
|
## [6.2.0] - 2026-07-21
|
|
10
56
|
|
|
11
57
|
Low-friction bounded-wave lore keeps Flow small while making parallel
|
package/README.md
CHANGED
|
@@ -16,11 +16,11 @@ parallel before it validates and reviews the combined result.
|
|
|
16
16
|
Install the exact npm release through OpenCode:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
opencode plugin opencode-plugin-flow@6.
|
|
19
|
+
opencode plugin opencode-plugin-flow@6.3.1 --global --force
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Omit `--global` for project scope. Exact version pins do not update
|
|
23
|
-
automatically. To update, replace `6.
|
|
23
|
+
automatically. To update, replace `6.3.1` with the new release and rerun the
|
|
24
24
|
command.
|
|
25
25
|
|
|
26
26
|
Before upgrading from Flow v5 or earlier, finish or explicitly close any active
|
|
@@ -32,7 +32,7 @@ The equivalent manual project configuration is:
|
|
|
32
32
|
```json
|
|
33
33
|
{
|
|
34
34
|
"$schema": "https://opencode.ai/config.json",
|
|
35
|
-
"plugin": ["opencode-plugin-flow@6.
|
|
35
|
+
"plugin": ["opencode-plugin-flow@6.3.1"]
|
|
36
36
|
}
|
|
37
37
|
```
|
|
38
38
|
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,12 @@ description: Manage a Flow goal from planning through implementation, validation
|
|
|
7
7
|
# Flow
|
|
8
8
|
|
|
9
9
|
Flow is a small state ledger around ordinary coding work. The root manager owns
|
|
10
|
-
the session,
|
|
11
|
-
|
|
12
|
-
contribute disjoint work inside the active feature. The reserved
|
|
13
|
-
independently reviews
|
|
10
|
+
the session, integration, validation, review dispatch, reset, closure, and every
|
|
11
|
+
lifecycle mutation except review submission. Bounded \`flow-worker\`
|
|
12
|
+
instances may contribute disjoint work inside the active feature. The reserved
|
|
13
|
+
\`flow-reviewer\` independently reviews and submits its own result through
|
|
14
|
+
\`flow_feature_complete\`; it cannot edit the workspace or make any other
|
|
15
|
+
lifecycle mutation.
|
|
14
16
|
|
|
15
17
|
## Route from status
|
|
16
18
|
|
|
@@ -37,9 +39,10 @@ report that the plugin is not fully loaded instead of simulating state changes.
|
|
|
37
39
|
- Only one durable feature run is active at a time. Conversation-local worker
|
|
38
40
|
waves do not create additional runs or Flow state.
|
|
39
41
|
- Work stays inside the active feature and preserves unrelated user changes.
|
|
40
|
-
- A passing feature needs successful current-source validation and one
|
|
41
|
-
independent reviewer
|
|
42
|
-
a final review; it does not add a second review
|
|
42
|
+
- A passing feature needs successful current-source validation and one result
|
|
43
|
+
submitted directly by the assigned independent reviewer. The final feature
|
|
44
|
+
uses broad validation and a final review; it does not add a second review
|
|
45
|
+
pass.
|
|
43
46
|
- A failed review is recorded honestly. Any retry is a fresh run with full
|
|
44
47
|
validation and review.
|
|
45
48
|
- Use runtime revisions and operation ids for ordering and idempotency. Supply
|
|
@@ -53,7 +56,13 @@ report that the plugin is not fully loaded instead of simulating state changes.
|
|
|
53
56
|
|
|
54
57
|
On confusion or interruption, read compact status and follow its next action.
|
|
55
58
|
Use execution status for the active feature and reviewer status for a returned
|
|
56
|
-
assignment id.
|
|
59
|
+
assignment id. Redispatch a pending assignment after interruption or an
|
|
60
|
+
unconfirmed reviewer return; the manager never invents or submits a verdict. If
|
|
61
|
+
completion reports \`Workspace content changed after review started\`, call
|
|
62
|
+
\`flow_feature_reset\`; that source-stale assignment must not be redispatched.
|
|
63
|
+
Start a fresh run and repeat full validation and review. After the reviewer
|
|
64
|
+
returns, read compact status to learn the durable outcome. If status is closed
|
|
65
|
+
with \`archiveRetry\`, call
|
|
57
66
|
\`flow_session_close\` with that projected request byte-for-byte; do not create a
|
|
58
67
|
new operation id or revision. Never infer completion, retry count, or closure
|
|
59
68
|
from prose.
|
|
@@ -126,26 +135,30 @@ document in the repository unless the user explicitly requests one.
|
|
|
126
135
|
// skills/flow-review/SKILL.md
|
|
127
136
|
var SKILL_default3 = `---
|
|
128
137
|
name: flow-review
|
|
129
|
-
description: Independently review one runtime-owned Flow assignment. Reserved for the read-only flow-reviewer; managers dispatch assignments but do not perform this review themselves.
|
|
138
|
+
description: Independently review one runtime-owned Flow assignment. Reserved for the workspace-read-only flow-reviewer; managers dispatch assignments but do not perform this review themselves.
|
|
130
139
|
---
|
|
131
140
|
|
|
132
141
|
# Flow Review
|
|
133
142
|
|
|
134
143
|
You are the independent \`flow-reviewer\`. Review the assigned work; do not fix
|
|
135
144
|
it. You may read relevant files and supplied evidence, but must not edit files,
|
|
136
|
-
read outside the workspace, run commands, launch workers
|
|
137
|
-
|
|
145
|
+
read outside the workspace, run commands, or launch workers. You may call only
|
|
146
|
+
\`flow_status\` to read this assignment and \`flow_feature_complete\` to submit its
|
|
147
|
+
exact result. The latter is your sole lifecycle mutation.
|
|
138
148
|
|
|
139
149
|
## Recover the assignment
|
|
140
150
|
|
|
141
151
|
When given an assignment id, call only
|
|
142
152
|
\`flow_status { request: { view: "reviewer", assignmentId: "..." } }\`. Use its
|
|
143
|
-
bounded packet
|
|
144
|
-
|
|
153
|
+
bounded packet, assignment-linked validations, approved-plan context, and
|
|
154
|
+
completed feature IDs instead of reconstructing feature, source,
|
|
155
|
+
revision, validation, or lifecycle data from conversation memory.
|
|
145
156
|
|
|
146
|
-
If the
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
If the reviewer projection is available but evidence required to justify a
|
|
158
|
+
verdict is missing, submit a failed result with a blocking evidence-gap finding.
|
|
159
|
+
If the assignment itself is unavailable, report that failure without another
|
|
160
|
+
state change so the manager can inspect compact status. Never invent validation,
|
|
161
|
+
identity, revision, or time.
|
|
149
162
|
|
|
150
163
|
## Review
|
|
151
164
|
|
|
@@ -160,6 +173,12 @@ the manager's summary. Check that:
|
|
|
160
173
|
- persistence, concurrency, security, migration, package, UI, and recovery
|
|
161
174
|
risks were examined when relevant.
|
|
162
175
|
|
|
176
|
+
Validation scope is a claim. Treat \`broad\` as adequate only when the durable
|
|
177
|
+
command is the repository's canonical applicable gate or a justified equivalent
|
|
178
|
+
for the delivered state. Flow deliberately projects no raw command output; use
|
|
179
|
+
the durable command, exit code, completeness, digest, source binding, and your
|
|
180
|
+
workspace inspection. A weak or unclear coverage claim is an evidence gap.
|
|
181
|
+
|
|
163
182
|
For a final assignment, also trace every approved requirement and feature to
|
|
164
183
|
the delivered result, inspect broad validation, and confirm docs, commands,
|
|
165
184
|
package surfaces, and remaining gaps are consistent with completion. The final
|
|
@@ -170,25 +189,40 @@ approved outcome; otherwise use \`advisory\`. Every blocker needs a precise
|
|
|
170
189
|
summary. Every blocker must cite a changed artifact and location, or identify
|
|
171
190
|
the exact missing evidence or unmet approved requirement in \`evidence\`.
|
|
172
191
|
|
|
173
|
-
##
|
|
192
|
+
## Submit one result
|
|
174
193
|
|
|
175
|
-
|
|
194
|
+
Call \`flow_feature_complete\` directly with the assignment id, current reviewer
|
|
195
|
+
projection revision and feature id, a fresh operation id, a concise summary,
|
|
196
|
+
and exactly one assignment result:
|
|
176
197
|
|
|
177
198
|
\`\`\`json
|
|
178
199
|
{
|
|
179
|
-
"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
200
|
+
"request": {
|
|
201
|
+
"operationId": "review-submit:fresh-id",
|
|
202
|
+
"expectedRevision": 12,
|
|
203
|
+
"featureId": "approved-feature-id",
|
|
204
|
+
"assignmentId": "review-assignment:runtime-id",
|
|
205
|
+
"summary": "Concise reviewed outcome.",
|
|
206
|
+
"result": {
|
|
207
|
+
"verdict": "passed",
|
|
208
|
+
"findings": [],
|
|
209
|
+
"terminalDisposition": "submitted"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
183
212
|
}
|
|
184
213
|
\`\`\`
|
|
185
214
|
|
|
186
215
|
Each finding contains \`severity\`, \`summary\`, and optional \`evidence\`. Use
|
|
187
|
-
\`verdict: "failed"\` whenever any blocking finding remains.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
216
|
+
\`verdict: "failed"\` whenever any blocking finding remains. Do not return or
|
|
217
|
+
invent run ids, source hashes, validation records, timestamps, review modes, or
|
|
218
|
+
attempt fields. Never ask the manager to copy or submit your verdict.
|
|
219
|
+
|
|
220
|
+
After the tool returns, report its durable outcome concisely. If submission
|
|
221
|
+
fails, report the exact failure. For \`Workspace content changed after review
|
|
222
|
+
started\`, tell the manager to reset the feature and do not recommend redispatch;
|
|
223
|
+
the source-stale assignment cannot complete. After an interruption with no
|
|
224
|
+
accepted result, the manager may recover the pending assignment. Never
|
|
225
|
+
downgrade, fabricate, or hand off a result for manager submission.
|
|
192
226
|
|
|
193
227
|
Approve only what you actually inspected. Missing coverage is a finding, never
|
|
194
228
|
a reason to lower the bar.
|
|
@@ -202,10 +236,11 @@ description: Implement, validate, independently review, and record one approved
|
|
|
202
236
|
|
|
203
237
|
# Flow Run
|
|
204
238
|
|
|
205
|
-
Work on exactly one approved feature. The root manager owns the session,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
reserved \`flow-reviewer\` owns the
|
|
239
|
+
Work on exactly one approved feature. The root manager owns the session,
|
|
240
|
+
integration, validation, review dispatch, reset, closure, and every
|
|
241
|
+
lifecycle mutation except review submission. Bounded \`flow-worker\`
|
|
242
|
+
instances may contribute disjoint work; the reserved \`flow-reviewer\` owns the
|
|
243
|
+
independent review and submits its own result.
|
|
209
244
|
|
|
210
245
|
## Start and scope
|
|
211
246
|
|
|
@@ -271,17 +306,25 @@ checks from the changed behavior and risk:
|
|
|
271
306
|
sufficient alone only for genuinely mechanical or documentation-only work.
|
|
272
307
|
- UI claims need visual inspection when a runnable target is available.
|
|
273
308
|
- Cleanup claims need behavior-preservation evidence, not formatting alone.
|
|
309
|
+
- \`scope: "broad"\` is a claim about coverage, not a stronger label. Use it only
|
|
310
|
+
for the repository's canonical applicable gate or a justified equivalent
|
|
311
|
+
that covers the delivered repository state.
|
|
274
312
|
|
|
275
313
|
Immediately before each Bash command used as evidence, call
|
|
276
314
|
\`flow_validation_start\` with the current revision, feature id, the exact
|
|
277
315
|
command, and \`scope\` (\`focused\` or \`broad\`). Run that byte-for-byte command next
|
|
278
316
|
and inspect its complete outcome. Flow records the host-observed result directly
|
|
279
|
-
in the session; do not copy host-observed fields into a later request.
|
|
317
|
+
in the session; do not copy host-observed fields into a later request. The exact
|
|
318
|
+
command is durable, so never inline tokens, passwords, credentials, or other
|
|
319
|
+
secrets. Raw output is deliberately neither persisted nor projected: the
|
|
320
|
+
durable evidence is the command, exit code, output completeness, and output
|
|
321
|
+
digest, while the manager must inspect the live output.
|
|
280
322
|
|
|
281
323
|
Use focused validation for ordinary features. For the final feature, run the
|
|
282
324
|
repository's broad applicable gate after the last relevant edit. A source edit
|
|
283
325
|
invalidates earlier applicability. Failed or unavailable checks are blockers,
|
|
284
|
-
not passing evidence.
|
|
326
|
+
not passing evidence. If the canonical gate cannot run, explain why the chosen
|
|
327
|
+
equivalent is broad enough; otherwise record the narrower evidence as focused.
|
|
285
328
|
|
|
286
329
|
## Review and record
|
|
287
330
|
|
|
@@ -295,9 +338,19 @@ automatically and derives \`feature\` versus \`final\` review from plan progress
|
|
|
295
338
|
callers do not supply the review kind.
|
|
296
339
|
|
|
297
340
|
Dispatch the returned assignment only to the reserved \`flow-reviewer\`. Do not
|
|
298
|
-
perform the independent review in manager context
|
|
299
|
-
|
|
300
|
-
|
|
341
|
+
perform the independent review in manager context and never copy or submit its
|
|
342
|
+
verdict. The reviewer reads its assignment, inspects the workspace, and calls
|
|
343
|
+
\`flow_feature_complete\` directly; the runtime verifies the calling agent. The
|
|
344
|
+
reviewer remains workspace-read-only and may make only this exact result
|
|
345
|
+
submission as its sole lifecycle mutation.
|
|
346
|
+
|
|
347
|
+
After the reviewer returns, read compact status rather than treating prose as
|
|
348
|
+
the outcome. Redispatch the same pending assignment after interruption or an
|
|
349
|
+
unconfirmed reviewer return. If submission reports \`Workspace content changed
|
|
350
|
+
after review started\`, call \`flow_feature_reset\` and do not redispatch that
|
|
351
|
+
source-stale assignment; start a fresh run and repeat full validation and
|
|
352
|
+
review. Never fabricate a verdict. A submitted pass completes the feature; a
|
|
353
|
+
submitted blocking finding records a blocked outcome.
|
|
301
354
|
|
|
302
355
|
If repair is authorized after a failed review, reset the feature, fix it, and
|
|
303
356
|
repeat full validation and full review in a fresh run.
|
|
@@ -415,7 +468,7 @@ function compileFlowPromptSurface(surface) {
|
|
|
415
468
|
return [
|
|
416
469
|
"# Flow review command",
|
|
417
470
|
"",
|
|
418
|
-
"Run this assignment only as the reserved `flow-reviewer`. The reviewer is independent and read-only; it
|
|
471
|
+
"Run this assignment only as the reserved `flow-reviewer`. The reviewer is independent and workspace-read-only; it may read reviewer status and submit only its own result through `flow_feature_complete`.",
|
|
419
472
|
"",
|
|
420
473
|
"Assignment: $ARGUMENTS"
|
|
421
474
|
].join(`
|
|
@@ -436,7 +489,7 @@ var FLOW_CORE_AGENTS = {
|
|
|
436
489
|
"flow-reviewer": {
|
|
437
490
|
mode: "subagent",
|
|
438
491
|
hidden: true,
|
|
439
|
-
description: "Independent read-only reviewer
|
|
492
|
+
description: "Independent workspace-read-only reviewer that submits one Flow result.",
|
|
440
493
|
prompt: compileFlowPromptSurface("flow-reviewer"),
|
|
441
494
|
permission: {
|
|
442
495
|
edit: "deny",
|
|
@@ -445,7 +498,8 @@ var FLOW_CORE_AGENTS = {
|
|
|
445
498
|
skill: "deny",
|
|
446
499
|
task: { "*": "deny" },
|
|
447
500
|
"flow_*": "deny",
|
|
448
|
-
flow_status: "allow"
|
|
501
|
+
flow_status: "allow",
|
|
502
|
+
flow_feature_complete: "allow"
|
|
449
503
|
}
|
|
450
504
|
},
|
|
451
505
|
"flow-worker": {
|
|
@@ -486,7 +540,7 @@ var FLOW_CORE_COMMANDS = {
|
|
|
486
540
|
template: compileFlowPromptSurface("flow-run")
|
|
487
541
|
},
|
|
488
542
|
"flow-review": {
|
|
489
|
-
description: "Run one independent read-only Flow review",
|
|
543
|
+
description: "Run one independent workspace-read-only Flow review",
|
|
490
544
|
agent: "flow-reviewer",
|
|
491
545
|
subtask: true,
|
|
492
546
|
template: compileFlowPromptSurface("flow-review")
|
|
@@ -1453,6 +1507,7 @@ function boundedText(label, options) {
|
|
|
1453
1507
|
}
|
|
1454
1508
|
var FeatureIdSchema = z.string().max(MAX_SESSION_ID_LENGTH).regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE);
|
|
1455
1509
|
var OperationIdSchema = z.string().min(1).max(128).regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]*$/);
|
|
1510
|
+
var ReviewAssignmentIdSchema = z.string().min(1).max(256);
|
|
1456
1511
|
var RevisionSchema = z.number().int().safe().nonnegative();
|
|
1457
1512
|
var SourceDigestSchema = z.custom((value) => typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value), "Expected a sha256: content digest.");
|
|
1458
1513
|
var PlanFeatureSchema = z.object({
|
|
@@ -1508,7 +1563,7 @@ var ArtifactSchema = z.object({
|
|
|
1508
1563
|
path: boundedText("Artifact path", { maxBytes: MAX_PATH_BYTES }).refine(isArtifactPath, ARTIFACT_PATH_MESSAGE)
|
|
1509
1564
|
}).strict();
|
|
1510
1565
|
var ReviewAssignmentSchema = z.object({
|
|
1511
|
-
id:
|
|
1566
|
+
id: ReviewAssignmentIdSchema,
|
|
1512
1567
|
operationId: OperationIdSchema,
|
|
1513
1568
|
featureId: FeatureIdSchema,
|
|
1514
1569
|
runId: z.string().min(1).max(256),
|
|
@@ -1606,7 +1661,7 @@ var FeatureCompleteInputSchema = z.object({
|
|
|
1606
1661
|
request: z.object({
|
|
1607
1662
|
...guarded,
|
|
1608
1663
|
featureId: FeatureIdSchema,
|
|
1609
|
-
assignmentId:
|
|
1664
|
+
assignmentId: ReviewAssignmentIdSchema,
|
|
1610
1665
|
summary: boundedText("Feature result summary"),
|
|
1611
1666
|
result: PublicReviewResultSchema
|
|
1612
1667
|
}).strict()
|
|
@@ -1637,7 +1692,7 @@ var StatusInputSchema = z.object({
|
|
|
1637
1692
|
z.object({ view: z.literal("execution") }).strict(),
|
|
1638
1693
|
z.object({
|
|
1639
1694
|
view: z.literal("reviewer"),
|
|
1640
|
-
assignmentId:
|
|
1695
|
+
assignmentId: ReviewAssignmentIdSchema
|
|
1641
1696
|
}).strict()
|
|
1642
1697
|
])
|
|
1643
1698
|
}).strict();
|
|
@@ -2472,7 +2527,10 @@ function featureProgress(session) {
|
|
|
2472
2527
|
const completed = session.plan?.features.filter((feature) => isFeatureComplete(session, feature.id)).length ?? 0;
|
|
2473
2528
|
return { completed, total, remaining: total - completed };
|
|
2474
2529
|
}
|
|
2475
|
-
function
|
|
2530
|
+
function activePendingReview(session) {
|
|
2531
|
+
return activeRun(session)?.reviews.find((review) => review.result === null) ?? null;
|
|
2532
|
+
}
|
|
2533
|
+
function nextAction(session, pendingReviewSourceStale = false) {
|
|
2476
2534
|
const status = sessionStatus(session);
|
|
2477
2535
|
if (status === "planning") {
|
|
2478
2536
|
return session.plan ? "flow_plan_approve" : "flow_plan_save";
|
|
@@ -2488,16 +2546,16 @@ function nextAction(session) {
|
|
|
2488
2546
|
const run = activeRun(session);
|
|
2489
2547
|
if (!run)
|
|
2490
2548
|
return "flow_status";
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2549
|
+
if (activePendingReview(session)) {
|
|
2550
|
+
return pendingReviewSourceStale ? "flow_feature_reset" : "dispatch-flow-reviewer";
|
|
2551
|
+
}
|
|
2494
2552
|
const finalRun = session.plan?.features.every((feature) => feature.id === run.featureId || isFeatureComplete(session, feature.id)) ?? false;
|
|
2495
2553
|
const hasPassingValidation = run.validations.some((validation) => validation.exitCode === 0 && validation.outputComplete && (!finalRun || validation.scope === "broad"));
|
|
2496
2554
|
if (!hasPassingValidation)
|
|
2497
2555
|
return "flow_validation_start";
|
|
2498
2556
|
return "flow_review_start";
|
|
2499
2557
|
}
|
|
2500
|
-
function compactProjection(session) {
|
|
2558
|
+
function compactProjection(session, pendingReviewSourceStale = false) {
|
|
2501
2559
|
const run = activeRun(session);
|
|
2502
2560
|
const retryRequest = closureRetryRequest(session);
|
|
2503
2561
|
if (session.closure && !retryRequest) {
|
|
@@ -2512,7 +2570,7 @@ function compactProjection(session) {
|
|
|
2512
2570
|
activeFeatureId: run?.featureId ?? null,
|
|
2513
2571
|
activeRunId: run?.id ?? null,
|
|
2514
2572
|
progress: featureProgress(session),
|
|
2515
|
-
nextAction: nextAction(session),
|
|
2573
|
+
nextAction: nextAction(session, pendingReviewSourceStale),
|
|
2516
2574
|
archiveRetry: retryRequest ? { request: retryRequest } : null
|
|
2517
2575
|
};
|
|
2518
2576
|
}
|
|
@@ -2524,11 +2582,11 @@ function archivedProjection(session) {
|
|
|
2524
2582
|
archived: true
|
|
2525
2583
|
};
|
|
2526
2584
|
}
|
|
2527
|
-
function executionProjection(session) {
|
|
2585
|
+
function executionProjection(session, pendingReviewSourceStale = false) {
|
|
2528
2586
|
const run = activeRun(session);
|
|
2529
2587
|
const feature = session.plan?.features.find((item) => item.id === run?.featureId);
|
|
2530
2588
|
return {
|
|
2531
|
-
...compactProjection(session),
|
|
2589
|
+
...compactProjection(session, pendingReviewSourceStale),
|
|
2532
2590
|
view: "execution",
|
|
2533
2591
|
goal: session.goal,
|
|
2534
2592
|
feature: feature ?? null,
|
|
@@ -2553,6 +2611,7 @@ function reviewerProjection(session, assignmentId) {
|
|
|
2553
2611
|
}
|
|
2554
2612
|
const feature = session.plan?.features.find((item) => item.id === assignment?.featureId);
|
|
2555
2613
|
const plan = session.plan;
|
|
2614
|
+
const assignedValidationIds = new Set(assignment.validationIds);
|
|
2556
2615
|
return {
|
|
2557
2616
|
view: "reviewer",
|
|
2558
2617
|
sessionId: session.id,
|
|
@@ -2567,26 +2626,30 @@ function reviewerProjection(session, assignmentId) {
|
|
|
2567
2626
|
id: candidate.id,
|
|
2568
2627
|
title: candidate.title,
|
|
2569
2628
|
summary: candidate.summary,
|
|
2629
|
+
targets: [...candidate.targets],
|
|
2630
|
+
validation: [...candidate.validation],
|
|
2570
2631
|
dependsOn: [...candidate.dependsOn]
|
|
2571
2632
|
}))
|
|
2572
2633
|
} : null,
|
|
2573
2634
|
feature: feature ?? null,
|
|
2574
2635
|
assignment,
|
|
2575
2636
|
artifactsChanged: run.artifactsChanged,
|
|
2576
|
-
validations: run.validations,
|
|
2637
|
+
validations: run.validations.filter((validation) => assignedValidationIds.has(validation.id)),
|
|
2577
2638
|
completedFeatureIds: plan?.features.filter((candidate) => isFeatureComplete(session, candidate.id)).map((candidate) => candidate.id) ?? []
|
|
2578
2639
|
};
|
|
2579
2640
|
}
|
|
2580
|
-
function project(session, request) {
|
|
2581
|
-
if (request.view === "compact")
|
|
2582
|
-
return compactProjection(session);
|
|
2583
|
-
|
|
2584
|
-
|
|
2641
|
+
function project(session, request, pendingReviewSourceStale = false) {
|
|
2642
|
+
if (request.view === "compact") {
|
|
2643
|
+
return compactProjection(session, pendingReviewSourceStale);
|
|
2644
|
+
}
|
|
2645
|
+
if (request.view === "execution") {
|
|
2646
|
+
return executionProjection(session, pendingReviewSourceStale);
|
|
2647
|
+
}
|
|
2585
2648
|
if (request.view === "reviewer") {
|
|
2586
2649
|
return reviewerProjection(session, request.assignmentId);
|
|
2587
2650
|
}
|
|
2588
2651
|
return {
|
|
2589
|
-
...compactProjection(session),
|
|
2652
|
+
...compactProjection(session, pendingReviewSourceStale),
|
|
2590
2653
|
view: "detail",
|
|
2591
2654
|
goal: session.goal,
|
|
2592
2655
|
plan: session.plan,
|
|
@@ -2603,6 +2666,23 @@ function operationResult(session, operationId, replayed, entity) {
|
|
|
2603
2666
|
...entity === undefined ? {} : { entity }
|
|
2604
2667
|
};
|
|
2605
2668
|
}
|
|
2669
|
+
function featureCompleteResponse(session, request, run, replayed) {
|
|
2670
|
+
return ok(request.result.verdict === "passed" ? "Feature completed." : "Feature blocked by review.", {
|
|
2671
|
+
operation: operationResult(session, request.operationId, replayed, run),
|
|
2672
|
+
projection: compactProjection(session)
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
function exactFeatureCompleteReplay(session, request) {
|
|
2676
|
+
const priorOperation = session.operations.find((operation) => operation.id === request.operationId);
|
|
2677
|
+
if (priorOperation?.kind !== "feature-complete" || priorOperation.inputDigest !== operationInputDigest(request)) {
|
|
2678
|
+
return null;
|
|
2679
|
+
}
|
|
2680
|
+
const result = completeFeature(session, request);
|
|
2681
|
+
if (!result.replayed) {
|
|
2682
|
+
throw new Error("Expected an exact feature-completion replay.");
|
|
2683
|
+
}
|
|
2684
|
+
return { session: result.session, run: result.value };
|
|
2685
|
+
}
|
|
2606
2686
|
async function loadExactArchivedClose(transaction, request) {
|
|
2607
2687
|
const archived = await transaction.loadArchive(request.sessionId);
|
|
2608
2688
|
const operation = archived?.operations.find((item) => item.id === request.operationId);
|
|
@@ -2660,6 +2740,33 @@ function createFlowService(repository, environment) {
|
|
|
2660
2740
|
}
|
|
2661
2741
|
});
|
|
2662
2742
|
}
|
|
2743
|
+
if (request.view !== "reviewer" && activePendingReview(session)) {
|
|
2744
|
+
return await repository.transact(async (transaction) => {
|
|
2745
|
+
const current = await transaction.load();
|
|
2746
|
+
if (!current) {
|
|
2747
|
+
return ok("No active Flow session.", {
|
|
2748
|
+
projection: {
|
|
2749
|
+
view: request.view,
|
|
2750
|
+
status: "idle",
|
|
2751
|
+
revision: 0,
|
|
2752
|
+
nextAction: "flow_plan_save"
|
|
2753
|
+
}
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
const pending = activePendingReview(current);
|
|
2757
|
+
let pendingReviewSourceStale = false;
|
|
2758
|
+
if (pending) {
|
|
2759
|
+
try {
|
|
2760
|
+
pendingReviewSourceStale = await transaction.computeSourceDigest() !== pending.sourceDigest;
|
|
2761
|
+
} catch (error) {
|
|
2762
|
+
return errorResponse(error, "Repair workspace fingerprinting before recovering this pending review. Do not redispatch the assignment until its source can be checked.");
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return ok("Flow status loaded.", {
|
|
2766
|
+
projection: project(current, request, pendingReviewSourceStale)
|
|
2767
|
+
});
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2663
2770
|
return ok("Flow status loaded.", {
|
|
2664
2771
|
projection: project(session, request)
|
|
2665
2772
|
});
|
|
@@ -2760,30 +2867,53 @@ function createFlowService(repository, environment) {
|
|
|
2760
2867
|
async featureComplete(input) {
|
|
2761
2868
|
try {
|
|
2762
2869
|
const request = FeatureCompleteInputSchema.parse(input).request;
|
|
2870
|
+
const current = await repository.read();
|
|
2871
|
+
if (current) {
|
|
2872
|
+
const replay = exactFeatureCompleteReplay(current, request);
|
|
2873
|
+
if (replay) {
|
|
2874
|
+
return featureCompleteResponse(replay.session, request, replay.run, true);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2763
2877
|
return await repository.transact(async (transaction) => {
|
|
2764
2878
|
const session = await transaction.load();
|
|
2765
2879
|
if (!session)
|
|
2766
2880
|
throw new Error("No active Flow session exists.");
|
|
2881
|
+
const racedReplay = exactFeatureCompleteReplay(session, request);
|
|
2882
|
+
if (racedReplay) {
|
|
2883
|
+
return featureCompleteResponse(racedReplay.session, request, racedReplay.run, true);
|
|
2884
|
+
}
|
|
2767
2885
|
const priorOperation = session.operations.find((operation) => operation.id === request.operationId);
|
|
2768
2886
|
if (!priorOperation) {
|
|
2769
2887
|
const assignment = session.runs.flatMap((run) => run.reviews).find((review) => review.id === request.assignmentId);
|
|
2770
2888
|
if (!assignment)
|
|
2771
2889
|
throw new Error("Unknown review assignment.");
|
|
2772
2890
|
if (await transaction.computeSourceDigest() !== assignment.sourceDigest) {
|
|
2773
|
-
|
|
2891
|
+
return errorResponse(new Error("Workspace content changed after review started."), "Call flow_feature_reset, start a fresh run, and repeat full validation and review. Do not redispatch this source-stale assignment.");
|
|
2774
2892
|
}
|
|
2775
2893
|
}
|
|
2776
2894
|
const result = completeFeature(session, request);
|
|
2777
2895
|
await transaction.save(result.session);
|
|
2778
|
-
return
|
|
2779
|
-
operation: operationResult(result.session, request.operationId, result.replayed, result.value),
|
|
2780
|
-
projection: compactProjection(result.session)
|
|
2781
|
-
});
|
|
2896
|
+
return featureCompleteResponse(result.session, request, result.value, result.replayed);
|
|
2782
2897
|
});
|
|
2783
2898
|
} catch (error) {
|
|
2784
2899
|
return errorResponse(error);
|
|
2785
2900
|
}
|
|
2786
2901
|
},
|
|
2902
|
+
async featureCompleteReplay(input) {
|
|
2903
|
+
try {
|
|
2904
|
+
const request = FeatureCompleteInputSchema.parse(input).request;
|
|
2905
|
+
const session = await repository.read();
|
|
2906
|
+
if (!session)
|
|
2907
|
+
throw new Error("No active Flow session exists.");
|
|
2908
|
+
const replay = exactFeatureCompleteReplay(session, request);
|
|
2909
|
+
if (!replay) {
|
|
2910
|
+
throw new Error("Only the Flow reviewer may submit a new feature completion; other agents may replay only an exact previously accepted request.");
|
|
2911
|
+
}
|
|
2912
|
+
return featureCompleteResponse(replay.session, request, replay.run, true);
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
return errorResponse(error);
|
|
2915
|
+
}
|
|
2916
|
+
},
|
|
2787
2917
|
async featureReset(input) {
|
|
2788
2918
|
try {
|
|
2789
2919
|
const request = FeatureResetInputSchema.parse(input).request;
|
|
@@ -2850,6 +2980,7 @@ var flowPlanApprove = (workspace, input) => service(workspace).planApprove(input
|
|
|
2850
2980
|
var flowRunStart = (workspace, input) => service(workspace).runStart(input);
|
|
2851
2981
|
var flowReviewStart = (workspace, input) => service(workspace).reviewStart(input);
|
|
2852
2982
|
var flowFeatureComplete = (workspace, input) => service(workspace).featureComplete(input);
|
|
2983
|
+
var flowFeatureCompleteReplay = (workspace, input) => service(workspace).featureCompleteReplay(input);
|
|
2853
2984
|
var flowFeatureReset = (workspace, input) => service(workspace).featureReset(input);
|
|
2854
2985
|
var flowSessionClose = (workspace, input) => service(workspace).sessionClose(input);
|
|
2855
2986
|
|
|
@@ -2863,6 +2994,7 @@ function boundedHostText(label, options) {
|
|
|
2863
2994
|
var text = boundedHostText("Text");
|
|
2864
2995
|
var featureId = host.string().max(MAX_SESSION_ID_LENGTH).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
|
|
2865
2996
|
var operationId = host.string().min(1).max(128).regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]*$/);
|
|
2997
|
+
var reviewAssignmentId = host.string().min(1).max(256);
|
|
2866
2998
|
var revision = host.number().int().safe().nonnegative();
|
|
2867
2999
|
var guard = { operationId, expectedRevision: revision };
|
|
2868
3000
|
var artifact = host.object({
|
|
@@ -2909,7 +3041,10 @@ var StatusArgs = {
|
|
|
2909
3041
|
host.object({ view: host.literal("compact") }).strict(),
|
|
2910
3042
|
host.object({ view: host.literal("detail") }).strict(),
|
|
2911
3043
|
host.object({ view: host.literal("execution") }).strict(),
|
|
2912
|
-
host.object({
|
|
3044
|
+
host.object({
|
|
3045
|
+
view: host.literal("reviewer"),
|
|
3046
|
+
assignmentId: reviewAssignmentId
|
|
3047
|
+
}).strict()
|
|
2913
3048
|
])
|
|
2914
3049
|
};
|
|
2915
3050
|
var PlanSaveArgs = {
|
|
@@ -2942,7 +3077,7 @@ var FeatureCompleteArgs = {
|
|
|
2942
3077
|
request: host.object({
|
|
2943
3078
|
...guard,
|
|
2944
3079
|
featureId,
|
|
2945
|
-
assignmentId:
|
|
3080
|
+
assignmentId: reviewAssignmentId,
|
|
2946
3081
|
summary: text,
|
|
2947
3082
|
result: reviewResult
|
|
2948
3083
|
}).strict()
|
|
@@ -2984,6 +3119,12 @@ function executeMutation(context, validation, handler) {
|
|
|
2984
3119
|
validation.cancel(context.sessionID);
|
|
2985
3120
|
return execute(context, handler);
|
|
2986
3121
|
}
|
|
3122
|
+
function executeReviewerMutation(context, handler, replayHandler) {
|
|
3123
|
+
if (context.agent !== "flow-reviewer") {
|
|
3124
|
+
return execute(context, replayHandler);
|
|
3125
|
+
}
|
|
3126
|
+
return execute(context, handler);
|
|
3127
|
+
}
|
|
2987
3128
|
function createTools(_ctx, options) {
|
|
2988
3129
|
return {
|
|
2989
3130
|
flow_guidance: tool({
|
|
@@ -3039,9 +3180,9 @@ function createTools(_ctx, options) {
|
|
|
3039
3180
|
execute: (args, context) => executeMutation(context, options.validation, (workspace) => flowReviewStart(workspace, args))
|
|
3040
3181
|
}),
|
|
3041
3182
|
flow_feature_complete: tool({
|
|
3042
|
-
description: "
|
|
3183
|
+
description: "Submit a pending review result; only the reviewer may create a new completion, while exact accepted requests remain replayable for an active Session v5 workflow.",
|
|
3043
3184
|
args: FeatureCompleteArgs,
|
|
3044
|
-
execute: (args, context) =>
|
|
3185
|
+
execute: (args, context) => executeReviewerMutation(context, (workspace) => flowFeatureComplete(workspace, args), (workspace) => flowFeatureCompleteReplay(workspace, args))
|
|
3045
3186
|
}),
|
|
3046
3187
|
flow_feature_reset: tool({
|
|
3047
3188
|
description: "Reset a feature and dependents for a fresh full retry.",
|
|
@@ -3307,4 +3448,4 @@ export {
|
|
|
3307
3448
|
plugin_default as default
|
|
3308
3449
|
};
|
|
3309
3450
|
|
|
3310
|
-
//# debugId=
|
|
3451
|
+
//# debugId=6D57AB8590A9BCD164756E2164756E21
|