@wirecat/cli-core 0.466.0 → 0.467.0
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 +36 -0
- package/README.md +4 -2
- package/dist/cli/command-runs.d.ts +38 -10
- package/dist/cli/command-runs.js +51 -17
- package/dist/cli/context.d.ts +3 -0
- package/dist/cli/context.js +15 -3
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/messenger/context.js +1 -1
- package/dist/cli/messenger/observed.js +10 -2
- package/dist/cli/messenger/report.js +48 -3
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.js +7 -3
- package/dist/cli/settings.d.ts +19 -6
- package/dist/cli/settings.js +14 -3
- package/dist/cli/shared-command-exports.d.ts +12 -4
- package/dist/entities/index.d.ts +39 -0
- package/dist/entities/recorded-run.d.ts +39 -0
- package/dist/entities/recorded-run.js +14 -1
- package/dist/mcp/agent.d.ts +3 -0
- package/dist/mcp/agent.js +2 -0
- package/dist/mcp/all-profiles.d.ts +2 -0
- package/dist/mcp/all-profiles.js +16 -3
- package/dist/mcp/bot/server.js +8 -3
- package/dist/mcp/caller.d.ts +38 -0
- package/dist/mcp/caller.js +95 -0
- package/dist/mcp/http/oauth.js +5 -1
- package/dist/mcp/server.js +19 -7
- package/dist/mcp/surface.js +11 -2
- package/dist/mcp/tool.d.ts +4 -1
- package/dist/mcp/tool.js +21 -11
- package/dist/runs/events.d.ts +16 -4
- package/dist/runs/events.js +4 -0
- package/dist/runs/log-attributes.js +7 -1
- package/dist/runs/log-events.js +5 -0
- package/dist/runs/recording.d.ts +10 -3
- package/dist/runs/recording.js +59 -15
- package/dist/runs/run-context.d.ts +55 -4
- package/dist/runs/run-context.js +34 -3
- package/dist/runs/run.d.ts +37 -11
- package/dist/runs/run.js +201 -52
- package/dist/runs/scope.d.ts +12 -0
- package/dist/runs/scope.js +6 -0
- package/dist/services/patience.js +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## 0.467.0 — 27.10.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **Every run is now recorded by default, successes too** — before, only failures were kept unless `--record` or
|
|
15
|
+
`record: true`. Successful runs are kept 7 days (`keep_successful_runs_for_days`), failures 30
|
|
16
|
+
(`keep_runs_for_days`), and the runs folder stays under `runs_size_limit_mb` (200; the oldest successes go
|
|
17
|
+
first, checked once a day). **Privacy:** with `record_input` on, as by default since 0.439.0, every successful
|
|
18
|
+
`send` now leaves its typed words (message text, a chat typed by title) in a local, 0600 run record for 7 days.
|
|
19
|
+
`record: false` restores failures only; `record_input: "failed"` keeps the typed words only in a run that failed;
|
|
20
|
+
`record_input: false` masks them everywhere; `--no-record` keeps nothing. A success that logged no events is one
|
|
21
|
+
line in the day's index, with no folder; `runs show` finds it there. MCP tools keep successes too. A command that
|
|
22
|
+
runs until stopped (`watch`, `serve`, `mcp`) and a command WireCat Desktop runs for itself
|
|
23
|
+
(`WIRECAT_CALLER=desktop`) keep a success only under `--record`. Search history still follows `record`.
|
|
24
|
+
- **A run says who asked for it.** `run.json` gains `via` (`cli`, `mcp-stdio`, `mcp-http`, `desktop`), `mcp`
|
|
25
|
+
(`tool`, `request_id`, `connection_id`, `proxied`) and `agent.client` (`name`, `version`, and over HTTP `id`, the
|
|
26
|
+
first 8 hex of the SHA-256 of the OAuth client id). Over HTTP the agent is `mcp-client` with the
|
|
27
|
+
`Mcp-Session-Id` as its session, never the environment of whoever started `mcp --http`: such runs were credited to
|
|
28
|
+
that agent before. The `run`, `run.details` and `run.hit` answer shapes declare the new fields. The every-profile server names the real caller to the profile server in
|
|
29
|
+
`_meta["wirecat.dev/caller"]`; a profile server trusts it only on a connection that server opened.
|
|
30
|
+
- **Request events gain `level`, `transport`, `attempt` and `wait_ms`**, and `requests` counts wire calls when a
|
|
31
|
+
transport logs them, otherwise operations. `providerRequest` takes a third argument with the call's method, path
|
|
32
|
+
template, transport, attempt, wait and bytes. A run writes at most 2,000 events or 1 MB, then one
|
|
33
|
+
`events.dropped` line and `events_dropped`. `runEvent` reaches the current run from a wire layer handed no sink. A
|
|
34
|
+
retry made above the transport (`patiently`, the bot APIs' rate-limit wait) marks the calls under it with
|
|
35
|
+
`withAttempt`, so their lines say `attempt: 2` and the wait. Events logged after a run finished — a long-running
|
|
36
|
+
server's connection — are dropped rather than written into it.
|
|
37
|
+
- **`record: false` in the config now keeps failures**; before, it kept nothing. `--no-record` still keeps nothing.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **`doctor reports create` labels the ids a run's metadata keeps** — the agent session, the OAuth client id, the
|
|
42
|
+
MCP connection and request, the trace and a failed chat — as it already labelled the events' ids.
|
|
43
|
+
|
|
8
44
|
## 0.466.0 — 27.10.2026
|
|
9
45
|
|
|
10
46
|
### Changed — may break callers
|
package/README.md
CHANGED
|
@@ -218,8 +218,10 @@ they deliberately differ. A rule changes in its own pull request, before the cod
|
|
|
218
218
|
- **The tool never picks an account.** Every account command names its profile — the first word in tg, max and
|
|
219
219
|
wa, an option such as `--account` or `--profile` in the others; without one, the command stops and lists the
|
|
220
220
|
choices — even when there is only one.
|
|
221
|
-
- **Run records keep the command as typed, never a secret.**
|
|
222
|
-
|
|
221
|
+
- **Run records keep the command as typed, never a secret.** Every run is kept, with one line per request it made,
|
|
222
|
+
so `runs show` can explain it: successes for 7 days, failures for 30, within a size limit. A token, a password or
|
|
223
|
+
a phone number is hidden; `record_input: false` leaves the typed words out, `"failed"` keeps them only for a
|
|
224
|
+
failure, and `record: false` keeps failures only.
|
|
223
225
|
- **Tests that cannot touch real data.** A sandbox keeps every test away from the owner's store, and no test may
|
|
224
226
|
sleep. A coverage floor: 92% of lines, and at least half of every file.
|
|
225
227
|
- **Contract cases for every adapter.** `contractCases` turns the port's promises into cases any test runner can run,
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { type RenderFormat, type Streams } from "@wirecat/cli-base";
|
|
2
2
|
import type { Command } from "commander";
|
|
3
3
|
import type { AppIdentity } from "../ports/app.js";
|
|
4
|
-
import type
|
|
5
|
-
|
|
4
|
+
import { type RequestEvent } from "../runs/events.js";
|
|
5
|
+
import { type RecordInput } from "../runs/run-context.js";
|
|
6
|
+
/** What the owner saved, read from the CLI's own config: `record`, `record_input` and how long runs are kept. */
|
|
6
7
|
export interface SavedRunSettings {
|
|
7
8
|
record?: boolean | undefined;
|
|
8
9
|
keepRunsForDays?: number | undefined;
|
|
10
|
+
keepSuccessfulRunsForDays?: number | undefined;
|
|
11
|
+
runsSizeLimitMb?: number | undefined;
|
|
9
12
|
/** `record_input`: left out, the typed words are kept, secrets hidden. */
|
|
10
|
-
recordInput?:
|
|
13
|
+
recordInput?: RecordInput | undefined;
|
|
11
14
|
}
|
|
12
15
|
export interface RunSettings {
|
|
13
16
|
record: boolean;
|
|
14
17
|
keepFailedRuns: boolean;
|
|
15
18
|
keepRunsForDays: number;
|
|
19
|
+
keepSuccessfulRunsForDays: number;
|
|
20
|
+
runsSizeLimitMb: number;
|
|
16
21
|
}
|
|
17
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* `--record`/`--no-record` first, then the config: every run is kept unless it says `record: false`, which keeps
|
|
24
|
+
* failures only; `--no-record` keeps nothing (WC-LIFE-15).
|
|
25
|
+
*/
|
|
18
26
|
export declare const runSettings: (flag: boolean | undefined, saved?: SavedRunSettings) => RunSettings;
|
|
19
27
|
export interface CommandRunOptions {
|
|
20
28
|
app: AppIdentity;
|
|
@@ -47,17 +55,37 @@ export interface CommandRuns {
|
|
|
47
55
|
events(event: RequestEvent): void;
|
|
48
56
|
}
|
|
49
57
|
export declare const commandRuns: (options: CommandRunOptions) => CommandRuns;
|
|
50
|
-
/** For a provider client far from the program: the event lands in the current command's run, or nowhere. */
|
|
51
|
-
export declare const providerEvent: (event: RequestEvent) => void;
|
|
52
58
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
59
|
+
* For a provider client far from the program: the event lands in the current command's run — a `startCli` command's,
|
|
60
|
+
* or the run `run()` opened — or nowhere.
|
|
55
61
|
*/
|
|
56
|
-
export declare const
|
|
62
|
+
export declare const providerEvent: (event: RequestEvent) => void;
|
|
63
|
+
/** What one wire call was, besides its operation: never the URL as sent, its query or its body. */
|
|
64
|
+
export interface ProviderCall {
|
|
65
|
+
method?: string;
|
|
66
|
+
/** The path with ids as `:id`, `/v2/users/:id/meetings`; `endpointTemplate` cuts anything past it. */
|
|
67
|
+
endpointTemplate?: string;
|
|
68
|
+
transport?: RequestEvent["transport"];
|
|
69
|
+
/** 1 for the first try; a retry passes 2 and up, with the wait before it. */
|
|
70
|
+
attempt?: number;
|
|
71
|
+
waitMs?: number;
|
|
72
|
+
bytes?: number;
|
|
73
|
+
}
|
|
74
|
+
export interface ProviderAnswer {
|
|
57
75
|
status?: number;
|
|
58
76
|
error?: unknown;
|
|
59
77
|
errorCode?: string;
|
|
60
|
-
|
|
78
|
+
/** The service's own name for a refusal, `rateLimitExceeded`; dropped unless shaped like one. */
|
|
79
|
+
providerError?: string;
|
|
80
|
+
bytes?: number;
|
|
81
|
+
counts?: Record<string, number>;
|
|
82
|
+
ids?: Record<string, string>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Times one wire call under a fixed operation name — nothing the call carries goes into the run. The returned
|
|
86
|
+
* function ends it; an `error` or an `errorCode` makes it a failed one.
|
|
87
|
+
*/
|
|
88
|
+
export declare const providerRequest: (operation: string, sink?: (event: RequestEvent) => void, call?: ProviderCall) => ({ status, error, errorCode, providerError, bytes, counts, ids }?: ProviderAnswer) => void;
|
|
61
89
|
/**
|
|
62
90
|
* WC-LIFE-15 for a CLI with its own commander program (`startCli`, not `run()`): adds `--record` and `--no-record`,
|
|
63
91
|
* keeps every failed command as a run — a usage error before any action too — and every command when recording is
|
package/dist/cli/command-runs.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { isCliError } from "@wirecat/cli-base";
|
|
3
|
+
import { metaOf } from "@wirecat/cli-base/commands";
|
|
4
|
+
import { providerErrorKey } from "../runs/events.js";
|
|
3
5
|
import { startRecording, wasSettled } from "../runs/recording.js";
|
|
4
|
-
import { KEEP_RUNS_FOR_DAYS } from "../runs/run.js";
|
|
5
|
-
import { runContextOf } from "../runs/run-context.js";
|
|
6
|
+
import { KEEP_RUNS_FOR_DAYS, KEEP_SUCCESSFUL_RUNS_FOR_DAYS, RUNS_SIZE_LIMIT_MB } from "../runs/run.js";
|
|
7
|
+
import { keepsSuccess, runContextOf } from "../runs/run-context.js";
|
|
8
|
+
import { currentAttempt, runEvent } from "../runs/scope.js";
|
|
6
9
|
import { environmentOf } from "./context.js";
|
|
7
10
|
import { rootOf } from "./profile.js";
|
|
8
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* `--record`/`--no-record` first, then the config: every run is kept unless it says `record: false`, which keeps
|
|
13
|
+
* failures only; `--no-record` keeps nothing (WC-LIFE-15).
|
|
14
|
+
*/
|
|
9
15
|
export const runSettings = (flag, saved = {}) => {
|
|
10
|
-
const record = flag ?? saved.record ??
|
|
16
|
+
const record = flag ?? saved.record ?? true;
|
|
11
17
|
return {
|
|
12
18
|
record,
|
|
13
|
-
keepFailedRuns: record ||
|
|
19
|
+
keepFailedRuns: record || flag !== false,
|
|
14
20
|
keepRunsForDays: saved.keepRunsForDays ?? KEEP_RUNS_FOR_DAYS,
|
|
21
|
+
keepSuccessfulRunsForDays: saved.keepSuccessfulRunsForDays ?? KEEP_SUCCESSFUL_RUNS_FOR_DAYS,
|
|
22
|
+
runsSizeLimitMb: saved.runsSizeLimitMb ?? RUNS_SIZE_LIMIT_MB,
|
|
15
23
|
};
|
|
16
24
|
};
|
|
17
25
|
const SKIPPED = new Set(["runs", "help"]);
|
|
@@ -23,7 +31,7 @@ const pathOf = (command) => {
|
|
|
23
31
|
};
|
|
24
32
|
const tracker = (options, typed) => {
|
|
25
33
|
let recording;
|
|
26
|
-
const open = (program, words, flag, format) => {
|
|
34
|
+
const open = (program, words, flag, format, lifetime) => {
|
|
27
35
|
recording = undefined;
|
|
28
36
|
// Reading the log is not worth a run of its own.
|
|
29
37
|
if (SKIPPED.has(words[0] ?? ""))
|
|
@@ -46,13 +54,22 @@ const tracker = (options, typed) => {
|
|
|
46
54
|
const streams = options.streams ?? environment.streams;
|
|
47
55
|
const env = options.env ?? environment.env;
|
|
48
56
|
const command = `${agent ? "mcp " : ""}${words.join(" ") || options.app.command}`;
|
|
57
|
+
const caller = environment.agent?.caller;
|
|
49
58
|
recording = startRecording({
|
|
50
59
|
app: options.app,
|
|
51
60
|
command,
|
|
52
61
|
...(profile === undefined ? {} : { profile }),
|
|
53
|
-
record:
|
|
62
|
+
record: keepsSuccess({
|
|
63
|
+
record: settings.record,
|
|
64
|
+
fromFlag: flag !== undefined,
|
|
65
|
+
env: env ?? process.env,
|
|
66
|
+
caller,
|
|
67
|
+
persistent: lifetime === "persistent",
|
|
68
|
+
}),
|
|
54
69
|
keepFailed: settings.keepFailedRuns,
|
|
55
70
|
keepDays: settings.keepRunsForDays,
|
|
71
|
+
keepSuccessDays: settings.keepSuccessfulRunsForDays,
|
|
72
|
+
maxBytes: settings.runsSizeLimitMb * 1024 * 1024,
|
|
56
73
|
trace: false,
|
|
57
74
|
format,
|
|
58
75
|
...(streams ? { streams } : {}),
|
|
@@ -63,7 +80,8 @@ const tracker = (options, typed) => {
|
|
|
63
80
|
program: rootOf(program),
|
|
64
81
|
...((environment.argv ?? typed) ? { argv: environment.argv ?? typed } : {}),
|
|
65
82
|
env: env ?? process.env,
|
|
66
|
-
recordInput: saved.recordInput
|
|
83
|
+
recordInput: saved.recordInput ?? true,
|
|
84
|
+
caller,
|
|
67
85
|
}),
|
|
68
86
|
});
|
|
69
87
|
};
|
|
@@ -76,7 +94,7 @@ const tracker = (options, typed) => {
|
|
|
76
94
|
start(action) {
|
|
77
95
|
const flags = action.optsWithGlobals();
|
|
78
96
|
const json = (flags.json !== undefined && flags.json !== false) || Boolean(flags.jsonl);
|
|
79
|
-
open(action, pathOf(action), flags.record, options.format?.() ?? (json ? "json" : "pretty"));
|
|
97
|
+
open(action, pathOf(action), flags.record, options.format?.() ?? (json ? "json" : "pretty"), metaOf(action).lifetime);
|
|
80
98
|
},
|
|
81
99
|
async succeed() {
|
|
82
100
|
await finish()?.succeed();
|
|
@@ -91,24 +109,40 @@ const tracker = (options, typed) => {
|
|
|
91
109
|
};
|
|
92
110
|
export const commandRuns = (options) => tracker(options);
|
|
93
111
|
const parses = new AsyncLocalStorage();
|
|
94
|
-
/** For a provider client far from the program: the event lands in the current command's run, or nowhere. */
|
|
95
|
-
export const providerEvent = (event) => parses.getStore()?.runs.events(event);
|
|
96
112
|
/**
|
|
97
|
-
*
|
|
113
|
+
* For a provider client far from the program: the event lands in the current command's run — a `startCli` command's,
|
|
114
|
+
* or the run `run()` opened — or nowhere.
|
|
115
|
+
*/
|
|
116
|
+
export const providerEvent = (event) => {
|
|
117
|
+
const parse = parses.getStore();
|
|
118
|
+
if (parse)
|
|
119
|
+
parse.runs.events(event);
|
|
120
|
+
else
|
|
121
|
+
runEvent(event);
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Times one wire call under a fixed operation name — nothing the call carries goes into the run. The returned
|
|
98
125
|
* function ends it; an `error` or an `errorCode` makes it a failed one.
|
|
99
126
|
*/
|
|
100
|
-
export const providerRequest = (operation, sink = providerEvent) => {
|
|
101
|
-
|
|
127
|
+
export const providerRequest = (operation, sink = providerEvent, call = {}) => {
|
|
128
|
+
const { bytes: sent, ...shape } = call;
|
|
129
|
+
const wire = { operation, level: "wire", ...(call.attempt === undefined ? currentAttempt() : {}), ...shape };
|
|
130
|
+
sink({ event: "request", ...wire, ...(sent === undefined ? {} : { bytes: sent }) });
|
|
102
131
|
const started = performance.now();
|
|
103
|
-
return ({ status, error, errorCode } = {}) => {
|
|
132
|
+
return ({ status, error, errorCode, providerError, bytes, counts, ids } = {}) => {
|
|
104
133
|
const failed = error !== undefined || errorCode !== undefined;
|
|
134
|
+
const key = providerErrorKey(providerError);
|
|
105
135
|
sink({
|
|
106
136
|
event: "response",
|
|
107
|
-
|
|
137
|
+
...wire,
|
|
108
138
|
...(status === undefined ? {} : { status }),
|
|
109
139
|
durationMs: Math.round(performance.now() - started),
|
|
110
140
|
outcome: failed ? "error" : "ok",
|
|
111
141
|
...(failed ? { errorCode: errorCode ?? (isCliError(error) ? error.code : "provider_error") } : {}),
|
|
142
|
+
...(key ? { providerError: key } : {}),
|
|
143
|
+
...(bytes === undefined ? {} : { bytes }),
|
|
144
|
+
...(counts ? { counts } : {}),
|
|
145
|
+
...(ids ? { ids } : {}),
|
|
112
146
|
});
|
|
113
147
|
};
|
|
114
148
|
};
|
|
@@ -144,7 +178,7 @@ const isHelpExit = (error) => {
|
|
|
144
178
|
*/
|
|
145
179
|
export const recordRuns = (program, options) => {
|
|
146
180
|
program
|
|
147
|
-
.option("--record", "keep this run
|
|
181
|
+
.option("--record", "keep this run even where the config says record false; record_input false leaves the typed words out")
|
|
148
182
|
.option("--no-record", "do not keep this run, even if it fails");
|
|
149
183
|
program.hook("preAction", (_root, action) => {
|
|
150
184
|
const parse = parses.getStore();
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { MessageEvent } from "../domain/models.js";
|
|
|
6
6
|
import type { AppIdentity } from "../ports/app.js";
|
|
7
7
|
import type { MessengerAdapter } from "../ports/messenger.js";
|
|
8
8
|
import type { EventSink } from "../runs/events.js";
|
|
9
|
+
import { type RunCaller } from "../runs/run-context.js";
|
|
9
10
|
import type { OpenRecognizer } from "../speech/transcribe.js";
|
|
10
11
|
import type { SecretInput } from "../terminal/prompt.js";
|
|
11
12
|
import { type Closeable } from "./deadline.js";
|
|
@@ -95,6 +96,8 @@ export interface AgentRun {
|
|
|
95
96
|
authority?: boolean;
|
|
96
97
|
/** The server's levels allow `bot.api`: a raw Bot API call, past the send guard, may run. */
|
|
97
98
|
botApi?: boolean;
|
|
99
|
+
/** Who made the MCP call, as the server received it; the run records it instead of the server's environment. */
|
|
100
|
+
caller?: RunCaller;
|
|
98
101
|
}
|
|
99
102
|
export interface RelayedRun {
|
|
100
103
|
/** The profile serve holds; a command for another is refused before it touches the connection. */
|
package/dist/cli/context.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { metaOf } from "@wirecat/cli-base/commands";
|
|
2
3
|
import { runBudget } from "../background/budget.js";
|
|
3
4
|
import { resolveOutput } from "../output.js";
|
|
4
5
|
import { recorded } from "../runs/recording.js";
|
|
5
|
-
import { runContextOf } from "../runs/run-context.js";
|
|
6
|
+
import { currentCaller, keepsSuccess, runContextOf } from "../runs/run-context.js";
|
|
6
7
|
import { withDeadline } from "./deadline.js";
|
|
7
8
|
import { withAbort } from "./execution.js";
|
|
8
9
|
import { rootOf } from "./profile.js";
|
|
@@ -101,13 +102,24 @@ export const baseContext = (command, resolveSettings) => {
|
|
|
101
102
|
program: rootOf(command),
|
|
102
103
|
...(environment.argv ? { argv: environment.argv } : {}),
|
|
103
104
|
env,
|
|
104
|
-
recordInput: settings.recordInput
|
|
105
|
+
recordInput: settings.recordInput ?? true,
|
|
106
|
+
caller: environment.agent?.caller ?? currentCaller(),
|
|
105
107
|
}),
|
|
106
108
|
...(ranFor === undefined ? {} : { profile: ranFor }),
|
|
107
|
-
record:
|
|
109
|
+
record: keepsSuccess({
|
|
110
|
+
record: settings.record,
|
|
111
|
+
fromFlag: settings.sources?.record === "flag",
|
|
112
|
+
env,
|
|
113
|
+
caller: environment.agent?.caller ?? currentCaller(),
|
|
114
|
+
persistent: metaOf(command).lifetime === "persistent",
|
|
115
|
+
}),
|
|
108
116
|
keepFailed: settings.keepFailedRuns,
|
|
109
117
|
trace: settings.trace,
|
|
110
118
|
keepDays: settings.keepRunsForDays,
|
|
119
|
+
...(settings.keepSuccessfulRunsForDays === undefined
|
|
120
|
+
? {}
|
|
121
|
+
: { keepSuccessDays: settings.keepSuccessfulRunsForDays }),
|
|
122
|
+
...(settings.runsSizeLimitMb === undefined ? {} : { maxBytes: settings.runsSizeLimitMb * 1024 * 1024 }),
|
|
111
123
|
processRun: !environment.agent,
|
|
112
124
|
format,
|
|
113
125
|
streams,
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { CORE_LOG_EVENTS } from "../runs/log-events.js";
|
|
|
26
26
|
export { crashOf, type Recording, type RecordingOptions, recorded, startRecording, wasSettled, } from "../runs/recording.js";
|
|
27
27
|
export { findRun, KEEP_RUNS_FOR_DAYS, listRuns, pruneRuns, type RunContext, type RunMetadata, readEvents, runsDirFor, runtime, startRun, } from "../runs/run.js";
|
|
28
28
|
export { runContextOf, secretInput } from "../runs/run-context.js";
|
|
29
|
-
export { currentRun, inRun, type RunScope, useAsProcessRun } from "../runs/scope.js";
|
|
29
|
+
export { type Attempt, currentAttempt, currentRun, inRun, type RunScope, runEvent, useAsProcessRun, withAttempt, } from "../runs/scope.js";
|
|
30
30
|
export { LANGUAGES } from "../search/languages.js";
|
|
31
31
|
export { guardedApp, guardedPress, guardedStart, type Pressed } from "../sends/buttons.js";
|
|
32
32
|
export { guardedClose, guardedCreatePoll, guardedVote } from "../sends/polls.js";
|
|
@@ -52,7 +52,7 @@ export { BotTokenStore, type BotTokenStoreOptions } from "./bot/token.js";
|
|
|
52
52
|
export { PressLog, UpdatesCursor } from "./bot/updates.js";
|
|
53
53
|
export type { CommandAvailability, DiscoveredCommand } from "./command-availability.js";
|
|
54
54
|
export { commandContract, type JsonSchema, SCHEMA_DIALECT } from "./command-contract.js";
|
|
55
|
-
export { type CommandRunOptions, type CommandRuns, commandRuns, providerEvent, providerRequest, type RunSettings, recordRuns, runSettings, type SavedRunSettings, } from "./command-runs.js";
|
|
55
|
+
export { type CommandRunOptions, type CommandRuns, commandRuns, type ProviderAnswer, type ProviderCall, providerEvent, providerRequest, type RunSettings, recordRuns, runSettings, type SavedRunSettings, } from "./command-runs.js";
|
|
56
56
|
export { CONTRACT } from "./commands-command.js";
|
|
57
57
|
export { refuseUnknownKey } from "./config-command.js";
|
|
58
58
|
export { type ConfigIssue, type ConfigIssueKind, checkConfigText, issueMessage, readConfigFile, readConfigText, settingPathOf, withoutSetting, } from "./config-issues.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -22,7 +22,7 @@ export { CORE_LOG_EVENTS } from "../runs/log-events.js";
|
|
|
22
22
|
export { crashOf, recorded, startRecording, wasSettled, } from "../runs/recording.js";
|
|
23
23
|
export { findRun, KEEP_RUNS_FOR_DAYS, listRuns, pruneRuns, readEvents, runsDirFor, runtime, startRun, } from "../runs/run.js";
|
|
24
24
|
export { runContextOf, secretInput } from "../runs/run-context.js";
|
|
25
|
-
export { currentRun, inRun, useAsProcessRun } from "../runs/scope.js";
|
|
25
|
+
export { currentAttempt, currentRun, inRun, runEvent, useAsProcessRun, withAttempt, } from "../runs/scope.js";
|
|
26
26
|
export { LANGUAGES } from "../search/languages.js";
|
|
27
27
|
export { guardedApp, guardedPress, guardedStart } from "../sends/buttons.js";
|
|
28
28
|
export { guardedClose, guardedCreatePoll, guardedVote } from "../sends/polls.js";
|
|
@@ -413,7 +413,7 @@ export const messengerContext = (command, messenger, resolving = {}) => {
|
|
|
413
413
|
offline: base.settings.offline,
|
|
414
414
|
reads: messenger.history ?? "server",
|
|
415
415
|
// Turning recording off by name (--no-record, or record: false) keeps no search history either.
|
|
416
|
-
history: base.settings.
|
|
416
|
+
history: base.settings.record,
|
|
417
417
|
searchCatchUp: base.settings.searchCatchUp,
|
|
418
418
|
pictures: base.settings.pictureSearch,
|
|
419
419
|
reranker: base.settings.searchReranker,
|
|
@@ -10,12 +10,19 @@ export const observed = (messenger, events) => {
|
|
|
10
10
|
const timed = async (operation, named, work, after) => {
|
|
11
11
|
const write = currentOperation();
|
|
12
12
|
const marked = (part = {}) => write === undefined ? part : { ...part, ids: { ...part.ids, operation: write } };
|
|
13
|
-
events({ event: "request", operation, ...marked(named) });
|
|
13
|
+
events({ event: "request", operation, level: "operation", ...marked(named) });
|
|
14
14
|
const started = performance.now();
|
|
15
15
|
const durationMs = () => Math.round(performance.now() - started);
|
|
16
16
|
try {
|
|
17
17
|
const answer = await work();
|
|
18
|
-
events({
|
|
18
|
+
events({
|
|
19
|
+
event: "response",
|
|
20
|
+
operation,
|
|
21
|
+
level: "operation",
|
|
22
|
+
durationMs: durationMs(),
|
|
23
|
+
outcome: "ok",
|
|
24
|
+
...marked(after?.(answer)),
|
|
25
|
+
});
|
|
19
26
|
return answer;
|
|
20
27
|
}
|
|
21
28
|
catch (error) {
|
|
@@ -23,6 +30,7 @@ export const observed = (messenger, events) => {
|
|
|
23
30
|
events({
|
|
24
31
|
event: "response",
|
|
25
32
|
operation,
|
|
33
|
+
level: "operation",
|
|
26
34
|
durationMs: durationMs(),
|
|
27
35
|
outcome: "error",
|
|
28
36
|
errorCode: isCliFailure(error) ? error.code : "generic_failure",
|
|
@@ -17,6 +17,14 @@ const EVENT_FIELDS = [
|
|
|
17
17
|
"error_code",
|
|
18
18
|
"provider_error",
|
|
19
19
|
"code",
|
|
20
|
+
"level",
|
|
21
|
+
"transport",
|
|
22
|
+
"attempt",
|
|
23
|
+
"wait_ms",
|
|
24
|
+
"method",
|
|
25
|
+
"endpoint_template",
|
|
26
|
+
"status",
|
|
27
|
+
"dropped",
|
|
20
28
|
];
|
|
21
29
|
/**
|
|
22
30
|
* **Everything a report holds is already free of content** — the doctor, the run log and the send
|
|
@@ -38,7 +46,7 @@ export const buildReport = ({ doctor, runsDir, runId, command, sends, now = new
|
|
|
38
46
|
},
|
|
39
47
|
run: chosen
|
|
40
48
|
? {
|
|
41
|
-
metadata: maskedRun(chosen.metadata, program),
|
|
49
|
+
metadata: maskedRun(chosen.metadata, label, program),
|
|
42
50
|
events: readEvents(chosen.dir).map(eventOf(label, eventFields)),
|
|
43
51
|
}
|
|
44
52
|
: null,
|
|
@@ -46,8 +54,43 @@ export const buildReport = ({ doctor, runsDir, runId, command, sends, now = new
|
|
|
46
54
|
};
|
|
47
55
|
return JSON.parse(hideHome(JSON.stringify(report), home));
|
|
48
56
|
};
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
/**
|
|
58
|
+
* A report leaves the machine, so it masks the typed words as `record_input: false` would, and every id that ties it
|
|
59
|
+
* to a session, a connection, a trace, an app or a chat becomes a label, as the events' ids do.
|
|
60
|
+
*/
|
|
61
|
+
const maskedRun = (metadata, label, program = new Command()) => {
|
|
62
|
+
const { agent, mcp, partial, traceId } = metadata;
|
|
63
|
+
return {
|
|
64
|
+
...metadata,
|
|
65
|
+
...(metadata.argv ? { argv: maskArgv(program, metadata.argv).argv } : {}),
|
|
66
|
+
...(traceId ? { traceId: label(traceId) } : {}),
|
|
67
|
+
...(agent
|
|
68
|
+
? {
|
|
69
|
+
agent: {
|
|
70
|
+
...agent,
|
|
71
|
+
session_id: agent.session_id === null ? null : label(agent.session_id),
|
|
72
|
+
...(agent.client
|
|
73
|
+
? { client: { ...agent.client, ...(agent.client.id ? { id: label(agent.client.id) } : {}) } }
|
|
74
|
+
: {}),
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
: {}),
|
|
78
|
+
...(mcp
|
|
79
|
+
? {
|
|
80
|
+
mcp: {
|
|
81
|
+
...mcp,
|
|
82
|
+
...(mcp.request_id ? { request_id: label(mcp.request_id) } : {}),
|
|
83
|
+
...(mcp.connection_id ? { connection_id: label(mcp.connection_id) } : {}),
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
: {}),
|
|
87
|
+
...(partial
|
|
88
|
+
? {
|
|
89
|
+
partial: { ...partial, failures: partial.failures.map((failure) => ({ ...failure, id: label(failure.id) })) },
|
|
90
|
+
}
|
|
91
|
+
: {}),
|
|
92
|
+
};
|
|
93
|
+
};
|
|
51
94
|
/**
|
|
52
95
|
* Every field of a journal line that names a chat, a message or a write. A send's `operationId` is
|
|
53
96
|
* its `sendId`, so one left raw would undo the other's label.
|
|
@@ -105,6 +148,8 @@ const RECORDED_ID_FIELDS = new Set([
|
|
|
105
148
|
const eventOf = (label, fields) => (event) => {
|
|
106
149
|
const kept = Object.fromEntries(Object.entries(event)
|
|
107
150
|
.filter(([key]) => (fields === "recorded" ? RECORDED_FIELDS.has(key) : EVENT_FIELDS.includes(key)))
|
|
151
|
+
// A line from before records were nested holds pino's numeric `level`, not the request's layer.
|
|
152
|
+
.filter(([key, value]) => key !== "level" || typeof value === "string")
|
|
108
153
|
.map(([key, value]) => [
|
|
109
154
|
key,
|
|
110
155
|
fields === "recorded" && RECORDED_ID_FIELDS.has(key) && value !== undefined && value !== null
|
package/dist/cli/program.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface ProgramDefinition {
|
|
|
13
13
|
permissionKey?: (path: readonly string[]) => string | null | undefined;
|
|
14
14
|
/** A CLI's own keys (`commandPermissions`), from `settingsFor`: its commands are refused by them before they run. */
|
|
15
15
|
permissions?: CommandPermissions | undefined;
|
|
16
|
-
resolveSettings: (flags?: GlobalFlags, options?: ResolveOptions) => Pick<Settings, "profile" | "keepFailedRuns" | "keepRunsForDays" | "skillHint"> & Partial<Pick<Settings, "recordInput" | "permissions" | "permissionSources" | "configPath" | "savedPermissions" | "sources">>;
|
|
16
|
+
resolveSettings: (flags?: GlobalFlags, options?: ResolveOptions) => Pick<Settings, "profile" | "keepFailedRuns" | "keepRunsForDays" | "skillHint"> & Partial<Pick<Settings, "recordInput" | "keepSuccessfulRunsForDays" | "permissions" | "permissionSources" | "configPath" | "savedPermissions" | "sources">>;
|
|
17
17
|
};
|
|
18
18
|
configure?: (program: Command) => void;
|
|
19
19
|
/** The tool's pitch and "what you can do" lines on `--help` and a bare run. */
|
package/dist/cli/program.js
CHANGED
|
@@ -69,8 +69,8 @@ export const createProgram = ({ app, commands, configure, firstScreen }, { out,
|
|
|
69
69
|
.option("--full", `${found} with their whole text and every provider field`)
|
|
70
70
|
.option("--dry-run", "preview parsed arguments and permissions before running the action")
|
|
71
71
|
.option("--yes", "go ahead without the question an ask level puts before a write")
|
|
72
|
-
.option("--record", "keep this run
|
|
73
|
-
.option("--no-record", "do not keep
|
|
72
|
+
.option("--record", "keep this run even where the config says record false; record_input false leaves the typed words out")
|
|
73
|
+
.option("--no-record", "do not keep this run, even if it fails")
|
|
74
74
|
.showHelpAfterError();
|
|
75
75
|
for (const command of mounted)
|
|
76
76
|
program.addCommand(command);
|
|
@@ -617,7 +617,8 @@ const keepFailure = async (failure, { definition, program, rest, argv, profile,
|
|
|
617
617
|
program,
|
|
618
618
|
argv,
|
|
619
619
|
env,
|
|
620
|
-
recordInput: settings ? settings.recordInput
|
|
620
|
+
recordInput: settings ? (settings.recordInput ?? true) : false,
|
|
621
|
+
caller: options.agent?.caller,
|
|
621
622
|
}),
|
|
622
623
|
// Settings that did not load never accepted the word, which may be a refused name or somebody's words.
|
|
623
624
|
profile: settings ? namedProfile(settings) : undefined,
|
|
@@ -628,6 +629,9 @@ const keepFailure = async (failure, { definition, program, rest, argv, profile,
|
|
|
628
629
|
streams: options.streams ?? processStreams,
|
|
629
630
|
env,
|
|
630
631
|
...(settings ? { keepDays: settings.keepRunsForDays } : {}),
|
|
632
|
+
...(settings?.keepSuccessfulRunsForDays === undefined
|
|
633
|
+
? {}
|
|
634
|
+
: { keepSuccessDays: settings.keepSuccessfulRunsForDays }),
|
|
631
635
|
}, async () => {
|
|
632
636
|
throw failure;
|
|
633
637
|
}).catch(() => { });
|
package/dist/cli/settings.d.ts
CHANGED
|
@@ -2,12 +2,15 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
2
2
|
import * as v from "valibot";
|
|
3
3
|
import { type AISettings } from "../analysis/settings.js";
|
|
4
4
|
import { type AppIdentity } from "../ports/app.js";
|
|
5
|
+
import type { RecordInput } from "../runs/run-context.js";
|
|
5
6
|
import { type Level, type PermissionKey } from "../sends/permissions.js";
|
|
6
7
|
import { type ConfigIssue } from "./config-issues.js";
|
|
7
8
|
import type { CommandPermissions } from "./permission-keys.js";
|
|
8
9
|
export declare const plain: (rule: string) => (issue: v.BaseIssue<unknown>) => string;
|
|
9
10
|
export declare const count: v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>;
|
|
10
11
|
export declare const flag: v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>;
|
|
12
|
+
/** `record_input`: true, false, or "failed" — the words typed kept only in a run that failed. */
|
|
13
|
+
export declare const recordInputSetting: v.UnionSchema<[v.BooleanSchema<undefined>, v.LiteralSchema<"failed", undefined>], (issue: v.BaseIssue<unknown>) => string>;
|
|
11
14
|
/**
|
|
12
15
|
* **What one messenger adds to the file**, merged into the one strict schema — a field an adapter
|
|
13
16
|
* reads but the schema did not know would be refused as unknown. Each entry must be optional.
|
|
@@ -102,15 +105,21 @@ export interface Settings extends AISettings {
|
|
|
102
105
|
* differently on purpose — milliseconds in the file, a duration with a unit on the command line.
|
|
103
106
|
*/
|
|
104
107
|
commandTimeoutMs: number | undefined;
|
|
108
|
+
/** Every run is kept unless the config says `record: false` or the command says `--no-record`. */
|
|
105
109
|
record: boolean;
|
|
106
110
|
/**
|
|
107
|
-
* A run record keeps the words typed, secrets hidden; off masks every value that may be content
|
|
108
|
-
* with settings of its own need not return it: left out, the words
|
|
111
|
+
* A run record keeps the words typed, secrets hidden; off masks every value that may be content; `"failed"` keeps
|
|
112
|
+
* them only in a run that failed. Optional so a CLI with settings of its own need not return it: left out, the words
|
|
113
|
+
* are kept.
|
|
109
114
|
*/
|
|
110
|
-
recordInput?:
|
|
111
|
-
/** A failed run is kept even unrecorded, unless
|
|
115
|
+
recordInput?: RecordInput;
|
|
116
|
+
/** A failed run is kept even unrecorded, unless `--no-record` turned recording off for this command. */
|
|
112
117
|
keepFailedRuns: boolean;
|
|
113
118
|
keepRunsForDays: number;
|
|
119
|
+
/** Successful runs go sooner than failed ones. Optional so a CLI with settings of its own need not return it. */
|
|
120
|
+
keepSuccessfulRunsForDays?: number;
|
|
121
|
+
/** The runs folder's size limit, in megabytes. */
|
|
122
|
+
runsSizeLimitMb?: number;
|
|
114
123
|
/** The variable that made this process read-only, above every level and flag (`<PREFIX>_READ_ONLY`). */
|
|
115
124
|
readOnlyLock?: string;
|
|
116
125
|
/**
|
|
@@ -276,8 +285,10 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
276
285
|
color: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
277
286
|
sender_colors: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
278
287
|
record: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
279
|
-
record_input: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
288
|
+
record_input: v.OptionalSchema<v.UnionSchema<[v.BooleanSchema<undefined>, v.LiteralSchema<"failed", undefined>], (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
280
289
|
keep_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
290
|
+
keep_successful_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
291
|
+
runs_size_limit_mb: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
281
292
|
permissions: v.OptionalSchema<v.RecordSchema<v.GenericSchema<string>, v.PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: v.PicklistIssue) => string>, (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
282
293
|
sends_per_hour: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
283
294
|
requests_per_minute: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 0, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
@@ -350,8 +361,10 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
350
361
|
color: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
351
362
|
sender_colors: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
352
363
|
record: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
353
|
-
record_input: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
364
|
+
record_input: v.OptionalSchema<v.UnionSchema<[v.BooleanSchema<undefined>, v.LiteralSchema<"failed", undefined>], (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
354
365
|
keep_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
366
|
+
keep_successful_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
367
|
+
runs_size_limit_mb: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
355
368
|
permissions: v.OptionalSchema<v.RecordSchema<v.GenericSchema<string>, v.PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: v.PicklistIssue) => string>, (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
356
369
|
sends_per_hour: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
357
370
|
requests_per_minute: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 0, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|