Skip to content

Any-file attachments to cloud agents and remote CLI sessions#4628

Merged
iscekic merged 16 commits into
mainfrom
feat/any-file-attachments
Jul 21, 2026
Merged

Any-file attachments to cloud agents and remote CLI sessions#4628
iscekic merged 16 commits into
mainfrom
feat/any-file-attachments

Conversation

@iscekic

@iscekic iscekic commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What & why

Lets the Kilo mobile app attach any (non-denied) file type to messages sent to both cloud-agent sessions and remote CLI sessions. Previously mobile attachments were limited to a fixed 9-extension allow-list on cloud-agent sessions only; remote CLI sessions had no attachment path at all. Image behavior is the parity floor — everything images could do, arbitrary files now do.

Companion CLI change (materialization + capability advertisement): Kilo-Org/kilocode#12394.

Policy (documented, enforced at every layer)

  • Limits: 5 MB raw bytes per file, 5 files per message. Zero-byte files rejected. The sandbox wrapper streams downloads with a hard 5 MB bound and deletes partials on overflow (it no longer trusts the content-length header alone).
  • Deny-list, not allow-list: an optional, backward-compatible extension field on the upload presign switches policy from the old allow-list to a deny-list (exe, dll, msi, com, scr, apk, ipa, dmg, pkg). When extension is absent (released clients, the existing web hook), the legacy allowed-MIME contract is preserved unchanged. The deny-list is enforced independently at every filename boundary: web tRPC schemas, the worker persistence schema, the runtime download validator, and the new download-presign helper.
  • MIME is derived solely from the stored extension via one canonical extension→MIME table duplicated (with exhaustive parity tests) in mobile, the cloud-agent-next worker, and the CLI. Unknown/absent extensions normalize to binapplication/octet-stream; there is no MIME-based extension inference anywhere.
  • Prompt shape: text/image/pdf attachments become file parts (as today); generic binaries are materialized to disk and surfaced to the agent as a text part naming the absolute path, filename, MIME, and size. A per-file download failure now replaces only that attachment with an explanatory text part instead of aborting the whole message.

Remote CLI enablement

  • New personal, author-scoped download presign cloudAgentNext.getAttachmentDownloadUrl (900 s). Mobile mints one signed GET per attachment and sends the CLI a first-class file part whose filename is the server-issued <uuid>.<ext> basename; the CLI classifies and materializes from that validated extension.
  • Capability gating: the CLI advertises capabilities.attachments in its relay heartbeat; session-ingest stores it per connection and projects it into both aggregateSessions() and the sessions.heartbeat event; the mobile composer only enables attachments for a remote session when capabilities.attachments === true, recomputed on every heartbeat (upgrade/downgrade/reconnect/absent) and fail-closed on reconnect. Released CLIs without the capability behave exactly as today (paperclip hidden).

Interaction with the known download-key divergence (deliberately not changed)

There is a known, deliberately-unfixed divergence where the existing cloud-agent download presign derives its R2 key from the session-owner userId while upload keys use the message-author userId (they diverge on org/shared sessions). This PR does not change key-derivation semantics anywhere. The new getAttachmentDownloadUrl derives its key from the caller (the author who uploaded), so it does not reproduce that divergence.

Testing

Comprehensive automated coverage, all green:

  • services/cloud-agent-next: unit (2248) + Workers integration (209) + wrapper bounded-streaming/materialization/per-file-failure (253).
  • services/session-ingest: unit (497) + integration (78) — capability true/false/absent transitions in both DO outputs.
  • apps/web cloud-agent-sdk: 998 tests — capability gate, heartbeat recompute, reconnect reset, send-path gating, send_message part ordering.
  • apps/mobile: 1080 tests — classify/deny-list/zero-byte/size, measured-size, failure classifier (terminal vs retryable), chip state/CTA presence/absence, capability gate, buildRemoteAttachmentParts mime-from-remoteName contract, retryable presign-failure feedback.
  • Repo-root typecheck (all 55 packages), lint, and check:unused are clean.

On-device E2E note: the selection/empty/CTA state logic and all server/SDK/CLI behaviors are covered by the automated suites above. Full interactive on-device E2E of a live agent turn requires an environment with real agent-container provisioning (local dev has no running bot/sandbox for cloud-agent sessions, and the remote-CLI content-read proof needs a real model). This is a local-environment limitation, not a behavioral gap.

Non-goals

No web-app UI changes; no changes to useCloudAgentAttachmentUpload.ts or lib/bot/attachments.ts behavior; no change to the cloud-agent {path, files} wire (original filename stays mobile-display-only); no key-derivation change; no limit increase or chunked upload; no org mirror for the download presign. Android shares the same JS paths; E2E focus is iOS this round.

