[cold-review] feat(peer): ping_peer delivery provenance (#1203) - #118
Draft
heavygee wants to merge 148 commits into
Draft
[cold-review] feat(peer): ping_peer delivery provenance (#1203)#118heavygee wants to merge 148 commits into
heavygee wants to merge 148 commits into
Conversation
heavygee
force-pushed
the
feat/a2a-p05-peer-provenance
branch
from
August 9, 2026 20:56
6444d0a to
b8c4c0e
Compare
Stop ghost user messages from peer nudges (tiann#1203 / A2A Layer 0.1): CLI stamps X-Hapi-Peer-Delivery from HAPI_SESSION_ID, hub stores sentFrom=peer with store-validated source session, and web badges the source link. Co-authored-by: Cursor <cursoragent@cursor.com>
Match rich-composer @mention chrome for who sent a peer nudge so provenance reads like an @ reference, not a separate prose badge. Co-authored-by: Cursor <cursoragent@cursor.com>
Close cold-pass-1 B1/M1: web JWT sends never trust body sourceSessionId; attributed delivery goes through POST /cli/sessions/:source/peer-messages. Receiving agents get a From: /sessions/<id> prefix for reply targeting. Co-authored-by: Cursor <cursoragent@cursor.com>
…te Pi Close Sol pass-2 B1/M1: peer-messages needs an HMAC capability minted at CLI create/load (hub JWT secret), so path+CLI-token alone cannot forge source. Pi preparePiUserMessage now suffixes From: /sessions/<id>. Co-authored-by: Cursor <cursoragent@cursor.com>
…sume HappyUserMessage now selects Object.is-stable primitives for peer chrome. Session-scoped CLI sockets receive hub-minted peer-capability so resumed wrappers attribute ping_peer without exposing mint on namespace-token GET. Co-authored-by: Cursor <cursoragent@cursor.com>
Socket peer-capability now requires the create-time session tag so a namespace-token sibling cannot steal another session's mint. Persist tag plus capability under HAPI_HOME for resume, await readiness in MCP ping_peer, and refuse silent unattributed downgrade when a source id is set. Co-authored-by: Cursor <cursoragent@cursor.com>
Remove HAPI_HOME peer-session credential files that same-UID siblings could read. Keep the bearer in the session parent and expose delivery through a descendant-gated unix broker. Resume injects the create-time tag via runner env for tag-gated socket mint; wrapped hapi ping-peer fails closed without the broker instead of silently sending unattributed. Co-authored-by: Cursor <cursoragent@cursor.com>
Hand resume mint tag via stdio fd 3 (not environ), port peercred to macOS, verify broker listener ancestry, unattributed fallback for terminal resume, and refuse RPC method shadowing of spawn payloads. Co-authored-by: Cursor <cursoragent@cursor.com>
Sync-consume the runner tag pipe on module load and at bootstrap entry so same-UID siblings cannot race /proc/<pid>/fd during ApiClient awaits. Co-authored-by: Cursor <cursoragent@cursor.com>
Stop putting create-time tag on resumed CLI fds/env (pidfd_getfd). Hub arms a 30s single-use mint when spawning resume; CLI connect consumes it. Create path still uses in-memory sessionTag. Co-authored-by: Cursor <cursoragent@cursor.com>
Stop first-connector /cli mint during the arm window. Hub arms a spawn-RPC nonce; runner redeems over HTTP and injects capability into the expected child via peercred/PID-checked unix socket. Co-authored-by: Cursor <cursoragent@cursor.com>
…ss2i B1) CLI CI uses Vitest, not bun:test. Align peerCapabilityInject tests with peerDeliverBroker peers so the suite gates CI. Refresh stale connect-mint comment. Co-authored-by: Cursor <cursoragent@cursor.com>
Address Codex review on tiann#1473: - Require create-time machineTag for machine room join and machine-scoped rpc-register so namespace token + machineId cannot steal spawn nonce - Load Linux peercred via libc.so.6 getsockopt only (no Darwin getpeereid) - Ack + retry rpc-register across reconnect ownership overlap - Await runner capability inject before resume bootstrap returns Co-authored-by: Cursor <cursoragent@cursor.com>
Unit mocks (and older clients) lack socket.timeout; fall back to plain emit and treat missing ack as fire-and-forget success. Co-authored-by: Cursor <cursoragent@cursor.com>
Fail closed on untagged machine re-bind (upgrade first-claim forgery), authorize session-scoped rpc-register, keep resume mint nonce idempotent under concurrent /resume, and auto-rotate machineId on hub 409 re-enroll. Also widen flaky claudeRemote timeout to match sibling tests. Co-authored-by: Cursor <cursoragent@cursor.com>
getOrCreateMachine now always receives the optional tag slot; update the mock assertion so CI matches the call shape. Co-authored-by: Cursor <cursoragent@cursor.com>
Host-fallback resume routing trusts self-reported metadata.host, so a same-namespace machine can steal the mint. Arm the nonce only when the online machine matches the session's recorded machineId; shorten default broker socket paths to fit portable unix sockaddr limits. Co-authored-by: Cursor <cursoragent@cursor.com>
Make resume capability inject async with platform/listen failure returning null so the runner stays up on Windows or bad paths. Persist rotated machineId into runner local state after legacy re-enroll. Co-authored-by: Cursor <cursoragent@cursor.com>
Notify lazy session materialization when hub forces machine re-enroll so the pending snapshot cannot overwrite the new machineId. Swallow broker connection handler rejections so mid-request disconnects do not crash the parent session via unhandledRejection. Co-authored-by: Cursor <cursoragent@cursor.com>
Authorize session RPC only with create-time tag or HMAC capability, and reconnect after resume inject so auth carries the capability. Require machineTag on already-tagged machine rows. Make legacy machineId rotation idempotent across concurrent 409 handlers. Co-authored-by: Cursor <cursoragent@cursor.com>
Gate peer deliveries before wrapper slash/special-command parsers and prefix Pi provenance so first-line skills cannot fire. Terminal resume mints sessionCapability via machine-tag ownership of the session's recorded machineId so session-scoped RPC can register without the create-time session tag. Co-authored-by: Cursor <cursoragent@cursor.com>
…pecials Remove the sibling-forgery resume mint that trusted shared machineTag. Terminal resume now requires runner one-shot inject. Peer /clear|/compact no longer enter Codex's second parser. Co-authored-by: Cursor <cursoragent@cursor.com>
machineTag in shared settings can no longer own spawn-happy-session after the runner drops. Require a process-local runnerProof lease (sticky on disconnect). Restore direct hapi resume without inject as unattributed. Co-authored-by: Cursor <cursoragent@cursor.com>
…ases Hub stores sha256(runnerProof) on machine rows (schema v24); websocket auth only verifies that generation. Reject meta.sentFrom=peer on generic CLI message ingress. Keep previousMachineIds so local resume survives legacy machine rotation. Co-authored-by: Cursor <cursoragent@cursor.com>
Do not persist runnerProof in runner.state. Existing machines with a null proof hash (or mismatched proof) force machine-id re-enroll. Bump migration tests to schema v24. Co-authored-by: Cursor <cursoragent@cursor.com>
Pass memory-only runnerProof through authorized version-handoff env (drained immediately). Await PeerDeliverBroker listen before exporting broker env so agents cannot snapshot a missing path. Co-authored-by: Cursor <cursoragent@cursor.com>
Stop putting runnerProof in replacement-runner environ; deliver via the existing PID-checked inject socket (path only in env). Terminal bootstrap no longer POSTs /cli/machines (runner INSERT binds the proof hash). Direct hapi resume without inject fails before launch so session RPC is not a zombie surface. Co-authored-by: Cursor <cursoragent@cursor.com>
Unwrap message envelopes before rejecting forged peer meta. Require the bound runnerProof on every machine registration. Recover proof from the OS keyring across ordinary restarts and migrate session machineIds on re-enroll. Direct hapi resume acquires runner-proven inject before any remote handoff stop; Windows gets a named-pipe inject transport. Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): preserve session sidebar scroll during navigation * fix(web): preserve nested session pane scroll * fix(web): avoid repeated file scroll restoration * fix(web): preserve scroll when leaving chat file previews * fix(web): preserve scroll after session resolution
…equest (tiann#1534) * fix(codex): cache codex model list to avoid spawning app-server per request listCodexModels() spawned a fresh `codex app-server` subprocess on every request: exec a version probe, boot the app-server, validate the ChatGPT session (token refresh over the network when needed), list models, then kill the process. Fleet measurement showed 0.5-4.4s per call on healthy machines and 33s (initialize timeout) on a machine with a slow OpenAI network path, and the web refetches on every session open / dialog mount (staleTime 30s). Mirror the opencode model cache: cache successful non-empty lists for 5 minutes and coalesce concurrent requests into a single app-server spawn. Failures and empty results are never cached, so a broken machine retries on the next request. Fixes tiann#1533 * test(codex): cover TTL expiration of the model list cache
tiann#1563) * fix(pi): settle autonomous agent lifecycles instead of swallowing them When Pi starts an agent lifecycle on its own — a subagent completion wake-up, scheduled work — no HAPI prompt is in flight, so the previous prompt lifecycle has already delivered its settlement and deliveredSettlement is still true. agent_start unconditionally set thinking=true, but every settlement path (agent_settled delivery, the legacy agent_end grace, the prompt-lifecycle fallback) was gated shut by that stale flag, so the autonomous turn's completion was swallowed: thinking stayed true forever, the FIFO pump stayed blocked (piIsStreaming), new messages queued without ever being sent, and abort waited on a settlement that could never arrive. The only escape was killing the session. Open a fresh settlement cycle when agent_start/turn_start arrives with deliveredSettlement still true: reset deliveredSettlement, agentEndObserved and activeAgentSettledSeen so the existing settlement paths (direct agent_settled, legacy agent_end grace) apply to the autonomous lifecycle. Prompt-driven lifecycles are unaffected because beginPromptLifecycle has already reset the flag before their agent_start arrives; mid-cycle retries are unaffected because their cycle has not settled yet. Evidence: hapi log 2026-08-13-18-39-10-pid-85758.log — 19:24:07 agent_start (no prompt accepted) → 19:25:40 agent_end + agent_settled both swallowed → session stuck thinking=true for 27+ minutes until killed. * fix(pi): generation-scope the settled callback against autonomous lifecycle races Review follow-up (HAPI Bot, Major): deliverSettlement() notifies onAgentSettled only after an async conversationHistory.syncEntries(). An autonomous lifecycle can begin in that window; the stale finally callback would then mark the new lifecycle's abort boundary as settled before it emits agent_settled. Capture lifecycleGeneration at settlement time and skip the notification when it no longer matches, and advance the generation when agent_start reopens a settlement cycle for an autonomous lifecycle so the in-flight callback turns stale. New regression test holds syncEntries open across the autonomous agent_start and asserts the stale callback does not settle the new boundary (fails without the fix).
* fix(web): prevent replaying historical assistant output * fix(web): handle history pagination during typing handoff * fix(web): preserve streaming handoff across history windows * fix(web): distinguish hydrated active runs from new output * fix(web): preserve handoff across tail hydration * fix(web): preserve first output in user-only turns
) * feat(search): add wildcard matching to search fields * fix(search): harden wildcard matching and file globs * fix(search): align file matching with shared wildcard semantics * fix(search): bound file wildcard search in runner * fix(search): normalize outline queries through shared matcher * fix(web): remove duplicate markdown test context field
* fix(cursor): retry transient ACP connection errors * fix(cursor): keep retry classification conservative * fix(cursor): avoid retrying completed tool effects * fix(cursor): require terminal retry failures * fix(cursor): track retry activity across extension events * fix(cursor): honor permission abort before retry
…sion (part of tiann#1438) (tiann#1475) * feat(web): composer model/effort value buttons and settings order Wide composers now show [model] and [effort] value buttons for non-Pi flavors (labels from the current session values), opening the settings sheet on click. Narrow viewports collapse to the settings button only via a new useNarrowViewport hook. The settings sheet reorders to Model -> Effort -> Permission -> other settings (Fast mode, collaboration, Copilot agent mode) so permission is first-class. Toolbar customization gains 'model'/'effort' items with settings labels. Pi keeps its dedicated model/thinking panels unchanged (unified descriptor-driven sheet is a follow-up). * fix(web): satisfy strict types in composer value-button test harness * fix(web): address review findings on composer model/effort value buttons - Normalize null/'auto'/'default' model wire values onto the value:null option so default-model sessions keep a localized label button (Major) - Exempt model/effort value buttons from the settings outside-click dismissal so a second click closes the sheet instead of reopening it - Read matchMedia synchronously in useNarrowViewport so narrow first paints never flash the wide toolbar - Add regression tests: model=null/'auto' labels, toggle-close behavior, and initial narrow-viewport render * feat(web): fold Pi into the generic composer model/effort value buttons Pi sessions previously exposed model/effort twice: dedicated 'Pi model' / 'Pi thinking level' toolbar buttons (PiModelPanel/PiThinkingLevelPanel) AND the settings sheet's generic Model/Effort sections. Consolidate so Pi looks exactly like every other flavor: - Pi now uses the generic model/effort value buttons; labels resolve from the provider-qualified piModels catalog (name -> modelId -> session id). - The settings sheet's Model section already renders provider-grouped Pi rows and Effort renders Pi thinking levels, so the dedicated panels and their toolbar slots are deleted. - Keep Pi's mid-turn control affordance (tiann#1442): Pi turns hold thread.isDisabled for minutes, so Pi model/effort controls stay enabled while a turn is running (configurationControlsDisabled instead of controlsDisabled), including the sheet rows. - Drop 'piModel'/'piThinking' toolbar layout items; persisted layouts normalize them away automatically. Tests: pi value-button label/sheet tests, mid-turn model selection via the unified sheet, toolbar layout defaults. * fix(web): keep a session-settings trigger on narrow viewports and mid-turn Pi Address the HAPI review bot's two Minor findings on the Pi consolidation: - Narrow viewports collapse the model/effort value buttons into the settings sheet, so a persisted toolbar layout hiding the gear left no session-settings trigger at all. ComposerButtons now forces the gear back into the rendered layout on narrow viewports (wide layouts keep honoring the user's hidden choice). - The Pi mid-turn live-control rule only reached the value buttons and sheet rows; with those buttons gone on narrow, the gear was still disabled by controlsDisabled for the whole (minutes-long) Pi turn. HappyComposer now passes settingsDisabled={modelEffortControlsDisabled} so the gear stays clickable mid-turn for Pi exactly like the buttons. Tests: narrow + hidden-gear layout keeps Settings; narrow Pi mid-turn gear stays enabled and opens the provider-grouped sheet. * fix(web): address HAPI Bot Pi settings-sheet findings Three Minor findings from the review bot on the unified Pi sheet: - Provider-qualified selection: rows compared only modelId, so duplicate model IDs across providers all looked selected. Compare against piSelectedModel's provider+modelId when available. - Thinking-level reset: the removed Pi panel toggled the current level back to null; the unified effort rows only submitted concrete values. Re-clicking the selected effort row now clears it for Pi. - Memo staleness: the settings-sheet memo did not depend on modelEffortControlsDisabled, so a Pi disabled-state transition while the sheet was open left rows enabled from the prior render. Tests: colliding model IDs highlight only the matching provider row, re-clicking the selected effort row sends null, and a rerender with active=false disables the open sheet's rows. * fix(web): include piSelectedModel in settings-sheet memo deps The overlays memo reads piSelectedModel for provider-qualified row highlighting but only declared the derived selectedPiModel. When piSelectedModel hydrates from absent to a qualifier that resolves to the same catalog object, the memo is reused and duplicate model IDs stay highlighted across providers. Add the raw prop to the dep array. * fix(web): gate Pi model rows on catalog; reset drill-down via value button Address the HAPI Bot review on the unified composer settings sheet: - Pi no longer falls back to the generic synthesized modelOptions rows when its provider catalog is empty/loading. Selecting one of those would post a bare model id that runPi cannot resolve to a provider (first cached match or 409). The Model section now only renders for Pi when piModelGroups exists, and renders grouped rows exclusively. - Closing the sheet through the model/effort value button now goes through handleSettingsToggle, so a Cursor variant drill-down resets to the base model list on reopen (previously only the gear and outside-click paths cleared it). Tests: empty Pi catalog hides Model section; value-button close resets Cursor drill-down. * fix(web): hide Pi effort controls until the selected model resolves Address the HAPI Bot review: with the catalog still loading/failed there is no selectedPiModel to derive a capability map from, but the unified effort control stayed enabled (mid-turn Pi controls are intentionally live). Selecting a level would send set_thinking_level for a model that may not support reasoning, and the RPC can be rejected after the sheet closed. The old dedicated panel guarded this state. showEffortSettings now requires a resolved, reasoning-capable Pi model; the effort value button hides the same way. With an empty catalog Pi exposes no settings trigger at all, matching the old control states. Tests: unresolved Pi catalog mid-turn exposes no effort action. * fix(web): hide the Pi model trigger until the catalog resolves Address the HAPI Bot Minor finding: with an empty/loading Pi catalog the model value button fell back to the bare session model id and rendered an enabled trigger that opens no Model section. Show the button only once the provider-qualified catalog entry resolves.
…dation) (tiann#1469) * feat(config): add agent config descriptor protocol and advertise via runner capability Introduce shared agent configuration descriptors covering model, effort, permission, and secondary settings per agent flavor, plus the canonical HAPI YOLO -> native permission mode mapping. Runners advertise the builtin descriptors through the runner-state capability so hubs and web can render configuration without hardcoded flavor branches. Migrate the OpenCode create-session model picker from a bespoke radio list to the shared SelectControl combobox. * feat(web): render create-session permission from agent config descriptor Replace the flavor-branched Grok/Codex-family/YOLO permission block with a descriptor-driven PermissionField. Pi now reports permission as managed instead of silently ignoring the YOLO toggle, and YOLO-only flavors show the native permission mode the preference maps to. Removes the superseded GrokPermissionModeSelector and CodexFamilyPermissionModeSelector components. * ci: retry flaky claudeRemote 5s-timeout failure * fix(web): persist explicit OpenCode Default selection instead of restoring a concrete model The parent initialization effect treated every null selected model as 'uninitialized' and auto-picked a concrete advertised model, clobbering the user's explicit Default choice (and a restored Default preference). null now means explicit Default and is preserved; only undefined (no choice made yet) triggers probe-based initialization. Add parent-level regression tests for Default persistence and remembered-model restore. * fix(web): accept undefined selected model in OpencodeModelSelector props * feat(web+cli+hub): unify create-session model/effort fields and add Pi model/effort support Pi's agent config descriptor now advertises model (machine) and effort (static thinking levels) for create AND session availability: - cli: ListPiModelsForMachine RPC runs 'pi --list-models' (cached, inflight deduped) and parses the provider/model table; startup model match accepts provider-qualified ids - hub: GET /api/machines/:id/pi-models route + rpcGateway/syncEngine passthrough - web: NewSession renders Pi models grouped by provider through the generic ModelSelector and a new descriptor-driven EffortField (replaces the per-flavor LaunchEffortSelector/ReasoningEffortSelector pair); launch payload forwards Pi model + thinking-level effort (runner already supported --model/ --effort for pi) * fix(web): render Pi provider groups in ModelSelector and scope Grok availability warning - ModelSelector now renders grouped options as <optgroup> (Pi models are provider-grouped; identical modelIds from different providers stay distinct) - PermissionField only receives autoPermissionModeSupported for Grok — a cached Grok probe result no longer leaks the Grok warning onto other agents Addresses HAPI Bot Minor findings on tiann#1469. * fix(web): drop Object.groupBy from ModelSelector; revalidate restored Pi models against the catalog - ModelSelector buckets options with a reduce instead of Object.groupBy (Safari < 17.4 has no polyfill — New Session would throw on those clients) - Pi restored model/effort are cleared when the value is absent from the live machine catalog, and Create waits for the catalog while a non-default Pi choice is being validated (mirrors Codex/Grok/Copilot handling) Addresses HAPI Bot findings on tiann#1469. * fix(pi+web): serialize startup model before thinking level; hide Pi launch controls during history import - PiSession gains startupModelSettled; the startup set_thinking_level waits for the requested model's set_model attempt to settle first, so a level the default model rejects is not lost before the requested model is confirmed (set_model and set_thinking_level were already serialized by the runtime mutation lock; this pins the model-first ordering) - Create Session hides Pi model/effort controls while a Pi history import is selected — the import reopens the native session as-is and would silently ignore launch-only model/effort values Addresses HAPI Bot findings on tiann#1469. * fix(pi): settle startup-model gate when model discovery fails or returns no models A failed or empty get_available_models response would leave the startupModelSettled gate unresolved, stranding a requested startup effort indefinitely. Resolve the gate on the error path and the empty-models path; adds regression tests for both.
…ransport (tiann#1591) Replace the Antigravity (agy) integration — a PTY wrapping the TUI with output-marker scraping ('? for shortcuts', 'Generating', trust dialogs, /model picker navigation, quota-screen regex) — with a headless print-mode transport: every user turn spawns `agy -p <msg> --conversation <uuid> --output-format stream-json`, and NDJSON events (init/step_update/result) map onto the existing transcript-entry channel (sendAgySessionMessage), so hub/web rendering is unchanged. ~8.3k LOC (incl. tests) removed. Fixes tiann#1588. Design: docs/design/agy-headless-transport.md. CLI: - new cli/src/agy/headless/: agyNdjsonParser (pure functions, malformed-line tolerance, step conversation-id adoption), AgyPlannerAccumulator (per-step delta accumulation with settling retries), AgyHeadlessDriver (per-turn spawn/kill loop, NDJSON chunk buffering, authoritative delivery ack via user_input/result, interrupt + retry + shutdown lifecycle with consume/ restore, process-tree termination, SSH agent preserved, prompt log redaction, per-turn model snapshot with conversation-DB fallback) - runAgy/loop/session rewired; agy is remote-only (no PTY, no local mode, no local-switch action); queued batches snapshot model/effort/mode - deleted agyPty, agyPtyLauncher, agyHookCarrier(+scope cache), agyModelKeys, agyQuestionKeys, agyAskQuestion, agySessionScanner, agyPermissionHandler (+tests); buildAgyHooksJson removed; startHookServer agy-pre-invocation route → 200 no-op - runner: agy reopen/resume via generic --existing-session-id; commands/ agy.ts defaults remote; resume rejects ACTIVE agy sessions (remote-only, in-flight turns cannot hand off) - MCP stays user-managed (agy reads ~/.gemini/config/mcp_config.json and workspace .agents/mcp_config.json natively in headless — verified) Hub/web: - machines.ts drops agy→pty forcing and rejects non-remote startingMode - NewSession drops agy startingMode='pty'; terminal toggle disappears automatically; RemoteModeDisplay hides the local-switch hint when absent - docs/guide/agents.md updated: headless print mode, no PTY/hooks, MCP via user's own mcp_config.json Tests: 47 parser+driver tests (fake-binary e2e, chunk-split NDJSON, delivery ack semantics, interrupt/retry/shutdown races, model attribution, EOF framing, malformed envelopes); full suite green (cli ~2340, hub 1093, web 2474, shared 262). Real-binary smoke on agy 1.1.13: single turn exit 0, --conversation resume keeps the same conversation_id.
* feat(hub): add background-only ServerChan fallback * fix(hub): validate ServerChan background setting type * fix(web): preserve pending visibility transitions * fix(web): guard visibility reports across subscriptions
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # cli/src/agy/runAgy.ts # cli/src/commands/resume.test.ts # cli/src/commands/resume.ts # web/src/lib/assistant-runtime.ts
Unverified peer previews and session/machine labels are stored untrusted text. Neutralize C0/C1 before printing so OSC/ESC cannot hijack the operator TTY. Co-authored-by: Cursor <cursoragent@cursor.com>
…line Sanitize remaining doctor interpolations (ids, flavor, lifecycle). Put session names on a Name: line so parentheses cannot split the From: header. Co-authored-by: Cursor <cursoragent@cursor.com>
tryReceiveOnce only finished on newline or socket error, so accept-then-clean-close hung the handoff retry loop. Bound each attempt with end/close handlers and a 1s silence timeout. Co-authored-by: Cursor <cursoragent@cursor.com>
Client update-metadata could rewrite session machineId and redirect local-resume-capability mint to an attacker-proven machine. Preserve machineId like other hub-owned keys. Pre-tag (NULL) source machines may migrate onto a proven destination. Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid null-narrowed expect() so hub tsc --noEmit passes in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Allowing NULL-tag sources let any proven destination absorb sessions. Require create-time tag continuity on this namespace endpoint; legacy recovery needs an operator path. Co-authored-by: Cursor <cursoragent@cursor.com>
Hub correctly rejects untagged migrate-sessions (takeover hole). Do not abort runner startup after the new machine id is already persisted — warn and leave legacy sessions on the old id for operator remap. Co-authored-by: Cursor <cursoragent@cursor.com>
parseSpecialCommand was already skipped for sentFrom=peer, but expandSkillReference still rewrote leading \$skill into /skill and mutated peer delivery into receiver-native control syntax. Co-authored-by: Cursor <cursoragent@cursor.com>
Cold review Majors on tiann#1473 tip: - Null peercred no longer authorizes on win32 (enumerable pipe path is not auth). - Delete unused hub reenrollGrant helpers; keep 410 endpoints + orphan tables noted. - Re-read session machineId immediately before arming resume peer mint. Co-authored-by: Cursor <cursoragent@cursor.com>
…client Codex on tip e7275bb: never log resumePeerMintNonce/token before redeem. Client tryReceiveOnce re-checks peercred before accepting injected secrets so a connect-time null cred cannot bypass server auth. Co-authored-by: Cursor <cursoragent@cursor.com>
Active terminal resume without a capability grant must not stop the owner. Namespace-only session-alive must not refresh active and veto archive. Co-authored-by: Cursor <cursoragent@cursor.com>
… resumes Machine tag in settings.json is same-UID readable, so forged proof must not replace runnerProofHash. Inactive resume without capability cannot register session RPC after the session-alive gate. Co-authored-by: Cursor <cursoragent@cursor.com>
…me spawn Copied machineTag must not take over sessions without the source runnerProof. Host-fallback spawn without a mint leaves an uncontrollable child after RPC auth gates. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Unrelated shells ask hub/runner to spawn with inject instead of failing. Cold restart may rebind runnerProofHash only when the machine is offline, keeping machineId so sessions are not stranded. Co-authored-by: Cursor <cursoragent@cursor.com>
Remove tag-only offline proof rebind (Codex Blocker). Persist runnerProof under ~/.hapi/runner.proof so cold restart keeps machineId and terminal hapi resume can mint + open the local TTY. Await redeem/deliver on spawn. Co-authored-by: Cursor <cursoragent@cursor.com>
Revert durable ~/.hapi/runner.proof (same-UID disk bearer Blocker). Cold restart strand + unrelated-shell resume remain accepted residuals until trusted remap. Keep awaited resume inject on the spawn path. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fork-side cold-review stage for A2A Layer 0.1 / P0.5 peer delivery provenance (not for merge).
Implements trusted attribution for
ping_peer/hapi ping-peerso peer nudges stop looking like operator keystrokes (sentFrom: webappghost-user bug).Tip:
02e754f4donfeat/a2a-p05-peer-provenance(offupstream/main).Local gates:
bun typecheckclean; focused hub/cli/shared/web tests green. Fullbun run testhit one environmental runner integration flake (live driver runner at 0.27.2) unrelated to this diff.Local Opus cold (
claude-opus-5-thinking-high) already run once; Blocker (UserMessage selector loop) + Majors (header trust docstring, deadHAPI_SESSION_NAME, hub name lookup, fail-open) addressed on tip before push.Test plan
/messageswithpeerbody and noX-Hapi-Peer-DeliverystayssentFrom: webappHAPI_SESSION_IDof a real same-namespace session: storedmeta.sentFrom=peer+ store-derivedsourceNamesentFrom=peer, no invented source id/sessions/<sourceSessionId>when presentIssues
Ref tiann#1203 (upstream Fixes lands on the tiann PR after full court press)
A2A note
Layer 0.1 / P0.5 - not a P2 work-contract. See Discussion tiann#1332.