forge-openclaw-plugin 0.2.26 → 0.2.27
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 +59 -3
- package/dist/assets/{board-ta0rUHOf.js → board-C6jCchjI.js} +2 -2
- package/dist/assets/{board-ta0rUHOf.js.map → board-C6jCchjI.js.map} +1 -1
- package/dist/assets/index-DVvS8iiU.css +1 -0
- package/dist/assets/index-zYB-9Dfo.js +85 -0
- package/dist/assets/index-zYB-9Dfo.js.map +1 -0
- package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js +2 -0
- package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js.map +1 -0
- package/dist/assets/{motion-fBKPB6yw.js → motion-DFHrH2rd.js} +2 -2
- package/dist/assets/{motion-fBKPB6yw.js.map → motion-DFHrH2rd.js.map} +1 -1
- package/dist/assets/{table-C-IGTQni.js → table-ZL7Di_u3.js} +2 -2
- package/dist/assets/{table-C-IGTQni.js.map → table-ZL7Di_u3.js.map} +1 -1
- package/dist/assets/{ui-DInOpaYF.js → ui-CKNPpz7q.js} +2 -2
- package/dist/assets/{ui-DInOpaYF.js.map → ui-CKNPpz7q.js.map} +1 -1
- package/dist/assets/vendor-DoNZuFhn.js +1247 -0
- package/dist/assets/vendor-DoNZuFhn.js.map +1 -0
- package/dist/index.html +7 -7
- package/dist/openclaw/local-runtime.js +16 -0
- package/dist/openclaw/routes.d.ts +27 -0
- package/dist/openclaw/routes.js +16 -12
- package/dist/server/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
- package/dist/server/server/migrations/038_data_management_settings.sql +11 -0
- package/dist/server/server/migrations/039_life_force_and_action_points.sql +114 -0
- package/dist/server/server/migrations/040_screen_time_domain.sql +89 -0
- package/dist/server/server/migrations/041_companion_source_states.sql +21 -0
- package/dist/server/server/migrations/042_movement_boxes.sql +47 -0
- package/dist/server/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
- package/dist/server/server/src/app.js +1684 -117
- package/dist/server/server/src/connectors/box-registry.js +44 -9
- package/dist/server/server/src/data-management-types.js +107 -0
- package/dist/server/server/src/db.js +68 -4
- package/dist/server/server/src/demo-data.js +2 -2
- package/dist/server/server/src/health.js +702 -18
- package/dist/server/server/src/managers/platform/llm-manager.js +7 -4
- package/dist/server/server/src/managers/platform/mock-workbench-provider.js +149 -0
- package/dist/server/server/src/managers/platform/secrets-manager.js +18 -1
- package/dist/server/server/src/managers/runtime.js +9 -0
- package/dist/server/server/src/movement.js +1971 -112
- package/dist/server/server/src/openapi.js +489 -1
- package/dist/server/server/src/psyche-types.js +9 -1
- package/dist/server/server/src/repositories/activity-events.js +8 -0
- package/dist/server/server/src/repositories/ai-connectors.js +522 -74
- package/dist/server/server/src/repositories/habits.js +37 -1
- package/dist/server/server/src/repositories/model-settings.js +13 -3
- package/dist/server/server/src/repositories/notes.js +3 -0
- package/dist/server/server/src/repositories/settings.js +380 -18
- package/dist/server/server/src/repositories/tasks.js +170 -10
- package/dist/server/server/src/runtime-data-root.js +82 -0
- package/dist/server/server/src/screen-time.js +802 -0
- package/dist/server/server/src/services/data-management.js +788 -0
- package/dist/server/server/src/services/entity-crud.js +205 -2
- package/dist/server/server/src/services/knowledge-graph.js +1455 -0
- package/dist/server/server/src/services/life-force-model.js +197 -0
- package/dist/server/server/src/services/life-force.js +1270 -0
- package/dist/server/server/src/services/psyche-observation-calendar.js +383 -16
- package/dist/server/server/src/types.js +286 -13
- package/dist/server/server/src/web.js +228 -13
- package/dist/server/src/components/customization/utility-widgets.js +136 -27
- package/dist/server/src/components/ui/info-tooltip.js +25 -0
- package/dist/server/src/components/workbench-boxes/calendar/calendar-boxes.js +78 -0
- package/dist/server/src/components/workbench-boxes/goals/goals-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/habits/habits-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/health/health-boxes.js +63 -8
- package/dist/server/src/components/workbench-boxes/insights/insights-boxes.js +50 -0
- package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +62 -54
- package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +18 -8
- package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +56 -38
- package/dist/server/src/components/workbench-boxes/overview/overview-boxes.js +65 -0
- package/dist/server/src/components/workbench-boxes/preferences/preferences-boxes.js +78 -0
- package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +35 -30
- package/dist/server/src/components/workbench-boxes/psyche/psyche-boxes.js +88 -0
- package/dist/server/src/components/workbench-boxes/questionnaires/questionnaires-boxes.js +61 -0
- package/dist/server/src/components/workbench-boxes/review/review-boxes.js +53 -0
- package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +3 -1
- package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +39 -3
- package/dist/server/src/components/workbench-boxes/strategies/strategies-boxes.js +62 -0
- package/dist/server/src/components/workbench-boxes/tasks/tasks-boxes.js +76 -0
- package/dist/server/src/components/workbench-boxes/today/today-boxes.js +47 -32
- package/dist/server/src/components/workbench-boxes/wiki/wiki-boxes.js +60 -0
- package/dist/server/src/lib/api.js +280 -21
- package/dist/server/src/lib/data-management-types.js +1 -0
- package/dist/server/src/lib/entity-visuals.js +279 -0
- package/dist/server/src/lib/knowledge-graph-types.js +276 -0
- package/dist/server/src/lib/knowledge-graph.js +470 -0
- package/dist/server/src/lib/schemas.js +4 -0
- package/dist/server/src/lib/snapshot-normalizer.js +43 -1
- package/dist/server/src/lib/workbench/contracts.js +229 -0
- package/dist/server/src/lib/workbench/nodes.js +200 -0
- package/dist/server/src/lib/workbench/registry.js +52 -5
- package/dist/server/src/lib/workbench/runtime.js +254 -38
- package/dist/server/src/lib/workbench/tool-catalog.js +68 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
- package/server/migrations/038_data_management_settings.sql +11 -0
- package/server/migrations/039_life_force_and_action_points.sql +114 -0
- package/server/migrations/040_screen_time_domain.sql +89 -0
- package/server/migrations/041_companion_source_states.sql +21 -0
- package/server/migrations/042_movement_boxes.sql +47 -0
- package/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
- package/skills/forge-openclaw/SKILL.md +24 -11
- package/skills/forge-openclaw/entity_conversation_playbooks.md +210 -34
- package/skills/forge-openclaw/psyche_entity_playbooks.md +113 -17
- package/dist/assets/index-Ro0ZF_az.css +0 -1
- package/dist/assets/index-ytlpSj23.js +0 -79
- package/dist/assets/index-ytlpSj23.js.map +0 -1
- package/dist/assets/vendor-lE3tZJcC.js +0 -876
- package/dist/assets/vendor-lE3tZJcC.js.map +0 -1
|
@@ -8,6 +8,7 @@ import { createAiConnectorSchema, aiConnectorConversationSchema, aiConnectorRunR
|
|
|
8
8
|
import { FORGE_DEFAULT_AGENT_ID, getAiModelConnectionById, listAiModelConnections, readModelConnectionCredential } from "./model-settings.js";
|
|
9
9
|
import { getAiProcessorById, listAiProcessorLinks, listAiProcessors } from "./ai-processors.js";
|
|
10
10
|
import { buildConnectorOutputCatalogEntry, executeForgeBoxTool, resolveForgeBoxSnapshot } from "../connectors/box-registry.js";
|
|
11
|
+
import { normalizeWorkbenchPortDefinition } from "../../../src/lib/workbench/nodes.js";
|
|
11
12
|
const execFile = promisify(execFileCallback);
|
|
12
13
|
const MAX_TOOL_STEPS = 6;
|
|
13
14
|
const MAX_RUN_HISTORY = 20;
|
|
@@ -164,7 +165,7 @@ function buildDefaultGraph(kind, title) {
|
|
|
164
165
|
data: {
|
|
165
166
|
label: "Output",
|
|
166
167
|
description: "Published connector output.",
|
|
167
|
-
outputKey: "
|
|
168
|
+
outputKey: "answer",
|
|
168
169
|
enabledToolKeys: []
|
|
169
170
|
}
|
|
170
171
|
}
|
|
@@ -190,7 +191,7 @@ function ensurePublishedOutputs(connectorId, graph) {
|
|
|
190
191
|
buildConnectorOutputCatalogEntry({
|
|
191
192
|
connectorId,
|
|
192
193
|
title: "Connector",
|
|
193
|
-
outputId: "
|
|
194
|
+
outputId: "answer"
|
|
194
195
|
})
|
|
195
196
|
].map((entry) => ({
|
|
196
197
|
id: entry.id.replace(/^connector-output:/, ""),
|
|
@@ -213,6 +214,7 @@ function mapRun(row) {
|
|
|
213
214
|
mode: row.mode,
|
|
214
215
|
status: row.status,
|
|
215
216
|
userInput: row.user_input,
|
|
217
|
+
inputs: parseJson(row.inputs_json, {}),
|
|
216
218
|
context: parseJson(row.context_json, {}),
|
|
217
219
|
conversationId: row.conversation_id,
|
|
218
220
|
result: parseJson(row.result_json, null),
|
|
@@ -233,6 +235,8 @@ function mapConversation(row) {
|
|
|
233
235
|
});
|
|
234
236
|
}
|
|
235
237
|
function mapConnector(row) {
|
|
238
|
+
const rawGraph = parseJson(row.graph_json, { nodes: [], edges: [] });
|
|
239
|
+
const normalizedGraph = normalizeConnectorGraph(rawGraph);
|
|
236
240
|
return aiConnectorSchema.parse({
|
|
237
241
|
id: row.id,
|
|
238
242
|
slug: row.slug,
|
|
@@ -241,7 +245,8 @@ function mapConnector(row) {
|
|
|
241
245
|
kind: row.kind,
|
|
242
246
|
homeSurfaceId: row.home_surface_id,
|
|
243
247
|
endpointEnabled: row.endpoint_enabled === 1,
|
|
244
|
-
graph:
|
|
248
|
+
graph: normalizedGraph,
|
|
249
|
+
publicInputs: parseJson(row.public_inputs_json, []),
|
|
245
250
|
publishedOutputs: parseJson(row.published_outputs_json, []),
|
|
246
251
|
lastRun: parseJson(row.last_run_json, null),
|
|
247
252
|
legacyProcessorId: row.legacy_processor_id,
|
|
@@ -255,6 +260,37 @@ export function listAiConnectorRuns(connectorId) {
|
|
|
255
260
|
.all(connectorId, MAX_RUN_HISTORY);
|
|
256
261
|
return rows.map(mapRun);
|
|
257
262
|
}
|
|
263
|
+
export function getAiConnectorRunById(connectorId, runId) {
|
|
264
|
+
const row = getDatabase()
|
|
265
|
+
.prepare(`SELECT * FROM ai_connector_runs WHERE connector_id = ? AND id = ?`)
|
|
266
|
+
.get(connectorId, runId);
|
|
267
|
+
return row ? mapRun(row) : null;
|
|
268
|
+
}
|
|
269
|
+
export function getAiConnectorRunNodeResults(connectorId, runId) {
|
|
270
|
+
const run = getAiConnectorRunById(connectorId, runId);
|
|
271
|
+
return run?.result?.nodeResults ?? null;
|
|
272
|
+
}
|
|
273
|
+
export function getAiConnectorRunNodeResult(connectorId, runId, nodeId) {
|
|
274
|
+
const results = getAiConnectorRunNodeResults(connectorId, runId);
|
|
275
|
+
if (!results) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return results.find((entry) => entry.nodeId === nodeId) ?? null;
|
|
279
|
+
}
|
|
280
|
+
export function getLatestAiConnectorNodeOutput(connectorId, nodeId) {
|
|
281
|
+
const run = listAiConnectorRuns(connectorId).find((entry) => entry.status === "completed" && entry.result);
|
|
282
|
+
if (!run?.result) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const nodeResult = run.result.nodeResults.find((entry) => entry.nodeId === nodeId) ?? null;
|
|
286
|
+
if (!nodeResult) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
run,
|
|
291
|
+
nodeResult
|
|
292
|
+
};
|
|
293
|
+
}
|
|
258
294
|
export function getAiConnectorConversationById(conversationId) {
|
|
259
295
|
const row = getDatabase()
|
|
260
296
|
.prepare(`SELECT * FROM ai_connector_conversations WHERE id = ?`)
|
|
@@ -288,20 +324,21 @@ function updateConnectorLastRun(connectorId, run) {
|
|
|
288
324
|
function insertRun(input) {
|
|
289
325
|
getDatabase()
|
|
290
326
|
.prepare(`INSERT INTO ai_connector_runs (
|
|
291
|
-
id, connector_id, mode, status, user_input, context_json, conversation_id, result_json, error, created_at, completed_at
|
|
292
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
327
|
+
id, connector_id, mode, status, user_input, inputs_json, context_json, conversation_id, result_json, error, created_at, completed_at
|
|
328
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
293
329
|
ON CONFLICT(id) DO UPDATE SET
|
|
294
330
|
connector_id = excluded.connector_id,
|
|
295
331
|
mode = excluded.mode,
|
|
296
332
|
status = excluded.status,
|
|
297
333
|
user_input = excluded.user_input,
|
|
334
|
+
inputs_json = excluded.inputs_json,
|
|
298
335
|
context_json = excluded.context_json,
|
|
299
336
|
conversation_id = excluded.conversation_id,
|
|
300
337
|
result_json = excluded.result_json,
|
|
301
338
|
error = excluded.error,
|
|
302
339
|
created_at = excluded.created_at,
|
|
303
340
|
completed_at = excluded.completed_at`)
|
|
304
|
-
.run(input.id, input.connectorId, input.mode, input.status, input.userInput, JSON.stringify(input.context), input.conversationId, input.result ? JSON.stringify(input.result) : null, input.error, input.createdAt, input.completedAt);
|
|
341
|
+
.run(input.id, input.connectorId, input.mode, input.status, input.userInput, JSON.stringify(input.inputs), JSON.stringify(input.context), input.conversationId, input.result ? JSON.stringify(input.result) : null, input.error, input.createdAt, input.completedAt);
|
|
305
342
|
updateConnectorLastRun(input.connectorId, input);
|
|
306
343
|
return input;
|
|
307
344
|
}
|
|
@@ -316,7 +353,33 @@ function resolveAllowedPath(inputPath) {
|
|
|
316
353
|
}
|
|
317
354
|
function tryParseStructuredAgentResponse(value) {
|
|
318
355
|
try {
|
|
319
|
-
|
|
356
|
+
const parsed = JSON.parse(value);
|
|
357
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
const action = parsed.action;
|
|
361
|
+
if (action === "final") {
|
|
362
|
+
const text = parsed.text;
|
|
363
|
+
return {
|
|
364
|
+
action,
|
|
365
|
+
text: typeof text === "string" ? text : value
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (action === "tool") {
|
|
369
|
+
const tool = parsed.tool;
|
|
370
|
+
const args = parsed.args;
|
|
371
|
+
if (typeof tool !== "string" || tool.trim().length === 0) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
action,
|
|
376
|
+
tool,
|
|
377
|
+
args: args && typeof args === "object" && !Array.isArray(args)
|
|
378
|
+
? args
|
|
379
|
+
: {}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
320
383
|
}
|
|
321
384
|
catch {
|
|
322
385
|
return null;
|
|
@@ -351,30 +414,95 @@ function coerceText(value) {
|
|
|
351
414
|
return "";
|
|
352
415
|
}
|
|
353
416
|
}
|
|
354
|
-
function
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
417
|
+
function coerceJsonObject(value) {
|
|
418
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
419
|
+
? value
|
|
420
|
+
: null;
|
|
421
|
+
}
|
|
422
|
+
function validatePortValueType(port, value) {
|
|
423
|
+
switch (port.kind) {
|
|
424
|
+
case "text":
|
|
425
|
+
case "markdown":
|
|
426
|
+
case "summary":
|
|
427
|
+
return typeof value === "string";
|
|
428
|
+
case "number":
|
|
429
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
430
|
+
case "boolean":
|
|
431
|
+
return typeof value === "boolean";
|
|
432
|
+
case "array":
|
|
433
|
+
case "entity_list":
|
|
434
|
+
case "record_list":
|
|
435
|
+
return Array.isArray(value);
|
|
436
|
+
case "object":
|
|
437
|
+
case "json":
|
|
438
|
+
case "record":
|
|
439
|
+
case "context":
|
|
440
|
+
case "filters":
|
|
441
|
+
case "metrics":
|
|
442
|
+
case "timeline":
|
|
443
|
+
case "selection":
|
|
444
|
+
case "entity":
|
|
445
|
+
return Boolean(value) && typeof value === "object";
|
|
446
|
+
default:
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function normalizePublicInputBindings(connector, publicInput) {
|
|
451
|
+
if (publicInput.bindings.length > 0) {
|
|
452
|
+
return publicInput.bindings;
|
|
453
|
+
}
|
|
454
|
+
return connector.graph.nodes.flatMap((node) => {
|
|
455
|
+
const inputs = defaultInputsForNode(node);
|
|
456
|
+
const params = node.data.params ?? [];
|
|
457
|
+
const matches = [];
|
|
458
|
+
if (inputs.some((entry) => entry.key === publicInput.key)) {
|
|
459
|
+
matches.push({
|
|
460
|
+
nodeId: node.id,
|
|
461
|
+
targetKey: publicInput.key,
|
|
462
|
+
targetKind: "input"
|
|
463
|
+
});
|
|
359
464
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
465
|
+
if (params.some((entry) => entry.key === publicInput.key)) {
|
|
466
|
+
matches.push({
|
|
467
|
+
nodeId: node.id,
|
|
468
|
+
targetKey: publicInput.key,
|
|
469
|
+
targetKind: "param"
|
|
470
|
+
});
|
|
364
471
|
}
|
|
365
|
-
|
|
366
|
-
|
|
472
|
+
return matches;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function buildPublicInputValue(publicInput, value) {
|
|
476
|
+
return {
|
|
477
|
+
sourceNodeId: `flow_input:${publicInput.key}`,
|
|
478
|
+
sourceHandle: publicInput.key,
|
|
479
|
+
targetHandle: publicInput.key,
|
|
480
|
+
text: coerceText(value),
|
|
481
|
+
json: coerceJsonObject(value)
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function buildOutputMap(primaryText, primaryJson, outputs = []) {
|
|
485
|
+
const outputMap = {};
|
|
486
|
+
const declaredOutputs = outputs.length > 0 ? outputs : [{ key: "summary" }];
|
|
487
|
+
declaredOutputs.forEach((output, index) => {
|
|
488
|
+
const value = primaryJson && output.key in primaryJson
|
|
489
|
+
? primaryJson[output.key]
|
|
490
|
+
: index === 0 || output.key === "summary"
|
|
491
|
+
? primaryText
|
|
492
|
+
: null;
|
|
493
|
+
outputMap[output.key] = {
|
|
367
494
|
text: coerceText(value),
|
|
368
495
|
json: value && typeof value === "object" && !Array.isArray(value)
|
|
369
496
|
? value
|
|
370
497
|
: null
|
|
371
498
|
};
|
|
372
|
-
}
|
|
499
|
+
});
|
|
373
500
|
return outputMap;
|
|
374
501
|
}
|
|
375
502
|
function readOutputSelection(value, handle) {
|
|
376
|
-
if (!handle
|
|
377
|
-
|
|
503
|
+
if (!handle) {
|
|
504
|
+
const lead = Object.values(value.outputMap)[0];
|
|
505
|
+
return lead ?? { text: value.text, json: value.json };
|
|
378
506
|
}
|
|
379
507
|
const selected = value.outputMap[handle];
|
|
380
508
|
if (selected) {
|
|
@@ -391,6 +519,148 @@ function readOutputSelection(value, handle) {
|
|
|
391
519
|
}
|
|
392
520
|
return { text: value.text, json: value.json };
|
|
393
521
|
}
|
|
522
|
+
function defaultOutputsForNode(node) {
|
|
523
|
+
if (node.data.outputs?.length) {
|
|
524
|
+
return node.data.outputs.map((port) => normalizeWorkbenchPortDefinition(port));
|
|
525
|
+
}
|
|
526
|
+
switch (node.type) {
|
|
527
|
+
case "user_input":
|
|
528
|
+
return [{ key: "message", label: "Message", kind: "text" }];
|
|
529
|
+
case "value":
|
|
530
|
+
return [{ key: "value", label: "Value", kind: "record" }];
|
|
531
|
+
case "merge":
|
|
532
|
+
return [{ key: "merged", label: "Merged context", kind: "context" }];
|
|
533
|
+
case "template":
|
|
534
|
+
return [{ key: "rendered", label: "Rendered output", kind: "markdown" }];
|
|
535
|
+
case "pick_key":
|
|
536
|
+
return [{ key: "selected", label: "Selected value", kind: "record" }];
|
|
537
|
+
case "functor":
|
|
538
|
+
case "chat":
|
|
539
|
+
return [{ key: "answer", label: "Answer", kind: "markdown" }];
|
|
540
|
+
case "output":
|
|
541
|
+
return [{ key: node.data.outputKey?.trim() || "result", label: "Published result", kind: "record" }];
|
|
542
|
+
case "box":
|
|
543
|
+
case "box_input":
|
|
544
|
+
return [{ key: "summary", label: "Summary", kind: "summary" }];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function defaultInputsForNode(node) {
|
|
548
|
+
if (node.data.inputs?.length) {
|
|
549
|
+
return node.data.inputs.map((port) => normalizeWorkbenchPortDefinition(port));
|
|
550
|
+
}
|
|
551
|
+
switch (node.type) {
|
|
552
|
+
case "functor":
|
|
553
|
+
case "chat":
|
|
554
|
+
return [{ key: "input", label: "Flow input", kind: "context" }];
|
|
555
|
+
case "merge":
|
|
556
|
+
return [
|
|
557
|
+
{ key: "left", label: "Left input", kind: "context" },
|
|
558
|
+
{ key: "right", label: "Right input", kind: "context" }
|
|
559
|
+
];
|
|
560
|
+
case "template":
|
|
561
|
+
return [{ key: "input", label: "Template input", kind: "context" }];
|
|
562
|
+
case "pick_key":
|
|
563
|
+
return [{ key: "object", label: "Source object", kind: "object" }];
|
|
564
|
+
case "output":
|
|
565
|
+
return [{ key: "result", label: "Published result", kind: "record" }];
|
|
566
|
+
default:
|
|
567
|
+
return [];
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function normalizeConnectorNodeContracts(node) {
|
|
571
|
+
const normalizePorts = (ports, direction) => ports.map((port) => {
|
|
572
|
+
const normalized = normalizeWorkbenchPortDefinition(port);
|
|
573
|
+
if (normalized.key !== "primary") {
|
|
574
|
+
return normalized;
|
|
575
|
+
}
|
|
576
|
+
const nextKey = direction === "output"
|
|
577
|
+
? node.type === "functor" || node.type === "chat"
|
|
578
|
+
? "answer"
|
|
579
|
+
: node.type === "box" || node.type === "box_input"
|
|
580
|
+
? "summary"
|
|
581
|
+
: node.type === "value"
|
|
582
|
+
? "value"
|
|
583
|
+
: node.type === "merge"
|
|
584
|
+
? "merged"
|
|
585
|
+
: node.type === "template"
|
|
586
|
+
? "rendered"
|
|
587
|
+
: node.type === "pick_key"
|
|
588
|
+
? "selected"
|
|
589
|
+
: "result"
|
|
590
|
+
: node.type === "functor" || node.type === "chat"
|
|
591
|
+
? "input"
|
|
592
|
+
: node.type === "output"
|
|
593
|
+
? "result"
|
|
594
|
+
: normalized.key;
|
|
595
|
+
return normalizeWorkbenchPortDefinition({
|
|
596
|
+
...normalized,
|
|
597
|
+
key: nextKey,
|
|
598
|
+
kind: nextKey === normalized.key ? normalized.kind : undefined
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
const normalizedInputs = normalizePorts(defaultInputsForNode(node).length > 0 ? defaultInputsForNode(node) : node.data.inputs ?? [], "input");
|
|
602
|
+
const normalizedOutputs = defaultOutputsForNode({
|
|
603
|
+
...node,
|
|
604
|
+
data: {
|
|
605
|
+
...node.data,
|
|
606
|
+
outputs: normalizePorts(node.data.outputs ?? [], "output")
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
const normalizedOutputKey = (() => {
|
|
610
|
+
const current = node.data.outputKey?.trim();
|
|
611
|
+
if (!current || current === "primary") {
|
|
612
|
+
return normalizedOutputs[0]?.key ?? "";
|
|
613
|
+
}
|
|
614
|
+
if (normalizedOutputs.some((output) => output.key === current)) {
|
|
615
|
+
return current;
|
|
616
|
+
}
|
|
617
|
+
return normalizedOutputs[0]?.key ?? current;
|
|
618
|
+
})();
|
|
619
|
+
return {
|
|
620
|
+
...node,
|
|
621
|
+
data: {
|
|
622
|
+
...node.data,
|
|
623
|
+
inputs: normalizedInputs,
|
|
624
|
+
outputs: normalizedOutputs,
|
|
625
|
+
outputKey: normalizedOutputKey
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function canonicalEdgeHandle(handle, ports, preferred) {
|
|
630
|
+
if (ports.length === 0) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
if (!handle || handle === "primary") {
|
|
634
|
+
if (preferred && ports.some((port) => port.key === preferred)) {
|
|
635
|
+
return preferred;
|
|
636
|
+
}
|
|
637
|
+
return ports[0]?.key ?? null;
|
|
638
|
+
}
|
|
639
|
+
if (ports.some((port) => port.key === handle)) {
|
|
640
|
+
return handle;
|
|
641
|
+
}
|
|
642
|
+
if (preferred && ports.some((port) => port.key === preferred)) {
|
|
643
|
+
return preferred;
|
|
644
|
+
}
|
|
645
|
+
return ports[0]?.key ?? null;
|
|
646
|
+
}
|
|
647
|
+
function normalizeConnectorGraph(graph) {
|
|
648
|
+
const normalizedNodes = graph.nodes.map((node) => normalizeConnectorNodeContracts(node));
|
|
649
|
+
const nodeMap = new Map(normalizedNodes.map((node) => [node.id, node]));
|
|
650
|
+
const normalizedEdges = graph.edges.map((edge) => {
|
|
651
|
+
const sourceNode = nodeMap.get(edge.source);
|
|
652
|
+
const targetNode = nodeMap.get(edge.target);
|
|
653
|
+
return {
|
|
654
|
+
...edge,
|
|
655
|
+
sourceHandle: canonicalEdgeHandle(edge.sourceHandle, sourceNode?.data.outputs ?? [], sourceNode?.data.outputs?.[0]?.key),
|
|
656
|
+
targetHandle: canonicalEdgeHandle(edge.targetHandle, targetNode?.data.inputs ?? [], targetNode?.data.inputs?.[0]?.key)
|
|
657
|
+
};
|
|
658
|
+
});
|
|
659
|
+
return {
|
|
660
|
+
nodes: normalizedNodes,
|
|
661
|
+
edges: normalizedEdges
|
|
662
|
+
};
|
|
663
|
+
}
|
|
394
664
|
async function executeMachineTool(tool, args) {
|
|
395
665
|
if (tool === "machine_read_file") {
|
|
396
666
|
const targetPath = typeof args.path === "string" ? resolveAllowedPath(args.path) : null;
|
|
@@ -429,9 +699,15 @@ function getConversationBasePrompt(input) {
|
|
|
429
699
|
return [
|
|
430
700
|
input.node.data.prompt?.trim() || "",
|
|
431
701
|
input.userInput ? `User input:\n${input.userInput}` : "",
|
|
702
|
+
input.conversation && input.node.type === "chat" && input.conversation.transcript.length > 0
|
|
703
|
+
? `Conversation history:\n${input.conversation.transcript
|
|
704
|
+
.slice(-8)
|
|
705
|
+
.map((entry) => `${entry.role}: ${entry.text}`)
|
|
706
|
+
.join("\n")}`
|
|
707
|
+
: "",
|
|
432
708
|
input.upstream.length > 0
|
|
433
709
|
? `Linked inputs:\n${input.upstream
|
|
434
|
-
.map((entry, index) => `Input ${index + 1}:\n${entry.text}${entry.json ? `\nJSON: ${JSON.stringify(entry.json)}` : ""}`)
|
|
710
|
+
.map((entry, index) => `Input ${entry.targetHandle || index + 1}:\n${entry.text}${entry.json ? `\nJSON: ${JSON.stringify(entry.json)}` : ""}`)
|
|
435
711
|
.join("\n\n")}`
|
|
436
712
|
: "",
|
|
437
713
|
input.transcript.length > 0 ? `Tool transcript:\n${input.transcript.join("\n\n")}` : ""
|
|
@@ -514,7 +790,7 @@ function resolveConnectorModelProfile(node, secrets) {
|
|
|
514
790
|
: credential?.kind === "oauth"
|
|
515
791
|
? credential.access
|
|
516
792
|
: null;
|
|
517
|
-
if (!explicitApiKey) {
|
|
793
|
+
if (!explicitApiKey && fallbackConnection.provider !== "mock") {
|
|
518
794
|
throw new Error("The selected connector model connection is missing a credential.");
|
|
519
795
|
}
|
|
520
796
|
const profile = {
|
|
@@ -532,7 +808,7 @@ function resolveConnectorModelProfile(node, secrets) {
|
|
|
532
808
|
};
|
|
533
809
|
return {
|
|
534
810
|
profile,
|
|
535
|
-
apiKey: explicitApiKey
|
|
811
|
+
apiKey: explicitApiKey ?? "mock"
|
|
536
812
|
};
|
|
537
813
|
}
|
|
538
814
|
async function runModelNode(input) {
|
|
@@ -555,7 +831,7 @@ async function runModelNode(input) {
|
|
|
555
831
|
'For a final answer return {"action":"final","text":"..."}',
|
|
556
832
|
'For a tool call return {"action":"tool","tool":"tool_key","args":{...}}',
|
|
557
833
|
`Available tools: ${activeTools
|
|
558
|
-
.map((tool) => `${tool.key} (${tool.description})`)
|
|
834
|
+
.map((tool) => `${tool.key} (${tool.description})${tool.argsSchema ? ` args=${JSON.stringify(tool.argsSchema)}` : ""}`)
|
|
559
835
|
.join("; ")}.`
|
|
560
836
|
].join(" ")
|
|
561
837
|
: "Return only the final answer text."
|
|
@@ -567,7 +843,8 @@ async function runModelNode(input) {
|
|
|
567
843
|
node: input.node,
|
|
568
844
|
userInput: input.userInput,
|
|
569
845
|
upstream: input.upstream,
|
|
570
|
-
transcript
|
|
846
|
+
transcript,
|
|
847
|
+
conversation: input.conversation
|
|
571
848
|
});
|
|
572
849
|
let rawText = "";
|
|
573
850
|
if (conversationAware && isOpenAiFamily(profile)) {
|
|
@@ -593,7 +870,8 @@ async function runModelNode(input) {
|
|
|
593
870
|
text: rawText.trim(),
|
|
594
871
|
json: tryParseJsonObject(rawText.trim()),
|
|
595
872
|
conversationId,
|
|
596
|
-
logs: transcript
|
|
873
|
+
logs: transcript,
|
|
874
|
+
availableTools: []
|
|
597
875
|
};
|
|
598
876
|
}
|
|
599
877
|
const structured = tryParseStructuredAgentResponse(rawText.trim());
|
|
@@ -602,7 +880,8 @@ async function runModelNode(input) {
|
|
|
602
880
|
text: structured?.text?.trim() || rawText.trim(),
|
|
603
881
|
json: tryParseJsonObject(structured?.text?.trim() || rawText.trim()),
|
|
604
882
|
conversationId,
|
|
605
|
-
logs: transcript
|
|
883
|
+
logs: transcript,
|
|
884
|
+
availableTools: activeTools.map((tool) => tool.key)
|
|
606
885
|
};
|
|
607
886
|
}
|
|
608
887
|
const toolResult = structured.tool.startsWith("machine_")
|
|
@@ -619,10 +898,14 @@ async function runModelNode(input) {
|
|
|
619
898
|
text: "Connector stopped after reaching the maximum tool step count.",
|
|
620
899
|
json: null,
|
|
621
900
|
conversationId,
|
|
622
|
-
logs: transcript
|
|
901
|
+
logs: transcript,
|
|
902
|
+
availableTools: activeTools.map((tool) => tool.key)
|
|
623
903
|
};
|
|
624
904
|
}
|
|
625
905
|
function validateConnectorGraph(graph) {
|
|
906
|
+
if (graph.nodes.length === 0) {
|
|
907
|
+
throw new Error("Connector graph has no nodes yet.");
|
|
908
|
+
}
|
|
626
909
|
const nodeIds = new Set(graph.nodes.map((node) => node.id));
|
|
627
910
|
for (const edge of graph.edges) {
|
|
628
911
|
if (!nodeIds.has(edge.source) || !nodeIds.has(edge.target)) {
|
|
@@ -654,8 +937,44 @@ function validateConnectorGraph(graph) {
|
|
|
654
937
|
for (const node of graph.nodes) {
|
|
655
938
|
visit(node.id);
|
|
656
939
|
}
|
|
940
|
+
const incomingCounts = new Map();
|
|
941
|
+
const outgoingCounts = new Map();
|
|
942
|
+
for (const edge of graph.edges) {
|
|
943
|
+
incomingCounts.set(edge.target, (incomingCounts.get(edge.target) ?? 0) + 1);
|
|
944
|
+
outgoingCounts.set(edge.source, (outgoingCounts.get(edge.source) ?? 0) + 1);
|
|
945
|
+
}
|
|
946
|
+
const outputNodes = graph.nodes.filter((node) => node.type === "output");
|
|
947
|
+
if (outputNodes.length === 0) {
|
|
948
|
+
throw new Error("Connector graph is missing an output node.");
|
|
949
|
+
}
|
|
950
|
+
const disconnectedOutput = outputNodes.find((node) => (incomingCounts.get(node.id) ?? 0) === 0);
|
|
951
|
+
if (disconnectedOutput) {
|
|
952
|
+
throw new Error(`Output node "${disconnectedOutput.data.label || disconnectedOutput.id}" has no incoming connection.`);
|
|
953
|
+
}
|
|
954
|
+
const aiNodeMissingPrompt = graph.nodes.find((node) => (node.type === "functor" || node.type === "chat") &&
|
|
955
|
+
!(node.data.promptTemplate?.trim() || node.data.prompt?.trim()));
|
|
956
|
+
if (aiNodeMissingPrompt) {
|
|
957
|
+
throw new Error(`AI node "${aiNodeMissingPrompt.data.label || aiNodeMissingPrompt.id}" is missing a prompt.`);
|
|
958
|
+
}
|
|
959
|
+
const mergeNodeMissingInputs = graph.nodes.find((node) => node.type === "merge" && (incomingCounts.get(node.id) ?? 0) < 2);
|
|
960
|
+
if (mergeNodeMissingInputs) {
|
|
961
|
+
throw new Error(`Merge node "${mergeNodeMissingInputs.data.label || mergeNodeMissingInputs.id}" must receive both left and right inputs.`);
|
|
962
|
+
}
|
|
963
|
+
const templateNodeMissingTemplate = graph.nodes.find((node) => node.type === "template" && !(node.data.template ?? "").trim());
|
|
964
|
+
if (templateNodeMissingTemplate) {
|
|
965
|
+
throw new Error(`Template node "${templateNodeMissingTemplate.data.label || templateNodeMissingTemplate.id}" is missing its template string.`);
|
|
966
|
+
}
|
|
967
|
+
const pickKeyNodeMissingSelection = graph.nodes.find((node) => node.type === "pick_key" && !(node.data.selectedKey ?? "").trim());
|
|
968
|
+
if (pickKeyNodeMissingSelection) {
|
|
969
|
+
throw new Error(`Pick-key node "${pickKeyNodeMissingSelection.data.label || pickKeyNodeMissingSelection.id}" is missing the key it should select.`);
|
|
970
|
+
}
|
|
971
|
+
const isolatedNode = graph.nodes.find((node) => node.type !== "output" &&
|
|
972
|
+
(outgoingCounts.get(node.id) ?? 0) === 0);
|
|
973
|
+
if (isolatedNode) {
|
|
974
|
+
throw new Error(`Node "${isolatedNode.data.label || isolatedNode.id}" is not connected to anything downstream.`);
|
|
975
|
+
}
|
|
657
976
|
}
|
|
658
|
-
function buildOutputResult(connector, resolvedNodeValues) {
|
|
977
|
+
function buildOutputResult(connector, resolvedNodeValues, nodeResults) {
|
|
659
978
|
const outputs = Object.fromEntries(connector.publishedOutputs.map((output) => {
|
|
660
979
|
const nodeValue = resolvedNodeValues.get(output.nodeId);
|
|
661
980
|
return [
|
|
@@ -670,7 +989,8 @@ function buildOutputResult(connector, resolvedNodeValues) {
|
|
|
670
989
|
const first = connector.publishedOutputs[0];
|
|
671
990
|
return aiConnectorRunResultSchema.parse({
|
|
672
991
|
primaryText: first ? outputs[first.id]?.text ?? "" : "",
|
|
673
|
-
outputs
|
|
992
|
+
outputs,
|
|
993
|
+
nodeResults
|
|
674
994
|
});
|
|
675
995
|
}
|
|
676
996
|
function parseValueLiteral(valueType, valueLiteral) {
|
|
@@ -717,11 +1037,50 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
717
1037
|
}
|
|
718
1038
|
const values = new Map();
|
|
719
1039
|
const debugNodes = [];
|
|
1040
|
+
const nodeResults = [];
|
|
720
1041
|
const debugErrors = [];
|
|
721
1042
|
const outputNodes = connector.graph.nodes.filter((node) => node.type === "output");
|
|
722
1043
|
const activeConversation = parsedInput.conversationId
|
|
723
1044
|
? getAiConnectorConversationById(parsedInput.conversationId)
|
|
724
1045
|
: getAiConnectorConversationForConnector(connector.id);
|
|
1046
|
+
const publicInputValues = new Map();
|
|
1047
|
+
const nodePublicInputs = new Map();
|
|
1048
|
+
const nodePublicParams = new Map();
|
|
1049
|
+
for (const publicInput of connector.publicInputs) {
|
|
1050
|
+
const hasProvided = Object.prototype.hasOwnProperty.call(parsedInput.inputs, publicInput.key);
|
|
1051
|
+
const resolvedValue = hasProvided
|
|
1052
|
+
? parsedInput.inputs[publicInput.key]
|
|
1053
|
+
: publicInput.defaultValue;
|
|
1054
|
+
if (resolvedValue === undefined) {
|
|
1055
|
+
if (publicInput.required) {
|
|
1056
|
+
throw new Error(`Flow input "${publicInput.label}" is required.`);
|
|
1057
|
+
}
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (!validatePortValueType(publicInput, resolvedValue)) {
|
|
1061
|
+
throw new Error(`Flow input "${publicInput.label}" must match the ${publicInput.kind} type.`);
|
|
1062
|
+
}
|
|
1063
|
+
const bindings = normalizePublicInputBindings(connector, publicInput);
|
|
1064
|
+
if (bindings.length === 0) {
|
|
1065
|
+
throw new Error(`Flow input "${publicInput.label}" is not bound to any node input or parameter yet.`);
|
|
1066
|
+
}
|
|
1067
|
+
publicInputValues.set(publicInput.key, resolvedValue);
|
|
1068
|
+
for (const binding of bindings) {
|
|
1069
|
+
if (binding.targetKind === "param") {
|
|
1070
|
+
const current = nodePublicParams.get(binding.nodeId) ?? {};
|
|
1071
|
+
current[binding.targetKey] = resolvedValue;
|
|
1072
|
+
nodePublicParams.set(binding.nodeId, current);
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const current = nodePublicInputs.get(binding.nodeId) ?? [];
|
|
1076
|
+
const publicBindingValue = buildPublicInputValue(publicInput, resolvedValue);
|
|
1077
|
+
current.push({
|
|
1078
|
+
...publicBindingValue,
|
|
1079
|
+
targetHandle: binding.targetKey
|
|
1080
|
+
});
|
|
1081
|
+
nodePublicInputs.set(binding.nodeId, current);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
725
1084
|
const evaluateNode = async (nodeId) => {
|
|
726
1085
|
const existing = values.get(nodeId);
|
|
727
1086
|
if (existing) {
|
|
@@ -731,8 +1090,9 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
731
1090
|
if (!node) {
|
|
732
1091
|
throw new Error(`Missing connector node ${nodeId}.`);
|
|
733
1092
|
}
|
|
1093
|
+
const startedAt = Date.now();
|
|
734
1094
|
const upstreamEdges = incoming.get(nodeId) ?? [];
|
|
735
|
-
const
|
|
1095
|
+
const graphUpstream = await Promise.all(upstreamEdges.map(async (edge) => {
|
|
736
1096
|
const upstreamValue = await evaluateNode(edge.source);
|
|
737
1097
|
const selected = readOutputSelection(upstreamValue, edge.sourceHandle);
|
|
738
1098
|
return {
|
|
@@ -741,16 +1101,55 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
741
1101
|
selected
|
|
742
1102
|
};
|
|
743
1103
|
}));
|
|
1104
|
+
const publicInputs = (nodePublicInputs.get(nodeId) ?? []).map((entry) => ({
|
|
1105
|
+
edge: {
|
|
1106
|
+
id: `${nodeId}_${entry.targetHandle}`,
|
|
1107
|
+
source: entry.sourceNodeId,
|
|
1108
|
+
target: nodeId,
|
|
1109
|
+
sourceHandle: entry.sourceHandle,
|
|
1110
|
+
targetHandle: entry.targetHandle,
|
|
1111
|
+
label: null
|
|
1112
|
+
},
|
|
1113
|
+
sourceValue: {
|
|
1114
|
+
text: entry.text,
|
|
1115
|
+
json: entry.json,
|
|
1116
|
+
tools: [],
|
|
1117
|
+
conversationId: null,
|
|
1118
|
+
outputMap: {
|
|
1119
|
+
[entry.sourceHandle ?? entry.targetHandle]: {
|
|
1120
|
+
text: entry.text,
|
|
1121
|
+
json: entry.json
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
logs: []
|
|
1125
|
+
},
|
|
1126
|
+
selected: {
|
|
1127
|
+
text: entry.text,
|
|
1128
|
+
json: entry.json
|
|
1129
|
+
}
|
|
1130
|
+
}));
|
|
1131
|
+
const upstream = [...graphUpstream, ...publicInputs];
|
|
1132
|
+
const resolvedInputsForDebug = upstream.map((entry) => ({
|
|
1133
|
+
sourceNodeId: entry.edge.source,
|
|
1134
|
+
sourceHandle: entry.edge.sourceHandle ?? null,
|
|
1135
|
+
targetHandle: entry.edge.targetHandle ?? null,
|
|
1136
|
+
text: entry.selected.text,
|
|
1137
|
+
json: entry.selected.json
|
|
1138
|
+
}));
|
|
744
1139
|
let resolved;
|
|
1140
|
+
let nodeToolKeys = [];
|
|
745
1141
|
if (node.type === "box" || node.type === "box_input") {
|
|
746
1142
|
const boxId = node.data.boxId?.trim() || "";
|
|
747
1143
|
const resolvedInputs = Object.fromEntries(upstream.map(({ edge, selected }, index) => [
|
|
748
1144
|
edge.targetHandle ?? edge.sourceHandle ?? `input_${index + 1}`,
|
|
749
1145
|
selected.json ?? selected.text
|
|
750
1146
|
]));
|
|
751
|
-
const resolvedParams =
|
|
752
|
-
|
|
753
|
-
|
|
1147
|
+
const resolvedParams = {
|
|
1148
|
+
...(node.data.paramValues && typeof node.data.paramValues === "object"
|
|
1149
|
+
? node.data.paramValues
|
|
1150
|
+
: {}),
|
|
1151
|
+
...(nodePublicParams.get(nodeId) ?? {})
|
|
1152
|
+
};
|
|
754
1153
|
const providedSnapshot = boxId ? parsedInput.boxSnapshots[boxId] : null;
|
|
755
1154
|
const snapshot = providedSnapshot && typeof providedSnapshot === "object"
|
|
756
1155
|
? {
|
|
@@ -783,10 +1182,7 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
783
1182
|
contentJson: null,
|
|
784
1183
|
tools: []
|
|
785
1184
|
};
|
|
786
|
-
const
|
|
787
|
-
...(node.data.outputs ?? []).map((port) => port.key),
|
|
788
|
-
...Object.keys(snapshot.contentJson ?? {})
|
|
789
|
-
];
|
|
1185
|
+
const outputDefs = defaultOutputsForNode(node);
|
|
790
1186
|
resolved = {
|
|
791
1187
|
text: snapshot.contentText,
|
|
792
1188
|
json: snapshot.contentJson,
|
|
@@ -794,12 +1190,14 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
794
1190
|
boxId: snapshot.boxId,
|
|
795
1191
|
key: tool.key,
|
|
796
1192
|
label: tool.label,
|
|
797
|
-
description: tool.description
|
|
1193
|
+
description: tool.description,
|
|
1194
|
+
argsSchema: tool.argsSchema
|
|
798
1195
|
})),
|
|
799
1196
|
conversationId: null,
|
|
800
|
-
outputMap: buildOutputMap(snapshot.contentText, snapshot.contentJson,
|
|
1197
|
+
outputMap: buildOutputMap(snapshot.contentText, snapshot.contentJson, outputDefs),
|
|
801
1198
|
logs: []
|
|
802
1199
|
};
|
|
1200
|
+
nodeToolKeys = resolved.tools.map((tool) => tool.key);
|
|
803
1201
|
}
|
|
804
1202
|
else if (node.type === "value") {
|
|
805
1203
|
const parsedValue = parseValueLiteral(node.data.valueType ?? "string", node.data.valueLiteral ?? "");
|
|
@@ -816,17 +1214,27 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
816
1214
|
json: jsonValue,
|
|
817
1215
|
tools: [],
|
|
818
1216
|
conversationId: null,
|
|
819
|
-
outputMap: buildOutputMap(textValue, jsonValue,
|
|
1217
|
+
outputMap: buildOutputMap(textValue, jsonValue, defaultOutputsForNode(node)),
|
|
820
1218
|
logs: []
|
|
821
1219
|
};
|
|
822
1220
|
}
|
|
823
1221
|
else if (node.type === "user_input") {
|
|
1222
|
+
const inputJson = Object.keys(parsedInput.context).length > 0
|
|
1223
|
+
? {
|
|
1224
|
+
message: parsedInput.userInput || "",
|
|
1225
|
+
inputs: Object.fromEntries(publicInputValues),
|
|
1226
|
+
context: parsedInput.context
|
|
1227
|
+
}
|
|
1228
|
+
: {
|
|
1229
|
+
message: parsedInput.userInput || "",
|
|
1230
|
+
inputs: Object.fromEntries(publicInputValues)
|
|
1231
|
+
};
|
|
824
1232
|
resolved = {
|
|
825
1233
|
text: parsedInput.userInput || "",
|
|
826
|
-
json:
|
|
1234
|
+
json: inputJson,
|
|
827
1235
|
tools: [],
|
|
828
1236
|
conversationId: activeConversation?.id ?? null,
|
|
829
|
-
outputMap: buildOutputMap(parsedInput.userInput || "",
|
|
1237
|
+
outputMap: buildOutputMap(parsedInput.userInput || "", inputJson, defaultOutputsForNode(node)),
|
|
830
1238
|
logs: []
|
|
831
1239
|
};
|
|
832
1240
|
}
|
|
@@ -840,11 +1248,23 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
840
1248
|
.filter((entry) => Boolean(entry) && typeof entry === "object"));
|
|
841
1249
|
resolved = {
|
|
842
1250
|
text: mergedText,
|
|
843
|
-
json: Object.keys(mergedJson).length > 0
|
|
1251
|
+
json: Object.keys(mergedJson).length > 0
|
|
1252
|
+
? {
|
|
1253
|
+
merged: mergedJson
|
|
1254
|
+
}
|
|
1255
|
+
: {
|
|
1256
|
+
merged: mergedText
|
|
1257
|
+
},
|
|
844
1258
|
tools: upstream.flatMap((entry) => entry.sourceValue.tools),
|
|
845
1259
|
conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
|
|
846
1260
|
.conversationId ?? null,
|
|
847
|
-
outputMap: buildOutputMap(mergedText, Object.keys(mergedJson).length > 0
|
|
1261
|
+
outputMap: buildOutputMap(mergedText, Object.keys(mergedJson).length > 0
|
|
1262
|
+
? {
|
|
1263
|
+
merged: mergedJson
|
|
1264
|
+
}
|
|
1265
|
+
: {
|
|
1266
|
+
merged: mergedText
|
|
1267
|
+
}, defaultOutputsForNode(node)),
|
|
848
1268
|
logs: []
|
|
849
1269
|
};
|
|
850
1270
|
}
|
|
@@ -855,11 +1275,17 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
855
1275
|
.replaceAll("{{json}}", primary.json ? JSON.stringify(primary.json) : "");
|
|
856
1276
|
resolved = {
|
|
857
1277
|
text: rendered,
|
|
858
|
-
json:
|
|
1278
|
+
json: {
|
|
1279
|
+
rendered,
|
|
1280
|
+
...(tryParseJsonObject(rendered) ?? {})
|
|
1281
|
+
},
|
|
859
1282
|
tools: [],
|
|
860
1283
|
conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
|
|
861
1284
|
.conversationId ?? null,
|
|
862
|
-
outputMap: buildOutputMap(rendered,
|
|
1285
|
+
outputMap: buildOutputMap(rendered, {
|
|
1286
|
+
rendered,
|
|
1287
|
+
...(tryParseJsonObject(rendered) ?? {})
|
|
1288
|
+
}, defaultOutputsForNode(node)),
|
|
863
1289
|
logs: []
|
|
864
1290
|
};
|
|
865
1291
|
}
|
|
@@ -874,26 +1300,37 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
874
1300
|
: null;
|
|
875
1301
|
resolved = {
|
|
876
1302
|
text: coerceText(selectedValue),
|
|
877
|
-
json: selectedJson
|
|
1303
|
+
json: selectedJson ?? {
|
|
1304
|
+
selected: selectedValue
|
|
1305
|
+
},
|
|
878
1306
|
tools: [],
|
|
879
1307
|
conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
|
|
880
1308
|
.conversationId ?? null,
|
|
881
|
-
outputMap: buildOutputMap(coerceText(selectedValue), selectedJson
|
|
1309
|
+
outputMap: buildOutputMap(coerceText(selectedValue), selectedJson ?? {
|
|
1310
|
+
selected: selectedValue
|
|
1311
|
+
}, defaultOutputsForNode(node)),
|
|
882
1312
|
logs: []
|
|
883
1313
|
};
|
|
884
1314
|
}
|
|
885
1315
|
else if (node.type === "output") {
|
|
886
|
-
const
|
|
887
|
-
|
|
1316
|
+
const outputHandle = node.data.outputKey?.trim() || null;
|
|
1317
|
+
const publishedSelections = upstream.map((entry) => readOutputSelection(entry.sourceValue, outputHandle ?? entry.edge.sourceHandle));
|
|
1318
|
+
const mergedText = publishedSelections
|
|
1319
|
+
.map((entry) => entry.text)
|
|
888
1320
|
.filter(Boolean)
|
|
889
1321
|
.join("\n\n");
|
|
1322
|
+
const leadSelection = publishedSelections[0] ?? { text: mergedText, json: null };
|
|
1323
|
+
const publishedKey = outputHandle || "result";
|
|
1324
|
+
const publishedJson = leadSelection.json ?? {
|
|
1325
|
+
[publishedKey]: leadSelection.text
|
|
1326
|
+
};
|
|
890
1327
|
resolved = {
|
|
891
1328
|
text: mergedText,
|
|
892
|
-
json:
|
|
1329
|
+
json: publishedJson,
|
|
893
1330
|
tools: [],
|
|
894
1331
|
conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
|
|
895
1332
|
.conversationId ?? null,
|
|
896
|
-
outputMap: buildOutputMap(mergedText,
|
|
1333
|
+
outputMap: buildOutputMap(mergedText, publishedJson, defaultOutputsForNode(node)),
|
|
897
1334
|
logs: []
|
|
898
1335
|
};
|
|
899
1336
|
}
|
|
@@ -908,41 +1345,50 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
908
1345
|
tools: entry.sourceValue.tools,
|
|
909
1346
|
conversationId: entry.sourceValue.conversationId,
|
|
910
1347
|
outputMap: entry.sourceValue.outputMap,
|
|
911
|
-
logs: entry.sourceValue.logs
|
|
1348
|
+
logs: entry.sourceValue.logs,
|
|
1349
|
+
targetHandle: entry.edge.targetHandle ?? null
|
|
912
1350
|
})),
|
|
913
1351
|
services,
|
|
914
1352
|
conversation: activeConversation
|
|
915
1353
|
});
|
|
916
|
-
const
|
|
1354
|
+
const outputDefs = defaultOutputsForNode(node);
|
|
917
1355
|
resolved = {
|
|
918
1356
|
text: modelResult.text,
|
|
919
1357
|
json: modelResult.json,
|
|
920
1358
|
tools: [],
|
|
921
1359
|
conversationId: modelResult.conversationId,
|
|
922
|
-
outputMap: buildOutputMap(modelResult.text, modelResult.json,
|
|
1360
|
+
outputMap: buildOutputMap(modelResult.text, modelResult.json, outputDefs),
|
|
923
1361
|
logs: modelResult.logs
|
|
924
1362
|
};
|
|
1363
|
+
nodeToolKeys = modelResult.availableTools;
|
|
925
1364
|
}
|
|
926
1365
|
values.set(nodeId, resolved);
|
|
927
1366
|
debugNodes.push({
|
|
928
1367
|
nodeId: node.id,
|
|
929
1368
|
nodeType: node.type,
|
|
930
1369
|
label: node.data.label,
|
|
931
|
-
input:
|
|
932
|
-
sourceNodeId: entry.edge.source,
|
|
933
|
-
sourceHandle: entry.edge.sourceHandle ?? null,
|
|
934
|
-
targetHandle: entry.edge.targetHandle ?? null,
|
|
935
|
-
text: entry.selected.text,
|
|
936
|
-
json: entry.selected.json
|
|
937
|
-
})),
|
|
1370
|
+
input: resolvedInputsForDebug,
|
|
938
1371
|
output: {
|
|
939
1372
|
text: resolved.text,
|
|
940
1373
|
json: resolved.json
|
|
941
1374
|
},
|
|
942
|
-
tools:
|
|
1375
|
+
tools: nodeToolKeys,
|
|
943
1376
|
logs: resolved.logs,
|
|
944
1377
|
error: null
|
|
945
1378
|
});
|
|
1379
|
+
nodeResults.push({
|
|
1380
|
+
nodeId: node.id,
|
|
1381
|
+
nodeType: node.type,
|
|
1382
|
+
label: node.data.label,
|
|
1383
|
+
input: resolvedInputsForDebug,
|
|
1384
|
+
primaryText: resolved.text,
|
|
1385
|
+
payload: resolved.json,
|
|
1386
|
+
outputMap: resolved.outputMap,
|
|
1387
|
+
tools: nodeToolKeys,
|
|
1388
|
+
logs: resolved.logs,
|
|
1389
|
+
error: null,
|
|
1390
|
+
timingMs: Date.now() - startedAt
|
|
1391
|
+
});
|
|
946
1392
|
return resolved;
|
|
947
1393
|
};
|
|
948
1394
|
try {
|
|
@@ -955,7 +1401,7 @@ async function executeConnector(connector, rawInput, services) {
|
|
|
955
1401
|
throw error;
|
|
956
1402
|
}
|
|
957
1403
|
const result = aiConnectorRunResultSchema.parse({
|
|
958
|
-
...buildOutputResult(connector, values),
|
|
1404
|
+
...buildOutputResult(connector, values, nodeResults),
|
|
959
1405
|
debugTrace: parsedInput.debug
|
|
960
1406
|
? {
|
|
961
1407
|
nodes: debugNodes,
|
|
@@ -1055,7 +1501,7 @@ function migrateLegacyProcessor(processorId) {
|
|
|
1055
1501
|
data: {
|
|
1056
1502
|
label: "Output",
|
|
1057
1503
|
description: "Imported legacy output.",
|
|
1058
|
-
outputKey: "
|
|
1504
|
+
outputKey: "answer",
|
|
1059
1505
|
enabledToolKeys: []
|
|
1060
1506
|
}
|
|
1061
1507
|
};
|
|
@@ -1115,13 +1561,13 @@ export function createAiConnector(input) {
|
|
|
1115
1561
|
const now = new Date().toISOString();
|
|
1116
1562
|
const id = `aic_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
1117
1563
|
const slug = buildConnectorSlug(parsed.title, id);
|
|
1118
|
-
const graph = parsed.graph.nodes.length > 0 ? parsed.graph : buildDefaultGraph(parsed.kind, parsed.title);
|
|
1564
|
+
const graph = normalizeConnectorGraph(parsed.graph.nodes.length > 0 ? parsed.graph : buildDefaultGraph(parsed.kind, parsed.title));
|
|
1119
1565
|
const publishedOutputs = ensurePublishedOutputs(id, graph);
|
|
1120
1566
|
getDatabase()
|
|
1121
1567
|
.prepare(`INSERT INTO ai_connectors (
|
|
1122
|
-
id, slug, title, description, kind, home_surface_id, endpoint_enabled, graph_json, published_outputs_json, last_run_json, legacy_processor_id, created_at, updated_at
|
|
1123
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1124
|
-
.run(id, slug, parsed.title, parsed.description, parsed.kind, parsed.homeSurfaceId, parsed.endpointEnabled ? 1 : 0, JSON.stringify(graph), JSON.stringify(publishedOutputs), null, input.legacyProcessorId ?? null, now, now);
|
|
1568
|
+
id, slug, title, description, kind, home_surface_id, endpoint_enabled, graph_json, public_inputs_json, published_outputs_json, last_run_json, legacy_processor_id, created_at, updated_at
|
|
1569
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1570
|
+
.run(id, slug, parsed.title, parsed.description, parsed.kind, parsed.homeSurfaceId, parsed.endpointEnabled ? 1 : 0, JSON.stringify(graph), JSON.stringify(parsed.publicInputs), JSON.stringify(publishedOutputs), null, input.legacyProcessorId ?? null, now, now);
|
|
1125
1571
|
return getAiConnectorById(id);
|
|
1126
1572
|
}
|
|
1127
1573
|
export function updateAiConnector(connectorId, patch) {
|
|
@@ -1130,7 +1576,7 @@ export function updateAiConnector(connectorId, patch) {
|
|
|
1130
1576
|
return null;
|
|
1131
1577
|
}
|
|
1132
1578
|
const parsed = updateAiConnectorSchema.parse(patch);
|
|
1133
|
-
const nextGraph = parsed.graph ?? current.graph;
|
|
1579
|
+
const nextGraph = normalizeConnectorGraph(parsed.graph ?? current.graph);
|
|
1134
1580
|
validateConnectorGraph(nextGraph);
|
|
1135
1581
|
const nextTitle = parsed.title ?? current.title;
|
|
1136
1582
|
const next = {
|
|
@@ -1141,14 +1587,15 @@ export function updateAiConnector(connectorId, patch) {
|
|
|
1141
1587
|
? buildConnectorSlug(parsed.title, current.id)
|
|
1142
1588
|
: current.slug,
|
|
1143
1589
|
graph: nextGraph,
|
|
1590
|
+
publicInputs: parsed.publicInputs ?? current.publicInputs,
|
|
1144
1591
|
publishedOutputs: ensurePublishedOutputs(current.id, nextGraph)
|
|
1145
1592
|
};
|
|
1146
1593
|
const now = new Date().toISOString();
|
|
1147
1594
|
getDatabase()
|
|
1148
1595
|
.prepare(`UPDATE ai_connectors
|
|
1149
|
-
SET slug = ?, title = ?, description = ?, kind = ?, home_surface_id = ?, endpoint_enabled = ?, graph_json = ?, published_outputs_json = ?, updated_at = ?
|
|
1596
|
+
SET slug = ?, title = ?, description = ?, kind = ?, home_surface_id = ?, endpoint_enabled = ?, graph_json = ?, public_inputs_json = ?, published_outputs_json = ?, updated_at = ?
|
|
1150
1597
|
WHERE id = ?`)
|
|
1151
|
-
.run(next.slug, next.title, next.description, next.kind, next.homeSurfaceId, next.endpointEnabled ? 1 : 0, JSON.stringify(next.graph), JSON.stringify(next.publishedOutputs), now, connectorId);
|
|
1598
|
+
.run(next.slug, next.title, next.description, next.kind, next.homeSurfaceId, next.endpointEnabled ? 1 : 0, JSON.stringify(next.graph), JSON.stringify(next.publicInputs), JSON.stringify(next.publishedOutputs), now, connectorId);
|
|
1152
1599
|
return getAiConnectorById(connectorId);
|
|
1153
1600
|
}
|
|
1154
1601
|
export function deleteAiConnector(connectorId) {
|
|
@@ -1170,6 +1617,7 @@ export async function runAiConnector(connectorId, input, services, mode = "run")
|
|
|
1170
1617
|
mode,
|
|
1171
1618
|
status: "running",
|
|
1172
1619
|
userInput: input.userInput ?? "",
|
|
1620
|
+
inputs: input.inputs ?? {},
|
|
1173
1621
|
context: input.context ?? {},
|
|
1174
1622
|
conversationId: input.conversationId ?? null,
|
|
1175
1623
|
result: null,
|