sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
|
|
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
|
import { sanitizeToolCallInputs } from "./session-transcript-repair.js";
|
2026-03-03 02:13:43 +00:00
|
|
|
import { castAgentMessage, castAgentMessages } from "./test-helpers/agent-message-fixtures.js";
|
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
|
|
|
|
|
|
|
|
function mkSessionsSpawnToolCall(content: string): AgentMessage {
|
2026-03-03 02:13:43 +00:00
|
|
|
return castAgentMessage({
|
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
|
|
|
role: "assistant",
|
|
|
|
|
content: [
|
|
|
|
|
{
|
|
|
|
|
type: "toolCall",
|
|
|
|
|
id: "call_1",
|
|
|
|
|
name: "sessions_spawn",
|
|
|
|
|
arguments: {
|
|
|
|
|
task: "do thing",
|
|
|
|
|
attachments: [
|
|
|
|
|
{
|
|
|
|
|
name: "README.md",
|
|
|
|
|
encoding: "utf8",
|
|
|
|
|
content,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
timestamp: Date.now(),
|
2026-03-03 02:13:43 +00:00
|
|
|
});
|
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("sanitizeToolCallInputs redacts sessions_spawn attachments", () => {
|
|
|
|
|
it("replaces attachments[].content with __OPENCLAW_REDACTED__", () => {
|
2026-03-07 13:06:35 -05:00
|
|
|
const secret = "SUPER_SECRET_SHOULD_NOT_PERSIST"; // pragma: allowlist secret
|
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
|
|
|
const input = [mkSessionsSpawnToolCall(secret)];
|
|
|
|
|
const out = sanitizeToolCallInputs(input);
|
|
|
|
|
expect(out).toHaveLength(1);
|
|
|
|
|
const msg = out[0] as { content?: unknown[] };
|
|
|
|
|
const tool = (msg.content?.[0] ?? null) as {
|
|
|
|
|
name?: string;
|
|
|
|
|
arguments?: { attachments?: Array<{ content?: string }> };
|
|
|
|
|
} | null;
|
|
|
|
|
expect(tool?.name).toBe("sessions_spawn");
|
|
|
|
|
expect(tool?.arguments?.attachments?.[0]?.content).toBe("__OPENCLAW_REDACTED__");
|
|
|
|
|
expect(JSON.stringify(out)).not.toContain(secret);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("redacts attachments content from tool input payloads too", () => {
|
2026-03-07 13:06:35 -05:00
|
|
|
const secret = "INPUT_SECRET_SHOULD_NOT_PERSIST"; // pragma: allowlist secret
|
2026-03-03 02:13:43 +00:00
|
|
|
const input = castAgentMessages([
|
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
|
|
|
{
|
|
|
|
|
role: "assistant",
|
|
|
|
|
content: [
|
|
|
|
|
{
|
|
|
|
|
type: "toolUse",
|
|
|
|
|
id: "call_2",
|
|
|
|
|
name: "sessions_spawn",
|
|
|
|
|
input: {
|
|
|
|
|
task: "do thing",
|
|
|
|
|
attachments: [{ name: "x.txt", content: secret }],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-03-03 02:13:43 +00:00
|
|
|
]);
|
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
|
|
|
|
|
|
|
|
const out = sanitizeToolCallInputs(input);
|
|
|
|
|
const msg = out[0] as { content?: unknown[] };
|
|
|
|
|
const tool = (msg.content?.[0] ?? null) as {
|
|
|
|
|
// Some providers emit tool calls as `input`/`toolUse`. We normalize to `toolCall` with `arguments`.
|
|
|
|
|
input?: { attachments?: Array<{ content?: string }> };
|
|
|
|
|
arguments?: { attachments?: Array<{ content?: string }> };
|
|
|
|
|
} | null;
|
|
|
|
|
expect(
|
|
|
|
|
tool?.input?.attachments?.[0]?.content || tool?.arguments?.attachments?.[0]?.content,
|
|
|
|
|
).toBe("__OPENCLAW_REDACTED__");
|
|
|
|
|
expect(JSON.stringify(out)).not.toContain(secret);
|
|
|
|
|
});
|
|
|
|
|
});
|