forge-openclaw-plugin 0.2.23 → 0.2.25

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 (84) hide show
  1. package/README.md +13 -0
  2. package/dist/assets/{board-_C6oMy5w.js → board-VmF4FAfr.js} +3 -3
  3. package/dist/assets/{board-_C6oMy5w.js.map → board-VmF4FAfr.js.map} +1 -1
  4. package/dist/assets/index-CFCKDIMH.js +67 -0
  5. package/dist/assets/index-CFCKDIMH.js.map +1 -0
  6. package/dist/assets/index-ZPY6U1TU.css +1 -0
  7. package/dist/assets/{motion-D4sZgCHd.js → motion-DvkU14p-.js} +3 -3
  8. package/dist/assets/motion-DvkU14p-.js.map +1 -0
  9. package/dist/assets/{table-BWzTaky1.js → table-DgiPof9E.js} +2 -2
  10. package/dist/assets/{table-BWzTaky1.js.map → table-DgiPof9E.js.map} +1 -1
  11. package/dist/assets/{ui-BzK4azQb.js → ui-nYfoC0Gq.js} +2 -2
  12. package/dist/assets/{ui-BzK4azQb.js.map → ui-nYfoC0Gq.js.map} +1 -1
  13. package/dist/assets/vendor-D9PTEPSB.js +824 -0
  14. package/dist/assets/vendor-D9PTEPSB.js.map +1 -0
  15. package/dist/assets/viz-Cqb6s--o.js +34 -0
  16. package/dist/assets/viz-Cqb6s--o.js.map +1 -0
  17. package/dist/index.html +8 -8
  18. package/dist/openclaw/parity.d.ts +1 -1
  19. package/dist/openclaw/parity.js +29 -0
  20. package/dist/openclaw/plugin-entry-shared.d.ts +1 -0
  21. package/dist/openclaw/plugin-entry-shared.js +7 -4
  22. package/dist/openclaw/plugin-sdk-types.d.ts +12 -0
  23. package/dist/openclaw/routes.js +236 -0
  24. package/dist/openclaw/session-bootstrap.d.ts +78 -0
  25. package/dist/openclaw/session-bootstrap.js +240 -0
  26. package/dist/openclaw/tools.js +279 -3
  27. package/dist/server/app.js +855 -19
  28. package/dist/server/connectors/box-registry.js +257 -0
  29. package/dist/server/db.js +2 -0
  30. package/dist/server/discovery-advertiser.js +114 -0
  31. package/dist/server/health.js +39 -11
  32. package/dist/server/index.js +4 -0
  33. package/dist/server/managers/platform/llm-manager.js +40 -4
  34. package/dist/server/managers/platform/openai-responses-provider.js +129 -19
  35. package/dist/server/movement.js +2935 -0
  36. package/dist/server/openapi.js +628 -5
  37. package/dist/server/psyche-types.js +15 -1
  38. package/dist/server/questionnaire-flow.js +552 -0
  39. package/dist/server/questionnaire-seeds.js +853 -0
  40. package/dist/server/questionnaire-types.js +340 -0
  41. package/dist/server/repositories/ai-connectors.js +944 -0
  42. package/dist/server/repositories/ai-processors.js +547 -0
  43. package/dist/server/repositories/diagnostic-logs.js +57 -4
  44. package/dist/server/repositories/entity-ownership.js +9 -1
  45. package/dist/server/repositories/habits.js +77 -9
  46. package/dist/server/repositories/model-settings.js +216 -0
  47. package/dist/server/repositories/notes.js +57 -15
  48. package/dist/server/repositories/preferences.js +124 -0
  49. package/dist/server/repositories/questionnaires.js +1338 -0
  50. package/dist/server/repositories/rewards.js +2 -2
  51. package/dist/server/repositories/settings.js +108 -12
  52. package/dist/server/repositories/surface-layouts.js +76 -0
  53. package/dist/server/repositories/wiki-memory.js +5 -1
  54. package/dist/server/services/entity-crud.js +81 -2
  55. package/dist/server/services/openai-codex-oauth.js +153 -0
  56. package/dist/server/services/psyche-observation-calendar.js +46 -0
  57. package/dist/server/types.js +492 -3
  58. package/dist/server/watch-mobile.js +562 -0
  59. package/dist/server/web.js +9 -2
  60. package/openclaw.plugin.json +1 -1
  61. package/package.json +6 -1
  62. package/server/migrations/024_questionnaires.sql +96 -0
  63. package/server/migrations/025_ai_model_connections.sql +26 -0
  64. package/server/migrations/026_custom_theme_settings.sql +2 -0
  65. package/server/migrations/027_ai_processors.sql +31 -0
  66. package/server/migrations/028_movement_domain.sql +136 -0
  67. package/server/migrations/029_watch_micro_capture.sql +23 -0
  68. package/server/migrations/030_surface_layouts.sql +5 -0
  69. package/server/migrations/031_ai_processor_runtime_upgrades.sql +10 -0
  70. package/server/migrations/032_ai_connectors.sql +44 -0
  71. package/server/migrations/033_movement_trip_point_sync.sql +36 -0
  72. package/server/migrations/034_movement_segment_sync.sql +49 -0
  73. package/skills/forge-openclaw/SKILL.md +12 -1
  74. package/skills/forge-openclaw/entity_conversation_playbooks.md +331 -84
  75. package/skills/forge-openclaw/psyche_entity_playbooks.md +252 -221
  76. package/dist/assets/index-Ch_xeZ2u.js +0 -63
  77. package/dist/assets/index-Ch_xeZ2u.js.map +0 -1
  78. package/dist/assets/index-DvVM7K6j.css +0 -1
  79. package/dist/assets/motion-D4sZgCHd.js.map +0 -1
  80. package/dist/assets/vendor-De38P6YR.js +0 -729
  81. package/dist/assets/vendor-De38P6YR.js.map +0 -1
  82. package/dist/assets/viz-C6hfyqzu.js +0 -34
  83. package/dist/assets/viz-C6hfyqzu.js.map +0 -1
  84. package/skills/forge-openclaw/cron_jobs.md +0 -395
