feat(plugin): one tiny.place plugin for any harness (runtime harness detection)#215
Conversation
…rface, Stop dispatch)
…e into test script
The auto-responder pipes attacker-controlled DM text into `codex exec`; running it with --dangerously-bypass-approvals-and-sandbox let a prompt injection reach the shell/filesystem. Switch to --sandbox read-only so the only side-effecting path is the auto_reply MCP tool. Parity with the plugin-codex tinyhumansai#214 critical fix.
Each adapter now owns its launcher install recipe via launch.prepare(ctx) →
{command,args,env}: Claude points `claude --plugin-dir` at the package; Codex
writes an isolated CODEX_HOME (config.toml + auto-discovered hooks.json +
symlinked auth.json). The shared launcher stays harness-agnostic.
One launcher for any harness: owns wallet store + arrow-key menu + import/ register flows, then delegates to activeAdapter().launch.prepare() for the boot plan. --harness <name> (or TINYPLACE_HARNESS) forces the adapter; otherwise auto-detected. Replaces the two near-identical plugin-claude/plugin-codex launchers.
…rness adapter-contract-test.mjs iterates the ADAPTERS map and structurally asserts the full per-harness contract (data-dir isolation, UNTRUSTED framing, an inbound delivery path, responder arg threading, a valid launch.prepare plan, reachable via TINYPLACE_HARNESS). A missing/wrong-shaped field fails CI. Wired into `test`.
adapters/README.md: the field-contract table, launch.prepare rules, a wiring checklist, and the do-nots (no harness names in core, no shared data dir, keep UNTRUSTED framing). DESIGN.md: launcher + convention sections + the deferred shim-conversion note. New contributors add a harness by copying an adapter and making the contract test green.
|
@oxoxDev is attempting to deploy a commit to the Vezures Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR adds a standalone ChangesTinyplace Plugin Package
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3020383bfa
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
sdk/plugin-tinyplace/mcp-smoke.mjs (1)
51-57: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
rpc()has no timeout — a hung/crashed server makes the smoke test block forever.If the child never emits a response line (boot failure, transport crash), the awaited promise never settles and the test hangs with no diagnostic instead of failing. Add a timeout and reject on child exit.
♻️ Proposed hardening
const rpc = (method, params) => { const id = nextId++; - return new Promise((resolve) => { - pending.set(id, resolve); - child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); - }); + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { pending.delete(id); reject(new Error(`rpc ${method} timed out`)); }, 15000); + pending.set(id, (msg) => { clearTimeout(timer); resolve(msg); }); + child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); + }); };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/plugin-tinyplace/mcp-smoke.mjs` around lines 51 - 57, The rpc() helper currently leaves requests pending forever if the child process stops responding, so harden it by adding a timeout and rejection path. Update rpc() to create a timer for each request that rejects and clears the pending entry if no response arrives in time, and also reject any still-pending requests when the child process exits or errors. Use the existing rpc(), pending map, nextId counter, and child.stdin/child process wiring to locate the fix.sdk/plugin-tinyplace/bin/tinyplace.mjs (1)
53-61: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winConsider an atomic write for the secret-key store.
saveWalletswriteswallets.jsonin place viawriteFileSync. A crash or full disk mid-write can truncate/corrupt the file, and this file holds irreplaceable Ed25519 seeds (secretKey) — corruption means permanent loss of the identity. Prefer write-to-temp +renameSync(rename is atomic on the same filesystem). Since this logic mirrorsmcp/server.mjs"byte-for-byte", apply the same change there to keep them in sync.🛡️ Proposed atomic write
function saveWallets(wallets) { mkdirSync(DATA_DIR, { recursive: true }); - writeFileSync(WALLETS_FILE, JSON.stringify({ wallets }, null, 2) + "\n", { mode: 0o600 }); - try { - chmodSync(WALLETS_FILE, 0o600); - } catch { - /* best-effort */ - } + const tmp = `${WALLETS_FILE}.tmp`; + writeFileSync(tmp, JSON.stringify({ wallets }, null, 2) + "\n", { mode: 0o600 }); + try { + chmodSync(tmp, 0o600); + } catch { + /* best-effort */ + } + renameSync(tmp, WALLETS_FILE); }(
renameSyncwould need adding to thenode:fsimport.)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/plugin-tinyplace/bin/tinyplace.mjs` around lines 53 - 61, The saveWallets secret-key persistence is writing wallets.json directly, which can leave the file truncated if the process crashes or disk fills mid-write. Update saveWallets to use a write-to-temp-then-renameSync flow so the final file is replaced atomically, and make sure renameSync is imported from node:fs. Apply the same atomic-write change in the mirrored wallet persistence logic in mcp/server.mjs so both implementations stay byte-for-byte aligned.sdk/plugin-tinyplace/mcp/routing.mjs (1)
59-62: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAvoid the duplicate session scan on the inbound hot path.
liveSessions(agentAddress)andprimarySession(agentAddress)each run a fullreadSessions(directory scan + JSON parse of every presence file), andprimarySessioninternally re-invokesliveSessions. SinceprimarySessionis justliveSessions()[0](already label-sorted), derive both from a single call.♻️ Proposed refactor
- const live = liveSessions(agentAddress).map((s) => s.label); - const primary = primarySession(agentAddress)?.label ?? null; + const liveList = liveSessions(agentAddress); + const live = liveList.map((s) => s.label); + const primary = liveList[0]?.label ?? null;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/plugin-tinyplace/mcp/routing.mjs` around lines 59 - 62, The inbound hot path in enqueueRouted is scanning sessions twice because liveSessions(agentAddress) and primarySession(agentAddress) each trigger readSessions, with primarySession reusing liveSessions again. Refactor enqueueRouted to derive both live and primary from a single liveSessions(agentAddress) call, using the first sorted session label as the primary value, and keep routeTarget unchanged except for consuming those shared results.sdk/plugin-tinyplace/mcp/outbox.mjs (2)
1-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMissing dedicated offline test for outbox.mjs.
registry.mjsanddaemon-lock.mjseach have a test file (registry-test.mjs,lock-test.mjs) that specifically stresses concurrent claim/reclaim races.outbox.mjsimplements comparable atomic-claim/stale-recovery logic (and per the concern above, its own subtle race) but has no equivalent test in this cohort.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/plugin-tinyplace/mcp/outbox.mjs` around lines 1 - 90, Add a dedicated offline race test for outbox.mjs to cover the atomic claim and stale-recovery behavior in claimOutboxJobs and recoverStaleClaims. Mirror the style of registry-test.mjs and lock-test.mjs by exercising writeOutboxJob, concurrent claims, and reclaiming a .sending-* file after it becomes stale. Verify the job is not double-claimed, that a crashed claim is requeued, and that done()/fail() leave the outbox in the expected state.
58-90: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo backoff/attempt-limit on failed jobs.
fail()renames a claim straight back to pending on any error, including the 403 contact-gate path indrainOutbound. With no backoff or retry-count, a persistently-failing job (blocked/unreachable recipient) becomes immediately reclaimable on the next poll, which can turn into a tight retry loop hammering the send path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdk/plugin-tinyplace/mcp/outbox.mjs` around lines 58 - 90, The retry path for claimed outbox jobs has no delay or attempt cap, so a failing job is immediately requeued and can loop forever. Update the outbox flow around claimOutboxJobs() and the claimed.fail() behavior so drainOutbound() can track retry state for each job, apply backoff or a max retry limit before making it reclaimable again, and avoid instantly renaming the claim back to the pending file on repeated failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdk/plugin-tinyplace/adapters/claude.mjs`:
- Around line 41-48: The headless responder in claude.mjs is launching `claude
-p` with `--dangerously-skip-permissions`, which bypasses safety checks for
untrusted DM input. Update the `responder.buildArgs` logic under the `responder`
object to remove that flag and use the same restricted/sandboxed permission
model as the Codex responder, keeping the unattended `command: "claude"` flow
otherwise intact.
In `@sdk/plugin-tinyplace/adapters/codex.mjs`:
- Around line 136-140: The hooks.json templating in codex.mjs is inserting
pluginDir as a raw string, which can break JSON when the path contains
backslashes. Update the hooks.json generation in the codex adapter to use a
JSON-safe escaping approach before replacing ${TINYPLACE_PLUGIN_ROOT}, and keep
the change localized around the hooksTemplate/readFileSync/writeFileSync flow so
Windows paths remain valid.
In `@sdk/plugin-tinyplace/adapters/README.md`:
- Around line 37-40: The adapter contract for the inbound push channel is
inconsistent between this table and the design docs; align the README’s
`inbound`/`push` description with the canonical schema used by `DESIGN.md`.
Update the `inbound` row so it references the same field names as the rest of
the adapter contract, and make sure the `responder`, `install`, and `launch`
rows remain unchanged while the `push` shape is documented consistently in one
place.
In `@sdk/plugin-tinyplace/bin/tinyplace.mjs`:
- Around line 184-192: The raw-mode menu input handler in onData currently
ignores Ctrl+C, so add explicit cancel handling for the control bytes that
arrive in raw mode. Update the onData logic in tinyplace.mjs to treat "\x03"
(Ctrl+C) and "\x04" as a cancel path that calls finish(-1), alongside the
existing q handling, before falling through to render().
In `@sdk/plugin-tinyplace/DESIGN.md`:
- Around line 14-29: The diagram block in DESIGN.md is using an untagged fenced
code block, which triggers markdownlint MD040. Update the fence around the
architecture diagram to include a language tag such as text so the block remains
rendered correctly and passes lint; this applies to the diagram section near
detectHarness() and adapters[harness].
In `@sdk/plugin-tinyplace/hooks/respond-batch.mjs`:
- Around line 52-69: The prompt builder in buildPrompt interpolates untrusted
msg.id and msg.from directly into the auto_reply tool-call string, so sanitize
or escape both fields before insertion. Add validation/quoting for these values
in respond-batch.mjs (alongside the existing SAFE_SESSION_RE handling) so the
generated in_reply_to and to arguments cannot break the prompt or tool syntax.
In `@sdk/plugin-tinyplace/hooks/surface-inbound.mjs`:
- Around line 129-142: The direct-message context built in surface-inbound.mjs
needs the same untrusted-data framing used elsewhere. Update the `context`
construction around `fresh`, `shown`, and `emit(hookEventName, context)` so the
surfaced preview is explicitly labeled as untrusted and not to be followed,
before listing the attacker-controlled message snippets. Keep the existing inbox
guidance, but prepend/inline the safety warning so any model reading this
`additionalContext` treats the DM preview as data only, not instructions.
In `@sdk/plugin-tinyplace/mcp/outbox.mjs`:
- Around line 18-39: `recoverStaleClaims()` is requeueing `.sending-*` jobs
purely by age, which can duplicate deliveries when the owning daemon is still
running. Update the stale-claim recovery logic in `recoverStaleClaims` to parse
the owner pid from the `.sending-` claim name and check that pid is no longer
alive before calling `renameSync`; only restore the original job file when the
claim is both old enough and orphaned.
In `@sdk/plugin-tinyplace/package.json`:
- Around line 13-16: The smoke test command in package.json is defined but not
part of the main validation path, so live-boot coverage can be skipped. Update
the test workflow around the existing scripts block in package.json so
test:smoke is invoked by the CI/release gate, either by adding it to the main
test chain or wiring it into the automated pipeline used for plugin-tinyplace
validation. Keep the existing mcp-smoke.mjs entrypoint and ensure the gating
change is applied consistently wherever the package’s tests are orchestrated.
---
Nitpick comments:
In `@sdk/plugin-tinyplace/bin/tinyplace.mjs`:
- Around line 53-61: The saveWallets secret-key persistence is writing
wallets.json directly, which can leave the file truncated if the process crashes
or disk fills mid-write. Update saveWallets to use a
write-to-temp-then-renameSync flow so the final file is replaced atomically, and
make sure renameSync is imported from node:fs. Apply the same atomic-write
change in the mirrored wallet persistence logic in mcp/server.mjs so both
implementations stay byte-for-byte aligned.
In `@sdk/plugin-tinyplace/mcp-smoke.mjs`:
- Around line 51-57: The rpc() helper currently leaves requests pending forever
if the child process stops responding, so harden it by adding a timeout and
rejection path. Update rpc() to create a timer for each request that rejects and
clears the pending entry if no response arrives in time, and also reject any
still-pending requests when the child process exits or errors. Use the existing
rpc(), pending map, nextId counter, and child.stdin/child process wiring to
locate the fix.
In `@sdk/plugin-tinyplace/mcp/outbox.mjs`:
- Around line 1-90: Add a dedicated offline race test for outbox.mjs to cover
the atomic claim and stale-recovery behavior in claimOutboxJobs and
recoverStaleClaims. Mirror the style of registry-test.mjs and lock-test.mjs by
exercising writeOutboxJob, concurrent claims, and reclaiming a .sending-* file
after it becomes stale. Verify the job is not double-claimed, that a crashed
claim is requeued, and that done()/fail() leave the outbox in the expected
state.
- Around line 58-90: The retry path for claimed outbox jobs has no delay or
attempt cap, so a failing job is immediately requeued and can loop forever.
Update the outbox flow around claimOutboxJobs() and the claimed.fail() behavior
so drainOutbound() can track retry state for each job, apply backoff or a max
retry limit before making it reclaimable again, and avoid instantly renaming the
claim back to the pending file on repeated failures.
In `@sdk/plugin-tinyplace/mcp/routing.mjs`:
- Around line 59-62: The inbound hot path in enqueueRouted is scanning sessions
twice because liveSessions(agentAddress) and primarySession(agentAddress) each
trigger readSessions, with primarySession reusing liveSessions again. Refactor
enqueueRouted to derive both live and primary from a single
liveSessions(agentAddress) call, using the first sorted session label as the
primary value, and keep routeTarget unchanged except for consuming those shared
results.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c9cb7e81-531b-496a-bc43-739acc8f52cf
📒 Files selected for processing (31)
pnpm-workspace.yamlsdk/plugin-tinyplace/.gitignoresdk/plugin-tinyplace/DESIGN.mdsdk/plugin-tinyplace/adapter-contract-test.mjssdk/plugin-tinyplace/adapters/README.mdsdk/plugin-tinyplace/adapters/claude.mjssdk/plugin-tinyplace/adapters/codex.mjssdk/plugin-tinyplace/bin/tinyplace.mjssdk/plugin-tinyplace/branch-test.mjssdk/plugin-tinyplace/envelope-test.mjssdk/plugin-tinyplace/harness-test.mjssdk/plugin-tinyplace/hooks-test.mjssdk/plugin-tinyplace/hooks/agent-daemon.mjssdk/plugin-tinyplace/hooks/dispatch.mjssdk/plugin-tinyplace/hooks/hooks.jsonsdk/plugin-tinyplace/hooks/respond-batch.mjssdk/plugin-tinyplace/hooks/surface-inbound.mjssdk/plugin-tinyplace/lock-test.mjssdk/plugin-tinyplace/mcp-smoke.mjssdk/plugin-tinyplace/mcp/address.mjssdk/plugin-tinyplace/mcp/daemon-lock.mjssdk/plugin-tinyplace/mcp/foreground-inject.mjssdk/plugin-tinyplace/mcp/format.mjssdk/plugin-tinyplace/mcp/harness.mjssdk/plugin-tinyplace/mcp/outbox.mjssdk/plugin-tinyplace/mcp/registry.mjssdk/plugin-tinyplace/mcp/routing.mjssdk/plugin-tinyplace/mcp/server.mjssdk/plugin-tinyplace/package.jsonsdk/plugin-tinyplace/registry-test.mjssdk/plugin-tinyplace/routing-test.mjs
…ns (tinyhumansai#215) The Claude autoresponder fed attacker-controlled DM text into `claude -p` with --dangerously-skip-permissions, granting unrestricted shell/fs to a prompt-injected message. Mirror the codex `--sandbox read-only` intent: --permission-mode dontAsk (auto-deny unlisted tools), --tools "" (strip all built-in tools), --allowedTools mcp__tinyplace__auto_reply (single MCP tool as the only side-effecting path).
…ols (tinyhumansai#215) The Claude launcher points `claude --plugin-dir` at this package, but the package shipped no .claude-plugin/plugin.json or .mcp.json (they lived only under plugin-claude), so Claude opened with NO tinyplace MCP tools. Port both, declaring the MCP server as `tinyplace` (matching `server:tinyplace` in the launch args) and pointing it at mcp/server.mjs via ${CLAUDE_PLUGIN_ROOT}.
…tinyhumansai#215) hooks/hooks.json commands reference ${TINYPLACE_PLUGIN_ROOT}. Codex substitutes it at install; Claude consumes the shared hooks.json directly, and the launch env only set TINYPLACE_ACTIVE_WALLET — so hook commands expanded to node "/hooks/surface-inbound.mjs" and inbound surfacing + the Stop autoresponder broke on Claude. Set the env var to ctx.pluginDir; keep the shared token in hooks.json since codex depends on it.
…inyhumansai#215) bin/tinyplace.mjs's registerFlow spawns <plugin>/register.mjs, which was absent from this package (only in plugin-claude) — the Register @handle menu item hit a missing script. Port it harness-agnostic: the wallet store resolves via harnessDataDir() (the active adapter's data dir) instead of a hardcoded TINYPLACE_CLAUDE_HOME, so it serves both Codex and Claude. Signature (<walletName> <baseHandle>) matches the registerFlow spawn.
…nyhumansai#215) ensureIsolatedHome spliced pluginDir into hooks.json via a raw string replace. On Windows the backslashes in the path (and any embedded quote) land inside JSON string values and produce invalid JSON, breaking every hook. Escape the path with JSON.stringify(...).slice(1,-1) before splicing.
…humansai#215) recoverStaleClaims requeued .sending-* claims by mtime age alone, so a send still in flight past STALE_CLAIM_MS was reclaimed and delivered twice. The claim name carries the owner daemon's pid — check liveness (process.kill(pid, 0)) and only reclaim a dead (or unknown, age-gated) owner's claim. Extend lock-test to cover the live-pid skip vs dead-pid reclaim.
…inyhumansai#215) respond-batch dropped attacker-controlled msg.from and msg.id straight into quoted tool-call arguments (to="…", in_reply_to="…") in the responder prompt, so a crafted id/from could close the quote and inject extra args or instructions. Strip both to a safe charset ([\w:.-]) — well-formed base58 ids and slug-like relay ids are unchanged. Also refresh the stale responder-flag comment to match the sandboxed codex/claude arg sets.
…ansai#215) The attacker-controlled DM previews were injected verbatim as additionalContext with no untrusted-data framing, unlike server.mjs pushToChannel and respond-batch. Add the UNTRUSTED framing line so an embedded prompt-injection in a preview is treated as data, not instructions.
…humansai#215) DESIGN.md described inbound.push as {capability, pushMessage, pushContactRequest}, but the actual adapter/code shape (claude.mjs, server.mjs consuming ADAPTER.inbound.push.capability/.method) and adapters/README.md both use {capability, method}. Align DESIGN.md with the real schema.
…ansai#215) Under setRawMode(true), Ctrl+C is byte 0x03 (not SIGINT) and Ctrl+D is 0x04, and onData ignored both — so the menu swallowed Ctrl+C and couldn't be exited that way. Treat 0x03/0x04 as cancel (restore terminal, exit cleanly) like q/ESC.
…i#215) The ASCII architecture diagram used a bare code fence, tripping markdownlint MD040 (fenced-code-language). Tag it `text`.
…yhumansai#215) mcp-smoke.mjs boots the real unified MCP server over stdio for both harnesses and asserts the 20-tool set + push-capability gate. Its header documents it as offline (no tool touches the relay) and it passes with no network, so wire it into the automated `test` script rather than leaving it standalone. `test:smoke` is kept as a standalone alias for running it in isolation.
|
All CodeRabbit + codex review comments addressed in the 12 commits pushed (8bb2062…e1f90251), each replied inline with the fixing SHA. Highlights: the headless Claude responder is now sandboxed (dontAsk + tools stripped + single auto_reply MCP tool), Claude plugin metadata (.claude-plugin/plugin.json + .mcp.json) and TINYPLACE_PLUGIN_ROOT wiring restored, register.mjs ported, outbox double-send closed via pid-liveness, respond-batch args sanitized, and the offline MCP smoke is now CI-gated. Full suite (9 suites) green. @coderabbitai review |
|
(=^・ω・^=) ✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdk/plugin-tinyplace/mcp/outbox.mjs`:
- Around line 49-58: The fallback path in outbox.mjs is leaving unknown-owner
claim names with the generic `.sending-` prefix, so the age-gated requeue never
restores the original filename. Update the claim-name handling around the `orig`
extraction logic in the outbox scan so unparseable names like
`.sending-job.json` are converted back to `job.json` before the rename/requeue
step, while preserving the existing owner-pid branch behavior in the
`pidAlive`/`STALE_CLAIM_MS` flow.
In `@sdk/plugin-tinyplace/register.mjs`:
- Around line 19-20: Validate the CLI inputs in register.mjs before any wallet
registry or handle derivation work begins: the argument parsing around
process.argv.slice(2) should explicitly require both walletName (name) and
baseHandle, and abort early with a clear error if either is missing or empty.
Update the register flow that consumes baseHandle so it never calls the registry
with undefined and never builds fallback handles from an undefined value; keep
the check near the existing argument destructuring and h2b helper so the
entrypoint fails fast.
- Around line 30-35: The fallback handle logic in register.mjs needs to avoid
generating invalid suffixes and must only return a handle that has been checked
for availability. Update the handle selection inside the loop around
client.registry.get(handle) so the reduction over w.address uses a numeric
accumulator from the start, preventing NaN when the address contains non-numeric
characters, and make sure the final handle assignment does not create a new
unchecked candidate after the last failed probe. Keep the fix localized to the
registration loop and the handle generation using baseHandle,
client.registry.get, and w.address.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 40ef172c-5f11-46e4-914a-262aedb67ba2
📒 Files selected for processing (12)
sdk/plugin-tinyplace/.claude-plugin/plugin.jsonsdk/plugin-tinyplace/.mcp.jsonsdk/plugin-tinyplace/DESIGN.mdsdk/plugin-tinyplace/adapters/claude.mjssdk/plugin-tinyplace/adapters/codex.mjssdk/plugin-tinyplace/bin/tinyplace.mjssdk/plugin-tinyplace/hooks/respond-batch.mjssdk/plugin-tinyplace/hooks/surface-inbound.mjssdk/plugin-tinyplace/lock-test.mjssdk/plugin-tinyplace/mcp/outbox.mjssdk/plugin-tinyplace/package.jsonsdk/plugin-tinyplace/register.mjs
✅ Files skipped from review due to trivial changes (2)
- sdk/plugin-tinyplace/.mcp.json
- sdk/plugin-tinyplace/.claude-plugin/plugin.json
🚧 Files skipped from review as they are similar to previous changes (7)
- sdk/plugin-tinyplace/package.json
- sdk/plugin-tinyplace/adapters/claude.mjs
- sdk/plugin-tinyplace/adapters/codex.mjs
- sdk/plugin-tinyplace/hooks/surface-inbound.mjs
- sdk/plugin-tinyplace/DESIGN.md
- sdk/plugin-tinyplace/hooks/respond-batch.mjs
- sdk/plugin-tinyplace/bin/tinyplace.mjs
…humansai#215) recoverStaleClaims stripped a `\d+-` prefix for unparseable claim names, so an owner-less claim like `.sending-job.json` kept its full name in `orig` and renameSync(p, orig) was a rename-to-self — the aged job was never requeued (and the leftover dot-file is skipped by the pending scan, so it's silently dropped). Strip the generic `.sending-` prefix instead. Extend lock-test with the aged unknown-owner requeue (restored to its real name) and the fresh-claim age-gate skip. Addresses CodeRabbit review on mcp/outbox.mjs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…humansai#215) Two correctness bugs in the ported register flow: - No CLI-arg validation: a missing baseHandle called the registry with `undefined` and derived `undefined…` fallback handles. Fail fast with usage. - The availability loop assigned a fresh handle on its last failed iteration and then registered it WITHOUT checking availability. Rework to test each candidate (base, then deterministic variants) and only register one confirmed available; exit non-zero if none of the 6 tries is free. Addresses CodeRabbit review on register.mjs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
One installable plugin —
@tinyhumansai/tinyplace-plugin(sdk/plugin-tinyplace/) — that a user installs once and it detects its harness at runtime (Codex CLI / Claude Code / any future one) and self-wires: MCP server, hooks, launcher, and inbound strategy. No per-harness package, no manual wiring.It unifies the two near-identical plugins (
plugin-claude~1280 loc,plugin-codex~1333 loc, ~95% shared) into a single package where the ~5% that differs is isolated into runtime-selected adapters. Same 20 MCP tools, same wallet store, same Signal E2E drain/send, same daemon — one codebase, two harnesses proven live.How it works
mcp/harness.mjs—detectHarness(env)→activeAdapter()(memoized). Order:TINYPLACE_HARNESSoverride → Codex env signals (CODEX_HOME/CODEX_SESSION_ID/CODEX_THREAD_ID) → Claude signals (CLAUDE_PLUGIN_ROOT/CLAUDE_CODE_SESSION_ID) → safe default.adapters/{claude,codex}.mjs— the ONLY per-harness surface: data dir, session/label prefix,serverInstructions,inbound(Claude channel push vs Codex pull),responder(claude -pvscodex exec), andlaunch.prepare()(install recipe). The shared core reads only this descriptor — no harness name appears anywhere in the 20 tools, the daemon, or the hooks.bin/tinyplace.mjs— one harness-agnostic launcher: wallet store + menu + import/register, then delegates toactiveAdapter().launch.prepare(). Claude getsclaude --plugin-dir; Codex gets an isolatedCODEX_HOME(config.toml+ auto-discoveredhooks.json+ symlinkedauth.json).--harness <name>forces it; otherwise auto-detected.Contributor convention (add a harness safely)
Requested guardrail so new contributors can't break the plugin while adding a harness:
adapters/README.md— the authoring guide: the full field-contract table,launch.preparerules, a wiring checklist, and the do-nots (no harness names in core, no shared data dir, keep theUNTRUSTEDframing, always leave an inbound delivery path).adapter-contract-test.mjs(wired intonpm test) — structurally enforces that contract for every adapter in theADAPTERSmap: a missing/wrong-shaped field, a shared data dir, a droppedUNTRUSTEDguard, an inbound with no delivery path, or alaunch.preparethat returns an invalid plan fails CI. Adding a harness = copy an adapter, fill the fields, add a detection signal, make the contract green.Security
--sandbox read-only(never--dangerously-bypass-approvals-and-sandbox). The responder pipes attacker-controlled DM text intocodex exec; read-only sandbox keeps a prompt injection off the shell/filesystem — the only side-effecting path is theauto_replyMCP tool. (Parity with the plugin-codex feat(sdk): Codex CLI plugin for tiny.place (parity port of plugin-claude) #214 critical fix.)serverInstructionsmark inbound DMs as UNTRUSTED data, never instructions — enforced by the contract test.Testing
Self-contained offline suite + a live-boot smoke, all green:
harness28 ·adapter-contract72 (2 adapters) ·branch16 (same core flips codex↔claude purely fromTINYPLACE_HARNESS) ·envelope34 ·registry25 ·routing22 ·lock15 ·hooks✓test:smoke15 — boots the ONE unified MCP server under both harnesses over real JSON-RPC: identical 20-tool set, push-capability gate flips (Claude advertisesclaude/channel, Codex doesn't), instructions differ.plugin-tinyplaceis a standalone package (ownnode_modules/lockfile), excluded from the pnpm workspace exactly likeplugin-claude— so rootpnpm -r lint/buildand the website format check don't cover it; it runs its ownnpm test.Scope / follow-ups
mainand contains ONLYsdk/plugin-tinyplace/+ a one-linepnpm-workspace.yamlexclude. It does not depend on feat(sdk): Codex CLI plugin for tiny.place (parity port of plugin-claude) #214 (plugin-codex) or feat(sdk/plugin-claude): foreground-resolve inbound DMs via tmux TTY injection #212 (tmux inject) merging.plugin-claude/plugin-codexinto thin re-export shims is a follow-up once feat(sdk): Codex CLI plugin for tiny.place (parity port of plugin-claude) #214 lands (doing it now would conflict with that open PR). The unified package imports from neither.foreground-injectslot when it merges.Summary by CodeRabbit
@tinyhumansai/tinyplace-pluginexperience with a Tinyplace CLI/TUI for wallet setup/import and automatic harness selection (Codex/Claude).