Repository navigation
Conversation
…sked Messages are now informational by default: team_send delivers and wakes the peer but does not ask for a reply. A pending-reply entry and a one-time reminder are created only when the sender sets requireResponse true (tool `requireResponse: true`, command `/team send --require-response`). Why: the old shape treated every request (re empty, non-fyi) as something to answer and remind about, so routine notifications cost the peer a full model turn. The flag is opt-in and lives on the message, not the config. - session: classifyInbound returns requireResponse (strictly body.requireResponse === true); fyi and re-bound replies never require a response, even with the flag set. No-response requests are injected with an explicit model-facing "no reply needed" note. - session: separate incomingIds association keeps re correlation for optional replies to no-response messages without creating reminders; consumed once. bindReply can skip it (allowAssociation:false) so an explicit requireResponse is not swallowed and still reaches the peer as a new request. - session/dispatch/index: thread the flag end-to-end, including the bulk- confirmation party (so confirming a broadcast cannot drop it) and the envelope body via sendBodyFrom (true written; absent/false both mean false). - i18n: dispatch.send.usage, dispatch.send.awaitReply, tool.requireResponse and ui.acRequireResponse in both catalogs; sink-guard whitelist updated. - docs: README, docs/how-it-works.md and the shipped skill describe the default and the opt-in. Tests: red->green regressions for omitted/false/true classification, pending/reminder, reply suppression, reply=off, optional-reply association, no-ping-pong, tool/command intention threading and bulk confirmation.
…s a reply
Public API now offers two clear tools instead of an opt-in boolean:
team_send({to,text}) delivered + wakes the peer, no reply requested
team_ask({to,text}) asks for a reply; reminded once if unanswered
and the same pair on the command line: /team send vs /team ask.
- remove requireResponse from the public tool schema, and
--require-response from command parsing / completion / help / docs
- keep requireResponse as the private wire field (absent for send,
true for ask) so envelopes stay interoperable
- rename the shared internal delivery helpers to the generic transmit
vocabulary: dispatch transmit/doTransmit/transmitResult and session
transmit/transmitBodyFrom. transport.send and the wire `send`
intention are untouched
- both tool wrappers share one parameter schema, render and execute
helper; menu offers send vs ask; completion exposes ask
- i18n: usage is parameterised by subcommand, new menu keys in both
catalogs, old flag-only key dropped
- new src/index.test.js: real index.ts harness via module.registerHooks
(fake transport + recording typebox) asserting public schema, wire
flags, immediate receipt, and completion/menu wiring; skips on old Node
- docs updated: README, docs/how-it-works.md, skill
…peer request Reviewer observation: an ask sent to a peer that already had an unanswered request from them was silently bound as a reply (re set) and its requireResponse normalized to false, so the peer was never reminded. Required semantics: team_ask ALWAYS requests a reply; team_send is the explicit reply path. - doTransmit: when wantsReply (ask) skip bindReply entirely — re null, hops 0, replyTo null — and do not consume the peer's pendingReplies or incomingIds, so a later team_send can still answer the original request - bindReply: drop the now-unused allowAssociation option and its doc - tests: dispatch command + tool regressions (pending ask from peer -> local ask stays a new request with the old pending retained -> send answers the original correlated request); also no consumption of the optional incomingIds association; plus an index.ts harness regression driving an inbound ask through the fake transport and checking the team_ask / team_send wire - model guidance + docs: team_ask always starts a new request (system prompt, team_ask guideline, README, how-it-works, skill) - private wire replies with re set still suppress reminders on the receiver
… a reply is pending
Replies are now explicit. An incoming request is answered only by
team_reply({ requestId, text }) (tool) / /team reply <requestId> <text>
(command): the id routes the answer to the stored sender and clears only
that one request, so two pending requests from the same peer stay
independently answerable. Missing, unknown, already-answered, or stale ids
fail without sending an envelope and without touching any pending record.
team_send / team_ask are purely new messages again (re=null, hops=0): they
no longer bind to, or consume, an incoming request. Answering a teammate is
always team_reply.
send/ask to a peer with an unanswered request are rejected before anything
is transmitted; the error lists the pending request id(s) and points at
team_reply. Multi-recipient sends (@Label, *, comma lists) are validated as
a whole and rejected atomically, and bulk broadcasts are re-checked inside
doTransmit because a request can arrive while the confirm dialog is open.
Unrelated peers are unaffected.
Pending requests are keyed by request id and separated from reminder
emission: reply=off and the once-only reminder no longer erase an
obligation, and mirror never satisfies one. onTurnSettled reminds each
pending request at most once while it remains unanswered.
The reminder strategy moves to /team replies <off|remind|mirror>; the old
/team reply <mode> (single mode word) still works and prints a note, as do
/team announce, auto, always, and the reply= flags.
Injected payloads are short and carry the request id with one team_reply
instruction; plain sends say no reply is required (optional explicit reply
still allowed). New M keys in both catalogs; menu and autocomplete expose
team_reply; README / how-it-works / SKILL updated. e2e/conformance.js is
unchanged and there are no new dependencies.
Review finding: explicitReplyResult consumed the matched request as soon as doTransmit produced the send intention -- before the real socket write. A rejected write therefore lost the obligation and could still report delivered:true. - doTransmit stamps the reply send intention with a private replyRequestId (never written to the envelope; transmitBodyFrom ignores it). - runIntentions consumes it only after transport.send returns true, and turns false/throw into a failure record plus a localized notify; a thrown send is caught safely and the pending request is retained. - runParty (confirmBulk) and invoke propagate send failures, so the tool and command paths report delivered:false instead of success for a rejected write. - add notify.sendError to both catalogs for the thrown case. - menu request-id matching is now exact, so one id cannot prefix-match another. - regressions in src/index.test.js: failed/throwing team_reply retains the pending and still blocks send/ask to that peer, retry success clears exactly that request; optional (non-request) association retained the same way; bulk confirm failure propagates; menu prefix safety. - dispatch tests updated: dispatch no longer consumes; consumption is the caller's job after a confirmed write.
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
Separate informational messages, new questions and explicit replies:
team_send({ to, text }): informational; no response reminder.team_ask({ to, text }): requests an answer; returns local transport acceptance immediately, not the answer.team_reply({ requestId, text }): answers the exact incoming message; missing, unknown or already-answered IDs fail.Shared internals are named
transmit/doTransmit. Short receiving-agent instructions include exact request IDs. All new user-visible text uses the English and Chinese catalogs.Pending-request rules
Commands mirror the tools:
/team send,/team ask,/team reply <requestId> <text>. Reminder policy moves to/team replies; legacy single-mode/team reply off|remind|mirrorremains supported with a migration hint.Stack
Based directly on
wave-i18n(#2), independent of hyperswarm (#1) and membership restoration (#3).combined-internalcontains all changes for internal testing. No fork planning documents are included.Verification
npm test: 470 tests, 464 pass, 0 fail, 6 existing SWIM-sidecar skips; actual executor harness ran.Four-participant E2E follow-up
Added
e2e/four-participant.test.jsand its child fixture. Four separate processes load the actual extension, invoke registered tools, and communicate through a real loopback broker/native WebSockets with no LLM and no mocked transport.Per run: 39 concurrent/interleaved questions to A, 40 exactly correlated replies overall, 87 actual received socket deliveries/model injections, and 36 once-only reminders. Tests cover out-of- 8000 order replies, the 32-payload cap, recipient-specific blocking, missing/stale IDs, simultaneous same-ID replies, informational no-reminder/no-ping-pong behavior, and bounded cleanup even when OS kill fails.
Focused E2E passed three consecutive final runs. Final Astra review: approved. Parent combined
npm test -- --test-concurrency=2: 472 tests, 466 pass, 0 fail, 6 existing SWIM skips; all four participants executed. Syntax and diff checks clean. No production/package/conformance changes.The default-concurrency feature run hit the host thread limit (
pthread_create: Resource temporarily unavailable) and timed out. Bounded concurrency is an explicitly approved validation workaround, not a passing claim for that default run; package scripts are unchanged.Limits
Pending request IDs remain runtime-scoped across reload/reconnect. Successful transmission means local transport acceptance, not recipient receipt or processing. Compact unanswered metadata grows with accepted obligations; full reminder payload storage is bounded. Executor tests require newer Node module hooks/type stripping. Live multi-machine validation of the new API remains an internal smoke-test step.