aims-workstation 0.1.14 → 0.1.15
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/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.15",
|
|
17
17
|
"description": "AIMS 工作站:本地智能体工作站——ARM 助手本地化(下载/离线/升级)、工作台对话式定制、多工作区隔离、分级自主与审计",
|
|
18
18
|
"bin": {
|
|
19
19
|
"aims-workstation": "bin/aims-workstation.js"
|
package/server.js
CHANGED
|
@@ -396,6 +396,27 @@ async function downloadAgent(agentId) {
|
|
|
396
396
|
return manifest;
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
// 本地档案写入(离线创建 / 未发布档案编辑):与 downloadAgent 同构的物化结构,source 标记 local。
|
|
400
|
+
// 发布到 ARM(同 id,见 /publish)后即转正为远端档案,本地结构不变——身份全程稳定。
|
|
401
|
+
const bumpPatch = (v) => { const p = String(v || 'v1.0.0').replace(/^v/, '').split('.').map(n => +n || 0); p[2] = (p[2] || 0) + 1; return `v${p[0] || 1}.${p[1] || 0}.${p[2]}`; };
|
|
402
|
+
function writeLocalAgent(agentId, { name, description, prompt }, { bump = false } = {}) {
|
|
403
|
+
const cur = bump ? (localManifest(agentId) || {}) : {};
|
|
404
|
+
const version = bump ? bumpPatch(cur.version) : 'v1.0.0';
|
|
405
|
+
const verDir = path.join(ARM_AGENTS_DIR, agentId, 'versions', version);
|
|
406
|
+
fs.mkdirSync(path.join(verDir, 'knowledges'), { recursive: true });
|
|
407
|
+
const manifest = {
|
|
408
|
+
agent_id: agentId, name, version, description: String(description || '').slice(0, 512),
|
|
409
|
+
downloaded_at: new Date().toISOString(), source: 'local',
|
|
410
|
+
prompt: String(prompt || ''),
|
|
411
|
+
skills: cur.skills || [], knowledges: cur.knowledges || [], ui_bindings: cur.ui_bindings || [], // 未发布档案无 ARM 绑定;编辑时保留既有
|
|
412
|
+
};
|
|
413
|
+
fs.writeFileSync(path.join(verDir, 'prompt.md'), manifest.prompt);
|
|
414
|
+
fs.writeFileSync(path.join(verDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
415
|
+
commitDownload(manifest);
|
|
416
|
+
audit({ actor: 'user', agentId, action: bump ? 'update-local-agent' : 'create-local-agent', version });
|
|
417
|
+
return manifest;
|
|
418
|
+
}
|
|
419
|
+
|
|
399
420
|
// 同步:比对远端目录与本地库,产出 new/update/delisted 事件;顺带冲账离线经验 outbox
|
|
400
421
|
async function armSync() {
|
|
401
422
|
const remote = await armListAgents();
|
|
@@ -1842,6 +1863,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1842
1863
|
update_available: !!(e.remote_version && cmpVersion(e.remote_version, e.installed_version) > 0),
|
|
1843
1864
|
delisted: !!e.delisted, downloaded_at: e.downloaded_at || null,
|
|
1844
1865
|
created_by_id: null, created_by: null, // 属主身份以远端目录为准(离线/已下架不可编辑,无需属主)
|
|
1866
|
+
local_only: !!e.local_only, // 本地未发布档案(离线创建):大厅显示「未发布」徽章 + 发布入口
|
|
1845
1867
|
});
|
|
1846
1868
|
}
|
|
1847
1869
|
if (online) { // 静默刷新远端版本与下架标记(显式事件统计走 /api/arm/sync)——仅在完整拉取时;
|
|
@@ -1908,24 +1930,36 @@ const server = http.createServer(async (req, res) => {
|
|
|
1908
1930
|
const { name, description, prompt, knowledge_ids } = await body(req);
|
|
1909
1931
|
const nm = String(name || '').trim(), pr = String(prompt || '').trim();
|
|
1910
1932
|
if (!nm || !pr) return json(res, 400, { error: '名称与 System Prompt 必填' });
|
|
1933
|
+
// 身份本地生成并随创建上行(新版 ARM 保持同 id;旧版忽略后以其返回 id 为准,行为不变)
|
|
1934
|
+
const localId = crypto.randomUUID();
|
|
1935
|
+
let newId, bound = 0, local = false;
|
|
1911
1936
|
try {
|
|
1912
|
-
const created = await armRest('/api/v1/agents', { method: 'POST', body: JSON.stringify({ name: nm, description: String(description || '').trim().slice(0, 512), prompt: pr, status: 'active' }) });
|
|
1913
|
-
|
|
1937
|
+
const created = await armRest('/api/v1/agents', { method: 'POST', body: JSON.stringify({ id: localId, name: nm, description: String(description || '').trim().slice(0, 512), prompt: pr, status: 'active' }) });
|
|
1938
|
+
newId = created?.id || created?.agent?.id;
|
|
1914
1939
|
if (!newId) throw new Error('ARM 未返回新助手 id');
|
|
1915
|
-
let bound = 0;
|
|
1916
1940
|
for (const kid of (Array.isArray(knowledge_ids) ? knowledge_ids : [])) { // 绑定幂等(重复绑定返回已有版本)
|
|
1917
1941
|
try { await armRest(`/api/v1/agents/${newId}/knowledges`, { method: 'POST', body: JSON.stringify({ knowledgeId: kid, kind: 'experience' }) }); bound++; }
|
|
1918
1942
|
catch { /* 单条绑定失败不阻断创建 */ }
|
|
1919
1943
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
return json(res,
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1944
|
+
} catch (e) {
|
|
1945
|
+
// 离线降级:仅网络不可达(ARM 请求失败/超时)时本地创建(07 离线理念:出生不依赖 ARM);
|
|
1946
|
+
// 业务错误(如名称已存在)照常上抛——那种情况下静默转本地只会掩盖问题。
|
|
1947
|
+
if (!/^ARM 请求失败/.test(String(e.message))) return json(res, 502, { error: `创建失败: ${e.message}` });
|
|
1948
|
+
newId = localId; local = true;
|
|
1949
|
+
}
|
|
1950
|
+
const manifest = local
|
|
1951
|
+
? writeLocalAgent(newId, { name: nm, description: String(description || '').trim(), prompt: pr })
|
|
1952
|
+
: await downloadAgent(newId); // 创建即物化本地(沿用下载链路)
|
|
1953
|
+
const reg = loadRegistry(); const entry = reg.agents.find((x) => x.agent_id === newId);
|
|
1954
|
+
if (entry) { entry.created_via_workstation = true; if (local) entry.local_only = true; } saveRegistry(reg);
|
|
1955
|
+
audit({ actor: 'user', action: local ? 'create-agent-local' : 'create-agent', agentId: newId, name: nm, knowledges: bound });
|
|
1956
|
+
return json(res, 200, { agent_id: newId, manifest, knowledges_bound: bound, local });
|
|
1957
|
+
}
|
|
1958
|
+
// 编辑档案(工作站创建的助手):ARM PUT 后重新物化,本地 prompt 同步更新;未发布档案仅写本地(版本前进)
|
|
1927
1959
|
if (req.method === 'GET' && pathname.match(/^\/api\/agents\/[0-9a-f-]{36}\/profile$/)) {
|
|
1928
1960
|
const id = pathname.split('/')[3];
|
|
1961
|
+
const le = loadRegistry().agents.find((x) => x.agent_id === id);
|
|
1962
|
+
if (le && le.local_only) { const m = localManifest(id) || {}; return json(res, 200, { name: m.name || le.name, description: String(m.description || ''), prompt: m.prompt || '' }); }
|
|
1929
1963
|
try {
|
|
1930
1964
|
const d = await armRest(`/api/v1/agents/${id}`);
|
|
1931
1965
|
return json(res, 200, { name: d.name, description: String(d.description || ''), prompt: d.prompt || '' });
|
|
@@ -1939,6 +1973,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
1939
1973
|
if (description !== undefined) patch.description = String(description || '').trim().slice(0, 512);
|
|
1940
1974
|
if (String(prompt || '').trim()) patch.prompt = String(prompt).trim();
|
|
1941
1975
|
if (!Object.keys(patch).length) return json(res, 400, { error: '没有可更新字段' });
|
|
1976
|
+
const le = loadRegistry().agents.find((x) => x.agent_id === id);
|
|
1977
|
+
if (le && le.local_only) { // 未发布档案:仅写本地(版本前进可回滚),发布后编辑将同步 ARM
|
|
1978
|
+
const cur = localManifest(id) || {};
|
|
1979
|
+
const manifest = writeLocalAgent(id, { name: patch.name || cur.name, description: patch.description ?? cur.description, prompt: patch.prompt || cur.prompt }, { bump: true });
|
|
1980
|
+
return json(res, 200, { ok: true, manifest, local: true });
|
|
1981
|
+
}
|
|
1942
1982
|
try {
|
|
1943
1983
|
await armRest(`/api/v1/agents/${id}`, { method: 'PUT', body: JSON.stringify(patch) });
|
|
1944
1984
|
let manifest = null;
|
|
@@ -1957,6 +1997,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1957
1997
|
}
|
|
1958
1998
|
let mBind = pathname.match(/^\/api\/agents\/([0-9a-f-]{36})\/bindings$/);
|
|
1959
1999
|
if (mBind && req.method === 'GET') {
|
|
2000
|
+
const le = loadRegistry().agents.find((x) => x.agent_id === mBind[1]);
|
|
2001
|
+
if (le && le.local_only) return json(res, 200, { skills: [], knowledges: [], local_only: true }); // 未发布档案无 ARM 绑定
|
|
1960
2002
|
try {
|
|
1961
2003
|
const d = await armRest(`/api/v1/agents/${mBind[1]}`);
|
|
1962
2004
|
return json(res, 200, {
|
|
@@ -2011,18 +2053,48 @@ const server = http.createServer(async (req, res) => {
|
|
|
2011
2053
|
return json(res, 200, { agent_id: newId, name: nm, copied, manifest: { agent_id: manifest.agent_id, name: manifest.name, version: manifest.version } });
|
|
2012
2054
|
} catch (e) { return json(res, 502, { error: `克隆失败: ${e.message}` }); }
|
|
2013
2055
|
}
|
|
2014
|
-
// 从 ARM
|
|
2056
|
+
// 从 ARM 删除(危险操作,前端二次确认):可选同时卸载本地档案;未发布档案只存在于本地,直接卸载
|
|
2015
2057
|
if (req.method === 'POST' && pathname.match(/^\/api\/agents\/[0-9a-f-]{36}\/arm-delete$/)) {
|
|
2016
2058
|
const id = pathname.split('/')[3];
|
|
2017
2059
|
const { also_uninstall, purge_workspaces } = await body(req);
|
|
2018
2060
|
try {
|
|
2019
|
-
|
|
2061
|
+
const le = loadRegistry().agents.find((x) => x.agent_id === id);
|
|
2062
|
+
if (!(le && le.local_only)) await armRest(`/api/v1/agents/${id}`, { method: 'DELETE' });
|
|
2020
2063
|
let uninstalled = false;
|
|
2021
|
-
if (also_uninstall &&
|
|
2022
|
-
audit({ actor: 'user', action: 'arm-delete-agent', agentId: id, also_uninstalled: uninstalled });
|
|
2064
|
+
if (also_uninstall && le) uninstalled = uninstallLocalAgent(id, !!purge_workspaces);
|
|
2065
|
+
audit({ actor: 'user', action: le && le.local_only ? 'delete-local-agent' : 'arm-delete-agent', agentId: id, also_uninstalled: uninstalled });
|
|
2023
2066
|
return json(res, 200, { ok: true, uninstalled });
|
|
2024
2067
|
} catch (e) { return json(res, 502, { error: `ARM 删除失败: ${e.message}` }); }
|
|
2025
2068
|
}
|
|
2069
|
+
// 发布到 ARM(本地未发布档案转正):同 id 上行(新版 ARM),身份不变、本地 deployment/工作台/数据零迁移。
|
|
2070
|
+
// 幂等:同 id 已在 ARM(上次发布部分成功)→ 校验同名即续走转正;旧版 ARM 忽略 id 返回新 id → 明确报错提示升级。
|
|
2071
|
+
if (req.method === 'POST' && pathname.match(/^\/api\/agents\/[0-9a-f-]{36}\/publish$/)) {
|
|
2072
|
+
const id = pathname.match(/^\/api\/agents\/([0-9a-f-]{36})\/publish$/)[1];
|
|
2073
|
+
const reg = loadRegistry();
|
|
2074
|
+
const le = reg.agents.find((x) => x.agent_id === id);
|
|
2075
|
+
if (!le || !le.local_only) return json(res, 400, { error: '该助手不是本地未发布档案(可能已发布)' });
|
|
2076
|
+
const m = localManifest(id);
|
|
2077
|
+
if (!m) return json(res, 400, { error: '本地档案缺失,请先重新创建' });
|
|
2078
|
+
try {
|
|
2079
|
+
let created;
|
|
2080
|
+
try {
|
|
2081
|
+
created = await armRest('/api/v1/agents', { method: 'POST', body: JSON.stringify({ id, name: m.name, description: String(m.description || '').slice(0, 512), prompt: m.prompt || '', status: 'active' }) });
|
|
2082
|
+
} catch (e) {
|
|
2083
|
+
if (/该 id 已存在/.test(String(e.message))) { // 幂等续走:确认同 id 同名即视为已创建
|
|
2084
|
+
const d = await armRest(`/api/v1/agents/${id}`);
|
|
2085
|
+
if (!d || d.name !== m.name) throw e;
|
|
2086
|
+
created = d;
|
|
2087
|
+
} else throw e;
|
|
2088
|
+
}
|
|
2089
|
+
const finalId = created?.id || created?.agent?.id;
|
|
2090
|
+
if (finalId && finalId !== id) return json(res, 502, { error: '当前 ARM 版本不支持保持助手身份(忽略客户端 id)——请升级 ARM 后再发布' });
|
|
2091
|
+
const manifest = await downloadAgent(id); // 重新物化:source 转正、版本对齐 ARM
|
|
2092
|
+
const reg2 = loadRegistry(); const en = reg2.agents.find((x) => x.agent_id === id);
|
|
2093
|
+
if (en) { en.local_only = false; en.remote_version = manifest.version; } saveRegistry(reg2);
|
|
2094
|
+
audit({ actor: 'user', action: 'publish-agent', agentId: id, name: m.name, version: manifest.version });
|
|
2095
|
+
return json(res, 200, { ok: true, manifest });
|
|
2096
|
+
} catch (e) { return json(res, 502, { error: `发布失败: ${e.message}` }); }
|
|
2097
|
+
}
|
|
2026
2098
|
// ── 本地助手库:下载 / 升级 / 版本历史 / 回滚 / 卸载 ──
|
|
2027
2099
|
let mAgt = pathname.match(/^\/api\/agents\/([0-9a-f-]{36})\/(download|upgrade|versions|rollback)$/);
|
|
2028
2100
|
if (mAgt && req.method === 'POST' && (mAgt[2] === 'download' || mAgt[2] === 'upgrade')) {
|