forge-openclaw-plugin 0.2.25 → 0.2.26

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 (189) hide show
  1. package/dist/assets/{board-VmF4FAfr.js → board-ta0rUHOf.js} +3 -3
  2. package/dist/assets/{board-VmF4FAfr.js.map → board-ta0rUHOf.js.map} +1 -1
  3. package/dist/assets/index-Ro0ZF_az.css +1 -0
  4. package/dist/assets/index-ytlpSj23.js +79 -0
  5. package/dist/assets/index-ytlpSj23.js.map +1 -0
  6. package/dist/assets/{motion-DvkU14p-.js → motion-fBKPB6yw.js} +2 -2
  7. package/dist/assets/{motion-DvkU14p-.js.map → motion-fBKPB6yw.js.map} +1 -1
  8. package/dist/assets/{table-DgiPof9E.js → table-C-IGTQni.js} +2 -2
  9. package/dist/assets/{table-DgiPof9E.js.map → table-C-IGTQni.js.map} +1 -1
  10. package/dist/assets/{ui-nYfoC0Gq.js → ui-DInOpaYF.js} +2 -2
  11. package/dist/assets/{ui-nYfoC0Gq.js.map → ui-DInOpaYF.js.map} +1 -1
  12. package/dist/assets/vendor-lE3tZJcC.js +876 -0
  13. package/dist/assets/vendor-lE3tZJcC.js.map +1 -0
  14. package/dist/index.html +7 -8
  15. package/dist/openclaw/local-runtime.d.ts +3 -1
  16. package/dist/openclaw/local-runtime.js +51 -15
  17. package/dist/openclaw/plugin-entry-shared.js +24 -2
  18. package/dist/openclaw/plugin-sdk-types.d.ts +17 -0
  19. package/dist/openclaw/tools.js +0 -3
  20. package/dist/server/server/migrations/001_core.sql +411 -0
  21. package/dist/server/server/migrations/002_psyche.sql +392 -0
  22. package/dist/server/server/migrations/003_habits.sql +30 -0
  23. package/dist/server/server/migrations/004_habit_links.sql +8 -0
  24. package/dist/server/server/migrations/005_habit_psyche_links.sql +24 -0
  25. package/dist/server/server/migrations/006_work_adjustments.sql +14 -0
  26. package/dist/server/server/migrations/007_weekly_review_closures.sql +17 -0
  27. package/dist/server/server/migrations/008_calendar_execution.sql +147 -0
  28. package/dist/server/server/migrations/009_true_calendar_events.sql +195 -0
  29. package/dist/server/server/migrations/010_calendar_selection_state.sql +6 -0
  30. package/dist/server/server/migrations/011_calendar_timezone_backfill.sql +11 -0
  31. package/dist/server/server/migrations/012_work_block_ranges.sql +7 -0
  32. package/dist/server/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
  33. package/dist/server/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
  34. package/dist/server/server/migrations/015_multi_user_and_strategies.sql +244 -0
  35. package/dist/server/server/migrations/016_health_companion.sql +158 -0
  36. package/dist/server/server/migrations/016_strategy_contracts_and_user_graph.sql +22 -0
  37. package/dist/server/server/migrations/017_preferences.sql +131 -0
  38. package/dist/server/server/migrations/018_preference_catalogs.sql +31 -0
  39. package/dist/server/server/migrations/019_wiki_memory.sql +255 -0
  40. package/dist/server/server/migrations/020_wiki_page_hierarchy.sql +11 -0
  41. package/dist/server/server/migrations/021_hide_evidence_from_wiki_index.sql +3 -0
  42. package/dist/server/server/migrations/022_wiki_ingest_background.sql +85 -0
  43. package/dist/server/server/migrations/023_diagnostic_logs.sql +28 -0
  44. package/dist/server/server/migrations/024_questionnaires.sql +96 -0
  45. package/dist/server/server/migrations/025_ai_model_connections.sql +26 -0
  46. package/dist/server/server/migrations/026_custom_theme_settings.sql +2 -0
  47. package/dist/server/server/migrations/027_ai_processors.sql +31 -0
  48. package/dist/server/server/migrations/028_movement_domain.sql +136 -0
  49. package/dist/server/server/migrations/029_watch_micro_capture.sql +23 -0
  50. package/dist/server/server/migrations/030_surface_layouts.sql +5 -0
  51. package/dist/server/server/migrations/031_ai_processor_runtime_upgrades.sql +10 -0
  52. package/dist/server/server/migrations/032_ai_connectors.sql +44 -0
  53. package/dist/server/server/migrations/033_movement_trip_point_sync.sql +36 -0
  54. package/dist/server/server/migrations/034_movement_segment_sync.sql +49 -0
  55. package/dist/server/server/migrations/035_google_local_auth_settings.sql +2 -0
  56. package/dist/server/server/migrations/036_google_local_auth_client_secret.sql +2 -0
  57. package/dist/server/{app.js → server/src/app.js} +242 -111
  58. package/dist/server/server/src/connectors/box-registry.js +188 -0
  59. package/dist/server/{db.js → server/src/db.js} +4 -0
  60. package/dist/server/server/src/debug.js +19 -0
  61. package/dist/server/{openapi.js → server/src/openapi.js} +2 -2
  62. package/dist/server/{repositories → server/src/repositories}/ai-connectors.js +286 -23
  63. package/dist/server/{repositories → server/src/repositories}/calendar.js +1 -1
  64. package/dist/server/{repositories → server/src/repositories}/settings.js +51 -3
  65. package/dist/server/{services → server/src/services}/calendar-runtime.js +775 -58
  66. package/dist/server/server/src/services/google-calendar-oauth-config.js +176 -0
  67. package/dist/server/{types.js → server/src/types.js} +137 -19
  68. package/dist/server/{web.js → server/src/web.js} +21 -2
  69. package/dist/server/src/components/customization/utility-widgets.js +330 -0
  70. package/dist/server/src/components/workbench-boxes/health/health-boxes.js +92 -0
  71. package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +128 -0
  72. package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +37 -0
  73. package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +114 -0
  74. package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +57 -0
  75. package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +4 -0
  76. package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +13 -0
  77. package/dist/server/src/components/workbench-boxes/today/today-boxes.js +63 -0
  78. package/dist/server/src/lib/api-error.js +37 -0
  79. package/dist/server/src/lib/api.js +1859 -0
  80. package/dist/server/src/lib/calendar-name-deduper.js +144 -0
  81. package/dist/server/src/lib/diagnostics.js +67 -0
  82. package/dist/server/src/lib/psyche-types.js +1 -0
  83. package/dist/server/src/lib/questionnaire-types.js +1 -0
  84. package/dist/server/src/lib/runtime-paths.js +24 -0
  85. package/dist/server/src/lib/schemas.js +234 -0
  86. package/dist/server/src/lib/snapshot-normalizer.js +374 -0
  87. package/dist/server/src/lib/theme-system.js +319 -0
  88. package/dist/server/src/lib/types.js +1 -0
  89. package/dist/server/src/lib/utils.js +22 -0
  90. package/dist/server/src/lib/workbench/boxes.js +16 -0
  91. package/dist/server/src/lib/workbench/nodes.js +15 -0
  92. package/dist/server/src/lib/workbench/registry.js +73 -0
  93. package/dist/server/src/lib/workbench/runtime.js +181 -0
  94. package/openclaw.plugin.json +1 -1
  95. package/package.json +1 -1
  96. package/server/index.js +68 -0
  97. package/server/migrations/035_google_local_auth_settings.sql +2 -0
  98. package/server/migrations/036_google_local_auth_client_secret.sql +2 -0
  99. package/skills/forge-openclaw/SKILL.md +3 -0
  100. package/skills/forge-openclaw/entity_conversation_playbooks.md +213 -24
  101. package/skills/forge-openclaw/psyche_entity_playbooks.md +82 -3
  102. package/dist/assets/index-CFCKDIMH.js +0 -67
  103. package/dist/assets/index-CFCKDIMH.js.map +0 -1
  104. package/dist/assets/index-ZPY6U1TU.css +0 -1
  105. package/dist/assets/vendor-D9PTEPSB.js +0 -824
  106. package/dist/assets/vendor-D9PTEPSB.js.map +0 -1
  107. package/dist/assets/viz-Cqb6s--o.js +0 -34
  108. package/dist/assets/viz-Cqb6s--o.js.map +0 -1
  109. package/dist/server/connectors/box-registry.js +0 -257
  110. /package/dist/server/{demo-data.js → server/src/demo-data.js} +0 -0
  111. /package/dist/server/{discovery-advertiser.js → server/src/discovery-advertiser.js} +0 -0
  112. /package/dist/server/{e2e-server.js → server/src/e2e-server.js} +0 -0
  113. /package/dist/server/{errors.js → server/src/errors.js} +0 -0
  114. /package/dist/server/{health.js → server/src/health.js} +0 -0
  115. /package/dist/server/{index.js → server/src/index.js} +0 -0
  116. /package/dist/server/{managers → server/src/managers}/base.js +0 -0
  117. /package/dist/server/{managers → server/src/managers}/contracts.js +0 -0
  118. /package/dist/server/{managers → server/src/managers}/platform/api-gateway-manager.js +0 -0
  119. /package/dist/server/{managers → server/src/managers}/platform/audit-manager.js +0 -0
  120. /package/dist/server/{managers → server/src/managers}/platform/authentication-manager.js +0 -0
  121. /package/dist/server/{managers → server/src/managers}/platform/authorization-manager.js +0 -0
  122. /package/dist/server/{managers → server/src/managers}/platform/background-job-manager.js +0 -0
  123. /package/dist/server/{managers → server/src/managers}/platform/configuration-manager.js +0 -0
  124. /package/dist/server/{managers → server/src/managers}/platform/database-manager.js +0 -0
  125. /package/dist/server/{managers → server/src/managers}/platform/event-bus-manager.js +0 -0
  126. /package/dist/server/{managers → server/src/managers}/platform/external-service-manager.js +0 -0
  127. /package/dist/server/{managers → server/src/managers}/platform/health-manager.js +0 -0
  128. /package/dist/server/{managers → server/src/managers}/platform/llm-manager.js +0 -0
  129. /package/dist/server/{managers → server/src/managers}/platform/migration-manager.js +0 -0
  130. /package/dist/server/{managers → server/src/managers}/platform/openai-responses-provider.js +0 -0
  131. /package/dist/server/{managers → server/src/managers}/platform/search-index-manager.js +0 -0
  132. /package/dist/server/{managers → server/src/managers}/platform/secrets-manager.js +0 -0
  133. /package/dist/server/{managers → server/src/managers}/platform/session-manager.js +0 -0
  134. /package/dist/server/{managers → server/src/managers}/platform/storage-manager.js +0 -0
  135. /package/dist/server/{managers → server/src/managers}/platform/token-manager.js +0 -0
  136. /package/dist/server/{managers → server/src/managers}/platform/transaction-manager.js +0 -0
  137. /package/dist/server/{managers → server/src/managers}/platform/trusted-network.js +0 -0
  138. /package/dist/server/{managers → server/src/managers}/runtime.js +0 -0
  139. /package/dist/server/{managers → server/src/managers}/type-guards.js +0 -0
  140. /package/dist/server/{movement.js → server/src/movement.js} +0 -0
  141. /package/dist/server/{preferences-seeds.js → server/src/preferences-seeds.js} +0 -0
  142. /package/dist/server/{preferences-types.js → server/src/preferences-types.js} +0 -0
  143. /package/dist/server/{psyche-types.js → server/src/psyche-types.js} +0 -0
  144. /package/dist/server/{questionnaire-flow.js → server/src/questionnaire-flow.js} +0 -0
  145. /package/dist/server/{questionnaire-seeds.js → server/src/questionnaire-seeds.js} +0 -0
  146. /package/dist/server/{questionnaire-types.js → server/src/questionnaire-types.js} +0 -0
  147. /package/dist/server/{repositories → server/src/repositories}/activity-events.js +0 -0
  148. /package/dist/server/{repositories → server/src/repositories}/ai-processors.js +0 -0
  149. /package/dist/server/{repositories → server/src/repositories}/collaboration.js +0 -0
  150. /package/dist/server/{repositories → server/src/repositories}/deleted-entities.js +0 -0
  151. /package/dist/server/{repositories → server/src/repositories}/diagnostic-logs.js +0 -0
  152. /package/dist/server/{repositories → server/src/repositories}/domains.js +0 -0
  153. /package/dist/server/{repositories → server/src/repositories}/entity-ownership.js +0 -0
  154. /package/dist/server/{repositories → server/src/repositories}/event-log.js +0 -0
  155. /package/dist/server/{repositories → server/src/repositories}/goals.js +0 -0
  156. /package/dist/server/{repositories → server/src/repositories}/habits.js +0 -0
  157. /package/dist/server/{repositories → server/src/repositories}/model-settings.js +0 -0
  158. /package/dist/server/{repositories → server/src/repositories}/notes.js +0 -0
  159. /package/dist/server/{repositories → server/src/repositories}/preferences.js +0 -0
  160. /package/dist/server/{repositories → server/src/repositories}/projects.js +0 -0
  161. /package/dist/server/{repositories → server/src/repositories}/psyche.js +0 -0
  162. /package/dist/server/{repositories → server/src/repositories}/questionnaires.js +0 -0
  163. /package/dist/server/{repositories → server/src/repositories}/rewards.js +0 -0
  164. /package/dist/server/{repositories → server/src/repositories}/strategies.js +0 -0
  165. /package/dist/server/{repositories → server/src/repositories}/surface-layouts.js +0 -0
  166. /package/dist/server/{repositories → server/src/repositories}/tags.js +0 -0
  167. /package/dist/server/{repositories → server/src/repositories}/task-runs.js +0 -0
  168. /package/dist/server/{repositories → server/src/repositories}/tasks.js +0 -0
  169. /package/dist/server/{repositories → server/src/repositories}/users.js +0 -0
  170. /package/dist/server/{repositories → server/src/repositories}/weekly-reviews.js +0 -0
  171. /package/dist/server/{repositories → server/src/repositories}/wiki-memory.js +0 -0
  172. /package/dist/server/{repositories → server/src/repositories}/work-adjustments.js +0 -0
  173. /package/dist/server/{seed-demo.js → server/src/seed-demo.js} +0 -0
  174. /package/dist/server/{services → server/src/services}/context.js +0 -0
  175. /package/dist/server/{services → server/src/services}/dashboard.js +0 -0
  176. /package/dist/server/{services → server/src/services}/entity-crud.js +0 -0
  177. /package/dist/server/{services → server/src/services}/gamification.js +0 -0
  178. /package/dist/server/{services → server/src/services}/insights.js +0 -0
  179. /package/dist/server/{services → server/src/services}/openai-codex-oauth.js +0 -0
  180. /package/dist/server/{services → server/src/services}/projects.js +0 -0
  181. /package/dist/server/{services → server/src/services}/psyche-observation-calendar.js +0 -0
  182. /package/dist/server/{services → server/src/services}/psyche.js +0 -0
  183. /package/dist/server/{services → server/src/services}/relations.js +0 -0
  184. /package/dist/server/{services → server/src/services}/reviews.js +0 -0
  185. /package/dist/server/{services → server/src/services}/run-recovery.js +0 -0
  186. /package/dist/server/{services → server/src/services}/tagging.js +0 -0
  187. /package/dist/server/{services → server/src/services}/task-run-watchdog.js +0 -0
  188. /package/dist/server/{services → server/src/services}/work-time.js +0 -0
  189. /package/dist/server/{watch-mobile.js → server/src/watch-mobile.js} +0 -0
