granttap-mcp 0.8.90 → 0.8.92
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/host/session-keys.ts +2 -1
- package/apps/bridge/src/mesh/knowledge/README.md +8 -0
- package/apps/bridge/src/mesh/knowledge/read.ts +65 -0
- package/apps/bridge/src/mesh/knowledge/runtime-read.ts +49 -0
- package/apps/bridge/src/monitor/handlers/inbound.ts +3 -0
- package/apps/bridge/src/policy/governed-projects.ts +1 -1
- package/apps/bridge/src/provider-account-status/README.md +21 -4
- package/apps/bridge/src/provider-account-status/index.ts +1 -0
- package/apps/bridge/src/provider-account-status/native-usage/README.md +12 -0
- package/apps/bridge/src/provider-account-status/native-usage/credentials.ts +69 -0
- package/apps/bridge/src/provider-account-status/native-usage/index.ts +30 -0
- package/apps/bridge/src/provider-account-status/native-usage/projection.ts +52 -0
- package/apps/bridge/src/provider-account-status/native-usage/reader.ts +85 -0
- package/apps/bridge/src/provider-model-catalog/index.ts +2 -3
- package/apps/bridge/src/task-routing/admission/index.ts +2 -1
- package/apps/mcp/src/desktop/engine-bridge.ts +1 -0
- package/apps/mcp/src/desktop/operations/index.ts +2 -0
- 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 +2 -2
- package/packages/protocol/messages/approval-questions/index.ts +1 -1
- package/packages/protocol/messages/mesh/knowledge/refresh.ts +44 -0
- package/packages/protocol/messages/sessions/index.ts +2 -2
- package/packages/protocol/schema.ts +4 -0
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
MeshEvent,
|
|
9
9
|
MeshSnapshot,
|
|
10
10
|
MeshInvocationPage,
|
|
11
|
+
ProjectKnowledgePage,
|
|
11
12
|
Payload,
|
|
12
13
|
ProjectPolicyPayload,
|
|
13
14
|
Role,
|
|
@@ -53,7 +54,7 @@ export async function sendMeshPayload(
|
|
|
53
54
|
|
|
54
55
|
export async function sendProjectPayload(
|
|
55
56
|
client: RelayClient,
|
|
56
|
-
payload: ProjectPolicyPayload | MeshClaimReleaseResult | MeshInvocationPage,
|
|
57
|
+
payload: ProjectPolicyPayload | MeshClaimReleaseResult | MeshInvocationPage | ProjectKnowledgePage,
|
|
57
58
|
to: Role | "all" = "phone",
|
|
58
59
|
options: SendOptions = {},
|
|
59
60
|
): Promise<void> {
|
|
@@ -12,3 +12,11 @@ Engine validates its scope. The phone may replay the same ID after a lost reply.
|
|
|
12
12
|
This is not a migration of old chats into verified knowledge.
|
|
13
13
|
|
|
14
14
|
GrantTap Commercial Source License.
|
|
15
|
+
|
|
16
|
+
`read.ts` is the bounded Project evidence read: Memory and the invocation journal
|
|
17
|
+
run in parallel with independent deadlines. Calls may belong to older Tasks
|
|
18
|
+
outside the current phone snapshot. A failed source leaves the other source
|
|
19
|
+
available. Whole records are omitted to fit the encrypted frame budget; record
|
|
20
|
+
content is never shortened. `runtime-read.ts` exposes this read over local Mac
|
|
21
|
+
IPC and an authenticated, requester-scoped Project relay response. It does not
|
|
22
|
+
rescan provider chats or rebuild architecture.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { MeshSnapshot, ProjectKnowledgeRecord } from "../../../../../packages/protocol/schema";
|
|
2
|
+
import { ProjectKnowledgePage, ProjectKnowledgeQuery } from "../../../../../packages/protocol/messages/mesh/knowledge/refresh";
|
|
3
|
+
import type { InvocationHistoryPage } from "../../engine/protocol/engine-invocation-protocol";
|
|
4
|
+
import { activeProjectKnowledge, supersededProjectKnowledgeIds } from "./active";
|
|
5
|
+
|
|
6
|
+
export type KnowledgeReadDependencies = {
|
|
7
|
+
snapshot: (projectId: string) => MeshSnapshot | undefined;
|
|
8
|
+
memory: (projectId: string) => Promise<ProjectKnowledgeRecord[] | undefined>;
|
|
9
|
+
history: (projectId: string) => Promise<InvocationHistoryPage | undefined>;
|
|
10
|
+
endpointId: string;
|
|
11
|
+
now: () => number;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function readProjectKnowledge(
|
|
16
|
+
query: ProjectKnowledgeQuery, dependencies: KnowledgeReadDependencies,
|
|
17
|
+
): Promise<ProjectKnowledgePage | undefined> {
|
|
18
|
+
if (!ProjectKnowledgeQuery.safeParse(query).success) return undefined;
|
|
19
|
+
const snapshot = dependencies.snapshot(query.projectId);
|
|
20
|
+
if (!snapshot || snapshot.projectId !== query.projectId) return undefined;
|
|
21
|
+
const timeout = dependencies.timeoutMs ?? 2_000;
|
|
22
|
+
let [memory, history] = await Promise.all([
|
|
23
|
+
boundedSource(() => dependencies.memory(query.projectId), timeout),
|
|
24
|
+
boundedSource(() => dependencies.history(query.projectId), timeout),
|
|
25
|
+
]);
|
|
26
|
+
if (memory?.some(record => record.projectId !== query.projectId || record.visibility !== "project")) {
|
|
27
|
+
memory = undefined;
|
|
28
|
+
}
|
|
29
|
+
if (history?.events.some(row => row.event.project_id !== query.projectId)) history = undefined;
|
|
30
|
+
const records = memory ?? [];
|
|
31
|
+
const knowledge = activeProjectKnowledge(query.projectId, records)
|
|
32
|
+
.sort((a, b) => b.recordedAt - a.recordedAt).slice(0, 32);
|
|
33
|
+
const invocations = (history?.events ?? []).slice(-32);
|
|
34
|
+
const page: ProjectKnowledgePage = {
|
|
35
|
+
type: "mesh.knowledge.page", sessionId: query.projectId, projectId: query.projectId,
|
|
36
|
+
requestId: query.requestId, sourceEndpointId: dependencies.endpointId,
|
|
37
|
+
memoryAvailability: memory ? "ready" : "unavailable",
|
|
38
|
+
invocationAvailability: history ? "ready" : "unavailable",
|
|
39
|
+
knowledge, invocations,
|
|
40
|
+
supersededKnowledgeRecordIds: supersededProjectKnowledgeIds(query.projectId, records),
|
|
41
|
+
incomplete: records.length > knowledge.length || history?.has_older === true
|
|
42
|
+
|| history?.has_more === true,
|
|
43
|
+
generatedAt: dependencies.now(),
|
|
44
|
+
};
|
|
45
|
+
// Keep encrypted frames below the transport limit without shortening a fact.
|
|
46
|
+
while (Buffer.byteLength(JSON.stringify(page)) > 128 * 1_024
|
|
47
|
+
&& (knowledge.length > 0 || invocations.length > 0)) {
|
|
48
|
+
const oldestRecord = knowledge.at(-1)?.recordedAt ?? Infinity;
|
|
49
|
+
const oldestCall = invocations[0]?.event.occurred_at ?? Infinity;
|
|
50
|
+
if (oldestRecord <= oldestCall) knowledge.pop();
|
|
51
|
+
else invocations.shift();
|
|
52
|
+
page.incomplete = true;
|
|
53
|
+
}
|
|
54
|
+
return ProjectKnowledgePage.parse(page);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function boundedSource<T>(read: () => Promise<T>, timeoutMs: number): Promise<T | undefined> {
|
|
58
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
59
|
+
try {
|
|
60
|
+
return await Promise.race([Promise.resolve().then(read), new Promise<undefined>(resolve => {
|
|
61
|
+
timer = setTimeout(() => resolve(undefined), timeoutMs);
|
|
62
|
+
})]);
|
|
63
|
+
} catch { return undefined; }
|
|
64
|
+
finally { if (timer) clearTimeout(timer); }
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { RelayClient } from "../../../../../packages/core/relay-client";
|
|
3
|
+
import { ProjectKnowledgeQuery } from "../../../../../packages/protocol/messages/mesh/knowledge/refresh";
|
|
4
|
+
import { configDir } from "../../config/runtime/paths";
|
|
5
|
+
import { EngineClient } from "../../engine/runtime/engine-client";
|
|
6
|
+
import { engineFeatureEnabled } from "../../engine/runtime/engine-supervisor";
|
|
7
|
+
import { projectKnowledge } from "../../engine/runtime/engine-memory";
|
|
8
|
+
import { sendProjectPayload } from "../../host/session-keys";
|
|
9
|
+
import { computerId } from "../identity/computer";
|
|
10
|
+
import { localMeshStore } from "../local-remote/local";
|
|
11
|
+
import { readProjectKnowledge } from "./read";
|
|
12
|
+
|
|
13
|
+
const engine = new EngineClient({ socketPath: join(configDir(), "engine.sock") });
|
|
14
|
+
|
|
15
|
+
export async function currentProjectKnowledge(query: ProjectKnowledgeQuery) {
|
|
16
|
+
return readProjectKnowledge(query, {
|
|
17
|
+
snapshot: projectId => localMeshStore().snapshot(projectId),
|
|
18
|
+
endpointId: computerId(), now: Date.now,
|
|
19
|
+
memory: projectId => projectKnowledge(projectId),
|
|
20
|
+
history: async projectId => {
|
|
21
|
+
if (!engineFeatureEnabled()) return undefined;
|
|
22
|
+
const result = await engine.request({ operation: "invocation.history",
|
|
23
|
+
input: { project_id: projectId, tail: true, limit: 32 } }, { timeoutMs: 1_000 });
|
|
24
|
+
return result.operation === "invocation.history" ? result.page : undefined;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function handleProjectKnowledgeQuery(
|
|
30
|
+
client: RelayClient, query: ProjectKnowledgeQuery, peerPublicKey?: string,
|
|
31
|
+
): Promise<boolean> {
|
|
32
|
+
if (!peerPublicKey) return false;
|
|
33
|
+
const page = await currentProjectKnowledge(query);
|
|
34
|
+
if (!page) return false;
|
|
35
|
+
if (page.memoryAvailability === "ready") {
|
|
36
|
+
localMeshStore().cacheKnowledge(page.projectId, page.knowledge);
|
|
37
|
+
}
|
|
38
|
+
await sendProjectPayload(client, page, "phone", {
|
|
39
|
+
peerPublicKey, ttlMs: 15_000, reliable: false,
|
|
40
|
+
}).catch(() => {});
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function desktopProjectKnowledge(input: unknown) {
|
|
45
|
+
const value = input as { project_id?: unknown; request_id?: unknown } | undefined;
|
|
46
|
+
const query = ProjectKnowledgeQuery.safeParse({ type: "mesh.knowledge.query",
|
|
47
|
+
projectId: value?.project_id, sessionId: value?.project_id, requestId: value?.request_id });
|
|
48
|
+
return query.success ? currentProjectKnowledge(query.data) : undefined;
|
|
49
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "../../mesh/runtime";
|
|
19
19
|
import { releaseClaimByPerson, releaseResult } from "../../mesh/admin";
|
|
20
20
|
import { handleInvocationQuery } from "../../engine/invocation/query";
|
|
21
|
+
import { handleProjectKnowledgeQuery } from "../../mesh/knowledge/runtime-read";
|
|
21
22
|
import { applyPairingJoin } from "../../pairing";
|
|
22
23
|
import { recordPhoneSeen } from "../../pairing/presence";
|
|
23
24
|
import { handleProjectPolicySet } from "../../project-policy/runtime";
|
|
@@ -123,6 +124,8 @@ export async function handleMonitorMessage(
|
|
|
123
124
|
const accepted = requestProjectCapability(payload);
|
|
124
125
|
if (accepted) void publish().catch(() => {});
|
|
125
126
|
return accepted;
|
|
127
|
+
} else if (payload.type === "mesh.knowledge.query" && loadRuntimeConfig().meshEnabled) {
|
|
128
|
+
return handleProjectKnowledgeQuery(client, payload, input.peerPublicKey);
|
|
126
129
|
} else if (payload.type === "mesh.invocation.query" && loadRuntimeConfig().meshEnabled) {
|
|
127
130
|
return handleInvocationQuery(client, payload);
|
|
128
131
|
} else if ((payload.type === "mesh.event" || payload.type === "mesh.snapshot")
|
|
@@ -9,10 +9,27 @@ expire after five minutes and changing the native auth file clears the cache.
|
|
|
9
9
|
Only visible picker metadata and reported usage percentages, window durations,
|
|
10
10
|
reset times and observation times cross the existing encrypted Mesh channel.
|
|
11
11
|
Identity, credentials, credits, instructions and raw provider responses stay out.
|
|
12
|
-
Missing windows remain unknown.
|
|
12
|
+
Missing windows remain unknown. `native-usage/reader.ts` independently reads the
|
|
13
|
+
already signed-in Claude, Cursor and Grok account interfaces, with an eight-second
|
|
14
|
+
request bound, a 64 KiB response bound and no redirects. Requests coalesce and
|
|
15
|
+
refresh at most once every two minutes. Expired windows disappear without
|
|
16
|
+
inventing a reset allowance. `native-usage/credentials.ts` reads existing native files
|
|
17
|
+
or the native macOS keychain entries; it never starts login, refreshes credentials
|
|
18
|
+
or changes provider settings. A relocated profile cannot use another account's
|
|
19
|
+
keychain. Temporary HOME directories never read the OS user's keychain.
|
|
20
|
+
|
|
21
|
+
Claude CLI aliases remain available. The GrantTap status-line wrapper observes
|
|
13
22
|
Claude's documented `rate_limits` after a native response, preserves the prior
|
|
14
23
|
status command/options, and stores only independently present 5h/7d windows.
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
Its newest real observation takes precedence over an older direct read. Expired
|
|
25
|
+
Claude credentials require a native Claude refresh; they are not a full allowance.
|
|
26
|
+
|
|
27
|
+
Cursor reports its weekly account usage, with the native billing-period pool as
|
|
28
|
+
a fallback for older plans. Grok reports its current subscription period and
|
|
29
|
+
credit usage percentage through its CLI billing interface. Unified subscriptions
|
|
30
|
+
may share a pool across Cursor and Grok: these are provider views, not additive
|
|
31
|
+
allowances. Grok's reported percentage can be rounded by the provider. A missing
|
|
32
|
+
or unauthenticated account report stays unknown. Session tokens, context size and
|
|
33
|
+
tool activity do not stand in for subscription limits.
|
|
17
34
|
|
|
18
|
-
Tests
|
|
35
|
+
Tests live in `tests/`. GrantTap Commercial Source License, see root LICENSE.
|
|
@@ -6,6 +6,7 @@ import { codexHookRpc, type HookRpc } from "../codex-hook-trust";
|
|
|
6
6
|
import { projectAccountModels, projectAccountUsage } from "./projection";
|
|
7
7
|
|
|
8
8
|
export { projectAccountModels, projectAccountUsage } from "./projection";
|
|
9
|
+
export { cachedNativeAccountUsage } from "./native-usage";
|
|
9
10
|
|
|
10
11
|
/** A bounded read of an already signed-in provider; no threads, turns or login. */
|
|
11
12
|
export function createProviderAccountReader(rpc: HookRpc, clock = Date.now) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Native account allowances
|
|
2
|
+
|
|
3
|
+
Public entry: `index.ts`, exported by the parent Provider Account Status module.
|
|
4
|
+
Reads the existing Claude, Cursor and Grok account interfaces without creating
|
|
5
|
+
sessions or changing credentials. `projection.ts` retains only usage windows;
|
|
6
|
+
`credentials.ts` bounds local native credential reads; `reader.ts` coalesces,
|
|
7
|
+
throttles and expires the independent requests. Native status-line observations
|
|
8
|
+
keep their actual timestamps and can supersede direct Claude observations.
|
|
9
|
+
|
|
10
|
+
Tests live in `tests/`; the parent README documents data sources and boundaries.
|
|
11
|
+
|
|
12
|
+
GrantTap Commercial Source License. See the repository root LICENSE.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { homedir, userInfo } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { NativeUsageProvider } from "./projection";
|
|
6
|
+
|
|
7
|
+
type Options = {
|
|
8
|
+
home?: string;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
platform?: NodeJS.Platform;
|
|
11
|
+
keychain?: (service: string, account?: string) => Promise<string | undefined>;
|
|
12
|
+
now?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The same existing native entries used by Claude Code and Cursor. Never refresh or write credentials. */
|
|
16
|
+
export function readNativeKeychain(service: string, account?: string, run = execFile): Promise<string | undefined> {
|
|
17
|
+
return new Promise(resolve => run("/usr/bin/security",
|
|
18
|
+
["find-generic-password", "-s", service, ...(account ? ["-a", account] : []), "-w"],
|
|
19
|
+
{ timeout: 3_000, maxBuffer: 65_536, windowsHide: true },
|
|
20
|
+
(error, stdout) => resolve(error ? undefined : stdout.trim() || undefined)));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function jsonFile(path: string): Promise<any> {
|
|
24
|
+
try {
|
|
25
|
+
if ((await stat(path)).size > 65_536) return undefined;
|
|
26
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
27
|
+
} catch { return undefined; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function token(value: unknown): string | undefined {
|
|
31
|
+
return typeof value === "string" && value.length > 0 && value.length <= 32_768
|
|
32
|
+
&& !/[\r\n]/.test(value) ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function claudeToken(value: any, now: number): string | undefined {
|
|
36
|
+
const row = value?.claudeAiOauth;
|
|
37
|
+
return typeof row?.expiresAt === "number" && row.expiresAt <= now ? undefined : token(row?.accessToken);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function readNativeCredential(provider: NativeUsageProvider, options: Options = {}): Promise<string | undefined> {
|
|
41
|
+
const home = options.home ?? homedir();
|
|
42
|
+
const env = options.env ?? process.env;
|
|
43
|
+
const platform = options.platform ?? process.platform;
|
|
44
|
+
const keychain = options.keychain ?? readNativeKeychain;
|
|
45
|
+
const keychainAllowed = options.keychain !== undefined || home === userInfo().homedir;
|
|
46
|
+
if (provider === "grok") {
|
|
47
|
+
const root = env.GRANTTAP_GROK_DIR ?? join(home, ".grok");
|
|
48
|
+
const auth = await jsonFile(join(root, "auth.json"));
|
|
49
|
+
return auth && Object.entries(auth).flatMap(([id, row]: [string, any]) =>
|
|
50
|
+
id.startsWith("https://auth.x.ai::") && row?.auth_mode === "oidc" ? [token(row.key)] : [])
|
|
51
|
+
.find(Boolean);
|
|
52
|
+
}
|
|
53
|
+
if (provider === "claude") {
|
|
54
|
+
const override = env.GRANTTAP_CLAUDE_DIR ?? env.NODVOX_CLAUDE_DIR ?? env.CLAUDE_CONFIG_DIR;
|
|
55
|
+
const saved = claudeToken(await jsonFile(join(override ?? join(home, ".claude"), ".credentials.json")), options.now ?? Date.now());
|
|
56
|
+
if (saved || platform !== "darwin" || override || !keychainAllowed) return saved;
|
|
57
|
+
if (!await jsonFile(join(home, ".claude", "settings.json"))) return undefined;
|
|
58
|
+
try {
|
|
59
|
+
const raw = await keychain("Claude Code-credentials");
|
|
60
|
+
return raw ? claudeToken(JSON.parse(raw), options.now ?? Date.now()) : undefined;
|
|
61
|
+
} catch { return undefined; }
|
|
62
|
+
}
|
|
63
|
+
const root = platform === "win32" ? join(env.APPDATA ?? join(home, "AppData", "Roaming"), "Cursor")
|
|
64
|
+
: platform === "darwin" ? join(home, ".cursor") : join(env.XDG_CONFIG_HOME ?? join(home, ".config"), "cursor");
|
|
65
|
+
const saved = token((await jsonFile(join(root, "auth.json")))?.accessToken);
|
|
66
|
+
if (saved || platform !== "darwin" || !keychainAllowed || !await jsonFile(join(root, "cli-config.json"))) return saved;
|
|
67
|
+
try { return token(await keychain("cursor-access-token", "cursor-user")); }
|
|
68
|
+
catch { return undefined; }
|
|
69
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import type { ProviderAccountUsage } from "../../../../../packages/protocol/schema";
|
|
3
|
+
import { readClaudeUsage } from "../claude-statusline";
|
|
4
|
+
import { readNativeCredential } from "./credentials";
|
|
5
|
+
import { createNativeAccountUsageReader } from "./reader";
|
|
6
|
+
|
|
7
|
+
let scope = "";
|
|
8
|
+
let reader = createNativeAccountUsageReader({ credential: readNativeCredential });
|
|
9
|
+
|
|
10
|
+
export function mergeNativeUsage(direct: ProviderAccountUsage[], statusLine: ProviderAccountUsage[],
|
|
11
|
+
now = Date.now()): ProviderAccountUsage[] {
|
|
12
|
+
const newest = new Map<string, ProviderAccountUsage>();
|
|
13
|
+
for (const report of [...direct, ...statusLine]) {
|
|
14
|
+
const key = `${report.provider}\u001f${report.limitId}`;
|
|
15
|
+
const windows = report.windows.filter(window => window.resetsAt > now);
|
|
16
|
+
if (!windows.length || now - report.observedAt > 300_000 || report.observedAt > now + 60_000) continue;
|
|
17
|
+
if ((newest.get(key)?.observedAt ?? -Infinity) <= report.observedAt) newest.set(key, { ...report, windows });
|
|
18
|
+
}
|
|
19
|
+
return [...newest.values()];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Account reads are scheduled beside the catalog; provider credentials never enter its result. */
|
|
23
|
+
export function cachedNativeAccountUsage(): ProviderAccountUsage[] {
|
|
24
|
+
const env = process.env;
|
|
25
|
+
const current = [homedir(), env.GRANTTAP_CLAUDE_DIR, env.NODVOX_CLAUDE_DIR, env.CLAUDE_CONFIG_DIR,
|
|
26
|
+
env.GRANTTAP_GROK_DIR, env.XDG_CONFIG_HOME, env.APPDATA].join("\u001f");
|
|
27
|
+
if (scope !== current) { scope = current; reader = createNativeAccountUsageReader({ credential: readNativeCredential }); }
|
|
28
|
+
void reader.refresh();
|
|
29
|
+
return mergeNativeUsage(reader.current(), readClaudeUsage());
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ProviderAccountUsage } from "../../../../../packages/protocol/schema";
|
|
2
|
+
|
|
3
|
+
type ProviderUsageWindow = ProviderAccountUsage["windows"][number];
|
|
4
|
+
|
|
5
|
+
export type NativeUsageProvider = "claude" | "cursor" | "grok";
|
|
6
|
+
|
|
7
|
+
function percent(value: unknown): value is number {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function timestamp(value: unknown): number {
|
|
12
|
+
return typeof value === "string" ? Date.parse(value) : NaN;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Only allowance windows cross the wire; identity, money and top-up settings never do. */
|
|
16
|
+
export function projectNativeAccountUsage(provider: NativeUsageProvider, input: any,
|
|
17
|
+
now = Date.now()): ProviderAccountUsage | undefined {
|
|
18
|
+
let windows: ProviderUsageWindow[] = [];
|
|
19
|
+
if (provider === "claude") {
|
|
20
|
+
windows = ([["five_hour", "primary", 300], ["seven_day", "secondary", 10_080]] as const)
|
|
21
|
+
.flatMap(([key, id, windowMinutes]) => {
|
|
22
|
+
const row = input?.[key];
|
|
23
|
+
const resetsAt = timestamp(row?.resets_at);
|
|
24
|
+
return percent(row?.utilization) && resetsAt > now
|
|
25
|
+
? [{ id, usedPercent: row.utilization, windowMinutes, resetsAt }] : [];
|
|
26
|
+
});
|
|
27
|
+
} else {
|
|
28
|
+
const start = timestamp(provider === "cursor" ? input?.currentPeriodStart : input?.config?.currentPeriod?.start);
|
|
29
|
+
const resetsAt = timestamp(provider === "cursor" ? input?.nextResetTimestampUtc : input?.config?.currentPeriod?.end);
|
|
30
|
+
const usedPercent = provider === "cursor" ? input?.usagePercent : input?.config?.creditUsagePercent;
|
|
31
|
+
if (percent(usedPercent) && Number.isFinite(start) && start <= now && resetsAt > now && resetsAt > start) {
|
|
32
|
+
const windowMinutes = Math.ceil((resetsAt - start) / 60_000);
|
|
33
|
+
if (Number.isSafeInteger(windowMinutes) && windowMinutes > 0) {
|
|
34
|
+
windows = [{ id: "primary", usedPercent, windowMinutes, resetsAt }];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return windows.length ? { provider, limitId: provider, observedAt: now, windows } : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Older Cursor plans report a billing-period pool rather than a weekly pool. */
|
|
42
|
+
export function projectCursorPeriodUsage(input: any, now = Date.now()): ProviderAccountUsage | undefined {
|
|
43
|
+
const start = Number(input?.billingCycleStart), end = Number(input?.billingCycleEnd);
|
|
44
|
+
const plan = input?.planUsage;
|
|
45
|
+
const usedPercent = percent(plan?.totalPercentUsed) ? plan.totalPercentUsed
|
|
46
|
+
: Number.isFinite(plan?.totalSpend) && plan.totalSpend >= 0 && Number.isFinite(plan?.limit) && plan.limit > 0
|
|
47
|
+
? plan.totalSpend / plan.limit * 100 : undefined;
|
|
48
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= start
|
|
49
|
+
|| start > 8.64e15 || end > 8.64e15) return undefined;
|
|
50
|
+
return projectNativeAccountUsage("cursor", { usagePercent: usedPercent,
|
|
51
|
+
currentPeriodStart: new Date(start).toISOString(), nextResetTimestampUtc: new Date(end).toISOString() }, now);
|
|
52
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ProviderAccountUsage } from "../../../../../packages/protocol/schema";
|
|
2
|
+
import { projectCursorPeriodUsage, projectNativeAccountUsage, type NativeUsageProvider } from "./projection";
|
|
3
|
+
|
|
4
|
+
type Options = {
|
|
5
|
+
credential: (provider: NativeUsageProvider) => Promise<string | undefined>;
|
|
6
|
+
fetcher?: (url: string, init: RequestInit) => Promise<Response>;
|
|
7
|
+
clock?: () => number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const requests = {
|
|
11
|
+
claude: { url: "https://api.anthropic.com/api/oauth/usage", method: "GET" },
|
|
12
|
+
cursor: { url: "https://api2.cursor.sh/aiserver.v1.DashboardService/GetSandUsageStatus", method: "POST" },
|
|
13
|
+
grok: { url: "https://cli-chat-proxy.grok.com/v1/billing?format=credits", method: "GET" },
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
async function boundedResponse(response: Response): Promise<unknown> {
|
|
17
|
+
if (!response.ok || !response.body) return undefined;
|
|
18
|
+
const reader = response.body.getReader();
|
|
19
|
+
const chunks: Uint8Array[] = [];
|
|
20
|
+
let length = 0;
|
|
21
|
+
try {
|
|
22
|
+
while (true) {
|
|
23
|
+
const chunk = await reader.read();
|
|
24
|
+
if (chunk.done) break;
|
|
25
|
+
length += chunk.value.byteLength;
|
|
26
|
+
if (length > 65_536) return undefined;
|
|
27
|
+
chunks.push(chunk.value);
|
|
28
|
+
}
|
|
29
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
30
|
+
} finally { await reader.cancel(); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Independent, bounded account reads. A catalog never waits for a native login or a turn. */
|
|
34
|
+
export function createNativeAccountUsageReader(options: Options) {
|
|
35
|
+
const clock = options.clock ?? Date.now;
|
|
36
|
+
const fetcher = options.fetcher ?? fetch;
|
|
37
|
+
const reports = new Map<NativeUsageProvider, ProviderAccountUsage>();
|
|
38
|
+
let pending: Promise<void> | undefined;
|
|
39
|
+
let attemptedAt = -Infinity;
|
|
40
|
+
|
|
41
|
+
async function update(provider: NativeUsageProvider): Promise<void> {
|
|
42
|
+
try {
|
|
43
|
+
const token = await options.credential(provider);
|
|
44
|
+
if (!token) { reports.delete(provider); return; }
|
|
45
|
+
const request = requests[provider];
|
|
46
|
+
const response = await fetcher(request.url, {
|
|
47
|
+
method: request.method, redirect: "error", signal: AbortSignal.timeout(8_000),
|
|
48
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json",
|
|
49
|
+
...(provider === "claude" ? { "anthropic-beta": "oauth-2025-04-20" } : {}),
|
|
50
|
+
...(provider === "cursor" ? { "Connect-Protocol-Version": "1" } : {}),
|
|
51
|
+
},
|
|
52
|
+
...(request.method === "POST" ? { body: "{}" } : {}),
|
|
53
|
+
});
|
|
54
|
+
const value = await boundedResponse(response);
|
|
55
|
+
let report = projectNativeAccountUsage(provider, value, clock());
|
|
56
|
+
if (provider === "cursor" && !report && value !== undefined) {
|
|
57
|
+
const legacy = await fetcher("https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
|
|
58
|
+
method: "POST", redirect: "error", signal: AbortSignal.timeout(8_000), body: "{}",
|
|
59
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
60
|
+
});
|
|
61
|
+
report = projectCursorPeriodUsage(await boundedResponse(legacy), clock());
|
|
62
|
+
}
|
|
63
|
+
if (report) reports.set(provider, report);
|
|
64
|
+
else reports.delete(provider);
|
|
65
|
+
} catch { reports.delete(provider); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
current(): ProviderAccountUsage[] {
|
|
70
|
+
const now = clock();
|
|
71
|
+
return [...reports.values()].flatMap(report => {
|
|
72
|
+
const windows = report.windows.filter(window => window.resetsAt > now);
|
|
73
|
+
return now - report.observedAt <= 300_000 && windows.length ? [{ ...report, windows }] : [];
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
refresh(): Promise<void> {
|
|
77
|
+
if (pending) return pending;
|
|
78
|
+
if (clock() - attemptedAt < 120_000) return Promise.resolve();
|
|
79
|
+
attemptedAt = clock();
|
|
80
|
+
pending = Promise.allSettled((Object.keys(requests) as NativeUsageProvider[]).map(update))
|
|
81
|
+
.then(() => undefined).finally(() => { pending = undefined; });
|
|
82
|
+
return pending;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -3,9 +3,8 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import type { AdvertisedModel, EndpointModelCatalog } from "../../../../packages/protocol/schema";
|
|
5
5
|
|
|
6
|
-
import { readClaudeUsage } from "../provider-account-status/claude-statusline";
|
|
7
6
|
import { cachedCLIModels } from "./cli-models";
|
|
8
|
-
import { cachedProviderAccount } from "../provider-account-status";
|
|
7
|
+
import { cachedNativeAccountUsage, cachedProviderAccount } from "../provider-account-status";
|
|
9
8
|
|
|
10
9
|
const MAX_BYTES = 2_000_000;
|
|
11
10
|
const MAX_AGE = 24 * 60 * 60_000;
|
|
@@ -82,6 +81,6 @@ export function readProviderModelCatalog(endpointId: string): EndpointModelCatal
|
|
|
82
81
|
const codex = readCodexModelCatalog(endpointId);
|
|
83
82
|
const cli = cachedCLIModels(endpointId);
|
|
84
83
|
return { ...codex, observedAt: Math.max(codex.observedAt, ...cli.map(row => row.observedAt)),
|
|
85
|
-
accountUsage: [...(codex.accountUsage ?? []), ...
|
|
84
|
+
accountUsage: [...(codex.accountUsage ?? []), ...cachedNativeAccountUsage()],
|
|
86
85
|
stale: codex.stale === true && cli.length === 0, models: [...codex.models, ...cli].slice(0, 256) };
|
|
87
86
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { TaskRoutingNeeds } from '../../../../../packages/protocol/task-resources';
|
|
2
3
|
import { loadConfig, loadRuntimeConfig, machineConfigPath } from '../../config';
|
|
3
4
|
import { applyNetworkRoute } from '../../device-network/settings';
|
|
4
5
|
import { scanSessions } from '../../sessions';
|
|
@@ -14,7 +15,7 @@ import { observedRoutingSession } from '../observed-turn';
|
|
|
14
15
|
|
|
15
16
|
export const OffloadInput = z.object({ action: z.enum(['request', 'cancel', 'status']).optional(),
|
|
16
17
|
operationId: z.string().trim().min(1).max(128).optional(),
|
|
17
|
-
requirements:
|
|
18
|
+
requirements: TaskRoutingNeeds.partial().extend({ reason: RoutingRequirementsInput.shape.reason }).optional() }).strict();
|
|
18
19
|
export type OffloadArguments = z.infer<typeof OffloadInput>;
|
|
19
20
|
type Proof = { sessionId: string; provider: RoutingRequestSource['provider'] };
|
|
20
21
|
type Ports = { store: RoutingRequests; enabled: () => boolean;
|
|
@@ -21,6 +21,7 @@ export const DESKTOP_OPERATIONS = new Set([
|
|
|
21
21
|
"desktop.machine_load", "desktop.capability_usage", "desktop.task_image", "desktop.task_workspace",
|
|
22
22
|
"desktop.installed_skills", "desktop.policy_status", "desktop.policy_set", "desktop.project_auto_accept",
|
|
23
23
|
"desktop.mesh_snapshots",
|
|
24
|
+
"desktop.project_knowledge",
|
|
24
25
|
"desktop.task_send", "desktop.task_queue", "desktop.transfer_continue", "desktop.task_control", "desktop.task_preferences",
|
|
25
26
|
"desktop.task_handoff",
|
|
26
27
|
"desktop.project_transport",
|
|
@@ -20,6 +20,7 @@ import { desktopTaskHandoff } from "../delivery/task-handoff";
|
|
|
20
20
|
import { desktopTaskCreate } from "../delivery/task-create";
|
|
21
21
|
import { desktopMeshCreate } from "../delivery/mesh-create";
|
|
22
22
|
import { desktopInvocationHistory } from "../invocation";
|
|
23
|
+
import { desktopProjectKnowledge } from "../../../../bridge/src/mesh/knowledge/runtime-read";
|
|
23
24
|
import { desktopUsage } from "../projection/usage";
|
|
24
25
|
import { desktopInstalledSkills } from "../projection/installed-skills";
|
|
25
26
|
import type { DesktopControllerEnrollment } from "../pairing";
|
|
@@ -74,6 +75,7 @@ export function desktopReadOperation(input: {
|
|
|
74
75
|
if (operation === "desktop.invocation_history") {
|
|
75
76
|
return desktopInvocationHistory(queryInput, engine, storePath);
|
|
76
77
|
}
|
|
78
|
+
if (operation === "desktop.project_knowledge") return desktopProjectKnowledge(queryInput);
|
|
77
79
|
if (operation === "desktop.project") {
|
|
78
80
|
return Promise.resolve(desktopMeshProject(queryInput, storePath));
|
|
79
81
|
}
|
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.92
|
|
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.92\";\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 22.13.0+ 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.92";
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "granttap-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.92",
|
|
4
4
|
"description": "Commercial computer runtime connecting local coding agents to GrantTap on Mac, iPhone, iPad and Apple Watch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"tsx": "^4.23.4",
|
|
43
43
|
"tweetnacl": "^1.0.3",
|
|
44
44
|
"ws": "^8.18.0",
|
|
45
|
-
"zod": "
|
|
45
|
+
"zod": "4.6.7"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/express": "^5.0.3",
|
|
@@ -15,7 +15,7 @@ export function questionAnswers(questions: z.infer<typeof ApprovalQuestions> | u
|
|
|
15
15
|
note: string | null | undefined): Record<string, string> | undefined {
|
|
16
16
|
if (!questions || !note || note.length > 20_000) return undefined;
|
|
17
17
|
try {
|
|
18
|
-
const answers = z.record(z.string().trim().min(1).max(4_000)).parse(JSON.parse(note));
|
|
18
|
+
const answers = z.record(z.string(), z.string().trim().min(1).max(4_000)).parse(JSON.parse(note));
|
|
19
19
|
if (Object.keys(answers).length !== questions.length
|
|
20
20
|
|| questions.some(item => !Object.hasOwn(answers, item.question))) return undefined;
|
|
21
21
|
return answers;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Identifier } from "../endpoint";
|
|
3
|
+
import { MeshInvocationEvent } from "../invocations";
|
|
4
|
+
import { ProjectKnowledgeRecord } from "./index";
|
|
5
|
+
|
|
6
|
+
const Sequence = z.number().int().nonnegative().safe();
|
|
7
|
+
export const ProjectKnowledgeQuery = z.object({
|
|
8
|
+
type: z.literal("mesh.knowledge.query"),
|
|
9
|
+
sessionId: Identifier, projectId: Identifier, requestId: Identifier,
|
|
10
|
+
}).strict().refine(value => value.sessionId === value.projectId, "Project scope mismatch");
|
|
11
|
+
export type ProjectKnowledgeQuery = z.infer<typeof ProjectKnowledgeQuery>;
|
|
12
|
+
|
|
13
|
+
/** One bounded Project read; no architecture rebuild or per-Task fan-out. */
|
|
14
|
+
export const ProjectKnowledgePage = z.object({
|
|
15
|
+
type: z.literal("mesh.knowledge.page"),
|
|
16
|
+
sessionId: Identifier, projectId: Identifier, requestId: Identifier,
|
|
17
|
+
sourceEndpointId: Identifier,
|
|
18
|
+
memoryAvailability: z.enum(["ready", "unavailable"]),
|
|
19
|
+
invocationAvailability: z.enum(["ready", "unavailable"]),
|
|
20
|
+
knowledge: z.array(ProjectKnowledgeRecord).max(32),
|
|
21
|
+
supersededKnowledgeRecordIds: z.array(Identifier).max(128),
|
|
22
|
+
invocations: z.array(z.object({ sequence: Sequence, event: MeshInvocationEvent }).strict()).max(32),
|
|
23
|
+
incomplete: z.boolean(), generatedAt: Sequence,
|
|
24
|
+
}).strict().superRefine((value, ctx) => {
|
|
25
|
+
let previous = 0;
|
|
26
|
+
const seenCalls = new Set<string>();
|
|
27
|
+
const orderedCalls = value.invocations.every(row => {
|
|
28
|
+
const valid = row.sequence > previous && !seenCalls.has(row.event.event_id);
|
|
29
|
+
previous = row.sequence;
|
|
30
|
+
seenCalls.add(row.event.event_id);
|
|
31
|
+
return valid;
|
|
32
|
+
});
|
|
33
|
+
if (!orderedCalls
|
|
34
|
+
|| new Set(value.knowledge.map(row => row.recordId)).size !== value.knowledge.length
|
|
35
|
+
|| new Set(value.supersededKnowledgeRecordIds).size !== value.supersededKnowledgeRecordIds.length
|
|
36
|
+
|| value.sessionId !== value.projectId
|
|
37
|
+
|| value.knowledge.some(record => record.projectId !== value.projectId || record.visibility !== "project")
|
|
38
|
+
|| value.invocations.some(row => row.event.project_id !== value.projectId)
|
|
39
|
+
|| value.memoryAvailability === "unavailable" && value.knowledge.length > 0
|
|
40
|
+
|| value.invocationAvailability === "unavailable" && value.invocations.length > 0) {
|
|
41
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Knowledge source or Project scope mismatch" });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
export type ProjectKnowledgePage = z.infer<typeof ProjectKnowledgePage>;
|
|
@@ -149,8 +149,8 @@ export const SessionsStatus = z.object({
|
|
|
149
149
|
gatingEnabled: z.boolean().optional(),
|
|
150
150
|
excludedSessions: z.array(z.string()).optional(),
|
|
151
151
|
autoAcceptDefault: AutoAcceptLevel.optional(),
|
|
152
|
-
autoAcceptBySession: z.record(AutoAcceptLevel).optional(),
|
|
153
|
-
autoAcceptByProject: z.record(AutoAcceptLevel).optional(),
|
|
152
|
+
autoAcceptBySession: z.record(z.string(), AutoAcceptLevel).optional(),
|
|
153
|
+
autoAcceptByProject: z.record(z.string(), AutoAcceptLevel).optional(),
|
|
154
154
|
autoAcceptPaused: z.boolean().optional(),
|
|
155
155
|
providerSettings: ProviderRuntimeSettings.optional(),
|
|
156
156
|
meshEnabled: z.boolean().optional(),
|
|
@@ -45,6 +45,7 @@ import { PairingJoin } from "./messages/pairing-join";
|
|
|
45
45
|
import { ControllerPairOffer, ControllerPairRequest } from "./messages/device/controller-pairing";
|
|
46
46
|
import { AccountMachineLink } from "./messages/device/account-machine-link";
|
|
47
47
|
import { KnowledgeWrite, KnowledgeWriteResult } from "./messages/mesh/knowledge/write";
|
|
48
|
+
import { ProjectKnowledgeQuery, ProjectKnowledgePage } from "./messages/mesh/knowledge/refresh";
|
|
48
49
|
import { MeshInvocationPage, MeshInvocationQuery } from "./messages/mesh/invocations";
|
|
49
50
|
import {
|
|
50
51
|
ProjectPolicyAck,
|
|
@@ -82,6 +83,7 @@ export * from "./messages/pairing-join";
|
|
|
82
83
|
export * from "./messages/device/controller-pairing";
|
|
83
84
|
export * from "./messages/device/account-machine-link";
|
|
84
85
|
export * from "./messages/mesh/knowledge/write";
|
|
86
|
+
export * from "./messages/mesh/knowledge/refresh";
|
|
85
87
|
export * from "./messages/mesh";
|
|
86
88
|
export * from "./messages/mesh/invocations";
|
|
87
89
|
export * from "./messages/primitives";
|
|
@@ -165,6 +167,8 @@ export const Payload = z.union([
|
|
|
165
167
|
ProjectCapabilityRequestSet,
|
|
166
168
|
MeshInvocationQuery,
|
|
167
169
|
MeshInvocationPage,
|
|
170
|
+
ProjectKnowledgeQuery,
|
|
171
|
+
ProjectKnowledgePage,
|
|
168
172
|
MeshEndpointPolicy,
|
|
169
173
|
ProjectPolicySet,
|
|
170
174
|
ProjectPolicyStatus,
|