@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.
- package/README.md +1 -1
- package/dist/agent-task-output.d.ts +29 -0
- package/dist/agent-task-output.d.ts.map +1 -0
- package/dist/agent-task-output.js +225 -0
- package/dist/agent-task-output.js.map +1 -0
- package/dist/agent-task-runner.d.ts +34 -0
- package/dist/agent-task-runner.d.ts.map +1 -1
- package/dist/agent-task-runner.js +634 -84
- package/dist/agent-task-runner.js.map +1 -1
- package/dist/auto-continue-preview-tool.d.ts +36 -0
- package/dist/auto-continue-preview-tool.d.ts.map +1 -0
- package/dist/auto-continue-preview-tool.js +119 -0
- package/dist/auto-continue-preview-tool.js.map +1 -0
- package/dist/completion-ui-cache.d.ts +6 -0
- package/dist/completion-ui-cache.d.ts.map +1 -0
- package/dist/completion-ui-cache.js +390 -0
- package/dist/completion-ui-cache.js.map +1 -0
- package/dist/event-hook-observer.d.ts +14 -0
- package/dist/event-hook-observer.d.ts.map +1 -0
- package/dist/event-hook-observer.js +257 -0
- package/dist/event-hook-observer.js.map +1 -0
- 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 +472 -4
- package/dist/managed-dispatch-adapter.js.map +1 -1
- package/dist/model-selection-engine.d.ts +60 -0
- package/dist/model-selection-engine.d.ts.map +1 -0
- package/dist/model-selection-engine.js +242 -0
- package/dist/model-selection-engine.js.map +1 -0
- package/dist/provider-usage-live-tool.d.ts +10 -0
- package/dist/provider-usage-live-tool.d.ts.map +1 -1
- package/dist/provider-usage-live-tool.js +262 -33
- package/dist/provider-usage-live-tool.js.map +1 -1
- package/dist/server.d.ts +36 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +497 -20
- package/dist/server.js.map +1 -1
- package/dist/stall-recovery.d.ts +34 -0
- package/dist/stall-recovery.d.ts.map +1 -1
- package/dist/stall-recovery.js +680 -3
- package/dist/stall-recovery.js.map +1 -1
- package/dist/status-live-tool.d.ts +54 -0
- package/dist/status-live-tool.d.ts.map +1 -1
- package/dist/status-live-tool.js +449 -44
- package/dist/status-live-tool.js.map +1 -1
- package/dist/tui-subtask-activity.d.ts +73 -0
- package/dist/tui-subtask-activity.d.ts.map +1 -0
- package/dist/tui-subtask-activity.js +271 -0
- package/dist/tui-subtask-activity.js.map +1 -0
- package/dist/tui-usage-snapshot.d.ts +14 -0
- package/dist/tui-usage-snapshot.d.ts.map +1 -1
- package/dist/tui-usage-snapshot.js +275 -8
- package/dist/tui-usage-snapshot.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +102 -44
- package/dist/tui.js.map +1 -1
- package/dist/workflow-assign-tool.d.ts +23 -0
- package/dist/workflow-assign-tool.d.ts.map +1 -0
- package/dist/workflow-assign-tool.js +135 -0
- package/dist/workflow-assign-tool.js.map +1 -0
- package/dist/workflow-author-tool.d.ts +29 -0
- package/dist/workflow-author-tool.d.ts.map +1 -0
- package/dist/workflow-author-tool.js +227 -0
- package/dist/workflow-author-tool.js.map +1 -0
- 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 +31 -3
- package/dist/workflow-dispatch-tool.js.map +1 -1
- package/dist/workflow-orchestrator.d.ts +31 -0
- package/dist/workflow-orchestrator.d.ts.map +1 -0
- package/dist/workflow-orchestrator.js +160 -0
- package/dist/workflow-orchestrator.js.map +1 -0
- package/dist/workflow-scheduler.d.ts.map +1 -1
- package/dist/workflow-scheduler.js +3 -1
- package/dist/workflow-scheduler.js.map +1 -1
- package/dist/workflow-synthesis-tool.d.ts +31 -0
- package/dist/workflow-synthesis-tool.d.ts.map +1 -0
- package/dist/workflow-synthesis-tool.js +194 -0
- package/dist/workflow-synthesis-tool.js.map +1 -0
- 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
|
-
|
|
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"
|