@linxin666/dsh-remote-web-ui 0.1.3 → 0.1.7
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 +14 -5
- package/lib/client.js +70 -70
- package/lib/mobile.js +160 -32
- package/lib/mobile.js.map +1 -1
- package/lib/types/mobile/mux.d.ts +66 -2
- package/lib/types/mobile/mux.d.ts.map +1 -1
- package/lib/types/mobile/mux.js +155 -8
- package/lib/types/mobile/mux.test.d.ts +2 -0
- package/lib/types/mobile/mux.test.d.ts.map +1 -0
- package/lib/types/mobile/mux.test.js +141 -0
- package/lib/types/mobile/views/App.d.ts.map +1 -1
- package/lib/types/mobile/views/App.js +6 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +20 -6
- package/lib/types/mobile/views/ChatView.test.js +92 -2
- package/package.json +1 -1
- package/src/mobile/mux.test.ts +172 -0
- package/src/mobile/mux.ts +170 -8
- package/src/mobile/views/App.tsx +7 -0
- package/src/mobile/views/ChatView.test.tsx +98 -3
- package/src/mobile/views/ChatView.tsx +20 -6
- package/lib/types/client/PairFailedNotice.js.map +0 -1
- package/lib/types/client/PhoneIcon.js.map +0 -1
- package/lib/types/client/PluginSettingsCard.js.map +0 -1
- package/lib/types/client/RemoteEntry.js.map +0 -1
- package/lib/types/client/RemotePanel.js.map +0 -1
- package/lib/types/client/RemoteSettingsCard.js.map +0 -1
- package/lib/types/client/deep-link.js.map +0 -1
- package/lib/types/client/index.js.map +0 -1
- package/lib/types/client/locales.js.map +0 -1
- package/lib/types/client/pair-api.js.map +0 -1
- package/lib/types/client/settings-form.js.map +0 -1
- package/lib/types/gate.js.map +0 -1
- package/lib/types/index.js.map +0 -1
- package/lib/types/invariant.js.map +0 -1
- package/lib/types/lan.js.map +0 -1
- package/lib/types/mobile/api.js.map +0 -1
- package/lib/types/mobile/index.js.map +0 -1
- package/lib/types/mobile/messages.js.map +0 -1
- package/lib/types/mobile/messages.test.js.map +0 -1
- package/lib/types/mobile/mobile-styles.js.map +0 -1
- package/lib/types/mobile/mux.js.map +0 -1
- package/lib/types/mobile/rpc.js.map +0 -1
- package/lib/types/mobile/views/App.js.map +0 -1
- package/lib/types/mobile/views/ChatView.js.map +0 -1
- package/lib/types/mobile/views/SessionListView.js.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.js.map +0 -1
- package/lib/types/mobile-api.js.map +0 -1
- package/lib/types/mobile-routes.js.map +0 -1
- package/lib/types/pairing.js.map +0 -1
- package/lib/types/routes.js.map +0 -1
- package/lib/types/tunnel.js.map +0 -1
package/lib/mobile.js
CHANGED
|
@@ -7625,7 +7625,7 @@ async function selectModel(sessionId, selection) {
|
|
|
7625
7625
|
});
|
|
7626
7626
|
}
|
|
7627
7627
|
//#endregion
|
|
7628
|
-
//#region
|
|
7628
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
|
|
7629
7629
|
var _a$1;
|
|
7630
7630
|
function $constructor(name, initializer, params) {
|
|
7631
7631
|
function init(inst, def) {
|
|
@@ -7684,7 +7684,7 @@ function config(newConfig) {
|
|
|
7684
7684
|
return globalConfig;
|
|
7685
7685
|
}
|
|
7686
7686
|
//#endregion
|
|
7687
|
-
//#region
|
|
7687
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/util.js
|
|
7688
7688
|
function getEnumValues(entries) {
|
|
7689
7689
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
7690
7690
|
return Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
|
|
@@ -7999,7 +7999,7 @@ function issue(...args) {
|
|
|
7999
7999
|
return { ...iss };
|
|
8000
8000
|
}
|
|
8001
8001
|
//#endregion
|
|
8002
|
-
//#region
|
|
8002
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/errors.js
|
|
8003
8003
|
const initializer$1 = (inst, def) => {
|
|
8004
8004
|
inst.name = "$ZodError";
|
|
8005
8005
|
Object.defineProperty(inst, "_zod", {
|
|
@@ -8059,7 +8059,7 @@ function formatError(error, mapper = (issue) => issue.message) {
|
|
|
8059
8059
|
return fieldErrors;
|
|
8060
8060
|
}
|
|
8061
8061
|
//#endregion
|
|
8062
|
-
//#region
|
|
8062
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js
|
|
8063
8063
|
const _parse = (_Err) => (schema, value, _ctx, _params) => {
|
|
8064
8064
|
const ctx = _ctx ? {
|
|
8065
8065
|
..._ctx,
|
|
@@ -8173,7 +8173,7 @@ const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
|
8173
8173
|
return _safeParseAsync(_Err)(schema, value, _ctx);
|
|
8174
8174
|
};
|
|
8175
8175
|
//#endregion
|
|
8176
|
-
//#region
|
|
8176
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/regexes.js
|
|
8177
8177
|
/**
|
|
8178
8178
|
* @deprecated CUID v1 is deprecated by its authors due to information leakage
|
|
8179
8179
|
* (timestamps embedded in the id). Use {@link cuid2} instead.
|
|
@@ -8237,7 +8237,7 @@ const boolean$1 = /^(?:true|false)$/i;
|
|
|
8237
8237
|
const lowercase = /^[^A-Z]*$/;
|
|
8238
8238
|
const uppercase = /^[^a-z]*$/;
|
|
8239
8239
|
//#endregion
|
|
8240
|
-
//#region
|
|
8240
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/checks.js
|
|
8241
8241
|
const $ZodCheck = /*@__PURE__*/ $constructor("$ZodCheck", (inst, def) => {
|
|
8242
8242
|
var _a;
|
|
8243
8243
|
inst._zod ?? (inst._zod = {});
|
|
@@ -8595,7 +8595,7 @@ const $ZodCheckOverwrite = /*@__PURE__*/ $constructor("$ZodCheckOverwrite", (ins
|
|
|
8595
8595
|
};
|
|
8596
8596
|
});
|
|
8597
8597
|
//#endregion
|
|
8598
|
-
//#region
|
|
8598
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/doc.js
|
|
8599
8599
|
var Doc = class {
|
|
8600
8600
|
constructor(args = []) {
|
|
8601
8601
|
this.content = [];
|
|
@@ -8626,14 +8626,14 @@ var Doc = class {
|
|
|
8626
8626
|
}
|
|
8627
8627
|
};
|
|
8628
8628
|
//#endregion
|
|
8629
|
-
//#region
|
|
8629
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/versions.js
|
|
8630
8630
|
const version = {
|
|
8631
8631
|
major: 4,
|
|
8632
8632
|
minor: 4,
|
|
8633
8633
|
patch: 3
|
|
8634
8634
|
};
|
|
8635
8635
|
//#endregion
|
|
8636
|
-
//#region
|
|
8636
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/schemas.js
|
|
8637
8637
|
const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
|
|
8638
8638
|
var _a;
|
|
8639
8639
|
inst ?? (inst = {});
|
|
@@ -9934,7 +9934,7 @@ function handleRefineResult(result, payload, input, inst) {
|
|
|
9934
9934
|
}
|
|
9935
9935
|
}
|
|
9936
9936
|
//#endregion
|
|
9937
|
-
//#region
|
|
9937
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/registries.js
|
|
9938
9938
|
var _a;
|
|
9939
9939
|
var $ZodRegistry = class {
|
|
9940
9940
|
constructor() {
|
|
@@ -9981,7 +9981,7 @@ function registry() {
|
|
|
9981
9981
|
(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
|
|
9982
9982
|
const globalRegistry = globalThis.__zod_globalRegistry;
|
|
9983
9983
|
//#endregion
|
|
9984
|
-
//#region
|
|
9984
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/api.js
|
|
9985
9985
|
// @__NO_SIDE_EFFECTS__
|
|
9986
9986
|
function _string(Class, params) {
|
|
9987
9987
|
return new Class({
|
|
@@ -10497,7 +10497,7 @@ function _check(fn, params) {
|
|
|
10497
10497
|
return ch;
|
|
10498
10498
|
}
|
|
10499
10499
|
//#endregion
|
|
10500
|
-
//#region
|
|
10500
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/to-json-schema.js
|
|
10501
10501
|
function initializeContext(params) {
|
|
10502
10502
|
let target = params?.target ?? "draft-2020-12";
|
|
10503
10503
|
if (target === "draft-4") target = "draft-04";
|
|
@@ -10790,7 +10790,7 @@ const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params)
|
|
|
10790
10790
|
return finalize(ctx, schema);
|
|
10791
10791
|
};
|
|
10792
10792
|
//#endregion
|
|
10793
|
-
//#region
|
|
10793
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/json-schema-processors.js
|
|
10794
10794
|
const formatMap = {
|
|
10795
10795
|
guid: "uuid",
|
|
10796
10796
|
url: "uri",
|
|
@@ -11058,7 +11058,7 @@ const lazyProcessor = (schema, ctx, _json, params) => {
|
|
|
11058
11058
|
seen.ref = innerType;
|
|
11059
11059
|
};
|
|
11060
11060
|
//#endregion
|
|
11061
|
-
//#region
|
|
11061
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/iso.js
|
|
11062
11062
|
const ZodISODateTime = /*@__PURE__*/ $constructor("ZodISODateTime", (inst, def) => {
|
|
11063
11063
|
$ZodISODateTime.init(inst, def);
|
|
11064
11064
|
ZodStringFormat.init(inst, def);
|
|
@@ -11088,7 +11088,7 @@ function duration(params) {
|
|
|
11088
11088
|
return /* @__PURE__ */ _isoDuration(ZodISODuration, params);
|
|
11089
11089
|
}
|
|
11090
11090
|
//#endregion
|
|
11091
|
-
//#region
|
|
11091
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/errors.js
|
|
11092
11092
|
const initializer = (inst, issues) => {
|
|
11093
11093
|
$ZodError.init(inst, issues);
|
|
11094
11094
|
inst.name = "ZodError";
|
|
@@ -11110,7 +11110,7 @@ const initializer = (inst, issues) => {
|
|
|
11110
11110
|
};
|
|
11111
11111
|
const ZodRealError = /*@__PURE__*/ $constructor("ZodError", initializer, { Parent: Error });
|
|
11112
11112
|
//#endregion
|
|
11113
|
-
//#region
|
|
11113
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/parse.js
|
|
11114
11114
|
const parse = /* @__PURE__ */ _parse(ZodRealError);
|
|
11115
11115
|
const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
11116
11116
|
const safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
@@ -11124,7 +11124,7 @@ const safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
|
|
|
11124
11124
|
const safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
|
|
11125
11125
|
const safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
|
|
11126
11126
|
//#endregion
|
|
11127
|
-
//#region
|
|
11127
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/schemas.js
|
|
11128
11128
|
const _installedGroups = /* @__PURE__ */ new WeakMap();
|
|
11129
11129
|
function _installLazyMethods(inst, group, methods) {
|
|
11130
11130
|
const proto = Object.getPrototypeOf(inst);
|
|
@@ -11954,7 +11954,7 @@ function superRefine(fn, params) {
|
|
|
11954
11954
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
11955
11955
|
}
|
|
11956
11956
|
//#endregion
|
|
11957
|
-
//#region
|
|
11957
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.6_8335eca05d5647f26dcc0c8fd5a39ec0/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/rpc.schema.js
|
|
11958
11958
|
/**
|
|
11959
11959
|
* Message-layer zod schemas: the four wire full forms + error body +
|
|
11960
11960
|
* carrier receipt. The payload slot is unknown in the full-form schemas — business payloads
|
|
@@ -12268,7 +12268,7 @@ union([object({ accepted: literal(true) }), object({
|
|
|
12268
12268
|
reason: union([literal("not-pending"), literal("bad-response")])
|
|
12269
12269
|
})]);
|
|
12270
12270
|
//#endregion
|
|
12271
|
-
//#region
|
|
12271
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.6_8335eca05d5647f26dcc0c8fd5a39ec0/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/session-search.js
|
|
12272
12272
|
/**
|
|
12273
12273
|
* Return the longest prefix containing at most `maximum` Unicode code points.
|
|
12274
12274
|
* @param value - text to bound.
|
|
@@ -12286,7 +12286,7 @@ function truncateUnicodeCodePoints(value, maximum) {
|
|
|
12286
12286
|
return value;
|
|
12287
12287
|
}
|
|
12288
12288
|
//#endregion
|
|
12289
|
-
//#region
|
|
12289
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.6_8335eca05d5647f26dcc0c8fd5a39ec0/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/sessions.schema.js
|
|
12290
12290
|
/**
|
|
12291
12291
|
* sessions domain zod schemas (names derived from map keys: sessionListRequestSchema /
|
|
12292
12292
|
* sessionListValueSchema). SessionEvent passthrough = strict envelope (type/seq/time) + wide
|
|
@@ -12425,6 +12425,10 @@ const sessionProjectionsBlockSchema = object({
|
|
|
12425
12425
|
asOfSeq: number().int().min(-1),
|
|
12426
12426
|
values: record(string(), unknown())
|
|
12427
12427
|
});
|
|
12428
|
+
object({
|
|
12429
|
+
blank: boolean(),
|
|
12430
|
+
lastPromptAt: number().nullable()
|
|
12431
|
+
});
|
|
12428
12432
|
object({
|
|
12429
12433
|
maxImageBytes: number().int().positive(),
|
|
12430
12434
|
maxImagesPerMessage: number().int().positive(),
|
|
@@ -12518,7 +12522,7 @@ object({ accepted: literal(true) });
|
|
|
12518
12522
|
object({ sessionId: sessionIdSchema });
|
|
12519
12523
|
object({ accepted: literal(true) });
|
|
12520
12524
|
//#endregion
|
|
12521
|
-
//#region
|
|
12525
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.6_8335eca05d5647f26dcc0c8fd5a39ec0/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/approvals.schema.js
|
|
12522
12526
|
/**
|
|
12523
12527
|
* approvals domain zod schemas (respond is a client-response; the payload schema serves
|
|
12524
12528
|
* the /api/respond endpoint's second parse after routing via the pending table).
|
|
@@ -12552,7 +12556,7 @@ const taskViewSchema = object({
|
|
|
12552
12556
|
finishedAt: number().int().nonnegative().optional()
|
|
12553
12557
|
});
|
|
12554
12558
|
//#endregion
|
|
12555
|
-
//#region
|
|
12559
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.6_8335eca05d5647f26dcc0c8fd5a39ec0/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/workspace.schema.js
|
|
12556
12560
|
/**
|
|
12557
12561
|
* workspace domain zod schemas (names derived from map keys). The
|
|
12558
12562
|
* WorkspaceId brand cast lives in sessions.schema (see the note there) and
|
|
@@ -12598,7 +12602,7 @@ object({ workspace: workspaceViewSchema });
|
|
|
12598
12602
|
object({ sessionId: sessionIdSchema });
|
|
12599
12603
|
object({ archivedSessionIds: array(sessionIdSchema) });
|
|
12600
12604
|
//#endregion
|
|
12601
|
-
//#region
|
|
12605
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.6_8335eca05d5647f26dcc0c8fd5a39ec0/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/events.schema.js
|
|
12602
12606
|
/**
|
|
12603
12607
|
* events domain zod schemas: MuxFrame / HostFrame unions (discriminatedUnion('type')).
|
|
12604
12608
|
* A frame is the payload slot of the ServerRequest full form; the SessionEvent inside
|
|
@@ -12760,17 +12764,41 @@ discriminatedUnion("type", [
|
|
|
12760
12764
|
function browserSource(url) {
|
|
12761
12765
|
return new EventSource(url);
|
|
12762
12766
|
}
|
|
12767
|
+
const DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
12768
|
+
const DEFAULT_STALL_THRESHOLD_MS = 12e3;
|
|
12769
|
+
/** Poll window: enough recent events to cover a few seconds of agent output. */
|
|
12770
|
+
const DEFAULT_POLL_PAGE_SIZE = 50;
|
|
12763
12771
|
/**
|
|
12764
12772
|
* Keep one SSE subscription open, fanning validated frames out to
|
|
12765
12773
|
* subscribers. EventSource owns reconnection (with its own backoff); this
|
|
12766
|
-
* class only manages the subscription lifecycle
|
|
12774
|
+
* class only manages the subscription lifecycle, plus a polling fallback
|
|
12775
|
+
* that keeps the open session live when the SSE channel cannot deliver.
|
|
12767
12776
|
*/
|
|
12768
12777
|
var MuxClient = class {
|
|
12769
12778
|
sourceFactory;
|
|
12779
|
+
pollLatest;
|
|
12780
|
+
pollIntervalMs;
|
|
12781
|
+
stallThresholdMs;
|
|
12782
|
+
now;
|
|
12770
12783
|
listeners = /* @__PURE__ */ new Set();
|
|
12771
12784
|
source;
|
|
12772
12785
|
stopped = false;
|
|
12773
12786
|
url;
|
|
12787
|
+
/** The session to keep live via fallback polling (undefined = none). */
|
|
12788
|
+
observeSessionId;
|
|
12789
|
+
/** Last epoch ms the SSE channel produced a frame (or the stream opened). */
|
|
12790
|
+
lastDataAt = 0;
|
|
12791
|
+
/**
|
|
12792
|
+
* Whether the SSE channel has ever delivered a frame in this stream (a
|
|
12793
|
+
* delivered frame proves the tunnel forwards SSE; silence alone then means
|
|
12794
|
+
* the agent idle, not a dead channel — only an onerror re-arms fallback).
|
|
12795
|
+
*/
|
|
12796
|
+
sseAlive = false;
|
|
12797
|
+
/** Per-session highest event seq already emitted, for poll dedup. */
|
|
12798
|
+
pollWatermark = /* @__PURE__ */ new Map();
|
|
12799
|
+
stallTimer;
|
|
12800
|
+
pollTimer;
|
|
12801
|
+
polling = false;
|
|
12774
12802
|
/**
|
|
12775
12803
|
* @param url - the mobile events endpoint (browser-relative).
|
|
12776
12804
|
* @param options - seams.
|
|
@@ -12778,17 +12806,25 @@ var MuxClient = class {
|
|
|
12778
12806
|
constructor(url = "/m/api/events.mux", options = {}) {
|
|
12779
12807
|
this.url = url;
|
|
12780
12808
|
this.sourceFactory = options.sourceFactory ?? browserSource;
|
|
12809
|
+
this.pollLatest = options.pollLatest ?? ((sessionId) => history(sessionId, void 0, DEFAULT_POLL_PAGE_SIZE));
|
|
12810
|
+
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
12811
|
+
this.stallThresholdMs = options.stallThresholdMs ?? DEFAULT_STALL_THRESHOLD_MS;
|
|
12812
|
+
this.now = options.now ?? (() => Date.now());
|
|
12781
12813
|
}
|
|
12782
12814
|
/** Open the stream (idempotent; EventSource reconnects until {@link stop}). */
|
|
12783
12815
|
start() {
|
|
12784
12816
|
this.stopped = false;
|
|
12785
|
-
|
|
12786
|
-
this.connect();
|
|
12817
|
+
this.lastDataAt = this.now();
|
|
12818
|
+
if (this.source === void 0) this.connect();
|
|
12819
|
+
this.startStallChecker();
|
|
12787
12820
|
}
|
|
12788
12821
|
/** Close for good. */
|
|
12789
12822
|
stop() {
|
|
12790
12823
|
this.stopped = true;
|
|
12824
|
+
this.stopStallChecker();
|
|
12825
|
+
this.stopPolling();
|
|
12791
12826
|
this.closeSource();
|
|
12827
|
+
this.observeSessionId = void 0;
|
|
12792
12828
|
}
|
|
12793
12829
|
/** Subscribe to validated frames; returns an unsubscribe function. */
|
|
12794
12830
|
onFrame(listener) {
|
|
@@ -12797,16 +12833,94 @@ var MuxClient = class {
|
|
|
12797
12833
|
this.listeners.delete(listener);
|
|
12798
12834
|
};
|
|
12799
12835
|
}
|
|
12836
|
+
/**
|
|
12837
|
+
* Point the fallback at one open session (or `undefined` to stop it).
|
|
12838
|
+
* While the SSE channel is stalled this client polls that session's
|
|
12839
|
+
* history and re-emits new events as `session/event` frames.
|
|
12840
|
+
*/
|
|
12841
|
+
observe(sessionId) {
|
|
12842
|
+
this.observeSessionId = sessionId;
|
|
12843
|
+
if (sessionId === void 0) {
|
|
12844
|
+
this.stopPolling();
|
|
12845
|
+
return;
|
|
12846
|
+
}
|
|
12847
|
+
if (!this.polling && !this.stopped && !this.sseAlive && this.now() - this.lastDataAt > this.stallThresholdMs) this.startPolling();
|
|
12848
|
+
}
|
|
12800
12849
|
connect() {
|
|
12850
|
+
this.sseAlive = false;
|
|
12801
12851
|
const source = this.sourceFactory(this.url);
|
|
12802
12852
|
this.source = source;
|
|
12803
12853
|
source.onmessage = (event) => {
|
|
12804
12854
|
this.handleMessage(event.data);
|
|
12805
12855
|
};
|
|
12806
12856
|
source.onerror = () => {
|
|
12807
|
-
if (this.stopped && this.source === source)
|
|
12857
|
+
if (this.stopped && this.source === source) {
|
|
12858
|
+
this.closeSource();
|
|
12859
|
+
return;
|
|
12860
|
+
}
|
|
12861
|
+
this.sseAlive = false;
|
|
12862
|
+
if (this.observeSessionId !== void 0) this.startPolling();
|
|
12808
12863
|
};
|
|
12809
12864
|
}
|
|
12865
|
+
startStallChecker() {
|
|
12866
|
+
this.stopStallChecker();
|
|
12867
|
+
this.stallTimer = setInterval(() => {
|
|
12868
|
+
if (this.stopped) return;
|
|
12869
|
+
if (this.observeSessionId === void 0) return;
|
|
12870
|
+
if (this.polling) return;
|
|
12871
|
+
if (this.sseAlive) return;
|
|
12872
|
+
if (this.now() - this.lastDataAt > this.stallThresholdMs) this.startPolling();
|
|
12873
|
+
}, 1e3);
|
|
12874
|
+
}
|
|
12875
|
+
stopStallChecker() {
|
|
12876
|
+
if (this.stallTimer !== void 0) {
|
|
12877
|
+
clearInterval(this.stallTimer);
|
|
12878
|
+
this.stallTimer = void 0;
|
|
12879
|
+
}
|
|
12880
|
+
}
|
|
12881
|
+
startPolling() {
|
|
12882
|
+
if (this.polling || this.stopped) return;
|
|
12883
|
+
this.polling = true;
|
|
12884
|
+
this.pollTick();
|
|
12885
|
+
this.pollTimer = setInterval(() => {
|
|
12886
|
+
this.pollTick();
|
|
12887
|
+
}, this.pollIntervalMs);
|
|
12888
|
+
}
|
|
12889
|
+
stopPolling() {
|
|
12890
|
+
this.polling = false;
|
|
12891
|
+
if (this.pollTimer !== void 0) {
|
|
12892
|
+
clearInterval(this.pollTimer);
|
|
12893
|
+
this.pollTimer = void 0;
|
|
12894
|
+
}
|
|
12895
|
+
}
|
|
12896
|
+
/**
|
|
12897
|
+
* Fetch the latest history page for the observed session and re-emit any
|
|
12898
|
+
* event above the per-session watermark as a `session/event` frame.
|
|
12899
|
+
* Idempotent by seq: listeners (and the fold) never see a duplicate.
|
|
12900
|
+
*/
|
|
12901
|
+
async pollTick() {
|
|
12902
|
+
const sessionId = this.observeSessionId;
|
|
12903
|
+
if (sessionId === void 0) {
|
|
12904
|
+
this.stopPolling();
|
|
12905
|
+
return;
|
|
12906
|
+
}
|
|
12907
|
+
try {
|
|
12908
|
+
const page = await this.pollLatest(sessionId);
|
|
12909
|
+
let maxSeq = this.pollWatermark.get(sessionId) ?? -1;
|
|
12910
|
+
for (const entry of page.events) {
|
|
12911
|
+
const event = entry.event;
|
|
12912
|
+
const seq = typeof event?.seq === "number" ? event.seq : -1;
|
|
12913
|
+
if (seq <= maxSeq) continue;
|
|
12914
|
+
maxSeq = seq;
|
|
12915
|
+
this.emit({
|
|
12916
|
+
type: "session/event",
|
|
12917
|
+
sessionId,
|
|
12918
|
+
event
|
|
12919
|
+
});
|
|
12920
|
+
}
|
|
12921
|
+
this.pollWatermark.set(sessionId, maxSeq);
|
|
12922
|
+
} catch {}
|
|
12923
|
+
}
|
|
12810
12924
|
handleMessage(data) {
|
|
12811
12925
|
if (typeof data !== "string" || data === "") return;
|
|
12812
12926
|
let parsed;
|
|
@@ -12819,8 +12933,14 @@ var MuxClient = class {
|
|
|
12819
12933
|
if (!envelope.success) return;
|
|
12820
12934
|
const frame = muxFrameSchema.safeParse(envelope.data.payload);
|
|
12821
12935
|
if (!frame.success) return;
|
|
12936
|
+
this.sseAlive = true;
|
|
12937
|
+
this.lastDataAt = this.now();
|
|
12938
|
+
if (this.polling) this.stopPolling();
|
|
12939
|
+
this.emit(frame.data);
|
|
12940
|
+
}
|
|
12941
|
+
emit(frame) {
|
|
12822
12942
|
for (const listener of this.listeners) try {
|
|
12823
|
-
listener(frame
|
|
12943
|
+
listener(frame);
|
|
12824
12944
|
} catch {}
|
|
12825
12945
|
}
|
|
12826
12946
|
closeSource() {
|
|
@@ -13495,7 +13615,6 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13495
13615
|
setLoading(false);
|
|
13496
13616
|
const projections = page.projections?.values;
|
|
13497
13617
|
setPermissions(parsePermissionSelect(projections?.["permissions"]));
|
|
13498
|
-
scrollToBottom();
|
|
13499
13618
|
}, (reason) => {
|
|
13500
13619
|
if (cancelled) return;
|
|
13501
13620
|
setError(errorText(reason));
|
|
@@ -13519,14 +13638,20 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13519
13638
|
if (frame.type === "session/projection" && frame.sessionId === session.sessionId && frame.key === "permissions") setPermissions(parsePermissionSelect(frame.value));
|
|
13520
13639
|
});
|
|
13521
13640
|
}, [mux, session.sessionId]);
|
|
13522
|
-
(0, import_react.useEffect)(() => {
|
|
13523
|
-
if (messages.some((message) => message.pending === true)) scrollToBottom();
|
|
13524
|
-
}, [messages]);
|
|
13525
13641
|
const scrollToBottom = (0, import_react.useCallback)(() => {
|
|
13526
13642
|
const el = scrollRef.current;
|
|
13527
13643
|
if (el === void 0) return;
|
|
13528
13644
|
el.scrollTop = el.scrollHeight;
|
|
13529
13645
|
}, []);
|
|
13646
|
+
const lastMessageKeyRef = (0, import_react.useRef)(void 0);
|
|
13647
|
+
(0, import_react.useEffect)(() => {
|
|
13648
|
+
const last = messages[messages.length - 1];
|
|
13649
|
+
if (last === void 0) return;
|
|
13650
|
+
const key = last.seq + ":" + (last.pending === true ? "p" : "f");
|
|
13651
|
+
if (key === lastMessageKeyRef.current) return;
|
|
13652
|
+
lastMessageKeyRef.current = key;
|
|
13653
|
+
scrollToBottom();
|
|
13654
|
+
}, [messages, scrollToBottom]);
|
|
13530
13655
|
/** Load one older page and prepend it. The fold is directional (incremental
|
|
13531
13656
|
* tails only), so the older page folds standalone and concatenates ahead —
|
|
13532
13657
|
* host page boundaries never cut a message, so the seam is exact. */
|
|
@@ -14531,6 +14656,9 @@ function App() {
|
|
|
14531
14656
|
mux.stop();
|
|
14532
14657
|
};
|
|
14533
14658
|
}, []);
|
|
14659
|
+
(0, import_react.useEffect)(() => {
|
|
14660
|
+
muxRef.current?.observe(route.kind === "chat" ? route.session.sessionId : void 0);
|
|
14661
|
+
}, [route]);
|
|
14534
14662
|
const back = (0, import_react.useCallback)(() => {
|
|
14535
14663
|
setRoute((previous) => {
|
|
14536
14664
|
if (previous.kind === "chat") return {
|