The four files under lib/loop/ were written against 0.2.1-rc.2 and have been
verified to import on their own; they are the substrate a replacement loop
needs to take over step(). This commit only puts them in the tree — lib/loop.js
is untouched, so nothing imports them yet and behaviour is unchanged.
Two packaging facts they bring with them:
files `lib/*.js` does not recurse, so lib/loop/ was shipping nothing.
`lib/loop.js` and `lib/loop/*.js` are now listed separately because
neither glob covers the other.
peer runtime-context.js imports @deepseek-ai/dsh-session, which the peer
table never declared. G2b's allowlist covered it, so the static guard
passed over a real declaration gap.
test/loop-ports.test.js pins the port interface rather than its behaviour: the
modules import alone, the export names survive, and the inbox projection keeps
key "inbox" / stateVersion 1. That literal pair is a wire format shared with
stock-loop sessions — a different key silently reads as "no pending input", a
different version makes stateSchema.parse refuse stored rows. The schema's
parse() is also exercised against seven malformed persisted rows, since
replacing a zod schema with a hand-written check is exactly the kind of change
that can be a no-op and still look fine.