@bitkyc08/opencodex 2.15.1 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/{index-CMCDkQ7U.js → index-CZwbOse7.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +2 -0
- package/src/adapters/google-antigravity-replay.ts +9 -1
- package/src/adapters/kiro-thinking.ts +8 -0
- package/src/adapters/kiro.ts +45 -42
- package/src/adapters/openai-chat.ts +5 -2
- package/src/adapters/openai-responses.ts +5 -1
- package/src/cli/dispatch.ts +6 -3
- package/src/cli/index.ts +1 -0
- package/src/generated/compatibility-version.json +48 -24
- package/src/integrations/config-io.ts +119 -1
- package/src/integrations/omp-yaml-source.ts +6 -1
- package/src/integrations/serialize.ts +80 -1
- package/src/integrations/state.ts +37 -6
- package/src/integrations/writer.ts +11 -3
- package/src/lab/automation/orchestrator.ts +19 -0
- package/src/lib/lab-activation.ts +161 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/translator-budget.ts +34 -0
- package/src/providers/antigravity-models.ts +65 -10
- package/src/routing/compatibility/assemble.ts +21 -107
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/server/index.ts +8 -17
- package/src/server/lifecycle.ts +5 -3
- package/src/server/management/routing-profile-routes.ts +9 -1
- package/src/server/management-api.ts +37 -6
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/responses/core.ts +20 -20
- package/src/types.ts +10 -0
package/src/adapters/kiro.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { createKiroToolNameRegistry, fallbackToolUseId, fingerprint, invocationI
|
|
|
19
19
|
import { namespacedToolName } from "../types";
|
|
20
20
|
import {
|
|
21
21
|
isTranslatorBudgetExceededError,
|
|
22
|
+
releaseTranslatedEvent,
|
|
23
|
+
retainTranslatedEvent,
|
|
22
24
|
type TranslatorBudget,
|
|
23
25
|
} from "../lib/translator-budget";
|
|
24
26
|
import type {
|
|
@@ -636,8 +638,8 @@ export function buildKiroPayload(
|
|
|
636
638
|
|
|
637
639
|
// Stream parsing (shared by parseStream + parseResponse)
|
|
638
640
|
// CodeWhisperer GenerateAssistantResponse ALWAYS returns an AWS eventstream body (there is no
|
|
639
|
-
// non-streaming mode), so
|
|
640
|
-
//
|
|
641
|
+
// non-streaming wire mode), so the streaming bridge and non-streaming Responses path decode the
|
|
642
|
+
// same way — parseResponse just collects what parseStream yields.
|
|
641
643
|
interface KiroAttemptParseResult {
|
|
642
644
|
terminal?: AdapterEvent;
|
|
643
645
|
needsFallback?: boolean;
|
|
@@ -859,16 +861,12 @@ async function* parseKiroAttempt(
|
|
|
859
861
|
);
|
|
860
862
|
let handedOff = false;
|
|
861
863
|
try {
|
|
862
|
-
|
|
863
|
-
while (!next.done) {
|
|
864
|
-
yield next.value;
|
|
865
|
-
next = await attempt.next();
|
|
866
|
-
}
|
|
864
|
+
const result = yield* attempt;
|
|
867
865
|
for (const event of deferred.splice(0)) {
|
|
868
866
|
try { yield event; } finally { retention.releaseEvent(event); }
|
|
869
867
|
}
|
|
870
868
|
handedOff = true;
|
|
871
|
-
return { ...
|
|
869
|
+
return { ...result, releaseRetained: () => retention.releaseAll() };
|
|
872
870
|
} finally {
|
|
873
871
|
if (!handedOff) retention.releaseAll();
|
|
874
872
|
}
|
|
@@ -897,6 +895,12 @@ async function* parseKiroAttemptEvents(
|
|
|
897
895
|
}
|
|
898
896
|
|
|
899
897
|
let open: { id: string; name: string; chunks: string[]; completion: boolean } | null = null;
|
|
898
|
+
let openCallId: string | undefined;
|
|
899
|
+
const closeOpenCall = () => {
|
|
900
|
+
if (!openCallId) return;
|
|
901
|
+
budget.closeCall(openCallId);
|
|
902
|
+
openCallId = undefined;
|
|
903
|
+
};
|
|
900
904
|
let outputChars = "";
|
|
901
905
|
let outputCharsBytes = 0;
|
|
902
906
|
let contextUsagePercentage: number | undefined;
|
|
@@ -1109,7 +1113,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1109
1113
|
if (!open) return { events: [] };
|
|
1110
1114
|
const tool = open;
|
|
1111
1115
|
open = null;
|
|
1112
|
-
|
|
1116
|
+
closeOpenCall();
|
|
1113
1117
|
const input = tool.chunks.join("");
|
|
1114
1118
|
if (!isCompleteKiroToolInput(input)) {
|
|
1115
1119
|
return { events: [], terminal: protocolTerminal(kiroTruncationErrorMessage("incomplete tool input JSON"), tool.completion) };
|
|
@@ -1221,11 +1225,12 @@ async function* parseKiroAttemptEvents(
|
|
|
1221
1225
|
if (started.terminal) return { assistantText, sawReasoning, terminal: started.terminal };
|
|
1222
1226
|
open = started.tool!;
|
|
1223
1227
|
budget.openCall(open.id);
|
|
1228
|
+
openCallId = open.id;
|
|
1224
1229
|
} else if (
|
|
1225
1230
|
(ev.toolUseId && ev.toolUseId !== open.id)
|
|
1226
1231
|
|| (ev.name && open.name !== "unknown" && ev.name !== open.name)
|
|
1227
1232
|
) {
|
|
1228
|
-
|
|
1233
|
+
closeOpenCall();
|
|
1229
1234
|
open = null;
|
|
1230
1235
|
return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage("tool input changed identity before stop")) };
|
|
1231
1236
|
}
|
|
@@ -1489,7 +1494,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1489
1494
|
};
|
|
1490
1495
|
} catch (err) {
|
|
1491
1496
|
if (isTranslatorBudgetExceededError(err)) {
|
|
1492
|
-
|
|
1497
|
+
closeOpenCall();
|
|
1493
1498
|
return {
|
|
1494
1499
|
assistantText,
|
|
1495
1500
|
sawReasoning,
|
|
@@ -1531,6 +1536,9 @@ async function* parseKiroAttemptEvents(
|
|
|
1531
1536
|
usage: usage(),
|
|
1532
1537
|
},
|
|
1533
1538
|
};
|
|
1539
|
+
} finally {
|
|
1540
|
+
thinking.dispose();
|
|
1541
|
+
closeOpenCall();
|
|
1534
1542
|
}
|
|
1535
1543
|
}
|
|
1536
1544
|
|
|
@@ -1547,7 +1555,7 @@ export async function* parseKiroStream(
|
|
|
1547
1555
|
contextInputEstimate?: number,
|
|
1548
1556
|
): AsyncGenerator<AdapterEvent> {
|
|
1549
1557
|
const contextWindowState: KiroContextWindowState = { value: contextWindow };
|
|
1550
|
-
const
|
|
1558
|
+
const firstResult = yield* parseKiroAttempt(
|
|
1551
1559
|
response,
|
|
1552
1560
|
budget,
|
|
1553
1561
|
completionMode,
|
|
@@ -1559,12 +1567,6 @@ export async function* parseKiroStream(
|
|
|
1559
1567
|
contextInputEstimate,
|
|
1560
1568
|
false,
|
|
1561
1569
|
);
|
|
1562
|
-
let firstNext = await first.next();
|
|
1563
|
-
while (!firstNext.done) {
|
|
1564
|
-
yield firstNext.value;
|
|
1565
|
-
firstNext = await first.next();
|
|
1566
|
-
}
|
|
1567
|
-
const firstResult = firstNext.value;
|
|
1568
1570
|
try {
|
|
1569
1571
|
if (!firstResult.needsFallback) {
|
|
1570
1572
|
if (firstResult.terminal) yield firstResult.terminal;
|
|
@@ -1642,7 +1644,7 @@ export async function* parseKiroStream(
|
|
|
1642
1644
|
return;
|
|
1643
1645
|
}
|
|
1644
1646
|
|
|
1645
|
-
const
|
|
1647
|
+
const secondResult = yield* parseKiroAttempt(
|
|
1646
1648
|
fallback.response,
|
|
1647
1649
|
budget,
|
|
1648
1650
|
"text_fallback",
|
|
@@ -1656,12 +1658,6 @@ export async function* parseKiroStream(
|
|
|
1656
1658
|
// A zero-output transport failure here must stay non-retryable to avoid duplicating that text.
|
|
1657
1659
|
priorEmittedOutput,
|
|
1658
1660
|
);
|
|
1659
|
-
let secondNext = await second.next();
|
|
1660
|
-
while (!secondNext.done) {
|
|
1661
|
-
yield secondNext.value;
|
|
1662
|
-
secondNext = await second.next();
|
|
1663
|
-
}
|
|
1664
|
-
const secondResult = secondNext.value;
|
|
1665
1661
|
try {
|
|
1666
1662
|
if (!secondResult.terminal) {
|
|
1667
1663
|
yield retryableKiroIncomplete(
|
|
@@ -1909,25 +1905,32 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1909
1905
|
return safeKiroHttpErrorMessage(status, headers, payloadText);
|
|
1910
1906
|
},
|
|
1911
1907
|
|
|
1912
|
-
//
|
|
1913
|
-
//
|
|
1914
|
-
//
|
|
1915
|
-
// tool failed with "web-search sidecar requires a non-streaming adapter" (kiro-only).
|
|
1908
|
+
// Kiro always returns an event stream, including for non-streaming Responses requests. Drain
|
|
1909
|
+
// the decoder into a budget-owned batch so an upstream stream cannot grow this array without
|
|
1910
|
+
// bound while the caller waits for the complete JSON response.
|
|
1916
1911
|
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
1917
1912
|
const events: AdapterEvent[] = [];
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1913
|
+
try {
|
|
1914
|
+
for await (const e of parseKiroStream(
|
|
1915
|
+
response,
|
|
1916
|
+
budget,
|
|
1917
|
+
modelId,
|
|
1918
|
+
inputTokens,
|
|
1919
|
+
contextWindow,
|
|
1920
|
+
toolNameMap,
|
|
1921
|
+
conversationId,
|
|
1922
|
+
completionMode,
|
|
1923
|
+
completionMode === "required" ? fallbackFactory : undefined,
|
|
1924
|
+
contextInputEstimate,
|
|
1925
|
+
)) {
|
|
1926
|
+
retainTranslatedEvent(e, budget, events.at(-1));
|
|
1927
|
+
events.push(e);
|
|
1928
|
+
}
|
|
1929
|
+
return events;
|
|
1930
|
+
} catch (error) {
|
|
1931
|
+
for (const event of events) releaseTranslatedEvent(event, budget);
|
|
1932
|
+
throw error;
|
|
1933
|
+
}
|
|
1931
1934
|
},
|
|
1932
1935
|
};
|
|
1933
1936
|
}
|
|
@@ -968,8 +968,11 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
968
968
|
if (provider.parallelToolCalls === false) {
|
|
969
969
|
// NIM documents the Boolean defaulting to false and kimi rejects true; pin the
|
|
970
970
|
// wire bit so Codex cannot opt in via request.options. Other opted-out providers
|
|
971
|
-
// omit the field so strict OpenAI-compatible hosts never see an
|
|
972
|
-
|
|
971
|
+
// omit the field by default so strict OpenAI-compatible hosts never see an
|
|
972
|
+
// unsupported knob, but a self-hosted gateway that DOES honor the field and keeps
|
|
973
|
+
// emitting parallel calls without it can opt in via pinParallelToolCallsFalse.
|
|
974
|
+
if (provider.baseUrl === "https://integrate.api.nvidia.com/v1"
|
|
975
|
+
|| provider.pinParallelToolCallsFalse === true) {
|
|
973
976
|
body.parallel_tool_calls = false;
|
|
974
977
|
}
|
|
975
978
|
} else if (provider.parallelToolCalls === true) {
|
|
@@ -1361,6 +1361,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1361
1361
|
}
|
|
1362
1362
|
|
|
1363
1363
|
const forward = provider.authMode === "forward";
|
|
1364
|
+
let convertedRoutedCustomToolNames: Set<string> | undefined;
|
|
1364
1365
|
const unexpandedMiss = !!parsed.previousResponseId && parsed._previousResponseInputExpanded !== true;
|
|
1365
1366
|
let outBody = stripPreviousResponseId(
|
|
1366
1367
|
parsed._rawBody,
|
|
@@ -1408,7 +1409,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1408
1409
|
outBody = promoteClientLoadedTools(outBody);
|
|
1409
1410
|
}
|
|
1410
1411
|
if (provider.authMode !== "forward") {
|
|
1411
|
-
|
|
1412
|
+
const rewritten = rewriteRoutedCustomToolsForUpstream(outBody);
|
|
1413
|
+
outBody = rewritten.body;
|
|
1414
|
+
convertedRoutedCustomToolNames = rewritten.names;
|
|
1412
1415
|
}
|
|
1413
1416
|
const sanitizedBody = normalizeToolSchemas(stripSparkCompatibility(stripUnsupportedReasoningParams(stripItemIdsWhenUnstored(stripInvalidItemIds(stripUnsupportedHostedTools(sanitizeReasoningInputContent(scrubOcxCompactionItems(outBody), { preserveRawReasoningContent: provider.preserveResponsesReasoningContent === true })))))));
|
|
1414
1417
|
const body = JSON.stringify(stripDisabledReasoningSummaries(
|
|
@@ -1426,6 +1429,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1426
1429
|
headers,
|
|
1427
1430
|
body,
|
|
1428
1431
|
releaseBodyObservation,
|
|
1432
|
+
...(convertedRoutedCustomToolNames ? { convertedRoutedCustomToolNames } : {}),
|
|
1429
1433
|
};
|
|
1430
1434
|
},
|
|
1431
1435
|
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -21,7 +21,6 @@ import { restoreNativeCodexAsync } from "../codex/inject";
|
|
|
21
21
|
import { stripGrokConfig } from "../grok/inject";
|
|
22
22
|
import { afterCatalogWriteHandleAppServers } from "../codex/app-server-processes";
|
|
23
23
|
import { normalizeUpdateChannel, runGuiUpdateWorker } from "../update/job";
|
|
24
|
-
import { serviceCommand } from "../service";
|
|
25
24
|
|
|
26
25
|
export interface CliDispatchDeps {
|
|
27
26
|
args: string[];
|
|
@@ -45,6 +44,7 @@ export interface CliDispatchDeps {
|
|
|
45
44
|
handleStatus: () => Promise<void>;
|
|
46
45
|
handleRecoverHistory: () => Promise<void>;
|
|
47
46
|
handleReady: (args: ReadyArgs) => Promise<number>;
|
|
47
|
+
serviceCommand: (...args: string[]) => Promise<void>;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
type CommandRunner = (deps: CliDispatchDeps) => Promise<number>;
|
|
@@ -261,8 +261,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
261
261
|
return 0;
|
|
262
262
|
},
|
|
263
263
|
service: async deps => {
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
process.exitCode = 0;
|
|
265
|
+
await deps.serviceCommand(...deps.args.slice(1));
|
|
266
|
+
// serviceCommand uses process.exitCode for recoverable install/stop failures
|
|
267
|
+
// that must finish cleanup before the single top-level process.exit runs.
|
|
268
|
+
return Number(process.exitCode ?? 0);
|
|
266
269
|
},
|
|
267
270
|
tray: async deps => {
|
|
268
271
|
const { windowsTrayCommand } = await import("../tray/windows");
|
package/src/cli/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"path": "package.json",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "5c2d417ce3cb466651385bca4d8fee522e0bdf1c6d8c2fc1f84b4cf54574aee1"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "scripts/model-metadata.source.json",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"path": "src/adapters/base.ts",
|
|
45
|
-
"sha256": "
|
|
45
|
+
"sha256": "f20fdca89cd4bac4ec049ab46e200f2aa6435dae548c5994b7edf5b6d73f1bd5"
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
"path": "src/adapters/client-fingerprint.ts",
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
"path": "src/adapters/google-antigravity-replay.ts",
|
|
185
|
-
"sha256": "
|
|
185
|
+
"sha256": "52704bcd65eab346025e6faffe70d0c1d07e39651e4139f34bc67759fbaa4f08"
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
"path": "src/adapters/google-antigravity-wire.ts",
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
},
|
|
243
243
|
{
|
|
244
244
|
"path": "src/adapters/kiro-thinking.ts",
|
|
245
|
-
"sha256": "
|
|
245
|
+
"sha256": "c7093dd20dc16156aee4d6a37de0b71f6e646c09f2ba99be8f3c6a388703a981"
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
"path": "src/adapters/kiro-tool-fallback.ts",
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
},
|
|
263
263
|
{
|
|
264
264
|
"path": "src/adapters/kiro.ts",
|
|
265
|
-
"sha256": "
|
|
265
|
+
"sha256": "fe61e9e4f8d7eada8baab90e09e9b2b03641c114eabe7846f2947cb0667996f1"
|
|
266
266
|
},
|
|
267
267
|
{
|
|
268
268
|
"path": "src/adapters/mimo-free.ts",
|
|
@@ -274,7 +274,7 @@
|
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
276
|
"path": "src/adapters/openai-chat.ts",
|
|
277
|
-
"sha256": "
|
|
277
|
+
"sha256": "c87e922e32ccf8c224891e9a3a42e86139eb7db8d97b9c46613f01a132afc6fa"
|
|
278
278
|
},
|
|
279
279
|
{
|
|
280
280
|
"path": "src/adapters/openai-responses-url.ts",
|
|
@@ -282,7 +282,7 @@
|
|
|
282
282
|
},
|
|
283
283
|
{
|
|
284
284
|
"path": "src/adapters/openai-responses.ts",
|
|
285
|
-
"sha256": "
|
|
285
|
+
"sha256": "8d4809ac4228beea1fa08716cd43c9ab7a87fdeaed5f30f39336ee808375b98d"
|
|
286
286
|
},
|
|
287
287
|
{
|
|
288
288
|
"path": "src/adapters/run-turn-queue.ts",
|
|
@@ -446,7 +446,7 @@
|
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
448
|
"path": "src/cli/dispatch.ts",
|
|
449
|
-
"sha256": "
|
|
449
|
+
"sha256": "478bd9539c26322ec5365e80f44674d507c708ccff6d09605a6e7804abedee79"
|
|
450
450
|
},
|
|
451
451
|
{
|
|
452
452
|
"path": "src/cli/doctor.ts",
|
|
@@ -462,7 +462,7 @@
|
|
|
462
462
|
},
|
|
463
463
|
{
|
|
464
464
|
"path": "src/cli/index.ts",
|
|
465
|
-
"sha256": "
|
|
465
|
+
"sha256": "63e8747afb606295340bd30598d7112b75efb2a89353234a874df42c5fc7e1a7"
|
|
466
466
|
},
|
|
467
467
|
{
|
|
468
468
|
"path": "src/cli/init.ts",
|
|
@@ -1038,7 +1038,7 @@
|
|
|
1038
1038
|
},
|
|
1039
1039
|
{
|
|
1040
1040
|
"path": "src/integrations/config-io.ts",
|
|
1041
|
-
"sha256": "
|
|
1041
|
+
"sha256": "5271736a6499bc4bcb68bde2ccefd8ef8ce7953bddf13e764fb1b9ee66433fe3"
|
|
1042
1042
|
},
|
|
1043
1043
|
{
|
|
1044
1044
|
"path": "src/integrations/journal.ts",
|
|
@@ -1054,7 +1054,7 @@
|
|
|
1054
1054
|
},
|
|
1055
1055
|
{
|
|
1056
1056
|
"path": "src/integrations/omp-yaml-source.ts",
|
|
1057
|
-
"sha256": "
|
|
1057
|
+
"sha256": "58a8769edc6c1f409f2851955e52dba3cdb5e30938d6b7dc19e59b900731d219"
|
|
1058
1058
|
},
|
|
1059
1059
|
{
|
|
1060
1060
|
"path": "src/integrations/ownership.ts",
|
|
@@ -1066,11 +1066,11 @@
|
|
|
1066
1066
|
},
|
|
1067
1067
|
{
|
|
1068
1068
|
"path": "src/integrations/serialize.ts",
|
|
1069
|
-
"sha256": "
|
|
1069
|
+
"sha256": "0560601276b25d82eaf6e5460c815e0a67ecb4cf1cf6e378952c60dedfb333e6"
|
|
1070
1070
|
},
|
|
1071
1071
|
{
|
|
1072
1072
|
"path": "src/integrations/state.ts",
|
|
1073
|
-
"sha256": "
|
|
1073
|
+
"sha256": "e2446f333ab3b57eebc120bf674f37aff6210cde7cf45628e7ff0ca77ca4d4f0"
|
|
1074
1074
|
},
|
|
1075
1075
|
{
|
|
1076
1076
|
"path": "src/integrations/store.ts",
|
|
@@ -1078,7 +1078,7 @@
|
|
|
1078
1078
|
},
|
|
1079
1079
|
{
|
|
1080
1080
|
"path": "src/integrations/writer.ts",
|
|
1081
|
-
"sha256": "
|
|
1081
|
+
"sha256": "92cc7c85a6bffa520f0fce57890f45758c712f69a128451be60368444d5ff725"
|
|
1082
1082
|
},
|
|
1083
1083
|
{
|
|
1084
1084
|
"path": "src/lab/artifacts/sanitize.ts",
|
|
@@ -1118,7 +1118,7 @@
|
|
|
1118
1118
|
},
|
|
1119
1119
|
{
|
|
1120
1120
|
"path": "src/lab/automation/orchestrator.ts",
|
|
1121
|
-
"sha256": "
|
|
1121
|
+
"sha256": "5f10d45593ea6464f6ecd1db6ee922bc7627fc6578fe6f3f93b7a35c1b479df8"
|
|
1122
1122
|
},
|
|
1123
1123
|
{
|
|
1124
1124
|
"path": "src/lab/automation/persistence.ts",
|
|
@@ -1548,6 +1548,10 @@
|
|
|
1548
1548
|
"path": "src/lib/injection-debug-log.ts",
|
|
1549
1549
|
"sha256": "ab3b8e53c546c0ff43dc129d29c0b11c2d258f86e9d12475d87bf55510eb02c9"
|
|
1550
1550
|
},
|
|
1551
|
+
{
|
|
1552
|
+
"path": "src/lib/lab-activation.ts",
|
|
1553
|
+
"sha256": "7cdfb758543a789a75354ebfba730c516d264a4d6a10648456f57e2654034dec"
|
|
1554
|
+
},
|
|
1551
1555
|
{
|
|
1552
1556
|
"path": "src/lib/lab-live-execution-authority.ts",
|
|
1553
1557
|
"sha256": "7a4c488521b861ad70a87f560d85cb5c9f552e16493c5d44e19734947b4ac714"
|
|
@@ -1564,6 +1568,10 @@
|
|
|
1564
1568
|
"path": "src/lib/lab-live-route-production.ts",
|
|
1565
1569
|
"sha256": "fb890cceb6390cd2d846c86c97e0d490ea8b58783cd7410d032b6d8573a07f49"
|
|
1566
1570
|
},
|
|
1571
|
+
{
|
|
1572
|
+
"path": "src/lib/lab-passive-linker-registration.ts",
|
|
1573
|
+
"sha256": "5f88d3428f87f1773f944e43d812356b96e881dcb8bd13d34baf504ce509e54c"
|
|
1574
|
+
},
|
|
1567
1575
|
{
|
|
1568
1576
|
"path": "src/lib/local-management-attestation.ts",
|
|
1569
1577
|
"sha256": "1bccf8f85ecd89cb017224024ca3d199d97c8ce88f5ac936dbc1419845a77f6e"
|
|
@@ -1580,6 +1588,10 @@
|
|
|
1580
1588
|
"path": "src/lib/open-url.ts",
|
|
1581
1589
|
"sha256": "a6db7ec7181ccb4e95411b2c7499fdd00f3dd48bebcdaca099da086c0dae2e59"
|
|
1582
1590
|
},
|
|
1591
|
+
{
|
|
1592
|
+
"path": "src/lib/optional-shutdown-hooks.ts",
|
|
1593
|
+
"sha256": "ba1fb3f46ddd1ad7d1cf90e22ae8df5dd2969dbb99449beaf8fac3100425a908"
|
|
1594
|
+
},
|
|
1583
1595
|
{
|
|
1584
1596
|
"path": "src/lib/pinned-http.ts",
|
|
1585
1597
|
"sha256": "27a69c4eac63db31ef754c84d7f5f2e2949221b5fe27e066420f91b40fcc51d7"
|
|
@@ -1658,7 +1670,7 @@
|
|
|
1658
1670
|
},
|
|
1659
1671
|
{
|
|
1660
1672
|
"path": "src/lib/translator-budget.ts",
|
|
1661
|
-
"sha256": "
|
|
1673
|
+
"sha256": "4d79fed2d10e23b87af2c67ea0a979ce580eb232ac35e1f6851b5f02ef5a07d7"
|
|
1662
1674
|
},
|
|
1663
1675
|
{
|
|
1664
1676
|
"path": "src/lib/upstream-reachability.ts",
|
|
@@ -1822,7 +1834,7 @@
|
|
|
1822
1834
|
},
|
|
1823
1835
|
{
|
|
1824
1836
|
"path": "src/providers/antigravity-models.ts",
|
|
1825
|
-
"sha256": "
|
|
1837
|
+
"sha256": "cbe65f34a127ad44fd8f6bd2214553b238449b4f10d948d5ed67f4b163964874"
|
|
1826
1838
|
},
|
|
1827
1839
|
{
|
|
1828
1840
|
"path": "src/providers/api-keys.ts",
|
|
@@ -1990,7 +2002,7 @@
|
|
|
1990
2002
|
},
|
|
1991
2003
|
{
|
|
1992
2004
|
"path": "src/routing/compatibility/assemble.ts",
|
|
1993
|
-
"sha256": "
|
|
2005
|
+
"sha256": "186010fe58a02823530819cf7bbf06cefdbb3224850ea8321736d1017f32783f"
|
|
1994
2006
|
},
|
|
1995
2007
|
{
|
|
1996
2008
|
"path": "src/routing/compatibility/behavior.ts",
|
|
@@ -2004,10 +2016,18 @@
|
|
|
2004
2016
|
"path": "src/routing/compatibility/endpoint.ts",
|
|
2005
2017
|
"sha256": "38578440e02ce712f8d84fa97ec2d1bc3396f31983be9b6e5b0010ef6edd146b"
|
|
2006
2018
|
},
|
|
2019
|
+
{
|
|
2020
|
+
"path": "src/routing/compatibility/lab-evidence-provider.ts",
|
|
2021
|
+
"sha256": "352a65b6d95c8e6eaa7e6f81fca49284b49a9ddf7bd1895be192b858784fd861"
|
|
2022
|
+
},
|
|
2007
2023
|
{
|
|
2008
2024
|
"path": "src/routing/compatibility/policy.ts",
|
|
2009
2025
|
"sha256": "d903d561ef2ae16c86d16c7ec157556ee0c78557aaa7e83dad46c1abc0e6530c"
|
|
2010
2026
|
},
|
|
2027
|
+
{
|
|
2028
|
+
"path": "src/routing/compatibility/provider-slot.ts",
|
|
2029
|
+
"sha256": "809c7b5dbe13b6e90d5abf51cadf33825f8d82b5fd1c7355d9ef25b736371bdc"
|
|
2030
|
+
},
|
|
2011
2031
|
{
|
|
2012
2032
|
"path": "src/routing/compatibility/reader.ts",
|
|
2013
2033
|
"sha256": "c48943c0b660609bbdf4d47896de6338cd46b2c9144f1c916646221fe437a678"
|
|
@@ -2114,11 +2134,11 @@
|
|
|
2114
2134
|
},
|
|
2115
2135
|
{
|
|
2116
2136
|
"path": "src/server/index.ts",
|
|
2117
|
-
"sha256": "
|
|
2137
|
+
"sha256": "cc6461e629f1f5e373d81ebee0720a9ba85c7deedded457bbd398ac962e0e404"
|
|
2118
2138
|
},
|
|
2119
2139
|
{
|
|
2120
2140
|
"path": "src/server/lifecycle.ts",
|
|
2121
|
-
"sha256": "
|
|
2141
|
+
"sha256": "02d0691de03c510419c971341a7015507491599b9a91bec369fd7144f85b05a3"
|
|
2122
2142
|
},
|
|
2123
2143
|
{
|
|
2124
2144
|
"path": "src/server/live.ts",
|
|
@@ -2134,7 +2154,7 @@
|
|
|
2134
2154
|
},
|
|
2135
2155
|
{
|
|
2136
2156
|
"path": "src/server/management-api.ts",
|
|
2137
|
-
"sha256": "
|
|
2157
|
+
"sha256": "0a513030e2b730393d86c87ad237f1e9f35e64586d8736c91c69fed6aaabb60d"
|
|
2138
2158
|
},
|
|
2139
2159
|
{
|
|
2140
2160
|
"path": "src/server/management-auth.ts",
|
|
@@ -2214,7 +2234,7 @@
|
|
|
2214
2234
|
},
|
|
2215
2235
|
{
|
|
2216
2236
|
"path": "src/server/management/routing-profile-routes.ts",
|
|
2217
|
-
"sha256": "
|
|
2237
|
+
"sha256": "08484dcc92a3672450122316dad9939707a2669eabf396084bfb93fa9dde35f9"
|
|
2218
2238
|
},
|
|
2219
2239
|
{
|
|
2220
2240
|
"path": "src/server/management/shared.ts",
|
|
@@ -2248,6 +2268,10 @@
|
|
|
2248
2268
|
"path": "src/server/memory-watchdog.ts",
|
|
2249
2269
|
"sha256": "d2f7f5cd03316cdfb0bd23abd77b4c28d87986a9a1cb9e0f53e3890695f07f4c"
|
|
2250
2270
|
},
|
|
2271
|
+
{
|
|
2272
|
+
"path": "src/server/passive-route-linker.ts",
|
|
2273
|
+
"sha256": "6a20d1e864efe80cd9d6c69df9616bc6092a6102cd8d339f0cb376f8821bed0b"
|
|
2274
|
+
},
|
|
2251
2275
|
{
|
|
2252
2276
|
"path": "src/server/port-reclaim.ts",
|
|
2253
2277
|
"sha256": "5b1a9e7e5e3d752ee7dd0dca1ea8737eeaf7945e2c9a4e3a0aed9805cc72d253"
|
|
@@ -2334,7 +2358,7 @@
|
|
|
2334
2358
|
},
|
|
2335
2359
|
{
|
|
2336
2360
|
"path": "src/server/responses/core.ts",
|
|
2337
|
-
"sha256": "
|
|
2361
|
+
"sha256": "0817eb37ba0ce9425afd9b9794eccab1095b7563d217652562cb281479f1df48"
|
|
2338
2362
|
},
|
|
2339
2363
|
{
|
|
2340
2364
|
"path": "src/server/responses/encrypted-payload.ts",
|
|
@@ -2482,7 +2506,7 @@
|
|
|
2482
2506
|
},
|
|
2483
2507
|
{
|
|
2484
2508
|
"path": "src/types.ts",
|
|
2485
|
-
"sha256": "
|
|
2509
|
+
"sha256": "33f4ee11228bb8faca03c57fdd5000512f70153c2fd446f88d78b0a43056d330"
|
|
2486
2510
|
},
|
|
2487
2511
|
{
|
|
2488
2512
|
"path": "src/update/badge.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
11
11
|
import type { ConfigFormat } from "../clients/config-export";
|
|
12
|
+
import { MAX_JSON_NESTING } from "./serialize";
|
|
12
13
|
import { atomicWriteFile } from "../config";
|
|
13
14
|
import type { JournalEntry } from "./journal";
|
|
14
15
|
import type { OwnershipRecord } from "./ownership";
|
|
@@ -23,12 +24,129 @@ import type { IntegrationClientId } from "./registry";
|
|
|
23
24
|
*/
|
|
24
25
|
export const PARSE_FAILED = Symbol("parse-failed");
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* What `JSON.parse` has already discarded by the time we hold the parsed value,
|
|
29
|
+
* and a rewrite would therefore silently change. Both classes are invisible in
|
|
30
|
+
* the parsed object, which is why this scans the RAW text — same reasoning as
|
|
31
|
+
* the TOML inf/nan guard below.
|
|
32
|
+
*
|
|
33
|
+
* Numbers: `1e999` overflows to Infinity (a rewrite bakes in `null` — the merge
|
|
34
|
+
* layer's JSON clone does it even before the serializer could refuse), `1e-9999`
|
|
35
|
+
* underflows to `+0`, an integer literal may have been rounded (a rewrite then
|
|
36
|
+
* hands consumers that read JSON integers exactly — python, jq, BigInt revivers
|
|
37
|
+
* — a different value), and `-0` re-serializes as `0`. Only literals whose value
|
|
38
|
+
* actually changed are refused: `1e21`, `1e-320` or 2^54 round-trip exactly and
|
|
39
|
+
* stay usable.
|
|
40
|
+
*
|
|
41
|
+
* Duplicate members: `{"a":1,"a":2}` parses to a single `a`, so rewriting the
|
|
42
|
+
* document DELETES the earlier member. That is content loss, not the formatting
|
|
43
|
+
* normalization we promise, and this classifier is what makes the rewrite of a
|
|
44
|
+
* user-edited file reachable at all — so it fails closed here.
|
|
45
|
+
*
|
|
46
|
+
* Depth: the same pass counts container nesting against MAX_JSON_NESTING
|
|
47
|
+
* (shared with the serializer, see serialize.ts). JSON.parse handles hundreds
|
|
48
|
+
* of thousands of levels iteratively, but the downstream merge and
|
|
49
|
+
* JSON.stringify recurse — a 100KB file nested 50k deep sailed through parse
|
|
50
|
+
* and guard, then blew up serialization with a raw RangeError after a
|
|
51
|
+
* multi-GB allocation spike. A hand-written scan rather than a JSON.parse
|
|
52
|
+
* source-access reviver on purpose: the reviver walk recurses internally, so
|
|
53
|
+
* its depth limit would be an unpredictable stack size instead of this
|
|
54
|
+
* deterministic ceiling.
|
|
55
|
+
*/
|
|
56
|
+
function jsonTextSafeToRewrite(text: string): boolean {
|
|
57
|
+
/**
|
|
58
|
+
* One frame per open container; a Set of member names for objects, null for
|
|
59
|
+
* arrays. Its length is the current nesting depth.
|
|
60
|
+
*/
|
|
61
|
+
const containers: Array<Set<string> | null> = [];
|
|
62
|
+
/** The most recent string literal — the member name if a `:` follows. */
|
|
63
|
+
let lastString: string | null = null;
|
|
64
|
+
let inString = false;
|
|
65
|
+
let escaped = false;
|
|
66
|
+
let stringStart = 0;
|
|
67
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
68
|
+
const ch = text[i]!;
|
|
69
|
+
if (inString) {
|
|
70
|
+
if (escaped) escaped = false;
|
|
71
|
+
else if (ch === "\\") escaped = true;
|
|
72
|
+
else if (ch === "\"") {
|
|
73
|
+
inString = false;
|
|
74
|
+
lastString = text.slice(stringStart, i + 1);
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (ch === "\"") { inString = true; stringStart = i; continue; }
|
|
79
|
+
if (ch === "{" || ch === "[") {
|
|
80
|
+
containers.push(ch === "{" ? new Set<string>() : null);
|
|
81
|
+
if (containers.length > MAX_JSON_NESTING) return false;
|
|
82
|
+
lastString = null;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === "}" || ch === "]") { containers.pop(); lastString = null; continue; }
|
|
86
|
+
if (ch === ":") {
|
|
87
|
+
const members = containers[containers.length - 1];
|
|
88
|
+
if (members && lastString !== null) {
|
|
89
|
+
/*
|
|
90
|
+
* Decoded, not raw: `"a"` and `"a"` are spellings of ONE member,
|
|
91
|
+
* and JSON.parse keeps only the last of them.
|
|
92
|
+
*/
|
|
93
|
+
let name: string;
|
|
94
|
+
try { name = JSON.parse(lastString) as string; } catch { return false; }
|
|
95
|
+
if (members.has(name)) return false;
|
|
96
|
+
members.add(name);
|
|
97
|
+
}
|
|
98
|
+
lastString = null;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (ch !== "-" && (ch < "0" || ch > "9")) continue;
|
|
102
|
+
let end = i + 1;
|
|
103
|
+
while (end < text.length && /[0-9+\-.eE]/.test(text[end]!)) end += 1;
|
|
104
|
+
const literal = text.slice(i, end);
|
|
105
|
+
i = end - 1;
|
|
106
|
+
const value = Number(literal);
|
|
107
|
+
if (!Number.isFinite(value)) return false;
|
|
108
|
+
if (value === 0) {
|
|
109
|
+
/*
|
|
110
|
+
* `-0` (re-serializes as `0`) and underflow: `1e-9999` is a nonzero
|
|
111
|
+
* value the parse already flattened to `+0`. The significand alone
|
|
112
|
+
* decides, so genuine zero spellings (`0`, `0.0`, `0e10`) stay usable.
|
|
113
|
+
*/
|
|
114
|
+
if (literal.startsWith("-") || /[1-9]/.test(literal.split(/[eE]/)[0]!)) return false;
|
|
115
|
+
}
|
|
116
|
+
/*
|
|
117
|
+
* Deliberately plain digit runs only. They are the one spelling real
|
|
118
|
+
* consumers read with exact integer semantics (python's json yields an
|
|
119
|
+
* arbitrary-precision int, jq preserves big integer literals), so baking
|
|
120
|
+
* in the rounded double changes what those consumers extract. Decimal or
|
|
121
|
+
* exponent spellings of the same value (`9007199254740993e0`, `…3.0`) are
|
|
122
|
+
* float semantics for every consumer — they round identically before and
|
|
123
|
+
* after a rewrite, and shortest-round-trip stringify preserves what any
|
|
124
|
+
* reader can observe, so refusing them would only manufacture dead ends
|
|
125
|
+
* (`1e308` is not exactly representable either, yet rewrites losslessly
|
|
126
|
+
* for every possible reader).
|
|
127
|
+
*/
|
|
128
|
+
const digits = literal[0] === "-" ? literal.slice(1) : literal;
|
|
129
|
+
if (/^[0-9]{16,}$/.test(digits) && BigInt(literal) !== BigInt(value)) return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
26
134
|
/** Parse a client config, tolerating absence. PARSE_FAILED on garbage. */
|
|
27
135
|
export function parseConfig(text: string | null, format: ConfigFormat): unknown | typeof PARSE_FAILED {
|
|
28
136
|
if (text === null || text.trim().length === 0) return {};
|
|
29
137
|
try {
|
|
30
138
|
switch (format) {
|
|
31
|
-
case "json":
|
|
139
|
+
case "json": {
|
|
140
|
+
/*
|
|
141
|
+
* Scanned BEFORE parsing: the guard is text-only, and refusing first
|
|
142
|
+
* means a hostile document is never materialized — the depth ceiling
|
|
143
|
+
* would otherwise cap the rewrite only after JSON.parse had already
|
|
144
|
+
* built the 50k-deep object graph. The outcome is unchanged: invalid
|
|
145
|
+
* JSON still returns PARSE_FAILED, from the catch below.
|
|
146
|
+
*/
|
|
147
|
+
if (!jsonTextSafeToRewrite(text)) return PARSE_FAILED;
|
|
148
|
+
return JSON.parse(text);
|
|
149
|
+
}
|
|
32
150
|
case "json5": return Bun.JSON5.parse(text);
|
|
33
151
|
case "yaml": return Bun.YAML.parse(text);
|
|
34
152
|
case "toml": {
|