@armadra/agent 0.7.3 → 0.7.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 +37 -0
- package/CHANGELOG.zh-CN.md +37 -0
- package/dist/agent/retry.d.ts +14 -5
- package/dist/agent/retry.js +30 -8
- package/dist/agent/session-cache-key.d.ts +8 -0
- package/dist/agent/session-cache-key.js +46 -0
- package/dist/agent/session-cache.d.ts +2 -3
- package/dist/agent/session-cache.js +19 -37
- package/dist/agent/session-classifier.d.ts +3 -2
- package/dist/agent/session-classifier.js +36 -10
- package/dist/agent/session-compaction.d.ts +24 -5
- package/dist/agent/session-compaction.js +54 -20
- package/dist/agent/session-core.d.ts +2 -0
- package/dist/agent/session-run.d.ts +12 -0
- package/dist/agent/session-run.js +29 -10
- package/dist/agent/session-subagent.d.ts +8 -1
- package/dist/agent/session-subagent.js +37 -14
- package/dist/agent/session-sync.d.ts +8 -2
- package/dist/agent/session-sync.js +7 -10
- package/dist/agent/session-tools.js +3 -2
- package/dist/agent/subagent-fork.d.ts +67 -0
- package/dist/agent/subagent-fork.js +87 -0
- package/dist/agent/subagent-registry.d.ts +2 -2
- package/dist/agent/subagent-registry.js +2 -2
- package/dist/agent/tool-availability.d.ts +8 -0
- package/dist/agent/tool-availability.js +8 -0
- package/dist/agent/tool-runner.js +13 -4
- package/dist/agent/worktree.d.ts +8 -0
- package/dist/agent/worktree.js +51 -4
- package/dist/agents/parse.js +6 -1
- package/dist/agents/task-record.js +3 -0
- package/dist/agents/types.d.ts +2 -0
- package/dist/ai/apis/anthropic-messages.js +10 -5
- package/dist/ai/apis/anthropic-request.d.ts +2 -1
- package/dist/ai/apis/anthropic-request.js +9 -3
- package/dist/ai/apis/google-generative-ai.js +4 -2
- package/dist/ai/apis/max-tokens.d.ts +42 -0
- package/dist/ai/apis/max-tokens.js +121 -0
- package/dist/ai/apis/openai-completions.js +10 -5
- package/dist/ai/apis/openai-request.js +1 -1
- package/dist/ai/apis/openai-responses-request.js +1 -1
- package/dist/ai/apis/openai-responses.js +9 -5
- package/dist/ai/apis/shared.d.ts +3 -1
- package/dist/ai/apis/shared.js +21 -3
- package/dist/ai/cache/fingerprint.d.ts +4 -1
- package/dist/ai/cache/fingerprint.js +18 -1
- package/dist/ai/cache/miss.d.ts +2 -1
- package/dist/ai/cache/miss.js +9 -3
- package/dist/ai/cache/reporting.js +5 -1
- package/dist/ai/cache/types.d.ts +10 -2
- package/dist/ai/cache/warmer.d.ts +6 -1
- package/dist/ai/cache/warmer.js +7 -0
- package/dist/ai/context.d.ts +12 -4
- package/dist/ai/context.js +43 -24
- package/dist/ai/fake/fake-provider.d.ts +8 -1
- package/dist/ai/fake/fake-provider.js +19 -7
- package/dist/ai/fake/fake-script.d.ts +2 -0
- package/dist/ai/http.d.ts +17 -9
- package/dist/ai/http.js +33 -13
- package/dist/ai/image-intern.d.ts +29 -0
- package/dist/ai/image-intern.js +58 -0
- package/dist/ai/json-body.d.ts +31 -0
- package/dist/ai/json-body.js +208 -0
- package/dist/ai/overflow.js +5 -0
- package/dist/ai/providers/catalog-data.js +13 -13
- package/dist/ai/providers/catalog.d.ts +33 -0
- package/dist/ai/providers/catalog.js +105 -6
- package/dist/ai/providers/enrich.d.ts +13 -3
- package/dist/ai/providers/enrich.js +87 -31
- package/dist/ai/sse.d.ts +3 -2
- package/dist/ai/sse.js +3 -2
- package/dist/ai/types.d.ts +9 -2
- package/dist/bundle/ama.cjs +3110 -1793
- package/dist/cli/compose-agents.js +3 -0
- package/dist/cli/compose-extensions.js +2 -0
- package/dist/cli/compose-request.d.ts +20 -0
- package/dist/cli/compose-request.js +37 -0
- package/dist/cli/compose-session.js +2 -2
- package/dist/cli/subcommands/model-meta.d.ts +2 -1
- package/dist/cli/subcommands/model-meta.js +3 -2
- package/dist/codemode/host-side.d.ts +11 -4
- package/dist/codemode/host-side.js +25 -6
- package/dist/codemode/tool.d.ts +2 -0
- package/dist/codemode/tool.js +4 -0
- package/dist/config/json-schema.js +9 -2
- package/dist/config/key-docs.js +13 -4
- package/dist/config/schema-w5.js +9 -1
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +19 -3
- package/dist/config/settings-registry.js +6 -0
- package/dist/config/types-w5.d.ts +2 -0
- package/dist/config/types.d.ts +19 -1
- package/dist/config/types.js +4 -0
- package/dist/i18n/catalog.d.ts +13 -2
- package/dist/i18n/messages/cli.d.ts +2 -0
- package/dist/i18n/messages/cli.js +2 -0
- package/dist/i18n/messages/config-keys.d.ts +10 -0
- package/dist/i18n/messages/config-keys.js +12 -2
- package/dist/i18n/messages/config.d.ts +14 -0
- package/dist/i18n/messages/config.js +4 -0
- package/dist/i18n/messages/errors.d.ts +3 -2
- package/dist/i18n/messages/errors.js +15 -2
- package/dist/i18n/messages/report.d.ts +2 -0
- package/dist/i18n/messages/report.js +2 -0
- package/dist/i18n/messages/settings.d.ts +4 -0
- package/dist/i18n/messages/settings.js +6 -2
- package/dist/i18n/messages/subcommands.d.ts +2 -2
- package/dist/i18n/messages/subcommands.js +4 -2
- package/dist/modes/acp/acp-server.js +15 -5
- package/dist/modes/acp/acp-sessions.d.ts +9 -0
- package/dist/modes/acp/acp-sessions.js +25 -0
- package/dist/modes/context-report.js +3 -2
- package/dist/modes/print/json-event.d.ts +12 -1
- package/dist/modes/print/json-event.js +48 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -0
- package/dist/modes/rpc/rpc-mode.js +9 -2
- package/dist/modes/session-report.d.ts +1 -1
- package/dist/modes/session-report.js +12 -6
- package/dist/rpc.d.ts +5 -2
- package/dist/session/line-reader.d.ts +28 -0
- package/dist/session/line-reader.js +82 -0
- package/dist/session/list.d.ts +14 -0
- package/dist/session/list.js +150 -0
- package/dist/session/manager.d.ts +7 -3
- package/dist/session/manager.js +21 -48
- package/dist/session/migrate.d.ts +2 -1
- package/dist/session/migrate.js +2 -1
- package/dist/session/projection.d.ts +16 -3
- package/dist/session/projection.js +96 -29
- package/dist/session/scan.d.ts +3 -3
- package/dist/session/scan.js +9 -18
- package/dist/session/store.d.ts +3 -0
- package/dist/session/store.js +37 -31
- package/dist/session/types.d.ts +7 -2
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.js +5 -4
- package/dist/tools/glob.d.ts +2 -2
- package/dist/tools/glob.js +2 -2
- package/dist/tools/grep.js +4 -6
- package/dist/tools/image-file.js +2 -1
- package/dist/tools/read-lines.d.ts +32 -0
- package/dist/tools/read-lines.js +147 -0
- package/dist/tools/read.d.ts +6 -2
- package/dist/tools/read.js +47 -27
- package/dist/tools/task.d.ts +9 -0
- package/dist/tools/task.js +21 -8
- package/dist/tools/truncate.d.ts +7 -0
- package/dist/tools/truncate.js +11 -0
- package/dist/tools/types.d.ts +13 -0
- package/dist/trace/build-index.js +13 -1
- package/dist/tui/ansi.d.ts +2 -0
- package/dist/tui/ansi.js +7 -3
- package/dist/tui/components/editor-buffer.js +2 -2
- package/dist/tui/components/editor.js +5 -6
- package/dist/tui/keys.js +2 -2
- package/dist/tui/stdin-buffer.js +3 -3
- package/docs/acp.md +5 -1
- package/docs/agents.md +42 -6
- package/docs/codemode.md +4 -2
- package/docs/en/acp.md +3 -2
- package/docs/en/host-api.md +1 -1
- package/docs/en/permissions.md +2 -2
- package/docs/en/providers.md +54 -20
- package/docs/en/rpc.md +23 -7
- package/docs/en/sessions.md +4 -2
- package/docs/en/tui.md +2 -2
- package/docs/permissions.md +4 -1
- package/docs/providers.md +85 -22
- package/docs/rpc.md +22 -6
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +3 -1
- package/docs/tui.md +1 -1
- package/package.json +2 -2
package/dist/session/manager.js
CHANGED
|
@@ -7,17 +7,19 @@
|
|
|
7
7
|
* - `open(file)`:读文件(修复末尾半行)、校验版本、加锁;叶子 = 文件中最后一条条目,
|
|
8
8
|
* 若其后还有 `leaf` 行(`/tree` 换叶子落盘,第三波 A7)则取最后一条 leaf 行。
|
|
9
9
|
* - `setLeaf(id)`:已落盘时追加一行 `leaf{id, timestamp}`;延迟会话在 `flush()` 时补写。
|
|
10
|
-
* - `fork(entryId)`:复制 root → entryId 的分支到新文件(头的 parentSession 指回本文件),
|
|
11
|
-
* 不复制 leaf
|
|
10
|
+
* - `fork(entryId, { head })`:复制 root → entryId 的分支到新文件(头的 parentSession 指回本文件),
|
|
11
|
+
* 不复制 leaf 行;给了 `head` 时它成为新根条目,复制的首条重挂到它下面(fork 子会话的 ama.task);
|
|
12
|
+
* `cwd` 缺省同本会话([ME-A] 隔离的 fork 子会话传 worktree)。
|
|
12
13
|
* - `close()`:释放锁;之后的 append 抛错。
|
|
13
14
|
*/
|
|
14
15
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
15
|
-
import { statSync } from "node:fs";
|
|
16
16
|
import { dirname, join } from "node:path";
|
|
17
|
+
import { internSessionImages } from "../ai/image-intern.js";
|
|
17
18
|
import { AmaError } from "../errors.js";
|
|
18
19
|
import { AMA_VERSION } from "../version.js";
|
|
20
|
+
import { listSessionItems } from "./list.js";
|
|
19
21
|
import { migrateSessionLines } from "./migrate.js";
|
|
20
|
-
import { acquireLock, appendLines,
|
|
22
|
+
import { acquireLock, appendLines, isSubagentSessionFile, listSessionFiles, readSessionLines, sessionDirForCwd, sessionFileName, writeNewSessionFile, } from "./store.js";
|
|
21
23
|
import { buildTree, indexEntries, pathToRoot } from "./tree.js";
|
|
22
24
|
import { SESSION_FORMAT_VERSION } from "./types.js";
|
|
23
25
|
function newEntryId(taken) {
|
|
@@ -79,6 +81,7 @@ export class SessionManager {
|
|
|
79
81
|
try {
|
|
80
82
|
const { lines } = readSessionLines(file, { repair: true });
|
|
81
83
|
const { header, entries, leafId } = migrateSessionLines(lines, file);
|
|
84
|
+
internSessionImages(entries); // D5:同一图片的 base64 只留一份;JSONL 不变
|
|
82
85
|
return new SessionManager(header, entries, { kind: "file", file, lock }, options.now ?? (() => new Date()), leafId);
|
|
83
86
|
}
|
|
84
87
|
catch (error) {
|
|
@@ -93,48 +96,7 @@ export class SessionManager {
|
|
|
93
96
|
}
|
|
94
97
|
/** 只读列出目录下的会话(最新在前);损坏的文件跳过。 */
|
|
95
98
|
static list(dir) {
|
|
96
|
-
|
|
97
|
-
for (const file of listSessionFiles(dir)) {
|
|
98
|
-
try {
|
|
99
|
-
const { header, entries } = migrateSessionLines(readSessionLines(file).lines, file);
|
|
100
|
-
let name;
|
|
101
|
-
let firstPrompt;
|
|
102
|
-
let messageCount = 0;
|
|
103
|
-
for (const entry of entries) {
|
|
104
|
-
if (entry.type === "session_info" && entry.name !== undefined)
|
|
105
|
-
name = entry.name;
|
|
106
|
-
if (entry.type !== "message" || entry.message.role === "system")
|
|
107
|
-
continue;
|
|
108
|
-
messageCount++;
|
|
109
|
-
if (firstPrompt === undefined && entry.message.role === "user") {
|
|
110
|
-
const { content } = entry.message;
|
|
111
|
-
firstPrompt =
|
|
112
|
-
typeof content === "string"
|
|
113
|
-
? content
|
|
114
|
-
: content.map((block) => (block.type === "text" ? block.text : "")).join("");
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const item = {
|
|
118
|
-
id: header.id,
|
|
119
|
-
file,
|
|
120
|
-
cwd: header.cwd,
|
|
121
|
-
createdAt: header.timestamp,
|
|
122
|
-
modifiedAt: statSync(file).mtime.toISOString(),
|
|
123
|
-
messageCount,
|
|
124
|
-
};
|
|
125
|
-
if (name !== undefined)
|
|
126
|
-
item.name = name;
|
|
127
|
-
if (isSubagentSession(header, entries[0]))
|
|
128
|
-
item.subagent = true;
|
|
129
|
-
if (firstPrompt !== undefined)
|
|
130
|
-
item.firstPrompt = firstPrompt.slice(0, 200);
|
|
131
|
-
items.push(item);
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
// 损坏 / 不可读:列表里跳过(sessions show 会给出具体错误)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return items;
|
|
99
|
+
return listSessionItems(dir);
|
|
138
100
|
}
|
|
139
101
|
// -------------------------------------------------------------------------
|
|
140
102
|
// SessionManagerApi
|
|
@@ -206,16 +168,27 @@ export class SessionManager {
|
|
|
206
168
|
setName(name) {
|
|
207
169
|
this.append({ type: "session_info", name });
|
|
208
170
|
}
|
|
209
|
-
fork(entryId) {
|
|
171
|
+
fork(entryId, forkOptions = {}) {
|
|
210
172
|
if (!this.index.has(entryId)) {
|
|
211
173
|
throw new AmaError("invalid_arguments", `no such session entry: ${entryId}`);
|
|
212
174
|
}
|
|
213
175
|
const copied = this.branch(entryId).map((entry) => structuredClone(entry));
|
|
176
|
+
if (forkOptions.head !== undefined) {
|
|
177
|
+
const head = {
|
|
178
|
+
...forkOptions.head,
|
|
179
|
+
id: newEntryId(indexEntries(copied)),
|
|
180
|
+
parentId: null,
|
|
181
|
+
timestamp: this.now().toISOString(),
|
|
182
|
+
};
|
|
183
|
+
if (copied[0] !== undefined)
|
|
184
|
+
copied[0].parentId = head.id;
|
|
185
|
+
copied.unshift(head);
|
|
186
|
+
}
|
|
214
187
|
const options = { now: this.now };
|
|
215
188
|
const parentFile = this.file();
|
|
216
189
|
if (parentFile !== undefined)
|
|
217
190
|
options.parentSession = parentFile;
|
|
218
|
-
const header = SessionManager.newHeader(this.cwd, options, this.now());
|
|
191
|
+
const header = SessionManager.newHeader(forkOptions.cwd ?? this.cwd, options, this.now());
|
|
219
192
|
let storage;
|
|
220
193
|
if (this.storage.kind === "memory")
|
|
221
194
|
storage = { kind: "memory" };
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - 缺头 / 头损坏 / 条目缺 id → 拒绝,不猜测、不重写文件。
|
|
8
8
|
* - [W3-C1b] `leaf` 行(`/tree` 位置,第三波 A7)不是条目:不进 entries;最后一条 leaf 行若在
|
|
9
9
|
* 最后一条条目之后,作为 `leafId` 返回(之后又追加了条目则作废)。id 须为字符串或 null。
|
|
10
|
-
* 将来格式升级时在这里加「旧版本 →
|
|
10
|
+
* 将来格式升级时在这里加「旧版本 → 当前」的内存迁移(文件只追加,不回写);`list.ts` 不经本函数、
|
|
11
|
+
* 自己按 v1 规则流式统计,升级格式时要同时改它(docs/memory-plan.md R6)。
|
|
11
12
|
*/
|
|
12
13
|
import type { SessionEntry, SessionHeader, SessionLine } from "./types.js";
|
|
13
14
|
export interface MigratedSession {
|
package/dist/session/migrate.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - 缺头 / 头损坏 / 条目缺 id → 拒绝,不猜测、不重写文件。
|
|
8
8
|
* - [W3-C1b] `leaf` 行(`/tree` 位置,第三波 A7)不是条目:不进 entries;最后一条 leaf 行若在
|
|
9
9
|
* 最后一条条目之后,作为 `leafId` 返回(之后又追加了条目则作废)。id 须为字符串或 null。
|
|
10
|
-
* 将来格式升级时在这里加「旧版本 →
|
|
10
|
+
* 将来格式升级时在这里加「旧版本 → 当前」的内存迁移(文件只追加,不回写);`list.ts` 不经本函数、
|
|
11
|
+
* 自己按 v1 规则流式统计,升级格式时要同时改它(docs/memory-plan.md R6)。
|
|
11
12
|
*/
|
|
12
13
|
import { AmaError } from "../errors.js";
|
|
13
14
|
import { SESSION_FORMAT_VERSION } from "./types.js";
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* `firstKeptEntryId` 到 compaction 之间的非 system 条目,再放 compaction 之后的条目。
|
|
6
6
|
* - `buildProjection(branch)`:对上一步的条目套用 `context_edit`(同一目标在活动分支上最新一条赢;
|
|
7
7
|
* `null` 剔除,字符串只换内容、保留角色与元数据),并把每个条目映射为 `AgentMessage`。
|
|
8
|
-
* 有 compaction
|
|
9
|
-
*
|
|
8
|
+
* 有 compaction 时([ME-B] D4「开头只写一次」):检查点只重放**对话开始前**(首条非 system
|
|
9
|
+
* 条目之前)的 system 消息,与压缩前请求的开头逐字节相同;此后到 compaction 之间的补丁合并成一条
|
|
10
|
+
* 合成补丁放在摘要之后,由 normalizeContext 渲染为尾部提醒。合成补丁的 `toolsAdded` 按首次出现
|
|
11
|
+
* 的顺序、用首次发送的声明,工具表与压缩前一致;多次压缩递归成立(都从原始条目重放)。
|
|
12
|
+
* 会话条目里不另存检查点与合成补丁,重放即可得到。
|
|
10
13
|
* - `buildContext(branch)`:投影消息 + 路径上最近的模型 / 思考级别选择。
|
|
11
14
|
*/
|
|
12
15
|
import type { Message, ModelRef, ModelThinkingLevel, SystemMessage, ToolDecl } from "../ai/types.js";
|
|
@@ -42,9 +45,19 @@ export interface SystemState {
|
|
|
42
45
|
sections: Record<string, string>;
|
|
43
46
|
tools: ToolDecl[];
|
|
44
47
|
}
|
|
45
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* 依次应用转录里的 system 消息(首条全量,之后是补丁);没有 system 消息 → undefined。
|
|
50
|
+
* 工具「保留原位、只换内容」(与 normalizeContext 一致);同一条里先加后删(`diffSystem` 的补丁两者
|
|
51
|
+
* 不相交,只有压缩的合成补丁会对同名既加又删:声明过、现已移除)。
|
|
52
|
+
*/
|
|
46
53
|
export declare function replaySystem(messages: Iterable<AgentMessage | Message>): SystemState | undefined;
|
|
47
54
|
/** 把重放结果写成一条完整的 system 消息。 */
|
|
48
55
|
export declare function systemCheckpoint(state: SystemState, timestamp: number): SystemMessage;
|
|
56
|
+
/**
|
|
57
|
+
* 压缩的合成补丁:`head`(对话开始前的状态)→ 之后全部补丁重放的结果。节按差异给;工具先列对话中
|
|
58
|
+
* 首次出现的新名字(首次发送的声明,normalizeContext 冻结的就是它),再列内容有更新的(只更新重放
|
|
59
|
+
* 状态,不改请求工具表);移除 = 声明过而现在不在。无差异 → undefined。
|
|
60
|
+
*/
|
|
61
|
+
export declare function synthesizeSystemPatch(head: SystemState, later: readonly SystemMessage[], timestamp: number): SystemMessage | undefined;
|
|
49
62
|
export declare function buildProjection(branch: readonly SessionEntry[]): Projection;
|
|
50
63
|
export declare function buildContext(branch: readonly SessionEntry[]): ProjectedContext;
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* `firstKeptEntryId` 到 compaction 之间的非 system 条目,再放 compaction 之后的条目。
|
|
6
6
|
* - `buildProjection(branch)`:对上一步的条目套用 `context_edit`(同一目标在活动分支上最新一条赢;
|
|
7
7
|
* `null` 剔除,字符串只换内容、保留角色与元数据),并把每个条目映射为 `AgentMessage`。
|
|
8
|
-
* 有 compaction
|
|
9
|
-
*
|
|
8
|
+
* 有 compaction 时([ME-B] D4「开头只写一次」):检查点只重放**对话开始前**(首条非 system
|
|
9
|
+
* 条目之前)的 system 消息,与压缩前请求的开头逐字节相同;此后到 compaction 之间的补丁合并成一条
|
|
10
|
+
* 合成补丁放在摘要之后,由 normalizeContext 渲染为尾部提醒。合成补丁的 `toolsAdded` 按首次出现
|
|
11
|
+
* 的顺序、用首次发送的声明,工具表与压缩前一致;多次压缩递归成立(都从原始条目重放)。
|
|
12
|
+
* 会话条目里不另存检查点与合成补丁,重放即可得到。
|
|
10
13
|
* - `buildContext(branch)`:投影消息 + 路径上最近的模型 / 思考级别选择。
|
|
11
14
|
*/
|
|
12
15
|
function parseTime(iso) {
|
|
@@ -98,7 +101,11 @@ export function applyReplacement(message, text) {
|
|
|
98
101
|
return message;
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* 依次应用转录里的 system 消息(首条全量,之后是补丁);没有 system 消息 → undefined。
|
|
106
|
+
* 工具「保留原位、只换内容」(与 normalizeContext 一致);同一条里先加后删(`diffSystem` 的补丁两者
|
|
107
|
+
* 不相交,只有压缩的合成补丁会对同名既加又删:声明过、现已移除)。
|
|
108
|
+
*/
|
|
102
109
|
export function replaySystem(messages) {
|
|
103
110
|
let found = false;
|
|
104
111
|
const sections = new Map();
|
|
@@ -113,12 +120,10 @@ export function replaySystem(messages) {
|
|
|
113
120
|
else
|
|
114
121
|
sections.set(name, text);
|
|
115
122
|
}
|
|
123
|
+
for (const tool of message.toolsAdded ?? [])
|
|
124
|
+
tools.set(tool.name, tool);
|
|
116
125
|
for (const name of message.toolsRemoved ?? [])
|
|
117
126
|
tools.delete(name);
|
|
118
|
-
for (const tool of message.toolsAdded ?? []) {
|
|
119
|
-
tools.delete(tool.name);
|
|
120
|
-
tools.set(tool.name, tool);
|
|
121
|
-
}
|
|
122
127
|
}
|
|
123
128
|
if (!found)
|
|
124
129
|
return undefined;
|
|
@@ -131,39 +136,101 @@ export function systemCheckpoint(state, timestamp) {
|
|
|
131
136
|
message.toolsAdded = state.tools.map((tool) => ({ ...tool }));
|
|
132
137
|
return message;
|
|
133
138
|
}
|
|
139
|
+
function sameDecl(a, b) {
|
|
140
|
+
return (a.description === b.description && JSON.stringify(a.parameters) === JSON.stringify(b.parameters));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 压缩的合成补丁:`head`(对话开始前的状态)→ 之后全部补丁重放的结果。节按差异给;工具先列对话中
|
|
144
|
+
* 首次出现的新名字(首次发送的声明,normalizeContext 冻结的就是它),再列内容有更新的(只更新重放
|
|
145
|
+
* 状态,不改请求工具表);移除 = 声明过而现在不在。无差异 → undefined。
|
|
146
|
+
*/
|
|
147
|
+
export function synthesizeSystemPatch(head, later, timestamp) {
|
|
148
|
+
const full = replaySystem([systemCheckpoint(head, timestamp), ...later]) ?? head;
|
|
149
|
+
const sections = {};
|
|
150
|
+
for (const [name, text] of Object.entries(full.sections))
|
|
151
|
+
if (head.sections[name] !== text)
|
|
152
|
+
sections[name] = text;
|
|
153
|
+
for (const name of Object.keys(head.sections))
|
|
154
|
+
if (!(name in full.sections))
|
|
155
|
+
sections[name] = null;
|
|
156
|
+
const sent = new Map(head.tools.map((tool) => [tool.name, tool]));
|
|
157
|
+
const added = [];
|
|
158
|
+
for (const message of later)
|
|
159
|
+
for (const tool of message.toolsAdded ?? []) {
|
|
160
|
+
if (sent.has(tool.name))
|
|
161
|
+
continue;
|
|
162
|
+
sent.set(tool.name, tool);
|
|
163
|
+
added.push(tool);
|
|
164
|
+
}
|
|
165
|
+
const now = new Map(full.tools.map((tool) => [tool.name, tool]));
|
|
166
|
+
for (const [name, tool] of sent) {
|
|
167
|
+
const current = now.get(name);
|
|
168
|
+
if (current !== undefined && !sameDecl(current, tool))
|
|
169
|
+
added.push(current);
|
|
170
|
+
}
|
|
171
|
+
const removed = [...sent.keys()].filter((name) => !now.has(name));
|
|
172
|
+
if (Object.keys(sections).length === 0 && added.length === 0 && removed.length === 0)
|
|
173
|
+
return undefined;
|
|
174
|
+
const patch = { role: "system", sections, timestamp };
|
|
175
|
+
if (added.length > 0)
|
|
176
|
+
patch.toolsAdded = added.map((tool) => ({ ...tool }));
|
|
177
|
+
if (removed.length > 0)
|
|
178
|
+
patch.toolsRemoved = removed;
|
|
179
|
+
return patch;
|
|
180
|
+
}
|
|
134
181
|
// ---------------------------------------------------------------------------
|
|
135
182
|
// 投影
|
|
136
183
|
// ---------------------------------------------------------------------------
|
|
184
|
+
/** 对话开始:首条进上下文且不是 system 的条目(custom_message 算;custom / usage 等不算)。 */
|
|
185
|
+
function startsConversation(entry) {
|
|
186
|
+
const message = entryToMessage(entry);
|
|
187
|
+
return message !== undefined && message.role !== "system";
|
|
188
|
+
}
|
|
189
|
+
function systemMessagesOf(entries) {
|
|
190
|
+
const out = [];
|
|
191
|
+
for (const entry of entries)
|
|
192
|
+
if (entry.type === "message" && entry.message.role === "system")
|
|
193
|
+
out.push(entry.message);
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
/** 压缩之前的 system 状态:开头检查点(对话开始前)+ 合成补丁(之后的全部补丁)。 */
|
|
197
|
+
function compactedSystem(before, timestamp) {
|
|
198
|
+
let headEnd = before.findIndex(startsConversation);
|
|
199
|
+
if (headEnd < 0)
|
|
200
|
+
headEnd = before.length;
|
|
201
|
+
const head = replaySystem(systemMessagesOf(before.slice(0, headEnd)));
|
|
202
|
+
const later = systemMessagesOf(before.slice(headEnd));
|
|
203
|
+
const out = {};
|
|
204
|
+
if (head !== undefined)
|
|
205
|
+
out.checkpoint = systemCheckpoint(head, timestamp);
|
|
206
|
+
if (later.length === 0)
|
|
207
|
+
return out;
|
|
208
|
+
const patch = synthesizeSystemPatch(head ?? { sections: {}, tools: [] }, later, timestamp);
|
|
209
|
+
if (patch !== undefined)
|
|
210
|
+
out.patch = patch;
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
137
213
|
export function buildProjection(branch) {
|
|
138
214
|
const edits = collectContextEdits(branch);
|
|
139
215
|
const at = latestCompactionIndex(branch);
|
|
140
216
|
const compaction = at >= 0 ? branch[at] : undefined;
|
|
141
217
|
const items = [];
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
const state = replaySystem(before);
|
|
149
|
-
if (state !== undefined) {
|
|
150
|
-
items.push({
|
|
151
|
-
entry: compaction,
|
|
152
|
-
message: systemCheckpoint(state, parseTime(compaction.timestamp)),
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
218
|
+
const system = compaction === undefined
|
|
219
|
+
? {}
|
|
220
|
+
: compactedSystem(branch.slice(0, at), parseTime(compaction.timestamp));
|
|
221
|
+
if (compaction !== undefined && system.checkpoint !== undefined)
|
|
222
|
+
items.push({ entry: compaction, message: system.checkpoint });
|
|
156
223
|
for (const entry of buildContextEntries(branch)) {
|
|
157
224
|
let message = entryToMessage(entry);
|
|
158
|
-
if (message
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (edit.replacement
|
|
163
|
-
|
|
164
|
-
message = applyReplacement(message, edit.replacement);
|
|
225
|
+
if (message !== undefined) {
|
|
226
|
+
const edit = edits.get(entry.id);
|
|
227
|
+
if (edit === undefined)
|
|
228
|
+
items.push({ entry, message });
|
|
229
|
+
else if (edit.replacement !== null)
|
|
230
|
+
items.push({ entry, message: applyReplacement(message, edit.replacement) });
|
|
165
231
|
}
|
|
166
|
-
|
|
232
|
+
if (entry === compaction && system.patch !== undefined)
|
|
233
|
+
items.push({ entry: compaction, message: system.patch });
|
|
167
234
|
}
|
|
168
235
|
return { items, messages: items.map((item) => item.message), compaction };
|
|
169
236
|
}
|
package/dist/session/scan.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 与 store.ts / manager.ts 的区别:这里**从不加锁、不修复、不写文件**——正在运行的会话也能读。
|
|
5
5
|
* - `sessionFilesInScope`:按 cwd(只看它的编码子目录)或全部子目录(跳过 `.trash`)列文件,最新在前;
|
|
6
|
-
* - `forEachLine
|
|
6
|
+
* - `forEachLine`:按块读盘、按行回调(不读全文、不 split);提前返回即停止读盘;
|
|
7
7
|
* - `lineType`:ama 写入的行以 `{"type":"…"` 开头(manager 的 append 把 type 放在第一个键),
|
|
8
8
|
* 据此不解析就能跳过不需要的行;其它程序写的行退回 JSON.parse;
|
|
9
9
|
* - `readSessionReadOnly`:头 + 条目 + 叶子(同 migrate.ts 的规则),末尾半行忽略、中间坏行抛错。
|
|
@@ -18,9 +18,9 @@ export declare function sessionFilesInScope(root: string, cwd?: string): string[
|
|
|
18
18
|
export declare function findSessionFileReadOnly(root: string, id: string, cwd?: string): string;
|
|
19
19
|
/**
|
|
20
20
|
* 逐行回调(不含行尾 `\n` / `\r`,跳过空行);`last` 表示文件最后一行且没有换行结尾
|
|
21
|
-
* (可能是写入中途的半行)。回调返回 false
|
|
21
|
+
* (可能是写入中途的半行)。回调返回 false 提前结束——按块读盘(line-reader.ts),停下即不再读。
|
|
22
22
|
*/
|
|
23
|
-
export declare function forEachLine(file: string, visit: (line: string, index: number, last: boolean) => boolean | void): void;
|
|
23
|
+
export declare function forEachLine(file: string, visit: (line: string, index: number, last: boolean) => boolean | void, chunkBytes?: number): void;
|
|
24
24
|
/** ama 写的行:不解析就取出 type(与消息的 role,若紧随其后);不是这种形状时 undefined。 */
|
|
25
25
|
export declare function lineType(line: string): {
|
|
26
26
|
type: string;
|
package/dist/session/scan.js
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 与 store.ts / manager.ts 的区别:这里**从不加锁、不修复、不写文件**——正在运行的会话也能读。
|
|
5
5
|
* - `sessionFilesInScope`:按 cwd(只看它的编码子目录)或全部子目录(跳过 `.trash`)列文件,最新在前;
|
|
6
|
-
* - `forEachLine
|
|
6
|
+
* - `forEachLine`:按块读盘、按行回调(不读全文、不 split);提前返回即停止读盘;
|
|
7
7
|
* - `lineType`:ama 写入的行以 `{"type":"…"` 开头(manager 的 append 把 type 放在第一个键),
|
|
8
8
|
* 据此不解析就能跳过不需要的行;其它程序写的行退回 JSON.parse;
|
|
9
9
|
* - `readSessionReadOnly`:头 + 条目 + 叶子(同 migrate.ts 的规则),末尾半行忽略、中间坏行抛错。
|
|
10
10
|
*/
|
|
11
|
-
import { existsSync,
|
|
11
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
12
12
|
import { basename, join } from "node:path";
|
|
13
13
|
import { AmaError } from "../errors.js";
|
|
14
|
+
import { forEachLineSync } from "./line-reader.js";
|
|
14
15
|
import { migrateSessionLines } from "./migrate.js";
|
|
15
16
|
import { SESSION_FILE_SUFFIX, TRASH_DIR_NAME, listSessionFiles, sessionDirForCwd, sessionIdFromFileName, } from "./store.js";
|
|
16
17
|
import { msg } from "../i18n/index.js";
|
|
@@ -68,26 +69,16 @@ export function findSessionFileReadOnly(root, id, cwd) {
|
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
71
|
* 逐行回调(不含行尾 `\n` / `\r`,跳过空行);`last` 表示文件最后一行且没有换行结尾
|
|
71
|
-
* (可能是写入中途的半行)。回调返回 false
|
|
72
|
+
* (可能是写入中途的半行)。回调返回 false 提前结束——按块读盘(line-reader.ts),停下即不再读。
|
|
72
73
|
*/
|
|
73
|
-
export function forEachLine(file, visit) {
|
|
74
|
-
const text = readFileSync(file, "utf8");
|
|
75
|
-
let start = 0;
|
|
74
|
+
export function forEachLine(file, visit, chunkBytes) {
|
|
76
75
|
let index = 0;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const last = end === -1;
|
|
80
|
-
if (last)
|
|
81
|
-
end = text.length;
|
|
82
|
-
let line = text.slice(start, end);
|
|
83
|
-
if (line.endsWith("\r"))
|
|
84
|
-
line = line.slice(0, -1);
|
|
85
|
-
start = end + 1;
|
|
76
|
+
forEachLineSync(file, (buf, _physical, last) => {
|
|
77
|
+
const line = buf.toString("utf8");
|
|
86
78
|
if (line.trim() === "")
|
|
87
|
-
continue;
|
|
88
|
-
if (visit(line, index++, last) === false)
|
|
89
79
|
return;
|
|
90
|
-
|
|
80
|
+
return visit(line, index++, last);
|
|
81
|
+
}, chunkBytes);
|
|
91
82
|
}
|
|
92
83
|
const TYPE_PREFIX = '{"type":"';
|
|
93
84
|
/** ama 写的行:不解析就取出 type(与消息的 role,若紧随其后);不是这种形状时 undefined。 */
|
package/dist/session/store.d.ts
CHANGED
|
@@ -23,8 +23,11 @@ export interface ReadResult {
|
|
|
23
23
|
/** 末尾半行被截掉时为 true。 */
|
|
24
24
|
repairedTail: boolean;
|
|
25
25
|
}
|
|
26
|
+
/** 空白行(同旧口径 `trim() === ""`):ASCII 空白直接判定,遇到非 ASCII 字节才解码再 trim。 */
|
|
27
|
+
export declare function isBlankLine(line: Buffer): boolean;
|
|
26
28
|
/**
|
|
27
29
|
* 读并解析 JSONL。`repair: true` 时把末尾半行从文件里截掉(只截最后一行,且只在它无法解析时)。
|
|
30
|
+
* 按块逐行读(line-reader.ts,docs/memory-plan.md §2.4):不生成全文字符串与 split 数组。
|
|
28
31
|
*/
|
|
29
32
|
export declare function readSessionLines(file: string, options?: {
|
|
30
33
|
repair?: boolean;
|
package/dist/session/store.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, readSync, rmSync, statSync, truncateSync, writeFileSync, writeSync, } from "node:fs";
|
|
11
11
|
import { basename, dirname, join } from "node:path";
|
|
12
12
|
import { AmaError } from "../errors.js";
|
|
13
|
+
import { forEachLineSync } from "./line-reader.js";
|
|
13
14
|
export const SESSION_FILE_SUFFIX = ".jsonl";
|
|
14
15
|
export const TRASH_DIR_NAME = ".trash";
|
|
15
16
|
export const TRASH_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -33,46 +34,51 @@ export function sessionIdFromFileName(file) {
|
|
|
33
34
|
const at = stem.indexOf("_");
|
|
34
35
|
return at < 0 ? undefined : stem.slice(at + 1);
|
|
35
36
|
}
|
|
37
|
+
/** 空白行(同旧口径 `trim() === ""`):ASCII 空白直接判定,遇到非 ASCII 字节才解码再 trim。 */
|
|
38
|
+
export function isBlankLine(line) {
|
|
39
|
+
for (const byte of line) {
|
|
40
|
+
if (byte === 0x20 || (byte >= 0x09 && byte <= 0x0d))
|
|
41
|
+
continue;
|
|
42
|
+
return byte >= 0x80 && line.toString("utf8").trim() === "";
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
36
46
|
/**
|
|
37
47
|
* 读并解析 JSONL。`repair: true` 时把末尾半行从文件里截掉(只截最后一行,且只在它无法解析时)。
|
|
48
|
+
* 按块逐行读(line-reader.ts,docs/memory-plan.md §2.4):不生成全文字符串与 split 数组。
|
|
38
49
|
*/
|
|
39
50
|
export function readSessionLines(file, options = {}) {
|
|
40
|
-
let text;
|
|
41
|
-
try {
|
|
42
|
-
text = readFileSync(file, "utf8");
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
throw new AmaError("session_not_found", `cannot read session file ${file}`, { cause: error });
|
|
46
|
-
}
|
|
47
|
-
const rawLines = text.split("\n");
|
|
48
|
-
const endsWithNewline = text.endsWith("\n");
|
|
49
|
-
if (endsWithNewline)
|
|
50
|
-
rawLines.pop();
|
|
51
51
|
const lines = [];
|
|
52
52
|
let repairedTail = false;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
53
|
+
let unterminated = false;
|
|
54
|
+
try {
|
|
55
|
+
forEachLineSync(file, (buf, index, last, offset) => {
|
|
56
|
+
if (last)
|
|
57
|
+
unterminated = true;
|
|
58
|
+
if (isBlankLine(buf))
|
|
59
|
+
return;
|
|
60
|
+
try {
|
|
61
|
+
lines.push(JSON.parse(buf.toString("utf8")));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (last) {
|
|
65
|
+
repairedTail = true;
|
|
66
|
+
if (options.repair === true)
|
|
67
|
+
truncateSync(file, offset);
|
|
68
|
+
return false;
|
|
67
69
|
}
|
|
68
|
-
|
|
70
|
+
throw new AmaError("session_corrupt", `${file}:${index + 1}: invalid JSON line`, {
|
|
71
|
+
cause: error,
|
|
72
|
+
});
|
|
69
73
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof AmaError)
|
|
78
|
+
throw error;
|
|
79
|
+
throw new AmaError("session_not_found", `cannot read session file ${file}`, { cause: error });
|
|
74
80
|
}
|
|
75
|
-
if (!repairedTail &&
|
|
81
|
+
if (!repairedTail && unterminated && options.repair === true) {
|
|
76
82
|
// 最后一行完整但缺 LF:补上,保证后续追加另起一行。
|
|
77
83
|
appendRaw(file, "\n");
|
|
78
84
|
}
|
package/dist/session/types.d.ts
CHANGED
|
@@ -199,7 +199,12 @@ export interface SessionManagerApi {
|
|
|
199
199
|
getTree(): SessionTreeNode[];
|
|
200
200
|
name(): string | undefined;
|
|
201
201
|
setName(name: string): void;
|
|
202
|
-
/**
|
|
203
|
-
|
|
202
|
+
/**
|
|
203
|
+
* 复制 root → entryId 的分支到新文件(parentSession 指回);`head` 作为新根条目,复制的首条重挂到
|
|
204
|
+
* 它下面([ME-C0] fork 子会话的 `custom{ama.task}` 标记)。
|
|
205
|
+
*/
|
|
206
|
+
fork(entryId: string, options?: {
|
|
207
|
+
head?: SessionEntryInput;
|
|
208
|
+
}): SessionManagerApi;
|
|
204
209
|
}
|
|
205
210
|
export {};
|
package/dist/tools/bash.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - `spawn(shell, args, { detached: !win, stdio: [ignore, pipe, pipe] })`,stdout / stderr 合流;
|
|
5
5
|
* - 超时(缺省 120 s,上限 600 s)与 abort 都走 `killProcessTree`:SIGTERM → 2 s → SIGKILL;
|
|
6
|
-
* - 滚动尾部节流后经 `ctx.onUpdate` 流出;超限(2000 行 / 50 KB
|
|
6
|
+
* - 滚动尾部节流后经 `ctx.onUpdate` 流出;超限(2000 行 / 50 KB 与会话结果上限减余量取小)尾截断并落全文;
|
|
7
7
|
* - 结构化结果 `{ output, exit_code, truncated, full_output_path?, wall_time_seconds }`;
|
|
8
8
|
* 信号退出 `128 + signo`;非零退出码 → isError;
|
|
9
9
|
* - 注入 `AMA_*` 环境变量(shell.ts)。shell 退出后不等仍占着管道的后台进程。
|
package/dist/tools/bash.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - `spawn(shell, args, { detached: !win, stdio: [ignore, pipe, pipe] })`,stdout / stderr 合流;
|
|
5
5
|
* - 超时(缺省 120 s,上限 600 s)与 abort 都走 `killProcessTree`:SIGTERM → 2 s → SIGKILL;
|
|
6
|
-
* - 滚动尾部节流后经 `ctx.onUpdate` 流出;超限(2000 行 / 50 KB
|
|
6
|
+
* - 滚动尾部节流后经 `ctx.onUpdate` 流出;超限(2000 行 / 50 KB 与会话结果上限减余量取小)尾截断并落全文;
|
|
7
7
|
* - 结构化结果 `{ output, exit_code, truncated, full_output_path?, wall_time_seconds }`;
|
|
8
8
|
* 信号退出 `128 + signo`;非零退出码 → isError;
|
|
9
9
|
* - 注入 `AMA_*` 环境变量(shell.ts)。shell 退出后不等仍占着管道的后台进程。
|
|
@@ -21,7 +21,7 @@ import { resolvePath } from "./paths.js";
|
|
|
21
21
|
import { buildShellArgs, buildToolEnv, resolveShell } from "./shell.js";
|
|
22
22
|
import { exitCodeOf, isGroupAlive, killProcessTree, trackProcessGroup, untrackProcessGroup, } from "./process-tree.js";
|
|
23
23
|
import { OutputAccumulator } from "./output-accumulator.js";
|
|
24
|
-
import { formatSize, resolveOutputDir, safeFileName } from "./truncate.js";
|
|
24
|
+
import { formatSize, resolveOutputDir, safeFileName, toolOutputBytes } from "./truncate.js";
|
|
25
25
|
import { jobsForSession } from "./background-jobs.js";
|
|
26
26
|
import { looksLikeSandboxDenial, runsSandboxed, sandboxDenialHint, wrapBashCommand, } from "../sandbox/bash.js";
|
|
27
27
|
export const DEFAULT_BASH_TIMEOUT_MS = 120_000;
|
|
@@ -128,6 +128,7 @@ export async function executeBash(input, ctx, options = {}) {
|
|
|
128
128
|
return startBackground(command, { cwd, env, shell, isWindows, sandbox }, ctx, options);
|
|
129
129
|
const started = Date.now();
|
|
130
130
|
const acc = new OutputAccumulator({
|
|
131
|
+
maxBytes: toolOutputBytes(ctx.maxResultChars),
|
|
131
132
|
spillPath: () => join(resolveOutputDir(ctx.outputDir), safeFileName(`bash-${ctx.toolCallId}.log`)),
|
|
132
133
|
});
|
|
133
134
|
let child;
|
|
@@ -345,8 +346,8 @@ export function createBashTool(options = {}) {
|
|
|
345
346
|
return {
|
|
346
347
|
name: "bash",
|
|
347
348
|
label: "Bash",
|
|
348
|
-
description: "Run a shell command; stdout+stderr combined, keeps the
|
|
349
|
-
`
|
|
349
|
+
description: "Run a shell command; stdout+stderr combined, keeps the tail (full output saved to a " +
|
|
350
|
+
`file). Timeout ${DEFAULT_BASH_TIMEOUT_MS / 1000} s, max ${MAX_BASH_TIMEOUT_MS / 1000} s. ` +
|
|
350
351
|
"background:true gives a job id for {job, action}." +
|
|
351
352
|
sandboxDescription(options.sandbox),
|
|
352
353
|
parameters: {
|
package/dist/tools/glob.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 以 `!` 开头的模式是排除。不含 `/` 的模式匹配**文件名**(任意深度),含 `/` 的匹配相对搜索根的
|
|
6
6
|
* 路径。点文件同样参与匹配(`.git` 由遍历层排除)。Windows 上大小写不敏感。
|
|
7
7
|
*
|
|
8
|
-
* 工具:遍历尊重 `.gitignore` / `.ignore`,按 mtime 倒序,缺省上限 1000
|
|
8
|
+
* 工具:遍历尊重 `.gitignore` / `.ignore`,按 mtime 倒序,缺省上限 200([ME-D] P2-2,原 1000)。
|
|
9
9
|
*/
|
|
10
10
|
import type { ToolContext, ToolDefinition, ToolResult } from "./types.js";
|
|
11
11
|
export interface GlobBodyOptions {
|
|
@@ -32,7 +32,7 @@ export interface GlobInput {
|
|
|
32
32
|
path?: string;
|
|
33
33
|
limit?: number;
|
|
34
34
|
}
|
|
35
|
-
export declare const DEFAULT_GLOB_LIMIT =
|
|
35
|
+
export declare const DEFAULT_GLOB_LIMIT = 200;
|
|
36
36
|
export interface GlobMatchFile {
|
|
37
37
|
abs: string;
|
|
38
38
|
rel: string;
|
package/dist/tools/glob.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 以 `!` 开头的模式是排除。不含 `/` 的模式匹配**文件名**(任意深度),含 `/` 的匹配相对搜索根的
|
|
6
6
|
* 路径。点文件同样参与匹配(`.git` 由遍历层排除)。Windows 上大小写不敏感。
|
|
7
7
|
*
|
|
8
|
-
* 工具:遍历尊重 `.gitignore` / `.ignore`,按 mtime 倒序,缺省上限 1000
|
|
8
|
+
* 工具:遍历尊重 `.gitignore` / `.ignore`,按 mtime 倒序,缺省上限 200([ME-D] P2-2,原 1000)。
|
|
9
9
|
*/
|
|
10
10
|
import { stat } from "node:fs/promises";
|
|
11
11
|
import { displayPath, resolvePath } from "./paths.js";
|
|
@@ -145,7 +145,7 @@ export class GlobMatcher {
|
|
|
145
145
|
return included && !this.exclude.some((g) => GlobMatcher.hit(g, rel));
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
export const DEFAULT_GLOB_LIMIT =
|
|
148
|
+
export const DEFAULT_GLOB_LIMIT = 200;
|
|
149
149
|
/** 收集匹配文件并按 mtime 倒序(同 mtime 按路径)。 */
|
|
150
150
|
export async function globFiles(root, pattern, signal) {
|
|
151
151
|
const matcher = new GlobMatcher(pattern);
|
package/dist/tools/grep.js
CHANGED
|
@@ -13,7 +13,7 @@ import { displayPath, resolvePath } from "./paths.js";
|
|
|
13
13
|
import { walk } from "./ignore.js";
|
|
14
14
|
import { GlobMatcher } from "./glob.js";
|
|
15
15
|
import { isBinary } from "./read.js";
|
|
16
|
-
import {
|
|
16
|
+
import { formatSize, toolOutputBytes, truncateHead, truncateLine } from "./truncate.js";
|
|
17
17
|
export const DEFAULT_GREP_LIMIT = 100;
|
|
18
18
|
export const GREP_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
19
19
|
export const GREP_CONCURRENCY = 16;
|
|
@@ -141,16 +141,14 @@ export async function executeGrep(input, ctx) {
|
|
|
141
141
|
if (matches === 0)
|
|
142
142
|
return { content: "No matches found", details: { matches: 0, files: 0 } };
|
|
143
143
|
const joined = blocks.join(context > 0 ? "\n--\n" : "\n");
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
147
|
-
});
|
|
144
|
+
const maxBytes = toolOutputBytes(ctx.maxResultChars);
|
|
145
|
+
const cut = truncateHead(joined, { maxLines: Number.MAX_SAFE_INTEGER, maxBytes });
|
|
148
146
|
let content = cut.content;
|
|
149
147
|
if (limited) {
|
|
150
148
|
content += `\n\n[Stopped at ${limit} matches. Narrow the search or raise limit.]`;
|
|
151
149
|
}
|
|
152
150
|
else if (cut.truncated) {
|
|
153
|
-
content +=
|
|
151
|
+
content += `\n\n[Output truncated at ${formatSize(maxBytes)}. Narrow the search.]`;
|
|
154
152
|
}
|
|
155
153
|
return {
|
|
156
154
|
content,
|