@mafdet/cli 0.4.4 → 0.4.6
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 +81 -0
- package/README.md +4 -6
- package/README.zh-CN.md +4 -3
- package/dist/agent/command-policy.d.ts.map +1 -1
- package/dist/agent/command-policy.js +19 -6
- package/dist/agent/context-budget.d.ts +19 -1
- package/dist/agent/context-budget.d.ts.map +1 -1
- package/dist/agent/context-budget.js +87 -12
- package/dist/agent/loop.d.ts +16 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +34 -2
- package/dist/agent/session.d.ts +14 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +15 -4
- package/dist/api/models.d.ts.map +1 -1
- package/dist/api/models.js +28 -2
- package/dist/api/sse-parser.d.ts +15 -0
- package/dist/api/sse-parser.d.ts.map +1 -1
- package/dist/api/sse-parser.js +39 -3
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +88 -30
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/commands/sessions.js +45 -10
- package/dist/output/money.d.ts +9 -0
- package/dist/output/money.d.ts.map +1 -1
- package/dist/output/money.js +14 -0
- package/dist/sessions/index-file.d.ts +2 -0
- package/dist/sessions/index-file.d.ts.map +1 -1
- package/dist/sessions/index-file.js +3 -0
- package/dist/sessions/open.d.ts.map +1 -1
- package/dist/sessions/open.js +3 -1
- package/dist/sessions/resume.d.ts +1 -0
- package/dist/sessions/resume.d.ts.map +1 -1
- package/dist/sessions/resume.js +5 -3
- package/dist/tui/agent-surface.d.ts.map +1 -1
- package/dist/tui/agent-surface.js +18 -12
- package/dist/tui/app.d.ts +14 -0
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +45 -11
- package/dist/tui/picker.d.ts.map +1 -1
- package/dist/tui/picker.js +2 -1
- package/dist/tui/session-centre.d.ts +5 -0
- package/dist/tui/session-centre.d.ts.map +1 -1
- package/dist/tui/session-centre.js +14 -4
- package/dist/tui/start-agent.d.ts +28 -0
- package/dist/tui/start-agent.d.ts.map +1 -1
- package/dist/tui/start-agent.js +39 -9
- package/package.json +1 -1
package/dist/api/sse-parser.d.ts
CHANGED
|
@@ -72,6 +72,19 @@ export interface StreamLimits {
|
|
|
72
72
|
readonly idleTimeoutMs?: number;
|
|
73
73
|
/** Total bytes accepted before the stream is treated as hostile. */
|
|
74
74
|
readonly maxBytes?: number;
|
|
75
|
+
/**
|
|
76
|
+
* R-3 · A ceiling on the whole stream, not just on the gaps in it.
|
|
77
|
+
*
|
|
78
|
+
* The idle deadline is the right primitive for "this hung" - a long
|
|
79
|
+
* generation is not a stalled one - but it is defeated by a trickle: a few
|
|
80
|
+
* bytes before each deadline holds the connection indefinitely. The byte cap
|
|
81
|
+
* bounds the volume and not the time, so this bounds the time.
|
|
82
|
+
*
|
|
83
|
+
* Generous on purpose. It is a backstop against a hostile upstream, not a
|
|
84
|
+
* limit on how long a model may think, and a single response that takes
|
|
85
|
+
* longer than this is not a case worth serving.
|
|
86
|
+
*/
|
|
87
|
+
readonly maxTotalMs?: number;
|
|
75
88
|
/**
|
|
76
89
|
* Closes the underlying connection. Cancelling the reader alone stops us
|
|
77
90
|
* reading; it does not reliably stop the gateway generating, and generation
|
|
@@ -81,6 +94,8 @@ export interface StreamLimits {
|
|
|
81
94
|
}
|
|
82
95
|
export declare const DEFAULT_STREAM_IDLE_MS = 120000;
|
|
83
96
|
export declare const DEFAULT_STREAM_MAX_BYTES: number;
|
|
97
|
+
/** Matches the agent's wall-clock ceiling (§7.4), for the same reason. */
|
|
98
|
+
export declare const DEFAULT_STREAM_TOTAL_MS: number;
|
|
84
99
|
/**
|
|
85
100
|
* Read a streaming response to completion.
|
|
86
101
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse-parser.d.ts","sourceRoot":"","sources":["../../src/api/sse-parser.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;CACf;AAWD,+EAA+E;AAC/E,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAE7C,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE;IAMjC,kDAAkD;IAClD,GAAG,IAAI,MAAM,EAAE;IASf,OAAO,CAAC,KAAK;CAYd;AAED,8DAA8D;AAC9D,qBAAa,mBAAmB;IAOlB,OAAO,CAAC,QAAQ,CAAC,MAAM;IANnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,IAAI,CAAS;gBAEQ,MAAM,GAAE,YAAiB;IAEtD,4DAA4D;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAgD9B,MAAM,IAAI,YAAY;CASvB;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,eAAO,MAAM,sBAAsB,SAAU,CAAC;AAC9C,eAAO,MAAM,wBAAwB,QAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"sse-parser.d.ts","sourceRoot":"","sources":["../../src/api/sse-parser.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;CACf;AAWD,+EAA+E;AAC/E,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAE7C,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE;IAMjC,kDAAkD;IAClD,GAAG,IAAI,MAAM,EAAE;IASf,OAAO,CAAC,KAAK;CAYd;AAED,8DAA8D;AAC9D,qBAAa,mBAAmB;IAOlB,OAAO,CAAC,QAAQ,CAAC,MAAM;IANnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,IAAI,CAAS;gBAEQ,MAAM,GAAE,YAAiB;IAEtD,4DAA4D;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAgD9B,MAAM,IAAI,YAAY;CASvB;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,eAAO,MAAM,sBAAsB,SAAU,CAAC;AAC9C,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AACzD,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB,QAAc,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,QAAQ,EAClB,MAAM,GAAE,YAAiB,EACzB,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,GAAE,YAAiB,GACxB,OAAO,CAAC,YAAY,CAAC,CAqGvB"}
|
package/dist/api/sse-parser.js
CHANGED
|
@@ -102,6 +102,8 @@ export class ChatStreamAssembler {
|
|
|
102
102
|
}
|
|
103
103
|
export const DEFAULT_STREAM_IDLE_MS = 120_000;
|
|
104
104
|
export const DEFAULT_STREAM_MAX_BYTES = 32 * 1024 * 1024;
|
|
105
|
+
/** Matches the agent's wall-clock ceiling (§7.4), for the same reason. */
|
|
106
|
+
export const DEFAULT_STREAM_TOTAL_MS = 30 * 60_000;
|
|
105
107
|
/**
|
|
106
108
|
* Read a streaming response to completion.
|
|
107
109
|
*
|
|
@@ -122,9 +124,13 @@ export async function readChatStream(response, events = {}, signal, limits = {})
|
|
|
122
124
|
return assembler.result();
|
|
123
125
|
const idleMs = limits.idleTimeoutMs ?? DEFAULT_STREAM_IDLE_MS;
|
|
124
126
|
const maxBytes = limits.maxBytes ?? DEFAULT_STREAM_MAX_BYTES;
|
|
127
|
+
const totalMs = limits.maxTotalMs ?? DEFAULT_STREAM_TOTAL_MS;
|
|
128
|
+
const startedAt = Date.now();
|
|
125
129
|
let received = 0;
|
|
126
130
|
const reader = body.getReader();
|
|
127
131
|
const onAbort = () => void reader.cancel();
|
|
132
|
+
/** Set when `[DONE]` ended the answer while bytes may still be coming. */
|
|
133
|
+
let finished = false;
|
|
128
134
|
// Attaching a listener is not enough: a signal that aborted between the
|
|
129
135
|
// request and here would never fire one, and the read below would hang
|
|
130
136
|
// forever on a stream nobody is going to close.
|
|
@@ -138,14 +144,32 @@ export async function readChatStream(response, events = {}, signal, limits = {})
|
|
|
138
144
|
for (;;) {
|
|
139
145
|
if (signal?.aborted)
|
|
140
146
|
break;
|
|
141
|
-
|
|
147
|
+
if (Date.now() - startedAt > totalMs) {
|
|
148
|
+
limits.abort?.();
|
|
149
|
+
await reader.cancel().catch(() => { });
|
|
150
|
+
throw new ApiError({
|
|
151
|
+
kind: "timeout",
|
|
152
|
+
exitCode: ExitCode.Unavailable,
|
|
153
|
+
message: `the stream ran for longer than ${totalMs}ms`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// T-6 · Whichever runs out first. Checking the total before the read
|
|
157
|
+
// and then waiting a whole idle period overshot the total by up to one
|
|
158
|
+
// of them - with a 30 minute total and a 2 minute idle that is a
|
|
159
|
+
// rounding error, but with the total below the idle it never applied
|
|
160
|
+
// at all.
|
|
161
|
+
const remaining = totalMs - (Date.now() - startedAt);
|
|
162
|
+
const chunk = await withIdleDeadline(reader.read(), Math.max(0, Math.min(idleMs, remaining)));
|
|
142
163
|
if (chunk === IDLE) {
|
|
143
164
|
limits.abort?.();
|
|
144
165
|
await reader.cancel().catch(() => { });
|
|
166
|
+
const overTotal = Date.now() - startedAt >= totalMs;
|
|
145
167
|
throw new ApiError({
|
|
146
168
|
kind: "timeout",
|
|
147
169
|
exitCode: ExitCode.Unavailable,
|
|
148
|
-
message:
|
|
170
|
+
message: overTotal
|
|
171
|
+
? `the stream ran for longer than ${totalMs}ms`
|
|
172
|
+
: `the stream sent nothing for ${idleMs}ms`,
|
|
149
173
|
});
|
|
150
174
|
}
|
|
151
175
|
const { done, value } = chunk;
|
|
@@ -162,8 +186,14 @@ export async function readChatStream(response, events = {}, signal, limits = {})
|
|
|
162
186
|
});
|
|
163
187
|
}
|
|
164
188
|
for (const payload of framer.push(value)) {
|
|
165
|
-
if (assembler.push(payload))
|
|
189
|
+
if (assembler.push(payload)) {
|
|
190
|
+
// R-3 · `[DONE]` means the answer is complete, not that the socket
|
|
191
|
+
// is. An upstream that keeps the connection open after it would
|
|
192
|
+
// otherwise hold ours too - releasing the reader lock in the
|
|
193
|
+
// `finally` does not close anything.
|
|
194
|
+
finished = true;
|
|
166
195
|
return assembler.result();
|
|
196
|
+
}
|
|
167
197
|
}
|
|
168
198
|
}
|
|
169
199
|
for (const payload of framer.end())
|
|
@@ -171,6 +201,12 @@ export async function readChatStream(response, events = {}, signal, limits = {})
|
|
|
171
201
|
}
|
|
172
202
|
finally {
|
|
173
203
|
signal?.removeEventListener("abort", onAbort);
|
|
204
|
+
if (finished) {
|
|
205
|
+
// Cancel first, then release: cancelling closes the body, and the abort
|
|
206
|
+
// reaches the fetch itself, which is what actually frees the socket.
|
|
207
|
+
void reader.cancel().catch(() => { });
|
|
208
|
+
limits.abort?.();
|
|
209
|
+
}
|
|
174
210
|
reader.releaseLock();
|
|
175
211
|
}
|
|
176
212
|
return assembler.result();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAQA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAiBvE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAmBtB,eAAO,MAAM,gBAAgB,0XAK+C,CAAC;AAE7E,eAAO,MAAM,YAAY,mgBAOmB,CAAC;AAE7C,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,qDAAqD;IACrD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,uBAAuB,EAAE,QAAQ,CAAC;CACrD;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,QAAQ,CAAC,CAgInB;AAoLD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,kBAAkB,EAAE,OAAO,0JAI3E"}
|
package/dist/commands/agent.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { fetchAccessibleModels, fetchCatalog, intersectModels, } from "../api/models.js";
|
|
2
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { approverFor } from "../agent/approvals.js";
|
|
3
5
|
import { runAgent } from "../agent/loop.js";
|
|
4
6
|
import { runSession } from "../agent/session.js";
|
|
7
|
+
import { safeForTerminal } from "../output/terminal-safe.js";
|
|
5
8
|
import { sessionsDir } from "../config/paths.js";
|
|
6
9
|
import { keyDigest } from "../security/redact.js";
|
|
7
|
-
import { decideRecording } from "../sessions/policy.js";
|
|
10
|
+
import { decideRecording, firstSaveNotice, NOTICE_FILE, } from "../sessions/policy.js";
|
|
8
11
|
import { SessionRecorder } from "../sessions/recorder.js";
|
|
9
12
|
import { SessionWriter } from "../sessions/store.js";
|
|
10
13
|
import { READ_ONLY_TOOLS } from "../agent/tools/read-only.js";
|
|
@@ -13,17 +16,23 @@ import { WRITE_TOOLS } from "../agent/tools/write.js";
|
|
|
13
16
|
import { Workspace, WorkspaceError } from "../filesystem/workspace.js";
|
|
14
17
|
import { ExitCode } from "../output/exit-codes.js";
|
|
15
18
|
import { renderJson, renderRecord } from "../output/format.js";
|
|
16
|
-
import {
|
|
19
|
+
import { formatUsdWithUnpriced } from "../output/money.js";
|
|
17
20
|
import { buildRuntime, reportError, requireApiKey, } from "./runtime.js";
|
|
18
21
|
const USAGE = `Usage:
|
|
19
|
-
mafdet agent "<goal>" [--model <id>] [--allow-write]
|
|
22
|
+
mafdet agent "<goal>" [--model <id>] [--allow-write] [--read-only]
|
|
20
23
|
[--allow-command "<prefix>"]... [--max-turns <n>]
|
|
21
24
|
[--max-cost-usd <n>] [--json]
|
|
25
|
+
mafdet agent Interactive session (needs a terminal)
|
|
26
|
+
[--title <text>] [--no-history]
|
|
22
27
|
|
|
23
28
|
Read-only by default. With --allow-write the agent may edit and create files,
|
|
24
|
-
and every change is shown as a diff and confirmed before it is written.
|
|
25
|
-
|
|
26
|
-
cannot write at all.
|
|
29
|
+
and every change is shown as a diff and confirmed before it is written.
|
|
30
|
+
--read-only cancels an earlier --allow-write. There is no flag that approves
|
|
31
|
+
everything in advance, and a non-interactive run cannot write at all.
|
|
32
|
+
|
|
33
|
+
An interactive session is saved on this computer; --no-history records
|
|
34
|
+
nothing and --title names it in 'mafdet sessions list'. To continue a saved
|
|
35
|
+
conversation, use 'mafdet --resume <id>'.
|
|
27
36
|
`;
|
|
28
37
|
export const READ_ONLY_PROMPT = `You are the Mafdet CLI agent working inside a user's workspace.
|
|
29
38
|
|
|
@@ -82,25 +91,33 @@ export async function agentCommand(argv, io) {
|
|
|
82
91
|
// F-007 · `decideRecording` already called this surface "interactive",
|
|
83
92
|
// so the policy claimed these runs were saved while nothing saved them.
|
|
84
93
|
const recorder = openRecorder(parsed, io, model, workspace.root, mode);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
// R-8 · In a finally: `close()` is what writes the final index entry, so
|
|
95
|
+
// a session that threw left the listing without it.
|
|
96
|
+
let summary;
|
|
97
|
+
try {
|
|
98
|
+
summary = await runSession({
|
|
99
|
+
client: runtime.dataPlane,
|
|
100
|
+
...(recorder === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: { onEvent: (event) => recorder.observe(event) }),
|
|
103
|
+
model,
|
|
104
|
+
pricing: resolved,
|
|
105
|
+
catalog,
|
|
106
|
+
systemPrompt: mode === "read-only" ? READ_ONLY_PROMPT : WRITE_PROMPT,
|
|
107
|
+
tools: buildToolset(mode, parsed.allowedCommands.length > 0),
|
|
108
|
+
toolContext: { workspace, allowedCommands: parsed.allowedCommands },
|
|
109
|
+
approver: io.approver ?? approverFor(mode, io, interactive),
|
|
110
|
+
mode,
|
|
111
|
+
allowedCommands: parsed.allowedCommands,
|
|
112
|
+
stdout: io.stdout,
|
|
113
|
+
stderr: io.stderr,
|
|
114
|
+
signal: io.signal,
|
|
115
|
+
isTty: isTty(io),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
recorder?.close();
|
|
120
|
+
}
|
|
104
121
|
io.stderr("\n" +
|
|
105
122
|
renderRecord([
|
|
106
123
|
["goals", `${summary.goals}`],
|
|
@@ -110,7 +127,10 @@ export async function agentCommand(argv, io) {
|
|
|
110
127
|
"tokens",
|
|
111
128
|
`${summary.promptTokens} in / ${summary.completionTokens} out`,
|
|
112
129
|
],
|
|
113
|
-
[
|
|
130
|
+
[
|
|
131
|
+
"cost",
|
|
132
|
+
`~${formatUsdWithUnpriced(summary.costMicroUsd, summary.unpricedRequests)}`,
|
|
133
|
+
],
|
|
114
134
|
]));
|
|
115
135
|
return ExitCode.Ok;
|
|
116
136
|
}
|
|
@@ -149,6 +169,29 @@ export async function agentCommand(argv, io) {
|
|
|
149
169
|
* writes nothing at all, and a store that cannot be opened costs the
|
|
150
170
|
* transcript rather than the session.
|
|
151
171
|
*/
|
|
172
|
+
/**
|
|
173
|
+
* R-8 · The first-save notice, once per machine.
|
|
174
|
+
*
|
|
175
|
+
* The marker file is shared with the TUI on purpose: the notice is about this
|
|
176
|
+
* computer, not about which command happened to write first.
|
|
177
|
+
*/
|
|
178
|
+
function showNoticeOnce(root, io) {
|
|
179
|
+
const marker = join(root, NOTICE_FILE);
|
|
180
|
+
try {
|
|
181
|
+
if (existsSync(marker))
|
|
182
|
+
return;
|
|
183
|
+
io.stderr(firstSaveNotice(root));
|
|
184
|
+
writeFileSync(marker, `${new Date().toISOString()}\n`, { mode: 0o600 });
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Telling the user is best effort; failing to record that we told them
|
|
188
|
+
// must not stop the session.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Whether a terminal is on the other end of stdout. Injectable for tests. */
|
|
192
|
+
function isTty(io) {
|
|
193
|
+
return io.isTty ?? process.stdout.isTTY === true;
|
|
194
|
+
}
|
|
152
195
|
function openRecorder(parsed, io, modelId, workspaceRoot, mode) {
|
|
153
196
|
const decision = decideRecording({
|
|
154
197
|
surface: "interactive",
|
|
@@ -169,6 +212,10 @@ function openRecorder(parsed, io, modelId, workspaceRoot, mode) {
|
|
|
169
212
|
workspace: workspaceRoot,
|
|
170
213
|
mode,
|
|
171
214
|
});
|
|
215
|
+
// R-8 · Said once per machine, on whichever surface records first. The
|
|
216
|
+
// TUI showed it and this one did not, so a `mafdet agent` user could have
|
|
217
|
+
// a transcript on disk without ever being told.
|
|
218
|
+
showNoticeOnce(root, io);
|
|
172
219
|
return new SessionRecorder({
|
|
173
220
|
writer,
|
|
174
221
|
root,
|
|
@@ -300,11 +347,16 @@ function reportEvent(event, io) {
|
|
|
300
347
|
/** Exit codes follow §4.5: a ceiling is 7, a cancel is 6. */
|
|
301
348
|
function finish(report, json, io) {
|
|
302
349
|
if (json) {
|
|
303
|
-
io.stdout(
|
|
350
|
+
io.stdout(
|
|
351
|
+
// T-4 · `unpricedRequests` rides along in the report, so a consumer can
|
|
352
|
+
// tell a genuinely free run from one nobody could price.
|
|
353
|
+
renderJson({ ...report, costMicroUsd: report.costMicroUsd.toString() }));
|
|
304
354
|
}
|
|
305
355
|
else {
|
|
306
|
-
|
|
307
|
-
|
|
356
|
+
// R-4 · The model's answer, sanitised only when a terminal is reading it.
|
|
357
|
+
if (report.finalText) {
|
|
358
|
+
io.stdout(`${safeForTerminal(report.finalText, isTty(io))}\n`);
|
|
359
|
+
}
|
|
308
360
|
io.stderr("\n" +
|
|
309
361
|
renderRecord([
|
|
310
362
|
["status", report.stopReason],
|
|
@@ -319,7 +371,10 @@ function finish(report, json, io) {
|
|
|
319
371
|
"tokens",
|
|
320
372
|
`${report.promptTokens} in / ${report.completionTokens} out`,
|
|
321
373
|
],
|
|
322
|
-
[
|
|
374
|
+
[
|
|
375
|
+
"cost",
|
|
376
|
+
`~${formatUsdWithUnpriced(report.costMicroUsd, report.unpricedRequests)}`,
|
|
377
|
+
],
|
|
323
378
|
]));
|
|
324
379
|
if (report.error)
|
|
325
380
|
io.stderr(`error: ${report.error}\n`);
|
|
@@ -334,6 +389,9 @@ function finish(report, json, io) {
|
|
|
334
389
|
case "max_cost":
|
|
335
390
|
// F-008 · A ceiling too: the window ran out, and the report says which.
|
|
336
391
|
case "context_limit":
|
|
392
|
+
// R-5 · Asked for a ceiling we cannot enforce; stopped rather than spend
|
|
393
|
+
// past it. A limit, so it belongs with the other limits.
|
|
394
|
+
case "cost_unknown":
|
|
337
395
|
// Stopped short on purpose; the report says so rather than pretending.
|
|
338
396
|
return ExitCode.LimitReached;
|
|
339
397
|
case "rate_limited":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AA4BnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAgBtB,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,SAAS,GACZ,OAAO,CAAC,QAAQ,CAAC,CAUnB;
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AA4BnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAgBtB,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,SAAS,GACZ,OAAO,CAAC,QAAQ,CAAC,CAUnB;AAoRD,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,MAUX,CAAC"}
|
|
@@ -63,7 +63,13 @@ async function run(argv, io) {
|
|
|
63
63
|
return ExitCode.Usage;
|
|
64
64
|
}
|
|
65
65
|
function list(argv, io, root) {
|
|
66
|
-
const { format } = parseFormatFlags(argv);
|
|
66
|
+
const { format, rest } = parseFormatFlags(argv);
|
|
67
|
+
// R-7 · `list` was the one subcommand that threw its leftovers away, so
|
|
68
|
+
// `sessions list --dry-run` exited 0 and listed everything.
|
|
69
|
+
if (rest.length > 0) {
|
|
70
|
+
io.stderr(`mafdet sessions list: unexpected argument '${rest[0]}'\n${USAGE}`);
|
|
71
|
+
return ExitCode.Usage;
|
|
72
|
+
}
|
|
67
73
|
const { sessions } = loadIndex(root);
|
|
68
74
|
if (format === "json") {
|
|
69
75
|
io.stdout(renderJson({
|
|
@@ -89,7 +95,12 @@ function list(argv, io, root) {
|
|
|
89
95
|
{ header: "REQUESTS", value: (s) => `${s.model_requests}` },
|
|
90
96
|
{
|
|
91
97
|
header: "EST. COST",
|
|
92
|
-
|
|
98
|
+
// R-6 · An unpriced session is not a free one. `$0.00` in the main
|
|
99
|
+
// column is the claim that matters; a note on stderr cannot correct
|
|
100
|
+
// a number the user has already read.
|
|
101
|
+
value: (s) => s.unpriced_requests
|
|
102
|
+
? "unknown"
|
|
103
|
+
: formatUsd(s.estimated_charge_micro_usd),
|
|
93
104
|
},
|
|
94
105
|
{ header: "UPDATED", value: (s) => s.updated_at },
|
|
95
106
|
]));
|
|
@@ -135,12 +146,20 @@ function show(argv, io, root) {
|
|
|
135
146
|
["model", safe(m.model_id, io)],
|
|
136
147
|
["workspace", safe(m.workspace, io)],
|
|
137
148
|
["mode", safe(m.mode, io)],
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
// R-4 · Timestamps come off disk like everything else here; a file that
|
|
150
|
+
// has been edited can carry a sequence in any field, not only the
|
|
151
|
+
// free-text ones.
|
|
152
|
+
["created", safe(m.created_at, io)],
|
|
153
|
+
["updated", safe(m.updated_at, io)],
|
|
140
154
|
["messages", `${m.message_count}`],
|
|
141
155
|
["model requests", `${m.model_requests}`],
|
|
142
156
|
["tokens", `${m.prompt_tokens} in / ${m.completion_tokens} out`],
|
|
143
|
-
[
|
|
157
|
+
[
|
|
158
|
+
"estimated cost",
|
|
159
|
+
m.unpriced_requests
|
|
160
|
+
? `${formatUsd(m.estimated_charge_micro_usd)} + ${m.unpriced_requests} unpriced`
|
|
161
|
+
: formatUsd(m.estimated_charge_micro_usd),
|
|
162
|
+
],
|
|
144
163
|
]));
|
|
145
164
|
if (session.truncatedTail) {
|
|
146
165
|
// Worth saying plainly: the user should know a crash cost them the tail.
|
|
@@ -322,6 +341,8 @@ async function usage(argv, io, root) {
|
|
|
322
341
|
completion_tokens: totals.completionTokens,
|
|
323
342
|
// Strings, so no parser turns a charge into a float.
|
|
324
343
|
estimated_charge_micro_usd: totals.estimatedMicroUsd.toString(),
|
|
344
|
+
// R-6 · Without this a consumer reads the total above as complete.
|
|
345
|
+
unpriced_requests: totals.unpricedRequests,
|
|
325
346
|
settled_charge_micro_usd: totals.settledMicroUsd.toString(),
|
|
326
347
|
by_settlement: totals.bySettlement,
|
|
327
348
|
by_model: totals.byModel.map((m) => ({
|
|
@@ -330,11 +351,16 @@ async function usage(argv, io, root) {
|
|
|
330
351
|
prompt_tokens: m.promptTokens,
|
|
331
352
|
completion_tokens: m.completionTokens,
|
|
332
353
|
estimated_charge_micro_usd: m.estimatedMicroUsd.toString(),
|
|
354
|
+
// T-10 · Without this a caller cannot tell whether one model's "0"
|
|
355
|
+
// is free or unpriced, even though the top-level total says some
|
|
356
|
+
// requests were not counted.
|
|
357
|
+
unpriced_requests: m.unpricedRequests,
|
|
333
358
|
})),
|
|
334
359
|
by_api_key: totals.byKey.map((k) => ({
|
|
335
360
|
api_key_ref: k.apiKeyRef,
|
|
336
361
|
model_requests: k.modelRequests,
|
|
337
362
|
estimated_charge_micro_usd: k.estimatedMicroUsd.toString(),
|
|
363
|
+
unpriced_requests: k.unpricedRequests,
|
|
338
364
|
})),
|
|
339
365
|
...(refreshed === undefined ? {} : { refresh: jsonRefresh(refreshed) }),
|
|
340
366
|
}));
|
|
@@ -348,7 +374,12 @@ async function usage(argv, io, root) {
|
|
|
348
374
|
`${totals.toolRuns}${totals.toolRunsRefused > 0 ? ` (${totals.toolRunsRefused} refused)` : ""}`,
|
|
349
375
|
],
|
|
350
376
|
["tokens", `${totals.promptTokens} in / ${totals.completionTokens} out`],
|
|
351
|
-
[
|
|
377
|
+
[
|
|
378
|
+
"local estimate",
|
|
379
|
+
totals.unpricedRequests > 0
|
|
380
|
+
? `${formatUsd(totals.estimatedMicroUsd.toString())} + ${totals.unpricedRequests} unpriced`
|
|
381
|
+
: formatUsd(totals.estimatedMicroUsd.toString()),
|
|
382
|
+
],
|
|
352
383
|
];
|
|
353
384
|
if (totals.bySettlement.settled > 0) {
|
|
354
385
|
rows.push([
|
|
@@ -366,11 +397,13 @@ async function usage(argv, io, root) {
|
|
|
366
397
|
if (totals.byModel.length > 1) {
|
|
367
398
|
io.stdout("\nBy model\n");
|
|
368
399
|
io.stdout(renderTable(totals.byModel, [
|
|
369
|
-
{ header: "MODEL", value: (m) => m.modelId },
|
|
400
|
+
{ header: "MODEL", value: (m) => safe(m.modelId, io) },
|
|
370
401
|
{ header: "REQUESTS", value: (m) => `${m.modelRequests}` },
|
|
371
402
|
{
|
|
372
403
|
header: "ESTIMATE",
|
|
373
|
-
value: (m) =>
|
|
404
|
+
value: (m) => m.unpricedRequests > 0
|
|
405
|
+
? `${formatUsd(m.estimatedMicroUsd.toString())} + ${m.unpricedRequests} unpriced`
|
|
406
|
+
: formatUsd(m.estimatedMicroUsd.toString()),
|
|
374
407
|
},
|
|
375
408
|
]));
|
|
376
409
|
}
|
|
@@ -378,11 +411,13 @@ async function usage(argv, io, root) {
|
|
|
378
411
|
// Split per key so two accounts' spending is never presented as one.
|
|
379
412
|
io.stdout("\nBy API key\n");
|
|
380
413
|
io.stdout(renderTable(totals.byKey, [
|
|
381
|
-
{ header: "KEY", value: (k) => k.apiKeyRef },
|
|
414
|
+
{ header: "KEY", value: (k) => safe(k.apiKeyRef, io) },
|
|
382
415
|
{ header: "REQUESTS", value: (k) => `${k.modelRequests}` },
|
|
383
416
|
{
|
|
384
417
|
header: "ESTIMATE",
|
|
385
|
-
value: (k) =>
|
|
418
|
+
value: (k) => k.unpricedRequests > 0
|
|
419
|
+
? `${formatUsd(k.estimatedMicroUsd.toString())} + ${k.unpricedRequests} unpriced`
|
|
420
|
+
: formatUsd(k.estimatedMicroUsd.toString()),
|
|
386
421
|
},
|
|
387
422
|
]));
|
|
388
423
|
}
|
package/dist/output/money.d.ts
CHANGED
|
@@ -13,4 +13,13 @@ export declare function parseMicroUsd(value: string | number | bigint): bigint;
|
|
|
13
13
|
export declare function formatUsd(microUsd: string | number | bigint, options?: {
|
|
14
14
|
minFractionDigits?: number;
|
|
15
15
|
}): string;
|
|
16
|
+
/**
|
|
17
|
+
* T-4 · A cost that may be incomplete, described rather than rounded to zero.
|
|
18
|
+
*
|
|
19
|
+
* Four surfaces render a running total - the command summary, the TUI summary,
|
|
20
|
+
* `/cost` and the JSON report - and each one showed `$0.00` for a run whose
|
|
21
|
+
* model the catalogue could not price. The session store had already learned
|
|
22
|
+
* to say "unknown"; the places the user actually watches had not.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatUsdWithUnpriced(microUsd: bigint | string, unpricedRequests: number): string;
|
|
16
25
|
//# sourceMappingURL=money.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../../src/output/money.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAOrE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAClC,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3C,MAAM,CAeR"}
|
|
1
|
+
{"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../../src/output/money.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAOrE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAClC,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3C,MAAM,CAeR;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAIR"}
|
package/dist/output/money.js
CHANGED
|
@@ -33,3 +33,17 @@ export function formatUsd(microUsd, options = {}) {
|
|
|
33
33
|
digits = fraction.slice(0, minDigits);
|
|
34
34
|
return `${negative ? "-" : ""}$${whole}.${digits}`;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* T-4 · A cost that may be incomplete, described rather than rounded to zero.
|
|
38
|
+
*
|
|
39
|
+
* Four surfaces render a running total - the command summary, the TUI summary,
|
|
40
|
+
* `/cost` and the JSON report - and each one showed `$0.00` for a run whose
|
|
41
|
+
* model the catalogue could not price. The session store had already learned
|
|
42
|
+
* to say "unknown"; the places the user actually watches had not.
|
|
43
|
+
*/
|
|
44
|
+
export function formatUsdWithUnpriced(microUsd, unpricedRequests) {
|
|
45
|
+
const shown = formatUsd(typeof microUsd === "bigint" ? microUsd : microUsd);
|
|
46
|
+
if (unpricedRequests <= 0)
|
|
47
|
+
return shown;
|
|
48
|
+
return `${shown} + ${unpricedRequests} unpriced`;
|
|
49
|
+
}
|
|
@@ -7,6 +7,8 @@ export interface IndexEntry {
|
|
|
7
7
|
readonly workspace: string;
|
|
8
8
|
readonly model_requests: number;
|
|
9
9
|
readonly estimated_charge_micro_usd: string;
|
|
10
|
+
/** R-6 · Requests the estimate above does not include; see F-012. */
|
|
11
|
+
readonly unpriced_requests?: number;
|
|
10
12
|
}
|
|
11
13
|
export interface SessionIndex {
|
|
12
14
|
readonly schema_version: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-file.d.ts","sourceRoot":"","sources":["../../src/sessions/index-file.ts"],"names":[],"mappings":"AAqBA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index-file.d.ts","sourceRoot":"","sources":["../../src/sessions/index-file.ts"],"names":[],"mappings":"AAqBA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;IAC5C,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;CAC1C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAsBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAoBvD;AAED,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAiBlE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAapD;AAwBD,yEAAyE;AACzE,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,eAAe,GACxB,YAAY,CAUd;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,YAAY,CAed;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBnD"}
|
|
@@ -25,6 +25,9 @@ function entryOf(metadata) {
|
|
|
25
25
|
workspace: metadata.workspace,
|
|
26
26
|
model_requests: metadata.model_requests,
|
|
27
27
|
estimated_charge_micro_usd: metadata.estimated_charge_micro_usd,
|
|
28
|
+
...(metadata.unpriced_requests
|
|
29
|
+
? { unpriced_requests: metadata.unpriced_requests }
|
|
30
|
+
: {}),
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
/** Newest first, which is the order a session list wants. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/sessions/open.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/sessions/open.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,oBAAoB,EAGpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,aAAa,EAAe,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;CAC/D,CAAC;AAEN,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,UAAU,CAqFnE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAC9C,MAAM,CAyBR"}
|
package/dist/sessions/open.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// and the checks are all re-derived from the key in use *now*, never from
|
|
7
7
|
// what the session was recorded with.
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
|
+
import { stripAnsi } from "../agent/command-policy.js";
|
|
9
10
|
import { acquireSessionLock, SessionLockedError, } from "./lock.js";
|
|
10
11
|
import { describeBlocker, permissionsForResume, rebuildContext, resumeBlockers, } from "./resume.js";
|
|
11
12
|
import { InvalidSessionIdError, resolveSessionDirectory, } from "./session-id.js";
|
|
@@ -102,8 +103,9 @@ export function openForResume(input) {
|
|
|
102
103
|
* know that will be surprised by the bill rather than by the feature.
|
|
103
104
|
*/
|
|
104
105
|
export function describeResume(result) {
|
|
106
|
+
// R-4 · Title and model come off disk and this is drawn on a terminal.
|
|
105
107
|
const lines = [
|
|
106
|
-
`resuming "${result.metadata.title}" (${result.metadata.model_id})`,
|
|
108
|
+
`resuming "${stripAnsi(result.metadata.title)}" (${stripAnsi(result.metadata.model_id)})`,
|
|
107
109
|
];
|
|
108
110
|
if (result.toolRunsSummarised > 0) {
|
|
109
111
|
lines.push(` ${result.toolRunsSummarised} earlier tool run(s) are summarised, not repeated`);
|
|
@@ -50,6 +50,7 @@ export interface ResumeCheckInput {
|
|
|
50
50
|
* with a key that could call a model says nothing about whether this key can.
|
|
51
51
|
*/
|
|
52
52
|
export declare function resumeBlockers(input: ResumeCheckInput): readonly ResumeBlocker[];
|
|
53
|
+
/** R-4 · The values quoted here are recorded ones, and this is shown on a terminal. */
|
|
53
54
|
export declare function describeBlocker(blocker: ResumeBlocker): string;
|
|
54
55
|
/**
|
|
55
56
|
* What a resumed run starts with, stated so the caller cannot forget.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/sessions/resume.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/sessions/resume.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,CAwClE;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,GACtB,SAAS,aAAa,EAAE,CAa1B;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAS9D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,kBAAkB,CAQpB"}
|
package/dist/sessions/resume.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// what was said. A replayed `tool_calls` block would invite the model to
|
|
10
10
|
// continue a plan the user approved yesterday, in a workspace that has since
|
|
11
11
|
// changed, with permissions this run may not have.
|
|
12
|
+
import { stripAnsi } from "../agent/command-policy.js";
|
|
12
13
|
const DEFAULT_MAX_MESSAGES = 40;
|
|
13
14
|
/**
|
|
14
15
|
* Rebuild the messages to send, from what was stored.
|
|
@@ -79,14 +80,15 @@ export function resumeBlockers(input) {
|
|
|
79
80
|
}
|
|
80
81
|
return blockers;
|
|
81
82
|
}
|
|
83
|
+
/** R-4 · The values quoted here are recorded ones, and this is shown on a terminal. */
|
|
82
84
|
export function describeBlocker(blocker) {
|
|
83
85
|
switch (blocker.kind) {
|
|
84
86
|
case "model-unavailable":
|
|
85
|
-
return `${blocker.modelId} is not callable with the key you are using now - choose another model to continue`;
|
|
87
|
+
return `${stripAnsi(blocker.modelId)} is not callable with the key you are using now - choose another model to continue`;
|
|
86
88
|
case "model-not-agent-capable":
|
|
87
|
-
return `${blocker.modelId} has no verified tool calling now, so the agent cannot drive it - choose another model`;
|
|
89
|
+
return `${stripAnsi(blocker.modelId)} has no verified tool calling now, so the agent cannot drive it - choose another model`;
|
|
88
90
|
case "workspace-missing":
|
|
89
|
-
return `${blocker.workspace} no longer exists - you can read this conversation, or continue it in another directory`;
|
|
91
|
+
return `${stripAnsi(blocker.workspace)} no longer exists - you can read this conversation, or continue it in another directory`;
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
export function permissionsForResume(declaredThisRun) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-surface.d.ts","sourceRoot":"","sources":["../../src/tui/agent-surface.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,IAAI,CACpB,cAAc,EACd,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAC1D,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;IACnD,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,UAAU,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA+B7B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"agent-surface.d.ts","sourceRoot":"","sources":["../../src/tui/agent-surface.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,IAAI,CACpB,cAAc,EACd,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAC1D,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;IACnD,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,UAAU,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA+B7B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC,CA+EzB;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAqBR"}
|