forge-openclaw-plugin 0.2.24 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/assets/{board-_C6oMy5w.js → board-ta0rUHOf.js} +3 -3
- package/dist/assets/{board-_C6oMy5w.js.map → board-ta0rUHOf.js.map} +1 -1
- package/dist/assets/index-Ro0ZF_az.css +1 -0
- package/dist/assets/index-ytlpSj23.js +79 -0
- package/dist/assets/index-ytlpSj23.js.map +1 -0
- package/dist/assets/{motion-D4sZgCHd.js → motion-fBKPB6yw.js} +3 -3
- package/dist/assets/motion-fBKPB6yw.js.map +1 -0
- package/dist/assets/{table-BWzTaky1.js → table-C-IGTQni.js} +2 -2
- package/dist/assets/{table-BWzTaky1.js.map → table-C-IGTQni.js.map} +1 -1
- package/dist/assets/{ui-BzK4azQb.js → ui-DInOpaYF.js} +2 -2
- package/dist/assets/{ui-BzK4azQb.js.map → ui-DInOpaYF.js.map} +1 -1
- package/dist/assets/vendor-lE3tZJcC.js +876 -0
- package/dist/assets/vendor-lE3tZJcC.js.map +1 -0
- package/dist/index.html +7 -8
- package/dist/openclaw/local-runtime.d.ts +3 -1
- package/dist/openclaw/local-runtime.js +51 -15
- 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 +31 -6
- package/dist/openclaw/plugin-sdk-types.d.ts +29 -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 -6
- package/dist/server/server/migrations/001_core.sql +411 -0
- package/dist/server/server/migrations/002_psyche.sql +392 -0
- package/dist/server/server/migrations/003_habits.sql +30 -0
- package/dist/server/server/migrations/004_habit_links.sql +8 -0
- package/dist/server/server/migrations/005_habit_psyche_links.sql +24 -0
- package/dist/server/server/migrations/006_work_adjustments.sql +14 -0
- package/dist/server/server/migrations/007_weekly_review_closures.sql +17 -0
- package/dist/server/server/migrations/008_calendar_execution.sql +147 -0
- package/dist/server/server/migrations/009_true_calendar_events.sql +195 -0
- package/dist/server/server/migrations/010_calendar_selection_state.sql +6 -0
- package/dist/server/server/migrations/011_calendar_timezone_backfill.sql +11 -0
- package/dist/server/server/migrations/012_work_block_ranges.sql +7 -0
- package/dist/server/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
- package/dist/server/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
- package/dist/server/server/migrations/015_multi_user_and_strategies.sql +244 -0
- package/dist/server/server/migrations/016_health_companion.sql +158 -0
- package/dist/server/server/migrations/016_strategy_contracts_and_user_graph.sql +22 -0
- package/dist/server/server/migrations/017_preferences.sql +131 -0
- package/dist/server/server/migrations/018_preference_catalogs.sql +31 -0
- package/dist/server/server/migrations/019_wiki_memory.sql +255 -0
- package/dist/server/server/migrations/020_wiki_page_hierarchy.sql +11 -0
- package/dist/server/server/migrations/021_hide_evidence_from_wiki_index.sql +3 -0
- package/dist/server/server/migrations/022_wiki_ingest_background.sql +85 -0
- package/dist/server/server/migrations/023_diagnostic_logs.sql +28 -0
- package/dist/server/server/migrations/024_questionnaires.sql +96 -0
- package/dist/server/server/migrations/025_ai_model_connections.sql +26 -0
- package/dist/server/server/migrations/026_custom_theme_settings.sql +2 -0
- package/dist/server/server/migrations/027_ai_processors.sql +31 -0
- package/dist/server/server/migrations/028_movement_domain.sql +136 -0
- package/dist/server/server/migrations/029_watch_micro_capture.sql +23 -0
- package/dist/server/server/migrations/030_surface_layouts.sql +5 -0
- package/dist/server/server/migrations/031_ai_processor_runtime_upgrades.sql +10 -0
- package/dist/server/server/migrations/032_ai_connectors.sql +44 -0
- package/dist/server/server/migrations/033_movement_trip_point_sync.sql +36 -0
- package/dist/server/server/migrations/034_movement_segment_sync.sql +49 -0
- package/dist/server/server/migrations/035_google_local_auth_settings.sql +2 -0
- package/dist/server/server/migrations/036_google_local_auth_client_secret.sql +2 -0
- package/dist/server/{app.js → server/src/app.js} +992 -25
- package/dist/server/server/src/connectors/box-registry.js +188 -0
- package/dist/server/{db.js → server/src/db.js} +6 -0
- package/dist/server/server/src/debug.js +19 -0
- package/dist/server/server/src/discovery-advertiser.js +114 -0
- package/dist/server/{health.js → server/src/health.js} +39 -11
- package/dist/server/{index.js → server/src/index.js} +4 -0
- package/dist/server/{managers → server/src/managers}/platform/llm-manager.js +40 -4
- package/dist/server/{managers → server/src/managers}/platform/openai-responses-provider.js +129 -19
- package/dist/server/server/src/movement.js +2935 -0
- package/dist/server/{openapi.js → server/src/openapi.js} +628 -5
- package/dist/server/{psyche-types.js → server/src/psyche-types.js} +15 -1
- package/dist/server/server/src/questionnaire-flow.js +552 -0
- package/dist/server/server/src/questionnaire-seeds.js +853 -0
- package/dist/server/server/src/questionnaire-types.js +340 -0
- package/dist/server/server/src/repositories/ai-connectors.js +1207 -0
- package/dist/server/server/src/repositories/ai-processors.js +547 -0
- package/dist/server/{repositories → server/src/repositories}/calendar.js +1 -1
- package/dist/server/{repositories → server/src/repositories}/entity-ownership.js +9 -1
- package/dist/server/{repositories → server/src/repositories}/habits.js +69 -5
- package/dist/server/server/src/repositories/model-settings.js +216 -0
- package/dist/server/{repositories → server/src/repositories}/notes.js +57 -15
- package/dist/server/{repositories → server/src/repositories}/preferences.js +124 -0
- package/dist/server/server/src/repositories/questionnaires.js +1338 -0
- package/dist/server/{repositories → server/src/repositories}/settings.js +156 -12
- package/dist/server/server/src/repositories/surface-layouts.js +76 -0
- package/dist/server/{repositories → server/src/repositories}/wiki-memory.js +5 -1
- package/dist/server/{services → server/src/services}/calendar-runtime.js +775 -58
- package/dist/server/{services → server/src/services}/entity-crud.js +81 -2
- package/dist/server/server/src/services/google-calendar-oauth-config.js +176 -0
- package/dist/server/server/src/services/openai-codex-oauth.js +153 -0
- package/dist/server/server/src/services/psyche-observation-calendar.js +46 -0
- package/dist/server/{types.js → server/src/types.js} +621 -14
- package/dist/server/server/src/watch-mobile.js +562 -0
- package/dist/server/{web.js → server/src/web.js} +30 -4
- package/dist/server/src/components/customization/utility-widgets.js +330 -0
- package/dist/server/src/components/workbench-boxes/health/health-boxes.js +92 -0
- package/dist/server/src/components/workbench-boxes/kanban/kanban-boxes.js +128 -0
- package/dist/server/src/components/workbench-boxes/movement/movement-boxes.js +37 -0
- package/dist/server/src/components/workbench-boxes/notes/notes-boxes.js +114 -0
- package/dist/server/src/components/workbench-boxes/projects/projects-boxes.js +57 -0
- package/dist/server/src/components/workbench-boxes/shared/define-workbench-box.js +4 -0
- package/dist/server/src/components/workbench-boxes/shared/generic-node-view.js +13 -0
- package/dist/server/src/components/workbench-boxes/today/today-boxes.js +63 -0
- package/dist/server/src/lib/api-error.js +37 -0
- package/dist/server/src/lib/api.js +1859 -0
- package/dist/server/src/lib/calendar-name-deduper.js +144 -0
- package/dist/server/src/lib/diagnostics.js +67 -0
- package/dist/server/src/lib/psyche-types.js +1 -0
- package/dist/server/src/lib/questionnaire-types.js +1 -0
- package/dist/server/src/lib/runtime-paths.js +24 -0
- package/dist/server/src/lib/schemas.js +234 -0
- package/dist/server/src/lib/snapshot-normalizer.js +374 -0
- package/dist/server/src/lib/theme-system.js +319 -0
- package/dist/server/src/lib/types.js +1 -0
- package/dist/server/src/lib/utils.js +22 -0
- package/dist/server/src/lib/workbench/boxes.js +16 -0
- package/dist/server/src/lib/workbench/nodes.js +15 -0
- package/dist/server/src/lib/workbench/registry.js +73 -0
- package/dist/server/src/lib/workbench/runtime.js +181 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -1
- package/server/index.js +68 -0
- 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/server/migrations/035_google_local_auth_settings.sql +2 -0
- package/server/migrations/036_google_local_auth_client_secret.sql +2 -0
- package/skills/forge-openclaw/SKILL.md +15 -1
- package/skills/forge-openclaw/entity_conversation_playbooks.md +523 -87
- package/skills/forge-openclaw/psyche_entity_playbooks.md +331 -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
- /package/dist/server/{demo-data.js → server/src/demo-data.js} +0 -0
- /package/dist/server/{e2e-server.js → server/src/e2e-server.js} +0 -0
- /package/dist/server/{errors.js → server/src/errors.js} +0 -0
- /package/dist/server/{managers → server/src/managers}/base.js +0 -0
- /package/dist/server/{managers → server/src/managers}/contracts.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/api-gateway-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/audit-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/authentication-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/authorization-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/background-job-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/configuration-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/database-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/event-bus-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/external-service-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/health-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/migration-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/search-index-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/secrets-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/session-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/storage-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/token-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/transaction-manager.js +0 -0
- /package/dist/server/{managers → server/src/managers}/platform/trusted-network.js +0 -0
- /package/dist/server/{managers → server/src/managers}/runtime.js +0 -0
- /package/dist/server/{managers → server/src/managers}/type-guards.js +0 -0
- /package/dist/server/{preferences-seeds.js → server/src/preferences-seeds.js} +0 -0
- /package/dist/server/{preferences-types.js → server/src/preferences-types.js} +0 -0
- /package/dist/server/{repositories → server/src/repositories}/activity-events.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/collaboration.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/deleted-entities.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/diagnostic-logs.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/domains.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/event-log.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/goals.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/projects.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/psyche.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/rewards.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/strategies.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/tags.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/task-runs.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/tasks.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/users.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/weekly-reviews.js +0 -0
- /package/dist/server/{repositories → server/src/repositories}/work-adjustments.js +0 -0
- /package/dist/server/{seed-demo.js → server/src/seed-demo.js} +0 -0
- /package/dist/server/{services → server/src/services}/context.js +0 -0
- /package/dist/server/{services → server/src/services}/dashboard.js +0 -0
- /package/dist/server/{services → server/src/services}/gamification.js +0 -0
- /package/dist/server/{services → server/src/services}/insights.js +0 -0
- /package/dist/server/{services → server/src/services}/projects.js +0 -0
- /package/dist/server/{services → server/src/services}/psyche.js +0 -0
- /package/dist/server/{services → server/src/services}/relations.js +0 -0
- /package/dist/server/{services → server/src/services}/reviews.js +0 -0
- /package/dist/server/{services → server/src/services}/run-recovery.js +0 -0
- /package/dist/server/{services → server/src/services}/tagging.js +0 -0
- /package/dist/server/{services → server/src/services}/task-run-watchdog.js +0 -0
- /package/dist/server/{services → server/src/services}/work-time.js +0 -0
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import Fastify from "fastify";
|
|
2
2
|
import cors from "@fastify/cors";
|
|
3
3
|
import multipart from "@fastify/multipart";
|
|
4
|
-
import {
|
|
4
|
+
import { CronExpressionParser } from "cron-parser";
|
|
5
|
+
import { z, ZodError } from "zod";
|
|
5
6
|
import { configureDatabase, configureDatabaseSeeding, runInTransaction } from "./db.js";
|
|
6
7
|
import { HttpError, isHttpError } from "./errors.js";
|
|
7
8
|
import { listActivityEvents, listActivityEventsForTask, recordActivityEvent, removeActivityEvent } from "./repositories/activity-events.js";
|
|
8
9
|
import { approveApprovalRequest, createAgentAction, createInsight, createInsightFeedback, deleteInsight, getInsightById, listAgentActions, listApprovalRequests, listInsights, rejectApprovalRequest, updateInsight } from "./repositories/collaboration.js";
|
|
10
|
+
import { createAiConnector, deleteAiConnector, getAiConnectorById, getAiConnectorBySlug, getAiConnectorConversationForConnector, listAiConnectorRuns, listAiConnectors, runAiConnector, updateAiConnector } from "./repositories/ai-connectors.js";
|
|
11
|
+
import { createAiProcessor, createAiProcessorLink, deleteAiProcessor, deleteAiProcessorLink, getAiProcessorById, getAiProcessorBySlug, listAiProcessors, getSurfaceProcessorGraph, runAiProcessor, updateAiProcessor } from "./repositories/ai-processors.js";
|
|
9
12
|
import { listEventLog } from "./repositories/event-log.js";
|
|
10
13
|
import { createDiagnosticMessage, DIAGNOSTIC_LOG_RETENTION_SWEEP_INTERVAL_MS, enforceDiagnosticLogRetention, listDiagnosticLogs, normalizeDiagnosticSource, recordDiagnosticLog, serializeDiagnosticError } from "./repositories/diagnostic-logs.js";
|
|
11
14
|
import { createGoal, getGoalById, listGoals, updateGoal } from "./repositories/goals.js";
|
|
15
|
+
import { getSurfaceLayout, resetSurfaceLayout, saveSurfaceLayout } from "./repositories/surface-layouts.js";
|
|
16
|
+
import { buildConnectorOutputCatalogEntry, listForgeBoxCatalog } from "./connectors/box-registry.js";
|
|
12
17
|
import { createHabit, createHabitCheckIn, deleteHabitCheckIn, getHabitById, listHabits, updateHabit } from "./repositories/habits.js";
|
|
13
18
|
import { listDomains } from "./repositories/domains.js";
|
|
14
19
|
import { buildNotesSummaryByEntity, createNote, getNoteById, listNotes, updateNote } from "./repositories/notes.js";
|
|
15
20
|
import { createWikiIngestJobSchema, createUploadedWikiIngestJob, createWikiSpace, createWikiSpaceSchema, deleteWikiIngestJob, deleteWikiProfile, getWikiHealth, getWikiIngestJob, getWikiHomePageDetail, getWikiPageDetail, getWikiPageDetailBySlug, getWikiSettingsPayload, ingestWikiSource, listWikiIngestJobs, listWikiLlmProfiles, listWikiPageTree, listWikiPages, listWikiSpaces, processWikiIngestJob, reindexWikiEmbeddings, reindexWikiEmbeddingsSchema, rerunWikiIngestJob, reviewWikiIngestJob, reviewWikiIngestJobSchema, searchWikiPages, syncWikiVaultFromDisk, syncWikiVaultSchema, testWikiLlmProfileSchema, upsertWikiEmbeddingProfile, upsertWikiEmbeddingProfileSchema, upsertWikiLlmProfile, upsertWikiLlmProfileSchema, wikiSearchQuerySchema } from "./repositories/wiki-memory.js";
|
|
16
21
|
import { filterOwnedEntities, setEntityOwner } from "./repositories/entity-ownership.js";
|
|
17
22
|
import { createBehavior, createBehaviorPattern, createBeliefEntry, createEmotionDefinition, createEventType, createModeGuideSession, createModeProfile, createPsycheValue, createTriggerReport, getBehaviorById, getBehaviorPatternById, getBeliefEntryById, getEmotionDefinitionById, getEventTypeById, getModeGuideSessionById, getModeProfileById, getPsycheValueById, getTriggerReportById, listBehaviors, listBehaviorPatterns, listBeliefEntries, listEmotionDefinitions, listEventTypes, listModeGuideSessions, listModeProfiles, listPsycheValues, listSchemaCatalog, listTriggerReports, updateBehavior, updateBehaviorPattern, updateBeliefEntry, updateEmotionDefinition, updateEventType, updateModeGuideSession, updateModeProfile, updatePsycheValue, updateTriggerReport } from "./repositories/psyche.js";
|
|
23
|
+
import { cloneQuestionnaireInstrument, completeQuestionnaireRun, createQuestionnaireInstrument, ensureQuestionnaireDraftVersion, getQuestionnaireInstrumentDetail, getQuestionnaireRunDetail, listQuestionnaireInstruments, publishQuestionnaireDraftVersion, startQuestionnaireRun, updateQuestionnaireDraftVersion, updateQuestionnaireRun } from "./repositories/questionnaires.js";
|
|
18
24
|
import { createProject, updateProject } from "./repositories/projects.js";
|
|
19
25
|
import { createPreferenceCatalog, createPreferenceCatalogItem, createPreferenceContext, createPreferenceItem, createPreferenceItemFromEntity, deletePreferenceCatalog, deletePreferenceCatalogItem, getPreferenceWorkspace, mergePreferenceContexts, startPreferenceGame, submitAbsoluteSignal, submitPairwiseJudgment, updatePreferenceCatalog, updatePreferenceCatalogItem, updatePreferenceContext, updatePreferenceItem, updatePreferenceScore } from "./repositories/preferences.js";
|
|
20
26
|
import { createStrategy, getStrategyById, listStrategies, updateStrategy } from "./repositories/strategies.js";
|
|
21
27
|
import { createManualRewardGrant, getDailyAmbientXp, getRewardRuleById, listRewardLedger, listRewardRules, recordWorkAdjustmentReward, recordSessionEvent, updateRewardRule } from "./repositories/rewards.js";
|
|
22
28
|
import { listAgentIdentities, getSettings, isPsycheAuthRequired, updateSettings, verifyAgentToken } from "./repositories/settings.js";
|
|
29
|
+
import { deleteAiModelConnection, getAiModelConnectionById, readModelConnectionCredential, upsertAiModelConnection } from "./repositories/model-settings.js";
|
|
23
30
|
import { createTag, getTagById, listTags, updateTag } from "./repositories/tags.js";
|
|
24
|
-
import { createUser, ensureSystemUsers, getUserById, listUserAccessGrants, listUserOwnershipSummaries, listUserXpSummaries, listUsers, resolveUserForMutation, updateUserAccessGrant, updateUser } from "./repositories/users.js";
|
|
31
|
+
import { createUser, ensureSystemUsers, getDefaultUser, getUserById, listUserAccessGrants, listUserOwnershipSummaries, listUserXpSummaries, listUsers, resolveUserForMutation, updateUserAccessGrant, updateUser } from "./repositories/users.js";
|
|
25
32
|
import { claimTaskRun, completeTaskRun, focusTaskRun, heartbeatTaskRun, listTaskRuns, recoverTimedOutTaskRuns, releaseTaskRun } from "./repositories/task-runs.js";
|
|
26
33
|
import { createTask, createTaskWithIdempotency, getTaskById, listTasks, uncompleteTask, updateTask } from "./repositories/tasks.js";
|
|
27
34
|
import { createWorkAdjustment } from "./repositories/work-adjustments.js";
|
|
@@ -32,20 +39,25 @@ import { buildGamificationOverview, buildGamificationProfile, buildXpMomentumPul
|
|
|
32
39
|
import { getInsightsPayload } from "./services/insights.js";
|
|
33
40
|
import { createEntities, deleteEntities, deleteEntity, getSettingsBinPayload, restoreEntities, searchEntities, updateEntities } from "./services/entity-crud.js";
|
|
34
41
|
import { getPsycheOverview } from "./services/psyche.js";
|
|
42
|
+
import { getPsycheObservationCalendar } from "./services/psyche-observation-calendar.js";
|
|
35
43
|
import { getProjectBoard, getProjectSummary, listProjectSummaries } from "./services/projects.js";
|
|
36
44
|
import { getWeeklyReviewPayload } from "./services/reviews.js";
|
|
37
45
|
import { finalizeWeeklyReviewClosure } from "./repositories/weekly-reviews.js";
|
|
38
46
|
import { createTaskRunWatchdog } from "./services/task-run-watchdog.js";
|
|
39
47
|
import { suggestTags } from "./services/tagging.js";
|
|
40
|
-
import { CalendarConnectionConflictError, completeMicrosoftCalendarOauth, createCalendarConnection, deleteCalendarEventProjection, discoverCalendarConnection, discoverExistingCalendarConnection, getMicrosoftCalendarOauthSession, listConnectedCalendarConnections, removeCalendarConnection, pushCalendarEventUpdate, readCalendarOverview, syncCalendarConnection, startMicrosoftCalendarOauth, testMicrosoftCalendarOauthConfiguration, listCalendarProviderMetadata, updateCalendarConnectionSelection } from "./services/calendar-runtime.js";
|
|
48
|
+
import { CalendarConnectionConflictError, completeGoogleCalendarOauth, completeMicrosoftCalendarOauth, createCalendarConnection, deleteCalendarEventProjection, discoverCalendarConnection, discoverExistingCalendarConnection, getGoogleCalendarOauthSession, getMicrosoftCalendarOauthSession, listConnectedCalendarConnections, removeCalendarConnection, pushCalendarEventUpdate, readCalendarOverview, syncCalendarConnection, startGoogleCalendarOauth, startMicrosoftCalendarOauth, testMicrosoftCalendarOauthConfiguration, listCalendarProviderMetadata, updateCalendarConnectionSelection } from "./services/calendar-runtime.js";
|
|
49
|
+
import { consumeOpenAiCodexOauthCredentials, getOpenAiCodexOauthSession, startOpenAiCodexOauthSession, submitOpenAiCodexOauthManualInput } from "./services/openai-codex-oauth.js";
|
|
41
50
|
import { PSYCHE_ENTITY_TYPES, createBehaviorSchema, createBeliefEntrySchema, createBehaviorPatternSchema, createEmotionDefinitionSchema, createEventTypeSchema, createModeGuideSessionSchema, createModeProfileSchema, createPsycheValueSchema, createTriggerReportSchema, updateBehaviorSchema, updateBeliefEntrySchema, updateBehaviorPatternSchema, updateEmotionDefinitionSchema, updateEventTypeSchema, updateModeGuideSessionSchema, updateModeProfileSchema, updatePsycheValueSchema, updateTriggerReportSchema } from "./psyche-types.js";
|
|
51
|
+
import { createQuestionnaireInstrumentSchema, publishQuestionnaireVersionSchema, startQuestionnaireRunSchema, updateQuestionnaireRunSchema, updateQuestionnaireVersionSchema } from "./questionnaire-types.js";
|
|
42
52
|
import { createPreferenceCatalogItemSchema, createPreferenceCatalogSchema, createPreferenceContextSchema, createPreferenceItemSchema, enqueueEntityPreferenceItemSchema, mergePreferenceContextsSchema, preferenceWorkspaceQuerySchema, startPreferenceGameSchema, submitAbsoluteSignalSchema, submitPairwiseJudgmentSchema, updatePreferenceCatalogItemSchema, updatePreferenceCatalogSchema, updatePreferenceContextSchema, updatePreferenceItemSchema, updatePreferenceScoreSchema } from "./preferences-types.js";
|
|
43
|
-
import { activityListQuerySchema, activitySourceSchema, createAgentActionSchema, createAgentTokenSchema, batchCreateEntitiesSchema, batchDeleteEntitiesSchema, batchRestoreEntitiesSchema, batchSearchEntitiesSchema, batchUpdateEntitiesSchema, createGoalSchema, createInsightFeedbackSchema, createInsightSchema, createStrategySchema, createUserSchema, createNoteSchema, createProjectSchema, createManualRewardGrantSchema, createCalendarEventSchema, createHabitCheckInSchema, createCalendarConnectionSchema, createDiagnosticLogSchema, discoverCalendarConnectionSchema, startMicrosoftCalendarOauthSchema, testMicrosoftCalendarOauthConfigurationSchema, createHabitSchema, createTaskTimeboxSchema, createWorkBlockTemplateSchema, createSessionEventSchema, createWorkAdjustmentSchema, createTagSchema, calendarOverviewQuerySchema, notesListQuerySchema, updateTagSchema, createTaskSchema, diagnosticLogListQuerySchema, eventsListQuerySchema, operatorLogWorkSchema, projectBoardPayloadSchema, projectListQuerySchema, entityDeleteQuerySchema, removeActivityEventSchema, resolveApprovalRequestSchema, rewardsLedgerQuerySchema, habitListQuerySchema, taskContextPayloadSchema, taskRunClaimSchema, taskRunFocusSchema, taskRunFinishSchema, taskRunHeartbeatSchema, taskRunListQuerySchema, taskListQuerySchema, tagSuggestionRequestSchema, uncompleteTaskSchema, updateSettingsSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateStrategySchema, updateUserSchema, updateCalendarConnectionSchema, updateCalendarEventSchema, updateNoteSchema, updateProjectSchema, updateRewardRuleSchema, updateTaskTimeboxSchema, updateTaskSchema, updateUserAccessGrantSchema, updateWorkBlockTemplateSchema, workAdjustmentResultSchema, finalizeWeeklyReviewResultSchema, goalListQuerySchema, recommendTaskTimeboxesSchema, strategyListQuerySchema } from "./types.js";
|
|
53
|
+
import { activityListQuerySchema, activitySourceSchema, createAgentActionSchema, createAgentTokenSchema, createAiConnectorSchema, createAiProcessorLinkSchema, createAiProcessorSchema, runAiConnectorSchema, writeSurfaceLayoutSchema, upsertAiModelConnectionSchema, testAiModelConnectionSchema, submitOpenAiCodexOauthManualCodeSchema, batchCreateEntitiesSchema, batchDeleteEntitiesSchema, batchRestoreEntitiesSchema, batchSearchEntitiesSchema, batchUpdateEntitiesSchema, createGoalSchema, createInsightFeedbackSchema, createInsightSchema, createStrategySchema, createUserSchema, createNoteSchema, createProjectSchema, createManualRewardGrantSchema, createCalendarEventSchema, createHabitCheckInSchema, createCalendarConnectionSchema, createDiagnosticLogSchema, discoverCalendarConnectionSchema, startGoogleCalendarOauthSchema, startMicrosoftCalendarOauthSchema, testMicrosoftCalendarOauthConfigurationSchema, createHabitSchema, createTaskTimeboxSchema, createWorkBlockTemplateSchema, createSessionEventSchema, createWorkAdjustmentSchema, createTagSchema, calendarOverviewQuerySchema, notesListQuerySchema, updateTagSchema, createTaskSchema, diagnosticLogListQuerySchema, eventsListQuerySchema, operatorLogWorkSchema, projectBoardPayloadSchema, projectListQuerySchema, entityDeleteQuerySchema, removeActivityEventSchema, resolveApprovalRequestSchema, rewardsLedgerQuerySchema, habitListQuerySchema, taskContextPayloadSchema, taskRunClaimSchema, taskRunFocusSchema, taskRunFinishSchema, taskRunHeartbeatSchema, taskRunListQuerySchema, taskListQuerySchema, tagSuggestionRequestSchema, uncompleteTaskSchema, updateSettingsSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateStrategySchema, updateUserSchema, updateCalendarConnectionSchema, updateCalendarEventSchema, updateNoteSchema, updateProjectSchema, updateRewardRuleSchema, updateTaskTimeboxSchema, updateTaskSchema, updateUserAccessGrantSchema, updateWorkBlockTemplateSchema, updateAiConnectorSchema, updateAiProcessorSchema, runAiProcessorSchema, workAdjustmentResultSchema, finalizeWeeklyReviewResultSchema, goalListQuerySchema, recommendTaskTimeboxesSchema, strategyListQuerySchema } from "./types.js";
|
|
44
54
|
import { buildOpenApiDocument } from "./openapi.js";
|
|
45
55
|
import { registerWebRoutes } from "./web.js";
|
|
46
56
|
import { createManagerRuntime } from "./managers/runtime.js";
|
|
47
57
|
import { isManagerError } from "./managers/type-guards.js";
|
|
48
|
-
import { createCompanionPairingSession, createCompanionPairingSessionSchema, getCompanionOverview, getFitnessViewData, getSleepViewData, ingestMobileHealthSync, mobileHealthSyncSchema, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
|
|
58
|
+
import { createCompanionPairingSession, createCompanionPairingSessionSchema, getCompanionOverview, getFitnessViewData, getSleepViewData, ingestMobileHealthSync, mobileHealthSyncSchema, requireValidPairing, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
|
|
59
|
+
import { createMovementPlace, getMovementAllTimeSummary, getMovementDayDetail, getMovementMobileBootstrap, getMovementTimeline, getMovementSelectionAggregate, getMovementSettings, getMovementTripDetail, getMovementMonthSummary, listMovementPlaces, movementMobileBootstrapSchema, movementMobilePlaceMutationSchema, movementMobileStayPatchSchema, movementMobileTimelineSchema, movementMobileTripPatchSchema, movementPlaceMutationSchema, movementPlacePatchSchema, movementSelectionAggregateSchema, movementStayPatchSchema, movementSettingsPatchSchema, movementTimelineQuerySchema, movementTripPointPatchSchema, movementTripPatchSchema, deleteMovementTripPoint, deleteMovementStay, deleteMovementTrip, updateMovementPlace, updateMovementStay, updateMovementSettings, updateMovementTrip, updateMovementTripPoint } from "./movement.js";
|
|
60
|
+
import { assertWatchReady, buildWatchBootstrap, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
|
|
49
61
|
const COMPATIBILITY_SUNSET = "transitional-node";
|
|
50
62
|
function markCompatibilityRoute(reply) {
|
|
51
63
|
reply.header("Deprecation", "true");
|
|
@@ -116,8 +128,7 @@ function buildApiBaseUrl(request) {
|
|
|
116
128
|
if (referer) {
|
|
117
129
|
try {
|
|
118
130
|
const url = new URL(referer);
|
|
119
|
-
|
|
120
|
-
return `${url.origin}${forgeMounted ? "/forge" : ""}/api/v1`;
|
|
131
|
+
return `${url.origin}/api/v1`;
|
|
121
132
|
}
|
|
122
133
|
catch {
|
|
123
134
|
// Fall through to host-based resolution.
|
|
@@ -1881,13 +1892,15 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
|
|
|
1881
1892
|
"Ask only for what is missing or unclear instead of walking the user through every optional field.",
|
|
1882
1893
|
"Use a progression of concrete example or intent, working name, purpose or meaning, placement in Forge, operational details, and linked context.",
|
|
1883
1894
|
"Ask one to three focused questions at a time. One is usually best when the user is uncertain or emotionally loaded.",
|
|
1895
|
+
"If the user already answered the normal opening question, do not repeat it. Move to the next missing clarification.",
|
|
1896
|
+
"Do not over-therapize logistical entities. For tasks, calendar events, work blocks, timeboxes, and task runs, one brief confirming sentence plus one question is usually enough.",
|
|
1884
1897
|
"Before saving, briefly summarize the working formulation in the user's own language when that would reduce ambiguity.",
|
|
1885
1898
|
"When updating an entity, start with what is changing, what should stay true, and what prompted the update now."
|
|
1886
1899
|
];
|
|
1887
1900
|
const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
1888
1901
|
{
|
|
1889
1902
|
focus: "goal",
|
|
1890
|
-
openingQuestion: "What direction
|
|
1903
|
+
openingQuestion: "What direction here feels important enough that you want to keep it in view?",
|
|
1891
1904
|
coachingGoal: "Clarify the direction and why it matters, not just produce a title.",
|
|
1892
1905
|
askSequence: [
|
|
1893
1906
|
"Ask what direction or outcome the user wants to keep in view.",
|
|
@@ -1898,12 +1911,13 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
1898
1911
|
},
|
|
1899
1912
|
{
|
|
1900
1913
|
focus: "project",
|
|
1901
|
-
openingQuestion: "If this
|
|
1914
|
+
openingQuestion: "If this became a real project, what would you be trying to make true?",
|
|
1902
1915
|
coachingGoal: "Turn an intention into a bounded workstream with a clear outcome.",
|
|
1903
1916
|
askSequence: [
|
|
1904
|
-
"Ask what this piece of work
|
|
1917
|
+
"Ask what this piece of work is trying to make true.",
|
|
1905
1918
|
"Ask what outcome would make the project feel real or complete for now.",
|
|
1906
1919
|
"Ask which goal it belongs under.",
|
|
1920
|
+
"Land on a working name once the scope is clear.",
|
|
1907
1921
|
"Clarify status, owner, and notes only after the scope is clear."
|
|
1908
1922
|
]
|
|
1909
1923
|
},
|
|
@@ -1930,18 +1944,18 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
1930
1944
|
},
|
|
1931
1945
|
{
|
|
1932
1946
|
focus: "habit",
|
|
1933
|
-
openingQuestion: "What
|
|
1947
|
+
openingQuestion: "What recurring move are you trying to strengthen or loosen?",
|
|
1934
1948
|
coachingGoal: "Define the recurring behavior and cadence clearly enough for honest later check-ins.",
|
|
1935
1949
|
askSequence: [
|
|
1936
1950
|
"Ask what the recurring behavior is in plain language.",
|
|
1937
1951
|
"Ask whether doing it is aligned or a slip.",
|
|
1938
|
-
"Ask about cadence and what counts as
|
|
1952
|
+
"Ask about cadence and what counts as an honest check-in in practice.",
|
|
1939
1953
|
"Ask about links only if they will help later review."
|
|
1940
1954
|
]
|
|
1941
1955
|
},
|
|
1942
1956
|
{
|
|
1943
1957
|
focus: "note",
|
|
1944
|
-
openingQuestion: "What
|
|
1958
|
+
openingQuestion: "What feels important to preserve from this?",
|
|
1945
1959
|
coachingGoal: "Preserve the useful context and link it to the right places without turning the note into a dump.",
|
|
1946
1960
|
askSequence: [
|
|
1947
1961
|
"Ask what the note needs to preserve.",
|
|
@@ -1994,7 +2008,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
1994
2008
|
},
|
|
1995
2009
|
{
|
|
1996
2010
|
focus: "event_type",
|
|
1997
|
-
openingQuestion: "
|
|
2011
|
+
openingQuestion: "When this kind of moment happens, what would you want to call it so future reports stay consistent?",
|
|
1998
2012
|
coachingGoal: "Create a reusable incident category that will actually help future reports stay consistent.",
|
|
1999
2013
|
askSequence: [
|
|
2000
2014
|
"Ask what category the label should capture.",
|
|
@@ -2004,7 +2018,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
2004
2018
|
},
|
|
2005
2019
|
{
|
|
2006
2020
|
focus: "emotion_definition",
|
|
2007
|
-
openingQuestion: "What emotion
|
|
2021
|
+
openingQuestion: "What emotion do you want Forge to help you name clearly and reuse later?",
|
|
2008
2022
|
coachingGoal: "Create a reusable emotion label with enough clarity to use consistently later.",
|
|
2009
2023
|
askSequence: [
|
|
2010
2024
|
"Ask what emotion label the user wants to preserve.",
|
|
@@ -2086,7 +2100,8 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
2086
2100
|
"A pattern is usually the best Psyche container for functional analysis.",
|
|
2087
2101
|
"If the user is describing one specific episode rather than a repeated loop, prefer a trigger report.",
|
|
2088
2102
|
"Reflect before the next question, and avoid interrogating through the schema fields in order.",
|
|
2089
|
-
"If the user asks to understand the loop first, do not lead with a finished working diagnosis or title before asking at least one clarifying question."
|
|
2103
|
+
"If the user asks to understand the loop first, do not lead with a finished working diagnosis or title before asking at least one clarifying question.",
|
|
2104
|
+
"Before you ask how to change the loop, ask what it is protecting, preventing, or managing for the user."
|
|
2090
2105
|
]
|
|
2091
2106
|
},
|
|
2092
2107
|
{
|
|
@@ -2127,7 +2142,8 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
2127
2142
|
notes: [
|
|
2128
2143
|
"Keep the user close to observable behavior rather than jumping straight to labels.",
|
|
2129
2144
|
"When the behavior clearly belongs inside a larger loop, suggest linking or also mapping the related behavior_pattern.",
|
|
2130
|
-
"If the user asks for understanding before storage, ask about the recent example and function of the move before classifying it."
|
|
2145
|
+
"If the user asks for understanding before storage, ask about the recent example and function of the move before classifying it.",
|
|
2146
|
+
"Ask what the move is trying to do for the user before moving into replacement planning."
|
|
2131
2147
|
]
|
|
2132
2148
|
},
|
|
2133
2149
|
{
|
|
@@ -2157,6 +2173,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
2157
2173
|
],
|
|
2158
2174
|
exampleQuestions: [
|
|
2159
2175
|
"If we turned that reaction into one sentence, what would it sound like?",
|
|
2176
|
+
"When that reaction hits, what does it start telling you?",
|
|
2160
2177
|
"Is it more of an always/never belief, or an if-then rule?",
|
|
2161
2178
|
"How true does it feel right now from 0 to 100?",
|
|
2162
2179
|
"What seems to support it, and what weakens it?",
|
|
@@ -2166,7 +2183,8 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
2166
2183
|
notes: [
|
|
2167
2184
|
"Schema catalog entries are reference concepts; belief_entry is the user-owned record.",
|
|
2168
2185
|
"If no schema catalog match is known, omit schemaId rather than inventing one.",
|
|
2169
|
-
"Do not argue the user out of the belief. Reflect it, understand its function, and then collaboratively test for flexibility."
|
|
2186
|
+
"Do not argue the user out of the belief. Reflect it, understand its function, and then collaboratively test for flexibility.",
|
|
2187
|
+
"When the wording is nearly there, ask whether it feels true enough before you move into confidence, evidence, or alternative-belief details."
|
|
2170
2188
|
]
|
|
2171
2189
|
},
|
|
2172
2190
|
{
|
|
@@ -2199,6 +2217,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
2199
2217
|
],
|
|
2200
2218
|
exampleQuestions: [
|
|
2201
2219
|
"When this part shows up, what is it like from the inside?",
|
|
2220
|
+
"When this part takes over, what is it trying to protect?",
|
|
2202
2221
|
"What kind of part does this feel like: coping, child, critic-parent, healthy-adult, or happy-child?",
|
|
2203
2222
|
"If you gave this mode a name, what would it be?",
|
|
2204
2223
|
"What is it afraid would happen if it stopped doing its job?",
|
|
@@ -2227,6 +2246,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
2227
2246
|
highValueOptionalFields: [],
|
|
2228
2247
|
exampleQuestions: [
|
|
2229
2248
|
"What just happened that brought this up right now?",
|
|
2249
|
+
"What just happened before this part came online?",
|
|
2230
2250
|
"If this part had a voice, what would it be saying?",
|
|
2231
2251
|
"What is it trying to protect you from?",
|
|
2232
2252
|
"What does it seem to need from you or from someone else?",
|
|
@@ -2322,7 +2342,8 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
2322
2342
|
"entityType alone is never enough; full data is required.",
|
|
2323
2343
|
"Batch multiple related creates together when they come from one user ask.",
|
|
2324
2344
|
"Goal, project, and task creates can include notes: [{ contentMarkdown, author?, tags?, destroyAt?, links? }] and Forge will auto-link those notes to the newly created entity.",
|
|
2325
|
-
"The same batch create route also handles calendar_event, work_block_template,
|
|
2345
|
+
"The same batch create route also handles calendar_event, work_block_template, task_timebox, preference_catalog, preference_catalog_item, preference_context, preference_item, and questionnaire_instrument.",
|
|
2346
|
+
"Calendar-event creates still trigger downstream projection sync when a writable provider calendar is selected."
|
|
2326
2347
|
],
|
|
2327
2348
|
example: '{"operations":[{"entityType":"task","data":{"title":"Write the public release notes","projectId":"project_123","status":"focus","notes":[{"contentMarkdown":"Starting from the changelog draft and the last QA pass."}]},"clientRef":"task-1"}]}'
|
|
2328
2349
|
},
|
|
@@ -2342,7 +2363,7 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
2342
2363
|
"Project lifecycle is status-driven: patch project.status to active, paused, or completed instead of looking for separate suspend, restart, or finish routes.",
|
|
2343
2364
|
"Setting project.status to completed finishes the project and auto-completes linked unfinished tasks through the normal task completion path.",
|
|
2344
2365
|
"Task and project scheduling rules stay on these same entity patches. Update task.schedulingRules, task.plannedDurationSeconds, or project.schedulingRules here.",
|
|
2345
|
-
"Use this same route to move or relink calendar_event records
|
|
2366
|
+
"Use this same route to move or relink calendar_event records, edit work_block_template or task_timebox records, and do normal field updates on preference_catalog, preference_catalog_item, preference_context, preference_item, and questionnaire_instrument."
|
|
2346
2367
|
],
|
|
2347
2368
|
example: '{"operations":[{"entityType":"project","id":"project_123","patch":{"status":"completed"},"clientRef":"project-finish-1"}]}'
|
|
2348
2369
|
},
|
|
@@ -2548,10 +2569,10 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
2548
2569
|
toolName: "forge_connect_calendar_provider",
|
|
2549
2570
|
summary: "Create a Forge calendar connection for Google, Apple, Exchange Online, or custom CalDAV.",
|
|
2550
2571
|
whenToUse: "Use only when the operator explicitly wants Forge connected to an external calendar provider.",
|
|
2551
|
-
inputShape: '{ provider: "google"|"apple"|"caldav"|"microsoft", label: string, username?: string,
|
|
2572
|
+
inputShape: '{ provider: "google"|"apple"|"caldav"|"microsoft", label: string, username?: string, password?: string, serverUrl?: string, authSessionId?: string, selectedCalendarUrls: string[], forgeCalendarUrl?: string, createForgeCalendar?: boolean }',
|
|
2552
2573
|
requiredFields: ["provider", "label", "provider-specific credentials"],
|
|
2553
2574
|
notes: [
|
|
2554
|
-
"Google uses
|
|
2575
|
+
"Google now uses an interactive localhost Authorization Code + PKCE flow. The user signs in interactively on the same machine running Forge, Forge exchanges the authorization code on the backend, and forge_connect_calendar_provider should only be used after a completed Google authSessionId exists.",
|
|
2555
2576
|
"Apple starts from https://caldav.icloud.com and autodiscovers the principal plus calendars after authentication.",
|
|
2556
2577
|
"Exchange Online uses Microsoft Graph. In the current Forge implementation it is read-only: Forge mirrors the selected calendars but does not publish work blocks or timeboxes back to Microsoft.",
|
|
2557
2578
|
"In the current self-hosted local runtime, Exchange Online now uses an interactive Microsoft public-client sign-in flow with PKCE after the operator has saved the Microsoft client ID, tenant, and redirect URI in Settings -> Calendar. Non-interactive callers should treat Microsoft connection setup as a Settings-owned operator action unless a completed authSessionId already exists.",
|
|
@@ -2777,6 +2798,9 @@ function buildAgentOnboardingPayload(request) {
|
|
|
2777
2798
|
wiki: "Forge Wiki is the file-first memory layer: local Markdown pages plus media, backlinks, optional embeddings, explicit spaces, and structured links back to Forge entities.",
|
|
2778
2799
|
sleepSession: "A sleep session is a first-class health record with timing, sleep and bed duration, stage breakdown, recovery metrics, annotations, and Forge links back to planning or Psyche context.",
|
|
2779
2800
|
workoutSession: "A workout session is a first-class sports record imported from HealthKit or generated from a habit. It holds workout type, timing, energy or distance when available, subjective effort, narrative context, and Forge links.",
|
|
2801
|
+
preferences: "Forge Preferences is the explicit taste-modeling domain. It has workspaces, contexts, concept libraries, direct items, pairwise judgments, direct signals, and inferred scores.",
|
|
2802
|
+
questionnaire: "Forge Psyche questionnaires are structured reusable instruments with provenance, scoring, draft and published versions, and user-owned answer runs.",
|
|
2803
|
+
selfObservation: "Forge self-observation is a dedicated Psyche calendar view backed by observed notes timestamped by frontmatter.observedAt, including deliberate reflection notes and rolling movement notes from the companion.",
|
|
2780
2804
|
insight: "An agent-authored observation or recommendation grounded in Forge data.",
|
|
2781
2805
|
calendar: "A connected calendar source mirrored into Forge. Calendar state combines provider events, recurring work blocks, and task timeboxes.",
|
|
2782
2806
|
workBlock: "A recurring half-day or custom time window such as Main Activity, Secondary Activity, Third Activity, Rest, Holiday, or Custom. Work blocks can allow or block work by default, can define active date bounds, and remain editable through the calendar surface.",
|
|
@@ -2814,6 +2838,94 @@ function buildAgentOnboardingPayload(request) {
|
|
|
2814
2838
|
"Behavior patterns, behaviors, beliefs, modes, and trigger reports cross-link to describe one reflective model rather than isolated records.",
|
|
2815
2839
|
"Insights can point at one entity, but they exist to capture interpretation or advice rather than raw work items."
|
|
2816
2840
|
],
|
|
2841
|
+
entityRouteModel: {
|
|
2842
|
+
batchCrudEntities: [
|
|
2843
|
+
"goal",
|
|
2844
|
+
"project",
|
|
2845
|
+
"task",
|
|
2846
|
+
"strategy",
|
|
2847
|
+
"habit",
|
|
2848
|
+
"tag",
|
|
2849
|
+
"note",
|
|
2850
|
+
"insight",
|
|
2851
|
+
"calendar_event",
|
|
2852
|
+
"work_block_template",
|
|
2853
|
+
"task_timebox",
|
|
2854
|
+
"psyche_value",
|
|
2855
|
+
"behavior_pattern",
|
|
2856
|
+
"behavior",
|
|
2857
|
+
"belief_entry",
|
|
2858
|
+
"mode_profile",
|
|
2859
|
+
"mode_guide_session",
|
|
2860
|
+
"event_type",
|
|
2861
|
+
"emotion_definition",
|
|
2862
|
+
"trigger_report",
|
|
2863
|
+
"preference_catalog",
|
|
2864
|
+
"preference_catalog_item",
|
|
2865
|
+
"preference_context",
|
|
2866
|
+
"preference_item",
|
|
2867
|
+
"questionnaire_instrument"
|
|
2868
|
+
],
|
|
2869
|
+
batchRoutes: {
|
|
2870
|
+
search: "/api/v1/entities/search",
|
|
2871
|
+
create: "/api/v1/entities/create",
|
|
2872
|
+
update: "/api/v1/entities/update",
|
|
2873
|
+
delete: "/api/v1/entities/delete",
|
|
2874
|
+
restore: "/api/v1/entities/restore"
|
|
2875
|
+
},
|
|
2876
|
+
actionEntities: {
|
|
2877
|
+
task_run: {
|
|
2878
|
+
readModel: "/api/v1/operator/context",
|
|
2879
|
+
actions: {
|
|
2880
|
+
start: "/api/v1/tasks/:taskId/runs",
|
|
2881
|
+
heartbeat: "/api/v1/task-runs/:id/heartbeat",
|
|
2882
|
+
focus: "/api/v1/task-runs/:id/focus",
|
|
2883
|
+
complete: "/api/v1/task-runs/:id/complete",
|
|
2884
|
+
release: "/api/v1/task-runs/:id/release"
|
|
2885
|
+
}
|
|
2886
|
+
},
|
|
2887
|
+
questionnaire_run: {
|
|
2888
|
+
read: "/api/v1/psyche/questionnaire-runs/:id",
|
|
2889
|
+
actions: {
|
|
2890
|
+
start: "/api/v1/psyche/questionnaires/:id/runs",
|
|
2891
|
+
update: "/api/v1/psyche/questionnaire-runs/:id",
|
|
2892
|
+
complete: "/api/v1/psyche/questionnaire-runs/:id/complete"
|
|
2893
|
+
}
|
|
2894
|
+
},
|
|
2895
|
+
preferences: {
|
|
2896
|
+
workspace: "/api/v1/preferences/workspace",
|
|
2897
|
+
actions: {
|
|
2898
|
+
startGame: "/api/v1/preferences/game/start",
|
|
2899
|
+
mergeContexts: "/api/v1/preferences/contexts/merge",
|
|
2900
|
+
enqueueFromEntity: "/api/v1/preferences/items/from-entity",
|
|
2901
|
+
submitJudgment: "/api/v1/preferences/judgments",
|
|
2902
|
+
submitSignal: "/api/v1/preferences/signals",
|
|
2903
|
+
overrideScore: "/api/v1/preferences/items/:id/score"
|
|
2904
|
+
}
|
|
2905
|
+
},
|
|
2906
|
+
questionnaires: {
|
|
2907
|
+
list: "/api/v1/psyche/questionnaires",
|
|
2908
|
+
detail: "/api/v1/psyche/questionnaires/:id",
|
|
2909
|
+
actions: {
|
|
2910
|
+
clone: "/api/v1/psyche/questionnaires/:id/clone",
|
|
2911
|
+
ensureDraft: "/api/v1/psyche/questionnaires/:id/draft",
|
|
2912
|
+
publishDraft: "/api/v1/psyche/questionnaires/:id/publish"
|
|
2913
|
+
}
|
|
2914
|
+
},
|
|
2915
|
+
selfObservation: {
|
|
2916
|
+
read: "/api/v1/psyche/self-observation/calendar",
|
|
2917
|
+
writeModel: "Create or update an observed note with frontmatter.observedAt. Manual reflections usually carry the Self-observation tag, while movement sync can also publish rolling observed notes tagged movement."
|
|
2918
|
+
},
|
|
2919
|
+
sleep_session: {
|
|
2920
|
+
read: "/api/v1/health/sleep",
|
|
2921
|
+
update: "/api/v1/health/sleep/:id"
|
|
2922
|
+
},
|
|
2923
|
+
workout_session: {
|
|
2924
|
+
read: "/api/v1/health/fitness",
|
|
2925
|
+
update: "/api/v1/health/workouts/:id"
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
},
|
|
2817
2929
|
multiUserModel: {
|
|
2818
2930
|
summary: "Forge is multi-user by default. Humans and bots share one entity graph, with explicit ownership on every record and directional relationship settings between every pair of users.",
|
|
2819
2931
|
defaultUserScopeBehavior: "If no user scope is provided, Forge returns all visible users. Use userId or repeated userIds when an agent should focus on one owner namespace or on a specific human/bot slice.",
|
|
@@ -2858,6 +2970,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
2858
2970
|
],
|
|
2859
2971
|
configNotes: [
|
|
2860
2972
|
"Localhost and Tailscale targets can usually use the operator-session path without a long-lived token.",
|
|
2973
|
+
"Use a distinct actor label such as Albert (claw) so OpenClaw-originated work stays obvious in Forge provenance.",
|
|
2861
2974
|
"Create each agent as a Forge bot user, then use userId or userIds in tool inputs whenever the agent should focus on one human, one bot, or a specific collaboration slice."
|
|
2862
2975
|
]
|
|
2863
2976
|
},
|
|
@@ -2875,6 +2988,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
2875
2988
|
],
|
|
2876
2989
|
configNotes: [
|
|
2877
2990
|
"Hermes keeps its durable Forge config under ~/.hermes/forge/config.json.",
|
|
2991
|
+
"Use a distinct actor label such as Albert (hermes) so Hermes-originated work stays obvious in Forge provenance.",
|
|
2878
2992
|
"Hermes uses the same multi-user scoping rules and should pass userIds intentionally when working across humans and bots.",
|
|
2879
2993
|
"The Forge relationship graph still decides whether Hermes may see, message, plan for, or affect another owner."
|
|
2880
2994
|
]
|
|
@@ -2958,8 +3072,8 @@ function buildAgentOnboardingPayload(request) {
|
|
|
2958
3072
|
saveSuggestionPlacement: "end_of_message",
|
|
2959
3073
|
saveSuggestionTone: "gentle_optional",
|
|
2960
3074
|
maxQuestionsPerTurn: 1,
|
|
2961
|
-
psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, and wait for the user's answer before offering a fuller formulation.",
|
|
2962
|
-
psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what
|
|
3075
|
+
psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, and wait for the user's answer before offering a fuller formulation.",
|
|
3076
|
+
psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what cue or body signal came first before the behavior, what the belief starts saying about self or outcome, what feels most at risk inside the mode, what the part is trying to get the user to do or stop doing, or where the shift began in the incident. Reflect briefly before the question and stay in one follow-up lane at a time.",
|
|
2963
3077
|
duplicateCheckRoute: "/api/v1/entities/search",
|
|
2964
3078
|
uiSuggestionRule: "offer_visual_ui_when_review_or_editing_would_be_easier",
|
|
2965
3079
|
browserFallbackRule: "Do not open the Forge UI or a browser just to create or update normal entities when the batch entity tools can do the job.",
|
|
@@ -3528,8 +3642,43 @@ export async function buildServer(options = {}) {
|
|
|
3528
3642
|
}
|
|
3529
3643
|
}, DIAGNOSTIC_LOG_RETENTION_SWEEP_INTERVAL_MS);
|
|
3530
3644
|
diagnosticRetentionTimer.unref?.();
|
|
3645
|
+
const activeCronRuns = new Set();
|
|
3646
|
+
const cronSchedulerTimer = setInterval(() => {
|
|
3647
|
+
const now = new Date();
|
|
3648
|
+
for (const processor of listAiProcessors()) {
|
|
3649
|
+
if (processor.triggerMode !== "cron" ||
|
|
3650
|
+
!processor.endpointEnabled ||
|
|
3651
|
+
!processor.cronExpression.trim() ||
|
|
3652
|
+
activeCronRuns.has(processor.id)) {
|
|
3653
|
+
continue;
|
|
3654
|
+
}
|
|
3655
|
+
try {
|
|
3656
|
+
const interval = CronExpressionParser.parse(processor.cronExpression, {
|
|
3657
|
+
currentDate: processor.lastRunAt && Number.isFinite(Date.parse(processor.lastRunAt))
|
|
3658
|
+
? processor.lastRunAt
|
|
3659
|
+
: new Date(now.getTime() - 60_000).toISOString()
|
|
3660
|
+
});
|
|
3661
|
+
const nextDueAt = interval.next().toDate();
|
|
3662
|
+
if (nextDueAt.getTime() > now.getTime()) {
|
|
3663
|
+
continue;
|
|
3664
|
+
}
|
|
3665
|
+
activeCronRuns.add(processor.id);
|
|
3666
|
+
void runAiProcessor(processor.id, { input: "", context: {}, widgetSnapshots: {} }, {
|
|
3667
|
+
llm: managers.llm,
|
|
3668
|
+
secrets: managers.secrets
|
|
3669
|
+
}, { trigger: "cron" }).finally(() => {
|
|
3670
|
+
activeCronRuns.delete(processor.id);
|
|
3671
|
+
});
|
|
3672
|
+
}
|
|
3673
|
+
catch {
|
|
3674
|
+
continue;
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
}, 30_000);
|
|
3678
|
+
cronSchedulerTimer.unref?.();
|
|
3531
3679
|
app.addHook("onClose", async () => {
|
|
3532
3680
|
clearInterval(diagnosticRetentionTimer);
|
|
3681
|
+
clearInterval(cronSchedulerTimer);
|
|
3533
3682
|
taskRunWatchdog?.stop();
|
|
3534
3683
|
await managers.backgroundJobs.stop();
|
|
3535
3684
|
});
|
|
@@ -3858,6 +4007,147 @@ export async function buildServer(options = {}) {
|
|
|
3858
4007
|
app.get("/api/v1/health/fitness", async (request) => ({
|
|
3859
4008
|
fitness: getFitnessViewData(resolveScopedUserIds(request.query))
|
|
3860
4009
|
}));
|
|
4010
|
+
app.get("/api/v1/movement/day", async (request) => {
|
|
4011
|
+
const query = request.query;
|
|
4012
|
+
return {
|
|
4013
|
+
movement: getMovementDayDetail({
|
|
4014
|
+
date: typeof query.date === "string" ? query.date : undefined,
|
|
4015
|
+
userIds: resolveScopedUserIds(query)
|
|
4016
|
+
})
|
|
4017
|
+
};
|
|
4018
|
+
});
|
|
4019
|
+
app.get("/api/v1/movement/month", async (request) => {
|
|
4020
|
+
const query = request.query;
|
|
4021
|
+
return {
|
|
4022
|
+
movement: getMovementMonthSummary({
|
|
4023
|
+
month: typeof query.month === "string" ? query.month : undefined,
|
|
4024
|
+
userIds: resolveScopedUserIds(query)
|
|
4025
|
+
})
|
|
4026
|
+
};
|
|
4027
|
+
});
|
|
4028
|
+
app.get("/api/v1/movement/all-time", async (request) => ({
|
|
4029
|
+
movement: getMovementAllTimeSummary(resolveScopedUserIds(request.query))
|
|
4030
|
+
}));
|
|
4031
|
+
app.get("/api/v1/movement/timeline", async (request) => {
|
|
4032
|
+
const parsed = movementTimelineQuerySchema.parse(request.query ?? {});
|
|
4033
|
+
return {
|
|
4034
|
+
movement: getMovementTimeline({
|
|
4035
|
+
...parsed,
|
|
4036
|
+
userIds: parsed.userIds.length > 0
|
|
4037
|
+
? parsed.userIds
|
|
4038
|
+
: (resolveScopedUserIds(request.query) ?? [])
|
|
4039
|
+
})
|
|
4040
|
+
};
|
|
4041
|
+
});
|
|
4042
|
+
app.get("/api/v1/movement/settings", async (request) => ({
|
|
4043
|
+
settings: getMovementSettings(resolveScopedUserIds(request.query))
|
|
4044
|
+
}));
|
|
4045
|
+
app.patch("/api/v1/movement/settings", async (request) => {
|
|
4046
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/settings" });
|
|
4047
|
+
const userId = resolveScopedUserIds(request.query)?.[0] ??
|
|
4048
|
+
getDefaultUser().id;
|
|
4049
|
+
return {
|
|
4050
|
+
settings: updateMovementSettings(userId, movementSettingsPatchSchema.parse(request.body ?? {}), toActivityContext(auth))
|
|
4051
|
+
};
|
|
4052
|
+
});
|
|
4053
|
+
app.get("/api/v1/movement/places", async (request) => ({
|
|
4054
|
+
places: listMovementPlaces(resolveScopedUserIds(request.query))
|
|
4055
|
+
}));
|
|
4056
|
+
app.post("/api/v1/movement/places", async (request, reply) => {
|
|
4057
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/places" });
|
|
4058
|
+
const userId = resolveScopedUserIds(request.query)?.[0] ??
|
|
4059
|
+
getDefaultUser().id;
|
|
4060
|
+
reply.code(201);
|
|
4061
|
+
return {
|
|
4062
|
+
place: createMovementPlace({
|
|
4063
|
+
...movementPlaceMutationSchema.parse(request.body ?? {}),
|
|
4064
|
+
userId,
|
|
4065
|
+
source: "user"
|
|
4066
|
+
}, toActivityContext(auth))
|
|
4067
|
+
};
|
|
4068
|
+
});
|
|
4069
|
+
app.patch("/api/v1/movement/places/:id", async (request, reply) => {
|
|
4070
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/places/:id" });
|
|
4071
|
+
const { id } = request.params;
|
|
4072
|
+
const place = updateMovementPlace(id, movementPlacePatchSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4073
|
+
if (!place) {
|
|
4074
|
+
reply.code(404);
|
|
4075
|
+
return { error: "Movement place not found" };
|
|
4076
|
+
}
|
|
4077
|
+
return { place };
|
|
4078
|
+
});
|
|
4079
|
+
app.patch("/api/v1/movement/stays/:id", async (request, reply) => {
|
|
4080
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/stays/:id" });
|
|
4081
|
+
const { id } = request.params;
|
|
4082
|
+
const stay = updateMovementStay(id, movementStayPatchSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4083
|
+
if (!stay) {
|
|
4084
|
+
reply.code(404);
|
|
4085
|
+
return { error: "Movement stay not found" };
|
|
4086
|
+
}
|
|
4087
|
+
return { stay };
|
|
4088
|
+
});
|
|
4089
|
+
app.delete("/api/v1/movement/stays/:id", async (request, reply) => {
|
|
4090
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/stays/:id" });
|
|
4091
|
+
const { id } = request.params;
|
|
4092
|
+
const result = deleteMovementStay(id, toActivityContext(auth));
|
|
4093
|
+
if (!result) {
|
|
4094
|
+
reply.code(404);
|
|
4095
|
+
return { error: "Movement stay not found" };
|
|
4096
|
+
}
|
|
4097
|
+
return result;
|
|
4098
|
+
});
|
|
4099
|
+
app.patch("/api/v1/movement/trips/:id", async (request, reply) => {
|
|
4100
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/trips/:id" });
|
|
4101
|
+
const { id } = request.params;
|
|
4102
|
+
const trip = updateMovementTrip(id, movementTripPatchSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4103
|
+
if (!trip) {
|
|
4104
|
+
reply.code(404);
|
|
4105
|
+
return { error: "Movement trip not found" };
|
|
4106
|
+
}
|
|
4107
|
+
return { trip };
|
|
4108
|
+
});
|
|
4109
|
+
app.delete("/api/v1/movement/trips/:id", async (request, reply) => {
|
|
4110
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/trips/:id" });
|
|
4111
|
+
const { id } = request.params;
|
|
4112
|
+
const result = deleteMovementTrip(id, toActivityContext(auth));
|
|
4113
|
+
if (!result) {
|
|
4114
|
+
reply.code(404);
|
|
4115
|
+
return { error: "Movement trip not found" };
|
|
4116
|
+
}
|
|
4117
|
+
return result;
|
|
4118
|
+
});
|
|
4119
|
+
app.patch("/api/v1/movement/trips/:id/points/:pointId", async (request, reply) => {
|
|
4120
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/trips/:id/points/:pointId" });
|
|
4121
|
+
const { id, pointId } = request.params;
|
|
4122
|
+
const result = updateMovementTripPoint(id, pointId, movementTripPointPatchSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4123
|
+
if (!result) {
|
|
4124
|
+
reply.code(404);
|
|
4125
|
+
return { error: "Movement datapoint not found" };
|
|
4126
|
+
}
|
|
4127
|
+
return result;
|
|
4128
|
+
});
|
|
4129
|
+
app.delete("/api/v1/movement/trips/:id/points/:pointId", async (request, reply) => {
|
|
4130
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/movement/trips/:id/points/:pointId" });
|
|
4131
|
+
const { id, pointId } = request.params;
|
|
4132
|
+
const result = deleteMovementTripPoint(id, pointId, toActivityContext(auth));
|
|
4133
|
+
if (!result) {
|
|
4134
|
+
reply.code(404);
|
|
4135
|
+
return { error: "Movement datapoint not found" };
|
|
4136
|
+
}
|
|
4137
|
+
return result;
|
|
4138
|
+
});
|
|
4139
|
+
app.get("/api/v1/movement/trips/:id", async (request, reply) => {
|
|
4140
|
+
const { id } = request.params;
|
|
4141
|
+
const movement = getMovementTripDetail(id);
|
|
4142
|
+
if (!movement) {
|
|
4143
|
+
reply.code(404);
|
|
4144
|
+
return { error: "Movement trip not found" };
|
|
4145
|
+
}
|
|
4146
|
+
return { movement };
|
|
4147
|
+
});
|
|
4148
|
+
app.post("/api/v1/movement/selection", async (request) => ({
|
|
4149
|
+
movement: getMovementSelectionAggregate(movementSelectionAggregateSchema.parse(request.body ?? {}))
|
|
4150
|
+
}));
|
|
3861
4151
|
app.post("/api/v1/health/pairing-sessions", async (request, reply) => {
|
|
3862
4152
|
requireOperatorSession(request.headers, {
|
|
3863
4153
|
route: "/api/v1/health/pairing-sessions"
|
|
@@ -3895,6 +4185,106 @@ export async function buildServer(options = {}) {
|
|
|
3895
4185
|
app.post("/api/v1/mobile/pairing/verify", async (request) => ({
|
|
3896
4186
|
pairing: verifyCompanionPairing(verifyCompanionPairingSchema.parse(request.body ?? {}))
|
|
3897
4187
|
}));
|
|
4188
|
+
app.post("/api/v1/mobile/movement/bootstrap", async (request) => {
|
|
4189
|
+
const parsed = movementMobileBootstrapSchema.parse(request.body ?? {});
|
|
4190
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4191
|
+
return {
|
|
4192
|
+
movement: getMovementMobileBootstrap(pairing)
|
|
4193
|
+
};
|
|
4194
|
+
});
|
|
4195
|
+
app.post("/api/v1/mobile/movement/places", async (request, reply) => {
|
|
4196
|
+
const parsed = movementMobilePlaceMutationSchema.parse(request.body ?? {});
|
|
4197
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4198
|
+
reply.code(201);
|
|
4199
|
+
return {
|
|
4200
|
+
place: createMovementPlace({
|
|
4201
|
+
...parsed.place,
|
|
4202
|
+
userId: pairing.user_id,
|
|
4203
|
+
source: "companion"
|
|
4204
|
+
}, {
|
|
4205
|
+
actor: "Forge Companion",
|
|
4206
|
+
source: "system"
|
|
4207
|
+
})
|
|
4208
|
+
};
|
|
4209
|
+
});
|
|
4210
|
+
app.post("/api/v1/mobile/movement/timeline", async (request) => {
|
|
4211
|
+
const parsed = movementMobileTimelineSchema.parse(request.body ?? {});
|
|
4212
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4213
|
+
return {
|
|
4214
|
+
movement: getMovementTimeline({
|
|
4215
|
+
before: parsed.before,
|
|
4216
|
+
limit: parsed.limit,
|
|
4217
|
+
userIds: [pairing.user_id]
|
|
4218
|
+
})
|
|
4219
|
+
};
|
|
4220
|
+
});
|
|
4221
|
+
app.patch("/api/v1/mobile/movement/stays/:id", async (request, reply) => {
|
|
4222
|
+
const parsed = movementMobileStayPatchSchema.parse(request.body ?? {});
|
|
4223
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4224
|
+
const { id } = request.params;
|
|
4225
|
+
const stay = updateMovementStay(id, parsed.patch, {
|
|
4226
|
+
actor: "Forge Companion",
|
|
4227
|
+
source: "system"
|
|
4228
|
+
}, { userId: pairing.user_id });
|
|
4229
|
+
if (!stay) {
|
|
4230
|
+
reply.code(404);
|
|
4231
|
+
return { error: "Movement stay not found" };
|
|
4232
|
+
}
|
|
4233
|
+
return { stay };
|
|
4234
|
+
});
|
|
4235
|
+
app.patch("/api/v1/mobile/movement/trips/:id", async (request, reply) => {
|
|
4236
|
+
const parsed = movementMobileTripPatchSchema.parse(request.body ?? {});
|
|
4237
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4238
|
+
const { id } = request.params;
|
|
4239
|
+
const trip = updateMovementTrip(id, parsed.patch, {
|
|
4240
|
+
actor: "Forge Companion",
|
|
4241
|
+
source: "system"
|
|
4242
|
+
}, { userId: pairing.user_id });
|
|
4243
|
+
if (!trip) {
|
|
4244
|
+
reply.code(404);
|
|
4245
|
+
return { error: "Movement trip not found" };
|
|
4246
|
+
}
|
|
4247
|
+
return { trip };
|
|
4248
|
+
});
|
|
4249
|
+
app.post("/api/v1/mobile/watch/bootstrap", async (request) => {
|
|
4250
|
+
const parsed = mobileWatchBootstrapSchema.parse(request.body ?? {});
|
|
4251
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4252
|
+
assertWatchReady(pairing);
|
|
4253
|
+
return {
|
|
4254
|
+
watch: buildWatchBootstrap(pairing)
|
|
4255
|
+
};
|
|
4256
|
+
});
|
|
4257
|
+
app.post("/api/v1/mobile/watch/habits/:id/check-ins", async (request, reply) => {
|
|
4258
|
+
const parsed = mobileWatchHabitCheckInSchema.parse(request.body ?? {});
|
|
4259
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4260
|
+
assertWatchReady(pairing);
|
|
4261
|
+
const { id } = request.params;
|
|
4262
|
+
const habit = createHabitCheckIn(id, {
|
|
4263
|
+
dateKey: parsed.dateKey,
|
|
4264
|
+
status: parsed.status,
|
|
4265
|
+
note: parsed.note
|
|
4266
|
+
}, { source: "system", actor: `watch:${parsed.dedupeKey}` });
|
|
4267
|
+
if (!habit) {
|
|
4268
|
+
reply.code(404);
|
|
4269
|
+
return { error: "Habit not found" };
|
|
4270
|
+
}
|
|
4271
|
+
return {
|
|
4272
|
+
habit,
|
|
4273
|
+
metrics: buildXpMetricsPayload(),
|
|
4274
|
+
watch: buildWatchBootstrap(pairing, {
|
|
4275
|
+
anchorDateKey: parsed.dateKey
|
|
4276
|
+
})
|
|
4277
|
+
};
|
|
4278
|
+
});
|
|
4279
|
+
app.post("/api/v1/mobile/watch/capture-events:batch", async (request) => {
|
|
4280
|
+
const parsed = mobileWatchCaptureBatchSchema.parse(request.body ?? {});
|
|
4281
|
+
const pairing = requireValidPairing(parsed.sessionId, parsed.pairingToken);
|
|
4282
|
+
assertWatchReady(pairing);
|
|
4283
|
+
return {
|
|
4284
|
+
receipt: ingestWatchCaptureBatch(pairing, parsed),
|
|
4285
|
+
watch: buildWatchBootstrap(pairing)
|
|
4286
|
+
};
|
|
4287
|
+
});
|
|
3898
4288
|
app.post("/api/v1/mobile/healthkit/sync", async (request) => ({
|
|
3899
4289
|
sync: ingestMobileHealthSync(mobileHealthSyncSchema.parse(request.body ?? {}))
|
|
3900
4290
|
}));
|
|
@@ -3948,6 +4338,88 @@ export async function buildServer(options = {}) {
|
|
|
3948
4338
|
const userIds = resolveScopedUserIds(request.query);
|
|
3949
4339
|
return { overview: getPsycheOverview(userIds) };
|
|
3950
4340
|
});
|
|
4341
|
+
app.get("/api/v1/psyche/questionnaires", async (request) => {
|
|
4342
|
+
requirePsycheScopedAccess(request.headers, ["psyche.read"], { route: "/api/v1/psyche/questionnaires" });
|
|
4343
|
+
const userIds = resolveScopedUserIds(request.query);
|
|
4344
|
+
return listQuestionnaireInstruments({ userIds });
|
|
4345
|
+
});
|
|
4346
|
+
app.post("/api/v1/psyche/questionnaires", async (request, reply) => {
|
|
4347
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write"], { route: "/api/v1/psyche/questionnaires" });
|
|
4348
|
+
const result = createQuestionnaireInstrument(createQuestionnaireInstrumentSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4349
|
+
reply.code(201);
|
|
4350
|
+
return result;
|
|
4351
|
+
});
|
|
4352
|
+
app.get("/api/v1/psyche/questionnaires/:id", async (request) => {
|
|
4353
|
+
requirePsycheScopedAccess(request.headers, ["psyche.read"], { route: "/api/v1/psyche/questionnaires/:id" });
|
|
4354
|
+
const { id } = request.params;
|
|
4355
|
+
const userIds = resolveScopedUserIds(request.query);
|
|
4356
|
+
return getQuestionnaireInstrumentDetail(id, { userIds });
|
|
4357
|
+
});
|
|
4358
|
+
app.post("/api/v1/psyche/questionnaires/:id/clone", async (request, reply) => {
|
|
4359
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write"], { route: "/api/v1/psyche/questionnaires/:id/clone" });
|
|
4360
|
+
const { id } = request.params;
|
|
4361
|
+
const body = (request.body ?? {});
|
|
4362
|
+
const userId = typeof body.userId === "string" && body.userId.trim().length > 0
|
|
4363
|
+
? body.userId.trim()
|
|
4364
|
+
: null;
|
|
4365
|
+
const result = cloneQuestionnaireInstrument(id, { userId }, toActivityContext(auth));
|
|
4366
|
+
reply.code(201);
|
|
4367
|
+
return result;
|
|
4368
|
+
});
|
|
4369
|
+
app.post("/api/v1/psyche/questionnaires/:id/draft", async (request) => {
|
|
4370
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write"], { route: "/api/v1/psyche/questionnaires/:id/draft" });
|
|
4371
|
+
const { id } = request.params;
|
|
4372
|
+
return ensureQuestionnaireDraftVersion(id, toActivityContext(auth));
|
|
4373
|
+
});
|
|
4374
|
+
app.patch("/api/v1/psyche/questionnaires/:id/draft", async (request) => {
|
|
4375
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write"], { route: "/api/v1/psyche/questionnaires/:id/draft" });
|
|
4376
|
+
const { id } = request.params;
|
|
4377
|
+
return updateQuestionnaireDraftVersion(id, updateQuestionnaireVersionSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4378
|
+
});
|
|
4379
|
+
app.post("/api/v1/psyche/questionnaires/:id/publish", async (request) => {
|
|
4380
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write"], { route: "/api/v1/psyche/questionnaires/:id/publish" });
|
|
4381
|
+
const { id } = request.params;
|
|
4382
|
+
return publishQuestionnaireDraftVersion(id, publishQuestionnaireVersionSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4383
|
+
});
|
|
4384
|
+
app.post("/api/v1/psyche/questionnaires/:id/runs", async (request, reply) => {
|
|
4385
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write", "psyche.read"], { route: "/api/v1/psyche/questionnaires/:id/runs" });
|
|
4386
|
+
const { id } = request.params;
|
|
4387
|
+
const result = startQuestionnaireRun(id, startQuestionnaireRunSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4388
|
+
reply.code(201);
|
|
4389
|
+
return result;
|
|
4390
|
+
});
|
|
4391
|
+
app.get("/api/v1/psyche/questionnaire-runs/:id", async (request) => {
|
|
4392
|
+
requirePsycheScopedAccess(request.headers, ["psyche.read"], { route: "/api/v1/psyche/questionnaire-runs/:id" });
|
|
4393
|
+
const { id } = request.params;
|
|
4394
|
+
const userIds = resolveScopedUserIds(request.query);
|
|
4395
|
+
return getQuestionnaireRunDetail(id, { userIds });
|
|
4396
|
+
});
|
|
4397
|
+
app.patch("/api/v1/psyche/questionnaire-runs/:id", async (request) => {
|
|
4398
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write"], { route: "/api/v1/psyche/questionnaire-runs/:id" });
|
|
4399
|
+
const { id } = request.params;
|
|
4400
|
+
return updateQuestionnaireRun(id, updateQuestionnaireRunSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4401
|
+
});
|
|
4402
|
+
app.post("/api/v1/psyche/questionnaire-runs/:id/complete", async (request) => {
|
|
4403
|
+
const auth = requirePsycheScopedAccess(request.headers, ["psyche.write"], { route: "/api/v1/psyche/questionnaire-runs/:id/complete" });
|
|
4404
|
+
const { id } = request.params;
|
|
4405
|
+
return completeQuestionnaireRun(id, toActivityContext(auth));
|
|
4406
|
+
});
|
|
4407
|
+
app.get("/api/v1/psyche/self-observation/calendar", async (request) => {
|
|
4408
|
+
requirePsycheScopedAccess(request.headers, ["psyche.read"], { route: "/api/v1/psyche/self-observation/calendar" });
|
|
4409
|
+
const query = calendarOverviewQuerySchema.parse(request.query ?? {});
|
|
4410
|
+
const now = new Date();
|
|
4411
|
+
const from = query.from ??
|
|
4412
|
+
new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
4413
|
+
const to = query.to ??
|
|
4414
|
+
new Date(now.getTime() + 21 * 24 * 60 * 60 * 1000).toISOString();
|
|
4415
|
+
return {
|
|
4416
|
+
calendar: getPsycheObservationCalendar({
|
|
4417
|
+
from,
|
|
4418
|
+
to,
|
|
4419
|
+
userIds: query.userIds
|
|
4420
|
+
})
|
|
4421
|
+
};
|
|
4422
|
+
});
|
|
3951
4423
|
app.get("/api/v1/psyche/values", async (request) => {
|
|
3952
4424
|
requirePsycheScopedAccess(request.headers, ["psyche.read"], { route: "/api/v1/psyche/values" });
|
|
3953
4425
|
const userIds = resolveScopedUserIds(request.query);
|
|
@@ -5239,6 +5711,37 @@ export async function buildServer(options = {}) {
|
|
|
5239
5711
|
providers: listCalendarProviderMetadata(),
|
|
5240
5712
|
connections: listConnectedCalendarConnections()
|
|
5241
5713
|
}));
|
|
5714
|
+
app.post("/api/v1/calendar/oauth/google/start", async (request) => {
|
|
5715
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
5716
|
+
route: "/api/v1/calendar/oauth/google/start"
|
|
5717
|
+
});
|
|
5718
|
+
return await startGoogleCalendarOauth(startGoogleCalendarOauthSchema.parse(request.body ?? {}), {
|
|
5719
|
+
browserOrigin: typeof request.body?.browserOrigin ===
|
|
5720
|
+
"string"
|
|
5721
|
+
? request.body.browserOrigin
|
|
5722
|
+
: null,
|
|
5723
|
+
openerOrigin: typeof request.headers.origin === "string"
|
|
5724
|
+
? request.headers.origin
|
|
5725
|
+
: typeof request.headers.referer === "string"
|
|
5726
|
+
? request.headers.referer
|
|
5727
|
+
: null,
|
|
5728
|
+
requestBaseOrigin: getRequestOrigin(request)
|
|
5729
|
+
});
|
|
5730
|
+
});
|
|
5731
|
+
app.get("/api/v1/calendar/oauth/google/session/:id", async (request, reply) => {
|
|
5732
|
+
requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/oauth/google/session/:id" });
|
|
5733
|
+
try {
|
|
5734
|
+
return getGoogleCalendarOauthSession(request.params.id);
|
|
5735
|
+
}
|
|
5736
|
+
catch (error) {
|
|
5737
|
+
if (error instanceof Error &&
|
|
5738
|
+
error.message.startsWith("Unknown Google calendar auth session")) {
|
|
5739
|
+
reply.code(404);
|
|
5740
|
+
return { error: "Google calendar auth session not found" };
|
|
5741
|
+
}
|
|
5742
|
+
throw error;
|
|
5743
|
+
}
|
|
5744
|
+
});
|
|
5242
5745
|
app.post("/api/v1/calendar/oauth/microsoft/start", async (request) => {
|
|
5243
5746
|
requireScopedAccess(request.headers, ["write"], {
|
|
5244
5747
|
route: "/api/v1/calendar/oauth/microsoft/start"
|
|
@@ -5311,6 +5814,54 @@ export async function buildServer(options = {}) {
|
|
|
5311
5814
|
setTimeout(() => window.close(), 180);
|
|
5312
5815
|
</script>
|
|
5313
5816
|
</body>
|
|
5817
|
+
</html>`;
|
|
5818
|
+
reply.type("text/html; charset=utf-8");
|
|
5819
|
+
return body;
|
|
5820
|
+
});
|
|
5821
|
+
app.get("/api/v1/calendar/oauth/google/callback", async (request, reply) => {
|
|
5822
|
+
const query = request.query;
|
|
5823
|
+
const result = await completeGoogleCalendarOauth({
|
|
5824
|
+
state: query.state ?? null,
|
|
5825
|
+
code: query.code ?? null,
|
|
5826
|
+
error: query.error ?? null,
|
|
5827
|
+
errorDescription: query.error_description ?? null
|
|
5828
|
+
});
|
|
5829
|
+
const session = result.session;
|
|
5830
|
+
const escapedOrigin = JSON.stringify(result.openerOrigin || "*");
|
|
5831
|
+
const escapedMessage = JSON.stringify({
|
|
5832
|
+
type: "forge:google-calendar-auth",
|
|
5833
|
+
sessionId: session.sessionId,
|
|
5834
|
+
status: session.status
|
|
5835
|
+
});
|
|
5836
|
+
const body = `<!doctype html>
|
|
5837
|
+
<html lang="en">
|
|
5838
|
+
<head>
|
|
5839
|
+
<meta charset="utf-8" />
|
|
5840
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5841
|
+
<title>Forge Google sign-in</title>
|
|
5842
|
+
<style>
|
|
5843
|
+
body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;background:#0b1320;color:#f8fafc;display:grid;place-items:center;min-height:100vh}
|
|
5844
|
+
main{max-width:30rem;padding:2rem;border:1px solid rgba(255,255,255,.08);border-radius:24px;background:linear-gradient(180deg,rgba(18,28,38,.98),rgba(11,17,28,.98))}
|
|
5845
|
+
h1{margin:0 0 .75rem;font-size:1.15rem}
|
|
5846
|
+
p{margin:0;color:rgba(248,250,252,.72);line-height:1.6}
|
|
5847
|
+
</style>
|
|
5848
|
+
</head>
|
|
5849
|
+
<body>
|
|
5850
|
+
<main>
|
|
5851
|
+
<h1>${session.status === "authorized" ? "Google account connected" : "Google sign-in needs attention"}</h1>
|
|
5852
|
+
<p>${session.status === "authorized" ? "Forge received your Google account and sent the result back to the calendar setup flow. You can close this window." : (session.error ?? "Forge could not complete Google sign-in. You can close this window and try again from Settings.")}</p>
|
|
5853
|
+
</main>
|
|
5854
|
+
<script>
|
|
5855
|
+
const message = ${escapedMessage};
|
|
5856
|
+
const targetOrigin = ${escapedOrigin};
|
|
5857
|
+
try {
|
|
5858
|
+
if (window.opener && !window.opener.closed) {
|
|
5859
|
+
window.opener.postMessage(message, targetOrigin);
|
|
5860
|
+
}
|
|
5861
|
+
} catch {}
|
|
5862
|
+
setTimeout(() => window.close(), 180);
|
|
5863
|
+
</script>
|
|
5864
|
+
</body>
|
|
5314
5865
|
</html>`;
|
|
5315
5866
|
reply.type("text/html; charset=utf-8");
|
|
5316
5867
|
return body;
|
|
@@ -6247,9 +6798,425 @@ export async function buildServer(options = {}) {
|
|
|
6247
6798
|
app.patch("/api/v1/settings", async (request) => {
|
|
6248
6799
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/settings" });
|
|
6249
6800
|
return {
|
|
6250
|
-
settings: updateSettings(updateSettingsSchema.parse(request.body ?? {}),
|
|
6801
|
+
settings: updateSettings(updateSettingsSchema.parse(request.body ?? {}), {
|
|
6802
|
+
activity: toActivityContext(auth),
|
|
6803
|
+
secrets: managers.secrets
|
|
6804
|
+
})
|
|
6805
|
+
};
|
|
6806
|
+
});
|
|
6807
|
+
app.post("/api/v1/settings/models/connections", async (request, reply) => {
|
|
6808
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6809
|
+
route: "/api/v1/settings/models/connections"
|
|
6810
|
+
});
|
|
6811
|
+
const parsed = upsertAiModelConnectionSchema.parse(request.body ?? {});
|
|
6812
|
+
const oauthCredential = parsed.oauthSessionId?.trim()
|
|
6813
|
+
? consumeOpenAiCodexOauthCredentials(parsed.oauthSessionId.trim())
|
|
6814
|
+
: null;
|
|
6815
|
+
const connection = upsertAiModelConnection(parsed, managers.secrets, {
|
|
6816
|
+
oauthCredential
|
|
6817
|
+
});
|
|
6818
|
+
const currentSettings = getSettings();
|
|
6819
|
+
const selectedWikiConnectionId = currentSettings.modelSettings.forgeAgent.wiki.connectionId;
|
|
6820
|
+
if (selectedWikiConnectionId === connection.id) {
|
|
6821
|
+
updateSettings({
|
|
6822
|
+
modelSettings: {
|
|
6823
|
+
forgeAgent: {
|
|
6824
|
+
wiki: {
|
|
6825
|
+
connectionId: connection.id,
|
|
6826
|
+
model: parsed.model
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
}, { secrets: managers.secrets });
|
|
6831
|
+
}
|
|
6832
|
+
reply.code(201);
|
|
6833
|
+
return { connection };
|
|
6834
|
+
});
|
|
6835
|
+
app.delete("/api/v1/settings/models/connections/:id", async (request, reply) => {
|
|
6836
|
+
requireScopedAccess(request.headers, ["write"], { route: "/api/v1/settings/models/connections/:id" });
|
|
6837
|
+
const deletedId = deleteAiModelConnection(request.params.id, managers.secrets);
|
|
6838
|
+
if (!deletedId) {
|
|
6839
|
+
reply.code(404);
|
|
6840
|
+
return { error: "AI model connection not found" };
|
|
6841
|
+
}
|
|
6842
|
+
return { deletedId };
|
|
6843
|
+
});
|
|
6844
|
+
app.post("/api/v1/settings/models/connections/test", async (request, reply) => {
|
|
6845
|
+
requireScopedAccess(request.headers, ["write"], { route: "/api/v1/settings/models/connections/test" });
|
|
6846
|
+
const parsed = testAiModelConnectionSchema.parse(request.body ?? {});
|
|
6847
|
+
const existing = parsed.connectionId
|
|
6848
|
+
? getAiModelConnectionById(parsed.connectionId)
|
|
6849
|
+
: null;
|
|
6850
|
+
const credential = parsed.connectionId
|
|
6851
|
+
? readModelConnectionCredential(parsed.connectionId, managers.secrets)
|
|
6852
|
+
: null;
|
|
6853
|
+
const explicitApiKey = parsed.apiKey?.trim() ||
|
|
6854
|
+
(credential?.kind === "api_key"
|
|
6855
|
+
? credential.apiKey
|
|
6856
|
+
: credential?.kind === "oauth"
|
|
6857
|
+
? credential.access
|
|
6858
|
+
: null);
|
|
6859
|
+
const result = await managers.llm.testWikiConnection({
|
|
6860
|
+
provider: parsed.provider ?? existing?.provider ?? "openai-api",
|
|
6861
|
+
baseUrl: parsed.baseUrl?.trim() ||
|
|
6862
|
+
existing?.baseUrl ||
|
|
6863
|
+
"https://api.openai.com/v1",
|
|
6864
|
+
model: parsed.model,
|
|
6865
|
+
systemPrompt: "",
|
|
6866
|
+
secretId: null,
|
|
6867
|
+
metadata: {}
|
|
6868
|
+
}, explicitApiKey, ({ level, message, details = {} }) => {
|
|
6869
|
+
recordDiagnosticLog({
|
|
6870
|
+
level,
|
|
6871
|
+
source: normalizeDiagnosticSource(request.headers["x-forge-source"]),
|
|
6872
|
+
scope: typeof details.scope === "string" ? details.scope : "model_settings",
|
|
6873
|
+
eventKey: typeof details.eventKey === "string"
|
|
6874
|
+
? details.eventKey
|
|
6875
|
+
: "model_connection_test",
|
|
6876
|
+
message,
|
|
6877
|
+
route: "/api/v1/settings/models/connections/test",
|
|
6878
|
+
functionName: "testModelConnection",
|
|
6879
|
+
details
|
|
6880
|
+
});
|
|
6881
|
+
});
|
|
6882
|
+
reply.code(200);
|
|
6883
|
+
return { result };
|
|
6884
|
+
});
|
|
6885
|
+
app.post("/api/v1/settings/models/oauth/openai-codex/start", async (request) => {
|
|
6886
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6887
|
+
route: "/api/v1/settings/models/oauth/openai-codex/start"
|
|
6888
|
+
});
|
|
6889
|
+
return { session: await startOpenAiCodexOauthSession() };
|
|
6890
|
+
});
|
|
6891
|
+
app.get("/api/v1/settings/models/oauth/openai-codex/session/:id", async (request, reply) => {
|
|
6892
|
+
requireScopedAccess(request.headers, ["write"], { route: "/api/v1/settings/models/oauth/openai-codex/session/:id" });
|
|
6893
|
+
try {
|
|
6894
|
+
return {
|
|
6895
|
+
session: getOpenAiCodexOauthSession(request.params.id)
|
|
6896
|
+
};
|
|
6897
|
+
}
|
|
6898
|
+
catch (error) {
|
|
6899
|
+
if (error instanceof Error &&
|
|
6900
|
+
error.message.startsWith("Unknown OpenAI Codex OAuth session")) {
|
|
6901
|
+
reply.code(404);
|
|
6902
|
+
return { error: "OpenAI Codex OAuth session not found" };
|
|
6903
|
+
}
|
|
6904
|
+
throw error;
|
|
6905
|
+
}
|
|
6906
|
+
});
|
|
6907
|
+
app.post("/api/v1/settings/models/oauth/openai-codex/session/:id/manual", async (request) => {
|
|
6908
|
+
requireScopedAccess(request.headers, ["write"], { route: "/api/v1/settings/models/oauth/openai-codex/session/:id/manual" });
|
|
6909
|
+
return {
|
|
6910
|
+
session: submitOpenAiCodexOauthManualInput(request.params.id, submitOpenAiCodexOauthManualCodeSchema.parse(request.body ?? {})
|
|
6911
|
+
.codeOrUrl)
|
|
6912
|
+
};
|
|
6913
|
+
});
|
|
6914
|
+
app.get("/api/v1/surfaces/:surfaceId/ai-processors", async (request) => {
|
|
6915
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
6916
|
+
route: "/api/v1/surfaces/:surfaceId/ai-processors"
|
|
6917
|
+
});
|
|
6918
|
+
return {
|
|
6919
|
+
graph: getSurfaceProcessorGraph(request.params.surfaceId)
|
|
6920
|
+
};
|
|
6921
|
+
});
|
|
6922
|
+
app.get("/api/v1/surfaces/:surfaceId/layout", async (request) => {
|
|
6923
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
6924
|
+
route: "/api/v1/surfaces/:surfaceId/layout"
|
|
6925
|
+
});
|
|
6926
|
+
return {
|
|
6927
|
+
layout: getSurfaceLayout(request.params.surfaceId)
|
|
6928
|
+
};
|
|
6929
|
+
});
|
|
6930
|
+
app.put("/api/v1/surfaces/:surfaceId/layout", async (request) => {
|
|
6931
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6932
|
+
route: "/api/v1/surfaces/:surfaceId/layout"
|
|
6933
|
+
});
|
|
6934
|
+
const surfaceId = request.params.surfaceId;
|
|
6935
|
+
return {
|
|
6936
|
+
layout: saveSurfaceLayout(surfaceId, writeSurfaceLayoutSchema.parse(request.body ?? {}))
|
|
6937
|
+
};
|
|
6938
|
+
});
|
|
6939
|
+
app.post("/api/v1/surfaces/:surfaceId/layout/reset", async (request) => {
|
|
6940
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6941
|
+
route: "/api/v1/surfaces/:surfaceId/layout/reset"
|
|
6942
|
+
});
|
|
6943
|
+
return {
|
|
6944
|
+
layout: resetSurfaceLayout(request.params.surfaceId)
|
|
6945
|
+
};
|
|
6946
|
+
});
|
|
6947
|
+
app.post("/api/v1/surfaces/:surfaceId/ai-processors", async (request, reply) => {
|
|
6948
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6949
|
+
route: "/api/v1/surfaces/:surfaceId/ai-processors"
|
|
6950
|
+
});
|
|
6951
|
+
const body = createAiProcessorSchema.parse({
|
|
6952
|
+
...(request.body ?? {}),
|
|
6953
|
+
surfaceId: request.params.surfaceId
|
|
6954
|
+
});
|
|
6955
|
+
const processor = createAiProcessor({
|
|
6956
|
+
...body
|
|
6957
|
+
});
|
|
6958
|
+
reply.code(201);
|
|
6959
|
+
return { processor };
|
|
6960
|
+
});
|
|
6961
|
+
app.patch("/api/v1/ai-processors/:id", async (request, reply) => {
|
|
6962
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6963
|
+
route: "/api/v1/ai-processors/:id"
|
|
6964
|
+
});
|
|
6965
|
+
const processor = updateAiProcessor(request.params.id, updateAiProcessorSchema.parse(request.body ?? {}));
|
|
6966
|
+
if (!processor) {
|
|
6967
|
+
reply.code(404);
|
|
6968
|
+
return { error: "AI processor not found" };
|
|
6969
|
+
}
|
|
6970
|
+
return { processor };
|
|
6971
|
+
});
|
|
6972
|
+
app.delete("/api/v1/ai-processors/:id", async (request, reply) => {
|
|
6973
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6974
|
+
route: "/api/v1/ai-processors/:id"
|
|
6975
|
+
});
|
|
6976
|
+
const processor = deleteAiProcessor(request.params.id);
|
|
6977
|
+
if (!processor) {
|
|
6978
|
+
reply.code(404);
|
|
6979
|
+
return { error: "AI processor not found" };
|
|
6980
|
+
}
|
|
6981
|
+
return { processor };
|
|
6982
|
+
});
|
|
6983
|
+
app.post("/api/v1/ai-processor-links", async (request, reply) => {
|
|
6984
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6985
|
+
route: "/api/v1/ai-processor-links"
|
|
6986
|
+
});
|
|
6987
|
+
const link = createAiProcessorLink(createAiProcessorLinkSchema.parse(request.body ?? {}));
|
|
6988
|
+
reply.code(201);
|
|
6989
|
+
return { link };
|
|
6990
|
+
});
|
|
6991
|
+
app.delete("/api/v1/ai-processor-links/:id", async (request, reply) => {
|
|
6992
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
6993
|
+
route: "/api/v1/ai-processor-links/:id"
|
|
6994
|
+
});
|
|
6995
|
+
const link = deleteAiProcessorLink(request.params.id);
|
|
6996
|
+
if (!link) {
|
|
6997
|
+
reply.code(404);
|
|
6998
|
+
return { error: "AI processor link not found" };
|
|
6999
|
+
}
|
|
7000
|
+
return { link };
|
|
7001
|
+
});
|
|
7002
|
+
app.post("/api/v1/ai-processors/:id/run", async (request, reply) => {
|
|
7003
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7004
|
+
route: "/api/v1/ai-processors/:id/run"
|
|
7005
|
+
});
|
|
7006
|
+
const processor = getAiProcessorById(request.params.id);
|
|
7007
|
+
if (!processor) {
|
|
7008
|
+
reply.code(404);
|
|
7009
|
+
return { error: "AI processor not found" };
|
|
7010
|
+
}
|
|
7011
|
+
return await runAiProcessor(processor.id, runAiProcessorSchema.parse(request.body ?? {}), {
|
|
7012
|
+
llm: managers.llm,
|
|
7013
|
+
secrets: managers.secrets
|
|
7014
|
+
}, { trigger: "manual" });
|
|
7015
|
+
});
|
|
7016
|
+
app.get("/api/v1/aiproc/:slug", async (request, reply) => {
|
|
7017
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
7018
|
+
route: "/api/v1/aiproc/:slug"
|
|
7019
|
+
});
|
|
7020
|
+
const processor = getAiProcessorBySlug(request.params.slug);
|
|
7021
|
+
if (!processor) {
|
|
7022
|
+
reply.code(404);
|
|
7023
|
+
return { error: "AI processor not found" };
|
|
7024
|
+
}
|
|
7025
|
+
return { processor };
|
|
7026
|
+
});
|
|
7027
|
+
app.post("/api/v1/aiproc/:slug/run", async (request, reply) => {
|
|
7028
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7029
|
+
route: "/api/v1/aiproc/:slug/run"
|
|
7030
|
+
});
|
|
7031
|
+
const processor = getAiProcessorBySlug(request.params.slug);
|
|
7032
|
+
if (!processor) {
|
|
7033
|
+
reply.code(404);
|
|
7034
|
+
return { error: "AI processor not found" };
|
|
7035
|
+
}
|
|
7036
|
+
return await runAiProcessor(processor.id, runAiProcessorSchema.parse(request.body ?? {}), {
|
|
7037
|
+
llm: managers.llm,
|
|
7038
|
+
secrets: managers.secrets
|
|
7039
|
+
}, { trigger: "route" });
|
|
7040
|
+
});
|
|
7041
|
+
const registerFlowApiRoutes = (basePath, noun, options) => {
|
|
7042
|
+
const collectionKey = options?.collectionKey ?? "connectors";
|
|
7043
|
+
const singularKey = options?.singularKey ?? "connector";
|
|
7044
|
+
const catalogPath = options?.catalogPath ?? `${basePath}/catalog/boxes`;
|
|
7045
|
+
app.get(catalogPath, async (request) => {
|
|
7046
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
7047
|
+
route: catalogPath
|
|
7048
|
+
});
|
|
7049
|
+
return {
|
|
7050
|
+
boxes: [
|
|
7051
|
+
...listForgeBoxCatalog(),
|
|
7052
|
+
...listAiConnectors().flatMap((connector) => connector.publishedOutputs.map((output) => buildConnectorOutputCatalogEntry({
|
|
7053
|
+
connectorId: connector.id,
|
|
7054
|
+
title: connector.title,
|
|
7055
|
+
outputId: output.id
|
|
7056
|
+
})))
|
|
7057
|
+
]
|
|
7058
|
+
};
|
|
7059
|
+
});
|
|
7060
|
+
app.get(basePath, async (request) => {
|
|
7061
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
7062
|
+
route: basePath
|
|
7063
|
+
});
|
|
7064
|
+
return {
|
|
7065
|
+
[collectionKey]: listAiConnectors()
|
|
7066
|
+
};
|
|
7067
|
+
});
|
|
7068
|
+
app.post(basePath, async (request, reply) => {
|
|
7069
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7070
|
+
route: basePath
|
|
7071
|
+
});
|
|
7072
|
+
const connector = createAiConnector(createAiConnectorSchema.parse(request.body ?? {}));
|
|
7073
|
+
reply.code(201);
|
|
7074
|
+
return { [singularKey]: connector };
|
|
7075
|
+
});
|
|
7076
|
+
app.get(`${basePath}/:id`, async (request, reply) => {
|
|
7077
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
7078
|
+
route: `${basePath}/:id`
|
|
7079
|
+
});
|
|
7080
|
+
const connector = getAiConnectorById(request.params.id);
|
|
7081
|
+
if (!connector) {
|
|
7082
|
+
reply.code(404);
|
|
7083
|
+
return { error: `${noun} not found` };
|
|
7084
|
+
}
|
|
7085
|
+
return {
|
|
7086
|
+
[singularKey]: connector,
|
|
7087
|
+
runs: listAiConnectorRuns(connector.id),
|
|
7088
|
+
conversation: getAiConnectorConversationForConnector(connector.id)
|
|
7089
|
+
};
|
|
7090
|
+
});
|
|
7091
|
+
app.patch(`${basePath}/:id`, async (request, reply) => {
|
|
7092
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7093
|
+
route: `${basePath}/:id`
|
|
7094
|
+
});
|
|
7095
|
+
const connector = updateAiConnector(request.params.id, updateAiConnectorSchema.parse(request.body ?? {}));
|
|
7096
|
+
if (!connector) {
|
|
7097
|
+
reply.code(404);
|
|
7098
|
+
return { error: `${noun} not found` };
|
|
7099
|
+
}
|
|
7100
|
+
return { [singularKey]: connector };
|
|
7101
|
+
});
|
|
7102
|
+
app.delete(`${basePath}/:id`, async (request, reply) => {
|
|
7103
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7104
|
+
route: `${basePath}/:id`
|
|
7105
|
+
});
|
|
7106
|
+
const connector = deleteAiConnector(request.params.id);
|
|
7107
|
+
if (!connector) {
|
|
7108
|
+
reply.code(404);
|
|
7109
|
+
return { error: `${noun} not found` };
|
|
7110
|
+
}
|
|
7111
|
+
return { [singularKey]: connector };
|
|
7112
|
+
});
|
|
7113
|
+
app.post(`${basePath}/:id/run`, async (request, reply) => {
|
|
7114
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7115
|
+
route: `${basePath}/:id/run`
|
|
7116
|
+
});
|
|
7117
|
+
const connector = getAiConnectorById(request.params.id);
|
|
7118
|
+
if (!connector) {
|
|
7119
|
+
reply.code(404);
|
|
7120
|
+
return { error: `${noun} not found` };
|
|
7121
|
+
}
|
|
7122
|
+
const execution = await runAiConnector(connector.id, runAiConnectorSchema.parse(request.body ?? {}), {
|
|
7123
|
+
llm: managers.llm,
|
|
7124
|
+
secrets: managers.secrets
|
|
7125
|
+
}, "run");
|
|
7126
|
+
return {
|
|
7127
|
+
[singularKey]: execution.connector,
|
|
7128
|
+
run: execution.run,
|
|
7129
|
+
conversation: execution.conversation
|
|
7130
|
+
};
|
|
7131
|
+
});
|
|
7132
|
+
app.post(`${basePath}/:id/chat`, async (request, reply) => {
|
|
7133
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7134
|
+
route: `${basePath}/:id/chat`
|
|
7135
|
+
});
|
|
7136
|
+
const connector = getAiConnectorById(request.params.id);
|
|
7137
|
+
if (!connector) {
|
|
7138
|
+
reply.code(404);
|
|
7139
|
+
return { error: `${noun} not found` };
|
|
7140
|
+
}
|
|
7141
|
+
const execution = await runAiConnector(connector.id, runAiConnectorSchema.parse(request.body ?? {}), {
|
|
7142
|
+
llm: managers.llm,
|
|
7143
|
+
secrets: managers.secrets
|
|
7144
|
+
}, "chat");
|
|
7145
|
+
return {
|
|
7146
|
+
[singularKey]: execution.connector,
|
|
7147
|
+
run: execution.run,
|
|
7148
|
+
conversation: execution.conversation
|
|
7149
|
+
};
|
|
7150
|
+
});
|
|
7151
|
+
app.get(`${basePath}/:id/output`, async (request, reply) => {
|
|
7152
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
7153
|
+
route: `${basePath}/:id/output`
|
|
7154
|
+
});
|
|
7155
|
+
const connector = getAiConnectorById(request.params.id);
|
|
7156
|
+
if (!connector) {
|
|
7157
|
+
reply.code(404);
|
|
7158
|
+
return { error: `${noun} not found` };
|
|
7159
|
+
}
|
|
7160
|
+
return {
|
|
7161
|
+
[singularKey]: connector,
|
|
7162
|
+
output: connector.lastRun?.result ?? null
|
|
7163
|
+
};
|
|
7164
|
+
});
|
|
7165
|
+
app.get(`${basePath}/:id/runs`, async (request, reply) => {
|
|
7166
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
7167
|
+
route: `${basePath}/:id/runs`
|
|
7168
|
+
});
|
|
7169
|
+
const connector = getAiConnectorById(request.params.id);
|
|
7170
|
+
if (!connector) {
|
|
7171
|
+
reply.code(404);
|
|
7172
|
+
return { error: `${noun} not found` };
|
|
7173
|
+
}
|
|
7174
|
+
return {
|
|
7175
|
+
runs: listAiConnectorRuns(connector.id)
|
|
7176
|
+
};
|
|
7177
|
+
});
|
|
7178
|
+
};
|
|
7179
|
+
registerFlowApiRoutes("/api/v1/workbench/flows", "Workbench flow", {
|
|
7180
|
+
collectionKey: "flows",
|
|
7181
|
+
singularKey: "flow",
|
|
7182
|
+
catalogPath: "/api/v1/workbench/catalog/boxes"
|
|
7183
|
+
});
|
|
7184
|
+
app.post("/api/v1/workbench/run", async (request, reply) => {
|
|
7185
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
7186
|
+
route: "/api/v1/workbench/run"
|
|
7187
|
+
});
|
|
7188
|
+
const payload = runAiConnectorSchema
|
|
7189
|
+
.extend({
|
|
7190
|
+
flowId: z.string().trim().min(1)
|
|
7191
|
+
})
|
|
7192
|
+
.parse(request.body ?? {});
|
|
7193
|
+
const flow = getAiConnectorById(payload.flowId);
|
|
7194
|
+
if (!flow) {
|
|
7195
|
+
reply.code(404);
|
|
7196
|
+
return { error: "Workbench flow not found" };
|
|
7197
|
+
}
|
|
7198
|
+
const { flowId, ...runInput } = payload;
|
|
7199
|
+
const execution = await runAiConnector(flow.id, runInput, {
|
|
7200
|
+
llm: managers.llm,
|
|
7201
|
+
secrets: managers.secrets
|
|
7202
|
+
}, "run");
|
|
7203
|
+
return {
|
|
7204
|
+
flow: execution.connector,
|
|
7205
|
+
run: execution.run,
|
|
7206
|
+
conversation: execution.conversation
|
|
6251
7207
|
};
|
|
6252
7208
|
});
|
|
7209
|
+
app.get("/api/v1/workbench/flows/by-slug/:slug", async (request, reply) => {
|
|
7210
|
+
requireScopedAccess(request.headers, ["read"], {
|
|
7211
|
+
route: "/api/v1/workbench/flows/by-slug/:slug"
|
|
7212
|
+
});
|
|
7213
|
+
const connector = getAiConnectorBySlug(request.params.slug);
|
|
7214
|
+
if (!connector) {
|
|
7215
|
+
reply.code(404);
|
|
7216
|
+
return { error: "Workbench flow not found" };
|
|
7217
|
+
}
|
|
7218
|
+
return { flow: connector };
|
|
7219
|
+
});
|
|
6253
7220
|
app.post("/api/v1/settings/tokens", async (request, reply) => {
|
|
6254
7221
|
const auth = requireOperatorSession(request.headers, { route: "/api/v1/settings/tokens" });
|
|
6255
7222
|
const token = managers.token.issueLocalAgentToken(createAgentTokenSchema.parse(request.body ?? {}), auth);
|