@flowdesk/opencode-plugin 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-task-output.d.ts +29 -0
  3. package/dist/agent-task-output.d.ts.map +1 -0
  4. package/dist/agent-task-output.js +225 -0
  5. package/dist/agent-task-output.js.map +1 -0
  6. package/dist/agent-task-runner.d.ts +34 -0
  7. package/dist/agent-task-runner.d.ts.map +1 -1
  8. package/dist/agent-task-runner.js +634 -84
  9. package/dist/agent-task-runner.js.map +1 -1
  10. package/dist/auto-continue-preview-tool.d.ts +36 -0
  11. package/dist/auto-continue-preview-tool.d.ts.map +1 -0
  12. package/dist/auto-continue-preview-tool.js +119 -0
  13. package/dist/auto-continue-preview-tool.js.map +1 -0
  14. package/dist/completion-ui-cache.d.ts +6 -0
  15. package/dist/completion-ui-cache.d.ts.map +1 -0
  16. package/dist/completion-ui-cache.js +390 -0
  17. package/dist/completion-ui-cache.js.map +1 -0
  18. package/dist/event-hook-observer.d.ts +14 -0
  19. package/dist/event-hook-observer.d.ts.map +1 -0
  20. package/dist/event-hook-observer.js +257 -0
  21. package/dist/event-hook-observer.js.map +1 -0
  22. package/dist/managed-dispatch-adapter.d.ts +62 -0
  23. package/dist/managed-dispatch-adapter.d.ts.map +1 -1
  24. package/dist/managed-dispatch-adapter.js +472 -4
  25. package/dist/managed-dispatch-adapter.js.map +1 -1
  26. package/dist/model-selection-engine.d.ts +60 -0
  27. package/dist/model-selection-engine.d.ts.map +1 -0
  28. package/dist/model-selection-engine.js +242 -0
  29. package/dist/model-selection-engine.js.map +1 -0
  30. package/dist/provider-usage-live-tool.d.ts +10 -0
  31. package/dist/provider-usage-live-tool.d.ts.map +1 -1
  32. package/dist/provider-usage-live-tool.js +262 -33
  33. package/dist/provider-usage-live-tool.js.map +1 -1
  34. package/dist/server.d.ts +36 -1
  35. package/dist/server.d.ts.map +1 -1
  36. package/dist/server.js +497 -20
  37. package/dist/server.js.map +1 -1
  38. package/dist/stall-recovery.d.ts +34 -0
  39. package/dist/stall-recovery.d.ts.map +1 -1
  40. package/dist/stall-recovery.js +680 -3
  41. package/dist/stall-recovery.js.map +1 -1
  42. package/dist/status-live-tool.d.ts +54 -0
  43. package/dist/status-live-tool.d.ts.map +1 -1
  44. package/dist/status-live-tool.js +449 -44
  45. package/dist/status-live-tool.js.map +1 -1
  46. package/dist/tui-subtask-activity.d.ts +73 -0
  47. package/dist/tui-subtask-activity.d.ts.map +1 -0
  48. package/dist/tui-subtask-activity.js +271 -0
  49. package/dist/tui-subtask-activity.js.map +1 -0
  50. package/dist/tui-usage-snapshot.d.ts +14 -0
  51. package/dist/tui-usage-snapshot.d.ts.map +1 -1
  52. package/dist/tui-usage-snapshot.js +275 -8
  53. package/dist/tui-usage-snapshot.js.map +1 -1
  54. package/dist/tui.d.ts.map +1 -1
  55. package/dist/tui.js +102 -44
  56. package/dist/tui.js.map +1 -1
  57. package/dist/workflow-assign-tool.d.ts +23 -0
  58. package/dist/workflow-assign-tool.d.ts.map +1 -0
  59. package/dist/workflow-assign-tool.js +135 -0
  60. package/dist/workflow-assign-tool.js.map +1 -0
  61. package/dist/workflow-author-tool.d.ts +29 -0
  62. package/dist/workflow-author-tool.d.ts.map +1 -0
  63. package/dist/workflow-author-tool.js +227 -0
  64. package/dist/workflow-author-tool.js.map +1 -0
  65. package/dist/workflow-dispatch-tool.d.ts +12 -0
  66. package/dist/workflow-dispatch-tool.d.ts.map +1 -1
  67. package/dist/workflow-dispatch-tool.js +31 -3
  68. package/dist/workflow-dispatch-tool.js.map +1 -1
  69. package/dist/workflow-orchestrator.d.ts +31 -0
  70. package/dist/workflow-orchestrator.d.ts.map +1 -0
  71. package/dist/workflow-orchestrator.js +160 -0
  72. package/dist/workflow-orchestrator.js.map +1 -0
  73. package/dist/workflow-scheduler.d.ts.map +1 -1
  74. package/dist/workflow-scheduler.js +3 -1
  75. package/dist/workflow-scheduler.js.map +1 -1
  76. package/dist/workflow-synthesis-tool.d.ts +31 -0
  77. package/dist/workflow-synthesis-tool.d.ts.map +1 -0
  78. package/dist/workflow-synthesis-tool.js +194 -0
  79. package/dist/workflow-synthesis-tool.js.map +1 -0
  80. package/package.json +2 -2
