@linxin666/dsh-remote-web-ui 0.2.0 → 0.2.1
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 +15 -7
- package/README.zh.md +4 -4
- package/lib/client.js +235 -37
- package/lib/client.js.map +1 -1
- package/lib/index.js +26 -7
- package/lib/mobile.js +209 -39
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +22 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +142 -3
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +11 -0
- package/lib/types/client/RemoteSettingsCard.d.ts +2 -2
- package/lib/types/client/UpdatePanel.d.ts.map +1 -1
- package/lib/types/client/UpdatePanel.js +2 -2
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +2 -0
- package/lib/types/mobile/api.d.ts +10 -0
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +4 -0
- package/lib/types/mobile/messages.d.ts +24 -0
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +60 -23
- package/lib/types/mobile/messages.test.js +49 -1
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +38 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +34 -11
- package/lib/types/mobile/views/SessionListView.d.ts.map +1 -1
- package/lib/types/mobile/views/SessionListView.js +30 -4
- package/lib/types/mobile/views/SessionListView.test.js +58 -1
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +4 -2
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +10 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +22 -2
- package/lib/types/tunnel.d.ts +1 -0
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -2
- package/lib/types/update.d.ts +1 -1
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +1 -1
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +219 -18
- package/src/client/RemoteEntry.tsx +10 -0
- package/src/client/RemoteSettingsCard.tsx +2 -2
- package/src/client/UpdatePanel.tsx +2 -0
- package/src/client/locales.ts +2 -0
- package/src/client/settings-card.module.css +90 -1
- package/src/gate.ts +2 -0
- package/src/mobile/api.ts +14 -1
- package/src/mobile/messages.test.ts +52 -1
- package/src/mobile/messages.ts +65 -22
- package/src/mobile/mobile-styles.ts +38 -0
- package/src/mobile/views/ChatView.tsx +33 -10
- package/src/mobile/views/SessionListView.test.tsx +69 -1
- package/src/mobile/views/SessionListView.tsx +58 -5
- package/src/mobile/views/WorkspaceView.tsx +4 -2
- package/src/mobile-api.ts +9 -1
- package/src/routes.ts +22 -2
- package/src/tunnel.ts +7 -2
- package/src/update.ts +2 -2
|
@@ -88,6 +88,27 @@ export declare function ValueField(props: FieldProps & {
|
|
|
88
88
|
/** Placeholder shown while the draft is empty. */
|
|
89
89
|
placeholder?: string;
|
|
90
90
|
}): import("react").JSX.Element;
|
|
91
|
+
interface SelectOption {
|
|
92
|
+
value: string;
|
|
93
|
+
label: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The shared dual-mode select control. While an appearance skin is active it
|
|
97
|
+
* renders the legacy native `<select>` untouched, so element-level skin
|
|
98
|
+
* selectors keep working; under the default appearance it renders a
|
|
99
|
+
* self-drawn `role="listbox"` popup whose open/close is transition-animated.
|
|
100
|
+
* Staged cards reach it through BooleanField/ChoiceField; immediate-apply
|
|
101
|
+
* editors (the side-card prefs) bind it directly through onEdit.
|
|
102
|
+
* 双模式下拉框:皮肤激活时用原生 select,默认外观用自绘动画弹层。
|
|
103
|
+
*/
|
|
104
|
+
export declare function SelectField(props: {
|
|
105
|
+
id: string;
|
|
106
|
+
options: ReadonlyArray<SelectOption>;
|
|
107
|
+
value: string;
|
|
108
|
+
disabled: boolean;
|
|
109
|
+
invalid: boolean;
|
|
110
|
+
onEdit: (text: string) => void;
|
|
111
|
+
}): import("react").JSX.Element;
|
|
91
112
|
/** A staged boolean field: 继承 / 开 / 关. */
|
|
92
113
|
export declare function BooleanField(props: FieldProps & {
|
|
93
114
|
/** Copy for the inherit option. */
|
|
@@ -107,4 +128,5 @@ export declare function ChoiceField(props: FieldProps & {
|
|
|
107
128
|
label: string;
|
|
108
129
|
}>;
|
|
109
130
|
}): import("react").JSX.Element;
|
|
131
|
+
export {};
|
|
110
132
|
//# sourceMappingURL=PluginSettingsCard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* it. Inlined into each consumer's client bundle; mirrors the official
|
|
9
9
|
* ui-plugin-config PluginCard in a self-contained slice.
|
|
10
10
|
*/
|
|
11
|
-
import { useState } from 'react';
|
|
11
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
12
12
|
import css from './settings-card.module.css';
|
|
13
13
|
/** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
|
|
14
14
|
export const CARD_COPY_KEYS = [
|
|
@@ -65,15 +65,154 @@ export function ValueField(props) {
|
|
|
65
65
|
? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
|
|
66
66
|
: null] }), _jsx("input", { id: props.id, className: props.invalid ? css.inputInvalid : css.input, type: "text", ...props.numeric === true ? { inputMode: 'numeric' } : {}, ...props.invalid ? { 'aria-invalid': true } : {}, value: props.text, placeholder: props.placeholder ?? '', disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); } }), _jsx("p", { className: props.invalid ? css.invalid : css.hint, children: props.invalid ? props.invalidLabel : props.hint })] }));
|
|
67
67
|
}
|
|
68
|
+
const NON_SKIN_BODY_MARKERS = new Set(['dshSkinCenter', 'dshSidebarCollapsed']);
|
|
69
|
+
// 检测使用使用皮肤,用了皮肤退回原生select样式,防止样式冲突。默认外观下使用优化后的select样式。
|
|
70
|
+
function isSkinActive() {
|
|
71
|
+
const datasetList = Object.keys(document.body.dataset);
|
|
72
|
+
const isActive = datasetList.some(key => key.startsWith('dsh') && !NON_SKIN_BODY_MARKERS.has(key));
|
|
73
|
+
return isActive;
|
|
74
|
+
}
|
|
75
|
+
const SELECT_CLOSE_MS = 100;
|
|
76
|
+
/**
|
|
77
|
+
* The shared dual-mode select control. While an appearance skin is active it
|
|
78
|
+
* renders the legacy native `<select>` untouched, so element-level skin
|
|
79
|
+
* selectors keep working; under the default appearance it renders a
|
|
80
|
+
* self-drawn `role="listbox"` popup whose open/close is transition-animated.
|
|
81
|
+
* Staged cards reach it through BooleanField/ChoiceField; immediate-apply
|
|
82
|
+
* editors (the side-card prefs) bind it directly through onEdit.
|
|
83
|
+
* 双模式下拉框:皮肤激活时用原生 select,默认外观用自绘动画弹层。
|
|
84
|
+
*/
|
|
85
|
+
export function SelectField(props) {
|
|
86
|
+
const { id, options, value } = props;
|
|
87
|
+
const [open, setOpen] = useState(false);
|
|
88
|
+
const [closing, setClosing] = useState(false);
|
|
89
|
+
const [phase, setPhase] = useState('initial');
|
|
90
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
91
|
+
const closeTimer = useRef(undefined);
|
|
92
|
+
const wrapRef = useRef(null);
|
|
93
|
+
const popupRef = useRef(null);
|
|
94
|
+
const currentIndex = () => {
|
|
95
|
+
const index = options.findIndex(option => option.value === value);
|
|
96
|
+
return index >= 0 ? index : 0;
|
|
97
|
+
};
|
|
98
|
+
const close = useCallback(() => {
|
|
99
|
+
if (closeTimer.current !== undefined)
|
|
100
|
+
clearTimeout(closeTimer.current);
|
|
101
|
+
setClosing(true);
|
|
102
|
+
closeTimer.current = setTimeout(() => {
|
|
103
|
+
setClosing(false);
|
|
104
|
+
setOpen(false);
|
|
105
|
+
}, SELECT_CLOSE_MS);
|
|
106
|
+
}, []);
|
|
107
|
+
const openPopup = () => {
|
|
108
|
+
if (closeTimer.current !== undefined)
|
|
109
|
+
clearTimeout(closeTimer.current);
|
|
110
|
+
setActiveIndex(currentIndex());
|
|
111
|
+
setPhase('initial');
|
|
112
|
+
setClosing(false);
|
|
113
|
+
setOpen(true);
|
|
114
|
+
};
|
|
115
|
+
const commit = (index) => {
|
|
116
|
+
const option = options[index];
|
|
117
|
+
if (option)
|
|
118
|
+
props.onEdit(option.value);
|
|
119
|
+
close();
|
|
120
|
+
};
|
|
121
|
+
const onTriggerClick = () => {
|
|
122
|
+
if (props.disabled)
|
|
123
|
+
return;
|
|
124
|
+
if (open && !closing)
|
|
125
|
+
close();
|
|
126
|
+
else
|
|
127
|
+
openPopup();
|
|
128
|
+
};
|
|
129
|
+
const onKeyDown = (event) => {
|
|
130
|
+
if (props.disabled)
|
|
131
|
+
return;
|
|
132
|
+
const count = options.length;
|
|
133
|
+
switch (event.key) {
|
|
134
|
+
case 'ArrowDown':
|
|
135
|
+
case 'ArrowUp':
|
|
136
|
+
case 'Enter':
|
|
137
|
+
case ' ':
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
if (!open) {
|
|
140
|
+
openPopup();
|
|
141
|
+
}
|
|
142
|
+
else if (!closing) {
|
|
143
|
+
if (event.key === 'ArrowDown')
|
|
144
|
+
setActiveIndex(index => (index + 1) % count);
|
|
145
|
+
else if (event.key === 'ArrowUp')
|
|
146
|
+
setActiveIndex(index => (index - 1 + count) % count);
|
|
147
|
+
else
|
|
148
|
+
commit(activeIndex);
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
case 'Escape':
|
|
152
|
+
if (open) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
event.stopPropagation();
|
|
155
|
+
close();
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
case 'Tab':
|
|
159
|
+
if (open)
|
|
160
|
+
close();
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
useEffect(() => () => {
|
|
165
|
+
if (closeTimer.current !== undefined)
|
|
166
|
+
clearTimeout(closeTimer.current);
|
|
167
|
+
}, []);
|
|
168
|
+
useLayoutEffect(() => {
|
|
169
|
+
if (open && !closing && phase === 'initial') {
|
|
170
|
+
void popupRef.current?.offsetHeight;
|
|
171
|
+
setPhase('open');
|
|
172
|
+
}
|
|
173
|
+
}, [open, closing, phase]);
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (!open)
|
|
176
|
+
return;
|
|
177
|
+
const onPointerDown = (event) => {
|
|
178
|
+
const target = event.target;
|
|
179
|
+
if (target instanceof Node && !wrapRef.current?.contains(target))
|
|
180
|
+
close();
|
|
181
|
+
};
|
|
182
|
+
document.addEventListener('pointerdown', onPointerDown);
|
|
183
|
+
return () => document.removeEventListener('pointerdown', onPointerDown);
|
|
184
|
+
}, [open, close]);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (props.disabled && open)
|
|
187
|
+
close();
|
|
188
|
+
}, [props.disabled, open, close]);
|
|
189
|
+
// 使用了皮肤则退回原生select样式防止样式冲突。
|
|
190
|
+
if (isSkinActive()) {
|
|
191
|
+
return (_jsx("select", { id: id, className: css.select, value: value, disabled: props.disabled, onChange: (event) => { props.onEdit(event.target.value); }, children: options.map(option => (_jsx("option", { value: option.value, children: option.label }, option.value))) }));
|
|
192
|
+
}
|
|
193
|
+
const label = options.find(option => option.value === value)?.label ?? '';
|
|
194
|
+
const popupClass = closing
|
|
195
|
+
? `${css.selectPopup} ${css.selectPopupClose}`
|
|
196
|
+
: phase === 'open'
|
|
197
|
+
? `${css.selectPopup} ${css.selectPopupOpen}`
|
|
198
|
+
: css.selectPopup;
|
|
199
|
+
return (_jsxs("div", { className: css.selectWrap, ref: wrapRef, children: [_jsxs("button", { type: "button", id: id, className: `${css.select} ${css.selectButton}`, disabled: props.disabled, "aria-haspopup": "listbox", "aria-expanded": open, "aria-activedescendant": open ? `${id}-o${activeIndex}` : undefined, "aria-invalid": props.invalid || undefined, onClick: onTriggerClick, onKeyDown: onKeyDown, children: [_jsx("span", { className: css.selectLabel, children: label }), _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.selectChevron} ${css.selectChevronOpen}` : css.selectChevron, "aria-hidden": "true", children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
|
|
200
|
+
? (_jsx("div", { className: popupClass, role: "listbox", ref: popupRef, children: options.map((option, index) => (_jsx("div", { id: `${id}-o${index}`, role: "option", "aria-selected": option.value === value, className: `${css.selectOption}${option.value === value ? ` ${css.selectOptionSelected}` : ''}${index === activeIndex && !closing ? ` ${css.selectOptionActive}` : ''}`, onClick: () => { commit(index); }, children: option.label }, option.value))) }))
|
|
201
|
+
: null] }));
|
|
202
|
+
}
|
|
68
203
|
/** A staged boolean field: 继承 / 开 / 关. */
|
|
69
204
|
export function BooleanField(props) {
|
|
70
205
|
return (_jsxs("div", { className: css.field, children: [_jsxs("div", { className: css.head, children: [_jsx("label", { className: css.label, htmlFor: props.id, children: props.label }), props.overridden
|
|
71
206
|
? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
|
|
72
|
-
: null] }),
|
|
207
|
+
: null] }), _jsx(SelectField, { id: props.id, options: [
|
|
208
|
+
{ value: '', label: props.inheritLabel },
|
|
209
|
+
{ value: 'true', label: props.onLabel },
|
|
210
|
+
{ value: 'false', label: props.offLabel },
|
|
211
|
+
], value: props.text, disabled: props.disabled, invalid: props.invalid, onEdit: props.onEdit }), _jsx("p", { className: css.hint, children: props.hint })] }));
|
|
73
212
|
}
|
|
74
213
|
/** A staged enumerated field rendered as a select. */
|
|
75
214
|
export function ChoiceField(props) {
|
|
76
215
|
return (_jsxs("div", { className: css.field, children: [_jsxs("div", { className: css.head, children: [_jsx("label", { className: css.label, htmlFor: props.id, children: props.label }), props.overridden
|
|
77
216
|
? (_jsxs("span", { className: css.badges, children: [_jsx("span", { className: css.badge, children: props.overriddenLabel }), _jsx("button", { type: "button", className: css.reset, disabled: props.disabled, onClick: props.onReset, children: props.resetLabel })] }))
|
|
78
|
-
: null] }),
|
|
217
|
+
: null] }), _jsx(SelectField, { id: props.id, options: [{ value: '', label: props.inheritLabel }, ...props.choices], value: props.text, disabled: props.disabled, invalid: props.invalid, onEdit: props.onEdit }), _jsx("p", { className: props.invalid ? css.invalid : css.hint, children: props.invalid ? props.invalidLabel : props.hint })] }));
|
|
79
218
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAQjF,wEAAwE;AACxE,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,gBAAgB,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAwBrF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAQjF,wEAAwE;AACxE,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,gBAAgB,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAwBrF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BAuLvE"}
|
|
@@ -52,6 +52,10 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
52
52
|
useEffect(() => { stateRef.current = state; }, [state]);
|
|
53
53
|
const [copied, setCopied] = useState(false);
|
|
54
54
|
const eventSource = useRef(undefined);
|
|
55
|
+
// Generation counter for the open flow: closing (or re-opening) the panel
|
|
56
|
+
// bumps it, so an in-flight issue() that resolves after a close does not
|
|
57
|
+
// spawn a stray EventSource.
|
|
58
|
+
const openSeq = useRef(0);
|
|
55
59
|
// The current workspace (the recent-workspace projection the shell's New
|
|
56
60
|
// Session flow targets) — the deep-link target for the phone.
|
|
57
61
|
const workspaceId = useWorkspaces(s => s.recentWorkspaceId);
|
|
@@ -98,8 +102,14 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
98
102
|
};
|
|
99
103
|
}, [workspaceId]);
|
|
100
104
|
const openPanel = useCallback(async () => {
|
|
105
|
+
const seq = ++openSeq.current;
|
|
101
106
|
setOpen(true);
|
|
102
107
|
const next = await mint();
|
|
108
|
+
// A close (or re-open) during the await invalidates this issue: skip the
|
|
109
|
+
// state write and the stream so a panel closed mid-mint neither leaks an
|
|
110
|
+
// EventSource nor resurrects a stale QR.
|
|
111
|
+
if (seq !== openSeq.current)
|
|
112
|
+
return;
|
|
103
113
|
setState(next);
|
|
104
114
|
// Live status: the desktop panel mirrors the pairing service state. The
|
|
105
115
|
// stream makes sense in the ready state and on the lan-required banner —
|
|
@@ -137,6 +147,7 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
137
147
|
};
|
|
138
148
|
}, [mint]);
|
|
139
149
|
const closePanel = useCallback(() => {
|
|
150
|
+
openSeq.current += 1;
|
|
140
151
|
closeEventSource();
|
|
141
152
|
setOpen(false);
|
|
142
153
|
}, [closeEventSource]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The remote-control settings card: pairing security and device limits.
|
|
3
|
-
* Registers into the `
|
|
4
|
-
*
|
|
3
|
+
* Registers into the `web-ui.plugin.item` child slot the Web UI plugin group
|
|
4
|
+
* renders, bound to the `remote-web-ui` settings namespace.
|
|
5
5
|
*/
|
|
6
6
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
7
7
|
import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UpdatePanel.d.ts","sourceRoot":"","sources":["../../../src/client/UpdatePanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGjE,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,IAAI,IAAI,CAAA;IACf,8CAA8C;IAC9C,SAAS,IAAI,IAAI,CAAA;CAClB;AAYD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"UpdatePanel.d.ts","sourceRoot":"","sources":["../../../src/client/UpdatePanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGjE,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,IAAI,IAAI,CAAA;IACf,8CAA8C;IAC9C,SAAS,IAAI,IAAI,CAAA;CAClB;AAYD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,gBAAgB,+BA0C5E"}
|
|
@@ -18,7 +18,7 @@ export function UpdatePanel({ t, view, onClose, onRecheck }) {
|
|
|
18
18
|
const status = view.kind === "result" || view.kind === "updating" ? view.status : undefined;
|
|
19
19
|
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
|
|
20
20
|
const subtitle = subtitleOf(t, view);
|
|
21
|
-
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(IconCloseOutline16, {}) })] }), view.kind === "checking" && _jsx("p", { className: css.updateStatus, children: t("update.checking") }), view.kind === "result" && status !== undefined && _jsx(ResultBody, { t: t, status: status }), view.kind === "updating" && status !== undefined && (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.updating", { name: anchorName(status) ?? "", version: anchorLatest(status) ?? "" }) }), _jsx(PackageList, { status: status })] })), view.kind === "done" && _jsx(DoneBody, { t: t, result: view.result }), view.kind === "error" && (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: view.message }), view.detail !== undefined && _jsx("pre", { className: css.updateOutput, children: view.detail })] })), (view.kind === "done" || view.kind === "error") && (_jsx("div", { className: css.updateActions, children: _jsxs("button", { type: "button", className: css.updateRetry, onClick: onRecheck, children: [_jsx(IconRefreshOutline16, {}), " ", t("update.retry")] }) }))] }));
|
|
21
|
+
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(IconCloseOutline16, {}) })] }), view.kind === "checking" && _jsx("p", { className: css.updateStatus, children: t("update.checking") }), view.kind === "result" && status !== undefined && _jsx(ResultBody, { t: t, status: status }), view.kind === "updating" && status !== undefined && (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.updating", { name: anchorName(status) ?? "", version: anchorLatest(status) ?? "" }) }), _jsx("p", { className: css.updateDetail, children: t("update.cooldownNotice") }), _jsx(PackageList, { status: status })] })), view.kind === "done" && _jsx(DoneBody, { t: t, result: view.result }), view.kind === "error" && (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: view.message }), view.detail !== undefined && _jsx("pre", { className: css.updateOutput, children: view.detail })] })), (view.kind === "done" || view.kind === "error") && (_jsx("div", { className: css.updateActions, children: _jsxs("button", { type: "button", className: css.updateRetry, onClick: onRecheck, children: [_jsx(IconRefreshOutline16, {}), " ", t("update.retry")] }) }))] }));
|
|
22
22
|
}
|
|
23
23
|
/** The subtitle copy per view state (absent on plain results). */
|
|
24
24
|
function subtitleOf(t, view) {
|
|
@@ -50,7 +50,7 @@ function ResultBody({ t, status }) {
|
|
|
50
50
|
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.offline") }), _jsx("p", { className: css.updateDetail, children: t("update.offlineDetail") })] }));
|
|
51
51
|
}
|
|
52
52
|
if (status.outdated) {
|
|
53
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.found") }), _jsx("p", { className: css.updateDetail, children: anchor !== undefined ? t("update.foundDetail", { name: anchor, version: latest ?? "" }) : "" })] }));
|
|
53
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.found") }), _jsx("p", { className: css.updateDetail, children: anchor !== undefined ? t("update.foundDetail", { name: anchor, version: latest ?? "" }) : "" }), _jsx("p", { className: css.updateDetail, children: t("update.cooldownNotice") })] }));
|
|
54
54
|
}
|
|
55
55
|
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.upToDate") }), anchor !== undefined && latest !== undefined && (_jsx("p", { className: css.updateDetail, children: t("update.upToDateDetail", { name: anchor, version: latest }) })), _jsx(PackageList, { status: status })] }));
|
|
56
56
|
}
|
|
@@ -79,6 +79,7 @@ export declare const zh: {
|
|
|
79
79
|
'update.found': string;
|
|
80
80
|
'update.foundDetail': string;
|
|
81
81
|
'update.updating': string;
|
|
82
|
+
'update.cooldownNotice': string;
|
|
82
83
|
'update.updatingTitle': string;
|
|
83
84
|
'update.done': string;
|
|
84
85
|
'update.doneDetail': string;
|
|
@@ -188,6 +189,7 @@ export declare const en: {
|
|
|
188
189
|
'update.found': string;
|
|
189
190
|
'update.foundDetail': string;
|
|
190
191
|
'update.updating': string;
|
|
192
|
+
'update.cooldownNotice': string;
|
|
191
193
|
'update.updatingTitle': string;
|
|
192
194
|
'update.done': string;
|
|
193
195
|
'update.doneDetail': string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0GmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0GsB,CAAA"}
|
|
@@ -81,6 +81,7 @@ export const zh = {
|
|
|
81
81
|
'update.found': '发现新版本',
|
|
82
82
|
'update.foundDetail': '{name} 有新版本 v{version},正在自动更新…',
|
|
83
83
|
'update.updating': '正在更新 {name} 至 v{version},可能需要 1-2 分钟…',
|
|
84
|
+
'update.cooldownNotice': '本次更新由你主动触发,将跳过 pnpm 的 24 小时发布冷却期(供应链保护)直接安装所选版本。',
|
|
84
85
|
'update.updatingTitle': '正在自动更新…',
|
|
85
86
|
'update.done': '更新完成',
|
|
86
87
|
'update.doneDetail': '所有组件已更新到最新版本。',
|
|
@@ -188,6 +189,7 @@ export const en = {
|
|
|
188
189
|
'update.found': 'A new version is available',
|
|
189
190
|
'update.foundDetail': '{name} has a newer release v{version}; updating automatically…',
|
|
190
191
|
'update.updating': 'Updating {name} to v{version}; this may take 1-2 minutes…',
|
|
192
|
+
'update.cooldownNotice': 'This user-initiated update bypasses pnpm\'s 24-hour release cooldown (supply-chain protection) and installs the selected version immediately.',
|
|
191
193
|
'update.updatingTitle': 'Updating automatically…',
|
|
192
194
|
'update.done': 'Update complete',
|
|
193
195
|
'update.doneDetail': 'All components are up to date.',
|
package/lib/types/gate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASvF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAKlE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,oBAAoB,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,EACtD,OAAO,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,GACxC,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASvF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAKlE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,oBAAoB,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,EACtD,OAAO,GAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAQ,GACxC,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAW9H;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAIhG"}
|
package/lib/types/gate.js
CHANGED
|
@@ -86,6 +86,8 @@ export function makeGateListener(service, requirePairingForLan = true, enabled =
|
|
|
86
86
|
const require = typeof requirePairingForLan === 'function' ? requirePairingForLan() : requirePairingForLan;
|
|
87
87
|
if (!require)
|
|
88
88
|
return next();
|
|
89
|
+
// The paired-device cookie is a full host-API credential: it passes this
|
|
90
|
+
// gate for the whole ApiProxy surface, not just the /m/api allowlist.
|
|
89
91
|
return isPairedDeviceRequest(service, request) ? next() : false;
|
|
90
92
|
};
|
|
91
93
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* rpc/mux layers).
|
|
6
6
|
*/
|
|
7
7
|
import type { WorkspaceView } from '@deepseek-ai/dsh-host-apiproxy/api/workspace';
|
|
8
|
+
import type { AgentPresetEntry } from '@deepseek-ai/dsh-host-apiproxy/api/agent-presets';
|
|
8
9
|
import type { SessionSummary, SessionModels, SessionProjectionsBlock } from '@deepseek-ai/dsh-host-apiproxy/api/sessions';
|
|
9
10
|
/** One session.list page. */
|
|
10
11
|
export interface SessionPage {
|
|
@@ -19,6 +20,12 @@ export interface CreatedSession {
|
|
|
19
20
|
/** The composition the new session runs (echoed so the caller can label it). */
|
|
20
21
|
agentPreset?: string;
|
|
21
22
|
}
|
|
23
|
+
/** Agent presets available when composing a new session. */
|
|
24
|
+
export interface AgentPresetRoster {
|
|
25
|
+
presets: readonly AgentPresetEntry[];
|
|
26
|
+
authorable: boolean;
|
|
27
|
+
hasDocument: boolean;
|
|
28
|
+
}
|
|
22
29
|
/** One history page (already bounded to whole messages by the host). */
|
|
23
30
|
export interface HistoryPage {
|
|
24
31
|
events: import('@deepseek-ai/dsh-host-apiproxy/api/sessions').HistoryEntry[];
|
|
@@ -40,6 +47,8 @@ export declare function listWorkspaces(): Promise<WorkspaceView[]>;
|
|
|
40
47
|
export declare function fetchMobilePreferences(): Promise<MobilePreferences>;
|
|
41
48
|
/** One session.list page; omit the cursor for the first page. */
|
|
42
49
|
export declare function listSessions(cursor?: string): Promise<SessionPage>;
|
|
50
|
+
/** Read the available agent compositions for a new session. */
|
|
51
|
+
export declare function listAgentPresets(): Promise<AgentPresetRoster>;
|
|
43
52
|
/**
|
|
44
53
|
* Create a blank session (entity birth precedes the first message). Name a
|
|
45
54
|
* workspace to attach it there, or a cwd; omitting both uses the host cwd.
|
|
@@ -47,6 +56,7 @@ export declare function listSessions(cursor?: string): Promise<SessionPage>;
|
|
|
47
56
|
export declare function createSession(options?: {
|
|
48
57
|
workspaceId?: string;
|
|
49
58
|
cwd?: string;
|
|
59
|
+
agentPreset?: string;
|
|
50
60
|
}): Promise<CreatedSession>;
|
|
51
61
|
/** One history window; omit beforeSeq for the tail page, pass a signal to abort. */
|
|
52
62
|
export declare function history(sessionId: string, beforeSeq?: number, maxMessages?: number, signal?: AbortSignal): Promise<HistoryPage>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/mobile/api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAA;AACjF,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAA;AAGzH,6BAA6B;AAC7B,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,6CAA6C,EAAE,YAAY,EAAE,CAAA;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,uBAAuB,CAAA;CACtC;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED,kEAAkE;AAClE,wBAAsB,cAAc,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAG/D;AAED,yFAAyF;AACzF,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAEzE;AAED,iEAAiE;AACjE,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAExE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/mobile/api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kDAAkD,CAAA;AACxF,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAA;AAGzH,6BAA6B;AAC7B,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACpC,UAAU,EAAE,OAAO,CAAA;IACnB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,6CAA6C,EAAE,YAAY,EAAE,CAAA;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,uBAAuB,CAAA;CACtC;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED,kEAAkE;AAClE,wBAAsB,cAAc,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAG/D;AAED,yFAAyF;AACzF,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAEzE;AAED,iEAAiE;AACjE,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAExE;AAED,+DAA+D;AAC/D,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAEnE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACzE,OAAO,CAAC,cAAc,CAAC,CAEzB;AAED,oFAAoF;AACpF,wBAAsB,OAAO,CAC3B,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,SAAK,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,CAMtB;AAED,qEAAqE;AACrE,wBAAsB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3E;AAED,wEAAwE;AACxE,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnF;AAED,oFAAoF;AACpF,wBAAsB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAEtE;AAED,2FAA2F;AAC3F,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE,OAAO,CAAC;IAAE,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAOtF"}
|
package/lib/types/mobile/api.js
CHANGED
|
@@ -18,6 +18,10 @@ export async function fetchMobilePreferences() {
|
|
|
18
18
|
export async function listSessions(cursor) {
|
|
19
19
|
return await callUnary('session.list', cursor === undefined ? {} : { cursor });
|
|
20
20
|
}
|
|
21
|
+
/** Read the available agent compositions for a new session. */
|
|
22
|
+
export async function listAgentPresets() {
|
|
23
|
+
return await callUnary('agentPreset.list', {});
|
|
24
|
+
}
|
|
21
25
|
/**
|
|
22
26
|
* Create a blank session (entity birth precedes the first message). Name a
|
|
23
27
|
* workspace to attach it there, or a cwd; omitting both uses the host cwd.
|
|
@@ -114,4 +114,28 @@ export interface WireEvent {
|
|
|
114
114
|
* @returns messages sorted by seq.
|
|
115
115
|
*/
|
|
116
116
|
export declare function foldEvents(events: readonly WireEvent[], existing?: readonly RenderMessage[]): RenderMessage[];
|
|
117
|
+
/**
|
|
118
|
+
* Incremental folder for one message stream. Live chat folds one event at a
|
|
119
|
+
* time; rebuilding the five index maps by scanning every message per event
|
|
120
|
+
* made that path O(n) per event (O(n * events) per turn). A folder keeps the
|
|
121
|
+
* indexes alive across folds, applies each event in O(1) map operations, and
|
|
122
|
+
* returns the previous snapshot identity unchanged when nothing applied, so
|
|
123
|
+
* React skips the re-render entirely. Replayed events are no-ops: the maxSeq
|
|
124
|
+
* watermark advanced by the first application skips them, which also makes a
|
|
125
|
+
* double-invoked React state updater harmless.
|
|
126
|
+
*/
|
|
127
|
+
export declare class EventFolder {
|
|
128
|
+
private state;
|
|
129
|
+
private snapshotList;
|
|
130
|
+
/** @param initial - seed rows (history tail load); omit for an empty stream. */
|
|
131
|
+
constructor(initial?: readonly RenderMessage[]);
|
|
132
|
+
/** Fold one batch incrementally; returns the current snapshot list. */
|
|
133
|
+
fold(events: readonly WireEvent[]): RenderMessage[];
|
|
134
|
+
/** Replace the whole stream (history reload / session switch). */
|
|
135
|
+
seed(messages: readonly RenderMessage[]): void;
|
|
136
|
+
/** Prepend an older history page (exact seam; no overlapping seqs). */
|
|
137
|
+
prepend(older: readonly RenderMessage[]): void;
|
|
138
|
+
/** Current snapshot list; a fresh copy whenever the folder changed. */
|
|
139
|
+
snapshot(): RenderMessage[];
|
|
140
|
+
}
|
|
117
141
|
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/mobile/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;IACD,sFAAsF;IACtF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAA;CAC1B;
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/mobile/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;IACD,sFAAsF;IACtF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAA;CAC1B;AAweD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAE7G;AAED;;;;;;;;;GASG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,YAAY,CAA6B;IAEjD,gFAAgF;IAChF,YAAY,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,EAE7C;IAED,uEAAuE;IACvE,IAAI,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,aAAa,EAAE,CAWlD;IAED,kEAAkE;IAClE,IAAI,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAG7C;IAED,uEAAuE;IACvE,OAAO,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAG7C;IAED,uEAAuE;IACvE,QAAQ,IAAI,aAAa,EAAE,CAI1B;CACF"}
|
|
@@ -83,17 +83,6 @@ function chunkTarget(data) {
|
|
|
83
83
|
result.step = step;
|
|
84
84
|
return result;
|
|
85
85
|
}
|
|
86
|
-
/** Max message seq across an existing list, or -1 for an empty list. */
|
|
87
|
-
function watermarkOf(existing) {
|
|
88
|
-
if (existing === undefined)
|
|
89
|
-
return -1;
|
|
90
|
-
let maxSeq = -1;
|
|
91
|
-
for (const message of existing) {
|
|
92
|
-
if (message.seq > maxSeq)
|
|
93
|
-
maxSeq = message.seq;
|
|
94
|
-
}
|
|
95
|
-
return maxSeq;
|
|
96
|
-
}
|
|
97
86
|
function createState(existing) {
|
|
98
87
|
const messages = existing === undefined ? [] : [...existing];
|
|
99
88
|
const state = {
|
|
@@ -103,8 +92,11 @@ function createState(existing) {
|
|
|
103
92
|
turnStepMessage: new Map(),
|
|
104
93
|
messageTurn: new Map(),
|
|
105
94
|
toolNames: new Map(),
|
|
95
|
+
maxSeq: -1,
|
|
106
96
|
};
|
|
107
97
|
for (const message of messages) {
|
|
98
|
+
if (message.seq > state.maxSeq)
|
|
99
|
+
state.maxSeq = message.seq;
|
|
108
100
|
state.byId.set(message.id, message);
|
|
109
101
|
if (message.kind !== 'assistant')
|
|
110
102
|
continue;
|
|
@@ -167,6 +159,8 @@ function retargetTurnStep(state, key, oldMessage, next) {
|
|
|
167
159
|
}
|
|
168
160
|
/** Fold one event into the working state. Assumes the event passes the watermark. */
|
|
169
161
|
function applyEvent(state, event) {
|
|
162
|
+
if (event.seq > state.maxSeq)
|
|
163
|
+
state.maxSeq = event.seq;
|
|
170
164
|
switch (event.type) {
|
|
171
165
|
case 'user/message':
|
|
172
166
|
applyUserMessage(state, event);
|
|
@@ -489,18 +483,61 @@ function applyTurnEnd(state, event) {
|
|
|
489
483
|
* @returns messages sorted by seq.
|
|
490
484
|
*/
|
|
491
485
|
export function foldEvents(events, existing) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
486
|
+
return new EventFolder(existing).fold(events);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Incremental folder for one message stream. Live chat folds one event at a
|
|
490
|
+
* time; rebuilding the five index maps by scanning every message per event
|
|
491
|
+
* made that path O(n) per event (O(n * events) per turn). A folder keeps the
|
|
492
|
+
* indexes alive across folds, applies each event in O(1) map operations, and
|
|
493
|
+
* returns the previous snapshot identity unchanged when nothing applied, so
|
|
494
|
+
* React skips the re-render entirely. Replayed events are no-ops: the maxSeq
|
|
495
|
+
* watermark advanced by the first application skips them, which also makes a
|
|
496
|
+
* double-invoked React state updater harmless.
|
|
497
|
+
*/
|
|
498
|
+
export class EventFolder {
|
|
499
|
+
state;
|
|
500
|
+
snapshotList;
|
|
501
|
+
/** @param initial - seed rows (history tail load); omit for an empty stream. */
|
|
502
|
+
constructor(initial) {
|
|
503
|
+
this.state = createState(initial);
|
|
499
504
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
505
|
+
/** Fold one batch incrementally; returns the current snapshot list. */
|
|
506
|
+
fold(events) {
|
|
507
|
+
const sorted = [...events].sort((a, b) => a.seq - b.seq);
|
|
508
|
+
let applied = false;
|
|
509
|
+
for (const event of sorted) {
|
|
510
|
+
if (event.seq <= this.state.maxSeq)
|
|
511
|
+
continue;
|
|
512
|
+
applyEvent(this.state, event);
|
|
513
|
+
applied = true;
|
|
514
|
+
}
|
|
515
|
+
if (!applied && this.snapshotList !== undefined)
|
|
516
|
+
return this.snapshotList;
|
|
517
|
+
this.snapshotList = snapshotOf(this.state);
|
|
518
|
+
return this.snapshotList;
|
|
519
|
+
}
|
|
520
|
+
/** Replace the whole stream (history reload / session switch). */
|
|
521
|
+
seed(messages) {
|
|
522
|
+
this.state = createState(messages);
|
|
523
|
+
this.snapshotList = undefined;
|
|
524
|
+
}
|
|
525
|
+
/** Prepend an older history page (exact seam; no overlapping seqs). */
|
|
526
|
+
prepend(older) {
|
|
527
|
+
this.state = createState([...older, ...this.state.messages]);
|
|
528
|
+
this.snapshotList = undefined;
|
|
529
|
+
}
|
|
530
|
+
/** Current snapshot list; a fresh copy whenever the folder changed. */
|
|
531
|
+
snapshot() {
|
|
532
|
+
if (this.snapshotList !== undefined)
|
|
533
|
+
return this.snapshotList;
|
|
534
|
+
this.snapshotList = snapshotOf(this.state);
|
|
535
|
+
return this.snapshotList;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/** Copy the folder's rows and keep them seq-ordered (skips re-sorting the common ordered case). */
|
|
539
|
+
function snapshotOf(state) {
|
|
540
|
+
const out = [...state.messages];
|
|
504
541
|
let ordered = true;
|
|
505
542
|
for (let index = 1; index < out.length; index += 1) {
|
|
506
543
|
const prev = out[index - 1];
|
|
@@ -510,5 +547,5 @@ export function foldEvents(events, existing) {
|
|
|
510
547
|
break;
|
|
511
548
|
}
|
|
512
549
|
}
|
|
513
|
-
return ordered ? out :
|
|
550
|
+
return ordered ? out : out.sort((a, b) => a.seq - b.seq || (a.id < b.id ? -1 : 1));
|
|
514
551
|
}
|