forge-openclaw-plugin 0.3.12 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/assets/{action-bar-BFjWjRIM.js → action-bar-B9MYlps2.js} +1 -1
  2. package/dist/assets/{activity-page-D-6yBWuZ.js → activity-page-DbzbChcE.js} +1 -1
  3. package/dist/assets/{ai-surface-workspace-BEfo9bRO.js → ai-surface-workspace-CF0257Hs.js} +1 -1
  4. package/dist/assets/{atlas-panel-BfMyJXxQ.js → atlas-panel-CO3RYAKn.js} +1 -1
  5. package/dist/assets/{calendar-page-D4tQNJ2V.js → calendar-page-BuuHKEHC.js} +1 -1
  6. package/dist/assets/{calendar-rules-C-6O_uGU.js → calendar-rules-DKftgNx5.js} +1 -1
  7. package/dist/assets/{calendar-week-toolbar-_NzeKsYx.js → calendar-week-toolbar-ChIpkT-G.js} +1 -1
  8. package/dist/assets/{companion-sync-lab-page-D1Oqsf6M.js → companion-sync-lab-page-BZRX4Btw.js} +1 -1
  9. package/dist/assets/{daily-metrics-dashboard-DtE3pVOl.js → daily-metrics-dashboard-CXDsaAQd.js} +1 -1
  10. package/dist/assets/{define-workbench-box-D-32C8nM.js → define-workbench-box-CpG0Zb1L.js} +1 -1
  11. package/dist/assets/{entity-link-multiselect-DcCvkesQ.js → entity-link-multiselect-Dl4rZqdg.js} +1 -1
  12. package/dist/assets/{entity-note-count-link-Cjsk5oT2.js → entity-note-count-link-Bs1aKYyD.js} +1 -1
  13. package/dist/assets/{entity-notes-surface-DQQPLjxd.js → entity-notes-surface-B56XSw7M.js} +1 -1
  14. package/dist/assets/{execution-board-agWQbN-y.js → execution-board-D66C_ikW.js} +1 -1
  15. package/dist/assets/{faceted-token-search-DldM3-ru.js → faceted-token-search-Dg2rjknH.js} +1 -1
  16. package/dist/assets/{flagship-signal-deck-BlLYW9Kz.js → flagship-signal-deck-C6KVPhmM.js} +1 -1
  17. package/dist/assets/{floating-action-menu-D9-psbha.js → floating-action-menu-DAFAEBcA.js} +1 -1
  18. package/dist/assets/{generic-node-view-CcepUVhP.js → generic-node-view-C6DK5hJ6.js} +1 -1
  19. package/dist/assets/{goal-detail-page-DP1n5-Hk.js → goal-detail-page-CC4VXud6.js} +1 -1
  20. package/dist/assets/{goal-dialog-Oxx8WqbZ.js → goal-dialog-15hD8EBp.js} +1 -1
  21. package/dist/assets/{goals-page-CUt1a4Y2.js → goals-page-CQ2lJMzI.js} +1 -1
  22. package/dist/assets/{habits-page-5REbWAlo.js → habits-page-a7KVPaQp.js} +1 -1
  23. package/dist/assets/{health-boxes-sHNML3tm.js → health-boxes-DqgvIYoL.js} +1 -1
  24. package/dist/assets/index-ClJbJhca.css +1 -0
  25. package/dist/assets/index-FpGANF9S.js +2 -0
  26. package/dist/assets/{inline-note-fields-Bql_KfR9.js → inline-note-fields-COgzxr_7.js} +1 -1
  27. package/dist/assets/{insight-flow-dialog-CN-CMSB7.js → insight-flow-dialog-Dmb6NSGp.js} +1 -1
  28. package/dist/assets/{insights-page-B1u6ONtQ.js → insights-page-OnqR4cYI.js} +1 -1
  29. package/dist/assets/{kanban-boxes-DB1kuUlY.js → kanban-boxes-BWUzntCV.js} +1 -1
  30. package/dist/assets/{kanban-page-BBW9_vMu.js → kanban-page-BI16Gzp_.js} +1 -1
  31. package/dist/assets/{knowledge-graph-page-CoJaydZb.js → knowledge-graph-page-DxEBaEke.js} +1 -1
  32. package/dist/assets/{life-force-page-DBYbA1GF.js → life-force-page-CDEXEQai.js} +1 -1
  33. package/dist/assets/{life-force-workspace-BiD9xOEt.js → life-force-workspace-C7UOnJEf.js} +1 -1
  34. package/dist/assets/{metric-tile-CuP9DOYm.js → metric-tile-boeHB1R1.js} +1 -1
  35. package/dist/assets/{movement-boxes-DZg_qPPg.js → movement-boxes-BUSqaTL2.js} +1 -1
  36. package/dist/assets/{movement-page-CmwsQGR_.js → movement-page-DcbO0497.js} +1 -1
  37. package/dist/assets/note-markdown-DXXI3W3V.js +3 -0
  38. package/dist/assets/{note-tags-input-C_x5WdK5.js → note-tags-input-CYh3TVW2.js} +1 -1
  39. package/dist/assets/{notes-boxes-BEFlp9yd.js → notes-boxes-CMJXX2K0.js} +1 -1
  40. package/dist/assets/{notes-page-B6Vl-GPf.js → notes-page-DvHMcQey.js} +1 -1
  41. package/dist/assets/{open-in-graph-button-C-bJekoH.js → open-in-graph-button-4UYrp1XP.js} +1 -1
  42. package/dist/assets/{orbit-map-DHeTM15g.js → orbit-map-BwK7sDaC.js} +1 -1
  43. package/dist/assets/{overview-page-BRWje1F9.js → overview-page-Z5vaUTm3.js} +1 -1
  44. package/dist/assets/{page-hero-8bITsx_x.js → page-hero-DvrM83_C.js} +1 -1
  45. package/dist/assets/{pill-cluster-XQjm-wPc.js → pill-cluster-DYI3Ibj8.js} +1 -1
  46. package/dist/assets/{preference-entity-handoff-button-DwYF_5i3.js → preference-entity-handoff-button-C2ATjvws.js} +1 -1
  47. package/dist/assets/{preferences-page-C7DBPpNb.js → preferences-page-BAexXHye.js} +1 -1
  48. package/dist/assets/{project-collections-mtxanSMf.js → project-collections-B9nr-Ts-.js} +1 -1
  49. package/dist/assets/{project-detail-page-BT87Goqc.js → project-detail-page-B9PqyPu9.js} +1 -1
  50. package/dist/assets/{project-dialog-DnZe757y.js → project-dialog-CBA-D65n.js} +1 -1
  51. package/dist/assets/{project-management-hierarchy-page-B3R2lNFI.js → project-management-hierarchy-page-DXK14jn0.js} +1 -1
  52. package/dist/assets/{project-management-section-nav-DyBWxHbe.js → project-management-section-nav-DJ3QKCtr.js} +1 -1
  53. package/dist/assets/{projects-boxes-CxZj3P29.js → projects-boxes-iBu_PRqe.js} +1 -1
  54. package/dist/assets/{projects-page-Bec11c0x.js → projects-page-CdAk-ByT.js} +1 -1
  55. package/dist/assets/{psyche-behaviors-page-DWRpYvl1.js → psyche-behaviors-page-CbhhTfU2.js} +1 -1
  56. package/dist/assets/{psyche-flashcards-page-CX4rcsXZ.js → psyche-flashcards-page-DQaw_vUQ.js} +1 -1
  57. package/dist/assets/{psyche-goal-map-page-Y6b3lCvV.js → psyche-goal-map-page-C-ZTVOEP.js} +1 -1
  58. package/dist/assets/{psyche-graph-CQuCWKIp.js → psyche-graph-DYzeClxn.js} +1 -1
  59. package/dist/assets/{psyche-metrics-page-DadDJOnm.js → psyche-metrics-page-C9hKn10A.js} +1 -1
  60. package/dist/assets/{psyche-mode-guide-page-B1nz0uCg.js → psyche-mode-guide-page-CR8e984W.js} +1 -1
  61. package/dist/assets/{psyche-modes-page-i3uSuhKA.js → psyche-modes-page-lQdpAcY3.js} +1 -1
  62. package/dist/assets/{psyche-page-Y_s-BE2m.js → psyche-page-CTdIDkw9.js} +1 -1
  63. package/dist/assets/{psyche-patterns-page-DaaOLIlN.js → psyche-patterns-page-Drgm-f7I.js} +1 -1
  64. package/dist/assets/{psyche-questionnaire-builder-page-CuF7rXOv.js → psyche-questionnaire-builder-page-gRwdGXde.js} +1 -1
  65. package/dist/assets/{psyche-questionnaire-detail-page-BfFEMkRY.js → psyche-questionnaire-detail-page-CIP9b2UI.js} +1 -1
  66. package/dist/assets/{psyche-questionnaire-run-detail-page-BoQTvd7Q.js → psyche-questionnaire-run-detail-page-SYndwtF3.js} +1 -1
  67. package/dist/assets/{psyche-questionnaire-run-page-C0qKiNZN.js → psyche-questionnaire-run-page-CXiJyd5i.js} +1 -1
  68. package/dist/assets/{psyche-questionnaires-page-B6hfD448.js → psyche-questionnaires-page-CFPKwA3O.js} +1 -1
  69. package/dist/assets/{psyche-report-detail-page-BlFL8moM.js → psyche-report-detail-page-dU30a2WE.js} +1 -1
  70. package/dist/assets/{psyche-reports-page-DAAcYENp.js → psyche-reports-page-Cn0EBndy.js} +1 -1
  71. package/dist/assets/{psyche-schemas-beliefs-page-CsxKSrKM.js → psyche-schemas-beliefs-page-Bab4xSWv.js} +1 -1
  72. package/dist/assets/{psyche-screen-time-page-n4b0e58x.js → psyche-screen-time-page-lIe6GQxJ.js} +1 -1
  73. package/dist/assets/{psyche-self-observation-page-BaxEQ2-3.js → psyche-self-observation-page-BTE3KfIl.js} +1 -1
  74. package/dist/assets/{psyche-values-page-DTv5NMSU.js → psyche-values-page-DclBZ9xw.js} +1 -1
  75. package/dist/assets/question-flow-dialog-Ded2E85L.js +2 -0
  76. package/dist/assets/{report-chain-fields-D132-EMh.js → report-chain-fields-DY640iqL.js} +1 -1
  77. package/dist/assets/{rewards-page-C533lVP-.js → rewards-page-FxUXB76B.js} +1 -1
  78. package/dist/assets/{scheduling-rules-editor-CDpontGp.js → scheduling-rules-editor-jakFfxqF.js} +1 -1
  79. package/dist/assets/{schema-badge-B3DiMnjB.js → schema-badge-30B5syHA.js} +1 -1
  80. package/dist/assets/{schemas-CH1_ngUX.js → schemas-Db29G8NU.js} +1 -1
  81. package/dist/assets/{select-menu-BOF-k4Ln.js → select-menu-BF2zI3RW.js} +1 -1
  82. package/dist/assets/{settings-agents-page-B5OQtlZX.js → settings-agents-page-Bh-Bv6FQ.js} +1 -1
  83. package/dist/assets/{settings-bin-page-D_bk3Kcu.js → settings-bin-page-DT8JJero.js} +1 -1
  84. package/dist/assets/{settings-calendar-page-PuSj9_kM.js → settings-calendar-page-BA4_Qqiu.js} +3 -3
  85. package/dist/assets/{settings-data-page-EwFMaeq6.js → settings-data-page-K4kpmQJY.js} +1 -1
  86. package/dist/assets/{settings-logs-page-BKkse0DX.js → settings-logs-page-DkuNPAZo.js} +1 -1
  87. package/dist/assets/{settings-mobile-page-BrIVmdeB.js → settings-mobile-page-4pCNwD91.js} +1 -1
  88. package/dist/assets/{settings-models-page-Mg84D_0K.js → settings-models-page-CZHG3t7W.js} +1 -1
  89. package/dist/assets/{settings-page-D-kul92f.js → settings-page-B_Be0vOY.js} +1 -1
  90. package/dist/assets/{settings-rewards-page-waNyCcX_.js → settings-rewards-page-C6nFTWJu.js} +1 -1
  91. package/dist/assets/{settings-section-nav-BmJWnrYk.js → settings-section-nav-Lo-VKCfZ.js} +1 -1
  92. package/dist/assets/{settings-users-page-DBgC6y56.js → settings-users-page-CVzNp4-m.js} +1 -1
  93. package/dist/assets/{settings-wiki-page-CM0te9dI.js → settings-wiki-page-DXM--7BB.js} +1 -1
  94. package/dist/assets/sleep-page-DvPdZMKx.js +1 -0
  95. package/dist/assets/{sports-page-eg5Rfc_E.js → sports-page-DjuZlOur.js} +1 -1
  96. package/dist/assets/{strategies-page-D4AqvFNW.js → strategies-page-PE1IlatS.js} +1 -1
  97. package/dist/assets/{strategy-detail-page-BlYVkXaW.js → strategy-detail-page-C4KiEGCI.js} +1 -1
  98. package/dist/assets/{strategy-dialog-FO9Oa0dB.js → strategy-dialog-Cwf7Y3P5.js} +1 -1
  99. package/dist/assets/surface-ubuOfukq.js +1 -0
  100. package/dist/assets/{task-detail-page-Dy-aRyY6.js → task-detail-page-B3SMFZQ3.js} +1 -1
  101. package/dist/assets/{task-dialog-DrA9pba7.js → task-dialog-CI_Fy_FW.js} +1 -1
  102. package/dist/assets/{timebox-planning-dialog-C_gnfxFx.js → timebox-planning-dialog-BtFuVoA1.js} +1 -1
  103. package/dist/assets/{today-boxes-BFOws_iC.js → today-boxes-CYuxSkZf.js} +1 -1
  104. package/dist/assets/{today-page-BoPj6a6q.js → today-page-CHGpLEZ3.js} +1 -1
  105. package/dist/assets/{training-load-page-DGU40Zfl.js → training-load-page-GZJF-Yy5.js} +1 -1
  106. package/dist/assets/{user-badge-CZWtYeMw.js → user-badge-ByhC6bMU.js} +1 -1
  107. package/dist/assets/{utility-widgets-B3wWGxQc.js → utility-widgets-ilORjDmg.js} +1 -1
  108. package/dist/assets/{vitals-page-BXRZEP_8.js → vitals-page-mdEqIm_9.js} +1 -1
  109. package/dist/assets/{weekly-review-page-D5cebA5x.js → weekly-review-page-C8W-yg5r.js} +1 -1
  110. package/dist/assets/weight-loss-page-BApTknCn.js +5 -0
  111. package/dist/assets/{wiki-article-markdown-CvaCjg_t.js → wiki-article-markdown-DBccllQg.js} +1 -1
  112. package/dist/assets/{wiki-editor-page-Dco79SLY.js → wiki-editor-page-BfRfH1O3.js} +2 -2
  113. package/dist/assets/{wiki-ingest-history-page-uvKRkRDF.js → wiki-ingest-history-page-C-6H3MU6.js} +1 -1
  114. package/dist/assets/{wiki-ingest-modal-BW6AJPea.js → wiki-ingest-modal-DnlhByD_.js} +1 -1
  115. package/dist/assets/{wiki-page-BIL5zMqv.js → wiki-page-DPZ55e3x.js} +1 -1
  116. package/dist/assets/{workbench-flow-page-Dx_nq8R_.js → workbench-flow-page-_-NKIx5R.js} +1 -1
  117. package/dist/assets/{workbench-page-BJnt_Zzf.js → workbench-page-NIAzggwX.js} +1 -1
  118. package/dist/assets/{workout-detail-page-CKgqyB-y.js → workout-detail-page-3fxr6HL4.js} +1 -1
  119. package/dist/index.html +2 -2
  120. package/dist/server/server/src/app.js +8 -4
  121. package/dist/server/server/src/health-weight-loss.js +321 -14
  122. package/dist/server/server/src/managers/platform/openai-responses-provider.js +2 -2
  123. package/dist/server/server/src/openapi.js +26 -3
  124. package/dist/server/server/src/repositories/ai-connectors.js +1 -1
  125. package/openclaw.plugin.json +1 -1
  126. package/package.json +1 -1
  127. package/skills/forge-openclaw/SKILL.md +22 -0
  128. package/skills/forge-openclaw/entity_conversation_playbooks.md +17 -0
  129. package/skills/forge-openclaw/psyche_entity_playbooks.md +7 -0
  130. package/dist/assets/index-CQ5r7ZUz.js +0 -2
  131. package/dist/assets/index-FxgNSuZX.css +0 -1
  132. package/dist/assets/note-markdown-B82ncnFt.js +0 -3
  133. package/dist/assets/question-flow-dialog-CskCt5NZ.js +0 -2
  134. package/dist/assets/sleep-page-C_krRE59.js +0 -1
  135. package/dist/assets/surface-MVeeZGKB.js +0 -1
  136. package/dist/assets/weight-loss-page-BuUdFh9z.js +0 -5
