xitto-kernel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/bin/xitto-kernel.js +3 -0
  4. package/docs/01-architecture.md +105 -0
  5. package/docs/02-domain-pack-spec.md +109 -0
  6. package/docs/03-kernel-contract.md +79 -0
  7. package/docs/04-migration-from-xitto-code.md +70 -0
  8. package/docs/05-example-packs.md +95 -0
  9. package/docs/06-authoring-a-pack.md +86 -0
  10. package/package.json +55 -0
  11. package/src/app/cli.js +243 -0
  12. package/src/app/index.js +5 -0
  13. package/src/app/main.js +87 -0
  14. package/src/app/markdown.js +36 -0
  15. package/src/app/providers.js +39 -0
  16. package/src/app/scaffold.js +40 -0
  17. package/src/app/templates/README.md.tmpl +32 -0
  18. package/src/app/templates/gitignore.tmpl +4 -0
  19. package/src/app/templates/index.js.tmpl +7 -0
  20. package/src/app/templates/pack.js.tmpl +32 -0
  21. package/src/app/templates/package.json.tmpl +12 -0
  22. package/src/index.js +15 -0
  23. package/src/kernel/agent-loop.js +285 -0
  24. package/src/kernel/compaction.js +65 -0
  25. package/src/kernel/guard-chain.js +42 -0
  26. package/src/kernel/hooks.js +47 -0
  27. package/src/kernel/index.js +291 -0
  28. package/src/kernel/mcp.js +54 -0
  29. package/src/kernel/memory.js +45 -0
  30. package/src/kernel/pack-loader.js +45 -0
  31. package/src/kernel/provider.js +20 -0
  32. package/src/kernel/security/allow.js +41 -0
  33. package/src/kernel/security/danger.js +37 -0
  34. package/src/kernel/security/permission-step.js +63 -0
  35. package/src/kernel/security/sandbox.js +111 -0
  36. package/src/kernel/session.js +36 -0
  37. package/src/kernel/skills.js +37 -0
  38. package/src/kernel/subagent.js +46 -0
  39. package/src/kernel/tool-registry.js +45 -0
  40. package/src/packs/coding/index.js +157 -0
  41. package/src/packs/data-query/index.js +67 -0
  42. package/src/packs/notes/index.js +79 -0
  43. package/src/types.js +79 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ishoplus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # xitto-kernel
