forge-openclaw-plugin 0.2.44 → 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.
@@ -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";
@@ -3936,6 +3936,9 @@ function buildValidationHelp(method, routeUrl, issues) {
3936
3936
  }
3937
3937
  function buildAgentOnboardingPayload(request) {
3938
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;
3939
3942
  return {
3940
3943
  forgeBaseUrl: origin,
3941
3944
  webAppUrl: `${origin}/forge/`,
@@ -3962,6 +3965,10 @@ function buildAgentOnboardingPayload(request) {
3962
3965
  recommendedTrustLevel: "trusted",
3963
3966
  recommendedAutonomyMode: "approval_required",
3964
3967
  recommendedApprovalMode: "approval_by_default",
3968
+ defaultBootstrapPolicy: defaultAgentBootstrapPolicy,
3969
+ effectiveBootstrapPolicy,
3970
+ defaultScopePolicy: defaultAgentScopePolicy,
3971
+ effectiveScopePolicy,
3965
3972
  authModes: {
3966
3973
  operatorSession: {
3967
3974
  label: "Quick connect",
@@ -3971,7 +3978,7 @@ function buildAgentOnboardingPayload(request) {
3971
3978
  },
3972
3979
  managedToken: {
3973
3980
  label: "Managed token",
3974
- summary: "Use a long-lived token when you want explicit scoped auth, remote non-Tailscale access, or durable agent credentials.",
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.",
3975
3982
  tokenRequired: true
3976
3983
  }
3977
3984
  },
@@ -4552,6 +4559,11 @@ function parseRequestAuth(headers) {
4552
4559
  const source = token ? "agent" : activity.source;
4553
4560
  return {
4554
4561
  token,
4562
+ scope: {
4563
+ userIds: token?.scopePolicy.userIds ?? [],
4564
+ projectIds: token?.scopePolicy.projectIds ?? [],
4565
+ tagIds: token?.scopePolicy.tagIds ?? []
4566
+ },
4555
4567
  actor,
4556
4568
  source,
4557
4569
  activity: {
@@ -4641,6 +4653,105 @@ function resolveScopedUserIds(query) {
4641
4653
  const unique = Array.from(new Set(values));
4642
4654
  return unique.length > 0 ? unique : undefined;
4643
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
+ }
4644
4755
  function attachMovementTimelineSleepOverlays(movement, userIds) {
4645
4756
  const rangeStart = movement.segments.reduce((earliest, segment) => {
4646
4757
  if (!earliest || Date.parse(segment.startedAt) < Date.parse(earliest)) {
@@ -4687,18 +4798,26 @@ function syncEntityOwnerFromBody(options) {
4687
4798
  const owner = resolveUserForMutation(requestedUserId, options.fallbackLabel);
4688
4799
  setEntityOwner(options.entityType, options.entityId, owner.id);
4689
4800
  }
4690
- function buildV1Context(userIds) {
4801
+ function buildV1Context(scope = {
4802
+ userIds: undefined,
4803
+ enforceUserIds: false,
4804
+ projectIds: [],
4805
+ tagIds: []
4806
+ }) {
4691
4807
  const users = listUsers();
4692
- const validUserIdSet = new Set(users.map((user) => user.id));
4693
- const scopedUserIds = userIds && userIds.length > 0
4694
- ? userIds.filter((userId) => validUserIdSet.has(userId))
4695
- : undefined;
4696
- const goals = filterOwnedEntities("goal", listGoals(), scopedUserIds);
4697
- const tasks = filterOwnedEntities("task", listTasks(), scopedUserIds);
4698
- const habits = filterOwnedEntities("habit", listHabits(), scopedUserIds);
4699
- const dashboard = getDashboard({ userIds: scopedUserIds });
4700
- const selectedUsers = scopedUserIds && scopedUserIds.length > 0
4701
- ? users.filter((user) => scopedUserIds.includes(user.id))
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))
4702
4821
  : users;
4703
4822
  return {
4704
4823
  meta: {
@@ -4710,29 +4829,33 @@ function buildV1Context(userIds) {
4710
4829
  },
4711
4830
  metrics: buildGamificationProfile(goals, tasks, habits),
4712
4831
  dashboard,
4713
- overview: getOverviewContext(new Date(), { userIds: scopedUserIds }),
4714
- today: getTodayContext(new Date(), { userIds: scopedUserIds }),
4715
- risk: getRiskContext(new Date(), { userIds: scopedUserIds }),
4832
+ overview: getOverviewContext(new Date(), { userIds: scopedUserIdsForReads }),
4833
+ today: getTodayContext(new Date(), { userIds: scopedUserIdsForReads }),
4834
+ risk: getRiskContext(new Date(), { userIds: scopedUserIdsForReads }),
4716
4835
  goals,
4717
- projects: listProjectSummaries({ userIds: scopedUserIds }),
4836
+ projects,
4718
4837
  tags: listTags(),
4719
4838
  tasks,
4720
4839
  habits,
4721
4840
  users,
4722
- strategies: listStrategies({ userIds: scopedUserIds }),
4841
+ strategies,
4723
4842
  userScope: {
4724
- selectedUserIds: scopedUserIds ?? [],
4843
+ selectedUserIds: validScopedUserIds ?? [],
4725
4844
  selectedUsers
4726
4845
  },
4727
- activeTaskRuns: listTaskRuns({ active: true, limit: 25 }),
4846
+ activeTaskRuns: scope.enforceUserIds &&
4847
+ validScopedUserIds !== undefined &&
4848
+ validScopedUserIds.length === 0
4849
+ ? []
4850
+ : listTaskRuns({ active: true, limit: 25, userIds: scopedUserIdsForReads }),
4728
4851
  activity: dashboard.recentActivity,
4729
- lifeForce: buildLifeForcePayload(new Date(), scopedUserIds)
4852
+ lifeForce: buildLifeForcePayload(new Date(), scopedUserIdsForReads)
4730
4853
  };
4731
4854
  }
4732
- function buildXpMetricsPayload() {
4733
- const goals = listGoals();
4734
- const tasks = listTasks();
4735
- 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();
4736
4859
  const rules = listRewardRules();
4737
4860
  const gamificationOverview = buildGamificationOverview(goals, tasks, habits);
4738
4861
  const dailyAmbientCap = rules
@@ -4791,13 +4914,26 @@ function describeWorkAdjustment(input) {
4791
4914
  : `${appliedLabel} ${direction} from the tracked work total.`
4792
4915
  };
4793
4916
  }
4794
- function buildOperatorContext(userIds) {
4795
- const tasks = filterOwnedEntities("task", listTasks(), userIds);
4796
- const dueHabits = filterOwnedEntities("habit", listHabits({ dueToday: true }), userIds).slice(0, 12);
4797
- const activeProjects = listProjectSummaries({
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({
4798
4931
  status: "active",
4799
- userIds
4800
- }).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);
4801
4937
  const focusTasks = tasks.filter((task) => task.status === "focus" || task.status === "in_progress");
4802
4938
  const recommendedNextTask = focusTasks[0] ??
4803
4939
  tasks.find((task) => task.status === "backlog") ??
@@ -4807,7 +4943,7 @@ function buildOperatorContext(userIds) {
4807
4943
  generatedAt: new Date().toISOString(),
4808
4944
  activeProjects: activeProjects.slice(0, 8),
4809
4945
  focusTasks: focusTasks.slice(0, 12),
4810
- dueHabits,
4946
+ dueHabits: scopedHabits,
4811
4947
  currentBoard: {
4812
4948
  backlog: tasks.filter((task) => task.status === "backlog").slice(0, 20),
4813
4949
  focus: tasks.filter((task) => task.status === "focus").slice(0, 20),
@@ -4817,10 +4953,16 @@ function buildOperatorContext(userIds) {
4817
4953
  blocked: tasks.filter((task) => task.status === "blocked").slice(0, 20),
4818
4954
  done: tasks.filter((task) => task.status === "done").slice(0, 20)
4819
4955
  },
4820
- recentActivity: listActivityEvents({ limit: 20, userIds }),
4821
- recentTaskRuns: listTaskRuns({ limit: 12, userIds }),
4956
+ recentActivity: listActivityEvents({
4957
+ limit: 20,
4958
+ userIds: scopedUserIdsForReads
4959
+ }),
4960
+ recentTaskRuns: listTaskRuns({
4961
+ limit: 12,
4962
+ userIds: scopedUserIdsForReads
4963
+ }),
4822
4964
  recommendedNextTask,
4823
- xp: buildXpMetricsPayload()
4965
+ xp: buildXpMetricsPayload({ goals, tasks, habits: scopedHabits })
4824
4966
  };
4825
4967
  }
4826
4968
  function buildUserDirectoryPayload() {
@@ -4948,7 +5090,13 @@ function buildOperatorOverviewRouteGuide() {
4948
5090
  }
4949
5091
  function buildOperatorOverview(request) {
4950
5092
  const auth = parseRequestAuth(request.headers);
4951
- const userIds = resolveScopedUserIds(request.query);
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;
4952
5100
  const canReadPsyche = auth.token
4953
5101
  ? hasTokenScope(auth.token, "psyche.read")
4954
5102
  : true;
@@ -4959,8 +5107,8 @@ function buildOperatorOverview(request) {
4959
5107
  ];
4960
5108
  return {
4961
5109
  generatedAt: new Date().toISOString(),
4962
- snapshot: buildV1Context(userIds),
4963
- operator: buildOperatorContext(userIds),
5110
+ snapshot: buildV1Context(readScope),
5111
+ operator: buildOperatorContext(readScope),
4964
5112
  sleep: getSleepViewData(userIds),
4965
5113
  domains: listDomains(),
4966
5114
  psyche: canReadPsyche ? getPsycheOverview(userIds) : null,
@@ -5449,7 +5597,10 @@ export async function buildServer(options = {}) {
5449
5597
  revoked: managers.session.revokeCurrentSession(request.headers, reply)
5450
5598
  }));
5451
5599
  app.get("/api/v1/openapi.json", async () => buildOpenApiDocument());
5452
- app.get("/api/v1/context", async (request) => buildV1Context(resolveScopedUserIds(request.query)));
5600
+ app.get("/api/v1/context", async (request) => {
5601
+ const auth = authenticateRequest(request.headers);
5602
+ return buildV1Context(resolveEffectiveReadScope(request.query, auth));
5603
+ });
5453
5604
  app.get("/api/v1/life-force", async (request) => ({
5454
5605
  lifeForce: buildLifeForcePayload(new Date(), resolveScopedUserIds(request.query)),
5455
5606
  templates: listLifeForceTemplates(resolveLifeForceUser(resolveScopedUserIds(request.query)).id)
@@ -6106,16 +6257,15 @@ export async function buildServer(options = {}) {
6106
6257
  return { sleep };
6107
6258
  });
6108
6259
  app.get("/api/v1/operator/context", async (request) => {
6109
- requireOperatorSession(request.headers, {
6260
+ const auth = requireAuthenticatedActor(request.headers, {
6110
6261
  route: "/api/v1/operator/context"
6111
6262
  });
6112
- const userIds = resolveScopedUserIds(request.query);
6113
6263
  return {
6114
- context: buildOperatorContext(userIds)
6264
+ context: buildOperatorContext(resolveEffectiveReadScope(request.query, auth))
6115
6265
  };
6116
6266
  });
6117
6267
  app.get("/api/v1/operator/overview", async (request) => {
6118
- requireOperatorSession(request.headers, {
6268
+ requireAuthenticatedActor(request.headers, {
6119
6269
  route: "/api/v1/operator/overview"
6120
6270
  });
6121
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
  }
@@ -51,6 +51,11 @@ export class SessionManager extends AbstractAuditedManager {
51
51
  host: null,
52
52
  ip: null,
53
53
  token: null,
54
+ scope: {
55
+ userIds: [],
56
+ projectIds: [],
57
+ tagIds: []
58
+ },
54
59
  session
55
60
  }, {
56
61
  actorLabel
@@ -1825,6 +1825,43 @@ export function buildOpenApiDocument() {
1825
1825
  }
1826
1826
  }
1827
1827
  };
1828
+ const agentBootstrapPolicy = {
1829
+ type: "object",
1830
+ additionalProperties: false,
1831
+ required: [
1832
+ "mode",
1833
+ "goalsLimit",
1834
+ "projectsLimit",
1835
+ "tasksLimit",
1836
+ "habitsLimit",
1837
+ "strategiesLimit",
1838
+ "peoplePageLimit",
1839
+ "includePeoplePages"
1840
+ ],
1841
+ properties: {
1842
+ mode: {
1843
+ type: "string",
1844
+ enum: ["disabled", "active_only", "scoped", "full"]
1845
+ },
1846
+ goalsLimit: { type: "integer", minimum: 0, maximum: 100 },
1847
+ projectsLimit: { type: "integer", minimum: 0, maximum: 100 },
1848
+ tasksLimit: { type: "integer", minimum: 0, maximum: 100 },
1849
+ habitsLimit: { type: "integer", minimum: 0, maximum: 100 },
1850
+ strategiesLimit: { type: "integer", minimum: 0, maximum: 100 },
1851
+ peoplePageLimit: { type: "integer", minimum: 0, maximum: 50 },
1852
+ includePeoplePages: { type: "boolean" }
1853
+ }
1854
+ };
1855
+ const agentScopePolicy = {
1856
+ type: "object",
1857
+ additionalProperties: false,
1858
+ required: ["userIds", "projectIds", "tagIds"],
1859
+ properties: {
1860
+ userIds: arrayOf({ type: "string" }),
1861
+ projectIds: arrayOf({ type: "string" }),
1862
+ tagIds: arrayOf({ type: "string" })
1863
+ }
1864
+ };
1828
1865
  const agentTokenSummary = {
1829
1866
  type: "object",
1830
1867
  additionalProperties: false,
@@ -1839,6 +1876,8 @@ export function buildOpenApiDocument() {
1839
1876
  "autonomyMode",
1840
1877
  "approvalMode",
1841
1878
  "description",
1879
+ "bootstrapPolicy",
1880
+ "scopePolicy",
1842
1881
  "lastUsedAt",
1843
1882
  "revokedAt",
1844
1883
  "createdAt",
@@ -1865,6 +1904,8 @@ export function buildOpenApiDocument() {
1865
1904
  enum: ["approval_by_default", "high_impact_only", "none"]
1866
1905
  },
1867
1906
  description: { type: "string" },
1907
+ bootstrapPolicy: { $ref: "#/components/schemas/AgentBootstrapPolicy" },
1908
+ scopePolicy: { $ref: "#/components/schemas/AgentScopePolicy" },
1868
1909
  lastUsedAt: nullable({ type: "string", format: "date-time" }),
1869
1910
  revokedAt: nullable({ type: "string", format: "date-time" }),
1870
1911
  createdAt: { type: "string", format: "date-time" },
@@ -2779,6 +2820,10 @@ export function buildOpenApiDocument() {
2779
2820
  "recommendedTrustLevel",
2780
2821
  "recommendedAutonomyMode",
2781
2822
  "recommendedApprovalMode",
2823
+ "defaultBootstrapPolicy",
2824
+ "effectiveBootstrapPolicy",
2825
+ "defaultScopePolicy",
2826
+ "effectiveScopePolicy",
2782
2827
  "authModes",
2783
2828
  "tokenRecovery",
2784
2829
  "requiredHeaders",
@@ -2827,6 +2872,18 @@ export function buildOpenApiDocument() {
2827
2872
  type: "string",
2828
2873
  enum: ["approval_by_default", "high_impact_only", "none"]
2829
2874
  },
2875
+ defaultBootstrapPolicy: {
2876
+ $ref: "#/components/schemas/AgentBootstrapPolicy"
2877
+ },
2878
+ effectiveBootstrapPolicy: {
2879
+ $ref: "#/components/schemas/AgentBootstrapPolicy"
2880
+ },
2881
+ defaultScopePolicy: {
2882
+ $ref: "#/components/schemas/AgentScopePolicy"
2883
+ },
2884
+ effectiveScopePolicy: {
2885
+ $ref: "#/components/schemas/AgentScopePolicy"
2886
+ },
2830
2887
  authModes: {
2831
2888
  type: "object",
2832
2889
  additionalProperties: false,
@@ -4172,6 +4229,8 @@ export function buildOpenApiDocument() {
4172
4229
  AgentRuntimeSessionEvent: agentRuntimeSessionEvent,
4173
4230
  AgentRuntimeSession: agentRuntimeSession,
4174
4231
  AgentRuntimeSessionHistory: agentRuntimeSessionHistory,
4232
+ AgentBootstrapPolicy: agentBootstrapPolicy,
4233
+ AgentScopePolicy: agentScopePolicy,
4175
4234
  AgentTokenSummary: agentTokenSummary,
4176
4235
  AgentTokenMutationResult: agentTokenMutationResult,
4177
4236
  Domain: domain,
@@ -7,7 +7,7 @@ import { recordActivityEvent } from "./activity-events.js";
7
7
  import { recordEventLog } from "./event-log.js";
8
8
  import { resolveGoogleCalendarOauthPublicConfig } from "../services/google-calendar-oauth-config.js";
9
9
  import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
10
- import { createAgentTokenSchema, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
10
+ import { agentBootstrapPolicySchema, agentScopePolicySchema, createAgentTokenSchema, legacyAgentBootstrapPolicy, defaultAgentScopePolicy, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
11
11
  const settingsFileSchema = settingsPayloadSchema.deepPartial();
12
12
  let settingsFileSyncDepth = 0;
13
13
  let lastSettingsFileStatus = {
@@ -319,6 +319,12 @@ function mapAgent(row) {
319
319
  });
320
320
  }
321
321
  function mapToken(row) {
322
+ const bootstrapPolicy = agentBootstrapPolicySchema.parse(row.bootstrap_policy_json
323
+ ? JSON.parse(row.bootstrap_policy_json)
324
+ : legacyAgentBootstrapPolicy);
325
+ const scopePolicy = agentScopePolicySchema.parse(row.scope_policy_json
326
+ ? JSON.parse(row.scope_policy_json)
327
+ : defaultAgentScopePolicy);
322
328
  return {
323
329
  id: row.id,
324
330
  label: row.label,
@@ -330,6 +336,8 @@ function mapToken(row) {
330
336
  autonomyMode: row.autonomy_mode,
331
337
  approvalMode: row.approval_mode,
332
338
  description: row.description,
339
+ bootstrapPolicy,
340
+ scopePolicy,
333
341
  lastUsedAt: row.last_used_at,
334
342
  revokedAt: row.revoked_at,
335
343
  createdAt: row.created_at,
@@ -409,6 +417,8 @@ export function listAgentTokens() {
409
417
  agent_tokens.label,
410
418
  agent_tokens.token_prefix,
411
419
  agent_tokens.scopes_json,
420
+ agent_tokens.bootstrap_policy_json,
421
+ agent_tokens.scope_policy_json,
412
422
  agent_tokens.agent_id,
413
423
  agent_identities.label AS agent_label,
414
424
  agent_tokens.trust_level,
@@ -807,9 +817,9 @@ export function createAgentToken(input, activity) {
807
817
  const tokenPrefix = `${token.slice(0, 10)}••••`;
808
818
  getDatabase()
809
819
  .prepare(`INSERT INTO agent_tokens (
810
- id, label, token_hash, token_prefix, scopes_json, agent_id, trust_level, autonomy_mode, approval_mode, description, created_at, updated_at
811
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
812
- .run(id, parsed.label, hashToken(token), tokenPrefix, JSON.stringify(parsed.scopes), agent.id, parsed.trustLevel, parsed.autonomyMode, parsed.approvalMode, parsed.description, now, now);
820
+ id, label, token_hash, token_prefix, scopes_json, bootstrap_policy_json, scope_policy_json, agent_id, trust_level, autonomy_mode, approval_mode, description, created_at, updated_at
821
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
822
+ .run(id, parsed.label, hashToken(token), tokenPrefix, JSON.stringify(parsed.scopes), JSON.stringify(parsed.bootstrapPolicy), JSON.stringify(parsed.scopePolicy), agent.id, parsed.trustLevel, parsed.autonomyMode, parsed.approvalMode, parsed.description, now, now);
813
823
  const tokenSummary = listAgentTokens().find((entry) => entry.id === id);
814
824
  if (activity) {
815
825
  recordActivityEvent({
@@ -823,7 +833,11 @@ export function createAgentToken(input, activity) {
823
833
  metadata: {
824
834
  agentId: agent.id,
825
835
  agentLabel: agent.label,
826
- scopes: parsed.scopes.join(",")
836
+ scopes: parsed.scopes.join(","),
837
+ bootstrapMode: parsed.bootstrapPolicy.mode,
838
+ scopedUserIds: parsed.scopePolicy.userIds.join(","),
839
+ scopedProjectIds: parsed.scopePolicy.projectIds.join(","),
840
+ scopedTagIds: parsed.scopePolicy.tagIds.join(",")
827
841
  }
828
842
  });
829
843
  recordEventLog({
@@ -836,7 +850,11 @@ export function createAgentToken(input, activity) {
836
850
  agentId: agent.id,
837
851
  trustLevel: parsed.trustLevel,
838
852
  autonomyMode: parsed.autonomyMode,
839
- approvalMode: parsed.approvalMode
853
+ approvalMode: parsed.approvalMode,
854
+ bootstrapMode: parsed.bootstrapPolicy.mode,
855
+ scopeUserCount: parsed.scopePolicy.userIds.length,
856
+ scopeProjectCount: parsed.scopePolicy.projectIds.length,
857
+ scopeTagCount: parsed.scopePolicy.tagIds.length
840
858
  }
841
859
  });
842
860
  }
@@ -929,6 +947,8 @@ export function verifyAgentToken(token) {
929
947
  agent_tokens.label,
930
948
  agent_tokens.token_prefix,
931
949
  agent_tokens.scopes_json,
950
+ agent_tokens.bootstrap_policy_json,
951
+ agent_tokens.scope_policy_json,
932
952
  agent_tokens.agent_id,
933
953
  agent_identities.label AS agent_label,
934
954
  agent_tokens.trust_level,