@flowdesk/opencode-plugin 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent-task-output.d.ts +12 -0
- package/dist/agent-task-output.d.ts.map +1 -1
- package/dist/agent-task-output.js +110 -4
- package/dist/agent-task-output.js.map +1 -1
- package/dist/agent-task-runner.d.ts +12 -1
- package/dist/agent-task-runner.d.ts.map +1 -1
- package/dist/agent-task-runner.js +237 -16
- package/dist/agent-task-runner.js.map +1 -1
- package/dist/completion-ui-cache.d.ts.map +1 -1
- package/dist/completion-ui-cache.js +159 -29
- package/dist/completion-ui-cache.js.map +1 -1
- package/dist/event-hook-observer.d.ts.map +1 -1
- package/dist/event-hook-observer.js +66 -2
- package/dist/event-hook-observer.js.map +1 -1
- package/dist/managed-dispatch-adapter.d.ts +62 -0
- package/dist/managed-dispatch-adapter.d.ts.map +1 -1
- package/dist/managed-dispatch-adapter.js +465 -1
- package/dist/managed-dispatch-adapter.js.map +1 -1
- package/dist/model-selection-engine.d.ts +15 -2
- package/dist/model-selection-engine.d.ts.map +1 -1
- package/dist/model-selection-engine.js +109 -42
- package/dist/model-selection-engine.js.map +1 -1
- package/dist/provider-usage-live-tool.d.ts.map +1 -1
- package/dist/provider-usage-live-tool.js +135 -33
- package/dist/provider-usage-live-tool.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +52 -3
- package/dist/server.js.map +1 -1
- package/dist/stall-recovery.d.ts +4 -3
- package/dist/stall-recovery.d.ts.map +1 -1
- package/dist/stall-recovery.js +245 -25
- package/dist/stall-recovery.js.map +1 -1
- package/dist/status-live-tool.d.ts.map +1 -1
- package/dist/status-live-tool.js +1 -0
- package/dist/status-live-tool.js.map +1 -1
- package/dist/tui-subtask-activity.d.ts +4 -0
- package/dist/tui-subtask-activity.d.ts.map +1 -1
- package/dist/tui-subtask-activity.js +29 -24
- package/dist/tui-subtask-activity.js.map +1 -1
- package/dist/tui-usage-snapshot.d.ts.map +1 -1
- package/dist/tui-usage-snapshot.js +92 -6
- package/dist/tui-usage-snapshot.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +43 -16
- package/dist/tui.js.map +1 -1
- package/dist/workflow-assign-tool.d.ts.map +1 -1
- package/dist/workflow-assign-tool.js +21 -3
- package/dist/workflow-assign-tool.js.map +1 -1
- package/dist/workflow-dispatch-tool.d.ts +12 -0
- package/dist/workflow-dispatch-tool.d.ts.map +1 -1
- package/dist/workflow-dispatch-tool.js +24 -26
- package/dist/workflow-dispatch-tool.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,19 @@ export interface FlowDeskAgentTaskOutputObservationV1 {
|
|
|
10
10
|
messageCount: number;
|
|
11
11
|
outputKind: FlowDeskAgentTaskOutputKindV1;
|
|
12
12
|
usableForSynthesis: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* ADVISORY ONLY. The captured text superficially resembles a refusal or an
|
|
15
|
+
* error message. This is a hint for the coordinator's substance judgement; it
|
|
16
|
+
* NEVER causes the capture layer to drop or fail the result.
|
|
17
|
+
*/
|
|
18
|
+
looksLikeRefusalOrError: boolean;
|
|
13
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* ADVISORY heuristic for the coordinator: does this text look like a refusal or
|
|
22
|
+
* a bare error message rather than a substantive answer? Capture never gates on
|
|
23
|
+
* this; the main coordinator uses it to decide whether to re-select and retry.
|
|
24
|
+
*/
|
|
25
|
+
export declare function flowDeskTextLooksLikeRefusalOrErrorV1(text: string | undefined): boolean;
|
|
14
26
|
export declare function flowDeskAgentTaskMessageItems(value: unknown): unknown[];
|
|
15
27
|
export declare function classifyFlowDeskAgentTaskOutputKindV1(text: string | undefined): FlowDeskAgentTaskOutputKindV1;
|
|
16
28
|
export declare function observeFlowDeskAgentTaskOutputV1(response: unknown): FlowDeskAgentTaskOutputObservationV1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-task-output.d.ts","sourceRoot":"","sources":["../src/agent-task-output.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mCAAmC,GAAG,OAAO,GAAG,SAAS,CAAC;AACtE,MAAM,MAAM,6BAA6B,GACtC,cAAc,GACd,kBAAkB,GAClB,eAAe,GACf,iBAAiB,GACjB,OAAO,CAAC;AAEX,MAAM,WAAW,oCAAoC;IACpD,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,6BAA6B,CAAC;IAC1C,kBAAkB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-task-output.d.ts","sourceRoot":"","sources":["../src/agent-task-output.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mCAAmC,GAAG,OAAO,GAAG,SAAS,CAAC;AACtE,MAAM,MAAM,6BAA6B,GACtC,cAAc,GACd,kBAAkB,GAClB,eAAe,GACf,iBAAiB,GACjB,OAAO,CAAC;AAEX,MAAM,WAAW,oCAAoC;IACpD,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,6BAA6B,CAAC;IAC1C,kBAAkB,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,uBAAuB,EAAE,OAAO,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,qCAAqC,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CA6BvF;AAUD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,CAmBvE;AA+CD,wBAAgB,qCAAqC,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,6BAA6B,CAqB7G;AAED,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,OAAO,GAAG,oCAAoC,CA4FxG"}
|
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADVISORY heuristic for the coordinator: does this text look like a refusal or
|
|
3
|
+
* a bare error message rather than a substantive answer? Capture never gates on
|
|
4
|
+
* this; the main coordinator uses it to decide whether to re-select and retry.
|
|
5
|
+
*/
|
|
6
|
+
export function flowDeskTextLooksLikeRefusalOrErrorV1(text) {
|
|
7
|
+
const normalized = text?.trim().toLowerCase() ?? "";
|
|
8
|
+
if (normalized.length === 0)
|
|
9
|
+
return false;
|
|
10
|
+
const patterns = [
|
|
11
|
+
"i cannot",
|
|
12
|
+
"i can't",
|
|
13
|
+
"i can not",
|
|
14
|
+
"i'm unable",
|
|
15
|
+
"i am unable",
|
|
16
|
+
"i'm sorry, but i can",
|
|
17
|
+
"i am sorry, but i can",
|
|
18
|
+
"i won't be able",
|
|
19
|
+
"i will not be able",
|
|
20
|
+
"as an ai",
|
|
21
|
+
"i'm not able to",
|
|
22
|
+
"i am not able to",
|
|
23
|
+
"error:",
|
|
24
|
+
"exception:",
|
|
25
|
+
"traceback (most recent call last)",
|
|
26
|
+
"rate limit",
|
|
27
|
+
"rate-limited",
|
|
28
|
+
"request failed",
|
|
29
|
+
"failed to",
|
|
30
|
+
"unable to complete",
|
|
31
|
+
];
|
|
32
|
+
// Only treat as refusal/error when the text is short-ish and dominated by the
|
|
33
|
+
// pattern, to avoid false positives on long answers that merely mention them.
|
|
34
|
+
const head = normalized.slice(0, 240);
|
|
35
|
+
return patterns.some(pattern => head.includes(pattern));
|
|
36
|
+
}
|
|
1
37
|
function isRecord(value) {
|
|
2
38
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
39
|
}
|
|
@@ -12,7 +48,22 @@ export function flowDeskAgentTaskMessageItems(value) {
|
|
|
12
48
|
return [];
|
|
13
49
|
if (Array.isArray(data.items))
|
|
14
50
|
return data.items;
|
|
15
|
-
|
|
51
|
+
if (Array.isArray(data.messages))
|
|
52
|
+
return data.messages;
|
|
53
|
+
// Gemini candidates wrapper: { candidates: [{ content: { role, parts }, finishReason }] }
|
|
54
|
+
if (Array.isArray(data.candidates)) {
|
|
55
|
+
const msgs = [];
|
|
56
|
+
for (const candidate of data.candidates) {
|
|
57
|
+
if (!isRecord(candidate))
|
|
58
|
+
continue;
|
|
59
|
+
const content = isRecord(candidate.content) ? candidate.content : candidate;
|
|
60
|
+
const finishReason = typeof candidate.finishReason === "string" ? candidate.finishReason
|
|
61
|
+
: typeof candidate.finish_reason === "string" ? candidate.finish_reason : undefined;
|
|
62
|
+
msgs.push({ ...content, _finishReason: finishReason });
|
|
63
|
+
}
|
|
64
|
+
return msgs;
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
16
67
|
}
|
|
17
68
|
function partText(part) {
|
|
18
69
|
if (typeof part.text === "string")
|
|
@@ -25,6 +76,24 @@ function isTerminalPart(part) {
|
|
|
25
76
|
return ((type === "step-finish" || type === "step_finish" || type === "finish") &&
|
|
26
77
|
(reason === "stop" || reason === "complete" || reason === "completed"));
|
|
27
78
|
}
|
|
79
|
+
/** Check message-level terminal signals (OpenAI finish_reason, Gemini finishReason). */
|
|
80
|
+
function isTerminalMessage(msg) {
|
|
81
|
+
// OpenAI: finish_reason at message or choice level
|
|
82
|
+
const fr = typeof msg.finish_reason === "string" ? msg.finish_reason
|
|
83
|
+
: typeof msg.finishReason === "string" ? msg.finishReason
|
|
84
|
+
: typeof msg._finishReason === "string" ? msg._finishReason : undefined;
|
|
85
|
+
if (fr !== undefined) {
|
|
86
|
+
const normalized = fr.toLowerCase();
|
|
87
|
+
if (normalized === "stop" || normalized === "end_turn" || normalized === "complete" || normalized === "completed") {
|
|
88
|
+
return { terminal: true, reason: fr };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// OpenAI status field
|
|
92
|
+
if (msg.status === "completed" || msg.status === "complete") {
|
|
93
|
+
return { terminal: true, reason: String(msg.status) };
|
|
94
|
+
}
|
|
95
|
+
return { terminal: false, reason: undefined };
|
|
96
|
+
}
|
|
28
97
|
function terminalReason(part) {
|
|
29
98
|
return typeof part.reason === "string" ? part.reason : undefined;
|
|
30
99
|
}
|
|
@@ -73,11 +142,43 @@ export function observeFlowDeskAgentTaskOutputV1(response) {
|
|
|
73
142
|
const msgRec = isRecord(msg) ? msg : undefined;
|
|
74
143
|
const info = isRecord(msgRec?.info) ? msgRec.info : msgRec;
|
|
75
144
|
const role = info?.role;
|
|
76
|
-
|
|
145
|
+
// Accept "assistant" and "model" (Gemini uses "model" for assistant role)
|
|
146
|
+
const isAssistantRole = role === "assistant" || role === "model";
|
|
147
|
+
// Check message-level terminal signals (OpenAI/Gemini)
|
|
148
|
+
if (msgRec !== undefined) {
|
|
149
|
+
const msgTerminal = isTerminalMessage(msgRec);
|
|
150
|
+
if (!msgTerminal.terminal && info !== undefined && info !== msgRec) {
|
|
151
|
+
const infoTerminal = isTerminalMessage(info);
|
|
152
|
+
if (infoTerminal.terminal) {
|
|
153
|
+
terminalObserved = true;
|
|
154
|
+
observedTerminalReason = infoTerminal.reason;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else if (msgTerminal.terminal) {
|
|
158
|
+
terminalObserved = true;
|
|
159
|
+
observedTerminalReason = msgTerminal.reason;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Collect parts from msg.parts, msg.info.parts, or msg.content (when content is array of parts)
|
|
163
|
+
let parts = Array.isArray(msgRec?.parts)
|
|
77
164
|
? msgRec.parts
|
|
78
165
|
: Array.isArray(info?.parts)
|
|
79
166
|
? info.parts
|
|
80
167
|
: [];
|
|
168
|
+
// OpenAI multi-part content: content is array of part objects
|
|
169
|
+
if (parts.length === 0 && msgRec !== undefined && Array.isArray(msgRec.content)) {
|
|
170
|
+
parts = msgRec.content;
|
|
171
|
+
}
|
|
172
|
+
// Message-level content string (OpenAI Chat Completions: { role: "assistant", content: "..." })
|
|
173
|
+
if (parts.length === 0 && isAssistantRole && msgRec !== undefined && typeof msgRec.content === "string" && msgRec.content.trim().length > 0) {
|
|
174
|
+
latestText = msgRec.content;
|
|
175
|
+
textPartCount++;
|
|
176
|
+
}
|
|
177
|
+
// Also check info-level content string
|
|
178
|
+
if (parts.length === 0 && isAssistantRole && info !== undefined && info !== msgRec && typeof info.content === "string" && info.content.trim().length > 0) {
|
|
179
|
+
latestText = info.content;
|
|
180
|
+
textPartCount++;
|
|
181
|
+
}
|
|
81
182
|
for (const rawPart of parts) {
|
|
82
183
|
const part = isRecord(rawPart) ? rawPart : undefined;
|
|
83
184
|
if (part === undefined)
|
|
@@ -92,9 +193,10 @@ export function observeFlowDeskAgentTaskOutputV1(response) {
|
|
|
92
193
|
reasoningPartCount++;
|
|
93
194
|
continue;
|
|
94
195
|
}
|
|
95
|
-
if (
|
|
196
|
+
if (!isAssistantRole)
|
|
96
197
|
continue;
|
|
97
|
-
|
|
198
|
+
// Accept text, output_text (OpenAI Responses API), or undefined type
|
|
199
|
+
if (part.type !== undefined && part.type !== "text" && part.type !== "output_text")
|
|
98
200
|
continue;
|
|
99
201
|
const text = partText(part);
|
|
100
202
|
if (typeof text === "string" && text.trim().length > 0) {
|
|
@@ -113,7 +215,11 @@ export function observeFlowDeskAgentTaskOutputV1(response) {
|
|
|
113
215
|
reasoningPartCount,
|
|
114
216
|
messageCount: items.length,
|
|
115
217
|
outputKind,
|
|
218
|
+
// Capture-side usability = "there is some text to keep". This is advisory
|
|
219
|
+
// transport metadata, NOT a substance/quality judgement (that is the
|
|
220
|
+
// coordinator's job). Only genuinely empty/tool-only captures are unusable.
|
|
116
221
|
usableForSynthesis: outputKind !== "empty" && outputKind !== "tool_trace_only",
|
|
222
|
+
looksLikeRefusalOrError: flowDeskTextLooksLikeRefusalOrErrorV1(latestText),
|
|
117
223
|
};
|
|
118
224
|
}
|
|
119
225
|
//# sourceMappingURL=agent-task-output.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-task-output.js","sourceRoot":"","sources":["../src/agent-task-output.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-task-output.js","sourceRoot":"","sources":["../src/agent-task-output.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type FlowDeskManagedDispatchBetaOpenCodeClientV1 } from "./managed-dispatch-adapter.js";
|
|
2
|
+
export interface FlowDeskAgentTaskFallbackBindingV1 {
|
|
3
|
+
agentRef: string;
|
|
4
|
+
providerQualifiedModelId: string;
|
|
5
|
+
}
|
|
2
6
|
export interface FlowDeskAgentTaskInputV1 {
|
|
3
7
|
workflowId: string;
|
|
4
8
|
taskId: string;
|
|
@@ -17,12 +21,19 @@ export interface FlowDeskAgentTaskInputV1 {
|
|
|
17
21
|
* The coordinator polls flowdesk_status_live to detect terminal state.
|
|
18
22
|
*/
|
|
19
23
|
asyncMode?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* When provided and the primary attempt fails with no_response,
|
|
26
|
+
* automatically retry once with this fallback agent/model binding.
|
|
27
|
+
*/
|
|
28
|
+
fallbackBinding?: FlowDeskAgentTaskFallbackBindingV1;
|
|
20
29
|
/** Override quiet period before nudge — for testing only */
|
|
21
30
|
_nudgeQuietPeriodMs?: number;
|
|
22
31
|
/** Override messages poll timeout — for testing only (default 3000ms in prod) */
|
|
23
32
|
_messagesTimeoutMs?: number;
|
|
24
|
-
/** Override launch timeout — for testing only (default
|
|
33
|
+
/** Override launch timeout — for testing only (default 30000ms in prod) */
|
|
25
34
|
_launchTimeoutMs?: number;
|
|
35
|
+
/** Internal: true when this is already a fallback retry (prevents infinite retry) */
|
|
36
|
+
_isFallbackRetry?: boolean;
|
|
26
37
|
}
|
|
27
38
|
export type FlowDeskAgentTaskResultV1 = {
|
|
28
39
|
status: "task_completed";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-task-runner.d.ts","sourceRoot":"","sources":["../src/agent-task-runner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-task-runner.d.ts","sourceRoot":"","sources":["../src/agent-task-runner.ts"],"names":[],"mappings":"AAcA,OAAO,EACN,KAAK,2CAA2C,EAGhD,MAAM,+BAA+B,CAAC;AASvC,MAAM,WAAW,kCAAkC;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,wBAAwB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,2CAA2C,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,kCAAkC,CAAC;IACrD,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,MAAM,yBAAyB,GAClC;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAE,GAC9F;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9F,+DAA+D;AAC/D,eAAO,MAAM,uCAAuC,EAAG,sCAA+C,CAAC;AAsBvG,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAMrH;AA+bD,wBAAsB,0BAA0B,CAC/C,KAAK,EAAE,wBAAwB,GAC7B,OAAO,CAAC,yBAAyB,CAAC,CAmjBpC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { applyFlowDeskSessionEvidenceWriteIntentsV1, prepareFlowDeskSessionEvidenceWriteIntentV1, } from "@flowdesk/core";
|
|
2
|
+
import { applyFlowDeskSessionEvidenceWriteIntentsV1, prepareFlowDeskSessionEvidenceWriteIntentV1, reloadFlowDeskSessionEvidenceV1, validateTopTierReviewVerdictV1, } from "@flowdesk/core";
|
|
3
3
|
import { launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1, materializeFlowDeskRuntimeLaneLaunchLifecycleEvidenceV1, } from "./managed-dispatch-adapter.js";
|
|
4
4
|
import { observeFlowDeskAgentTaskOutputV1 } from "./agent-task-output.js";
|
|
5
5
|
import { refreshFlowDeskCompletionUiCachesV1 } from "./completion-ui-cache.js";
|
|
@@ -9,6 +9,10 @@ const AGENT_TASK_CONTEXT_MAX_PROMPT_TEXT = 32_768;
|
|
|
9
9
|
const INVALID_PARENT_SESSION_REF = "ses-invalid-parent-session-binding";
|
|
10
10
|
/** Schema version for async child session tracking evidence */
|
|
11
11
|
export const AGENT_TASK_CHILD_SESSION_SCHEMA_VERSION = "flowdesk.agent_task_child_session.v1";
|
|
12
|
+
/** Stable-idle finalization thresholds for non-terminal captured text. */
|
|
13
|
+
const STABLE_IDLE_MIN_CYCLES = 3;
|
|
14
|
+
const STABLE_IDLE_MIN_MS = 12_000;
|
|
15
|
+
const STABLE_IDLE_MIN_LEN = 16;
|
|
12
16
|
export function sanitizeFlowDeskTaskResultTextV1(text) {
|
|
13
17
|
return {
|
|
14
18
|
text: text.length > TASK_RESULT_MAX_TEXT ? text.slice(0, TASK_RESULT_MAX_TEXT) : text,
|
|
@@ -77,7 +81,7 @@ async function extractAssistantTextFromResponse(client, childSessionId, opts) {
|
|
|
77
81
|
const messages = client.session.messages;
|
|
78
82
|
if (messages === undefined)
|
|
79
83
|
return undefined;
|
|
80
|
-
const quietPeriodMs = opts?.quietPeriodMs ??
|
|
84
|
+
const quietPeriodMs = opts?.quietPeriodMs ?? 10_000;
|
|
81
85
|
const maxNudges = opts?.maxNudges ?? 2;
|
|
82
86
|
const MESSAGES_TIMEOUT_MS = opts?.messagesTimeoutMs ?? 3_000; // per-call cap — handles both snapshot and long-poll
|
|
83
87
|
const method = messages;
|
|
@@ -139,6 +143,12 @@ async function extractAssistantTextFromResponse(client, childSessionId, opts) {
|
|
|
139
143
|
let lastHeartbeatMs = startMs;
|
|
140
144
|
let nudgeCount = 0;
|
|
141
145
|
let latestCandidate;
|
|
146
|
+
// Stable-idle tracking: capture non-terminal text once it has settled, so a
|
|
147
|
+
// good answer is not lost just because the SDK shape never surfaced an
|
|
148
|
+
// explicit terminal/finish marker.
|
|
149
|
+
let stableText;
|
|
150
|
+
let stableCount = 0;
|
|
151
|
+
let firstStableMs = 0;
|
|
142
152
|
try {
|
|
143
153
|
while (true) {
|
|
144
154
|
const response = await callMessages();
|
|
@@ -160,10 +170,36 @@ async function extractAssistantTextFromResponse(client, childSessionId, opts) {
|
|
|
160
170
|
lastHeartbeatMs = nowMs;
|
|
161
171
|
}
|
|
162
172
|
const observed = observe(response);
|
|
163
|
-
if (observed?.latestText !== undefined && observed.latestText.trim().length > 0)
|
|
173
|
+
if (observed?.latestText !== undefined && observed.latestText.trim().length > 0) {
|
|
164
174
|
latestCandidate = observed;
|
|
175
|
+
// Track text stability for idle finalization. Active tool runs reset
|
|
176
|
+
// stability so we never finalize mid tool-call.
|
|
177
|
+
if (observed.hasRunningTool) {
|
|
178
|
+
stableText = undefined;
|
|
179
|
+
stableCount = 0;
|
|
180
|
+
}
|
|
181
|
+
else if (observed.latestText === stableText) {
|
|
182
|
+
stableCount++;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
stableText = observed.latestText;
|
|
186
|
+
stableCount = 1;
|
|
187
|
+
firstStableMs = nowMs;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
165
190
|
if (observed?.terminalObserved === true && observed.latestText !== undefined && observed.latestText.trim().length > 0) {
|
|
166
|
-
return { text: observed.latestText, completionStatus: "final", outputKind: observed.outputKind, usableForSynthesis: observed.usableForSynthesis };
|
|
191
|
+
return { text: observed.latestText, completionStatus: "final", outputKind: observed.outputKind, usableForSynthesis: observed.usableForSynthesis, finalizationReason: "terminal_marker", looksLikeRefusalOrError: observed.looksLikeRefusalOrError };
|
|
192
|
+
}
|
|
193
|
+
// Stable-idle: non-terminal text that has been unchanged across several
|
|
194
|
+
// poll cycles and a minimum interval is treated as captured (not a
|
|
195
|
+
// semantic success claim — completion_status stays "final" but the
|
|
196
|
+
// finalization_reason records that this was idle-based capture).
|
|
197
|
+
if (latestCandidate?.latestText !== undefined &&
|
|
198
|
+
stableText !== undefined &&
|
|
199
|
+
stableText.trim().length >= STABLE_IDLE_MIN_LEN &&
|
|
200
|
+
stableCount >= STABLE_IDLE_MIN_CYCLES &&
|
|
201
|
+
nowMs - firstStableMs >= STABLE_IDLE_MIN_MS) {
|
|
202
|
+
return { text: latestCandidate.latestText, completionStatus: "final", outputKind: latestCandidate.outputKind, usableForSynthesis: latestCandidate.usableForSynthesis, finalizationReason: "stable_idle", looksLikeRefusalOrError: latestCandidate.looksLikeRefusalOrError };
|
|
167
203
|
}
|
|
168
204
|
const silenceMs = nowMs - lastActivityMs;
|
|
169
205
|
if (silenceMs >= quietPeriodMs) {
|
|
@@ -183,7 +219,7 @@ async function extractAssistantTextFromResponse(client, childSessionId, opts) {
|
|
|
183
219
|
else {
|
|
184
220
|
// Exhausted all nudges. Preserve usable candidate text as partial output.
|
|
185
221
|
if (latestCandidate?.latestText !== undefined && latestCandidate.latestText.trim().length > 0) {
|
|
186
|
-
return { text: latestCandidate.latestText, completionStatus: "partial", outputKind: latestCandidate.outputKind, usableForSynthesis: latestCandidate.usableForSynthesis };
|
|
222
|
+
return { text: latestCandidate.latestText, completionStatus: "partial", outputKind: latestCandidate.outputKind, usableForSynthesis: latestCandidate.usableForSynthesis, finalizationReason: "nudge_exhausted_partial", looksLikeRefusalOrError: latestCandidate.looksLikeRefusalOrError };
|
|
187
223
|
}
|
|
188
224
|
return undefined;
|
|
189
225
|
}
|
|
@@ -260,6 +296,7 @@ function writeAgentTaskProgress(input) {
|
|
|
260
296
|
}
|
|
261
297
|
function writeAgentTaskTerminalLifecycle(input) {
|
|
262
298
|
const childSessionRef = input.childSessionRef === input.parentSessionRef ? undefined : input.childSessionRef;
|
|
299
|
+
const messageRef = input.messageRef ?? (input.state === "complete" ? `msg-${input.laneId}` : undefined);
|
|
263
300
|
const record = {
|
|
264
301
|
schema_version: "flowdesk.lane_lifecycle_record.v1",
|
|
265
302
|
lane_id: input.laneId,
|
|
@@ -267,11 +304,13 @@ function writeAgentTaskTerminalLifecycle(input) {
|
|
|
267
304
|
attempt_id: input.attemptId,
|
|
268
305
|
parent_session_ref: input.parentSessionRef,
|
|
269
306
|
...(childSessionRef === undefined ? {} : { child_session_ref: childSessionRef }),
|
|
270
|
-
...(
|
|
307
|
+
...(messageRef === undefined ? {} : { message_ref: messageRef }),
|
|
271
308
|
agent_ref: input.agentRef,
|
|
272
309
|
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
273
310
|
state: input.state,
|
|
311
|
+
...(input.verdictRef === undefined ? {} : { verdict_ref: input.verdictRef }),
|
|
274
312
|
...(input.outputRef === undefined ? {} : { output_ref: input.outputRef }),
|
|
313
|
+
...(input.state === "complete" ? { runtime_echo_ref: `runtime-echo-${input.laneId}`, telemetry_ref: `telemetry-${input.laneId}` } : {}),
|
|
275
314
|
timeout_ms: input.timeoutMs ?? 0,
|
|
276
315
|
orphan_max_age_ms: 0,
|
|
277
316
|
retry_count: 0,
|
|
@@ -289,6 +328,72 @@ function writeAgentTaskTerminalLifecycle(input) {
|
|
|
289
328
|
record: record,
|
|
290
329
|
});
|
|
291
330
|
}
|
|
331
|
+
function extractJsonBlocksFromText(raw) {
|
|
332
|
+
const trimmed = raw.trim();
|
|
333
|
+
const results = [];
|
|
334
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}"))
|
|
335
|
+
return [trimmed];
|
|
336
|
+
const fencePattern = /```(?:json)?\s*\n?(\{[\s\S]*?\})\s*\n?```/g;
|
|
337
|
+
for (const match of trimmed.matchAll(fencePattern)) {
|
|
338
|
+
if (match[1])
|
|
339
|
+
results.push(match[1].trim());
|
|
340
|
+
}
|
|
341
|
+
if (results.length > 0)
|
|
342
|
+
return results;
|
|
343
|
+
let depth = 0;
|
|
344
|
+
let start = -1;
|
|
345
|
+
let lastBlock;
|
|
346
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
347
|
+
const ch = trimmed[i];
|
|
348
|
+
if (ch === "{") {
|
|
349
|
+
if (depth === 0)
|
|
350
|
+
start = i;
|
|
351
|
+
depth++;
|
|
352
|
+
}
|
|
353
|
+
else if (ch === "}") {
|
|
354
|
+
depth--;
|
|
355
|
+
if (depth === 0 && start !== -1) {
|
|
356
|
+
lastBlock = trimmed.slice(start, i + 1).trim();
|
|
357
|
+
start = -1;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return lastBlock === undefined ? [] : [lastBlock];
|
|
362
|
+
}
|
|
363
|
+
function observedTopTierReviewerVerdictFromText(input) {
|
|
364
|
+
for (const block of extractJsonBlocksFromText(input.text)) {
|
|
365
|
+
try {
|
|
366
|
+
const candidate = JSON.parse(block);
|
|
367
|
+
const validation = validateTopTierReviewVerdictV1(candidate);
|
|
368
|
+
if (!validation.ok)
|
|
369
|
+
continue;
|
|
370
|
+
const verdict = candidate;
|
|
371
|
+
if (verdict.workflow_id === input.workflowId)
|
|
372
|
+
return verdict;
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Keep scanning candidates.
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
function persistObservedReviewerVerdict(input) {
|
|
381
|
+
const evidenceId = input.verdict.verdict_id;
|
|
382
|
+
if (!writeSessionEvidence({
|
|
383
|
+
rootDir: input.rootDir,
|
|
384
|
+
workflowId: input.workflowId,
|
|
385
|
+
evidenceId,
|
|
386
|
+
record: input.verdict,
|
|
387
|
+
}))
|
|
388
|
+
return false;
|
|
389
|
+
const reloaded = reloadFlowDeskSessionEvidenceV1({
|
|
390
|
+
rootDir: input.rootDir,
|
|
391
|
+
workflowId: input.workflowId,
|
|
392
|
+
});
|
|
393
|
+
return reloaded.ok && reloaded.blocked.length === 0 && reloaded.entries.some((entry) => entry.evidenceClass === "reviewer_verdict" &&
|
|
394
|
+
entry.evidenceId === evidenceId &&
|
|
395
|
+
entry.record.verdict_id === input.verdict.verdict_id);
|
|
396
|
+
}
|
|
292
397
|
export async function executeFlowDeskAgentTaskV1(input) {
|
|
293
398
|
const observedAt = new Date().toISOString();
|
|
294
399
|
const token = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -376,9 +481,8 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
376
481
|
observedAt,
|
|
377
482
|
});
|
|
378
483
|
// Launch the lane — wrap in absolute timeout so session.prompt blocking doesn't hang forever.
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
const LAUNCH_TIMEOUT_MS = input._launchTimeoutMs ?? 60_000;
|
|
484
|
+
// 30s default — if session.prompt blocks for more than 30s with no activity, give up.
|
|
485
|
+
const LAUNCH_TIMEOUT_MS = input._launchTimeoutMs ?? 30_000;
|
|
382
486
|
const launchTimeoutHandle = setTimeout(() => { }, LAUNCH_TIMEOUT_MS);
|
|
383
487
|
const dispatchMethod = input.client.session.promptAsync !== undefined ? "promptAsync" : "prompt";
|
|
384
488
|
const launchResult = await Promise.race([
|
|
@@ -488,6 +592,11 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
488
592
|
updatedAt: new Date().toISOString(),
|
|
489
593
|
timeoutMs: input.timeoutMs,
|
|
490
594
|
});
|
|
595
|
+
refreshFlowDeskCompletionUiCachesV1({
|
|
596
|
+
rootDir: input.rootDir,
|
|
597
|
+
workflowId: input.workflowId,
|
|
598
|
+
observedAt,
|
|
599
|
+
});
|
|
491
600
|
return {
|
|
492
601
|
status: "task_failed",
|
|
493
602
|
failureCategory,
|
|
@@ -508,6 +617,11 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
508
617
|
observedAt,
|
|
509
618
|
progressSummaryLabel: `agent task lane launch heartbeat`,
|
|
510
619
|
});
|
|
620
|
+
refreshFlowDeskCompletionUiCachesV1({
|
|
621
|
+
rootDir: input.rootDir,
|
|
622
|
+
workflowId: input.workflowId,
|
|
623
|
+
observedAt,
|
|
624
|
+
});
|
|
511
625
|
// Extract child session ID
|
|
512
626
|
const childSessionId = launchResult.childSessionRef?.startsWith("ses-")
|
|
513
627
|
? launchResult.childSessionRef.slice("ses-".length)
|
|
@@ -546,6 +660,11 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
546
660
|
progressSeq: 2,
|
|
547
661
|
progressLabel: "agent task waiting for async child result",
|
|
548
662
|
});
|
|
663
|
+
refreshFlowDeskCompletionUiCachesV1({
|
|
664
|
+
rootDir: input.rootDir,
|
|
665
|
+
workflowId: input.workflowId,
|
|
666
|
+
observedAt: new Date().toISOString(),
|
|
667
|
+
});
|
|
549
668
|
return { status: "task_launched", laneId: input.laneId, childSessionId: resolvedChildId };
|
|
550
669
|
}
|
|
551
670
|
let resultObservation;
|
|
@@ -555,7 +674,7 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
555
674
|
const agentName = launchResult.status === "lane_launch_started" && typeof launchResult.agent === "string"
|
|
556
675
|
? launchResult.agent : undefined;
|
|
557
676
|
resultObservation = await extractAssistantTextFromResponse(input.client, childSessionId, {
|
|
558
|
-
quietPeriodMs: input._nudgeQuietPeriodMs ??
|
|
677
|
+
quietPeriodMs: input._nudgeQuietPeriodMs ?? 10_000, // default 10s per policy
|
|
559
678
|
maxNudges: 2,
|
|
560
679
|
runtimeModel,
|
|
561
680
|
agentName,
|
|
@@ -628,6 +747,37 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
628
747
|
updatedAt: new Date().toISOString(),
|
|
629
748
|
timeoutMs: input.timeoutMs,
|
|
630
749
|
});
|
|
750
|
+
refreshFlowDeskCompletionUiCachesV1({
|
|
751
|
+
rootDir: input.rootDir,
|
|
752
|
+
workflowId: input.workflowId,
|
|
753
|
+
observedAt: new Date().toISOString(),
|
|
754
|
+
});
|
|
755
|
+
// Auto-retry with fallback binding if configured and this is not already a retry
|
|
756
|
+
if (input.fallbackBinding !== undefined && !input._isFallbackRetry) {
|
|
757
|
+
const retryToken = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
758
|
+
const retryTaskId = `${input.taskId}-retry-${retryToken.slice(0, 6)}`;
|
|
759
|
+
const retryLaneId = `${input.laneId}-retry`;
|
|
760
|
+
writeAgentTaskProgress({
|
|
761
|
+
rootDir: input.rootDir,
|
|
762
|
+
workflowId: input.workflowId,
|
|
763
|
+
laneId: retryLaneId,
|
|
764
|
+
taskId: retryTaskId,
|
|
765
|
+
agentRef: input.fallbackBinding.agentRef,
|
|
766
|
+
providerQualifiedModelId: input.fallbackBinding.providerQualifiedModelId,
|
|
767
|
+
phase: "retrying",
|
|
768
|
+
progressSeq: 0,
|
|
769
|
+
progressLabel: `auto-retry with ${input.fallbackBinding.providerQualifiedModelId} after ${failureCategory}`,
|
|
770
|
+
});
|
|
771
|
+
return executeFlowDeskAgentTaskV1({
|
|
772
|
+
...input,
|
|
773
|
+
taskId: retryTaskId,
|
|
774
|
+
laneId: retryLaneId,
|
|
775
|
+
agentRef: input.fallbackBinding.agentRef,
|
|
776
|
+
providerQualifiedModelId: input.fallbackBinding.providerQualifiedModelId,
|
|
777
|
+
fallbackBinding: undefined,
|
|
778
|
+
_isFallbackRetry: true,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
631
781
|
return {
|
|
632
782
|
status: "task_failed",
|
|
633
783
|
failureCategory,
|
|
@@ -656,9 +806,17 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
656
806
|
completion_status: resultObservation?.completionStatus ?? "final",
|
|
657
807
|
output_kind: resultObservation?.outputKind ?? "final_answer",
|
|
658
808
|
usable_for_synthesis: resultObservation?.usableForSynthesis ?? true,
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
809
|
+
// Capture/judgement separation: text was captured, so this is NOT a
|
|
810
|
+
// contract failure. output_kind/completion_status/looks_like_refusal_or_error
|
|
811
|
+
// are advisory inputs for the coordinator's substance judgement, never a
|
|
812
|
+
// capture-side drop. missing_contract is only ever true when an explicit
|
|
813
|
+
// contract was requested AND no text was captured (that path returns
|
|
814
|
+
// task_failed above, so here it is always false).
|
|
815
|
+
missing_contract: false,
|
|
816
|
+
...(resultObservation?.finalizationReason === undefined
|
|
817
|
+
? {}
|
|
818
|
+
: { finalization_reason: resultObservation.finalizationReason }),
|
|
819
|
+
looks_like_refusal_or_error: resultObservation?.looksLikeRefusalOrError ?? false,
|
|
662
820
|
created_at: observedAt,
|
|
663
821
|
dispatch_authority_enabled: false,
|
|
664
822
|
};
|
|
@@ -669,13 +827,73 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
669
827
|
record: taskResultRecord,
|
|
670
828
|
});
|
|
671
829
|
if (!taskResultWritten) {
|
|
830
|
+
const taskFailedEvidenceId = `task-failed-${input.taskId}-${token}-result-write`;
|
|
831
|
+
const redactedReason = "task_result evidence persistence failed";
|
|
832
|
+
writeSessionEvidence({
|
|
833
|
+
rootDir: input.rootDir,
|
|
834
|
+
workflowId: input.workflowId,
|
|
835
|
+
evidenceId: taskFailedEvidenceId,
|
|
836
|
+
record: {
|
|
837
|
+
schema_version: "flowdesk.task_failed.v1",
|
|
838
|
+
workflow_id: input.workflowId,
|
|
839
|
+
lane_id: input.laneId,
|
|
840
|
+
task_id: input.taskId,
|
|
841
|
+
agent_ref: input.agentRef,
|
|
842
|
+
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
843
|
+
failure_category: "unknown",
|
|
844
|
+
redacted_reason: redactedReason,
|
|
845
|
+
created_at: observedAt,
|
|
846
|
+
dispatch_authority_enabled: false,
|
|
847
|
+
},
|
|
848
|
+
});
|
|
849
|
+
writeAgentTaskProgress({
|
|
850
|
+
rootDir: input.rootDir,
|
|
851
|
+
workflowId: input.workflowId,
|
|
852
|
+
laneId: input.laneId,
|
|
853
|
+
taskId: input.taskId,
|
|
854
|
+
agentRef: input.agentRef,
|
|
855
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
856
|
+
phase: "failed",
|
|
857
|
+
progressSeq: 4,
|
|
858
|
+
progressLabel: "agent task result persistence failed",
|
|
859
|
+
});
|
|
860
|
+
writeAgentTaskTerminalLifecycle({
|
|
861
|
+
rootDir: input.rootDir,
|
|
862
|
+
workflowId: input.workflowId,
|
|
863
|
+
laneId: input.laneId,
|
|
864
|
+
attemptId,
|
|
865
|
+
parentSessionRef,
|
|
866
|
+
agentRef: input.agentRef,
|
|
867
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
868
|
+
state: "invocation_failed",
|
|
869
|
+
evidenceId: `lifecycle-task-terminal-${input.laneId}-${token}-result-write`,
|
|
870
|
+
createdAt: observedAt,
|
|
871
|
+
updatedAt: new Date().toISOString(),
|
|
872
|
+
timeoutMs: input.timeoutMs,
|
|
873
|
+
});
|
|
874
|
+
refreshFlowDeskCompletionUiCachesV1({
|
|
875
|
+
rootDir: input.rootDir,
|
|
876
|
+
workflowId: input.workflowId,
|
|
877
|
+
observedAt,
|
|
878
|
+
});
|
|
672
879
|
return {
|
|
673
880
|
status: "task_failed",
|
|
674
881
|
failureCategory: "unknown",
|
|
675
|
-
redactedReason
|
|
882
|
+
redactedReason,
|
|
676
883
|
laneId: input.laneId,
|
|
677
884
|
};
|
|
678
885
|
}
|
|
886
|
+
const observedReviewerVerdict = observedTopTierReviewerVerdictFromText({
|
|
887
|
+
text: fullResultText,
|
|
888
|
+
workflowId: input.workflowId,
|
|
889
|
+
});
|
|
890
|
+
const reviewerVerdictPersisted = observedReviewerVerdict === undefined
|
|
891
|
+
? false
|
|
892
|
+
: persistObservedReviewerVerdict({
|
|
893
|
+
rootDir: input.rootDir,
|
|
894
|
+
workflowId: input.workflowId,
|
|
895
|
+
verdict: observedReviewerVerdict,
|
|
896
|
+
});
|
|
679
897
|
writeAgentTaskProgress({
|
|
680
898
|
rootDir: input.rootDir,
|
|
681
899
|
workflowId: input.workflowId,
|
|
@@ -685,7 +903,9 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
685
903
|
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
686
904
|
phase: "finalizing",
|
|
687
905
|
progressSeq: 3,
|
|
688
|
-
progressLabel:
|
|
906
|
+
progressLabel: reviewerVerdictPersisted
|
|
907
|
+
? "agent task result captured with reviewer verdict evidence"
|
|
908
|
+
: "agent task result captured",
|
|
689
909
|
});
|
|
690
910
|
writeAgentTaskTerminalLifecycle({
|
|
691
911
|
rootDir: input.rootDir,
|
|
@@ -697,7 +917,8 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
697
917
|
messageRef: launchResult.messageRef?.startsWith("msg-") ? launchResult.messageRef : undefined,
|
|
698
918
|
agentRef: input.agentRef,
|
|
699
919
|
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
700
|
-
state: "incomplete",
|
|
920
|
+
state: reviewerVerdictPersisted ? "complete" : "incomplete",
|
|
921
|
+
verdictRef: reviewerVerdictPersisted ? observedReviewerVerdict?.verdict_id : undefined,
|
|
701
922
|
outputRef: `output-${taskResultEvidenceId}`,
|
|
702
923
|
evidenceId: `lifecycle-task-terminal-${input.laneId}-${token}`,
|
|
703
924
|
createdAt: observedAt,
|