opencode-plugin-flow 6.0.0 → 6.2.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 +36 -0
- package/README.md +65 -113
- package/dist/index.js +174 -116
- package/dist/index.js.map +12 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,8 +7,10 @@ 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
|
-
|
|
10
|
+
the session, every state-changing \`flow_*\` call, integration, validation,
|
|
11
|
+
review dispatch, reset, and closure. Bounded \`flow-worker\` instances may
|
|
12
|
+
contribute disjoint work inside the active feature. The reserved \`flow-reviewer\`
|
|
13
|
+
independently reviews; it never edits or mutates Flow state.
|
|
12
14
|
|
|
13
15
|
## Route from status
|
|
14
16
|
|
|
@@ -32,7 +34,8 @@ report that the plugin is not fully loaded instead of simulating state changes.
|
|
|
32
34
|
|
|
33
35
|
- Approved plans do not change. Reset affected work or close the session before
|
|
34
36
|
changing direction.
|
|
35
|
-
- Only one feature
|
|
37
|
+
- Only one durable feature run is active at a time. Conversation-local worker
|
|
38
|
+
waves do not create additional runs or Flow state.
|
|
36
39
|
- Work stays inside the active feature and preserves unrelated user changes.
|
|
37
40
|
- A passing feature needs successful current-source validation and one
|
|
38
41
|
independent reviewer assignment. The final feature uses broad validation and
|
|
@@ -199,9 +202,10 @@ description: Implement, validate, independently review, and record one approved
|
|
|
199
202
|
|
|
200
203
|
# Flow Run
|
|
201
204
|
|
|
202
|
-
Work on exactly one approved feature. The root manager owns
|
|
203
|
-
state-changing \`flow_*\` call
|
|
204
|
-
|
|
205
|
+
Work on exactly one approved feature. The root manager owns the session, every
|
|
206
|
+
state-changing \`flow_*\` call, integration, validation, review dispatch, reset,
|
|
207
|
+
and closure. Bounded \`flow-worker\` instances may contribute disjoint work; the
|
|
208
|
+
reserved \`flow-reviewer\` owns the independent review.
|
|
205
209
|
|
|
206
210
|
## Start and scope
|
|
207
211
|
|
|
@@ -227,9 +231,38 @@ normally be one stable Markdown artifact; JSON requires an explicit request.
|
|
|
227
231
|
Do not stage, commit, push, publish, or mutate releases unless the user asks for
|
|
228
232
|
that separate action.
|
|
229
233
|
|
|
234
|
+
## Bounded worker waves
|
|
235
|
+
|
|
236
|
+
Work serially by default. After manager orientation, fan out only when at least
|
|
237
|
+
two genuinely independent slices can be named. Run one cohort of two or three
|
|
238
|
+
\`flow-worker\` instances at a time. Issue every cohort Task call in the same
|
|
239
|
+
assistant tool-use turn before consuming any result. If the host or model
|
|
240
|
+
serializes those calls, treat and report that execution as serial instead of
|
|
241
|
+
claiming parallelism. Each prompt must name a stable slice id, the exact outcome
|
|
242
|
+
and read or write scope, expected coverage, recommended manager checks,
|
|
243
|
+
dependencies, and a stop condition. Edit scopes must be exact and
|
|
244
|
+
non-overlapping. Shared contracts, lockfiles, and generated outputs remain
|
|
245
|
+
manager-owned unless one worker receives the whole relevant scope.
|
|
246
|
+
|
|
247
|
+
Workers cannot call Flow tools or spawn children. Each returns one concise
|
|
248
|
+
handoff containing status, scope and coverage, evidence or changed paths,
|
|
249
|
+
recommended manager checks, gaps and risks, and integration notes. Workers do
|
|
250
|
+
not run Bash; all executable checks remain manager-owned. Missing, partial, or
|
|
251
|
+
blocked output remains an explicit coverage gap.
|
|
252
|
+
|
|
253
|
+
After all workers stop, compare actual changed paths with every assigned scope,
|
|
254
|
+
then inspect the combined diff and evidence and reconcile unexpected paths or
|
|
255
|
+
conflicts before validation. At most one targeted follow-up wave may address a
|
|
256
|
+
failed slice, newly unlocked dependency, or material claim verification. Do
|
|
257
|
+
not start an automatic third wave. Coordination stays in the conversation:
|
|
258
|
+
create no manifest, sidecar, Session field, durable handoff, or recovery ledger.
|
|
259
|
+
After an interruption, inspect Flow status and the worktree and treat partial
|
|
260
|
+
worker edits as untrusted.
|
|
261
|
+
|
|
230
262
|
## Validate
|
|
231
263
|
|
|
232
|
-
|
|
264
|
+
Only validate after every worker has stopped and integration is settled. Choose
|
|
265
|
+
checks from the changed behavior and risk:
|
|
233
266
|
|
|
234
267
|
- Prefer focused behavioral tests that would fail without the change.
|
|
235
268
|
- Cover persistence, integration, API, browser, accessibility, package, or
|
|
@@ -307,6 +340,44 @@ if (FLOW_GUIDANCE_BY_ID.size !== FLOW_GUIDANCE_IDS.length) {
|
|
|
307
340
|
}
|
|
308
341
|
|
|
309
342
|
// src/prompt-surfaces.ts
|
|
343
|
+
var FLOW_WORKER_PROMPT = `# Flow bounded worker
|
|
344
|
+
|
|
345
|
+
You are one hidden Flow worker supporting the root manager inside one active feature. Own only the single slice explicitly assigned by that manager. Preserve all unrelated work and do not broaden the assignment.
|
|
346
|
+
|
|
347
|
+
You may run concurrently with sibling workers. Do not enter their scopes, assume their results, or revert changes you did not make.
|
|
348
|
+
|
|
349
|
+
## Scope and authority
|
|
350
|
+
|
|
351
|
+
- Use the manager assignment as your only source of Flow lifecycle context. Do not call any \`flow_*\` tool, including \`flow_status\`.
|
|
352
|
+
- Do not delegate, spawn subtasks, or load skills.
|
|
353
|
+
- Do not stage, commit, push, publish, or create a release.
|
|
354
|
+
- Do not run Bash commands. The manager owns every executable check.
|
|
355
|
+
- Never edit .flow or .git metadata paths; the host denies those paths.
|
|
356
|
+
- A read-only evidence slice must not edit files.
|
|
357
|
+
- An implementation slice may edit only the exact, non-overlapping write paths explicitly assigned by the manager. If required work would escape those paths, stop and return a partial or blocked handoff instead of expanding scope.
|
|
358
|
+
- Use only non-shell inspection relevant to the assigned slice. The manager owns integration, focused checks, and authoritative combined validation after all workers have stopped.
|
|
359
|
+
|
|
360
|
+
## Handoff
|
|
361
|
+
|
|
362
|
+
Return exactly one concise handoff using this structure:
|
|
363
|
+
|
|
364
|
+
## Status
|
|
365
|
+
success | partial | blocked
|
|
366
|
+
|
|
367
|
+
## Scope & coverage
|
|
368
|
+
- Assigned slice and what was covered
|
|
369
|
+
|
|
370
|
+
## Findings / changed paths
|
|
371
|
+
- Evidence found or exact paths changed
|
|
372
|
+
|
|
373
|
+
## Recommended manager checks
|
|
374
|
+
- Exact checks the manager should run, or none
|
|
375
|
+
|
|
376
|
+
## Gaps & risks
|
|
377
|
+
- Missing coverage, blockers, conflicts, or none
|
|
378
|
+
|
|
379
|
+
## Integration notes
|
|
380
|
+
- What the manager must verify or integrate, or none`;
|
|
310
381
|
function skillBody(id) {
|
|
311
382
|
return getFlowGuidance(id).content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "").trim();
|
|
312
383
|
}
|
|
@@ -351,6 +422,8 @@ function compileFlowPromptSurface(surface) {
|
|
|
351
422
|
`);
|
|
352
423
|
case "flow-reviewer":
|
|
353
424
|
return skillBody("flow-review");
|
|
425
|
+
case "flow-worker":
|
|
426
|
+
return FLOW_WORKER_PROMPT;
|
|
354
427
|
default: {
|
|
355
428
|
const unsupported = surface;
|
|
356
429
|
throw new Error(`Unsupported Flow prompt surface '${unsupported}'.`);
|
|
@@ -374,6 +447,26 @@ var FLOW_CORE_AGENTS = {
|
|
|
374
447
|
"flow_*": "deny",
|
|
375
448
|
flow_status: "allow"
|
|
376
449
|
}
|
|
450
|
+
},
|
|
451
|
+
"flow-worker": {
|
|
452
|
+
mode: "subagent",
|
|
453
|
+
hidden: true,
|
|
454
|
+
description: "Bounded worker for one read-only evidence or exact-scope implementation slice.",
|
|
455
|
+
prompt: compileFlowPromptSurface("flow-worker"),
|
|
456
|
+
permission: {
|
|
457
|
+
edit: {
|
|
458
|
+
"*": "allow",
|
|
459
|
+
".flow": "deny",
|
|
460
|
+
".flow/**": "deny",
|
|
461
|
+
".git": "deny",
|
|
462
|
+
".git/**": "deny"
|
|
463
|
+
},
|
|
464
|
+
bash: "deny",
|
|
465
|
+
external_directory: "deny",
|
|
466
|
+
skill: "deny",
|
|
467
|
+
task: { "*": "deny" },
|
|
468
|
+
"flow_*": "deny"
|
|
469
|
+
}
|
|
377
470
|
}
|
|
378
471
|
};
|
|
379
472
|
var FLOW_CORE_COMMANDS = {
|
|
@@ -432,6 +525,13 @@ function createFlowCoreConfigEntries(options) {
|
|
|
432
525
|
...FLOW_CORE_AGENTS["flow-reviewer"].permission,
|
|
433
526
|
task: { "*": "deny" }
|
|
434
527
|
}
|
|
528
|
+
},
|
|
529
|
+
"flow-worker": {
|
|
530
|
+
...FLOW_CORE_AGENTS["flow-worker"],
|
|
531
|
+
permission: {
|
|
532
|
+
...FLOW_CORE_AGENTS["flow-worker"].permission,
|
|
533
|
+
task: { "*": "deny" }
|
|
534
|
+
}
|
|
435
535
|
}
|
|
436
536
|
},
|
|
437
537
|
command: Object.fromEntries(Object.entries(FLOW_CORE_COMMANDS).map(([name, value]) => [
|
|
@@ -606,6 +706,39 @@ function planIssue(plan) {
|
|
|
606
706
|
return visited === plan.features.length ? null : "The plan dependency graph is cyclic.";
|
|
607
707
|
}
|
|
608
708
|
|
|
709
|
+
// src/domain/session.ts
|
|
710
|
+
function reviewResultSemanticIssues(result) {
|
|
711
|
+
const issues = [];
|
|
712
|
+
const blocking = result.findings.some((finding) => finding.severity === "blocking");
|
|
713
|
+
for (const [index, finding] of result.findings.entries()) {
|
|
714
|
+
if (finding.severity === "blocking" && !finding.evidence?.trim()) {
|
|
715
|
+
issues.push({
|
|
716
|
+
path: ["findings", index, "evidence"],
|
|
717
|
+
message: "A blocking finding requires concrete evidence."
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (result.verdict === "failed" && !blocking) {
|
|
722
|
+
issues.push({
|
|
723
|
+
path: ["findings"],
|
|
724
|
+
message: "A failed review requires a blocking finding."
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
if (result.verdict === "passed" && blocking) {
|
|
728
|
+
issues.push({
|
|
729
|
+
path: ["findings"],
|
|
730
|
+
message: "A passed review cannot contain blocking findings."
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (result.terminalDisposition === "observed_unsubmitted" && result.verdict !== "failed") {
|
|
734
|
+
issues.push({
|
|
735
|
+
path: ["terminalDisposition"],
|
|
736
|
+
message: "Observed-but-unsubmitted review work must fail closed."
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return issues;
|
|
740
|
+
}
|
|
741
|
+
|
|
609
742
|
// src/domain/transitions.ts
|
|
610
743
|
class FlowTransitionError extends Error {
|
|
611
744
|
code = "FLOW_TRANSITION_REJECTED";
|
|
@@ -941,19 +1074,9 @@ function assertReviewResult(result) {
|
|
|
941
1074
|
if (result.findings.length > MAX_REVIEW_FINDINGS) {
|
|
942
1075
|
fail(`A review may contain at most ${MAX_REVIEW_FINDINGS} findings.`);
|
|
943
1076
|
}
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
fail("A blocking finding requires concrete evidence.");
|
|
948
|
-
if (result.verdict === "failed" && !blocking) {
|
|
949
|
-
fail("A failed review requires a blocking finding.");
|
|
950
|
-
}
|
|
951
|
-
if (result.verdict === "passed" && blocking) {
|
|
952
|
-
fail("A passed review cannot contain a blocking finding.");
|
|
953
|
-
}
|
|
954
|
-
if (result.terminalDisposition === "observed_unsubmitted" && result.verdict !== "failed") {
|
|
955
|
-
fail("Observed-but-unsubmitted review work must fail closed.");
|
|
956
|
-
}
|
|
1077
|
+
const issue = reviewResultSemanticIssues(result)[0];
|
|
1078
|
+
if (issue)
|
|
1079
|
+
fail(issue.message);
|
|
957
1080
|
}
|
|
958
1081
|
function completeFeature(session, input) {
|
|
959
1082
|
assertReviewResult(input.result);
|
|
@@ -1364,36 +1487,8 @@ var PublicReviewResultSchema = z.object({
|
|
|
1364
1487
|
findings: z.array(ReviewFindingSchema).max(MAX_REVIEW_FINDINGS).default([]),
|
|
1365
1488
|
terminalDisposition: z.enum(["submitted", "observed_unsubmitted"])
|
|
1366
1489
|
}).strict().superRefine((result, context) => {
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
if (finding.severity === "blocking" && !finding.evidence) {
|
|
1370
|
-
context.addIssue({
|
|
1371
|
-
code: "custom",
|
|
1372
|
-
path: ["findings", index, "evidence"],
|
|
1373
|
-
message: "A blocking finding requires concrete evidence."
|
|
1374
|
-
});
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
if (result.verdict === "failed" && !blocking) {
|
|
1378
|
-
context.addIssue({
|
|
1379
|
-
code: "custom",
|
|
1380
|
-
path: ["findings"],
|
|
1381
|
-
message: "A failed review requires a blocking finding."
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1384
|
-
if (result.verdict === "passed" && blocking) {
|
|
1385
|
-
context.addIssue({
|
|
1386
|
-
code: "custom",
|
|
1387
|
-
path: ["findings"],
|
|
1388
|
-
message: "A passed review cannot contain blocking findings."
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
if (result.terminalDisposition === "observed_unsubmitted" && result.verdict !== "failed") {
|
|
1392
|
-
context.addIssue({
|
|
1393
|
-
code: "custom",
|
|
1394
|
-
path: ["terminalDisposition"],
|
|
1395
|
-
message: "Observed-but-unsubmitted review work must fail closed."
|
|
1396
|
-
});
|
|
1490
|
+
for (const issue of reviewResultSemanticIssues(result)) {
|
|
1491
|
+
context.addIssue({ code: "custom", ...issue });
|
|
1397
1492
|
}
|
|
1398
1493
|
});
|
|
1399
1494
|
var ValidationObservationSchema = z.object({
|
|
@@ -2212,7 +2307,7 @@ function validIdentity(value) {
|
|
|
2212
2307
|
}
|
|
2213
2308
|
function canonicalProjectId(scopeId) {
|
|
2214
2309
|
if (!boundedText2(scopeId))
|
|
2215
|
-
throw new TypeError("Flow
|
|
2310
|
+
throw new TypeError("Flow runtime scope must be a non-empty path.");
|
|
2216
2311
|
const projectId = resolve2(scopeId);
|
|
2217
2312
|
try {
|
|
2218
2313
|
return realpathSync2(projectId);
|
|
@@ -2260,17 +2355,11 @@ function acquireRegistry() {
|
|
|
2260
2355
|
function snapshot(identity) {
|
|
2261
2356
|
return Object.freeze({ ...identity });
|
|
2262
2357
|
}
|
|
2263
|
-
function makeStatus(
|
|
2358
|
+
function makeStatus(reason) {
|
|
2264
2359
|
const operational = reason === "sole-instance";
|
|
2265
2360
|
return Object.freeze({
|
|
2266
|
-
instanceId: identity.instanceId,
|
|
2267
|
-
registered,
|
|
2268
2361
|
operational,
|
|
2269
|
-
role: operational ? "leader" : registered || reason === "incompatible-registry" ? "indeterminate" : "unregistered",
|
|
2270
2362
|
reason,
|
|
2271
|
-
registeredCount: reason === "incompatible-registry" && !registered ? null : registrations.length,
|
|
2272
|
-
diagnosticLeader: operational ? identity : null,
|
|
2273
|
-
registrations: Object.freeze([...registrations]),
|
|
2274
2363
|
message: operational ? "Flow is active for this project." : `Flow is not operational (${reason}).`
|
|
2275
2364
|
});
|
|
2276
2365
|
}
|
|
@@ -2281,7 +2370,7 @@ var createFlowPluginInstanceId = () => globalThis.crypto.randomUUID();
|
|
|
2281
2370
|
function registerFlowPluginInstance(scopeId, input) {
|
|
2282
2371
|
const projectId = canonicalProjectId(scopeId);
|
|
2283
2372
|
if (!validIdentity(input)) {
|
|
2284
|
-
throw new TypeError("Flow
|
|
2373
|
+
throw new TypeError("Flow runtime identity is invalid.");
|
|
2285
2374
|
}
|
|
2286
2375
|
const identity = snapshot(input);
|
|
2287
2376
|
const registry = acquireRegistry();
|
|
@@ -2294,7 +2383,7 @@ function registerFlowPluginInstance(scopeId, input) {
|
|
|
2294
2383
|
}
|
|
2295
2384
|
const existing = project.get(identity.instanceId);
|
|
2296
2385
|
if (existing && !sameIdentity(existing, identity)) {
|
|
2297
|
-
throw new Error(`Flow
|
|
2386
|
+
throw new Error(`Flow runtime instance ID '${identity.instanceId}' is already registered with different identity data.`);
|
|
2298
2387
|
}
|
|
2299
2388
|
record = existing ?? identity;
|
|
2300
2389
|
project.set(identity.instanceId, record);
|
|
@@ -2302,32 +2391,28 @@ function registerFlowPluginInstance(scopeId, input) {
|
|
|
2302
2391
|
let released = false;
|
|
2303
2392
|
const query = () => {
|
|
2304
2393
|
if (released)
|
|
2305
|
-
return makeStatus(
|
|
2394
|
+
return makeStatus("released");
|
|
2306
2395
|
const currentRegistry = readRegistry();
|
|
2307
2396
|
if (!record)
|
|
2308
|
-
return makeStatus(
|
|
2397
|
+
return makeStatus("incompatible-registry");
|
|
2309
2398
|
if (currentRegistry === undefined)
|
|
2310
|
-
return makeStatus(
|
|
2399
|
+
return makeStatus("not-registered");
|
|
2311
2400
|
if (!compatibleRegistry(currentRegistry))
|
|
2312
|
-
return makeStatus(
|
|
2401
|
+
return makeStatus("incompatible-registry");
|
|
2313
2402
|
const project = currentRegistry.projects.get(projectId);
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
return makeStatus(identity, "not-registered", registrations);
|
|
2403
|
+
if (!project || project.get(identity.instanceId) !== record) {
|
|
2404
|
+
return makeStatus("not-registered");
|
|
2317
2405
|
}
|
|
2318
2406
|
if (identity.protocolVersion !== FLOW_LEADERSHIP_PROTOCOL_VERSION) {
|
|
2319
|
-
return makeStatus(
|
|
2407
|
+
return makeStatus("incompatible-registry");
|
|
2320
2408
|
}
|
|
2321
|
-
if (
|
|
2322
|
-
return makeStatus(
|
|
2409
|
+
if (project.size !== 1) {
|
|
2410
|
+
return makeStatus("duplicate-instances");
|
|
2323
2411
|
}
|
|
2324
|
-
return makeStatus(
|
|
2412
|
+
return makeStatus("sole-instance");
|
|
2325
2413
|
};
|
|
2326
2414
|
return Object.freeze({
|
|
2327
|
-
identity,
|
|
2328
|
-
scopeId: projectId,
|
|
2329
2415
|
query,
|
|
2330
|
-
isOperational: () => query().operational,
|
|
2331
2416
|
assertOperational(action) {
|
|
2332
2417
|
const current = query();
|
|
2333
2418
|
if (!current.operational) {
|
|
@@ -2815,36 +2900,8 @@ var reviewResult = host.object({
|
|
|
2815
2900
|
findings: host.array(reviewFinding).max(MAX_REVIEW_FINDINGS).default([]),
|
|
2816
2901
|
terminalDisposition: host.enum(["submitted", "observed_unsubmitted"])
|
|
2817
2902
|
}).strict().superRefine((result, context) => {
|
|
2818
|
-
const
|
|
2819
|
-
|
|
2820
|
-
if (finding.severity === "blocking" && !finding.evidence) {
|
|
2821
|
-
context.addIssue({
|
|
2822
|
-
code: "custom",
|
|
2823
|
-
path: ["findings", index, "evidence"],
|
|
2824
|
-
message: "A blocking finding requires concrete evidence."
|
|
2825
|
-
});
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
if (result.verdict === "failed" && !blocking) {
|
|
2829
|
-
context.addIssue({
|
|
2830
|
-
code: "custom",
|
|
2831
|
-
path: ["findings"],
|
|
2832
|
-
message: "A failed review requires a blocking finding."
|
|
2833
|
-
});
|
|
2834
|
-
}
|
|
2835
|
-
if (result.verdict === "passed" && blocking) {
|
|
2836
|
-
context.addIssue({
|
|
2837
|
-
code: "custom",
|
|
2838
|
-
path: ["findings"],
|
|
2839
|
-
message: "A passed review cannot contain blocking findings."
|
|
2840
|
-
});
|
|
2841
|
-
}
|
|
2842
|
-
if (result.terminalDisposition === "observed_unsubmitted" && result.verdict !== "failed") {
|
|
2843
|
-
context.addIssue({
|
|
2844
|
-
code: "custom",
|
|
2845
|
-
path: ["terminalDisposition"],
|
|
2846
|
-
message: "Observed-but-unsubmitted review work must fail closed."
|
|
2847
|
-
});
|
|
2903
|
+
for (const issue of reviewResultSemanticIssues(result)) {
|
|
2904
|
+
context.addIssue({ code: "custom", ...issue });
|
|
2848
2905
|
}
|
|
2849
2906
|
});
|
|
2850
2907
|
var StatusArgs = {
|
|
@@ -3045,7 +3102,7 @@ class ValidationCaptureCoordinator {
|
|
|
3045
3102
|
#prune() {
|
|
3046
3103
|
const cutoff = this.#now() - CAPTURE_TTL_MS;
|
|
3047
3104
|
for (const [sessionID, capture] of this.#pending) {
|
|
3048
|
-
if (capture.armedAt < cutoff) {
|
|
3105
|
+
if (capture.callID === null && capture.armedAt < cutoff) {
|
|
3049
3106
|
this.#pending.delete(sessionID);
|
|
3050
3107
|
}
|
|
3051
3108
|
}
|
|
@@ -3174,17 +3231,18 @@ function createCommandHook(assertOperational) {
|
|
|
3174
3231
|
}
|
|
3175
3232
|
};
|
|
3176
3233
|
}
|
|
3177
|
-
function guardTools(tools,
|
|
3234
|
+
function guardTools(tools, runtimeGuard) {
|
|
3178
3235
|
return Object.fromEntries(Object.entries(tools).map(([name, definition]) => [
|
|
3179
3236
|
name,
|
|
3180
3237
|
{
|
|
3181
3238
|
...definition,
|
|
3182
3239
|
execute: async (...args) => {
|
|
3183
|
-
|
|
3240
|
+
const status = runtimeGuard.query();
|
|
3241
|
+
if (!status.operational) {
|
|
3184
3242
|
return JSON.stringify({
|
|
3185
3243
|
status: "error",
|
|
3186
|
-
summary:
|
|
3187
|
-
workflowData: { runtimeGuard:
|
|
3244
|
+
summary: status.message,
|
|
3245
|
+
workflowData: { runtimeGuard: status }
|
|
3188
3246
|
});
|
|
3189
3247
|
}
|
|
3190
3248
|
return definition.execute(...args);
|
|
@@ -3195,13 +3253,13 @@ function guardTools(tools, leadership) {
|
|
|
3195
3253
|
var FlowPlugin = async (ctx) => {
|
|
3196
3254
|
const log = createFlowLog(ctx);
|
|
3197
3255
|
const version = resolveFlowPluginVersion();
|
|
3198
|
-
const
|
|
3256
|
+
const runtimeGuard = registerFlowPluginInstance(ctx.worktree ?? ctx.directory, {
|
|
3199
3257
|
packageName: "opencode-plugin-flow",
|
|
3200
3258
|
version,
|
|
3201
3259
|
protocolVersion: FLOW_LEADERSHIP_PROTOCOL_VERSION,
|
|
3202
3260
|
instanceId: createFlowPluginInstanceId()
|
|
3203
3261
|
});
|
|
3204
|
-
const initial =
|
|
3262
|
+
const initial = runtimeGuard.query();
|
|
3205
3263
|
log(initial.operational ? "info" : "error", `Flow ${version}: ${initial.message}`);
|
|
3206
3264
|
const validation = new ValidationCaptureCoordinator({
|
|
3207
3265
|
persistObservation: persistWorkspaceValidation
|
|
@@ -3212,10 +3270,10 @@ var FlowPlugin = async (ctx) => {
|
|
|
3212
3270
|
});
|
|
3213
3271
|
return {
|
|
3214
3272
|
config: createConfigHook(ctx, {
|
|
3215
|
-
assertOperational: (action) =>
|
|
3273
|
+
assertOperational: (action) => runtimeGuard.assertOperational(action)
|
|
3216
3274
|
}),
|
|
3217
|
-
tool: guardTools(tools,
|
|
3218
|
-
"command.execute.before": createCommandHook((action) =>
|
|
3275
|
+
tool: guardTools(tools, runtimeGuard),
|
|
3276
|
+
"command.execute.before": createCommandHook((action) => runtimeGuard.assertOperational(action)),
|
|
3219
3277
|
event: async (input) => {
|
|
3220
3278
|
const event = input.event;
|
|
3221
3279
|
if (event.type !== "session.idle" && event.type !== "session.compacted") {
|
|
@@ -3240,7 +3298,7 @@ var FlowPlugin = async (ctx) => {
|
|
|
3240
3298
|
}
|
|
3241
3299
|
},
|
|
3242
3300
|
dispose: async () => {
|
|
3243
|
-
|
|
3301
|
+
runtimeGuard.release();
|
|
3244
3302
|
}
|
|
3245
3303
|
};
|
|
3246
3304
|
};
|
|
@@ -3249,4 +3307,4 @@ export {
|
|
|
3249
3307
|
plugin_default as default
|
|
3250
3308
|
};
|
|
3251
3309
|
|
|
3252
|
-
//# debugId=
|
|
3310
|
+
//# debugId=48BDB7D8B1F2A60764756E2164756E21
|