plugin-agent-orchestrator 1.0.27 → 1.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +9 -7
  2. package/dist/client/index.js +1 -1
  3. package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
  4. package/dist/client-v2/264.718a107e43fc163c.js +10 -0
  5. package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
  6. package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
  7. package/dist/client-v2/418.29e713f79131eece.js +10 -0
  8. package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
  9. package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
  10. package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
  11. package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
  12. package/dist/client-v2/index.js +1 -1
  13. package/dist/externalVersion.js +7 -10
  14. package/dist/locale/en-US.json +94 -25
  15. package/dist/locale/vi-VN.json +94 -25
  16. package/dist/locale/zh-CN.json +94 -25
  17. package/dist/server/collections/agent-execution-spans.js +37 -0
  18. package/dist/server/collections/agent-harness-profiles.js +2 -2
  19. package/dist/server/collections/agent-memory-contexts.js +125 -0
  20. package/dist/server/collections/orchestrator-logs.js +2 -2
  21. package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
  22. package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
  23. package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
  24. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
  25. package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
  26. package/dist/server/plugin.js +128 -74
  27. package/dist/server/resources/agent-monitor.js +454 -0
  28. package/dist/server/services/AgentHarness.js +24 -499
  29. package/dist/server/services/AgentMemoryContextService.js +216 -0
  30. package/dist/server/services/ExecutionSpanService.js +2 -2
  31. package/dist/server/services/NativeSubAgentObserver.js +413 -0
  32. package/dist/server/skill-hub/mcp/McpController.js +16 -5
  33. package/dist/server/skill-hub/plugin.js +81 -5
  34. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
  35. package/dist/server/tools/delegate-task.js +11 -589
  36. package/dist/server/utils/skill-settings.js +18 -1
  37. package/package.json +47 -49
  38. package/src/client/AIEmployeesContext.tsx +5 -18
  39. package/src/client/AgentRunsTab.tsx +2 -771
  40. package/src/client/HarnessProfilesTab.tsx +2 -257
  41. package/src/client/OrchestratorSettings.tsx +97 -106
  42. package/src/client/RulesTab.tsx +2 -788
  43. package/src/client/plugin.tsx +0 -2
  44. package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
  45. package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
  46. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  47. package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
  48. package/src/client/skill-hub/components/SkillManager.tsx +194 -181
  49. package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
  50. package/src/client/skill-hub/locale.ts +16 -16
  51. package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
  52. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  53. package/src/client/skill-hub/utils/jsonFields.ts +7 -3
  54. package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
  55. package/src/client-v2/components/AgentRunsTab.tsx +182 -455
  56. package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
  57. package/src/client-v2/components/RulesTab.tsx +2 -782
  58. package/src/client-v2/components/TracingTab.tsx +1 -1
  59. package/src/client-v2/hooks/useApiRequest.ts +8 -1
  60. package/src/client-v2/pages/RulesPage.tsx +2 -2
  61. package/src/client-v2/plugin.tsx +3 -3
  62. package/src/locale/en-US.json +94 -25
  63. package/src/locale/vi-VN.json +94 -25
  64. package/src/locale/zh-CN.json +94 -25
  65. package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
  66. package/src/server/__tests__/skill-settings.test.ts +6 -6
  67. package/src/server/__tests__/smoke.test.ts +1 -0
  68. package/src/server/collections/agent-execution-spans.ts +37 -0
  69. package/src/server/collections/agent-harness-profiles.ts +59 -59
  70. package/src/server/collections/agent-loop-events.ts +71 -71
  71. package/src/server/collections/agent-loop-steps.ts +144 -144
  72. package/src/server/collections/agent-memory-contexts.ts +95 -0
  73. package/src/server/collections/orchestrator-logs.ts +4 -4
  74. package/src/server/collections/skill-definitions.ts +111 -111
  75. package/src/server/collections/skill-executions.ts +106 -106
  76. package/src/server/collections/skill-loop-configs.ts +65 -65
  77. package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
  78. package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
  79. package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
  80. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
  81. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  82. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
  83. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
  84. package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
  85. package/src/server/plugin.ts +151 -94
  86. package/src/server/resources/agent-monitor.ts +482 -0
  87. package/src/server/services/AgentHarness.ts +38 -623
  88. package/src/server/services/AgentMemoryContextService.ts +256 -0
  89. package/src/server/services/AgentPlanValidator.ts +73 -73
  90. package/src/server/services/ExecutionSpanService.ts +6 -2
  91. package/src/server/services/FileManager.ts +144 -144
  92. package/src/server/services/NativeSubAgentObserver.ts +507 -0
  93. package/src/server/services/SkillManager.ts +583 -583
  94. package/src/server/services/SkillRepositoryService.ts +5 -7
  95. package/src/server/services/TokenTracker.ts +3 -3
  96. package/src/server/services/WorkerEnvManager.ts +1 -2
  97. package/src/server/skill-hub/actions/git-import.ts +5 -7
  98. package/src/server/skill-hub/mcp/McpController.ts +41 -14
  99. package/src/server/skill-hub/plugin.ts +89 -6
  100. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
  101. package/src/server/skill-hub/utils/json-fields.ts +1 -1
  102. package/src/server/tools/delegate-task.ts +13 -847
  103. package/src/server/utils/skill-settings.ts +24 -6
  104. package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
  105. package/dist/client-v2/418.5ae055abf141820e.js +0 -10
  106. package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
  107. package/dist/client-v2/892.72db4161511c8a16.js +0 -10
  108. package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
  109. package/src/client/tools/PlanApprovalCard.tsx +0 -176
  110. package/src/client/tools/registerOrchestratorCards.ts +0 -17
