Feat/mode a phase b wrapper#9
Closed
manojp99 wants to merge 93 commits into
Closed
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.
Per design §4.10.3, bump PROTOCOL_VERSION from '2026-05-aaa-v0' to '0.1.0' across all three required locations: - src/amplifier_agent_lib/protocol/methods.py - wrappers/typescript/src/index.ts (PROTOCOL_VERSION_REQUIRED_BY_WRAPPER) - wrappers/python/src/amplifier_agent_client/__init__.py (PROTOCOL_VERSION_REQUIRED_BY_WRAPPER) Regenerated spec.md via amplifier_agent_lib.protocol._gen (schemas/ files contain no version literal; types.ts regenerated for completeness — no diff). Updated wrapper test fixtures that hardcoded the old version string so pnpm test / pnpm typecheck and wrappers/python/tests/* continue to pass. Updated tests/test_protocol_gen.py to assert '0.1.0' in spec.md. Added tests/test_protocol_version_bump.py with test_protocol_version_is_0_1_0 asserting PROTOCOL_VERSION == '0.1.0' (RED → GREEN verified).
…cpServers/.host (A1) Add three new TypedDicts to the protocol surface (design §4.10.1): - McpServerConfig: per-server MCP configuration with required transport field - HostCapabilities: total=False host capability advertisement - InitializeHostParams: total=False envelope wrapping host capabilities Extend InitializeParams with two NotRequired fields: - mcpServers: dict[str, McpServerConfig] - host: InitializeHostParams Regenerate spec.md and schemas/. Wire the new params through both wrappers: - TypeScript: SpawnAgentParams gains mcpServers/host; passed to agent/initialize - Python: spawn_agent() gains mcp_servers/host kwargs; merged into init payload Update test_protocol_gen.py expected schema set to include the three new schema files. Add tests/test_wire_types_v01.py with four tests verifying the new TypedDicts and InitializeParams field extensions. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…d exit codes Add _EXIT_CODE_BY_CLASSIFICATION mapping (engine/transport/unknown→1, protocol→2, approval→3) and _CLASSIFICATION_BY_CODE table mapping known AaaError codes onto classifications. Rewire the except blocks in single_turn.run() to build §4.3 error envelopes and emit them on the real stdout (bypassing the redirect_stdout guard), exiting with the classification-mapped exit code. Also relax AaaError.__init__ to accept code and message positionally (kwargs still work) — required by callers in the wrapper layer (amplifier_agent_client/__init__.py) and by the §4.3 envelope test. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…secrets 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Launches the actual amplifier-agent binary via subprocess.run with ANTHROPIC_BASE_URL pointed at an in-process HTTP mock LLM, and asserts the stdout envelope matches §4.1. Per amendment §8.1 A4', the mock LLM HTTP server is the only mock allowed in real-binary tests. The mock returns a complete Anthropic SSE message stream (message_start through message_stop) because the amplifier-module-provider-anthropic provider uses streaming by default (use_streaming=True). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…CP group cleanup 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
RED step for A3'/CR-C (2026-05-24 Mode A pivot amendment, §5.2). Pins the simplified Mode A DisplayEvent shape as a discriminated union with four variants: init (sessionId), activity (no fields), result (text), error (code, classification, severity, correlationId, message, retryable, optional stderrTail). Replaces the locked-design flat interface that required type/sessionId/turnId/payload on every event. Verifies (i)–(iv) per the spec via @ts-expect-error directives anchored to fields that must NOT exist on each variant, structural Object.keys assertions, and an exhaustive switch with never-narrowing for the union. State at this commit: - tsc --noEmit on this test file fails with the exact errors predicted by §5.2 (missing turnId/payload on init/activity/result/error literals; unknown code/classification/severity/correlationId/message/retryable on error literal; unused @ts-expect-error directives because turnId and payload still exist on the locked-design interface). - npm test -- session-mode-a-shape passes at runtime because vitest's esbuild transform strips all TS-only constructs (the @ts-expect-error directives become comments and the type annotations become bare object literals). This is a known vitest+esbuild behavior; type-level RED is verifiable via tsc directly. GREEN step (next task) rewrites src/session.ts to the new union and the type errors clear.
Replace the flat DisplayEvent interface with a discriminated union
({ init | activity | result | error }) per the Mode A pivot amendment
§5.2. Removes fields the Mode A wire cannot meaningfully populate:
turnId, parentTurnId, synthesized, payload.
The breaking-change marker (!) reflects that consumers of
SessionHandle.submit()'s yielded events must now narrow via ev.type
and read per-variant typed fields (text, code, classification, …)
instead of the previous turnId / payload bag.
Existing SessionHandle.submit() emitter code (l14.ts, display.ts,
session.ts:217/225) type-errors against the new shape — this is
expected and is rewritten in Task 7/8 once the subprocess driver
lands. A TODO(phase-b-task-8) marker has been added above the
L14 import so the cleanup is recoverable from grep.
The Task 2 RED test (session-mode-a-shape.test.ts, 5 cases) is now
GREEN — DisplayEvent matches the simplified union exactly.
Refs: docs/designs/2026-05-24-aaa-v2-mode-a-pivot-amendment.md §5.2
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…aError
Per amendment §5.3, the Mode A wire has no mid-turn request channel.
The earlier draft of the amendment had spawnAgent() accept the callback
and log a stderr warning; SC-C adversarial review found that warning-only
acceptance ships silent auto-allow to a host author who believed their
callback was wired up. Reject loudly instead.
- Add first guard in spawnAgent() (src/index.ts): if params.approval.onRequest
is provided, throw AaaError('approval_not_supported_in_v1') with
classification='protocol', severity='error', BEFORE any subprocess work.
- Add verbatim JSDoc from amendment §5.3 above the SpawnAgentParams.approval
field documenting the NOT-SUPPORTED-IN-v1 status and the v1.x revival path
(WG-4 in amendment §6).
- New unit test test/spawn-rejects-approval.test.ts verifies the rejection
with toMatchObject({name:'AaaError', code:'approval_not_supported_in_v1',
classification:'protocol'}).
The throw happens before any subprocess work; the host author sees the
failure immediately at spawnAgent() call time instead of at some downstream
point where a tool was supposed to prompt but silently auto-allowed.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add wrappers/typescript/src/argv-builder.ts exporting assembleArgv() and
AssembleArgvInput. Pure function — no I/O, no env reads — building the
argv array for 'amplifier-agent run' in the canonical order:
run --session-id <sid> {--fresh|--resume}
[--cwd] [--provider] [--mcp-servers] [--host-capabilities]
[--env-allowlist] [--env-extra]
--output json --protocol-version <ver>
[--allow-protocol-skew] -y <prompt>
SC-C: wrapper always emits -y to enforce auto-allow at the bundle layer;
approval responsibility lives in the orchestrating host, not the engine.
Tests cover the 5 spec cases: happy-path argv equality, resume mode,
host-capabilities JSON threading, mcp-servers inline JSON threading, and
mcp-servers @path threading (caller pre-spilled).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add mcp-spill.ts implementing CR-A's secret-aware MCP servers resolution
for the Mode A v2 wrapper:
- resolveMcpServersFlag(mcpServers, sessionId): returns inline JSON when no
server has a non-empty env block; otherwise spills the full config to a
0600 tmpfile under $XDG_RUNTIME_DIR/amplifier-agent/<sessionId>/ (falling
back to os.tmpdir()) under a 0700 per-session directory and returns
`@<path>` as the flag value.
- cleanupSpillFile(spillPath): idempotent unlink that swallows ENOENT and
is a no-op for null input, so callers can call it unconditionally on
every exit path.
Empty env objects (`env: {}`) do NOT trigger spilling — only env blocks
with at least one key are considered secret-bearing.
Tests: 7 cases covering null/undefined/empty input, inline JSON when no
env, 0600 spill when any env present, mode/contents verification, and
idempotent cleanup (including null and double-unlink).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Implements parseRunOutput() per §4.1 (run-output envelope schema) and §4.4 (SC-D exit-code/envelope precedence) from the Mode A v2 pivot amendment. Rule 1 — envelope parseable per §4.1 → envelope is authoritative: - error===null → result event with text=reply (regardless of exit code) - error present → error event populated from envelope (retryable=false) Rule 2 — envelope absent / unparseable / partial → synthesize from outcome: - exit 0 → envelope_missing / protocol (engine protocol violation) - exit N≠0 → engine_exit_<N> / engine, stderrTail truncated to 4096 chars Partial JSON is NOT half-parsed; missing required §4.1 fields fall to rule 2. Tests: 6 SC-D cases (1a/1b/1c/2a/2b/2c) + stderrTail truncation. All pass. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Rewrite SessionHandle per the 2026-05-24 Mode A pivot amendment §5.2: the
handle is now a per-submit subprocess driver, not a JSON-RPC client.
Per §5.2 contract:
- SC-1: yield {type:'init', sessionId} synchronously before any async work
- CR-A: resolve --mcp-servers via resolveMcpServersFlag (inline or 0600 spill)
- assembleArgv builds the engine argv (no I/O in the builder)
- SC-B: spawn detached:true so PID == PGID for group signals
- 2s activity ticker pushed into the iterator queue while child runs
- exit promise races a configurable timeout (default 10min)
- on exit: parseRunOutput({stdout, stderr, exitCode})
- on timeout: synthesize {type:'error', code:'engine_hung'} + cancel()
- on spawn error (ENOENT/EACCES): {type:'error', code:'spawn_failed'}
- cleanup spill file after the drain loop ends (every exit path)
- cancel(): SIGTERM the -pgid, wait 5s, SIGKILL the -pgid if alive;
then unlink the spill file (idempotent)
- dispose() is an alias for cancel()
spawnAgent (index.ts) is synchronous-in-spirit: validate params, resolve
binary, build env, return new SessionHandle(params). No subprocess at
spawn time. No probeEngineVersion call. The version.ts / checkProtocolVersion
path is unused — flagged TODO for Task-9 cleanup.
Deleted:
- src/l14.ts, src/jsonrpc.ts (Mode B JSON-RPC artifacts — no longer used)
- src/display.ts (display.onEvent consumption path removed in §2.2)
- test/l14.test.ts, test/jsonrpc.test.ts (covered the deleted modules)
- test/display.test.ts (asserted on the removed parentTurnId field)
- test/session.test.ts (asserted on result/delta JSON-RPC notifications)
- test/spawn-agent.test.ts (asserted on the removed _versionProbe seam)
Added:
- test/session-subprocess.test.ts: 9 tests covering the §5.2 contract —
init-before-spawn, one-shot lifecycle, envelope→result, non-zero-exit→
engine_exit_<n>, timeout→engine_hung, MCP spill cleanup, getEngineInfo,
spawn ENOENT→spawn_failed, cancel-before-submit no-op.
BREAKING CHANGE: SessionHandle constructor signature changed from
(rpc, deps, approval?, display?) to (params: SessionHandleParams).
SessionDeps / RpcLike / TERMINAL_NOTIFICATION are removed from the
public surface. spawnAgent's _transportFactory / _versionProbe
test seams are gone — Mode A has no transport to inject.
Verification:
- npx tsc --noEmit: clean
- npm test: 61/61 PASS across 12 files
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… driver
Mirrors Tasks 3-8 (TS) onto wrappers/python/src/amplifier_agent_client/:
- types.py: add DisplayEvent Literal-discriminated TypedDict union
(DisplayEventInit | DisplayEventActivity | DisplayEventResult | DisplayEventError)
per amendment §5.2 / CR-C
- spawn.py: AaaError raised on env_injection_rejected (unchanged contract)
- argv_builder.py: NEW — pure argv assembly mirroring TS argv-builder.ts
- mcp_spill.py: NEW — secret-aware spill via asyncio.to_thread for blocking
file I/O (0700 dir, 0600 file). Mirrors TS mcp-spill.ts (CR-A)
- run_output_parser.py: NEW — §4.1 envelope parser with SC-D precedence rules
- session.py: REWRITE — subprocess driver using asyncio.create_subprocess_exec
with start_new_session=True (POSIX setsid). cancel() uses
os.killpg(os.getpgid(proc.pid), SIGTERM) for group signal, with 5s grace
before SIGKILL. submit() yields DisplayEvents per amendment §5.2.
- __init__.py: REWRITE — spawn_agent() now:
- rejects approval.on_request LOUDLY with AaaError(approval_not_supported_in_v1)
(SC-C — no warning-only acceptance)
- skips agent/initialize, version probe, and Transport (Mode A v2 pivot)
- returns SessionHandle without spawning subprocess (deferred to submit())
Tests mirror TS:
- test_display_event_shape.py — discriminated-union exhaustiveness (CR-C)
- test_spawn_rejects_approval.py — SC-C loud rejection
- test_argv_builder.py — canonical argv assembly, 5 cases
- test_mcp_spill.py — spill behavior, 0600 mode, idempotent cleanup
- test_run_output_parser.py — Rule 1/Rule 2 precedence, stderr tail truncation
Deleted (per task spec + cleanup of orphans):
- l14.py, jsonrpc.py and their tests (explicit)
- display.py (orphan; DisplayEvent shape no longer has parent_turn_id)
- test_session.py, test_spawn_agent.py, test_display.py (test removed flows)
Verification: cd wrappers/python && uv run pytest && uv run ruff check
src/ tests/ && uv run pyright src/ — all green (48 passed, ruff clean,
pyright clean).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Bump TypeScript wrapper from 0.2.0 to 0.3.0 and Python wrapper to 0.3.0 to mark the Mode A pivot wrapper rewrites (commits d2a3c86, df1c263) as a breaking-change release. The DisplayEvent type was simplified (CR-C), SessionHandle was rewritten as a subprocess driver, and the JSON-RPC transport layer was removed. Notes: - Python wrapper version was at 0.0.0 (pre-existing version skew with TS wrapper at 0.2.0). Bumped to 0.3.0 to align with TS. - The pre-pivot conformance parity lint (tests/test_conformance_parity.py) is now obsolete — its runners (runner_py.py, runner_ts.ts) import the deleted amplifier_agent_client.jsonrpc/ScriptedTransport machinery. Per the design (§A4'/CR-D), this lint and its fixtures will be replaced by the real-binary fixture suite in Tasks 11–20 of the Phase B plan. Not addressed in this task. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Author the SC-C real-binary conformance fixture per amendment §8.1 A4'.
The fixture asserts two independent properties:
(i) spawnAgent throws AaaError(approval_not_supported_in_v1) with
classification='protocol' SYNCHRONOUSLY when a non-null
approval.onRequest callback is supplied.
(ii) No 'amplifier-agent run' subprocess is started — verified by a
before/after pgrep snapshot whose counts must match exactly.
Together, these close the SC-C threat surface: the wrapper cannot silently
swallow an approval callback (because it throws) AND cannot do any
subprocess work under the rejected configuration (because pgrep proves no
engine launched).
The corresponding wrapper unit tests already exist
(wrappers/typescript/test/spawn-rejects-approval.test.ts and
wrappers/python/tests/test_spawn_rejects_approval.py from commits
f2ab685 and df1c263); this fixture adds the cross-language conformance
gate and the no-subprocess (pgrep) assertion that the unit tests do not
cover.
Schema note: this fixture uses the v0.3 real-binary fixture schema
(test_type: real-binary, invocation:, pre_steps/post_steps with capture).
The runner_ts.ts / runner_py.py support for this schema is being
introduced in parallel via the sibling Phase B real-binary runner
upgrade — running the runners against this fixture today errors at
schema validation, which is expected pending that upgrade.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Author the SC-B real-binary conformance fixture per amendment §5.2 / §8.1
A4'. The fixture validates that cancel() reaps the engine's *process group*
(not just the engine PID) when MCP children ignore SIGTERM.
Two artifacts:
1. wrappers/conformance/fixtures/slow-mcp/server.py — a stdio MCP child
that installs a no-op SIGTERM handler and sleeps 120s. Forces the
wrapper's cancel() to escalate to SIGKILL on the group; a graceful
SIGTERM can never reap this process.
2. wrappers/conformance/fixtures/mode-a-orphan-cleanup.yaml — the
real-binary fixture. Wrapper spawns the engine with slow-mcp
configured as an MCP server, begins a turn, and the harness invokes
cancel() mid-turn via the interrupt_after_ms hook (1500ms after
submit, long enough for the engine to fork the child).
Assertions:
(i) The engine PID itself is dead after cancel() resolves
(capture_equals engine_state_after = dead).
(i.b) Every MCP child captured before cancel() is dead after
(capture_excludes_substring children_state_after, ':alive').
Forbids the case where the wrapper signalled only the engine PID
and orphaned the MCP children to init/launchd.
(i.c) The pre-cancel snapshot is non-empty (capture_non_empty), so a
test that trivially passes with zero spawned children fails
loudly.
(ii) No captured child remains in 'Z' state per ps -o stat=
(capture_excludes_substring children_ps_stat_after, 'Z').
Catches the wrapper-killed-but-did-not-wait() failure mode.
Together these close SC-B's threat surface: malicious or buggy MCP
children cannot survive cancel(), and the wrapper does not leak zombie
process-table slots.
Schema note: this fixture uses the v0.3 real-binary schema (test_type:
real-binary, invocation:, hooks: with interrupt_after_ms,
capture-based assertions). Running runner_ts.ts / runner_py.py against
it today errors at schema validation — they only support the
scripted-replay schema. The real-binary driver upgrade is being
introduced in parallel; the SC-C precedent (commit a68064d) committed
under the same condition.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Forward-looking real-binary fixture for the §4.4 SC-D envelope-precedence
rule: when the engine has flushed a valid §4.1 envelope, the wrapper MUST
yield based on the envelope (result/error) regardless of the engine's exit
code. Exit codes become load-bearing ONLY in the no-envelope path.
Fixture coaxes the engine into the adversarial state (clean envelope + exit
1) by loading force-exit-bundle/, whose on_turn_end hook calls os._exit(1)
after the engine's stdout-discipline contract (CR-B) has flushed the
envelope. The hook uses os._exit (not sys.exit) to skip atexit/finalizers,
modeling the production failure mode SC-D guards against (post-flush
native-extension crash, segfault during teardown, etc.).
Assertions:
(i) Pre-conditions: engine_exit_code == 1, envelope.error is null.
(ii) Load-bearing: wrapper yields a result event whose text matches the
envelope's reply; does NOT yield an error event.
(iii) Informational: wrapper logs the exit/envelope mismatch to
debug-stderr (substring match, not load-bearing — present for
post-mortem observability, not protocol enforcement).
Schema follows the SC-B (mode-a-orphan-cleanup) and SC-C
(mode-a-approval-callback-rejected) real-binary fixtures already in the
suite. Runners will branch on test_type: real-binary in the Phase B
Acceptance Gate; current scripted-replay runners will skip these fixtures.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Root cause of all 10 conformance test failures (9 parity + 1 exit gate). Phase B (feat/mode-a-phase-b-wrapper, commits 1497ff9–c5ea7a3) rewrote amplifier_agent_client as a Mode A subprocess driver, deleting the old amplifier_agent_client.jsonrpc module in the process. runner_py.py was not updated and continued importing: from amplifier_agent_client.jsonrpc import JsonRpcClient This caused a ModuleNotFoundError on every invocation. The runner exited 1 with an empty stdout; json.loads('') raised JSONDecodeError in the parity test; the exit gate test re-ran the parity test and cascaded. Fix: inline JsonRpcClient directly in runner_py.py, mirroring the TypeScript runner (runner_ts.ts) which has always had its own minimal JsonRpcClient inlined. The inlined implementation is semantically equivalent to the deleted module — it routes frames from the synchronous ScriptedTransport to asyncio Futures, which are resolved before the awaiting coroutine resumes. After this fix: uv run pytest tests/test_conformance_parity.py -q → 9 passed uv run pytest tests/test_phase_2_2_2_3_2_5_exit_gate.py -q → green uv run pytest tests/ -q → 453 passed, 3 skipped Note on the prescribed fix (STOP condition hit): The parent investigation hypothesised that the root cause was stale Mode B fixtures left in src/amplifier_agent_lib/protocol/conformance/fixtures/. The prescribed fix required all 10 Mode A fixtures to exist in wrappers/conformance/fixtures/. Actual count: 3 (not 10): mode-a-approval-callback-rejected.yaml mode-a-envelope-precedence.yaml mode-a-orphan-cleanup.yaml The remaining 7 planned fixtures were never authored in Phase B. Additionally, neither runner handles the 'real-binary' test_type those fixtures use; the canonical loader (loader.py) requires 'script' as a mandatory key and would reject real-binary fixtures. Moving them to src/ without updating the runners and loader would produce a new set of failures. The minimal correct fix is this commit: restore the broken import. The Mode B fixtures in src/ are valid scripted-replay tests and continue to work correctly with both runners. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Adds tests/test_conformance_fixtures_freshness.py with two guards that
prevent the regression class debugged on 2026-05-24.
Background: Phase B deleted amplifier_agent_client.jsonrpc without updating
runner_py.py. The runner crashed on import (ModuleNotFoundError), emitted
empty stdout, and all 9 parity tests plus the exit-gate test failed with an
inscrutable json.decoder.JSONDecodeError rather than a meaningful fixture diff.
The fix was made in the previous commit (restore inline JsonRpcClient).
This file ensures the same breakage cannot silently re-occur:
Guard 1 — test_runner_py_produces_valid_json_for_each_fixture (parametrized)
Invokes runner_py.py against each canonical fixture via subprocess. Asserts:
- exit code is 0 or 1 (not 2+, which indicates a crash)
- stdout is parseable JSON with 'fixture', 'assertions', and 'passed' keys
If any import error or unhandled exception occurs, this test fails with a
human-readable message pointing to the exact fixture and stderr output.
Guard 2 — test_all_canonical_fixtures_are_loader_compatible
Calls load_fixture() on every YAML in the canonical fixture directory.
Catches FixtureValidationError (e.g. missing 'script' key for real-binary
fixtures) and reports all violations in a single assertion failure.
Prevents adding Mode A real-binary fixtures to the canonical dir without
first updating the loader to accept the new schema.
Both guards fire before the parity test runs, giving a clean failure signal
rather than cryptic JSONDecodeError propagation.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
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>
Two workflows: 1. .github/workflows/ci.yml — runs on push to main + every PR. - Python: uv sync, ruff check, pyright, pytest. ~3-5 min. - TypeScript wrapper: bun install, tsc build, vitest run. ~1-2 min. - Concurrency group cancels in-flight runs for the same ref on new pushes. 2. .github/workflows/release-notes.yml — runs on tag push (v* or wrapper-v*). - Auto-generates GitHub Release with changelog notes derived from PRs/commits. - Tag with '-' suffix is auto-flagged as prerelease (v0.3.0-rc1, etc.). - Gives consumers a stable pin target for git+install workflows. Publish-to-registry workflows (PyPI, npm) deliberately NOT added yet — POC stage prefers git+install paths from public GitHub so package names stay swappable without consumer-facing migration. Will add publish workflows once names stabilize. Co-Authored-By: Amplifier <amplifier@microsoft.com>
manojp99
pushed a commit
that referenced
this pull request
Jun 3, 2026
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.
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.
No description provided.