@flowdesk/opencode-plugin 0.1.14 → 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.
- package/README.md +1 -1
- package/dist/agent-task-output.d.ts +12 -0
- package/dist/agent-task-output.d.ts.map +1 -1
- package/dist/agent-task-output.js +110 -4
- package/dist/agent-task-output.js.map +1 -1
- package/dist/agent-task-runner.d.ts +12 -1
- package/dist/agent-task-runner.d.ts.map +1 -1
- package/dist/agent-task-runner.js +237 -16
- package/dist/agent-task-runner.js.map +1 -1
- package/dist/completion-ui-cache.d.ts.map +1 -1
- package/dist/completion-ui-cache.js +159 -29
- package/dist/completion-ui-cache.js.map +1 -1
- package/dist/event-hook-observer.d.ts.map +1 -1
- package/dist/event-hook-observer.js +66 -2
- package/dist/event-hook-observer.js.map +1 -1
- package/dist/managed-dispatch-adapter.d.ts +62 -0
- package/dist/managed-dispatch-adapter.d.ts.map +1 -1
- package/dist/managed-dispatch-adapter.js +465 -1
- package/dist/managed-dispatch-adapter.js.map +1 -1
- package/dist/model-selection-engine.d.ts +15 -2
- package/dist/model-selection-engine.d.ts.map +1 -1
- package/dist/model-selection-engine.js +109 -42
- package/dist/model-selection-engine.js.map +1 -1
- package/dist/provider-usage-live-tool.d.ts.map +1 -1
- package/dist/provider-usage-live-tool.js +135 -33
- package/dist/provider-usage-live-tool.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +52 -3
- package/dist/server.js.map +1 -1
- package/dist/stall-recovery.d.ts +4 -3
- package/dist/stall-recovery.d.ts.map +1 -1
- package/dist/stall-recovery.js +245 -25
- package/dist/stall-recovery.js.map +1 -1
- package/dist/status-live-tool.d.ts.map +1 -1
- package/dist/status-live-tool.js +1 -0
- package/dist/status-live-tool.js.map +1 -1
- package/dist/tui-subtask-activity.d.ts +4 -0
- package/dist/tui-subtask-activity.d.ts.map +1 -1
- package/dist/tui-subtask-activity.js +29 -24
- package/dist/tui-subtask-activity.js.map +1 -1
- package/dist/tui-usage-snapshot.d.ts.map +1 -1
- package/dist/tui-usage-snapshot.js +92 -6
- package/dist/tui-usage-snapshot.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +43 -16
- package/dist/tui.js.map +1 -1
- package/dist/workflow-assign-tool.d.ts.map +1 -1
- package/dist/workflow-assign-tool.js +21 -3
- package/dist/workflow-assign-tool.js.map +1 -1
- package/dist/workflow-dispatch-tool.d.ts +12 -0
- package/dist/workflow-dispatch-tool.d.ts.map +1 -1
- package/dist/workflow-dispatch-tool.js +24 -26
- package/dist/workflow-dispatch-tool.js.map +1 -1
- 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"
|