@@ -1,142 +1,145 @@
1
- import { DataTypes } from '@nocobase/database';
2
- import { Migration } from '@nocobase/server';
3
-
4
- export default class AddPlanApprovalAndHarnessProfiles extends Migration {
5
- on = 'afterLoad';
6
- appVersion = '>=0.1.0';
7
-
8
- async up() {
9
- const db = (this as any).db;
10
- const queryInterface = db.sequelize.getQueryInterface();
11
- const tablePrefix = db.options.tablePrefix || '';
12
-
13
- await this.addRunColumns(queryInterface, `${tablePrefix}agentLoopRuns`);
14
- await this.addStepColumns(queryInterface, `${tablePrefix}agentLoopSteps`);
15
- await this.addConfigColumns(queryInterface, `${tablePrefix}orchestratorConfig`);
16
- await this.ensureHarnessProfiles(queryInterface, `${tablePrefix}agentHarnessProfiles`);
17
- await this.seedDefaultProfiles();
18
- }
19
-
20
- async addRunColumns(queryInterface: any, tableName: string) {
21
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
22
- if (!tableExists) return;
23
- const tableDesc = await queryInterface.describeTable(tableName);
24
- const addIfMissing = async (name: string, spec: any) => {
25
- if (tableDesc[name]) return;
26
- await queryInterface.addColumn(tableName, name, spec);
27
- };
28
-
29
- await addIfMissing('approvalStatus', { type: DataTypes.STRING(30), allowNull: true, defaultValue: 'none' });
30
- await addIfMissing('approvedById', { type: DataTypes.BIGINT, allowNull: true });
31
- await addIfMissing('approvedAt', { type: DataTypes.DATE, allowNull: true });
32
- await addIfMissing('rejectionReason', { type: DataTypes.TEXT, allowNull: true });
33
- await addIfMissing('changeRequest', { type: DataTypes.TEXT, allowNull: true });
34
- await addIfMissing('planVersion', { type: DataTypes.INTEGER, allowNull: true, defaultValue: 1 });
35
- await addIfMissing('planSource', { type: DataTypes.STRING(50), allowNull: true });
36
- await addIfMissing('plannerModel', { type: DataTypes.STRING(100), allowNull: true });
37
- await addIfMissing('lockedBy', { type: DataTypes.STRING(100), allowNull: true });
38
- await addIfMissing('lockedUntil', { type: DataTypes.DATE, allowNull: true });
39
- }
40
-
41
- async addStepColumns(queryInterface: any, tableName: string) {
42
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
43
- if (!tableExists) return;
44
- const tableDesc = await queryInterface.describeTable(tableName);
45
- if (!tableDesc.dependencyPolicy) {
46
- await queryInterface.addColumn(tableName, 'dependencyPolicy', {
47
- type: DataTypes.STRING(30),
48
- allowNull: true,
49
- defaultValue: 'require_success',
50
- });
51
- }
52
- }
53
-
54
- async addConfigColumns(queryInterface: any, tableName: string) {
55
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
56
- if (!tableExists) return;
57
- const tableDesc = await queryInterface.describeTable(tableName);
58
- if (!tableDesc.harnessTag) {
59
- await queryInterface.addColumn(tableName, 'harnessTag', {
60
- type: DataTypes.STRING(100),
61
- allowNull: true,
62
- defaultValue: 'default',
63
- });
64
- }
65
- }
66
-
67
- async ensureHarnessProfiles(queryInterface: any, tableName: string) {
68
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
69
- if (tableExists) return;
70
- await queryInterface.createTable(tableName, {
71
- id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
72
- tag: { type: DataTypes.STRING(100), allowNull: false, unique: true },
73
- title: { type: DataTypes.STRING(200), allowNull: true },
74
- description: { type: DataTypes.TEXT, allowNull: true },
75
- enabled: { type: DataTypes.BOOLEAN, allowNull: true, defaultValue: true },
76
- settings: { type: DataTypes.JSON, allowNull: true, defaultValue: {} },
77
- createdAt: { type: DataTypes.DATE, allowNull: true },
78
- updatedAt: { type: DataTypes.DATE, allowNull: true },
79
- });
80
- }
81
-
82
- async seedDefaultProfiles() {
83
- const repo = (this as any).db.getRepository('agentHarnessProfiles');
84
- if (!repo) return;
85
- const profiles = [
86
- {
87
- tag: 'default',
88
- title: 'Default',
89
- description: 'Balanced profile for normal multi-agent work.',
90
- settings: {
91
- requirePlanApproval: true,
92
- allowSubAgents: true,
93
- allowToolCalls: true,
94
- maxParallelSubAgents: 3,
95
- maxControllerSteps: 100,
96
- },
97
- },
98
- {
99
- tag: 'safe',
100
- title: 'Safe',
101
- description: 'Strict approval-first profile for higher-risk work.',
102
- settings: {
103
- requirePlanApproval: true,
104
- allowSubAgents: true,
105
- allowToolCalls: true,
106
- maxParallelSubAgents: 1,
107
- maxControllerSteps: 50,
108
- requireVerification: true,
109
- },
110
- },
111
- {
112
- tag: 'file-heavy',
113
- title: 'File Heavy',
114
- description: 'Profile for tasks that inspect or transform many attachments/files.',
115
- settings: {
116
- requirePlanApproval: true,
117
- allowSubAgents: true,
118
- allowToolCalls: true,
119
- maxParallelSubAgents: 2,
120
- maxControllerSteps: 120,
121
- preferFileTools: true,
122
- },
123
- },
124
- ];
125
- for (const profile of profiles) {
126
- const existing = await repo.findOne({ filter: { tag: profile.tag } });
127
- if (existing) continue;
128
- await repo.create({
129
- values: {
130
- ...profile,
131
- enabled: true,
132
- createdAt: new Date(),
133
- updatedAt: new Date(),
134
- },
135
- });
136
- }
137
- }
138
-
139
- async down() {
140
- // No rollback: new nullable columns and the profile table are backward compatible.
141
- }
142
- }
1
+ import { DataTypes } from '@nocobase/database';
2
+ import { Migration } from '@nocobase/server';
3
+
4
+ export default class AddPlanApprovalAndHarnessProfiles extends Migration {
5
+ on = 'afterLoad';
6
+ appVersion = '>=0.1.0';
7
+
8
+ async up() {
9
+ const db = (this as any).db;
10
+ const queryInterface = db.sequelize.getQueryInterface();
11
+ const tablePrefix = db.options.tablePrefix || '';
12
+
13
+ await this.addRunColumns(queryInterface, `${tablePrefix}agentLoopRuns`);
14
+ await this.addStepColumns(queryInterface, `${tablePrefix}agentLoopSteps`);
15
+ await this.addConfigColumns(queryInterface, `${tablePrefix}orchestratorConfig`);
16
+ await this.ensureHarnessProfiles(queryInterface, `${tablePrefix}agentHarnessProfiles`);
17
+ await this.seedDefaultProfiles();
18
+ }
19
+
20
+ async addRunColumns(queryInterface: any, tableName: string) {
21
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
22
+ if (!tableExists) return;
23
+ const tableDesc = await queryInterface.describeTable(tableName);
24
+ const addIfMissing = async (name: string, spec: any) => {
25
+ if (tableDesc[name]) return;
26
+ await queryInterface.addColumn(tableName, name, spec);
27
+ };
28
+
29
+ await addIfMissing('approvalStatus', { type: DataTypes.STRING(30), allowNull: true, defaultValue: 'none' });
30
+ await addIfMissing('approvedById', { type: DataTypes.BIGINT, allowNull: true });
31
+ await addIfMissing('approvedAt', { type: DataTypes.DATE, allowNull: true });
32
+ await addIfMissing('rejectionReason', { type: DataTypes.TEXT, allowNull: true });
33
+ await addIfMissing('changeRequest', { type: DataTypes.TEXT, allowNull: true });
34
+ await addIfMissing('planVersion', { type: DataTypes.INTEGER, allowNull: true, defaultValue: 1 });
35
+ await addIfMissing('planSource', { type: DataTypes.STRING(50), allowNull: true });
36
+ await addIfMissing('plannerModel', { type: DataTypes.STRING(100), allowNull: true });
37
+ await addIfMissing('lockedBy', { type: DataTypes.STRING(100), allowNull: true });
38
+ await addIfMissing('lockedUntil', { type: DataTypes.DATE, allowNull: true });
39
+ }
40
+
41
+ async addStepColumns(queryInterface: any, tableName: string) {
42
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
43
+ if (!tableExists) return;
44
+ const tableDesc = await queryInterface.describeTable(tableName);
45
+ if (!tableDesc.dependencyPolicy) {
46
+ await queryInterface.addColumn(tableName, 'dependencyPolicy', {
47
+ type: DataTypes.STRING(30),
48
+ allowNull: true,
49
+ defaultValue: 'require_success',
50
+ });
51
+ }
52
+ }
53
+
54
+ async addConfigColumns(queryInterface: any, tableName: string) {
55
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
56
+ if (!tableExists) return;
57
+ const tableDesc = await queryInterface.describeTable(tableName);
58
+ if (!tableDesc.harnessTag) {
59
+ await queryInterface.addColumn(tableName, 'harnessTag', {
60
+ type: DataTypes.STRING(100),
61
+ allowNull: true,
62
+ defaultValue: 'default',
63
+ });
64
+ }
65
+ }
66
+
67
+ async ensureHarnessProfiles(queryInterface: any, tableName: string) {
68
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
69
+ if (tableExists) return;
70
+ await queryInterface.createTable(tableName, {
71
+ id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
72
+ tag: { type: DataTypes.STRING(100), allowNull: false, unique: true },
73
+ title: { type: DataTypes.STRING(200), allowNull: true },
74
+ description: { type: DataTypes.TEXT, allowNull: true },
75
+ enabled: { type: DataTypes.BOOLEAN, allowNull: true, defaultValue: true },
76
+ settings: { type: DataTypes.JSON, allowNull: true, defaultValue: {} },
77
+ createdAt: { type: DataTypes.DATE, allowNull: true },
78
+ updatedAt: { type: DataTypes.DATE, allowNull: true },
79
+ });
80
+ }
81
+
82
+ async seedDefaultProfiles() {
83
+ const repo = (this as any).db.getRepository('agentHarnessProfiles');
84
+ if (!repo) return;
85
+ const profiles = [
86
+ {
87
+ tag: 'default',
88
+ title: 'Default',
89
+ description: 'Default native observer and memory policy for normal AI employee work.',
90
+ settings: {
91
+ nativeObserverEnabled: true,
92
+ memoryInjectionEnabled: true,
93
+ memoryScopes: ['public', 'user', 'agent_user'],
94
+ knowledgeScopes: ['public', 'private'],
95
+ maxMemoryContextChars: 6000,
96
+ tracingRetentionDays: 30,
97
+ },
98
+ },
99
+ {
100
+ tag: 'safe',
101
+ title: 'Safe',
102
+ description:
103
+ 'Conservative native observer policy with private context enabled only for matching user/agent pairs.',
104
+ settings: {
105
+ nativeObserverEnabled: true,
106
+ memoryInjectionEnabled: true,
107
+ memoryScopes: ['public', 'user', 'agent_user'],
108
+ knowledgeScopes: ['public', 'private'],
109
+ maxMemoryContextChars: 4000,
110
+ tracingRetentionDays: 14,
111
+ },
112
+ },
113
+ {
114
+ tag: 'file-heavy',
115
+ title: 'File Heavy',
116
+ description: 'Native observer policy for agents that need more context while working with files and artifacts.',
117
+ settings: {
118
+ nativeObserverEnabled: true,
119
+ memoryInjectionEnabled: true,
120
+ memoryScopes: ['public', 'user', 'agent_user'],
121
+ knowledgeScopes: ['public', 'private'],
122
+ maxMemoryContextChars: 8000,
123
+ tracingRetentionDays: 30,
124
+ preferFileTools: true,
125
+ },
126
+ },
127
+ ];
128
+ for (const profile of profiles) {
129
+ const existing = await repo.findOne({ filter: { tag: profile.tag } });
130
+ if (existing) continue;
131
+ await repo.create({
132
+ values: {
133
+ ...profile,
134
+ enabled: true,
135
+ createdAt: new Date(),
136
+ updatedAt: new Date(),
137
+ },
138
+ });
139
+ }
140
+ }
141
+
142
+ async down() {
143
+ // No rollback: new nullable columns and the profile table are backward compatible.
144
+ }
145
+ }
@@ -33,7 +33,7 @@ export default class NormalizeAIEmployeeToolBindings extends Migration {
33
33
  }
