@flowdesk/opencode-plugin 0.1.14 → 0.1.16

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 (63) hide show
  1. package/README.md +6 -6
  2. package/dist/agent-task-output.d.ts +12 -0
  3. package/dist/agent-task-output.d.ts.map +1 -1
  4. package/dist/agent-task-output.js +110 -4
  5. package/dist/agent-task-output.js.map +1 -1
  6. package/dist/agent-task-runner.d.ts +12 -1
  7. package/dist/agent-task-runner.d.ts.map +1 -1
  8. package/dist/agent-task-runner.js +237 -16
  9. package/dist/agent-task-runner.js.map +1 -1
  10. package/dist/completion-ui-cache.d.ts.map +1 -1
  11. package/dist/completion-ui-cache.js +159 -29
  12. package/dist/completion-ui-cache.js.map +1 -1
  13. package/dist/event-hook-observer.d.ts.map +1 -1
  14. package/dist/event-hook-observer.js +66 -2
  15. package/dist/event-hook-observer.js.map +1 -1
  16. package/dist/index.d.ts +2 -18
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -17
  19. package/dist/index.js.map +1 -1
  20. package/dist/managed-dispatch-adapter.d.ts +62 -0
  21. package/dist/managed-dispatch-adapter.d.ts.map +1 -1
  22. package/dist/managed-dispatch-adapter.js +465 -1
  23. package/dist/managed-dispatch-adapter.js.map +1 -1
  24. package/dist/metadata.d.ts +19 -0
  25. package/dist/metadata.d.ts.map +1 -0
  26. package/dist/metadata.js +18 -0
  27. package/dist/metadata.js.map +1 -0
  28. package/dist/model-selection-engine.d.ts +15 -2
  29. package/dist/model-selection-engine.d.ts.map +1 -1
  30. package/dist/model-selection-engine.js +109 -42
  31. package/dist/model-selection-engine.js.map +1 -1
  32. package/dist/provider-usage-live-tool.d.ts.map +1 -1
  33. package/dist/provider-usage-live-tool.js +135 -33
  34. package/dist/provider-usage-live-tool.js.map +1 -1
  35. package/dist/server.d.ts +1 -0
  36. package/dist/server.d.ts.map +1 -1
  37. package/dist/server.js +53 -4
  38. package/dist/server.js.map +1 -1
  39. package/dist/stall-recovery.d.ts +4 -3
  40. package/dist/stall-recovery.d.ts.map +1 -1
  41. package/dist/stall-recovery.js +245 -25
  42. package/dist/stall-recovery.js.map +1 -1
  43. package/dist/status-live-tool.d.ts.map +1 -1
  44. package/dist/status-live-tool.js +1 -0
  45. package/dist/status-live-tool.js.map +1 -1
  46. package/dist/tui-subtask-activity.d.ts +4 -0
  47. package/dist/tui-subtask-activity.d.ts.map +1 -1
  48. package/dist/tui-subtask-activity.js +29 -24
  49. package/dist/tui-subtask-activity.js.map +1 -1
  50. package/dist/tui-usage-snapshot.d.ts.map +1 -1
  51. package/dist/tui-usage-snapshot.js +92 -6
  52. package/dist/tui-usage-snapshot.js.map +1 -1
  53. package/dist/tui.d.ts.map +1 -1
  54. package/dist/tui.js +43 -16
  55. package/dist/tui.js.map +1 -1
  56. package/dist/workflow-assign-tool.d.ts.map +1 -1
  57. package/dist/workflow-assign-tool.js +21 -3
  58. package/dist/workflow-assign-tool.js.map +1 -1
  59. package/dist/workflow-dispatch-tool.d.ts +12 -0
  60. package/dist/workflow-dispatch-tool.d.ts.map +1 -1
  61. package/dist/workflow-dispatch-tool.js +24 -26
  62. package/dist/workflow-dispatch-tool.js.map +1 -1
  63. 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":
@@ -2666,6 +2756,201 @@ export function materializeFlowDeskRuntimeLaneLaunchLifecycleEvidenceV1(input) {
2666
2756
  authority: runtimeLaneLaunchLifecycleAuthority(true),
2667
2757
  };
2668
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
+ }
2669
2954
  export function materializeFlowDeskRuntimeLaneCompleteLifecycleEvidenceV1(input) {
2670
2955
  if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
2671
2956
  return blockRuntimeLaneLaunchLifecycle({
@@ -2840,6 +3125,35 @@ function dispatchOptions(request, model, text) {
2840
3125
  },
2841
3126
  };
2842
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
+ }
2843
3157
  export async function dispatchManagedDispatchBetaPromptV1(input) {
2844
3158
  const guardDecision = evaluateManagedDispatchBetaGuardBoundaryV1(input.boundaryInput);
2845
3159
  if (guardDecision.status !== "eligible")
@@ -2911,6 +3225,132 @@ export async function dispatchManagedDispatchBetaPromptV1(input) {
2911
3225
  if (!reservation.ok || reservation.reservationEvidenceReloaded !== true) {
2912
3226
  return blocked(input.boundaryInput, guardDecision, `Dispatch idempotency reservation materialization blocked: ${reservation.redactedFailureReason ?? "reload not proven"}.`);
2913
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
+ }
2914
3354
  const options = dispatchOptions(input.request, runtimeModel, text);
2915
3355
  let response;
2916
3356
  try {
@@ -2940,6 +3380,30 @@ export async function dispatchManagedDispatchBetaPromptV1(input) {
2940
3380
  verification: verificationFor(input.boundaryInput),
2941
3381
  };
2942
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
+ }
2943
3407
  return {
2944
3408
  adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
2945
3409
  status: dispatchMethod === "promptAsync"