Power your agent
คำสั่งของผู้เผยแพร่เอง ไม่ใช่เอนด์พอยต์ของ PlugMCP
Host a copy with our keys and logs.
npx -y [email protected]Claude Code ไม่มีลิงก์ติดตั้งสำหรับคำสั่งแบบโลคัล ให้คัดลอกคำสั่งด้านล่างแล้วรันในเทอร์มินัล
claude mcp add aiterm-steer-delivery -- npx -y aiterm-steer-delivery@0.5.3รันในเทอร์มินัล
{ "mcpServers": { "aiterm-steer-delivery": { "command": "npx", "args": [ "-y", "[email protected]" ] } }}claude_desktop_config.json
URL แบบรีโมตหรือคำสั่งบนเครื่องของคุณ? การติดตั้งสองแบบต่างกันอย่างไร
รันบน PlugMCP
A new server you create. It is not this listing's official endpoint. Use the publisher install when you want their URL and their credentials.
เปิดจากผู้เผยแพร่
Let your MCP server talk back to the AI session that called it.
Your MCP server can deliver text to the parent AI session that called its tool, even after the tool call has returned:
Works with Codex, Claude Code, and Cursor. Other clients (Grok and so on) receive through a background process. Linux, macOS, and Windows.
If you run many AI sessions at once, the useful work often happens between them: a sub-agent finishes and its parent should hear about it, or one session posts in a shared room and another should react. A tool call returns once. Anything that happens later has nowhere to go unless the parent comes back and asks.
Delivering that later text to the right conversation is the hard part:
/clear.CODEX_HOMEs on the same machine.This package is the delivery Aiterm uses to hand a sub-agent's answer back to its parent. It was extracted from Aiterm without changing behavior, so other products do not have to rebuild conversation correlation, hook registration, and OS handling every time.
Every route uses the client's official mechanism. Nothing types into a terminal or patches the client.
Delivery never auto-retries a send whose outcome is unknown. If the library cannot confirm whether text reached the parent, it reports outcome_unknown (or the channel state unknown) and leaves the decision to you, so the parent never gets the same message twice by accident.
npm install aiterm-steer-delivery
Requirements:
zod.pwsh.exe).Everything product-specific lives in one object. The delivery mechanism is the same for every product.
// profile.mjs
export const PROFILE = {
id: "peertable", display_name: "Peertable",
setup_command: "peertable parent connect", codex_steer_command: "peertable parent connect --target codex",
mcp_server: "peertable_parent", dispatch_tools: ["parent_join", "parent_leave"],
state_root: () => stateDir, config_root: () => configDir,
hooks: { codex: "peertable-parent-codex-hook.mjs", claude: "peertable-parent-claude-hook.mjs", cursor: "peertable-parent-cursor-hook.mjs" },
codex_client_name: "peertable_parent_delivery", codex_hook_schema: "peertable.codex-parent-hooks.v1",
backup_suffix: ".peertable-backup",
channels: { claude_expiry_notice: "..." }, // only for channel products
};
Hook entry files stay in your product, because their names identify your hooks and keep products from removing each other's hooks. Each is a two-line file:
#!/usr/bin/env node
import { runClaudeHookMain } from "aiterm-steer-delivery";
import { PROFILE } from "./profile.mjs";
await runClaudeHookMain(PROFILE);
Use runCodexHookMain, runClaudeHookMain, and runCursorHookMain for the three hook files. Background receivers use runCursorReceiveMain (one answer) or runChannelReceiveMain (channels).
The Cursor parent starts the one-answer receiver detached, so it outlives the parent. Before it claims an answer, the receiver checks that the parent is still reading its output. If the parent is gone, it leaves the answer unclaimed and exits with code 5; the sender sees the delivery as not yet received instead of delivered.
In your setup command:
mergeClaudeParentHooks(PROFILE, settingsFile, { command: nodePath, script: hookPath }) for Claude Code,mergeCursorParentHooks(PROFILE, hooksFile, { command: nodePath, script: hookPath }) for Cursor,configureCodexSteer(PROFILE, "enable", { hook: hookPath }) for Codex.They keep other products' and the user's hooks and their order, and back up the file before writing. removeClaudeParentHooks, removeCursorParentHooks, and configureCodexSteer(PROFILE, "disable", …) undo them. configureCodexSteer returns restart_required when Codex processes that started before the hooks were installed are still running. PowerShell paths use the actual filesystem spelling, so PATH casing alone does not rewrite Codex hooks or require a restart.
The Claude Code hooks are written as one shell line, without args:
exec '<node>' '<hook>'. exec replaces sh, so Claude Code stays the direct parent of the hook.& '<node>' '<hook>'; exit (Get-Variable LASTEXITCODE -ValueOnly) with shell: "powershell". PowerShell would otherwise turn the hook's exit code 2 into 1, and asyncRewake needs the 2.Grok also runs the hooks in ~/.claude/settings.json, but it drops args and runs command alone. With the command + args form that 0.1.x wrote, Grok started bare node, which read the hook input as a script and failed at every Stop. runClaudeHookMain now does nothing and exits 0 when Grok starts it (Grok puts its own hookEventName in the input), because Grok parents receive through the background process, not through hooks.
mergeClaudeParentHooks replaces your product's 0.1.x entries with the new form, and removeClaudeParentHooks removes both forms. To check a settings file without writing it, use claudeParentHooksRegistered(PROFILE, document) and claudeParentHookScripts(PROFILE, document) (the hook file paths the entries point to); do not read args yourself. Before you go back to 0.1.x, call removeClaudeParentHooks from 0.2.x: 0.1.x does not recognize the new entries and would add a second set.
The MCP parent-delivery APIs offer two ways to deliver.
One answer per request (what Aiterm does): identify the parent from the MCP request, bind a delivery id, and submit the final text once.
import * as steer from "aiterm-steer-delivery";
const parent = steer.codexParentFromRequest(clientName, request.params._meta);
await steer.verifyCodexParent(PROFILE, parent);
const { queued_submission_id } = await steer.submitCodexParentAnswer(PROFILE, parent, deliveryId, text);
When a Codex submission fails and the text is known not to be in the official queue (outcome_unknown is false: the receiver rejected it, or the call stopped before asking the queue), you can submit again with the same delivery id. Codex rejects an archived thread with CODEX_RECEIVER_REJECTED, so after codex unarchive the same delivery id goes through. After an accepted submission, or a failure whose outcome is unknown (outcome_unknown is true), a product with hooks enabled gets CODEX_DELIVERY_DUPLICATE for the same delivery id while the ownership record (pending/<delivery id>.json) exists, and nothing is sent. Use one delivery id per text; never reuse an id for a different text.
Claude Code and Cursor follow the same shape with claudeParentFromRequest / bindClaudeParentDelivery / submitClaudeParentAnswer and cursorParentFromRequest / prepareCursorDelivery / submitCursorParentAnswer. Each …ParentFromRequest returns null when the caller is a different client, so you can try them in turn. For Cursor, put the delivery id in your tool result, as parent_delivery: { delivery_id } in structuredContent and as a delivery_id=<uuid> line in the text, so the hook can bind it to the conversation.
Claude Code does not run the PostToolUse hook when a tool result is an error (isError). When you return an error, call discardClaudeHookRequest(clientName, request.params._meta, claudeHookRoot(PROFILE)) before returning, to remove that call's request directory. A directory with a bound delivery is kept. If you do not call it, the next request's hook removes the leftover after one day.
Deliver to a Codex parent without registering product hooks (ask Aiterm's parent delivery):
On a machine with Aiterm (0.56.0 or later) installed, a product can hand a final answer to Aiterm instead of owning Codex hooks. Aiterm's registered hooks and the official queue deliver it. The product never writes Aiterm's files.
const parent = steer.codexParentFromRequest(clientName, request.params._meta);
const { steer: mode } = await steer.verifyCodexParentViaAiterm(parent); // "enabled" | "disabled"
await steer.submitCodexParentAnswerViaAiterm(parent, randomUUID(), text); // a fresh UUID per message
await steer.codexDeliveryDetailViaAiterm(parent, deliveryId); // { state, hook, turn_id, queued }
steer from verify is about configuration (enabled: Aiterm's hooks are registered and trusted, and the parent started after they were installed; disabled: official queue only, so the text arrives after the turn ends). The result of submit is the queue's acceptance. Actual arrival is read with codexDeliveryDetailViaAiterm (hook: "emitted" with turn_id: Aiterm's hook put the text into that turn; queued: whether it is still in the official queue).CodexDeliveryError as before (delivery_code, outcome_unknown), carrying the reason Aiterm returned. When Aiterm's command is missing, too old, or returns something unreadable, the error is AITERM_PROVIDER_UNAVAILABLE; there is no fallback to another route. Reusing a delivery id is refused with PARENT_DELIVERY_DUPLICATE.aiterm-parent-delivery) is located from the cli option or AITERM_PARENT_DELIVERY_CLI, then ~/.config/aiterm-mcp/delivery-provider.json written by aiterm-setup, then PATH. findAitermDeliveryProvider() shows what was found.configureCodexSteer(PROFILE, "disable", { hook }). Trust for other hooks that move is copied to their new positions.Codex starts a queued message only in a conversation that some running Codex process has loaded. The Codex app unloads a conversation 60 seconds after nobody is viewing it (thread_unload_delay_secs), and from then on its queue is not looked at until the conversation is opened again. Without help, a reply sent to such a conversation waits until a person opens it.
submitCodexParentAnswer therefore starts a small watcher in a separate process after the queue accepts the text. About 15 seconds later the watcher looks at the queue again:
source is vscode), and its last turn ended normally (or it has no turn yet): the watcher asks the OS to open codex://threads/<id>. The app loads the conversation and the official queue starts the turn. The app's window switches to that conversation; on macOS the app is not brought to the front.The outcome is saved under <config_root>/codex-parent-hooks/wake/<delivery id>.json for three days and can be read with readCodexWakeResult(PROFILE, deliveryId): delivered, running, interrupted, not_app_thread, unknown_state, no_opener, open_failed, woken, or opened_still_queued. The acceptance returned by submitCodexParentAnswer does not depend on the watcher.
The watcher runs on macOS and Windows. On Windows the link is opened only from an interactive session (a link opened from a service or an SSH session never reaches the app on the desktop; the outcome is no_opener). On Linux nothing is started yet: asking the Linux app to open the same link did not load the conversation in our tests, so a reply to a sleeping conversation still waits there until the conversation is opened. On servers and in containers nothing is started. Set AITERM_STEER_CODEX_WAKE=0 to turn it off. To check and wake right now from your own process, call wakeCodexParentIfAsleep(PROFILE, parent, deliveryId, { delay_ms: 0 }).
Many messages to one conversation (channels, e.g. Peertable's room messages to the parent):
const channel = steer.openChannel(PROFILE, parent); // parent from the request; null for background-only
await steer.sendToChannel(PROFILE, channel.channel_id, randomUUID(), text);
steer.channelDeliveryState(PROFILE, channel.channel_id, id); // queued | sending | emitted | unknown | withdrawn
Claude Code re-arms its waiter at every Stop, and arms it at SessionStart as well. The waiter belongs to the Claude Code process that ran the hook: it keeps waiting while that process is alive, and it delivers every open channel of that conversation (session_id), including channels opened by an earlier process. So when Claude Code restarts and resumes the same conversation in a new process, messages that arrived while it was down wake the resumed conversation, even before it runs a turn. (A registration written by 0.4.1 or earlier has no SessionStart hook; it still works, and the resumed conversation starts receiving at the end of its first turn. Run your setup again to add the hook.) Cursor binds the channel with steer.channelMarker(channel) placed in your tool result, and idle Cursor/Grok parents run steer.channelReceiveProcess(...) in the background; each result carries next_wait_process to re-arm. withdrawFromChannel takes back text no receiver has claimed yet, so you can resend it on a new channel; closeChannel ends one.
Put both the marker and the receiver command in the text of your tool result, not only in structuredContent: the Cursor CLI model does not read structured content. steer.waitProcessCommandLine(steer.channelReceiveProcess(script, channel.channel_id)) gives the command as one shell line (POSIX sh, or PowerShell on Windows).
sendClaudeInbox(target, text, options) sends one user message to Claude Code's session inbox. Capture CLAUDE_CODE_MESSAGING_SOCKET and CLAUDE_CODE_MESSAGING_TOKEN in the target's SessionStart hook. This API works before that conversation has called an MCP tool, and needs no ProductProfile or ClaudeParent.
import { sendClaudeInbox } from "aiterm-steer-delivery";
const text = "Continue the work.";
const result = await sendClaudeInbox({ socket_path, token }, text, {
timeout_ms: 30_000,
// Your product observes the target's UserPromptSubmit receipt.
confirm_acceptance: signal => productReceipts.waitForPrompt({ session_id, text, signal }),
});
Capture the endpoint from the target hook; do not infer it from a PID. Windows requires a token; POSIX sends the authentication line when a token is supplied. Keep the token private. confirm_acceptance starts before connecting; arm any receipt registration before calling. Match the target session and this message, rather than an older receipt or merely a new timestamp. The observer receives an AbortSignal when the call finishes. Its false result means unconfirmed.
The raw inbox post has no same-connection acknowledgement proving conversation acceptance. accepted requires your observer to confirm receipt, for example at UserPromptSubmit; it does not prove task completion. not_sent means no message write was attempted. After a write is attempted, a failure or missing confirmation returns unknown with outcome_unknown:true. With no observer, even a successful write returns unknown. The library never retries. Claude's inbound controls still apply. The result contains only status, reason, and outcome_unknown and excludes the token, message, and endpoint.
Delivery errors are SteerDeliveryError (CodexDeliveryError, ClaudeDeliveryError, CursorDeliveryError, ChannelError) with:
delivery_code: a stable reason, for example CLAUDE_PARENT_HOOK_UNAVAILABLE or CHANNEL_CLOSED;outcome_unknown: true when the text may or may not have reached the parent. Do not resend automatically.Setup failures are SetupError, whose code becomes your setup result's reason_code.
Channel delivery states:
A channel accepts a delivery id once. Sending the same id to the same channel again is refused with CHANNEL_DELIVERY_DUPLICATE, whichever state the first one is in (including queued); nothing is added, and channelDeliveryState tells you where the first one is.
On a Codex channel, sendToChannel returns submitted with the official queue's queued_submission_id, and channelDeliveryState returns null.
Known limit: Claude Code sessions that carry an agent_id (started with --agent, or native subagents) are not supported as parents.
aiterm-steer-delivery --profile <profile.json> codex <parent|verify|submit|state|wake|setup> prints one JSON line. state_root and config_root are absolute paths in the JSON profile. codex setup enable registers the package's own Codex hook entry for your profile.
aiterm-steer-delivery --profile profile.json codex parent --client <name> --meta <json>
aiterm-steer-delivery --profile profile.json codex verify --thread <uuid> [--codex-home <dir>]
aiterm-steer-delivery --profile profile.json codex submit --thread <uuid> --delivery <uuid> --text-file <file|-> [--codex-home <dir>]
aiterm-steer-delivery --profile profile.json codex state --thread <uuid> --delivery <uuid> [--codex-home <dir>]
aiterm-steer-delivery --profile profile.json codex wake --thread <uuid> --delivery <uuid> [--delay-ms <n>] [--codex-home <dir>]
aiterm-steer-delivery --profile profile.json codex setup <enable|disable|status>
codex submit starts the watcher described in Sleeping Codex conversations; the command returns as soon as the queue accepts the text. codex state also returns wake once the watcher has saved its outcome. codex wake checks and wakes right now.
Success is {"ok":true,...}. Failure is {"ok":false,"code":...,"message":...,"outcome_unknown":...} with exit code 1.
npm ci
npm test # builds with tsc, then runs node --test
CI runs the tests on Ubuntu, macOS, and Windows. Pushing a v<version> tag publishes to npm with provenance. Changes are listed in CHANGELOG.md.
Aiterm's steer delivery as a library: deliver text to the parent AI session that called your MCP tool — steered into the running turn when it is busy, delivered normally when it is idle. Codex (offici
{ "mcpServers": { "aiterm-steer-delivery": { "command": "npx", "args": [ "-y", "[email protected]" ] } }}ภาพรวม
Aiterm's steer delivery as a library: deliver text to the parent AI session that called your MCP tool — steered into the running turn when it is busy, delivered normally when it is idle. Codex (official queue + sync hooks), Claude Code (asyncRewake hooks)
เครื่องมือ
ยังไม่มีรายการเครื่องมือ เซิร์ฟเวอร์แบบรีโมตจะได้รายการหลังการตรวจสอบสถานะครั้งแรก
FAQ
คัดลอกคอนฟิก stdio สำหรับ Cursor, Claude Desktop, Claude Code หรือ VS Code ได้ในหน้านี้ คอนฟิกนี้ใช้การติดตั้งของผู้เผยแพร่ ไม่ใช่พร็อกซีของ PlugMCP
ระเบียนนี้ไม่ได้ระบุตัวแปรสภาพแวดล้อมที่จำเป็น
Aiterm Steer Delivery ทำงานแบบโลคัลผ่าน stdio
ตรวจสอบล่าสุดเมื่อ 2026-11-14 Aiterm's steer delivery as a library: deliver text to the parent AI session that called your MCP tool — steered into the running turn when it is busy, delivered normally when it is idle. Codex (official queue + sync hooks), Claude Code (asyncRewake hooks)