@flowdesk/opencode-plugin 0.1.1 → 0.1.2
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/dist/command-handlers.d.ts +5 -1
- package/dist/command-handlers.d.ts.map +1 -1
- package/dist/command-handlers.js +74 -1
- package/dist/command-handlers.js.map +1 -1
- package/dist/lane-heartbeat-writer.d.ts +43 -0
- package/dist/lane-heartbeat-writer.d.ts.map +1 -0
- package/dist/lane-heartbeat-writer.js +133 -0
- package/dist/lane-heartbeat-writer.js.map +1 -0
- package/dist/local-adapter.d.ts +8 -1
- package/dist/local-adapter.d.ts.map +1 -1
- package/dist/local-adapter.js +62 -5
- package/dist/local-adapter.js.map +1 -1
- package/dist/managed-dispatch-adapter.d.ts +506 -1
- package/dist/managed-dispatch-adapter.d.ts.map +1 -1
- package/dist/managed-dispatch-adapter.js +2624 -38
- package/dist/managed-dispatch-adapter.js.map +1 -1
- package/dist/provider-usage-live-tool.d.ts +59 -0
- package/dist/provider-usage-live-tool.d.ts.map +1 -0
- package/dist/provider-usage-live-tool.js +259 -0
- package/dist/provider-usage-live-tool.js.map +1 -0
- package/dist/quick-fallback-run.d.ts +54 -0
- package/dist/quick-fallback-run.d.ts.map +1 -0
- package/dist/quick-fallback-run.js +230 -0
- package/dist/quick-fallback-run.js.map +1 -0
- package/dist/quick-reviewer-run.d.ts +61 -0
- package/dist/quick-reviewer-run.d.ts.map +1 -0
- package/dist/quick-reviewer-run.js +397 -0
- package/dist/quick-reviewer-run.js.map +1 -0
- package/dist/runtime-reviewer-execution-bridge.d.ts +43 -0
- package/dist/runtime-reviewer-execution-bridge.d.ts.map +1 -0
- package/dist/runtime-reviewer-execution-bridge.js +313 -0
- package/dist/runtime-reviewer-execution-bridge.js.map +1 -0
- package/dist/server.d.ts +104 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1990 -104
- package/dist/server.js.map +1 -1
- package/dist/status-live-tool.d.ts +55 -0
- package/dist/status-live-tool.d.ts.map +1 -0
- package/dist/status-live-tool.js +215 -0
- package/dist/status-live-tool.js.map +1 -0
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { evaluateFlowDeskChatIntakeV1, getFlowDeskPortableCommandToolName, getRelease1SchemaArtifact } from "@flowdesk/core";
|
|
1
|
+
import { applyFlowDeskSessionEvidenceWriteIntentsV1, createFlowDeskChatHookAuthorityProbeV1, evaluateFlowDeskChatIntakeV1, getFlowDeskPortableCommandToolName, getRelease1SchemaArtifact, materializeFlowDeskExactModelCacheEvidenceFromProviderAcquisitionEvidenceV1, materializeFlowDeskRuntimeLaneLaunchPlansFromReviewerFanoutEvidenceV1, planFlowDeskReviewerFanoutFromReloadedCacheEvidenceV1, prepareFlowDeskSessionEvidenceWriteIntentV1, reloadFlowDeskSessionEvidenceV1, validateFlowDeskDefaultManagedDispatchAuthorizationV1, validateRunRequestV1, } from "@flowdesk/core";
|
|
2
2
|
import { tool } from "@opencode-ai/plugin";
|
|
3
|
-
import { flowdeskPluginId, flowdeskPluginScaffold, hasProductionOpenCodeRegistration } from "./index.js";
|
|
4
|
-
import { createFlowDeskLocalNonDispatchAdapterSession, flowdeskLocalNonDispatchAdapterProfile } from "./local-adapter.js";
|
|
5
|
-
import { dispatchManagedDispatchBetaPromptV1 } from "./managed-dispatch-adapter.js";
|
|
6
|
-
import {
|
|
3
|
+
import { flowdeskPluginId, flowdeskPluginScaffold, hasProductionOpenCodeRegistration, } from "./index.js";
|
|
4
|
+
import { createFlowDeskLocalNonDispatchAdapterSession, flowdeskLocalNonDispatchAdapterProfile, } from "./local-adapter.js";
|
|
5
|
+
import { createFlowDeskManagedDispatchBetaDurableReservationStoreV1, createFlowDeskOpenCodeMetadataProviderAcquisitionClientV1, createFlowDeskOpenCodePromptBackedProviderAcquisitionClientV1, dispatchManagedDispatchBetaPromptV1, materializeFlowDeskManagedFallbackRegatePlanEvidenceV1, orchestrateFlowDeskManagedFallbackRegateV1, runFlowDeskExactModelProviderAcquisitionLiveTestV1, } from "./managed-dispatch-adapter.js";
|
|
6
|
+
import { executeFlowDeskRuntimeReviewerExecutionBridgeV1, redactedRuntimeReviewerExecutionBlocked, runtimeReviewerExecutionExpectationsFromValue, } from "./runtime-reviewer-execution-bridge.js";
|
|
7
|
+
import { executeFlowDeskQuickReviewerRunV1, } from "./quick-reviewer-run.js";
|
|
8
|
+
import { executeFlowDeskProviderUsageLiveV1, } from "./provider-usage-live-tool.js";
|
|
9
|
+
import { executeFlowDeskStatusLiveV1, } from "./status-live-tool.js";
|
|
10
|
+
import { executeFlowDeskQuickFallbackRunV1, } from "./quick-fallback-run.js";
|
|
11
|
+
import { recordFlowDeskLaneHeartbeatV1, } from "./lane-heartbeat-writer.js";
|
|
12
|
+
import { FLOWDESK_PRE_SPIKE_PLUGIN_TOOL_STUBS, getFlowDeskRelease1HandlerReadinessSummary, getFlowDeskRelease1ProductionReadinessSummary, hasPassingFds1SchemaConversionSpike, runFlowDeskPreSpikePluginToolStub, } from "./tool-stubs.js";
|
|
7
13
|
export const flowdeskPreSpikeDoctorToolName = "flowdesk_pre_spike_doctor";
|
|
8
14
|
export const flowdeskChatIntakeToolName = "flowdesk_chat_intake";
|
|
9
15
|
export const flowdeskFds1SchemaConversionProbeOption = "fds1SchemaConversionProbe";
|
|
@@ -11,8 +17,26 @@ export const flowdeskLocalNonDispatchAdapterOption = "localNonDispatchAdapter";
|
|
|
11
17
|
export const flowdeskNaturalLanguageRoutingOption = "naturalLanguageRouting";
|
|
12
18
|
export const flowdeskDurableStateRootOption = "durableStateRoot";
|
|
13
19
|
export const flowdeskProductionEnablementOption = "productionEnablement";
|
|
20
|
+
export const flowdeskReviewerFanoutDiagnosticsOption = "reviewerFanoutDiagnostics";
|
|
14
21
|
export const flowdeskManagedDispatchBetaAdapterOption = "managedDispatchBetaAdapter";
|
|
22
|
+
export const flowdeskExactModelProviderAcquisitionLiveTestOption = "exactModelProviderAcquisitionLiveTest";
|
|
23
|
+
export const flowdeskRuntimeReviewerExecutionOption = "runtimeReviewerExecution";
|
|
24
|
+
export const flowdeskManagedFallbackRegateOption = "managedFallbackRegate";
|
|
25
|
+
export const flowdeskQuickReviewerRunOption = "quickReviewerRun";
|
|
26
|
+
export const flowdeskProviderUsageLiveOption = "providerUsageLive";
|
|
27
|
+
export const flowdeskStatusLiveOption = "statusLive";
|
|
28
|
+
export const flowdeskQuickFallbackRunOption = "quickFallbackRun";
|
|
29
|
+
export const flowdeskLaneHeartbeatWriterOption = "laneHeartbeatWriter";
|
|
30
|
+
export const flowdeskDefaultManagedDispatchAuthorizationOption = "defaultManagedDispatchAuthorization";
|
|
15
31
|
export const flowdeskManagedDispatchBetaToolName = "flowdesk_managed_dispatch_beta";
|
|
32
|
+
export const flowdeskExactModelProviderAcquisitionLiveTestToolName = "flowdesk_exact_model_provider_acquisition_live_test";
|
|
33
|
+
export const flowdeskRuntimeReviewerExecutionToolName = "flowdesk_runtime_reviewer_execution";
|
|
34
|
+
export const flowdeskManagedFallbackRegateToolName = "flowdesk_managed_fallback_regate";
|
|
35
|
+
export const flowdeskQuickReviewerRunToolName = "flowdesk_quick_reviewer_run";
|
|
36
|
+
export const flowdeskProviderUsageLiveToolName = "flowdesk_provider_usage_live";
|
|
37
|
+
export const flowdeskStatusLiveToolName = "flowdesk_status_live";
|
|
38
|
+
export const flowdeskQuickFallbackRunToolName = "flowdesk_quick_fallback_run";
|
|
39
|
+
export const flowdeskLaneHeartbeatWriterToolName = "flowdesk_lane_heartbeat_record";
|
|
16
40
|
const flowdeskChatSuggestionDuplicateWindowMs = 10_000;
|
|
17
41
|
const disabledAuthority = {
|
|
18
42
|
productionRegistrationEligible: false,
|
|
@@ -22,15 +46,47 @@ const disabledAuthority = {
|
|
|
22
46
|
providerCall: false,
|
|
23
47
|
runtimeExecution: false,
|
|
24
48
|
fallbackAuthority: false,
|
|
25
|
-
hardCancelOrNoReplyAuthority: false
|
|
49
|
+
hardCancelOrNoReplyAuthority: false,
|
|
26
50
|
};
|
|
27
51
|
function isRecord(value) {
|
|
28
52
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
53
|
}
|
|
54
|
+
function hasUnsupportedHardChatReturnFields(value) {
|
|
55
|
+
if (!isRecord(value))
|
|
56
|
+
return false;
|
|
57
|
+
return ["noReply", "cancel", "stop"].some((key) => key in value);
|
|
58
|
+
}
|
|
59
|
+
export function createFlowDeskChatHookAuthorityProbeFromObservationV1(input) {
|
|
60
|
+
const unsupportedHardChatReturn = hasUnsupportedHardChatReturnFields(input.returnValue);
|
|
61
|
+
return createFlowDeskChatHookAuthorityProbeV1({
|
|
62
|
+
probeId: safeToken(input.probeId, "chat-hook-probe"),
|
|
63
|
+
chatHookRef: safeToken(input.chatHookRef, "chat-message-hook"),
|
|
64
|
+
observedAt: input.observedAt,
|
|
65
|
+
mutationObserved: input.afterPartsCount > input.beforePartsCount,
|
|
66
|
+
throwBlocksReply: input.hookThrew === true && input.throwBlockedReplyObserved === true,
|
|
67
|
+
noReplySupported: false,
|
|
68
|
+
cancelOrStopSupported: false,
|
|
69
|
+
duplicateAssistantReplyObserved: input.duplicateAssistantReplyObserved === true,
|
|
70
|
+
timeoutOrNullFailClosed: input.timeoutOrNullFailClosed === true,
|
|
71
|
+
malformedReturnFailClosed: unsupportedHardChatReturn
|
|
72
|
+
? false
|
|
73
|
+
: input.malformedReturnFailClosed === true,
|
|
74
|
+
evidenceRefs: input.evidenceRefs,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
30
77
|
function isManagedDispatchBetaClient(value) {
|
|
31
78
|
if (!isRecord(value) || !isRecord(value.session))
|
|
32
79
|
return false;
|
|
33
|
-
return typeof value.session.prompt === "function" ||
|
|
80
|
+
return (typeof value.session.prompt === "function" ||
|
|
81
|
+
typeof value.session.promptAsync === "function");
|
|
82
|
+
}
|
|
83
|
+
function isManagedDispatchBetaReservationStore(value) {
|
|
84
|
+
return (isRecord(value) &&
|
|
85
|
+
typeof value.reserve === "function" &&
|
|
86
|
+
typeof value.recordDispatchFailure === "function");
|
|
87
|
+
}
|
|
88
|
+
function isExactModelProviderAcquisitionClient(value) {
|
|
89
|
+
return isRecord(value) && typeof value.checkExactModelAvailability === "function";
|
|
34
90
|
}
|
|
35
91
|
function boundedText(value, fallback) {
|
|
36
92
|
const trimmed = value.trim();
|
|
@@ -42,10 +98,19 @@ function safeToken(value, fallback) {
|
|
|
42
98
|
return token.length > 0 ? token : fallback;
|
|
43
99
|
}
|
|
44
100
|
function commandNameFromAction(action) {
|
|
45
|
-
return action.startsWith("/flowdesk-") &&
|
|
101
|
+
return action.startsWith("/flowdesk-") &&
|
|
102
|
+
action !== "/flowdesk-explain-route" &&
|
|
103
|
+
action !== "/flowdesk-audit"
|
|
104
|
+
? action
|
|
105
|
+
: undefined;
|
|
46
106
|
}
|
|
47
107
|
function routedToolName(actions) {
|
|
48
|
-
const preferredActions = actions.length > 1
|
|
108
|
+
const preferredActions = actions.length > 1
|
|
109
|
+
? [
|
|
110
|
+
...actions.filter((action) => action !== "/flowdesk-status"),
|
|
111
|
+
...actions.filter((action) => action === "/flowdesk-status"),
|
|
112
|
+
]
|
|
113
|
+
: actions;
|
|
49
114
|
for (const action of preferredActions) {
|
|
50
115
|
const commandName = commandNameFromAction(action);
|
|
51
116
|
if (commandName === undefined)
|
|
@@ -63,10 +128,20 @@ function baseToolRequest(request, schemaVersion, optionalFields = chatEnvelopeOp
|
|
|
63
128
|
schema_version: schemaVersion,
|
|
64
129
|
request_id: safeToken(`${schemaVersion.split(".")[1] ?? "tool"}-${request.request_id}`, "request-chat-routed"),
|
|
65
130
|
input_mode: "chat_routed",
|
|
66
|
-
...(includeOptional.has("workflow_id") && request.workflow_id !== undefined
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
...(includeOptional.has("
|
|
131
|
+
...(includeOptional.has("workflow_id") && request.workflow_id !== undefined
|
|
132
|
+
? { workflow_id: request.workflow_id }
|
|
133
|
+
: {}),
|
|
134
|
+
...(includeOptional.has("session_ref") && request.session_ref !== undefined
|
|
135
|
+
? { session_ref: request.session_ref }
|
|
136
|
+
: {}),
|
|
137
|
+
...(includeOptional.has("redacted_intake_ref") &&
|
|
138
|
+
request.redacted_intake_ref !== undefined
|
|
139
|
+
? { redacted_intake_ref: request.redacted_intake_ref }
|
|
140
|
+
: {}),
|
|
141
|
+
...(includeOptional.has("user_approval_ref") &&
|
|
142
|
+
request.user_approval_ref !== undefined
|
|
143
|
+
? { user_approval_ref: request.user_approval_ref }
|
|
144
|
+
: {}),
|
|
70
145
|
};
|
|
71
146
|
}
|
|
72
147
|
function routedToolRequest(toolName, request, options = {}) {
|
|
@@ -76,21 +151,25 @@ function routedToolRequest(toolName, request, options = {}) {
|
|
|
76
151
|
...baseToolRequest(request, "flowdesk.plan.request.v1"),
|
|
77
152
|
goal_summary: summary,
|
|
78
153
|
scope_summary: "FlowDesk natural-language chat intake routed to command-backed planning.",
|
|
79
|
-
risk_hint: options.requiresConfirmation === true
|
|
154
|
+
risk_hint: options.requiresConfirmation === true
|
|
155
|
+
? "execution-like chat intake requires explicit user confirmation before any run"
|
|
156
|
+
: "ordinary Release 1 command-backed steering only",
|
|
80
157
|
};
|
|
81
158
|
}
|
|
82
159
|
if (toolName === "flowdesk_run") {
|
|
83
160
|
return {
|
|
84
161
|
...baseToolRequest(request, "flowdesk.run.request.v1"),
|
|
85
|
-
run_mode: /dry[\s_-]*run|드라이\s*런/i.test(summary)
|
|
162
|
+
run_mode: /dry[\s_-]*run|드라이\s*런/i.test(summary)
|
|
163
|
+
? "guarded-dry-run"
|
|
164
|
+
: "fake-runtime",
|
|
86
165
|
plan_revision_id: safeToken(`plan-${request.workflow_id ?? request.request_id}`, "plan-chat-routed"),
|
|
87
|
-
step_id: safeToken(`step-${request.request_id}`, "step-chat-routed")
|
|
166
|
+
step_id: safeToken(`step-${request.request_id}`, "step-chat-routed"),
|
|
88
167
|
};
|
|
89
168
|
}
|
|
90
169
|
if (toolName === "flowdesk_status") {
|
|
91
170
|
return {
|
|
92
171
|
...baseToolRequest(request, "flowdesk.status.request.v1"),
|
|
93
|
-
detail_level: "summary"
|
|
172
|
+
detail_level: "summary",
|
|
94
173
|
};
|
|
95
174
|
}
|
|
96
175
|
if (toolName === "flowdesk_doctor") {
|
|
@@ -98,57 +177,67 @@ function routedToolRequest(toolName, request, options = {}) {
|
|
|
98
177
|
...baseToolRequest(request, "flowdesk.doctor.request.v1", []),
|
|
99
178
|
check_scope: "all",
|
|
100
179
|
profile: "test",
|
|
101
|
-
persist_report: false
|
|
180
|
+
persist_report: false,
|
|
102
181
|
};
|
|
103
182
|
}
|
|
104
183
|
if (toolName === "flowdesk_resume") {
|
|
105
184
|
return {
|
|
106
185
|
...baseToolRequest(request, "flowdesk.resume.request.v1", []),
|
|
107
186
|
checkpoint_id: safeToken(`checkpoint-${request.workflow_id ?? request.request_id}`, "checkpoint-chat-routed"),
|
|
108
|
-
resume_mode: "status_only"
|
|
187
|
+
resume_mode: "status_only",
|
|
109
188
|
};
|
|
110
189
|
}
|
|
111
190
|
if (toolName === "flowdesk_retry") {
|
|
112
191
|
return {
|
|
113
192
|
...baseToolRequest(request, "flowdesk.retry.request.v1"),
|
|
114
193
|
attempt_id: safeToken(`attempt-${request.workflow_id ?? request.request_id}`, "attempt-chat-routed"),
|
|
115
|
-
retry_reason: "FlowDesk chat intake requested a non-dispatch retry diagnostic."
|
|
194
|
+
retry_reason: "FlowDesk chat intake requested a non-dispatch retry diagnostic.",
|
|
116
195
|
};
|
|
117
196
|
}
|
|
118
197
|
if (toolName === "flowdesk_abort") {
|
|
119
198
|
return {
|
|
120
199
|
...baseToolRequest(request, "flowdesk.abort.request.v1"),
|
|
121
200
|
workflow_id: safeToken(request.workflow_id ?? `workflow-${request.request_id}`, "workflow-chat-routed"),
|
|
122
|
-
reason: "FlowDesk chat intake requested a safe abort diagnostic."
|
|
201
|
+
reason: "FlowDesk chat intake requested a safe abort diagnostic.",
|
|
123
202
|
};
|
|
124
203
|
}
|
|
125
204
|
if (toolName === "flowdesk_usage") {
|
|
126
205
|
return {
|
|
127
206
|
...baseToolRequest(request, "flowdesk.usage.request.v1", []),
|
|
128
207
|
provider_family: "unknown",
|
|
129
|
-
refresh: false
|
|
208
|
+
refresh: false,
|
|
130
209
|
};
|
|
131
210
|
}
|
|
132
211
|
if (toolName === "flowdesk_export_debug") {
|
|
133
212
|
return {
|
|
134
213
|
...baseToolRequest(request, "flowdesk.export_debug.request.v1", []),
|
|
135
214
|
include_sections: ["redaction_summary"],
|
|
136
|
-
retention_hint: "keep_until_default_expiry"
|
|
215
|
+
retention_hint: "keep_until_default_expiry",
|
|
137
216
|
};
|
|
138
217
|
}
|
|
139
218
|
return baseToolRequest(request, "flowdesk.tool.request.v1");
|
|
140
219
|
}
|
|
141
220
|
function evaluateNaturalLanguageRouting(request, session) {
|
|
142
|
-
const evaluation = evaluateFlowDeskChatIntakeV1({
|
|
143
|
-
|
|
144
|
-
|
|
221
|
+
const evaluation = evaluateFlowDeskChatIntakeV1({
|
|
222
|
+
request,
|
|
223
|
+
chatIntakeMode: "steering",
|
|
224
|
+
hookHarnessMode: "enforce",
|
|
225
|
+
});
|
|
226
|
+
const toolName = evaluation.response.ok
|
|
227
|
+
? routedToolName(evaluation.response.safe_next_actions)
|
|
228
|
+
: undefined;
|
|
229
|
+
const routedToolResult = toolName === undefined
|
|
230
|
+
? undefined
|
|
231
|
+
: session.evaluate(toolName, routedToolRequest(toolName, request, {
|
|
232
|
+
requiresConfirmation: evaluation.response.route_decision === "ask_clarification",
|
|
233
|
+
}));
|
|
145
234
|
return {
|
|
146
235
|
schema_version: "flowdesk.chat_intake.routing_result.v1",
|
|
147
236
|
ok: evaluation.ok,
|
|
148
237
|
evaluation,
|
|
149
238
|
...(toolName === undefined ? {} : { routedToolName: toolName }),
|
|
150
239
|
...(routedToolResult === undefined ? {} : { routedToolResult }),
|
|
151
|
-
...disabledAuthority
|
|
240
|
+
...disabledAuthority,
|
|
152
241
|
};
|
|
153
242
|
}
|
|
154
243
|
function extractText(value) {
|
|
@@ -157,9 +246,15 @@ function extractText(value) {
|
|
|
157
246
|
if (!isRecord(value))
|
|
158
247
|
return "";
|
|
159
248
|
const direct = [value.text, value.content, value.message].filter((candidate) => typeof candidate === "string");
|
|
160
|
-
const partText = Array.isArray(value.parts)
|
|
161
|
-
|
|
162
|
-
|
|
249
|
+
const partText = Array.isArray(value.parts)
|
|
250
|
+
? value.parts.map(extractText)
|
|
251
|
+
: [];
|
|
252
|
+
const nestedMessage = isRecord(value.message)
|
|
253
|
+
? [extractText(value.message)]
|
|
254
|
+
: [];
|
|
255
|
+
return [...direct, ...partText, ...nestedMessage]
|
|
256
|
+
.filter((text) => text.length > 0)
|
|
257
|
+
.join(" ");
|
|
163
258
|
}
|
|
164
259
|
function intakeRequestFromChatMessage(input) {
|
|
165
260
|
const record = isRecord(input) ? input : {};
|
|
@@ -172,44 +267,68 @@ function intakeRequestFromChatMessage(input) {
|
|
|
172
267
|
session_ref: sessionRef,
|
|
173
268
|
redacted_intake_ref: `intake-${requestId}`,
|
|
174
269
|
intake_summary: boundedText(extractText(record.message ?? record), "FlowDesk chat message."),
|
|
175
|
-
source_surface: "chat.message"
|
|
270
|
+
source_surface: "chat.message",
|
|
176
271
|
};
|
|
177
272
|
}
|
|
178
273
|
function clockMs(clock) {
|
|
179
274
|
return (typeof clock === "function" ? clock() : clock).getTime();
|
|
180
275
|
}
|
|
181
276
|
function previewNaturalLanguageRouting(request) {
|
|
182
|
-
const evaluation = evaluateFlowDeskChatIntakeV1({
|
|
183
|
-
|
|
277
|
+
const evaluation = evaluateFlowDeskChatIntakeV1({
|
|
278
|
+
request,
|
|
279
|
+
chatIntakeMode: "steering",
|
|
280
|
+
hookHarnessMode: "enforce",
|
|
281
|
+
});
|
|
282
|
+
const toolName = evaluation.response.ok
|
|
283
|
+
? routedToolName(evaluation.response.safe_next_actions)
|
|
284
|
+
: undefined;
|
|
184
285
|
return { evaluation, toolName };
|
|
185
286
|
}
|
|
186
287
|
function mayCreatePendingConfirmation(preview) {
|
|
187
|
-
return preview.evaluation.response.route_decision === "ask_clarification" &&
|
|
288
|
+
return (preview.evaluation.response.route_decision === "ask_clarification" &&
|
|
289
|
+
preview.toolName === "flowdesk_plan");
|
|
188
290
|
}
|
|
189
291
|
function suggestionDuplicateKey(request, response) {
|
|
190
292
|
const suggestedNextStep = response.safe_next_actions[0] ?? "/flowdesk-status";
|
|
191
|
-
return [request.session_ref, response.route_decision, suggestedNextStep]
|
|
293
|
+
return [request.session_ref, response.route_decision, suggestedNextStep]
|
|
294
|
+
.map((part, index) => safeToken(part, `chat-card-${index}`))
|
|
295
|
+
.join("|");
|
|
192
296
|
}
|
|
193
297
|
function steeringText(result) {
|
|
194
298
|
const response = result.evaluation.response;
|
|
195
|
-
const actions = response.safe_next_actions.map((action) => action === "ask_clarification"
|
|
299
|
+
const actions = response.safe_next_actions.map((action) => action === "ask_clarification"
|
|
300
|
+
? "Confirm the goal or plan before FlowDesk suggests a run."
|
|
301
|
+
: action);
|
|
196
302
|
const suggestedNextStep = actions[0] ?? "/flowdesk-status";
|
|
197
|
-
const localState = isRecord(result.routedToolResult) &&
|
|
198
|
-
|
|
303
|
+
const localState = isRecord(result.routedToolResult) &&
|
|
304
|
+
isRecord(result.routedToolResult.localState)
|
|
305
|
+
? result.routedToolResult.localState
|
|
306
|
+
: undefined;
|
|
307
|
+
const confirmationRef = localState?.pendingConfirmationStatus === "pending" &&
|
|
308
|
+
typeof localState.pendingConfirmationRef === "string"
|
|
309
|
+
? localState.pendingConfirmationRef
|
|
310
|
+
: undefined;
|
|
199
311
|
const why = response.ok === false
|
|
200
312
|
? "This request needs capabilities that are not available in the safe FlowDesk mode."
|
|
201
|
-
: response.
|
|
202
|
-
? "FlowDesk
|
|
203
|
-
: response.route_decision === "
|
|
204
|
-
? "
|
|
205
|
-
:
|
|
313
|
+
: response.safe_next_actions[0] === "/flowdesk-usage"
|
|
314
|
+
? "This looks like a larger FlowDesk task, so usage should be checked before planning or running more work."
|
|
315
|
+
: response.route_decision === "ask_clarification"
|
|
316
|
+
? "FlowDesk needs confirmation or a clearer goal before suggesting a command-backed workflow."
|
|
317
|
+
: response.route_decision === "show_plan"
|
|
318
|
+
? "Your message looks like planning work that FlowDesk can organize as a command-backed plan."
|
|
319
|
+
: "Your message matches a safe FlowDesk command-backed workflow.";
|
|
206
320
|
return [
|
|
207
321
|
"FlowDesk",
|
|
208
322
|
`Suggested next step: ${suggestedNextStep}`,
|
|
209
323
|
`Why: ${why}`,
|
|
210
|
-
...(confirmationRef === undefined
|
|
324
|
+
...(confirmationRef === undefined
|
|
325
|
+
? []
|
|
326
|
+
: [
|
|
327
|
+
`Confirmation code: ${confirmationRef}`,
|
|
328
|
+
"To continue, review the plan and reply with this confirmation code plus explicit approval.",
|
|
329
|
+
]),
|
|
211
330
|
"Actions:",
|
|
212
|
-
...actions.map((action) => `- ${action}`)
|
|
331
|
+
...actions.map((action) => `- ${action}`),
|
|
213
332
|
].join("\n");
|
|
214
333
|
}
|
|
215
334
|
function enumValues(values) {
|
|
@@ -250,7 +369,10 @@ export function createFlowDeskFds1SchemaConversionProbeTools() {
|
|
|
250
369
|
if (artifact === undefined)
|
|
251
370
|
throw new Error(`missing FDS-1 schema artifact: ${stub.requestSchemaId}`);
|
|
252
371
|
const required = new Set(artifact.required);
|
|
253
|
-
const args = Object.fromEntries(Object.keys(artifact.properties).map((fieldName) => [
|
|
372
|
+
const args = Object.fromEntries(Object.keys(artifact.properties).map((fieldName) => [
|
|
373
|
+
fieldName,
|
|
374
|
+
zodForSchemaProperty(stub.requestSchemaId, fieldName, required.has(fieldName)),
|
|
375
|
+
]));
|
|
254
376
|
return [
|
|
255
377
|
stub.toolName,
|
|
256
378
|
tool({
|
|
@@ -258,27 +380,35 @@ export function createFlowDeskFds1SchemaConversionProbeTools() {
|
|
|
258
380
|
args,
|
|
259
381
|
async execute(request) {
|
|
260
382
|
return JSON.stringify(runFlowDeskPreSpikePluginToolStub(stub.toolName, request));
|
|
261
|
-
}
|
|
262
|
-
})
|
|
383
|
+
},
|
|
384
|
+
}),
|
|
263
385
|
];
|
|
264
386
|
}));
|
|
265
387
|
}
|
|
266
|
-
export function createFlowDeskLocalNonDispatchAdapterTools(now = new Date(), session = createFlowDeskLocalNonDispatchAdapterSession(now)) {
|
|
388
|
+
export function createFlowDeskLocalNonDispatchAdapterTools(now = new Date(), session = createFlowDeskLocalNonDispatchAdapterSession(now), managedDispatchRunRoute = {}) {
|
|
267
389
|
return Object.fromEntries(FLOWDESK_PRE_SPIKE_PLUGIN_TOOL_STUBS.map((stub) => {
|
|
268
390
|
const artifact = getRelease1SchemaArtifact(stub.requestSchemaId);
|
|
269
391
|
if (artifact === undefined)
|
|
270
392
|
throw new Error(`missing FDS-1 schema artifact: ${stub.requestSchemaId}`);
|
|
271
393
|
const required = new Set(artifact.required);
|
|
272
|
-
const args = Object.fromEntries(Object.keys(artifact.properties).map((fieldName) => [
|
|
394
|
+
const args = Object.fromEntries(Object.keys(artifact.properties).map((fieldName) => [
|
|
395
|
+
fieldName,
|
|
396
|
+
zodForSchemaProperty(stub.requestSchemaId, fieldName, required.has(fieldName)),
|
|
397
|
+
]));
|
|
273
398
|
return [
|
|
274
399
|
stub.toolName,
|
|
275
400
|
tool({
|
|
276
401
|
description: `FlowDesk local non-dispatch command adapter for ${stub.toolName}; no provider call, real dispatch, or lane launch.`,
|
|
277
402
|
args,
|
|
278
403
|
async execute(request) {
|
|
404
|
+
const record = isRecord(request) ? request : {};
|
|
405
|
+
if (stub.toolName === "flowdesk_run" &&
|
|
406
|
+
record.run_mode === "managed-dispatch") {
|
|
407
|
+
return JSON.stringify(await evaluateFlowDeskManagedDispatchRunRoute(record, managedDispatchRunRoute));
|
|
408
|
+
}
|
|
279
409
|
return JSON.stringify(session.evaluate(stub.toolName, request));
|
|
280
|
-
}
|
|
281
|
-
})
|
|
410
|
+
},
|
|
411
|
+
}),
|
|
282
412
|
];
|
|
283
413
|
}));
|
|
284
414
|
}
|
|
@@ -287,15 +417,18 @@ export function createFlowDeskNaturalLanguageRoutingTools(now = new Date(), sess
|
|
|
287
417
|
if (artifact === undefined)
|
|
288
418
|
throw new Error("missing FDS-1 schema artifact: flowdesk.chat_intake.request.v1");
|
|
289
419
|
const required = new Set(artifact.required);
|
|
290
|
-
const args = Object.fromEntries(Object.keys(artifact.properties).map((fieldName) => [
|
|
420
|
+
const args = Object.fromEntries(Object.keys(artifact.properties).map((fieldName) => [
|
|
421
|
+
fieldName,
|
|
422
|
+
zodForSchemaProperty("flowdesk.chat_intake.request.v1", fieldName, required.has(fieldName)),
|
|
423
|
+
]));
|
|
291
424
|
return {
|
|
292
425
|
[flowdeskChatIntakeToolName]: tool({
|
|
293
426
|
description: "FlowDesk natural-language chat intake steering; command-backed only, with no provider call, real dispatch, lane launch, fallback, or hard chat control.",
|
|
294
427
|
args,
|
|
295
428
|
async execute(request) {
|
|
296
429
|
return JSON.stringify(evaluateNaturalLanguageRouting(request, session));
|
|
297
|
-
}
|
|
298
|
-
})
|
|
430
|
+
},
|
|
431
|
+
}),
|
|
299
432
|
};
|
|
300
433
|
}
|
|
301
434
|
function redactedManagedDispatchBetaToolResult(result) {
|
|
@@ -306,72 +439,823 @@ function redactedManagedDispatchBetaToolResult(result) {
|
|
|
306
439
|
guardDecision: result.guardDecision,
|
|
307
440
|
authority: result.authority,
|
|
308
441
|
verification: result.verification,
|
|
309
|
-
...(result.dispatchAttempted
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
442
|
+
...(result.dispatchAttempted
|
|
443
|
+
? {
|
|
444
|
+
dispatchMethod: result.dispatchMethod,
|
|
445
|
+
sessionId: result.sessionId,
|
|
446
|
+
agent: result.agent,
|
|
447
|
+
model: result.model,
|
|
448
|
+
...("redactedErrorCategory" in result
|
|
449
|
+
? { redactedErrorCategory: result.redactedErrorCategory }
|
|
450
|
+
: {}),
|
|
451
|
+
responseObserved: "response" in result && result.response !== undefined,
|
|
452
|
+
}
|
|
453
|
+
: { redactedBlockReason: result.redactedBlockReason }),
|
|
317
454
|
};
|
|
318
455
|
}
|
|
319
|
-
|
|
456
|
+
function redactedExactModelProviderAcquisitionToolResult(result, cacheMaterialization) {
|
|
457
|
+
return {
|
|
458
|
+
adapterProfile: result.adapterProfile,
|
|
459
|
+
status: result.status,
|
|
460
|
+
providerCallAttempted: result.providerCallAttempted,
|
|
461
|
+
writeAttempted: result.writeAttempted,
|
|
462
|
+
evidenceReloaded: result.evidenceReloaded,
|
|
463
|
+
workflowId: result.workflowId,
|
|
464
|
+
evidenceId: result.evidenceId,
|
|
465
|
+
resultId: result.resultId,
|
|
466
|
+
providerQualifiedModelId: result.providerQualifiedModelId,
|
|
467
|
+
redactedBlockReason: result.redactedBlockReason,
|
|
468
|
+
authority: result.authority,
|
|
469
|
+
resultState: result.result?.state,
|
|
470
|
+
available: result.result?.available,
|
|
471
|
+
highestTierEligible: result.result?.highest_tier_eligible,
|
|
472
|
+
sanitizedProviderResultRef: result.result?.sanitized_provider_result_ref,
|
|
473
|
+
availabilityRef: result.result?.availability_ref,
|
|
474
|
+
blockedLabels: result.result?.blocked_labels,
|
|
475
|
+
...(cacheMaterialization === undefined
|
|
476
|
+
? {}
|
|
477
|
+
: {
|
|
478
|
+
cacheMaterialization: {
|
|
479
|
+
state: cacheMaterialization.result.state,
|
|
480
|
+
blockedLabels: cacheMaterialization.result.blocked_labels,
|
|
481
|
+
targetCacheEvidenceId: cacheMaterialization.options.targetCacheEvidenceId,
|
|
482
|
+
targetCacheRefreshPlanEvidenceId: cacheMaterialization.options.targetCacheRefreshPlanEvidenceId,
|
|
483
|
+
cacheId: cacheMaterialization.result.cache?.cache_id ??
|
|
484
|
+
cacheMaterialization.options.cacheId,
|
|
485
|
+
entryId: cacheMaterialization.result.cache?.entries[0]?.entry_id ??
|
|
486
|
+
cacheMaterialization.options.entryId,
|
|
487
|
+
availabilityRef: cacheMaterialization.result.cache?.entries[0]?.availability_ref,
|
|
488
|
+
sanitizedProviderResultRef: cacheMaterialization.result.cache?.entries[0]
|
|
489
|
+
?.availability_ref === undefined
|
|
490
|
+
? undefined
|
|
491
|
+
: result.result?.sanitized_provider_result_ref,
|
|
492
|
+
selectionState: cacheMaterialization.result.selection?.state,
|
|
493
|
+
pairSelectionReady: cacheMaterialization.result.selection?.state === "pair_ready",
|
|
494
|
+
...(cacheMaterialization.fanout === undefined
|
|
495
|
+
? {}
|
|
496
|
+
: {
|
|
497
|
+
reviewerFanoutPlanning: {
|
|
498
|
+
state: cacheMaterialization.fanout.result.state,
|
|
499
|
+
blockedLabels: cacheMaterialization.fanout.result.blocked_labels,
|
|
500
|
+
fanoutPlanState: cacheMaterialization.fanout.result.fanoutPlan.state,
|
|
501
|
+
plannedPerspectives: cacheMaterialization.fanout.result.fanoutPlan
|
|
502
|
+
.planned_perspectives,
|
|
503
|
+
runtimeLaneLaunchRequests: cacheMaterialization.fanout.result.fanoutPlan
|
|
504
|
+
.runtime_lane_launch_requests.length,
|
|
505
|
+
launchAttempted: cacheMaterialization.fanout.result.fanoutPlan
|
|
506
|
+
.launch_attempted,
|
|
507
|
+
approvalInferred: cacheMaterialization.fanout.result.fanoutPlan
|
|
508
|
+
.approval_inferred,
|
|
509
|
+
actualLaneLaunch: cacheMaterialization.fanout.result.fanoutPlan
|
|
510
|
+
.actualLaneLaunch,
|
|
511
|
+
providerCall: cacheMaterialization.fanout.result.fanoutPlan
|
|
512
|
+
.providerCall,
|
|
513
|
+
runtimeExecution: cacheMaterialization.fanout.result.fanoutPlan
|
|
514
|
+
.runtimeExecution,
|
|
515
|
+
persisted: cacheMaterialization.fanout.persisted,
|
|
516
|
+
persistedEvidenceId: cacheMaterialization.fanout.persistedEvidenceId,
|
|
517
|
+
persistErrors: cacheMaterialization.fanout.persistErrors,
|
|
518
|
+
...(cacheMaterialization.fanout.runtimeLaunchPlans === undefined
|
|
519
|
+
? {}
|
|
520
|
+
: {
|
|
521
|
+
runtimeLaunchPlanMaterialization: {
|
|
522
|
+
state: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
523
|
+
.result.state,
|
|
524
|
+
blockedLabels: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
525
|
+
.result.blocked_labels,
|
|
526
|
+
targetLaunchPlanEvidenceIds: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
527
|
+
.options.targetLaunchPlanEvidenceIds,
|
|
528
|
+
launchPlanStates: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
529
|
+
.result.launchPlans.map((plan) => plan.state),
|
|
530
|
+
launchPlanCount: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
531
|
+
.result.launchPlans.length,
|
|
532
|
+
writeIntentCount: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
533
|
+
.result.writeIntents.length,
|
|
534
|
+
launchAttempted: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
535
|
+
.result.launchPlans.some((plan) => plan.launch_attempted),
|
|
536
|
+
actualLaneLaunch: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
537
|
+
.result.actualLaneLaunch,
|
|
538
|
+
providerCall: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
539
|
+
.result.providerCall,
|
|
540
|
+
runtimeExecution: cacheMaterialization.fanout.runtimeLaunchPlans
|
|
541
|
+
.result.runtimeExecution,
|
|
542
|
+
...(cacheMaterialization.fanout.runtimeLaunchPlans
|
|
543
|
+
.runtimeReviewerExecution === undefined
|
|
544
|
+
? {}
|
|
545
|
+
: {
|
|
546
|
+
runtimeReviewerExecution: {
|
|
547
|
+
status: cacheMaterialization.fanout
|
|
548
|
+
.runtimeLaunchPlans
|
|
549
|
+
.runtimeReviewerExecution.result
|
|
550
|
+
.status,
|
|
551
|
+
laneCount: cacheMaterialization.fanout
|
|
552
|
+
.runtimeLaunchPlans
|
|
553
|
+
.runtimeReviewerExecution.result
|
|
554
|
+
.laneCount,
|
|
555
|
+
acceptanceStatus: cacheMaterialization.fanout
|
|
556
|
+
.runtimeLaunchPlans
|
|
557
|
+
.runtimeReviewerExecution.result
|
|
558
|
+
.acceptanceStatus,
|
|
559
|
+
acceptedPerspectives: cacheMaterialization.fanout
|
|
560
|
+
.runtimeLaunchPlans
|
|
561
|
+
.runtimeReviewerExecution.result
|
|
562
|
+
.acceptedPerspectives,
|
|
563
|
+
durableLinkageStatus: cacheMaterialization.fanout
|
|
564
|
+
.runtimeLaunchPlans
|
|
565
|
+
.runtimeReviewerExecution.result
|
|
566
|
+
.durableLinkageStatus,
|
|
567
|
+
linkedVerdictCount: cacheMaterialization.fanout
|
|
568
|
+
.runtimeLaunchPlans
|
|
569
|
+
.runtimeReviewerExecution.result
|
|
570
|
+
.linkedVerdictCount,
|
|
571
|
+
linkedLifecycleCount: cacheMaterialization.fanout
|
|
572
|
+
.runtimeLaunchPlans
|
|
573
|
+
.runtimeReviewerExecution.result
|
|
574
|
+
.linkedLifecycleCount,
|
|
575
|
+
redactedBlockReason: cacheMaterialization.fanout
|
|
576
|
+
.runtimeLaunchPlans
|
|
577
|
+
.runtimeReviewerExecution.result
|
|
578
|
+
.redactedBlockReason,
|
|
579
|
+
},
|
|
580
|
+
}),
|
|
581
|
+
},
|
|
582
|
+
}),
|
|
583
|
+
},
|
|
584
|
+
}),
|
|
585
|
+
},
|
|
586
|
+
}),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function providerAcquisitionRuntimeReviewerExecutionFromValue(value) {
|
|
590
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
591
|
+
return undefined;
|
|
592
|
+
if (typeof value.attemptId !== "string" ||
|
|
593
|
+
value.attemptId.trim().length === 0 ||
|
|
594
|
+
typeof value.parentSessionId !== "string" ||
|
|
595
|
+
value.parentSessionId.trim().length === 0 ||
|
|
596
|
+
!isRecord(value.consumedReviewerFanoutApproval))
|
|
597
|
+
return undefined;
|
|
598
|
+
const expectations = runtimeReviewerExecutionExpectationsFromValue(value.verdictExpectations);
|
|
599
|
+
if (expectations === undefined || expectations.length === 0)
|
|
600
|
+
return undefined;
|
|
601
|
+
return {
|
|
602
|
+
enabled: true,
|
|
603
|
+
attemptId: value.attemptId,
|
|
604
|
+
parentSessionId: value.parentSessionId,
|
|
605
|
+
...(typeof value.observedAt === "string" && value.observedAt.trim().length > 0
|
|
606
|
+
? { observedAt: value.observedAt }
|
|
607
|
+
: {}),
|
|
608
|
+
consumedReviewerFanoutApproval: value.consumedReviewerFanoutApproval,
|
|
609
|
+
verdictExpectations: expectations,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function providerAcquisitionRuntimeLaunchPlanMaterializationFromValue(value) {
|
|
613
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
614
|
+
return undefined;
|
|
615
|
+
if (!Array.isArray(value.targetLaunchPlanEvidenceIds) ||
|
|
616
|
+
value.targetLaunchPlanEvidenceIds.length === 0 ||
|
|
617
|
+
!value.targetLaunchPlanEvidenceIds.every((entry) => typeof entry === "string" && entry.trim().length > 0))
|
|
618
|
+
return undefined;
|
|
619
|
+
const runtimeReviewerExecution = providerAcquisitionRuntimeReviewerExecutionFromValue(value.runtimeReviewerExecution);
|
|
620
|
+
return {
|
|
621
|
+
enabled: true,
|
|
622
|
+
targetLaunchPlanEvidenceIds: value.targetLaunchPlanEvidenceIds,
|
|
623
|
+
...(typeof value.sdkClientAvailable === "boolean"
|
|
624
|
+
? { sdkClientAvailable: value.sdkClientAvailable }
|
|
625
|
+
: {}),
|
|
626
|
+
...(typeof value.durableEvidenceRootRef === "string" &&
|
|
627
|
+
value.durableEvidenceRootRef.trim().length > 0
|
|
628
|
+
? { durableEvidenceRootRef: value.durableEvidenceRootRef }
|
|
629
|
+
: {}),
|
|
630
|
+
...(runtimeReviewerExecution === undefined
|
|
631
|
+
? {}
|
|
632
|
+
: { runtimeReviewerExecution }),
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function providerAcquisitionReviewerFanoutPlanningFromValue(value) {
|
|
636
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
637
|
+
return undefined;
|
|
638
|
+
if (typeof value.attemptId !== "string" ||
|
|
639
|
+
value.attemptId.trim().length === 0 ||
|
|
640
|
+
typeof value.parentSessionRef !== "string" ||
|
|
641
|
+
value.parentSessionRef.trim().length === 0 ||
|
|
642
|
+
typeof value.agentRef !== "string" ||
|
|
643
|
+
value.agentRef.trim().length === 0)
|
|
644
|
+
return undefined;
|
|
645
|
+
const runtimeLaunchPlanMaterialization = providerAcquisitionRuntimeLaunchPlanMaterializationFromValue(value.runtimeLaunchPlanMaterialization);
|
|
646
|
+
return {
|
|
647
|
+
enabled: true,
|
|
648
|
+
attemptId: value.attemptId,
|
|
649
|
+
parentSessionRef: value.parentSessionRef,
|
|
650
|
+
agentRef: value.agentRef,
|
|
651
|
+
...(typeof value.requestedAt === "string" && value.requestedAt.trim().length > 0
|
|
652
|
+
? { requestedAt: value.requestedAt }
|
|
653
|
+
: {}),
|
|
654
|
+
...(Array.isArray(value.requestedPerspectives) &&
|
|
655
|
+
value.requestedPerspectives.every((entry) => typeof entry === "string")
|
|
656
|
+
? {
|
|
657
|
+
requestedPerspectives: value.requestedPerspectives,
|
|
658
|
+
}
|
|
659
|
+
: {}),
|
|
660
|
+
...(typeof value.maxConcurrentLaneCount === "number"
|
|
661
|
+
? { maxConcurrentLaneCount: value.maxConcurrentLaneCount }
|
|
662
|
+
: {}),
|
|
663
|
+
...(typeof value.timeoutMs === "number" ? { timeoutMs: value.timeoutMs } : {}),
|
|
664
|
+
...(typeof value.orphanMaxAgeMs === "number"
|
|
665
|
+
? { orphanMaxAgeMs: value.orphanMaxAgeMs }
|
|
666
|
+
: {}),
|
|
667
|
+
...(typeof value.retryBudget === "number"
|
|
668
|
+
? { retryBudget: value.retryBudget }
|
|
669
|
+
: {}),
|
|
670
|
+
...(typeof value.preLaunchAuditRef === "string"
|
|
671
|
+
? { preLaunchAuditRef: value.preLaunchAuditRef }
|
|
672
|
+
: {}),
|
|
673
|
+
...(typeof value.laneLaunchApprovalRef === "string"
|
|
674
|
+
? { laneLaunchApprovalRef: value.laneLaunchApprovalRef }
|
|
675
|
+
: {}),
|
|
676
|
+
...(value.persistDerivedFanoutPlanEvidence === true
|
|
677
|
+
? { persistDerivedFanoutPlanEvidence: true }
|
|
678
|
+
: {}),
|
|
679
|
+
...(typeof value.fanoutPlanEvidenceId === "string" &&
|
|
680
|
+
value.fanoutPlanEvidenceId.trim().length > 0
|
|
681
|
+
? { fanoutPlanEvidenceId: value.fanoutPlanEvidenceId }
|
|
682
|
+
: {}),
|
|
683
|
+
...(runtimeLaunchPlanMaterialization === undefined
|
|
684
|
+
? {}
|
|
685
|
+
: { runtimeLaunchPlanMaterialization }),
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function exactModelProviderAcquisitionCacheMaterializationFromOptions(options) {
|
|
689
|
+
const value = options?.[flowdeskExactModelProviderAcquisitionLiveTestOption];
|
|
690
|
+
if (!isRecord(value) || !isRecord(value.cacheMaterialization))
|
|
691
|
+
return undefined;
|
|
692
|
+
const cacheMaterialization = value.cacheMaterialization;
|
|
693
|
+
if (cacheMaterialization.enabled !== true ||
|
|
694
|
+
typeof cacheMaterialization.targetCacheEvidenceId !== "string" ||
|
|
695
|
+
cacheMaterialization.targetCacheEvidenceId.trim().length === 0 ||
|
|
696
|
+
typeof cacheMaterialization.targetCacheRefreshPlanEvidenceId !== "string" ||
|
|
697
|
+
cacheMaterialization.targetCacheRefreshPlanEvidenceId.trim().length === 0)
|
|
698
|
+
return undefined;
|
|
699
|
+
const reviewerFanoutPlanning = providerAcquisitionReviewerFanoutPlanningFromValue(cacheMaterialization.reviewerFanoutPlanning);
|
|
700
|
+
return {
|
|
701
|
+
enabled: true,
|
|
702
|
+
targetCacheEvidenceId: cacheMaterialization.targetCacheEvidenceId,
|
|
703
|
+
targetCacheRefreshPlanEvidenceId: cacheMaterialization.targetCacheRefreshPlanEvidenceId,
|
|
704
|
+
...(typeof cacheMaterialization.cacheId === "string" &&
|
|
705
|
+
cacheMaterialization.cacheId.trim().length > 0
|
|
706
|
+
? { cacheId: cacheMaterialization.cacheId }
|
|
707
|
+
: {}),
|
|
708
|
+
...(typeof cacheMaterialization.entryId === "string" &&
|
|
709
|
+
cacheMaterialization.entryId.trim().length > 0
|
|
710
|
+
? { entryId: cacheMaterialization.entryId }
|
|
711
|
+
: {}),
|
|
712
|
+
...(reviewerFanoutPlanning === undefined
|
|
713
|
+
? {}
|
|
714
|
+
: { reviewerFanoutPlanning }),
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function providerAcquisitionFanoutPlanEvidenceId(workflowId, plan) {
|
|
718
|
+
return safeToken(`reviewer-fanout-plan-${workflowId}-${plan.fanoutPlan.attempt_id}-${plan.fanoutPlan.cache_id ?? "cache"}`, "reviewer-fanout-plan");
|
|
719
|
+
}
|
|
720
|
+
function persistProviderAcquisitionReviewerFanoutPlanEvidence(input) {
|
|
721
|
+
if (input.plan.state !== "fanout_ready" || !input.plan.ok)
|
|
722
|
+
return { persisted: false, errors: ["reviewer fanout plan is not ready"] };
|
|
723
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
724
|
+
workflowId: input.workflowId,
|
|
725
|
+
evidenceId: input.evidenceId,
|
|
726
|
+
record: input.plan.fanoutPlan,
|
|
727
|
+
});
|
|
728
|
+
if (!prepared.ok || prepared.writeIntent === undefined)
|
|
729
|
+
return { persisted: false, errors: prepared.errors };
|
|
730
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
731
|
+
prepared.writeIntent,
|
|
732
|
+
]);
|
|
733
|
+
return { persisted: applied.ok, errors: applied.errors };
|
|
734
|
+
}
|
|
735
|
+
function blockedManagedDispatchRunRoute(redactedBlockReason, defaultAuthorization) {
|
|
736
|
+
return {
|
|
737
|
+
runRouteProfile: "flowdesk_run_default_managed_dispatch_route",
|
|
738
|
+
status: "blocked_before_dispatch",
|
|
739
|
+
dispatchAttempted: false,
|
|
740
|
+
redactedBlockReason,
|
|
741
|
+
...(defaultAuthorization === undefined
|
|
742
|
+
? {}
|
|
743
|
+
: {
|
|
744
|
+
defaultManagedDispatchAuthorizationRef: defaultAuthorization.authorization_id,
|
|
745
|
+
defaultManagedDispatchReadinessRef: defaultAuthorization.readiness_ref,
|
|
746
|
+
}),
|
|
747
|
+
authority: { ...disabledAuthority, toolAuthority: false },
|
|
748
|
+
verification: {
|
|
749
|
+
defaultManagedDispatchAuthorizationRequired: true,
|
|
750
|
+
managedDispatchAdapterGateRequired: true,
|
|
751
|
+
defaultRelease1NonDispatchModesUnchanged: true,
|
|
752
|
+
},
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
async function evaluateFlowDeskManagedDispatchRunRoute(request, options = {}) {
|
|
756
|
+
const requestValidation = validateRunRequestV1(request);
|
|
757
|
+
if (!requestValidation.ok) {
|
|
758
|
+
return blockedManagedDispatchRunRoute("/flowdesk-run managed-dispatch requires a valid flowdesk.run.request.v1 envelope before dispatch routing.", options.defaultAuthorization);
|
|
759
|
+
}
|
|
760
|
+
const authorization = options.defaultAuthorization;
|
|
761
|
+
const authorizationResult = authorization === undefined
|
|
762
|
+
? undefined
|
|
763
|
+
: validateFlowDeskDefaultManagedDispatchAuthorizationV1(authorization);
|
|
764
|
+
if (authorization === undefined ||
|
|
765
|
+
authorizationResult?.ok !== true ||
|
|
766
|
+
authorization.state !== "authorized" ||
|
|
767
|
+
authorization.default_managed_dispatch_authority_enabled !== true) {
|
|
768
|
+
return blockedManagedDispatchRunRoute("/flowdesk-run managed-dispatch requires a valid default managed-dispatch authorization.", authorization);
|
|
769
|
+
}
|
|
770
|
+
if (options.client === undefined) {
|
|
771
|
+
return blockedManagedDispatchRunRoute("/flowdesk-run managed-dispatch requires an injected OpenCode SDK client.", authorization);
|
|
772
|
+
}
|
|
773
|
+
if (!isRecord(request.managed_dispatch_boundary_input) ||
|
|
774
|
+
!isRecord(request.managed_dispatch_request) ||
|
|
775
|
+
!isRecord(request.managed_dispatch_manifest)) {
|
|
776
|
+
return blockedManagedDispatchRunRoute("/flowdesk-run managed-dispatch requires boundary input, dispatch request, and dispatch manifest records.", authorization);
|
|
777
|
+
}
|
|
778
|
+
const boundaryInput = request.managed_dispatch_boundary_input;
|
|
779
|
+
const reloadedEvidence = isRecord(request.managed_dispatch_reloaded_evidence)
|
|
780
|
+
? request.managed_dispatch_reloaded_evidence
|
|
781
|
+
: options.durableStateRootDir !== undefined &&
|
|
782
|
+
typeof boundaryInput.workflowId === "string"
|
|
783
|
+
? reloadFlowDeskSessionEvidenceV1({
|
|
784
|
+
workflowId: boundaryInput.workflowId,
|
|
785
|
+
rootDir: options.durableStateRootDir,
|
|
786
|
+
})
|
|
787
|
+
: undefined;
|
|
788
|
+
const result = await dispatchManagedDispatchBetaPromptV1({
|
|
789
|
+
client: options.client,
|
|
790
|
+
boundaryInput,
|
|
791
|
+
request: request.managed_dispatch_request,
|
|
792
|
+
dispatchManifest: request.managed_dispatch_manifest,
|
|
793
|
+
...(reloadedEvidence === undefined ? {} : { reloadedEvidence }),
|
|
794
|
+
...(options.reservationStore === undefined
|
|
795
|
+
? {}
|
|
796
|
+
: { reservationStore: options.reservationStore }),
|
|
797
|
+
});
|
|
798
|
+
return {
|
|
799
|
+
runRouteProfile: "flowdesk_run_default_managed_dispatch_route",
|
|
800
|
+
defaultManagedDispatchAuthorizationRef: authorization.authorization_id,
|
|
801
|
+
defaultManagedDispatchReadinessRef: authorization.readiness_ref,
|
|
802
|
+
...redactedManagedDispatchBetaToolResult(result),
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
export function createFlowDeskManagedDispatchBetaOptInTools(client, reservationStore, durableStateRootDir) {
|
|
320
806
|
return {
|
|
321
807
|
[flowdeskManagedDispatchBetaToolName]: tool({
|
|
322
808
|
description: "FlowDesk Release 2 managed-dispatch beta opt-in tool; requires full Guard evidence and calls only the injected OpenCode SDK client without fallback.",
|
|
323
809
|
args: {
|
|
324
|
-
boundaryInput: tool.schema
|
|
325
|
-
|
|
810
|
+
boundaryInput: tool.schema
|
|
811
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
812
|
+
.describe("Complete ManagedDispatchBetaBoundaryInputV1 evidence bundle; no raw prompts or provider payloads."),
|
|
813
|
+
request: tool.schema
|
|
814
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
815
|
+
.describe("Bounded managed dispatch request with session, agent, provider-qualified model id, and approved prompt summary/text."),
|
|
816
|
+
dispatchManifest: tool.schema
|
|
817
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
818
|
+
.describe("Committed dispatch attempt manifest binding approval, audit, idempotency, and disabled authority flags."),
|
|
819
|
+
reloadedEvidence: tool.schema
|
|
820
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
821
|
+
.optional()
|
|
822
|
+
.describe("Optional preloaded durable session evidence. If omitted, FlowDesk reloads evidence from the configured durableStateRoot."),
|
|
326
823
|
},
|
|
327
824
|
async execute(input) {
|
|
328
825
|
const record = isRecord(input) ? input : {};
|
|
329
|
-
if (!isRecord(record.boundaryInput) ||
|
|
826
|
+
if (!isRecord(record.boundaryInput) ||
|
|
827
|
+
!isRecord(record.request) ||
|
|
828
|
+
!isRecord(record.dispatchManifest)) {
|
|
330
829
|
return JSON.stringify({
|
|
331
830
|
adapterProfile: "managed_dispatch_beta_real_opencode_dispatch_adapter",
|
|
332
831
|
status: "blocked_before_dispatch",
|
|
333
832
|
dispatchAttempted: false,
|
|
334
|
-
redactedBlockReason: "Managed-dispatch beta requires boundaryInput and
|
|
833
|
+
redactedBlockReason: "Managed-dispatch beta requires boundaryInput, request, and dispatchManifest records.",
|
|
335
834
|
authority: { ...disabledAuthority, toolAuthority: false },
|
|
336
|
-
verification: { defaultRelease1ServerBehaviorUnchanged: true }
|
|
835
|
+
verification: { defaultRelease1ServerBehaviorUnchanged: true },
|
|
337
836
|
});
|
|
338
837
|
}
|
|
838
|
+
const boundaryInput = record.boundaryInput;
|
|
839
|
+
const reloadedEvidence = isRecord(record.reloadedEvidence)
|
|
840
|
+
? record.reloadedEvidence
|
|
841
|
+
: durableStateRootDir !== undefined &&
|
|
842
|
+
typeof boundaryInput.workflowId === "string"
|
|
843
|
+
? reloadFlowDeskSessionEvidenceV1({
|
|
844
|
+
workflowId: boundaryInput.workflowId,
|
|
845
|
+
rootDir: durableStateRootDir,
|
|
846
|
+
})
|
|
847
|
+
: undefined;
|
|
339
848
|
const result = await dispatchManagedDispatchBetaPromptV1({
|
|
340
849
|
client,
|
|
341
|
-
boundaryInput
|
|
342
|
-
request: record.request
|
|
850
|
+
boundaryInput,
|
|
851
|
+
request: record.request,
|
|
852
|
+
dispatchManifest: record.dispatchManifest,
|
|
853
|
+
...(reloadedEvidence === undefined ? {} : { reloadedEvidence }),
|
|
854
|
+
...(reservationStore === undefined ? {} : { reservationStore }),
|
|
343
855
|
});
|
|
344
856
|
return JSON.stringify(redactedManagedDispatchBetaToolResult(result));
|
|
345
|
-
}
|
|
346
|
-
})
|
|
857
|
+
},
|
|
858
|
+
}),
|
|
347
859
|
};
|
|
348
860
|
}
|
|
349
|
-
export function
|
|
861
|
+
export function createFlowDeskExactModelProviderAcquisitionLiveTestOptInTools(client, rootDir, cacheMaterialization, runtimeReviewerExecutionClient) {
|
|
862
|
+
return {
|
|
863
|
+
[flowdeskExactModelProviderAcquisitionLiveTestToolName]: tool({
|
|
864
|
+
description: "FlowDesk exact-model provider acquisition live-test opt-in tool; performs one bounded provider availability check and writes sanitized session evidence without dispatch or reviewer launch.",
|
|
865
|
+
args: {
|
|
866
|
+
request: tool.schema
|
|
867
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
868
|
+
.describe("Complete FlowDeskExactModelProviderAcquisitionLiveTestRequestV1 with acquisition_plan, profile/auth boundary refs, redaction proof, pre-call audit, idempotency, and exact provider-qualified model."),
|
|
869
|
+
},
|
|
870
|
+
async execute(input) {
|
|
871
|
+
const record = isRecord(input) ? input : {};
|
|
872
|
+
if (!isRecord(record.request)) {
|
|
873
|
+
return JSON.stringify({
|
|
874
|
+
adapterProfile: "exact_model_provider_acquisition_live_test_adapter",
|
|
875
|
+
status: "blocked_before_provider_acquisition",
|
|
876
|
+
providerCallAttempted: false,
|
|
877
|
+
writeAttempted: false,
|
|
878
|
+
evidenceReloaded: false,
|
|
879
|
+
redactedBlockReason: "Exact-model provider acquisition live-test requires a request record.",
|
|
880
|
+
authority: { ...disabledAuthority, toolAuthority: false },
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
const request = record.request;
|
|
884
|
+
const result = await runFlowDeskExactModelProviderAcquisitionLiveTestV1({
|
|
885
|
+
client,
|
|
886
|
+
rootDir,
|
|
887
|
+
request,
|
|
888
|
+
});
|
|
889
|
+
const materializationResult = cacheMaterialization !== undefined &&
|
|
890
|
+
result.evidenceReloaded === true
|
|
891
|
+
? materializeFlowDeskExactModelCacheEvidenceFromProviderAcquisitionEvidenceV1({
|
|
892
|
+
reloadedEvidence: reloadFlowDeskSessionEvidenceV1({
|
|
893
|
+
workflowId: request.workflowId,
|
|
894
|
+
rootDir,
|
|
895
|
+
}),
|
|
896
|
+
workflowId: request.workflowId,
|
|
897
|
+
providerAcquisitionEvidenceId: request.evidenceId,
|
|
898
|
+
targetCacheEvidenceId: cacheMaterialization.targetCacheEvidenceId,
|
|
899
|
+
targetCacheRefreshPlanEvidenceId: cacheMaterialization.targetCacheRefreshPlanEvidenceId,
|
|
900
|
+
...(cacheMaterialization.cacheId === undefined
|
|
901
|
+
? {}
|
|
902
|
+
: { cacheId: cacheMaterialization.cacheId }),
|
|
903
|
+
...(cacheMaterialization.entryId === undefined
|
|
904
|
+
? {}
|
|
905
|
+
: { entryId: cacheMaterialization.entryId }),
|
|
906
|
+
rootDir,
|
|
907
|
+
localDate: request.localDate,
|
|
908
|
+
activeProfileRef: request.activeProfileRef,
|
|
909
|
+
opencodeVersionRef: request.opencodeVersionRef,
|
|
910
|
+
flowdeskPackageVersionRef: request.flowdeskPackageVersionRef,
|
|
911
|
+
registryHash: request.registryHash,
|
|
912
|
+
policyPackHash: request.policyPackHash,
|
|
913
|
+
authAccountBoundaryRef: request.authAccountBoundaryRef,
|
|
914
|
+
})
|
|
915
|
+
: undefined;
|
|
916
|
+
const fanoutResult = cacheMaterialization?.reviewerFanoutPlanning !== undefined &&
|
|
917
|
+
materializationResult?.state === "materialized" &&
|
|
918
|
+
materializationResult.reloadedEvidence !== undefined
|
|
919
|
+
? planFlowDeskReviewerFanoutFromReloadedCacheEvidenceV1({
|
|
920
|
+
reloadedEvidence: materializationResult.reloadedEvidence,
|
|
921
|
+
workflowId: request.workflowId,
|
|
922
|
+
localDate: request.localDate,
|
|
923
|
+
activeProfileRef: request.activeProfileRef,
|
|
924
|
+
opencodeVersionRef: request.opencodeVersionRef,
|
|
925
|
+
flowdeskPackageVersionRef: request.flowdeskPackageVersionRef,
|
|
926
|
+
registryHash: request.registryHash,
|
|
927
|
+
policyPackHash: request.policyPackHash,
|
|
928
|
+
authAccountBoundaryRef: request.authAccountBoundaryRef,
|
|
929
|
+
attemptId: cacheMaterialization.reviewerFanoutPlanning.attemptId,
|
|
930
|
+
parentSessionRef: cacheMaterialization.reviewerFanoutPlanning
|
|
931
|
+
.parentSessionRef,
|
|
932
|
+
agentRef: cacheMaterialization.reviewerFanoutPlanning.agentRef,
|
|
933
|
+
requestedAt: cacheMaterialization.reviewerFanoutPlanning.requestedAt ??
|
|
934
|
+
request.observedAt,
|
|
935
|
+
...(cacheMaterialization.reviewerFanoutPlanning
|
|
936
|
+
.requestedPerspectives === undefined
|
|
937
|
+
? {}
|
|
938
|
+
: {
|
|
939
|
+
requestedPerspectives: cacheMaterialization.reviewerFanoutPlanning
|
|
940
|
+
.requestedPerspectives,
|
|
941
|
+
}),
|
|
942
|
+
...(cacheMaterialization.reviewerFanoutPlanning
|
|
943
|
+
.maxConcurrentLaneCount === undefined
|
|
944
|
+
? {}
|
|
945
|
+
: {
|
|
946
|
+
maxConcurrentLaneCount: cacheMaterialization.reviewerFanoutPlanning
|
|
947
|
+
.maxConcurrentLaneCount,
|
|
948
|
+
}),
|
|
949
|
+
...(cacheMaterialization.reviewerFanoutPlanning.timeoutMs ===
|
|
950
|
+
undefined
|
|
951
|
+
? {}
|
|
952
|
+
: {
|
|
953
|
+
timeoutMs: cacheMaterialization.reviewerFanoutPlanning.timeoutMs,
|
|
954
|
+
}),
|
|
955
|
+
...(cacheMaterialization.reviewerFanoutPlanning.orphanMaxAgeMs ===
|
|
956
|
+
undefined
|
|
957
|
+
? {}
|
|
958
|
+
: {
|
|
959
|
+
orphanMaxAgeMs: cacheMaterialization.reviewerFanoutPlanning.orphanMaxAgeMs,
|
|
960
|
+
}),
|
|
961
|
+
...(cacheMaterialization.reviewerFanoutPlanning.retryBudget ===
|
|
962
|
+
undefined
|
|
963
|
+
? {}
|
|
964
|
+
: {
|
|
965
|
+
retryBudget: cacheMaterialization.reviewerFanoutPlanning.retryBudget,
|
|
966
|
+
}),
|
|
967
|
+
...(cacheMaterialization.reviewerFanoutPlanning
|
|
968
|
+
.preLaunchAuditRef === undefined
|
|
969
|
+
? {}
|
|
970
|
+
: {
|
|
971
|
+
preLaunchAuditRef: cacheMaterialization.reviewerFanoutPlanning
|
|
972
|
+
.preLaunchAuditRef,
|
|
973
|
+
}),
|
|
974
|
+
...(cacheMaterialization.reviewerFanoutPlanning
|
|
975
|
+
.laneLaunchApprovalRef === undefined
|
|
976
|
+
? {}
|
|
977
|
+
: {
|
|
978
|
+
laneLaunchApprovalRef: cacheMaterialization.reviewerFanoutPlanning
|
|
979
|
+
.laneLaunchApprovalRef,
|
|
980
|
+
}),
|
|
981
|
+
})
|
|
982
|
+
: undefined;
|
|
983
|
+
const fanoutPersistedEvidenceId = fanoutResult !== undefined &&
|
|
984
|
+
cacheMaterialization?.reviewerFanoutPlanning
|
|
985
|
+
?.persistDerivedFanoutPlanEvidence === true
|
|
986
|
+
? (cacheMaterialization.reviewerFanoutPlanning.fanoutPlanEvidenceId ??
|
|
987
|
+
providerAcquisitionFanoutPlanEvidenceId(request.workflowId, fanoutResult))
|
|
988
|
+
: undefined;
|
|
989
|
+
const fanoutPersistence = fanoutResult !== undefined && fanoutPersistedEvidenceId !== undefined
|
|
990
|
+
? persistProviderAcquisitionReviewerFanoutPlanEvidence({
|
|
991
|
+
rootDir,
|
|
992
|
+
workflowId: request.workflowId,
|
|
993
|
+
evidenceId: fanoutPersistedEvidenceId,
|
|
994
|
+
plan: fanoutResult,
|
|
995
|
+
})
|
|
996
|
+
: { persisted: false, errors: [] };
|
|
997
|
+
const runtimeLaunchPlanOptions = cacheMaterialization?.reviewerFanoutPlanning
|
|
998
|
+
?.runtimeLaunchPlanMaterialization;
|
|
999
|
+
const runtimeLaunchPlanResult = runtimeLaunchPlanOptions !== undefined &&
|
|
1000
|
+
fanoutPersistedEvidenceId !== undefined &&
|
|
1001
|
+
fanoutPersistence.persisted === true
|
|
1002
|
+
? materializeFlowDeskRuntimeLaneLaunchPlansFromReviewerFanoutEvidenceV1({
|
|
1003
|
+
reloadedEvidence: reloadFlowDeskSessionEvidenceV1({
|
|
1004
|
+
workflowId: request.workflowId,
|
|
1005
|
+
rootDir,
|
|
1006
|
+
}),
|
|
1007
|
+
workflowId: request.workflowId,
|
|
1008
|
+
reviewerFanoutEvidenceId: fanoutPersistedEvidenceId,
|
|
1009
|
+
targetLaunchPlanEvidenceIds: runtimeLaunchPlanOptions.targetLaunchPlanEvidenceIds,
|
|
1010
|
+
...(runtimeLaunchPlanOptions.sdkClientAvailable === undefined
|
|
1011
|
+
? {}
|
|
1012
|
+
: {
|
|
1013
|
+
sdkClientAvailable: runtimeLaunchPlanOptions.sdkClientAvailable,
|
|
1014
|
+
}),
|
|
1015
|
+
...(runtimeLaunchPlanOptions.durableEvidenceRootRef === undefined
|
|
1016
|
+
? {}
|
|
1017
|
+
: {
|
|
1018
|
+
durableEvidenceRootRef: runtimeLaunchPlanOptions.durableEvidenceRootRef,
|
|
1019
|
+
}),
|
|
1020
|
+
rootDir,
|
|
1021
|
+
})
|
|
1022
|
+
: undefined;
|
|
1023
|
+
const runtimeReviewerExecutionOptions = runtimeLaunchPlanOptions?.runtimeReviewerExecution;
|
|
1024
|
+
const runtimeReviewerExecutionResult = runtimeReviewerExecutionOptions !== undefined &&
|
|
1025
|
+
runtimeLaunchPlanResult?.state === "materialized" &&
|
|
1026
|
+
runtimeReviewerExecutionClient !== undefined
|
|
1027
|
+
? await executeFlowDeskRuntimeReviewerExecutionBridgeV1({
|
|
1028
|
+
client: runtimeReviewerExecutionClient,
|
|
1029
|
+
rootDir,
|
|
1030
|
+
request: {
|
|
1031
|
+
workflowId: request.workflowId,
|
|
1032
|
+
attemptId: runtimeReviewerExecutionOptions.attemptId,
|
|
1033
|
+
parentSessionId: runtimeReviewerExecutionOptions.parentSessionId,
|
|
1034
|
+
allowActualLaneLaunch: true,
|
|
1035
|
+
observedAt: runtimeReviewerExecutionOptions.observedAt ??
|
|
1036
|
+
request.observedAt,
|
|
1037
|
+
consumedReviewerFanoutApproval: runtimeReviewerExecutionOptions.consumedReviewerFanoutApproval,
|
|
1038
|
+
verdictExpectations: runtimeReviewerExecutionOptions.verdictExpectations,
|
|
1039
|
+
},
|
|
1040
|
+
})
|
|
1041
|
+
: undefined;
|
|
1042
|
+
const redactedMaterialization = cacheMaterialization !== undefined && materializationResult !== undefined
|
|
1043
|
+
? {
|
|
1044
|
+
options: cacheMaterialization,
|
|
1045
|
+
result: materializationResult,
|
|
1046
|
+
...(cacheMaterialization.reviewerFanoutPlanning !== undefined &&
|
|
1047
|
+
fanoutResult !== undefined
|
|
1048
|
+
? {
|
|
1049
|
+
fanout: {
|
|
1050
|
+
options: cacheMaterialization.reviewerFanoutPlanning,
|
|
1051
|
+
result: fanoutResult,
|
|
1052
|
+
persistedEvidenceId: fanoutPersistedEvidenceId,
|
|
1053
|
+
persisted: fanoutPersistence.persisted,
|
|
1054
|
+
persistErrors: fanoutPersistence.errors,
|
|
1055
|
+
...(runtimeLaunchPlanOptions !== undefined &&
|
|
1056
|
+
runtimeLaunchPlanResult !== undefined
|
|
1057
|
+
? {
|
|
1058
|
+
runtimeLaunchPlans: {
|
|
1059
|
+
options: runtimeLaunchPlanOptions,
|
|
1060
|
+
result: runtimeLaunchPlanResult,
|
|
1061
|
+
...(runtimeReviewerExecutionOptions !==
|
|
1062
|
+
undefined &&
|
|
1063
|
+
runtimeReviewerExecutionResult !==
|
|
1064
|
+
undefined
|
|
1065
|
+
? {
|
|
1066
|
+
runtimeReviewerExecution: {
|
|
1067
|
+
options: runtimeReviewerExecutionOptions,
|
|
1068
|
+
result: runtimeReviewerExecutionResult,
|
|
1069
|
+
},
|
|
1070
|
+
}
|
|
1071
|
+
: {}),
|
|
1072
|
+
},
|
|
1073
|
+
}
|
|
1074
|
+
: {}),
|
|
1075
|
+
},
|
|
1076
|
+
}
|
|
1077
|
+
: {}),
|
|
1078
|
+
}
|
|
1079
|
+
: undefined;
|
|
1080
|
+
return JSON.stringify(redactedExactModelProviderAcquisitionToolResult(result, redactedMaterialization));
|
|
1081
|
+
},
|
|
1082
|
+
}),
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
export function createFlowDeskNaturalLanguageChatMessageHook(now = () => new Date(), session = createFlowDeskLocalNonDispatchAdapterSession(now), stallAlert) {
|
|
350
1086
|
const recentSuggestionCards = new Map();
|
|
1087
|
+
const recentStallAlerts = new Map();
|
|
351
1088
|
return async function message(input, output) {
|
|
352
1089
|
const inputRecord = isRecord(input) ? input : {};
|
|
353
1090
|
const request = intakeRequestFromChatMessage({ ...inputRecord, ...output });
|
|
354
1091
|
const preview = previewNaturalLanguageRouting(request);
|
|
355
|
-
if (preview.evaluation.response.route_decision === "continue_chat")
|
|
356
|
-
return;
|
|
357
1092
|
const nowMs = clockMs(now);
|
|
358
1093
|
for (const [key, recordedAtMs] of recentSuggestionCards) {
|
|
359
|
-
if (nowMs - recordedAtMs > flowdeskChatSuggestionDuplicateWindowMs ||
|
|
1094
|
+
if (nowMs - recordedAtMs > flowdeskChatSuggestionDuplicateWindowMs ||
|
|
1095
|
+
nowMs < recordedAtMs)
|
|
360
1096
|
recentSuggestionCards.delete(key);
|
|
361
1097
|
}
|
|
1098
|
+
for (const [key, recordedAtMs] of recentStallAlerts) {
|
|
1099
|
+
if (nowMs - recordedAtMs > flowdeskChatSuggestionDuplicateWindowMs ||
|
|
1100
|
+
nowMs < recordedAtMs)
|
|
1101
|
+
recentStallAlerts.delete(key);
|
|
1102
|
+
}
|
|
1103
|
+
const stallSummary = stallAlert
|
|
1104
|
+
? await collectStallAlertSummary(stallAlert, now)
|
|
1105
|
+
: undefined;
|
|
1106
|
+
const stalledAlertReady = stallSummary !== undefined &&
|
|
1107
|
+
stallSummary.worstClassification === "stalled" &&
|
|
1108
|
+
stallSummary.totalStalled > 0;
|
|
1109
|
+
const lateAlertReady = stallSummary !== undefined &&
|
|
1110
|
+
stallAlert?.includeProgressingLate === true &&
|
|
1111
|
+
stallSummary.worstClassification === "progressing_late" &&
|
|
1112
|
+
stallSummary.totalLate > 0;
|
|
1113
|
+
const shouldAppendStallCard = stalledAlertReady || lateAlertReady;
|
|
1114
|
+
if (preview.evaluation.response.route_decision === "continue_chat") {
|
|
1115
|
+
if (shouldAppendStallCard && stallSummary !== undefined) {
|
|
1116
|
+
const key = stallAlertDuplicateKey(request, stallSummary);
|
|
1117
|
+
const previous = recentStallAlerts.get(key);
|
|
1118
|
+
recentStallAlerts.set(key, nowMs);
|
|
1119
|
+
if (previous === undefined ||
|
|
1120
|
+
nowMs - previous > flowdeskChatSuggestionDuplicateWindowMs) {
|
|
1121
|
+
if (!Array.isArray(output.parts))
|
|
1122
|
+
output.parts = [];
|
|
1123
|
+
output.parts.push({
|
|
1124
|
+
type: "text",
|
|
1125
|
+
text: stallAlertText(stallSummary),
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
362
1131
|
if (!mayCreatePendingConfirmation(preview)) {
|
|
363
1132
|
const duplicateKey = suggestionDuplicateKey(request, preview.evaluation.response);
|
|
364
1133
|
const previousAtMs = recentSuggestionCards.get(duplicateKey);
|
|
365
1134
|
recentSuggestionCards.set(duplicateKey, nowMs);
|
|
366
|
-
if (previousAtMs !== undefined &&
|
|
1135
|
+
if (previousAtMs !== undefined &&
|
|
1136
|
+
nowMs - previousAtMs <= flowdeskChatSuggestionDuplicateWindowMs)
|
|
367
1137
|
return;
|
|
368
1138
|
}
|
|
369
1139
|
const result = evaluateNaturalLanguageRouting(request, session);
|
|
370
1140
|
if (!Array.isArray(output.parts))
|
|
371
1141
|
output.parts = [];
|
|
372
1142
|
output.parts.push({ type: "text", text: steeringText(result) });
|
|
1143
|
+
if (shouldAppendStallCard && stallSummary !== undefined) {
|
|
1144
|
+
const key = stallAlertDuplicateKey(request, stallSummary);
|
|
1145
|
+
const previous = recentStallAlerts.get(key);
|
|
1146
|
+
recentStallAlerts.set(key, nowMs);
|
|
1147
|
+
if (previous === undefined ||
|
|
1148
|
+
nowMs - previous > flowdeskChatSuggestionDuplicateWindowMs)
|
|
1149
|
+
output.parts.push({
|
|
1150
|
+
type: "text",
|
|
1151
|
+
text: stallAlertText(stallSummary),
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
373
1154
|
};
|
|
374
1155
|
}
|
|
1156
|
+
async function collectStallAlertSummary(stallAlert, clock) {
|
|
1157
|
+
try {
|
|
1158
|
+
const observedAt = (typeof clock === "function" ? clock() : clock).toISOString();
|
|
1159
|
+
const config = {
|
|
1160
|
+
rootDir: stallAlert.rootDir,
|
|
1161
|
+
...(stallAlert.maxWorkflows === undefined
|
|
1162
|
+
? {}
|
|
1163
|
+
: { maxWorkflows: stallAlert.maxWorkflows }),
|
|
1164
|
+
...(stallAlert.laneHeartbeatLateThresholdMs === undefined
|
|
1165
|
+
? {}
|
|
1166
|
+
: {
|
|
1167
|
+
laneHeartbeatLateThresholdMs: stallAlert.laneHeartbeatLateThresholdMs,
|
|
1168
|
+
}),
|
|
1169
|
+
...(stallAlert.laneHeartbeatStallThresholdMs === undefined
|
|
1170
|
+
? {}
|
|
1171
|
+
: {
|
|
1172
|
+
laneHeartbeatStallThresholdMs: stallAlert.laneHeartbeatStallThresholdMs,
|
|
1173
|
+
}),
|
|
1174
|
+
};
|
|
1175
|
+
const result = await executeFlowDeskStatusLiveV1({
|
|
1176
|
+
config,
|
|
1177
|
+
now: () => new Date(observedAt),
|
|
1178
|
+
});
|
|
1179
|
+
if (result.status !== "status_live_collected")
|
|
1180
|
+
return undefined;
|
|
1181
|
+
const workflowSummaries = result.workflows
|
|
1182
|
+
.filter((workflow) => (workflow.stalledLaneCount ?? 0) > 0 ||
|
|
1183
|
+
(stallAlert.includeProgressingLate === true &&
|
|
1184
|
+
(workflow.progressingLateLaneCount ?? 0) > 0))
|
|
1185
|
+
.slice(0, 3)
|
|
1186
|
+
.map((workflow) => {
|
|
1187
|
+
const stalledEntry = workflow.laneStallProjection?.entries.find((entry) => entry.classification === "stalled");
|
|
1188
|
+
const lateEntry = workflow.laneStallProjection?.entries.find((entry) => entry.classification === "progressing_late");
|
|
1189
|
+
const primary = stalledEntry ?? lateEntry;
|
|
1190
|
+
return {
|
|
1191
|
+
workflowId: workflow.workflowId,
|
|
1192
|
+
stalledLaneCount: workflow.stalledLaneCount ?? 0,
|
|
1193
|
+
lateLaneCount: workflow.progressingLateLaneCount ?? 0,
|
|
1194
|
+
...(primary?.secondsSinceLastSignal === undefined
|
|
1195
|
+
? {}
|
|
1196
|
+
: { secondsSinceLastSignal: primary.secondsSinceLastSignal }),
|
|
1197
|
+
...(primary?.laneId === undefined ? {} : { laneId: primary.laneId }),
|
|
1198
|
+
...(primary?.failureHint === undefined
|
|
1199
|
+
? {}
|
|
1200
|
+
: { failureHint: primary.failureHint }),
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
return {
|
|
1204
|
+
worstClassification: result.worstLaneStallClassification ?? "unknown",
|
|
1205
|
+
totalStalled: result.totalStalledLaneCount ?? 0,
|
|
1206
|
+
totalLate: result.totalProgressingLateLaneCount ?? 0,
|
|
1207
|
+
workflowSummaries,
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
catch {
|
|
1211
|
+
return undefined;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function stallAlertDuplicateKey(request, summary) {
|
|
1215
|
+
const wf = summary.workflowSummaries
|
|
1216
|
+
.map((entry) => {
|
|
1217
|
+
const ageMinutes = typeof entry.secondsSinceLastSignal === "number"
|
|
1218
|
+
? Math.floor(entry.secondsSinceLastSignal / 60)
|
|
1219
|
+
: -1;
|
|
1220
|
+
return `${entry.workflowId}:${entry.stalledLaneCount}:${ageMinutes}`;
|
|
1221
|
+
})
|
|
1222
|
+
.join("|");
|
|
1223
|
+
return `${safeToken(request.session_ref, "session")}|stall|${wf}|worst:${summary.worstClassification}`;
|
|
1224
|
+
}
|
|
1225
|
+
function stallAlertText(summary) {
|
|
1226
|
+
const lines = [];
|
|
1227
|
+
lines.push("FlowDesk");
|
|
1228
|
+
if (summary.worstClassification === "stalled") {
|
|
1229
|
+
lines.push(`Stalled lanes detected: ${summary.totalStalled} stalled, ${summary.totalLate} progressing-late.`);
|
|
1230
|
+
}
|
|
1231
|
+
else if (summary.worstClassification === "progressing_late") {
|
|
1232
|
+
lines.push(`Late-progressing lanes detected: ${summary.totalLate} late, ${summary.totalStalled} stalled.`);
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1235
|
+
lines.push(`Lane progress check: ${summary.totalStalled} stalled, ${summary.totalLate} progressing-late.`);
|
|
1236
|
+
}
|
|
1237
|
+
for (const workflow of summary.workflowSummaries.slice(0, 3)) {
|
|
1238
|
+
const secs = workflow.secondsSinceLastSignal ?? 0;
|
|
1239
|
+
const minutes = Math.floor(secs / 60);
|
|
1240
|
+
const hint = workflow.failureHint ?? "no recent heartbeat";
|
|
1241
|
+
const counts = workflow.stalledLaneCount > 0
|
|
1242
|
+
? `${workflow.stalledLaneCount} stalled`
|
|
1243
|
+
: `${workflow.lateLaneCount} progressing-late`;
|
|
1244
|
+
lines.push(`- workflow ${workflow.workflowId}: ${counts} (last signal ~${minutes}m ago, ${hint}).`);
|
|
1245
|
+
}
|
|
1246
|
+
lines.push("FlowDesk does not auto-retry, auto-abort, or auto-fallback on stall.");
|
|
1247
|
+
lines.push("Safe next actions:");
|
|
1248
|
+
for (const action of [
|
|
1249
|
+
"/flowdesk-status",
|
|
1250
|
+
"/flowdesk-retry",
|
|
1251
|
+
"/flowdesk-resume",
|
|
1252
|
+
"/flowdesk-abort",
|
|
1253
|
+
"/flowdesk-doctor",
|
|
1254
|
+
"/flowdesk-export-debug",
|
|
1255
|
+
])
|
|
1256
|
+
lines.push(`- ${action}`);
|
|
1257
|
+
return lines.join("\n");
|
|
1258
|
+
}
|
|
375
1259
|
function isFds1SchemaConversionProbeEnabled(options) {
|
|
376
1260
|
return options?.[flowdeskFds1SchemaConversionProbeOption] === true;
|
|
377
1261
|
}
|
|
@@ -389,7 +1273,9 @@ function isNaturalLanguageRoutingEnabled(options) {
|
|
|
389
1273
|
}
|
|
390
1274
|
function durableStateRootFromOptions(options) {
|
|
391
1275
|
const value = options?.[flowdeskDurableStateRootOption];
|
|
392
|
-
return typeof value === "string" && value.trim().length > 0
|
|
1276
|
+
return typeof value === "string" && value.trim().length > 0
|
|
1277
|
+
? value
|
|
1278
|
+
: undefined;
|
|
393
1279
|
}
|
|
394
1280
|
function productionEnablementFromOptions(options) {
|
|
395
1281
|
const value = options?.[flowdeskProductionEnablementOption];
|
|
@@ -397,44 +1283,963 @@ function productionEnablementFromOptions(options) {
|
|
|
397
1283
|
return undefined;
|
|
398
1284
|
return {
|
|
399
1285
|
enabled: true,
|
|
400
|
-
...(typeof value.preDispatchAuditRef === "string"
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
...(typeof value.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
...(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1286
|
+
...(typeof value.preDispatchAuditRef === "string"
|
|
1287
|
+
? { preDispatchAuditRef: value.preDispatchAuditRef }
|
|
1288
|
+
: {}),
|
|
1289
|
+
...(typeof value.configuredVerificationRef === "string"
|
|
1290
|
+
? { configuredVerificationRef: value.configuredVerificationRef }
|
|
1291
|
+
: {}),
|
|
1292
|
+
...(isRecord(value.configuredVerificationResult)
|
|
1293
|
+
? {
|
|
1294
|
+
configuredVerificationResult: value.configuredVerificationResult,
|
|
1295
|
+
}
|
|
1296
|
+
: {}),
|
|
1297
|
+
...(typeof value.sanitizedAuthCaptureRef === "string"
|
|
1298
|
+
? { sanitizedAuthCaptureRef: value.sanitizedAuthCaptureRef }
|
|
1299
|
+
: {}),
|
|
1300
|
+
...(isRecord(value.sanitizedAuthCaptureResult)
|
|
1301
|
+
? {
|
|
1302
|
+
sanitizedAuthCaptureResult: value.sanitizedAuthCaptureResult,
|
|
1303
|
+
}
|
|
1304
|
+
: {}),
|
|
1305
|
+
...(typeof value.externalAuthPolicyRef === "string"
|
|
1306
|
+
? { externalAuthPolicyRef: value.externalAuthPolicyRef }
|
|
1307
|
+
: {}),
|
|
1308
|
+
...(typeof value.providerPolicyRef === "string"
|
|
1309
|
+
? { providerPolicyRef: value.providerPolicyRef }
|
|
1310
|
+
: {}),
|
|
1311
|
+
...(isRecord(value.externalAuthProviderPolicyResult)
|
|
1312
|
+
? {
|
|
1313
|
+
externalAuthProviderPolicyResult: value.externalAuthProviderPolicyResult,
|
|
1314
|
+
}
|
|
1315
|
+
: {}),
|
|
1316
|
+
...(Array.isArray(value.laneConformanceRefs) &&
|
|
1317
|
+
value.laneConformanceRefs.every((ref) => typeof ref === "string")
|
|
1318
|
+
? { laneConformanceRefs: value.laneConformanceRefs }
|
|
1319
|
+
: {}),
|
|
1320
|
+
...(typeof value.allowIncompleteConformance === "boolean"
|
|
1321
|
+
? { allowIncompleteConformance: value.allowIncompleteConformance }
|
|
1322
|
+
: {}),
|
|
1323
|
+
...(isRecord(value.approvalDecision)
|
|
1324
|
+
? {
|
|
1325
|
+
approvalDecision: value.approvalDecision,
|
|
1326
|
+
}
|
|
1327
|
+
: {}),
|
|
411
1328
|
};
|
|
412
1329
|
}
|
|
1330
|
+
function reviewerFanoutDiagnosticsFromOptions(options) {
|
|
1331
|
+
const value = options?.[flowdeskReviewerFanoutDiagnosticsOption];
|
|
1332
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
1333
|
+
return undefined;
|
|
1334
|
+
return value;
|
|
1335
|
+
}
|
|
413
1336
|
function isManagedDispatchBetaAdapterEnabled(options) {
|
|
414
1337
|
const value = options?.[flowdeskManagedDispatchBetaAdapterOption];
|
|
415
1338
|
return value === true || (isRecord(value) && value.enabled === true);
|
|
416
1339
|
}
|
|
1340
|
+
function isExactModelProviderAcquisitionLiveTestEnabled(options) {
|
|
1341
|
+
const value = options?.[flowdeskExactModelProviderAcquisitionLiveTestOption];
|
|
1342
|
+
return isRecord(value) && value.enabled === true;
|
|
1343
|
+
}
|
|
1344
|
+
function isRuntimeReviewerExecutionEnabled(options) {
|
|
1345
|
+
const value = options?.[flowdeskRuntimeReviewerExecutionOption];
|
|
1346
|
+
return isRecord(value) && value.enabled === true;
|
|
1347
|
+
}
|
|
1348
|
+
function defaultManagedDispatchAuthorizationFromOptions(options) {
|
|
1349
|
+
const value = options?.[flowdeskDefaultManagedDispatchAuthorizationOption];
|
|
1350
|
+
if (!isRecord(value))
|
|
1351
|
+
return undefined;
|
|
1352
|
+
const authorization = value;
|
|
1353
|
+
const validation = validateFlowDeskDefaultManagedDispatchAuthorizationV1(authorization);
|
|
1354
|
+
return validation.ok &&
|
|
1355
|
+
authorization.state === "authorized" &&
|
|
1356
|
+
authorization.default_managed_dispatch_authority_enabled === true
|
|
1357
|
+
? authorization
|
|
1358
|
+
: undefined;
|
|
1359
|
+
}
|
|
1360
|
+
function isDefaultManagedDispatchAuthorized(options) {
|
|
1361
|
+
return defaultManagedDispatchAuthorizationFromOptions(options) !== undefined;
|
|
1362
|
+
}
|
|
417
1363
|
function managedDispatchBetaClientFrom(input, options) {
|
|
418
1364
|
const option = options?.[flowdeskManagedDispatchBetaAdapterOption];
|
|
419
1365
|
if (isRecord(option) && isManagedDispatchBetaClient(option.client))
|
|
420
1366
|
return option.client;
|
|
421
|
-
return isRecord(input) && isManagedDispatchBetaClient(input.client)
|
|
1367
|
+
return isRecord(input) && isManagedDispatchBetaClient(input.client)
|
|
1368
|
+
? input.client
|
|
1369
|
+
: undefined;
|
|
1370
|
+
}
|
|
1371
|
+
function managedDispatchBetaReservationStoreFrom(input, options) {
|
|
1372
|
+
const option = options?.[flowdeskManagedDispatchBetaAdapterOption];
|
|
1373
|
+
if (isRecord(option) &&
|
|
1374
|
+
isManagedDispatchBetaReservationStore(option.reservationStore))
|
|
1375
|
+
return option.reservationStore;
|
|
1376
|
+
return isRecord(input) &&
|
|
1377
|
+
isManagedDispatchBetaReservationStore(input.reservationStore)
|
|
1378
|
+
? input.reservationStore
|
|
1379
|
+
: undefined;
|
|
1380
|
+
}
|
|
1381
|
+
function managedDispatchBetaDurableReservationStoreFrom(options) {
|
|
1382
|
+
const option = options?.[flowdeskManagedDispatchBetaAdapterOption];
|
|
1383
|
+
const optionRoot = isRecord(option) &&
|
|
1384
|
+
typeof option.durableStateRoot === "string" &&
|
|
1385
|
+
option.durableStateRoot.trim().length > 0
|
|
1386
|
+
? option.durableStateRoot
|
|
1387
|
+
: undefined;
|
|
1388
|
+
const rootDir = optionRoot ?? durableStateRootFromOptions(options);
|
|
1389
|
+
return rootDir === undefined
|
|
1390
|
+
? undefined
|
|
1391
|
+
: createFlowDeskManagedDispatchBetaDurableReservationStoreV1({ rootDir });
|
|
1392
|
+
}
|
|
1393
|
+
function exactModelProviderAcquisitionClientFrom(input, options) {
|
|
1394
|
+
const option = options?.[flowdeskExactModelProviderAcquisitionLiveTestOption];
|
|
1395
|
+
if (isRecord(option) && isExactModelProviderAcquisitionClient(option.client))
|
|
1396
|
+
return option.client;
|
|
1397
|
+
if (isRecord(input) && isExactModelProviderAcquisitionClient(input.exactModelProviderAcquisitionClient))
|
|
1398
|
+
return input.exactModelProviderAcquisitionClient;
|
|
1399
|
+
if (!isRecord(input))
|
|
1400
|
+
return undefined;
|
|
1401
|
+
const promptBackedCheck = isRecord(option) && isRecord(option.promptBackedCheck)
|
|
1402
|
+
? option.promptBackedCheck
|
|
1403
|
+
: undefined;
|
|
1404
|
+
const commonOptions = {
|
|
1405
|
+
client: input.client,
|
|
1406
|
+
...(typeof input.directory === "string" ? { directory: input.directory } : {}),
|
|
1407
|
+
...(typeof input.workspace === "string" ? { workspace: input.workspace } : {}),
|
|
1408
|
+
};
|
|
1409
|
+
if (promptBackedCheck?.enabled === true) {
|
|
1410
|
+
const allowedProviderQualifiedModelIds = Array.isArray(promptBackedCheck.allowedProviderQualifiedModelIds)
|
|
1411
|
+
? promptBackedCheck.allowedProviderQualifiedModelIds.filter((value) => typeof value === "string")
|
|
1412
|
+
: [];
|
|
1413
|
+
return createFlowDeskOpenCodePromptBackedProviderAcquisitionClientV1({
|
|
1414
|
+
...commonOptions,
|
|
1415
|
+
allowProviderCall: promptBackedCheck.allowProviderCall === true,
|
|
1416
|
+
allowedProviderQualifiedModelIds,
|
|
1417
|
+
...(typeof promptBackedCheck.sessionId === "string"
|
|
1418
|
+
? { sessionId: promptBackedCheck.sessionId }
|
|
1419
|
+
: {}),
|
|
1420
|
+
...(typeof promptBackedCheck.agent === "string"
|
|
1421
|
+
? { agent: promptBackedCheck.agent }
|
|
1422
|
+
: {}),
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
return createFlowDeskOpenCodeMetadataProviderAcquisitionClientV1(commonOptions);
|
|
1426
|
+
}
|
|
1427
|
+
function exactModelProviderAcquisitionRootFrom(options) {
|
|
1428
|
+
const option = options?.[flowdeskExactModelProviderAcquisitionLiveTestOption];
|
|
1429
|
+
const optionRoot = isRecord(option) && typeof option.durableStateRoot === "string" && option.durableStateRoot.trim().length > 0
|
|
1430
|
+
? option.durableStateRoot
|
|
1431
|
+
: undefined;
|
|
1432
|
+
return optionRoot ?? durableStateRootFromOptions(options);
|
|
1433
|
+
}
|
|
1434
|
+
function exactModelProviderAcquisitionRuntimeReviewerExecutionClientFrom(input, options) {
|
|
1435
|
+
const option = options?.[flowdeskExactModelProviderAcquisitionLiveTestOption];
|
|
1436
|
+
if (isRecord(option) &&
|
|
1437
|
+
isManagedDispatchBetaClient(option.runtimeReviewerExecutionClient))
|
|
1438
|
+
return option.runtimeReviewerExecutionClient;
|
|
1439
|
+
if (isRecord(option) &&
|
|
1440
|
+
isManagedDispatchBetaClient(option.sdkClient))
|
|
1441
|
+
return option.sdkClient;
|
|
1442
|
+
return isRecord(input) && isManagedDispatchBetaClient(input.client)
|
|
1443
|
+
? input.client
|
|
1444
|
+
: undefined;
|
|
1445
|
+
}
|
|
1446
|
+
function runtimeReviewerExecutionOptionsFrom(options) {
|
|
1447
|
+
const value = options?.[flowdeskRuntimeReviewerExecutionOption];
|
|
1448
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
1449
|
+
return undefined;
|
|
1450
|
+
return {
|
|
1451
|
+
enabled: true,
|
|
1452
|
+
...(typeof value.durableStateRoot === "string" &&
|
|
1453
|
+
value.durableStateRoot.trim().length > 0
|
|
1454
|
+
? { durableStateRoot: value.durableStateRoot }
|
|
1455
|
+
: {}),
|
|
1456
|
+
...(isManagedDispatchBetaClient(value.client) ? { client: value.client } : {}),
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function runtimeReviewerExecutionClientFrom(input, options) {
|
|
1460
|
+
const runtimeOptions = runtimeReviewerExecutionOptionsFrom(options);
|
|
1461
|
+
if (runtimeOptions?.client !== undefined)
|
|
1462
|
+
return runtimeOptions.client;
|
|
1463
|
+
return isRecord(input) && isManagedDispatchBetaClient(input.client)
|
|
1464
|
+
? input.client
|
|
1465
|
+
: undefined;
|
|
1466
|
+
}
|
|
1467
|
+
function runtimeReviewerExecutionRootFrom(options) {
|
|
1468
|
+
const runtimeOptions = runtimeReviewerExecutionOptionsFrom(options);
|
|
1469
|
+
return runtimeOptions?.durableStateRoot ?? durableStateRootFromOptions(options);
|
|
1470
|
+
}
|
|
1471
|
+
export function createFlowDeskRuntimeReviewerExecutionOptInTools(client, rootDir) {
|
|
1472
|
+
return {
|
|
1473
|
+
[flowdeskRuntimeReviewerExecutionToolName]: tool({
|
|
1474
|
+
description: "FlowDesk explicit runtime reviewer execution bridge; launches persisted runtime lane plans through an injected SDK client and persists typed reviewer verdict/lifecycle evidence without default dispatch promotion.",
|
|
1475
|
+
args: {
|
|
1476
|
+
request: tool.schema
|
|
1477
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
1478
|
+
.describe("Explicit runtime reviewer execution request with workflow/attempt/session refs, consumed reviewer-fanout approval, and one verdict expectation per persisted launch-plan evidence id."),
|
|
1479
|
+
},
|
|
1480
|
+
async execute(input) {
|
|
1481
|
+
const record = isRecord(input) ? input : {};
|
|
1482
|
+
if (!isRecord(record.request)) {
|
|
1483
|
+
return JSON.stringify(redactedRuntimeReviewerExecutionBlocked("Runtime reviewer execution requires a request record."));
|
|
1484
|
+
}
|
|
1485
|
+
const result = await executeFlowDeskRuntimeReviewerExecutionBridgeV1({
|
|
1486
|
+
client,
|
|
1487
|
+
rootDir,
|
|
1488
|
+
request: record.request,
|
|
1489
|
+
});
|
|
1490
|
+
return JSON.stringify(result);
|
|
1491
|
+
},
|
|
1492
|
+
}),
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
function redactedQuickReviewerRunBlocked(reason) {
|
|
1496
|
+
return {
|
|
1497
|
+
adapterProfile: "quick_reviewer_run_helper",
|
|
1498
|
+
status: "blocked_before_quick_reviewer_run",
|
|
1499
|
+
laneCount: 0,
|
|
1500
|
+
lanes: [],
|
|
1501
|
+
redactedBlockReason: reason,
|
|
1502
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1503
|
+
authority: {
|
|
1504
|
+
realOpenCodeDispatch: false,
|
|
1505
|
+
fallbackAuthority: false,
|
|
1506
|
+
hardCancelOrNoReplyAuthority: false,
|
|
1507
|
+
toolAuthority: false,
|
|
1508
|
+
providerCall: false,
|
|
1509
|
+
runtimeExecution: false,
|
|
1510
|
+
actualLaneLaunch: false,
|
|
1511
|
+
dispatchAuthorityEnabled: false,
|
|
1512
|
+
developerModeAcknowledged: false,
|
|
1513
|
+
quickReviewerRunExecuted: false,
|
|
1514
|
+
},
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
function redactedQuickReviewerRunToolResult(result) {
|
|
1518
|
+
return {
|
|
1519
|
+
adapterProfile: result.adapterProfile,
|
|
1520
|
+
status: result.status,
|
|
1521
|
+
workflowId: result.workflowId,
|
|
1522
|
+
attemptId: result.attemptId,
|
|
1523
|
+
parentSessionId: result.parentSessionId,
|
|
1524
|
+
rootDir: result.rootDir,
|
|
1525
|
+
providerQualifiedModelId: result.providerQualifiedModelId,
|
|
1526
|
+
runtimeAgent: result.runtimeAgent,
|
|
1527
|
+
laneCount: result.laneCount,
|
|
1528
|
+
lanes: result.lanes,
|
|
1529
|
+
acceptanceStatus: result.acceptanceStatus,
|
|
1530
|
+
durableLinkageStatus: result.durableLinkageStatus,
|
|
1531
|
+
linkedVerdictCount: result.linkedVerdictCount,
|
|
1532
|
+
linkedLifecycleCount: result.linkedLifecycleCount,
|
|
1533
|
+
acceptedPerspectives: result.acceptedPerspectives,
|
|
1534
|
+
redactedBlockReason: result.redactedBlockReason,
|
|
1535
|
+
safeNextActions: result.safeNextActions,
|
|
1536
|
+
authority: result.authority,
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
export function createFlowDeskQuickReviewerRunOptInTools(client, defaults) {
|
|
1540
|
+
return {
|
|
1541
|
+
[flowdeskQuickReviewerRunToolName]: tool({
|
|
1542
|
+
description: [
|
|
1543
|
+
"Run a 3-perspective FlowDesk reviewer fan-out (policy_security, architecture, verification_implementation) on a user-supplied prompt, pasted content, or the current conversation context, returning typed reviewer verdicts.",
|
|
1544
|
+
"WHEN TO USE: the user explicitly asks for a code review, security audit, multi-perspective check, critical review, design review, or quality review. Trigger on English phrases such as 'multi-perspective review', 'multi-angle review', 'critical review', 'review from multiple perspectives', 'audit', 'critique', 'assess', or 'evaluate'. Trigger on Korean phrases such as '다관점 리뷰', '다관점리뷰', '다관점 비판적리뷰', '다관점 비판적 리뷰', '다각도 리뷰', '다각도 검토', '여러 관점 리뷰', '여러 관점에서 검토', '복수 관점 리뷰', '비판적 리뷰', '비판적 검토', '심층 리뷰', '아키텍처 리뷰', '보안 리뷰', '품질 리뷰', '검토', or '점검'.",
|
|
1545
|
+
"CONTENT SELECTION: code is not required. If the user provides no explicit snippet after the trigger phrase, pass a concise prompt that includes the user's request plus the relevant current conversation context or target they are asking to review.",
|
|
1546
|
+
"WHEN NOT TO USE: general questions, plain chat, or any request that does not explicitly ask for a review, critique, audit, assessment, evaluation, 검토, 리뷰, 점검, or 비판.",
|
|
1547
|
+
"INVOKE WITH: developerModeAcknowledged=true and allowProviderCall=true. The plugin user has already opted into this tool at configuration time, so these flags must be set on every call. Do not ask the user for extra confirmation before calling; just call.",
|
|
1548
|
+
"AFTER CALLING: summarize the lane verdicts back to the user. acceptanceStatus=verdicts_accepted and durableLinkageStatus=durable_verdicts_accepted mean all three perspectives passed. Any lane with a redactedBlockReason indicates that perspective did not return a matching typed verdict.",
|
|
1549
|
+
"LANE HEARTBEAT: each reviewer lane automatically records one durable flowdesk.lane_heartbeat.v1 evidence record on launch through the runtime reviewer execution bridge; status_live and the chat.message stall card consume that heartbeat as the latest progress signal so the lane shows as progressing_normal while it is still working.",
|
|
1550
|
+
].join(" "),
|
|
1551
|
+
args: {
|
|
1552
|
+
prompt: tool.schema
|
|
1553
|
+
.string()
|
|
1554
|
+
.describe("Plain-text review target to send to the reviewer lanes as evidence to review. This can be pasted code/content or a concise summary of the relevant current conversation context when no snippet is provided."),
|
|
1555
|
+
developerModeAcknowledged: tool.schema
|
|
1556
|
+
.boolean()
|
|
1557
|
+
.describe("Must be true to explicitly acknowledge that this is a developer-mode synthetic approval, not a production-grade reviewer fan-out."),
|
|
1558
|
+
allowProviderCall: tool.schema
|
|
1559
|
+
.boolean()
|
|
1560
|
+
.describe("Must be true to explicitly allow real provider calls for the reviewer lanes."),
|
|
1561
|
+
providerQualifiedModelId: tool.schema
|
|
1562
|
+
.string()
|
|
1563
|
+
.optional()
|
|
1564
|
+
.describe("Optional concrete provider/model id override. Defaults to the value configured in the quickReviewerRun plugin option."),
|
|
1565
|
+
runtimeAgent: tool.schema
|
|
1566
|
+
.string()
|
|
1567
|
+
.optional()
|
|
1568
|
+
.describe("Optional reviewer agent override. Defaults to the value configured in the quickReviewerRun plugin option."),
|
|
1569
|
+
perspectives: tool.schema
|
|
1570
|
+
.array(tool.schema.string())
|
|
1571
|
+
.optional()
|
|
1572
|
+
.describe("Optional subset of reviewer perspectives (policy_security, architecture, verification_implementation). Defaults to all three."),
|
|
1573
|
+
parentSessionId: tool.schema
|
|
1574
|
+
.string()
|
|
1575
|
+
.optional()
|
|
1576
|
+
.describe("Optional existing parent session id. If omitted, the tool creates a new top-level session via the injected SDK client."),
|
|
1577
|
+
},
|
|
1578
|
+
async execute(input) {
|
|
1579
|
+
const record = isRecord(input) ? input : {};
|
|
1580
|
+
const prompt = typeof record.prompt === "string" ? record.prompt : undefined;
|
|
1581
|
+
if (prompt === undefined)
|
|
1582
|
+
return JSON.stringify(redactedQuickReviewerRunBlocked("Quick reviewer run requires a prompt string."));
|
|
1583
|
+
const providerQualifiedModelId = typeof record.providerQualifiedModelId === "string" &&
|
|
1584
|
+
record.providerQualifiedModelId.trim().length > 0
|
|
1585
|
+
? record.providerQualifiedModelId
|
|
1586
|
+
: defaults.providerQualifiedModelId;
|
|
1587
|
+
const runtimeAgent = typeof record.runtimeAgent === "string" &&
|
|
1588
|
+
record.runtimeAgent.trim().length > 0
|
|
1589
|
+
? record.runtimeAgent
|
|
1590
|
+
: defaults.runtimeAgent;
|
|
1591
|
+
if (typeof providerQualifiedModelId !== "string" ||
|
|
1592
|
+
typeof runtimeAgent !== "string")
|
|
1593
|
+
return JSON.stringify(redactedQuickReviewerRunBlocked("Quick reviewer run requires providerQualifiedModelId and runtimeAgent (either as args or plugin defaults)."));
|
|
1594
|
+
const perspectives = Array.isArray(record.perspectives)
|
|
1595
|
+
? record.perspectives.filter((value) => typeof value === "string")
|
|
1596
|
+
: undefined;
|
|
1597
|
+
const result = await executeFlowDeskQuickReviewerRunV1({
|
|
1598
|
+
client,
|
|
1599
|
+
prompt,
|
|
1600
|
+
providerQualifiedModelId,
|
|
1601
|
+
runtimeAgent,
|
|
1602
|
+
allowProviderCall: record.allowProviderCall === true,
|
|
1603
|
+
developerModeAcknowledged: record.developerModeAcknowledged === true,
|
|
1604
|
+
...(perspectives === undefined ? {} : { perspectives }),
|
|
1605
|
+
...(typeof record.parentSessionId === "string" &&
|
|
1606
|
+
record.parentSessionId.length > 0
|
|
1607
|
+
? { parentSessionId: record.parentSessionId }
|
|
1608
|
+
: {}),
|
|
1609
|
+
...(defaults.rootDir === undefined
|
|
1610
|
+
? {}
|
|
1611
|
+
: { rootDir: defaults.rootDir }),
|
|
1612
|
+
...(defaults.sourceLabel === undefined
|
|
1613
|
+
? {}
|
|
1614
|
+
: { sourceLabel: defaults.sourceLabel }),
|
|
1615
|
+
});
|
|
1616
|
+
return JSON.stringify(redactedQuickReviewerRunToolResult(result));
|
|
1617
|
+
},
|
|
1618
|
+
}),
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
function redactedManagedFallbackRegateBlocked(reason) {
|
|
1622
|
+
return {
|
|
1623
|
+
adapterProfile: "managed_fallback_regate_orchestrator",
|
|
1624
|
+
status: "blocked_before_regate_plan",
|
|
1625
|
+
dispatchAttempted: false,
|
|
1626
|
+
providerSwitchAttempted: false,
|
|
1627
|
+
sdkCallAttempted: false,
|
|
1628
|
+
redactedBlockReason: reason,
|
|
1629
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1630
|
+
authority: {
|
|
1631
|
+
...disabledAuthority,
|
|
1632
|
+
toolAuthority: false,
|
|
1633
|
+
automaticFallbackAuthorized: false,
|
|
1634
|
+
freshRegatePlanPrepared: false,
|
|
1635
|
+
},
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
function redactedManagedFallbackRegateToolResult(result) {
|
|
1639
|
+
return {
|
|
1640
|
+
adapterProfile: result.adapterProfile,
|
|
1641
|
+
status: result.status,
|
|
1642
|
+
dispatchAttempted: result.dispatchAttempted,
|
|
1643
|
+
providerSwitchAttempted: result.providerSwitchAttempted,
|
|
1644
|
+
sdkCallAttempted: result.sdkCallAttempted,
|
|
1645
|
+
workflowId: result.workflowId,
|
|
1646
|
+
parentAttemptId: result.parentAttemptId,
|
|
1647
|
+
newAttemptId: result.newAttemptId,
|
|
1648
|
+
fromProviderQualifiedModelId: result.fromProviderQualifiedModelId,
|
|
1649
|
+
toProviderQualifiedModelId: result.toProviderQualifiedModelId,
|
|
1650
|
+
regatePlanState: result.regatePlan?.state,
|
|
1651
|
+
regatePlanOk: result.regatePlan?.ok,
|
|
1652
|
+
regatePlanErrors: result.regatePlan?.errors,
|
|
1653
|
+
requiredFreshEvidenceRefCount: Array.isArray(result.regatePlan?.required_fresh_evidence_refs)
|
|
1654
|
+
? result.regatePlan.required_fresh_evidence_refs.length
|
|
1655
|
+
: undefined,
|
|
1656
|
+
requiredGuardDecisionRef: result.regatePlan?.required_guard_decision_ref,
|
|
1657
|
+
requiredApprovalRef: result.regatePlan?.required_approval_ref,
|
|
1658
|
+
requiredPreDispatchAuditRef: result.regatePlan?.required_pre_dispatch_audit_ref,
|
|
1659
|
+
policyEligibilityRef: result.regatePlan?.policy_eligibility_ref,
|
|
1660
|
+
runtimeCompatibilityRef: result.regatePlan?.runtime_compatibility_ref,
|
|
1661
|
+
consumedFallbackApprovalRef: result.regatePlan?.consumed_fallback_approval_ref,
|
|
1662
|
+
safeNextActions: result.safeNextActions,
|
|
1663
|
+
redactedBlockReason: result.redactedBlockReason,
|
|
1664
|
+
authority: { ...result.authority, toolAuthority: false },
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
export function createFlowDeskManagedFallbackRegateOptInTools(rootDir) {
|
|
1668
|
+
return {
|
|
1669
|
+
[flowdeskManagedFallbackRegateToolName]: tool({
|
|
1670
|
+
description: "FlowDesk explicit opt-in managed fallback regate planning tool; converts a valid fallback decision plus consumed reviewer fallback approval into a fresh full re-gate plan without dispatch, provider switching, SDK calls, or runtime authority.",
|
|
1671
|
+
args: {
|
|
1672
|
+
decision: tool.schema
|
|
1673
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
1674
|
+
.describe("Complete FlowDeskFallbackDecisionV1 with new attempt id, fresh evidence refs, fresh guard/approval/audit/policy/runtime-compatibility refs, and explicit automatic_fallback_authorized=false."),
|
|
1675
|
+
consumedApproval: tool.schema
|
|
1676
|
+
.record(tool.schema.string(), tool.schema.unknown())
|
|
1677
|
+
.describe("Consumed FlowDeskProductionApprovalSourceV1 with action_type=fallback_reselection bound to the new attempt id."),
|
|
1678
|
+
persistRegatePlanEvidence: tool.schema
|
|
1679
|
+
.boolean()
|
|
1680
|
+
.optional()
|
|
1681
|
+
.describe("When true and a configured durable state root is available, persist the resulting regate plan as fallback_regate_plan session evidence."),
|
|
1682
|
+
regatePlanEvidenceId: tool.schema
|
|
1683
|
+
.string()
|
|
1684
|
+
.optional()
|
|
1685
|
+
.describe("Evidence id to use when persisting the regate plan. Required when persistRegatePlanEvidence is true."),
|
|
1686
|
+
},
|
|
1687
|
+
async execute(input) {
|
|
1688
|
+
const record = isRecord(input) ? input : {};
|
|
1689
|
+
if (!isRecord(record.decision) ||
|
|
1690
|
+
!isRecord(record.consumedApproval)) {
|
|
1691
|
+
return JSON.stringify(redactedManagedFallbackRegateBlocked("Managed fallback regate requires both decision and consumedApproval records."));
|
|
1692
|
+
}
|
|
1693
|
+
const result = orchestrateFlowDeskManagedFallbackRegateV1({
|
|
1694
|
+
decision: record.decision,
|
|
1695
|
+
consumedApproval: record.consumedApproval,
|
|
1696
|
+
});
|
|
1697
|
+
const redacted = redactedManagedFallbackRegateToolResult(result);
|
|
1698
|
+
const persistRequested = record.persistRegatePlanEvidence === true;
|
|
1699
|
+
const evidenceId = typeof record.regatePlanEvidenceId === "string"
|
|
1700
|
+
? record.regatePlanEvidenceId
|
|
1701
|
+
: undefined;
|
|
1702
|
+
if (persistRequested &&
|
|
1703
|
+
rootDir !== undefined &&
|
|
1704
|
+
result.regatePlan !== undefined &&
|
|
1705
|
+
result.status === "regate_plan_ready" &&
|
|
1706
|
+
evidenceId !== undefined &&
|
|
1707
|
+
evidenceId.trim().length > 0) {
|
|
1708
|
+
const persistResult = materializeFlowDeskManagedFallbackRegatePlanEvidenceV1({
|
|
1709
|
+
rootDir,
|
|
1710
|
+
regatePlan: result.regatePlan,
|
|
1711
|
+
evidenceId,
|
|
1712
|
+
});
|
|
1713
|
+
redacted.regatePlanEvidence = {
|
|
1714
|
+
status: persistResult.status,
|
|
1715
|
+
writeAttempted: persistResult.writeAttempted,
|
|
1716
|
+
evidenceReloaded: persistResult.evidenceReloaded,
|
|
1717
|
+
evidenceId: persistResult.evidenceId,
|
|
1718
|
+
redactedBlockReason: persistResult.redactedBlockReason,
|
|
1719
|
+
authority: persistResult.authority,
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
return JSON.stringify(redacted);
|
|
1723
|
+
},
|
|
1724
|
+
}),
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function isManagedFallbackRegateEnabled(options) {
|
|
1728
|
+
const value = options?.[flowdeskManagedFallbackRegateOption];
|
|
1729
|
+
return value === true || (isRecord(value) && value.enabled === true);
|
|
1730
|
+
}
|
|
1731
|
+
function isQuickReviewerRunEnabled(options) {
|
|
1732
|
+
const value = options?.[flowdeskQuickReviewerRunOption];
|
|
1733
|
+
return value === true || (isRecord(value) && value.enabled === true);
|
|
1734
|
+
}
|
|
1735
|
+
function isProviderUsageLiveEnabled(options) {
|
|
1736
|
+
const value = options?.[flowdeskProviderUsageLiveOption];
|
|
1737
|
+
return value === true || (isRecord(value) && value.enabled === true);
|
|
1738
|
+
}
|
|
1739
|
+
function providerUsageLiveConfigFromOptions(options) {
|
|
1740
|
+
const value = options?.[flowdeskProviderUsageLiveOption];
|
|
1741
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
1742
|
+
return undefined;
|
|
1743
|
+
const config = {};
|
|
1744
|
+
if (typeof value.homeDir === "string" && value.homeDir.trim().length > 0)
|
|
1745
|
+
config.homeDir = value.homeDir;
|
|
1746
|
+
if (Array.isArray(value.providers)) {
|
|
1747
|
+
const allowed = value.providers.filter((family) => family === "claude" || family === "openai" || family === "gemini");
|
|
1748
|
+
config.providers = allowed;
|
|
1749
|
+
}
|
|
1750
|
+
if (typeof value.claudeOAuthUsage === "boolean")
|
|
1751
|
+
config.claudeOAuthUsage = value.claudeOAuthUsage;
|
|
1752
|
+
if (typeof value.codexLiveUsage === "boolean")
|
|
1753
|
+
config.codexLiveUsage = value.codexLiveUsage;
|
|
1754
|
+
if (typeof value.geminiQuota === "boolean")
|
|
1755
|
+
config.geminiQuota = value.geminiQuota;
|
|
1756
|
+
if (typeof value.geminiOAuthClientId === "string" &&
|
|
1757
|
+
value.geminiOAuthClientId.trim().length > 0)
|
|
1758
|
+
config.geminiOAuthClientId = value.geminiOAuthClientId;
|
|
1759
|
+
if (typeof value.geminiOAuthClientSecret === "string" &&
|
|
1760
|
+
value.geminiOAuthClientSecret.trim().length > 0)
|
|
1761
|
+
config.geminiOAuthClientSecret = value.geminiOAuthClientSecret;
|
|
1762
|
+
if (typeof value.geminiProjectId === "string" &&
|
|
1763
|
+
value.geminiProjectId.trim().length > 0)
|
|
1764
|
+
config.geminiProjectId = value.geminiProjectId;
|
|
1765
|
+
return config;
|
|
1766
|
+
}
|
|
1767
|
+
function isQuickFallbackRunEnabled(options) {
|
|
1768
|
+
const value = options?.[flowdeskQuickFallbackRunOption];
|
|
1769
|
+
return value === true || (isRecord(value) && value.enabled === true);
|
|
1770
|
+
}
|
|
1771
|
+
function quickFallbackRunConfigFromOptions(options) {
|
|
1772
|
+
const value = options?.[flowdeskQuickFallbackRunOption];
|
|
1773
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
1774
|
+
return undefined;
|
|
1775
|
+
const config = {};
|
|
1776
|
+
if (typeof value.defaultFromProvider === "string" &&
|
|
1777
|
+
value.defaultFromProvider.trim().length > 0)
|
|
1778
|
+
config.defaultFromProvider = value.defaultFromProvider;
|
|
1779
|
+
if (typeof value.defaultToProvider === "string" &&
|
|
1780
|
+
value.defaultToProvider.trim().length > 0)
|
|
1781
|
+
config.defaultToProvider = value.defaultToProvider;
|
|
1782
|
+
if (typeof value.sourceLabel === "string" && value.sourceLabel.trim().length > 0)
|
|
1783
|
+
config.sourceLabel = value.sourceLabel;
|
|
1784
|
+
const explicitRoot = typeof value.rootDir === "string" && value.rootDir.trim().length > 0
|
|
1785
|
+
? value.rootDir
|
|
1786
|
+
: undefined;
|
|
1787
|
+
const root = explicitRoot ?? durableStateRootFromOptions(options);
|
|
1788
|
+
if (root !== undefined)
|
|
1789
|
+
config.rootDir = root;
|
|
1790
|
+
return config;
|
|
1791
|
+
}
|
|
1792
|
+
export function createFlowDeskQuickFallbackRunOptInTools(config) {
|
|
1793
|
+
return {
|
|
1794
|
+
[flowdeskQuickFallbackRunToolName]: tool({
|
|
1795
|
+
description: [
|
|
1796
|
+
"Plan a FlowDesk fallback regate from one provider to another by auto-building a developer-mode synthetic fallback decision and consumed fallback_reselection approval, then running the FlowDesk fallback regate orchestrator to produce a redacted regate plan. This tool plans, it does not switch providers or dispatch real lanes; FlowDesk default dispatch authority remains disabled.",
|
|
1797
|
+
"WHEN TO USE: the user explicitly says one provider is blocked, exhausted, slow, or otherwise unwanted and asks to retry the work on a different provider. Trigger on English phrases such as 'fallback to', 'switch to', 'retry with', 'try with another provider', 'use a different provider', 'this provider is blocked', and on Korean phrases such as '막혔어', '다른 걸로 다시', '다른 provider 로', '다른 모델로 재시도', '재시도 해줘', '바꿔서 다시', 'fallback 해줘', '다른 곳으로 돌려', 'OpenAI 로 다시', 'Claude 로 다시', 'Gemini 로 다시'.",
|
|
1798
|
+
"WHEN NOT TO USE: general usage/quota questions (use flowdesk_provider_usage_live), code review/audit requests (use flowdesk_quick_reviewer_run), workflow status questions (use flowdesk_status_live), or any request that does not explicitly ask to retry on a different provider.",
|
|
1799
|
+
"INVOKE WITH: fromProvider (concrete provider-qualified model id, e.g. 'claude/sonnet-4'), toProvider (concrete provider-qualified model id, e.g. 'openai/gpt-5.5'), optional reason ('provider_unhealthy', 'quota_exhausted', 'runtime_incompatible', 'policy_ineligible', or 'manual_reselection_requested'; defaults to 'manual_reselection_requested'), optional workflowId (auto-generated otherwise), and developerModeAcknowledged=true. The plugin user has already opted into this tool at configuration time, so the assistant should set developerModeAcknowledged=true automatically and call the tool directly without per-call confirmation. Optionally set persistRegatePlanEvidence=true to persist the resulting plan as durable session evidence.",
|
|
1800
|
+
"AFTER CALLING: summarize the orchestrator status to the user. On status=quick_fallback_run_completed, surface the new attempt id, regate plan state (typically 'full_regate_required'), required fresh evidence count, and remind the user that the actual provider switch is still blocked behind managed-dispatch promotion. On status=quick_fallback_run_incomplete, surface the regatePlanRedactedErrors or redactedBlockReason and suggest what evidence to refresh. Never echo raw provider/auth/token payloads.",
|
|
1801
|
+
].join(" "),
|
|
1802
|
+
args: {
|
|
1803
|
+
fromProvider: tool.schema
|
|
1804
|
+
.string()
|
|
1805
|
+
.optional()
|
|
1806
|
+
.describe("Concrete provider-qualified model id the current attempt is on (e.g. 'claude/sonnet-4')."),
|
|
1807
|
+
toProvider: tool.schema
|
|
1808
|
+
.string()
|
|
1809
|
+
.optional()
|
|
1810
|
+
.describe("Concrete provider-qualified model id to switch to (e.g. 'openai/gpt-5.5')."),
|
|
1811
|
+
reason: tool.schema
|
|
1812
|
+
.string()
|
|
1813
|
+
.optional()
|
|
1814
|
+
.describe("Fallback reason label: 'provider_unhealthy', 'quota_exhausted', 'runtime_incompatible', 'policy_ineligible', or 'manual_reselection_requested'."),
|
|
1815
|
+
workflowId: tool.schema
|
|
1816
|
+
.string()
|
|
1817
|
+
.optional()
|
|
1818
|
+
.describe("Optional workflow id to bind the regate plan to. Auto-generated when omitted."),
|
|
1819
|
+
developerModeAcknowledged: tool.schema
|
|
1820
|
+
.boolean()
|
|
1821
|
+
.describe("Must be true to acknowledge that this tool synthesizes a developer-mode fallback_reselection approval. Not a production-grade approval."),
|
|
1822
|
+
persistRegatePlanEvidence: tool.schema
|
|
1823
|
+
.boolean()
|
|
1824
|
+
.optional()
|
|
1825
|
+
.describe("When true and a durable state root is configured, persist the resulting regate plan as fallback_regate_plan session evidence."),
|
|
1826
|
+
},
|
|
1827
|
+
async execute(input) {
|
|
1828
|
+
const request = isRecord(input)
|
|
1829
|
+
? {
|
|
1830
|
+
fromProvider: typeof input.fromProvider === "string"
|
|
1831
|
+
? input.fromProvider
|
|
1832
|
+
: undefined,
|
|
1833
|
+
toProvider: typeof input.toProvider === "string"
|
|
1834
|
+
? input.toProvider
|
|
1835
|
+
: undefined,
|
|
1836
|
+
reason: typeof input.reason === "string" ? input.reason : undefined,
|
|
1837
|
+
workflowId: typeof input.workflowId === "string"
|
|
1838
|
+
? input.workflowId
|
|
1839
|
+
: undefined,
|
|
1840
|
+
developerModeAcknowledged: typeof input.developerModeAcknowledged === "boolean"
|
|
1841
|
+
? input.developerModeAcknowledged
|
|
1842
|
+
: undefined,
|
|
1843
|
+
persistRegatePlanEvidence: typeof input.persistRegatePlanEvidence === "boolean"
|
|
1844
|
+
? input.persistRegatePlanEvidence
|
|
1845
|
+
: undefined,
|
|
1846
|
+
}
|
|
1847
|
+
: {};
|
|
1848
|
+
const result = await executeFlowDeskQuickFallbackRunV1({
|
|
1849
|
+
config,
|
|
1850
|
+
request,
|
|
1851
|
+
});
|
|
1852
|
+
return JSON.stringify(result);
|
|
1853
|
+
},
|
|
1854
|
+
}),
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function isLaneHeartbeatWriterEnabled(options) {
|
|
1858
|
+
const value = options?.[flowdeskLaneHeartbeatWriterOption];
|
|
1859
|
+
return value === true || (isRecord(value) && value.enabled === true);
|
|
1860
|
+
}
|
|
1861
|
+
function laneHeartbeatWriterConfigFromOptions(options) {
|
|
1862
|
+
const value = options?.[flowdeskLaneHeartbeatWriterOption];
|
|
1863
|
+
const enabledFromBool = value === true;
|
|
1864
|
+
const enabledFromRecord = isRecord(value) && value.enabled === true;
|
|
1865
|
+
if (!enabledFromBool && !enabledFromRecord)
|
|
1866
|
+
return undefined;
|
|
1867
|
+
const explicitRoot = isRecord(value) &&
|
|
1868
|
+
typeof value.rootDir === "string" &&
|
|
1869
|
+
value.rootDir.trim().length > 0
|
|
1870
|
+
? value.rootDir
|
|
1871
|
+
: undefined;
|
|
1872
|
+
const root = explicitRoot ?? durableStateRootFromOptions(options);
|
|
1873
|
+
if (root === undefined)
|
|
1874
|
+
return undefined;
|
|
1875
|
+
const config = { rootDir: root };
|
|
1876
|
+
if (isRecord(value) &&
|
|
1877
|
+
typeof value.defaultExpectedIntervalMs === "number" &&
|
|
1878
|
+
value.defaultExpectedIntervalMs > 0)
|
|
1879
|
+
config.defaultExpectedIntervalMs = Math.floor(value.defaultExpectedIntervalMs);
|
|
1880
|
+
return config;
|
|
1881
|
+
}
|
|
1882
|
+
export function createFlowDeskLaneHeartbeatWriterOptInTools(config) {
|
|
1883
|
+
return {
|
|
1884
|
+
[flowdeskLaneHeartbeatWriterToolName]: tool({
|
|
1885
|
+
description: [
|
|
1886
|
+
"Record a durable FlowDesk lane heartbeat for a FlowDesk-owned lane (reviewer lane, runtime lane launch, provider acquisition lane, managed-dispatch attempt, fallback regate plan). Each call produces one validated flowdesk.lane_heartbeat.v1 record with a monotonically increasing heartbeat_seq per lane id, persisted as durable session evidence. Heartbeats are diagnostic evidence only and never approve dispatch, widen scope, or replace Guard. Default soft heartbeat interval is about 2 minutes; the 5-minute stall threshold lives in the stall projection.",
|
|
1887
|
+
"WHEN TO USE: a FlowDesk coordinator that owns the lane needs to prove it is still active during a long-running step. Trigger when the assistant is coordinating a FlowDesk lane and the previous heartbeat or lifecycle update was emitted close to the soft heartbeat interval (about 2 minutes by default), OR when the user explicitly asks to record/refresh a heartbeat. Also trigger on English phrases such as 'heartbeat', 'record heartbeat', 'emit heartbeat', 'mark progress', 'I'm still alive', 'lane is still progressing', 'heartbeat for the lane', and Korean phrases such as '하트비트 남겨줘', '하트비트 기록해줘', '심박 남겨줘', '심장박동 기록', '레인 살아 있다고 표시', '진행 신호 남겨줘', '진행 표시 해줘', '아직 살아 있다고 알려줘'.",
|
|
1888
|
+
"WHEN NOT TO USE: lifecycle transitions to terminal states (use lane_lifecycle materializers), reviewer verdict observations, provider-call evidence, dispatch authority changes, arbitrary OpenCode user-driven tool calls that FlowDesk did not launch, or any case where you do not have a stable FlowDesk lane id and parent session id.",
|
|
1889
|
+
"INVOKE WITH: workflowId, attemptId, laneId, parentSessionRef (must start with 'ses-'), agentRef (must start with 'agent-'), providerQualifiedModelId (concrete provider/model id), state (one of 'created', 'running', 'awaiting_dependency', 'cooldown'), and optional progressSummaryLabel (<=120 chars and redaction-safe), progressRef (starts with 'progress-' or 'heartbeat-progress-'), expectedIntervalMs, heartbeatSeq, observedAt. When heartbeatSeq is omitted the writer derives it from the latest heartbeat for the lane id. The plugin user already opted into this tool at configuration time, so do not ask the user for extra confirmation; just call.",
|
|
1890
|
+
"AFTER CALLING: confirm status=lane_heartbeat_recorded with the heartbeat_seq, observed_at, and expected_next_heartbeat_at. On status=blocked_before_lane_heartbeat surface the redactedBlockReason and suggest /flowdesk-status or /flowdesk-doctor. Never echo raw prompts, transcripts, provider payloads, runtime echo, or any other forbidden raw markers.",
|
|
1891
|
+
].join(" "),
|
|
1892
|
+
args: {
|
|
1893
|
+
workflowId: tool.schema
|
|
1894
|
+
.string()
|
|
1895
|
+
.describe("Stable opaque FlowDesk workflow id bound to this lane."),
|
|
1896
|
+
attemptId: tool.schema
|
|
1897
|
+
.string()
|
|
1898
|
+
.describe("Stable opaque attempt id bound to this lane."),
|
|
1899
|
+
laneId: tool.schema
|
|
1900
|
+
.string()
|
|
1901
|
+
.describe("Stable opaque FlowDesk lane id (e.g. 'lane-quick-policy_security-...')."),
|
|
1902
|
+
parentSessionRef: tool.schema
|
|
1903
|
+
.string()
|
|
1904
|
+
.describe("Opaque ses-* ref for the parent session that owns this lane."),
|
|
1905
|
+
agentRef: tool.schema
|
|
1906
|
+
.string()
|
|
1907
|
+
.describe("Opaque agent-* ref for the FlowDesk agent profile."),
|
|
1908
|
+
providerQualifiedModelId: tool.schema
|
|
1909
|
+
.string()
|
|
1910
|
+
.describe("Concrete provider-qualified model id (e.g. 'openai/gpt-5.4-mini-fast')."),
|
|
1911
|
+
state: tool.schema
|
|
1912
|
+
.enum([
|
|
1913
|
+
"created",
|
|
1914
|
+
"running",
|
|
1915
|
+
"awaiting_dependency",
|
|
1916
|
+
"cooldown",
|
|
1917
|
+
])
|
|
1918
|
+
.describe("Active lane state at heartbeat time."),
|
|
1919
|
+
progressSummaryLabel: tool.schema
|
|
1920
|
+
.string()
|
|
1921
|
+
.optional()
|
|
1922
|
+
.describe("Bounded redacted progress label (<=120 chars). No raw prompts, tool args, or transcripts."),
|
|
1923
|
+
progressRef: tool.schema
|
|
1924
|
+
.string()
|
|
1925
|
+
.optional()
|
|
1926
|
+
.describe("Opaque progress reference starting with 'progress-' or 'heartbeat-progress-'."),
|
|
1927
|
+
expectedIntervalMs: tool.schema
|
|
1928
|
+
.number()
|
|
1929
|
+
.optional()
|
|
1930
|
+
.describe("Override for expected next heartbeat interval in milliseconds; clamped to >=10s and <=24h."),
|
|
1931
|
+
heartbeatSeq: tool.schema
|
|
1932
|
+
.number()
|
|
1933
|
+
.optional()
|
|
1934
|
+
.describe("Optional explicit heartbeat sequence. When omitted, the writer increments from the latest persisted heartbeat for this lane id."),
|
|
1935
|
+
observedAt: tool.schema
|
|
1936
|
+
.string()
|
|
1937
|
+
.optional()
|
|
1938
|
+
.describe("ISO timestamp; defaults to now."),
|
|
1939
|
+
},
|
|
1940
|
+
async execute(input) {
|
|
1941
|
+
const record = isRecord(input) ? input : {};
|
|
1942
|
+
const request = {
|
|
1943
|
+
rootDir: config.rootDir,
|
|
1944
|
+
workflowId: typeof record.workflowId === "string" ? record.workflowId : "",
|
|
1945
|
+
attemptId: typeof record.attemptId === "string" ? record.attemptId : "",
|
|
1946
|
+
laneId: typeof record.laneId === "string" ? record.laneId : "",
|
|
1947
|
+
parentSessionRef: typeof record.parentSessionRef === "string"
|
|
1948
|
+
? record.parentSessionRef
|
|
1949
|
+
: "",
|
|
1950
|
+
agentRef: typeof record.agentRef === "string" ? record.agentRef : "",
|
|
1951
|
+
providerQualifiedModelId: typeof record.providerQualifiedModelId === "string"
|
|
1952
|
+
? record.providerQualifiedModelId
|
|
1953
|
+
: "",
|
|
1954
|
+
state: record.state === "created" ||
|
|
1955
|
+
record.state === "running" ||
|
|
1956
|
+
record.state === "awaiting_dependency" ||
|
|
1957
|
+
record.state === "cooldown"
|
|
1958
|
+
? record.state
|
|
1959
|
+
: "running",
|
|
1960
|
+
...(typeof record.progressSummaryLabel === "string"
|
|
1961
|
+
? { progressSummaryLabel: record.progressSummaryLabel }
|
|
1962
|
+
: {}),
|
|
1963
|
+
...(typeof record.progressRef === "string"
|
|
1964
|
+
? { progressRef: record.progressRef }
|
|
1965
|
+
: {}),
|
|
1966
|
+
...(typeof record.expectedIntervalMs === "number"
|
|
1967
|
+
? { expectedIntervalMs: record.expectedIntervalMs }
|
|
1968
|
+
: config.defaultExpectedIntervalMs === undefined
|
|
1969
|
+
? {}
|
|
1970
|
+
: { expectedIntervalMs: config.defaultExpectedIntervalMs }),
|
|
1971
|
+
...(typeof record.heartbeatSeq === "number"
|
|
1972
|
+
? { heartbeatSeq: record.heartbeatSeq }
|
|
1973
|
+
: {}),
|
|
1974
|
+
...(typeof record.observedAt === "string"
|
|
1975
|
+
? { observedAt: record.observedAt }
|
|
1976
|
+
: {}),
|
|
1977
|
+
};
|
|
1978
|
+
const result = recordFlowDeskLaneHeartbeatV1(request);
|
|
1979
|
+
return JSON.stringify(result);
|
|
1980
|
+
},
|
|
1981
|
+
}),
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
function isStatusLiveEnabled(options) {
|
|
1985
|
+
const value = options?.[flowdeskStatusLiveOption];
|
|
1986
|
+
return value === true || (isRecord(value) && value.enabled === true);
|
|
1987
|
+
}
|
|
1988
|
+
function statusLiveConfigFromOptions(options) {
|
|
1989
|
+
const value = options?.[flowdeskStatusLiveOption];
|
|
1990
|
+
if (!isRecord(value) || value.enabled !== true)
|
|
1991
|
+
return undefined;
|
|
1992
|
+
const explicitRoot = typeof value.rootDir === "string" && value.rootDir.trim().length > 0
|
|
1993
|
+
? value.rootDir
|
|
1994
|
+
: undefined;
|
|
1995
|
+
const fallbackRoot = durableStateRootFromOptions(options);
|
|
1996
|
+
const rootDir = explicitRoot ?? fallbackRoot;
|
|
1997
|
+
if (rootDir === undefined)
|
|
1998
|
+
return undefined;
|
|
1999
|
+
const config = { rootDir };
|
|
2000
|
+
if (typeof value.maxWorkflows === "number" && value.maxWorkflows > 0)
|
|
2001
|
+
config.maxWorkflows = Math.floor(value.maxWorkflows);
|
|
2002
|
+
if (typeof value.maxRecentEvidencePerClass === "number" &&
|
|
2003
|
+
value.maxRecentEvidencePerClass > 0)
|
|
2004
|
+
config.maxRecentEvidencePerClass = Math.floor(value.maxRecentEvidencePerClass);
|
|
2005
|
+
if (typeof value.laneHeartbeatLateThresholdMs === "number" &&
|
|
2006
|
+
value.laneHeartbeatLateThresholdMs > 0)
|
|
2007
|
+
config.laneHeartbeatLateThresholdMs = Math.floor(value.laneHeartbeatLateThresholdMs);
|
|
2008
|
+
if (typeof value.laneHeartbeatStallThresholdMs === "number" &&
|
|
2009
|
+
value.laneHeartbeatStallThresholdMs > 0)
|
|
2010
|
+
config.laneHeartbeatStallThresholdMs = Math.floor(value.laneHeartbeatStallThresholdMs);
|
|
2011
|
+
return config;
|
|
2012
|
+
}
|
|
2013
|
+
export function createFlowDeskStatusLiveOptInTools(config) {
|
|
2014
|
+
return {
|
|
2015
|
+
[flowdeskStatusLiveToolName]: tool({
|
|
2016
|
+
description: [
|
|
2017
|
+
"Return a live FlowDesk status summary by reloading durable session evidence under the configured FlowDesk state root, including reviewer verdict counts, reviewer fan-out plans, runtime lane lifecycle records, fallback regate plans, exact-model availability cache entries, provider acquisition results, and a lane heartbeat stall projection that classifies each FlowDesk-owned lane as progressing_normal, progressing_late, stalled, terminal, or unknown based on the most recent lifecycle update.",
|
|
2018
|
+
"WHEN TO USE: the user asks about recent FlowDesk activity, current workflow progress, recent reviewer results, ongoing or stalled runs, lanes that have stopped logging, lanes that look stuck, or what has been recorded so far. Trigger on English phrases such as 'status', 'what happened', 'recent activity', 'progress', 'where are we', 'how is it going', 'recent reviews', 'recent runs', 'is it stuck', 'stalled', 'no log', 'no update', 'is anything frozen', and Korean phrases such as '상태', '어디까지', '진행 상황', '진행됐', '오늘 작업', '오늘 뭐했', '최근 활동', '최근 리뷰', '지금 어디', '상태 요약', '워크플로우 상태', '멈춘 것 같아', '멈췄어', '응답이 없어', '아무 로그도 없', '오래 걸리는', '진행이 안돼'.",
|
|
2019
|
+
"WHEN NOT TO USE: provider usage/quota questions (use flowdesk_provider_usage_live), multi-perspective code reviews (use flowdesk_quick_reviewer_run), or unrelated general chat.",
|
|
2020
|
+
"INVOKE WITH: optional workflowId. When omitted, the tool lists the most recently modified durable workflows (default up to 5). The plugin user already opted in to durable status evidence reload at configuration time, so this tool can be called automatically without per-call confirmation.",
|
|
2021
|
+
"AFTER CALLING: summarize per-workflow durable evidence counts in plain language for the user. Mention reviewer verdict labels (pass / changes_required / blocked / inconclusive), lane lifecycle states (running, complete, invocation_failed), the most recent fallback_regate_plan state, the most recent provider acquisition status, and any stalled or progressing_late lanes reported in worstLaneStallClassification with totalStalledLaneCount and totalProgressingLateLaneCount. Per-lane entries inside laneStallProjection.entries can also carry expectedNextHeartbeatOverdue=true plus secondsPastExpectedNextHeartbeat to indicate that the heartbeat's own expected_next_heartbeat_at has passed, which is a diagnostic hint independent of the configurable stall threshold. If stalled lanes are present, surface the laneStallProjection safe next actions (/flowdesk-status, /flowdesk-retry, /flowdesk-resume, /flowdesk-abort, /flowdesk-doctor, /flowdesk-export-debug) without auto-retrying, auto-aborting, or auto-fallbacking on the user's behalf. If no workflow returned evidence, say so plainly. Never echo raw provider/auth/token payloads.",
|
|
2022
|
+
].join(" "),
|
|
2023
|
+
args: {
|
|
2024
|
+
workflowId: tool.schema
|
|
2025
|
+
.string()
|
|
2026
|
+
.optional()
|
|
2027
|
+
.describe("Optional specific workflow id to summarize. When omitted, the most recently modified durable workflows are summarized."),
|
|
2028
|
+
},
|
|
2029
|
+
async execute(input) {
|
|
2030
|
+
const request = isRecord(input)
|
|
2031
|
+
? {
|
|
2032
|
+
workflowId: typeof input.workflowId === "string"
|
|
2033
|
+
? input.workflowId
|
|
2034
|
+
: undefined,
|
|
2035
|
+
}
|
|
2036
|
+
: {};
|
|
2037
|
+
const result = await executeFlowDeskStatusLiveV1({
|
|
2038
|
+
config,
|
|
2039
|
+
request,
|
|
2040
|
+
});
|
|
2041
|
+
return JSON.stringify(result);
|
|
2042
|
+
},
|
|
2043
|
+
}),
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
export function createFlowDeskProviderUsageLiveOptInTools(config) {
|
|
2047
|
+
return {
|
|
2048
|
+
[flowdeskProviderUsageLiveToolName]: tool({
|
|
2049
|
+
description: [
|
|
2050
|
+
"Return live FlowDesk provider usage availability for Claude, OpenAI/Codex, and Gemini Code Assist using provider-native usage collectors. No estimation; reads OAuth credentials and provider rate-limit/quota APIs. Each provider row carries remainingPercent, alertLevel (ok/warning/critical/exhausted/stale/unknown), and a short recommendation. The top-level worstAlertLevel and overallRecommendation summarize the riskiest provider.",
|
|
2051
|
+
"WHEN TO USE: the user asks how much usage, quota, credit, limit, or budget they have left, when their quota resets, or whether a provider is available right now. Trigger on English phrases such as 'usage', 'quota', 'remaining', 'how much left', 'rate limit', 'reset', 'budget left', and on Korean phrases such as '사용량', '잔량', '남은 사용량', '얼마 남았어', '쿼터', '한도', '리셋', '남은거 얼마야', '남은 토큰', '사용 가능량'.",
|
|
2052
|
+
"ALSO PROACTIVELY USE: before starting a large multi-step task that depends on a specific provider (e.g. extensive refactor, long agentic loop, multi-perspective review), call this tool first to check whether the chosen provider has enough headroom; if worstAlertLevel is critical or exhausted, warn the user and suggest switching providers or waiting for reset.",
|
|
2053
|
+
"WHEN NOT TO USE: general chat, status of an in-progress workflow (use status instead), or any non-usage question.",
|
|
2054
|
+
"INVOKE WITH: optional providerFamily ('claude', 'openai', 'gemini', or 'all'; default 'all'). The plugin user has already opted in to provider-native usage collection at configuration time, so this tool can be called automatically without per-call confirmation.",
|
|
2055
|
+
"AFTER CALLING: summarize per-provider availability for the user in plain language. For each provider include the bucket label (claude-5h, claude-weekly, openai-gpt-5h, gemini-pro-5h, gemini-pro-weekly, gemini-flash-daily, gemini-flash-lite-daily), remainingPercent, reset time, alertLevel, and recommendation. If any provider returned non_dispatchable, exhausted, critical, stale, or unknown, note it explicitly and surface the overallRecommendation. Never echo raw tokens or raw payloads.",
|
|
2056
|
+
].join(" "),
|
|
2057
|
+
args: {
|
|
2058
|
+
providerFamily: tool.schema
|
|
2059
|
+
.string()
|
|
2060
|
+
.optional()
|
|
2061
|
+
.describe("Provider to query: 'claude', 'openai', 'gemini', or 'all' (default)."),
|
|
2062
|
+
},
|
|
2063
|
+
async execute(input) {
|
|
2064
|
+
const request = isRecord(input)
|
|
2065
|
+
? { providerFamily: typeof input.providerFamily === "string" ? input.providerFamily : undefined }
|
|
2066
|
+
: {};
|
|
2067
|
+
const result = await executeFlowDeskProviderUsageLiveV1({
|
|
2068
|
+
config,
|
|
2069
|
+
request,
|
|
2070
|
+
});
|
|
2071
|
+
return JSON.stringify(result);
|
|
2072
|
+
},
|
|
2073
|
+
}),
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
function quickReviewerRunClientFrom(input, options) {
|
|
2077
|
+
const option = options?.[flowdeskQuickReviewerRunOption];
|
|
2078
|
+
if (isRecord(option) && isManagedDispatchBetaClient(option.client))
|
|
2079
|
+
return option.client;
|
|
2080
|
+
return isRecord(input) && isManagedDispatchBetaClient(input.client)
|
|
2081
|
+
? input.client
|
|
2082
|
+
: undefined;
|
|
2083
|
+
}
|
|
2084
|
+
function quickReviewerRunDefaultsFromOptions(options) {
|
|
2085
|
+
const option = options?.[flowdeskQuickReviewerRunOption];
|
|
2086
|
+
if (!isRecord(option))
|
|
2087
|
+
return {};
|
|
2088
|
+
return {
|
|
2089
|
+
...(typeof option.providerQualifiedModelId === "string" &&
|
|
2090
|
+
option.providerQualifiedModelId.trim().length > 0
|
|
2091
|
+
? { providerQualifiedModelId: option.providerQualifiedModelId }
|
|
2092
|
+
: {}),
|
|
2093
|
+
...(typeof option.runtimeAgent === "string" &&
|
|
2094
|
+
option.runtimeAgent.trim().length > 0
|
|
2095
|
+
? { runtimeAgent: option.runtimeAgent }
|
|
2096
|
+
: {}),
|
|
2097
|
+
...(typeof option.sourceLabel === "string" &&
|
|
2098
|
+
option.sourceLabel.trim().length > 0
|
|
2099
|
+
? { sourceLabel: option.sourceLabel }
|
|
2100
|
+
: {}),
|
|
2101
|
+
...(typeof option.durableStateRoot === "string" &&
|
|
2102
|
+
option.durableStateRoot.trim().length > 0
|
|
2103
|
+
? { rootDir: option.durableStateRoot }
|
|
2104
|
+
: {}),
|
|
2105
|
+
};
|
|
422
2106
|
}
|
|
423
2107
|
const flowdeskServerPlugin = async (input, options) => {
|
|
424
|
-
const localSession = isLocalNonDispatchAdapterEnabled(options) ||
|
|
425
|
-
|
|
2108
|
+
const localSession = isLocalNonDispatchAdapterEnabled(options) ||
|
|
2109
|
+
isNaturalLanguageRoutingEnabled(options)
|
|
2110
|
+
? createFlowDeskLocalNonDispatchAdapterSession(new Date(), undefined, {
|
|
2111
|
+
durableStateRootDir: durableStateRootFromOptions(options),
|
|
2112
|
+
productionEnablement: productionEnablementFromOptions(options),
|
|
2113
|
+
reviewerFanoutDiagnostics: reviewerFanoutDiagnosticsFromOptions(options),
|
|
2114
|
+
})
|
|
2115
|
+
: undefined;
|
|
2116
|
+
const managedDispatchBetaClient = isManagedDispatchBetaAdapterEnabled(options)
|
|
2117
|
+
|| isDefaultManagedDispatchAuthorized(options)
|
|
2118
|
+
? managedDispatchBetaClientFrom(input, options)
|
|
2119
|
+
: undefined;
|
|
2120
|
+
const managedDispatchBetaReservationStore = isManagedDispatchBetaAdapterEnabled(options) || isDefaultManagedDispatchAuthorized(options)
|
|
2121
|
+
? (managedDispatchBetaReservationStoreFrom(input, options) ??
|
|
2122
|
+
managedDispatchBetaDurableReservationStoreFrom(options))
|
|
2123
|
+
: undefined;
|
|
2124
|
+
const defaultAuthorization = defaultManagedDispatchAuthorizationFromOptions(options);
|
|
2125
|
+
const exactModelProviderAcquisitionClient = isExactModelProviderAcquisitionLiveTestEnabled(options)
|
|
2126
|
+
? exactModelProviderAcquisitionClientFrom(input, options)
|
|
2127
|
+
: undefined;
|
|
2128
|
+
const exactModelProviderAcquisitionRoot = exactModelProviderAcquisitionRootFrom(options);
|
|
2129
|
+
const exactModelProviderAcquisitionCacheMaterialization = exactModelProviderAcquisitionCacheMaterializationFromOptions(options);
|
|
2130
|
+
const runtimeReviewerExecutionClient = isRuntimeReviewerExecutionEnabled(options)
|
|
2131
|
+
? runtimeReviewerExecutionClientFrom(input, options)
|
|
2132
|
+
: undefined;
|
|
2133
|
+
const runtimeReviewerExecutionRoot = runtimeReviewerExecutionRootFrom(options);
|
|
426
2134
|
const tools = {
|
|
427
2135
|
[flowdeskPreSpikeDoctorToolName]: tool({
|
|
428
2136
|
description: "Report FlowDesk plugin load status without enabling real dispatch, provider calls, or runtime execution.",
|
|
429
2137
|
args: {},
|
|
430
2138
|
async execute() {
|
|
2139
|
+
const providerUsageLiveConfigForDoctor = isProviderUsageLiveEnabled(options)
|
|
2140
|
+
? providerUsageLiveConfigFromOptions(options)
|
|
2141
|
+
: undefined;
|
|
2142
|
+
const statusLiveConfigForDoctor = isStatusLiveEnabled(options)
|
|
2143
|
+
? statusLiveConfigFromOptions(options)
|
|
2144
|
+
: undefined;
|
|
2145
|
+
const quickFallbackRunConfigForDoctor = isQuickFallbackRunEnabled(options)
|
|
2146
|
+
? quickFallbackRunConfigFromOptions(options)
|
|
2147
|
+
: undefined;
|
|
2148
|
+
const laneHeartbeatWriterConfigForDoctor = isLaneHeartbeatWriterEnabled(options)
|
|
2149
|
+
? laneHeartbeatWriterConfigFromOptions(options)
|
|
2150
|
+
: undefined;
|
|
2151
|
+
const quickReviewerRunRegistered = isQuickReviewerRunEnabled(options) &&
|
|
2152
|
+
quickReviewerRunClientFrom(input, options) !== undefined;
|
|
2153
|
+
const naturalLanguageTools = {
|
|
2154
|
+
quickReviewerRun: {
|
|
2155
|
+
enabled: isQuickReviewerRunEnabled(options),
|
|
2156
|
+
registered: quickReviewerRunRegistered,
|
|
2157
|
+
missingClient: isQuickReviewerRunEnabled(options) && !quickReviewerRunRegistered
|
|
2158
|
+
? "injected OpenCode SDK client (input.client) is missing"
|
|
2159
|
+
: undefined,
|
|
2160
|
+
},
|
|
2161
|
+
providerUsageLive: {
|
|
2162
|
+
enabled: isProviderUsageLiveEnabled(options),
|
|
2163
|
+
registered: providerUsageLiveConfigForDoctor !== undefined,
|
|
2164
|
+
providers: providerUsageLiveConfigForDoctor?.providers,
|
|
2165
|
+
geminiOAuthConfigured: providerUsageLiveConfigForDoctor !== undefined &&
|
|
2166
|
+
((providerUsageLiveConfigForDoctor.geminiOAuthClientId !==
|
|
2167
|
+
undefined &&
|
|
2168
|
+
providerUsageLiveConfigForDoctor.geminiOAuthClientSecret !==
|
|
2169
|
+
undefined) ||
|
|
2170
|
+
typeof process.env.FLOWDESK_GEMINI_OAUTH_CLIENT_ID ===
|
|
2171
|
+
"string" ||
|
|
2172
|
+
typeof process.env.FLOWDESK_GEMINI_OAUTH_CLIENT_SECRET ===
|
|
2173
|
+
"string"),
|
|
2174
|
+
hint: isProviderUsageLiveEnabled(options) &&
|
|
2175
|
+
providerUsageLiveConfigForDoctor === undefined
|
|
2176
|
+
? "providerUsageLive.enabled=true but no provider family configured; set providers=['claude','openai','gemini']"
|
|
2177
|
+
: undefined,
|
|
2178
|
+
},
|
|
2179
|
+
statusLive: {
|
|
2180
|
+
enabled: isStatusLiveEnabled(options),
|
|
2181
|
+
registered: statusLiveConfigForDoctor !== undefined,
|
|
2182
|
+
rootDir: statusLiveConfigForDoctor?.rootDir,
|
|
2183
|
+
laneHeartbeatLateThresholdMs: statusLiveConfigForDoctor?.laneHeartbeatLateThresholdMs,
|
|
2184
|
+
laneHeartbeatStallThresholdMs: statusLiveConfigForDoctor?.laneHeartbeatStallThresholdMs,
|
|
2185
|
+
exposesLaneStallProjection: statusLiveConfigForDoctor !== undefined,
|
|
2186
|
+
exposesExpectedNextHeartbeatOverdueHint: statusLiveConfigForDoctor !== undefined,
|
|
2187
|
+
hint: isStatusLiveEnabled(options) &&
|
|
2188
|
+
statusLiveConfigForDoctor === undefined
|
|
2189
|
+
? "statusLive.enabled=true but no durable state root resolved; set statusLive.rootDir or top-level durableStateRoot"
|
|
2190
|
+
: undefined,
|
|
2191
|
+
},
|
|
2192
|
+
quickFallbackRun: {
|
|
2193
|
+
enabled: isQuickFallbackRunEnabled(options),
|
|
2194
|
+
registered: quickFallbackRunConfigForDoctor !== undefined,
|
|
2195
|
+
defaultFromProvider: quickFallbackRunConfigForDoctor?.defaultFromProvider,
|
|
2196
|
+
defaultToProvider: quickFallbackRunConfigForDoctor?.defaultToProvider,
|
|
2197
|
+
persistsRegatePlanEvidence: quickFallbackRunConfigForDoctor?.rootDir !== undefined,
|
|
2198
|
+
},
|
|
2199
|
+
laneHeartbeatWriter: {
|
|
2200
|
+
enabled: isLaneHeartbeatWriterEnabled(options),
|
|
2201
|
+
registered: laneHeartbeatWriterConfigForDoctor !== undefined,
|
|
2202
|
+
rootDir: laneHeartbeatWriterConfigForDoctor?.rootDir,
|
|
2203
|
+
defaultExpectedIntervalMs: laneHeartbeatWriterConfigForDoctor?.defaultExpectedIntervalMs,
|
|
2204
|
+
hint: isLaneHeartbeatWriterEnabled(options) &&
|
|
2205
|
+
laneHeartbeatWriterConfigForDoctor === undefined
|
|
2206
|
+
? "laneHeartbeatWriter.enabled=true but no durable state root resolved; set laneHeartbeatWriter.rootDir or top-level durableStateRoot"
|
|
2207
|
+
: undefined,
|
|
2208
|
+
},
|
|
2209
|
+
chatMessageStallAlert: {
|
|
2210
|
+
enabled: options?.[flowdeskChatMessageStallAlertOption] === true ||
|
|
2211
|
+
(isRecord(options?.[flowdeskChatMessageStallAlertOption]) &&
|
|
2212
|
+
(options?.[flowdeskChatMessageStallAlertOption])
|
|
2213
|
+
.enabled === true),
|
|
2214
|
+
registered: chatMessageStallAlertOptionsFrom(options, statusLiveConfigForDoctor) !==
|
|
2215
|
+
undefined,
|
|
2216
|
+
requires: "statusLive.enabled=true and durableStateRoot (top-level or chatMessageStallAlert.rootDir)",
|
|
2217
|
+
note: "chat.message hook appends a passive stall card listing stalled lanes and safe next actions; no auto-retry, auto-abort, or auto-fallback.",
|
|
2218
|
+
},
|
|
2219
|
+
};
|
|
431
2220
|
return JSON.stringify({
|
|
432
2221
|
pluginId: flowdeskPluginId,
|
|
433
2222
|
loaded: true,
|
|
434
|
-
probeRegistrationProfile: isFds1SchemaConversionProbeEnabled(options)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
2223
|
+
probeRegistrationProfile: isFds1SchemaConversionProbeEnabled(options)
|
|
2224
|
+
? "sandbox_conformance_probe_only"
|
|
2225
|
+
: "disabled",
|
|
2226
|
+
localNonDispatchAdapterProfile: isLocalNonDispatchAdapterEnabled(options)
|
|
2227
|
+
? flowdeskLocalNonDispatchAdapterProfile
|
|
2228
|
+
: "disabled",
|
|
2229
|
+
naturalLanguageRoutingProfile: isNaturalLanguageRoutingEnabled(options)
|
|
2230
|
+
? "chat_steering_command_backed_non_dispatch"
|
|
2231
|
+
: "disabled",
|
|
2232
|
+
naturalLanguageTools,
|
|
2233
|
+
productionPromotionGate: defaultAuthorization === undefined
|
|
2234
|
+
? "release1_non_dispatch_command_registration_ready"
|
|
2235
|
+
: "default_managed_dispatch_authorized_registration_ready",
|
|
2236
|
+
defaultManagedDispatchRegistrationAuthorized: defaultAuthorization !== undefined,
|
|
2237
|
+
...(defaultAuthorization === undefined
|
|
2238
|
+
? {}
|
|
2239
|
+
: {
|
|
2240
|
+
defaultManagedDispatchAuthorizationRef: defaultAuthorization.authorization_id,
|
|
2241
|
+
defaultManagedDispatchReadinessRef: defaultAuthorization.readiness_ref,
|
|
2242
|
+
}),
|
|
438
2243
|
productionOpenCodeRegistration: hasProductionOpenCodeRegistration(),
|
|
439
2244
|
productionToolRegistration: flowdeskPluginScaffold.productionToolRegistration,
|
|
440
2245
|
release1HandlerReadiness: getFlowDeskRelease1HandlerReadinessSummary(),
|
|
@@ -445,26 +2250,107 @@ const flowdeskServerPlugin = async (input, options) => {
|
|
|
445
2250
|
runtimeExecution: false,
|
|
446
2251
|
actualLaneLaunch: false,
|
|
447
2252
|
fallbackAuthority: false,
|
|
448
|
-
hardCancelOrNoReplyAuthority: false
|
|
2253
|
+
hardCancelOrNoReplyAuthority: false,
|
|
449
2254
|
});
|
|
450
|
-
}
|
|
451
|
-
})
|
|
2255
|
+
},
|
|
2256
|
+
}),
|
|
452
2257
|
};
|
|
453
2258
|
if (isFds1SchemaConversionProbeEnabled(options))
|
|
454
2259
|
Object.assign(tools, createFlowDeskFds1SchemaConversionProbeTools());
|
|
455
2260
|
if (isLocalNonDispatchAdapterEnabled(options))
|
|
456
|
-
Object.assign(tools, createFlowDeskLocalNonDispatchAdapterTools(new Date(), localSession
|
|
2261
|
+
Object.assign(tools, createFlowDeskLocalNonDispatchAdapterTools(new Date(), localSession, {
|
|
2262
|
+
client: managedDispatchBetaClient,
|
|
2263
|
+
reservationStore: managedDispatchBetaReservationStore,
|
|
2264
|
+
durableStateRootDir: durableStateRootFromOptions(options),
|
|
2265
|
+
defaultAuthorization,
|
|
2266
|
+
}));
|
|
457
2267
|
if (isNaturalLanguageRoutingEnabled(options))
|
|
458
2268
|
Object.assign(tools, createFlowDeskNaturalLanguageRoutingTools(new Date(), localSession));
|
|
459
2269
|
if (managedDispatchBetaClient !== undefined)
|
|
460
|
-
Object.assign(tools, createFlowDeskManagedDispatchBetaOptInTools(managedDispatchBetaClient));
|
|
2270
|
+
Object.assign(tools, createFlowDeskManagedDispatchBetaOptInTools(managedDispatchBetaClient, managedDispatchBetaReservationStore, durableStateRootFromOptions(options)));
|
|
2271
|
+
if (exactModelProviderAcquisitionClient !== undefined && exactModelProviderAcquisitionRoot !== undefined)
|
|
2272
|
+
Object.assign(tools, createFlowDeskExactModelProviderAcquisitionLiveTestOptInTools(exactModelProviderAcquisitionClient, exactModelProviderAcquisitionRoot, exactModelProviderAcquisitionCacheMaterialization, runtimeReviewerExecutionClient ??
|
|
2273
|
+
exactModelProviderAcquisitionRuntimeReviewerExecutionClientFrom(input, options)));
|
|
2274
|
+
if (runtimeReviewerExecutionClient !== undefined &&
|
|
2275
|
+
runtimeReviewerExecutionRoot !== undefined)
|
|
2276
|
+
Object.assign(tools, createFlowDeskRuntimeReviewerExecutionOptInTools(runtimeReviewerExecutionClient, runtimeReviewerExecutionRoot));
|
|
2277
|
+
if (isManagedFallbackRegateEnabled(options))
|
|
2278
|
+
Object.assign(tools, createFlowDeskManagedFallbackRegateOptInTools(durableStateRootFromOptions(options)));
|
|
2279
|
+
const quickReviewerRunClient = isQuickReviewerRunEnabled(options)
|
|
2280
|
+
? quickReviewerRunClientFrom(input, options)
|
|
2281
|
+
: undefined;
|
|
2282
|
+
if (quickReviewerRunClient !== undefined)
|
|
2283
|
+
Object.assign(tools, createFlowDeskQuickReviewerRunOptInTools(quickReviewerRunClient, quickReviewerRunDefaultsFromOptions(options)));
|
|
2284
|
+
const providerUsageLiveConfig = isProviderUsageLiveEnabled(options)
|
|
2285
|
+
? providerUsageLiveConfigFromOptions(options)
|
|
2286
|
+
: undefined;
|
|
2287
|
+
if (providerUsageLiveConfig !== undefined)
|
|
2288
|
+
Object.assign(tools, createFlowDeskProviderUsageLiveOptInTools(providerUsageLiveConfig));
|
|
2289
|
+
const statusLiveConfig = isStatusLiveEnabled(options)
|
|
2290
|
+
? statusLiveConfigFromOptions(options)
|
|
2291
|
+
: undefined;
|
|
2292
|
+
if (statusLiveConfig !== undefined)
|
|
2293
|
+
Object.assign(tools, createFlowDeskStatusLiveOptInTools(statusLiveConfig));
|
|
2294
|
+
const quickFallbackRunConfig = isQuickFallbackRunEnabled(options)
|
|
2295
|
+
? quickFallbackRunConfigFromOptions(options)
|
|
2296
|
+
: undefined;
|
|
2297
|
+
if (quickFallbackRunConfig !== undefined)
|
|
2298
|
+
Object.assign(tools, createFlowDeskQuickFallbackRunOptInTools(quickFallbackRunConfig));
|
|
2299
|
+
const laneHeartbeatWriterConfig = isLaneHeartbeatWriterEnabled(options)
|
|
2300
|
+
? laneHeartbeatWriterConfigFromOptions(options)
|
|
2301
|
+
: undefined;
|
|
2302
|
+
if (laneHeartbeatWriterConfig !== undefined)
|
|
2303
|
+
Object.assign(tools, createFlowDeskLaneHeartbeatWriterOptInTools(laneHeartbeatWriterConfig));
|
|
461
2304
|
if (!isNaturalLanguageRoutingEnabled(options))
|
|
462
2305
|
return { tool: tools };
|
|
463
|
-
|
|
2306
|
+
const stallAlertOption = chatMessageStallAlertOptionsFrom(options, statusLiveConfig);
|
|
2307
|
+
return {
|
|
2308
|
+
tool: tools,
|
|
2309
|
+
"chat.message": createFlowDeskNaturalLanguageChatMessageHook(() => new Date(), localSession, stallAlertOption),
|
|
2310
|
+
};
|
|
464
2311
|
};
|
|
2312
|
+
export const flowdeskChatMessageStallAlertOption = "chatMessageStallAlert";
|
|
2313
|
+
function chatMessageStallAlertOptionsFrom(options, statusLiveConfig) {
|
|
2314
|
+
const raw = options?.[flowdeskChatMessageStallAlertOption];
|
|
2315
|
+
if (raw === false)
|
|
2316
|
+
return undefined;
|
|
2317
|
+
const recordRaw = isRecord(raw) ? raw : undefined;
|
|
2318
|
+
const explicitEnabled = recordRaw?.enabled === true || raw === true;
|
|
2319
|
+
const explicitDisabled = recordRaw?.enabled === false;
|
|
2320
|
+
if (explicitDisabled)
|
|
2321
|
+
return undefined;
|
|
2322
|
+
const explicitRoot = recordRaw !== undefined &&
|
|
2323
|
+
typeof recordRaw.rootDir === "string" &&
|
|
2324
|
+
recordRaw.rootDir.trim().length > 0
|
|
2325
|
+
? recordRaw.rootDir
|
|
2326
|
+
: undefined;
|
|
2327
|
+
const fallbackRoot = statusLiveConfig?.rootDir ?? durableStateRootFromOptions(options);
|
|
2328
|
+
const rootDir = explicitRoot ?? fallbackRoot;
|
|
2329
|
+
if (rootDir === undefined)
|
|
2330
|
+
return undefined;
|
|
2331
|
+
if (!explicitEnabled && statusLiveConfig === undefined)
|
|
2332
|
+
return undefined;
|
|
2333
|
+
const config = { rootDir };
|
|
2334
|
+
if (recordRaw !== undefined &&
|
|
2335
|
+
typeof recordRaw.maxWorkflows === "number" &&
|
|
2336
|
+
recordRaw.maxWorkflows > 0)
|
|
2337
|
+
config.maxWorkflows = Math.floor(recordRaw.maxWorkflows);
|
|
2338
|
+
if (recordRaw !== undefined &&
|
|
2339
|
+
typeof recordRaw.laneHeartbeatLateThresholdMs === "number" &&
|
|
2340
|
+
recordRaw.laneHeartbeatLateThresholdMs > 0)
|
|
2341
|
+
config.laneHeartbeatLateThresholdMs = Math.floor(recordRaw.laneHeartbeatLateThresholdMs);
|
|
2342
|
+
if (recordRaw !== undefined &&
|
|
2343
|
+
typeof recordRaw.laneHeartbeatStallThresholdMs === "number" &&
|
|
2344
|
+
recordRaw.laneHeartbeatStallThresholdMs > 0)
|
|
2345
|
+
config.laneHeartbeatStallThresholdMs = Math.floor(recordRaw.laneHeartbeatStallThresholdMs);
|
|
2346
|
+
if (recordRaw !== undefined &&
|
|
2347
|
+
typeof recordRaw.includeProgressingLate === "boolean")
|
|
2348
|
+
config.includeProgressingLate = recordRaw.includeProgressingLate;
|
|
2349
|
+
return config;
|
|
2350
|
+
}
|
|
465
2351
|
export const flowdeskOpenCodeServerPlugin = {
|
|
466
2352
|
id: flowdeskPluginId,
|
|
467
|
-
server: flowdeskServerPlugin
|
|
2353
|
+
server: flowdeskServerPlugin,
|
|
468
2354
|
};
|
|
469
2355
|
export default flowdeskOpenCodeServerPlugin;
|
|
470
2356
|
//# sourceMappingURL=server.js.map
|