惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
雷峰网
雷峰网
有赞技术团队
有赞技术团队
罗磊的独立博客
博客园 - 叶小钗
Jina AI
Jina AI
博客园 - 司徒正美
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
大猫的无限游戏
大猫的无限游戏
量子位
MyScale Blog
MyScale Blog
V
Visual Studio Blog
博客园 - 聂微东
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
小众软件
小众软件
宝玉的分享
宝玉的分享

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix: validate node media duration · openclaw/openclaw@49f...
steipete · 2026-05-29 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -18,6 +18,7 @@ import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";

1818

import type { ImageSanitizationLimits } from "../image-sanitization.js";

1919

import type { AgentToolResult } from "../runtime/index.js";

2020

import { sanitizeToolResultImages } from "../tool-images.js";

21+

import { readPositiveIntegerParam } from "./common.js";

2122

import type { GatewayCallOptions } from "./gateway.js";

2223

import { callGatewayTool } from "./gateway.js";

2324

import { resolveNode, resolveNodeId } from "./nodes-utils.js";

@@ -46,6 +47,7 @@ export const POLICY_REDIRECT_INVOKE_COMMANDS: ReadonlySet<string> = new Set([

4647

]);

4748
4849

export type NodeMediaAction = "camera_snap" | "photos_latest" | "camera_clip" | "screen_record";

50+

const MAX_RECORDING_DURATION_MS = 300_000;

4951
5052

type ExecuteNodeMediaActionParams = {

5153

action: NodeMediaAction;

@@ -294,12 +296,11 @@ async function executeCameraClip({

294296

if (facing !== "front" && facing !== "back") {

295297

throw new Error("invalid facing (front|back)");

296298

}

297-

const durationMs =

298-

typeof params.durationMs === "number" && Number.isFinite(params.durationMs)

299-

? params.durationMs

300-

: typeof params.duration === "string"

301-

? parseDurationMs(params.duration)

302-

: 3000;

299+

const durationMs = Math.min(

300+

readPositiveIntegerParam(params, "durationMs") ??

301+

(typeof params.duration === "string" ? parseDurationMs(params.duration) : 3000),

302+

MAX_RECORDING_DURATION_MS,

303+

);

303304

const includeAudio = typeof params.includeAudio === "boolean" ? params.includeAudio : true;

304305

const deviceId =

305306

typeof params.deviceId === "string" && params.deviceId.trim()

@@ -341,12 +342,9 @@ async function executeScreenRecord({

341342

const node = requireString(params, "node");

342343

const nodeId = await resolveNodeId(gatewayOpts, node);

343344

const durationMs = Math.min(

344-

typeof params.durationMs === "number" && Number.isFinite(params.durationMs)

345-

? params.durationMs

346-

: typeof params.duration === "string"

347-

? parseDurationMs(params.duration)

348-

: 10_000,

349-

300_000,

345+

readPositiveIntegerParam(params, "durationMs") ??

346+

(typeof params.duration === "string" ? parseDurationMs(params.duration) : 10_000),

347+

MAX_RECORDING_DURATION_MS,

350348

);

351349

const fps = typeof params.fps === "number" && Number.isFinite(params.fps) ? params.fps : 10;

352350

const screenIndex =

Original file line numberDiff line numberDiff line change

@@ -124,15 +124,19 @@ describe("createNodesTool screen_record duration guardrails", () => {

124124

nodesCameraMocks.writeCameraPayloadToFile.mockClear();

125125

});

126126
127-

it("caps durationMs schema at 300000", () => {

127+

it("bounds durationMs schema to positive values capped at 300000", () => {

128128

const tool = createNodesTool();

129129

const schema = tool.parameters as {

130130

properties?: {

131131

durationMs?: {

132+

minimum?: number;

132133

maximum?: number;

134+

type?: string;

133135

};

134136

};

135137

};

138+

expect(schema.properties?.durationMs?.type).toBe("integer");

139+

expect(schema.properties?.durationMs?.minimum).toBe(1);

136140

expect(schema.properties?.durationMs?.maximum).toBe(300_000);

137141

});

138142

@@ -158,6 +162,48 @@ describe("createNodesTool screen_record duration guardrails", () => {

158162

expect(call[2].params?.durationMs).toBe(300_000);

159163

});

160164
165+

it("clamps camera_clip durationMs argument to 300000 before gateway invoke", async () => {

166+

gatewayMocks.callGatewayTool.mockResolvedValue({ payload: { ok: true } });

167+

nodesCameraMocks.parseCameraClipPayload.mockReturnValue({

168+

base64: "ZmFrZQ==",

169+

format: "mp4",

170+

durationMs: 300_000,

171+

hasAudio: true,

172+

});

173+

nodesCameraMocks.writeCameraClipPayloadToFile.mockResolvedValue("/tmp/clip.mp4");

174+

const tool = createNodesTool();

175+
176+

await tool.execute("call-clip", {

177+

action: "camera_clip",

178+

node: "macbook",

179+

durationMs: 900_000,

180+

});

181+
182+

const call = gatewayMocks.callGatewayTool.mock.calls[0] as

183+

| [string, unknown, { params?: { durationMs?: unknown } }]

184+

| undefined;

185+

expect(call?.[0]).toBe("node.invoke");

186+

expect(call?.[2].params?.durationMs).toBe(300_000);

187+

});

188+
189+

it.each([

190+

["screen_record", 0],

191+

["screen_record", 1.5],

192+

["camera_clip", -1],

193+

["camera_clip", "1sec"],

194+

])("rejects invalid %s durationMs value %s", async (action, durationMs) => {

195+

const tool = createNodesTool();

196+
197+

await expect(

198+

tool.execute("call-invalid-duration", {

199+

action,

200+

node: "macbook",

201+

durationMs,

202+

}),

203+

).rejects.toThrow("durationMs must be a positive integer");

204+

expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();

205+

});

206+
161207

it("rejects the removed run action", async () => {

162208

const tool = createNodesTool();

163209
Original file line numberDiff line numberDiff line change

@@ -97,7 +97,7 @@ const NodesToolSchema = Type.Object({

9797

deviceId: Type.Optional(Type.String()),

9898

limit: Type.Optional(Type.Number()),

9999

duration: Type.Optional(Type.String()),

100-

durationMs: Type.Optional(Type.Number({ maximum: 300_000 })),

100+

durationMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 300_000 })),

101101

includeAudio: Type.Optional(Type.Boolean()),

102102

// screen_record

103103

fps: Type.Optional(Type.Number()),