forge-openclaw-plugin 0.2.25 → 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.
Files changed (237) hide show
  1. package/README.md +59 -3
  2. package/dist/assets/{board-VmF4FAfr.js → board-C6jCchjI.js} +3 -3
  3. package/dist/assets/{board-VmF4FAfr.js.map → board-C6jCchjI.js.map} +1 -1
  4. package/dist/assets/index-DVvS8iiU.css +1 -0
  5. package/dist/assets/index-zYB-9Dfo.js +85 -0
  6. package/dist/assets/index-zYB-9Dfo.js.map +1 -0
  7. package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js +2 -0
  8. package/dist/assets/knowledge-graph-layout.worker-DRvzPxhP.js.map +1 -0
  9. package/dist/assets/{motion-DvkU14p-.js → motion-DFHrH2rd.js} +2 -2
  10. package/dist/assets/{motion-DvkU14p-.js.map → motion-DFHrH2rd.js.map} +1 -1
  11. package/dist/assets/{table-DgiPof9E.js → table-ZL7Di_u3.js} +2 -2
  12. package/dist/assets/{table-DgiPof9E.js.map → table-ZL7Di_u3.js.map} +1 -1
  13. package/dist/assets/{ui-nYfoC0Gq.js → ui-CKNPpz7q.js} +2 -2
  14. package/dist/assets/{ui-nYfoC0Gq.js.map → ui-CKNPpz7q.js.map} +1 -1
  15. package/dist/assets/vendor-DoNZuFhn.js +1247 -0
  16. package/dist/assets/vendor-DoNZuFhn.js.map +1 -0
  17. package/dist/index.html +7 -8
  18. package/dist/openclaw/local-runtime.d.ts +3 -1
  19. package/dist/openclaw/local-runtime.js +67 -15
  20. package/dist/openclaw/plugin-entry-shared.js +24 -2
  21. package/dist/openclaw/plugin-sdk-types.d.ts +17 -0
  22. package/dist/openclaw/routes.d.ts +27 -0
  23. package/dist/openclaw/routes.js +16 -12
  24. package/dist/openclaw/tools.js +0 -3
  25. package/dist/server/server/migrations/001_core.sql +411 -0
  26. package/dist/server/server/migrations/002_psyche.sql +392 -0
  27. package/dist/server/server/migrations/003_habits.sql +30 -0
  28. package/dist/server/server/migrations/004_habit_links.sql +8 -0
  29. package/dist/server/server/migrations/005_habit_psyche_links.sql +24 -0
  30. package/dist/server/server/migrations/006_work_adjustments.sql +14 -0
  31. package/dist/server/server/migrations/007_weekly_review_closures.sql +17 -0
  32. package/dist/server/server/migrations/008_calendar_execution.sql +147 -0
  33. package/dist/server/server/migrations/009_true_calendar_events.sql +195 -0
  34. package/dist/server/server/migrations/010_calendar_selection_state.sql +6 -0
  35. package/dist/server/server/migrations/011_calendar_timezone_backfill.sql +11 -0
  36. package/dist/server/server/migrations/012_work_block_ranges.sql +7 -0
  37. package/dist/server/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
  38. package/dist/server/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
  39. package/dist/server/server/migrations/015_multi_user_and_strategies.sql +244 -0
  40. package/dist/server/server/migrations/016_health_companion.sql +158 -0
  41. package/dist/server/server/migrations/016_strategy_contracts_and_user_graph.sql +22 -0
  42. package/dist/server/server/migrations/017_preferences.sql +131 -0
  43. package/dist/server/server/migrations/018_preference_catalogs.sql +31 -0
  44. package/dist/server/server/migrations/019_wiki_memory.sql +255 -0
  45. package/dist/server/server/migrations/020_wiki_page_hierarchy.sql +11 -0
  46. package/dist/server/server/migrations/021_hide_evidence_from_wiki_index.sql +3 -0
  47. package/dist/server/server/migrations/022_wiki_ingest_background.sql +85 -0
  48. package/dist/server/server/migrations/023_diagnostic_logs.sql +28 -0
  49. package/dist/server/server/migrations/024_questionnaires.sql +96 -0
  50. package/dist/server/server/migrations/025_ai_model_connections.sql +26 -0
  51. package/dist/server/server/migrations/026_custom_theme_settings.sql +2 -0
  52. package/dist/server/server/migrations/027_ai_processors.sql +31 -0
  53. package/dist/server/server/migrations/028_movement_domain.sql +136 -0
  54. package/dist/server/server/migrations/029_watch_micro_capture.sql +23 -0
  55. package/dist/server/server/migrations/030_surface_layouts.sql +5 -0
  56. package/dist/server/server/migrations/031_ai_processor_runtime_upgrades.sql +10 -0
  57. package/dist/server/server/migrations/032_ai_connectors.sql +44 -0
  58. package/dist/server/server/migrations/033_movement_trip_point_sync.sql +36 -0
  59. package/dist/server/server/migrations/034_movement_segment_sync.sql +49 -0
  60. package/dist/server/server/migrations/035_google_local_auth_settings.sql +2 -0
  61. package/dist/server/server/migrations/036_google_local_auth_client_secret.sql +2 -0
  62. package/dist/server/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
  63. package/dist/server/server/migrations/038_data_management_settings.sql +11 -0
  64. package/dist/server/server/migrations/039_life_force_and_action_points.sql +114 -0
  65. package/dist/server/server/migrations/040_screen_time_domain.sql +89 -0
  66. package/dist/server/server/migrations/041_companion_source_states.sql +21 -0
  67. package/dist/server/server/migrations/042_movement_boxes.sql +47 -0
  68. package/dist/server/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
  69. package/dist/server/{app.js → server/src/app.js} +2112 -414
  70. package/dist/server/server/src/connectors/box-registry.js +223 -0
  71. package/dist/server/server/src/data-management-types.js +107 -0
  72. package/dist/server/{db.js → server/src/db.js} +72 -4
  73. package/dist/server/server/src/debug.js +19 -0
  74. package/dist/server/{demo-data.js → server/src/demo-data.js} +2 -2
  75. package/dist/server/{health.js → server/src/health.js} +702 -18
  76. package/dist/server/{managers → server/src/managers}/platform/llm-manager.js +7 -4
  77. package/dist/server/server/src/managers/platform/mock-workbench-provider.js +149 -0
  78. package/dist/server/{managers → server/src/managers}/platform/secrets-manager.js +18 -1
  79. package/dist/server/{managers → server/src/managers}/runtime.js +9 -0
  80. package/dist/server/{movement.js → server/src/movement.js} +1971 -112
  81. package/dist/server/{openapi.js → server/src/openapi.js} +491 -3
  82. package/dist/server/{psyche-types.js → server/src/psyche-types.js} +9 -1
  83. package/dist/server/{repositories → server/src/repositories}/activity-events.js +8 -0
  84. package/dist/server/{repositories → server/src/repositories}/ai-connectors.js +758 -47
  85. package/dist/server/{repositories → server/src/repositories}/calendar.js +1 -1
  86. package/dist/server/{repositories → server/src/repositories}/habits.js +37 -1
  87. package/dist/server/{repositories → server/src/repositories}/model-settings.js +13 -3
  88. package/dist/server/{repositories → server/src/repositories}/notes.js +3 -0
  89. package/dist/server/{repositories → server/src/repositories}/settings.js +431 -21
  90. package/dist/server/{repositories → server/src/repositories}/tasks.js +170 -10
  91. package/dist/server/server/src/runtime-data-root.js +82 -0
  92. package/dist/server/server/src/screen-time.js +802 -0
  93. package/dist/server/{services → server/src/services}/calendar-runtime.js +775 -58
  94. package/dist/server/server/src/services/data-management.js +788 -0
  95. package/dist/server/{services → server/src/services}/entity-crud.js +205 -2
  96. package/dist/server/server/src/services/google-calendar-oauth-config.js +176 -0
  97. package/dist/server/server/src/services/knowledge-graph.js +1455 -0
  98. package/dist/server/server/src/services/life-force-model.js +197 -0
  99. package/dist/server/server/src/services/life-force.js +1270 -0
  100. package/dist/server/server/src/services/psyche-observation-calendar.js +413 -0
  101. package/dist/server/{types.js → server/src/types.js} +420 -29
  102. package/dist/server/server/src/web.js +332 -0
  103. package/dist/server/src/components/customization/utility-widgets.js +439 -0
  104. package/dist/server/src/components/ui/info-tooltip.js +25 -0
  105. package/dist/server/src/components/workbench-boxes/calendar/calendar-boxes.js +78 -0
  106. package/dist/server/src/components/workbench-boxes/goals/goals-boxes.js +62 -0
  107. package/dist/server/src/components/workbench-boxes/habits/habits-boxes.js +62 -0
  108. package/dist/server/src/components/workbench-boxes/health/health-boxes.js +147 -0
  109. package/dist/server/src/components/workbench-boxes/insights/insights-boxes.js +50 -0
  110. package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +136 -0
  111. package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +47 -0
  112. package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +132 -0
  113. package/dist/server/src/components/workbench-boxes/overview/overview-boxes.js +65 -0
  114. package/dist/server/src/components/workbench-boxes/preferences/preferences-boxes.js +78 -0
  115. package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +62 -0
  116. package/dist/server/src/components/workbench-boxes/psyche/psyche-boxes.js +88 -0
  117. package/dist/server/src/components/workbench-boxes/questionnaires/questionnaires-boxes.js +61 -0
  118. package/dist/server/src/components/workbench-boxes/review/review-boxes.js +53 -0
  119. package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +6 -0
  120. package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +49 -0
  121. package/dist/server/src/components/workbench-boxes/strategies/strategies-boxes.js +62 -0
  122. package/dist/server/src/components/workbench-boxes/tasks/tasks-boxes.js +76 -0
  123. package/dist/server/src/components/workbench-boxes/today/today-boxes.js +78 -0
  124. package/dist/server/src/components/workbench-boxes/wiki/wiki-boxes.js +60 -0
  125. package/dist/server/src/lib/api-error.js +37 -0
  126. package/dist/server/src/lib/api.js +2118 -0
  127. package/dist/server/src/lib/calendar-name-deduper.js +144 -0
  128. package/dist/server/src/lib/data-management-types.js +1 -0
  129. package/dist/server/src/lib/diagnostics.js +67 -0
  130. package/dist/server/src/lib/entity-visuals.js +279 -0
  131. package/dist/server/src/lib/knowledge-graph-types.js +276 -0
  132. package/dist/server/src/lib/knowledge-graph.js +470 -0
  133. package/dist/server/src/lib/psyche-types.js +1 -0
  134. package/dist/server/src/lib/questionnaire-types.js +1 -0
  135. package/dist/server/src/lib/runtime-paths.js +24 -0
  136. package/dist/server/src/lib/schemas.js +238 -0
  137. package/dist/server/src/lib/snapshot-normalizer.js +416 -0
  138. package/dist/server/src/lib/theme-system.js +319 -0
  139. package/dist/server/src/lib/types.js +1 -0
  140. package/dist/server/src/lib/utils.js +22 -0
  141. package/dist/server/src/lib/workbench/boxes.js +16 -0
  142. package/dist/server/src/lib/workbench/contracts.js +229 -0
  143. package/dist/server/src/lib/workbench/nodes.js +215 -0
  144. package/dist/server/src/lib/workbench/registry.js +120 -0
  145. package/dist/server/src/lib/workbench/runtime.js +397 -0
  146. package/dist/server/src/lib/workbench/tool-catalog.js +68 -0
  147. package/openclaw.plugin.json +1 -1
  148. package/package.json +1 -1
  149. package/server/index.js +68 -0
  150. package/server/migrations/035_google_local_auth_settings.sql +2 -0
  151. package/server/migrations/036_google_local_auth_client_secret.sql +2 -0
  152. package/server/migrations/037_workbench_public_inputs_and_run_inputs.sql +5 -0
  153. package/server/migrations/038_data_management_settings.sql +11 -0
  154. package/server/migrations/039_life_force_and_action_points.sql +114 -0
  155. package/server/migrations/040_screen_time_domain.sql +89 -0
  156. package/server/migrations/041_companion_source_states.sql +21 -0
  157. package/server/migrations/042_movement_boxes.sql +47 -0
  158. package/server/migrations/043_movement_box_overlap_overrides.sql +26 -0
  159. package/skills/forge-openclaw/SKILL.md +27 -11
  160. package/skills/forge-openclaw/entity_conversation_playbooks.md +411 -46
  161. package/skills/forge-openclaw/psyche_entity_playbooks.md +195 -20
  162. package/dist/assets/index-CFCKDIMH.js +0 -67
  163. package/dist/assets/index-CFCKDIMH.js.map +0 -1
  164. package/dist/assets/index-ZPY6U1TU.css +0 -1
  165. package/dist/assets/vendor-D9PTEPSB.js +0 -824
  166. package/dist/assets/vendor-D9PTEPSB.js.map +0 -1
  167. package/dist/assets/viz-Cqb6s--o.js +0 -34
  168. package/dist/assets/viz-Cqb6s--o.js.map +0 -1
  169. package/dist/server/connectors/box-registry.js +0 -257
  170. package/dist/server/services/psyche-observation-calendar.js +0 -46
  171. package/dist/server/web.js +0 -98
  172. /package/dist/server/{discovery-advertiser.js → server/src/discovery-advertiser.js} +0 -0
  173. /package/dist/server/{e2e-server.js → server/src/e2e-server.js} +0 -0
  174. /package/dist/server/{errors.js → server/src/errors.js} +0 -0
  175. /package/dist/server/{index.js → server/src/index.js} +0 -0
  176. /package/dist/server/{managers → server/src/managers}/base.js +0 -0
  177. /package/dist/server/{managers → server/src/managers}/contracts.js +0 -0
  178. /package/dist/server/{managers → server/src/managers}/platform/api-gateway-manager.js +0 -0
  179. /package/dist/server/{managers → server/src/managers}/platform/audit-manager.js +0 -0
  180. /package/dist/server/{managers → server/src/managers}/platform/authentication-manager.js +0 -0
  181. /package/dist/server/{managers → server/src/managers}/platform/authorization-manager.js +0 -0
  182. /package/dist/server/{managers → server/src/managers}/platform/background-job-manager.js +0 -0
  183. /package/dist/server/{managers → server/src/managers}/platform/configuration-manager.js +0 -0
  184. /package/dist/server/{managers → server/src/managers}/platform/database-manager.js +0 -0
  185. /package/dist/server/{managers → server/src/managers}/platform/event-bus-manager.js +0 -0
  186. /package/dist/server/{managers → server/src/managers}/platform/external-service-manager.js +0 -0
  187. /package/dist/server/{managers → server/src/managers}/platform/health-manager.js +0 -0
  188. /package/dist/server/{managers → server/src/managers}/platform/migration-manager.js +0 -0
  189. /package/dist/server/{managers → server/src/managers}/platform/openai-responses-provider.js +0 -0
  190. /package/dist/server/{managers → server/src/managers}/platform/search-index-manager.js +0 -0
  191. /package/dist/server/{managers → server/src/managers}/platform/session-manager.js +0 -0
  192. /package/dist/server/{managers → server/src/managers}/platform/storage-manager.js +0 -0
  193. /package/dist/server/{managers → server/src/managers}/platform/token-manager.js +0 -0
  194. /package/dist/server/{managers → server/src/managers}/platform/transaction-manager.js +0 -0
  195. /package/dist/server/{managers → server/src/managers}/platform/trusted-network.js +0 -0
  196. /package/dist/server/{managers → server/src/managers}/type-guards.js +0 -0
  197. /package/dist/server/{preferences-seeds.js → server/src/preferences-seeds.js} +0 -0
  198. /package/dist/server/{preferences-types.js → server/src/preferences-types.js} +0 -0
  199. /package/dist/server/{questionnaire-flow.js → server/src/questionnaire-flow.js} +0 -0
  200. /package/dist/server/{questionnaire-seeds.js → server/src/questionnaire-seeds.js} +0 -0
  201. /package/dist/server/{questionnaire-types.js → server/src/questionnaire-types.js} +0 -0
  202. /package/dist/server/{repositories → server/src/repositories}/ai-processors.js +0 -0
  203. /package/dist/server/{repositories → server/src/repositories}/collaboration.js +0 -0
  204. /package/dist/server/{repositories → server/src/repositories}/deleted-entities.js +0 -0
  205. /package/dist/server/{repositories → server/src/repositories}/diagnostic-logs.js +0 -0
  206. /package/dist/server/{repositories → server/src/repositories}/domains.js +0 -0
  207. /package/dist/server/{repositories → server/src/repositories}/entity-ownership.js +0 -0
  208. /package/dist/server/{repositories → server/src/repositories}/event-log.js +0 -0
  209. /package/dist/server/{repositories → server/src/repositories}/goals.js +0 -0
  210. /package/dist/server/{repositories → server/src/repositories}/preferences.js +0 -0
  211. /package/dist/server/{repositories → server/src/repositories}/projects.js +0 -0
  212. /package/dist/server/{repositories → server/src/repositories}/psyche.js +0 -0
  213. /package/dist/server/{repositories → server/src/repositories}/questionnaires.js +0 -0
  214. /package/dist/server/{repositories → server/src/repositories}/rewards.js +0 -0
  215. /package/dist/server/{repositories → server/src/repositories}/strategies.js +0 -0
  216. /package/dist/server/{repositories → server/src/repositories}/surface-layouts.js +0 -0
  217. /package/dist/server/{repositories → server/src/repositories}/tags.js +0 -0
  218. /package/dist/server/{repositories → server/src/repositories}/task-runs.js +0 -0
  219. /package/dist/server/{repositories → server/src/repositories}/users.js +0 -0
  220. /package/dist/server/{repositories → server/src/repositories}/weekly-reviews.js +0 -0
  221. /package/dist/server/{repositories → server/src/repositories}/wiki-memory.js +0 -0
  222. /package/dist/server/{repositories → server/src/repositories}/work-adjustments.js +0 -0
  223. /package/dist/server/{seed-demo.js → server/src/seed-demo.js} +0 -0
  224. /package/dist/server/{services → server/src/services}/context.js +0 -0
  225. /package/dist/server/{services → server/src/services}/dashboard.js +0 -0
  226. /package/dist/server/{services → server/src/services}/gamification.js +0 -0
  227. /package/dist/server/{services → server/src/services}/insights.js +0 -0
  228. /package/dist/server/{services → server/src/services}/openai-codex-oauth.js +0 -0
  229. /package/dist/server/{services → server/src/services}/projects.js +0 -0
  230. /package/dist/server/{services → server/src/services}/psyche.js +0 -0
  231. /package/dist/server/{services → server/src/services}/relations.js +0 -0
  232. /package/dist/server/{services → server/src/services}/reviews.js +0 -0
  233. /package/dist/server/{services → server/src/services}/run-recovery.js +0 -0
  234. /package/dist/server/{services → server/src/services}/tagging.js +0 -0
  235. /package/dist/server/{services → server/src/services}/task-run-watchdog.js +0 -0
  236. /package/dist/server/{services → server/src/services}/work-time.js +0 -0
  237. /package/dist/server/{watch-mobile.js → server/src/watch-mobile.js} +0 -0
