@pasko70/pibo 1.7.4 → 1.7.5
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/apps/chat/chat-api-routes.js +14 -0
- package/dist/apps/chat/web-app.js +125 -2
- package/dist/apps/chat/workflow-manual-trigger-runtime.js +223 -0
- package/dist/apps/chat-ui/assets/{dist-CSKVaxJI.js → dist-9reC6CRy.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BZoXuF9y.js → dist-B3s0osfY.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BS-A0VYR.js → dist-BFDieA29.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-OTFZZTiY.js → dist-CD2PBsXa.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C6kgrKy2.js → dist-CKfKOLLy.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D6v32djn.js → dist-CrBAoz7q.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-MCSKIQ04.js → dist-DY6s8EGj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bl9cf-yT.js → dist-Jx6fE2Hb.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B8jyBeFH.js → dist-TfPabg6A.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BzmsjE6I.js → dist-b5e4NjPA.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ChDQZXGN.js → dist-kiXdn9N3.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BLY-pTWm.css +1 -0
- package/dist/apps/chat-ui/assets/{index-CXXHizfD.js → index-DN6wYo-Z.js} +7 -7
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/{pibo-vscode-ext-1.7.4.vsix → pibo-vscode-ext-1.7.5.vsix} +0 -0
- package/dist/plugins/builtin.js +2 -0
- package/dist/ralph/cli.js +2 -1
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-DgVHk3gL.css +0 -1
|
@@ -94,6 +94,20 @@ export function workflowDraftActionResource(pathname) {
|
|
|
94
94
|
throw new PiboWebHttpError("Invalid workflow draft action", 400);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
export function workflowDraftManualTriggerRunResource(pathname) {
|
|
98
|
+
const prefix = `${CHAT_WEB_API_PREFIX}/workflows/drafts/`;
|
|
99
|
+
if (!pathname.startsWith(prefix))
|
|
100
|
+
return undefined;
|
|
101
|
+
const parts = pathname.slice(prefix.length).split("/");
|
|
102
|
+
if (parts.length !== 2 || !parts[0] || parts[1] !== "manual-trigger-runs")
|
|
103
|
+
return undefined;
|
|
104
|
+
try {
|
|
105
|
+
return { draftId: decodeURIComponent(parts[0]) };
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw new PiboWebHttpError("Invalid workflow draft manual trigger route", 400);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
97
111
|
export function workflowDuplicateResourceId(pathname) {
|
|
98
112
|
const prefix = `${CHAT_WEB_API_PREFIX}/workflows/`;
|
|
99
113
|
const suffix = "/duplicate";
|
|
@@ -43,7 +43,7 @@ import { prepareChatFileAttachments, resolveDownloadPath, responseChatFileDownlo
|
|
|
43
43
|
import { chatSettingsRoute, chatSettingsRouteInvalidatesBootstrapCatalog, chatSettingsRouteRequiresSameOrigin, handleChatSettingsRoute, } from "./chat-settings-routes.js";
|
|
44
44
|
import { chatCapabilityRoute, chatCapabilityRouteRequiresSameOrigin, handleChatCapabilityRoute, } from "./chat-capability-routes.js";
|
|
45
45
|
import { chatUserSkillRoute, chatUserSkillRouteRequiresSameOrigin, handleChatUserSkillRoute, syncChatUserSkills, } from "./chat-user-skill-routes.js";
|
|
46
|
-
import { CHAT_WEB_API_PREFIX, agentResourceId, projectResourcePath, projectSessionResourceId, projectWorkflowHumanActionsResource, projectWorkflowSessionStartResource, roomResourcePath, sessionActionResource, sessionResourceId, signalResource, workflowArchiveResourceId, workflowCatalogResourceId, workflowDraftActionResource, workflowDraftResourceId, workflowDuplicateResourceId, workflowNextDraftResourceId, workflowPickerKind, workflowPromptAssetResourceId, workflowVersionResource, } from "./chat-api-routes.js";
|
|
46
|
+
import { CHAT_WEB_API_PREFIX, agentResourceId, projectResourcePath, projectSessionResourceId, projectWorkflowHumanActionsResource, projectWorkflowSessionStartResource, roomResourcePath, sessionActionResource, sessionResourceId, signalResource, workflowArchiveResourceId, workflowCatalogResourceId, workflowDraftActionResource, workflowDraftManualTriggerRunResource, workflowDraftResourceId, workflowDuplicateResourceId, workflowNextDraftResourceId, workflowPickerKind, workflowPromptAssetResourceId, workflowVersionResource, } from "./chat-api-routes.js";
|
|
47
47
|
import { assertProjectSessionPatchFields, buildStreamingFixtureSchedule, createAgentInput, createAgentUpdate, createRoomUpdate, createSessionUpdate, normalizeAgentArchived, normalizeClientTxnId, normalizeMessageText, normalizeParentRoomId, normalizeProjectArchived, normalizeProjectDescription, normalizeProjectPath, normalizeProjectSessionArchived, normalizeRoomDeleteConfirmation, normalizeRoomName, normalizeRoomTopic, normalizeRoomType, normalizeRoomWorkspace, normalizeSessionDeleteConfirmation, normalizeSessionTitle, normalizeStreamingFixtureCadenceMs, normalizeStreamingFixtureDeltas, normalizeStreamingFixtureMix, normalizeStreamingFixturePreludeMessages, normalizeStreamingFixturePreludeOnly, normalizeStreamingFixtureProfile, normalizeStreamingFixtureSuppressLiveDeltas, normalizeStreamingFixtureTraceSnapshots, resolveCreateSessionProfile, } from "./chat-request-normalizers.js";
|
|
48
48
|
import { ChatWorkflowArchiveStore, ChatWorkflowDraftStore, ChatWorkflowLifecycleEventStore, ChatWorkflowPromptAssetStore, ChatWorkflowPublishedVersionStore, ChatWorkflowTombstoneStore, hashWorkflowDefinitionJson, normalizeWorkflowPromptAssetLabel, sanitizeWorkflowDiagnostics, } from "./workflow-persistence.js";
|
|
49
49
|
import { normalizeProjectWorkflowHumanActionBody, projectWorkflowHumanActionDiagnosticResponse, projectWorkflowHumanActionLifecyclePayload, projectWorkflowHumanActionRuntimeDiagnostic, projectWorkflowHumanActionSubmittedLifecyclePayload, projectWorkflowPendingHumanActionFromToken, validateProjectWorkflowHumanActionRequest, } from "./project-workflow-human-actions.js";
|
|
@@ -51,6 +51,7 @@ import { createProjectWorkflowRunCurrent, createProjectWorkflowSessionSnapshot,
|
|
|
51
51
|
import { STATIC_WORKFLOW_VERSION_CATALOG, buildProjectWorkflowVersionCatalog, buildProjectWorkflowVersionOptions, buildWorkflowCatalogInspect as buildWorkflowCatalogInspectWithServices, buildWorkflowCatalogList as buildWorkflowCatalogListWithServices, buildWorkflowVersionHistory, buildWorkflowVersionInspect as buildWorkflowVersionInspectWithServices, buildWorkflowVersionList as buildWorkflowVersionListWithServices, buildWorkflowVersionPicker, createPublishedWorkflowDefinition, selectWorkflowCatalogDisplayVersion, workflowDefinitionTags, workflowVersionPickerOptionFromCatalogRecord, } from "./workflow-catalog.js";
|
|
52
52
|
import { cloneJsonObject, isWorkflowValidationPipelineDiagnostic, normalizeWorkflowValidationTrigger, normalizeWorkflowVersionIntent, parseWorkflowDraftDefinitionFromPatch, summarizeWorkflowDiagnostics, withoutRawWorkflowIrParseDiagnostic, } from "./workflow-validation-helpers.js";
|
|
53
53
|
import { validateWorkflowEdgeAdapterOutputCompatibilityLike, validateWorkflowEdgeDirectCompatibilityLike, } from "./workflow-edge-compatibility.js";
|
|
54
|
+
import { runWorkflowManualTextTrigger, } from "./workflow-manual-trigger-runtime.js";
|
|
54
55
|
import { validateJsonSchemaObjectLike } from "./workflow-json-schema-validation.js";
|
|
55
56
|
import { WORKFLOW_ADAPTER_REF_OPTIONS, WORKFLOW_GUARD_REF_OPTIONS, WORKFLOW_HUMAN_ACTION_REF_OPTIONS, buildWorkflowPromptAssetPicker, buildWorkflowRegisteredRefPicker, getWorkflowPromptAssetDocument, isWorkflowPromptAssetRegistered, } from "./workflow-registered-ref-pickers.js";
|
|
56
57
|
import { validateRegisteredAdapterRefLike, validateWorkflowGuardRefLike, validateWorkflowHumanActionRefsLike, validateWorkflowPromptAssetRefLike, } from "./workflow-registered-ref-validation.js";
|
|
@@ -1536,6 +1537,31 @@ function runWorkflowDraftValidation(state, context, webSession, record, trigger)
|
|
|
1536
1537
|
});
|
|
1537
1538
|
return { validation, diagnostics };
|
|
1538
1539
|
}
|
|
1540
|
+
function normalizeWorkflowDraftManualTriggerRunBody(body) {
|
|
1541
|
+
const triggerNodeId = typeof body.triggerNodeId === "string" ? body.triggerNodeId.trim() : "";
|
|
1542
|
+
if (!triggerNodeId)
|
|
1543
|
+
throw new PiboWebHttpError("Manual trigger node id is required", 400);
|
|
1544
|
+
if (typeof body.input !== "string")
|
|
1545
|
+
throw new PiboWebHttpError("Manual trigger input must be text", 400);
|
|
1546
|
+
return { triggerNodeId, input: body.input };
|
|
1547
|
+
}
|
|
1548
|
+
function resolveWorkflowRuntimeProfile(input) {
|
|
1549
|
+
const profile = input.context.channelContext.getProfiles?.().find((candidate) => candidate.name === input.requestedId || candidate.aliases.includes(input.requestedId));
|
|
1550
|
+
return profile?.name;
|
|
1551
|
+
}
|
|
1552
|
+
function workflowManualTriggerRunResponse(result, validation, draft) {
|
|
1553
|
+
return {
|
|
1554
|
+
ok: result.ok,
|
|
1555
|
+
draft: serializeWorkflowDraft(draft),
|
|
1556
|
+
validation: validation.validation,
|
|
1557
|
+
diagnostics: sanitizeWorkflowDiagnostics([...validation.diagnostics, ...result.diagnostics]),
|
|
1558
|
+
...(result.run ? { run: result.run } : {}),
|
|
1559
|
+
nodeAttempts: result.nodeAttempts,
|
|
1560
|
+
edgeTransfers: result.edgeTransfers,
|
|
1561
|
+
...(result.output !== undefined ? { output: result.output } : {}),
|
|
1562
|
+
...(result.error ? { error: result.error } : {}),
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1539
1565
|
function validatePublishedWorkflowBoundary(input) {
|
|
1540
1566
|
const diagnostics = sanitizeWorkflowDiagnostics(validateWorkflowDefinitionForV2(input.definition, input));
|
|
1541
1567
|
return {
|
|
@@ -1656,15 +1682,62 @@ function validateWorkflowNodeLike(nodeId, value, input, diagnostics) {
|
|
|
1656
1682
|
validateWorkflowHumanNodeLike(nodeId, value, diagnostics);
|
|
1657
1683
|
return;
|
|
1658
1684
|
}
|
|
1685
|
+
if (kind === "trigger") {
|
|
1686
|
+
validateWorkflowTriggerNodeLike(nodeId, value, diagnostics);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1659
1689
|
diagnostics.push({
|
|
1660
1690
|
code: "WorkflowValidationError.unknownNodeKind",
|
|
1661
|
-
message: `Workflow node '${nodeId}' must use kind agent, code, workflow, adapter, or human.`,
|
|
1691
|
+
message: `Workflow node '${nodeId}' must use kind trigger, agent, code, workflow, adapter, or human.`,
|
|
1662
1692
|
severity: "error",
|
|
1663
1693
|
path: `${path}.kind`,
|
|
1664
1694
|
nodeId,
|
|
1665
1695
|
hint: "Use one of the registered V2 editable node kinds.",
|
|
1666
1696
|
});
|
|
1667
1697
|
}
|
|
1698
|
+
function validateWorkflowTriggerNodeLike(nodeId, node, diagnostics) {
|
|
1699
|
+
const path = `$.nodes.${nodeId}`;
|
|
1700
|
+
const trigger = isJsonObject(node.trigger) ? node.trigger : undefined;
|
|
1701
|
+
if (!trigger) {
|
|
1702
|
+
diagnostics.push({
|
|
1703
|
+
code: "WorkflowValidationError.missingTriggerConfig",
|
|
1704
|
+
message: `Trigger node '${nodeId}' must include a trigger configuration object.`,
|
|
1705
|
+
severity: "error",
|
|
1706
|
+
path: `${path}.trigger`,
|
|
1707
|
+
nodeId,
|
|
1708
|
+
});
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (trigger.kind !== "manual") {
|
|
1712
|
+
diagnostics.push({
|
|
1713
|
+
code: "WorkflowValidationError.unsupportedTriggerKind",
|
|
1714
|
+
message: `Trigger node '${nodeId}' must use manual trigger kind in this Workflow V1 slice.`,
|
|
1715
|
+
severity: "error",
|
|
1716
|
+
path: `${path}.trigger.kind`,
|
|
1717
|
+
nodeId,
|
|
1718
|
+
hint: "Webhook, cron, and other trigger providers are planned but not implemented yet.",
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
if (trigger.mode !== undefined && trigger.mode !== "editor" && trigger.mode !== "api") {
|
|
1722
|
+
diagnostics.push({
|
|
1723
|
+
code: "WorkflowValidationError.unsupportedTriggerMode",
|
|
1724
|
+
message: `Trigger node '${nodeId}' has unsupported manual trigger mode.`,
|
|
1725
|
+
severity: "error",
|
|
1726
|
+
path: `${path}.trigger.mode`,
|
|
1727
|
+
nodeId,
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
if (!node.output) {
|
|
1731
|
+
diagnostics.push({
|
|
1732
|
+
code: "WorkflowValidationError.missingTriggerOutput",
|
|
1733
|
+
message: `Trigger node '${nodeId}' must declare an output port.`,
|
|
1734
|
+
severity: "error",
|
|
1735
|
+
path: `${path}.output`,
|
|
1736
|
+
nodeId,
|
|
1737
|
+
hint: "Use a text output port for the first manual trigger implementation.",
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1668
1741
|
function validateWorkflowAgentNodeLike(nodeId, node, input, diagnostics) {
|
|
1669
1742
|
const path = `$.nodes.${nodeId}`;
|
|
1670
1743
|
if (node.runtime !== "pibo") {
|
|
@@ -3522,6 +3595,56 @@ export function createChatWebApp(options = {}) {
|
|
|
3522
3595
|
const body = await readJsonBody(request);
|
|
3523
3596
|
return responseJson({ asset: saveWorkflowPromptAssetRevision(state, webSession, body) }, { status: 201 });
|
|
3524
3597
|
}
|
|
3598
|
+
const workflowDraftManualTriggerRun = workflowDraftManualTriggerRunResource(url.pathname);
|
|
3599
|
+
if (workflowDraftManualTriggerRun && request.method === "POST") {
|
|
3600
|
+
requireSameOriginJsonRequest(request);
|
|
3601
|
+
const webSession = await requireSession(request, context);
|
|
3602
|
+
const body = normalizeWorkflowDraftManualTriggerRunBody(await readJsonBody(request));
|
|
3603
|
+
const record = requireMutableWorkflowDraft(state, webSession, workflowDraftManualTriggerRun.draftId);
|
|
3604
|
+
const validation = runWorkflowDraftValidation(state, context, webSession, record, "before_workflow_start");
|
|
3605
|
+
if (!validation.validation.ok) {
|
|
3606
|
+
recordWorkflowLifecycleEvent(state, webSession, {
|
|
3607
|
+
type: "workflow.editor_test_run.blocked",
|
|
3608
|
+
workflowId: record.workflowId,
|
|
3609
|
+
workflowVersion: typeof record.definition.version === "string" ? record.definition.version : undefined,
|
|
3610
|
+
draftId: record.draftId,
|
|
3611
|
+
status: "blocked",
|
|
3612
|
+
validation: validation.validation,
|
|
3613
|
+
diagnostics: validation.diagnostics,
|
|
3614
|
+
payload: { triggerNodeId: body.triggerNodeId },
|
|
3615
|
+
});
|
|
3616
|
+
return workflowValidationBlockedResponse("Workflow draft has validation errors and cannot be test-run", validation, { draft: serializeWorkflowDraft(record) });
|
|
3617
|
+
}
|
|
3618
|
+
const result = await runWorkflowManualTextTrigger({
|
|
3619
|
+
definition: record.definition,
|
|
3620
|
+
triggerNodeId: body.triggerNodeId,
|
|
3621
|
+
input: body.input,
|
|
3622
|
+
actorId: auditActorIdFor(webSession),
|
|
3623
|
+
draftId: record.draftId,
|
|
3624
|
+
channelContext: context.channelContext,
|
|
3625
|
+
channel: CHAT_WEB_CHANNEL,
|
|
3626
|
+
defaultWorkspace: getDefaultPiboWorkspace(),
|
|
3627
|
+
onSessionCreated: (session) => state.sessionQuery.upsertSession(session),
|
|
3628
|
+
resolveProfile: (profileId) => resolveWorkflowRuntimeProfile({ context, requestedId: profileId }),
|
|
3629
|
+
});
|
|
3630
|
+
const diagnostics = result.ok ? validation.diagnostics : sanitizeWorkflowDiagnostics([...validation.diagnostics, ...result.diagnostics]);
|
|
3631
|
+
recordWorkflowLifecycleEvent(state, webSession, {
|
|
3632
|
+
type: result.ok ? "workflow.editor_test_run.completed" : "workflow.editor_test_run.failed",
|
|
3633
|
+
workflowId: record.workflowId,
|
|
3634
|
+
workflowVersion: typeof record.definition.version === "string" ? record.definition.version : undefined,
|
|
3635
|
+
draftId: record.draftId,
|
|
3636
|
+
workflowRunId: result.run?.id,
|
|
3637
|
+
status: result.ok ? "accepted" : "blocked",
|
|
3638
|
+
validation: validation.validation,
|
|
3639
|
+
diagnostics,
|
|
3640
|
+
payload: {
|
|
3641
|
+
triggerNodeId: body.triggerNodeId,
|
|
3642
|
+
...(result.run?.status ? { status: result.run.status } : {}),
|
|
3643
|
+
...(result.ok && result.output !== undefined ? { output: result.output } : {}),
|
|
3644
|
+
},
|
|
3645
|
+
});
|
|
3646
|
+
return responseJson(workflowManualTriggerRunResponse(result, { validation: validation.validation, diagnostics }, record), { status: result.ok ? 202 : 400 });
|
|
3647
|
+
}
|
|
3525
3648
|
const workflowDraftAction = workflowDraftActionResource(url.pathname);
|
|
3526
3649
|
if (workflowDraftAction && request.method === "POST") {
|
|
3527
3650
|
requireSameOriginJsonRequest(request);
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export async function runWorkflowManualTextTrigger(options) {
|
|
3
|
+
const diagnostics = validateWorkflowManualTextTrigger(options.definition, options.triggerNodeId, options.input, options.resolveProfile);
|
|
4
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
5
|
+
return { ok: false, nodeAttempts: [], edgeTransfers: [], diagnostics, error: { code: "WorkflowRuntimeError.manualTriggerRunBlocked", message: "Manual trigger run was blocked by validation errors." } };
|
|
6
|
+
}
|
|
7
|
+
const now = new Date().toISOString();
|
|
8
|
+
const workflowId = stringValue(options.definition.id) || "workflow";
|
|
9
|
+
const workflowVersion = stringValue(options.definition.version) || "draft";
|
|
10
|
+
const run = {
|
|
11
|
+
id: `wfr_${randomUUID()}`,
|
|
12
|
+
workflowId,
|
|
13
|
+
workflowVersion,
|
|
14
|
+
status: "completed",
|
|
15
|
+
triggerNodeId: options.triggerNodeId,
|
|
16
|
+
input: options.input,
|
|
17
|
+
createdAt: now,
|
|
18
|
+
updatedAt: now,
|
|
19
|
+
};
|
|
20
|
+
const nodeAttempts = [];
|
|
21
|
+
const edgeTransfers = [];
|
|
22
|
+
const triggerAttempt = {
|
|
23
|
+
id: `wna_${randomUUID()}`,
|
|
24
|
+
workflowRunId: run.id,
|
|
25
|
+
nodeId: options.triggerNodeId,
|
|
26
|
+
kind: "trigger",
|
|
27
|
+
status: "completed",
|
|
28
|
+
input: options.input,
|
|
29
|
+
output: options.input,
|
|
30
|
+
startedAt: now,
|
|
31
|
+
completedAt: now,
|
|
32
|
+
};
|
|
33
|
+
nodeAttempts.push(triggerAttempt);
|
|
34
|
+
const queue = [];
|
|
35
|
+
enqueueOutgoingEdges(options.definition, run.id, triggerAttempt, queue, edgeTransfers);
|
|
36
|
+
const executedNodes = new Set();
|
|
37
|
+
let lastOutput = options.input;
|
|
38
|
+
while (queue.length) {
|
|
39
|
+
const pending = queue.shift();
|
|
40
|
+
if (executedNodes.has(pending.nodeId)) {
|
|
41
|
+
const failedAt = new Date().toISOString();
|
|
42
|
+
run.status = "failed";
|
|
43
|
+
run.failedAt = failedAt;
|
|
44
|
+
run.updatedAt = failedAt;
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
run,
|
|
48
|
+
nodeAttempts,
|
|
49
|
+
edgeTransfers,
|
|
50
|
+
diagnostics: [{
|
|
51
|
+
code: "WorkflowRuntimeError.joinUnsupported",
|
|
52
|
+
message: `Workflow node '${pending.nodeId}' received more than one input, but manual trigger V1 does not support joins yet.`,
|
|
53
|
+
severity: "error",
|
|
54
|
+
nodeId: pending.nodeId,
|
|
55
|
+
edgeId: pending.viaEdgeId,
|
|
56
|
+
path: `$.edges.${pending.viaEdgeId}`,
|
|
57
|
+
}],
|
|
58
|
+
error: { code: "WorkflowRuntimeError.joinUnsupported", message: "Manual trigger run failed because a join node was reached." },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
executedNodes.add(pending.nodeId);
|
|
62
|
+
const result = await runAgentNode({ ...options, runId: run.id, nodeId: pending.nodeId, input: pending.input });
|
|
63
|
+
nodeAttempts.push(result.attempt);
|
|
64
|
+
if (!result.ok) {
|
|
65
|
+
const failedAt = new Date().toISOString();
|
|
66
|
+
run.status = "failed";
|
|
67
|
+
run.failedAt = failedAt;
|
|
68
|
+
run.updatedAt = failedAt;
|
|
69
|
+
return { ok: false, run, nodeAttempts, edgeTransfers, diagnostics: result.diagnostics, error: result.attempt.error };
|
|
70
|
+
}
|
|
71
|
+
lastOutput = result.output;
|
|
72
|
+
enqueueOutgoingEdges(options.definition, run.id, result.attempt, queue, edgeTransfers);
|
|
73
|
+
}
|
|
74
|
+
const completedAt = new Date().toISOString();
|
|
75
|
+
run.output = lastOutput;
|
|
76
|
+
run.completedAt = completedAt;
|
|
77
|
+
run.updatedAt = completedAt;
|
|
78
|
+
return { ok: true, run, nodeAttempts, edgeTransfers, diagnostics, output: lastOutput };
|
|
79
|
+
}
|
|
80
|
+
export function validateWorkflowManualTextTrigger(definition, triggerNodeId, input, resolveProfile) {
|
|
81
|
+
const diagnostics = [];
|
|
82
|
+
const nodes = objectValue(definition.nodes);
|
|
83
|
+
const edges = objectValue(definition.edges);
|
|
84
|
+
const trigger = objectValue(nodes?.[triggerNodeId]);
|
|
85
|
+
if (!trigger) {
|
|
86
|
+
diagnostics.push({ code: "WorkflowRuntimeError.unknownTriggerNode", message: `Manual trigger node '${triggerNodeId}' does not exist.`, severity: "error", nodeId: triggerNodeId, path: `$.nodes.${triggerNodeId}` });
|
|
87
|
+
return diagnostics;
|
|
88
|
+
}
|
|
89
|
+
if (trigger.kind !== "trigger")
|
|
90
|
+
diagnostics.push({ code: "WorkflowRuntimeError.triggerNodeRequired", message: `Workflow node '${triggerNodeId}' is not a trigger node.`, severity: "error", nodeId: triggerNodeId, path: `$.nodes.${triggerNodeId}.kind` });
|
|
91
|
+
const triggerConfig = objectValue(trigger.trigger);
|
|
92
|
+
if (triggerConfig?.kind !== "manual")
|
|
93
|
+
diagnostics.push({ code: "WorkflowRuntimeError.manualTriggerRequired", message: `Workflow trigger node '${triggerNodeId}' must use manual trigger kind.`, severity: "error", nodeId: triggerNodeId, path: `$.nodes.${triggerNodeId}.trigger.kind` });
|
|
94
|
+
if (objectValue(trigger.output)?.kind !== "text")
|
|
95
|
+
diagnostics.push({ code: "WorkflowRuntimeError.textTriggerOutputRequired", message: `Workflow trigger node '${triggerNodeId}' must declare a text output port.`, severity: "error", nodeId: triggerNodeId, path: `$.nodes.${triggerNodeId}.output` });
|
|
96
|
+
if (typeof input !== "string")
|
|
97
|
+
diagnostics.push({ code: "WorkflowInterfaceError.textValueExpected", message: "Manual trigger input must be text.", severity: "error", path: "$.input" });
|
|
98
|
+
for (const [edgeId, rawEdge] of Object.entries(edges ?? {})) {
|
|
99
|
+
const edge = objectValue(rawEdge);
|
|
100
|
+
if (!edge)
|
|
101
|
+
continue;
|
|
102
|
+
if (edge.guard)
|
|
103
|
+
diagnostics.push({ code: "WorkflowRuntimeError.edgeGuardUnsupported", message: `Workflow edge '${edgeId}' uses a guard, which manual trigger V1 does not execute yet.`, severity: "error", edgeId, path: `$.edges.${edgeId}.guard` });
|
|
104
|
+
if (edge.adapter)
|
|
105
|
+
diagnostics.push({ code: "WorkflowRuntimeError.edgeAdapterUnsupported", message: `Workflow edge '${edgeId}' uses an adapter, which manual trigger V1 does not execute yet.`, severity: "error", edgeId, path: `$.edges.${edgeId}.adapter` });
|
|
106
|
+
const from = objectValue(edge.from);
|
|
107
|
+
const to = objectValue(edge.to);
|
|
108
|
+
const source = typeof from?.nodeId === "string" ? objectValue(nodes?.[from.nodeId]) : undefined;
|
|
109
|
+
const targetNodeId = typeof to?.nodeId === "string" ? to.nodeId : "";
|
|
110
|
+
const target = targetNodeId ? objectValue(nodes?.[targetNodeId]) : undefined;
|
|
111
|
+
if (!source || !target)
|
|
112
|
+
continue;
|
|
113
|
+
if (source.kind !== "trigger" && source.kind !== "agent")
|
|
114
|
+
continue;
|
|
115
|
+
if (target.kind !== "agent")
|
|
116
|
+
diagnostics.push({ code: "WorkflowRuntimeError.agentTargetRequired", message: `Workflow edge '${edgeId}' targets a non-agent node, which manual trigger V1 does not execute yet.`, severity: "error", edgeId, nodeId: targetNodeId, path: `$.edges.${edgeId}.to.nodeId` });
|
|
117
|
+
const sourceOutput = objectValue(source.output);
|
|
118
|
+
const targetInput = objectValue(target.input);
|
|
119
|
+
if (sourceOutput?.kind && targetInput?.kind && (sourceOutput.kind !== "text" || targetInput.kind !== "text")) {
|
|
120
|
+
diagnostics.push({ code: "WorkflowGraphError.incompatibleEdgePorts", message: `Workflow edge '${edgeId}' must connect text output to text input for manual trigger V1.`, severity: "error", edgeId, path: `$.edges.${edgeId}` });
|
|
121
|
+
}
|
|
122
|
+
if (target.kind === "agent") {
|
|
123
|
+
const profile = objectValue(target.profile);
|
|
124
|
+
const profileId = typeof profile?.id === "string" ? profile.id : "";
|
|
125
|
+
if (!profileId || !resolveProfile(profileId))
|
|
126
|
+
diagnostics.push({ code: "WorkflowRuntimeError.unknownAgentProfile", message: `Agent node '${targetNodeId}' references unavailable profile '${profileId || "<missing>"}'.`, severity: "error", nodeId: targetNodeId, path: `$.nodes.${targetNodeId}.profile.id` });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return diagnostics;
|
|
130
|
+
}
|
|
131
|
+
function enqueueOutgoingEdges(definition, runId, sourceAttempt, queue, transfers) {
|
|
132
|
+
const edges = objectValue(definition.edges) ?? {};
|
|
133
|
+
for (const [edgeId, rawEdge] of Object.entries(edges).sort(([left], [right]) => left.localeCompare(right))) {
|
|
134
|
+
const edge = objectValue(rawEdge);
|
|
135
|
+
const from = objectValue(edge?.from);
|
|
136
|
+
const to = objectValue(edge?.to);
|
|
137
|
+
if (from?.nodeId !== sourceAttempt.nodeId || typeof to?.nodeId !== "string")
|
|
138
|
+
continue;
|
|
139
|
+
const payload = sourceAttempt.output ?? "";
|
|
140
|
+
transfers.push({ id: `wet_${randomUUID()}`, workflowRunId: runId, edgeId, sourceNodeAttemptId: sourceAttempt.id, targetNodeId: to.nodeId, status: "transferred", payload, createdAt: new Date().toISOString() });
|
|
141
|
+
queue.push({ nodeId: to.nodeId, input: payload, viaEdgeId: edgeId, sourceAttemptId: sourceAttempt.id });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function runAgentNode(options) {
|
|
145
|
+
const startedAt = new Date().toISOString();
|
|
146
|
+
const node = objectValue(objectValue(options.definition.nodes)?.[options.nodeId]) ?? {};
|
|
147
|
+
const profileId = stringValue(objectValue(node.profile)?.id) || "base";
|
|
148
|
+
const resolvedProfile = options.resolveProfile(profileId) ?? profileId;
|
|
149
|
+
const attempt = { id: `wna_${randomUUID()}`, workflowRunId: options.runId, nodeId: options.nodeId, kind: "agent", status: "completed", input: options.input, startedAt };
|
|
150
|
+
try {
|
|
151
|
+
const session = options.channelContext.createSession({
|
|
152
|
+
channel: options.channel,
|
|
153
|
+
kind: "workflow-agent",
|
|
154
|
+
profile: resolvedProfile,
|
|
155
|
+
workspace: options.defaultWorkspace,
|
|
156
|
+
title: `${stringValue(options.definition.id) || "Workflow"} · ${stringValue(node.label) || options.nodeId}`,
|
|
157
|
+
metadata: {
|
|
158
|
+
workflowRunId: options.runId,
|
|
159
|
+
workflowRunSource: "manual.editor",
|
|
160
|
+
...(options.draftId ? { workflowDraftId: options.draftId } : {}),
|
|
161
|
+
workflowTriggerNodeId: options.triggerNodeId,
|
|
162
|
+
workflowNodeId: options.nodeId,
|
|
163
|
+
workflowNodeAttemptId: attempt.id,
|
|
164
|
+
workflowNodeLabel: stringValue(node.label) || options.nodeId,
|
|
165
|
+
...(options.actorId ? { actorId: options.actorId } : {}),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
options.onSessionCreated?.(session);
|
|
169
|
+
attempt.piboSessionId = session.id;
|
|
170
|
+
const prompt = renderPromptTemplate(stringValue(node.promptTemplate) || "{{input}}", options.input);
|
|
171
|
+
const output = await emitMessageAndWaitForAssistant(options.channelContext, session.id, prompt);
|
|
172
|
+
attempt.output = output;
|
|
173
|
+
attempt.completedAt = new Date().toISOString();
|
|
174
|
+
return { ok: true, attempt, output };
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
attempt.status = "failed";
|
|
178
|
+
attempt.failedAt = new Date().toISOString();
|
|
179
|
+
attempt.error = { code: "WorkflowRuntimeError.agentNodeFailed", message: error instanceof Error ? error.message : "Agent node failed with a non-Error value." };
|
|
180
|
+
return { ok: false, attempt, diagnostics: [{ code: attempt.error.code, message: attempt.error.message, severity: "error", nodeId: options.nodeId, path: `$.nodes.${options.nodeId}` }] };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function emitMessageAndWaitForAssistant(channelContext, piboSessionId, text, timeoutMs = 120000) {
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const eventId = `wfm_${randomUUID()}`;
|
|
186
|
+
let settled = false;
|
|
187
|
+
let timeout;
|
|
188
|
+
let unsubscribe = () => { };
|
|
189
|
+
const finish = (value) => {
|
|
190
|
+
if (settled)
|
|
191
|
+
return;
|
|
192
|
+
settled = true;
|
|
193
|
+
clearTimeout(timeout);
|
|
194
|
+
unsubscribe();
|
|
195
|
+
if (value instanceof Error)
|
|
196
|
+
reject(value);
|
|
197
|
+
else
|
|
198
|
+
resolve(value);
|
|
199
|
+
};
|
|
200
|
+
timeout = setTimeout(() => finish(new Error(`Timed out waiting for assistant reply from workflow agent session '${piboSessionId}'.`)), timeoutMs);
|
|
201
|
+
unsubscribe = channelContext.subscribe((event) => {
|
|
202
|
+
if (event.piboSessionId !== piboSessionId)
|
|
203
|
+
return;
|
|
204
|
+
if (event.type === "assistant_message") {
|
|
205
|
+
if (event.eventId === eventId)
|
|
206
|
+
finish(event.text);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (event.type === "session_error" && event.eventId === eventId)
|
|
210
|
+
finish(new Error(event.error));
|
|
211
|
+
});
|
|
212
|
+
channelContext.emit({ type: "message", piboSessionId, id: eventId, text, source: "actor" }).catch(finish);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function renderPromptTemplate(template, input) {
|
|
216
|
+
return template.replace(/{{\s*input\s*}}/g, input);
|
|
217
|
+
}
|
|
218
|
+
function objectValue(value) {
|
|
219
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
220
|
+
}
|
|
221
|
+
function stringValue(value) {
|
|
222
|
+
return typeof value === "string" ? value : undefined;
|
|
223
|
+
}
|