@mafdet/cli 0.4.0 → 0.4.2
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 +50 -0
- package/README.md +64 -3
- package/README.zh-CN.md +53 -1
- package/dist/agent/loop.d.ts +14 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +16 -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/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/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 +10 -0
- package/dist/tui/agent-surface.d.ts.map +1 -1
- package/dist/tui/agent-surface.js +27 -3
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +61 -3
- package/dist/tui/input.d.ts +13 -1
- package/dist/tui/input.d.ts.map +1 -1
- package/dist/tui/input.js +9 -2
- 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 +16 -1
- package/dist/tui/start-agent.d.ts.map +1 -1
- package/dist/tui/start-agent.js +125 -1
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// CLI-025 · Adding up what a session used.
|
|
2
|
+
//
|
|
3
|
+
// This module does arithmetic and nothing else. In particular it never prices
|
|
4
|
+
// anything: it sums the `estimated_charge_micro_usd` each request already
|
|
5
|
+
// recorded, which the agent loop computed with the gateway's own arithmetic.
|
|
6
|
+
// A second price implementation here would drift from the first one, and the
|
|
7
|
+
// first one is the one that was reconciled against the ledger.
|
|
8
|
+
//
|
|
9
|
+
// The four states in §20.7 are the other half. `estimated` is what we worked
|
|
10
|
+
// out locally; `settled` is what the server's ledger says; `pending` means the
|
|
11
|
+
// answer exists but has not been fetched; `unknown` means we never got a
|
|
12
|
+
// request id and so can never ask. They are counted separately because
|
|
13
|
+
// collapsing them would let a display show an estimate as though it were the
|
|
14
|
+
// final charge.
|
|
15
|
+
const ZERO = {
|
|
16
|
+
modelRequests: 0,
|
|
17
|
+
promptTokens: 0,
|
|
18
|
+
completionTokens: 0,
|
|
19
|
+
estimatedMicroUsd: 0n,
|
|
20
|
+
settledMicroUsd: 0n,
|
|
21
|
+
bySettlement: { settled: 0, pending: 0, unknown: 0 },
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A request with no id can never be reconciled, whatever it says.
|
|
25
|
+
*
|
|
26
|
+
* CLI-001 found that a cancelled or timed-out request may not return one, and
|
|
27
|
+
* calling such a request `pending` would promise an answer that will never
|
|
28
|
+
* arrive.
|
|
29
|
+
*
|
|
30
|
+
* Note what is *not* consulted: the `settlement_status` written on the usage
|
|
31
|
+
* line itself. That records what was known at the moment of the request, which
|
|
32
|
+
* is always "pending" - only a `settlement` event, appended by CLI-027 after
|
|
33
|
+
* the ledger was actually asked, can make a request settled.
|
|
34
|
+
*/
|
|
35
|
+
function statusOf(event, settled) {
|
|
36
|
+
if (typeof event.request_id !== "string" || event.request_id.length === 0) {
|
|
37
|
+
return "unknown";
|
|
38
|
+
}
|
|
39
|
+
return settled ? "settled" : "pending";
|
|
40
|
+
}
|
|
41
|
+
function add(base, event, settledMicro) {
|
|
42
|
+
const status = statusOf(event, settledMicro !== undefined);
|
|
43
|
+
return {
|
|
44
|
+
modelRequests: base.modelRequests + 1,
|
|
45
|
+
promptTokens: base.promptTokens + count(event.prompt_tokens),
|
|
46
|
+
completionTokens: base.completionTokens + count(event.completion_tokens),
|
|
47
|
+
estimatedMicroUsd: base.estimatedMicroUsd + micro(event.estimated_charge_micro_usd),
|
|
48
|
+
settledMicroUsd: status === "settled"
|
|
49
|
+
? base.settledMicroUsd + (settledMicro ?? 0n)
|
|
50
|
+
: base.settledMicroUsd,
|
|
51
|
+
bySettlement: {
|
|
52
|
+
...base.bySettlement,
|
|
53
|
+
[status]: base.bySettlement[status] + 1,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The settled amount per request id, from the `settlement` events.
|
|
59
|
+
*
|
|
60
|
+
* Read in a first pass because a settlement is appended long after the request
|
|
61
|
+
* it settles. Later wins: re-running `--refresh` after the ledger corrected a
|
|
62
|
+
* charge should show the correction, not the first answer we happened to get.
|
|
63
|
+
*/
|
|
64
|
+
function settlementsIn(events) {
|
|
65
|
+
const found = new Map();
|
|
66
|
+
for (const event of events) {
|
|
67
|
+
if (event.type !== "settlement")
|
|
68
|
+
continue;
|
|
69
|
+
const id = event.request_id;
|
|
70
|
+
if (typeof id !== "string" || id.length === 0)
|
|
71
|
+
continue;
|
|
72
|
+
found.set(id, micro(event.settled_charge_micro_usd));
|
|
73
|
+
}
|
|
74
|
+
return found;
|
|
75
|
+
}
|
|
76
|
+
function count(value) {
|
|
77
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
78
|
+
}
|
|
79
|
+
/** Money is integral end to end; a malformed figure counts as zero, not NaN. */
|
|
80
|
+
function micro(value) {
|
|
81
|
+
if (typeof value !== "string" || !/^-?\d+$/.test(value))
|
|
82
|
+
return 0n;
|
|
83
|
+
return BigInt(value);
|
|
84
|
+
}
|
|
85
|
+
export function summariseUsage(events) {
|
|
86
|
+
let totals = ZERO;
|
|
87
|
+
const byModel = new Map();
|
|
88
|
+
const byKey = new Map();
|
|
89
|
+
const settlements = settlementsIn(events);
|
|
90
|
+
let userMessages = 0;
|
|
91
|
+
let toolRuns = 0;
|
|
92
|
+
let toolRunsRefused = 0;
|
|
93
|
+
let rateLimitHits = 0;
|
|
94
|
+
let rateLimitWaitMs = 0;
|
|
95
|
+
for (const event of events) {
|
|
96
|
+
if (event.type === "user_message") {
|
|
97
|
+
userMessages += 1;
|
|
98
|
+
}
|
|
99
|
+
else if (event.type === "tool_result") {
|
|
100
|
+
toolRuns += 1;
|
|
101
|
+
if (event.status === "refused")
|
|
102
|
+
toolRunsRefused += 1;
|
|
103
|
+
}
|
|
104
|
+
else if (event.type === "rate_limited") {
|
|
105
|
+
rateLimitHits += 1;
|
|
106
|
+
rateLimitWaitMs += count(event.wait_ms);
|
|
107
|
+
}
|
|
108
|
+
else if (event.type === "usage") {
|
|
109
|
+
const usage = event;
|
|
110
|
+
const settled = typeof usage.request_id === "string"
|
|
111
|
+
? settlements.get(usage.request_id)
|
|
112
|
+
: undefined;
|
|
113
|
+
totals = add(totals, usage, settled);
|
|
114
|
+
const model = typeof usage.model_id === "string" ? usage.model_id : "(unknown model)";
|
|
115
|
+
byModel.set(model, add(byModel.get(model) ?? ZERO, usage, settled));
|
|
116
|
+
// Grouped only when the key is known: inventing a bucket for requests
|
|
117
|
+
// recorded before per-key attribution existed would misattribute them.
|
|
118
|
+
if (typeof usage.api_key_ref === "string" &&
|
|
119
|
+
usage.api_key_ref.length > 0) {
|
|
120
|
+
byKey.set(usage.api_key_ref, add(byKey.get(usage.api_key_ref) ?? ZERO, usage, settled));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
...totals,
|
|
126
|
+
userMessages,
|
|
127
|
+
toolRuns,
|
|
128
|
+
toolRunsRefused,
|
|
129
|
+
rateLimitHits,
|
|
130
|
+
rateLimitWaitMs,
|
|
131
|
+
byModel: [...byModel.entries()]
|
|
132
|
+
.map(([modelId, t]) => ({ modelId, ...t }))
|
|
133
|
+
.sort((a, b) => b.modelRequests - a.modelRequests),
|
|
134
|
+
byKey: [...byKey.entries()]
|
|
135
|
+
.map(([apiKeyRef, t]) => ({ apiKeyRef, ...t }))
|
|
136
|
+
.sort((a, b) => b.modelRequests - a.modelRequests),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* How to describe the settled column without overclaiming.
|
|
141
|
+
*
|
|
142
|
+
* Returns undefined when there is nothing honest to say, so a caller renders
|
|
143
|
+
* no figure rather than a zero - a zero reads as "it was free".
|
|
144
|
+
*/
|
|
145
|
+
export function settledCaveat(totals) {
|
|
146
|
+
const { settled, pending, unknown } = totals.bySettlement;
|
|
147
|
+
if (settled === 0 && pending === 0 && unknown === 0)
|
|
148
|
+
return undefined;
|
|
149
|
+
const parts = [];
|
|
150
|
+
if (pending > 0)
|
|
151
|
+
parts.push(`${pending} not settled yet`);
|
|
152
|
+
if (unknown > 0)
|
|
153
|
+
parts.push(`${unknown} with no request id, so unknowable`);
|
|
154
|
+
if (parts.length === 0)
|
|
155
|
+
return undefined;
|
|
156
|
+
return `${parts.join(", ")}; the estimate covers all ${totals.modelRequests}`;
|
|
157
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runSession, type SessionOptions, type SessionSummary } from "../agent/session.js";
|
|
2
2
|
import { type TerminalCapabilities } from "./theme.js";
|
|
3
|
+
import type { SessionRecorder } from "../sessions/recorder.js";
|
|
3
4
|
import type { WidgetIo } from "./widgets.js";
|
|
4
5
|
export interface AgentSurfaceOptions {
|
|
5
6
|
readonly io: WidgetIo;
|
|
@@ -9,6 +10,15 @@ export interface AgentSurfaceOptions {
|
|
|
9
10
|
};
|
|
10
11
|
/** Everything runSession needs except the parts this surface supplies. */
|
|
11
12
|
readonly session: Omit<SessionOptions, "stdout" | "stderr" | "readLine" | "approver" | "onEvent">;
|
|
13
|
+
/**
|
|
14
|
+
* Read fresh for every turn, so a cancelled request can be followed by a new
|
|
15
|
+
* one. Passed as a function rather than a getter on `session`: the spread
|
|
16
|
+
* below would evaluate a getter once and copy the result, quietly freezing
|
|
17
|
+
* the signal to whatever controller existed at startup.
|
|
18
|
+
*/
|
|
19
|
+
readonly getSignal?: () => AbortSignal | undefined;
|
|
20
|
+
/** Present when this run is being recorded; absent for --no-history. */
|
|
21
|
+
readonly recorder?: SessionRecorder;
|
|
12
22
|
/** Injected by the tests; production uses runSession. */
|
|
13
23
|
readonly run?: typeof runSession;
|
|
14
24
|
}
|
|
@@ -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;AAEhE,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,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;AAEhE,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,CA8DzB;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAgBR"}
|
|
@@ -56,6 +56,7 @@ export async function runAgentSurface(options) {
|
|
|
56
56
|
const cards = new ToolCardLog();
|
|
57
57
|
const run = options.run ?? runSession;
|
|
58
58
|
const onEvent = (event) => {
|
|
59
|
+
options.recorder?.observe(event);
|
|
59
60
|
const changed = cards.apply(event);
|
|
60
61
|
if (changed !== undefined) {
|
|
61
62
|
options.stdout.write(renderCard(changed, capabilities));
|
|
@@ -67,16 +68,39 @@ export async function runAgentSurface(options) {
|
|
|
67
68
|
options.stdout.write(dim(` rate limited - waiting ${Math.round(event.waitMs / 1000)}s\n`));
|
|
68
69
|
}
|
|
69
70
|
};
|
|
70
|
-
const
|
|
71
|
+
const sessionOptions = {
|
|
71
72
|
...options.session,
|
|
72
73
|
stdout: (text) => void options.stdout.write(text),
|
|
73
74
|
// The line session sends progress to stderr so it survives redirection.
|
|
74
75
|
// Here everything is on one terminal, so it goes to the same place.
|
|
75
76
|
stderr: (text) => void options.stdout.write(dim(text)),
|
|
76
77
|
approver: tuiApprover(io),
|
|
77
|
-
readLine: () =>
|
|
78
|
+
readLine: async () => {
|
|
79
|
+
// Checkpoint between goals rather than per event: the listing only needs
|
|
80
|
+
// to be current when someone might go and look at it.
|
|
81
|
+
options.recorder?.checkpoint();
|
|
82
|
+
const line = await promptLine(io, "> ");
|
|
83
|
+
// The user's own words never appear in the agent event stream, so they
|
|
84
|
+
// are recorded here. Slash commands are control, not conversation.
|
|
85
|
+
if (line !== undefined &&
|
|
86
|
+
line.trim().length > 0 &&
|
|
87
|
+
!line.startsWith("/")) {
|
|
88
|
+
options.recorder?.goal(line);
|
|
89
|
+
}
|
|
90
|
+
return line;
|
|
91
|
+
},
|
|
78
92
|
onEvent,
|
|
79
|
-
}
|
|
93
|
+
};
|
|
94
|
+
// Installed after the spread, on purpose - see getSignal above.
|
|
95
|
+
if (options.getSignal !== undefined) {
|
|
96
|
+
Object.defineProperty(sessionOptions, "signal", {
|
|
97
|
+
get: options.getSignal,
|
|
98
|
+
enumerable: true,
|
|
99
|
+
configurable: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const summary = await run(sessionOptions);
|
|
103
|
+
options.recorder?.close();
|
|
80
104
|
options.stdout.write(renderSummary(summary, capabilities));
|
|
81
105
|
return summary;
|
|
82
106
|
}
|
package/dist/tui/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAaxD,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAcrE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,GAAG;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiJnE"}
|
package/dist/tui/app.js
CHANGED
|
@@ -9,13 +9,16 @@ import { ExitCode } from "../output/exit-codes.js";
|
|
|
9
9
|
import { loadCatalogue, saveDefaultModel } from "./adapters/models.js";
|
|
10
10
|
import { renderBanner } from "./banner.js";
|
|
11
11
|
import { keyboard } from "./input.js";
|
|
12
|
+
import { FORCED_EXIT_NOTICE, InterruptTracker } from "./interrupt.js";
|
|
12
13
|
import { runOnboarding } from "./onboarding.js";
|
|
13
14
|
import { noAgentModelsNotice, pickModel } from "./picker.js";
|
|
14
15
|
import { palette, detectCapabilities, } from "./theme.js";
|
|
15
16
|
import { followResize } from "./resize.js";
|
|
16
17
|
import { installTerminalGuard } from "./terminal.js";
|
|
17
18
|
import { inspectWorkspace, renderWorkspaceSummary, } from "./workspace-summary.js";
|
|
18
|
-
import { parsePermissions, startAgent } from "./start-agent.js";
|
|
19
|
+
import { parsePermissions, parseSessionOptions, startAgent, } from "./start-agent.js";
|
|
20
|
+
import { sessionsDir } from "../config/paths.js";
|
|
21
|
+
import { chooseSession } from "./session-centre.js";
|
|
19
22
|
import { select } from "./widgets.js";
|
|
20
23
|
export async function runTui(options) {
|
|
21
24
|
// Live, not a snapshot: a window resized mid-session must not keep laying
|
|
@@ -35,10 +38,46 @@ export async function runTui(options) {
|
|
|
35
38
|
options.stderr.write(`mafdet: ${message}\n`);
|
|
36
39
|
},
|
|
37
40
|
});
|
|
38
|
-
|
|
41
|
+
// Two-stage Ctrl+C (plan 19.7). In raw mode Ctrl+C arrives as a byte rather
|
|
42
|
+
// than a signal, and during a model request nothing is reading keys - so
|
|
43
|
+
// without this it did nothing at all until the request finished by itself.
|
|
44
|
+
const interrupts = new InterruptTracker();
|
|
45
|
+
let inFlight;
|
|
46
|
+
const { dim } = palette(capabilities);
|
|
47
|
+
const onInterrupt = () => {
|
|
48
|
+
const outcome = interrupts.press();
|
|
49
|
+
if (outcome.action === "cancel-operation") {
|
|
50
|
+
options.stdout.write(dim("\n cancelling...\n"));
|
|
51
|
+
// Abort what is running, then hand the session a fresh controller so it
|
|
52
|
+
// returns to the prompt instead of ending.
|
|
53
|
+
inFlight?.abort();
|
|
54
|
+
inFlight = new AbortController();
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (outcome.action === "force-exit") {
|
|
58
|
+
options.stdout.write(`\n ${FORCED_EXIT_NOTICE}\n`);
|
|
59
|
+
guard.restore();
|
|
60
|
+
options.host.exit(130);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// Idle: leave it to whatever is reading, which already treats Ctrl+C as
|
|
64
|
+
// "cancel this prompt" and ends the session.
|
|
65
|
+
return false;
|
|
66
|
+
};
|
|
67
|
+
const keys = keyboard(options.stdin, { onInterrupt });
|
|
39
68
|
const io = {
|
|
40
69
|
write: (text) => void options.stdout.write(text),
|
|
41
|
-
|
|
70
|
+
// Waiting for input is exactly what "idle" means, so the tracker learns it
|
|
71
|
+
// here rather than by being told from inside the session.
|
|
72
|
+
nextKeys: async () => {
|
|
73
|
+
interrupts.finish();
|
|
74
|
+
try {
|
|
75
|
+
return await keys.nextKeys();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
interrupts.begin();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
42
81
|
capabilities,
|
|
43
82
|
};
|
|
44
83
|
try {
|
|
@@ -67,6 +106,17 @@ export async function runTui(options) {
|
|
|
67
106
|
if (chosen === undefined)
|
|
68
107
|
return ExitCode.Cancelled;
|
|
69
108
|
options.stdout.write(renderWorkspaceSummary(inspectWorkspace(options.workspace, existsSync), capabilities));
|
|
109
|
+
// Offered only when there is something to resume, and only when the user
|
|
110
|
+
// did not already say what they wanted on the command line.
|
|
111
|
+
const sessionOptions = parseSessionOptions(options.argv);
|
|
112
|
+
let resumeId = sessionOptions.resumeId;
|
|
113
|
+
if (resumeId === undefined && !sessionOptions.noHistory) {
|
|
114
|
+
const choice = await chooseSession(io, sessionsDir(options.commandIo.env ?? process.env));
|
|
115
|
+
if (choice.kind === "cancelled")
|
|
116
|
+
return ExitCode.Cancelled;
|
|
117
|
+
if (choice.kind === "resume")
|
|
118
|
+
resumeId = choice.sessionId;
|
|
119
|
+
}
|
|
70
120
|
return await startAgent({
|
|
71
121
|
io,
|
|
72
122
|
capabilities,
|
|
@@ -76,6 +126,14 @@ export async function runTui(options) {
|
|
|
76
126
|
model: chosen,
|
|
77
127
|
workspace: options.workspace,
|
|
78
128
|
permissions: parsePermissions(options.argv),
|
|
129
|
+
session: {
|
|
130
|
+
...sessionOptions,
|
|
131
|
+
...(resumeId === undefined ? {} : { resumeId }),
|
|
132
|
+
},
|
|
133
|
+
getSignal: () => {
|
|
134
|
+
inFlight ??= new AbortController();
|
|
135
|
+
return inFlight.signal;
|
|
136
|
+
},
|
|
79
137
|
});
|
|
80
138
|
}
|
|
81
139
|
finally {
|
package/dist/tui/input.d.ts
CHANGED
|
@@ -13,5 +13,17 @@ export interface RawStdin {
|
|
|
13
13
|
off?(event: string, listener: (chunk: string) => void): unknown;
|
|
14
14
|
readonly isTTY?: boolean;
|
|
15
15
|
}
|
|
16
|
-
export
|
|
16
|
+
export interface KeyboardOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Called the moment an interrupt key is decoded, whether or not anyone is
|
|
19
|
+
* waiting for input. Return true to consume it, so a Ctrl+C that cancelled a
|
|
20
|
+
* request is not also delivered to the prompt as "quit".
|
|
21
|
+
*
|
|
22
|
+
* Without this the byte simply queued: in raw mode Ctrl+C is not a signal,
|
|
23
|
+
* and during a model request nothing is calling nextKeys(), so pressing it
|
|
24
|
+
* did nothing at all until the request finished on its own.
|
|
25
|
+
*/
|
|
26
|
+
readonly onInterrupt?: () => boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function keyboard(stdin: RawStdin, options?: KeyboardOptions): KeySource;
|
|
17
29
|
//# sourceMappingURL=input.d.ts.map
|
package/dist/tui/input.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/tui/input.ts"],"names":[],"mappings":"AAQA,OAAO,EAAc,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,WAAW,SAAS;IACxB,QAAQ,IAAI,OAAO,CAAC,SAAS,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IAChD,4DAA4D;IAC5D,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACpC,MAAM,CAAC,IAAI,OAAO,CAAC;IACnB,KAAK,CAAC,IAAI,OAAO,CAAC;IAClB,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/tui/input.ts"],"names":[],"mappings":"AAQA,OAAO,EAAc,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,WAAW,SAAS;IACxB,QAAQ,IAAI,OAAO,CAAC,SAAS,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IAChD,4DAA4D;IAC5D,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACpC,MAAM,CAAC,IAAI,OAAO,CAAC;IACnB,KAAK,CAAC,IAAI,OAAO,CAAC;IAClB,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CACtC;AAED,wBAAgB,QAAQ,CACtB,KAAK,EAAE,QAAQ,EACf,OAAO,GAAE,eAAoB,GAC5B,SAAS,CAsDX"}
|
package/dist/tui/input.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// that arrives is decoded and kept, and a widget asking for input drains the
|
|
7
7
|
// queue before it waits.
|
|
8
8
|
import { decodeKeys } from "./keys.js";
|
|
9
|
-
export function keyboard(stdin) {
|
|
9
|
+
export function keyboard(stdin, options = {}) {
|
|
10
10
|
const pending = [];
|
|
11
11
|
let waiting;
|
|
12
12
|
let ended = false;
|
|
@@ -21,7 +21,14 @@ export function keyboard(stdin) {
|
|
|
21
21
|
pending.push([...keys]);
|
|
22
22
|
};
|
|
23
23
|
const onData = (chunk) => {
|
|
24
|
-
|
|
24
|
+
let keys = decodeKeys(chunk);
|
|
25
|
+
if (options.onInterrupt !== undefined &&
|
|
26
|
+
keys.some((k) => k.kind === "interrupt")) {
|
|
27
|
+
// Handled out of band; only drop the key if the handler took it.
|
|
28
|
+
const consumed = options.onInterrupt();
|
|
29
|
+
if (consumed)
|
|
30
|
+
keys = keys.filter((k) => k.kind !== "interrupt");
|
|
31
|
+
}
|
|
25
32
|
if (keys.length > 0)
|
|
26
33
|
deliver(keys);
|
|
27
34
|
};
|
package/dist/tui/route.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/tui/route.ts"],"names":[],"mappings":"AAOA,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAClB,qBAAqB,GACrB,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,WAAW,GACX,IAAI,CAAC;AAET,MAAM,MAAM,aAAa;AACvB,gDAAgD;AAC9C;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE;AAC1B,4EAA4E;GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE;AAC9B,yEAAyE;GACvE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAA;CAAE,CAAC;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CAC5D;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/tui/route.ts"],"names":[],"mappings":"AAOA,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAClB,qBAAqB,GACrB,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,WAAW,GACX,IAAI,CAAC;AAET,MAAM,MAAM,aAAa;AACvB,gDAAgD;AAC9C;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE;AAC1B,4EAA4E;GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE;AAC9B,yEAAyE;GACvE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAA;CAAE,CAAC;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CAC5D;AA0CD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,aAAa,CAoBlE;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAezD"}
|
package/dist/tui/route.js
CHANGED
|
@@ -9,9 +9,26 @@
|
|
|
9
9
|
* `mafdet` so that `mafdet --json` can never be answered with a drawing.
|
|
10
10
|
*/
|
|
11
11
|
const MACHINE_FLAGS = new Set(["--json", "--quiet", "-q"]);
|
|
12
|
-
/**
|
|
12
|
+
/** Flags whose next argument is a value, not a command. */
|
|
13
|
+
const FLAGS_TAKING_A_VALUE = new Set([
|
|
14
|
+
"--title",
|
|
15
|
+
"--allow-command",
|
|
16
|
+
"--resume",
|
|
17
|
+
]);
|
|
13
18
|
function hasCommand(argv) {
|
|
14
|
-
|
|
19
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
20
|
+
const arg = argv[i];
|
|
21
|
+
if (arg.startsWith("-")) {
|
|
22
|
+
// `mafdet --title "Fix login"` is still a bare invocation: the value
|
|
23
|
+
// belongs to the flag, and reading it as a command would send the user
|
|
24
|
+
// to "unknown command 'Fix login'".
|
|
25
|
+
if (FLAGS_TAKING_A_VALUE.has(arg))
|
|
26
|
+
i += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
15
32
|
}
|
|
16
33
|
/**
|
|
17
34
|
* CI systems attach a pipe often enough that the TTY checks usually catch them,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type IndexEntry } from "../sessions/index-file.js";
|
|
2
|
+
import { type TerminalCapabilities } from "./theme.js";
|
|
3
|
+
import { type WidgetIo } from "./widgets.js";
|
|
4
|
+
export type SessionChoice = {
|
|
5
|
+
readonly kind: "new";
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: "resume";
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: "cancelled";
|
|
11
|
+
};
|
|
12
|
+
export declare function recentSessions(root: string): readonly IndexEntry[];
|
|
13
|
+
export declare function describeSession(entry: IndexEntry, capabilities: TerminalCapabilities): string;
|
|
14
|
+
export declare function chooseSession(io: WidgetIo, root: string): Promise<SessionChoice>;
|
|
15
|
+
//# sourceMappingURL=session-centre.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-centre.d.ts","sourceRoot":"","sources":["../../src/tui/session-centre.ts"],"names":[],"mappings":"AAOA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAKrD,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAEnC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,UAAU,EAAE,CAQlE;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,oBAAoB,GACjC,MAAM,CAQR;AAOD,wBAAsB,aAAa,CACjC,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,aAAa,CAAC,CAoBxB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// CLI-024 · New conversation, or one of the recent ones.
|
|
2
|
+
//
|
|
3
|
+
// Offered only when there is something to resume: a menu whose only entry is
|
|
4
|
+
// "New conversation" is a keystroke charged for nothing. Everything here reads
|
|
5
|
+
// the local index and sends no request, so browsing costs nothing (plan 20.2).
|
|
6
|
+
import { formatUsd } from "../output/money.js";
|
|
7
|
+
import { loadIndex } from "../sessions/index-file.js";
|
|
8
|
+
import { palette } from "./theme.js";
|
|
9
|
+
import { select } from "./widgets.js";
|
|
10
|
+
/** How many recent conversations to offer before it stops being a list. */
|
|
11
|
+
const RECENT_LIMIT = 8;
|
|
12
|
+
export function recentSessions(root) {
|
|
13
|
+
try {
|
|
14
|
+
return loadIndex(root).sessions.slice(0, RECENT_LIMIT);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// A store we cannot read is a store with nothing to offer, not a failure
|
|
18
|
+
// that should stop someone starting a new conversation.
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function describeSession(entry, capabilities) {
|
|
23
|
+
const parts = [
|
|
24
|
+
entry.model_id,
|
|
25
|
+
workspaceName(entry.workspace),
|
|
26
|
+
`${entry.model_requests} request(s)`,
|
|
27
|
+
`~${formatUsd(entry.estimated_charge_micro_usd)}`,
|
|
28
|
+
];
|
|
29
|
+
return palette(capabilities).dim(parts.join(" - "));
|
|
30
|
+
}
|
|
31
|
+
function workspaceName(workspace) {
|
|
32
|
+
const name = workspace.split(/[\\/]/).filter(Boolean).at(-1);
|
|
33
|
+
return name === undefined || name.length === 0 ? workspace : name;
|
|
34
|
+
}
|
|
35
|
+
export async function chooseSession(io, root) {
|
|
36
|
+
const recent = recentSessions(root);
|
|
37
|
+
if (recent.length === 0)
|
|
38
|
+
return { kind: "new" };
|
|
39
|
+
const answer = await select(io, {
|
|
40
|
+
title: "What would you like to do?",
|
|
41
|
+
items: [
|
|
42
|
+
{ value: "", label: "New conversation" },
|
|
43
|
+
...recent.map((entry) => ({
|
|
44
|
+
value: entry.session_id,
|
|
45
|
+
label: entry.title,
|
|
46
|
+
detail: stripStyles(describeSession(entry, io.capabilities)),
|
|
47
|
+
})),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
if (answer.kind === "cancelled")
|
|
51
|
+
return { kind: "cancelled" };
|
|
52
|
+
return answer.value === ""
|
|
53
|
+
? { kind: "new" }
|
|
54
|
+
: { kind: "resume", sessionId: answer.value };
|
|
55
|
+
}
|
|
56
|
+
/** The select widget dims details itself; doubling it up reads as a bug. */
|
|
57
|
+
function stripStyles(text) {
|
|
58
|
+
return text.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
|
|
59
|
+
}
|
|
@@ -3,6 +3,12 @@ import { type CommandIo } from "../commands/runtime.js";
|
|
|
3
3
|
import { ExitCode } from "../output/exit-codes.js";
|
|
4
4
|
import { type TerminalCapabilities } from "./theme.js";
|
|
5
5
|
import type { WidgetIo } from "./widgets.js";
|
|
6
|
+
export interface SessionOptions {
|
|
7
|
+
readonly noHistory: boolean;
|
|
8
|
+
readonly title?: string;
|
|
9
|
+
/** Continue this session rather than starting a new one. */
|
|
10
|
+
readonly resumeId?: string;
|
|
11
|
+
}
|
|
6
12
|
export interface AgentPermissions {
|
|
7
13
|
readonly allowWrite: boolean;
|
|
8
14
|
readonly allowedCommands: readonly string[];
|
|
@@ -13,6 +19,8 @@ export interface AgentPermissions {
|
|
|
13
19
|
* The same spellings the agent command uses, so there is one grammar for
|
|
14
20
|
* granting these and no second one to keep in step.
|
|
15
21
|
*/
|
|
22
|
+
/** `--no-history` and `--title`, read off the bare invocation. */
|
|
23
|
+
export declare function parseSessionOptions(argv: readonly string[]): SessionOptions;
|
|
16
24
|
export declare function parsePermissions(argv: readonly string[]): AgentPermissions;
|
|
17
25
|
export interface StartAgentOptions {
|
|
18
26
|
readonly io: WidgetIo;
|
|
@@ -25,7 +33,14 @@ export interface StartAgentOptions {
|
|
|
25
33
|
readonly model: ResolvedModel;
|
|
26
34
|
readonly workspace: string;
|
|
27
35
|
readonly permissions: AgentPermissions;
|
|
28
|
-
readonly
|
|
36
|
+
readonly session?: SessionOptions;
|
|
37
|
+
/**
|
|
38
|
+
* Read fresh for every goal. A cancel aborts the request in flight and then
|
|
39
|
+
* swaps in a new controller, so the session survives Ctrl+C instead of
|
|
40
|
+
* ending on it - runSession re-reads `signal` each turn, which is what makes
|
|
41
|
+
* that possible.
|
|
42
|
+
*/
|
|
43
|
+
readonly getSignal?: () => AbortSignal | undefined;
|
|
29
44
|
}
|
|
30
45
|
export declare function startAgent(options: StartAgentOptions): Promise<ExitCode>;
|
|
31
46
|
//# sourceMappingURL=start-agent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-agent.d.ts","sourceRoot":"","sources":["../../src/tui/start-agent.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"start-agent.d.ts","sourceRoot":"","sources":["../../src/tui/start-agent.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAcnD,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;GAKG;AACH,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAqB3E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAe1E;AAED,MAAM,WAAW,iBAAiB;IAChC,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,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;CACpD;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,QAAQ,CAAC,CAkGnB"}
|