aios-management-web 0.1.0
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/.env.json +21 -0
- package/README.md +257 -0
- package/data/management-console.db +0 -0
- package/data/management-console.db-shm +0 -0
- package/data/management-console.db-wal +0 -0
- package/dist/assets/index-CV_wjCAG.js +464 -0
- package/dist/assets/index-DfMPB0eV.css +1 -0
- package/dist/index.html +13 -0
- package/docs/spec.md +199 -0
- package/index.html +12 -0
- package/package.json +37 -0
- package/scripts/reset-kernel.js +59 -0
- package/scripts/reset-password.js +22 -0
- package/server/fakes.js +57 -0
- package/server/index.js +21 -0
- package/server/src/api/middleware/auth.js +29 -0
- package/server/src/api/middleware/internal.js +44 -0
- package/server/src/api/routes/index.js +677 -0
- package/server/src/app.js +90 -0
- package/server/src/background/index.js +106 -0
- package/server/src/background/protocol.js +15 -0
- package/server/src/config/env.js +90 -0
- package/server/src/db/index.js +501 -0
- package/server/src/infra/mqtt/management-rpc-client.js +213 -0
- package/server/src/infra/providers/hzg-provider-client.js +39 -0
- package/server/src/infra/s3/object-storage.js +97 -0
- package/server/src/services/agent-quota.js +54 -0
- package/server/src/services/agent-service.js +696 -0
- package/server/src/services/agent-status-sync-service.js +132 -0
- package/server/src/services/audit-log-service.js +39 -0
- package/server/src/services/auth-service.js +153 -0
- package/server/src/services/catalog-sync-service.js +712 -0
- package/server/src/services/external-service.js +308 -0
- package/server/src/services/kernel-reset-service.js +86 -0
- package/server/src/services/portal-service.js +555 -0
- package/server/src/services/system-service.js +580 -0
- package/server/src/services/topic-ping-service.js +282 -0
- package/server/src/utils/errors.js +36 -0
- package/server/src/utils/security.js +22 -0
- package/server/test/agent-service-alignment.test.js +316 -0
- package/server/test/agent-service-create.test.js +662 -0
- package/server/test/agent-status-sync-service.test.js +167 -0
- package/server/test/agent-update-audit.test.js +63 -0
- package/server/test/auth-middleware.test.js +71 -0
- package/server/test/background-services.test.js +160 -0
- package/server/test/catalog-sync-service.test.js +920 -0
- package/server/test/db-reset-migration.test.js +123 -0
- package/server/test/env-config.test.js +68 -0
- package/server/test/external-service.test.js +380 -0
- package/server/test/hzg-provider-client.test.js +50 -0
- package/server/test/internal-auth-middleware.test.js +66 -0
- package/server/test/kernel-reset-service.test.js +112 -0
- package/server/test/management-rpc-client.test.js +105 -0
- package/server/test/portal-service-access-tokens.test.js +121 -0
- package/server/test/portal-service-alignment.test.js +318 -0
- package/server/test/portal-service-management-logs.test.js +114 -0
- package/server/test/reset-kernel-cli.test.js +23 -0
- package/server/test/service-api-auth-middleware.test.js +59 -0
- package/server/test/system-service-alignment.test.js +265 -0
- package/server/test/topic-ping-service.test.js +182 -0
- package/server/test/usage-refresh-audit-route.test.js +82 -0
- package/src/App.jsx +1 -0
- package/src/api.js +1 -0
- package/src/app/App.jsx +346 -0
- package/src/app/api-client.js +112 -0
- package/src/components/AppShell.jsx +117 -0
- package/src/components/CardTitleWithReload.jsx +20 -0
- package/src/components/DeleteActionButton.jsx +31 -0
- package/src/main.jsx +14 -0
- package/src/pages/AgentsPage.jsx +647 -0
- package/src/pages/AiosUsersPage.jsx +151 -0
- package/src/pages/DashboardPage.jsx +72 -0
- package/src/pages/LoginPage.jsx +41 -0
- package/src/pages/SettingsPage.jsx +431 -0
- package/src/pages/SkillsPage.jsx +175 -0
- package/src/pages/SystemLogsPage.jsx +349 -0
- package/src/pages/SystemsPage.jsx +498 -0
- package/src/pages/TemplatesPage.jsx +207 -0
- package/src/pages/UserManagementPage.jsx +25 -0
- package/src/pages/UsersPage.jsx +192 -0
- package/src/pages/system-logs/SystemLogsTabs.jsx +362 -0
- package/src/styles.css +222 -0
- package/src/utils/format.js +63 -0
- package/test/.reports/fast-2026-05-25T08-32-39-420Z.json +299 -0
- package/test/integration/common.js +208 -0
- package/test/integration/fast.js +135 -0
- package/test/integration/full.js +306 -0
- package/test/run-browser-e2e.js +212 -0
- package/test/run-jasmine.js +21 -0
- package/test/setup.js +1 -0
- package/vite.config.js +12 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
import { jsonParse, withSerializedTransaction } from "../db/index.js";
|
|
2
|
+
import {
|
|
3
|
+
normalizeAgentStatus,
|
|
4
|
+
normalizeUsage,
|
|
5
|
+
statusAfterUsageRefresh
|
|
6
|
+
} from "./agent-quota.js";
|
|
7
|
+
|
|
8
|
+
const USAGE_SYNC_CONCURRENCY = 2;
|
|
9
|
+
const USAGE_FIELDS = [
|
|
10
|
+
"daily",
|
|
11
|
+
"day",
|
|
12
|
+
"dailyTokens",
|
|
13
|
+
"today",
|
|
14
|
+
"totalTokens"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function hasUsageFields(payload) {
|
|
18
|
+
if (!payload || typeof payload !== "object") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return USAGE_FIELDS.some((field) => Object.prototype.hasOwnProperty.call(payload, field));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseUsagePayload(payload) {
|
|
26
|
+
if (!payload || typeof payload !== "object") {
|
|
27
|
+
throw new Error("agent.usage returned an empty payload");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const source = payload.usage || payload.tokens || payload;
|
|
31
|
+
if (!hasUsageFields(source)) {
|
|
32
|
+
throw new Error("agent.usage did not return usage fields");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return normalizeUsage(payload);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function mapWithConcurrency(items, limit, mapper) {
|
|
39
|
+
const results = new Array(items.length);
|
|
40
|
+
let nextIndex = 0;
|
|
41
|
+
|
|
42
|
+
async function worker() {
|
|
43
|
+
while (nextIndex < items.length) {
|
|
44
|
+
const currentIndex = nextIndex;
|
|
45
|
+
nextIndex += 1;
|
|
46
|
+
results[currentIndex] = await mapper(items[currentIndex], currentIndex);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const workerCount = Math.min(Math.max(1, limit), Math.max(1, items.length));
|
|
51
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function firstText(...values) {
|
|
56
|
+
for (const value of values) {
|
|
57
|
+
if (typeof value === "string" && value.trim()) {
|
|
58
|
+
return value.trim();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isBuiltInItem(item) {
|
|
66
|
+
return Boolean(item?.["is-built-in"] ?? item?.is_builtin ?? item?.isBuiltIn);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class CatalogSyncService {
|
|
70
|
+
constructor({ db, rpcClient, auditLogService = null, env = null }) {
|
|
71
|
+
this.db = db;
|
|
72
|
+
this.rpcClient = rpcClient;
|
|
73
|
+
this.auditLogService = auditLogService;
|
|
74
|
+
this.managementTimeoutMs = Number(env?.managementTimeoutMs || 120000);
|
|
75
|
+
this.pendingUsageRefresh = null;
|
|
76
|
+
this.pendingAgentSync = null;
|
|
77
|
+
this.pendingSkillSync = null;
|
|
78
|
+
this.pendingTemplateSync = null;
|
|
79
|
+
this.pendingSystemSync = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getUsageRefreshStatus() {
|
|
83
|
+
return this.readState("usage_refresh_state");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getAgentSyncStatus() {
|
|
87
|
+
return this.readState("agent_sync_state");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getSkillSyncStatus() {
|
|
91
|
+
return this.readState("skill_sync_state");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getTemplateSyncStatus() {
|
|
95
|
+
return this.readState("template_sync_state");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getSystemSyncStatus() {
|
|
99
|
+
return this.readState("system_sync_state");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
callManagement(action, params = {}, timeoutMs = this.managementTimeoutMs) {
|
|
103
|
+
return this.rpcClient.call(action, params, timeoutMs);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async syncAgentsFromKernel(now = new Date().toISOString()) {
|
|
107
|
+
const remote = await this.callManagement("agent.list");
|
|
108
|
+
const items = Array.isArray(remote?.items) ? remote.items : [];
|
|
109
|
+
await withSerializedTransaction(this.db, () => {
|
|
110
|
+
this.syncAgents(items, new Map(), now);
|
|
111
|
+
});
|
|
112
|
+
return items.length;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async syncTemplatesFromKernel(now = new Date().toISOString()) {
|
|
116
|
+
const remote = await this.callManagement("agent.template.list");
|
|
117
|
+
const items = Array.isArray(remote?.items) ? remote.items : [];
|
|
118
|
+
await withSerializedTransaction(this.db, () => {
|
|
119
|
+
this.syncTemplates(items, now);
|
|
120
|
+
});
|
|
121
|
+
return items.length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async syncSkillsFromKernel(now = new Date().toISOString()) {
|
|
125
|
+
const remote = await this.callManagement("skills.global.list");
|
|
126
|
+
const items = Array.isArray(remote?.items) ? remote.items : [];
|
|
127
|
+
await withSerializedTransaction(this.db, () => {
|
|
128
|
+
this.syncSkills(items, now);
|
|
129
|
+
});
|
|
130
|
+
return items.length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async syncSystemsFromKernel(now = new Date().toISOString()) {
|
|
134
|
+
const [remote, ontologies] = await Promise.all([
|
|
135
|
+
this.callManagement("apps.list"),
|
|
136
|
+
this.callManagement("ontology.list")
|
|
137
|
+
]);
|
|
138
|
+
const items = Array.isArray(remote?.items) ? remote.items : [];
|
|
139
|
+
const ontologyItems = Array.isArray(ontologies?.items) ? ontologies.items : [];
|
|
140
|
+
const builtInOntologies = new Set(ontologyItems
|
|
141
|
+
.filter(isBuiltInItem)
|
|
142
|
+
.map((item) => firstText(item?.name, item?.ontologyName, item?.id).toLowerCase())
|
|
143
|
+
.filter(Boolean));
|
|
144
|
+
await withSerializedTransaction(this.db, () => {
|
|
145
|
+
this.syncSystems(items, now, builtInOntologies);
|
|
146
|
+
});
|
|
147
|
+
return items.length;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async syncAgentsTask({ trigger = "manual" } = {}) {
|
|
151
|
+
if (this.pendingAgentSync) {
|
|
152
|
+
return await this.pendingAgentSync;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.pendingAgentSync = this.runSingleSync({
|
|
156
|
+
tableName: "agent_sync_state",
|
|
157
|
+
trigger,
|
|
158
|
+
run: () => this.syncAgentsFromKernel(),
|
|
159
|
+
summaryKey: "agents"
|
|
160
|
+
}).finally(() => {
|
|
161
|
+
this.pendingAgentSync = null;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return await this.pendingAgentSync;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async syncSkillsTask({ trigger = "manual" } = {}) {
|
|
168
|
+
if (this.pendingSkillSync) {
|
|
169
|
+
return await this.pendingSkillSync;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.pendingSkillSync = this.runSingleSync({
|
|
173
|
+
tableName: "skill_sync_state",
|
|
174
|
+
trigger,
|
|
175
|
+
run: () => this.syncSkillsFromKernel(),
|
|
176
|
+
summaryKey: "skills"
|
|
177
|
+
}).finally(() => {
|
|
178
|
+
this.pendingSkillSync = null;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return await this.pendingSkillSync;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async syncTemplatesTask({ trigger = "manual" } = {}) {
|
|
185
|
+
if (this.pendingTemplateSync) {
|
|
186
|
+
return await this.pendingTemplateSync;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.pendingTemplateSync = this.runSingleSync({
|
|
190
|
+
tableName: "template_sync_state",
|
|
191
|
+
trigger,
|
|
192
|
+
run: () => this.syncTemplatesFromKernel(),
|
|
193
|
+
summaryKey: "templates"
|
|
194
|
+
}).finally(() => {
|
|
195
|
+
this.pendingTemplateSync = null;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return await this.pendingTemplateSync;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async syncSystemsTask({ trigger = "manual" } = {}) {
|
|
202
|
+
if (this.pendingSystemSync) {
|
|
203
|
+
return await this.pendingSystemSync;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.pendingSystemSync = this.runSingleSync({
|
|
207
|
+
tableName: "system_sync_state",
|
|
208
|
+
trigger,
|
|
209
|
+
run: () => this.syncSystemsFromKernel(),
|
|
210
|
+
summaryKey: "systems"
|
|
211
|
+
}).finally(() => {
|
|
212
|
+
this.pendingSystemSync = null;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return await this.pendingSystemSync;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async fetchAgentUsageSnapshots(agentItems, { strict = false } = {}) {
|
|
219
|
+
const pairs = await mapWithConcurrency(agentItems, USAGE_SYNC_CONCURRENCY, async (item) => {
|
|
220
|
+
const agentId = String(item?.agentId || "").trim();
|
|
221
|
+
if (!agentId) {
|
|
222
|
+
return [agentId, null];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const raw = await this.callManagement("agent.usage", { agentId });
|
|
227
|
+
return [agentId, {
|
|
228
|
+
raw,
|
|
229
|
+
usage: parseUsagePayload(raw),
|
|
230
|
+
captured_at: new Date().toISOString()
|
|
231
|
+
}];
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (strict) {
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
return [agentId, null];
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return new Map(pairs.filter(([agentId]) => agentId));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async runSingleSync({ tableName, trigger, run, summaryKey }) {
|
|
244
|
+
const startedAt = new Date().toISOString();
|
|
245
|
+
|
|
246
|
+
if (!this.rpcClient.isConfigured()) {
|
|
247
|
+
this.updateState(tableName, {
|
|
248
|
+
status: "skipped",
|
|
249
|
+
triggerSource: trigger,
|
|
250
|
+
startedAt,
|
|
251
|
+
finishedAt: startedAt,
|
|
252
|
+
lastSuccessAt: null,
|
|
253
|
+
errorMessage: "Management RPC is not configured",
|
|
254
|
+
summary: {}
|
|
255
|
+
});
|
|
256
|
+
return this.readState(tableName);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.updateState(tableName, {
|
|
260
|
+
status: "running",
|
|
261
|
+
triggerSource: trigger,
|
|
262
|
+
startedAt,
|
|
263
|
+
finishedAt: null,
|
|
264
|
+
errorMessage: null,
|
|
265
|
+
summary: {}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const count = await run();
|
|
270
|
+
const finishedAt = new Date().toISOString();
|
|
271
|
+
this.updateState(tableName, {
|
|
272
|
+
status: "success",
|
|
273
|
+
triggerSource: trigger,
|
|
274
|
+
startedAt,
|
|
275
|
+
finishedAt,
|
|
276
|
+
lastSuccessAt: finishedAt,
|
|
277
|
+
errorMessage: null,
|
|
278
|
+
summary: {
|
|
279
|
+
[summaryKey]: count
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
return this.readState(tableName);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
const finishedAt = new Date().toISOString();
|
|
285
|
+
this.updateState(tableName, {
|
|
286
|
+
status: "failed",
|
|
287
|
+
triggerSource: trigger,
|
|
288
|
+
startedAt,
|
|
289
|
+
finishedAt,
|
|
290
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
291
|
+
summary: {}
|
|
292
|
+
});
|
|
293
|
+
return this.readState(tableName);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async refreshAgentUsage({ trigger = "scheduled" } = {}) {
|
|
298
|
+
if (this.pendingUsageRefresh) {
|
|
299
|
+
return await this.pendingUsageRefresh;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.pendingUsageRefresh = this.runUsageRefresh(trigger).finally(() => {
|
|
303
|
+
this.pendingUsageRefresh = null;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return await this.pendingUsageRefresh;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async runUsageRefresh(trigger) {
|
|
310
|
+
const startedAt = new Date().toISOString();
|
|
311
|
+
|
|
312
|
+
if (!this.rpcClient.isConfigured()) {
|
|
313
|
+
this.updateState("usage_refresh_state", {
|
|
314
|
+
status: "skipped",
|
|
315
|
+
triggerSource: trigger,
|
|
316
|
+
startedAt,
|
|
317
|
+
finishedAt: startedAt,
|
|
318
|
+
lastSuccessAt: null,
|
|
319
|
+
errorMessage: "Management RPC is not configured",
|
|
320
|
+
summary: {
|
|
321
|
+
refreshed_agents: 0
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return this.getUsageRefreshStatus();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.updateState("usage_refresh_state", {
|
|
328
|
+
status: "running",
|
|
329
|
+
triggerSource: trigger,
|
|
330
|
+
startedAt,
|
|
331
|
+
finishedAt: null,
|
|
332
|
+
errorMessage: null,
|
|
333
|
+
summary: {}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const agentRows = this.db.prepare(`
|
|
337
|
+
SELECT *
|
|
338
|
+
FROM agents
|
|
339
|
+
ORDER BY slug
|
|
340
|
+
`).all();
|
|
341
|
+
|
|
342
|
+
if (agentRows.length === 0) {
|
|
343
|
+
this.updateState("usage_refresh_state", {
|
|
344
|
+
status: "skipped",
|
|
345
|
+
triggerSource: trigger,
|
|
346
|
+
startedAt,
|
|
347
|
+
finishedAt: startedAt,
|
|
348
|
+
lastSuccessAt: null,
|
|
349
|
+
errorMessage: null,
|
|
350
|
+
summary: {}
|
|
351
|
+
});
|
|
352
|
+
return this.getUsageRefreshStatus();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const remoteItems = agentRows.map((row) => ({
|
|
356
|
+
agentId: row.slug
|
|
357
|
+
}));
|
|
358
|
+
const now = new Date();
|
|
359
|
+
const capturedAt = now.toISOString();
|
|
360
|
+
const updateAgent = this.db.prepare(`
|
|
361
|
+
UPDATE agents
|
|
362
|
+
SET usage_snapshot_json = ?, status = ?, updated_at = ?
|
|
363
|
+
WHERE id = ?
|
|
364
|
+
`);
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const usageMap = await this.fetchAgentUsageSnapshots(remoteItems, { strict: true });
|
|
368
|
+
let refreshedAgents = 0;
|
|
369
|
+
|
|
370
|
+
await withSerializedTransaction(this.db, () => {
|
|
371
|
+
for (const row of agentRows) {
|
|
372
|
+
const snapshot = usageMap.get(row.slug);
|
|
373
|
+
if (!snapshot) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const nextStatus = statusAfterUsageRefresh(row, snapshot.usage);
|
|
378
|
+
updateAgent.run(JSON.stringify(snapshot), nextStatus, capturedAt, row.id);
|
|
379
|
+
refreshedAgents += 1;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const finishedAt = new Date().toISOString();
|
|
383
|
+
this.updateState("usage_refresh_state", {
|
|
384
|
+
status: "success",
|
|
385
|
+
triggerSource: trigger,
|
|
386
|
+
startedAt,
|
|
387
|
+
finishedAt,
|
|
388
|
+
lastSuccessAt: finishedAt,
|
|
389
|
+
errorMessage: null,
|
|
390
|
+
summary: {
|
|
391
|
+
refreshed_agents: refreshedAgents,
|
|
392
|
+
captured_at: capturedAt
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
return this.getUsageRefreshStatus();
|
|
398
|
+
} catch (error) {
|
|
399
|
+
const finishedAt = new Date().toISOString();
|
|
400
|
+
this.updateState("usage_refresh_state", {
|
|
401
|
+
status: "failed",
|
|
402
|
+
triggerSource: trigger,
|
|
403
|
+
startedAt,
|
|
404
|
+
finishedAt,
|
|
405
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
406
|
+
summary: {}
|
|
407
|
+
});
|
|
408
|
+
return this.getUsageRefreshStatus();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
syncAgents(items, usageMap, now) {
|
|
413
|
+
const existingRows = this.db.prepare("SELECT * FROM agents").all();
|
|
414
|
+
const existingMap = new Map(existingRows.map((row) => [row.slug, row]));
|
|
415
|
+
const seen = new Set();
|
|
416
|
+
const insert = this.db.prepare(`
|
|
417
|
+
INSERT INTO agents (
|
|
418
|
+
slug, agent_name, description, docs_content, template_name, status, tags_json,
|
|
419
|
+
daily_limit, usage_snapshot_json, remote_state_json, created_at, updated_at
|
|
420
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
421
|
+
`);
|
|
422
|
+
const update = this.db.prepare(`
|
|
423
|
+
UPDATE agents
|
|
424
|
+
SET agent_name = ?, template_name = ?, status = ?, usage_snapshot_json = ?, remote_state_json = ?, updated_at = ?
|
|
425
|
+
WHERE id = ?
|
|
426
|
+
`);
|
|
427
|
+
const remove = this.db.prepare("DELETE FROM agents WHERE id = ?");
|
|
428
|
+
|
|
429
|
+
for (const item of items) {
|
|
430
|
+
const slug = String(item?.agentId || "").trim();
|
|
431
|
+
if (!slug) {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
seen.add(slug);
|
|
436
|
+
const existing = existingMap.get(slug);
|
|
437
|
+
const existingUsageSnapshot = jsonParse(existing?.usage_snapshot_json, {
|
|
438
|
+
usage: { daily: 0 }
|
|
439
|
+
});
|
|
440
|
+
const usageSnapshot = usageMap.get(slug) || {
|
|
441
|
+
...existingUsageSnapshot,
|
|
442
|
+
usage: normalizeUsage(existingUsageSnapshot.usage)
|
|
443
|
+
};
|
|
444
|
+
const remoteStateJson = JSON.stringify(item ?? {});
|
|
445
|
+
const agentName = firstText(item?.name, slug);
|
|
446
|
+
const status = normalizeAgentStatus(item?.status);
|
|
447
|
+
const templateName = firstText(item?.templateName, item?.template, existing?.template_name, "default");
|
|
448
|
+
|
|
449
|
+
if (existing) {
|
|
450
|
+
const nextStatus = existing.status === "disabled"
|
|
451
|
+
? "disabled"
|
|
452
|
+
: (existing.status === "overlimit" ? "overlimit" : normalizeAgentStatus(item?.status));
|
|
453
|
+
update.run(
|
|
454
|
+
agentName,
|
|
455
|
+
templateName,
|
|
456
|
+
nextStatus,
|
|
457
|
+
JSON.stringify(usageSnapshot),
|
|
458
|
+
remoteStateJson,
|
|
459
|
+
now,
|
|
460
|
+
existing.id
|
|
461
|
+
);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
insert.run(
|
|
466
|
+
slug,
|
|
467
|
+
agentName,
|
|
468
|
+
"",
|
|
469
|
+
"",
|
|
470
|
+
templateName,
|
|
471
|
+
status,
|
|
472
|
+
"[]",
|
|
473
|
+
-1,
|
|
474
|
+
JSON.stringify(usageSnapshot),
|
|
475
|
+
remoteStateJson,
|
|
476
|
+
now,
|
|
477
|
+
now
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
for (const row of existingRows) {
|
|
482
|
+
if (!seen.has(row.slug)) {
|
|
483
|
+
remove.run(row.id);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
syncTemplates(items, now) {
|
|
489
|
+
const existingRows = this.db.prepare("SELECT * FROM agent_templates").all();
|
|
490
|
+
const existingMap = new Map(existingRows.map((row) => [row.template_name, row]));
|
|
491
|
+
const seen = new Set();
|
|
492
|
+
const insert = this.db.prepare(`
|
|
493
|
+
INSERT INTO agent_templates (
|
|
494
|
+
template_name, description, artifact_id, remote_status, remote_result_json, is_builtin, created_at, updated_at
|
|
495
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
496
|
+
`);
|
|
497
|
+
const update = this.db.prepare(`
|
|
498
|
+
UPDATE agent_templates
|
|
499
|
+
SET description = ?, artifact_id = ?, remote_status = ?, remote_result_json = ?, is_builtin = ?, updated_at = ?
|
|
500
|
+
WHERE id = ?
|
|
501
|
+
`);
|
|
502
|
+
const remove = this.db.prepare("DELETE FROM agent_templates WHERE id = ?");
|
|
503
|
+
|
|
504
|
+
for (const item of items) {
|
|
505
|
+
const templateName = String(item?.templateName || "").trim();
|
|
506
|
+
if (!templateName) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
seen.add(templateName);
|
|
511
|
+
const existing = existingMap.get(templateName);
|
|
512
|
+
const artifactId = existing?.artifact_id ?? this.createPlaceholderArtifact({
|
|
513
|
+
kind: "template",
|
|
514
|
+
originalName: firstText(item?.path, `${templateName}.remote`)
|
|
515
|
+
});
|
|
516
|
+
const remoteResultJson = JSON.stringify(item ?? {});
|
|
517
|
+
const isBuiltin = isBuiltInItem(item) ? 1 : 0;
|
|
518
|
+
|
|
519
|
+
if (existing) {
|
|
520
|
+
update.run(
|
|
521
|
+
existing.description || "",
|
|
522
|
+
artifactId,
|
|
523
|
+
"ready",
|
|
524
|
+
remoteResultJson,
|
|
525
|
+
isBuiltin,
|
|
526
|
+
now,
|
|
527
|
+
existing.id
|
|
528
|
+
);
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
insert.run(
|
|
533
|
+
templateName,
|
|
534
|
+
"",
|
|
535
|
+
artifactId,
|
|
536
|
+
"ready",
|
|
537
|
+
remoteResultJson,
|
|
538
|
+
isBuiltin,
|
|
539
|
+
now,
|
|
540
|
+
now
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
for (const row of existingRows) {
|
|
545
|
+
if (!seen.has(row.template_name)) {
|
|
546
|
+
remove.run(row.id);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
syncSkills(items, now) {
|
|
552
|
+
const existingRows = this.db.prepare("SELECT * FROM skills").all();
|
|
553
|
+
const existingMap = new Map(existingRows.map((row) => [row.slug, row]));
|
|
554
|
+
const seen = new Set();
|
|
555
|
+
const insert = this.db.prepare(`
|
|
556
|
+
INSERT INTO skills (
|
|
557
|
+
slug, description, artifact_id, remote_status, is_builtin, created_at, updated_at
|
|
558
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
559
|
+
`);
|
|
560
|
+
const update = this.db.prepare(`
|
|
561
|
+
UPDATE skills
|
|
562
|
+
SET description = ?, artifact_id = ?, remote_status = ?, is_builtin = ?, updated_at = ?
|
|
563
|
+
WHERE id = ?
|
|
564
|
+
`);
|
|
565
|
+
const remove = this.db.prepare("DELETE FROM skills WHERE id = ?");
|
|
566
|
+
|
|
567
|
+
for (const item of items) {
|
|
568
|
+
const slug = String(item?.slug || "").trim();
|
|
569
|
+
if (!slug) {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
seen.add(slug);
|
|
574
|
+
const existing = existingMap.get(slug);
|
|
575
|
+
const origin = item?.origin && typeof item.origin === "object" ? item.origin : {};
|
|
576
|
+
const description = firstText(origin.description, existing?.description);
|
|
577
|
+
const isBuiltin = isBuiltInItem(item) ? 1 : 0;
|
|
578
|
+
|
|
579
|
+
if (existing) {
|
|
580
|
+
update.run(
|
|
581
|
+
description,
|
|
582
|
+
existing.artifact_id ?? null,
|
|
583
|
+
"installed",
|
|
584
|
+
isBuiltin,
|
|
585
|
+
now,
|
|
586
|
+
existing.id
|
|
587
|
+
);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
insert.run(
|
|
592
|
+
slug,
|
|
593
|
+
description,
|
|
594
|
+
null,
|
|
595
|
+
"installed",
|
|
596
|
+
isBuiltin,
|
|
597
|
+
now,
|
|
598
|
+
now
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
for (const row of existingRows) {
|
|
603
|
+
if (!seen.has(row.slug)) {
|
|
604
|
+
remove.run(row.id);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
syncSystems(items, now = new Date().toISOString(), builtInOntologies = new Set()) {
|
|
610
|
+
const existingRows = this.db.prepare("SELECT * FROM business_systems").all();
|
|
611
|
+
const seen = new Set();
|
|
612
|
+
const updateBuiltin = this.db.prepare(`
|
|
613
|
+
UPDATE business_systems
|
|
614
|
+
SET is_builtin = ?, updated_at = ?
|
|
615
|
+
WHERE id = ?
|
|
616
|
+
`);
|
|
617
|
+
const remove = this.db.prepare("DELETE FROM business_systems WHERE id = ?");
|
|
618
|
+
|
|
619
|
+
for (const item of items) {
|
|
620
|
+
const applicationName = firstText(item?.applicationName, item?.id, item?.name).toLowerCase();
|
|
621
|
+
if (!applicationName) {
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
seen.add(applicationName);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
for (const row of existingRows) {
|
|
629
|
+
if (!seen.has(row.application_name)) {
|
|
630
|
+
remove.run(row.id);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const isBuiltin = builtInOntologies.has(String(row.application_name || "").toLowerCase()) ? 1 : 0;
|
|
635
|
+
if (Number(row.is_builtin || 0) !== isBuiltin) {
|
|
636
|
+
updateBuiltin.run(isBuiltin, now, row.id);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
createPlaceholderArtifact({ kind, originalName }) {
|
|
642
|
+
const result = this.db.prepare(`
|
|
643
|
+
INSERT INTO artifacts (
|
|
644
|
+
kind, bucket, object_key, original_name, mime_type, byte_size, created_by, created_at
|
|
645
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
646
|
+
`).run(
|
|
647
|
+
kind,
|
|
648
|
+
"",
|
|
649
|
+
"",
|
|
650
|
+
originalName,
|
|
651
|
+
"application/octet-stream",
|
|
652
|
+
0,
|
|
653
|
+
null,
|
|
654
|
+
new Date().toISOString()
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
return result.lastInsertRowid;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
readState(tableName) {
|
|
661
|
+
const row = this.db.prepare(`SELECT * FROM ${tableName} WHERE id = 1`).get();
|
|
662
|
+
if (!row) {
|
|
663
|
+
return {
|
|
664
|
+
id: 1,
|
|
665
|
+
status: "idle",
|
|
666
|
+
trigger_source: null,
|
|
667
|
+
started_at: null,
|
|
668
|
+
finished_at: null,
|
|
669
|
+
last_success_at: null,
|
|
670
|
+
error_message: null,
|
|
671
|
+
summary: {}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
...row,
|
|
677
|
+
summary: jsonParse(row.summary_json, {})
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
updateState(tableName, { status, triggerSource, startedAt, finishedAt, lastSuccessAt, errorMessage, summary }) {
|
|
682
|
+
const current = this.db.prepare(`SELECT * FROM ${tableName} WHERE id = 1`).get();
|
|
683
|
+
const createdAt = current?.created_at || new Date().toISOString();
|
|
684
|
+
const nextLastSuccessAt = lastSuccessAt ?? current?.last_success_at ?? null;
|
|
685
|
+
|
|
686
|
+
this.db.prepare(`
|
|
687
|
+
INSERT INTO ${tableName} (
|
|
688
|
+
id, status, trigger_source, started_at, finished_at, last_success_at,
|
|
689
|
+
error_message, summary_json, created_at, updated_at
|
|
690
|
+
) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
691
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
692
|
+
status = excluded.status,
|
|
693
|
+
trigger_source = excluded.trigger_source,
|
|
694
|
+
started_at = excluded.started_at,
|
|
695
|
+
finished_at = excluded.finished_at,
|
|
696
|
+
last_success_at = excluded.last_success_at,
|
|
697
|
+
error_message = excluded.error_message,
|
|
698
|
+
summary_json = excluded.summary_json,
|
|
699
|
+
updated_at = excluded.updated_at
|
|
700
|
+
`).run(
|
|
701
|
+
status,
|
|
702
|
+
triggerSource || null,
|
|
703
|
+
startedAt || null,
|
|
704
|
+
finishedAt || null,
|
|
705
|
+
nextLastSuccessAt,
|
|
706
|
+
errorMessage || null,
|
|
707
|
+
JSON.stringify(summary || {}),
|
|
708
|
+
createdAt,
|
|
709
|
+
new Date().toISOString()
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|