opencode-plugin-flow 8.0.0 → 8.1.0

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 CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  One short entry per release, written for users deciding whether to upgrade.
4
4
 
5
+ ## [8.1.0] - 2026-08-19
6
+
7
+ Inspect surveys can finish with blockers, and `/flow-auto` hands back a findings list.
8
+
9
+ - Compact `flow_status` and close delivery now carry a derived `findingsDigest`
10
+ over every identified finding across attempts. `/flow-auto` prints that list
11
+ when it parks or stops instead of going silent after a blocked review.
12
+ - **Session v5 schema:** `PlanFeature.kind` is optional `change` or `inspect`.
13
+ Absent is `change`. A failed inspect review completes that slice so later
14
+ features can start without `flow_feature_reset`. `completed` then means the
15
+ survey finished, not that the tree is clean. Existing documents keep the
16
+ repair loop.
17
+
18
+ Install or update:
19
+
20
+ ```bash
21
+ opencode plugin opencode-plugin-flow@8.1.0 --global --force
22
+ ```
23
+
5
24
  ## [8.0.0] - 2026-08-18
6
25
 
7
26
  One evidence record, a thinner run loop, and a freeze on further declarations.
package/README.md CHANGED
@@ -40,7 +40,7 @@ expensive, and it is overhead when it is not.
40
40
  Install the exact npm release through OpenCode:
41
41
 
42
42
  ```bash
43
- opencode plugin opencode-plugin-flow@8.0.0 --global --force
43
+ opencode plugin opencode-plugin-flow@8.1.0 --global --force
44
44
  ```
45
45
 
46
46
  Omit `--global` for project scope. Version pins are exact and never update on
@@ -51,7 +51,7 @@ The equivalent manual project configuration is:
51
51
  ```json
52
52
  {
53
53
  "$schema": "https://opencode.ai/config.json",
54
- "plugin": ["opencode-plugin-flow@8.0.0"]
54
+ "plugin": ["opencode-plugin-flow@8.1.0"]
55
55
  }
56
56
  ```
57
57
 
package/dist/index.js CHANGED
@@ -109,7 +109,8 @@ If a Flow tool is absent, report an incomplete plugin load; never simulate state
109
109
  ## Recovery
110
110
 