iscekic added 12 commits July 17, 2026 22:27
…zation

Extend cloud-agent attachments from a fixed 9-extension allow-list to any
non-denied file type, with per-file materialization failure handling.

- Add optional `extension` to the upload-presign input (personal + org).
  When present, the R2 key suffix derives from the validated extension and
  `contentType` relaxes to a MIME-shape string; when absent, the legacy
  allowed-MIME contract is preserved unchanged (released web hook and
  pre-upgrade mobile clients keep working).
- Enforce a shared deny-list (exe, dll, msi, com, scr, apk, ipa, dmg, pkg)
  at every filename boundary: web schemas, worker persistence schema,
  runtime download validation, and the new download-presign helper.
- Pin a canonical extension->MIME table; unknown/absent extensions resolve
  to application/octet-stream. No MIME-based extension inference.
- Wrapper streams downloads with a 5 MB bound, deletes partials on
  overflow, and converts per-file failures (non-2xx, timeout, overflow)
  into explanatory text parts so the agent turn proceeds. Generic-binary
  attachments are surfaced as text parts with path + filename + mime + size.
- Add generateCloudAgentAttachmentDownloadUrl (author-scoped key, 900s) and
  cloudAgentGetAttachmentDownloadUrlSchema for the remote-CLI download path.
Extend the agent composer from images/9 extensions to any non-denied file.