34
34
 
35
35
  async down() {
36
- // No rollback: this only moves tool-shaped entries from skillSettings.skills
37
- // into the current NocoBase skillSettings.tools shape.
36
+ // No rollback: this only normalizes current tool-shaped entries and removes
37
+ // retired custom orchestrator tools that are no longer registered.
38
38
  }
39
39
  }
@@ -0,0 +1,193 @@
1
+ import { DataTypes } from '@nocobase/database';
2
+ import { Migration } from '@nocobase/server';
3
+ import { normalizeAIEmployeeSkillSettings } from '../utils/skill-settings';
4
+
5
+ type ProfileSeed = {
6
+ tag: string;
7
+ title: string;
8
+ description: string;
9
+ settings: Record<string, unknown>;
10
+ };
11
+
12
+ const nativePolicyProfiles: ProfileSeed[] = [
13
+ {
14
+ tag: 'default',
15
+ title: 'Default',
16
+ description: 'Default native observer and memory policy for normal AI employee work.',
17
+ settings: {
18
+ nativeObserverEnabled: true,
19
+ memoryInjectionEnabled: true,
20
+ memoryScopes: ['public', 'user', 'agent_user'],
21
+ knowledgeScopes: ['public', 'private'],
22
+ maxMemoryContextChars: 6000,
23
+ tracingRetentionDays: 30,
24
+ },
25
+ },
26
+ {
27
+ tag: 'safe',
28
+ title: 'Safe',
29
+ description: 'Conservative native observer policy with private context enabled only for matching user/agent pairs.',
30
+ settings: {
31
+ nativeObserverEnabled: true,
32
+ memoryInjectionEnabled: true,
33
+ memoryScopes: ['public', 'user', 'agent_user'],
34
+ knowledgeScopes: ['public', 'private'],
35
+ maxMemoryContextChars: 4000,
36
+ tracingRetentionDays: 14,
37
+ },
38
+ },
39
+ {
40
+ tag: 'file-heavy',
41
+ title: 'File Heavy',
42
+ description: 'Native observer policy for agents that need more context while working with files and artifacts.',
43
+ settings: {
44
+ nativeObserverEnabled: true,
45
+ memoryInjectionEnabled: true,
46
+ memoryScopes: ['public', 'user', 'agent_user'],
47
+ knowledgeScopes: ['public', 'private'],
48
+ maxMemoryContextChars: 8000,
49
+ tracingRetentionDays: 30,
50
+ preferFileTools: true,
51
+ },
52
+ },
53
+ ];
54
+
55
+ function readModelValue(record: unknown, key: string) {
56
+ const model = record as { get?: (name: string) => unknown; [key: string]: unknown };
57
+ return typeof model?.get === 'function' ? model.get(key) : model?.[key];
58
+ }
59
+
60
+ function asObject(value: unknown) {
61
+ return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
62
+ }
63
+
64
+ function dropRetiredPolicyKeys(settings: Record<string, unknown>) {
65
+ const retired = new Set([
66
+ 'requirePlanApproval',
67
+ 'allowSubAgents',
68
+ 'allowToolCalls',
69
+ 'maxParallelSubAgents',
70
+ 'maxControllerSteps',
71
+ 'requireVerification',
72
+ ]);
73
+ return Object.fromEntries(Object.entries(settings).filter(([key]) => !retired.has(key)));
74
+ }
75
+
76
+ function normalizeOptionalString(value: unknown) {
77
+ return typeof value === 'string' ? value.trim() : '';
78
+ }
79
+
80
+ function buildContextKey(record: Record<string, unknown>) {
81
+ const scope = normalizeOptionalString(record.scope);
82
+ const userPart = scope === 'public' ? 'public' : String(record.userId || '');
83
+ const agentPart = normalizeOptionalString(record.aiEmployeeUsername) || '*';
84
+ return `${scope}:${userPart}:${agentPart}`;
85
+ }
86
+
87
+ export default class NativePolicyProfileDefaults extends Migration {
88
+ on = 'afterLoad';
89
+ appVersion = '>=0.1.0';
90
+
91
+ async up() {
92
+ await this.ensureNativePolicyProfiles();
93
+ await this.ensureAgentMemoryContextKeys();
94
+ await this.normalizeAIEmployeeToolBindings();
95
+ }
96
+
97
+ private async ensureNativePolicyProfiles() {
98
+ const repo = (this as unknown as { db: { getRepository: (name: string) => any } }).db.getRepository(
99
+ 'agentHarnessProfiles',
100
+ );
101
+ if (!repo) return;
102
+
103
+ for (const profile of nativePolicyProfiles) {
104
+ const existing = await repo.findOne({ filter: { tag: profile.tag } });
105
+ if (!existing) {
106
+ await repo.create({
107
+ values: {
108
+ ...profile,
109
+ enabled: true,
110
+ createdAt: new Date(),
111
+ updatedAt: new Date(),
112
+ },
113
+ });
114
+ continue;
115
+ }
116
+
117
+ const currentSettings = asObject(readModelValue(existing, 'settings'));
118
+ const nextSettings = {
119
+ ...profile.settings,
120
+ ...dropRetiredPolicyKeys(currentSettings),
121
+ };
122
+ await existing.update({
123
+ settings: nextSettings,
124
+ updatedAt: new Date(),
125
+ });
126
+ }
127
+ }
128
+
129
+ private async ensureAgentMemoryContextKeys() {
130
+ const db = (this as any).db;
131
+ const queryInterface = db.sequelize.getQueryInterface();
132
+ const tableName = `${db.options.tablePrefix || ''}agentMemoryContexts`;
133
+ const tableExists = await queryInterface
134
+ .describeTable(tableName)
135
+ .then(() => true)
136
+ .catch(() => false);
137
+ if (!tableExists) return;
138
+
139
+ const tableDesc = await queryInterface.describeTable(tableName);
140
+ if (!tableDesc.contextKey) {
141
+ await queryInterface.addColumn(tableName, 'contextKey', {
142
+ type: DataTypes.STRING(300),
143
+ allowNull: true,
144
+ });
145
+ }
146
+
147
+ const repo = db.getRepository('agentMemoryContexts');
148
+ const rows = await repo.find({});
149
+ for (const row of rows) {
150
+ const data = row.toJSON?.() || row;
151
+ if (!normalizeOptionalString(data.scope)) continue;
152
+ const contextKey = normalizeOptionalString(data.contextKey) || buildContextKey(data);
153
+ if (!contextKey) continue;
154
+ if (contextKey !== data.contextKey) {
155
+ await row.update({ contextKey });
156
+ }
157
+ }
158
+
159
+ await queryInterface
160
+ .changeColumn(tableName, 'contextKey', {
161
+ type: DataTypes.STRING(300),
162
+ allowNull: false,
163
+ })
164
+ .catch(() => {});
165
+
166
+ await queryInterface
167
+ .addIndex(tableName, ['contextKey'], {
168
+ unique: true,
169
+ name: `${tableName}_contextKey_unique`,
170
+ })
171
+ .catch(() => {});
172
+ }
173
+
174
+ private async normalizeAIEmployeeToolBindings() {
175
+ const repo = (this as unknown as { db: { getRepository: (name: string) => any } }).db.getRepository('aiEmployees');
176
+ if (!repo) return;
177
+
178
+ const rows = await repo.find({});
179
+ for (const row of rows) {
180
+ const skillSettings = row.get?.('skillSettings') ?? row.skillSettings;
181
+ const normalized = normalizeAIEmployeeSkillSettings(skillSettings);
182
+ if (!normalized.changed) continue;
183
+ await row.update({
184
+ skillSettings: normalized.skillSettings,
185
+ });
186
+ }
187
+ }
188
+
189
+ async down() {
190
+ // No rollback: these defaults only add native policy keys and normalize
191
+ // context keys required by the current unique constraint.
192
+ }
193
+ }