@kortix/sdk 0.12.8 → 0.13.0
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.
- package/README.md +3 -6
- package/dist/browser/session-sync/session-sync-registry.d.ts +7 -2
- package/dist/browser/session-sync/session-sync-registry.d.ts.map +1 -1
- package/dist/browser/session-sync/session-sync-registry.js +33 -44
- package/dist/browser/session-sync/session-sync-registry.js.map +1 -1
- package/dist/browser/stores/opencode-compaction-store.d.ts +9 -3
- package/dist/browser/stores/opencode-compaction-store.d.ts.map +1 -1
- package/dist/browser/stores/opencode-compaction-store.js +5 -12
- package/dist/browser/stores/opencode-compaction-store.js.map +1 -1
- package/dist/browser/stores/sandbox-connection-store.d.ts +28 -17
- package/dist/browser/stores/sandbox-connection-store.d.ts.map +1 -1
- package/dist/browser/stores/sandbox-connection-store.js +32 -84
- package/dist/browser/stores/sandbox-connection-store.js.map +1 -1
- package/dist/browser/stores/session-working-store.d.ts +73 -0
- package/dist/browser/stores/session-working-store.d.ts.map +1 -0
- package/dist/browser/stores/session-working-store.js +86 -0
- package/dist/browser/stores/session-working-store.js.map +1 -0
- package/dist/browser/stores/sync-store/types.d.ts +14 -4
- package/dist/browser/stores/sync-store/types.d.ts.map +1 -1
- package/dist/browser/stores/sync-store.d.ts +140 -3
- package/dist/browser/stores/sync-store.d.ts.map +1 -1
- package/dist/browser/stores/sync-store.js +425 -36
- package/dist/browser/stores/sync-store.js.map +1 -1
- package/dist/core/client/kortix.d.ts +102 -1
- package/dist/core/client/kortix.d.ts.map +1 -1
- package/dist/core/client/kortix.js +78 -0
- package/dist/core/client/kortix.js.map +1 -1
- package/dist/core/files/client.d.ts +50 -2
- package/dist/core/files/client.d.ts.map +1 -1
- package/dist/core/files/client.js +253 -44
- package/dist/core/files/client.js.map +1 -1
- package/dist/core/files/types.d.ts +11 -0
- package/dist/core/files/types.d.ts.map +1 -1
- package/dist/core/http/abort-error.d.ts +89 -0
- package/dist/core/http/abort-error.d.ts.map +1 -0
- package/dist/core/http/abort-error.js +190 -0
- package/dist/core/http/abort-error.js.map +1 -0
- package/dist/core/http/api-client.d.ts.map +1 -1
- package/dist/core/http/api-client.js +18 -4
- package/dist/core/http/api-client.js.map +1 -1
- package/dist/core/rest/platform-client/github-app.d.ts +13 -0
- package/dist/core/rest/platform-client/github-app.d.ts.map +1 -1
- package/dist/core/rest/platform-client/github-app.js +2 -0
- package/dist/core/rest/platform-client/github-app.js.map +1 -1
- package/dist/core/rest/platform-client/members.d.ts +10 -64
- package/dist/core/rest/platform-client/members.d.ts.map +1 -1
- package/dist/core/rest/platform-client/members.js +10 -28
- package/dist/core/rest/platform-client/members.js.map +1 -1
- package/dist/core/rest/projects-client/accounts.d.ts +30 -1
- package/dist/core/rest/projects-client/accounts.d.ts.map +1 -1
- package/dist/core/rest/projects-client/accounts.js +1 -1
- package/dist/core/rest/projects-client/accounts.js.map +1 -1
- package/dist/core/rest/projects-client/apps.d.ts +13 -0
- package/dist/core/rest/projects-client/apps.d.ts.map +1 -1
- package/dist/core/rest/projects-client/apps.js.map +1 -1
- package/dist/core/rest/projects-client/assignments.d.ts +114 -0
- package/dist/core/rest/projects-client/assignments.d.ts.map +1 -0
- package/dist/core/rest/projects-client/assignments.js +91 -0
- package/dist/core/rest/projects-client/assignments.js.map +1 -0
- package/dist/core/rest/projects-client/billing.d.ts +10 -1
- package/dist/core/rest/projects-client/billing.d.ts.map +1 -1
- package/dist/core/rest/projects-client/billing.js +11 -2
- package/dist/core/rest/projects-client/billing.js.map +1 -1
- package/dist/core/rest/projects-client/connectors.d.ts +2 -0
- package/dist/core/rest/projects-client/connectors.d.ts.map +1 -1
- package/dist/core/rest/projects-client/connectors.js.map +1 -1
- package/dist/core/rest/projects-client/files.js +2 -2
- package/dist/core/rest/projects-client/files.js.map +1 -1
- package/dist/core/rest/projects-client/gateway.d.ts +51 -0
- package/dist/core/rest/projects-client/gateway.d.ts.map +1 -1
- package/dist/core/rest/projects-client/gateway.js.map +1 -1
- package/dist/core/rest/projects-client/iam.d.ts +51 -3
- package/dist/core/rest/projects-client/iam.d.ts.map +1 -1
- package/dist/core/rest/projects-client/iam.js +18 -1
- package/dist/core/rest/projects-client/iam.js.map +1 -1
- package/dist/core/rest/projects-client/index.d.ts +1 -0
- package/dist/core/rest/projects-client/index.d.ts.map +1 -1
- package/dist/core/rest/projects-client/index.js +1 -0
- package/dist/core/rest/projects-client/index.js.map +1 -1
- package/dist/core/rest/projects-client/projects.d.ts +1 -1
- package/dist/core/rest/projects-client/projects.d.ts.map +1 -1
- package/dist/core/rest/projects-client/projects.js +3 -0
- package/dist/core/rest/projects-client/projects.js.map +1 -1
- package/dist/core/rest/projects-client/secrets.d.ts +11 -0
- package/dist/core/rest/projects-client/secrets.d.ts.map +1 -1
- package/dist/core/rest/projects-client/secrets.js +1 -1
- package/dist/core/rest/projects-client/secrets.js.map +1 -1
- package/dist/core/rest/projects-client/session-costs.d.ts +33 -0
- package/dist/core/rest/projects-client/session-costs.d.ts.map +1 -1
- package/dist/core/rest/projects-client/session-costs.js.map +1 -1
- package/dist/core/rest/projects-client/sessions.d.ts +226 -2
- package/dist/core/rest/projects-client/sessions.d.ts.map +1 -1
- package/dist/core/rest/projects-client/sessions.js +101 -4
- package/dist/core/rest/projects-client/sessions.js.map +1 -1
- package/dist/core/rest/projects-client/shared.d.ts +10 -1
- package/dist/core/rest/projects-client/shared.d.ts.map +1 -1
- package/dist/core/rest/projects-client/shared.js.map +1 -1
- package/dist/core/rest/projects-client/tokens.d.ts +13 -1
- package/dist/core/rest/projects-client/tokens.d.ts.map +1 -1
- package/dist/core/rest/projects-client/tokens.js +7 -2
- package/dist/core/rest/projects-client/tokens.js.map +1 -1
- package/dist/core/rest/projects-client/triggers.d.ts +69 -2
- package/dist/core/rest/projects-client/triggers.d.ts.map +1 -1
- package/dist/core/rest/projects-client/triggers.js +1 -1
- package/dist/core/rest/projects-client/triggers.js.map +1 -1
- package/dist/core/session/compaction.d.ts +98 -0
- package/dist/core/session/compaction.d.ts.map +1 -0
- package/dist/core/session/compaction.js +108 -0
- package/dist/core/session/compaction.js.map +1 -0
- package/dist/core/session/health.d.ts +22 -0
- package/dist/core/session/health.d.ts.map +1 -1
- package/dist/core/session/health.js +31 -5
- package/dist/core/session/health.js.map +1 -1
- package/dist/core/session/index.d.ts +2 -0
- package/dist/core/session/index.d.ts.map +1 -1
- package/dist/core/session/index.js +2 -0
- package/dist/core/session/index.js.map +1 -1
- package/dist/core/session/message-queue.d.ts +79 -15
- package/dist/core/session/message-queue.d.ts.map +1 -1
- package/dist/core/session/message-queue.js +73 -27
- package/dist/core/session/message-queue.js.map +1 -1
- package/dist/core/session/rewind.d.ts +76 -10
- package/dist/core/session/rewind.d.ts.map +1 -1
- package/dist/core/session/rewind.js +68 -11
- package/dist/core/session/rewind.js.map +1 -1
- package/dist/core/session/working.d.ts +241 -0
- package/dist/core/session/working.d.ts.map +1 -0
- package/dist/core/session/working.js +261 -0
- package/dist/core/session/working.js.map +1 -0
- package/dist/core/session-sync/session-sync-controller.d.ts +35 -20
- package/dist/core/session-sync/session-sync-controller.d.ts.map +1 -1
- package/dist/core/session-sync/session-sync-controller.js +48 -115
- package/dist/core/session-sync/session-sync-controller.js.map +1 -1
- package/dist/core/stream/event-stream.d.ts +22 -0
- package/dist/core/stream/event-stream.d.ts.map +1 -1
- package/dist/core/stream/event-stream.js +132 -36
- package/dist/core/stream/event-stream.js.map +1 -1
- package/dist/core/turns/grouping.d.ts +32 -2
- package/dist/core/turns/grouping.d.ts.map +1 -1
- package/dist/core/turns/grouping.js +31 -2
- package/dist/core/turns/grouping.js.map +1 -1
- package/dist/core/turns/index.d.ts +1 -0
- package/dist/core/turns/index.d.ts.map +1 -1
- package/dist/core/turns/index.js +1 -0
- package/dist/core/turns/index.js.map +1 -1
- package/dist/core/turns/open-turn.d.ts +81 -0
- package/dist/core/turns/open-turn.d.ts.map +1 -0
- package/dist/core/turns/open-turn.js +77 -0
- package/dist/core/turns/open-turn.js.map +1 -0
- package/dist/core/turns/state.js +26 -26
- package/dist/core/turns/state.js.map +1 -1
- package/dist/core/turns/types.d.ts +5 -0
- package/dist/core/turns/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/kortix.esm.min.js +25 -25
- package/dist/kortix.global.js +25 -25
- package/dist/node/server.d.ts.map +1 -1
- package/dist/node/server.js +14 -2
- package/dist/node/server.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +7 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/initial-session-pin.d.ts +17 -0
- package/dist/react/initial-session-pin.d.ts.map +1 -1
- package/dist/react/initial-session-pin.js +58 -0
- package/dist/react/initial-session-pin.js.map +1 -1
- package/dist/react/opencode.d.ts +6 -2
- package/dist/react/opencode.d.ts.map +1 -1
- package/dist/react/opencode.js +5 -1
- package/dist/react/opencode.js.map +1 -1
- package/dist/react/prefetch-session-start.d.ts +10 -4
- package/dist/react/prefetch-session-start.d.ts.map +1 -1
- package/dist/react/prefetch-session-start.js +10 -4
- package/dist/react/prefetch-session-start.js.map +1 -1
- package/dist/react/query-keys.d.ts +8 -2
- package/dist/react/query-keys.d.ts.map +1 -1
- package/dist/react/query-keys.js +8 -2
- package/dist/react/query-keys.js.map +1 -1
- package/dist/react/session-start-stash.d.ts +25 -0
- package/dist/react/session-start-stash.d.ts.map +1 -1
- package/dist/react/session-start-stash.js +34 -3
- package/dist/react/session-start-stash.js.map +1 -1
- package/dist/react/session-title-sync.d.ts +5 -1
- package/dist/react/session-title-sync.d.ts.map +1 -1
- package/dist/react/session-title-sync.js +16 -4
- package/dist/react/session-title-sync.js.map +1 -1
- package/dist/react/use-can.d.ts +105 -0
- package/dist/react/use-can.d.ts.map +1 -0
- package/dist/react/use-can.js +190 -0
- package/dist/react/use-can.js.map +1 -0
- package/dist/react/use-canonical-opencode-session.d.ts +6 -1
- package/dist/react/use-canonical-opencode-session.d.ts.map +1 -1
- package/dist/react/use-canonical-opencode-session.js +46 -3
- package/dist/react/use-canonical-opencode-session.js.map +1 -1
- package/dist/react/use-opencode-events/handle-event.d.ts +10 -0
- package/dist/react/use-opencode-events/handle-event.d.ts.map +1 -1
- package/dist/react/use-opencode-events/handle-event.js +76 -7
- package/dist/react/use-opencode-events/handle-event.js.map +1 -1
- package/dist/react/use-opencode-events/helpers.d.ts +21 -8
- package/dist/react/use-opencode-events/helpers.d.ts.map +1 -1
- package/dist/react/use-opencode-events/helpers.js +25 -26
- package/dist/react/use-opencode-events/helpers.js.map +1 -1
- package/dist/react/use-opencode-events/index.d.ts.map +1 -1
- package/dist/react/use-opencode-events/index.js +43 -26
- package/dist/react/use-opencode-events/index.js.map +1 -1
- package/dist/react/use-opencode-events/use-event-stream-refs.d.ts.map +1 -1
- package/dist/react/use-opencode-events/use-event-stream-refs.js +66 -3
- package/dist/react/use-opencode-events/use-event-stream-refs.js.map +1 -1
- package/dist/react/use-opencode-local.d.ts +17 -0
- package/dist/react/use-opencode-local.d.ts.map +1 -1
- package/dist/react/use-opencode-local.js +41 -7
- package/dist/react/use-opencode-local.js.map +1 -1
- package/dist/react/use-opencode-sessions/messages.d.ts +101 -1
- package/dist/react/use-opencode-sessions/messages.d.ts.map +1 -1
- package/dist/react/use-opencode-sessions/messages.js +481 -63
- package/dist/react/use-opencode-sessions/messages.js.map +1 -1
- package/dist/react/use-project-apps.d.ts +24 -1
- package/dist/react/use-project-apps.d.ts.map +1 -1
- package/dist/react/use-project-apps.js +5 -3
- package/dist/react/use-project-apps.js.map +1 -1
- package/dist/react/use-runtime-boot-stalled.d.ts +32 -0
- package/dist/react/use-runtime-boot-stalled.d.ts.map +1 -0
- package/dist/react/use-runtime-boot-stalled.js +48 -0
- package/dist/react/use-runtime-boot-stalled.js.map +1 -0
- package/dist/react/use-runtime-reconnect.d.ts +67 -1
- package/dist/react/use-runtime-reconnect.d.ts.map +1 -1
- package/dist/react/use-runtime-reconnect.js +116 -18
- package/dist/react/use-runtime-reconnect.js.map +1 -1
- package/dist/react/use-session-prompts.d.ts +113 -0
- package/dist/react/use-session-prompts.d.ts.map +1 -0
- package/dist/react/use-session-prompts.js +242 -0
- package/dist/react/use-session-prompts.js.map +1 -0
- package/dist/react/use-session-send.d.ts +169 -20
- package/dist/react/use-session-send.d.ts.map +1 -1
- package/dist/react/use-session-send.js +178 -29
- package/dist/react/use-session-send.js.map +1 -1
- package/dist/react/use-session-sync.d.ts +35 -2
- package/dist/react/use-session-sync.d.ts.map +1 -1
- package/dist/react/use-session-sync.js +46 -9
- package/dist/react/use-session-sync.js.map +1 -1
- package/dist/react/use-session-working.d.ts +57 -0
- package/dist/react/use-session-working.d.ts.map +1 -0
- package/dist/react/use-session-working.js +178 -0
- package/dist/react/use-session-working.js.map +1 -0
- package/dist/react/use-session.d.ts +122 -5
- package/dist/react/use-session.d.ts.map +1 -1
- package/dist/react/use-session.js +343 -69
- package/dist/react/use-session.js.map +1 -1
- package/package.json +2 -2
- package/dist/browser/stores/tab-store.d.ts +0 -91
- package/dist/browser/stores/tab-store.d.ts.map +0 -1
- package/dist/browser/stores/tab-store.js +0 -369
- package/dist/browser/stores/tab-store.js.map +0 -1
- package/dist/core/session/send-queue.d.ts +0 -70
- package/dist/core/session/send-queue.d.ts.map +0 -1
- package/dist/core/session/send-queue.js +0 -116
- package/dist/core/session/send-queue.js.map +0 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long an unanswered optimistic receipt may claim `working` on its own.
|
|
3
|
+
*
|
|
4
|
+
* Bounded because a send whose response never arrives must not latch the UI —
|
|
5
|
+
* the failure this whole phase exists to end. Released the moment a server
|
|
6
|
+
* source that CAN know about the send answers; the cap only covers the case
|
|
7
|
+
* where none ever does.
|
|
8
|
+
*/
|
|
9
|
+
export const OPTIMISTIC_RECEIPT_MAX_MS = 60_000;
|
|
10
|
+
/**
|
|
11
|
+
* How long a server observation keeps deciding anything.
|
|
12
|
+
*
|
|
13
|
+
* `useSessionWorking` polls `GET .../turn` every 5s while working and every
|
|
14
|
+
* 15s while idle, and react-query hands back the last SUCCESSFUL read for as
|
|
15
|
+
* long as the ones after it fail. Without this bound, one successful read of an
|
|
16
|
+
* open turn followed by a dev-api 503, an expired JWT, or a closed laptop lid
|
|
17
|
+
* pinned the composer on "working" for the lifetime of the tab — literally the
|
|
18
|
+
* reported bug. Three idle cadences is long enough that an ordinary retry or a
|
|
19
|
+
* backgrounded tab never trips it, and short enough that a dead poll stops
|
|
20
|
+
* being believed.
|
|
21
|
+
*/
|
|
22
|
+
export const SERVER_OBSERVATION_MAX_MS = 45_000;
|
|
23
|
+
/**
|
|
24
|
+
* How long a status frame keeps deciding anything.
|
|
25
|
+
*
|
|
26
|
+
* The stream needs its own bound for the same reason the server read does, and
|
|
27
|
+
* the failure it covers is the SAME one: the SSE stream reaches this tab
|
|
28
|
+
* through the API that serves `/turn`, so when that API drains they stop
|
|
29
|
+
* together. The stream does not go `null` when it stops — the last `busy`
|
|
30
|
+
* frame this tab ever observed stays in the store — so bounding only the
|
|
31
|
+
* server read moved the latch one source over instead of ending it.
|
|
32
|
+
*
|
|
33
|
+
* Equal to the server bound on purpose: while the API answers at all, a fresh
|
|
34
|
+
* `/turn` read decides and this bound is never reached; once it stops
|
|
35
|
+
* answering, both observations are equally blind and neither may stand.
|
|
36
|
+
*/
|
|
37
|
+
export const STREAM_OBSERVATION_MAX_MS = 45_000;
|
|
38
|
+
/**
|
|
39
|
+
* How long ONE reading of the durable inbox keeps deciding.
|
|
40
|
+
*
|
|
41
|
+
* Three cadences of the prompt list's own poll (3s), not of `/turn`'s. A
|
|
42
|
+
* reading says "the server still intends to run N rows", and that claim decays
|
|
43
|
+
* fast: the row it counted may already have been delivered, run, and finished.
|
|
44
|
+
* Held for the server bound instead, a finished prompt left the composer on
|
|
45
|
+
* Stop with its reply on screen for up to 45s.
|
|
46
|
+
*/
|
|
47
|
+
export const INBOX_OBSERVATION_MAX_MS = 10_000;
|
|
48
|
+
/**
|
|
49
|
+
* How long a stop this tab issued may bar a server read from reporting the
|
|
50
|
+
* turn it is ending.
|
|
51
|
+
*
|
|
52
|
+
* Bounded for the same reason the send receipt is, in the other direction: an
|
|
53
|
+
* abort whose acknowledgement never comes must not pin the composer on `idle`
|
|
54
|
+
* over a turn that is genuinely still running. Generous next to the ~1.6s a
|
|
55
|
+
* cancel takes to reach the daemon, and far below the send receipt's cap
|
|
56
|
+
* because the evidence arrives sooner.
|
|
57
|
+
*/
|
|
58
|
+
export const OPTIMISTIC_ABORT_MAX_MS = 15_000;
|
|
59
|
+
/**
|
|
60
|
+
* How many inbox rows are work the server still intends to run.
|
|
61
|
+
*
|
|
62
|
+
* `held` is excluded on purpose: it is the Stop button's own state — the row
|
|
63
|
+
* stays, deliberately not due, until the user sends something or presses "send
|
|
64
|
+
* now". Counting it would leave the composer on Stop with nothing running.
|
|
65
|
+
* `failed` is excluded for the same reason in the other direction: it is over.
|
|
66
|
+
*/
|
|
67
|
+
export function countLiveInboxPrompts(prompts) {
|
|
68
|
+
let live = 0;
|
|
69
|
+
for (const prompt of prompts) {
|
|
70
|
+
if (prompt.state === 'queued' || prompt.state === 'delivering')
|
|
71
|
+
live += 1;
|
|
72
|
+
else if (prompt.state === 'waiting' && prompt.reason !== 'held')
|
|
73
|
+
live += 1;
|
|
74
|
+
}
|
|
75
|
+
return live;
|
|
76
|
+
}
|
|
77
|
+
/** ms epoch, or null for an absent/unparseable instant. */
|
|
78
|
+
function instant(value) {
|
|
79
|
+
if (!value)
|
|
80
|
+
return null;
|
|
81
|
+
const parsed = Date.parse(value);
|
|
82
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The one place a session's working state is decided.
|
|
86
|
+
*
|
|
87
|
+
* Pure, so every rule below is asserted rather than observed by hand. No
|
|
88
|
+
* timers, no latch, no fabricated `SessionStatus` write — a caller that wants
|
|
89
|
+
* to re-evaluate simply calls again with a newer `nowMs`.
|
|
90
|
+
*
|
|
91
|
+
* Precedence:
|
|
92
|
+
*
|
|
93
|
+
* 1. The server is holding a turn open, and no NEWER stream frame contradicts
|
|
94
|
+
* it → working. `GET .../turn` reads the lifecycle authority rather than the
|
|
95
|
+
* ledger, so it is the strongest answer there is — but it is still a read
|
|
96
|
+
* taken at an instant, and an SSE frame from after that instant knows more.
|
|
97
|
+
* 2. The server holds durable inbox rows for this session → working. A prompt
|
|
98
|
+
* is accepted long before it is a turn: the row has to be drained, the box
|
|
99
|
+
* may have to resume (18.9s Daytona / 24.5s Platinum, measured), and only
|
|
100
|
+
* then does `beginSandboxTurn` run. "No turn is running" and "nothing of
|
|
101
|
+
* yours is in flight" stopped being the same statement when the inbox
|
|
102
|
+
* landed.
|
|
103
|
+
* 3. The server says no turns, and its read is at least as new as the stream's
|
|
104
|
+
* last frame → idle. (A dropped end-of-turn frame is exactly why the server
|
|
105
|
+
* has to be able to outrank a stale stream busy.)
|
|
106
|
+
* 4. The stream's own frame decides.
|
|
107
|
+
* 5. A live optimistic receipt → working.
|
|
108
|
+
* 6. Nothing observed → idle.
|
|
109
|
+
*
|
|
110
|
+
* With TWO cross-cutting freshness rules:
|
|
111
|
+
*
|
|
112
|
+
* * An observation that could not yet know about a live LOCAL action cannot
|
|
113
|
+
* answer for it — the send's floors (`SendReceipt.acceptedAtMs`) and the
|
|
114
|
+
* stop's (`AbortReceipt.settledAtMs`), all built the same way.
|
|
115
|
+
* * EVERY observation has a maximum age, and past it decides nothing at all in
|
|
116
|
+
* either direction: `SERVER_OBSERVATION_MAX_MS`, `STREAM_OBSERVATION_MAX_MS`,
|
|
117
|
+
* `INBOX_OBSERVATION_MAX_MS`. An observation the poll has failed to refresh
|
|
118
|
+
* for that long is not evidence, and standing on one is the latch. Callers
|
|
119
|
+
* must re-evaluate at those instants — see `workingExpiryAtMs`.
|
|
120
|
+
*/
|
|
121
|
+
export function projectWorking(inputs) {
|
|
122
|
+
const { optimistic, abort, inbox, server, stream, nowMs } = inputs;
|
|
123
|
+
const receiptLive = !!optimistic && nowMs - optimistic.atMs < OPTIMISTIC_RECEIPT_MAX_MS;
|
|
124
|
+
// TWO floors, because the two server-side observers have different knowledge.
|
|
125
|
+
//
|
|
126
|
+
// `GET .../turn` reads the control plane's ledger, and there is NO row in it
|
|
127
|
+
// until `POST .../prompts` returns. A read issued in that window is stamped
|
|
128
|
+
// after the receipt and still provably cannot see the send, so it may not
|
|
129
|
+
// answer for it at all — hence the infinity while `acceptedAtMs` is null.
|
|
130
|
+
//
|
|
131
|
+
// The SSE stream is the RUNTIME's own voice, and the store stamps a frame
|
|
132
|
+
// when this tab observed it — which only moves when the frame itself changes.
|
|
133
|
+
// So a frame stamped after the send is a NEW transition, not a stale reading,
|
|
134
|
+
// and it is the freshest thing there is. Blocking it too would leave a
|
|
135
|
+
// command's receipt (nothing ever "accepts" a command) claiming `working` for
|
|
136
|
+
// a full minute after its turn had visibly ended.
|
|
137
|
+
const serverFloor = receiptLive
|
|
138
|
+
? (optimistic.acceptedAtMs ?? Number.POSITIVE_INFINITY)
|
|
139
|
+
: Number.NEGATIVE_INFINITY;
|
|
140
|
+
const streamFloor = receiptLive ? optimistic.atMs : Number.NEGATIVE_INFINITY;
|
|
141
|
+
// The stop's floor, built exactly like the send's. A `/turn` read issued
|
|
142
|
+
// before the cancel was ACKNOWLEDGED still reports the turn the cancel is
|
|
143
|
+
// ending, so it may not report `working` from it — the read is honest and
|
|
144
|
+
// out of date. Infinite while the acknowledgement is outstanding, and gone
|
|
145
|
+
// entirely once the receipt ages out, so a cancel nobody answers cannot pin
|
|
146
|
+
// the composer on idle over a turn that is really still running.
|
|
147
|
+
const abortLive = !!abort && nowMs - abort.atMs < OPTIMISTIC_ABORT_MAX_MS;
|
|
148
|
+
const abortFloor = abortLive
|
|
149
|
+
? (abort.settledAtMs ?? Number.POSITIVE_INFINITY)
|
|
150
|
+
: Number.NEGATIVE_INFINITY;
|
|
151
|
+
const serverFresh = !!server && nowMs - server.atMs <= SERVER_OBSERVATION_MAX_MS;
|
|
152
|
+
const streamFresh = !!stream && nowMs - stream.atMs <= STREAM_OBSERVATION_MAX_MS;
|
|
153
|
+
const inboxFresh = !!inbox && nowMs - inbox.atMs <= INBOX_OBSERVATION_MAX_MS;
|
|
154
|
+
const openTurn = serverFresh ? server.turns[0] : undefined;
|
|
155
|
+
const serverOpenTurnToken = openTurn?.turn_token ?? null;
|
|
156
|
+
// A turn the authority is holding open, unless the stream has since said the
|
|
157
|
+
// session went idle. The stream frame is newer BY OBSERVATION, and the daemon
|
|
158
|
+
// relays `turn_end` to the control plane at the same moment the frame is
|
|
159
|
+
// emitted — so a fresher idle frame means this read is simply out of date.
|
|
160
|
+
if (openTurn && server.atMs >= abortFloor && (!stream || server.atMs >= stream.atMs)) {
|
|
161
|
+
return {
|
|
162
|
+
state: 'working',
|
|
163
|
+
source: 'server',
|
|
164
|
+
turnId: openTurn.message_id,
|
|
165
|
+
since: instant(openTurn.started_at) ?? server.atMs,
|
|
166
|
+
serverOpenTurnToken,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// Durable rows outrank an idle read: the read is right that no TURN is
|
|
170
|
+
// running and wrong that nothing is happening.
|
|
171
|
+
if (inboxFresh && inbox.pending > 0) {
|
|
172
|
+
return {
|
|
173
|
+
state: 'working',
|
|
174
|
+
source: 'server',
|
|
175
|
+
turnId: null,
|
|
176
|
+
since: inbox.atMs,
|
|
177
|
+
serverOpenTurnToken,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const serverAnswers = serverFresh && !openTurn && server.atMs >= serverFloor;
|
|
181
|
+
const streamAnswers = streamFresh && stream.atMs >= streamFloor;
|
|
182
|
+
if (serverAnswers && (!stream || server.atMs >= stream.atMs)) {
|
|
183
|
+
return {
|
|
184
|
+
state: 'idle',
|
|
185
|
+
source: 'server',
|
|
186
|
+
turnId: null,
|
|
187
|
+
since: instant(server.lastEnded?.ended_at) ?? server.atMs,
|
|
188
|
+
serverOpenTurnToken,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (streamAnswers) {
|
|
192
|
+
return {
|
|
193
|
+
state: stream.type === 'idle' ? 'idle' : 'working',
|
|
194
|
+
source: 'stream',
|
|
195
|
+
turnId: null,
|
|
196
|
+
since: stream.atMs,
|
|
197
|
+
serverOpenTurnToken,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (receiptLive) {
|
|
201
|
+
return {
|
|
202
|
+
state: 'working',
|
|
203
|
+
source: 'optimistic',
|
|
204
|
+
turnId: optimistic.messageId,
|
|
205
|
+
since: optimistic.atMs,
|
|
206
|
+
serverOpenTurnToken,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Nothing has answered. Idle is the only honest default: a session is not
|
|
210
|
+
// working because a page failed to ask.
|
|
211
|
+
const newest = server && stream
|
|
212
|
+
? server.atMs >= stream.atMs
|
|
213
|
+
? { source: 'server', atMs: server.atMs }
|
|
214
|
+
: { source: 'stream', atMs: stream.atMs }
|
|
215
|
+
: server
|
|
216
|
+
? { source: 'server', atMs: server.atMs }
|
|
217
|
+
: stream
|
|
218
|
+
? { source: 'stream', atMs: stream.atMs }
|
|
219
|
+
: { source: 'server', atMs: nowMs };
|
|
220
|
+
return {
|
|
221
|
+
state: 'idle',
|
|
222
|
+
source: newest.source,
|
|
223
|
+
turnId: null,
|
|
224
|
+
since: newest.atMs,
|
|
225
|
+
serverOpenTurnToken,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The next instant at which `projectWorking` could answer differently on its
|
|
230
|
+
* own — i.e. when the OLDEST-lived input ages past its bound.
|
|
231
|
+
*
|
|
232
|
+
* The projection is pure and moves with `nowMs`, which means something has to
|
|
233
|
+
* ask it again at that instant or the bound is never applied. Nothing did: the
|
|
234
|
+
* hook re-renders when react-query hands it new `data`, and a run of failed
|
|
235
|
+
* reads hands back the SAME retained `data` — the observer does not even
|
|
236
|
+
* notify. `SERVER_OBSERVATION_MAX_MS` was therefore evaluated only on renders
|
|
237
|
+
* that happened for some unrelated reason, and in the exact outage it was
|
|
238
|
+
* written for (one success, then a run of 503s) there are none.
|
|
239
|
+
*
|
|
240
|
+
* `null` when nothing is left to expire. Deadlines already in the past are
|
|
241
|
+
* skipped, so re-arming from the timer this returns terminates.
|
|
242
|
+
*/
|
|
243
|
+
export function workingExpiryAtMs(inputs) {
|
|
244
|
+
const { optimistic, abort, inbox, server, stream, nowMs } = inputs;
|
|
245
|
+
const deadlines = [
|
|
246
|
+
optimistic ? optimistic.atMs + OPTIMISTIC_RECEIPT_MAX_MS : null,
|
|
247
|
+
abort ? abort.atMs + OPTIMISTIC_ABORT_MAX_MS : null,
|
|
248
|
+
inbox ? inbox.atMs + INBOX_OBSERVATION_MAX_MS : null,
|
|
249
|
+
server ? server.atMs + SERVER_OBSERVATION_MAX_MS : null,
|
|
250
|
+
stream ? stream.atMs + STREAM_OBSERVATION_MAX_MS : null,
|
|
251
|
+
];
|
|
252
|
+
let next = null;
|
|
253
|
+
for (const deadline of deadlines) {
|
|
254
|
+
if (deadline === null || deadline <= nowMs)
|
|
255
|
+
continue;
|
|
256
|
+
if (next === null || deadline < next)
|
|
257
|
+
next = deadline;
|
|
258
|
+
}
|
|
259
|
+
return next;
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=working.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"working.js","sourceRoot":"","sources":["../../../src/core/session/working.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,12 +19,22 @@ export interface SessionSyncPage {
|
|
|
19
19
|
messages: SessionSyncMessage[];
|
|
20
20
|
nextCursor?: string;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Transcript state, and nothing else.
|
|
24
|
+
*
|
|
25
|
+
* This snapshot used to carry `isPromptObservedBusy` — a busy OPINION inferred
|
|
26
|
+
* from silence by a phase machine with a stall timer, a retry budget and an
|
|
27
|
+
* epoch counter. It was one of four disagreeing answers to "is this session
|
|
28
|
+
* working?", and the one that latched: every signal that could release it
|
|
29
|
+
* (`session.idle`, a status frame, the poll) can be lost, and losing them left
|
|
30
|
+
* the composer pinned on "stop" until the user reloaded. The answer now comes
|
|
31
|
+
* from `projectWorking` over the server's own turn authority
|
|
32
|
+
* (`core/session/working.ts`), so this controller holds no opinion about it.
|
|
33
|
+
*/
|
|
22
34
|
export interface SessionSyncSnapshot {
|
|
23
35
|
freshness: SessionSyncFreshness;
|
|
24
36
|
hasOlder: boolean;
|
|
25
37
|
isLoadingOlder: boolean;
|
|
26
|
-
/** True while an accepted REST prompt has not reached stable runtime idle. */
|
|
27
|
-
isPromptObservedBusy?: boolean;
|
|
28
38
|
}
|
|
29
39
|
export interface SessionSyncScheduler {
|
|
30
40
|
now: () => number;
|
|
@@ -47,9 +57,23 @@ export interface SessionSyncControllerOptions {
|
|
|
47
57
|
limit: number;
|
|
48
58
|
before?: string;
|
|
49
59
|
}) => Promise<SessionSyncPage>;
|
|
60
|
+
/**
|
|
61
|
+
* @deprecated Never called. The liveness poll no longer reads status: `GET
|
|
62
|
+
* .../turn` is the status authority, and this controller's own `setBusy` is
|
|
63
|
+
* already driven FROM that projection, so a status read here could only
|
|
64
|
+
* confirm or latch — never correct. Retained (ignored) because 0.12.8
|
|
65
|
+
* published it on this options type; removed in the next major.
|
|
66
|
+
*/
|
|
50
67
|
loadStatus?: () => Promise<SessionStatus>;
|
|
51
68
|
hydrate: (messages: SessionSyncMessage[]) => void;
|
|
52
69
|
markLoaded: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* @deprecated Never called. Writing a REST poll's answer into the slot SSE
|
|
72
|
+
* status frames land in made a poll indistinguishable from the runtime's own
|
|
73
|
+
* voice, and re-stamped the stream observation on every tick so its age
|
|
74
|
+
* bound was never reached. Retained (ignored) because 0.12.8 published it;
|
|
75
|
+
* removed in the next major.
|
|
76
|
+
*/
|
|
53
77
|
setStatus?: (status: SessionStatus) => void;
|
|
54
78
|
onTelemetry?: (event: SessionSyncTelemetryEvent) => void;
|
|
55
79
|
scheduler?: SessionSyncScheduler;
|
|
@@ -81,8 +105,6 @@ export declare class SessionSyncController {
|
|
|
81
105
|
private tailRequest;
|
|
82
106
|
private olderRequest;
|
|
83
107
|
private livenessTimer;
|
|
84
|
-
private promptSettlementTimer;
|
|
85
|
-
private promptObservationPhase;
|
|
86
108
|
private lastActivityAt;
|
|
87
109
|
private listeners;
|
|
88
110
|
private destroyed;
|
|
@@ -94,19 +116,13 @@ export declare class SessionSyncController {
|
|
|
94
116
|
loadOlder: () => Promise<void>;
|
|
95
117
|
noteActivity(): void;
|
|
96
118
|
/**
|
|
97
|
-
*
|
|
119
|
+
* Switch the liveness poll on or off.
|
|
98
120
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
121
|
+
* NOT an opinion about whether the session is working — that answer belongs
|
|
122
|
+
* to `projectWorking` alone. This only says whether anyone still needs the
|
|
123
|
+
* transcript refreshed behind the SSE stream: a caller passes the working
|
|
124
|
+
* state it already has, and the last consumer leaving passes `false`.
|
|
102
125
|
*/
|
|
103
|
-
beginPromptObservation(): void;
|
|
104
|
-
/** Observe an authoritative runtime status from SSE or status reconciliation. */
|
|
105
|
-
observePromptStatus(status: SessionStatus): void;
|
|
106
|
-
/** Mark assistant output as proof that the accepted prompt started. */
|
|
107
|
-
observePromptActivity(): void;
|
|
108
|
-
/** End observation after rejection, cancellation, or a terminal runtime error. */
|
|
109
|
-
endPromptObservation(): void;
|
|
110
126
|
setBusy(isBusy: boolean): void;
|
|
111
127
|
destroy(): void;
|
|
112
128
|
private loadTail;
|
|
@@ -115,13 +131,12 @@ export declare class SessionSyncController {
|
|
|
115
131
|
private loadCompleteTurn;
|
|
116
132
|
private rememberUserMessages;
|
|
117
133
|
private checkLiveness;
|
|
118
|
-
|
|
119
|
-
private
|
|
134
|
+
/** Resolve when `work` settles, or when `timeoutMs` elapses — whichever first. */
|
|
135
|
+
private raceDeadline;
|
|
120
136
|
private setCursor;
|
|
121
137
|
private stopLivenessTimer;
|
|
122
|
-
private
|
|
123
|
-
private
|
|
124
|
-
private clearPromptSettlementTimer;
|
|
138
|
+
private startTimer;
|
|
139
|
+
private cancelTimer;
|
|
125
140
|
private update;
|
|
126
141
|
}
|
|
127
142
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sync-controller.d.ts","sourceRoot":"","sources":["../../../src/core/session-sync/session-sync-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE/E;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"session-sync-controller.d.ts","sourceRoot":"","sources":["../../../src/core/session-sync/session-sync-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,27 +31,12 @@ export function loadHttpSessionHistory(options) {
|
|
|
31
31
|
return loadCompleteSessionHistory(createHttpSessionSyncPageLoader(options));
|
|
32
32
|
}
|
|
33
33
|
export function createHttpSessionSyncController(options) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const request = async (path) => {
|
|
38
|
-
const token = await options.getToken?.();
|
|
39
|
-
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
40
|
-
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
41
|
-
});
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`Session synchronization failed: ${response.status}`);
|
|
44
|
-
}
|
|
45
|
-
return response;
|
|
46
|
-
};
|
|
34
|
+
// No `loadStatus` any more: the controller never reads status, so the
|
|
35
|
+
// `/session/status` request this used to build was dead weight on every
|
|
36
|
+
// liveness tick. The transcript tail is the whole of this controller's job.
|
|
47
37
|
return new SessionSyncController({
|
|
48
38
|
...options,
|
|
49
|
-
loadPage,
|
|
50
|
-
loadStatus: async () => {
|
|
51
|
-
const response = await request('/session/status');
|
|
52
|
-
const statuses = (await response.json());
|
|
53
|
-
return statuses[options.sessionId] ?? { type: 'idle' };
|
|
54
|
-
},
|
|
39
|
+
loadPage: createHttpSessionSyncPageLoader(options),
|
|
55
40
|
});
|
|
56
41
|
}
|
|
57
42
|
export async function loadCompleteSessionHistory(loadPage) {
|
|
@@ -82,7 +67,6 @@ const defaultScheduler = {
|
|
|
82
67
|
setTimeout: (handler, timeoutMs) => setTimeout(handler, timeoutMs),
|
|
83
68
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
84
69
|
};
|
|
85
|
-
const PROMPT_IDLE_SETTLEMENT_MS = 500;
|
|
86
70
|
/**
|
|
87
71
|
* Owns bounded session history synchronization without depending on React,
|
|
88
72
|
* Zustand, IndexedDB, or a specific HTTP client.
|
|
@@ -95,7 +79,6 @@ export class SessionSyncController {
|
|
|
95
79
|
freshness: 'idle',
|
|
96
80
|
hasOlder: false,
|
|
97
81
|
isLoadingOlder: false,
|
|
98
|
-
isPromptObservedBusy: false,
|
|
99
82
|
};
|
|
100
83
|
nextCursor;
|
|
101
84
|
knownUserMessageIds = new Set();
|
|
@@ -103,8 +86,6 @@ export class SessionSyncController {
|
|
|
103
86
|
tailRequest;
|
|
104
87
|
olderRequest;
|
|
105
88
|
livenessTimer;
|
|
106
|
-
promptSettlementTimer;
|
|
107
|
-
promptObservationPhase = 'idle';
|
|
108
89
|
lastActivityAt;
|
|
109
90
|
listeners = new Set();
|
|
110
91
|
destroyed = false;
|
|
@@ -166,43 +147,13 @@ export class SessionSyncController {
|
|
|
166
147
|
}
|
|
167
148
|
}
|
|
168
149
|
/**
|
|
169
|
-
*
|
|
150
|
+
* Switch the liveness poll on or off.
|
|
170
151
|
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
152
|
+
* NOT an opinion about whether the session is working — that answer belongs
|
|
153
|
+
* to `projectWorking` alone. This only says whether anyone still needs the
|
|
154
|
+
* transcript refreshed behind the SSE stream: a caller passes the working
|
|
155
|
+
* state it already has, and the last consumer leaving passes `false`.
|
|
174
156
|
*/
|
|
175
|
-
beginPromptObservation() {
|
|
176
|
-
this.clearPromptSettlementTimer();
|
|
177
|
-
this.promptObservationPhase = 'awaiting-work';
|
|
178
|
-
this.update({ isPromptObservedBusy: true });
|
|
179
|
-
this.setBusy(true);
|
|
180
|
-
}
|
|
181
|
-
/** Observe an authoritative runtime status from SSE or status reconciliation. */
|
|
182
|
-
observePromptStatus(status) {
|
|
183
|
-
if (this.promptObservationPhase === 'idle')
|
|
184
|
-
return;
|
|
185
|
-
if (status.type !== 'idle') {
|
|
186
|
-
this.markPromptRunning();
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (this.promptObservationPhase === 'awaiting-work')
|
|
190
|
-
return;
|
|
191
|
-
this.schedulePromptSettlement();
|
|
192
|
-
}
|
|
193
|
-
/** Mark assistant output as proof that the accepted prompt started. */
|
|
194
|
-
observePromptActivity() {
|
|
195
|
-
if (this.promptObservationPhase === 'idle')
|
|
196
|
-
return;
|
|
197
|
-
this.markPromptRunning();
|
|
198
|
-
}
|
|
199
|
-
/** End observation after rejection, cancellation, or a terminal runtime error. */
|
|
200
|
-
endPromptObservation() {
|
|
201
|
-
this.clearPromptSettlementTimer();
|
|
202
|
-
this.promptObservationPhase = 'idle';
|
|
203
|
-
this.update({ isPromptObservedBusy: false });
|
|
204
|
-
this.setBusy(false);
|
|
205
|
-
}
|
|
206
157
|
setBusy(isBusy) {
|
|
207
158
|
if (!isBusy) {
|
|
208
159
|
this.stopLivenessTimer();
|
|
@@ -216,7 +167,6 @@ export class SessionSyncController {
|
|
|
216
167
|
destroy() {
|
|
217
168
|
this.destroyed = true;
|
|
218
169
|
this.stopLivenessTimer();
|
|
219
|
-
this.clearPromptSettlementTimer();
|
|
220
170
|
this.listeners.clear();
|
|
221
171
|
}
|
|
222
172
|
async loadTail(reason) {
|
|
@@ -225,9 +175,6 @@ export class SessionSyncController {
|
|
|
225
175
|
const page = await this.loadCompleteTurn(firstPage, 'tail', reason);
|
|
226
176
|
if (this.destroyed)
|
|
227
177
|
return;
|
|
228
|
-
if (this.containsNewPromptReply(page.messages)) {
|
|
229
|
-
this.observePromptActivity();
|
|
230
|
-
}
|
|
231
178
|
this.rememberUserMessages(page.messages);
|
|
232
179
|
this.options.hydrate(page.messages);
|
|
233
180
|
if (!this.olderHistoryStarted) {
|
|
@@ -316,34 +263,38 @@ export class SessionSyncController {
|
|
|
316
263
|
if (this.destroyed || this.scheduler.now() - this.lastActivityAt <= this.livenessIntervalMs) {
|
|
317
264
|
return;
|
|
318
265
|
}
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
266
|
+
// Reconcile the TAIL, and nothing else. This is the repair for a dropped
|
|
267
|
+
// SSE stream: the transcript catches up on messages the stream never
|
|
268
|
+
// delivered.
|
|
269
|
+
//
|
|
270
|
+
// A status half used to follow it, reading the runtime over REST and
|
|
271
|
+
// writing the answer into the same slot SSE frames land in. It is gone:
|
|
272
|
+
// `GET .../turn` is the status authority now, and `setBusy` — the switch
|
|
273
|
+
// that decides whether this poll runs at all — is already driven FROM that
|
|
274
|
+
// projection, so a fourth stamped input here could only confirm or latch,
|
|
275
|
+
// never correct.
|
|
276
|
+
//
|
|
277
|
+
// The wait stays bounded: a read proxied to the sandbox can park
|
|
278
|
+
// indefinitely (a wedged opencode never answers and never errors), and the
|
|
279
|
+
// poll must keep its cadence rather than stall on one request.
|
|
280
|
+
await this.raceDeadline(this.reconcile('poll'), this.livenessIntervalMs);
|
|
324
281
|
this.lastActivityAt = this.scheduler.now();
|
|
325
282
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
this.options.setStatus(await this.options.loadStatus());
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
345
|
-
// The next liveness interval retries both bounded reads.
|
|
346
|
-
}
|
|
283
|
+
/** Resolve when `work` settles, or when `timeoutMs` elapses — whichever first. */
|
|
284
|
+
raceDeadline(work, timeoutMs) {
|
|
285
|
+
return new Promise((resolve) => {
|
|
286
|
+
let handle;
|
|
287
|
+
let settled = false;
|
|
288
|
+
const finish = () => {
|
|
289
|
+
if (settled)
|
|
290
|
+
return;
|
|
291
|
+
settled = true;
|
|
292
|
+
this.cancelTimer(handle);
|
|
293
|
+
resolve();
|
|
294
|
+
};
|
|
295
|
+
handle = this.startTimer(finish, timeoutMs);
|
|
296
|
+
void work.then(finish, finish);
|
|
297
|
+
});
|
|
347
298
|
}
|
|
348
299
|
setCursor(cursor) {
|
|
349
300
|
this.nextCursor = cursor;
|
|
@@ -355,44 +306,26 @@ export class SessionSyncController {
|
|
|
355
306
|
this.scheduler.clearInterval(this.livenessTimer);
|
|
356
307
|
this.livenessTimer = undefined;
|
|
357
308
|
}
|
|
358
|
-
|
|
359
|
-
this.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
schedulePromptSettlement() {
|
|
363
|
-
if (this.promptObservationPhase === 'settling')
|
|
364
|
-
return;
|
|
365
|
-
this.clearPromptSettlementTimer();
|
|
366
|
-
this.promptObservationPhase = 'settling';
|
|
367
|
-
const settle = () => {
|
|
368
|
-
this.promptSettlementTimer = undefined;
|
|
369
|
-
if (this.destroyed || this.promptObservationPhase !== 'settling')
|
|
370
|
-
return;
|
|
371
|
-
this.promptObservationPhase = 'idle';
|
|
372
|
-
this.update({ isPromptObservedBusy: false });
|
|
373
|
-
this.stopLivenessTimer();
|
|
374
|
-
};
|
|
375
|
-
this.promptSettlementTimer = this.scheduler.setTimeout
|
|
376
|
-
? this.scheduler.setTimeout(settle, PROMPT_IDLE_SETTLEMENT_MS)
|
|
377
|
-
: setTimeout(settle, PROMPT_IDLE_SETTLEMENT_MS);
|
|
309
|
+
startTimer(handler, delayMs) {
|
|
310
|
+
return this.scheduler.setTimeout
|
|
311
|
+
? this.scheduler.setTimeout(handler, delayMs)
|
|
312
|
+
: setTimeout(handler, delayMs);
|
|
378
313
|
}
|
|
379
|
-
|
|
380
|
-
if (
|
|
314
|
+
cancelTimer(handle) {
|
|
315
|
+
if (handle === undefined)
|
|
381
316
|
return;
|
|
382
317
|
if (this.scheduler.clearTimeout) {
|
|
383
|
-
this.scheduler.clearTimeout(
|
|
318
|
+
this.scheduler.clearTimeout(handle);
|
|
384
319
|
}
|
|
385
320
|
else {
|
|
386
|
-
clearTimeout(
|
|
321
|
+
clearTimeout(handle);
|
|
387
322
|
}
|
|
388
|
-
this.promptSettlementTimer = undefined;
|
|
389
323
|
}
|
|
390
324
|
update(next) {
|
|
391
325
|
const snapshot = { ...this.snapshot, ...next };
|
|
392
326
|
if (snapshot.freshness === this.snapshot.freshness &&
|
|
393
327
|
snapshot.hasOlder === this.snapshot.hasOlder &&
|
|
394
|
-
snapshot.isLoadingOlder === this.snapshot.isLoadingOlder
|
|
395
|
-
snapshot.isPromptObservedBusy === this.snapshot.isPromptObservedBusy) {
|
|
328
|
+
snapshot.isLoadingOlder === this.snapshot.isLoadingOlder) {
|
|
396
329
|
return;
|
|
397
330
|
}
|
|
398
331
|
this.snapshot = snapshot;
|