2
+
3
+ [![CI](https://github.com/ishoplus/xitto-kernel/actions/workflows/ci.yml/badge.svg)](https://github.com/ishoplus/xitto-kernel/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
5
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org)
6
+
7
+ > 領域無關的 agent 底座(**可當依賴套件** — 你的領域 agent 是獨立專案,import kernel 而非 clone,升級不固化)
8
+
9
+ 把 [`xitto-code`](../xitto-code) 這個完整的編碼智能體,抽象成一個**領域無關的 agent kernel** + 可插拔的 **DomainPack**。
10
+ 同一個 kernel(多步工具循環、守衛鏈、權限/沙箱、provider 抽象)能承載任何領域的 agent;
11
+ 「編碼」只是其中一個 DomainPack,換成「資料查詢」「知識庫」「客服/維運」等只需替換 pack。
12
+ 互動 CLI 在 app 層(薄);更豐富的 TUI 或其他前端可作為另一個 app 消費同一組 kernel 事件。
13
+
14
+ ## 一句話
15
+
16
+ > **kernel 提供「怎麼跑一個 agent」,DomainPack 提供「這個 agent 會什麼、守什麼」。**
17
+
18
+ ## 設計從哪來
19
+
20
+ xitto-code 經掃描後,約 **8 成已是領域無關的 kernel**;真正跟編碼綁死的只有三件事:
21
+ `read-before-edit`、`lint/型別自動驗收`、`git 整合`。本設計把這三件事從 kernel 剝離成 pack 的職責。
22
+
23
+ ## 快速開始
24
+
25
+ **前置需求**
26
+ - Node.js ≥ 20
27
+ - `~/.xitto-code/providers.json` —— LLM provider 設定(與 xitto-code 共用,內含 API key)。
28
+ 沒有的話,複製一份填入 key 即可(格式見 xitto-code 的 `providers.example.json`)。
29
+
30
+ **一次性設定**(讓 `xitto-kernel` 成為全域命令)
31
+ ```bash
32
+ cd xitto-kernel
33
+ npm install
34
+ npm link # 之後任何目錄都能用 xitto-kernel 命令
35
+ ```
36
+ > 不想 link 也行:直接 `node /路徑/xitto-kernel/bin/xitto-kernel.js …` 或在 repo 內 `npm start`。
37
+
38
+ **跑內建 pack(互動 CLI)**
39
+ ```bash
40
+ xitto-kernel # coding agent(讀寫檔案、跑命令)
41
+ xitto-kernel --pack notes # 筆記 / 知識庫 agent
42
+ xitto-kernel --pack data-query
43
+ xitto-kernel --sandbox # 啟動就開 Seatbelt 沙箱
44
+ ```
45
+
46
+ **CLI 內操作**:直接打需求(模型會自己呼叫工具);指令 `/help` `/sandbox [on|off]` `/tools` `/clear` `/exit`;`Ctrl+C` 中斷該輪、閒置時再按一次離開。
47
+
48
+ ## 做你自己的領域 agent(不固化)
49
+
50
+ kernel 是**被依賴的套件**,不是被 clone 的範本。你的 agent 是獨立小專案:
51
+
52
+ ```bash
53
+ xitto-kernel new-agent my-bot # 產出獨立專案(import kernel,不改 kernel)
54
+ cd my-bot && npm install && npm start
55
+ ```
56
+
57
+ 產出的 `my-bot/` 只有:`pack.js`(你的領域:會什麼/守什麼)+ `index.js`(幾行啟動)+ `package.json`(`"xitto-kernel": "file:…"`)。
58
+ runtime(多步循環/串流/權限/沙箱/CLI)全在 kernel;`npm update xitto-kernel` 升級底座,**你的 agent 不會被固化**。
59
+
60
+ ```
61
+ my-bot/ ← 你的獨立專案
62
+ ├── package.json dependencies: { xitto-kernel: file:… }
63
+ ├── pack.js ← 你的 DomainPack
64
+ └── index.js import { runCli, loadModel } from 'xitto-kernel/app'
65
+ ```
66
+
67
+ > 內建的 coding / data-query / notes 是「官方範例 pack」,住在 kernel repo 裡;你的 pack 住在你自己的專案裡。兩者並存、互不固化。
68
+
69
+ ## 搭建狀態
70
+
71
+ ```
72
+ xitto-kernel/
73
+ ├── src/
74
+ │ ├── types.js 型別定義(DomainPack / Tool / KernelServices …)
75
+ │ ├── index.js 公開 API(createKernel / loadPack / defineDomainPack …)
76
+ │ ├── kernel/
77
+ │ │ ├── pack-loader.js ✅ pack 載入/驗證
78
+ │ │ ├── tool-registry.js ✅ 工具 metadata 驅動(取代寫死名單)
79
+ │ │ ├── guard-chain.js ✅ 固定順序 beforeToolCall 守衛鏈
80
+ │ │ ├── agent-loop.js ✅ 移植自 xitto-code 的 Agent(串流 + 多步工具循環)
81
+ │ │ ├── provider.js ✅ provider 呼叫適配(pi-ai streamSimple + cache 相容)
82
+ │ │ ├── security/ ✅ 真實 sandbox(守衛鏈第 5 格)
83
+ │ │ │ ├── sandbox.js ✅ 靜態策略 + macOS Seatbelt OS 級隔離
84
+ │ │ │ ├── danger.js ✅ 危險命令偵測(rm -rf / fork bomb / curl|sh …)
85
+ │ │ │ ├── allow.js ✅ 命令簽章白名單
86
+ │ │ │ └── permission-step.js ✅ 第 5 格:deny→靜態策略→危險→確認(metadata 驅動)
87
+ │ │ └── index.js ✅ createKernel:runTool + runTurn + sandbox 接線
88
+ │ ├── app/ ✅ app 層(薄;TUI 不在 kernel 內)
89
+ │ │ ├── index.js ✅ xitto-kernel/app 公開 API(runCli/loadModel/newAgent)
90
+ │ │ ├── cli.js ✅ 互動 CLI:串流文字 + 工具顯示 + /指令 + Ctrl+C 中斷
91
+ │ │ ├── main.js ✅ 進入點 + new-agent 子指令
92
+ │ │ ├── scaffold.js ✅ 腳手架:產出獨立 agent 專案(不改 kernel)
93
+ │ │ ├── templates/ ✅ 獨立專案樣板(package.json/index.js/pack.js…)
94
+ │ │ └── providers.js ✅ providers.json 載入(provider 設定屬 app,非 kernel)
95
+ │ └── packs/
96
+ │ ├── coding/ ✅ 參考 pack(read/ls/write/edit/bash 真實工具)
97
+ │ ├── data-query/ ✅ 第二領域(證明正交)
98
+ │ └── notes/ ✅ 第三領域(知識庫;示範「怎麼做新領域 agent」)
99
+ ├── bin/xitto-kernel.js ✅ CLI 進入點(run / new-agent)
100
+ ├── test/ ✅ 41 測試全綠(runTurn + Seatbelt 隔離 + 腳手架 + …)
101
+ └── examples/
102
+ ├── demo.js ✅ 不靠 LLM:同 kernel、兩領域、守衛真實生效
103
+ └── live.js ✅ 真實 LLM(MiniMax):模型實際呼叫工具完成任務
104
+ ```
105
+
106
+ **也可跑**:`npm test`(41 綠)、`npm run demo`(不靠 LLM)、`node examples/live.js`(真實 LLM)。
107
+ **runTurn 已移植**:串流 → 工具呼叫(過 kernel 守衛鏈)→ 回灌 → 再串流的多步循環,能用真實 provider 驅動。
108
+ **真實 sandbox 已接守衛鏈第 5 格**:(A) 靜態策略擋網路/提權/危險命令;(B) macOS Seatbelt 在執行期 OS 級隔離,擋下靜態策略漏掉的混淆越界寫入。`sandboxable` 工具自動包裹,`tool.readOnly` 自動放行——全 metadata 驅動,無領域名單。
109
+ **仍為接縫(後續)**:回合內壓縮、hooks/skills/MCP/subagent、contextFiles 載入、互動權限確認(CLI 目前 headless 放行 mutating、危險命令仍擋)。更豐富的 Ink TUI 可作為另一個 app 消費同一組 kernel 事件。
110
+
111
+ ## 文件索引
112
+
113
+ | 文件 | 內容 |
114
+ |------|------|
115
+ | [01-architecture.md](docs/01-architecture.md) | 分層架構、kernel 模組清單、一次 turn 的生命週期與 kernel/pack 交界 |
116
+ | [02-domain-pack-spec.md](docs/02-domain-pack-spec.md) | `DomainPack` 介面完整規格(逐欄位、必填/選填、預設)|
117
+ | [03-kernel-contract.md](docs/03-kernel-contract.md) | kernel 對 pack 提供的服務(`KernelServices`)與生命週期 hook |
118
+ | [04-migration-from-xitto-code.md](docs/04-migration-from-xitto-code.md) | 從 xitto-code 抽離的具體步驟:每個耦合點怎麼搬、風險 |
119
+ | [05-example-packs.md](docs/05-example-packs.md) | 範例 pack 對照(coding / data-query 已內建 + ops 示意)驗證同介面能跑不同領域 |
120
+ | [06-authoring-a-pack.md](docs/06-authoring-a-pack.md) | **怎麼用底座做一個新領域 agent**:最小 pack、工具形狀、三步驟、放工具 vs prompt |
121
+
122
+ ## 現況與後續
123
+
124
+ **已完成**:pack 系統、工具 metadata 驅動、固定順序守衛鏈、agent loop(真實 LLM 多步循環)、
125
+ 真實 sandbox(靜態策略 + macOS Seatbelt)、pack.verify 自我驗收、pack.contextFiles 載入、
126
+ **跨 session 記憶 + resume**、**互動權限確認**(/auto、--yes)、**/plan 計劃模式 + /undo**、
127
+ **git 能力**(coding pack)、**spawn_agent 子 agent**、**PreToolUse/PostToolUse hooks**、
128
+ **skills 漸進揭露**、**MCP 工具接入**、互動 CLI、腳手架(`new-agent` 產出獨立專案)。75 測試全綠。
129
+
130
+ **仍為接縫(後續)**:發佈到 npm(讓 `file:` 依賴變正式版本;版本已備 0.1.0)。Ink 全功能 TUI 可作為另一個 app(目前 CLI 已有輕量串流 markdown + 彩色 diff)。
131
+
132
+ **設計取向**:沿用 Node ESM + pi-ai provider 抽象;不重寫 xitto-code(kernel 是抽象,xitto-code 仍可獨立存在)。
133
+
134
+ ## 貢獻
135
+
136
+ 見 [CONTRIBUTING.md](CONTRIBUTING.md)。核心原則:kernel 必須領域無關(安全行為靠工具 metadata,不寫死領域名單);新領域 = 新增一個 pack,kernel 零改動。
137
+
138
+ ## 授權
139
+
140
+ [MIT](LICENSE) © ishoplus
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/app/main.js';
3
+ main().catch((err) => { console.error('\x1b[31m' + (err?.stack || err?.message || err) + '\x1b[0m'); process.exit(1); });
@@ -0,0 +1,105 @@
1
+ # 01 · 架構:kernel + DomainPack
2
+
3
+ ## 分層
4
+
5
+ ```
6
+ ┌──────────────────────────────────────────────────────────────┐
7
+ │ 入口 / 編排 (app) │
8
+ │ 解析 CLI、載入設定、選 DomainPack、掛載 TUI、跑互動循環 │
9
+ ├──────────────────────────────────────────────────────────────┤
10
+ │ DomainPack(可插拔,一個領域一份) │
11
+ │ tools · systemPrompt · contextFiles · mutatingTools │
12
+ │ verify? · preToolPolicy? · permissionPolicy? │
13
+ ├──────────────────────────────────────────────────────────────┤
14
+ │ Kernel(領域無關,固定) │
15
+ │ ┌────────────┬──────────────┬───────────────┬──────────────┐ │
16
+ │ │ agent loop │ provider 抽象 │ 工具註冊/執行 │ 權限/沙箱 │ │
17
+ │ │ 記憶 │ 上下文壓縮 │ session 持久化 │ 子 agent │ │
18
+ │ │ skills │ commands │ hooks │ MCP │ │
19
+ │ │ TUI(串流) │ 重試/think 過濾 │ │
20
+ │ └────────────┴──────────────┴───────────────┴──────────────┘ │
21
+ ├──────────────────────────────────────────────────────────────┤
22
+ │ Provider 層(pi-ai):anthropic / openai / google / … │
23
+ └──────────────────────────────────────────────────────────────┘
24
+ ```
25
+
26
+ **規則**:kernel 不認識任何領域概念(不知道什麼是「檔案」「commit」「SQL」)。
27
+ 所有領域知識都在 DomainPack 裡。kernel 只認得「工具」「訊息」「權限決策」「記憶條目」這些通用原語。
28
+
29
+ ## Kernel 模組清單(從 xitto-code 哪些檔來)
30
+
31
+ 這些檔在 xitto-code 已經是領域無關的,直接構成 kernel:
32
+
33
+ | kernel 能力 | xitto-code 來源 | 是否需改 |
34
+ |------|------|------|
35
+ | agent 循環 | `agent-loop.js` | 移除寫死的工具名集合(見 04)|
36
+ | provider 設定 | `config.js` | 無 |
37
+ | 上下文壓縮 | `compaction.js` | 無 |
38
+ | 跨 session 記憶 | `memory.js` | 無 |
39
+ | 對話持久化 | `session.js` | 無 |
40
+ | 技能/指令/hooks/MCP | `skills.js` `commands.js` `hooks.js` `mcp.js` | 無 |
41
+ | 子 agent / workflow | `subagent.js` | 工具集改由 pack 提供(見 04)|
42
+ | 權限/白名單/危險偵測/沙箱 | `permissions.js` `allow.js` `danger.js` `sandbox.js` | 預設值參數化,機制不動 |
43
+ | TUI 串流/重試/think 過濾 | `tui.js` `retry.js` `think-filter.js` | 無 |
44
+ | 工具組裝骨架 | `agent-factory.js` | **主要改點**:工具來源 + 守衛改由 pack 注入 |
45
+ | 編排器 | `index.js` | **主要改點**:verify/git/read-before-edit 剝離成 pack |
46
+
47
+ ## DomainPack 是什麼(概覽,完整規格見 02)
48
+
49
+ 一個領域 = 一組注入 kernel 的東西:
50
+
51
+ ```js
52
+ const codingPack = {
53
+ name: 'coding',
54
+ tools, // read/write/edit/bash/grep…(編碼)
55
+ systemPrompt, // 編碼行為準則
56
+ contextFiles, // ['CLAUDE.md','AGENTS.md','XITTO.md']
57
+ mutatingTools, // ['write','edit','bash'] — 取代 kernel 寫死的集合
58
+ verify, // lint/型別自動驗收(其他領域可換或不要)
59
+ preToolPolicy, // read-before-edit(編碼特有守衛)
60
+ permissionPolicy, // 沙箱/白名單預設
61
+ };
62
+ ```
63
+
64
+ ## 一次 turn 的生命週期(標出 kernel ↔ pack 交界)
65
+
66
+ ```
67
+ 使用者輸入
68
+
69
+
70
+ [kernel] 注入 systemPrompt(pack) + contextFiles(pack) + 記憶 + skills
71
+
72
+
73
+ [kernel] agent loop:串流助理回覆
74
+ │ ├─ 文字 → TUI(kernel)
75
+ │ └─ 工具呼叫
76
+ │ │
77
+ │ ▼
78
+ │ [kernel] beforeToolCall 守衛鏈:
79
+ │ 1. planMode 擋 mutatingTools(pack 定義哪些算 mutating)
80
+ │ 2. 上下文熔斷(kernel)
81
+ │ 3. pack.preToolPolicy(領域守衛,如 read-before-edit) ← pack
82
+ │ 4. PreToolUse hooks(kernel)
83
+ │ 5. permissionHook(kernel;策略預設來自 pack.permissionPolicy)
84
+ │ │
85
+ │ ▼
86
+ │ [kernel] 執行工具(pack.tools 之一);沙箱包裹(kernel,若該領域啟用)
87
+ │ │
88
+ │ ▼
89
+ │ [kernel] PostToolUse hooks → 回灌修正
90
+
91
+ ▼ (本輪工具跑完、助理收尾)
92
+ [kernel] pack.verify?():領域自我驗收(編碼=lint;文件=連結檢查;無=略過) ← pack
93
+ │ 失敗 → 回灌讓 agent 修正(kernel 機制)
94
+
95
+ [kernel] 邊界壓縮(compaction) + 存 session + 更新 TUI 狀態列
96
+ ```
97
+
98
+ 交界很乾淨:**kernel 跑流程,pack 在四個插槽填領域內容**——
99
+ `systemPrompt/contextFiles`(起手)、`mutatingTools`(plan 守衛)、`preToolPolicy`(工具前守衛)、`verify`(收尾驗收),加上 `tools` 與 `permissionPolicy`。
100
+
101
+ ## 為什麼這樣切是對的
102
+
103
+ - **kernel 的價值是「正確地跑一個長時間、多工具、會壓縮、會記憶、有權限的 agent」**——這部分跟領域無關,最難寫好,也最值得重用。
104
+ - **領域的價值是「會什麼工具、守什麼規矩、怎麼算做完」**——這部分每個領域不同,但都能塞進同一組插槽。
105
+ - xitto-code 已驗證 kernel 那半部能穩定運作(287 測試、真實多 provider、沙箱),抽象只是把編碼那半部拔成可換的。
@@ -0,0 +1,109 @@
1
+ # 02 · DomainPack 介面規格
2
+
3
+ 一個 DomainPack 是一個純資料物件(含少量函數)。kernel 啟動時載入一個 pack,把它的欄位接到對應插槽。
4
+
5
+ ## 完整介面(JSDoc 形式;實際型別定義見 [`src/types.js`](../src/types.js))
6
+
7
+ ```js
8
+ /**
9
+ * @typedef {Object} DomainPack
10
+ *
11
+ * ── 必填 ──────────────────────────────────────────────
12
+ * @property {string} name 領域識別名('coding' / 'data-query' / 'notes')
13
+ * @property {() => Tool[]} tools 建立該領域的工具集(見下方 Tool)。
14
+ * 收 KernelServices 之外只需 cwd/設定,回傳工具陣列。
15
+ * @property {string} systemPrompt 領域行為準則,注入 system prompt。
16
+ *
17
+ * ── 選填(不給就用 kernel 的安全預設)─────────────────
18
+ * @property {string[]} [contextFiles] 由近而遠尋找的慣例檔名(編碼=['CLAUDE.md','AGENTS.md'])。
19
+ * 預設 [](不載入任何專案規範檔)。
20
+ * @property {string[]} [mutatingTools] 哪些工具算「會改動狀態」,供 plan 模式與熔斷判斷。
21
+ * 預設:tools 中標了 `mutating:true` 的那些(見 Tool.mutating)。
22
+ * @property {VerifyPolicy} [verify] 每輪收尾的領域自我驗收。預設 undefined(不驗收)。
23
+ * @property {PreToolPolicy} [preToolPolicy] 工具執行前的領域守衛。預設 undefined(無額外守衛)。
24
+ * @property {PermissionPolicy} [permissionPolicy] 該領域的權限/沙箱預設。預設 kernel 全域預設。
25
+ * @property {string} [memoryGuide] 要不要、何時主動存記憶的領域提示。預設 kernel 通用版。
26
+ */
27
+ ```
28
+
29
+ ### Tool(kernel 的通用工具原語)
30
+
31
+ kernel 不在乎工具做什麼,只在乎這個形狀。**這就是 xitto-code 既有的工具形狀**,所以任何現有工具不改即可用:
32
+
33
+ ```js
34
+ /**
35
+ * @typedef {Object} Tool
36
+ * @property {string} name
37
+ * @property {string} label UI 顯示用短名
38
+ * @property {string} description 給模型看的用途說明
39
+ * @property {object} parameters JSON Schema
40
+ * @property {(id, params, signal?, onUpdate?, ctx?) => Promise<ToolResult>} execute
41
+ * @property {boolean} [mutating] 是否會改動狀態(預設 false)。kernel 據此自動推導 mutatingTools。
42
+ * @property {boolean} [readOnly] 是否唯讀(唯讀工具 kernel 自動放行、不問權限)。
43
+ * @property {boolean} [sandboxable] 執行是否可被沙箱包裹(如走 shell 的工具)。預設 false。
44
+ */
45
+ ```
46
+
47
+ > 關鍵:把「這工具會不會改動 / 是否唯讀 / 能否沙箱」變成**工具自帶的 metadata**,
48
+ > kernel 就不必再寫死 `MUTATING = {'write','edit','bash'}` 這種領域名單(見 04 的改點)。
49
+
50
+ ### VerifyPolicy(領域自我驗收)
51
+
52
+ ```js
53
+ /**
54
+ * @typedef {Object} VerifyPolicy
55
+ * @property {(ctx: VerifyContext) => Promise<{ok: boolean, output?: string}>} run
56
+ * 回 ok:false 時 kernel 把 output 回灌給 agent 要求修正(沿用 xitto-code 的 runAutoVerify 機制,最多 N 輪)。
57
+ * @property {(ctx) => boolean} [shouldRun] 是否該跑(編碼:本輪有改檔才跑)。預設 turnModified。
58
+ * @property {number} [maxRounds] 回灌修正上限。預設 2。
59
+ */
60
+ ```
61
+
62
+ - 編碼:`run` = 跑 `detectVerifyCmd`(tsc/eslint),`shouldRun` = 本輪有 write/edit。
63
+ - 文件寫作:`run` = 連結/拼字檢查。
64
+ - 資料查詢:通常 `verify` 不給(查詢無「驗收」概念)。
65
+
66
+ ### PreToolPolicy(工具前領域守衛)
67
+
68
+ ```js
69
+ /**
70
+ * @typedef {Object} PreToolPolicy
71
+ * @property {(ctx: PreToolContext, kernel: KernelServices) => PolicyDecision} check
72
+ * 回 undefined = 放行;回 { block:true, reason } = 擋下並把 reason 餵回模型。
73
+ */
74
+ ```
75
+
76
+ - 編碼:實作 **read-before-edit**——「編輯已存在但未讀過的檔 → 擋下要求先 read」。
77
+ (這正是目前寫死在 `agent-factory.js` 的邏輯,搬成 pack 後 kernel 不再認識「edit/檔案」概念。)
78
+ - 其他領域:可換成自己的守衛(如客服 pack:「退款前必須先查到訂單」)或不給。
79
+
80
+ ### PermissionPolicy(權限/沙箱預設)
81
+
82
+ ```js
83
+ /**
84
+ * @typedef {Object} PermissionPolicy
85
+ * @property {'default'|'acceptEdits'|'plan'} [defaultMode]
86
+ * @property {boolean|object} [sandbox] 同 xitto-code settings.json 的 permissions.sandbox
87
+ * @property {string[]} [allow] 預設放行的工具/命令簽章
88
+ * @property {string[]} [deny] 預設禁止
89
+ */
90
+ ```
91
+
92
+ kernel 的 `permissions.js`/`sandbox.js` 機制完全不動,只是「預設值」改成可由 pack 提供、再被使用者的 `settings.json` 覆蓋。
93
+
94
+ ## 必填/選填總表
95
+
96
+ | 欄位 | 必填 | 不給時 |
97
+ |------|:----:|------|
98
+ | `name` | ✅ | — |
99
+ | `tools` | ✅ | — |
100
+ | `systemPrompt` | ✅ | — |
101
+ | `contextFiles` | ⬜ | 不載入專案規範檔 |
102
+ | `mutatingTools` | ⬜ | 從 `tool.mutating` 自動推導 |
103
+ | `verify` | ⬜ | 不做自我驗收 |
104
+ | `preToolPolicy` | ⬜ | 無領域守衛 |
105
+ | `permissionPolicy` | ⬜ | kernel 全域預設 |
106
+ | `memoryGuide` | ⬜ | kernel 通用記憶提示 |
107
+
108
+ > **最小 pack 只需三個必填欄位**就能跑起來——這是「底座好不好用」的關鍵指標:
109
+ > 新領域的入門成本 = 寫工具 + 一段 prompt + 一個名字。
@@ -0,0 +1,79 @@
1
+ # 03 · Kernel 契約
2
+
3
+ 兩個方向的契約:**kernel 對外提供什麼服務給 pack/工具用**,以及 **kernel 怎麼消費 pack**。
4
+
5
+ ## A. KernelServices — kernel 提供給 pack 與工具的服務
6
+
7
+ pack 的 `preToolPolicy.check` 與工具的 `execute(... ctx)` 可拿到這組服務(依賴注入,便於測試)。
8
+ 這些都是 xitto-code 已有、領域無關的能力:
9
+
10
+ ```js
11
+ /**
12
+ * @typedef {Object} KernelServices
13
+ * @property {string} cwd
14
+ * @property {Memory} memory // memory_save / memory_list(memory.js)
15
+ * @property {Spawn} spawn // 派唯讀子 agent 做聚焦調查(subagent.js)
16
+ * @property {Ask} ask // 向使用者提問(方向鍵選單 / 純文字)
17
+ * @property {Notify} notify // 推訊息到 TUI transcript
18
+ * @property {Model} model // 當前模型(provider 無關)
19
+ * @property {(messages) => Promise<CompactInfo>} compact // 手動觸發壓縮
20
+ * @property {Sandbox} sandbox // 查詢沙箱狀態 / 包裹命令(sandbox.js)
21
+ */
22
+ ```
23
+
24
+ **設計重點**:pack 不直接碰 provider、TUI 內部、session 檔——只透過 KernelServices。
25
+ 這讓 pack 可在 fake kernel 下單測(xitto-code 已用這套依賴注入風格,見 `createAgent(deps)`)。
26
+
27
+ ## B. kernel 怎麼消費 pack(接線點)
28
+
29
+ kernel 啟動時把 pack 的欄位接到既有插槽。對照 xitto-code 現況:
30
+
31
+ | pack 欄位 | kernel 接到哪 | xitto-code 對應現況 |
32
+ |------|------|------|
33
+ | `tools()` | 工具註冊表 | `agent-factory.js buildTools()` 目前寫死 `createCodingTools` |
34
+ | `systemPrompt` | system prompt 組裝 | `index.js` 的 `base.prompt` |
35
+ | `contextFiles` | 專案規範載入 | `context.js` 目前寫死 CLAUDE.md/AGENTS.md/XITTO.md |
36
+ | `mutatingTools` | plan 守衛 + 熔斷 | `agent-factory.js` 的 `MUTATING`/`HEAVY_TOOLS` 常數 |
37
+ | `preToolPolicy` | `beforeToolCall` 守衛鏈 | `agent-factory.js` 寫死的 read-before-edit |
38
+ | `verify` | 回合收尾 | `index.js runAutoVerify()` + `detectVerifyCmd` |
39
+ | `permissionPolicy` | 權限預設 | `settings.js` 的預設值 |
40
+
41
+ ## C. beforeToolCall 守衛鏈(kernel 固定順序,pack 只插一格)
42
+
43
+ kernel 維持這個固定順序(安全性靠順序保證),pack 只能在第 3 格插領域守衛:
44
+
45
+ ```
46
+ 1. planMode && pack.mutatingTools.has(name) → 擋(kernel)
47
+ 2. 上下文熔斷(kernel)
48
+ 3. pack.preToolPolicy?.check(ctx, services) → 領域守衛(PACK 的唯一插槽)
49
+ 4. PreToolUse hooks(kernel,使用者 settings.json)
50
+ 5. permissionHook(kernel):deny → 沙箱違規 → 危險命令 → 確認/白名單
51
+ 6. 通過 → 執行;sandboxable 工具且沙箱開 → Seatbelt 包裹(kernel)
52
+ ```
53
+
54
+ > pack **不能**重排或跳過 4/5/6——權限、沙箱、危險命令偵測是 kernel 的不可繞過保證。
55
+ > pack 只能「額外擋更多」(第 3 格),不能「放行 kernel 想擋的」。這是安全模型的核心約束。
56
+
57
+ ## D. 工具 metadata 驅動(取代寫死名單)
58
+
59
+ kernel 不再有 `MUTATING = {'write','edit','bash'}` 這種領域常數,改成讀工具自帶的 metadata:
60
+
61
+ ```js
62
+ // kernel 內部(領域無關)
63
+ const isMutating = (t) => t.mutating === true;
64
+ const isReadOnly = (t) => t.readOnly === true;
65
+ const canSandbox = (t) => t.sandboxable === true;
66
+
67
+ // mutatingTools:pack 顯式給就用,否則從工具 metadata 推導
68
+ const mutating = pack.mutatingTools ?? pack.tools().filter(isMutating).map(t => t.name);
69
+ ```
70
+
71
+ 好處:加新工具時,「它會不會改動 / 該不該沙箱」由工具自己宣告,kernel 與其他 pack 都不用改。
72
+
73
+ ## E. 不變式(kernel 對任何 pack 的保證)
74
+
75
+ 1. **權限不可繞過**:唯讀工具自動放行;其餘一律過 permissionHook。pack 無法關掉這層。
76
+ 2. **沙箱由 kernel 套用**:pack 宣告 `sandboxable` 工具,是否真包裹由 kernel + 使用者設定決定。
77
+ 3. **記憶/壓縮/session 自動運作**:pack 不需關心上下文會不會爆、怎麼存檔。
78
+ 4. **provider 無關**:pack 不寫任何 provider 專屬程式碼;換模型不影響 pack。
79
+ 5. **可中斷**:任何工具執行中 Esc/Ctrl+C 都能中止(kernel 的 abort 機制)。
@@ -0,0 +1,70 @@
1
+ # 04 · 從 xitto-code 抽離的具體步驟
2
+
3
+ 目標:把 xitto-code **就地**重構成 `kernel + coding pack`,行為不變、測試全綠,之後新增第二個 pack 即驗證底座成立。
4
+ 這份是「每個耦合點怎麼搬」的清單,依風險由低到高。
5
+
6
+ ## 耦合點總表(掃描 src/ 得出)
7
+
8
+ | # | 耦合點 | 現況位置 | 抽法 | 風險 |
9
+ |---|------|------|------|:---:|
10
+ | 1 | 工具 metadata | 各工具定義 | 幫工具加 `mutating/readOnly/sandboxable` 欄位 | 低 |
11
+ | 2 | `MUTATING`/`HEAVY_TOOLS` 常數 | `agent-factory.js:24,27` | 改讀工具 metadata + `pack.mutatingTools` | 低 |
12
+ | 3 | 工具來源 | `agent-factory.js:60` `subagent.js` | `buildTools` 收 `pack.tools()` 而非寫死 `createCodingTools` | 中 |
13
+ | 4 | read-before-edit | `agent-factory.js:158` | 搬進 `codingPack.preToolPolicy` | 中 |
14
+ | 5 | autoVerify(lint/型別) | `index.js:266,549` `util.detectVerifyCmd` | 搬進 `codingPack.verify` | 中 |
15
+ | 6 | git 整合 | `index.js` `git.js` | 成為 coding pack 的工具/指令;kernel 不認識 git | 中 |
16
+ | 7 | contextFiles 檔名 | `context.js` | `pack.contextFiles` 參數化 | 低 |
17
+ | 8 | 沙箱預設/偏 bash | `sandbox.js` `settings.js` | 機制留 kernel;預設改由 `pack.permissionPolicy` | 低 |
18
+ | 9 | 編排器肥大 | `index.js`(~800 行)| 拆出 pack 載入 + kernel 啟動,編排器變薄 | 高 |
19
+
20
+ ## 逐項做法
21
+
22
+ ### 1–2 · 工具 metadata 驅動(先做,零行為變化)
23
+ - 在現有工具定義補 `mutating/readOnly/sandboxable`(如 `write/edit/bash → mutating:true`、`bash → sandboxable:true`、`read/grep/ls → readOnly:true`)。
24
+ - `agent-factory.js` 把 `MUTATING.has(name)` 改成查 metadata;`READ_ONLY`(permissions.js)同理。
25
+ - **驗證**:行為與測試完全不變(只是把名單來源換成 metadata)。
26
+
27
+ ### 3 · 工具來源參數化
28
+ - `buildTools({cwd, pack, ...})`:用 `pack.tools()` 取代 `createCodingTools(cwd)`。
29
+ - `subagent.js` 的子 agent 工具集同樣改由 pack 提供(子 agent 用「同 pack 的唯讀子集」)。
30
+ - coding pack 的 `tools()` 內部仍呼叫 `createCodingTools` —— 只是包裝進 pack,**行為不變**。
31
+
32
+ ### 4 · read-before-edit → pack.preToolPolicy
33
+ - 把 `agent-factory.js` 那段「edit/write 已存在未讀 → 擋」搬成 `codingPack.preToolPolicy.check`。
34
+ - kernel 的守衛鏈第 3 格呼叫它。kernel 從此不認識「edit」「檔案」。
35
+ - `readFiles` 這個已讀集合:改放進 KernelServices 或 pack 的閉包狀態。
36
+
37
+ ### 5 · autoVerify → pack.verify
38
+ - `detectVerifyCmd`(找 tsc/eslint)+ `runAutoVerify` 的回灌循環:
39
+ - 回灌**機制**(最多 N 輪、失敗餵回模型)留 kernel(通用)。
40
+ - 「跑什麼指令、何時跑」變成 `codingPack.verify.run / shouldRun`。
41
+
42
+ ### 6 · git → coding pack 的能力
43
+ - `/diff` `/commit` `/push` 與 `git.js`:成為 coding pack 提供的指令與工具。
44
+ - kernel 只提供「pack 可註冊自訂指令」的機制(已有 `commands.js`),git 變成 coding pack 註冊的東西。
45
+ - 狀態列的 git 分支顯示:改成 pack 可選提供的「狀態列貢獻者」。
46
+
47
+ ### 7 · contextFiles
48
+ - `context.js` 的 `['XITTO.md','CLAUDE.md','AGENTS.md','.xitto-code.md']` 改成 `pack.contextFiles`。
49
+
50
+ ### 8 · 沙箱
51
+ - `sandbox.js`/`permissions.js` 機制不動(已是領域無關)。
52
+ - 只把「預設啟用與否、allowWritePrefixes」改成可由 `pack.permissionPolicy` 提供預設,使用者 `settings.json` 仍可覆蓋。
53
+
54
+ ### 9 · 編排器瘦身(最後做、風險最高)
55
+ - `index.js` 拆成:`loadPack()` → `createKernel(pack, config)` → `mountTui()`。
56
+ - verify/git/read-before-edit 都已搬走後,`index.js` 只剩「載 pack、起 kernel、接 TUI 事件」。
57
+
58
+ ## 抽離順序(建議)
59
+
60
+ ```
61
+ 第一波(零行為變化、低風險):1 → 2 → 7 → 8
62
+ 第二波(搬邏輯、靠測試守): 3 → 4 → 5 → 6
63
+ 第三波(結構重整): 9
64
+ 驗收:新增一個非編碼 pack(見 05),同一 kernel 跑起來 → 底座成立
65
+ ```
66
+
67
+ ## 怎麼證明抽對了
68
+
69
+ - **回歸**:每一波後 xitto-code 的 287 測試必須全綠(coding pack 行為 == 原本)。
70
+ - **正交**:新增第二、三個 pack(data-query / notes)**不需要改 kernel 任何一行**。做得到 → 抽象成功;做不到 → 哪裡還有領域洩漏到 kernel,補抽。(已驗證:見 `src/packs/`)
@@ -0,0 +1,95 @@
1
+ # 05 · 範例 Pack(驗證同介面能跑不同領域)
2
+
3
+ 不同 pack 用**同一個介面**填不同領域,證明 kernel 不需為任何領域改動。
4
+ > 已實作可跑的 pack 在 [`src/packs/`](../src/packs/):**coding**、**data-query**、**notes**(見下 A、B 與 [docs/06](06-authoring-a-pack.md))。
5
+ > 本文另含一個 **ops / 客服** 領域作為「介面能延伸到哪」的示意(未內建,純說明)。
6
+ > 重點都一樣:同樣六個插槽,內容換了,kernel 沒換。
7
+
8
+ ## A. coding pack(參考實作 == 現在的 xitto-code)
9
+
10
+ ```js
11
+ export const codingPack = {
12
+ name: 'coding',
13
+ tools: () => [...createCodingTools(cwd), grep, find, ls, glob, readImage, ...bgTools],
14
+ systemPrompt: '你是嚴謹的編碼 agent…(行為準則)',
15
+ contextFiles: ['CLAUDE.md', 'AGENTS.md', 'XITTO.md', '.xitto-code.md'],
16
+ // mutatingTools 省略 → 從工具 metadata 自動推導(write/edit/bash)
17
+ verify: {
18
+ shouldRun: (ctx) => ctx.turnModified,
19
+ run: async () => runShell(detectVerifyCmd(cwd)), // tsc / eslint
20
+ maxRounds: 2,
21
+ },
22
+ preToolPolicy: {
23
+ check: (ctx) => { // read-before-edit
24
+ if ((ctx.name === 'edit' || ctx.name === 'write') && exists(ctx.path) && !read.has(ctx.path))
25
+ return { block: true, reason: `請先 read ${ctx.path} 再編輯` };
26
+ },
27
+ },
28
+ permissionPolicy: { sandbox: { enabled: false }, defaultMode: 'default' },
29
+ };
30
+ ```
31
+
32
+ ## B. data-query pack(資料查詢 / 分析)
33
+
34
+ ```js
35
+ export const dataQueryPack = {
36
+ name: 'data-query',
37
+ tools: () => [sqlQuery, listTables, describeTable, chartRender, exportCsv],
38
+ systemPrompt: '你是資料分析 agent。先看 schema 再查;破壞性 SQL 一律先確認…',
39
+ contextFiles: ['SCHEMA.md', 'METRICS.md'], // 專案的表結構/指標定義
40
+ mutatingTools: ['sqlExec'], // 只有寫入型 SQL 算 mutating;查詢唯讀
41
+ // verify 省略 → 查詢沒有「自我驗收」概念
42
+ preToolPolicy: {
43
+ check: (ctx) => { // schema-before-query(對照 read-before-edit)
44
+ if (ctx.name === 'sqlQuery' && !ctx.session.schemaLoaded)
45
+ return { block: true, reason: '請先用 listTables/describeTable 了解結構再下查詢' };
46
+ },
47
+ },
48
+ permissionPolicy: { deny: ['bash:DROP', 'bash:TRUNCATE'], defaultMode: 'default' },
49
+ };
50
+ ```
51
+
52
+ 對照:`schema-before-query` 之於資料查詢,正如 `read-before-edit` 之於編碼——**同一個 preToolPolicy 插槽**。
53
+
54
+ ## C. ops / customer-support pack(維運 / 客服工單)— 示意(未內建)
55
+
56
+ > 這個沒有內建實作,純粹展示「同介面能延伸到動錢/對外的高風險領域」。
57
+ > 實際內建的第三個 pack 是 **notes**(知識庫),見 [docs/06](06-authoring-a-pack.md) 與 `src/packs/notes/`。
58
+
59
+ ```js
60
+ export const opsPack = {
61
+ name: 'ops',
62
+ tools: () => [searchTickets, getOrder, issueRefund, postReply, runRunbook],
63
+ systemPrompt: '你是客服維運 agent。退款前必須先查到訂單;對外回覆前先給人審核…',
64
+ contextFiles: ['RUNBOOK.md', 'POLICY.md'],
65
+ mutatingTools: ['issueRefund', 'postReply'], // 對外/動錢的算 mutating
66
+ preToolPolicy: {
67
+ check: (ctx) => { // 退款前須先查到訂單
68
+ if (ctx.name === 'issueRefund' && !ctx.session.orderVerified)
69
+ return { block: true, reason: '退款前請先用 getOrder 確認訂單存在與金額' };
70
+ },
71
+ },
72
+ permissionPolicy: { defaultMode: 'default' }, // issueRefund/postReply 非唯讀 → 一律走確認
73
+ };
74
+ ```
75
+
76
+ ## 同介面對照表
77
+
78
+ | 插槽 | coding | data-query | ops |
79
+ |------|------|------|------|
80
+ | `tools` | read/write/edit/bash | sql/chart/export | tickets/order/refund |
81
+ | `systemPrompt` | 編碼準則 | 分析準則 | 客服準則 |
82
+ | `contextFiles` | CLAUDE.md | SCHEMA.md | RUNBOOK.md |
83
+ | `mutatingTools` | write/edit/bash | sqlExec | refund/reply |
84
+ | `verify` | lint/型別 | —(無)| —(或 SLA 檢查)|
85
+ | `preToolPolicy` | read-before-edit | schema-before-query | order-before-refund |
86
+ | `permissionPolicy` | 沙箱可選 | deny 破壞性 SQL | 對外動作走確認 |
87
+
88
+ > **三個領域、同六個插槽、kernel 一行未改。** 這張表就是「xitto-code 能否當底座」的答案:
89
+ > 能——因為領域差異恰好能被這組插槽吸收,沒有任何一格需要 kernel 認識具體領域。
90
+
91
+ ## 反例:什麼情況代表「抽象失敗」
92
+
93
+ 若某個新領域需要 kernel **本身**改動才能支援(例如改 agent loop、改權限鏈順序、在 kernel 裡寫 `if (domain === 'x')`),
94
+ 就代表有領域知識洩漏進 kernel,該把那塊再抽成新的 pack 插槽或 KernelServices 能力。
95
+ **判準:新領域 = 只新增一個 pack 檔,kernel/其他 pack 零改動。**