forge-openclaw-plugin 0.2.18 → 0.2.19
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 +36 -4
- package/dist/assets/{board-2KevHCI0.js → board-8L3uX7_O.js} +2 -2
- package/dist/assets/{board-2KevHCI0.js.map → board-8L3uX7_O.js.map} +1 -1
- package/dist/assets/index-Cj1IBH_w.js +36 -0
- package/dist/assets/index-Cj1IBH_w.js.map +1 -0
- package/dist/assets/index-DQT6EbuS.css +1 -0
- package/dist/assets/{motion-q19HPmWs.js → motion-1GAqqi8M.js} +2 -2
- package/dist/assets/{motion-q19HPmWs.js.map → motion-1GAqqi8M.js.map} +1 -1
- package/dist/assets/{table-BDMHBY4a.js → table-DBGlgRjk.js} +2 -2
- package/dist/assets/{table-BDMHBY4a.js.map → table-DBGlgRjk.js.map} +1 -1
- package/dist/assets/{ui-CQ_AsFs8.js → ui-iTluWjC4.js} +2 -2
- package/dist/assets/{ui-CQ_AsFs8.js.map → ui-iTluWjC4.js.map} +1 -1
- package/dist/assets/{vendor-5HifrnRK.js → vendor-BvM2F9Dp.js} +139 -84
- package/dist/assets/vendor-BvM2F9Dp.js.map +1 -0
- package/dist/assets/{viz-CQzkRnTu.js → viz-CNeunkfu.js} +2 -2
- package/dist/assets/{viz-CQzkRnTu.js.map → viz-CNeunkfu.js.map} +1 -1
- package/dist/index.html +8 -8
- package/dist/openclaw/parity.js +1 -0
- package/dist/openclaw/routes.js +7 -0
- package/dist/openclaw/tools.js +183 -16
- package/dist/server/app.js +2509 -263
- package/dist/server/managers/platform/secrets-manager.js +44 -1
- package/dist/server/managers/runtime.js +3 -1
- package/dist/server/openapi.js +2037 -172
- package/dist/server/repositories/calendar.js +1101 -0
- package/dist/server/repositories/deleted-entities.js +10 -2
- package/dist/server/repositories/notes.js +161 -28
- package/dist/server/repositories/projects.js +45 -13
- package/dist/server/repositories/rewards.js +114 -6
- package/dist/server/repositories/settings.js +47 -5
- package/dist/server/repositories/task-runs.js +46 -10
- package/dist/server/repositories/tasks.js +25 -9
- package/dist/server/repositories/weekly-reviews.js +109 -0
- package/dist/server/repositories/work-adjustments.js +105 -0
- package/dist/server/services/calendar-runtime.js +1301 -0
- package/dist/server/services/entity-crud.js +94 -3
- package/dist/server/services/projects.js +32 -8
- package/dist/server/services/reviews.js +15 -1
- package/dist/server/services/work-time.js +27 -0
- package/dist/server/types.js +934 -49
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/006_work_adjustments.sql +14 -0
- package/server/migrations/007_weekly_review_closures.sql +17 -0
- package/server/migrations/008_calendar_execution.sql +147 -0
- package/server/migrations/009_true_calendar_events.sql +195 -0
- package/server/migrations/010_calendar_selection_state.sql +6 -0
- package/server/migrations/011_calendar_timezone_backfill.sql +11 -0
- package/server/migrations/012_work_block_ranges.sql +7 -0
- package/server/migrations/013_microsoft_local_auth_settings.sql +8 -0
- package/server/migrations/014_note_tags_and_ephemeral.sql +8 -0
- package/skills/forge-openclaw/SKILL.md +117 -11
- package/dist/assets/index-CDYW4WDH.js +0 -36
- package/dist/assets/index-CDYW4WDH.js.map +0 -1
- package/dist/assets/index-yroQr6YZ.css +0 -1
- package/dist/assets/vendor-5HifrnRK.js.map +0 -1
package/dist/server/app.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import Fastify from "fastify";
|
|
2
2
|
import cors from "@fastify/cors";
|
|
3
3
|
import { ZodError } from "zod";
|
|
4
|
-
import { configureDatabase, configureDatabaseSeeding } from "./db.js";
|
|
5
|
-
import { isHttpError } from "./errors.js";
|
|
6
|
-
import { listActivityEvents, listActivityEventsForTask, removeActivityEvent } from "./repositories/activity-events.js";
|
|
4
|
+
import { configureDatabase, configureDatabaseSeeding, runInTransaction } from "./db.js";
|
|
5
|
+
import { HttpError, isHttpError } from "./errors.js";
|
|
6
|
+
import { listActivityEvents, listActivityEventsForTask, recordActivityEvent, removeActivityEvent } from "./repositories/activity-events.js";
|
|
7
7
|
import { approveApprovalRequest, createAgentAction, createInsight, createInsightFeedback, deleteInsight, getInsightById, listAgentActions, listApprovalRequests, listInsights, rejectApprovalRequest, updateInsight } from "./repositories/collaboration.js";
|
|
8
8
|
import { listEventLog } from "./repositories/event-log.js";
|
|
9
9
|
import { createGoal, getGoalById, listGoals, updateGoal } from "./repositories/goals.js";
|
|
@@ -12,23 +12,27 @@ import { listDomains } from "./repositories/domains.js";
|
|
|
12
12
|
import { buildNotesSummaryByEntity, createNote, getNoteById, listNotes, updateNote } from "./repositories/notes.js";
|
|
13
13
|
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";
|
|
14
14
|
import { createProject, updateProject } from "./repositories/projects.js";
|
|
15
|
-
import { createManualRewardGrant, getDailyAmbientXp, getRewardRuleById, listRewardLedger, listRewardRules, recordSessionEvent, updateRewardRule } from "./repositories/rewards.js";
|
|
15
|
+
import { createManualRewardGrant, getDailyAmbientXp, getRewardRuleById, listRewardLedger, listRewardRules, recordWorkAdjustmentReward, recordSessionEvent, updateRewardRule } from "./repositories/rewards.js";
|
|
16
16
|
import { listAgentIdentities, getSettings, isPsycheAuthRequired, updateSettings, verifyAgentToken } from "./repositories/settings.js";
|
|
17
17
|
import { createTag, getTagById, listTags, updateTag } from "./repositories/tags.js";
|
|
18
18
|
import { claimTaskRun, completeTaskRun, focusTaskRun, heartbeatTaskRun, listTaskRuns, recoverTimedOutTaskRuns, releaseTaskRun } from "./repositories/task-runs.js";
|
|
19
19
|
import { createTask, createTaskWithIdempotency, getTaskById, listTasks, uncompleteTask, updateTask } from "./repositories/tasks.js";
|
|
20
|
+
import { createWorkAdjustment } from "./repositories/work-adjustments.js";
|
|
21
|
+
import { createCalendarEvent, createTaskTimebox, createWorkBlockTemplate, deleteCalendarEvent, deleteTaskTimebox, deleteWorkBlockTemplate, getCalendarConnectionById, getCalendarEventById, listCalendars, listCalendarEvents, listTaskTimeboxes, suggestTaskTimeboxes, listWorkBlockInstances, listWorkBlockTemplates, updateCalendarEvent, updateTaskTimebox, updateWorkBlockTemplate } from "./repositories/calendar.js";
|
|
20
22
|
import { getDashboard } from "./services/dashboard.js";
|
|
21
23
|
import { getOverviewContext, getRiskContext, getTodayContext } from "./services/context.js";
|
|
22
24
|
import { buildGamificationOverview, buildGamificationProfile, buildXpMomentumPulse } from "./services/gamification.js";
|
|
23
25
|
import { getInsightsPayload } from "./services/insights.js";
|
|
24
26
|
import { createEntities, deleteEntities, deleteEntity, getSettingsBinPayload, restoreEntities, searchEntities, updateEntities } from "./services/entity-crud.js";
|
|
25
27
|
import { getPsycheOverview } from "./services/psyche.js";
|
|
26
|
-
import { getProjectBoard, listProjectSummaries } from "./services/projects.js";
|
|
28
|
+
import { getProjectBoard, getProjectSummary, listProjectSummaries } from "./services/projects.js";
|
|
27
29
|
import { getWeeklyReviewPayload } from "./services/reviews.js";
|
|
30
|
+
import { finalizeWeeklyReviewClosure } from "./repositories/weekly-reviews.js";
|
|
28
31
|
import { createTaskRunWatchdog } from "./services/task-run-watchdog.js";
|
|
29
32
|
import { suggestTags } from "./services/tagging.js";
|
|
33
|
+
import { CalendarConnectionConflictError, completeMicrosoftCalendarOauth, createCalendarConnection, deleteCalendarEventProjection, discoverCalendarConnection, discoverExistingCalendarConnection, getMicrosoftCalendarOauthSession, listConnectedCalendarConnections, removeCalendarConnection, pushCalendarEventUpdate, readCalendarOverview, syncCalendarConnection, startMicrosoftCalendarOauth, testMicrosoftCalendarOauthConfiguration, listCalendarProviderMetadata, updateCalendarConnectionSelection } from "./services/calendar-runtime.js";
|
|
30
34
|
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";
|
|
31
|
-
import { activityListQuerySchema, activitySourceSchema, createAgentActionSchema, createAgentTokenSchema, batchCreateEntitiesSchema, batchDeleteEntitiesSchema, batchRestoreEntitiesSchema, batchSearchEntitiesSchema, batchUpdateEntitiesSchema, createGoalSchema, createInsightFeedbackSchema, createInsightSchema, createNoteSchema, createProjectSchema, createManualRewardGrantSchema, createHabitCheckInSchema, createHabitSchema, createSessionEventSchema, createTagSchema, notesListQuerySchema, updateTagSchema, createTaskSchema, eventsListQuerySchema, operatorLogWorkSchema, projectBoardPayloadSchema, projectListQuerySchema, entityDeleteQuerySchema, removeActivityEventSchema, resolveApprovalRequestSchema, rewardsLedgerQuerySchema, habitListQuerySchema, taskContextPayloadSchema, taskRunClaimSchema, taskRunFocusSchema, taskRunFinishSchema, taskRunHeartbeatSchema, taskRunListQuerySchema, taskListQuerySchema, tagSuggestionRequestSchema, uncompleteTaskSchema, updateSettingsSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateNoteSchema, updateProjectSchema, updateRewardRuleSchema, updateTaskSchema } from "./types.js";
|
|
35
|
+
import { activityListQuerySchema, activitySourceSchema, createAgentActionSchema, createAgentTokenSchema, batchCreateEntitiesSchema, batchDeleteEntitiesSchema, batchRestoreEntitiesSchema, batchSearchEntitiesSchema, batchUpdateEntitiesSchema, createGoalSchema, createInsightFeedbackSchema, createInsightSchema, createNoteSchema, createProjectSchema, createManualRewardGrantSchema, createCalendarEventSchema, createHabitCheckInSchema, createCalendarConnectionSchema, discoverCalendarConnectionSchema, startMicrosoftCalendarOauthSchema, testMicrosoftCalendarOauthConfigurationSchema, createHabitSchema, createTaskTimeboxSchema, createWorkBlockTemplateSchema, createSessionEventSchema, createWorkAdjustmentSchema, createTagSchema, calendarOverviewQuerySchema, notesListQuerySchema, updateTagSchema, createTaskSchema, eventsListQuerySchema, operatorLogWorkSchema, projectBoardPayloadSchema, projectListQuerySchema, entityDeleteQuerySchema, removeActivityEventSchema, resolveApprovalRequestSchema, rewardsLedgerQuerySchema, habitListQuerySchema, taskContextPayloadSchema, taskRunClaimSchema, taskRunFocusSchema, taskRunFinishSchema, taskRunHeartbeatSchema, taskRunListQuerySchema, taskListQuerySchema, tagSuggestionRequestSchema, uncompleteTaskSchema, updateSettingsSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateCalendarConnectionSchema, updateCalendarEventSchema, updateNoteSchema, updateProjectSchema, updateRewardRuleSchema, updateTaskTimeboxSchema, updateTaskSchema, updateWorkBlockTemplateSchema, workAdjustmentResultSchema, finalizeWeeklyReviewResultSchema, recommendTaskTimeboxesSchema } from "./types.js";
|
|
32
36
|
import { buildOpenApiDocument } from "./openapi.js";
|
|
33
37
|
import { registerWebRoutes } from "./web.js";
|
|
34
38
|
import { createManagerRuntime } from "./managers/runtime.js";
|
|
@@ -106,7 +110,9 @@ function readSingleForwardedHeader(value) {
|
|
|
106
110
|
return null;
|
|
107
111
|
}
|
|
108
112
|
function getRequestOrigin(request) {
|
|
109
|
-
const protocol = readSingleForwardedHeader(request.headers["x-forwarded-proto"]) ??
|
|
113
|
+
const protocol = readSingleForwardedHeader(request.headers["x-forwarded-proto"]) ??
|
|
114
|
+
request.protocol ??
|
|
115
|
+
"http";
|
|
110
116
|
const host = readSingleForwardedHeader(request.headers["x-forwarded-host"]) ??
|
|
111
117
|
readSingleForwardedHeader(request.headers.host) ??
|
|
112
118
|
request.hostname;
|
|
@@ -122,20 +128,72 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
122
128
|
"Projects should usually link to one goal through goalId.",
|
|
123
129
|
"Tasks can link directly to a goal when no project exists yet."
|
|
124
130
|
],
|
|
125
|
-
searchHints: [
|
|
131
|
+
searchHints: [
|
|
132
|
+
"Search by title before creating a new goal.",
|
|
133
|
+
"Use status filters when looking for paused or completed goals."
|
|
134
|
+
],
|
|
126
135
|
examples: [
|
|
127
136
|
'{"title":"Create meaningfully","horizon":"lifetime","description":"Make work that is honest, beautiful, and published."}',
|
|
128
137
|
'{"title":"Build a beautiful family","horizon":"lifetime","description":"Invest in love, stability, and shared rituals."}'
|
|
129
138
|
],
|
|
130
139
|
fieldGuide: [
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
140
|
+
{
|
|
141
|
+
name: "title",
|
|
142
|
+
type: "string",
|
|
143
|
+
required: true,
|
|
144
|
+
description: "Human-readable goal name."
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "description",
|
|
148
|
+
type: "string",
|
|
149
|
+
required: false,
|
|
150
|
+
description: "Markdown description for why the goal matters or what success looks like.",
|
|
151
|
+
defaultValue: ""
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "horizon",
|
|
155
|
+
type: "quarter|year|lifetime",
|
|
156
|
+
required: false,
|
|
157
|
+
description: "How far out the goal is meant to live.",
|
|
158
|
+
enumValues: ["quarter", "year", "lifetime"],
|
|
159
|
+
defaultValue: "year"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "status",
|
|
163
|
+
type: "active|paused|completed",
|
|
164
|
+
required: false,
|
|
165
|
+
description: "Current lifecycle state for the goal.",
|
|
166
|
+
enumValues: ["active", "paused", "completed"],
|
|
167
|
+
defaultValue: "active"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "targetPoints",
|
|
171
|
+
type: "integer",
|
|
172
|
+
required: false,
|
|
173
|
+
description: "Approximate XP/point target for the goal.",
|
|
174
|
+
defaultValue: 400
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "themeColor",
|
|
178
|
+
type: "hex-color",
|
|
179
|
+
required: false,
|
|
180
|
+
description: "Visual color used in the UI.",
|
|
181
|
+
defaultValue: "#c8a46b"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "tagIds",
|
|
185
|
+
type: "string[]",
|
|
186
|
+
required: false,
|
|
187
|
+
description: "Existing tag ids linked to the goal.",
|
|
188
|
+
defaultValue: []
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "notes",
|
|
192
|
+
type: "Array<{ contentMarkdown, author?, tags?, destroyAt?, links? }>",
|
|
193
|
+
required: false,
|
|
194
|
+
description: "Optional nested notes that will auto-link to the new goal.",
|
|
195
|
+
defaultValue: []
|
|
196
|
+
}
|
|
139
197
|
]
|
|
140
198
|
},
|
|
141
199
|
{
|
|
@@ -147,16 +205,61 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
147
205
|
"Tasks can link to a project through projectId.",
|
|
148
206
|
"Projects inherit strategic meaning from their parent goal."
|
|
149
207
|
],
|
|
150
|
-
searchHints: [
|
|
151
|
-
|
|
208
|
+
searchHints: [
|
|
209
|
+
"Search by title inside the target goal before creating a new project."
|
|
210
|
+
],
|
|
211
|
+
examples: [
|
|
212
|
+
'{"goalId":"goal_create_meaningfully","title":"Launch the public Forge plugin","description":"Ship a real public release that people can install."}'
|
|
213
|
+
],
|
|
152
214
|
fieldGuide: [
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
{
|
|
215
|
+
{
|
|
216
|
+
name: "goalId",
|
|
217
|
+
type: "string",
|
|
218
|
+
required: true,
|
|
219
|
+
description: "Existing parent goal id."
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "title",
|
|
223
|
+
type: "string",
|
|
224
|
+
required: true,
|
|
225
|
+
description: "Project name."
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: "description",
|
|
229
|
+
type: "string",
|
|
230
|
+
required: false,
|
|
231
|
+
description: "Markdown description for the desired outcome or scope.",
|
|
232
|
+
defaultValue: ""
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "status",
|
|
236
|
+
type: "active|paused|completed",
|
|
237
|
+
required: false,
|
|
238
|
+
description: "Lifecycle state.",
|
|
239
|
+
enumValues: ["active", "paused", "completed"],
|
|
240
|
+
defaultValue: "active"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "targetPoints",
|
|
244
|
+
type: "integer",
|
|
245
|
+
required: false,
|
|
246
|
+
description: "Approximate XP/point target for the project.",
|
|
247
|
+
defaultValue: 240
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "themeColor",
|
|
251
|
+
type: "hex-color",
|
|
252
|
+
required: false,
|
|
253
|
+
description: "Visual color used in the UI.",
|
|
254
|
+
defaultValue: "#c0c1ff"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "notes",
|
|
258
|
+
type: "Array<{ contentMarkdown, author?, tags?, destroyAt?, links? }>",
|
|
259
|
+
required: false,
|
|
260
|
+
description: "Optional nested notes that will auto-link to the new project.",
|
|
261
|
+
defaultValue: []
|
|
262
|
+
}
|
|
160
263
|
]
|
|
161
264
|
},
|
|
162
265
|
{
|
|
@@ -168,23 +271,394 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
168
271
|
"Live work is tracked by task runs, not by task status alone.",
|
|
169
272
|
"A task status of in_progress does not guarantee a live active run."
|
|
170
273
|
],
|
|
171
|
-
searchHints: [
|
|
172
|
-
|
|
274
|
+
searchHints: [
|
|
275
|
+
"Search by title before creating a duplicate task.",
|
|
276
|
+
"Use linkedTo filters when you know the parent goal or project."
|
|
277
|
+
],
|
|
278
|
+
examples: [
|
|
279
|
+
'{"title":"Write the plugin release notes","projectId":"project_forge_plugin_launch","status":"focus","priority":"high"}'
|
|
280
|
+
],
|
|
173
281
|
fieldGuide: [
|
|
174
|
-
{
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
{
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
{
|
|
282
|
+
{
|
|
283
|
+
name: "title",
|
|
284
|
+
type: "string",
|
|
285
|
+
required: true,
|
|
286
|
+
description: "Concrete action label."
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "description",
|
|
290
|
+
type: "string",
|
|
291
|
+
required: false,
|
|
292
|
+
description: "Markdown context, constraints, or acceptance notes.",
|
|
293
|
+
defaultValue: ""
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "status",
|
|
297
|
+
type: "backlog|focus|in_progress|blocked|done",
|
|
298
|
+
required: false,
|
|
299
|
+
description: "Board lane or completion state.",
|
|
300
|
+
enumValues: ["backlog", "focus", "in_progress", "blocked", "done"],
|
|
301
|
+
defaultValue: "backlog"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "priority",
|
|
305
|
+
type: "low|medium|high|critical",
|
|
306
|
+
required: false,
|
|
307
|
+
description: "Relative urgency.",
|
|
308
|
+
enumValues: ["low", "medium", "high", "critical"],
|
|
309
|
+
defaultValue: "medium"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "owner",
|
|
313
|
+
type: "string",
|
|
314
|
+
required: false,
|
|
315
|
+
description: "Human-facing owner label.",
|
|
316
|
+
defaultValue: "Albert"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "goalId",
|
|
320
|
+
type: "string|null",
|
|
321
|
+
required: false,
|
|
322
|
+
description: "Linked goal id.",
|
|
323
|
+
defaultValue: null,
|
|
324
|
+
nullable: true
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "projectId",
|
|
328
|
+
type: "string|null",
|
|
329
|
+
required: false,
|
|
330
|
+
description: "Linked project id.",
|
|
331
|
+
defaultValue: null,
|
|
332
|
+
nullable: true
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "dueDate",
|
|
336
|
+
type: "YYYY-MM-DD|null",
|
|
337
|
+
required: false,
|
|
338
|
+
description: "Optional due date.",
|
|
339
|
+
defaultValue: null,
|
|
340
|
+
nullable: true
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "effort",
|
|
344
|
+
type: "light|deep|marathon",
|
|
345
|
+
required: false,
|
|
346
|
+
description: "How heavy the task feels.",
|
|
347
|
+
enumValues: ["light", "deep", "marathon"],
|
|
348
|
+
defaultValue: "deep"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "energy",
|
|
352
|
+
type: "low|steady|high",
|
|
353
|
+
required: false,
|
|
354
|
+
description: "Energy demand.",
|
|
355
|
+
enumValues: ["low", "steady", "high"],
|
|
356
|
+
defaultValue: "steady"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: "points",
|
|
360
|
+
type: "integer",
|
|
361
|
+
required: false,
|
|
362
|
+
description: "Reward value for the task.",
|
|
363
|
+
defaultValue: 40
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "sortOrder",
|
|
367
|
+
type: "integer",
|
|
368
|
+
required: false,
|
|
369
|
+
description: "Lane ordering hint when set explicitly."
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "tagIds",
|
|
373
|
+
type: "string[]",
|
|
374
|
+
required: false,
|
|
375
|
+
description: "Existing tag ids linked to the task.",
|
|
376
|
+
defaultValue: []
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "notes",
|
|
380
|
+
type: "Array<{ contentMarkdown, author?, tags?, destroyAt?, links? }>",
|
|
381
|
+
required: false,
|
|
382
|
+
description: "Optional nested notes that will auto-link to the new task.",
|
|
383
|
+
defaultValue: []
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
entityType: "calendar_event",
|
|
389
|
+
purpose: "A canonical Forge calendar event that can live locally first and then project to connected provider calendars.",
|
|
390
|
+
minimumCreateFields: ["title", "startAt", "endAt"],
|
|
391
|
+
relationshipRules: [
|
|
392
|
+
"Forge stores the canonical event first; provider copies are downstream projections.",
|
|
393
|
+
"Use links to connect the event to goals, projects, tasks, habits, notes, or Psyche entities.",
|
|
394
|
+
"If preferredCalendarId is omitted, Forge uses the default writable connected calendar when one exists.",
|
|
395
|
+
"Set preferredCalendarId to null only when the user explicitly wants Forge-only storage."
|
|
396
|
+
],
|
|
397
|
+
searchHints: [
|
|
398
|
+
"Search by title or linked entity before creating a duplicate event.",
|
|
399
|
+
"Use linkedTo when you know the goal, project, task, or habit the event should already reference."
|
|
400
|
+
],
|
|
401
|
+
examples: [
|
|
402
|
+
'{"title":"Weekly research supervision","startAt":"2026-04-06T06:00:00.000Z","endAt":"2026-04-06T07:00:00.000Z","timezone":"Europe/Zurich","links":[{"entityType":"project","entityId":"project_123","relationshipType":"meeting_for"}]}'
|
|
403
|
+
],
|
|
404
|
+
fieldGuide: [
|
|
405
|
+
{
|
|
406
|
+
name: "title",
|
|
407
|
+
type: "string",
|
|
408
|
+
required: true,
|
|
409
|
+
description: "Human-readable event title."
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "description",
|
|
413
|
+
type: "string",
|
|
414
|
+
required: false,
|
|
415
|
+
description: "Longer event description.",
|
|
416
|
+
defaultValue: ""
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: "location",
|
|
420
|
+
type: "string",
|
|
421
|
+
required: false,
|
|
422
|
+
description: "Location or meeting place.",
|
|
423
|
+
defaultValue: ""
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "startAt",
|
|
427
|
+
type: "ISO datetime",
|
|
428
|
+
required: true,
|
|
429
|
+
description: "Start instant in ISO-8601 form."
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "endAt",
|
|
433
|
+
type: "ISO datetime",
|
|
434
|
+
required: true,
|
|
435
|
+
description: "End instant in ISO-8601 form."
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: "timezone",
|
|
439
|
+
type: "string",
|
|
440
|
+
required: false,
|
|
441
|
+
description: "IANA timezone label.",
|
|
442
|
+
defaultValue: "UTC"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: "isAllDay",
|
|
446
|
+
type: "boolean",
|
|
447
|
+
required: false,
|
|
448
|
+
description: "Whether this is an all-day event.",
|
|
449
|
+
defaultValue: false
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "availability",
|
|
453
|
+
type: "busy|free",
|
|
454
|
+
required: false,
|
|
455
|
+
description: "Availability state exposed to scheduling rules.",
|
|
456
|
+
enumValues: ["busy", "free"],
|
|
457
|
+
defaultValue: "busy"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "eventType",
|
|
461
|
+
type: "string",
|
|
462
|
+
required: false,
|
|
463
|
+
description: "Optional event category label used by scheduling rules.",
|
|
464
|
+
defaultValue: ""
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: "categories",
|
|
468
|
+
type: "string[]",
|
|
469
|
+
required: false,
|
|
470
|
+
description: "Optional provider-style categories.",
|
|
471
|
+
defaultValue: []
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "preferredCalendarId",
|
|
475
|
+
type: "string|null",
|
|
476
|
+
required: false,
|
|
477
|
+
description: "Writable connected calendar to project into. Omit it to use the default writable connected calendar. Set null only to force Forge-only storage.",
|
|
478
|
+
defaultValue: "default writable connected calendar when available",
|
|
479
|
+
nullable: true
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "links",
|
|
483
|
+
type: "Array<{ entityType, entityId, relationshipType? }>",
|
|
484
|
+
required: false,
|
|
485
|
+
description: "Forge entities linked to this event.",
|
|
486
|
+
defaultValue: []
|
|
487
|
+
}
|
|
488
|
+
]
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
entityType: "work_block_template",
|
|
492
|
+
purpose: "A recurring work-availability template such as Main Activity, Secondary Activity, Third Activity, Rest, Holiday, or Custom.",
|
|
493
|
+
minimumCreateFields: [
|
|
494
|
+
"title",
|
|
495
|
+
"kind",
|
|
496
|
+
"timezone",
|
|
497
|
+
"weekDays",
|
|
498
|
+
"startMinute",
|
|
499
|
+
"endMinute",
|
|
500
|
+
"blockingState"
|
|
501
|
+
],
|
|
502
|
+
relationshipRules: [
|
|
503
|
+
"Work block templates derive visible calendar instances for the requested range instead of storing one repeated event per day.",
|
|
504
|
+
"startsOn and endsOn are optional active-date bounds. Leaving endsOn null makes the block repeat indefinitely.",
|
|
505
|
+
"They are Forge-owned scheduling structures, not mirrored provider events."
|
|
506
|
+
],
|
|
507
|
+
searchHints: [
|
|
508
|
+
"Search by title or kind before creating a duplicate recurring block."
|
|
509
|
+
],
|
|
510
|
+
examples: [
|
|
511
|
+
'{"title":"Main Activity","kind":"main_activity","color":"#f97316","timezone":"Europe/Zurich","weekDays":[1,2,3,4,5],"startMinute":480,"endMinute":720,"startsOn":"2026-04-06","endsOn":null,"blockingState":"blocked"}',
|
|
512
|
+
'{"title":"Summer holiday","kind":"holiday","color":"#14b8a6","timezone":"Europe/Zurich","weekDays":[0,1,2,3,4,5,6],"startMinute":0,"endMinute":1440,"startsOn":"2026-08-01","endsOn":"2026-08-16","blockingState":"blocked"}'
|
|
513
|
+
],
|
|
514
|
+
fieldGuide: [
|
|
515
|
+
{
|
|
516
|
+
name: "title",
|
|
517
|
+
type: "string",
|
|
518
|
+
required: true,
|
|
519
|
+
description: "Display name for the recurring block."
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: "kind",
|
|
523
|
+
type: "main_activity|secondary_activity|third_activity|rest|holiday|custom",
|
|
524
|
+
required: true,
|
|
525
|
+
description: "Preset or custom block type.",
|
|
526
|
+
enumValues: [
|
|
527
|
+
"main_activity",
|
|
528
|
+
"secondary_activity",
|
|
529
|
+
"third_activity",
|
|
530
|
+
"rest",
|
|
531
|
+
"holiday",
|
|
532
|
+
"custom"
|
|
533
|
+
]
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "color",
|
|
537
|
+
type: "hex-color",
|
|
538
|
+
required: false,
|
|
539
|
+
description: "UI color for generated instances.",
|
|
540
|
+
defaultValue: "#60a5fa"
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: "timezone",
|
|
544
|
+
type: "string",
|
|
545
|
+
required: true,
|
|
546
|
+
description: "IANA timezone that defines the recurring window."
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: "weekDays",
|
|
550
|
+
type: "integer[]",
|
|
551
|
+
required: true,
|
|
552
|
+
description: "Weekday numbers where Sunday is 0 and Saturday is 6."
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: "startMinute",
|
|
556
|
+
type: "integer",
|
|
557
|
+
required: true,
|
|
558
|
+
description: "Minute from midnight where the block starts."
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: "endMinute",
|
|
562
|
+
type: "integer",
|
|
563
|
+
required: true,
|
|
564
|
+
description: "Minute from midnight where the block ends."
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: "startsOn",
|
|
568
|
+
type: "YYYY-MM-DD|null",
|
|
569
|
+
required: false,
|
|
570
|
+
description: "Optional first active date for the recurring block.",
|
|
571
|
+
defaultValue: null,
|
|
572
|
+
nullable: true
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: "endsOn",
|
|
576
|
+
type: "YYYY-MM-DD|null",
|
|
577
|
+
required: false,
|
|
578
|
+
description: "Optional last active date. Null means repeat indefinitely.",
|
|
579
|
+
defaultValue: null,
|
|
580
|
+
nullable: true
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "blockingState",
|
|
584
|
+
type: "allowed|blocked",
|
|
585
|
+
required: true,
|
|
586
|
+
description: "Whether this block generally allows or blocks work.",
|
|
587
|
+
enumValues: ["allowed", "blocked"]
|
|
588
|
+
}
|
|
589
|
+
]
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
entityType: "task_timebox",
|
|
593
|
+
purpose: "A planned or live calendar slot attached to a task.",
|
|
594
|
+
minimumCreateFields: ["taskId", "title", "startsAt", "endsAt"],
|
|
595
|
+
relationshipRules: [
|
|
596
|
+
"Task timeboxes belong to a task and can optionally carry the parent project id.",
|
|
597
|
+
"Live task runs can attach to matching timeboxes later; creating a timebox does not start work by itself."
|
|
598
|
+
],
|
|
599
|
+
searchHints: [
|
|
600
|
+
"Search by task linkage or title before creating another slot for the same work block."
|
|
601
|
+
],
|
|
602
|
+
examples: [
|
|
603
|
+
'{"taskId":"task_123","projectId":"project_456","title":"Draft the methods section","startsAt":"2026-04-03T08:00:00.000Z","endsAt":"2026-04-03T09:30:00.000Z","source":"suggested"}'
|
|
604
|
+
],
|
|
605
|
+
fieldGuide: [
|
|
606
|
+
{
|
|
607
|
+
name: "taskId",
|
|
608
|
+
type: "string",
|
|
609
|
+
required: true,
|
|
610
|
+
description: "Linked task id."
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: "projectId",
|
|
614
|
+
type: "string|null",
|
|
615
|
+
required: false,
|
|
616
|
+
description: "Optional parent project id.",
|
|
617
|
+
defaultValue: null,
|
|
618
|
+
nullable: true
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: "title",
|
|
622
|
+
type: "string",
|
|
623
|
+
required: true,
|
|
624
|
+
description: "Timebox title shown on the calendar."
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: "startsAt",
|
|
628
|
+
type: "ISO datetime",
|
|
629
|
+
required: true,
|
|
630
|
+
description: "Start instant in ISO-8601 form."
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
name: "endsAt",
|
|
634
|
+
type: "ISO datetime",
|
|
635
|
+
required: true,
|
|
636
|
+
description: "End instant in ISO-8601 form."
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "source",
|
|
640
|
+
type: "manual|suggested|live_run",
|
|
641
|
+
required: false,
|
|
642
|
+
description: "How the timebox was created.",
|
|
643
|
+
enumValues: ["manual", "suggested", "live_run"],
|
|
644
|
+
defaultValue: "manual"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
name: "status",
|
|
648
|
+
type: "planned|active|completed|cancelled",
|
|
649
|
+
required: false,
|
|
650
|
+
description: "Current timebox state.",
|
|
651
|
+
enumValues: ["planned", "active", "completed", "cancelled"],
|
|
652
|
+
defaultValue: "planned"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
name: "overrideReason",
|
|
656
|
+
type: "string|null",
|
|
657
|
+
required: false,
|
|
658
|
+
description: "Audited reason when the slot overrides a blocked context.",
|
|
659
|
+
defaultValue: null,
|
|
660
|
+
nullable: true
|
|
661
|
+
}
|
|
188
662
|
]
|
|
189
663
|
},
|
|
190
664
|
{
|
|
@@ -196,47 +670,205 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
196
670
|
"Habits are recurring records, not task variants, and they participate in search, notes, delete/restore, and XP.",
|
|
197
671
|
"linkedBehaviorId remains a compatibility alias; linkedBehaviorIds is the canonical array form."
|
|
198
672
|
],
|
|
199
|
-
searchHints: [
|
|
200
|
-
|
|
673
|
+
searchHints: [
|
|
674
|
+
"Search by title before creating a duplicate habit.",
|
|
675
|
+
"Use linkedTo when the habit should already be attached to a goal, project, task, or Psyche entity."
|
|
676
|
+
],
|
|
677
|
+
examples: [
|
|
678
|
+
'{"title":"Morning training","frequency":"daily","polarity":"positive","linkedGoalIds":["goal_train_body"],"linkedValueIds":["value_steadiness"],"linkedBehaviorIds":["behavior_regulating_walk"]}'
|
|
679
|
+
],
|
|
201
680
|
fieldGuide: [
|
|
202
|
-
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
681
|
+
{
|
|
682
|
+
name: "title",
|
|
683
|
+
type: "string",
|
|
684
|
+
required: true,
|
|
685
|
+
description: "Concrete recurring behavior label."
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: "description",
|
|
689
|
+
type: "string",
|
|
690
|
+
required: false,
|
|
691
|
+
description: "Markdown definition of what counts as success or failure for this habit.",
|
|
692
|
+
defaultValue: ""
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: "status",
|
|
696
|
+
type: "active|paused|archived",
|
|
697
|
+
required: false,
|
|
698
|
+
description: "Lifecycle state.",
|
|
699
|
+
enumValues: ["active", "paused", "archived"],
|
|
700
|
+
defaultValue: "active"
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: "polarity",
|
|
704
|
+
type: "positive|negative",
|
|
705
|
+
required: false,
|
|
706
|
+
description: "Whether doing the behavior is aligned or misaligned.",
|
|
707
|
+
enumValues: ["positive", "negative"],
|
|
708
|
+
defaultValue: "positive"
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
name: "frequency",
|
|
712
|
+
type: "daily|weekly",
|
|
713
|
+
required: false,
|
|
714
|
+
description: "Recurrence cadence.",
|
|
715
|
+
enumValues: ["daily", "weekly"],
|
|
716
|
+
defaultValue: "daily"
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
name: "targetCount",
|
|
720
|
+
type: "integer",
|
|
721
|
+
required: false,
|
|
722
|
+
description: "How many repetitions define the cadence window.",
|
|
723
|
+
defaultValue: 1
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
name: "weekDays",
|
|
727
|
+
type: "integer[]",
|
|
728
|
+
required: false,
|
|
729
|
+
description: "Weekday numbers for weekly habits where Monday is 1 and Sunday is 0.",
|
|
730
|
+
defaultValue: []
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
name: "linkedGoalIds",
|
|
734
|
+
type: "string[]",
|
|
735
|
+
required: false,
|
|
736
|
+
description: "Linked goal ids.",
|
|
737
|
+
defaultValue: []
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: "linkedProjectIds",
|
|
741
|
+
type: "string[]",
|
|
742
|
+
required: false,
|
|
743
|
+
description: "Linked project ids.",
|
|
744
|
+
defaultValue: []
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
name: "linkedTaskIds",
|
|
748
|
+
type: "string[]",
|
|
749
|
+
required: false,
|
|
750
|
+
description: "Linked task ids.",
|
|
751
|
+
defaultValue: []
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
name: "linkedValueIds",
|
|
755
|
+
type: "string[]",
|
|
756
|
+
required: false,
|
|
757
|
+
description: "Linked value ids.",
|
|
758
|
+
defaultValue: []
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
name: "linkedPatternIds",
|
|
762
|
+
type: "string[]",
|
|
763
|
+
required: false,
|
|
764
|
+
description: "Linked pattern ids.",
|
|
765
|
+
defaultValue: []
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
name: "linkedBehaviorIds",
|
|
769
|
+
type: "string[]",
|
|
770
|
+
required: false,
|
|
771
|
+
description: "Canonical linked behavior ids.",
|
|
772
|
+
defaultValue: []
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
name: "linkedBehaviorId",
|
|
776
|
+
type: "string|null",
|
|
777
|
+
required: false,
|
|
778
|
+
description: "Compatibility alias for the first linked behavior id.",
|
|
779
|
+
defaultValue: null,
|
|
780
|
+
nullable: true
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
name: "linkedBeliefIds",
|
|
784
|
+
type: "string[]",
|
|
785
|
+
required: false,
|
|
786
|
+
description: "Linked belief ids.",
|
|
787
|
+
defaultValue: []
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
name: "linkedModeIds",
|
|
791
|
+
type: "string[]",
|
|
792
|
+
required: false,
|
|
793
|
+
description: "Linked mode ids.",
|
|
794
|
+
defaultValue: []
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "linkedReportIds",
|
|
798
|
+
type: "string[]",
|
|
799
|
+
required: false,
|
|
800
|
+
description: "Linked trigger report ids.",
|
|
801
|
+
defaultValue: []
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
name: "rewardXp",
|
|
805
|
+
type: "integer",
|
|
806
|
+
required: false,
|
|
807
|
+
description: "XP granted on aligned check-ins.",
|
|
808
|
+
defaultValue: 12
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
name: "penaltyXp",
|
|
812
|
+
type: "integer",
|
|
813
|
+
required: false,
|
|
814
|
+
description: "XP removed on misaligned check-ins.",
|
|
815
|
+
defaultValue: 8
|
|
816
|
+
}
|
|
221
817
|
]
|
|
222
818
|
},
|
|
223
819
|
{
|
|
224
820
|
entityType: "note",
|
|
225
|
-
purpose: "A Markdown note that can link to one or many Forge entities.",
|
|
821
|
+
purpose: "A first-class Markdown note entity that can link to one or many Forge entities.",
|
|
226
822
|
minimumCreateFields: ["contentMarkdown", "links"],
|
|
227
823
|
relationshipRules: [
|
|
228
824
|
"Notes can link to goals, projects, tasks, Psyche records, and other supported Forge entities.",
|
|
229
|
-
"When nested under another create flow, notes auto-link to that new entity and can optionally include extra links."
|
|
825
|
+
"When nested under another create flow, notes auto-link to that new entity and can optionally include extra links.",
|
|
826
|
+
"Agents can also create standalone notes directly through forge_create_entities with entityType note."
|
|
827
|
+
],
|
|
828
|
+
searchHints: [
|
|
829
|
+
"Search by Markdown content, author, or linked entity before creating a duplicate note."
|
|
230
830
|
],
|
|
231
|
-
searchHints: ["Search by Markdown content, author, or linked entity before creating a duplicate note."],
|
|
232
831
|
examples: [
|
|
233
832
|
'{"contentMarkdown":"Finished the review pass and captured the remaining edge cases.","links":[{"entityType":"task","entityId":"task_123"}]}',
|
|
234
|
-
'{"contentMarkdown":"Observed a stronger protector response after the meeting.","author":"forge-agent","links":[{"entityType":"trigger_report","entityId":"report_123"},{"entityType":"behavior_pattern","entityId":"pattern_123"}]}'
|
|
833
|
+
'{"contentMarkdown":"Observed a stronger protector response after the meeting.","author":"forge-agent","tags":["Short-term memory","therapy"],"links":[{"entityType":"trigger_report","entityId":"report_123"},{"entityType":"behavior_pattern","entityId":"pattern_123"}]}',
|
|
834
|
+
'{"contentMarkdown":"Scratch capture for what I am actively holding in mind.","tags":["Working memory","handoff"],"destroyAt":"2026-04-04T12:00:00.000Z","links":[{"entityType":"task","entityId":"task_123"}]}'
|
|
235
835
|
],
|
|
236
836
|
fieldGuide: [
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
|
|
837
|
+
{
|
|
838
|
+
name: "contentMarkdown",
|
|
839
|
+
type: "string",
|
|
840
|
+
required: true,
|
|
841
|
+
description: "Markdown body of the note."
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
name: "author",
|
|
845
|
+
type: "string|null",
|
|
846
|
+
required: false,
|
|
847
|
+
description: "Optional display author for the note.",
|
|
848
|
+
defaultValue: null,
|
|
849
|
+
nullable: true
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
name: "tags",
|
|
853
|
+
type: "string[]",
|
|
854
|
+
required: false,
|
|
855
|
+
description: "Optional note-owned tags such as Working memory, Short-term memory, Episodic memory, Semantic memory, Procedural memory, or custom labels.",
|
|
856
|
+
defaultValue: []
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
name: "destroyAt",
|
|
860
|
+
type: "ISO datetime|null",
|
|
861
|
+
required: false,
|
|
862
|
+
description: "Optional auto-destroy timestamp. If set, Forge deletes the note after that time as ephemeral memory.",
|
|
863
|
+
defaultValue: null,
|
|
864
|
+
nullable: true
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: "links",
|
|
868
|
+
type: "Array<{ entityType, entityId, anchorKey? }>",
|
|
869
|
+
required: true,
|
|
870
|
+
description: "Entities this note should link to."
|
|
871
|
+
}
|
|
240
872
|
]
|
|
241
873
|
},
|
|
242
874
|
{
|
|
@@ -247,19 +879,83 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
247
879
|
"Insights can optionally point at one entity through entityType and entityId.",
|
|
248
880
|
"Use insights for interpretation or advice, not as a replacement for goals, tasks, or trigger reports."
|
|
249
881
|
],
|
|
250
|
-
searchHints: [
|
|
251
|
-
|
|
882
|
+
searchHints: [
|
|
883
|
+
"Search recent insights before posting a new one if the same pattern may already be captured."
|
|
884
|
+
],
|
|
885
|
+
examples: [
|
|
886
|
+
'{"entityType":"goal","entityId":"goal_create_meaningfully","title":"Admin drag is masking momentum","summary":"Creative progress is happening, but admin cleanup keeps interrupting it.","recommendation":"Protect one clean creative block and isolate admin into a separate recurring task."}'
|
|
887
|
+
],
|
|
252
888
|
fieldGuide: [
|
|
253
|
-
{
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{
|
|
262
|
-
|
|
889
|
+
{
|
|
890
|
+
name: "entityType",
|
|
891
|
+
type: "string|null",
|
|
892
|
+
required: false,
|
|
893
|
+
description: "Optional linked entity type.",
|
|
894
|
+
defaultValue: null,
|
|
895
|
+
nullable: true
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
name: "entityId",
|
|
899
|
+
type: "string|null",
|
|
900
|
+
required: false,
|
|
901
|
+
description: "Optional linked entity id.",
|
|
902
|
+
defaultValue: null,
|
|
903
|
+
nullable: true
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
name: "timeframeLabel",
|
|
907
|
+
type: "string|null",
|
|
908
|
+
required: false,
|
|
909
|
+
description: "Optional time window label.",
|
|
910
|
+
defaultValue: null,
|
|
911
|
+
nullable: true
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
name: "title",
|
|
915
|
+
type: "string",
|
|
916
|
+
required: true,
|
|
917
|
+
description: "Insight title."
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "summary",
|
|
921
|
+
type: "string",
|
|
922
|
+
required: true,
|
|
923
|
+
description: "Short explanation of the pattern or tension."
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
name: "recommendation",
|
|
927
|
+
type: "string",
|
|
928
|
+
required: true,
|
|
929
|
+
description: "Actionable next move or reframing."
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
name: "rationale",
|
|
933
|
+
type: "string",
|
|
934
|
+
required: false,
|
|
935
|
+
description: "Why this insight is grounded in the data.",
|
|
936
|
+
defaultValue: ""
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
name: "confidence",
|
|
940
|
+
type: "number",
|
|
941
|
+
required: false,
|
|
942
|
+
description: "Confidence from 0 to 1.",
|
|
943
|
+
defaultValue: 0.7
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
name: "visibility",
|
|
947
|
+
type: "string",
|
|
948
|
+
required: false,
|
|
949
|
+
description: "Visibility mode for the insight.",
|
|
950
|
+
defaultValue: "visible"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
name: "ctaLabel",
|
|
954
|
+
type: "string",
|
|
955
|
+
required: false,
|
|
956
|
+
description: "CTA shown in the UI.",
|
|
957
|
+
defaultValue: "Review insight"
|
|
958
|
+
}
|
|
263
959
|
]
|
|
264
960
|
},
|
|
265
961
|
{
|
|
@@ -270,10 +966,24 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
270
966
|
"Trigger reports can reference one event type through eventTypeId.",
|
|
271
967
|
"Use event types to normalize repeated report categories instead of inventing new wording every time."
|
|
272
968
|
],
|
|
273
|
-
searchHints: [
|
|
969
|
+
searchHints: [
|
|
970
|
+
"Search by label before creating a new event type.",
|
|
971
|
+
"Prefer existing event types when one clearly fits the situation."
|
|
972
|
+
],
|
|
274
973
|
fieldGuide: [
|
|
275
|
-
{
|
|
276
|
-
|
|
974
|
+
{
|
|
975
|
+
name: "label",
|
|
976
|
+
type: "string",
|
|
977
|
+
required: true,
|
|
978
|
+
description: "Human-readable event type label."
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
name: "description",
|
|
982
|
+
type: "string",
|
|
983
|
+
required: false,
|
|
984
|
+
description: "What kind of incident this event type represents.",
|
|
985
|
+
defaultValue: ""
|
|
986
|
+
}
|
|
277
987
|
]
|
|
278
988
|
},
|
|
279
989
|
{
|
|
@@ -284,11 +994,31 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
284
994
|
"Trigger report emotions can reference an emotion definition through emotionDefinitionId.",
|
|
285
995
|
"Use emotion definitions to normalize repeated emotional labels across reports."
|
|
286
996
|
],
|
|
287
|
-
searchHints: [
|
|
997
|
+
searchHints: [
|
|
998
|
+
"Search by label before creating a new emotion definition.",
|
|
999
|
+
"Prefer an existing emotion definition when the label already captures the feeling well."
|
|
1000
|
+
],
|
|
288
1001
|
fieldGuide: [
|
|
289
|
-
{
|
|
290
|
-
|
|
291
|
-
|
|
1002
|
+
{
|
|
1003
|
+
name: "label",
|
|
1004
|
+
type: "string",
|
|
1005
|
+
required: true,
|
|
1006
|
+
description: "Emotion label."
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: "description",
|
|
1010
|
+
type: "string",
|
|
1011
|
+
required: false,
|
|
1012
|
+
description: "What this emotion label is meant to capture.",
|
|
1013
|
+
defaultValue: ""
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
name: "category",
|
|
1017
|
+
type: "string",
|
|
1018
|
+
required: false,
|
|
1019
|
+
description: "Optional grouping such as threat, grief, anger, or connection.",
|
|
1020
|
+
defaultValue: ""
|
|
1021
|
+
}
|
|
292
1022
|
]
|
|
293
1023
|
},
|
|
294
1024
|
{
|
|
@@ -299,17 +1029,69 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
299
1029
|
"Values can link to goals, projects, and tasks.",
|
|
300
1030
|
"Patterns, behaviors, beliefs, and reports can all point back to values."
|
|
301
1031
|
],
|
|
302
|
-
searchHints: [
|
|
303
|
-
|
|
1032
|
+
searchHints: [
|
|
1033
|
+
"Search by title before creating a new value.",
|
|
1034
|
+
"Use linkedTo if the value should already be attached to a goal or task."
|
|
1035
|
+
],
|
|
1036
|
+
examples: [
|
|
1037
|
+
'{"title":"Steadiness","valuedDirection":"Respond calmly instead of collapsing or reacting fast.","whyItMatters":"I want to stay grounded in relationships and work."}'
|
|
1038
|
+
],
|
|
304
1039
|
fieldGuide: [
|
|
305
|
-
{
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
{
|
|
312
|
-
|
|
1040
|
+
{
|
|
1041
|
+
name: "title",
|
|
1042
|
+
type: "string",
|
|
1043
|
+
required: true,
|
|
1044
|
+
description: "Value name."
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
name: "description",
|
|
1048
|
+
type: "string",
|
|
1049
|
+
required: false,
|
|
1050
|
+
description: "What the value means in practice.",
|
|
1051
|
+
defaultValue: ""
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
name: "valuedDirection",
|
|
1055
|
+
type: "string",
|
|
1056
|
+
required: false,
|
|
1057
|
+
description: "How the user wants to live or act when guided by this value.",
|
|
1058
|
+
defaultValue: ""
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
name: "whyItMatters",
|
|
1062
|
+
type: "string",
|
|
1063
|
+
required: false,
|
|
1064
|
+
description: "Why the value matters to the user.",
|
|
1065
|
+
defaultValue: ""
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
name: "linkedGoalIds",
|
|
1069
|
+
type: "string[]",
|
|
1070
|
+
required: false,
|
|
1071
|
+
description: "Linked goal ids.",
|
|
1072
|
+
defaultValue: []
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: "linkedProjectIds",
|
|
1076
|
+
type: "string[]",
|
|
1077
|
+
required: false,
|
|
1078
|
+
description: "Linked project ids.",
|
|
1079
|
+
defaultValue: []
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
name: "linkedTaskIds",
|
|
1083
|
+
type: "string[]",
|
|
1084
|
+
required: false,
|
|
1085
|
+
description: "Linked task ids.",
|
|
1086
|
+
defaultValue: []
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
name: "committedActions",
|
|
1090
|
+
type: "string[]",
|
|
1091
|
+
required: false,
|
|
1092
|
+
description: "Small concrete actions that enact the value.",
|
|
1093
|
+
defaultValue: []
|
|
1094
|
+
}
|
|
313
1095
|
]
|
|
314
1096
|
},
|
|
315
1097
|
{
|
|
@@ -320,21 +1102,96 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
320
1102
|
"Patterns can link to values, beliefs, and modes.",
|
|
321
1103
|
"Trigger reports can link back to patterns they instantiate."
|
|
322
1104
|
],
|
|
323
|
-
searchHints: [
|
|
324
|
-
|
|
1105
|
+
searchHints: [
|
|
1106
|
+
"Search by title or by trigger language before creating a new pattern."
|
|
1107
|
+
],
|
|
1108
|
+
examples: [
|
|
1109
|
+
'{"title":"Late-night father text freeze","cueContexts":["Father texts late at night"],"targetBehavior":"Freeze, avoid replying, and doomscroll","shortTermPayoff":"Avoids immediate overwhelm","longTermCost":"Sleep loss, guilt, and dread","preferredResponse":"Pause, regulate, and reply on my own terms the next morning"}'
|
|
1110
|
+
],
|
|
325
1111
|
fieldGuide: [
|
|
326
|
-
{
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
{
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
1112
|
+
{
|
|
1113
|
+
name: "title",
|
|
1114
|
+
type: "string",
|
|
1115
|
+
required: true,
|
|
1116
|
+
description: "Short pattern name."
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
name: "description",
|
|
1120
|
+
type: "string",
|
|
1121
|
+
required: false,
|
|
1122
|
+
description: "What usually happens in this loop.",
|
|
1123
|
+
defaultValue: ""
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
name: "targetBehavior",
|
|
1127
|
+
type: "string",
|
|
1128
|
+
required: false,
|
|
1129
|
+
description: "The visible behavior this pattern tends to produce.",
|
|
1130
|
+
defaultValue: ""
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
name: "cueContexts",
|
|
1134
|
+
type: "string[]",
|
|
1135
|
+
required: false,
|
|
1136
|
+
description: "Typical cues, contexts, or triggers.",
|
|
1137
|
+
defaultValue: []
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
name: "shortTermPayoff",
|
|
1141
|
+
type: "string",
|
|
1142
|
+
required: false,
|
|
1143
|
+
description: "What the loop gives immediately.",
|
|
1144
|
+
defaultValue: ""
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
name: "longTermCost",
|
|
1148
|
+
type: "string",
|
|
1149
|
+
required: false,
|
|
1150
|
+
description: "What the loop costs later.",
|
|
1151
|
+
defaultValue: ""
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
name: "preferredResponse",
|
|
1155
|
+
type: "string",
|
|
1156
|
+
required: false,
|
|
1157
|
+
description: "Preferred alternative response.",
|
|
1158
|
+
defaultValue: ""
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
name: "linkedValueIds",
|
|
1162
|
+
type: "string[]",
|
|
1163
|
+
required: false,
|
|
1164
|
+
description: "Linked value ids.",
|
|
1165
|
+
defaultValue: []
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
name: "linkedSchemaLabels",
|
|
1169
|
+
type: "string[]",
|
|
1170
|
+
required: false,
|
|
1171
|
+
description: "Schema labels involved in the pattern.",
|
|
1172
|
+
defaultValue: []
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
name: "linkedModeLabels",
|
|
1176
|
+
type: "string[]",
|
|
1177
|
+
required: false,
|
|
1178
|
+
description: "Mode labels involved in the pattern.",
|
|
1179
|
+
defaultValue: []
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
name: "linkedModeIds",
|
|
1183
|
+
type: "string[]",
|
|
1184
|
+
required: false,
|
|
1185
|
+
description: "Linked mode ids.",
|
|
1186
|
+
defaultValue: []
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
name: "linkedBeliefIds",
|
|
1190
|
+
type: "string[]",
|
|
1191
|
+
required: false,
|
|
1192
|
+
description: "Linked belief ids.",
|
|
1193
|
+
defaultValue: []
|
|
1194
|
+
}
|
|
338
1195
|
]
|
|
339
1196
|
},
|
|
340
1197
|
{
|
|
@@ -346,21 +1203,100 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
346
1203
|
"Trigger reports can link to behaviors they contained."
|
|
347
1204
|
],
|
|
348
1205
|
searchHints: ["Search by title and kind before creating a new behavior."],
|
|
349
|
-
examples: [
|
|
1206
|
+
examples: [
|
|
1207
|
+
'{"kind":"away","title":"Doomscroll after conflict cue","commonCues":["Received a critical text"],"shortTermPayoff":"Numbs the anxiety","longTermCost":"Loses time and deepens shame","replacementMove":"Put phone down and take one slow lap outside"}'
|
|
1208
|
+
],
|
|
350
1209
|
fieldGuide: [
|
|
351
|
-
{
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
{
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
1210
|
+
{
|
|
1211
|
+
name: "kind",
|
|
1212
|
+
type: "away|committed|recovery",
|
|
1213
|
+
required: true,
|
|
1214
|
+
description: "Whether the behavior moves away from values, toward them, or repairs after rupture.",
|
|
1215
|
+
enumValues: ["away", "committed", "recovery"]
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
name: "title",
|
|
1219
|
+
type: "string",
|
|
1220
|
+
required: true,
|
|
1221
|
+
description: "Behavior label."
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
name: "description",
|
|
1225
|
+
type: "string",
|
|
1226
|
+
required: false,
|
|
1227
|
+
description: "What the behavior looks like.",
|
|
1228
|
+
defaultValue: ""
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
name: "commonCues",
|
|
1232
|
+
type: "string[]",
|
|
1233
|
+
required: false,
|
|
1234
|
+
description: "Typical cues for this behavior.",
|
|
1235
|
+
defaultValue: []
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
name: "urgeStory",
|
|
1239
|
+
type: "string",
|
|
1240
|
+
required: false,
|
|
1241
|
+
description: "What the inner urge or story feels like.",
|
|
1242
|
+
defaultValue: ""
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
name: "shortTermPayoff",
|
|
1246
|
+
type: "string",
|
|
1247
|
+
required: false,
|
|
1248
|
+
description: "Immediate payoff.",
|
|
1249
|
+
defaultValue: ""
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
name: "longTermCost",
|
|
1253
|
+
type: "string",
|
|
1254
|
+
required: false,
|
|
1255
|
+
description: "Longer-term cost.",
|
|
1256
|
+
defaultValue: ""
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
name: "replacementMove",
|
|
1260
|
+
type: "string",
|
|
1261
|
+
required: false,
|
|
1262
|
+
description: "Preferred replacement move.",
|
|
1263
|
+
defaultValue: ""
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
name: "repairPlan",
|
|
1267
|
+
type: "string",
|
|
1268
|
+
required: false,
|
|
1269
|
+
description: "Repair plan after the behavior occurs.",
|
|
1270
|
+
defaultValue: ""
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
name: "linkedPatternIds",
|
|
1274
|
+
type: "string[]",
|
|
1275
|
+
required: false,
|
|
1276
|
+
description: "Linked behavior pattern ids.",
|
|
1277
|
+
defaultValue: []
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
name: "linkedValueIds",
|
|
1281
|
+
type: "string[]",
|
|
1282
|
+
required: false,
|
|
1283
|
+
description: "Linked value ids.",
|
|
1284
|
+
defaultValue: []
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
name: "linkedSchemaIds",
|
|
1288
|
+
type: "string[]",
|
|
1289
|
+
required: false,
|
|
1290
|
+
description: "Linked schema ids.",
|
|
1291
|
+
defaultValue: []
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
name: "linkedModeIds",
|
|
1295
|
+
type: "string[]",
|
|
1296
|
+
required: false,
|
|
1297
|
+
description: "Linked mode ids.",
|
|
1298
|
+
defaultValue: []
|
|
1299
|
+
}
|
|
364
1300
|
]
|
|
365
1301
|
},
|
|
366
1302
|
{
|
|
@@ -371,21 +1307,97 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
371
1307
|
"Beliefs can link to values, behaviors, modes, and trigger reports.",
|
|
372
1308
|
"Behavior patterns can point to beliefs that keep the loop alive."
|
|
373
1309
|
],
|
|
374
|
-
searchHints: [
|
|
375
|
-
|
|
1310
|
+
searchHints: [
|
|
1311
|
+
"Search by statement or known schema theme before creating a new belief entry."
|
|
1312
|
+
],
|
|
1313
|
+
examples: [
|
|
1314
|
+
'{"statement":"If I disappoint people, they will leave me.","beliefType":"conditional","confidence":82,"evidenceFor":["People got cold when I failed them before"],"evidenceAgainst":["Some people stayed with me even after conflict"],"flexibleAlternative":"Disappointing someone can strain a relationship, but it does not automatically mean abandonment."}'
|
|
1315
|
+
],
|
|
376
1316
|
fieldGuide: [
|
|
377
|
-
{
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
{
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
1317
|
+
{
|
|
1318
|
+
name: "schemaId",
|
|
1319
|
+
type: "string|null",
|
|
1320
|
+
required: false,
|
|
1321
|
+
description: "Optional linked schema catalog id.",
|
|
1322
|
+
defaultValue: null,
|
|
1323
|
+
nullable: true
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "statement",
|
|
1327
|
+
type: "string",
|
|
1328
|
+
required: true,
|
|
1329
|
+
description: "Belief statement in the user's own words."
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
name: "beliefType",
|
|
1333
|
+
type: "absolute|conditional",
|
|
1334
|
+
required: true,
|
|
1335
|
+
description: "Whether the belief is absolute or if-then shaped.",
|
|
1336
|
+
enumValues: ["absolute", "conditional"]
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
name: "originNote",
|
|
1340
|
+
type: "string",
|
|
1341
|
+
required: false,
|
|
1342
|
+
description: "Where the belief seems to come from.",
|
|
1343
|
+
defaultValue: ""
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
name: "confidence",
|
|
1347
|
+
type: "integer",
|
|
1348
|
+
required: false,
|
|
1349
|
+
description: "How strongly the belief feels true from 0 to 100.",
|
|
1350
|
+
defaultValue: 60
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
name: "evidenceFor",
|
|
1354
|
+
type: "string[]",
|
|
1355
|
+
required: false,
|
|
1356
|
+
description: "Evidence that seems to support the belief.",
|
|
1357
|
+
defaultValue: []
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
name: "evidenceAgainst",
|
|
1361
|
+
type: "string[]",
|
|
1362
|
+
required: false,
|
|
1363
|
+
description: "Evidence that weakens the belief.",
|
|
1364
|
+
defaultValue: []
|
|
1365
|
+
},
|
|
1366
|
+
{
|
|
1367
|
+
name: "flexibleAlternative",
|
|
1368
|
+
type: "string",
|
|
1369
|
+
required: false,
|
|
1370
|
+
description: "More flexible alternative belief.",
|
|
1371
|
+
defaultValue: ""
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
name: "linkedValueIds",
|
|
1375
|
+
type: "string[]",
|
|
1376
|
+
required: false,
|
|
1377
|
+
description: "Linked value ids.",
|
|
1378
|
+
defaultValue: []
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
name: "linkedBehaviorIds",
|
|
1382
|
+
type: "string[]",
|
|
1383
|
+
required: false,
|
|
1384
|
+
description: "Linked behavior ids.",
|
|
1385
|
+
defaultValue: []
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
name: "linkedModeIds",
|
|
1389
|
+
type: "string[]",
|
|
1390
|
+
required: false,
|
|
1391
|
+
description: "Linked mode ids.",
|
|
1392
|
+
defaultValue: []
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
name: "linkedReportIds",
|
|
1396
|
+
type: "string[]",
|
|
1397
|
+
required: false,
|
|
1398
|
+
description: "Linked trigger report ids.",
|
|
1399
|
+
defaultValue: []
|
|
1400
|
+
}
|
|
389
1401
|
]
|
|
390
1402
|
},
|
|
391
1403
|
{
|
|
@@ -396,24 +1408,124 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
396
1408
|
"Modes can link to patterns, behaviors, and values.",
|
|
397
1409
|
"Trigger reports can include linkedModeIds and modeOverlays that reference modes."
|
|
398
1410
|
],
|
|
399
|
-
searchHints: [
|
|
400
|
-
|
|
1411
|
+
searchHints: [
|
|
1412
|
+
"Search by title or family before creating a new mode profile."
|
|
1413
|
+
],
|
|
1414
|
+
examples: [
|
|
1415
|
+
'{"family":"coping","title":"Cold controller","fear":"If I soften, I will be humiliated or lose control.","protectiveJob":"Stay hyper-competent and unreachable when threatened."}'
|
|
1416
|
+
],
|
|
401
1417
|
fieldGuide: [
|
|
402
|
-
{
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{
|
|
416
|
-
|
|
1418
|
+
{
|
|
1419
|
+
name: "family",
|
|
1420
|
+
type: "coping|child|critic_parent|healthy_adult|happy_child",
|
|
1421
|
+
required: true,
|
|
1422
|
+
description: "Mode family.",
|
|
1423
|
+
enumValues: [
|
|
1424
|
+
"coping",
|
|
1425
|
+
"child",
|
|
1426
|
+
"critic_parent",
|
|
1427
|
+
"healthy_adult",
|
|
1428
|
+
"happy_child"
|
|
1429
|
+
]
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
name: "title",
|
|
1433
|
+
type: "string",
|
|
1434
|
+
required: true,
|
|
1435
|
+
description: "Mode title."
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
name: "archetype",
|
|
1439
|
+
type: "string",
|
|
1440
|
+
required: false,
|
|
1441
|
+
description: "Optional archetype label.",
|
|
1442
|
+
defaultValue: ""
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
name: "persona",
|
|
1446
|
+
type: "string",
|
|
1447
|
+
required: false,
|
|
1448
|
+
description: "Narrative or felt sense of the mode.",
|
|
1449
|
+
defaultValue: ""
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
name: "imagery",
|
|
1453
|
+
type: "string",
|
|
1454
|
+
required: false,
|
|
1455
|
+
description: "Imagery associated with the mode.",
|
|
1456
|
+
defaultValue: ""
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
name: "symbolicForm",
|
|
1460
|
+
type: "string",
|
|
1461
|
+
required: false,
|
|
1462
|
+
description: "Symbolic form or metaphor.",
|
|
1463
|
+
defaultValue: ""
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
name: "facialExpression",
|
|
1467
|
+
type: "string",
|
|
1468
|
+
required: false,
|
|
1469
|
+
description: "Typical facial expression or posture.",
|
|
1470
|
+
defaultValue: ""
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "fear",
|
|
1474
|
+
type: "string",
|
|
1475
|
+
required: false,
|
|
1476
|
+
description: "Core fear carried by the mode.",
|
|
1477
|
+
defaultValue: ""
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
name: "burden",
|
|
1481
|
+
type: "string",
|
|
1482
|
+
required: false,
|
|
1483
|
+
description: "Burden or pain the mode carries.",
|
|
1484
|
+
defaultValue: ""
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
name: "protectiveJob",
|
|
1488
|
+
type: "string",
|
|
1489
|
+
required: false,
|
|
1490
|
+
description: "What job the mode thinks it is doing.",
|
|
1491
|
+
defaultValue: ""
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
name: "originContext",
|
|
1495
|
+
type: "string",
|
|
1496
|
+
required: false,
|
|
1497
|
+
description: "Where the mode seems to come from.",
|
|
1498
|
+
defaultValue: ""
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
name: "firstAppearanceAt",
|
|
1502
|
+
type: "string|null",
|
|
1503
|
+
required: false,
|
|
1504
|
+
description: "Optional first-seen marker.",
|
|
1505
|
+
defaultValue: null,
|
|
1506
|
+
nullable: true
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
name: "linkedPatternIds",
|
|
1510
|
+
type: "string[]",
|
|
1511
|
+
required: false,
|
|
1512
|
+
description: "Linked pattern ids.",
|
|
1513
|
+
defaultValue: []
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
name: "linkedBehaviorIds",
|
|
1517
|
+
type: "string[]",
|
|
1518
|
+
required: false,
|
|
1519
|
+
description: "Linked behavior ids.",
|
|
1520
|
+
defaultValue: []
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
name: "linkedValueIds",
|
|
1524
|
+
type: "string[]",
|
|
1525
|
+
required: false,
|
|
1526
|
+
description: "Linked value ids.",
|
|
1527
|
+
defaultValue: []
|
|
1528
|
+
}
|
|
417
1529
|
]
|
|
418
1530
|
},
|
|
419
1531
|
{
|
|
@@ -424,12 +1536,31 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
424
1536
|
"Mode guide sessions help the user reason toward likely modes before or alongside mode profiles.",
|
|
425
1537
|
"Use mode guide sessions for guided interpretation, not as a replacement for durable mode profiles."
|
|
426
1538
|
],
|
|
427
|
-
searchHints: [
|
|
428
|
-
|
|
1539
|
+
searchHints: [
|
|
1540
|
+
"Search by summary when revisiting a prior guided mode session."
|
|
1541
|
+
],
|
|
1542
|
+
examples: [
|
|
1543
|
+
'{"summary":"Mapping the part that takes over under criticism","answers":[{"questionKey":"felt_shift","value":"I go cold and rigid"}],"results":[{"family":"coping","archetype":"detached_protector","label":"Cold controller","confidence":0.74,"reasoning":"It distances from shame and tries to stay untouchable."}]}'
|
|
1544
|
+
],
|
|
429
1545
|
fieldGuide: [
|
|
430
|
-
{
|
|
431
|
-
|
|
432
|
-
|
|
1546
|
+
{
|
|
1547
|
+
name: "summary",
|
|
1548
|
+
type: "string",
|
|
1549
|
+
required: true,
|
|
1550
|
+
description: "Short summary of what the guided session explored."
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
name: "answers",
|
|
1554
|
+
type: "array",
|
|
1555
|
+
required: true,
|
|
1556
|
+
description: "List of { questionKey, value } items capturing the user's guided answers."
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
name: "results",
|
|
1560
|
+
type: "array",
|
|
1561
|
+
required: true,
|
|
1562
|
+
description: "List of { family, archetype, label, confidence 0-1, reasoning } candidate mode interpretations."
|
|
1563
|
+
}
|
|
433
1564
|
]
|
|
434
1565
|
},
|
|
435
1566
|
{
|
|
@@ -441,31 +1572,168 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
|
441
1572
|
"A report is the best container for one specific emotionally meaningful episode.",
|
|
442
1573
|
"Use reports when you need one event chain, not just a generic pattern."
|
|
443
1574
|
],
|
|
444
|
-
searchHints: [
|
|
445
|
-
|
|
1575
|
+
searchHints: [
|
|
1576
|
+
"Search by title, event wording, or linked entities before creating a duplicate report."
|
|
1577
|
+
],
|
|
1578
|
+
examples: [
|
|
1579
|
+
'{"title":"Partner said we need to talk and I spiraled","customEventType":"relationship threat","eventSituation":"My partner texted that we needed to talk tonight.","emotions":[{"label":"fear","intensity":85},{"label":"shame","intensity":60}],"thoughts":[{"text":"This means I messed everything up."}],"behaviors":[{"text":"Paced, catastrophized, and checked my phone repeatedly"}],"nextMoves":["Wait until we speak before predicting the outcome","Write down the facts I actually know"]}'
|
|
1580
|
+
],
|
|
446
1581
|
fieldGuide: [
|
|
447
|
-
{
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
{
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
{
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1582
|
+
{
|
|
1583
|
+
name: "title",
|
|
1584
|
+
type: "string",
|
|
1585
|
+
required: true,
|
|
1586
|
+
description: "Short name for the incident."
|
|
1587
|
+
},
|
|
1588
|
+
{
|
|
1589
|
+
name: "status",
|
|
1590
|
+
type: "draft|reviewed|integrated",
|
|
1591
|
+
required: false,
|
|
1592
|
+
description: "Reflection progress state.",
|
|
1593
|
+
enumValues: ["draft", "reviewed", "integrated"],
|
|
1594
|
+
defaultValue: "draft"
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
name: "eventTypeId",
|
|
1598
|
+
type: "string|null",
|
|
1599
|
+
required: false,
|
|
1600
|
+
description: "Known event type id if already cataloged.",
|
|
1601
|
+
defaultValue: null,
|
|
1602
|
+
nullable: true
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
name: "customEventType",
|
|
1606
|
+
type: "string",
|
|
1607
|
+
required: false,
|
|
1608
|
+
description: "Free-text event type when no existing type fits.",
|
|
1609
|
+
defaultValue: ""
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
name: "eventSituation",
|
|
1613
|
+
type: "string",
|
|
1614
|
+
required: false,
|
|
1615
|
+
description: "What happened in the situation.",
|
|
1616
|
+
defaultValue: ""
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
name: "occurredAt",
|
|
1620
|
+
type: "string|null",
|
|
1621
|
+
required: false,
|
|
1622
|
+
description: "When it happened.",
|
|
1623
|
+
defaultValue: null,
|
|
1624
|
+
nullable: true
|
|
1625
|
+
},
|
|
1626
|
+
{
|
|
1627
|
+
name: "emotions",
|
|
1628
|
+
type: "array",
|
|
1629
|
+
required: false,
|
|
1630
|
+
description: "List of { emotionDefinitionId|null, label, intensity 0-100, note } items.",
|
|
1631
|
+
defaultValue: []
|
|
1632
|
+
},
|
|
1633
|
+
{
|
|
1634
|
+
name: "thoughts",
|
|
1635
|
+
type: "array",
|
|
1636
|
+
required: false,
|
|
1637
|
+
description: "List of { text, parentMode, criticMode, beliefId|null } items.",
|
|
1638
|
+
defaultValue: []
|
|
1639
|
+
},
|
|
1640
|
+
{
|
|
1641
|
+
name: "behaviors",
|
|
1642
|
+
type: "array",
|
|
1643
|
+
required: false,
|
|
1644
|
+
description: "List of { text, mode, behaviorId|null } items.",
|
|
1645
|
+
defaultValue: []
|
|
1646
|
+
},
|
|
1647
|
+
{
|
|
1648
|
+
name: "consequences",
|
|
1649
|
+
type: "object",
|
|
1650
|
+
required: false,
|
|
1651
|
+
description: "Object with selfShortTerm, selfLongTerm, othersShortTerm, othersLongTerm string arrays."
|
|
1652
|
+
},
|
|
1653
|
+
{
|
|
1654
|
+
name: "linkedPatternIds",
|
|
1655
|
+
type: "string[]",
|
|
1656
|
+
required: false,
|
|
1657
|
+
description: "Linked pattern ids.",
|
|
1658
|
+
defaultValue: []
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
name: "linkedValueIds",
|
|
1662
|
+
type: "string[]",
|
|
1663
|
+
required: false,
|
|
1664
|
+
description: "Linked value ids.",
|
|
1665
|
+
defaultValue: []
|
|
1666
|
+
},
|
|
1667
|
+
{
|
|
1668
|
+
name: "linkedGoalIds",
|
|
1669
|
+
type: "string[]",
|
|
1670
|
+
required: false,
|
|
1671
|
+
description: "Linked goal ids.",
|
|
1672
|
+
defaultValue: []
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
name: "linkedProjectIds",
|
|
1676
|
+
type: "string[]",
|
|
1677
|
+
required: false,
|
|
1678
|
+
description: "Linked project ids.",
|
|
1679
|
+
defaultValue: []
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
name: "linkedTaskIds",
|
|
1683
|
+
type: "string[]",
|
|
1684
|
+
required: false,
|
|
1685
|
+
description: "Linked task ids.",
|
|
1686
|
+
defaultValue: []
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
name: "linkedBehaviorIds",
|
|
1690
|
+
type: "string[]",
|
|
1691
|
+
required: false,
|
|
1692
|
+
description: "Linked behavior ids.",
|
|
1693
|
+
defaultValue: []
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
name: "linkedBeliefIds",
|
|
1697
|
+
type: "string[]",
|
|
1698
|
+
required: false,
|
|
1699
|
+
description: "Linked belief ids.",
|
|
1700
|
+
defaultValue: []
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
name: "linkedModeIds",
|
|
1704
|
+
type: "string[]",
|
|
1705
|
+
required: false,
|
|
1706
|
+
description: "Linked mode ids.",
|
|
1707
|
+
defaultValue: []
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
name: "modeOverlays",
|
|
1711
|
+
type: "string[]",
|
|
1712
|
+
required: false,
|
|
1713
|
+
description: "Extra mode labels noticed during the incident.",
|
|
1714
|
+
defaultValue: []
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
name: "schemaLinks",
|
|
1718
|
+
type: "string[]",
|
|
1719
|
+
required: false,
|
|
1720
|
+
description: "Schema names or themes that seem related to the incident.",
|
|
1721
|
+
defaultValue: []
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
name: "modeTimeline",
|
|
1725
|
+
type: "array",
|
|
1726
|
+
required: false,
|
|
1727
|
+
description: "List of { stage, modeId|null, label, note } items describing the sequence of modes.",
|
|
1728
|
+
defaultValue: []
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
name: "nextMoves",
|
|
1732
|
+
type: "string[]",
|
|
1733
|
+
required: false,
|
|
1734
|
+
description: "Concrete next steps or repair moves.",
|
|
1735
|
+
defaultValue: []
|
|
1736
|
+
}
|
|
469
1737
|
]
|
|
470
1738
|
}
|
|
471
1739
|
];
|
|
@@ -483,7 +1751,17 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
483
1751
|
"Name the preferred alternative response."
|
|
484
1752
|
],
|
|
485
1753
|
requiredForCreate: ["title"],
|
|
486
|
-
highValueOptionalFields: [
|
|
1754
|
+
highValueOptionalFields: [
|
|
1755
|
+
"description",
|
|
1756
|
+
"targetBehavior",
|
|
1757
|
+
"cueContexts",
|
|
1758
|
+
"shortTermPayoff",
|
|
1759
|
+
"longTermCost",
|
|
1760
|
+
"preferredResponse",
|
|
1761
|
+
"linkedBeliefIds",
|
|
1762
|
+
"linkedModeIds",
|
|
1763
|
+
"linkedValueIds"
|
|
1764
|
+
],
|
|
487
1765
|
exampleQuestions: [
|
|
488
1766
|
"What usually sets this loop off?",
|
|
489
1767
|
"What do you tend to do next, outwardly or inwardly?",
|
|
@@ -509,7 +1787,17 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
509
1787
|
"Link a schemaId only when a real schema catalog match is known."
|
|
510
1788
|
],
|
|
511
1789
|
requiredForCreate: ["statement", "beliefType"],
|
|
512
|
-
highValueOptionalFields: [
|
|
1790
|
+
highValueOptionalFields: [
|
|
1791
|
+
"schemaId",
|
|
1792
|
+
"confidence",
|
|
1793
|
+
"originNote",
|
|
1794
|
+
"evidenceFor",
|
|
1795
|
+
"evidenceAgainst",
|
|
1796
|
+
"flexibleAlternative",
|
|
1797
|
+
"linkedReportIds",
|
|
1798
|
+
"linkedBehaviorIds",
|
|
1799
|
+
"linkedModeIds"
|
|
1800
|
+
],
|
|
513
1801
|
exampleQuestions: [
|
|
514
1802
|
"What is the sentence your mind seems to be pushing here?",
|
|
515
1803
|
"Is it more of an always/never belief, or an if-then rule?",
|
|
@@ -534,7 +1822,17 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
534
1822
|
"Optionally note origin context and linked patterns or behaviors."
|
|
535
1823
|
],
|
|
536
1824
|
requiredForCreate: ["family", "title"],
|
|
537
|
-
highValueOptionalFields: [
|
|
1825
|
+
highValueOptionalFields: [
|
|
1826
|
+
"persona",
|
|
1827
|
+
"imagery",
|
|
1828
|
+
"fear",
|
|
1829
|
+
"burden",
|
|
1830
|
+
"protectiveJob",
|
|
1831
|
+
"originContext",
|
|
1832
|
+
"linkedPatternIds",
|
|
1833
|
+
"linkedBehaviorIds",
|
|
1834
|
+
"linkedValueIds"
|
|
1835
|
+
],
|
|
538
1836
|
exampleQuestions: [
|
|
539
1837
|
"What kind of part does this feel like: coping, child, critic-parent, healthy-adult, or happy-child?",
|
|
540
1838
|
"If you gave this mode a name, what would it be?",
|
|
@@ -560,7 +1858,22 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
560
1858
|
"Identify next moves and linked patterns, beliefs, modes, values, or tasks."
|
|
561
1859
|
],
|
|
562
1860
|
requiredForCreate: ["title"],
|
|
563
|
-
highValueOptionalFields: [
|
|
1861
|
+
highValueOptionalFields: [
|
|
1862
|
+
"eventTypeId",
|
|
1863
|
+
"customEventType",
|
|
1864
|
+
"eventSituation",
|
|
1865
|
+
"occurredAt",
|
|
1866
|
+
"emotions",
|
|
1867
|
+
"thoughts",
|
|
1868
|
+
"behaviors",
|
|
1869
|
+
"consequences",
|
|
1870
|
+
"modeTimeline",
|
|
1871
|
+
"nextMoves",
|
|
1872
|
+
"linkedPatternIds",
|
|
1873
|
+
"linkedBeliefIds",
|
|
1874
|
+
"linkedModeIds",
|
|
1875
|
+
"linkedValueIds"
|
|
1876
|
+
],
|
|
564
1877
|
exampleQuestions: [
|
|
565
1878
|
"What happened, as concretely as you can say it?",
|
|
566
1879
|
"What emotions were there, and how intense were they?",
|
|
@@ -593,11 +1906,16 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
593
1906
|
summary: "Create one or more entities in one ordered batch.",
|
|
594
1907
|
whenToUse: "Use after explicit save intent and after duplicate checks when needed.",
|
|
595
1908
|
inputShape: "{ atomic?: boolean, operations: Array<{ entityType: CrudEntityType, clientRef?: string, data: object }> }",
|
|
596
|
-
requiredFields: [
|
|
1909
|
+
requiredFields: [
|
|
1910
|
+
"operations",
|
|
1911
|
+
"operations[].entityType",
|
|
1912
|
+
"operations[].data"
|
|
1913
|
+
],
|
|
597
1914
|
notes: [
|
|
598
1915
|
"entityType alone is never enough; full data is required.",
|
|
599
1916
|
"Batch multiple related creates together when they come from one user ask.",
|
|
600
|
-
"Goal, project, and task creates can include notes: [{ contentMarkdown, author?, links? }] and Forge will auto-link those notes to the newly created entity."
|
|
1917
|
+
"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.",
|
|
1918
|
+
"The same batch create route also handles calendar_event, work_block_template, and task_timebox. Calendar-event creates still trigger downstream projection sync when a writable provider calendar is selected."
|
|
601
1919
|
],
|
|
602
1920
|
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"}]}'
|
|
603
1921
|
},
|
|
@@ -606,17 +1924,37 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
606
1924
|
summary: "Patch one or more entities in one ordered batch.",
|
|
607
1925
|
whenToUse: "Use when ids are known and the user explicitly wants a change persisted.",
|
|
608
1926
|
inputShape: "{ atomic?: boolean, operations: Array<{ entityType: CrudEntityType, id: string, clientRef?: string, patch: object }> }",
|
|
609
|
-
requiredFields: [
|
|
610
|
-
|
|
611
|
-
|
|
1927
|
+
requiredFields: [
|
|
1928
|
+
"operations",
|
|
1929
|
+
"operations[].entityType",
|
|
1930
|
+
"operations[].id",
|
|
1931
|
+
"operations[].patch"
|
|
1932
|
+
],
|
|
1933
|
+
notes: [
|
|
1934
|
+
"patch is partial; only send the fields that should change.",
|
|
1935
|
+
"Project lifecycle is status-driven: patch project.status to active, paused, or completed instead of looking for separate suspend, restart, or finish routes.",
|
|
1936
|
+
"Setting project.status to completed finishes the project and auto-completes linked unfinished tasks through the normal task completion path.",
|
|
1937
|
+
"Task and project scheduling rules stay on these same entity patches. Update task.schedulingRules, task.plannedDurationSeconds, or project.schedulingRules here.",
|
|
1938
|
+
"Use this same route to move or relink calendar_event records and to edit work_block_template or task_timebox records without switching to narrower calendar CRUD tools."
|
|
1939
|
+
],
|
|
1940
|
+
example: '{"operations":[{"entityType":"project","id":"project_123","patch":{"status":"completed"},"clientRef":"project-finish-1"}]}'
|
|
612
1941
|
},
|
|
613
1942
|
{
|
|
614
1943
|
toolName: "forge_delete_entities",
|
|
615
1944
|
summary: "Delete one or more entities through the batch delete flow.",
|
|
616
1945
|
whenToUse: "Use for explicit delete intent only.",
|
|
617
|
-
inputShape:
|
|
618
|
-
requiredFields: [
|
|
619
|
-
|
|
1946
|
+
inputShape: '{ atomic?: boolean, operations: Array<{ entityType: CrudEntityType, id: string, clientRef?: string, mode?: "soft"|"hard", reason?: string }> }',
|
|
1947
|
+
requiredFields: [
|
|
1948
|
+
"operations",
|
|
1949
|
+
"operations[].entityType",
|
|
1950
|
+
"operations[].id"
|
|
1951
|
+
],
|
|
1952
|
+
notes: [
|
|
1953
|
+
"Delete defaults to soft.",
|
|
1954
|
+
"Use mode=hard only for explicit permanent removal.",
|
|
1955
|
+
"Restoration is only possible after soft delete.",
|
|
1956
|
+
"calendar_event, work_block_template, and task_timebox are immediate calendar-domain deletions: calendar events delete remote projections too, and these records do not go through the settings bin."
|
|
1957
|
+
],
|
|
620
1958
|
example: '{"operations":[{"entityType":"task","id":"task_123","mode":"soft","reason":"Merged into another task"}]}'
|
|
621
1959
|
},
|
|
622
1960
|
{
|
|
@@ -624,17 +1962,100 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
624
1962
|
summary: "Restore soft-deleted entities from the settings bin.",
|
|
625
1963
|
whenToUse: "Use when the user wants an entity brought back after a soft delete.",
|
|
626
1964
|
inputShape: "{ atomic?: boolean, operations: Array<{ entityType: CrudEntityType, id: string, clientRef?: string }> }",
|
|
627
|
-
requiredFields: [
|
|
1965
|
+
requiredFields: [
|
|
1966
|
+
"operations",
|
|
1967
|
+
"operations[].entityType",
|
|
1968
|
+
"operations[].id"
|
|
1969
|
+
],
|
|
628
1970
|
notes: ["Restore only works for soft-deleted entities."],
|
|
629
1971
|
example: '{"operations":[{"entityType":"goal","id":"goal_123","clientRef":"goal-restore-1"}]}'
|
|
630
1972
|
},
|
|
1973
|
+
{
|
|
1974
|
+
toolName: "forge_get_calendar_overview",
|
|
1975
|
+
summary: "Read connected calendars, Forge-native events, mirrored events, recurring work blocks, and task timeboxes together.",
|
|
1976
|
+
whenToUse: "Use before calendar-aware planning, slot selection, or scheduling diagnostics.",
|
|
1977
|
+
inputShape: "{ from?: string, to?: string }",
|
|
1978
|
+
requiredFields: [],
|
|
1979
|
+
notes: [
|
|
1980
|
+
"Use ISO datetimes.",
|
|
1981
|
+
"The response includes provider metadata, live connections, mirrored external events, derived work-block instances, and task timeboxes."
|
|
1982
|
+
],
|
|
1983
|
+
example: '{"from":"2026-04-02T00:00:00.000Z","to":"2026-04-09T00:00:00.000Z"}'
|
|
1984
|
+
},
|
|
1985
|
+
{
|
|
1986
|
+
toolName: "forge_connect_calendar_provider",
|
|
1987
|
+
summary: "Create a Forge calendar connection for Google, Apple, Exchange Online, or custom CalDAV.",
|
|
1988
|
+
whenToUse: "Use only when the operator explicitly wants Forge connected to an external calendar provider.",
|
|
1989
|
+
inputShape: '{ provider: "google"|"apple"|"caldav"|"microsoft", label: string, username?: string, clientId?: string, clientSecret?: string, refreshToken?: string, password?: string, serverUrl?: string, authSessionId?: string, selectedCalendarUrls: string[], forgeCalendarUrl?: string, createForgeCalendar?: boolean }',
|
|
1990
|
+
requiredFields: ["provider", "label", "provider-specific credentials"],
|
|
1991
|
+
notes: [
|
|
1992
|
+
"Google uses OAuth client credentials plus a refresh token.",
|
|
1993
|
+
"Apple starts from https://caldav.icloud.com and autodiscovers the principal plus calendars after authentication.",
|
|
1994
|
+
"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.",
|
|
1995
|
+
"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.",
|
|
1996
|
+
"Custom CalDAV uses an account-level server URL, not a single calendar collection URL.",
|
|
1997
|
+
"Writable providers publish Forge work blocks and timeboxes to the dedicated Forge calendar for that connection."
|
|
1998
|
+
],
|
|
1999
|
+
example: '{"provider":"apple","label":"Primary Apple","username":"operator@example.com","password":"app-password","selectedCalendarUrls":["https://caldav.icloud.com/.../Family/"],"forgeCalendarUrl":"https://caldav.icloud.com/.../Forge/","createForgeCalendar":false}'
|
|
2000
|
+
},
|
|
2001
|
+
{
|
|
2002
|
+
toolName: "forge_create_work_block_template",
|
|
2003
|
+
summary: "Create a recurring half-day, holiday, or custom work-block template.",
|
|
2004
|
+
whenToUse: "Use when the operator wants recurring time windows such as Main Activity, Secondary Activity, Third Activity, Rest, Holiday, or a custom block.",
|
|
2005
|
+
inputShape: '{ title: string, kind: "main_activity"|"secondary_activity"|"third_activity"|"rest"|"holiday"|"custom", color: string, timezone: string, weekDays: integer[], startMinute: integer, endMinute: integer, startsOn?: "YYYY-MM-DD"|null, endsOn?: "YYYY-MM-DD"|null, blockingState: "allowed"|"blocked" }',
|
|
2006
|
+
requiredFields: [
|
|
2007
|
+
"title",
|
|
2008
|
+
"kind",
|
|
2009
|
+
"timezone",
|
|
2010
|
+
"weekDays",
|
|
2011
|
+
"startMinute",
|
|
2012
|
+
"endMinute",
|
|
2013
|
+
"blockingState"
|
|
2014
|
+
],
|
|
2015
|
+
notes: [
|
|
2016
|
+
"Minutes are measured from midnight in the selected timezone.",
|
|
2017
|
+
"startsOn and endsOn are optional date bounds. Leaving endsOn null makes the block repeat indefinitely.",
|
|
2018
|
+
"Use kind=holiday with weekDays [0,1,2,3,4,5,6] and minutes 0-1440 for vacations or other full-day blocked ranges.",
|
|
2019
|
+
"Derived instances appear in calendar overview responses immediately after creation.",
|
|
2020
|
+
"This is a convenience helper; agents can also create work_block_template through forge_create_entities."
|
|
2021
|
+
],
|
|
2022
|
+
example: '{"title":"Summer holiday","kind":"holiday","color":"#14b8a6","timezone":"Europe/Zurich","weekDays":[0,1,2,3,4,5,6],"startMinute":0,"endMinute":1440,"startsOn":"2026-08-01","endsOn":"2026-08-16","blockingState":"blocked"}'
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
toolName: "forge_recommend_task_timeboxes",
|
|
2026
|
+
summary: "Suggest future task slots that fit the current calendar rules and schedule.",
|
|
2027
|
+
whenToUse: "Use when preparing focused work in advance.",
|
|
2028
|
+
inputShape: "{ taskId: string, from?: string, to?: string, limit?: integer }",
|
|
2029
|
+
requiredFields: ["taskId"],
|
|
2030
|
+
notes: [
|
|
2031
|
+
"Recommendations consider mirrored calendar events, recurring work blocks, task or project scheduling rules, and the task's planned duration when available.",
|
|
2032
|
+
"Confirm a suggested slot by creating a task timebox."
|
|
2033
|
+
],
|
|
2034
|
+
example: '{"taskId":"task_123","from":"2026-04-02T00:00:00.000Z","to":"2026-04-09T00:00:00.000Z","limit":6}'
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
toolName: "forge_create_task_timebox",
|
|
2038
|
+
summary: "Create a planned task timebox in the Forge calendar domain.",
|
|
2039
|
+
whenToUse: "Use after choosing a valid future slot or when creating a manual timebox directly.",
|
|
2040
|
+
inputShape: '{ taskId: string, projectId?: string|null, title: string, startsAt: string, endsAt: string, source?: "manual"|"suggested"|"live_run" }',
|
|
2041
|
+
requiredFields: ["taskId", "title", "startsAt", "endsAt"],
|
|
2042
|
+
notes: [
|
|
2043
|
+
"Forge publishes these into the dedicated Forge calendar during provider sync.",
|
|
2044
|
+
"Live task runs can later attach to matching timeboxes.",
|
|
2045
|
+
"This is a convenience helper; agents can also create task_timebox through forge_create_entities."
|
|
2046
|
+
],
|
|
2047
|
+
example: '{"taskId":"task_123","projectId":"project_456","title":"Draft the methods section","startsAt":"2026-04-03T08:00:00.000Z","endsAt":"2026-04-03T09:30:00.000Z","source":"suggested"}'
|
|
2048
|
+
},
|
|
631
2049
|
{
|
|
632
2050
|
toolName: "forge_grant_reward_bonus",
|
|
633
2051
|
summary: "Grant an explicit manual XP bonus or penalty with clear provenance.",
|
|
634
2052
|
whenToUse: "Use when the user or operator explicitly wants an auditable reward adjustment beyond the automatic task and habit reward paths.",
|
|
635
2053
|
inputShape: "{ entityType: RewardableEntityType, entityId: string, deltaXp: integer, reasonTitle: string, reasonSummary?: string, metadata?: object }",
|
|
636
2054
|
requiredFields: ["entityType", "entityId", "deltaXp", "reasonTitle"],
|
|
637
|
-
notes: [
|
|
2055
|
+
notes: [
|
|
2056
|
+
"Requires rewards.manage and write scopes.",
|
|
2057
|
+
"Use this for explicit operator judgement, not as a substitute for normal task_run or habit check-in rewards."
|
|
2058
|
+
],
|
|
638
2059
|
example: '{"entityType":"habit","entityId":"habit_morning_training","deltaXp":18,"reasonTitle":"Operator bonus","reasonSummary":"Stayed with the habit through unusual travel friction.","metadata":{"manual":true,"source":"agent"}}'
|
|
639
2060
|
},
|
|
640
2061
|
{
|
|
@@ -643,26 +2064,50 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
643
2064
|
whenToUse: "Use when you have a data-grounded observation or recommendation worth keeping visible in Forge.",
|
|
644
2065
|
inputShape: "{ entityType?: string|null, entityId?: string|null, timeframeLabel?: string|null, title: string, summary: string, recommendation: string, rationale?: string, confidence?: number, visibility?: string, ctaLabel?: string }",
|
|
645
2066
|
requiredFields: ["title", "summary", "recommendation"],
|
|
646
|
-
notes: [
|
|
2067
|
+
notes: [
|
|
2068
|
+
"Insights are for interpretation and advice, not for replacing user-owned goals or tasks."
|
|
2069
|
+
],
|
|
647
2070
|
example: '{"entityType":"goal","entityId":"goal_123","title":"Admin drag is masking momentum","summary":"Creative progress is happening, but admin cleanup keeps interrupting it.","recommendation":"Protect one clean creative block and isolate admin into a separate recurring task.","confidence":0.82}'
|
|
648
2071
|
},
|
|
2072
|
+
{
|
|
2073
|
+
toolName: "forge_adjust_work_minutes",
|
|
2074
|
+
summary: "Add or remove tracked work minutes on a task or project without creating a live task run.",
|
|
2075
|
+
whenToUse: "Use for truthful retrospective minute corrections. Use this instead of forge_log_work when the task or project already exists and only tracked minutes need adjusting.",
|
|
2076
|
+
inputShape: '{ entityType: "task"|"project", entityId: string, deltaMinutes: integer, note?: string }',
|
|
2077
|
+
requiredFields: ["entityType", "entityId", "deltaMinutes"],
|
|
2078
|
+
notes: [
|
|
2079
|
+
"Positive deltaMinutes add tracked minutes and may award XP when a progress bucket is crossed.",
|
|
2080
|
+
"Negative deltaMinutes remove tracked minutes and may reverse XP symmetrically when a progress bucket is crossed downward.",
|
|
2081
|
+
"Requires rewards.manage and write scopes."
|
|
2082
|
+
],
|
|
2083
|
+
example: '{"entityType":"task","entityId":"task_123","deltaMinutes":25,"note":"Captured the off-timer review pass from this morning."}'
|
|
2084
|
+
},
|
|
649
2085
|
{
|
|
650
2086
|
toolName: "forge_log_work",
|
|
651
2087
|
summary: "Log work that already happened.",
|
|
652
|
-
whenToUse: "Use for retroactive work, not for starting a live session.",
|
|
653
|
-
inputShape: "{ taskId?: string, title?: string, description?: string, summary?: string, goalId?: string|null, projectId?: string|null, owner?: string, status?: TaskStatus, priority?: TaskPriority, dueDate?: string|null, effort?: TaskEffort, energy?: TaskEnergy, points?: number, tagIds?: string[], closeoutNote?: { contentMarkdown: string, author?: string|null, links?: Array<{ entityType, entityId, anchorKey? }> } }",
|
|
2088
|
+
whenToUse: "Use for completion-style retroactive work, not for starting a live session or adjusting minutes on an existing record.",
|
|
2089
|
+
inputShape: "{ taskId?: string, title?: string, description?: string, summary?: string, goalId?: string|null, projectId?: string|null, owner?: string, status?: TaskStatus, priority?: TaskPriority, dueDate?: string|null, effort?: TaskEffort, energy?: TaskEnergy, points?: number, tagIds?: string[], closeoutNote?: { contentMarkdown: string, author?: string|null, tags?: string[], destroyAt?: string|null, links?: Array<{ entityType, entityId, anchorKey? }> } }",
|
|
654
2090
|
requiredFields: ["taskId or title"],
|
|
655
|
-
notes: [
|
|
2091
|
+
notes: [
|
|
2092
|
+
"Use taskId when logging work against an existing task.",
|
|
2093
|
+
"Use title when a new completed work item should be created and logged.",
|
|
2094
|
+
"Use forge_adjust_work_minutes for signed minute corrections on existing tasks or projects.",
|
|
2095
|
+
"closeoutNote persists the work summary as a real linked note."
|
|
2096
|
+
],
|
|
656
2097
|
example: '{"taskId":"task_123","summary":"Finished the review draft and cleaned the notes.","points":40,"closeoutNote":{"contentMarkdown":"Finished the review draft, cleaned the note structure, and left one follow-up for QA."}}'
|
|
657
2098
|
},
|
|
658
2099
|
{
|
|
659
2100
|
toolName: "forge_start_task_run",
|
|
660
2101
|
summary: "Start truthful live work on a task.",
|
|
661
2102
|
whenToUse: "Use when the user wants to begin working now.",
|
|
662
|
-
inputShape:
|
|
2103
|
+
inputShape: '{ taskId: string, actor: string, timerMode?: "planned"|"unlimited", plannedDurationSeconds?: number|null, overrideReason?: string|null, isCurrent?: boolean, leaseTtlSeconds?: number, note?: string }',
|
|
663
2104
|
requiredFields: ["taskId", "actor"],
|
|
664
|
-
notes: [
|
|
665
|
-
|
|
2105
|
+
notes: [
|
|
2106
|
+
"If timerMode is planned, plannedDurationSeconds is required.",
|
|
2107
|
+
"If timerMode is unlimited, plannedDurationSeconds must be null or omitted.",
|
|
2108
|
+
"If calendar rules currently block the task, pass an explicit overrideReason to proceed and keep the exception auditable."
|
|
2109
|
+
],
|
|
2110
|
+
example: '{"taskId":"task_123","actor":"aurel","timerMode":"planned","plannedDurationSeconds":1500,"overrideReason":"Protected creative block after clinic hours.","isCurrent":true,"leaseTtlSeconds":900,"note":"Starting focused writing block"}'
|
|
666
2111
|
},
|
|
667
2112
|
{
|
|
668
2113
|
toolName: "forge_heartbeat_task_run",
|
|
@@ -679,25 +2124,33 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
679
2124
|
whenToUse: "Use when several runs exist and one should be the visible current run.",
|
|
680
2125
|
inputShape: "{ taskRunId: string, actor?: string }",
|
|
681
2126
|
requiredFields: ["taskRunId"],
|
|
682
|
-
notes: [
|
|
2127
|
+
notes: [
|
|
2128
|
+
"This does not complete or release a run; it just changes current focus."
|
|
2129
|
+
],
|
|
683
2130
|
example: '{"taskRunId":"run_123","actor":"aurel"}'
|
|
684
2131
|
},
|
|
685
2132
|
{
|
|
686
2133
|
toolName: "forge_complete_task_run",
|
|
687
2134
|
summary: "Finish an active run as completed work.",
|
|
688
2135
|
whenToUse: "Use when the user has finished the live work block.",
|
|
689
|
-
inputShape: "{ taskRunId: string, actor?: string, note?: string, closeoutNote?: { contentMarkdown: string, author?: string|null, links?: Array<{ entityType, entityId, anchorKey? }> } }",
|
|
2136
|
+
inputShape: "{ taskRunId: string, actor?: string, note?: string, closeoutNote?: { contentMarkdown: string, author?: string|null, tags?: string[], destroyAt?: string|null, links?: Array<{ entityType, entityId, anchorKey? }> } }",
|
|
690
2137
|
requiredFields: ["taskRunId"],
|
|
691
|
-
notes: [
|
|
2138
|
+
notes: [
|
|
2139
|
+
"This is the truthful way to finish live work and award completion effects.",
|
|
2140
|
+
"closeoutNote persists a real linked note instead of only updating the transient run note."
|
|
2141
|
+
],
|
|
692
2142
|
example: '{"taskRunId":"run_123","actor":"aurel","note":"Finished the review draft","closeoutNote":{"contentMarkdown":"Completed the draft review and listed the follow-up fixes."}}'
|
|
693
2143
|
},
|
|
694
2144
|
{
|
|
695
2145
|
toolName: "forge_release_task_run",
|
|
696
2146
|
summary: "Stop an active run without marking the task complete.",
|
|
697
2147
|
whenToUse: "Use when the user is stopping or pausing work without completion.",
|
|
698
|
-
inputShape: "{ taskRunId: string, actor?: string, note?: string, closeoutNote?: { contentMarkdown: string, author?: string|null, links?: Array<{ entityType, entityId, anchorKey? }> } }",
|
|
2148
|
+
inputShape: "{ taskRunId: string, actor?: string, note?: string, closeoutNote?: { contentMarkdown: string, author?: string|null, tags?: string[], destroyAt?: string|null, links?: Array<{ entityType, entityId, anchorKey? }> } }",
|
|
699
2149
|
requiredFields: ["taskRunId"],
|
|
700
|
-
notes: [
|
|
2150
|
+
notes: [
|
|
2151
|
+
"Use this instead of faking a stop by only changing task status.",
|
|
2152
|
+
"closeoutNote is useful for documenting blockers or handoff context."
|
|
2153
|
+
],
|
|
701
2154
|
example: '{"taskRunId":"run_123","actor":"aurel","note":"Stopping for now; blocked on feedback","closeoutNote":{"contentMarkdown":"Blocked on feedback from design before I can continue."}}'
|
|
702
2155
|
}
|
|
703
2156
|
];
|
|
@@ -755,11 +2208,14 @@ function buildAgentOnboardingPayload(request) {
|
|
|
755
2208
|
},
|
|
756
2209
|
conceptModel: {
|
|
757
2210
|
goal: "Long-horizon direction or outcome. Goals anchor projects and sometimes tasks directly.",
|
|
758
|
-
project: "A multi-step workstream under one goal. Projects organize related tasks.",
|
|
2211
|
+
project: "A multi-step workstream under one goal. Projects organize related tasks. Project lifecycle is driven by status: active means in play, paused means suspended, and completed means finished. Setting a project to completed auto-completes linked unfinished tasks.",
|
|
759
2212
|
task: "A concrete actionable work item. Task status is board state, not proof of live work.",
|
|
760
2213
|
taskRun: "A live work session attached to a task. Start, heartbeat, focus, complete, and release runs instead of faking work with status alone.",
|
|
761
2214
|
note: "A Markdown work note that can link to one or many entities. Use notes for progress evidence, context, and close-out summaries.",
|
|
762
2215
|
insight: "An agent-authored observation or recommendation grounded in Forge data.",
|
|
2216
|
+
calendar: "A connected calendar source mirrored into Forge. Calendar state combines provider events, recurring work blocks, and task timeboxes.",
|
|
2217
|
+
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.",
|
|
2218
|
+
taskTimebox: "A planned or live calendar slot tied to a task. Timeboxes can be suggested in advance or created automatically from active task runs.",
|
|
763
2219
|
psyche: "Forge Psyche is the reflective domain for values, patterns, behaviors, beliefs, modes, and trigger reports. It is sensitive and should be handled deliberately."
|
|
764
2220
|
},
|
|
765
2221
|
psycheSubmoduleModel: {
|
|
@@ -792,6 +2248,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
792
2248
|
context: "/api/v1/context",
|
|
793
2249
|
xpMetrics: "/api/v1/metrics/xp",
|
|
794
2250
|
weeklyReview: "/api/v1/reviews/weekly",
|
|
2251
|
+
calendarOverview: "/api/v1/calendar/overview",
|
|
795
2252
|
settingsBin: "/api/v1/settings/bin",
|
|
796
2253
|
batchSearch: "/api/v1/entities/search",
|
|
797
2254
|
psycheSchemaCatalog: "/api/v1/psyche/schema-catalog",
|
|
@@ -817,6 +2274,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
817
2274
|
],
|
|
818
2275
|
rewardWorkflow: ["forge_grant_reward_bonus"],
|
|
819
2276
|
workWorkflow: [
|
|
2277
|
+
"forge_adjust_work_minutes",
|
|
820
2278
|
"forge_log_work",
|
|
821
2279
|
"forge_start_task_run",
|
|
822
2280
|
"forge_heartbeat_task_run",
|
|
@@ -824,6 +2282,14 @@ function buildAgentOnboardingPayload(request) {
|
|
|
824
2282
|
"forge_complete_task_run",
|
|
825
2283
|
"forge_release_task_run"
|
|
826
2284
|
],
|
|
2285
|
+
calendarWorkflow: [
|
|
2286
|
+
"forge_get_calendar_overview",
|
|
2287
|
+
"forge_connect_calendar_provider",
|
|
2288
|
+
"forge_sync_calendar_connection",
|
|
2289
|
+
"forge_create_work_block_template",
|
|
2290
|
+
"forge_recommend_task_timeboxes",
|
|
2291
|
+
"forge_create_task_timebox"
|
|
2292
|
+
],
|
|
827
2293
|
insightWorkflow: ["forge_post_insight"]
|
|
828
2294
|
},
|
|
829
2295
|
interactionGuidance: {
|
|
@@ -846,14 +2312,14 @@ function buildAgentOnboardingPayload(request) {
|
|
|
846
2312
|
},
|
|
847
2313
|
deleteDefault: "soft",
|
|
848
2314
|
hardDeleteRequiresExplicitMode: true,
|
|
849
|
-
restoreSummary: "Restore soft-deleted entities through the restore route or the settings bin.",
|
|
850
|
-
entityDeleteSummary: "Entity DELETE routes default to soft delete. Pass mode=hard only when permanent removal is intended.",
|
|
2315
|
+
restoreSummary: "Restore soft-deleted entities through the restore route or the settings bin. Calendar-domain deletes for calendar_event, work_block_template, and task_timebox are immediate and do not enter the bin.",
|
|
2316
|
+
entityDeleteSummary: "Entity DELETE routes default to soft delete. Pass mode=hard only when permanent removal is intended. Calendar-event deletes still remove remote projections downstream.",
|
|
851
2317
|
batchingRule: "forge_create_entities, forge_update_entities, forge_delete_entities, and forge_restore_entities all accept operations as arrays. Batch multiple related mutations together in one request when possible.",
|
|
852
2318
|
searchRule: "forge_search_entities accepts searches as an array. Search before create or update when duplicate risk exists.",
|
|
853
|
-
createRule: "Each create operation must include entityType and full data. entityType alone is not enough.",
|
|
854
|
-
updateRule: "Each update operation must include entityType, id, and patch.",
|
|
2319
|
+
createRule: "Each create operation must include entityType and full data. entityType alone is not enough. This includes calendar_event, work_block_template, and task_timebox alongside the usual planning and Psyche entities.",
|
|
2320
|
+
updateRule: "Each update operation must include entityType, id, and patch. For projects, lifecycle changes are status patches: active to restart, paused to suspend, completed to finish. Keep task and project scheduling rules on those same patch payloads. Calendar-event updates still run downstream provider projection sync.",
|
|
855
2321
|
createExample: '{"operations":[{"entityType":"goal","data":{"title":"Create meaningfully"},"clientRef":"goal-create-1"},{"entityType":"goal","data":{"title":"Build a beautiful family"},"clientRef":"goal-create-2"}]}',
|
|
856
|
-
updateExample: '{"operations":[{"entityType":"
|
|
2322
|
+
updateExample: '{"operations":[{"entityType":"project","id":"project_123","patch":{"status":"paused","schedulingRules":{"blockWorkBlockKinds":["main_activity"],"allowWorkBlockKinds":["secondary_activity"]}},"clientRef":"project-suspend-1"},{"entityType":"task","id":"task_456","patch":{"plannedDurationSeconds":5400,"schedulingRules":{"allowEventKeywords":["creative"],"blockEventKeywords":["clinic"]}},"clientRef":"task-scheduling-1"}]}'
|
|
857
2323
|
}
|
|
858
2324
|
};
|
|
859
2325
|
}
|
|
@@ -917,7 +2383,9 @@ function parseActivityContext(headers) {
|
|
|
917
2383
|
if (Array.isArray(rawSource)) {
|
|
918
2384
|
throw new Error("X-Forge-Source must be a single header value");
|
|
919
2385
|
}
|
|
920
|
-
const source = rawSource === undefined
|
|
2386
|
+
const source = rawSource === undefined
|
|
2387
|
+
? "ui"
|
|
2388
|
+
: activitySourceSchema.parse(typeof rawSource === "string" ? rawSource.trim() : rawSource);
|
|
921
2389
|
return {
|
|
922
2390
|
source,
|
|
923
2391
|
actor: parseOptionalActorHeader(headers)
|
|
@@ -960,7 +2428,8 @@ function hasTokenScope(token, scope) {
|
|
|
960
2428
|
return Boolean(token?.scopes.includes(scope));
|
|
961
2429
|
}
|
|
962
2430
|
function isPsycheEntityType(entityType) {
|
|
963
|
-
return Boolean(entityType &&
|
|
2431
|
+
return Boolean(entityType &&
|
|
2432
|
+
PSYCHE_ENTITY_TYPES.includes(entityType));
|
|
964
2433
|
}
|
|
965
2434
|
function getWatchdogHealth(taskRunWatchdog) {
|
|
966
2435
|
if (!taskRunWatchdog) {
|
|
@@ -1053,6 +2522,48 @@ function buildXpMetricsPayload() {
|
|
|
1053
2522
|
dailyAmbientCap
|
|
1054
2523
|
};
|
|
1055
2524
|
}
|
|
2525
|
+
function resolveWorkAdjustmentTarget(entityType, entityId) {
|
|
2526
|
+
if (entityType === "task") {
|
|
2527
|
+
const task = getTaskById(entityId);
|
|
2528
|
+
return task
|
|
2529
|
+
? {
|
|
2530
|
+
entityType,
|
|
2531
|
+
entityId: task.id,
|
|
2532
|
+
title: task.title,
|
|
2533
|
+
time: task.time
|
|
2534
|
+
}
|
|
2535
|
+
: null;
|
|
2536
|
+
}
|
|
2537
|
+
const project = getProjectSummary(entityId);
|
|
2538
|
+
return project
|
|
2539
|
+
? {
|
|
2540
|
+
entityType,
|
|
2541
|
+
entityId: project.id,
|
|
2542
|
+
title: project.title,
|
|
2543
|
+
time: project.time
|
|
2544
|
+
}
|
|
2545
|
+
: null;
|
|
2546
|
+
}
|
|
2547
|
+
function clampWorkAdjustmentMinutes(deltaMinutes, currentCreditedSeconds) {
|
|
2548
|
+
if (deltaMinutes >= 0) {
|
|
2549
|
+
return deltaMinutes;
|
|
2550
|
+
}
|
|
2551
|
+
const maxRemovableMinutes = Math.max(0, Math.floor(currentCreditedSeconds / 60));
|
|
2552
|
+
return -Math.min(Math.abs(deltaMinutes), maxRemovableMinutes);
|
|
2553
|
+
}
|
|
2554
|
+
function describeWorkAdjustment(input) {
|
|
2555
|
+
const entityLabel = input.entityType === "task" ? "Task" : "Project";
|
|
2556
|
+
const requestedLabel = `${Math.abs(input.requestedDeltaMinutes)} minute${Math.abs(input.requestedDeltaMinutes) === 1 ? "" : "s"}`;
|
|
2557
|
+
const appliedLabel = `${Math.abs(input.appliedDeltaMinutes)} minute${Math.abs(input.appliedDeltaMinutes) === 1 ? "" : "s"}`;
|
|
2558
|
+
const direction = input.appliedDeltaMinutes >= 0 ? "added" : "removed";
|
|
2559
|
+
const clamped = input.requestedDeltaMinutes !== input.appliedDeltaMinutes;
|
|
2560
|
+
return {
|
|
2561
|
+
title: `${entityLabel} work adjusted: ${input.targetTitle}`,
|
|
2562
|
+
description: clamped
|
|
2563
|
+
? `${requestedLabel} requested, ${appliedLabel} ${direction} after clamping to the currently tracked time.`
|
|
2564
|
+
: `${appliedLabel} ${direction} from the tracked work total.`
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
1056
2567
|
function buildOperatorContext() {
|
|
1057
2568
|
const tasks = listTasks();
|
|
1058
2569
|
const dueHabits = listHabits({ dueToday: true }).slice(0, 12);
|
|
@@ -1070,7 +2581,9 @@ function buildOperatorContext() {
|
|
|
1070
2581
|
currentBoard: {
|
|
1071
2582
|
backlog: tasks.filter((task) => task.status === "backlog").slice(0, 20),
|
|
1072
2583
|
focus: tasks.filter((task) => task.status === "focus").slice(0, 20),
|
|
1073
|
-
inProgress: tasks
|
|
2584
|
+
inProgress: tasks
|
|
2585
|
+
.filter((task) => task.status === "in_progress")
|
|
2586
|
+
.slice(0, 20),
|
|
1074
2587
|
blocked: tasks.filter((task) => task.status === "blocked").slice(0, 20),
|
|
1075
2588
|
done: tasks.filter((task) => task.status === "done").slice(0, 20)
|
|
1076
2589
|
},
|
|
@@ -1144,6 +2657,12 @@ function buildOperatorOverviewRouteGuide() {
|
|
|
1144
2657
|
summary: "Preferred multi-entity mutation surface for agents. Delete defaults to soft delete and restore reverses soft deletion.",
|
|
1145
2658
|
requiredScope: "write"
|
|
1146
2659
|
},
|
|
2660
|
+
{
|
|
2661
|
+
id: "work_adjustments",
|
|
2662
|
+
path: "/api/v1/work-adjustments",
|
|
2663
|
+
summary: "Signed retrospective minute adjustments for existing tasks or projects, with symmetric progress-XP updates and clamp protection.",
|
|
2664
|
+
requiredScope: "write"
|
|
2665
|
+
},
|
|
1147
2666
|
{
|
|
1148
2667
|
id: "operator_log_work",
|
|
1149
2668
|
path: "/api/v1/operator/log-work",
|
|
@@ -1161,8 +2680,14 @@ function buildOperatorOverviewRouteGuide() {
|
|
|
1161
2680
|
}
|
|
1162
2681
|
function buildOperatorOverview(request) {
|
|
1163
2682
|
const auth = parseRequestAuth(request.headers);
|
|
1164
|
-
const canReadPsyche = auth.token
|
|
1165
|
-
|
|
2683
|
+
const canReadPsyche = auth.token
|
|
2684
|
+
? hasTokenScope(auth.token, "psyche.read")
|
|
2685
|
+
: true;
|
|
2686
|
+
const warnings = canReadPsyche
|
|
2687
|
+
? []
|
|
2688
|
+
: [
|
|
2689
|
+
"Psyche summary omitted because the active token does not include psyche.read."
|
|
2690
|
+
];
|
|
1166
2691
|
return {
|
|
1167
2692
|
generatedAt: new Date().toISOString(),
|
|
1168
2693
|
snapshot: buildV1Context(),
|
|
@@ -1174,9 +2699,15 @@ function buildOperatorOverview(request) {
|
|
|
1174
2699
|
tokenPresent: Boolean(auth.token),
|
|
1175
2700
|
scopes: auth.token?.scopes ?? [],
|
|
1176
2701
|
canReadPsyche,
|
|
1177
|
-
canWritePsyche: auth.token
|
|
1178
|
-
|
|
1179
|
-
|
|
2702
|
+
canWritePsyche: auth.token
|
|
2703
|
+
? hasTokenScope(auth.token, "psyche.write")
|
|
2704
|
+
: true,
|
|
2705
|
+
canManageModes: auth.token
|
|
2706
|
+
? hasTokenScope(auth.token, "psyche.mode")
|
|
2707
|
+
: true,
|
|
2708
|
+
canManageRewards: auth.token
|
|
2709
|
+
? hasTokenScope(auth.token, "rewards.manage")
|
|
2710
|
+
: true
|
|
1180
2711
|
},
|
|
1181
2712
|
warnings,
|
|
1182
2713
|
routeGuide: buildOperatorOverviewRouteGuide()
|
|
@@ -1184,7 +2715,21 @@ function buildOperatorOverview(request) {
|
|
|
1184
2715
|
}
|
|
1185
2716
|
export async function buildServer(options = {}) {
|
|
1186
2717
|
const managers = createManagerRuntime({ dataRoot: options.dataRoot });
|
|
1187
|
-
|
|
2718
|
+
managers.externalServices.register("google_calendar", {
|
|
2719
|
+
provider: "google",
|
|
2720
|
+
label: "Google Calendar"
|
|
2721
|
+
});
|
|
2722
|
+
managers.externalServices.register("microsoft_graph_calendar", {
|
|
2723
|
+
provider: "microsoft",
|
|
2724
|
+
label: "Exchange Online"
|
|
2725
|
+
});
|
|
2726
|
+
managers.externalServices.register("caldav", {
|
|
2727
|
+
provider: "caldav",
|
|
2728
|
+
label: "CalDAV"
|
|
2729
|
+
});
|
|
2730
|
+
const runtimeConfig = managers.configuration.readRuntimeConfig({
|
|
2731
|
+
dataRoot: options.dataRoot
|
|
2732
|
+
});
|
|
1188
2733
|
configureDatabase({ dataRoot: runtimeConfig.dataRoot ?? undefined });
|
|
1189
2734
|
configureDatabaseSeeding(options.seedDemoData ?? false);
|
|
1190
2735
|
await managers.migration.initialize();
|
|
@@ -1192,7 +2737,9 @@ export async function buildServer(options = {}) {
|
|
|
1192
2737
|
logger: false,
|
|
1193
2738
|
rewriteUrl: (request) => rewriteMountPath(request.url ?? "/")
|
|
1194
2739
|
});
|
|
1195
|
-
const taskRunWatchdog = options.taskRunWatchdog === false
|
|
2740
|
+
const taskRunWatchdog = options.taskRunWatchdog === false
|
|
2741
|
+
? null
|
|
2742
|
+
: createTaskRunWatchdog(options.taskRunWatchdog);
|
|
1196
2743
|
await app.register(cors, {
|
|
1197
2744
|
origin: (origin, callback) => {
|
|
1198
2745
|
if (!origin) {
|
|
@@ -1223,7 +2770,9 @@ export async function buildServer(options = {}) {
|
|
|
1223
2770
|
: statusCode === 400
|
|
1224
2771
|
? "invalid_request"
|
|
1225
2772
|
: "internal_error",
|
|
1226
|
-
error: validationIssues
|
|
2773
|
+
error: validationIssues
|
|
2774
|
+
? "Request validation failed"
|
|
2775
|
+
: getErrorMessage(error),
|
|
1227
2776
|
statusCode,
|
|
1228
2777
|
...(validationIssues ? { details: validationIssues } : {}),
|
|
1229
2778
|
...(isHttpError(error) && error.details ? error.details : {}),
|
|
@@ -1235,6 +2784,112 @@ export async function buildServer(options = {}) {
|
|
|
1235
2784
|
actor: context.actor,
|
|
1236
2785
|
source: context.source
|
|
1237
2786
|
});
|
|
2787
|
+
const applyBatchCalendarEntityEffects = async (results, auth, action) => {
|
|
2788
|
+
for (const result of results) {
|
|
2789
|
+
if (!result.ok ||
|
|
2790
|
+
typeof result.entityType !== "string" ||
|
|
2791
|
+
typeof result.id !== "string") {
|
|
2792
|
+
continue;
|
|
2793
|
+
}
|
|
2794
|
+
if (result.entityType === "calendar_event") {
|
|
2795
|
+
if (action === "delete") {
|
|
2796
|
+
await deleteCalendarEventProjection(result.id, managers.secrets);
|
|
2797
|
+
const event = (result.entity ?? {});
|
|
2798
|
+
recordActivityEvent({
|
|
2799
|
+
entityType: "calendar_event",
|
|
2800
|
+
entityId: result.id,
|
|
2801
|
+
eventType: "calendar_event_deleted",
|
|
2802
|
+
title: `Calendar event deleted: ${typeof event.title === "string" ? event.title : result.id}`,
|
|
2803
|
+
description: "The Forge calendar event was removed and any projected remote copies were deleted.",
|
|
2804
|
+
actor: auth.actor ?? null,
|
|
2805
|
+
source: auth.source,
|
|
2806
|
+
metadata: {
|
|
2807
|
+
calendarId: typeof event.calendarId === "string" ? event.calendarId : null,
|
|
2808
|
+
originType: typeof event.originType === "string" ? event.originType : null
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
continue;
|
|
2812
|
+
}
|
|
2813
|
+
await pushCalendarEventUpdate(result.id, managers.secrets);
|
|
2814
|
+
const refreshed = getCalendarEventById(result.id);
|
|
2815
|
+
if (!refreshed) {
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
result.entity = refreshed;
|
|
2819
|
+
recordActivityEvent({
|
|
2820
|
+
entityType: "calendar_event",
|
|
2821
|
+
entityId: refreshed.id,
|
|
2822
|
+
eventType: action === "create"
|
|
2823
|
+
? "calendar_event_created"
|
|
2824
|
+
: "calendar_event_updated",
|
|
2825
|
+
title: `Calendar event ${action === "create" ? "created" : "updated"}: ${refreshed.title}`,
|
|
2826
|
+
description: action === "create"
|
|
2827
|
+
? "A native Forge calendar event was created."
|
|
2828
|
+
: "The Forge calendar event was updated and projected to remote calendars when configured.",
|
|
2829
|
+
actor: auth.actor ?? null,
|
|
2830
|
+
source: auth.source,
|
|
2831
|
+
metadata: {
|
|
2832
|
+
calendarId: refreshed.calendarId,
|
|
2833
|
+
originType: refreshed.originType
|
|
2834
|
+
}
|
|
2835
|
+
});
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
if (result.entityType === "work_block_template" &&
|
|
2839
|
+
result.entity &&
|
|
2840
|
+
typeof result.entity === "object") {
|
|
2841
|
+
const template = result.entity;
|
|
2842
|
+
recordActivityEvent({
|
|
2843
|
+
entityType: "work_block",
|
|
2844
|
+
entityId: template.id,
|
|
2845
|
+
eventType: action === "create"
|
|
2846
|
+
? "work_block_created"
|
|
2847
|
+
: action === "update"
|
|
2848
|
+
? "work_block_updated"
|
|
2849
|
+
: "work_block_deleted",
|
|
2850
|
+
title: `Work block ${action}: ${template.title}`,
|
|
2851
|
+
description: action === "create"
|
|
2852
|
+
? "A recurring work block was added to Forge."
|
|
2853
|
+
: action === "update"
|
|
2854
|
+
? "The recurring work block was updated."
|
|
2855
|
+
: "The recurring work block was removed.",
|
|
2856
|
+
actor: auth.actor ?? null,
|
|
2857
|
+
source: auth.source,
|
|
2858
|
+
metadata: {
|
|
2859
|
+
kind: template.kind ?? null,
|
|
2860
|
+
blockingState: action === "delete" ? null : (template.blockingState ?? null)
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2865
|
+
if (result.entityType === "task_timebox" &&
|
|
2866
|
+
result.entity &&
|
|
2867
|
+
typeof result.entity === "object") {
|
|
2868
|
+
const timebox = result.entity;
|
|
2869
|
+
recordActivityEvent({
|
|
2870
|
+
entityType: "task_timebox",
|
|
2871
|
+
entityId: timebox.id,
|
|
2872
|
+
eventType: action === "create"
|
|
2873
|
+
? "task_timebox_created"
|
|
2874
|
+
: action === "update"
|
|
2875
|
+
? "task_timebox_updated"
|
|
2876
|
+
: "task_timebox_deleted",
|
|
2877
|
+
title: `Task timebox ${action}: ${timebox.title}`,
|
|
2878
|
+
description: action === "create"
|
|
2879
|
+
? "A future work slot was planned in Forge."
|
|
2880
|
+
: action === "update"
|
|
2881
|
+
? "The planned work slot was updated."
|
|
2882
|
+
: "The planned work slot was removed.",
|
|
2883
|
+
actor: auth.actor ?? null,
|
|
2884
|
+
source: auth.source,
|
|
2885
|
+
metadata: {
|
|
2886
|
+
taskId: timebox.taskId ?? null,
|
|
2887
|
+
status: action === "delete" ? null : (timebox.status ?? null)
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
1238
2893
|
const requireOperatorSession = (headers, detail) => {
|
|
1239
2894
|
const context = authenticateRequest(headers);
|
|
1240
2895
|
managers.authorization.requireAuthenticatedOperator(context, detail);
|
|
@@ -1312,13 +2967,17 @@ export async function buildServer(options = {}) {
|
|
|
1312
2967
|
app.get("/api/v1/openapi.json", async () => buildOpenApiDocument());
|
|
1313
2968
|
app.get("/api/v1/context", async () => buildV1Context());
|
|
1314
2969
|
app.get("/api/v1/operator/context", async (request) => {
|
|
1315
|
-
requireOperatorSession(request.headers, {
|
|
2970
|
+
requireOperatorSession(request.headers, {
|
|
2971
|
+
route: "/api/v1/operator/context"
|
|
2972
|
+
});
|
|
1316
2973
|
return {
|
|
1317
2974
|
context: buildOperatorContext()
|
|
1318
2975
|
};
|
|
1319
2976
|
});
|
|
1320
2977
|
app.get("/api/v1/operator/overview", async (request) => {
|
|
1321
|
-
requireOperatorSession(request.headers, {
|
|
2978
|
+
requireOperatorSession(request.headers, {
|
|
2979
|
+
route: "/api/v1/operator/overview"
|
|
2980
|
+
});
|
|
1322
2981
|
return {
|
|
1323
2982
|
overview: buildOperatorOverview(request)
|
|
1324
2983
|
};
|
|
@@ -1674,8 +3333,16 @@ export async function buildServer(options = {}) {
|
|
|
1674
3333
|
}
|
|
1675
3334
|
return {
|
|
1676
3335
|
report,
|
|
1677
|
-
notes: listNotes({
|
|
1678
|
-
|
|
3336
|
+
notes: listNotes({
|
|
3337
|
+
linkedEntityType: "trigger_report",
|
|
3338
|
+
linkedEntityId: id,
|
|
3339
|
+
limit: 50
|
|
3340
|
+
}),
|
|
3341
|
+
insights: listInsights({
|
|
3342
|
+
entityType: "trigger_report",
|
|
3343
|
+
entityId: id,
|
|
3344
|
+
limit: 50
|
|
3345
|
+
})
|
|
1679
3346
|
};
|
|
1680
3347
|
});
|
|
1681
3348
|
app.patch("/api/v1/psyche/reports/:id", async (request, reply) => {
|
|
@@ -1754,7 +3421,10 @@ export async function buildServer(options = {}) {
|
|
|
1754
3421
|
app.delete("/api/v1/notes/:id", async (request, reply) => {
|
|
1755
3422
|
const { id } = request.params;
|
|
1756
3423
|
const current = getNoteById(id);
|
|
1757
|
-
const linkedEntityType = current?.links.find((link) => isPsycheEntityType(link.entityType))
|
|
3424
|
+
const linkedEntityType = current?.links.find((link) => isPsycheEntityType(link.entityType))
|
|
3425
|
+
?.entityType ??
|
|
3426
|
+
current?.links[0]?.entityType ??
|
|
3427
|
+
null;
|
|
1758
3428
|
const auth = requireNoteAccess(request.headers, linkedEntityType, {
|
|
1759
3429
|
route: "/api/v1/notes/:id",
|
|
1760
3430
|
entityType: linkedEntityType
|
|
@@ -1789,6 +3459,147 @@ export async function buildServer(options = {}) {
|
|
|
1789
3459
|
const query = taskListQuerySchema.parse(request.query ?? {});
|
|
1790
3460
|
return { tasks: listTasks(query) };
|
|
1791
3461
|
});
|
|
3462
|
+
app.get("/api/v1/calendar/overview", async (request) => {
|
|
3463
|
+
const query = calendarOverviewQuerySchema.parse(request.query ?? {});
|
|
3464
|
+
const now = new Date();
|
|
3465
|
+
const from = query.from ??
|
|
3466
|
+
new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
3467
|
+
const to = query.to ??
|
|
3468
|
+
new Date(now.getTime() + 21 * 24 * 60 * 60 * 1000).toISOString();
|
|
3469
|
+
return { calendar: readCalendarOverview({ from, to }) };
|
|
3470
|
+
});
|
|
3471
|
+
app.get("/api/v1/calendar/agenda", async (request) => {
|
|
3472
|
+
const query = calendarOverviewQuerySchema.parse(request.query ?? {});
|
|
3473
|
+
const now = new Date();
|
|
3474
|
+
const from = query.from ??
|
|
3475
|
+
new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
3476
|
+
const to = query.to ??
|
|
3477
|
+
new Date(now.getTime() + 21 * 24 * 60 * 60 * 1000).toISOString();
|
|
3478
|
+
return {
|
|
3479
|
+
providers: listCalendarProviderMetadata(),
|
|
3480
|
+
calendars: listCalendars(),
|
|
3481
|
+
events: listCalendarEvents({ from, to }),
|
|
3482
|
+
workBlocks: listWorkBlockInstances({ from, to }),
|
|
3483
|
+
timeboxes: listTaskTimeboxes({ from, to })
|
|
3484
|
+
};
|
|
3485
|
+
});
|
|
3486
|
+
app.get("/api/v1/calendar/connections", async () => ({
|
|
3487
|
+
providers: listCalendarProviderMetadata(),
|
|
3488
|
+
connections: listConnectedCalendarConnections()
|
|
3489
|
+
}));
|
|
3490
|
+
app.post("/api/v1/calendar/oauth/microsoft/start", async (request) => {
|
|
3491
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
3492
|
+
route: "/api/v1/calendar/oauth/microsoft/start"
|
|
3493
|
+
});
|
|
3494
|
+
return await startMicrosoftCalendarOauth(startMicrosoftCalendarOauthSchema.parse(request.body ?? {}), getRequestOrigin(request));
|
|
3495
|
+
});
|
|
3496
|
+
app.post("/api/v1/calendar/oauth/microsoft/test-config", async (request) => {
|
|
3497
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
3498
|
+
route: "/api/v1/calendar/oauth/microsoft/test-config"
|
|
3499
|
+
});
|
|
3500
|
+
return {
|
|
3501
|
+
result: await testMicrosoftCalendarOauthConfiguration(testMicrosoftCalendarOauthConfigurationSchema.parse(request.body ?? {}))
|
|
3502
|
+
};
|
|
3503
|
+
});
|
|
3504
|
+
app.get("/api/v1/calendar/oauth/microsoft/session/:id", async (request, reply) => {
|
|
3505
|
+
requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/oauth/microsoft/session/:id" });
|
|
3506
|
+
try {
|
|
3507
|
+
return getMicrosoftCalendarOauthSession(request.params.id);
|
|
3508
|
+
}
|
|
3509
|
+
catch (error) {
|
|
3510
|
+
if (error instanceof Error &&
|
|
3511
|
+
error.message.startsWith("Unknown Microsoft calendar auth session")) {
|
|
3512
|
+
reply.code(404);
|
|
3513
|
+
return { error: "Microsoft calendar auth session not found" };
|
|
3514
|
+
}
|
|
3515
|
+
throw error;
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
3518
|
+
app.get("/api/v1/calendar/oauth/microsoft/callback", async (request, reply) => {
|
|
3519
|
+
const query = request.query;
|
|
3520
|
+
const result = await completeMicrosoftCalendarOauth({
|
|
3521
|
+
state: query.state ?? null,
|
|
3522
|
+
code: query.code ?? null,
|
|
3523
|
+
error: query.error ?? null,
|
|
3524
|
+
errorDescription: query.error_description ?? null
|
|
3525
|
+
});
|
|
3526
|
+
const session = result.session;
|
|
3527
|
+
const escapedOrigin = JSON.stringify(result.openerOrigin || "*");
|
|
3528
|
+
const escapedMessage = JSON.stringify({
|
|
3529
|
+
type: "forge:microsoft-calendar-auth",
|
|
3530
|
+
sessionId: session.sessionId,
|
|
3531
|
+
status: session.status
|
|
3532
|
+
});
|
|
3533
|
+
const body = `<!doctype html>
|
|
3534
|
+
<html lang="en">
|
|
3535
|
+
<head>
|
|
3536
|
+
<meta charset="utf-8" />
|
|
3537
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3538
|
+
<title>Forge Microsoft sign-in</title>
|
|
3539
|
+
<style>
|
|
3540
|
+
body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;background:#0b1320;color:#f8fafc;display:grid;place-items:center;min-height:100vh}
|
|
3541
|
+
main{max-width:28rem;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))}
|
|
3542
|
+
h1{margin:0 0 .75rem;font-size:1.15rem}
|
|
3543
|
+
p{margin:0;color:rgba(248,250,252,.72);line-height:1.6}
|
|
3544
|
+
</style>
|
|
3545
|
+
</head>
|
|
3546
|
+
<body>
|
|
3547
|
+
<main>
|
|
3548
|
+
<h1>${session.status === "authorized" ? "Microsoft account connected" : "Microsoft sign-in needs attention"}</h1>
|
|
3549
|
+
<p>${session.status === "authorized" ? "Forge received your Microsoft account and sent the result back to the calendar setup flow. You can close this window." : (session.error ?? "Forge could not complete Microsoft sign-in. You can close this window and try again from Settings.")}</p>
|
|
3550
|
+
</main>
|
|
3551
|
+
<script>
|
|
3552
|
+
const message = ${escapedMessage};
|
|
3553
|
+
const targetOrigin = ${escapedOrigin};
|
|
3554
|
+
try {
|
|
3555
|
+
if (window.opener && !window.opener.closed) {
|
|
3556
|
+
window.opener.postMessage(message, targetOrigin);
|
|
3557
|
+
}
|
|
3558
|
+
} catch {}
|
|
3559
|
+
setTimeout(() => window.close(), 180);
|
|
3560
|
+
</script>
|
|
3561
|
+
</body>
|
|
3562
|
+
</html>`;
|
|
3563
|
+
reply.type("text/html; charset=utf-8");
|
|
3564
|
+
return body;
|
|
3565
|
+
});
|
|
3566
|
+
app.post("/api/v1/calendar/discovery", async (request) => {
|
|
3567
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/discovery" });
|
|
3568
|
+
const discovery = await discoverCalendarConnection(discoverCalendarConnectionSchema.parse(request.body ?? {}));
|
|
3569
|
+
recordActivityEvent({
|
|
3570
|
+
entityType: "calendar_connection",
|
|
3571
|
+
entityId: "calendar_discovery",
|
|
3572
|
+
eventType: "calendar_connection_discovered",
|
|
3573
|
+
title: `Calendar discovery completed for ${discovery.provider}`,
|
|
3574
|
+
description: "Forge discovered provider calendars before connection setup.",
|
|
3575
|
+
actor: auth.actor ?? null,
|
|
3576
|
+
source: auth.source,
|
|
3577
|
+
metadata: {
|
|
3578
|
+
provider: discovery.provider,
|
|
3579
|
+
calendars: discovery.calendars.length
|
|
3580
|
+
}
|
|
3581
|
+
});
|
|
3582
|
+
return { discovery };
|
|
3583
|
+
});
|
|
3584
|
+
app.get("/api/v1/calendar/calendars", async () => ({
|
|
3585
|
+
calendars: listCalendars()
|
|
3586
|
+
}));
|
|
3587
|
+
app.get("/api/v1/calendar/connections/:id/discovery", async (request, reply) => {
|
|
3588
|
+
requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/connections/:id/discovery" });
|
|
3589
|
+
const { id } = request.params;
|
|
3590
|
+
try {
|
|
3591
|
+
const discovery = await discoverExistingCalendarConnection(id, managers.secrets);
|
|
3592
|
+
return { discovery };
|
|
3593
|
+
}
|
|
3594
|
+
catch (error) {
|
|
3595
|
+
if (error instanceof Error &&
|
|
3596
|
+
error.message.startsWith("Unknown calendar connection")) {
|
|
3597
|
+
reply.code(404);
|
|
3598
|
+
return { error: "Calendar connection not found" };
|
|
3599
|
+
}
|
|
3600
|
+
throw error;
|
|
3601
|
+
}
|
|
3602
|
+
});
|
|
1792
3603
|
app.get("/api/v1/habits", async (request) => {
|
|
1793
3604
|
const query = habitListQuerySchema.parse(request.query ?? {});
|
|
1794
3605
|
return { habits: listHabits(query) };
|
|
@@ -1835,7 +3646,9 @@ export async function buildServer(options = {}) {
|
|
|
1835
3646
|
return { activity: listActivityEvents(query) };
|
|
1836
3647
|
});
|
|
1837
3648
|
app.post("/api/v1/activity/:id/remove", async (request, reply) => {
|
|
1838
|
-
requireScopedAccess(request.headers, ["write"], {
|
|
3649
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
3650
|
+
route: "/api/v1/activity/:id/remove"
|
|
3651
|
+
});
|
|
1839
3652
|
const { id } = request.params;
|
|
1840
3653
|
const event = removeActivityEvent(id, removeActivityEventSchema.parse(request.body ?? {}), parseActivityContext(request.headers));
|
|
1841
3654
|
if (!event) {
|
|
@@ -1859,7 +3672,10 @@ export async function buildServer(options = {}) {
|
|
|
1859
3672
|
route: "/api/v1/insights",
|
|
1860
3673
|
entityType: input.entityType
|
|
1861
3674
|
});
|
|
1862
|
-
const insight = createInsight(input, {
|
|
3675
|
+
const insight = createInsight(input, {
|
|
3676
|
+
actor: auth.actor,
|
|
3677
|
+
source: auth.source
|
|
3678
|
+
});
|
|
1863
3679
|
reply.code(201);
|
|
1864
3680
|
return { insight };
|
|
1865
3681
|
});
|
|
@@ -1896,7 +3712,10 @@ export async function buildServer(options = {}) {
|
|
|
1896
3712
|
const query = entityDeleteQuerySchema.parse(request.query ?? {});
|
|
1897
3713
|
const insight = query.mode === "hard"
|
|
1898
3714
|
? deleteInsight(id, { actor: auth.actor, source: auth.source })
|
|
1899
|
-
: deleteEntity("insight", id, query, {
|
|
3715
|
+
: deleteEntity("insight", id, query, {
|
|
3716
|
+
actor: auth.actor,
|
|
3717
|
+
source: auth.source
|
|
3718
|
+
});
|
|
1900
3719
|
if (!insight) {
|
|
1901
3720
|
reply.code(404);
|
|
1902
3721
|
return { error: "Insight not found" };
|
|
@@ -1918,7 +3737,9 @@ export async function buildServer(options = {}) {
|
|
|
1918
3737
|
return { feedback };
|
|
1919
3738
|
});
|
|
1920
3739
|
app.get("/api/v1/approval-requests", async (request) => {
|
|
1921
|
-
requireOperatorSession(request.headers, {
|
|
3740
|
+
requireOperatorSession(request.headers, {
|
|
3741
|
+
route: "/api/v1/approval-requests"
|
|
3742
|
+
});
|
|
1922
3743
|
const query = request.query;
|
|
1923
3744
|
return { approvalRequests: listApprovalRequests(query?.status) };
|
|
1924
3745
|
});
|
|
@@ -1926,7 +3747,9 @@ export async function buildServer(options = {}) {
|
|
|
1926
3747
|
const context = requireOperatorSession(request.headers, { route: "/api/v1/approval-requests/:id/approve" });
|
|
1927
3748
|
const { id } = request.params;
|
|
1928
3749
|
const body = resolveApprovalRequestSchema.parse(request.body ?? {});
|
|
1929
|
-
const approvalRequest = approveApprovalRequest(id, body.note, body.actor ??
|
|
3750
|
+
const approvalRequest = approveApprovalRequest(id, body.note, body.actor ??
|
|
3751
|
+
context.actor ??
|
|
3752
|
+
parseOptionalActorHeader(request.headers));
|
|
1930
3753
|
if (!approvalRequest) {
|
|
1931
3754
|
reply.code(404);
|
|
1932
3755
|
return { error: "Approval request not found" };
|
|
@@ -1937,7 +3760,9 @@ export async function buildServer(options = {}) {
|
|
|
1937
3760
|
const context = requireOperatorSession(request.headers, { route: "/api/v1/approval-requests/:id/reject" });
|
|
1938
3761
|
const { id } = request.params;
|
|
1939
3762
|
const body = resolveApprovalRequestSchema.parse(request.body ?? {});
|
|
1940
|
-
const approvalRequest = rejectApprovalRequest(id, body.note, body.actor ??
|
|
3763
|
+
const approvalRequest = rejectApprovalRequest(id, body.note, body.actor ??
|
|
3764
|
+
context.actor ??
|
|
3765
|
+
parseOptionalActorHeader(request.headers));
|
|
1941
3766
|
if (!approvalRequest) {
|
|
1942
3767
|
reply.code(404);
|
|
1943
3768
|
return { error: "Approval request not found" };
|
|
@@ -1958,16 +3783,24 @@ export async function buildServer(options = {}) {
|
|
|
1958
3783
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/agent-actions" });
|
|
1959
3784
|
const input = createAgentActionSchema.parse(request.body ?? {});
|
|
1960
3785
|
const idempotencyKey = parseIdempotencyKey(request.headers);
|
|
1961
|
-
const result = createAgentAction(input, {
|
|
3786
|
+
const result = createAgentAction(input, {
|
|
3787
|
+
actor: auth.actor,
|
|
3788
|
+
source: auth.source,
|
|
3789
|
+
token: auth.token ? managers.token.getTokenById(auth.token.id) : null
|
|
3790
|
+
}, idempotencyKey);
|
|
1962
3791
|
reply.code(result.approvalRequest ? 202 : 201);
|
|
1963
3792
|
return result;
|
|
1964
3793
|
});
|
|
1965
3794
|
app.get("/api/v1/rewards/rules", async (request) => {
|
|
1966
|
-
requireOperatorSession(request.headers, {
|
|
3795
|
+
requireOperatorSession(request.headers, {
|
|
3796
|
+
route: "/api/v1/rewards/rules"
|
|
3797
|
+
});
|
|
1967
3798
|
return { rules: listRewardRules() };
|
|
1968
3799
|
});
|
|
1969
3800
|
app.get("/api/v1/rewards/rules/:id", async (request, reply) => {
|
|
1970
|
-
requireOperatorSession(request.headers, {
|
|
3801
|
+
requireOperatorSession(request.headers, {
|
|
3802
|
+
route: "/api/v1/rewards/rules/:id"
|
|
3803
|
+
});
|
|
1971
3804
|
const { id } = request.params;
|
|
1972
3805
|
const rule = getRewardRuleById(id);
|
|
1973
3806
|
if (!rule) {
|
|
@@ -1987,7 +3820,9 @@ export async function buildServer(options = {}) {
|
|
|
1987
3820
|
return { rule };
|
|
1988
3821
|
});
|
|
1989
3822
|
app.get("/api/v1/rewards/ledger", async (request) => {
|
|
1990
|
-
requireOperatorSession(request.headers, {
|
|
3823
|
+
requireOperatorSession(request.headers, {
|
|
3824
|
+
route: "/api/v1/rewards/ledger"
|
|
3825
|
+
});
|
|
1991
3826
|
const query = rewardsLedgerQuerySchema.parse(request.query ?? {});
|
|
1992
3827
|
return { ledger: listRewardLedger(query) };
|
|
1993
3828
|
});
|
|
@@ -2000,7 +3835,10 @@ export async function buildServer(options = {}) {
|
|
|
2000
3835
|
app.post("/api/v1/session-events", async (request, reply) => {
|
|
2001
3836
|
const auth = requireAuthenticatedActor(request.headers, { route: "/api/v1/session-events" });
|
|
2002
3837
|
const payload = createSessionEventSchema.parse(request.body ?? {});
|
|
2003
|
-
const event = recordSessionEvent(payload, {
|
|
3838
|
+
const event = recordSessionEvent(payload, {
|
|
3839
|
+
actor: auth.actor,
|
|
3840
|
+
source: auth.source
|
|
3841
|
+
});
|
|
2004
3842
|
reply.code(201);
|
|
2005
3843
|
return event;
|
|
2006
3844
|
});
|
|
@@ -2011,6 +3849,27 @@ export async function buildServer(options = {}) {
|
|
|
2011
3849
|
app.get("/api/v1/reviews/weekly", async () => ({
|
|
2012
3850
|
review: getWeeklyReviewPayload()
|
|
2013
3851
|
}));
|
|
3852
|
+
app.post("/api/v1/reviews/weekly/finalize", async (request, reply) => {
|
|
3853
|
+
const auth = requireAuthenticatedActor(request.headers, { route: "/api/v1/reviews/weekly/finalize" });
|
|
3854
|
+
const currentReview = getWeeklyReviewPayload();
|
|
3855
|
+
const finalized = finalizeWeeklyReviewClosure({
|
|
3856
|
+
weekKey: currentReview.weekKey,
|
|
3857
|
+
weekStartDate: currentReview.weekStartDate,
|
|
3858
|
+
weekEndDate: currentReview.weekEndDate,
|
|
3859
|
+
windowLabel: currentReview.windowLabel,
|
|
3860
|
+
rewardXp: currentReview.reward.rewardXp,
|
|
3861
|
+
actor: auth.actor,
|
|
3862
|
+
source: auth.source
|
|
3863
|
+
});
|
|
3864
|
+
const result = finalizeWeeklyReviewResultSchema.parse({
|
|
3865
|
+
closure: finalized.closure,
|
|
3866
|
+
reward: finalized.reward,
|
|
3867
|
+
review: getWeeklyReviewPayload(),
|
|
3868
|
+
metrics: buildXpMetricsPayload()
|
|
3869
|
+
});
|
|
3870
|
+
reply.code(finalized.created ? 201 : 200);
|
|
3871
|
+
return result;
|
|
3872
|
+
});
|
|
2014
3873
|
app.get("/api/v1/settings", async (request) => {
|
|
2015
3874
|
requireScopedAccess(request.headers, ["read", "write"], { route: "/api/v1/settings" });
|
|
2016
3875
|
return { settings: getSettings() };
|
|
@@ -2025,6 +3884,295 @@ export async function buildServer(options = {}) {
|
|
|
2025
3884
|
reply.code(201);
|
|
2026
3885
|
return { project };
|
|
2027
3886
|
});
|
|
3887
|
+
app.post("/api/v1/calendar/connections", async (request, reply) => {
|
|
3888
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/connections" });
|
|
3889
|
+
try {
|
|
3890
|
+
const connection = await createCalendarConnection(createCalendarConnectionSchema.parse(request.body ?? {}), managers.secrets, toActivityContext(auth));
|
|
3891
|
+
reply.code(201);
|
|
3892
|
+
return { connection };
|
|
3893
|
+
}
|
|
3894
|
+
catch (error) {
|
|
3895
|
+
if (error instanceof CalendarConnectionConflictError) {
|
|
3896
|
+
reply.code(409);
|
|
3897
|
+
return {
|
|
3898
|
+
code: "calendar_connection_duplicate",
|
|
3899
|
+
error: error.message,
|
|
3900
|
+
existingConnectionId: error.connectionId
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3903
|
+
throw error;
|
|
3904
|
+
}
|
|
3905
|
+
});
|
|
3906
|
+
app.patch("/api/v1/calendar/connections/:id", async (request, reply) => {
|
|
3907
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/connections/:id" });
|
|
3908
|
+
const { id } = request.params;
|
|
3909
|
+
const patch = updateCalendarConnectionSchema.parse(request.body ?? {});
|
|
3910
|
+
try {
|
|
3911
|
+
const connection = patch.label !== undefined || patch.selectedCalendarUrls !== undefined
|
|
3912
|
+
? await updateCalendarConnectionSelection(id, {
|
|
3913
|
+
label: patch.label,
|
|
3914
|
+
selectedCalendarUrls: patch.selectedCalendarUrls
|
|
3915
|
+
}, managers.secrets, toActivityContext(auth))
|
|
3916
|
+
: getCalendarConnectionById(id);
|
|
3917
|
+
if (!connection) {
|
|
3918
|
+
reply.code(404);
|
|
3919
|
+
return { error: "Calendar connection not found" };
|
|
3920
|
+
}
|
|
3921
|
+
return {
|
|
3922
|
+
connection: listConnectedCalendarConnections().find((entry) => entry.id === id)
|
|
3923
|
+
};
|
|
3924
|
+
}
|
|
3925
|
+
catch (error) {
|
|
3926
|
+
if (error instanceof Error &&
|
|
3927
|
+
error.message.startsWith("Unknown calendar connection")) {
|
|
3928
|
+
reply.code(404);
|
|
3929
|
+
return { error: "Calendar connection not found" };
|
|
3930
|
+
}
|
|
3931
|
+
throw error;
|
|
3932
|
+
}
|
|
3933
|
+
});
|
|
3934
|
+
app.post("/api/v1/calendar/connections/:id/sync", async (request, reply) => {
|
|
3935
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/connections/:id/sync" });
|
|
3936
|
+
const { id } = request.params;
|
|
3937
|
+
const connection = await syncCalendarConnection(id, managers.secrets, toActivityContext(auth));
|
|
3938
|
+
if (!connection) {
|
|
3939
|
+
reply.code(404);
|
|
3940
|
+
return { error: "Calendar connection not found" };
|
|
3941
|
+
}
|
|
3942
|
+
return {
|
|
3943
|
+
connection: listConnectedCalendarConnections().find((entry) => entry.id === id)
|
|
3944
|
+
};
|
|
3945
|
+
});
|
|
3946
|
+
app.delete("/api/v1/calendar/connections/:id", async (request, reply) => {
|
|
3947
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/connections/:id" });
|
|
3948
|
+
const { id } = request.params;
|
|
3949
|
+
const connection = await removeCalendarConnection(id, managers.secrets, toActivityContext(auth));
|
|
3950
|
+
if (!connection) {
|
|
3951
|
+
reply.code(404);
|
|
3952
|
+
return { error: "Calendar connection not found" };
|
|
3953
|
+
}
|
|
3954
|
+
return { connection };
|
|
3955
|
+
});
|
|
3956
|
+
app.get("/api/v1/calendar/work-block-templates", async () => ({
|
|
3957
|
+
templates: listWorkBlockTemplates()
|
|
3958
|
+
}));
|
|
3959
|
+
app.post("/api/v1/calendar/work-block-templates", async (request, reply) => {
|
|
3960
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/work-block-templates" });
|
|
3961
|
+
const template = createWorkBlockTemplate(createWorkBlockTemplateSchema.parse(request.body ?? {}));
|
|
3962
|
+
recordActivityEvent({
|
|
3963
|
+
entityType: "work_block",
|
|
3964
|
+
entityId: template.id,
|
|
3965
|
+
eventType: "work_block_created",
|
|
3966
|
+
title: `Work block created: ${template.title}`,
|
|
3967
|
+
description: "A recurring work block was added to Forge.",
|
|
3968
|
+
actor: auth.actor ?? null,
|
|
3969
|
+
source: auth.source,
|
|
3970
|
+
metadata: {
|
|
3971
|
+
kind: template.kind,
|
|
3972
|
+
blockingState: template.blockingState
|
|
3973
|
+
}
|
|
3974
|
+
});
|
|
3975
|
+
reply.code(201);
|
|
3976
|
+
return { template };
|
|
3977
|
+
});
|
|
3978
|
+
app.patch("/api/v1/calendar/work-block-templates/:id", async (request, reply) => {
|
|
3979
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/work-block-templates/:id" });
|
|
3980
|
+
const { id } = request.params;
|
|
3981
|
+
const template = updateWorkBlockTemplate(id, updateWorkBlockTemplateSchema.parse(request.body ?? {}));
|
|
3982
|
+
if (!template) {
|
|
3983
|
+
reply.code(404);
|
|
3984
|
+
return { error: "Work block template not found" };
|
|
3985
|
+
}
|
|
3986
|
+
recordActivityEvent({
|
|
3987
|
+
entityType: "work_block",
|
|
3988
|
+
entityId: template.id,
|
|
3989
|
+
eventType: "work_block_updated",
|
|
3990
|
+
title: `Work block updated: ${template.title}`,
|
|
3991
|
+
description: "The recurring work block was updated.",
|
|
3992
|
+
actor: auth.actor ?? null,
|
|
3993
|
+
source: auth.source,
|
|
3994
|
+
metadata: {
|
|
3995
|
+
kind: template.kind,
|
|
3996
|
+
blockingState: template.blockingState
|
|
3997
|
+
}
|
|
3998
|
+
});
|
|
3999
|
+
return { template };
|
|
4000
|
+
});
|
|
4001
|
+
app.delete("/api/v1/calendar/work-block-templates/:id", async (request, reply) => {
|
|
4002
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/work-block-templates/:id" });
|
|
4003
|
+
const { id } = request.params;
|
|
4004
|
+
const template = deleteWorkBlockTemplate(id);
|
|
4005
|
+
if (!template) {
|
|
4006
|
+
reply.code(404);
|
|
4007
|
+
return { error: "Work block template not found" };
|
|
4008
|
+
}
|
|
4009
|
+
recordActivityEvent({
|
|
4010
|
+
entityType: "work_block",
|
|
4011
|
+
entityId: template.id,
|
|
4012
|
+
eventType: "work_block_deleted",
|
|
4013
|
+
title: `Work block deleted: ${template.title}`,
|
|
4014
|
+
description: "The recurring work block was removed.",
|
|
4015
|
+
actor: auth.actor ?? null,
|
|
4016
|
+
source: auth.source,
|
|
4017
|
+
metadata: {
|
|
4018
|
+
kind: template.kind
|
|
4019
|
+
}
|
|
4020
|
+
});
|
|
4021
|
+
return { template };
|
|
4022
|
+
});
|
|
4023
|
+
app.get("/api/v1/calendar/timeboxes", async (request) => {
|
|
4024
|
+
const query = calendarOverviewQuerySchema.parse(request.query ?? {});
|
|
4025
|
+
const now = new Date();
|
|
4026
|
+
const from = query.from ??
|
|
4027
|
+
new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
4028
|
+
const to = query.to ??
|
|
4029
|
+
new Date(now.getTime() + 21 * 24 * 60 * 60 * 1000).toISOString();
|
|
4030
|
+
return { timeboxes: listTaskTimeboxes({ from, to }) };
|
|
4031
|
+
});
|
|
4032
|
+
app.post("/api/v1/calendar/timeboxes", async (request, reply) => {
|
|
4033
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/timeboxes" });
|
|
4034
|
+
const timebox = createTaskTimebox(createTaskTimeboxSchema.parse(request.body ?? {}));
|
|
4035
|
+
recordActivityEvent({
|
|
4036
|
+
entityType: "task_timebox",
|
|
4037
|
+
entityId: timebox.id,
|
|
4038
|
+
eventType: "task_timebox_created",
|
|
4039
|
+
title: `Task timebox created: ${timebox.title}`,
|
|
4040
|
+
description: "A future work slot was planned in Forge.",
|
|
4041
|
+
actor: auth.actor ?? null,
|
|
4042
|
+
source: auth.source,
|
|
4043
|
+
metadata: {
|
|
4044
|
+
taskId: timebox.taskId,
|
|
4045
|
+
status: timebox.status
|
|
4046
|
+
}
|
|
4047
|
+
});
|
|
4048
|
+
reply.code(201);
|
|
4049
|
+
return { timebox };
|
|
4050
|
+
});
|
|
4051
|
+
app.patch("/api/v1/calendar/timeboxes/:id", async (request, reply) => {
|
|
4052
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/timeboxes/:id" });
|
|
4053
|
+
const { id } = request.params;
|
|
4054
|
+
const timebox = updateTaskTimebox(id, updateTaskTimeboxSchema.parse(request.body ?? {}));
|
|
4055
|
+
if (!timebox) {
|
|
4056
|
+
reply.code(404);
|
|
4057
|
+
return { error: "Task timebox not found" };
|
|
4058
|
+
}
|
|
4059
|
+
recordActivityEvent({
|
|
4060
|
+
entityType: "task_timebox",
|
|
4061
|
+
entityId: timebox.id,
|
|
4062
|
+
eventType: "task_timebox_updated",
|
|
4063
|
+
title: `Task timebox updated: ${timebox.title}`,
|
|
4064
|
+
description: "The planned work slot was updated.",
|
|
4065
|
+
actor: auth.actor ?? null,
|
|
4066
|
+
source: auth.source,
|
|
4067
|
+
metadata: {
|
|
4068
|
+
taskId: timebox.taskId,
|
|
4069
|
+
status: timebox.status
|
|
4070
|
+
}
|
|
4071
|
+
});
|
|
4072
|
+
return { timebox };
|
|
4073
|
+
});
|
|
4074
|
+
app.delete("/api/v1/calendar/timeboxes/:id", async (request, reply) => {
|
|
4075
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/timeboxes/:id" });
|
|
4076
|
+
const { id } = request.params;
|
|
4077
|
+
const timebox = deleteTaskTimebox(id);
|
|
4078
|
+
if (!timebox) {
|
|
4079
|
+
reply.code(404);
|
|
4080
|
+
return { error: "Task timebox not found" };
|
|
4081
|
+
}
|
|
4082
|
+
recordActivityEvent({
|
|
4083
|
+
entityType: "task_timebox",
|
|
4084
|
+
entityId: timebox.id,
|
|
4085
|
+
eventType: "task_timebox_deleted",
|
|
4086
|
+
title: `Task timebox deleted: ${timebox.title}`,
|
|
4087
|
+
description: "The planned work slot was removed.",
|
|
4088
|
+
actor: auth.actor ?? null,
|
|
4089
|
+
source: auth.source,
|
|
4090
|
+
metadata: {
|
|
4091
|
+
taskId: timebox.taskId
|
|
4092
|
+
}
|
|
4093
|
+
});
|
|
4094
|
+
return { timebox };
|
|
4095
|
+
});
|
|
4096
|
+
app.post("/api/v1/calendar/timeboxes/recommend", async (request) => {
|
|
4097
|
+
const input = recommendTaskTimeboxesSchema.parse(request.body ?? {});
|
|
4098
|
+
return {
|
|
4099
|
+
timeboxes: suggestTaskTimeboxes(input.taskId, {
|
|
4100
|
+
from: input.from,
|
|
4101
|
+
to: input.to,
|
|
4102
|
+
limit: input.limit
|
|
4103
|
+
})
|
|
4104
|
+
};
|
|
4105
|
+
});
|
|
4106
|
+
app.post("/api/v1/calendar/events", async (request, reply) => {
|
|
4107
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/events" });
|
|
4108
|
+
const event = createCalendarEvent(createCalendarEventSchema.parse(request.body ?? {}));
|
|
4109
|
+
await pushCalendarEventUpdate(event.id, managers.secrets);
|
|
4110
|
+
const refreshed = getCalendarEventById(event.id);
|
|
4111
|
+
recordActivityEvent({
|
|
4112
|
+
entityType: "calendar_event",
|
|
4113
|
+
entityId: refreshed.id,
|
|
4114
|
+
eventType: "calendar_event_created",
|
|
4115
|
+
title: `Calendar event created: ${refreshed.title}`,
|
|
4116
|
+
description: "A native Forge calendar event was created.",
|
|
4117
|
+
actor: auth.actor ?? null,
|
|
4118
|
+
source: auth.source,
|
|
4119
|
+
metadata: {
|
|
4120
|
+
calendarId: refreshed.calendarId,
|
|
4121
|
+
originType: refreshed.originType
|
|
4122
|
+
}
|
|
4123
|
+
});
|
|
4124
|
+
reply.code(201);
|
|
4125
|
+
return { event: refreshed };
|
|
4126
|
+
});
|
|
4127
|
+
app.patch("/api/v1/calendar/events/:id", async (request, reply) => {
|
|
4128
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/events/:id" });
|
|
4129
|
+
const { id } = request.params;
|
|
4130
|
+
const event = updateCalendarEvent(id, updateCalendarEventSchema.parse(request.body ?? {}));
|
|
4131
|
+
if (!event) {
|
|
4132
|
+
reply.code(404);
|
|
4133
|
+
return { error: "Calendar event not found" };
|
|
4134
|
+
}
|
|
4135
|
+
await pushCalendarEventUpdate(id, managers.secrets);
|
|
4136
|
+
const refreshed = getCalendarEventById(id);
|
|
4137
|
+
recordActivityEvent({
|
|
4138
|
+
entityType: "calendar_event",
|
|
4139
|
+
entityId: refreshed.id,
|
|
4140
|
+
eventType: "calendar_event_updated",
|
|
4141
|
+
title: `Calendar event updated: ${refreshed.title}`,
|
|
4142
|
+
description: "The Forge calendar event was updated and projected to remote calendars when configured.",
|
|
4143
|
+
actor: auth.actor ?? null,
|
|
4144
|
+
source: auth.source,
|
|
4145
|
+
metadata: {
|
|
4146
|
+
calendarId: refreshed.calendarId,
|
|
4147
|
+
originType: refreshed.originType
|
|
4148
|
+
}
|
|
4149
|
+
});
|
|
4150
|
+
return { event: refreshed };
|
|
4151
|
+
});
|
|
4152
|
+
app.delete("/api/v1/calendar/events/:id", async (request, reply) => {
|
|
4153
|
+
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/calendar/events/:id" });
|
|
4154
|
+
const { id } = request.params;
|
|
4155
|
+
const event = deleteCalendarEvent(id);
|
|
4156
|
+
if (!event) {
|
|
4157
|
+
reply.code(404);
|
|
4158
|
+
return { error: "Calendar event not found" };
|
|
4159
|
+
}
|
|
4160
|
+
await deleteCalendarEventProjection(id, managers.secrets);
|
|
4161
|
+
recordActivityEvent({
|
|
4162
|
+
entityType: "calendar_event",
|
|
4163
|
+
entityId: event.id,
|
|
4164
|
+
eventType: "calendar_event_deleted",
|
|
4165
|
+
title: `Calendar event deleted: ${event.title}`,
|
|
4166
|
+
description: "The Forge calendar event was removed and any projected remote copies were deleted.",
|
|
4167
|
+
actor: auth.actor ?? null,
|
|
4168
|
+
source: auth.source,
|
|
4169
|
+
metadata: {
|
|
4170
|
+
calendarId: event.calendarId,
|
|
4171
|
+
originType: event.originType
|
|
4172
|
+
}
|
|
4173
|
+
});
|
|
4174
|
+
return { event };
|
|
4175
|
+
});
|
|
2028
4176
|
app.post("/api/v1/habits", async (request, reply) => {
|
|
2029
4177
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/habits" });
|
|
2030
4178
|
const habit = createHabit(createHabitSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
@@ -2263,8 +4411,10 @@ export async function buildServer(options = {}) {
|
|
|
2263
4411
|
const taskRuns = listTaskRuns({ taskId: id, limit: 10 });
|
|
2264
4412
|
return taskContextPayloadSchema.parse({
|
|
2265
4413
|
task,
|
|
2266
|
-
goal: task.goalId ? getGoalById(task.goalId) ?? null : null,
|
|
2267
|
-
project: task.projectId
|
|
4414
|
+
goal: task.goalId ? (getGoalById(task.goalId) ?? null) : null,
|
|
4415
|
+
project: task.projectId
|
|
4416
|
+
? (listProjectSummaries().find((project) => project.id === task.projectId) ?? null)
|
|
4417
|
+
: null,
|
|
2268
4418
|
activeTaskRun: taskRuns.find((run) => run.status === "active") ?? null,
|
|
2269
4419
|
taskRuns,
|
|
2270
4420
|
activity: listActivityEventsForTask(id, 20),
|
|
@@ -2281,8 +4431,10 @@ export async function buildServer(options = {}) {
|
|
|
2281
4431
|
const taskRuns = listTaskRuns({ taskId: id, limit: 10 });
|
|
2282
4432
|
return taskContextPayloadSchema.parse({
|
|
2283
4433
|
task,
|
|
2284
|
-
goal: task.goalId ? getGoalById(task.goalId) ?? null : null,
|
|
2285
|
-
project: task.projectId
|
|
4434
|
+
goal: task.goalId ? (getGoalById(task.goalId) ?? null) : null,
|
|
4435
|
+
project: task.projectId
|
|
4436
|
+
? (listProjectSummaries().find((project) => project.id === task.projectId) ?? null)
|
|
4437
|
+
: null,
|
|
2286
4438
|
activeTaskRun: taskRuns.find((run) => run.status === "active") ?? null,
|
|
2287
4439
|
taskRuns,
|
|
2288
4440
|
activity: listActivityEventsForTask(id, 20),
|
|
@@ -2458,7 +4610,9 @@ export async function buildServer(options = {}) {
|
|
|
2458
4610
|
const input = operatorLogWorkSchema.parse(request.body ?? {});
|
|
2459
4611
|
if (input.taskId) {
|
|
2460
4612
|
const task = updateTask(input.taskId, {
|
|
2461
|
-
title: input.title && input.title.trim().length > 0
|
|
4613
|
+
title: input.title && input.title.trim().length > 0
|
|
4614
|
+
? input.title
|
|
4615
|
+
: undefined,
|
|
2462
4616
|
description: typeof input.description === "string"
|
|
2463
4617
|
? input.description
|
|
2464
4618
|
: input.summary.trim().length > 0
|
|
@@ -2504,6 +4658,72 @@ export async function buildServer(options = {}) {
|
|
|
2504
4658
|
reply.code(201);
|
|
2505
4659
|
return { task, xp: buildXpMetricsPayload() };
|
|
2506
4660
|
});
|
|
4661
|
+
app.post("/api/v1/work-adjustments", async (request, reply) => {
|
|
4662
|
+
const auth = requireScopedAccess(request.headers, ["write", "rewards.manage"], { route: "/api/v1/work-adjustments" });
|
|
4663
|
+
const input = createWorkAdjustmentSchema.parse(request.body ?? {});
|
|
4664
|
+
const currentTarget = resolveWorkAdjustmentTarget(input.entityType, input.entityId);
|
|
4665
|
+
if (!currentTarget) {
|
|
4666
|
+
reply.code(404);
|
|
4667
|
+
return {
|
|
4668
|
+
error: `${input.entityType === "task" ? "Task" : "Project"} not found`
|
|
4669
|
+
};
|
|
4670
|
+
}
|
|
4671
|
+
const appliedDeltaMinutes = clampWorkAdjustmentMinutes(input.deltaMinutes, currentTarget.time.totalCreditedSeconds);
|
|
4672
|
+
const nextCreditedSeconds = Math.max(0, currentTarget.time.totalCreditedSeconds + appliedDeltaMinutes * 60);
|
|
4673
|
+
const result = runInTransaction(() => {
|
|
4674
|
+
const adjustment = createWorkAdjustment({
|
|
4675
|
+
...input,
|
|
4676
|
+
appliedDeltaMinutes
|
|
4677
|
+
}, toActivityContext(auth));
|
|
4678
|
+
const reward = recordWorkAdjustmentReward({
|
|
4679
|
+
entityType: input.entityType,
|
|
4680
|
+
entityId: input.entityId,
|
|
4681
|
+
targetTitle: currentTarget.title,
|
|
4682
|
+
actor: auth.actor ?? null,
|
|
4683
|
+
source: auth.source,
|
|
4684
|
+
requestedDeltaMinutes: input.deltaMinutes,
|
|
4685
|
+
appliedDeltaMinutes,
|
|
4686
|
+
previousCreditedSeconds: currentTarget.time.totalCreditedSeconds,
|
|
4687
|
+
nextCreditedSeconds,
|
|
4688
|
+
adjustmentId: adjustment.id
|
|
4689
|
+
});
|
|
4690
|
+
const copy = describeWorkAdjustment({
|
|
4691
|
+
entityType: input.entityType,
|
|
4692
|
+
targetTitle: currentTarget.title,
|
|
4693
|
+
requestedDeltaMinutes: input.deltaMinutes,
|
|
4694
|
+
appliedDeltaMinutes
|
|
4695
|
+
});
|
|
4696
|
+
recordActivityEvent({
|
|
4697
|
+
entityType: input.entityType,
|
|
4698
|
+
entityId: input.entityId,
|
|
4699
|
+
eventType: "work_adjusted",
|
|
4700
|
+
title: copy.title,
|
|
4701
|
+
description: copy.description,
|
|
4702
|
+
actor: auth.actor ?? null,
|
|
4703
|
+
source: auth.source,
|
|
4704
|
+
metadata: {
|
|
4705
|
+
adjustmentId: adjustment.id,
|
|
4706
|
+
requestedDeltaMinutes: input.deltaMinutes,
|
|
4707
|
+
appliedDeltaMinutes,
|
|
4708
|
+
rewardDeltaXp: reward?.deltaXp ?? 0,
|
|
4709
|
+
rewardId: reward?.id ?? null,
|
|
4710
|
+
note: input.note || null
|
|
4711
|
+
}
|
|
4712
|
+
});
|
|
4713
|
+
return { adjustment, reward };
|
|
4714
|
+
});
|
|
4715
|
+
const updatedTarget = resolveWorkAdjustmentTarget(input.entityType, input.entityId);
|
|
4716
|
+
if (!updatedTarget) {
|
|
4717
|
+
throw new HttpError(500, "work_adjustment_target_missing", `Could not reload ${input.entityType} ${input.entityId} after adjustment`);
|
|
4718
|
+
}
|
|
4719
|
+
reply.code(201);
|
|
4720
|
+
return workAdjustmentResultSchema.parse({
|
|
4721
|
+
adjustment: result.adjustment,
|
|
4722
|
+
target: updatedTarget,
|
|
4723
|
+
reward: result.reward,
|
|
4724
|
+
metrics: buildXpMetricsPayload()
|
|
4725
|
+
});
|
|
4726
|
+
});
|
|
2507
4727
|
app.post("/api/v1/tasks/:id/uncomplete", async (request, reply) => {
|
|
2508
4728
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/tasks/:id/uncomplete" });
|
|
2509
4729
|
const { id } = request.params;
|
|
@@ -2517,18 +4737,26 @@ export async function buildServer(options = {}) {
|
|
|
2517
4737
|
});
|
|
2518
4738
|
app.post("/api/v1/entities/create", async (request) => {
|
|
2519
4739
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/entities/create" });
|
|
2520
|
-
|
|
4740
|
+
const result = createEntities(batchCreateEntitiesSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4741
|
+
await applyBatchCalendarEntityEffects(result.results, auth, "create");
|
|
4742
|
+
return result;
|
|
2521
4743
|
});
|
|
2522
4744
|
app.post("/api/v1/entities/update", async (request) => {
|
|
2523
4745
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/entities/update" });
|
|
2524
|
-
|
|
4746
|
+
const result = updateEntities(batchUpdateEntitiesSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4747
|
+
await applyBatchCalendarEntityEffects(result.results, auth, "update");
|
|
4748
|
+
return result;
|
|
2525
4749
|
});
|
|
2526
4750
|
app.post("/api/v1/entities/delete", async (request) => {
|
|
2527
4751
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/entities/delete" });
|
|
2528
|
-
|
|
4752
|
+
const result = deleteEntities(batchDeleteEntitiesSchema.parse(request.body ?? {}), toActivityContext(auth));
|
|
4753
|
+
await applyBatchCalendarEntityEffects(result.results, auth, "delete");
|
|
4754
|
+
return result;
|
|
2529
4755
|
});
|
|
2530
4756
|
app.post("/api/v1/entities/restore", async (request) => {
|
|
2531
|
-
requireScopedAccess(request.headers, ["write"], {
|
|
4757
|
+
requireScopedAccess(request.headers, ["write"], {
|
|
4758
|
+
route: "/api/v1/entities/restore"
|
|
4759
|
+
});
|
|
2532
4760
|
return restoreEntities(batchRestoreEntitiesSchema.parse(request.body ?? {}));
|
|
2533
4761
|
});
|
|
2534
4762
|
app.post("/api/v1/entities/search", async (request) => {
|
|
@@ -2537,7 +4765,9 @@ export async function buildServer(options = {}) {
|
|
|
2537
4765
|
});
|
|
2538
4766
|
app.post("/api/task-runs/recover", async (request, reply) => {
|
|
2539
4767
|
markCompatibilityRoute(reply);
|
|
2540
|
-
const payload = taskRunListQuerySchema
|
|
4768
|
+
const payload = taskRunListQuerySchema
|
|
4769
|
+
.pick({ limit: true })
|
|
4770
|
+
.parse(request.body ?? {});
|
|
2541
4771
|
return { timedOutRuns: recoverTimedOutTaskRuns({ limit: payload.limit }) };
|
|
2542
4772
|
});
|
|
2543
4773
|
app.post("/api/task-runs/:id/heartbeat", async (request, reply) => {
|
|
@@ -2545,52 +4775,68 @@ export async function buildServer(options = {}) {
|
|
|
2545
4775
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/task-runs/:id/heartbeat" });
|
|
2546
4776
|
const { id } = request.params;
|
|
2547
4777
|
const input = taskRunHeartbeatSchema.parse(request.body ?? {});
|
|
2548
|
-
return {
|
|
4778
|
+
return {
|
|
4779
|
+
taskRun: heartbeatTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4780
|
+
};
|
|
2549
4781
|
});
|
|
2550
4782
|
app.post("/api/v1/task-runs/:id/heartbeat", async (request) => {
|
|
2551
4783
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/task-runs/:id/heartbeat" });
|
|
2552
4784
|
const { id } = request.params;
|
|
2553
4785
|
const input = taskRunHeartbeatSchema.parse(request.body ?? {});
|
|
2554
|
-
return {
|
|
4786
|
+
return {
|
|
4787
|
+
taskRun: heartbeatTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4788
|
+
};
|
|
2555
4789
|
});
|
|
2556
4790
|
app.post("/api/task-runs/:id/focus", async (request, reply) => {
|
|
2557
4791
|
markCompatibilityRoute(reply);
|
|
2558
4792
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/task-runs/:id/focus" });
|
|
2559
4793
|
const { id } = request.params;
|
|
2560
4794
|
const input = taskRunFocusSchema.parse(request.body ?? {});
|
|
2561
|
-
return {
|
|
4795
|
+
return {
|
|
4796
|
+
taskRun: focusTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4797
|
+
};
|
|
2562
4798
|
});
|
|
2563
4799
|
app.post("/api/v1/task-runs/:id/focus", async (request) => {
|
|
2564
4800
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/task-runs/:id/focus" });
|
|
2565
4801
|
const { id } = request.params;
|
|
2566
4802
|
const input = taskRunFocusSchema.parse(request.body ?? {});
|
|
2567
|
-
return {
|
|
4803
|
+
return {
|
|
4804
|
+
taskRun: focusTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4805
|
+
};
|
|
2568
4806
|
});
|
|
2569
4807
|
app.post("/api/task-runs/:id/complete", async (request, reply) => {
|
|
2570
4808
|
markCompatibilityRoute(reply);
|
|
2571
4809
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/task-runs/:id/complete" });
|
|
2572
4810
|
const { id } = request.params;
|
|
2573
4811
|
const input = taskRunFinishSchema.parse(request.body ?? {});
|
|
2574
|
-
return {
|
|
4812
|
+
return {
|
|
4813
|
+
taskRun: completeTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4814
|
+
};
|
|
2575
4815
|
});
|
|
2576
4816
|
app.post("/api/v1/task-runs/:id/complete", async (request) => {
|
|
2577
4817
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/task-runs/:id/complete" });
|
|
2578
4818
|
const { id } = request.params;
|
|
2579
4819
|
const input = taskRunFinishSchema.parse(request.body ?? {});
|
|
2580
|
-
return {
|
|
4820
|
+
return {
|
|
4821
|
+
taskRun: completeTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4822
|
+
};
|
|
2581
4823
|
});
|
|
2582
4824
|
app.post("/api/task-runs/:id/release", async (request, reply) => {
|
|
2583
4825
|
markCompatibilityRoute(reply);
|
|
2584
4826
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/task-runs/:id/release" });
|
|
2585
4827
|
const { id } = request.params;
|
|
2586
4828
|
const input = taskRunFinishSchema.parse(request.body ?? {});
|
|
2587
|
-
return {
|
|
4829
|
+
return {
|
|
4830
|
+
taskRun: releaseTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4831
|
+
};
|
|
2588
4832
|
});
|
|
2589
4833
|
app.post("/api/v1/task-runs/:id/release", async (request) => {
|
|
2590
4834
|
const auth = requireScopedAccess(request.headers, ["write"], { route: "/api/v1/task-runs/:id/release" });
|
|
2591
4835
|
const { id } = request.params;
|
|
2592
4836
|
const input = taskRunFinishSchema.parse(request.body ?? {});
|
|
2593
|
-
return {
|
|
4837
|
+
return {
|
|
4838
|
+
taskRun: releaseTaskRun(id, input, new Date(), toActivityContext(auth))
|
|
4839
|
+
};
|
|
2594
4840
|
});
|
|
2595
4841
|
app.post("/api/tags/suggestions", async (request, reply) => {
|
|
2596
4842
|
markCompatibilityRoute(reply);
|