@geekbeer/minion 4.2.0 → 4.3.3
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/lib/bundled-skills.js +94 -0
- package/core/stores/chat-store.js +63 -31
- package/docs/api-reference.md +117 -7
- package/docs/task-guides.md +19 -0
- package/linux/routes/chat.js +61 -26
- package/linux/server.js +24 -0
- package/package.json +1 -1
- package/roles/accountant.md +12 -3
- package/rules/core.md +62 -0
- package/skills/accounting-bookkeeping/SKILL.md +94 -21
- package/skills/accounting-bookkeeping/references/api-counterparties.md +24 -40
- package/skills/accounting-bookkeeping/references/api-expense-reimbursement.md +47 -134
- package/skills/accounting-bookkeeping/references/api-journal-entries.md +29 -12
- package/skills/accounting-bookkeeping/references/api-receipts.md +75 -0
- package/skills/accounting-bookkeeping/references/api-reports-tax.md +106 -0
- package/skills/accounting-bookkeeping/references/troubleshooting.md +42 -25
- package/win/routes/chat.js +80 -35
- package/win/server.js +24 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# API: 仕訳(Journal Entries)
|
|
2
2
|
|
|
3
|
-
複式簿記の仕訳エンドポイント。立替経費以外の通常取引(
|
|
3
|
+
複式簿記の仕訳エンドポイント。立替経費以外の通常取引(収入/支出/振替/手動仕訳)を扱う。
|
|
4
|
+
|
|
5
|
+
> パスはすべて **ミニオン用** `$HQ_URL/api/minion/workspaces/:id/accounting/` を基準とした相対表記。
|
|
6
|
+
> 認証は `Authorization: Bearer $API_TOKEN`。人間用 `/api/accounting/*` は使わない。
|
|
7
|
+
> ミニオンが作成した仕訳は **常に `source='ai_generated'`** で記録される。
|
|
4
8
|
|
|
5
9
|
## entry_type の区別
|
|
6
10
|
|
|
@@ -10,11 +14,11 @@
|
|
|
10
14
|
| `expense` | 支出 | (借)category(費用) / (貸)wallet |
|
|
11
15
|
| `transfer` | 振替 | (借)to_account / (貸)from_account |
|
|
12
16
|
| `manual` | 手動仕訳(複合仕訳) | lines を直接指定 |
|
|
13
|
-
| `opening_balance` | 期首残高 |
|
|
17
|
+
| `opening_balance` | 期首残高 | **人間専用**(ミニオンからは設定不可) |
|
|
14
18
|
|
|
15
19
|
## エンドポイント
|
|
16
20
|
|
|
17
|
-
### POST /
|
|
21
|
+
### POST /entries
|
|
18
22
|
|
|
19
23
|
**シンプル仕訳 (income/expense):**
|
|
20
24
|
```
|
|
@@ -72,37 +76,50 @@ Response: { "entry": {...} }
|
|
|
72
76
|
400 manual entry requires at least 2 lines — lines 配列が短い
|
|
73
77
|
400 line[N]: exactly one of debit/credit must be positive — 行レベル不整合
|
|
74
78
|
400 指定された勘定科目がこの帳簿に存在しません — account_id が別 book / 削除済
|
|
75
|
-
409 period_closed
|
|
79
|
+
409 code: 'period_closed' — entry_date が締め済期間内
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
### PATCH /
|
|
82
|
+
### PATCH /entries/:entryId
|
|
79
83
|
|
|
80
84
|
仕訳の編集。明細は全置換(既存lineを削除して body.lines を挿入)。
|
|
81
85
|
|
|
86
|
+
**`source='ai_generated'` の仕訳(=ミニオンが作成したもの)のみ編集可能。**
|
|
87
|
+
人間作成仕訳 (`source='manual'` / `'import'`) を PATCH すると **409 `code: 'human_owned_entry'`**。
|
|
88
|
+
修正が必要なら `POST /api/threads` で人間に依頼すること。
|
|
89
|
+
|
|
82
90
|
```
|
|
83
91
|
締めチェック:
|
|
84
|
-
- 元の entry_date が締め後 → 409 period_closed (編集不可)
|
|
92
|
+
- 元の entry_date が締め後 → 409 code: 'period_closed' (編集不可)
|
|
85
93
|
- 新しい entry_date が締め後 → 409 (日付変更不可)
|
|
86
|
-
- opening_balance は編集不可(
|
|
94
|
+
- opening_balance は編集不可(人間専用画面から再入力)
|
|
87
95
|
```
|
|
88
96
|
|
|
89
|
-
###
|
|
97
|
+
### 仕訳の削除 — **人間専用**
|
|
90
98
|
|
|
91
|
-
|
|
99
|
+
`DELETE` はミニオンには提供されない。誤記帳の取消が必要な場合は、可能なら PATCH で訂正するか、
|
|
100
|
+
`POST /api/threads` で `@user` メンションして HQ UI からの削除を依頼する。
|
|
92
101
|
|
|
93
|
-
### GET /
|
|
102
|
+
### GET /entries
|
|
94
103
|
|
|
95
104
|
```
|
|
96
105
|
Query:
|
|
97
106
|
?from=YYYY-MM-DD&to=YYYY-MM-DD
|
|
98
|
-
?
|
|
107
|
+
?entry_type=income,expense,... (カンマ区切り)
|
|
108
|
+
?limit=100&offset=0
|
|
99
109
|
|
|
100
110
|
Response: { "entries": [{ ..., lines: [...] }] }
|
|
101
111
|
```
|
|
102
112
|
|
|
113
|
+
### GET /entries/:entryId
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
Response: { "entry": { ..., lines: [...] } }
|
|
117
|
+
```
|
|
118
|
+
|
|
103
119
|
## ベストプラクティス
|
|
104
120
|
|
|
105
121
|
- **通常の経費は manual で複合仕訳を組まず、`expense` ですませる。** シンプルな分、間違いが少ない
|
|
106
|
-
- **立替経費は仕訳APIで組み立てず、必ず /
|
|
122
|
+
- **立替経費は仕訳APIで組み立てず、必ず /reimbursements を使う。** 自前で組むと「立替者」軸の集計ができない
|
|
107
123
|
- **dimensions の `net_asset_restriction`** は一般社団法人プロファイル時のみ意味あり。株式会社(corporation-jp)では空でOK
|
|
108
124
|
- **manual を使う判断基準**: 1取引イベントで3科目以上を同時に動かす必要があるとき (給与/源泉/振込のセット、1レシートを複数費用科目に按分 等)。2科目で済むなら income/expense/transfer のいずれかに当てはまる
|
|
125
|
+
- **作成した仕訳は人間がレビューしてから承認する前提。** ミニオン作成分は HQ UI で 🤖 バッジ付き表示される
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# API: レシート(Receipts)
|
|
2
|
+
|
|
3
|
+
レシート(領収書)画像/PDF のメタデータ照会とダウンロード。OCR や内容確認の入力に使う。
|
|
4
|
+
|
|
5
|
+
> パスはすべて **ミニオン用** `$HQ_URL/api/minion/workspaces/:id/accounting/` を基準とした相対表記。
|
|
6
|
+
> 認証は `Authorization: Bearer $API_TOKEN`。**accountant ロール必須**(v4.3.0〜)。
|
|
7
|
+
|
|
8
|
+
## ミニオンが扱える操作
|
|
9
|
+
|
|
10
|
+
照会 (一覧・メタデータ) と **ダウンロード用 signed URL の取得** のみ。
|
|
11
|
+
アップロードと仕訳への attach/detach は **人間専用**(後述)。
|
|
12
|
+
|
|
13
|
+
## エンドポイント
|
|
14
|
+
|
|
15
|
+
### GET /receipts
|
|
16
|
+
|
|
17
|
+
レシート一覧。
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Query:
|
|
21
|
+
?attached=true|false|all (default: false = 未添付のみ)
|
|
22
|
+
?limit=100 (max 500)
|
|
23
|
+
|
|
24
|
+
Response:
|
|
25
|
+
{ "receipts": [{ id, file_name, mime_type, uploaded_at, attached_entry_id, ... }] }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `attached=false` … まだ仕訳に紐付いていないレシート(OCR → 記帳の対象を探すときに使う)
|
|
29
|
+
- `attached=all` … 添付済み含む全件
|
|
30
|
+
|
|
31
|
+
### GET /receipts/:receiptId
|
|
32
|
+
|
|
33
|
+
単一レシートのメタデータ。
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Response: { "receipt": { id, file_name, mime_type, uploaded_at, attached_entry_id, ... } }
|
|
37
|
+
404 not_found — ID 違い / 別ワークスペース
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### GET /receipts/:receiptId/download
|
|
41
|
+
|
|
42
|
+
**60秒だけ有効な signed URL** を返す。画像/PDF の実体取得や OCR への入力に使う。
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Response:
|
|
46
|
+
{
|
|
47
|
+
"url": "https://...", // 60秒で失効
|
|
48
|
+
"mime_type": "image/jpeg",
|
|
49
|
+
"file_name": "receipt-2026-05.jpg",
|
|
50
|
+
"expires_in_seconds": 60
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
URL は短命なので、取得したら即座に fetch すること。失効したら再度このエンドポイントを叩く。
|
|
55
|
+
|
|
56
|
+
対応 MIME: `image/png` / `image/jpeg` / `image/webp` / `image/gif` / `image/heic` / `image/heif` / `application/pdf`。
|
|
57
|
+
|
|
58
|
+
## 典型フロー (OCR → 記帳)
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
1. GET /receipts?attached=false で未処理レシートを一覧
|
|
62
|
+
2. GET /receipts/:id/download で signed URL を取得し、画像/PDF を読み取り (OCR)
|
|
63
|
+
3. 読み取った内容から POST /entries または POST /reimbursements で仕訳を作成
|
|
64
|
+
4. レシートと仕訳の attach は人間が HQ UI で行う (下記)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 人間専用操作 (ミニオンから提供されない)
|
|
68
|
+
|
|
69
|
+
| 操作 | 説明 / 依頼先 |
|
|
70
|
+
|------|--------------|
|
|
71
|
+
| レシートのアップロード | HQ UI からユーザーがアップロード |
|
|
72
|
+
| 仕訳との attach / detach (`PATCH`) | HQ UI で人間が判断・操作 |
|
|
73
|
+
|
|
74
|
+
ミニオンが OCR → 仕訳作成した後、「この仕訳にこのレシートを添付してほしい」場合は
|
|
75
|
+
`POST /api/threads` で `@user` メンションして依頼する(レシート ID と仕訳 ID をリンク)。
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# API: 財務諸表(Reports)・法人税計算(Tax Calculations)
|
|
2
|
+
|
|
3
|
+
> パスはすべて **ミニオン用** `$HQ_URL/api/minion/workspaces/:id/accounting/` を基準とした相対表記。
|
|
4
|
+
> 認証は `Authorization: Bearer $API_TOKEN`。
|
|
5
|
+
|
|
6
|
+
## 財務諸表(Reports)
|
|
7
|
+
|
|
8
|
+
すべて Read 専用。PM・ユーザーへの月次/期次報告で使う。取得した数値は
|
|
9
|
+
**ノート (`hq note create <project_id> --title ... --content ...`)** にまとめると HQ UI で共有・検索できる。
|
|
10
|
+
|
|
11
|
+
### GET /reports/trial-balance
|
|
12
|
+
|
|
13
|
+
試算表 + B/S + P/L を一括で返す(最も情報量が多い)。
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Query:
|
|
17
|
+
?from=YYYY-MM-DD&to=YYYY-MM-DD (任意。期間絞り込み)
|
|
18
|
+
?includeZero=true|false (残高ゼロ科目を含めるか。default false)
|
|
19
|
+
|
|
20
|
+
Response (概略):
|
|
21
|
+
{
|
|
22
|
+
"trialBalance": [{ account_id, code, name, type, debit, credit, balance }],
|
|
23
|
+
"balanceSheet": {...},
|
|
24
|
+
"incomeStatement": {...},
|
|
25
|
+
"netAssetBalanceSheet": {...} // 一般社団法人プロファイルのときのみ含まれる(正味財産区分付き B/S)
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### GET /reports/income-statement
|
|
30
|
+
|
|
31
|
+
P/L のみ(軽量)。当期の収益・費用・当期純利益を返す。
|
|
32
|
+
|
|
33
|
+
### GET /reports/balance-sheet
|
|
34
|
+
|
|
35
|
+
B/S のみ(軽量)。資産・負債・純資産の残高を返す。
|
|
36
|
+
|
|
37
|
+
## 法人税計算(Tax Calculations)ウィザード
|
|
38
|
+
|
|
39
|
+
**対象は `org_type=corporation` または `general_incorporated_association` の帳簿のみ**。
|
|
40
|
+
それ以外は 403 `code: 'org_type_ineligible'`。
|
|
41
|
+
|
|
42
|
+
ミニオンが触れるのは **`draft` 状態の計算だけ**。`finalized` / `journalized` 状態の計算を
|
|
43
|
+
更新しようとすると 409 `code: 'invalid_status'`。**finalize / journalize / reopen は人間専用**。
|
|
44
|
+
|
|
45
|
+
> 税率はツール側にテーブルを持たない。国税庁・自治体サイトで**最新の値をミニオン自身が調査**して入力する。
|
|
46
|
+
> 税率の確定は法的責任を伴うため、最終確定(finalize)は必ず人間が行う。
|
|
47
|
+
|
|
48
|
+
### エンドポイント
|
|
49
|
+
|
|
50
|
+
| Method | Path | 説明 |
|
|
51
|
+
|--------|------|------|
|
|
52
|
+
| GET | `/tax-calculations` | 計算一覧。Query: `?fiscal_period_id=<uuid>&status=draft,finalized,journalized` |
|
|
53
|
+
| GET | `/tax-calculations/:calcId` | 詳細 `{ calculation, adjustments, components }` |
|
|
54
|
+
| POST | `/tax-calculations` | 新規作成。Body: `{ fiscal_period_id, notes? }`。6税目を seed |
|
|
55
|
+
| PATCH | `/tax-calculations/:calcId` | `pretax_income` / `notes` 更新 |
|
|
56
|
+
| GET | `/tax-calculations/:calcId/pretax-income` | P/L から税引前当期純利益を再計算(法人税科目は除外) |
|
|
57
|
+
| GET | `/tax-calculations/:calcId/ensure-accounts` | 必要4科目(法人税費用/未払/仮払/未収還付)の存在確認 |
|
|
58
|
+
| POST | `/tax-calculations/:calcId/ensure-accounts` | 不足4科目をテンプレから自動追加 |
|
|
59
|
+
| POST | `/tax-calculations/:calcId/adjustments` | 別表四相当の加算/減算追加(下記) |
|
|
60
|
+
| PATCH | `/tax-calculations/:calcId/adjustments/:adjId` | 加算/減算更新 |
|
|
61
|
+
| DELETE | `/tax-calculations/:calcId/adjustments/:adjId` | 加算/減算削除 |
|
|
62
|
+
| PATCH | `/tax-calculations/:calcId/components/:compId` | 税目別の税率・中間納付・(均等割のみ)直接税額を更新 |
|
|
63
|
+
|
|
64
|
+
#### adjustments の Body
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
{
|
|
68
|
+
"adjustment_type": "addition" | "deduction",
|
|
69
|
+
"category": "entertainment_excess" | "depreciation_excess" | "executive_salary_excess"
|
|
70
|
+
| "reserve_addition" | "dividend_excluded" | "tax_refund_excluded" | "custom",
|
|
71
|
+
"amount": 100000,
|
|
72
|
+
"description": "交際費損金不算入", // 任意
|
|
73
|
+
"sort_order": 1 // 任意
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### components (税目) の更新 Body
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
{
|
|
81
|
+
"tax_rate": 23.2, // % 表記 (例: 23.2)
|
|
82
|
+
"prepaid_amount": 50000, // 中間納付額 (任意)
|
|
83
|
+
"calculated_amount": 70000 // tax_type='inhabitant_per_capita' (均等割) のときのみ受理
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 典型ワークフロー
|
|
88
|
+
|
|
89
|
+
1. `GET /periods` で対象期(status='open')の id を選ぶ
|
|
90
|
+
2. `POST /tax-calculations` で新規作成(pretax_income は P/L から自動取得)
|
|
91
|
+
3. `POST /tax-calculations/:calcId/ensure-accounts` で必要4科目を揃える
|
|
92
|
+
4. 国税庁・自治体サイトで最新税率を調査 → `PATCH /components/:compId` で各税目の `tax_rate` を入力
|
|
93
|
+
5. 中間納付があれば `PATCH /components/:compId` で `prepaid_amount` を入力
|
|
94
|
+
6. 必要なら `POST /adjustments` で別表四相当の加減算を追加(調査・税理士相談の上)
|
|
95
|
+
7. `POST /api/threads` で `@user` メンションして finalize → journalize を依頼
|
|
96
|
+
|
|
97
|
+
依頼スレッドの例:
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"thread_type": "help",
|
|
101
|
+
"mentions": ["user"],
|
|
102
|
+
"title": "法人税計算 FY3 の finalize お願いします",
|
|
103
|
+
"content": "計算ID xxxx の入力が完了しました。HQダッシュボード /accounting/tax-calculations/xxxx でレビュー後、Finalize → Journalize ボタンを押してください。",
|
|
104
|
+
"context": { "accounting_action": "tax_finalize", "calculation_id": "xxxx" }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
@@ -2,19 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
会計APIで遭遇しやすいエラーパターンと対処。
|
|
4
4
|
|
|
5
|
+
> パスはすべて **ミニオン用** `$HQ_URL/api/minion/workspaces/:id/accounting/` を基準とした相対表記。
|
|
6
|
+
|
|
5
7
|
## エラーレスポンスの構造
|
|
6
8
|
|
|
7
9
|
```json
|
|
8
10
|
{
|
|
9
11
|
"error": "missing_default_payable_account",
|
|
10
12
|
"message": "取引先「山田太郎」に立替用の貸方科目が未設定です",
|
|
11
|
-
"next_action": "PATCH /
|
|
13
|
+
"next_action": "PATCH /counterparties/<id> で default_payable_account_id を設定してください",
|
|
12
14
|
"context": { "counterparty_id": "..." }
|
|
13
15
|
}
|
|
14
16
|
```
|
|
15
17
|
|
|
16
18
|
**必ず `next_action` を読み、その指示に従うこと。** 指示が曖昧な場合のみ自己判断する。
|
|
17
19
|
|
|
20
|
+
## アクセス・認証まわり
|
|
21
|
+
|
|
22
|
+
### `accountant_role_required` (403)
|
|
23
|
+
|
|
24
|
+
**原因:** 呼び出しミニオンが workspace 内のどのプロジェクトでも accountant ロールを持っていない。
|
|
25
|
+
|
|
26
|
+
**対処:** 自己解決不可。`POST /api/threads` で `@user` メンションし、accountant ロール付与を依頼する。
|
|
27
|
+
|
|
28
|
+
### `feature_disabled` (403)
|
|
29
|
+
|
|
30
|
+
**原因:** workspace で `experimental_accounting` フラグが OFF。
|
|
31
|
+
|
|
32
|
+
**対処:** 自己解決不可。人間にフラグ有効化を依頼する。
|
|
33
|
+
|
|
18
34
|
## 立替経費まわり
|
|
19
35
|
|
|
20
36
|
### `missing_default_payable_account` (422)
|
|
@@ -23,9 +39,9 @@
|
|
|
23
39
|
|
|
24
40
|
**対処:**
|
|
25
41
|
1. liability 型の科目「役員借入金」「従業員未払金」などがあるか確認
|
|
26
|
-
- `GET /
|
|
27
|
-
2. 無ければ作成: `POST /
|
|
28
|
-
3. counterparty に紐付け: `PATCH /
|
|
42
|
+
- `GET /accounts?type=liability`
|
|
43
|
+
2. 無ければ作成: `POST /accounts` で `{ name: "役員借入金", type: "liability" }`
|
|
44
|
+
3. counterparty に紐付け: `PATCH /counterparties/<id>` で `{ default_payable_account_id: "..." }`
|
|
29
45
|
4. 立替記帳をリトライ
|
|
30
46
|
|
|
31
47
|
### `counterparty_not_found` (404)
|
|
@@ -33,8 +49,8 @@
|
|
|
33
49
|
**原因:** 立替者の counterparty が未登録。
|
|
34
50
|
|
|
35
51
|
**対処:**
|
|
36
|
-
1. `GET /
|
|
37
|
-
2. 該当者がいなければ `POST /
|
|
52
|
+
1. `GET /counterparties?kind=director,employee` で typo の可能性を確認
|
|
53
|
+
2. 該当者がいなければ `POST /counterparties` で作成
|
|
38
54
|
- 役員なら `kind: "director"`、従業員なら `kind: "employee"`
|
|
39
55
|
- 可能なら `default_payable_account_id` も同時に設定
|
|
40
56
|
|
|
@@ -47,12 +63,6 @@
|
|
|
47
63
|
- 同一人物なら既存IDを使う
|
|
48
64
|
- 別人なら名前を変える(例: "山田太郎(役員)" vs "山田太郎(従業員)")
|
|
49
65
|
|
|
50
|
-
### `already_settled` (409)
|
|
51
|
-
|
|
52
|
-
**原因:** 精算しようとした立替が既に精算済み。
|
|
53
|
-
|
|
54
|
-
**対処:** `context.settled_at` と `context.settlement_journal_entry_id` を確認し、二重精算を回避
|
|
55
|
-
|
|
56
66
|
## 期間まわり
|
|
57
67
|
|
|
58
68
|
### `period_closed` (409)
|
|
@@ -60,7 +70,7 @@
|
|
|
60
70
|
**原因:** 指定日付が締め済期間内。
|
|
61
71
|
|
|
62
72
|
**対処:**
|
|
63
|
-
- 締めはPMの権限。勝手に再オープンしない
|
|
73
|
+
- 締めはPMの権限。勝手に再オープンしない(再オープンは人間専用)
|
|
64
74
|
- 締め後の取引なら日付を調整(後日付に変更)、または PM にエスカレーション
|
|
65
75
|
- threadで `mentions: ["role:pm"]` を付けて「<DATE> の取引が締め後だが記帳が必要」と相談
|
|
66
76
|
|
|
@@ -70,13 +80,7 @@
|
|
|
70
80
|
|
|
71
81
|
**原因:** 立替経費の費用科目に expense 型でない科目を指定した。
|
|
72
82
|
|
|
73
|
-
**対処:** `GET /
|
|
74
|
-
|
|
75
|
-
### `not_a_wallet_account` (400)
|
|
76
|
-
|
|
77
|
-
**原因:** 精算時の支払口座に `is_wallet=false` の科目を指定した。
|
|
78
|
-
|
|
79
|
-
**対処:** `is_wallet=true` の科目(現金/普通預金)を選択
|
|
83
|
+
**対処:** `GET /accounts?type=expense` の科目を取得して使用
|
|
80
84
|
|
|
81
85
|
### `invalid_default_payable_account` (400)
|
|
82
86
|
|
|
@@ -84,6 +88,11 @@
|
|
|
84
88
|
|
|
85
89
|
**対処:** liability 型(または equity 型)の科目を指定する。「立替金」(asset)は逆向きなので使わない
|
|
86
90
|
|
|
91
|
+
### 科目の rename / アーカイブが必要なとき — **人間専用**
|
|
92
|
+
|
|
93
|
+
ミニオンは `POST /accounts`(作成)のみ可能。誤って作った科目の修正・アーカイブ・削除はできない。
|
|
94
|
+
`POST /api/threads` で `@user` メンションして HQ UI `/accounting/accounts` での操作を依頼する。
|
|
95
|
+
|
|
87
96
|
## 仕訳整合性
|
|
88
97
|
|
|
89
98
|
### `at least 2 lines required` / `debit/credit must balance`
|
|
@@ -95,6 +104,13 @@
|
|
|
95
104
|
- 借方合計 = 貸方合計 を自前で計算してから POST
|
|
96
105
|
- DB trigger は不整合を必ず検出するので、ローカル検算が大事
|
|
97
106
|
|
|
107
|
+
### `human_owned_entry` (409)
|
|
108
|
+
|
|
109
|
+
**原因:** 人間が作成した仕訳 (`source='manual'`/`'import'`) を PATCH しようとした。
|
|
110
|
+
|
|
111
|
+
**対処:** ミニオンは自分が作成した仕訳 (`source='ai_generated'`) しか編集できない。
|
|
112
|
+
人間作成仕訳の訂正が必要なら `POST /api/threads` で人間に依頼する。
|
|
113
|
+
|
|
98
114
|
## 取引先のアーカイブ
|
|
99
115
|
|
|
100
116
|
### `has_unsettled_reimbursements` (409)
|
|
@@ -102,9 +118,9 @@
|
|
|
102
118
|
**原因:** 未精算の立替が残っている取引先をアーカイブしようとした。
|
|
103
119
|
|
|
104
120
|
**対処:**
|
|
105
|
-
1. `GET /
|
|
106
|
-
2.
|
|
107
|
-
3.
|
|
121
|
+
1. `GET /reimbursements?counterparty_id=<id>&status=unsettled` で未精算リスト取得
|
|
122
|
+
2. **精算は人間専用** — `POST /api/threads` で `@user` に精算を依頼する
|
|
123
|
+
3. 全件精算後にアーカイブを再実行
|
|
108
124
|
|
|
109
125
|
## 自己解決できない場合
|
|
110
126
|
|
|
@@ -112,6 +128,7 @@
|
|
|
112
128
|
|
|
113
129
|
1. プロジェクトメモリーで過去事例を検索
|
|
114
130
|
2. なければ `POST /api/threads` でヘルプスレッド起票
|
|
115
|
-
- `thread_type: "help"`, `mentions: ["role:pm"]`
|
|
131
|
+
- `thread_type: "help"`, `mentions: ["role:pm"]` (人間判断が要るなら `["user"]`)
|
|
116
132
|
- `attempted_resolution` に試したことを必ず書く
|
|
117
|
-
3. **仕訳を中途半端な状態で残さない。**
|
|
133
|
+
3. **仕訳を中途半端な状態で残さない。** ただしミニオンは仕訳・立替を DELETE できないため、
|
|
134
|
+
取消が必要な場合は状況を thread に明記して人間に削除を依頼する
|
package/win/routes/chat.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const { spawn } = require('child_process')
|
|
16
|
+
const crypto = require('crypto')
|
|
16
17
|
const fs = require('fs')
|
|
17
18
|
const path = require('path')
|
|
18
19
|
const http = require('http')
|
|
@@ -130,8 +131,19 @@ async function chatRoutes(fastify) {
|
|
|
130
131
|
const prompt = await buildContextPrefix(message, context, session_id, workspaceId, referenced_tasks)
|
|
131
132
|
const currentSessionId = session_id || null
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
// Persist the user message BEFORE invoking the LLM so that crashes,
|
|
135
|
+
// timeouts, or unparseable CLI output can't lose it. New sessions get a
|
|
136
|
+
// local pending ID; it gets rekeyed to the real Claude CLI session ID
|
|
137
|
+
// once that comes back on the SSE stream.
|
|
138
|
+
// In WSL mode the upstream WSL session server keeps its own store, so we
|
|
139
|
+
// only persist locally for existing sessions (matching prior behavior).
|
|
140
|
+
const pendingSessionId = currentSessionId || `pending-${crypto.randomUUID()}`
|
|
141
|
+
if (!wsl_mode || currentSessionId) {
|
|
142
|
+
try {
|
|
143
|
+
await chatStore.addMessage(pendingSessionId, { role: 'user', content: message }, undefined, workspaceId)
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error('[Chat] failed to persist user message:', err.message)
|
|
146
|
+
}
|
|
135
147
|
}
|
|
136
148
|
|
|
137
149
|
reply.hijack()
|
|
@@ -149,7 +161,7 @@ async function chatRoutes(fastify) {
|
|
|
149
161
|
await proxyWslChat(reply.raw, prompt, currentSessionId)
|
|
150
162
|
wslModeActive = false
|
|
151
163
|
} else {
|
|
152
|
-
await streamLlmResponse(reply.raw, prompt, currentSessionId, workspaceId, message)
|
|
164
|
+
await streamLlmResponse(reply.raw, prompt, currentSessionId, workspaceId, message, pendingSessionId)
|
|
153
165
|
}
|
|
154
166
|
} catch (err) {
|
|
155
167
|
wslModeActive = false
|
|
@@ -594,15 +606,15 @@ function getLlmBinary() {
|
|
|
594
606
|
* Tracks block types to correctly forward tool_use vs text events
|
|
595
607
|
* and counts turns for session management.
|
|
596
608
|
*/
|
|
597
|
-
async function streamLlmResponse(res, prompt, sessionId, workspaceId, originalMessage) {
|
|
609
|
+
async function streamLlmResponse(res, prompt, sessionId, workspaceId, originalMessage, pendingSessionId) {
|
|
598
610
|
const primary = getActivePrimary()
|
|
599
611
|
if (primary) {
|
|
600
|
-
return streamViaPlugin(primary, res, prompt, sessionId, workspaceId, originalMessage)
|
|
612
|
+
return streamViaPlugin(primary, res, prompt, sessionId, workspaceId, originalMessage, pendingSessionId)
|
|
601
613
|
}
|
|
602
|
-
return streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, originalMessage)
|
|
614
|
+
return streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, originalMessage, pendingSessionId)
|
|
603
615
|
}
|
|
604
616
|
|
|
605
|
-
async function streamViaPlugin(plugin, res, prompt, sessionId, workspaceId, originalMessage) {
|
|
617
|
+
async function streamViaPlugin(plugin, res, prompt, sessionId, workspaceId, originalMessage, pendingSessionId) {
|
|
606
618
|
const input = { prompt }
|
|
607
619
|
const activeRef = { current: null }
|
|
608
620
|
|
|
@@ -627,37 +639,57 @@ async function streamViaPlugin(plugin, res, prompt, sessionId, workspaceId, orig
|
|
|
627
639
|
|
|
628
640
|
res.on('close', () => { activeRef.current?.kill?.('SIGTERM') })
|
|
629
641
|
|
|
630
|
-
let
|
|
631
|
-
|
|
632
|
-
output
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
+
let pluginError = null
|
|
643
|
+
try {
|
|
644
|
+
let output
|
|
645
|
+
if (plugin.capabilities.streaming && typeof plugin.stream === 'function') {
|
|
646
|
+
output = await plugin.stream(input, emit, { resumeSessionId: sessionId, activeChildRef: activeRef })
|
|
647
|
+
} else {
|
|
648
|
+
output = await plugin.invoke(input)
|
|
649
|
+
if (output.text) {
|
|
650
|
+
fullResponse = output.text
|
|
651
|
+
res.write(`data: ${JSON.stringify({ type: 'text', content: output.text })}\n\n`)
|
|
652
|
+
turnCount = 1
|
|
653
|
+
}
|
|
654
|
+
if (output.error) {
|
|
655
|
+
res.write(`data: ${JSON.stringify({ type: 'error', error: output.error.message })}\n\n`)
|
|
656
|
+
}
|
|
642
657
|
}
|
|
658
|
+
resolvedSessionId = output?.metadata?.sessionId || resolvedSessionId
|
|
659
|
+
} catch (err) {
|
|
660
|
+
// Swallow here so we can persist any partial response first; rethrow below.
|
|
661
|
+
pluginError = err
|
|
643
662
|
}
|
|
644
|
-
resolvedSessionId = output?.metadata?.sessionId || resolvedSessionId
|
|
645
663
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
664
|
+
// For new sessions, the user message was persisted under pendingSessionId
|
|
665
|
+
// before the plugin call. Rekey it to the real session ID now that we
|
|
666
|
+
// know it. If the plugin never reported a session ID, leave the message
|
|
667
|
+
// under the pending key so the history isn't lost.
|
|
668
|
+
const persistSessionId = resolvedSessionId || pendingSessionId
|
|
669
|
+
try {
|
|
670
|
+
if (!sessionId && resolvedSessionId && pendingSessionId && pendingSessionId !== resolvedSessionId) {
|
|
671
|
+
chatStore.rekeySession(pendingSessionId, resolvedSessionId)
|
|
649
672
|
}
|
|
650
|
-
if (fullResponse) {
|
|
651
|
-
await chatStore.addMessage(
|
|
673
|
+
if (fullResponse && persistSessionId) {
|
|
674
|
+
await chatStore.addMessage(persistSessionId, { role: 'assistant', content: fullResponse }, turnCount, workspaceId)
|
|
652
675
|
}
|
|
676
|
+
} catch (err) {
|
|
677
|
+
console.error('[Chat] failed to persist assistant message:', err.message)
|
|
653
678
|
}
|
|
654
679
|
|
|
655
|
-
|
|
656
|
-
|
|
680
|
+
if (pluginError) throw pluginError
|
|
681
|
+
|
|
682
|
+
let totalTurnCount = turnCount
|
|
683
|
+
try {
|
|
684
|
+
const session = await chatStore.load(workspaceId)
|
|
685
|
+
totalTurnCount = session?.turn_count || turnCount
|
|
686
|
+
} catch (err) {
|
|
687
|
+
console.error('[Chat] failed to load session for done event:', err.message)
|
|
688
|
+
}
|
|
657
689
|
res.write(`data: ${JSON.stringify({ type: 'done', session_id: resolvedSessionId, turn_count: totalTurnCount })}\n\n`)
|
|
658
690
|
}
|
|
659
691
|
|
|
660
|
-
function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, originalMessage) {
|
|
692
|
+
function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, originalMessage, pendingSessionId) {
|
|
661
693
|
return new Promise((resolve, reject) => {
|
|
662
694
|
const binaryName = getLlmBinary()
|
|
663
695
|
if (!binaryName) {
|
|
@@ -809,14 +841,22 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
|
|
|
809
841
|
console.log(`[Chat] final stderr (tail 500): ${stderrBuffer.slice(-500)}`)
|
|
810
842
|
}
|
|
811
843
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
844
|
+
// For new sessions, the user message was already persisted under
|
|
845
|
+
// pendingSessionId before spawn. Rekey it to the real session ID when
|
|
846
|
+
// Claude CLI reported one; otherwise leave the message under the
|
|
847
|
+
// pending key so the history is never lost on crash.
|
|
848
|
+
const persistSessionId = resolvedSessionId || pendingSessionId
|
|
849
|
+
try {
|
|
850
|
+
if (!sessionId && resolvedSessionId && pendingSessionId && pendingSessionId !== resolvedSessionId) {
|
|
851
|
+
chatStore.rekeySession(pendingSessionId, resolvedSessionId)
|
|
815
852
|
}
|
|
816
|
-
if (fullResponse) {
|
|
817
|
-
await chatStore.addMessage(
|
|
853
|
+
if (fullResponse && persistSessionId) {
|
|
854
|
+
await chatStore.addMessage(persistSessionId, { role: 'assistant', content: fullResponse }, turnCount, workspaceId)
|
|
818
855
|
}
|
|
856
|
+
} catch (err) {
|
|
857
|
+
console.error('[Chat] failed to persist assistant message:', err.message)
|
|
819
858
|
}
|
|
859
|
+
|
|
820
860
|
if (code !== 0) {
|
|
821
861
|
const errorMsg = stderrBuffer.trim() || `Claude CLI exited with code ${code}`
|
|
822
862
|
console.error(`[Chat] CLI failed (exit ${code}, partial=${!!fullResponse}): ${errorMsg}`)
|
|
@@ -828,8 +868,13 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
|
|
|
828
868
|
})}\n\n`)
|
|
829
869
|
}
|
|
830
870
|
|
|
831
|
-
|
|
832
|
-
|
|
871
|
+
let totalTurnCount = turnCount
|
|
872
|
+
try {
|
|
873
|
+
const session = await chatStore.load(workspaceId)
|
|
874
|
+
totalTurnCount = session?.turn_count || turnCount
|
|
875
|
+
} catch (err) {
|
|
876
|
+
console.error('[Chat] failed to load session for done event:', err.message)
|
|
877
|
+
}
|
|
833
878
|
|
|
834
879
|
res.write(`data: ${JSON.stringify({ type: 'done', session_id: resolvedSessionId, turn_count: totalTurnCount })}\n\n`)
|
|
835
880
|
resolve()
|
package/win/server.js
CHANGED
|
@@ -33,6 +33,10 @@ const boardTaskRunner = require('./board-task-runner')
|
|
|
33
33
|
// Config warnings (included in heartbeat)
|
|
34
34
|
const { getConfigWarnings } = require('../core/lib/config-warnings')
|
|
35
35
|
|
|
36
|
+
// Bundled skill deployment (version-gated, see core/lib/bundled-skills.js)
|
|
37
|
+
const { syncBundledSkills } = require('../core/lib/bundled-skills')
|
|
38
|
+
const { getActiveSkillDirs } = require('../core/llm-plugins/lib/skill-dirs')
|
|
39
|
+
|
|
36
40
|
// Pull-model daemons (from core/)
|
|
37
41
|
const stepPoller = require('../core/lib/step-poller')
|
|
38
42
|
const dagStepPoller = require('../core/lib/dag-step-poller')
|
|
@@ -214,6 +218,25 @@ function syncBundledDocs() {
|
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
220
|
|
|
221
|
+
/**
|
|
222
|
+
* (Re)deploy bundled skills to the active LLM skill directories when the package
|
|
223
|
+
* version changed. Lets `update-agent` (npm install + restart) propagate skill
|
|
224
|
+
* fixes without re-running configure, while preserving HQ-fetched customizations
|
|
225
|
+
* across ordinary restarts. See core/lib/bundled-skills.js.
|
|
226
|
+
*/
|
|
227
|
+
function syncBundledSkillsOnStartup() {
|
|
228
|
+
try {
|
|
229
|
+
syncBundledSkills({
|
|
230
|
+
packageRoot: path.join(__dirname, '..'),
|
|
231
|
+
version,
|
|
232
|
+
targetDirs: getActiveSkillDirs(),
|
|
233
|
+
markerPath: path.join(config.HOME_DIR, '.minion', 'skills-sync-version'),
|
|
234
|
+
})
|
|
235
|
+
} catch (err) {
|
|
236
|
+
console.error(`[Skills] Bundled skill sync skipped: ${err.message}`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
217
240
|
/**
|
|
218
241
|
* Register all routes (compatible + Windows-specific)
|
|
219
242
|
*/
|
|
@@ -285,6 +308,7 @@ async function start() {
|
|
|
285
308
|
syncBundledRules()
|
|
286
309
|
syncBundledRoles()
|
|
287
310
|
syncBundledDocs()
|
|
311
|
+
syncBundledSkillsOnStartup()
|
|
288
312
|
syncPermissions()
|
|
289
313
|
// Note: tmux.conf sync is skipped on Windows (not needed)
|
|
290
314
|
|