@flowdesk/opencode-plugin 0.1.12 → 0.1.14
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 +17 -0
- package/dist/agent-task-output.d.ts.map +1 -0
- package/dist/agent-task-output.js +119 -0
- package/dist/agent-task-output.js.map +1 -0
- package/dist/agent-task-runner.d.ts +24 -0
- package/dist/agent-task-runner.d.ts.map +1 -1
- package/dist/agent-task-runner.js +536 -61
- package/dist/agent-task-runner.js.map +1 -1
- package/dist/auto-continue-preview-tool.d.ts +36 -0
- package/dist/auto-continue-preview-tool.d.ts.map +1 -0
- package/dist/auto-continue-preview-tool.js +119 -0
- package/dist/auto-continue-preview-tool.js.map +1 -0
- package/dist/completion-ui-cache.d.ts +6 -0
- package/dist/completion-ui-cache.d.ts.map +1 -0
- package/dist/completion-ui-cache.js +260 -0
- package/dist/completion-ui-cache.js.map +1 -0
- package/dist/controlled-write-tool.d.ts +49 -0
- package/dist/controlled-write-tool.d.ts.map +1 -0
- package/dist/controlled-write-tool.js +296 -0
- package/dist/controlled-write-tool.js.map +1 -0
- package/dist/event-hook-observer.d.ts +14 -0
- package/dist/event-hook-observer.d.ts.map +1 -0
- package/dist/event-hook-observer.js +193 -0
- package/dist/event-hook-observer.js.map +1 -0
- package/dist/managed-dispatch-adapter.d.ts +1 -0
- package/dist/managed-dispatch-adapter.d.ts.map +1 -1
- package/dist/managed-dispatch-adapter.js +100 -29
- package/dist/managed-dispatch-adapter.js.map +1 -1
- package/dist/model-selection-engine.d.ts +47 -0
- package/dist/model-selection-engine.d.ts.map +1 -0
- package/dist/model-selection-engine.js +175 -0
- package/dist/model-selection-engine.js.map +1 -0
- package/dist/provider-usage-live-tool.d.ts +27 -0
- package/dist/provider-usage-live-tool.d.ts.map +1 -1
- package/dist/provider-usage-live-tool.js +443 -4
- package/dist/provider-usage-live-tool.js.map +1 -1
- package/dist/quick-reviewer-run.d.ts +3 -0
- package/dist/quick-reviewer-run.d.ts.map +1 -1
- package/dist/quick-reviewer-run.js +20 -8
- package/dist/quick-reviewer-run.js.map +1 -1
- package/dist/runtime-reviewer-execution-bridge.d.ts +21 -0
- package/dist/runtime-reviewer-execution-bridge.d.ts.map +1 -1
- package/dist/runtime-reviewer-execution-bridge.js +238 -0
- package/dist/runtime-reviewer-execution-bridge.js.map +1 -1
- package/dist/server.d.ts +60 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +800 -41
- package/dist/server.js.map +1 -1
- package/dist/stall-recovery.d.ts +60 -0
- package/dist/stall-recovery.d.ts.map +1 -1
- package/dist/stall-recovery.js +763 -11
- package/dist/stall-recovery.js.map +1 -1
- package/dist/status-live-tool.d.ts +81 -0
- package/dist/status-live-tool.d.ts.map +1 -1
- package/dist/status-live-tool.js +620 -38
- package/dist/status-live-tool.js.map +1 -1
- package/dist/tui-subtask-activity.d.ts +69 -0
- package/dist/tui-subtask-activity.d.ts.map +1 -0
- package/dist/tui-subtask-activity.js +266 -0
- package/dist/tui-subtask-activity.js.map +1 -0
- package/dist/tui-usage-snapshot.d.ts +44 -0
- package/dist/tui-usage-snapshot.d.ts.map +1 -0
- package/dist/tui-usage-snapshot.js +397 -0
- package/dist/tui-usage-snapshot.js.map +1 -0
- package/dist/tui.d.ts +7 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +134 -0
- package/dist/tui.js.map +1 -0
- package/dist/workflow-assign-tool.d.ts +23 -0
- package/dist/workflow-assign-tool.d.ts.map +1 -0
- package/dist/workflow-assign-tool.js +117 -0
- package/dist/workflow-assign-tool.js.map +1 -0
- package/dist/workflow-author-tool.d.ts +29 -0
- package/dist/workflow-author-tool.d.ts.map +1 -0
- package/dist/workflow-author-tool.js +227 -0
- package/dist/workflow-author-tool.js.map +1 -0
- package/dist/workflow-dispatch-plan-tool.d.ts +47 -0
- package/dist/workflow-dispatch-plan-tool.d.ts.map +1 -0
- package/dist/workflow-dispatch-plan-tool.js +251 -0
- package/dist/workflow-dispatch-plan-tool.js.map +1 -0
- package/dist/workflow-dispatch-tool.d.ts +56 -0
- package/dist/workflow-dispatch-tool.d.ts.map +1 -0
- package/dist/workflow-dispatch-tool.js +306 -0
- package/dist/workflow-dispatch-tool.js.map +1 -0
- package/dist/workflow-orchestrator.d.ts +31 -0
- package/dist/workflow-orchestrator.d.ts.map +1 -0
- package/dist/workflow-orchestrator.js +160 -0
- package/dist/workflow-orchestrator.js.map +1 -0
- package/dist/workflow-scheduler.d.ts +19 -0
- package/dist/workflow-scheduler.d.ts.map +1 -0
- package/dist/workflow-scheduler.js +45 -0
- package/dist/workflow-scheduler.js.map +1 -0
- package/dist/workflow-synthesis-tool.d.ts +31 -0
- package/dist/workflow-synthesis-tool.d.ts.map +1 -0
- package/dist/workflow-synthesis-tool.js +194 -0
- package/dist/workflow-synthesis-tool.js.map +1 -0
- package/package.json +10 -2
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { applyFlowDeskSessionEvidenceWriteIntentsV1, prepareFlowDeskSessionEvidenceWriteIntentV1, } from "@flowdesk/core";
|
|
3
3
|
import { launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1, materializeFlowDeskRuntimeLaneLaunchLifecycleEvidenceV1, } from "./managed-dispatch-adapter.js";
|
|
4
|
+
import { observeFlowDeskAgentTaskOutputV1 } from "./agent-task-output.js";
|
|
5
|
+
import { refreshFlowDeskCompletionUiCachesV1 } from "./completion-ui-cache.js";
|
|
4
6
|
import { recordFlowDeskLaneHeartbeatV1 } from "./lane-heartbeat-writer.js";
|
|
5
7
|
const TASK_RESULT_MAX_TEXT = 32_768;
|
|
8
|
+
const AGENT_TASK_CONTEXT_MAX_PROMPT_TEXT = 32_768;
|
|
9
|
+
const INVALID_PARENT_SESSION_REF = "ses-invalid-parent-session-binding";
|
|
10
|
+
/** Schema version for async child session tracking evidence */
|
|
11
|
+
export const AGENT_TASK_CHILD_SESSION_SCHEMA_VERSION = "flowdesk.agent_task_child_session.v1";
|
|
12
|
+
export function sanitizeFlowDeskTaskResultTextV1(text) {
|
|
13
|
+
return {
|
|
14
|
+
text: text.length > TASK_RESULT_MAX_TEXT ? text.slice(0, TASK_RESULT_MAX_TEXT) : text,
|
|
15
|
+
changed: false,
|
|
16
|
+
truncated: text.length > TASK_RESULT_MAX_TEXT,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
6
19
|
function agentTaskLaunchPlan(input) {
|
|
7
20
|
return {
|
|
8
21
|
schema_version: "flowdesk.runtime_lane_launch_plan.v1",
|
|
@@ -31,38 +44,162 @@ function agentTaskLaunchPlan(input) {
|
|
|
31
44
|
runtimeExecution: false,
|
|
32
45
|
};
|
|
33
46
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
47
|
+
function validateAgentTaskParentSessionId(parentSessionId) {
|
|
48
|
+
const value = parentSessionId.trim();
|
|
49
|
+
if (value.length === 0)
|
|
50
|
+
return { ok: false, redactedReason: "missing_parent_session_binding", parentSessionRef: INVALID_PARENT_SESSION_REF };
|
|
51
|
+
if (value.length > 128)
|
|
52
|
+
return { ok: false, redactedReason: "invalid_parent_session_binding", parentSessionRef: INVALID_PARENT_SESSION_REF };
|
|
53
|
+
// `ses-...` is FlowDesk's opaque session-ref wrapper, not the raw OpenCode
|
|
54
|
+
// session id expected by SDK `session.create({ parentID })`. Accepting it here
|
|
55
|
+
// causes evidence such as `ses-ses-flowdesk-coordinator` and can make the SDK
|
|
56
|
+
// wait on a non-existent synthetic parent session until launch timeout.
|
|
57
|
+
if (value.startsWith("ses-"))
|
|
58
|
+
return { ok: false, redactedReason: "invalid_parent_session_binding", parentSessionRef: INVALID_PARENT_SESSION_REF };
|
|
59
|
+
if (/\s/.test(value))
|
|
60
|
+
return { ok: false, redactedReason: "invalid_parent_session_binding", parentSessionRef: INVALID_PARENT_SESSION_REF };
|
|
61
|
+
if (!/^[A-Za-z0-9_.:-]+$/.test(value))
|
|
62
|
+
return { ok: false, redactedReason: "invalid_parent_session_binding", parentSessionRef: INVALID_PARENT_SESSION_REF };
|
|
63
|
+
return { ok: true, parentSessionRef: `ses-${value}` };
|
|
64
|
+
}
|
|
65
|
+
/** Bounded nudge text — versioned constant, never echoes user input */
|
|
66
|
+
const AGENT_TASK_NUDGE_TEXT = "Please provide your final answer now. If you have completed your analysis, output your complete response.";
|
|
67
|
+
/**
|
|
68
|
+
* Polls `session.messages` with a per-call 3-second cap so it works whether the SDK
|
|
69
|
+
* uses snapshot (returns immediately) or long-poll (blocks until output) semantics.
|
|
70
|
+
*
|
|
71
|
+
* Heartbeat: fires every `quietPeriodMs` of silence — only when inactive.
|
|
72
|
+
* Nudge: after `quietPeriodMs` of silence, sends a bounded prompt to the child
|
|
73
|
+
* session asking for the final answer. Max `maxNudges` nudges total.
|
|
74
|
+
* After exhausting nudges with no response, returns undefined.
|
|
75
|
+
*/
|
|
76
|
+
async function extractAssistantTextFromResponse(client, childSessionId, opts) {
|
|
36
77
|
const messages = client.session.messages;
|
|
37
78
|
if (messages === undefined)
|
|
38
79
|
return undefined;
|
|
39
|
-
|
|
80
|
+
const quietPeriodMs = opts?.quietPeriodMs ?? 30_000;
|
|
81
|
+
const maxNudges = opts?.maxNudges ?? 2;
|
|
82
|
+
const MESSAGES_TIMEOUT_MS = opts?.messagesTimeoutMs ?? 3_000; // per-call cap — handles both snapshot and long-poll
|
|
83
|
+
const method = messages;
|
|
84
|
+
/**
|
|
85
|
+
* Call session.messages with a ceiling timeout so we can check inactivity periodically.
|
|
86
|
+
* This handles both snapshot APIs (return immediately) and long-poll APIs
|
|
87
|
+
* (block until LLM produces output). With the timeout, a long-poll call that
|
|
88
|
+
* hasn't returned after MESSAGES_TIMEOUT_MS resolves as null so we can
|
|
89
|
+
* check the inactivity clock and possibly send a nudge.
|
|
90
|
+
*/
|
|
91
|
+
const callMessages = () => {
|
|
92
|
+
const messagePromise = (async () => {
|
|
93
|
+
const current = await method.call(client.session, { sessionID: childSessionId });
|
|
94
|
+
if (isSdkErrorResponse(current))
|
|
95
|
+
return method.call(client.session, { path: { id: childSessionId } });
|
|
96
|
+
return current;
|
|
97
|
+
})();
|
|
98
|
+
// Only race against timeout when the API might block (MESSAGES_TIMEOUT_MS > 0)
|
|
99
|
+
if (MESSAGES_TIMEOUT_MS <= 0)
|
|
100
|
+
return messagePromise;
|
|
101
|
+
return Promise.race([
|
|
102
|
+
messagePromise,
|
|
103
|
+
new Promise(resolve => setTimeout(() => resolve(null), MESSAGES_TIMEOUT_MS)),
|
|
104
|
+
]);
|
|
105
|
+
};
|
|
106
|
+
/** Send a nudge to the child session with a hard timeout to prevent blocking.
|
|
107
|
+
* Uses noReply: true so the child does not generate a spurious second assistant turn.
|
|
108
|
+
*/
|
|
109
|
+
const sendNudge = async () => {
|
|
110
|
+
const promptFn = client.session.prompt ?? client.session.promptAsync;
|
|
111
|
+
if (promptFn === undefined)
|
|
112
|
+
return "skipped";
|
|
113
|
+
const NUDGE_TIMEOUT_MS = 5_000;
|
|
40
114
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? partRecord.text
|
|
53
|
-
: typeof partRecord?.content === "string"
|
|
54
|
-
? partRecord.content
|
|
55
|
-
: undefined;
|
|
56
|
-
if (typeof text === "string" && text.trim().length > 0)
|
|
57
|
-
return text;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return undefined;
|
|
115
|
+
await Promise.race([
|
|
116
|
+
promptFn.call(client.session, {
|
|
117
|
+
sessionID: childSessionId,
|
|
118
|
+
noReply: true,
|
|
119
|
+
...(opts?.runtimeModel !== undefined ? { model: opts.runtimeModel } : {}),
|
|
120
|
+
...(opts?.agentName !== undefined ? { agent: opts.agentName } : {}),
|
|
121
|
+
parts: [{ type: "text", text: AGENT_TASK_NUDGE_TEXT }],
|
|
122
|
+
}),
|
|
123
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("nudge timeout")), NUDGE_TIMEOUT_MS)),
|
|
124
|
+
]);
|
|
125
|
+
return "sent";
|
|
61
126
|
}
|
|
62
127
|
catch {
|
|
63
|
-
return
|
|
128
|
+
return "timeout";
|
|
64
129
|
}
|
|
65
|
-
}
|
|
130
|
+
};
|
|
131
|
+
const observe = (response) => {
|
|
132
|
+
if (response === null)
|
|
133
|
+
return undefined; // timed-out poll cycle
|
|
134
|
+
return observeFlowDeskAgentTaskOutputV1(response);
|
|
135
|
+
};
|
|
136
|
+
const startMs = Date.now();
|
|
137
|
+
let lastActivityMs = startMs;
|
|
138
|
+
let lastSignature = "";
|
|
139
|
+
let lastHeartbeatMs = startMs;
|
|
140
|
+
let nudgeCount = 0;
|
|
141
|
+
let latestCandidate;
|
|
142
|
+
try {
|
|
143
|
+
while (true) {
|
|
144
|
+
const response = await callMessages();
|
|
145
|
+
const nowMs = Date.now();
|
|
146
|
+
// Build signature (null response = timeout, no change)
|
|
147
|
+
const sig = response === null ? lastSignature : (() => {
|
|
148
|
+
const data = asResponseData(response);
|
|
149
|
+
const record = asRecord(data);
|
|
150
|
+
const items = Array.isArray(data) ? data
|
|
151
|
+
: Array.isArray(record?.items) ? record.items
|
|
152
|
+
: Array.isArray(record?.messages) ? record.messages : [];
|
|
153
|
+
const observed = observe(response);
|
|
154
|
+
return `${items.length}:${observed?.latestText?.length ?? 0}:${observed?.terminalObserved === true ? "terminal" : "open"}`;
|
|
155
|
+
})();
|
|
156
|
+
if (sig !== lastSignature) {
|
|
157
|
+
// New activity — reset all inactivity clocks
|
|
158
|
+
lastSignature = sig;
|
|
159
|
+
lastActivityMs = nowMs;
|
|
160
|
+
lastHeartbeatMs = nowMs;
|
|
161
|
+
}
|
|
162
|
+
const observed = observe(response);
|
|
163
|
+
if (observed?.latestText !== undefined && observed.latestText.trim().length > 0)
|
|
164
|
+
latestCandidate = observed;
|
|
165
|
+
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 };
|
|
167
|
+
}
|
|
168
|
+
const silenceMs = nowMs - lastActivityMs;
|
|
169
|
+
if (silenceMs >= quietPeriodMs) {
|
|
170
|
+
// Emit heartbeat on first quiet-period expiry of each silence window
|
|
171
|
+
if (nowMs - lastHeartbeatMs >= quietPeriodMs) {
|
|
172
|
+
lastHeartbeatMs = nowMs;
|
|
173
|
+
opts?.heartbeatFn?.(nowMs - startMs);
|
|
174
|
+
}
|
|
175
|
+
// Send nudge after quiet period
|
|
176
|
+
if (nudgeCount < maxNudges) {
|
|
177
|
+
nudgeCount++;
|
|
178
|
+
await sendNudge();
|
|
179
|
+
// Reset activity clock after nudge — give a fresh quiet window
|
|
180
|
+
lastActivityMs = Date.now();
|
|
181
|
+
lastHeartbeatMs = lastActivityMs;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Exhausted all nudges. Preserve usable candidate text as partial output.
|
|
185
|
+
if (latestCandidate?.latestText !== undefined && latestCandidate.latestText.trim().length > 0) {
|
|
186
|
+
return { text: latestCandidate.latestText, completionStatus: "partial", outputKind: latestCandidate.outputKind, usableForSynthesis: latestCandidate.usableForSynthesis };
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// No activity and not yet at quiet period — yield to event loop before next poll.
|
|
193
|
+
// Sleep for up to 1s or quietPeriodMs/10, whichever is smaller, to avoid tight loops
|
|
194
|
+
// while still being responsive when messages arrive quickly (snapshot mode).
|
|
195
|
+
const yieldMs = Math.max(10, Math.min(1_000, Math.floor(quietPeriodMs / 10)));
|
|
196
|
+
await new Promise(resolve => setTimeout(resolve, yieldMs));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
66
203
|
}
|
|
67
204
|
function asRecord(value) {
|
|
68
205
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
@@ -73,12 +210,126 @@ function asResponseData(value) {
|
|
|
73
210
|
const record = asRecord(value);
|
|
74
211
|
return record !== undefined && "data" in record ? record.data : value;
|
|
75
212
|
}
|
|
213
|
+
function isSdkErrorResponse(value) {
|
|
214
|
+
const record = asRecord(value);
|
|
215
|
+
const data = asRecord(asResponseData(value));
|
|
216
|
+
return record?.error !== undefined || data?.error !== undefined;
|
|
217
|
+
}
|
|
76
218
|
function sha256Hex(text) {
|
|
77
219
|
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
78
220
|
}
|
|
221
|
+
function writeSessionEvidence(input) {
|
|
222
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
223
|
+
workflowId: input.workflowId,
|
|
224
|
+
evidenceId: input.evidenceId,
|
|
225
|
+
record: input.record,
|
|
226
|
+
});
|
|
227
|
+
if (prepared.ok && prepared.writeIntent !== undefined) {
|
|
228
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [prepared.writeIntent]);
|
|
229
|
+
return applied.ok && applied.writtenPaths.length > 0;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
function progressLabel(value) {
|
|
234
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
235
|
+
return compact.length > 120 ? `${compact.slice(0, 119)}…` : compact;
|
|
236
|
+
}
|
|
237
|
+
function writeAgentTaskProgress(input) {
|
|
238
|
+
const observedAt = input.observedAt ?? new Date().toISOString();
|
|
239
|
+
const record = {
|
|
240
|
+
schema_version: "flowdesk.agent_task_progress.v1",
|
|
241
|
+
workflow_id: input.workflowId,
|
|
242
|
+
lane_id: input.laneId,
|
|
243
|
+
task_id: input.taskId,
|
|
244
|
+
agent_ref: input.agentRef,
|
|
245
|
+
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
246
|
+
progress_seq: input.progressSeq,
|
|
247
|
+
observed_at: observedAt,
|
|
248
|
+
phase: input.phase,
|
|
249
|
+
progress_label: progressLabel(input.progressLabel),
|
|
250
|
+
progress_ref: `progress-${input.laneId}-${input.progressSeq}`,
|
|
251
|
+
redaction_version: "v1",
|
|
252
|
+
dispatch_authority_enabled: false,
|
|
253
|
+
};
|
|
254
|
+
writeSessionEvidence({
|
|
255
|
+
rootDir: input.rootDir,
|
|
256
|
+
workflowId: input.workflowId,
|
|
257
|
+
evidenceId: `agent-task-progress-${input.laneId}-${input.progressSeq}`,
|
|
258
|
+
record: record,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function writeAgentTaskTerminalLifecycle(input) {
|
|
262
|
+
const childSessionRef = input.childSessionRef === input.parentSessionRef ? undefined : input.childSessionRef;
|
|
263
|
+
const record = {
|
|
264
|
+
schema_version: "flowdesk.lane_lifecycle_record.v1",
|
|
265
|
+
lane_id: input.laneId,
|
|
266
|
+
workflow_id: input.workflowId,
|
|
267
|
+
attempt_id: input.attemptId,
|
|
268
|
+
parent_session_ref: input.parentSessionRef,
|
|
269
|
+
...(childSessionRef === undefined ? {} : { child_session_ref: childSessionRef }),
|
|
270
|
+
...(input.messageRef === undefined ? {} : { message_ref: input.messageRef }),
|
|
271
|
+
agent_ref: input.agentRef,
|
|
272
|
+
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
273
|
+
state: input.state,
|
|
274
|
+
...(input.outputRef === undefined ? {} : { output_ref: input.outputRef }),
|
|
275
|
+
timeout_ms: input.timeoutMs ?? 0,
|
|
276
|
+
orphan_max_age_ms: 0,
|
|
277
|
+
retry_count: 0,
|
|
278
|
+
created_at: input.createdAt,
|
|
279
|
+
updated_at: input.updatedAt,
|
|
280
|
+
dispatch_authority_enabled: false,
|
|
281
|
+
providerCall: false,
|
|
282
|
+
actualLaneLaunch: false,
|
|
283
|
+
runtimeExecution: false,
|
|
284
|
+
};
|
|
285
|
+
writeSessionEvidence({
|
|
286
|
+
rootDir: input.rootDir,
|
|
287
|
+
workflowId: input.workflowId,
|
|
288
|
+
evidenceId: input.evidenceId,
|
|
289
|
+
record: record,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
79
292
|
export async function executeFlowDeskAgentTaskV1(input) {
|
|
80
293
|
const observedAt = new Date().toISOString();
|
|
81
294
|
const token = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
295
|
+
const parentBinding = validateAgentTaskParentSessionId(input.parentSessionId);
|
|
296
|
+
const parentSessionRef = parentBinding.parentSessionRef;
|
|
297
|
+
const attemptId = `attempt-task-${token}`;
|
|
298
|
+
if (!parentBinding.ok) {
|
|
299
|
+
const taskFailedEvidenceId = `task-failed-${input.taskId}-${token}-invalid-parent`;
|
|
300
|
+
const redactedReason = parentBinding.redactedReason;
|
|
301
|
+
writeSessionEvidence({
|
|
302
|
+
rootDir: input.rootDir,
|
|
303
|
+
workflowId: input.workflowId,
|
|
304
|
+
evidenceId: taskFailedEvidenceId,
|
|
305
|
+
record: {
|
|
306
|
+
schema_version: "flowdesk.task_failed.v1",
|
|
307
|
+
workflow_id: input.workflowId,
|
|
308
|
+
lane_id: input.laneId,
|
|
309
|
+
task_id: input.taskId,
|
|
310
|
+
agent_ref: input.agentRef,
|
|
311
|
+
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
312
|
+
failure_category: "sdk_create_failed",
|
|
313
|
+
redacted_reason: redactedReason,
|
|
314
|
+
created_at: observedAt,
|
|
315
|
+
dispatch_authority_enabled: false,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
writeAgentTaskTerminalLifecycle({
|
|
319
|
+
rootDir: input.rootDir,
|
|
320
|
+
workflowId: input.workflowId,
|
|
321
|
+
laneId: input.laneId,
|
|
322
|
+
attemptId,
|
|
323
|
+
parentSessionRef,
|
|
324
|
+
agentRef: input.agentRef,
|
|
325
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
326
|
+
state: "invocation_failed",
|
|
327
|
+
evidenceId: `lifecycle-task-terminal-${input.laneId}-${token}-invalid-parent`,
|
|
328
|
+
createdAt: observedAt,
|
|
329
|
+
updatedAt: observedAt,
|
|
330
|
+
});
|
|
331
|
+
return { status: "task_failed", failureCategory: "sdk_create_failed", redactedReason, laneId: input.laneId };
|
|
332
|
+
}
|
|
82
333
|
const launchPlan = agentTaskLaunchPlan({
|
|
83
334
|
workflowId: input.workflowId,
|
|
84
335
|
laneId: input.laneId,
|
|
@@ -88,17 +339,97 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
88
339
|
token,
|
|
89
340
|
});
|
|
90
341
|
const runningLifecycleEvidenceId = `lifecycle-task-running-${input.laneId}-${token}`;
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
342
|
+
const promptTextTruncated = input.promptText.length > AGENT_TASK_CONTEXT_MAX_PROMPT_TEXT;
|
|
343
|
+
const agentTaskContextRecord = {
|
|
344
|
+
schema_version: "flowdesk.agent_task_context.v1",
|
|
345
|
+
workflow_id: input.workflowId,
|
|
346
|
+
lane_id: input.laneId,
|
|
347
|
+
task_id: input.taskId,
|
|
348
|
+
agent_ref: input.agentRef,
|
|
349
|
+
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
350
|
+
parent_session_ref: parentSessionRef,
|
|
351
|
+
prompt_text: promptTextTruncated
|
|
352
|
+
? input.promptText.slice(0, AGENT_TASK_CONTEXT_MAX_PROMPT_TEXT)
|
|
353
|
+
: input.promptText,
|
|
354
|
+
prompt_text_truncated: promptTextTruncated,
|
|
355
|
+
prompt_text_sha256: sha256Hex(input.promptText),
|
|
356
|
+
redaction_version: "v1",
|
|
357
|
+
created_at: observedAt,
|
|
358
|
+
dispatch_authority_enabled: false,
|
|
359
|
+
};
|
|
360
|
+
writeSessionEvidence({
|
|
361
|
+
rootDir: input.rootDir,
|
|
362
|
+
workflowId: input.workflowId,
|
|
363
|
+
evidenceId: `agent-task-context-${input.taskId}-${token}`,
|
|
364
|
+
record: agentTaskContextRecord,
|
|
101
365
|
});
|
|
366
|
+
writeAgentTaskProgress({
|
|
367
|
+
rootDir: input.rootDir,
|
|
368
|
+
workflowId: input.workflowId,
|
|
369
|
+
laneId: input.laneId,
|
|
370
|
+
taskId: input.taskId,
|
|
371
|
+
agentRef: input.agentRef,
|
|
372
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
373
|
+
phase: "started",
|
|
374
|
+
progressSeq: 1,
|
|
375
|
+
progressLabel: "agent task lane launch started",
|
|
376
|
+
observedAt,
|
|
377
|
+
});
|
|
378
|
+
// Launch the lane — wrap in absolute timeout so session.prompt blocking doesn't hang forever.
|
|
379
|
+
// The launch phase timeout is longer (5 min) since promptAsync may queue work before responding.
|
|
380
|
+
// 1 min default — if session.prompt blocks for more than 1 min with no activity, give up
|
|
381
|
+
const LAUNCH_TIMEOUT_MS = input._launchTimeoutMs ?? 60_000;
|
|
382
|
+
const launchTimeoutHandle = setTimeout(() => { }, LAUNCH_TIMEOUT_MS);
|
|
383
|
+
const dispatchMethod = input.client.session.promptAsync !== undefined ? "promptAsync" : "prompt";
|
|
384
|
+
const launchResult = await Promise.race([
|
|
385
|
+
launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1({
|
|
386
|
+
client: input.client,
|
|
387
|
+
launchPlan,
|
|
388
|
+
request: {
|
|
389
|
+
allowActualLaneLaunch: true,
|
|
390
|
+
parentSessionId: input.parentSessionId,
|
|
391
|
+
promptText: input.promptText,
|
|
392
|
+
dispatchMethod,
|
|
393
|
+
},
|
|
394
|
+
}),
|
|
395
|
+
new Promise(resolve => setTimeout(() => resolve({ status: "launch_timeout" }), LAUNCH_TIMEOUT_MS)),
|
|
396
|
+
]);
|
|
397
|
+
clearTimeout(launchTimeoutHandle);
|
|
398
|
+
if ("status" in launchResult && launchResult.status === "launch_timeout") {
|
|
399
|
+
// session.prompt blocked for too long — treat as invocation failure
|
|
400
|
+
const failedEvidenceId = `task-failed-${input.taskId}-${token}-launch-timeout`;
|
|
401
|
+
writeSessionEvidence({
|
|
402
|
+
rootDir: input.rootDir,
|
|
403
|
+
workflowId: input.workflowId,
|
|
404
|
+
evidenceId: failedEvidenceId,
|
|
405
|
+
record: {
|
|
406
|
+
schema_version: "flowdesk.task_failed.v1",
|
|
407
|
+
workflow_id: input.workflowId,
|
|
408
|
+
lane_id: input.laneId,
|
|
409
|
+
task_id: input.taskId,
|
|
410
|
+
agent_ref: input.agentRef,
|
|
411
|
+
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
412
|
+
failure_category: "sdk_create_failed",
|
|
413
|
+
redacted_reason: "lane launch timed out: session.prompt did not respond",
|
|
414
|
+
created_at: observedAt,
|
|
415
|
+
dispatch_authority_enabled: false,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
writeAgentTaskTerminalLifecycle({
|
|
419
|
+
rootDir: input.rootDir,
|
|
420
|
+
workflowId: input.workflowId,
|
|
421
|
+
laneId: input.laneId,
|
|
422
|
+
attemptId,
|
|
423
|
+
parentSessionRef,
|
|
424
|
+
agentRef: input.agentRef,
|
|
425
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
426
|
+
state: "invocation_failed",
|
|
427
|
+
evidenceId: `lifecycle-task-terminal-${input.laneId}-${token}-launch-timeout`,
|
|
428
|
+
createdAt: observedAt,
|
|
429
|
+
updatedAt: new Date().toISOString(),
|
|
430
|
+
});
|
|
431
|
+
return { status: "task_failed", failureCategory: "sdk_create_failed", redactedReason: "launch timeout: session.prompt did not respond within the allowed window", laneId: input.laneId };
|
|
432
|
+
}
|
|
102
433
|
// Write running lifecycle evidence
|
|
103
434
|
materializeFlowDeskRuntimeLaneLaunchLifecycleEvidenceV1({
|
|
104
435
|
rootDir: input.rootDir,
|
|
@@ -112,9 +443,9 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
112
443
|
recordFlowDeskLaneHeartbeatV1({
|
|
113
444
|
rootDir: input.rootDir,
|
|
114
445
|
workflowId: input.workflowId,
|
|
115
|
-
attemptId
|
|
446
|
+
attemptId,
|
|
116
447
|
laneId: input.laneId,
|
|
117
|
-
parentSessionRef
|
|
448
|
+
parentSessionRef,
|
|
118
449
|
agentRef: input.agentRef,
|
|
119
450
|
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
120
451
|
state: "running",
|
|
@@ -137,14 +468,26 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
137
468
|
created_at: observedAt,
|
|
138
469
|
dispatch_authority_enabled: false,
|
|
139
470
|
};
|
|
140
|
-
|
|
471
|
+
writeSessionEvidence({
|
|
472
|
+
rootDir: input.rootDir,
|
|
141
473
|
workflowId: input.workflowId,
|
|
142
474
|
evidenceId: taskFailedEvidenceId,
|
|
143
475
|
record: taskFailedRecord,
|
|
144
476
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
477
|
+
writeAgentTaskTerminalLifecycle({
|
|
478
|
+
rootDir: input.rootDir,
|
|
479
|
+
workflowId: input.workflowId,
|
|
480
|
+
laneId: input.laneId,
|
|
481
|
+
attemptId,
|
|
482
|
+
parentSessionRef,
|
|
483
|
+
agentRef: input.agentRef,
|
|
484
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
485
|
+
state: "invocation_failed",
|
|
486
|
+
evidenceId: `lifecycle-task-terminal-${input.laneId}-${token}`,
|
|
487
|
+
createdAt: observedAt,
|
|
488
|
+
updatedAt: new Date().toISOString(),
|
|
489
|
+
timeoutMs: input.timeoutMs,
|
|
490
|
+
});
|
|
148
491
|
return {
|
|
149
492
|
status: "task_failed",
|
|
150
493
|
failureCategory,
|
|
@@ -153,30 +496,93 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
153
496
|
};
|
|
154
497
|
}
|
|
155
498
|
// Lane launched successfully - record heartbeat
|
|
156
|
-
const attemptId = launchPlan.attempt_id ?? `attempt-task-${token}`;
|
|
157
499
|
recordFlowDeskLaneHeartbeatV1({
|
|
158
500
|
rootDir: input.rootDir,
|
|
159
501
|
workflowId: input.workflowId,
|
|
160
502
|
attemptId,
|
|
161
503
|
laneId: input.laneId,
|
|
162
|
-
parentSessionRef
|
|
504
|
+
parentSessionRef,
|
|
163
505
|
agentRef: input.agentRef,
|
|
164
506
|
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
165
507
|
state: "running",
|
|
166
508
|
observedAt,
|
|
167
509
|
progressSummaryLabel: `agent task lane launch heartbeat`,
|
|
168
510
|
});
|
|
169
|
-
// Extract child session ID
|
|
511
|
+
// Extract child session ID
|
|
170
512
|
const childSessionId = launchResult.childSessionRef?.startsWith("ses-")
|
|
171
513
|
? launchResult.childSessionRef.slice("ses-".length)
|
|
172
514
|
: undefined;
|
|
173
|
-
|
|
515
|
+
// ── Async mode: return immediately, watchdog handles polling/nudging/abort ──
|
|
516
|
+
if (input.asyncMode === true) {
|
|
517
|
+
const resolvedChildId = childSessionId ?? "";
|
|
518
|
+
// Write child session evidence so watchdog can find it
|
|
519
|
+
writeSessionEvidence({
|
|
520
|
+
rootDir: input.rootDir,
|
|
521
|
+
workflowId: input.workflowId,
|
|
522
|
+
evidenceId: `agent-task-child-session-${input.laneId}-${token}`,
|
|
523
|
+
record: {
|
|
524
|
+
schema_version: AGENT_TASK_CHILD_SESSION_SCHEMA_VERSION,
|
|
525
|
+
workflow_id: input.workflowId,
|
|
526
|
+
lane_id: input.laneId,
|
|
527
|
+
task_id: input.taskId,
|
|
528
|
+
child_session_id: resolvedChildId,
|
|
529
|
+
parent_session_ref: parentSessionRef,
|
|
530
|
+
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
531
|
+
agent_ref: input.agentRef,
|
|
532
|
+
nudge_count: 0,
|
|
533
|
+
last_nudge_at: null,
|
|
534
|
+
created_at: observedAt,
|
|
535
|
+
dispatch_authority_enabled: false,
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
writeAgentTaskProgress({
|
|
539
|
+
rootDir: input.rootDir,
|
|
540
|
+
workflowId: input.workflowId,
|
|
541
|
+
laneId: input.laneId,
|
|
542
|
+
taskId: input.taskId,
|
|
543
|
+
agentRef: input.agentRef,
|
|
544
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
545
|
+
phase: "waiting",
|
|
546
|
+
progressSeq: 2,
|
|
547
|
+
progressLabel: "agent task waiting for async child result",
|
|
548
|
+
});
|
|
549
|
+
return { status: "task_launched", laneId: input.laneId, childSessionId: resolvedChildId };
|
|
550
|
+
}
|
|
551
|
+
let resultObservation;
|
|
174
552
|
if (childSessionId !== undefined) {
|
|
175
|
-
|
|
553
|
+
const runtimeModel = launchResult.status === "lane_launch_started" && typeof launchResult.model === "string"
|
|
554
|
+
? launchResult.model : undefined;
|
|
555
|
+
const agentName = launchResult.status === "lane_launch_started" && typeof launchResult.agent === "string"
|
|
556
|
+
? launchResult.agent : undefined;
|
|
557
|
+
resultObservation = await extractAssistantTextFromResponse(input.client, childSessionId, {
|
|
558
|
+
quietPeriodMs: input._nudgeQuietPeriodMs ?? 20_000, // default 20s per policy
|
|
559
|
+
maxNudges: 2,
|
|
560
|
+
runtimeModel,
|
|
561
|
+
agentName,
|
|
562
|
+
messagesTimeoutMs: input._messagesTimeoutMs,
|
|
563
|
+
heartbeatFn: (elapsedMs) => {
|
|
564
|
+
recordFlowDeskLaneHeartbeatV1({
|
|
565
|
+
rootDir: input.rootDir,
|
|
566
|
+
workflowId: input.workflowId,
|
|
567
|
+
attemptId,
|
|
568
|
+
laneId: input.laneId,
|
|
569
|
+
parentSessionRef,
|
|
570
|
+
agentRef: input.agentRef,
|
|
571
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
572
|
+
state: "running",
|
|
573
|
+
observedAt: new Date().toISOString(),
|
|
574
|
+
progressSummaryLabel: `agent task waiting for response elapsed=${Math.floor(elapsedMs / 1000)}s`,
|
|
575
|
+
});
|
|
576
|
+
},
|
|
577
|
+
});
|
|
176
578
|
}
|
|
579
|
+
const resultText = resultObservation?.text;
|
|
177
580
|
if (resultText === undefined) {
|
|
178
581
|
// No response text - write task_failed
|
|
179
582
|
const taskFailedEvidenceId = `task-failed-${input.taskId}-${token}`;
|
|
583
|
+
const failureCategory = "no_response";
|
|
584
|
+
const evidenceFailureCategory = "no_response";
|
|
585
|
+
const redactedReason = "lane launched but no assistant response text found";
|
|
180
586
|
const taskFailedRecord = {
|
|
181
587
|
schema_version: "flowdesk.task_failed.v1",
|
|
182
588
|
workflow_id: input.workflowId,
|
|
@@ -184,30 +590,54 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
184
590
|
task_id: input.taskId,
|
|
185
591
|
agent_ref: input.agentRef,
|
|
186
592
|
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
187
|
-
failure_category:
|
|
188
|
-
redacted_reason:
|
|
593
|
+
failure_category: evidenceFailureCategory,
|
|
594
|
+
redacted_reason: redactedReason,
|
|
189
595
|
created_at: observedAt,
|
|
190
596
|
dispatch_authority_enabled: false,
|
|
191
597
|
};
|
|
192
|
-
|
|
598
|
+
writeSessionEvidence({
|
|
599
|
+
rootDir: input.rootDir,
|
|
193
600
|
workflowId: input.workflowId,
|
|
194
601
|
evidenceId: taskFailedEvidenceId,
|
|
195
602
|
record: taskFailedRecord,
|
|
196
603
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
604
|
+
writeAgentTaskProgress({
|
|
605
|
+
rootDir: input.rootDir,
|
|
606
|
+
workflowId: input.workflowId,
|
|
607
|
+
laneId: input.laneId,
|
|
608
|
+
taskId: input.taskId,
|
|
609
|
+
agentRef: input.agentRef,
|
|
610
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
611
|
+
phase: "failed",
|
|
612
|
+
progressSeq: 3,
|
|
613
|
+
progressLabel: failureCategory === "no_response" ? "agent task finished without response" : "agent task output contract not satisfied",
|
|
614
|
+
});
|
|
615
|
+
writeAgentTaskTerminalLifecycle({
|
|
616
|
+
rootDir: input.rootDir,
|
|
617
|
+
workflowId: input.workflowId,
|
|
618
|
+
laneId: input.laneId,
|
|
619
|
+
attemptId,
|
|
620
|
+
parentSessionRef,
|
|
621
|
+
childSessionRef: launchResult.childSessionRef,
|
|
622
|
+
messageRef: launchResult.messageRef?.startsWith("msg-") ? launchResult.messageRef : undefined,
|
|
623
|
+
agentRef: input.agentRef,
|
|
624
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
625
|
+
state: "no_output",
|
|
626
|
+
evidenceId: `lifecycle-task-terminal-${input.laneId}-${token}`,
|
|
627
|
+
createdAt: observedAt,
|
|
628
|
+
updatedAt: new Date().toISOString(),
|
|
629
|
+
timeoutMs: input.timeoutMs,
|
|
630
|
+
});
|
|
200
631
|
return {
|
|
201
632
|
status: "task_failed",
|
|
202
|
-
failureCategory
|
|
203
|
-
redactedReason
|
|
633
|
+
failureCategory,
|
|
634
|
+
redactedReason,
|
|
204
635
|
laneId: input.laneId,
|
|
205
636
|
};
|
|
206
637
|
}
|
|
207
|
-
// Truncate if needed
|
|
208
638
|
const fullResultText = resultText;
|
|
209
|
-
const
|
|
210
|
-
const storedResultText =
|
|
639
|
+
const sanitizedResult = sanitizeFlowDeskTaskResultTextV1(fullResultText);
|
|
640
|
+
const storedResultText = sanitizedResult.text;
|
|
211
641
|
const promptSha256 = sha256Hex(input.promptText);
|
|
212
642
|
const resultSha256 = sha256Hex(fullResultText);
|
|
213
643
|
// Write task_result evidence
|
|
@@ -221,19 +651,64 @@ export async function executeFlowDeskAgentTaskV1(input) {
|
|
|
221
651
|
provider_qualified_model_id: input.providerQualifiedModelId,
|
|
222
652
|
task_prompt_sha256: promptSha256,
|
|
223
653
|
result_text: storedResultText,
|
|
224
|
-
result_text_truncated: truncated,
|
|
654
|
+
result_text_truncated: sanitizedResult.truncated,
|
|
225
655
|
result_text_sha256: resultSha256,
|
|
656
|
+
completion_status: resultObservation?.completionStatus ?? "final",
|
|
657
|
+
output_kind: resultObservation?.outputKind ?? "final_answer",
|
|
658
|
+
usable_for_synthesis: resultObservation?.usableForSynthesis ?? true,
|
|
659
|
+
missing_contract: input.outputContract === "final_assistant_text" &&
|
|
660
|
+
(resultObservation?.completionStatus !== "final" ||
|
|
661
|
+
["empty", "process_notes", "tool_trace_only"].includes(String(resultObservation?.outputKind ?? ""))),
|
|
226
662
|
created_at: observedAt,
|
|
227
663
|
dispatch_authority_enabled: false,
|
|
228
664
|
};
|
|
229
|
-
const
|
|
665
|
+
const taskResultWritten = writeSessionEvidence({
|
|
666
|
+
rootDir: input.rootDir,
|
|
230
667
|
workflowId: input.workflowId,
|
|
231
668
|
evidenceId: taskResultEvidenceId,
|
|
232
669
|
record: taskResultRecord,
|
|
233
670
|
});
|
|
234
|
-
if (
|
|
235
|
-
|
|
671
|
+
if (!taskResultWritten) {
|
|
672
|
+
return {
|
|
673
|
+
status: "task_failed",
|
|
674
|
+
failureCategory: "unknown",
|
|
675
|
+
redactedReason: "task_result evidence persistence failed",
|
|
676
|
+
laneId: input.laneId,
|
|
677
|
+
};
|
|
236
678
|
}
|
|
679
|
+
writeAgentTaskProgress({
|
|
680
|
+
rootDir: input.rootDir,
|
|
681
|
+
workflowId: input.workflowId,
|
|
682
|
+
laneId: input.laneId,
|
|
683
|
+
taskId: input.taskId,
|
|
684
|
+
agentRef: input.agentRef,
|
|
685
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
686
|
+
phase: "finalizing",
|
|
687
|
+
progressSeq: 3,
|
|
688
|
+
progressLabel: "agent task result captured",
|
|
689
|
+
});
|
|
690
|
+
writeAgentTaskTerminalLifecycle({
|
|
691
|
+
rootDir: input.rootDir,
|
|
692
|
+
workflowId: input.workflowId,
|
|
693
|
+
laneId: input.laneId,
|
|
694
|
+
attemptId,
|
|
695
|
+
parentSessionRef,
|
|
696
|
+
childSessionRef: launchResult.childSessionRef,
|
|
697
|
+
messageRef: launchResult.messageRef?.startsWith("msg-") ? launchResult.messageRef : undefined,
|
|
698
|
+
agentRef: input.agentRef,
|
|
699
|
+
providerQualifiedModelId: input.providerQualifiedModelId,
|
|
700
|
+
state: "incomplete",
|
|
701
|
+
outputRef: `output-${taskResultEvidenceId}`,
|
|
702
|
+
evidenceId: `lifecycle-task-terminal-${input.laneId}-${token}`,
|
|
703
|
+
createdAt: observedAt,
|
|
704
|
+
updatedAt: new Date().toISOString(),
|
|
705
|
+
timeoutMs: input.timeoutMs,
|
|
706
|
+
});
|
|
707
|
+
refreshFlowDeskCompletionUiCachesV1({
|
|
708
|
+
rootDir: input.rootDir,
|
|
709
|
+
workflowId: input.workflowId,
|
|
710
|
+
observedAt,
|
|
711
|
+
});
|
|
237
712
|
return {
|
|
238
713
|
status: "task_completed",
|
|
239
714
|
resultText: fullResultText,
|