111
111
  On interruption, read compact status; load \`flow-run\` for an active or blocked feature
112
- and use execution or reviewer status, never prose, for lifecycle truth.
112
+ and use execution or reviewer status, never prose, for lifecycle truth. At a
113
+ checkpoint or lease stop, print compact \`findingsDigest\` before any delivery map.
113
114
 
114
115
  Summaries keep plan/source IDs \`verified\` or \`incomplete\`. A prior finding is
115
116
  terminally \`fixed\` only when review passes with current evidence. A failed
@@ -198,7 +199,8 @@ Save one plan with:
198
199
  gate is the command every broad observation must run; a failed or
199
200
  claimed-broad gate still vetoes review.
200
201
  - \`features\`: ordered outcome slices, each with a stable \`id\`, \`title\`,
201
- \`summary\`, bounded \`targets\`, concrete \`validation\`, and \`dependsOn\` ids.
202
+ \`summary\`, bounded \`targets\`, concrete \`validation\`, \`dependsOn\` ids, and
203
+ optional \`kind\`.
202
204
 
203
205
  Each feature needs one observable outcome judgeable from bounded evidence and
204
206
  focused validation. Split only independent failures or true dependencies; file
@@ -213,6 +215,13 @@ outcome and its evidence.
213
215
  A \`validation\` entry naming a command is recorded byte-for-byte; prose there stays
214
216
  reviewer judgment, never a fabricated result.
215
217
 
218
+ If the request is inspect-only (review, audit, survey, no promised edit), invent
219
+ no repair features. Save at most a small set of inspect features with
220
+ \`kind: "inspect"\`, whose \`validation\` is reviewer inspection, whose \`targets\`
221
+ are existing paths, and whose \`decisions\` state that no source edit is
222
+ authorized. The gate may be the repo's existing check. Ask before turning an
223
+ inspect request into repairs.
224
+
216
225
  Before saving, confirm:
217
226
 
218
227
  - every requirement maps to a feature or an explicit non-goal;
@@ -277,17 +286,15 @@ the manager's summary. Check that:
277
286
  - changed behavior is correct at public and downstream call sites;
278
287
  - validation is strong enough for the behavior and main failure modes;
279
288
  - scope did not drift and unrelated user work was preserved;
280
- - relevant adjacent states, failure/cleanup ordering, repetition, retry,
281
- interruption, reentrancy, concurrency, and overlapping invariants still work;
282
289
  - the feature's actual base diff includes no unexplained deletion, rename, file
283
290
  type, generated artifact, or executable/file-mode change; and
284
291
  - persistence, concurrency, security, migration, compatibility, package, UI,
285
292
  and recovery risks were examined when relevant.
286
293
 
287
- Finish the supplied feature-specific risk checklist, represented by a bounded
288
- matrix for concurrency or state-machine work. Continue that matrix after finding
289
- one blocker so independently detectable interleavings arrive in the same review
290
- cohort.
294
+ Finish the supplied feature-specific risk checklist. Continue a concurrency or
295
+ state-machine matrix after one blocker only when \`assignment.packet.riskLenses\`
296
+ is non-empty or the packet summary includes a matrix. Otherwise inspect the
297
+ changed artifacts, the supplied validation, and live \`priorFindings\`.
291
298
 
292
299
  Scope plan/source IDs by assignment kind. An ordinary feature review records
293
300
  dispositions only for IDs mapped to the active feature or explicitly supplied
@@ -311,8 +318,8 @@ not a failure; a missing or conflicting baseline fact, or a material mode,
311
318
  platform, race, or failure-path claim without proof, is.
312
319
 
313
320
  Flow deliberately projects no raw command output; use the durable command, exit
314
- code, completeness, digest, source binding, and your workspace inspection. A weak or
315
- unclear coverage claim is an evidence gap.
321
+ code, completeness, digest, source binding, and your workspace inspection. A weak
322
+ coverage claim is an evidence gap.
316
323
 
317
324
  For a final assignment, also inspect broad validation and confirm docs,
318
325
  commands, package surfaces, and remaining gaps are consistent with completion.
@@ -326,8 +333,8 @@ IDs in summary or evidence.
326
333
 
327
334
  Report every problem you find. Severity is a routing decision the runtime acts
328
335
  on, not a filter on what to mention: \`blocking\` when the issue invalidates the
329
- approved outcome, \`advisory\` otherwise. When you are unsure, report it as
330
- \`advisory\` rather than omitting it.
336
+ approved outcome, \`advisory\` otherwise. If unsure, report it as
337
+ \`advisory\` rather than omitting it. Inspect kind completes with blockers.
331
338
 
332
339
  Set \`scopeBlocker: true\` on a blocking finding whose repair requires material
333
340
  work outside the approved plan, and identify the boundary in \`evidence\`. The
@@ -515,7 +522,8 @@ feature.
515
522
 
516
523
  ### Blocked review
517
524
 
518
- Follow \`nextAction\` with the one detail projection. The runtime already weighs
525
+ Follow \`nextAction\` with the one detail projection for routing, then print
526
+ compact \`findingsDigest\` as the user-facing list. The runtime already weighs
519
527
  \`failedReviewCount\` and \`blockedFeature.scopeBlocker\`.
520
528
 
521
529
  - Ready \`await-user-direction\` has no blocked run left to reset. Identify the
@@ -534,8 +542,8 @@ Follow \`nextAction\` with the one detail projection. The runtime already weighs
534
542
  feature, pass that exact \`featureId\` as \`nextFeatureId\` on
535
543
  \`flow_feature_reset\`.
536
544
 
537
- Direct \`/flow-run\` reports this one feature's cumulative outcome and
538
- \`nextAction\`, then stops. Under \`/flow-auto\`, return to its lifecycle loop.
545
+ Direct \`/flow-run\` reports compact \`findingsDigest\` and \`nextAction\`, then
546
+ stops. Under \`/flow-auto\`, return to its lifecycle loop.
539
547
  `;