@@ -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: "primary",
168
+ outputKey: "answer",
168
169
  enabledToolKeys: []
169
170
  }
170
171
  }
@@ -190,20 +191,20 @@ function ensurePublishedOutputs(connectorId, graph) {
190
191
  buildConnectorOutputCatalogEntry({
191
192
  connectorId,
192
193
  title: "Connector",
193
- outputId: "primary"
194
+ outputId: "answer"
194
195
  })
195
196
  ].map((entry) => ({
196
- id: entry.boxId.replace(/^connector-output:/, ""),
197
+ id: entry.id.replace(/^connector-output:/, ""),
197
198
  nodeId: "node_output",
198
- label: entry.label,
199
- apiPath: `/api/v1/ai-connectors/${connectorId}/output`
199
+ label: entry.title,
200
+ apiPath: `/api/v1/workbench/flows/${connectorId}/output`
200
201
  }));
201
202
  }
202
203
  return outputNodes.map((node, index) => ({
203
204
  id: `${connectorId}_out_${index + 1}`,
204
205
  nodeId: node.id,
205
206
  label: node.data.label || `Output ${index + 1}`,
206
- apiPath: `/api/v1/ai-connectors/${connectorId}/output`
207
+ apiPath: `/api/v1/workbench/flows/${connectorId}/output`
207
208
  }));
