forge-openclaw-plugin 0.2.88 → 0.2.90
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/dist/server/server/src/app.js +135 -13
- package/dist/server/server/src/health.js +60 -42
- package/dist/server/server/src/openapi.js +185 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/forge-openclaw/SKILL.md +12 -5
- package/skills/forge-openclaw/entity_conversation_playbooks.md +48 -13
- package/skills/forge-openclaw/psyche_entity_playbooks.md +17 -0
|
@@ -66,7 +66,7 @@ import { registerWebRoutes } from "./web.js";
|
|
|
66
66
|
import { createManagerRuntime } from "./managers/runtime.js";
|
|
67
67
|
import { isManagerError } from "./managers/type-guards.js";
|
|
68
68
|
import { buildCompanionPairingTransport, getCompanionIrohStatus, stopCompanionIroh } from "./services/companion-iroh.js";
|
|
69
|
-
import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getHealthZoneProfileForUser, getWorkoutSessionById, getWorkoutSessionDetailById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, healthZoneProfilePatchSchema, abortMobileHealthSyncSession, completeMobileHealthSyncSession, ingestMobileHealthSync, ingestMobileHealthSyncChunk, mobileHealthSyncChunkSchema, mobileHealthSyncSessionCompleteSchema, mobileHealthSyncSessionStartSchema, mobileHealthSyncSchema, patchHealthZoneProfileForUser, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, startMobileHealthSyncSession, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
|
|
69
|
+
import { createCompanionPairingSession, createCompanionPairingSessionSchema, createSleepSession, createSleepSessionSchema, createWorkoutSession, createWorkoutSessionSchema, deleteSleepSession, deleteWorkoutSession, getCompanionPairingSessionById, getCompanionOverview, getFitnessViewData, getSleepSessionById, getSleepSessionDetailById, getSleepTimelineOverlaysForRange, getSleepViewData, getVitalsViewData, getHealthZoneProfileForUser, getMobileHealthSyncSessionStatus, getWorkoutSessionById, getWorkoutSessionDetailById, heartbeatCompanionPairing, heartbeatCompanionPairingSchema, healthZoneProfilePatchSchema, abortMobileHealthSyncSession, completeMobileHealthSyncSession, ingestMobileHealthSync, ingestMobileHealthSyncChunk, mobileHealthSyncChunkSchema, mobileHealthSyncSessionCompleteSchema, mobileHealthSyncSessionStartSchema, mobileHealthSyncSchema, patchHealthZoneProfileForUser, patchCompanionPairingSourceState, patchCompanionPairingSourceStateSchema, companionSourceKeySchema, requireValidPairing, startMobileHealthSyncSession, revokeAllCompanionPairingSessions, revokeAllCompanionPairingSessionsSchema, revokeCompanionPairingSession, updateMobileCompanionSourceState, updateMobileCompanionSourceStateSchema, verifyCompanionPairing, verifyCompanionPairingSchema, updateSleepMetadata, updateSleepMetadataSchema, updateWorkoutMetadata, updateWorkoutMetadataSchema } from "./health.js";
|
|
70
70
|
import { analyzeMovementUserBoxPreflight, createMovementUserBox, createMovementPlace, deleteMovementUserBox, getMovementAllTimeSummary, getMovementBoxDetail, getMovementDayDetail, getMovementMobileBootstrap, getMovementTimeline, getMovementSelectionAggregate, getMovementSettings, getMovementTripDetail, getMovementMonthSummary, invalidateAutomaticMovementBox, listMovementPlaces, movementAutomaticBoxInvalidateSchema, movementMobileBootstrapSchema, movementMobilePlaceMutationSchema, movementMobileStayPatchSchema, movementMobileUserBoxCreateSchema, movementMobileUserBoxPreflightSchema, movementMobileUserBoxPatchSchema, movementMobileAutomaticBoxInvalidateSchema, movementMobileTimelineSchema, movementPlaceMutationSchema, movementPlacePatchSchema, movementSelectionAggregateSchema, movementStayPatchSchema, movementTripPatchSchema, movementUserBoxCreateSchema, movementUserBoxPreflightSchema, movementUserBoxPatchSchema, movementSettingsPatchSchema, movementTimelineQuerySchema, movementTripPointPatchSchema, deleteMovementStay, deleteMovementTrip, deleteMovementTripPoint, updateMovementPlace, updateMovementSettings, updateMovementStay, updateMovementTrip, updateMovementUserBox, updateMovementTripPoint, resolveMovementTimelineSegmentForBox } from "./movement.js";
|
|
71
71
|
import { getScreenTimeAllTimeSummary, getScreenTimeDayDetail, getScreenTimeMonthSummary, getScreenTimeSettings, screenTimeSettingsPatchSchema, updateScreenTimeSettings } from "./screen-time.js";
|
|
72
72
|
import { assertWatchReady, buildWatchBootstrap, ingestWatchCaptureBatch, mobileWatchBootstrapSchema, mobileWatchCaptureBatchSchema, mobileWatchHabitCheckInSchema } from "./watch-mobile.js";
|
|
@@ -279,6 +279,8 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
|
|
|
279
279
|
relationshipRules: [
|
|
280
280
|
"Every project belongs to a goal through goalId.",
|
|
281
281
|
"Tasks can link to a project through projectId.",
|
|
282
|
+
"Projects can have one owner through userId plus one or more human or bot assignees through assigneeUserIds.",
|
|
283
|
+
"Projects are PRD-backed initiatives: productRequirementsDocument stores the project brief and workflowStatus stores the board lane separately from lifecycle status.",
|
|
282
284
|
"Projects inherit strategic meaning from their parent goal."
|
|
283
285
|
],
|
|
284
286
|
searchHints: [
|
|
@@ -315,6 +317,14 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
|
|
|
315
317
|
enumValues: ["active", "paused", "completed"],
|
|
316
318
|
defaultValue: "active"
|
|
317
319
|
},
|
|
320
|
+
{
|
|
321
|
+
name: "workflowStatus",
|
|
322
|
+
type: "backlog|focus|in_progress|blocked|done",
|
|
323
|
+
required: false,
|
|
324
|
+
description: "Board workflow lane. Keep this separate from lifecycle status.",
|
|
325
|
+
enumValues: ["backlog", "focus", "in_progress", "blocked", "done"],
|
|
326
|
+
defaultValue: "backlog"
|
|
327
|
+
},
|
|
318
328
|
{
|
|
319
329
|
name: "userId",
|
|
320
330
|
type: "string|null",
|
|
@@ -323,6 +333,26 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
|
|
|
323
333
|
defaultValue: null,
|
|
324
334
|
nullable: true
|
|
325
335
|
},
|
|
336
|
+
{
|
|
337
|
+
name: "assigneeUserIds",
|
|
338
|
+
type: "string[]",
|
|
339
|
+
required: false,
|
|
340
|
+
description: "Human or bot user ids assigned to collaborate on the project.",
|
|
341
|
+
defaultValue: []
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: "productRequirementsDocument",
|
|
345
|
+
type: "string",
|
|
346
|
+
required: false,
|
|
347
|
+
description: "Project PRD or brief: outcome, scope, constraints, and acceptance direction.",
|
|
348
|
+
defaultValue: ""
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "schedulingRules",
|
|
352
|
+
type: "CalendarSchedulingRules",
|
|
353
|
+
required: false,
|
|
354
|
+
description: "Optional calendar/work-block rules that govern when this project may be scheduled."
|
|
355
|
+
},
|
|
326
356
|
{
|
|
327
357
|
name: "targetPoints",
|
|
328
358
|
type: "integer",
|
|
@@ -431,19 +461,24 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
|
|
|
431
461
|
},
|
|
432
462
|
{
|
|
433
463
|
entityType: "task",
|
|
434
|
-
purpose: "A concrete actionable work item.
|
|
464
|
+
purpose: "A concrete actionable work item. The same stored family carries issue, task, and subtask levels; tasks are the one-session execution layer.",
|
|
435
465
|
minimumCreateFields: ["title"],
|
|
436
466
|
relationshipRules: [
|
|
437
|
-
"
|
|
467
|
+
"Use level=issue for vertical slices under a project, level=task for one focused AI or human work session, and level=subtask for lightweight child steps.",
|
|
468
|
+
"In hierarchy-aware Forge PM, issues live directly under projects, tasks live under issues, and subtasks live under tasks.",
|
|
469
|
+
"Legacy or inbox tasks may still link to a goal, project, both, or neither when the hierarchy is intentionally absent.",
|
|
470
|
+
"When placement matters, ask for the project, issue, or parent task that changes the hierarchy instead of asking a flat parent question.",
|
|
471
|
+
"Work items can have one owner through userId plus one or more human or bot assignees through assigneeUserIds.",
|
|
438
472
|
"Live work is tracked by task runs, not by task status alone.",
|
|
439
473
|
"A task status of in_progress does not guarantee a live active run."
|
|
440
474
|
],
|
|
441
475
|
searchHints: [
|
|
442
476
|
"Search by title before creating a duplicate task.",
|
|
443
|
-
"Use linkedTo filters when you know the parent goal or
|
|
477
|
+
"Use linkedTo filters when you know the parent goal, project, issue, task, or subtask.",
|
|
478
|
+
"Use level and parentWorkItemId filters when the user is navigating the issue/task/subtask hierarchy."
|
|
444
479
|
],
|
|
445
480
|
examples: [
|
|
446
|
-
'{"title":"
|
|
481
|
+
'{"title":"Add route-key example coverage","level":"task","projectId":"project_forge_plugin_launch","parentWorkItemId":"issue_question_flow_contract","aiInstructions":"Update the onboarding contract and tests so Workbench one-off execution has a route-key example.","status":"focus","priority":"high"}'
|
|
447
482
|
],
|
|
448
483
|
fieldGuide: [
|
|
449
484
|
{
|
|
@@ -459,6 +494,14 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
|
|
|
459
494
|
description: "Markdown context, constraints, or acceptance notes.",
|
|
460
495
|
defaultValue: ""
|
|
461
496
|
},
|
|
497
|
+
{
|
|
498
|
+
name: "level",
|
|
499
|
+
type: "issue|task|subtask",
|
|
500
|
+
required: false,
|
|
501
|
+
description: "Work-item level. Use issue for vertical slices, task for one focused execution session, and subtask for lightweight child steps.",
|
|
502
|
+
enumValues: ["issue", "task", "subtask"],
|
|
503
|
+
defaultValue: "task"
|
|
504
|
+
},
|
|
462
505
|
{
|
|
463
506
|
name: "status",
|
|
464
507
|
type: "backlog|focus|in_progress|blocked|done",
|
|
@@ -490,6 +533,13 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
|
|
|
490
533
|
defaultValue: null,
|
|
491
534
|
nullable: true
|
|
492
535
|
},
|
|
536
|
+
{
|
|
537
|
+
name: "assigneeUserIds",
|
|
538
|
+
type: "string[]",
|
|
539
|
+
required: false,
|
|
540
|
+
description: "Human or bot user ids assigned to collaborate on this issue, task, or subtask.",
|
|
541
|
+
defaultValue: []
|
|
542
|
+
},
|
|
493
543
|
{
|
|
494
544
|
name: "goalId",
|
|
495
545
|
type: "string|null",
|
|
@@ -506,6 +556,52 @@ const AGENT_ONBOARDING_ENTITY_CATALOG_BASE = [
|
|
|
506
556
|
defaultValue: null,
|
|
507
557
|
nullable: true
|
|
508
558
|
},
|
|
559
|
+
{
|
|
560
|
+
name: "parentWorkItemId",
|
|
561
|
+
type: "string|null",
|
|
562
|
+
required: false,
|
|
563
|
+
description: "Parent issue id for level=task or parent task id for level=subtask. Issues should sit directly under projectId.",
|
|
564
|
+
defaultValue: null,
|
|
565
|
+
nullable: true
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: "aiInstructions",
|
|
569
|
+
type: "string",
|
|
570
|
+
required: false,
|
|
571
|
+
description: "Execution instructions for the agent or human doing the one-session task.",
|
|
572
|
+
defaultValue: ""
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: "executionMode",
|
|
576
|
+
type: "afk|hitl|null",
|
|
577
|
+
required: false,
|
|
578
|
+
description: "Whether the issue or task is intended for autonomous work or human-in-the-loop work.",
|
|
579
|
+
enumValues: ["afk", "hitl"],
|
|
580
|
+
defaultValue: null,
|
|
581
|
+
nullable: true
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "acceptanceCriteria",
|
|
585
|
+
type: "string[]",
|
|
586
|
+
required: false,
|
|
587
|
+
description: "Structured success criteria, usually Given/When/Then for issues or concrete done checks for tasks.",
|
|
588
|
+
defaultValue: []
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "blockerLinks",
|
|
592
|
+
type: "Array<{ entityType, entityId, label? }>",
|
|
593
|
+
required: false,
|
|
594
|
+
description: "Optional blockers linked to Forge entities that make the work unable to proceed.",
|
|
595
|
+
defaultValue: []
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: "completionReport",
|
|
599
|
+
type: "{ modifiedFiles: string[], workSummary: string, linkedGitRefIds: string[] }|null",
|
|
600
|
+
required: false,
|
|
601
|
+
description: "Closeout report for completed work items, especially tasks finished by agents.",
|
|
602
|
+
defaultValue: null,
|
|
603
|
+
nullable: true
|
|
604
|
+
},
|
|
509
605
|
{
|
|
510
606
|
name: "dueDate",
|
|
511
607
|
type: "YYYY-MM-DD|null",
|
|
@@ -3007,10 +3103,11 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
3007
3103
|
"Ask what this piece of work is trying to make true.",
|
|
3008
3104
|
"Reflect the emerging boundary so the user can hear what is in scope.",
|
|
3009
3105
|
"Ask what outcome would make the project feel real or complete for now.",
|
|
3106
|
+
"Ask what belongs in the project PRD or brief when the user is shaping delivery rather than only naming a project.",
|
|
3010
3107
|
"Ask what belongs inside the boundary and what can stay out if the scope still feels muddy.",
|
|
3011
3108
|
"Ask which goal it belongs under.",
|
|
3012
3109
|
"Land on a working name once the scope is clear.",
|
|
3013
|
-
"Clarify status, owner, and notes only after the scope is clear."
|
|
3110
|
+
"Clarify lifecycle status, workflow lane, owner, human/bot assignees, scheduling rules, and notes only after the scope is clear."
|
|
3014
3111
|
]
|
|
3015
3112
|
},
|
|
3016
3113
|
{
|
|
@@ -3028,11 +3125,13 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
3028
3125
|
{
|
|
3029
3126
|
focus: "task",
|
|
3030
3127
|
openingQuestion: "What is the next concrete move here?",
|
|
3031
|
-
coachingGoal: "Identify the next concrete
|
|
3128
|
+
coachingGoal: "Identify the next concrete one-session work item and place it in the issue/task/subtask hierarchy when that hierarchy matters.",
|
|
3032
3129
|
askSequence: [
|
|
3033
3130
|
"Ask what the next concrete action is.",
|
|
3034
|
-
"Ask
|
|
3035
|
-
"Ask
|
|
3131
|
+
"Ask whether this is an issue, one-session task, or subtask only when the level is not already obvious.",
|
|
3132
|
+
"Ask where it belongs in the hierarchy: project for an issue, issue for a task, or parent task for a subtask. Use goal or standalone only when the user is intentionally outside the PM hierarchy.",
|
|
3133
|
+
"Capture the execution contract in aiInstructions when the task is for an agent session.",
|
|
3134
|
+
"Ask what would make it easier to do: due date, priority, owner, human/bot assignees, acceptance criteria, or brief context."
|
|
3036
3135
|
]
|
|
3037
3136
|
},
|
|
3038
3137
|
{
|
|
@@ -3499,6 +3598,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
3499
3598
|
"Use an ACT-style values clarification stance: values are directions to live toward, not boxes to complete.",
|
|
3500
3599
|
"Ask one or two questions at a time, reflect back the user's language, and only then move toward naming committed actions or linked work items.",
|
|
3501
3600
|
"Reflect the pain, longing, or importance that makes the value alive before narrowing to action.",
|
|
3601
|
+
"Once one ordinary moment is clear, offer one tentative hypothesis about the pain, longing, or value conflict that makes this value alive now, then ask whether it lands before turning it into action wording.",
|
|
3502
3602
|
"If the user says they want to understand it first, start with one orienting question before offering a formulation or save suggestion."
|
|
3503
3603
|
]
|
|
3504
3604
|
},
|
|
@@ -3585,7 +3685,8 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
3585
3685
|
"When the behavior clearly belongs inside a larger loop, suggest linking or also mapping the related behavior_pattern.",
|
|
3586
3686
|
"If the user asks for understanding before storage, ask about the recent example and function of the move before classifying it.",
|
|
3587
3687
|
"Ask what the move is trying to do for the user before moving into replacement planning.",
|
|
3588
|
-
"Name the immediate protective job before discussing costs or alternatives."
|
|
3688
|
+
"Name the immediate protective job before discussing costs or alternatives.",
|
|
3689
|
+
"Once one example is clear, offer one tentative hypothesis about the immediate problem the behavior solves, the cue or urge that pulls it online, and the cost it creates later."
|
|
3589
3690
|
]
|
|
3590
3691
|
},
|
|
3591
3692
|
{
|
|
@@ -3703,7 +3804,8 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
3703
3804
|
notes: [
|
|
3704
3805
|
"A mode_guide_session is the exploration worksheet, not the final identity claim.",
|
|
3705
3806
|
"Store the user's answers faithfully and keep interpretations tentative unless the user wants a durable mode_profile.",
|
|
3706
|
-
"Use candidate mode interpretations as testable hypotheses tied to the user's answers, not as certain labels."
|
|
3807
|
+
"Use candidate mode interpretations as testable hypotheses tied to the user's answers, not as certain labels.",
|
|
3808
|
+
"After enough answers are visible, offer one hypothesis about what the active part is trying to stop, force, prevent, or secure before proposing any durable mode label."
|
|
3707
3809
|
]
|
|
3708
3810
|
},
|
|
3709
3811
|
{
|
|
@@ -3750,6 +3852,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
3750
3852
|
"Use shared batch entity routes for flashcard create, update, delete, restore, and search.",
|
|
3751
3853
|
"When the user says they feel an urge or trigger, search flashcards by triggerSentence, triggerSituation, tags, message, and linked Psyche records before creating a new card.",
|
|
3752
3854
|
"When showing a card, display or quote the message first, then add brief grounding, urge-surfing, cognitive defusion, schema-mode, or values-based coaching around it.",
|
|
3855
|
+
"If the cue, urge sentence, mode, belief, or value pivot is clear, offer one hypothesis about what the card needs to meet in the hard moment before polishing its final wording.",
|
|
3753
3856
|
"Do not make the user fill styling fields before the therapeutic sentence is clear."
|
|
3754
3857
|
]
|
|
3755
3858
|
},
|
|
@@ -3830,7 +3933,8 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
3830
3933
|
notes: [
|
|
3831
3934
|
"event_type is a Psyche taxonomy record, but it still needs active listening because it names emotionally meaningful episodes.",
|
|
3832
3935
|
"Do not open with pure label wording unless the lived category and boundary are already clear.",
|
|
3833
|
-
"Offer a candidate label after the repeated moment is understood, and keep the user's wording when it already fits."
|
|
3936
|
+
"Offer a candidate label after the repeated moment is understood, and keep the user's wording when it already fits.",
|
|
3937
|
+
"Once one recurring example is clear, offer one hypothesis about the repeated emotional or relational stake that future reports need this event type to preserve."
|
|
3834
3938
|
]
|
|
3835
3939
|
},
|
|
3836
3940
|
{
|
|
@@ -3864,6 +3968,7 @@ const AGENT_ONBOARDING_PSYCHE_PLAYBOOKS = [
|
|
|
3864
3968
|
notes: [
|
|
3865
3969
|
"emotion_definition is a Psyche taxonomy record, not a generic dictionary entry.",
|
|
3866
3970
|
"Start from the lived feeling before asking for category or browsing fields.",
|
|
3971
|
+
"Once the lived signature is visible, offer one hypothesis about what the emotion warns about, protects, demands, or longs for before saving the reusable definition.",
|
|
3867
3972
|
"If the user already gives a good label, reflect the felt signature and ask only for the one boundary or definition detail that still matters."
|
|
3868
3973
|
]
|
|
3869
3974
|
}
|
|
@@ -4501,11 +4606,13 @@ function buildAgentOnboardingPayload(request) {
|
|
|
4501
4606
|
entityConversationPlaybooks: AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS.map(enrichConversationPlaybookWithRouteInfo),
|
|
4502
4607
|
relationshipModel: [
|
|
4503
4608
|
"Every Forge record belongs to one typed user owner: either human or bot.",
|
|
4609
|
+
"Projects and work items can also carry assigneeUserIds for one or more human or bot collaborators; ownership and assignment are separate.",
|
|
4504
4610
|
"Read routes may scope to one user with userId or to several users with repeated userIds.",
|
|
4505
4611
|
"Ownership and linkage are separate: a human-owned project can link to bot-owned tasks, strategies, notes, or insights.",
|
|
4506
4612
|
"Goals are the top-level strategic layer.",
|
|
4507
4613
|
"Projects belong to one goal through goalId.",
|
|
4508
|
-
"
|
|
4614
|
+
"The task entity is the stored work-item family for issue, task, and subtask levels.",
|
|
4615
|
+
"Hierarchy-aware PM should place issues under projects, tasks under issues, and subtasks under tasks; standalone or goal-linked tasks are legacy/inbox escape hatches rather than the default planning path.",
|
|
4509
4616
|
"Strategies can target one or many goals or projects while sequencing project and task nodes through a directed acyclic graph.",
|
|
4510
4617
|
"A strategy remains editable until it is locked. Once locked, the plan becomes a contract and graph-shape edits should stop until the strategy is explicitly unlocked.",
|
|
4511
4618
|
"Habits are recurring records that can connect directly to goals, projects, tasks, and durable Psyche entities.",
|
|
@@ -5114,6 +5221,8 @@ function buildAgentOnboardingPayload(request) {
|
|
|
5114
5221
|
reviewShortcutRule: "When the user is reviewing or correcting an existing record, ask what practical question they want the read or correction to answer, then narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
|
|
5115
5222
|
readModelWriteRule: "Self-observation is note-backed and should be written through observed notes with frontmatter.observedAt only when a lightweight episode observation is the right container. Do not use it as the default bucket for Psyche material: prefer trigger_report for one emotionally meaningful episode, behavior_pattern for functional analysis of a recurring loop, behavior for one repeated move, belief_entry for a core sentence, mode_guide_session or mode_profile for a central part-state, and wiki_page for durable memory such as books, articles, concepts, sources, or personal manuals. Sleep and workout sessions stay on batch CRUD by default; use the reflective review helpers only when enriching one already-known record after review.",
|
|
5116
5223
|
psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what cue or body signal came first before the behavior, what the belief starts saying about self or outcome, what feels most at risk inside the mode, what the part is trying to get the user to do or stop doing, or where the shift began in the incident. Reflect briefly before the question, choose one follow-up lane at a time, say what is becoming clearer before the next deeper question, and if several Psyche entities are visible hold the adjacent ones lightly until the main container is clear.",
|
|
5224
|
+
followUpQuestionRule: "After a substantive answer, do not restart the opener or jump to the next schema field. First say what became clearer in concrete language, then choose exactly one next lane: wording, boundary, placement, timing, route scope, link, hypothesis, or write confirmation. Ask the smallest question that would change the record shape, route choice, useful wording, timing, or links. If nothing decision-relevant would change, stop asking, summarize the working record, and act with consent.",
|
|
5225
|
+
antiDriftRule: "Avoid vague reflective filler and internal route language. Replace phrases like 'that sounds important' with the specific stake you heard, and replace API nouns like surface, CRUD, payload, mutation path, or endpoint with user-facing product nouns such as belief, pattern, note, wiki page, timeline, overlay, weekday template, flow, run, node result, or published output. If a question would only decorate the intake, skip it.",
|
|
5117
5226
|
duplicateCheckRoute: "/api/v1/entities/search",
|
|
5118
5227
|
uiSuggestionRule: "offer_visual_ui_when_review_or_editing_would_be_easier",
|
|
5119
5228
|
browserFallbackRule: "Do not open the Forge UI or a browser just to create or update normal entities when the batch entity tools can do the job. Batch CRUD is the default for simple entities; avoid spamming the agent with a large one-route-per-entity mental model.",
|
|
@@ -5161,6 +5270,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
5161
5270
|
workbenchPublishedOutput: '{"routeKey":"publishedOutput","pathParams":{"id":"flow_research_digest"}}',
|
|
5162
5271
|
workbenchLatestNodeOutput: '{"routeKey":"latestNodeOutput","pathParams":{"id":"flow_research_digest","nodeId":"node_summary"}}',
|
|
5163
5272
|
workbenchRunFlow: '{"routeKey":"runFlow","pathParams":{"id":"flow_research_digest"},"body":{"input":{"topic":"question flow quality"}}}',
|
|
5273
|
+
workbenchRunByPayload: '{"routeKey":"runByPayload","body":{"flow":{"title":"One-off digest","nodes":[]},"input":{"topic":"question flow quality"}}}',
|
|
5164
5274
|
workbenchChatFlow: '{"routeKey":"chatFlow","pathParams":{"id":"flow_research_digest"},"body":{"message":"Refine the summary around API route risks and keep the published output stable."}}'
|
|
5165
5275
|
}
|
|
5166
5276
|
}
|
|
@@ -7670,6 +7780,18 @@ export async function buildServer(options = {}) {
|
|
|
7670
7780
|
app.post("/api/v1/mobile/healthkit/sync-sessions", async (request) => ({
|
|
7671
7781
|
upload: startMobileHealthSyncSession(mobileHealthSyncSessionStartSchema.parse(request.body ?? {}))
|
|
7672
7782
|
}));
|
|
7783
|
+
app.get("/api/v1/mobile/healthkit/sync-sessions/:id", async (request) => {
|
|
7784
|
+
const { id } = request.params;
|
|
7785
|
+
const query = z
|
|
7786
|
+
.object({
|
|
7787
|
+
sessionId: z.string().trim().min(1),
|
|
7788
|
+
pairingToken: z.string().trim().min(1)
|
|
7789
|
+
})
|
|
7790
|
+
.parse(request.query ?? {});
|
|
7791
|
+
return {
|
|
7792
|
+
upload: getMobileHealthSyncSessionStatus(id, query)
|
|
7793
|
+
};
|
|
7794
|
+
});
|
|
7673
7795
|
app.post("/api/v1/mobile/healthkit/sync-sessions/:id/chunks", { bodyLimit: 40_000_000 }, async (request) => {
|
|
7674
7796
|
const { id } = request.params;
|
|
7675
7797
|
const rawPayloadJson = JSON.stringify((request.body ?? {}).payload ?? {});
|
|
@@ -2714,6 +2714,57 @@ function readMobileSyncSession(syncSessionId) {
|
|
|
2714
2714
|
.prepare(`SELECT * FROM health_mobile_sync_sessions WHERE id = ?`)
|
|
2715
2715
|
.get(syncSessionId);
|
|
2716
2716
|
}
|
|
2717
|
+
function mobileSyncSessionProgress(syncSessionId) {
|
|
2718
|
+
const chunks = getDatabase()
|
|
2719
|
+
.prepare(`SELECT family, record_count, byte_count
|
|
2720
|
+
FROM health_mobile_sync_chunks
|
|
2721
|
+
WHERE sync_session_id = ?`)
|
|
2722
|
+
.all(syncSessionId);
|
|
2723
|
+
const receivedCounts = {};
|
|
2724
|
+
const byteTotals = {};
|
|
2725
|
+
for (const chunk of chunks) {
|
|
2726
|
+
receivedCounts[chunk.family] =
|
|
2727
|
+
(receivedCounts[chunk.family] ?? 0) + chunk.record_count;
|
|
2728
|
+
byteTotals[chunk.family] =
|
|
2729
|
+
(byteTotals[chunk.family] ?? 0) + chunk.byte_count;
|
|
2730
|
+
}
|
|
2731
|
+
return {
|
|
2732
|
+
receivedCounts,
|
|
2733
|
+
byteTotals,
|
|
2734
|
+
chunkCount: chunks.length,
|
|
2735
|
+
receivedBytes: chunks.reduce((sum, chunk) => sum + chunk.byte_count, 0)
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
function mobileSyncSessionUploadPayload(session, receivedChunkIds) {
|
|
2739
|
+
return {
|
|
2740
|
+
syncSessionId: session.id,
|
|
2741
|
+
schemaVersion: HEALTH_MOBILE_SYNC_SCHEMA_VERSION,
|
|
2742
|
+
status: session.status,
|
|
2743
|
+
chunkTargetBytes: HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES,
|
|
2744
|
+
chunkMaxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
|
|
2745
|
+
chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
|
|
2746
|
+
acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
|
|
2747
|
+
supportsCompression: true,
|
|
2748
|
+
acceptedFamilies: safeJsonParse(session.requested_families_json, []),
|
|
2749
|
+
receivedChunkIds,
|
|
2750
|
+
progress: mobileSyncSessionProgress(session.id)
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
export function getMobileHealthSyncSessionStatus(syncSessionId, payload) {
|
|
2754
|
+
const pairing = requireValidPairing(payload.sessionId, payload.pairingToken);
|
|
2755
|
+
const session = readMobileSyncSession(syncSessionId);
|
|
2756
|
+
if (!session || session.pairing_session_id !== pairing.id) {
|
|
2757
|
+
throw new HttpError(404, "sync_session_not_found", "The HealthKit sync session does not exist.");
|
|
2758
|
+
}
|
|
2759
|
+
const receivedChunkIds = getDatabase()
|
|
2760
|
+
.prepare(`SELECT chunk_id
|
|
2761
|
+
FROM health_mobile_sync_chunks
|
|
2762
|
+
WHERE sync_session_id = ?
|
|
2763
|
+
ORDER BY sequence ASC`)
|
|
2764
|
+
.all(syncSessionId)
|
|
2765
|
+
.map((row) => row.chunk_id);
|
|
2766
|
+
return mobileSyncSessionUploadPayload(session, receivedChunkIds);
|
|
2767
|
+
}
|
|
2717
2768
|
function ensureRunningMobileSyncSession(syncSessionId) {
|
|
2718
2769
|
expireStaleMobileSyncSessions();
|
|
2719
2770
|
const session = readMobileSyncSession(syncSessionId);
|
|
@@ -3038,30 +3089,13 @@ function applyWorkoutEvidenceChunkImmediately(session, payload) {
|
|
|
3038
3089
|
});
|
|
3039
3090
|
}
|
|
3040
3091
|
function updateMobileSyncSessionProgress(syncSessionId) {
|
|
3041
|
-
const
|
|
3042
|
-
.prepare(`SELECT family, record_count, byte_count
|
|
3043
|
-
FROM health_mobile_sync_chunks
|
|
3044
|
-
WHERE sync_session_id = ?`)
|
|
3045
|
-
.all(syncSessionId);
|
|
3046
|
-
const receivedCounts = {};
|
|
3047
|
-
const byteTotals = {};
|
|
3048
|
-
for (const chunk of chunks) {
|
|
3049
|
-
receivedCounts[chunk.family] =
|
|
3050
|
-
(receivedCounts[chunk.family] ?? 0) + chunk.record_count;
|
|
3051
|
-
byteTotals[chunk.family] =
|
|
3052
|
-
(byteTotals[chunk.family] ?? 0) + chunk.byte_count;
|
|
3053
|
-
}
|
|
3092
|
+
const progress = mobileSyncSessionProgress(syncSessionId);
|
|
3054
3093
|
getDatabase()
|
|
3055
3094
|
.prepare(`UPDATE health_mobile_sync_sessions
|
|
3056
3095
|
SET received_counts_json = ?, byte_totals_json = ?, updated_at = ?
|
|
3057
3096
|
WHERE id = ?`)
|
|
3058
|
-
.run(JSON.stringify(receivedCounts), JSON.stringify(byteTotals), nowIso(), syncSessionId);
|
|
3059
|
-
return
|
|
3060
|
-
receivedCounts,
|
|
3061
|
-
byteTotals,
|
|
3062
|
-
chunkCount: chunks.length,
|
|
3063
|
-
receivedBytes: chunks.reduce((sum, chunk) => sum + chunk.byte_count, 0)
|
|
3064
|
-
};
|
|
3097
|
+
.run(JSON.stringify(progress.receivedCounts), JSON.stringify(progress.byteTotals), nowIso(), syncSessionId);
|
|
3098
|
+
return progress;
|
|
3065
3099
|
}
|
|
3066
3100
|
export function startMobileHealthSyncSession(payload) {
|
|
3067
3101
|
const parsed = mobileHealthSyncSessionStartSchema.parse(payload);
|
|
@@ -3092,17 +3126,7 @@ export function startMobileHealthSyncSession(payload) {
|
|
|
3092
3126
|
ORDER BY sequence ASC`)
|
|
3093
3127
|
.all(resumeSyncSessionId)
|
|
3094
3128
|
.map((row) => row.chunk_id);
|
|
3095
|
-
return
|
|
3096
|
-
syncSessionId: resumeSyncSessionId,
|
|
3097
|
-
schemaVersion: HEALTH_MOBILE_SYNC_SCHEMA_VERSION,
|
|
3098
|
-
chunkTargetBytes: HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES,
|
|
3099
|
-
chunkMaxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
|
|
3100
|
-
chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
|
|
3101
|
-
acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
|
|
3102
|
-
supportsCompression: true,
|
|
3103
|
-
acceptedFamilies: existingFamilies,
|
|
3104
|
-
receivedChunkIds
|
|
3105
|
-
};
|
|
3129
|
+
return mobileSyncSessionUploadPayload(existing, receivedChunkIds);
|
|
3106
3130
|
}
|
|
3107
3131
|
getDatabase()
|
|
3108
3132
|
.prepare(`UPDATE health_mobile_sync_sessions
|
|
@@ -3127,17 +3151,11 @@ export function startMobileHealthSyncSession(payload) {
|
|
|
3127
3151
|
sourceStates: parsed.sourceStates,
|
|
3128
3152
|
metadata: parsed.metadata
|
|
3129
3153
|
}), JSON.stringify(parsed.expectedCounts), now, now, now);
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
|
|
3136
|
-
acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
|
|
3137
|
-
supportsCompression: true,
|
|
3138
|
-
acceptedFamilies: parsed.requestedFamilies,
|
|
3139
|
-
receivedChunkIds: []
|
|
3140
|
-
};
|
|
3154
|
+
const session = readMobileSyncSession(syncSessionId);
|
|
3155
|
+
if (!session) {
|
|
3156
|
+
throw new HttpError(500, "sync_session_not_found", "The HealthKit sync session could not be created.");
|
|
3157
|
+
}
|
|
3158
|
+
return mobileSyncSessionUploadPayload(session, []);
|
|
3141
3159
|
}
|
|
3142
3160
|
export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJson) {
|
|
3143
3161
|
const parsed = mobileHealthSyncChunkSchema.parse(payload);
|
|
@@ -28,6 +28,49 @@ const HTTP_METHODS = new Set([
|
|
|
28
28
|
"options",
|
|
29
29
|
"head"
|
|
30
30
|
]);
|
|
31
|
+
const mobileHealthSyncProgressSchema = {
|
|
32
|
+
type: "object",
|
|
33
|
+
required: [
|
|
34
|
+
"chunkCount",
|
|
35
|
+
"receivedBytes",
|
|
36
|
+
"receivedCounts",
|
|
37
|
+
"byteTotals"
|
|
38
|
+
],
|
|
39
|
+
properties: {
|
|
40
|
+
chunkCount: { type: "number" },
|
|
41
|
+
receivedBytes: { type: "number" },
|
|
42
|
+
receivedCounts: { type: "object", additionalProperties: { type: "number" } },
|
|
43
|
+
byteTotals: { type: "object", additionalProperties: { type: "number" } }
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const mobileHealthSyncUploadSchema = {
|
|
47
|
+
type: "object",
|
|
48
|
+
required: [
|
|
49
|
+
"syncSessionId",
|
|
50
|
+
"schemaVersion",
|
|
51
|
+
"status",
|
|
52
|
+
"targetChunkBytes",
|
|
53
|
+
"maxChunkBytes",
|
|
54
|
+
"payloadEncodings",
|
|
55
|
+
"acceptedFamilies",
|
|
56
|
+
"receivedChunkIds",
|
|
57
|
+
"progress"
|
|
58
|
+
],
|
|
59
|
+
properties: {
|
|
60
|
+
syncSessionId: { type: "string" },
|
|
61
|
+
schemaVersion: { type: "string" },
|
|
62
|
+
status: {
|
|
63
|
+
type: "string",
|
|
64
|
+
enum: ["running", "completed", "failed", "aborted"]
|
|
65
|
+
},
|
|
66
|
+
targetChunkBytes: { type: "number" },
|
|
67
|
+
maxChunkBytes: { type: "number" },
|
|
68
|
+
payloadEncodings: arrayOf({ type: "string" }),
|
|
69
|
+
acceptedFamilies: arrayOf({ type: "string" }),
|
|
70
|
+
receivedChunkIds: arrayOf({ type: "string" }),
|
|
71
|
+
progress: mobileHealthSyncProgressSchema
|
|
72
|
+
}
|
|
73
|
+
};
|
|
31
74
|
const CALENDAR_PROVIDER_VALUES = [
|
|
32
75
|
"google",
|
|
33
76
|
"apple",
|
|
@@ -4052,6 +4095,8 @@ export function buildOpenApiDocument() {
|
|
|
4052
4095
|
"reviewShortcutRule",
|
|
4053
4096
|
"readModelWriteRule",
|
|
4054
4097
|
"psycheOpeningQuestionRule",
|
|
4098
|
+
"followUpQuestionRule",
|
|
4099
|
+
"antiDriftRule",
|
|
4055
4100
|
"duplicateCheckRoute",
|
|
4056
4101
|
"uiSuggestionRule",
|
|
4057
4102
|
"browserFallbackRule",
|
|
@@ -4067,6 +4112,8 @@ export function buildOpenApiDocument() {
|
|
|
4067
4112
|
reviewShortcutRule: { type: "string" },
|
|
4068
4113
|
readModelWriteRule: { type: "string" },
|
|
4069
4114
|
psycheOpeningQuestionRule: { type: "string" },
|
|
4115
|
+
followUpQuestionRule: { type: "string" },
|
|
4116
|
+
antiDriftRule: { type: "string" },
|
|
4070
4117
|
duplicateCheckRoute: { type: "string" },
|
|
4071
4118
|
uiSuggestionRule: { type: "string" },
|
|
4072
4119
|
browserFallbackRule: { type: "string" },
|
|
@@ -5291,6 +5338,144 @@ export function buildOpenApiDocument() {
|
|
|
5291
5338
|
}
|
|
5292
5339
|
}
|
|
5293
5340
|
},
|
|
5341
|
+
"/api/v1/mobile/healthkit/sync-sessions": {
|
|
5342
|
+
post: {
|
|
5343
|
+
summary: "Start or resume a resumable mobile HealthKit upload session",
|
|
5344
|
+
requestBody: {
|
|
5345
|
+
required: true,
|
|
5346
|
+
content: {
|
|
5347
|
+
"application/json": {
|
|
5348
|
+
schema: {
|
|
5349
|
+
type: "object",
|
|
5350
|
+
required: ["sessionId", "pairingToken", "schemaVersion"],
|
|
5351
|
+
properties: {
|
|
5352
|
+
sessionId: { type: "string" },
|
|
5353
|
+
pairingToken: { type: "string" },
|
|
5354
|
+
schemaVersion: { type: "string" },
|
|
5355
|
+
resumeSyncSessionId: nullable({ type: "string" }),
|
|
5356
|
+
requestedFamilies: arrayOf({ type: "string" }),
|
|
5357
|
+
sourceStates: arrayOf({
|
|
5358
|
+
type: "object",
|
|
5359
|
+
additionalProperties: true
|
|
5360
|
+
}),
|
|
5361
|
+
device: {
|
|
5362
|
+
type: "object",
|
|
5363
|
+
additionalProperties: true
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
},
|
|
5370
|
+
responses: {
|
|
5371
|
+
"200": jsonResponse({
|
|
5372
|
+
type: "object",
|
|
5373
|
+
required: ["upload"],
|
|
5374
|
+
properties: {
|
|
5375
|
+
upload: mobileHealthSyncUploadSchema
|
|
5376
|
+
}
|
|
5377
|
+
}, "Accepted upload session plus received chunk progress")
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
},
|
|
5381
|
+
"/api/v1/mobile/healthkit/sync-sessions/{id}": {
|
|
5382
|
+
get: {
|
|
5383
|
+
summary: "Inspect a mobile HealthKit upload session and accepted chunk progress",
|
|
5384
|
+
parameters: [
|
|
5385
|
+
{
|
|
5386
|
+
name: "id",
|
|
5387
|
+
in: "path",
|
|
5388
|
+
required: true,
|
|
5389
|
+
schema: { type: "string" }
|
|
5390
|
+
},
|
|
5391
|
+
{
|
|
5392
|
+
name: "sessionId",
|
|
5393
|
+
in: "query",
|
|
5394
|
+
required: true,
|
|
5395
|
+
schema: { type: "string" }
|
|
5396
|
+
},
|
|
5397
|
+
{
|
|
5398
|
+
name: "pairingToken",
|
|
5399
|
+
in: "query",
|
|
5400
|
+
required: true,
|
|
5401
|
+
schema: { type: "string" }
|
|
5402
|
+
}
|
|
5403
|
+
],
|
|
5404
|
+
responses: {
|
|
5405
|
+
"200": jsonResponse({
|
|
5406
|
+
type: "object",
|
|
5407
|
+
required: ["upload"],
|
|
5408
|
+
properties: {
|
|
5409
|
+
upload: mobileHealthSyncUploadSchema
|
|
5410
|
+
}
|
|
5411
|
+
}, "Upload session status and accepted chunk progress")
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
},
|
|
5415
|
+
"/api/v1/mobile/healthkit/sync-sessions/{id}/chunks": {
|
|
5416
|
+
post: {
|
|
5417
|
+
summary: "Upload one idempotent HealthKit chunk into a resumable session",
|
|
5418
|
+
parameters: [
|
|
5419
|
+
{
|
|
5420
|
+
name: "id",
|
|
5421
|
+
in: "path",
|
|
5422
|
+
required: true,
|
|
5423
|
+
schema: { type: "string" }
|
|
5424
|
+
}
|
|
5425
|
+
],
|
|
5426
|
+
requestBody: {
|
|
5427
|
+
required: true,
|
|
5428
|
+
content: {
|
|
5429
|
+
"application/json": {
|
|
5430
|
+
schema: {
|
|
5431
|
+
type: "object",
|
|
5432
|
+
required: [
|
|
5433
|
+
"sessionId",
|
|
5434
|
+
"pairingToken",
|
|
5435
|
+
"chunkId",
|
|
5436
|
+
"family",
|
|
5437
|
+
"sequence",
|
|
5438
|
+
"records",
|
|
5439
|
+
"byteCount",
|
|
5440
|
+
"checksum"
|
|
5441
|
+
],
|
|
5442
|
+
properties: {
|
|
5443
|
+
sessionId: { type: "string" },
|
|
5444
|
+
pairingToken: { type: "string" },
|
|
5445
|
+
chunkId: { type: "string" },
|
|
5446
|
+
family: { type: "string" },
|
|
5447
|
+
sequence: { type: "number" },
|
|
5448
|
+
records: { type: "number" },
|
|
5449
|
+
byteCount: { type: "number" },
|
|
5450
|
+
checksum: { type: "string" },
|
|
5451
|
+
compression: nullable({ type: "string" }),
|
|
5452
|
+
payloadEncoding: nullable({ type: "string" }),
|
|
5453
|
+
payload: {}
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
},
|
|
5459
|
+
responses: {
|
|
5460
|
+
"200": jsonResponse({
|
|
5461
|
+
type: "object",
|
|
5462
|
+
required: ["chunk"],
|
|
5463
|
+
properties: {
|
|
5464
|
+
chunk: {
|
|
5465
|
+
type: "object",
|
|
5466
|
+
additionalProperties: true,
|
|
5467
|
+
required: ["accepted", "duplicate", "progress"],
|
|
5468
|
+
properties: {
|
|
5469
|
+
accepted: { type: "boolean" },
|
|
5470
|
+
duplicate: { type: "boolean" },
|
|
5471
|
+
progress: mobileHealthSyncProgressSchema
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
}, "Chunk receipt with duplicate detection and aggregate progress")
|
|
5476
|
+
}
|
|
5477
|
+
}
|
|
5478
|
+
},
|
|
5294
5479
|
"/api/v1/doctor": {
|
|
5295
5480
|
get: {
|
|
5296
5481
|
summary: "Run Forge Doctor diagnostics for runtime, settings, storage, entities, hierarchy, rewards, and fix proposals",
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.2.90",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED
|
@@ -145,6 +145,8 @@ Concrete route-key examples for internal use:
|
|
|
145
145
|
`{"routeKey":"latestNodeOutput","pathParams":{"id":"flow_research_digest","nodeId":"node_summary"}}`
|
|
146
146
|
- Workbench run execution:
|
|
147
147
|
`{"routeKey":"runFlow","pathParams":{"id":"flow_research_digest"},"body":{"input":{"topic":"question flow quality"}}}`
|
|
148
|
+
- Workbench one-off input execution:
|
|
149
|
+
`{"routeKey":"runByPayload","body":{"flow":{"title":"One-off digest","nodes":[]},"input":{"topic":"question flow quality"}}}`
|
|
148
150
|
- Workbench flow chat follow-up:
|
|
149
151
|
`{"routeKey":"chatFlow","pathParams":{"id":"flow_research_digest"},"body":{"message":"Refine the summary around API route risks and keep the published output stable."}}`
|
|
150
152
|
|
|
@@ -349,7 +351,8 @@ Only ask if missing or unclear:
|
|
|
349
351
|
`project`
|
|
350
352
|
Use for a bounded workstream under a goal.
|
|
351
353
|
Minimum field: `title`
|
|
352
|
-
Usually useful: `goalId`, `description`, `
|
|
354
|
+
Usually useful: `goalId`, `description`, `productRequirementsDocument`,
|
|
355
|
+
`status`, `workflowStatus`, `userId`, `assigneeUserIds`, `schedulingRules`
|
|
353
356
|
Only ask if missing or unclear:
|
|
354
357
|
|
|
355
358
|
1. What should this project be called?
|
|
@@ -357,14 +360,18 @@ Only ask if missing or unclear:
|
|
|
357
360
|
3. What outcome should it produce?
|
|
358
361
|
|
|
359
362
|
`task`
|
|
360
|
-
Use for one concrete
|
|
363
|
+
Use for one concrete work item; the same stored family carries `issue`, `task`, and
|
|
364
|
+
`subtask` levels.
|
|
361
365
|
Minimum field: `title`
|
|
362
|
-
Usually useful: `
|
|
366
|
+
Usually useful: `level`, `projectId`, `parentWorkItemId`, `aiInstructions`,
|
|
367
|
+
`executionMode`, `acceptanceCriteria`, `priority`, `dueDate`, `status`, `userId`,
|
|
368
|
+
`assigneeUserIds`
|
|
363
369
|
Only ask if missing or unclear:
|
|
364
370
|
|
|
365
371
|
1. What is the task in one concrete sentence?
|
|
366
|
-
2.
|
|
367
|
-
3.
|
|
372
|
+
2. Is this an issue, one-session task, or subtask?
|
|
373
|
+
3. Should it live under a project, issue, parent task, or intentional inbox?
|
|
374
|
+
4. Does it need agent instructions, acceptance criteria, a due date, priority, owner, or assignees?
|
|
368
375
|
|
|
369
376
|
`habit`
|
|
370
377
|
Use for a recurring commitment or recurring slip with explicit cadence and XP consequences.
|
|
@@ -370,6 +370,23 @@ Closing turn:
|
|
|
370
370
|
- ask whether it feels true enough to save or needs one correction
|
|
371
371
|
- if the user says yes, move to the write instead of reopening the intake
|
|
372
372
|
|
|
373
|
+
## Second-turn discipline
|
|
374
|
+
|
|
375
|
+
After the user answers the opening question, do not restart the opener and do not
|
|
376
|
+
jump to the next schema field. First say what became clearer in concrete language,
|
|
377
|
+
then choose exactly one next lane: wording, boundary, placement, timing, route scope,
|
|
378
|
+
link, hypothesis, or write confirmation.
|
|
379
|
+
|
|
380
|
+
The second question should be the smallest question that would change the record
|
|
381
|
+
shape, route choice, useful wording, timing, or links. If no answer would change one
|
|
382
|
+
of those things, stop asking, summarize the working record, and act with consent.
|
|
383
|
+
|
|
384
|
+
Do not drift into vague reflection or internal route language. Replace "that sounds
|
|
385
|
+
important" with the specific stake you heard, and replace API words such as surface,
|
|
386
|
+
CRUD, payload, mutation path, or endpoint with product nouns the user recognizes:
|
|
387
|
+
belief, pattern, note, wiki page, timeline, overlay, weekday template, flow, run,
|
|
388
|
+
node result, or published output.
|
|
389
|
+
|
|
373
390
|
## Steering moves
|
|
374
391
|
|
|
375
392
|
Use these small moves to keep the intake natural and intentional.
|
|
@@ -690,17 +707,23 @@ Arc:
|
|
|
690
707
|
1. Ask what this piece of work is trying to make true.
|
|
691
708
|
2. Reflect the emerging boundary so the user can hear what is in scope.
|
|
692
709
|
3. Ask what outcome would make it feel real or complete for now.
|
|
693
|
-
4. Ask what belongs
|
|
710
|
+
4. Ask what belongs in the project PRD or brief when the user is shaping delivery
|
|
711
|
+
rather than only naming a project.
|
|
712
|
+
5. Ask what belongs inside the boundary and what can stay out if the scope still
|
|
694
713
|
feels muddy.
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
714
|
+
6. Ask which goal it belongs under.
|
|
715
|
+
7. Land on a working name once the scope is clear.
|
|
716
|
+
8. Clarify lifecycle status, workflow lane, owner, human/bot assignees, scheduling
|
|
717
|
+
rules, and notes only after the scope is clear.
|
|
698
718
|
|
|
699
719
|
Helpful follow-up lanes:
|
|
700
720
|
|
|
701
721
|
- what concrete outcome would make this project complete enough
|
|
722
|
+
- what should go into the PRD or brief
|
|
702
723
|
- what belongs inside the boundary and what does not
|
|
703
724
|
- which goal gives the project meaning
|
|
725
|
+
- whether one owner or several human/bot assignees need to be explicit
|
|
726
|
+
- whether scheduling rules or a board workflow lane matter now
|
|
704
727
|
|
|
705
728
|
Ready to save when:
|
|
706
729
|
|
|
@@ -744,26 +767,37 @@ Preferred opening question:
|
|
|
744
767
|
|
|
745
768
|
## Task
|
|
746
769
|
|
|
747
|
-
Aim: identify the next concrete
|
|
770
|
+
Aim: identify the next concrete one-session work item and place it correctly in the
|
|
771
|
+
issue/task/subtask hierarchy when that hierarchy matters.
|
|
748
772
|
|
|
749
773
|
Arc:
|
|
750
774
|
|
|
751
775
|
1. Ask what the next concrete action is.
|
|
752
|
-
2. Ask
|
|
753
|
-
|
|
754
|
-
|
|
776
|
+
2. Ask whether it is an issue, one-session task, or subtask only when the level is
|
|
777
|
+
not already obvious.
|
|
778
|
+
3. Ask where it belongs in the hierarchy: project for an issue, issue for a task, or
|
|
779
|
+
parent task for a subtask. Use goal or standalone only when the user is
|
|
780
|
+
intentionally outside the PM hierarchy.
|
|
781
|
+
4. Capture the execution contract in `aiInstructions` when the work is meant for an
|
|
782
|
+
AI or agent session.
|
|
783
|
+
5. Ask what would make it easier to do: due date, priority, owner, human/bot
|
|
784
|
+
assignees, acceptance criteria, or one line of context.
|
|
755
785
|
|
|
756
786
|
Helpful follow-up lanes:
|
|
757
787
|
|
|
758
788
|
- turn vague intent into an actionable verb
|
|
759
|
-
-
|
|
760
|
-
-
|
|
789
|
+
- decide whether the work item is an issue, task, or subtask
|
|
790
|
+
- identify parent project, issue, or task
|
|
791
|
+
- capture the one-session execution contract in `aiInstructions`
|
|
792
|
+
- decide whether one owner or several human/bot assignees need to be explicit
|
|
793
|
+
- capture the one timing, priority, or acceptance detail that will actually help
|
|
761
794
|
|
|
762
795
|
Ready to save when:
|
|
763
796
|
|
|
764
797
|
- the task is phrased as an actionable move
|
|
765
|
-
-
|
|
766
|
-
-
|
|
798
|
+
- the level is clear enough: issue, task, or subtask
|
|
799
|
+
- placement is clear enough: project, issue, parent task, or intentional inbox
|
|
800
|
+
- any crucial timing, acceptance criteria, or execution instruction is captured
|
|
767
801
|
|
|
768
802
|
Preferred opening question:
|
|
769
803
|
|
|
@@ -1760,7 +1794,8 @@ Direct action rules:
|
|
|
1760
1794
|
"catalog" read when the user needs a runnable flow versus an input-box contract.
|
|
1761
1795
|
- If the user wants to execute a known saved flow, use `/api/v1/workbench/flows/:id/run`.
|
|
1762
1796
|
- If the user wants one-off input execution without depending on a saved flow id, use
|
|
1763
|
-
|
|
1797
|
+
`POST /api/v1/workbench/run` through the dedicated one-off execution lane and keep
|
|
1798
|
+
the user-facing question about the one-off input contract.
|
|
1764
1799
|
- If the user wants to debug one failed execution, narrow whether they need the run
|
|
1765
1800
|
detail, one node result, the latest node output, or the published output before you
|
|
1766
1801
|
ask for flow changes.
|
|
@@ -339,6 +339,23 @@ Closing turn:
|
|
|
339
339
|
linked
|
|
340
340
|
- if the user says it lands, move toward the write instead of reopening exploration
|
|
341
341
|
|
|
342
|
+
## Second-turn therapeutic discipline
|
|
343
|
+
|
|
344
|
+
After the user's first real answer, do not simply ask another broad origin question
|
|
345
|
+
and do not move into repair because the schema has optional fields left. Briefly name
|
|
346
|
+
the emotional center, function, danger, or value conflict that became clearer, then
|
|
347
|
+
choose one next lane: situation, sequence, meaning, protection, cost, longing,
|
|
348
|
+
naming, hypothesis, link, or save confirmation.
|
|
349
|
+
|
|
350
|
+
When one concrete example is already clear, a useful second or third turn can be a
|
|
351
|
+
careful interpretive hypothesis rather than another request for raw material. The
|
|
352
|
+
hypothesis should name what the response may be protecting, predicting, relieving, or
|
|
353
|
+
costing, then ask whether that lands or needs correction.
|
|
354
|
+
|
|
355
|
+
If the accepted formulation is already accurate enough to save, stop deepening. Ask
|
|
356
|
+
one accuracy question, preserve the user's wording, and write through the shared
|
|
357
|
+
batch entity route after consent.
|
|
358
|
+
|
|
342
359
|
## Name, Define, Connect
|
|
343
360
|
|
|
344
361
|
Use this checkpoint once the material is coherent enough.
|