minutes-mcp 0.21.0 → 0.22.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/dist/captureRelay.d.ts +35 -0
- package/dist/captureRelay.js +181 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +939 -43
- package/dist-ui/index.html +140 -114
- package/package.json +5 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface CaptureRelayCursor {
|
|
2
|
+
session_id?: string;
|
|
3
|
+
transcript_seq: number;
|
|
4
|
+
nudge_seq: number;
|
|
5
|
+
}
|
|
6
|
+
export interface CaptureRelayDiscovery {
|
|
7
|
+
v: number;
|
|
8
|
+
session_id: string;
|
|
9
|
+
transport: "unix_socket" | "windows_named_pipe";
|
|
10
|
+
endpoint: string;
|
|
11
|
+
owner_pid: number;
|
|
12
|
+
evidence_mode: string;
|
|
13
|
+
auth_token: string;
|
|
14
|
+
started_at: string;
|
|
15
|
+
heartbeat_at: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CaptureRelayFrame {
|
|
18
|
+
type: string;
|
|
19
|
+
seq?: number;
|
|
20
|
+
session_id?: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface CaptureRelaySnapshot {
|
|
25
|
+
discovery: Omit<CaptureRelayDiscovery, "auth_token">;
|
|
26
|
+
cursor: CaptureRelayCursor;
|
|
27
|
+
frames: CaptureRelayFrame[];
|
|
28
|
+
}
|
|
29
|
+
export interface CaptureRelayAttachOptions {
|
|
30
|
+
discoveryPath?: string;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
replayQuietMs?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function captureRelayDiscoveryPath(): string;
|
|
35
|
+
export declare function attachCaptureRelay(cursor?: CaptureRelayCursor, options?: CaptureRelayAttachOptions): Promise<CaptureRelaySnapshot>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { createConnection } from "node:net";
|
|
5
|
+
const RELAY_PROTOCOL_VERSION = 1;
|
|
6
|
+
const HEARTBEAT_STALE_MS = 5_000;
|
|
7
|
+
export function captureRelayDiscoveryPath() {
|
|
8
|
+
return join(homedir(), ".minutes", "capture-relay.json");
|
|
9
|
+
}
|
|
10
|
+
export async function attachCaptureRelay(cursor = { transcript_seq: 0, nudge_seq: 0 }, options = {}) {
|
|
11
|
+
const discoveryPath = options.discoveryPath ?? captureRelayDiscoveryPath();
|
|
12
|
+
const discovery = await readAndValidateDiscovery(discoveryPath);
|
|
13
|
+
const timeoutMs = options.timeoutMs ?? 1_000;
|
|
14
|
+
const replayQuietMs = options.replayQuietMs ?? 40;
|
|
15
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
16
|
+
let socket;
|
|
17
|
+
let settled = false;
|
|
18
|
+
let acknowledged = false;
|
|
19
|
+
let buffer = "";
|
|
20
|
+
let quietTimer;
|
|
21
|
+
const frames = [];
|
|
22
|
+
const nextCursor = {
|
|
23
|
+
session_id: cursor.session_id,
|
|
24
|
+
transcript_seq: cursor.transcript_seq,
|
|
25
|
+
nudge_seq: cursor.nudge_seq,
|
|
26
|
+
};
|
|
27
|
+
const finish = (error) => {
|
|
28
|
+
if (settled)
|
|
29
|
+
return;
|
|
30
|
+
settled = true;
|
|
31
|
+
clearTimeout(timeoutTimer);
|
|
32
|
+
if (quietTimer)
|
|
33
|
+
clearTimeout(quietTimer);
|
|
34
|
+
socket?.destroy();
|
|
35
|
+
if (error) {
|
|
36
|
+
rejectPromise(error);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
resolvePromise({
|
|
40
|
+
discovery: redactDiscovery(discovery),
|
|
41
|
+
cursor: nextCursor,
|
|
42
|
+
frames,
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const scheduleReplayComplete = () => {
|
|
46
|
+
if (!acknowledged)
|
|
47
|
+
return;
|
|
48
|
+
if (quietTimer)
|
|
49
|
+
clearTimeout(quietTimer);
|
|
50
|
+
quietTimer = setTimeout(() => finish(), replayQuietMs);
|
|
51
|
+
};
|
|
52
|
+
const timeoutTimer = setTimeout(() => finish(new Error("the capture attachment relay did not respond in time")), timeoutMs);
|
|
53
|
+
socket = createConnection(discovery.endpoint);
|
|
54
|
+
socket.setEncoding("utf8");
|
|
55
|
+
socket.once("connect", () => {
|
|
56
|
+
socket?.write(`${JSON.stringify({
|
|
57
|
+
v: RELAY_PROTOCOL_VERSION,
|
|
58
|
+
auth_token: discovery.auth_token,
|
|
59
|
+
action: { type: "observe", cursor },
|
|
60
|
+
})}\n`);
|
|
61
|
+
});
|
|
62
|
+
socket.on("data", (chunk) => {
|
|
63
|
+
buffer += chunk;
|
|
64
|
+
while (true) {
|
|
65
|
+
const newline = buffer.indexOf("\n");
|
|
66
|
+
if (newline < 0)
|
|
67
|
+
break;
|
|
68
|
+
const line = buffer.slice(0, newline).trim();
|
|
69
|
+
buffer = buffer.slice(newline + 1);
|
|
70
|
+
if (!line)
|
|
71
|
+
continue;
|
|
72
|
+
let frame;
|
|
73
|
+
try {
|
|
74
|
+
frame = JSON.parse(line);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
finish(new Error("the capture attachment relay sent invalid data"));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (frame.type === "error") {
|
|
81
|
+
finish(new Error(String(frame.message ?? "capture attachment failed")));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!acknowledged) {
|
|
85
|
+
if (frame.type !== "attached" && frame.type !== "cursor_reset") {
|
|
86
|
+
finish(new Error("the capture attachment relay did not acknowledge this client"));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
acknowledged = true;
|
|
90
|
+
nextCursor.session_id = discovery.session_id;
|
|
91
|
+
}
|
|
92
|
+
if (frame.type === "cursor_reset") {
|
|
93
|
+
nextCursor.session_id = String(frame.session_id ?? discovery.session_id);
|
|
94
|
+
nextCursor.transcript_seq = 0;
|
|
95
|
+
nextCursor.nudge_seq = 0;
|
|
96
|
+
}
|
|
97
|
+
else if (frame.type === "transcript" && typeof frame.seq === "number") {
|
|
98
|
+
nextCursor.transcript_seq = Math.max(nextCursor.transcript_seq, frame.seq);
|
|
99
|
+
}
|
|
100
|
+
else if (frame.type === "nudge" && typeof frame.seq === "number") {
|
|
101
|
+
nextCursor.nudge_seq = Math.max(nextCursor.nudge_seq, frame.seq);
|
|
102
|
+
}
|
|
103
|
+
frames.push(frame);
|
|
104
|
+
}
|
|
105
|
+
scheduleReplayComplete();
|
|
106
|
+
});
|
|
107
|
+
socket.once("error", (error) => finish(error));
|
|
108
|
+
socket.once("close", () => {
|
|
109
|
+
if (!settled && !acknowledged) {
|
|
110
|
+
finish(new Error("the capture owner closed the attachment relay"));
|
|
111
|
+
}
|
|
112
|
+
else if (!settled) {
|
|
113
|
+
finish();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async function readAndValidateDiscovery(path) {
|
|
119
|
+
let raw;
|
|
120
|
+
try {
|
|
121
|
+
raw = await readFile(path, "utf8");
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
throw new Error("no active capture attachment relay was found");
|
|
125
|
+
}
|
|
126
|
+
let discovery;
|
|
127
|
+
try {
|
|
128
|
+
discovery = JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new Error("the capture attachment discovery file is invalid");
|
|
132
|
+
}
|
|
133
|
+
if (discovery.v !== RELAY_PROTOCOL_VERSION) {
|
|
134
|
+
throw new Error(`capture attachment protocol ${discovery.v} is not supported`);
|
|
135
|
+
}
|
|
136
|
+
if (!Number.isSafeInteger(discovery.owner_pid) || discovery.owner_pid <= 0) {
|
|
137
|
+
throw new Error("the capture attachment owner PID is invalid");
|
|
138
|
+
}
|
|
139
|
+
const heartbeat = Date.parse(discovery.heartbeat_at);
|
|
140
|
+
if (!Number.isFinite(heartbeat) || Date.now() - heartbeat > HEARTBEAT_STALE_MS) {
|
|
141
|
+
throw new Error("the capture attachment heartbeat is stale");
|
|
142
|
+
}
|
|
143
|
+
if (!discovery.auth_token || discovery.auth_token.length < 32) {
|
|
144
|
+
throw new Error("the capture attachment token is invalid");
|
|
145
|
+
}
|
|
146
|
+
validateLocalEndpoint(path, discovery);
|
|
147
|
+
if (process.platform !== "win32") {
|
|
148
|
+
const mode = (await stat(path)).mode & 0o777;
|
|
149
|
+
if ((mode & 0o077) !== 0) {
|
|
150
|
+
throw new Error("the capture attachment discovery file is not private to this user");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
process.kill(discovery.owner_pid, 0);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error?.code === "ESRCH") {
|
|
158
|
+
throw new Error(`capture owner process ${discovery.owner_pid} is no longer running`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return discovery;
|
|
162
|
+
}
|
|
163
|
+
function validateLocalEndpoint(discoveryPath, discovery) {
|
|
164
|
+
if (discovery.transport === "windows_named_pipe") {
|
|
165
|
+
if (!discovery.endpoint.toLowerCase().startsWith("\\\\.\\pipe\\minutes-capture-")) {
|
|
166
|
+
throw new Error("the capture attachment named pipe is not a Minutes local endpoint");
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (discovery.transport !== "unix_socket") {
|
|
171
|
+
throw new Error("the capture attachment transport is invalid");
|
|
172
|
+
}
|
|
173
|
+
const expected = resolve(dirname(discoveryPath), "capture-relay.sock");
|
|
174
|
+
if (resolve(discovery.endpoint) !== expected) {
|
|
175
|
+
throw new Error("the capture attachment socket is outside the private Minutes directory");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function redactDiscovery(discovery) {
|
|
179
|
+
const { auth_token: _authToken, ...safe } = discovery;
|
|
180
|
+
return safe;
|
|
181
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* - activity_summary: Summarize meeting-adjacent desktop context for a session/path/window
|
|
15
15
|
* - search_context: Search app and captured window-title desktop context
|
|
16
16
|
* - get_moment: Show the local rewind around a linked artifact, session, or timestamp
|
|
17
|
+
* - get_screen_context: Retrieve bounded, session-linked screen images
|
|
17
18
|
* - consistency_report: Flag conflicting decisions and stale commitments
|
|
18
19
|
* - get_person_profile: Rich relationship profile for a person (graph index)
|
|
19
20
|
* - track_commitments: List open/stale commitments, filter by person
|
|
@@ -24,6 +25,8 @@
|
|
|
24
25
|
* - list_voices: List enrolled voice profiles for speaker identification
|
|
25
26
|
* - confirm_speaker: Confirm/correct speaker attribution in a meeting
|
|
26
27
|
* - get_meeting_insights: Query structured insights (decisions, commitments, etc.) with confidence filtering
|
|
28
|
+
* - start_copilot / stop_copilot: Control the independent real-time copilot engine
|
|
29
|
+
* - copilot_status / read_copilot_nudges: Observe copilot health and cursor-based nudges
|
|
27
30
|
*
|
|
28
31
|
* All tools use execFile (not exec) to shell out to the `minutes` CLI binary.
|
|
29
32
|
* No shell interpolation — safe from injection.
|
|
@@ -94,6 +97,61 @@ export declare function shouldRunMainEntry(argv1: string | null | undefined, mod
|
|
|
94
97
|
export declare function parseKnowledgeConfig(configContent: string): KnowledgeConfigStatus | null;
|
|
95
98
|
export declare const LIVE_EVENTS_RESOURCE_URI = "minutes://events/live";
|
|
96
99
|
export declare const LIVE_EVENTS_URI_TEMPLATE = "minutes://events/live{?since_seq,limit}";
|
|
100
|
+
export declare const LIVE_COPILOT_RESOURCE_URI = "minutes://live/copilot";
|
|
101
|
+
type JsonObject = Record<string, unknown>;
|
|
102
|
+
export type CopilotStatusPayload = {
|
|
103
|
+
available: boolean;
|
|
104
|
+
active: boolean;
|
|
105
|
+
state: string;
|
|
106
|
+
pid: number | null;
|
|
107
|
+
goal: string | null;
|
|
108
|
+
surface: string | null;
|
|
109
|
+
provider: string | null;
|
|
110
|
+
model: string | null;
|
|
111
|
+
evidence_cursor: number;
|
|
112
|
+
capture_attachment: string | null;
|
|
113
|
+
last_error: string | null;
|
|
114
|
+
raw: string;
|
|
115
|
+
error?: string;
|
|
116
|
+
};
|
|
117
|
+
export type CopilotObserverSession = {
|
|
118
|
+
v: 1;
|
|
119
|
+
pid: number;
|
|
120
|
+
goal: string;
|
|
121
|
+
surface: "stdout" | "tui";
|
|
122
|
+
started_ts: string;
|
|
123
|
+
};
|
|
124
|
+
export type ObservedCopilotNudge = {
|
|
125
|
+
cursor: number;
|
|
126
|
+
format: "json" | "text" | "raw";
|
|
127
|
+
nudge: JsonObject;
|
|
128
|
+
raw: string;
|
|
129
|
+
observed_ts: string | null;
|
|
130
|
+
expires_at: string | null;
|
|
131
|
+
expired: boolean | null;
|
|
132
|
+
};
|
|
133
|
+
export type CopilotNudgeObservation = {
|
|
134
|
+
attached: boolean;
|
|
135
|
+
cursor: number;
|
|
136
|
+
session: CopilotObserverSession | null;
|
|
137
|
+
nudges: ObservedCopilotNudge[];
|
|
138
|
+
note: string;
|
|
139
|
+
};
|
|
140
|
+
export type LiveCopilotResourcePayload = {
|
|
141
|
+
v: 1;
|
|
142
|
+
resource: typeof LIVE_COPILOT_RESOURCE_URI;
|
|
143
|
+
available: boolean;
|
|
144
|
+
active: boolean;
|
|
145
|
+
state: string;
|
|
146
|
+
status: CopilotStatusPayload;
|
|
147
|
+
nudge_stream: {
|
|
148
|
+
attached: boolean;
|
|
149
|
+
cursor: number;
|
|
150
|
+
note: string;
|
|
151
|
+
};
|
|
152
|
+
latest_nudge: ObservedCopilotNudge | null;
|
|
153
|
+
current_nudge: ObservedCopilotNudge | null;
|
|
154
|
+
};
|
|
97
155
|
export type LiveEventsResourceOptions = {
|
|
98
156
|
uri: string;
|
|
99
157
|
sinceSeq: number | null;
|
|
@@ -114,10 +172,30 @@ export type LiveEventsResourcePayload = {
|
|
|
114
172
|
};
|
|
115
173
|
export declare function parseLiveEventsResourceUri(rawUri: string): LiveEventsResourceOptions | null;
|
|
116
174
|
export declare function buildLiveEventsResourcePayload(options: LiveEventsResourceOptions, events: unknown[], latestSeq: number): LiveEventsResourcePayload;
|
|
175
|
+
/** Parse the stable human-readable `minutes copilot status` surface. */
|
|
176
|
+
export declare function parseCopilotStatusOutput(raw: string): CopilotStatusPayload;
|
|
177
|
+
/** Parse the CLI's stdout presentation stream without invoking inference. */
|
|
178
|
+
export declare function parseCopilotNudgeLog(raw: string, nowMs?: number, latestObservedMs?: number): ObservedCopilotNudge[];
|
|
179
|
+
export declare function buildLiveCopilotResourcePayload(status: CopilotStatusPayload, observation: CopilotNudgeObservation): LiveCopilotResourcePayload;
|
|
180
|
+
export type CopilotNudgeReadPage = {
|
|
181
|
+
cursor: number;
|
|
182
|
+
next_cursor: number;
|
|
183
|
+
cursor_reset: boolean;
|
|
184
|
+
has_more: boolean;
|
|
185
|
+
nudges: ObservedCopilotNudge[];
|
|
186
|
+
};
|
|
187
|
+
export declare function selectCopilotNudges(observation: CopilotNudgeObservation, options: {
|
|
188
|
+
cursor?: number;
|
|
189
|
+
since?: string;
|
|
190
|
+
limit?: number;
|
|
191
|
+
}, nowMs?: number): CopilotNudgeReadPage;
|
|
117
192
|
export type LiveEventsSubscriptionOptions = {
|
|
118
193
|
pollIntervalMs?: number;
|
|
194
|
+
enableLiveEvents?: boolean;
|
|
195
|
+
enableCopilot?: boolean;
|
|
119
196
|
latestEventSeq?: () => Promise<number>;
|
|
120
197
|
readEventsSinceSeq?: (sinceSeq: number, limit: number) => Promise<unknown[]>;
|
|
198
|
+
copilotFingerprint?: () => Promise<string>;
|
|
121
199
|
sendResourceUpdated?: (uri: string) => Promise<void>;
|
|
122
200
|
onError?: (error: unknown) => void;
|
|
123
201
|
};
|