kenaz 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/.claude/skills/code_reviewer/SKILL.md +0 -1
- package/core/.claude/skills/code_reviewer/patterns.yaml +0 -8
- package/core/.claude/skills/dev_agent/SKILL.md +3 -6
- package/core/.claude/skills/heimdall/SKILL.md +5 -6
- package/core/.claude/skills/huginn/SKILL.md +11 -38
- package/core/.collaboration/core/task-2288-followups.test.js +2 -37
- package/core/.collaboration/core/task-2421-replica.test.js +229 -0
- package/core/.collaboration/core/task-api.js +223 -230
- package/core/.collaboration/core/task-api.test.js +1 -138
- package/core/.collaboration/core/task-replica.js +189 -0
- package/core/.formation/.merge-lock +1 -0
- package/core/.formation/bifrost/codex-doctor.js +12 -2
- package/core/.formation/bifrost/codex-doctor.test.js +15 -0
- package/core/.formation/bifrost/launch.js +2 -2
- package/core/.formation/bifrost/launch.test.js +2 -2
- package/core/.formation/bifrost/setup-codex.js +84 -16
- package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
- package/core/.formation/bifrost/shared/codex-injection.js +3 -17
- package/core/.formation/bifrost/shared/consolidate.js +1 -51
- package/core/.formation/bifrost/sync.js +4 -5
- package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +1 -1
- package/core/.formation/tools/dispatch.js +2 -2
- package/core/.formation/tools/kenaz-stats.js +1 -1
- package/core/.formation/tools/mcp-allowlist.test.js +5 -3
- package/core/.formation/tools/post-complete/bounded-http.js +1 -40
- package/core/.formation/tools/post-complete/bounded-http.test.js +2 -89
- package/core/.formation/tools/post-complete/cognitive.js +2 -119
- package/core/.formation/tools/post-complete/task2420.test.js +129 -0
- package/core/.formation/tools/post-complete/testing.js +3 -21
- package/core/.formation/tools/post-complete.js +131 -436
- package/core/.formation/tools/post-complete.test.js +0 -135
- package/core/.formation/tools/route-audit.test.js +8 -20
- package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
- package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
- package/core/.rule/INDEX.yaml +3 -3
- package/core/.rule/general/ddx-synapse-stack.yaml +11 -11
- package/core/.rule/general/specialist-identity-contract.yaml +5 -5
- package/core/.rule/general/synapse-code-intelligence.yaml +25 -111
- package/core/.rule/general/worktree-isolation.yaml +2 -2
- package/core/.rule/pipeline/task-execution.yaml +6 -9
- package/core/.specialist/Amelia/README.md +5 -5
- package/core/.specialist/Amelia/capability.yaml +10 -18
- package/core/.specialist/Amelia/knowledge.yaml +15 -66
- package/core/.specialist/Amelia/workflow.yaml +12 -21
- package/core/.specialist/Ansuz/capability.yaml +12 -87
- package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +1 -1
- package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +2 -3
- package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +1 -3
- package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +8 -21
- package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +0 -4
- package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +1 -1
- package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +0 -1
- package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +0 -3
- package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +3 -3
- package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +1 -7
- package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +3 -4
- package/core/.specialist/Ansuz/concepts/identity.yaml +1 -2
- package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +0 -1
- package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +3 -4
- package/core/.specialist/Ansuz/core.yaml +3 -6
- package/core/.specialist/Ansuz/elixir.yaml +6 -6
- package/core/.specialist/Ansuz/exo_cortex_tools.yaml +5 -9
- package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +2 -2
- package/core/.specialist/Ansuz/guards/lesson_guards.yaml +6 -19
- package/core/.specialist/Ansuz/identity_contract.yaml +4 -4
- package/core/.specialist/Ansuz/knowledge.yaml +13 -40
- package/core/.specialist/Ansuz/modules/ddx.yaml +2 -2
- package/core/.specialist/Ansuz/modules/prophecy.yaml +19 -19
- package/core/.specialist/Ansuz/modules/pulse_empty.yaml +6 -17
- package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +3 -102
- package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +2 -44
- package/core/.specialist/Ansuz/workflow.yaml +32 -76
- package/core/.specialist/Forseti/core.yaml +2 -7
- package/core/.specialist/Forseti/rule.yaml +1 -1
- package/core/.specialist/Heimdall/SKILL.md +2 -3
- package/core/.specialist/Heimdall/capability.yaml +22 -45
- package/core/.specialist/Heimdall/core.yaml +24 -126
- package/core/.specialist/Heimdall/principles.yaml +8 -11
- package/core/.specialist/Heimdall/rule.yaml +2 -2
- package/core/.specialist/Huginn/capability.yaml +31 -52
- package/core/.specialist/Huginn/core.yaml +43 -123
- package/core/.specialist/Huginn/principles.yaml +5 -5
- package/core/.specialist/Huginn/rule.yaml +3 -9
- package/core/.specialist/REFLECTION_TO_CONTROL.md +3 -4
- package/core/.specialist/shared/guard_schema.json +1 -1
- package/core/.specialist/shared/principles.yaml +6 -6
- package/core/.specialist/tools/__tests__/rea-detect.test.js +10 -0
- package/core/.specialist/tools/phase-gate.js +10 -230
- package/core/.specialist/tools/phase-gate.test.js +2 -2
- package/core/.specialist/tools/prompt-files.test.js +1 -1
- package/core/.specialist/tools/rea-detect.js +6 -2
- package/core/.specialist/tools/session.js +2 -89
- package/core/.synapse/memory-shared.json.sync +1 -0
- package/core/.system/memory/README.md +3 -0
- package/core/CLAUDE.md +6 -7
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +75 -83
- package/core/dev/specs/peer/CODEX_MANAGED_DAEMON.md +54 -0
- package/core/dev/specs/peer/setup-codex-hooks.test.js +38 -0
- package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +56 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +1 -0
- package/core/plugins/kenaz/INVENTORY.md +1 -1
- package/core/plugins/kenaz/scripts/kenaz.test.js +70 -1
- package/core/plugins/kenaz/scripts/ygg.js +45 -4
- package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +0 -1
- package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +0 -8
- package/core/plugins/kenaz/skills/dev_agent/SKILL.md +3 -6
- package/lib/cli.js +17 -9
- package/lib/cli.test.js +10 -0
- package/lib/dashboard.test.js +3 -3
- package/package.json +6 -6
- package/vendor/install-kenaz-cli.js +167 -16
- package/core/.claude/hooks/handoff-snapshot-on-decision.js +0 -188
- package/core/.claude/skills/synapse_maintain/SKILL.md +0 -84
- package/core/.collaboration/core/task-2264-single-after-capture.test.js +0 -61
- package/core/.formation/tools/alaya-gate.js +0 -198
- package/core/.formation/tools/post-complete/health.js +0 -464
- package/core/.formation/tools/post-complete/task2038.test.js +0 -246
- package/core/.formation/tools/post-complete/task2341.test.js +0 -74
- package/core/.rule/general/cortex-tools.yaml +0 -181
- package/core/.rule/general/memory-language.yaml +0 -67
- package/core/.rule/general/memory-source-priority.yaml +0 -207
- package/core/.rule/general/synapse-cognitive-fusion.yaml +0 -301
- package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +0 -14
- package/core/.specialist/Ansuz/concepts/write_verification.yaml +0 -26
- package/core/.specialist/Ansuz/modules/recall.yaml +0 -45
- package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +0 -156
- package/core/.specialist/tools/context-injection.js +0 -395
- package/core/dev/scripts/probe/hippocampus-concurrency.js +0 -247
|
@@ -1,128 +1,120 @@
|
|
|
1
|
-
# CLI-first
|
|
1
|
+
# CLI-first 安裝與首次使用流程(TASK_2367 / TASK_2376)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
狀態:**待用戶審閱**(2026-10-11 改寫為中文並更新到現況)。
|
|
4
|
+
目標流程:`npx kenaz install` 一次 → 在專案資料夾開 `claude` → `/kenaz init` → `/kenaz:Ansuz`。
|
|
5
|
+
Yggdrasil 用 `kenaz ygg create` / `kenaz ygg join <key>`;Alaya 用 `kenaz alaya register`(機碼註冊,不需登入)。Dashboard 已退役。
|
|
5
6
|
|
|
6
|
-
## 1.
|
|
7
|
+
## 1. 組成與位置
|
|
7
8
|
|
|
8
|
-
|
|
|
9
|
+
| 元件 | 是什麼 | 位置 |
|
|
9
10
|
|---|---|---|
|
|
10
|
-
| kenaz
|
|
11
|
-
|
|
|
12
|
-
| kenaz
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
11
|
+
| npm `kenaz` | CLI(install / uninstall / status / doctor / init / ygg / alaya / model / stats / learn)+打包好的 kenaz-core | `npx kenaz ...` |
|
|
12
|
+
| `@kenazai/server-<os>-<cpu>` | kenaz-server 執行檔(無視窗後端:MCP + HTTP,只聽本機,port 18080,被占用時往後找) | 安裝後在 `~/.kenaz/bin/` |
|
|
13
|
+
| kenaz-core | plugin、hooks、腳本、規則(plugin 與 server 都讀它) | 安裝後在 `~/.kenaz/core/` |
|
|
14
|
+
| kenaz plugin | `/kenaz` 指令、`/kenaz:Ansuz` 與 hooks | `~/.kenaz/core/plugins/kenaz` |
|
|
15
|
+
| 使用者層資料 | 服務啟動器、log、專案清單、憑證、教訓(`~/.kenaz/knowledge`) | `~/.kenaz/` |
|
|
16
|
+
| 專案資料 | 任務、日誌、專案設定 | 每個專案的 `.kenaz/`,**解除安裝永遠不動** |
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## 2. 以服務方式執行的東西
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
只有 `kenaz-server`:每個使用者一份(不需系統管理員、不是系統服務),登入時啟動,只聽本機。
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| OS | Mechanism | Files |
|
|
22
|
+
| 作業系統 | 方式 | 檔案 |
|
|
24
23
|
|---|---|---|
|
|
25
|
-
| Windows
|
|
26
|
-
| macOS
|
|
27
|
-
| Linux
|
|
28
|
-
|
|
29
|
-
Templates are generated by `install-kenaz-cli.js` (`launchdPlist`, `systemdUnit`, `winVbs`) and unit-tested as strings; only Windows is exercised for real.
|
|
24
|
+
| Windows | 使用者層排程工作 `KenazServer`(登入時觸發,執行隱藏視窗的 `wscript.exe ~\.kenaz\kenaz-server.vbs`)。若 `schtasks /Create` 被拒(登入觸發可能需要管理員權限),改放「啟動」資料夾的 `KenazServer.vbs` 並立即啟動 | `~\.kenaz\kenaz-server.vbs` |
|
|
25
|
+
| macOS | launchd 使用者代理 `ai.kenaz.server`(RunAtLoad + KeepAlive) | `~/Library/LaunchAgents/ai.kenaz.server.plist` |
|
|
26
|
+
| Linux(有 systemd) | `systemd --user` 的 `kenaz-server.service`;無人登入的主機需 `loginctl enable-linger $USER` | `~/.config/systemd/user/kenaz-server.service` |
|
|
27
|
+
| Linux(無 systemd:容器、WSL1、精簡主機)(TASK_2413) | crontab 加一行標記過的 `@reboot`(只取代自己那行,其他保留)並立即在背景啟動;連 crontab 都沒有時,立即啟動並印出要加進開機腳本的指令 | `~/.kenaz/kenaz-server.log` |
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
`kenaz doctor` 會顯示目前的服務模式(systemd / launchd / windows task / startup launcher / cron / manual)。
|
|
32
30
|
|
|
33
|
-
## 3. Plugin
|
|
31
|
+
## 3. Plugin 登錄(Claude Code)
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
33
|
+
在 `~/.claude/settings.json` 的 `env.CLAUDE_CODE_PLUGIN_DIRS` 加入 `~/.kenaz/core/plugins/kenaz`:
|
|
34
|
+
- 只合併、不覆蓋;重複執行結果相同;路徑比對不分斜線方向與大小寫
|
|
35
|
+
- 第一次修改前備份一份 `settings.json.bak-kenaz-peer`,寫入用暫存檔+改名(不會寫一半)
|
|
36
|
+
- JSON 格式壞掉時只回報、不動它
|
|
37
|
+
- 前提:`claude --version` >= 2.1.287(太舊會跳過並提示 `claude update`)
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
Codex:`setup-codex.js` 寫入 `~/.codex/config.toml` 的 MCP 設定、原生 hooks 與 `/prompts:kenaz`。升級時會把舊路徑、舊參數的 kenaz hooks 換成新的(TASK_2418),不動使用者自己的 hooks。
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
## 4. 首次使用
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
1. 在任何資料夾執行 `claude`(plugin 自動載入,不需 `--plugin-dir`)。
|
|
44
|
+
2. `/kenaz init`:建立 `.kenaz/project.yaml`、確認後端有回應,並列出可選步驟(不強制):
|
|
45
|
+
- 團隊協作:`kenaz ygg create`(自建房間金鑰並加入,把金鑰給隊友)或 `kenaz ygg join <key>`
|
|
46
|
+
- Alaya:`kenaz alaya register`(機碼註冊,立即可用)
|
|
47
|
+
- 下一步:`/kenaz:Ansuz`
|
|
48
|
+
3. 後端沒回應時,init 會說明並指向 `npx kenaz install`。
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
2. `/kenaz init` creates `.kenaz/project.yaml`, probes `/api/health`, and prints the optional steps and the next step. It does not force them:
|
|
49
|
-
- Yggdrasil: `/kenaz ygg join <key>` (also `status`, `leave`)
|
|
50
|
-
- Alaya: `k-cli alaya register` (device code, approve in any browser)
|
|
51
|
-
- Next: `/Ansuz`
|
|
52
|
-
3. If the backend is down, init says so and points at `install-kenaz-cli.js install`.
|
|
50
|
+
## 5. 升級與解除安裝
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
- **自動更新**:kenaz-server 會從簽章過的 feed 自行更新(執行檔+kenaz-core+plugin),重新啟動後生效。
|
|
53
|
+
- **手動升級**:`npx kenaz@latest install`。會先停掉「這個路徑」的舊 server(不會依名稱砍其他程序)、換新檔、**明確重啟服務**,再用 `/api/health` 確認跑的是新版本;版本不符會警告並印出重啟指令(TASK_2418)。
|
|
54
|
+
- **解除安裝**:`npx kenaz uninstall` 只移除我們加的 plugin 項目、服務啟動器與 `~/.kenaz/core`、`~/.kenaz/bin`。**專案的 `.kenaz/` 與 `~/.kenaz` 的使用者資料保留**,settings 備份也保留。
|
|
55
|
+
- `status` 顯示 plugin 登錄與服務狀態;任何指令加 `--dry-run` 只印計畫、不改東西。
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
- Uninstall: `install-kenaz-cli.js uninstall` removes only our entry from `CLAUDE_CODE_PLUGIN_DIRS`, deletes the scheduled task / Startup launcher / plist / unit and the launcher file. Project `.kenaz/` data and `~/.kenaz/` memory data stay. The settings backup stays.
|
|
58
|
-
- `status` reports plugin registration and service state. `--dry-run` prints the plan for any command and changes nothing.
|
|
57
|
+
## 6. 驗收
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
自動化(已完成):installer、CLI、Codex hooks 的 node 測試只用暫存 home,不碰真實環境。
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
**乾淨 Windows 環境實測(待用戶執行,等下一次發版後)**:
|
|
62
|
+
1. 準備乾淨環境:Windows Sandbox(控制台 → 開啟或關閉 Windows 功能 → 勾「Windows 沙箱」→ 重開機),或新建一個本機 Windows 帳號。
|
|
63
|
+
2. 在裡面安裝 Node.js(LTS)與 Claude Code,登入 Claude。
|
|
64
|
+
3. `npx kenaz install`。預期:plugin 已登錄、排程工作或啟動資料夾啟動器、`http://127.0.0.1:18080/api/health` 有回應、最後的版本確認為 ok。
|
|
65
|
+
4. 新建空資料夾 → `claude` → `/kenaz init` → `/kenaz:Ansuz`。預期:Ansuz 正常開機,不需 `--plugin-dir`。
|
|
66
|
+
5. `npx kenaz doctor`:每列應為 ok 或 skip。
|
|
67
|
+
6. `npx kenaz uninstall`。預期:plugin 已移除、服務啟動器已刪、測試資料夾的 `.kenaz` 仍在。
|
|
68
|
+
7. 記下每一步卡住、看不懂、或需要額外動作的地方,回報給 Ansuz。
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
1. New Windows user (or VM), install Node and `claude` >= 2.1.287; copy kenaz-core and `kenaz-server.exe`.
|
|
66
|
-
2. `node dashboard\scripts\install-kenaz-cli.js install --dry-run --core-dir <core> --server-bin <exe>` and read the plan.
|
|
67
|
-
3. Rerun without `--dry-run`. Expect: `plugin: added`, scheduled task `KenazServer` (or Startup launcher), server answering `http://127.0.0.1:18080/api/health`.
|
|
68
|
-
4. `mkdir test-proj; cd test-proj; claude` then `/kenaz init`, then `/Ansuz`. Expect Ansuz boots with no `--plugin-dir`.
|
|
69
|
-
5. `... uninstall`. Expect `plugin: removed`, task gone (`schtasks /Query /TN KenazServer` fails), `test-proj\.kenaz` still present.
|
|
70
|
+
## 7. 未決事項
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
- macOS 與 Linux 的服務步驟目前只有單元測試,真機只有 Windows 實際跑過(field report 顯示 macOS 用戶已在使用,升級重啟問題已修,待下一版驗證)。
|
|
73
|
+
- Windows 在沒有管理員權限時會走「啟動資料夾」路線;乾淨環境實測要確認實際走哪一條。
|
|
74
|
+
- Codex 桌面版(managed daemon)目前無法即時推送 peer 訊息,改為下一次提問時拉取(TASK_2419,調查記錄見 `dev/specs/peer/CODEX_MANAGED_DAEMON.md`)。
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
- Packaging: who ships kenaz-core + binary to `~/.kenaz/bin` (release pipeline, not this card).
|
|
75
|
-
- `k-cli` itself is not yet installed by this script; Alaya registration assumes it is on PATH.
|
|
76
|
+
## 8. 已定案的決策(用戶,2026-10-09 起)
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- **
|
|
81
|
-
-
|
|
82
|
-
- **
|
|
83
|
-
-
|
|
78
|
+
- **發佈:npm**。`npx kenaz install`;各平台執行檔是選用的平台套件(`@kenazai/server-win32-x64`、`-darwin-arm64`、`-darwin-x64`、`-linux-x64`、`-linux-arm64`)。
|
|
79
|
+
- **兩種 harness 都支援**:偵測到 Claude Code 就登錄 plugin,偵測到 Codex 就設定 MCP 與 hooks。
|
|
80
|
+
- **平台**:Windows、macOS、Linux 首版就支援。
|
|
81
|
+
- **Node.js 是前提**(兩種 harness 本來就需要);installer 會檢查版本並說明怎麼裝。
|
|
82
|
+
- **更新**:kenaz-server 自行更新,有簽章驗證。
|
|
83
|
+
- **Codex 與 Claude Code 對等**:MCP 工具、Yggdrasil 指令、Ansuz 流程、Codex 原生 subagent 派工。
|
|
84
|
+
- **發版**:等用戶說「發版」才發(緊急修正除外)。Windows 與 Linux 在本機建置,只有 macOS 在 GitHub Actions 建置以節省費用(流程見記憶 `reference_npm_cli_release`)。
|
|
84
85
|
|
|
85
|
-
## 9. npm
|
|
86
|
+
## 9. npm 套件結構(TASK_2377)
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
- `packages/kenaz`:CLI+打包的 kenaz-core(`prepack` 跑 `scripts/stage.js`,只收 git 追蹤的白名單檔案)。
|
|
89
|
+
- `packages/server-*`:各平台執行檔;版本與 `dashboard/src-tauri/Cargo.toml` 同步。
|
|
90
|
+
- install:檢查 Node 版本 → 複製 kenaz-core 到 `~/.kenaz/core`、執行檔到 `~/.kenaz/bin` → 記錄 `~/.kenaz/install.json` → Claude Code 與 Codex 各自設定 → 啟動並確認服務。
|
|
91
|
+
- doctor:Node 版本、core 版本、server 執行檔、`/api/health`、服務模式、Claude plugin、Codex MCP 與 hooks;任何 FAIL 以代碼 1 結束。
|
|
92
|
+
- 本機檢查:`npm pack` 後裝到暫存前綴,用 `--home <暫存目錄>` 執行(絕不用真實 home)。
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
- install: Node >= 18 check; copy packaged kenaz-core to `~/.kenaz/core` and the platform binary dir to `~/.kenaz/bin`; record `~/.kenaz/install.json`; Claude Code (if `claude` on PATH) via `install-kenaz-cli.js` (both plugins + service); Codex (if `codex` on PATH) via `setup-codex.js` (MCP, native hooks, `/prompts:kenaz`). Neither is reimplemented.
|
|
91
|
-
- uninstall reverses both (`setup-codex.js uninstall()` removes only the MCP entry, shim hook blocks and prompt it added) and deletes core/bin/install.json; project `.kenaz/` is never touched.
|
|
92
|
-
- doctor: node version, core version, server binary, `/api/health` reachable, Claude plugins registered, Codex MCP + hooks. Exit 1 on any FAIL.
|
|
93
|
-
- `prepack` runs `scripts/stage.js`: stages core with the same git-tracked whitelist as the Dashboard bundle (`kenaz-core-staging.js`, ~19 MB) and vendors `install-kenaz-cli.js`.
|
|
94
|
-
- Release: `.gitlab-ci.yml` cross-builds the 5 targets (native runners; darwin-x64 cross-compiled on the M1 runner), packs tarballs as artifacts, `publish` is manual and does not call npm.
|
|
95
|
-
- Local check: `npm pack ./packages/kenaz` etc., install into a prefix, run with `--home <tmp>` (never the real home).
|
|
96
|
-
- Open: the server loads `libonnxruntime` via `ORT_DYLIB_PATH`; CI ships it next to the binary in `bin/`, but the service launchers do not set `ORT_DYLIB_PATH` yet. macOS/Linux service steps and all 5 CI build jobs are unverified until a pipeline/Mac run.
|
|
94
|
+
## 10. 從 Dashboard 遷移(TASK_2389 / TASK_2396)
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
範圍:只偵測並提示。CLI 不會結束 Dashboard 行程、不改登錄檔或 Startup 資料夾。原因:`kenaz-server` 與 Dashboard 都綁 port 18080,不能同時執行。
|
|
96
|
+
Dashboard(Tauri 桌面 app)已於 2026-10-11 從 repo 移除,kenaz-server 是唯一後端。已安裝的 Dashboard 可繼續用但不再更新。兩者都綁 port 18080,不能同時執行;CLI 只偵測並提示,不會替你結束 Dashboard。
|
|
101
97
|
|
|
102
98
|
### 給使用者的步驟(可直接貼進公告)
|
|
103
99
|
|
|
104
|
-
1. 在系統匣(右下角)對 Kenaz Dashboard
|
|
105
|
-
2. 開啟 Dashboard
|
|
100
|
+
1. 在系統匣(右下角)對 Kenaz Dashboard 按右鍵,選「結束 / Quit」,確認程式完全關閉。
|
|
101
|
+
2. 開啟 Dashboard 設定,關閉「開機時啟動」,避免登入後它又占回 port 18080。
|
|
106
102
|
3. 在終端機執行:`npx kenaz install`
|
|
107
103
|
4. 到專案資料夾執行 `claude`,再輸入 `/kenaz init`。
|
|
108
104
|
5. 驗證:`npx kenaz doctor`,每一列應為 ok 或 skip。
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
要改回 Dashboard:從開始選單啟動它,並在設定中重新開啟「開機時啟動」。`kenaz uninstall` 結束時會印出同樣的提示。
|
|
106
|
+
Dashboard 還在執行時,`npx kenaz install` 會印出上面的步驟並結束,不會啟動服務;只想先裝 plugin 與 core:加 `--keep-dashboard`。
|
|
112
107
|
|
|
113
|
-
###
|
|
108
|
+
### 資料共用,不需搬移
|
|
114
109
|
|
|
115
110
|
| 資料 | 位置 |
|
|
116
111
|
|---|---|
|
|
117
112
|
| 專案清單 | `~/.kenaz/projects.json` |
|
|
118
113
|
| 憑證 / relay key / 各專案 Yggdrasil key | `~/.kenaz/credentials.yaml` |
|
|
119
114
|
| 偏好設定 | `~/.kenaz/config.json` |
|
|
120
|
-
| Synapse 記憶 | `<專案>/.synapse/db/memory.db` |
|
|
121
115
|
| 任務、日誌 | `<專案>/.kenaz/` |
|
|
122
116
|
| Peer 收件匣 | `peer_dir(project_root)/inbox.sqlite` |
|
|
123
117
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
### doctor 為什麼檢查 plugin 路徑
|
|
118
|
+
記憶系統(Synapse 等)已於 2026-10-11 移除(TASK_2411),舊的 `.synapse/` 與 `~/.kenaz/cortex/*.db` 留在磁碟上、不再使用;教訓改用 `kenaz learn`。
|
|
127
119
|
|
|
128
|
-
Dashboard
|
|
120
|
+
Dashboard 各項行為的去向(哪些已搬進 kenaz-server、哪些刻意移除)記錄在 git 歷史中此檔 2026-10-11 之前的版本。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Codex desktop managed daemon as a peer push target (TASK_2419, field report d200d31d)
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
The Codex desktop "Work" app (codex 0.162.x) talks to a managed `codex app-server` daemon. Kenaz's Bifrost launcher
|
|
6
|
+
(`.formation/bifrost/launch.js`, TASK_2167) only registers the ws_url of an app-server it starts itself
|
|
7
|
+
(`.kenaz/codex/sessions.json`). With the desktop app nothing is registered, so `/api/peer/status` showed
|
|
8
|
+
`delivery_mode=none, sessions=[]` and the poller logged NO_TARGET, although mail was stored and the
|
|
9
|
+
UserPromptSubmit `--pull` hook (`codex-hook-shim.js`) does deliver it on the next prompt.
|
|
10
|
+
|
|
11
|
+
## Findings (evidence, codex-cli 0.162.0 on this machine, read-only)
|
|
12
|
+
|
|
13
|
+
1. `codex app-server --help`: `--listen <URL>` accepts `stdio://` (default), `unix://`, `unix://PATH`, `ws://IP:PORT`, `off`.
|
|
14
|
+
Subcommands: `daemon` (bootstrap/start/restart/stop/version/enable-remote-control), `proxy` ("Proxy stdio bytes to the
|
|
15
|
+
running app-server control socket", `--sock <SOCKET_PATH>`). WS auth flags: `--ws-auth capability-token|signed-bearer-token`,
|
|
16
|
+
`--ws-token-file`, `--ws-shared-secret-file` (apply to ws listeners).
|
|
17
|
+
2. The managed daemon is discoverable on disk, no env var needed: `$CODEX_HOME/app-server-control/app-server-control.sock`
|
|
18
|
+
(default `~/.codex/app-server-control/`), plus `$CODEX_HOME/app-server-daemon/daemon.pid`, `settings.json`
|
|
19
|
+
(`remoteControlEnabled:false`). Both existed here. It is a unix-domain socket (default path), not a ws://host:port.
|
|
20
|
+
3. Protocol (`codex app-server generate-json-schema --out <tmp>`, ClientRequest.json) has the methods needed:
|
|
21
|
+
`initialize`, `thread/list`, `thread/loaded/list`, `thread/read`, `thread/resume`, `turn/start`, `turn/steer`,
|
|
22
|
+
`turn/interrupt`, `thread/inject_items`. So injection into a thread is possible in principle (the same methods
|
|
23
|
+
`codex_wake.rs::deliver_interactive` already uses over ws for launcher-started servers).
|
|
24
|
+
4. Auth on the unix control socket: none visible; access control is filesystem permission of the socket owner.
|
|
25
|
+
|
|
26
|
+
## Why Kenaz does not push into it (decision)
|
|
27
|
+
|
|
28
|
+
- Unverified transport: whether the `unix://` listener speaks the same WebSocket framing as `ws://` is not confirmed here
|
|
29
|
+
(no daemon was started or connected; real user state must not be touched). `codex_wake.rs` uses `tokio_tungstenite::connect_async`
|
|
30
|
+
(TCP ws only); a unix-socket client would be new, and tokio `UnixStream` does not exist on Windows (AF_UNIX there needs another crate).
|
|
31
|
+
- Target ambiguity: the desktop app owns many threads; `thread/loaded/list` does not say which one the user is looking at. Guessing
|
|
32
|
+
wrongly injects a turn into the wrong conversation.
|
|
33
|
+
- Safety: push is deliberately opt-in (`codex_push: true`, user decision 2026-10-02) because an injected turn drives a full-access
|
|
34
|
+
agent from untrusted peer text. A socket that any same-user process can drive, with a guessed thread, widens that.
|
|
35
|
+
|
|
36
|
+
Conclusion: not supported safely today. Degraded mode is made explicit instead.
|
|
37
|
+
|
|
38
|
+
## What changed
|
|
39
|
+
|
|
40
|
+
- `GET /api/peer/status`: when no push/pull session exists but a managed daemon is detected
|
|
41
|
+
(`codex_managed_daemon_in`, `peer_delivery_mode.rs`) a session `{agent:"codex", session_id:"codex-managed-daemon",
|
|
42
|
+
delivery_mode:"pull-on-prompt"}` is added; `delivery_mode` becomes `pull-on-prompt` with a Codex-specific note
|
|
43
|
+
(no push channel; mail arrives on the next prompt via `--pull`; run `node .formation/bifrost/codex-doctor.js`).
|
|
44
|
+
`kenaz ygg status` (plugins/kenaz/scripts/ygg.js) relays that mode/note unchanged.
|
|
45
|
+
- NO_TARGET log detail (`peer_delivery_state::no_claude_reason`): TASK_2410 had already replaced the misleading
|
|
46
|
+
"claude not found on PATH" for Codex nodes; it now says "Codex managed daemon detected: no push channel ... via the --pull hook"
|
|
47
|
+
when the daemon is present, instead of "Codex not started via the Kenaz launcher".
|
|
48
|
+
- `codex-doctor.js`: `diagnose()` returns `managed_daemon` and adds an informational note (does not fail `ok`).
|
|
49
|
+
|
|
50
|
+
## Possible future push (not done)
|
|
51
|
+
|
|
52
|
+
Confirm ws-over-unix framing against a temp `CODEX_HOME` daemon, add a unix transport to `AppServerClient`, require
|
|
53
|
+
`codex_push: true`, and pick the thread via an explicit user choice (or the thread whose last hook-shim pull was most recent)
|
|
54
|
+
rather than guessing. Until then use the pull path, or start Codex via `launch.js` with `codex_push: true`.
|
|
@@ -20,3 +20,41 @@ test('missingHookEvents reports unregistered events and registerHooks fills exac
|
|
|
20
20
|
const cfg = fs.readFileSync(path.join(home, 'config.toml'), 'utf8');
|
|
21
21
|
assert.equal((cfg.match(/\[\[hooks\.UserPromptSubmit\.hooks\]\]/g) || []).length, 1);
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
// TASK_2418: a kenaz block from an old checkout, or a UserPromptSubmit block without --pull, is stale: rewritten once.
|
|
25
|
+
test('stale kenaz hook blocks (old checkout path, missing --pull) are detected and replaced exactly once; user hooks stay', () => {
|
|
26
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-home-'));
|
|
27
|
+
process.env.CODEX_HOME = home;
|
|
28
|
+
const s = require('../../../.formation/bifrost/setup-codex.js');
|
|
29
|
+
const cur = s.getHookShimPath(process.cwd()).split('\\').join('/');
|
|
30
|
+
const old = '/old/checkout/.formation/bifrost/openai/codex-hook-shim.js';
|
|
31
|
+
const h = (ev, cmd) => ['[[hooks.' + ev + ']]', '', '[[hooks.' + ev + '.hooks]]', 'type = "command"', "command = '" + cmd + "'", 'timeout = 15', ''];
|
|
32
|
+
const cfgPath = path.join(home, 'config.toml');
|
|
33
|
+
fs.writeFileSync(cfgPath, [
|
|
34
|
+
'model = "x"', '',
|
|
35
|
+
...h('Stop', 'node user-hook.js'),
|
|
36
|
+
'# kenaz-dashboard native hooks (TASK_1733) -- auto-registered by setup-codex.js',
|
|
37
|
+
...h('PreToolUse', 'node "' + old + '"'),
|
|
38
|
+
...h('PostToolUse', 'node "' + cur + '"'),
|
|
39
|
+
...h('UserPromptSubmit', 'node "' + cur + '"'),
|
|
40
|
+
...h('SessionStart', 'node "' + cur + '"'),
|
|
41
|
+
...h('Stop', 'node "' + old + '"'),
|
|
42
|
+
].join('\n'));
|
|
43
|
+
assert.deepEqual(s.missingHookEvents(), ['PreToolUse', 'UserPromptSubmit', 'Stop']);
|
|
44
|
+
assert.equal(s.areHooksAlreadyRegistered(process.cwd()), true); // PostToolUse/SessionStart are current
|
|
45
|
+
s.registerHooks(() => {}, process.cwd(), s.missingHookEvents());
|
|
46
|
+
assert.deepEqual(s.missingHookEvents(), []);
|
|
47
|
+
const cfg = fs.readFileSync(cfgPath, 'utf8');
|
|
48
|
+
for (const ev of ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'Stop']) {
|
|
49
|
+
const n = (cfg.match(new RegExp('\\[\\[hooks\\.' + ev + '\\.hooks\\]\\][^\\[]*codex-hook-shim\\.js', 'g')) || []).length;
|
|
50
|
+
assert.equal(n, 1, ev + ' has exactly one kenaz handler');
|
|
51
|
+
}
|
|
52
|
+
assert.ok(!cfg.includes('/old/checkout'));
|
|
53
|
+
assert.match(cfg, /UserPromptSubmit\.hooks\]\][^\[]*--pull/);
|
|
54
|
+
assert.ok(cfg.includes('node user-hook.js') && cfg.includes('model = "x"'));
|
|
55
|
+
assert.equal((cfg.match(/kenaz-dashboard native hooks/g) || []).length, 1);
|
|
56
|
+
// uninstall removes the rewritten blocks and still keeps the user's
|
|
57
|
+
s.uninstall({ silent: true });
|
|
58
|
+
const after = fs.readFileSync(cfgPath, 'utf8');
|
|
59
|
+
assert.ok(!after.includes('codex-hook-shim.js') && after.includes('node user-hook.js'));
|
|
60
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
2026-10-03T21:07:10.515249Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
|
|
2
|
+
2026-10-03T21:07:45.377199Z INFO MCP tool call tool="alaya_recall" plugin=None
|
|
3
|
+
2026-10-03T21:07:53.574088Z WARN [session_watcher] reflex check: role=assistant, text_len=119, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
|
|
4
|
+
2026-10-03T21:07:53.574900Z WARN [session_watcher] reflex check: role=assistant, text_len=119, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
|
|
5
|
+
2026-10-03T21:07:54.168238Z INFO MCP tool call tool="cortex_recall" plugin=None
|
|
6
|
+
2026-10-03T21:07:54.372461Z INFO MCP tool call tool="alaya_recall" plugin=None
|
|
7
|
+
2026-10-03T21:07:54.728901Z INFO MCP tool call tool="hippocampus_episode_recall" plugin=None
|
|
8
|
+
2026-10-03T21:07:55.213271Z INFO MCP tool call tool="synapse_health" plugin=None
|
|
9
|
+
2026-10-03T21:08:37.929534Z WARN [session_watcher] reflex check: role=assistant, text_len=2101, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
|
|
10
|
+
2026-10-03T21:08:37.930488Z WARN [session_watcher] reflex check: role=assistant, text_len=2101, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
|
|
11
|
+
2026-10-03T21:10:36.990645Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
|
|
12
|
+
2026-10-03T21:10:37.101533Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
|
|
13
|
+
2026-10-03T21:10:47.558693Z WARN [session_watcher] reflex check: role=assistant, text_len=153, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
|
|
14
|
+
2026-10-03T21:10:47.558790Z WARN [session_watcher] reflex check: role=assistant, text_len=153, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
|
|
15
|
+
2026-10-03T21:12:34.601726Z INFO backend logging initialized version="0.2.263" pid=8024 started_at=2026-10-04 05:12:34.601 profile="debug" log_path=C:\Users\user\AppData\Local\Kenaz Dashboard\backend.log
|
|
16
|
+
2026-10-03T21:12:34.601803Z INFO startup mark label="after_init_tracing" t_ms=3 thread=main
|
|
17
|
+
2026-10-03T21:12:35.010413Z INFO startup mark label="setup_entry" t_ms=411 thread=main
|
|
18
|
+
2026-10-03T21:12:35.015743Z INFO startup mark label="mcp_servers_spawn_issued" t_ms=417 thread=main
|
|
19
|
+
2026-10-03T21:12:35.015846Z INFO startup mark label="mcp_servers_task_entered" t_ms=417 thread=tokio-runtime-worker
|
|
20
|
+
2026-10-03T21:12:35.016550Z INFO startup mark label="mcp_servers_start_done" t_ms=417 thread=tokio-runtime-worker
|
|
21
|
+
2026-10-03T21:12:35.262343Z INFO startup mark label="onnx_preload_dylib_done" t_ms=663 thread=main
|
|
22
|
+
2026-10-03T21:12:35.262445Z INFO startup mark label="onnx_warmup_thread_spawn_issued" t_ms=663 thread=main
|
|
23
|
+
2026-10-03T21:12:35.262574Z INFO startup mark label="delta_graph_recovery_thread_spawn_issued" t_ms=664 thread=main
|
|
24
|
+
2026-10-03T21:12:35.262763Z INFO startup mark label="sleep_cycle_daemon_spawn_issued" t_ms=664 thread=main
|
|
25
|
+
2026-10-03T21:12:35.262754Z INFO startup mark label="onnx_warmup_begin" t_ms=664 thread=onnx-warmup
|
|
26
|
+
2026-10-03T21:12:35.262886Z INFO startup mark label="delta_graph_recovery_begin" t_ms=664 thread=delta-gap-recovery
|
|
27
|
+
2026-10-03T21:12:35.265586Z INFO startup mark label="http_sidecar_task_spawn_issued" t_ms=667 thread=main
|
|
28
|
+
2026-10-03T21:12:35.265647Z INFO startup mark label="auto_index_watcher_spawn_issued" t_ms=667 thread=main
|
|
29
|
+
2026-10-03T21:12:35.265727Z INFO startup mark label="http_sidecar_task_entered" t_ms=667 thread=tokio-runtime-worker
|
|
30
|
+
2026-10-03T21:12:35.265789Z INFO startup mark label="setup_fn_returning" t_ms=667 thread=main
|
|
31
|
+
2026-10-03T21:12:35.265824Z INFO startup mark label="auto_index_watcher_task_entered" t_ms=667 thread=tokio-runtime-worker
|
|
32
|
+
2026-10-03T21:12:35.291199Z INFO [synapse] IS_INDEXING set — indexing started project=C:/Users/user/Documents/GitHub/KenazAI mode="incremental"
|
|
33
|
+
2026-10-03T21:12:35.291229Z INFO [synapse] reindex phase transition phase="init" project=C:/Users/user/Documents/GitHub/KenazAI
|
|
34
|
+
2026-10-03T21:12:35.358777Z INFO Loading ONNX embedding model [query] (ArcticEmbedXs, dim=384) from C:\Users\user\Documents\GitHub\KenazAI\dashboard\src-tauri\target\debug\resources\onnx\arctic-embed-xs
|
|
35
|
+
2026-10-03T21:12:35.635924Z INFO Embedding engine [query] loaded successfully (ArcticEmbedXs, dim=384, outputs=["last_hidden_state"], token_type_ids=true)
|
|
36
|
+
2026-10-03T21:12:35.636005Z INFO startup mark label="onnx_warmup_done" t_ms=1037 thread=onnx-warmup
|
|
37
|
+
2026-10-03T21:12:36.101980Z INFO startup mark label="http_server_start_begin" t_ms=1503 thread=tokio-runtime-worker
|
|
38
|
+
2026-10-03T21:12:36.119887Z INFO startup mark label="http_server_start_ready" t_ms=1521 thread=tokio-runtime-worker
|
|
39
|
+
2026-10-03T21:12:36.122223Z INFO synapse worker forwarder configuration workers_enabled=true current_project=C:/Users/user/Documents/GitHub/KenazAI
|
|
40
|
+
2026-10-03T21:12:36.122290Z INFO reaper armed leaseless_idle_secs=300 leased_idle_secs=900 tick_secs=60 per_worker_rss_cap_mb=400 total_rss_cap_mb=1200 max_workers=3
|
|
41
|
+
2026-10-03T21:12:36.275465Z INFO startup mark label="ready_wait_end" t_ms=1676 thread=tokio-runtime-worker
|
|
42
|
+
2026-10-03T21:12:37.479820Z INFO [synapse] reindex phase transition phase="kuzu_write_start:2_files" project=C:/Users/user/Documents/GitHub/KenazAI
|
|
43
|
+
2026-10-03T21:12:37.815038Z ERROR [conn pool] GRAPH CORRUPT SUSPECT for C:/Users/user/Documents/GitHub/KenazAI\.synapse\db\graph: Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005 — no further kuzu calls will be issued against this Database for the rest of this process's lifetime (persisted; the next start will also refuse it). Recovery: rebuild_graph.
|
|
44
|
+
2026-10-03T21:12:37.815639Z ERROR [synapse] dashboard/src-tauri/src/features/synapse/scanner.rs write hit a native ACCESS_VIOLATION — graph marked corrupt_suspect, aborting the round instead of quarantining just this file: Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005
|
|
45
|
+
2026-10-03T21:12:37.815845Z ERROR Incremental index round aborted mid-batch (with_connection closure returned Err): Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005. Recording phase=failed and NOT advancing last_commit_hash, so the file(s) that caused this are retried (re-appear in the git diff) on the next incremental cycle instead of being permanently skipped.
|
|
46
|
+
2026-10-03T21:12:37.823713Z INFO [synapse] IS_INDEXING cleared project=C:/Users/user/Documents/GitHub/KenazAI
|
|
47
|
+
2026-10-03T21:12:39.477700Z INFO [synapse spool] drain: attempted=1 replayed=0 still_pending=1 dead_lettered=0
|
|
48
|
+
2026-10-03T21:12:40.139564Z INFO startup mark label="session_watcher_start_begin" t_ms=5541 thread=tokio-runtime-worker
|
|
49
|
+
2026-10-03T21:12:40.139634Z INFO startup mark label="session_watcher_start_done" t_ms=5541 thread=tokio-runtime-worker
|
|
50
|
+
2026-10-03T21:12:40.139740Z INFO [session_watcher] Watching "C:\\Users\\user\\.claude\\projects\\C--Users-user-Documents-GitHub-KenazAI" (notify + polling fallback)
|
|
51
|
+
2026-10-03T21:12:44.502961Z INFO startup mark label="synapse_kuzu_memorydb_open_done" t_ms=9904 thread=delta-gap-recovery
|
|
52
|
+
2026-10-03T21:12:44.680900Z INFO startup mark label="delta_graph_recovery_done" t_ms=10082 thread=delta-gap-recovery
|
|
53
|
+
2026-10-03T21:12:44.706667Z INFO startup mark label="heat_cache_load_done" t_ms=10108 thread=delta-gap-recovery
|
|
54
|
+
2026-10-03T21:13:01.645377Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
|
|
55
|
+
2026-10-03T21:13:01.645462Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
|
|
56
|
+
2026-10-03T21:13:01.650906Z INFO MCP tool call tool="synapse_cypher" plugin=None
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005
|
|
@@ -37,7 +37,7 @@ Hooks only act in Kenaz projects (`.kenaz/project.yaml`, `_project-guard.js`): r
|
|
|
37
37
|
| Class | Skills |
|
|
38
38
|
|---|---|
|
|
39
39
|
| ship (34) | architect_agent, assessment_agent, backend_agent, backend_engineer, code_reviewer, dev_agent, designer, developer, feature_agent, frontend_agent, incident_response, product_manager, qa_engineer, security_agent, spec_optimizer, sre_agent, adapt, animate, audit, bolder, clarify, colorize, critique, delight, distill, extract, frontend-design, harden, normalize, onboard, optimize, polish, quieter, ui-ux-pro-max |
|
|
40
|
-
| core | heimdall, huginn,
|
|
40
|
+
| core | heimdall, huginn, sunny, optimization_agent |
|
|
41
41
|
| retire | legacy_dev |
|
|
42
42
|
| third-party / domain (not shipped; install separately from .agents/skills) | vercel-react-best-practices, supabase-postgres-best-practices, remotion-best-practices, pcb-review |
|
|
43
43
|
|
|
@@ -59,7 +59,7 @@ test('ygg status fixture: masked key, room, peers, mail (TASK_2366)', () => {
|
|
|
59
59
|
const s = buildStatus(
|
|
60
60
|
{ enabled: true, effective_project_key: 'acme/app', node_name: 'laptop', publishable_key: key, key_source: 'project', collaboration_projects: ['acme/app'] },
|
|
61
61
|
{ connected: true, nodes: [{ node_name: 'laptop', is_online: true, updated_at: new Date().toISOString() }, { node_name: 'desk', is_online: true, updated_at: new Date().toISOString() }, { node_name: 'old', is_online: false }, { node_name: 'BA-Cheng', is_online: true, updated_at: '2026-06-30T00:00:00Z' }] },
|
|
62
|
-
{ pending_count: 2, undeliverable_count: 1 });
|
|
62
|
+
{ pending_count: 2, waiting_count: 2, undeliverable_count: 1 });
|
|
63
63
|
const out = renderStatus(s);
|
|
64
64
|
assert.ok(!out.includes('SECRETSECRET') && !JSON.stringify(s).includes('SECRETSECRET'));
|
|
65
65
|
assert.match(out, /ygk_\*{4}1234 \(source: project\)/);
|
|
@@ -67,6 +67,7 @@ test('ygg status fixture: masked key, room, peers, mail (TASK_2366)', () => {
|
|
|
67
67
|
assert.match(out, /Relay:\s+connected/);
|
|
68
68
|
assert.match(out, /Peers online:\s+desk$/m);
|
|
69
69
|
assert.match(out, /Pending mail:\s+2/);
|
|
70
|
+
assert.match(out, /Waiting for a session: 2/);
|
|
70
71
|
assert.match(out, /Undeliverable:\s+1/);
|
|
71
72
|
const down = renderStatus(buildStatus({ enabled: false }, { connected: false, reason: 'relay unreachable', nodes: [] }, null));
|
|
72
73
|
assert.match(down, /NOT connected \(relay unreachable\)/);
|
|
@@ -121,3 +122,71 @@ test('ygg join activates the room in collaboration_projects; status explains how
|
|
|
121
122
|
const { buildStatus, renderStatus } = require('./ygg.js');
|
|
122
123
|
assert.match(renderStatus(buildStatus(cfg, { connected: false, nodes: [] }, null)), /not active in this project - run "ygg join <key>"/);
|
|
123
124
|
});
|
|
125
|
+
|
|
126
|
+
// TASK_2416: `ygg create` posts to the relay (config relay_url), then joins with the returned key.
|
|
127
|
+
// One local stub plays both the Kenaz backend and the relay; nothing touches ~/.kenaz or ~/.claude.
|
|
128
|
+
async function withStub(relayReply, fn) {
|
|
129
|
+
const http = require('http');
|
|
130
|
+
const seen = { relay: [], config: [] };
|
|
131
|
+
const cfg = { enabled: false, effective_project_key: 'tmp-room', node_name: 'n', publishable_key: '', collaboration_projects: [], relay_url: '' };
|
|
132
|
+
const srv = http.createServer((req, res) => {
|
|
133
|
+
let b = ''; req.on('data', (d) => { b += d; });
|
|
134
|
+
req.on('end', () => {
|
|
135
|
+
res.setHeader('content-type', 'application/json');
|
|
136
|
+
if (req.url === '/api/v1/teams') {
|
|
137
|
+
seen.relay.push({ method: req.method, body: b });
|
|
138
|
+
res.statusCode = relayReply.status; return res.end(JSON.stringify(relayReply.body));
|
|
139
|
+
}
|
|
140
|
+
if (req.url.startsWith('/api/nodes')) return res.end('[]');
|
|
141
|
+
if (req.url.startsWith('/api/yggdrasil/config')) {
|
|
142
|
+
if (req.method === 'POST') { seen.config.push(JSON.parse(b)); return res.end(JSON.stringify({ ok: true })); }
|
|
143
|
+
return res.end(JSON.stringify(cfg));
|
|
144
|
+
}
|
|
145
|
+
res.end(JSON.stringify({ success: true, created: false }));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
await new Promise((r) => srv.listen(0, '127.0.0.1', r));
|
|
149
|
+
cfg.relay_url = 'ws://127.0.0.1:' + srv.address().port;
|
|
150
|
+
const prev = process.env.KENAZ_HTTP_PORT;
|
|
151
|
+
process.env.KENAZ_HTTP_PORT = String(srv.address().port);
|
|
152
|
+
try { return await fn(seen, cfg); } finally {
|
|
153
|
+
if (prev === undefined) delete process.env.KENAZ_HTTP_PORT; else process.env.KENAZ_HTTP_PORT = prev;
|
|
154
|
+
srv.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
test('ygg create posts the name to the relay, joins with the new key, prints it once (TASK_2416)', async () => {
|
|
159
|
+
const KEY = 'ygk_' + '2'.repeat(32);
|
|
160
|
+
await withStub({ status: 201, body: { ok: true, team_id: 't1', team_name: 'Acme', key: KEY, max_nodes: 10 } }, async (seen) => {
|
|
161
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg-create-'));
|
|
162
|
+
const { createTeam, renderStatus } = require('./ygg.js');
|
|
163
|
+
const s = await createTeam(dir, 'Acme', 'project');
|
|
164
|
+
assert.deepStrictEqual(JSON.parse(seen.relay[0].body), { name: 'Acme' });
|
|
165
|
+
assert.strictEqual(seen.config[0].publishable_key, KEY);
|
|
166
|
+
assert.strictEqual(seen.config[0].enabled, true);
|
|
167
|
+
assert.deepStrictEqual(seen.config[0].collaboration_projects, ['tmp-room']);
|
|
168
|
+
const out = renderStatus(s);
|
|
169
|
+
assert.match(out, new RegExp('share with teammates: kenaz ygg join ' + KEY));
|
|
170
|
+
assert.ok(!out.includes('Key: ' + KEY), 'status line keeps the key masked');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('ygg create explains 429, 503, missing relay_url and unreachable relay (TASK_2416)', async () => {
|
|
175
|
+
const { createTeam } = require('./ygg.js');
|
|
176
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg-create-err-'));
|
|
177
|
+
await withStub({ status: 429, body: { ok: false, error: 'Too many team creations (limit 3 per hour)' } }, async (seen) => {
|
|
178
|
+
await assert.rejects(createTeam(dir, '', 'project'), /rate limited by the relay: Too many team creations/);
|
|
179
|
+
assert.strictEqual(seen.config.length, 0, 'no join after a failed create');
|
|
180
|
+
});
|
|
181
|
+
await withStub({ status: 503, body: { ok: false, error: 'REGISTRY_SERVICE_KEY not configured' } }, async () => {
|
|
182
|
+
await assert.rejects(createTeam(dir, '', 'project'), /does not offer self-serve keys: REGISTRY_SERVICE_KEY/);
|
|
183
|
+
});
|
|
184
|
+
await withStub({ status: 201, body: {} }, async (seen, cfg) => {
|
|
185
|
+
cfg.relay_url = '';
|
|
186
|
+
await assert.rejects(createTeam(dir, '', 'project'), /no relay_url configured/);
|
|
187
|
+
});
|
|
188
|
+
await withStub({ status: 201, body: {} }, async (seen, cfg) => {
|
|
189
|
+
cfg.relay_url = 'ws://127.0.0.1:1'; // nothing listens
|
|
190
|
+
await assert.rejects(createTeam(dir, '', 'project'), /cannot reach the relay at http:\/\/127\.0\.0\.1:1/);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
// node ygg.js <join <key> [--project|--global] | status | leave> [--project-path <dir>] [--json]
|
|
3
|
+
// node ygg.js <join <key> [--project|--global] | create [--name N] [--project|--global] | status | leave> [--project-path <dir>] [--json]
|
|
4
4
|
// Yggdrasil setup without the Dashboard (TASK_2366). Thin client over the Rust backend's
|
|
5
5
|
// /api/yggdrasil/config + /api/peer/status (same per-project/global key rules as the Dashboard,
|
|
6
6
|
// TASK_2321/2349). k-cli `ygg` shells out to this file - the one implementation.
|
|
@@ -73,6 +73,7 @@ function buildStatus(cfg, relay, peer) {
|
|
|
73
73
|
? 'Room "' + cfg.effective_project_key + '" is a generic name; ' + online.join(', ') + ' may be an unrelated project. For a private room set yggdrasil_project: "' + cfg.effective_project_key + '-<unique-id>" in .kenaz/yggdrasil.yaml.'
|
|
74
74
|
: '',
|
|
75
75
|
pending_mail: peer && typeof peer.pending_count === 'number' ? peer.pending_count : null,
|
|
76
|
+
waiting_mail: peer && typeof peer.waiting_count === 'number' ? peer.waiting_count : null,
|
|
76
77
|
undeliverable_mail: peer && typeof peer.undeliverable_count === 'number' ? peer.undeliverable_count : null,
|
|
77
78
|
// TASK_2380: real delivery mode per live session (push | pull-on-prompt | none), never a bare 'enabled'.
|
|
78
79
|
delivery_mode: peer && peer.delivery_mode ? peer.delivery_mode : 'unknown',
|
|
@@ -92,10 +93,15 @@ function renderStatus(s) {
|
|
|
92
93
|
'Relay: ' + (s.relay_connected ? 'connected' : 'NOT connected' + (s.relay_note ? ' (' + s.relay_note + ')' : '')),
|
|
93
94
|
'Peers online: ' + (s.peers_online.length ? s.peers_online.join(', ') : 'none'),
|
|
94
95
|
'Pending mail: ' + (s.pending_mail == null ? 'unknown' : s.pending_mail),
|
|
96
|
+
'Waiting for a session: ' + (s.waiting_mail == null ? 'unknown' : s.waiting_mail) + ' (stored on this node, readable)',
|
|
95
97
|
'Undeliverable: ' + (s.undeliverable_mail == null ? 'unknown' : s.undeliverable_mail),
|
|
96
98
|
'Delivery: ' + s.delivery_mode + (s.sessions.length ? '' : ' (no live sessions)'),
|
|
97
99
|
];
|
|
98
100
|
for (const x of s.sessions) L.push(' - ' + x.agent + ' ' + String(x.session_id).slice(0, 8) + ': ' + x.delivery_mode);
|
|
101
|
+
if (s.created) {
|
|
102
|
+
L.push('', 'New team key (shown once, save it): ' + s.created.key + (s.created.team_name ? ' [' + s.created.team_name + ']' : ''));
|
|
103
|
+
L.push('share with teammates: kenaz ygg join ' + s.created.key);
|
|
104
|
+
}
|
|
99
105
|
if (s.room_warning) L.push('WARNING: ' + s.room_warning);
|
|
100
106
|
if (s.delivery_note) L.push('Note: ' + s.delivery_note);
|
|
101
107
|
return L.join('\n');
|
|
@@ -141,6 +147,38 @@ async function join(dir, key, scope) {
|
|
|
141
147
|
return status(dir);
|
|
142
148
|
}
|
|
143
149
|
|
|
150
|
+
// TASK_2416: self-serve room key. POST <relay>/api/v1/teams (anonymous, per-IP limited), then join
|
|
151
|
+
// with the new key so this node is in the room immediately. The full key is returned once.
|
|
152
|
+
async function createTeam(dir, name, scope) {
|
|
153
|
+
const cfg = await getConfig(dir);
|
|
154
|
+
if (cfg.error) throw new Error(cfg.error);
|
|
155
|
+
const base = relayHttpBase(cfg.relay_url);
|
|
156
|
+
if (!base) throw new Error('no relay_url configured - cannot reach the relay to create a key (run "npx kenaz@latest install" or set relay_url in .kenaz/yggdrasil.yaml)');
|
|
157
|
+
let res;
|
|
158
|
+
try {
|
|
159
|
+
res = await fetch(base + '/api/v1/teams', {
|
|
160
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
161
|
+
body: JSON.stringify(name ? { name } : {}), signal: AbortSignal.timeout(15000),
|
|
162
|
+
});
|
|
163
|
+
} catch (e) {
|
|
164
|
+
throw new Error('cannot reach the relay at ' + base + ' (' + (e.cause && e.cause.code || e.message) + '); check your network and try again');
|
|
165
|
+
}
|
|
166
|
+
let data = null;
|
|
167
|
+
try { data = await res.json(); } catch (e) { /* non-JSON body */ }
|
|
168
|
+
if (!res.ok || !data || !data.key) {
|
|
169
|
+
const why = data && data.error ? data.error : 'HTTP ' + res.status;
|
|
170
|
+
if (res.status === 429) throw new Error('rate limited by the relay: ' + why);
|
|
171
|
+
if (res.status === 503) throw new Error('this relay does not offer self-serve keys: ' + why);
|
|
172
|
+
throw new Error('relay refused to create a key (HTTP ' + res.status + '): ' + why);
|
|
173
|
+
}
|
|
174
|
+
let s;
|
|
175
|
+
try { s = await join(dir, data.key, scope); } catch (e) {
|
|
176
|
+
// The key exists on the relay now; never lose it because the local join failed.
|
|
177
|
+
throw new Error('key created (' + data.key + ') but joining failed: ' + e.message + ' - retry with: kenaz ygg join ' + data.key);
|
|
178
|
+
}
|
|
179
|
+
return Object.assign(s, { created: { key: data.key, team_name: data.team_name || name || '' } });
|
|
180
|
+
}
|
|
181
|
+
|
|
144
182
|
async function leave(dir) {
|
|
145
183
|
const cfg = await getConfig(dir);
|
|
146
184
|
await postConfig(dir, postBody(cfg, { key_scope: 'clear_project', collaboration_projects: withCollab(cfg, false) }));
|
|
@@ -148,7 +186,7 @@ async function leave(dir) {
|
|
|
148
186
|
}
|
|
149
187
|
|
|
150
188
|
async function main(argv) {
|
|
151
|
-
const rest = []; let dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); let scope = 'project'; let json = false;
|
|
189
|
+
const rest = []; let dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); let scope = 'project'; let json = false; let name = '';
|
|
152
190
|
for (let i = 0; i < argv.length; i++) {
|
|
153
191
|
const a = argv[i];
|
|
154
192
|
if (a === '--project-path' && argv[i + 1]) dir = argv[++i];
|
|
@@ -156,6 +194,8 @@ async function main(argv) {
|
|
|
156
194
|
else if (a === '--global') scope = 'global';
|
|
157
195
|
else if (a === '--project') scope = 'project';
|
|
158
196
|
else if (a === '--json') json = true;
|
|
197
|
+
else if (a === '--name' && argv[i + 1]) name = argv[++i];
|
|
198
|
+
else if (a.startsWith('--name=')) name = a.slice(7);
|
|
159
199
|
else rest.push(a);
|
|
160
200
|
}
|
|
161
201
|
dir = path.resolve(dir);
|
|
@@ -163,9 +203,10 @@ async function main(argv) {
|
|
|
163
203
|
let s;
|
|
164
204
|
try {
|
|
165
205
|
if (sub === 'join') s = await join(dir, rest[1], scope);
|
|
206
|
+
else if (sub === 'create') s = await createTeam(dir, name, scope);
|
|
166
207
|
else if (sub === 'leave') s = await leave(dir);
|
|
167
208
|
else if (sub === 'status') s = await status(dir);
|
|
168
|
-
else throw new Error('unknown ygg subcommand: ' + sub + ' (join <key> | status | leave)');
|
|
209
|
+
else throw new Error('unknown ygg subcommand: ' + sub + ' (join <key> | create [--name N] | status | leave)');
|
|
169
210
|
} catch (e) {
|
|
170
211
|
console.error('ygg: ' + e.message + (/fetch failed|timeout|abort/i.test(e.message) ? ' (is the Kenaz backend running on ' + BASE() + '?)' : ''));
|
|
171
212
|
process.exit(1);
|
|
@@ -173,5 +214,5 @@ async function main(argv) {
|
|
|
173
214
|
console.log(json ? JSON.stringify(s, null, 2) : renderStatus(s));
|
|
174
215
|
}
|
|
175
216
|
|
|
176
|
-
module.exports = { nodeIsOnline, maskKey, buildStatus, renderStatus, join };
|
|
217
|
+
module.exports = { nodeIsOnline, maskKey, buildStatus, renderStatus, join, createTeam };
|
|
177
218
|
if (require.main === module) main(process.argv.slice(2));
|
|
@@ -232,7 +232,6 @@ description: 程式碼審查、三級問題分類(Blocker/Suggestion/Nit)、
|
|
|
232
232
|
|
|
233
233
|
| Mutation | Counterpart | 缺失嚴重度 |
|
|
234
234
|
|----------|-------------|-----------|
|
|
235
|
-
| `hippocampus_episode_start` | `hippocampus_episode_close` | leak(episode 永不歸檔) |
|
|
236
235
|
| `task-api.js claim` | `task-api.js complete` 或 `move blocked` | orphan(task 卡在 active/) |
|
|
237
236
|
| `addEventListener` | `removeEventListener` | leak(listener 記憶體洩漏) |
|
|
238
237
|
| `setInterval` | `clearInterval` | leak(定時器持續觸發) |
|