feat(wrappers): Phase 2.2 + 2.3 + 2.5 — TypeScript + Python wrappers + cross-language conformance#7
Merged
Conversation
- Add _is_typed_dict(), _discover_typed_dicts(), _write_error_codes_schema() to protocol/_gen.py - Update main() to iterate all TypedDicts across methods, notifications, and capabilities modules and write one *.schema.json per TypedDict - Add error_codes.schema.json enumerating all ErrorCode StrEnum values - Generate schemas/ directory with 30 TypedDict schemas + error_codes.schema.json - Add schemas/__init__.py marker file - Add two new tests: test_gen_emits_schema_for_every_typeddict and test_gen_error_codes_schema_is_string_enum
- Add wrappers/python/pyproject.toml: amplifier-agent-client v0.0.0, hatchling build backend, pytest-asyncio strict mode, ruff 120/py312 - Add wrappers/python/src/amplifier_agent_client/__init__.py: exports only PROTOCOL_VERSION_REQUIRED_BY_WRAPPER = '2026-05-aaa-v0' - Add wrappers/python/tests/test_smoke.py: async smoke test asserting the protocol version constant is importable and correct - Update root pyproject.toml workspace members to include wrappers/python Co-authored-by: Amplifier <amplifier@microsoft.com>
Implement Transport class for both TypeScript and Python wrappers.
Each Transport spawns a child process, exchanges JSON frames as NDJSON
over its stdio, drains stderr to an optional sink, and terminates cleanly.
TypeScript (wrappers/typescript/src/transport.ts):
- spawn() with stdio ['pipe','pipe','pipe'], readline on stdout/stderr
- onFrame(cb): register callbacks for parsed JSON frames
- send(obj): writes JSON.stringify(obj) + '\n' to stdin
- terminate(): sends SIGTERM, awaits child 'close' (exitPromise)
- Non-JSON stdout lines logged to stderr sink and dropped silently
- ExitInfo {code, signal}, TransportOptions {command, args, env, cwd?, stderr?}
Python (wrappers/python/src/amplifier_agent_client/transport.py):
- start(): asyncio.create_subprocess_exec with PIPEs
- frames(): async generator via asyncio.Queue + 0.1s poll timeout;
exits when _stdout_done is set and queue is empty
- send(obj): json.dumps(obj) + '\n' encoded then drain()
- terminate(): proc.terminate(), wait up to 5s, fallback proc.kill()
- _read_stdout: async for loop with try/except; drops non-JSON silently
- _drain_stderr: drains stderr to optional sink
Defensive requirement (MCP-style tolerance): non-JSON stdout lines are
logged to the stderr sink (or process.stderr/sys.stderr) and dropped
silently - never raised. Matches engine pattern at jsonrpc.py.
Tests:
- wrappers/typescript/test/transport.test.ts: 3 vitest cases
- wrappers/python/tests/test_transport.py: 3 pytest-asyncio cases
All 6 tests pass (3/3 TS, 3/3 Py). No regressions.
- TS: JsonRpcClient at wrappers/typescript/src/jsonrpc.ts
- TransportLike interface (send/onFrame), Notification interface, RequestHandler type
- call(): allocates request id, creates Promise, sends {jsonrpc:'2.0',id,method,params}
- dispatch(): routes response→resolve pending, server-request→handler+response, notification→fanout
- Unknown server methods return -32601 error
- NC-L16 designed out: each call() has independent Promise row in pending Map
- Py: JsonRpcClient at wrappers/python/src/amplifier_agent_client/jsonrpc.py
- _TransportLike Protocol (send/on_frame), RequestHandler type alias
- call(): uses asyncio.get_running_loop().create_future() for per-id isolation
- _dispatch(): routes by frame keys with task lifecycle management (RUF006)
- _handle_request: awaits handler, sends result or error back
- Tests: 5 cases each (TS + Py): call resolves, concurrent calls no interference,
notifications fanout, server request dispatched, unknown method -32601 error
Implements Task 7: SessionHandle.submit(prompt) returns
AsyncIterable<DisplayEvent> (TS) / AsyncIterator[DisplayEvent] (Py).
Each submit() call:
- Sends turn/submit JSON-RPC request
- Yields every display/event notification received
- Terminates when result/final notification arrives OR when
turn/submit JSON-RPC response arrives (whichever first)
- Throws AaaError (TS) / RuntimeError (Py) on second call (D10 one-shot)
TypeScript (wrappers/typescript/src/session.ts):
- DisplayEvent interface {type, sessionId, turnId, parentTurnId?,
synthesized?, payload}
- AaaError class extends Error with code/remediation
- TERMINAL_NOTIFICATION = 'result/final'
- SessionDeps {sessionId, terminate}
- SessionHandle with submitted flag
- makeIterable async generator using push-queue + wakeUp pattern
Python (wrappers/python/src/amplifier_agent_client/session.py):
- AaaError exception with code/remediation
- DisplayEvent class with snake_case fields
- SessionHandle with _submitted flag
- _stream async generator with asyncio.Queue sentinel pattern
- submit_task background task with finally sentinel
Tests (both languages):
- (a) yields display events and ends on result/final: drives
2 result/delta notifs + result/final, verifies collected event
types == ['result/delta', 'result/delta', 'result/final']
- (b) second submit() raises typed one-shot error matching
/one-shot|already submitted/i
All 17 TS tests pass. All 15 Python session+unit tests pass.
Implements design §4.6 contract #1: if the engine emits a non-null reply in its turn/submit response but no result/final notification was observed first, the wrapper synthesizes a result/final-shaped DisplayEvent with synthesized: true as the last yielded event. Pure synthesis functions: - TS: synthesizeFinalIfMissing({sawFinal, reply, sessionId, turnId}) → DisplayEvent | null - Py: synthesize_final_if_missing(*, saw_final, reply, session_id, turn_id) → dict | None Both return null/None when sawFinal=true or reply=null/None. Session wiring: - TS session.ts: tracks sawFinal boolean; changes .finally() to .then(onFulfilled, onRejected) to capture reply and call synthesis - Py session.py: uses mutable saw_final_flag={seen: False} shared between on_notif and submit_task closures; synthesis in try block Tests: - Three-case pure function tests for both languages - Integration test (Branch B) driving stub through the synthesis path: engine sends result/delta events and a turn/submit reply but never emits result/final; last event is type=result/final, synthesized=True
- Add wrappers/typescript/src/approval.ts: ApprovalRequest, ApprovalResponse,
ApprovalAdapter types; makeApprovalHandler(adapter) returns (params)=>Promise.
No adapter → {decision:'deny', reason:'no_adapter_configured'}. Adapter race
against setTimeout(timeoutMs) → {decision:'timeout'} on expiry. .catch →
{decision:'deny', reason:'adapter_error'}.
- Add wrappers/python/src/amplifier_agent_client/approval.py: symmetric
make_approval_handler(*, on_request, timeout_ms). on_request=None →
no_adapter_configured. asyncio.wait_for timeout → {decision:'timeout'}.
Exception → {decision:'deny', reason:'adapter_error'}.
- Update wrappers/typescript/src/session.ts: extend RpcLike with optional
onRequest?; SessionHandle constructor takes optional ApprovalAdapter; wires
rpc.onRequest('approval/request', makeApprovalHandler(approval)) if provided.
- Update wrappers/python/src/amplifier_agent_client/session.py: SessionHandle
constructor takes optional approval_on_request + approval_timeout_ms; wires
rpc.on_request('approval/request', make_approval_handler(...)) if provided.
- Add wrappers/typescript/test/approval.test.ts: 3 cases (allow response,
timeout at 50ms, no-adapter deny).
- Add wrappers/python/tests/test_approval.py: same 3 async cases.
All 24 TS + 25 Py tests pass.
- Add display.ts (TS) with SubagentMode, DisplayAdapter interface, and applyDisplayFilter() predicate factory - Add display.py (Py) with apply_display_filter() predicate factory - Wire display adapter into session.ts: apply filter before yielding to iterator AND before invoking display.onEvent push callback - Wire display adapter into session.py: same dual-path filtering - Add tests in display.test.ts covering: (a) subagentEvents='all' keeps all events including parentTurnId ones (b) subagentEvents='none' drops events with parentTurnId (c) default (unset) is 'all' (d) onEvent push callback receives same events as iterator (e) subagentEvents='none' suppresses parentTurnId events from both paths - Add tests in test_display.py with same five cases TS: 29/29 tests pass. Py: 30/30 tests pass.
- Add wrappers/conformance/runner_py.py: Python conformance runner using
amplifier_agent_lib.protocol.conformance.loader and JsonRpcClient.
ScriptedTransport replays server_to_client frames synchronously in
send(). L14 synthesis applied after turn/submit if engine omits
result/final. Emits JSON report {fixture, language, passed, assertions}.
- Add wrappers/conformance/runner_ts.ts: TypeScript port using yaml npm
package for fixture loading and an inline minimal JSON-RPC client.
Same ScriptedTransport pattern and L14 logic. Reports language:typescript.
- Add wrappers/conformance/tests/test_runner_py.py: pytest tests verifying
capability_negotiation.yaml and l14_synthesis.yaml pass (Python).
- Add wrappers/conformance/test/runner-ts.test.ts: vitest tests verifying
capability_negotiation.yaml and l14_synthesis.yaml pass (TypeScript).
- Add pnpm-workspace.yaml at repo root with members wrappers/typescript
and wrappers/conformance.
- Add wrappers/conformance/package.json (private package with yaml^2.4.0
dep, amplifier-agent-client-ts workspace:* dep, vitest devDep).
- Add wrappers/conformance/tsconfig.json and vitest.config.ts.
Assertion kinds supported: notification_emitted, no_notification,
error_returned, response_matches (unknown kinds skipped with ok=True).
source:engine filter on no_notification distinguishes synthesized
events from engine-emitted notifications.
Tests: 2/2 Python, 2/2 TypeScript.
Adds tests/test_conformance_parity.py — parametrised @pytest.mark.integration test that runs both the TypeScript and Python conformance runners on each of the 5 YAML fixtures and asserts they produce identical (kind, passed) tuples. This is the H6 mitigation from design §4.6: prevents the silent failure mode 'TS green / Py green but they are testing different things'. Design: - _REPO_ROOT and _FIXTURE_DIR constants anchor paths relative to repo root - _run_py() invokes `uv run python wrappers/conformance/runner_py.py <f>` - _run_ts() invokes `pnpm exec tsx runner_ts.ts <f>` from wrappers/conformance - On divergence, prints per-assertion diff with Py vs TS column alignment Fix bundled: wrappers/conformance/runner_ts.ts error_returned handler was using String(err) which serialises plain objects as '[object Object]', so code checks against data.code strings always failed. Now uses JSON.stringify(err) for structured errors, matching Python's str(frame['error']) behaviour.
…r_py - Guard against None when checking assertion_id in errors dict (line 202) - Add explicit type annotation for assertion_id in response_matches case (line 216) - Guard against None in responses.get(assertion_id) call (line 218) - Run ruff format to fix formatting across both conformance files This resolves the critical type safety issue where int | None was being passed to dict operations expecting int keys. The fixes ensure that fixtures with missing 'id' fields in assertions won't accidentally pass vacuously as None.
manojp99
pushed a commit
that referenced
this pull request
May 26, 2026
Resolves conflicts from PRs #6 (Phase 2.1 wire spec hardening) and #7 (Phase 2.2/2.3/2.5 wrappers + conformance) landing on main while this branch carried the Mode A pivot work that supersedes Mode B. Resolution strategy: - Wrapper source + tests (TypeScript + Python): take OURS. Mode A subprocess-driver implementation supersedes Mode B JSON-RPC version per the 2026-05-24 Mode A pivot amendment (CR-C breaking change). - Mode B-only modules brought in by main (display.{py,ts}, jsonrpc.{py,ts}, l14.{py,ts}, and their tests): DELETED. Mode A pivot eliminated these capabilities; deferred to v1.x per amendment §6. - Conformance fixtures (5 OLD Mode B JSON-RPC fixtures), conformance loader, runner_py.py, runner-ts.test.ts, freshness guard tests: take OURS. Debug cycle 3 fixed runner_py.py import + added freshness guard. - Protocol schemas, spec.md, version_info.py, Phase 2.1 tests: take OURS. Our branch is on top of main's Phase 2.1 baseline and has additional Mode A pivot-related touches. Regression check: 453 passed, 3 skipped, 0 failed (uv run pytest tests/ -q). Same green state as pre-merge HEAD (7bd3a80). Co-Authored-By: Amplifier <amplifier@microsoft.com>
manojp99
added a commit
that referenced
this pull request
May 28, 2026
… wrappers (#21) * docs(readme): align with Mode A pivot, protocol 0.2.0, and shipped L3 wrappers This README was significantly out of date with recent changes: - PR #8 (Mode A pivot): Replaced stdio JSON-RPC wire protocol with subprocess driver model (argv in / JSON envelope out). README still documented Mode B (--stdio flag, agent/initialize JSON-RPC methods) as primary interface. - PR #17: Bumped protocol version 0.1.0 → 0.2.0, changed --mcp-servers (inline JSON) to --mcp-config-path (file path) for MCP config delivery. Neither reflected in README. - PR #20: Changed OpenAI default from gpt-4 to gpt-5.5 (matches extended_thinking=true in bundle). README used outdated default. - PR #7: TypeScript wrapper shipped at 0.4.0 on npm, Python wrapper shipped in wrappers/python/. README claimed L3 wrappers were 'designed, implementation next'. Applied 10 targeted corrections across README: 1. 'What it is' section — removed Mode B bullet, fixed lifecycle 2. 'Why' section — removed JSON-RPC-mirrors-MCP claim 3. 'Quick start' — removed --stdio example, pointed at wrapper SDKs 4. 'Modes' table — collapsed to Mode A only, added historical design-doc note 5. 'Admin commands' — added missing 'prepare' and 'verify' commands 6. 'Approval flow' — removed unimplemented 'c' response, removed stdio paragraph 7. 'Embedding in Python' — replaced broken classmethod example with correct constructor and instance method usage 8. 'Architecture diagram' — updated arrow label to reflect Mode A transport 9. 'Wire protocol' section — replaced Mode B docs with Mode A (0.2.0 argv flags, JSON envelope schema, stdout/stderr split) 10. 'Status' — updated from 'Phase 1, L3 next' to shipped state (0.3.0 engine, 0.4.0 TS wrapper, Python wrapper, 0.2.0 protocol, conformance suite, path-based MCP) with next steps (L2 host adapters, container packaging). Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * docs(readme): add TypeScript SDK section and fix git+URL install command - Added "TypeScript / Node.js SDK" section documenting the shipped npm package (amplifier-agent-ts@0.4.0) with install command, quick-start example, Node ≥ 20 requirement, and pointers to full README and type definitions. - Fixed broken Python install commands: replaced bare PyPI references (uv tool install / pipx install) with git+URL form pinned to engine-v0.3.0. Package is not yet published to PyPI; this fixes the broken install path demonstrated by user feedback. Added explanatory note about separate engine vs wrapper version tagging (engine-v0.3.0 vs wrapper-v0.4.0). Both gaps were identified during PR #21 review: TypeScript SDK was effectively undocumented for consumers despite npm publication, and Python install was impossible without the git+URL form. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * docs(readme): replace registry install with from-source instructions The Python engine hasn't been published to any registry. Removed all registry-style install commands (uv tool install amplifier-agent, pipx install amplifier-agent, and git+URL forms with PEP 508 specs) as they were misleading. From-source install via git clone + uv tool install -e . is now the only documented path. Added note that engine and wrapper releases are tagged separately (engine-v0.3.0 vs wrapper-v0.4.0) so users can git checkout <tag> before installing if they want a pinned version. This does not affect the TypeScript section — amplifier-agent-ts is published to npm and npm install amplifier-agent-ts is correct. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * docs(readme): use uv tool install with bare git+URL (no registry needed) uv tool install works fine with a raw git+https://... argument; no PEP 508 name @ url wrapper, no registry, no separate clone+sync step. Pinning supported via @<tag>. From-source editable install retained as a contributor note. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --------- Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com> Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
manojp99
pushed a commit
that referenced
this pull request
Jun 3, 2026
…igest (#7) Closes the Task-9 TODO: getEngineInfo() now returns the values captured during the engine version probe that spawnAgent() runs at init (Issue #9). Previously both fields were hardcoded empty strings. - engineVersion populated from `amplifier-agent version --json` payload's `version` field. - bundleDigest populated from the probe payload's optional `bundleDigest` field. The engine's current `version --json` output (from admin/version_info.py) only emits {version, protocolVersion} — bundleDigest will be empty string until a future engine release exposes it. Forward-compatible: when the engine adds it, the wrapper picks it up automatically with no further changes. DONE_WITH_CONCERNS for the bundleDigest follow-up: filed as an engine-side ask for a future PR. The wrapper does what it can with the data the engine surface exposes today; the contract is wired so the field will populate the moment the engine emits it. Closes #7.
manojp99
pushed a commit
that referenced
this pull request
Jun 3, 2026
Wrapper hardening release closing 8 consumer-reported gaps at 0.5.0: #1 configPath surface #2 stderr NDJSON parsing #3 runChildProcess injection #4 display.onEvent dispatch #5 public re-exports #6 Transport dead code (root cause of #2/#4) #7 getEngineInfo() implementation #9 checkProtocolVersion() wired into init path #10 approval API mapped to engine -y/-n + approval.mode Issue #8 in the consumer report was a misread — InitializeParams. mcpConfigPath is intentionally retained in protocol-0.3.0. No type change needed; the schema is canonical and correct. This is a minor bump per 0.x convention even though some changes are BREAKING — the wrapper hasn't shipped a 1.0 yet, so breaking changes ride minor bumps. See CHANGELOG for the BREAKING list. Engine compatibility: requires amplifier-agent >= 0.4.0. Pinned protocol: 0.3.0.
manojp99
added a commit
that referenced
this pull request
Jun 3, 2026
…, approval, getEngineInfo, +5 more) (#36) * feat(wrapper-ts): re-export internal helpers from index.ts (#5) Adds named re-exports from the package entry point so consumers can import internal helpers without reaching into private deep paths: assembleArgv, AssembleArgvInput resolveMcpConfigPath, cleanupSpillFile, McpSpillResult buildEnv, resolveBinaryPath, probeEngineVersion, DEFAULT_ALLOWLIST, BLOCKED_ENV_KEYS, ResolveBinaryPathOptions, BuildEnvOptions Transport, TransportOptions, ExitInfo checkProtocolVersion, VersionCheckResult, VersionCheckOk, VersionCheckFail, CheckProtocolVersionOptions parseRunOutput, STDERR_TAIL_BYTES, SubprocessOutcome makeApprovalHandler, ApprovalAdapter, ApprovalRequest, ApprovalHandler Each export is annotated @public. Closes #5. * feat(wrapper-ts): wire checkProtocolVersion() into init path (#9) spawnAgent() now probes the engine's protocol version once during initialization (via amplifier-agent version --json) and runs checkProtocolVersion() against PROTOCOL_VERSION_REQUIRED_BY_WRAPPER BEFORE constructing a SessionHandle. Mismatch fails fast wrapper-side with AaaError(protocol_version_mismatch), saving a full subprocess roundtrip later. Adds two new SpawnAgentParams fields: - allowProtocolSkew?: boolean — bypass the check (mirrors engine's host_config.allowProtocolSkew) - _engineVersionProbe?: () => Promise<EngineVersionPayload> — test-only injection point for the probe Also bumps PROTOCOL_VERSION_REQUIRED_BY_WRAPPER from "0.2.0" to "0.3.0" to match the engine's current wire protocol (amplifier_agent_lib.protocol.methods.PROTOCOL_VERSION). The wrapper was shipping with a stale pin; the new check would have surfaced this at startup. Closes #9. * feat(wrapper-ts): add runChildProcess injection point (#3) Adds SpawnAgentParams.runChildProcess?: ChildProcessFactory — a public seam to substitute the subprocess factory used inside SessionHandle. When set, the wrapper invokes the factory in place of child_process.spawn, preserving the same options shape (detached, stdio, env, optional cwd). Useful for: - Sandboxing (e.g. wrapping the child in a container or namespace) - Test doubles (e.g. EventEmitter fakes that drive scripted outputs) - Harness wrapping (e.g. observing the subprocess from outside) ChildProcessFactory is exported as a @public type from index.ts. Closes #3. * feat(wrapper-ts)!: wire Transport NDJSON pipeline + dispatch to display.onEvent (#2, #4, #6) The engine emits one JSON object per line on the child subprocess's stderr stream for each wire-protocol notification (progress, result/delta, result/final, thinking/delta, thinking/final, tool/started, tool/completed, approval/request, approval/timeout, plus wire-level error). Before this change the wrapper buffered stderr as raw text and silently dropped every event — the existing Transport class implemented NDJSON parsing but was never wired anywhere (dead code). This change: - Adds parseNdjsonStream(stream, {onJson, onNonJson?}) — a standalone helper extracted from the parsing logic Transport already had. Resolves when the stream emits 'close'. Exported @public. - Wires parseNdjsonStream onto child.stderr inside SessionHandle.makeIterable(). JSON lines are parsed into 'notification' DisplayEvents and dispatched to params.display?.onEvent. Non-JSON lines (and JSON lines, for completeness) are still accumulated into stderrBuf so the stderrTail surface on parseRunOutput remains diagnostically useful. - Extends the DisplayEvent discriminated union with a new {type: 'notification', method: string, params: unknown} variant. **BREAKING**: existing exhaustive switch statements on event.type will no longer be exhaustive without a notification branch. - Threads SpawnAgentParams.display through to SessionHandle so the callback that was previously silently dropped is now actually fired (Issue #4). Closes #2, #4, #6. BREAKING CHANGE: display.onEvent callbacks are now actually invoked with wire-event notifications. Callers that registered onEvent expecting it to be a no-op may observe new event flow. The DisplayEvent union has a new 'notification' variant; exhaustive switch statements need a corresponding branch. * feat(wrapper-ts): surface --config flag via SpawnAgentParams.configPath (#1) Engine PR #27 / v0.4.0 added the --config <path> flag and the host_config layer (approval mode, MCP servers, provider defaults, allowProtocolSkew, etc.). The wrapper had no surface to forward this, so callers had to fall back to AMPLIFIER_AGENT_CONFIG in env.extra. This change: - Adds SpawnAgentParams.configPath?: string (public, @public TSDoc). - Adds AssembleArgvInput.configPath?: string. - assembleArgv emits --config <path> when configPath is set. - Threads configPath through SessionHandleParams to the per-submit argv assembly. Also drive-by adds approvalMode field to AssembleArgvInput (used by #10's commit). The argv-builder now reads input.approvalMode and emits -y / -n / nothing accordingly. Default remains -y for backward compat with callers that haven't opted into the approval API. Closes #1. * feat(wrapper-ts)!: wire approval API to engine -y/-n + approval.mode (#10) Previously, SpawnAgentParams.approval threw AaaError( approval_not_supported_in_v1) whenever set because it required the mid-turn onRequest callback that v1 doesn't support. This change extends SpawnAgentParams.approval to also accept the static-policy shape { mode: 'yes' | 'no' | 'prompt' }, which maps to engine argv: - 'yes' -> -y (auto-allow every tool call) - 'no' -> -n (auto-deny every tool call) - 'prompt' -> emit no flag; engine falls back to host_config.approval.mode or the bundle's TTY-based default. This is how a host hands policy resolution back to the engine. The legacy { onRequest, timeoutMs } form still throws approval_not_supported_in_v1 — the Mode A wire has no mid-turn channel. Mid-turn callbacks will return when WG-4 lands. Engine compatibility: { mode: 'prompt' } requires amplifier-agent >= 0.4.0 (PR #34 added host_config.approval.mode). Closes #10. BREAKING CHANGE: SpawnAgentParams.approval is now a union shape; callers passing { mode } no longer hit approval_not_supported_in_v1. Callers that defensively catch that error need to remove the try/catch when migrating to the mode shape. * feat(wrapper-ts): implement getEngineInfo() — engineVersion + bundleDigest (#7) Closes the Task-9 TODO: getEngineInfo() now returns the values captured during the engine version probe that spawnAgent() runs at init (Issue #9). Previously both fields were hardcoded empty strings. - engineVersion populated from `amplifier-agent version --json` payload's `version` field. - bundleDigest populated from the probe payload's optional `bundleDigest` field. The engine's current `version --json` output (from admin/version_info.py) only emits {version, protocolVersion} — bundleDigest will be empty string until a future engine release exposes it. Forward-compatible: when the engine adds it, the wrapper picks it up automatically with no further changes. DONE_WITH_CONCERNS for the bundleDigest follow-up: filed as an engine-side ask for a future PR. The wrapper does what it can with the data the engine surface exposes today; the contract is wired so the field will populate the moment the engine emits it. Closes #7. * chore(wrapper-ts): rebuild dist after hardening release changes Mirrors PR #29 / #31 pattern: dist/ is tracked so consumers installing from the git tarball get the compiled artifacts without a build step. Regenerated from npm run build after issues #1, #2, #3, #4, #5, #6, #7, #9, #10 landed. * chore(release): bump amplifier-agent-ts to 0.6.0 + CHANGELOG Wrapper hardening release closing 8 consumer-reported gaps at 0.5.0: #1 configPath surface #2 stderr NDJSON parsing #3 runChildProcess injection #4 display.onEvent dispatch #5 public re-exports #6 Transport dead code (root cause of #2/#4) #7 getEngineInfo() implementation #9 checkProtocolVersion() wired into init path #10 approval API mapped to engine -y/-n + approval.mode Issue #8 in the consumer report was a misread — InitializeParams. mcpConfigPath is intentionally retained in protocol-0.3.0. No type change needed; the schema is canonical and correct. This is a minor bump per 0.x convention even though some changes are BREAKING — the wrapper hasn't shipped a 1.0 yet, so breaking changes ride minor bumps. See CHANGELOG for the BREAKING list. Engine compatibility: requires amplifier-agent >= 0.4.0. Pinned protocol: 0.3.0. --------- Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
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
Phases 2.2 + 2.3 + 2.5 of the AaA v2 design (
docs/designs/2026-05-20-aaa-v2-wrapper-and-wire.md). Ships both language wrappers, the shared conformance harness, and the cross-language parity lint that consumes the YAML wire-sequence fixtures from PR #6.This is the bottom layer of the §4 locked architecture — what host adapters (Phase 2.4 NanoClaw, Phase 2.5 Paperclip) will consume to talk to
amplifier-agentwithout knowing about JSON-RPC, L14 synthesis, version skew, or subprocess plumbing.Stacked on PR #6. This branch was developed off
feat/phase-2-1-wire-spec-hardeningwhile PR #6 was open. After PR #6 merges, this branch must be rebased onto newmain(no logical conflicts expected — the file sets are orthogonal).What ships
TypeScript wrapper (
wrappers/typescript/)amplifier-agent-client-tsspawn,transport(NDJSON),jsonrpc(per-id),session,approval,display,version,l14,info,types(generated fromprotocol/schemas/*.schema.json)Python wrapper (
wrappers/python/)amplifier-agent-client-py(separate distributable fromamplifier_agent_lib)types.pyre-exports fromamplifier_agent_lib.protocol(single source of truth)Cross-language conformance (
wrappers/conformance/)runner_ts.tsandrunner_py.py— scripted-replay drivers that load YAML fixtures fromsrc/amplifier_agent_lib/protocol/conformance/fixtures/, drive their respective wrapper against a real engine subprocess, and capture wire framesparity_lint.py— asserts both runners produce equivalent assertion outcomes against all 5 fixtures--resumecontinuityEngine-side addition
amplifier-agent version --jsonsubcommand atsrc/amplifier_agent_cli/admin/version_info.pyPROTOCOL_VERSIONbefore spawning a turnPublic API (verbatim from design §8)
Python wrapper mirrors with snake_case.
Per-task ceremony (carried over from Plan 2)
Test Plan
uv run python -m pytest tests/ wrappers/python/tests/ -q)pnpm test --run)All checks passed!acrosssrc/,tests/,wrappers/0 errors, 0 warnings, 0 informationsversion_skew.yamlstrict-refuse branch only; override branch still pending per PR feat(protocol): Phase 2.1 — wire spec hardening (generator, schemas, conformance fixtures) #6 follow-up)AMPLIFIER_AGENT_E2Eenv var (intentional skip without it)Note: Phase 2.1's 4 environmental test failures (
test_delegation_e2e,test_phase_2_0c_exit_gate_real_turn_emits_result_events,test_resume_continuity_two_turns_share_context) now pass under this branch. The infra changes (pnpm workspace, new admin command, etc.) appear to have resolved the cache-state drift.Known follow-ups (NOT blockers)
version_skew.yamloverride branch still missing (Plan 2 follow-up; tracked).node_modules/is currently untracked — add to.gitignoreif not already excluded.Out of scope (next plans)
References
docs/designs/2026-05-20-aaa-v2-wrapper-and-wire.md(especially §4.2, §4.3, §5, §8 D1–D10, §10.2 steps 3-5)docs/plans/2026-05-20-phase-2-2-2-3-2-5-wrappers-and-conformance.mdGenerated with Amplifier