@wirecat/cli-core 0.508.0 → 0.510.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 +25 -0
- package/dist/calendar/cli/index.js +1 -1
- package/dist/calendar/server-search.d.ts +2 -1
- package/dist/calendar/server-search.js +16 -6
- package/dist/mcp/caller.d.ts +6 -0
- package/dist/mcp/caller.js +13 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/server.js +4 -4
- package/dist/mcp/tool.d.ts +2 -1
- package/dist/mcp/tool.js +1 -1
- package/dist/store/sqlite/facts.d.ts +3 -3
- package/dist/store/sqlite/facts.js +4 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.510.0 — 31.10.2026
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`search events --backend both` marks a repeating event the provider also found.** The stored search shows a series
|
|
15
|
+
as one hit, while a provider such as Google answers the series and each moved occurrence apart, so 0.508.0 left the
|
|
16
|
+
stored hit without `server` and listed the provider's copies again below it. A provider hit is now matched through
|
|
17
|
+
its stored copy's series, and the stored hit takes `server` once.
|
|
18
|
+
- **`search events --help`** says what `--backend both` does in words ("ask both").
|
|
19
|
+
|
|
20
|
+
## 0.509.0 — 31.10.2026
|
|
21
|
+
|
|
22
|
+
### Changed — may break callers
|
|
23
|
+
|
|
24
|
+
- **The MCP agent log names the agent that made the call, not the server.** A row in `agent_actions` was always the
|
|
25
|
+
bot `<command>-mcp` (`tg-mcp`); it is now the client as it named itself (`clientInfo.name` from `initialize`, or the
|
|
26
|
+
per-request `io.modelcontextprotocol/clientInfo`), else the coding agent whose environment started a stdio server
|
|
27
|
+
(`claude-code`), else `<command>-mcp`. A filter on `tg-mcp` no longer finds new rows. The name is the client's own
|
|
28
|
+
claim: a label, never a permission. `callingAgent(fallback, caller?)` from `@wirecat/cli-core/mcp` gives the same
|
|
29
|
+
name to a CLI that runs its own server, and `Registration.log` receives the call's caller.
|
|
30
|
+
- **Any proposed or accepted fact can be corrected.** `facts.add({ supersedes })` took only a decision; it now takes a
|
|
31
|
+
fact of any type, and refuses one already superseded, reversed or rejected. When the new fact is accepted (at once,
|
|
32
|
+
for the owner's), the old one becomes `superseded` whether it was proposed or accepted; a proposed one used to stay
|
|
33
|
+
proposed.
|
|
34
|
+
|
|
10
35
|
## 0.508.0 — 31.10.2026
|
|
11
36
|
|
|
12
37
|
### Added
|
|
@@ -226,7 +226,7 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
226
226
|
const search = example(annotate(searches.command("events"), { effects: deps.serverSearch ? LOCAL_OR_REMOTE_READ : LOCAL_READ })
|
|
227
227
|
.description(deps.serverSearch
|
|
228
228
|
? "find stored events by words, and by meaning when its model is downloaded; with --backend server, ask " +
|
|
229
|
-
"the provider instead, with --backend both, both"
|
|
229
|
+
"the provider instead, or with --backend both, ask both"
|
|
230
230
|
: "find stored events by words, and by meaning when its model is downloaded")
|
|
231
231
|
.argument("<query...>", "what to find in titles, descriptions, places and attendees"), "search events roadmap planning --json", ...(deps.serverSearch
|
|
232
232
|
? ["search events --backend server quarterly review", "search events --backend both quarterly review"]
|
|
@@ -35,7 +35,8 @@ type ServerFound = Awaited<ReturnType<typeof serverSearchAnswer>>;
|
|
|
35
35
|
/**
|
|
36
36
|
* `--backend both`: the stored search's hits in its own order, each one the provider also found marked `server`, then
|
|
37
37
|
* what only the provider found, earliest first. One page and no cursor: the two sides rank and page differently.
|
|
38
|
-
* A provider hit is matched
|
|
38
|
+
* A provider hit is matched through its stored copy: a series, or any of its occurrences, to the stored hit of that
|
|
39
|
+
* series. A provider hit the store does not hold is listed on its own.
|
|
39
40
|
*/
|
|
40
41
|
export declare const bothSearchAnswer: (store: EventStore, accountId: number, archived: EventsFound, served: ServerFound, limit: number) => Promise<{
|
|
41
42
|
query: string;
|
|
@@ -39,24 +39,34 @@ export const serverSearchAnswer = async (source, store, { accountId, account, qu
|
|
|
39
39
|
hits.sort((a, b) => (a.starts_at ?? "").localeCompare(b.starts_at ?? ""));
|
|
40
40
|
return { query, by: "server", hits: hits.slice(0, limit), has_more: more || hits.length > limit };
|
|
41
41
|
};
|
|
42
|
+
/** As the stored search groups hits: the occurrences of one series are one hit. */
|
|
43
|
+
const groupOf = (eventId, eventSeriesId) => eventSeriesId !== null ? `s${eventSeriesId}` : eventId !== null ? `e${eventId}` : undefined;
|
|
42
44
|
/**
|
|
43
45
|
* `--backend both`: the stored search's hits in its own order, each one the provider also found marked `server`, then
|
|
44
46
|
* what only the provider found, earliest first. One page and no cursor: the two sides rank and page differently.
|
|
45
|
-
* A provider hit is matched
|
|
47
|
+
* A provider hit is matched through its stored copy: a series, or any of its occurrences, to the stored hit of that
|
|
48
|
+
* series. A provider hit the store does not hold is listed on its own.
|
|
46
49
|
*/
|
|
47
50
|
export const bothSearchAnswer = async (store, accountId, archived, served, limit) => {
|
|
48
51
|
const calendars = new Map((await store.calendars(accountId)).map((calendar) => [calendar.externalId, calendar]));
|
|
49
52
|
const hits = archived.hits.map((hit) => ({ ...hit }));
|
|
50
|
-
const
|
|
53
|
+
const byGroup = new Map();
|
|
54
|
+
for (const hit of hits) {
|
|
55
|
+
const details = await store.event(Number(hit.ref.slice("event:".length)));
|
|
56
|
+
const group = details && groupOf(details.event.id, details.event.eventSeriesId);
|
|
57
|
+
if (group)
|
|
58
|
+
byGroup.set(group, hit);
|
|
59
|
+
}
|
|
51
60
|
const onlyServer = [];
|
|
52
61
|
for (const hit of served.hits) {
|
|
53
62
|
const calendar = calendars.get(hit.calendar);
|
|
54
63
|
const entry = calendar ? await store.entry(calendar.id, hit.external_id) : null;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
else
|
|
64
|
+
const group = entry ? groupOf(entry.eventId, entry.eventSeriesId) : undefined;
|
|
65
|
+
const held = group === undefined ? undefined : byGroup.get(group);
|
|
66
|
+
if (!held)
|
|
59
67
|
onlyServer.push(hit);
|
|
68
|
+
else if (!held.found_by.includes("server"))
|
|
69
|
+
held.found_by = [...held.found_by, "server"];
|
|
60
70
|
}
|
|
61
71
|
const merged = [...hits, ...onlyServer];
|
|
62
72
|
return {
|
package/dist/mcp/caller.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { type RunCaller } from "../runs/run-context.js";
|
|
2
2
|
export { currentCaller, withCaller } from "../runs/run-context.js";
|
|
3
|
+
/**
|
|
4
|
+
* The name the audit log keeps for the agent behind one call: the client as it named itself, else the coding agent
|
|
5
|
+
* whose environment started a stdio server, else `fallback`. A client's name is its own claim, a label and never a
|
|
6
|
+
* permission (MCP's `clientInfo` is self-reported).
|
|
7
|
+
*/
|
|
8
|
+
export declare const callingAgent: (fallback: string, caller?: RunCaller | undefined) => string;
|
|
3
9
|
/** The `_meta` key the every-profile server names the real caller under, for the profile server it passes a call to. */
|
|
4
10
|
export declare const CALLER_META_KEY = "wirecat.dev/caller";
|
|
5
11
|
/** The name the every-profile server gives itself in `initialize`; only a connection from it may name a caller. */
|
package/dist/mcp/caller.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { CLIENT_INFO_META_KEY } from "@modelcontextprotocol/server";
|
|
2
|
-
import { agentOf, clientHash } from "../runs/run-context.js";
|
|
2
|
+
import { agentOf, clientHash, currentCaller, } from "../runs/run-context.js";
|
|
3
3
|
export { currentCaller, withCaller } from "../runs/run-context.js";
|
|
4
|
+
/**
|
|
5
|
+
* The name the audit log keeps for the agent behind one call: the client as it named itself, else the coding agent
|
|
6
|
+
* whose environment started a stdio server, else `fallback`. A client's name is its own claim, a label and never a
|
|
7
|
+
* permission (MCP's `clientInfo` is self-reported).
|
|
8
|
+
*/
|
|
9
|
+
export const callingAgent = (fallback, caller = currentCaller()) => {
|
|
10
|
+
const client = caller?.agent?.client?.name;
|
|
11
|
+
if (client && client !== "unknown")
|
|
12
|
+
return client;
|
|
13
|
+
const kind = caller?.agent?.kind;
|
|
14
|
+
return kind && kind !== "mcp-client" ? kind : fallback;
|
|
15
|
+
};
|
|
4
16
|
/** The `_meta` key the every-profile server names the real caller under, for the profile server it passes a call to. */
|
|
5
17
|
export const CALLER_META_KEY = "wirecat.dev/caller";
|
|
6
18
|
/** The name the every-profile server gives itself in `initialize`; only a connection from it may name a caller. */
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { mcpUrlOf, portOf, publicUrlOf } from "../http/public-url.js";
|
|
|
2
2
|
export { type AgentAnswer, type AgentContext, type AgentProgram, type AgentTool, type CatalogueOptions, type CommandAvailability, runCommandForAgent, toolsFromCommands, } from "./agent.js";
|
|
3
3
|
export { type AllProfilesOptions, allProfilesServer, type ChildSpec, serveAllProfilesOverHttp, serveAllProfilesOverStdio, } from "./all-profiles.js";
|
|
4
4
|
export { type BotServerOptions, createBotServer, type RunBotCommand } from "./bot/server.js";
|
|
5
|
+
export { callingAgent } from "./caller.js";
|
|
5
6
|
export { revokeAll } from "./http/oauth.js";
|
|
6
7
|
export { type HttpOptions, MCP_PATH, serveOverHttp } from "./http/serve.js";
|
|
7
8
|
export { httpLockFile } from "./http/state.js";
|
package/dist/mcp/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { mcpUrlOf, portOf, publicUrlOf } from "../http/public-url.js";
|
|
|
3
3
|
export { runCommandForAgent, toolsFromCommands, } from "./agent.js";
|
|
4
4
|
export { allProfilesServer, serveAllProfilesOverHttp, serveAllProfilesOverStdio, } from "./all-profiles.js";
|
|
5
5
|
export { createBotServer } from "./bot/server.js";
|
|
6
|
+
export { callingAgent } from "./caller.js";
|
|
6
7
|
export { revokeAll } from "./http/oauth.js";
|
|
7
8
|
export { MCP_PATH, serveOverHttp } from "./http/serve.js";
|
|
8
9
|
export { httpLockFile } from "./http/state.js";
|
package/dist/mcp/server.js
CHANGED
|
@@ -26,7 +26,7 @@ import { levelFor, readKeysForCommand } from "../sends/permissions.js";
|
|
|
26
26
|
import { openStore } from "../store/store.js";
|
|
27
27
|
import { runCommandForAgent, toolsFromCommands } from "./agent.js";
|
|
28
28
|
import { agentLog } from "./agent-log.js";
|
|
29
|
-
import { callerOf, withCaller } from "./caller.js";
|
|
29
|
+
import { callerOf, callingAgent, withCaller } from "./caller.js";
|
|
30
30
|
import { mcpHttpMarker } from "./http/state.js";
|
|
31
31
|
import { instructions } from "./instructions.js";
|
|
32
32
|
import { registerPrompts } from "./prompts.js";
|
|
@@ -182,10 +182,10 @@ export const createServer = (command, context, messenger, sessionOptions) => {
|
|
|
182
182
|
});
|
|
183
183
|
const skill = messenger.skill ? skillResource(app, messenger.skill) : undefined;
|
|
184
184
|
const log = agentLog(() => openStore({ env: context.env, command: app.command, version: app.version }));
|
|
185
|
-
const audit = async (call) => {
|
|
185
|
+
const audit = async (call, caller) => {
|
|
186
186
|
if (!recalledAccount(app, provider, settings.profile, context.env))
|
|
187
187
|
return;
|
|
188
|
-
await log.record({ actor: { bot: `${app.command}-mcp
|
|
188
|
+
await log.record({ actor: { bot: callingAgent(`${app.command}-mcp`, caller) }, ...call });
|
|
189
189
|
};
|
|
190
190
|
const targets = judgedTargets(root);
|
|
191
191
|
const passed = Object.fromEntries(Object.entries(environment).filter(([key]) => !PER_RUN.has(key)));
|
|
@@ -267,7 +267,7 @@ export const createServer = (command, context, messenger, sessionOptions) => {
|
|
|
267
267
|
...outcomeOf(result),
|
|
268
268
|
startedAt,
|
|
269
269
|
finishedAt: Date.now(),
|
|
270
|
-
}).catch(() => undefined);
|
|
270
|
+
}, caller).catch(() => undefined);
|
|
271
271
|
return result;
|
|
272
272
|
},
|
|
273
273
|
});
|
package/dist/mcp/tool.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { WarmEmbedders } from "../embeddings/embed.js";
|
|
|
9
9
|
import { type EntityName } from "../entities/index.js";
|
|
10
10
|
import { type AppErrors } from "../errors/reasons.js";
|
|
11
11
|
import type { MessengerAdapter } from "../ports/messenger.js";
|
|
12
|
+
import { type RunCaller } from "../runs/run-context.js";
|
|
12
13
|
import type { SendGuard } from "../sends/guard.js";
|
|
13
14
|
import { type PermissionKey } from "../sends/permissions.js";
|
|
14
15
|
import { type Services } from "../services/index.js";
|
|
@@ -121,7 +122,7 @@ export interface Registration {
|
|
|
121
122
|
/** A host's permission scope encloses local reads and online calls alike. */
|
|
122
123
|
around?: <T>(name: string, definition: AnyTool, work: () => Promise<T>) => Promise<T>;
|
|
123
124
|
/** Keeps one audit row per call — tool, tier, outcome, times — never the arguments or the answer. */
|
|
124
|
-
log?: (call: ToolCall) => Promise<void>;
|
|
125
|
+
log?: (call: ToolCall, caller: RunCaller | undefined) => Promise<void>;
|
|
125
126
|
/** The CLI's commands, by which an answer's next steps are judged safe to run unasked. */
|
|
126
127
|
targets?: NonNullable<StepInput["targets"]>;
|
|
127
128
|
/** How a call's caller is worked out on this connection (`callerOf`); without it the environment's agent is kept. */
|
package/dist/mcp/tool.js
CHANGED
|
@@ -9,7 +9,7 @@ export interface Fact {
|
|
|
9
9
|
ref: string;
|
|
10
10
|
type: (typeof FACT_TYPES)[number];
|
|
11
11
|
statement: string;
|
|
12
|
-
/**
|
|
12
|
+
/** Proposed, then accepted or rejected; a correction supersedes it; only a decision is reversed. */
|
|
13
13
|
status: (typeof FACT_STATUSES)[number];
|
|
14
14
|
project: string | null;
|
|
15
15
|
subject: string | null;
|
|
@@ -45,7 +45,7 @@ export interface FactInput {
|
|
|
45
45
|
occurredAt?: string;
|
|
46
46
|
evidence?: string[];
|
|
47
47
|
scope?: Fact["scope"] & string;
|
|
48
|
-
/**
|
|
48
|
+
/** The proposed or accepted fact this one corrects, `superseded` once this one is accepted. */
|
|
49
49
|
supersedes?: string;
|
|
50
50
|
/** The memory an agent drew it from: a `created-from` link. */
|
|
51
51
|
createdFrom?: string;
|
|
@@ -60,7 +60,7 @@ export interface FactsStore {
|
|
|
60
60
|
status?: Fact["status"];
|
|
61
61
|
limit?: number;
|
|
62
62
|
}): Promise<Fact[]>;
|
|
63
|
-
/** The owner accepts a proposed fact;
|
|
63
|
+
/** The owner accepts a proposed fact; the fact it supersedes stops holding. */
|
|
64
64
|
accept(reference: string): Promise<Fact>;
|
|
65
65
|
/** An accepted decision no longer holds and nothing replaced it. */
|
|
66
66
|
reverse(reference: string): Promise<Fact>;
|
|
@@ -45,7 +45,7 @@ export const factOperationsOver = (context) => {
|
|
|
45
45
|
const accept = (id) => {
|
|
46
46
|
const row = database.prepare("SELECT * FROM facts WHERE id = ?").get(id);
|
|
47
47
|
if (row.status !== "proposed" && row.status !== "accepted")
|
|
48
|
-
throw new CliError("validation_error", `a ${String(row.status)}
|
|
48
|
+
throw new CliError("validation_error", `a ${String(row.status)} fact cannot be accepted`);
|
|
49
49
|
const { actor } = authorActor(context, "owner");
|
|
50
50
|
const at = now();
|
|
51
51
|
database
|
|
@@ -53,7 +53,7 @@ export const factOperationsOver = (context) => {
|
|
|
53
53
|
.run(actor.type, actor.id, at, id);
|
|
54
54
|
if (row.supersedes_id != null)
|
|
55
55
|
database
|
|
56
|
-
.prepare("UPDATE facts SET status = 'superseded', updated_at = ? WHERE id = ? AND status
|
|
56
|
+
.prepare("UPDATE facts SET status = 'superseded', updated_at = ? WHERE id = ? AND status IN ('proposed', 'accepted')")
|
|
57
57
|
.run(at, Number(row.supersedes_id));
|
|
58
58
|
};
|
|
59
59
|
return {
|
|
@@ -66,16 +66,14 @@ export const factOperationsOver = (context) => {
|
|
|
66
66
|
const occurredAt = input.occurredAt === undefined ? null : calendarMoment(input.occurredAt, "occurredAt");
|
|
67
67
|
if (occurredAt !== null && !Number.isFinite(occurredAt))
|
|
68
68
|
throw new CliError("validation_error", "occurredAt takes an ISO timestamp");
|
|
69
|
-
if (input.supersedes !== undefined && type !== "decision")
|
|
70
|
-
throw new CliError("validation_error", "only a decision supersedes another");
|
|
71
69
|
const id = write(database, () => {
|
|
72
70
|
const project = input.project === undefined ? undefined : requiredThing(database, input.project, "the project");
|
|
73
71
|
if (project && project.type !== "project")
|
|
74
72
|
throw new CliError("validation_error", `${input.project} is not a project`);
|
|
75
73
|
const subject = input.subject === undefined ? undefined : requiredThing(database, input.subject, "the subject");
|
|
76
74
|
const replaced = input.supersedes === undefined ? undefined : rowOf(input.supersedes);
|
|
77
|
-
if (replaced && replaced.
|
|
78
|
-
throw new CliError("validation_error", `${input.supersedes} is
|
|
75
|
+
if (replaced && replaced.status !== "proposed" && replaced.status !== "accepted")
|
|
76
|
+
throw new CliError("validation_error", `${input.supersedes} is ${String(replaced.status)}; only a proposed or accepted fact can be corrected`);
|
|
79
77
|
const owner = input.by === "owner";
|
|
80
78
|
const { actor, model } = authorActor(context, input.by);
|
|
81
79
|
const at = now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.510.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",
|