208
209
  }
209
210
  function mapRun(row) {
@@ -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: parseJson(row.graph_json, { nodes: [], edges: [] }),
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,11 +353,313 @@ function resolveAllowedPath(inputPath) {
316
353
  }
317
354
  function tryParseStructuredAgentResponse(value) {
318
355
  try {
319
- return JSON.parse(value);
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;
383
+ }
384
+ catch {
385
+ return null;
386
+ }
387
+ }
388
+ function tryParseJsonObject(value) {
389
+ try {
390
+ const parsed = JSON.parse(value);
391
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
392
+ return parsed;
393
+ }
394
+ return null;
395
+ }
396
+ catch {
397
+ return null;
398
+ }
399
+ }
400
+ function coerceText(value) {
401
+ if (typeof value === "string") {
402
+ return value;
403
+ }
404
+ if (value == null) {
405
+ return "";
406
+ }
407
+ if (typeof value === "number" || typeof value === "boolean") {
408
+ return String(value);
409
+ }
410
+ try {
411
+ return JSON.stringify(value);
320
412
  }
321
413
  catch {
414
+ return "";
415
+ }
416
+ }
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
+ });
464
+ }
465
+ if (params.some((entry) => entry.key === publicInput.key)) {
466
+ matches.push({
467
+ nodeId: node.id,
468
+ targetKey: publicInput.key,
469
+ targetKind: "param"
470
+ });
471
+ }
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] = {
494
+ text: coerceText(value),
495
+ json: value && typeof value === "object" && !Array.isArray(value)
496
+ ? value
497
+ : null
498
+ };
499
+ });
500
+ return outputMap;
501
+ }
502
+ function readOutputSelection(value, handle) {
503
+ if (!handle) {
504
+ const lead = Object.values(value.outputMap)[0];
505
+ return lead ?? { text: value.text, json: value.json };
506
+ }
507
+ const selected = value.outputMap[handle];
508
+ if (selected) {
509
+ return selected;
510
+ }
511
+ if (value.json && handle in value.json) {
512
+ const raw = value.json[handle];
513
+ return {
514
+ text: coerceText(raw),
515
+ json: raw && typeof raw === "object" && !Array.isArray(raw)
516
+ ? raw
517
+ : null
518
+ };
519
+ }
520
+ return { text: value.text, json: value.json };
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) {
322
631
  return null;
323
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
+ };
324
663
  }
