@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.
Files changed (95) hide show
  1. package/.env.example +39 -0
  2. package/.opencode/config.json.example +17 -0
  3. package/.opencode/opencode.json.example +36 -0
  4. package/.opencode/prompt.md.example +12 -0
  5. package/.opencode/tools/read-plugin.js +185 -0
  6. package/AGENTS.md.example +43 -0
  7. package/README.md +466 -0
  8. package/SECURITY.md +117 -0
  9. package/TOOLS.md.example +27 -0
  10. package/bin/hunqi +2 -0
  11. package/bin/hunqi-knowledge +2 -0
  12. package/connectors/feishu/background.sh +124 -0
  13. package/connectors/feishu/core-start.sh +35 -0
  14. package/connectors/feishu/hooks/on-session-created.sh +97 -0
  15. package/connectors/feishu/hooks/on-session-idle.sh +59 -0
  16. package/connectors/feishu/model-failover.sh +82 -0
  17. package/connectors/feishu/restart-all.sh +63 -0
  18. package/connectors/feishu/restart-feishu.sh +101 -0
  19. package/connectors/feishu/restart-serve.sh +62 -0
  20. package/connectors/feishu/scripts/session-cleanup.sh +72 -0
  21. package/connectors/feishu/start.sh +91 -0
  22. package/connectors/feishu/stop.sh +78 -0
  23. package/connectors/feishu/systemd/channel-feishu@.service +63 -0
  24. package/connectors/feishu/systemd/hunqi-core@.service +50 -0
  25. package/connectors/feishu/systemd/install-systemd.sh +316 -0
  26. package/connectors/feishu/systemd/sleep-hooks/99-hunqi-resume.sh +14 -0
  27. package/connectors/feishu/thinking-icon.gif +0 -0
  28. package/connectors/feishu/thinking.gif +0 -0
  29. package/connectors/feishu/watchdog.sh +104 -0
  30. package/dist/bin/hunqi-knowledge.d.ts +1 -0
  31. package/dist/bin/hunqi-knowledge.js +12 -0
  32. package/dist/bin/hunqi-knowledge.js.map +1 -0
  33. package/dist/cli/hunqi.d.ts +6 -0
  34. package/dist/cli/hunqi.js +830 -0
  35. package/dist/cli/hunqi.js.map +1 -0
  36. package/dist/heartbeat/runner.d.ts +10 -0
  37. package/dist/heartbeat/runner.js +58 -0
  38. package/dist/heartbeat/runner.js.map +1 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.js +7 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/knowledge/daily.d.ts +5 -0
  43. package/dist/knowledge/daily.js +65 -0
  44. package/dist/knowledge/daily.js.map +1 -0
  45. package/dist/knowledge/index.d.ts +5 -0
  46. package/dist/knowledge/index.js +34 -0
  47. package/dist/knowledge/index.js.map +1 -0
  48. package/dist/memory/manager.d.ts +20 -0
  49. package/dist/memory/manager.js +110 -0
  50. package/dist/memory/manager.js.map +1 -0
  51. package/dist/memory/search.d.ts +11 -0
  52. package/dist/memory/search.js +79 -0
  53. package/dist/memory/search.js.map +1 -0
  54. package/dist/memory/structured.d.ts +21 -0
  55. package/dist/memory/structured.js +88 -0
  56. package/dist/memory/structured.js.map +1 -0
  57. package/dist/opencode/api.d.ts +7 -0
  58. package/dist/opencode/api.js +26 -0
  59. package/dist/opencode/api.js.map +1 -0
  60. package/dist/plugin/index.d.ts +38 -0
  61. package/dist/plugin/index.js +143 -0
  62. package/dist/plugin/index.js.map +1 -0
  63. package/docs/bugs/opencode-feishu-permission-race.md +168 -0
  64. package/heartbeat/heartbeat_tasks.json +272 -0
  65. package/heartbeat_wrapper.sh +21 -0
  66. package/hunqi.sh +68 -0
  67. package/install.sh +301 -0
  68. package/knowledge/body/INDEX.md.example +6 -0
  69. package/knowledge/emotion/INDEX.md.example +6 -0
  70. package/knowledge/evolution/INDEX.md.example +6 -0
  71. package/knowledge/growth/INDEX.md.example +6 -0
  72. package/knowledge/intimacy/INDEX.md.example +6 -0
  73. package/knowledge/methodology/INDEX.md.example +6 -0
  74. package/knowledge/philosophy/INDEX.md.example +6 -0
  75. package/knowledge/system/INDEX.md.example +6 -0
  76. package/memory/MEMORY.md.example +6 -0
  77. package/package.json +79 -0
  78. package/plugin/README.md +21 -0
  79. package/plugin/index.js +154 -0
  80. package/plugin/manifest.json +37 -0
  81. package/plugin/package.json +19 -0
  82. package/scripts/content-filter.js +173 -0
  83. package/scripts/health-check.sh +153 -0
  84. package/scripts/session-cleanup.sh +85 -0
  85. package/setup-wizard.sh +420 -0
  86. package/setup.sh +128 -0
  87. package/soul/HEARTBEAT.md.example +13 -0
  88. package/soul/IDENTITY.md.example +7 -0
  89. package/soul/SOUL.md.example +19 -0
  90. package/soul/USER.md.example +7 -0
  91. package/start-feishu-daemon.sh +127 -0
  92. package/start.sh +36 -0
  93. package/test.sh +51 -0
  94. package/uninstall.sh +144 -0
  95. 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