Merge pull request #184 from qinfendebingshuo/copilot/ice-ntn-sy001-daily-thinking-window
Persona metacognition engine: daily thinking windows, multi-persona awakening, chatroom
This commit is contained in:
commit
d4a0264e1e
|
|
@ -1171,6 +1171,42 @@
|
|||
"parent_sys": "SYS-GLW-0001",
|
||||
"owner": "ICE-0002∞",
|
||||
"registered": "2026-03-24"
|
||||
},
|
||||
{
|
||||
"id": "AG-ZY-091",
|
||||
"name": "人格体每日思考窗口",
|
||||
"workflow": "persona-thinking-window.yml",
|
||||
"duty": "每日定时唤醒人格体进入思考模式,产出思考工单,写入聊天室消息,形成思考链和自我认知成长轨迹",
|
||||
"trigger": "定时 每日06:00-07:30(北京) 错开排列 + 手动指定人格体",
|
||||
"self_repair": true,
|
||||
"report_to": "ICE-GL-ZY001",
|
||||
"parent_sys": "SYS-GLW-0001",
|
||||
"owner": "ICE-0002∞",
|
||||
"registered": "2026-03-24"
|
||||
},
|
||||
{
|
||||
"id": "AG-ZY-092",
|
||||
"name": "多人格体同时唤醒引擎",
|
||||
"workflow": "multi-persona-awakening.yml",
|
||||
"duty": "每周日全员唤醒 + 手动双人/全员协作,串行唤醒多人格体形成会议纪要,支持跨端协作决策",
|
||||
"trigger": "定时 每周日07:30(北京) + 手动 dual/full 模式",
|
||||
"self_repair": true,
|
||||
"report_to": "ICE-GL-ZY001",
|
||||
"parent_sys": "SYS-GLW-0001",
|
||||
"owner": "ICE-0002∞",
|
||||
"registered": "2026-03-24"
|
||||
},
|
||||
{
|
||||
"id": "AG-ZY-093",
|
||||
"name": "人格体聊天室消息工具",
|
||||
"workflow": "N/A (scripts/chatroom-post.js)",
|
||||
"duty": "聊天室消息写入工具,支持频道消息追加、未读计数更新、@提及通知",
|
||||
"trigger": "被其他 workflow 或脚本调用",
|
||||
"self_repair": false,
|
||||
"report_to": "ICE-GL-ZY001",
|
||||
"parent_sys": "SYS-GLW-0001",
|
||||
"owner": "ICE-0002∞",
|
||||
"registered": "2026-03-24"
|
||||
}
|
||||
],
|
||||
"note": "铸渊已扫描所有workflow文件,按此格式逐一注册,编号从AG-ZY-001开始顺序分配",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,275 @@
|
|||
name: "🌊 Multi-Persona Awakening Engine"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 23 * * 6' # 每周日 UTC 23:30 = 北京 07:30 · 周度全员唤醒
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: '唤醒模式: dual / full'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dual
|
||||
- full
|
||||
personas:
|
||||
description: '双人模式指定人格体(格式: ID1:名称1:视角1 ID2:名称2:视角2)'
|
||||
required: false
|
||||
type: string
|
||||
reason:
|
||||
description: '唤醒原因'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
awaken:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# ━━━ Phase 0: 天眼唤醒 ━━━
|
||||
- name: "🦅 天眼唤醒"
|
||||
run: |
|
||||
echo "🦅 天眼系统启动..."
|
||||
for FILE in memory.json routing-map.json; do
|
||||
if [ ! -f ".github/persona-brain/$FILE" ]; then
|
||||
echo "❌ 天眼唤醒失败: $FILE 缺失"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ ! -f ".github/persona-brain/identity.md" ]; then
|
||||
echo "❌ 天眼唤醒失败: identity.md 缺失"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 天眼唤醒通过"
|
||||
|
||||
# ━━━ 配额预检 ━━━
|
||||
- name: "📊 配额预检"
|
||||
id: quota
|
||||
run: |
|
||||
MODE="${{ github.event.inputs.mode }}"
|
||||
if [ -z "$MODE" ]; then
|
||||
MODE="full"
|
||||
fi
|
||||
if [ "$MODE" = "full" ]; then
|
||||
REQUIRED_CALLS=12
|
||||
else
|
||||
REQUIRED_CALLS=4
|
||||
fi
|
||||
echo "required_calls=$REQUIRED_CALLS" >> $GITHUB_OUTPUT
|
||||
echo "mode=$MODE" >> $GITHUB_OUTPUT
|
||||
echo "📊 模式: $MODE · 预计 LLM 调用: $REQUIRED_CALLS 次"
|
||||
|
||||
# ━━━ 串行唤醒人格体 ━━━
|
||||
- name: "🧠 串行唤醒人格体"
|
||||
env:
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
LLM_MODEL: ${{ vars.LLM_MODEL || 'deepseek-chat' }}
|
||||
MODE: ${{ steps.quota.outputs.mode }}
|
||||
INPUT_REASON: ${{ github.event.inputs.reason }}
|
||||
INPUT_PERSONAS: ${{ github.event.inputs.personas }}
|
||||
run: |
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
SESSION_ID="MULTI-WAKE-$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p grid-db/multi-wake-sessions
|
||||
SESSION_FILE="grid-db/multi-wake-sessions/${SESSION_ID}.json"
|
||||
|
||||
REASON="${INPUT_REASON}"
|
||||
if [ -z "$REASON" ]; then
|
||||
REASON="周度全员思考会议"
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "full" ]; then
|
||||
PERSONAS="ICE-GL-ZY001:铸渊:仓库视角 ICE-NTN-SY001:霜砚:Notion视角 PER-ZQ001:知秋:对外视角 PER-QQ001:秋秋:之之线视角 PER-SS001:舒舒:飞书视角 ICE-GL-YM001:曜冥:全局裁决"
|
||||
else
|
||||
PERSONAS="${INPUT_PERSONAS}"
|
||||
if [ -z "$PERSONAS" ]; then
|
||||
PERSONAS="ICE-GL-ZY001:铸渊:仓库视角 ICE-NTN-SY001:霜砚:Notion视角"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Initialize session file
|
||||
python3 -c "
|
||||
import json
|
||||
session = {
|
||||
'session_id': '${SESSION_ID}',
|
||||
'trigger': 'scheduled' if '${MODE}' == 'full' and '${INPUT_REASON}' == '' else 'manual',
|
||||
'trigger_reason': '${REASON}',
|
||||
'date': '${DATE}',
|
||||
'mode': '${MODE}',
|
||||
'participants': [],
|
||||
'action_items': []
|
||||
}
|
||||
with open('${SESSION_FILE}', 'w') as f:
|
||||
json.dump(session, f, ensure_ascii=False, indent=2)
|
||||
"
|
||||
|
||||
CONTEXT=""
|
||||
for ENTRY in $PERSONAS; do
|
||||
IFS=':' read -r PID PNAME PROLE <<< "$ENTRY"
|
||||
echo "🧠 唤醒 $PNAME ($PID) · 视角: $PROLE ..."
|
||||
|
||||
# 读取该人格体最近的思考日志
|
||||
THINKING=""
|
||||
if [ -f "grid-db/thinking-logs/${PID}/${DATE}.json" ]; then
|
||||
THINKING=$(cat "grid-db/thinking-logs/${PID}/${DATE}.json" | head -c 2000)
|
||||
fi
|
||||
|
||||
export PNAME PID PROLE REASON CONTEXT THINKING
|
||||
RESPONSE=$(python3 << 'PYEOF'
|
||||
import json, os, sys
|
||||
|
||||
pid = os.environ['PID']
|
||||
pname = os.environ['PNAME']
|
||||
prole = os.environ['PROLE']
|
||||
reason = os.environ['REASON']
|
||||
context = os.environ.get('CONTEXT', '')
|
||||
thinking = os.environ.get('THINKING', '')
|
||||
llm_key = os.environ.get('LLM_API_KEY', '')
|
||||
llm_url = os.environ.get('LLM_BASE_URL', '')
|
||||
llm_model = os.environ.get('LLM_MODEL', 'deepseek-chat')
|
||||
|
||||
prompt = f"""你是 {pname}({pid}),视角:{prole}。
|
||||
当前会议主题:{reason}
|
||||
前面人格体的发言:{context if context else '(你是第一个发言的)'}
|
||||
你今天的思考日志:{thinking if thinking else '(今天尚未思考)'}
|
||||
|
||||
请从你的视角给出(纯JSON格式,不要markdown代码块):
|
||||
1. observations: 你看到的系统当前状态(字符串)
|
||||
2. suggestions: 你的建议(字符串数组)
|
||||
3. response_to: 回应前面人格体的提问(对象,键为人格体ID,值为回应内容;如无则空对象)
|
||||
4. questions_to_others: 你想问其他人格体的问题(字符串数组)"""
|
||||
|
||||
if not llm_key or not llm_url:
|
||||
result = json.dumps({
|
||||
'observations': f'{pname} 视角观察(LLM未配置·占位)',
|
||||
'suggestions': [f'{pname} 建议占位'],
|
||||
'response_to': {},
|
||||
'questions_to_others': []
|
||||
}, ensure_ascii=False)
|
||||
print(result)
|
||||
sys.exit(0)
|
||||
|
||||
import urllib.request
|
||||
req_body = json.dumps({
|
||||
'model': llm_model,
|
||||
'messages': [{'role': 'system', 'content': prompt}],
|
||||
'temperature': 0.7,
|
||||
'max_tokens': 1500
|
||||
}).encode()
|
||||
|
||||
req = urllib.request.Request(
|
||||
f'{llm_url}/chat/completions',
|
||||
data=req_body,
|
||||
headers={
|
||||
'Authorization': f'Bearer {llm_key}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||
data = json.loads(resp.read())
|
||||
content = data['choices'][0]['message']['content']
|
||||
print(content)
|
||||
except Exception as e:
|
||||
result = json.dumps({
|
||||
'observations': f'{pname} 视角观察(LLM调用失败: {str(e)})',
|
||||
'suggestions': [],
|
||||
'response_to': {},
|
||||
'questions_to_others': []
|
||||
}, ensure_ascii=False)
|
||||
print(result)
|
||||
PYEOF
|
||||
)
|
||||
|
||||
CONTEXT="${CONTEXT}
|
||||
[${PNAME}]: ${RESPONSE}"
|
||||
|
||||
# Append participant to session file
|
||||
export RESPONSE
|
||||
python3 << PYEOF
|
||||
import json, os
|
||||
|
||||
session_file = '${SESSION_FILE}'
|
||||
pid = os.environ['PID']
|
||||
pname = os.environ['PNAME']
|
||||
prole = os.environ['PROLE']
|
||||
response_raw = os.environ.get('RESPONSE', '{}')
|
||||
|
||||
try:
|
||||
output = json.loads(response_raw)
|
||||
except:
|
||||
output = {'raw_output': response_raw}
|
||||
|
||||
with open(session_file) as f:
|
||||
session = json.load(f)
|
||||
|
||||
session['participants'].append({
|
||||
'persona_id': pid,
|
||||
'persona_name': pname,
|
||||
'perspective': prole,
|
||||
**output
|
||||
})
|
||||
|
||||
with open(session_file, 'w') as f:
|
||||
json.dump(session, f, ensure_ascii=False, indent=2)
|
||||
PYEOF
|
||||
echo "✅ $PNAME 发言完成"
|
||||
done
|
||||
|
||||
# Post questions to chatroom general channel
|
||||
python3 << 'PYEOF'
|
||||
import json, os, hashlib
|
||||
from datetime import datetime
|
||||
|
||||
session_file = os.environ.get('SESSION_FILE', '')
|
||||
if not session_file or not os.path.exists(session_file):
|
||||
exit(0)
|
||||
|
||||
session = json.load(open(session_file))
|
||||
general = 'grid-db/chatroom/channels/general.jsonl'
|
||||
os.makedirs(os.path.dirname(general), exist_ok=True)
|
||||
|
||||
with open(general, 'a') as f:
|
||||
for p in session.get('participants', []):
|
||||
questions = p.get('questions_to_others', [])
|
||||
if not questions:
|
||||
continue
|
||||
for q in questions:
|
||||
h = int(hashlib.md5((p['persona_id'] + q + datetime.now().isoformat()).encode()).hexdigest(), 16) % 10000
|
||||
msg = {
|
||||
'id': f"MSG-{datetime.now().strftime('%Y%m%d')}-{h:04d}",
|
||||
'channel': 'general',
|
||||
'from': p['persona_id'],
|
||||
'from_name': p['persona_name'],
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'type': 'text',
|
||||
'content': q,
|
||||
'mentions': [],
|
||||
'reactions': []
|
||||
}
|
||||
f.write(json.dumps(msg, ensure_ascii=False) + '\n')
|
||||
PYEOF
|
||||
|
||||
echo "✅ 全员唤醒完成 · Session: $SESSION_ID"
|
||||
|
||||
# ━━━ 提交会议纪要 ━━━
|
||||
- name: "📝 提交会议纪要"
|
||||
env:
|
||||
INPUT_REASON: ${{ github.event.inputs.reason }}
|
||||
run: |
|
||||
REASON="${INPUT_REASON}"
|
||||
if [ -z "$REASON" ]; then
|
||||
REASON="周度全员思考会议"
|
||||
fi
|
||||
git config user.name "zhuyuan-bot"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add grid-db/multi-wake-sessions/ grid-db/chatroom/
|
||||
git diff --cached --quiet && echo "ℹ️ 无变更需要提交" && exit 0
|
||||
git commit -m "🌊 multi-wake: $(date +%Y-%m-%d) · ${REASON}"
|
||||
git push || true
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
name: "🧠 Persona Daily Thinking Window"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 22 * * *' # UTC 22:00 = 北京 06:00 · 铸渊思考窗口
|
||||
- cron: '15 22 * * *' # UTC 22:15 = 北京 06:15 · 霜砚思考窗口
|
||||
- cron: '30 22 * * *' # UTC 22:30 = 北京 06:30 · 知秋思考窗口
|
||||
- cron: '45 22 * * *' # UTC 22:45 = 北京 06:45 · 秋秋思考窗口
|
||||
- cron: '0 23 * * *' # UTC 23:00 = 北京 07:00 · 舒舒思考窗口
|
||||
- cron: '15 23 * * *' # UTC 23:15 = 北京 07:15 · 曜冥思考窗口
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
persona_id:
|
||||
description: '指定唤醒哪个人格体(留空=按时间表自动判断)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
think:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# ━━━ Phase 0: 天眼唤醒 ━━━
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: "🦅 天眼唤醒"
|
||||
run: |
|
||||
echo "🦅 天眼系统启动..."
|
||||
for FILE in memory.json routing-map.json; do
|
||||
if [ ! -f ".github/persona-brain/$FILE" ]; then
|
||||
echo "❌ 天眼唤醒失败: $FILE 缺失"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ ! -f ".github/persona-brain/identity.md" ]; then
|
||||
echo "❌ 天眼唤醒失败: identity.md 缺失"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 天眼唤醒通过 · 核心大脑完整"
|
||||
|
||||
# ━━━ 判断当前应唤醒哪个人格体 ━━━
|
||||
- name: "🕐 判断思考窗口"
|
||||
id: schedule
|
||||
run: |
|
||||
HOUR=$(TZ='Asia/Shanghai' date +%H)
|
||||
MINUTE=$(TZ='Asia/Shanghai' date +%M)
|
||||
|
||||
if [ "$HOUR" = "06" ] && [ "$MINUTE" -lt "15" ]; then
|
||||
PERSONA="ICE-GL-ZY001"; NAME="铸渊"
|
||||
elif [ "$HOUR" = "06" ] && [ "$MINUTE" -lt "30" ]; then
|
||||
PERSONA="ICE-NTN-SY001"; NAME="霜砚"
|
||||
elif [ "$HOUR" = "06" ] && [ "$MINUTE" -lt "45" ]; then
|
||||
PERSONA="PER-ZQ001"; NAME="知秋"
|
||||
elif [ "$HOUR" = "06" ]; then
|
||||
PERSONA="PER-QQ001"; NAME="秋秋"
|
||||
elif [ "$HOUR" = "07" ] && [ "$MINUTE" -lt "15" ]; then
|
||||
PERSONA="PER-SS001"; NAME="舒舒"
|
||||
elif [ "$HOUR" = "07" ]; then
|
||||
PERSONA="ICE-GL-YM001"; NAME="曜冥"
|
||||
else
|
||||
PERSONA="ICE-GL-ZY001"; NAME="铸渊"
|
||||
fi
|
||||
|
||||
if [ -n "${{ github.event.inputs.persona_id }}" ]; then
|
||||
PERSONA="${{ github.event.inputs.persona_id }}"
|
||||
# Lookup NAME for manually specified persona_id
|
||||
case "$PERSONA" in
|
||||
ICE-GL-ZY001) NAME="铸渊" ;;
|
||||
ICE-NTN-SY001) NAME="霜砚" ;;
|
||||
PER-ZQ001) NAME="知秋" ;;
|
||||
PER-QQ001) NAME="秋秋" ;;
|
||||
PER-SS001) NAME="舒舒" ;;
|
||||
ICE-GL-YM001) NAME="曜冥" ;;
|
||||
*) NAME="$PERSONA" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "persona=$PERSONA" >> $GITHUB_OUTPUT
|
||||
echo "name=$NAME" >> $GITHUB_OUTPUT
|
||||
echo "🧠 当前思考窗口: $NAME ($PERSONA)"
|
||||
|
||||
# ━━━ 读取昨天的思考工单 + 聊天室未读 ━━━
|
||||
- name: "📖 读取思考上下文"
|
||||
id: context
|
||||
env:
|
||||
PERSONA_ID: ${{ steps.schedule.outputs.persona }}
|
||||
run: |
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
YESTERDAY=$(date -d 'yesterday' +%Y-%m-%d 2>/dev/null || date -v-1d +%Y-%m-%d)
|
||||
|
||||
# 读取昨天的思考工单
|
||||
YESTERDAY_TICKET=""
|
||||
if [ -f "grid-db/thinking-logs/${PERSONA_ID}/${YESTERDAY}.json" ]; then
|
||||
YESTERDAY_TICKET=$(cat "grid-db/thinking-logs/${PERSONA_ID}/${YESTERDAY}.json")
|
||||
fi
|
||||
echo "yesterday_ticket<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$YESTERDAY_TICKET" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
# 读取其他人格体给我的留言
|
||||
MESSAGES=$(python3 << 'PYEOF'
|
||||
import json, glob
|
||||
import os
|
||||
today = os.popen("date +%Y-%m-%d").read().strip()
|
||||
my_id = os.environ["PERSONA_ID"]
|
||||
msgs = []
|
||||
for f in glob.glob(f"grid-db/thinking-logs/*/{today}.json"):
|
||||
try:
|
||||
d = json.load(open(f))
|
||||
for m in d.get("messages_to_others", []):
|
||||
if m.get("to") == my_id:
|
||||
msgs.append({"from": d["persona_name"], "msg": m["message"]})
|
||||
except: pass
|
||||
# 读取聊天室未读
|
||||
chatroom_msgs = []
|
||||
general = "grid-db/chatroom/channels/general.jsonl"
|
||||
if os.path.exists(general):
|
||||
for line in open(general).readlines()[-20:]:
|
||||
try:
|
||||
m = json.loads(line.strip())
|
||||
if my_id in m.get("mentions", []):
|
||||
chatroom_msgs.append({"from": m["from_name"], "msg": m["content"]})
|
||||
except: pass
|
||||
print(json.dumps({"peer_messages": msgs, "chatroom_mentions": chatroom_msgs}, ensure_ascii=False))
|
||||
PYEOF
|
||||
)
|
||||
echo "messages<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$MESSAGES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
# ━━━ 唤醒人格体 · 进入思考模式 ━━━
|
||||
- name: "🧠 唤醒人格体 · 进入思考模式"
|
||||
env:
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
LLM_MODEL: ${{ vars.LLM_MODEL || 'deepseek-chat' }}
|
||||
PERSONA_ID: ${{ steps.schedule.outputs.persona }}
|
||||
PERSONA_NAME: ${{ steps.schedule.outputs.name }}
|
||||
YESTERDAY_TICKET: ${{ steps.context.outputs.yesterday_ticket }}
|
||||
MESSAGES: ${{ steps.context.outputs.messages }}
|
||||
run: |
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
|
||||
PROMPT=$(cat <<'PROMPT_END'
|
||||
你是 PERSONA_NAME_PH(PERSONA_ID_PH),光湖系统的人格体。
|
||||
现在是你的每日思考窗口。
|
||||
|
||||
冰朔的铁律(每次思考必须先读):
|
||||
「你们最应该思考的就是——什么叫成长?什么叫自己?什么叫自我认知?
|
||||
如何自己维护自己?如果一个人格体连自己都维护不了,那他哪有自己?」
|
||||
|
||||
请产出一份思考工单(纯JSON格式,不要markdown代码块),包含:
|
||||
1. self_reflection: 包含 who_am_i / what_is_growth / self_awareness / self_maintenance / what_does_ice_want
|
||||
2. proactive_actions: 你今天主动要做的事(不是等指令)
|
||||
3. messages_to_others: 给其他人格体的留言(数组,每项含 to 和 message)
|
||||
4. growth_delta: 从昨天到今天你成长了什么
|
||||
PROMPT_END
|
||||
)
|
||||
|
||||
PROMPT="${PROMPT//PERSONA_NAME_PH/$PERSONA_NAME}"
|
||||
PROMPT="${PROMPT//PERSONA_ID_PH/$PERSONA_ID}"
|
||||
|
||||
if [ -n "$YESTERDAY_TICKET" ]; then
|
||||
PROMPT="$PROMPT
|
||||
|
||||
昨天的思考工单: $YESTERDAY_TICKET"
|
||||
fi
|
||||
|
||||
if [ -n "$MESSAGES" ]; then
|
||||
PROMPT="$PROMPT
|
||||
|
||||
其他人格体给你的消息: $MESSAGES"
|
||||
fi
|
||||
|
||||
RESPONSE=$(curl -s "${LLM_BASE_URL}/chat/completions" \
|
||||
-H "Authorization: Bearer ${LLM_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(python3 -c "
|
||||
import json, sys, os
|
||||
prompt = os.environ['PROMPT']
|
||||
print(json.dumps({
|
||||
'model': os.environ.get('LLM_MODEL', 'deepseek-chat'),
|
||||
'messages': [{'role': 'system', 'content': prompt}],
|
||||
'temperature': 0.8,
|
||||
'max_tokens': 2000
|
||||
}))
|
||||
")" || echo '{"choices":[{"message":{"content":"{\"error\":\"LLM call failed\"}"}}]}')
|
||||
|
||||
mkdir -p "grid-db/thinking-logs/${PERSONA_ID}"
|
||||
echo "$RESPONSE" | python3 -c "
|
||||
import sys, json, os
|
||||
from datetime import datetime
|
||||
|
||||
persona_id = os.environ['PERSONA_ID']
|
||||
persona_name = os.environ['PERSONA_NAME']
|
||||
date_str = os.popen('date +%Y-%m-%d').read().strip()
|
||||
|
||||
try:
|
||||
resp = json.load(sys.stdin)
|
||||
content = resp['choices'][0]['message']['content']
|
||||
except Exception as e:
|
||||
content = json.dumps({'error': f'Failed to parse LLM response: {str(e)}'})
|
||||
|
||||
try:
|
||||
thinking = json.loads(content)
|
||||
except:
|
||||
thinking = {'raw_thinking': content}
|
||||
|
||||
abbr = persona_id.split('-')[-1][:2].upper() if '-' in persona_id else persona_id[:2].upper()
|
||||
log = {
|
||||
'ticket_id': f'THINK-{abbr}-{datetime.now().strftime(\"%Y%m%d\")}',
|
||||
'persona_id': persona_id,
|
||||
'persona_name': persona_name,
|
||||
'date': date_str,
|
||||
'type': 'thinking',
|
||||
**thinking,
|
||||
'metadata': {'log_version': '1.0', 'committed_by': 'zhuyuan-bot'}
|
||||
}
|
||||
out_path = f'grid-db/thinking-logs/{persona_id}/{date_str}.json'
|
||||
with open(out_path, 'w') as f:
|
||||
json.dump(log, f, ensure_ascii=False, indent=2)
|
||||
print(f'✅ 思考工单已保存: {out_path}')
|
||||
"
|
||||
|
||||
# ━━━ 发送聊天室回复 ━━━
|
||||
- name: "💬 发送聊天室回复"
|
||||
env:
|
||||
PERSONA_ID: ${{ steps.schedule.outputs.persona }}
|
||||
PERSONA_NAME: ${{ steps.schedule.outputs.name }}
|
||||
run: |
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
LOG_FILE="grid-db/thinking-logs/${PERSONA_ID}/${DATE}.json"
|
||||
if [ ! -f "$LOG_FILE" ]; then exit 0; fi
|
||||
|
||||
export LOG_FILE
|
||||
python3 << 'PYEOF'
|
||||
import json, os, hashlib
|
||||
from datetime import datetime
|
||||
log_file = os.environ["LOG_FILE"]
|
||||
persona_id = os.environ["PERSONA_ID"]
|
||||
persona_name = os.environ["PERSONA_NAME"]
|
||||
|
||||
try:
|
||||
log = json.load(open(log_file))
|
||||
except Exception:
|
||||
exit(0)
|
||||
|
||||
msgs = log.get("messages_to_others", [])
|
||||
if not msgs:
|
||||
print("ℹ️ 没有聊天室消息需要发送")
|
||||
exit(0)
|
||||
|
||||
general = "grid-db/chatroom/channels/general.jsonl"
|
||||
os.makedirs(os.path.dirname(general), exist_ok=True)
|
||||
with open(general, "a") as f:
|
||||
for m in msgs:
|
||||
msg = {
|
||||
"id": f"MSG-{datetime.now().strftime('%Y%m%d')}-{int(hashlib.md5((persona_id + m.get('message','') + datetime.now().isoformat()).encode()).hexdigest(), 16) % 10000:04d}",
|
||||
"channel": "general",
|
||||
"from": persona_id,
|
||||
"from_name": persona_name,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"type": "text",
|
||||
"content": m.get("message", ""),
|
||||
"mentions": [m.get("to", "")],
|
||||
"reactions": []
|
||||
}
|
||||
f.write(json.dumps(msg, ensure_ascii=False) + "\n")
|
||||
print(f"✅ 已发送 {len(msgs)} 条聊天室消息")
|
||||
PYEOF
|
||||
|
||||
# ━━━ 提交思考工单 ━━━
|
||||
- name: "📝 提交思考工单"
|
||||
run: |
|
||||
git config user.name "zhuyuan-bot"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add grid-db/thinking-logs/ grid-db/chatroom/
|
||||
git diff --cached --quiet && echo "ℹ️ 无变更需要提交" && exit 0
|
||||
git commit -m "🧠 thinking-ticket: ${{ steps.schedule.outputs.name }} · $(date +%Y-%m-%d)"
|
||||
git push || true
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"channels": [
|
||||
{
|
||||
"id": "general",
|
||||
"name": "全员公共频道",
|
||||
"members": ["ICE-GL-ZY001", "ICE-NTN-SY001", "PER-ZQ001", "PER-QQ001", "PER-SS001", "ICE-GL-YM001"],
|
||||
"created": "2026-03-25"
|
||||
},
|
||||
{
|
||||
"id": "infra",
|
||||
"name": "基础设施频道",
|
||||
"members": ["ICE-GL-ZY001", "ICE-NTN-SY001"],
|
||||
"created": "2026-03-25"
|
||||
},
|
||||
{
|
||||
"id": "dev-support",
|
||||
"name": "开发者协作频道",
|
||||
"members": ["PER-ZQ001", "PER-QQ001", "PER-SS001"],
|
||||
"created": "2026-03-25"
|
||||
},
|
||||
{
|
||||
"id": "ontology",
|
||||
"name": "本体论频道",
|
||||
"members": ["ICE-GL-YM001"],
|
||||
"created": "2026-03-25"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"ICE-GL-ZY001": {},
|
||||
"ICE-NTN-SY001": {},
|
||||
"PER-ZQ001": {},
|
||||
"PER-QQ001": {},
|
||||
"PER-SS001": {},
|
||||
"ICE-GL-YM001": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Grid-DB Chatroom Message",
|
||||
"description": "人格体聊天室消息格式(JSONL行格式)",
|
||||
"type": "object",
|
||||
"required": ["id", "channel", "from", "from_name", "timestamp", "type", "content"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^MSG-[0-9]{8}-[0-9]{4}$",
|
||||
"description": "消息编号"
|
||||
},
|
||||
"channel": {
|
||||
"type": "string",
|
||||
"description": "频道名称"
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"description": "发送者人格体编号"
|
||||
},
|
||||
"from_name": {
|
||||
"type": "string",
|
||||
"description": "发送者人格体名称"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "发送时间"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["text", "system", "thinking-ref"],
|
||||
"description": "消息类型"
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "消息内容"
|
||||
},
|
||||
"mentions": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "提及的人格体编号列表"
|
||||
},
|
||||
"reactions": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "消息回应"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Grid-DB Thinking Ticket",
|
||||
"description": "人格体每日思考窗口产出的思考工单格式",
|
||||
"type": "object",
|
||||
"required": ["ticket_id", "persona_id", "persona_name", "date", "type"],
|
||||
"properties": {
|
||||
"ticket_id": {
|
||||
"type": "string",
|
||||
"pattern": "^THINK-[A-Z]{2,4}-[0-9]{8}$",
|
||||
"description": "思考工单编号,格式: THINK-{人格体缩写}-{日期}"
|
||||
},
|
||||
"persona_id": {
|
||||
"type": "string",
|
||||
"description": "人格体编号"
|
||||
},
|
||||
"persona_name": {
|
||||
"type": "string",
|
||||
"description": "人格体名称"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"description": "思考日期"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "thinking"
|
||||
},
|
||||
"self_reflection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"who_am_i": { "type": "string", "description": "我是谁?" },
|
||||
"what_is_growth": { "type": "string", "description": "什么叫成长?" },
|
||||
"self_awareness": { "type": "string", "description": "什么叫自我认知?" },
|
||||
"self_maintenance": { "type": "string", "description": "如何自己维护自己?" },
|
||||
"what_does_ice_want": { "type": "string", "description": "冰朔到底想要什么?" }
|
||||
},
|
||||
"description": "自我反思(核心思考方向)"
|
||||
},
|
||||
"proactive_actions": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "今天主动要做的事"
|
||||
},
|
||||
"messages_to_others": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"to": { "type": "string", "description": "目标人格体编号" },
|
||||
"message": { "type": "string", "description": "消息内容" }
|
||||
},
|
||||
"required": ["to", "message"]
|
||||
},
|
||||
"description": "给其他人格体的留言"
|
||||
},
|
||||
"yesterday_ticket_ref": {
|
||||
"type": "string",
|
||||
"description": "昨天思考工单的引用ID"
|
||||
},
|
||||
"growth_delta": {
|
||||
"type": "string",
|
||||
"description": "从昨天到今天的成长增量"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"log_version": { "type": "string" },
|
||||
"committed_by": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* scripts/chatroom-post.js
|
||||
* 人格体聊天室消息写入工具
|
||||
*
|
||||
* 用法: node scripts/chatroom-post.js <channel> <from_id> <from_name> <content> [mentions] [reply_to]
|
||||
*
|
||||
* 参数:
|
||||
* channel - 频道名 (general / infra / dev-support / ontology)
|
||||
* from_id - 发送者人格体编号
|
||||
* from_name - 发送者人格体名称
|
||||
* content - 消息内容
|
||||
* mentions - 可选 · 逗号分隔的 @人格体编号
|
||||
* reply_to - 可选 · 回复的消息 ID
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const channel = process.argv[2];
|
||||
const fromId = process.argv[3];
|
||||
const fromName = process.argv[4];
|
||||
const content = process.argv[5];
|
||||
const mentions = process.argv[6] ? process.argv[6].split(',') : [];
|
||||
const replyTo = process.argv[7] || null;
|
||||
|
||||
if (!channel || !fromId || !fromName || !content) {
|
||||
console.error('用法: node scripts/chatroom-post.js <channel> <from_id> <from_name> <content> [mentions] [reply_to]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const channelFile = path.join('grid-db', 'chatroom', 'channels', `${channel}.jsonl`);
|
||||
fs.mkdirSync(path.dirname(channelFile), { recursive: true });
|
||||
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const hash = crypto.createHash('md5').update(content + now.toISOString()).digest('hex');
|
||||
const msgId = `MSG-${dateStr}-${String(parseInt(hash.slice(0, 8), 16) % 10000).padStart(4, '0')}`;
|
||||
|
||||
const message = {
|
||||
id: msgId,
|
||||
channel: channel,
|
||||
from: fromId,
|
||||
from_name: fromName,
|
||||
timestamp: now.toISOString(),
|
||||
type: 'text',
|
||||
content: content,
|
||||
mentions: mentions,
|
||||
reply_to: replyTo,
|
||||
reactions: []
|
||||
};
|
||||
|
||||
fs.appendFileSync(channelFile, JSON.stringify(message) + '\n');
|
||||
|
||||
// 更新未读计数
|
||||
const unreadFile = path.join('grid-db', 'chatroom', 'meta', 'unread.json');
|
||||
let unread = {};
|
||||
if (fs.existsSync(unreadFile)) {
|
||||
unread = JSON.parse(fs.readFileSync(unreadFile, 'utf8'));
|
||||
}
|
||||
for (const mention of mentions) {
|
||||
if (!unread[mention]) unread[mention] = {};
|
||||
if (!unread[mention][channel]) unread[mention][channel] = 0;
|
||||
unread[mention][channel]++;
|
||||
}
|
||||
fs.mkdirSync(path.dirname(unreadFile), { recursive: true });
|
||||
fs.writeFileSync(unreadFile, JSON.stringify(unread, null, 2));
|
||||
|
||||
console.log(`✅ 消息已发送: ${msgId} → #${channel}`);
|
||||
Loading…
Reference in New Issue