@wirecat/cli-core 0.452.0 → 0.454.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 +71 -0
- package/dist/cli/mcp-server-command.js +5 -1
- package/dist/cli/messenger/search-answers.d.ts +1 -1
- package/dist/cli/messenger/search-answers.js +1 -1
- package/dist/cli/messenger/search-command.js +6 -3
- package/dist/entities/contact-context.d.ts +42 -0
- package/dist/entities/contact-context.js +11 -0
- package/dist/entities/hit.d.ts +3 -3
- package/dist/entities/hit.js +3 -3
- package/dist/entities/index.d.ts +61 -21
- package/dist/entities/task-results.d.ts +20 -20
- package/dist/entities/task-results.js +3 -15
- package/dist/mcp/http/activity.d.ts +24 -0
- package/dist/mcp/http/activity.js +86 -0
- package/dist/mcp/http/serve.js +14 -0
- package/dist/mcp/http/state.d.ts +5 -0
- package/dist/mcp/http/state.js +14 -4
- package/dist/meetings/event-store.d.ts +23 -0
- package/dist/meetings/index.d.ts +1 -1
- package/dist/search/query.js +1 -3
- package/dist/services/events-search.d.ts +67 -0
- package/dist/services/events-search.js +131 -0
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +2 -1
- package/dist/services/notes-search.d.ts +25 -1
- package/dist/services/notes-search.js +24 -11
- package/dist/services/person-context.d.ts +20 -1
- package/dist/services/person-context.js +46 -1
- package/dist/services/search-all-meetings.d.ts +1 -1
- package/dist/services/search-all-meetings.js +7 -2
- package/dist/services/search-all.d.ts +12 -7
- package/dist/services/search-all.js +39 -4
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/project-transfer/request.d.ts +1 -1
- package/dist/store/project-transfer/schema.d.ts +2 -2
- package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
- package/dist/store/sqlite/calendar.js +85 -11
- package/dist/store/sqlite/event-search.d.ts +66 -0
- package/dist/store/sqlite/event-search.js +163 -0
- package/dist/store/sqlite/files.d.ts +4 -0
- package/dist/store/sqlite/files.js +28 -0
- package/dist/store/sqlite/link-kinds.d.ts +5 -1
- package/dist/store/sqlite/link-kinds.js +5 -0
- package/dist/store/sqlite/note-index.d.ts +17 -1
- package/dist/store/sqlite/note-index.js +32 -8
- package/dist/store/sqlite/note-search.d.ts +43 -0
- package/dist/store/sqlite/note-search.js +24 -24
- package/dist/store/sqlite/notes.d.ts +4 -5
- package/dist/store/sqlite/notes.js +2 -2
- package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
- package/dist/store/store.d.ts +2 -1
- package/dist/store/store.js +2 -1
- package/package.json +1 -1
|
@@ -37,9 +37,9 @@ export declare const taskCard: v.StrictObjectSchema<{
|
|
|
37
37
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
38
38
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
39
39
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
40
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
40
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
41
41
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
42
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
42
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
43
43
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
44
44
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
45
45
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -51,9 +51,9 @@ export declare const taskCard: v.StrictObjectSchema<{
|
|
|
51
51
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
52
52
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
53
53
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
54
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
54
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
55
55
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
56
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
56
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
57
57
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
58
58
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
59
59
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -138,9 +138,9 @@ export declare const taskCreated: v.StrictObjectSchema<{
|
|
|
138
138
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
139
139
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
140
140
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
141
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
141
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
142
142
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
143
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
143
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
144
144
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
145
145
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
146
146
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -152,9 +152,9 @@ export declare const taskCreated: v.StrictObjectSchema<{
|
|
|
152
152
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
153
153
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
154
154
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
155
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
155
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
156
156
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
157
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
157
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
158
158
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
159
159
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
160
160
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -240,9 +240,9 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
240
240
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
241
241
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
242
242
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
243
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
243
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
244
244
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
245
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
245
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
246
246
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
247
247
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
248
248
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -254,9 +254,9 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
254
254
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
255
255
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
256
256
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
257
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
257
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
258
258
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
259
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
259
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
260
260
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
261
261
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
262
262
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -370,9 +370,9 @@ export declare const taskMerge: v.StrictObjectSchema<{
|
|
|
370
370
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
371
371
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
372
372
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
373
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
373
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
374
374
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
375
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
375
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
376
376
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
377
377
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
378
378
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -384,9 +384,9 @@ export declare const taskMerge: v.StrictObjectSchema<{
|
|
|
384
384
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
385
385
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
386
386
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
387
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
387
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
388
388
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
389
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
389
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
390
390
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
391
391
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
392
392
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -465,9 +465,9 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
|
|
|
465
465
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
466
466
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
467
467
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
468
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
468
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
469
469
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
470
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
470
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
471
471
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
472
472
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
473
473
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -479,9 +479,9 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
|
|
|
479
479
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
480
480
|
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
481
481
|
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
482
|
-
readonly kind: v.PicklistSchema<["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
482
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to", "attached"], undefined>;
|
|
483
483
|
readonly anchor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
484
|
-
readonly origin: v.PicklistSchema<["file", "owner", "suggested", "message"], undefined>;
|
|
484
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
485
485
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
486
486
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
487
487
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
import { TASK_TYPES } from "../domain/knowledge.js";
|
|
3
|
+
import { LINK_KINDS, LINK_SOURCES } from "../store/sqlite/link-kinds.js";
|
|
3
4
|
import { TASK_KINDS, TASK_ORIGINS, TASK_STATES, TASK_VERDICTS } from "../tasks/index.js";
|
|
4
5
|
import { count, id, reference, text, time } from "./fields.js";
|
|
5
6
|
import { closeSuggestion } from "./task.js";
|
|
@@ -9,22 +10,9 @@ const link = v.strictObject({
|
|
|
9
10
|
id,
|
|
10
11
|
from: reference,
|
|
11
12
|
to: v.nullable(reference),
|
|
12
|
-
kind: v.picklist(
|
|
13
|
-
"links-to",
|
|
14
|
-
"blocked-by",
|
|
15
|
-
"follow-up-of",
|
|
16
|
-
"about",
|
|
17
|
-
"member-of",
|
|
18
|
-
"labelled",
|
|
19
|
-
"answered-by",
|
|
20
|
-
"evidence",
|
|
21
|
-
"created-from",
|
|
22
|
-
"duplicate-of",
|
|
23
|
-
"related-to",
|
|
24
|
-
"assigned-to",
|
|
25
|
-
]),
|
|
13
|
+
kind: v.picklist(LINK_KINDS),
|
|
26
14
|
anchor: v.nullable(text),
|
|
27
|
-
origin: v.picklist(
|
|
15
|
+
origin: v.picklist(LINK_SOURCES),
|
|
28
16
|
target_text: v.nullable(text),
|
|
29
17
|
role: v.nullable(text),
|
|
30
18
|
evidence: v.nullable(text),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** What each logged-in app did last, keyed by its client id. Tool names and times only: never arguments. */
|
|
2
|
+
export interface ClientActivity {
|
|
3
|
+
lastRequestAt: string;
|
|
4
|
+
lastTool?: string;
|
|
5
|
+
lastToolAt?: string;
|
|
6
|
+
/** The local day `toolCalls` counts, `YYYY-MM-DD`. */
|
|
7
|
+
day: string;
|
|
8
|
+
toolCalls: number;
|
|
9
|
+
}
|
|
10
|
+
export type Activity = Record<string, ClientActivity>;
|
|
11
|
+
export declare const httpActivityFile: (tokenFile: string) => string;
|
|
12
|
+
export declare const localDay: (ms: number) => string;
|
|
13
|
+
export declare const readActivity: (path: string) => Activity;
|
|
14
|
+
/** The names of the tools a JSON-RPC body calls; a batch may call several. */
|
|
15
|
+
export declare const calledTools: (body: unknown) => string[];
|
|
16
|
+
/**
|
|
17
|
+
* Keeps the activity file of one running server. A call is written at once, so status shows it while the owner
|
|
18
|
+
* watches; other requests at most every two seconds. Its own file, not the token file: `mcp --revoke` rewrites that
|
|
19
|
+
* from another process.
|
|
20
|
+
*/
|
|
21
|
+
export declare const activityRecorder: (path: string, now?: () => number) => {
|
|
22
|
+
record: (clientId: string, tools: string[]) => void;
|
|
23
|
+
flush: () => void;
|
|
24
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
const WRITE_EVERY_MS = 2000;
|
|
5
|
+
/** Any app may send any name; one that does not look like a tool's is not kept. */
|
|
6
|
+
const TOOL_NAME = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
7
|
+
export const httpActivityFile = (tokenFile) => `${tokenFile.replace(/\.json$/, "")}.activity.json`;
|
|
8
|
+
export const localDay = (ms) => {
|
|
9
|
+
const d = new Date(ms);
|
|
10
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
11
|
+
};
|
|
12
|
+
export const readActivity = (path) => {
|
|
13
|
+
if (!existsSync(path))
|
|
14
|
+
return {};
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
17
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** The names of the tools a JSON-RPC body calls; a batch may call several. */
|
|
24
|
+
export const calledTools = (body) => (Array.isArray(body) ? body : [body]).flatMap((message) => {
|
|
25
|
+
if (!message || typeof message !== "object")
|
|
26
|
+
return [];
|
|
27
|
+
const { method, params } = message;
|
|
28
|
+
const name = params?.name;
|
|
29
|
+
return method === "tools/call" && typeof name === "string" && TOOL_NAME.test(name) ? [name] : [];
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Keeps the activity file of one running server. A call is written at once, so status shows it while the owner
|
|
33
|
+
* watches; other requests at most every two seconds. Its own file, not the token file: `mcp --revoke` rewrites that
|
|
34
|
+
* from another process.
|
|
35
|
+
*/
|
|
36
|
+
export const activityRecorder = (path, now = Date.now) => {
|
|
37
|
+
const activity = readActivity(path);
|
|
38
|
+
let timer;
|
|
39
|
+
const write = () => {
|
|
40
|
+
timer = undefined;
|
|
41
|
+
try {
|
|
42
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
43
|
+
const temporary = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
44
|
+
writeFileSync(temporary, JSON.stringify(activity), { mode: 0o600, flag: "wx" });
|
|
45
|
+
chmodSync(temporary, 0o600);
|
|
46
|
+
renameSync(temporary, path);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Activity is a convenience for the owner; serving must not fail over it.
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
record: (clientId, tools) => {
|
|
54
|
+
const at = now();
|
|
55
|
+
const iso = new Date(at).toISOString();
|
|
56
|
+
const day = localDay(at);
|
|
57
|
+
const before = activity[clientId];
|
|
58
|
+
const counted = before?.day === day ? before.toolCalls : 0;
|
|
59
|
+
const last = tools.at(-1);
|
|
60
|
+
activity[clientId] = {
|
|
61
|
+
lastRequestAt: iso,
|
|
62
|
+
day,
|
|
63
|
+
toolCalls: counted + tools.length,
|
|
64
|
+
...(last
|
|
65
|
+
? { lastTool: last, lastToolAt: iso }
|
|
66
|
+
: before?.lastTool
|
|
67
|
+
? { lastTool: before.lastTool, ...(before.lastToolAt ? { lastToolAt: before.lastToolAt } : {}) }
|
|
68
|
+
: {}),
|
|
69
|
+
};
|
|
70
|
+
if (last) {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
write();
|
|
73
|
+
}
|
|
74
|
+
else if (!timer) {
|
|
75
|
+
timer = setTimeout(write, WRITE_EVERY_MS);
|
|
76
|
+
timer.unref();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
flush: () => {
|
|
80
|
+
if (!timer)
|
|
81
|
+
return;
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
write();
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
package/dist/mcp/http/serve.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { createMcpHandler, getOAuthProtectedResourceMetadataUrl, hostHeaderValidationResponse, isLegacyRequest, oauthMetadataResponse, requireBearerAuth, WebStandardStreamableHTTPServerTransport, } from "@modelcontextprotocol/server";
|
|
3
3
|
import { CliError } from "@wirecat/cli-base";
|
|
4
4
|
import { listen } from "../../http/listen.js";
|
|
5
|
+
import { activityRecorder, calledTools, httpActivityFile } from "./activity.js";
|
|
5
6
|
import { ownerLogin } from "./oauth.js";
|
|
6
7
|
import { httpLock } from "./state.js";
|
|
7
8
|
export const MCP_PATH = "/mcp";
|
|
@@ -74,6 +75,16 @@ const metadataRequest = (request, path, base) => {
|
|
|
74
75
|
url.pathname = inserted ? AS_METADATA : path;
|
|
75
76
|
return new Request(url, { method: request.method, headers: request.headers });
|
|
76
77
|
};
|
|
78
|
+
const toolsIn = async (request) => {
|
|
79
|
+
if (request.method !== "POST")
|
|
80
|
+
return [];
|
|
81
|
+
try {
|
|
82
|
+
return calledTools(await request.clone().json());
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
77
88
|
/**
|
|
78
89
|
* MCP over Streamable HTTP on 127.0.0.1, behind the owner's tunnel (CLI-58). Every MCP request needs
|
|
79
90
|
* a token from the owner login; the Host header must be the tunnel's or this machine's, so a page in
|
|
@@ -116,6 +127,7 @@ export const serveOverHttp = async (build, options) => {
|
|
|
116
127
|
verifier: login.verifier,
|
|
117
128
|
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resource),
|
|
118
129
|
});
|
|
130
|
+
const activity = activityRecorder(httpActivityFile(tokenFile), now);
|
|
119
131
|
const allowedHosts = [...new Set([publicUrl.hostname, ...LOCAL])];
|
|
120
132
|
const allowedOrigins = [publicUrl.origin];
|
|
121
133
|
const listening = await listen(async (request) => {
|
|
@@ -145,6 +157,7 @@ export const serveOverHttp = async (build, options) => {
|
|
|
145
157
|
const auth = await bearer(request);
|
|
146
158
|
if (auth instanceof Response)
|
|
147
159
|
return auth;
|
|
160
|
+
activity.record(auth.clientId, await toolsIn(request));
|
|
148
161
|
return (await isLegacyRequest(request)) ? legacy.handle(request, auth) : mcp.fetch(request, { authInfo: auth });
|
|
149
162
|
}, { host: "127.0.0.1", port }).catch((error) => {
|
|
150
163
|
login.close();
|
|
@@ -156,6 +169,7 @@ export const serveOverHttp = async (build, options) => {
|
|
|
156
169
|
url: listening.url,
|
|
157
170
|
close: async () => {
|
|
158
171
|
login.close();
|
|
172
|
+
activity.flush();
|
|
159
173
|
try {
|
|
160
174
|
await listening.close();
|
|
161
175
|
await legacy.close();
|
package/dist/mcp/http/state.d.ts
CHANGED
|
@@ -44,6 +44,11 @@ export interface SignedInClient {
|
|
|
44
44
|
/** What the app said its name was when it registered — anyone may register, so it proves nothing. */
|
|
45
45
|
claimedName: string | null;
|
|
46
46
|
lastLoginAt: string;
|
|
47
|
+
/** Any MCP request with its token, so the app reached the server after logging in. */
|
|
48
|
+
lastRequestAt: string | null;
|
|
49
|
+
lastTool: string | null;
|
|
50
|
+
lastToolAt: string | null;
|
|
51
|
+
toolCallsToday: number;
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
49
54
|
* The apps that hold a token now, and when each last got one. Ids and hashes stay in the file: what
|
package/dist/mcp/http/state.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { holdLock, readLock, releaseLock, takeLock } from "../../background/lock.js";
|
|
3
|
+
import { httpActivityFile, localDay, readActivity } from "./activity.js";
|
|
3
4
|
export const ACCESS_SECONDS = 60 * 60;
|
|
4
5
|
export const REFRESH_SECONDS = 30 * 24 * 60 * 60;
|
|
5
6
|
export const readStored = (path) => existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : { clients: [], tokens: [] };
|
|
@@ -32,6 +33,8 @@ export const httpLock = (path, first) => {
|
|
|
32
33
|
export const signedInClients = (tokenFile, now = Date.now()) => {
|
|
33
34
|
const seconds = Math.floor(now / 1000);
|
|
34
35
|
const { clients, tokens } = readStored(tokenFile);
|
|
36
|
+
const activity = readActivity(httpActivityFile(tokenFile));
|
|
37
|
+
const today = localDay(now);
|
|
35
38
|
const last = new Map();
|
|
36
39
|
for (const token of tokens) {
|
|
37
40
|
if (token.expiresAt <= seconds)
|
|
@@ -41,9 +44,16 @@ export const signedInClients = (tokenFile, now = Date.now()) => {
|
|
|
41
44
|
}
|
|
42
45
|
return clients
|
|
43
46
|
.filter((client) => last.has(client.id))
|
|
44
|
-
.map((client) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
.map((client) => {
|
|
48
|
+
const did = activity[client.id];
|
|
49
|
+
return {
|
|
50
|
+
claimedName: client.name ?? null,
|
|
51
|
+
lastLoginAt: new Date((last.get(client.id) ?? 0) * 1000).toISOString(),
|
|
52
|
+
lastRequestAt: did?.lastRequestAt ?? null,
|
|
53
|
+
lastTool: did?.lastTool ?? null,
|
|
54
|
+
lastToolAt: did?.lastToolAt ?? null,
|
|
55
|
+
toolCallsToday: did?.day === today ? did.toolCalls : 0,
|
|
56
|
+
};
|
|
57
|
+
})
|
|
48
58
|
.sort((a, b) => b.lastLoginAt.localeCompare(a.lastLoginAt));
|
|
49
59
|
};
|
|
@@ -30,6 +30,20 @@ export interface EntryParticipantInput {
|
|
|
30
30
|
/** A room or a piece of equipment: listed, never a contact. */
|
|
31
31
|
resource?: boolean;
|
|
32
32
|
}
|
|
33
|
+
/** A Google Drive file attached to a calendar entry (Google's `attachments[]`). */
|
|
34
|
+
export interface EntryAttachmentInput {
|
|
35
|
+
/** The Drive file's id: `files.external_id` of a stored Drive file. */
|
|
36
|
+
fileId: string;
|
|
37
|
+
title?: string | null;
|
|
38
|
+
}
|
|
39
|
+
/** A Drive file attached to an entry, and the document it is once Drive has stored it. */
|
|
40
|
+
export interface EntryAttachment {
|
|
41
|
+
entryId: number;
|
|
42
|
+
fileId: string;
|
|
43
|
+
title: string | null;
|
|
44
|
+
/** `null` while no Drive account holds the file, while two do, or while its document is deleted. */
|
|
45
|
+
documentId: number | null;
|
|
46
|
+
}
|
|
33
47
|
export type EntryKind = "single" | "series" | "occurrence";
|
|
34
48
|
export type EntryStatus = "confirmed" | "tentative" | "cancelled";
|
|
35
49
|
/**
|
|
@@ -75,6 +89,12 @@ export interface EntryInput {
|
|
|
75
89
|
externalUpdatedAt?: number | null;
|
|
76
90
|
/** Only the keys `ENTRY_METADATA_KEYS` names are kept. */
|
|
77
91
|
metadata?: Record<string, unknown> | null;
|
|
92
|
+
/**
|
|
93
|
+
* Its Drive files: each links the entry to the file's document, now or once Drive stores the file. Absent keeps the
|
|
94
|
+
* links the entry has; a list replaces them. Google leaves `attachments` out of an event that has none, so a provider
|
|
95
|
+
* sending a whole event sends `[]` then, or a file removed from the event stays linked.
|
|
96
|
+
*/
|
|
97
|
+
attachments?: EntryAttachmentInput[];
|
|
78
98
|
}
|
|
79
99
|
export interface StoredEntry {
|
|
80
100
|
id: number;
|
|
@@ -138,6 +158,7 @@ export interface EventDetails {
|
|
|
138
158
|
entries: StoredEntry[];
|
|
139
159
|
participants: StoredParticipant[];
|
|
140
160
|
revisions: EntryRevision[];
|
|
161
|
+
attachments: EntryAttachment[];
|
|
141
162
|
}
|
|
142
163
|
export interface EventFilter {
|
|
143
164
|
/** Events that end after this instant (exclusive end). */
|
|
@@ -148,6 +169,8 @@ export interface EventFilter {
|
|
|
148
169
|
calendarId?: number;
|
|
149
170
|
/** Events whose entries list one of this person's contacts. */
|
|
150
171
|
personId?: number;
|
|
172
|
+
/** Latest start first; earliest first when absent. */
|
|
173
|
+
newest?: boolean;
|
|
151
174
|
limit?: number;
|
|
152
175
|
offset?: number;
|
|
153
176
|
}
|
package/dist/meetings/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { calendarText, ENTRY_METADATA_KEYS, entryMetadata, withoutLinkQueries }
|
|
|
4
4
|
export type { MeetingContextStore, MeetingEventLinkInput, MeetingEventLinkReceipt, ParticipantPerson, ParticipantPersonInput, ParticipantPersonLinkInput, ParticipantPersonLinkReceipt, } from "./context-store.js";
|
|
5
5
|
export { type MeetingCueIdentity, meetingCueFingerprint } from "./cue-fingerprint.js";
|
|
6
6
|
export { EVENT_QUERY } from "./event-query.js";
|
|
7
|
-
export type { CalendarInput, EntryInput, EntryKind, EntryParticipantInput, EntryRevision, EntryStatus, EventDetails, EventFilter, EventStore, LinkReview, ReviewDecision, StoredCalendar, StoredEntry, StoredParticipant, } from "./event-store.js";
|
|
7
|
+
export type { CalendarInput, EntryAttachment, EntryAttachmentInput, EntryInput, EntryKind, EntryParticipantInput, EntryRevision, EntryStatus, EventDetails, EventFilter, EventStore, LinkReview, ReviewDecision, StoredCalendar, StoredEntry, StoredParticipant, } from "./event-store.js";
|
|
8
8
|
export { linkEvent } from "./events.js";
|
|
9
9
|
export { exportMeeting, type MeetingExport, type MeetingExportFormat, type MeetingExportOptions, serializeVtt, } from "./export.js";
|
|
10
10
|
export { type FindingEvidence, type FindingOutcome, findingHash, MEETING_FINDINGS_KIND, type MeetingFactFinding, type MeetingFinding, type MeetingFindingsInput, type MeetingFindingsReceipt, type MeetingTaskFinding, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
package/dist/search/query.js
CHANGED
|
@@ -27,9 +27,7 @@ export const sourceOf = (name, value, providers) => {
|
|
|
27
27
|
return source;
|
|
28
28
|
if (isKind(source))
|
|
29
29
|
fail(`${name}${source} names ${source}, which a message search does not read — ` +
|
|
30
|
-
|
|
31
|
-
? "no search reads them yet"
|
|
32
|
-
: `search ${source === "meetings" ? "all --source meetings" : "notes"} does`));
|
|
30
|
+
`search ${source === "meetings" || source === "events" ? `all --source ${source}` : "notes"} does`);
|
|
33
31
|
const named = providerNamed(source);
|
|
34
32
|
if (named && providers.includes(named.name))
|
|
35
33
|
return named.name;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { WarmEmbedders } from "../embeddings/embed.js";
|
|
2
|
+
import type { EventHit } from "../store/index.js";
|
|
3
|
+
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
4
|
+
export interface EventsQuery {
|
|
5
|
+
limit?: number;
|
|
6
|
+
offset?: number;
|
|
7
|
+
/** Calendar accounts to read; every one the store holds when absent. */
|
|
8
|
+
accounts?: readonly AccountKey[];
|
|
9
|
+
/** Every word as written: no stems. */
|
|
10
|
+
exact?: boolean;
|
|
11
|
+
/** Latest start first instead of best first. */
|
|
12
|
+
newest?: boolean;
|
|
13
|
+
/** For `date:`, which reads an event's start — the zone a bare day is read in. */
|
|
14
|
+
timezone?: string;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
/** How many matching events are read before grouping; for tests. */
|
|
17
|
+
candidates?: number;
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
/** The tool's command, named in the hint when the model is missing. */
|
|
20
|
+
command?: string;
|
|
21
|
+
/** The search's own models, when another part of it loads the same one. */
|
|
22
|
+
embedders?: Pick<WarmEmbedders, "get">;
|
|
23
|
+
}
|
|
24
|
+
export interface FoundEvent {
|
|
25
|
+
/** `event:<id>`, of the occurrence shown: of a series, the match nearest now that is not cancelled. */
|
|
26
|
+
ref: string;
|
|
27
|
+
/** The calendar account it is in; `null` for an event no calendar holds, such as a Zoom meeting's own. */
|
|
28
|
+
provider: string | null;
|
|
29
|
+
account: string | null;
|
|
30
|
+
title: string | null;
|
|
31
|
+
starts_at: string | null;
|
|
32
|
+
ends_at: string | null;
|
|
33
|
+
all_day: boolean;
|
|
34
|
+
/** confirmed, tentative or cancelled. */
|
|
35
|
+
status: string | null;
|
|
36
|
+
location: string | null;
|
|
37
|
+
/** The event's page at the calendar. */
|
|
38
|
+
url?: string;
|
|
39
|
+
/** Around the first word of the query in its description, place and attendees. */
|
|
40
|
+
excerpt: string;
|
|
41
|
+
/** How many occurrences of its series matched; 1 for an event that does not repeat. */
|
|
42
|
+
occurrences: number;
|
|
43
|
+
/** When the store's copy last changed. */
|
|
44
|
+
updated_at: string;
|
|
45
|
+
found_by: ("words" | "meaning")[];
|
|
46
|
+
}
|
|
47
|
+
export interface EventsFound {
|
|
48
|
+
query: string;
|
|
49
|
+
/** Words and word stems always; meaning too when the model is there and the query is plain words. */
|
|
50
|
+
by: "words" | "words and meaning";
|
|
51
|
+
/** Why meaning was not searched, when it was not. */
|
|
52
|
+
meaning_skipped?: string;
|
|
53
|
+
hits: FoundEvent[];
|
|
54
|
+
has_more: boolean;
|
|
55
|
+
/** More events matched than one search reads (50,000); the later ones were not read — narrow the query. */
|
|
56
|
+
truncated?: true;
|
|
57
|
+
next_offset?: number;
|
|
58
|
+
}
|
|
59
|
+
/** The calendar accounts the store holds: every account whose provider is of the `events` kind. */
|
|
60
|
+
export declare const calendarAccounts: (store: MessageStore) => Promise<AccountKey[]>;
|
|
61
|
+
export declare const foundEvent: (hit: Omit<EventHit, "relevance">, query: string, foundBy: FoundEvent["found_by"]) => FoundEvent;
|
|
62
|
+
/**
|
|
63
|
+
* Calendar events by words and, when the local text model is there and the query is plain words, by meaning, merged
|
|
64
|
+
* by reciprocal rank: title, cleaned description, place and attendees' names. The occurrences of one series are one
|
|
65
|
+
* hit. Working location, focus time, out of office, birthdays and busy-only slots are never found.
|
|
66
|
+
*/
|
|
67
|
+
export declare const searchEvents: (store: MessageStore, query: string, { limit, offset, accounts, exact, newest, timezone, signal, candidates, env, command, embedders, }?: EventsQuery) => Promise<EventsFound>;
|