325
664
  async function executeMachineTool(tool, args) {
326
665
  if (tool === "machine_read_file") {
@@ -360,9 +699,15 @@ function getConversationBasePrompt(input) {
360
699
  return [
361
700
  input.node.data.prompt?.trim() || "",
362
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
+ : "",
363
708
  input.upstream.length > 0
364
709
  ? `Linked inputs:\n${input.upstream
365
- .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)}` : ""}`)
366
711
  .join("\n\n")}`
367
712
  : "",
368
713
  input.transcript.length > 0 ? `Tool transcript:\n${input.transcript.join("\n\n")}` : ""
@@ -445,7 +790,7 @@ function resolveConnectorModelProfile(node, secrets) {
445
790
  : credential?.kind === "oauth"
446
791
  ? credential.access
447
792
  : null;
448
- if (!explicitApiKey) {
793
+ if (!explicitApiKey && fallbackConnection.provider !== "mock") {
449
794
  throw new Error("The selected connector model connection is missing a credential.");
450
795
  }
451
796
  const profile = {
@@ -463,7 +808,7 @@ function resolveConnectorModelProfile(node, secrets) {
463
808
  };
464
809
  return {
465
810
  profile,
466
- apiKey: explicitApiKey
811
+ apiKey: explicitApiKey ?? "mock"
467
812
  };
468
813
  }
469
814
  async function runModelNode(input) {
@@ -486,7 +831,7 @@ async function runModelNode(input) {
486
831
  'For a final answer return {"action":"final","text":"..."}',
487
832
  'For a tool call return {"action":"tool","tool":"tool_key","args":{...}}',
488
833
  `Available tools: ${activeTools
489
- .map((tool) => `${tool.key} (${tool.description})`)
834
+ .map((tool) => `${tool.key} (${tool.description})${tool.argsSchema ? ` args=${JSON.stringify(tool.argsSchema)}` : ""}`)
490
835
  .join("; ")}.`
491
836
  ].join(" ")
492
837
  : "Return only the final answer text."
@@ -498,7 +843,8 @@ async function runModelNode(input) {
498
843
  node: input.node,
499
844
  userInput: input.userInput,
500
845
  upstream: input.upstream,
501
- transcript
846
+ transcript,
847
+ conversation: input.conversation
502
848
  });
503
849
  let rawText = "";
504
850
  if (conversationAware && isOpenAiFamily(profile)) {
@@ -522,27 +868,44 @@ async function runModelNode(input) {
522
868
  if (activeTools.length === 0) {
523
869
  return {
524
870
  text: rawText.trim(),
525
- conversationId
871
+ json: tryParseJsonObject(rawText.trim()),
872
+ conversationId,
873
+ logs: transcript,
874
+ availableTools: []
526
875
  };
527
876
  }
528
877
  const structured = tryParseStructuredAgentResponse(rawText.trim());
529
878
  if (!structured || structured.action === "final") {
530
879
  return {
531
880
  text: structured?.text?.trim() || rawText.trim(),
532
- conversationId
881
+ json: tryParseJsonObject(structured?.text?.trim() || rawText.trim()),
882
+ conversationId,
883
+ logs: transcript,
884
+ availableTools: activeTools.map((tool) => tool.key)
533
885
  };
534
886
  }
535
887
  const toolResult = structured.tool.startsWith("machine_")
536
888
  ? await executeMachineTool(structured.tool, structured.args)
537
- : await executeForgeBoxTool(activeTools.find((tool) => tool.key === structured.tool)?.boxId ?? "", structured.tool, structured.args);
889
+ : await executeForgeBoxTool(activeTools.find((tool) => tool.key === structured.tool)?.boxId ?? "", structured.tool, structured.args, {
890
+ actor: {
891
+ userIds: null,
892
+ source: "agent"
893
+ }
894
+ });
538
895
  transcript.push(`Tool call ${structured.tool}: ${JSON.stringify(structured.args)}`, `Tool result: ${JSON.stringify(toolResult)}`);
539
896
  }
540
897
  return {
541
898
  text: "Connector stopped after reaching the maximum tool step count.",
542
- conversationId
899
+ json: null,
900
+ conversationId,
901
+ logs: transcript,
902
+ availableTools: activeTools.map((tool) => tool.key)
543
903
  };
544
904
  }
545
905
  function validateConnectorGraph(graph) {
906
+ if (graph.nodes.length === 0) {
907
+ throw new Error("Connector graph has no nodes yet.");
908
+ }
546
909
  const nodeIds = new Set(graph.nodes.map((node) => node.id));
547
910
  for (const edge of graph.edges) {
548
911
  if (!nodeIds.has(edge.source) || !nodeIds.has(edge.target)) {
@@ -574,8 +937,44 @@ function validateConnectorGraph(graph) {
574
937
  for (const node of graph.nodes) {
575
938
  visit(node.id);
576
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
+ }
577
976
  }
578
- function buildOutputResult(connector, resolvedNodeValues) {
977
+ function buildOutputResult(connector, resolvedNodeValues, nodeResults) {
579
978
  const outputs = Object.fromEntries(connector.publishedOutputs.map((output) => {
580
979
  const nodeValue = resolvedNodeValues.get(output.nodeId);
581
980
  return [
@@ -590,9 +989,31 @@ function buildOutputResult(connector, resolvedNodeValues) {
590
989
  const first = connector.publishedOutputs[0];
591
990
  return aiConnectorRunResultSchema.parse({
592
991
  primaryText: first ? outputs[first.id]?.text ?? "" : "",
593
- outputs
992
+ outputs,
993
+ nodeResults
594
994
  });
595
995
  }
996
+ function parseValueLiteral(valueType, valueLiteral) {
997
+ if (valueType === "null") {
998
+ return null;
999
+ }
1000
+ if (valueType === "boolean") {
1001
+ return valueLiteral.trim().toLowerCase() === "true";
1002
+ }
1003
+ if (valueType === "number") {
1004
+ const parsed = Number(valueLiteral);
1005
+ return Number.isFinite(parsed) ? parsed : 0;
1006
+ }
1007
+ if (valueType === "array" || valueType === "object") {
1008
+ try {
1009
+ return JSON.parse(valueLiteral || (valueType === "array" ? "[]" : "{}"));
1010
+ }
1011
+ catch {
1012
+ return valueType === "array" ? [] : {};
1013
+ }
1014
+ }
1015
+ return valueLiteral;
1016
+ }
596
1017
  function createConversationRecord(input) {
597
1018
  const now = new Date().toISOString();
598
1019
  return saveAiConnectorConversation(aiConnectorConversationSchema.parse({
@@ -615,10 +1036,51 @@ async function executeConnector(connector, rawInput, services) {
615
1036
  incoming.set(edge.target, list);
616
1037
  }
617
1038
  const values = new Map();
1039
+ const debugNodes = [];
1040
+ const nodeResults = [];
1041
+ const debugErrors = [];
618
1042
  const outputNodes = connector.graph.nodes.filter((node) => node.type === "output");
619
1043
  const activeConversation = parsedInput.conversationId
620
1044
  ? getAiConnectorConversationById(parsedInput.conversationId)
621
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
+ }
622
1084
  const evaluateNode = async (nodeId) => {
623
1085
  const existing = values.get(nodeId);
624
1086
  if (existing) {
@@ -628,18 +1090,90 @@ async function executeConnector(connector, rawInput, services) {
628
1090
  if (!node) {
629
1091
  throw new Error(`Missing connector node ${nodeId}.`);
630
1092
  }
631
- const upstream = await Promise.all((incoming.get(nodeId) ?? []).map((edge) => evaluateNode(edge.source)));
1093
+ const startedAt = Date.now();
1094
+ const upstreamEdges = incoming.get(nodeId) ?? [];
1095
+ const graphUpstream = await Promise.all(upstreamEdges.map(async (edge) => {
1096
+ const upstreamValue = await evaluateNode(edge.source);
1097
+ const selected = readOutputSelection(upstreamValue, edge.sourceHandle);
1098
+ return {
1099
+ edge,
1100
+ sourceValue: upstreamValue,
1101
+ selected
1102
+ };
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
+ }));
632
1139
  let resolved;
633
- if (node.type === "box_input") {
1140
+ let nodeToolKeys = [];
1141
+ if (node.type === "box" || node.type === "box_input") {
634
1142
  const boxId = node.data.boxId?.trim() || "";
1143
+ const resolvedInputs = Object.fromEntries(upstream.map(({ edge, selected }, index) => [
1144
+ edge.targetHandle ?? edge.sourceHandle ?? `input_${index + 1}`,
1145
+ selected.json ?? selected.text
1146
+ ]));
1147
+ const resolvedParams = {
1148
+ ...(node.data.paramValues && typeof node.data.paramValues === "object"
1149
+ ? node.data.paramValues
1150
+ : {}),
1151
+ ...(nodePublicParams.get(nodeId) ?? {})
1152
+ };
635
1153
  const providedSnapshot = boxId ? parsedInput.boxSnapshots[boxId] : null;
636
1154
  const snapshot = providedSnapshot && typeof providedSnapshot === "object"
637
1155
  ? {
638
- ...resolveForgeBoxSnapshot(boxId),
1156
+ ...resolveForgeBoxSnapshot(boxId, {
1157
+ actor: {
1158
+ userIds: null,
1159
+ source: "agent"
1160
+ }
1161
+ }, {
1162
+ inputs: resolvedInputs,
1163
+ params: resolvedParams
1164
+ }),
639
1165
  contentJson: providedSnapshot
640
1166
  }
641
1167
  : boxId
642
- ? resolveForgeBoxSnapshot(boxId)
1168
+ ? resolveForgeBoxSnapshot(boxId, {
1169
+ actor: {
1170
+ userIds: null,
1171
+ source: "agent"
1172
+ }
1173
+ }, {
1174
+ inputs: resolvedInputs,
1175
+ params: resolvedParams
1176
+ })
643
1177
  : {
644
1178
  boxId: "",
645
1179
  label: node.data.label,
@@ -648,6 +1182,7 @@ async function executeConnector(connector, rawInput, services) {
648
1182
  contentJson: null,
649
1183
  tools: []
650
1184
  };
1185
+ const outputDefs = defaultOutputsForNode(node);
651
1186
  resolved = {
652
1187
  text: snapshot.contentText,
653
1188
  json: snapshot.contentJson,
@@ -655,26 +1190,148 @@ async function executeConnector(connector, rawInput, services) {
655
1190
  boxId: snapshot.boxId,
656
1191
  key: tool.key,
657
1192
  label: tool.label,
658
- description: tool.description
1193
+ description: tool.description,
1194
+ argsSchema: tool.argsSchema
659
1195
  })),
660
- conversationId: null
1196
+ conversationId: null,
1197
+ outputMap: buildOutputMap(snapshot.contentText, snapshot.contentJson, outputDefs),
1198
+ logs: []
1199
+ };
1200
+ nodeToolKeys = resolved.tools.map((tool) => tool.key);
1201
+ }
1202
+ else if (node.type === "value") {
1203
+ const parsedValue = parseValueLiteral(node.data.valueType ?? "string", node.data.valueLiteral ?? "");
1204
+ const jsonValue = parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue)
1205
+ ? parsedValue
1206
+ : null;
1207
+ const textValue = parsedValue === null
1208
+ ? "null"
1209
+ : typeof parsedValue === "string"
1210
+ ? parsedValue
1211
+ : JSON.stringify(parsedValue, null, 2);
1212
+ resolved = {
1213
+ text: textValue,
1214
+ json: jsonValue,
1215
+ tools: [],
1216
+ conversationId: null,
1217
+ outputMap: buildOutputMap(textValue, jsonValue, defaultOutputsForNode(node)),
1218
+ logs: []
661
1219
  };
662
1220
  }
663
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
+ };
664
1232
  resolved = {
665
1233
  text: parsedInput.userInput || "",
666
- json: Object.keys(parsedInput.context).length > 0 ? parsedInput.context : null,
1234
+ json: inputJson,
667
1235
  tools: [],
668
- conversationId: activeConversation?.id ?? null
1236
+ conversationId: activeConversation?.id ?? null,
1237
+ outputMap: buildOutputMap(parsedInput.userInput || "", inputJson, defaultOutputsForNode(node)),
1238
+ logs: []
1239
+ };
1240
+ }
1241
+ else if (node.type === "merge") {
1242
+ const mergedText = upstream
1243
+ .map((entry) => entry.selected.text)
1244
+ .filter(Boolean)
1245
+ .join("\n\n");
1246
+ const mergedJson = Object.assign({}, ...upstream
1247
+ .map((entry) => entry.selected.json)
1248
+ .filter((entry) => Boolean(entry) && typeof entry === "object"));
1249
+ resolved = {
1250
+ text: mergedText,
1251
+ json: Object.keys(mergedJson).length > 0
1252
+ ? {
1253
+ merged: mergedJson
1254
+ }
1255
+ : {
1256
+ merged: mergedText
1257
+ },
1258
+ tools: upstream.flatMap((entry) => entry.sourceValue.tools),
1259
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
1260
+ .conversationId ?? null,
1261
+ outputMap: buildOutputMap(mergedText, Object.keys(mergedJson).length > 0
1262
+ ? {
1263
+ merged: mergedJson
1264
+ }
1265
+ : {
1266
+ merged: mergedText
1267
+ }, defaultOutputsForNode(node)),
1268
+ logs: []
1269
+ };
1270
+ }
1271
+ else if (node.type === "template") {
1272
+ const primary = upstream[0]?.selected ?? { text: "", json: null };
1273
+ const rendered = (node.data.template ?? node.data.promptTemplate ?? "")
1274
+ .replaceAll("{{input}}", primary.text)
1275
+ .replaceAll("{{json}}", primary.json ? JSON.stringify(primary.json) : "");
1276
+ resolved = {
1277
+ text: rendered,
1278
+ json: {
1279
+ rendered,
1280
+ ...(tryParseJsonObject(rendered) ?? {})
1281
+ },
1282
+ tools: [],
1283
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
1284
+ .conversationId ?? null,
1285
+ outputMap: buildOutputMap(rendered, {
1286
+ rendered,
1287
+ ...(tryParseJsonObject(rendered) ?? {})
1288
+ }, defaultOutputsForNode(node)),
1289
+ logs: []
1290
+ };
1291
+ }
1292
+ else if (node.type === "pick_key") {
1293
+ const primary = upstream[0]?.selected ?? { text: "", json: null };
1294
+ const selectedKey = node.data.selectedKey?.trim() || "";
1295
+ const selectedValue = primary.json && selectedKey in primary.json ? primary.json[selectedKey] : null;
1296
+ const selectedJson = selectedValue &&
1297
+ typeof selectedValue === "object" &&
1298
+ !Array.isArray(selectedValue)
1299
+ ? selectedValue
1300
+ : null;
1301
+ resolved = {
1302
+ text: coerceText(selectedValue),
1303
+ json: selectedJson ?? {
1304
+ selected: selectedValue
1305
+ },
1306
+ tools: [],
1307
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
1308
+ .conversationId ?? null,
1309
+ outputMap: buildOutputMap(coerceText(selectedValue), selectedJson ?? {
1310
+ selected: selectedValue
1311
+ }, defaultOutputsForNode(node)),
1312
+ logs: []
669
1313
  };
670
1314
  }
671
1315
  else if (node.type === "output") {
672
- const mergedText = upstream.map((entry) => entry.text).filter(Boolean).join("\n\n");
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)
1320
+ .filter(Boolean)
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
+ };
673
1327
  resolved = {
674
1328
  text: mergedText,
675
- json: upstream[0]?.json ?? null,
1329
+ json: publishedJson,
676
1330
  tools: [],
677
- conversationId: upstream.find((entry) => entry.conversationId)?.conversationId ?? null
1331
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
1332
+ .conversationId ?? null,
1333
+ outputMap: buildOutputMap(mergedText, publishedJson, defaultOutputsForNode(node)),
1334
+ logs: []
678
1335
  };
679
1336
  }
680
1337
  else {
@@ -682,24 +1339,76 @@ async function executeConnector(connector, rawInput, services) {
682
1339
  connector,
683
1340
  node,
684
1341
  userInput: parsedInput.userInput,
685
- upstream,
1342
+ upstream: upstream.map((entry) => ({
1343
+ text: entry.selected.text,
1344
+ json: entry.selected.json,
1345
+ tools: entry.sourceValue.tools,
1346
+ conversationId: entry.sourceValue.conversationId,
1347
+ outputMap: entry.sourceValue.outputMap,
1348
+ logs: entry.sourceValue.logs,
1349
+ targetHandle: entry.edge.targetHandle ?? null
1350
+ })),
686
1351
  services,
687
1352
  conversation: activeConversation
688
1353
  });
1354
+ const outputDefs = defaultOutputsForNode(node);
689
1355
  resolved = {
690
1356
  text: modelResult.text,
691
- json: null,
1357
+ json: modelResult.json,
692
1358
  tools: [],
693
- conversationId: modelResult.conversationId
1359
+ conversationId: modelResult.conversationId,
1360
+ outputMap: buildOutputMap(modelResult.text, modelResult.json, outputDefs),
1361
+ logs: modelResult.logs
694
1362
  };
1363
+ nodeToolKeys = modelResult.availableTools;
695
1364
  }
696
1365
  values.set(nodeId, resolved);
1366
+ debugNodes.push({
1367
+ nodeId: node.id,
1368
+ nodeType: node.type,
1369
+ label: node.data.label,
1370
+ input: resolvedInputsForDebug,
1371
+ output: {
1372
+ text: resolved.text,
1373
+ json: resolved.json
1374
+ },
1375
+ tools: nodeToolKeys,
1376
+ logs: resolved.logs,
1377
+ error: null
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
+ });
697
1392
  return resolved;
698
1393
  };
699
- for (const outputNode of outputNodes) {
700
- await evaluateNode(outputNode.id);
1394
+ try {
1395
+ for (const outputNode of outputNodes) {
1396
+ await evaluateNode(outputNode.id);
1397
+ }
701
1398
  }
702
- const result = buildOutputResult(connector, values);
1399
+ catch (error) {
1400
+ debugErrors.push(error instanceof Error ? error.message : "Flow execution failed");
1401
+ throw error;
1402
+ }
1403
+ const result = aiConnectorRunResultSchema.parse({
1404
+ ...buildOutputResult(connector, values, nodeResults),
1405
+ debugTrace: parsedInput.debug
1406
+ ? {
1407
+ nodes: debugNodes,
1408
+ errors: debugErrors
1409
+ }
1410
+ : undefined
1411
+ });
703
1412
  const conversationProviderNode = connector.graph.nodes.find((node) => node.type === "chat");
704
1413
  const resolvedConversationId = [...values.values()].find((entry) => entry.conversationId)?.conversationId ?? null;
705
1414
  const nextConversation = conversationProviderNode
@@ -792,7 +1501,7 @@ function migrateLegacyProcessor(processorId) {
792
1501
  data: {
793
1502
  label: "Output",
794
1503
  description: "Imported legacy output.",
795
- outputKey: "primary",
1504
+ outputKey: "answer",
796
1505
  enabledToolKeys: []
797
1506
  }
798
1507
  };
@@ -852,13 +1561,13 @@ export function createAiConnector(input) {
852
1561
  const now = new Date().toISOString();
853
1562
  const id = `aic_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
854
1563
  const slug = buildConnectorSlug(parsed.title, id);
855
- 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));
856
1565
  const publishedOutputs = ensurePublishedOutputs(id, graph);
857
1566
  getDatabase()
858
1567
  .prepare(`INSERT INTO ai_connectors (
859
- 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
860
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
861
- .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);
862
1571
  return getAiConnectorById(id);
863
1572
  }
864
1573
  export function updateAiConnector(connectorId, patch) {
@@ -867,7 +1576,7 @@ export function updateAiConnector(connectorId, patch) {
867
1576
  return null;
868
1577
  }
869
1578
  const parsed = updateAiConnectorSchema.parse(patch);
870
- const nextGraph = parsed.graph ?? current.graph;
1579
+ const nextGraph = normalizeConnectorGraph(parsed.graph ?? current.graph);
871
1580
  validateConnectorGraph(nextGraph);
872
1581
  const nextTitle = parsed.title ?? current.title;
873
1582
  const next = {
@@ -878,14 +1587,15 @@ export function updateAiConnector(connectorId, patch) {
878
1587
  ? buildConnectorSlug(parsed.title, current.id)
879
1588
  : current.slug,
880
1589
  graph: nextGraph,
1590
+ publicInputs: parsed.publicInputs ?? current.publicInputs,
881
1591
  publishedOutputs: ensurePublishedOutputs(current.id, nextGraph)
882
1592
  };
883
1593
  const now = new Date().toISOString();
884
1594
  getDatabase()
885
1595
  .prepare(`UPDATE ai_connectors
886
- 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 = ?
887
1597
  WHERE id = ?`)
888
- .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);
889
1599
  return getAiConnectorById(connectorId);
890
1600
  }
891
1601
  export function deleteAiConnector(connectorId) {
@@ -907,6 +1617,7 @@ export async function runAiConnector(connectorId, input, services, mode = "run")
907
1617
  mode,
908
1618
  status: "running",
909
1619
  userInput: input.userInput ?? "",
1620
+ inputs: input.inputs ?? {},
910
1621
  context: input.context ?? {},
911
1622
  conversationId: input.conversationId ?? null,
912
1623
  result: null,