540
548
 
541
549
  // src/guidance/ids.ts
@@ -560,7 +568,9 @@ var FLOW_MANAGER_KERNEL = [
560
568
  ].join(" "),
561
569
  [
562
570
  "- Make one automatic fresh full retry only when the projected `nextAction`",
563
- "is `flow_feature_reset`; otherwise checkpoint."
571
+ "is `flow_feature_reset`; otherwise checkpoint.",
572
+ "On `await-user-direction` or a lease stop, print compact `findingsDigest`",
573
+ "without inventing ids."
564
574
  ].join(" "),
565
575
  [
566
576
  "- Before review, require current-source evidence appropriate to the changed outcome,",
@@ -972,6 +982,9 @@ function planEvidence(plan) {
972
982
  function planGate(plan) {
973
983
  return planEvidence(plan).find((entry) => entry.scope === "gate")?.command;
974
984
  }
985
+ function featureKind(feature) {
986
+ return feature?.kind === "inspect" ? "inspect" : "change";
987
+ }
975
988
  function reviewResultSemanticIssues(result) {
976
989
  const issues = [];
977
990
  const blocking = result.findings.some((finding) => finding.severity === "blocking");
@@ -1713,6 +1726,7 @@ function completeFeature(session, input) {
1713
1726
  }
1714
1727
  }
1715
1728
  const findings = assignFindingIds(input.result.findings, findingIdPrefix(run.featureId, assignment.createdRevision));
1729
+ const inspect = featureKind(session.plan?.features.find((feature) => feature.id === run.featureId)) === "inspect";
1716
1730
  const next = commit(session, "feature-complete", input.operationId, input, (draft, revision) => ({
1717
1731
  ...draft,
1718
1732
  runs: draft.runs.map((item) => {
@@ -1720,7 +1734,7 @@ function completeFeature(session, input) {
1720
1734
  return item;
1721
1735
  return {
1722
1736
  ...item,
1723
- state: input.result.verdict === "passed" ? "completed" : "blocked",
1737
+ state: input.result.verdict === "passed" || inspect ? "completed" : "blocked",
1724
1738
  summary: input.summary,
1725
1739
  reviews: item.reviews.map((review) => review.id === assignment.id ? {
1726
1740
  ...review,
@@ -1815,7 +1829,7 @@ function closeSession(session, input) {
1815
1829
  fail("sessionId does not match active state.");
1816
1830
  assertMutable(session);
1817
1831
  if (input.kind === "completed" && sessionStatus(session) !== "completed") {
1818
- fail("A completed close requires every planned feature to pass review.");
1832
+ fail("A completed close requires every planned feature to be complete.");
1819
1833
  }
1820
1834
  if (input.kind === "completed") {
1821
1835
  const unsatisfied = unsatisfiedExtraEvidence(session);
@@ -1875,8 +1889,14 @@ class ArchiveCollisionError extends Error {
1875
1889
  import { z } from "zod";
1876
1890
 
1877
1891
  // src/domain/session-invariants.ts
1878
- function featurePassedBefore(session, featureId, revision) {
1879
- return session.runs.some((run) => run.featureId === featureId && run.reviews.some((review) => review.result?.verdict === "passed" && review.result.recordedRevision < revision));
1892
+ function featureSettledBefore(session, featureId, revision) {
1893
+ const inspect = featureKind(session.plan?.features.find((feature) => feature.id === featureId)) === "inspect";
1894
+ return session.runs.some((run) => run.featureId === featureId && run.reviews.some((review) => {
1895
+ const result = review.result;
1896
+ if (!result || result.recordedRevision >= revision)
1897
+ return false;
1898
+ return result.verdict === "passed" || inspect;
1899
+ }));
1880
1900
  }
1881
1901
  function sessionInvariantIssues(session) {
1882
1902
  const issues = [];
@@ -1993,7 +2013,7 @@ function sessionInvariantIssues(session) {
1993
2013
  if (referenced.some((validation) => validation.recordedRevision >= review.createdRevision)) {
1994
2014
  issues.push(`Review '${review.id}' references later validation.`);
1995
2015
  }
1996
- const expectedKind = session.plan.features.every((feature) => feature.id === run.featureId || featurePassedBefore(session, feature.id, review.createdRevision)) ? "final" : "feature";
2016
+ const expectedKind = session.plan.features.every((feature) => feature.id === run.featureId || featureSettledBefore(session, feature.id, review.createdRevision)) ? "final" : "feature";
1997
2017
  if (review.kind !== expectedKind) {
1998
2018
  issues.push(`Review '${review.id}' has the wrong derived kind.`);
1999
2019
  }
@@ -2011,7 +2031,10 @@ function sessionInvariantIssues(session) {
2011
2031
  issues.push(`Active run '${run.id}' contains a recorded outcome.`);
2012
2032
  }
2013
2033
  if (run.state === "completed" && last?.result?.verdict !== "passed") {
2014
- issues.push(`Completed run '${run.id}' lacks a passing review.`);
2034
+ const inspect = featureKind(session.plan?.features.find((feature) => feature.id === run.featureId)) === "inspect";
2035
+ if (!(inspect && last?.result?.verdict === "failed")) {
2036
+ issues.push(`Completed run '${run.id}' lacks a passing review.`);
2037
+ }
2015
2038
  }
2016
2039
  if (run.state === "blocked" && last?.result?.verdict !== "failed") {
2017
2040
  issues.push(`Blocked run '${run.id}' lacks a failed review.`);
@@ -2050,7 +2073,8 @@ var PlanFeatureSchema = z.object({
2050
2073
  summary: boundedText("Feature summary"),
2051
2074
  targets: z.array(boundedText("Feature target")).max(MAX_PLAN_FEATURES).default([]),
2052
2075
  validation: z.array(boundedText("Feature validation")).max(MAX_PLAN_FEATURES).default([]),
2053
- dependsOn: z.array(FeatureIdSchema).max(MAX_PLAN_FEATURES).default([])
2076
+ dependsOn: z.array(FeatureIdSchema).max(MAX_PLAN_FEATURES).default([]),
2077
+ kind: z.enum(["change", "inspect"]).optional()
2054
2078
  }).strict();
2055
2079
  var EvidenceEntrySchema = z.object({
2056
2080
  requirement: boundedText("Evidence requirement"),
@@ -2260,6 +2284,69 @@ var StatusInputSchema = z.object({
2260
2284
  ])
2261
2285
  }).strict();
2262
2286
 
2287
+ // src/application/findings-digest.ts
2288
+ function plannedFeatureIds(session) {
2289
+ if (session.plan)
2290
+ return session.plan.features.map((feature) => feature.id);
2291
+ const ids = [];
2292
+ for (const run of session.runs) {
2293
+ if (ids.includes(run.featureId))
2294
+ continue;
2295
+ ids.push(run.featureId);
2296
+ }
2297
+ return ids;
2298
+ }
2299
+ function findingsDigest(session) {
2300
+ const rows = [];
2301
+ const indexById = new Map;
2302
+ for (const featureId of plannedFeatureIds(session)) {
2303
+ for (const run of session.runs) {
2304
+ if (run.featureId !== featureId)
2305
+ continue;
2306
+ for (const review of run.reviews) {
2307
+ const result = review.result;
2308
+ if (!result)
2309
+ continue;
2310
+ for (const finding of result.findings) {
2311
+ if (!finding.findingId)
2312
+ continue;
2313
+ const row = {
2314
+ featureId,
2315
+ findingId: finding.findingId,
2316
+ severity: finding.severity,
2317
+ summary: finding.summary,
2318
+ attempt: run.attempt,
2319
+ verdict: result.verdict,
2320
+ live: false,
2321
+ ...finding.evidence === undefined ? {} : { evidence: finding.evidence }
2322
+ };
2323
+ const existing = indexById.get(finding.findingId);
2324
+ if (existing === undefined) {
2325
+ indexById.set(finding.findingId, rows.length);
2326
+ rows.push(row);
2327
+ } else {
2328
+ rows[existing] = row;
2329
+ }
2330
+ }
2331
+ }
2332
+ }
2333
+ }
2334
+ return rows.map((row) => ({
2335
+ ...row,
2336
+ live: liveFindingIds(session, row.featureId).includes(row.findingId)
2337
+ }));
2338
+ }
2339
+ function digestReportLines(digest) {
2340
+ if (digest.length === 0)
2341
+ return ["Findings digest: none"];
2342
+ const line = (row, kind) => `- ${kind} ${row.featureId} ${row.findingId} ${row.severity}: ${row.summary}`;
2343
+ return [
2344
+ "Findings digest:",
2345
+ ...digest.filter((row) => row.live).map((row) => line(row, "live")),
2346
+ ...digest.filter((row) => !row.live).map((row) => line(row, "historical"))
2347
+ ];
2348
+ }
2349
+
2263
2350
  // src/application/delivery.ts
2264
2351
  var LIMITATIONS = [
2265
2352
  "Artifact paths and the canonical gate are caller declarations; Flow validates binding, not completeness or fitness.",
@@ -2340,6 +2427,7 @@ function formatReport(delivery) {
2340
2427
  `Progress: ${delivery.progress.completed} of ${delivery.progress.total} features complete`,
2341
2428
  "Features:",
2342
2429
  ...lines,
2430
+ ...digestReportLines(delivery.findingsDigest),
2343
2431
  `Assurance: ${delivery.assurance.conclusion.replaceAll("-", " ")}`,
2344
2432
  "Assurance checks:",
2345
2433
  ...delivery.assurance.checks.map((item) => `- ${item.status} [${TIER_LABELS[item.tier]}] ${item.label}: ${item.explanation}`),
@@ -2361,6 +2449,7 @@ function deliveryProjection(session) {
2361
2449
  const latest = grouped.flatMap(({ runs }) => runs.slice(-1));
2362
2450
  const latestArtifacts = new Set(latest.flatMap((run) => run.artifactsChanged.map((item) => item.path)));
2363
2451
  const allArtifacts = new Set(session.runs.flatMap((run) => run.artifactsChanged.map((item) => item.path)));
2452
+ const digest = findingsDigest(session);
2364
2453
  const delivery = {
2365
2454
  goal: session.goal,
2366
2455
  closure: { kind: session.closure.kind, summary: session.closure.summary },
@@ -2376,17 +2465,15 @@ function deliveryProjection(session) {
2376
2465
  attempts: runs.length,
2377
2466
  latestState: run?.state ?? "not-started",
2378
2467
  outcomeSummary: run?.summary ?? null,
2379
- terminalFindings: run?.reviews.at(-1)?.result?.findings.map(({ severity, summary }) => ({
2380
- severity,
2381
- summary
2382
- })) ?? []
2468
+ terminalFindings: digest.filter((row) => row.featureId === feature.id && row.live).map(({ severity, summary }) => ({ severity, summary }))
2383
2469
  };
2384
2470
  }),
2385
2471
  reportedArtifacts: {
2386
2472
  latestAttempts: [...latestArtifacts].sort(),
2387
2473
  supersededAttemptsOnly: [...allArtifacts].filter((path) => !latestArtifacts.has(path)).sort()
2388
2474
  },
2389
- assurance: assuranceProjection(session)
2475
+ assurance: assuranceProjection(session),
2476
+ findingsDigest: digest
2390
2477
  };
2391
2478
  return { ...delivery, report: formatReport(delivery) };
2392
2479
  }
@@ -2462,7 +2549,8 @@ function compactProjection(session, pendingReviewSourceStale = false) {
2462
2549
  blockedFeature,
2463
2550
  progress: featureProgress(session),
2464
2551
  nextAction: nextAction(session, pendingReviewSourceStale, blockedFeature),
2465
- archiveRetry: retryRequest ? { request: retryRequest } : null
2552
+ archiveRetry: retryRequest ? { request: retryRequest } : null,
2553
+ findingsDigest: findingsDigest(session)
2466
2554
  };
2467
2555
  }
2468
2556
  function archivedProjection(session) {
@@ -2550,7 +2638,8 @@ function idleProjection(view) {
2550
2638
  view,
2551
2639
  status: "idle",
2552
2640
  revision: 0,
2553
- nextAction: "flow_plan_save"
2641
+ nextAction: "flow_plan_save",
2642
+ findingsDigest: []
2554
2643
  };
2555
2644
  }
2556
2645
  function project(session, request, pendingReviewSourceStale = false) {
@@ -3801,6 +3890,10 @@ var CONTINUATION_ROUTE = [
3801
3890
  "for a fresh close use compact session id/revision plus a fresh operation id,",
3802
3891
  "and replay archiveRetry exactly from its projected request."
3803
3892
  ].join(" ");
3893
+ var HANDBACK_ROUTE = [
3894
+ "Call flow_status with the compact view first.",
3895
+ "Print findingsDigest as the user-facing list. Do not invent ids."
3896
+ ].join(" ");
3804
3897
  function inspectMessage(parts) {
3805
3898
  let token = null;
3806
3899
  let text = "";
@@ -3826,6 +3919,9 @@ function isCheckpoint(projection) {
3826
3919
  function isPendingReviewer(projection) {
3827
3920
  return projection.status === "running" && projection.nextAction === "dispatch-flow-reviewer";
3828
3921
  }
3922
+ function isHandback(projection) {
3923
+ return projection.status === "blocked" || projection.nextAction === "flow_feature_reset" || projection.nextAction === "dispatch-flow-reviewer";
3924
+ }
3829
3925
 
3830
3926
  class AutoDriveCoordinator {
3831
3927
  #lease = null;
@@ -3875,6 +3971,31 @@ class AutoDriveCoordinator {
3875
3971
  lease.lastPromptedRevision = null;
3876
3972
  this.#setTiming("waiting-for-user");
3877
3973
  }
3974
+ async#promptHandback(lease, projection) {
3975
+ if (!isHandback(projection))
3976
+ return;
3977
+ if (lease.handbackPromptedRevision === projection.revision)
3978
+ return;
3979
+ if (!lease.delivery)
3980
+ return;
3981
+ lease.handbackPromptedRevision = projection.revision;
3982
+ lease.messageId = null;
3983
+ this.#setTiming("active");
3984
+ lease.inFlight = "prompt";
3985
+ try {
3986
+ const handback = [
3987
+ `Flow is handing control back at compact revision ${projection.revision}.`,
3988
+ HANDBACK_ROUTE,
3989
+ `Then follow ${projection.nextAction} or stop at await-user-direction.`,
3990
+ "Do not expand the approved goal."
3991
+ ].join(" ");
3992
+ await this.#options.prompt(lease.hostSessionId, `${handback}
3993
+
3994
+ ${FLOW_MANAGER_KERNEL}`, lease.delivery, { [FLOW_AUTO_METADATA_KEY]: lease.token });
3995
+ } catch (error) {
3996
+ this.#stop(lease, `Flow auto prompt failed: ${String(error)}`);
3997
+ }
3998
+ }
3878
3999
  async#read(lease) {
3879
4000
  try {
3880
4001
  return await this.#options.readProjection();
@@ -3897,6 +4018,7 @@ class AutoDriveCoordinator {
3897
4018
  baseline: null,
3898
4019
  delivery: null,
3899
4020
  lastPromptedRevision: null,
4021
+ handbackPromptedRevision: null,
3900
4022
  checkpoint: null,
3901
4023
  pendingReply: false,
3902
4024
  inFlight: null,
@@ -4093,8 +4215,12 @@ ${FLOW_MANAGER_KERNEL}`;
4093
4215
  const mutationAdvanced = advance !== undefined && projection.revision === advance && isMechanical(projection);
4094
4216
  if (lease.pendingReply) {
4095
4217
  lease.pendingReply = false;
4096
- if (boundary && (!checkpoint || projection.revision > checkpoint.revision))
4218
+ if (boundary && (!checkpoint || projection.revision > checkpoint.revision)) {
4219
+ await this.#promptHandback(lease, projection);
4220
+ if (this.#lease !== lease)
4221
+ return;
4097
4222
  return void this.#waitAt(lease, projection.revision);
4223
+ }
4098
4224
  if (!checkpoint || !boundary && !mutationAdvanced)
4099
4225
  return void this.deactivate(hostSessionId);
4100
4226
  checkpoint.answered = true;
@@ -4104,15 +4230,27 @@ ${FLOW_MANAGER_KERNEL}`;
4104
4230
  if (boundary) {
4105
4231
  if (checkpoint && projection.revision < checkpoint.revision)
4106
4232
  return void this.deactivate(hostSessionId);
4233
+ await this.#promptHandback(lease, projection);
4234
+ if (this.#lease !== lease)
4235
+ return;
4107
4236
  return void this.#waitAt(lease, projection.revision);
4108
4237
  }
4238
+ if (!isMechanical(projection)) {
4239
+ const already = lease.handbackPromptedRevision === projection.revision;
4240
+ await this.#promptHandback(lease, projection);
4241
+ if (this.#lease !== lease)
4242
+ return;
4243
+ if (!already && lease.handbackPromptedRevision === projection.revision) {
4244
+ this.#setTiming("paused");
4245
+ return;
4246
+ }
4247
+ return void this.deactivate(hostSessionId);
4248
+ }
4109
4249
  if (checkpoint) {
4110
4250
  if (projection.revision <= checkpoint.revision || !mutationAdvanced)
4111
4251
  return void this.deactivate(hostSessionId);
4112
4252
  lease.checkpoint = null;
4113
4253
  }
4114
- if (!isMechanical(projection))
4115
- return void this.deactivate(hostSessionId);
4116
4254
  if (lease.lastPromptedRevision === projection.revision) {
4117
4255
  this.#setTiming("paused");
4118
4256
  return this.#warn(`Flow auto-drive paused after revision ${projection.revision} made no lifecycle progress.`);
@@ -4380,7 +4518,8 @@ var planFeature = host.object({
4380
4518
  summary: text,
4381
4519
  targets: host.array(text).max(MAX_PLAN_FEATURES).default([]),
4382
4520
  validation: host.array(text).max(MAX_PLAN_FEATURES).default([]),
4383
- dependsOn: host.array(featureId).max(MAX_PLAN_FEATURES).default([])
4521
+ dependsOn: host.array(featureId).max(MAX_PLAN_FEATURES).default([]),
4522
+ kind: host.enum(["change", "inspect"]).optional()
4384
4523
  }).strict();
4385
4524
  var plan = host.object({
4386
4525
  summary: text,
@@ -5023,4 +5162,4 @@ export {
5023
5162
  plugin_default as default
5024
5163
  };
5025
5164
 
5026
- //# debugId=84B9887B14A5141164756E2164756E21
5165
+ //# debugId=4CB19287BD614D3B64756E2164756E21