@wirecat/cli-core 0.359.0 → 0.360.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
CHANGED
|
@@ -7,6 +7,18 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.360.0 — 19.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Adapter test authors can shorten a connection's close wait and re-key pauses.** The run environment takes
|
|
15
|
+
`closeWaitMs` (how long a connection's close waits on its saves and passive listens, 5 s by default) and
|
|
16
|
+
`rekeyPacing` (`{ lockMs, pauseMs }` for chat re-keys, 50 ms and 25 ms by default). Commands, the shared services and
|
|
17
|
+
the MCP server's tool calls pass them on; a command `serve` runs for a client through its relay keeps the
|
|
18
|
+
defaults. A test through `run()` no longer pays the real waits. Only code
|
|
19
|
+
that builds the environment can set them; no flag, variable or MCP call does
|
|
20
|
+
([ADAPTERS.md](docs/dev/ADAPTERS.md#running-the-contract-cases)).
|
|
21
|
+
|
|
10
22
|
## 0.359.0 — 19.10.2026
|
|
11
23
|
|
|
12
24
|
### Fixed
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ export interface BaseEnvironment {
|
|
|
35
35
|
recognizer?: OpenRecognizer;
|
|
36
36
|
/** How long `store restore` and `store reset` wait on another connection's write, 1 s unless a test hands one in. */
|
|
37
37
|
storeLockWaitMs?: number;
|
|
38
|
+
/** How long a connection's close waits on its saves and passive listens, 5 s unless a test hands one in. */
|
|
39
|
+
closeWaitMs?: number;
|
|
40
|
+
/** How long one pass of chat re-keys holds the store's write lock and the pause after it; tests hand in zeros. */
|
|
41
|
+
rekeyPacing?: {
|
|
42
|
+
lockMs: number;
|
|
43
|
+
pauseMs: number;
|
|
44
|
+
};
|
|
38
45
|
/** Where raw bytes go when stdout is not this process's own — an MCP session relayed by serve. */
|
|
39
46
|
stdout?: NodeJS.WritableStream;
|
|
40
47
|
/** Where a prompt and what is typed at it go, in place of the process's stderr; tests hand one in. */
|
|
@@ -8,7 +8,7 @@ import { type PermissionKey } from "../../sends/permissions.js";
|
|
|
8
8
|
import { type ServiceMessenger, type Services } from "../../services/index.js";
|
|
9
9
|
import type { OpenRecognizer } from "../../speech/transcribe.js";
|
|
10
10
|
import { type AccountKey, type DeletionScope, type MessageStore } from "../../store/store.js";
|
|
11
|
-
import { type BaseContext } from "../context.js";
|
|
11
|
+
import { type BaseContext, type BaseEnvironment } from "../context.js";
|
|
12
12
|
import { type PermissionKeyOf } from "../permission-keys.js";
|
|
13
13
|
import type { GlobalFlags, ResolveOptions, Settings } from "../settings.js";
|
|
14
14
|
/**
|
|
@@ -225,6 +225,8 @@ export interface MessengerContext extends BaseContext {
|
|
|
225
225
|
name?: string;
|
|
226
226
|
}) => Promise<T>;
|
|
227
227
|
}
|
|
228
|
+
/** The waits a test shortens through its environment; a run without them keeps the defaults. */
|
|
229
|
+
type ConnectionWaits = Pick<BaseEnvironment, "closeWaitMs" | "rekeyPacing">;
|
|
228
230
|
/**
|
|
229
231
|
* A connection as every shared reader sees it: its account remembered, each call a run event, what
|
|
230
232
|
* the reads answer saved to the store. `close` closes the connection and the store it opened. One
|
|
@@ -234,7 +236,7 @@ export interface MessengerContext extends BaseContext {
|
|
|
234
236
|
* does not `watch` is open: what it queued while nothing was connected is delivered once and cannot be fetched again.
|
|
235
237
|
* A connection opened with `listen: true` is left to its `watch` and `feed`, which save the same.
|
|
236
238
|
*/
|
|
237
|
-
export declare const connected: (connection: MessengerAdapter, { app, provider, name, deletedWithoutChat, pace, history, savedChatId, }: Pick<Messenger, "app" | "provider" | "name" | "deletedWithoutChat" | "pace" | "history" | "savedChatId">, { settings, env, renderer }: Pick<BaseContext, "settings" | "env" | "renderer">, events: EventSink, store?: LazyStore, { listen: watching }?: Pick<ConnectOptions, "listen">) => {
|
|
239
|
+
export declare const connected: (connection: MessengerAdapter, { app, provider, name, deletedWithoutChat, pace, history, savedChatId, }: Pick<Messenger, "app" | "provider" | "name" | "deletedWithoutChat" | "pace" | "history" | "savedChatId">, { settings, env, renderer }: Pick<BaseContext, "settings" | "env" | "renderer">, events: EventSink, store?: LazyStore, { listen: watching, closeWaitMs, rekeyPacing, }?: Pick<ConnectOptions, "listen"> & ConnectionWaits) => {
|
|
238
240
|
adapter: MessengerAdapter;
|
|
239
241
|
close: () => Promise<void>;
|
|
240
242
|
};
|
|
@@ -253,3 +255,4 @@ export declare const connectedFor: (command: Command, messenger: Pick<Messenger,
|
|
|
253
255
|
*/
|
|
254
256
|
export declare const refuseLocalWrite: (context: MessengerContext, command: string, permission: PermissionKey) => void;
|
|
255
257
|
export declare const messengerContext: (command: Command, messenger: Messenger, resolving?: Pick<ResolveOptions, "needsProfile">) => MessengerContext;
|
|
258
|
+
export {};
|
|
@@ -118,7 +118,7 @@ const settled = async (pending, ms) => {
|
|
|
118
118
|
* never fails the command; one that fails once the connection is `closing` is the close, not news. It still listens
|
|
119
119
|
* while the connection closes, so what arrives then is saved too.
|
|
120
120
|
*/
|
|
121
|
-
const listened = ({ run, operation, what }, events, warn) => {
|
|
121
|
+
const listened = ({ run, operation, what }, events, warn, waitMs) => {
|
|
122
122
|
const abort = new AbortController();
|
|
123
123
|
let closing = false;
|
|
124
124
|
const ended = Promise.resolve()
|
|
@@ -137,7 +137,7 @@ const listened = ({ run, operation, what }, events, warn) => {
|
|
|
137
137
|
},
|
|
138
138
|
stop: () => {
|
|
139
139
|
abort.abort();
|
|
140
|
-
return settled(new Set([ended]),
|
|
140
|
+
return settled(new Set([ended]), waitMs);
|
|
141
141
|
},
|
|
142
142
|
};
|
|
143
143
|
};
|
|
@@ -171,6 +171,10 @@ const rekeyRecipients = async (app, profile, env, warn, from, to) => {
|
|
|
171
171
|
warn(`the recipient list still names ${from}: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
172
|
}
|
|
173
173
|
};
|
|
174
|
+
const waitsOf = (command) => {
|
|
175
|
+
const { closeWaitMs, rekeyPacing } = environmentOf(command);
|
|
176
|
+
return { ...(closeWaitMs === undefined ? {} : { closeWaitMs }), ...(rekeyPacing ? { rekeyPacing } : {}) };
|
|
177
|
+
};
|
|
174
178
|
/**
|
|
175
179
|
* A connection as every shared reader sees it: its account remembered, each call a run event, what
|
|
176
180
|
* the reads answer saved to the store. `close` closes the connection and the store it opened. One
|
|
@@ -180,7 +184,7 @@ const rekeyRecipients = async (app, profile, env, warn, from, to) => {
|
|
|
180
184
|
* does not `watch` is open: what it queued while nothing was connected is delivered once and cannot be fetched again.
|
|
181
185
|
* A connection opened with `listen: true` is left to its `watch` and `feed`, which save the same.
|
|
182
186
|
*/
|
|
183
|
-
export const connected = (connection, { app, provider, name, deletedWithoutChat, pace, history, savedChatId, }, { settings, env, renderer }, events, store = lazyStore(env, app.command), { listen: watching = false } = {}) => {
|
|
187
|
+
export const connected = (connection, { app, provider, name, deletedWithoutChat, pace, history, savedChatId, }, { settings, env, renderer }, events, store = lazyStore(env, app.command), { listen: watching = false, closeWaitMs = SAVES_WAIT_MS, rekeyPacing, } = {}) => {
|
|
184
188
|
const self = connection.self();
|
|
185
189
|
if (self !== null)
|
|
186
190
|
rememberAccount(app, settings.profile, self, env);
|
|
@@ -207,12 +211,13 @@ export const connected = (connection, { app, provider, name, deletedWithoutChat,
|
|
|
207
211
|
announcing,
|
|
208
212
|
closing: closing.signal,
|
|
209
213
|
...(deletedWithoutChat ? { deletedWithoutChat } : {}),
|
|
214
|
+
...(rekeyPacing ? { rekeyPacing } : {}),
|
|
210
215
|
rekeyed: (from, to) => rekeyRecipients(app, settings.profile, env, renderer.warn, from, to),
|
|
211
216
|
recipientIds: () => (new RecipientList(recipientsPathFor(app, settings.profile, env), app.command).read() ?? []).flatMap((recipient) => [recipient.id, ...(recipient.partnerId ? [recipient.partnerId] : [])]),
|
|
212
217
|
...(history === "store" ? { namesFromStore: savedChatId ? { savedChatId } : {} } : {}),
|
|
213
218
|
});
|
|
214
219
|
const passives = history === "store" && self !== null && !watching
|
|
215
|
-
? passivesOf(saving).map((passive) => listened(passive, events, renderer.warn))
|
|
220
|
+
? passivesOf(saving).map((passive) => listened(passive, events, renderer.warn, closeWaitMs))
|
|
216
221
|
: [];
|
|
217
222
|
return {
|
|
218
223
|
adapter: saving,
|
|
@@ -231,7 +236,7 @@ export const connected = (connection, { app, provider, name, deletedWithoutChat,
|
|
|
231
236
|
events({ event: "warning", code: "store_not_written", operation });
|
|
232
237
|
renderer.warn(`not saved to the local store: the messenger was still handing on ${what} at exit`);
|
|
233
238
|
});
|
|
234
|
-
await settled(new Set([...pending, ...announcing]),
|
|
239
|
+
await settled(new Set([...pending, ...announcing]), closeWaitMs);
|
|
235
240
|
closing.abort();
|
|
236
241
|
if (pending.size > 0) {
|
|
237
242
|
events({ event: "warning", code: "store_not_written", operation: "close" });
|
|
@@ -265,7 +270,10 @@ export const connectedFor = async (command, messenger, base, options) => {
|
|
|
265
270
|
}
|
|
266
271
|
return {
|
|
267
272
|
connection,
|
|
268
|
-
...connected(connection, messenger, base, options.events, store,
|
|
273
|
+
...connected(connection, messenger, base, options.events, store, {
|
|
274
|
+
...waitsOf(command),
|
|
275
|
+
...(options.listen ? { listen: true } : {}),
|
|
276
|
+
}),
|
|
269
277
|
};
|
|
270
278
|
};
|
|
271
279
|
const unrecorded = (command, messenger, profile, offline) => {
|
|
@@ -410,7 +418,7 @@ export const messengerContext = (command, messenger, resolving = {}) => {
|
|
|
410
418
|
throw new CliError("validation_error", "the read connection has already been released");
|
|
411
419
|
}
|
|
412
420
|
base.track(connection);
|
|
413
|
-
held = connected(connection, messenger, base, events);
|
|
421
|
+
held = connected(connection, messenger, base, events, undefined, waitsOf(command));
|
|
414
422
|
}
|
|
415
423
|
return held.adapter;
|
|
416
424
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.360.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|