aims-workstation 0.1.4 → 0.1.6
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 +13 -9
- package/package.json +1 -1
- package/scripts/publish-npm.sh +54 -0
- package/server.js +24 -17
- package/web/dist/assets/index-C9Ko3aMI.js +104 -0
- package/web/dist/assets/index-u4w4mrti.css +2 -0
- package/web/dist/index.html +2 -2
- package/scripts/publish-nexus.sh +0 -15
- package/web/dist/assets/index-B8MrcnV-.css +0 -2
- package/web/dist/assets/index-XtJADtwi.js +0 -60
package/.env.example
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
PORT=5600
|
|
3
3
|
|
|
4
4
|
# 平台 LLM(OpenAI 兼容端点;也可在「系统设置」页运行时切换)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
# 缺省公司内网 bifrost 网关(内网可达即开箱可用);以下三行留空/删除即用默认
|
|
6
|
+
LLM_BASE_URL=http://bifrost.infra.dev.aimstek.cn/v1
|
|
7
|
+
LLM_API_KEY=def
|
|
8
|
+
LLM_MODEL=def
|
|
8
9
|
|
|
9
|
-
# ARM
|
|
10
|
-
|
|
11
|
-
#
|
|
10
|
+
# ARM 系统(助手档案 + 经验库):站点地址缺省公司内网 http://arm.agent-platform.dev.aimstek.cn,留空即用默认
|
|
11
|
+
# 匿名可浏览目录;下载/上岗/新建/经验回流需令牌——到 ARM 生成访问令牌(arm_pat_…)后填入,或在「系统设置 · ARM 源」粘贴
|
|
12
|
+
# ARM_MCP_URL=https://your-arm-host/api/mcp # 可选:私有化部署时覆盖
|
|
13
|
+
# ARM_API_BASE=http://localhost:3001 # 可选:本地联调 ARM 时启用
|
|
14
|
+
# PAT 留空则自动读取 ~/.arm/config.json 的 token
|
|
12
15
|
ARM_PAT=
|
|
13
|
-
|
|
14
|
-
# ARM REST API 默认值(v2:助手检索/下载 + 知识混合检索;运行时可在「系统设置」页切换源)
|
|
15
|
-
ARM_API_BASE=http://localhost:3001
|
|
16
16
|
ARM_API_TOKEN=<arm_pat_xxx-or-leave-empty>
|
|
17
|
+
|
|
18
|
+
# npm 发版令牌(npmjs.com → Access Tokens 生成;供 scripts/publish-npm.sh 使用,发布免浏览器授权)
|
|
19
|
+
# 选 Granular(限定本包、Read and write)或 Classic → Automation
|
|
20
|
+
# NPM_TOKEN=npm_xxxxxxxxxxxxxxxxxxxx
|
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.6",
|
|
17
17
|
"description": "AIMS 工作站:本地智能体工作站——ARM 助手本地化(下载/离线/升级)、工作台对话式定制、多工作区隔离、分级自主与审计",
|
|
18
18
|
"bin": {
|
|
19
19
|
"aims-workstation": "bin/aims-workstation.js"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 发布到 npm 公共仓(令牌免浏览器 2FA 授权)
|
|
3
|
+
#
|
|
4
|
+
# 令牌配置(一次性):在包根目录 .env 里加一行
|
|
5
|
+
# NPM_TOKEN=npm_xxxxxxxxxxxx
|
|
6
|
+
# 令牌到 npmjs.com → 头像 → Access Tokens 生成:
|
|
7
|
+
# 推荐 Granular(限定 aims-workstation 包、Read and write、可设过期时间);
|
|
8
|
+
# 选 Classic 时必须用 Automation 类型——Publish 类型在 2FA 开启下仍会要求验证码。
|
|
9
|
+
# CI/临时覆盖可用环境变量:NPM_TOKEN=npm_xxx ./scripts/publish-npm.sh
|
|
10
|
+
# ⚠ 令牌不要写进本文件:scripts/ 在 package.json 的 files 清单里,会随 npm 包发布出去
|
|
11
|
+
#
|
|
12
|
+
# 用法(在 aims-workstation/ 目录下):
|
|
13
|
+
# ./scripts/publish-npm.sh 发布当前版本
|
|
14
|
+
# ./scripts/publish-npm.sh patch 先升版本(patch/minor/major/1.2.3)再发布
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
cd "$(dirname "$0")/.."
|
|
17
|
+
|
|
18
|
+
REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org/}"
|
|
19
|
+
|
|
20
|
+
# 令牌:环境变量 > .env 的 NPM_TOKEN(不 source 整个 .env,只取这一行)
|
|
21
|
+
TOKEN="${NPM_TOKEN:-}"
|
|
22
|
+
if [[ -z "$TOKEN" && -f .env ]]; then
|
|
23
|
+
TOKEN="$(sed -n 's/^NPM_TOKEN=//p' .env | tail -n1 | tr -d "[:space:]\"'")"
|
|
24
|
+
fi
|
|
25
|
+
if [[ -z "$TOKEN" ]]; then
|
|
26
|
+
echo "✗ 未找到 NPM_TOKEN(环境变量或 .env)。到 npmjs.com → Access Tokens 生成后执行:"
|
|
27
|
+
echo " echo 'NPM_TOKEN=npm_你的令牌' >> .env"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
case "$TOKEN" in
|
|
31
|
+
npm_*) ;;
|
|
32
|
+
*) echo "⚠ 令牌不以 npm_ 开头(npmjs.org 令牌通常以 npm_ 开头),请确认来源" ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
if [[ $# -ge 1 ]]; then
|
|
36
|
+
npm version "$1" --no-git-tag-version
|
|
37
|
+
fi
|
|
38
|
+
NAME="$(node -p "require('./package.json').name")"
|
|
39
|
+
VERSION="$(node -p "require('./package.json').version")"
|
|
40
|
+
echo "→ 发布 $NAME@$VERSION → $REGISTRY"
|
|
41
|
+
|
|
42
|
+
# 令牌经临时 userconfig 传入,不写 ~/.npmrc、不落盘
|
|
43
|
+
CFG="$(mktemp)"
|
|
44
|
+
trap 'rm -f "$CFG"' EXIT
|
|
45
|
+
HOST_KEY="$(echo "$REGISTRY" | sed -E 's|^https?://||; s|/+$||')"
|
|
46
|
+
printf '//%s/:_authToken=%s\n' "$HOST_KEY" "$TOKEN" > "$CFG"
|
|
47
|
+
|
|
48
|
+
echo "→ 身份: $(NPM_CONFIG_USERCONFIG="$CFG" npm whoami --registry="$REGISTRY" 2>/dev/null || echo '未知(Granular 令牌不支持 whoami,属正常,直接尝试发布)')"
|
|
49
|
+
NPM_CONFIG_USERCONFIG="$CFG" npm publish --registry="$REGISTRY"
|
|
50
|
+
echo "✅ 已发布 $NAME@$VERSION"
|
|
51
|
+
echo " 安装: npx $NAME@$VERSION"
|
|
52
|
+
if [[ $# -ge 1 ]]; then
|
|
53
|
+
echo "ℹ 版本号已变更,记得提交 package.json / package-lock.json"
|
|
54
|
+
fi
|
package/server.js
CHANGED
|
@@ -67,13 +67,13 @@ for (const envFile of [path.join(PKG_ROOT, '.env'), path.join(AIMS_HOME, '.env')
|
|
|
67
67
|
try {
|
|
68
68
|
for (const line of fs.readFileSync(envFile, 'utf8').split('\n')) {
|
|
69
69
|
const m = line.match(/^\s*([A-Z_]+)\s*=\s*(.*)\s*$/);
|
|
70
|
-
|
|
70
|
+
// 跳过模板占位符(<xxx-or-leave-empty> 之类):直接复制 .env.example 为 .env 时不应产生毒害值
|
|
71
|
+
if (m && env[m[1]] === undefined && !/^<[^>]*>$/.test(m[2])) env[m[1]] = m[2];
|
|
71
72
|
}
|
|
72
73
|
} catch { /* .env 可选 */ }
|
|
73
74
|
}
|
|
74
75
|
const PORT = +(process.env.PORT || env.PORT || 5600);
|
|
75
76
|
const HOST = process.env.AIMS_HOST || '0.0.0.0'; // 默认局域网可达(团队共用);仅本机使用可设 AIMS_HOST=127.0.0.1
|
|
76
|
-
const ARM_MCP_URL = process.env.ARM_MCP_URL || env.ARM_MCP_URL;
|
|
77
77
|
// ── 运行时配置(ARM 源与 LLM 均可私有化部署:在「系统设置」页切换,存 AIMS_HOME/config/platform.json,优先级 运行时 > 环境变量 > .env)──
|
|
78
78
|
const CFG_FILE = path.join(AIMS_HOME, 'config/platform.json');
|
|
79
79
|
const cfg = { arm_api_base: '', arm_mcp_url: '', arm_token: '', llm_base_url: '', llm_api_key: '', llm_model: '', scene_profile: '', access_token: '' };
|
|
@@ -96,15 +96,16 @@ function loadCfg() { try { Object.assign(cfg, JSON.parse(fs.readFileSync(CFG_FIL
|
|
|
96
96
|
loadCfg();
|
|
97
97
|
const guardNow = () => cfg.guard;
|
|
98
98
|
function saveCfg() { fs.writeFileSync(CFG_FILE, JSON.stringify(cfg, null, 2)); }
|
|
99
|
-
// 站点地址:运行时配置 > ARM_API_BASE(env) > 从 MCP URL
|
|
100
|
-
const
|
|
101
|
-
const
|
|
99
|
+
// ARM 站点地址:运行时配置 > ARM_API_BASE(env) > 从 MCP URL 推导 > 公司内网默认(内网可达即开箱可用);MCP 端点:显式配置 > 跟随站点地址
|
|
100
|
+
const ARM_DEFAULT_BASE = 'http://arm.agent-platform.dev.aimstek.cn';
|
|
101
|
+
const armApiBase = () => cfg.arm_api_base || process.env.ARM_API_BASE || env.ARM_API_BASE || (env.ARM_MCP_URL ? String(env.ARM_MCP_URL).replace(/\/api\/mcp$/, '') : ARM_DEFAULT_BASE);
|
|
102
|
+
const armMcpUrl = () => cfg.arm_mcp_url || `${armApiBase()}/api/mcp`;
|
|
102
103
|
const armApiToken = () => cfg.arm_token || env.ARM_API_TOKEN || ARM_PAT;
|
|
103
|
-
// LLM:OpenAI 兼容端点(baseUrl
|
|
104
|
-
const LLM_DEFAULT_BASE = '
|
|
104
|
+
// LLM:OpenAI 兼容端点(baseUrl/模型/密钥),缺省公司内网 bifrost 网关(内网可达即可用,无需配置)
|
|
105
|
+
const LLM_DEFAULT_BASE = 'http://bifrost.infra.dev.aimstek.cn/v1';
|
|
105
106
|
const llmBaseUrl = () => cfg.llm_base_url || env.LLM_BASE_URL || LLM_DEFAULT_BASE;
|
|
106
|
-
const llmApiKey = () => cfg.llm_api_key || env.LLM_API_KEY || '';
|
|
107
|
-
const llmModelName = () => cfg.llm_model || env.LLM_MODEL || '
|
|
107
|
+
const llmApiKey = () => cfg.llm_api_key || env.LLM_API_KEY || 'def';
|
|
108
|
+
const llmModelName = () => cfg.llm_model || env.LLM_MODEL || 'def';
|
|
108
109
|
// 场景画像:平台级、全局一份的用户场景描述(团队环境/系统清单/规范),注入所有助手;空则不注入。
|
|
109
110
|
// 与知识检索互补(常驻事实 vs 按需检索);凭据不进画像(放"凭据在哪、找谁要")。
|
|
110
111
|
const sceneProfile = () => String(cfg.scene_profile || '').trim();
|
|
@@ -214,7 +215,10 @@ async function armRest(pathname, init = {}, timeout_ms = 20000) {
|
|
|
214
215
|
try {
|
|
215
216
|
const res = await fetch(armApiBase() + pathname, { ...init, headers, signal: ctrl.signal });
|
|
216
217
|
const data = await res.json();
|
|
217
|
-
if (!res.ok || data.ok === false)
|
|
218
|
+
if (!res.ok || data.ok === false) {
|
|
219
|
+
const noAuth = res.status === 401 || res.status === 403 || /401|403|unauthorized|forbidden/i.test(String(data.msg || ''));
|
|
220
|
+
throw new Error(`ARM REST ${pathname.split('?')[0]}: ${data.msg || res.status}${noAuth ? '——令牌未配置或无权限:到 ARM(' + armApiBase() + ')生成访问令牌(arm_pat_…)后,粘贴到「系统设置 · ARM 源 · 访问令牌」' : ''}`);
|
|
221
|
+
}
|
|
218
222
|
return data.data;
|
|
219
223
|
} catch (e) {
|
|
220
224
|
if (e.message === 'fetch failed') { // undici 把网络层错误笼统包装,展开真实原因(DNS/连接/TLS)
|
|
@@ -1449,7 +1453,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1449
1453
|
const k = llmApiKey();
|
|
1450
1454
|
return json(res, 200, {
|
|
1451
1455
|
arm: {
|
|
1452
|
-
api_base: armApiBase(), api_base_runtime: !!cfg.arm_api_base,
|
|
1456
|
+
api_base: armApiBase(), api_base_runtime: !!cfg.arm_api_base, default_base: ARM_DEFAULT_BASE,
|
|
1453
1457
|
mcp_url: armMcpUrl(), mcp_explicit: !!cfg.arm_mcp_url,
|
|
1454
1458
|
token_set: !!t, token_masked: t ? `${t.slice(0, 10)}…${t.slice(-4)}` : '',
|
|
1455
1459
|
token_source: cfg.arm_token ? 'runtime' : (env.ARM_API_TOKEN ? 'env' : (ARM_PAT ? 'pat' : 'none')),
|
|
@@ -1566,9 +1570,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
1566
1570
|
if (req.method === 'GET' && pathname === '/api/agents') {
|
|
1567
1571
|
const kw = (url.searchParams.get('keyword') || '').trim().toLowerCase();
|
|
1568
1572
|
const reg = loadRegistry();
|
|
1569
|
-
let online = true, remoteCards = [];
|
|
1573
|
+
let online = true, remoteCards = [], authError = false;
|
|
1570
1574
|
try { remoteCards = await armListAgents(url.searchParams.get('keyword') || ''); } // REST 全量优先,旧版回落 MCP
|
|
1571
|
-
catch { online = false; }
|
|
1575
|
+
catch (e) { online = false; authError = /令牌未配置或无权限/.test(String(e.message)); } // 区分"未授权"与"不可达",前端引导配置令牌
|
|
1572
1576
|
const byId = new Map();
|
|
1573
1577
|
for (const e of reg.agents) {
|
|
1574
1578
|
const lm = localManifest(e.agent_id) || {};
|
|
@@ -1606,7 +1610,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1606
1610
|
saveRegistry(reg);
|
|
1607
1611
|
}
|
|
1608
1612
|
}
|
|
1609
|
-
return json(res, 200, { online, last_sync_at: reg.last_sync_at || null, agents: [...byId.values()] });
|
|
1613
|
+
return json(res, 200, { online, auth_error: authError, last_sync_at: reg.last_sync_at || null, agents: [...byId.values()] });
|
|
1610
1614
|
}
|
|
1611
1615
|
// ── 新建助手:本地起草(AI 生成 prompt)→ ARM JSON 创建(创建即发布)→ 可选知识绑定(幂等)→ 下载物化 ──
|
|
1612
1616
|
if (req.method === 'POST' && pathname === '/api/agents/draft-prompt') {
|
|
@@ -1614,8 +1618,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1614
1618
|
if (!String(description || '').trim()) return json(res, 400, { error: '请先填写助手职责描述' });
|
|
1615
1619
|
try {
|
|
1616
1620
|
const ctx = {
|
|
1617
|
-
systemPrompt: '
|
|
1618
|
-
messages: [{ role: 'user', content:
|
|
1621
|
+
systemPrompt: '你是助手档案起草器。根据用户的职责描述输出严格 JSON(不要 markdown 代码围栏):{"name":"助手名称","prompt":"System Prompt"}。name:12 字以内的中文名称,体现职责;prompt:第一句角色定位,随后 3-6 条职责要点(每条一个短句),最后 1-2 条工作方式或能力边界约束,200 字以内。只输出 JSON。',
|
|
1622
|
+
messages: [{ role: 'user', content: `助手名称(用户意向,可参考):${String(name || '').trim() || '(待定)'}\n职责描述:${String(description).trim()}\n\n请输出 JSON:`, timestamp: Date.now() }],
|
|
1619
1623
|
tools: [],
|
|
1620
1624
|
};
|
|
1621
1625
|
const stream = models.stream(model, ctx);
|
|
@@ -1623,7 +1627,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
1623
1627
|
for await (const ev of stream) { if (ev.type === 'text_delta') out += ev.delta; }
|
|
1624
1628
|
const final = await stream.result();
|
|
1625
1629
|
const text = ((final.content || []).filter(b => b.type === 'text').map(b => b.text).join('') || out).trim();
|
|
1626
|
-
|
|
1630
|
+
let draftName = '', prompt = text;
|
|
1631
|
+
const jm = text.match(/\{[\s\S]*\}/); // JSON 容错解析:失败则整段当 prompt,不阻断
|
|
1632
|
+
if (jm) { try { const j = JSON.parse(jm[0]); if (j.prompt) { prompt = String(j.prompt).trim(); if (j.name) draftName = String(j.name).trim().slice(0, 24); } } catch { } }
|
|
1633
|
+
return json(res, 200, { prompt, name: draftName });
|
|
1627
1634
|
} catch (e) { return json(res, 502, { error: `起草失败: ${e.message}` }); }
|
|
1628
1635
|
}
|
|
1629
1636
|
if (req.method === 'GET' && pathname === '/api/agents/knowledges') { // 新建向导的知识选择器:ARM 关键词检索
|