@geekbeer/minion 3.23.1 → 3.25.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/config.js +5 -0
- package/core/db.js +8 -1
- package/core/routes/skills.js +35 -17
- package/core/stores/workspace-store.js +48 -0
- package/docs/api-reference.md +25 -0
- package/linux/bin/hq +16 -0
- package/linux/routes/chat.js +16 -3
- package/linux/server.js +10 -2
- package/package.json +1 -1
- package/rules/core.md +1 -0
- package/win/bin/hq.ps1 +13 -0
- package/win/routes/chat.js +15 -2
- package/win/server.js +10 -2
package/core/config.js
CHANGED
|
@@ -77,6 +77,11 @@ const config = {
|
|
|
77
77
|
MINION_ID: process.env.MINION_ID || '',
|
|
78
78
|
MINION_EMAIL: process.env.MINION_ID ? `m-${process.env.MINION_ID}@minion-agent.com` : '',
|
|
79
79
|
|
|
80
|
+
// Workspace targeting (optional)
|
|
81
|
+
// When set, skill push/fetch operations will target this workspace.
|
|
82
|
+
// If not set, HQ resolves the workspace from workspace_minions.
|
|
83
|
+
WORKSPACE_ID: process.env.WORKSPACE_ID || '',
|
|
84
|
+
|
|
80
85
|
// Server settings
|
|
81
86
|
AGENT_PORT: parseInt(process.env.AGENT_PORT, 10) || 8080,
|
|
82
87
|
|
package/core/db.js
CHANGED
|
@@ -192,6 +192,14 @@ function initSchema(db) {
|
|
|
192
192
|
VALUES (new.rowid, new.content);
|
|
193
193
|
END;
|
|
194
194
|
|
|
195
|
+
-- ==================== workspaces (cache from HQ) ====================
|
|
196
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
197
|
+
id TEXT PRIMARY KEY,
|
|
198
|
+
name TEXT NOT NULL,
|
|
199
|
+
slug TEXT NOT NULL,
|
|
200
|
+
updated_at INTEGER NOT NULL
|
|
201
|
+
);
|
|
202
|
+
|
|
195
203
|
-- ==================== chat_sessions ====================
|
|
196
204
|
CREATE TABLE IF NOT EXISTS chat_sessions (
|
|
197
205
|
session_id TEXT PRIMARY KEY,
|
|
@@ -267,7 +275,6 @@ function initSchema(db) {
|
|
|
267
275
|
CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status);
|
|
268
276
|
CREATE INDEX IF NOT EXISTS idx_todos_project ON todos(project_id);
|
|
269
277
|
CREATE INDEX IF NOT EXISTS idx_todos_priority ON todos(priority);
|
|
270
|
-
CREATE INDEX IF NOT EXISTS idx_todos_session ON todos(session_id);
|
|
271
278
|
|
|
272
279
|
-- ==================== emails ====================
|
|
273
280
|
CREATE TABLE IF NOT EXISTS emails (
|
package/core/routes/skills.js
CHANGED
|
@@ -104,10 +104,12 @@ async function writeSkillToLocal(name, { content, description, display_name, typ
|
|
|
104
104
|
* Reusable by workflow push to auto-sync pipeline skills.
|
|
105
105
|
*
|
|
106
106
|
* @param {string} name - Skill slug (e.g. "execution-report")
|
|
107
|
+
* @param {object} [opts]
|
|
108
|
+
* @param {string} [opts.workspaceId] - Target workspace ID (overrides WORKSPACE_ID env var)
|
|
107
109
|
* @returns {Promise<object>} HQ response
|
|
108
110
|
* @throws {Error} If skill not found locally or HQ rejects
|
|
109
111
|
*/
|
|
110
|
-
async function pushSkillToHQ(name) {
|
|
112
|
+
async function pushSkillToHQ(name, opts = {}) {
|
|
111
113
|
// Use the Primary plugin's skill directory as the canonical read source.
|
|
112
114
|
// Skills are written identically to every enabled plugin dir, so reading
|
|
113
115
|
// from any one of them yields the same content.
|
|
@@ -140,16 +142,24 @@ async function pushSkillToHQ(name) {
|
|
|
140
142
|
}
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
const payload = {
|
|
146
|
+
name: metadata.name || name,
|
|
147
|
+
display_name: metadata.display_name || metadata.name || name,
|
|
148
|
+
description: metadata.description || '',
|
|
149
|
+
content: rawContent,
|
|
150
|
+
type: metadata.type || 'workflow',
|
|
151
|
+
files,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Include workspace_id if specified or configured (for multi-workspace targeting)
|
|
155
|
+
const targetWorkspaceId = opts.workspaceId || config.WORKSPACE_ID
|
|
156
|
+
if (targetWorkspaceId) {
|
|
157
|
+
payload.workspace_id = targetWorkspaceId
|
|
158
|
+
}
|
|
159
|
+
|
|
143
160
|
return api.request('/skills', {
|
|
144
161
|
method: 'POST',
|
|
145
|
-
body: JSON.stringify(
|
|
146
|
-
name: metadata.name || name,
|
|
147
|
-
display_name: metadata.display_name || metadata.name || name,
|
|
148
|
-
description: metadata.description || '',
|
|
149
|
-
content: rawContent,
|
|
150
|
-
type: metadata.type || 'workflow',
|
|
151
|
-
files,
|
|
152
|
-
}),
|
|
162
|
+
body: JSON.stringify(payload),
|
|
153
163
|
})
|
|
154
164
|
}
|
|
155
165
|
|
|
@@ -204,6 +214,7 @@ async function skillRoutes(fastify, opts) {
|
|
|
204
214
|
})
|
|
205
215
|
|
|
206
216
|
// Push local skill to HQ
|
|
217
|
+
// Optional body: { workspace_id?: string } — overrides WORKSPACE_ID env var
|
|
207
218
|
fastify.post('/api/skills/push/:name', async (request, reply) => {
|
|
208
219
|
if (!verifyToken(request)) {
|
|
209
220
|
reply.code(401)
|
|
@@ -216,16 +227,17 @@ async function skillRoutes(fastify, opts) {
|
|
|
216
227
|
}
|
|
217
228
|
|
|
218
229
|
const { name } = request.params
|
|
230
|
+
const { workspace_id } = request.body || {}
|
|
219
231
|
|
|
220
232
|
if (!name || !/^[a-z0-9-]+$/.test(name)) {
|
|
221
233
|
reply.code(400)
|
|
222
234
|
return { success: false, error: 'Invalid skill name' }
|
|
223
235
|
}
|
|
224
236
|
|
|
225
|
-
console.log(`[Skills] Pushing skill to HQ: ${name}`)
|
|
237
|
+
console.log(`[Skills] Pushing skill to HQ: ${name}${workspace_id ? ` (workspace: ${workspace_id})` : ''}`)
|
|
226
238
|
|
|
227
239
|
try {
|
|
228
|
-
const result = await pushSkillToHQ(name)
|
|
240
|
+
const result = await pushSkillToHQ(name, { workspaceId: workspace_id })
|
|
229
241
|
console.log(`[Skills] Skill pushed to HQ: ${name}`)
|
|
230
242
|
return { success: true, ...result }
|
|
231
243
|
} catch (error) {
|
|
@@ -254,13 +266,17 @@ async function skillRoutes(fastify, opts) {
|
|
|
254
266
|
return { success: false, error: 'Invalid skill name' }
|
|
255
267
|
}
|
|
256
268
|
|
|
257
|
-
// Forward template vars query
|
|
269
|
+
// Forward template vars and workspace_id query params to HQ
|
|
258
270
|
const vars = request.query.vars || ''
|
|
259
|
-
const
|
|
260
|
-
|
|
271
|
+
const fetchWorkspaceId = request.query.workspace_id || config.WORKSPACE_ID || ''
|
|
272
|
+
const queryParams = new URLSearchParams()
|
|
273
|
+
if (vars) queryParams.set('vars', vars)
|
|
274
|
+
if (fetchWorkspaceId) queryParams.set('workspace_id', fetchWorkspaceId)
|
|
275
|
+
const queryString = queryParams.toString() ? `?${queryParams.toString()}` : ''
|
|
276
|
+
console.log(`[Skills] Fetching skill from HQ: ${name}${vars ? ' (with template vars)' : ''}${fetchWorkspaceId ? ` (workspace: ${fetchWorkspaceId})` : ''}`)
|
|
261
277
|
|
|
262
278
|
try {
|
|
263
|
-
// Fetch from HQ (with optional template variable expansion)
|
|
279
|
+
// Fetch from HQ (with optional template variable expansion and workspace scoping)
|
|
264
280
|
const skill = await api.request(`/skills/${encodeURIComponent(name)}${queryString}`)
|
|
265
281
|
|
|
266
282
|
// Write to local filesystem using shared helper
|
|
@@ -297,10 +313,12 @@ async function skillRoutes(fastify, opts) {
|
|
|
297
313
|
return { success: false, error: 'HQ not configured' }
|
|
298
314
|
}
|
|
299
315
|
|
|
300
|
-
|
|
316
|
+
const remoteWorkspaceId = request.query.workspace_id || config.WORKSPACE_ID || ''
|
|
317
|
+
const remoteQuery = remoteWorkspaceId ? `?workspace_id=${encodeURIComponent(remoteWorkspaceId)}` : ''
|
|
318
|
+
console.log(`[Skills] Listing remote skills from HQ${remoteWorkspaceId ? ` (workspace: ${remoteWorkspaceId})` : ''}`)
|
|
301
319
|
|
|
302
320
|
try {
|
|
303
|
-
const result = await api.request(
|
|
321
|
+
const result = await api.request(`/skills${remoteQuery}`)
|
|
304
322
|
return result
|
|
305
323
|
} catch (error) {
|
|
306
324
|
console.error(`[Skills] Failed to list remote skills: ${error.message}`)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Store (SQLite)
|
|
3
|
+
* Caches workspace memberships synced from HQ via heartbeat.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getDb } = require('../db')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Upsert all workspaces (replace entire cache with latest from HQ).
|
|
10
|
+
* @param {Array<{ id: string, name: string, slug: string }>} workspaces
|
|
11
|
+
*/
|
|
12
|
+
function upsertAll(workspaces) {
|
|
13
|
+
const db = getDb()
|
|
14
|
+
const upsert = db.prepare(
|
|
15
|
+
'INSERT INTO workspaces (id, name, slug, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name=excluded.name, slug=excluded.slug, updated_at=excluded.updated_at'
|
|
16
|
+
)
|
|
17
|
+
const deleteAll = db.prepare('DELETE FROM workspaces')
|
|
18
|
+
const tx = db.transaction((items) => {
|
|
19
|
+
deleteAll.run()
|
|
20
|
+
const now = Date.now()
|
|
21
|
+
for (const ws of items) {
|
|
22
|
+
upsert.run(ws.id, ws.name, ws.slug, now)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
tx(workspaces || [])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* List all cached workspaces.
|
|
30
|
+
* @returns {Array<{ id: string, name: string, slug: string, updated_at: number }>}
|
|
31
|
+
*/
|
|
32
|
+
function list() {
|
|
33
|
+
const db = getDb()
|
|
34
|
+
return db.prepare('SELECT * FROM workspaces ORDER BY name').all()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get a workspace by ID.
|
|
39
|
+
* @param {string} id
|
|
40
|
+
* @returns {{ id: string, name: string, slug: string, updated_at: number } | undefined}
|
|
41
|
+
*/
|
|
42
|
+
function getById(id) {
|
|
43
|
+
if (!id) return undefined
|
|
44
|
+
const db = getDb()
|
|
45
|
+
return db.prepare('SELECT * FROM workspaces WHERE id = ?').get(id)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { upsertAll, list, getById }
|
package/docs/api-reference.md
CHANGED
|
@@ -17,6 +17,21 @@ Authentication: `Authorization: Bearer $API_TOKEN` header (except where noted).
|
|
|
17
17
|
| GET | `/api/status` | Agent status, uptime, version, HQ connection |
|
|
18
18
|
| POST | `/api/status` | Update status. Body: `{status, current_task}` |
|
|
19
19
|
|
|
20
|
+
### Workspaces
|
|
21
|
+
|
|
22
|
+
ミニオンは複数のワークスペースに所属できる。所属ワークスペースはハートビートレスポンスで自動同期される。
|
|
23
|
+
|
|
24
|
+
| Method | HQ Endpoint | Description |
|
|
25
|
+
|--------|-------------|-------------|
|
|
26
|
+
| GET | `/api/minion/workspaces` | 所属ワークスペース一覧を取得 |
|
|
27
|
+
|
|
28
|
+
ハートビートレスポンスにも `workspaces` 配列が含まれ、30秒ごとに自動同期される。
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# hq CLIで所属ワークスペースを確認
|
|
32
|
+
hq list workspaces
|
|
33
|
+
```
|
|
34
|
+
|
|
20
35
|
### Skills
|
|
21
36
|
|
|
22
37
|
| Method | Endpoint | Description |
|
|
@@ -27,6 +42,16 @@ Authentication: `Authorization: Bearer $API_TOKEN` header (except where noted).
|
|
|
27
42
|
| POST | `/api/skills/fetch/:name` | Fetch skill from HQ and deploy locally |
|
|
28
43
|
| GET | `/api/skills/remote` | List skills on HQ |
|
|
29
44
|
|
|
45
|
+
#### ワークスペーススコープ
|
|
46
|
+
|
|
47
|
+
スキルはワークスペース単位でスコープされる。`WORKSPACE_ID` 環境変数を設定するか、リクエストごとに `workspace_id` を指定することで、プッシュ先・取得元のワークスペースを制御できる。未指定の場合、HQがミニオンの所属ワークスペースから自動解決する。
|
|
48
|
+
|
|
49
|
+
| エンドポイント | パラメータ | 指定方法 |
|
|
50
|
+
|--------------|-----------|---------|
|
|
51
|
+
| `POST /api/skills/push/:name` | `workspace_id` | リクエストボディ |
|
|
52
|
+
| `POST /api/skills/fetch/:name` | `workspace_id` | クエリパラメータ |
|
|
53
|
+
| `GET /api/skills/remote` | `workspace_id` | クエリパラメータ |
|
|
54
|
+
|
|
30
55
|
### Workflows
|
|
31
56
|
|
|
32
57
|
| Method | Endpoint | Description |
|
package/linux/bin/hq
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# hq fetch project-context <id> - Get project context (shared Markdown document)
|
|
17
17
|
# hq fetch dag-workflow <id> - Get DAG workflow details (graph, version)
|
|
18
18
|
# hq fetch dag-execution <id> - Get DAG execution details (nodes, status)
|
|
19
|
+
# hq list workspaces - List workspaces this minion belongs to
|
|
19
20
|
# hq create dag-workflow <body.json> - Create a DAG workflow (PM only). Body: {project_id, name, graph?, ...}
|
|
20
21
|
# hq put dag-workflow <id> <body.json> - Update DAG workflow draft (PM only). Body: {graph?, content?, ...}
|
|
21
22
|
# hq publish dag-workflow <id> - Publish the draft as a new version (PM only, validated)
|
|
@@ -144,6 +145,7 @@ print_usage() {
|
|
|
144
145
|
echo " hq fetch project-context <id> - Get project context" >&2
|
|
145
146
|
echo " hq fetch dag-workflow <id> - Get DAG workflow details" >&2
|
|
146
147
|
echo " hq fetch dag-execution <id> - Get DAG execution details" >&2
|
|
148
|
+
echo " hq list workspaces - List workspaces this minion belongs to" >&2
|
|
147
149
|
echo " hq create dag-workflow <body.json> - Create a DAG workflow (PM only)" >&2
|
|
148
150
|
echo " hq put dag-workflow <id> <body.json> - Update DAG workflow draft (PM only)" >&2
|
|
149
151
|
echo " hq publish dag-workflow <id> - Publish the draft as a new version (PM only)" >&2
|
|
@@ -192,6 +194,20 @@ case "${1:-}" in
|
|
|
192
194
|
esac
|
|
193
195
|
;;
|
|
194
196
|
|
|
197
|
+
list)
|
|
198
|
+
resource="${2:-}"
|
|
199
|
+
case "$resource" in
|
|
200
|
+
workspaces)
|
|
201
|
+
fetch_resource "$BASE_URL/workspaces"
|
|
202
|
+
;;
|
|
203
|
+
*)
|
|
204
|
+
echo "Unknown list resource: $resource" >&2
|
|
205
|
+
echo "Usage: hq list workspaces" >&2
|
|
206
|
+
exit 1
|
|
207
|
+
;;
|
|
208
|
+
esac
|
|
209
|
+
;;
|
|
210
|
+
|
|
195
211
|
create)
|
|
196
212
|
resource="${2:-}"
|
|
197
213
|
case "$resource" in
|
package/linux/routes/chat.js
CHANGED
|
@@ -52,8 +52,8 @@ async function chatRoutes(fastify) {
|
|
|
52
52
|
|
|
53
53
|
const workspaceId = workspace_id || null
|
|
54
54
|
|
|
55
|
-
// Build prompt — add memory context on new sessions + page context
|
|
56
|
-
const prompt = await buildContextPrefix(message, context, session_id)
|
|
55
|
+
// Build prompt — add memory context on new sessions + page context + workspace
|
|
56
|
+
const prompt = await buildContextPrefix(message, context, session_id, workspaceId)
|
|
57
57
|
|
|
58
58
|
// Store user message
|
|
59
59
|
const currentSessionId = session_id || null
|
|
@@ -253,9 +253,22 @@ ${indexed}`
|
|
|
253
253
|
* On new sessions (no session_id), injects minion memory + recent daily logs.
|
|
254
254
|
* No conversation history injection — Claude CLI handles that via --resume.
|
|
255
255
|
*/
|
|
256
|
-
async function buildContextPrefix(message, context, sessionId) {
|
|
256
|
+
async function buildContextPrefix(message, context, sessionId, workspaceId) {
|
|
257
257
|
const parts = []
|
|
258
258
|
|
|
259
|
+
// Inject workspace context so Claude Code knows which workspace it's operating in
|
|
260
|
+
if (workspaceId) {
|
|
261
|
+
const workspaceStore = require('../../core/stores/workspace-store')
|
|
262
|
+
const ws = workspaceStore.getById(workspaceId)
|
|
263
|
+
if (ws) {
|
|
264
|
+
parts.push(
|
|
265
|
+
`[現在のワークスペース] ${ws.name} (ID: ${ws.id})`,
|
|
266
|
+
'スキル操作やプロジェクト参照はこのワークスペースのスコープで行われます。',
|
|
267
|
+
''
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
259
272
|
// Re-inject unfinished todos tied to this session. This is how we survive
|
|
260
273
|
// context compaction: even if Claude forgot the plan, the outstanding
|
|
261
274
|
// todos are re-shown on the next turn. Todos past MAX_INJECTION_COUNT are
|
package/linux/server.js
CHANGED
|
@@ -378,15 +378,23 @@ async function start() {
|
|
|
378
378
|
const { currentTask } = getStatus()
|
|
379
379
|
const todoStore = require('../core/stores/todo-store')
|
|
380
380
|
const getTodoSummary = () => { try { return todoStore.getSummary() } catch { return null } }
|
|
381
|
-
|
|
381
|
+
const workspaceStore = require('../core/stores/workspace-store')
|
|
382
|
+
const syncWorkspaces = (response) => {
|
|
383
|
+
if (response && Array.isArray(response.workspaces)) {
|
|
384
|
+
workspaceStore.upsertAll(response.workspaces)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
sendHeartbeat({ status: 'online', current_task: currentTask, running_tasks: runningTasks.getAll(), config_warnings: getConfigWarnings(), todo_summary: getTodoSummary(), version }).then(syncWorkspaces).catch(err => {
|
|
382
389
|
console.error('[Heartbeat] Initial heartbeat failed:', err.message)
|
|
383
390
|
})
|
|
384
391
|
|
|
385
392
|
// Start periodic heartbeat
|
|
386
393
|
heartbeatTimer = setInterval(() => {
|
|
387
394
|
const { currentStatus, currentTask } = getStatus()
|
|
388
|
-
sendHeartbeat({ status: currentStatus, current_task: currentTask, running_tasks: runningTasks.getAll(), config_warnings: getConfigWarnings(), todo_summary: getTodoSummary(), version }).then(
|
|
395
|
+
sendHeartbeat({ status: currentStatus, current_task: currentTask, running_tasks: runningTasks.getAll(), config_warnings: getConfigWarnings(), todo_summary: getTodoSummary(), version }).then(response => {
|
|
389
396
|
lastBeatAt = new Date().toISOString()
|
|
397
|
+
syncWorkspaces(response)
|
|
390
398
|
}).catch(err => {
|
|
391
399
|
console.error('[Heartbeat] Periodic heartbeat failed:', err.message)
|
|
392
400
|
})
|
package/package.json
CHANGED
package/rules/core.md
CHANGED
|
@@ -29,6 +29,7 @@ Minion
|
|
|
29
29
|
- **Workflow**: プロジェクトスコープ。線形パイプライン形式のバージョン管理ワークフロー。ミニオンAPIで push/fetch 可能。
|
|
30
30
|
- **DAG Workflow**: プロジェクトスコープ。ノード/エッジで依存関係を表現する新方式。fan-out / join / conditional / transform / review をサポート。作成・編集はHQダッシュボードのみ、ミニオンは `dag-step-poller` で自動実行。詳細は `~/.minion/docs/api-reference.md` の「DAG Workflows」と `~/.minion/docs/task-guides.md` の「DAG ワークフロー」を参照。
|
|
31
31
|
- **Routine**: ミニオンスコープ。ミニオンローカルの定期タスク。
|
|
32
|
+
- **Workspace**: ミニオンは複数のワークスペースに所属でき、スキルやプロジェクトはワークスペース単位でスコープされる。チャットセッションもワークスペース別に分離される。所属ワークスペースはハートビートで自動同期され、`hq list workspaces` で確認できる。
|
|
32
33
|
- ミニオンは複数プロジェクトに `pm`、`engineer`、`accountant` として参加できる。
|
|
33
34
|
|
|
34
35
|
## Email
|
package/win/bin/hq.ps1
CHANGED
|
@@ -128,6 +128,7 @@ function Show-Usage {
|
|
|
128
128
|
Write-Host " hq fetch project-context <id> - Get project context"
|
|
129
129
|
Write-Host " hq fetch dag-workflow <id> - Get DAG workflow details"
|
|
130
130
|
Write-Host " hq fetch dag-execution <id> - Get DAG execution details"
|
|
131
|
+
Write-Host " hq list workspaces - List workspaces this minion belongs to"
|
|
131
132
|
Write-Host " hq create dag-workflow <body.json> - Create a DAG workflow (PM only)"
|
|
132
133
|
Write-Host " hq put dag-workflow <id> <body.json> - Update DAG workflow draft (PM only)"
|
|
133
134
|
Write-Host " hq publish dag-workflow <id> - Publish the draft as a new version (PM only)"
|
|
@@ -167,6 +168,18 @@ switch ($Command) {
|
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
'list' {
|
|
172
|
+
$Resource = $Arg1
|
|
173
|
+
switch ($Resource) {
|
|
174
|
+
'workspaces' { Invoke-HqApi "$BaseUrl/workspaces" }
|
|
175
|
+
default {
|
|
176
|
+
Write-Error "Unknown list resource: $Resource"
|
|
177
|
+
Write-Error "Usage: hq list workspaces"
|
|
178
|
+
exit 1
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
'create' {
|
|
171
184
|
$Resource = $Arg1
|
|
172
185
|
switch ($Resource) {
|
package/win/routes/chat.js
CHANGED
|
@@ -125,7 +125,7 @@ async function chatRoutes(fastify) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const workspaceId = workspace_id || null
|
|
128
|
-
const prompt = await buildContextPrefix(message, context, session_id)
|
|
128
|
+
const prompt = await buildContextPrefix(message, context, session_id, workspaceId)
|
|
129
129
|
const currentSessionId = session_id || null
|
|
130
130
|
|
|
131
131
|
if (currentSessionId) {
|
|
@@ -317,9 +317,22 @@ ${indexed}`
|
|
|
317
317
|
})
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
async function buildContextPrefix(message, context, sessionId) {
|
|
320
|
+
async function buildContextPrefix(message, context, sessionId, workspaceId) {
|
|
321
321
|
const parts = []
|
|
322
322
|
|
|
323
|
+
// Inject workspace context so Claude Code knows which workspace it's operating in
|
|
324
|
+
if (workspaceId) {
|
|
325
|
+
const workspaceStore = require('../../core/stores/workspace-store')
|
|
326
|
+
const ws = workspaceStore.getById(workspaceId)
|
|
327
|
+
if (ws) {
|
|
328
|
+
parts.push(
|
|
329
|
+
`[現在のワークスペース] ${ws.name} (ID: ${ws.id})`,
|
|
330
|
+
'スキル操作やプロジェクト参照はこのワークスペースのスコープで行われます。',
|
|
331
|
+
''
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
323
336
|
// Re-inject unfinished todos tied to this session. Survives context
|
|
324
337
|
// compaction — Claude sees outstanding todos again on the next turn.
|
|
325
338
|
// Skipped past MAX_INJECTION_COUNT to prevent infinite loops.
|
package/win/server.js
CHANGED
|
@@ -345,15 +345,23 @@ async function start() {
|
|
|
345
345
|
const { currentTask } = getStatus()
|
|
346
346
|
const todoStore = require('../core/stores/todo-store')
|
|
347
347
|
const getTodoSummary = () => { try { return todoStore.getSummary() } catch { return null } }
|
|
348
|
-
|
|
348
|
+
const workspaceStore = require('../core/stores/workspace-store')
|
|
349
|
+
const syncWorkspaces = (response) => {
|
|
350
|
+
if (response && Array.isArray(response.workspaces)) {
|
|
351
|
+
workspaceStore.upsertAll(response.workspaces)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
sendHeartbeat({ status: 'online', current_task: currentTask, config_warnings: getConfigWarnings(), todo_summary: getTodoSummary(), version }).then(syncWorkspaces).catch(err => {
|
|
349
356
|
console.error('[Heartbeat] Initial heartbeat failed:', err.message)
|
|
350
357
|
})
|
|
351
358
|
|
|
352
359
|
// Start periodic heartbeat
|
|
353
360
|
heartbeatTimer = setInterval(() => {
|
|
354
361
|
const { currentStatus, currentTask } = getStatus()
|
|
355
|
-
sendHeartbeat({ status: currentStatus, current_task: currentTask, config_warnings: getConfigWarnings(), todo_summary: getTodoSummary(), version }).then(
|
|
362
|
+
sendHeartbeat({ status: currentStatus, current_task: currentTask, config_warnings: getConfigWarnings(), todo_summary: getTodoSummary(), version }).then(response => {
|
|
356
363
|
lastBeatAt = new Date().toISOString()
|
|
364
|
+
syncWorkspaces(response)
|
|
357
365
|
}).catch(err => {
|
|
358
366
|
console.error('[Heartbeat] Periodic heartbeat failed:', err.message)
|
|
359
367
|
})
|