@thincoder/core 0.9.2 → 0.9.4
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/CHANGELOG.md +76 -0
- package/README.md +1 -0
- package/agent/completion.mjs +3 -1
- package/agent/family-tools.mjs +12 -9
- package/agent/helpers.mjs +11 -2
- package/agent/run-stages.mjs +27 -5
- package/agent/setup.mjs +6 -0
- package/agent/write-gate.mjs +5 -5
- package/agent-tools/advisor-async.mjs +4 -4
- package/agent-tools/advisor.mjs +1 -1
- package/agent-tools/async-discard.mjs +1 -1
- package/agent-tools/audit-block.mjs +106 -0
- package/agent-tools/batch-lifecycle.mjs +72 -16
- package/agent-tools/batch-skeleton.mjs +69 -7
- package/agent-tools/batch.mjs +19 -6
- package/agent-tools/context.mjs +174 -0
- package/agent-tools/goal.mjs +7 -0
- package/agent-tools/parent-channel.mjs +2 -2
- package/agent-tools/plan.mjs +6 -6
- package/agent-tools/read-history.mjs +122 -24
- package/agent-tools/settings.mjs +4 -2
- package/agent-tools/subagent-async.mjs +3 -3
- package/agent-tools/subagent-spawn.mjs +29 -101
- package/agent-tools/task.mjs +11 -0
- package/agent-tools.mjs +4 -1
- package/agent.mjs +10 -4
- package/config-presets.mjs +2 -2
- package/config.mjs +1 -1
- package/context.mjs +66 -121
- package/fts-text.mjs +41 -0
- package/memory/core.mjs +4 -18
- package/memory/schema.mjs +4 -11
- package/model-specs.mjs +22 -7
- package/package.json +5 -1
- package/prompts/common.md +2 -2
- package/prompts/discipline-engineering.md +17 -2
- package/prompts/persona-engineering.md +1 -1
- package/session-gc.mjs +11 -0
- package/session-index-build.mjs +298 -0
- package/session-index-cmd.mjs +61 -0
- package/session-index-pass.mjs +95 -0
- package/session-index-query.mjs +102 -0
- package/session-index.mjs +285 -0
- package/session-slots-manifest.mjs +19 -0
- package/token-window.mjs +188 -0
- package/tools/bash.mjs +4 -15
- package/tools/execute.mjs +5 -13
- package/tools/git-checkpoint.mjs +1 -1
- package/tools/git-ext.mjs +23 -20
- package/tools/git-run.mjs +141 -0
- package/tools/git.mjs +42 -36
- package/tools/index.mjs +3 -1
- package/tools/process-tree.mjs +20 -0
- package/tools/shared.mjs +8 -4
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* agent-tools/read-history.mjs — read_history tool (SESSION.md §6.9 + §6.13 R19 cross-session).
|
|
2
|
+
* agent-tools/read-history.mjs — read_history tool (SESSION.md §6.9 + §6.13 R19 cross-session + §6.19).
|
|
3
3
|
*
|
|
4
4
|
* Query message history — THIS session by default, any session on disk with `path`
|
|
5
5
|
* (SESSION.md §6.13 R19): an explicit session file path deep-queries that file's
|
|
6
|
-
* history line; "cwd:<dir>" discovers the sessions stored for that directory
|
|
6
|
+
* history line; "cwd:<dir>" discovers the sessions stored for that directory; "all"
|
|
7
|
+
* searches every indexed session across projects (§6.19 D-SE47).
|
|
7
8
|
*
|
|
8
9
|
* Default (no path) — THIS session's full human-readable record (record store when bound
|
|
9
10
|
* — disk-backed, SESSION.md §6.14; agent._fullHistory memory fallback otherwise:
|
|
@@ -19,22 +20,25 @@
|
|
|
19
20
|
* Returns a JSON array in chronological order. Every message without ts comes
|
|
20
21
|
* back as ts:null and can never match a time window (legacy sessions). Content
|
|
21
22
|
* is truncated to ~500 chars with an explicit marker — full text lives in the
|
|
22
|
-
* session file. assistant tool_calls
|
|
23
|
-
*
|
|
23
|
+
* session file. assistant tool_calls come back as [{name, arguments}] with the
|
|
24
|
+
* stored argument string capped at 300 chars (matches the stored precision).
|
|
24
25
|
*
|
|
25
|
-
* Cross-session (SESSION.md §6.13 D-R19a
|
|
26
|
-
* relative to the project cwd) →
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* Cross-session (SESSION.md §6.13 D-R19a + §6.19 D-SE47 — 索引优先 + 主存回落):
|
|
27
|
+
* path = a session file path (absolute, or relative to the project cwd) → when the derived
|
|
28
|
+
* session index covers that session the query runs as SQL (no line-scan / message-count
|
|
29
|
+
* guards ⇒ oversized sessions answer); otherwise the pre-existing JSON path with BOTH
|
|
30
|
+
* guards, verbatim (not a sessions-tree file / index unavailable / no such row). path =
|
|
31
|
+
* "cwd:<dir>" → list every slot stored for that directory (slot number + full file path +
|
|
32
|
+
* title/message count/updatedAt — no dead-slot filtering, v1 decision). path = "all" →
|
|
33
|
+
* cross-session search over every INDEXED session (keyword = FTS words / phrases; rows carry
|
|
34
|
+
* session.file as the follow-up anchor).
|
|
35
|
+
* The index face enters load-time via dynamic import (W8 contract②: node:sqlite must never
|
|
36
|
+
* join the assembly-time static closure) — a host without node:sqlite falls back to the file
|
|
37
|
+
* path silently, and an index write failure never endangers the session files (zero authority).
|
|
32
38
|
*
|
|
33
39
|
* readonly: true — planMode pass / no permission ask. Registered depth-0 only:
|
|
34
40
|
* subagents get their own throwaway history, so querying "the session" from a
|
|
35
41
|
* child would be semantically confusing (SESSION.md §6.9 refinement 1 + §6.13 T-R19.4).
|
|
36
|
-
* §6.13 R19 extension mirrored per SESSION.md §6.13 — double-end isomorphic, no
|
|
37
|
-
* cross-end byte test (thincoder-vscode/src/agent-tools/read-history.mjs).
|
|
38
42
|
*/
|
|
39
43
|
|
|
40
44
|
import { openSync, readSync, closeSync, readFileSync, existsSync, statSync } from "node:fs"
|
|
@@ -44,7 +48,10 @@ import { listSlots, slotPath } from "../session-slots.mjs"
|
|
|
44
48
|
const DEFAULT_LIMIT = 50
|
|
45
49
|
const MAX_LIMIT = 200
|
|
46
50
|
const CONTENT_CAP = 500
|
|
51
|
+
const ARG_CAP = 300
|
|
47
52
|
const VALID_ROLES = new Set(["user", "assistant", "tool"])
|
|
53
|
+
/** 跨会话检索字面量(§6.19 D-SE47——全部**已索引**会话)。 */
|
|
54
|
+
const ALL_PATH = "all"
|
|
48
55
|
|
|
49
56
|
/** 单槽检索行扫护栏(SESSION.md §6.13 D-R19a——评审 #3 定稿:超限不再读全文,返回定稿错误文案)。 */
|
|
50
57
|
export const READ_HISTORY_SCAN_MAX = 200_000
|
|
@@ -89,6 +96,19 @@ function toolCallName(tc) {
|
|
|
89
96
|
return tc?.function?.name ?? tc?.name ?? ""
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
/** Assistant tool_calls output shape: [{name, arguments}] — `arguments` = the STORED string
|
|
100
|
+
* (SESSION.md §6.19 D-SE47: 上限 300 字符 = 存储面同值——不虚构超出存储的精度). */
|
|
101
|
+
function toolCallEntries(tcs) {
|
|
102
|
+
return tcs.map((tc) => {
|
|
103
|
+
const name = toolCallName(tc)
|
|
104
|
+
const args = typeof tc?.function?.arguments === "string" ? tc.function.arguments
|
|
105
|
+
: typeof tc?.arguments === "string" ? tc.arguments : null
|
|
106
|
+
const out = { name }
|
|
107
|
+
if (args !== null) out.arguments = args.length <= ARG_CAP ? args : args.slice(0, ARG_CAP) + "…"
|
|
108
|
+
return out
|
|
109
|
+
}).filter((t) => t.name)
|
|
110
|
+
}
|
|
111
|
+
|
|
92
112
|
/** Parse a ts window boundary (epoch ms number; numeric strings tolerated). Returns the number or an error string. */
|
|
93
113
|
function parseTs(value, label) {
|
|
94
114
|
if (value === undefined || value === null) return null
|
|
@@ -107,7 +127,8 @@ function toEntry(m) {
|
|
|
107
127
|
if (m.tool_call_id !== undefined) entry.tool_call_id = m.tool_call_id
|
|
108
128
|
entry.content = truncateContent(messageText(m))
|
|
109
129
|
if (Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
|
|
110
|
-
|
|
130
|
+
const calls = toolCallEntries(m.tool_calls)
|
|
131
|
+
if (calls.length > 0) entry.tool_calls = calls
|
|
111
132
|
}
|
|
112
133
|
return entry
|
|
113
134
|
}
|
|
@@ -168,7 +189,7 @@ function exceedsScanMax(file) {
|
|
|
168
189
|
}
|
|
169
190
|
}
|
|
170
191
|
|
|
171
|
-
/**
|
|
192
|
+
/** 跨会话深查(回落面):单个槽文件,同 filter 面(§6.13 D-R19a——两道护栏逐字保留)。 */
|
|
172
193
|
function querySessionFile(pathArg, { role, kwRe, tool, since, until, direction, limit }, baseCwd) {
|
|
173
194
|
const file = isAbsolute(pathArg) ? pathArg : resolve(baseCwd ?? process.cwd(), pathArg)
|
|
174
195
|
if (!existsSync(file)) return `Error: session file not found: ${file}`
|
|
@@ -219,6 +240,74 @@ function discoverCwd(raw, baseCwd) {
|
|
|
219
240
|
return `Session slots for cwd: ${dir} (newest first):\n${lines.join("\n")}`
|
|
220
241
|
}
|
|
221
242
|
|
|
243
|
+
// ── 索引面(§6.19 D-SE47:索引优先 + 主存回落;动态装载——W8 契约②)─────────────────
|
|
244
|
+
|
|
245
|
+
/** 索引面模块(动态 import:node:sqlite 不进装配期静态闭包);宿主不具备(无 node:sqlite / 装配失败)⇒ null。 */
|
|
246
|
+
async function loadIndexFace() {
|
|
247
|
+
try {
|
|
248
|
+
const [idx, query, build] = await Promise.all([
|
|
249
|
+
import("../session-index.mjs"),
|
|
250
|
+
import("../session-index-query.mjs"),
|
|
251
|
+
import("../session-index-build.mjs"),
|
|
252
|
+
])
|
|
253
|
+
return { idx, query, build }
|
|
254
|
+
} catch { return null }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 索引行 → 输出条目(与 JSON 面 `toEntry` 同形——两条路径结果逐条相等,T-11 等价面)。 */
|
|
258
|
+
function indexEntry(r) {
|
|
259
|
+
const entry = { ts: typeof r.ts === "number" ? r.ts : null, role: r.role ?? null }
|
|
260
|
+
if (r.name !== undefined && r.name !== null) entry.name = r.name
|
|
261
|
+
if (r.tool_call_id !== undefined && r.tool_call_id !== null) entry.tool_call_id = r.tool_call_id
|
|
262
|
+
entry.content = truncateContent(r.content ?? "")
|
|
263
|
+
if (Array.isArray(r.tool_calls) && r.tool_calls.length > 0) entry.tool_calls = r.tool_calls
|
|
264
|
+
return entry
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** 单会话:索引命中 ⇒ SQL 检索结果;未命中(非 sessions 树 / 库不可用 / 库内无此会话)⇒ `null` = 回落。
|
|
268
|
+
* 懒保证射程 = 有段档(`src=sidecar`——`session-index-build` 内判;`src=json` 档免 ensure)。 */
|
|
269
|
+
async function queryIndexedSession(file, filters) {
|
|
270
|
+
const face = await loadIndexFace()
|
|
271
|
+
if (!face) return null
|
|
272
|
+
let db = null
|
|
273
|
+
try {
|
|
274
|
+
db = face.idx.openSessionIndex()
|
|
275
|
+
if (!db) return null
|
|
276
|
+
try { face.build.ensureSessionIndexed(db, file) } catch { /* 懒保证失败 ⇒ 用现有行判命中(回落路径兜住) */ }
|
|
277
|
+
const rows = face.query.querySessionRows(db, file, filters)
|
|
278
|
+
return rows === null ? null : JSON.stringify(rows.map(indexEntry))
|
|
279
|
+
} catch { return null } finally { if (db) db.close() }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** 当前会话槽文件(`all` 面懒保证目标——触发点①:`SESSION.md` §6.19「查询前对目标会话(`path=<文件>`)/
|
|
283
|
+
* 当前会话(`all`)`ensure`」;射程仍 = 有段档 ⇒ `src=json` 档零动作;无绑定 / 无槽 ⇒ null)。 */
|
|
284
|
+
function currentSessionFile(ctx) {
|
|
285
|
+
const cwd = ctx.agent?.cwd
|
|
286
|
+
const slot = ctx.agent?._slot
|
|
287
|
+
if (!cwd || !Number.isInteger(slot) || slot < 1) return null
|
|
288
|
+
try { return slotPath(cwd, slot) } catch { return null }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** 跨会话检索(`path:"all"`——全部已索引会话):行携 `session.file` 回查锚 + `idx`。 */
|
|
292
|
+
async function queryAllSessions(filters, current = null) {
|
|
293
|
+
const face = await loadIndexFace()
|
|
294
|
+
if (!face) return "Error: session index unavailable — this host has no node:sqlite; query one session with path=<file> instead"
|
|
295
|
+
let db = null
|
|
296
|
+
try {
|
|
297
|
+
db = face.idx.openSessionIndex()
|
|
298
|
+
if (!db) return "Error: session index unavailable — the derived index could not be opened; query one session with path=<file> instead"
|
|
299
|
+
if (current !== null) { try { face.build.ensureSessionIndexed(db, current) } catch { /* 懒保证失败 ⇒ 用现有行(回落面零影响) */ } }
|
|
300
|
+
const rows = face.query.queryAllRows(db, filters)
|
|
301
|
+
return JSON.stringify(rows.map((r) => ({
|
|
302
|
+
session: { slot: r.slot, cwd: r.cwd ?? null, file: r.file, title: r.title ?? null },
|
|
303
|
+
idx: r.idx,
|
|
304
|
+
...indexEntry(r),
|
|
305
|
+
})))
|
|
306
|
+
} catch (e) {
|
|
307
|
+
return `Error: session index query failed — ${e.message}`
|
|
308
|
+
} finally { if (db) db.close() }
|
|
309
|
+
}
|
|
310
|
+
|
|
222
311
|
export const readHistoryTool = {
|
|
223
312
|
name: "read_history",
|
|
224
313
|
description:
|
|
@@ -228,27 +317,32 @@ export const readHistoryTool = {
|
|
|
228
317
|
"Filters combine with AND: role / keyword (case-insensitive substring of message text) / " +
|
|
229
318
|
"tool (tool result messages by name AND the assistant messages that declared the call — pair with tool_call_id / ts for timing) / " +
|
|
230
319
|
"since-until (epoch ms time window; only messages with ts can match) / limit (default 50, clamped to 200) / direction (which end of the matches to take). " +
|
|
231
|
-
"Returns a JSON array in chronological order: [{ts, role, name?, tool_call_id?, content (≈500 chars, truncated marker), tool_calls (
|
|
320
|
+
"Returns a JSON array in chronological order: [{ts, role, name?, tool_call_id?, content (≈500 chars, truncated marker), tool_calls ([{name, arguments}] — arguments capped at 300 chars)}]. " +
|
|
232
321
|
"Messages without ts return ts:null. Content is truncated — the full text is in the session file. " +
|
|
233
322
|
"Cross-session (path, optional): a session file path deep-queries THAT session's history with the same filters " +
|
|
234
|
-
"(relative paths resolve against the project cwd)
|
|
235
|
-
"
|
|
236
|
-
"
|
|
323
|
+
"(relative paths resolve against the project cwd) — answered from the derived session index when it covers that session, " +
|
|
324
|
+
"otherwise from the session file itself (a file over 50,000 messages or 200,000 lines is refused as \"session too large\"). " +
|
|
325
|
+
"\"cwd:<dir>\" lists every session slot stored for that directory — one line per slot: slot number + full session file path + " +
|
|
326
|
+
"title + message count + updatedAt; copy a listed file path into path= to deep-query it. " +
|
|
327
|
+
"\"all\" searches EVERY INDEXED session at once (cross-project; keyword = FTS words / phrases; each row carries session.file for the follow-up deep query) — " +
|
|
328
|
+
"`all` covers the derived index only: sessions never indexed yet are invisible there (query them with path=<file>, or index everything first with the CLI `thincoder session index --rebuild`).\n" +
|
|
237
329
|
SEARCH_FAMILY_GUIDE,
|
|
238
330
|
parameters: {
|
|
239
331
|
type: "object",
|
|
240
332
|
properties: {
|
|
241
333
|
role: { type: "string", enum: ["user", "assistant", "tool"], description: "Only messages with this role." },
|
|
242
|
-
keyword: { type: "string", description: "Case-insensitive substring of the message text (multimodal messages match on their text parts)." },
|
|
334
|
+
keyword: { type: "string", description: "Case-insensitive substring of the message text (multimodal messages match on their text parts); with path=\"all\" it is matched as FTS words / phrases over the indexed text." },
|
|
243
335
|
tool: { type: "string", description: "Only messages for this tool: role=tool messages with name=tool, plus assistant messages that declared a call to it." },
|
|
244
336
|
since: { type: "integer", description: "Earliest ts to match, epoch ms, INCLUSIVE. Messages without ts never match a time window." },
|
|
245
337
|
until: { type: "integer", description: "Latest ts to match, epoch ms, INCLUSIVE. since > until yields an empty result." },
|
|
246
338
|
limit: { type: "integer", description: "Maximum messages to return (default 50; larger values are clamped to 200)." },
|
|
247
339
|
direction: { type: "string", enum: ["oldest", "newest"], description: "Take the limit window from the oldest or newest end of the matched set (default newest)." },
|
|
248
|
-
path: { type: "string", description: "Optional — query another session instead of this one: a session file path (as listed by a \"cwd:<dir>\" call) deep-queries that session; \"cwd:<dir>\" lists that directory's session slots (slot number + full file path + title + message count + updatedAt)." },
|
|
340
|
+
path: { type: "string", description: "Optional — query another session instead of this one: a session file path (as listed by a \"cwd:<dir>\" call) deep-queries that session; \"cwd:<dir>\" lists that directory's session slots (slot number + full file path + title + message count + updatedAt); \"all\" searches every indexed session across projects (FTS words / phrases; rows carry session.file)." },
|
|
249
341
|
},
|
|
250
342
|
},
|
|
251
343
|
readonly: true,
|
|
344
|
+
// 返回类型 = `string`(本会话缺省 / `cwd:` 发现面——与修前逐字同形)∥ `Promise<string>`(索引面:
|
|
345
|
+
// `path=<文件>` 索引优先 / `path:"all"`——需动态装载核索引面)。装配面一律 `await`(dispatch.js 同款)。
|
|
252
346
|
execute(args, ctx) {
|
|
253
347
|
const a = args ?? {}
|
|
254
348
|
if (a.path !== undefined && (typeof a.path !== "string" || a.path.trim().length === 0)) {
|
|
@@ -284,9 +378,13 @@ export const readHistoryTool = {
|
|
|
284
378
|
|
|
285
379
|
const pathArg = typeof a.path === "string" && a.path.trim().length > 0 ? a.path.trim() : null
|
|
286
380
|
if (pathArg !== null) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
381
|
+
if (pathArg.startsWith("cwd:")) return discoverCwd(pathArg.slice("cwd:".length), baseCwd)
|
|
382
|
+
const filters = { keyword, role, tool, since, until, limit, direction }
|
|
383
|
+
if (pathArg === ALL_PATH) return queryAllSessions(filters, currentSessionFile(ctx))
|
|
384
|
+
const file = isAbsolute(pathArg) ? pathArg : resolve(baseCwd, pathArg)
|
|
385
|
+
// 索引优先(命中 ⇒ SQL 检索);未命中 ⇒ 既有 JSON 路径 + 两道护栏(逐字保留——回落面)
|
|
386
|
+
return queryIndexedSession(file, filters)
|
|
387
|
+
.then((indexed) => (indexed !== null ? indexed : querySessionFile(pathArg, { role, kwRe, tool, since, until, direction, limit }, baseCwd)))
|
|
290
388
|
}
|
|
291
389
|
|
|
292
390
|
// 本会话(无 path):绑定记录存储 → 方向流式迭代(磁盘为准——全量可见、内存窗口外
|
package/agent-tools/settings.mjs
CHANGED
|
@@ -18,7 +18,8 @@ const SENSITIVE_SEGMENT = /(^|[._-])(api[_-]?key|key|token|secret|password|autho
|
|
|
18
18
|
const SENSITIVE_FAMILY = /(^|[._-])(headers|env)($|[._-])/i
|
|
19
19
|
const MASKED = "••••(masked)"
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/** #58(hygiene-sweep 批):谓词/标记导出——CLI `/mcp` 表单现值脱敏同源(`cmd-mcp-form.mjs`)。 */
|
|
22
|
+
export function isSensitiveKey(path) {
|
|
22
23
|
return SENSITIVE_SEGMENT.test(path) || SENSITIVE_FAMILY.test(path)
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -184,7 +185,8 @@ function flatten(obj, prefix = "", out = []) {
|
|
|
184
185
|
|
|
185
186
|
/** 行格式化:`key = value (type)`——敏感键值遮罩 */
|
|
186
187
|
function formatLine({ path, value }) {
|
|
187
|
-
|
|
188
|
+
// #58(hygiene-sweep 批):对象值渲染收正(原 `[object Object]`——JSON 化;敏感键仍恒遮)
|
|
189
|
+
const shown = isSensitiveKey(path) ? MASKED : value !== null && typeof value === "object" ? JSON.stringify(value) : value
|
|
188
190
|
return `${path} = ${shown} (${Array.isArray(value) ? "array" : typeof value})`
|
|
189
191
|
}
|
|
190
192
|
|
|
@@ -43,7 +43,7 @@ export function enqueueAsk(owner, key, ask) {
|
|
|
43
43
|
return chain
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Async pool limits per role domain (AGENT-LOOP-
|
|
46
|
+
// Async pool limits per role domain (AGENT-LOOP-ASYNC-POOL.md §6.10 — R14, 2026-09-06):
|
|
47
47
|
// the old single cap (ASYNC_SUBAGENT_LIMIT = 4, §15 D-A4) evolved into two
|
|
48
48
|
// independent pools — eng-coder 4 / other roles 4 (user ruling "eng-coder 四路,
|
|
49
49
|
// 其他 4 路") — a full engCoder pool never blocks an explore spawn and vice versa
|
|
@@ -57,8 +57,8 @@ export const ASYNC_POOL_LIMITS = { engCoder: 4, other: 4 }
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Role → pool domain (single source of truth — §11.1 修正 #8): the CLI role
|
|
60
|
-
*
|
|
61
|
-
* (mode-filtered: normal → explore/plan/coder, engineering → explore/
|
|
60
|
+
* whitelist is subagent.mjs's ROLES = { explore, plan, coder, eng-coder, eng-designer }
|
|
61
|
+
* (mode-filtered: normal → explore/plan/coder, engineering → explore/eng-designer/eng-coder).
|
|
62
62
|
* eng-coder → engCoder pool; every other role (including unknown roles — fail-safe)
|
|
63
63
|
* → other pool. escalate spawns its expert internally (role "coder" — other pool).
|
|
64
64
|
*/
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* subagent-spawn.mjs — spawn 路径装配(2026-09-05 module-split:subagent.mjs 726
|
|
3
|
-
* > 500 硬限——spawn 前置 helpers(
|
|
4
|
-
*
|
|
3
|
+
* > 500 硬限——spawn 前置 helpers(effectiveSubagentModel/resolveDesignSlot——审计块
|
|
4
|
+
* 构造器 2026-09-22 起另立 ./audit-block.mjs)+ §20 调度参数准入(prepareScheduling)+ child 装配
|
|
5
5
|
* (buildSpawnChild)verbatim 迁入(仅闭包变量参数化),语义零变;executeAsyncSpawn
|
|
6
6
|
* 另在 subagent-run.mjs。subagent.mjs execute 经本文件 import 调用。
|
|
7
7
|
* 2026-09-07:executeConsumeDesignAction 消费执行器(token 链终消费制——与 spawn 侧
|
|
8
8
|
* slot 族同域——ENGINEERING-MODE.md §2.6 F1——removeDesignTokenSlot 自 token-ttl.mjs)。
|
|
9
|
+
* 2026-09-22(台账 #23 · AGENT-LOOP-SUBAGENT.md §6.26):审计块构造器(summarizeEngTaskBook
|
|
10
|
+
* + buildAuditBlock)外提 `./audit-block.mjs`;spawn 级固定机制性指令改走 system 固块
|
|
11
|
+
* (`child._spawnSystemBlock` 单点绑定——消费点 = 核 `prepareRun`),`input` 只留任务书。
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
import { resolve } from "node:path"
|
|
@@ -27,57 +30,9 @@ import {
|
|
|
27
30
|
// M5 F2(ENGINEERING-MODE-V2-MODULE-DELEGATION §2.3):任务书六强制字段校验本体落
|
|
28
31
|
// spawn-gates.mjs(纯谓词零依赖)——本处只加 import 调用。
|
|
29
32
|
import { validateTaskBookFields } from "./spawn-gates.mjs"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* audit spawn — the three audit-relevant elements VERBATIM (design doc paths /
|
|
34
|
-
* affected-file list / acceptance criteria); verbose context/background is
|
|
35
|
-
* dropped (the auditor can read the design docs themselves — they stay
|
|
36
|
-
* available outside this input). Independence preserved: the input is
|
|
37
|
-
* _engTaskInput (mechanically kept by the parent spawn) — never the
|
|
38
|
-
* eng-coder's self-report. Sections are located by header marker, prioritizing
|
|
39
|
-
* header lines (structured task books: "## 文件清单 …") and falling back to
|
|
40
|
-
* inline markers (flat one-line task books); a section runs to the next header
|
|
41
|
-
* of the SAME OR HIGHER level ("## 文件清单" survives a "### 修改" sub-header).
|
|
42
|
-
* Marker not found → the section is reported as missing (never fabricate).
|
|
43
|
-
*/
|
|
44
|
-
function summarizeEngTaskBook(taskInput) {
|
|
45
|
-
if (!taskInput) return "(unavailable)"
|
|
46
|
-
const SECTIONS = [
|
|
47
|
-
{ name: "Design docs involved", markers: [/Docs? involved/i, /涉及文档/] },
|
|
48
|
-
{ name: "Affected-file list", markers: [/Files? (?:list|to (?:modify|change)|modified)/i, /受影响文件/, /文件清单/, /涉及文件/] },
|
|
49
|
-
{ name: "Acceptance criteria", markers: [/Acceptance(?: criteria)?/i, /验收标准/] },
|
|
50
|
-
]
|
|
51
|
-
const lines = taskInput.split("\n")
|
|
52
|
-
const headerLevel = (l) => {
|
|
53
|
-
const m = l.match(/^\s*(#{1,6})\s/)
|
|
54
|
-
return m ? m[1].length : 0
|
|
55
|
-
}
|
|
56
|
-
const headerIdx = lines.map((l, i) => (headerLevel(l) > 0 ? i : -1)).filter((i) => i >= 0)
|
|
57
|
-
const boundsFor = (from, level) => {
|
|
58
|
-
for (const j of headerIdx) {
|
|
59
|
-
if (j > from && (level === 0 || headerLevel(lines[j]) <= level)) return j
|
|
60
|
-
}
|
|
61
|
-
return lines.length
|
|
62
|
-
}
|
|
63
|
-
const out = []
|
|
64
|
-
for (const { name, markers } of SECTIONS) {
|
|
65
|
-
let from = -1
|
|
66
|
-
let level = 0
|
|
67
|
-
for (const i of headerIdx) {
|
|
68
|
-
if (markers.some((m) => m.test(lines[i]))) { from = i; level = headerLevel(lines[i]); break }
|
|
69
|
-
}
|
|
70
|
-
if (from === -1) {
|
|
71
|
-
for (let i = 0; i < lines.length; i++) {
|
|
72
|
-
if (markers.some((m) => m.test(lines[i]))) { from = i; level = 0; break }
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (from === -1) { out.push(`${name}: (not found in the parent task book)`); continue }
|
|
76
|
-
const body = lines.slice(from, boundsFor(from, level)).join("\n").trim()
|
|
77
|
-
out.push(body || `${name}: (empty section)`)
|
|
78
|
-
}
|
|
79
|
-
return out.join("\n\n")
|
|
80
|
-
}
|
|
33
|
+
// 台账 #23(AGENT-LOOP-SUBAGENT.md §6.26 · D23-3):审计块构造器外提(`summarizeEngTaskBook`
|
|
34
|
+
// + `buildAuditBlock` 纯函数)——spawn 档只做「收集 + 绑定」;块文本逐字搬移(D23-4)。
|
|
35
|
+
import { buildAuditBlock } from "./audit-block.mjs"
|
|
81
36
|
|
|
82
37
|
/**
|
|
83
38
|
* Effective subagent model override for a role (CLI parity shared with VS Code):
|
|
@@ -238,8 +193,8 @@ export function prepareScheduling(parent, filesRaw, dependsRaw, wantAsync) {
|
|
|
238
193
|
/**
|
|
239
194
|
* §20 准入通过后的 child 装配(2026-09-05 module-split——自 execute 参数化提取,
|
|
240
195
|
* 原 318-488 段 verbatim——语义零变)。副作用保留:relay 取号(sync 支 allocRelay——
|
|
241
|
-
* `[model]` 出生声明归 SYNC-CANCEL 单点,#133)/ 子代理 _logId / _engTaskInput 携带
|
|
242
|
-
*
|
|
196
|
+
* `[model]` 出生声明归 SYNC-CANCEL 单点,#133)/ 子代理 _logId / _engTaskInput 携带 /
|
|
197
|
+
* _spawnSystemBlock 绑定(台账 #23——spawn 固块单点)全部在此发生。返回阻塞/异步两路径共用的
|
|
243
198
|
* { child, input, childOpts, childRunOpts, relayPrefix, childProvider }。
|
|
244
199
|
*/
|
|
245
200
|
export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, dependsOn, engAuditAttempt) {
|
|
@@ -386,60 +341,33 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
|
|
|
386
341
|
// git 只读变体亦随裁定废弃——D-AG5)。顶层主 agent 注入保留(§3 prepareRun——
|
|
387
342
|
// setup.mjs depth===0——D-AG7 范围边界)。
|
|
388
343
|
let input = args.context ? `Context:\n${args.context}\n\nTask:\n${args.task}` : args.task
|
|
389
|
-
// §
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
344
|
+
// 台账 #23(AGENT-LOOP-SUBAGENT.md §6.26 · D23-1):**spawn 级固定机制性指令改走 system 固块**——
|
|
345
|
+
// 两处(批次档路径行 + 审计模板块)原随 user 首条下发,压缩把首条并入摘要即丢(长任务中后期
|
|
346
|
+
// 既看不到批次档路径、也看不到审计模板 ⇒ 过度勘察的真病根);固块值在 child 生命周期内恒定
|
|
347
|
+
// ⇒ run 内 system 前缀逐字节稳定(前缀缓存不破),且不在 `agent.history` 内 ⇒ 压缩结构上吞不掉。
|
|
348
|
+
// 收集序 = 原拼接序(批次档行 → 审计块);空数组 ⇒ 不绑定(depth-0 / explore / plan / consult
|
|
349
|
+
// 的 system 逐字节同改前——§6.26 缓存契约兼容证明)。
|
|
350
|
+
const spawnBlocks = []
|
|
351
|
+
// §2.11 第 1 点(FR16 载体 + FR17 铁律 #5 的机械面):批次档**绝对路径**下发给工程角色
|
|
352
|
+
// (eng-coder 实现 / eng-designer 写稿)——任务文本本身不动(批次档 §2 才是任务书本体;
|
|
353
|
+
// 本行让子代理"拿到本档路径")。行文不含 summarizeEngTaskBook 的三组段 marker
|
|
354
|
+
// (Docs involved / Files list / Acceptance criteria)——段匹配不受影响。仅工程角色注入。
|
|
355
|
+
if (engineeringRole) spawnBlocks.push(`Batch record (batchDoc): ${batchDocAbs}`)
|
|
395
356
|
// §18 D-E2 ③ (round4 #4, T-E13/T-E15): an eng-coder audit spawn's task book is
|
|
396
357
|
// the eng-coder's OWN spawn task — mechanically kept as _engTaskInput by the
|
|
397
358
|
// parent spawn and injected as the D-TS5 A2 mechanical summary (design docs /
|
|
398
359
|
// affected-file list / acceptance criteria verbatim, verbose context dropped)
|
|
399
360
|
// — ∪ the mechanically tracked _touchedFiles — NEVER the eng-coder's
|
|
400
361
|
// self-written list: a self-report could omit exactly the out-of-scope file
|
|
401
|
-
// the audit must catch.
|
|
402
|
-
if (engAuditAttempt !== null)
|
|
403
|
-
const touched = (ctx.agent._touchedFiles ?? []).map((f) => `- ${f}`).join("\n") || "- (none yet)"
|
|
404
|
-
input += `\n\n[Audit scope — mechanical context, independent of the eng-coder's self-report:]\n` +
|
|
405
|
-
// §18.7 D-TS4 A1:审计指令模板(四类偏差 + 范围限制 + 校验清单格式)——审计语义
|
|
406
|
-
// 不再靠模型自悟;范围限制是 §18.5 D-AG3 声明(下方 Zero-git scope authority)
|
|
407
|
-
// 的同源一句指注,不重复声明。
|
|
408
|
-
`[Audit instructions — mechanical template:]\n` +
|
|
409
|
-
`You are auditing an eng-coder delivery against its approved design — audit for EXACTLY these four deviation categories:\n` +
|
|
410
|
-
`- PARTIAL: an acceptance criterion implemented partially or not at all;\n` +
|
|
411
|
-
`- SILENT-SIMPLIFICATION: a "simpler approximation" of a specified behavior substituted for the spec;\n` +
|
|
412
|
-
`- DOC-DRIFT: code changed without the owning design-doc section (module map / affected-files table) updated in the same delivery;\n` +
|
|
413
|
-
`- OUT-OF-LIST: changes outside the approved file list.\n` +
|
|
414
|
-
`Audit scope = _touchedFiles above UNION the files confirmed by the parent task book (single source — the Zero-git scope authority note below; NOT a second copy): ` +
|
|
415
|
-
`workspace changes not listed there are unrelated to this delivery and are NOT grounds for an out-of-list finding.\n` +
|
|
416
|
-
`Scope discipline (F-TS6 A1): read ONLY the audited files and the design-doc sections relevant to this delivery — do NOT re-read whole documents.\n` +
|
|
417
|
-
`Every deviation item MUST be fieldized: file:line + design reference (doc path + section/AC id) + severity + evidence (quoted code or doc text).\n` +
|
|
418
|
-
// §18.7 D-TS5 A2:任务书从全量 verbatim 改机械摘要块(三要素逐字——排除冗长上下文)。
|
|
419
|
-
`[Parent spawn task book — mechanical summary: design docs + affected-file list + acceptance criteria verbatim; verbose context/background dropped — the design docs are still available for reading outside this input:]\n` +
|
|
420
|
-
`${summarizeEngTaskBook(ctx.agent._engTaskInput)}\n` +
|
|
421
|
-
`Files actually touched by the eng-coder (mechanical union — audit these against the file list):\n${touched}\n` +
|
|
422
|
-
// §18.5 D-AG3(2026-09-04):审计零 git 范围权威声明——本审计任务零 git(不注入
|
|
423
|
-
// git 上下文——§18.5 全角色零 git);_touchedFiles 为审计范围;工作区未列于
|
|
424
|
-
// _touchedFiles 的改动与本任务无关,不作超清单依据(VS Code auditTaskBook 同款措辞)。
|
|
425
|
-
"Zero-git scope authority: this audit task receives NO git context — nothing is injected. " +
|
|
426
|
-
"The evidence base is the design documents, the current disk state (read/glob/grep), and the _touchedFiles list above. " +
|
|
427
|
-
"Workspace changes NOT listed in _touchedFiles are unrelated to this delivery — they are NOT grounds for an out-of-file-list finding." +
|
|
428
|
-
// §18.13 D-A1.2:审计预算句——A1 指令模板 + A2 摘要块之后、A3 报告模板之前(定序——评审 #7)。
|
|
429
|
-
// 逐字设计锚(D-A1.2 代码块):只读该读的——10 轮机械预算——超时报 PROBLEM 下结论。
|
|
430
|
-
// 前导 \n 与 A3 同款块分隔约定(上一句 Zero-git 句末无换行——不触碰既有句)。
|
|
431
|
-
`\n[Audit budget — mechanical]: read ONLY the touched files listed above and the design-doc sections the parent task book names (affected-files table, acceptance criteria, status line). Do NOT read whole documents. Budget = 10 tool rounds max — if you cannot conclude within it, report PROBLEM (inconclusive) rather than continuing to explore.\n` +
|
|
432
|
-
// §18.7 D-TS6 A3:审计输出报告格式模板(三态——字段化行——不让模型自由发挥)。
|
|
433
|
-
`\n[Audit report format — mechanical template:]\n` +
|
|
434
|
-
`Report EXACTLY one of three states:\n` +
|
|
435
|
-
`- CLEAN — no deviation across the four categories: reply the line "Four deviation categories: none found." (四类偏差均未发现);\n` +
|
|
436
|
-
`- DEVIATIONS — one row per deviation, every row fieldized: | category | file:line | design reference | severity | evidence |;\n` +
|
|
437
|
-
`- PROBLEM — the audit itself could not run / inconclusive: state what blocked it.\n`
|
|
438
|
-
}
|
|
362
|
+
// the audit must catch. 块文本(含模板逐字)住 `./audit-block.mjs`(D23-3 外提)。
|
|
363
|
+
if (engAuditAttempt !== null) spawnBlocks.push(buildAuditBlock(ctx))
|
|
439
364
|
// The child's own task input rides the child object: an eng-coder's audit
|
|
440
365
|
// spawns reuse it as the task-book SOURCE — injected as the D-TS5 A2
|
|
441
|
-
// mechanical summary, not verbatim (see above).
|
|
366
|
+
// mechanical summary, not verbatim (see above). D23-6:语义 = **纯任务书**(不含固块)。
|
|
442
367
|
if (role === "eng-coder") child._engTaskInput = input
|
|
368
|
+
// spawn 固块单点绑定(sync / async 同点——buildSpawnChild 唯一调用点):此后只读 ⇒ 同一
|
|
369
|
+
// child 的任意 run(resume 续跑 / 报告追问重跑)逐字节恒定;消费点 = 核 `prepareRun`。
|
|
370
|
+
if (spawnBlocks.length > 0) child._spawnSystemBlock = spawnBlocks.join("\n\n")
|
|
443
371
|
|
|
444
372
|
// Relay content/reasoning/tool/output to the parent TUI via the unified spawn-child
|
|
445
373
|
// pipeline (AGENT-LOOP.md §7.2 D3). Prefix includes a unique id: parallel child agents
|
|
@@ -466,7 +394,7 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
|
|
|
466
394
|
// LOGGING(LOGGING.md):子代理内部事件(子内 llm:*/tool:*)以 childId 归属——
|
|
467
395
|
// agent._logId 随 runAgent 的 logCtx 透出(主文件单文件全记、按 childId grep)。
|
|
468
396
|
child._logId = relayPrefix.slice(0, -1)
|
|
469
|
-
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-
|
|
397
|
+
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 W1——spawn 主路径,sync + async
|
|
470
398
|
// 共用):子 → 父在飞通道装配单点(label = relay 前缀去尾;`sync` 供工具返回注分形)。
|
|
471
399
|
child._upstream = { parent, label: relayPrefix.slice(0, -1), sync: !wantAsync }
|
|
472
400
|
const childOpts = {
|
package/agent-tools/task.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// F-CC4(CONTEXT-COMPACTION.md §6.16.5):task 列表变更 ⇒ 方向转换轻推(本档只调用;文案/去重单源 = agent-tools/context.mjs)
|
|
2
|
+
import { pushContextNudge } from "./context.mjs"
|
|
3
|
+
|
|
1
4
|
/** Common synonyms LLMs tend to use — normalize to canonical values */
|
|
2
5
|
const STATUS_ALIASES = {
|
|
3
6
|
completed: "done",
|
|
@@ -54,6 +57,13 @@ export const taskTool = {
|
|
|
54
57
|
},
|
|
55
58
|
readonly: true,
|
|
56
59
|
async execute(args, ctx) {
|
|
60
|
+
// F10(工程模式机械停用——双层门的 **execute 层兜底**;先例 = escalate `subagent-actions.mjs:349`):
|
|
61
|
+
// 前置位 = 本行以下全部副作用之前(alias 归一 / `_taskPushbacks` 归零 / `_onTaskUpdate` / 轻推)
|
|
62
|
+
// ⇒ 零副作用(列表不变);装配层已摘(`agent/family-tools.mjs` 工程分支不入表,KD8)。
|
|
63
|
+
// 普通模式(engineering 非真)逐字走原路径。
|
|
64
|
+
if (ctx?.agent?.config?.agent?.engineering) {
|
|
65
|
+
return "Error: engineering mode is ON — task is unavailable (the batch record + the ledger are the tracking authority in engineering mode). Update progress in the batch record (§5/§6) or the ledger (`/ledger`); task lists go stale in this mode's parallel structure."
|
|
66
|
+
}
|
|
57
67
|
// Keep only non-done items + the 3 most recently completed (for context reference), max 20 to prevent accumulation
|
|
58
68
|
const warnings = []
|
|
59
69
|
const raw = (args.items ?? []).map((it) => {
|
|
@@ -78,6 +88,7 @@ export const taskTool = {
|
|
|
78
88
|
ctx.agent.tasks = items
|
|
79
89
|
ctx.agent._taskPushbacks = 0 // task list changed — the completion gate earns a fresh reminder
|
|
80
90
|
ctx.agent._onTaskUpdate?.(items)
|
|
91
|
+
if (ctx.depth === 0) pushContextNudge(ctx.agent) // F-CC4 轻推(§6.16.5——depth-0 门:子代理拿不到 context 工具)
|
|
81
92
|
const done = items.filter((i) => i.status === "done").length
|
|
82
93
|
const open = items.length - done
|
|
83
94
|
const warningText = warnings.length > 0 ? ` ⚠️ ${warnings.join("; ")}` : ""
|
package/agent-tools.mjs
CHANGED
|
@@ -14,6 +14,9 @@ export { timerTool } from "./agent-tools/timer.mjs"
|
|
|
14
14
|
export { advisorTool } from "./agent-tools/advisor.mjs"
|
|
15
15
|
export { engTool } from "./agent-tools/eng.mjs"
|
|
16
16
|
export { readHistoryTool } from "./agent-tools/read-history.mjs"
|
|
17
|
+
// 模型主动整理上下文(CONTEXT-COMPACTION.md §6.16 · F-CC5):单工具三操作 stats / prune / compact
|
|
18
|
+
// ——depth-0 段挂载(子代理面裁见 §6.16.6 `family-tools.mjs` `depthOnly`)。
|
|
19
|
+
export { contextTool } from "./agent-tools/context.mjs"
|
|
17
20
|
export { batchTool } from "./agent-tools/batch.mjs"
|
|
18
21
|
// 批次档生命周期工具(BATCH-RECORD §4.3 挂载表——主名 `batch` 单工具四 action:depth-0 主 agent
|
|
19
22
|
// create/close + append §1/§4/§6 + status §1(轮 2 裁定②:§4/§6 状态面走普通文档写)(D-BR18);
|
|
@@ -22,6 +25,6 @@ export { batchTool } from "./agent-tools/batch.mjs"
|
|
|
22
25
|
// CORE-UNIFICATION TOOLS #83(统一登记册——VSC `agent-tools/index.mjs:15` 含 consult 家族;
|
|
23
26
|
// CLI 原把 consult 另挂 `agent/setup.mjs:173` ⇒ 归位:登记册即单一来源,装配方只读本档)。
|
|
24
27
|
export { consultStartTool, consultStopTool } from "./agent-tools/consult.mjs"
|
|
25
|
-
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-
|
|
28
|
+
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 装配接线):子代理上行通道工具
|
|
26
29
|
// (depth>0 段装配——`agent/family-tools.mjs`;depth-0 / consult 不装配)。
|
|
27
30
|
export { parentChannelTool } from "./agent-tools/parent-channel.mjs"
|
package/agent.mjs
CHANGED
|
@@ -26,8 +26,9 @@ import {
|
|
|
26
26
|
DEFAULT_MAX_TURNS, DEFAULT_SUBAGENT_TURNS,
|
|
27
27
|
MIN_REPORT_CHARS, REPORT_CONTINUATION,
|
|
28
28
|
AUTO_TURN_DIGEST_DOMAIN,
|
|
29
|
+
AUTO_TURN_DIGEST_DOMAIN_ENG, // §6.15.3(F10 第三面):工程模式 digest 基座变体(task 指针改批次档 + 台账)
|
|
29
30
|
UPSTREAM_TURN_DOMAIN, // §6.27.12.8:上行唤醒轮域文本(手动档——ask 轮不沿用 digest 域文本)
|
|
30
|
-
restoreGuard, //
|
|
31
|
+
restoreGuard, // digest D-S6 读侧单点(AGENT-LOOP-ASYNC-POOL.md §6.8;P2 机制层端差批 §2.18——键清单归核)
|
|
31
32
|
} from "./agent/helpers.mjs"
|
|
32
33
|
// ENG 提醒族 + auto-turn domain 2026-09-05 迁 agent/helpers.mjs(agent.mjs 530 > 500 硬限)
|
|
33
34
|
// PROMPT-SYSTEM 施工② G1(2026-09-10):六件槽位常量装载收口 prompt-overlays.mjs
|
|
@@ -159,13 +160,15 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
159
160
|
agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
|
|
160
161
|
agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
|
|
161
162
|
agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
|
|
163
|
+
// F-CC2(§6.16.2):模型主动压缩的排队槽也是回合级——上一回合末尾未被安全点消费的请求不得跨回合生效
|
|
164
|
+
agent._pendingCompact = null
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
|
-
//
|
|
167
|
+
// digest D-S6 manual tier(AGENT-LOOP-ASYNC-POOL.md §6.8): action-domain reminder (system-driven turn — organize only).
|
|
165
168
|
// §6.27.12.4 ②: an up-stream wake turn answers a RUNNING subagent waiting for the reply — it
|
|
166
169
|
// must not reuse the digest text ("no one is waiting" is the opposite of the truth).
|
|
167
170
|
if ((autoTurn || upstreamTurn) && !agent.autoApprove) {
|
|
168
|
-
agent.history.push({ role: "user", content: upstreamTurn ? UPSTREAM_TURN_DOMAIN : AUTO_TURN_DIGEST_DOMAIN, transient: true })
|
|
171
|
+
agent.history.push({ role: "user", content: upstreamTurn ? UPSTREAM_TURN_DOMAIN : (agent.config?.agent?.engineering === true ? AUTO_TURN_DIGEST_DOMAIN_ENG : AUTO_TURN_DIGEST_DOMAIN), transient: true })
|
|
169
172
|
}
|
|
170
173
|
// eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
|
|
171
174
|
// (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
|
|
@@ -192,8 +195,11 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
192
195
|
// context.mjs compressIfNeeded 经 extras 透出到 logCtx)
|
|
193
196
|
traceDepth: depth,
|
|
194
197
|
}
|
|
198
|
+
// F-CC1(§6.16.4 阈值单源接线):本回合压缩判定所用的阈值 + 固定开销面暂存——
|
|
199
|
+
// `context` 工具的 stats 报**同一口径**(不自行重算第二口径;VSC checkAndCompact 同款暂存)。
|
|
200
|
+
agent._ctxBasis = { threshold, overhead: compactionOverhead }
|
|
195
201
|
|
|
196
|
-
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-
|
|
202
|
+
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 消费点):子 → 父在飞消息的
|
|
197
203
|
// 回合边界注入单点取用一次(模块缓存 ⇒ 每 run 一次代价);动态 import = 零新增静态边
|
|
198
204
|
// (先例 = 上方 injectAsyncResult :113-117)。
|
|
199
205
|
const { drainChildUpstream } = await import("./agent-tools/parent-channel.mjs")
|
package/config-presets.mjs
CHANGED
|
@@ -21,8 +21,8 @@ export const PROVIDER_PRESETS = {
|
|
|
21
21
|
"glm-code": { baseURL: "https://open.bigmodel.cn/api/coding/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM Coding Plan (coding endpoint — same key as GLM; server-forced thinking)" },
|
|
22
22
|
qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen / Alibaba" },
|
|
23
23
|
qwenplan: { baseURL: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen Token Plan (百炼套餐)" },
|
|
24
|
-
mimo: { baseURL: "https://api.xiaomimimo.com/v1", model: "mimo-v2.
|
|
25
|
-
mimoplan: { baseURL: "https://token-plan-cn.xiaomimimo.com/v1", model: "mimo-v2.
|
|
24
|
+
mimo: { baseURL: "https://api.xiaomimimo.com/v1", model: "mimo-v2.6-pro", thinking: { type: "enabled" }, maxTokens: 131072, desc: "MiMo (Xiaomi)" },
|
|
25
|
+
mimoplan: { baseURL: "https://token-plan-cn.xiaomimimo.com/v1", model: "mimo-v2.6-pro", thinking: { type: "enabled" }, maxTokens: 131072, desc: "MiMo Token Plan (小米套餐 — tp- keys; 与按量付费 sk- 密钥不通用)" },
|
|
26
26
|
minimax: { baseURL: "https://api.minimaxi.com/v1", model: "MiniMax-M3", thinking: { type: "adaptive" }, maxTokens: 128000, chatPath: "/text/chatcompletion_v2", desc: "MiniMax" },
|
|
27
27
|
openai: { baseURL: "https://api.openai.com/v1", model: "gpt-4o", desc: "OpenAI" },
|
|
28
28
|
claude: { baseURL: "https://api.anthropic.com/v1", model: "claude-sonnet-4", format: "anthropic", maxTokens: 8192, desc: "Claude (Anthropic)" },
|
package/config.mjs
CHANGED
|
@@ -49,7 +49,7 @@ export const DEFAULTS = {
|
|
|
49
49
|
advisor: { guard: false }, // code review is always available; guard: true pushes completion back until reviewed (opt-in). Also accepts provider/model/thinking/reasoningEffort/timeoutMs overrides. Deprecated: enabled (2026-08-21)
|
|
50
50
|
autoThink: false, // auto-classify task difficulty and set reasoning effort per-turn
|
|
51
51
|
engineering: false, // strict methodology enforcement — design-before-code (design review + user approval before code)
|
|
52
|
-
// Async pool limits (AGENT-LOOP-
|
|
52
|
+
// Async pool limits (AGENT-LOOP-ASYNC-POOL.md §6.10 D-24a/R14 + R13 — POOL-CONFIG-
|
|
53
53
|
// UNIFIED 2026-09-09): { engCoder, other, advisor } — eng-coder pool / other-role
|
|
54
54
|
// pool / advisor-review pool, defaults 4/4/4 (user ruling "eng-coder 四路,其他
|
|
55
55
|
// 4 路" + advisor 评审池并入同一可配体系——三池统一默认 4)。engCoder/other
|