opencode-plugin-flow 5.2.0 → 5.2.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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  One short entry per release, written for users deciding whether to upgrade.
4
4
 
5
+ ## [5.2.1] - 2026-07-19
6
+
7
+ Desktop helper runtime lore restores durable session closure under OpenCode
8
+ Desktop while preserving Flow's pinned-filesystem safety boundary:
9
+
10
+ - Electron-hosted Flow now starts its short-lived filesystem helper through the
11
+ host executable's Node mode. Bun-hosted OpenCode retains its dedicated CLI
12
+ mode, and ordinary Node hosts remain unchanged.
13
+ - Helper launch and protocol failures are classified separately from malformed
14
+ or ambiguous canonical history. Recovery guidance preserves the active
15
+ session and its exact durable close operation instead of blaming healthy
16
+ archive state.
17
+ - Persistence coverage exercises the Electron launch contract end to end and
18
+ proves that runtime failures remain atomic and receive the correct guidance.
19
+
5
20
  ## [5.2.0] - 2026-07-19
6
21
 
7
22
  Runtime-owned review assignment lore removes the recovery loops seen in long
package/README.md CHANGED
@@ -17,7 +17,7 @@ Full project documentation is available in the
17
17
  ## Quick start
18
18
 
19
19
  ```bash
20
- opencode plugin opencode-plugin-flow@5.2.0 --global --force
20
+ opencode plugin opencode-plugin-flow@5.2.1 --global --force
21
21
  ```
22
22
 
23
23
  Start or restart OpenCode, then give Flow a goal:
@@ -235,7 +235,7 @@ or sync command is required. To preview recoverable migration of pristine v4
235
235
  global skill folders:
236
236
 
237
237
  ```bash
238
- npx -y opencode-plugin-flow@5.2.0 legacy-cleanup --dry-run
238
+ npx -y opencode-plugin-flow@5.2.1 legacy-cleanup --dry-run
239
239
  ```
240
240
 
241
241
  ## Development
@@ -3,7 +3,10 @@ import type { EvidenceArtifactStore } from "./evidence-artifact-store.js";
3
3
  import type { SourceIdentityProvider } from "./source-identity.js";
4
4
  export declare class ArchivedSessionLookupError extends Error {
5
5
  readonly code = "FLOW_ARCHIVE_LOOKUP_FAILED";
6
- constructor(message: string, options?: ErrorOptions);
6
+ readonly failureKind: "history-integrity" | "helper-runtime";
7
+ constructor(message: string, options?: ErrorOptions & {
8
+ failureKind?: "history-integrity" | "helper-runtime";
9
+ });
7
10
  }
