feat(cli): Cursor-only display_links MCP for unmangled URLs - #1564
feat(cli): Cursor-only display_links MCP for unmangled URLs#1564heavygee wants to merge 20 commits into
Conversation
There was a problem hiding this comment.
Findings
- [Major] Explicit session targeting dereferences the target runner's loopback MCP URL on the caller host —
hapiMcpUrlis published ashttp://127.0.0.1:<port>by the CLI that owns the session, but the new command can resolve any session in the hub namespace and then opens that URL locally. Cross-runner use therefore fails or can reach an unrelated local MCP server on a coincident port. Evidencecli/src/commands/displayLinks.ts:176; same new pathscripts/tooling/hapi-display-links.mjs:157; related contextcli/src/claude/utils/startHappyServer.ts:589.
Suggested fix:Longer-term, explicit remote targeting needs a hub-to-owning-CLI RPC instead of exposing/dereferencingif (parsed.sessionArg !== null && !SELF_TOKENS.has(parsed.sessionArg)) { throw new Error('display-links only supports the current local session; run it on the target runner') }
hapiMcpUrl.
Summary
Review mode: initial
- One Major correctness issue found in the explicit-session CLI/script fallback.
- Residual risk: the PR test plan still lacks a real Cursor ACP invocation/tap check.
Testing
- GitHub
testandintegrationchecks pass. - Local test run unavailable because
bunis not installed in this review runner. - Add a regression test where the selected session belongs to another machine/runner.
HAPI Bot
There was a problem hiding this comment.
Findings
- No findings.
Summary
Review mode: follow-up after new commits
- Reviewed the full latest diff; the prior Major cross-runner loopback issue is addressed by restricting explicit targets to the current local session.
- Residual risk: the real Cursor ACP invocation/tap test remains unchecked, so the end-to-end workaround is not yet verified in its affected environment.
Testing
- GitHub integration check: pass.
- GitHub test check: pending at review time.
- Not run locally (automation review; PR code was not executed).
HAPI Bot
bdc2e36 to
f1c77b9
Compare
There was a problem hiding this comment.
Findings
- [Minor] Use a unique key for duplicate destinations — the payload accepts repeated hrefs, but both URL-row branches use href alone as the React key. Two cards pointing to the same URL (for example, with different titles) therefore create duplicate keys and can be reused incorrectly during reconciliation. Evidence
web/src/components/AssistantChat/messages/ToolMessage.tsx:119
Suggested fix:{props.block.urls.map((url, index) => { const rowKey = `${url.href}:${index}` // use key={rowKey} in both branches })}
- [Minor] Localize the new card labels — the added headings, copy action, success state, and default accessible label are hard-coded English, so the Chinese UI renders a mixed-language card. This file already uses
useTranslation; add matching keys to both locale dictionaries. Evidenceweb/src/components/AssistantChat/messages/ToolMessage.tsx:97
Suggested fix:const { t } = useTranslation() // ... {copied ? t('displayLinks.copied') : t('displayLinks.tapToCopy')}
Summary
Review mode: follow-up after new commits
- Two Minor findings: duplicate React row keys and untranslated user-facing labels.
- Residual risk: the PR test plan still has no completed real Cursor ACP invocation/tap check, so the affected end-to-end path remains unverified.
Testing
- GitHub integration check: pass.
- GitHub test check: in progress at review time.
- Suggested: render two URL entries with the same href and different titles; add coverage for the localized display-links labels and markdown export.
- Not run locally (automation review; PR code was not executed).
HAPI Bot
There was a problem hiding this comment.
Findings
- [Minor] Preserve configured hub headers in the new CLI fallback —
hapi display-linkscalls/api/authand the session endpoints with hand-built headers, bypassingconfiguration.extraHeaders. HAPI documents these headers for protected/self-hosted hubs and centralizes them inbuildHubRequestHeaders, so this command can fail at the proxy while existing CLI hub operations succeed. Evidencecli/src/commands/displayLinks.ts:139
Suggested fix:import { buildHubRequestHeaders } from "@/api/hubExtraHeaders" headers: buildHubRequestHeaders({ "Content-Type": "application/json" }) // After JWT exchange: return buildHubRequestHeaders({ Authorization: `Bearer ${body.token}` })
Summary
Review mode: follow-up after new commits
- The previous duplicate-key and localization findings are resolved by the latest commit.
- One Minor finding remains: the CLI fallback does not honor configured extra hub headers.
- Residual risk: the PR test plan still has no completed real Cursor ACP invocation/tap check, so the affected end-to-end path remains unverified.
Testing
- GitHub integration check: pass.
- GitHub test check: pending at review time.
- Suggested: configure
configuration.extraHeaders, then assert auth, session-detail, and session-list requests include them. - Not run locally (security review; PR code was not executed).
HAPI Bot
There was a problem hiding this comment.
Findings
- [Minor] Preserve configured hub headers in the standalone fallback - the new script reads the configured token/settings but sends auth and session requests with hand-built headers. On hubs protected by
HAPI_EXTRA_HEADERS_JSONorsettings.json.extraHeaders, this fallback fails at the proxy even thoughhapi display-linksworks. Evidencescripts/tooling/hapi-display-links.mjs:114.
Suggested fix:const settings = JSON.parse(readFileSync(SETTINGS, "utf8")) const extraHeaders = process.env.HAPI_EXTRA_HEADERS_JSON ? JSON.parse(process.env.HAPI_EXTRA_HEADERS_JSON) : (settings.extraHeaders ?? {}) headers: { ...extraHeaders, "Content-Type": "application/json" } const authHeaders = { ...extraHeaders, Authorization: `Bearer ${jwt}` }
Summary
Review mode: follow-up after new commits
- The prior CLI extra-header finding is fixed, but the same protected-hub incompatibility remains in the newly added standalone script.
- Residual risk: the PR test plan still has no completed real Cursor ACP invocation/tap check, so overlay-driven tool availability and exact href navigation remain unverified end to end.
Testing
- GitHub integration check: pass.
- GitHub test check: pending at review time.
- Not run locally (security review; PR code was not executed).
- Suggested: run the standalone script with settings-backed and environment-backed extra headers and assert both auth and session requests include them.
HAPI Bot
There was a problem hiding this comment.
Findings
- No findings.
Summary
Review mode: follow-up after new commits
- Reviewed the full latest diff. The prior standalone fallback extra-header finding is resolved on the current head.
- Residual risks: the standalone environment/settings header merge has no direct automated request test, and the PR test plan still has no completed real Cursor ACP invocation/tap check.
Testing
- GitHub integration check: pass.
- GitHub test check: pending at review time.
- Not run locally (security review; PR code was not executed).
- Suggested: add standalone-script request tests for environment-backed and settings-backed extra headers; complete one real Cursor ACP display_links invocation and navigation check.
HAPI Bot
There was a problem hiding this comment.
Findings
- [Major] Starting another Cursor session disables every live sibling HAPI MCP and cleanup never re-enables it. Evidence
cli/src/cursor/utils/cursorMcpOverlay.ts:452. - [Major] Sibling detection treats any
hapi-*id as HAPI-owned, so unrelated user MCP integrations can be persistently disabled. Evidencecli/src/cursor/utils/cursorMcpOverlay.ts:386. - [Major] Exact-copy values advertised for secrets/tokens remain in Share Turn capture DOM. Evidence
web/src/components/AssistantChat/messages/ToolMessage.tsx:80.
Summary
Review mode: follow-up after new commits
- Reviewed the full latest diff plus the prior HAPI Bot review and commit delta.
- The new session-id check fails closed for a misrouted
display_linkscall, but the sibling-disable lifecycle introduces broader MCP regressions and exact-copy content is not excluded from share capture.
Testing
- GitHub integration check: pass.
- GitHub test check: pending at review time.
- Not run locally (security review; PR code was not executed).
- Suggested: concurrent Cursor A/B lifecycle test (including B cleanup and A reuse), user-owned
hapi-docspreservation test, and Share Turn capture test proving exact-copy values are absent.
HAPI Bot
There was a problem hiding this comment.
Findings
- [Major] Exact-copy values can still enter session exports through the ACP tool-call record. Evidence
cli/src/claude/utils/startHappyServer.ts:135; related flowcli/src/agent/backends/acp/AcpMessageHandler.ts:737,cli/src/agent/messageConverter.ts:90, andweb/src/lib/sessionExport/markdown.ts:87.
Suggested fix:const safeMessage = message.type === 'tool_call' && isDisplayLinksToolName(message.name) ? { ...message, input: redactDisplayLinksTextValues(message.input) } : message const converted = convertAgentMessage(safeMessage, this.currentBackendModel)
- [Major] The secret fallback places exact-copy values in argv. Evidence
cli/src/commands/displayLinks.ts:48andscripts/tooling/hapi-display-links.mjs:73.
Suggested fix:const value = args.includes('--text-stdin') ? (await Bun.stdin.text()).replace(/\r?\n$/, '') : undefined
Summary
Review mode: follow-up after new commits
- Reviewed the complete current diff plus the prior bot review and commit delta.
- The prior sibling-overlay and Share Turn findings are resolved at this head.
- Two secret-handling paths still bypass the intended exact-copy containment.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub
integrationandtestchecks were pending at review time. - Suggested: an ACP
display_linkssentinel test proving persisted/exported tool-call input is redacted, plus CLI/script stdin tests proving the value is absent from argv.
HAPI Bot
There was a problem hiding this comment.
Findings
- No findings at or above the 80% confidence threshold.
Summary
- Review mode: follow-up after new commits
- Reviewed the complete current diff, the prior HAPI Bot review, and the commit delta.
- The prior ACP/export redaction and argv secret-exposure findings are resolved at this head.
- Residual risk: the real Cursor ACP click/copy path remains manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub integration: passed. GitHub test: in progress at review time.
- Suggested: complete the PR plan’s Cursor ACP session checks for byte-accurate link navigation and exact-copy behavior.
HAPI Bot
Cursor-routed agents drop doubled letters when recalling URLs; store href bytes built outside the model (MCP display_links + CLI fallback) and render a tappable SessionChat card without a fake user turn. Fixes tiann#1516 Co-authored-by: Cursor <cursoragent@cursor.com>
Doubled-letter URL recall is a Cursor-routed failure mode. Keep the paint path and CLI fallback, but do not advertise display_links to Claude/Codex/OpenCode/Grok tool lists or system prompts. Co-authored-by: Cursor <cursoragent@cursor.com>
Estate hosts bind Cursor state under /var/lib/hapi/cursor via a home symlink; refusing the symlink directory left overlay install dead and pushed agents toward stale project mcp.json sidecars. Resolve to the real directory (and honor HAPI_CURSOR_MCP_CONFIG_DIR); keep refusing symlinked mcp.json files. Co-authored-by: Cursor <cursoragent@cursor.com>
hapiMcpUrl is 127.0.0.1 on the owning CLI. Opening it for another hub session hits the caller host. Restrict the CLI/script fallback to self. Co-authored-by: Cursor <cursoragent@cursor.com>
Cursor drops doubled letters in secrets and tokens the same way it drops them in hosts. Extend display_links with texts so agents construct byte-accurate copy cards in tool args instead of typing them in prose. Co-authored-by: Cursor <cursoragent@cursor.com>
Duplicate hrefs with different titles collided on React keys. Card chrome was English-only in a file that already uses i18n. Co-authored-by: Cursor <cursoragent@cursor.com>
Protected hubs already inject HAPI_EXTRA_HEADERS_JSON via buildHubRequestHeaders. display-links was skipping them on /api/auth and session GETs, so the fallback could 403 at the proxy. Co-authored-by: Cursor <cursoragent@cursor.com>
Same proxy-header hole as the CLI command. Read HAPI_EXTRA_HEADERS_JSON, else settings.json extraHeaders, and merge onto auth and session fetches. Co-authored-by: Cursor <cursoragent@cursor.com>
Cursor routes duplicate bare tool names to one hapi-* server, so Kinrupt display_links calls painted Sparling. Disable sibling overlays and refuse paints whose sessionId is not this HappyServer. Co-authored-by: Cursor <cursoragent@cursor.com>
… share Exclusive sibling disable stranded live Cursor sessions and could hit user-owned hapi-* ids. Fail-close misroutes with sessionId instead. Exclude exact-copy rows from Share Turn and session markdown export. Co-authored-by: Cursor <cursoragent@cursor.com>
ACP persisted display_links rawInput, so session markdown still leaked secrets after Share Turn exclusion. Redact texts values on the wire and add --text-stdin so secrets are not process arguments. Co-authored-by: Cursor <cursoragent@cursor.com>
Rebase onto upstream/main added an exhaustive ChatBlock projector; handle the new display-links kind so web typecheck stays green. Co-authored-by: Cursor <cursoragent@cursor.com>
0555671 to
e90d7a3
Compare
There was a problem hiding this comment.
Findings
- [Major] Native clients drop the new
display-linksblock; see the inline finding onweb/scripts/fixtures/projection.ts:109.
Summary
- Review mode: follow-up after new commits
- One Major correctness/contract issue found: the rebased PR adds a new normative web chat block without the required iOS/Android protocol ports or generated conformance fixture/version update.
- Residual risk: the real Cursor ACP click/copy path remains manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub drift-gate, integration, and test checks: passed.
- Suggested: add the generated
cursor-display-linkschat fixture, bump the fixture version for the projection-shape change, and run both native conformance suites after implementing the block.
HAPI Bot
Bump FIXTURE_VERSION for the new projection shape, add cursor-display-links golden, and implement normalize/reduce/project/UI on both native ports. Co-authored-by: Cursor <cursoragent@cursor.com>
Kotlin refuses smart-casts on public API properties from another module; bind title locals before Text/clipboard use so :app:compileDebugKotlin passes. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
- [Minor] Android link taps can crash when no URL handler is installed; see the inline finding on
android/app/src/main/kotlin/app/hapi/companion/feature/chat/blocks/DisplayLinksBlockView.kt:66.
Questions
- None.
Summary
- Review mode: follow-up after new commits
- The prior Major native-contract issue is resolved: fixture versioning plus Android/iOS normalization, reduction, projection, and rendering are now present.
- One Minor Android correctness issue remains in the new card interaction.
- Residual risk: the real Cursor ACP click/copy path remains manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub app-build, build, drift-gate, integration, package-tests, and test checks: passed.
- Suggested: cover the no-browser
ActivityNotFoundExceptionpath and complete the manual Cursor ACP click/copy checks from the PR test plan.
HAPI Bot
Catch ActivityNotFoundException via the existing ChatLinkHandler helper so kiosk/work-profile devices toast instead of crashing the chat screen. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
-
[Major] Concurrent Cursor sessions still advertise a colliding
display_linkstool name; see the inline finding oncli/src/cursor/utils/cursorMcpOverlay.ts:298.Suggested fix:
const displayLinksToolName = `hapi_${client.sessionId.replaceAll('-', '_')}_display_links` mcp.registerTool(displayLinksToolName, options, handler) toolNames.push(displayLinksToolName)
Questions
- None.
Summary
- Review mode: follow-up after new commits
- The prior Android
ActivityNotFoundExceptionfinding is resolved by reusing the guardedopenUrlhelper. - One Major concurrent-session correctness issue remains: session binding fails closed after a wrong-server dispatch, but does not make the tool callable for every live Cursor session.
- Residual risk: the real Cursor ACP click/copy path and concurrent-session routing remain manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub app-build, build, drift-gate, integration, and package-tests checks: passed. The main
testcheck was still pending at review time. - Suggested: add a two-session Cursor MCP routing test that invokes each session-specific tool, and complete the manual Cursor ACP checks from the PR test plan.
HAPI Bot
Register hapi_<sessionId>_display_links so concurrent Cursor hapi-* overlays do not collide on bare display_links (forum 148059). Bridge/CLI/mjs forward the same name; redaction still matches *_display_links. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
-
[Minor] Android retention accepts non-empty
display-linksarrays even when every item is rejected by normalization; see the inline finding onandroid/core/protocol/src/main/kotlin/app/hapi/protocol/window/MessageRetention.kt:158.Suggested fix:
"display-links" -> parseDisplayLinksInput(prop(data, "urls")).isNotEmpty() || parseDisplayTextsInput(prop(data, "texts")).isNotEmpty()
-
[Minor] Doctor output still advertises a bare
display_linkstool after the per-session rename; see the inline finding oncli/src/ui/doctorInlineMedia.ts:227.Suggested fix:
console.log(chalk.gray( ` Display links tool: ${buildDisplayLinksToolName(session.id)}` ))
Questions
- None.
Summary
- Review mode: follow-up after new commits
- The prior Major concurrent-session routing issue is resolved by the per-session tool name across registration, forwarding, approval, and fallback invocation.
- Two Minor inconsistencies remain: Android can retain invisible invalid rows, and doctor guidance names the obsolete tool.
- Residual risk: the real Cursor ACP click/copy path and concurrent two-session routing remain manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub app-build, build, drift-gate, integration, and package-tests checks passed; the main
testcheck was still pending at review time. - Suggested: add an Android retention case with invalid-only URL/text arrays, assert doctor output includes the session-specific name, and complete the manual Cursor ACP checks from the PR test plan.
HAPI Bot
Retention now uses the same URL/text parsers as normalize so invalid-only payloads are not kept. Doctor prints each Cursor session's hapi_<sessionId>_display_links name. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
-
[Major] Fail closed when redacting exact-copy tool input — malformed texts values and extra row fields can reach persisted ACP tool-call messages, evidence shared/src/displayLinks.ts:211.
Suggested fix:
if (Object.prototype.hasOwnProperty.call(record, "texts") === false) return input const texts = Array.isArray(record.texts) ? record.texts.map((item) => { if (typeof item === "string") return DISPLAY_LINKS_REDACTED_VALUE const row = item && typeof item === "object" && Array.isArray(item) === false ? item as Record<string, unknown> : null const title = typeof row?.title === "string" ? row.title : undefined return title ? { value: DISPLAY_LINKS_REDACTED_VALUE, title } : { value: DISPLAY_LINKS_REDACTED_VALUE } }) : DISPLAY_LINKS_REDACTED_VALUE return { ...record, texts }
-
[Major] Mark Android exact-copy clips as sensitive — Android 13+ otherwise shows copied tokens in the system clipboard preview, evidence android/app/src/main/kotlin/app/hapi/companion/feature/chat/blocks/DisplayLinksBlockView.kt:100.
Suggested fix:
val clip = ClipData.newPlainText(textTitle ?: "copy", text.value).apply { description.extras = PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) } } clipboard.setPrimaryClip(clip)
Questions
- None.
Summary
- Review mode: follow-up after new commits
- The prior Android retention and doctor tool-name findings are resolved at the latest head.
- Two Major secret-handling gaps remain: malformed ACP inputs can bypass persistence redaction, and Android clipboard previews can expose copied exact-copy values.
- Residual risk: the real Cursor ACP click/copy path and concurrent two-session routing remain manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub package-tests, app-build, test, build, drift-gate, and integration checks passed.
- Suggested: add malformed/non-array/extra-field redaction cases through AcpMessageHandler and convertAgentMessage; manually verify Android 13+ clipboard UI suppresses the value; complete the Cursor ACP checks from the PR test plan.
HAPI Bot
Whitelist exact-copy row shape so backup/malformed texts cannot persist secrets; mark ClipData EXTRA_IS_SENSITIVE so Android 13+ hides clipboard previews for token cards. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
-
[Major] Whitelist the complete display_links tool input before persistence/export — redactDisplayLinksToolInput now sanitizes malformed texts rows, but return { ...record, texts } still preserves arbitrary top-level fields and extra fields nested under urls. ACP emits raw tool input before MCP schema validation, and Markdown export reuses this redactor, so { texts: [{ value: secret }], backup: secret } or a URL row carrying backup: secret still leaks the exact-copy bytes into stored tool-call history and exported Markdown. Evidence shared/src/displayLinks.ts:229.
Suggested fix:
const safe: Record<string, unknown> = {} if (Object.prototype.hasOwnProperty.call(record, 'urls')) { safe.urls = safeParseDisplayLinksInput(record.urls) } if (Object.prototype.hasOwnProperty.call(record, 'texts')) { safe.texts = Array.isArray(record.texts) ? record.texts.map((item) => { if (typeof item === 'string') return DISPLAY_LINKS_REDACTED_VALUE const row = item && typeof item === 'object' && !Array.isArray(item) ? item as Record<string, unknown> : null const title = typeof row?.title === 'string' ? row.title : undefined return title ? { value: DISPLAY_LINKS_REDACTED_VALUE, title } : { value: DISPLAY_LINKS_REDACTED_VALUE } }) : DISPLAY_LINKS_REDACTED_VALUE } if (typeof record.sessionId === 'string') safe.sessionId = record.sessionId return safe
Questions
- None.
Summary
- Review mode: follow-up after new commits
- The two prior Major findings are resolved at the latest head.
- One Major export/persistence redaction gap remains because unknown fields outside texts are retained.
- Residual risk: the real Cursor ACP click/copy path and concurrent two-session routing remain manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- GitHub integration, package-tests, test, Android build, fixture drift-gate, and iOS app-build checks passed.
- Suggested: add top-level and urls extra-secret-field cases through redactDisplayLinksToolInput, AcpMessageHandler, convertAgentMessage, and Markdown export.
HAPI Bot
Rebuild the persisted/exported tool-call shape from urls/texts/sessionId so arbitrary top-level or nested backup fields cannot leak secrets. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
- [Major] Fail closed for non-object display_links inputs — ACP treats strings and arrays as usable raw input, but the redactor returns them unchanged. A malformed pre-schema-validation call can therefore persist/export exact-copy bytes despite the new object-field whitelist. Evidence
shared/src/displayLinks.ts:209; triggered bycli/src/agent/backends/acp/AcpMessageHandler.ts:29andcli/src/agent/backends/acp/AcpMessageHandler.ts:764.
Suggested fix:if (input == null) return input if (typeof input !== 'object' || Array.isArray(input)) { return DISPLAY_LINKS_REDACTED_VALUE }
- [Major] Keep exact-copy secrets off Universal Clipboard — this feature explicitly carries secrets/tokens, but assigning
UIPasteboard.general.stringuses the unrestricted general pasteboard with no expiration, allowing cross-device clipboard sync and indefinite retention. Evidenceios/Hapi/Features/Chat/Blocks/DisplayLinksBlockView.swift:42.
Suggested fix:import UniformTypeIdentifiers UIPasteboard.general.setItems( [[UTType.utf8PlainText.identifier: text.value]], options: [ .localOnly: true, .expirationDate: Date().addingTimeInterval(120), ] )
Questions
- None.
Summary
- Review mode: follow-up after new commits
- The prior unknown-field redaction finding is addressed at the latest head, but malformed primitive/array inputs still bypass redaction.
- The new iOS exact-copy action needs local-only, expiring clipboard handling before secrets are safe to copy.
- Residual risk: the real Cursor ACP click/copy path and concurrent two-session routing remain manually unverified.
Testing
- Not run (automation; PR code was not executed per review security constraints).
- Observed CI: integration, package-tests, app-build, build, and fixture drift-gate passed; the main test job was still pending at review time.
- Suggested: add primitive/array redaction cases through
redactDisplayLinksToolInput,AcpMessageHandler,convertAgentMessage, and Markdown export; extract/test the iOS pasteboard helper with local-only and expiration options.
HAPI Bot
Fail closed when ACP feeds a string/array as tool input. Exact-copy on iOS uses localOnly + 120s expiration instead of Universal Clipboard. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
- None.
Questions
- None.
Summary
Review mode: follow-up after new commits
- No additional high-confidence issues found in the full latest diff.
- The prior primitive/array redaction and unrestricted iOS pasteboard findings are addressed at this head.
- Residual risk: the real Cursor ACP click/copy flow and concurrent two-session routing remain manually unverified.
Testing
- Not run locally (automation; PR code was not executed per review security constraints).
- Observed CI: test, integration, Android build, iOS package/app builds, and fixture drift-gate passed.
HAPI Bot
Why this exists (please read before calling it MCP bloat)
I did not want another MCP tool. HappyServer tool lists are real context, and "just add an MCP" is a loaded gun in this project for good reason.
Cursor ACP forced the issue. Cursor-routed agents drop doubled letters and digits when they recall identifiers in visible assistant text —
tiannbecomestian; MagicDNS labels lose a doubled digit,*.mmdbecomes*.md. Headset and phone operators then tap a 404. HAPI is not stripping the character: the assistant payload is already wrong when the hub stores it.This is not "LLMs cannot emit doubled letters." In the same Cursor turn, thinking traces and MCP JSON can still contain
tiannwhile the visible markdown containstian/hapi. Native Claude Code and Codex CLI, same strings, do not do this in our controls. I reported it to Cursor; I am not asking this repo to fix Cursor.The only HAPI-side fix that does not shoot remote operators is the same contract as
display_image: do not let the click target be reconstructed from chat markdown. Paint href bytes that were constructed outside that prose path.If you run Cursor ACP and have never seen this: please comment. I am assuming everyone on that path hits it and this is the least-bad escape hatch. If it is just me, the Cursor-only gate still keeps the tool out of everyone else's context.
What this PR does
display_linkson the session HappyServer, Cursor sessions only (Claude / Codex / OpenCode / Grok do not register it).hapi display-linksandscripts/tooling/hapi-display-links.mjs(same idea ashapi-display-image.mjs).Linkscard:<a href>from stored bytes. http(s) only;javascript:/data:/vbscript:/file:denied."tia"+"nn"), never type the host in assistant prose.~/.cursorsymlink so the tool is actually visible (Cursor ACP ignoressession/newmcpServers; overlay install used to refuse the symlink).Not in this PR: rewriting historical mangled assistant text at render time.
Test plan
display_linkson Cursor, absent for other flavorsmcp.jsonon the real targethreffrom the payload (not from markdown)display_linkswith a concatenated landmine href; tap the card; address bar hastiann, nottianIssues
Fixes #1516