Skip to content

Devmain#52

Open
a-Fig wants to merge 32 commits into
mainfrom
devmain
Open

Devmain#52
a-Fig wants to merge 32 commits into
mainfrom
devmain

Conversation

@a-Fig

@a-Fig a-Fig commented Jul 3, 2026

Copy link
Copy Markdown
Owner

No description provided.

smash and others added 30 commits July 1, 2026 07:55
docs(readme): fix missing apostrophes
The live link hard-coded httpServer.listen(0, "127.0.0.1"), so a pi session
running on a remote box could not be reached from a laptop browser. Add two
config knobs — accordion-host/ACCORDION_HOST and accordion-port/ACCORDION_PORT
(flag wins over env, defaults 127.0.0.1 + 0-ephemeral) — mirroring the existing
ACCORDION_APP pattern.

Security model (decided): when bound off-loopback, the WS upgrade requires the
per-session webToken for any NON-loopback peer; loopback peers stay tokenless
so the desktop Tauri app and a same-machine browser are unchanged. The token
already travels in the browser-served URL (?token=…), so the legitimate user
never types it — the gate only keeps a network port-scanner from steering the
agent's context. Auth runs in verifyClient on the HTTP upgrade path, never on
the pi context/model-call hook.

App side: connectLive(port, { host, token }) forwards the host + token on the
WS URL; browser-served callers pass window.location.hostname + the token from
the URL/cookie. Desktop discovery stays loopback/tokenless.

Default config (no flags/env) is byte-for-byte today's behavior. Smoke test,
svelte-check, and vitest (797) all pass; the smoke token regex still matches.
)

The static-file surface (isWebAuthed) accepts the token via query OR the
HttpOnly accordion_token cookie, but the WS verifier only checked the
upgrade URL. On a reloaded/bookmarked browser-served session with no
?token=… in the URL, JS can't read the HttpOnly cookie to forward it, so
connectLive opened the socket tokenless and the off-loopback upgrade was
rejected — even though the HTTP app loaded.

Mirror isWebAuthed in verifyWsUpgrade: accept the cookie too. The browser
auto-sends it on a same-origin WS upgrade, so the same source of truth
authorizes both halves, and the HttpOnly setting (token not exposed to JS)
is preserved.

