@mafdet/cli 0.4.1 → 0.4.3
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 +63 -0
- package/README.md +79 -12
- package/README.zh-CN.md +69 -12
- package/dist/agent/context-budget.d.ts +37 -0
- package/dist/agent/context-budget.d.ts.map +1 -0
- package/dist/agent/context-budget.js +105 -0
- package/dist/agent/loop.d.ts +25 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +36 -3
- package/dist/agent/session.d.ts +5 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +1 -1
- package/dist/agent/tools/read-only.d.ts.map +1 -1
- package/dist/agent/tools/read-only.js +6 -0
- package/dist/agent/tools/run-command.d.ts +28 -0
- package/dist/agent/tools/run-command.d.ts.map +1 -1
- package/dist/agent/tools/run-command.js +34 -13
- package/dist/api/account.d.ts +43 -0
- package/dist/api/account.d.ts.map +1 -1
- package/dist/api/account.js +40 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +118 -18
- package/dist/commands/sessions.d.ts +5 -0
- package/dist/commands/sessions.d.ts.map +1 -0
- package/dist/commands/sessions.js +379 -0
- package/dist/config/paths.d.ts +7 -0
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +24 -0
- package/dist/filesystem/protected-dirs.d.ts +26 -0
- package/dist/filesystem/protected-dirs.d.ts.map +1 -0
- package/dist/filesystem/protected-dirs.js +78 -0
- package/dist/filesystem/workspace.d.ts +12 -1
- package/dist/filesystem/workspace.d.ts.map +1 -1
- package/dist/filesystem/workspace.js +24 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/security/redact.d.ts +26 -3
- package/dist/security/redact.d.ts.map +1 -1
- package/dist/security/redact.js +48 -3
- package/dist/sessions/index-file.d.ts +38 -0
- package/dist/sessions/index-file.d.ts.map +1 -0
- package/dist/sessions/index-file.js +174 -0
- package/dist/sessions/lock.d.ts +39 -0
- package/dist/sessions/lock.d.ts.map +1 -0
- package/dist/sessions/lock.js +134 -0
- package/dist/sessions/open.d.ts +45 -0
- package/dist/sessions/open.d.ts.map +1 -0
- package/dist/sessions/open.js +96 -0
- package/dist/sessions/policy.d.ts +32 -0
- package/dist/sessions/policy.d.ts.map +1 -0
- package/dist/sessions/policy.js +57 -0
- package/dist/sessions/reconcile.d.ts +49 -0
- package/dist/sessions/reconcile.d.ts.map +1 -0
- package/dist/sessions/reconcile.js +115 -0
- package/dist/sessions/recorder.d.ts +42 -0
- package/dist/sessions/recorder.d.ts.map +1 -0
- package/dist/sessions/recorder.js +147 -0
- package/dist/sessions/resume.d.ts +67 -0
- package/dist/sessions/resume.d.ts.map +1 -0
- package/dist/sessions/resume.js +100 -0
- package/dist/sessions/store.d.ts +141 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +272 -0
- package/dist/sessions/usage.d.ts +35 -0
- package/dist/sessions/usage.d.ts.map +1 -0
- package/dist/sessions/usage.js +157 -0
- package/dist/tui/agent-surface.d.ts +3 -0
- package/dist/tui/agent-surface.d.ts.map +1 -1
- package/dist/tui/agent-surface.js +23 -1
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +18 -1
- package/dist/tui/route.d.ts.map +1 -1
- package/dist/tui/route.js +19 -2
- package/dist/tui/session-centre.d.ts +15 -0
- package/dist/tui/session-centre.d.ts.map +1 -0
- package/dist/tui/session-centre.js +59 -0
- package/dist/tui/start-agent.d.ts +9 -0
- package/dist/tui/start-agent.d.ts.map +1 -1
- package/dist/tui/start-agent.js +122 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,69 @@ The version is the contract for the JSON output shapes and the exit codes.
|
|
|
6
6
|
Being 0.x, semver still permits a break in a minor bump; the intent is not to
|
|
7
7
|
use that latitude, and the promise becomes binding at 1.0.0.
|
|
8
8
|
|
|
9
|
+
## 0.4.3 - 2026-08-11
|
|
10
|
+
|
|
11
|
+
A long agent run no longer walks into the model's context window, a one-shot
|
|
12
|
+
chat can be saved, and the Windows timeout kill now walks the process tree.
|
|
13
|
+
|
|
14
|
+
**Automatic compaction.** When a run gets close to the model's context window,
|
|
15
|
+
the oldest tool exchanges are dropped from what is sent and you are told it
|
|
16
|
+
happened. The size is measured rather than estimated: every response reports
|
|
17
|
+
how many input tokens it actually cost, which is the provider's own count of
|
|
18
|
+
exactly the conversation we sent. What gets dropped is always a whole exchange
|
|
19
|
+
|
|
20
|
+
- an assistant tool call and the results answering it are inseparable - and
|
|
21
|
+
the system prompt and your original goal are never touched. A model whose
|
|
22
|
+
context length the catalogue does not publish is left alone, because guessing
|
|
23
|
+
a window would either truncate a conversation that fit or fail to truncate one
|
|
24
|
+
that did not. Your saved transcript is unaffected; only what is sent gets
|
|
25
|
+
shorter.
|
|
26
|
+
|
|
27
|
+
**`mafdet chat "..." --save`** records a one-shot chat, through exactly the
|
|
28
|
+
same store as an interactive session - the same redaction, the same private
|
|
29
|
+
files, the same `mafdet sessions delete`. Chats are still not saved by
|
|
30
|
+
default, `--no-history` still wins over `--save`, and a chat you cancel is not
|
|
31
|
+
saved at all.
|
|
32
|
+
|
|
33
|
+
**Windows command timeouts** now use `taskkill /T`, then `taskkill /T /F`,
|
|
34
|
+
where POSIX uses SIGTERM then SIGKILL on the process group. Note the standing
|
|
35
|
+
caveat: this path has not been run on a real Windows machine, so a surviving
|
|
36
|
+
grandchild process there remains a known limitation rather than a fixed bug.
|
|
37
|
+
|
|
38
|
+
## 0.4.2 - 2026-08-11
|
|
39
|
+
|
|
40
|
+
Conversations are now saved on this computer, and a session's cost can be
|
|
41
|
+
reconciled against the billing ledger.
|
|
42
|
+
|
|
43
|
+
`mafdet sessions list|show|usage|delete` browses what is stored locally. None
|
|
44
|
+
of it calls a model, so reading your own history is never charged. Recording
|
|
45
|
+
is on by default for interactive sessions and off wherever a machine is
|
|
46
|
+
reading the output; `--no-history` writes nothing at all.
|
|
47
|
+
|
|
48
|
+
The transcript is plain text and stays on this machine - it is not uploaded,
|
|
49
|
+
and Mafdet holds no copy. That is a statement about the archive, not about the
|
|
50
|
+
model: the messages you send are still sent to the API to be answered and
|
|
51
|
+
still appear in your usage records. The README says where the files live on
|
|
52
|
+
each platform, and that deleting one is an ordinary file delete rather than a
|
|
53
|
+
secure erase.
|
|
54
|
+
|
|
55
|
+
`mafdet sessions usage <id> --refresh` asks the billing ledger what the
|
|
56
|
+
requests actually cost and replaces the local estimate with the settled
|
|
57
|
+
figure. It sends the request IDs already in the transcript and nothing else -
|
|
58
|
+
no messages, no title, no workspace, no session ID. Requests paid for by a
|
|
59
|
+
different API key are left alone and reported rather than guessed at, and a
|
|
60
|
+
request the ledger has not settled stays `pending` rather than being shown as
|
|
61
|
+
zero.
|
|
62
|
+
|
|
63
|
+
The agent cannot read the CLI's own directories, even when you run `mafdet`
|
|
64
|
+
from your home directory and the session store is technically inside the
|
|
65
|
+
workspace.
|
|
66
|
+
|
|
67
|
+
Fixed on the way: the value recorded to attribute usage to a key was the
|
|
68
|
+
display fingerprint, which renders every live key identically - so a session
|
|
69
|
+
that used two keys reported them as one. It is now a digest that actually
|
|
70
|
+
differs per key, and still cannot be turned back into a key.
|
|
71
|
+
|
|
9
72
|
## 0.4.1 - 2026-08-07
|
|
10
73
|
|
|
11
74
|
Fixes Ctrl+C in the interactive session, which did nothing while a model
|
package/README.md
CHANGED
|
@@ -77,18 +77,20 @@ prompt as an argument, or use `--input <file>`.
|
|
|
77
77
|
|
|
78
78
|
## Commands
|
|
79
79
|
|
|
80
|
-
| Command
|
|
81
|
-
|
|
|
82
|
-
| `mafdet doctor`
|
|
83
|
-
| `mafdet models list [--surface chat\|embeddings\|speech] [--agent]`
|
|
84
|
-
| `mafdet models show <id>`
|
|
85
|
-
| `mafdet chat [prompt] [--model] [--system <file>] [--input <file>] [--no-stream] [--quiet]` | Send a prompt; streams by default |
|
|
86
|
-
| `mafdet agent "<goal>" [--allow-write] [--allow-command "<prefix>"]`
|
|
87
|
-
| `mafdet agent` (no goal, needs a terminal)
|
|
88
|
-
| `mafdet embed [text\|<file>] --model <id> [--output <file>]`
|
|
89
|
-
| `mafdet speech [text\|<file>] --model <id> --output <file> --voice <name>`
|
|
90
|
-
| `mafdet account balance\|usage\|subscription`
|
|
91
|
-
| `mafdet
|
|
80
|
+
| Command | What it does |
|
|
81
|
+
| ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
82
|
+
| `mafdet doctor` | One pass over key, endpoints, model, tool-calling, balance, workspace, git |
|
|
83
|
+
| `mafdet models list [--surface chat\|embeddings\|speech] [--agent]` | Models this key can call, joined with catalogue metadata |
|
|
84
|
+
| `mafdet models show <id>` | One model in detail, including whether the agent may drive it |
|
|
85
|
+
| `mafdet chat [prompt] [--model] [--system <file>] [--input <file>] [--no-stream] [--quiet] [--save]` | Send a prompt; streams by default |
|
|
86
|
+
| `mafdet agent "<goal>" [--allow-write] [--allow-command "<prefix>"]` | Local agent; read-only unless told otherwise |
|
|
87
|
+
| `mafdet agent` (no goal, needs a terminal) | Interactive session |
|
|
88
|
+
| `mafdet embed [text\|<file>] --model <id> [--output <file>]` | Text to vector |
|
|
89
|
+
| `mafdet speech [text\|<file>] --model <id> --output <file> --voice <name>` | Text to an audio file |
|
|
90
|
+
| `mafdet account balance\|usage\|subscription` | Wallet, spend and plan (needs the `account:read` scope) |
|
|
91
|
+
| `mafdet sessions list\|show\|usage\|delete` | Conversations saved on this computer (never calls a model) |
|
|
92
|
+
| `mafdet sessions usage <id> --refresh` | Replace the local estimate with the billing ledger's figure |
|
|
93
|
+
| `mafdet config list` | The resolved settings and where each came from |
|
|
92
94
|
|
|
93
95
|
`--json` on any query command emits one complete JSON document; `--plain`
|
|
94
96
|
emits tab-separated rows with no header, for `cut` and `awk`.
|
|
@@ -181,6 +183,71 @@ flag: it would put the key into shell history and the process list. Keys are
|
|
|
181
183
|
never written to a config file and never printed in full — only as a
|
|
182
184
|
fingerprint like `sk-mafdet-live-<redacted>`.
|
|
183
185
|
|
|
186
|
+
## Saved conversations
|
|
187
|
+
|
|
188
|
+
Interactive sessions are saved on this computer, and only on this computer.
|
|
189
|
+
They are not uploaded, and Mafdet holds no copy of them.
|
|
190
|
+
|
|
191
|
+
That is a statement about the transcript, not about the model. The messages you
|
|
192
|
+
send are still sent to the API to be answered and still appear in your usage
|
|
193
|
+
records; what stays local is the saved copy.
|
|
194
|
+
|
|
195
|
+
Where they live:
|
|
196
|
+
|
|
197
|
+
| Platform | Path |
|
|
198
|
+
| ----------- | --------------------------------------------------- |
|
|
199
|
+
| Linux / WSL | `${XDG_STATE_HOME:-~/.local/state}/mafdet/sessions` |
|
|
200
|
+
| macOS | `~/Library/Application Support/mafdet/sessions` |
|
|
201
|
+
| Windows | `%LOCALAPPDATA%\mafdet\sessions` |
|
|
202
|
+
|
|
203
|
+
The files are **plain text**. A transcript contains your messages, the model's
|
|
204
|
+
replies, and any source the agent read while working — so treat a session
|
|
205
|
+
directory the way you would treat the project it came from. API keys are the
|
|
206
|
+
one exception: everything is passed through redaction on the way to disk, so a
|
|
207
|
+
key you paste into a prompt is stored as `<redacted>`. Files are created
|
|
208
|
+
`0600` and directories `0700` on Linux and macOS; Windows has no equivalent
|
|
209
|
+
mode bits and inherits the ACL of `%LOCALAPPDATA%`.
|
|
210
|
+
|
|
211
|
+
The agent cannot read these directories, even when you run `mafdet` from your
|
|
212
|
+
home directory and the store is technically inside the workspace. Otherwise an
|
|
213
|
+
agent working on one project could read another project's transcript in full.
|
|
214
|
+
|
|
215
|
+
Turning it off and cleaning up:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
mafdet --no-history # this run is not recorded at all
|
|
219
|
+
mafdet chat "..." --save # a one-shot chat is saved only when asked
|
|
220
|
+
mafdet sessions list # what is saved, and the path when nothing is
|
|
221
|
+
mafdet sessions delete <id> --yes
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
A single `mafdet chat` is not a conversation anyone resumes, so it is not
|
|
225
|
+
recorded unless `--save` is given. `--no-history` still wins over `--save`, and
|
|
226
|
+
a chat cancelled with Ctrl+C is not saved at all — half a reply is a worse
|
|
227
|
+
record than none.
|
|
228
|
+
|
|
229
|
+
`--no-history` writes nothing — not an empty directory, not an index entry.
|
|
230
|
+
|
|
231
|
+
Deleting removes the local messages and this session's usage mapping. It does
|
|
232
|
+
**not** remove API usage, billing records or charges: those belong to the API
|
|
233
|
+
key, not to the transcript. And it is an ordinary file delete, not a secure
|
|
234
|
+
erase — backups, snapshots and the filesystem itself may still hold copies.
|
|
235
|
+
|
|
236
|
+
## Reconciling what a session cost
|
|
237
|
+
|
|
238
|
+
`mafdet sessions usage <id>` adds up what a conversation used from the local
|
|
239
|
+
record. Those charges are estimates, computed with the gateway's own
|
|
240
|
+
arithmetic but computed here.
|
|
241
|
+
|
|
242
|
+
`--refresh` asks the billing ledger for the real figure. It sends the request
|
|
243
|
+
IDs already in the transcript and nothing else — no messages, no title, no
|
|
244
|
+
workspace, no session ID. Requests paid for by a different API key are left
|
|
245
|
+
alone and reported, so you know to run it again with that key.
|
|
246
|
+
|
|
247
|
+
A request that the ledger has not caught up with stays `pending`, and a request
|
|
248
|
+
that never returned an ID stays `unknown`. Neither is ever shown as `0`, which
|
|
249
|
+
would read as "it was free".
|
|
250
|
+
|
|
184
251
|
## Cost and cancellation
|
|
185
252
|
|
|
186
253
|
`chat` prints tokens and an estimated charge to stderr, computed with the same
|
package/README.zh-CN.md
CHANGED
|
@@ -60,18 +60,20 @@ mafdet doctor
|
|
|
60
60
|
|
|
61
61
|
## 命令
|
|
62
62
|
|
|
63
|
-
| 命令
|
|
64
|
-
|
|
|
65
|
-
| `mafdet doctor`
|
|
66
|
-
| `mafdet models list [--surface chat\|embeddings\|speech] [--agent]`
|
|
67
|
-
| `mafdet models show <id>`
|
|
68
|
-
| `mafdet chat [提示词] [--model] [--system <文件>] [--input <文件>] [--no-stream] [--quiet]` | 发一次对话,默认流式 |
|
|
69
|
-
| `mafdet embed [文本\|<文件>] --model <id> [--output <文件>]`
|
|
70
|
-
| `mafdet speech [文本\|<文件>] --model <id> --output <文件> --voice <音色>`
|
|
71
|
-
| `mafdet agent "<目标>" [--allow-write] [--allow-command "<前缀>"]`
|
|
72
|
-
| `mafdet agent`(不带目标,需终端)
|
|
73
|
-
| `mafdet account balance\|usage\|subscription`
|
|
74
|
-
| `mafdet
|
|
63
|
+
| 命令 | 作用 |
|
|
64
|
+
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
65
|
+
| `mafdet doctor` | 一次检查 Node、两个端点、Key、工作区、git、模型与工具调用能力、余额 |
|
|
66
|
+
| `mafdet models list [--surface chat\|embeddings\|speech] [--agent]` | 这把 Key 能调用的模型,并附上目录元数据 |
|
|
67
|
+
| `mafdet models show <id>` | 单个模型详情,含 Agent 是否可以驱动它 |
|
|
68
|
+
| `mafdet chat [提示词] [--model] [--system <文件>] [--input <文件>] [--no-stream] [--quiet] [--save]` | 发一次对话,默认流式 |
|
|
69
|
+
| `mafdet embed [文本\|<文件>] --model <id> [--output <文件>]` | 文本转向量 |
|
|
70
|
+
| `mafdet speech [文本\|<文件>] --model <id> --output <文件> --voice <音色>` | 文本转音频 |
|
|
71
|
+
| `mafdet agent "<目标>" [--allow-write] [--allow-command "<前缀>"]` | 本地 Agent,默认只读 |
|
|
72
|
+
| `mafdet agent`(不带目标,需终端) | 交互会话 |
|
|
73
|
+
| `mafdet account balance\|usage\|subscription` | 钱包、用量、套餐(需 `account:read` scope) |
|
|
74
|
+
| `mafdet sessions list\|show\|usage\|delete` | 保存在这台电脑上的会话(不会调用模型) |
|
|
75
|
+
| `mafdet sessions usage <id> --refresh` | 用账单账本的权威金额替换本地估算 |
|
|
76
|
+
| `mafdet config list` | 当前生效的配置及其来源 |
|
|
75
77
|
|
|
76
78
|
查询类命令都支持 `--json`(输出一份完整 JSON 文档)与 `--plain`(无表头的 TSV,方便
|
|
77
79
|
`cut`、`awk` 处理)。
|
|
@@ -156,6 +158,61 @@ Key 只从 `MAFDET_API_KEY` 读取。**刻意不提供 `--api-key` 参数**—
|
|
|
156
158
|
shell 历史和进程列表。Key 不会被写入任何配置文件,也不会完整出现在任何输出里,
|
|
157
159
|
只以 `sk-mafdet-live-<redacted>` 这样的指纹形式展示。
|
|
158
160
|
|
|
161
|
+
## 保存下来的会话
|
|
162
|
+
|
|
163
|
+
交互式会话会保存在这台电脑上,**也只在这台电脑上**。它们不会上传,Mafdet 没有副本。
|
|
164
|
+
|
|
165
|
+
这是关于**存档**的说法,不是关于模型的。你发送的消息仍然会送到 API 去作答,
|
|
166
|
+
仍然出现在你的用量记录里;留在本地的是那份保存下来的副本。
|
|
167
|
+
|
|
168
|
+
存放位置:
|
|
169
|
+
|
|
170
|
+
| 平台 | 路径 |
|
|
171
|
+
| ----------- | --------------------------------------------------- |
|
|
172
|
+
| Linux / WSL | `${XDG_STATE_HOME:-~/.local/state}/mafdet/sessions` |
|
|
173
|
+
| macOS | `~/Library/Application Support/mafdet/sessions` |
|
|
174
|
+
| Windows | `%LOCALAPPDATA%\mafdet\sessions` |
|
|
175
|
+
|
|
176
|
+
这些文件是**明文**。一份存档包含你的消息、模型的回复,以及 agent 工作时读过的任何源码——
|
|
177
|
+
所以请把会话目录当作它所对应的那个项目本身来对待。API Key 是唯一的例外:
|
|
178
|
+
所有内容在写盘前都会过一遍脱敏,你粘进提示词里的 Key 会以 `<redacted>` 的形式存下来。
|
|
179
|
+
Linux 与 macOS 上文件权限 `0600`、目录 `0700`;**Windows 没有等价的权限位**,
|
|
180
|
+
继承 `%LOCALAPPDATA%` 的 ACL。
|
|
181
|
+
|
|
182
|
+
**Agent 读不到这些目录**——即使你在家目录里运行 `mafdet`、存档实际落在工作区内也一样。
|
|
183
|
+
否则在 A 项目里工作的 agent 就能读到 B 项目会话的完整明文。
|
|
184
|
+
|
|
185
|
+
关闭与清理:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
mafdet --no-history # 这一次运行完全不记录
|
|
189
|
+
mafdet chat "..." --save # 一次性对话只有明确要求时才保存
|
|
190
|
+
mafdet sessions list # 已保存的会话;没有时会告诉你路径
|
|
191
|
+
mafdet sessions delete <id> --yes
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
一次 `mafdet chat` 不是有人会去恢复的对话,所以**不带 `--save` 就不保存**。
|
|
195
|
+
`--no-history` 仍然压过 `--save`;被 Ctrl+C 取消的对话**完全不保存**——
|
|
196
|
+
半个回复是比没有更糟的记录。
|
|
197
|
+
|
|
198
|
+
`--no-history` 什么都不留——不留空目录,也不留索引条目。
|
|
199
|
+
|
|
200
|
+
删除会移除本地消息和这个会话的用量映射。它**不会**移除 API 用量、账单记录或扣费:
|
|
201
|
+
那些属于 API Key,不属于存档。而且它只是一次普通的文件删除,**不是安全擦除**——
|
|
202
|
+
备份、快照和文件系统本身仍可能保有副本。
|
|
203
|
+
|
|
204
|
+
## 对账:这次会话到底花了多少
|
|
205
|
+
|
|
206
|
+
`mafdet sessions usage <id>` 从本地记录累加一次会话的用量。那些金额是**估算**,
|
|
207
|
+
算法与网关一致,但是在本地算的。
|
|
208
|
+
|
|
209
|
+
`--refresh` 会去问账单账本要真实金额。它只发送存档里已有的 request ID,**别的什么都不发**——
|
|
210
|
+
不发消息、不发标题、不发工作区路径、不发 session ID。由另一把 API Key 支付的请求会被跳过
|
|
211
|
+
并如实告知,好让你知道该换那把 Key 再跑一次。
|
|
212
|
+
|
|
213
|
+
账本还没跟上的请求保持 `pending`,从未拿到 ID 的请求保持 `unknown`。
|
|
214
|
+
**两者都绝不显示为 `0`**——0 读起来像「免费」。
|
|
215
|
+
|
|
159
216
|
## 费用与取消
|
|
160
217
|
|
|
161
218
|
`chat` 会在 stderr 打印 token 数与预估费用,算法与网关计费一致,但**以钱包为准**。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ChatMessage } from "./loop.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fraction of the usable window at which we start shortening.
|
|
4
|
+
*
|
|
5
|
+
* Not 1.0, and the gap is not caution for its own sake: the request that
|
|
6
|
+
* measured `prompt_tokens` is smaller than the next one will be, because the
|
|
7
|
+
* assistant reply, its tool results and the next user message all land in
|
|
8
|
+
* between and none of them are counted yet.
|
|
9
|
+
*/
|
|
10
|
+
export declare const COMPACT_AT = 0.75;
|
|
11
|
+
/** Room left for the reply, which shares the window with the input. */
|
|
12
|
+
export declare function replyReserve(contextLength: number): number;
|
|
13
|
+
/** Tool exchanges kept whatever happens, so the recent work stays intact. */
|
|
14
|
+
export declare const KEEP_RECENT_EXCHANGES = 2;
|
|
15
|
+
/**
|
|
16
|
+
* Is the conversation close enough to the window to shorten it?
|
|
17
|
+
*
|
|
18
|
+
* `lastPromptTokens` is what the provider counted for the previous request in
|
|
19
|
+
* this run. Zero means nothing has been measured yet, and an unknown window
|
|
20
|
+
* means we have nothing to compare against.
|
|
21
|
+
*/
|
|
22
|
+
export declare function shouldCompact(contextLength: number | undefined, lastPromptTokens: number): boolean;
|
|
23
|
+
export interface PruneResult {
|
|
24
|
+
/** Messages removed from the array. */
|
|
25
|
+
readonly removed: number;
|
|
26
|
+
/** Complete tool exchanges dropped. */
|
|
27
|
+
readonly exchanges: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Drop the oldest complete tool exchanges, in place.
|
|
31
|
+
*
|
|
32
|
+
* The system prompt and the opening goal are never touched - without the goal
|
|
33
|
+
* the model no longer knows what it is doing, which is a worse failure than
|
|
34
|
+
* running out of context.
|
|
35
|
+
*/
|
|
36
|
+
export declare function pruneToolExchanges(history: ChatMessage[], keepRecent?: number): PruneResult;
|
|
37
|
+
//# sourceMappingURL=context-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget.d.ts","sourceRoot":"","sources":["../../src/agent/context-budget.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,OAAO,CAAC;AAE/B,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAKT;AAED,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAsCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,GAAE,MAA8B,GACzC,WAAW,CAkBb"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// §7.4 · Keeping a long run inside the model's context window.
|
|
2
|
+
//
|
|
3
|
+
// The plan asks for "总上下文按模型窗口设置预算,接近上限时压缩历史". Two decisions
|
|
4
|
+
// shape what that can honestly be:
|
|
5
|
+
//
|
|
6
|
+
// The size of the history is *measured*, not estimated. Every response carries
|
|
7
|
+
// `prompt_tokens` for the request that produced it, which is the provider's own
|
|
8
|
+
// count of exactly the conversation we sent. Estimating instead would mean
|
|
9
|
+
// shipping a tokenizer per provider - CLI-000B already found the gateway's own
|
|
10
|
+
// reservation under-counts badly because it cannot see the tool schema, and a
|
|
11
|
+
// second wrong estimate here would be no better.
|
|
12
|
+
//
|
|
13
|
+
// And what gets dropped is whole tool exchanges, never individual messages.
|
|
14
|
+
// CLI-014 established the rule the hard way: an assistant message carrying
|
|
15
|
+
// `tool_calls` and the `tool` results answering them are inseparable, because
|
|
16
|
+
// the API rejects a tool result with no matching call and a call with no
|
|
17
|
+
// result. So the unit of removal is the pair, or nothing.
|
|
18
|
+
//
|
|
19
|
+
// When the model's context length is unknown, nothing happens at all. A guess
|
|
20
|
+
// at the window would either truncate a conversation that fit, or fail to
|
|
21
|
+
// truncate one that did not.
|
|
22
|
+
/**
|
|
23
|
+
* Fraction of the usable window at which we start shortening.
|
|
24
|
+
*
|
|
25
|
+
* Not 1.0, and the gap is not caution for its own sake: the request that
|
|
26
|
+
* measured `prompt_tokens` is smaller than the next one will be, because the
|
|
27
|
+
* assistant reply, its tool results and the next user message all land in
|
|
28
|
+
* between and none of them are counted yet.
|
|
29
|
+
*/
|
|
30
|
+
export const COMPACT_AT = 0.75;
|
|
31
|
+
/** Room left for the reply, which shares the window with the input. */
|
|
32
|
+
export function replyReserve(contextLength) {
|
|
33
|
+
return Math.min(4096, Math.floor(contextLength / 8));
|
|
34
|
+
}
|
|
35
|
+
/** Tool exchanges kept whatever happens, so the recent work stays intact. */
|
|
36
|
+
export const KEEP_RECENT_EXCHANGES = 2;
|
|
37
|
+
/**
|
|
38
|
+
* Is the conversation close enough to the window to shorten it?
|
|
39
|
+
*
|
|
40
|
+
* `lastPromptTokens` is what the provider counted for the previous request in
|
|
41
|
+
* this run. Zero means nothing has been measured yet, and an unknown window
|
|
42
|
+
* means we have nothing to compare against.
|
|
43
|
+
*/
|
|
44
|
+
export function shouldCompact(contextLength, lastPromptTokens) {
|
|
45
|
+
if (contextLength === undefined || contextLength <= 0)
|
|
46
|
+
return false;
|
|
47
|
+
if (lastPromptTokens <= 0)
|
|
48
|
+
return false;
|
|
49
|
+
const usable = contextLength - replyReserve(contextLength);
|
|
50
|
+
return usable > 0 && lastPromptTokens >= usable * COMPACT_AT;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The assistant-with-tool_calls messages and the tool results that answer them.
|
|
54
|
+
*
|
|
55
|
+
* An exchange is only counted when every one of its calls has a result: a
|
|
56
|
+
* partial one is the tail of a run still in progress, and removing half of it
|
|
57
|
+
* is precisely the shape the API refuses.
|
|
58
|
+
*/
|
|
59
|
+
function exchangesIn(history) {
|
|
60
|
+
const found = [];
|
|
61
|
+
for (let i = 0; i < history.length; i += 1) {
|
|
62
|
+
const message = history[i];
|
|
63
|
+
if (message === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
const calls = message.role === "assistant" ? message.tool_calls : undefined;
|
|
66
|
+
if (calls === undefined || calls.length === 0)
|
|
67
|
+
continue;
|
|
68
|
+
const answered = new Set();
|
|
69
|
+
let end = i + 1;
|
|
70
|
+
while (end < history.length && history[end]?.role === "tool") {
|
|
71
|
+
const id = history[end]?.tool_call_id;
|
|
72
|
+
if (typeof id === "string")
|
|
73
|
+
answered.add(id);
|
|
74
|
+
end += 1;
|
|
75
|
+
}
|
|
76
|
+
if (calls.every((call) => answered.has(call.id))) {
|
|
77
|
+
found.push({ start: i, end });
|
|
78
|
+
}
|
|
79
|
+
i = end - 1;
|
|
80
|
+
}
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Drop the oldest complete tool exchanges, in place.
|
|
85
|
+
*
|
|
86
|
+
* The system prompt and the opening goal are never touched - without the goal
|
|
87
|
+
* the model no longer knows what it is doing, which is a worse failure than
|
|
88
|
+
* running out of context.
|
|
89
|
+
*/
|
|
90
|
+
export function pruneToolExchanges(history, keepRecent = KEEP_RECENT_EXCHANGES) {
|
|
91
|
+
const exchanges = exchangesIn(history);
|
|
92
|
+
const droppable = exchanges.slice(0, Math.max(0, exchanges.length - keepRecent));
|
|
93
|
+
if (droppable.length === 0)
|
|
94
|
+
return { removed: 0, exchanges: 0 };
|
|
95
|
+
const drop = new Set();
|
|
96
|
+
for (const exchange of droppable) {
|
|
97
|
+
for (let i = exchange.start; i < exchange.end; i += 1)
|
|
98
|
+
drop.add(i);
|
|
99
|
+
}
|
|
100
|
+
const kept = history.filter((_, index) => !drop.has(index));
|
|
101
|
+
const removed = history.length - kept.length;
|
|
102
|
+
history.length = 0;
|
|
103
|
+
history.push(...kept);
|
|
104
|
+
return { removed, exchanges: droppable.length };
|
|
105
|
+
}
|
package/dist/agent/loop.d.ts
CHANGED
|
@@ -88,10 +88,35 @@ export type AgentEvent = {
|
|
|
88
88
|
} | {
|
|
89
89
|
type: "text";
|
|
90
90
|
text: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* One model request's accounting, as it happens. The summary already
|
|
94
|
+
* aggregates this; the event exists so a recorder can attribute each request
|
|
95
|
+
* to the ledger row it became (CLI-024/025) - `requestId` is the gateway's
|
|
96
|
+
* x-litellm-call-id, which is unique per row in request_ledger.
|
|
97
|
+
*/
|
|
98
|
+
| {
|
|
99
|
+
type: "usage";
|
|
100
|
+
model: string;
|
|
101
|
+
requestId?: string;
|
|
102
|
+
promptTokens: number;
|
|
103
|
+
completionTokens: number;
|
|
104
|
+
costMicroUsd: bigint;
|
|
91
105
|
} | {
|
|
92
106
|
type: "rate_limited";
|
|
93
107
|
waitMs: number;
|
|
94
108
|
attempt: number;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* §7.4 · The history was shortened to stay inside the model's window.
|
|
112
|
+
* Surfaced rather than done quietly: the user is entitled to know that
|
|
113
|
+
* earlier tool output is no longer being sent.
|
|
114
|
+
*/
|
|
115
|
+
| {
|
|
116
|
+
type: "compacted";
|
|
117
|
+
exchanges: number;
|
|
118
|
+
messages: number;
|
|
119
|
+
promptTokens: number;
|
|
95
120
|
};
|
|
96
121
|
export interface ChatMessage {
|
|
97
122
|
role: "system" | "user" | "assistant" | "tool";
|
package/dist/agent/loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/loop.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiError } from "../api/errors.js";
|
|
2
|
+
import { pruneToolExchanges, shouldCompact } from "./context-budget.js";
|
|
2
3
|
import { toolToJsonSchema, } from "./tools/types.js";
|
|
3
4
|
/** Plan §7.4. */
|
|
4
5
|
export const DEFAULT_LIMITS = {
|
|
@@ -44,6 +45,8 @@ export async function runAgent(options) {
|
|
|
44
45
|
completionTokens: 0,
|
|
45
46
|
costMicroUsd: 0n,
|
|
46
47
|
};
|
|
48
|
+
/** What the provider counted for the previous request; 0 until one lands. */
|
|
49
|
+
let lastPromptTokens = 0;
|
|
47
50
|
for (let turn = 1; turn <= limits.maxTurns; turn += 1) {
|
|
48
51
|
if (options.signal?.aborted)
|
|
49
52
|
return stop(report, "cancelled");
|
|
@@ -55,6 +58,22 @@ export async function runAgent(options) {
|
|
|
55
58
|
}
|
|
56
59
|
report.turns = turn;
|
|
57
60
|
options.onEvent?.({ type: "turn", turn });
|
|
61
|
+
// §7.4 · Shorten before asking, using what the provider counted last time.
|
|
62
|
+
// Only whole tool exchanges go, and only when the window is known.
|
|
63
|
+
if (shouldCompact(options.pricing?.contextLength, lastPromptTokens)) {
|
|
64
|
+
const pruned = pruneToolExchanges(messages);
|
|
65
|
+
if (pruned.exchanges > 0) {
|
|
66
|
+
options.onEvent?.({
|
|
67
|
+
type: "compacted",
|
|
68
|
+
exchanges: pruned.exchanges,
|
|
69
|
+
messages: pruned.removed,
|
|
70
|
+
promptTokens: lastPromptTokens,
|
|
71
|
+
});
|
|
72
|
+
// Reset the measurement: the next response measures the shorter
|
|
73
|
+
// history, and reusing the old figure would compact again every turn.
|
|
74
|
+
lastPromptTokens = 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
58
77
|
let response;
|
|
59
78
|
try {
|
|
60
79
|
response = await requestWithRateLimitHandling(options, limits, {
|
|
@@ -74,11 +93,23 @@ export async function runAgent(options) {
|
|
|
74
93
|
return stop(report, "error", error instanceof Error ? error.message : String(error));
|
|
75
94
|
}
|
|
76
95
|
const usage = response.usage;
|
|
96
|
+
lastPromptTokens = usage?.prompt_tokens ?? lastPromptTokens;
|
|
77
97
|
report.promptTokens += usage?.prompt_tokens ?? 0;
|
|
78
98
|
report.completionTokens += usage?.completion_tokens ?? 0;
|
|
79
99
|
// Cost comes from returned usage, never from the reservation: CLI-000B
|
|
80
100
|
// showed the reserve cannot see the tool schema and under-counts badly.
|
|
81
|
-
|
|
101
|
+
const turnCost = priceTurn(options.pricing, usage);
|
|
102
|
+
report.costMicroUsd += turnCost;
|
|
103
|
+
options.onEvent?.({
|
|
104
|
+
type: "usage",
|
|
105
|
+
model: options.model,
|
|
106
|
+
...(response.requestId === undefined
|
|
107
|
+
? {}
|
|
108
|
+
: { requestId: response.requestId }),
|
|
109
|
+
promptTokens: usage?.prompt_tokens ?? 0,
|
|
110
|
+
completionTokens: usage?.completion_tokens ?? 0,
|
|
111
|
+
costMicroUsd: turnCost,
|
|
112
|
+
});
|
|
82
113
|
const choice = response.choices?.[0];
|
|
83
114
|
const message = choice?.message;
|
|
84
115
|
const calls = message?.tool_calls ?? [];
|
|
@@ -198,8 +229,10 @@ async function requestWithRateLimitHandling(options, limits, body, report, clock
|
|
|
198
229
|
for (let attempt = 0;; attempt += 1) {
|
|
199
230
|
try {
|
|
200
231
|
report.modelRequests += 1;
|
|
201
|
-
const { data } = await options.client.postJson("/chat/completions", body, { signal: options.signal });
|
|
202
|
-
|
|
232
|
+
const { data, requestId } = await options.client.postJson("/chat/completions", body, { signal: options.signal });
|
|
233
|
+
// Carried on the response so the caller can tie this turn to its ledger
|
|
234
|
+
// row without the loop needing to know what a session is.
|
|
235
|
+
return { ...data, requestId };
|
|
203
236
|
}
|
|
204
237
|
catch (error) {
|
|
205
238
|
// Everything except an explicit 429 propagates. A timeout or a 5xx may
|
package/dist/agent/session.d.ts
CHANGED
|
@@ -32,6 +32,11 @@ export interface SessionOptions {
|
|
|
32
32
|
* the same events are drawn.
|
|
33
33
|
*/
|
|
34
34
|
onEvent?: (event: AgentEvent) => void;
|
|
35
|
+
/**
|
|
36
|
+
* A conversation to continue, rebuilt from a saved session (CLI-024).
|
|
37
|
+
* Appended to in place, exactly as the fresh array would be.
|
|
38
|
+
*/
|
|
39
|
+
history?: ChatMessage[];
|
|
35
40
|
}
|
|
36
41
|
export interface SessionSummary {
|
|
37
42
|
goals: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/agent/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAY,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAapE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,oEAAoE;IACpE,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/agent/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAY,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAapE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,oEAAoE;IACpE,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,CAAC,CAwFzB;AAgHD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CA2B7D"}
|
package/dist/agent/session.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-only.d.ts","sourceRoot":"","sources":["../../../src/agent/tools/read-only.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,YAAY,CAAC;AAmB9D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYjD;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuBjD;
|
|
1
|
+
{"version":3,"file":"read-only.d.ts","sourceRoot":"","sources":["../../../src/agent/tools/read-only.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,YAAY,CAAC;AAmB9D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYjD;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuBjD;AAwLD,eAAO,MAAM,eAAe,EAAE,cAAc,EAM3C,CAAC"}
|
|
@@ -87,6 +87,12 @@ function walk(workspace, onFile) {
|
|
|
87
87
|
if (entry.isDirectory()) {
|
|
88
88
|
if (SKIPPED_DIRECTORIES.has(entry.name))
|
|
89
89
|
continue;
|
|
90
|
+
// The CLI's own directories, uniformly. Dot-directories are skipped
|
|
91
|
+
// anyway, which covers Linux and Windows by accident - but on macOS
|
|
92
|
+
// the state directory is `Library/Application Support/mafdet`, so
|
|
93
|
+
// without this the session ids would be listed there and nowhere else.
|
|
94
|
+
if (workspace.isOwnedByCli(full))
|
|
95
|
+
continue;
|
|
90
96
|
stack.push(full);
|
|
91
97
|
continue;
|
|
92
98
|
}
|
|
@@ -15,6 +15,34 @@ export interface CommandResult {
|
|
|
15
15
|
* failure mode where a "timed out" test run keeps holding a port.
|
|
16
16
|
*/
|
|
17
17
|
export declare function executeCommand(argv: readonly string[], cwd: string, timeoutMs?: number): Promise<CommandResult>;
|
|
18
|
+
/**
|
|
19
|
+
* What "kill the whole tree" means on this platform.
|
|
20
|
+
*
|
|
21
|
+
* Pure, and separated from doing it, because the Windows branch cannot be
|
|
22
|
+
* executed here: there is no Windows machine in this project, and a kill path
|
|
23
|
+
* that has never run is exactly the kind of code that is wrong in a way only
|
|
24
|
+
* production finds. Keeping the decision pure means the *shape* of the command
|
|
25
|
+
* is under test on both platforms even though only one of them can be run.
|
|
26
|
+
*
|
|
27
|
+
* POSIX: a negative pid signals the process group, which is why the child is
|
|
28
|
+
* spawned detached. Killing the parent alone leaves grandchildren running -
|
|
29
|
+
* the familiar case where a "timed out" test runner keeps holding a port.
|
|
30
|
+
*
|
|
31
|
+
* Windows has no process groups in that sense. `taskkill /T` walks the child
|
|
32
|
+
* tree instead, and `/F` is the forceful pass: without it taskkill asks
|
|
33
|
+
* politely, which a console process is free to ignore - the same distinction
|
|
34
|
+
* as SIGTERM and SIGKILL.
|
|
35
|
+
*/
|
|
36
|
+
export type KillPlan = {
|
|
37
|
+
readonly kind: "signal";
|
|
38
|
+
readonly target: number;
|
|
39
|
+
readonly signal: NodeJS.Signals;
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: "spawn";
|
|
42
|
+
readonly command: string;
|
|
43
|
+
readonly args: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
export declare function killPlan(pid: number, force: boolean, platform?: string): KillPlan;
|
|
18
46
|
export declare const runCommandTool: ToolDefinition;
|
|
19
47
|
export declare const COMMAND_TOOLS: ToolDefinition[];
|
|
20
48
|
//# sourceMappingURL=run-command.d.ts.map
|