- Replace the closed allow-list with the shared deny-list policy; classify
  by measured local byte size (never the picker's optional size), rejecting
  zero-byte, deny-listed, >5 MB, and >5-file selections with specific
  messages while keeping valid siblings in a mixed selection.
- Derive attachment MIME solely from the normalized extension (fallback bin
  -> application/octet-stream); the picker MIME is never consulted.
- Per-chip determinate upload progress via expo-file-system/legacy
  createUploadTask (binary PUT), with a blob-PUT fallback.
- Split upload failures into terminal (presign BAD_REQUEST/FORBIDDEN/
  UNPROCESSABLE_CONTENT -> "This file can't be uploaded.", no retry) vs
  retryable (network/timeout/408/429/5xx/PUT -> "Upload failed. Tap to
  retry."); send stays blocked while any attachment is uploading or failed.
- Expose a typed composer submission payload { wire, messageUuid, files:
  [{ remoteName, originalName, size }] } (no mime) for the remote-CLI send
  path; widen the composer onSend signature with an optional third arg.
- Add iOS UIFileSharingEnabled + LSSupportsOpeningDocumentsInPlace for
  Files-picker fixture ingress.
- Add cloudAgentNext.getAttachmentDownloadUrl: a personal, author-scoped
  presigned GET for remote-CLI attachment materialization, validated via
  S1's shared download schema. No org mirror; no key-derivation changes.
- Plumb an optional CLI heartbeat capability flag through session-ingest:
  services/session-ingest accepts capabilities.attachments on the heartbeat,
  UserConnectionDO stores it per connection (mirroring protocolVersion) and
  projects it into both aggregateSessions() rows and the sessions.heartbeat
  event, omitting it when the latest heartbeat omits it.
- Expose capabilities on the activeSessions.list session schema so the
  mobile client can gate the attachment UI on remote CLI sessions.
- Gate the composer paperclip on the remote CLI's advertised capability:
  supportsAttachments for a remote session is true only when the heartbeat
  carries capabilities.attachments === true, recomputed on every heartbeat
  (upgrade/downgrade/reconnect/absent). Cloud-agent sessions are unchanged.
- SDK send input gains an optional attachmentParts field (distinct from the
  cloud-only {path,files} attachments); cli-live-transport appends the file
  parts after the text part in send_message. Remove the blanket
  'Only Cloud Agent sessions support attachments' throw for capable remotes.
- Mobile builds ready FilePartInput parts for a capable remote session:
  mint an author-scoped presigned GET per file, set filename to the
  server-issued remoteName (<uuid>.<ext>) and derive mime solely from that
  extension via the canonical table. The original filename never leaves the
  device; the {path,files} cloud wire is untouched.
… presign-failure feedback

- cli-live-transport resets the advertised attachment capability on live
  reconnect so the composer gate closes until the next heartbeat/sessions
  .list re-advertises it (fail-closed); add a transport-level reconnect test.
- session-detail surfaces a retryable message and preserves the draft +
  attachments when minting a remote attachment's presigned GET fails before
  send, instead of silently swallowing the error.
Fix CI: oxfmt the new test file and make BuildRemoteAttachmentPartsResult
module-internal (knip flagged it as an unused export).
Comment thread apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts Outdated
Comment thread apps/web/src/routers/cloud-agent-next-schemas.ts Outdated
Comment thread apps/web/src/lib/r2/cloud-agent-attachments.ts Outdated
Comment thread services/cloud-agent-next/wrapper/src/session-bootstrap.ts
Comment thread services/cloud-agent-next/wrapper/src/session-bootstrap.ts
Comment thread apps/mobile/app.config.ts
@kilo-code-bot

kilo-code-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Commit 92fe234 correctly resolves all five previously flagged code-level findings (extension re-derivation, deny-list error message, deny-list enforcement in the upload-URL helper, partial-file cleanup on non-overflow download failures, and the one-byte download-cap tolerance) with no new issues introduced in the changed files.

Files Reviewed (10 files)
  • apps/mobile/src/lib/agent-attachments/agent-attachment-types.ts
  • apps/mobile/src/lib/agent-attachments/upload-task.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts - previous WARNING fixed
  • apps/web/src/lib/r2/cloud-agent-attachments.test.ts
  • apps/web/src/lib/r2/cloud-agent-attachments.ts - previous SUGGESTION fixed
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts
  • apps/web/src/routers/cloud-agent-next-schemas.ts - previous WARNING fixed
  • services/cloud-agent-next/wrapper/src/session-bootstrap.test.ts
  • services/cloud-agent-next/wrapper/src/session-bootstrap.ts - previous WARNING and SUGGESTION fixed
Previous Review Summaries (2 snapshots, latest commit 92fe234)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 92fe234)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Commit 92fe234 correctly resolves all five previously flagged code-level findings (extension re-derivation, deny-list error message, deny-list enforcement in the upload-URL helper, partial-file cleanup on non-overflow download failures, and the one-byte download-cap tolerance) with no new issues introduced in the changed files.

Files Reviewed (10 files)
  • apps/mobile/src/lib/agent-attachments/agent-attachment-types.ts
  • apps/mobile/src/lib/agent-attachments/upload-task.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts - previous WARNING fixed
  • apps/web/src/lib/r2/cloud-agent-attachments.test.ts
  • apps/web/src/lib/r2/cloud-agent-attachments.ts - previous SUGGESTION fixed
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts
  • apps/web/src/routers/cloud-agent-next-schemas.ts - previous WARNING fixed
  • services/cloud-agent-next/wrapper/src/session-bootstrap.test.ts
  • services/cloud-agent-next/wrapper/src/session-bootstrap.ts - previous WARNING and SUGGESTION fixed

Previous review (commit fb32f5a)

Status: 6 Issues Found | Recommendation: Address before merge

Executive Summary

The most impactful issue is a client-side attachment extension re-derivation bug in use-agent-attachment-upload.ts that can send an unvalidated, raw filename extension to the upload presign, bypassing the classification/normalization already performed on the same attachment.

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts 99 Extension re-derived from raw filename bypasses classification/normalization
apps/web/src/routers/cloud-agent-next-schemas.ts 109 Deny-list rejection message uses a literal '<ext>' placeholder instead of the actual extension
services/cloud-agent-next/wrapper/src/session-bootstrap.ts 928 Partial attachment file not deleted on non-overflow download failures (network error/timeout mid-stream)

SUGGESTION

File Line Issue
apps/web/src/lib/r2/cloud-agent-attachments.ts 115 normalizeAttachmentExtension doesn't enforce the deny-list itself, contradicting its docstring (currently not exploitable; callers validate first)
services/cloud-agent-next/wrapper/src/session-bootstrap.ts 40 One-byte tolerance above the documented 5 MiB download cap
apps/mobile/app.config.ts 48 New iOS file-sharing entitlements expose the whole Documents directory, not just attachments
Files Reviewed (60 files)
  • apps/mobile/.kilo/MOBILE_WORKFLOW.md
  • apps/mobile/.kilo/agent/mobile-e2e-verifier.md
  • apps/mobile/app.config.ts - 1 issue
  • apps/mobile/src/app/(app)/agent-chat/new.tsx
  • apps/mobile/src/components/agents/attachment-chip-description.ts
  • apps/mobile/src/components/agents/attachment-picker.test.ts
  • apps/mobile/src/components/agents/attachment-picker.ts
  • apps/mobile/src/components/agents/attachment-preview-strip.test.ts
  • apps/mobile/src/components/agents/attachment-preview-strip.tsx
  • apps/mobile/src/components/agents/chat-composer.tsx
  • apps/mobile/src/components/agents/mobile-session-manager-helpers.ts
  • apps/mobile/src/components/agents/mobile-session-manager.test.ts
  • apps/mobile/src/components/agents/mobile-session-manager.ts
  • apps/mobile/src/components/agents/mobile-session-transport-payload.ts
  • apps/mobile/src/components/agents/session-detail-content.test.ts
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/session-detail-send-attachment.test.ts
  • apps/mobile/src/components/agents/session-detail-send-attachment.ts
  • apps/mobile/src/lib/agent-attachments/agent-attachment-types.ts
  • apps/mobile/src/lib/agent-attachments/constants.ts
  • apps/mobile/src/lib/agent-attachments/upload-task.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts - 1 issue
  • apps/mobile/src/lib/agent-attachments/validate.test.ts
  • apps/mobile/src/lib/agent-attachments/validate.ts
  • apps/web/src/lib/cloud-agent-sdk/cli-live-transport.test.ts
  • apps/web/src/lib/cloud-agent-sdk/cli-live-transport.ts
  • apps/web/src/lib/cloud-agent-sdk/index.ts
  • apps/web/src/lib/cloud-agent-sdk/schemas.test.ts
  • apps/web/src/lib/cloud-agent-sdk/schemas.ts
  • apps/web/src/lib/cloud-agent-sdk/session-manager.test.ts
  • apps/web/src/lib/cloud-agent-sdk/session-manager.ts
  • apps/web/src/lib/cloud-agent-sdk/session.ts
  • apps/web/src/lib/cloud-agent-sdk/transport.ts
  • apps/web/src/lib/cloud-agent-sdk/types.ts
  • apps/web/src/lib/cloud-agent/constants.ts
  • apps/web/src/lib/r2/cloud-agent-attachments.test.ts
  • apps/web/src/lib/r2/cloud-agent-attachments.ts - 1 issue
  • apps/web/src/routers/active-sessions-router.test.ts
  • apps/web/src/routers/active-sessions-router.ts
  • apps/web/src/routers/cloud-agent-next-router.test.ts
  • apps/web/src/routers/cloud-agent-next-router.ts
  • apps/web/src/routers/cloud-agent-next-schemas.test.ts
  • apps/web/src/routers/cloud-agent-next-schemas.ts - 1 issue
  • apps/web/src/routers/organizations/organization-cloud-agent-next-router.ts
  • services/cloud-agent-next/src/execution/attachment-prompt-parts.test.ts
  • services/cloud-agent-next/src/execution/attachment-prompt-parts.ts
  • services/cloud-agent-next/src/persistence/schemas.test.ts
  • services/cloud-agent-next/src/persistence/schemas.ts
  • services/cloud-agent-next/src/router/schemas.ts
  • services/cloud-agent-next/src/utils/attachment-download.test.ts
  • services/cloud-agent-next/src/utils/attachment-download.ts
  • services/cloud-agent-next/wrapper/src/session-bootstrap.test.ts
  • services/cloud-agent-next/wrapper/src/session-bootstrap.ts - 2 issues
  • services/session-ingest/src/dos/UserConnectionDO.test.ts
  • services/session-ingest/src/dos/UserConnectionDO.ts
  • services/session-ingest/src/types/user-connection-protocol.test.ts
  • services/session-ingest/src/types/user-connection-protocol.ts

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5 · Input: 32 · Output: 5.3K · Cached: 704.2K

Review guidance: REVIEW.md from base branch main

…-depth, wrapper partial cleanup + cap boundary

- mobile: retain the normalized extension on the attachment and send it to
  the presign instead of re-deriving (and casting) from the raw filename, so
  an invalid suffix stays consistent with its bin/octet-stream classification.
- web schema: interpolate the offending extension into the deny-list error.
- r2 upload helper: reject deny-listed extensions before signing
  (defense-in-depth, matching the download helper).
- wrapper: delete the partial file on any download failure (not only
  overflow), and reject a file exactly one byte over the 5 MiB cap.
…ents

# Conflicts:
#	apps/web/src/routers/active-sessions-router.test.ts
#	apps/web/src/routers/active-sessions-router.ts
#	services/session-ingest/src/dos/UserConnectionDO.test.ts
#	services/session-ingest/src/dos/UserConnectionDO.ts
#	services/session-ingest/src/types/user-connection-protocol.ts
@iscekic
iscekic enabled auto-merge (squash) July 21, 2026 12:16
A static import of the router module in the listInstances test evaluated
config.server.ts (freezing SESSION_INGEST_WORKER_URL at the empty .env.test
value) before beforeAll could set the env, breaking the dynamic-import dance.
Move the schema-validation cases to active-sessions-router.schema.test.ts,
which Jest sandboxes separately.
@iscekic
iscekic merged commit c85249d into main Jul 21, 2026
20 checks passed
@iscekic
iscekic deleted the feat/any-file-attachments branch July 21, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants