forge-openclaw-plugin 0.2.105 → 0.2.106

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.
@@ -6,11 +6,12 @@ import { updateWorkoutMetadata } from "./health.js";
6
6
  import { canonicalizeMovementCategoryTags, listMovementPlaces, normalizeMovementCategoryTag, updateMovementPlace } from "./movement.js";
7
7
  import { createHabitCheckIn, listHabits } from "./repositories/habits.js";
8
8
  import { listGoals } from "./repositories/goals.js";
9
+ import { createNote } from "./repositories/notes.js";
9
10
  import { listProjectSummaries } from "./services/projects.js";
10
11
  import { listTasks, updateTask } from "./repositories/tasks.js";
11
12
  import { claimTaskRun, completeTaskRun, focusTaskRun, heartbeatTaskRun, listTaskRuns, releaseTaskRun } from "./repositories/task-runs.js";
13
+ import { createTriggerReport, listBehaviorPatterns, listBehaviors, listEmotionDefinitions, listEventTypes, listModeProfiles, listPsycheValues, listTriggerReports } from "./repositories/psyche.js";
12
14
  import { formatLocalDateKey } from "../../src/lib/date-keys.js";
13
- const watchCapability = "watch-ready";
14
15
  const watchHistoryStateSchema = z.enum(["aligned", "unaligned", "unknown"]);
15
16
  const watchPromptKindSchema = z.enum([
16
17
  "new_place",
@@ -414,7 +415,164 @@ function buildPendingPrompts(userId) {
414
415
  });
415
416
  return prompts.slice(0, 8);
416
417
  }
