aims-workstation 0.1.11 → 0.1.13
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.example
CHANGED
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@earendil-works/pi-ai": "^1.2.0"
|
|
14
14
|
},
|
|
15
15
|
"packageManager": "pnpm@11.21.0+sha512.521705bce689924eac72f5a3587122f362689ef6571e55ba80076fd637c11132ecffada26fad4ea79c485bfddbfd3d5a2a5b05805a77e893de71ec8a6cca3bb1",
|
|
16
|
-
"version": "0.1.
|
|
16
|
+
"version": "0.1.13",
|
|
17
17
|
"description": "AIMS 工作站:本地智能体工作站——ARM 助手本地化(下载/离线/升级)、工作台对话式定制、多工作区隔离、分级自主与审计",
|
|
18
18
|
"bin": {
|
|
19
19
|
"aims-workstation": "bin/aims-workstation.js"
|
package/server.js
CHANGED
|
@@ -236,6 +236,19 @@ async function armRest(pathname, init = {}, timeout_ms = 20000) {
|
|
|
236
236
|
async function armCreateKnowledge({ name, description, content, tags }) {
|
|
237
237
|
return armRest('/api/v1/knowledges', { method: 'POST', body: JSON.stringify({ name, description, content, tags }) }, 25000);
|
|
238
238
|
}
|
|
239
|
+
|
|
240
|
+
// 经验与助手关联:沉淀出的知识绑定到当前助手(助手检索范围=已绑定知识;缺这步=沉淀了但检索不到)。
|
|
241
|
+
// 绑定失败不阻断沉淀(老版 ARM 无端点时仅审计留痕)。
|
|
242
|
+
async function bindExperienceToAgent(agentId, knowledgeId) {
|
|
243
|
+
try {
|
|
244
|
+
await armRest(`/api/v1/agents/${agentId}/knowledges`, { method: 'POST', body: JSON.stringify({ knowledgeId, kind: 'experience' }) });
|
|
245
|
+
audit({ actor: 'experience-reflow', target: 'ARM', action: 'bind', agentId, knowledge_id: knowledgeId });
|
|
246
|
+
return true;
|
|
247
|
+
} catch (e) {
|
|
248
|
+
audit({ actor: 'experience-reflow', target: 'ARM', action: 'bind-failed', agentId, knowledge_id: knowledgeId, error: String(e.message || e).slice(0, 90) });
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
239
252
|
const vVer = (v) => (String(v || '').startsWith('v') ? String(v) : `v${v || '0.0.0'}`); // REST 无 v 前缀,统一内部表示
|
|
240
253
|
|
|
241
254
|
const agentCache = new Map();
|
|
@@ -399,7 +412,7 @@ async function flushExperienceOutbox() {
|
|
|
399
412
|
const lines = fs.readFileSync(OUTBOX_FILE, 'utf8').split('\n').filter(Boolean);
|
|
400
413
|
const remain = []; let ok = 0;
|
|
401
414
|
for (const line of lines) {
|
|
402
|
-
try { const it = JSON.parse(line); await armCreateKnowledge(it.args); ok++; }
|
|
415
|
+
try { const it = JSON.parse(line); const r = await armCreateKnowledge(it.args); if (it.agentId && r?.id) await bindExperienceToAgent(it.agentId, r.id); ok++; }
|
|
403
416
|
catch { remain.push(line); }
|
|
404
417
|
}
|
|
405
418
|
fs.writeFileSync(OUTBOX_FILE, remain.length ? remain.join('\n') + '\n' : '');
|
|
@@ -618,7 +631,7 @@ const TOOLS = {
|
|
|
618
631
|
desc: '把本次处置经验沉淀进企业知识库(ARM 经验库;离线时存本地待回流队列)',
|
|
619
632
|
run: async ({ title, markdown }, agentId) => {
|
|
620
633
|
const arm = agentProfileNow(agentId);
|
|
621
|
-
const content = ['---', 'kind: experience', 'source: aims-platform-mvp', `agent: ${arm.name || agentId}@${arm.version || ''}`, 'scenario: 立库A区智能运维@mvp', '---', '', markdown].join('\n');
|
|
634
|
+
const content = ['---', 'kind: experience', 'source: aims-platform-mvp', `agent_id: ${agentId}`, `agent: ${arm.name || agentId}@${arm.version || ''}`, 'scenario: 立库A区智能运维@mvp', '---', '', markdown].join('\n');
|
|
622
635
|
const args = { name: title, description: `MVP回流:${title}`, content, tags: ['MVP回流'] };
|
|
623
636
|
let r;
|
|
624
637
|
try { r = await armCreateKnowledge(args); }
|
|
@@ -627,9 +640,10 @@ const TOOLS = {
|
|
|
627
640
|
audit({ actor: 'experience-reflow', target: 'outbox', title });
|
|
628
641
|
return `ARM 不可达(${String(e.message).slice(0, 60)}),经验已存本地待回流队列;恢复连接后在助手大厅点「检查更新」将自动回流。`;
|
|
629
642
|
}
|
|
643
|
+
const bound = await bindExperienceToAgent(agentId, r.id);
|
|
630
644
|
metric({ kind: 'experience', agentId, title, knowledge_id: r.id });
|
|
631
|
-
audit({ actor: 'experience-reflow', target: 'ARM', title });
|
|
632
|
-
return `经验已沉淀到 ARM 知识库:${r.name}(id: ${r.id}${r.shareUrl ? `,${r.shareUrl}` : ''}
|
|
645
|
+
audit({ actor: 'experience-reflow', target: 'ARM', title, bound });
|
|
646
|
+
return `经验已沉淀到 ARM 知识库:${r.name}(id: ${r.id}${r.shareUrl ? `,${r.shareUrl}` : ''})${bound ? ',并已绑定到当前助手(检索即得)' : ';绑定助手未成功(已在审计留痕),可到 ARM 手动关联'}`;
|
|
633
647
|
},
|
|
634
648
|
},
|
|
635
649
|
};
|
|
@@ -674,7 +688,23 @@ function loadWorkbench(agentId) {
|
|
|
674
688
|
const p = path.join(WB_DIR, `${agentId}.json`);
|
|
675
689
|
return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : null;
|
|
676
690
|
}
|
|
691
|
+
// ── 多视图契约(DSL v2):views 为场景视图;panels 始终同步为各视图面板的扁平拼接(存量读取点零改动)──
|
|
692
|
+
function viewsOfSrv(dsl) {
|
|
693
|
+
if (Array.isArray(dsl?.views) && dsl.views.length) return dsl.views
|
|
694
|
+
return [{ id: 'main', name: '主视图', panels: Array.isArray(dsl?.panels) ? dsl.panels : [] }]
|
|
695
|
+
}
|
|
696
|
+
function normDsl(dsl) {
|
|
697
|
+
if (!dsl || typeof dsl !== 'object') return dsl
|
|
698
|
+
dsl.views = viewsOfSrv(dsl).map(v => ({
|
|
699
|
+
id: v.id || `view_${Math.random().toString(36).slice(2, 6)}`,
|
|
700
|
+
name: v.name || '视图', icon: v.icon,
|
|
701
|
+
panels: Array.isArray(v.panels) ? v.panels : [],
|
|
702
|
+
}))
|
|
703
|
+
dsl.panels = dsl.views.flatMap(v => v.panels)
|
|
704
|
+
return dsl
|
|
705
|
+
}
|
|
677
706
|
function saveWorkbench(agentId, dsl) {
|
|
707
|
+
dsl = normDsl(dsl);
|
|
678
708
|
fs.writeFileSync(path.join(WB_DIR, `${agentId}.json`), JSON.stringify(dsl, null, 2));
|
|
679
709
|
const vp = path.join(WB_VER_DIR, `${agentId}.json`);
|
|
680
710
|
const vers = fs.existsSync(vp) ? JSON.parse(fs.readFileSync(vp, 'utf8')) : [];
|
|
@@ -911,19 +941,22 @@ function loadDraft(agentId) {
|
|
|
911
941
|
return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : null;
|
|
912
942
|
}
|
|
913
943
|
function saveDraft(agentId, dsl) {
|
|
914
|
-
|
|
915
|
-
const
|
|
944
|
+
dsl = normDsl(dsl); // v1/v2 统一:补 views、同步扁平 panels
|
|
945
|
+
const prevFlat = viewsOfSrv(loadDraft(agentId) || loadWorkbench(agentId) || {}).flatMap(v => v.panels);
|
|
916
946
|
// 防护性合并:同 id 同类型的面板,Agent 遗漏的字段(如 action_group.actions)从上一版补全,防止全量重写丢配置
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
947
|
+
for (const v of dsl.views) {
|
|
948
|
+
v.panels = v.panels.map(p => {
|
|
949
|
+
const old = prevFlat.find(x => x.id === p.id && x.type === p.type);
|
|
950
|
+
return old ? { ...old, ...p } : p;
|
|
951
|
+
});
|
|
952
|
+
for (const p of v.panels) {
|
|
953
|
+
if ((p.type === 'custom' || p.type === 'custom_panel') && !panelCodeOf(p)) throw new Error(`面板 ${p.id || p.title} 引用的代码/插件缺失`);
|
|
954
|
+
}
|
|
923
955
|
}
|
|
956
|
+
dsl.panels = dsl.views.flatMap(v => v.panels); // 合并后再次同步
|
|
924
957
|
dsl.owner_agent = agentId;
|
|
925
958
|
fs.writeFileSync(draftPath(agentId), JSON.stringify(dsl, null, 2));
|
|
926
|
-
audit({ actor: 'dev-agent', agentId, action: 'draft', panels: dsl.panels.length });
|
|
959
|
+
audit({ actor: 'dev-agent', agentId, action: 'draft', panels: dsl.panels.length, views: dsl.views.length });
|
|
927
960
|
}
|
|
928
961
|
|
|
929
962
|
// ── Skill 装载(Agent Skills 规范:SKILL.md frontmatter 注入描述,正文按需读取)──
|
|
@@ -1311,7 +1344,7 @@ const DEV_TOOLS = {
|
|
|
1311
1344
|
},
|
|
1312
1345
|
update_workbench: {
|
|
1313
1346
|
desc: '把完整的工作台 DSL 写入草稿(不会直接生效,需用户在编辑器审阅后发布)。基于当前 DSL 增量修改后整体提交',
|
|
1314
|
-
schema: { dsl: { type: 'object', description: "完整工作台 DSL {workbench,version,panels:[
|
|
1347
|
+
schema: { dsl: { type: 'object', description: "完整工作台 DSL {workbench,version,views:[{id,name,panels:[…]}],panels:[…]}。panels 元素含 id/type/title;自定义面板用 type=custom_panel 且 panel=插件id,或 type=custom 且 code=面板代码。多场景用 views 分视图(新场景新建视图);panels 扁平字段平台会自动同步,可只维护 views" } },
|
|
1315
1348
|
required: ['dsl'],
|
|
1316
1349
|
run: async ({ dsl }) => {
|
|
1317
1350
|
saveDraft(devCtx.agentId, dsl);
|
|
@@ -1384,13 +1417,14 @@ function devSystemPrompt() {
|
|
|
1384
1417
|
' </div>`',
|
|
1385
1418
|
'}',
|
|
1386
1419
|
'',
|
|
1387
|
-
'【当前工作台】', JSON.stringify(live ? { workbench: live.workbench,
|
|
1420
|
+
'【当前工作台】', JSON.stringify(live ? { workbench: live.workbench, views: viewsOfSrv(live).map(v => ({ id: v.id, name: v.name, panels: v.panels.map(p => ({ id: p.id, type: p.type, title: p.title, panel: p.panel })) })) } : null),
|
|
1388
1421
|
'【插件库】', JSON.stringify(plugins.map(p => ({ id: p.id, name: p.name, version: p.version }))),
|
|
1389
1422
|
'',
|
|
1390
1423
|
'【修改已有插件】先 read_panel_plugin 看当前代码 → 需要持久数据就 create_collection → write_panel_plugin 同 id 覆盖(版本自增)→ 需要时 update_workbench 调整布局。同 id 写入即升级,工作台引用不受影响。',
|
|
1391
1424
|
'【自探索工作流(必须遵守)】理解需求 → 探索 → 规划 → 实现 → 提交草稿:',
|
|
1392
1425
|
'1. 探索:新需求涉及助手既有能力时,必须先 exec 读它的 AGENT.md(按名 grep 定位),必要时读其 skills/脚本与验证相关接口(curl 实测返回结构)。能推送通常就能搜索——从脚本/配置里找到真实的服务地址与 API。',
|
|
1393
1426
|
'2. 规划:把探索结论映射为面板方案(数据从哪来、经什么能力取、展示成什么),方案要说清"复用了助手的哪个既有能力"。',
|
|
1427
|
+
'· 工作台支持多视图(DSL views:[{id,name,panels}],不同高频场景各建一块屏,如「日常监控/专项巡检/报表」):需求涉及新场景时优先新建视图,而不是往现有视图堆面板;提交的 DSL 带 views 即可,扁平 panels 由平台自动同步',
|
|
1394
1428
|
'3. 实现:面板运行在浏览器,内网接口跨域受限——一律经平台代理取数:fetch("/api/proxy?url=" + encodeURIComponent(接口地址)),返回 {status, body}。慢接口(>8s 的重查询)追加 &timeout_ms=30000(上限 60s)与 &max_kb=512(默认 128KB 会截断大结果);但优先改用服务端过滤/分页参数(q=/limit/时间范围)减小单次拉取——不要靠放宽超时硬扛全量拉取。',
|
|
1395
1429
|
'4. 提交:write_panel_plugin + update_workbench 草稿,明确告知用户审阅发布。',
|
|
1396
1430
|
'【工作方式】改已有面板时基于当前 DSL 增量修改;单一职责,先小后大。',
|
|
@@ -2062,7 +2096,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
2062
2096
|
}
|
|
2063
2097
|
if (req.method === 'PUT' && pathname === '/api/workbench') {
|
|
2064
2098
|
const { agent_id, dsl } = await body(req);
|
|
2065
|
-
if (!agent_id || !dsl?.panels) return json(res, 400, { error: 'agent_id 与 dsl
|
|
2099
|
+
if (!agent_id || !(dsl?.panels || dsl?.views?.length)) return json(res, 400, { error: 'agent_id 与 dsl(panels 或 views)必填' });
|
|
2066
2100
|
dsl.version = nextVersion(dsl.version, agent_id);
|
|
2067
2101
|
saveWorkbench(agent_id, dsl);
|
|
2068
2102
|
try { fs.rmSync(draftPath(agent_id), { force: true }); } catch { }
|
|
@@ -2574,11 +2608,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
2574
2608
|
if (req.method === 'POST' && pathname === '/api/experience/commit') {
|
|
2575
2609
|
const { agentId, name, description, content } = await body(req);
|
|
2576
2610
|
const arm = agentProfileNow(agentId);
|
|
2577
|
-
const full = ['---', 'kind: experience', 'source: aims-platform-mvp', `agent: ${arm.name || agentId}@${arm.version || ''}`, 'scenario: 立库A区智能运维@mvp', '---', '', content].join('\n');
|
|
2611
|
+
const full = ['---', 'kind: experience', 'source: aims-platform-mvp', `agent_id: ${agentId}`, `agent: ${arm.name || agentId}@${arm.version || ''}`, 'scenario: 立库A区智能运维@mvp', '---', '', content].join('\n');
|
|
2578
2612
|
const r = await armCreateKnowledge({ name, description: description || `工作台人工审阅沉淀`, content: full, tags: ['MVP回流'] });
|
|
2613
|
+
const bound = await bindExperienceToAgent(agentId, r.id);
|
|
2579
2614
|
metric({ kind: 'experience', agentId, title: name, knowledge_id: r.id });
|
|
2580
|
-
audit({ actor: 'experience-reflow', target: 'ARM', name });
|
|
2581
|
-
return json(res, 200, { result: `已创建 ${r.name}(id: ${r.id})${r.shareUrl ? `\n${r.shareUrl}` : ''}`, knowledge_id: r.id, share_url: r.shareUrl || '' });
|
|
2615
|
+
audit({ actor: 'experience-reflow', target: 'ARM', name, bound });
|
|
2616
|
+
return json(res, 200, { result: `已创建 ${r.name}(id: ${r.id})${r.shareUrl ? `\n${r.shareUrl}` : ''}${bound ? '\n已绑定到当前助手(检索即得)' : '\n绑定助手未成功(已在审计留痕),可到 ARM 手动关联'}`, knowledge_id: r.id, share_url: r.shareUrl || '' });
|
|
2582
2617
|
}
|
|
2583
2618
|
// ── 自进化看板 ──
|
|
2584
2619
|
if (req.method === 'GET' && pathname === '/api/evolution') {
|