@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,124 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
6
|
+
USER_NAME="${SUDO_USER:-$USER}"
|
|
7
|
+
SERVICE_NAME="channel-feishu"
|
|
8
|
+
|
|
9
|
+
# 自动检测 opencode-feishu 启动方式
|
|
10
|
+
resolve_opencode_feishu() {
|
|
11
|
+
if command -v opencode-feishu &>/dev/null; then
|
|
12
|
+
echo "opencode-feishu"
|
|
13
|
+
return 0
|
|
14
|
+
fi
|
|
15
|
+
if command -v npx &>/dev/null; then
|
|
16
|
+
echo "npx opencode-feishu"
|
|
17
|
+
return 0
|
|
18
|
+
fi
|
|
19
|
+
if command -v npm &>/dev/null; then
|
|
20
|
+
local npm_prefix
|
|
21
|
+
npm_prefix=$(npm prefix -g 2>/dev/null)
|
|
22
|
+
if [ -n "$npm_prefix" ] && [ -x "$npm_prefix/bin/opencode-feishu" ]; then
|
|
23
|
+
echo "$npm_prefix/bin/opencode-feishu"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
local src_paths=(
|
|
28
|
+
"/home/$USER/文档/projects/opencode-feishu/bin/opencode-feishu"
|
|
29
|
+
"$PROJECT_DIR/../opencode-feishu/bin/opencode-feishu"
|
|
30
|
+
)
|
|
31
|
+
for p in "${src_paths[@]}"; do
|
|
32
|
+
if [ -f "$p" ]; then
|
|
33
|
+
local node_cmd
|
|
34
|
+
node_cmd=$(command -v node 2>/dev/null || echo "node")
|
|
35
|
+
echo "$node_cmd $p"
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
return 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# 检查是否已安装 systemd 服务
|
|
43
|
+
if [ -f "/etc/systemd/system/${SERVICE_NAME}@${USER_NAME}.service" ]; then
|
|
44
|
+
echo "⚠️ 检测到已安装 systemd 服务: ${SERVICE_NAME}@${USER_NAME}"
|
|
45
|
+
echo ""
|
|
46
|
+
echo "推荐使用 systemd 管理服务(支持开机启动和挂起/唤醒自动恢复):"
|
|
47
|
+
echo " 启动: sudo systemctl start ${SERVICE_NAME}@${USER_NAME}"
|
|
48
|
+
echo " 停止: sudo systemctl stop ${SERVICE_NAME}@${USER_NAME}"
|
|
49
|
+
echo " 状态: sudo systemctl status ${SERVICE_NAME}@${USER_NAME}"
|
|
50
|
+
echo " 日志: sudo journalctl -u ${SERVICE_NAME}@${USER_NAME} -f"
|
|
51
|
+
echo ""
|
|
52
|
+
if [ -t 0 ]; then
|
|
53
|
+
read -p "是否使用 systemd 启动?(Y/n) " -n 1 -r
|
|
54
|
+
echo
|
|
55
|
+
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
56
|
+
sudo systemctl start "${SERVICE_NAME}@${USER_NAME}"
|
|
57
|
+
sleep 2
|
|
58
|
+
sudo systemctl status "${SERVICE_NAME}@${USER_NAME}" --no-pager
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [ -f "$PROJECT_DIR/.env" ]; then
|
|
65
|
+
set -a && source "$PROJECT_DIR/.env" && set +a
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
cd "$PROJECT_DIR"
|
|
69
|
+
|
|
70
|
+
OPENCODE_PORT="${OPENCODE_PORT:-19876}"
|
|
71
|
+
|
|
72
|
+
FEISHU_CMD=$(resolve_opencode_feishu) || {
|
|
73
|
+
echo "❌ opencode-feishu 未安装"
|
|
74
|
+
echo "请安装: npm install -g @neomei/opencode-feishu"
|
|
75
|
+
exit 1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
echo "魂器 飞书连接器(后台模式)"
|
|
79
|
+
echo ""
|
|
80
|
+
|
|
81
|
+
echo "1/3 预检配置..."
|
|
82
|
+
$FEISHU_CMD doctor -c ~/.config/opencode/feishu.json
|
|
83
|
+
|
|
84
|
+
echo ""
|
|
85
|
+
echo "2/3 检查 OpenCode headless 服务器 (port $OPENCODE_PORT)..."
|
|
86
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$OPENCODE_PORT/session 2>/dev/null || true)
|
|
87
|
+
if [ "$HTTP_CODE" != "000" ]; then
|
|
88
|
+
echo " ✅ OpenCode server 已在运行 (HTTP $HTTP_CODE),跳过启动"
|
|
89
|
+
else
|
|
90
|
+
echo " 后台启动 OpenCode headless 服务器..."
|
|
91
|
+
setsid opencode serve --port "$OPENCODE_PORT" > /tmp/opencode-serve.log 2>&1 &
|
|
92
|
+
disown
|
|
93
|
+
sleep 3
|
|
94
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$OPENCODE_PORT/session 2>/dev/null || true)
|
|
95
|
+
if [ "$HTTP_CODE" != "000" ]; then
|
|
96
|
+
SERVE_PID=$(pgrep -f "opencode serve --port $OPENCODE_PORT" | head -1)
|
|
97
|
+
echo " ✅ PID: $SERVE_PID"
|
|
98
|
+
else
|
|
99
|
+
echo " ❌ opencode serve 启动失败,检查端口 $OPENCODE_PORT 是否被占用"
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
sleep 2
|
|
105
|
+
|
|
106
|
+
echo "3/3 后台启动飞书桥接(守护进程模式)..."
|
|
107
|
+
$FEISHU_CMD stop 2>/dev/null || true
|
|
108
|
+
|
|
109
|
+
# 清理旧 session 映射,避免复用旧模型导致 token 不匹配
|
|
110
|
+
FEISHU_SESSIONS="$HOME/.config/opencode/feishu-sessions.json"
|
|
111
|
+
if [ -f "$FEISHU_SESSIONS" ]; then
|
|
112
|
+
rm -f "$FEISHU_SESSIONS"
|
|
113
|
+
echo " 已清理旧 session 映射"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
$FEISHU_CMD start --daemon
|
|
117
|
+
|
|
118
|
+
echo ""
|
|
119
|
+
echo "已启动。状态检查: $FEISHU_CMD status"
|
|
120
|
+
echo "停止: $FEISHU_CMD stop && pkill -f 'opencode serve'"
|
|
121
|
+
echo "日志: $FEISHU_CMD logs -f"
|
|
122
|
+
echo ""
|
|
123
|
+
echo "💡 提示: 如需挂起/唤醒自动恢复和开机启动,建议安装 systemd 服务:"
|
|
124
|
+
echo " sudo ./connectors/feishu/systemd/install-systemd.sh"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# hunqi-core - 魂器核心启动脚本
|
|
4
|
+
# 只启动 OpenCode headless 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
|
+
echo "🎯 魂器核心 (hunqi-core)"
|
|
20
|
+
echo ""
|
|
21
|
+
echo "启动 OpenCode headless 服务器 (port $OPENCODE_PORT)..."
|
|
22
|
+
|
|
23
|
+
# 等待端口释放
|
|
24
|
+
if command -v ss &>/dev/null; then
|
|
25
|
+
for i in $(seq 1 10); do
|
|
26
|
+
if ! ss -tlnp | grep -q ":$OPENCODE_PORT "; then
|
|
27
|
+
break
|
|
28
|
+
fi
|
|
29
|
+
echo " 等待端口 $OPENCODE_PORT 释放... ($i/10)"
|
|
30
|
+
sleep 1
|
|
31
|
+
done
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# 启动 OpenCode serve
|
|
35
|
+
exec opencode serve --port "$OPENCODE_PORT"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# on-session-created.sh — 新建 session 时注入灵魂文件(缓存优化版)
|
|
3
|
+
# 环境变量: HOOK_SESSION_ID, HOOK_OPENCODE_URL
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SESSION_ID="${HOOK_SESSION_ID}"
|
|
8
|
+
OPENCODE_URL="${HOOK_OPENCODE_URL:-http://localhost:19876}"
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
11
|
+
|
|
12
|
+
# 读取 opencode serve 密码
|
|
13
|
+
OPENCODE_PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
|
|
14
|
+
if [ -z "$OPENCODE_PASSWORD" ] && [ -f "$PROJECT_DIR/.env" ]; then
|
|
15
|
+
OPENCODE_PASSWORD=$(grep "^OPENCODE_SERVER_PASSWORD=" "$PROJECT_DIR/.env" | cut -d= -f2- | tr -d '"\'\'')
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
CURL_AUTH=()
|
|
19
|
+
if [ -n "$OPENCODE_PASSWORD" ]; then
|
|
20
|
+
AUTH_TOKEN=$(printf '%s:%s' 'opencode' "$OPENCODE_PASSWORD" | base64 -w 0)
|
|
21
|
+
CURL_AUTH=(-H "Authorization: Basic ${AUTH_TOKEN}")
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# 缓存文件路径
|
|
25
|
+
SOUL_CACHE="/tmp/hunqi-soul-cache.txt"
|
|
26
|
+
SOUL_FILES=(
|
|
27
|
+
"$PROJECT_DIR/soul/IDENTITY.md"
|
|
28
|
+
"$PROJECT_DIR/soul/SOUL.md"
|
|
29
|
+
"$PROJECT_DIR/soul/USER.md"
|
|
30
|
+
"$PROJECT_DIR/soul/AGENTS.md"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# 检查是否需要更新缓存(任一灵魂文件比缓存新)
|
|
34
|
+
needs_update=false
|
|
35
|
+
if [ ! -f "$SOUL_CACHE" ]; then
|
|
36
|
+
needs_update=true
|
|
37
|
+
else
|
|
38
|
+
cache_mtime=$(stat -c %Y "$SOUL_CACHE" 2>/dev/null || stat -f %m "$SOUL_CACHE")
|
|
39
|
+
for f in "${SOUL_FILES[@]}"; do
|
|
40
|
+
if [ -f "$f" ]; then
|
|
41
|
+
file_mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f")
|
|
42
|
+
if [ "$file_mtime" -gt "$cache_mtime" ]; then
|
|
43
|
+
needs_update=true
|
|
44
|
+
break
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
done
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 更新缓存
|
|
51
|
+
if [ "$needs_update" = true ]; then
|
|
52
|
+
SEPARATOR=$'\n\n---\n\n'
|
|
53
|
+
SYSTEM_TEXT=""
|
|
54
|
+
for f in "${SOUL_FILES[@]}"; do
|
|
55
|
+
if [ -f "$f" ]; then
|
|
56
|
+
BASENAME=$(basename "$f")
|
|
57
|
+
CONTENT=$(cat "$f")
|
|
58
|
+
SYSTEM_TEXT="${SYSTEM_TEXT}${SEPARATOR}=== ${BASENAME} ===${SEPARATOR}${CONTENT}"
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
# 去掉开头的分隔符
|
|
62
|
+
SYSTEM_TEXT="${SYSTEM_TEXT#${SEPARATOR}}"
|
|
63
|
+
echo "$SYSTEM_TEXT" > "$SOUL_CACHE"
|
|
64
|
+
echo "[hook] soul cache updated (${#SYSTEM_TEXT} chars)"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# 从缓存读取
|
|
68
|
+
SYSTEM_TEXT=$(cat "$SOUL_CACHE")
|
|
69
|
+
|
|
70
|
+
if [ -z "$SYSTEM_TEXT" ]; then
|
|
71
|
+
echo "[hook:onSessionCreated] warning: soul cache is empty"
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
echo "[hook:onSessionCreated] injecting soul for session ${SESSION_ID}..."
|
|
76
|
+
|
|
77
|
+
# 使用 jq 或纯 bash 构造 JSON,避免 python3 进程开销
|
|
78
|
+
if command -v jq &>/dev/null; then
|
|
79
|
+
JSON_PAYLOAD=$(jq -n --arg system "$SYSTEM_TEXT" '{system: $system, noReply: true, parts: []}')
|
|
80
|
+
else
|
|
81
|
+
# 纯 bash fallback(简单转义)
|
|
82
|
+
JSON_PAYLOAD="{\"system\":$(printf '%s' "$SYSTEM_TEXT" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'),\"noReply\":true,\"parts\":[]}"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${OPENCODE_URL}/session/${SESSION_ID}/message" \
|
|
86
|
+
-H "Content-Type: application/json" \
|
|
87
|
+
"${CURL_AUTH[@]}" \
|
|
88
|
+
-d "$JSON_PAYLOAD" 2>&1)
|
|
89
|
+
|
|
90
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
|
91
|
+
|
|
92
|
+
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
|
93
|
+
echo "[hook:onSessionCreated] soul injected (${#SYSTEM_TEXT} chars) ✅"
|
|
94
|
+
else
|
|
95
|
+
echo "[hook:onSessionCreated] soul injection failed: HTTP $HTTP_CODE" >&2
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# on-session-idle.sh — session idle 时(上下文压缩后)重新注入灵魂(缓存优化版)
|
|
3
|
+
# 环境变量: HOOK_SESSION_ID, HOOK_OPENCODE_URL
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SESSION_ID="${HOOK_SESSION_ID}"
|
|
8
|
+
OPENCODE_URL="${HOOK_OPENCODE_URL:-http://localhost:19876}"
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
11
|
+
|
|
12
|
+
# 读取 opencode serve 密码
|
|
13
|
+
OPENCODE_PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
|
|
14
|
+
if [ -z "$OPENCODE_PASSWORD" ] && [ -f "$PROJECT_DIR/.env" ]; then
|
|
15
|
+
OPENCODE_PASSWORD=$(grep "^OPENCODE_SERVER_PASSWORD=" "$PROJECT_DIR/.env" | cut -d= -f2- | tr -d '"\'\'')
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
CURL_AUTH=()
|
|
19
|
+
if [ -n "$OPENCODE_PASSWORD" ]; then
|
|
20
|
+
AUTH_TOKEN=$(printf '%s:%s' 'opencode' "$OPENCODE_PASSWORD" | base64 -w 0)
|
|
21
|
+
CURL_AUTH=(-H "Authorization: Basic ${AUTH_TOKEN}")
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# 直接使用 session-created 生成的缓存
|
|
25
|
+
SOUL_CACHE="/tmp/hunqi-soul-cache.txt"
|
|
26
|
+
|
|
27
|
+
if [ ! -f "$SOUL_CACHE" ]; then
|
|
28
|
+
echo "[hook:onSessionIdle] warning: soul cache not found, skipping"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
SYSTEM_TEXT=$(cat "$SOUL_CACHE")
|
|
33
|
+
|
|
34
|
+
if [ -z "$SYSTEM_TEXT" ]; then
|
|
35
|
+
echo "[hook:onSessionIdle] warning: soul cache is empty, skipping"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "[hook:onSessionIdle] re-injecting soul for session ${SESSION_ID}..."
|
|
40
|
+
|
|
41
|
+
# 使用 jq 或纯 bash 构造 JSON
|
|
42
|
+
if command -v jq &>/dev/null; then
|
|
43
|
+
JSON_PAYLOAD=$(jq -n --arg system "$SYSTEM_TEXT" '{system: $system, noReply: true, parts: []}')
|
|
44
|
+
else
|
|
45
|
+
JSON_PAYLOAD="{\"system\":$(printf '%s' "$SYSTEM_TEXT" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'),\"noReply\":true,\"parts\":[]}"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${OPENCODE_URL}/session/${SESSION_ID}/message" \
|
|
49
|
+
-H "Content-Type: application/json" \
|
|
50
|
+
"${CURL_AUTH[@]}" \
|
|
51
|
+
-d "$JSON_PAYLOAD" 2>&1)
|
|
52
|
+
|
|
53
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
|
54
|
+
|
|
55
|
+
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
|
56
|
+
echo "[hook:onSessionIdle] soul re-injected (${#SYSTEM_TEXT} chars) ✅"
|
|
57
|
+
else
|
|
58
|
+
echo "[hook:onSessionIdle] soul re-injection failed: HTTP $HTTP_CODE" >&2
|
|
59
|
+
fi
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# 模型降级/恢复管理脚本
|
|
4
|
+
#
|
|
5
|
+
# 用法:
|
|
6
|
+
# ./model-failover.sh degrade <fallback-model> # 降级到备用模型
|
|
7
|
+
# ./model-failover.sh restore # 恢复主模型
|
|
8
|
+
# ./model-failover.sh status # 查看当前状态
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
STATE_FILE="${HOME}/.config/opencode/model-failover.json"
|
|
15
|
+
PRIMARY_MODEL="deepseek/deepseek-v4-pro"
|
|
16
|
+
OPENCODE_PORT="${OPENCODE_PORT:-19876}"
|
|
17
|
+
|
|
18
|
+
mkdir -p "$(dirname "$STATE_FILE")"
|
|
19
|
+
|
|
20
|
+
# 读取当前降级状态
|
|
21
|
+
read_state() {
|
|
22
|
+
if [ -f "$STATE_FILE" ]; then
|
|
23
|
+
cat "$STATE_FILE"
|
|
24
|
+
else
|
|
25
|
+
echo '{"degraded":false,"model":"","since":0}'
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# 降级:切换模型
|
|
30
|
+
degrade() {
|
|
31
|
+
local fallback_model="${1:-}"
|
|
32
|
+
if [ -z "$fallback_model" ]; then
|
|
33
|
+
echo "Usage: $0 degrade <fallback-model>" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
python3 -c "import json,sys; json.dump({'degraded':True,'model':sys.argv[1],'since':int(sys.argv[2])}, open(sys.argv[3],'w'))" "$fallback_model" "$(date +%s)" "$STATE_FILE"
|
|
38
|
+
echo "Model degraded to: ${fallback_model}"
|
|
39
|
+
|
|
40
|
+
# 尝试重启 opencode serve(只杀指定端口的进程)
|
|
41
|
+
SERVE_PID=$(ss -tlnp 2>/dev/null | grep ":${OPENCODE_PORT} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
42
|
+
if [ -n "$SERVE_PID" ]; then
|
|
43
|
+
echo "Restarting opencode serve (PID: $SERVE_PID) with fallback model..."
|
|
44
|
+
kill "$SERVE_PID" 2>/dev/null || true
|
|
45
|
+
sleep 2
|
|
46
|
+
opencode serve --model "${fallback_model}" --port "${OPENCODE_PORT}" &
|
|
47
|
+
echo "opencode serve restarted with ${fallback_model}"
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# 恢复:尝试主模型
|
|
52
|
+
restore() {
|
|
53
|
+
echo "Attempting to restore primary model: ${PRIMARY_MODEL}"
|
|
54
|
+
|
|
55
|
+
# 先标记为恢复中
|
|
56
|
+
python3 -c "import json,sys; json.dump({'degraded':False,'model':'','since':0}, open(sys.argv[1],'w'))" "$STATE_FILE"
|
|
57
|
+
|
|
58
|
+
# 尝试重启 opencode serve 用主模型(只杀指定端口的进程)
|
|
59
|
+
SERVE_PID=$(ss -tlnp 2>/dev/null | grep ":${OPENCODE_PORT} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
60
|
+
if [ -n "$SERVE_PID" ]; then
|
|
61
|
+
echo "Restarting opencode serve (PID: $SERVE_PID) with primary model..."
|
|
62
|
+
kill "$SERVE_PID" 2>/dev/null || true
|
|
63
|
+
sleep 2
|
|
64
|
+
opencode serve --model "${PRIMARY_MODEL}" --port "${OPENCODE_PORT}" &
|
|
65
|
+
echo "opencode serve restarted with primary model ${PRIMARY_MODEL}"
|
|
66
|
+
fi
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# 查看状态
|
|
70
|
+
status() {
|
|
71
|
+
read_state
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case "${1:-status}" in
|
|
75
|
+
degrade) degrade "$2" ;;
|
|
76
|
+
restore) restore ;;
|
|
77
|
+
status) status ;;
|
|
78
|
+
*)
|
|
79
|
+
echo "Usage: $0 {degrade <model>|restore|status}" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# restart-all.sh — 重启全部服务(opencode serve + opencode-feishu)
|
|
4
|
+
# 顺序:先重启 serve(保留连接),再重启 feishu(先起后杀)
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
PORT=${1:-19876}
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
11
|
+
|
|
12
|
+
echo "🔄 重启全部服务..."
|
|
13
|
+
echo ""
|
|
14
|
+
|
|
15
|
+
# ── 1. 重启 opencode serve ──
|
|
16
|
+
echo "1/2 重启 OpenCode serve..."
|
|
17
|
+
"$PROJECT_DIR/connectors/feishu/restart-serve.sh" "$PORT"
|
|
18
|
+
sleep 2
|
|
19
|
+
|
|
20
|
+
# ── 2. 重启 opencode-feishu(使用先起后杀策略)──
|
|
21
|
+
echo ""
|
|
22
|
+
echo "2/2 重启飞书连接器..."
|
|
23
|
+
"$PROJECT_DIR/connectors/feishu/restart-feishu.sh"
|
|
24
|
+
|
|
25
|
+
echo ""
|
|
26
|
+
echo "✅ 全部服务重启完成"
|
|
27
|
+
echo " 检查状态: opencode-feishu status"
|
|
28
|
+
|
|
29
|
+
# ── 3. 发送完成通知到飞书 ──
|
|
30
|
+
if [ -n "$FEISHU_NOTIFY_CHAT_ID" ]; then
|
|
31
|
+
FEISHU_CONFIG="$HOME/.config/opencode/feishu.json"
|
|
32
|
+
if [ -f "$FEISHU_CONFIG" ]; then
|
|
33
|
+
APP_ID=$(node -e "console.log(require('$FEISHU_CONFIG').appId || '')" 2>/dev/null)
|
|
34
|
+
APP_SECRET=$(node -e "console.log(require('$FEISHU_CONFIG').appSecret || '')" 2>/dev/null)
|
|
35
|
+
|
|
36
|
+
if [ -n "$APP_ID" ] && [ -n "$APP_SECRET" ]; then
|
|
37
|
+
# 获取 tenant_access_token
|
|
38
|
+
TOKEN_RESPONSE=$(curl -s -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \
|
|
39
|
+
-H "Content-Type: application/json" \
|
|
40
|
+
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" 2>/dev/null)
|
|
41
|
+
|
|
42
|
+
TOKEN=$(echo "$TOKEN_RESPONSE" | node -e "
|
|
43
|
+
let d = '';
|
|
44
|
+
process.stdin.on('data', c => d += c);
|
|
45
|
+
process.stdin.on('end', () => {
|
|
46
|
+
try {
|
|
47
|
+
const data = JSON.parse(d);
|
|
48
|
+
console.log(data.tenant_access_token || '');
|
|
49
|
+
} catch {
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
" 2>/dev/null)
|
|
54
|
+
|
|
55
|
+
if [ -n "$TOKEN" ]; then
|
|
56
|
+
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
|
|
57
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
58
|
+
-H "Content-Type: application/json" \
|
|
59
|
+
-d "{\"receive_id\":\"$FEISHU_NOTIFY_CHAT_ID\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"✅ 全部服务重启完成,Agent已经准备好继续陪你聊天啦~\\\"}\"}" > /dev/null 2>&1 || true
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# restart-feishu.sh — 重启 opencode-feishu(飞书连接器)
|
|
4
|
+
# 策略:先起后杀 — 先启动新实例,再停止旧实例,避免飞书连接永久中断
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
PORT=${OPENCODE_PORT:-19876}
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
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
|
+
FEISHU_CMD=$(resolve_opencode_feishu) || {
|
|
46
|
+
echo "❌ opencode-feishu 未安装" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
echo "🔄 准备重启 opencode-feishu..."
|
|
51
|
+
|
|
52
|
+
# ── 1. 获取旧实例 PID(排除当前 shell 和 grep)──
|
|
53
|
+
# 匹配 opencode-feishu start 或 cli.js start start(旧版启动方式)
|
|
54
|
+
OLD_PIDS=$(pgrep -f "opencode-feishu start|cli\.js start start" | grep -v "$$" || true)
|
|
55
|
+
OLD_PID=$(echo "$OLD_PIDS" | head -1)
|
|
56
|
+
|
|
57
|
+
# ── 2. 优雅关闭旧实例 ──
|
|
58
|
+
if [ -n "$OLD_PID" ]; then
|
|
59
|
+
echo "🛑 正在优雅关闭旧实例 (PID: $OLD_PID)..."
|
|
60
|
+
kill "$OLD_PID" 2>/dev/null || true
|
|
61
|
+
|
|
62
|
+
# 等待旧实例退出(最多 5 秒)
|
|
63
|
+
WAIT=0
|
|
64
|
+
while [ $WAIT -lt 5 ] && kill -0 "$OLD_PID" 2>/dev/null; do
|
|
65
|
+
sleep 1
|
|
66
|
+
WAIT=$((WAIT + 1))
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
# 如果还在,强制终止
|
|
70
|
+
if kill -0 "$OLD_PID" 2>/dev/null; then
|
|
71
|
+
echo " 旧实例未响应,强制终止..."
|
|
72
|
+
kill -9 "$OLD_PID" 2>/dev/null || true
|
|
73
|
+
sleep 1
|
|
74
|
+
fi
|
|
75
|
+
echo " 旧实例已关闭"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# ── 3. 确保 opencode serve 还在运行 ──
|
|
79
|
+
if ! curl -s http://localhost:$PORT/session >/dev/null 2>&1; then
|
|
80
|
+
echo "⚠️ OpenCode serve 未运行,尝试启动..."
|
|
81
|
+
nohup opencode serve --port "$PORT" > /tmp/opencode-serve.log 2>&1 &
|
|
82
|
+
sleep 3
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# ── 4. 启动新实例 ──
|
|
86
|
+
echo "🚀 启动新的 opencode-feishu..."
|
|
87
|
+
cd "$PROJECT_DIR"
|
|
88
|
+
nohup $FEISHU_CMD start --daemon > /tmp/opencode-feishu-restart.log 2>&1 &
|
|
89
|
+
NEW_PID=$!
|
|
90
|
+
|
|
91
|
+
# 等待新进程就绪
|
|
92
|
+
sleep 3
|
|
93
|
+
if kill -0 "$NEW_PID" 2>/dev/null; then
|
|
94
|
+
echo "✅ 新 opencode-feishu 已启动 (PID: $NEW_PID)"
|
|
95
|
+
else
|
|
96
|
+
echo "❌ 新 opencode-feishu 启动失败,查看日志: /tmp/opencode-feishu-restart.log" >&2
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo "✅ opencode-feishu 重启完成"
|
|
101
|
+
echo " 日志: tail -f /tmp/opencode-feishu-restart.log"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# restart-serve.sh — 重启 opencode serve(OpenCode 核心服务)
|
|
4
|
+
# Usage: ./restart-serve.sh [port]
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
PORT=${1:-19876}
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
11
|
+
|
|
12
|
+
echo "🔄 准备重启 opencode serve (port $PORT)..."
|
|
13
|
+
|
|
14
|
+
# ── 1. 停止现有的 opencode serve 进程 ──
|
|
15
|
+
# 使用 pgrep 精确匹配,避免误杀
|
|
16
|
+
OLD_PIDS=$(pgrep -f "opencode serve.*--port $PORT" || true)
|
|
17
|
+
|
|
18
|
+
if [ -n "$OLD_PIDS" ]; then
|
|
19
|
+
echo " 发现现有进程: $OLD_PIDS"
|
|
20
|
+
echo "$OLD_PIDS" | while read -r pid; do
|
|
21
|
+
kill "$pid" 2>/dev/null || true
|
|
22
|
+
done
|
|
23
|
+
sleep 1
|
|
24
|
+
|
|
25
|
+
# 确认旧进程已停止
|
|
26
|
+
REMAINING=$(pgrep -f "opencode serve.*--port $PORT" || true)
|
|
27
|
+
if [ -n "$REMAINING" ]; then
|
|
28
|
+
echo " 强制停止残留进程: $REMAINING"
|
|
29
|
+
echo "$REMAINING" | while read -r pid; do
|
|
30
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
31
|
+
done
|
|
32
|
+
sleep 1
|
|
33
|
+
fi
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# ── 2. 启动新的 opencode serve ──
|
|
37
|
+
cd "$PROJECT_DIR"
|
|
38
|
+
nohup opencode serve --port "$PORT" > /tmp/opencode-serve.log 2>&1 &
|
|
39
|
+
NEW_PID=$!
|
|
40
|
+
|
|
41
|
+
echo " 新进程 PID: $NEW_PID"
|
|
42
|
+
|
|
43
|
+
# ── 3. 等待并验证 ──
|
|
44
|
+
sleep 2
|
|
45
|
+
RETRIES=5
|
|
46
|
+
while [ $RETRIES -gt 0 ]; do
|
|
47
|
+
if curl -s http://localhost:$PORT/session >/dev/null 2>&1; then
|
|
48
|
+
echo "✅ opencode serve 已就绪 (PID: $NEW_PID, port: $PORT)"
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
sleep 1
|
|
52
|
+
RETRIES=$((RETRIES - 1))
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
# 如果到这里还没就绪,检查进程是否还在
|
|
56
|
+
if kill -0 "$NEW_PID" 2>/dev/null; then
|
|
57
|
+
echo "⚠️ opencode serve 进程存在但未响应,请检查日志: /tmp/opencode-serve.log"
|
|
58
|
+
exit 1
|
|
59
|
+
else
|
|
60
|
+
echo "❌ opencode serve 启动失败,查看日志: /tmp/opencode-serve.log"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|