forge-openclaw-plugin 0.2.7 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +81 -1
  2. package/dist/assets/{board-CzgvdLO8.js → board-C_m78kvK.js} +2 -2
  3. package/dist/assets/{board-CzgvdLO8.js.map → board-C_m78kvK.js.map} +1 -1
  4. package/dist/assets/index-BWtLtXwb.js +36 -0
  5. package/dist/assets/index-BWtLtXwb.js.map +1 -0
  6. package/dist/assets/index-Dp5GXY_z.css +1 -0
  7. package/dist/assets/{motion-STUd1O46.js → motion-CpZvZumD.js} +2 -2
  8. package/dist/assets/{motion-STUd1O46.js.map → motion-CpZvZumD.js.map} +1 -1
  9. package/dist/assets/{table-CtNlETLc.js → table-DtyXTw03.js} +2 -2
  10. package/dist/assets/{table-CtNlETLc.js.map → table-DtyXTw03.js.map} +1 -1
  11. package/dist/assets/{ui-ThzkR_oW.js → ui-BXbpiKyS.js} +2 -2
  12. package/dist/assets/{ui-ThzkR_oW.js.map → ui-BXbpiKyS.js.map} +1 -1
  13. package/dist/assets/{vendor-DyHAI6nk.js → vendor-QBH6qVEe.js} +84 -74
  14. package/dist/assets/vendor-QBH6qVEe.js.map +1 -0
  15. package/dist/assets/{viz-BJuBCz_G.js → viz-w-IMeueL.js} +2 -2
  16. package/dist/assets/{viz-BJuBCz_G.js.map → viz-w-IMeueL.js.map} +1 -1
  17. package/dist/index.html +8 -8
  18. package/dist/openclaw/api-client.d.ts +1 -0
  19. package/dist/openclaw/local-runtime.d.ts +8 -0
  20. package/dist/openclaw/local-runtime.js +123 -1
  21. package/dist/openclaw/plugin-entry-shared.js +12 -0
  22. package/dist/openclaw/routes.js +4 -0
  23. package/dist/server/app.js +104 -67
  24. package/dist/server/demo-data.js +49 -0
  25. package/dist/server/openapi.js +84 -43
  26. package/dist/server/psyche-types.js +1 -30
  27. package/dist/server/repositories/deleted-entities.js +60 -26
  28. package/dist/server/repositories/goals.js +2 -5
  29. package/dist/server/repositories/notes.js +359 -0
  30. package/dist/server/repositories/projects.js +2 -5
  31. package/dist/server/repositories/psyche.js +11 -14
  32. package/dist/server/repositories/task-runs.js +2 -0
  33. package/dist/server/repositories/tasks.js +21 -10
  34. package/dist/server/seed-demo.js +11 -0
  35. package/dist/server/services/dashboard.js +4 -1
  36. package/dist/server/services/entity-crud.js +27 -30
  37. package/dist/server/services/insights.js +5 -5
  38. package/dist/server/services/projects.js +3 -1
  39. package/dist/server/services/psyche.js +4 -4
  40. package/dist/server/types.js +70 -11
  41. package/openclaw.plugin.json +12 -1
  42. package/package.json +1 -1
  43. package/server/migrations/001_core.sql +78 -0
  44. package/server/migrations/002_psyche.sql +164 -13
  45. package/skills/forge-openclaw/SKILL.md +19 -5
  46. package/dist/assets/index-8d_oM8fL.js +0 -27
  47. package/dist/assets/index-8d_oM8fL.js.map +0 -1
  48. package/dist/assets/index-D4A_bq8m.css +0 -1
  49. package/dist/assets/vendor-DyHAI6nk.js.map +0 -1
  50. package/dist/server/repositories/comments.js +0 -176
  51. package/server/migrations/003_timer_execution.sql +0 -18
  52. package/server/migrations/004_psyche_linked_entities.sql +0 -5
  53. package/server/migrations/005_adaptive_schemas.sql +0 -157
  54. package/server/migrations/006_psyche_auth_setting.sql +0 -4
  55. package/server/migrations/007_deleted_entities.sql +0 -16
@@ -1,14 +1,14 @@
1
1
  import { getDatabase, runInTransaction } from "../db.js";
