Compare commits
No commits in common. "main" and "feat/sidecar-pair-smoke" have entirely different histories.
main
...
feat/sidec
|
|
@ -1,8 +1,8 @@
|
||||||
# Next Steps — Resume Pointer
|
# Next Steps — Resume Pointer
|
||||||
|
|
||||||
> **Last updated:** 2026-05-17.
|
> **Last updated:** 2026-05-15.
|
||||||
> **Where we are:** Phase 2 mostly done. T-2.0..T-2.6, T-2.8, T-2.9, T-2.10, T-2.11 on main. App runs end-to-end on iPhone 12 mini + simulator. 130 unit tests (8 pre-existing failures), 12/12 UI tests green.
|
> **Where we are:** Phase 2 in progress. T-2.0..T-2.5 + T-2.9 done. App boots on iPhone (iOS 26, wireless).
|
||||||
> **Where we go next:** T-2.7 PreConnectPool, T-2.12 TestFlight, T-2.13 MVP smoke. Plus: fix 8 pre-existing unit-test failures (4 Keychain entitlements + 4 Pairing http-scheme).
|
> **Where we go next:** T-2.6 SessionSwitcher, T-2.8 StatusBar, T-2.11 Face-ID. Then T-2.7 PreConnectPool, T-2.10 Background lifecycle, T-2.12 TestFlight.
|
||||||
|
|
||||||
This document is the "where did I leave off" anchor. Read this first when
|
This document is the "where did I leave off" anchor. Read this first when
|
||||||
resuming work. The rest of `docs/` is reference.
|
resuming work. The rest of `docs/` is reference.
|
||||||
|
|
@ -14,64 +14,90 @@ resuming work. The rest of `docs/` is reference.
|
||||||
| Item | Status |
|
| Item | Status |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Phase 0 — Stream Spike | ✅ done. Verdict GREEN with caveat (pipe-pane unreliable). Branch `feat/spike-stream` kept. |
|
| Phase 0 — Stream Spike | ✅ done. Verdict GREEN with caveat (pipe-pane unreliable). Branch `feat/spike-stream` kept. |
|
||||||
| Phase 0.5 — Control-Mode Spike | ✅ done. Verdict: **Path B — tmux control mode**. |
|
| Phase 0.5 — Control-Mode Spike | ✅ done. Verdict: **Path B — tmux control mode**. Branch `feat/spike-tmux-cc` kept. |
|
||||||
| Phase 1 plan | ✅ updated to Path B. |
|
| Phase 1 plan | ✅ updated to Path B. T-1.1 now specifies control mode + `%output` parser. Architecture diagram, risks (R4 + R5) added. |
|
||||||
| Interface Contracts (IC-1..IC-4) | ✅ **frozen** 2026-05-15. |
|
| Interface Contracts (IC-1..IC-4) | ✅ **frozen** 2026-05-15. See SYNC.md. |
|
||||||
| Phase 1 implementation | ✅ **done** 2026-05-15. All T-1.0..T-1.10 on main. Smoke 12/12. |
|
| Phase 1 implementation | ✅ **done** 2026-05-15. All T-1.0..T-1.10 on main. Smoke 12/12. |
|
||||||
| Phase 1 — follow-up fixes | ✅ POST /pair smoke test, WS keys/key/paste handler, POST /sessions response shape. |
|
| Phase 2 iOS app — scaffold | ✅ T-2.0 done. pi-remote-ios repo, Xcode project, SwiftTerm+Starscream. |
|
||||||
| Phase 2 iOS — T-2.0 scaffold | ✅ done. |
|
| Phase 2 iOS app — core layers | ✅ T-2.1..T-2.5, T-2.9 done. WebSocket, Pairing, Terminal, ModifierBar, SessionConnection, APNs. 111 unit tests. |
|
||||||
| Phase 2 iOS — T-2.1..T-2.5, T-2.9 core | ✅ done. WebSocket, Pairing, Terminal, ModifierBar, SessionConnection, APNs. |
|
| Phase 2 iOS app — UI + lifecycle | ⛔ T-2.6 SessionSwitcher, T-2.7 PreConnect, T-2.8 StatusBar, T-2.10 Background, T-2.11 Face-ID, T-2.12 TestFlight. |
|
||||||
| Phase 2 iOS — T-2.6 SessionSwitcher | ✅ **done** 2026-05-16. SessionRegistry + SessionSwitcher + SessionRow. |
|
| iOS work | blocked, untouched. |
|
||||||
| Phase 2 iOS — T-2.7 PreConnectPool | ⛔ not started. |
|
|
||||||
| Phase 2 iOS — T-2.8 StatusBar | ✅ **done** 2026-05-16. State indicator, action buttons. |
|
|
||||||
| Phase 2 iOS — T-2.10 Background lifecycle | ✅ **done** 2026-05-17. AppState.lifecycleTransitions publisher, SessionConnection suspend/resume w/ ResumeCursor, stale-frame freeze, post-Face-ID reconnect. 22 lifecycle tests + 6 follow-up. TDD pattern: tests → impl → review → fixup (B-1 + 3 nits + 4 coverage gaps). |
|
|
||||||
| Phase 2 iOS — T-2.11 Face-ID + Settings | ✅ **done** 2026-05-16. SettingsView, FaceIDGate, LockView. |
|
|
||||||
| Phase 2 iOS — T-2.12 TestFlight | ⛔ not started. Needs Apple credentials. |
|
|
||||||
| Phase 2 iOS — T-2.13 MVP smoke | ⛔ not started. |
|
|
||||||
| Phase 2 iOS — XCUITest infrastructure (bonus) | ✅ **done** 2026-05-16. piRemoteUITests target, 8/8 tests, `--uitest` mode, accessibility IDs, idb workflow documented in `docs/SIMULATOR-AUTOMATION.md` (iOS repo). |
|
|
||||||
| Bug Backlog | ⚠️ 8 pre-existing unit-test failures (4 Keychain `-34018` entitlements + 4 Pairing http-scheme tests). Not regressions, undocumented. |
|
|
||||||
|
|
||||||
Branches on remote `git.vpsj.de/jay/pi-remote-control`:
|
Branches on remote `git.vpsj.de/jay/pi-remote-control`:
|
||||||
- `main` — all Phase 1 + follow-up fixes (latest: POST /sessions shape).
|
- `main` — has all docs, no implementation changes yet.
|
||||||
- `feat/spike-stream` — Phase 0 PoC, throwaway.
|
- `feat/spike-stream` — Phase 0 PoC, throwaway.
|
||||||
- `feat/spike-tmux-cc` — Phase 0.5 PoC, throwaway. Reference impl for T-1.1.
|
- `feat/spike-tmux-cc` — Phase 0.5 PoC, throwaway. Reference impl for T-1.1.
|
||||||
|
|
||||||
Branches on remote `git.vpsj.de/jay/pi-remote-ios`:
|
|
||||||
- `main` — T-2.0..T-2.6, T-2.8, T-2.9, T-2.11 + UI test target + idb docs.
|
|
||||||
- legacy `feat/p2-t2.*` branches kept for history; all already on main via merge commits.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Open work (in priority order)
|
## Orchestrator todo list (in order)
|
||||||
|
|
||||||
### Phase 2 remaining
|
These are things only the orchestrator does — not delegatable to a worker
|
||||||
|
subagent.
|
||||||
|
|
||||||
- **T-2.7 PreConnectPool.** Hot WS per known session + cached last frame;
|
### 1. Freeze interface contracts
|
||||||
switching shows the cached frame instantly with a "syncing…" pill.
|
|
||||||
Touches: `Sources/Core/Sessions/PreConnectPool.swift` (new),
|
|
||||||
`Sources/Core/Sessions/SessionConnection.swift`. Now safe to schedule
|
|
||||||
after T-2.10's lifecycle hooks landed.
|
|
||||||
- **T-2.12 TestFlight pipeline.** Build / archive / upload scripts +
|
|
||||||
internal testers list. Verify production APNs path. Needs Apple
|
|
||||||
Developer credentials (paid program). Touches: `docs/DISTRIBUTION.md`,
|
|
||||||
`scripts/` or Fastfile.
|
|
||||||
- **T-2.13 MVP smoke.** Manual on-device checklist: pair → render → input
|
|
||||||
→ backgrounded → push → reopen <1s → session-switch. Documents in
|
|
||||||
`docs/PHASE-2-report.md` (iOS repo).
|
|
||||||
|
|
||||||
### Bug backlog (not regressions, pre-existing)
|
Open `PHASE-1-sidecar.md` §Interface Contracts and re-read IC-1..IC-4.
|
||||||
|
For each: does it still look right after the spikes? Anything to tweak?
|
||||||
|
|
||||||
- **Keychain unit tests fail with `-34018` errSecMissingEntitlement** (4 cases).
|
Things to double-check:
|
||||||
Likely needs Keychain-Access-Group in the test target's entitlements,
|
- **IC-1 ClientToServer**: do we want to add `{ type:"resize"; cols:int; rows:int }` so iPad-Landscape can renegotiate tmux pane size? (Spec said fixed 120×40, but if we ever want elastic, this is the place.) Recommendation: defer, fix at 120×40 for v1, revisit only if it bites.
|
||||||
or running under signed bundle. Investigate or skip with a documented reason.
|
- **IC-1 ServerToClient**: `tree` event — gruppe T is out of iOS scope, so this can be dropped from the contract OR kept as "reserved, server may emit but client ignores". Recommendation: drop to keep contract tight.
|
||||||
- **Pairing unit tests `testParseQR_{http,https,wrongScheme}_throws` fail**
|
- **IC-2 REST shape**: `/sessions/:id/thumbnail` is referenced by iOS-D-01c (Phase 3). Should the endpoint return raw text/plain capture, or a structured JSON `{cols,rows,lines:[…]}`? Recommendation: raw text/plain — simpler, smaller, client parses lines itself.
|
||||||
(4 cases). Either the parser silently accepts http/https now (regression
|
- **IC-3 pairing**: `deviceToken` and `environment` are now mandatory in Phase 2. Pre-Phase-2 they're optional. Mark accordingly.
|
||||||
hidden by the `fp` optional change) or the tests need updating to match
|
- **IC-4 TOML config**: `[apns]` section can have an `environment_default` for testing convenience? Probably no — environment is per-device. Leave as-is.
|
||||||
the new lenient behaviour.
|
|
||||||
|
|
||||||
### Phase 3 — not yet in scope
|
After review:
|
||||||
|
- Edit `SYNC.md` "Frozen Interface Contracts" table: set Status `frozen` and fill `Frozen at` date.
|
||||||
|
- Commit on main.
|
||||||
|
|
||||||
Slash palette, voice, themes, search, etc. See `docs/PHASE-3-ios-augmentation.md`.
|
### 2. Dispatch T-1.0 — Server Refactor
|
||||||
|
|
||||||
|
Single agent, blocking everyone else. Refactor existing
|
||||||
|
`extensions/remote-control/server.ts` (335 lines) and friends into the
|
||||||
|
modular layout from `PHASE-1-sidecar.md` §Architecture Sketch.
|
||||||
|
|
||||||
|
Prompt template (adapt with concrete contract numbers):
|
||||||
|
|
||||||
|
```
|
||||||
|
# Task: Phase 1 T-1.0 — Server Refactor
|
||||||
|
|
||||||
|
Working dir: /Users/jay/.pi/agent/git/git.vpsj.de/jay/pi-remote-control
|
||||||
|
|
||||||
|
Read first:
|
||||||
|
- docs/NEXT-STEPS.md (state of play)
|
||||||
|
- docs/PHASE-1-sidecar.md §Architecture Sketch and §Task Breakdown row T-1.0
|
||||||
|
- docs/SYNC.md (claim the task)
|
||||||
|
- existing code: extensions/remote-control/{index,server,html,messages,auth,config}.ts
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
- Carve existing server.ts into the server/ + server/routes/ + server/upgrade.ts
|
||||||
|
structure from the phase doc.
|
||||||
|
- Keep the LEGACY HTML client (html.ts) working end-to-end. Add comments
|
||||||
|
tagging these legacy paths.
|
||||||
|
- No new features. No new endpoints. Just structural refactor + ensure
|
||||||
|
existing tests / smoke usage still works.
|
||||||
|
- After your refactor lands, the directory layout matches the Phase-1
|
||||||
|
Architecture Sketch.
|
||||||
|
|
||||||
|
Out of scope: anything touching tmux/, buffer/, pi/, auth/{pairing,tls}.ts,
|
||||||
|
apns/, cli/. Those are for later T-1.* tasks.
|
||||||
|
|
||||||
|
Branch: feat/p1-t1-0-server-refactor
|
||||||
|
```
|
||||||
|
|
||||||
|
After T-1.0 merges into main, the parallel fan-out becomes available.
|
||||||
|
|
||||||
|
### 3. Plan the parallel fan-out
|
||||||
|
|
||||||
|
Once T-1.0 is in main:
|
||||||
|
- 5 worker dispatches in parallel: T-1.1, T-1.2, T-1.3, T-1.4, T-1.10.
|
||||||
|
- Each gets a tight prompt referencing their row in `PHASE-1-sidecar.md`
|
||||||
|
and the relevant frozen ICs.
|
||||||
|
- T-1.1 explicitly says: use control mode, reference `spike-cc.ts` on
|
||||||
|
`feat/spike-tmux-cc`, follow R-CC-1..R-CC-5 expectations.
|
||||||
|
|
||||||
|
T-1.5, T-1.6, T-1.7 follow as their deps come in.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -79,18 +105,26 @@ Slash palette, voice, themes, search, etc. See `docs/PHASE-3-ios-augmentation.md
|
||||||
|
|
||||||
1. `docs/NEXT-STEPS.md` (this file)
|
1. `docs/NEXT-STEPS.md` (this file)
|
||||||
2. `docs/SYNC.md` — current claims, gate, contracts
|
2. `docs/SYNC.md` — current claims, gate, contracts
|
||||||
3. `docs/PHASE-2-ios-mvp.md` — what's left to build for iOS
|
3. `docs/PHASE-1-sidecar.md` — what to build
|
||||||
4. iOS repo: `docs/SIMULATOR-AUTOMATION.md` — how to drive the sim
|
4. `docs/reference/PHASE-0-report.md` and `PHASE-0.5-report.md` — why we're
|
||||||
5. iOS repo: `docs/BUILD.md` — build + install + launch commands
|
building it this way
|
||||||
6. Spec only if you forget the why: `docs/reference/SPEC-ios-app.md`
|
5. Spec only if you forget the why: `docs/reference/SPEC-ios-app.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Conventions established
|
## Open questions for next session
|
||||||
|
|
||||||
- **Async subagent dispatch.** All worker tasks via `subagent({ async: true, context: "fresh" })`. Worktree isolation for parallel-on-same-repo work; same-repo iOS+sidecar splits are inherently isolated.
|
- ~~**OQ-1.** Drop the `tree` event from IC-1 ServerToClient~~ — **resolved**: dropped. IC-1 frozen 2026-05-15.
|
||||||
- **Reviewer fan-in.** After parallel implementation, dispatch one reviewer agent with all branch summaries pre-loaded; reviewer writes `review.md` for the orchestrator to apply.
|
- ~~**OQ-2.** Resize message in IC-1?~~ — **resolved**: deferred, fixed 120×40 for v1.
|
||||||
- **Sim test infra (`--uitest` mode).** Pre-fetch a fresh `/pair-qr` token per test, launch with `--reset-state --pair-with-url <url>`. `MainTerminalView` in `--uitest` mode skips the WS so XCUITest can reach app-idle within the 120 s window. Pasteboard prompt suppressed via `xcrun simctl privacy grant pasteboard de.vpsj.pi-remote`.
|
- ~~**OQ-3.** tmux control-mode connection per-server vs per-session?~~ — **resolved**: per-session (like the spike). Simpler, spike reference code exists, isolates parser state per session. Refactor to per-server later if scale demands it.
|
||||||
- **Sidecar manual restart pattern.** `tmux kill-session -t pi-sidecar; tmux new-session -d -s pi-sidecar -x 220 -y 50 "pi -nt -ne -ns -np -nc --no-session --offline -e extensions/remote-control --remote-control"`. Pre-fill 3 sessions (`main`, `work`, `logs`) for any manual E2E test.
|
- ~~**OQ-4.** Worker model for T-1.0?~~ — **resolved**: `anthropic/claude-sonnet-4-6` with `context: fresh`. Haiku rejected: previous swarm attempt produced broken imports across `index.ts`/`html.ts`/`messages.ts` even on a stronger model (see deleted `feat/p1-t1-0-server-refactor` branch). T-1.0 is the blocker for all parallel fan-out — reliability over cost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Don't do tomorrow
|
||||||
|
|
||||||
|
- Don't merge the spike branches into main. They're reference, not code.
|
||||||
|
- Don't start T-1.1 before T-1.0 is in main.
|
||||||
|
- Don't start any iOS task until Phase 1 is at least feature-complete.
|
||||||
- Don't expand scope. Stick to v3 spec; new ideas go into a v4 review
|
- Don't expand scope. Stick to v3 spec; new ideas go into a v4 review
|
||||||
round, not into open PRs.
|
round, not into open PRs.
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ The point: no central scheduler is required. A short structured edit on
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Phase 0 — Spike Stream | done | ✅ GREEN LIGHT with caveat: pipe-pane unreliable. See `reference/PHASE-0-report.md`. |
|
| Phase 0 — Spike Stream | done | ✅ GREEN LIGHT with caveat: pipe-pane unreliable. See `reference/PHASE-0-report.md`. |
|
||||||
| Phase 0.5 — Spike tmux Control Mode | done | ✅ VERDICT: Path B (control mode) recommended. See `reference/PHASE-0.5-report.md`. |
|
| Phase 0.5 — Spike tmux Control Mode | done | ✅ VERDICT: Path B (control mode) recommended. See `reference/PHASE-0.5-report.md`. |
|
||||||
| Phase 1 — Sidecar | **done** | All T-1.0..T-1.10 implemented. Smoke 22/22 green (incl. POST /pair). Follow-up fixes on main: WS keys handler, POST /sessions shape, control-mode encoding. |
|
| Phase 1 — Sidecar | **done** | All T-1.0..T-1.10 implemented. Smoke 12/12 green. |
|
||||||
| Phase 2 — iOS MVP | **in progress** | T-2.0..T-2.6, T-2.8, T-2.9, T-2.10, T-2.11 on main. Bonus: XCUITest target + idb automation docs. Open: T-2.7, T-2.12, T-2.13. Repo: `pi-remote-ios`. |
|
| Phase 2 — iOS MVP | **in progress** | T-2.0..T-2.5, T-2.9 done. App runs on device (iOS 26, wireless). Repo: `pi-remote-ios`. |
|
||||||
| Phase 3 — iOS Augmentation | blocked on Phase 2 | Continuous after MVP ships. |
|
| Phase 3 — iOS Augmentation | blocked on Phase 2 | Continuous after MVP ships. |
|
||||||
|
|
||||||
Update the **Status** column when a phase transitions. Allowed states:
|
Update the **Status** column when a phase transitions. Allowed states:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* S-09 — multi-session CRUD routes.
|
* S-09 — multi-session CRUD routes.
|
||||||
*
|
*
|
||||||
* POST /sessions → { id, name, state, lastOutputAt }
|
* POST /sessions → { id, name }
|
||||||
* GET /sessions → [{ id, name, description, state, lastOutputAt }]
|
* GET /sessions → [{ id, name, description, state, lastOutputAt }]
|
||||||
* PATCH /sessions/:id → updates @description
|
* PATCH /sessions/:id → updates @description
|
||||||
* DELETE /sessions/:id → kills tmux session, optionally clears buffer
|
* DELETE /sessions/:id → kills tmux session, optionally clears buffer
|
||||||
|
|
@ -110,9 +110,7 @@ async function handleCreate(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const id = await spawnSession({ name });
|
const id = await spawnSession({ name });
|
||||||
// Include state + lastOutputAt to match the GET /sessions response shape
|
sendJson(res, 201, { id, name });
|
||||||
// so iOS clients can decode the response with the same type.
|
|
||||||
sendJson(res, 201, { id, name, state: "idle", lastOutputAt: "" });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
sendJson(res, 500, { error: "internal_error", message: String(err) });
|
sendJson(res, 500, { error: "internal_error", message: String(err) });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import { readChunks } from "../../buffer/reader.js";
|
||||||
import type { StateEvent } from "../../pi/events.js";
|
import type { StateEvent } from "../../pi/events.js";
|
||||||
import { SequenceCounter } from "../../sequence.js";
|
import { SequenceCounter } from "../../sequence.js";
|
||||||
import { ControlClient } from "../../tmux/control.js";
|
import { ControlClient } from "../../tmux/control.js";
|
||||||
import { sendKey, sendKeys, sendPaste } from "../../tmux/input.js";
|
|
||||||
import { resizeSession } from "../../tmux/manager.js";
|
import { resizeSession } from "../../tmux/manager.js";
|
||||||
import { capturePane } from "../../tmux/snapshot.js";
|
import { capturePane } from "../../tmux/snapshot.js";
|
||||||
import type { WsClient, WsServer } from "../types.js";
|
import type { WsClient, WsServer } from "../types.js";
|
||||||
|
|
@ -163,54 +162,6 @@ function handleStreamConnection(
|
||||||
const cols = typeof m.cols === "number" ? m.cols : 80;
|
const cols = typeof m.cols === "number" ? m.cols : 80;
|
||||||
const rows = typeof m.rows === "number" ? m.rows : 24;
|
const rows = typeof m.rows === "number" ? m.rows : 24;
|
||||||
resizeSession(sessionId, cols, rows).catch(() => {});
|
resizeSession(sessionId, cols, rows).catch(() => {});
|
||||||
} else if (m.type === "keys") {
|
|
||||||
if (typeof m.data !== "string") {
|
|
||||||
sendJson(ws, {
|
|
||||||
type: "error",
|
|
||||||
code: "bad_input",
|
|
||||||
message: "keys.data must be a string",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendKeys(sessionId, m.data).catch((err) => {
|
|
||||||
sendJson(ws, {
|
|
||||||
type: "error",
|
|
||||||
code: "bad_input",
|
|
||||||
message: `Failed to send keys: ${String(err)}`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (m.type === "key") {
|
|
||||||
if (typeof m.name !== "string") {
|
|
||||||
sendJson(ws, {
|
|
||||||
type: "error",
|
|
||||||
code: "bad_input",
|
|
||||||
message: "key.name must be a string",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendKey(sessionId, m.name).catch((err) => {
|
|
||||||
sendJson(ws, {
|
|
||||||
type: "error",
|
|
||||||
code: "bad_input",
|
|
||||||
message: `Failed to send key: ${String(err)}`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (m.type === "paste") {
|
|
||||||
if (typeof m.data !== "string") {
|
|
||||||
sendJson(ws, {
|
|
||||||
type: "error",
|
|
||||||
code: "bad_input",
|
|
||||||
message: "paste.data must be a string",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendPaste(sessionId, m.data).catch((err) => {
|
|
||||||
sendJson(ws, {
|
|
||||||
type: "error",
|
|
||||||
code: "bad_input",
|
|
||||||
message: `Failed to send paste: ${String(err)}`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (m.type === "snapshot-request") {
|
} else if (m.type === "snapshot-request") {
|
||||||
capturePane({ session: sessionId, escapes: true })
|
capturePane({ session: sessionId, escapes: true })
|
||||||
.then((text) => {
|
.then((text) => {
|
||||||
|
|
|
||||||
|
|
@ -173,40 +173,6 @@ describe("T-1.8 stream integration", () => {
|
||||||
assert.ok(found, `Should observe marker "${marker}" in stream output`);
|
assert.ok(found, `Should observe marker "${marker}" in stream output`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("WS stream: send keys via WS, observe in stream output", async () => {
|
|
||||||
const ws = await openWebSocket(
|
|
||||||
`${WS_BASE}/sessions/${sessionId}/stream${AUTH}`,
|
|
||||||
);
|
|
||||||
ws.send(JSON.stringify({ type: "resume", lastSeq: null }));
|
|
||||||
|
|
||||||
const marker = `ws-smoke-${Date.now()}`;
|
|
||||||
|
|
||||||
// Wait a tick to let resume complete, then send keys via WS
|
|
||||||
await new Promise((r) => setTimeout(r, 200));
|
|
||||||
ws.send(JSON.stringify({ type: "keys", data: `echo ${marker}` }));
|
|
||||||
ws.send(JSON.stringify({ type: "key", name: "enter" }));
|
|
||||||
|
|
||||||
const found = await new Promise((resolve) => {
|
|
||||||
const timeout = setTimeout(() => resolve(false), 6000);
|
|
||||||
let accumulated = "";
|
|
||||||
ws.on("message", (data) => {
|
|
||||||
if (Buffer.isBuffer(data) && data.length > 8) {
|
|
||||||
accumulated += data.slice(8).toString("utf8");
|
|
||||||
if (accumulated.includes(marker)) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
resolve(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await closeWebSocket(ws);
|
|
||||||
assert.ok(
|
|
||||||
found,
|
|
||||||
`Should observe marker "${marker}" sent via WS keys message in stream output`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("WS stream: reconnect with lastSeq → receives only delta", async () => {
|
it("WS stream: reconnect with lastSeq → receives only delta", async () => {
|
||||||
// First pass: collect frames and note highest seq
|
// First pass: collect frames and note highest seq
|
||||||
const ws1 = await openWebSocket(
|
const ws1 = await openWebSocket(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue