forge-openclaw-plugin 0.2.43 → 0.2.45
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/assets/index-BejDHw1R.js +91 -0
- package/dist/assets/index-BejDHw1R.js.map +1 -0
- package/dist/assets/index-DtEvFzXp.css +1 -0
- package/dist/index.html +2 -2
- package/dist/openclaw/api-client.d.ts +1 -0
- package/dist/openclaw/plugin-entry-shared.js +13 -0
- package/dist/openclaw/session-bootstrap.d.ts +11 -0
- package/dist/openclaw/session-bootstrap.js +151 -13
- package/dist/openclaw/tools.js +42 -10
- package/dist/server/server/migrations/050_agent_token_bootstrap_policy.sql +2 -0
- package/dist/server/server/migrations/051_agent_token_scope_policy.sql +2 -0
- package/dist/server/server/src/app.js +286 -51
- package/dist/server/server/src/managers/platform/authentication-manager.js +5 -0
- package/dist/server/server/src/managers/platform/session-manager.js +5 -0
- package/dist/server/server/src/movement.js +2 -3
- package/dist/server/server/src/openapi.js +78 -1
- package/dist/server/server/src/repositories/settings.js +26 -6
- package/dist/server/server/src/types.js +83 -1
- package/dist/server/src/lib/schemas.js +16 -1
- package/openclaw.plugin.json +10 -1
- package/package.json +1 -1
- package/server/migrations/050_agent_token_bootstrap_policy.sql +2 -0
- package/server/migrations/051_agent_token_scope_policy.sql +2 -0
- package/skills/forge-openclaw/entity_conversation_playbooks.md +14 -4
- package/dist/assets/index-Bqqi_RSB.js +0 -91
- package/dist/assets/index-Bqqi_RSB.js.map +0 -1
- package/dist/assets/index-IrxlatFN.css +0 -1
|
@@ -56,7 +56,7 @@ import { PSYCHE_ENTITY_TYPES, createBehaviorSchema, createBeliefEntrySchema, cre
|
|
|
56
56
|
import { createQuestionnaireInstrumentSchema, publishQuestionnaireVersionSchema, startQuestionnaireRunSchema, updateQuestionnaireRunSchema, updateQuestionnaireVersionSchema } from "./questionnaire-types.js";
|
|
57
57
|
import { createPreferenceCatalogItemSchema, createPreferenceCatalogSchema, createPreferenceContextSchema, createPreferenceItemSchema, enqueueEntityPreferenceItemSchema, mergePreferenceContextsSchema, preferenceWorkspaceQuerySchema, startPreferenceGameSchema, submitAbsoluteSignalSchema, submitPairwiseJudgmentSchema, updatePreferenceCatalogItemSchema, updatePreferenceCatalogSchema, updatePreferenceContextSchema, updatePreferenceItemSchema, updatePreferenceScoreSchema } from "./preferences-types.js";
|
|
58
58
|
import { createDataBackupSchema, dataExportQuerySchema, restoreDataBackupSchema, switchDataRootSchema, updateDataManagementSettingsSchema } from "./data-management-types.js";
|
|
59
|
-
import { activityListQuerySchema, activitySourceSchema, createAgentActionSchema, createAgentRuntimeSessionEventSchema, createAgentRuntimeSessionSchema, createAgentTokenSchema, createAiConnectorSchema, createAiProcessorLinkSchema, createAiProcessorSchema, runAiConnectorSchema, writeSurfaceLayoutSchema, upsertAiModelConnectionSchema, testAiModelConnectionSchema, submitOpenAiCodexOauthManualCodeSchema, batchCreateEntitiesSchema, batchDeleteEntitiesSchema, batchRestoreEntitiesSchema, batchSearchEntitiesSchema, batchUpdateEntitiesSchema, createGoalSchema, createInsightFeedbackSchema, createInsightSchema, createStrategySchema, createUserSchema, createNoteSchema, createProjectSchema, createManualRewardGrantSchema, createCalendarEventSchema, createHabitCheckInSchema, createCalendarConnectionSchema, createDiagnosticLogSchema, discoverCalendarConnectionSchema, startGoogleCalendarOauthSchema, startMicrosoftCalendarOauthSchema, testMicrosoftCalendarOauthConfigurationSchema, createHabitSchema, createTaskTimeboxSchema, createWorkBlockTemplateSchema, createSessionEventSchema, createWorkAdjustmentSchema, createTagSchema, calendarOverviewQuerySchema, psycheObservationCalendarExportQuerySchema, notesListQuerySchema, updateTagSchema, createTaskSchema, diagnosticLogListQuerySchema, disconnectAgentRuntimeSessionSchema, eventsListQuerySchema, heartbeatAgentRuntimeSessionSchema, operatorLogWorkSchema, projectBoardPayloadSchema, projectListQuerySchema, entityDeleteQuerySchema, removeActivityEventSchema, reconnectAgentRuntimeSessionSchema, resolveApprovalRequestSchema, rewardsLedgerQuerySchema, habitListQuerySchema, taskContextPayloadSchema, taskRunClaimSchema, taskRunFocusSchema, taskRunFinishSchema, taskRunHeartbeatSchema, taskRunListQuerySchema, taskSplitCreateSchema, taskListQuerySchema, tagSuggestionRequestSchema, uncompleteTaskSchema, updateSettingsSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateStrategySchema, updateUserSchema, updateCalendarConnectionSchema, updateCalendarEventSchema, updateNoteSchema, updateProjectSchema, updateRewardRuleSchema, updateTaskTimeboxSchema, updateTaskSchema, lifeForceProfilePatchSchema, lifeForceTemplateUpdateSchema, fatigueSignalCreateSchema, updateUserAccessGrantSchema, updateWorkBlockTemplateSchema, updateAiConnectorSchema, updateAiProcessorSchema, runAiProcessorSchema, workAdjustmentResultSchema, finalizeWeeklyReviewResultSchema, goalListQuerySchema, recommendTaskTimeboxesSchema, strategyListQuerySchema } from "./types.js";
|
|
59
|
+
import { activityListQuerySchema, activitySourceSchema, defaultAgentBootstrapPolicy, defaultAgentScopePolicy, createAgentActionSchema, createAgentRuntimeSessionEventSchema, createAgentRuntimeSessionSchema, createAgentTokenSchema, createAiConnectorSchema, createAiProcessorLinkSchema, createAiProcessorSchema, runAiConnectorSchema, writeSurfaceLayoutSchema, upsertAiModelConnectionSchema, testAiModelConnectionSchema, submitOpenAiCodexOauthManualCodeSchema, batchCreateEntitiesSchema, batchDeleteEntitiesSchema, batchRestoreEntitiesSchema, batchSearchEntitiesSchema, batchUpdateEntitiesSchema, createGoalSchema, createInsightFeedbackSchema, createInsightSchema, createStrategySchema, createUserSchema, createNoteSchema, createProjectSchema, createManualRewardGrantSchema, createCalendarEventSchema, createHabitCheckInSchema, createCalendarConnectionSchema, createDiagnosticLogSchema, discoverCalendarConnectionSchema, startGoogleCalendarOauthSchema, startMicrosoftCalendarOauthSchema, testMicrosoftCalendarOauthConfigurationSchema, createHabitSchema, createTaskTimeboxSchema, createWorkBlockTemplateSchema, createSessionEventSchema, createWorkAdjustmentSchema, createTagSchema, calendarOverviewQuerySchema, psycheObservationCalendarExportQuerySchema, notesListQuerySchema, updateTagSchema, createTaskSchema, diagnosticLogListQuerySchema, disconnectAgentRuntimeSessionSchema, eventsListQuerySchema, heartbeatAgentRuntimeSessionSchema, operatorLogWorkSchema, projectBoardPayloadSchema, projectListQuerySchema, entityDeleteQuerySchema, removeActivityEventSchema, reconnectAgentRuntimeSessionSchema, resolveApprovalRequestSchema, rewardsLedgerQuerySchema, habitListQuerySchema, taskContextPayloadSchema, taskRunClaimSchema, taskRunFocusSchema, taskRunFinishSchema, taskRunHeartbeatSchema, taskRunListQuerySchema, taskSplitCreateSchema, taskListQuerySchema, tagSuggestionRequestSchema, uncompleteTaskSchema, updateSettingsSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateStrategySchema, updateUserSchema, updateCalendarConnectionSchema, updateCalendarEventSchema, updateNoteSchema, updateProjectSchema, updateRewardRuleSchema, updateTaskTimeboxSchema, updateTaskSchema, lifeForceProfilePatchSchema, lifeForceTemplateUpdateSchema, fatigueSignalCreateSchema, updateUserAccessGrantSchema, updateWorkBlockTemplateSchema, updateAiConnectorSchema, updateAiProcessorSchema, runAiProcessorSchema, workAdjustmentResultSchema, finalizeWeeklyReviewResultSchema, goalListQuerySchema, recommendTaskTimeboxesSchema, strategyListQuerySchema } from "./types.js";
|
|
60
60
|
import { buildOpenApiDocument } from "./openapi.js";
|
|
61
61
|
import { registerWebRoutes } from "./web.js";
|
|
62
62
|
import { createManagerRuntime } from "./managers/runtime.js";
|
|
@@ -1980,6 +1980,29 @@ function buildPreferredMutationPath(entityType) {
|
|
|
1980
1980
|
return "Read-only surface.";
|
|
1981
1981
|
}
|
|
1982
1982
|
}
|
|
1983
|
+
function buildPreferredMutationTool(entityType) {
|
|
1984
|
+
if (entityType in AGENT_ONBOARDING_BATCH_ROUTE_BASES) {
|
|
1985
|
+
return "forge_create_entities | forge_update_entities | forge_delete_entities | forge_search_entities";
|
|
1986
|
+
}
|
|
1987
|
+
switch (entityType) {
|
|
1988
|
+
case "wiki_page":
|
|
1989
|
+
return "forge_upsert_wiki_page";
|
|
1990
|
+
case "calendar_connection":
|
|
1991
|
+
return "forge_connect_calendar_provider | forge_sync_calendar_connection";
|
|
1992
|
+
case "task_run":
|
|
1993
|
+
return "forge_start_task_run | forge_heartbeat_task_run | forge_focus_task_run | forge_complete_task_run | forge_release_task_run";
|
|
1994
|
+
case "questionnaire_run":
|
|
1995
|
+
return "forge_start_questionnaire_run | forge_update_questionnaire_run | forge_complete_questionnaire_run";
|
|
1996
|
+
case "preference_judgment":
|
|
1997
|
+
return "forge_submit_preferences_judgment";
|
|
1998
|
+
case "preference_signal":
|
|
1999
|
+
return "forge_submit_preferences_signal";
|
|
2000
|
+
case "work_adjustment":
|
|
2001
|
+
return "forge_adjust_work_minutes";
|
|
2002
|
+
default:
|
|
2003
|
+
return null;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
1983
2006
|
function buildPreferredReadPath(entityType) {
|
|
1984
2007
|
if (entityType in AGENT_ONBOARDING_BATCH_ROUTE_BASES) {
|
|
1985
2008
|
return AGENT_ONBOARDING_BATCH_ROUTE_BASES[entityType];
|
|
@@ -2024,11 +2047,10 @@ function enrichOnboardingEntityGuide(entry) {
|
|
|
2024
2047
|
: null,
|
|
2025
2048
|
preferredMutationPath: buildPreferredMutationPath(entry.entityType),
|
|
2026
2049
|
preferredReadPath: buildPreferredReadPath(entry.entityType),
|
|
2027
|
-
preferredMutationTool:
|
|
2028
|
-
|
|
2029
|
-
: classification === "specialized_domain_surface"
|
|
2050
|
+
preferredMutationTool: buildPreferredMutationTool(entry.entityType) ??
|
|
2051
|
+
(classification === "specialized_domain_surface"
|
|
2030
2052
|
? "Follow forge_get_agent_onboarding.entityRouteModel.specializedDomainSurfaces for the dedicated route family."
|
|
2031
|
-
: null
|
|
2053
|
+
: null)
|
|
2032
2054
|
};
|
|
2033
2055
|
}
|
|
2034
2056
|
const AGENT_ONBOARDING_ENTITY_CATALOG = [
|
|
@@ -2758,7 +2780,8 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
|
|
|
2758
2780
|
"For specialized surfaces, ask what would make the answer or change useful before you ask route-shaped details such as provider, weekday, flow id, run id, or trip id.",
|
|
2759
2781
|
"When the user already named a precise correction or review target, do not widen back out into a meta lane question. Confirm only the missing route-selecting detail and then act.",
|
|
2760
2782
|
"Once the route family is clear, say it plainly enough that another agent could follow the same path without guessing.",
|
|
2761
|
-
"For Movement specifically, treat missing-data corrections as user-defined overlay boxes unless the user is editing an already-recorded stay or trip. When the user already gave a clear instruction like 'that missing block was home', act after only the last ambiguity is resolved."
|
|
2783
|
+
"For Movement specifically, treat missing-data corrections as user-defined overlay boxes unless the user is editing an already-recorded stay or trip. When the user already gave a clear instruction like 'that missing block was home', act after only the last ambiguity is resolved.",
|
|
2784
|
+
"For meaning-bearing updates, especially in Psyche, briefly say what feels newly true before you ask for the one structural detail that still changes the save."
|
|
2762
2785
|
];
|
|
2763
2786
|
const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
2764
2787
|
{
|
|
@@ -2917,7 +2940,8 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
2917
2940
|
"Confirm the task.",
|
|
2918
2941
|
"Confirm the actor only if it is not already obvious.",
|
|
2919
2942
|
"Ask whether the run should be planned or unlimited only if that changes the action.",
|
|
2920
|
-
"Start the run instead of turning it into a longer intake."
|
|
2943
|
+
"Start the run instead of turning it into a longer intake.",
|
|
2944
|
+
"Use the dedicated task-run tool for start, heartbeat, focus, complete, and release work instead of bouncing to the UI or a generic web route."
|
|
2921
2945
|
]
|
|
2922
2946
|
},
|
|
2923
2947
|
{
|
|
@@ -3071,6 +3095,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
3071
3095
|
"Ask whether the focus is a stay, a trip, a place, a timeline window, or a selected span.",
|
|
3072
3096
|
"Ask for the time window, place, or movement item that makes the question concrete.",
|
|
3073
3097
|
"Ask what they are trying to notice, preserve, or answer through that movement context.",
|
|
3098
|
+
"Choose the dedicated day, month, all-time, timeline, places, trip-detail, or selection route once the question shape is clear.",
|
|
3074
3099
|
"Skip the meta lane question when the user already named the exact correction or review target and only one ambiguity remains.",
|
|
3075
3100
|
"If the request is filling a missing-data gap, use a user-defined movement box rather than a raw stay or trip patch.",
|
|
3076
3101
|
"If the request is repairing already-saved movement data, use the repair route that matches the saved object instead of treating it like a missing span.",
|
|
@@ -3104,6 +3129,7 @@ const AGENT_ONBOARDING_ENTITY_CONVERSATION_PLAYBOOKS = [
|
|
|
3104
3129
|
"Ask whether they need the flow contract, a run result, a published output, or a node result.",
|
|
3105
3130
|
"Ask what the user is trying to learn, repair, or publish through that flow.",
|
|
3106
3131
|
"If the user already named the flow and action clearly, skip the meta lane question and ask only for the missing run, node, or output scope.",
|
|
3132
|
+
"If the user wants a stable public input contract or published output, prefer those dedicated reads instead of detouring through run history first.",
|
|
3107
3133
|
"If the user is still shaping a payload or edit, prefer flow detail or box catalog reads before asking for structured inputs.",
|
|
3108
3134
|
"Prefer flow detail or published-output reads for stable contracts, and use run or node-result routes only when the user is asking about execution history or debugging.",
|
|
3109
3135
|
"Route to the dedicated workbench route family once the execution lane is clear."
|
|
@@ -3853,8 +3879,66 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
|
|
|
3853
3879
|
example: '{"taskRunId":"run_123","actor":"aurel","note":"Stopping for now; blocked on feedback","closeoutNote":{"contentMarkdown":"Blocked on feedback from design before I can continue."}}'
|
|
3854
3880
|
}
|
|
3855
3881
|
];
|
|
3882
|
+
const VALIDATION_ROUTE_TOOL_MAP = {
|
|
3883
|
+
"POST /api/v1/entities/search": "forge_search_entities",
|
|
3884
|
+
"POST /api/v1/entities/create": "forge_create_entities",
|
|
3885
|
+
"POST /api/v1/entities/update": "forge_update_entities",
|
|
3886
|
+
"POST /api/v1/entities/delete": "forge_delete_entities",
|
|
3887
|
+
"POST /api/v1/entities/restore": "forge_restore_entities",
|
|
3888
|
+
"POST /api/v1/tasks/:id/runs": "forge_start_task_run",
|
|
3889
|
+
"POST /api/v1/task-runs/:id/heartbeat": "forge_heartbeat_task_run",
|
|
3890
|
+
"POST /api/v1/task-runs/:id/focus": "forge_focus_task_run",
|
|
3891
|
+
"POST /api/v1/task-runs/:id/complete": "forge_complete_task_run",
|
|
3892
|
+
"POST /api/v1/task-runs/:id/release": "forge_release_task_run",
|
|
3893
|
+
"POST /api/tasks/:id/runs": "forge_start_task_run",
|
|
3894
|
+
"POST /api/task-runs/:id/heartbeat": "forge_heartbeat_task_run",
|
|
3895
|
+
"POST /api/task-runs/:id/focus": "forge_focus_task_run",
|
|
3896
|
+
"POST /api/task-runs/:id/complete": "forge_complete_task_run",
|
|
3897
|
+
"POST /api/task-runs/:id/release": "forge_release_task_run"
|
|
3898
|
+
};
|
|
3899
|
+
function formatValidationSummary(issues) {
|
|
3900
|
+
return issues
|
|
3901
|
+
.slice(0, 3)
|
|
3902
|
+
.map((issue) => `${issue.path}: ${issue.message}`)
|
|
3903
|
+
.join("; ");
|
|
3904
|
+
}
|
|
3905
|
+
function buildValidationHelp(method, routeUrl, issues) {
|
|
3906
|
+
const route = routeUrl || "unknown_route";
|
|
3907
|
+
const toolName = VALIDATION_ROUTE_TOOL_MAP[`${method.toUpperCase()} ${route}`];
|
|
3908
|
+
const toolInput = toolName
|
|
3909
|
+
? AGENT_ONBOARDING_TOOL_INPUT_CATALOG.find((entry) => entry.toolName === toolName)
|
|
3910
|
+
: undefined;
|
|
3911
|
+
return {
|
|
3912
|
+
route,
|
|
3913
|
+
validationSummary: formatValidationSummary(issues),
|
|
3914
|
+
...(toolInput
|
|
3915
|
+
? {
|
|
3916
|
+
expectedShape: {
|
|
3917
|
+
toolName: toolInput.toolName,
|
|
3918
|
+
inputShape: toolInput.inputShape,
|
|
3919
|
+
requiredFields: [...toolInput.requiredFields],
|
|
3920
|
+
example: toolInput.example,
|
|
3921
|
+
notes: [...toolInput.notes]
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
: {
|
|
3925
|
+
expectedShape: {
|
|
3926
|
+
inputShape: "See details[] for the rejected fields on this route.",
|
|
3927
|
+
requiredFields: [],
|
|
3928
|
+
example: null,
|
|
3929
|
+
notes: [
|
|
3930
|
+
"Retry with only the documented top-level keys for this route.",
|
|
3931
|
+
"Omit optional fields instead of sending null unless the route contract explicitly allows null."
|
|
3932
|
+
]
|
|
3933
|
+
}
|
|
3934
|
+
})
|
|
3935
|
+
};
|
|
3936
|
+
}
|
|
3856
3937
|
function buildAgentOnboardingPayload(request) {
|
|
3857
3938
|
const origin = getRequestOrigin(request);
|
|
3939
|
+
const auth = parseRequestAuth(request.headers);
|
|
3940
|
+
const effectiveBootstrapPolicy = auth.token?.bootstrapPolicy ?? defaultAgentBootstrapPolicy;
|
|
3941
|
+
const effectiveScopePolicy = auth.token?.scopePolicy ?? defaultAgentScopePolicy;
|
|
3858
3942
|
return {
|
|
3859
3943
|
forgeBaseUrl: origin,
|
|
3860
3944
|
webAppUrl: `${origin}/forge/`,
|
|
@@ -3881,6 +3965,10 @@ function buildAgentOnboardingPayload(request) {
|
|
|
3881
3965
|
recommendedTrustLevel: "trusted",
|
|
3882
3966
|
recommendedAutonomyMode: "approval_required",
|
|
3883
3967
|
recommendedApprovalMode: "approval_by_default",
|
|
3968
|
+
defaultBootstrapPolicy: defaultAgentBootstrapPolicy,
|
|
3969
|
+
effectiveBootstrapPolicy,
|
|
3970
|
+
defaultScopePolicy: defaultAgentScopePolicy,
|
|
3971
|
+
effectiveScopePolicy,
|
|
3884
3972
|
authModes: {
|
|
3885
3973
|
operatorSession: {
|
|
3886
3974
|
label: "Quick connect",
|
|
@@ -3890,7 +3978,7 @@ function buildAgentOnboardingPayload(request) {
|
|
|
3890
3978
|
},
|
|
3891
3979
|
managedToken: {
|
|
3892
3980
|
label: "Managed token",
|
|
3893
|
-
summary: "Use a long-lived token when you want explicit scoped auth, remote non-Tailscale access,
|
|
3981
|
+
summary: "Use a long-lived token when you want explicit scoped auth, remote non-Tailscale access, durable agent credentials, a custom bootstrap budget, or default user/project/tag read boundaries per agent.",
|
|
3894
3982
|
tokenRequired: true
|
|
3895
3983
|
}
|
|
3896
3984
|
},
|
|
@@ -4471,6 +4559,11 @@ function parseRequestAuth(headers) {
|
|
|
4471
4559
|
const source = token ? "agent" : activity.source;
|
|
4472
4560
|
return {
|
|
4473
4561
|
token,
|
|
4562
|
+
scope: {
|
|
4563
|
+
userIds: token?.scopePolicy.userIds ?? [],
|
|
4564
|
+
projectIds: token?.scopePolicy.projectIds ?? [],
|
|
4565
|
+
tagIds: token?.scopePolicy.tagIds ?? []
|
|
4566
|
+
},
|
|
4474
4567
|
actor,
|
|
4475
4568
|
source,
|
|
4476
4569
|
activity: {
|
|
@@ -4560,6 +4653,105 @@ function resolveScopedUserIds(query) {
|
|
|
4560
4653
|
const unique = Array.from(new Set(values));
|
|
4561
4654
|
return unique.length > 0 ? unique : undefined;
|
|
4562
4655
|
}
|
|
4656
|
+
const EMPTY_SCOPED_USER_IDS = ["__forge_scope_none__"];
|
|
4657
|
+
function normalizeScopedUserIdsForReads(options) {
|
|
4658
|
+
const validUserIdSet = new Set(options.validUserIds);
|
|
4659
|
+
const validScopedUserIds = options.scope.userIds !== undefined
|
|
4660
|
+
? options.scope.userIds.filter((userId) => validUserIdSet.has(userId))
|
|
4661
|
+
: undefined;
|
|
4662
|
+
const scopedUserIdsForReads = options.scope.enforceUserIds &&
|
|
4663
|
+
validScopedUserIds !== undefined &&
|
|
4664
|
+
validScopedUserIds.length === 0
|
|
4665
|
+
? EMPTY_SCOPED_USER_IDS
|
|
4666
|
+
: validScopedUserIds && validScopedUserIds.length > 0
|
|
4667
|
+
? validScopedUserIds
|
|
4668
|
+
: undefined;
|
|
4669
|
+
return {
|
|
4670
|
+
validScopedUserIds,
|
|
4671
|
+
scopedUserIdsForReads
|
|
4672
|
+
};
|
|
4673
|
+
}
|
|
4674
|
+
function resolveEffectiveReadScope(query, auth) {
|
|
4675
|
+
const requestedUserIds = resolveScopedUserIds(query);
|
|
4676
|
+
const tokenUserIds = auth.token?.scopePolicy.userIds ?? [];
|
|
4677
|
+
const effectiveUserIds = tokenUserIds.length > 0
|
|
4678
|
+
? requestedUserIds
|
|
4679
|
+
? requestedUserIds.filter((userId) => tokenUserIds.includes(userId))
|
|
4680
|
+
: [...tokenUserIds]
|
|
4681
|
+
: requestedUserIds;
|
|
4682
|
+
return {
|
|
4683
|
+
userIds: effectiveUserIds !== undefined ? Array.from(new Set(effectiveUserIds)) : undefined,
|
|
4684
|
+
enforceUserIds: tokenUserIds.length > 0,
|
|
4685
|
+
projectIds: auth.token?.scopePolicy.projectIds ?? [],
|
|
4686
|
+
tagIds: auth.token?.scopePolicy.tagIds ?? []
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
function hasScopeFilters(scope) {
|
|
4690
|
+
return scope.projectIds.length > 0 || scope.tagIds.length > 0;
|
|
4691
|
+
}
|
|
4692
|
+
function intersects(values, allowed) {
|
|
4693
|
+
if (!values || values.length === 0) {
|
|
4694
|
+
return false;
|
|
4695
|
+
}
|
|
4696
|
+
return values.some((value) => allowed.includes(value));
|
|
4697
|
+
}
|
|
4698
|
+
function applyTaskScope(tasks, scope) {
|
|
4699
|
+
if (!hasScopeFilters(scope)) {
|
|
4700
|
+
return tasks;
|
|
4701
|
+
}
|
|
4702
|
+
return tasks.filter((task) => {
|
|
4703
|
+
if (scope.projectIds.length > 0 &&
|
|
4704
|
+
(!task.projectId || !scope.projectIds.includes(task.projectId))) {
|
|
4705
|
+
return false;
|
|
4706
|
+
}
|
|
4707
|
+
if (scope.tagIds.length > 0 && !intersects(task.tagIds, scope.tagIds)) {
|
|
4708
|
+
return false;
|
|
4709
|
+
}
|
|
4710
|
+
return true;
|
|
4711
|
+
});
|
|
4712
|
+
}
|
|
4713
|
+
function applyProjectScope(projects, scope) {
|
|
4714
|
+
if (!hasScopeFilters(scope)) {
|
|
4715
|
+
return projects;
|
|
4716
|
+
}
|
|
4717
|
+
return projects.filter((project) => {
|
|
4718
|
+
if (scope.projectIds.length > 0 &&
|
|
4719
|
+
!scope.projectIds.includes(project.id)) {
|
|
4720
|
+
return false;
|
|
4721
|
+
}
|
|
4722
|
+
if (scope.tagIds.length > 0 &&
|
|
4723
|
+
!intersects(project.tagIds ?? undefined, scope.tagIds)) {
|
|
4724
|
+
return false;
|
|
4725
|
+
}
|
|
4726
|
+
return true;
|
|
4727
|
+
});
|
|
4728
|
+
}
|
|
4729
|
+
function applyGoalScope(goals, scope, allowedGoalIds) {
|
|
4730
|
+
if (!hasScopeFilters(scope)) {
|
|
4731
|
+
return goals;
|
|
4732
|
+
}
|
|
4733
|
+
return goals.filter((goal) => (scope.tagIds.length > 0 &&
|
|
4734
|
+
intersects(goal.tagIds ?? undefined, scope.tagIds)) ||
|
|
4735
|
+
allowedGoalIds.has(goal.id));
|
|
4736
|
+
}
|
|
4737
|
+
function applyHabitScope(habits, scope, allowedGoalIds, allowedTaskIds) {
|
|
4738
|
+
if (!hasScopeFilters(scope)) {
|
|
4739
|
+
return habits;
|
|
4740
|
+
}
|
|
4741
|
+
return habits.filter((habit) => intersects(habit.linkedProjectIds, scope.projectIds) ||
|
|
4742
|
+
intersects(habit.linkedTaskIds, [...allowedTaskIds]) ||
|
|
4743
|
+
intersects(habit.linkedGoalIds, [...allowedGoalIds]));
|
|
4744
|
+
}
|
|
4745
|
+
function applyStrategyScope(strategies, scope, allowedGoalIds) {
|
|
4746
|
+
if (!hasScopeFilters(scope)) {
|
|
4747
|
+
return strategies;
|
|
4748
|
+
}
|
|
4749
|
+
return strategies.filter((strategy) => intersects(strategy.targetProjectIds, scope.projectIds) ||
|
|
4750
|
+
intersects(strategy.targetGoalIds, [...allowedGoalIds]) ||
|
|
4751
|
+
strategy.linkedEntities.some((link) => (link.entityType === "project" &&
|
|
4752
|
+
scope.projectIds.includes(link.entityId)) ||
|
|
4753
|
+
(link.entityType === "goal" && allowedGoalIds.has(link.entityId))));
|
|
4754
|
+
}
|
|
4563
4755
|
function attachMovementTimelineSleepOverlays(movement, userIds) {
|
|
4564
4756
|
const rangeStart = movement.segments.reduce((earliest, segment) => {
|
|
4565
4757
|
if (!earliest || Date.parse(segment.startedAt) < Date.parse(earliest)) {
|
|
@@ -4606,18 +4798,26 @@ function syncEntityOwnerFromBody(options) {
|
|
|
4606
4798
|
const owner = resolveUserForMutation(requestedUserId, options.fallbackLabel);
|
|
4607
4799
|
setEntityOwner(options.entityType, options.entityId, owner.id);
|
|
4608
4800
|
}
|
|
4609
|
-
function buildV1Context(
|
|
4801
|
+
function buildV1Context(scope = {
|
|
4802
|
+
userIds: undefined,
|
|
4803
|
+
enforceUserIds: false,
|
|
4804
|
+
projectIds: [],
|
|
4805
|
+
tagIds: []
|
|
4806
|
+
}) {
|
|
4610
4807
|
const users = listUsers();
|
|
4611
|
-
const
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
const
|
|
4616
|
-
const tasks = filterOwnedEntities("task", listTasks(),
|
|
4617
|
-
const
|
|
4618
|
-
|
|
4619
|
-
const
|
|
4620
|
-
|
|
4808
|
+
const { validScopedUserIds, scopedUserIdsForReads } = normalizeScopedUserIdsForReads({
|
|
4809
|
+
scope,
|
|
4810
|
+
validUserIds: users.map((user) => user.id)
|
|
4811
|
+
});
|
|
4812
|
+
const projects = applyProjectScope(listProjectSummaries({ userIds: scopedUserIdsForReads }), scope);
|
|
4813
|
+
const tasks = applyTaskScope(filterOwnedEntities("task", listTasks(), scopedUserIdsForReads), scope);
|
|
4814
|
+
const goals = applyGoalScope(filterOwnedEntities("goal", listGoals(), scopedUserIdsForReads), scope, new Set([...projects.map((project) => project.goalId), ...tasks.map((task) => task.goalId)]
|
|
4815
|
+
.filter((value) => typeof value === "string" && value.length > 0)));
|
|
4816
|
+
const habits = applyHabitScope(filterOwnedEntities("habit", listHabits(), scopedUserIdsForReads), scope, new Set(goals.map((goal) => goal.id)), new Set(tasks.map((task) => task.id)));
|
|
4817
|
+
const strategies = applyStrategyScope(listStrategies({ userIds: scopedUserIdsForReads }), scope, new Set(goals.map((goal) => goal.id)));
|
|
4818
|
+
const dashboard = getDashboard({ userIds: scopedUserIdsForReads });
|
|
4819
|
+
const selectedUsers = validScopedUserIds !== undefined
|
|
4820
|
+
? users.filter((user) => validScopedUserIds.includes(user.id))
|
|
4621
4821
|
: users;
|
|
4622
4822
|
return {
|
|
4623
4823
|
meta: {
|
|
@@ -4629,29 +4829,33 @@ function buildV1Context(userIds) {
|
|
|
4629
4829
|
},
|
|
4630
4830
|
metrics: buildGamificationProfile(goals, tasks, habits),
|
|
4631
4831
|
dashboard,
|
|
4632
|
-
overview: getOverviewContext(new Date(), { userIds:
|
|
4633
|
-
today: getTodayContext(new Date(), { userIds:
|
|
4634
|
-
risk: getRiskContext(new Date(), { userIds:
|
|
4832
|
+
overview: getOverviewContext(new Date(), { userIds: scopedUserIdsForReads }),
|
|
4833
|
+
today: getTodayContext(new Date(), { userIds: scopedUserIdsForReads }),
|
|
4834
|
+
risk: getRiskContext(new Date(), { userIds: scopedUserIdsForReads }),
|
|
4635
4835
|
goals,
|
|
4636
|
-
projects
|
|
4836
|
+
projects,
|
|
4637
4837
|
tags: listTags(),
|
|
4638
4838
|
tasks,
|
|
4639
4839
|
habits,
|
|
4640
4840
|
users,
|
|
4641
|
-
strategies
|
|
4841
|
+
strategies,
|
|
4642
4842
|
userScope: {
|
|
4643
|
-
selectedUserIds:
|
|
4843
|
+
selectedUserIds: validScopedUserIds ?? [],
|
|
4644
4844
|
selectedUsers
|
|
4645
4845
|
},
|
|
4646
|
-
activeTaskRuns:
|
|
4846
|
+
activeTaskRuns: scope.enforceUserIds &&
|
|
4847
|
+
validScopedUserIds !== undefined &&
|
|
4848
|
+
validScopedUserIds.length === 0
|
|
4849
|
+
? []
|
|
4850
|
+
: listTaskRuns({ active: true, limit: 25, userIds: scopedUserIdsForReads }),
|
|
4647
4851
|
activity: dashboard.recentActivity,
|
|
4648
|
-
lifeForce: buildLifeForcePayload(new Date(),
|
|
4852
|
+
lifeForce: buildLifeForcePayload(new Date(), scopedUserIdsForReads)
|
|
4649
4853
|
};
|
|
4650
4854
|
}
|
|
4651
|
-
function buildXpMetricsPayload() {
|
|
4652
|
-
const goals = listGoals();
|
|
4653
|
-
const tasks = listTasks();
|
|
4654
|
-
const habits = listHabits();
|
|
4855
|
+
function buildXpMetricsPayload(input = {}) {
|
|
4856
|
+
const goals = input.goals ?? listGoals();
|
|
4857
|
+
const tasks = input.tasks ?? listTasks();
|
|
4858
|
+
const habits = input.habits ?? listHabits();
|
|
4655
4859
|
const rules = listRewardRules();
|
|
4656
4860
|
const gamificationOverview = buildGamificationOverview(goals, tasks, habits);
|
|
4657
4861
|
const dailyAmbientCap = rules
|
|
@@ -4710,13 +4914,26 @@ function describeWorkAdjustment(input) {
|
|
|
4710
4914
|
: `${appliedLabel} ${direction} from the tracked work total.`
|
|
4711
4915
|
};
|
|
4712
4916
|
}
|
|
4713
|
-
function buildOperatorContext(
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4917
|
+
function buildOperatorContext(scope = {
|
|
4918
|
+
userIds: undefined,
|
|
4919
|
+
enforceUserIds: false,
|
|
4920
|
+
projectIds: [],
|
|
4921
|
+
tagIds: []
|
|
4922
|
+
}) {
|
|
4923
|
+
const users = listUsers();
|
|
4924
|
+
const { scopedUserIdsForReads } = normalizeScopedUserIdsForReads({
|
|
4925
|
+
scope,
|
|
4926
|
+
validUserIds: users.map((user) => user.id)
|
|
4927
|
+
});
|
|
4928
|
+
const tasks = applyTaskScope(filterOwnedEntities("task", listTasks(), scopedUserIdsForReads), scope);
|
|
4929
|
+
const dueHabits = filterOwnedEntities("habit", listHabits({ dueToday: true }), scopedUserIdsForReads);
|
|
4930
|
+
const activeProjects = applyProjectScope(listProjectSummaries({
|
|
4717
4931
|
status: "active",
|
|
4718
|
-
userIds
|
|
4719
|
-
}).filter((project) => project.activeTaskCount > 0 || project.completedTaskCount > 0);
|
|
4932
|
+
userIds: scopedUserIdsForReads
|
|
4933
|
+
}), scope).filter((project) => project.activeTaskCount > 0 || project.completedTaskCount > 0);
|
|
4934
|
+
const goals = applyGoalScope(filterOwnedEntities("goal", listGoals(), scopedUserIdsForReads), scope, new Set([...activeProjects.map((project) => project.goalId), ...tasks.map((task) => task.goalId)]
|
|
4935
|
+
.filter((value) => typeof value === "string" && value.length > 0)));
|
|
4936
|
+
const scopedHabits = applyHabitScope(dueHabits, scope, new Set(goals.map((goal) => goal.id)), new Set(tasks.map((task) => task.id))).slice(0, 12);
|
|
4720
4937
|
const focusTasks = tasks.filter((task) => task.status === "focus" || task.status === "in_progress");
|
|
4721
4938
|
const recommendedNextTask = focusTasks[0] ??
|
|
4722
4939
|
tasks.find((task) => task.status === "backlog") ??
|
|
@@ -4726,7 +4943,7 @@ function buildOperatorContext(userIds) {
|
|
|
4726
4943
|
generatedAt: new Date().toISOString(),
|
|
4727
4944
|
activeProjects: activeProjects.slice(0, 8),
|
|
4728
4945
|
focusTasks: focusTasks.slice(0, 12),
|
|
4729
|
-
dueHabits,
|
|
4946
|
+
dueHabits: scopedHabits,
|
|
4730
4947
|
currentBoard: {
|
|
4731
4948
|
backlog: tasks.filter((task) => task.status === "backlog").slice(0, 20),
|
|
4732
4949
|
focus: tasks.filter((task) => task.status === "focus").slice(0, 20),
|
|
@@ -4736,10 +4953,16 @@ function buildOperatorContext(userIds) {
|
|
|
4736
4953
|
blocked: tasks.filter((task) => task.status === "blocked").slice(0, 20),
|
|
4737
4954
|
done: tasks.filter((task) => task.status === "done").slice(0, 20)
|
|
4738
4955
|
},
|
|
4739
|
-
recentActivity: listActivityEvents({
|
|
4740
|
-
|
|
4956
|
+
recentActivity: listActivityEvents({
|
|
4957
|
+
limit: 20,
|
|
4958
|
+
userIds: scopedUserIdsForReads
|
|
4959
|
+
}),
|
|
4960
|
+
recentTaskRuns: listTaskRuns({
|
|
4961
|
+
limit: 12,
|
|
4962
|
+
userIds: scopedUserIdsForReads
|
|
4963
|
+
}),
|
|
4741
4964
|
recommendedNextTask,
|
|
4742
|
-
xp: buildXpMetricsPayload()
|
|
4965
|
+
xp: buildXpMetricsPayload({ goals, tasks, habits: scopedHabits })
|
|
4743
4966
|
};
|
|
4744
4967
|
}
|
|
4745
4968
|
function buildUserDirectoryPayload() {
|
|
@@ -4867,7 +5090,13 @@ function buildOperatorOverviewRouteGuide() {
|
|
|
4867
5090
|
}
|
|
4868
5091
|
function buildOperatorOverview(request) {
|
|
4869
5092
|
const auth = parseRequestAuth(request.headers);
|
|
4870
|
-
const
|
|
5093
|
+
const readScope = resolveEffectiveReadScope(request.query, auth);
|
|
5094
|
+
const users = listUsers();
|
|
5095
|
+
const { scopedUserIdsForReads } = normalizeScopedUserIdsForReads({
|
|
5096
|
+
scope: readScope,
|
|
5097
|
+
validUserIds: users.map((user) => user.id)
|
|
5098
|
+
});
|
|
5099
|
+
const userIds = scopedUserIdsForReads;
|
|
4871
5100
|
const canReadPsyche = auth.token
|
|
4872
5101
|
? hasTokenScope(auth.token, "psyche.read")
|
|
4873
5102
|
: true;
|
|
@@ -4878,8 +5107,8 @@ function buildOperatorOverview(request) {
|
|
|
4878
5107
|
];
|
|
4879
5108
|
return {
|
|
4880
5109
|
generatedAt: new Date().toISOString(),
|
|
4881
|
-
snapshot: buildV1Context(
|
|
4882
|
-
operator: buildOperatorContext(
|
|
5110
|
+
snapshot: buildV1Context(readScope),
|
|
5111
|
+
operator: buildOperatorContext(readScope),
|
|
4883
5112
|
sleep: getSleepViewData(userIds),
|
|
4884
5113
|
domains: listDomains(),
|
|
4885
5114
|
psyche: canReadPsyche ? getPsycheOverview(userIds) : null,
|
|
@@ -5075,6 +5304,10 @@ export async function buildServer(options = {}) {
|
|
|
5075
5304
|
});
|
|
5076
5305
|
app.setErrorHandler((error, request, reply) => {
|
|
5077
5306
|
const validationIssues = error instanceof ZodError ? formatValidationIssues(error) : undefined;
|
|
5307
|
+
const routeUrl = request.routeOptions.url || request.url;
|
|
5308
|
+
const validationHelp = validationIssues
|
|
5309
|
+
? buildValidationHelp(request.method, routeUrl, validationIssues)
|
|
5310
|
+
: undefined;
|
|
5078
5311
|
const statusCode = isHttpError(error)
|
|
5079
5312
|
? error.statusCode
|
|
5080
5313
|
: isManagerError(error)
|
|
@@ -5082,7 +5315,6 @@ export async function buildServer(options = {}) {
|
|
|
5082
5315
|
: error instanceof ZodError
|
|
5083
5316
|
? 400
|
|
5084
5317
|
: 500;
|
|
5085
|
-
const routeUrl = request.routeOptions.url || request.url;
|
|
5086
5318
|
if (!shouldSkipAutomaticDiagnosticRoute(routeUrl)) {
|
|
5087
5319
|
try {
|
|
5088
5320
|
recordDiagnosticLog({
|
|
@@ -5123,10 +5355,11 @@ export async function buildServer(options = {}) {
|
|
|
5123
5355
|
? "invalid_request"
|
|
5124
5356
|
: "internal_error",
|
|
5125
5357
|
error: validationIssues
|
|
5126
|
-
?
|
|
5358
|
+
? `Request validation failed for ${request.method.toUpperCase()} ${routeUrl}. ${validationHelp?.validationSummary ?? ""}`.trim()
|
|
5127
5359
|
: getErrorMessage(error),
|
|
5128
5360
|
statusCode,
|
|
5129
5361
|
...(validationIssues ? { details: validationIssues } : {}),
|
|
5362
|
+
...(validationHelp ?? {}),
|
|
5130
5363
|
...(isHttpError(error) && error.details ? error.details : {}),
|
|
5131
5364
|
...(isManagerError(error) && error.details ? error.details : {})
|
|
5132
5365
|
});
|
|
@@ -5364,7 +5597,10 @@ export async function buildServer(options = {}) {
|
|
|
5364
5597
|
revoked: managers.session.revokeCurrentSession(request.headers, reply)
|
|
5365
5598
|
}));
|
|
5366
5599
|
app.get("/api/v1/openapi.json", async () => buildOpenApiDocument());
|
|
5367
|
-
app.get("/api/v1/context", async (request) =>
|
|
5600
|
+
app.get("/api/v1/context", async (request) => {
|
|
5601
|
+
const auth = authenticateRequest(request.headers);
|
|
5602
|
+
return buildV1Context(resolveEffectiveReadScope(request.query, auth));
|
|
5603
|
+
});
|
|
5368
5604
|
app.get("/api/v1/life-force", async (request) => ({
|
|
5369
5605
|
lifeForce: buildLifeForcePayload(new Date(), resolveScopedUserIds(request.query)),
|
|
5370
5606
|
templates: listLifeForceTemplates(resolveLifeForceUser(resolveScopedUserIds(request.query)).id)
|
|
@@ -6021,16 +6257,15 @@ export async function buildServer(options = {}) {
|
|
|
6021
6257
|
return { sleep };
|
|
6022
6258
|
});
|
|
6023
6259
|
app.get("/api/v1/operator/context", async (request) => {
|
|
6024
|
-
|
|
6260
|
+
const auth = requireAuthenticatedActor(request.headers, {
|
|
6025
6261
|
route: "/api/v1/operator/context"
|
|
6026
6262
|
});
|
|
6027
|
-
const userIds = resolveScopedUserIds(request.query);
|
|
6028
6263
|
return {
|
|
6029
|
-
context: buildOperatorContext(
|
|
6264
|
+
context: buildOperatorContext(resolveEffectiveReadScope(request.query, auth))
|
|
6030
6265
|
};
|
|
6031
6266
|
});
|
|
6032
6267
|
app.get("/api/v1/operator/overview", async (request) => {
|
|
6033
|
-
|
|
6268
|
+
requireAuthenticatedActor(request.headers, {
|
|
6034
6269
|
route: "/api/v1/operator/overview"
|
|
6035
6270
|
});
|
|
6036
6271
|
return {
|
|
@@ -39,6 +39,11 @@ export class AuthenticationManager extends AbstractManager {
|
|
|
39
39
|
actor,
|
|
40
40
|
source,
|
|
41
41
|
token,
|
|
42
|
+
scope: {
|
|
43
|
+
userIds: token?.scopePolicy.userIds ?? [],
|
|
44
|
+
projectIds: token?.scopePolicy.projectIds ?? [],
|
|
45
|
+
tagIds: token?.scopePolicy.tagIds ?? []
|
|
46
|
+
},
|
|
42
47
|
session
|
|
43
48
|
};
|
|
44
49
|
}
|
|
@@ -2780,10 +2780,9 @@ function updateMovementBoxOverrideState(id, input) {
|
|
|
2780
2780
|
overridden_user_box_ids_json = ?,
|
|
2781
2781
|
override_ranges_json = ?,
|
|
2782
2782
|
is_overridden = ?,
|
|
2783
|
-
is_fully_hidden =
|
|
2784
|
-
updated_at = ?
|
|
2783
|
+
is_fully_hidden = ?
|
|
2785
2784
|
WHERE id = ?`)
|
|
2786
|
-
.run(input.overrideCount, JSON.stringify(input.overriddenAutomaticBoxIds), input.trueStartedAt, input.trueEndedAt, input.overriddenStartedAt, input.overriddenEndedAt, input.overriddenByBoxId, JSON.stringify(input.overriddenUserBoxIds), JSON.stringify(input.overrideRanges), input.isOverridden ? 1 : 0, input.isFullyHidden ? 1 : 0,
|
|
2785
|
+
.run(input.overrideCount, JSON.stringify(input.overriddenAutomaticBoxIds), input.trueStartedAt, input.trueEndedAt, input.overriddenStartedAt, input.overriddenEndedAt, input.overriddenByBoxId, JSON.stringify(input.overriddenUserBoxIds), JSON.stringify(input.overrideRanges), input.isOverridden ? 1 : 0, input.isFullyHidden ? 1 : 0, id);
|
|
2787
2786
|
}
|
|
2788
2787
|
function recomputeMovementBoxOverrideState(userId) {
|
|
2789
2788
|
const rows = listMovementBoxRows({ userIds: [userId] });
|