2
- import { deleteComment, getCommentById, listComments, updateComment, createComment } from "../repositories/comments.js";
3
2
  import { createInsight, deleteInsight, getInsightById, listInsights, updateInsight } from "../repositories/collaboration.js";
4
- import { createBehaviorPatternSchema, createBehaviorSchema, createBeliefEntrySchema, createCommentSchema, createEmotionDefinitionSchema, createEventTypeSchema, createModeGuideSessionSchema, createModeProfileSchema, createPsycheValueSchema, createTriggerReportSchema, updateBehaviorPatternSchema, updateBehaviorSchema, updateBeliefEntrySchema, updateCommentSchema, updateEmotionDefinitionSchema, updateEventTypeSchema, updateModeGuideSessionSchema, updateModeProfileSchema, updatePsycheValueSchema, updateTriggerReportSchema } from "../psyche-types.js";
3
+ import { createNote, deleteNote, getNoteById, listNotes, unlinkNotesForEntity, updateNote } from "../repositories/notes.js";
4
+ import { createBehaviorPatternSchema, createBehaviorSchema, createBeliefEntrySchema, createEmotionDefinitionSchema, createEventTypeSchema, createModeGuideSessionSchema, createModeProfileSchema, createPsycheValueSchema, createTriggerReportSchema, updateBehaviorPatternSchema, updateBehaviorSchema, updateBeliefEntrySchema, updateEmotionDefinitionSchema, updateEventTypeSchema, updateModeGuideSessionSchema, updateModeProfileSchema, updatePsycheValueSchema, updateTriggerReportSchema } from "../psyche-types.js";
5
5
  import { buildSettingsBinPayload, cascadeSoftDeleteAnchoredCollaboration, clearDeletedEntityRecord, getDeletedEntityRecord, listDeletedEntities, restoreAnchoredCollaboration, restoreDeletedEntityRecord, upsertDeletedEntityRecord } from "../repositories/deleted-entities.js";
6
6
  import { createGoal, deleteGoal, getGoalById, listGoals, updateGoal } from "../repositories/goals.js";
7
7
  import { createBehavior, createBehaviorPattern, createBeliefEntry, createEmotionDefinition, createEventType, createModeGuideSession, createModeProfile, createPsycheValue, createTriggerReport, deleteBehavior, deleteBehaviorPattern, deleteBeliefEntry, deleteEmotionDefinition, deleteEventType, deleteModeGuideSession, deleteModeProfile, deletePsycheValue, deleteTriggerReport, getBehaviorById, getBehaviorPatternById, getBeliefEntryById, getEmotionDefinitionById, getEventTypeById, getModeGuideSessionById, getModeProfileById, getPsycheValueById, getTriggerReportById, listBehaviors, listBehaviorPatterns, listBeliefEntries, listEmotionDefinitions, listEventTypes, listModeGuideSessions, listModeProfiles, listPsycheValues, listTriggerReports, updateBehavior, updateBehaviorPattern, updateBeliefEntry, updateEmotionDefinition, updateEventType, updateModeGuideSession, updateModeProfile, updatePsycheValue, updateTriggerReport } from "../repositories/psyche.js";
8
8
  import { createProject, deleteProject, getProjectById, listProjects, updateProject } from "../repositories/projects.js";
9
9
  import { createTag, deleteTag, getTagById, listTags, updateTag } from "../repositories/tags.js";
10
10
  import { createTask, deleteTask, getTaskById, listTasks, updateTask } from "../repositories/tasks.js";