@@ -314,14 +314,14 @@ export function getTotalXp() {
314
314
  const row = getDatabase()
315
315
  .prepare(`SELECT COALESCE(SUM(delta_xp), 0) AS total FROM reward_ledger`)
316
316
  .get();
317
- return row.total;
317
+ return Math.max(0, row.total);
318
318
  }
319
319
  export function getWeeklyXp(weekStartIso) {
320
320
  ensureDefaultRewardRules();
321
321
  const row = getDatabase()
322
322
  .prepare(`SELECT COALESCE(SUM(delta_xp), 0) AS total FROM reward_ledger WHERE created_at >= ?`)
323
323
  .get(weekStartIso);
324
- return row.total;
324
+ return Math.max(0, row.total);
325
325
  }
326
326
  export function getDailyAmbientXp(dayKey) {
327
327
  ensureDefaultRewardRules();
@@ -2,7 +2,8 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
  import { getDatabase, runInTransaction } from "../db.js";
3
3
  import { recordActivityEvent } from "./activity-events.js";
4
4
  import { recordEventLog } from "./event-log.js";
5
- import { createAgentTokenSchema, agentIdentitySchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
5
+ import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
6
+ import { createAgentTokenSchema, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
6
7
  function boolFromInt(value) {
7
8
  return value === 1;
8
9
  }
@@ -24,9 +25,25 @@ function normalizeMicrosoftRedirectUri(value) {
24
25
  const trimmed = value?.trim();
25
26
  return trimmed && trimmed.length > 0 ? trimmed : defaultMicrosoftRedirectUri();
26
27
  }
28
+ function normalizeModelConnectionId(value) {
29
+ const trimmed = value?.trim();
30
+ return trimmed && trimmed.length > 0 ? trimmed : "";
31
+ }
27
32
  function buildTokenSecret() {
28
33
  return `fg_live_${randomBytes(18).toString("hex")}`;
29
34
  }
35
+ function parseCustomThemeJson(raw) {
36
+ const trimmed = raw?.trim();
37
+ if (!trimmed) {
38
+ return null;
39
+ }
40
+ try {
41
+ return customThemeSchema.parse(JSON.parse(trimmed));
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
30
47
  function mapAgent(row) {
31
48
  return agentIdentitySchema.parse({
32
49
  id: row.id,
@@ -118,9 +135,10 @@ function readSettingsRow() {
118
135
  ensureSettingsRow();
119
136
  return getDatabase()
120
137
  .prepare(`SELECT
121
- operator_name, operator_email, operator_title, theme_preference, locale_preference,
138
+ operator_name, operator_email, operator_title, theme_preference, custom_theme_json, locale_preference,
122
139
  goal_drift_alerts, daily_quest_reminders, achievement_celebrations, max_active_tasks, time_accounting_mode,
123
- integrity_score, last_audit_at, psyche_auth_required, microsoft_client_id, microsoft_tenant_id, microsoft_redirect_uri, created_at, updated_at
140
+ integrity_score, last_audit_at, psyche_auth_required, microsoft_client_id, microsoft_tenant_id, microsoft_redirect_uri,
141
+ forge_basic_chat_connection_id, forge_basic_chat_model, forge_wiki_connection_id, forge_wiki_model, created_at, updated_at
124
142
  FROM app_settings
125
143
  WHERE id = 1`)
126
144
  .get();
@@ -167,7 +185,23 @@ export function listAgentIdentities() {
167
185
  GROUP BY agent_identities.id
168
186
  ORDER BY agent_identities.created_at DESC`)
169
187
  .all();
170
- return rows.map(mapAgent);
188
+ const manualAgents = rows.map(mapAgent);
189
+ const modelAgents = listAiModelConnections().map(buildConnectionAgentIdentity);
190
+ const settings = readSettingsRow();
191
+ const forgeAgent = agentIdentitySchema.parse({
192
+ id: FORGE_DEFAULT_AGENT_ID,
193
+ label: "Forge Agent",
194
+ agentType: "forge_default",
195
+ trustLevel: "trusted",
196
+ autonomyMode: "approval_required",
197
+ approvalMode: "approval_by_default",
198
+ description: "Built-in Forge operator agent. Owns Forge-native task flows, prompts, and orchestration.",
199
+ tokenCount: 0,
200
+ activeTokenCount: 0,
201
+ createdAt: settings.created_at,
202
+ updatedAt: settings.updated_at
203
+ });
204
+ return [forgeAgent, ...modelAgents, ...manualAgents];
171
205
  }
172
206
  export function isPsycheAuthRequired() {
173
207
  ensureSettingsRow();
@@ -178,9 +212,15 @@ export function isPsycheAuthRequired() {
178
212
  }
179
213
  export function getSettings() {
180
214
  const row = readSettingsRow();
215
+ const connections = listAiModelConnections();
181
216
  const microsoftClientId = row.microsoft_client_id?.trim() ?? "";
182
217
  const microsoftTenantId = normalizeMicrosoftTenantId(row.microsoft_tenant_id);
183
218
  const microsoftRedirectUri = normalizeMicrosoftRedirectUri(row.microsoft_redirect_uri);
219
+ const basicChatConnectionId = normalizeModelConnectionId(row.forge_basic_chat_connection_id);
220
+ const wikiConnectionId = normalizeModelConnectionId(row.forge_wiki_connection_id);
221
+ const basicChatConnection = connections.find((entry) => entry.id === basicChatConnectionId) ?? null;
222
+ const wikiConnection = connections.find((entry) => entry.id === wikiConnectionId) ?? null;
223
+ const customTheme = parseCustomThemeJson(row.custom_theme_json);
184
224
  return settingsPayloadSchema.parse({
185
225
  profile: {
186
226
  operatorName: row.operator_name,
@@ -197,6 +237,7 @@ export function getSettings() {
197
237
  timeAccountingMode: row.time_accounting_mode
198
238
  },
199
239
  themePreference: row.theme_preference,
240
+ customTheme,
200
241
  localePreference: row.locale_preference,
201
242
  security: {
202
243
  integrityScore: row.integrity_score,
@@ -221,11 +262,37 @@ export function getSettings() {
221
262
  : "Save the Microsoft client ID and the Forge callback redirect URI here before you try to sign in."
222
263
  }
223
264
  },
265
+ modelSettings: {
266
+ forgeAgent: {
267
+ basicChat: {
268
+ connectionId: basicChatConnection?.id ?? null,
269
+ connectionLabel: basicChatConnection?.label ?? null,
270
+ provider: basicChatConnection?.provider ?? null,
271
+ baseUrl: basicChatConnection?.baseUrl ?? null,
272
+ model: row.forge_basic_chat_model?.trim() || basicChatConnection?.model || "gpt-5.4-mini"
273
+ },
274
+ wiki: {
275
+ connectionId: wikiConnection?.id ?? null,
276
+ connectionLabel: wikiConnection?.label ?? null,
277
+ provider: wikiConnection?.provider ?? null,
278
+ baseUrl: wikiConnection?.baseUrl ?? null,
279
+ model: row.forge_wiki_model?.trim() || wikiConnection?.model || "gpt-5.4-mini"
280
+ }
281
+ },
282
+ connections,
283
+ oauth: {
284
+ openAiCodex: {
285
+ authorizeUrl: "https://auth.openai.com/oauth/authorize",
286
+ callbackUrl: "http://127.0.0.1:1455/auth/callback",
287
+ setupMessage: "Forge mirrors OpenClaw's local OpenAI Codex PKCE flow. The browser returns to localhost:1455, and Forge can also accept a pasted redirect URL when the callback cannot bind."
288
+ }
289
+ }
290
+ },
224
291
  agents: listAgentIdentities(),
225
292
  agentTokens: listAgentTokens()
226
293
  });
227
294
  }
228
- export function updateSettings(input, activity) {
295
+ export function updateSettings(input, options = {}) {
229
296
  const parsed = updateSettingsSchema.parse(input);
230
297
  return runInTransaction(() => {
231
298
  const current = getSettings();
@@ -246,6 +313,7 @@ export function updateSettings(input, activity) {
246
313
  timeAccountingMode: parsed.execution?.timeAccountingMode ?? current.execution.timeAccountingMode
247
314
  },
248
315
  themePreference: parsed.themePreference ?? current.themePreference,
316
+ customTheme: parsed.customTheme === undefined ? (current.customTheme ?? null) : parsed.customTheme,
249
317
  localePreference: parsed.localePreference ?? current.localePreference,
250
318
  psycheAuthRequired: parsed.security?.psycheAuthRequired ?? current.security.psycheAuthRequired,
251
319
  calendarProviders: {
@@ -257,33 +325,61 @@ export function updateSettings(input, activity) {
257
325
  redirectUri: normalizeMicrosoftRedirectUri(parsed.calendarProviders?.microsoft?.redirectUri ??
258
326
  current.calendarProviders.microsoft.redirectUri)
259
327
  }
328
+ },
329
+ modelSettings: {
330
+ forgeAgent: {
331
+ basicChat: {
332
+ connectionId: parsed.modelSettings?.forgeAgent?.basicChat?.connectionId !==
333
+ undefined
334
+ ? normalizeModelConnectionId(parsed.modelSettings.forgeAgent.basicChat.connectionId)
335
+ : current.modelSettings.forgeAgent.basicChat.connectionId ?? "",
336
+ model: parsed.modelSettings?.forgeAgent?.basicChat?.model?.trim() ||
337
+ current.modelSettings.forgeAgent.basicChat.model
338
+ },
339
+ wiki: {
340
+ connectionId: parsed.modelSettings?.forgeAgent?.wiki?.connectionId !== undefined
341
+ ? normalizeModelConnectionId(parsed.modelSettings.forgeAgent.wiki.connectionId)
342
+ : current.modelSettings.forgeAgent.wiki.connectionId ?? "",
343
+ model: parsed.modelSettings?.forgeAgent?.wiki?.model?.trim() ||
344
+ current.modelSettings.forgeAgent.wiki.model
345
+ }
346
+ }
260
347
  }
261
348
  };
262
349
  getDatabase()
263
350
  .prepare(`UPDATE app_settings
264
- SET operator_name = ?, operator_email = ?, operator_title = ?, theme_preference = ?, locale_preference = ?,
351
+ SET operator_name = ?, operator_email = ?, operator_title = ?, theme_preference = ?, custom_theme_json = ?, locale_preference = ?,
265
352
  goal_drift_alerts = ?, daily_quest_reminders = ?, achievement_celebrations = ?, max_active_tasks = ?, time_accounting_mode = ?,
266
- psyche_auth_required = ?, microsoft_client_id = ?, microsoft_tenant_id = ?, microsoft_redirect_uri = ?, updated_at = ?
353
+ psyche_auth_required = ?, microsoft_client_id = ?, microsoft_tenant_id = ?, microsoft_redirect_uri = ?,
354
+ forge_basic_chat_connection_id = ?, forge_basic_chat_model = ?, forge_wiki_connection_id = ?, forge_wiki_model = ?, updated_at = ?
267
355
  WHERE id = 1`)
268
- .run(next.profile.operatorName, next.profile.operatorEmail, next.profile.operatorTitle, next.themePreference, 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, now);
269
- if (activity) {
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);
357
+ if (options.secrets) {
358
+ syncForgeManagedWikiProfile(options.secrets);
359
+ }
360
+ if (options.activity) {
270
361
  recordActivityEvent({
271
362
  entityType: "system",
272
363
  entityId: "app_settings",
273
364
  eventType: "settings_updated",
274
365
  title: "Forge settings updated",
275
366
  description: `Theme is now ${next.themePreference}. Language is ${next.localePreference}.`,
276
- actor: activity.actor ?? null,
277
- source: activity.source,
367
+ actor: options.activity.actor ?? null,
368
+ source: options.activity.source,
278
369
  metadata: {
279
370
  themePreference: next.themePreference,
371
+ customThemeLabel: next.customTheme?.label ?? null,
280
372
  localePreference: next.localePreference,
281
373
  goalDriftAlerts: next.notifications.goalDriftAlerts,
282
374
  dailyQuestReminders: next.notifications.dailyQuestReminders,
283
375
  maxActiveTasks: next.execution.maxActiveTasks,
284
376
  timeAccountingMode: next.execution.timeAccountingMode,
285
377
  microsoftConfigured: next.calendarProviders.microsoft.clientId.trim().length > 0,
286
- microsoftTenantId: next.calendarProviders.microsoft.tenantId
378
+ microsoftTenantId: next.calendarProviders.microsoft.tenantId,
379
+ forgeBasicChatModel: next.modelSettings.forgeAgent.basicChat.model,
380
+ forgeWikiModel: next.modelSettings.forgeAgent.wiki.model,
381
+ forgeBasicChatConnectionId: next.modelSettings.forgeAgent.basicChat.connectionId || null,
382
+ forgeWikiConnectionId: next.modelSettings.forgeAgent.wiki.connectionId || null
287
383
  }
288
384
  });
289
385
  }
@@ -0,0 +1,76 @@
1
+ import { getDatabase } from "../db.js";
2
+ import { surfaceLayoutPayloadSchema } from "../types.js";
3
+ function parseLayout(row) {
4
+ const parsed = JSON.parse(row.payload_json);
5
+ const legacy = "layouts" in parsed ? parsed : null;
6
+ const legacyOrder = Array.isArray(legacy?.layouts?.lg)
7
+ ? [...legacy.layouts.lg]
8
+ .sort((left, right) => {
9
+ const leftY = typeof left.y === "number" ? left.y : 0;
10
+ const rightY = typeof right.y === "number" ? right.y : 0;
11
+ if (leftY !== rightY) {
12
+ return leftY - rightY;
13
+ }
14
+ const leftX = typeof left.x === "number" ? left.x : 0;
15
+ const rightX = typeof right.x === "number" ? right.x : 0;
16
+ return leftX - rightX;
17
+ })
18
+ .map((item) => item.i)
19
+ .filter((item) => typeof item === "string" && item.trim().length > 0)
20
+ : [];
21
+ const widgets = Object.fromEntries(Object.entries(parsed.widgets ?? {}).map(([widgetId, rawValue]) => {
22
+ const value = rawValue && typeof rawValue === "object"
23
+ ? rawValue
24
+ : {};
25
+ const legacyLg = legacy?.layouts?.lg?.find((entry) => entry.i === widgetId);
26
+ return [
27
+ widgetId,
28
+ {
29
+ hidden: value.hidden === true,
30
+ fullWidth: value.fullWidth === true ||
31
+ (typeof legacyLg?.w === "number" && legacyLg.w >= 10),
32
+ titleVisible: value.titleVisible !== false,
33
+ descriptionVisible: value.descriptionVisible !== false
34
+ }
35
+ ];
36
+ }));
37
+ return surfaceLayoutPayloadSchema.parse({
38
+ ...parsed,
39
+ surfaceId: row.surface_id,
40
+ order: Array.isArray(parsed.order) && parsed.order.length > 0
41
+ ? parsed.order
42
+ : legacyOrder,
43
+ widgets,
44
+ updatedAt: row.updated_at
45
+ });
46
+ }
47
+ export function getSurfaceLayout(surfaceId) {
48
+ const row = getDatabase()
49
+ .prepare(`SELECT surface_id, payload_json, updated_at
50
+ FROM surface_layouts
51
+ WHERE surface_id = ?`)
52
+ .get(surfaceId);
53
+ return row ? parseLayout(row) : null;
54
+ }
55
+ export function saveSurfaceLayout(surfaceId, payload) {
56
+ const now = new Date().toISOString();
57
+ const next = surfaceLayoutPayloadSchema.parse({
58
+ surfaceId,
59
+ updatedAt: now,
60
+ ...payload
61
+ });
62
+ getDatabase()
63
+ .prepare(`INSERT INTO surface_layouts (surface_id, payload_json, updated_at)
64
+ VALUES (?, ?, ?)
65
+ ON CONFLICT(surface_id) DO UPDATE SET
66
+ payload_json = excluded.payload_json,
67
+ updated_at = excluded.updated_at`)
68
+ .run(surfaceId, JSON.stringify(next), now);
69
+ return getSurfaceLayout(surfaceId);
70
+ }
71
+ export function resetSurfaceLayout(surfaceId) {
72
+ getDatabase()
73
+ .prepare(`DELETE FROM surface_layouts WHERE surface_id = ?`)
74
+ .run(surfaceId);
75
+ return null;
76
+ }
@@ -274,6 +274,7 @@ export const upsertWikiLlmProfileSchema = z.object({
274
274
  baseUrl: z.string().trim().default("https://api.openai.com/v1"),
275
275
  model: z.string().trim().min(1),
276
276
  apiKey: z.string().trim().optional(),
277
+ secretId: z.string().trim().nullable().optional(),
277
278
  systemPrompt: z.string().trim().default(""),
278
279
  reasoningEffort: wikiLlmReasoningEffortSchema.optional(),
279
280
  verbosity: wikiLlmVerbositySchema.optional(),
@@ -737,6 +738,7 @@ function getNoteStoragePath(note, space) {
737
738
  }
738
739
  function buildNoteFrontmatter(note) {
739
740
  return {
741
+ ...note.frontmatter,
740
742
  id: note.id,
741
743
  kind: note.kind,
742
744
  title: note.title,
@@ -2058,7 +2060,9 @@ export function upsertWikiLlmProfile(input, secrets) {
2058
2060
  const now = nowIso();
2059
2061
  const id = parsed.id?.trim() ||
2060
2062
  `wiki_llm_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
2061
- let secretId = listWikiLlmProfiles().find((entry) => entry.id === id)?.secretId ?? null;
2063
+ let secretId = parsed.secretId ??
2064
+ listWikiLlmProfiles().find((entry) => entry.id === id)?.secretId ??
2065
+ null;
2062
2066
  if (parsed.apiKey?.trim()) {
2063
2067
  secretId =
2064
2068
  secretId ??
@@ -3,16 +3,20 @@ import { createInsight, deleteInsight, getInsightById, listInsights, updateInsig
3
3
  import { createCalendarEvent, createTaskTimebox, createWorkBlockTemplate, deleteCalendarEvent, deleteTaskTimebox, deleteWorkBlockTemplate, getCalendarEventById, getTaskTimeboxById, getWorkBlockTemplateById, listCalendarEvents, listTaskTimeboxes, listWorkBlockTemplates, updateCalendarEvent, updateTaskTimebox, updateWorkBlockTemplate } from "../repositories/calendar.js";
4
4
  import { createNote, deleteNote, getNoteById, listNotes, unlinkNotesForEntity, updateNote } from "../repositories/notes.js";
5
5
  import { clearEntityOwner, filterOwnedEntities } from "../repositories/entity-ownership.js";
6
+ import { createPreferenceCatalog, createPreferenceCatalogItem, createPreferenceContext, createPreferenceItem, deletePreferenceCatalog, deletePreferenceCatalogItem, deletePreferenceContext, deletePreferenceItem, getPreferenceCatalogById, getPreferenceCatalogItemById, getPreferenceContextById, getPreferenceItemById, listPreferenceCatalogItems, listPreferenceCatalogs, listPreferenceContexts, listPreferenceItems, updatePreferenceCatalog, updatePreferenceCatalogItem, updatePreferenceContext, updatePreferenceItem } from "../repositories/preferences.js";
6
7
  import { createBehaviorPatternSchema, createBehaviorSchema, createBeliefEntrySchema, createEmotionDefinitionSchema, createEventTypeSchema, createModeGuideSessionSchema, createModeProfileSchema, createPsycheValueSchema, createTriggerReportSchema, updateBehaviorPatternSchema, updateBehaviorSchema, updateBeliefEntrySchema, updateEmotionDefinitionSchema, updateEventTypeSchema, updateModeGuideSessionSchema, updateModeProfileSchema, updatePsycheValueSchema, updateTriggerReportSchema } from "../psyche-types.js";
7
8
  import { buildSettingsBinPayload, cascadeSoftDeleteAnchoredCollaboration, clearDeletedEntityRecord, getDeletedEntityRecord, listDeletedEntities, restoreAnchoredCollaboration, restoreDeletedEntityRecord, upsertDeletedEntityRecord } from "../repositories/deleted-entities.js";
8
9
  import { createGoal, deleteGoal, getGoalById, listGoals, updateGoal } from "../repositories/goals.js";
9
10
  import { createHabit, deleteHabit, getHabitById, listHabits, updateHabit } from "../repositories/habits.js";
11
+ import { createQuestionnaireInstrument, deleteQuestionnaireInstrument, getQuestionnaireInstrumentEntityById, listQuestionnaireInstrumentEntities, updateQuestionnaireInstrument, updateQuestionnaireInstrumentSchema } from "../repositories/questionnaires.js";
10
12
  import { createBehavior, createBehaviorPattern, createBeliefEntry, createEmotionDefinition, createEventType, createModeGuideSession, createModeProfile, createPsycheValue, createTriggerReport, deleteBehavior, deleteBehaviorPattern, deleteBeliefEntry, deleteEmotionDefinition, deleteEventType, deleteModeGuideSession, deleteModeProfile, deletePsycheValue, deleteTriggerReport, getBehaviorById, getBehaviorPatternById, getBeliefEntryById, getEmotionDefinitionById, getEventTypeById, getModeGuideSessionById, getModeProfileById, getPsycheValueById, getTriggerReportById, listBehaviors, listBehaviorPatterns, listBeliefEntries, listEmotionDefinitions, listEventTypes, listModeGuideSessions, listModeProfiles, listPsycheValues, listTriggerReports, updateBehavior, updateBehaviorPattern, updateBeliefEntry, updateEmotionDefinition, updateEventType, updateModeGuideSession, updateModeProfile, updatePsycheValue, updateTriggerReport } from "../repositories/psyche.js";
11
13
  import { createProject, deleteProject, getProjectById, listProjects, updateProject } from "../repositories/projects.js";
12
14
  import { createStrategy, deleteStrategy, getStrategyById, listStrategies, updateStrategy } from "../repositories/strategies.js";
13
15
  import { createTag, deleteTag, getTagById, listTags, updateTag } from "../repositories/tags.js";
14
16
  import { createTask, deleteTask, getTaskById, listTasks, updateTask } from "../repositories/tasks.js";
15
17
  import { createCalendarEventSchema, createGoalSchema, createHabitSchema, createInsightSchema, createNoteSchema, createProjectSchema, createStrategySchema, createTaskTimeboxSchema, createTagSchema, createTaskSchema, createWorkBlockTemplateSchema, updateCalendarEventSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateNoteSchema, updateProjectSchema, updateStrategySchema, updateTaskTimeboxSchema, updateTagSchema, updateTaskSchema, updateWorkBlockTemplateSchema } from "../types.js";
18
+ import { createPreferenceCatalogItemSchema, createPreferenceCatalogSchema, createPreferenceContextSchema, createPreferenceItemSchema, updatePreferenceCatalogItemSchema, updatePreferenceCatalogSchema, updatePreferenceContextSchema, updatePreferenceItemSchema } from "../preferences-types.js";
19
+ import { createQuestionnaireInstrumentSchema } from "../questionnaire-types.js";
16
20
  const ENTITY_CALENDAR_LIST_RANGE = {
17
21
  from: "1970-01-01T00:00:00.000Z",
18
22
  to: "2100-01-01T00:00:00.000Z"
@@ -249,6 +253,62 @@ const CRUD_ENTITY_CAPABILITIES = {
249
253
  create: (data, context) => createTriggerReport(data, context),
250
254
  update: (id, patch, context) => updateTriggerReport(id, patch, context),
251
255
  hardDelete: (id, context) => deleteTriggerReport(id, context)
256
+ },
257
+ preference_catalog: {
258
+ entityType: "preference_catalog",
259
+ routeBase: "/api/v1/preferences/catalogs",
260
+ deleteMode: "immediate",
261
+ inBin: false,
262
+ list: () => listPreferenceCatalogs(),
263
+ get: (id) => getPreferenceCatalogById(id),
264
+ create: (data) => createPreferenceCatalog(data),
265
+ update: (id, patch) => updatePreferenceCatalog(id, patch),
266
+ hardDelete: (id) => deletePreferenceCatalog(id)
267
+ },
268
+ preference_catalog_item: {
269
+ entityType: "preference_catalog_item",
270
+ routeBase: "/api/v1/preferences/catalog-items",
271
+ deleteMode: "immediate",
272
+ inBin: false,
273
+ list: () => listPreferenceCatalogItems(),
274
+ get: (id) => getPreferenceCatalogItemById(id),
275
+ create: (data) => createPreferenceCatalogItem(data),
276
+ update: (id, patch) => updatePreferenceCatalogItem(id, patch),
277
+ hardDelete: (id) => deletePreferenceCatalogItem(id)
278
+ },
279
+ preference_context: {
280
+ entityType: "preference_context",
281
+ routeBase: "/api/v1/preferences/contexts",
282
+ deleteMode: "immediate",
283
+ inBin: false,
284
+ list: () => listPreferenceContexts(),
285
+ get: (id) => getPreferenceContextById(id),
286
+ create: (data) => createPreferenceContext(data),
287
+ update: (id, patch) => updatePreferenceContext(id, patch),
288
+ hardDelete: (id) => deletePreferenceContext(id)
289
+ },
290
+ preference_item: {
291
+ entityType: "preference_item",
292
+ routeBase: "/api/v1/preferences/items",
293
+ deleteMode: "immediate",
294
+ inBin: false,
295
+ list: () => listPreferenceItems(),
296
+ get: (id) => getPreferenceItemById(id),
297
+ create: (data) => createPreferenceItem(data),
298
+ update: (id, patch) => updatePreferenceItem(id, patch),
299
+ hardDelete: (id) => deletePreferenceItem(id)
300
+ },
301
+ questionnaire_instrument: {
302
+ entityType: "questionnaire_instrument",
303
+ routeBase: "/api/v1/psyche/questionnaires",
304
+ deleteMode: "immediate",
305
+ inBin: false,
306
+ list: () => listQuestionnaireInstrumentEntities(),
307
+ get: (id) => getQuestionnaireInstrumentEntityById(id),
308
+ create: (data, context) => createQuestionnaireInstrument(data, context)
309
+ .instrument,
310
+ update: (id, patch, context) => updateQuestionnaireInstrument(id, patch, context),
311
+ hardDelete: (id, context) => deleteQuestionnaireInstrument(id, context)
252
312
  }
253
313
  };
254
314
  export function getCrudEntityCapabilityMatrix() {
@@ -283,7 +343,12 @@ const CREATE_ENTITY_SCHEMAS = {
283
343
  mode_guide_session: createModeGuideSessionSchema,
284
344
  event_type: createEventTypeSchema,
285
345
  emotion_definition: createEmotionDefinitionSchema,
286
- trigger_report: createTriggerReportSchema
346
+ trigger_report: createTriggerReportSchema,
347
+ preference_catalog: createPreferenceCatalogSchema,
348
+ preference_catalog_item: createPreferenceCatalogItemSchema,
349
+ preference_context: createPreferenceContextSchema,
350
+ preference_item: createPreferenceItemSchema,
351
+ questionnaire_instrument: createQuestionnaireInstrumentSchema
287
352
  };
288
353
  const UPDATE_ENTITY_SCHEMAS = {
289
354
  goal: updateGoalSchema,
@@ -305,7 +370,12 @@ const UPDATE_ENTITY_SCHEMAS = {
305
370
  mode_guide_session: updateModeGuideSessionSchema,
306
371
  event_type: updateEventTypeSchema,
307
372
  emotion_definition: updateEmotionDefinitionSchema,
308
- trigger_report: updateTriggerReportSchema
373
+ trigger_report: updateTriggerReportSchema,
374
+ preference_catalog: updatePreferenceCatalogSchema,
375
+ preference_catalog_item: updatePreferenceCatalogItemSchema,
376
+ preference_context: updatePreferenceContextSchema,
377
+ preference_item: updatePreferenceItemSchema,
378
+ questionnaire_instrument: updateQuestionnaireInstrumentSchema
309
379
  };
310
380
  function parseCreateInput(entityType, data) {
311
381
  return CREATE_ENTITY_SCHEMAS[entityType].parse(data);
@@ -476,6 +546,15 @@ function matchesLinkedTo(entityType, entity, linkedTo) {
476
546
  (linkedTo.entityType === "behavior" && Array.isArray(entity.linkedBehaviorIds) && entity.linkedBehaviorIds.includes(linkedTo.id)) ||
477
547
  (linkedTo.entityType === "belief_entry" && Array.isArray(entity.linkedBeliefIds) && entity.linkedBeliefIds.includes(linkedTo.id)) ||
478
548
  (linkedTo.entityType === "mode_profile" && Array.isArray(entity.linkedModeIds) && entity.linkedModeIds.includes(linkedTo.id)));
549
+ case "preference_catalog_item":
550
+ return linkedTo.entityType === "preference_catalog" && entity.catalogId === linkedTo.id;
551
+ case "preference_item":
552
+ return (typeof entity.sourceEntityType === "string" &&
553
+ typeof entity.sourceEntityId === "string" &&
554
+ entity.sourceEntityType === linkedTo.entityType &&
555
+ entity.sourceEntityId === linkedTo.id);
556
+ case "questionnaire_instrument":
557
+ return false;
479
558
  default:
480
559
  return false;
481
560
  }
@@ -0,0 +1,153 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { loginOpenAICodex } from "@mariozechner/pi-ai/oauth";
3
+ import { openAiCodexOauthSessionSchema } from "../types.js";
4
+ const sessions = new Map();
5
+ const SESSION_TTL_MS = 15 * 60 * 1000;
6
+ function createDeferred() {
7
+ let resolve;
8
+ let reject;
9
+ const promise = new Promise((nextResolve, nextReject) => {
10
+ resolve = nextResolve;
11
+ reject = nextReject;
12
+ });
13
+ return { promise, resolve, reject };
14
+ }
15
+ function updateSession(sessionId, patch) {
16
+ const existing = sessions.get(sessionId);
17
+ if (!existing) {
18
+ throw new Error(`Unknown OpenAI Codex OAuth session ${sessionId}`);
19
+ }
20
+ existing.publicSession = openAiCodexOauthSessionSchema.parse({
21
+ ...existing.publicSession,
22
+ ...patch
23
+ });
24
+ return existing.publicSession;
25
+ }
26
+ function requireRecord(sessionId) {
27
+ const record = sessions.get(sessionId);
28
+ if (!record) {
29
+ throw new Error(`Unknown OpenAI Codex OAuth session ${sessionId}`);
30
+ }
31
+ const expiresAt = new Date(record.publicSession.expiresAt).getTime();
32
+ if (Date.now() >= expiresAt && record.publicSession.status !== "authorized") {
33
+ record.publicSession = openAiCodexOauthSessionSchema.parse({
34
+ ...record.publicSession,
35
+ status: "expired"
36
+ });
37
+ }
38
+ return record;
39
+ }
40
+ function parseError(error) {
41
+ if (error instanceof Error) {
42
+ return error.message;
43
+ }
44
+ return String(error);
45
+ }
46
+ export async function startOpenAiCodexOauthSession(options = {}) {
47
+ const id = `ocx_${randomUUID().replaceAll("-", "").slice(0, 8)}`;
48
+ const now = new Date();
49
+ const record = {
50
+ publicSession: openAiCodexOauthSessionSchema.parse({
51
+ id,
52
+ status: "starting",
53
+ authUrl: null,
54
+ accountLabel: null,
55
+ error: null,
56
+ createdAt: now.toISOString(),
57
+ expiresAt: new Date(now.getTime() + SESSION_TTL_MS).toISOString(),
58
+ credentialExpiresAt: null
59
+ }),
60
+ manualInput: createDeferred(),
61
+ authReady: createDeferred(),
62
+ credentials: null
63
+ };
64
+ sessions.set(id, record);
65
+ const login = options.login ?? loginOpenAICodex;
66
+ void login({
67
+ onAuth: ({ url }) => {
68
+ updateSession(id, {
69
+ status: "awaiting_browser",
70
+ authUrl: url,
71
+ error: null
72
+ });
73
+ record.authReady.resolve();
74
+ },
75
+ onPrompt: async () => {
76
+ updateSession(id, {
77
+ status: "awaiting_manual_input"
78
+ });
79
+ record.authReady.resolve();
80
+ return await record.manualInput.promise;
81
+ },
82
+ onManualCodeInput: async () => {
83
+ updateSession(id, {
84
+ status: "awaiting_manual_input"
85
+ });
86
+ record.authReady.resolve();
87
+ return await record.manualInput.promise;
88
+ }
89
+ })
90
+ .then((credentials) => {
91
+ record.credentials = credentials;
92
+ updateSession(id, {
93
+ status: "authorized",
94
+ accountLabel: typeof credentials.accountId === "string"
95
+ ? credentials.accountId
96
+ : null,
97
+ credentialExpiresAt: typeof credentials.expires === "number"
98
+ ? new Date(credentials.expires).toISOString()
99
+ : null
100
+ });
101
+ })
102
+ .catch((error) => {
103
+ updateSession(id, {
104
+ status: "error",
105
+ error: parseError(error)
106
+ });
107
+ try {
108
+ record.authReady.resolve();
109
+ }
110
+ catch {
111
+ return;
112
+ }
113
+ });
114
+ await Promise.race([
115
+ record.authReady.promise,
116
+ new Promise((resolve) => setTimeout(resolve, 250))
117
+ ]);
118
+ return record.publicSession;
119
+ }
120
+ export function getOpenAiCodexOauthSession(sessionId) {
121
+ return requireRecord(sessionId).publicSession;
122
+ }
123
+ export function submitOpenAiCodexOauthManualInput(sessionId, codeOrUrl) {
124
+ const record = requireRecord(sessionId);
125
+ if (record.publicSession.status !== "awaiting_browser" &&
126
+ record.publicSession.status !== "awaiting_manual_input" &&
127
+ record.publicSession.status !== "starting") {
128
+ throw new Error("OpenAI Codex OAuth session is not waiting for input.");
129
+ }
130
+ record.manualInput.resolve(codeOrUrl);
131
+ return updateSession(sessionId, {
132
+ status: "awaiting_manual_input"
133
+ });
134
+ }
135
+ export function consumeOpenAiCodexOauthCredentials(sessionId) {
136
+ const record = requireRecord(sessionId);
137
+ if (record.publicSession.status !== "authorized" || !record.credentials) {
138
+ throw new Error("OpenAI Codex OAuth session is not authorized yet.");
139
+ }
140
+ updateSession(sessionId, {
141
+ status: "consumed"
142
+ });
143
+ return {
144
+ kind: "oauth",
145
+ provider: "openai-codex",
146
+ access: String(record.credentials.access),
147
+ refresh: String(record.credentials.refresh),
148
+ expires: Number(record.credentials.expires),
149
+ accountId: typeof record.credentials.accountId === "string"
150
+ ? record.credentials.accountId
151
+ : ""
152
+ };
153
+ }
@@ -0,0 +1,46 @@
1
+ import { psycheObservationCalendarPayloadSchema } from "../psyche-types.js";
2
+ import { listNotesByObservedAtRange, resolveNoteObservedAt } from "../repositories/notes.js";
3
+ import { filterOwnedEntities } from "../repositories/entity-ownership.js";
4
+ import { listBehaviorPatterns, listTriggerReports } from "../repositories/psyche.js";
5
+ function collectAvailableTags(observations) {
6
+ const seen = new Set();
7
+ const tags = [];
8
+ for (const observation of observations) {
9
+ for (const tag of observation.note.tags ?? []) {
10
+ const normalized = tag.trim().toLowerCase();
11
+ if (!normalized || seen.has(normalized)) {
12
+ continue;
13
+ }
14
+ seen.add(normalized);
15
+ tags.push(tag);
16
+ }
17
+ }
18
+ return tags.sort((left, right) => left.localeCompare(right));
19
+ }
20
+ export function getPsycheObservationCalendar({ from, to, userIds }) {
21
+ const patterns = filterOwnedEntities("behavior_pattern", listBehaviorPatterns(), userIds);
22
+ const reports = filterOwnedEntities("trigger_report", listTriggerReports(200), userIds);
23
+ const notes = listNotesByObservedAtRange({ from, to, userIds, limit: 600 });
24
+ const patternsById = new Map(patterns.map((pattern) => [pattern.id, pattern]));
25
+ const reportsById = new Map(reports.map((report) => [report.id, report]));
26
+ const observations = notes.map((note) => ({
27
+ id: note.id,
28
+ observedAt: resolveNoteObservedAt(note),
29
+ note,
30
+ linkedPatterns: note.links
31
+ .filter((link) => link.entityType === "behavior_pattern")
32
+ .map((link) => patternsById.get(link.entityId) ?? null)
33
+ .filter((pattern) => pattern !== null),
34
+ linkedReports: note.links
35
+ .filter((link) => link.entityType === "trigger_report")
36
+ .map((link) => reportsById.get(link.entityId) ?? null)
37
+ .filter((report) => report !== null)
38
+ }));
39
+ return psycheObservationCalendarPayloadSchema.parse({
40
+ generatedAt: new Date().toISOString(),
41
+ from,
42
+ to,
43
+ observations,
44
+ availableTags: collectAvailableTags(observations)
45
+ });
46
+ }