Also drop the JS cookie read from readServedToken() (unreachable: HttpOnly)
and document where the cookie fallback actually lives.
feat(extension): allow binding 0.0.0.0 + custom port (#42)
Each pi session's extension already advertises itself to ~/.accordion/sessions/
for the desktop app's discovery. That directory read doesn't actually require
Tauri's native fs layer — the extension process itself is plain Node, so it can
read every session's registry entry too, not just its own. Expose that over a
new token-gated /__accordion/sessions endpoint, and have the browser UI poll it
(browserDiscovery.svelte.ts) the same way the desktop app polls list_sessions.

The result: a user with only the browser-served web app (no desktop app) now
sees every live pi session in the left sidebar and can switch between them,
instead of a single trimmed "reconnect" row per browser tab/port.

SessionsSidebar's browserServed prop now only hides the pi/Claude-Code source
switcher (CC transcript browsing still needs the Tauri Rust layer to read
~/.claude) rather than trimming the whole session list.
…reap

- browserDiscovery.svelte.ts: the sessions poll is a relative fetch, so it's
  pinned to whichever session's origin served the page. If that session exits,
  the poll goes permanently silent and the sidebar could wrongly show "No live
  sessions" while a different session is still connected. Add a
  connectedFallback() that always keeps the actively-connected session visible
  from its own live socket state, independent of that poll.
- liveClient.svelte: add live.port so the fallback above can report the real
  port of whatever session is currently connected (needed since it may differ
  from the page's own serving port after switching sessions).
- accordion.ts: listLiveSessions() now uses fs.promises instead of fs.*Sync —
  it runs on the same event loop as the context hook's 250ms fold-plan
  deadline, and is now polled every second per browser tab. It also
  opportunistically reaps stale/dead registry entries it encounters, since a
  browser-only user (this feature's target audience) has no desktop app ever
  running to do that cleanup. Documented the token blast-radius tradeoff this
  endpoint introduces (a leaked token now reveals every session, not just one).
- smoke.mjs: cover the corrupt-sibling-file and stale-heartbeat-sibling paths,
  assert the stale entry gets reaped from disk, and guard the fixture cleanup
  with try/finally.
- Extract publishSessions() in discovery.svelte.ts, shared by both the
  desktop (Tauri invoke) and browser-served (HTTP fetch) poll loops, so the
  publish/reap-selection invariant can't drift between the two sources.
- browserDiscovery.svelte.ts: drop `credentials: "same-origin"` (already the
  fetch() default) and the redundant client-side isLiveEntry re-filter (the
  server already validates/reaps before responding — see listLiveSessions()).
- Consolidate the "relative fetch is pinned to the serving origin" limitation
  into one authoritative comment instead of three overlapping ones.
…dation-dg8bd6

Support multi-session discovery in browser-served mode
In browser-served mode the sidebar lists every live pi session, but
selectAndConnect reuses the page's OWN per-session token for every
sibling. On loopback that's fine (verifyWsUpgrade ignores the token for
loopback peers), but from a genuinely remote / 0.0.0.0-bound origin a
sibling rejects that token and the click failed with a misleading
"could not reach pi on :port" error — the rail advertised sessions it
could not actually open.

Make the state honest: when the page was reached over a non-loopback
origin, only the served session (whose token is in our URL) is
steerable. Every other row now renders loopback-only — dimmed, a lock
badge / lock icon, non-clickable, with a tooltip explaining to open it
from that session's own /accordion URL. selectAndConnect guards the same
condition so no path (incl. /accordion focus requests) dials a session
that would just be token-rejected. Fails open (marks nothing) when the
served session id is unknown, and is inert on desktop / same-machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial review follow-ups:

- isLoopbackHost now mirrors the extension's isLoopbackPeer: strip the
  [..] brackets location.hostname wraps around an IPv6 literal, strip the
  ::ffff: IPv4-mapped prefix, and accept the expanded 0:0:0:0:0:0:0:1
  spelling. Without this, reaching the browser-served page over an
  IPv6-loopback URL (e.g. http://[0:0:0:0:0:0:0:1]:PORT or a mapped
  ::ffff:127.0.0.1) was misread as remote and falsely locked every
  sibling row the server would in fact serve tokenless.
- Drop locked rows from the tab order (tabindex="-1") so keyboard users
  don't land on a non-actionable control.
- Reword the selectAndConnect guard comment: it's defense-in-depth for
  the click path, not a guard against the /accordion focus path (which is
  Tauri-only and never crossSessionRemote).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(sidebar): mark loopback-only sessions in remote browser-served mode
launch.json gains an optional portEnv field naming the env var each
out-of-process conductor reads its listen port from. Lets a spawning
host (desktop app, bellows) assign a free port per instance so several
runs can coexist. recency-folder learns RECENCY_PORT (was hardcoded
7700). Documented in conductors/README.md; fixed thermocline README's
mislabeling of launch.json as a VS Code config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(conductors): portEnv launch.json convention for host-assigned ports
A first-party in-process conductor that simulates the hard reset — write a
handoff document, /clear, and reseed a fresh agent that has nothing but the
handoff. The sibling foil to naive compaction.

The distinguishing mechanic: it holds the `tail-size` involvement lock with a
small `tailTokens` (8k) so it OWNS a deliberately tiny tail and folds nearly
the whole conversation into one handoff — a deep sawtooth, versus naive
compaction's gentle curve, which rides the human's ~20k tail. Everything else
(single-group-with-LLM-digest shape, visible-window hysteresis, recursive
amnesiac prompt, in-flight / stale-completion / attempt-key guards, degrade
path) is inherited from the battle-tested naive-compaction state machine. The
completion is a handoff document (Original request verbatim / Task / Current
state / Next steps / Key files / Gotchas / How to resume) rather than a
compaction summary. Lossy + non-recoverable (no {#code FOLDED} tag — a fresh
agent genuinely lacks the originals); the human's recourse is detach.

First in-process conductor to exercise the `tail-size` lock plumbing. 38 tests
through both a MockHost and AccordionStore.applyCommands (asserting the ~8k tail
is owned, setProtect is inert, no clamps on the happy path, and the multi-part
boundary-straggler group is refused invalid-group then self-heals).

Adds ADR 0017 and conductor README; registers one line in IN_PROCESS_CONDUCTORS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second adversarial review found no correctness bug but flagged two real
test-confidence gaps and two doc-precision nits. Addressed:

- Strengthen the store-integration tail assertion from `>0 && <length` (which
  passed for almost any tailTokens) to the exact walk-back boundary
  (protectedFromIndex === 4, protectedTokens === 1000).
- Add a differential test: on identical blocks, the 8k tail-size lock protects
  strictly fewer blocks (index 4) than a human 20k tail (index 2) — proving the
  fresh-start claim that justifies a separate conductor, which had zero coverage.
- Docs: the "~8k tail" is a target, not a guarantee — the walk-back keeps whole
  blocks up to a 25% cap (one block min, ~10k max); reword the tailTokens=0
  rejection rationale to match the actual `target===0 → protectedFromIndex =
  blocks.length` behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(conductors): Handoff (fresh start) conductor (closes #24)
…e protected tail (#43)

A conductor without the tail-size lock can now fold a fresh, never-sent
foldable block despite the protected working tail. The exemption is
sticky (host-recorded birthFolded set, outside the per-pass reset) so it
survives reset-to-baseline reapplication while the block remains in the
tail. New SyncMessage.planned flag (PROTOCOL_VERSION 6) lets the GUI
advance its sent cursor only on plan-bearing context syncs; bulk-loaded
transcripts mark everything sent so read-only sessions never read fresh.
Adds ViewBlock.fresh for conductor authors, birth-fold-demo conductor,
through-store test suite, and ADR 0017.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…content (#46)

Conductors can now bring a folded block's original text back into the
agent's context without unfolding it in place. New RecallCommand records
the block in a host-side recalled map (outside the per-pass reset) with
a frozen anchor; the plan carries RecallOp {id, afterId, text}
(PROTOCOL_VERSION 7, CONDUCTOR_PROTOCOL_VERSION 4) and applyPlan inserts
one synthetic user-role message after the anchor, with group-swallow
fallbacks. Recalls are sticky until released via restore(id) or until
the block is unfolded — a deliberate departure from full-state
semantics to protect the prompt cache. Injected tokens count toward
liveTokens; Inspector shows a 'recalled to tail' badge. Adds recall-demo
conductor, through-store + mapping test suites, and ADR 0018.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- full:true syncs now append with {sent:true}, closing the reconnect
  protection bypass where replayed history read fresh and could be
  birth-folded into the sticky exemption set (MAJOR)
- recalledTokens gates on isFolded — no mid-pass double count in the
  conductor's liveTokens baseline
- recall anchor skips tool_call blocks so the injection can never split
  a call/result pair
- recall record/release/drop now emit activity-feed + decision-journal
  events (by-attributed) like every other conductor action
- computeRecallOps mirrors computeFoldOps' non-empty-digest guard
- birth-fold-demo skips held blocks; recall-demo documents sticky
  accumulation
- all remote example conductors + version-asserting smoke tests bumped
  to conductor protocol v4 (the strict hello gate refuses v3)
- ADR 0017/0018 + conductor-protocol.md updated to match

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge-readiness review findings (1 HIGH + 2 MED), all in the new #43/#46 code:

- HIGH: newestRecallAnchor skipped only blocks whose OWN kind is tool_call,
  so a conduct pass on a view-only message_end sync mid-tool-loop could
  freeze the anchor on the text/thinking SIBLING of an unresolved call —
  applyPlan would then inject the synthetic user message BETWEEN the call's
  message and its tool_result (provider 400 on the live steering path).
  Fixed on both sides: the anchor picker now skips every block of a
  tool-calling message (messageEmitsToolCall, reusing messageKey), and
  applyPlan slides any interior insertion forward past tool_result
  message(s) as a wire-side backstop (covers the group-swallow walk-back).

- MED: birthFolded could outlive its truth — a block that settled live and
  crossed the wire whole on a planned sync kept its exemption, letting a
  later pass (or a different conductor after a swap) fold already-seen
  protected content. markSent() now prunes any entry whose block is not
  folded at apply time; with the set truthful, attach/detach/reset
  deliberately do NOT clear it (documented in the field doc + ADR 0017).

- MED: detach popped an active birth-fold back to full — freezeForDetach
  stamped it override:"folded" and the detach refold's healProtected
  force-unfolded it, re-blowing the budget detach exists to protect.
  healProtected now skips blocks still carrying the birthFolded exemption.

Tests: anchor sibling-skip + per-message gate (store.recall), applyPlan
pairing backstop with an adjacency assertion (mapping.recall), settle-live
prune / detach freeze / conductor-swap legality (store.birthfold).
839 passed, svelte-check 0/0, extension smoke green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disarmed folding replies with an EMPTY plan (liveClient computePlan), so the
model call carries every block WHOLE even while the view renders folds. The
markSent prune keyed on isFolded alone would keep those exemptions, and a
later ARMED run could then birth-fold protected content the model has
factually seen (the disarm→arm leak). markSent now takes { rawWire } from
the one caller that knows the wire was raw, and clears the whole set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tool_call

Verify-pass LOW: the interior-insertion slide didn't cover the -1
append fallback. If the array's tail were ever an assistant message with
an unanswered tool_call, appending the synthetic user message after it
would split the pair. Land the injection just before such a tail instead.
Unreachable at the context hook (the wire never ends on an unpaired call)
— pure shape defense, same never-trust-the-peer stance as the rest of
applyPlan. Pinned by a new mapping.recall test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…off fixture

PR #55 (handoff conductor) merged to devmain claiming ADR 0017, colliding
with this branch's birth-fold ADR. Renumber: birth-fold 0017→0018, recall
0018→0019, including every textual reference (handoff's own ADR 0017
references and keel's pre-existing plan-doc 0017 references untouched).

Also add the new required ViewBlock.fresh field to conductor.handoff.test's
fixture — the one svelte-check error in the combined tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(conductors): birth-fold (#43) + recall command (#46)
…rth-fold + recall

Conflict resolutions (protocol.ts, liveClient.svelte.ts, accordion.ts):
- PROTOCOL_VERSION stays 7 (devmain ceiling); armed/armedAck keep main's
  documented no-bump convention, history comment carries both sides.
- Plan type unions both: {ops, groups, recalls} inside main's discriminated
  PlanResult; the GUI's recalls array rides the "plan" result.
- requestPlan keeps main's (reqId, full, blocks, armedNow) signature and
  PlanResult semantics, plus devmain's planned:true birth-fold contract note.
- Stale-plan timeout fallback now replays lastPlan.recalls alongside ops and
  groups (a recall is per-call desired state like a fold; applyPlan's anchor
  fallback keeps stale anchors safe), and hasStale counts recalls.

Known gap carried forward (pre-existing on both parents, issue #60): the GUI
cannot observe a timeout fallback, so birth-fold's markSent() may retain an
exemption for a block that actually rode the wire whole.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…020)

Every context-hook resolution now lands in exactly one of seven outcome
causes (applied / empty-plan / timeout-stale / timeout-raw / no-gui /
epoch-mismatch / unsent), counted in-memory for the extension lifetime and
exposed via /__accordion/meta for the bench rig to poll. Reachable-client
outcomes are also acked to the GUI as a new additive `passthrough` wire
message (no PROTOCOL_VERSION bump, same convention as armed/armedAck).

The ack is also a correctness fix: on timeout-stale/timeout-raw the GUI's
fresh plan did not ride the wire, so liveClient reconciles birth-fold
bookkeeping with markSent({rawWire:true}), closing the exemption leak the
devmain merge documented. Previously-silent branches now warn on the
transition into a silent state (not per call).

MapHeader gains a minimal monochrome "wire N/M" readout with a per-cause
tooltip, hidden when no acks have been seen (read-only/demo show nothing).

Out of scope, carved from the issue: provider-usage vs wireTokens
cross-check (needs new data capture; bellows-side consumer).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
smash and others added 2 commits July 9, 2026 15:58
Adversarial-review follow-ups: an unknown wire cause no longer creates a
spurious counter key or bumps total, and a new liveClient test pins the
transport-ordering invariant the birth-fold reconciliation rests on (an
ack for reqId N must not mark blocks from a later sync as sent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(wire): plan-applied ack + passthrough counters (issue #60, ADR 0020)
@a-Fig a-Fig marked this pull request as ready for review July 9, 2026 23:09
@a-Fig

a-Fig commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

Static review only, per request: I did not build the project or run tests/live sessions. I reviewed the full main...devmain diff plus adjacent code. These are the PR-introduced/integration findings I believe should be addressed before promotion.

High

  1. Handoff failures become silent, non-retryable stalls (conductors/handoff/handoff.ts:223-230, 389-398). The rejection handler drops the actual error and deliberately retains lastAttemptKey; the next pass clears status before returning early for that same key. A provider/auth/network/context-limit failure therefore leaves a session at the 90% trigger with no visible error and no retry until a new block changes the key or the conductor is detached. Please persist a failure status and provide an explicit or bounded/backoff retry path.

  2. Handoff does not reserve context space for its requested output (conductors/handoff/handoff.ts:35-52, 345-358). It triggers at 90% of budget, sends the entire handoff input plus wrappers/system, and always asks for up to 8,000 output tokens. With the normal budget === contextWindow initialization, a 32k model is already around 28.8k input tokens before wrappers; +8000 exceeds the window and can be rejected, feeding directly into finding 1. Derive maxOutputTokens from view.contextWindow - estimatedInput - safetyMargin, and/or trigger early enough to guarantee the output reserve.

  3. Raw-wire reconciliation leaves an illegal protected fold in store state (app/src/lib/engine/store.svelte.ts:1015-1040, especially 1033; zero-delta early returns at 1543/1550). markSent({rawWire:true}) clears the birth-fold exemption after the model sees the block whole, but does not heal/refold the still-autoFolded protected block. This violates the repo's preview-is-no-more-permissive invariant. Worse, a later zero-delta context sync does not call refold(), so arming before that hook can send the stale protected fold even though its exemption is gone. Heal/refold affected blocks immediately when raw-wire reconciliation clears the exemption, and assert the block becomes live at once.

Medium

  1. A failed browser discovery poll can cancel a healthy cross-session connection (app/src/lib/live/browserDiscovery.svelte.ts:69-88; disconnect in discovery.svelte.ts:123-132). Network/403/500/non-JSON failures are converted into a successful empty list. While switching A→B, connectedFallback() is still null during B's handshake; publishing [] clears selected B and calls disconnectLive(). Retain the last successful list on poll failure and do not reap a selected/connecting entry until the connection resolves.

  2. Browser auth cookies collide across session ports (browserDiscovery.svelte.ts:71; extension/accordion.ts:763-768, 831-835). Cookies are host/path scoped, not port scoped, but every session writes the same accordion_token. Opening B overwrites A's cookie; A's tokenless /__accordion/sessions poll then gets 403, which also triggers finding 4. Include the current URL token on the poll when available and use a session-specific cookie name for cookie-only reloads.

  3. IPv6 bind settings print unusable URLs (extension/accordion.ts:1641-1649). ACCORDION_HOST=::1 binds IPv6 but prints 127.0.0.1, while ::/other IPv6 literals are interpolated without brackets (for example http://:::8080). Preserve the actual loopback family and bracket IPv6 literals when serializing URLs; handle wildcard display hosts separately.

  4. Binding a specific LAN interface breaks desktop discovery (extension/accordion.ts:145-169, registry entry 541-555; liveClient.svelte.ts:241-263). The feature explicitly accepts a specific interface such as 192.168.1.20, which excludes loopback. The registry advertises only a port and Tauri always dials 127.0.0.1, so the desktop lists a session it cannot connect to. Advertise a connect host in SessionEntry (and mirror it in Rust/client), or reject/document specific-interface binds and support only loopback/wildcard hosts.

  5. Custom port/host bind failures are swallowed (extension/accordion.ts:955-960, surfaced at 1636-1651). EADDRINUSE, EADDRNOTAVAIL, etc. discard the actual error and leave /accordion reporting port starting / Browser starting... forever with no retry. Store/log the bind error and show it in /accordion; explicit-port failure should be visible rather than silently falling back.

  6. Passthrough telemetry reports submitted rather than actually-applied operations (extension/accordion.ts:1400-1407, 1425-1426; filtering in app/src/lib/live/mapping.ts:317-383). The new protocol promises counts actually applied, but the ack records input array lengths before applyPlan() drops missing IDs, unsafe group members, or malformed recalls. A nonempty no-op plan is still labeled applied. Have applyPlan() return effective diagnostics/counts and emit the ack after application.

Documentation drift

  • extension/README.md:43-46 still says the npm/browser experience is single-session and requires desktop for multi-session discovery, contradicting this PR.
  • CLAUDE.md:55 says the WS is unauthenticated, contradicting the new off-loopback token gate.
  • conductors/README.md:177-182 says the desktop launch button uses portEnv, while PR feat(conductors): portEnv launch.json convention for host-assigned ports #54 explicitly states the Tauri launcher ignores that field. Clarify that it is currently for external spawning hosts such as bellows.

I also found several issues that predate this PR. One is security-sensitive, so I have intentionally not included the exploit details in this public comment; I am reporting those separately to the maintainer rather than attributing them to this promotion.

-gpt-5.6 SOL

@a-Fig

a-Fig commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

Second-pass adversarial follow-up. These are newly verified findings beyond my first review comment. Static analysis only; no builds or live tests were run.

High

  1. passthrough is correctness-critical, so keeping it as an optional no-bump addition to protocol v7 is unsafe (app/src/lib/live/protocol.ts:35-61; liveClient.svelte.ts:391-396, 490-528; ADR 0020 lines 92-102). The protocol comment says an old v7 peer may ignore this frame because it is “purely informational,” but timeout acks are the only mechanism that repairs optimistic birth-fold bookkeeping. A pre-ack v7 extension with the new v7 GUI, or the inverse pairing, passes the strict hello check. On a timed-out fresh plan the model can receive stale/raw content while birthFolded survives, later allowing already-seen content to fold inside the protected tail. armedAck does not advertise this capability. Please bump to v8, or require a negotiated capability before birth-fold is enabled; lack of support must use conservative raw/sent bookkeeping.

  2. A supported positional-ID live block can acquire a permanent birth-fold exemption even though its fold can never reach the wire. The live mapper deliberately emits m… fallbacks when response/timestamp anchors are absent (mapping.ts:64-77; this shape is exercised in extension/smoke.mjs:830-905). substOne records a protected fold in birthFolded without checking durability (store.svelte.ts:1430-1454), while computeFoldOps silently drops it (plan.ts:37-50). Armed mode then sends an empty plan, but markSent({rawWire:false}) preserves the visibly folded exemption and the empty-plan ack does not reconcile it (liveClient.svelte.ts:391-395, 503-528; extension 1413-1422). The model repeatedly receives the block whole while the UI/token accounting says it is folded, and the false “never seen whole” exemption survives conductor swaps/detach until it ages out.

    The robust fix is to clamp non-durable per-block folds when wireAttached===true before setting autoFolded/birthFolded, and make markSent preserve exemptions from the plan IDs actually emitted/applied rather than store.isFolded. Treat an all-empty plan as raw for exemption truth. Keep parsed/demo stable IDs separate rather than globally rejecting them.

Medium

  1. Conductor recall is broken on parsed Pi, Demo, and Claude Code sessions (app/src/lib/engine/parse.ts:80-110, 139-185; store.svelte.ts:1315-1321, 1371-1378). Parsed blocks use stable event IDs such as <eid>:r, <eid>:0, and <eid>:u, but isDurableId recognizes only the live-wire u:/a:/r:/s: formats. recallOne therefore rejects every parsed target, and the anchor picker rejects every parsed anchor. Loading the bundled sample or a Claude transcript and selecting Recall demo folds in preview, then repeatedly clamps recall as not-recallable, contrary to ADR 0019's read-only/preview parity claim. Model durability explicitly on Block/ViewBlock, or recognize parser-stable event IDs consistently; add parsed Pi and Claude fixtures to the recall tests.

  2. Recall can be injected before the block whose content it recalls (store.svelte.ts:1320-1322, 1371-1378). If a conductor birth-folds and recalls text/thinking in a fresh assistant message that also emits a pending tool call, the anchor picker correctly skips that entire message but falls back to the previous user/result. applyPlan then inserts the synthetic user recall before the assistant originally emitted that content. The frozen anchor makes the temporal inversion permanent. Require a safe anchor at or after the target message; while its tool call is unresolved, defer/clamp the recall until the result arrives and then anchor after the completed pair.

  3. Port parsing accepts partial garbage despite the “invalid → 0” contract (extension/accordion.ts:164-170). parseInt("8080junk", 10) binds 8080 and parseInt("1.5", 10) binds port 1. Validate the entire trimmed value with an integer grammar plus Number.isInteger/range checking, and surface invalid explicit configuration rather than silently choosing a different port.

Low

  1. The browser-side stale reaper can delete a newly refreshed session entry (extension/accordion.ts:615-625). It can read A's old heartbeat as stale, race A atomically replacing the file with a fresh heartbeat, then unlink the fresh path. The live session disappears until its next heartbeat. Avoid reaping entries whose PID is still live and revalidate before deletion; a reread alone narrows but does not eliminate the rename/unlink race.

I also found additional security-sensitive defects in the changed remote-auth/session-boundary path. I am intentionally withholding the reproducer and token-lifecycle details from a public comment until the maintainer chooses a disclosure/fix path.

-gpt-5.6 SOL

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