Skip to content

Commit ad25cea

Browse files
Merge 64aa0f1 into 37c6e2d
2 parents 37c6e2d + 64aa0f1 commit ad25cea

7 files changed

Lines changed: 330 additions & 32 deletions

File tree

scripts/lib/ci-node-test-plan.mjs

Lines changed: 155 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,34 @@ function resolveCommandShardName(file) {
7171
if (name.startsWith("doctor/shared/") || name.startsWith("doctor/")) {
7272
return "agentic-commands-doctor-shared";
7373
}
74-
return "agentic-commands-doctor";
74+
if (name.startsWith("doctor-auth") || name.startsWith("doctor-claude")) {
75+
return "agentic-commands-doctor-auth";
76+
}
77+
if (name.startsWith("doctor-config") || name.startsWith("doctor-legacy-config")) {
78+
return "agentic-commands-doctor-config";
79+
}
80+
if (name.startsWith("doctor-cron")) {
81+
return "agentic-commands-doctor-cron";
82+
}
83+
if (
84+
name.startsWith("doctor-gateway") ||
85+
name.startsWith("doctor-heartbeat") ||
86+
name.startsWith("doctor-memory") ||
87+
name.startsWith("doctor-plugin") ||
88+
name.startsWith("doctor-session") ||
89+
name.startsWith("doctor-state") ||
90+
name.startsWith("doctor-workspace")
91+
) {
92+
return "agentic-commands-doctor-runtime";
93+
}
94+
if (
95+
name.startsWith("doctor-platform-notes") ||
96+
name.startsWith("doctor-sandbox") ||
97+
name.startsWith("doctor-whatsapp")
98+
) {
99+
return "agentic-commands-doctor-misc-platform";
100+
}
101+
return "agentic-commands-doctor-misc-core";
75102
}
76103
if (
77104
name.startsWith("auth-choice") ||
@@ -95,7 +122,7 @@ function createAgenticCommandSplitShards() {
95122
const commandsLightTests = new Set(commandsLightTestFiles);
96123
const groups = new Map();
97124
for (const file of listTestFiles("src/commands")) {
98-
if (commandsLightTests.has(file)) {
125+
if (commandsLightTests.has(file) || file.endsWith(".e2e.test.ts")) {
99126
continue;
100127
}
101128
const shardName = resolveCommandShardName(file);
@@ -104,7 +131,12 @@ function createAgenticCommandSplitShards() {
104131

105132
return [
106133
"agentic-commands-agent-channel",
107-
"agentic-commands-doctor",
134+
"agentic-commands-doctor-auth",
135+
"agentic-commands-doctor-config",
136+
"agentic-commands-doctor-cron",
137+
"agentic-commands-doctor-misc-core",
138+
"agentic-commands-doctor-misc-platform",
139+
"agentic-commands-doctor-runtime",
108140
"agentic-commands-doctor-shared",
109141
"agentic-commands-models",
110142
"agentic-commands-onboard-config",
@@ -119,6 +151,117 @@ function createAgenticCommandSplitShards() {
119151
.filter((shard) => shard.includePatterns.length > 0);
120152
}
121153

154+
function resolveInfraStateShardName(file) {
155+
const name = relative("src/infra", file).replaceAll("\\", "/");
156+
if (name.startsWith("approval")) {
157+
return "core-runtime-infra-approval";
158+
}
159+
if (name.startsWith("exec") || name.startsWith("system-run")) {
160+
return "core-runtime-infra-exec";
161+
}
162+
if (name.startsWith("heartbeat")) {
163+
return "core-runtime-infra-heartbeat";
164+
}
165+
if (name.startsWith("outbound/")) {
166+
return "core-runtime-infra-outbound";
167+
}
168+
if (name.startsWith("net/") || name.startsWith("fetch")) {
169+
return "core-runtime-infra-network";
170+
}
171+
if (
172+
name.startsWith("device") ||
173+
name.startsWith("node-pairing") ||
174+
name.startsWith("pairing") ||
175+
name.startsWith("push")
176+
) {
177+
return "core-runtime-infra-device-push";
178+
}
179+
if (
180+
name.startsWith("install") ||
181+
name.startsWith("npm") ||
182+
name.startsWith("package") ||
183+
name.startsWith("provider-usage") ||
184+
name.startsWith("update")
185+
) {
186+
return "core-runtime-infra-package-provider";
187+
}
188+
if (name.startsWith("session") || name.startsWith("state-migrations")) {
189+
return "core-runtime-infra-session-state";
190+
}
191+
if (name < "g") {
192+
return "core-runtime-infra-misc-a-f";
193+
}
194+
if (name.startsWith("gateway")) {
195+
return "core-runtime-infra-misc-gateway";
196+
}
197+
if (name < "m") {
198+
return "core-runtime-infra-misc-g-l";
199+
}
200+
if (name < "p") {
201+
return "core-runtime-infra-misc-m-o";
202+
}
203+
if (
204+
name.startsWith("parse") ||
205+
name.startsWith("path") ||
206+
name.startsWith("plain") ||
207+
name.startsWith("plugin") ||
208+
name.startsWith("ports") ||
209+
name.startsWith("prototype")
210+
) {
211+
return "core-runtime-infra-misc-path-ports";
212+
}
213+
if (
214+
name.startsWith("process") ||
215+
name.startsWith("replace") ||
216+
name.startsWith("resolve") ||
217+
name.startsWith("restart") ||
218+
name.startsWith("retry") ||
219+
name.startsWith("run") ||
220+
name.startsWith("runtime")
221+
) {
222+
return "core-runtime-infra-misc-process-restart";
223+
}
224+
if (name < "t") {
225+
return "core-runtime-infra-misc-s-system";
226+
}
227+
return "core-runtime-infra-misc-t-z";
228+
}
229+
230+
function createInfraStateSplitShards() {
231+
const groups = new Map();
232+
for (const file of listTestFiles("src/infra")) {
233+
const shardName = resolveInfraStateShardName(file);
234+
groups.set(shardName, [...(groups.get(shardName) ?? []), file]);
235+
}
236+
237+
return [
238+
"core-runtime-infra-approval",
239+
"core-runtime-infra-device-push",
240+
"core-runtime-infra-exec",
241+
"core-runtime-infra-heartbeat",
242+
"core-runtime-infra-misc-a-f",
243+
"core-runtime-infra-misc-g-l",
244+
"core-runtime-infra-misc-gateway",
245+
"core-runtime-infra-misc-m-o",
246+
"core-runtime-infra-misc-path-ports",
247+
"core-runtime-infra-misc-process-restart",
248+
"core-runtime-infra-misc-s-system",
249+
"core-runtime-infra-misc-t-z",
250+
"core-runtime-infra-network",
251+
"core-runtime-infra-outbound",
252+
"core-runtime-infra-package-provider",
253+
"core-runtime-infra-session-state",
254+
]
255+
.map((shardName) => ({
256+
configs: ["test/vitest/vitest.infra.config.ts"],
257+
includePatterns: groups.get(shardName) ?? [],
258+
requiresDist: false,
259+
runner: "blacksmith-4vcpu-ubuntu-2404",
260+
shardName,
261+
}))
262+
.filter((shard) => shard.includePatterns.length > 0);
263+
}
264+
122265
const GATEWAY_SERVER_BACKED_HTTP_TESTS = new Set([
123266
"src/gateway/embeddings-http.test.ts",
124267
"src/gateway/models-http.test.ts",
@@ -286,13 +429,16 @@ const SPLIT_NODE_SHARDS = new Map([
286429
[
287430
"core-runtime",
288431
[
432+
...createInfraStateSplitShards(),
289433
{
290-
shardName: "core-runtime-infra-state",
291-
configs: [
292-
"test/vitest/vitest.infra.config.ts",
293-
"test/vitest/vitest.hooks.config.ts",
294-
"test/vitest/vitest.secrets.config.ts",
295-
],
434+
shardName: "core-runtime-hooks",
435+
configs: ["test/vitest/vitest.hooks.config.ts"],
436+
requiresDist: false,
437+
runner: "blacksmith-4vcpu-ubuntu-2404",
438+
},
439+
{
440+
shardName: "core-runtime-secrets",
441+
configs: ["test/vitest/vitest.secrets.config.ts"],
296442
requiresDist: false,
297443
runner: "blacksmith-4vcpu-ubuntu-2404",
298444
},

src/agents/agent-bundle-mcp-runtime.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,8 +1137,10 @@ process.on("SIGINT", shutdown);`,
11371137
toolCount: 0,
11381138
resources: { listChanged: true },
11391139
});
1140-
await expect(fs.readFile(logPath, "utf8")).resolves.toContain(
1140+
await waitForFileText(
1141+
logPath,
11411142
"reject tools/list method not found",
1143+
LIST_TOOLS_SERVER_LOG_TIMEOUT_MS,
11421144
);
11431145
} finally {
11441146
await runtime.dispose();

src/agents/embedded-agent-runner/run/attempt.test.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,31 +78,22 @@ function requireRecord(value: unknown, label: string): Record<string, unknown> {
7878
return value as Record<string, unknown>;
7979
}
8080

81-
function requireContentItem(
82-
content: Array<{ type?: string; text?: string; name?: string }> | unknown[],
83-
index = 0,
84-
) {
81+
function requireContentItem(content: unknown[], index = 0) {
8582
return requireRecord(content[index], `content item ${index}`);
8683
}
8784

8885
function wrappedPluginSystemContext(text: string): string {
8986
return wrapPluginSystemContextSection(text) ?? "";
9087
}
9188

92-
function expectSingleTextContent(
93-
content: Array<{ type?: string; text?: string }> | unknown[],
94-
textFragment: string,
95-
) {
89+
function expectSingleTextContent(content: unknown[], textFragment: string) {
9690
expect(content).toHaveLength(1);
9791
const item = requireContentItem(content);
9892
expect(item.type).toBe("text");
9993
expect(item.text).toContain(textFragment);
10094
}
10195

102-
function expectSingleToolCallContent(
103-
content: Array<{ type?: string; name?: string }> | unknown[],
104-
name: string,
105-
) {
96+
function expectSingleToolCallContent(content: unknown[], name: string) {
10697
expect(content).toHaveLength(1);
10798
const item = requireContentItem(content);
10899
expect(item.type).toBe("toolCall");

src/llm/utils/json-parse.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, expect, it } from "vitest";
2+
import { parseJsonWithRepair, parseStreamingJson, repairJson } from "./json-parse.js";
3+
4+
describe("json-parse repairJson invalid \\u escapes", () => {
5+
it("repairs a \\u not followed by four hex digits so the result parses", () => {
6+
// JS string is: {"path":"C:\users"} — a model emitting an unescaped Windows path.
7+
const broken = '{"path":"C:\\users"}';
8+
expect(() => JSON.parse(repairJson(broken))).not.toThrow();
9+
expect(parseJsonWithRepair(broken)).toEqual({ path: "C:\\users" });
10+
});
11+
12+
it("preserves valid \\uXXXX escapes", () => {
13+
expect(parseJsonWithRepair('{"e":"\\u0041"}')).toEqual({ e: "A" });
14+
});
15+
16+
it("recovers streaming tool-call arguments instead of dropping them to {}", () => {
17+
// LaTeX-style \u (\underline) is a valid string value the model may emit in args.
18+
const args = '{"cmd":"\\underline{x}"}';
19+
expect(parseStreamingJson(args)).toEqual({ cmd: "\\underline{x}" });
20+
});
21+
});

src/llm/utils/json-parse.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ export function repairJson(json: string): string {
6464
index += 5;
6565
continue;
6666
}
67+
// A \u not followed by four hex digits is an invalid escape: double the
68+
// backslash like the other invalid escapes below. Falling through would
69+
// hit the valid-escape branch (VALID_JSON_ESCAPES contains "u") and
70+
// re-emit the broken \u, leaving the JSON unparseable.
71+
repaired += "\\\\";
72+
continue;
6773
}
6874

6975
if (VALID_JSON_ESCAPES.has(nextChar)) {

src/plugins/host-hook-turn-types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { AgentMessage } from "../agents/runtime/index.js";
21
import type { PluginJsonValue } from "./host-hook-json.js";
32

43
export type PluginNextTurnInjectionPlacement = "prepend_context" | "append_context";
@@ -28,7 +27,7 @@ export type PluginNextTurnInjectionEnqueueResult = {
2827

2928
export type PluginAgentTurnPrepareEvent = {
3029
prompt: string;
31-
messages: AgentMessage[] | unknown[];
30+
messages: unknown[];
3231
queuedInjections: PluginNextTurnInjectionRecord[];
3332
};
3433

0 commit comments

Comments
 (0)