11
- import { createGoalSchema, createInsightSchema, createProjectSchema, createTagSchema, createTaskSchema, updateGoalSchema, updateInsightSchema, updateProjectSchema, updateTagSchema, updateTaskSchema } from "../types.js";
11
+ import { createGoalSchema, createInsightSchema, createNoteSchema, createProjectSchema, createTagSchema, createTaskSchema, updateGoalSchema, updateInsightSchema, updateNoteSchema, updateProjectSchema, updateTagSchema, updateTaskSchema } from "../types.js";
12
12
  class AtomicBatchRollback extends Error {
13
13
  index;
14
14
  code;
@@ -58,14 +58,14 @@ const CRUD_ENTITY_CAPABILITIES = {
58
58
  update: (id, patch, context) => updateTag(id, patch, context),
59
59
  hardDelete: (id, context) => deleteTag(id, context)
60
60
  },
61
- comment: {
62
- entityType: "comment",
63
- routeBase: "/api/v1/comments",
64
- list: () => listComments(),
65
- get: (id) => getCommentById(id),
66
- create: (data, context) => createComment(data, context),
67
- update: (id, patch, context) => updateComment(id, patch, context),
68
- hardDelete: (id, context) => deleteComment(id, context)
61
+ note: {
62
+ entityType: "note",
63
+ routeBase: "/api/v1/notes",
64
+ list: () => listNotes(),
65
+ get: (id) => getNoteById(id),
66
+ create: (data, context) => createNote(data, context),
67
+ update: (id, patch, context) => updateNote(id, patch, context),
68
+ hardDelete: (id, context) => deleteNote(id, context)
69
69
  },
70
70
  insight: {
71
71
  entityType: "insight",
@@ -175,7 +175,7 @@ const CREATE_ENTITY_SCHEMAS = {
175
175
  project: createProjectSchema,
176
176
  task: createTaskSchema,
177
177
  tag: createTagSchema,
178
- comment: createCommentSchema,
178
+ note: createNoteSchema,
179
179
  insight: createInsightSchema,
180
180
  psyche_value: createPsycheValueSchema,
181
181
  behavior_pattern: createBehaviorPatternSchema,
@@ -192,7 +192,7 @@ const UPDATE_ENTITY_SCHEMAS = {
192
192
  project: updateProjectSchema,
193
193
  task: updateTaskSchema,
194
194
  tag: updateTagSchema,
195
- comment: updateCommentSchema,
195
+ note: updateNoteSchema,
196
196
  insight: updateInsightSchema,
197
197
  psyche_value: updatePsycheValueSchema,
198
198
  behavior_pattern: updateBehaviorPatternSchema,
@@ -312,7 +312,14 @@ function matchesLinkedTo(entityType, entity, linkedTo) {
312
312
  return linkedTo.entityType === "goal" && entity.goalId === linkedTo.id;
313
313
  case "task":
314
314
  return (linkedTo.entityType === "goal" && entity.goalId === linkedTo.id) || (linkedTo.entityType === "project" && entity.projectId === linkedTo.id);
315
- case "comment":
315
+ case "note":
316
+ return (Array.isArray(entity.links) &&
317
+ entity.links.some((link) => typeof link === "object" &&
318
+ link !== null &&
319
+ "entityType" in link &&
320
+ "entityId" in link &&
321
+ link.entityType === linkedTo.entityType &&
322
+ link.entityId === linkedTo.id));
316
323
  case "insight":
317
324
  return entity.entityType === linkedTo.entityType && entity.entityId === linkedTo.id;
318
325
  case "psyche_value":
@@ -378,18 +385,7 @@ function purgeAnchoredCollaboration(entityType, entityId) {
378
385
  .prepare(`DELETE FROM deleted_entities WHERE entity_type = 'insight' AND entity_id IN (${placeholders})`)
379
386
  .run(...insightIds.map((row) => row.id));
380
387
  }
381
- const commentIds = getDatabase()
382
- .prepare(`SELECT id FROM entity_comments WHERE entity_type = ? AND entity_id = ?`)
383
- .all(entityType, entityId);
384
- if (commentIds.length > 0) {
385
- const placeholders = commentIds.map(() => "?").join(", ");
386
- getDatabase()
387
- .prepare(`DELETE FROM entity_comments WHERE id IN (${placeholders})`)
388
- .run(...commentIds.map((row) => row.id));
389
- getDatabase()
390
- .prepare(`DELETE FROM deleted_entities WHERE entity_type = 'comment' AND entity_id IN (${placeholders})`)
391
- .run(...commentIds.map((row) => row.id));
392
- }
388
+ unlinkNotesForEntity(entityType, entityId, { source: "system", actor: null });
393
389
  }
394
390
  export function deleteEntity(entityType, id, options, context) {
395
391
  const capability = getCapability(entityType);
@@ -417,13 +413,13 @@ export function deleteEntity(entityType, id, options, context) {
417
413
  deleteReason: options.reason ?? "",
418
414
  context
419
415
  });
420
- if (entityType !== "comment" && entityType !== "insight") {
416
+ if (entityType !== "note" && entityType !== "insight") {
421
417
  cascadeSoftDeleteAnchoredCollaboration(entityType, id, context, options.reason ?? "");
422
418
  }
423
419
  return entity;
424
420
  }
425
421
  clearDeletedEntityRecord(entityType, id);
426
- if (entityType !== "comment" && entityType !== "insight") {
422
+ if (entityType !== "note" && entityType !== "insight") {
427
423
  purgeAnchoredCollaboration(entityType, id);
428
424
  }
429
425
  const deleted = capability.hardDelete(id, context);
@@ -437,7 +433,7 @@ export function restoreEntity(entityType, id) {
437
433
  if (!deleted) {
438
434
  return undefined;
439
435
  }
440
- if (entityType !== "comment" && entityType !== "insight") {
436
+ if (entityType !== "note" && entityType !== "insight") {
441
437
  restoreAnchoredCollaboration(entityType, id);
442
438
  }
443
439
  return getCapability(entityType).get(id) ?? deleted.snapshot;
@@ -539,9 +535,10 @@ export function restoreEntities(input) {
539
535
  }
540
536
  export function searchEntities(input) {
541
537
  const deleted = listDeletedEntities();
538
+ const defaultEntityTypes = Object.keys(CRUD_ENTITY_CAPABILITIES);
542
539
  return {
543
540
  results: input.searches.map((search) => {
544
- const entityTypes = search.entityTypes && search.entityTypes.length > 0 ? search.entityTypes : Object.keys(CRUD_ENTITY_CAPABILITIES);
541
+ const entityTypes = search.entityTypes && search.entityTypes.length > 0 ? search.entityTypes : defaultEntityTypes;
545
542
  const liveMatches = entityTypes.flatMap((entityType) => getCapability(entityType)
546
543
  .list()
547
544
  .filter((entity) => (search.ids && search.ids.length > 0 ? search.ids.includes(String(entity.id ?? "")) : true))
@@ -71,16 +71,16 @@ export function getInsightsPayload(now = new Date()) {
71
71
  executionTrends: trends,
72
72
  domainBalance,
73
73
  coaching: {
74
- title: hottestGoal ? `Protect ${hottestGoal.title}` : "Rebuild momentum",
74
+ title: hottestGoal ? `Protect progress on ${hottestGoal.title}` : "Rebuild momentum",
75
75
  summary: blockedTasks > 0
76
- ? `${blockedTasks} blocked task${blockedTasks === 1 ? "" : "s"} are leaking velocity out of the system.`
76
+ ? `${blockedTasks} blocked task${blockedTasks === 1 ? "" : "s"} are slowing active work across Forge right now.`
77
77
  : overdueTasks > 0
78
- ? `${overdueTasks} overdue task${overdueTasks === 1 ? "" : "s"} are the main drag vector right now.`
78
+ ? `${overdueTasks} overdue task${overdueTasks === 1 ? "" : "s"} are creating the biggest execution drag right now.`
79
79
  : "Recent evidence shows enough movement to push the next arc more aggressively.",
80
80
  recommendation: blockedTasks > 0
81
- ? "Clear one blocked task before adding more new work."
81
+ ? "Clear one blocked task before adding more new work so the active lane can move again."
82
82
  : hottestGoal
83
- ? `Queue the next decisive move for ${hottestGoal.title} and keep a deep-work lane active.`
83
+ ? `Create or schedule the next concrete move under ${hottestGoal.title}, then protect one deep-work lane to carry it forward this week.`
84
84
  : "Pick one life goal, one project, and one task to stabilize the next 24 hours.",
85
85
  ctaLabel: "Trigger coaching insight"
86
86
  },
@@ -1,5 +1,6 @@
1
1
  import { listActivityEvents } from "../repositories/activity-events.js";
2
2
  import { getGoalById, listGoals } from "../repositories/goals.js";
3
+ import { buildNotesSummaryByEntity } from "../repositories/notes.js";
3
4
  import { listProjects } from "../repositories/projects.js";
4
5
  import { listTasks } from "../repositories/tasks.js";
5
6
  import { emptyTaskTimeSummary } from "./work-time.js";
@@ -70,6 +71,7 @@ export function getProjectBoard(projectId) {
70
71
  project,
71
72
  goal,
72
73
  tasks: listTasks({ projectId }),
73
- activity: listActivityEvents({ entityType: "project", entityId: projectId, limit: 20 }).concat(listActivityEvents({ entityType: "task", limit: 100 }).filter((event) => listTasks({ projectId }).some((task) => task.id === event.entityId)).slice(0, 20))
74
+ activity: listActivityEvents({ entityType: "project", entityId: projectId, limit: 20 }).concat(listActivityEvents({ entityType: "task", limit: 100 }).filter((event) => listTasks({ projectId }).some((task) => task.id === event.entityId)).slice(0, 20)),
75
+ notesSummaryByEntity: buildNotesSummaryByEntity()
74
76
  });
75
77
  }
@@ -1,6 +1,6 @@
1
1
  import { getDomainBySlug } from "../repositories/domains.js";
2
- import { listComments } from "../repositories/comments.js";
3
2
  import { listInsights } from "../repositories/collaboration.js";
3
+ import { listNotes } from "../repositories/notes.js";
4
4
  import { listBehaviorPatterns, listBehaviors, listBeliefEntries, listModeProfiles, listPsycheValues, listSchemaCatalog, listTriggerReports } from "../repositories/psyche.js";
5
5
  import { psycheOverviewPayloadSchema } from "../psyche-types.js";
6
6
  const PSYCHE_ENTITY_TYPE_SET = new Set([
@@ -23,9 +23,9 @@ export function getPsycheOverview() {
23
23
  const modes = listModeProfiles();
24
24
  const reports = listTriggerReports(5);
25
25
  const schemaCatalog = listSchemaCatalog();
26
- const comments = listComments({ limit: 200 });
26
+ const notes = listNotes({ limit: 200 });
27
27
  const openInsights = listInsights({ limit: 100 }).filter((insight) => insight.entityType && PSYCHE_ENTITY_TYPE_SET.has(insight.entityType)).length;
28
- const unresolvedComments = comments.filter((comment) => PSYCHE_ENTITY_TYPE_SET.has(comment.entityType)).length;
28
+ const openNotes = notes.filter((note) => note.links.some((link) => PSYCHE_ENTITY_TYPE_SET.has(link.entityType))).length;
29
29
  const committedActions = [
30
30
  ...values.flatMap((value) => value.committedActions),
31
31
  ...behaviors.filter((behavior) => behavior.kind === "committed").map((behavior) => behavior.title),
@@ -57,7 +57,7 @@ export function getPsycheOverview() {
57
57
  reports,
58
58
  schemaPressure,
59
59
  openInsights,
60
- unresolvedComments,
60
+ openNotes,
61
61
  committedActions
62
62
  });
63
63
  }
@@ -21,8 +21,9 @@ export const activityEntityTypeSchema = z.enum([
21
21
  "behavior",
22
22
  "belief_entry",
23
23
  "mode_profile",
24
+ "mode_guide_session",
24
25
  "trigger_report",
25
- "comment",
26
+ "note",
26
27
  "event_type",
27
28
  "emotion_definition",
28
29
  "tag",
@@ -50,7 +51,7 @@ export const crudEntityTypeSchema = z.enum([
50
51
  "project",
51
52
  "task",
52
53
  "tag",
53
- "comment",
54
+ "note",
54
55
  "insight",
55
56
  "psyche_value",
56
57
  "behavior_pattern",
@@ -121,6 +122,27 @@ export const taskTimeSummarySchema = z.object({
121
122
  hasCurrentRun: z.boolean(),
122
123
  currentRunId: z.string().nullable()
123
124
  });
125
+ export const noteLinkSchema = z.object({
126
+ entityType: crudEntityTypeSchema,
127
+ entityId: nonEmptyTrimmedString,
128
+ anchorKey: trimmedString.nullable().default(null)
129
+ });
130
+ export const noteSchema = z.object({
131
+ id: z.string(),
132
+ contentMarkdown: nonEmptyTrimmedString,
133
+ contentPlain: trimmedString,
134
+ author: z.string().nullable(),
135
+ source: activitySourceSchema,
136
+ createdAt: z.string(),
137
+ updatedAt: z.string(),
138
+ links: z.array(noteLinkSchema).min(1)
139
+ });
140
+ export const noteSummarySchema = z.object({
141
+ count: z.number().int().nonnegative(),
142
+ latestNoteId: z.string().nullable(),
143
+ latestCreatedAt: z.string().nullable()
144
+ });
145
+ export const notesSummaryByEntitySchema = z.record(z.string(), noteSummarySchema);
124
146
  export const goalSchema = z.object({
125
147
  id: z.string(),
126
148
  title: nonEmptyTrimmedString,
@@ -299,7 +321,8 @@ export const dashboardPayloadSchema = z.object({
299
321
  gamification: gamificationProfileSchema,
300
322
  achievements: z.array(achievementSignalSchema),
301
323
  milestoneRewards: z.array(milestoneRewardSchema),
302
- recentActivity: z.array(activityEventSchema)
324
+ recentActivity: z.array(activityEventSchema),
325
+ notesSummaryByEntity: notesSummaryByEntitySchema.default({})
303
326
  });
304
327
  export const contextDomainBalanceSchema = z.object({
305
328
  tagId: z.string(),
@@ -379,13 +402,15 @@ export const taskContextPayloadSchema = z.object({
379
402
  project: projectSummarySchema.nullable(),
380
403
  activeTaskRun: taskRunSchema.nullable(),
381
404
  taskRuns: z.array(taskRunSchema),
382
- activity: z.array(activityEventSchema)
405
+ activity: z.array(activityEventSchema),
406
+ notesSummaryByEntity: notesSummaryByEntitySchema.default({})
383
407
  });
384
408
  export const projectBoardPayloadSchema = z.object({
385
409
  project: projectSummarySchema,
386
410
  goal: goalSchema,
387
411
  tasks: z.array(taskSchema),
388
- activity: z.array(activityEventSchema)
412
+ activity: z.array(activityEventSchema),
413
+ notesSummaryByEntity: notesSummaryByEntitySchema.default({})
389
414
  });
390
415
  export const insightsHeatmapCellSchema = z.object({
391
416
  id: z.string(),
@@ -697,6 +722,34 @@ export const settingsBinPayloadSchema = z.object({
697
722
  countsByEntityType: z.record(z.string(), z.number().int().nonnegative()),
698
723
  records: z.array(deletedEntityRecordSchema)
699
724
  });
725
+ export const createNoteLinkSchema = z.object({
726
+ entityType: crudEntityTypeSchema,
727
+ entityId: nonEmptyTrimmedString,
728
+ anchorKey: trimmedString.nullable().default(null)
729
+ });
730
+ export const createNoteSchema = z.object({
731
+ contentMarkdown: nonEmptyTrimmedString,
732
+ author: trimmedString.nullable().default(null),
733
+ links: z.array(createNoteLinkSchema).min(1)
734
+ });
735
+ export const nestedCreateNoteSchema = z.object({
736
+ contentMarkdown: nonEmptyTrimmedString,
737
+ author: trimmedString.nullable().default(null),
738
+ links: z.array(createNoteLinkSchema).default([])
739
+ });
740
+ export const updateNoteSchema = z.object({
741
+ contentMarkdown: nonEmptyTrimmedString.optional(),
742
+ author: trimmedString.nullable().optional(),
743
+ links: z.array(createNoteLinkSchema).min(1).optional()
744
+ });
745
+ export const notesListQuerySchema = z.object({
746
+ linkedEntityType: crudEntityTypeSchema.optional(),
747
+ linkedEntityId: nonEmptyTrimmedString.optional(),
748
+ anchorKey: trimmedString.nullable().optional(),
749
+ author: trimmedString.optional(),
750
+ query: trimmedString.optional(),
751
+ limit: z.coerce.number().int().positive().max(200).optional()
752
+ });
700
753
  export const taskListQuerySchema = z.object({
701
754
  status: taskStatusSchema.optional(),
702
755
  owner: nonEmptyTrimmedString.optional(),
@@ -731,7 +784,8 @@ export const createGoalSchema = z.object({
731
784
  status: goalStatusSchema.default("active"),
732
785
  targetPoints: z.number().int().min(25).max(10000).default(400),
733
786
  themeColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).default("#c8a46b"),
734
- tagIds: uniqueStringArraySchema.default([])
787
+ tagIds: uniqueStringArraySchema.default([]),
788
+ notes: z.array(nestedCreateNoteSchema).default([])
735
789
  });
736
790
  export const updateGoalSchema = createGoalSchema.partial();
737
791
  export const createTagSchema = z.object({
@@ -747,7 +801,8 @@ export const createProjectSchema = z.object({
747
801
  description: trimmedString.default(""),
748
802
  status: projectStatusSchema.default("active"),
749
803
  targetPoints: z.number().int().min(25).max(10000).default(240),
750
- themeColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).default("#c0c1ff")
804
+ themeColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).default("#c0c1ff"),
805
+ notes: z.array(nestedCreateNoteSchema).default([])
751
806
  });
752
807
  export const updateProjectSchema = createProjectSchema.partial();
753
808
  export const taskMutationShape = {
@@ -763,7 +818,8 @@ export const taskMutationShape = {
763
818
  energy: taskEnergySchema.default("steady"),
764
819
  points: z.number().int().min(5).max(500).default(40),
765
820
  sortOrder: z.number().int().nonnegative().optional(),
766
- tagIds: uniqueStringArraySchema.default([])
821
+ tagIds: uniqueStringArraySchema.default([]),
822
+ notes: z.array(nestedCreateNoteSchema).default([])
767
823
  };
768
824
  export const createTaskSchema = z.object(taskMutationShape);
769
825
  export const updateTaskSchema = z.object({
@@ -779,7 +835,8 @@ export const updateTaskSchema = z.object({
779
835
  energy: taskEnergySchema.optional(),
780
836
  points: z.number().int().min(5).max(500).optional(),
781
837
  sortOrder: z.number().int().nonnegative().optional(),
782
- tagIds: uniqueStringArraySchema.optional()
838
+ tagIds: uniqueStringArraySchema.optional(),
839
+ notes: z.array(nestedCreateNoteSchema).optional()
783
840
  });
784
841
  export const tagSuggestionRequestSchema = z.object({
785
842
  title: trimmedString.default(""),
@@ -817,7 +874,8 @@ export const taskRunHeartbeatSchema = z.object({
817
874
  });
818
875
  export const taskRunFinishSchema = z.object({
819
876
  actor: nonEmptyTrimmedString.optional(),
820
- note: trimmedString.default("")
877
+ note: trimmedString.default(""),
878
+ closeoutNote: nestedCreateNoteSchema.optional()
821
879
  });
822
880
  export const taskRunFocusSchema = z.object({
823
881
  actor: nonEmptyTrimmedString.optional()
@@ -982,7 +1040,8 @@ export const operatorLogWorkSchema = z
982
1040
  effort: taskEffortSchema.optional(),
983
1041
  energy: taskEnergySchema.optional(),
984
1042
  points: z.number().int().min(5).max(500).optional(),
985
- tagIds: uniqueStringArraySchema.optional()
1043
+ tagIds: uniqueStringArraySchema.optional(),
1044
+ closeoutNote: nestedCreateNoteSchema.optional()
986
1045
  })
987
1046
  .superRefine((value, context) => {
988
1047
  if (!value.taskId && (!value.title || value.title.trim().length === 0)) {
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.7",
5
+ "version": "0.2.11",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -17,6 +17,12 @@
17
17
  "help": "Forge server port. Change this if your local machine uses another port.",
18
18
  "placeholder": "4317"
19
19
  },
20
+ "dataRoot": {
21
+ "label": "Forge Data Root",
22
+ "help": "Optional absolute folder path for Forge data. Use this when you want Forge to read and write a specific data directory instead of the runtime working directory.",
23
+ "placeholder": "/Users/you/forge-data",
24
+ "advanced": true
25
+ },
20
26
  "apiToken": {
21
27
  "label": "Forge API Token",
22
28
  "help": "Optional bearer token. Leave blank for one-step localhost or Tailscale operator-session bootstrap.",
@@ -50,6 +56,11 @@
50
56
  "maximum": 65535,
51
57
  "description": "Forge server port. Override this when your local machine uses a different port."
52
58
  },
59
+ "dataRoot": {
60
+ "type": "string",
61
+ "default": "",
62
+ "description": "Optional absolute path for the Forge data folder root. Leave blank to use the runtime working directory."
63
+ },
53
64
  "apiToken": {
54
65
  "type": "string",
55
66
  "default": "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.7",
3
+ "version": "0.2.11",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -331,3 +331,81 @@ CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_
331
331
  CREATE INDEX IF NOT EXISTS idx_session_events_type ON session_events(event_type, created_at DESC);
332
332
  CREATE INDEX IF NOT EXISTS idx_operator_sessions_active
333
333
  ON operator_sessions(revoked_at, expires_at, last_used_at DESC);
334
+
335
+ ALTER TABLE task_runs ADD COLUMN timer_mode TEXT NOT NULL DEFAULT 'unlimited';
336
+ ALTER TABLE task_runs ADD COLUMN planned_duration_seconds INTEGER;
337
+ ALTER TABLE task_runs ADD COLUMN is_current INTEGER NOT NULL DEFAULT 0;
338
+
339
+ UPDATE task_runs
340
+ SET timer_mode = 'unlimited',
341
+ planned_duration_seconds = NULL
342
+ WHERE timer_mode IS NULL OR timer_mode = '';
343
+
344
+ ALTER TABLE app_settings ADD COLUMN max_active_tasks INTEGER NOT NULL DEFAULT 2;
345
+ ALTER TABLE app_settings ADD COLUMN time_accounting_mode TEXT NOT NULL DEFAULT 'split';
346
+ ALTER TABLE app_settings ADD COLUMN psyche_auth_required INTEGER NOT NULL DEFAULT 0;
347
+
348
+ CREATE INDEX IF NOT EXISTS idx_task_runs_actor_status_claimed
349
+ ON task_runs(actor, status, claimed_at);
350
+
351
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_task_runs_single_current_per_actor
352
+ ON task_runs(actor)
353
+ WHERE status = 'active' AND is_current = 1;
354
+
355
+ CREATE TABLE deleted_entities (
356
+ entity_type TEXT NOT NULL,
357
+ entity_id TEXT NOT NULL,
358
+ title TEXT NOT NULL,
359
+ subtitle TEXT NOT NULL DEFAULT '',
360
+ deleted_at TEXT NOT NULL,
361
+ deleted_by_actor TEXT,
362
+ deleted_source TEXT NOT NULL,
363
+ delete_reason TEXT NOT NULL DEFAULT '',
364
+ snapshot_json TEXT NOT NULL,
365
+ PRIMARY KEY (entity_type, entity_id)
366
+ );
367
+
368
+ CREATE INDEX idx_deleted_entities_deleted_at
369
+ ON deleted_entities (deleted_at DESC);
370
+
371
+ CREATE TABLE IF NOT EXISTS notes (
372
+ id TEXT PRIMARY KEY,
373
+ content_markdown TEXT NOT NULL,
374
+ content_plain TEXT NOT NULL DEFAULT '',
375
+ author TEXT,
376
+ source TEXT NOT NULL,
377
+ created_at TEXT NOT NULL,
378
+ updated_at TEXT NOT NULL
379
+ );
380
+
381
+ CREATE TABLE IF NOT EXISTS note_links (
382
+ note_id TEXT NOT NULL,
383
+ entity_type TEXT NOT NULL,
384
+ entity_id TEXT NOT NULL,
385
+ anchor_key TEXT NOT NULL DEFAULT '',
386
+ created_at TEXT NOT NULL,
387
+ PRIMARY KEY (note_id, entity_type, entity_id, anchor_key),
388
+ FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
389
+ );
390
+
391
+ CREATE INDEX IF NOT EXISTS idx_note_links_target
392
+ ON note_links (entity_type, entity_id, created_at DESC);
393
+
394
+ CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts
395
+ USING fts5(
396
+ note_id UNINDEXED,
397
+ content_plain,
398
+ author
399
+ );
400
+
401
+ INSERT INTO notes_fts (note_id, content_plain, author)
402
+ SELECT
403
+ notes.id,
404
+ notes.content_plain,
405
+ COALESCE(notes.author, '')
406
+ FROM notes
407
+ WHERE NOT EXISTS (
408
+ SELECT 1
409
+ FROM notes_fts
410
+ WHERE notes_fts.note_id = notes.id
411
+ );