@linxin666/dsh-remote-web-ui 0.2.6 → 0.2.8
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.i18n.yaml +2 -2
- package/README.md +8 -1
- package/README.zh.md +2 -1
- package/lib/client.js +55 -10
- package/lib/client.js.map +1 -1
- package/lib/index.js +12 -8
- package/lib/mobile.js +37 -18
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/FenceNotice.d.ts +3 -2
- package/lib/types/client/FenceNotice.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +8 -8
- package/lib/types/client/RemotePanel.js +3 -3
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +2 -2
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +2 -2
- package/lib/types/client/UpdatePanel.js +1 -1
- package/lib/types/client/deep-link.js +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +45 -23
- package/lib/types/client/pair-api.d.ts +6 -0
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +11 -0
- package/lib/types/client/remote-channel.d.ts +25 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +24 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/update-api.d.ts.map +1 -1
- package/lib/types/gate.js +1 -1
- package/lib/types/index.js +15 -15
- package/lib/types/invariant.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +1 -1
- package/lib/types/mobile/PairRequiredView.js +1 -1
- package/lib/types/mobile/PairRequiredView.test.js +1 -1
- package/lib/types/mobile/api.js +1 -1
- package/lib/types/mobile/index.js +5 -5
- package/lib/types/mobile/markdown.test.js +1 -1
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +5 -3
- package/lib/types/mobile/messages.test.js +21 -1
- package/lib/types/mobile/mobile-theme.test.js +2 -2
- package/lib/types/mobile/mux.d.ts +7 -4
- package/lib/types/mobile/mux.d.ts.map +1 -1
- package/lib/types/mobile/mux.js +44 -16
- package/lib/types/mobile/mux.test.js +58 -1
- package/lib/types/mobile/rpc.d.ts.map +1 -1
- package/lib/types/mobile/theme-toggle.js +1 -1
- package/lib/types/mobile/views/App.js +7 -7
- package/lib/types/mobile/views/App.test.js +2 -2
- package/lib/types/mobile/views/ChatView.js +6 -6
- package/lib/types/mobile/views/ChatView.test.js +3 -3
- package/lib/types/mobile/views/SessionListView.js +3 -3
- package/lib/types/mobile/views/SessionListView.test.js +2 -2
- package/lib/types/mobile/views/WorkspaceView.js +3 -3
- package/lib/types/mobile/views/WorkspaceView.test.js +3 -3
- package/lib/types/mobile-api.d.ts +1 -1
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +2 -2
- package/lib/types/pairing-access.d.ts.map +1 -1
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.persist.test.js +2 -2
- package/lib/types/remote-api.js +6 -6
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/routes.d.ts +9 -7
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +8 -5
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/update-routes.d.ts +2 -2
- package/lib/types/update-routes.d.ts.map +1 -1
- package/lib/types/update-routes.js +1 -1
- package/package.json +15 -12
- package/src/client/FenceNotice.tsx +3 -2
- package/src/client/RemoteEntry.tsx +3 -2
- package/src/client/UpdateEntry.tsx +1 -0
- package/src/client/index.ts +40 -15
- package/src/client/pair-api.ts +16 -0
- package/src/client/remote-channel.ts +42 -0
- package/src/index.ts +1 -1
- package/src/mobile/messages.test.ts +23 -0
- package/src/mobile/messages.ts +5 -3
- package/src/mobile/mux.test.ts +64 -0
- package/src/mobile/mux.ts +44 -17
- package/src/routes.ts +7 -2
|
@@ -470,7 +470,8 @@ function applyTurnEnd(state, event) {
|
|
|
470
470
|
...message,
|
|
471
471
|
...(wasPending ? { pending: false } : {}),
|
|
472
472
|
...(failed ? { failed: true } : {}),
|
|
473
|
-
|
|
473
|
+
// Preserve each step's own final-event seq. Collapsing every message
|
|
474
|
+
// onto turn/end makes same-turn ordering depend on arbitrary ids.
|
|
474
475
|
time: event.time,
|
|
475
476
|
});
|
|
476
477
|
}
|
|
@@ -542,10 +543,11 @@ function snapshotOf(state) {
|
|
|
542
543
|
for (let index = 1; index < out.length; index += 1) {
|
|
543
544
|
const prev = out[index - 1];
|
|
544
545
|
const current = out[index];
|
|
545
|
-
if (prev.seq > current.seq
|
|
546
|
+
if (prev.seq > current.seq) {
|
|
546
547
|
ordered = false;
|
|
547
548
|
break;
|
|
548
549
|
}
|
|
549
550
|
}
|
|
550
|
-
|
|
551
|
+
// Array.sort is stable: equal-seq rows keep their event insertion order.
|
|
552
|
+
return ordered ? out : out.sort((a, b) => a.seq - b.seq);
|
|
551
553
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** foldEvents: message-list folding from a session event stream. */
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { EventFolder, foldEvents } from
|
|
3
|
+
import { EventFolder, foldEvents } from "./messages.js";
|
|
4
4
|
/** Assemble one event with an auto-incrementing seq / time. */
|
|
5
5
|
function makeEvent(type, data, seq, time = seq * 1_000) {
|
|
6
6
|
return { type, seq, time, data };
|
|
@@ -152,6 +152,26 @@ describe('foldEvents', () => {
|
|
|
152
152
|
expect(assistant?.failed).toBeFalsy();
|
|
153
153
|
expect(assistant?.pending).toBeFalsy();
|
|
154
154
|
});
|
|
155
|
+
it('keeps same-turn steps in final-event order after turn/end', () => {
|
|
156
|
+
const result = foldEvents([
|
|
157
|
+
makeEvent('user/message', userMessageData('u-1', 'multi-step'), 0),
|
|
158
|
+
makeEvent('assistant/message', assistantMessageData('step-z', 0, 0, 'first'), 2),
|
|
159
|
+
makeEvent('assistant/message', assistantMessageData('step-a', 0, 1, 'second'), 4),
|
|
160
|
+
makeEvent('assistant/message', assistantMessageData('step-m', 0, 2, 'third'), 6),
|
|
161
|
+
makeEvent('turn/end', { turn: 0, reason: { kind: 'completed' } }, 7),
|
|
162
|
+
]);
|
|
163
|
+
const assistants = result.filter(message => message.kind === 'assistant');
|
|
164
|
+
expect(assistants.map(message => message.id)).toEqual(['step-z', 'step-a', 'step-m']);
|
|
165
|
+
expect(assistants.map(message => message.seq)).toEqual([2, 4, 6]);
|
|
166
|
+
expect(assistants.every(message => message.pending !== true)).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
it('keeps stable insertion order when legacy rows share a seq', () => {
|
|
169
|
+
const folder = new EventFolder([
|
|
170
|
+
{ id: 'z-last-lexically', kind: 'assistant', text: 'first', seq: 5, time: 5_000 },
|
|
171
|
+
{ id: 'a-first-lexically', kind: 'assistant', text: 'second', seq: 5, time: 5_000 },
|
|
172
|
+
]);
|
|
173
|
+
expect(folder.snapshot().map(message => message.id)).toEqual(['z-last-lexically', 'a-first-lexically']);
|
|
174
|
+
});
|
|
155
175
|
it('is idempotent: applying the same batch twice yields an identical list', () => {
|
|
156
176
|
const events = [
|
|
157
177
|
makeEvent('user/message', userMessageData('u-1', 'hi'), 0),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
/** mobile-theme: light default, persisted dark toggle, document wiring. */
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
-
import { getMobileTheme, setMobileTheme, subscribeMobileTheme, toggleMobileTheme, } from
|
|
4
|
+
import { getMobileTheme, setMobileTheme, subscribeMobileTheme, toggleMobileTheme, } from "./mobile-theme.js";
|
|
5
5
|
const STORAGE_KEY = 'dsh.remote.theme';
|
|
6
6
|
/** jsdom in this setup ships a bare localStorage object; install a real fake. */
|
|
7
7
|
function makeStorage() {
|
|
@@ -54,7 +54,7 @@ describe('mobile-theme', () => {
|
|
|
54
54
|
it('boot reads the stored theme and initMobileTheme applies it', async () => {
|
|
55
55
|
storage.setItem(STORAGE_KEY, 'dark');
|
|
56
56
|
vi.resetModules();
|
|
57
|
-
const fresh = await import(
|
|
57
|
+
const fresh = await import("./mobile-theme.js");
|
|
58
58
|
expect(fresh.getMobileTheme()).toBe('dark');
|
|
59
59
|
fresh.initMobileTheme();
|
|
60
60
|
expect(document.documentElement.dataset.theme).toBe('dark');
|
|
@@ -35,9 +35,9 @@ export interface MuxClientOptions {
|
|
|
35
35
|
* the ordinary HTTP channel (unaffected by SSE-impairing tunnels).
|
|
36
36
|
*/
|
|
37
37
|
pollLatest?: (sessionId: string) => Promise<HistoryPage>;
|
|
38
|
-
/**
|
|
38
|
+
/** Initial poll cadence while SSE is stalled (default 3000 ms). Empty polls back off to 60000 ms. */
|
|
39
39
|
pollIntervalMs?: number;
|
|
40
|
-
/**
|
|
40
|
+
/** Initial SSE stall window (default 12000 ms); a previously-live stream gets three windows before fallback. */
|
|
41
41
|
stallThresholdMs?: number;
|
|
42
42
|
/** Clock seam for tests (defaults to Date.now). */
|
|
43
43
|
now?: () => number;
|
|
@@ -72,8 +72,8 @@ export declare class MuxClient {
|
|
|
72
72
|
private lastDataAt;
|
|
73
73
|
/**
|
|
74
74
|
* Whether the SSE channel has ever delivered a frame in this stream (a
|
|
75
|
-
* delivered frame proves the tunnel
|
|
76
|
-
*
|
|
75
|
+
* delivered frame proves the tunnel can forward SSE; sustained silence may
|
|
76
|
+
* still mean a suspended mobile tunnel, so polling re-arms after 3 windows).
|
|
77
77
|
*/
|
|
78
78
|
private sseAlive;
|
|
79
79
|
/** Per-session highest event seq already emitted, for poll dedup. */
|
|
@@ -83,6 +83,8 @@ export declare class MuxClient {
|
|
|
83
83
|
private polling;
|
|
84
84
|
/** Epoch ms of the next due poll while polling (kept on the same tick timer). */
|
|
85
85
|
private nextPollAt;
|
|
86
|
+
/** Adaptive delay: productive polls reset it; empty/error polls add one base interval up to one minute. */
|
|
87
|
+
private pollDelayMs;
|
|
86
88
|
/**
|
|
87
89
|
* @param url - the mobile events endpoint (browser-relative).
|
|
88
90
|
* @param options - seams.
|
|
@@ -110,6 +112,7 @@ export declare class MuxClient {
|
|
|
110
112
|
private startTick;
|
|
111
113
|
private stopTick;
|
|
112
114
|
private tick;
|
|
115
|
+
private isSseStalled;
|
|
113
116
|
private startPolling;
|
|
114
117
|
private stopPolling;
|
|
115
118
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux.d.ts","sourceRoot":"","sources":["../../../src/mobile/mux.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2CAA2C,CAAA;AAGzE,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AAEpE,kCAAkC;AAClC,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,eAAe,CAAA;IAChD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IACxD,
|
|
1
|
+
{"version":3,"file":"mux.d.ts","sourceRoot":"","sources":["../../../src/mobile/mux.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2CAA2C,CAAA;AAGzE,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AAEpE,kCAAkC;AAClC,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,eAAe,CAAA;IAChD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IACxD,qGAAqG;IACrG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gHAAgH;IAChH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;IACrD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;IAC1C,KAAK,IAAI,IAAI,CAAA;CACd;AAyBD;;;;;GAKG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAChE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6C;IACxE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IACjE,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAE5B,wEAAwE;IACxE,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,6EAA6E;IAC7E,OAAO,CAAC,UAAU,CAAI;IACtB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAQ;IACxB,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAC1D,+FAA+F;IAC/F,OAAO,CAAC,SAAS,CAA4C;IAC7D,OAAO,CAAC,OAAO,CAAQ;IACvB,iFAAiF;IACjF,OAAO,CAAC,UAAU,CAAI;IACtB,2GAA2G;IAC3G,OAAO,CAAC,WAAW,CAAQ;IAE3B;;;OAGG;gBACS,GAAG,SAAsB,EAAE,OAAO,GAAE,gBAAqB;IAUrE,+EAA+E;IAC/E,KAAK,IAAI,IAAI;IAOb,sBAAsB;IACtB,IAAI,IAAI,IAAI;IASZ,sEAAsE;IACtE,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxD;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAU5C,OAAO,CAAC,OAAO;IAqBf;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,IAAI;IAgBZ,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,WAAW;IAMnB;;;;OAIG;YACW,QAAQ;IAsCtB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,IAAI;IAUZ,OAAO,CAAC,WAAW;CAapB"}
|
package/lib/types/mobile/mux.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { muxFrameSchema } from '@deepseek-ai/dsh-host-apiproxy/api/events.schema';
|
|
27
27
|
import { serverRequestSchema } from '@deepseek-ai/dsh-host-apiproxy/api/rpc.schema';
|
|
28
|
-
import { history as fetchHistory } from
|
|
28
|
+
import { history as fetchHistory } from "./api.js";
|
|
29
29
|
/** Browser default source factory. */
|
|
30
30
|
function browserSource(url) {
|
|
31
31
|
// The DOM EventSource is structurally compatible; the `this`-typed handler
|
|
@@ -34,6 +34,8 @@ function browserSource(url) {
|
|
|
34
34
|
}
|
|
35
35
|
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
36
36
|
const DEFAULT_STALL_THRESHOLD_MS = 12000;
|
|
37
|
+
const LIVE_SSE_STALL_MULTIPLIER = 3;
|
|
38
|
+
const MAX_POLL_BACKOFF_MS = 60000;
|
|
37
39
|
/**
|
|
38
40
|
* Stall-check granularity: the single scheduler tick runs at least this
|
|
39
41
|
* often so fallback arms within a second of the stall threshold passing,
|
|
@@ -64,8 +66,8 @@ export class MuxClient {
|
|
|
64
66
|
lastDataAt = 0;
|
|
65
67
|
/**
|
|
66
68
|
* Whether the SSE channel has ever delivered a frame in this stream (a
|
|
67
|
-
* delivered frame proves the tunnel
|
|
68
|
-
*
|
|
69
|
+
* delivered frame proves the tunnel can forward SSE; sustained silence may
|
|
70
|
+
* still mean a suspended mobile tunnel, so polling re-arms after 3 windows).
|
|
69
71
|
*/
|
|
70
72
|
sseAlive = false;
|
|
71
73
|
/** Per-session highest event seq already emitted, for poll dedup. */
|
|
@@ -75,6 +77,8 @@ export class MuxClient {
|
|
|
75
77
|
polling = false;
|
|
76
78
|
/** Epoch ms of the next due poll while polling (kept on the same tick timer). */
|
|
77
79
|
nextPollAt = 0;
|
|
80
|
+
/** Adaptive delay: productive polls reset it; empty/error polls add one base interval up to one minute. */
|
|
81
|
+
pollDelayMs;
|
|
78
82
|
/**
|
|
79
83
|
* @param url - the mobile events endpoint (browser-relative).
|
|
80
84
|
* @param options - seams.
|
|
@@ -84,6 +88,7 @@ export class MuxClient {
|
|
|
84
88
|
this.sourceFactory = options.sourceFactory ?? browserSource;
|
|
85
89
|
this.pollLatest = options.pollLatest ?? ((sessionId) => fetchHistory(sessionId, undefined, DEFAULT_POLL_PAGE_SIZE));
|
|
86
90
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
91
|
+
this.pollDelayMs = this.pollIntervalMs;
|
|
87
92
|
this.stallThresholdMs = options.stallThresholdMs ?? DEFAULT_STALL_THRESHOLD_MS;
|
|
88
93
|
this.now = options.now ?? (() => Date.now());
|
|
89
94
|
}
|
|
@@ -121,9 +126,8 @@ export class MuxClient {
|
|
|
121
126
|
return;
|
|
122
127
|
}
|
|
123
128
|
// If SSE is already stalled for this session, start patching right away.
|
|
124
|
-
if (!this.polling && !this.stopped &&
|
|
129
|
+
if (!this.polling && !this.stopped && this.isSseStalled())
|
|
125
130
|
this.startPolling();
|
|
126
|
-
}
|
|
127
131
|
}
|
|
128
132
|
connect() {
|
|
129
133
|
// A fresh stream starts unknown; only a delivered frame proves it works.
|
|
@@ -170,30 +174,36 @@ export class MuxClient {
|
|
|
170
174
|
if (this.observeSessionId === undefined)
|
|
171
175
|
return;
|
|
172
176
|
if (this.polling) {
|
|
173
|
-
// The stall phase has ended; the same tick now paces the polls.
|
|
177
|
+
// The stall phase has ended; the same tick now paces the adaptive polls.
|
|
174
178
|
if (this.now() >= this.nextPollAt) {
|
|
175
|
-
|
|
179
|
+
// pollTick schedules the next run after it settles, so slow requests
|
|
180
|
+
// cannot overlap with another scheduler tick.
|
|
181
|
+
this.nextPollAt = Number.POSITIVE_INFINITY;
|
|
176
182
|
void this.pollTick();
|
|
177
183
|
}
|
|
178
184
|
return;
|
|
179
185
|
}
|
|
180
|
-
|
|
181
|
-
// against it. Fallback arms again only via onerror or a stream that has
|
|
182
|
-
// never delivered.
|
|
183
|
-
if (this.sseAlive)
|
|
184
|
-
return;
|
|
185
|
-
if ((this.now() - this.lastDataAt) > this.stallThresholdMs)
|
|
186
|
+
if (this.isSseStalled())
|
|
186
187
|
this.startPolling();
|
|
187
188
|
}
|
|
189
|
+
isSseStalled() {
|
|
190
|
+
const windowMs = this.sseAlive
|
|
191
|
+
? this.stallThresholdMs * LIVE_SSE_STALL_MULTIPLIER
|
|
192
|
+
: this.stallThresholdMs;
|
|
193
|
+
return (this.now() - this.lastDataAt) > windowMs;
|
|
194
|
+
}
|
|
188
195
|
startPolling() {
|
|
189
196
|
if (this.polling || this.stopped)
|
|
190
197
|
return;
|
|
191
198
|
this.polling = true;
|
|
192
|
-
this.
|
|
199
|
+
this.pollDelayMs = this.pollIntervalMs;
|
|
200
|
+
this.nextPollAt = Number.POSITIVE_INFINITY;
|
|
193
201
|
void this.pollTick();
|
|
194
202
|
}
|
|
195
203
|
stopPolling() {
|
|
196
204
|
this.polling = false;
|
|
205
|
+
this.pollDelayMs = this.pollIntervalMs;
|
|
206
|
+
this.nextPollAt = 0;
|
|
197
207
|
}
|
|
198
208
|
/**
|
|
199
209
|
* Fetch the latest history page for the observed session and re-emit any
|
|
@@ -206,21 +216,39 @@ export class MuxClient {
|
|
|
206
216
|
this.stopPolling();
|
|
207
217
|
return;
|
|
208
218
|
}
|
|
219
|
+
let emitted = 0;
|
|
209
220
|
try {
|
|
210
221
|
const page = await this.pollLatest(sessionId);
|
|
211
222
|
let maxSeq = this.pollWatermark.get(sessionId) ?? -1;
|
|
212
|
-
|
|
223
|
+
const ordered = [...page.events].sort((left, right) => {
|
|
224
|
+
const leftSeq = typeof left.event?.seq === 'number' ? left.event.seq : -1;
|
|
225
|
+
const rightSeq = typeof right.event?.seq === 'number' ? right.event.seq : -1;
|
|
226
|
+
return leftSeq - rightSeq;
|
|
227
|
+
});
|
|
228
|
+
for (const entry of ordered) {
|
|
213
229
|
const event = entry.event;
|
|
214
230
|
const seq = typeof event?.seq === 'number' ? event.seq : -1;
|
|
215
231
|
if (seq <= maxSeq)
|
|
216
232
|
continue;
|
|
217
233
|
maxSeq = seq;
|
|
234
|
+
emitted += 1;
|
|
218
235
|
this.emit({ type: 'session/event', sessionId: sessionId, event });
|
|
219
236
|
}
|
|
220
237
|
this.pollWatermark.set(sessionId, maxSeq);
|
|
221
238
|
}
|
|
222
239
|
catch {
|
|
223
|
-
// Transient (network, pairing, history paging);
|
|
240
|
+
// Transient (network, pairing, history paging); retry with backoff.
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
if (emitted > 0) {
|
|
244
|
+
this.pollDelayMs = this.pollIntervalMs;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.pollDelayMs = Math.min(MAX_POLL_BACKOFF_MS, this.pollDelayMs + this.pollIntervalMs);
|
|
248
|
+
}
|
|
249
|
+
if (this.polling && this.observeSessionId === sessionId) {
|
|
250
|
+
this.nextPollAt = this.now() + this.pollDelayMs;
|
|
251
|
+
}
|
|
224
252
|
}
|
|
225
253
|
}
|
|
226
254
|
handleMessage(data) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** mux: live-event client, SSE delivery + stall-driven polling fallback. */
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { MuxClient } from
|
|
3
|
+
import { MuxClient } from "./mux.js";
|
|
4
4
|
/** Create an EventSource factory that records every opened source. */
|
|
5
5
|
function makeSources() {
|
|
6
6
|
const sources = [];
|
|
@@ -76,6 +76,21 @@ describe('MuxClient polling fallback', () => {
|
|
|
76
76
|
expect(frames[1]?.event).toMatchObject({ seq: 1 });
|
|
77
77
|
client.stop();
|
|
78
78
|
});
|
|
79
|
+
it('sorts an out-of-order history page before advancing the watermark', async () => {
|
|
80
|
+
const { factory } = makeSources();
|
|
81
|
+
const pollLatest = vi.fn(async (_sessionId) => pageOf([2, 1, 3]));
|
|
82
|
+
const client = new MuxClient('/m/api/events.mux', baseOptions(pollLatest, factory));
|
|
83
|
+
const seqs = [];
|
|
84
|
+
client.onFrame((frame) => {
|
|
85
|
+
if (frame.type === 'session/event' && typeof frame.event.seq === 'number')
|
|
86
|
+
seqs.push(frame.event.seq);
|
|
87
|
+
});
|
|
88
|
+
client.start();
|
|
89
|
+
client.observe('s1');
|
|
90
|
+
await vi.advanceTimersByTimeAsync(1200);
|
|
91
|
+
expect(seqs).toEqual([1, 2, 3]);
|
|
92
|
+
client.stop();
|
|
93
|
+
});
|
|
79
94
|
it('keeps the watermark so a repeated page never re-emits old events', async () => {
|
|
80
95
|
const { factory } = makeSources();
|
|
81
96
|
// Two calls return the same page: the second must emit nothing.
|
|
@@ -139,6 +154,48 @@ describe('MuxClient polling fallback', () => {
|
|
|
139
154
|
expect(live[0]).toMatchObject({ type: 'session/subscribed', sessionId: 's1' });
|
|
140
155
|
client.stop();
|
|
141
156
|
});
|
|
157
|
+
it('recovers when a previously-live SSE stream becomes silently stalled', async () => {
|
|
158
|
+
const { factory, sources } = makeSources();
|
|
159
|
+
const pollLatest = vi.fn(async (_sessionId) => pageOf([5]));
|
|
160
|
+
const client = new MuxClient('/m/api/events.mux', baseOptions(pollLatest, factory));
|
|
161
|
+
const frames = [];
|
|
162
|
+
client.onFrame(frame => { frames.push(frame); });
|
|
163
|
+
client.start();
|
|
164
|
+
client.observe('s1');
|
|
165
|
+
sources[0]?.onmessage?.({ data: envelopeWith({ type: 'session/subscribed', sessionId: 's1', lastSeq: 4 }) });
|
|
166
|
+
await vi.advanceTimersByTimeAsync(2400);
|
|
167
|
+
expect(pollLatest).not.toHaveBeenCalled();
|
|
168
|
+
// A once-live stream gets three stall windows; the next scheduler tick
|
|
169
|
+
// crosses that boundary and starts the ordinary-HTTP recovery path.
|
|
170
|
+
await vi.advanceTimersByTimeAsync(400);
|
|
171
|
+
expect(pollLatest).toHaveBeenCalledTimes(1);
|
|
172
|
+
expect(frames.at(-1)?.event).toMatchObject({ seq: 5 });
|
|
173
|
+
client.stop();
|
|
174
|
+
});
|
|
175
|
+
it('backs empty polls off and resets to the base cadence after progress', async () => {
|
|
176
|
+
const { factory } = makeSources();
|
|
177
|
+
const pages = [pageOf([]), pageOf([1]), pageOf([1, 2])];
|
|
178
|
+
const pollLatest = vi.fn(async (_sessionId) => pages.shift() ?? pageOf([]));
|
|
179
|
+
const client = new MuxClient('/m/api/events.mux', baseOptions(pollLatest, factory));
|
|
180
|
+
client.start();
|
|
181
|
+
client.observe('s1');
|
|
182
|
+
await vi.advanceTimersByTimeAsync(1200);
|
|
183
|
+
expect(pollLatest).toHaveBeenCalledTimes(1);
|
|
184
|
+
await vi.advanceTimersByTimeAsync(400);
|
|
185
|
+
expect(pollLatest).toHaveBeenCalledTimes(1);
|
|
186
|
+
await vi.advanceTimersByTimeAsync(400);
|
|
187
|
+
expect(pollLatest).toHaveBeenCalledTimes(2);
|
|
188
|
+
// The productive second poll resets the next delay from 800 ms to 400 ms.
|
|
189
|
+
await vi.advanceTimersByTimeAsync(400);
|
|
190
|
+
expect(pollLatest).toHaveBeenCalledTimes(3);
|
|
191
|
+
await vi.advanceTimersByTimeAsync(400);
|
|
192
|
+
expect(pollLatest).toHaveBeenCalledTimes(4);
|
|
193
|
+
await vi.advanceTimersByTimeAsync(400);
|
|
194
|
+
expect(pollLatest).toHaveBeenCalledTimes(4);
|
|
195
|
+
await vi.advanceTimersByTimeAsync(400);
|
|
196
|
+
expect(pollLatest).toHaveBeenCalledTimes(5);
|
|
197
|
+
client.stop();
|
|
198
|
+
});
|
|
142
199
|
it('drives the whole lifecycle on a single scheduler tick (one interval)', async () => {
|
|
143
200
|
const { factory } = makeSources();
|
|
144
201
|
const setIntervalSpy = vi.spyOn(globalThis, 'setInterval');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../../src/mobile/rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0EAA0E;AAC1E,qBAAa,iBAAkB,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../../src/mobile/rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0EAA0E;AAC1E,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,kEAAkE;AAClE,qBAAa,YAAa,SAAQ,KAAK;IACrC,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;gBAErC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAKrD;AAID,8EAA8E;AAC9E,wBAAgB,SAAS,IAAI,MAAM,CAMlC;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAmCZ"}
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* mode); the choice persists across visits.
|
|
7
7
|
*/
|
|
8
8
|
import { useSyncExternalStore } from 'react';
|
|
9
|
-
import { getMobileTheme, subscribeMobileTheme, toggleMobileTheme } from
|
|
9
|
+
import { getMobileTheme, subscribeMobileTheme, toggleMobileTheme } from "./mobile-theme.js";
|
|
10
10
|
/** Sun glyph: circle + eight rays (inline SVG, no emoji). */
|
|
11
11
|
function SunIcon() {
|
|
12
12
|
return (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", "aria-hidden": true, children: [_jsx("circle", { cx: "12", cy: "12", r: "4.2" }), _jsx("path", { d: "M12 2.5v2.4M12 19.1v2.4M2.5 12h2.4M19.1 12h2.4M5 5l1.7 1.7M17.3 17.3 19 19M19 5l-1.7 1.7M6.7 17.3 5 19" })] }));
|
|
@@ -6,13 +6,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* affordance, and every piece of data is fetched on demand.
|
|
7
7
|
*/
|
|
8
8
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
9
|
-
import { fetchMobilePreferences, history as fetchHistory, listSessions, listWorkspaces, prompt } from
|
|
10
|
-
import { MuxClient } from
|
|
11
|
-
import { RpcCallError, RpcTransportError } from
|
|
12
|
-
import { ChatView } from
|
|
13
|
-
import { SessionListView } from
|
|
14
|
-
import { WorkspaceView as WorkspaceRoster } from
|
|
15
|
-
import { PairRequiredView } from
|
|
9
|
+
import { fetchMobilePreferences, history as fetchHistory, listSessions, listWorkspaces, prompt } from "../api.js";
|
|
10
|
+
import { MuxClient } from "../mux.js";
|
|
11
|
+
import { RpcCallError, RpcTransportError } from "../rpc.js";
|
|
12
|
+
import { ChatView } from "./ChatView.js";
|
|
13
|
+
import { SessionListView } from "./SessionListView.js";
|
|
14
|
+
import { WorkspaceView as WorkspaceRoster } from "./WorkspaceView.js";
|
|
15
|
+
import { PairRequiredView } from "../PairRequiredView.js";
|
|
16
16
|
/** Read the optional workspace target carried from the pairing QR flow. */
|
|
17
17
|
export function mobileWorkspaceTarget(search) {
|
|
18
18
|
const value = new URLSearchParams(search).get('workspace');
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
// @vitest-environment jsdom
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
5
|
-
import { RpcTransportError } from
|
|
5
|
+
import { RpcTransportError } from "../rpc.js";
|
|
6
6
|
const api = vi.hoisted(() => ({
|
|
7
7
|
fetchMobilePreferences: vi.fn(),
|
|
8
8
|
history: vi.fn(),
|
|
@@ -21,7 +21,7 @@ vi.mock('../mux.ts', () => ({
|
|
|
21
21
|
vi.mock('./WorkspaceView.tsx', () => ({ WorkspaceView: () => _jsx("div", { children: "workspace-ready" }) }));
|
|
22
22
|
vi.mock('./SessionListView.tsx', () => ({ SessionListView: () => _jsx("div", { children: "sessions-ready" }) }));
|
|
23
23
|
vi.mock('./ChatView.tsx', () => ({ ChatView: () => _jsx("div", { children: "chat-ready" }) }));
|
|
24
|
-
import { App, mobilePairStateForError } from
|
|
24
|
+
import { App, mobilePairStateForError } from "./App.js";
|
|
25
25
|
afterEach(() => {
|
|
26
26
|
cleanup();
|
|
27
27
|
vi.clearAllMocks();
|
|
@@ -12,12 +12,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
* permission pickers, both as bottom sheets.
|
|
13
13
|
*/
|
|
14
14
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
|
-
import { loadHistory, prompt } from
|
|
16
|
-
import { errorText, formatTime, staleHostHint } from
|
|
17
|
-
import { fetchMobilePreferences, models, selectModel, sendCommand } from
|
|
18
|
-
import { EventFolder, foldEvents } from
|
|
19
|
-
import { renderMarkdown } from
|
|
20
|
-
import { ThemeToggle } from
|
|
15
|
+
import { loadHistory, prompt } from "./App.js";
|
|
16
|
+
import { errorText, formatTime, staleHostHint } from "./App.js";
|
|
17
|
+
import { fetchMobilePreferences, models, selectModel, sendCommand } from "../api.js";
|
|
18
|
+
import { EventFolder, foldEvents } from "../messages.js";
|
|
19
|
+
import { renderMarkdown } from "../markdown.js";
|
|
20
|
+
import { ThemeToggle } from "../theme-toggle.js";
|
|
21
21
|
/**
|
|
22
22
|
* Hard cap on live events buffered while the initial history tail page is in
|
|
23
23
|
* flight. Beyond this the oldest buffered event is dropped and a follow-up
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
/** ChatView: collapsible message folds, toolbar chips, and the bottom sheets. */
|
|
4
4
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
6
|
-
import { ChatView, MAX_TAIL_BUFFER_EVENTS } from
|
|
6
|
+
import { ChatView, MAX_TAIL_BUFFER_EVENTS } from "./ChatView.js";
|
|
7
7
|
// The api module is fully mocked; App.tsx's history wrapper is overridden to
|
|
8
8
|
// feed fixed history pages, its pure helpers (errorText / formatTime) stay real.
|
|
9
9
|
vi.mock('../api.ts', () => ({
|
|
@@ -20,8 +20,8 @@ vi.mock('./App.tsx', async (importOriginal) => {
|
|
|
20
20
|
prompt: vi.fn(async () => { }),
|
|
21
21
|
};
|
|
22
22
|
});
|
|
23
|
-
import { fetchMobilePreferences, models, selectModel, sendCommand } from
|
|
24
|
-
import { loadHistory, prompt } from
|
|
23
|
+
import { fetchMobilePreferences, models, selectModel, sendCommand } from "../api.js";
|
|
24
|
+
import { loadHistory, prompt } from "./App.js";
|
|
25
25
|
const session = {
|
|
26
26
|
sessionId: 's-1',
|
|
27
27
|
title: '测试会话',
|
|
@@ -12,9 +12,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
* the new chat — the same "new session opens" flow as the desktop UI.
|
|
13
13
|
*/
|
|
14
14
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
15
|
-
import { createSession, listAgentPresets, listSessions, listWorkspaces } from
|
|
16
|
-
import { errorText, formatTime, staleHostHint, toSessionView } from
|
|
17
|
-
import { ThemeToggle } from
|
|
15
|
+
import { createSession, listAgentPresets, listSessions, listWorkspaces } from "../api.js";
|
|
16
|
+
import { errorText, formatTime, staleHostHint, toSessionView } from "./App.js";
|
|
17
|
+
import { ThemeToggle } from "../theme-toggle.js";
|
|
18
18
|
/** Rows that belong to the opened workspace (its owned session id set). */
|
|
19
19
|
function ownedItems(page, workspace) {
|
|
20
20
|
const owned = new Set(workspace.sessionIds);
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
/** SessionListView: owned-row filtering, incremental pages, session creation. */
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
6
|
-
import { SessionListView } from
|
|
6
|
+
import { SessionListView } from "./SessionListView.js";
|
|
7
7
|
// The api module is fully mocked; the view's App.tsx helpers stay real.
|
|
8
8
|
vi.mock('../api.ts', () => ({
|
|
9
9
|
listSessions: vi.fn(),
|
|
@@ -11,7 +11,7 @@ vi.mock('../api.ts', () => ({
|
|
|
11
11
|
listAgentPresets: vi.fn(),
|
|
12
12
|
createSession: vi.fn(),
|
|
13
13
|
}));
|
|
14
|
-
import { createSession, listAgentPresets, listSessions, listWorkspaces } from
|
|
14
|
+
import { createSession, listAgentPresets, listSessions, listWorkspaces } from "../api.js";
|
|
15
15
|
const listSessionsMock = vi.mocked(listSessions);
|
|
16
16
|
const listWorkspacesMock = vi.mocked(listWorkspaces);
|
|
17
17
|
const listAgentPresetsMock = vi.mocked(listAgentPresets);
|
|
@@ -6,9 +6,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* until a workspace is opened).
|
|
7
7
|
*/
|
|
8
8
|
import { useEffect, useState } from 'react';
|
|
9
|
-
import { listWorkspaces } from
|
|
10
|
-
import { errorText } from
|
|
11
|
-
import { ThemeToggle } from
|
|
9
|
+
import { listWorkspaces } from "../api.js";
|
|
10
|
+
import { errorText } from "./App.js";
|
|
11
|
+
import { ThemeToggle } from "../theme-toggle.js";
|
|
12
12
|
/**
|
|
13
13
|
* Render the workspace roster.
|
|
14
14
|
* @param props - the pick action.
|
|
@@ -3,12 +3,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
/** Mobile workspace landing: roster rendering and QR deep-link selection. */
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
6
|
-
import { mobileWorkspaceTarget } from
|
|
7
|
-
import { WorkspaceView } from
|
|
6
|
+
import { mobileWorkspaceTarget } from "./App.js";
|
|
7
|
+
import { WorkspaceView } from "./WorkspaceView.js";
|
|
8
8
|
vi.mock('../api.ts', () => ({
|
|
9
9
|
listWorkspaces: vi.fn(),
|
|
10
10
|
}));
|
|
11
|
-
import { listWorkspaces } from
|
|
11
|
+
import { listWorkspaces } from "../api.js";
|
|
12
12
|
const listWorkspacesMock = vi.mocked(listWorkspaces);
|
|
13
13
|
const workspaces = [
|
|
14
14
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-api.d.ts","sourceRoot":"","sources":["../../src/mobile-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAA;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAyDlD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,CAAA;IAClB,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,OAAO,CAAA;IAChC,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB
|
|
1
|
+
{"version":3,"file":"mobile-api.d.ts","sourceRoot":"","sources":["../../src/mobile-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAA;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAyDlD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,CAAA;IAClB,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,OAAO,CAAA;IAChC,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB;;CAEnB,CAAA;AAOV;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CA4InE"}
|
package/lib/types/mobile-api.js
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* unary channel), gated identically.
|
|
20
20
|
*/
|
|
21
21
|
import { RpcId } from '@deepseek-ai/dsh-host-apiproxy/api/rpc';
|
|
22
|
-
import { readBoundedJson, writeJson } from
|
|
23
|
-
import { readCookie } from
|
|
22
|
+
import { readBoundedJson, writeJson } from "./http.js";
|
|
23
|
+
import { readCookie } from "./gate.js";
|
|
24
24
|
/**
|
|
25
25
|
* Methods the phone surface may call. Everything else is refused HERE — but
|
|
26
26
|
* note the paired-device cookie also passes the global api/gate for the full
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing-access.d.ts","sourceRoot":"","sources":["../../src/pairing-access.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE3D,wDAAwD;AACxD,eAAO,MAAM,qBAAqB,uBAAuB,CAAA;AAEzD;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,OAAO;IAO3C,OAAO,CAAC,QAAQ,CAAC,KAAK;IANxB;;;OAGG;
|
|
1
|
+
{"version":3,"file":"pairing-access.d.ts","sourceRoot":"","sources":["../../src/pairing-access.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE3D,wDAAwD;AACxD,eAAO,MAAM,qBAAqB,uBAAuB,CAAA;AAEzD;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,OAAO;IAO3C,OAAO,CAAC,QAAQ,CAAC,KAAK;IANxB;;;OAGG;gBAED,GAAG,EAAE,OAAO,EACK,KAAK,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO;IAK/D;;;;OAIG;IACH,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO;CAGlD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,8 +8,8 @@ import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node
|
|
|
8
8
|
import { tmpdir } from 'node:os';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
-
import { pairingConfigOf } from
|
|
12
|
-
import { DEFAULT_IDLE_EXPIRE_MS, PairingService } from
|
|
11
|
+
import { pairingConfigOf } from "./index.js";
|
|
12
|
+
import { DEFAULT_IDLE_EXPIRE_MS, PairingService } from "./pairing.js";
|
|
13
13
|
const BASE_CONFIG = {
|
|
14
14
|
tokenTtlMs: 60_000,
|
|
15
15
|
offlineAfterMs: 25_000,
|
package/lib/types/remote-api.js
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
* browser marker added after authentication. Plugin loopback fences then
|
|
20
20
|
* pass. The pairing cookie never leaves this process.
|
|
21
21
|
*/
|
|
22
|
-
import { readCookie } from
|
|
23
|
-
import { writeJson } from
|
|
24
|
-
import { proxyLoopbackHttp, proxyLoopbackUpgrade } from
|
|
25
|
-
import { DESKTOP_LAUNCHER_PATH, LOOPBACK_ONLY_METHODS, PLUGIN_MANAGER_PATH, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, WEB_UI_SETTINGS_BRIDGE_PATH, } from
|
|
26
|
-
export { DESKTOP_LAUNCHER_PATH, LOOPBACK_ONLY_METHODS, PLUGIN_MANAGER_PATH, REMOTE_API_PATHS, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, WEB_UI_SETTINGS_BRIDGE_PATH, } from
|
|
27
|
-
export { REMOTE_API_PREFIX } from
|
|
22
|
+
import { readCookie } from "./gate.js";
|
|
23
|
+
import { writeJson } from "./http.js";
|
|
24
|
+
import { proxyLoopbackHttp, proxyLoopbackUpgrade } from "./loopback-proxy.js";
|
|
25
|
+
import { DESKTOP_LAUNCHER_PATH, LOOPBACK_ONLY_METHODS, PLUGIN_MANAGER_PATH, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, WEB_UI_SETTINGS_BRIDGE_PATH, } from "./remote-methods.js";
|
|
26
|
+
export { DESKTOP_LAUNCHER_PATH, LOOPBACK_ONLY_METHODS, PLUGIN_MANAGER_PATH, REMOTE_API_PATHS, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, WEB_UI_SETTINGS_BRIDGE_PATH, } from "./remote-methods.js";
|
|
27
|
+
export { REMOTE_API_PREFIX } from "./remote-methods.js";
|
|
28
28
|
const ALLOWED_METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
29
29
|
/** Reject traversal and empty segments; allow plugin file-path characters. */
|
|
30
30
|
function isSafeSegment(segment) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-methods.d.ts","sourceRoot":"","sources":["../../src/remote-methods.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,gFAAgF;AAChF,eAAO,MAAM,aAAa,YAAY,CAAA;AAEtC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,gBAAyB,CAAA;AAEvD,qFAAqF;AACrF,eAAO,MAAM,gBAAgB
|
|
1
|
+
{"version":3,"file":"remote-methods.d.ts","sourceRoot":"","sources":["../../src/remote-methods.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,gFAAgF;AAChF,eAAO,MAAM,aAAa,YAAY,CAAA;AAEtC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,gBAAyB,CAAA;AAEvD,qFAAqF;AACrF,eAAO,MAAM,gBAAgB;;;CAGnB,CAAA;AAEV;;;GAGG;AACH,eAAO,MAAM,oBAAoB,qKAMvB,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAExD,6FAA6F;AAC7F,eAAO,MAAM,qBAAqB,8BAA8B,CAAA;AAEhE,iFAAiF;AACjF,eAAO,MAAM,2BAA2B,6BAA6B,CAAA;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,aAgBhC,CAAA"}
|