forge-openclaw-plugin 0.2.24 → 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.
- package/README.md +13 -0
- package/dist/assets/{board-_C6oMy5w.js → board-VmF4FAfr.js} +3 -3
- package/dist/assets/{board-_C6oMy5w.js.map → board-VmF4FAfr.js.map} +1 -1
- package/dist/assets/index-CFCKDIMH.js +67 -0
- package/dist/assets/index-CFCKDIMH.js.map +1 -0
- package/dist/assets/index-ZPY6U1TU.css +1 -0
- package/dist/assets/{motion-D4sZgCHd.js → motion-DvkU14p-.js} +3 -3
- package/dist/assets/motion-DvkU14p-.js.map +1 -0
- package/dist/assets/{table-BWzTaky1.js → table-DgiPof9E.js} +2 -2
- package/dist/assets/{table-BWzTaky1.js.map → table-DgiPof9E.js.map} +1 -1
- package/dist/assets/{ui-BzK4azQb.js → ui-nYfoC0Gq.js} +2 -2
- package/dist/assets/{ui-BzK4azQb.js.map → ui-nYfoC0Gq.js.map} +1 -1
- package/dist/assets/vendor-D9PTEPSB.js +824 -0
- package/dist/assets/vendor-D9PTEPSB.js.map +1 -0
- package/dist/assets/viz-Cqb6s--o.js +34 -0
- package/dist/assets/viz-Cqb6s--o.js.map +1 -0
- package/dist/index.html +8 -8
- package/dist/openclaw/parity.d.ts +1 -1
- package/dist/openclaw/parity.js +29 -0
- package/dist/openclaw/plugin-entry-shared.d.ts +1 -0
- package/dist/openclaw/plugin-entry-shared.js +7 -4
- package/dist/openclaw/plugin-sdk-types.d.ts +12 -0
- package/dist/openclaw/routes.js +236 -0
- package/dist/openclaw/session-bootstrap.d.ts +78 -0
- package/dist/openclaw/session-bootstrap.js +240 -0
- package/dist/openclaw/tools.js +279 -3
- package/dist/server/app.js +855 -19
- package/dist/server/connectors/box-registry.js +257 -0
- package/dist/server/db.js +2 -0
- package/dist/server/discovery-advertiser.js +114 -0
- package/dist/server/health.js +39 -11
- package/dist/server/index.js +4 -0
- package/dist/server/managers/platform/llm-manager.js +40 -4
- package/dist/server/managers/platform/openai-responses-provider.js +129 -19
- package/dist/server/movement.js +2935 -0
- package/dist/server/openapi.js +628 -5
- package/dist/server/psyche-types.js +15 -1
- package/dist/server/questionnaire-flow.js +552 -0
- package/dist/server/questionnaire-seeds.js +853 -0
- package/dist/server/questionnaire-types.js +340 -0
- package/dist/server/repositories/ai-connectors.js +944 -0
- package/dist/server/repositories/ai-processors.js +547 -0
- package/dist/server/repositories/entity-ownership.js +9 -1
- package/dist/server/repositories/habits.js +69 -5
- package/dist/server/repositories/model-settings.js +216 -0
- package/dist/server/repositories/notes.js +57 -15
- package/dist/server/repositories/preferences.js +124 -0
- package/dist/server/repositories/questionnaires.js +1338 -0
- package/dist/server/repositories/settings.js +108 -12
- package/dist/server/repositories/surface-layouts.js +76 -0
- package/dist/server/repositories/wiki-memory.js +5 -1
- package/dist/server/services/entity-crud.js +81 -2
- package/dist/server/services/openai-codex-oauth.js +153 -0
- package/dist/server/services/psyche-observation-calendar.js +46 -0
- package/dist/server/types.js +492 -3
- package/dist/server/watch-mobile.js +562 -0
- package/dist/server/web.js +9 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -1
- package/server/migrations/024_questionnaires.sql +96 -0
- package/server/migrations/025_ai_model_connections.sql +26 -0
- package/server/migrations/026_custom_theme_settings.sql +2 -0
- package/server/migrations/027_ai_processors.sql +31 -0
- package/server/migrations/028_movement_domain.sql +136 -0
- package/server/migrations/029_watch_micro_capture.sql +23 -0
- package/server/migrations/030_surface_layouts.sql +5 -0
- package/server/migrations/031_ai_processor_runtime_upgrades.sql +10 -0
- package/server/migrations/032_ai_connectors.sql +44 -0
- package/server/migrations/033_movement_trip_point_sync.sql +36 -0
- package/server/migrations/034_movement_segment_sync.sql +49 -0
- package/skills/forge-openclaw/SKILL.md +12 -1
- package/skills/forge-openclaw/entity_conversation_playbooks.md +331 -84
- package/skills/forge-openclaw/psyche_entity_playbooks.md +252 -221
- package/dist/assets/index-DTCwBWAs.js +0 -65
- package/dist/assets/index-DTCwBWAs.js.map +0 -1
- package/dist/assets/index-DttXlAgi.css +0 -1
- package/dist/assets/motion-D4sZgCHd.js.map +0 -1
- package/dist/assets/vendor-De38P6YR.js +0 -729
- package/dist/assets/vendor-De38P6YR.js.map +0 -1
- package/dist/assets/viz-C6hfyqzu.js +0 -34
- package/dist/assets/viz-C6hfyqzu.js.map +0 -1
- package/skills/forge-openclaw/cron_jobs.md +0 -395
|
@@ -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 {
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 = ?,
|
|
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 (
|
|
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 =
|
|
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
|
+
}
|