@geekbeer/minion 4.3.3 → 4.4.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.
- package/core/lib/claude-settings-sync.js +106 -0
- package/docs/task-guides.md +11 -0
- package/linux/routes/chat.js +31 -5
- package/linux/server.js +4 -26
- package/package.json +1 -1
- package/rules/core.md +14 -0
- package/settings/hooks/block-hq-navigation.js +114 -0
- package/skills/accounting-bookkeeping/SKILL.md +6 -1
- package/skills/accounting-bookkeeping/references/api-expense-reimbursement.md +29 -11
- package/skills/accounting-bookkeeping/references/api-journal-entries.md +4 -0
- package/skills/accounting-bookkeeping/references/troubleshooting.md +18 -3
- package/win/routes/chat.js +28 -3
- package/win/server.js +4 -19
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sync bundled Claude Code settings (permissions + hooks) into
|
|
5
|
+
* <homeDir>/.claude/settings.json. Shared by the Linux and Windows servers so
|
|
6
|
+
* both platforms get identical baseline permissions and the PreToolUse hooks.
|
|
7
|
+
*
|
|
8
|
+
* User overrides live in settings.local.json which Claude merges on top of the
|
|
9
|
+
* settings.json we write here, so re-syncing on every boot is safe.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs')
|
|
13
|
+
const path = require('path')
|
|
14
|
+
|
|
15
|
+
// packages/minion/core/lib/claude-settings-sync.js -> packages/minion
|
|
16
|
+
const PACKAGE_ROOT = path.join(__dirname, '..', '..')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Copy bundled hook scripts to <claudeDir>/hooks/ and return the PreToolUse
|
|
20
|
+
* hook config block to embed in settings.json. Returns null when no hooks are
|
|
21
|
+
* bundled.
|
|
22
|
+
*
|
|
23
|
+
* Currently wires block-hq-navigation.js, which denies Playwright navigation to
|
|
24
|
+
* HQ (*.minion-agent.com) so the minion uses the API instead of scraping the
|
|
25
|
+
* dashboard.
|
|
26
|
+
*/
|
|
27
|
+
function syncHookScripts(claudeDir, log) {
|
|
28
|
+
try {
|
|
29
|
+
const bundledHooksDir = path.join(PACKAGE_ROOT, 'settings', 'hooks')
|
|
30
|
+
if (!fs.existsSync(bundledHooksDir)) return null
|
|
31
|
+
|
|
32
|
+
const targetHooksDir = path.join(claudeDir, 'hooks')
|
|
33
|
+
if (!fs.existsSync(targetHooksDir)) {
|
|
34
|
+
fs.mkdirSync(targetHooksDir, { recursive: true })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const files = fs.readdirSync(bundledHooksDir).filter((f) => f.endsWith('.js'))
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
fs.copyFileSync(
|
|
40
|
+
path.join(bundledHooksDir, file),
|
|
41
|
+
path.join(targetHooksDir, file),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const navHook = path.join(targetHooksDir, 'block-hq-navigation.js')
|
|
46
|
+
if (!fs.existsSync(navHook)) return null
|
|
47
|
+
|
|
48
|
+
log(`[Hooks] Synced ${files.length} hook script(s) to ~/.claude/hooks/`)
|
|
49
|
+
return {
|
|
50
|
+
PreToolUse: [
|
|
51
|
+
{
|
|
52
|
+
matcher: 'mcp__playwright__browser_navigate',
|
|
53
|
+
hooks: [{ type: 'command', command: `node ${navHook}` }],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
log(`[Hooks] Failed to sync hook scripts: ${err.message}`)
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sync bundled permissions + hooks to <homeDir>/.claude/settings.json.
|
|
65
|
+
* @param {string} homeDir - the minion HOME (config.HOME_DIR)
|
|
66
|
+
* @param {(msg: string) => void} [log] - logger (defaults to console.log)
|
|
67
|
+
*/
|
|
68
|
+
function syncClaudeSettings(homeDir, log = console.log) {
|
|
69
|
+
const bundledPath = path.join(PACKAGE_ROOT, 'settings', 'permissions.json')
|
|
70
|
+
const settingsDir = path.join(homeDir, '.claude')
|
|
71
|
+
const settingsPath = path.join(settingsDir, 'settings.json')
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
if (!fs.existsSync(bundledPath)) return
|
|
75
|
+
|
|
76
|
+
const bundled = JSON.parse(fs.readFileSync(bundledPath, 'utf-8'))
|
|
77
|
+
|
|
78
|
+
let settings = {}
|
|
79
|
+
if (fs.existsSync(settingsPath)) {
|
|
80
|
+
try {
|
|
81
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
|
|
82
|
+
} catch {
|
|
83
|
+
log('[Permissions] existing settings.json invalid, overwriting')
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
settings.permissions = {
|
|
88
|
+
allow: bundled.allow || [],
|
|
89
|
+
deny: bundled.deny || [],
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fs.mkdirSync(settingsDir, { recursive: true })
|
|
93
|
+
|
|
94
|
+
const hooks = syncHookScripts(settingsDir, log)
|
|
95
|
+
if (hooks) {
|
|
96
|
+
settings.hooks = hooks
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
100
|
+
log(`[Permissions] Synced: allow=${(bundled.allow || []).length}, deny=${(bundled.deny || []).length}`)
|
|
101
|
+
} catch (err) {
|
|
102
|
+
log(`[Permissions] Failed to sync permissions: ${err.message}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { syncClaudeSettings }
|
package/docs/task-guides.md
CHANGED
|
@@ -41,6 +41,17 @@ curl -X POST http://localhost:8080/api/web/extract \
|
|
|
41
41
|
| 「ログインしてダッシュボード操作」 | Playwright MCP |
|
|
42
42
|
| 「フォームを送信」 | Playwright MCP |
|
|
43
43
|
| 「複数ページ巡回して全件取得」 | `/api/web/extract` をループ呼び出し (各ページに対して) |
|
|
44
|
+
| **HQ (`*.minion-agent.com`) のノート/タスク/プロジェクト** | **HQ API(Playwright禁止)** |
|
|
45
|
+
|
|
46
|
+
### HQ (`*.minion-agent.com`) のリンクは API で取得する
|
|
47
|
+
|
|
48
|
+
HQダッシュボードのページURLを Playwright MCP で開いてはならない(PreToolUse フック `block-hq-navigation.js` で拒否される)。受け取ったURL/タグは以下に変換する。チャットで参照された場合、本文はプロンプト先頭の「参照ノート」「参照チケット」ブロックに既に注入済みなので、まずそれを読む。
|
|
49
|
+
|
|
50
|
+
| 受け取った形 | 取得方法 |
|
|
51
|
+
|------|---------|
|
|
52
|
+
| `[note:UUID]` / `…/notes/:id` | `GET $HQ_URL/api/minion/workspaces/:wsId/notes/:id`(または `…/projects/:pid/notes/:id`) |
|
|
53
|
+
| `[task:UUID]` / `…/tasks/:id` | `GET $HQ_URL/api/minion/projects/:pid/tasks/:id` |
|
|
54
|
+
| `…/projects/:id` | `GET $HQ_URL/api/minion/projects/:id` |
|
|
44
55
|
|
|
45
56
|
### キャッシュの確認・破棄 (debug)
|
|
46
57
|
|
package/linux/routes/chat.js
CHANGED
|
@@ -44,7 +44,7 @@ async function chatRoutes(fastify) {
|
|
|
44
44
|
return { success: false, error: 'Unauthorized' }
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const { message, session_id, context, workspace_id, referenced_tasks } = request.body || {}
|
|
47
|
+
const { message, session_id, context, workspace_id, referenced_tasks, referenced_notes } = request.body || {}
|
|
48
48
|
|
|
49
49
|
if (!message || typeof message !== 'string') {
|
|
50
50
|
reply.code(400)
|
|
@@ -54,9 +54,10 @@ async function chatRoutes(fastify) {
|
|
|
54
54
|
const workspaceId = workspace_id || null
|
|
55
55
|
|
|
56
56
|
// Build prompt — add memory context on new sessions + page context + workspace
|
|
57
|
-
// referenced_tasks
|
|
58
|
-
// so the user's chat log keeps just the [task:UUID]
|
|
59
|
-
|
|
57
|
+
// referenced_tasks / referenced_notes are injected into the prompt only (not
|
|
58
|
+
// stored in history) so the user's chat log keeps just the [task:UUID] /
|
|
59
|
+
// [note:UUID] tag (or pasted URL), not a noisy dump.
|
|
60
|
+
const prompt = await buildContextPrefix(message, context, session_id, workspaceId, referenced_tasks, referenced_notes)
|
|
60
61
|
|
|
61
62
|
// Persist the user message BEFORE invoking the LLM so that crashes,
|
|
62
63
|
// timeouts, or unparseable CLI output can't lose it. For new sessions we
|
|
@@ -267,7 +268,7 @@ ${indexed}`
|
|
|
267
268
|
* On new sessions (no session_id), injects minion memory + recent daily logs.
|
|
268
269
|
* No conversation history injection — Claude CLI handles that via --resume.
|
|
269
270
|
*/
|
|
270
|
-
async function buildContextPrefix(message, context, sessionId, workspaceId, referencedTasks) {
|
|
271
|
+
async function buildContextPrefix(message, context, sessionId, workspaceId, referencedTasks, referencedNotes) {
|
|
271
272
|
const parts = []
|
|
272
273
|
|
|
273
274
|
// Resolved [task:UUID] tags from HQ — surface ticket details so Claude can
|
|
@@ -288,6 +289,29 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
288
289
|
parts.push('')
|
|
289
290
|
}
|
|
290
291
|
|
|
292
|
+
// Resolved [note:UUID] tags / pasted note URLs from HQ — surface the note body
|
|
293
|
+
// inline so Claude answers WITHOUT opening the HQ page with Playwright. The
|
|
294
|
+
// body is a snippet; the full note is one API call away.
|
|
295
|
+
if (Array.isArray(referencedNotes) && referencedNotes.length > 0) {
|
|
296
|
+
parts.push('[参照ノート — ユーザーがメッセージ内で `[note:UUID]` 形式またはURLで参照しているHQノート。Playwrightで開かず、全文が必要なら下記APIを使うこと]')
|
|
297
|
+
const NOTE_LIMIT = 2000
|
|
298
|
+
for (const n of referencedNotes) {
|
|
299
|
+
if (!n || !n.id) continue
|
|
300
|
+
const body = String(n.content || '').trim()
|
|
301
|
+
const truncated = body.length > NOTE_LIMIT
|
|
302
|
+
const snippet = truncated ? body.slice(0, NOTE_LIMIT) : body
|
|
303
|
+
parts.push(`- [note:${n.id}] ${n.title || '(無題)'} (status: ${n.status || '?'})`)
|
|
304
|
+
if (snippet) {
|
|
305
|
+
parts.push(' ```', ...snippet.split('\n').map((l) => ` ${l}`), ' ```')
|
|
306
|
+
}
|
|
307
|
+
parts.push(
|
|
308
|
+
` 全文/更新: GET|PATCH $HQ_URL/api/minion/workspaces/${n.workspace_id}/notes/${n.id}` +
|
|
309
|
+
(truncated ? ' (本文は上記抜粋、全文はAPIで取得)' : ''),
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
parts.push('')
|
|
313
|
+
}
|
|
314
|
+
|
|
291
315
|
// Inject workspace context so Claude Code knows which workspace it's operating in
|
|
292
316
|
if (workspaceId) {
|
|
293
317
|
const workspaceStore = require('../../core/stores/workspace-store')
|
|
@@ -388,6 +412,8 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
388
412
|
'',
|
|
389
413
|
'Playwright MCP (`mcp__playwright__*`) は **ログイン・フォーム入力・複数画面の対話操作**が必要な場合のみ使用する。',
|
|
390
414
|
'単純な閲覧・要約・一覧取得用途ではMCPを使わない。',
|
|
415
|
+
'',
|
|
416
|
+
'HQ (`*.minion-agent.com`) のページURLは **Playwrightで開かずAPIで取得する**。ノートは `GET $HQ_URL/api/minion/workspaces/:wsId/notes/:id`、タスクは `GET $HQ_URL/api/minion/projects/:pid/tasks/:id`。チャットで参照されたノート/タスクの本文は上記「参照ノート」「参照チケット」ブロックに既に注入済み。',
|
|
391
417
|
''
|
|
392
418
|
)
|
|
393
419
|
}
|
package/linux/server.js
CHANGED
|
@@ -38,6 +38,7 @@ const PACKAGE_ROOT = path.join(__dirname, '..')
|
|
|
38
38
|
// Core shared modules
|
|
39
39
|
const { config, validate, isHqConfigured } = require('../core/config')
|
|
40
40
|
const { sendHeartbeat } = require('../core/api')
|
|
41
|
+
const { syncClaudeSettings } = require('../core/lib/claude-settings-sync')
|
|
41
42
|
const { version } = require('../package.json')
|
|
42
43
|
|
|
43
44
|
const workflowStore = require('../core/stores/workflow-store')
|
|
@@ -159,34 +160,11 @@ process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
|
159
160
|
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
|
-
* Sync bundled permissions into ~/.claude/settings.json.
|
|
163
|
+
* Sync bundled permissions + PreToolUse hooks into ~/.claude/settings.json.
|
|
164
|
+
* Delegates to the shared core module so Linux and Windows stay in sync.
|
|
163
165
|
*/
|
|
164
166
|
function syncPermissions() {
|
|
165
|
-
|
|
166
|
-
const settingsDir = path.join(config.HOME_DIR, '.claude')
|
|
167
|
-
const settingsPath = path.join(settingsDir, 'settings.json')
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
if (!fs.existsSync(bundledPath)) return
|
|
171
|
-
|
|
172
|
-
const bundled = JSON.parse(fs.readFileSync(bundledPath, 'utf-8'))
|
|
173
|
-
|
|
174
|
-
let settings = {}
|
|
175
|
-
if (fs.existsSync(settingsPath)) {
|
|
176
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
settings.permissions = {
|
|
180
|
-
allow: bundled.allow || [],
|
|
181
|
-
deny: bundled.deny || [],
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
fs.mkdirSync(settingsDir, { recursive: true })
|
|
185
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
186
|
-
console.log(`[Permissions] Synced: allow=${bundled.allow.length}, deny=${bundled.deny.length}`)
|
|
187
|
-
} catch (err) {
|
|
188
|
-
console.error(`[Permissions] Failed to sync permissions: ${err.message}`)
|
|
189
|
-
}
|
|
167
|
+
syncClaudeSettings(config.HOME_DIR, (msg) => console.log(msg))
|
|
190
168
|
}
|
|
191
169
|
|
|
192
170
|
/**
|
package/package.json
CHANGED
package/rules/core.md
CHANGED
|
@@ -47,6 +47,20 @@ Minion
|
|
|
47
47
|
- **Workspace**: ミニオンは複数のワークスペースに所属でき、スキルやプロジェクトはワークスペース単位でスコープされる。チャットセッションもワークスペース別に分離される。所属ワークスペースはハートビートで自動同期され、`hq list workspaces` で確認できる。
|
|
48
48
|
- ミニオンは複数プロジェクトに `pm`、`engineer`、`accountant` として参加できる。
|
|
49
49
|
|
|
50
|
+
## HQ リソースへのリンクは API で取得する
|
|
51
|
+
|
|
52
|
+
`*.minion-agent.com`(HQダッシュボード)のページURLを **Playwright MCP で開いてはならない**。ほぼ全リソースがAPIで取得できる。受け取ったURL/タグは以下のAPIに変換して使うこと。
|
|
53
|
+
|
|
54
|
+
| 受け取った形 | 取得方法 |
|
|
55
|
+
|------|---------|
|
|
56
|
+
| `[note:UUID]` / `…/notes/:id` | `GET $HQ_URL/api/minion/workspaces/:wsId/notes/:id`(プロジェクト紐づきなら `…/projects/:pid/notes/:id` も可) |
|
|
57
|
+
| `[task:UUID]` / `…/tasks/:id` | `GET $HQ_URL/api/minion/projects/:pid/tasks/:id` |
|
|
58
|
+
| `…/projects/:id` | `GET $HQ_URL/api/minion/projects/:id` |
|
|
59
|
+
|
|
60
|
+
- チャットでユーザーが `[note:UUID]` / `[task:UUID]` やノートURLを貼った場合、**本文はプロンプト先頭の「参照ノート」「参照チケット」ブロックに既に注入済み**。まずそれを読むこと。全文・最新版が要るときだけ上記APIを叩く。
|
|
61
|
+
- 認証なしのPlaywright閲覧はログイン画面や不完全なHTMLを掴むため結果が壊れる。HQリンクは必ずAPIで取得する。
|
|
62
|
+
- このルールは PreToolUse フックでも強制されており、`mcp__playwright__browser_navigate` で `*.minion-agent.com` を開こうとすると拒否される。
|
|
63
|
+
|
|
50
64
|
## Email
|
|
51
65
|
|
|
52
66
|
各ミニオンには専用メールアドレス `m-{MINION_ID}@minion-agent.com` が割り当てられている。
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PreToolUse hook: block Playwright navigation to HQ (*.minion-agent.com).
|
|
4
|
+
*
|
|
5
|
+
* The minion must read HQ resources (notes, tasks, projects) via the Agent/HQ
|
|
6
|
+
* API, not by scraping the dashboard with Playwright — unauthenticated browser
|
|
7
|
+
* navigation just lands on a login page or partial HTML and corrupts results.
|
|
8
|
+
* This hook is the enforcement layer behind the "HQ リソースへのリンクは API で
|
|
9
|
+
* 取得する" rule in core.md.
|
|
10
|
+
*
|
|
11
|
+
* Wired into ~/.claude/settings.json as:
|
|
12
|
+
* hooks.PreToolUse[] -> matcher "mcp__playwright__browser_navigate"
|
|
13
|
+
*
|
|
14
|
+
* Receives the tool call as JSON on stdin; denies via hookSpecificOutput when
|
|
15
|
+
* the target host matches HQ, allows everything else. Blocked attempts are
|
|
16
|
+
* appended to ~/.minion/logs/hq-nav-blocks.log for effectiveness measurement.
|
|
17
|
+
*/
|
|
18
|
+
const fs = require('fs')
|
|
19
|
+
const os = require('os')
|
|
20
|
+
const path = require('path')
|
|
21
|
+
|
|
22
|
+
function readStdin() {
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync(0, 'utf8')
|
|
25
|
+
} catch {
|
|
26
|
+
return ''
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Hosts considered "HQ". Overridable via HQ_NAV_BLOCK_HOSTS (comma-separated). */
|
|
31
|
+
function blockedHostSuffixes() {
|
|
32
|
+
const fromEnv = (process.env.HQ_NAV_BLOCK_HOSTS || '')
|
|
33
|
+
.split(',')
|
|
34
|
+
.map((s) => s.trim().toLowerCase())
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
const suffixes = new Set(['minion-agent.com', ...fromEnv])
|
|
37
|
+
// Also include the configured HQ host, in case it lives on a custom domain.
|
|
38
|
+
if (process.env.HQ_URL) {
|
|
39
|
+
try {
|
|
40
|
+
suffixes.add(new URL(process.env.HQ_URL).hostname.toLowerCase())
|
|
41
|
+
} catch {
|
|
42
|
+
/* ignore malformed HQ_URL */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [...suffixes]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isBlocked(rawUrl, suffixes) {
|
|
49
|
+
let host
|
|
50
|
+
try {
|
|
51
|
+
host = new URL(rawUrl).hostname.toLowerCase()
|
|
52
|
+
} catch {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
return suffixes.some((s) => host === s || host.endsWith(`.${s}`))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function logBlock(url) {
|
|
59
|
+
try {
|
|
60
|
+
const dir = path.join(os.homedir(), '.minion', 'logs')
|
|
61
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
62
|
+
fs.appendFileSync(
|
|
63
|
+
path.join(dir, 'hq-nav-blocks.log'),
|
|
64
|
+
`${new Date().toISOString()}\t${url}\n`,
|
|
65
|
+
)
|
|
66
|
+
} catch {
|
|
67
|
+
/* best-effort logging only */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function allow() {
|
|
72
|
+
process.exit(0)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function deny(url) {
|
|
76
|
+
logBlock(url)
|
|
77
|
+
const reason =
|
|
78
|
+
`Playwright で ${url} を開くことは禁止されています。HQ (*.minion-agent.com) の` +
|
|
79
|
+
'リソースはAPIで取得してください。例: ノートは `GET $HQ_URL/api/minion/workspaces/:wsId/notes/:id`、' +
|
|
80
|
+
'タスクは `GET $HQ_URL/api/minion/projects/:pid/tasks/:id`。' +
|
|
81
|
+
'チャットで参照されたノート/タスクの本文はプロンプト先頭の「参照ノート」「参照チケット」ブロックに既に注入されています。'
|
|
82
|
+
process.stdout.write(
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
hookSpecificOutput: {
|
|
85
|
+
hookEventName: 'PreToolUse',
|
|
86
|
+
permissionDecision: 'deny',
|
|
87
|
+
permissionDecisionReason: reason,
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
process.exit(0)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function main() {
|
|
95
|
+
let payload
|
|
96
|
+
try {
|
|
97
|
+
payload = JSON.parse(readStdin() || '{}')
|
|
98
|
+
} catch {
|
|
99
|
+
allow()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const url = payload?.tool_input?.url
|
|
103
|
+
if (typeof url !== 'string' || !url) {
|
|
104
|
+
allow()
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
if (isBlocked(url, blockedHostSuffixes())) {
|
|
108
|
+
deny(url)
|
|
109
|
+
} else {
|
|
110
|
+
allow()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main()
|
|
@@ -57,10 +57,15 @@ API 全体のエンドポイント表は `~/.minion/docs/api-reference.md` の
|
|
|
57
57
|
- default_payable_account_id が未設定なら PATCH で先に設定する
|
|
58
58
|
2. POST /reimbursements で記帳
|
|
59
59
|
body: { paid_by_counterparty_id, occurred_on, amount, expense_account_id, description }
|
|
60
|
-
→ 内部で「(借)
|
|
60
|
+
→ 内部で「(借)借方科目 / (貸)立替者の負債科目」の仕訳が自動生成される
|
|
61
|
+
- expense_account_id は費用に限らず、会社の負債 (未払金・未払税金) も指定可。
|
|
62
|
+
例: 役員が会社の未払法人税を立替納付 → expense_account_id=未払法人税等
|
|
61
63
|
3. レシートがあれば事前に GET /receipts で対象を確認 (アップロードと仕訳への attach は人間が HQ UI で行う)
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
**立替は必ず /reimbursements で記帳する。** 複合仕訳 (A の /entries) に counterparty_id を
|
|
67
|
+
付けて立替の仕訳を自前で組んでも、立替精算リストに出ず消し込みできない (補助元帳のみ)。
|
|
68
|
+
|
|
64
69
|
詳細: `references/api-expense-reimbursement.md`
|
|
65
70
|
|
|
66
71
|
### B') レシートの照会・OCR
|
|
@@ -11,14 +11,28 @@
|
|
|
11
11
|
ミニオンに提供されるのは **一覧取得 (GET)** と **記帳 (POST)** の2つだけ。
|
|
12
12
|
精算・編集・削除は実出金や取消を伴うため **人間専用**(後述)。
|
|
13
13
|
|
|
14
|
+
## ⚠️ いつ /reimbursements を使うか (最重要)
|
|
15
|
+
|
|
16
|
+
**「立替精算リストに載せて後で消し込みたい立替」は、必ずこの `POST /reimbursements` で記帳すること。**
|
|
17
|
+
|
|
18
|
+
手動仕訳 (`POST /entries`) に `counterparty_id` を付けて立替の仕訳を自前で組んでも、
|
|
19
|
+
`accounting_expense_reimbursements` レコードが作られないため **立替精算リストには出ない**
|
|
20
|
+
(取引先別補助元帳には出るが、settle / bulk-settle の対象にならない)。
|
|
21
|
+
|
|
22
|
+
立替記帳は `/entries` ではなく必ず `/reimbursements` を使う、と覚えること。
|
|
23
|
+
|
|
14
24
|
## 仕訳パターン
|
|
15
25
|
|
|
16
26
|
**立替記帳時 (POST /reimbursements が自動生成):**
|
|
17
27
|
```
|
|
18
|
-
(借)
|
|
28
|
+
(借) 借方科目 (費用 または 会社の負債) amount [counterparty_id=<立替者>]
|
|
19
29
|
(貸) counterparty.default_payable_account_id amount [counterparty_id=<立替者>]
|
|
20
30
|
```
|
|
21
31
|
|
|
32
|
+
借方は通常は費用科目だが、**会社の負債 (未払金・未払税金等) を立替で支払って取り崩すケース**では
|
|
33
|
+
負債科目を借方に置く。例: 役員が会社の未払法人税を立替納付 →
|
|
34
|
+
`(借) 未払法人税等 / (貸) 役員借入金`。いずれの場合も立替精算リストに載り、消し込みできる。
|
|
35
|
+
|
|
22
36
|
**精算時 (人間が HQ UI で実行):**
|
|
23
37
|
```
|
|
24
38
|
(借) counterparty.default_payable_account_id amount [counterparty_id=<立替者>]
|
|
@@ -47,7 +61,7 @@ Response:
|
|
|
47
61
|
|
|
48
62
|
### POST /reimbursements
|
|
49
63
|
|
|
50
|
-
立替経費を記帳する。仕訳(
|
|
64
|
+
立替経費を記帳する。仕訳(借方=費用 or 会社の負債, 貸方=payable)を自動生成する。
|
|
51
65
|
|
|
52
66
|
```
|
|
53
67
|
Body:
|
|
@@ -55,21 +69,25 @@ Body:
|
|
|
55
69
|
"paid_by_counterparty_id": "<立替者のcounterparty uuid>",
|
|
56
70
|
"occurred_on": "YYYY-MM-DD",
|
|
57
71
|
"amount": 1000,
|
|
58
|
-
"expense_account_id": "
|
|
72
|
+
"expense_account_id": "<借方科目 uuid>", // 費用 / 繰延資産 / 会社の負債のいずれか
|
|
59
73
|
"description": "新幹線代 東京→大阪", // 任意
|
|
60
74
|
"payable_account_id": "<貸方科目 uuid>" // 任意。省略時は counterparty.default_payable_account_id を使用
|
|
61
75
|
}
|
|
62
76
|
|
|
63
|
-
Response: { "reimbursement": { "id", "expense_journal_entry_id" } }
|
|
77
|
+
Response: { "reimbursement": { "id", "expense_journal_entry_id" }, "entry": {...} }
|
|
78
|
+
|
|
79
|
+
`expense_account_id` (= 借方科目) は名前に反して費用科目に限らない。**立替で何を支払ったか**で選ぶ:
|
|
80
|
+
- 経費の立替 → 費用科目 (旅費交通費 等)
|
|
81
|
+
- 創立費等の立替 → 繰延資産科目
|
|
82
|
+
- 会社の負債の立替 → 負債科目 (未払法人税等・未払金 等) ← 「いつ /reimbursements を使うか」参照
|
|
83
|
+
このルートは借方の型チェックを行わないため、上記の用途に応じた科目をそのまま渡す。
|
|
64
84
|
|
|
65
|
-
|
|
66
|
-
400
|
|
67
|
-
400
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
409 counterparty_archived — 立替者がアーカイブ済み
|
|
85
|
+
主要エラー (このルートが実際に返すもの):
|
|
86
|
+
400 missing required fields — paid_by_counterparty_id / occurred_on / amount / expense_account_id のいずれか欠落
|
|
87
|
+
400 amount must be positive — amount が正の数値でない
|
|
88
|
+
404 Counterparty not found — 立替者が見つからない / 別帳簿 (next_action: POST で先に作成)
|
|
89
|
+
400 payable_account_id 未指定 — payable_account_id 省略かつ counterparty.default_payable_account_id 未設定
|
|
71
90
|
409 code: 'period_closed' — occurred_on が締め済期間内
|
|
72
|
-
422 missing_default_payable_account — 立替者の default_payable_account_id が未設定 (先に PATCH /counterparties で設定)
|
|
73
91
|
```
|
|
74
92
|
|
|
75
93
|
## 人間専用操作 (ミニオンから提供されない)
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
複式簿記の仕訳エンドポイント。立替経費以外の通常取引(収入/支出/振替/手動仕訳)を扱う。
|
|
4
4
|
|
|
5
|
+
> **立替 (役員/従業員の立替払い) はこのエンドポイントで組まず、必ず `/reimbursements` を使うこと。**
|
|
6
|
+
> `manual` 仕訳に `counterparty_id` を付けて立替仕訳を自前で組んでも立替精算リストに出ず、消し込みできない。
|
|
7
|
+
> 詳細は `api-expense-reimbursement.md` の「いつ /reimbursements を使うか」。
|
|
8
|
+
|
|
5
9
|
> パスはすべて **ミニオン用** `$HQ_URL/api/minion/workspaces/:id/accounting/` を基準とした相対表記。
|
|
6
10
|
> 認証は `Authorization: Bearer $API_TOKEN`。人間用 `/api/accounting/*` は使わない。
|
|
7
11
|
> ミニオンが作成した仕訳は **常に `source='ai_generated'`** で記録される。
|
|
@@ -63,6 +63,16 @@
|
|
|
63
63
|
- 同一人物なら既存IDを使う
|
|
64
64
|
- 別人なら名前を変える(例: "山田太郎(役員)" vs "山田太郎(従業員)")
|
|
65
65
|
|
|
66
|
+
### 記帳は成功したのに立替精算リストに出ない
|
|
67
|
+
|
|
68
|
+
**原因:** 立替を `/reimbursements` ではなく**手動仕訳 (`/entries`)** で記帳した。
|
|
69
|
+
`counterparty_id` を行に付けても、`accounting_expense_reimbursements` レコードが作られないため、
|
|
70
|
+
立替精算リスト (`GET /reimbursements`) には出ない (取引先別補助元帳には出る)。
|
|
71
|
+
|
|
72
|
+
**対処:**
|
|
73
|
+
1. 誤って `/entries` で作った立替仕訳は、ミニオンからは DELETE できない → 人間に取消を依頼
|
|
74
|
+
2. 改めて `POST /reimbursements` で記帳し直す (借方が会社の負債なら `expense_account_id` に負債科目を渡す)
|
|
75
|
+
|
|
66
76
|
## 期間まわり
|
|
67
77
|
|
|
68
78
|
### `period_closed` (409)
|
|
@@ -76,11 +86,16 @@
|
|
|
76
86
|
|
|
77
87
|
## 科目まわり
|
|
78
88
|
|
|
79
|
-
### `
|
|
89
|
+
### 立替の借方科目 (`expense_account_id`) に何を指定できるか
|
|
80
90
|
|
|
81
|
-
|
|
91
|
+
`/reimbursements` の `expense_account_id` (= 借方) は**型チェックされず**、費用に限らない。
|
|
92
|
+
立替で何を支払ったかに応じて選ぶ:
|
|
93
|
+
- 経費の立替 → 費用科目 (`GET /accounts?type=expense`)
|
|
94
|
+
- 会社の負債 (未払金・未払税金) の立替払い → 負債科目 (`GET /accounts?type=liability`)
|
|
95
|
+
- 創立費等の立替 → 繰延資産科目
|
|
82
96
|
|
|
83
|
-
|
|
97
|
+
(以前のドキュメントには「expense 型でないと `invalid_expense_account_type` で弾かれる」とあったが、
|
|
98
|
+
`/reimbursements` ルートはこの検証を行わない。負債を借方に置く立替も正しく記帳できる。)
|
|
84
99
|
|
|
85
100
|
### `invalid_default_payable_account` (400)
|
|
86
101
|
|
package/win/routes/chat.js
CHANGED
|
@@ -119,7 +119,7 @@ async function chatRoutes(fastify) {
|
|
|
119
119
|
return { success: false, error: 'Unauthorized' }
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
const { message, session_id, context, wsl_mode, workspace_id, referenced_tasks } = request.body || {}
|
|
122
|
+
const { message, session_id, context, wsl_mode, workspace_id, referenced_tasks, referenced_notes } = request.body || {}
|
|
123
123
|
if (!message || typeof message !== 'string') {
|
|
124
124
|
reply.code(400)
|
|
125
125
|
return { success: false, error: 'message is required' }
|
|
@@ -128,7 +128,7 @@ async function chatRoutes(fastify) {
|
|
|
128
128
|
const workspaceId = workspace_id || null
|
|
129
129
|
// referenced_tasks is injected into the prompt only (not stored in history)
|
|
130
130
|
// so the user's chat log keeps just the [task:UUID] tag, not a noisy dump.
|
|
131
|
-
const prompt = await buildContextPrefix(message, context, session_id, workspaceId, referenced_tasks)
|
|
131
|
+
const prompt = await buildContextPrefix(message, context, session_id, workspaceId, referenced_tasks, referenced_notes)
|
|
132
132
|
const currentSessionId = session_id || null
|
|
133
133
|
|
|
134
134
|
// Persist the user message BEFORE invoking the LLM so that crashes,
|
|
@@ -336,7 +336,7 @@ ${indexed}`
|
|
|
336
336
|
})
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
async function buildContextPrefix(message, context, sessionId, workspaceId, referencedTasks) {
|
|
339
|
+
async function buildContextPrefix(message, context, sessionId, workspaceId, referencedTasks, referencedNotes) {
|
|
340
340
|
const parts = []
|
|
341
341
|
|
|
342
342
|
// Resolved [task:UUID] tags from HQ — surface ticket details so Claude can
|
|
@@ -355,6 +355,29 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
355
355
|
parts.push('')
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
// Resolved [note:UUID] tags / pasted note URLs from HQ — surface the note body
|
|
359
|
+
// inline so Claude answers WITHOUT opening the HQ page with Playwright. The
|
|
360
|
+
// body is a snippet; the full note is one API call away.
|
|
361
|
+
if (Array.isArray(referencedNotes) && referencedNotes.length > 0) {
|
|
362
|
+
parts.push('[参照ノート — ユーザーがメッセージ内で `[note:UUID]` 形式またはURLで参照しているHQノート。Playwrightで開かず、全文が必要なら下記APIを使うこと]')
|
|
363
|
+
const NOTE_LIMIT = 2000
|
|
364
|
+
for (const n of referencedNotes) {
|
|
365
|
+
if (!n || !n.id) continue
|
|
366
|
+
const body = String(n.content || '').trim()
|
|
367
|
+
const truncated = body.length > NOTE_LIMIT
|
|
368
|
+
const snippet = truncated ? body.slice(0, NOTE_LIMIT) : body
|
|
369
|
+
parts.push(`- [note:${n.id}] ${n.title || '(無題)'} (status: ${n.status || '?'})`)
|
|
370
|
+
if (snippet) {
|
|
371
|
+
parts.push(' ```', ...snippet.split('\n').map((l) => ` ${l}`), ' ```')
|
|
372
|
+
}
|
|
373
|
+
parts.push(
|
|
374
|
+
` 全文/更新: GET|PATCH $HQ_URL/api/minion/workspaces/${n.workspace_id}/notes/${n.id}` +
|
|
375
|
+
(truncated ? ' (本文は上記抜粋、全文はAPIで取得)' : ''),
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
parts.push('')
|
|
379
|
+
}
|
|
380
|
+
|
|
358
381
|
// Inject workspace context so Claude Code knows which workspace it's operating in
|
|
359
382
|
if (workspaceId) {
|
|
360
383
|
const workspaceStore = require('../../core/stores/workspace-store')
|
|
@@ -454,6 +477,8 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
454
477
|
'',
|
|
455
478
|
'Playwright MCP (`mcp__playwright__*`) は **ログイン・フォーム入力・複数画面の対話操作**が必要な場合のみ使用する。',
|
|
456
479
|
'単純な閲覧・要約・一覧取得用途ではMCPを使わない。',
|
|
480
|
+
'',
|
|
481
|
+
'HQ (`*.minion-agent.com`) のページURLは **Playwrightで開かずAPIで取得する**。ノートは `GET $HQ_URL/api/minion/workspaces/:wsId/notes/:id`、タスクは `GET $HQ_URL/api/minion/projects/:pid/tasks/:id`。チャットで参照されたノート/タスクの本文は上記「参照ノート」「参照チケット」ブロックに既に注入済み。',
|
|
457
482
|
''
|
|
458
483
|
)
|
|
459
484
|
}
|
package/win/server.js
CHANGED
|
@@ -35,6 +35,7 @@ const { getConfigWarnings } = require('../core/lib/config-warnings')
|
|
|
35
35
|
|
|
36
36
|
// Bundled skill deployment (version-gated, see core/lib/bundled-skills.js)
|
|
37
37
|
const { syncBundledSkills } = require('../core/lib/bundled-skills')
|
|
38
|
+
const { syncClaudeSettings } = require('../core/lib/claude-settings-sync')
|
|
38
39
|
const { getActiveSkillDirs } = require('../core/llm-plugins/lib/skill-dirs')
|
|
39
40
|
|
|
40
41
|
// Pull-model daemons (from core/)
|
|
@@ -126,27 +127,11 @@ process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
|
126
127
|
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
127
128
|
|
|
128
129
|
/**
|
|
129
|
-
* Sync bundled permissions into ~/.claude/settings.json.
|
|
130
|
+
* Sync bundled permissions + PreToolUse hooks into ~/.claude/settings.json.
|
|
131
|
+
* Delegates to the shared core module so Linux and Windows stay in sync.
|
|
130
132
|
*/
|
|
131
133
|
function syncPermissions() {
|
|
132
|
-
|
|
133
|
-
const settingsDir = path.join(config.HOME_DIR, '.claude')
|
|
134
|
-
const settingsPath = path.join(settingsDir, 'settings.json')
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
if (!fs.existsSync(bundledPath)) return
|
|
138
|
-
const bundled = JSON.parse(fs.readFileSync(bundledPath, 'utf-8'))
|
|
139
|
-
let settings = {}
|
|
140
|
-
if (fs.existsSync(settingsPath)) {
|
|
141
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
|
|
142
|
-
}
|
|
143
|
-
settings.permissions = { allow: bundled.allow || [], deny: bundled.deny || [] }
|
|
144
|
-
fs.mkdirSync(settingsDir, { recursive: true })
|
|
145
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
146
|
-
console.log(`[Permissions] Synced: allow=${bundled.allow.length}, deny=${bundled.deny.length}`)
|
|
147
|
-
} catch (err) {
|
|
148
|
-
console.error(`[Permissions] Failed to sync permissions: ${err.message}`)
|
|
149
|
-
}
|
|
134
|
+
syncClaudeSettings(config.HOME_DIR, (msg) => console.log(msg))
|
|
150
135
|
}
|
|
151
136
|
|
|
152
137
|
/**
|