8
11
  export type SessionTransaction = EvidenceArtifactStore & SourceIdentityProvider & {
9
12
  load(): Promise<Session | null>;
package/dist/index.js CHANGED
@@ -3313,9 +3313,11 @@ class UnsupportedFlowSessionVersionError extends Error {
3313
3313
  // src/application/ports/session-repository.ts
3314
3314
  class ArchivedSessionLookupError extends Error {
3315
3315
  code = "FLOW_ARCHIVE_LOOKUP_FAILED";
3316
+ failureKind;
3316
3317
  constructor(message, options) {
3317
3318
  super(message, options);
3318
3319
  this.name = "ArchivedSessionLookupError";
3320
+ this.failureKind = options?.failureKind ?? "history-integrity";
3319
3321
  }
3320
3322
  }
3321
3323
 
@@ -7292,6 +7294,17 @@ async function assertManagedDirectoryIdentity(path, description, expected) {
7292
7294
  throw new UnsafeFlowWorkspaceLayoutError(`Flow detected that ${description} changed during a managed operation: ${path}.`);
7293
7295
  }
7294
7296
  }
7297
+
7298
+ class PinnedFilesystemHelperError extends Error {
7299
+ code = "FLOW_PINNED_HELPER_FAILED";
7300
+ constructor(message, options) {
7301
+ super(message, options);
7302
+ this.name = "PinnedFilesystemHelperError";
7303
+ }
7304
+ }
7305
+ function pinnedHelperFailure(message, cause) {
7306
+ return new PinnedFilesystemHelperError(message, { cause });
7307
+ }
7295
7308
  function helperFailure(stderr, cause) {
7296
7309
  let detail = null;
7297
7310
  try {
@@ -7300,16 +7313,23 @@ function helperFailure(stderr, cause) {
7300
7313
  if (detail?.code === "FLOW_PINNED_DIRECTORY_MISMATCH" || detail?.code === "FLOW_ARCHIVE_CASE_COLLISION" || detail?.code === "FLOW_ARCHIVE_UNKNOWN_JSON") {
7301
7314
  return new UnsafeFlowWorkspaceLayoutError(detail.message ?? "Flow detected an unsafe pinned directory change.", { cause });
7302
7315
  }
7303
- const error = new Error(detail?.message ?? "Flow pinned filesystem helper failed.", { cause });
7304
- if (detail?.code)
7305
- error.code = detail.code;
7306
- return error;
7316
+ return new PinnedFilesystemHelperError(detail?.message ?? "Flow pinned filesystem helper failed.", { cause });
7317
+ }
7318
+ function pinnedHelperEnvironment(runtime) {
7319
+ if (runtime === "electron") {
7320
+ return { ...process.env, ELECTRON_RUN_AS_NODE: "1" };
7321
+ }
7322
+ if (runtime === "bun") {
7323
+ return { ...process.env, BUN_BE_BUN: "1" };
7324
+ }
7325
+ return process.env;
7307
7326
  }
7308
7327
  async function runPinnedDirectoryHelper(cwd, request, input = "", afterPinned, options = {}) {
7309
7328
  const encodedRequest = Buffer.from(JSON.stringify(request), "utf8").toString("base64");
7329
+ const runtime = options.pinnedHelperTestRuntime ?? (process.versions.electron ? "electron" : process.versions.bun ? "bun" : "node");
7310
7330
  const child = spawn(options.pinnedHelperTestExecutable ?? process.execPath, ["--eval", PINNED_DIRECTORY_HELPER_SOURCE, encodedRequest], {
7311
7331
  cwd,
7312
- env: process.versions.bun ? { ...process.env, BUN_BE_BUN: "1" } : process.env,
7332
+ env: pinnedHelperEnvironment(runtime),
7313
7333
  stdio: ["pipe", "pipe", "pipe"],
7314
7334
  windowsHide: true
7315
7335
  });
@@ -7337,7 +7357,7 @@ async function runPinnedDirectoryHelper(cwd, request, input = "", afterPinned, o
7337
7357
  child.kill("SIGKILL");
7338
7358
  };
7339
7359
  const readyTimeout = setTimeout(() => {
7340
- terminateHelper(new Error("Flow pinned filesystem helper timed out before readiness."));
7360
+ terminateHelper(pinnedHelperFailure("Flow pinned filesystem helper timed out before readiness."));
7341
7361
  }, positiveTimeout(options.pinnedHelperReadyTimeoutMs, PINNED_HELPER_READY_TIMEOUT_MS));
7342
7362
  readyTimeout.unref();
7343
7363
  let completionTimeout;
@@ -7351,18 +7371,18 @@ async function runPinnedDirectoryHelper(cwd, request, input = "", afterPinned, o
7351
7371
  try {
7352
7372
  event = JSON.parse(stdout.slice(0, newline));
7353
7373
  } catch (error) {
7354
- terminateHelper(new Error("Flow pinned filesystem helper returned an invalid ready event.", { cause: error }));
7374
+ terminateHelper(pinnedHelperFailure("Flow pinned filesystem helper returned an invalid ready event.", error));
7355
7375
  return;
7356
7376
  }
7357
7377
  if (event.event !== "pinned") {
7358
- terminateHelper(new Error("Flow pinned filesystem helper omitted its ready event."));
7378
+ terminateHelper(pinnedHelperFailure("Flow pinned filesystem helper omitted its ready event."));
7359
7379
  return;
7360
7380
  }
7361
7381
  stdout = stdout.slice(newline + 1);
7362
7382
  readyState = "resolved";
7363
7383
  clearTimeout(readyTimeout);
7364
7384
  completionTimeout = setTimeout(() => {
7365
- terminateHelper(new Error("Flow pinned filesystem helper timed out before completion."));
7385
+ terminateHelper(pinnedHelperFailure("Flow pinned filesystem helper timed out before completion."));
7366
7386
  }, positiveTimeout(options.pinnedHelperCompletionTimeoutMs, PINNED_HELPER_COMPLETION_TIMEOUT_MS));
7367
7387
  completionTimeout.unref();
7368
7388
  resolvePinned?.();
@@ -7394,7 +7414,7 @@ async function runPinnedDirectoryHelper(cwd, request, input = "", afterPinned, o
7394
7414
  return;
7395
7415
  }
7396
7416
  if (readyState !== "resolved") {
7397
- const failure = new Error("Flow pinned filesystem helper exited before readiness.");
7417
+ const failure = pinnedHelperFailure("Flow could not start its pinned filesystem helper under the current host runtime.");
7398
7418
  if (readyState === "pending") {
7399
7419
  readyState = "rejected";
7400
7420
  rejectPinned?.(failure);
@@ -7409,9 +7429,7 @@ async function runPinnedDirectoryHelper(cwd, request, input = "", afterPinned, o
7409
7429
  try {
7410
7430
  resolve2(JSON.parse(stdout));
7411
7431
  } catch (error) {
7412
- reject(new Error("Flow pinned filesystem helper returned invalid output.", {
7413
- cause: error
7414
- }));
7432
+ reject(pinnedHelperFailure("Flow pinned filesystem helper returned invalid output.", error));
7415
7433
  }
7416
7434
  });
7417
7435
  });
@@ -7645,6 +7663,12 @@ async function findCanonicalArchivedSession(worktree, predicate) {
7645
7663
  } catch (error) {
7646
7664
  if (error instanceof ArchivedSessionLookupError)
7647
7665
  throw error;
7666
+ if (error instanceof PinnedFilesystemHelperError) {
7667
+ throw new ArchivedSessionLookupError(error.message, {
7668
+ cause: error,
7669
+ failureKind: "helper-runtime"
7670
+ });
7671
+ }
7648
7672
  throw new ArchivedSessionLookupError("Flow could not verify archived operation history safely.", { cause: error });
7649
7673
  }
7650
7674
  }
@@ -8171,43 +8195,46 @@ function archivedCloseResponse(session, operationId) {
8171
8195
  }, [], operationId);
8172
8196
  }
8173
8197
  function archivedLookupFailureResponse(error, operationId) {
8198
+ const helperRuntimeFailure = error.failureKind === "helper-runtime";
8174
8199
  return rejectedMutationResponse({
8175
8200
  status: "error",
8176
- summary: "Flow could not verify archived retry history.",
8177
- nextAction: "Inspect canonical Flow history integrity before retrying this close operation.",
8201
+ summary: helperRuntimeFailure ? "Flow could not start its filesystem helper to read archived retry history." : "Flow could not verify archived retry history.",
8202
+ nextAction: helperRuntimeFailure ? "Restart OpenCode with the current Flow build, then retry this close operation." : "Inspect canonical Flow history integrity before retrying this close operation.",
8178
8203
  dataNote: WORKFLOW_DATA_NOTE,
8179
8204
  workflowData: {
8180
8205
  failure: {
8181
8206
  summary: error.message,
8182
- recovery: "Preserve archive files and resolve corrupt or ambiguous canonical history; quarantine records are not replay sources."
8207
+ recovery: helperRuntimeFailure ? "Preserve Flow state, restart OpenCode after updating Flow, and retry with the same close operation id." : "Preserve archive files and resolve corrupt or ambiguous canonical history; quarantine records are not replay sources."
8183
8208
  }
8184
8209
  }
8185
8210
  }, null, operationId);
8186
8211
  }
8187
8212
  function archivedCloseStartLookupFailureResponse(error, session, operationId) {
8213
+ const helperRuntimeFailure = error.failureKind === "helper-runtime";
8188
8214
  return rejectedMutationResponse({
8189
8215
  status: "error",
8190
- summary: "Flow could not prove that this close operation id is unique in canonical history.",
8191
- nextAction: "Inspect canonical Flow history integrity before starting this close operation.",
8216
+ summary: helperRuntimeFailure ? "Flow could not start its filesystem helper to verify this close operation id." : "Flow could not prove that this close operation id is unique in canonical history.",
8217
+ nextAction: helperRuntimeFailure ? "Restart OpenCode with the current Flow build, then start this close operation again." : "Inspect canonical Flow history integrity before starting this close operation.",
8192
8218
  dataNote: WORKFLOW_DATA_NOTE,
8193
8219
  workflowData: {
8194
8220
  failure: {
8195
8221
  summary: error.message,
8196
- recovery: "Preserve the active session and resolve corrupt or ambiguous canonical history before retrying with a verified operation id."
8222
+ recovery: helperRuntimeFailure ? "Preserve the active session, restart OpenCode after updating Flow, and retry with the same unconsumed operation id." : "Preserve the active session and resolve corrupt or ambiguous canonical history before retrying with a verified operation id."
8197
8223
  }
8198
8224
  }
8199
8225
  }, session, operationId);
8200
8226
  }
8201
8227
  function archivedCloseRetryLookupFailureResponse(error, session, operationId) {
8228
+ const helperRuntimeFailure = error.failureKind === "helper-runtime";
8202
8229
  return rejectedMutationResponse({
8203
8230
  status: "error",
8204
- summary: "Flow could not verify canonical history before publishing the pending close.",
8205
- nextAction: "Inspect canonical Flow history integrity before retrying archive publication.",
8231
+ summary: helperRuntimeFailure ? "Flow could not start its filesystem helper before publishing the pending close." : "Flow could not verify canonical history before publishing the pending close.",
8232
+ nextAction: helperRuntimeFailure ? "Restart OpenCode with the current Flow build, then retry archive publication." : "Inspect canonical Flow history integrity before retrying archive publication.",
8206
8233
  dataNote: WORKFLOW_DATA_NOTE,
8207
8234
  workflowData: {
8208
8235
  failure: {
8209
8236
  summary: error.message,
8210
- recovery: "Preserve the active closed session and resolve corrupt, ambiguous, or conflicting canonical history before retrying its durable close operation."
8237
+ recovery: helperRuntimeFailure ? "Preserve the active closed session, restart OpenCode after updating Flow, and retry its exact durable close operation." : "Preserve the active closed session and resolve corrupt, ambiguous, or conflicting canonical history before retrying its durable close operation."
8211
8238
  }
8212
8239
  }
8213
8240
  }, session, operationId);
@@ -9862,4 +9889,4 @@ export {
9862
9889
  plugin_default as default
9863
9890
  };
9864
9891
 
9865
- //# debugId=A0752D8309CC46B964756E2164756E21
9892
+ //# debugId=E001CAB8DE3EBE1964756E2164756E21