@ours.network/fleet 1.2.0-nightly.62 → 1.2.0-nightly.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +6 -0
  2. package/dist/application/conversation-history.js +4 -1
  3. package/dist/application/conversation-role-observer.d.ts +62 -0
  4. package/dist/application/conversation-role-observer.js +206 -0
  5. package/dist/application/role-removal-service.js +1 -1
  6. package/dist/application/session-control.d.ts +31 -17
  7. package/dist/application/session-control.js +148 -33
  8. package/dist/build-info.json +4 -4
  9. package/dist/notifications/fleet-producer.js +4 -4
  10. package/dist/notifications/target-cleanup.d.ts +2 -0
  11. package/dist/notifications/target-cleanup.js +24 -2
  12. package/dist/ops.js +1 -1
  13. package/dist/session/acp.d.ts +8 -1
  14. package/dist/session/acp.js +97 -43
  15. package/dist/session/arbiter.d.ts +4 -0
  16. package/dist/session/arbiter.js +18 -0
  17. package/dist/session/codex-app-server.d.ts +7 -1
  18. package/dist/session/codex-app-server.js +60 -9
  19. package/dist/session/control.d.ts +5 -2
  20. package/dist/session/control.js +145 -20
  21. package/dist/session/conversation-observation.d.ts +25 -0
  22. package/dist/session/conversation-observation.js +100 -0
  23. package/dist/session/conversation-replay.d.ts +50 -0
  24. package/dist/session/conversation-replay.js +203 -0
  25. package/dist/session/conversation-store.d.ts +14 -0
  26. package/dist/session/conversation-store.js +201 -17
  27. package/dist/session/conversation-types.d.ts +29 -1
  28. package/dist/session/types.d.ts +6 -0
  29. package/dist/subscriptions/login.d.ts +1 -0
  30. package/dist/subscriptions/login.js +30 -5
  31. package/dist/web/auth.d.ts +2 -0
  32. package/dist/web/auth.js +10 -1
  33. package/dist/web/conversation-stream-codec.d.ts +23 -0
  34. package/dist/web/conversation-stream-codec.js +110 -0
  35. package/dist/web/conversation-stream.d.ts +25 -0
  36. package/dist/web/conversation-stream.js +231 -0
  37. package/dist/web/runtime.js +12 -2
  38. package/dist/web/server.d.ts +2 -0
  39. package/dist/web/server.js +2 -0
  40. package/docs/conversation-stream.md +176 -0
  41. package/package.json +2 -1
