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,920 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
|
|
3
|
+
import { AgentStatusSyncService } from "../src/services/agent-status-sync-service.js";
|
|
4
|
+
import { CatalogSyncService } from "../src/services/catalog-sync-service.js";
|
|
5
|
+
|
|
6
|
+
const TEST_NOW = "2026-05-26T00:00:00.000Z";
|
|
7
|
+
|
|
8
|
+
function insertAgent(db, {
|
|
9
|
+
slug,
|
|
10
|
+
agentName = slug,
|
|
11
|
+
description = "",
|
|
12
|
+
docsContent = "",
|
|
13
|
+
templateName = "default",
|
|
14
|
+
status = "normal",
|
|
15
|
+
tagsJson = "[]",
|
|
16
|
+
dailyLimit = -1,
|
|
17
|
+
usageSnapshotJson = "{}",
|
|
18
|
+
remoteStateJson = "{}",
|
|
19
|
+
createdAt = TEST_NOW,
|
|
20
|
+
updatedAt = TEST_NOW
|
|
21
|
+
}) {
|
|
22
|
+
return db.prepare(`
|
|
23
|
+
INSERT INTO agents (
|
|
24
|
+
slug, agent_name, description, docs_content, template_name, status, tags_json,
|
|
25
|
+
daily_limit, usage_snapshot_json, remote_state_json, created_at, updated_at
|
|
26
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
27
|
+
`).run(
|
|
28
|
+
slug,
|
|
29
|
+
agentName,
|
|
30
|
+
description,
|
|
31
|
+
docsContent,
|
|
32
|
+
templateName,
|
|
33
|
+
status,
|
|
34
|
+
tagsJson,
|
|
35
|
+
dailyLimit,
|
|
36
|
+
usageSnapshotJson,
|
|
37
|
+
remoteStateJson,
|
|
38
|
+
createdAt,
|
|
39
|
+
updatedAt
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createDb() {
|
|
44
|
+
const db = new DatabaseSync(":memory:");
|
|
45
|
+
db.exec(`
|
|
46
|
+
PRAGMA foreign_keys = ON;
|
|
47
|
+
|
|
48
|
+
CREATE TABLE usage_refresh_state (
|
|
49
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
50
|
+
status TEXT NOT NULL,
|
|
51
|
+
trigger_source TEXT,
|
|
52
|
+
started_at TEXT,
|
|
53
|
+
finished_at TEXT,
|
|
54
|
+
last_success_at TEXT,
|
|
55
|
+
error_message TEXT,
|
|
56
|
+
summary_json TEXT NOT NULL,
|
|
57
|
+
created_at TEXT NOT NULL,
|
|
58
|
+
updated_at TEXT NOT NULL
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE TABLE agent_sync_state (
|
|
62
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
63
|
+
status TEXT NOT NULL,
|
|
64
|
+
trigger_source TEXT,
|
|
65
|
+
started_at TEXT,
|
|
66
|
+
finished_at TEXT,
|
|
67
|
+
last_success_at TEXT,
|
|
68
|
+
error_message TEXT,
|
|
69
|
+
summary_json TEXT NOT NULL,
|
|
70
|
+
created_at TEXT NOT NULL,
|
|
71
|
+
updated_at TEXT NOT NULL
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE skill_sync_state (
|
|
75
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
76
|
+
status TEXT NOT NULL,
|
|
77
|
+
trigger_source TEXT,
|
|
78
|
+
started_at TEXT,
|
|
79
|
+
finished_at TEXT,
|
|
80
|
+
last_success_at TEXT,
|
|
81
|
+
error_message TEXT,
|
|
82
|
+
summary_json TEXT NOT NULL,
|
|
83
|
+
created_at TEXT NOT NULL,
|
|
84
|
+
updated_at TEXT NOT NULL
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
CREATE TABLE template_sync_state (
|
|
88
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
89
|
+
status TEXT NOT NULL,
|
|
90
|
+
trigger_source TEXT,
|
|
91
|
+
started_at TEXT,
|
|
92
|
+
finished_at TEXT,
|
|
93
|
+
last_success_at TEXT,
|
|
94
|
+
error_message TEXT,
|
|
95
|
+
summary_json TEXT NOT NULL,
|
|
96
|
+
created_at TEXT NOT NULL,
|
|
97
|
+
updated_at TEXT NOT NULL
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE TABLE system_sync_state (
|
|
101
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
102
|
+
status TEXT NOT NULL,
|
|
103
|
+
trigger_source TEXT,
|
|
104
|
+
started_at TEXT,
|
|
105
|
+
finished_at TEXT,
|
|
106
|
+
last_success_at TEXT,
|
|
107
|
+
error_message TEXT,
|
|
108
|
+
summary_json TEXT NOT NULL,
|
|
109
|
+
created_at TEXT NOT NULL,
|
|
110
|
+
updated_at TEXT NOT NULL
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE artifacts (
|
|
114
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
115
|
+
kind TEXT NOT NULL,
|
|
116
|
+
bucket TEXT NOT NULL,
|
|
117
|
+
object_key TEXT NOT NULL,
|
|
118
|
+
original_name TEXT NOT NULL,
|
|
119
|
+
mime_type TEXT NOT NULL,
|
|
120
|
+
byte_size INTEGER NOT NULL,
|
|
121
|
+
created_by INTEGER,
|
|
122
|
+
created_at TEXT NOT NULL
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
CREATE TABLE agents (
|
|
126
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
127
|
+
slug TEXT NOT NULL UNIQUE,
|
|
128
|
+
agent_name TEXT NOT NULL,
|
|
129
|
+
description TEXT NOT NULL,
|
|
130
|
+
docs_content TEXT NOT NULL,
|
|
131
|
+
template_name TEXT NOT NULL DEFAULT 'default',
|
|
132
|
+
status TEXT NOT NULL,
|
|
133
|
+
tags_json TEXT NOT NULL,
|
|
134
|
+
daily_limit INTEGER NOT NULL,
|
|
135
|
+
usage_snapshot_json TEXT NOT NULL,
|
|
136
|
+
remote_state_json TEXT NOT NULL,
|
|
137
|
+
created_at TEXT NOT NULL,
|
|
138
|
+
updated_at TEXT NOT NULL
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
CREATE TABLE external_sessions (
|
|
142
|
+
session_id TEXT PRIMARY KEY,
|
|
143
|
+
aios_user_id INTEGER NOT NULL,
|
|
144
|
+
agent_id INTEGER NOT NULL,
|
|
145
|
+
created_at TEXT NOT NULL,
|
|
146
|
+
updated_at TEXT NOT NULL,
|
|
147
|
+
UNIQUE (aios_user_id, agent_id),
|
|
148
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
|
|
149
|
+
FOREIGN KEY (aios_user_id) REFERENCES aios_users(id) ON DELETE CASCADE
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
CREATE TABLE aios_users (
|
|
153
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
154
|
+
username TEXT NOT NULL UNIQUE,
|
|
155
|
+
created_at TEXT NOT NULL,
|
|
156
|
+
updated_at TEXT NOT NULL
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
CREATE TABLE agent_permissions (
|
|
160
|
+
agent_id INTEGER NOT NULL,
|
|
161
|
+
aios_user_id INTEGER NOT NULL,
|
|
162
|
+
PRIMARY KEY (agent_id, aios_user_id),
|
|
163
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
|
|
164
|
+
FOREIGN KEY (aios_user_id) REFERENCES aios_users(id) ON DELETE CASCADE
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
CREATE TABLE agent_templates (
|
|
168
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
169
|
+
template_name TEXT NOT NULL UNIQUE,
|
|
170
|
+
description TEXT NOT NULL,
|
|
171
|
+
artifact_id INTEGER NOT NULL,
|
|
172
|
+
remote_status TEXT NOT NULL,
|
|
173
|
+
remote_result_json TEXT,
|
|
174
|
+
is_builtin INTEGER NOT NULL DEFAULT 0,
|
|
175
|
+
created_at TEXT NOT NULL,
|
|
176
|
+
updated_at TEXT NOT NULL
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
CREATE TABLE skills (
|
|
180
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
181
|
+
slug TEXT NOT NULL UNIQUE,
|
|
182
|
+
description TEXT NOT NULL,
|
|
183
|
+
artifact_id INTEGER,
|
|
184
|
+
remote_status TEXT NOT NULL,
|
|
185
|
+
is_builtin INTEGER NOT NULL DEFAULT 0,
|
|
186
|
+
created_at TEXT NOT NULL,
|
|
187
|
+
updated_at TEXT NOT NULL
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE TABLE agent_skill_bindings (
|
|
191
|
+
agent_id INTEGER NOT NULL,
|
|
192
|
+
skill_id INTEGER NOT NULL,
|
|
193
|
+
PRIMARY KEY (agent_id, skill_id),
|
|
194
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
|
|
195
|
+
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
CREATE TABLE business_systems (
|
|
199
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
200
|
+
provider TEXT NOT NULL,
|
|
201
|
+
application_name TEXT NOT NULL UNIQUE,
|
|
202
|
+
description TEXT NOT NULL,
|
|
203
|
+
ontology_artifact_id INTEGER,
|
|
204
|
+
scheme TEXT NOT NULL,
|
|
205
|
+
host TEXT NOT NULL,
|
|
206
|
+
port INTEGER NOT NULL,
|
|
207
|
+
status TEXT NOT NULL,
|
|
208
|
+
last_connectivity_test_status TEXT NOT NULL,
|
|
209
|
+
last_connectivity_test_result_json TEXT NOT NULL,
|
|
210
|
+
is_builtin INTEGER NOT NULL DEFAULT 0,
|
|
211
|
+
created_at TEXT NOT NULL,
|
|
212
|
+
updated_at TEXT NOT NULL
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
`);
|
|
216
|
+
|
|
217
|
+
for (const tableName of [
|
|
218
|
+
"usage_refresh_state",
|
|
219
|
+
"agent_sync_state",
|
|
220
|
+
"skill_sync_state",
|
|
221
|
+
"template_sync_state",
|
|
222
|
+
"system_sync_state"
|
|
223
|
+
]) {
|
|
224
|
+
db.prepare(`
|
|
225
|
+
INSERT INTO ${tableName} (
|
|
226
|
+
id, status, trigger_source, started_at, finished_at, last_success_at,
|
|
227
|
+
error_message, summary_json, created_at, updated_at
|
|
228
|
+
) VALUES (1, 'idle', NULL, NULL, NULL, NULL, NULL, '{}', ?, ?)
|
|
229
|
+
`).run("2026-05-26T00:00:00.000Z", "2026-05-26T00:00:00.000Z");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return db;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
it("syncs remote agents into sqlite and preserves local-only fields", async () => {
|
|
236
|
+
const db = createDb();
|
|
237
|
+
insertAgent(db, {
|
|
238
|
+
slug: "alpha",
|
|
239
|
+
agentName: "Alpha Agent",
|
|
240
|
+
description: "keep-me",
|
|
241
|
+
usageSnapshotJson: JSON.stringify({
|
|
242
|
+
usage: {
|
|
243
|
+
daily: 1,
|
|
244
|
+
weekly: 2,
|
|
245
|
+
monthly: 3,
|
|
246
|
+
total: 4
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
});
|
|
250
|
+
insertAgent(db, {
|
|
251
|
+
slug: "stale-agent",
|
|
252
|
+
agentName: "Stale Agent"
|
|
253
|
+
});
|
|
254
|
+
const staleAgentId = db.prepare("SELECT id FROM agents WHERE slug = ?").get("stale-agent").id;
|
|
255
|
+
const userResult = db.prepare(`
|
|
256
|
+
INSERT INTO aios_users (
|
|
257
|
+
username, created_at, updated_at
|
|
258
|
+
) VALUES (?, ?, ?)
|
|
259
|
+
`).run(
|
|
260
|
+
"user-a",
|
|
261
|
+
TEST_NOW,
|
|
262
|
+
TEST_NOW
|
|
263
|
+
);
|
|
264
|
+
db.prepare(`
|
|
265
|
+
INSERT INTO agent_permissions (agent_id, aios_user_id)
|
|
266
|
+
VALUES (?, ?)
|
|
267
|
+
`).run(staleAgentId, userResult.lastInsertRowid);
|
|
268
|
+
db.prepare(`
|
|
269
|
+
INSERT INTO external_sessions (
|
|
270
|
+
session_id, aios_user_id, agent_id, created_at, updated_at
|
|
271
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
272
|
+
`).run(
|
|
273
|
+
"session-stale",
|
|
274
|
+
userResult.lastInsertRowid,
|
|
275
|
+
staleAgentId,
|
|
276
|
+
"2026-05-26T00:00:00.000Z",
|
|
277
|
+
"2026-05-26T00:00:00.000Z"
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const service = new CatalogSyncService({
|
|
281
|
+
db,
|
|
282
|
+
rpcClient: {
|
|
283
|
+
isConfigured() {
|
|
284
|
+
return true;
|
|
285
|
+
},
|
|
286
|
+
async call(action, params, timeoutMs) {
|
|
287
|
+
if (action === "agent.list") {
|
|
288
|
+
return {
|
|
289
|
+
items: [{
|
|
290
|
+
agentId: "alpha",
|
|
291
|
+
name: "Alpha Remote",
|
|
292
|
+
status: "active"
|
|
293
|
+
}]
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
env: {
|
|
301
|
+
managementTimeoutMs: 120000
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const status = await service.syncAgentsTask({ trigger: "manual" });
|
|
306
|
+
|
|
307
|
+
expect(status.status).toBe("success");
|
|
308
|
+
expect(status.summary).toEqual({
|
|
309
|
+
agents: 1
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const agent = db.prepare("SELECT * FROM agents WHERE slug = ?").get("alpha");
|
|
313
|
+
expect(agent.description).toBe("keep-me");
|
|
314
|
+
expect(agent.agent_name).toBe("Alpha Remote");
|
|
315
|
+
expect(agent.status).toBe("normal");
|
|
316
|
+
expect(JSON.parse(agent.usage_snapshot_json).usage).toEqual({
|
|
317
|
+
daily: 1
|
|
318
|
+
});
|
|
319
|
+
expect(db.prepare("SELECT * FROM agents WHERE slug = ?").get("stale-agent")).toBeUndefined();
|
|
320
|
+
expect(db.prepare(`
|
|
321
|
+
SELECT COUNT(*) AS count
|
|
322
|
+
FROM agent_permissions
|
|
323
|
+
WHERE agent_id = ?
|
|
324
|
+
`).get(staleAgentId).count).toBe(0);
|
|
325
|
+
expect(db.prepare("SELECT COUNT(*) AS count FROM external_sessions WHERE agent_id = ?").get(staleAgentId).count).toBe(0);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("syncs templates, skills and systems into their own state tables", async () => {
|
|
329
|
+
const db = createDb();
|
|
330
|
+
const missingSkillResult = db.prepare(`
|
|
331
|
+
INSERT INTO skills (
|
|
332
|
+
slug, description, artifact_id, remote_status, created_at, updated_at
|
|
333
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
334
|
+
`).run(
|
|
335
|
+
"missing-global",
|
|
336
|
+
"",
|
|
337
|
+
null,
|
|
338
|
+
"installed",
|
|
339
|
+
"2026-05-26T00:00:00.000Z",
|
|
340
|
+
"2026-05-26T00:00:00.000Z"
|
|
341
|
+
);
|
|
342
|
+
insertAgent(db, {
|
|
343
|
+
slug: "skill-owner",
|
|
344
|
+
agentName: "Skill Owner"
|
|
345
|
+
});
|
|
346
|
+
const skillOwnerId = db.prepare("SELECT id FROM agents WHERE slug = ?").get("skill-owner").id;
|
|
347
|
+
db.prepare(`
|
|
348
|
+
INSERT INTO agent_skill_bindings (agent_id, skill_id)
|
|
349
|
+
VALUES (?, ?)
|
|
350
|
+
`).run(skillOwnerId, missingSkillResult.lastInsertRowid);
|
|
351
|
+
const staleTemplateArtifact = db.prepare(`
|
|
352
|
+
INSERT INTO artifacts (
|
|
353
|
+
kind, bucket, object_key, original_name, mime_type, byte_size, created_by, created_at
|
|
354
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
355
|
+
`).run(
|
|
356
|
+
"template",
|
|
357
|
+
"",
|
|
358
|
+
"",
|
|
359
|
+
"stale-template.remote",
|
|
360
|
+
"application/octet-stream",
|
|
361
|
+
0,
|
|
362
|
+
null,
|
|
363
|
+
"2026-05-26T00:00:00.000Z"
|
|
364
|
+
);
|
|
365
|
+
db.prepare(`
|
|
366
|
+
INSERT INTO agent_templates (
|
|
367
|
+
template_name, description, artifact_id, remote_status, remote_result_json,
|
|
368
|
+
created_at, updated_at
|
|
369
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
370
|
+
`).run(
|
|
371
|
+
"stale-template",
|
|
372
|
+
"",
|
|
373
|
+
staleTemplateArtifact.lastInsertRowid,
|
|
374
|
+
"ready",
|
|
375
|
+
"{}",
|
|
376
|
+
"2026-05-26T00:00:00.000Z",
|
|
377
|
+
"2026-05-26T00:00:00.000Z"
|
|
378
|
+
);
|
|
379
|
+
db.prepare(`
|
|
380
|
+
INSERT INTO business_systems (
|
|
381
|
+
provider, application_name, description, ontology_artifact_id,
|
|
382
|
+
scheme, host, port, status, last_connectivity_test_status, last_connectivity_test_result_json,
|
|
383
|
+
created_at, updated_at
|
|
384
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
385
|
+
`).run(
|
|
386
|
+
"hzg",
|
|
387
|
+
"stale-local",
|
|
388
|
+
"delete-local",
|
|
389
|
+
null,
|
|
390
|
+
"http",
|
|
391
|
+
"stale.example.com",
|
|
392
|
+
8081,
|
|
393
|
+
"active",
|
|
394
|
+
"unknown",
|
|
395
|
+
"{}",
|
|
396
|
+
"2026-05-26T00:00:00.000Z",
|
|
397
|
+
"2026-05-26T00:00:00.000Z"
|
|
398
|
+
);
|
|
399
|
+
db.prepare(`
|
|
400
|
+
INSERT INTO business_systems (
|
|
401
|
+
provider, application_name, description, ontology_artifact_id,
|
|
402
|
+
scheme, host, port, status, last_connectivity_test_status, last_connectivity_test_result_json,
|
|
403
|
+
created_at, updated_at
|
|
404
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
405
|
+
`).run(
|
|
406
|
+
"hzg",
|
|
407
|
+
"crm",
|
|
408
|
+
"keep-local",
|
|
409
|
+
null,
|
|
410
|
+
"http",
|
|
411
|
+
"crm.example.com",
|
|
412
|
+
8080,
|
|
413
|
+
"active",
|
|
414
|
+
"unknown",
|
|
415
|
+
"{}",
|
|
416
|
+
"2026-05-26T00:00:00.000Z",
|
|
417
|
+
"2026-05-26T00:00:00.000Z"
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const service = new CatalogSyncService({
|
|
421
|
+
db,
|
|
422
|
+
rpcClient: {
|
|
423
|
+
isConfigured() {
|
|
424
|
+
return true;
|
|
425
|
+
},
|
|
426
|
+
async call(action) {
|
|
427
|
+
if (action === "agent.template.list") {
|
|
428
|
+
return {
|
|
429
|
+
items: [{
|
|
430
|
+
templateName: "default",
|
|
431
|
+
path: "/var/aios/workspace-templates/default",
|
|
432
|
+
"is-built-in": true
|
|
433
|
+
}]
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
if (action === "skills.global.list") {
|
|
437
|
+
return {
|
|
438
|
+
items: [{
|
|
439
|
+
slug: "global-skill",
|
|
440
|
+
"is-built-in": true,
|
|
441
|
+
origin: {
|
|
442
|
+
slug: "global-skill",
|
|
443
|
+
description: "from kernel"
|
|
444
|
+
}
|
|
445
|
+
}]
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (action === "apps.list") {
|
|
449
|
+
return {
|
|
450
|
+
items: [{
|
|
451
|
+
id: "crm",
|
|
452
|
+
name: "CRM"
|
|
453
|
+
}]
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (action === "ontology.list") {
|
|
457
|
+
return {
|
|
458
|
+
items: [{
|
|
459
|
+
name: "crm",
|
|
460
|
+
"is-built-in": true
|
|
461
|
+
}]
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const [templateStatus, skillStatus, systemStatus] = await Promise.all([
|
|
470
|
+
service.syncTemplatesTask({ trigger: "manual" }),
|
|
471
|
+
service.syncSkillsTask({ trigger: "manual" }),
|
|
472
|
+
service.syncSystemsTask({ trigger: "manual" })
|
|
473
|
+
]);
|
|
474
|
+
|
|
475
|
+
expect(templateStatus.summary).toEqual({ templates: 1 });
|
|
476
|
+
expect(skillStatus.summary).toEqual({ skills: 1 });
|
|
477
|
+
expect(systemStatus.summary).toEqual({ systems: 1 });
|
|
478
|
+
|
|
479
|
+
const template = db.prepare("SELECT * FROM agent_templates WHERE template_name = ?").get("default");
|
|
480
|
+
const skill = db.prepare("SELECT * FROM skills WHERE slug = ?").get("global-skill");
|
|
481
|
+
expect(template.remote_status).toBe("ready");
|
|
482
|
+
expect(template.is_builtin).toBe(1);
|
|
483
|
+
expect(skill.remote_status).toBe("installed");
|
|
484
|
+
expect(skill.is_builtin).toBe(1);
|
|
485
|
+
expect(db.prepare("SELECT * FROM skills WHERE slug = ?").get("missing-global")).toBeUndefined();
|
|
486
|
+
expect(db.prepare("SELECT COUNT(*) AS count FROM agent_skill_bindings WHERE skill_id = ?").get(missingSkillResult.lastInsertRowid).count).toBe(0);
|
|
487
|
+
expect(db.prepare("SELECT * FROM agent_templates WHERE template_name = ?").get("stale-template")).toBeUndefined();
|
|
488
|
+
expect(db.prepare("SELECT * FROM business_systems WHERE application_name = ?").get("crm").is_builtin).toBe(1);
|
|
489
|
+
expect(db.prepare("SELECT * FROM business_systems WHERE application_name = ?").get("stale-local")).toBeUndefined();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("keeps local catalog rows when kernel sync fails", async () => {
|
|
493
|
+
const db = createDb();
|
|
494
|
+
insertAgent(db, {
|
|
495
|
+
slug: "local-agent",
|
|
496
|
+
agentName: "Local Agent"
|
|
497
|
+
});
|
|
498
|
+
const templateArtifact = db.prepare(`
|
|
499
|
+
INSERT INTO artifacts (
|
|
500
|
+
kind, bucket, object_key, original_name, mime_type, byte_size, created_by, created_at
|
|
501
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
502
|
+
`).run(
|
|
503
|
+
"template",
|
|
504
|
+
"",
|
|
505
|
+
"",
|
|
506
|
+
"local-template.remote",
|
|
507
|
+
"application/octet-stream",
|
|
508
|
+
0,
|
|
509
|
+
null,
|
|
510
|
+
"2026-05-26T00:00:00.000Z"
|
|
511
|
+
);
|
|
512
|
+
db.prepare(`
|
|
513
|
+
INSERT INTO agent_templates (
|
|
514
|
+
template_name, description, artifact_id, remote_status, remote_result_json,
|
|
515
|
+
created_at, updated_at
|
|
516
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
517
|
+
`).run(
|
|
518
|
+
"local-template",
|
|
519
|
+
"",
|
|
520
|
+
templateArtifact.lastInsertRowid,
|
|
521
|
+
"ready",
|
|
522
|
+
"{}",
|
|
523
|
+
"2026-05-26T00:00:00.000Z",
|
|
524
|
+
"2026-05-26T00:00:00.000Z"
|
|
525
|
+
);
|
|
526
|
+
db.prepare(`
|
|
527
|
+
INSERT INTO skills (
|
|
528
|
+
slug, description, artifact_id, remote_status, created_at, updated_at
|
|
529
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
530
|
+
`).run(
|
|
531
|
+
"local-skill",
|
|
532
|
+
"",
|
|
533
|
+
null,
|
|
534
|
+
"installed",
|
|
535
|
+
"2026-05-26T00:00:00.000Z",
|
|
536
|
+
"2026-05-26T00:00:00.000Z"
|
|
537
|
+
);
|
|
538
|
+
db.prepare(`
|
|
539
|
+
INSERT INTO business_systems (
|
|
540
|
+
provider, application_name, description, ontology_artifact_id,
|
|
541
|
+
scheme, host, port, status, last_connectivity_test_status, last_connectivity_test_result_json,
|
|
542
|
+
created_at, updated_at
|
|
543
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
544
|
+
`).run(
|
|
545
|
+
"hzg",
|
|
546
|
+
"local-system",
|
|
547
|
+
"",
|
|
548
|
+
null,
|
|
549
|
+
"http",
|
|
550
|
+
"local.example.com",
|
|
551
|
+
8080,
|
|
552
|
+
"active",
|
|
553
|
+
"unknown",
|
|
554
|
+
"{}",
|
|
555
|
+
"2026-05-26T00:00:00.000Z",
|
|
556
|
+
"2026-05-26T00:00:00.000Z"
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const service = new CatalogSyncService({
|
|
560
|
+
db,
|
|
561
|
+
rpcClient: {
|
|
562
|
+
isConfigured() {
|
|
563
|
+
return true;
|
|
564
|
+
},
|
|
565
|
+
async call() {
|
|
566
|
+
throw new Error("kernel unavailable");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
const agentStatus = await service.syncAgentsTask({ trigger: "manual" });
|
|
572
|
+
const templateStatus = await service.syncTemplatesTask({ trigger: "manual" });
|
|
573
|
+
const skillStatus = await service.syncSkillsTask({ trigger: "manual" });
|
|
574
|
+
const systemStatus = await service.syncSystemsTask({ trigger: "manual" });
|
|
575
|
+
|
|
576
|
+
expect(agentStatus.status).toBe("failed");
|
|
577
|
+
expect(templateStatus.status).toBe("failed");
|
|
578
|
+
expect(skillStatus.status).toBe("failed");
|
|
579
|
+
expect(systemStatus.status).toBe("failed");
|
|
580
|
+
expect(db.prepare("SELECT * FROM agents WHERE slug = ?").get("local-agent")).toBeDefined();
|
|
581
|
+
expect(db.prepare("SELECT * FROM agent_templates WHERE template_name = ?").get("local-template")).toBeDefined();
|
|
582
|
+
expect(db.prepare("SELECT * FROM skills WHERE slug = ?").get("local-skill")).toBeDefined();
|
|
583
|
+
expect(db.prepare("SELECT * FROM business_systems WHERE application_name = ?").get("local-system")).toBeDefined();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it("refreshes agent usage snapshots with kernel stock values", async () => {
|
|
587
|
+
const db = createDb();
|
|
588
|
+
insertAgent(db, {
|
|
589
|
+
slug: "alpha",
|
|
590
|
+
agentName: "Alpha",
|
|
591
|
+
status: "active",
|
|
592
|
+
usageSnapshotJson: JSON.stringify({
|
|
593
|
+
usage: {
|
|
594
|
+
daily: 1,
|
|
595
|
+
weekly: 2,
|
|
596
|
+
monthly: 3,
|
|
597
|
+
total: 10
|
|
598
|
+
}
|
|
599
|
+
})
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const service = new CatalogSyncService({
|
|
603
|
+
db,
|
|
604
|
+
auditLogService: {
|
|
605
|
+
write() {}
|
|
606
|
+
},
|
|
607
|
+
rpcClient: {
|
|
608
|
+
isConfigured() {
|
|
609
|
+
return true;
|
|
610
|
+
},
|
|
611
|
+
async call(action) {
|
|
612
|
+
if (action !== "agent.usage") {
|
|
613
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return {
|
|
617
|
+
usage: {
|
|
618
|
+
daily: 4
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const result = await service.refreshAgentUsage({ trigger: "scheduled" });
|
|
626
|
+
|
|
627
|
+
expect(result.status).toBe("success");
|
|
628
|
+
expect(result.trigger_source).toBe("scheduled");
|
|
629
|
+
expect(result.summary.refreshed_agents).toBe(1);
|
|
630
|
+
expect(result.summary.captured_at).toContain("T");
|
|
631
|
+
|
|
632
|
+
const agent = db.prepare("SELECT * FROM agents WHERE slug = ?").get("alpha");
|
|
633
|
+
expect(JSON.parse(agent.usage_snapshot_json).usage).toEqual({
|
|
634
|
+
daily: 4
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it("overwrites agent usage snapshots with latest kernel stock values", async () => {
|
|
639
|
+
const db = createDb();
|
|
640
|
+
insertAgent(db, {
|
|
641
|
+
slug: "alpha",
|
|
642
|
+
agentName: "Alpha"
|
|
643
|
+
});
|
|
644
|
+
const usageValues = [4, 6];
|
|
645
|
+
const service = new CatalogSyncService({
|
|
646
|
+
db,
|
|
647
|
+
rpcClient: {
|
|
648
|
+
isConfigured() {
|
|
649
|
+
return true;
|
|
650
|
+
},
|
|
651
|
+
async call(action) {
|
|
652
|
+
if (action !== "agent.usage") {
|
|
653
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
usage: {
|
|
658
|
+
daily: usageValues.shift()
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
await service.refreshAgentUsage({ trigger: "scheduled" });
|
|
666
|
+
await service.refreshAgentUsage({ trigger: "scheduled" });
|
|
667
|
+
|
|
668
|
+
const agent = db.prepare("SELECT * FROM agents WHERE slug = ?").get("alpha");
|
|
669
|
+
expect(JSON.parse(agent.usage_snapshot_json).usage).toEqual({
|
|
670
|
+
daily: 6
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("does not write audit logs during agent usage refresh", async () => {
|
|
675
|
+
const db = createDb();
|
|
676
|
+
insertAgent(db, {
|
|
677
|
+
slug: "alpha",
|
|
678
|
+
agentName: "Alpha",
|
|
679
|
+
status: "active"
|
|
680
|
+
});
|
|
681
|
+
const auditWrites = [];
|
|
682
|
+
|
|
683
|
+
const service = new CatalogSyncService({
|
|
684
|
+
db,
|
|
685
|
+
auditLogService: {
|
|
686
|
+
write(entry) {
|
|
687
|
+
auditWrites.push(entry);
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
rpcClient: {
|
|
691
|
+
isConfigured() {
|
|
692
|
+
return true;
|
|
693
|
+
},
|
|
694
|
+
async call(action) {
|
|
695
|
+
if (action !== "agent.usage") {
|
|
696
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
usage: {
|
|
701
|
+
daily: 4
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const result = await service.refreshAgentUsage({ trigger: "scheduled" });
|
|
709
|
+
|
|
710
|
+
expect(result.status).toBe("success");
|
|
711
|
+
expect(auditWrites).toEqual([]);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it("moves non-disabled agents to overlimit when refreshed usage reaches quota", async () => {
|
|
715
|
+
const db = createDb();
|
|
716
|
+
insertAgent(db, {
|
|
717
|
+
slug: "alpha",
|
|
718
|
+
agentName: "Alpha",
|
|
719
|
+
dailyLimit: 10
|
|
720
|
+
});
|
|
721
|
+
insertAgent(db, {
|
|
722
|
+
slug: "beta",
|
|
723
|
+
agentName: "Beta",
|
|
724
|
+
status: "disabled",
|
|
725
|
+
dailyLimit: 10
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
const service = new CatalogSyncService({
|
|
729
|
+
db,
|
|
730
|
+
auditLogService: {
|
|
731
|
+
write() {}
|
|
732
|
+
},
|
|
733
|
+
rpcClient: {
|
|
734
|
+
isConfigured() {
|
|
735
|
+
return true;
|
|
736
|
+
},
|
|
737
|
+
async call(action, params) {
|
|
738
|
+
if (action !== "agent.usage") {
|
|
739
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return {
|
|
743
|
+
usage: {
|
|
744
|
+
daily: params.agentId === "alpha" ? 12 : 20,
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
const result = await service.refreshAgentUsage({ trigger: "scheduled" });
|
|
752
|
+
|
|
753
|
+
expect(result.status).toBe("success");
|
|
754
|
+
expect(db.prepare("SELECT status FROM agents WHERE slug = ?").get("alpha").status).toBe("overlimit");
|
|
755
|
+
expect(db.prepare("SELECT status FROM agents WHERE slug = ?").get("beta").status).toBe("disabled");
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it("serializes concurrent sqlite write transactions across sync services", async () => {
|
|
759
|
+
const db = createDb();
|
|
760
|
+
insertAgent(db, {
|
|
761
|
+
slug: "alpha",
|
|
762
|
+
agentName: "Alpha",
|
|
763
|
+
usageSnapshotJson: JSON.stringify({
|
|
764
|
+
usage: {
|
|
765
|
+
daily: 1,
|
|
766
|
+
weekly: 2,
|
|
767
|
+
monthly: 3,
|
|
768
|
+
total: 10
|
|
769
|
+
}
|
|
770
|
+
})
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
let releaseUsageCall;
|
|
774
|
+
const usageBlocked = new Promise((resolve) => {
|
|
775
|
+
releaseUsageCall = resolve;
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
const sharedRpcClient = {
|
|
779
|
+
isConfigured() {
|
|
780
|
+
return true;
|
|
781
|
+
},
|
|
782
|
+
async call(action) {
|
|
783
|
+
if (action === "agent.usage") {
|
|
784
|
+
await usageBlocked;
|
|
785
|
+
return {
|
|
786
|
+
usage: {
|
|
787
|
+
daily: 4
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (action === "agent.list") {
|
|
793
|
+
return {
|
|
794
|
+
items: [{
|
|
795
|
+
agentId: "alpha",
|
|
796
|
+
name: "Alpha Remote",
|
|
797
|
+
status: "active"
|
|
798
|
+
}]
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
const catalogSyncService = new CatalogSyncService({
|
|
807
|
+
db,
|
|
808
|
+
rpcClient: sharedRpcClient,
|
|
809
|
+
auditLogService: {
|
|
810
|
+
write() {}
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
const agentStatusSyncService = new AgentStatusSyncService({
|
|
814
|
+
db,
|
|
815
|
+
rpcClient: sharedRpcClient,
|
|
816
|
+
auditLogService: {
|
|
817
|
+
write() {}
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
const usagePromise = catalogSyncService.refreshAgentUsage({ trigger: "scheduled" });
|
|
822
|
+
const statusPromise = agentStatusSyncService.refreshStatuses({ trigger: "scheduled" });
|
|
823
|
+
|
|
824
|
+
releaseUsageCall();
|
|
825
|
+
|
|
826
|
+
const [usageResult, statusResult] = await Promise.all([usagePromise, statusPromise]);
|
|
827
|
+
|
|
828
|
+
expect(usageResult.status).toBe("success");
|
|
829
|
+
expect(statusResult.status).toBe("success");
|
|
830
|
+
expect(statusResult.updated_agents).toBe(1);
|
|
831
|
+
|
|
832
|
+
const agent = db.prepare("SELECT * FROM agents WHERE slug = ?").get("alpha");
|
|
833
|
+
expect(agent.status).toBe("normal");
|
|
834
|
+
expect(agent.agent_name).toBe("Alpha Remote");
|
|
835
|
+
expect(JSON.parse(agent.usage_snapshot_json).usage.daily).toBe(4);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it("records skipped status when rpc is not configured", async () => {
|
|
839
|
+
const db = createDb();
|
|
840
|
+
const service = new CatalogSyncService({
|
|
841
|
+
db,
|
|
842
|
+
rpcClient: {
|
|
843
|
+
isConfigured() {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
const status = await service.syncAgentsTask({ trigger: "startup" });
|
|
850
|
+
|
|
851
|
+
expect(status.status).toBe("skipped");
|
|
852
|
+
expect(status.trigger_source).toBe("startup");
|
|
853
|
+
expect(status.error_message).toContain("not configured");
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
it("fails usage refresh when agent.usage returns a non-usage payload", async () => {
|
|
857
|
+
const db = createDb();
|
|
858
|
+
insertAgent(db, {
|
|
859
|
+
slug: "alpha",
|
|
860
|
+
agentName: "Alpha",
|
|
861
|
+
status: "active"
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
const service = new CatalogSyncService({
|
|
865
|
+
db,
|
|
866
|
+
rpcClient: {
|
|
867
|
+
isConfigured() {
|
|
868
|
+
return true;
|
|
869
|
+
},
|
|
870
|
+
async call(action) {
|
|
871
|
+
if (action !== "agent.usage") {
|
|
872
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return {
|
|
876
|
+
configPath: "/var/aios/.openclaw/openclaw.json",
|
|
877
|
+
defaultModel: "corp-openai/gpt-5.4"
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
const result = await service.refreshAgentUsage({ trigger: "scheduled" });
|
|
884
|
+
|
|
885
|
+
expect(result.status).toBe("failed");
|
|
886
|
+
expect(result.error_message).toContain("usage fields");
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it("does not sample agent usage during agent sync", async () => {
|
|
890
|
+
const db = createDb();
|
|
891
|
+
const calls = [];
|
|
892
|
+
|
|
893
|
+
const service = new CatalogSyncService({
|
|
894
|
+
db,
|
|
895
|
+
rpcClient: {
|
|
896
|
+
isConfigured() {
|
|
897
|
+
return true;
|
|
898
|
+
},
|
|
899
|
+
async call(action) {
|
|
900
|
+
calls.push(action);
|
|
901
|
+
if (action === "agent.list") {
|
|
902
|
+
return {
|
|
903
|
+
items: [
|
|
904
|
+
{ agentId: "alpha", name: "Alpha", status: "active" },
|
|
905
|
+
{ agentId: "beta", name: "Beta", status: "active" },
|
|
906
|
+
{ agentId: "gamma", name: "Gamma", status: "active" }
|
|
907
|
+
]
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
throw new Error(`Unexpected action: ${action}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
const result = await service.syncAgentsTask({ trigger: "manual" });
|
|
917
|
+
expect(result.status).toBe("success");
|
|
918
|
+
expect(result.summary).toEqual({ agents: 3 });
|
|
919
|
+
expect(calls).toEqual(["agent.list"]);
|
|
920
|
+
});
|