@@ -1,8 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
3
  import { dirname, resolve, sep } from "node:path";
4
- import { applyFlowDeskSessionEvidenceWriteIntentsV1, evaluateFlowDeskDispatchAttemptDurablePrecallV1, evaluateManagedDispatchBetaGuardBoundaryV1, planFlowDeskFallbackRegateV1, prepareFlowDeskDispatchIdempotencyReservationV1, prepareFlowDeskDispatchIdempotencyStateUpdateV1, prepareFlowDeskSessionEvidenceWriteIntentV1, promoteFlowDeskExternalWriteAuthorityV1, promoteFlowDeskFallbackReselectionRegateV1, promoteFlowDeskManagedDispatchBetaAuthorityV1, promoteFlowDeskReviewerTypedVerdictsV1, recordFlowDeskExactModelAvailabilityCacheProviderAcquisitionResultV1, reloadFlowDeskSessionEvidenceV1, validateFlowDeskExactModelAvailabilityCacheAcquisitionPlanV1, validateFlowDeskFallbackRegatePlanV1, validateFlowDeskProductionApprovalSourceV1, validateFlowDeskPermissionAskDecisionV1, validateFlowDeskPromptNoReplyDecisionV1, validateFlowDeskRuntimeLaneLaunchPlanV1, validateFlowDeskSessionAbortDecisionV1, validateNoForbiddenRawPayloads, validateTopTierReviewVerdictV1, FLOWDESK_TOP_TIER_REVIEW_PERSPECTIVES, } from "@flowdesk/core";
4
+ import { applyFlowDeskSessionEvidenceWriteIntentsV1, evaluateFlowDeskDispatchAttemptDurablePrecallV1, evaluateManagedDispatchBetaGuardBoundaryV1, planFlowDeskFallbackRegateV1, prepareFlowDeskDispatchIdempotencyReservationV1, prepareFlowDeskDispatchIdempotencyStateUpdateV1, prepareFlowDeskSessionEvidenceWriteIntentV1, promoteFlowDeskExternalWriteAuthorityV1, promoteFlowDeskFallbackReselectionRegateV1, promoteFlowDeskManagedDispatchBetaAuthorityV1, promoteFlowDeskReviewerTypedVerdictsV1, recordFlowDeskExactModelAvailabilityCacheProviderAcquisitionResultV1, reloadFlowDeskSessionEvidenceV1, validateFlowDeskExactModelAvailabilityCacheAcquisitionPlanV1, validateFlowDeskFallbackRegatePlanV1, validateFlowDeskProductionApprovalSourceV1, validateFlowDeskPermissionAskDecisionV1, validateFlowDeskPromptNoReplyDecisionV1, validateFlowDeskRuntimeLaneLaunchPlanV1, planFlowDeskRuntimeLaneLaunchV1, validateFlowDeskSessionAbortDecisionV1, validateNoForbiddenRawPayloads, validateTopTierReviewVerdictV1, FLOWDESK_TOP_TIER_REVIEW_PERSPECTIVES, } from "@flowdesk/core";
5
+ import { observeFlowDeskAgentTaskOutputV1 } from "./agent-task-output.js";
5
6
  export const flowdeskManagedDispatchBetaAdapterProfile = "managed_dispatch_beta_real_opencode_dispatch_adapter";
7
+ const MANAGED_DISPATCH_LANE_RESULT_MAX_TEXT = 32_768;
6
8
  function disabledAuthority() {
7
9
  return {
8
10
  realOpenCodeDispatch: false,
@@ -1933,6 +1935,53 @@ export function createFlowDeskManagedDispatchBetaDurableReservationStoreV1(optio
1933
1935
  : { redactedFailureReason: materialized.redactedFailureReason }),
1934
1936
  };
1935
1937
  },