@@ -0,0 +1,176 @@
1
+ # Conversation event stream (wire version 1)
2
+
3
+ `GET /api/v1/roles/:id/conversation/events?expectedGeneration=GENERATION&after=CURSOR`
4
+ uses the existing Fleet route boundary, including `/fleet/api/v1/...` through the
5
+ prefix gateway. Use authenticated `fetch`, then read the response body. Cookies
6
+ work for standalone and same-origin frame access; the hosted App sends its current
7
+ workspace access bearer in `Authorization`. Query parameters are data, never
8
+ credentials. `after` is a nonnegative safe integer; generation is required.
9
+ No new WebSocket path, controller lease, notification acknowledgement, work
10
+ submission, or read receipt is introduced.
11
+
12
+ ## Framing
13
+
14
+ Content type is `text/event-stream; charset=utf-8`, with `Cache-Control: no-store`
15
+ and `X-Accel-Buffering: no`. Every frame is exactly:
16
+
17
+ ```text
18
+ event: conversation
19
+ data: {"version":1,"type":"TYPE","payload":...}
20
+
21
+ ```
22
+
23
+ The six logical types are:
24
+
25
+ - `role`: complete public role replacement (`roleId`, `roleRevision`,
26
+ `roleLifetime`, `availability`, `health`, optional backend/fallback/failure).
27
+ Local state paths are omitted. This ordering is independent of session state.
28
+ - `ledger`: either a single durable conversation event, or a coverage marker
29
+ `{coveredThrough:number}`. All events covered by a marker precede it. Replay
30
+ can legitimately advance coverage without visible events. Never advance a
31
+ recovery cursor from maximum rendered event sequence alone.
32
+ - `ready`: `{sessionGeneration,stateRevision,ledgerBarrier,coveredThrough,
33
+ capability:"complete",authorizedUntil}`. The coherent initial snapshot arrives
34
+ as a preceding `state` record, keeping its full 16 MiB payload budget. Hold
35
+ initial state/replay as provisional until matching ready; validate generation,
36
+ stateRevision and ledgerBarrier against the staged state and coverage against
37
+ delivered replay. Only successful private replay certifies `coveredThrough`;
38
+ the snapshot barrier alone does not. Disconnect between state and ready must
39
+ not publish live initial state or advance the certified cursor. Reconnect with
40
+ same-generation certified coverage. State revision and event sequence differ.
41
+ - `state`: complete conversation observation replacement, including
42
+ `sessionGeneration`, `stateRevision` and `ledgerBarrier`. Do not merge it as a
43
+ patch or use its barrier as coverage.
44
+ - `resync`: `{reason,retry,capability}`. Clear partial assembly and resynchronize
45
+ explicitly or enter supported fallback. `retry:false` means persistent
46
+ unsupported/oversize observation: do not loop reconnects. `capability` is
47
+ `degraded`, except `auth-expired` below.
48
+ - `keepalive`: `{at:number}`. Transport liveness only; it cannot certify role,
49
+ session, replay, or private-control health.
50
+
51
+ A large logical payload uses `start`, ordered `chunk`, then `commit` frames,
52
+ without the `payload` field:
53
+
54
+ ```json
55
+ {"version":1,"type":"start","id":"3","recordType":"state","totalBytes":1048576,"generation":"g","revision":7}
56
+ {"version":1,"type":"chunk","id":"3","index":0,"data":"a slice of the serialized payload JSON"}
57
+ {"version":1,"type":"commit","id":"3","totalBytes":1048576,"generation":"g","revision":7}
58
+ ```
59
+
60
+ Chunks contain JSON text, **not base64**. Concatenate `data`, UTF-8 count it,
61
+ then JSON-parse only after commit. The optional generation/revision are derived
62
+ from payload sessionGeneration/stateRevision or roleRevision;
63
+ commit and reconstructed payload must match start. Reject duplicate/out-of-order
64
+ chunks, empty chunks, mismatched identities/revisions/lengths, overlapping assemblies, invalid
65
+ UTF-8, over-budget frames, and an incomplete end. Never publish partial snapshots.
66
+ `ConversationStreamDecoder` implements these framing checks without Node APIs.
67
+ It is not a domain reducer: consumers must additionally enforce current workspace,
68
+ generation and monotonic state/role revisions before applying records.
69
+
70
+ ## Limits and ownership
71
+
72
+ Encoded frames, including SSE and JSON overhead, are at most 512 KiB. Payload JSON
73
+ is at most 16 MiB; one assembly is allowed at a time. The encoder splits at safe
74
+ UTF-16 boundaries into at most 48 Ki code units, so JSON escaping still fits the
75
+ encoded-frame bound. The active logical record is streamed progressively while
76
+ waiting encoded records are capped at 8 MiB per web connection. A valid 16 MiB
77
+ ledger event is sent independently of its coverage marker so the page envelope
78
+ does not consume its logical-record budget. Oversize records explicitly degrade.
79
+ The writer waits for socket drain before writing another frame; overflow sends a
80
+ resync when writable, otherwise closes. Ledger delivery has no batching delay
81
+ (therefore no intentional wait beyond the required 50 ms); network backpressure
82
+ can delay actual receipt.
83
+
84
+ The private supervisor separately owns its 8 MiB live replay backlog and writes
85
+ 64 KiB slices under socket backpressure. The adapter bounds each raw NDJSON line
86
+ at 16 MiB plus the maximum computed serialized replay/ready/state/event envelope
87
+ (replay has an empty events array; ready omits its null placeholder; coverage uses
88
+ `Number.MAX_SAFE_INTEGER`). This admits one exact 16 MiB event;
89
+ malformed or oversized passive input closes the stream. The receiving browser
90
+ owns its 16 MiB assembly limit. A parsed record, serialized UTF-16 string, encoded
91
+ frame, and socket buffers can coexist: these are logical budgets, not an RSS cap.
92
+ Aggregate memory scales with connections, as do role watchers and private sockets.
93
+ There is no global subscriber cap. HTTP/1 browsers commonly share about six
94
+ connections per origin; multiple tabs, pane consumers and voice can exhaust that
95
+ pool. Dispose surplus consumers; HTTP/2 deployment and browser pooling vary.
96
+
97
+ Each web connection owns one role observer, including while the role is missing.
98
+ Session describe/follow borrow it. Missing/replaced roles stop the old follow;
99
+ reappearance can open another follow, still bound to the requested generation.
100
+ Abort, revocation, server shutdown and late connector completion release owners
101
+ idempotently. Pane observation does not acquire or release voice's independent
102
+ controller. Private heartbeats remain serialized every two seconds; six seconds
103
+ without one ends private health even if SSE keepalives continue.
104
+
105
+ Fleet emits browser keepalives every second. Each App pane/voice attempt independently
106
+ bounds opening through complete ready to ten seconds, regardless of incoming activity.
107
+ After ready, nonempty byte chunks (including keepalives/comments and partial records)
108
+ reset a ten-second silence deadline. Elapsed checks before activity and on browser
109
+ visibility/pageshow resume give no hidden grace; suspended code expires at its first
110
+ subsequent check. Expiry retires health and aborts the old attempt before bounded
111
+ recovery/fallback. These browser clocks do not renew or replace credential deadlines.
112
+ Comment-only/blank SSE frames are transport activity and publish no domain record.
113
+
114
+ ## Authentication, fallback and filesystem assumptions
115
+
116
+ The stream is bound to its **opening** credential deadline. Rotating access lasts
117
+ at most five minutes; it is not renewable in place. On `auth-expired`, obtain the
118
+ renewed access credential and reconnect using only same-generation certified
119
+ coverage. Count this normal auth reconnect separately from degraded operation.
120
+ Fixed credentials use their device/sunset deadline. Cookie idle expiry is checked
121
+ without updating lastSeenAt, including the exact 30-minute boundary; ordinary
122
+ requests still update activity. Absolute expiry and revocation also end streams.
123
+ The terminal resync uses `capability:"auth-expired"`, with retry `"authenticate"`
124
+ for rotating credentials or `"reauthenticate"` for cookies/fixed credentials.
125
+ Actions after expiry must reauthenticate; heartbeats cannot refresh session policy.
126
+
127
+ `conversation_live_complete` describes current intersection of healthy role
128
+ observation and private replay/heartbeat capability. It never certifies requested
129
+ history. REST remains for initial latest history, user-requested older pages,
130
+ explicit recovery, and older/unsupported/degraded server fallback. Healthy active
131
+ consumers must not periodically fetch role details or history.
132
+
133
+ Complete role delivery relies on native directory notifications for fleet config,
134
+ role manifests and session files. Qualified native host filesystems are the
135
+ intended deployment (macOS local APFS or Linux local filesystems with functional
136
+ inotify). Watch errors, unexpected closure, unsupported watching, or invalid
137
+ current role reads withdraw completeness. Silent event loss is not detectable
138
+ without polling and is not promised away. Docker must expose the same state files
139
+ and private socket to the Fleet process, with working directory-change delivery
140
+ across the selected bind mount/volume. Docker Desktop host-to-VM mounts, network
141
+ filesystems and remote/FUSE mounts require deployment-specific notification tests;
142
+ use degraded REST fallback when those assumptions cannot be established. No ACP
143
+ library changes are required.
144
+
145
+ ## Admission receipt provenance
146
+
147
+ Prompt admission responses additionally expose optional `sessionGeneration`: the
148
+ original durable admission generation. Both backend session generations are immutable
149
+ for the admitting lifetime. Cached receipts retain that generation, and reopened command
150
+ indexes derive it from the original `prompt.admitted` event, never a replacement session.
151
+ Historical receipts without provable generation leave the field absent.
152
+
153
+ This permits an explicitly retried pre-change `{text, commandId}` browser draft to settle
154
+ only when matching receipt identity and original generation validate against its captured
155
+ current owner. Missing/old provenance keeps a visible unresolved draft and its idempotency
156
+ ID. Existing generation-bound drafts retain their original fences. `eventCursor` and causal
157
+ coverage/inventory settlement remain unchanged; provenance is not a new settlement watermark.
158
+
159
+ ## Cross-repository qualification
160
+
161
+ `test/web/fixtures/conversation-stack.ts` shares the real HTTP and private-control
162
+ stack with the legacy conversation tests and the matching App checkout's optional
163
+ `tests/fleet-live-data.integration.mjs`. ACP uses the existing child-process protocol
164
+ fixture; native Codex uses the existing app-server protocol driver. Both exercise
165
+ actual session, ledger, control adapter, HTTP framing and App decoder/reconciler/UI.
166
+ Role availability changes are controlled fixture inputs; this does not qualify native
167
+ filesystem event delivery on an arbitrary mount.
168
+
169
+ Run Fleet's full tests and typecheck after integrated changes. Retain the SSE,
170
+ workspace-auth-provenance and workspace-tunnel suites for frame/hosted authentication,
171
+ expiry/revocation, delayed-open disposal and bounded slow consumers. App reports
172
+ healthy content requests separately from bootstrap, paging, credential reconnect,
173
+ explicit recovery and unsupported-host fallback. DOM mutation and fixture latency
174
+ measurements have no claim about real-provider inference or physical-device audio.
175
+ The App and Fleet changes require coordinated upstream delivery; source qualification
176
+ does not establish that installed or serving packages contain them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.2.0-nightly.62",
3
+ "version": "1.2.0-nightly.64",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -20,6 +20,7 @@
20
20
  "README.md",
21
21
  "docs/task-workspaces.md",
22
22
  "docs/live-voice.md",
23
+ "docs/conversation-stream.md",
23
24
  "docs/room-layouts.md",
24
25
  "docs/validation/managed-cli-permissions.md",
25
26
  "docs/fleet-migration.md",