@@ -3,6 +3,7 @@ import { mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { DatabaseSync } from "node:sqlite";
6
+ import { logForgeDebug } from "./debug.js";
6
7
  import { ensureQuestionnaireSeeds } from "./repositories/questionnaires.js";
7
8
  function nowIso() {
8
9
  return new Date().toISOString();
@@ -289,6 +290,7 @@ export async function initializeDatabase() {
289
290
  await mkdir(getDataDir(), { recursive: true });
290
291
  const database = getDatabase();
291
292
  const migrationFiles = await listMigrationFiles();
293
+ const pendingMigrations = [];
292
294
  database.exec(`
293
295
  CREATE TABLE IF NOT EXISTS migrations (
294
296
  id TEXT PRIMARY KEY,
@@ -303,6 +305,7 @@ export async function initializeDatabase() {
303
305
  if (applied.has(file)) {
304
306
  continue;
305
307
  }
308
+ pendingMigrations.push(file);
306
309
  const sql = await readFile(path.join(migrationsDir, file), "utf8");
307
310
  database.exec("BEGIN");
308
311
  try {
@@ -317,6 +320,7 @@ export async function initializeDatabase() {
317
320
  throw error;
318
321
  }
319
322
  }
323
+ logForgeDebug(`[forge-db] initialized database path=${getDatabasePath()} applied_count=${appliedRows.length} pending_applied=${pendingMigrations.length} pending_list=${pendingMigrations.join(",") || "none"}`);
320
324
  if (seedDemoDataEnabled) {
321
325
  seedData();
322
326
  }
@@ -0,0 +1,19 @@
1
+ function isTruthyFlag(value) {
2
+ if (!value) {
3
+ return false;
4
+ }
5
+ const normalized = value.trim().toLowerCase();
6
+ return (normalized === "1" ||
7
+ normalized === "true" ||
8
+ normalized === "yes" ||
9
+ normalized === "on");
10
+ }
11
+ export function isForgeDebugLoggingEnabled(env = process.env) {
12
+ return isTruthyFlag(env.FORGE_DEBUG_LOGS);
13
+ }
14
+ export function logForgeDebug(message, env = process.env) {
15
+ if (!isForgeDebugLoggingEnabled(env)) {
16
+ return;
17
+ }
18
+ console.info(message);
19
+ }
@@ -1998,7 +1998,7 @@ export function buildOpenApiDocument() {
1998
1998
  },
1999
1999
  themePreference: {
2000
2000
  type: "string",
2001
- enum: ["obsidian", "solar", "aurora", "ember", "custom", "system"]
2001
+ enum: ["obsidian", "solar", "aurora", "ember", "paper", "dawn", "atelier", "custom", "system"]
2002
2002
  },
2003
2003
  customTheme: nullable({
2004
2004
  type: "object",
@@ -2515,7 +2515,7 @@ export function buildOpenApiDocument() {
2515
2515
  execution: { $ref: "#/components/schemas/ExecutionSettings" },
2516
2516
  themePreference: {
2517
2517
  type: "string",
2518
- enum: ["obsidian", "solar", "aurora", "ember", "custom", "system"]
2518
+ enum: ["obsidian", "solar", "aurora", "ember", "paper", "dawn", "atelier", "custom", "system"]
2519
2519
  },
2520
2520
  customTheme: nullable({
2521
2521
  type: "object",
@@ -193,17 +193,17 @@ function ensurePublishedOutputs(connectorId, graph) {
193
193
  outputId: "primary"
194
194
  })
195
195
  ].map((entry) => ({
196
- id: entry.boxId.replace(/^connector-output:/, ""),
196
+ id: entry.id.replace(/^connector-output:/, ""),
197
197
  nodeId: "node_output",
198
- label: entry.label,
199
- apiPath: `/api/v1/ai-connectors/${connectorId}/output`
198
+ label: entry.title,
199
+ apiPath: `/api/v1/workbench/flows/${connectorId}/output`
200
200
  }));
201
201
  }
202
202
  return outputNodes.map((node, index) => ({
203
203
  id: `${connectorId}_out_${index + 1}`,
204
204
  nodeId: node.id,
205
205
  label: node.data.label || `Output ${index + 1}`,
206
- apiPath: `/api/v1/ai-connectors/${connectorId}/output`
206
+ apiPath: `/api/v1/workbench/flows/${connectorId}/output`
207
207
  }));
208
208
  }
209
209
  function mapRun(row) {
@@ -322,6 +322,75 @@ function tryParseStructuredAgentResponse(value) {
322
322
  return null;
323
323
  }
324
324
  }
325
+ function tryParseJsonObject(value) {
326
+ try {
327
+ const parsed = JSON.parse(value);
328
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
329
+ return parsed;
330
+ }
331
+ return null;
332
+ }
333
+ catch {
334
+ return null;
335
+ }
336
+ }
337
+ function coerceText(value) {
338
+ if (typeof value === "string") {
339
+ return value;
340
+ }
341
+ if (value == null) {
342
+ return "";
343
+ }
344
+ if (typeof value === "number" || typeof value === "boolean") {
345
+ return String(value);
346
+ }
347
+ try {
348
+ return JSON.stringify(value);
349
+ }
350
+ catch {
351
+ return "";
352
+ }
353
+ }
354
+ function buildOutputMap(primaryText, primaryJson, outputKeys = []) {
355
+ const outputMap = {
356
+ primary: {
357
+ text: primaryText,
358
+ json: primaryJson
359
+ }
360
+ };
361
+ for (const key of outputKeys) {
362
+ if (!primaryJson || !(key in primaryJson)) {
363
+ continue;
364
+ }
365
+ const value = primaryJson[key];
366
+ outputMap[key] = {
367
+ text: coerceText(value),
368
+ json: value && typeof value === "object" && !Array.isArray(value)
369
+ ? value
370
+ : null
371
+ };
372
+ }
373
+ return outputMap;
374
+ }
375
+ function readOutputSelection(value, handle) {
376
+ if (!handle || handle === "primary") {
377
+ return { text: value.text, json: value.json };
378
+ }
379
+ const selected = value.outputMap[handle];
380
+ if (selected) {
381
+ return selected;
382
+ }
383
+ if (value.json && handle in value.json) {
384
+ const raw = value.json[handle];
385
+ return {
386
+ text: coerceText(raw),
387
+ json: raw && typeof raw === "object" && !Array.isArray(raw)
388
+ ? raw
389
+ : null
390
+ };
391
+ }
392
+ return { text: value.text, json: value.json };
393
+ }
325
394
  async function executeMachineTool(tool, args) {
326
395
  if (tool === "machine_read_file") {
327
396
  const targetPath = typeof args.path === "string" ? resolveAllowedPath(args.path) : null;
@@ -522,24 +591,35 @@ async function runModelNode(input) {
522
591
  if (activeTools.length === 0) {
523
592
  return {
524
593
  text: rawText.trim(),
525
- conversationId
594
+ json: tryParseJsonObject(rawText.trim()),
595
+ conversationId,
596
+ logs: transcript
526
597
  };
527
598
  }
528
599
  const structured = tryParseStructuredAgentResponse(rawText.trim());
529
600
  if (!structured || structured.action === "final") {
530
601
  return {
531
602
  text: structured?.text?.trim() || rawText.trim(),
532
- conversationId
603
+ json: tryParseJsonObject(structured?.text?.trim() || rawText.trim()),
604
+ conversationId,
605
+ logs: transcript
533
606
  };
534
607
  }
535
608
  const toolResult = structured.tool.startsWith("machine_")
536
609
  ? await executeMachineTool(structured.tool, structured.args)
537
- : await executeForgeBoxTool(activeTools.find((tool) => tool.key === structured.tool)?.boxId ?? "", structured.tool, structured.args);
610
+ : await executeForgeBoxTool(activeTools.find((tool) => tool.key === structured.tool)?.boxId ?? "", structured.tool, structured.args, {
611
+ actor: {
612
+ userIds: null,
613
+ source: "agent"
614
+ }
615
+ });
538
616
  transcript.push(`Tool call ${structured.tool}: ${JSON.stringify(structured.args)}`, `Tool result: ${JSON.stringify(toolResult)}`);
539
617
  }
540
618
  return {
541
619
  text: "Connector stopped after reaching the maximum tool step count.",
542
- conversationId
620
+ json: null,
621
+ conversationId,
622
+ logs: transcript
543
623
  };
544
624
  }
545
625
  function validateConnectorGraph(graph) {
@@ -593,6 +673,27 @@ function buildOutputResult(connector, resolvedNodeValues) {
593
673
  outputs
594
674
  });
595
675
  }
676
+ function parseValueLiteral(valueType, valueLiteral) {
677
+ if (valueType === "null") {
678
+ return null;
679
+ }
680
+ if (valueType === "boolean") {
681
+ return valueLiteral.trim().toLowerCase() === "true";
682
+ }
683
+ if (valueType === "number") {
684
+ const parsed = Number(valueLiteral);
685
+ return Number.isFinite(parsed) ? parsed : 0;
686
+ }
687
+ if (valueType === "array" || valueType === "object") {
688
+ try {
689
+ return JSON.parse(valueLiteral || (valueType === "array" ? "[]" : "{}"));
690
+ }
691
+ catch {
692
+ return valueType === "array" ? [] : {};
693
+ }
694
+ }
695
+ return valueLiteral;
696
+ }
596
697
  function createConversationRecord(input) {
597
698
  const now = new Date().toISOString();
598
699
  return saveAiConnectorConversation(aiConnectorConversationSchema.parse({
@@ -615,6 +716,8 @@ async function executeConnector(connector, rawInput, services) {
615
716
  incoming.set(edge.target, list);
616
717
  }
617
718
  const values = new Map();
719
+ const debugNodes = [];
720
+ const debugErrors = [];
618
721
  const outputNodes = connector.graph.nodes.filter((node) => node.type === "output");
619
722
  const activeConversation = parsedInput.conversationId
620
723
  ? getAiConnectorConversationById(parsedInput.conversationId)
@@ -628,18 +731,50 @@ async function executeConnector(connector, rawInput, services) {
628
731
  if (!node) {
629
732
  throw new Error(`Missing connector node ${nodeId}.`);
630
733
  }
631
- const upstream = await Promise.all((incoming.get(nodeId) ?? []).map((edge) => evaluateNode(edge.source)));
734
+ const upstreamEdges = incoming.get(nodeId) ?? [];
735
+ const upstream = await Promise.all(upstreamEdges.map(async (edge) => {
736
+ const upstreamValue = await evaluateNode(edge.source);
737
+ const selected = readOutputSelection(upstreamValue, edge.sourceHandle);
738
+ return {
739
+ edge,
740
+ sourceValue: upstreamValue,
741
+ selected
742
+ };
743
+ }));
632
744
  let resolved;
633
- if (node.type === "box_input") {
745
+ if (node.type === "box" || node.type === "box_input") {
634
746
  const boxId = node.data.boxId?.trim() || "";
747
+ const resolvedInputs = Object.fromEntries(upstream.map(({ edge, selected }, index) => [
748
+ edge.targetHandle ?? edge.sourceHandle ?? `input_${index + 1}`,
749
+ selected.json ?? selected.text
750
+ ]));
751
+ const resolvedParams = node.data.paramValues && typeof node.data.paramValues === "object"
752
+ ? node.data.paramValues
753
+ : {};
635
754
  const providedSnapshot = boxId ? parsedInput.boxSnapshots[boxId] : null;
636
755
  const snapshot = providedSnapshot && typeof providedSnapshot === "object"
637
756
  ? {
638
- ...resolveForgeBoxSnapshot(boxId),
757
+ ...resolveForgeBoxSnapshot(boxId, {
758
+ actor: {
759
+ userIds: null,
760
+ source: "agent"
761
+ }
762
+ }, {
763
+ inputs: resolvedInputs,
764
+ params: resolvedParams
765
+ }),
639
766
  contentJson: providedSnapshot
640
767
  }
641
768
  : boxId
642
- ? resolveForgeBoxSnapshot(boxId)
769
+ ? resolveForgeBoxSnapshot(boxId, {
770
+ actor: {
771
+ userIds: null,
772
+ source: "agent"
773
+ }
774
+ }, {
775
+ inputs: resolvedInputs,
776
+ params: resolvedParams
777
+ })
643
778
  : {
644
779
  boxId: "",
645
780
  label: node.data.label,
@@ -648,6 +783,10 @@ async function executeConnector(connector, rawInput, services) {
648
783
  contentJson: null,
649
784
  tools: []
650
785
  };
786
+ const outputKeys = [
787
+ ...(node.data.outputs ?? []).map((port) => port.key),
788
+ ...Object.keys(snapshot.contentJson ?? {})
789
+ ];
651
790
  resolved = {
652
791
  text: snapshot.contentText,
653
792
  json: snapshot.contentJson,
@@ -657,7 +796,28 @@ async function executeConnector(connector, rawInput, services) {
657
796
  label: tool.label,
658
797
  description: tool.description
659
798
  })),
660
- conversationId: null
799
+ conversationId: null,
800
+ outputMap: buildOutputMap(snapshot.contentText, snapshot.contentJson, outputKeys),
801
+ logs: []
802
+ };
803
+ }
804
+ else if (node.type === "value") {
805
+ const parsedValue = parseValueLiteral(node.data.valueType ?? "string", node.data.valueLiteral ?? "");
806
+ const jsonValue = parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue)
807
+ ? parsedValue
808
+ : null;
809
+ const textValue = parsedValue === null
810
+ ? "null"
811
+ : typeof parsedValue === "string"
812
+ ? parsedValue
813
+ : JSON.stringify(parsedValue, null, 2);
814
+ resolved = {
815
+ text: textValue,
816
+ json: jsonValue,
817
+ tools: [],
818
+ conversationId: null,
819
+ outputMap: buildOutputMap(textValue, jsonValue, ["primary"]),
820
+ logs: []
661
821
  };
662
822
  }
663
823
  else if (node.type === "user_input") {
@@ -665,16 +825,76 @@ async function executeConnector(connector, rawInput, services) {
665
825
  text: parsedInput.userInput || "",
666
826
  json: Object.keys(parsedInput.context).length > 0 ? parsedInput.context : null,
667
827
  tools: [],
668
- conversationId: activeConversation?.id ?? null
828
+ conversationId: activeConversation?.id ?? null,
829
+ outputMap: buildOutputMap(parsedInput.userInput || "", Object.keys(parsedInput.context).length > 0 ? parsedInput.context : null, Object.keys(parsedInput.context ?? {})),
830
+ logs: []
831
+ };
832
+ }
833
+ else if (node.type === "merge") {
834
+ const mergedText = upstream
835
+ .map((entry) => entry.selected.text)
836
+ .filter(Boolean)
837
+ .join("\n\n");
838
+ const mergedJson = Object.assign({}, ...upstream
839
+ .map((entry) => entry.selected.json)
840
+ .filter((entry) => Boolean(entry) && typeof entry === "object"));
841
+ resolved = {
842
+ text: mergedText,
843
+ json: Object.keys(mergedJson).length > 0 ? mergedJson : null,
844
+ tools: upstream.flatMap((entry) => entry.sourceValue.tools),
845
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
846
+ .conversationId ?? null,
847
+ outputMap: buildOutputMap(mergedText, Object.keys(mergedJson).length > 0 ? mergedJson : null, Object.keys(mergedJson)),
848
+ logs: []
849
+ };
850
+ }
851
+ else if (node.type === "template") {
852
+ const primary = upstream[0]?.selected ?? { text: "", json: null };
853
+ const rendered = (node.data.template ?? node.data.promptTemplate ?? "")
854
+ .replaceAll("{{input}}", primary.text)
855
+ .replaceAll("{{json}}", primary.json ? JSON.stringify(primary.json) : "");
856
+ resolved = {
857
+ text: rendered,
858
+ json: tryParseJsonObject(rendered),
859
+ tools: [],
860
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
861
+ .conversationId ?? null,
862
+ outputMap: buildOutputMap(rendered, tryParseJsonObject(rendered)),
863
+ logs: []
864
+ };
865
+ }
866
+ else if (node.type === "pick_key") {
867
+ const primary = upstream[0]?.selected ?? { text: "", json: null };
868
+ const selectedKey = node.data.selectedKey?.trim() || "";
869
+ const selectedValue = primary.json && selectedKey in primary.json ? primary.json[selectedKey] : null;
870
+ const selectedJson = selectedValue &&
871
+ typeof selectedValue === "object" &&
872
+ !Array.isArray(selectedValue)
873
+ ? selectedValue
874
+ : null;
875
+ resolved = {
876
+ text: coerceText(selectedValue),
877
+ json: selectedJson,
878
+ tools: [],
879
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
880
+ .conversationId ?? null,
881
+ outputMap: buildOutputMap(coerceText(selectedValue), selectedJson),
882
+ logs: []
669
883
  };
670
884
  }
671
885
  else if (node.type === "output") {
672
- const mergedText = upstream.map((entry) => entry.text).filter(Boolean).join("\n\n");
886
+ const mergedText = upstream
887
+ .map((entry) => entry.selected.text)
888
+ .filter(Boolean)
889
+ .join("\n\n");
673
890
  resolved = {
674
891
  text: mergedText,
675
- json: upstream[0]?.json ?? null,
892
+ json: upstream[0]?.selected.json ?? null,
676
893
  tools: [],
677
- conversationId: upstream.find((entry) => entry.conversationId)?.conversationId ?? null
894
+ conversationId: upstream.find((entry) => entry.sourceValue.conversationId)?.sourceValue
895
+ .conversationId ?? null,
896
+ outputMap: buildOutputMap(mergedText, upstream[0]?.selected.json ?? null, Object.keys(upstream[0]?.selected.json ?? {})),
897
+ logs: []
678
898
  };
679
899
  }
680
900
  else {
@@ -682,24 +902,67 @@ async function executeConnector(connector, rawInput, services) {
682
902
  connector,
683
903
  node,
684
904
  userInput: parsedInput.userInput,
685
- upstream,
905
+ upstream: upstream.map((entry) => ({
906
+ text: entry.selected.text,
907
+ json: entry.selected.json,
908
+ tools: entry.sourceValue.tools,
909
+ conversationId: entry.sourceValue.conversationId,
910
+ outputMap: entry.sourceValue.outputMap,
911
+ logs: entry.sourceValue.logs
912
+ })),
686
913
  services,
687
914
  conversation: activeConversation
688
915
  });
916
+ const outputKeys = (node.data.outputs ?? []).map((port) => port.key);
689
917
  resolved = {
690
918
  text: modelResult.text,
691
- json: null,
919
+ json: modelResult.json,
692
920
  tools: [],
693
- conversationId: modelResult.conversationId
921
+ conversationId: modelResult.conversationId,
922
+ outputMap: buildOutputMap(modelResult.text, modelResult.json, outputKeys),
923
+ logs: modelResult.logs
694
924
  };
695
925
  }
696
926
  values.set(nodeId, resolved);
927
+ debugNodes.push({
928
+ nodeId: node.id,
929
+ nodeType: node.type,
930
+ label: node.data.label,
931
+ input: upstream.map((entry) => ({
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
+ })),
938
+ output: {
939
+ text: resolved.text,
940
+ json: resolved.json
941
+ },
942
+ tools: resolved.tools.map((tool) => tool.key),
943
+ logs: resolved.logs,
944
+ error: null
945
+ });
697
946
  return resolved;
698
947
  };
699
- for (const outputNode of outputNodes) {
700
- await evaluateNode(outputNode.id);
948
+ try {
949
+ for (const outputNode of outputNodes) {
950
+ await evaluateNode(outputNode.id);
951
+ }
701
952
  }
702
- const result = buildOutputResult(connector, values);
953
+ catch (error) {
954
+ debugErrors.push(error instanceof Error ? error.message : "Flow execution failed");
955
+ throw error;
956
+ }
957
+ const result = aiConnectorRunResultSchema.parse({
958
+ ...buildOutputResult(connector, values),
959
+ debugTrace: parsedInput.debug
960
+ ? {
961
+ nodes: debugNodes,
962
+ errors: debugErrors
963
+ }
964
+ : undefined
965
+ });
703
966
  const conversationProviderNode = connector.graph.nodes.find((node) => node.type === "chat");
704
967
  const resolvedConversationId = [...values.values()].find((entry) => entry.conversationId)?.conversationId ?? null;
705
968
  const nextConversation = conversationProviderNode
@@ -1132,7 +1132,7 @@ export function getCalendarOverview(query) {
1132
1132
  provider: "google",
1133
1133
  label: "Google Calendar",
1134
1134
  supportsDedicatedForgeCalendar: true,
1135
- connectionHelp: "Use a Google refresh token plus client credentials to sync calendars and publish Forge-owned events and timeboxes."
1135
+ connectionHelp: "Forge uses a localhost Authorization Code + PKCE flow. Users sign in with Google from the same machine running Forge, Forge exchanges the code on the backend, and stores a per-user refresh token server-side."
1136
1136
  },
1137
1137
  {
1138
1138
  provider: "apple",
@@ -1,7 +1,9 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
  import { getDatabase, runInTransaction } from "../db.js";
3
+ import { logForgeDebug } from "../debug.js";
3
4
  import { recordActivityEvent } from "./activity-events.js";
4
5
  import { recordEventLog } from "./event-log.js";
6
+ import { resolveGoogleCalendarOauthPublicConfig } from "../services/google-calendar-oauth-config.js";
5
7
  import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
6
8
  import { createAgentTokenSchema, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
7
9
  function boolFromInt(value) {
@@ -25,6 +27,12 @@ function normalizeMicrosoftRedirectUri(value) {
25
27
  const trimmed = value?.trim();
26
28
  return trimmed && trimmed.length > 0 ? trimmed : defaultMicrosoftRedirectUri();
27
29
  }
30
+ function logCalendarSettingsDebug(message, details) {
31
+ const serialized = Object.entries(details)
32
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
33
+ .join(" ");
34
+ logForgeDebug(`[forge-calendar-settings] ${message} ${serialized}`);
35
+ }
28
36
  function normalizeModelConnectionId(value) {
29
37
  const trimmed = value?.trim();
30
38
  return trimmed && trimmed.length > 0 ? trimmed : "";
@@ -137,7 +145,7 @@ function readSettingsRow() {
137
145
  .prepare(`SELECT
138
146
  operator_name, operator_email, operator_title, theme_preference, custom_theme_json, locale_preference,
139
147
  goal_drift_alerts, daily_quest_reminders, achievement_celebrations, max_active_tasks, time_accounting_mode,
140
- integrity_score, last_audit_at, psyche_auth_required, microsoft_client_id, microsoft_tenant_id, microsoft_redirect_uri,
148
+ integrity_score, last_audit_at, psyche_auth_required, google_client_id, google_client_secret, microsoft_client_id, microsoft_tenant_id, microsoft_redirect_uri,
141
149
  forge_basic_chat_connection_id, forge_basic_chat_model, forge_wiki_connection_id, forge_wiki_model, created_at, updated_at
142
150
  FROM app_settings
143
151
  WHERE id = 1`)
@@ -213,6 +221,18 @@ export function isPsycheAuthRequired() {
213
221
  export function getSettings() {
214
222
  const row = readSettingsRow();
215
223
  const connections = listAiModelConnections();
224
+ const googleConfig = resolveGoogleCalendarOauthPublicConfig(process.env, {
225
+ clientId: row.google_client_id,
226
+ clientSecret: row.google_client_secret
227
+ });
228
+ logCalendarSettingsDebug("get_settings", {
229
+ storedGoogleClientId: row.google_client_id,
230
+ storedGoogleClientSecret: row.google_client_secret.length > 0,
231
+ resolvedGoogleClientId: googleConfig.clientId,
232
+ resolvedGoogleClientSecret: googleConfig.clientSecret.length > 0,
233
+ googleIsConfigured: googleConfig.isConfigured,
234
+ googleRedirectUri: googleConfig.redirectUri
235
+ });
216
236
  const microsoftClientId = row.microsoft_client_id?.trim() ?? "";
217
237
  const microsoftTenantId = normalizeMicrosoftTenantId(row.microsoft_tenant_id);
218
238
  const microsoftRedirectUri = normalizeMicrosoftRedirectUri(row.microsoft_redirect_uri);
@@ -248,6 +268,7 @@ export function getSettings() {
248
268
  psycheAuthRequired: boolFromInt(row.psyche_auth_required)
249
269
  },
250
270
  calendarProviders: {
271
+ google: googleConfig,
251
272
  microsoft: {
252
273
  clientId: microsoftClientId,
253
274
  tenantId: microsoftTenantId,
@@ -297,6 +318,20 @@ export function updateSettings(input, options = {}) {
297
318
  return runInTransaction(() => {
298
319
  const current = getSettings();
299
320
  const now = new Date().toISOString();
321
+ const nextGoogleClientId = parsed.calendarProviders?.google?.clientId?.trim() ??
322
+ current.calendarProviders.google.storedClientId;
323
+ const nextGoogleClientSecret = parsed.calendarProviders?.google?.clientSecret?.trim() ??
324
+ current.calendarProviders.google.storedClientSecret;
325
+ logCalendarSettingsDebug("update_settings_requested", {
326
+ requestedGoogleClientId: parsed.calendarProviders?.google?.clientId ?? null,
327
+ requestedGoogleClientSecret: parsed.calendarProviders?.google?.clientSecret !== undefined
328
+ ? parsed.calendarProviders.google.clientSecret.length > 0
329
+ : null,
330
+ currentGoogleClientId: current.calendarProviders.google.storedClientId,
331
+ currentGoogleClientSecret: current.calendarProviders.google.storedClientSecret.length > 0,
332
+ nextGoogleClientId,
333
+ nextGoogleClientSecret: nextGoogleClientSecret.length > 0
334
+ });
300
335
  const next = {
301
336
  profile: {
302
337
  operatorName: parsed.profile?.operatorName ?? current.profile.operatorName,
@@ -317,6 +352,10 @@ export function updateSettings(input, options = {}) {
317
352
  localePreference: parsed.localePreference ?? current.localePreference,
318
353
  psycheAuthRequired: parsed.security?.psycheAuthRequired ?? current.security.psycheAuthRequired,
319
354
  calendarProviders: {
355
+ google: resolveGoogleCalendarOauthPublicConfig(process.env, {
356
+ clientId: nextGoogleClientId,
357
+ clientSecret: nextGoogleClientSecret
358
+ }),
320
359
  microsoft: {
321
360
  clientId: parsed.calendarProviders?.microsoft?.clientId?.trim() ??
322
361
  current.calendarProviders.microsoft.clientId,
@@ -350,10 +389,16 @@ export function updateSettings(input, options = {}) {
350
389
  .prepare(`UPDATE app_settings
351
390
  SET operator_name = ?, operator_email = ?, operator_title = ?, theme_preference = ?, custom_theme_json = ?, locale_preference = ?,
352
391
  goal_drift_alerts = ?, daily_quest_reminders = ?, achievement_celebrations = ?, max_active_tasks = ?, time_accounting_mode = ?,
353
- psyche_auth_required = ?, microsoft_client_id = ?, microsoft_tenant_id = ?, microsoft_redirect_uri = ?,
392
+ psyche_auth_required = ?, google_client_id = ?, google_client_secret = ?, microsoft_client_id = ?, microsoft_tenant_id = ?, microsoft_redirect_uri = ?,
354
393
  forge_basic_chat_connection_id = ?, forge_basic_chat_model = ?, forge_wiki_connection_id = ?, forge_wiki_model = ?, updated_at = ?
355
394
  WHERE id = 1`)
356
- .run(next.profile.operatorName, next.profile.operatorEmail, next.profile.operatorTitle, next.themePreference, next.customTheme ? JSON.stringify(next.customTheme) : "", next.localePreference, toInt(next.notifications.goalDriftAlerts), toInt(next.notifications.dailyQuestReminders), toInt(next.notifications.achievementCelebrations), next.execution.maxActiveTasks, next.execution.timeAccountingMode, toInt(next.psycheAuthRequired), next.calendarProviders.microsoft.clientId, next.calendarProviders.microsoft.tenantId, next.calendarProviders.microsoft.redirectUri, next.modelSettings.forgeAgent.basicChat.connectionId, next.modelSettings.forgeAgent.basicChat.model, next.modelSettings.forgeAgent.wiki.connectionId, next.modelSettings.forgeAgent.wiki.model, now);
395
+ .run(next.profile.operatorName, next.profile.operatorEmail, next.profile.operatorTitle, next.themePreference, next.customTheme ? JSON.stringify(next.customTheme) : "", next.localePreference, toInt(next.notifications.goalDriftAlerts), toInt(next.notifications.dailyQuestReminders), toInt(next.notifications.achievementCelebrations), next.execution.maxActiveTasks, next.execution.timeAccountingMode, toInt(next.psycheAuthRequired), nextGoogleClientId, nextGoogleClientSecret, next.calendarProviders.microsoft.clientId, next.calendarProviders.microsoft.tenantId, next.calendarProviders.microsoft.redirectUri, next.modelSettings.forgeAgent.basicChat.connectionId, next.modelSettings.forgeAgent.basicChat.model, next.modelSettings.forgeAgent.wiki.connectionId, next.modelSettings.forgeAgent.wiki.model, now);
396
+ logCalendarSettingsDebug("update_settings_committed", {
397
+ persistedGoogleClientId: nextGoogleClientId,
398
+ persistedGoogleClientSecret: nextGoogleClientSecret.length > 0,
399
+ persistedMicrosoftClientId: next.calendarProviders.microsoft.clientId,
400
+ updatedAt: now
401
+ });
357
402
  if (options.secrets) {
358
403
  syncForgeManagedWikiProfile(options.secrets);
359
404
  }
@@ -374,6 +419,9 @@ export function updateSettings(input, options = {}) {
374
419
  dailyQuestReminders: next.notifications.dailyQuestReminders,
375
420
  maxActiveTasks: next.execution.maxActiveTasks,
376
421
  timeAccountingMode: next.execution.timeAccountingMode,
422
+ googleConfigured: next.calendarProviders.google.isConfigured,
423
+ googleAppBaseUrl: next.calendarProviders.google.appBaseUrl,
424
+ googleRedirectUri: next.calendarProviders.google.redirectUri,
377
425
  microsoftConfigured: next.calendarProviders.microsoft.clientId.trim().length > 0,
378
426
  microsoftTenantId: next.calendarProviders.microsoft.tenantId,
379
427
  forgeBasicChatModel: next.modelSettings.forgeAgent.basicChat.model,