1938
+ recordDispatchCompleted(input) {
1939
+ const recordedAt = now().toISOString();
1940
+ const evidenceId = snapshotRefFor(input.manifest, "dispatch-completed");
1941
+ const current = currentIdempotencySnapshot(options.rootDir, input.manifest.workflow_id);
1942
+ if (!current.ok) {
1943
+ return {
1944
+ ok: false,
1945
+ reservationEvidenceReloaded: false,
1946
+ redactedFailureReason: current.redactedFailureReason,
1947
+ };
1948
+ }
1949
+ const stateUpdate = prepareFlowDeskDispatchIdempotencyStateUpdateV1({
1950
+ workflowId: input.manifest.workflow_id,
1951
+ attemptId: input.manifest.attempt_id,
1952
+ idempotencyKey: input.manifest.idempotency_key,
1953
+ snapshotRef: evidenceId,
1954
+ recordedAt,
1955
+ nextState: "dispatch_completed",
1956
+ existingSnapshot: reservedSnapshots.get(reservationKey(input.manifest)) ??
1957
+ current.snapshot ??
1958
+ existingIdempotencySnapshot(input.reloadedEvidence),
1959
+ });
1960
+ if (!stateUpdate.state_update_prepared ||
1961
+ stateUpdate.snapshot === undefined) {
1962
+ return {
1963
+ ok: false,
1964
+ reservationEvidenceReloaded: false,
1965
+ redactedFailureReason: "completed state preparation blocked",
1966
+ };
1967
+ }
1968
+ const materialized = materializeSnapshot({
1969
+ rootDir: options.rootDir,
1970
+ manifest: input.manifest,
1971
+ evidenceId,
1972
+ snapshot: stateUpdate.snapshot,
1973
+ expectedState: "dispatch_completed",
1974
+ });
1975
+ if (materialized.ok && materialized.snapshot !== undefined)
1976
+ reservedSnapshots.set(reservationKey(input.manifest), materialized.snapshot);
1977
+ return {
1978
+ ok: materialized.ok,
1979
+ reservationEvidenceReloaded: materialized.reservationEvidenceReloaded,
1980
+ ...(materialized.redactedFailureReason === undefined
1981
+ ? {}
1982
+ : { redactedFailureReason: materialized.redactedFailureReason }),
1983
+ };
1984
+ },
1936
1985
  recordDispatchFailure(input) {
1937
1986
  const recordedAt = now().toISOString();
1938
1987
  const evidenceId = snapshotRefFor(input.manifest, "dispatch-failed");
@@ -2023,6 +2072,47 @@ function parseProviderQualifiedModelId(value) {
2023
2072
  return undefined;
2024
2073
  return { providerID, modelID };
2025
2074
  }
2075
+ function workingModelCacheAllowsDispatch(input) {
2076
+ if (input.durableStateRootDir === undefined || input.durableStateRootDir.trim().length === 0) {
2077
+ return {
2078
+ ok: false,
2079
+ reason: "Working-model durable state root is required before managed dispatch.",
2080
+ };
2081
+ }
2082
+ try {
2083
+ const root = resolve(input.durableStateRootDir);
2084
+ const snapshotPath = resolve(root, "model-availability", "working-models.json");
2085
+ if (snapshotPath !== root && !snapshotPath.startsWith(`${root}${sep}`)) {
2086
+ return {
2087
+ ok: false,
2088
+ reason: "Working-model evidence path validation failed; refresh working-model evidence before managed dispatch.",
2089
+ };
2090
+ }
2091
+ if (!existsSync(snapshotPath)) {
2092
+ return {
2093
+ ok: false,
2094
+ reason: "Working-model snapshot is missing; refresh working-model evidence before managed dispatch.",
2095
+ };
2096
+ }
2097
+ const raw = JSON.parse(readFileSync(snapshotPath, "utf8"));
2098
+ const availableModelIds = Array.isArray(raw.available_model_ids)
2099
+ ? raw.available_model_ids.filter((value) => typeof value === "string")
2100
+ : [];
2101
+ if (!availableModelIds.includes(input.providerQualifiedModelId)) {
2102
+ return {
2103
+ ok: false,
2104
+ reason: "Requested provider-qualified model is not present in cached working-model snapshot; refresh working-model evidence before managed dispatch.",
2105
+ };
2106
+ }
2107
+ return { ok: true };
2108
+ }
2109
+ catch {
2110
+ return {
2111
+ ok: false,
2112
+ reason: "Working-model snapshot is missing or unreadable; refresh working-model evidence before managed dispatch.",
2113
+ };
2114
+ }
2115
+ }
2026
2116
  function opencodeRuntimeProviderIDForFlowDeskProviderFamily(providerFamily) {
2027
2117
  switch (providerFamily) {
2028
2118
  case "claude":
@@ -2471,7 +2561,7 @@ export async function launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1(input) {
2471
2561
  };
2472
2562
  let response;
2473
2563
  try {
2474
- response = await callSdkWithLegacyFallback(dispatch, input.client.session, {
2564
+ const flatPromptOptions = {
2475
2565
  sessionID: childSessionId,
2476
2566
  ...(input.request.directory === undefined
2477
2567
  ? {}
@@ -2479,7 +2569,8 @@ export async function launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1(input) {
2479
2569
  model: runtimeModel,
2480
2570
  agent,
2481
2571
  parts: [{ type: "text", text }],
2482
- }, {
2572
+ };
2573
+ const structuredPromptOptions = {
2483
2574
  path: { id: childSessionId },
2484
2575
  ...(input.request.directory === undefined
2485
2576
  ? {}
@@ -2489,7 +2580,10 @@ export async function launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1(input) {
2489
2580
  agent,
2490
2581
  parts: [{ type: "text", text }],
2491
2582
  },
2492
- });
2583
+ };
2584
+ const firstPromptOptions = dispatchMethod === "promptAsync" ? structuredPromptOptions : flatPromptOptions;
2585
+ const fallbackPromptOptions = dispatchMethod === "promptAsync" ? flatPromptOptions : structuredPromptOptions;
2586
+ response = await callSdkWithLegacyFallback(dispatch, input.client.session, firstPromptOptions, fallbackPromptOptions);
2493
2587
  if (isSdkErrorResponse(response))
2494
2588
  throw new Error("sdk prompt failed");
2495
2589
  }
@@ -2662,6 +2756,201 @@ export function materializeFlowDeskRuntimeLaneLaunchLifecycleEvidenceV1(input) {
2662
2756
  authority: runtimeLaneLaunchLifecycleAuthority(true),
2663
2757
  };
2664
2758
  }
2759
+ function managedDispatchLaneFinalizeAuthority() {
2760
+ return {
2761
+ realOpenCodeDispatch: false,
2762
+ providerCall: false,
2763
+ runtimeExecution: false,
2764
+ actualLaneLaunch: false,
2765
+ fallbackAuthority: false,
2766
+ hardCancelOrNoReplyAuthority: false,
2767
+ toolAuthority: false,
2768
+ nudgeOrAbortPerformed: false,
2769
+ };
2770
+ }
2771
+ function managedDispatchLaneHasTerminalTaskEvidence(input) {
2772
+ const reloaded = reloadFlowDeskSessionEvidenceV1({
2773
+ workflowId: input.workflowId,
2774
+ rootDir: input.rootDir,
2775
+ });
2776
+ if (!reloaded.ok)
2777
+ return false;
2778
+ return reloaded.entries.some((entry) => {
2779
+ if (entry.evidenceClass !== "task_result" && entry.evidenceClass !== "task_failed")
2780
+ return false;
2781
+ return entry.record.lane_id === input.laneId;
2782
+ });
2783
+ }
2784
+ /**
2785
+ * Observe a launched managed-dispatch lane's child session once and record
2786
+ * terminal evidence (task_result + terminal lane_lifecycle, or a no_output
2787
+ * lifecycle). Observation-only: no nudge, no abort, no dispatch authority.
2788
+ */
2789
+ export async function observeAndFinalizeManagedDispatchLaneV1(input) {
2790
+ const baseAuthority = managedDispatchLaneFinalizeAuthority();
2791
+ const blocked = (reason) => ({
2792
+ adapterProfile: "managed_dispatch_lane_finalize_observer",
2793
+ status: "blocked_before_finalize",
2794
+ workflowId: input.workflowId,
2795
+ laneId: input.laneId,
2796
+ redactedBlockReason: reason,
2797
+ authority: baseAuthority,
2798
+ });
2799
+ if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
2800
+ return blocked("durable state root is required");
2801
+ if (typeof input.laneId !== "string" || input.laneId.trim().length === 0)
2802
+ return blocked("laneId is required");
2803
+ if (typeof input.childSessionId !== "string" || input.childSessionId.trim().length === 0)
2804
+ return blocked("childSessionId is required");
2805
+ if (managedDispatchLaneHasTerminalTaskEvidence({ rootDir: input.rootDir, workflowId: input.workflowId, laneId: input.laneId })) {
2806
+ return {
2807
+ adapterProfile: "managed_dispatch_lane_finalize_observer",
2808
+ status: "lane_already_terminal",
2809
+ workflowId: input.workflowId,
2810
+ laneId: input.laneId,
2811
+ authority: baseAuthority,
2812
+ };
2813
+ }
2814
+ const observedAt = (input.now ? input.now() : new Date()).toISOString();
2815
+ const messagesTimeoutMs = input.messagesTimeoutMs ?? 3_000;
2816
+ const parentSessionRef = input.parentSessionRef ?? "ses-managed-dispatch";
2817
+ const taskId = input.laneId.startsWith("task-") ? input.laneId : `task-${input.laneId}`;
2818
+ // Read the child session once (no nudge/abort). Handle both legacy sessionID
2819
+ // and current { path: { id } } SDK message shapes plus a timeout.
2820
+ let raw = null;
2821
+ const messages = input.client.session.messages;
2822
+ if (typeof messages === "function") {
2823
+ try {
2824
+ const readMessages = async () => {
2825
+ const current = await messages.call(input.client.session, { sessionID: input.childSessionId });
2826
+ const currentRecord = asRecord(current);
2827
+ const currentData = asRecord(responseData(current));
2828
+ if (currentRecord?.error === undefined && currentData?.error === undefined)
2829
+ return current;
2830
+ return messages.call(input.client.session, { path: { id: input.childSessionId } });
2831
+ };
2832
+ raw = await Promise.race([
2833
+ readMessages(),
2834
+ new Promise((resolve) => setTimeout(() => resolve(null), messagesTimeoutMs)),
2835
+ ]);
2836
+ }
2837
+ catch {
2838
+ raw = null;
2839
+ }
2840
+ }
2841
+ const observed = raw === null ? undefined : observeFlowDeskAgentTaskOutputV1(raw);
2842
+ const latestText = observed?.latestText;
2843
+ const writeLifecycle = (state, outputRef, evidenceId) => {
2844
+ const record = {
2845
+ schema_version: "flowdesk.lane_lifecycle_record.v1",
2846
+ lane_id: input.laneId,
2847
+ workflow_id: input.workflowId,
2848
+ attempt_id: input.attemptId,
2849
+ parent_session_ref: parentSessionRef,
2850
+ child_session_ref: input.childSessionId.startsWith("ses-") ? input.childSessionId : `ses-${input.childSessionId}`,
2851
+ agent_ref: input.agentRef,
2852
+ provider_qualified_model_id: input.providerQualifiedModelId,
2853
+ state,
2854
+ ...(outputRef === undefined ? {} : { output_ref: outputRef }),
2855
+ timeout_ms: 0,
2856
+ orphan_max_age_ms: 0,
2857
+ retry_count: 0,
2858
+ created_at: observedAt,
2859
+ updated_at: observedAt,
2860
+ dispatch_authority_enabled: false,
2861
+ providerCall: false,
2862
+ actualLaneLaunch: false,
2863
+ runtimeExecution: false,
2864
+ };
2865
+ const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
2866
+ workflowId: input.workflowId,
2867
+ evidenceId,
2868
+ record: record,
2869
+ });
2870
+ if (!prepared.ok || prepared.writeIntent === undefined)
2871
+ return false;
2872
+ const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [prepared.writeIntent]);
2873
+ return applied.ok && applied.writtenPaths.length > 0;
2874
+ };
2875
+ const reloadHas = (predicate) => {
2876
+ const reloaded = reloadFlowDeskSessionEvidenceV1({
2877
+ workflowId: input.workflowId,
2878
+ rootDir: input.rootDir,
2879
+ });
2880
+ return reloaded.ok && reloaded.blocked.length === 0 && reloaded.entries.some(predicate);
2881
+ };
2882
+ if (typeof latestText === "string" && latestText.trim().length > 0) {
2883
+ const truncated = latestText.length > MANAGED_DISPATCH_LANE_RESULT_MAX_TEXT;
2884
+ const storedText = truncated ? latestText.slice(0, MANAGED_DISPATCH_LANE_RESULT_MAX_TEXT) : latestText;
2885
+ const completionStatus = observed?.terminalObserved === true ? "final" : "partial";
2886
+ const finalizationReason = observed?.terminalObserved === true ? "terminal_marker" : "timeout_partial";
2887
+ const taskResultEvidenceId = `task-result-managed-dispatch-${input.laneId}`;
2888
+ const taskResultRecord = {
2889
+ schema_version: "flowdesk.task_result.v1",
2890
+ workflow_id: input.workflowId,
2891
+ lane_id: input.laneId,
2892
+ task_id: taskId,
2893
+ agent_ref: input.agentRef,
2894
+ provider_qualified_model_id: input.providerQualifiedModelId,
2895
+ task_prompt_sha256: createHash("sha256").update("managed-dispatch-lane-finalize").digest("hex"),
2896
+ result_text: storedText,
2897
+ result_text_truncated: truncated,
2898
+ result_text_sha256: createHash("sha256").update(latestText).digest("hex"),
2899
+ completion_status: completionStatus,
2900
+ output_kind: observed?.outputKind ?? "final_answer",
2901
+ usable_for_synthesis: observed?.usableForSynthesis ?? true,
2902
+ missing_contract: false,
2903
+ finalization_reason: finalizationReason,
2904
+ looks_like_refusal_or_error: observed?.looksLikeRefusalOrError ?? false,
2905
+ created_at: observedAt,
2906
+ dispatch_authority_enabled: false,
2907
+ };
2908
+ const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
2909
+ workflowId: input.workflowId,
2910
+ evidenceId: taskResultEvidenceId,
2911
+ record: taskResultRecord,
2912
+ });
2913
+ if (!prepared.ok || prepared.writeIntent === undefined)
2914
+ return blocked(prepared.errors.join(", ") || "task_result evidence intent invalid");
2915
+ const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [prepared.writeIntent]);
2916
+ if (!applied.ok || applied.writtenPaths.length === 0)
2917
+ return blocked("task_result evidence write failed");
2918
+ if (!reloadHas((entry) => entry.evidenceClass === "task_result" && entry.evidenceId === taskResultEvidenceId && entry.record.lane_id === input.laneId))
2919
+ return blocked("task_result evidence reload verification failed");
2920
+ const terminalLifecycleEvidenceId = `lifecycle-managed-dispatch-terminal-${input.laneId}`;
2921
+ if (!writeLifecycle("incomplete", `output-${taskResultEvidenceId}`, terminalLifecycleEvidenceId))
2922
+ return blocked("terminal lane_lifecycle evidence write failed");
2923
+ if (!reloadHas((entry) => entry.evidenceClass === "lane_lifecycle" && entry.evidenceId === terminalLifecycleEvidenceId && entry.record.lane_id === input.laneId && entry.record.state === "incomplete"))
2924
+ return blocked("terminal lane_lifecycle evidence reload verification failed");
2925
+ return {
2926
+ adapterProfile: "managed_dispatch_lane_finalize_observer",
2927
+ status: "lane_finalized",
2928
+ workflowId: input.workflowId,
2929
+ laneId: input.laneId,
2930
+ taskResultEvidenceId,
2931
+ terminalLifecycleEvidenceId,
2932
+ finalizationReason,
2933
+ completionStatus,
2934
+ looksLikeRefusalOrError: observed?.looksLikeRefusalOrError ?? false,
2935
+ authority: baseAuthority,
2936
+ };
2937
+ }
2938
+ // No usable text observed — record a terminal no_output lifecycle so the lane
2939
+ // stops projecting as running/stalled. No task_failed authority is implied.
2940
+ const terminalLifecycleEvidenceId = `lifecycle-managed-dispatch-terminal-${input.laneId}`;
2941
+ if (!writeLifecycle("no_output", undefined, terminalLifecycleEvidenceId))
2942
+ return blocked("terminal no_output lane_lifecycle evidence write failed");
2943
+ if (!reloadHas((entry) => entry.evidenceClass === "lane_lifecycle" && entry.evidenceId === terminalLifecycleEvidenceId && entry.record.lane_id === input.laneId && entry.record.state === "no_output"))
2944
+ return blocked("terminal no_output lane_lifecycle evidence reload verification failed");
2945
+ return {
2946
+ adapterProfile: "managed_dispatch_lane_finalize_observer",
2947
+ status: "lane_no_output",
2948
+ workflowId: input.workflowId,
2949
+ laneId: input.laneId,
2950
+ terminalLifecycleEvidenceId,
2951
+ authority: baseAuthority,
2952
+ };
2953
+ }
2665
2954
  export function materializeFlowDeskRuntimeLaneCompleteLifecycleEvidenceV1(input) {
2666
2955
  if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
2667
2956
  return blockRuntimeLaneLaunchLifecycle({
@@ -2836,6 +3125,35 @@ function dispatchOptions(request, model, text) {
2836
3125
  },
2837
3126
  };
2838
3127
  }
3128
+ function managedDispatchLaneLaunchPlan(input) {
3129
+ const laneId = input.request.laneId ?? `lane-${input.manifest.attempt_id}`;
3130
+ return planFlowDeskRuntimeLaneLaunchV1({
3131
+ request: {
3132
+ schema_version: "flowdesk.runtime_lane_launch_request.v1",
3133
+ launch_request_id: input.request.launchRequestId ??
3134
+ `launch-request-${input.manifest.attempt_id}`,
3135
+ workflow_id: input.manifest.workflow_id,
3136
+ attempt_id: input.manifest.attempt_id,
3137
+ lane_id: laneId,
3138
+ parent_session_ref: refFrom("ses", input.request.sessionId),
3139
+ agent_ref: refFrom("agent", input.request.agent),
3140
+ provider_qualified_model_id: input.request.provider_qualified_model_id,
3141
+ launch_reason: "managed_dispatch",
3142
+ pre_launch_audit_ref: input.manifest.pre_dispatch_audit_ref,
3143
+ lane_launch_approval_ref: input.manifest.consumed_approval_ref,
3144
+ requested_at: input.manifest.created_at,
3145
+ timeout_ms: 60_000,
3146
+ orphan_max_age_ms: 180_000,
3147
+ retry_budget: 0,
3148
+ dispatch_authority_enabled: false,
3149
+ providerCall: false,
3150
+ actualLaneLaunch: false,
3151
+ runtimeExecution: false,
3152
+ },
3153
+ sdkClientAvailable: input.sdkClientAvailable,
3154
+ durableEvidenceRootRef: `evidence-root-${input.manifest.workflow_id}`,
3155
+ });
3156
+ }
2839
3157
  export async function dispatchManagedDispatchBetaPromptV1(input) {
2840
3158
  const guardDecision = evaluateManagedDispatchBetaGuardBoundaryV1(input.boundaryInput);
2841
3159
  if (guardDecision.status !== "eligible")
@@ -2907,6 +3225,132 @@ export async function dispatchManagedDispatchBetaPromptV1(input) {
2907
3225
  if (!reservation.ok || reservation.reservationEvidenceReloaded !== true) {
2908
3226
  return blocked(input.boundaryInput, guardDecision, `Dispatch idempotency reservation materialization blocked: ${reservation.redactedFailureReason ?? "reload not proven"}.`);
2909
3227
  }
3228
+ const workingModelGate = workingModelCacheAllowsDispatch({
3229
+ durableStateRootDir: input.durableStateRootDir,
3230
+ providerQualifiedModelId: approvedProviderQualifiedModelId,
3231
+ });
3232
+ if (!workingModelGate.ok) {
3233
+ return blocked(input.boundaryInput, guardDecision, workingModelGate.reason);
3234
+ }
3235
+ const dispatchMode = input.request.dispatchMode ?? "prompt";
3236
+ if (dispatchMode === "lane_launch") {
3237
+ const launchPlan = managedDispatchLaneLaunchPlan({
3238
+ boundaryInput: input.boundaryInput,
3239
+ request: input.request,
3240
+ manifest: input.dispatchManifest,
3241
+ sdkClientAvailable: input.client.session.create !== undefined,
3242
+ });
3243
+ const launchResult = await launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1({
3244
+ client: input.client,
3245
+ launchPlan,
3246
+ request: {
3247
+ allowActualLaneLaunch: input.request.allowActualLaneLaunch === true,
3248
+ parentSessionId: input.request.sessionId,
3249
+ promptText: text,
3250
+ ...(input.request.directory === undefined
3251
+ ? {}
3252
+ : { directory: input.request.directory }),
3253
+ dispatchMethod,
3254
+ ...(input.request.laneTitle === undefined
3255
+ ? {}
3256
+ : { title: input.request.laneTitle }),
3257
+ },
3258
+ });
3259
+ if (launchResult.status !== "lane_launch_started") {
3260
+ return {
3261
+ adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
3262
+ status: "dispatch_failed",
3263
+ dispatchAttempted: true,
3264
+ dispatchMethod,
3265
+ guardDecision,
3266
+ sessionId: input.request.sessionId,
3267
+ agent: input.request.agent,
3268
+ model: runtimeModel,
3269
+ ...(input.request.directory === undefined
3270
+ ? {}
3271
+ : { directory: input.request.directory }),
3272
+ redactedErrorCategory: "runtime",
3273
+ authority: { ...enabledDispatchAuthority(), runtimeExecution: false },
3274
+ verification: verificationFor(input.boundaryInput),
3275
+ };
3276
+ }
3277
+ if (input.durableStateRootDir !== undefined) {
3278
+ const lifecycle = materializeFlowDeskRuntimeLaneLaunchLifecycleEvidenceV1({
3279
+ rootDir: input.durableStateRootDir,
3280
+ launchPlan,
3281
+ launchResult,
3282
+ evidenceId: `lifecycle-managed-dispatch-${launchPlan.lane_id}`,
3283
+ observedAt: new Date().toISOString(),
3284
+ timeoutMs: 60_000,
3285
+ orphanMaxAgeMs: 180_000,
3286
+ retryCount: 0,
3287
+ });
3288
+ if (lifecycle.status !== "lane_lifecycle_recorded") {
3289
+ return {
3290
+ adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
3291
+ status: "dispatch_failed",
3292
+ dispatchAttempted: true,
3293
+ dispatchMethod,
3294
+ guardDecision,
3295
+ sessionId: input.request.sessionId,
3296
+ agent: input.request.agent,
3297
+ model: runtimeModel,
3298
+ ...(input.request.directory === undefined
3299
+ ? {}
3300
+ : { directory: input.request.directory }),
3301
+ redactedErrorCategory: "runtime",
3302
+ authority: { ...enabledDispatchAuthority(), runtimeExecution: false },
3303
+ verification: verificationFor(input.boundaryInput),
3304
+ };
3305
+ }
3306
+ }
3307
+ const completedRecord = input.reservationStore.recordDispatchCompleted === undefined
3308
+ ? { ok: true, reservationEvidenceReloaded: true }
3309
+ : await input.reservationStore.recordDispatchCompleted({
3310
+ manifest: input.dispatchManifest,
3311
+ reloadedEvidence: input.reloadedEvidence,
3312
+ });
3313
+ if (!completedRecord.ok || completedRecord.reservationEvidenceReloaded !== true) {
3314
+ return {
3315
+ adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
3316
+ status: "dispatch_failed",
3317
+ dispatchAttempted: true,
3318
+ dispatchMethod,
3319
+ guardDecision,
3320
+ sessionId: input.request.sessionId,
3321
+ agent: input.request.agent,
3322
+ model: runtimeModel,
3323
+ ...(input.request.directory === undefined
3324
+ ? {}
3325
+ : { directory: input.request.directory }),
3326
+ redactedErrorCategory: "runtime",
3327
+ authority: { ...enabledDispatchAuthority(), runtimeExecution: false },
3328
+ verification: verificationFor(input.boundaryInput),
3329
+ };
3330
+ }
3331
+ return {
3332
+ adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
3333
+ status: "dispatch_accepted",
3334
+ dispatchAttempted: true,
3335
+ dispatchMethod,
3336
+ guardDecision,
3337
+ sessionId: input.request.sessionId,
3338
+ agent: input.request.agent,
3339
+ model: runtimeModel,
3340
+ ...(input.request.directory === undefined
3341
+ ? {}
3342
+ : { directory: input.request.directory }),
3343
+ ...(launchResult.laneId === undefined ? {} : { laneId: launchResult.laneId }),
3344
+ ...(launchResult.childSessionRef === undefined
3345
+ ? {}
3346
+ : { childSessionRef: launchResult.childSessionRef }),
3347
+ ...(launchResult.messageRef === undefined
3348
+ ? {}
3349
+ : { messageRef: launchResult.messageRef }),
3350
+ authority: { ...enabledDispatchAuthority(), actualLaneLaunch: true },
3351
+ verification: verificationFor(input.boundaryInput),
3352
+ };
3353
+ }
2910
3354
  const options = dispatchOptions(input.request, runtimeModel, text);
2911
3355
  let response;
2912
3356
  try {
@@ -2936,6 +3380,30 @@ export async function dispatchManagedDispatchBetaPromptV1(input) {
2936
3380
  verification: verificationFor(input.boundaryInput),
2937
3381
  };
2938
3382
  }
3383
+ const completedRecord = input.reservationStore.recordDispatchCompleted === undefined
3384
+ ? { ok: true, reservationEvidenceReloaded: true }
3385
+ : await input.reservationStore.recordDispatchCompleted({
3386
+ manifest: input.dispatchManifest,
3387
+ reloadedEvidence: input.reloadedEvidence,
3388
+ });
3389
+ if (!completedRecord.ok || completedRecord.reservationEvidenceReloaded !== true) {
3390
+ return {
3391
+ adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
3392
+ status: "dispatch_failed",
3393
+ dispatchAttempted: true,
3394
+ dispatchMethod,
3395
+ guardDecision,
3396
+ sessionId: input.request.sessionId,
3397
+ agent: input.request.agent,
3398
+ model: runtimeModel,
3399
+ ...(input.request.directory === undefined
3400
+ ? {}
3401
+ : { directory: input.request.directory }),
3402
+ redactedErrorCategory: "runtime",
3403
+ authority: { ...enabledDispatchAuthority(), runtimeExecution: false },
3404
+ verification: verificationFor(input.boundaryInput),
3405
+ };
3406
+ }
2939
3407
  return {
2940
3408
  adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
2941
3409
  status: dispatchMethod === "promptAsync"