@kontextmind/kxm 0.7.15 → 0.7.16
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/.claude-plugin/marketplace.json +1 -1
- package/docs/configuration.md +1 -2
- package/docs/operator-pi-packages.md +5 -2
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +4 -0
- package/plugins/kxm/dist/core.js +4 -0
- package/plugins/kxm/dist/extension.js +3066 -2
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +4 -0
- package/plugins/kxm/dist/server.js +4 -0
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/extension.ts +9 -0
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/providers/antigravity/LICENSE +21 -0
- package/plugins/kxm/src/providers/antigravity/README.md +19 -0
- package/plugins/kxm/src/providers/antigravity/auth/index.ts +17 -0
- package/plugins/kxm/src/providers/antigravity/auth/oauth.ts +449 -0
- package/plugins/kxm/src/providers/antigravity/client/client.ts +562 -0
- package/plugins/kxm/src/providers/antigravity/client/index.ts +1 -0
- package/plugins/kxm/src/providers/antigravity/diagnostics/diagnostics.ts +97 -0
- package/plugins/kxm/src/providers/antigravity/diagnostics/index.ts +1 -0
- package/plugins/kxm/src/providers/antigravity/index.ts +24 -0
- package/plugins/kxm/src/providers/antigravity/models/discovery.ts +155 -0
- package/plugins/kxm/src/providers/antigravity/models/grouping.ts +425 -0
- package/plugins/kxm/src/providers/antigravity/models/index.ts +3 -0
- package/plugins/kxm/src/providers/antigravity/models/models.ts +501 -0
- package/plugins/kxm/src/providers/antigravity/pi-compat.ts +279 -0
- package/plugins/kxm/src/providers/antigravity/register.ts +123 -0
- package/plugins/kxm/src/providers/antigravity/stream/index.ts +1 -0
- package/plugins/kxm/src/providers/antigravity/stream/stream.ts +1461 -0
- package/plugins/kxm/src/providers/antigravity/types/enums.ts +50 -0
- package/plugins/kxm/src/providers/antigravity/types/index.ts +2 -0
- package/plugins/kxm/src/providers/antigravity/types/types.ts +293 -0
- package/plugins/kxm/src/providers/antigravity/usage/index.ts +1 -0
- package/plugins/kxm/src/providers/antigravity/usage/usage.ts +361 -0
- package/plugins/kxm/src/providers/antigravity/utils/http.ts +32 -0
- package/plugins/kxm/src/providers/antigravity/utils/index.ts +3 -0
- package/plugins/kxm/src/providers/antigravity/utils/security.ts +75 -0
- package/plugins/kxm/src/providers/antigravity/utils/util.ts +133 -0
- package/plugins/kxm/src/redact.ts +4 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @ts-nocheck — vendored snapshot; host tsconfig is stricter than upstream.
|
|
2
|
+
import { antigravityEnv } from "./util.ts";
|
|
3
|
+
|
|
4
|
+
const PREWARM_TIMEOUT_MS = 5_000;
|
|
5
|
+
|
|
6
|
+
/** Host fetch. Node 22+ already pools connections; no extra HTTP client dep. */
|
|
7
|
+
export async function antigravityFetch(
|
|
8
|
+
input: string | URL,
|
|
9
|
+
init: RequestInit = {},
|
|
10
|
+
): Promise<Response> {
|
|
11
|
+
return fetch(input, init);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Open the TLS connection when the extension loads so the first message of a session
|
|
16
|
+
* does not pay the handshake either. Best-effort: failures are ignored.
|
|
17
|
+
*/
|
|
18
|
+
export function prewarmConnection(url: string): void {
|
|
19
|
+
if (antigravityEnv("NO_PREWARM") === "1") return;
|
|
20
|
+
if (process.env.NODE_TEST_CONTEXT) return;
|
|
21
|
+
void (async () => {
|
|
22
|
+
try {
|
|
23
|
+
const res = await antigravityFetch(url, {
|
|
24
|
+
method: "HEAD",
|
|
25
|
+
signal: AbortSignal.timeout(PREWARM_TIMEOUT_MS),
|
|
26
|
+
});
|
|
27
|
+
await res.arrayBuffer();
|
|
28
|
+
} catch {
|
|
29
|
+
// Warm-up only; the real request will establish the connection instead.
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @ts-nocheck — vendored snapshot; host tsconfig is stricter than upstream.
|
|
2
|
+
import { antigravityEnv } from "./util.ts";
|
|
3
|
+
|
|
4
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1", "localhost"]);
|
|
5
|
+
const ALLOWED_API_HOST_SUFFIXES = [".googleapis.com", ".sandbox.googleapis.com"];
|
|
6
|
+
|
|
7
|
+
/** Only loopback binds are allowed so OAuth codes cannot be stolen off-machine. */
|
|
8
|
+
export function resolveCallbackHost(raw = antigravityEnv("CALLBACK_HOST")): string {
|
|
9
|
+
const host = (raw || "127.0.0.1").trim().toLowerCase();
|
|
10
|
+
if (!LOOPBACK_HOSTS.has(host)) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`Unsafe ANTIGRAVITY_CALLBACK_HOST="${host}". Only loopback hosts are allowed: 127.0.0.1, ::1, localhost.`,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return host === "localhost" ? "127.0.0.1" : host;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Prevent token exfiltration via poisoned BASE_URL (SSRF / credential leak). */
|
|
19
|
+
export function assertSafeApiBaseUrl(raw: string): string {
|
|
20
|
+
let url: URL;
|
|
21
|
+
try {
|
|
22
|
+
url = new URL(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error(`Invalid ANTIGRAVITY_BASE_URL: ${raw}`);
|
|
25
|
+
}
|
|
26
|
+
if (url.protocol !== "https:") {
|
|
27
|
+
throw new Error(`ANTIGRAVITY_BASE_URL must use https (got ${url.protocol})`);
|
|
28
|
+
}
|
|
29
|
+
if (url.username || url.password) {
|
|
30
|
+
throw new Error("ANTIGRAVITY_BASE_URL must not include credentials");
|
|
31
|
+
}
|
|
32
|
+
const host = url.hostname.toLowerCase();
|
|
33
|
+
const allowed =
|
|
34
|
+
host === "googleapis.com" || ALLOWED_API_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix));
|
|
35
|
+
if (!allowed) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`ANTIGRAVITY_BASE_URL host "${host}" is not allowed. Use a *.googleapis.com endpoint.`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
41
|
+
return `${url.origin}${path === "/" ? "" : path}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Redact bearer tokens, refresh tokens, and similar secrets from diagnostics/errors. */
|
|
45
|
+
export function redactSecrets(text: string): string {
|
|
46
|
+
return text
|
|
47
|
+
.replace(/\bya29\.[A-Za-z0-9._~+/-]+=*/g, "[redacted-access-token]")
|
|
48
|
+
.replace(/\b1\/\/[A-Za-z0-9_-]+/g, "[redacted-refresh-token]")
|
|
49
|
+
.replace(/\b1\/[A-Za-z0-9_-]{20,}/g, "[redacted-refresh-token]")
|
|
50
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi, "Bearer [redacted]")
|
|
51
|
+
.replace(
|
|
52
|
+
/("?(?:access_token|refresh_token|id_token|token|client_secret|code_verifier|authorization)"?\s*[:=]\s*")[^"]*(")/gi,
|
|
53
|
+
"$1[redacted]$2",
|
|
54
|
+
)
|
|
55
|
+
.replace(
|
|
56
|
+
/("?(?:access_token|refresh_token|id_token|token|client_secret|code_verifier|authorization)"?\s*[:=]\s*)[^\s&,}]+/gi,
|
|
57
|
+
"$1[redacted]",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function maskEmail(email: string | undefined): string | undefined {
|
|
62
|
+
if (!email || typeof email !== "string") return undefined;
|
|
63
|
+
const parts = email.split("@");
|
|
64
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return "[redacted-email]";
|
|
65
|
+
const name = parts[0];
|
|
66
|
+
const domain = parts[1];
|
|
67
|
+
const lastChar = name.at(-1) || "";
|
|
68
|
+
const maskedName = name.length > 2 ? `${name[0]}***${lastChar}` : `${name[0]}***`;
|
|
69
|
+
return `${maskedName}@${domain}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function safeError(error: unknown): string {
|
|
73
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
74
|
+
return redactSecrets(raw);
|
|
75
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// @ts-nocheck — vendored snapshot; host tsconfig is stricter than upstream.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { getModelEnum } from "../models/models.ts";
|
|
4
|
+
|
|
5
|
+
export function antigravityEnv(name: string): string | undefined {
|
|
6
|
+
return process.env[`ANTIGRAVITY_${name}`] || process.env[`NOAGY_${name}`];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function asString(value: unknown): string | undefined {
|
|
14
|
+
return typeof value === "string" && value ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function sanitizeText(text: unknown): string {
|
|
18
|
+
return String(text ?? "").replace(/[\uD800-\uDFFF]/g, "\uFFFD");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function escapeHtml(text: string): string {
|
|
22
|
+
return text
|
|
23
|
+
.replace(/&/g, "&")
|
|
24
|
+
.replace(/</g, "<")
|
|
25
|
+
.replace(/>/g, ">")
|
|
26
|
+
.replace(/"/g, """)
|
|
27
|
+
.replace(/'/g, "'");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function escapeRegExp(text: string): string {
|
|
31
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function nowRequestId(): string {
|
|
35
|
+
return antigravityRequestEnvelope("unknown", false).requestId;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Deterministic RFC 4122 v5 UUID from seed (survives restarts for the same session seed). */
|
|
39
|
+
export function stableUuid(seed: string): string {
|
|
40
|
+
const bytes = createHash("sha1").update(seed).digest().subarray(0, 16);
|
|
41
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x50;
|
|
42
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
43
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
44
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type AntigravityEnvelopeOptions = {
|
|
48
|
+
isClaude?: boolean;
|
|
49
|
+
isNonGemini?: boolean;
|
|
50
|
+
step?: number;
|
|
51
|
+
lastStepIndex?: string;
|
|
52
|
+
requestIndex?: number;
|
|
53
|
+
userTurnIndex?: number;
|
|
54
|
+
trajectoryId?: string;
|
|
55
|
+
conversationId?: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const sessionTrajectoryMap = new Map<string, { conversationId: string; trajectoryId: string }>();
|
|
59
|
+
|
|
60
|
+
/** Stable conversationId and trajectoryId within a multi-turn conversation session. */
|
|
61
|
+
export function resolveSessionTrajectory(context?: {
|
|
62
|
+
messages?: Array<{ role?: string; timestamp?: number; content?: unknown }>;
|
|
63
|
+
}): { conversationId: string; trajectoryId: string } {
|
|
64
|
+
const firstMsg = context?.messages?.[0];
|
|
65
|
+
if (!firstMsg) {
|
|
66
|
+
return { conversationId: crypto.randomUUID(), trajectoryId: crypto.randomUUID() };
|
|
67
|
+
}
|
|
68
|
+
const contentSeed =
|
|
69
|
+
typeof firstMsg.content === "string"
|
|
70
|
+
? firstMsg.content.slice(0, 64)
|
|
71
|
+
: Array.isArray(firstMsg.content)
|
|
72
|
+
? JSON.stringify(firstMsg.content[0] ?? "").slice(0, 64)
|
|
73
|
+
: "";
|
|
74
|
+
const seed = `${firstMsg.role || "user"}:${firstMsg.timestamp || ""}:${contentSeed}`;
|
|
75
|
+
let entry = sessionTrajectoryMap.get(seed);
|
|
76
|
+
if (!entry) {
|
|
77
|
+
entry = {
|
|
78
|
+
conversationId: stableUuid(`antigravity:conv:${seed}`),
|
|
79
|
+
trajectoryId: stableUuid(`antigravity:traj:${seed}`),
|
|
80
|
+
};
|
|
81
|
+
sessionTrajectoryMap.set(seed, entry);
|
|
82
|
+
if (sessionTrajectoryMap.size > 64) {
|
|
83
|
+
const oldestKey = sessionTrajectoryMap.keys().next().value;
|
|
84
|
+
if (oldestKey !== undefined) sessionTrajectoryMap.delete(oldestKey);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return entry;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function clearSessionTrajectoryMap(): void {
|
|
91
|
+
sessionTrajectoryMap.clear();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function antigravityRequestEnvelope(
|
|
95
|
+
wireModelId: string,
|
|
96
|
+
optionsOrIsClaude: boolean | AntigravityEnvelopeOptions = false,
|
|
97
|
+
): { requestId: string; sessionId: string; labels: Record<string, string> } {
|
|
98
|
+
const options: AntigravityEnvelopeOptions =
|
|
99
|
+
typeof optionsOrIsClaude === "boolean" ? { isClaude: optionsOrIsClaude } : optionsOrIsClaude;
|
|
100
|
+
|
|
101
|
+
const isClaude = Boolean(options.isClaude);
|
|
102
|
+
const isNonGemini = Boolean(options.isNonGemini || isClaude);
|
|
103
|
+
const step = Math.max(1, options.step ?? 1);
|
|
104
|
+
const lastStepIndex = options.lastStepIndex ?? String(Math.max(0, step - 1));
|
|
105
|
+
const requestIndex = options.requestIndex ?? options.userTurnIndex ?? Math.max(0, step - 1);
|
|
106
|
+
const agentId = options.conversationId || crypto.randomUUID();
|
|
107
|
+
const trajectoryId = options.trajectoryId || crypto.randomUUID();
|
|
108
|
+
const bytes = crypto.getRandomValues(new Uint8Array(8));
|
|
109
|
+
const sessionId = String(new DataView(bytes.buffer, bytes.byteOffset, 8).getBigInt64(0, true));
|
|
110
|
+
|
|
111
|
+
const claudeLabel = isClaude ? "true" : "false";
|
|
112
|
+
const nonGeminiLabel = isNonGemini ? "true" : "false";
|
|
113
|
+
|
|
114
|
+
const labels: Record<string, string> = {
|
|
115
|
+
last_step_index: lastStepIndex,
|
|
116
|
+
request_id: `${trajectoryId}-${requestIndex}`,
|
|
117
|
+
trajectory_id: trajectoryId,
|
|
118
|
+
used_claude: claudeLabel,
|
|
119
|
+
used_claude_conservative: claudeLabel,
|
|
120
|
+
used_non_gemini_model: nonGeminiLabel,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const modelEnum = getModelEnum(wireModelId);
|
|
124
|
+
if (modelEnum) {
|
|
125
|
+
labels.model_enum = modelEnum;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
requestId: `agent/${agentId}/${Date.now()}/${trajectoryId}/${step}`,
|
|
130
|
+
sessionId,
|
|
131
|
+
labels,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -4,6 +4,10 @@ const SECRET_PATTERNS: RegExp[] = [
|
|
|
4
4
|
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
5
5
|
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/gi,
|
|
6
6
|
/\bBearer\s+[A-Za-z0-9._~+/=-]+\b/gi,
|
|
7
|
+
/\bya29\.[A-Za-z0-9._~+/-]+=*/g,
|
|
8
|
+
/\b1\/\/[A-Za-z0-9_-]+/g,
|
|
9
|
+
/\b1\/[A-Za-z0-9_-]{20,}/g,
|
|
10
|
+
/("?(?:access_token|refresh_token|id_token)"?\s*[:=]\s*")[^"]*(")/gi,
|
|
7
11
|
/\bKXM_[A-Z0-9_]*(TOKEN|SECRET|KEY)[A-Z0-9_]*=\S+/gi,
|
|
8
12
|
/\b(GITHUB_TOKEN|GH_TOKEN|KXM_AUTH_TOKEN|KXM_WORKFLOW_SIGNAL_SECRET)=\S+/gi,
|
|
9
13
|
/\b[A-Fa-f0-9]{64}\b/g,
|