@geekbeer/minion 3.36.0 → 3.40.1
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/db/helpers.js +18 -0
- package/core/db/index.js +146 -0
- package/core/db/migrations/000_initial_schema.js +157 -0
- package/core/db/migrations/001_fts_trigram.js +78 -0
- package/core/db/migrations/002_emails_fts.js +41 -0
- package/core/db/migrations/003_memories_project_id.js +17 -0
- package/core/db/migrations/004_chat_sessions_workspace.js +18 -0
- package/core/db/migrations/005_todos_session_injection.js +19 -0
- package/core/db/migrations/006_daily_logs_workspace.js +69 -0
- package/core/db/migrations/007_workspace_scoping.js +29 -0
- package/core/db/migrations/008_todos_workspace.js +22 -0
- package/core/db/migrations/index.js +41 -0
- package/core/lib/config-warnings.js +16 -8
- package/core/lib/end-of-day.js +30 -14
- package/core/lib/reflection-scheduler.js +23 -9
- package/core/lib/thread-watcher.js +3 -0
- package/core/routes/daily-logs.js +64 -27
- package/core/routes/routines.js +6 -2
- package/core/routes/skills.js +4 -0
- package/core/routes/todos.js +20 -7
- package/core/routes/workflows.js +17 -7
- package/core/stores/daily-log-store.js +61 -30
- package/core/stores/execution-store.js +40 -18
- package/core/stores/routine-store.js +32 -14
- package/core/stores/todo-store.js +37 -10
- package/core/stores/workflow-store.js +34 -13
- package/docs/api-reference.md +66 -25
- package/linux/routes/chat.js +14 -9
- package/linux/routes/directives.js +4 -0
- package/linux/routine-runner.js +2 -0
- package/linux/workflow-runner.js +2 -0
- package/package.json +4 -2
- package/rules/core.md +1 -0
- package/scripts/new-migration.js +53 -0
- package/win/routes/chat.js +14 -9
- package/win/routes/directives.js +4 -0
- package/win/routine-runner.js +2 -0
- package/win/workflow-runner.js +2 -0
- package/core/db.js +0 -583
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Warning categories:
|
|
9
9
|
* - llm_not_authenticated: No LLM service is authenticated (Claude/Gemini/Codex)
|
|
10
|
-
* -
|
|
10
|
+
* - llm_plugin_primary_not_set: LLM plugin `primary` is not selected (required for workflow/routine execution)
|
|
11
|
+
* - llm_plugin_primary_not_enabled: `primary` plugin is not present in `enabled` list (inconsistent config)
|
|
11
12
|
* - tunnel_not_running: Cloudflare tunnel is not running (required for VNC/terminal access)
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
const { execSync } = require('child_process')
|
|
15
16
|
const { config } = require('../config')
|
|
16
|
-
const { getLlmServices
|
|
17
|
+
const { getLlmServices } = require('./llm-checker')
|
|
18
|
+
const { readConfig: readLlmPluginConfig } = require('../llm-plugins/registry')
|
|
17
19
|
const { IS_WINDOWS } = require('./platform')
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -59,15 +61,21 @@ function getConfigWarnings() {
|
|
|
59
61
|
if (!hasAuthenticatedLlm) {
|
|
60
62
|
warnings.push({
|
|
61
63
|
code: 'llm_not_authenticated',
|
|
62
|
-
message: 'LLM
|
|
64
|
+
message: 'LLMが未認証です。Claude/Gemini/Codexのいずれかを認証してください',
|
|
63
65
|
})
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
// Check
|
|
67
|
-
|
|
68
|
+
// Check LLM plugin selection
|
|
69
|
+
const pluginCfg = readLlmPluginConfig()
|
|
70
|
+
if (!pluginCfg.primary) {
|
|
68
71
|
warnings.push({
|
|
69
|
-
code: '
|
|
70
|
-
message: '
|
|
72
|
+
code: 'llm_plugin_primary_not_set',
|
|
73
|
+
message: 'LLMプラグインのprimaryが未設定です。ワークフロー/ルーティン実行に必要です',
|
|
74
|
+
})
|
|
75
|
+
} else if (!pluginCfg.enabled.includes(pluginCfg.primary)) {
|
|
76
|
+
warnings.push({
|
|
77
|
+
code: 'llm_plugin_primary_not_enabled',
|
|
78
|
+
message: `primaryプラグイン "${pluginCfg.primary}" がenabledに含まれていません`,
|
|
71
79
|
})
|
|
72
80
|
}
|
|
73
81
|
|
|
@@ -75,7 +83,7 @@ function getConfigWarnings() {
|
|
|
75
83
|
if (config.HQ_URL && !isTunnelRunning()) {
|
|
76
84
|
warnings.push({
|
|
77
85
|
code: 'tunnel_not_running',
|
|
78
|
-
message: 'Cloudflare
|
|
86
|
+
message: 'Cloudflareトンネルが停止しています。VNC/ターミナル接続ができません',
|
|
79
87
|
})
|
|
80
88
|
}
|
|
81
89
|
|
package/core/lib/end-of-day.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared end-of-day processing logic
|
|
3
3
|
*
|
|
4
|
-
* Extracts daily conversation summary and long-term learnings from the chat session
|
|
5
|
-
* Used by both the chat route (manual trigger) and the
|
|
4
|
+
* Extracts daily conversation summary and long-term learnings from the chat session
|
|
5
|
+
* of a specific workspace. Used by both the chat route (manual trigger) and the
|
|
6
|
+
* reflection scheduler (automatic, loops over all workspaces).
|
|
6
7
|
*
|
|
7
8
|
* @module core/lib/end-of-day
|
|
8
9
|
*/
|
|
@@ -10,19 +11,35 @@
|
|
|
10
11
|
const chatStore = require('../stores/chat-store')
|
|
11
12
|
const memoryStore = require('../stores/memory-store')
|
|
12
13
|
const dailyLogStore = require('../stores/daily-log-store')
|
|
14
|
+
const workspaceStore = require('../stores/workspace-store')
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
* Run end-of-day processing: generate daily log + extract
|
|
17
|
+
* Run end-of-day processing for a single workspace: generate daily log + extract
|
|
18
|
+
* memories from the workspace's chat session. If no conversation exists, a stub
|
|
19
|
+
* log is saved so that the absence of activity is itself a record.
|
|
16
20
|
*
|
|
17
21
|
* @param {Object} opts
|
|
22
|
+
* @param {string} opts.workspaceId - Workspace to process ('' = unassigned)
|
|
18
23
|
* @param {(prompt: string) => Promise<string>} opts.runQuickLlmCall - Platform-specific LLM call function
|
|
19
24
|
* @param {boolean} [opts.clearSession=false] - Whether to clear the chat session after processing
|
|
20
|
-
* @returns {Promise<{ daily_log: string|null, memory_entries_added: number }>}
|
|
25
|
+
* @returns {Promise<{ daily_log: string|null, memory_entries_added: number, had_conversation: boolean }>}
|
|
21
26
|
*/
|
|
22
|
-
async function runEndOfDay({ runQuickLlmCall, clearSession = false }) {
|
|
23
|
-
|
|
27
|
+
async function runEndOfDay({ workspaceId, runQuickLlmCall, clearSession = false }) {
|
|
28
|
+
if (workspaceId == null) {
|
|
29
|
+
throw new Error('workspaceId is required for end-of-day processing')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const today = new Date().toISOString().split('T')[0]
|
|
33
|
+
const session = await chatStore.load(workspaceId)
|
|
34
|
+
|
|
35
|
+
// No conversation — record a stub so the idle day is still logged.
|
|
24
36
|
if (!session || session.messages.length === 0) {
|
|
25
|
-
|
|
37
|
+
const ws = workspaceId ? workspaceStore.getById(workspaceId) : null
|
|
38
|
+
const wsLabel = ws ? `${ws.name}` : (workspaceId ? `workspace:${workspaceId}` : '未所属')
|
|
39
|
+
const stub = `# ${today} (${wsLabel})\n\n本日、このワークスペースでの会話はありませんでした。`
|
|
40
|
+
await dailyLogStore.saveLog(workspaceId, today, stub)
|
|
41
|
+
console.log(`[EndOfDay] No conversation in workspace=${workspaceId || '(unassigned)'}, saved stub log for ${today}`)
|
|
42
|
+
return { daily_log: today, memory_entries_added: 0, had_conversation: false }
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
// Build conversation text for summarization (last 50 messages)
|
|
@@ -52,7 +69,6 @@ ${conversationText}
|
|
|
52
69
|
|
|
53
70
|
JSON形式のみで回答してください(\`\`\`jsonブロックは不要):`
|
|
54
71
|
|
|
55
|
-
const today = new Date().toISOString().split('T')[0]
|
|
56
72
|
let dailyLog = null
|
|
57
73
|
let memoriesAdded = 0
|
|
58
74
|
|
|
@@ -73,8 +89,8 @@ JSON形式のみで回答してください(\`\`\`jsonブロックは不要)
|
|
|
73
89
|
// Save daily log
|
|
74
90
|
if (parsed.daily_summary) {
|
|
75
91
|
dailyLog = parsed.daily_summary
|
|
76
|
-
await dailyLogStore.saveLog(today, dailyLog)
|
|
77
|
-
console.log(`[EndOfDay] Saved daily log for ${today}`)
|
|
92
|
+
await dailyLogStore.saveLog(workspaceId, today, dailyLog)
|
|
93
|
+
console.log(`[EndOfDay] Saved daily log for ${today} workspace=${workspaceId || '(unassigned)'}`)
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
// Save learnings to memory
|
|
@@ -102,15 +118,15 @@ JSON形式のみで回答してください(\`\`\`jsonブロックは不要)
|
|
|
102
118
|
.map(m => `**${m.role === 'user' ? 'ユーザー' : 'アシスタント'}**: ${m.content.substring(0, 300)}`)
|
|
103
119
|
.join('\n\n')
|
|
104
120
|
dailyLog = `# ${today} 会話ログ\n\n${fallback}`
|
|
105
|
-
await dailyLogStore.saveLog(today, dailyLog)
|
|
121
|
+
await dailyLogStore.saveLog(workspaceId, today, dailyLog)
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
if (clearSession) {
|
|
109
|
-
await chatStore.clear()
|
|
110
|
-
console.log(
|
|
125
|
+
await chatStore.clear(workspaceId)
|
|
126
|
+
console.log(`[EndOfDay] Session cleared for workspace=${workspaceId || '(unassigned)'}`)
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
return { daily_log: today, memory_entries_added: memoriesAdded }
|
|
129
|
+
return { daily_log: today, memory_entries_added: memoriesAdded, had_conversation: true }
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
module.exports = { runEndOfDay }
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const { Cron } = require('croner')
|
|
20
20
|
const { config } = require('../config')
|
|
21
21
|
const { runEndOfDay } = require('./end-of-day')
|
|
22
|
+
const workspaceStore = require('../stores/workspace-store')
|
|
22
23
|
|
|
23
24
|
/** @type {import('croner').Cron | null} */
|
|
24
25
|
let cronJob = null
|
|
@@ -134,16 +135,29 @@ async function runReflection() {
|
|
|
134
135
|
console.log('[ReflectionScheduler] Starting daily reflection...')
|
|
135
136
|
|
|
136
137
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
// Always include '' (unassigned) so pre-workspace-scoping legacy conversations
|
|
139
|
+
// (or minions not yet synced with HQ) are still processed.
|
|
140
|
+
const workspaces = workspaceStore.list()
|
|
141
|
+
const workspaceIds = ['', ...workspaces.map(w => w.id)]
|
|
142
|
+
|
|
143
|
+
let totalMemories = 0
|
|
144
|
+
let totalLogs = 0
|
|
145
|
+
|
|
146
|
+
for (const workspaceId of workspaceIds) {
|
|
147
|
+
try {
|
|
148
|
+
const result = await runEndOfDay({
|
|
149
|
+
workspaceId,
|
|
150
|
+
runQuickLlmCall: llmCallFn,
|
|
151
|
+
clearSession: false,
|
|
152
|
+
})
|
|
153
|
+
if (result.daily_log) totalLogs++
|
|
154
|
+
totalMemories += result.memory_entries_added
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error(`[ReflectionScheduler] Workspace ${workspaceId || '(unassigned)'} failed: ${err.message}`)
|
|
157
|
+
}
|
|
146
158
|
}
|
|
159
|
+
|
|
160
|
+
console.log(`[ReflectionScheduler] Completed: ${totalLogs} logs across ${workspaceIds.length} workspaces, ${totalMemories} memories`)
|
|
147
161
|
} catch (err) {
|
|
148
162
|
console.error(`[ReflectionScheduler] Failed: ${err.message}`)
|
|
149
163
|
} finally {
|
|
@@ -310,6 +310,8 @@ ${roleGuidance}
|
|
|
310
310
|
const linkedProjectId = (threadSummary.linked_projects && threadSummary.linked_projects.length > 0)
|
|
311
311
|
? threadSummary.linked_projects[0].id
|
|
312
312
|
: null
|
|
313
|
+
// HQ resolves the thread's workspace_id on delivery (see
|
|
314
|
+
// src/app/api/minion/threads/route.ts). Fall back to '' if missing.
|
|
313
315
|
todoStore.add({
|
|
314
316
|
title: parsed.todo.title,
|
|
315
317
|
priority: parsed.todo.priority || 'normal',
|
|
@@ -317,6 +319,7 @@ ${roleGuidance}
|
|
|
317
319
|
source_type: 'thread',
|
|
318
320
|
source_id: threadSummary.id,
|
|
319
321
|
project_id: linkedProjectId,
|
|
322
|
+
workspace_id: threadSummary.workspace_id || '',
|
|
320
323
|
})
|
|
321
324
|
console.log(`[ThreadWatcher] Created TODO from thread: "${parsed.todo.title}"`)
|
|
322
325
|
} catch (err) {
|
|
@@ -1,40 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Daily log endpoints
|
|
3
3
|
*
|
|
4
|
-
* Provides CRUD + search for daily conversation summaries.
|
|
4
|
+
* Provides CRUD + search for daily conversation summaries, scoped by workspace.
|
|
5
5
|
* Logs are stored in SQLite with FTS5 full-text search.
|
|
6
6
|
*
|
|
7
|
+
* All endpoints require a `workspace_id` identifying the scope. Pass `''`
|
|
8
|
+
* (empty string) to address legacy / unassigned logs.
|
|
9
|
+
*
|
|
7
10
|
* Endpoints:
|
|
8
|
-
* GET /api/daily-logs
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* GET /api/daily-logs?workspace_id=... - List logs
|
|
12
|
+
* GET /api/daily-logs?workspace_id=...&search=keyword - FTS5 search
|
|
13
|
+
* POST /api/daily-logs - Create (body: workspace_id, date, content)
|
|
14
|
+
* GET /api/daily-logs/:date?workspace_id=... - Get a specific day's log
|
|
15
|
+
* PUT /api/daily-logs/:date - Update (body: workspace_id, content)
|
|
16
|
+
* DELETE /api/daily-logs/:date?workspace_id=... - Delete
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
19
|
const { verifyToken } = require('../lib/auth')
|
|
16
20
|
const dailyLogStore = require('../stores/daily-log-store')
|
|
17
21
|
|
|
22
|
+
function pickWorkspaceId(raw) {
|
|
23
|
+
// Accept '' (explicit unassigned) but reject undefined/null.
|
|
24
|
+
if (raw === '' || typeof raw === 'string') return raw
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Register daily log routes as Fastify plugin
|
|
20
30
|
* @param {import('fastify').FastifyInstance} fastify
|
|
21
31
|
*/
|
|
22
32
|
async function dailyLogRoutes(fastify) {
|
|
23
33
|
|
|
24
|
-
// GET /api/daily-logs - List or search logs
|
|
34
|
+
// GET /api/daily-logs - List or search logs (scoped)
|
|
25
35
|
fastify.get('/api/daily-logs', async (request, reply) => {
|
|
26
36
|
if (!verifyToken(request)) {
|
|
27
37
|
reply.code(401)
|
|
28
38
|
return { success: false, error: 'Unauthorized' }
|
|
29
39
|
}
|
|
30
40
|
|
|
41
|
+
const workspaceId = pickWorkspaceId(request.query?.workspace_id)
|
|
42
|
+
if (workspaceId === null) {
|
|
43
|
+
reply.code(400)
|
|
44
|
+
return { success: false, error: 'workspace_id query param is required' }
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
const { search } = request.query || {}
|
|
32
48
|
if (search) {
|
|
33
|
-
const logs = dailyLogStore.searchLogs(search)
|
|
49
|
+
const logs = dailyLogStore.searchLogs(workspaceId, search)
|
|
34
50
|
return { success: true, logs }
|
|
35
51
|
}
|
|
36
52
|
|
|
37
|
-
const logs = dailyLogStore.listLogs()
|
|
53
|
+
const logs = dailyLogStore.listLogs(workspaceId)
|
|
38
54
|
return { success: true, logs }
|
|
39
55
|
})
|
|
40
56
|
|
|
@@ -45,39 +61,50 @@ async function dailyLogRoutes(fastify) {
|
|
|
45
61
|
return { success: false, error: 'Unauthorized' }
|
|
46
62
|
}
|
|
47
63
|
|
|
48
|
-
const { date, content } = request.body || {}
|
|
64
|
+
const { workspace_id, date, content } = request.body || {}
|
|
65
|
+
const workspaceId = pickWorkspaceId(workspace_id)
|
|
66
|
+
if (workspaceId === null) {
|
|
67
|
+
reply.code(400)
|
|
68
|
+
return { success: false, error: 'workspace_id is required' }
|
|
69
|
+
}
|
|
49
70
|
if (!date || !content) {
|
|
50
71
|
reply.code(400)
|
|
51
72
|
return { success: false, error: 'date and content are required' }
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
// Check if log already exists
|
|
55
|
-
const existing = dailyLogStore.loadLog(date)
|
|
75
|
+
// Check if log already exists for this (workspace, date)
|
|
76
|
+
const existing = dailyLogStore.loadLog(workspaceId, date)
|
|
56
77
|
if (existing !== null) {
|
|
57
78
|
reply.code(409)
|
|
58
79
|
return { success: false, error: 'Log already exists for this date. Use PUT to update.' }
|
|
59
80
|
}
|
|
60
81
|
|
|
61
|
-
dailyLogStore.saveLog(date, content)
|
|
62
|
-
console.log(`[DailyLogs] Created log: ${date}`)
|
|
63
|
-
return { success: true, log: { date, content } }
|
|
82
|
+
dailyLogStore.saveLog(workspaceId, date, content)
|
|
83
|
+
console.log(`[DailyLogs] Created log: ${date} workspace=${workspaceId || '(unassigned)'}`)
|
|
84
|
+
return { success: true, log: { workspace_id: workspaceId, date, content } }
|
|
64
85
|
})
|
|
65
86
|
|
|
66
|
-
// GET /api/daily-logs/:date - Get a specific day's log
|
|
87
|
+
// GET /api/daily-logs/:date - Get a specific day's log for a workspace
|
|
67
88
|
fastify.get('/api/daily-logs/:date', async (request, reply) => {
|
|
68
89
|
if (!verifyToken(request)) {
|
|
69
90
|
reply.code(401)
|
|
70
91
|
return { success: false, error: 'Unauthorized' }
|
|
71
92
|
}
|
|
72
93
|
|
|
94
|
+
const workspaceId = pickWorkspaceId(request.query?.workspace_id)
|
|
95
|
+
if (workspaceId === null) {
|
|
96
|
+
reply.code(400)
|
|
97
|
+
return { success: false, error: 'workspace_id query param is required' }
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
const { date } = request.params
|
|
74
|
-
const content = dailyLogStore.loadLog(date)
|
|
101
|
+
const content = dailyLogStore.loadLog(workspaceId, date)
|
|
75
102
|
if (content === null) {
|
|
76
103
|
reply.code(404)
|
|
77
104
|
return { success: false, error: 'Log not found' }
|
|
78
105
|
}
|
|
79
106
|
|
|
80
|
-
return { success: true, log: { date, content } }
|
|
107
|
+
return { success: true, log: { workspace_id: workspaceId, date, content } }
|
|
81
108
|
})
|
|
82
109
|
|
|
83
110
|
// PUT /api/daily-logs/:date - Update a daily log
|
|
@@ -87,23 +114,27 @@ async function dailyLogRoutes(fastify) {
|
|
|
87
114
|
return { success: false, error: 'Unauthorized' }
|
|
88
115
|
}
|
|
89
116
|
|
|
90
|
-
const {
|
|
91
|
-
const
|
|
117
|
+
const { workspace_id, content } = request.body || {}
|
|
118
|
+
const workspaceId = pickWorkspaceId(workspace_id)
|
|
119
|
+
if (workspaceId === null) {
|
|
120
|
+
reply.code(400)
|
|
121
|
+
return { success: false, error: 'workspace_id is required' }
|
|
122
|
+
}
|
|
92
123
|
if (!content) {
|
|
93
124
|
reply.code(400)
|
|
94
125
|
return { success: false, error: 'content is required' }
|
|
95
126
|
}
|
|
96
127
|
|
|
97
|
-
|
|
98
|
-
const existing = dailyLogStore.loadLog(date)
|
|
128
|
+
const { date } = request.params
|
|
129
|
+
const existing = dailyLogStore.loadLog(workspaceId, date)
|
|
99
130
|
if (existing === null) {
|
|
100
131
|
reply.code(404)
|
|
101
132
|
return { success: false, error: 'Log not found' }
|
|
102
133
|
}
|
|
103
134
|
|
|
104
|
-
dailyLogStore.saveLog(date, content)
|
|
105
|
-
console.log(`[DailyLogs] Updated log: ${date}`)
|
|
106
|
-
return { success: true, log: { date, content } }
|
|
135
|
+
dailyLogStore.saveLog(workspaceId, date, content)
|
|
136
|
+
console.log(`[DailyLogs] Updated log: ${date} workspace=${workspaceId || '(unassigned)'}`)
|
|
137
|
+
return { success: true, log: { workspace_id: workspaceId, date, content } }
|
|
107
138
|
})
|
|
108
139
|
|
|
109
140
|
// DELETE /api/daily-logs/:date - Delete a specific day's log
|
|
@@ -113,14 +144,20 @@ async function dailyLogRoutes(fastify) {
|
|
|
113
144
|
return { success: false, error: 'Unauthorized' }
|
|
114
145
|
}
|
|
115
146
|
|
|
147
|
+
const workspaceId = pickWorkspaceId(request.query?.workspace_id)
|
|
148
|
+
if (workspaceId === null) {
|
|
149
|
+
reply.code(400)
|
|
150
|
+
return { success: false, error: 'workspace_id query param is required' }
|
|
151
|
+
}
|
|
152
|
+
|
|
116
153
|
const { date } = request.params
|
|
117
|
-
const deleted = dailyLogStore.deleteLog(date)
|
|
154
|
+
const deleted = dailyLogStore.deleteLog(workspaceId, date)
|
|
118
155
|
if (!deleted) {
|
|
119
156
|
reply.code(404)
|
|
120
157
|
return { success: false, error: 'Log not found' }
|
|
121
158
|
}
|
|
122
159
|
|
|
123
|
-
console.log(`[DailyLogs] Deleted log: ${date}`)
|
|
160
|
+
console.log(`[DailyLogs] Deleted log: ${date} workspace=${workspaceId || '(unassigned)'}`)
|
|
124
161
|
return { success: true }
|
|
125
162
|
})
|
|
126
163
|
}
|
package/core/routes/routines.js
CHANGED
|
@@ -69,14 +69,18 @@ async function routineRoutes(fastify, opts) {
|
|
|
69
69
|
}
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
-
// List
|
|
72
|
+
// List routines with their current status.
|
|
73
|
+
// Optional ?workspace_id= filter: when provided, scope to that workspace;
|
|
74
|
+
// when absent, return all.
|
|
73
75
|
fastify.get('/api/routines', async (request, reply) => {
|
|
74
76
|
if (!verifyToken(request)) {
|
|
75
77
|
reply.code(401)
|
|
76
78
|
return { success: false, error: 'Unauthorized' }
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
const
|
|
81
|
+
const rawWs = request.query?.workspace_id
|
|
82
|
+
const workspaceId = (rawWs === '' || typeof rawWs === 'string') ? rawWs : undefined
|
|
83
|
+
const routines = await routineStore.load(workspaceId)
|
|
80
84
|
const status = routineRunner.getStatus()
|
|
81
85
|
|
|
82
86
|
// Merge routine data with runtime status
|
package/core/routes/skills.js
CHANGED
|
@@ -340,6 +340,7 @@ async function skillRoutes(fastify, opts) {
|
|
|
340
340
|
execution_id,
|
|
341
341
|
step_index,
|
|
342
342
|
workflow_name,
|
|
343
|
+
workspace_id,
|
|
343
344
|
role,
|
|
344
345
|
revision_feedback,
|
|
345
346
|
review_history,
|
|
@@ -380,6 +381,7 @@ async function skillRoutes(fastify, opts) {
|
|
|
380
381
|
skill_name,
|
|
381
382
|
workflow_id: null,
|
|
382
383
|
workflow_name: workflow_name || null,
|
|
384
|
+
workspace_id: workspace_id || '',
|
|
383
385
|
status: 'running',
|
|
384
386
|
outcome: null,
|
|
385
387
|
started_at: startedAt,
|
|
@@ -407,6 +409,7 @@ async function skillRoutes(fastify, opts) {
|
|
|
407
409
|
const syntheticWorkflow = {
|
|
408
410
|
id: effectiveExecutionId,
|
|
409
411
|
name: workflow_name || skill_name,
|
|
412
|
+
workspace_id: workspace_id || '',
|
|
410
413
|
}
|
|
411
414
|
const result = await workflowRunner.runWorkflow({
|
|
412
415
|
...syntheticWorkflow,
|
|
@@ -421,6 +424,7 @@ async function skillRoutes(fastify, opts) {
|
|
|
421
424
|
skill_name,
|
|
422
425
|
workflow_id: null,
|
|
423
426
|
workflow_name: workflow_name || null,
|
|
427
|
+
workspace_id: workspace_id || '',
|
|
424
428
|
status: 'failed',
|
|
425
429
|
outcome: 'failure',
|
|
426
430
|
started_at: startedAt,
|
package/core/routes/todos.js
CHANGED
|
@@ -22,18 +22,22 @@ const todoStore = require('../stores/todo-store')
|
|
|
22
22
|
*/
|
|
23
23
|
async function todoRoutes(fastify) {
|
|
24
24
|
|
|
25
|
-
// GET /api/todos/summary - Status counts (must be before /:id)
|
|
25
|
+
// GET /api/todos/summary - Status counts (must be before /:id).
|
|
26
|
+
// Optional ?workspace_id= scopes the counts.
|
|
26
27
|
fastify.get('/api/todos/summary', async (request, reply) => {
|
|
27
28
|
if (!verifyToken(request)) {
|
|
28
29
|
reply.code(401)
|
|
29
30
|
return { success: false, error: 'Unauthorized' }
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
+
const rawWs = request.query?.workspace_id
|
|
34
|
+
const workspaceId = (rawWs === '' || typeof rawWs === 'string') ? rawWs : undefined
|
|
35
|
+
const summary = todoStore.getSummary(workspaceId)
|
|
33
36
|
return { success: true, summary }
|
|
34
37
|
})
|
|
35
38
|
|
|
36
|
-
// GET /api/todos - List with optional filters
|
|
39
|
+
// GET /api/todos - List with optional filters.
|
|
40
|
+
// Optional ?workspace_id= scopes to a workspace; omit for cross-workspace view.
|
|
37
41
|
fastify.get('/api/todos', async (request, reply) => {
|
|
38
42
|
if (!verifyToken(request)) {
|
|
39
43
|
reply.code(401)
|
|
@@ -41,12 +45,15 @@ async function todoRoutes(fastify) {
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
const { status, priority, project_id, source_type, session_id, limit } = request.query || {}
|
|
48
|
+
const rawWs = request.query?.workspace_id
|
|
49
|
+
const workspaceId = (rawWs === '' || typeof rawWs === 'string') ? rawWs : undefined
|
|
44
50
|
const todos = todoStore.list({
|
|
45
51
|
status,
|
|
46
52
|
priority,
|
|
47
53
|
project_id,
|
|
48
54
|
source_type,
|
|
49
55
|
session_id,
|
|
56
|
+
workspace_id: workspaceId,
|
|
50
57
|
limit: limit ? parseInt(limit, 10) : undefined,
|
|
51
58
|
})
|
|
52
59
|
|
|
@@ -69,22 +76,28 @@ async function todoRoutes(fastify) {
|
|
|
69
76
|
return { success: true, todo }
|
|
70
77
|
})
|
|
71
78
|
|
|
72
|
-
// POST /api/todos - Create a new todo
|
|
79
|
+
// POST /api/todos - Create a new todo.
|
|
80
|
+
// workspace_id is required (pass "" for unassigned/legacy); no auto-resolution
|
|
81
|
+
// is performed so callers must always decide the scope explicitly.
|
|
73
82
|
fastify.post('/api/todos', async (request, reply) => {
|
|
74
83
|
if (!verifyToken(request)) {
|
|
75
84
|
reply.code(401)
|
|
76
85
|
return { success: false, error: 'Unauthorized' }
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
const { title, description, priority, source_type, source_id, project_id, due_at, data, session_id } = request.body || {}
|
|
88
|
+
const { title, description, priority, source_type, source_id, project_id, workspace_id, due_at, data, session_id } = request.body || {}
|
|
80
89
|
if (!title) {
|
|
81
90
|
reply.code(400)
|
|
82
91
|
return { success: false, error: 'title is required' }
|
|
83
92
|
}
|
|
93
|
+
if (workspace_id !== '' && typeof workspace_id !== 'string') {
|
|
94
|
+
reply.code(400)
|
|
95
|
+
return { success: false, error: 'workspace_id is required (pass "" for unassigned/legacy todos)' }
|
|
96
|
+
}
|
|
84
97
|
|
|
85
98
|
try {
|
|
86
|
-
const todo = todoStore.add({ title, description, priority, source_type, source_id, project_id, due_at, data, session_id })
|
|
87
|
-
console.log(`[Todos] Created: ${todo.id} "${todo.title}"`)
|
|
99
|
+
const todo = todoStore.add({ title, description, priority, source_type, source_id, project_id, workspace_id, due_at, data, session_id })
|
|
100
|
+
console.log(`[Todos] Created: ${todo.id} "${todo.title}" ws=${workspace_id || '(unassigned)'}`)
|
|
88
101
|
reply.code(201)
|
|
89
102
|
return { success: true, todo }
|
|
90
103
|
} catch (err) {
|
package/core/routes/workflows.js
CHANGED
|
@@ -52,10 +52,12 @@ async function workflowRoutes(fastify, opts) {
|
|
|
52
52
|
console.log(`[Workflows] Receiving ${incomingWorkflows.length} workflows from HQ`)
|
|
53
53
|
|
|
54
54
|
try {
|
|
55
|
-
// Load existing workflows
|
|
55
|
+
// Load existing workflows (unfiltered — this is cross-workspace admin sync)
|
|
56
56
|
const existingWorkflows = await workflowStore.load()
|
|
57
57
|
|
|
58
|
-
// Merge: update existing or add new (upsert by id)
|
|
58
|
+
// Merge: update existing or add new (upsert by id).
|
|
59
|
+
// Incoming workflows from HQ should carry workspace_id; the store will
|
|
60
|
+
// persist it alongside the DB row for fast filtering later.
|
|
59
61
|
const workflowMap = new Map(existingWorkflows.map(w => [w.id, w]))
|
|
60
62
|
for (const incoming of incomingWorkflows) {
|
|
61
63
|
workflowMap.set(incoming.id, incoming)
|
|
@@ -82,14 +84,18 @@ async function workflowRoutes(fastify, opts) {
|
|
|
82
84
|
}
|
|
83
85
|
})
|
|
84
86
|
|
|
85
|
-
// List
|
|
87
|
+
// List workflows with their current status.
|
|
88
|
+
// Optional ?workspace_id= filter: when provided, scope to that workspace;
|
|
89
|
+
// when absent, return all (used by admin/cross-workspace views).
|
|
86
90
|
fastify.get('/api/workflows', async (request, reply) => {
|
|
87
91
|
if (!verifyToken(request)) {
|
|
88
92
|
reply.code(401)
|
|
89
93
|
return { success: false, error: 'Unauthorized' }
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
const
|
|
96
|
+
const rawWs = request.query?.workspace_id
|
|
97
|
+
const workspaceId = (rawWs === '' || typeof rawWs === 'string') ? rawWs : undefined
|
|
98
|
+
const workflows = await workflowStore.load(workspaceId)
|
|
93
99
|
const status = workflowRunner.getStatus()
|
|
94
100
|
|
|
95
101
|
// Merge workflow data with runtime status
|
|
@@ -251,6 +257,7 @@ async function workflowRoutes(fastify, opts) {
|
|
|
251
257
|
})),
|
|
252
258
|
content: workflow.content || '',
|
|
253
259
|
project_id: workflow.project_id || null,
|
|
260
|
+
workspace_id: workflow.workspace_id || '',
|
|
254
261
|
})
|
|
255
262
|
|
|
256
263
|
// 4. Reload cron jobs
|
|
@@ -413,7 +420,8 @@ async function workflowRoutes(fastify, opts) {
|
|
|
413
420
|
}
|
|
414
421
|
})
|
|
415
422
|
|
|
416
|
-
// List execution history
|
|
423
|
+
// List execution history.
|
|
424
|
+
// Optional ?workspace_id= filter scopes to a workspace; omit for cross-workspace view.
|
|
417
425
|
fastify.get('/api/executions', async (request, reply) => {
|
|
418
426
|
if (!verifyToken(request)) {
|
|
419
427
|
reply.code(401)
|
|
@@ -422,12 +430,14 @@ async function workflowRoutes(fastify, opts) {
|
|
|
422
430
|
|
|
423
431
|
const limit = parseInt(request.query.limit) || 50
|
|
424
432
|
const workflow_id = request.query.workflow_id
|
|
433
|
+
const rawWs = request.query.workspace_id
|
|
434
|
+
const workspaceId = (rawWs === '' || typeof rawWs === 'string') ? rawWs : undefined
|
|
425
435
|
|
|
426
436
|
let executions
|
|
427
437
|
if (workflow_id) {
|
|
428
|
-
executions = await executionStore.getByWorkflow(workflow_id, limit)
|
|
438
|
+
executions = await executionStore.getByWorkflow(workflow_id, limit, workspaceId)
|
|
429
439
|
} else {
|
|
430
|
-
executions = await executionStore.list(limit)
|
|
440
|
+
executions = await executionStore.list(limit, workspaceId)
|
|
431
441
|
}
|
|
432
442
|
|
|
433
443
|
return { executions }
|