Skip to content

Commit 5bfdf94

Browse files
authored
Merge branch 'main' into fix/9303-deprecated-start-guidance
2 parents 6eee71f + 1eb2091 commit 5bfdf94

206 files changed

Lines changed: 9948 additions & 9377 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ci/test-file-size-budget.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"src/lib/onboard/preflight.test.ts": 1875,
88
"test/generate-openclaw-config.test.ts": 1907,
99
"test/install-preflight.test.ts": 3025,
10-
"test/nemoclaw-start.test.ts": 4785,
10+
"test/nemoclaw-start.test.ts": 4761,
1111
"test/onboard-messaging.test.ts": 2033,
1212
"test/onboard-selection.test.ts": 4177
1313
}

nemoclaw/src/blueprint/runner.test.ts

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -409,58 +409,62 @@ describe("runner", () => {
409409
expect(plan.dry_run).toBe(false);
410410
});
411411

412-
it("does not expose credential field names or secret values in public plan output", async () => {
413-
captureStdout();
414-
mockExeca.mockResolvedValue({ exitCode: 0 });
415-
const bp = {
416-
components: {
417-
inference: {
418-
profiles: {
419-
secrets: {
420-
provider_type: "openai",
421-
provider_name: "secret-provider",
422-
endpoint: "https://api.example.com/v1",
423-
model: "gpt-4",
424-
credential_env: "SECRET_KEY",
425-
credential_default: "default-secret-value",
426-
token: "future-token-value",
427-
authorization: "Bearer future-authorization",
428-
},
429-
},
430-
},
431-
sandbox: { image: "openclaw", name: "sb", forward_ports: [18789] },
432-
},
433-
};
434-
process.env.SECRET_KEY = "real-secret-value";
435-
try {
436-
const plan = await actionPlan("secrets", bp);
437-
const rendered = capturedJsonOutput<{
438-
inference: Record<string, unknown>;
439-
}>();
440-
const out = stdoutText();
441-
442-
expect(plan.inference).not.toHaveProperty("credential_env");
443-
expect(rendered.inference).toEqual({
444-
provider_type: "openai",
445-
provider_name: "secret-provider",
446-
endpoint: "https://api.example.com/v1",
447-
model: "gpt-4",
448-
});
449-
for (const leaked of [
412+
it.each(
413+
[
450414
"credential_env",
451415
"credential_default",
452416
"SECRET_KEY",
453417
"default-secret-value",
454418
"real-secret-value",
455419
"future-token-value",
456420
"future-authorization",
457-
]) {
421+
],
422+
)(
423+
"does not expose credential field names or secret values in public plan output [%s]",
424+
async (leaked) => {
425+
captureStdout();
426+
mockExeca.mockResolvedValue({ exitCode: 0 });
427+
const bp = {
428+
components: {
429+
inference: {
430+
profiles: {
431+
secrets: {
432+
provider_type: "openai",
433+
provider_name: "secret-provider",
434+
endpoint: "https://api.example.com/v1",
435+
model: "gpt-4",
436+
credential_env: "SECRET_KEY",
437+
credential_default: "default-secret-value",
438+
token: "future-token-value",
439+
authorization: "Bearer future-authorization",
440+
},
441+
},
442+
},
443+
sandbox: { image: "openclaw", name: "sb", forward_ports: [18789] },
444+
},
445+
};
446+
process.env.SECRET_KEY = "real-secret-value";
447+
try {
448+
const plan = await actionPlan("secrets", bp);
449+
const rendered = capturedJsonOutput<{
450+
inference: Record<string, unknown>;
451+
}>();
452+
const out = stdoutText();
453+
454+
expect(plan.inference).not.toHaveProperty("credential_env");
455+
expect(rendered.inference).toEqual({
456+
provider_type: "openai",
457+
provider_name: "secret-provider",
458+
endpoint: "https://api.example.com/v1",
459+
model: "gpt-4",
460+
});
461+
458462
expect(out).not.toContain(leaked);
463+
} finally {
464+
delete process.env.SECRET_KEY;
459465
}
460-
} finally {
461-
delete process.env.SECRET_KEY;
462-
}
463-
});
466+
},
467+
);
464468

465469
it("passes dryRun through to the plan", async () => {
466470
captureStdout();

nemoclaw/src/register.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,7 @@ describe("plugin registration", () => {
177177

178178
const bannerLines = output.split("\n").filter((line) => line.length > 0);
179179
expect(bannerLines.length).toBeGreaterThan(0);
180-
for (const line of bannerLines) {
181-
expect(line.startsWith("[gateway] ")).toBe(true);
182-
}
180+
expect(bannerLines.every((line) => line.startsWith("[gateway] "))).toBe(true);
183181
});
184182

185183
it("falls back to onboard config when openclaw.json has no primary model", () => {

src/lib/actions/sandbox/auto-pair-approval-receipt.test.ts

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,8 @@ describe("auto-pair approval receipts (#4616)", () => {
1919
const pythonUnavailable =
2020
spawnSync("sh", ["-c", "command -v python3"], { stdio: "ignore" }).status !== 0;
2121

22-
it.skipIf(pythonUnavailable)("omits raw output from devices-list failure classifications", () => {
23-
const policy = readAutoPairApprovalPolicyModule();
24-
expect(policy).toBeTruthy();
25-
const script = buildAutoPairApprovalScript(
26-
Buffer.from(policy as string, "utf-8").toString("base64"),
27-
{
28-
emitReceipt: true,
29-
budget: { listTimeoutS: 0.5 },
30-
},
31-
);
32-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-auto-pair-list-receipt-"));
33-
try {
34-
fs.writeFileSync(
35-
path.join(tmpDir, "openclaw"),
36-
`#!${process.execPath}
37-
const args = process.argv.slice(2);
38-
if (args[0] !== "devices" || args[1] !== "list") process.exit(2);
39-
const sleepMs = Number(process.env.NEMOCLAW_LIST_SLEEP_MS || "0");
40-
if (sleepMs > 0) {
41-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, sleepMs);
42-
}
43-
process.stdout.write(process.env.NEMOCLAW_LIST_STDOUT || "");
44-
process.stderr.write(process.env.NEMOCLAW_LIST_STDERR || "");
45-
process.exit(Number(process.env.NEMOCLAW_LIST_EXIT_CODE || "0"));
46-
`,
47-
{ mode: 0o755 },
48-
);
49-
for (const [environment, receipt] of [
22+
it.skipIf(pythonUnavailable).each(
23+
[
5024
[{ NEMOCLAW_LIST_SLEEP_MS: "800" }, "list-timeout"],
5125
[
5226
{ NEMOCLAW_LIST_EXIT_CODE: "1", NEMOCLAW_LIST_STDERR: "raw failure" },
@@ -77,7 +51,37 @@ process.exit(Number(process.env.NEMOCLAW_LIST_EXIT_CODE || "0"));
7751
[{ NEMOCLAW_LIST_STDOUT: "raw invalid json" }, "list-invalid-json"],
7852
[{ NEMOCLAW_LIST_STDOUT: "[]\n" }, "list-invalid-output"],
7953
[{ NEMOCLAW_LIST_STDOUT: "{}\n" }, "list-missing-pending"],
80-
] as const) {
54+
] as const,
55+
)(
56+
"omits raw output from devices-list failure classifications [case %#]",
57+
(environment, receipt) => {
58+
const policy = readAutoPairApprovalPolicyModule();
59+
expect(policy).toBeTruthy();
60+
const script = buildAutoPairApprovalScript(
61+
Buffer.from(policy as string, "utf-8").toString("base64"),
62+
{
63+
emitReceipt: true,
64+
budget: { listTimeoutS: 0.5 },
65+
},
66+
);
67+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-auto-pair-list-receipt-"));
68+
try {
69+
fs.writeFileSync(
70+
path.join(tmpDir, "openclaw"),
71+
`#!${process.execPath}
72+
const args = process.argv.slice(2);
73+
if (args[0] !== "devices" || args[1] !== "list") process.exit(2);
74+
const sleepMs = Number(process.env.NEMOCLAW_LIST_SLEEP_MS || "0");
75+
if (sleepMs > 0) {
76+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, sleepMs);
77+
}
78+
process.stdout.write(process.env.NEMOCLAW_LIST_STDOUT || "");
79+
process.stderr.write(process.env.NEMOCLAW_LIST_STDERR || "");
80+
process.exit(Number(process.env.NEMOCLAW_LIST_EXIT_CODE || "0"));
81+
`,
82+
{ mode: 0o755 },
83+
);
84+
8185
const result = spawnSync("sh", ["-c", script], {
8286
encoding: "utf-8",
8387
env: {
@@ -89,11 +93,11 @@ process.exit(Number(process.env.NEMOCLAW_LIST_EXIT_CODE || "0"));
8993
});
9094
expect(parseAutoPairApprovalReceipt(result.stdout)).toBe(receipt);
9195
expect(`${result.stdout}${result.stderr}`).not.toContain("raw ");
96+
} finally {
97+
fs.rmSync(tmpDir, { recursive: true, force: true });
9298
}
93-
} finally {
94-
fs.rmSync(tmpDir, { recursive: true, force: true });
95-
}
96-
});
99+
},
100+
);
97101

98102
it("distinguishes host execution failures without returning their details", () => {
99103
const timeoutError = Object.assign(new Error("private timeout detail"), {

src/lib/actions/sandbox/auto-pair-approval-script.test.ts

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -109,43 +109,38 @@ describe("buildAutoPairApprovalScript (#4263/#4616)", () => {
109109
expect(module).not.toContain("recover_failed_scope_approval");
110110
});
111111

112-
it.each(
113-
Array.from(
114-
[
115-
`${RECEIPT_MARKER}=approved-one\nlater output\n`,
116-
`${RECEIPT_MARKER}=approve-failed\n${RECEIPT_MARKER}=approved-one\n`,
117-
`${RECEIPT_MARKER}=raw-request-id\n`,
118-
],
119-
(value) => [value],
120-
),
121-
)("accepts exactly one terminal fixed receipt [case %#]", (output) => {
122-
for (const receipt of [
123-
"approved-one",
124-
"list-failed",
125-
"list-state-path-invalid",
126-
"list-platform-unsupported",
127-
"list-state-root-failed",
128-
"list-devices-directory-failed",
129-
"list-pending-unsafe",
130-
"list-pending-unstable",
131-
"list-pending-invalid-shape",
132-
"list-pending-unavailable",
133-
"list-timeout",
134-
"list-exec-failed",
135-
"list-scope-upgrade-pending",
136-
"list-device-pairing-required",
137-
"list-gateway-connect-failed",
138-
"list-command-failed",
139-
"list-empty-output",
140-
"list-invalid-json",
141-
"list-invalid-output",
142-
"list-missing-pending",
143-
] as const) {
144-
expect(
145-
parseAutoPairApprovalReceipt(`ignored setup output\n${RECEIPT_MARKER}=${receipt}\n`),
146-
).toBe(receipt);
147-
}
112+
it.each([
113+
"approved-one",
114+
"list-failed",
115+
"list-state-path-invalid",
116+
"list-platform-unsupported",
117+
"list-state-root-failed",
118+
"list-devices-directory-failed",
119+
"list-pending-unsafe",
120+
"list-pending-unstable",
121+
"list-pending-invalid-shape",
122+
"list-pending-unavailable",
123+
"list-timeout",
124+
"list-exec-failed",
125+
"list-scope-upgrade-pending",
126+
"list-device-pairing-required",
127+
"list-gateway-connect-failed",
128+
"list-command-failed",
129+
"list-empty-output",
130+
"list-invalid-json",
131+
"list-invalid-output",
132+
"list-missing-pending",
133+
] as const)("accepts the terminal fixed receipt %s", (receipt) => {
134+
expect(
135+
parseAutoPairApprovalReceipt(`ignored setup output\n${RECEIPT_MARKER}=${receipt}\n`),
136+
).toBe(receipt);
137+
});
148138

139+
it.each([
140+
`${RECEIPT_MARKER}=approved-one\nlater output\n`,
141+
`${RECEIPT_MARKER}=approve-failed\n${RECEIPT_MARKER}=approved-one\n`,
142+
`${RECEIPT_MARKER}=raw-request-id\n`,
143+
])("rejects a non-terminal, duplicate, or unknown receipt [case %#]", (output) => {
149144
expect(parseAutoPairApprovalReceipt(output)).toBeNull();
150145
});
151146
});

src/lib/actions/sandbox/connect-hermes-light-theme.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,9 @@ describe("Hermes sandbox connect light terminal skin", () => {
378378

379379
const skinWriteCall = skinWriteCalls(harness)[0];
380380
expect(skinWriteCall?.[0]).toEqual(["sandbox", "exec", "--name", "alpha", "--", "sh", "-s"]);
381-
for (const part of (skinWriteCall?.[0] ?? []) as string[]) {
382-
expect(part).not.toMatch(/[\n\r]/);
383-
}
381+
expect(((skinWriteCall?.[0] ?? []) as string[]).every((part) => !/[\n\r]/.test(part))).toBe(
382+
true,
383+
);
384384
const opts = skinWriteCall?.[1] as { input?: string; stdio?: unknown } | undefined;
385385
expect(opts?.input ?? "").toContain('mv -f "$tmp" "$skin_dir/nemoclaw-light.yaml"');
386386
expect(opts?.input ?? "").toContain("\n");

0 commit comments

Comments
 (0)