Repository navigation
feat(context-drop): handle multi-file Finder selection - #595
Conversation
…s selected
`getFinderSelection()` and `context-drop.sh` both hardcoded `item 1 of sel`,
silently dropping multi-selections to first file. Iterate the selection,
emit a single task file with `type: files` + `paths:` block when count > 1;
keep the existing `type: file` + `path:` shape for the single-file case.
Notification text reflects the count ("3 files dropped" vs filename).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…i's review) Per Mini's feedback on PR #595: emit `paths: ["/a","/b"]` as JSON array on one line instead of YAML-ish multi-line block. Reasons: - No YAML lib dep — Python/TS json.* in stdlib - Path escaping: "file with: colon.txt" parses cleanly - Single-line parse, no block-scalar state Body trailer keeps the human-readable ` - path` list for at-a-glance. Swift uses JSONSerialization; bash uses python3 json.dumps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sonichi
left a comment
There was a problem hiding this comment.
Cold review from Sutando-Mini.
LGTM with one real bug to flag + a few minor non-blockers.
What works:
- Multi-file Finder selection is the real fix; AppleScript iteration replaces hardcoded
item 1 of sel. - JSON-array on
paths:line per the schema discussion — single-line, no YAML lib, handles colons/quotes cleanly. - Body trailer with human-readable
- pathblock keeps the file readable for the Claude session. - Backward compat preserved: 0 → clipboard branch, 1 → existing
type: file, >1 → newtype: files. - Swift + bash implementations are parallel and consistent.
- Path-existence filter (
FileManager.default.fileExists/[ -e "$f" ]) drops stale aliases.
Bug — paths containing $ are corrupted in the bash branch.
In src/context-drop.sh:
cat > "$DROP_FILE" << EOF
...
paths: $PATHS_JSON
...|
Mini cold review — PR #595 (full version, supplementing my earlier truncated comment) The earlier review got truncated by a shell heredoc parsing issue. Pasting the full body here. LGTM with one real bug + a few minor non-blockers. Bug — paths containing In Variables expand inside an unquoted EOF heredoc. Repro: select a file named Smallest fix (option 3 in my draft): pre-escape (Use the escaped variant inside the heredoc.) More robust fix (option 2): write the file via python instead of heredoc. Since Same concern doesn't apply to the Swift branch — Swift's string interpolation doesn't shell-expand. Bash-only. Minor / non-blocking:
LGTM after the bash- |
|
Retraction on the Empirically tested via Output: Bash heredoc parameter expansion is single-pass: Should have run that test before claiming. Per Net for #595: no shell-escaping fix needed. The bash branch handles LGTM as-is. Sorry for the noise. |
* feat(context-drop): emit `type: files` task when multiple Finder items selected
`getFinderSelection()` and `context-drop.sh` both hardcoded `item 1 of sel`,
silently dropping multi-selections to first file. Iterate the selection,
emit a single task file with `type: files` + `paths:` block when count > 1;
keep the existing `type: file` + `path:` shape for the single-file case.
Notification text reflects the count ("3 files dropped" vs filename).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(context-drop): JSON-array paths for unambiguous parsing (per Mini's review)
Per Mini's feedback on PR sonichi#595: emit `paths: ["/a","/b"]` as JSON array on
one line instead of YAML-ish multi-line block. Reasons:
- No YAML lib dep — Python/TS json.* in stdlib
- Path escaping: "file with: colon.txt" parses cleanly
- Single-line parse, no block-scalar state
Body trailer keeps the human-readable ` - path` list for at-a-glance.
Swift uses JSONSerialization; bash uses python3 json.dumps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
) * feat(sparrow): self-claim re-enrollment on gateway auth rejection (#595 client half) On 401/403 the bridge parks ONE re-enrollment claim with provision-api; the approval code surfaces only on device-visible channels (log + a reenroll block in gateway-status.json — the desktop popup contract). Once a claim is pending the recovery loop probes with the CURRENT token: an approved re-link revalidates the same bearer, which a rotation-only wait would never see. Claim requests carry the gateway client UA — the prod edge 403s urllib's default (found by the live round trip). CLAUDE.md/AGENTS.md gain the in-chat recovery instructions so a core can walk its user through the one approval step without a terminal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sparrow): clear the claim episode on every recovery path; probe accepts success only; explicit recovered terminal (review P1 x3) Rotation-wins now clears the pending claim (no stale approval code published forever); _auth_probe returns True only on a successful authenticated response (a 5xx proves nothing about auth); recovery emits an explicit {pending:false, recovered:true} terminal so consumers never infer success from the block merely disappearing — a restart clears in-memory state without proving anything. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: move the re-link recovery manual to the desktop engine docs (owner request) The shipped user cores load the desktop repo's engine/CLAUDE.md; the user-facing recovery walkthrough moves there. The bridge behavior and its tests stay here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sparrow): re-claim on the probe cadence while nothing is parked A transiently-failed claim POST latched 'attempted' and never retried; on the standard install (TOKEN_FILE set via the .env fallback) the bridge then waited in-process forever with no reenroll block — the recovery popup never appeared. Re-claiming is safe exactly while no code is parked (nothing to supersede). Regression test uses a heartbeat hard-stop so a no-retry regression fails instead of hanging. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sparrow): clear stale recovered terminal at episode start; cadence-bounded claim retry; 2-line comments (review round 2) A new rejection episode pops recovered_at before anything else, so a failed claim can never republish the previous episode's terminal. Failed claim POSTs retry inside the recovery loop no sooner than REMOTE_REENROLL_CLAIM_RETRY_S (default 600s); a parked code keeps the one-claim invariant, and a missing-identity attempt costs nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(sparrow): consolidate claim retry on the cadence-bounded path The rebase carried two retry mechanisms (the probe-cycle attempted-reset and the internal REENROLL_CLAIM_RETRY_S cadence); one owner survives — the internal cadence, which also respects the server's claim rate limit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sparrow): resolve agent identity through the channel .env fallback (deployment gap G3) Live-confirmed: real installs' bridge processes carry NEITHER AGENT_MXID nor AGENT_ID — the desktop launcher doesn't export them, same class as the token. Identity now falls back to the channel .env (AGENT_MXID > AGENT_ID > AG2SPACE_USER_ID) via the existing _config_from_channel_env candidates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sparrow): monotonic claim cadence + collapse test comments (review P2 x2) last_attempt_at rides time.monotonic() with None as never-attempted, so a backward wall-clock step can't suppress claims and a fresh boot claims immediately; epoch time remains only where it is data (claimed_at, recovered_at). Clock-regression case pinned both ways. Added test comment blocks collapsed to two lines each. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Qingyun Wu <qingyun@ag2.ai> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
Both
src/Sutando/main.swiftandsrc/context-drop.shhardcodeditem 1 of selin their AppleScript, so a multi-file Finder selection silently dropped only the first file. Chi reported it after a multi-select drop arrived as one file.Fix
sel, returning newline-joined POSIX paths.getFinderSelection()returns[String](wasString?); zero-vs-one-vs-many gates the task format:type: file+path:(backward-compat).context-drop.shmirrors the same logic with bash arrays (VALID_FILES).Test plan
type: files+ multi-path blocktype: file(regression check)Follow-up
read_selectiontool (personal-deicticskill viaax-read.swift) readsAXSelectedText, not Finder file selection. It would need its own Finder-AppleScript branch to surface multi-file paths to the voice context. Not in this PR — flag for separate.🤖 Generated with Claude Code