@@ -13,6 +13,9 @@ const scoreSchema = z
13
13
  .union([z.coerce.number().int().min(0).max(10), z.null()])
14
14
  .optional();
15
15
  const tagsSchema = z.array(z.string().trim().min(1)).default([]);
16
+ const parsedStringDefaultSchema = z.preprocess((value) => (typeof value === "string" ? value : ""), z.string().trim());
17
+ const parsedOptionalDatetimeSchema = z.preprocess((value) => (typeof value === "string" && value.trim() ? value : undefined), z.string().datetime().optional());
18
+ const parsedOptionalNumberSchema = z.preprocess((value) => (value === null || value === "" ? undefined : value), z.coerce.number().finite().optional());
16
19
  const linksSchema = z
17
20
  .array(z.object({
18
21
  entityType: z.string().trim().min(1),
@@ -2169,28 +2172,300 @@ function getNutritionCodexProfile(connectionId) {
2169
2172
  }
2170
2173
  const parsedMealItemSchema = z.object({
2171
2174
  name: z.string().trim().min(1),
2175
+ searchQuery: parsedStringDefaultSchema.default(""),
2176
+ brand: parsedStringDefaultSchema.default(""),
2172
2177
  quantity: z.coerce.number().positive().default(1),
2173
2178
  unit: z.string().trim().min(1).default("serving"),
2174
- grams: optionalNumberSchema,
2175
- calories: optionalNumberSchema,
2176
- proteinGrams: optionalNumberSchema,
2177
- carbohydrateGrams: optionalNumberSchema,
2178
- fatGrams: optionalNumberSchema,
2179
- fiberGrams: optionalNumberSchema,
2180
- sugarGrams: optionalNumberSchema,
2181
- sodiumMg: optionalNumberSchema,
2179
+ grams: parsedOptionalNumberSchema,
2180
+ calories: parsedOptionalNumberSchema,
2181
+ proteinGrams: parsedOptionalNumberSchema,
2182
+ carbohydrateGrams: parsedOptionalNumberSchema,
2183
+ fatGrams: parsedOptionalNumberSchema,
2184
+ fiberGrams: parsedOptionalNumberSchema,
2185
+ sugarGrams: parsedOptionalNumberSchema,
2186
+ sodiumMg: parsedOptionalNumberSchema,
2182
2187
  tags: tagsSchema,
2183
2188
  confidence: z.coerce.number().min(0).max(1).default(0.45)
2184
2189
  });
2185
2190
  const parsedMealSchema = z.object({
2186
2191
  mealLabel: z.string().trim().default(""),
2187
- loggedAt: z.string().datetime().optional(),
2192
+ loggedAt: parsedOptionalDatetimeSchema,
2188
2193
  items: z.array(parsedMealItemSchema).min(1),
2189
2194
  uncertaintyReasons: z.array(z.string()).default([]),
2190
2195
  clarificationQuestions: z.array(z.string()).default([]),
2191
2196
  tags: tagsSchema
2192
2197
  });
2198
+ const parsedMealNutritionCompletionSchema = z.object({
2199
+ items: z
2200
+ .array(z.object({
2201
+ index: z.coerce.number().int().min(0),
2202
+ grams: parsedOptionalNumberSchema,
2203
+ calories: parsedOptionalNumberSchema,
2204
+ proteinGrams: parsedOptionalNumberSchema,
2205
+ carbohydrateGrams: parsedOptionalNumberSchema,
2206
+ fatGrams: parsedOptionalNumberSchema,
2207
+ fiberGrams: parsedOptionalNumberSchema,
2208
+ sugarGrams: parsedOptionalNumberSchema,
2209
+ sodiumMg: parsedOptionalNumberSchema,
2210
+ confidence: z.coerce.number().min(0).max(1).default(0.45),
2211
+ reason: parsedStringDefaultSchema.default("")
2212
+ }))
2213
+ .default([]),
2214
+ validationNotes: z.array(z.string()).default([])
2215
+ });
2216
+ function searchableFoodText(value) {
2217
+ return (value ?? "")
2218
+ .trim()
2219
+ .toLowerCase()
2220
+ .normalize("NFKD")
2221
+ .replace(/[\u0300-\u036f]/g, "")
2222
+ .replace(/[^a-z0-9]+/g, " ")
2223
+ .replace(/\s+/g, " ")
2224
+ .trim();
2225
+ }
2226
+ function foodSearchTokens(value) {
2227
+ return searchableFoodText(value)
2228
+ .split(" ")
2229
+ .filter((token) => token.length >= 3);
2230
+ }
2231
+ function foodHasRequiredMacros(food) {
2232
+ return (hasNutritionValue(food.calories) &&
2233
+ hasNutritionValue(food.proteinGrams) &&
2234
+ hasNutritionValue(food.carbohydrateGrams) &&
2235
+ hasNutritionValue(food.fatGrams));
2236
+ }
2237
+ function scoreNutritionFoodMatch(item, food) {
2238
+ const queryText = searchableFoodText([item.searchQuery, item.brand, item.name].filter(Boolean).join(" "));
2239
+ const foodText = searchableFoodText([food.brand, food.name].join(" "));
2240
+ const queryTokens = foodSearchTokens(queryText);
2241
+ if (queryTokens.length === 0 || !foodText) {
2242
+ return 0;
2243
+ }
2244
+ const matchedTokens = queryTokens.filter((token) => foodText.includes(token));
2245
+ const coverage = matchedTokens.length / queryTokens.length;
2246
+ const exactBoost = queryText && foodText.includes(queryText)
2247
+ ? 0.35
2248
+ : searchableFoodText(item.name) &&
2249
+ foodText.includes(searchableFoodText(item.name))
2250
+ ? 0.2
2251
+ : 0;
2252
+ const nutritionBoost = foodHasRequiredMacros(food) ? 0.2 : -0.2;
2253
+ return coverage + exactBoost + nutritionBoost;
2254
+ }
2255
+ const countedFoodGramEstimates = [
2256
+ { tokens: ["almond"], gramsPerUnit: 1.2 },
2257
+ { tokens: ["cashew"], gramsPerUnit: 1.6 },
2258
+ { tokens: ["pistachio"], gramsPerUnit: 0.6 },
2259
+ { tokens: ["walnut"], gramsPerUnit: 4 },
2260
+ { tokens: ["hazelnut"], gramsPerUnit: 1.3 },
2261
+ { tokens: ["egg"], gramsPerUnit: 50 },
2262
+ { tokens: ["slice", "bread"], gramsPerUnit: 35 }
2263
+ ];
2264
+ function estimateCountedFoodGrams(item) {
2265
+ if (hasNutritionValue(item.grams) && item.grams > 0) {
2266
+ return item.grams;
2267
+ }
2268
+ if (!hasNutritionValue(item.quantity) || item.quantity <= 0) {
2269
+ return item.grams;
2270
+ }
2271
+ const unit = normalizeCustomFoodToken(item.unit);
2272
+ const countLikeUnits = new Set([
2273
+ "",
2274
+ "piece",
2275
+ "pieces",
2276
+ "unit",
2277
+ "units",
2278
+ "count",
2279
+ "counts",
2280
+ "custom",
2281
+ "almond",
2282
+ "almonds",
2283
+ "nut",
2284
+ "nuts"
2285
+ ]);
2286
+ if (!countLikeUnits.has(unit)) {
2287
+ return item.grams;
2288
+ }
2289
+ const text = searchableFoodText([item.name, item.searchQuery, item.brand].filter(Boolean).join(" "));
2290
+ const estimate = countedFoodGramEstimates.find(({ tokens }) => tokens.every((token) => text.includes(token)));
2291
+ return estimate ? round(item.quantity * estimate.gramsPerUnit, 1) : item.grams;
2292
+ }
2293
+ async function resolveParsedMealItemNutrition(item) {
2294
+ const searchQueries = Array.from(new Set([
2295
+ item.searchQuery,
2296
+ [item.brand, item.name].filter(Boolean).join(" "),
2297
+ item.name
2298
+ ]
2299
+ .map((query) => query.trim())
2300
+ .filter(Boolean)));
2301
+ const seen = new Set();
2302
+ let match = null;
2303
+ for (const query of searchQueries) {
2304
+ const foods = await searchNutritionFoods({ query, limit: 1 })
2305
+ .then((result) => result.foods)
2306
+ .catch(() => []);
2307
+ const candidates = foods
2308
+ .filter((food) => {
2309
+ if (seen.has(food.id)) {
2310
+ return false;
2311
+ }
2312
+ seen.add(food.id);
2313
+ return foodHasRequiredMacros(food);
2314
+ })
2315
+ .map((food) => ({
2316
+ food,
2317
+ score: scoreNutritionFoodMatch(item, food)
2318
+ }))
2319
+ .sort((left, right) => right.score - left.score);
2320
+ match = candidates.find((candidate) => candidate.score >= 0.45) ?? null;
2321
+ if (match) {
2322
+ break;
2323
+ }
2324
+ }
2325
+ const baseItem = {
2326
+ name: item.name,
2327
+ brand: item.brand || null,
2328
+ quantity: item.quantity,
2329
+ unit: item.unit,
2330
+ grams: estimateCountedFoodGrams(item),
2331
+ calories: item.calories,
2332
+ proteinGrams: item.proteinGrams,
2333
+ carbohydrateGrams: item.carbohydrateGrams,
2334
+ fatGrams: item.fatGrams,
2335
+ fiberGrams: item.fiberGrams,
2336
+ sugarGrams: item.sugarGrams,
2337
+ sodiumMg: item.sodiumMg,
2338
+ tags: item.tags,
2339
+ confidence: item.confidence,
2340
+ nutrients: {}
2341
+ };
2342
+ if (match) {
2343
+ const resolved = resolveMealItemForInsert({
2344
+ ...baseItem,
2345
+ foodId: match.food.id,
2346
+ name: match.food.name,
2347
+ brand: match.food.brand || item.brand || null,
2348
+ tags: Array.from(new Set([
2349
+ ...item.tags,
2350
+ "catalog_resolved",
2351
+ match.food.source.replaceAll("-", "_")
2352
+ ])),
2353
+ confidence: Math.max(item.confidence, match.food.confidence ?? 0.65),
2354
+ nutrients: {
2355
+ catalogResolution: {
2356
+ source: match.food.source,
2357
+ sourceId: match.food.sourceId,
2358
+ searchQueries,
2359
+ score: round(match.score, 2)
2360
+ }
2361
+ }
2362
+ }, { cacheConfirmedCustomFood: false });
2363
+ return {
2364
+ ...resolved,
2365
+ resolutionNote: `Resolved "${item.name}" to ${match.food.name}${match.food.brand ? ` (${match.food.brand})` : ""}.`
2366
+ };
2367
+ }
2368
+ return {
2369
+ ...baseItem,
2370
+ tags: Array.from(new Set([...item.tags, "chatgpt_estimate"])),
2371
+ nutrients: {
2372
+ estimateResolution: {
2373
+ searchQueries,
2374
+ searchedSources: ["local_cache", "open_food_facts", "usda_fdc"],
2375
+ matched: false
2376
+ }
2377
+ },
2378
+ resolutionNote: searchQueries.length > 0
2379
+ ? `No catalog match with complete macros for "${item.name}"; using ChatGPT estimate for review.`
2380
+ : null
2381
+ };
2382
+ }
2383
+ async function completeMissingParsedMealNutritionWithChatGpt(rawText, items, llm, profile) {
2384
+ const incompleteItems = items
2385
+ .map((item, index) => ({ item, index, missing: missingRequiredNutritionFields(item) }))
2386
+ .filter(({ missing }) => missing.length > 0);
2387
+ if (incompleteItems.length === 0) {
2388
+ return { items, validationNotes: [], llmCallCount: 0 };
2389
+ }
2390
+ const result = await llm.runTextPrompt(profile, {
2391
+ systemPrompt: "You are Forge's nutrition fact checker. Return strict JSON only. Use conservative, plausible nutrition values and explain uncertainty briefly.",
2392
+ prompt: `Some parsed food-log items still lack required nutrition after Forge searched its local, Open Food Facts, and USDA-backed catalogs.
2393
+
2394
+ Original user text:
2395
+ ${rawText}
2396
+
2397
+ Items needing complete nutrition:
2398
+ ${JSON.stringify(incompleteItems.map(({ item, index, missing }) => ({
2399
+ index,
2400
+ name: item.name,
2401
+ brand: item.brand,
2402
+ quantity: item.quantity,
2403
+ unit: item.unit,
2404
+ grams: item.grams,
2405
+ missing
2406
+ })))}
2407
+
2408
+ Return strict JSON only:
2409
+ {
2410
+ "items": [
2411
+ {
2412
+ "index": 0,
2413
+ "grams": 100,
2414
+ "calories": 0,
2415
+ "proteinGrams": 0,
2416
+ "carbohydrateGrams": 0,
2417
+ "fatGrams": 0,
2418
+ "fiberGrams": null,
2419
+ "sugarGrams": null,
2420
+ "sodiumMg": null,
2421
+ "confidence": 0.0,
2422
+ "reason": "short source/estimate explanation"
2423
+ }
2424
+ ],
2425
+ "validationNotes": []
2426
+ }
2427
+
2428
+ Every returned item must include calories, proteinGrams, carbohydrateGrams, and fatGrams. Estimate grams for counted portions when needed.`
2429
+ });
2430
+ const completion = parsedMealNutritionCompletionSchema.parse(JSON.parse(extractJsonObject(result.outputText)));
2431
+ const completedByIndex = new Map(completion.items.map((item) => [item.index, item]));
2432
+ return {
2433
+ items: items.map((item, index) => {
2434
+ const completed = completedByIndex.get(index);
2435
+ if (!completed) {
2436
+ return item;
2437
+ }
2438
+ const reason = completed.reason || "ChatGPT supplied conservative nutrition values.";
2439
+ return {
2440
+ ...item,
2441
+ grams: item.grams ?? completed.grams,
2442
+ calories: item.calories ?? completed.calories,
2443
+ proteinGrams: item.proteinGrams ?? completed.proteinGrams,
2444
+ carbohydrateGrams: item.carbohydrateGrams ?? completed.carbohydrateGrams,
2445
+ fatGrams: item.fatGrams ?? completed.fatGrams,
2446
+ fiberGrams: item.fiberGrams ?? completed.fiberGrams,
2447
+ sugarGrams: item.sugarGrams ?? completed.sugarGrams,
2448
+ sodiumMg: item.sodiumMg ?? completed.sodiumMg,
2449
+ confidence: Math.max(item.confidence, completed.confidence),
2450
+ tags: Array.from(new Set([...item.tags, "chatgpt_validated"])),
2451
+ nutrients: {
2452
+ ...item.nutrients,
2453
+ chatGptNutritionValidation: {
2454
+ reason,
2455
+ confidence: completed.confidence
2456
+ }
2457
+ },
2458
+ resolutionNote: item.resolutionNote
2459
+ ? `${item.resolutionNote} ${reason}`
2460
+ : reason
2461
+ };
2462
+ }),
2463
+ validationNotes: completion.validationNotes,
2464
+ llmCallCount: 1
2465
+ };
2466
+ }
2193
2467
  export async function parseNutritionFoodLogWithChatGpt(input, llm) {
2468
+ const parseStartedAt = Date.now();
2194
2469
  const parsed = nutritionParseRequestSchema.parse(input);
2195
2470
  const profile = getNutritionCodexProfile(parsed.connectionId);
2196
2471
  if (!profile) {
@@ -2218,7 +2493,9 @@ Return this JSON shape:
2218
2493
  "loggedAt": "ISO time if known",
2219
2494
  "items": [
2220
2495
  {
2221
- "name": "food name",
2496
+ "name": "normalized food name",
2497
+ "searchQuery": "best search query for Forge nutrition catalogs, including corrected brand/product wording when obvious",
2498
+ "brand": "brand or product line if known",
2222
2499
  "quantity": 1,
2223
2500
  "unit": "serving|g|ml|piece|cup|tbsp|custom",
2224
2501
  "grams": null,
@@ -2238,31 +2515,60 @@ Return this JSON shape:
2238
2515
  "tags": []
2239
2516
  }
2240
2517
 
2241
- Use null for unknown nutrients. Prefer conservative estimates and mark uncertainty.`;
2518
+ Decompose mixed meals into separate food items. Correct obvious food-brand typos in searchQuery but keep the name human-readable. When a quantity is known from the text, preserve it. For counted foods such as nuts, eggs, fruit, slices, pieces, and bars, estimate grams when possible because Forge scales catalog nutrition by grams. For packaged foods, include brand/product hints in searchQuery. Fill calories, proteinGrams, carbohydrateGrams, and fatGrams with conservative estimates when catalog lookup may fail; use null only when there is genuinely no defensible estimate. Before returning JSON, validate every item against the original text and make sure each item has either a useful searchQuery for public nutrition catalogs or complete calories/proteinGrams/carbohydrateGrams/fatGrams. Mark uncertainty.`;
2242
2519
  const result = await llm.runTextPrompt(profile, {
2243
2520
  systemPrompt: "You are Forge's nutrition parser. Return strict JSON only. Never claim precision when food quantity is unclear.",
2244
2521
  prompt
2245
2522
  });
2246
2523
  const parsedResult = parsedMealSchema.parse(JSON.parse(extractJsonObject(result.outputText)));
2524
+ const initiallyResolvedItems = await Promise.all(parsedResult.items.map((item) => resolveParsedMealItemNutrition(item)));
2525
+ const { items: resolvedItems, validationNotes, llmCallCount: nutritionCompletionLlmCallCount } = await completeMissingParsedMealNutritionWithChatGpt(parsed.text, initiallyResolvedItems, llm, profile);
2526
+ const stillIncompleteItems = resolvedItems
2527
+ .map((item) => ({
2528
+ name: item.name,
2529
+ missing: missingRequiredNutritionFields(item)
2530
+ }))
2531
+ .filter((item) => item.missing.length > 0);
2532
+ if (stillIncompleteItems.length > 0) {
2533
+ throw new Error(`ChatGPT could not verify complete nutrition for: ${stillIncompleteItems
2534
+ .map((item) => `${item.name} (${item.missing.join(", ")})`)
2535
+ .join("; ")}. Search the catalog or create a custom food with calories, protein, carbs, and fat before logging.`);
2536
+ }
2537
+ const resolutionNotes = resolvedItems
2538
+ .map((item) => item.resolutionNote)
2539
+ .filter((note) => Boolean(note));
2540
+ const parseSummary = {
2541
+ itemCount: resolvedItems.length,
2542
+ completeNutritionItemCount: resolvedItems.filter((item) => missingRequiredNutritionFields(item).length === 0).length,
2543
+ catalogResolvedItemCount: resolvedItems.filter((item) => item.tags.includes("catalog_resolved")).length,
2544
+ chatGptEstimatedItemCount: resolvedItems.filter((item) => item.tags.includes("chatgpt_estimate")).length,
2545
+ chatGptValidatedItemCount: resolvedItems.filter((item) => item.tags.includes("chatgpt_validated")).length,
2546
+ elapsedMs: Date.now() - parseStartedAt,
2547
+ llmCallCount: 1 + nutritionCompletionLlmCallCount
2548
+ };
2247
2549
  const candidate = {
2248
2550
  userId,
2249
2551
  loggedAt: parsedResult.loggedAt ?? parsed.mealTime ?? nowIso(),
2250
2552
  mealLabel: parsedResult.mealLabel || "ChatGPT parsed meal",
2251
2553
  source: parsed.imageRefs.length > 0 ? "photo" : "chatgpt",
2252
2554
  confirmationState: "candidate",
2253
- notes: parsedResult.uncertaintyReasons.join("; "),
2555
+ notes: [
2556
+ ...resolutionNotes,
2557
+ ...validationNotes,
2558
+ ...parsedResult.uncertaintyReasons
2559
+ ].join("; "),
2254
2560
  imageRefs: parsed.imageRefs,
2255
2561
  parserProvenance: {
2256
2562
  provider: "openai-codex",
2257
2563
  model: profile.model,
2258
2564
  uncertaintyReasons: parsedResult.uncertaintyReasons,
2259
2565
  clarificationQuestions: parsedResult.clarificationQuestions,
2566
+ parseSummary,
2260
2567
  rawText: parsed.text
2261
2568
  },
2262
2569
  links: [],
2263
- items: parsedResult.items.map((item) => ({
2570
+ items: resolvedItems.map(({ resolutionNote: _resolutionNote, ...item }) => ({
2264
2571
  ...item,
2265
- nutrients: {},
2266
2572
  tags: Array.from(new Set([...item.tags, ...parsedResult.tags]))
2267
2573
  }))
2268
2574
  };
@@ -2270,6 +2576,7 @@ Use null for unknown nutrients. Prefer conservative estimates and mark uncertain
2270
2576
  return {
2271
2577
  candidate,
2272
2578
  log,
2579
+ parseSummary,
2273
2580
  clarificationQuestions: parsedResult.clarificationQuestions,
2274
2581
  uncertaintyReasons: parsedResult.uncertaintyReasons
2275
2582
  };
@@ -521,7 +521,7 @@ export class OpenAiResponsesProvider {
521
521
  ? "Connection test."
522
522
  : "Reply with the single word ok.",
523
523
  ...(isCodexProfile(profile) ? { stream: true, store: false } : {}),
524
- max_output_tokens: 24,
524
+ ...(isCodexProfile(profile) ? {} : { max_output_tokens: 24 }),
525
525
  reasoning: buildReasoningConfiguration(profile),
526
526
  text: buildTextConfiguration({ profile })
527
527
  })
@@ -628,7 +628,7 @@ export class OpenAiResponsesProvider {
628
628
  ...(isCodexProfile(profile) ? { stream: true, store: false } : {}),
629
629
  reasoning: buildReasoningConfiguration(profile),
630
630
  text: buildTextConfiguration({ profile }),
631
- max_output_tokens: 1200
631
+ ...(isCodexProfile(profile) ? {} : { max_output_tokens: 1200 })
632
632
  })
633
633
  });
634
634
  if (!response.ok) {
@@ -6152,9 +6152,10 @@ export function buildOpenApiDocument() {
6152
6152
  additionalProperties: false,
6153
6153
  properties: {
6154
6154
  text: { type: "string" },
6155
- imageDescription: { type: "string" },
6156
- loggedAt: { type: "string", format: "date-time" },
6157
- mealLabel: { type: "string" }
6155
+ mealTime: { type: "string", format: "date-time" },
6156
+ imageRefs: arrayOf({ type: "string" }),
6157
+ connectionId: { type: "string" },
6158
+ commitCandidate: { type: "boolean" }
6158
6159
  }
6159
6160
  }
6160
6161
  }
@@ -6166,6 +6167,7 @@ export function buildOpenApiDocument() {
6166
6167
  required: [
6167
6168
  "candidate",
6168
6169
  "log",
6170
+ "parseSummary",
6169
6171
  "clarificationQuestions",
6170
6172
  "uncertaintyReasons"
6171
6173
  ],
@@ -6176,6 +6178,27 @@ export function buildOpenApiDocument() {
6176
6178
  log: nullable({
6177
6179
  $ref: "#/components/schemas/NutritionFoodLog"
6178
6180
  }),
6181
+ parseSummary: {
6182
+ type: "object",
6183
+ required: [
6184
+ "itemCount",
6185
+ "completeNutritionItemCount",
6186
+ "catalogResolvedItemCount",
6187
+ "chatGptEstimatedItemCount",
6188
+ "chatGptValidatedItemCount",
6189
+ "elapsedMs",
6190
+ "llmCallCount"
6191
+ ],
6192
+ properties: {
6193
+ itemCount: { type: "number" },
6194
+ completeNutritionItemCount: { type: "number" },
6195
+ catalogResolvedItemCount: { type: "number" },
6196
+ chatGptEstimatedItemCount: { type: "number" },
6197
+ chatGptValidatedItemCount: { type: "number" },
6198
+ elapsedMs: { type: "number" },
6199
+ llmCallCount: { type: "number" }
6200
+ }
6201
+ },
6179
6202
  clarificationQuestions: arrayOf({ type: "string" }),
6180
6203
  uncertaintyReasons: arrayOf({ type: "string" })
6181
6204
  }
@@ -778,7 +778,7 @@ async function runOpenAiConversationPrompt(input) {
778
778
  text: typeof input.profile.metadata.verbosity === "string"
779
779
  ? { verbosity: input.profile.metadata.verbosity }
780
780
  : undefined,
781
- max_output_tokens: 1200
781
+ ...(isCodexProfile(input.profile) ? {} : { max_output_tokens: 1200 })
782
782
  })
783
783
  });
784
784
  if (!response.ok) {
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.3.12",
5
+ "version": "0.3.14",
6
6
  "activation": {
7
7
  "onStartup": true,
8
8
  "onCapabilities": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -193,6 +193,15 @@ Wiki rule:
193
193
  - Use the wiki tools when the user wants SQLite-backed reference pages, backlink-aware recall, ingest from a URL or local file, or wiki maintenance work such as unresolved-link cleanup.
194
194
  - `forge_ingest_wiki_source` now queues the ingest as background work; use the Forge UI handoff when the user wants to review or keep only selected wiki/entity candidates after the ingest finishes.
195
195
  - Keep evidence notes and wiki pages conceptually distinct: evidence notes are linked operating records, while wiki pages are curated long-form memory.
196
+ - Ingestion policy for OpenClaw, Hermes, Codex, and Claude Code adapters:
197
+ - Before ingesting, call `forge_get_wiki_settings` so the agent knows the available spaces, LLM profiles, and embedding profiles. Prefer the shared wiki space for durable shared knowledge unless the current settings or user request clearly require another space.
198
+ - Use `forge_ingest_wiki_source` for raw text, local files, and URLs. Do not build an ad hoc importer or write raw source pages manually unless the Forge ingest tool is broken; if it is broken, fix the ingest path and tests before proceeding.
199
+ - Preserve source/evidence artifacts for audit, but do not make the wiki a transcript dump, movement log, release log, or repetitive check-in archive. Evidence supports knowledge; canonical wiki pages should be curated, readable, structured articles.
200
+ - Detect and propose durable pages for important people, organizations, projects, places, events, concepts, recurring relationship patterns, decisions, preferences, commitments, and timelines. A detected person or important concept should become or update a real wiki page, not disappear into one source note.
201
+ - Merge duplicates deliberately into one canonical page per real concept/person/project. Do not merely rename a page, hide a duplicate, or keep competing partial pages. Combine the durable information, preserve aliases and backlinks, and link the original evidence/source pages from the canonical page.
202
+ - Keep normal personal context when it is useful knowledge. Redact or omit actual secrets and security/payment credentials such as passwords, API keys, tokens, private auth links, card numbers, and similar material; do not over-redact ordinary names, relationships, work context, events, or preferences just because they are personal.
203
+ - After ingest or merge work, run `forge_sync_wiki_vault`, then use `forge_get_wiki_health`, search/list checks, and spot reads to verify created and updated pages, duplicate candidates, unresolved links, missing summaries, evidence links, and whether source material is reachable from canonical pages.
204
+ - Report what was created, updated, merged, left unresolved, and how evidence is preserved. If the user asked for reviewable candidates, hand off to the Forge UI ingest review instead of pretending the adapter can approve candidates inline.
196
205
 
197
206
  Wiki navigation and search rule:
198
207
 
@@ -263,6 +272,10 @@ Entity conversation rule:
263
272
  - Do not ask for optional tags, priority, status, dates, color, links, or assignees
264
273
  when accepted wording and meaningful body are enough unless that metadata changes
265
274
  accountability, retrieval, or execution.
275
+ - After a read returns data and several next actions are plausible, choose the one
276
+ most directly supported by what was learned and ask only for the missing detail
277
+ that would permit that action. Do not hand the user a broad menu after the read has
278
+ already narrowed the work.
266
279
  - Let each question have one job. Know what you are trying to clarify before you ask it.
267
280
  - Before you ask, decide the exact missing thing you need and how that answer will help you name, place, or save the record.
268
281
  - Prefer a progression of:
@@ -335,6 +348,9 @@ Psyche interview rule:
335
348
  - Phrase interpretive hypotheses as collaborative and testable, not as verdicts. A good hypothesis says what the reaction may be protecting, predicting, relieving, or costing, then asks whether that lands or needs correction.
336
349
  - For Psyche hypotheses, reduce the formulation burden. After one concrete example, offer one tentative function, danger, protection, payoff, or cost hypothesis and ask one fit-or-correction question. Do not make the user prove the experience, list evidence, or design repair before the wording feels held.
337
350
  - Do not keep asking broad exploratory Psyche questions after the cue, meaning, protection, payoff, or cost is already visible. For `behavior_pattern`, `belief_entry`, `mode_profile`, `mode_guide_session`, and `trigger_report`, the next helpful move is usually one active formulation plus one correction question, not another passive reflection.
351
+ - Do not leave the user with interpretation alone. Once the hypothesis lands or is
352
+ corrected, name the primary Forge record it becomes and ask one accuracy or consent
353
+ question that moves toward saving the corrected formulation.
338
354
  - Use the hypothesis timing checkpoint before asking a second or third deepening question: offer a hypothesis when one concrete episode, body cue, belief sentence, behavior, or mode voice is visible and the hypothesis would change the record shape, wording, links, or next action. Do not hypothesize yet when no concrete moment is visible, the user only wants a direct mechanical save, the user is flooded or unsafe, or the only available interpretation would be diagnosis-like, an origin story, or a certainty claim.
339
355
  - If several Psyche containers are plausible, do not ask the user to choose from a taxonomy menu first. Reflect the lived difference, offer one careful hypothesis when a concrete example is visible, then distinguish the options in plain language: one episode as a `trigger_report`, a recurring loop as a `behavior_pattern`, one repeated move as `behavior`, one sentence as `belief_entry`, a part-state as `mode_profile` or `mode_guide_session`, or reusable future-labeling as `event_type` or `emotion_definition`.
340
356
  - For Psyche updates, start with what feels newly true, newly visible, or newly inaccurate, then ask what should stay true before changing the formulation.
@@ -660,6 +676,12 @@ through `forge_create_entities` or `forge_update_entities`.
660
676
  - If you are unsure which specialized route family applies, check `forge_get_agent_onboarding` and use its `entityRouteModel.specializedDomainSurfaces` section before guessing.
661
677
  - If the truth of the current Movement, Life Force, or Workbench state is still unclear, prefer the dedicated read before the mutation so the correction stays truthful.
662
678
  - After a concrete Movement, Life Force, or Workbench correction, mutation, or result-producing run, read the relevant specialized view back when the user is trying to understand the result rather than only store it: timeline or place/settings detail for Movement, the Life Force overview for energy-planning impact, and flow detail, run detail, node result, latest node output, published output, or run history for Workbench.
679
+ - After any dedicated Movement, Life Force, or Workbench read, translate the result
680
+ into one next action: no change, Movement overlay/place/settings/link, Life Force
681
+ workload/recovery/timebox/meeting/task-choice change, or Workbench
682
+ rerun/node-inspection/flow-edit/publish/preserve/stop. Ask only for the missing
683
+ span, place, weekday, flow, run, node, output, correction, preservation choice, or
684
+ confirmation that would change that action.
663
685
 
664
686
  Use live work tools for `task_run`:
665
687
  `forge_log_work`, `forge_start_task_run`, `forge_heartbeat_task_run`, `forge_focus_task_run`, `forge_complete_task_run`, `forge_release_task_run`
@@ -222,6 +222,10 @@ worked.
222
222
  - Ask a follow-up only if it changes the next action: save, update, correct, link,
223
223
  schedule, run, publish, enrich, or open the UI. If the read already answers the
224
224
  question, close cleanly instead of asking a ceremonial "what next?"
225
+ - If the read produces several possible actions, choose the one that most directly
226
+ answers the user's practical question and ask only for the missing detail that would
227
+ permit that action. Do not hand the user a broad menu after you just learned enough
228
+ to narrow the next move.
225
229
  - For Movement, Life Force, Workbench, calendar, health, and operator overviews,
226
230
  keep the follow-up anchored to the read result: the span that is missing, the
227
231
  weekday curve that needs correction, the failed run or node, the overloaded day, or
@@ -2023,6 +2027,10 @@ Direct action rules:
2023
2027
  object.
2024
2028
  - If the user is asking where they were during one uncertain window, prefer a timeline
2025
2029
  read before you create a correction. Mutate only after the lived truth is clear.
2030
+ - After a Movement read, translate the returned data into one next action: no change,
2031
+ a manual overlay, a place boundary correction, a settings change, or a linked note.
2032
+ Ask only for the missing span, place, boundary, or confirmation that enables that
2033
+ action.
2026
2034
  - When the user has already given the real answer, for example "I stayed home during
2027
2035
  that missing block", do not ask a broad review question again. Confirm only the
2028
2036
  interval or place if that is still ambiguous, then act.
@@ -2167,6 +2175,10 @@ Direct action rules:
2167
2175
  - After a fatigue signal, profile patch, or weekday-template edit, verify through the
2168
2176
  Life Force overview when the next planning decision depends on the updated energy
2169
2177
  picture.
2178
+ - After a Life Force overview, translate the read into one planning implication before
2179
+ asking for a write: lighter workload, added recovery, protected timebox, meeting
2180
+ change, task-choice change, or no change. Ask for a profile, template, or signal
2181
+ detail only when that implication requires a mutation.
2170
2182
 
2171
2183
  Ready to act when:
2172
2184
 
@@ -2273,6 +2285,11 @@ Direct action rules:
2273
2285
  - If the user wants to understand what inputs a flow can accept before editing or
2274
2286
  running it, read the box catalog or flow detail before asking for structured
2275
2287
  input details.
2288
+ - After a Workbench read, translate the returned artifact into one next action:
2289
+ rerun with clearer input, inspect a specific node, edit the saved flow, publish or
2290
+ preserve the output, or stop because the answer is already sufficient. Ask only for
2291
+ the missing input, node, run, preservation choice, or confirmation that would change
2292
+ that action.
2276
2293
  - For new flows, ask what the flow should reliably produce, what input contract it
2277
2294
  should accept, and what first node or box should anchor it. Do not start by asking
2278
2295
  for raw JSON.
@@ -297,6 +297,9 @@ of leaving it as warm reflective prose.
297
297
  that.
298
298
  - Save through shared batch entity routes only after the user accepts the working
299
299
  wording or explicitly asks to save.
300
+ - Do not leave the user with interpretation alone. Once the hypothesis lands or is
301
+ corrected, name the primary Forge record it becomes and ask one accuracy or consent
302
+ question that moves toward that save.
300
303
 
301
304
  ## Psyche progressive disclosure
302
305
 
@@ -317,6 +320,10 @@ should preserve momentum instead of making them retell the whole story.
317
320
  unclear.
318
321
  - If the working material is already accurate enough, ask one accuracy or consent
319
322
  question instead of reopening origin, evidence, or repair.
323
+ - If the user accepts or corrects a hypothesis, immediately turn the corrected
324
+ interpretation into the nearest saveable shape: belief sentence, functional loop,
325
+ part voice, trigger chain, value phrase, event kind, emotion signature, or
326
+ flashcard message.
320
327
  - If the remaining unknown is optional therapeutic metadata, save a provisional
321
328
  version after one accuracy check and preserve nuance in a linked note when needed.
322
329