Fix structured attachment rendering (#69)

* review: harden readonly git checks and lint verification

* test: satisfy readonly reviewer sandbox typing

* test: fix readonly reviewer sandbox assertions

* test: remove impossible readonly sandbox guards

* test: relax readonly sandbox assertions

* test: cast readonly sandbox expectation shape

* test: cast readonly sandbox expectation through unknown

* Phase 0 — STEP_DONE/TASK_DONE split + owner-follow-up integration smoke

* Add STEP_DONE guards, verdict storage, and stale delivery suppression

* style: format paired stepdone telemetry files

* Route STEP_DONE through reviewer

* Add structured Discord attachments

* style: format structured attachment test

* Fix room thread bot output parity

* Remove duplicate work item attachment migration from owner branch

* Remove legacy dashboard rooms renderer

* Extract dashboard parsed body renderer

* Extract dashboard redaction helpers

* Extract dashboard RoomCardV2 component

* Include RoomCardV2 test in vitest suite

* Extract dashboard RoomBoardV2 component

* Extract dashboard EmptyState component

* Extract dashboard InboxPanel component

* Split dashboard InboxPanel card renderer

* Extract dashboard TaskPanel component

* Extract dashboard UsagePanel component

* Fix dashboard room thread chunk rendering

* Extract dashboard ServicePanel component

* Render dashboard live progress markdown

* Extract dashboard SettingsPanel component

* Fix structured attachment rendering
This commit is contained in:
Eyejoker
2026-04-28 17:36:11 +09:00
committed by GitHub
parent 96d99f63e0
commit 6197e90c8c
18 changed files with 837 additions and 104 deletions

View File

@@ -21,7 +21,6 @@ const DEFAULT_TEMP_ATTACHMENT_DIR_PREFIXES = [
'ejclaw-attachment-',
'ejclaw-discord-image-',
] as const;
const DEFAULT_TEMP_ATTACHMENT_FILE_PREFIXES = ['ejclaw-'] as const;
function unique(values: Array<string | null | undefined>): string[] {
return [
@@ -44,8 +43,8 @@ export function getDefaultAttachmentBaseDirs(): string[] {
process.env.CODEX_HOME || (home ? path.join(home, '.codex') : null);
// Keep defaults narrow. Runtime-specific workspaces must be passed via
// attachmentBaseDirs so one room cannot attach another room's files by path.
// Ad-hoc generated files under os.tmpdir()/ejclaw-attachment-* are handled
// separately after resolving symlinks, without allowlisting all of /tmp.
// Ad-hoc generated files under os.tmpdir() are handled separately after
// resolving symlinks, without allowlisting all of /tmp recursively.
return unique([
path.join(DATA_DIR, 'attachments'),
codexHome ? path.join(codexHome, 'generated_images') : null,
@@ -84,7 +83,7 @@ function isWithinDefaultTempAttachmentDir(
);
}
function isDefaultTempAttachmentFile(
function isDirectTempAttachmentFile(
realPath: string,
tempDir: string | null,
): boolean {
@@ -93,10 +92,10 @@ function isDefaultTempAttachmentFile(
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
return false;
}
if (relative.includes(path.sep)) return false;
return DEFAULT_TEMP_ATTACHMENT_FILE_PREFIXES.some((prefix) =>
relative.startsWith(prefix),
);
// Agents commonly write Playwright screenshots and generated images directly
// under /tmp with task-specific names. Keep this limited to direct children;
// nested temp directories still need an EJClaw prefix or explicit baseDir.
return !relative.includes(path.sep);
}
function detectImageMime(filePath: string): string | null {
@@ -188,7 +187,7 @@ export function validateOutboundAttachments(
if (
!matchesAllowedBaseDir(realPath, baseDirs) &&
!isWithinDefaultTempAttachmentDir(realPath, defaultTempDir) &&
!isDefaultTempAttachmentFile(realPath, defaultTempDir)
!isDirectTempAttachmentFile(realPath, defaultTempDir)
) {
rejected.push({ path: requestedPath, reason: 'outside-allowed-dirs' });
continue;