417
- function projectionForStoredEvent(event) {
418
+ function stringPayloadValue(payload, key) {
419
+ const value = payload[key];
420
+ return typeof value === "string" ? value.trim() : "";
421
+ }
422
+ function createWatchObservationNote(pairing, event, options) {
423
+ return createNote({
424
+ kind: "evidence",
425
+ title: options.title,
426
+ slug: "",
427
+ spaceId: "",
428
+ parentSlug: null,
429
+ indexOrder: 0,
430
+ showInIndex: false,
431
+ aliases: [],
432
+ summary: "",
433
+ contentMarkdown: options.contentLines
434
+ .filter((line) => line.trim().length > 0)
435
+ .join("\n") || options.title,
436
+ author: "Apple Watch",
437
+ tags: ["watch", "psyche", "Self-observation", ...(options.tags ?? [])],
438
+ links: options.links ?? [],
439
+ destroyAt: null,
440
+ sourcePath: "",
441
+ frontmatter: {
442
+ observedAt: event.recordedAt,
443
+ watchEventType: event.eventType,
444
+ watchPromptId: event.promptId ?? null
445
+ },
446
+ revisionHash: "",
447
+ lastSyncedAt: null,
448
+ userId: pairing.user_id
449
+ }, { source: "system", actor: "Apple Watch" });
450
+ }
451
+ function projectPsycheEvent(pairing, event) {
452
+ if (event.eventType === "trigger_capture") {
453
+ const trigger = stringPayloadValue(event.payload, "trigger");
454
+ const eventTypeLabel = stringPayloadValue(event.payload, "eventTypeLabel");
455
+ const eventTypeId = stringPayloadValue(event.payload, "eventTypeId") || null;
456
+ const emotion = stringPayloadValue(event.payload, "emotion");
457
+ const outcome = stringPayloadValue(event.payload, "outcome");
458
+ const intensity = stringPayloadValue(event.payload, "intensity");
459
+ const situation = stringPayloadValue(event.payload, "situation") ||
460
+ stringPayloadValue(event.payload, "choice") ||
461
+ trigger ||
462
+ eventTypeLabel ||
463
+ "Watch trigger observation";
464
+ const report = createTriggerReport({
465
+ title: trigger ? `Watch trigger: ${trigger}` : "Watch trigger observation",
466
+ status: "draft",
467
+ eventTypeId,
468
+ customEventType: eventTypeId ? "" : eventTypeLabel || trigger || "Watch trigger",
469
+ eventSituation: situation,
470
+ occurredAt: event.recordedAt,
471
+ emotions: emotion
472
+ ? [
473
+ {
474
+ emotionDefinitionId: stringPayloadValue(event.payload, "emotionId") || null,
475
+ label: emotion,
476
+ intensity: intensity ? Number(intensity) || 0 : 0,
477
+ note: "Captured from Apple Watch."
478
+ }
479
+ ]
480
+ : [],
481
+ thoughts: [],
482
+ behaviors: outcome
483
+ ? [
484
+ {
485
+ text: outcome,
486
+ mode: "",
487
+ behaviorId: stringPayloadValue(event.payload, "behaviorId") || null
488
+ }
489
+ ]
490
+ : [],
491
+ consequences: {
492
+ selfShortTerm: [],
493
+ selfLongTerm: [],
494
+ othersShortTerm: [],
495
+ othersLongTerm: []
496
+ },
497
+ linkedPatternIds: stringPayloadValue(event.payload, "patternId")
498
+ ? [stringPayloadValue(event.payload, "patternId")]
499
+ : [],
500
+ linkedValueIds: stringPayloadValue(event.payload, "valueId")
501
+ ? [stringPayloadValue(event.payload, "valueId")]
502
+ : [],
503
+ linkedGoalIds: [],
504
+ linkedProjectIds: [],
505
+ linkedTaskIds: [],
506
+ linkedBehaviorIds: stringPayloadValue(event.payload, "behaviorId")
507
+ ? [stringPayloadValue(event.payload, "behaviorId")]
508
+ : [],
509
+ linkedBeliefIds: [],
510
+ linkedModeIds: stringPayloadValue(event.payload, "modeId")
511
+ ? [stringPayloadValue(event.payload, "modeId")]
512
+ : [],
513
+ modeOverlays: [],
514
+ schemaLinks: [],
515
+ modeTimeline: [],
516
+ nextMoves: [],
517
+ userId: pairing.user_id
518
+ }, { source: "system", actor: "Apple Watch" });
519
+ const note = createWatchObservationNote(pairing, event, {
520
+ title: "Watch trigger observation",
521
+ contentLines: [
522
+ `Trigger: ${trigger || eventTypeLabel || "unspecified"}`,
523
+ emotion ? `Emotion: ${emotion}` : "",
524
+ outcome ? `Outcome: ${outcome}` : "",
525
+ situation ? `Situation: ${situation}` : ""
526
+ ],
527
+ links: [{ entityType: "trigger_report", entityId: report.id, anchorKey: null }],
528
+ tags: ["trigger"]
529
+ });
530
+ return {
531
+ status: "projected",
532
+ details: {
533
+ target: "psyche_trigger_report",
534
+ reportId: report.id,
535
+ noteId: note.id
536
+ }
537
+ };
538
+ }
539
+ if (event.eventType === "emotion_check_in" ||
540
+ event.eventType === "routine_check" ||
541
+ event.eventType === "mark_moment") {
542
+ const emotion = stringPayloadValue(event.payload, "emotion");
543
+ const routine = stringPayloadValue(event.payload, "routine");
544
+ const surface = stringPayloadValue(event.payload, "surface");
545
+ const note = createWatchObservationNote(pairing, event, {
546
+ title: event.eventType === "emotion_check_in"
547
+ ? "Watch emotion check-in"
548
+ : event.eventType === "routine_check"
549
+ ? "Watch routine check"
550
+ : "Watch moment",
551
+ contentLines: [
552
+ emotion ? `Emotion: ${emotion}` : "",
553
+ routine ? `Routine: ${routine}` : "",
554
+ surface ? `Surface: ${surface}` : "",
555
+ stringPayloadValue(event.payload, "choice")
556
+ ? `Choice: ${stringPayloadValue(event.payload, "choice")}`
557
+ : ""
558
+ ],
559
+ tags: [event.eventType.replaceAll("_", "-")]
560
+ });
561
+ return {
562
+ status: "projected",
563
+ details: {
564
+ target: "psyche_observation_note",
565
+ noteId: note.id
566
+ }
567
+ };
568
+ }
569
+ return null;
570
+ }
571
+ function projectionForStoredEvent(pairing, event) {
572
+ const psycheProjection = projectPsycheEvent(pairing, event);
573
+ if (psycheProjection) {
574
+ return psycheProjection;
575
+ }
418
576
  if (event.eventType === "place_label" && event.linkedContext.placeId) {
419
577
  const nextLabel = typeof event.payload.label === "string" ? event.payload.label.trim() : "";
420
578
  const categoryCandidate = Array.isArray(event.payload.categoryTags)
@@ -512,10 +670,11 @@ function projectionForStoredEvent(event) {
512
670
  };
513
671
  }
514
672
  export function assertWatchReady(pairing) {
515
- const capabilities = safeJsonParse(pairing.capability_flags_json, []);
516
- if (!capabilities.includes(watchCapability)) {
517
- throw new HttpError(403, "watch_pairing_not_enabled", "This companion pairing is not allowed to serve watch data.");
518
- }
673
+ // Any valid mobile pairing can receive a compact watch snapshot. Older
674
+ // pairings were created before the explicit `watch-ready` capability existed;
675
+ // rejecting them leaves the installed watch app permanently empty even though
676
+ // the same pairing token can already sync HealthKit and movement data.
677
+ void pairing;
519
678
  }
520
679
  function compactTask(task) {
521
680
  return {
@@ -735,6 +894,151 @@ function buildSyncSnapshot(pairing) {
735
894
  actionReceiptCount: actionReceiptCount.count
736
895
  };
737
896
  }
897
+ function compactOption(option, payload = {}) {
898
+ const label = (option.label ?? option.title ?? "").trim();
899
+ return {
900
+ id: option.id ?? label,
901
+ label,
902
+ subtitle: (option.category ?? option.description ?? "").trim(),
903
+ payload
904
+ };
905
+ }
906
+ function compactFallbackOptions(labels, payloadKey) {
907
+ return labels.map((label) => ({
908
+ id: label,
909
+ label,
910
+ subtitle: "",
911
+ payload: { [payloadKey]: label }
912
+ }));
913
+ }
914
+ function buildWatchPsycheSnapshot(pairing) {
915
+ const userIds = pairing.user_id === "user_operator" ? undefined : [pairing.user_id];
916
+ const owned = (items) => userIds ? items.filter((item) => item.userId == null || userIds.includes(item.userId)) : items;
917
+ const events = owned(listEventTypes())
918
+ .slice(0, 8)
919
+ .map((eventType) => compactOption(eventType, {
920
+ eventTypeId: eventType.id,
921
+ eventTypeLabel: eventType.label
922
+ }))
923
+ .filter((option) => option.label.length > 0);
924
+ const emotions = owned(listEmotionDefinitions())
925
+ .slice(0, 10)
926
+ .map((emotion) => compactOption(emotion, {
927
+ emotionId: emotion.id,
928
+ emotion: emotion.label
929
+ }))
930
+ .filter((option) => option.label.length > 0);
931
+ const values = owned(listPsycheValues())
932
+ .slice(0, 8)
933
+ .map((value) => compactOption(value, {
934
+ valueId: value.id,
935
+ value: value.title
936
+ }))
937
+ .filter((option) => option.label.length > 0);
938
+ const patterns = owned(listBehaviorPatterns())
939
+ .slice(0, 8)
940
+ .map((pattern) => compactOption(pattern, {
941
+ patternId: pattern.id,
942
+ trigger: pattern.title
943
+ }))
944
+ .filter((option) => option.label.length > 0);
945
+ const behaviors = owned(listBehaviors())
946
+ .slice(0, 8)
947
+ .map((behavior) => compactOption(behavior, {
948
+ behaviorId: behavior.id,
949
+ behavior: behavior.title
950
+ }))
951
+ .filter((option) => option.label.length > 0);
952
+ const modes = owned(listModeProfiles())
953
+ .slice(0, 8)
954
+ .map((mode) => compactOption(mode, {
955
+ modeId: mode.id,
956
+ mode: mode.title
957
+ }))
958
+ .filter((option) => option.label.length > 0);
959
+ const recentReports = owned(listTriggerReports(6)).map((report) => ({
960
+ id: report.id,
961
+ title: report.title,
962
+ occurredAt: report.occurredAt,
963
+ status: report.status
964
+ }));
965
+ return {
966
+ emotionOptions,
967
+ triggerOptions,
968
+ routinePromptOptions,
969
+ questions: [
970
+ {
971
+ id: "event",
972
+ title: "What happened?",
973
+ prompt: "Pick the closest event type.",
974
+ eventType: "trigger_capture",
975
+ options: events.length > 0
976
+ ? events
977
+ : compactFallbackOptions(triggerOptions.slice(0, 6), "eventTypeLabel")
978
+ },
979
+ {
980
+ id: "emotion",
981
+ title: "Emotion",
982
+ prompt: "What emotion is present?",
983
+ eventType: "emotion_check_in",
984
+ options: emotions.length > 0
985
+ ? emotions
986
+ : compactFallbackOptions(emotionOptions.slice(0, 6), "emotion")
987
+ },
988
+ {
989
+ id: "pattern",
990
+ title: "Pattern",
991
+ prompt: "Does this match a known loop?",
992
+ eventType: "trigger_capture",
993
+ options: patterns.length > 0
994
+ ? patterns
995
+ : compactFallbackOptions(triggerOptions.slice(0, 6), "trigger")
996
+ },
997
+ {
998
+ id: "outcome",
999
+ title: "Urge outcome",
1000
+ prompt: "What did you do with the urge?",
1001
+ eventType: "trigger_capture",
1002
+ options: compactFallbackOptions(["Resisted", "Indulged", "Delayed", "Repaired"], "outcome")
1003
+ },
1004
+ {
1005
+ id: "value",
1006
+ title: "Value",
1007
+ prompt: "Which value is this about?",
1008
+ eventType: "mark_moment",
1009
+ options: values.length > 0
1010
+ ? values
1011
+ : compactFallbackOptions(["Health", "Work", "Love", "Courage"], "value")
1012
+ },
1013
+ {
1014
+ id: "mode",
1015
+ title: "Mode",
1016
+ prompt: "Which mode is active?",
1017
+ eventType: "trigger_capture",
1018
+ options: modes.length > 0
1019
+ ? modes
1020
+ : compactFallbackOptions(["Protected", "Avoidant", "Driven", "Connected"], "mode")
1021
+ },
1022
+ {
1023
+ id: "behavior",
1024
+ title: "Behavior",
1025
+ prompt: "What behavior showed up?",
1026
+ eventType: "trigger_capture",
1027
+ options: behaviors.length > 0
1028
+ ? behaviors
1029
+ : compactFallbackOptions(["Avoided", "Approached", "Scrolled", "Asked"], "behavior")
1030
+ },
1031
+ {
1032
+ id: "routine",
1033
+ title: "Routine",
1034
+ prompt: "Log one daily signal.",
1035
+ eventType: "routine_check",
1036
+ options: compactFallbackOptions(routinePromptOptions.slice(0, 6), "routine")
1037
+ }
1038
+ ],
1039
+ recentReports
1040
+ };
1041
+ }
738
1042
  function buildWatchSurfaces() {
739
1043
  return [
740
1044
  { id: "now", title: "Now", icon: "sparkle" },
@@ -805,11 +1109,7 @@ export function buildWatchBootstrap(pairing, options) {
805
1109
  today,
806
1110
  health: buildHealthSnapshot(pairing.user_id),
807
1111
  movement: buildMovementSnapshot(pairing.user_id),
808
- psyche: {
809
- emotionOptions,
810
- triggerOptions,
811
- routinePromptOptions
812
- },
1112
+ psyche: buildWatchPsycheSnapshot(pairing),
813
1113
  inbox: {
814
1114
  prompts: pendingPrompts
815
1115
  },
@@ -856,7 +1156,7 @@ export function ingestWatchCaptureBatch(pairing, input) {
856
1156
  const receivedAt = nowIso();
857
1157
  insert.run(id, pairing.id, pairing.user_id, event.dedupeKey, parsed.device.sourceDevice, event.eventType, event.promptId, event.recordedAt, receivedAt, JSON.stringify(event.linkedContext), JSON.stringify(event.payload), "stored", "{}", receivedAt);
858
1158
  storedCount += 1;
859
- const projection = projectionForStoredEvent(event);
1159
+ const projection = projectionForStoredEvent(pairing, event);
860
1160
  updateProjection.run(projection.status, JSON.stringify(projection.details), id);
861
1161
  if (projection.status === "projected") {
862
1162
  projectedCount += 1;
@@ -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.105",
5
+ "version": "0.2.106",
6
6
  "activation": {
7
7
  "onStartup": true,
8
8
  "onCapabilities": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.105",
3
+ "version": "0.2.106",
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": "Apache-2.0",