feat(sandbox): thread backend.interactions through stream prompt options#166
Merged
Merged
Conversation
Brings BackendConfig.interactions and the SDK-internal wire allowlist (normalizeRuntimeBackendConfig) that forwards the field; older SDK versions silently strip it. Raises the peerDependency floor to >=0.9.7 so consumers get a version that actually forwards interactions.
The sandbox question channel (agent pauses mid-turn to ask the user) is
off unless backend.interactions is set. agent-app builds the backend
object but hardcoded it without the field, so consumers could not enable
it — the missing link between the SDK surface and products like gtm-agent.
Add an opt-in interactions?: { question?, permission?, plan? } option to
StreamSandboxPromptOptions and forward it VERBATIM into the streaming
backend config. No per-harness matrix here: the sidecar rejects
unsupported kinds loudly at session init; a local matrix would drift.
The detached box.prompt path (driveSandboxTurn) deliberately never sets
it — cron/mission-step turns have no consumer to answer a question.
detectInteractiveQuestion also learns the generic interaction/kind:
"question" event shape, strengthening the disallowQuestions backstop.
drewstone
added a commit
that referenced
this pull request
Jul 8, 2026
…t-stream primitive (#176) * feat(chat): add shared ChatInteraction contract to web-react Lift the framework-agnostic interaction contract (ChatInteraction type, wire<->persisted codecs, dedupe/transition/compose-answer helpers) into web-react so every agent-app consumer imports one source of truth instead of hand-rolling it. Pure module (no React), re-exported from the web-react barrel alongside the chat-stream primitives. * feat(chat): surface sidecar interaction line via onInteraction callback dispatchChatStreamLine silently dropped the sidecar 'interaction' NDJSON line — the backend already threads it into the stream (#166), but the client parser had no seam for it. Add an optional onInteraction callback to ChatStreamCallbacks and a case in the switch that validates the ask with the shared wire codec and surfaces the parsed ChatInteraction; a malformed ask is logged and skipped. Optional callback keeps every existing consumer unchanged. Covers dispatch, the {kind:'event'} envelope, and consumeChatStream drain, plus a non-breaking (no callback) path.
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.
Closes #165.
Summary
The sandbox question channel — where an agent pauses mid-turn to ask the user a clarifying question — is off unless the session's
backend.interactionsis set. agent-app builds thebackendobject but hardcoded it to{ type, profile, ...model }with nointeractions, so consumers likegtm-agentcould not enable it. agent-app was the missing link between the published SDK surface (@tangle-network/sandbox@0.9.7) and consumers.This threads an opt-in
interactionspassthrough through the streaming/interactive prompt path and deliberately withholds it from the detached path (which has no consumer to answer a question). The field is forwarded verbatim — agent-app does not duplicate the per-harness support matrix; the sidecar rejects unsupported kinds loudly at session init, and a local matrix would drift.Changes
package.json— bump@tangle-network/sandboxdevDep^0.9.5→^0.9.7and raise the peerDependency floor>=0.9.4→>=0.9.7. Older SDKs strip the field client-side (normalizeRuntimeBackendConfig), so the floor makes the requirement explicit.StreamSandboxPromptOptions— new inline-typedinteractions?: { question?; permission?; plan? }option (noBackendConfigimport; typechecks independent of SDK version via the existing cast).streamSandboxPrompt— spreadsinteractionsverbatim into the backend, mirroring themodelspread.driveSandboxTurn— left untouched, with a comment documenting the policy (detached cron/mission-step turns have no consumer to answer a question). Box-create payload also untouched.detectInteractiveQuestion— extended to recognize the genericinteraction/kind:"question"event shape (the SDK docstring confirms enabled kinds surface exactly asinteractionstream events), strengthening thedisallowQuestionsbackstop.undefinedby default,driveSandboxTurnnever sets it, and the detector recognizing/ignoring interaction events.Note on the issue's file reference
The issue points at
runSandboxPrompt/:1784as the autonomous path to avoid. In the actual code,runSandboxPromptbuilds no backend (it delegates tostreamSandboxPrompt); the function at:1784isdriveSandboxTurn, the detachedbox.promptpath — that is the one left untouched.Acceptance criteria
StreamSandboxPromptOptions.interactionsexists and reachesbox.streamPrompt's backend config verbatim (test)driveSandboxTurn) never sets it (test)@tangle-network/sandboxbumped to ≥ 0.9.7Verification
pnpm typecheck— cleanpnpm test src/sandbox/index.test.ts— interaction/sandbox tests pass (6 targeted, 110 total)pnpm build— ESM + DTS both succeedTwo pre-existing, environment-specific failures are unrelated to this change: the
deferred profile filessuite shells out to a realbashthat breaks on the local.bash_profile(confirmed failing identically on cleanmain), and the DTS build needsNODE_OPTIONS=--max-old-space-size=8192on this machine due to the existing type-surface size.