@geekbeer/minion 3.22.0 → 3.23.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/db.js +24 -1
- package/core/routes/todos.js +4 -3
- package/core/stores/todo-store.js +65 -5
- package/docs/api-reference.md +40 -9
- package/linux/routes/chat.js +28 -0
- package/package.json +1 -1
- package/rules/core.md +12 -0
- package/win/routes/chat.js +27 -0
package/core/db.js
CHANGED
|
@@ -259,12 +259,15 @@ function initSchema(db) {
|
|
|
259
259
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
260
260
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
261
261
|
completed_at TEXT,
|
|
262
|
-
data TEXT
|
|
262
|
+
data TEXT,
|
|
263
|
+
session_id TEXT,
|
|
264
|
+
injection_count INTEGER NOT NULL DEFAULT 0
|
|
263
265
|
);
|
|
264
266
|
|
|
265
267
|
CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status);
|
|
266
268
|
CREATE INDEX IF NOT EXISTS idx_todos_project ON todos(project_id);
|
|
267
269
|
CREATE INDEX IF NOT EXISTS idx_todos_priority ON todos(priority);
|
|
270
|
+
CREATE INDEX IF NOT EXISTS idx_todos_session ON todos(session_id);
|
|
268
271
|
|
|
269
272
|
-- ==================== emails ====================
|
|
270
273
|
CREATE TABLE IF NOT EXISTS emails (
|
|
@@ -473,6 +476,26 @@ function migrateSchema(db) {
|
|
|
473
476
|
try { db.exec("INSERT OR IGNORE INTO schema_version (version) VALUES (4)") } catch {}
|
|
474
477
|
}
|
|
475
478
|
}
|
|
479
|
+
|
|
480
|
+
if (currentVersion < 5) {
|
|
481
|
+
try {
|
|
482
|
+
console.log('[DB] Migration 5: Adding session_id / injection_count to todos...')
|
|
483
|
+
|
|
484
|
+
db.exec(`
|
|
485
|
+
ALTER TABLE todos ADD COLUMN session_id TEXT;
|
|
486
|
+
ALTER TABLE todos ADD COLUMN injection_count INTEGER NOT NULL DEFAULT 0;
|
|
487
|
+
|
|
488
|
+
CREATE INDEX IF NOT EXISTS idx_todos_session ON todos(session_id);
|
|
489
|
+
|
|
490
|
+
INSERT INTO schema_version (version) VALUES (5);
|
|
491
|
+
`)
|
|
492
|
+
|
|
493
|
+
console.log('[DB] Migration 5 complete: todos.session_id / injection_count added')
|
|
494
|
+
} catch (err) {
|
|
495
|
+
console.warn(`[DB] Migration 5 skipped: ${err.message}`)
|
|
496
|
+
try { db.exec("INSERT OR IGNORE INTO schema_version (version) VALUES (5)") } catch {}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
476
499
|
}
|
|
477
500
|
|
|
478
501
|
/**
|
package/core/routes/todos.js
CHANGED
|
@@ -40,12 +40,13 @@ async function todoRoutes(fastify) {
|
|
|
40
40
|
return { success: false, error: 'Unauthorized' }
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const { status, priority, project_id, source_type, limit } = request.query || {}
|
|
43
|
+
const { status, priority, project_id, source_type, session_id, limit } = request.query || {}
|
|
44
44
|
const todos = todoStore.list({
|
|
45
45
|
status,
|
|
46
46
|
priority,
|
|
47
47
|
project_id,
|
|
48
48
|
source_type,
|
|
49
|
+
session_id,
|
|
49
50
|
limit: limit ? parseInt(limit, 10) : undefined,
|
|
50
51
|
})
|
|
51
52
|
|
|
@@ -75,14 +76,14 @@ async function todoRoutes(fastify) {
|
|
|
75
76
|
return { success: false, error: 'Unauthorized' }
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
const { title, description, priority, source_type, source_id, project_id, due_at, data } = request.body || {}
|
|
79
|
+
const { title, description, priority, source_type, source_id, project_id, due_at, data, session_id } = request.body || {}
|
|
79
80
|
if (!title) {
|
|
80
81
|
reply.code(400)
|
|
81
82
|
return { success: false, error: 'title is required' }
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
try {
|
|
85
|
-
const todo = todoStore.add({ title, description, priority, source_type, source_id, project_id, due_at, data })
|
|
86
|
+
const todo = todoStore.add({ title, description, priority, source_type, source_id, project_id, due_at, data, session_id })
|
|
86
87
|
console.log(`[Todos] Created: ${todo.id} "${todo.title}"`)
|
|
87
88
|
reply.code(201)
|
|
88
89
|
return { success: true, todo }
|
|
@@ -19,6 +19,11 @@ const VALID_SOURCE_TYPES = ['thread', 'workflow', 'directive', 'user', 'self']
|
|
|
19
19
|
// Auto-prune completed todos older than this
|
|
20
20
|
const PRUNE_DAYS = 30
|
|
21
21
|
|
|
22
|
+
// Max number of times an incomplete todo gets auto-injected into the chat
|
|
23
|
+
// prompt before we give up and stop injecting it (to avoid infinite loops
|
|
24
|
+
// when Claude cannot finish a task).
|
|
25
|
+
const MAX_INJECTION_COUNT = 5
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
* Create a new TODO.
|
|
24
29
|
* @param {object} todo - { title, description?, priority?, source_type?, source_id?, project_id?, due_at?, data? }
|
|
@@ -50,12 +55,14 @@ function add(todo) {
|
|
|
50
55
|
created_at: now,
|
|
51
56
|
updated_at: now,
|
|
52
57
|
completed_at: null,
|
|
58
|
+
session_id: todo.session_id || null,
|
|
59
|
+
injection_count: 0,
|
|
53
60
|
...(todo.data ? { data: todo.data } : {}),
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
db.prepare(`
|
|
57
|
-
INSERT INTO todos (id, title, description, status, priority, source_type, source_id, project_id, due_at, created_at, updated_at, completed_at, data)
|
|
58
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
64
|
+
INSERT INTO todos (id, title, description, status, priority, source_type, source_id, project_id, due_at, created_at, updated_at, completed_at, data, session_id, injection_count)
|
|
65
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
59
66
|
`).run(
|
|
60
67
|
record.id,
|
|
61
68
|
record.title,
|
|
@@ -69,7 +76,9 @@ function add(todo) {
|
|
|
69
76
|
record.created_at,
|
|
70
77
|
record.updated_at,
|
|
71
78
|
record.completed_at,
|
|
72
|
-
record.data ? JSON.stringify(record.data) : null
|
|
79
|
+
record.data ? JSON.stringify(record.data) : null,
|
|
80
|
+
record.session_id,
|
|
81
|
+
record.injection_count
|
|
73
82
|
)
|
|
74
83
|
|
|
75
84
|
console.log(`[TodoStore] Added: "${record.title}" (${record.priority}, source=${record.source_type || 'none'})`)
|
|
@@ -115,7 +124,8 @@ function update(id, updates) {
|
|
|
115
124
|
db.prepare(`
|
|
116
125
|
UPDATE todos SET title = ?, description = ?, status = ?, priority = ?,
|
|
117
126
|
source_type = ?, source_id = ?, project_id = ?, due_at = ?,
|
|
118
|
-
updated_at = ?, completed_at = ?, data =
|
|
127
|
+
updated_at = ?, completed_at = ?, data = ?,
|
|
128
|
+
session_id = ?, injection_count = ?
|
|
119
129
|
WHERE id = ?
|
|
120
130
|
`).run(
|
|
121
131
|
merged.title,
|
|
@@ -129,6 +139,8 @@ function update(id, updates) {
|
|
|
129
139
|
merged.updated_at,
|
|
130
140
|
merged.completed_at,
|
|
131
141
|
merged.data ? JSON.stringify(merged.data) : null,
|
|
142
|
+
merged.session_id ?? null,
|
|
143
|
+
merged.injection_count ?? 0,
|
|
132
144
|
id
|
|
133
145
|
)
|
|
134
146
|
|
|
@@ -160,9 +172,50 @@ function getById(id) {
|
|
|
160
172
|
return row ? parseRow(row) : null
|
|
161
173
|
}
|
|
162
174
|
|
|
175
|
+
/**
|
|
176
|
+
* List incomplete (pending/in_progress) todos linked to a chat session that
|
|
177
|
+
* still have remaining injection budget. Used by the chat route to remind
|
|
178
|
+
* Claude of unfinished work even after context compaction.
|
|
179
|
+
* @param {string} sessionId
|
|
180
|
+
* @returns {Array}
|
|
181
|
+
*/
|
|
182
|
+
function listActiveForSession(sessionId) {
|
|
183
|
+
if (!sessionId) return []
|
|
184
|
+
const db = getDb()
|
|
185
|
+
const rows = db.prepare(`
|
|
186
|
+
SELECT * FROM todos
|
|
187
|
+
WHERE session_id = ?
|
|
188
|
+
AND status IN ('pending', 'in_progress')
|
|
189
|
+
AND injection_count < ?
|
|
190
|
+
ORDER BY
|
|
191
|
+
CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'normal' THEN 2 WHEN 'low' THEN 3 END,
|
|
192
|
+
created_at ASC
|
|
193
|
+
`).all(sessionId, MAX_INJECTION_COUNT)
|
|
194
|
+
return rows.map(parseRow)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Increment injection_count for the given todo IDs. Should be called once
|
|
199
|
+
* the reminder has been actually written into the outgoing prompt.
|
|
200
|
+
* @param {string[]} ids
|
|
201
|
+
*/
|
|
202
|
+
function markInjected(ids) {
|
|
203
|
+
if (!Array.isArray(ids) || ids.length === 0) return
|
|
204
|
+
const db = getDb()
|
|
205
|
+
const now = new Date().toISOString()
|
|
206
|
+
const stmt = db.prepare(`
|
|
207
|
+
UPDATE todos
|
|
208
|
+
SET injection_count = injection_count + 1, updated_at = ?
|
|
209
|
+
WHERE id = ?
|
|
210
|
+
`)
|
|
211
|
+
for (const id of ids) {
|
|
212
|
+
stmt.run(now, id)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
163
216
|
/**
|
|
164
217
|
* List TODOs with optional filters.
|
|
165
|
-
* @param {object} opts - { status?, priority?, project_id?, source_type?, limit? }
|
|
218
|
+
* @param {object} opts - { status?, priority?, project_id?, source_type?, session_id?, limit? }
|
|
166
219
|
* @returns {Array}
|
|
167
220
|
*/
|
|
168
221
|
function list(opts = {}) {
|
|
@@ -186,6 +239,10 @@ function list(opts = {}) {
|
|
|
186
239
|
conditions.push('source_type = ?')
|
|
187
240
|
params.push(opts.source_type)
|
|
188
241
|
}
|
|
242
|
+
if (opts.session_id) {
|
|
243
|
+
conditions.push('session_id = ?')
|
|
244
|
+
params.push(opts.session_id)
|
|
245
|
+
}
|
|
189
246
|
|
|
190
247
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
|
191
248
|
const limit = opts.limit || 100
|
|
@@ -260,6 +317,9 @@ module.exports = {
|
|
|
260
317
|
remove,
|
|
261
318
|
getById,
|
|
262
319
|
list,
|
|
320
|
+
listActiveForSession,
|
|
321
|
+
markInjected,
|
|
263
322
|
getSummary,
|
|
264
323
|
pruneCompleted,
|
|
324
|
+
MAX_INJECTION_COUNT,
|
|
265
325
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -229,6 +229,38 @@ curl -X PUT /api/config/env \
|
|
|
229
229
|
The scheduler starts automatically on server boot if `REFLECTION_TIME` is configured.
|
|
230
230
|
Changes via the config API take effect immediately (no restart required).
|
|
231
231
|
|
|
232
|
+
### Todos
|
|
233
|
+
|
|
234
|
+
ミニオンローカルのToDoリスト。SQLiteに永続化され、HQにも同期される。
|
|
235
|
+
|
|
236
|
+
| Method | Endpoint | Description |
|
|
237
|
+
|--------|----------|-------------|
|
|
238
|
+
| GET | `/api/todos` | List todos. Query: `status`, `priority`, `project_id`, `source_type`, `session_id`, `limit` |
|
|
239
|
+
| GET | `/api/todos/summary` | Status counts |
|
|
240
|
+
| GET | `/api/todos/:id` | Get single todo |
|
|
241
|
+
| POST | `/api/todos` | Create. Body: `{title, description?, priority?, source_type?, source_id?, project_id?, due_at?, session_id?, data?}` |
|
|
242
|
+
| PUT | `/api/todos/:id` | Update any field including `status` |
|
|
243
|
+
| DELETE | `/api/todos/:id` | Delete |
|
|
244
|
+
|
|
245
|
+
**フィールド**:
|
|
246
|
+
- `status`: `pending` / `in_progress` / `done` / `cancelled`
|
|
247
|
+
- `priority`: `low` / `normal` / `high` / `urgent`
|
|
248
|
+
- `session_id`: チャットセッションID(任意)。**設定すると圧縮を跨いだ自動再掲の対象になる** — 次ターン以降のプロンプト冒頭で未完了Todoが自動表示される。
|
|
249
|
+
- `injection_count`: 自動再掲された回数(読み取り専用)。一定回数を超えたTodoは再掲が停止する。
|
|
250
|
+
|
|
251
|
+
**セッション紐づけ例**:
|
|
252
|
+
```bash
|
|
253
|
+
# 作成時にsession_idを指定すると、このセッションのチャットに自動で再掲される
|
|
254
|
+
curl -X POST -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
255
|
+
http://localhost:8080/api/todos \
|
|
256
|
+
-d '{"title": "レポートを保存", "session_id": "'$SESSION_ID'", "priority": "high"}'
|
|
257
|
+
|
|
258
|
+
# 完了マーク(即座に更新すること)
|
|
259
|
+
curl -X PUT -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
260
|
+
http://localhost:8080/api/todos/$TODO_ID \
|
|
261
|
+
-d '{"status": "done"}'
|
|
262
|
+
```
|
|
263
|
+
|
|
232
264
|
### Config
|
|
233
265
|
|
|
234
266
|
| Method | Endpoint | Description |
|
|
@@ -1187,16 +1219,16 @@ DAG ワークフローの graph は以下の構造で保存される(`dag_work
|
|
|
1187
1219
|
"label": "Per item",
|
|
1188
1220
|
"fan_out_source": ".items",
|
|
1189
1221
|
"template": { "nodes": [ ... ], "edges": [ ... ] },
|
|
1190
|
-
"
|
|
1222
|
+
"join_mode": "all",
|
|
1223
|
+
"on_failure": "collect",
|
|
1191
1224
|
"max_concurrency": 3
|
|
1192
1225
|
},
|
|
1193
1226
|
{
|
|
1194
1227
|
"id": "n4",
|
|
1195
1228
|
"type": "join",
|
|
1196
1229
|
"label": "Collect",
|
|
1197
|
-
"fan_out_node": "n3",
|
|
1198
1230
|
"join_mode": "all",
|
|
1199
|
-
"
|
|
1231
|
+
"aggregation": "array"
|
|
1200
1232
|
},
|
|
1201
1233
|
{ "id": "n5", "type": "end", "label": "End" }
|
|
1202
1234
|
],
|
|
@@ -1218,8 +1250,8 @@ DAG ワークフローの graph は以下の構造で保存される(`dag_work
|
|
|
1218
1250
|
| `skill` | スキル実行。`skill_version_id` と `assigned_role` が必須 | ✅ |
|
|
1219
1251
|
| `transform` | LLM によるデータ変換。`transform_instruction` が必須 | ✅ |
|
|
1220
1252
|
| `review` | レビューゲート。`approved` / `revision_requested` で分岐 | ❌ (内部) |
|
|
1221
|
-
| `fan_out` | 配列入力をテンプレートsub-graph
|
|
1222
|
-
| `join` | fan_out
|
|
1253
|
+
| `fan_out` | 配列入力をテンプレートsub-graphに展開して並列実行。子が全て settle すると自ノードが completed に遷移 | ❌ (内部) |
|
|
1254
|
+
| `join` | N本の上流エッジを待ち合わせる汎用バリア。fan_out とは独立 | ❌ (内部) |
|
|
1223
1255
|
| `conditional` | 条件分岐(`llm` / `regex` / `jq`) | ❌ (内部) |
|
|
1224
1256
|
|
|
1225
1257
|
#### DagNode 主要フィールド
|
|
@@ -1233,11 +1265,10 @@ DAG ワークフローの graph は以下の構造で保存される(`dag_work
|
|
|
1233
1265
|
| `assigned_role` | `pm`\|`engineer`\|`accountant` | 実行ロール |
|
|
1234
1266
|
| `fan_out_source` | string? | fan_out: 入力から配列を取り出すドット記法(例 `.items`) |
|
|
1235
1267
|
| `template` | DagGraph? | fan_out: 各要素ごとに展開するsub-graph |
|
|
1236
|
-
| `join_node` | string? | fan_out: 対応する join ノードID |
|
|
1237
1268
|
| `max_concurrency` | number? | fan_out: 並列インスタンス上限 |
|
|
1238
|
-
| `
|
|
1239
|
-
| `
|
|
1240
|
-
| `
|
|
1269
|
+
| `join_mode` | `all`\|`any`\|`majority` | fan_out / join: 完了判定 |
|
|
1270
|
+
| `on_failure` | `fail_all`\|`ignore`\|`collect` | fan_out / join: 失敗時の挙動 |
|
|
1271
|
+
| `aggregation` | `array`\|`merge` | join: 上流出力の束ね方(デフォルト `array`) |
|
|
1241
1272
|
| `condition_type` | `llm`\|`regex`\|`jq` | conditional: 条件評価方式 |
|
|
1242
1273
|
| `condition_expression` | string | conditional: 条件式 |
|
|
1243
1274
|
| `branches` | Record<string,string> | conditional: 条件出力→遷移先ノードID |
|
package/linux/routes/chat.js
CHANGED
|
@@ -22,6 +22,7 @@ const path = require('path')
|
|
|
22
22
|
const { verifyToken } = require('../../core/lib/auth')
|
|
23
23
|
const { config } = require('../../core/config')
|
|
24
24
|
const chatStore = require('../../core/stores/chat-store')
|
|
25
|
+
const todoStore = require('../../core/stores/todo-store')
|
|
25
26
|
const { runEndOfDay } = require('../../core/lib/end-of-day')
|
|
26
27
|
const { DATA_DIR } = require('../../core/lib/platform')
|
|
27
28
|
const { getActivePrimary } = require('../../core/llm-plugins/lib/active')
|
|
@@ -255,6 +256,33 @@ ${indexed}`
|
|
|
255
256
|
async function buildContextPrefix(message, context, sessionId) {
|
|
256
257
|
const parts = []
|
|
257
258
|
|
|
259
|
+
// Re-inject unfinished todos tied to this session. This is how we survive
|
|
260
|
+
// context compaction: even if Claude forgot the plan, the outstanding
|
|
261
|
+
// todos are re-shown on the next turn. Todos past MAX_INJECTION_COUNT are
|
|
262
|
+
// skipped by the store to prevent infinite loops.
|
|
263
|
+
if (sessionId) {
|
|
264
|
+
parts.push(
|
|
265
|
+
`[現在のチャットセッションID] ${sessionId}`,
|
|
266
|
+
'新規Todoを作成する際は `session_id` にこの値を含めてください(圧縮を跨いだ自動再掲の対象になります)。',
|
|
267
|
+
''
|
|
268
|
+
)
|
|
269
|
+
const activeTodos = todoStore.listActiveForSession(sessionId)
|
|
270
|
+
if (activeTodos.length > 0) {
|
|
271
|
+
parts.push(
|
|
272
|
+
'[未完了のToDo(このセッション起点)]',
|
|
273
|
+
'以下のToDoが未完了のまま残っています。着手前に「既に完了していないか」を確認し、',
|
|
274
|
+
'完了済みなら `PUT /api/todos/:id` で status=done に更新、未完なら続行してください。',
|
|
275
|
+
''
|
|
276
|
+
)
|
|
277
|
+
for (const t of activeTodos) {
|
|
278
|
+
const desc = t.description ? ` — ${t.description}` : ''
|
|
279
|
+
parts.push(`- [${t.id}] (${t.status}/${t.priority}) ${t.title}${desc}`)
|
|
280
|
+
}
|
|
281
|
+
parts.push('')
|
|
282
|
+
todoStore.markInjected(activeTodos.map(t => t.id))
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
258
286
|
// Tell the LLM how to access memory and daily logs via API
|
|
259
287
|
if (!sessionId) {
|
|
260
288
|
const port = require('../../core/config').config.AGENT_PORT
|
package/package.json
CHANGED
package/rules/core.md
CHANGED
|
@@ -203,6 +203,18 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
|
|
|
203
203
|
- `pm.md` — PM (Project Manager) のガイドライン
|
|
204
204
|
- `engineer.md` — Engineer のガイドライン
|
|
205
205
|
|
|
206
|
+
## Todo運用ルール
|
|
207
|
+
|
|
208
|
+
チャット中のタスクは `/api/todos` に登録して進捗管理すること。圧縮(context compaction)を跨いでも作業を完遂するための仕組みがある。
|
|
209
|
+
|
|
210
|
+
- **Todoは「1往復で完了する粒度」に分解して登録する。** 大きいタスクは複数のTodoに分ける。粒度を小さく保てば、完了マークを圧縮に奪われにくい。
|
|
211
|
+
- **完了したら即座に done にマークする。** まとめて更新しない。`PUT /api/todos/:id` で `status=done`。
|
|
212
|
+
- **チャットセッション内で作成するTodoには必ず `session_id` を含める。** プロンプト冒頭の `[現在のチャットセッションID]` の値を使う。紐づいた未完了Todoは次ターン以降に自動で再掲される(圧縮を跨いでも失われない)。
|
|
213
|
+
- **再掲されたTodoを見たら、着手前に「既に完了していないか」を確認する。** 完了済みなら done に更新、未完なら続行。
|
|
214
|
+
- 同一Todoが規定回数以上再掲されても未完了のままの場合、再掲は自動停止する。進展しないTodoはブロッカーとして起票するか手動で `cancelled` にすること。
|
|
215
|
+
|
|
216
|
+
API詳細は `~/.minion/docs/api-reference.md` の「Todos」セクションを参照。
|
|
217
|
+
|
|
206
218
|
## Blocker Handling (ブロッカー対処)
|
|
207
219
|
|
|
208
220
|
タスク実行中にブロッカー(自力で解決できない問題)に遭遇した場合、以下のフローに従うこと。
|
package/win/routes/chat.js
CHANGED
|
@@ -19,6 +19,7 @@ const http = require('http')
|
|
|
19
19
|
const { verifyToken } = require('../../core/lib/auth')
|
|
20
20
|
const { config } = require('../../core/config')
|
|
21
21
|
const chatStore = require('../../core/stores/chat-store')
|
|
22
|
+
const todoStore = require('../../core/stores/todo-store')
|
|
22
23
|
const { DATA_DIR } = require('../../core/lib/platform')
|
|
23
24
|
const { runEndOfDay } = require('../../core/lib/end-of-day')
|
|
24
25
|
const { getActivePrimary } = require('../../core/llm-plugins/lib/active')
|
|
@@ -319,6 +320,32 @@ ${indexed}`
|
|
|
319
320
|
async function buildContextPrefix(message, context, sessionId) {
|
|
320
321
|
const parts = []
|
|
321
322
|
|
|
323
|
+
// Re-inject unfinished todos tied to this session. Survives context
|
|
324
|
+
// compaction — Claude sees outstanding todos again on the next turn.
|
|
325
|
+
// Skipped past MAX_INJECTION_COUNT to prevent infinite loops.
|
|
326
|
+
if (sessionId) {
|
|
327
|
+
parts.push(
|
|
328
|
+
`[現在のチャットセッションID] ${sessionId}`,
|
|
329
|
+
'新規Todoを作成する際は `session_id` にこの値を含めてください(圧縮を跨いだ自動再掲の対象になります)。',
|
|
330
|
+
''
|
|
331
|
+
)
|
|
332
|
+
const activeTodos = todoStore.listActiveForSession(sessionId)
|
|
333
|
+
if (activeTodos.length > 0) {
|
|
334
|
+
parts.push(
|
|
335
|
+
'[未完了のToDo(このセッション起点)]',
|
|
336
|
+
'以下のToDoが未完了のまま残っています。着手前に「既に完了していないか」を確認し、',
|
|
337
|
+
'完了済みなら `PUT /api/todos/:id` で status=done に更新、未完なら続行してください。',
|
|
338
|
+
''
|
|
339
|
+
)
|
|
340
|
+
for (const t of activeTodos) {
|
|
341
|
+
const desc = t.description ? ` — ${t.description}` : ''
|
|
342
|
+
parts.push(`- [${t.id}] (${t.status}/${t.priority}) ${t.title}${desc}`)
|
|
343
|
+
}
|
|
344
|
+
parts.push('')
|
|
345
|
+
todoStore.markInjected(activeTodos.map(t => t.id))
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
322
349
|
// Tell the LLM how to access memory and daily logs via API
|
|
323
350
|
if (!sessionId) {
|
|
324
351
|
const port = require('../../core/config').config.AGENT_PORT
|