granttap-mcp 0.8.47 → 0.8.48
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/apps/bridge/src/engine/README.md +2 -1
- package/apps/bridge/src/engine/runtime/engine-projects.ts +9 -4
- package/apps/bridge/src/mesh/runtime/graph/analysis.ts +69 -0
- package/apps/bridge/src/mesh/runtime/index.ts +26 -96
- package/apps/bridge/src/mesh/runtime/snapshot/compose.ts +45 -0
- package/apps/bridge/src/mesh/runtime/snapshot/enriched.ts +2 -1
- package/apps/bridge/src/monitor/README.md +13 -0
- package/apps/bridge/src/monitor/graph-refresh.ts +7 -4
- package/apps/bridge/src/monitor/handlers/inbound.ts +2 -2
- package/apps/bridge/src/monitor/mesh-publication/README.md +9 -0
- package/apps/bridge/src/monitor/mesh-publication/index.ts +45 -0
- package/apps/bridge/src/monitor/support/runtime.ts +4 -34
- package/apps/bridge/src/session-catalog/README.md +2 -1
- package/apps/bridge/src/session-catalog/index.ts +4 -2
- package/apps/bridge/src/session-images/README.md +5 -0
- package/apps/bridge/src/session-images/index.ts +3 -1
- package/apps/bridge/src/transcript-history/images/native.ts +13 -8
- package/apps/bridge/src/transcript-history/images.ts +5 -3
- package/apps/mcp/src/desktop/image/index.ts +4 -1
- package/cursor-plugin/README.md +1 -1
- package/cursor-plugin/mcp.json +1 -1
- package/cursor-plugin/stdio-bootstrap.cjs +1 -1
- package/package.json +1 -1
- package/packages/protocol/messages/interaction/index.ts +1 -0
- package/packages/protocol/messages/mesh/snapshot.ts +1 -0
|
@@ -14,7 +14,8 @@ Mesh snapshot enrichment queues Weavatrix repository analysis through
|
|
|
14
14
|
`repository-graph-jobs.ts`. One analysis runs at a time, repeated snapshot
|
|
15
15
|
ticks reuse a fresh bounded report, and failed analyses back off. An absent
|
|
16
16
|
report leaves the graph unavailable and Cortex degraded; it does not block
|
|
17
|
-
policy IPC
|
|
17
|
+
policy IPC: repository analysis owns a separate bounded-lifetime socket, while
|
|
18
|
+
policy and Backbone requests keep their control connection. It does not turn a repository label into an architecture result.
|
|
18
19
|
Claude, Codex, and Cursor hooks can evaluate the engine's local Project
|
|
19
20
|
Governance policy only when both `GRANTTAP_ENGINE_ENABLED` and
|
|
20
21
|
`GRANTTAP_PROJECT_POLICY_ENABLED` are enabled. Project `DENY` and `ASK` precede
|
|
@@ -182,7 +182,7 @@ export async function projectRepositoryGraphs(
|
|
|
182
182
|
priority?: number } = {},
|
|
183
183
|
): Promise<ProjectRepositoryGraph[]> {
|
|
184
184
|
if (!engineFeatureEnabled(options.env ?? process.env)) return [];
|
|
185
|
-
const client = options.client
|
|
185
|
+
const client = options.client;
|
|
186
186
|
const repositories = [...new Map(bindings.map((item) => [item.repositoryId, item])).values()].slice(0, 64);
|
|
187
187
|
const graphs = options.background
|
|
188
188
|
? repositories.map((binding) => repositoryGraphJobs.read(
|
|
@@ -191,7 +191,7 @@ export async function projectRepositoryGraphs(
|
|
|
191
191
|
options.priority ?? 0,
|
|
192
192
|
))
|
|
193
193
|
: await Promise.all(repositories.map((binding) =>
|
|
194
|
-
analyzeRepositoryGraph(client, projectId, binding.repositoryId,
|
|
194
|
+
analyzeRepositoryGraph(client, projectId, binding.repositoryId, 120_000).then((graph) => {
|
|
195
195
|
repositoryGraphJobs.remember(JSON.stringify([projectId, binding.repositoryId, binding.revision]), graph);
|
|
196
196
|
return graph;
|
|
197
197
|
})));
|
|
@@ -199,10 +199,13 @@ export async function projectRepositoryGraphs(
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
async function analyzeRepositoryGraph(
|
|
202
|
-
client: EngineClientLike, projectId: string, repositoryId: string, timeoutMs: number,
|
|
202
|
+
client: EngineClientLike | undefined, projectId: string, repositoryId: string, timeoutMs: number,
|
|
203
203
|
): Promise<ProjectRepositoryGraph | undefined> {
|
|
204
|
+
// The daemon serves a socket serially. Analysis must not occupy the channel
|
|
205
|
+
// used for policy, bindings and Backbone reads while a repository is scanned.
|
|
206
|
+
const channel = client ?? new EngineClient({ socketPath: join(configDir(), "engine.sock") });
|
|
204
207
|
try {
|
|
205
|
-
const result = await
|
|
208
|
+
const result = await channel.request({
|
|
206
209
|
operation: "graph.analyze_repository", input: {
|
|
207
210
|
project_id: projectId, repository_id: repositoryId,
|
|
208
211
|
},
|
|
@@ -251,6 +254,8 @@ async function analyzeRepositoryGraph(
|
|
|
251
254
|
analysisStatus: "UNAVAILABLE", analysisErrorCode: code,
|
|
252
255
|
nodes: [], relations: [], totalNodes: 0, totalRelations: 0, truncated: false,
|
|
253
256
|
};
|
|
257
|
+
} finally {
|
|
258
|
+
if (!client) channel.close();
|
|
254
259
|
}
|
|
255
260
|
}
|
|
256
261
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { RelayClient } from "../../../../../../packages/core/relay-client";
|
|
2
|
+
import type { MeshSnapshot } from "../../../../../../packages/protocol/schema";
|
|
3
|
+
import { projectBackbone, projectRepositoryGraphs, waitForProjectBindingSync } from "../../../engine/runtime/engine-projects";
|
|
4
|
+
import { sendMeshPayload } from "../../../host/session-keys";
|
|
5
|
+
import { publishedCatalog } from "../../../session-catalog";
|
|
6
|
+
import { meshSnapshot, meshSnapshots } from "..";
|
|
7
|
+
import { refreshLocalGraphBindings } from "./binding-sync";
|
|
8
|
+
|
|
9
|
+
/** A person's explicit Graph refresh bypasses background job backoff. */
|
|
10
|
+
type GraphAnalysisDependencies = {
|
|
11
|
+
snapshot?: (projectId: string) => MeshSnapshot | undefined;
|
|
12
|
+
snapshots?: typeof meshSnapshots;
|
|
13
|
+
wait: typeof waitForProjectBindingSync;
|
|
14
|
+
sync?: typeof refreshLocalGraphBindings;
|
|
15
|
+
backbone: typeof projectBackbone;
|
|
16
|
+
graphs: typeof projectRepositoryGraphs;
|
|
17
|
+
send: (client: RelayClient, snapshot: MeshSnapshot) => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const graphAnalysisDependencies: GraphAnalysisDependencies = {
|
|
21
|
+
snapshot: meshSnapshot,
|
|
22
|
+
wait: waitForProjectBindingSync,
|
|
23
|
+
sync: refreshLocalGraphBindings,
|
|
24
|
+
backbone: projectBackbone,
|
|
25
|
+
graphs: projectRepositoryGraphs,
|
|
26
|
+
send: (client, snapshot) => sendMeshPayload(client, snapshot, "phone", {
|
|
27
|
+
ttlMs: 60_000, reliable: true,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export async function analyzeProjectGraphNow(
|
|
32
|
+
client: RelayClient, projectId: string,
|
|
33
|
+
dependencies: GraphAnalysisDependencies = graphAnalysisDependencies,
|
|
34
|
+
requestIds?: ReadonlySet<string>,
|
|
35
|
+
): Promise<boolean> {
|
|
36
|
+
const known = publishedCatalog(client);
|
|
37
|
+
const read = dependencies.snapshot ?? ((id: string) => dependencies.snapshots?.().find(item => item.projectId === id));
|
|
38
|
+
const snapshot = dependencies === graphAnalysisDependencies
|
|
39
|
+
? meshSnapshot(projectId, known ? [...known.sessions, ...(known.history ?? [])] : undefined)
|
|
40
|
+
: read(projectId);
|
|
41
|
+
if (!snapshot) return false;
|
|
42
|
+
await dependencies.wait(projectId);
|
|
43
|
+
const current = dependencies === graphAnalysisDependencies
|
|
44
|
+
? meshSnapshot(projectId, known ? [...known.sessions, ...(known.history ?? [])] : undefined)
|
|
45
|
+
: read(projectId);
|
|
46
|
+
if (!current) return false;
|
|
47
|
+
const failures = dependencies.sync ? await dependencies.sync(current) : [];
|
|
48
|
+
const rejected = new Set(failures.map(report => report.repositoryId));
|
|
49
|
+
const [backbone, repositoryGraphs] = await Promise.all([
|
|
50
|
+
dependencies.backbone(projectId),
|
|
51
|
+
dependencies.graphs(projectId, (current.bindings ?? []).filter(binding => !rejected.has(binding.repositoryId)), { background: false }),
|
|
52
|
+
]);
|
|
53
|
+
const observed = [...repositoryGraphs, ...failures];
|
|
54
|
+
const reports = observed.length > 0 ? observed : [{
|
|
55
|
+
projectId, repositoryId: current.project.canonicalRepositoryId,
|
|
56
|
+
revision: "unverified", weavatrixVersion: "unknown",
|
|
57
|
+
analysisStatus: "UNAVAILABLE" as const, analysisErrorCode: "REPOSITORY_NOT_BOUND",
|
|
58
|
+
nodes: [], relations: [], totalNodes: 0, totalRelations: 0, truncated: false,
|
|
59
|
+
}];
|
|
60
|
+
const receipts = requestIds?.size ? requestIds : new Set<string | undefined>([undefined]);
|
|
61
|
+
for (const id of receipts) {
|
|
62
|
+
await dependencies.send(client, {
|
|
63
|
+
...current, backbone, repositoryGraphs: reports, generatedAt: Date.now(),
|
|
64
|
+
...(id ? { graphAnalysisRequestId: id } : {}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
@@ -10,9 +10,6 @@ import type {
|
|
|
10
10
|
SessionInfo,
|
|
11
11
|
TaskCapsule,
|
|
12
12
|
} from "../../../../../packages/protocol/schema";
|
|
13
|
-
import {
|
|
14
|
-
projectBackbone, projectRepositoryGraphs, waitForProjectBindingSync,
|
|
15
|
-
} from "../../engine/runtime/engine-projects";
|
|
16
13
|
import { configDir } from "../../config";
|
|
17
14
|
import {
|
|
18
15
|
createClaudeSession,
|
|
@@ -25,10 +22,7 @@ import { isProviderEnabled } from "../../config/runtime";
|
|
|
25
22
|
import { sendMeshPayload } from "../../host/session-keys";
|
|
26
23
|
import { linkSessionsToProjects, workingTreeState } from "../catalog";
|
|
27
24
|
import { catalogFromEndpoint } from "../catalog/models";
|
|
28
|
-
import { projectMcpServers } from "../catalog/project/capabilities";
|
|
29
|
-
import { configuredProjectMcpServers, mergeConfiguredMcpServers } from "../catalog/project/configured-mcp";
|
|
30
25
|
import { saveProjectCapabilityRequest } from "../catalog/project/requests";
|
|
31
|
-
import { projectCapabilityObservations } from "../catalog/project/observations";
|
|
32
26
|
import { computerId } from "../identity/computer";
|
|
33
27
|
import { createCheckpoint } from "../admin/checkpoint";
|
|
34
28
|
import { buildTaskCapsule } from "../admin/capsule";
|
|
@@ -36,11 +30,11 @@ import { handoffReadiness } from "../tasks/readiness";
|
|
|
36
30
|
import { createHandoffFlow } from "./handoff";
|
|
37
31
|
import type { MeshRuntimeDependencies } from "./dependencies";
|
|
38
32
|
import { localMeshStore } from "../local-remote/local";
|
|
33
|
+
import { composeMeshSnapshot } from "./snapshot/compose";
|
|
39
34
|
import { enrichMeshSnapshot } from "./snapshot/enriched";
|
|
40
35
|
import { createHandoffWorktree, repositoryHasCommit } from "./worktree";
|
|
41
36
|
import { fetchRevision, pushBranch } from "../local-remote/remote";
|
|
42
|
-
import { projectSessionCapabilityInventory
|
|
43
|
-
import { refreshLocalGraphBindings } from "./graph/binding-sync";
|
|
37
|
+
import { projectSessionCapabilityInventory } from "./capabilities";
|
|
44
38
|
import { createMeshSessionCatalog } from "./catalog/session-catalog";
|
|
45
39
|
|
|
46
40
|
export type { MeshRuntimeDependencies };
|
|
@@ -158,53 +152,29 @@ export function createMeshRuntime(deps: MeshRuntimeDependencies) {
|
|
|
158
152
|
const catalog = (sessions: SessionInfo[]) =>
|
|
159
153
|
linkSessionsToProjects(deps.store(), sessions, deps.computer());
|
|
160
154
|
|
|
161
|
-
const composeSnapshot = (
|
|
162
|
-
projectId: string,
|
|
163
|
-
store = deps.store(),
|
|
164
|
-
sessions = deps.sessions(),
|
|
165
|
-
models = catalogFromEndpoint(deps.computer(), sessions),
|
|
166
|
-
): MeshSnapshot | undefined => {
|
|
167
|
-
const snapshot = store.snapshot(projectId, deps.computer());
|
|
168
|
-
if (!snapshot) return undefined;
|
|
169
|
-
const linked = projectExecutionCapabilitySessions(
|
|
170
|
-
snapshot, sessions, deps.computer(), deps.capabilityInventory,
|
|
171
|
-
);
|
|
172
|
-
const reported = projectMcpServers(
|
|
173
|
-
linked.filter((session) => session.computerId === deps.computer()), projectId,
|
|
174
|
-
);
|
|
175
|
-
const workspaces = (snapshot.bindings ?? [])
|
|
176
|
-
.filter((binding) => binding.endpointId === deps.computer()
|
|
177
|
-
&& binding.available && binding.localPathHint)
|
|
178
|
-
.map((binding) => binding.localPathHint!);
|
|
179
|
-
const mcpServers = mergeConfiguredMcpServers(
|
|
180
|
-
configuredProjectMcpServers(deps.computer(), workspaces), reported,
|
|
181
|
-
);
|
|
182
|
-
const capabilityObservations = projectCapabilityObservations({
|
|
183
|
-
projectId, endpointId: deps.computer(), bound: workspaces.length > 0,
|
|
184
|
-
requests: snapshot.capabilityRequests ?? [], skills: snapshot.skills ?? [],
|
|
185
|
-
mcpServers, now: deps.now(),
|
|
186
|
-
});
|
|
187
|
-
return {
|
|
188
|
-
...snapshot,
|
|
189
|
-
publisherEndpointId: deps.computer(),
|
|
190
|
-
mcpServers: mcpServers.length > 0 ? mcpServers : undefined,
|
|
191
|
-
capabilityObservations: capabilityObservations.length > 0
|
|
192
|
-
? capabilityObservations : undefined,
|
|
193
|
-
modelCatalog: models.models.length > 0 || models.reason ? [models] : undefined,
|
|
194
|
-
};
|
|
195
|
-
};
|
|
196
155
|
|
|
197
156
|
return {
|
|
198
157
|
catalog,
|
|
199
|
-
snapshot(projectId: string): MeshSnapshot | undefined {
|
|
200
|
-
return
|
|
158
|
+
snapshot(projectId: string, sessions?: SessionInfo[]): MeshSnapshot | undefined {
|
|
159
|
+
return composeMeshSnapshot(deps, projectId, deps.store(), sessions ?? deps.sessions());
|
|
201
160
|
},
|
|
202
161
|
snapshots(): MeshSnapshot[] {
|
|
203
162
|
const store = deps.store();
|
|
204
163
|
const sessions = deps.sessions();
|
|
205
164
|
const models = catalogFromEndpoint(deps.computer(), sessions);
|
|
206
165
|
return store.projectIds().flatMap((projectId) =>
|
|
207
|
-
|
|
166
|
+
composeMeshSnapshot(deps, projectId, store, sessions, models) ?? []);
|
|
167
|
+
},
|
|
168
|
+
async snapshotsAsync(sessions = deps.sessions()): Promise<MeshSnapshot[]> {
|
|
169
|
+
const store = deps.store();
|
|
170
|
+
const models = catalogFromEndpoint(deps.computer(), sessions);
|
|
171
|
+
const snapshots: MeshSnapshot[] = [];
|
|
172
|
+
for (const id of store.projectIds()) {
|
|
173
|
+
await new Promise<void>(resolve => setImmediate(resolve));
|
|
174
|
+
const snapshot = composeMeshSnapshot(deps, id, store, sessions, models);
|
|
175
|
+
if (snapshot) snapshots.push(snapshot);
|
|
176
|
+
}
|
|
177
|
+
return snapshots;
|
|
208
178
|
},
|
|
209
179
|
requestCapability(input: ProjectCapabilityRequestSet): boolean {
|
|
210
180
|
const project = deps.store().snapshot(input.projectId)?.project;
|
|
@@ -251,16 +221,21 @@ export function meshSnapshots(): MeshSnapshot[] {
|
|
|
251
221
|
return defaultRuntime.snapshots();
|
|
252
222
|
}
|
|
253
223
|
|
|
254
|
-
|
|
255
|
-
|
|
224
|
+
/** Yield between Project projections so live decisions and heartbeats continue. */
|
|
225
|
+
export function meshSnapshotsAsync(sessions?: SessionInfo[]): Promise<MeshSnapshot[]> {
|
|
226
|
+
return defaultRuntime.snapshotsAsync(sessions);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function meshSnapshot(projectId: string, sessions?: SessionInfo[]): MeshSnapshot | undefined {
|
|
230
|
+
return defaultRuntime.snapshot(projectId, sessions);
|
|
256
231
|
}
|
|
257
232
|
|
|
258
233
|
export function requestProjectCapability(input: ProjectCapabilityRequestSet): boolean {
|
|
259
234
|
return defaultRuntime.requestCapability(input);
|
|
260
235
|
}
|
|
261
236
|
|
|
262
|
-
export async function meshSnapshotsWithEngine(): Promise<MeshSnapshot[]> {
|
|
263
|
-
return Promise.all(
|
|
237
|
+
export async function meshSnapshotsWithEngine(snapshots = meshSnapshots()): Promise<MeshSnapshot[]> {
|
|
238
|
+
return Promise.all(snapshots.map((snapshot) => enrichMeshSnapshot(snapshot)));
|
|
264
239
|
}
|
|
265
240
|
|
|
266
241
|
export async function meshSnapshotWithEngine(projectId: string): Promise<MeshSnapshot | undefined> {
|
|
@@ -268,52 +243,7 @@ export async function meshSnapshotWithEngine(projectId: string): Promise<MeshSna
|
|
|
268
243
|
return snapshot ? enrichMeshSnapshot(snapshot) : undefined;
|
|
269
244
|
}
|
|
270
245
|
|
|
271
|
-
|
|
272
|
-
type GraphAnalysisDependencies = {
|
|
273
|
-
snapshots: typeof meshSnapshots;
|
|
274
|
-
wait: typeof waitForProjectBindingSync;
|
|
275
|
-
sync?: typeof refreshLocalGraphBindings;
|
|
276
|
-
backbone: typeof projectBackbone;
|
|
277
|
-
graphs: typeof projectRepositoryGraphs;
|
|
278
|
-
send: (client: RelayClient, snapshot: MeshSnapshot) => Promise<void>;
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const graphAnalysisDependencies: GraphAnalysisDependencies = {
|
|
282
|
-
snapshots: meshSnapshots,
|
|
283
|
-
wait: waitForProjectBindingSync,
|
|
284
|
-
sync: refreshLocalGraphBindings,
|
|
285
|
-
backbone: projectBackbone,
|
|
286
|
-
graphs: projectRepositoryGraphs,
|
|
287
|
-
send: (client, snapshot) => sendMeshPayload(client, snapshot, "phone", {
|
|
288
|
-
ttlMs: 60_000, reliable: true,
|
|
289
|
-
}),
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
export async function analyzeProjectGraphNow(
|
|
293
|
-
client: RelayClient, projectId: string,
|
|
294
|
-
dependencies: GraphAnalysisDependencies = graphAnalysisDependencies,
|
|
295
|
-
): Promise<boolean> {
|
|
296
|
-
const snapshot = dependencies.snapshots().find((item) => item.projectId === projectId);
|
|
297
|
-
if (!snapshot) return false;
|
|
298
|
-
await dependencies.wait(projectId);
|
|
299
|
-
const current = dependencies.snapshots().find((item) => item.projectId === projectId);
|
|
300
|
-
if (!current) return false;
|
|
301
|
-
if (dependencies.sync) await dependencies.sync(current);
|
|
302
|
-
const [backbone, repositoryGraphs] = await Promise.all([
|
|
303
|
-
dependencies.backbone(projectId),
|
|
304
|
-
dependencies.graphs(projectId, current.bindings ?? [], { background: false }),
|
|
305
|
-
]);
|
|
306
|
-
const reports = repositoryGraphs.length > 0 ? repositoryGraphs : [{
|
|
307
|
-
projectId, repositoryId: current.project.canonicalRepositoryId,
|
|
308
|
-
revision: "unverified", weavatrixVersion: "unknown",
|
|
309
|
-
analysisStatus: "UNAVAILABLE" as const, analysisErrorCode: "REPOSITORY_NOT_BOUND",
|
|
310
|
-
nodes: [], relations: [], totalNodes: 0, totalRelations: 0, truncated: false,
|
|
311
|
-
}];
|
|
312
|
-
await dependencies.send(client, {
|
|
313
|
-
...current, backbone, repositoryGraphs: reports, generatedAt: Date.now(),
|
|
314
|
-
});
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
246
|
+
export { analyzeProjectGraphNow } from "./graph/analysis";
|
|
317
247
|
|
|
318
248
|
export function handleMeshPayload(
|
|
319
249
|
client: RelayClient,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MeshSnapshot } from "../../../../../../packages/protocol/schema";
|
|
2
|
+
import type { MeshRuntimeDependencies } from "../dependencies";
|
|
3
|
+
import { catalogFromEndpoint } from "../../catalog/models";
|
|
4
|
+
import { projectMcpServers } from "../../catalog/project/capabilities";
|
|
5
|
+
import { configuredProjectMcpServers, mergeConfiguredMcpServers } from "../../catalog/project/configured-mcp";
|
|
6
|
+
import { projectCapabilityObservations } from "../../catalog/project/observations";
|
|
7
|
+
import { projectExecutionCapabilitySessions } from "../capabilities";
|
|
8
|
+
|
|
9
|
+
export function composeMeshSnapshot(
|
|
10
|
+
deps: MeshRuntimeDependencies,
|
|
11
|
+
projectId: string,
|
|
12
|
+
store = deps.store(),
|
|
13
|
+
sessions = deps.sessions(),
|
|
14
|
+
models = catalogFromEndpoint(deps.computer(), sessions),
|
|
15
|
+
): MeshSnapshot | undefined {
|
|
16
|
+
const snapshot = store.snapshot(projectId, deps.computer());
|
|
17
|
+
if (!snapshot) return undefined;
|
|
18
|
+
const linked = projectExecutionCapabilitySessions(
|
|
19
|
+
snapshot, sessions, deps.computer(), deps.capabilityInventory,
|
|
20
|
+
);
|
|
21
|
+
const reported = projectMcpServers(
|
|
22
|
+
linked.filter((session) => session.computerId === deps.computer()), projectId,
|
|
23
|
+
);
|
|
24
|
+
const workspaces = (snapshot.bindings ?? [])
|
|
25
|
+
.filter((binding) => binding.endpointId === deps.computer()
|
|
26
|
+
&& binding.available && binding.localPathHint)
|
|
27
|
+
.map((binding) => binding.localPathHint!);
|
|
28
|
+
const mcpServers = mergeConfiguredMcpServers(
|
|
29
|
+
configuredProjectMcpServers(deps.computer(), workspaces), reported,
|
|
30
|
+
);
|
|
31
|
+
const capabilityObservations = projectCapabilityObservations({
|
|
32
|
+
projectId, endpointId: deps.computer(), bound: workspaces.length > 0,
|
|
33
|
+
requests: snapshot.capabilityRequests ?? [], skills: snapshot.skills ?? [],
|
|
34
|
+
mcpServers, now: deps.now(),
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
...snapshot,
|
|
38
|
+
publisherEndpointId: deps.computer(),
|
|
39
|
+
mcpServers: mcpServers.length > 0 ? mcpServers : undefined,
|
|
40
|
+
capabilityObservations: capabilityObservations.length > 0
|
|
41
|
+
? capabilityObservations : undefined,
|
|
42
|
+
modelCatalog: models.models.length > 0 || models.reason ? [models] : undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
@@ -40,7 +40,8 @@ export async function enrichMeshSnapshot(
|
|
|
40
40
|
...(snapshot.supersededKnowledgeRecordIds ?? []),
|
|
41
41
|
...supersededProjectKnowledgeIds(snapshot.projectId, allMemory),
|
|
42
42
|
])].sort().slice(-128);
|
|
43
|
-
const enriched = { ...snapshot, backbone,
|
|
43
|
+
const enriched = { ...snapshot, backbone, ...(repositoryGraphs.length || identityFailures.length
|
|
44
|
+
? { repositoryGraphs: [...repositoryGraphs, ...identityFailures] } : {}),
|
|
44
45
|
repositoryDetails: details,
|
|
45
46
|
knowledge: activeProjectKnowledge(snapshot.projectId, allMemory)
|
|
46
47
|
.sort((a, b) => b.recordedAt - a.recordedAt).slice(0, 16),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Session monitor
|
|
2
|
+
|
|
3
|
+
`index.ts` owns discovery, subscriptions and phone delivery for this computer.
|
|
4
|
+
Chat catalogs publish independently of background Mesh enrichment. The Mesh
|
|
5
|
+
publisher reuses known native identities and yields between Projects.
|
|
6
|
+
|
|
7
|
+
Architecture responses acknowledge their explicit request. Coalesced builds
|
|
8
|
+
retain every controller's receipt; periodic cached reports remain ordinary Mesh
|
|
9
|
+
snapshots. Repository analysis uses its own Engine socket to keep policy and
|
|
10
|
+
Backbone requests responsive.
|
|
11
|
+
|
|
12
|
+
Integration tests remain in `tests/monitor`; background publication tests live
|
|
13
|
+
beside `mesh-publication`. This module uses the repository MIT License.
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { RelayClient } from "../../../../packages/core/relay-client";
|
|
2
2
|
import { analyzeProjectGraphNow } from "../mesh/runtime";
|
|
3
3
|
|
|
4
|
-
const inFlight = new Set<string
|
|
4
|
+
const inFlight = new Map<string, Set<string>>();
|
|
5
5
|
|
|
6
6
|
/** One user request starts one analysis per Project, even during reconnect. */
|
|
7
7
|
export function requestProjectGraphRefresh(
|
|
8
8
|
client: RelayClient, projectId: string,
|
|
9
9
|
analyze: typeof analyzeProjectGraphNow = analyzeProjectGraphNow,
|
|
10
|
+
requestId?: string,
|
|
10
11
|
): boolean {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const pending = inFlight.get(projectId);
|
|
13
|
+
if (pending) { if (requestId) pending.add(requestId); return false; }
|
|
14
|
+
const receipts = new Set(requestId ? [requestId] : []);
|
|
15
|
+
inFlight.set(projectId, receipts);
|
|
16
|
+
void analyze(client, projectId, undefined, receipts).catch(() => false).finally(() => {
|
|
14
17
|
inFlight.delete(projectId);
|
|
15
18
|
});
|
|
16
19
|
return true;
|
|
@@ -172,10 +172,10 @@ async function handleInboundRefresh(
|
|
|
172
172
|
// An explicit refresh may pay for the full history scan; routine Mesh
|
|
173
173
|
// snapshots use the cached history and fresh live sessions.
|
|
174
174
|
invalidateMeshSessionHistory();
|
|
175
|
-
await publish(true).catch(() => {});
|
|
176
175
|
if (payload.graphProjectId && loadRuntimeConfig().meshEnabled) {
|
|
177
|
-
requestProjectGraphRefresh(client, payload.graphProjectId);
|
|
176
|
+
requestProjectGraphRefresh(client, payload.graphProjectId, undefined, payload.graphAnalysisRequestId);
|
|
178
177
|
}
|
|
178
|
+
void publish(true).catch(() => {});
|
|
179
179
|
return true;
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Mesh publication
|
|
2
|
+
|
|
3
|
+
`index.ts` owns one background Mesh publication per monitor transport. Project
|
|
4
|
+
projection yields to live decisions and uses the observed native catalog. Plain
|
|
5
|
+
snapshots, policy reports, and Engine enrichment remain separate from fresh chat
|
|
6
|
+
catalog delivery. Monitor close stops publication from the old transport.
|
|
7
|
+
|
|
8
|
+
Tests in `tests/` exercise current catalog progress while Mesh delivery is held.
|
|
9
|
+
This module uses the repository MIT License.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { RelayClient } from "../../../../../packages/core/relay-client";
|
|
2
|
+
import type { SessionsStatus } from "../../../../../packages/protocol/schema";
|
|
3
|
+
import { localMeshStore } from "../../mesh/local-remote/local";
|
|
4
|
+
import { meshSnapshotsAsync, meshSnapshotsWithEngine } from "../../mesh/runtime";
|
|
5
|
+
import { deriveObservedClaims } from "../../mesh/observed/claims";
|
|
6
|
+
import { ingestRuntimeInvocations } from "../../engine/invocation/ingest";
|
|
7
|
+
import { publishProjectPolicyStatuses } from "../../project-policy/runtime";
|
|
8
|
+
import { publishedCatalog } from "../../session-catalog";
|
|
9
|
+
import { sendMeshPayload } from "../../host/session-keys";
|
|
10
|
+
import { INTERVAL_MS } from "../handlers/catalog";
|
|
11
|
+
|
|
12
|
+
const pending = new WeakMap<RelayClient, { active: boolean }>();
|
|
13
|
+
|
|
14
|
+
/** One background projection per transport; it never holds the current chat catalog. */
|
|
15
|
+
export function publishMonitorMeshes(client: RelayClient, status: SessionsStatus): void {
|
|
16
|
+
if (pending.has(client)) return;
|
|
17
|
+
const token = { active: true };
|
|
18
|
+
pending.set(client, token);
|
|
19
|
+
const work = (async () => {
|
|
20
|
+
await new Promise<void>(resolve => setImmediate(resolve));
|
|
21
|
+
if (!token.active || !client.isConnected) return;
|
|
22
|
+
deriveObservedClaims(localMeshStore(), status.sessions);
|
|
23
|
+
void ingestRuntimeInvocations(status.sessions).catch(() => {});
|
|
24
|
+
const known = publishedCatalog(client);
|
|
25
|
+
const snapshots = await meshSnapshotsAsync([...status.sessions, ...(known?.history ?? [])]);
|
|
26
|
+
for (const snapshot of snapshots) {
|
|
27
|
+
if (!token.active || !client.isConnected) return;
|
|
28
|
+
await sendMeshPayload(client, snapshot, "phone", { ttlMs: INTERVAL_MS * 24, reliable: false }).catch(() => {});
|
|
29
|
+
}
|
|
30
|
+
if (!token.active || !client.isConnected) return;
|
|
31
|
+
await publishProjectPolicyStatuses(client, snapshots.map(mesh => mesh.projectId)).catch(() => {});
|
|
32
|
+
const enriched = await meshSnapshotsWithEngine(snapshots);
|
|
33
|
+
for (const snapshot of enriched) {
|
|
34
|
+
if (!token.active || !client.isConnected) return;
|
|
35
|
+
await sendMeshPayload(client, snapshot, "phone", { ttlMs: INTERVAL_MS * 24, reliable: false }).catch(() => {});
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
void work.catch(() => {}).finally(() => { if (pending.get(client) === token) pending.delete(client); });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function stopMonitorMeshes(client: RelayClient): void {
|
|
42
|
+
const token = pending.get(client);
|
|
43
|
+
if (token) token.active = false;
|
|
44
|
+
pending.delete(client);
|
|
45
|
+
}
|
|
@@ -12,10 +12,7 @@ import { localMeshStore } from "../../mesh/local-remote/local";
|
|
|
12
12
|
import { refreshMcpLoad } from "../../machine-load/mcp/refresh";
|
|
13
13
|
import { approvalsStatus } from "../../approvals/state";
|
|
14
14
|
import { primeSessionKeys } from "../../host/session-keys";
|
|
15
|
-
import {
|
|
16
|
-
import { meshCatalog, meshSnapshots, meshSnapshotsWithEngine } from "../../mesh/runtime";
|
|
17
|
-
import { deriveObservedClaims } from "../../mesh/observed/claims";
|
|
18
|
-
import { ingestRuntimeInvocations } from "../../engine/invocation/ingest";
|
|
15
|
+
import { meshCatalog } from "../../mesh/runtime";
|
|
19
16
|
import { HEARTBEAT_INTERVAL_MS, publishHeartbeat } from "./heartbeat";
|
|
20
17
|
import { confirmPendingController } from "../../pairing/controllers";
|
|
21
18
|
import { recordControllerName } from "../../pairing/presence";
|
|
@@ -24,14 +21,13 @@ import { singleFlightPublisher } from "./single-flight";
|
|
|
24
21
|
import { publishedCatalog, rememberPublishedCatalog, resumePublishedChats } from "../../session-catalog";
|
|
25
22
|
import { createMachineLoadPublisher } from "../../machine-load";
|
|
26
23
|
import { startMachineLoadLoop } from "../../machine-load/host/loop";
|
|
27
|
-
import { publishProjectPolicyStatuses } from "../../project-policy/runtime";
|
|
28
24
|
import { scanCapabilityUsage, scanSessionHistory, scanSessions, scopeCapabilityUsageToRoom, TOKEN_WINDOW_HOURS } from "../../sessions";
|
|
29
25
|
import { boundedCatalogHistory, INTERVAL_MS, publishSessionEvents } from "../handlers/catalog";
|
|
30
26
|
import { sweepAttachments } from "../handlers/receipts";
|
|
31
27
|
import { handleMonitorMessage } from "../handlers/inbound";
|
|
32
28
|
|
|
33
29
|
const HISTORY_INTERVAL_MS = 10_000;
|
|
34
|
-
|
|
30
|
+
import { publishMonitorMeshes, stopMonitorMeshes } from "../mesh-publication";
|
|
35
31
|
|
|
36
32
|
export type SessionMonitor = {
|
|
37
33
|
publish: () => Promise<void>;
|
|
@@ -140,22 +136,7 @@ async function publishMonitorTick(input: {
|
|
|
140
136
|
await publishSessionEvents(client, sessionId, status).catch(() => false);
|
|
141
137
|
}
|
|
142
138
|
await client.send(approvalsStatus(), "phone", { ttlMs: INTERVAL_MS * 3, reliable: false });
|
|
143
|
-
if (loadRuntimeConfig().meshEnabled)
|
|
144
|
-
deriveObservedClaims(localMeshStore(), status.sessions);
|
|
145
|
-
void ingestRuntimeInvocations(status.sessions).catch(() => {});
|
|
146
|
-
const meshes = meshSnapshots();
|
|
147
|
-
for (const mesh of meshes) {
|
|
148
|
-
await sendMeshPayload(client, mesh, "phone", {
|
|
149
|
-
ttlMs: INTERVAL_MS * 24,
|
|
150
|
-
reliable: false,
|
|
151
|
-
}).catch(() => {});
|
|
152
|
-
}
|
|
153
|
-
await publishProjectPolicyStatuses(
|
|
154
|
-
client,
|
|
155
|
-
meshes.map((mesh) => mesh.projectId),
|
|
156
|
-
).catch(() => {});
|
|
157
|
-
publishEnrichedMeshes(client);
|
|
158
|
-
}
|
|
139
|
+
if (loadRuntimeConfig().meshEnabled) publishMonitorMeshes(client, status);
|
|
159
140
|
if (Date.now() - lastCapabilityPublishedAt >= 30_000) {
|
|
160
141
|
const usage = scanCapabilityUsage([
|
|
161
142
|
...status.sessions,
|
|
@@ -167,18 +148,6 @@ async function publishMonitorTick(input: {
|
|
|
167
148
|
return { lastHistoryPublishedAt, lastCapabilityPublishedAt };
|
|
168
149
|
}
|
|
169
150
|
|
|
170
|
-
function publishEnrichedMeshes(client: RelayClient): void {
|
|
171
|
-
if (meshEnrichmentInFlight) return;
|
|
172
|
-
meshEnrichmentInFlight = (async () => {
|
|
173
|
-
const meshes = await meshSnapshotsWithEngine();
|
|
174
|
-
for (const mesh of meshes) {
|
|
175
|
-
await sendMeshPayload(client, mesh, "phone", {
|
|
176
|
-
ttlMs: INTERVAL_MS * 24, reliable: false,
|
|
177
|
-
}).catch(() => {});
|
|
178
|
-
}
|
|
179
|
-
})().finally(() => { meshEnrichmentInFlight = undefined; });
|
|
180
|
-
}
|
|
181
|
-
|
|
182
151
|
/**
|
|
183
152
|
* Attach task discovery and phone-to-agent delivery to the same relay client
|
|
184
153
|
* used by MCP tools. This keeps STDIO stdout clean and removes the old need for
|
|
@@ -254,6 +223,7 @@ export function startSessionMonitor(client: RelayClient): SessionMonitor {
|
|
|
254
223
|
publish: () => publish(false),
|
|
255
224
|
close: () => {
|
|
256
225
|
stopCatalog();
|
|
226
|
+
stopMonitorMeshes(client);
|
|
257
227
|
stopHeartbeat();
|
|
258
228
|
loadLoop.stop();
|
|
259
229
|
off();
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
`index.ts` keeps the native identities of the latest published catalog. Requests
|
|
4
4
|
for a chat already shown on the phone use that identity instead of walking every
|
|
5
|
-
provider directory again
|
|
5
|
+
provider directory again, including image chunk requests. The in-memory catalog
|
|
6
|
+
retains current names; the private restart cache contains only native identity. A new discovery window cannot make a known chat
|
|
6
7
|
unreadable merely because hundreds of newer logs appeared.
|
|
7
8
|
|
|
8
9
|
The private `session-catalog-cache.json` contains metadata and validated native
|
|
@@ -20,10 +20,12 @@ function metadata(session: SessionInfo): SessionInfo {
|
|
|
20
20
|
|
|
21
21
|
/** Keep native identity, not capability descriptors or message contents. */
|
|
22
22
|
export function rememberPublishedCatalog(client: RelayClient, status: SessionsStatus): void {
|
|
23
|
+
const previous = catalogs.get(client);
|
|
24
|
+
const history = status.history ?? previous?.history;
|
|
23
25
|
const compact: SessionsStatus = { type: "sessions.status", machine: status.machine,
|
|
24
|
-
sessions: status.sessions.map(metadata), history:
|
|
26
|
+
sessions: status.sessions.map(metadata), history: history?.map(metadata),
|
|
25
27
|
generatedAt: status.generatedAt };
|
|
26
|
-
catalogs.set(client,
|
|
28
|
+
catalogs.set(client, { ...status, history });
|
|
27
29
|
const paths = Object.fromEntries([...compact.sessions, ...(compact.history ?? [])]
|
|
28
30
|
.flatMap(session => { const path = nativeTranscriptSource(session);
|
|
29
31
|
return path ? [[session.sessionId, path]] : []; }));
|
|
@@ -12,3 +12,8 @@ account state. Apple clients retain validated original bytes locally and use
|
|
|
12
12
|
their scoped copy when offline. Tests live in `tests/`.
|
|
13
13
|
|
|
14
14
|
Licensed under the MIT License. See the repository root `LICENSE`.
|
|
15
|
+
|
|
16
|
+
Native image identities remain readable after the chat head advances. Codex
|
|
17
|
+
attachments are located by timestamp and content block together, so a caption
|
|
18
|
+
with the same timestamp cannot hide the picture. Original bytes are delivered in
|
|
19
|
+
bounded encrypted chunks and the Apple client retains them for offline use.
|
|
@@ -3,6 +3,7 @@ import { SessionImageChunk } from '../../../../packages/protocol/schema';
|
|
|
3
3
|
import type { RelayClient } from '../../../../packages/core/relay-client';
|
|
4
4
|
import { sendSessionPayload } from '../host/session-keys';
|
|
5
5
|
import { readTranscriptHistory } from '../transcript-history';
|
|
6
|
+
import { publishedSession } from '../session-catalog';
|
|
6
7
|
import { cachedSessionActivity } from '../monitor/support/session-activity';
|
|
7
8
|
import { scanSessions, scanSessionHistory } from '../sessions';
|
|
8
9
|
import { sessionImageChunk, transcriptImages } from '../../../mcp/src/desktop/image';
|
|
@@ -22,7 +23,8 @@ export function readSessionImage(query: SessionImageQuery, session: SessionInfo,
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export async function publishSessionImage(client: RelayClient, query: SessionImageQuery): Promise<boolean> {
|
|
25
|
-
const session =
|
|
26
|
+
const session = publishedSession(client, query.sessionId)
|
|
27
|
+
?? scanSessions().sessions.find(s => s.sessionId === query.sessionId)
|
|
26
28
|
?? scanSessionHistory().find(s => s.sessionId === query.sessionId);
|
|
27
29
|
const activity = session ? readTranscriptHistory(session, query.historyCursor)
|
|
28
30
|
?? cachedSessionActivity(session) : undefined;
|
|
@@ -21,14 +21,16 @@ export function nativeTranscriptImage(session: SessionInfo, imageId: string, off
|
|
|
21
21
|
}
|
|
22
22
|
if (!path || !['claude', 'codex'].includes(session.agent)) return undefined;
|
|
23
23
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
let pageCursor = cursor;
|
|
25
|
+
for (let page = 0; page < 32; page++) {
|
|
26
|
+
const window = previousNativeLines(path, session.sessionId, pageCursor);
|
|
27
|
+
if (!window) return undefined;
|
|
28
|
+
const lines = window.lines.map(line => line.text);
|
|
29
|
+
if (session.agent === 'codex') {
|
|
30
|
+
const full = codexImageChunk(session.sessionId, imageId, 0, lines)
|
|
31
|
+
?? codexImageChunk(session.sessionId, imageId, 0);
|
|
32
|
+
return full ? codexImageChunk(session.sessionId, imageId, offset) : undefined;
|
|
33
|
+
}
|
|
32
34
|
for (const line of lines) {
|
|
33
35
|
let row;
|
|
34
36
|
try { row = JSON.parse(line); } catch { continue; }
|
|
@@ -46,6 +48,9 @@ export function nativeTranscriptImage(session: SessionInfo, imageId: string, off
|
|
|
46
48
|
return slice(cached, offset);
|
|
47
49
|
}
|
|
48
50
|
}
|
|
51
|
+
if (window.start === 0) break;
|
|
52
|
+
pageCursor = window.cursorAt(window.lines[0]?.offset ?? window.start);
|
|
53
|
+
}
|
|
49
54
|
} catch { return undefined; }
|
|
50
55
|
return undefined;
|
|
51
56
|
}
|
|
@@ -5,9 +5,10 @@ import { previousNativeLines } from "./native-lines";
|
|
|
5
5
|
/** Locate an older image row without copying image bytes into chat pages. */
|
|
6
6
|
export function nativeImageLines(sessionId: string, entryId: string): string[] | undefined {
|
|
7
7
|
if (!entryId.startsWith(`${sessionId}:`)) return undefined;
|
|
8
|
-
const
|
|
8
|
+
const [stampText, blockText] = entryId.slice(sessionId.length + 1).split(":");
|
|
9
|
+
const stamp = Number(stampText), blockIndex = Number(blockText);
|
|
9
10
|
const path = codexLogPathBySession.get(sessionId);
|
|
10
|
-
if (!path || !Number.isFinite(stamp)) return undefined;
|
|
11
|
+
if (!path || !Number.isFinite(stamp) || !Number.isSafeInteger(blockIndex) || blockIndex < 0) return undefined;
|
|
11
12
|
let cursor: string | undefined;
|
|
12
13
|
try {
|
|
13
14
|
for (let page = 0; page < 32; page++) {
|
|
@@ -19,7 +20,8 @@ export function nativeImageLines(sessionId: string, entryId: string): string[] |
|
|
|
19
20
|
const createdAt = ts(row?.timestamp);
|
|
20
21
|
if (createdAt) earliest = Math.min(earliest, createdAt);
|
|
21
22
|
if (createdAt === stamp && row?.type === "response_item" && p?.type === "message"
|
|
22
|
-
&& p.role === "user" && Array.isArray(p.content)
|
|
23
|
+
&& p.role === "user" && Array.isArray(p.content)
|
|
24
|
+
&& p.content[blockIndex]?.type === "input_image") return [line.text];
|
|
23
25
|
}
|
|
24
26
|
if (window.start === 0 || earliest < stamp) return undefined;
|
|
25
27
|
cursor = window.cursorAt(window.lines[0]?.offset ?? window.start);
|
|
@@ -15,7 +15,10 @@ export function sessionImageChunk(session: SessionInfo, activity: SessionActivit
|
|
|
15
15
|
if (activity.sessionId !== session.sessionId) return undefined;
|
|
16
16
|
const native = activity.entries.some(entry => entry.id === imageId && entry.kind === 'user'
|
|
17
17
|
&& entry.attachments?.includes('Image'));
|
|
18
|
-
if (native
|
|
18
|
+
if (native || imageId.startsWith(`${session.sessionId}:`)) {
|
|
19
|
+
const picture = nativeTranscriptImage(session, imageId, offset, cursor);
|
|
20
|
+
if (picture) return picture;
|
|
21
|
+
}
|
|
19
22
|
const artifact = activity.entries.flatMap(artifactImages).find(image => image.id === imageId);
|
|
20
23
|
if (!artifact) return undefined;
|
|
21
24
|
const loaded = readStoreState(storePath ?? join(configDir(), 'project-mesh.json'));
|
package/cursor-plugin/README.md
CHANGED
|
@@ -10,7 +10,7 @@ keeps each phone response tied to the exact originating chat and prompt.
|
|
|
10
10
|
Install the supported CLI on this computer, then run:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install -g granttap-mcp@0.8.
|
|
13
|
+
npm install -g granttap-mcp@0.8.48
|
|
14
14
|
granttap setup
|
|
15
15
|
granttap status
|
|
16
16
|
```
|
package/cursor-plugin/mcp.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"command": "node",
|
|
5
5
|
"args": [
|
|
6
6
|
"-e",
|
|
7
|
-
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.
|
|
7
|
+
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.48\";\nconst dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === \"1\";\nconst platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM\n ? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;\nconst nodeRoot = dryRun && process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT\n ? process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT : dirname(process.execPath);\nconst pathRoots = (process.env.PATH || \"\").split(delimiter).filter(Boolean);\nconst appData = process.env.APPDATA;\nconst roots = [nodeRoot, ...pathRoots, ...(appData ? [join(appData, \"npm\")] : [])];\nconst first = (paths) => paths.find((path) => existsSync(path));\n\nfunction launch() {\n if (platform !== \"win32\") {\n const installed = first(roots.map((root) => join(root, \"granttap-mcp\")));\n if (installed) return { command: installed, args: [] };\n const npx = first(roots.map((root) => join(root, \"npx\"))) || \"npx\";\n return { command: npx, args: [\"-y\", PACKAGE] };\n }\n const installed = first(roots.map((root) =>\n join(root, \"node_modules\", \"granttap-mcp\", \"bin\", \"granttap-mcp.mjs\")));\n if (installed) return { command: process.execPath, args: [installed] };\n const npx = first(roots.map((root) =>\n join(root, \"node_modules\", \"npm\", \"bin\", \"npx-cli.js\")));\n if (npx) return { command: process.execPath, args: [npx, \"-y\", PACKAGE] };\n throw new Error(\"Node.js/npm 20+ is required on this Windows device. Install npm and retry GrantTap.\");\n}\n\nlet target;\ntry { target = launch(); }\ncatch (error) {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n}\n\nif (dryRun) {\n process.stdout.write(JSON.stringify(target));\n process.exit(0);\n}\n\nconst child = spawn(target.command, target.args, { stdio: \"inherit\", windowsHide: true });\nchild.on(\"error\", (error) => {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n});\nchild.on(\"exit\", (code, signal) => {\n if (signal && platform !== \"win32\") {\n try { process.kill(process.pid, signal); } catch { process.exit(code ?? 1); }\n return;\n }\n process.exit(code ?? 1);\n});\n"
|
|
8
8
|
]
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -7,7 +7,7 @@ const { spawn } = require("node:child_process");
|
|
|
7
7
|
const { existsSync } = require("node:fs");
|
|
8
8
|
const { delimiter, dirname, join } = require("node:path");
|
|
9
9
|
|
|
10
|
-
const PACKAGE = "granttap-mcp@0.8.
|
|
10
|
+
const PACKAGE = "granttap-mcp@0.8.48";
|
|
11
11
|
const dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === "1";
|
|
12
12
|
const platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM
|
|
13
13
|
? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;
|
package/package.json
CHANGED
|
@@ -144,5 +144,6 @@ export const SessionsRefresh = z.object({
|
|
|
144
144
|
type: z.literal("sessions.refresh"),
|
|
145
145
|
createdAt: z.number(),
|
|
146
146
|
graphProjectId: z.string().min(1).max(128).optional(),
|
|
147
|
+
graphAnalysisRequestId: z.string().min(1).max(128).optional(),
|
|
147
148
|
});
|
|
148
149
|
export type SessionsRefresh = z.infer<typeof SessionsRefresh>;
|
|
@@ -250,6 +250,7 @@ export const MeshSnapshot = z.object({
|
|
|
250
250
|
dependencies: z.array(TaskDependency).max(128),
|
|
251
251
|
events: z.array(MeshEvent).max(128),
|
|
252
252
|
generatedAt: z.number().nonnegative(),
|
|
253
|
+
graphAnalysisRequestId: z.string().min(1).max(128).optional(),
|
|
253
254
|
}).strict().superRefine((value, ctx) => {
|
|
254
255
|
const detailKeys = new Set(value.repositoryDetails?.map((item) => `${item.endpointId}\0${item.repositoryId}`));
|
|
255
256
|
if (detailKeys.size !== (value.repositoryDetails?.length ?? 0)
|