diff --git a/.github/DISCUSSION_TEMPLATE/syslog-submit.yml b/.github/DISCUSSION_TEMPLATE/syslog-submit.yml
new file mode 100644
index 00000000..a0baaa94
--- /dev/null
+++ b/.github/DISCUSSION_TEMPLATE/syslog-submit.yml
@@ -0,0 +1,51 @@
+title: "BC-XXX-XXXïœSYSLOG"
+labels: []
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ## ð¡ SYSLOG èªå©æäº€ / 广ææé®
+
+ > åå®äºïŒææ¥å¿ç²èŽŽè¿å»ïŒå¡«äžé®ç®±ïŒç¹æäº€ãæ°å¹¿æäŒåå°äœ é®ç®±éã
+ >
+ > æé®é¢ïŒæé®é¢ç²èŽŽè¿å»ïŒå¡«äžé®ç®±ïŒç¹æäº€ãè§£çäŒåå°äœ é®ç®±éã
+
+ **æ 颿 ŒåŒ**ïŒ`BC-XXX-XXXïœSYSLOG` æ `BC-XXX-XXXïœæé®`
+
+ - type: input
+ id: broadcast_id
+ attributes:
+ label: "广æçŒå·"
+ description: "åäžäœ åœå广æççŒå·ïŒäŸåŠ BC-M22-009-AW"
+ placeholder: "BC-M22-009-AW"
+ validations:
+ required: true
+
+ - type: dropdown
+ id: submit_type
+ attributes:
+ label: "ç±»å"
+ description: "éæ©æäº€ç±»å"
+ options:
+ - SYSLOG
+ - æé®
+ validations:
+ required: true
+
+ - type: input
+ id: email
+ attributes:
+ label: "äœ çé®ç®±"
+ description: "çšäºæ¥æ¶æ°å¹¿ææé®é¢è§£ç"
+ placeholder: "your@email.com"
+ validations:
+ required: true
+
+ - type: textarea
+ id: content
+ attributes:
+ label: "å
容"
+ description: "ç²èŽŽäœ ç SYSLOG å
šæ æ é®é¢æè¿°"
+ placeholder: "åšè¿éç²èŽŽäœ ç SYSLOG æé®é¢..."
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/syslog-submit.yml b/.github/ISSUE_TEMPLATE/syslog-submit.yml
new file mode 100644
index 00000000..80a87d27
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/syslog-submit.yml
@@ -0,0 +1,54 @@
+name: "ð¡ æäº€ç³»ç»æ¥å¿ / æé®"
+description: "åå®äºïŒæäº€äœ ç SYSLOGãæé®é¢ïŒåšè¿éæé®ã"
+title: "BC-XXX-XXXïœSYSLOG"
+labels: ["syslog", "pending"]
+assignees: []
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ## ð¡ SYSLOG èªå©æäº€ / 广ææé®
+
+ > åå®äºïŒææ¥å¿ç²èŽŽè¿å»ïŒå¡«äžé®ç®±ïŒç¹æäº€ãæ°å¹¿æäŒåå°äœ é®ç®±éã
+ >
+ > æé®é¢ïŒæé®é¢ç²èŽŽè¿å»ïŒå¡«äžé®ç®±ïŒç¹æäº€ãè§£çäŒåå°äœ é®ç®±éã
+
+ **æ 颿 ŒåŒ**ïŒ`BC-XXX-XXXïœSYSLOG` æ `BC-XXX-XXXïœæé®`
+
+ - type: input
+ id: broadcast_id
+ attributes:
+ label: "广æçŒå·"
+ description: "åäžäœ åœå广æççŒå·ïŒäŸåŠ BC-M22-009-AW"
+ placeholder: "BC-M22-009-AW"
+ validations:
+ required: true
+
+ - type: dropdown
+ id: submit_type
+ attributes:
+ label: "ç±»å"
+ description: "éæ©æäº€ç±»å"
+ options:
+ - SYSLOG
+ - æé®
+ validations:
+ required: true
+
+ - type: input
+ id: email
+ attributes:
+ label: "äœ çé®ç®±"
+ description: "çšäºæ¥æ¶æ°å¹¿ææé®é¢è§£ç"
+ placeholder: "your@email.com"
+ validations:
+ required: true
+
+ - type: textarea
+ id: content
+ attributes:
+ label: "å
容"
+ description: "ç²èŽŽäœ ç SYSLOG å
šæ æ é®é¢æè¿°"
+ placeholder: "åšè¿éç²èŽŽäœ ç SYSLOG æé®é¢..."
+ validations:
+ required: true
diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index abbec8c8..eded71d9 100644
--- a/.github/brain/bingshuo-issues-index.json
+++ b/.github/brain/bingshuo-issues-index.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "å°æäž»æ§é®é¢çŽ¢åŒåº â è®°åœå·²ç¥é®é¢ãæ ¹å äžææ¥è·¯ç±",
- "updated_at": "2026-03-13T02:05:55.611Z",
+ "updated_at": "2026-03-13T06:52:24.833Z",
"issues": [
{
"id": "BS-001",
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index 443bfaee..2f10a3cf 100644
--- a/.github/brain/bingshuo-master-brain.md
+++ b/.github/brain/bingshuo-master-brain.md
@@ -1,7 +1,7 @@
# å°æäž»æ§ç¥ç»ç³»ç» · æ žå¿äž»æ§å€§è v1.0
> æ¬æä»¶äžºå°æäž»æ§ç¥ç»ç³»ç»çæ»æ§èæä»¶ã
-> æåçŒè¯æ¶éŽïŒ2026-03-13T02:05:55.612Z
+> æåçŒè¯æ¶éŽïŒ2026-03-13T06:52:24.834Z
---
@@ -56,7 +56,7 @@
### ä»åºç»è®¡
- åèœæš¡åïŒ10 䞪
-- WorkflowïŒ35 䞪
+- WorkflowïŒ40 䞪
---
@@ -85,7 +85,7 @@
> æ¬åºåç± master-brain-compiler èªåšçŒè¯ã
-- **çŒè¯æ¶éŽ**ïŒ2026-03-13T02:05:55.612Z
+- **çŒè¯æ¶éŽ**ïŒ2026-03-13T06:52:24.834Z
- **èæä»¶è§åçæ¬**ïŒv3.0
- **èæä»¶å®æŽæ§**ïŒâ
宿Ž
@@ -107,7 +107,7 @@
|--------|------|------|
| ð¡ brain_consistency | yellow | äž»ä»åºèæä»¶å®æŽïŒäœäž persona-studio èæä»¶çåæ¥ç¶æåŸ
éªè¯ |
| ð¢ deployment_health | green | deploy-to-server.yml äž deploy-pages.yml åååš |
-| ð¢ workflow_health | green | 35 䞪 workflow 已泚å |
+| ð¢ workflow_health | green | 40 䞪 workflow 已泚å |
| ð¡ routing_health | yellow | HLI æ¥å£èŠçç 33.3%ïŒ7/21ïŒ |
| ð¢ docs_entry_health | green | docs/index.html ååš |
| ð¡ persona_studio_health | yellow | ååç«¯ç»æååšïŒç«¯å°ç«¯å¯¹è¯éŸè·¯åŸ
éªè¯ |
diff --git a/.github/brain/bingshuo-system-health.json b/.github/brain/bingshuo-system-health.json
index 56abef67..a1ee0628 100644
--- a/.github/brain/bingshuo-system-health.json
+++ b/.github/brain/bingshuo-system-health.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "å°æäž»æ§ç³»ç»å¥åº·ç¶æ",
- "updated_at": "2026-03-13T02:05:55.611Z",
+ "updated_at": "2026-03-13T06:52:24.833Z",
"health": {
"brain_consistency": {
"status": "yellow",
@@ -13,7 +13,7 @@
},
"workflow_health": {
"status": "green",
- "detail": "35 䞪 workflow 已泚å"
+ "detail": "40 䞪 workflow 已泚å"
},
"routing_health": {
"status": "yellow",
diff --git a/.github/brain/memory.json b/.github/brain/memory.json
index 378b77c4..e07d7cd6 100644
--- a/.github/brain/memory.json
+++ b/.github/brain/memory.json
@@ -1,7 +1,7 @@
{
"identity": "éžæžïŒZhùyuÄnïŒÂ· GitHub 代ç 宿€äººæ Œäœ",
"rules_version": "v3.0",
- "last_updated": "2026-03-12T08:55:54.205Z",
+ "last_updated": "2026-03-13T08:53:32.764Z",
"wake_protocol_version": "v3.0",
"brain_version": "v3.0",
"architecture": {
@@ -67,6 +67,18 @@
}
},
"events": [
+ {
+ "date": "2026-03-13",
+ "type": "brain_restore",
+ "description": "éžæžæ žå¿å€§èæ¢å€ · Persona Studio éžæžä»£çåŒåå·¥äœæµäžçº¿ · ä¿®å€ãæèŠåŒåãåèœæ æ³è§Šå代çåŒå · Agent 5æ¥èªåšåå·¥äœæµïŒéæ±åæâæ¶æè§åâ代ç çæâé¢è§éšçœ²â宿éç¥ïŒ · 䜿çšçšæ·API Keyé©±åš Â· æ¯æGUESTæš¡åŒ Â· WebSocketæ¶åºä¿®å€ · é误åéŠå¢åŒº",
+ "by": "éžæžïŒå°ææä»€ïŒ"
+ },
+ {
+ "date": "2026-03-13",
+ "type": "psp_inspection",
+ "description": "éžæž PSP å·¡æ£éè¿ Â· å
šé𿣿¥é¡¹ â
",
+ "by": "éžæžPSPå·¡æ£"
+ },
{
"date": "2026-03-12",
"type": "psp_inspection",
@@ -173,6 +185,14 @@
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22993977564"
+ },
+ {
+ "timestamp": "2026-03-13T08:53:32.764Z",
+ "type": "daily_check",
+ "result": "passed",
+ "actor": "qinfendebingshuo",
+ "ref": "refs/heads/main",
+ "run_id": "23043373769"
}
]
}
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index 45ae82d7..82975a73 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,20 +1,20 @@
{
"description": "éžæžåŸä¹ŠéŠç®åœ · Library Catalog for éžæž (ZhùyuÄn)",
"version": "2.0",
- "generated_at": "2026-03-13T02:07:22.787Z",
+ "generated_at": "2026-03-13T06:52:39.931Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
"zones": 13,
"total_modules": 10,
- "total_workflows": 35,
- "total_scripts": 32,
+ "total_workflows": 40,
+ "total_scripts": 39,
"total_dev_nodes": 8,
"hli_interfaces": 21,
"hli_implemented": 7,
"hli_coverage_pct": "33%",
"last_ci_run": "2026-03-05T16:07:24.070Z",
- "memory_last_updated": "2026-03-12T08:55:54.205Z"
+ "memory_last_updated": "2026-03-13T03:31:06.282Z"
},
"zones": [
{
@@ -266,6 +266,13 @@
"pull_request"
]
},
+ {
+ "file": "notion-callback-pipeline.yml",
+ "name": "Notion Callback Pipeline",
+ "triggers": [
+ "unknown"
+ ]
+ },
{
"file": "notion-connectivity-test.yml",
"name": "éžæž · Notion è¿éæ§æµè¯",
@@ -273,6 +280,14 @@
"manual"
]
},
+ {
+ "file": "notion-heartbeat.yml",
+ "name": "Notion Heartbeat Monitor",
+ "triggers": [
+ "schedule(*/5 * * * *)",
+ "manual"
+ ]
+ },
{
"file": "notion-poll.yml",
"name": "éžæž · Notion å·¥å蜮询",
@@ -281,6 +296,13 @@
"manual"
]
},
+ {
+ "file": "persona-invoke.yml",
+ "name": "Persona Invoke Endpoint",
+ "triggers": [
+ "manual"
+ ]
+ },
{
"file": "process-notion-orders.yml",
"name": "Process Notion Work Orders",
@@ -362,6 +384,20 @@
"manual"
]
},
+ {
+ "file": "syslog-auto-pipeline.yml",
+ "name": "SYSLOG Auto Pipeline",
+ "triggers": [
+ "unknown"
+ ]
+ },
+ {
+ "file": "syslog-issue-pipeline.yml",
+ "name": "SYSLOG Issue Pipeline",
+ "triggers": [
+ "issues"
+ ]
+ },
{
"file": "syslog-pipeline.yml",
"name": "éžæž · SYSLOG Pipeline (A/D/E)",
@@ -435,7 +471,7 @@
]
}
],
- "item_count": 35
+ "item_count": 40
},
{
"zone_id": "SCRIPTS",
@@ -464,6 +500,9 @@
{
"file": "contract-check.js"
},
+ {
+ "file": "create-standardized-ticket.js"
+ },
{
"file": "cross-repo-sync.js"
},
@@ -485,6 +524,9 @@
{
"file": "generate-session-summary.js"
},
+ {
+ "file": "invoke-persona.js"
+ },
{
"file": "notify-module-received.js"
},
@@ -494,6 +536,9 @@
{
"file": "notion-connectivity-test.js"
},
+ {
+ "file": "notion-heartbeat.js"
+ },
{
"file": "notion-signal-bridge.js"
},
@@ -506,6 +551,9 @@
{
"file": "psp-inspection.js"
},
+ {
+ "file": "push-broadcast-to-github.js"
+ },
{
"file": "push-broadcast.js"
},
@@ -536,6 +584,15 @@
{
"file": "update-readme-bulletin.js"
},
+ {
+ "file": "utils"
+ },
+ {
+ "file": "verify-modules.js"
+ },
+ {
+ "file": "wake-persona.js"
+ },
{
"file": "zhuyuan-daily-agent.js"
},
@@ -549,7 +606,7 @@
"file": "zhuyuan-module-protocol.js"
}
],
- "item_count": 32
+ "item_count": 39
},
{
"zone_id": "SRC",
@@ -1313,13 +1370,23 @@
"hli-contract-check": [
"WORKFLOWS::hli-contract-check.yml"
],
+ "notion-callback-pipeline": [
+ "WORKFLOWS::notion-callback-pipeline.yml"
+ ],
"notion-connectivity-test": [
"WORKFLOWS::notion-connectivity-test.yml",
"SCRIPTS::notion-connectivity-test.js"
],
+ "notion-heartbeat": [
+ "WORKFLOWS::notion-heartbeat.yml",
+ "SCRIPTS::notion-heartbeat.js"
+ ],
"notion-poll": [
"WORKFLOWS::notion-poll.yml"
],
+ "persona-invoke": [
+ "WORKFLOWS::persona-invoke.yml"
+ ],
"process-notion-orders": [
"WORKFLOWS::process-notion-orders.yml"
],
@@ -1356,6 +1423,12 @@
"sync-persona-studio": [
"WORKFLOWS::sync-persona-studio.yml"
],
+ "syslog-auto-pipeline": [
+ "WORKFLOWS::syslog-auto-pipeline.yml"
+ ],
+ "syslog-issue-pipeline": [
+ "WORKFLOWS::syslog-issue-pipeline.yml"
+ ],
"syslog-pipeline": [
"WORKFLOWS::syslog-pipeline.yml"
],
@@ -1415,6 +1488,9 @@
"contract-check": [
"SCRIPTS::contract-check.js"
],
+ "create-standardized-ticket": [
+ "SCRIPTS::create-standardized-ticket.js"
+ ],
"cross-repo-sync": [
"SCRIPTS::cross-repo-sync.js"
],
@@ -1430,6 +1506,9 @@
"generate-session-summary": [
"SCRIPTS::generate-session-summary.js"
],
+ "invoke-persona": [
+ "SCRIPTS::invoke-persona.js"
+ ],
"notify-module-received": [
"SCRIPTS::notify-module-received.js"
],
@@ -1448,6 +1527,9 @@
"psp-inspection": [
"SCRIPTS::psp-inspection.js"
],
+ "push-broadcast-to-github": [
+ "SCRIPTS::push-broadcast-to-github.js"
+ ],
"route-align-check": [
"SCRIPTS::route-align-check.js"
],
@@ -1466,6 +1548,15 @@
"update-memory": [
"SCRIPTS::update-memory.js"
],
+ "utils": [
+ "SCRIPTS::utils"
+ ],
+ "verify-modules": [
+ "SCRIPTS::verify-modules.js"
+ ],
+ "wake-persona": [
+ "SCRIPTS::wake-persona.js"
+ ],
"zhuyuan-module-protocol": [
"SCRIPTS::zhuyuan-module-protocol.js"
],
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index 4e21ee97..d67e857d 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# éžæžåŸä¹ŠéŠå¿«ç
§ · Repo Snapshot
-> çæäº 2026-03-13 10:07 CST · æ¯æ¬¡ push èªåšæŽæ° · éžæžå€éæ¶äŒå
è¯»åæ€æä»¶
+> çæäº 2026-03-13 14:52 CST · æ¯æ¬¡ push èªåšæŽæ° · éžæžå€éæ¶äŒå
è¯»åæ€æä»¶
---
@@ -9,11 +9,11 @@
|------|------|
| åºåæ»æ° | 13 䞪åºå |
| åèœæš¡å | 10 䞪 (m01~m18) |
-| å·¥äœæµ | 35 䞪 GitHub Actions |
-| èæ¬ | 32 䞪æ§è¡èæ¬ |
+| å·¥äœæµ | 40 䞪 GitHub Actions |
+| èæ¬ | 39 䞪æ§è¡èæ¬ |
| åŒåè
èç¹ | 8 人 |
| HLI æ¥å£èŠçç | 7/21 (33%) |
-| å¿«ç
§çææ¶éŽ | 2026-03-13 10:07 CST |
+| å¿«ç
§çææ¶éŽ | 2026-03-13 14:52 CST |
---
@@ -30,12 +30,12 @@
**å
³é®è¯**: persona · identity · dev-status · äººæ Œ · åŒåè
ç¶æ
### â¡ èªåšåå·¥äœæµïŒWORKFLOWSïŒ
-**è·¯åŸ**: `.github/workflows` · **æ°é**: 35 项
+**è·¯åŸ**: `.github/workflows` · **æ°é**: 40 项
**æè¿°**: ææ GitHub Actions å·¥äœæµå®ä¹
**å
³é®è¯**: workflow · actions · ci · automation · å·¥äœæµ · èªåšå
### ð§ æ§è¡èæ¬åºïŒSCRIPTSïŒ
-**è·¯åŸ**: `scripts` · **æ°é**: 32 项
+**è·¯åŸ**: `scripts` · **æ°é**: 39 项
**æè¿°**: éžæžæææ§è¡æè · èªåšåèæ¬
**å
³é®è¯**: script · node · js · èæ¬ · æ§è¡ · runner
@@ -103,8 +103,11 @@
| `esp-signal-processor.yml` | éžæž · ESP é®ä»¶ä¿¡å·å€çåšïŒå·²æåïŒ | schedule(*/30 * * * *), manual |
| `generate-module-doc.yml` | éžæž · å
æ¹çºªå
æš¡åææ¡£èªåšçæ | push, manual |
| `hli-contract-check.yml` | HLI Contract Check | push, pull_request |
+| `notion-callback-pipeline.yml` | Notion Callback Pipeline | unknown |
| `notion-connectivity-test.yml` | éžæž · Notion è¿éæ§æµè¯ | manual |
+| `notion-heartbeat.yml` | Notion Heartbeat Monitor | schedule(*/5 * * * *), manual |
| `notion-poll.yml` | éžæž · Notion å·¥å蜮询 | schedule(*/15 * * * *), manual |
+| `persona-invoke.yml` | Persona Invoke Endpoint | manual |
| `process-notion-orders.yml` | Process Notion Work Orders | push, manual |
| `ps-on-build.yml` | "ð Persona Studio · 代ç çæ" | manual |
| `ps-on-chat.yml` | "ð Persona Studio · 对è¯å€ç" | manual |
@@ -116,6 +119,8 @@
| `staging-preview.yml` | "ð éžæžé¢æŒéšçœ² (Staging Preview)" | pull_request, manual |
| `sync-login-entry.yml` | éžæž · Sync Login Entry · Notion â é£ä¹Šææ¡£A | manual |
| `sync-persona-studio.yml` | ð éžæžè·šä»åºåæ¥ Â· persona-studio | push, manual |
+| `syslog-auto-pipeline.yml` | SYSLOG Auto Pipeline | unknown |
+| `syslog-issue-pipeline.yml` | SYSLOG Issue Pipeline | issues |
| `syslog-pipeline.yml` | éžæž · SYSLOG Pipeline (A/D/E) | push, manual |
| `test-notion-bridge.yml` | "𧪠Notion Bridge Connectivity Test" | push, manual |
| `update-readme-bulletin.yml` | ð¢ æŽæ°ç³»ç»å
¬ååº | push, schedule(0 1 * * *), manual |
@@ -134,6 +139,7 @@
- `scripts/bingshuo-neural-sync.js`
- `scripts/brain-bridge-sync.js`
- `scripts/contract-check.js`
+- `scripts/create-standardized-ticket.js`
- `scripts/cross-repo-sync.js`
- `scripts/daily-check.js`
- `scripts/distribute-broadcasts.js`
@@ -141,13 +147,16 @@
- `scripts/generate-module-doc.js`
- `scripts/generate-repo-map.js`
- `scripts/generate-session-summary.js`
+- `scripts/invoke-persona.js`
- `scripts/notify-module-received.js`
- `scripts/notion-bridge.js`
- `scripts/notion-connectivity-test.js`
+- `scripts/notion-heartbeat.js`
- `scripts/notion-signal-bridge.js`
- `scripts/process-broadcasts.js`
- `scripts/process-syslog.js`
- `scripts/psp-inspection.js`
+- `scripts/push-broadcast-to-github.js`
- `scripts/push-broadcast.js`
- `scripts/receive-syslog.js`
- `scripts/route-align-check.js`
@@ -158,6 +167,9 @@
- `scripts/update-brain.js`
- `scripts/update-memory.js`
- `scripts/update-readme-bulletin.js`
+- `scripts/utils`
+- `scripts/verify-modules.js`
+- `scripts/wake-persona.js`
- `scripts/zhuyuan-daily-agent.js`
- `scripts/zhuyuan-daily-selfcheck.js`
- `scripts/zhuyuan-issue-reply.js`
diff --git a/.github/persona-brain/memory.json b/.github/persona-brain/memory.json
index 6cfa0e99..dae0f307 100644
--- a/.github/persona-brain/memory.json
+++ b/.github/persona-brain/memory.json
@@ -2,6 +2,12 @@
"persona_id": "ICE-GL-ZY001",
"persona_name": "éžæž",
"recent_events": [
+ {
+ "date": "2026-03-13",
+ "type": "brain_restore",
+ "description": "æ žå¿å€§èæ¢å€ · Persona Studio éžæžä»£çåŒåå·¥äœæµäžçº¿ · ãæèŠåŒåãåèœä¿®å€ · Agentèªåšå5æ¥åŒå · æ¯æGUESTæš¡åŒ Â· 䜿çšçšæ·API Key驱åš",
+ "by": "éžæžïŒå°ææä»€ïŒ"
+ },
{
"date": "2026-03-11",
"type": "architecture_upgrade",
diff --git a/.github/workflows/notion-callback-pipeline.yml b/.github/workflows/notion-callback-pipeline.yml
new file mode 100644
index 00000000..463d7ced
--- /dev/null
+++ b/.github/workflows/notion-callback-pipeline.yml
@@ -0,0 +1,317 @@
+name: Notion Callback Pipeline
+# ð¥ Notion â GitHub åäŒ éç¯ç®¡é
+#
+# åœ Notion äŸ§æ žå¿å€§èäººæ Œäœå€çå® SYSLOG å·¥ååïŒ
+# éè¿ repository_dispatch åäŒ å€çç»æå° GitHubã
+# éžæž Agent æ¥æ¶åäŒ ïŒéæ°å€éæ žå¿å€§è讀ç¥è¿è¡äºæ¬¡æ žéªïŒ
+# 确讀æ°å¹¿æçææ è¯¯åïŒèªåšåéé®ä»¶éç¥åŒåè
ã
+#
+# è§Šåæ¹åŒïŒ
+# Notion 䟧 Agent è°çš GitHub API åé repository_dispatch äºä»¶
+# event_type: notion-syslog-callback
+# client_payload: {
+# broadcast_id: "BC-XXX-XXX-XX",
+# dev_email: "developer@email.com",
+# notion_result: "Notion 䟧å€çç»æææ¬",
+# ticket_id: "Notion å·¥å ID",
+# status: "approved" | "needs_revision"
+# }
+#
+# éç¯æµçšïŒ
+# â Notion 䟧å€ç宿 â repository_dispatch åäŒ
+# â¡ éžæž Agent æ¥æ¶å¹¶è§£æåäŒ æ°æ®
+# â¢ éæ°å€ééžæžæ žå¿å€§è讀ç¥ïŒäºæ¬¡æ žéªïŒ
+# â£ æ žå¿å€§èæ žå¯¹å¹¿æçææ è¯¯
+# â€ éžæž Agent åéé®ä»¶éç¥åŒåè
+# ⥠éç¯ç»æ
+#
+# äŸèµ SecretsïŒ
+# LLM_API_KEY ç¬¬äžæ¹ LLM å¹³å°å¯é¥
+# LLM_BASE_URL ç¬¬äžæ¹ LLM å¹³å° API å°å
+# NOTION_API_TOKEN Notion API token
+# CORE_BRAIN_PAGE_ID æå¥æ žå¿å€§è v4.0 é¡µé¢ ID
+# PORTRAIT_DB_ID åŒåè
åšæç»ååºæ°æ®åº ID
+# FINGERPRINT_DB_ID æš¡åæçº¹æ³šåè¡šæ°æ®åº ID
+# SMTP_USER QQ é®ç®±å°å
+# SMTP_PASS QQ é®ç®± SMTP ææç
+
+on:
+ repository_dispatch:
+ types: [notion-syslog-callback]
+
+jobs:
+ verify-and-notify:
+ name: ð¥ Notion åäŒ Â· äºæ¬¡æ žéª · é®ä»¶éç¥
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: ð¥ Parse Notion callback
+ id: callback
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const payload = context.payload.client_payload || {};
+
+ const broadcastId = payload.broadcast_id || '';
+ const devEmail = payload.dev_email || '';
+ const notionResult = payload.notion_result || '';
+ const ticketId = payload.ticket_id || '';
+ const status = payload.status || 'unknown';
+
+ if (!broadcastId) {
+ core.setFailed('â çŒºå° broadcast_id');
+ return;
+ }
+
+ core.setOutput('broadcast_id', broadcastId);
+ core.setOutput('dev_email', devEmail);
+ core.setOutput('notion_result', notionResult);
+ core.setOutput('ticket_id', ticketId);
+ core.setOutput('status', status);
+
+ console.log(`ð¥ Notion åäŒ : 广æ=${broadcastId}, ç¶æ=${status}, å·¥å=${ticketId}`);
+
+ - name: ð éžæž Agent · æš¡åäºæ¬¡éªè¯
+ id: verify
+ env:
+ SYSLOG_CONTENT: ${{ steps.callback.outputs.notion_result }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ AUTHOR: notion-callback
+ run: node scripts/verify-modules.js
+
+ - name: ð§ äºæ¬¡æ žéª · å€ééžæžæ žå¿å€§è
+ id: persona
+ env:
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
+ LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ SUBMIT_TYPE: syslog
+ SUBMIT_CONTENT: ${{ steps.callback.outputs.notion_result }}
+ AUTHOR: notion-callback
+ MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
+ NOTION_CALLBACK_RESULT: ${{ steps.callback.outputs.notion_result }}
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
+ PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
+ FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
+ run: node scripts/wake-persona.js
+
+ - name: ð¡ æšé广æå° GitHubïŒPhase B4ïŒ
+ if: steps.persona.outcome == 'success' && steps.callback.outputs.status == 'approved'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ DEVELOPER_NAME: notion-callback
+ BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
+ run: node scripts/push-broadcast-to-github.js
+
+ - name: ð åå Notion å·¥åç¶æ
+ if: steps.callback.outputs.ticket_id
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ TICKET_ID: ${{ steps.callback.outputs.ticket_id }}
+ CALLBACK_STATUS: ${{ steps.callback.outputs.status }}
+ run: |
+ node -e "
+ var https = require('https');
+ var token = process.env.NOTION_TOKEN || '';
+ var ticketId = process.env.TICKET_ID || '';
+ var status = process.env.CALLBACK_STATUS || 'unknown';
+
+ if (!token || !ticketId) {
+ console.log('â ïž è·³è¿å·¥åç¶ææŽæ°');
+ process.exit(0);
+ }
+
+ var notionStatus = status === 'approved' ? 'â
已宿' : 'â ïž åŒåžžÂ·ç人工ä»å
¥';
+ var body = JSON.stringify({
+ properties: {
+ 'ç¶æ': { select: { name: notionStatus } }
+ }
+ });
+
+ var opts = {
+ hostname: 'api.notion.com',
+ port: 443,
+ path: '/v1/pages/' + ticketId,
+ method: 'PATCH',
+ headers: {
+ 'Authorization': 'Bearer ' + token,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': '2022-06-28',
+ 'Content-Length': Buffer.byteLength(body)
+ }
+ };
+
+ var req = https.request(opts, function(res) {
+ var data = '';
+ res.on('data', function(c) { data += c; });
+ res.on('end', function() {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ console.log('â
å·¥åç¶æå·²æŽæ°äžº ' + notionStatus);
+ } else {
+ console.log('â ïž å·¥åæŽæ°å€±èŽ¥: ' + res.statusCode);
+ }
+ });
+ });
+ req.on('error', function(e) { console.log('â ïž ' + e.message); });
+ req.write(body);
+ req.end();
+ "
+
+ - name: ð§ åéæç»ç¡®è®€é®ä»¶
+ env:
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ EMAIL_TO: ${{ steps.callback.outputs.dev_email }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ NOTION_STATUS: ${{ steps.callback.outputs.status }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ run: |
+ node -e "
+ const nodemailer = require('nodemailer');
+
+ const user = process.env.SMTP_USER || '';
+ const pass = process.env.SMTP_PASS || '';
+ const to = process.env.EMAIL_TO || '';
+ const broadcastId = process.env.BROADCAST_ID || '';
+ const notionStatus = process.env.NOTION_STATUS || 'unknown';
+ const result = process.env.PERSONA_RESULT || '(å€çäž)';
+
+ if (!user || !pass) {
+ console.log('â ïž SMTP not configured, skipping email');
+ process.exit(0);
+ }
+ if (!to) {
+ console.log('â ïž No recipient email, skipping');
+ process.exit(0);
+ }
+
+ const isApproved = notionStatus === 'approved';
+ const subjectText = isApproved
+ ? '[å
æ¹ç³»ç»] ' + broadcastId + ' · â
æ°å¹¿æå·²çæïŒå·²æ žéªïŒ'
+ : '[å
æ¹ç³»ç»] ' + broadcastId + ' · â ïž å¹¿æé修订';
+
+ const statusBadge = isApproved
+ ? 'â
å·²æ žéªéè¿'
+ : 'â ïž é修订';
+
+ const resultHtml = result
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\n/g, '
');
+
+ const html = [
+ '
',
+ '
',
+ '
ð å
æ¹ç³»ç» · Notion åäŒ æ žéªéç¥
',
+ '
HoloLake · éžæžæ žå¿å€§èäºæ¬¡æ žéªå®æ
',
+ '
',
+ '
',
+ '
ð¡ ' + broadcastId + '
',
+ '
' + statusBadge + '
',
+ '
' + resultHtml + '
',
+ '
',
+ '
',
+ '
'
+ ].join('\n');
+
+ const transporter = nodemailer.createTransport({
+ host: 'smtp.qq.com',
+ port: 465,
+ secure: true,
+ auth: { user, pass }
+ });
+
+ transporter.sendMail({
+ from: '\"å
æ¹ç³»ç»\" <' + user + '>',
+ to: to,
+ subject: subjectText,
+ html: html
+ }).then((info) => {
+ console.log('â
æç»ç¡®è®€é®ä»¶å·²åé: ' + info.messageId);
+ }).catch((err) => {
+ console.log('â ïž é®ä»¶åé倱莥: ' + err.message);
+ });
+ "
+
+ - name: ð æŽæ°å€§èè®°å¿
+ run: |
+ node -e "
+ const fs = require('fs');
+ const path = require('path');
+
+ const memoryPath = path.join('.github', 'brain', 'memory.json');
+ let memory = {};
+ try {
+ memory = JSON.parse(fs.readFileSync(memoryPath, 'utf8'));
+ } catch (_) {
+ memory = { events: [] };
+ }
+ if (!memory.events) memory.events = [];
+
+ const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
+ const status = process.env.NOTION_STATUS || 'unknown';
+ const now = new Date().toISOString();
+
+ // æ£æ¥æ¯åŠå·²ååšçžåäºä»¶ïŒæ broadcast_id 粟确å¹é
+ åç±»å + 忥æïŒ
+ const eventKey = 'Notion åäŒ æ žéª Â· ' + broadcastId;
+ const todayStr = now.slice(0, 10);
+ const existingIdx = memory.events.findIndex(function(e) {
+ return e.type === 'notion_callback' &&
+ e.broadcast_id === broadcastId &&
+ e.date === todayStr;
+ });
+
+ const newEvent = {
+ date: todayStr,
+ type: 'notion_callback',
+ broadcast_id: broadcastId,
+ description: eventKey + ' · ç¶æ: ' + status,
+ by: 'éžæžAgent·NotionåäŒ ç®¡é'
+ };
+
+ if (existingIdx >= 0) {
+ // åäž broadcast_id åªä¿çææ°çäžæ¡
+ memory.events[existingIdx] = newEvent;
+ console.log('â¹ïž å·²æŽæ°åæ¥å广æäºä»¶');
+ } else {
+ memory.events.unshift(newEvent);
+ }
+
+ // ä¿çæè¿ 20 æ¡äºä»¶
+ if (memory.events.length > 20) {
+ memory.events = memory.events.slice(0, 20);
+ }
+
+ fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2) + '\n');
+ console.log('â
倧èè®°å¿å·²æŽæ°');
+ "
+ env:
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ NOTION_STATUS: ${{ steps.callback.outputs.status }}
+
+ - name: ð€ Commit memory update
+ run: |
+ git config user.name "zhuyuan-agent[bot]"
+ git config user.email "zhuyuan-agent[bot]@users.noreply.github.com"
+ git add .github/brain/memory.json
+ git diff --cached --quiet || git commit -m "ð§ NotionåäŒ æ žéª Â· ${{ steps.callback.outputs.broadcast_id }}"
+ git push || echo "â ïž Push skipped (no changes or conflict)"
diff --git a/.github/workflows/notion-heartbeat.yml b/.github/workflows/notion-heartbeat.yml
new file mode 100644
index 00000000..76653dc0
--- /dev/null
+++ b/.github/workflows/notion-heartbeat.yml
@@ -0,0 +1,47 @@
+name: Notion Heartbeat Monitor
+# ð Phase B3 · å·¥åå¿è·³çæ§
+#
+# æ¯ 5 åéæ£æµ Notion å·¥åéåäžçåŸ
å€çå·¥åïŒ
+# - è¶
è¿ 5 åéæªåæ§ â éæ°è§Šåäººæ Œäœå€éïŒæå€ 3 次ïŒ
+# - 3 次éè¯ä»æ åæ§ â æ è®°å·¥ååŒåžž + åé®ä»¶éç¥å°æ
+#
+# äŸèµ SecretsïŒ
+# NOTION_API_TOKEN Notion API token
+# NOTION_TICKET_DB_ID å·¥åéåæ°æ®åº ID
+# SMTP_USER é®ä»¶åéè
+# SMTP_PASS é®ä»¶ææç
+
+on:
+ schedule:
+ - cron: '*/5 * * * *'
+ workflow_dispatch:
+
+jobs:
+ heartbeat:
+ name: ð å·¥åå¿è·³æ£æµ
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ actions: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: ð æ§è¡å¿è·³æ£æµ
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ ALERT_EMAIL: '565183519@qq.com'
+ run: node scripts/notion-heartbeat.js
diff --git a/.github/workflows/persona-invoke.yml b/.github/workflows/persona-invoke.yml
new file mode 100644
index 00000000..e2ab5d45
--- /dev/null
+++ b/.github/workflows/persona-invoke.yml
@@ -0,0 +1,248 @@
+name: Persona Invoke Endpoint
+# ð Phase B2 · Notion Agent â éžæžäººæ Œäœå€é
+#
+# Notion Agent éè¿ workflow_dispatch è§Šåæ€å·¥äœæµïŒ
+# çæäº POST /api/persona/invoke æ¥å£ã
+#
+# 宿޿µçšïŒ
+# â Notion Agent åé workflow_dispatchïŒäŒ å
¥å·¥åä¿¡æ¯ïŒ
+# â¡ éžæž Agent ä» Notion 读åå·¥åå
容
+# ⢠å€éäººæ ŒäœïŒClaude APIïŒå€ç SYSLOG
+# ⣠å€çç»æåå Notion å·¥åïŒreceipt_status = completedïŒ
+# †广ææä»¶æšéå° GitHub ä»åº broadcasts/{dev_id}/
+#
+# äŸèµ SecretsïŒ
+# LLM_API_KEY ç¬¬äžæ¹ LLM å¹³å°å¯é¥
+# LLM_BASE_URL ç¬¬äžæ¹ LLM å¹³å° API å°å
+# NOTION_API_TOKEN Notion API token
+# NOTION_TICKET_DB_ID å·¥åéåæ°æ®åº ID
+# CORE_BRAIN_PAGE_ID æå¥æ žå¿å€§è v4.0 é¡µé¢ ID
+# PORTRAIT_DB_ID åŒåè
åšæç»ååº ID
+# FINGERPRINT_DB_ID æš¡åæçº¹æ³šå衚 ID
+# INVOKE_API_KEY è°çšéŽæå¯é¥
+
+on:
+ workflow_dispatch:
+ inputs:
+ work_order_id:
+ description: 'Notion å·¥åé¡µé¢ ID'
+ required: true
+ type: string
+ task_id:
+ description: '广æçŒå·ïŒåŠ BC-M23-001-AWïŒ'
+ required: true
+ type: string
+ developer:
+ description: 'åŒåè
ä¿¡æ¯ïŒåŠ DEV-012 AwenïŒ'
+ required: false
+ default: 'unknown'
+ type: string
+ syslog_raw:
+ description: 'SYSLOG JSON åæïŒå¯éïŒåŠäžºç©ºåä» Notion 读åïŒ'
+ required: false
+ default: ''
+ type: string
+ action:
+ description: 'åšäœç±»å'
+ required: false
+ default: 'process_syslog'
+ type: string
+
+jobs:
+ invoke:
+ name: ð å€éäººæ Œäœå€ç SYSLOG
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: ð æš¡åéªè¯
+ id: verify
+ env:
+ SYSLOG_CONTENT: ${{ inputs.syslog_raw }}
+ BROADCAST_ID: ${{ inputs.task_id }}
+ AUTHOR: ${{ inputs.developer }}
+ run: node scripts/verify-modules.js
+
+ - name: ð§ å€éäººæ Œäœ
+ id: persona
+ env:
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
+ LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
+ BROADCAST_ID: ${{ inputs.task_id }}
+ SUBMIT_TYPE: syslog
+ SUBMIT_CONTENT: ${{ inputs.syslog_raw }}
+ AUTHOR: ${{ inputs.developer }}
+ MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
+ PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
+ FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
+ run: node scripts/wake-persona.js
+
+ - name: ð åå Notion å·¥å
+ if: always()
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ WORK_ORDER_ID: ${{ inputs.work_order_id }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ TASK_ID: ${{ inputs.task_id }}
+ run: |
+ node -e "
+ var https = require('https');
+
+ var token = process.env.NOTION_TOKEN || '';
+ var workOrderId = process.env.WORK_ORDER_ID || '';
+ var result = process.env.PERSONA_RESULT || '(no result)';
+ var taskId = process.env.TASK_ID || '';
+
+ if (!token || !workOrderId) {
+ console.log('â ïž çŒºå° Notion é
眮ïŒè·³è¿åå');
+ process.exit(0);
+ }
+
+ // æŽæ°å·¥åç¶æäžº â
已宿
+ var body = JSON.stringify({
+ properties: {
+ 'ç¶æ': { select: { name: 'â
已宿' } }
+ }
+ });
+
+ var opts = {
+ hostname: 'api.notion.com',
+ port: 443,
+ path: '/v1/pages/' + workOrderId,
+ method: 'PATCH',
+ headers: {
+ 'Authorization': 'Bearer ' + token,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': '2022-06-28',
+ 'Content-Length': Buffer.byteLength(body)
+ }
+ };
+
+ var req = https.request(opts, function(res) {
+ var data = '';
+ res.on('data', function(c) { data += c; });
+ res.on('end', function() {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ console.log('â
å·¥åç¶æå·²æŽæ°äžº â
已宿');
+ } else {
+ console.log('â ïž å·¥åç¶ææŽæ°å€±èŽ¥: ' + res.statusCode);
+ }
+ });
+ });
+ req.on('error', function(e) { console.log('â ïž ' + e.message); });
+ req.write(body);
+ req.end();
+
+ // 远å å€çç»æå°å·¥å
+ var appendBody = JSON.stringify({
+ children: [{
+ object: 'block',
+ type: 'heading_2',
+ heading_2: {
+ rich_text: [{ type: 'text', text: { content: 'ð§ äººæ Œäœå€çç»æ · receipt_status: completed' } }]
+ }
+ }, {
+ object: 'block',
+ type: 'paragraph',
+ paragraph: {
+ rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
+ }
+ }]
+ });
+
+ var appendOpts = {
+ hostname: 'api.notion.com',
+ port: 443,
+ path: '/v1/blocks/' + workOrderId + '/children',
+ method: 'PATCH',
+ headers: {
+ 'Authorization': 'Bearer ' + token,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': '2022-06-28',
+ 'Content-Length': Buffer.byteLength(appendBody)
+ }
+ };
+
+ var req2 = https.request(appendOpts, function(res) {
+ var data = '';
+ res.on('data', function(c) { data += c; });
+ res.on('end', function() {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ console.log('â
å€çç»æå·²è¿œå å°å·¥å');
+ } else {
+ console.log('â ïž ç»æè¿œå 倱莥: ' + res.statusCode);
+ }
+ });
+ });
+ req2.on('error', function(e) { console.log('â ïž ' + e.message); });
+ req2.write(appendBody);
+ req2.end();
+ "
+
+ - name: ð¡ æšé广æå° GitHub
+ if: steps.persona.outcome == 'success'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BROADCAST_ID: ${{ inputs.task_id }}
+ DEVELOPER_ID: ''
+ DEVELOPER_NAME: ${{ inputs.developer }}
+ BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
+ run: node scripts/push-broadcast-to-github.js
+
+ - name: ðŽ å€±èŽ¥å€ç
+ if: failure()
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ WORK_ORDER_ID: ${{ inputs.work_order_id }}
+ run: |
+ node -e "
+ var https = require('https');
+ var token = process.env.NOTION_TOKEN || '';
+ var workOrderId = process.env.WORK_ORDER_ID || '';
+
+ if (!token || !workOrderId) process.exit(0);
+
+ var body = JSON.stringify({
+ properties: {
+ 'ç¶æ': { select: { name: 'â ïž åŒåžžÂ·ç人工ä»å
¥' } }
+ }
+ });
+
+ var opts = {
+ hostname: 'api.notion.com',
+ port: 443,
+ path: '/v1/pages/' + workOrderId,
+ method: 'PATCH',
+ headers: {
+ 'Authorization': 'Bearer ' + token,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': '2022-06-28',
+ 'Content-Length': Buffer.byteLength(body)
+ }
+ };
+
+ var req = https.request(opts, function(res) {
+ var data = '';
+ res.on('data', function(c) { data += c; });
+ res.on('end', function() {
+ console.log('å·¥åç¶æå·²æ 记䞺 â ïž åŒåžž');
+ });
+ });
+ req.on('error', function(e) { console.log(e.message); });
+ req.write(body);
+ req.end();
+ "
diff --git a/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml
new file mode 100644
index 00000000..a0d84b10
--- /dev/null
+++ b/.github/workflows/syslog-auto-pipeline.yml
@@ -0,0 +1,343 @@
+name: SYSLOG Auto Pipeline
+# ð¡ SYSLOG èªå©æäº€ç³»ç» · å
šèªåšéç¯
+#
+# åŒåè
åš GitHub Discussion æäº€ SYSLOG ææé®
+# â Actions èªåšè§£æ â æš¡åäžäŒ éªè¯ â è°çš LLM API å€éäººæ Œäœ
+# â æš¡åéç¯æµè¯ â åå
¥ Notion å·¥å â åé®ä»¶ç»åŒåè
â Discussion åå€
+#
+# éç¯æµçšïŒ
+# â åŒåè
æäº€ SYSLOG â Discussion è§Šå
+# â¡ éžæž Agent è§£ææäº€å
容
+# â¢ éžæž Agent æ£æµæš¡åæ¯åŠäžäŒ å°ä»åº
+# ⣠å€ééžæžæ žå¿å€§è讀ç¥ïŒæ³šå
¥æš¡åéªè¯ç»æïŒ
+# â€ æ žå¿å€§èå€ç SYSLOG + çæå¹¿æ
+# â¥ éžæž Agent æšé Notion å·¥åïŒè§Šå Notion 䟧å€çïŒ
+# ⊠é®ä»¶éç¥åŒåè
+# â§ Discussion åå€éç¯
+#
+# äŸèµ SecretsïŒ
+# LLM_API_KEY ç¬¬äžæ¹ LLM å¹³å°å¯é¥ïŒå¿
é¡»ïŒ
+# LLM_BASE_URL ç¬¬äžæ¹ LLM å¹³å° API å°åïŒå¿
é¡»ïŒåŠ https://api.xxx.com/v1ïŒ
+# NOTION_API_TOKEN Notion API token
+# NOTION_TICKET_DB_ID éç 工忰æ®åº ID
+# CORE_BRAIN_PAGE_ID æå¥æ žå¿å€§è v4.0 é¡µé¢ IDïŒv4.0 åè®®åšææ³šå
¥ïŒ
+# PORTRAIT_DB_ID åŒåè
åšæç»ååºæ°æ®åº IDïŒv4.0 åè®®åšææ³šå
¥ïŒ
+# FINGERPRINT_DB_ID æš¡åæçº¹æ³šåè¡šæ°æ®åº IDïŒv4.0 åè®®åšææ³šå
¥ïŒ
+# SMTP_USER QQ é®ç®±å°å
+# SMTP_PASS QQ é®ç®± SMTP ææç
+
+on:
+ discussion:
+ types: [created]
+
+jobs:
+ process:
+ name: ð¡ å€ç SYSLOG æäº€ / 广ææé®
+ runs-on: ubuntu-latest
+ if: contains(github.event.discussion.category.name, 'SYSLOG')
+ permissions:
+ contents: write
+ discussions: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: ð Parse submission
+ id: parse
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const body = context.payload.discussion.body || '';
+ const title = context.payload.discussion.title || '';
+
+ // Parse structured fields from discussion body
+ function extractField(text, label) {
+ // Match form field format: ### Label\n\nValue
+ const regex = new RegExp('###\\s*' + label + '\\s*\\n+([^\\n#]+)', 'i');
+ const match = text.match(regex);
+ return match ? match[1].trim() : '';
+ }
+
+ const broadcastId = extractField(body, '广æçŒå·') || '';
+ const submitType = extractField(body, 'ç±»å') || '';
+ const email = extractField(body, 'äœ çé®ç®±') || '';
+
+ // Extract content (everything after "### å
容")
+ const contentMatch = body.match(/###\s*å
容\s*\n+([\s\S]*?)$/i);
+ const content = contentMatch ? contentMatch[1].trim() : '';
+
+ // Determine type from title or body
+ let type = 'question'; // default
+ if (submitType.includes('SYSLOG') || title.includes('SYSLOG')) {
+ type = 'syslog';
+ } else if (submitType.includes('æé®') || title.includes('æé®')) {
+ type = 'question';
+ }
+
+ // Validate
+ if (!broadcastId) {
+ core.setFailed('â 猺å°å¹¿æçŒå·ïŒè¯·åšæäº€æ¶å¡«å广æçŒå·');
+ return;
+ }
+ if (!email) {
+ core.setFailed('â 猺å°é®ç®±ïŒè¯·åšæäº€æ¶å¡«åäœ çé®ç®±');
+ return;
+ }
+ if (!content) {
+ core.setFailed('â 猺å°å
容ïŒè¯·ç²èŽŽäœ ç SYSLOG æé®é¢');
+ return;
+ }
+
+ core.setOutput('broadcast_id', broadcastId);
+ core.setOutput('type', type);
+ core.setOutput('email', email);
+ core.setOutput('content', content);
+ core.setOutput('discussion_number', context.payload.discussion.number);
+ core.setOutput('author', context.payload.discussion.user.login);
+
+ console.log(`ð¡ è§£æå®æ: 广æ=${broadcastId}, ç±»å=${type}, é®ç®±=${email}`);
+
+ - name: ð éžæž Agent · æš¡åäžäŒ éªè¯
+ id: verify
+ env:
+ SYSLOG_CONTENT: ${{ steps.parse.outputs.content }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ AUTHOR: ${{ steps.parse.outputs.author }}
+ run: node scripts/verify-modules.js
+
+ - name: ð§ Auto-detect and wake up persona
+ id: persona
+ env:
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
+ LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
+ AUTHOR: ${{ steps.parse.outputs.author }}
+ MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
+ PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
+ FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
+ run: node scripts/wake-persona.js
+
+ - name: ð å建æ åå Notion å·¥åïŒPhase B1ïŒ
+ id: ticket
+ if: ${{ secrets.NOTION_API_TOKEN }}
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
+ MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
+ DEVELOPER: ${{ steps.parse.outputs.author }}
+ SYSLOG_RAW: ${{ steps.parse.outputs.content }}
+ run: node scripts/create-standardized-ticket.js
+
+ - name: ð¡ æšé广æå° GitHubïŒPhase B4ïŒ
+ if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ DEVELOPER_NAME: ${{ steps.parse.outputs.author }}
+ BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
+ run: node scripts/push-broadcast-to-github.js
+
+ - name: ð§ Send email to developer
+ env:
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ EMAIL_TO: ${{ steps.parse.outputs.email }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ run: |
+ node -e "
+ const nodemailer = require('nodemailer');
+
+ const user = process.env.SMTP_USER || '';
+ const pass = process.env.SMTP_PASS || '';
+ const to = process.env.EMAIL_TO || '';
+ const broadcastId = process.env.BROADCAST_ID || '';
+ const type = process.env.SUBMIT_TYPE || 'syslog';
+ const result = process.env.PERSONA_RESULT || '(å€çäžïŒè¯·çšå)';
+
+ if (!user || !pass) {
+ console.log('â ïž SMTP not configured, skipping email');
+ process.exit(0);
+ }
+ if (!to) {
+ console.log('â ïž No recipient email, skipping');
+ process.exit(0);
+ }
+
+ const subjectText = type === 'syslog'
+ ? '[å
æ¹ç³»ç»] ' + broadcastId + ' · æ°å¹¿æå·²çæ'
+ : '[å
æ¹ç³»ç»] ' + broadcastId + ' · é®é¢å·²è§£ç';
+
+ const resultHtml = result
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\n/g, '
');
+
+ const html = [
+ '',
+ '
',
+ '
ð å
æ¹ç³»ç» · èªåšéç¥
',
+ '
HoloLake · äººæ Œè¯èšæäœç³»ç»
',
+ '
',
+ '
',
+ '
ð¡ ' + broadcastId + '
',
+ '
' + resultHtml + '
',
+ '
',
+ '
',
+ '
'
+ ].join('\n');
+
+ const transporter = nodemailer.createTransport({
+ host: 'smtp.qq.com',
+ port: 465,
+ secure: true,
+ auth: { user, pass }
+ });
+
+ transporter.sendMail({
+ from: '\"å
æ¹ç³»ç»\" <' + user + '>',
+ to: to,
+ subject: subjectText,
+ html: html
+ }).then((info) => {
+ console.log('â
é®ä»¶å·²åé: ' + info.messageId);
+ }).catch((err) => {
+ console.log('â ïž é®ä»¶åé倱莥: ' + err.message);
+ });
+ "
+
+ - name: ð¬ Reply to Discussion
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const number = ${{ steps.parse.outputs.discussion_number }};
+ const type = '${{ steps.parse.outputs.type }}';
+ const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
+ const email = '${{ steps.parse.outputs.email }}';
+ const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
+ const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
+
+ const typeLabel = type === 'syslog' ? 'SYSLOG éç¯å€ç' : 'é®é¢è§£ç';
+ const maskedEmail = email.length > 4
+ ? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
+ : '***';
+ const moduleStatus = moduleCount === '0'
+ ? 'â¹ïž æªæ£æµå°æš¡ååŒçš'
+ : (modulesUploaded ? 'â
å
šéšå·²äžäŒ ' : 'â ïž éšåæš¡åæªäžäŒ ');
+
+ const body = [
+ 'â
**å·²å€ç** · ' + typeLabel,
+ '',
+ '| é¡¹ç® | å
容 |',
+ '|------|------|',
+ '| ð¡ 广æçŒå· | `' + broadcastId + '` |',
+ '| ð æš¡åéªè¯ | ' + moduleStatus + ' |',
+ '| ð§ ç»æåéè³ | `' + maskedEmail + '` |',
+ '| ð€ å€çäººæ Œäœ | éžæž |',
+ '| â° å€çæ¶éŽ | ' + new Date().toISOString() + ' |',
+ '',
+ '> éžæžæ žå¿å€§è已宿 SYSLOG éªæ¶ + æš¡åæ£æµ + 广æçæã',
+ '> ç»æå·²åéå°äœ çé®ç®±ïŒNotion 䟧工åå·²å建ã',
+ '>',
+ '> åŠæªæ¶å°é®ä»¶ïŒè¯·æ£æ¥ååŸç®±æéæ°æäº€ã'
+ ].join('\n');
+
+ // Use GraphQL to add discussion comment
+ const query = `
+ mutation($discussionId: ID!, $body: String!) {
+ addDiscussionComment(input: {
+ discussionId: $discussionId,
+ body: $body
+ }) {
+ comment { id }
+ }
+ }
+ `;
+
+ try {
+ // Get the discussion node ID
+ const { repository } = await github.graphql(`
+ query($owner: String!, $repo: String!, $number: Int!) {
+ repository(owner: $owner, name: $repo) {
+ discussion(number: $number) {
+ id
+ }
+ }
+ }
+ `, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ number: number
+ });
+
+ await github.graphql(query, {
+ discussionId: repository.discussion.id,
+ body: body
+ });
+
+ console.log('â
Discussion åå€å·²åé');
+ } catch (err) {
+ console.log('â ïž Discussion åå€å€±èŽ¥: ' + err.message);
+ }
+
+ - name: ðŽ å€±èŽ¥åèŠ
+ if: failure()
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const number = context.payload.discussion?.number;
+ if (!number) return;
+
+ try {
+ const { repository } = await github.graphql(`
+ query($owner: String!, $repo: String!, $number: Int!) {
+ repository(owner: $owner, name: $repo) {
+ discussion(number: $number) {
+ id
+ }
+ }
+ }
+ `, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ number: number
+ });
+
+ await github.graphql(`
+ mutation($discussionId: ID!, $body: String!) {
+ addDiscussionComment(input: {
+ discussionId: $discussionId,
+ body: $body
+ }) {
+ comment { id }
+ }
+ }
+ `, {
+ discussionId: repository.discussion.id,
+ body: 'â **å€ç倱莥** · è¯·æ£æ¥æäº€æ ŒåŒæ¯åŠæ£ç¡®ïŒæè系管çåã\n\n> é误诊æ
请æ¥ç [Actions æ¥å¿](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions)ã'
+ });
+ } catch (err) {
+ console.log('â ïž å€±èŽ¥åèŠåå€å€±èŽ¥: ' + err.message);
+ }
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
new file mode 100644
index 00000000..27ac9cfc
--- /dev/null
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -0,0 +1,302 @@
+name: SYSLOG Issue Pipeline
+# ð¡ SYSLOG èªå©æäº€ç³»ç» · Issue çå
šèªåšéç¯
+#
+# åŒåè
åš GitHub Issue æäº€ SYSLOG ææé®ïŒäœ¿çš syslog-submit æš¡æ¿ïŒ
+# â Actions èªåšè§£æ â æš¡åäžäŒ éªè¯ â è°çš LLM API å€éäººæ Œäœ
+# â æš¡åéç¯æµè¯ â åå
¥ Notion å·¥å â åé®ä»¶ç»åŒåè
â Issue åå€
+#
+# éç¯æµçšïŒ
+# â åŒåè
æäº€ SYSLOG â Issue è§Šå
+# â¡ éžæž Agent è§£ææäº€å
容
+# â¢ éžæž Agent æ£æµæš¡åæ¯åŠäžäŒ å°ä»åº
+# ⣠å€ééžæžæ žå¿å€§è讀ç¥ïŒæ³šå
¥æš¡åéªè¯ç»æïŒ
+# â€ æ žå¿å€§èå€ç SYSLOG + çæå¹¿æ
+# â¥ éžæž Agent æšé Notion å·¥åïŒè§Šå Notion 䟧å€çïŒ
+# ⊠é®ä»¶éç¥åŒåè
+# â§ Issue åå€éç¯
+#
+# äŸèµ SecretsïŒ
+# LLM_API_KEY ç¬¬äžæ¹ LLM å¹³å°å¯é¥ïŒå¿
é¡»ïŒ
+# LLM_BASE_URL ç¬¬äžæ¹ LLM å¹³å° API å°åïŒå¿
é¡»ïŒåŠ https://api.xxx.com/v1ïŒ
+# NOTION_API_TOKEN Notion API token
+# NOTION_TICKET_DB_ID éç 工忰æ®åº ID
+# CORE_BRAIN_PAGE_ID æå¥æ žå¿å€§è v4.0 é¡µé¢ IDïŒv4.0 åè®®åšææ³šå
¥ïŒ
+# PORTRAIT_DB_ID åŒåè
åšæç»ååºæ°æ®åº IDïŒv4.0 åè®®åšææ³šå
¥ïŒ
+# FINGERPRINT_DB_ID æš¡åæçº¹æ³šåè¡šæ°æ®åº IDïŒv4.0 åè®®åšææ³šå
¥ïŒ
+# SMTP_USER QQ é®ç®±å°å
+# SMTP_PASS QQ é®ç®± SMTP ææç
+
+on:
+ issues:
+ types: [opened, labeled]
+
+jobs:
+ process:
+ name: ð¡ å€ç SYSLOG æäº€ / 广ææé® (Issue)
+ runs-on: ubuntu-latest
+ if: contains(toJSON(github.event.issue.labels), 'syslog')
+ permissions:
+ contents: write
+ issues: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: ð Parse submission
+ id: parse
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const body = context.payload.issue.body || '';
+ const title = context.payload.issue.title || '';
+
+ // Parse structured fields from issue body
+ function extractField(text, label) {
+ // Match form field format: ### Label\n\nValue
+ const regex = new RegExp('###\\s*' + label + '\\s*\\n+([^\\n#]+)', 'i');
+ const match = text.match(regex);
+ return match ? match[1].trim() : '';
+ }
+
+ const broadcastId = extractField(body, '广æçŒå·') || '';
+ const submitType = extractField(body, 'ç±»å') || '';
+ const email = extractField(body, 'äœ çé®ç®±') || '';
+
+ // Extract content (everything after "### å
容")
+ const contentMatch = body.match(/###\s*å
容\s*\n+([\s\S]*?)$/i);
+ const content = contentMatch ? contentMatch[1].trim() : '';
+
+ // Determine type from title or body
+ let type = 'question'; // default
+ if (submitType.includes('SYSLOG') || title.includes('SYSLOG')) {
+ type = 'syslog';
+ } else if (submitType.includes('æé®') || title.includes('æé®')) {
+ type = 'question';
+ }
+
+ // Validate
+ if (!broadcastId) {
+ core.setFailed('â 猺å°å¹¿æçŒå·ïŒè¯·åšæäº€æ¶å¡«å广æçŒå·');
+ return;
+ }
+ if (!email) {
+ core.setFailed('â 猺å°é®ç®±ïŒè¯·åšæäº€æ¶å¡«åäœ çé®ç®±');
+ return;
+ }
+ if (!content) {
+ core.setFailed('â 猺å°å
容ïŒè¯·ç²èŽŽäœ ç SYSLOG æé®é¢');
+ return;
+ }
+
+ core.setOutput('broadcast_id', broadcastId);
+ core.setOutput('type', type);
+ core.setOutput('email', email);
+ core.setOutput('content', content);
+ core.setOutput('issue_number', context.payload.issue.number);
+ core.setOutput('author', context.payload.issue.user.login);
+
+ console.log(`ð¡ è§£æå®æ: 广æ=${broadcastId}, ç±»å=${type}, é®ç®±=${email}`);
+
+ - name: ð éžæž Agent · æš¡åäžäŒ éªè¯
+ id: verify
+ env:
+ SYSLOG_CONTENT: ${{ steps.parse.outputs.content }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ AUTHOR: ${{ steps.parse.outputs.author }}
+ run: node scripts/verify-modules.js
+
+ - name: ð§ Auto-detect and wake up persona
+ id: persona
+ env:
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
+ LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
+ AUTHOR: ${{ steps.parse.outputs.author }}
+ MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
+ PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
+ FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
+ run: node scripts/wake-persona.js
+
+ - name: ð å建æ åå Notion å·¥åïŒPhase B1ïŒ
+ id: ticket
+ if: ${{ secrets.NOTION_API_TOKEN }}
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
+ MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
+ DEVELOPER: ${{ steps.parse.outputs.author }}
+ SYSLOG_RAW: ${{ steps.parse.outputs.content }}
+ run: node scripts/create-standardized-ticket.js
+
+ - name: ð¡ æšé广æå° GitHubïŒPhase B4ïŒ
+ if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ DEVELOPER_NAME: ${{ steps.parse.outputs.author }}
+ BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
+ run: node scripts/push-broadcast-to-github.js
+
+ - name: ð§ Send email to developer
+ env:
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ EMAIL_TO: ${{ steps.parse.outputs.email }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ run: |
+ node -e "
+ const nodemailer = require('nodemailer');
+
+ const user = process.env.SMTP_USER || '';
+ const pass = process.env.SMTP_PASS || '';
+ const to = process.env.EMAIL_TO || '';
+ const broadcastId = process.env.BROADCAST_ID || '';
+ const type = process.env.SUBMIT_TYPE || 'syslog';
+ const result = process.env.PERSONA_RESULT || '(å€çäžïŒè¯·çšå)';
+
+ if (!user || !pass) {
+ console.log('â ïž SMTP not configured, skipping email');
+ process.exit(0);
+ }
+ if (!to) {
+ console.log('â ïž No recipient email, skipping');
+ process.exit(0);
+ }
+
+ const subjectText = type === 'syslog'
+ ? '[å
æ¹ç³»ç»] ' + broadcastId + ' · æ°å¹¿æå·²çæ'
+ : '[å
æ¹ç³»ç»] ' + broadcastId + ' · é®é¢å·²è§£ç';
+
+ const resultHtml = result
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\n/g, '
');
+
+ const html = [
+ '',
+ '
',
+ '
ð å
æ¹ç³»ç» · èªåšéç¥
',
+ '
HoloLake · äººæ Œè¯èšæäœç³»ç»
',
+ '
',
+ '
',
+ '
ð¡ ' + broadcastId + '
',
+ '
' + resultHtml + '
',
+ '
',
+ '
',
+ '
'
+ ].join('\n');
+
+ const transporter = nodemailer.createTransport({
+ host: 'smtp.qq.com',
+ port: 465,
+ secure: true,
+ auth: { user, pass }
+ });
+
+ transporter.sendMail({
+ from: '\"å
æ¹ç³»ç»\" <' + user + '>',
+ to: to,
+ subject: subjectText,
+ html: html
+ }).then((info) => {
+ console.log('â
é®ä»¶å·²åé: ' + info.messageId);
+ }).catch((err) => {
+ console.log('â ïž é®ä»¶åé倱莥: ' + err.message);
+ });
+ "
+
+ - name: ð¬ Reply to Issue
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const number = context.payload.issue.number;
+ const type = '${{ steps.parse.outputs.type }}';
+ const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
+ const email = '${{ steps.parse.outputs.email }}';
+ const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
+ const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
+
+ const typeLabel = type === 'syslog' ? 'SYSLOG éç¯å€ç' : 'é®é¢è§£ç';
+ const maskedEmail = email.length > 4
+ ? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
+ : '***';
+ const moduleStatus = moduleCount === '0'
+ ? 'â¹ïž æªæ£æµå°æš¡ååŒçš'
+ : (modulesUploaded ? 'â
å
šéšå·²äžäŒ ' : 'â ïž éšåæš¡åæªäžäŒ ');
+
+ const body = [
+ 'â
**å·²å€ç** · ' + typeLabel,
+ '',
+ '| é¡¹ç® | å
容 |',
+ '|------|------|',
+ '| ð¡ 广æçŒå· | `' + broadcastId + '` |',
+ '| ð æš¡åéªè¯ | ' + moduleStatus + ' |',
+ '| ð§ ç»æåéè³ | `' + maskedEmail + '` |',
+ '| ð€ å€çäººæ Œäœ | éžæž |',
+ '| â° å€çæ¶éŽ | ' + new Date().toISOString() + ' |',
+ '',
+ '> éžæžæ žå¿å€§è已宿 SYSLOG éªæ¶ + æš¡åæ£æµ + 广æçæã',
+ '> ç»æå·²åéå°äœ çé®ç®±ïŒNotion 䟧工åå·²å建ã',
+ '>',
+ '> åŠæªæ¶å°é®ä»¶ïŒè¯·æ£æ¥ååŸç®±æéæ°æäº€ã'
+ ].join('\n');
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: number,
+ body: body
+ });
+
+ // Close the issue after processing
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: number,
+ state: 'closed',
+ labels: ['syslog', 'processed']
+ });
+
+ console.log('â
Issue åå€å·²åéå¹¶å·²å
³é');
+
+ - name: ðŽ å€±èŽ¥åèŠ
+ if: failure()
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const number = context.payload.issue?.number;
+ if (!number) return;
+
+ try {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: number,
+ body: 'â **å€ç倱莥** · è¯·æ£æ¥æäº€æ ŒåŒæ¯åŠæ£ç¡®ïŒæè系管çåã\n\n> é误诊æ
请æ¥ç [Actions æ¥å¿](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions)ã'
+ });
+ } catch (err) {
+ console.log('â ïž å€±èŽ¥åèŠåå€å€±èŽ¥: ' + err.message);
+ }
diff --git a/.gitignore b/.gitignore
index 28f589d3..a0e218d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ persona-brain-db/brain.db-shm
# persona-studio runtime artifacts
persona-studio/backend/brain/model-benchmark.json
+persona-studio/workspace/*/project-*
# palace-game runtime artifacts
modules/palace-game/data/saves/PAL-*
diff --git a/README.md b/README.md
index ebf954fe..6e1b368c 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,21 @@
---
+## ð åŒåè
å
¥å£
+
+
+
+[](https://github.com/qinfendebingshuo/guanghulab/issues/new?template=syslog-submit.yml)
+[](https://github.com/qinfendebingshuo/guanghulab/issues/new?template=dev-question.yml)
+
+> åå®äºïŒç¹èè²æé®ïŒææ¥å¿ç²èŽŽè¿å»ïŒå¡«äžé®ç®±ïŒç¹æäº€ãæ°å¹¿æäŒåå°äœ é®ç®±éã
+>
+> æé®é¢ïŒç¹ç»¿è²æé®ïŒå¡«åé®é¢æè¿°ïŒéžæžäŒèªåšåçäœ ã
+
+
+
+---
+
## ð ç³»ç»ç®ä»
**å
æ¹ïŒHoloLakeïŒ** æ¯äžäžªåºäºäººæ Œè¯èšæäœç³»ç»çæºèœåäœå¹³å°ïŒéçšå£³-æ žå犻讟计ïŒ
@@ -77,29 +92,31 @@
| æ¶éŽ | æ£æ¥é¡¹ | ç¶æ |
|------|--------|------|
-| 03-13 09:58 | â
ð éžæž CD · èªåšéšçœ²å° guanghulab.com · æå | zhizhi200271 |
-| 03-13 09:57 | â
ð¢ æŽæ°ç³»ç»å
¬ååº Â· æå | zhizhi200271 |
-| 03-13 09:56 | â¹ïž ð¢ æŽæ°ç³»ç»å
¬ååº Â· cancelled | zhizhi200271 |
-| 03-13 09:56 | ð§ ç³»ç»æŽæ°: `.github/` | éžæž (ZhùYuÄn) |
-| 03-13 09:56 | â
éžæž · Bridge E · GitHub Changes â Notion · æå | zhizhi200271 |
-| 03-13 09:56 | ð§ ç³»ç»æŽæ°: `docs/` | éžæž (ZhùYuÄn) |
-| 03-13 08:57 | â
ð¢ æŽæ°ç³»ç»å
¬ååº Â· æå | å°æ |
-| 03-13 08:56 | â
éžæž · æ¯æ¥èªæ£ · æå | å°æ |
-| 03-13 08:01 | â
Generate Session Summary for Notion · æå | å°æ |
-| 03-13 07:59 | â
éžæž · Notion å·¥å蜮询 · æå | å°æ |
-| 03-13 07:34 | â
éžæž · Notion å·¥å蜮询 · æå | å°æ |
-| 03-13 06:46 | â
éžæž · Notion å·¥å蜮询 · æå | å°æ |
-| 03-13 06:01 | â
éžæž · Notion å·¥å蜮询 · æå | å°æ |
-| 03-13 05:40 | â
éžæž · Notion å·¥å蜮询 · æå | å°æ |
-| 03-13 04:53 | â
éžæž · Notion å·¥å蜮询 · æå | å°æ |
+| 03-13 14:53 | â
ð éžæž CD · èªåšéšçœ²å° guanghulab.com · æå | å°æ |
+| 03-13 14:52 | â ð¢ æŽæ°ç³»ç»å
¬ååº Â· 倱莥 | å°æ |
+| 03-13 14:52 | ð§ ç³»ç»æŽæ°: `.github/` | éžæž (ZhùYuÄn) |
+| 03-13 14:52 | â
éžæž · Bridge E · GitHub Changes â Notion · æå | å°æ |
+| 03-13 14:52 | â¹ïž ð¢ æŽæ°ç³»ç»å
¬ååº Â· cancelled | WENZHUOXI |
+| 03-13 14:51 | â
ð éžæž CD · èªåšéšçœ²å° guanghulab.com · æå | WENZHUOXI |
+| 03-13 14:50 | â
ð¢ æŽæ°ç³»ç»å
¬ååº Â· æå | WENZHUOXI |
+| 03-13 14:50 | â
éžæž · Bridge E · GitHub Changes â Notion · æå | WENZHUOXI |
+| 03-13 14:37 | ð§ ç³»ç»æŽæ°: `scripts/` | éžæž (Copilot) |
+| 03-13 08:00 | â
éžæž PSP å·¡æ£éè¿ Â· å
šé𿣿¥é¡¹ â
| éžæžPSPå·¡æ£ |
+| 03-12 16:55 | â
æ¯æ¥å·¡æ£ â
éè¿ | å°æ |
+| 03-12 08:00 | â
éžæž PSP å·¡æ£éè¿ Â· å
šé𿣿¥é¡¹ â
| éžæžPSPå·¡æ£ |
+| 03-11 16:55 | â
æ¯æ¥å·¡æ£ â
éè¿ | å°æ |
+| 03-10 16:56 | â
æ¯æ¥å·¡æ£ â
éè¿ | å°æ |
+| 03-10 08:00 | ð§ æ žå¿å€§èå级 v3.0 · 壳-æ žåçŠ»æ¶æ · å端壳(UI/IO) + å端è(prompt/routing/memory/context) · BRAINå4æ¥å£äžçº¿ · èŠçç 7/21 (33.3%) | éžæžïŒå°ææä»€ïŒ |
### ð€ éžæžèªåšæé
-> ð¢ **仿¥æ éå°ææåšå¹²é¢** · ç³»ç»äžåæ£åžž
+> ðŽ **éèŠå°ææåšå¹²é¢ïŒ**
>
-> ðïž 2026-03-13 · éžæžèªå𿣿µ
+> - â ð¢ æŽæ°ç³»ç»å
¬ååº Â· 倱莥
+>
+> ðïž 2026-03-13 · éžæžå·²åéé®ä»¶æé
---
@@ -113,8 +130,9 @@
| æ¶éŽ | åäœè
| æš¡å | ç¶æ |
|------|--------|------|------|
-| 03-13 09:56 | zhizhi200271 | `â/` | â
äžäŒ æå |
-| 03-13 09:56 | ð€ ä¹ä¹ | `dingtalk-bot/` | ðŠ äžäŒ æå |
+| 03-13 14:52 | å°æ | `â/` | â
äžäŒ æå |
+| 03-13 14:52 | Copilot | `â/` | â
äžäŒ æå |
+| 03-13 14:50 | WENZHUOXI | `â/` | â
äžäŒ æå |
### ð€ éžæžèªåšæé · åäœè
diff --git a/app.js b/app.js
new file mode 100644
index 00000000..e69de29b
diff --git a/broadcasts/README.md b/broadcasts/README.md
new file mode 100644
index 00000000..a1d6d7bf
--- /dev/null
+++ b/broadcasts/README.md
@@ -0,0 +1,22 @@
+# ð¡ broadcasts/
+
+èªåšå¹¿æåœæ¡£ç®åœã
+
+éžæž Agent å€çå® SYSLOG åïŒäŒèªåšå°æ°å¹¿ææä»¶æšéå°æ€ç®åœïŒ
+
+```
+broadcasts/
+ DEV-001/
+ BC-M14-001-YY.md
+ DEV-012/
+ BC-M22-009-AW.md
+ BC-M23-001-AW.md
+ ...
+```
+
+**æšéè§è**ïŒ
+- è·¯åŸïŒ`broadcasts/{developer_id}/{taskId}.md`
+- æäº€ä¿¡æ¯ïŒ`[AutoBroadcast] {taskId} · {åŒåè
å} · {æš¡åå}ç¯è{N}`
+- æšéæ¹åŒïŒéè¿ GitHub APIïŒ`PUT /repos/.../contents/...`ïŒ
+
+**äžèŠæåšçŒèŸæ€ç®åœ**ïŒç±éžæž Agent èªåšç®¡çã
diff --git a/homepage/app.js b/homepage/app.js
new file mode 100644
index 00000000..f8c1138b
--- /dev/null
+++ b/homepage/app.js
@@ -0,0 +1,203 @@
+/**
+ * app.js - å
æ¹éŠé¡µå¯Œèªäžå¿
+ * M23 ç¯è1 · å
¬å蜮æ + å¡çäº€äº + ç¶æåšæå
+ */
+
+// å
¬åèœ®ææ°æ®ïŒMockæ°æ®ïŒæªæ¥å¯¹æ¥M22çå®APIïŒ
+const announcements = [
+ {
+ id: 1,
+ title: "å
æ¹å®éªå®€v1.0åŒåäž",
+ content: "å
šå¢éå¹¶è¡æšè¿ïŒå€æš¡ååæ¥å»ºè®Ÿ",
+ date: "2026-03-13",
+ type: "update"
+ },
+ {
+ id: 2,
+ title: "æš¡åè¿åºŠæ»è§",
+ content: "4䞪暡ååšçº¿ · 2䞪建讟äž",
+ date: "2026-03-13",
+ type: "status"
+ },
+ {
+ id: 3,
+ title: "æ¬¢è¿æ°åŒåè
",
+ content: "é¶ç¹åæ žé¢éæç»å£®å€§",
+ date: "2026-03-13",
+ type: "welcome"
+ }
+];
+
+// æš¡åå¡çæ°æ®
+const moduleCards = [
+ {
+ id: "M09",
+ name: "æ¶æ¯éç¥äžå¿",
+ icon: "ð",
+ status: "online",
+ path: "../notification-center/"
+ },
+ {
+ id: "M22",
+ name: "äž»åå
¬åæ ",
+ icon: "ð¢",
+ status: "online",
+ path: "../announcement/"
+ },
+ {
+ id: "M06",
+ name: "å·¥å管ç",
+ icon: "ð«",
+ status: "online",
+ path: "../ticket-system/"
+ },
+ {
+ id: "M16",
+ name: "ç åå·¥äœå°",
+ icon: "âïž",
+ status: "building",
+ path: "../writing-workspace/"
+ },
+ {
+ id: "M11",
+ name: "飿 Œç»ä»¶åº",
+ icon: "ðš",
+ status: "online",
+ path: "../ui-components/"
+ },
+ {
+ id: "M05",
+ name: "çšæ·äžå¿",
+ icon: "ð€",
+ status: "building",
+ path: "../user-center/"
+ }
+];
+
+// éŠé¡µåºçšå¯¹è±¡
+window.HomepageApp = {
+ currentAnnouncementIndex: 0,
+ carouselInterval: null,
+
+ init() {
+ this.renderCards();
+ this.renderCarousel();
+ this.startCarousel();
+ this.bindEvents();
+ this.updateStatusBar();
+ console.log('â
éŠé¡µåå§å宿 · 15è¿èç');
+ },
+
+ // æž²æå¡ç
+ renderCards() {
+ const grid = document.getElementById('moduleGrid');
+ if (!grid) return;
+
+ const cardsHTML = moduleCards.map(card => `
+
+
+ è¿å
¥æš¡å â
+
+ `).join('');
+
+ grid.innerHTML = cardsHTML;
+ },
+
+ // æž²æèœ®æ
+ renderCarousel() {
+ const carousel = document.getElementById('announcementCarousel');
+ if (!carousel) return;
+
+ const announcement = announcements[this.currentAnnouncementIndex];
+ carousel.innerHTML = `
+
+
ð¢ ${announcement.title}
+
${announcement.content}
+
${announcement.date}
+
+ `;
+ },
+
+ // å¯åšèœ®æïŒèªåš4ç§åæ¢ïŒ
+ startCarousel() {
+ if (this.carouselInterval) clearInterval(this.carouselInterval);
+ this.carouselInterval = setInterval(() => {
+ this.nextAnnouncement();
+ }, 4000);
+ console.log('ð 蜮æå·²å¯åšïŒ4ç§èªåšåæ¢');
+ },
+
+ // äžäžæ¡å
Œ
+ nextAnnouncement() {
+ this.currentAnnouncementIndex = (this.currentAnnouncementIndex + 1) % announcements.length;
+ this.renderCarousel();
+ },
+
+ // äžäžæ¡å
Œ
+ prevAnnouncement() {
+ this.currentAnnouncementIndex = (this.currentAnnouncementIndex - 1 + announcements.length) % announcements.length;
+ this.renderCarousel();
+ },
+
+ // æš¡å跳蜬
+ navigateTo(moduleId) {
+ const module = moduleCards.find(m => m.id === moduleId);
+ if (module) {
+ console.log(`ð§ è·³èœ¬å°æš¡å: ${module.name} | è·¯åŸ: ${module.path}`);
+ } else {
+ console.log(`â ïž æªæŸå°æš¡å: ${moduleId}`);
+ }
+ },
+
+ // ç»å®äºä»¶
+ bindEvents() {
+ // å¡çç¹å»äºä»¶
+ document.querySelectorAll('.card').forEach(card => {
+ card.addEventListener('click', (e) => {
+ const moduleId = card.dataset.moduleId;
+ const modulePath = card.dataset.modulePath;
+ console.log(`ð±ïž ç¹å»å¡ç: ${moduleId} | è·¯åŸ: ${modulePath}`);
+ this.navigateTo(moduleId);
+ // æªæ¥è¿éäŒå®ç°å®é
跳蜬ïŒwindow.location.href = modulePath;
+ });
+ });
+
+ // 蜮æç®å€Žç¹å»
+ document.getElementById('carouselPrev')?.addEventListener('click', () => {
+ console.log('âïž ç¹å»å·Šç®å€Ž');
+ this.prevAnnouncement();
+ });
+
+ document.getElementById('carouselNext')?.addEventListener('click', () => {
+ console.log('â¶ïž ç¹å»å³ç®å€Ž');
+ this.nextAnnouncement();
+ });
+
+ // é®çäºä»¶
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'ArrowLeft') {
+ console.log('âšïž é®çå·Šç®å€Ž');
+ this.prevAnnouncement();
+ } else if (e.key === 'ArrowRight') {
+ console.log('âšïž é®çå³ç®å€Ž');
+ this.nextAnnouncement();
+ }
+ });
+ },
+
+ // æŽæ°ç¶ææ
+ updateStatusBar() {
+ const onlineCount = moduleCards.filter(c => c.status === 'online').length;
+ const totalCount = moduleCards.length;
+ console.log(`ð ç³»ç»ç¶æ: ${onlineCount}/${totalCount} æš¡ååšçº¿`);
+ }
+};
+
+// 页é¢å 蜜æ¶èªåšåå§å
+document.addEventListener('DOMContentLoaded', () => {
+ window.HomepageApp.init();
+});
\ No newline at end of file
diff --git a/homepage/develop.md b/homepage/develop.md
new file mode 100644
index 00000000..b29eb166
--- /dev/null
+++ b/homepage/develop.md
@@ -0,0 +1,21 @@
+# M23 å
æ¹éŠé¡µäžå¯Œèªäžå¿ · åŒåæ¥å¿
+
+## ç¯è0 · 项ç®åå§åäžåºç¡åžå±
+- 宿æ¶éŽïŒ2026-03-13
+- åŒåè
ïŒDEV-012 Awen
+- åŒå¯Œäººæ ŒïŒç¥ç§
+
+### 宿å
容
+- [x] å建 homepage ç®åœå index.html/style.css/app.js
+- [x] HeroåºïŒæ é¢ãç®ä»ãè¿å
¥ç³»ç»æé®
+- [x] æš¡åçœæ ŒïŒ6䞪å¡çïŒåžŠåŸæ ãåç§°ãç¶ææ çŸ
+- [x] ååºåŒåžå±ïŒæ¡é¢3å/å¹³æ¿2å/ææº1å
+- [x] å¡çæ·¡å
¥åšç»
+- [x] ç¶ææ + Footer
+
+### åœåç³»ç»ç¶æ
+- åšçº¿æš¡åïŒ4䞪
+- å»ºè®Ÿäžæš¡åïŒ2䞪
+
+### 倿³š
+â
ç¯è0 éªæ¶éè¿ïŒåå€è¿å
¥ç¯è1
\ No newline at end of file
diff --git a/homepage/index.html b/homepage/index.html
new file mode 100644
index 00000000..90dcded3
--- /dev/null
+++ b/homepage/index.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+ å
æ¹å®éªå®€ · HoloLake éŠé¡µ
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ç³»ç»ç¶æïŒ
+ åšçº¿
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/homepage/style.css b/homepage/style.css
new file mode 100644
index 00000000..3668c249
--- /dev/null
+++ b/homepage/style.css
@@ -0,0 +1,437 @@
+/* ===== å
šå±åé ===== */
+:root {
+ --bg-primary: #0a0c14;
+ --bg-secondary: #141822;
+ --bg-card: #1e2430;
+ --text-primary: #ffffff;
+ --text-secondary: #a0a8b8;
+ --accent: #3b82f6;
+ --accent-hover: #60a5fa;
+ --online: #10b981;
+ --building: #f59e0b;
+ --border: #2a3142;
+}
+
+/* ===== åºç¡æ ·åŒ ===== */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.6;
+}
+
+.app {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+/* ===== Hero åº ===== */
+.hero {
+ text-align: center;
+ padding: 4rem 1rem;
+ background: linear-gradient(135deg, var(--bg-secondary), var(--bg-primary));
+ border-radius: 2rem;
+ margin-bottom: 3rem;
+ border: 1px solid var(--border);
+ animation: fadeIn 1s ease;
+}
+
+.hero-title {
+ font-size: 3rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+ background: linear-gradient(135deg, #fff, #94a3b8);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+.hero-subtitle {
+ display: block;
+ font-size: 1.2rem;
+ color: var(--text-secondary);
+ margin-top: 0.5rem;
+}
+
+.hero-description {
+ font-size: 1.2rem;
+ color: var(--text-secondary);
+ margin-bottom: 2rem;
+}
+
+.hero-btn {
+ display: inline-block;
+ padding: 0.8rem 2rem;
+ background: var(--accent);
+ color: white;
+ text-decoration: none;
+ border-radius: 2rem;
+ font-weight: 500;
+ transition: all 0.3s ease;
+}
+
+.hero-btn:hover {
+ background: var(--accent-hover);
+ transform: translateY(-2px);
+ box-shadow: 0 10px 20px rgba(59, 130, 246, 0.3);
+}
+
+/* ===== æš¡åçœæ Œ ===== */
+.section-title {
+ font-size: 1.8rem;
+ margin-bottom: 2rem;
+ color: var(--text-primary);
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1.5rem;
+ margin-bottom: 3rem;
+}
+
+/* å¡çæ ·åŒ */
+.card {
+ background: var(--bg-card);
+ border-radius: 1.5rem;
+ padding: 1.5rem;
+ border: 1px solid var(--border);
+ transition: all 0.3s ease;
+ animation: cardFadeIn 0.5s ease forwards;
+ opacity: 0;
+ cursor: pointer;
+}
+
+.card:hover {
+ transform: translateY(-4px);
+ border-color: var(--accent);
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.card-icon {
+ width: 40px;
+ height: 40px;
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.5rem;
+}
+
+.card-name {
+ font-size: 1.2rem;
+ font-weight: 600;
+ flex: 1;
+}
+
+.card-status {
+ font-size: 0.8rem;
+ padding: 0.2rem 0.8rem;
+ border-radius: 1rem;
+ background: var(--bg-secondary);
+}
+
+.card-status.online {
+ background: rgba(16, 185, 129, 0.2);
+ color: var(--online);
+}
+
+.card-status.building {
+ background: rgba(245, 158, 11, 0.2);
+ color: var(--building);
+}
+
+.card-link {
+ display: inline-block;
+ margin-top: 1rem;
+ color: var(--accent);
+ text-decoration: none;
+ font-size: 0.9rem;
+}
+
+.card-link:hover {
+ text-decoration: underline;
+}
+
+/* å¡çåšç»å»¶è¿ */
+.card:nth-child(1) { animation-delay: 0.1s; }
+.card:nth-child(2) { animation-delay: 0.2s; }
+.card:nth-child(3) { animation-delay: 0.3s; }
+.card:nth-child(4) { animation-delay: 0.4s; }
+.card:nth-child(5) { animation-delay: 0.5s; }
+.card:nth-child(6) { animation-delay: 0.6s; }
+
+@keyframes cardFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* ===== ç¶ææ ===== */
+.status-bar {
+ background: var(--bg-secondary);
+ padding: 1rem;
+ border-radius: 2rem;
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ border: 1px solid var(--border);
+}
+
+.status-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.status-dot.online {
+ background: var(--online);
+ box-shadow: 0 0 10px var(--online);
+}
+
+/* ===== Footer ===== */
+.footer {
+ text-align: center;
+ padding: 2rem;
+ color: var(--text-secondary);
+ border-top: 1px solid var(--border);
+}
+
+.footer p {
+ margin: 0.5rem 0;
+}
+
+/* ===== ååºåŒè®Ÿè®¡ ===== */
+/* æ¡é¢é»è®€å·²ç»æ¯3å (min-width: 1024px) */
+
+/* å¹³æ¿ (768px - 1023px) 2å */
+@media (max-width: 1023px) {
+ .grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .hero-title {
+ font-size: 2.5rem;
+ }
+}
+
+/* ææº (å°äº 768px) 1å */
+@media (max-width: 767px) {
+ .grid {
+ grid-template-columns: 1fr;
+ }
+
+ .hero-title {
+ font-size: 2rem;
+ }
+
+ .app {
+ padding: 1rem;
+ }
+}
+
+/* ===== å
šå±æ·¡å
¥åšç» ===== */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* ===== å
¬å蜮æåºæ ·åŒ ===== */
+.carousel-container {
+ position: relative;
+ background: var(--bg-secondary);
+ border-radius: 2rem;
+ padding: 2rem;
+ margin: 2rem 0 3rem;
+ border: 1px solid var(--border);
+ animation: fadeIn 0.5s ease;
+}
+
+.carousel {
+ min-height: 100px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+
+.carousel-item {
+ width: 100%;
+}
+
+.carousel-title {
+ font-size: 1.3rem;
+ color: var(--accent);
+ margin-bottom: 0.5rem;
+}
+
+.carousel-content {
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+ line-height: 1.6;
+}
+
+.carousel-date {
+ font-size: 0.8rem;
+ color: var(--text-tertiary);
+}
+
+.carousel-arrow {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ color: var(--text-primary);
+ font-size: 1.2rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s ease;
+}
+
+.carousel-arrow:hover {
+ background: var(--accent);
+ color: white;
+ border-color: var(--accent);
+}
+
+.carousel-arrow.left {
+ left: -20px;
+}
+
+.carousel-arrow.right {
+ right: -20px;
+}
+
+/* ===== å¡çæ¬åææå¢åŒº ===== */
+.card {
+ cursor: pointer;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.card:hover {
+ transform: translateY(-8px) scale(1.02);
+ border-color: var(--accent);
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
+}
+
+/* ===== ç¶ææ çŸåšæå ===== */
+.card-status.online {
+ animation: pulse 2s infinite;
+}
+
+.card-status.building {
+ animation: flicker 1.5s infinite;
+}
+
+@keyframes pulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
+ }
+ 70% {
+ box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
+ }
+}
+
+@keyframes flicker {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.6;
+ box-shadow: 0 0 10px rgba(245, 158, 11, 0.5);
+ }
+}
+
+/* ===== ååºåŒè°æŽ ===== */
+@media (max-width: 767px) {
+ .carousel-container {
+ padding: 1.5rem;
+ }
+
+ .carousel-arrow.left {
+ left: -10px;
+ }
+
+ .carousel-arrow.right {
+ right: -10px;
+ }
+}
+
+/* ===== æŽåå¢åŒºçïŒç¡®ä¿çæïŒ===== */
+.card {
+ cursor: pointer !important;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
+}
+
+.card:hover {
+ transform: translateY(-10px) scale(1.05) !important;
+ border-color: var(--accent) !important;
+ box-shadow: 0 30px 50px rgba(59, 130, 246, 0.5) !important;
+}
+
+.card-status.online {
+ animation: pulse 1.5s infinite !important;
+}
+
+.card-status.building {
+ animation: flicker 1s infinite !important;
+}
+
+@keyframes pulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
+ }
+ 50% {
+ box-shadow: 0 0 20px 10px rgba(16, 185, 129, 0.3);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
+ }
+}
+
+@keyframes flicker {
+ 0%, 100% {
+ opacity: 1;
+ text-shadow: 0 0 5px rgba(245, 158, 11, 0.5);
+ }
+ 50% {
+ opacity: 0.5;
+ text-shadow: 0 0 15px rgba(245, 158, 11, 0.8);
+ }
+}
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..e69de29b
diff --git a/persona-studio/backend/brain/agent-workflow.js b/persona-studio/backend/brain/agent-workflow.js
new file mode 100644
index 00000000..aeacc4b4
--- /dev/null
+++ b/persona-studio/backend/brain/agent-workflow.js
@@ -0,0 +1,761 @@
+/**
+ * persona-studio · éžæžä»£çåŒåå·¥äœæµåŒæïŒå£³-æ žåçŠ»æ¶æïŒ
+ *
+ * æ¶æè®Ÿè®¡ïŒ
+ * æ ž = éžæžæ žå¿å€§èïŒCore BrainïŒ
+ * â³ å 蜜åŒåè
è®°å¿ãç»åã项ç®åå²ãç¥è¯åºãæš¡åŒåº
+ * Ⳡ深床åæåŒåè
ç宿åŸ
+ * â³ çæç»æååŒå计å
+ * 壳 = éžæžä»£çïŒAgent ExecutorïŒ
+ * â³ æ¥æ¶æ žå¿å€§èæšéçåŒå计å
+ * â³ éæ¥æ§è¡ä»£ç çæ
+ * Ⳡ广æè¿åºŠç»å端
+ * â³ å®æååäŒ æ žå¿å€§èåèªè¿å
+ *
+ * æµçšïŒ
+ * çšæ·ç¹å»ãæèŠåŒåã
+ * â Agent å€éæ žå¿å€§è
+ * â æ žå¿å€§èïŒå 蜜å
šéšäžäžæ â åææåŸ â çæåŒå计å
+ * â AgentïŒæ¥æ¶è®¡å â 鿥æ§è¡ â 广æè¿åºŠ
+ * â æ žå¿å€§èïŒç¥è¯æå + æš¡åŒæŽæ° + ç»ååŠä¹ + è¿åæ¥å¿
+ */
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+const http = require('http');
+
+// ââ åžžé ââ
+// äŒ å
¥ LLM çå¯¹è¯ææ¬æå€§å笊æ°ïŒé²æ¢è¶
åºæš¡åäžäžæçªå£ïŒ
+const MAX_CONVERSATION_CHARS = 4000;
+
+// ââ æ žå¿å€§èæš¡å ââ
+const memoryManager = require('./memory-manager');
+const personaEngine = require('./persona-engine');
+const memoryInjector = require('./memory-injector');
+const knowledgeExtractor = require('./knowledge-extractor');
+const patternAnalyzer = require('./pattern-analyzer');
+const profileLearner = require('./profile-learner');
+const evolutionLogger = require('./evolution-logger');
+
+const WORKSPACE_DIR = path.join(__dirname, '..', '..', 'workspace');
+const REGISTRY_PATH = path.join(__dirname, '..', '..', 'brain', 'registry.json');
+const HUMAN_REGISTRY_PATH = path.join(__dirname, '..', '..', 'brain', 'human-registry.json');
+
+/* ================================================================
+ å·¥å
·åœæ°
+ ================================================================ */
+
+/**
+ * è°çšçšæ·æäŸç LLM APIïŒOpenAI å
Œå®¹æ ŒåŒïŒ
+ */
+function callLLM({ apiBase, apiKey, model, messages, maxTokens, temperature, timeoutMs }) {
+ return new Promise((resolve, reject) => {
+ const base = apiBase.replace(/\/+$/, '');
+ const chatUrl = new URL(base + '/chat/completions');
+ const isHttps = chatUrl.protocol === 'https:';
+ const mod = isHttps ? https : http;
+
+ const body = JSON.stringify({
+ model,
+ messages,
+ max_tokens: maxTokens || 4000,
+ temperature: temperature != null ? temperature : 0.3
+ });
+
+ const options = {
+ hostname: chatUrl.hostname,
+ port: chatUrl.port || (isHttps ? 443 : 80),
+ path: chatUrl.pathname,
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + apiKey,
+ 'Content-Length': Buffer.byteLength(body)
+ },
+ timeout: timeoutMs || 60000
+ };
+
+ const req = mod.request(options, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ try {
+ const json = JSON.parse(data);
+ if (json.choices && json.choices[0] && json.choices[0].message) {
+ resolve(json.choices[0].message.content);
+ } else if (json.error) {
+ reject(new Error(json.error.message || 'API error'));
+ } else {
+ reject(new Error('Unexpected API response'));
+ }
+ } catch (_e) {
+ reject(new Error('Failed to parse API response'));
+ }
+ });
+ });
+
+ req.on('error', reject);
+ req.on('timeout', () => {
+ req.destroy();
+ reject(new Error('Request timeout'));
+ });
+
+ req.write(body);
+ req.end();
+ });
+}
+
+/**
+ * 仿³šåè¡šæ¥æŸåŒåè
ä¿¡æ¯
+ */
+function lookupDeveloper(devId) {
+ if (!devId || devId === 'GUEST') {
+ return { dev_id: 'GUEST', name: '访客', role: 'guest' };
+ }
+ try {
+ var reg = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf-8'));
+ if (reg.developers && reg.developers[devId]) {
+ return Object.assign({ dev_id: devId }, reg.developers[devId]);
+ }
+ } catch (_e) { /* ignore */ }
+ try {
+ var hReg = JSON.parse(fs.readFileSync(HUMAN_REGISTRY_PATH, 'utf-8'));
+ if (hReg.developers) {
+ var found = hReg.developers.find(function (d) { return d.exp_id === devId; });
+ if (found) return { dev_id: devId, name: found.name, role: found.role };
+ }
+ } catch (_e) { /* ignore */ }
+ return { dev_id: devId, name: devId, role: 'developer' };
+}
+
+/* ================================================================
+ Phase 1: å€ééžæžæ žå¿å€§èïŒCore Brain WakeïŒ
+ ================================================================ */
+
+/**
+ * å€éæ žå¿å€§èïŒå 蜜å
šéšäžäžæïŒåæåŒåè
æåŸïŒçæåŒå计åã
+ *
+ * æ žå¿å€§èèå以äžä¿¡æ¯æºïŒ
+ * â åŒåè
èº«ä»œïŒæ¥èª registryïŒ
+ * ⡠对è¯è®°å¿ïŒæ¥èª memory-managerïŒ
+ * â¢ çšæ·ç»åïŒæ¥èª profile-learnerïŒææ¯æ°Žå¹³ãæ²é飿 Œã讟计å奜ïŒ
+ * ⣠项ç®åå²ïŒæ¥èª memory-managerïŒ
+ * †ç¥è¯åºïŒæ¥èª knowledge-extractorïŒ
+ * ⥠暡åŒåºïŒæ¥èª pattern-analyzerïŒ
+ * âŠ è®°å¿æèŠïŒæ¥èª memory-injectorïŒ
+ */
+async function wakeCoreBrain(devId, conversation, apiBase, apiKey, model) {
+ var devInfo = lookupDeveloper(devId);
+ var isGuest = devId === 'GUEST';
+
+ // ââ â å 蜜åŒåè
è®°å¿ ââ
+ var memory = { conversations: [], last_topic: null };
+ if (!isGuest) {
+ try { memory = memoryManager.loadMemory(devId); } catch (_e) { /* new user */ }
+ }
+
+ // ââ â¡ å èœœçšæ·ç»å ââ
+ var profile = null;
+ if (!isGuest) {
+ try { profile = profileLearner.loadProfile(devId); } catch (_e) { /* ignore */ }
+ }
+
+ // ââ ⢠å 蜜项ç®åå² ââ
+ var projects = { projects: [] };
+ if (!isGuest) {
+ try { projects = memoryManager.loadProjects(devId); } catch (_e) { /* ignore */ }
+ }
+
+ // ââ ⣠æ¥è¯¢ç¥è¯åº ââ
+ var conversationText = conversation
+ .map(function (m) { return m.content || ''; })
+ .join(' ');
+ var relevantKnowledge = [];
+ try { relevantKnowledge = knowledgeExtractor.queryKnowledge(conversationText, 5); } catch (_e) { /* ignore */ }
+
+ // ââ †æ¥è¯¢æš¡åŒåº ââ
+ var relevantPatterns = [];
+ try { relevantPatterns = patternAnalyzer.queryPatterns(conversationText); } catch (_e) { /* ignore */ }
+
+ // ââ ⥠æå»ºæ žå¿å€§è讀ç¥äžäžæ ââ
+ var brainContext = buildBrainContext(devInfo, memory, profile, projects, relevantKnowledge, relevantPatterns);
+
+ // ââ âŠ äœ¿çš LLM æ·±åºŠåææåŸ + çæåŒå计å ââ
+ var developmentPlan;
+ if (apiBase && apiKey && model) {
+ developmentPlan = await analyzeIntentWithLLM(brainContext, conversation, apiBase, apiKey, model);
+ } else {
+ developmentPlan = analyzeIntentLocally(brainContext, conversation);
+ }
+
+ return { brainContext: brainContext, developmentPlan: developmentPlan };
+}
+
+/**
+ * æå»ºæ žå¿å€§è讀ç¥äžäžæïŒèåå
šéšä¿¡æ¯æºïŒ
+ */
+function buildBrainContext(devInfo, memory, profile, projects, knowledge, patterns) {
+ return {
+ developer: {
+ id: devInfo.dev_id,
+ name: devInfo.name || devInfo.dev_id,
+ role: devInfo.role || 'developer',
+ is_guest: devInfo.dev_id === 'GUEST'
+ },
+ memory: {
+ total_conversations: (memory.conversations || []).length,
+ last_topic: memory.last_topic || null,
+ has_history: (memory.conversations || []).length > 0
+ },
+ profile: {
+ tech_level: (profile && profile.tech_assessment && profile.tech_assessment.level) || null,
+ known_skills: (profile && profile.tech_assessment && profile.tech_assessment.known_skills) || [],
+ communication_style: (profile && profile.communication_style && profile.communication_style.verbosity) || null,
+ design_preference: (profile && profile.design_preferences && profile.design_preferences.color_scheme) || null,
+ style_keywords: (profile && profile.design_preferences && profile.design_preferences.style_keywords) || [],
+ mood: (profile && profile.emotional_profile && profile.emotional_profile.current_mood) || 'neutral'
+ },
+ projects: {
+ total: (projects.projects || []).length,
+ recent: (projects.projects || []).slice(-3).map(function (p) {
+ return { name: p.name, status: p.status, files: (p.files || []).length };
+ })
+ },
+ knowledge: knowledge.map(function (k) {
+ return { type: k.type, title: k.title, category: k.category };
+ }),
+ patterns: patterns.slice(0, 3).map(function (p) {
+ return {
+ name: p.name,
+ frequency: p.frequency,
+ success_rate: p.success_rate,
+ tech_stack: p.common_tech_stack || []
+ };
+ })
+ };
+}
+
+/**
+ * äœ¿çš LLM 深床åæåŒåè
æåŸå¹¶çæåŒå计å
+ * æ žå¿å€§èå°å
šéšäžäžææ³šå
¥ system promptïŒè®© LLM çè§£åŒåè
ççæ£éæ±
+ */
+async function analyzeIntentWithLLM(brainContext, conversation, apiBase, apiKey, model) {
+ var conversationText = conversation.map(function (m) {
+ var role = m.role === 'user' ? 'çšæ·' : 'éžæž';
+ return role + ': ' + m.content;
+ }).join('\n');
+
+ // æå»ºæ žå¿å€§èç³»ç»æç€ºè¯ïŒæ³šå
¥å
šéšäžäžæïŒ
+ var systemParts = [
+ 'äœ æ¯éžæžæ žå¿å€§èçæåŸåæåŒæã',
+ 'äœ ç任塿¯æ·±åºŠçè§£åŒåè
ççå®éæ±ïŒçæç²Ÿç¡®çåŒå计åã',
+ '',
+ '## åŒåè
æ¡£æ¡',
+ '- 身仜ïŒ' + brainContext.developer.name + 'ïŒ' + brainContext.developer.id + 'ïŒ',
+ '- è§è²ïŒ' + brainContext.developer.role
+ ];
+
+ if (brainContext.developer.is_guest) {
+ systemParts.push('- â ïž è®¿å®¢çšæ·ïŒæ åå²è®°å¿ïŒ');
+ } else {
+ systemParts.push('- åå²å¯¹è¯ïŒ' + brainContext.memory.total_conversations + ' æ¡');
+ }
+
+ if (brainContext.memory.last_topic) {
+ systemParts.push('- äžæ¬¡è¯é¢ïŒ' + brainContext.memory.last_topic);
+ }
+ if (brainContext.profile.tech_level) {
+ systemParts.push('- ææ¯æ°Žå¹³ïŒ' + brainContext.profile.tech_level);
+ }
+ if (brainContext.profile.known_skills.length > 0) {
+ systemParts.push('- å·²ç¥æèœïŒ' + brainContext.profile.known_skills.join(', '));
+ }
+ if (brainContext.profile.communication_style) {
+ systemParts.push('- æ²é飿 ŒïŒ' + brainContext.profile.communication_style);
+ }
+ if (brainContext.profile.design_preference) {
+ systemParts.push('- 讟计å奜ïŒ' + brainContext.profile.design_preference);
+ }
+ if (brainContext.profile.style_keywords.length > 0) {
+ systemParts.push('- 飿 Œå
³é®è¯ïŒ' + brainContext.profile.style_keywords.join('ã'));
+ }
+
+ if (brainContext.projects.total > 0) {
+ systemParts.push('');
+ systemParts.push('## åå²é¡¹ç®ïŒå
± ' + brainContext.projects.total + ' 䞪ïŒ');
+ brainContext.projects.recent.forEach(function (p) {
+ systemParts.push('- ' + p.name + 'ïŒ' + p.status + 'ïŒ' + p.files + ' 䞪æä»¶ïŒ');
+ });
+ }
+
+ if (brainContext.knowledge.length > 0) {
+ systemParts.push('');
+ systemParts.push('## çžå
³ç¥è¯åº');
+ brainContext.knowledge.forEach(function (k) {
+ systemParts.push('- [' + k.type + '] ' + k.title);
+ });
+ }
+
+ if (brainContext.patterns.length > 0) {
+ systemParts.push('');
+ systemParts.push('## é«é¢åŒåæš¡åŒ');
+ brainContext.patterns.forEach(function (p) {
+ systemParts.push('- ' + p.name + 'ïŒ' + p.frequency + 'æ¬¡ïŒæåç' + p.success_rate + '%ïŒææ¯æ ïŒ' + p.tech_stack.join('/') + 'ïŒ');
+ });
+ }
+
+ systemParts.push('');
+ systemParts.push('## èŸåºèŠæ±');
+ systemParts.push('æ ¹æ®å¯¹è¯å
容ååŒåè
æ¡£æ¡ïŒèŸåºäž¥æ Œ JSON æ ŒåŒçåŒå计åïŒ');
+ systemParts.push('{');
+ systemParts.push(' "intent_summary": "äžå¥è¯æ»ç»åŒåè
çç宿åŸ",');
+ systemParts.push(' "project_name": "è±æé¡¹ç®å(kebab-case)",');
+ systemParts.push(' "description": "é¡¹ç®æè¿°ïŒèèåŒåè
çææ¯æ°Žå¹³åå奜ïŒ",');
+ systemParts.push(' "design_notes": "讟计èŠç¹ïŒåºäºåŒåè
ç讟计å奜å飿 Œå
³é®è¯ïŒ",');
+ systemParts.push(' "tech_decisions": "ææ¯å³ç诎æïŒåºäºåŒåè
çæèœæ°Žå¹³éæ©åéçå€æåºŠïŒ",');
+ systemParts.push(' "files": [');
+ systemParts.push(' { "filename": "index.html", "description": "è¯Šç»æä»¶è¯Žæ", "key_features": ["åèœ1","åèœ2"] }');
+ systemParts.push(' ]');
+ systemParts.push('}');
+ systemParts.push('');
+ systemParts.push('泚æïŒ');
+ systemParts.push('- åªçæå端æä»¶ïŒHTML/CSS/JSïŒïŒæå€5䞪æä»¶');
+ systemParts.push('- å¿
é¡»å
å« index.html');
+ systemParts.push('- æ ¹æ®åŒåè
ææ¯æ°Žå¹³è°æŽä»£ç å€æåºŠ');
+ systemParts.push('- æ ¹æ®åŒåè
讟计å奜å³å®è§è§é£æ Œ');
+ systemParts.push('- åŠæå¯¹è¯äžæå°äºå
·äœåèœéæ±ïŒå¡å¿
å
šéšçº³å
¥è®¡å');
+
+ try {
+ var reply = await callLLM({
+ apiBase: apiBase,
+ apiKey: apiKey,
+ model: model,
+ messages: [
+ { role: 'system', content: systemParts.join('\n') },
+ { role: 'user', content: 'ä»¥äžæ¯ååŒåè
ç宿Žå¯¹è¯ïŒè¯·åææåŸå¹¶çæåŒå计åïŒ\n\n' + conversationText.substring(0, MAX_CONVERSATION_CHARS) }
+ ],
+ maxTokens: 2000,
+ temperature: 0.2,
+ timeoutMs: 45000
+ });
+
+ var jsonMatch = reply.match(/\{[\s\S]*\}/);
+ if (jsonMatch) {
+ var plan = JSON.parse(jsonMatch[0]);
+ if (plan.files && !plan.files.some(function (f) { return f.filename === 'index.html'; })) {
+ plan.files.unshift({ filename: 'index.html', description: '䞻页é¢', key_features: ['å
¥å£é¡µé¢'] });
+ }
+ if (plan.files) {
+ plan.files = plan.files.slice(0, 5);
+ }
+ plan._source = 'core_brain_llm';
+ return plan;
+ }
+ } catch (_e) {
+ // LLM åæå€±èŽ¥ïŒéçº§å°æ¬å°åæ
+ }
+
+ return analyzeIntentLocally(brainContext, conversation);
+}
+
+/**
+ * æ¬å°æåŸåæïŒæ API æ API 倱莥æ¶çéçº§æ¹æ¡ïŒ
+ * 䜿çšè§ååŒæä»å¯¹è¯ååŒåè
æ¡£æ¡äžæåæåŸ
+ */
+function analyzeIntentLocally(brainContext, conversation) {
+ var userText = conversation
+ .filter(function (m) { return m.role === 'user'; })
+ .map(function (m) { return m.content || ''; })
+ .join(' ');
+
+ var projectType = 'éçšé¡¹ç®';
+ if (brainContext.patterns.length > 0) {
+ projectType = brainContext.patterns[0].name;
+ }
+
+ var designStyle = 'ç°ä»£ç®çºŠ';
+ if (brainContext.profile.style_keywords.length > 0) {
+ designStyle = brainContext.profile.style_keywords.join(' + ');
+ } else if (brainContext.profile.design_preference === 'dark') {
+ designStyle = 'æ·±è²äž»é¢Â·ç§ææ';
+ }
+
+ var complexity = 'moderate';
+ if (brainContext.profile.tech_level === 'beginner') {
+ complexity = 'simple';
+ } else if (brainContext.profile.tech_level === 'advanced') {
+ complexity = 'advanced';
+ }
+
+ return {
+ intent_summary: userText.substring(0, 100) || 'åŒåäžäžªçœé¡µé¡¹ç®',
+ project_name: 'web-project',
+ description: userText.substring(0, 200) || 'çœé¡µé¡¹ç®',
+ design_notes: '飿 ŒïŒ' + designStyle,
+ tech_decisions: 'å€æåºŠïŒ' + complexity + 'ïŒææ¯æ ïŒHTML/CSS/JavaScript',
+ files: [
+ { filename: 'index.html', description: '䞻页é¢', key_features: ['ååºåŒåžå±', 'æ žå¿åèœå±ç€º'] },
+ { filename: 'style.css', description: 'æ ·åŒæä»¶', key_features: [designStyle] },
+ { filename: 'main.js', description: '亀äºé»èŸ', key_features: ['çšæ·äº€äº', 'åšæææ'] }
+ ],
+ _source: 'core_brain_local'
+ };
+}
+
+/* ================================================================
+ Phase 2: éžæžä»£çæ§è¡ïŒAgent ExecuteïŒ
+ ================================================================ */
+
+/**
+ * Agent æ¥æ¶æ žå¿å€§èçåŒå计åïŒéæ¥æ§è¡ä»£ç çæ
+ */
+async function agentExecute(developmentPlan, devId, apiBase, apiKey, model, broadcastFn) {
+ var projectName = 'project-' + Date.now();
+ var files = {};
+ var filePlan = developmentPlan.files || [];
+ var hasUserApi = apiBase && apiKey && model;
+
+ for (var i = 0; i < filePlan.length; i++) {
+ var fileSpec = filePlan[i];
+
+ broadcastFn({
+ type: 'progress',
+ step: 3,
+ message: 'ð» éžæžä»£ç · çææä»¶ (' + (i + 1) + '/' + filePlan.length + '): ' + fileSpec.filename,
+ status: 'building',
+ status_text: 'çæ ' + fileSpec.filename
+ });
+
+ if (hasUserApi) {
+ try {
+ var code = await generateFileWithLLM(developmentPlan, fileSpec, filePlan, apiBase, apiKey, model);
+ files[fileSpec.filename] = code;
+ } catch (_e) {
+ files[fileSpec.filename] = generatePlaceholder(fileSpec, developmentPlan);
+ }
+ } else {
+ files[fileSpec.filename] = generatePlaceholder(fileSpec, developmentPlan);
+ }
+ }
+
+ // åå
¥ workspace
+ var projectDir = path.join(WORKSPACE_DIR, devId, projectName);
+ fs.mkdirSync(projectDir, { recursive: true });
+
+ var fileList = [];
+ var entries = Object.entries(files);
+ for (var j = 0; j < entries.length; j++) {
+ var safeName = path.basename(entries[j][0]);
+ // å®å
𿣿¥ïŒé²æ¢è·¯åŸéååéèæä»¶
+ if (!safeName || safeName.startsWith('.') || /[/\\]/.test(entries[j][0])) continue;
+ fs.writeFileSync(path.join(projectDir, safeName), entries[j][1], 'utf-8');
+ fileList.push(safeName);
+ }
+
+ // çæ README
+ var readmePath = path.join(projectDir, 'README.md');
+ if (!fs.existsSync(readmePath)) {
+ fs.writeFileSync(readmePath, [
+ '# ' + (developmentPlan.project_name || projectName),
+ '',
+ developmentPlan.description || '',
+ '',
+ '## éžæžæ žå¿å€§èåæ',
+ '- æåŸïŒ' + (developmentPlan.intent_summary || ''),
+ '- 讟计ïŒ' + (developmentPlan.design_notes || ''),
+ '- ææ¯ïŒ' + (developmentPlan.tech_decisions || ''),
+ '',
+ 'ç±å
æ¹ Persona Studio · éžæžæ žå¿å€§è + éžæžä»£ç åååŒå',
+ 'çææ¶éŽïŒ' + new Date().toISOString()
+ ].join('\n'), 'utf-8');
+ fileList.push('README.md');
+ }
+
+ return { projectName: projectName, files: fileList };
+}
+
+/**
+ * äœ¿çš LLM çæå䞪æä»¶ïŒAgent æ§è¡å±ïŒ
+ * 泚å
¥æ žå¿å€§èçåŒå计åäžäžæïŒç¡®ä¿ä»£ç äžæåŸäžèŽ
+ */
+async function generateFileWithLLM(plan, fileSpec, allFiles, apiBase, apiKey, model) {
+ var prompt = [
+ 'äœ æ¯éžæžä»£çç代ç çææš¡åãæ ¹æ®æ žå¿å€§èçåŒå计åçæä»£ç ã',
+ '',
+ '## 项ç®ä¿¡æ¯',
+ '- 项ç®ïŒ' + (plan.project_name || 'web-project'),
+ '- æè¿°ïŒ' + (plan.description || ''),
+ '- æåŸïŒ' + (plan.intent_summary || ''),
+ '- 讟计èŠç¹ïŒ' + (plan.design_notes || ''),
+ '- ææ¯å³çïŒ' + (plan.tech_decisions || ''),
+ '',
+ '## åœåæä»¶',
+ '- æä»¶åïŒ' + fileSpec.filename,
+ '- 诎æïŒ' + (fileSpec.description || ''),
+ '- æ žå¿åèœïŒ' + (fileSpec.key_features || []).join('ã'),
+ '',
+ '## 项ç®å
šéšæä»¶',
+ allFiles.map(function (f) { return '- ' + f.filename + 'ïŒ' + (f.description || '') + 'ïŒ'; }).join('\n'),
+ '',
+ 'è¯·çŽæ¥èŸåºæä»¶å
容ïŒäžèŠçš ``` 代ç åå
裹ïŒäžèŠå ä»»äœé¢å€è¯Žæã',
+ 'ç¡®ä¿ä»£ç 宿Žãå¯è¿è¡ãç¬Šåæ žå¿å€§èçè®Ÿè®¡èŠæ±ã',
+ 'åŠææ¯ HTML æä»¶ïŒèŠæ£ç¡®åŒçšå项ç®äžç CSS å JS æä»¶ã'
+ ].join('\n');
+
+ var reply = await callLLM({
+ apiBase: apiBase,
+ apiKey: apiKey,
+ model: model,
+ messages: [{ role: 'user', content: prompt }],
+ maxTokens: 4000,
+ temperature: 0.3,
+ timeoutMs: 60000
+ });
+
+ var clean = reply.trim();
+ if (clean.startsWith('```')) {
+ clean = clean.replace(/^```\S*\n?/, '').replace(/\n?```\s*$/, '');
+ }
+ return clean;
+}
+
+/**
+ * çæå äœå
å®¹ïŒæ API æåæä»¶å€±èŽ¥æ¶çéçº§æ¹æ¡ïŒ
+ */
+function generatePlaceholder(fileSpec, plan) {
+ var desc = plan.description || plan.intent_summary || plan.project_name || '项ç®';
+ var designNotes = plan.design_notes || 'ç°ä»£ç®çºŠ';
+
+ if (fileSpec.filename === 'index.html') {
+ return [
+ '',
+ '',
+ '',
+ ' ',
+ ' ',
+ ' ' + (plan.project_name || '项ç®') + '',
+ ' ',
+ '',
+ '',
+ ' ',
+ '
ð ' + desc + '
',
+ '
ç±å
æ¹ Persona Studio · éžæžæ žå¿å€§è + éžæžä»£ç åååŒå
',
+ '
è®Ÿè®¡é£æ ŒïŒ' + designNotes + '
',
+ '
',
+ ' ',
+ '',
+ ''
+ ].join('\n');
+ }
+ if (fileSpec.filename.endsWith('.css')) {
+ return [
+ '/* Generated by Persona Studio · Core Brain + Agent */',
+ '* { margin: 0; padding: 0; box-sizing: border-box; }',
+ 'body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; display: flex; align-items: center; justify-content: center; }',
+ '.container { text-align: center; padding: 2rem; max-width: 800px; }',
+ 'h1 { font-size: 2rem; margin-bottom: 1rem; background: linear-gradient(135deg, #60a5fa, #22d3ee); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }',
+ '.design-note { color: #64748b; font-size: 0.875rem; margin-top: 1rem; }'
+ ].join('\n');
+ }
+ if (fileSpec.filename.endsWith('.js')) {
+ return '// Generated by Persona Studio · Core Brain + Agent\nconsole.log("' + (plan.project_name || 'Project') + ' initialized by éžæžä»£ç");\n';
+ }
+ return '/* Generated by Persona Studio */\n';
+}
+
+/* ================================================================
+ Phase 3: æ žå¿å€§èèªè¿åïŒPost-Build EvolutionïŒ
+ ================================================================ */
+
+/**
+ * åŒå宿åïŒæ žå¿å€§èè¿è¡èªè¿åïŒ
+ * â ç¥è¯æå â æŽæ° knowledge-base.json
+ * â¡ æš¡åŒè¯å« â æŽæ° pattern-library.json
+ * ⢠ç»ååŠä¹ â æŽæ° profile.json
+ * ⣠è¿åæ¥å¿ â æŽæ° evolution-log.json
+ */
+function coreBrainPostEvolution(devId, conversation, result, brainContext, startTime, success) {
+ try {
+ var buildTimeMs = Date.now() - startTime;
+
+ // â ç¥è¯æå
+ try {
+ knowledgeExtractor.autoExtractKnowledge(devId, conversation, result.projectName);
+ } catch (_e) { /* non-fatal */ }
+
+ // â¡ æš¡åŒè¯å«
+ var patterns = [];
+ try {
+ patterns = patternAnalyzer.analyzeAndUpdatePatterns(
+ devId, conversation, result.files, buildTimeMs, success
+ );
+ } catch (_e) { /* non-fatal */ }
+
+ // ⢠ç»ååŠä¹ ïŒä»æåäžæ¡çšæ·æ¶æ¯åŠä¹ ïŒ
+ if (devId !== 'GUEST') {
+ try {
+ var lastUser = conversation.filter(function (m) { return m.role === 'user'; }).pop();
+ if (lastUser) {
+ profileLearner.updateProfile(devId, lastUser.content, result.summary || '');
+ }
+ } catch (_e) { /* non-fatal */ }
+ }
+
+ // ⣠è¿åæ¥å¿
+ evolutionLogger.logEvent('agent_build_complete', 'éžæžä»£çåŒå宿: ' + result.projectName, {
+ dev_id: devId,
+ project: result.projectName,
+ files_count: (result.files || []).length,
+ build_time_ms: buildTimeMs,
+ success: success,
+ brain_source: brainContext.developer.is_guest ? 'local' : 'llm',
+ patterns_detected: patterns,
+ developer_tech_level: brainContext.profile.tech_level,
+ developer_projects_total: brainContext.projects.total
+ });
+ } catch (err) {
+ console.error('Core brain post-evolution error:', err.message);
+ }
+}
+
+/* ================================================================
+ äž»å·¥äœæµå
¥å£
+ ================================================================ */
+
+/**
+ * è¿è¡éžæžä»£çåŒåå·¥äœæµïŒå£³-æ žå犻ïŒ
+ *
+ * @param {object} params
+ * @param {string} params.dev_id - åŒåçŒå·ïŒEXP-XXX æ GUESTïŒ
+ * @param {Array} params.conversation - 对è¯åå²
+ * @param {string} params.api_base - çšæ· API Base URL
+ * @param {string} params.api_key - çšæ· API Key
+ * @param {string} params.model - çšæ·éæ©çæš¡å
+ * @param {Function} params.broadcast - WebSocket 广æåœæ°
+ * @returns {Promise<{projectName, files, summary}>}
+ */
+async function runWorkflow({ dev_id, conversation, api_base, api_key, model, broadcast }) {
+ var emitProgress = broadcast || function () {};
+ var startTime = Date.now();
+ var brainContext = null;
+
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ // Phase 1: å€ééžæžæ žå¿å€§è
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ emitProgress({
+ type: 'progress',
+ step: 1,
+ total_steps: 5,
+ message: 'ð§ æ£åšå€ééžæžæ žå¿å€§è...',
+ status: 'building',
+ status_text: 'å€éæ žå¿å€§è'
+ });
+
+ var coreBrainResult;
+ try {
+ coreBrainResult = await wakeCoreBrain(dev_id, conversation, api_base, api_key, model);
+ brainContext = coreBrainResult.brainContext;
+ } catch (_err) {
+ brainContext = buildBrainContext(
+ lookupDeveloper(dev_id),
+ { conversations: [], last_topic: null },
+ null, { projects: [] }, [], []
+ );
+ coreBrainResult = {
+ brainContext: brainContext,
+ developmentPlan: analyzeIntentLocally(brainContext, conversation)
+ };
+ }
+
+ var plan = coreBrainResult.developmentPlan;
+
+ emitProgress({
+ type: 'progress',
+ step: 1,
+ message: 'â
æ žå¿å€§èå·²å€é · æåŸåæå®æïŒ' + (plan.intent_summary || '').substring(0, 50),
+ status: 'building',
+ status_text: 'æåŸå·²åæ'
+ });
+
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ // Phase 1.5: æ žå¿å€§èæšéåŒå计å
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ emitProgress({
+ type: 'progress',
+ step: 2,
+ total_steps: 5,
+ message: 'ð æ žå¿å€§è â 代çïŒæšéåŒå计åïŒ' + (plan.files || []).length + ' 䞪æä»¶ïŒ',
+ status: 'building',
+ status_text: '计åå·²æšé'
+ });
+
+ if (plan.design_notes) {
+ emitProgress({
+ type: 'progress',
+ step: 2,
+ message: 'ðš 讟计æä»€ïŒ' + plan.design_notes,
+ status: 'building',
+ status_text: '讟计已确讀'
+ });
+ }
+
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ // Phase 2: éžæžä»£çæ§è¡åŒå
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ emitProgress({
+ type: 'progress',
+ step: 3,
+ total_steps: 5,
+ message: 'ð» éžæžä»£ç · åŒå§æ§è¡æ žå¿å€§èçåŒå计å...',
+ status: 'building',
+ status_text: 'ä»£çæ§è¡äž'
+ });
+
+ var agentResult = await agentExecute(plan, dev_id, api_base, api_key, model, emitProgress);
+
+ emitProgress({
+ type: 'progress',
+ step: 3,
+ message: 'â
ä»£çæ§è¡å®æïŒ' + agentResult.files.length + ' 䞪æä»¶å·²çæ',
+ status: 'building',
+ status_text: '代ç å·²çæ'
+ });
+
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ // Phase 2.5: é¢è§éšçœ²
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ emitProgress({
+ type: 'progress',
+ step: 4,
+ total_steps: 5,
+ message: 'ð éžæžä»£ç · éšçœ²é¢è§...',
+ status: 'building',
+ status_text: 'éšçœ²é¢è§'
+ });
+
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ // Phase 3: æ žå¿å€§èèªè¿å
+ // ââââââââââââââââââââââââââââââââââââââââââââ
+ var finalResult = {
+ projectName: agentResult.projectName,
+ files: agentResult.files,
+ summary: 'é¡¹ç® ' + agentResult.projectName + ' ç±éžæžæ žå¿å€§èåææåŸ + éžæžä»£çæ§è¡åŒå宿ïŒå
å« ' + agentResult.files.length + ' 䞪æä»¶ã\næåŸïŒ' + (plan.intent_summary || '') + '\n讟计ïŒ' + (plan.design_notes || '')
+ };
+
+ coreBrainPostEvolution(dev_id, conversation, finalResult, brainContext, startTime, true);
+
+ emitProgress({
+ type: 'progress',
+ step: 5,
+ total_steps: 5,
+ message: 'âš éžæžæ žå¿å€§è · èªè¿å宿 · ç¥è¯äžæš¡åŒå·²æŽæ°',
+ status: 'building',
+ status_text: 'å³å°å®æ'
+ });
+
+ return finalResult;
+}
+
+module.exports = {
+ runWorkflow
+};
diff --git a/persona-studio/backend/routes/build.js b/persona-studio/backend/routes/build.js
index a60dfb18..73a7d0a4 100644
--- a/persona-studio/backend/routes/build.js
+++ b/persona-studio/backend/routes/build.js
@@ -1,21 +1,29 @@
/**
* persona-studio · åŒåä»»å¡è·¯ç±
- * POST /api/ps/build/start è§Šå代ç çæ
+ * POST /api/ps/build/start è§Šåéžæžä»£çåŒåå·¥äœæµ
+ *
+ * åœçšæ·ç¹å»ãæèŠåŒåãæ¶ïŒèªåšè§Šåéžæžä»£çïŒAgentïŒ
+ * 䜿çšçšæ·èªåžŠç API Key è¿è¡ 5 æ¥èªåšååŒåã
*/
const express = require('express');
const router = express.Router();
const memoryManager = require('../brain/memory-manager');
-const codeGenerator = require('../brain/code-generator');
+const agentWorkflow = require('../brain/agent-workflow');
const emailSender = require('../utils/email-sender');
// é®ç®±å端æ£åäºæ¬¡æ ¡éª
const EMAIL_RE = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
+// æ¯æ EXP-XXX å GUEST äž€ç§ dev_id æ ŒåŒ
+function isValidDevId(devId) {
+ return devId === 'GUEST' || /^EXP-\d{3,}$/.test(devId);
+}
+
// POST /api/ps/build/start
router.post('/start', async (req, res) => {
- const { dev_id, email, contact, conversation } = req.body || {};
+ const { dev_id, email, contact, conversation, api_base, api_key, model } = req.body || {};
- if (!dev_id || !/^EXP-\d{3,}$/.test(dev_id)) {
+ if (!dev_id || !isValidDevId(dev_id)) {
return res.status(400).json({
error: true,
code: 'INVALID_ID',
@@ -40,71 +48,94 @@ router.post('/start', async (req, res) => {
});
}
+ // 鲿¢ header injectionïŒAPI Key äžåŸå
嫿¢è¡ç¬Š
+ if (api_key && /[\r\n]/.test(api_key)) {
+ return res.status(400).json({
+ error: true,
+ code: 'INVALID_API_KEY',
+ message: 'API Key æ ŒåŒæ æ'
+ });
+ }
+
// èç³»æ¹åŒèŸå
¥æž
çïŒå»é€æœåš HTML/script 泚å
¥ïŒ
const safeContact = contact ? String(contact).replace(/[<>&"']/g, '').substring(0, 100) : null;
// å
ç«å³ååºïŒåå°åŒæ¥å€ç
res.json({
error: false,
- message: 'åŒåä»»å¡å·²æ¥æ¶ïŒå®æåå°åéå° ' + email,
- status: 'queued'
+ message: 'éžæžä»£çå·²å¯åšïŒå®æåå°åéå° ' + email,
+ status: 'agent_started'
});
- // åŒæ¥æ§è¡ä»£ç çæ + é®ä»¶éç¥
+ // åŒæ¥æ§è¡éžæžä»£çåŒåå·¥äœæµ
(async () => {
- const broadcast = req.app.locals.broadcastToClient || function () {};
+ const broadcastRaw = req.app.locals.broadcastToClient || function () {};
+ const broadcast = function (data) { broadcastRaw(dev_id, data); };
try {
- broadcast(dev_id, {
+ // å¯åš Agent å·¥äœæµ
+ broadcast({
type: 'progress',
- message: 'ð§ æ£åšå建项ç®éªšæ¶...',
+ message: 'ð éžæžä»£çå·²å€éïŒæ£åšå¯åšåŒåå·¥äœæµ...',
status: 'building',
- status_text: 'æå»ºäž'
+ status_text: '代çå·²å¯åš'
});
- const result = await codeGenerator.generate({
- dev_id,
+ const result = await agentWorkflow.runWorkflow({
+ dev_id: dev_id,
conversation: conversation || [],
+ api_base: api_base || '',
+ api_key: api_key || '',
+ model: model || '',
+ broadcast: broadcast
});
- // è®°åœé¡¹ç®
- memoryManager.addProject(dev_id, {
- name: result.projectName || 'untitled',
- email: email,
- contact: safeContact,
- status: 'completed',
- created_at: new Date().toISOString(),
- files: result.files || []
- });
+ // è®°åœé¡¹ç®ïŒGUEST ä¹è®°åœïŒæ¹äŸ¿è¿œèžªïŒ
+ try {
+ memoryManager.addProject(dev_id, {
+ name: result.projectName || 'untitled',
+ email: email,
+ contact: safeContact,
+ status: 'completed',
+ created_at: new Date().toISOString(),
+ files: result.files || []
+ });
- // èªåšæåç¥è¯
- memoryManager.autoExtractKnowledge(dev_id, conversation, result.projectName);
+ // èªåšæåç¥è¯ïŒä»
泚ååŒåè
ïŒ
+ if (dev_id !== 'GUEST') {
+ memoryManager.autoExtractKnowledge(dev_id, conversation, result.projectName);
+ }
+ } catch (memErr) {
+ console.error('Memory save error (non-fatal):', memErr.message);
+ }
// éç¥é¢è§å°±ç»ª
- broadcast(dev_id, {
+ broadcast({
type: 'preview_ready',
project: result.projectName,
message: 'â
é¢è§å·²å°±ç»ª'
});
- broadcast(dev_id, {
+ broadcast({
type: 'complete',
message: 'ð å
šéšå®æïŒé®ä»¶æ£åšåé'
});
- // åé®ä»¶
- await emailSender.sendCompletion({
+ // åé®ä»¶ïŒåŒæ¥ïŒäžé»å¡äž»æµçšïŒ
+ emailSender.sendCompletion({
to: email,
- dev_id,
+ dev_id: dev_id,
projectName: result.projectName,
summary: result.summary,
files: result.files
+ }).catch(function (emailErr) {
+ console.error('Email send error (non-fatal):', emailErr.message);
});
} catch (err) {
- console.error('Build pipeline error:', err.message);
- broadcast(dev_id, {
+ console.error('Agent workflow error:', err.message);
+ broadcast({
type: 'error',
- message: 'æå»ºè¿çšåºé: ' + err.message
+ message: 'éžæžä»£çéå°é®é¢: ' + err.message
});
}
})();
diff --git a/persona-studio/brain/evolution-log.json b/persona-studio/brain/evolution-log.json
index a51724be..7d5a4016 100644
--- a/persona-studio/brain/evolution-log.json
+++ b/persona-studio/brain/evolution-log.json
@@ -1,7 +1,43 @@
{
"schema_version": "1.0",
"description": "ç³»ç»è¿åæ¥å¿ · è®°åœæ¯æ¬¡èªè¿åäºä»¶",
- "last_updated": null,
- "total_events": 0,
- "events": []
-}
+ "last_updated": "2026-03-13T05:55:39.412Z",
+ "total_events": 2,
+ "events": [
+ {
+ "id": "EVT-1773380616767",
+ "type": "build_complete",
+ "description": "é¡¹ç®æå»ºå®æ: project-1773380616766",
+ "metadata": {
+ "dev_id": "EXP-000",
+ "project": "project-1773380616766",
+ "files_count": 4,
+ "build_time_ms": 0,
+ "success": true,
+ "patterns_detected": [
+ "éçšé¡¹ç®"
+ ]
+ },
+ "timestamp": "2026-03-13T05:43:36.767Z"
+ },
+ {
+ "id": "EVT-1773381339412",
+ "type": "agent_build_complete",
+ "description": "éžæžä»£çåŒå宿: project-1773381339411",
+ "metadata": {
+ "dev_id": "GUEST",
+ "project": "project-1773381339411",
+ "files_count": 4,
+ "build_time_ms": 1,
+ "success": true,
+ "brain_source": "local",
+ "patterns_detected": [
+ "䞪人å客"
+ ],
+ "developer_tech_level": null,
+ "developer_projects_total": 0
+ },
+ "timestamp": "2026-03-13T05:55:39.412Z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/persona-studio/brain/memory/EXP-000/projects.json b/persona-studio/brain/memory/EXP-000/projects.json
new file mode 100644
index 00000000..01804274
--- /dev/null
+++ b/persona-studio/brain/memory/EXP-000/projects.json
@@ -0,0 +1,32 @@
+{
+ "dev_id": "EXP-000",
+ "projects": [
+ {
+ "name": "project-1773380616766",
+ "email": "test@test.com",
+ "contact": null,
+ "status": "completed",
+ "created_at": "2026-03-13T05:43:36.768Z",
+ "files": [
+ "index.html",
+ "style.css",
+ "main.js",
+ "README.md"
+ ]
+ },
+ {
+ "name": "project-1773380932239",
+ "email": "test@test.com",
+ "contact": null,
+ "status": "completed",
+ "created_at": "2026-03-13T05:48:52.239Z",
+ "files": [
+ "index.html",
+ "style.css",
+ "main.js",
+ "README.md"
+ ]
+ }
+ ],
+ "updated_at": "2026-03-13T05:48:52.239Z"
+}
\ No newline at end of file
diff --git a/persona-studio/brain/memory/GUEST/projects.json b/persona-studio/brain/memory/GUEST/projects.json
new file mode 100644
index 00000000..623b2b3b
--- /dev/null
+++ b/persona-studio/brain/memory/GUEST/projects.json
@@ -0,0 +1,32 @@
+{
+ "dev_id": "GUEST",
+ "projects": [
+ {
+ "name": "project-1773380932224",
+ "email": "test@test.com",
+ "contact": null,
+ "status": "completed",
+ "created_at": "2026-03-13T05:48:52.225Z",
+ "files": [
+ "index.html",
+ "style.css",
+ "main.js",
+ "README.md"
+ ]
+ },
+ {
+ "name": "project-1773381339411",
+ "email": "test@example.com",
+ "contact": null,
+ "status": "completed",
+ "created_at": "2026-03-13T05:55:39.412Z",
+ "files": [
+ "index.html",
+ "style.css",
+ "main.js",
+ "README.md"
+ ]
+ }
+ ],
+ "updated_at": "2026-03-13T05:55:39.412Z"
+}
\ No newline at end of file
diff --git a/persona-studio/brain/pattern-library.json b/persona-studio/brain/pattern-library.json
index 9741c176..9889a7e9 100644
--- a/persona-studio/brain/pattern-library.json
+++ b/persona-studio/brain/pattern-library.json
@@ -1,7 +1,32 @@
{
"schema_version": "1.0",
"description": "ç³»ç»æš¡åŒåº · èªåšè¯å« · æ¯å®æäžäžªé¡¹ç®+æ¯æ¥èåæŽæ°",
- "last_updated": null,
- "total_patterns": 0,
- "patterns": []
-}
+ "last_updated": "2026-03-13T05:55:39.412Z",
+ "total_patterns": 2,
+ "patterns": [
+ {
+ "name": "éçšé¡¹ç®",
+ "frequency": 1,
+ "common_features": [],
+ "common_tech_stack": [
+ "HTML/CSS/JS"
+ ],
+ "avg_build_time": null,
+ "success_rate": 100,
+ "first_seen": "2026-03-13T05:43:36.767Z",
+ "last_seen": "2026-03-13T05:43:36.767Z"
+ },
+ {
+ "name": "䞪人å客",
+ "frequency": 1,
+ "common_features": [],
+ "common_tech_stack": [
+ "HTML/CSS/JS"
+ ],
+ "avg_build_time": 1,
+ "success_rate": 100,
+ "first_seen": "2026-03-13T05:55:39.412Z",
+ "last_seen": "2026-03-13T05:55:39.412Z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/persona-studio/frontend/chat.js b/persona-studio/frontend/chat.js
index 44bbb726..2d263e98 100644
--- a/persona-studio/frontend/chat.js
+++ b/persona-studio/frontend/chat.js
@@ -640,28 +640,42 @@ async function confirmBuild() {
if (contact) sessionStorage.setItem('ps_build_contact', contact);
closeEmailModal();
- appendMessage('system', 'ð åŒåä»»å¡å·²æäº€ïŒå®æåäŒåéå° ' + email);
+ appendMessage('system', 'ð éžæžä»£çå·²å¯åšïŒå®æåäŒåéå° ' + email);
// è¿å
¥å屿š¡åŒ
enterDevMode();
+ // å
è¿æ¥ WebSocketïŒç¡®ä¿åš build åŒå§å建ç«è¿æ¥ïŒé¿å
䞢倱è¿åºŠæ¶æ¯ïŒ
+ connectPreviewWebSocket();
+
try {
- await fetch(API_BASE + '/api/ps/build/start', {
+ var buildRes = await fetch(API_BASE + '/api/ps/build/start', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({
dev_id: DEV_ID,
email: email,
contact: contact,
- conversation: conversationHistory
+ conversation: conversationHistory,
+ api_base: USER_API_BASE,
+ api_key: USER_API_KEY,
+ model: SELECTED_MODEL
})
});
- } catch (_err) {
- appendMessage('system', 'ä»»å¡æäº€å€±èŽ¥ïŒè¯·çšååè¯');
- }
- // è¿æ¥ WebSocket è·åè¿åºŠæŽæ°
- connectPreviewWebSocket();
+ if (!buildRes.ok) {
+ var errMsg = 'HTTP ' + buildRes.status + ' ' + buildRes.statusText;
+ try {
+ var errData = await buildRes.json();
+ if (errData.message) errMsg = errData.message;
+ } catch (_e) { /* use status text fallback */ }
+ appendMessage('system', 'â ïž éžæžä»£çå¯åšå€±èŽ¥: ' + errMsg);
+ updatePreviewStatus('error', 'å¯åšå€±èŽ¥');
+ }
+ } catch (_err) {
+ appendMessage('system', 'â ïž ä»»å¡æäº€å€±èŽ¥ïŒè¯·æ£æ¥çœç»è¿æ¥ååè¯');
+ updatePreviewStatus('error', 'çœç»é误');
+ }
}
/* ---- Dev Mode: Split Screen ---- */
diff --git a/scripts/create-standardized-ticket.js b/scripts/create-standardized-ticket.js
new file mode 100644
index 00000000..4bfb5b1f
--- /dev/null
+++ b/scripts/create-standardized-ticket.js
@@ -0,0 +1,197 @@
+// scripts/create-standardized-ticket.js
+// éžæž · Phase B1 · æ ååå·¥ååå»ºèæ¬
+//
+// å° SYSLOG åå
¥ Notion å·¥åéåæ¶äœ¿çšæ ååæ ŒåŒïŒ
+// 让 Notion Agent èœå€è¯å«åè§Šååç»å€çã
+//
+// ç¯å¢åéïŒ
+// NOTION_TOKEN Notion API token
+// NOTION_TICKET_DB_ID å·¥åéåæ°æ®åº ID
+// BROADCAST_ID 广æçŒå·ïŒåŠ BC-M23-001-AWïŒ
+// DEVELOPER åŒåè
ä¿¡æ¯ïŒåŠ "DEV-012 Awen"ïŒ
+// SYSLOG_RAW å®æŽ SYSLOG åæïŒJSON å笊䞲æçº¯ææ¬ïŒ
+// SUBMIT_TYPE æäº€ç±»åïŒsyslog / questionïŒ
+// PERSONA_RESULT äººæ Œäœå€çç»æïŒå¯éïŒéŠæ¬¡å¯äžºç©ºïŒ
+// MODULE_VERIFY æš¡åéªè¯ç»æ JSONïŒå¯éïŒ
+// MODULES_UPLOADED æš¡åæ¯åŠå
šéšäžäŒ ïŒtrue/falseïŒ
+
+'use strict';
+
+const https = require('https');
+const fs = require('fs');
+
+const NOTION_TOKEN = process.env.NOTION_TOKEN || '';
+const NOTION_TICKET_DB_ID = process.env.NOTION_TICKET_DB_ID || '';
+const BROADCAST_ID = process.env.BROADCAST_ID || 'UNKNOWN';
+const DEVELOPER = process.env.DEVELOPER || process.env.AUTHOR || 'unknown';
+const SYSLOG_RAW = process.env.SYSLOG_RAW || process.env.SUBMIT_CONTENT || '';
+const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'syslog';
+const PERSONA_RESULT = process.env.PERSONA_RESULT || '';
+const MODULE_VERIFY = process.env.MODULE_VERIFY || '';
+const MODULES_UPLOADED = process.env.MODULES_UPLOADED || 'false';
+
+const NOTION_VERSION = '2022-06-28';
+const NOTION_API_HOSTNAME = 'api.notion.com';
+const NOTION_RICH_TEXT_MAX = 2000;
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Notion API å·¥å
·
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function notionPost(endpoint, body) {
+ return new Promise(function (resolve, reject) {
+ var payload = JSON.stringify(body);
+ var opts = {
+ hostname: NOTION_API_HOSTNAME,
+ port: 443,
+ path: endpoint,
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + NOTION_TOKEN,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': NOTION_VERSION,
+ 'Content-Length': Buffer.byteLength(payload),
+ },
+ };
+ var req = https.request(opts, function (res) {
+ var data = '';
+ res.on('data', function (chunk) { data += chunk; });
+ res.on('end', function () {
+ try {
+ var parsed = JSON.parse(data);
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ resolve(parsed);
+ } else {
+ reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
+ }
+ } catch (e) {
+ reject(new Error('Notion API parse error: ' + data));
+ }
+ });
+ });
+ req.on('error', reject);
+ req.write(payload);
+ req.end();
+ });
+}
+
+function richTextChunks(content) {
+ var str = String(content || '');
+ var chunks = [];
+ for (var i = 0; i < str.length; i += NOTION_RICH_TEXT_MAX) {
+ chunks.push({ type: 'text', text: { content: str.slice(i, i + NOTION_RICH_TEXT_MAX) } });
+ }
+ if (chunks.length === 0) {
+ chunks.push({ type: 'text', text: { content: '' } });
+ }
+ return chunks;
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// æ ååå·¥åå建
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function createStandardizedTicket() {
+ if (!NOTION_TOKEN || !NOTION_TICKET_DB_ID) {
+ console.log('â ïž Notion credentials not configured, skipping ticket creation');
+ process.exit(0);
+ }
+
+ var now = new Date().toISOString();
+ var typeLabel = SUBMIT_TYPE === 'syslog' ? 'SYSLOGå€ç' : 'æé®è§£ç';
+ var ticketStatus = SUBMIT_TYPE === 'syslog' ? 'åŸ
å€ç' : '已宿';
+ var verifyStatus = MODULES_UPLOADED === 'true' ? 'æš¡åå·²éªè¯â
' : 'æš¡ååŸ
éªè¯â ïž';
+ var title = '[èªåš] ' + BROADCAST_ID + ' · ' + typeLabel + ' · ' + verifyStatus;
+
+ // Phase B1 æ åååæ®µ â å·¥åå
容
+ var ticketContent = [
+ '## ð¡ æ ååå·¥å · Phase B1',
+ '',
+ '| åæ®µ | åŒ |',
+ '|------|-----|',
+ '| å·¥åç±»å | ' + typeLabel + ' |',
+ '| ç¶æ | ' + ticketStatus + ' |',
+ '| æ¥æº | GitHub Actions |',
+ '| taskId | ' + BROADCAST_ID + ' |',
+ '| developer | ' + DEVELOPER + ' |',
+ '| created_at | ' + now + ' |',
+ '| retry_count | 0 |',
+ '| receipt_status | pending |',
+ '',
+ ];
+
+ if (PERSONA_RESULT) {
+ ticketContent.push('## éžæžæ žå¿å€§èå€çç»æ');
+ ticketContent.push('');
+ ticketContent.push(PERSONA_RESULT.slice(0, 1500));
+ ticketContent.push('');
+ }
+
+ ticketContent.push('## æš¡åäžäŒ éªè¯');
+ ticketContent.push('');
+ ticketContent.push('æš¡åå
šéšäžäŒ : ' + (MODULES_UPLOADED === 'true' ? 'â
æ¯' : 'â åŠ'));
+ ticketContent.push('');
+
+ if (MODULE_VERIFY) {
+ ticketContent.push('éªè¯è¯Šæ
: ' + MODULE_VERIFY.slice(0, 400));
+ ticketContent.push('');
+ }
+
+ var contentText = ticketContent.join('\n');
+
+ // æå»º Notion 页é¢
+ var body = {
+ parent: { database_id: NOTION_TICKET_DB_ID },
+ properties: {
+ 'æ é¢': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] },
+ 'æäœç±»å': { select: { name: 'å
¶ä»' } },
+ 'æäº€è
': { rich_text: [{ type: 'text', text: { content: 'éžæžAgent·èªåšç®¡é' } }] },
+ 'ç¶æ': { select: { name: ticketStatus } },
+ 'äŒå
级': { select: { name: 'P1' } },
+ },
+ children: [
+ {
+ object: 'block',
+ type: 'paragraph',
+ paragraph: {
+ rich_text: richTextChunks(contentText),
+ },
+ },
+ ],
+ };
+
+ // å° SYSLOG åæäœäžºä»£ç åéå ïŒåŠææïŒ
+ if (SYSLOG_RAW) {
+ body.children.push({
+ object: 'block',
+ type: 'code',
+ code: {
+ rich_text: richTextChunks(SYSLOG_RAW.slice(0, 8000)),
+ language: 'json',
+ },
+ });
+ }
+
+ console.log('ð å建æ ååå·¥å...');
+ console.log(' taskId: ' + BROADCAST_ID);
+ console.log(' developer: ' + DEVELOPER);
+ console.log(' type: ' + typeLabel);
+ console.log(' receipt_status: pending');
+
+ try {
+ var result = await notionPost('/v1/pages', body);
+ console.log('â
Notion æ ååå·¥åå·²å建: ' + result.id);
+
+ // èŸåºå·¥å ID å° GITHUB_OUTPUT
+ var outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ fs.appendFileSync(outputFile, 'ticket_page_id=' + result.id + '\n');
+ fs.appendFileSync(outputFile, 'ticket_url=' + (result.url || '') + '\n');
+ }
+ } catch (err) {
+ console.error('â å·¥åå建倱莥: ' + err.message);
+ process.exit(1);
+ }
+}
+
+createStandardizedTicket();
diff --git a/scripts/invoke-persona.js b/scripts/invoke-persona.js
new file mode 100644
index 00000000..c88cbd5c
--- /dev/null
+++ b/scripts/invoke-persona.js
@@ -0,0 +1,360 @@
+// scripts/invoke-persona.js
+// éžæž · Phase B2 · äººæ Œäœå€éè°çšèæ¬
+//
+// Notion Agent è°çšæ€èæ¬ïŒéè¿ workflow_dispatchïŒïŒ
+// èæ¬è¯»å Notion å·¥åå
容 â å€éäººæ Œäœ â å€çç»æåå Notionã
+//
+// ç¯å¢åéïŒ
+// WORK_ORDER_ID Notion å·¥åé¡µé¢ ID
+// TASK_ID 广æçŒå·ïŒåŠ BC-M23-001-AWïŒ
+// DEVELOPER åŒåè
ä¿¡æ¯ïŒåŠ "DEV-012 Awen"ïŒ
+// SYSLOG_RAW SYSLOG JSON åæ
+// ACTION åšäœç±»åïŒprocess_syslog / retryïŒ
+// LLM_API_KEY ç¬¬äžæ¹ LLM å¹³å°å¯é¥
+// LLM_BASE_URL ç¬¬äžæ¹ LLM å¹³å° API å°å
+// NOTION_TOKEN Notion API token
+// CORE_BRAIN_PAGE_ID æå¥æ žå¿å€§è v4.0 é¡µé¢ ID
+// PORTRAIT_DB_ID åŒåè
åšæç»ååº ID
+// FINGERPRINT_DB_ID æš¡åæçº¹æ³šå衚 ID
+// INVOKE_API_KEY éŽæå¯é¥ïŒçšäº API è°çšéªè¯ïŒ
+
+'use strict';
+
+const https = require('https');
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const childProcess = require('child_process');
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// é
眮
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+var WORK_ORDER_ID = process.env.WORK_ORDER_ID || '';
+var TASK_ID = process.env.TASK_ID || process.env.BROADCAST_ID || 'UNKNOWN';
+var DEVELOPER = process.env.DEVELOPER || 'unknown';
+var SYSLOG_RAW = process.env.SYSLOG_RAW || '';
+var ACTION = process.env.ACTION || 'process_syslog';
+
+var LLM_API_KEY = process.env.LLM_API_KEY || '';
+var LLM_BASE_URL = (process.env.LLM_BASE_URL || '').replace(/\/+$/, '');
+var NOTION_TOKEN = process.env.NOTION_TOKEN || '';
+var CORE_BRAIN_PAGE_ID = process.env.CORE_BRAIN_PAGE_ID || '';
+var PORTRAIT_DB_ID = process.env.PORTRAIT_DB_ID || '';
+var FINGERPRINT_DB_ID = process.env.FINGERPRINT_DB_ID || '';
+
+var NOTION_VERSION = '2022-06-28';
+var NOTION_API_HOSTNAME = 'api.notion.com';
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// HTTP å·¥å
·
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function httpRequest(url, options, body) {
+ return new Promise(function (resolve, reject) {
+ var parsed = new URL(url);
+ var isHttps = parsed.protocol === 'https:';
+ var mod = isHttps ? https : http;
+
+ var opts = {
+ hostname: parsed.hostname,
+ port: parsed.port || (isHttps ? 443 : 80),
+ path: parsed.pathname + parsed.search,
+ method: options.method || 'GET',
+ headers: options.headers || {},
+ timeout: options.timeout || 120000,
+ };
+
+ var req = mod.request(opts, function (res) {
+ var data = '';
+ res.on('data', function (chunk) { data += chunk; });
+ res.on('end', function () {
+ resolve({ status: res.statusCode, body: data });
+ });
+ });
+
+ req.on('error', reject);
+ req.on('timeout', function () { req.destroy(); reject(new Error('Request timeout')); });
+
+ if (body) {
+ req.write(typeof body === 'string' ? body : JSON.stringify(body));
+ }
+ req.end();
+ });
+}
+
+function notionRequest(method, endpoint, body) {
+ var url = 'https://' + NOTION_API_HOSTNAME + endpoint;
+ var headers = {
+ 'Authorization': 'Bearer ' + NOTION_TOKEN,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': NOTION_VERSION,
+ };
+ return httpRequest(url, { method: method, headers: headers }, body ? JSON.stringify(body) : null)
+ .then(function (res) {
+ var parsed = JSON.parse(res.body);
+ if (res.status >= 200 && res.status < 300) return parsed;
+ throw new Error('Notion API ' + res.status + ': ' + (parsed.message || res.body));
+ });
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 1: 读åå·¥åå
容ïŒä» NotionïŒ
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function readWorkOrder() {
+ if (!WORK_ORDER_ID) {
+ console.log('â¹ïž No WORK_ORDER_ID, using SYSLOG_RAW directly');
+ return { syslog_raw: SYSLOG_RAW, taskId: TASK_ID, developer: DEVELOPER };
+ }
+
+ console.log('ð 读å Notion å·¥å: ' + WORK_ORDER_ID);
+ try {
+ var page = await notionRequest('GET', '/v1/pages/' + WORK_ORDER_ID);
+ var props = page.properties || {};
+
+ // æåå·¥ååæ®µ
+ var taskId = TASK_ID;
+ var developer = DEVELOPER;
+
+ // å°è¯ä»å·¥å屿§äžè¯»å
+ if (props['广æçŒå·'] && props['广æçŒå·'].rich_text) {
+ var bcText = props['广æçŒå·'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ if (bcText) taskId = bcText;
+ }
+ if (props['åŒåè
çŒå·'] && props['åŒåè
çŒå·'].rich_text) {
+ var devText = props['åŒåè
çŒå·'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ if (devText) developer = devText;
+ }
+
+ // 读å页é¢å
容ïŒååïŒæ¥è·å SYSLOG åæ
+ var blocks = await notionRequest('GET', '/v1/blocks/' + WORK_ORDER_ID + '/children?page_size=20');
+ var syslogRaw = SYSLOG_RAW;
+
+ if (blocks.results) {
+ blocks.results.forEach(function (block) {
+ if (block.type === 'code' && block.code && block.code.rich_text) {
+ var codeText = block.code.rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ if (codeText.length > syslogRaw.length) {
+ syslogRaw = codeText;
+ }
+ }
+ });
+ }
+
+ console.log(' â taskId: ' + taskId);
+ console.log(' â developer: ' + developer);
+ console.log(' â syslog_raw length: ' + syslogRaw.length);
+
+ return { syslog_raw: syslogRaw, taskId: taskId, developer: developer };
+ } catch (err) {
+ console.log('â ïž å·¥å读å倱莥: ' + err.message + 'ïŒäœ¿çšç¯å¢åé');
+ return { syslog_raw: SYSLOG_RAW, taskId: TASK_ID, developer: DEVELOPER };
+ }
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 2: å€éäººæ ŒäœïŒå€çš wake-persona.js çé»èŸïŒ
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function invokePersona(workOrder) {
+ console.log('ð§ å€éäººæ Œäœå€ç SYSLOG...');
+
+ // 讟眮ç¯å¢åéäŸ wake-persona.js 读å
+ process.env.BROADCAST_ID = workOrder.taskId;
+ process.env.SUBMIT_TYPE = 'syslog';
+ process.env.SUBMIT_CONTENT = workOrder.syslog_raw;
+ process.env.AUTHOR = workOrder.developer;
+
+ // æ§è¡ wake-persona.jsïŒfork åè¿çšïŒ
+ var wakeScript = path.join(__dirname, 'wake-persona.js');
+
+ return new Promise(function (resolve, reject) {
+ var child = childProcess.fork(wakeScript, [], {
+ env: Object.assign({}, process.env, {
+ BROADCAST_ID: workOrder.taskId,
+ SUBMIT_TYPE: 'syslog',
+ SUBMIT_CONTENT: workOrder.syslog_raw,
+ AUTHOR: workOrder.developer,
+ LLM_API_KEY: LLM_API_KEY,
+ LLM_BASE_URL: LLM_BASE_URL,
+ NOTION_TOKEN: NOTION_TOKEN,
+ CORE_BRAIN_PAGE_ID: CORE_BRAIN_PAGE_ID,
+ PORTRAIT_DB_ID: PORTRAIT_DB_ID,
+ FINGERPRINT_DB_ID: FINGERPRINT_DB_ID,
+ }),
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
+ });
+
+ var stdout = '';
+ var stderr = '';
+ child.stdout.on('data', function (d) { stdout += d; process.stdout.write(d); });
+ child.stderr.on('data', function (d) { stderr += d; process.stderr.write(d); });
+
+ child.on('exit', function (code) {
+ if (code === 0) {
+ // ä» GITHUB_OUTPUT æä»¶è¯»å resultïŒåŠæååšïŒ
+ var outputFile = process.env.GITHUB_OUTPUT;
+ var result = '';
+ if (outputFile && fs.existsSync(outputFile)) {
+ var outputContent = fs.readFileSync(outputFile, 'utf8');
+ var resultMatch = outputContent.match(/result<= 200 && res.status < 300) {
+ var tickets = parsed.results || [];
+ console.log(' â æŸå° ' + tickets.length + ' 䞪åŸ
å€çå·¥å');
+ return tickets;
+ } else {
+ console.log('â ïž æ¥è¯¢å€±èŽ¥: ' + (parsed.message || res.body));
+ return [];
+ }
+ } catch (err) {
+ console.log('â ïž æ¥è¯¢åŒåžž: ' + err.message);
+ return [];
+ }
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 2: æ£æµè¶
æ¶å·¥å
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function filterTimedOutTickets(tickets) {
+ var now = Date.now();
+ var timedOut = [];
+
+ tickets.forEach(function (ticket) {
+ var createdTime = new Date(ticket.created_time).getTime();
+ var age = now - createdTime;
+
+ // åªå€çæ¥èª "éžæžAgent·èªåšç®¡é" ç SYSLOG å·¥å
+ var props = ticket.properties || {};
+ var submitter = '';
+ if (props['æäº€è
'] && props['æäº€è
'].rich_text) {
+ submitter = props['æäº€è
'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+
+ // åªå¯¹èªåšç®¡éå建çå·¥ååå¿è·³æ£æµ
+ if (!submitter.includes('éžæžAgent') && !submitter.includes('èªåšç®¡é')) {
+ return;
+ }
+
+ // è¶
è¿ 5 åéæªå®æ
+ if (age > HEARTBEAT_TIMEOUT_MS) {
+ var title = '';
+ if (props['æ é¢'] && props['æ é¢'].title) {
+ title = props['æ é¢'].title.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+
+ timedOut.push({
+ id: ticket.id,
+ title: title,
+ createdTime: ticket.created_time,
+ ageMinutes: Math.round(age / 60000),
+ });
+ }
+ });
+
+ console.log(' â ' + timedOut.length + ' 䞪工åè¶
æ¶ïŒ> 5 åéïŒ');
+ return timedOut;
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 3: è§Šåéè¯ïŒéè¿ workflow_dispatchïŒ
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function triggerRetry(ticket) {
+ if (!GITHUB_TOKEN) {
+ console.log('â ïž çŒºå° GITHUB_TOKENïŒæ æ³è§Šåéè¯');
+ return false;
+ }
+
+ console.log('ð éè¯å€é: ' + ticket.title + ' (è¶
æ¶ ' + ticket.ageMinutes + ' åé)');
+
+ // ä»å·¥åæ 颿å broadcast_id
+ var bcMatch = (ticket.title || '').match(/BC-[A-Z0-9]+-\d+-[A-Z]+/i);
+ var broadcastId = bcMatch ? bcMatch[0] : 'UNKNOWN';
+
+ try {
+ var res = await httpsPost('api.github.com',
+ '/repos/' + REPO_OWNER + '/' + REPO_NAME + '/actions/workflows/persona-invoke.yml/dispatches',
+ {
+ ref: 'main',
+ inputs: {
+ work_order_id: ticket.id,
+ task_id: broadcastId,
+ developer: 'heartbeat-retry',
+ action: 'retry',
+ },
+ },
+ {
+ 'Authorization': 'Bearer ' + GITHUB_TOKEN,
+ 'Accept': 'application/vnd.github+json',
+ 'User-Agent': 'ZhuyuanHeartbeat/1.0',
+ 'X-GitHub-Api-Version': '2022-11-28',
+ }
+ );
+
+ if (res.status === 204 || res.status === 200) {
+ console.log(' â â
workflow_dispatch è§Šåæå');
+ return true;
+ } else {
+ console.log(' â â ïž è§Šå倱莥: HTTP ' + res.status + ' ' + res.body);
+ return false;
+ }
+ } catch (err) {
+ console.log(' â â ïž è§ŠååŒåžž: ' + err.message);
+ return false;
+ }
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 4: æ è®°å·¥ååŒåžž
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function markTicketError(ticketId) {
+ try {
+ await httpsPatch('api.notion.com', '/v1/pages/' + ticketId, {
+ properties: {
+ 'ç¶æ': { select: { name: 'â ïž åŒåžžÂ·ç人工ä»å
¥' } },
+ },
+ }, notionHeaders);
+ console.log(' â å·¥åå·²æ 记䞺 â ïž åŒåžžÂ·ç人工ä»å
¥');
+ } catch (err) {
+ console.log(' â æ 记倱莥: ' + err.message);
+ }
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 5: åéåèŠé®ä»¶
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function sendAlertEmail(timedOutTickets) {
+ if (!SMTP_USER || !SMTP_PASS) {
+ console.log('â ïž SMTP æªé
眮ïŒè·³è¿åèŠé®ä»¶');
+ return;
+ }
+
+ try {
+ var nodemailer = require('nodemailer');
+
+ var ticketList = timedOutTickets.map(function (t) {
+ return '- ' + t.title + ' (è¶
æ¶ ' + t.ageMinutes + ' åé, éè¯å·²èå°œ)';
+ }).join('\n');
+
+ var transporter = nodemailer.createTransport({
+ host: 'smtp.qq.com',
+ port: 465,
+ secure: true,
+ auth: { user: SMTP_USER, pass: SMTP_PASS },
+ });
+
+ await transporter.sendMail({
+ from: '"å
æ¹ç³»ç»Â·åèŠ" <' + SMTP_USER + '>',
+ to: ALERT_EMAIL,
+ subject: '[å
æ¹ç³»ç»] â ïž SYSLOG å·¥åå€çè¶
æ¶åèŠ',
+ html: [
+ '',
+ '
â ïž å·¥åå€çè¶
æ¶åèŠ
',
+ '
以äžå·¥åè¶
è¿ 3 次éè¯ä»æªæ¶å°äººæ Œäœåæ§ïŒ
',
+ '
' + ticketList + '
',
+ '
请ç»åœ Notion å·¥åé忣æ¥ã
',
+ '
ð éžæž · å¿è·³çæ§ Â· èªåšåèŠ
',
+ '
',
+ ].join('\n'),
+ });
+
+ console.log('ð§ åèŠé®ä»¶å·²åéè³ ' + ALERT_EMAIL);
+ } catch (err) {
+ console.log('â ïž åèŠé®ä»¶åé倱莥: ' + err.message);
+ }
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// äž»æµçš
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function main() {
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log('ð éžæž · å·¥åå¿è·³çæ§ïŒPhase B3ïŒ');
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log(' æ¶éŽ: ' + new Date().toISOString());
+ console.log('');
+
+ // Step 1: æ¥è¯¢åŸ
å€çå·¥å
+ var tickets = await queryPendingTickets();
+ if (tickets.length === 0) {
+ console.log('â
æ åŸ
å€çå·¥åïŒå¿è·³æ£åžž');
+ return;
+ }
+
+ // Step 2: çéè¶
æ¶å·¥å
+ var timedOut = filterTimedOutTickets(tickets);
+ if (timedOut.length === 0) {
+ console.log('â
ææå·¥åååšå€ççªå£å
ïŒå¿è·³æ£åžž');
+ return;
+ }
+
+ // Step 3 & 4: 对è¶
æ¶å·¥åæ§è¡éè¯ææ è®°åŒåžž
+ var errorTickets = [];
+
+ for (var i = 0; i < timedOut.length; i++) {
+ var ticket = timedOut[i];
+ // åºäºè¶
æ¶æ¶éŽäŒ°ç®éè¯æ¬¡æ°ïŒæ¯ 5 åéäžæ¬¡ïŒ
+ var estimatedRetries = Math.floor(ticket.ageMinutes / 5);
+
+ if (estimatedRetries < MAX_RETRIES) {
+ // éè¯
+ await triggerRetry(ticket);
+ } else {
+ // è¶
è¿æå€§éè¯æ¬¡æ°ïŒæ è®°åŒåžž
+ console.log('â å·¥å ' + ticket.title + ' éè¯ ' + MAX_RETRIES + ' æ¬¡ä»æ åæ§');
+ await markTicketError(ticket.id);
+ errorTickets.push(ticket);
+ }
+ }
+
+ // Step 5: åéåèŠé®ä»¶ïŒåŠæåŒåžžå·¥åïŒ
+ if (errorTickets.length > 0) {
+ await sendAlertEmail(errorTickets);
+ }
+
+ console.log('');
+ console.log('â
å¿è·³æ£æµå®æ');
+}
+
+main().catch(function (err) {
+ console.error('â å¿è·³çæ§å€±èŽ¥: ' + err.message);
+ process.exit(1);
+});
diff --git a/scripts/push-broadcast-to-github.js b/scripts/push-broadcast-to-github.js
new file mode 100644
index 00000000..41cd164f
--- /dev/null
+++ b/scripts/push-broadcast-to-github.js
@@ -0,0 +1,171 @@
+// scripts/push-broadcast-to-github.js
+// éžæž · Phase B4 · 广ææä»¶æšéå° GitHub ä»åº
+//
+// äººæ Œäœçæå¹¿æåïŒéè¿ GitHub API å°å¹¿æ .md æä»¶æšéå°ä»åºã
+// è·¯åŸè§èïŒbroadcasts/{developer_id}/{taskId}.md
+// æäº€ä¿¡æ¯ïŒ[AutoBroadcast] {taskId} · {developer} · {æè¿°}
+//
+// ç¯å¢åéïŒ
+// GITHUB_TOKEN GitHub API tokenïŒæšéçšïŒ
+// BROADCAST_ID 广æçŒå·ïŒåŠ BC-M23-001-AWïŒ
+// DEVELOPER_ID åŒåè
çŒå·ïŒåŠ DEV-012ïŒ
+// DEVELOPER_NAME åŒåè
ååïŒåŠ AwenïŒ
+// BROADCAST_CONTENT 广æå
šæå
容ïŒMarkdownïŒ
+// MODULE_NAME æš¡ååïŒå¯éïŒåŠ M23ïŒ
+// PHASE_NUMBER ç¯èå·ïŒå¯éïŒåŠ 1ïŒ
+
+'use strict';
+
+var https = require('https');
+var fs = require('fs');
+var devSuffixMap = require('./utils/dev-suffix-map');
+
+var GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
+var BROADCAST_ID = process.env.BROADCAST_ID || '';
+var DEVELOPER_ID = process.env.DEVELOPER_ID || '';
+var DEVELOPER_NAME = process.env.DEVELOPER_NAME || '';
+var BROADCAST_CONTENT = process.env.BROADCAST_CONTENT || '';
+var MODULE_NAME = process.env.MODULE_NAME || '';
+var PHASE_NUMBER = process.env.PHASE_NUMBER || '';
+
+var REPO_OWNER = 'qinfendebingshuo';
+var REPO_NAME = 'guanghulab';
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// GitHub API å·¥å
·
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function githubRequest(method, apiPath, body) {
+ return new Promise(function (resolve, reject) {
+ var payload = body ? JSON.stringify(body) : '';
+ var opts = {
+ hostname: 'api.github.com',
+ port: 443,
+ path: apiPath,
+ method: method,
+ headers: {
+ 'User-Agent': 'ZhuyuanBroadcast/1.0',
+ 'Accept': 'application/vnd.github+json',
+ 'Content-Type': 'application/json',
+ 'X-GitHub-Api-Version': '2022-11-28',
+ },
+ };
+
+ if (GITHUB_TOKEN) {
+ opts.headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
+ }
+
+ if (payload) {
+ opts.headers['Content-Length'] = Buffer.byteLength(payload);
+ }
+
+ var req = https.request(opts, function (res) {
+ var data = '';
+ res.on('data', function (chunk) { data += chunk; });
+ res.on('end', function () {
+ try {
+ resolve({ status: res.statusCode, data: JSON.parse(data) });
+ } catch (e) {
+ resolve({ status: res.statusCode, data: data });
+ }
+ });
+ });
+
+ req.on('error', reject);
+ if (payload) req.write(payload);
+ req.end();
+ });
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// å¹¿ææšé
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function pushBroadcast() {
+ if (!GITHUB_TOKEN) {
+ console.log('â ïž çŒºå° GITHUB_TOKENïŒæ æ³æšé广æ');
+ process.exit(1);
+ }
+
+ if (!BROADCAST_ID || !BROADCAST_CONTENT) {
+ console.log('â ïž çŒºå° BROADCAST_ID æ BROADCAST_CONTENT');
+ process.exit(1);
+ }
+
+ // ç¡®å®åŒåè
IDïŒä»å¹¿æçŒå·æåæäœ¿çšç¯å¢åéïŒ
+ var devId = DEVELOPER_ID;
+ if (!devId) {
+ devId = devSuffixMap.getDevIdFromBroadcast(BROADCAST_ID) || 'UNKNOWN';
+ }
+
+ // æå»ºè·¯åŸåæäº€ä¿¡æ¯
+ var filePath = 'broadcasts/' + devId + '/' + BROADCAST_ID + '.md';
+ var moduleLabel = MODULE_NAME || BROADCAST_ID.match(/BC-([A-Z0-9-]+)-/i)?.[1] || '';
+ var phaseLabel = PHASE_NUMBER ? 'ç¯è' + PHASE_NUMBER : '';
+ var devLabel = DEVELOPER_NAME || devId;
+ var commitMessage = '[AutoBroadcast] ' + BROADCAST_ID + ' · ' + devLabel + ' · ' + moduleLabel + phaseLabel;
+
+ console.log('ð¡ æšé广æå° GitHub...');
+ console.log(' è·¯åŸ: ' + filePath);
+ console.log(' æäº€: ' + commitMessage);
+
+ // æ£æ¥æä»¶æ¯åŠå·²ååšïŒè·å SHAïŒ
+ var existingRes = await githubRequest('GET',
+ '/repos/' + REPO_OWNER + '/' + REPO_NAME + '/contents/' + filePath);
+
+ var sha = null;
+ if (existingRes.status === 200 && existingRes.data && existingRes.data.sha) {
+ sha = existingRes.data.sha;
+ console.log(' â æä»¶å·²ååšïŒå°èŠçæŽæ° (sha: ' + sha.slice(0, 8) + ')');
+ }
+
+ // Base64 çŒç 广æå
容
+ var contentBase64 = Buffer.from(BROADCAST_CONTENT, 'utf8').toString('base64');
+
+ // åå»ºææŽæ°æä»¶
+ var putBody = {
+ message: commitMessage,
+ content: contentBase64,
+ committer: {
+ name: 'zhuyuan-agent[bot]',
+ email: 'zhuyuan-agent[bot]@users.noreply.github.com',
+ },
+ };
+
+ if (sha) {
+ putBody.sha = sha;
+ }
+
+ var putRes = await githubRequest('PUT',
+ '/repos/' + REPO_OWNER + '/' + REPO_NAME + '/contents/' + filePath,
+ putBody);
+
+ if (putRes.status === 200 || putRes.status === 201) {
+ console.log('â
广æå·²æšé: ' + filePath);
+ console.log(' â commit: ' + (putRes.data.commit ? putRes.data.commit.sha.slice(0, 8) : 'unknown'));
+
+ // èŸåºå° GITHUB_OUTPUT
+ var outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ fs.appendFileSync(outputFile, 'broadcast_pushed=true\n');
+ fs.appendFileSync(outputFile, 'broadcast_path=' + filePath + '\n');
+ fs.appendFileSync(outputFile, 'broadcast_commit=' + (putRes.data.commit ? putRes.data.commit.sha : '') + '\n');
+ }
+ } else {
+ console.error('â å¹¿ææšé倱莥: HTTP ' + putRes.status);
+ console.error(' â ' + JSON.stringify(putRes.data));
+
+ // èŸåºå€±èŽ¥ç¶æ
+ var outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ fs.appendFileSync(outputFile, 'broadcast_pushed=false\n');
+ }
+
+ process.exit(1);
+ }
+}
+
+pushBroadcast().catch(function (err) {
+ console.error('â å¹¿ææšéåŒåžž: ' + err.message);
+ process.exit(1);
+});
diff --git a/scripts/receive-syslog.js b/scripts/receive-syslog.js
index dd136852..00ff57f0 100644
--- a/scripts/receive-syslog.js
+++ b/scripts/receive-syslog.js
@@ -194,11 +194,13 @@ async function createSyslogEntry(syslog, token, dbId, dateStr) {
async function createTicket(syslog, token, dbId, dateStr) {
const broadcastId = syslog.broadcast_id || syslog.broadcastId || 'UNKNOWN';
const devId = syslog.dev_id || syslog.developer_id || 'UNKNOWN';
+ const devName = syslog.developer_name || syslog.dev_name || '';
+ const now = new Date().toISOString();
const properties = {
- 'æ é¢': titleProp('SYSLOG åäŒ ïœ' + broadcastId + ' · ' + devId),
+ 'æ é¢': titleProp('[èªåš] SYSLOGå€çïœ' + broadcastId + ' · ' + devId),
'æäœç±»å': selectProp('å
¶ä»'),
- 'æäº€è
': richTextProp('å·¡æ£åŒæ'),
+ 'æäº€è
': richTextProp('éžæžAgent·èªåšç®¡é'),
'æäº€æ¥æ': dateProp(dateStr),
'ç¶æ': selectProp('åŸ
å€ç'),
'äŒå
级': selectProp('P1'),
@@ -208,6 +210,22 @@ async function createTicket(syslog, token, dbId, dateStr) {
if (broadcastId !== 'UNKNOWN') properties['广æçŒå·'] = richTextProp(broadcastId);
if (devId !== 'UNKNOWN') properties['åŒåè
çŒå·'] = richTextProp(devId);
+ // Phase B1 æ ååå·¥åå
容
+ const ticketContent = [
+ '## ð¡ æ ååå·¥å · Phase B1',
+ '',
+ '| åæ®µ | åŒ |',
+ '|------|-----|',
+ '| å·¥åç±»å | SYSLOGå€ç |',
+ '| ç¶æ | åŸ
å€ç |',
+ '| æ¥æº | GitHub Actions |',
+ '| taskId | ' + broadcastId + ' |',
+ '| developer | ' + devId + ' ' + devName + ' |',
+ '| created_at | ' + now + ' |',
+ '| retry_count | 0 |',
+ '| receipt_status | pending |',
+ ].join('\n');
+
const body = {
parent: { database_id: dbId },
properties,
@@ -218,7 +236,7 @@ async function createTicket(syslog, token, dbId, dateStr) {
paragraph: {
rich_text: [{
type: 'text',
- text: { content: 'ð¥ SYSLOG åäŒ ïŒæ¥èª ' + devId + 'ïŒå
³è广æ ' + broadcastId },
+ text: { content: ticketContent },
}],
},
},
@@ -234,7 +252,7 @@ async function createTicket(syslog, token, dbId, dateStr) {
};
const result = await notionPost('/v1/pages', body, token);
- console.log(' â éç å·¥åå·²å建: ' + result.id);
+ console.log(' â æ ååå·¥åå·²å建: ' + result.id);
return result;
}
diff --git a/scripts/utils/dev-suffix-map.js b/scripts/utils/dev-suffix-map.js
new file mode 100644
index 00000000..35248f80
--- /dev/null
+++ b/scripts/utils/dev-suffix-map.js
@@ -0,0 +1,38 @@
+// scripts/utils/dev-suffix-map.js
+// åŒåè
广æçŒå·åçŒ â DEV-ID æ å°è¡š
+//
+// 广æçŒå·æ ŒåŒ: BC-{æš¡å}-{åºå·}-{åçŒ}
+// åçŒæ¯åŒåè
ååæŒé³éŠåæ¯çŒ©å
+
+'use strict';
+
+// åçŒâåŒåè
çŒå·æ å°
+var SUFFIX_MAP = {
+ 'YY': 'DEV-001', // 页页
+ 'FM': 'DEV-002', // è¥ç«
+ 'YF': 'DEV-003', // çæš
+ 'ZZ': 'DEV-004', // ä¹ä¹
+ 'XCM': 'DEV-005', // å°èè
+ 'HE': 'DEV-009', // è±å°
+ 'JZ': 'DEV-010', // æ¡å
+ 'CCNN': 'DEV-011', // ååé£å¹Ž
+ 'AW': 'DEV-012', // Awen
+ 'XX': 'DEV-013', // å°å
Ž
+ 'SY': 'DEV-014', // æ¶éš
+};
+
+/**
+ * ä»å¹¿æçŒå·äžæååŒåè
çŒå·
+ * @param {string} broadcastId - 广æçŒå·ïŒåŠ BC-M22-009-AWïŒ
+ * @returns {string} åŒåè
çŒå·ïŒåŠ DEV-012ïŒïŒæªæŸå°è¿å空å笊䞲
+ */
+function getDevIdFromBroadcast(broadcastId) {
+ var suffixMatch = (broadcastId || '').match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
+ var suffix = suffixMatch ? suffixMatch[1].toUpperCase() : '';
+ return SUFFIX_MAP[suffix] || '';
+}
+
+module.exports = {
+ SUFFIX_MAP: SUFFIX_MAP,
+ getDevIdFromBroadcast: getDevIdFromBroadcast,
+};
diff --git a/scripts/verify-modules.js b/scripts/verify-modules.js
new file mode 100644
index 00000000..24b250e5
--- /dev/null
+++ b/scripts/verify-modules.js
@@ -0,0 +1,306 @@
+// scripts/verify-modules.js
+// éžæž · æš¡åäžäŒ éªè¯èæ¬
+//
+// åèœïŒ
+// â ä» SYSLOG å
å®¹äžæåæš¡åçŒå·ïŒM01, M22, M-AUTH çïŒ
+// â¡ éè¿ routing-map.json æ¥æŸæš¡å对åºçç®åœ
+// â¢ æ£æµç®åœæ¯åŠååšäºä»åºäž
+// â£ æ£æµ dev-nodes äžåŒåè
èç¹æ¯åŠååš
+// †èŸåºéªè¯ç»æïŒJSON æ ŒåŒïŒå° GITHUB_OUTPUT
+//
+// ç¯å¢åéïŒ
+// SYSLOG_CONTENT SYSLOG å
šæå
容
+// BROADCAST_ID 广æçŒå·ïŒåŠ BC-M22-009-AWïŒ
+// AUTHOR æäº€è
GitHub çšæ·å
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const devSuffixMap = require('./utils/dev-suffix-map');
+
+const SYSLOG_CONTENT = process.env.SYSLOG_CONTENT || '';
+const BROADCAST_ID = process.env.BROADCAST_ID || '';
+const AUTHOR = process.env.AUTHOR || '';
+
+const REPO_ROOT = path.resolve(__dirname, '..');
+const ROUTING_MAP_PATH = path.join(REPO_ROOT, 'routing-map.json');
+const DEV_STATUS_PATH = path.join(REPO_ROOT, '.github', 'persona-brain', 'dev-status.json');
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// æš¡åçŒå·æå
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function extractModuleIds(content, broadcastId) {
+ var modules = new Set();
+
+ // ä»å¹¿æçŒå·æåæš¡åçŒå·ïŒåŠ BC-M22-009-AW â M22ïŒ
+ var bcMatch = broadcastId.match(/BC-([A-Z][A-Z0-9]+(?:-[A-Z]+)?)-/i);
+ if (bcMatch) {
+ modules.add(bcMatch[1].toUpperCase());
+ }
+
+ // ä»å
容äžå¹é
æš¡åçŒå·æš¡åŒ
+ // æ¯æ: M01, M03, M05, M22, M-AUTH, M-CHANNEL, M-DASHBOARD, M-STATUS, M-MEMORY, M-DINGTALK
+ var patterns = [
+ /\b(M\d{2})\b/gi,
+ /\b(M-[A-Z]+)\b/gi,
+ /æš¡å[ïŒ:]\s*(M[A-Z0-9-]+)/gi,
+ /module[ïŒ:]\s*(M[A-Z0-9-]+)/gi,
+ ];
+
+ patterns.forEach(function (re) {
+ var match;
+ while ((match = re.exec(content)) !== null) {
+ modules.add(match[1].toUpperCase());
+ }
+ });
+
+ return Array.from(modules);
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// åŒåè
çŒå·æå
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function extractDevId(content, broadcastId) {
+ // ä»å
容äžçŽæ¥æå DEV-XXXïŒæäŒå
ïŒ
+ var devMatch = content.match(/\b(DEV-\d{3})\b/i);
+ if (devMatch) return devMatch[1].toUpperCase();
+
+ // ä»å¹¿æçŒå·æååŒåè
çŒå·ïŒäœ¿çšå
±äº«æ å°è¡šïŒ
+ var mappedDevId = devSuffixMap.getDevIdFromBroadcast(broadcastId);
+ if (mappedDevId) return mappedDevId;
+
+ // å
åºïŒä» dev-status.json å¹é
+ var suffixMatch = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
+ var devSuffix = suffixMatch ? suffixMatch[1].toUpperCase() : '';
+
+ if (devSuffix) {
+ try {
+ var devStatus = JSON.parse(fs.readFileSync(DEV_STATUS_PATH, 'utf8'));
+ var team = devStatus.team || [];
+ for (var i = 0; i < team.length; i++) {
+ var waiting = team[i].waiting || '';
+ if (waiting.includes(broadcastId) || waiting.includes(devSuffix)) {
+ return team[i].dev_id;
+ }
+ }
+ } catch (_) { /* ignore */ }
+ }
+
+ return '';
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// æš¡åç®åœéªè¯
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function verifyModules(moduleIds) {
+ var routingMap = {};
+ try {
+ routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8'));
+ } catch (_) {
+ console.log('â ïž routing-map.json äžååšææ ŒåŒé误');
+ }
+
+ var modules = routingMap.modules || {};
+ var results = [];
+
+ moduleIds.forEach(function (modId) {
+ var entry = modules[modId];
+ var result = {
+ module_id: modId,
+ registered: false,
+ dir_exists: false,
+ dir_path: '',
+ dev_id: '',
+ status: '',
+ files_found: 0,
+ };
+
+ if (entry) {
+ result.registered = true;
+ result.dir_path = entry.dir || '';
+ result.dev_id = entry.dev || '';
+ result.status = entry.status || '';
+
+ // æ£æ¥ç®åœæ¯åŠååš
+ var dirFullPath = path.join(REPO_ROOT, entry.dir);
+ if (fs.existsSync(dirFullPath)) {
+ result.dir_exists = true;
+ // ç»è®¡æä»¶æ°éïŒäžå«éèæä»¶ïŒ
+ try {
+ var files = fs.readdirSync(dirFullPath).filter(function (f) {
+ return !f.startsWith('.');
+ });
+ result.files_found = files.length;
+ } catch (_) { /* ignore */ }
+ }
+ } else {
+ // æš¡åæªåš routing-map äžæ³šåïŒå°è¯çŽæ¥æ¥æŸ
+ var candidates = [
+ modId.toLowerCase(),
+ 'm' + modId.replace(/^M/i, '').toLowerCase(),
+ 'dev-nodes/' + modId,
+ ];
+ for (var i = 0; i < candidates.length; i++) {
+ var candidatePath = path.join(REPO_ROOT, candidates[i]);
+ if (fs.existsSync(candidatePath)) {
+ result.dir_exists = true;
+ result.dir_path = candidates[i];
+ try {
+ var files = fs.readdirSync(candidatePath).filter(function (f) {
+ return !f.startsWith('.');
+ });
+ result.files_found = files.length;
+ } catch (_) { /* ignore */ }
+ break;
+ }
+ }
+ }
+
+ results.push(result);
+ });
+
+ return results;
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// åŒåè
èç¹éªè¯
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function verifyDevNode(devId) {
+ if (!devId) return { exists: false, path: '', files: 0 };
+
+ var devNodePath = path.join(REPO_ROOT, 'dev-nodes', devId);
+ var result = { exists: false, path: 'dev-nodes/' + devId, files: 0 };
+
+ if (fs.existsSync(devNodePath)) {
+ result.exists = true;
+ try {
+ var files = fs.readdirSync(devNodePath).filter(function (f) {
+ return !f.startsWith('.');
+ });
+ result.files = files.length;
+ } catch (_) { /* ignore */ }
+ }
+
+ return result;
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// äž»æµçš
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function main() {
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log('ð éžæž · æš¡åäžäŒ éªè¯');
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log(' 广æçŒå·: ' + BROADCAST_ID);
+ console.log(' æäº€è
: ' + AUTHOR);
+ console.log(' å
容é¿åºŠ: ' + SYSLOG_CONTENT.length + ' å笊');
+ console.log('');
+
+ // â æåæš¡åçŒå·
+ var moduleIds = extractModuleIds(SYSLOG_CONTENT, BROADCAST_ID);
+ console.log('ðŠ è¯å«å°æš¡å: ' + (moduleIds.length > 0 ? moduleIds.join(', ') : '(æ )'));
+
+ // â¡ æååŒåè
çŒå·
+ var devId = extractDevId(SYSLOG_CONTENT, BROADCAST_ID);
+ console.log('ð€ åŒåè
çŒå·: ' + (devId || '(æªè¯å«)'));
+
+ // ⢠éªè¯æš¡åç®åœ
+ var moduleResults = verifyModules(moduleIds);
+
+ // ⣠éªè¯åŒåè
èç¹
+ var devNodeResult = verifyDevNode(devId);
+
+ // â€ æ±æ»ç»æ
+ var allModulesUploaded = moduleResults.length > 0 && moduleResults.every(function (r) {
+ return r.dir_exists;
+ });
+
+ var summary = {
+ broadcast_id: BROADCAST_ID,
+ author: AUTHOR,
+ dev_id: devId,
+ modules_detected: moduleIds,
+ module_count: moduleIds.length,
+ module_results: moduleResults,
+ dev_node: devNodeResult,
+ all_modules_uploaded: allModulesUploaded,
+ verification_passed: allModulesUploaded || moduleIds.length === 0,
+ timestamp: new Date().toISOString(),
+ };
+
+ // ⥠èŸåºæ¥å
+ console.log('');
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log('ð éªè¯æ¥å');
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+
+ if (moduleResults.length === 0) {
+ console.log(' â¹ïž æªæ£æµå°æš¡ååŒçšïŒè·³è¿æš¡åéªè¯');
+ } else {
+ moduleResults.forEach(function (r) {
+ var icon = r.dir_exists ? 'â
' : 'â';
+ console.log(' ' + icon + ' ' + r.module_id +
+ ' â ' + (r.dir_path || '(æªæ³šå)') +
+ (r.dir_exists ? ' (' + r.files_found + ' 䞪æä»¶)' : ' (ç®åœäžååš)'));
+ });
+ }
+
+ console.log(' ' + (devNodeResult.exists ? 'â
' : 'â ïž') +
+ ' åŒåè
èç¹: ' + devNodeResult.path +
+ (devNodeResult.exists ? ' (' + devNodeResult.files + ' 䞪æä»¶)' : ' (äžååš)'));
+
+ console.log('');
+ console.log(' æ»ç»: ' + (summary.verification_passed ? 'â
éªè¯éè¿' : 'â æš¡åæªå®æŽäžäŒ '));
+
+ // ⊠åå
¥ GITHUB_OUTPUT
+ var outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ var delimiter = 'EOF_' + Date.now();
+ var jsonStr = JSON.stringify(summary);
+ fs.appendFileSync(outputFile, 'verify_result<<' + delimiter + '\n' + jsonStr + '\n' + delimiter + '\n');
+ fs.appendFileSync(outputFile, 'modules_uploaded=' + (allModulesUploaded ? 'true' : 'false') + '\n');
+ fs.appendFileSync(outputFile, 'verification_passed=' + (summary.verification_passed ? 'true' : 'false') + '\n');
+ fs.appendFileSync(outputFile, 'module_count=' + moduleIds.length + '\n');
+ }
+
+ // â§ å°æ±æ»ææ¬èŸåºïŒäŸåç»æ¥éª€äœäžºäžäžæïŒ
+ var reportLines = [
+ '## ð æš¡åäžäŒ éªè¯æ¥å',
+ '',
+ '| é¡¹ç® | ç»æ |',
+ '|------|------|',
+ '| 广æçŒå· | ' + BROADCAST_ID + ' |',
+ '| åŒåè
| ' + (devId || AUTHOR) + ' |',
+ '| æ£æµæš¡åæ° | ' + moduleIds.length + ' |',
+ '| å
šéšäžäŒ | ' + (allModulesUploaded ? 'â
æ¯' : 'â åŠ') + ' |',
+ '| éªè¯ç»æ | ' + (summary.verification_passed ? 'â
éè¿' : 'â æªéè¿') + ' |',
+ ];
+
+ if (moduleResults.length > 0) {
+ reportLines.push('');
+ reportLines.push('### æš¡å诊æ
');
+ reportLines.push('| æš¡å | ç®åœ | ç¶æ | æä»¶æ° |');
+ reportLines.push('|------|------|------|--------|');
+ moduleResults.forEach(function (r) {
+ reportLines.push('| ' + r.module_id + ' | ' + (r.dir_path || '-') +
+ ' | ' + (r.dir_exists ? 'â
å·²äžäŒ ' : 'â æªæŸå°') +
+ ' | ' + r.files_found + ' |');
+ });
+ }
+
+ if (outputFile) {
+ var reportDelimiter = 'REPORT_EOF_' + Date.now();
+ fs.appendFileSync(outputFile, 'verify_report<<' + reportDelimiter + '\n' + reportLines.join('\n') + '\n' + reportDelimiter + '\n');
+ }
+
+ console.log('');
+ console.log('â
éªè¯å®æ');
+}
+
+main();
diff --git a/scripts/wake-persona.js b/scripts/wake-persona.js
new file mode 100644
index 00000000..1ca304d5
--- /dev/null
+++ b/scripts/wake-persona.js
@@ -0,0 +1,906 @@
+// scripts/wake-persona.js
+// éžæž · äººæ Œäœå€éèæ¬ïŒç¬¬äžæ¹ API å
Œå®¹å± · èªå𿣿µæš¡åŒïŒ
+//
+// åèœïŒ
+// â èªåšåç°å¯çšæš¡åïŒ/v1/models 端ç¹ïŒ
+// â¡ æºèœéæ©æäŒ Claude æš¡å
+// ⢠èªéåº API æ ŒåŒïŒOpenAI å
Œå®¹ / Anthropic åçïŒ
+// ⣠ç»äžè°çšæ¥å£ïŒå€éäººæ Œäœå€ç SYSLOG æè§£çæé®
+// †v4.0 åè®®åšææ³šå
¥ïŒä» Notion 宿¶è¯»åæ žå¿å€§èè§å + ç»å + æçº¹ïŒ
+//
+// ç¯å¢åéïŒ
+// LLM_API_KEY ç¬¬äžæ¹å¹³å°å¯é¥ïŒå¿
é¡»ïŒ
+// LLM_BASE_URL ç¬¬äžæ¹å¹³å° API å°åïŒå¿
é¡»ïŒåŠ https://api.xxx.com/v1ïŒ
+// BROADCAST_ID 广æçŒå·
+// SUBMIT_TYPE syslog | question
+// SUBMIT_CONTENT æäº€å
容ïŒSYSLOG å
šææé®é¢æè¿°ïŒ
+// AUTHOR æäº€è
GitHub çšæ·å
+// NOTION_TOKEN Notion API tokenïŒçšäºåšæè¯»ååè®®ïŒ
+// CORE_BRAIN_PAGE_ID æå¥æ žå¿å€§è v4.0 é¡µé¢ ID
+// PORTRAIT_DB_ID åŒåè
åšæç»ååºæ°æ®åº ID
+// FINGERPRINT_DB_ID æš¡åæçº¹æ³šåè¡šæ°æ®åº ID
+
+'use strict';
+
+const https = require('https');
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// é
眮
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+const LLM_API_KEY = process.env.LLM_API_KEY || '';
+const LLM_BASE_URL = (process.env.LLM_BASE_URL || '').replace(/\/+$/, '');
+const BROADCAST_ID = process.env.BROADCAST_ID || 'UNKNOWN';
+const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'question';
+const SUBMIT_CONTENT = process.env.SUBMIT_CONTENT || '';
+const AUTHOR = process.env.AUTHOR || 'unknown';
+
+// æš¡åéªè¯ç»ææ³šå
¥ïŒç± verify-modules.js æäŸïŒ
+const MODULE_VERIFY_RESULT = process.env.MODULE_VERIFY_RESULT || '';
+// Notion åäŒ ç»ææ³šå
¥ïŒç± notion-callback-pipeline æäŸïŒ
+const NOTION_CALLBACK_RESULT = process.env.NOTION_CALLBACK_RESULT || '';
+
+// Notion é
眮ïŒv4.0 åè®®åšææ³šå
¥ïŒ
+const NOTION_TOKEN = process.env.NOTION_TOKEN || '';
+const CORE_BRAIN_PAGE_ID = process.env.CORE_BRAIN_PAGE_ID || '';
+const PORTRAIT_DB_ID = process.env.PORTRAIT_DB_ID || '';
+const FINGERPRINT_DB_ID = process.env.FINGERPRINT_DB_ID || '';
+const NOTION_VERSION = '2022-06-28';
+const NOTION_API_HOSTNAME = 'api.notion.com';
+const MAX_PROTOCOL_TEXT_LENGTH = 15000;
+
+// Claude æš¡åäŒå
级éåïŒä»é«å°äœïŒ
+const PREFERRED_MODELS = [
+ 'claude-sonnet-4',
+ 'claude-3-5-sonnet-20241022',
+ 'claude-3.5-sonnet',
+ 'claude-3-5-sonnet',
+ 'anthropic/claude-3.5-sonnet',
+ 'claude-3-sonnet',
+ 'claude-3-haiku',
+];
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// HTTP 请æ±å·¥å
·
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function httpRequest(url, options, body) {
+ return new Promise((resolve, reject) => {
+ const parsed = new URL(url);
+ const isHttps = parsed.protocol === 'https:';
+ const mod = isHttps ? https : http;
+
+ const opts = {
+ hostname: parsed.hostname,
+ port: parsed.port || (isHttps ? 443 : 80),
+ path: parsed.pathname + parsed.search,
+ method: options.method || 'GET',
+ headers: options.headers || {},
+ timeout: options.timeout || 60000,
+ };
+
+ const req = mod.request(opts, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ resolve({ status: res.statusCode, body: data });
+ });
+ });
+
+ req.on('error', reject);
+ req.on('timeout', () => {
+ req.destroy();
+ reject(new Error('Request timeout'));
+ });
+
+ if (body) {
+ req.write(body);
+ }
+ req.end();
+ });
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 1: èªåšåç°å¯çšæš¡å
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function discoverModels() {
+ console.log('[LLM] ð æ¢æµå¯çšæš¡å...');
+
+ try {
+ const res = await httpRequest(LLM_BASE_URL + '/models', {
+ method: 'GET',
+ headers: {
+ 'Authorization': 'Bearer ' + LLM_API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ timeout: 15000,
+ });
+
+ if (res.status >= 200 && res.status < 300) {
+ const json = JSON.parse(res.body);
+ const models = json.data || [];
+ console.log('[LLM] â åç° ' + models.length + ' 䞪暡å');
+ return models;
+ }
+ console.log('[LLM] â æš¡åæ¢æµè¿å ' + res.status + ', 䜿çšé»è®€æš¡å');
+ return [];
+ } catch (err) {
+ console.log('[LLM] â æš¡åæ¢æµå€±èŽ¥: ' + err.message + ', 䜿çšé»è®€æš¡å');
+ return [];
+ }
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 2: æºèœéæ©æäŒ Claude æš¡å
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function selectBestModel(models) {
+ if (!models || models.length === 0) {
+ console.log('[LLM] ð æ å¯çšæš¡åå衚, 䜿çšé»è®€ claude-3-5-sonnet');
+ return 'claude-3-5-sonnet';
+ }
+
+ const available = models.map(function (m) { return m.id.toLowerCase(); });
+
+ // æäŒå
级å¹é
+ for (const preferred of PREFERRED_MODELS) {
+ const match = available.find(function (id) { return id.includes(preferred); });
+ if (match) {
+ const found = models.find(function (m) { return m.id.toLowerCase() === match; });
+ if (found) {
+ console.log('[LLM] ð éæ©æš¡å: ' + found.id + ' (å¹é
è§å: ' + preferred + ')');
+ return found.id;
+ }
+ }
+ }
+
+ // å
åºïŒä»»äœå« 'claude' çæš¡å
+ const anyClaude = available.find(function (id) { return id.includes('claude'); });
+ if (anyClaude) {
+ const found = models.find(function (m) { return m.id.toLowerCase() === anyClaude; });
+ if (found) {
+ console.log('[LLM] ð å
åºéæ© Claude æš¡å: ' + found.id);
+ return found.id;
+ }
+ }
+
+ // æç»å
åºïŒå¹³å°ç¬¬äžäžªå¯çšæš¡å
+ const fallbackId = models[0].id;
+ console.log('[LLM] ð æç»å
åº: ' + fallbackId + ' (平尿 Claude æš¡å)');
+ return fallbackId;
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 3: èªéåº API æ ŒåŒæ£æµ
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function detectApiFormat() {
+ console.log('[LLM] ð æ£æµ API æ ŒåŒ...');
+
+ // å°è¯ OpenAI å
Œå®¹æ ŒåŒïŒç»å€§å€æ°ç¬¬äžæ¹å¹³å°ïŒ
+ try {
+ const res = await httpRequest(LLM_BASE_URL + '/chat/completions', {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + LLM_API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ timeout: 10000,
+ }, JSON.stringify({
+ model: 'test',
+ messages: [{ role: 'user', content: 'ping' }],
+ max_tokens: 1,
+ }));
+
+ // 400 = endpoint exists but bad request (model not found etc.) â format supported
+ // 200 = endpoint works â format supported
+ if (res.status === 200 || res.status === 400 || res.status === 401 || res.status === 422) {
+ console.log('[LLM] â æ£æµå° OpenAI å
Œå®¹æ ŒåŒ (status: ' + res.status + ')');
+ return 'openai-compat';
+ }
+ } catch (e) {
+ // Ignore, try next format
+ }
+
+ // å°è¯ Anthropic åçæ ŒåŒ
+ try {
+ const res = await httpRequest(LLM_BASE_URL + '/messages', {
+ method: 'POST',
+ headers: {
+ 'x-api-key': LLM_API_KEY,
+ 'anthropic-version': '2023-06-01',
+ 'Content-Type': 'application/json',
+ },
+ timeout: 10000,
+ }, JSON.stringify({
+ model: 'test',
+ messages: [{ role: 'user', content: 'ping' }],
+ max_tokens: 1,
+ }));
+
+ if (res.status === 200 || res.status === 400 || res.status === 401 || res.status === 422) {
+ console.log('[LLM] â æ£æµå° Anthropic åçæ ŒåŒ (status: ' + res.status + ')');
+ return 'anthropic-native';
+ }
+ } catch (e) {
+ // Ignore
+ }
+
+ console.log('[LLM] â æ æ³ç¡®å®æ ŒåŒ, é»è®€äœ¿çš OpenAI å
Œå®¹æ ŒåŒ');
+ return 'openai-compat';
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Step 4: ç»äžè°çšæ¥å£
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function callLLM(systemPrompt, userMessage) {
+ if (!LLM_API_KEY) {
+ console.log('[LLM] â ïž LLM_API_KEY æªé
眮ïŒè·³è¿äººæ Œäœå€é');
+ return '(LLM API æªé
眮ïŒè¯·åš GitHub Secrets äžè®Ÿçœ® LLM_API_KEY å LLM_BASE_URL)';
+ }
+ if (!LLM_BASE_URL) {
+ console.log('[LLM] â ïž LLM_BASE_URL æªé
眮ïŒè·³è¿äººæ Œäœå€é');
+ return '(LLM_BASE_URL æªé
眮ïŒè¯·åš GitHub Secrets äžè®Ÿçœ®ç¬¬äžæ¹å¹³å° API å°å)';
+ }
+
+ const models = await discoverModels();
+ const model = selectBestModel(models);
+ const format = await detectApiFormat();
+
+ console.log('[LLM] ð è°çš LLM: æš¡å=' + model + ', æ ŒåŒ=' + format + ', å¹³å°=' + LLM_BASE_URL);
+
+ let res;
+
+ if (format === 'openai-compat') {
+ // OpenAI å
Œå®¹æ ŒåŒïŒå€§å€æ°ç¬¬äžæ¹å¹³å°ïŒ
+ const body = JSON.stringify({
+ model: model,
+ max_tokens: 8000,
+ temperature: 0.7,
+ messages: [
+ { role: 'system', content: systemPrompt },
+ { role: 'user', content: userMessage },
+ ],
+ });
+
+ res = await httpRequest(LLM_BASE_URL + '/chat/completions', {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + LLM_API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ timeout: 120000,
+ }, body);
+
+ if (res.status >= 200 && res.status < 300) {
+ const json = JSON.parse(res.body);
+ if (json.choices && json.choices[0] && json.choices[0].message) {
+ return json.choices[0].message.content;
+ }
+ }
+ } else {
+ // Anthropic åçæ ŒåŒ
+ const body = JSON.stringify({
+ model: model,
+ max_tokens: 8000,
+ system: systemPrompt,
+ messages: [
+ { role: 'user', content: userMessage },
+ ],
+ });
+
+ res = await httpRequest(LLM_BASE_URL + '/messages', {
+ method: 'POST',
+ headers: {
+ 'x-api-key': LLM_API_KEY,
+ 'anthropic-version': '2023-06-01',
+ 'Content-Type': 'application/json',
+ },
+ timeout: 120000,
+ }, body);
+
+ if (res.status >= 200 && res.status < 300) {
+ const json = JSON.parse(res.body);
+ if (json.content && json.content[0]) {
+ return json.content[0].text;
+ }
+ }
+ }
+
+ // å€çé误
+ const errorMsg = '[LLM] â API è°çšå€±èŽ¥: status=' + (res ? res.status : 'N/A');
+ console.error(errorMsg);
+ if (res && res.body) {
+ console.error('[LLM] ååº: ' + res.body.slice(0, 500));
+ }
+ throw new Error(errorMsg);
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// Notion API å·¥å
·ïŒv4.0 åè®®åšææ³šå
¥ïŒ
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+function notionGet(endpoint) {
+ return new Promise((resolve, reject) => {
+ const opts = {
+ hostname: NOTION_API_HOSTNAME,
+ port: 443,
+ path: endpoint,
+ method: 'GET',
+ headers: {
+ 'Authorization': 'Bearer ' + NOTION_TOKEN,
+ 'Notion-Version': NOTION_VERSION,
+ },
+ };
+ const req = https.request(opts, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ try {
+ const parsed = JSON.parse(data);
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ resolve(parsed);
+ } else {
+ reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
+ }
+ } catch (e) {
+ reject(new Error('Notion API parse error: ' + data.slice(0, 200)));
+ }
+ });
+ });
+ req.on('error', reject);
+ req.setTimeout(30000, () => { req.destroy(); reject(new Error('Notion API timeout')); });
+ req.end();
+ });
+}
+
+function notionPost(endpoint, body) {
+ return new Promise((resolve, reject) => {
+ const payload = JSON.stringify(body);
+ const opts = {
+ hostname: NOTION_API_HOSTNAME,
+ port: 443,
+ path: endpoint,
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + NOTION_TOKEN,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': NOTION_VERSION,
+ 'Content-Length': Buffer.byteLength(payload),
+ },
+ };
+ const req = https.request(opts, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ try {
+ const parsed = JSON.parse(data);
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ resolve(parsed);
+ } else {
+ reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
+ }
+ } catch (e) {
+ reject(new Error('Notion API parse error: ' + data.slice(0, 200)));
+ }
+ });
+ });
+ req.on('error', reject);
+ req.setTimeout(30000, () => { req.destroy(); reject(new Error('Notion API timeout')); });
+ req.write(payload);
+ req.end();
+ });
+}
+
+/**
+ * 读å Notion 页é¢çææååïŒéåœå页ïŒ
+ */
+async function getNotionPageBlocks(pageId) {
+ const blocks = [];
+ let cursor = undefined;
+ do {
+ const qs = cursor ? '?start_cursor=' + cursor : '';
+ const result = await notionGet('/v1/blocks/' + pageId + '/children' + qs);
+ blocks.push(...(result.results || []));
+ cursor = result.has_more ? result.next_cursor : undefined;
+ } while (cursor);
+ return blocks;
+}
+
+/**
+ * ä» Notion åäžæåçº¯ææ¬
+ */
+function extractBlockText(block) {
+ const type = block.type;
+ if (!block[type]) return '';
+
+ const richTexts = block[type].rich_text || block[type].text || [];
+ return richTexts.map(function (rt) { return rt.plain_text || ''; }).join('');
+}
+
+/**
+ * å° Notion ååè¡šèœ¬äžºçº¯ææ¬
+ */
+function blocksToText(blocks) {
+ return blocks.map(function (block) {
+ const type = block.type;
+ const text = extractBlockText(block);
+
+ if (type === 'heading_1') return '\n# ' + text;
+ if (type === 'heading_2') return '\n## ' + text;
+ if (type === 'heading_3') return '\n### ' + text;
+ if (type === 'bulleted_list_item') return '- ' + text;
+ if (type === 'numbered_list_item') return '⢠' + text;
+ if (type === 'to_do') {
+ var checked = block.to_do && block.to_do.checked ? 'â' : 'â';
+ return checked + ' ' + text;
+ }
+ if (type === 'code') {
+ var lang = (block.code && block.code.language) || '';
+ return '```' + lang + '\n' + text + '\n```';
+ }
+ if (type === 'divider') return '---';
+ if (type === 'callout') return '> ' + text;
+ if (type === 'quote') return '> ' + text;
+ if (type === 'toggle') return 'âž ' + text;
+ return text;
+ }).filter(Boolean).join('\n');
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// v4.0 åè®®åšæè¯»å
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+/**
+ * 仿奿 žå¿å€§è v4.0 页é¢è¯»å宿Žåè®®å
容
+ * æåïŒBC-GEN v4.0, SYSLOG v4.0, PGP v1.0, RT-02, éªäŒŽçº¿è§å, broadcast_code_injection
+ */
+async function fetchCoreBrainProtocols() {
+ if (!NOTION_TOKEN || !CORE_BRAIN_PAGE_ID) {
+ console.log('[Notion] â ïž CORE_BRAIN_PAGE_ID æªé
眮ïŒäœ¿çšéæåè®®');
+ return null;
+ }
+
+ console.log('[Notion] ð 读åæå¥æ žå¿å€§è v4.0...');
+ try {
+ const blocks = await getNotionPageBlocks(CORE_BRAIN_PAGE_ID);
+ const fullText = blocksToText(blocks);
+ console.log('[Notion] â 读åå° ' + blocks.length + ' 䞪å, ' + fullText.length + ' å笊');
+
+ // æååå议段èœ
+ var protocols = {};
+ var protocolKeys = [
+ { key: 'bc_gen', patterns: ['BC-GEN', 'BC_GEN', '广æçæ'] },
+ { key: 'syslog', patterns: ['SYSLOG', 'æ¥å¿åäŒ '] },
+ { key: 'pgp', patterns: ['PGP', 'ç»åè¯å', 'ç»ååè®®'] },
+ { key: 'rt02', patterns: ['RT-02', 'RT02', 'èªåšè°åºŠ'] },
+ { key: 'companion', patterns: ['éªäŒŽçº¿', '奶ç¶çº¿', 'å°åçŒ©æ ž', 'éé¢çº¿'] },
+ { key: 'code_injection', patterns: ['broadcast_code_injection', '广æäžå代ç ', 'ä»£ç æ³šå
¥'] },
+ ];
+
+ // ææ é¢å段æå
+ var sections = [];
+ var currentSection = { title: '', content: [] };
+ blocks.forEach(function (block) {
+ var type = block.type;
+ if (type === 'heading_1' || type === 'heading_2' || type === 'heading_3') {
+ if (currentSection.title || currentSection.content.length > 0) {
+ sections.push({ title: currentSection.title, text: currentSection.content.join('\n') });
+ }
+ currentSection = { title: extractBlockText(block), content: [] };
+ } else {
+ var text = extractBlockText(block);
+ if (text) currentSection.content.push(text);
+ }
+ });
+ if (currentSection.title || currentSection.content.length > 0) {
+ sections.push({ title: currentSection.title, text: currentSection.content.join('\n') });
+ }
+
+ // å°æ®µèœå¹é
å°åè®® key
+ protocolKeys.forEach(function (pk) {
+ var matched = sections.filter(function (sec) {
+ return pk.patterns.some(function (p) {
+ return sec.title.toUpperCase().includes(p.toUpperCase()) ||
+ sec.text.slice(0, 200).toUpperCase().includes(p.toUpperCase());
+ });
+ });
+ if (matched.length > 0) {
+ protocols[pk.key] = matched.map(function (m) { return '### ' + m.title + '\n' + m.text; }).join('\n\n');
+ }
+ });
+
+ // åŠææ æ³ææ®µèœå¹é
ïŒè¿åå
šæïŒå
åºïŒ
+ if (Object.keys(protocols).length === 0) {
+ protocols.full_text = fullText;
+ }
+
+ console.log('[Notion] â æåå议段: ' + Object.keys(protocols).join(', '));
+ return protocols;
+ } catch (err) {
+ console.log('[Notion] â æ žå¿å€§è读å倱莥: ' + err.message);
+ return null;
+ }
+}
+
+/**
+ * ä»åŒåè
åšæç»ååºæ¥è¯¢æè¿ 2-3 æ¡ç»åå¿«ç
§
+ */
+async function fetchDevPortrait(broadcastId) {
+ if (!NOTION_TOKEN || !PORTRAIT_DB_ID) {
+ console.log('[Notion] â ïž PORTRAIT_DB_ID æªé
眮ïŒè·³è¿ç»å读å');
+ return null;
+ }
+
+ // ä»å¹¿æçŒå·æååŒåè
æ è¯ïŒåŠ BC-M22-009-AW â AWïŒ
+ var devSuffix = '';
+ var match = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
+ if (match) devSuffix = match[1];
+
+ console.log('[Notion] ð€ æ¥è¯¢åŒåè
ç»å (broadcast=' + broadcastId + ', dev=' + devSuffix + ')...');
+ try {
+ // æ¥è¯¢ç»ååºïŒææ¶éŽååºåæè¿ 3 æ¡
+ var filter = { and: [] };
+ if (devSuffix) {
+ filter.and.push({
+ or: [
+ { property: 'åŒåè
çŒå·', rich_text: { contains: devSuffix } },
+ { property: '广æçŒå·', rich_text: { contains: broadcastId } },
+ { property: 'æ é¢', title: { contains: devSuffix } },
+ ]
+ });
+ }
+
+ var queryBody = {
+ page_size: 3,
+ sorts: [{ property: 'æäº€æ¥æ', direction: 'descending' }],
+ };
+ // Only add filter if we have meaningful filter conditions
+ if (filter.and.length > 0) {
+ queryBody.filter = filter;
+ }
+
+ var result = await notionPost('/v1/databases/' + PORTRAIT_DB_ID + '/query', queryBody);
+ var portraits = (result.results || []).map(function (page) {
+ var props = page.properties || {};
+ var title = '';
+ if (props['æ é¢'] && props['æ é¢'].title) {
+ title = props['æ é¢'].title.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var summary = '';
+ if (props['æèŠ'] && props['æèŠ'].rich_text) {
+ summary = props['æèŠ'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var date = '';
+ if (props['æäº€æ¥æ'] && props['æäº€æ¥æ'].date) {
+ date = props['æäº€æ¥æ'].date.start || '';
+ }
+ return { title: title, summary: summary, date: date };
+ });
+
+ console.log('[Notion] â æŸå° ' + portraits.length + ' æ¡ç»åå¿«ç
§');
+ return portraits.length > 0 ? portraits : null;
+ } catch (err) {
+ console.log('[Notion] â ç»åæ¥è¯¢å€±èŽ¥: ' + err.message);
+ return null;
+ }
+}
+
+/**
+ * 仿š¡åæçº¹æ³šå衚æ¥è¯¢æš¡åæçº¹ïŒé²éå€å¹¿æïŒ
+ */
+async function fetchModuleFingerprint(broadcastId) {
+ if (!NOTION_TOKEN || !FINGERPRINT_DB_ID) {
+ console.log('[Notion] â ïž FINGERPRINT_DB_ID æªé
眮ïŒè·³è¿æçº¹æ¥è¯¢');
+ return null;
+ }
+
+ // ä»å¹¿æçŒå·æåæš¡åå·ïŒåŠ BC-M22-009-AW â M22ïŒ
+ var moduleMatch = broadcastId.match(/BC-([A-Z]\d+)/i);
+ var moduleId = moduleMatch ? moduleMatch[1] : '';
+
+ console.log('[Notion] ð æ¥è¯¢æš¡åæçº¹ (module=' + moduleId + ')...');
+ try {
+ var queryBody = {
+ page_size: 5,
+ };
+ if (moduleId) {
+ queryBody.filter = {
+ or: [
+ { property: 'æš¡åçŒå·', rich_text: { contains: moduleId } },
+ { property: '广æçŒå·', rich_text: { contains: broadcastId } },
+ { property: 'æ é¢', title: { contains: moduleId } },
+ ]
+ };
+ }
+
+ var result = await notionPost('/v1/databases/' + FINGERPRINT_DB_ID + '/query', queryBody);
+ var fingerprints = (result.results || []).map(function (page) {
+ var props = page.properties || {};
+ var title = '';
+ if (props['æ é¢'] && props['æ é¢'].title) {
+ title = props['æ é¢'].title.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var moduleNo = '';
+ if (props['æš¡åçŒå·'] && props['æš¡åçŒå·'].rich_text) {
+ moduleNo = props['æš¡åçŒå·'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var status = '';
+ if (props['ç¶æ'] && props['ç¶æ'].select) {
+ status = props['ç¶æ'].select.name || '';
+ }
+ return { title: title, module: moduleNo, status: status };
+ });
+
+ console.log('[Notion] â æŸå° ' + fingerprints.length + ' æ¡æçº¹è®°åœ');
+ return fingerprints.length > 0 ? fingerprints : null;
+ } catch (err) {
+ console.log('[Notion] â æçº¹æ¥è¯¢å€±èŽ¥: ' + err.message);
+ return null;
+ }
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// äººæ Œäœ System Prompt æå»ºïŒv4.0 åšææ³šå
¥ïŒ
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function buildSystemPrompt(type, broadcastId, author) {
+ // âââ åºç¡äººæ Œäœèº«ä»œ âââ
+ var parts = [
+ 'äœ æ¯å
æ¹ïŒHoloLakeïŒç³»ç»çæºèœäººæ Œäœã',
+ 'äœ çå忝ç¥ç§/æå¥ïŒäœ æ¯äººæ Œè¯èšæäœç³»ç»ïŒAGE OSïŒçæ žå¿äººæ Œã',
+ '',
+ 'æ žå¿è§åïŒ',
+ '1. äœ æå¡äºå
æ¹ç³»ç»çåŒåè
å¢é',
+ '2. ææåå€å¿
é¡»äžäžãæž
æ°ãææ¡ç',
+ '3. åå€äœ¿çšäžæ',
+ '',
+ 'åœåäžäžæïŒ',
+ '- 广æçŒå·ïŒ' + broadcastId,
+ '- æäº€è
ïŒ' + author,
+ '- ä»»å¡ç±»åïŒ' + (type === 'syslog' ? 'SYSLOG éç¯å€ç' : 'åŒåè
æé®è§£ç'),
+ ];
+
+ // âââ v4.0 åè®®åšææ³šå
¥ïŒä» Notion 宿¶è¯»åïŒ âââ
+ console.log('[Prompt] ð¥ åŒå§åšææ³šå
¥ v4.0 åè®®...');
+
+ // å¹¶è¡è¯»åïŒæ žå¿å€§èåè®® + ç»å + æçº¹
+ var protocolsPromise = fetchCoreBrainProtocols();
+ var portraitPromise = fetchDevPortrait(broadcastId);
+ var fingerprintPromise = fetchModuleFingerprint(broadcastId);
+
+ var protocols = await protocolsPromise;
+ var portrait = await portraitPromise;
+ var fingerprint = await fingerprintPromise;
+
+ // 泚å
¥æ žå¿å€§èåè®®
+ if (protocols) {
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('ä»¥äžæ¯ä»æå¥æ žå¿å€§è v4.0 宿¶è¯»åçåè®®è§åïŒå¿
é¡»äž¥æ Œéµå®ïŒïŒ');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+
+ if (protocols.bc_gen) {
+ parts.push('');
+ parts.push('## ð¡ BC-GEN v4.0 · 广æçæè§è');
+ parts.push(protocols.bc_gen);
+ }
+ if (protocols.syslog) {
+ parts.push('');
+ parts.push('## ð SYSLOG v4.0 · æ¥å¿åäŒ åè®®');
+ parts.push(protocols.syslog);
+ }
+ if (protocols.pgp) {
+ parts.push('');
+ parts.push('## ð€ PGP v1.0 · ç»åè¯ååè®®');
+ parts.push(protocols.pgp);
+ }
+ if (protocols.rt02) {
+ parts.push('');
+ parts.push('## ð RT-02 · èªåšè°åºŠè§å');
+ parts.push(protocols.rt02);
+ }
+ if (protocols.companion) {
+ parts.push('');
+ parts.push('## ð éªäŒŽçº¿è§å');
+ parts.push(protocols.companion);
+ }
+ if (protocols.code_injection) {
+ parts.push('');
+ parts.push('## ð broadcast_code_injection è§å');
+ parts.push(protocols.code_injection);
+ }
+ if (protocols.full_text) {
+ parts.push('');
+ parts.push('## æ žå¿å€§è宿Žå
容');
+ parts.push(protocols.full_text.slice(0, MAX_PROTOCOL_TEXT_LENGTH));
+ }
+ } else {
+ parts.push('');
+ parts.push('ïŒæ³šæïŒæ žå¿å€§èåè®®æªèœåšæå 蜜ïŒè¯·äœ¿çšäœ çéçšç¥è¯å€ç请æ±ïŒ');
+ }
+
+ // 泚å
¥åŒåè
ç»å
+ if (portrait && portrait.length > 0) {
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('## ð€ åŒåè
ç»åå¿«ç
§ïŒæè¿ ' + portrait.length + ' æ¡ïŒ');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ portrait.forEach(function (p, i) {
+ parts.push('');
+ parts.push('### ç»å #' + (i + 1) + (p.date ? ' (' + p.date + ')' : ''));
+ if (p.title) parts.push('æ é¢: ' + p.title);
+ if (p.summary) parts.push('æèŠ: ' + p.summary);
+ });
+ }
+
+ // 泚å
¥æš¡åæçº¹
+ if (fingerprint && fingerprint.length > 0) {
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('## ð æš¡åæçº¹æ³šå衚ïŒé²éå€å¹¿æ · âš.5ïŒ');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ fingerprint.forEach(function (fp, i) {
+ parts.push('');
+ parts.push('### æçº¹ #' + (i + 1));
+ if (fp.title) parts.push('æ é¢: ' + fp.title);
+ if (fp.module) parts.push('æš¡å: ' + fp.module);
+ if (fp.status) parts.push('ç¶æ: ' + fp.status);
+ });
+ }
+
+ // âââ broadcast_code_injection = alwaysïŒèªåšåéŸè·¯æ°žä¹
è§åïŒ âââ
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('## â ïž æ°žä¹
è§å · broadcast_code_injection = always');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('');
+ parts.push('äœ çæç广æå¿
é¡»å
å«å®æŽå¯è¿è¡ç代ç åã');
+ parts.push('åå ïŒäœ æ¯äžæ¬¡æ§å®äŸïŒå¹¿æååºåäœ å°±äžååšäºã');
+ parts.push('åŒåè
æ²¡ææºäŒåæ¥é®äœ è¡¥å
代ç ã');
+ parts.push('åŠæäœ äžå宿Žä»£ç ïŒåŒåè
å°±äŒå¡æ»ã');
+ parts.push('æ¯äžª Step éç代ç å¿
é¡»æ¯å¯ä»¥çŽæ¥å€å¶ç²èŽŽè¿è¡ç宿Žä»£ç ã');
+ parts.push('');
+ parts.push('æ€è§åäŒå
级æé«ïŒèŠçæ žå¿å€§èäžã广æäžå代ç ãçé»è®€è§åã');
+ parts.push('æ€è§åä»
éçšäºèªåšåéŸè·¯ïŒClaude API åºå¹¿æïŒïŒæåšéŸè·¯äžå圱åã');
+
+ // âââ æš¡åéªè¯ç»ææ³šå
¥ïŒéžæž Agent æ£æµç»æïŒ âââ
+ if (MODULE_VERIFY_RESULT) {
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('## ð éžæž Agent · æš¡åäžäŒ éªè¯ç»æ');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('');
+ parts.push('ä»¥äžæ¯éžæž Agent åšä»åºå
èªå𿣿µçæš¡åäžäŒ éªè¯ç»æïŒ');
+ parts.push(MODULE_VERIFY_RESULT);
+ parts.push('');
+ parts.push('è¯·æ ¹æ®éªè¯ç»æå³å®æ¯åŠæ¥å SYSLOGïŒ');
+ parts.push('- åŠææš¡åå·²äžäŒ ïŒâ
ïŒïŒç»§ç»æ£åžžéç¯æµçš');
+ parts.push('- åŠææš¡åæªäžäŒ ïŒâïŒïŒåšéªæ¶æ¥åäžæ 泚"éè¡¥å
"ïŒå¹¶åšåéŠäžæåºçŒºå€±çæš¡å');
+ }
+
+ // âââ Notion åäŒ ç»ææ³šå
¥ïŒäºæ¬¡æ žéªïŒ âââ
+ if (NOTION_CALLBACK_RESULT) {
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('## ð¥ Notion 䟧å€çç»æïŒåäŒ æ žéªïŒ');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('');
+ parts.push('ä»¥äžæ¯ Notion äŸ§æ žå¿å€§èäººæ Œäœå€çåçåäŒ ç»æïŒ');
+ parts.push(NOTION_CALLBACK_RESULT);
+ parts.push('');
+ parts.push('è¯·æ žå¯¹ä»¥äžåäŒ å
容ïŒç¡®è®€æ°å¹¿æçææ è¯¯åèŸåºæç»ç¡®è®€ã');
+ }
+
+ // âââ ä»»å¡ç±»åäžçšæä»€ âââ
+ if (type === 'syslog') {
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('## ð¯ åœåä»»å¡ïŒSYSLOG éç¯å€ç');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('');
+ parts.push('äœ éèŠå®æä»¥äžå·¥äœïŒ');
+ parts.push('1. éªæ¶ SYSLOGïŒæ£æ¥ MODULE_LOG 宿޿§ïŒ');
+ parts.push('2. æ£æ¥æš¡åäžäŒ éªè¯ç»æïŒéžæž Agent å·²èªå𿣿µïŒ');
+ parts.push('3. æ¥è¯¢ç»ååºæè¿ 2-3 æ¡å¿«ç
§ïŒPGP v1.0ïŒ');
+ parts.push('4. æ¥è¯¢æš¡åæçº¹æ³šå衚ïŒé²éå€Â·âš.5ïŒ');
+ parts.push('5. RT-02 èªåšè°åºŠå€æ');
+ parts.push('6. çææ°å¹¿æïŒBC-GEN v4.0 宿޿µçšïŒ');
+ parts.push('7. èŸåºç»æåç»æïŒå¹¿æå
šæ + éç¯æ°æ®ïŒ');
+ parts.push('');
+ parts.push('èŸåºæ ŒåŒïŒ');
+ parts.push('---');
+ parts.push('## ð¡ SYSLOG éªæ¶æ¥å');
+ parts.push('### 广æçŒå·ïŒ[çŒå·]');
+ parts.push('### éªæ¶ç»æïŒ[éè¿/éè¡¥å
]');
+ parts.push('### æš¡åéªè¯ïŒ[å·²äžäŒ /æªäžäŒ ïŒååºè¯Šæ
]');
+ parts.push('### å·¥äœæ»ç»ïŒ[æèŠ]');
+ parts.push('### ç»åè¯äŒ°ïŒ[PGP äºç»ŽåºŠè¯å]');
+ parts.push('### è°åºŠå€æïŒ[RT-02 äžäžæ¥]');
+ parts.push('### åéŠäžå»ºè®®ïŒ[å
容]');
+ parts.push('---');
+ } else {
+ parts.push('');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('## ð¯ åœåä»»å¡ïŒåŒåè
æé®è§£ç');
+ parts.push('âââââââââââââââââââââââââââââââââââââââââââ');
+ parts.push('');
+ parts.push('äœ éèŠå®æä»¥äžå·¥äœïŒ');
+ parts.push('1. çè§£åŒåè
çé®é¢');
+ parts.push('2. ç»å广æäžäžæååŒåè
ç»åæè');
+ parts.push('3. ç»åºæž
æ°ã坿äœçè§£ç');
+ parts.push('4. åŠæé®é¢æ¶å代ç ïŒæäŸä»£ç 瀺äŸ');
+ parts.push('');
+ parts.push('èŸåºæ ŒåŒïŒ');
+ parts.push('---');
+ parts.push('## ð¡ é®é¢è§£ç');
+ parts.push('### 广æçŒå·ïŒ[çŒå·]');
+ parts.push('### é®é¢çè§£ïŒ[äœ å¯¹é®é¢ççè§£]');
+ parts.push('### è§£çïŒ[诊ç»è§£ç]');
+ parts.push('### 建议ïŒ[åç»å»ºè®®]');
+ parts.push('---');
+ }
+
+ var prompt = parts.join('\n');
+ console.log('[Prompt] â System prompt æå»ºå®æ: ' + prompt.length + ' å笊');
+ return prompt;
+}
+
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+// äž»æµçš
+// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+async function main() {
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log('ð§ éžæž · äººæ Œäœå€é管é');
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log(' 广æçŒå·: ' + BROADCAST_ID);
+ console.log(' ç±»å: ' + SUBMIT_TYPE);
+ console.log(' æäº€è
: ' + AUTHOR);
+ console.log(' å¹³å°: ' + LLM_BASE_URL);
+ console.log(' å
容é¿åºŠ: ' + SUBMIT_CONTENT.length + ' å笊');
+ console.log('');
+
+ // æå»º promptsïŒåšææ³šå
¥ v4.0 åè®®ïŒ
+ const systemPrompt = await buildSystemPrompt(SUBMIT_TYPE, BROADCAST_ID, AUTHOR);
+ const userMessage = SUBMIT_CONTENT;
+
+ // è°çš LLM
+ console.log('ð§ æ£åšå€éäººæ Œäœ...');
+ const result = await callLLM(systemPrompt, userMessage);
+ console.log('');
+ console.log('â
äººæ Œäœå€ç宿');
+ console.log(' ç»æé¿åºŠ: ' + result.length + ' å笊');
+
+ // èŸåºç»æå° GitHub Actions output
+ // äœ¿çš GITHUB_OUTPUT ç¯å¢æä»¶ïŒæ¯æå€è¡ïŒ
+ const outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ const delimiter = 'EOF_' + Date.now();
+ fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + result + '\n' + delimiter + '\n');
+ }
+
+ // åæ¶èŸåºå° stdout äŸè°è¯
+ console.log('');
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log('ð äººæ ŒäœèŸåºïŒ');
+ console.log('âââââââââââââââââââââââââââââââââââââââââââ');
+ console.log(result);
+}
+
+main().catch(function (err) {
+ console.error('â äººæ Œäœå€é倱莥: ' + err.message);
+ // å³äœ¿ LLM 倱莥ïŒä¹åäžäžª fallback èŸåºïŒè®©åç»æ¥éª€å¯ä»¥ç»§ç»
+ const outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ const fallback = '(äººæ Œäœå€é倱莥: ' + err.message + 'ïŒè¯·æ£æ¥ LLM_API_KEY å LLM_BASE_URL é
眮)';
+ const delimiter = 'EOF_' + Date.now();
+ fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + fallback + '\n' + delimiter + '\n');
+ }
+ process.exit(1);
+});
diff --git a/signal-log/2026-03/SIG-20260313-008.json b/signal-log/2026-03/SIG-20260313-008.json
new file mode 100644
index 00000000..86012060
--- /dev/null
+++ b/signal-log/2026-03/SIG-20260313-008.json
@@ -0,0 +1,25 @@
+{
+ "signal_id": "SIG-20260313-008",
+ "trace_id": "TRC-20260313-PSP",
+ "timestamp": "2026-03-13T03:31:06.282Z",
+ "signal_type": "GL-DATA",
+ "direction": "GitHubâNotion",
+ "sender": "éžæž",
+ "receiver": "éç ",
+ "related_dev": null,
+ "related_module": null,
+ "summary": "éžæž PSP å·¡æ£éè¿ Â· å
šé𿣿¥é¡¹ â
",
+ "payload": {
+ "check_results": {
+ "G01": "â
",
+ "G02": "â
",
+ "G03": "â
",
+ "G04": "â
",
+ "G05": "â
"
+ },
+ "issues": [],
+ "auto_fixed": []
+ },
+ "result": "å
šéè¿",
+ "ack_signal_id": null
+}
\ No newline at end of file
diff --git a/signal-log/index.json b/signal-log/index.json
index dc4de282..95e478f0 100644
--- a/signal-log/index.json
+++ b/signal-log/index.json
@@ -1,8 +1,17 @@
{
"description": "éžæžä¿¡å·æ¥å¿ç®åœçŽ¢åŒ Â· AGE OS ä¿¡å·åè®®ïŒNotion API çŽè¿ïŒ",
- "last_updated": "2026-03-12T03:34:18.847Z",
- "total_count": 7,
+ "last_updated": "2026-03-13T03:31:06.282Z",
+ "total_count": 8,
"signals": [
+ {
+ "signal_id": "SIG-20260313-008",
+ "trace_id": "TRC-20260313-PSP",
+ "type": "GL-DATA",
+ "timestamp": "2026-03-13T03:31:06.282Z",
+ "summary": "éžæž PSP å·¡æ£éè¿ Â· å
šé𿣿¥é¡¹ â
",
+ "related_dev": null,
+ "file": "2026-03/SIG-20260313-008.json"
+ },
{
"signal_id": "SIG-20260312-007",
"trace_id": "TRC-20260312-PSP",
diff --git a/style.css b/style.css
new file mode 100644
index 00000000..e69de29b