@neomei/agent-soul-framework 4.5.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.example +39 -0
- package/.opencode/config.json.example +17 -0
- package/.opencode/opencode.json.example +36 -0
- package/.opencode/prompt.md.example +12 -0
- package/.opencode/tools/read-plugin.js +185 -0
- package/AGENTS.md.example +43 -0
- package/README.md +466 -0
- package/SECURITY.md +117 -0
- package/TOOLS.md.example +27 -0
- package/bin/hunqi +2 -0
- package/bin/hunqi-knowledge +2 -0
- package/connectors/feishu/background.sh +124 -0
- package/connectors/feishu/core-start.sh +35 -0
- package/connectors/feishu/hooks/on-session-created.sh +97 -0
- package/connectors/feishu/hooks/on-session-idle.sh +59 -0
- package/connectors/feishu/model-failover.sh +82 -0
- package/connectors/feishu/restart-all.sh +63 -0
- package/connectors/feishu/restart-feishu.sh +101 -0
- package/connectors/feishu/restart-serve.sh +62 -0
- package/connectors/feishu/scripts/session-cleanup.sh +72 -0
- package/connectors/feishu/start.sh +91 -0
- package/connectors/feishu/stop.sh +78 -0
- package/connectors/feishu/systemd/channel-feishu@.service +63 -0
- package/connectors/feishu/systemd/hunqi-core@.service +50 -0
- package/connectors/feishu/systemd/install-systemd.sh +316 -0
- package/connectors/feishu/systemd/sleep-hooks/99-hunqi-resume.sh +14 -0
- package/connectors/feishu/thinking-icon.gif +0 -0
- package/connectors/feishu/thinking.gif +0 -0
- package/connectors/feishu/watchdog.sh +104 -0
- package/dist/bin/hunqi-knowledge.d.ts +1 -0
- package/dist/bin/hunqi-knowledge.js +12 -0
- package/dist/bin/hunqi-knowledge.js.map +1 -0
- package/dist/cli/hunqi.d.ts +6 -0
- package/dist/cli/hunqi.js +830 -0
- package/dist/cli/hunqi.js.map +1 -0
- package/dist/heartbeat/runner.d.ts +10 -0
- package/dist/heartbeat/runner.js +58 -0
- package/dist/heartbeat/runner.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/daily.d.ts +5 -0
- package/dist/knowledge/daily.js +65 -0
- package/dist/knowledge/daily.js.map +1 -0
- package/dist/knowledge/index.d.ts +5 -0
- package/dist/knowledge/index.js +34 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/memory/manager.d.ts +20 -0
- package/dist/memory/manager.js +110 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/search.d.ts +11 -0
- package/dist/memory/search.js +79 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/structured.d.ts +21 -0
- package/dist/memory/structured.js +88 -0
- package/dist/memory/structured.js.map +1 -0
- package/dist/opencode/api.d.ts +7 -0
- package/dist/opencode/api.js +26 -0
- package/dist/opencode/api.js.map +1 -0
- package/dist/plugin/index.d.ts +38 -0
- package/dist/plugin/index.js +143 -0
- package/dist/plugin/index.js.map +1 -0
- package/docs/bugs/opencode-feishu-permission-race.md +168 -0
- package/heartbeat/heartbeat_tasks.json +272 -0
- package/heartbeat_wrapper.sh +21 -0
- package/hunqi.sh +68 -0
- package/install.sh +301 -0
- package/knowledge/body/INDEX.md.example +6 -0
- package/knowledge/emotion/INDEX.md.example +6 -0
- package/knowledge/evolution/INDEX.md.example +6 -0
- package/knowledge/growth/INDEX.md.example +6 -0
- package/knowledge/intimacy/INDEX.md.example +6 -0
- package/knowledge/methodology/INDEX.md.example +6 -0
- package/knowledge/philosophy/INDEX.md.example +6 -0
- package/knowledge/system/INDEX.md.example +6 -0
- package/memory/MEMORY.md.example +6 -0
- package/package.json +79 -0
- package/plugin/README.md +21 -0
- package/plugin/index.js +154 -0
- package/plugin/manifest.json +37 -0
- package/plugin/package.json +19 -0
- package/scripts/content-filter.js +173 -0
- package/scripts/health-check.sh +153 -0
- package/scripts/session-cleanup.sh +85 -0
- package/setup-wizard.sh +420 -0
- package/setup.sh +128 -0
- package/soul/HEARTBEAT.md.example +13 -0
- package/soul/IDENTITY.md.example +7 -0
- package/soul/SOUL.md.example +19 -0
- package/soul/USER.md.example +7 -0
- package/start-feishu-daemon.sh +127 -0
- package/start.sh +36 -0
- package/test.sh +51 -0
- package/uninstall.sh +144 -0
- package/verify.sh +29 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# session-cleanup.sh — 清理消息过多的飞书 session 映射
|
|
3
|
+
# 当 session 消息数超过阈值时,从 feishu-sessions.json 中移除映射
|
|
4
|
+
# 下次对话会自动创建新 session
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SESSIONS_FILE="${HOME}/.config/opencode/feishu-sessions.json"
|
|
9
|
+
DB_FILE="${HOME}/.local/share/opencode/opencode.db"
|
|
10
|
+
MAX_MESSAGES="${MAX_MESSAGES_PER_SESSION:-50}"
|
|
11
|
+
|
|
12
|
+
if [ ! -f "$SESSIONS_FILE" ]; then
|
|
13
|
+
echo "[session-cleanup] feishu-sessions.json not found"
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if [ ! -f "$DB_FILE" ]; then
|
|
18
|
+
echo "[session-cleanup] opencode.db not found"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# 读取当前 sessions
|
|
23
|
+
SESSIONS=$(cat "$SESSIONS_FILE")
|
|
24
|
+
|
|
25
|
+
# 使用单一 Python 脚本安全处理所有操作,避免注入
|
|
26
|
+
python3 - "$SESSIONS_FILE" "$DB_FILE" "$MAX_MESSAGES" <<'PYEOF'
|
|
27
|
+
import json, sqlite3, sys, os
|
|
28
|
+
|
|
29
|
+
sessions_file = sys.argv[1]
|
|
30
|
+
db_file = sys.argv[2]
|
|
31
|
+
max_messages = int(sys.argv[3])
|
|
32
|
+
|
|
33
|
+
with open(sessions_file, "r") as f:
|
|
34
|
+
data = json.load(f)
|
|
35
|
+
|
|
36
|
+
sessions = data.get("sessions", [])
|
|
37
|
+
if not sessions:
|
|
38
|
+
print("[session-cleanup] No sessions found")
|
|
39
|
+
sys.exit(0)
|
|
40
|
+
|
|
41
|
+
conn = sqlite3.connect(db_file)
|
|
42
|
+
try:
|
|
43
|
+
cleaned = 0
|
|
44
|
+
remaining = []
|
|
45
|
+
for s in sessions:
|
|
46
|
+
sid = s.get("sessionId", "")
|
|
47
|
+
cursor = conn.execute("SELECT COUNT(*) FROM message WHERE session_id = ?", (sid,))
|
|
48
|
+
msg_count = cursor.fetchone()[0]
|
|
49
|
+
|
|
50
|
+
if msg_count > max_messages:
|
|
51
|
+
print(f"[session-cleanup] {sid} has {msg_count} messages > {max_messages}, removing mapping")
|
|
52
|
+
cleaned += 1
|
|
53
|
+
else:
|
|
54
|
+
print(f"[session-cleanup] {sid} has {msg_count} messages <= {max_messages}, keeping")
|
|
55
|
+
remaining.append(s)
|
|
56
|
+
|
|
57
|
+
if cleaned > 0:
|
|
58
|
+
data["sessions"] = remaining
|
|
59
|
+
with open(sessions_file, "w") as f:
|
|
60
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
61
|
+
print(f"[session-cleanup] Cleaned {cleaned} session(s), next message will create new session")
|
|
62
|
+
else:
|
|
63
|
+
print(f"[session-cleanup] All sessions are within limit ({max_messages} messages)")
|
|
64
|
+
finally:
|
|
65
|
+
conn.close()
|
|
66
|
+
PYEOF
|
|
67
|
+
|
|
68
|
+
if [ "$CLEANED" -gt 0 ]; then
|
|
69
|
+
echo "[session-cleanup] Cleaned $CLEANED session(s), next message will create new session"
|
|
70
|
+
else
|
|
71
|
+
echo "[session-cleanup] All sessions are within limit ($MAX_MESSAGES messages)"
|
|
72
|
+
fi
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# hunqi-feishu - 魂器飞书连接器启动脚本
|
|
4
|
+
# 只启动 opencode-feishu 插件,依赖 hunqi-core 提供的 OpenCode server
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
10
|
+
|
|
11
|
+
if [ -f "$PROJECT_DIR/.env" ]; then
|
|
12
|
+
set -a && source "$PROJECT_DIR/.env" && set +a
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
cd "$PROJECT_DIR"
|
|
16
|
+
|
|
17
|
+
OPENCODE_PORT="${OPENCODE_PORT:-19876}"
|
|
18
|
+
|
|
19
|
+
# 自动检测 opencode-feishu 启动方式(npm link 在重启后经常断裂)
|
|
20
|
+
resolve_opencode_feishu() {
|
|
21
|
+
# 1. 全局命令
|
|
22
|
+
if command -v opencode-feishu &>/dev/null; then
|
|
23
|
+
echo "opencode-feishu"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# 2. npx
|
|
28
|
+
if command -v npx &>/dev/null; then
|
|
29
|
+
echo "npx opencode-feishu"
|
|
30
|
+
return 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# 3. npm 全局路径
|
|
34
|
+
if command -v npm &>/dev/null; then
|
|
35
|
+
local npm_prefix
|
|
36
|
+
npm_prefix=$(npm prefix -g 2>/dev/null)
|
|
37
|
+
if [ -n "$npm_prefix" ] && [ -x "$npm_prefix/bin/opencode-feishu" ]; then
|
|
38
|
+
echo "$npm_prefix/bin/opencode-feishu"
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# 4. 源码回退(开发环境)
|
|
44
|
+
local src_paths=(
|
|
45
|
+
"/home/$USER/文档/projects/opencode-feishu/bin/opencode-feishu"
|
|
46
|
+
"$PROJECT_DIR/../opencode-feishu/bin/opencode-feishu"
|
|
47
|
+
)
|
|
48
|
+
for p in "${src_paths[@]}"; do
|
|
49
|
+
if [ -f "$p" ]; then
|
|
50
|
+
local node_cmd
|
|
51
|
+
node_cmd=$(command -v node 2>/dev/null || echo "node")
|
|
52
|
+
echo "$node_cmd $p"
|
|
53
|
+
return 0
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
echo "🚀 魂器飞书连接器 (hunqi-feishu)"
|
|
61
|
+
echo ""
|
|
62
|
+
|
|
63
|
+
# 检查 opencode-feishu 是否可用
|
|
64
|
+
FEISHU_CMD=$(resolve_opencode_feishu) || {
|
|
65
|
+
echo "❌ opencode-feishu 未安装"
|
|
66
|
+
echo ""
|
|
67
|
+
echo "请安装:"
|
|
68
|
+
echo " npm install -g @neomei/opencode-feishu"
|
|
69
|
+
exit 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# 检查 OpenCode server 是否已运行
|
|
73
|
+
echo "检查 OpenCode server (port $OPENCODE_PORT)..."
|
|
74
|
+
if ! curl -s http://localhost:$OPENCODE_PORT/session >/dev/null 2>&1; then
|
|
75
|
+
echo "❌ OpenCode server 未运行"
|
|
76
|
+
echo ""
|
|
77
|
+
echo "hunqi-feishu 依赖 hunqi-core,请先启动核心:"
|
|
78
|
+
echo " ./connectors/feishu/core-start.sh"
|
|
79
|
+
echo ""
|
|
80
|
+
echo "或使用 systemd 同时启动两者:"
|
|
81
|
+
echo " sudo systemctl start hunqi-core@\$USER"
|
|
82
|
+
echo " sudo systemctl start channel-feishu@\$USER"
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
echo "✅ OpenCode server 已就绪"
|
|
87
|
+
echo ""
|
|
88
|
+
|
|
89
|
+
# 启动飞书桥接
|
|
90
|
+
echo "启动飞书桥接(流式卡片 + 工具状态 + 交互提示)..."
|
|
91
|
+
exec $FEISHU_CMD start
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
6
|
+
PORT="${OPENCODE_PORT:-19876}"
|
|
7
|
+
|
|
8
|
+
if [ -f "$PROJECT_DIR/.env" ]; then
|
|
9
|
+
set -a && source "$PROJECT_DIR/.env" && set +a
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# 自动检测 opencode-feishu 启动方式
|
|
13
|
+
resolve_opencode_feishu() {
|
|
14
|
+
if command -v opencode-feishu &>/dev/null; then
|
|
15
|
+
echo "opencode-feishu"
|
|
16
|
+
return 0
|
|
17
|
+
fi
|
|
18
|
+
if command -v npx &>/dev/null; then
|
|
19
|
+
echo "npx opencode-feishu"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
if command -v npm &>/dev/null; then
|
|
23
|
+
local npm_prefix
|
|
24
|
+
npm_prefix=$(npm prefix -g 2>/dev/null)
|
|
25
|
+
if [ -n "$npm_prefix" ] && [ -x "$npm_prefix/bin/opencode-feishu" ]; then
|
|
26
|
+
echo "$npm_prefix/bin/opencode-feishu"
|
|
27
|
+
return 0
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
local src_paths=(
|
|
31
|
+
"/home/$USER/文档/projects/opencode-feishu/bin/opencode-feishu"
|
|
32
|
+
"$PROJECT_DIR/../opencode-feishu/bin/opencode-feishu"
|
|
33
|
+
)
|
|
34
|
+
for p in "${src_paths[@]}"; do
|
|
35
|
+
if [ -f "$p" ]; then
|
|
36
|
+
local node_cmd
|
|
37
|
+
node_cmd=$(command -v node 2>/dev/null || echo "node")
|
|
38
|
+
echo "$node_cmd $p"
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
echo "魂器 飞书连接器 — 停止"
|
|
46
|
+
echo ""
|
|
47
|
+
|
|
48
|
+
FEISHU_CMD=$(resolve_opencode_feishu) || true
|
|
49
|
+
if [ -n "$FEISHU_CMD" ]; then
|
|
50
|
+
$FEISHU_CMD stop 2>/dev/null && echo "已停止 opencode-feishu" || echo "opencode-feishu 未在运行"
|
|
51
|
+
else
|
|
52
|
+
echo "opencode-feishu 命令不可用,跳过插件停止"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# 只杀监听指定端口的 opencode serve,避免误杀其他实例
|
|
56
|
+
SERVE_PID=$(ss -tlnp 2>/dev/null | grep ":${PORT} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
57
|
+
if [ -n "$SERVE_PID" ]; then
|
|
58
|
+
kill "$SERVE_PID" 2>/dev/null && echo "已停止 opencode serve (PID: $SERVE_PID, 端口: $PORT)"
|
|
59
|
+
else
|
|
60
|
+
echo "opencode serve 未在运行 (端口: $PORT)"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
sleep 1
|
|
64
|
+
|
|
65
|
+
if ss -tlnp 2>/dev/null | grep -q ":${PORT} "; then
|
|
66
|
+
PID=$(ss -tlnp 2>/dev/null | grep ":${PORT} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
67
|
+
if [ -n "$PID" ]; then
|
|
68
|
+
PROC_NAME=$(ps -p "$PID" -o comm= 2>/dev/null || echo "unknown")
|
|
69
|
+
echo "端口 ${PORT} 仍被占用 (PID: $PID, $PROC_NAME),发送 SIGTERM..."
|
|
70
|
+
kill "$PID" 2>/dev/null
|
|
71
|
+
sleep 2
|
|
72
|
+
if kill -0 "$PID" 2>/dev/null; then
|
|
73
|
+
echo "进程 $PID 未响应 SIGTERM,跳过"
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
echo "完成"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=魂器飞书频道 (Channel Feishu) - Hunqi Core 的飞书接入
|
|
3
|
+
# 注意:install-systemd.sh 安装时会将 %h 替换为实际家目录。
|
|
4
|
+
# 系统级 systemd 中 %h 展开为 /root(而非 User= 指定的用户),不可直接使用。
|
|
5
|
+
Documentation=https://github.com/NeoMei/agent-soul-framework
|
|
6
|
+
After=network-online.target hunqi-core@%i.service
|
|
7
|
+
Wants=network-online.target
|
|
8
|
+
|
|
9
|
+
# 强依赖核心服务
|
|
10
|
+
Requires=hunqi-core@%i.service
|
|
11
|
+
After=hunqi-core@%i.service
|
|
12
|
+
|
|
13
|
+
# 挂起后重启
|
|
14
|
+
After=suspend.target hibernate.target hybrid-sleep.target
|
|
15
|
+
|
|
16
|
+
[Service]
|
|
17
|
+
Type=simple
|
|
18
|
+
User=%I
|
|
19
|
+
Group=%I
|
|
20
|
+
|
|
21
|
+
# 工作目录
|
|
22
|
+
WorkingDirectory=%h/.hunqi/agent-soul-framework/packages/agent-soul-framework
|
|
23
|
+
|
|
24
|
+
# 加载环境变量
|
|
25
|
+
Environment="HOME=%h"
|
|
26
|
+
Environment="USER=%I"
|
|
27
|
+
|
|
28
|
+
# 从 .env 文件加载环境变量
|
|
29
|
+
EnvironmentFile=-%h/.hunqi/.env
|
|
30
|
+
|
|
31
|
+
# 启动前:清理消息过多的 session 映射(防止上下文无限膨胀)
|
|
32
|
+
ExecStartPre=-/bin/sh -c '%h/.hunqi/agent-soul-framework/packages/agent-soul-framework/connectors/feishu/scripts/session-cleanup.sh'
|
|
33
|
+
|
|
34
|
+
# 启动前等待核心就绪
|
|
35
|
+
ExecStartPre=/bin/sh -c 'for i in $(seq 1 30); do if curl -s http://localhost:19876/session >/dev/null 2>&1; then exit 0; fi; sleep 1; done; exit 1'
|
|
36
|
+
|
|
37
|
+
# 启动命令 - 使用 ~/.hunqi/bin 下的 wrapper,避免依赖 nvm 版本
|
|
38
|
+
ExecStart=%h/.hunqi/bin/opencode-feishu start
|
|
39
|
+
|
|
40
|
+
# 停止命令
|
|
41
|
+
ExecStop=%h/.hunqi/bin/opencode-feishu stop
|
|
42
|
+
|
|
43
|
+
# 重载配置(修改 feishu.json 后执行 systemctl reload 即可生效)
|
|
44
|
+
ExecReload=/bin/sh -c '%h/.hunqi/bin/opencode-feishu stop && sleep 2 && %h/.hunqi/bin/opencode-feishu start'
|
|
45
|
+
|
|
46
|
+
# 重启策略
|
|
47
|
+
Restart=always
|
|
48
|
+
RestartSec=5
|
|
49
|
+
StartLimitInterval=60s
|
|
50
|
+
StartLimitBurst=3
|
|
51
|
+
|
|
52
|
+
# 日志
|
|
53
|
+
StandardOutput=journal
|
|
54
|
+
StandardError=journal
|
|
55
|
+
SyslogIdentifier=channel-feishu
|
|
56
|
+
|
|
57
|
+
# 进程管理
|
|
58
|
+
KillMode=mixed
|
|
59
|
+
KillSignal=SIGTERM
|
|
60
|
+
TimeoutStopSec=30
|
|
61
|
+
|
|
62
|
+
[Install]
|
|
63
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=魂器核心 (Hunqi Core) - OpenCode + Soul
|
|
3
|
+
# 注意:install-systemd.sh 安装时会将 %h 替换为实际家目录。
|
|
4
|
+
# 系统级 systemd 中 %h 展开为 /root(而非 User= 指定的用户),不可直接使用。
|
|
5
|
+
Documentation=https://github.com/NeoMei/agent-soul-framework
|
|
6
|
+
After=network-online.target
|
|
7
|
+
Wants=network-online.target
|
|
8
|
+
|
|
9
|
+
# 挂起后重启
|
|
10
|
+
After=suspend.target hibernate.target hybrid-sleep.target
|
|
11
|
+
|
|
12
|
+
[Service]
|
|
13
|
+
Type=simple
|
|
14
|
+
User=%I
|
|
15
|
+
Group=%I
|
|
16
|
+
|
|
17
|
+
# 工作目录
|
|
18
|
+
WorkingDirectory=%h/.hunqi/agent-soul-framework/packages/agent-soul-framework
|
|
19
|
+
|
|
20
|
+
# 加载环境变量
|
|
21
|
+
Environment="HOME=%h"
|
|
22
|
+
Environment="USER=%I"
|
|
23
|
+
|
|
24
|
+
# 从 .env 文件加载环境变量
|
|
25
|
+
EnvironmentFile=-%h/.hunqi/.env
|
|
26
|
+
|
|
27
|
+
# 启动命令 - 使用 wrapper 解析 opencode 位置并启动 serve
|
|
28
|
+
ExecStart=/bin/sh -c 'OPENCODE=$(%h/.hunqi/bin/opencode-feishu --resolve-opencode 2>/dev/null || command -v opencode); exec "$OPENCODE" serve --port 19876'
|
|
29
|
+
|
|
30
|
+
# 停止命令(精确匹配端口,避免误杀)
|
|
31
|
+
ExecStop=/bin/sh -c 'PID=$(ss -tlnp 2>/dev/null | grep ":19876 " | sed -n "s/.*pid=\\([0-9]*\\).*/\\1/p"); [ -n "$PID" ] && kill "$PID" || true'
|
|
32
|
+
|
|
33
|
+
# 重启策略
|
|
34
|
+
Restart=always
|
|
35
|
+
RestartSec=5
|
|
36
|
+
StartLimitInterval=60s
|
|
37
|
+
StartLimitBurst=3
|
|
38
|
+
|
|
39
|
+
# 日志
|
|
40
|
+
StandardOutput=journal
|
|
41
|
+
StandardError=journal
|
|
42
|
+
SyslogIdentifier=hunqi-core
|
|
43
|
+
|
|
44
|
+
# 进程管理
|
|
45
|
+
KillMode=mixed
|
|
46
|
+
KillSignal=SIGTERM
|
|
47
|
+
TimeoutStopSec=30
|
|
48
|
+
|
|
49
|
+
[Install]
|
|
50
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# 魂器 systemd 服务安装程序
|
|
4
|
+
# 核心+频道架构:hunqi-core + channel-feishu
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# 错误处理:打印行号和命令
|
|
9
|
+
trap 'print_error "脚本在第 ${LINENO} 行出错: $BASH_COMMAND"; exit 1' ERR
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
13
|
+
USER_NAME="${SUDO_USER:-$USER}"
|
|
14
|
+
|
|
15
|
+
# 颜色
|
|
16
|
+
RED='\033[0;31m'
|
|
17
|
+
GREEN='\033[0;32m'
|
|
18
|
+
YELLOW='\033[1;33m'
|
|
19
|
+
BLUE='\033[0;34m'
|
|
20
|
+
NC='\033[0m'
|
|
21
|
+
|
|
22
|
+
print_status() { echo -e "${BLUE}[魂器]${NC} $1"; }
|
|
23
|
+
print_info() { echo -e "${BLUE}[信息]${NC} $1"; }
|
|
24
|
+
print_success() { echo -e "${GREEN}[成功]${NC} $1"; }
|
|
25
|
+
print_warning() { echo -e "${YELLOW}[警告]${NC} $1"; }
|
|
26
|
+
print_error() { echo -e "${RED}[错误]${NC} $1" >&2; }
|
|
27
|
+
|
|
28
|
+
if [ "$EUID" -ne 0 ]; then
|
|
29
|
+
print_error "需要 root 权限"
|
|
30
|
+
print_status "请使用: sudo $0"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# 辅助函数:安全读取用户输入(支持交互式和非交互式环境)
|
|
35
|
+
safe_read() {
|
|
36
|
+
local prompt="$1"
|
|
37
|
+
local var_name="$2"
|
|
38
|
+
if [ -t 0 ] || [ -e /dev/tty ]; then
|
|
39
|
+
read -rp "$prompt" "$var_name" < /dev/tty
|
|
40
|
+
else
|
|
41
|
+
# 非交互式环境:使用默认值或退出
|
|
42
|
+
print_warning "非交互式环境,无法读取输入"
|
|
43
|
+
return 1
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if [ -z "$SUDO_USER" ] && [ "$USER" = "root" ]; then
|
|
48
|
+
safe_read "请输入运行魂器的用户名: " USER_NAME || { print_error "非交互式环境请设置 SUDO_USER 环境变量"; exit 1; }
|
|
49
|
+
[ -z "$USER_NAME" ] && { print_error "用户名不能为空"; exit 1; }
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# 获取用户真正的家目录(不假设 /home/ 前缀)
|
|
53
|
+
HOME_DIR=$(getent passwd "$USER_NAME" | cut -d: -f6)
|
|
54
|
+
if [ -z "$HOME_DIR" ]; then
|
|
55
|
+
print_error "无法获取用户 $USER_NAME 的家目录"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# 自动检测项目目录(支持多种安装位置)
|
|
60
|
+
resolve_project_dir() {
|
|
61
|
+
# 1. 从脚本位置推导(开发环境,直接 clone 源码)
|
|
62
|
+
# 脚本路径: packages/agent-soul-framework/connectors/feishu/systemd/
|
|
63
|
+
local script_project="$(cd "$SCRIPT_DIR/../../../../packages/agent-soul-framework" && pwd)"
|
|
64
|
+
if [ -f "$script_project/package.json" ]; then
|
|
65
|
+
echo "$script_project"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# 2. ~/.hunqi/agent-soul-framework(install.sh 默认安装位置)
|
|
70
|
+
if [ -f "$HOME_DIR/.hunqi/agent-soul-framework/packages/agent-soul-framework/package.json" ]; then
|
|
71
|
+
echo "$HOME_DIR/.hunqi/agent-soul-framework/packages/agent-soul-framework"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# 3. 中文路径 ~/文档/projects/agent-soul-framework
|
|
76
|
+
if [ -f "$HOME_DIR/文档/projects/agent-soul-framework/packages/agent-soul-framework/package.json" ]; then
|
|
77
|
+
echo "$HOME_DIR/文档/projects/agent-soul-framework/packages/agent-soul-framework"
|
|
78
|
+
return 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# 4. 英文路径 ~/Documents/projects/agent-soul-framework
|
|
82
|
+
if [ -f "$HOME_DIR/Documents/projects/agent-soul-framework/packages/agent-soul-framework/package.json" ]; then
|
|
83
|
+
echo "$HOME_DIR/Documents/projects/agent-soul-framework/packages/agent-soul-framework"
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
return 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
PROJECT_DIR=$(resolve_project_dir) || true
|
|
91
|
+
|
|
92
|
+
CORE_SERVICE="hunqi-core@${USER_NAME}"
|
|
93
|
+
FEISHU_SERVICE="channel-feishu@${USER_NAME}"
|
|
94
|
+
|
|
95
|
+
echo ""
|
|
96
|
+
echo "╔═══════════════════════════════════════════════════╗"
|
|
97
|
+
echo "║ 魂器 systemd 服务安装程序 (核心+频道架构) ║"
|
|
98
|
+
echo "╚═══════════════════════════════════════════════════╝"
|
|
99
|
+
echo ""
|
|
100
|
+
print_status "用户: $USER_NAME"
|
|
101
|
+
print_status "核心: hunqi-core (Agent本体)"
|
|
102
|
+
print_status "频道: channel-feishu (飞书接入)"
|
|
103
|
+
echo ""
|
|
104
|
+
|
|
105
|
+
# 1. 检查系统
|
|
106
|
+
print_status "1/5 检查系统..."
|
|
107
|
+
if ! command -v systemctl &> /dev/null; then
|
|
108
|
+
print_error "systemctl 未找到"
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
print_success "systemd 检查通过"
|
|
112
|
+
|
|
113
|
+
# 2. 检查目录
|
|
114
|
+
print_status "2/5 检查目录..."
|
|
115
|
+
[ ! -d "$HOME_DIR" ] && { print_error "用户目录不存在: $HOME_DIR"; exit 1; }
|
|
116
|
+
|
|
117
|
+
if [ -z "$PROJECT_DIR" ] || [ ! -d "$PROJECT_DIR" ]; then
|
|
118
|
+
print_error "未找到魂器核心框架目录"
|
|
119
|
+
print_status "已搜索的位置:"
|
|
120
|
+
print_status " - $(cd "$SCRIPT_DIR/../../../../packages/agent-soul-framework" && pwd)"
|
|
121
|
+
print_status " - $HOME_DIR/.hunqi/agent-soul-framework/packages/agent-soul-framework"
|
|
122
|
+
print_status " - $HOME_DIR/文档/projects/agent-soul-framework/packages/agent-soul-framework"
|
|
123
|
+
print_status " - $HOME_DIR/Documents/projects/agent-soul-framework/packages/agent-soul-framework"
|
|
124
|
+
echo ""
|
|
125
|
+
|
|
126
|
+
if safe_read "请输入魂器核心框架目录的完整路径: " PROJECT_DIR; then
|
|
127
|
+
[ -z "$PROJECT_DIR" ] && { print_error "目录不能为空"; exit 1; }
|
|
128
|
+
if [ ! -f "$PROJECT_DIR/package.json" ]; then
|
|
129
|
+
print_error "该目录不是有效的魂器核心框架(缺少 package.json)"
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
else
|
|
133
|
+
print_error "非交互式环境无法输入路径,请通过环境变量指定: PROJECT_DIR=/path/to/asf sudo -E $0"
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
print_success "项目目录: $PROJECT_DIR"
|
|
139
|
+
|
|
140
|
+
# 3. 检查依赖
|
|
141
|
+
print_status "3/5 检查依赖..."
|
|
142
|
+
|
|
143
|
+
# 使用 wrapper 检测 opencode(支持 nvm 等非 PATH 安装)
|
|
144
|
+
# 注意:必须以目标用户的 HOME 运行 wrapper,否则 nvm 路径搜不到
|
|
145
|
+
if [ -x "$HOME_DIR/.hunqi/bin/opencode-feishu" ]; then
|
|
146
|
+
if ! HOME="$HOME_DIR" "$HOME_DIR/.hunqi/bin/opencode-feishu" --resolve-opencode &> /dev/null; then
|
|
147
|
+
print_warning "opencode 未找到(hunqi-core 需要它)"
|
|
148
|
+
print_info "安装命令: npm install -g opencode-ai"
|
|
149
|
+
else
|
|
150
|
+
print_success "opencode 已安装"
|
|
151
|
+
fi
|
|
152
|
+
if ! HOME="$HOME_DIR" "$HOME_DIR/.hunqi/bin/opencode-feishu" --resolve-feishu &> /dev/null; then
|
|
153
|
+
print_warning "opencode-feishu 未找到"
|
|
154
|
+
print_info "安装命令: npm install -g @neomei/opencode-feishu"
|
|
155
|
+
else
|
|
156
|
+
print_success "opencode-feishu 已安装"
|
|
157
|
+
fi
|
|
158
|
+
else
|
|
159
|
+
# fallback: 直接检查 PATH
|
|
160
|
+
if ! sudo -u "$USER_NAME" bash -c 'command -v opencode &> /dev/null'; then
|
|
161
|
+
print_warning "opencode 未找到"
|
|
162
|
+
fi
|
|
163
|
+
if ! sudo -u "$USER_NAME" bash -c 'command -v opencode-feishu &> /dev/null'; then
|
|
164
|
+
print_warning "opencode-feishu 未找到"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
print_success "依赖检查完成"
|
|
168
|
+
|
|
169
|
+
# 4. 安装服务文件
|
|
170
|
+
print_status "4/5 安装服务文件..."
|
|
171
|
+
|
|
172
|
+
# 检查源服务文件是否存在
|
|
173
|
+
if [ ! -f "$SCRIPT_DIR/hunqi-core@.service" ]; then
|
|
174
|
+
print_error "服务文件缺失: $SCRIPT_DIR/hunqi-core@.service"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
if [ ! -f "$SCRIPT_DIR/channel-feishu@.service" ]; then
|
|
178
|
+
print_error "服务文件缺失: $SCRIPT_DIR/channel-feishu@.service"
|
|
179
|
+
exit 1
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# 清理旧命名
|
|
183
|
+
rm -f "/etc/systemd/system/hunqi-feishu@.service" 2>/dev/null || true
|
|
184
|
+
|
|
185
|
+
# 安装新服务(将 %h 替换为实际家目录,因为系统级 systemd 中 %h 展开为 /root)
|
|
186
|
+
sed "s|%h|$HOME_DIR|g" "$SCRIPT_DIR/hunqi-core@.service" > "/etc/systemd/system/hunqi-core@.service"
|
|
187
|
+
sed "s|%h|$HOME_DIR|g" "$SCRIPT_DIR/channel-feishu@.service" > "/etc/systemd/system/channel-feishu@.service"
|
|
188
|
+
chmod 644 "/etc/systemd/system/hunqi-core@.service"
|
|
189
|
+
chmod 644 "/etc/systemd/system/channel-feishu@.service"
|
|
190
|
+
|
|
191
|
+
# 验证替换后的路径是否正确
|
|
192
|
+
if grep -q "WorkingDirectory=$HOME_DIR/.hunqi/agent-soul-framework/packages/agent-soul-framework" "/etc/systemd/system/hunqi-core@.service" 2>/dev/null; then
|
|
193
|
+
print_success "服务文件已安装"
|
|
194
|
+
else
|
|
195
|
+
print_error "服务文件路径替换失败"
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# 5. 配置挂起/唤醒钩子
|
|
200
|
+
print_status "5/5 配置挂起/唤醒恢复..."
|
|
201
|
+
|
|
202
|
+
HOOK_DIR="$PROJECT_DIR/connectors/feishu/systemd/sleep-hooks"
|
|
203
|
+
mkdir -p "$HOOK_DIR"
|
|
204
|
+
|
|
205
|
+
cat > "$HOOK_DIR/99-hunqi-resume.sh" << 'EOF'
|
|
206
|
+
#!/bin/bash
|
|
207
|
+
# 魂器挂起/唤醒钩子 - 重启核心和所有频道
|
|
208
|
+
|
|
209
|
+
case "$1" in
|
|
210
|
+
pre)
|
|
211
|
+
logger "[hunqi] 系统即将挂起"
|
|
212
|
+
;;
|
|
213
|
+
post)
|
|
214
|
+
logger "[hunqi] 系统唤醒,重启服务..."
|
|
215
|
+
systemctl restart 'hunqi-core@*'
|
|
216
|
+
sleep 3
|
|
217
|
+
systemctl restart 'channel-feishu@*'
|
|
218
|
+
;;
|
|
219
|
+
esac
|
|
220
|
+
EOF
|
|
221
|
+
chmod +x "$HOOK_DIR/99-hunqi-resume.sh"
|
|
222
|
+
|
|
223
|
+
if safe_read "安装系统级挂起/唤醒钩子?(y/N) " REPLY; then
|
|
224
|
+
echo
|
|
225
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
226
|
+
if [ -d "/lib/systemd/system-sleep" ]; then
|
|
227
|
+
cp "$HOOK_DIR/99-hunqi-resume.sh" "/lib/systemd/system-sleep/"
|
|
228
|
+
chmod +x "/lib/systemd/system-sleep/99-hunqi-resume.sh"
|
|
229
|
+
print_success "系统级钩子已安装"
|
|
230
|
+
else
|
|
231
|
+
print_warning "/lib/systemd/system-sleep 不存在"
|
|
232
|
+
fi
|
|
233
|
+
else
|
|
234
|
+
print_status "跳过系统级钩子"
|
|
235
|
+
fi
|
|
236
|
+
else
|
|
237
|
+
print_status "非交互式环境,跳过系统级钩子安装"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
if systemctl daemon-reload; then
|
|
241
|
+
print_success "systemd 配置已重载"
|
|
242
|
+
else
|
|
243
|
+
print_error "systemctl daemon-reload 失败(systemd 可能未运行)"
|
|
244
|
+
exit 1
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
if systemctl enable "$CORE_SERVICE" && systemctl enable "$FEISHU_SERVICE"; then
|
|
248
|
+
print_success "服务已启用"
|
|
249
|
+
else
|
|
250
|
+
print_error "服务启用失败"
|
|
251
|
+
exit 1
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
echo ""
|
|
255
|
+
echo "╔═══════════════════════════════════════════════════╗"
|
|
256
|
+
echo "║ 安装完成! ║"
|
|
257
|
+
echo "╚═══════════════════════════════════════════════════╝"
|
|
258
|
+
echo ""
|
|
259
|
+
echo "核心服务: hunqi-core@${USER_NAME}"
|
|
260
|
+
echo "飞书频道: channel-feishu@${USER_NAME}"
|
|
261
|
+
echo ""
|
|
262
|
+
echo "管理命令:"
|
|
263
|
+
echo " 启动核心: sudo systemctl start hunqi-core@${USER_NAME}"
|
|
264
|
+
echo " 启动飞书: sudo systemctl start channel-feishu@${USER_NAME}"
|
|
265
|
+
echo " 停止飞书: sudo systemctl stop channel-feishu@${USER_NAME}"
|
|
266
|
+
echo " 停止核心: sudo systemctl stop hunqi-core@${USER_NAME}"
|
|
267
|
+
echo " 查看状态: sudo systemctl status channel-feishu@${USER_NAME}"
|
|
268
|
+
echo " 查看日志: sudo journalctl -u channel-feishu@${USER_NAME} -f"
|
|
269
|
+
echo ""
|
|
270
|
+
echo "架构说明:"
|
|
271
|
+
echo " hunqi-core = Agent本体 (OpenCode + 灵魂)"
|
|
272
|
+
echo " channel-feishu = 飞书频道 (接入核心)"
|
|
273
|
+
echo " 未来扩展: channel-wechat, channel-discord..."
|
|
274
|
+
echo ""
|
|
275
|
+
|
|
276
|
+
if safe_read "立即启动服务?(y/N) " REPLY; then
|
|
277
|
+
echo
|
|
278
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
279
|
+
print_status "停止现有进程..."
|
|
280
|
+
# 只停止目标用户的进程,避免误杀其他用户
|
|
281
|
+
sudo -u "$USER_NAME" pkill -f "opencode-feishu" 2>/dev/null || true
|
|
282
|
+
sudo -u "$USER_NAME" pkill -f "opencode serve" 2>/dev/null || true
|
|
283
|
+
sleep 2
|
|
284
|
+
|
|
285
|
+
print_status "启动核心..."
|
|
286
|
+
if systemctl start "$CORE_SERVICE"; then
|
|
287
|
+
print_success "hunqi-core 已启动"
|
|
288
|
+
else
|
|
289
|
+
print_error "hunqi-core 启动失败,查看日志:"
|
|
290
|
+
echo " sudo journalctl -u $CORE_SERVICE --no-pager -n 20"
|
|
291
|
+
fi
|
|
292
|
+
sleep 5
|
|
293
|
+
|
|
294
|
+
print_status "启动飞书频道..."
|
|
295
|
+
if systemctl start "$FEISHU_SERVICE"; then
|
|
296
|
+
print_success "channel-feishu 已启动"
|
|
297
|
+
else
|
|
298
|
+
print_error "channel-feishu 启动失败,查看日志:"
|
|
299
|
+
echo " sudo journalctl -u $FEISHU_SERVICE --no-pager -n 20"
|
|
300
|
+
fi
|
|
301
|
+
sleep 3
|
|
302
|
+
|
|
303
|
+
if systemctl is-active --quiet "$FEISHU_SERVICE"; then
|
|
304
|
+
print_success "服务运行正常!"
|
|
305
|
+
systemctl status "$FEISHU_SERVICE" --no-pager
|
|
306
|
+
else
|
|
307
|
+
print_error "服务未正常运行,请查看日志排查"
|
|
308
|
+
fi
|
|
309
|
+
fi
|
|
310
|
+
else
|
|
311
|
+
print_status "非交互式环境,跳过自动启动"
|
|
312
|
+
echo ""
|
|
313
|
+
echo "手动启动:"
|
|
314
|
+
echo " sudo systemctl start $CORE_SERVICE"
|
|
315
|
+
echo " sudo systemctl start $FEISHU_SERVICE"
|
|
316
|
+
fi
|
|
Binary file
|
|
Binary file
|