@linxin666/dsh-remote-web-ui 0.1.11 → 0.1.13
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 +6 -0
- package/README.md +12 -1
- package/README.zh.md +171 -0
- package/lib/client.js +170 -99
- package/lib/client.js.map +1 -1
- package/lib/index.js +224 -4
- package/lib/mobile.js +109 -33
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +4 -0
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +3 -1
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +10 -2
- package/lib/types/client/deep-link.js +10 -5
- package/lib/types/client/index.d.ts +13 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +2 -1
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +8 -0
- package/lib/types/client/update-api.d.ts +10 -0
- package/lib/types/client/update-api.d.ts.map +1 -1
- package/lib/types/client/update-api.js +22 -2
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +7 -1
- package/lib/types/mobile/api.d.ts +9 -2
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +7 -3
- 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 +3 -0
- package/lib/types/mobile/views/App.d.ts +3 -1
- package/lib/types/mobile/views/App.d.ts.map +1 -1
- package/lib/types/mobile/views/App.js +18 -3
- package/lib/types/mobile/views/ChatView.d.ts +6 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +95 -7
- package/lib/types/mobile/views/ChatView.test.js +161 -3
- package/lib/types/mobile/views/WorkspaceView.d.ts +3 -1
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +9 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +2 -0
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +1 -0
- package/lib/types/mobile/views/WorkspaceView.test.js +56 -0
- package/lib/types/mobile-api.d.ts +2 -0
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +17 -2
- package/package.json +2 -12
- package/src/client/PluginSettingsCard.tsx +30 -5
- package/src/client/RemoteSettingsCard.tsx +18 -0
- package/src/client/UpdateEntry.tsx +10 -2
- package/src/client/deep-link.ts +10 -4
- package/src/client/index.ts +15 -2
- package/src/client/locales.ts +8 -0
- package/src/client/settings-card.module.css +139 -144
- package/src/client/update-api.ts +22 -2
- package/src/index.ts +14 -1
- package/src/mobile/api.ts +14 -2
- package/src/mobile/mobile-styles.ts +3 -0
- package/src/mobile/views/App.tsx +22 -3
- package/src/mobile/views/ChatView.test.tsx +185 -3
- package/src/mobile/views/ChatView.tsx +102 -8
- package/src/mobile/views/WorkspaceView.test.tsx +66 -0
- package/src/mobile/views/WorkspaceView.tsx +11 -2
- package/src/mobile-api.ts +20 -2
|
@@ -1,76 +1,83 @@
|
|
|
1
|
+
/* Plugin settings card chrome + staged form fields.
|
|
2
|
+
* Aligned with the official ui-settings-plugins PluginCard / fields CSS:
|
|
3
|
+
* same semantic tokens, radius, typography and states so family cards read
|
|
4
|
+
* as siblings of the built-in Shell / Agent loop / Web search cards. */
|
|
5
|
+
|
|
1
6
|
.card {
|
|
2
|
-
list-style: none;
|
|
3
7
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
4
|
-
border-radius: 8px;
|
|
5
8
|
background: var(--dsw-alias-bg-layer-3);
|
|
6
|
-
|
|
9
|
+
border-radius: 12px;
|
|
10
|
+
list-style: none;
|
|
7
11
|
transition: border-color 0.16s, background 0.16s;
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
.card:hover {
|
|
15
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
.cardOpen {
|
|
11
19
|
background: var(--dsw-alias-bg-layer-2);
|
|
12
20
|
border-color: var(--dsw-alias-label-dimmed);
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
.header {
|
|
16
|
-
|
|
17
|
-
align-items: center;
|
|
18
|
-
gap: 8px;
|
|
24
|
+
appearance: none;
|
|
19
25
|
width: 100%;
|
|
20
|
-
padding: 10px 14px;
|
|
21
|
-
border: 0;
|
|
22
|
-
background: transparent;
|
|
23
|
-
cursor: pointer;
|
|
24
|
-
text-align: left;
|
|
25
26
|
font: inherit;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
color: inherit;
|
|
28
|
+
text-align: left;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
background: transparent;
|
|
31
|
+
border: 0;
|
|
32
|
+
border-radius: 12px;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 12px;
|
|
35
|
+
padding: 14px 16px;
|
|
36
|
+
display: flex;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
.header:focus-visible {
|
|
39
|
-
outline:
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.header:disabled {
|
|
44
|
-
opacity: 0.5;
|
|
45
|
-
cursor: default;
|
|
40
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
41
|
+
outline-offset: -2px;
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
.headText {
|
|
49
|
-
display: flex;
|
|
50
45
|
flex-direction: column;
|
|
51
|
-
gap: 2px;
|
|
52
46
|
flex: 1;
|
|
47
|
+
gap: 4px;
|
|
53
48
|
min-width: 0;
|
|
49
|
+
display: flex;
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
.name {
|
|
57
|
-
font-weight: 600;
|
|
58
53
|
color: var(--dsw-alias-label-primary);
|
|
54
|
+
font-size: 15px;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
line-height: 1.4;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
.description {
|
|
62
|
-
font-size: 12px;
|
|
63
60
|
color: var(--dsw-alias-label-tertiary);
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
line-height: 1.5;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
.pending {
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
68
|
+
color: var(--dsw-alias-label-secondary);
|
|
69
|
+
border-radius: 999px;
|
|
70
|
+
flex: none;
|
|
71
|
+
padding: 1px 8px;
|
|
72
|
+
font-size: 11px;
|
|
73
|
+
font-weight: 500;
|
|
74
|
+
line-height: 17px;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
.chevron {
|
|
72
|
-
transition: transform 120ms ease;
|
|
73
78
|
color: var(--dsw-alias-label-tertiary);
|
|
79
|
+
flex: none;
|
|
80
|
+
transition: transform 0.16s;
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
.chevronOpen {
|
|
@@ -78,52 +85,59 @@
|
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
.body {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
gap: 14px;
|
|
88
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
89
|
+
margin: 0 16px;
|
|
90
|
+
padding-bottom: 8px;
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
.readOnly {
|
|
88
|
-
margin: 0;
|
|
89
|
-
font-size: 12px;
|
|
90
94
|
color: var(--dsw-alias-label-tertiary);
|
|
95
|
+
margin: 12px 0 0;
|
|
96
|
+
font-size: 12px;
|
|
97
|
+
line-height: 1.5;
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
.notExposed {
|
|
94
|
-
|
|
101
|
+
color: var(--dsw-alias-state-warn-primary);
|
|
102
|
+
margin: 12px 0 0;
|
|
95
103
|
font-size: 12px;
|
|
96
104
|
line-height: 1.5;
|
|
97
|
-
color: var(--dsw-alias-state-warn-primary);
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
.footer {
|
|
101
|
-
|
|
102
|
-
align-items: center;
|
|
108
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
103
109
|
justify-content: flex-end;
|
|
110
|
+
align-items: center;
|
|
104
111
|
gap: 8px;
|
|
112
|
+
padding: 12px 0 4px;
|
|
113
|
+
display: flex;
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
.failed {
|
|
108
|
-
|
|
117
|
+
min-width: 0;
|
|
118
|
+
color: var(--dsw-alias-label-error);
|
|
119
|
+
flex: 1;
|
|
120
|
+
margin: 0;
|
|
109
121
|
font-size: 12px;
|
|
110
|
-
|
|
122
|
+
line-height: 1.5;
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
.discard,
|
|
114
126
|
.save {
|
|
115
|
-
|
|
116
|
-
border-radius: 6px;
|
|
127
|
+
appearance: none;
|
|
117
128
|
font: inherit;
|
|
118
|
-
font-size: 13px;
|
|
119
129
|
cursor: pointer;
|
|
120
|
-
|
|
130
|
+
border: 1px solid transparent;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
padding: 5px 14px;
|
|
133
|
+
font-size: 13px;
|
|
134
|
+
line-height: 1.5;
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
.discard {
|
|
124
|
-
border:
|
|
125
|
-
background: transparent;
|
|
138
|
+
border-color: var(--dsw-alias-border-l2);
|
|
126
139
|
color: var(--dsw-alias-label-secondary);
|
|
140
|
+
background: transparent;
|
|
127
141
|
}
|
|
128
142
|
|
|
129
143
|
.discard:hover:not(:disabled) {
|
|
@@ -131,173 +145,154 @@
|
|
|
131
145
|
border-color: var(--dsw-alias-label-dimmed);
|
|
132
146
|
}
|
|
133
147
|
|
|
134
|
-
.discard:active:not(:disabled) {
|
|
135
|
-
background: var(--dsw-alias-interactive-bg-active);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.discard:focus-visible {
|
|
139
|
-
outline: none;
|
|
140
|
-
box-shadow: 0 0 0 2px var(--dsw-alias-bg-layer-3), 0 0 0 4px var(--dsw-alias-brand-primary);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
148
|
.save {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
color: var(--dsw-alias-label-primary-foreground);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.save:hover:not(:disabled) {
|
|
150
|
-
border-color: var(--dsw-alias-button-info-hover);
|
|
151
|
-
background: var(--dsw-alias-button-info-hover);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.save:active:not(:disabled) {
|
|
155
|
-
border-color: var(--dsw-alias-button-info-hover);
|
|
156
|
-
background: var(--dsw-alias-button-info-hover);
|
|
157
|
-
filter: brightness(0.96);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.save:focus-visible {
|
|
161
|
-
outline: none;
|
|
162
|
-
box-shadow: 0 0 0 2px var(--dsw-alias-bg-layer-3), 0 0 0 4px var(--dsw-alias-brand-primary);
|
|
149
|
+
background: var(--dsw-alias-label-primary);
|
|
150
|
+
color: var(--dsw-alias-bg-layer-3);
|
|
163
151
|
}
|
|
164
152
|
|
|
165
153
|
.discard:disabled,
|
|
166
154
|
.save:disabled {
|
|
167
|
-
opacity: 0.
|
|
155
|
+
opacity: 0.4;
|
|
168
156
|
cursor: default;
|
|
169
157
|
}
|
|
170
158
|
|
|
159
|
+
.discard:focus-visible,
|
|
160
|
+
.save:focus-visible {
|
|
161
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
162
|
+
outline-offset: 1px;
|
|
163
|
+
}
|
|
164
|
+
|
|
171
165
|
.field {
|
|
172
|
-
display: flex;
|
|
173
166
|
flex-direction: column;
|
|
174
|
-
gap:
|
|
167
|
+
gap: 6px;
|
|
168
|
+
padding: 12px 0;
|
|
169
|
+
display: flex;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.field + .field {
|
|
173
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
175
174
|
}
|
|
176
175
|
|
|
177
176
|
.head {
|
|
178
|
-
display: flex;
|
|
179
177
|
align-items: center;
|
|
180
178
|
gap: 8px;
|
|
179
|
+
display: flex;
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
.label {
|
|
183
|
+
min-width: 0;
|
|
184
|
+
color: var(--dsw-alias-label-primary);
|
|
185
|
+
flex: 1;
|
|
184
186
|
font-size: 13px;
|
|
185
187
|
font-weight: 500;
|
|
186
|
-
|
|
188
|
+
line-height: 1.5;
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
.badges {
|
|
190
|
-
display: flex;
|
|
191
192
|
align-items: center;
|
|
192
|
-
gap:
|
|
193
|
+
gap: 8px;
|
|
194
|
+
display: inline-flex;
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
.badge {
|
|
196
|
-
flex: none;
|
|
197
|
-
min-width: 0;
|
|
198
|
-
font-size: 11px;
|
|
199
|
-
padding: 1px 6px;
|
|
200
|
-
border-radius: 999px;
|
|
201
|
-
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
202
|
-
color: var(--dsw-alias-state-business-primary);
|
|
203
198
|
white-space: nowrap;
|
|
199
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
200
|
+
color: var(--dsw-alias-label-secondary);
|
|
201
|
+
border-radius: 999px;
|
|
202
|
+
padding: 1px 8px;
|
|
203
|
+
font-size: 11px;
|
|
204
|
+
font-weight: 500;
|
|
205
|
+
line-height: 17px;
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
.reset {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
border: 0;
|
|
210
|
-
border-radius: 4px;
|
|
211
|
-
background: transparent;
|
|
212
|
-
color: var(--dsw-alias-state-business-primary);
|
|
209
|
+
font: inherit;
|
|
210
|
+
color: var(--dsw-alias-label-secondary);
|
|
213
211
|
cursor: pointer;
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
background: transparent;
|
|
213
|
+
border: none;
|
|
214
|
+
padding: 0;
|
|
215
|
+
font-size: 12px;
|
|
216
|
+
line-height: 1.5;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
.reset:hover:not(:disabled) {
|
|
219
220
|
color: var(--dsw-alias-label-primary);
|
|
220
|
-
background: var(--dsw-alias-interactive-bg-hover);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
.reset:
|
|
224
|
-
|
|
223
|
+
.reset:disabled {
|
|
224
|
+
cursor: default;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
.reset:focus-visible {
|
|
228
|
-
outline:
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.reset:disabled {
|
|
233
|
-
opacity: 0.5;
|
|
234
|
-
cursor: default;
|
|
228
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
229
|
+
outline-offset: 2px;
|
|
235
230
|
}
|
|
236
231
|
|
|
237
232
|
.input,
|
|
238
233
|
.select {
|
|
239
|
-
padding: 6px 8px;
|
|
240
234
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
241
|
-
|
|
235
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
236
|
+
height: 34px;
|
|
242
237
|
font: inherit;
|
|
243
|
-
font-size: 13px;
|
|
244
238
|
color: var(--dsw-alias-label-primary);
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
border-radius: 8px;
|
|
240
|
+
padding: 0 12px;
|
|
241
|
+
font-size: 13px;
|
|
242
|
+
line-height: 1.5;
|
|
247
243
|
}
|
|
248
244
|
|
|
249
245
|
.input:focus-visible,
|
|
250
246
|
.select:focus-visible {
|
|
251
|
-
outline: none;
|
|
252
247
|
border-color: var(--dsw-alias-brand-primary);
|
|
253
|
-
|
|
248
|
+
outline: none;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.input:disabled,
|
|
252
|
+
.select:disabled {
|
|
253
|
+
color: var(--dsw-alias-label-tertiary);
|
|
254
|
+
cursor: default;
|
|
254
255
|
}
|
|
255
256
|
|
|
256
257
|
.inputInvalid {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
border: 1px solid var(--dsw-alias-label-error);
|
|
259
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
260
|
+
height: 34px;
|
|
260
261
|
font: inherit;
|
|
261
|
-
font-size: 13px;
|
|
262
262
|
color: var(--dsw-alias-label-primary);
|
|
263
|
-
|
|
263
|
+
border-radius: 8px;
|
|
264
|
+
padding: 0 12px;
|
|
265
|
+
font-size: 13px;
|
|
266
|
+
line-height: 1.5;
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
.inputInvalid:focus-visible {
|
|
267
|
-
outline:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
outline: 2px solid var(--dsw-alias-label-error);
|
|
271
|
+
outline-offset: 1px;
|
|
272
|
+
border-color: var(--dsw-alias-label-error);
|
|
270
273
|
}
|
|
271
274
|
|
|
272
|
-
.
|
|
273
|
-
|
|
274
|
-
opacity: 0.6;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
.hint {
|
|
275
|
+
.invalid {
|
|
276
|
+
color: var(--dsw-alias-label-error);
|
|
278
277
|
margin: 0;
|
|
279
278
|
font-size: 12px;
|
|
280
|
-
|
|
279
|
+
line-height: 1.5;
|
|
281
280
|
}
|
|
282
281
|
|
|
283
|
-
.
|
|
282
|
+
.hint {
|
|
283
|
+
color: var(--dsw-alias-label-tertiary);
|
|
284
284
|
margin: 0;
|
|
285
285
|
font-size: 12px;
|
|
286
|
-
|
|
286
|
+
line-height: 1.5;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
/* Accessibility: collapse the interaction transitions for reduced-motion
|
|
290
|
-
users; the card chrome itself stays static. */
|
|
291
289
|
@media (prefers-reduced-motion: reduce) {
|
|
292
290
|
.card,
|
|
293
291
|
.header,
|
|
294
292
|
.chevron,
|
|
293
|
+
.chevronOpen,
|
|
295
294
|
.discard,
|
|
296
|
-
.save
|
|
297
|
-
.reset,
|
|
298
|
-
.input,
|
|
299
|
-
.select,
|
|
300
|
-
.inputInvalid {
|
|
295
|
+
.save {
|
|
301
296
|
transition: none;
|
|
302
297
|
}
|
|
303
298
|
}
|
package/src/client/update-api.ts
CHANGED
|
@@ -6,14 +6,34 @@
|
|
|
6
6
|
|
|
7
7
|
import type { UpdateRunResult, UpdateStatus } from '../update.ts'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when the update status probe fails: carries the HTTP status
|
|
11
|
+
* so the panel can tell "the update route is not mounted" (404 — the host
|
|
12
|
+
* process runs an older plugin build) apart from real network failures.
|
|
13
|
+
*/
|
|
14
|
+
export class UpdateStatusError extends Error {
|
|
15
|
+
/** HTTP status of the failed response (0 when the fetch never returned). */
|
|
16
|
+
readonly status: number
|
|
17
|
+
constructor(status: number) {
|
|
18
|
+
super('update status unavailable (HTTP ' + String(status) + ')')
|
|
19
|
+
this.status = status
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
/**
|
|
10
24
|
* Probe the update status: install mode, owning profile, and the
|
|
11
25
|
* current-vs-latest comparison for every family package.
|
|
12
26
|
* @returns the status snapshot.
|
|
13
27
|
*/
|
|
14
28
|
export async function fetchUpdateStatus(): Promise<UpdateStatus> {
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
let response: Response
|
|
30
|
+
try {
|
|
31
|
+
response = await fetch('/api/update/status')
|
|
32
|
+
} catch {
|
|
33
|
+
// Network-level failure: nothing came back at all.
|
|
34
|
+
throw new UpdateStatusError(0)
|
|
35
|
+
}
|
|
36
|
+
if (!response.ok) throw new UpdateStatusError(response.status)
|
|
17
37
|
return await response.json() as UpdateStatus
|
|
18
38
|
}
|
|
19
39
|
|
package/src/index.ts
CHANGED
|
@@ -96,6 +96,13 @@ export interface Config {
|
|
|
96
96
|
* tunnel setup. The manual `publicBaseUrl` is ignored while this is on.
|
|
97
97
|
*/
|
|
98
98
|
autoTunnel?: boolean
|
|
99
|
+
/**
|
|
100
|
+
* Mobile composer behavior: when true (default), a plain Enter in the
|
|
101
|
+
* phone chat textarea sends the prompt and Shift+Enter inserts a newline.
|
|
102
|
+
* When false, plain Enter inserts a newline and only the send button
|
|
103
|
+
* sends (Shift+Enter keeps inserting a newline).
|
|
104
|
+
*/
|
|
105
|
+
mobileEnterToSend?: boolean
|
|
99
106
|
/** Master switch for the plugin (browser half + host pairing surfaces). */
|
|
100
107
|
enabled?: boolean
|
|
101
108
|
}
|
|
@@ -108,6 +115,7 @@ export const Config: z<Config> = z.object({
|
|
|
108
115
|
requirePairingForLan: z.boolean().default(true),
|
|
109
116
|
publicBaseUrl: z.string(),
|
|
110
117
|
autoTunnel: z.boolean().default(false),
|
|
118
|
+
mobileEnterToSend: z.boolean().default(true),
|
|
111
119
|
enabled: z.boolean().default(true),
|
|
112
120
|
})
|
|
113
121
|
|
|
@@ -130,6 +138,7 @@ const DEFAULTS: ResolvedConfig = {
|
|
|
130
138
|
requirePairingForLan: true,
|
|
131
139
|
publicBaseUrl: undefined,
|
|
132
140
|
autoTunnel: false,
|
|
141
|
+
mobileEnterToSend: true,
|
|
133
142
|
enabled: true,
|
|
134
143
|
}
|
|
135
144
|
|
|
@@ -147,6 +156,7 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
147
156
|
requirePairingForLan: config?.requirePairingForLan ?? DEFAULTS.requirePairingForLan,
|
|
148
157
|
publicBaseUrl: config?.publicBaseUrl,
|
|
149
158
|
autoTunnel: config?.autoTunnel ?? DEFAULTS.autoTunnel,
|
|
159
|
+
mobileEnterToSend: config?.mobileEnterToSend ?? DEFAULTS.mobileEnterToSend,
|
|
150
160
|
enabled: config?.enabled ?? DEFAULTS.enabled,
|
|
151
161
|
}
|
|
152
162
|
// The live source the pairing service and the gate read: the settings
|
|
@@ -163,6 +173,7 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
163
173
|
requirePairingForLan: value.requirePairingForLan ?? DEFAULTS.requirePairingForLan,
|
|
164
174
|
publicBaseUrl: value.publicBaseUrl,
|
|
165
175
|
autoTunnel: value.autoTunnel ?? DEFAULTS.autoTunnel,
|
|
176
|
+
mobileEnterToSend: value.mobileEnterToSend ?? DEFAULTS.mobileEnterToSend,
|
|
166
177
|
enabled: value.enabled ?? DEFAULTS.enabled,
|
|
167
178
|
}
|
|
168
179
|
}
|
|
@@ -267,7 +278,9 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
267
278
|
const routes = [
|
|
268
279
|
...makeRoutes({ service, lanAddresses }),
|
|
269
280
|
...makeMobileRoutes(),
|
|
270
|
-
...(apiProxy !== undefined
|
|
281
|
+
...(apiProxy !== undefined
|
|
282
|
+
? makeMobileApiRoutes({ service, apiProxy, mobileEnterToSend: () => resolve().mobileEnterToSend })
|
|
283
|
+
: []),
|
|
271
284
|
...updateRoutes,
|
|
272
285
|
]
|
|
273
286
|
const gate = makeGateListener(service, () => resolve().requirePairingForLan, () => resolve().enabled)
|
package/src/mobile/api.ts
CHANGED
|
@@ -35,12 +35,23 @@ export interface HistoryPage {
|
|
|
35
35
|
projections?: SessionProjectionsBlock
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Read-only display preferences the plugin answers locally on `/m/api`. */
|
|
39
|
+
export interface MobilePreferences {
|
|
40
|
+
/** Plain Enter sends the drafted prompt (false: Enter inserts a newline). */
|
|
41
|
+
mobileEnterToSend: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
/** The workspace roster (session ids come back per workspace). */
|
|
39
45
|
export async function listWorkspaces(): Promise<WorkspaceView[]> {
|
|
40
46
|
const { items } = await callUnary<{ items: WorkspaceView[] }>('workspace.list', {})
|
|
41
47
|
return items
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
/** Read-only mobile display preferences (answered by the plugin, not the host proxy). */
|
|
51
|
+
export async function fetchMobilePreferences(): Promise<MobilePreferences> {
|
|
52
|
+
return await callUnary<MobilePreferences>('mobile.preferences', {})
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
/** One session.list page; omit the cursor for the first page. */
|
|
45
56
|
export async function listSessions(cursor?: string): Promise<SessionPage> {
|
|
46
57
|
return await callUnary<SessionPage>('session.list', cursor === undefined ? {} : { cursor })
|
|
@@ -56,17 +67,18 @@ export async function createSession(
|
|
|
56
67
|
return await callUnary<CreatedSession>('session.create', options)
|
|
57
68
|
}
|
|
58
69
|
|
|
59
|
-
/** One history window; omit beforeSeq for the tail page. */
|
|
70
|
+
/** One history window; omit beforeSeq for the tail page, pass a signal to abort. */
|
|
60
71
|
export async function history(
|
|
61
72
|
sessionId: string,
|
|
62
73
|
beforeSeq?: number,
|
|
63
74
|
maxMessages = 30,
|
|
75
|
+
signal?: AbortSignal,
|
|
64
76
|
): Promise<HistoryPage> {
|
|
65
77
|
return await callUnary<HistoryPage>('session.history', {
|
|
66
78
|
sessionId,
|
|
67
79
|
maxMessages,
|
|
68
80
|
...(beforeSeq !== undefined ? { beforeSeq } : {}),
|
|
69
|
-
})
|
|
81
|
+
}, signal)
|
|
70
82
|
}
|
|
71
83
|
|
|
72
84
|
/** Send one text prompt (queued: the agent picks it up in order). */
|
|
@@ -765,6 +765,9 @@ body {
|
|
|
765
765
|
min-width: 0;
|
|
766
766
|
min-height: 40px;
|
|
767
767
|
max-height: 120px;
|
|
768
|
+
/* Grow with the content (still bounded by min/max-height); browsers
|
|
769
|
+
without field-sizing support ignore this line and keep the old size. */
|
|
770
|
+
field-sizing: content;
|
|
768
771
|
padding: 9px 12px;
|
|
769
772
|
border: 1px solid var(--m-border);
|
|
770
773
|
border-radius: 10px;
|
package/src/mobile/views/App.tsx
CHANGED
|
@@ -42,6 +42,12 @@ export interface RenderMessage {
|
|
|
42
42
|
toolSummary?: string
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/** Read the optional workspace target carried from the pairing QR flow. */
|
|
46
|
+
export function mobileWorkspaceTarget(search: string): string | undefined {
|
|
47
|
+
const value = new URLSearchParams(search).get('workspace')
|
|
48
|
+
return value === null || value === '' ? undefined : value
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
/** Map a list row to the surface model; the title comes from projections when present. */
|
|
46
52
|
export function toSessionView(item: {
|
|
47
53
|
sessionId: string
|
|
@@ -83,6 +89,9 @@ export function formatTime(epochMs: number): string {
|
|
|
83
89
|
*/
|
|
84
90
|
export function App() {
|
|
85
91
|
const [route, setRoute] = useState<Route>({ kind: 'workspaces' })
|
|
92
|
+
const [initialWorkspaceId, setInitialWorkspaceId] = useState<string | undefined>(
|
|
93
|
+
() => mobileWorkspaceTarget(window.location.search),
|
|
94
|
+
)
|
|
86
95
|
const muxRef = useRef<MuxClient | undefined>(undefined)
|
|
87
96
|
|
|
88
97
|
// The mux stream lives for the page lifetime: session events keep the
|
|
@@ -113,10 +122,20 @@ export function App() {
|
|
|
113
122
|
setRoute({ kind: 'chat', session, workspace })
|
|
114
123
|
}, [])
|
|
115
124
|
|
|
125
|
+
const openWorkspace = useCallback((workspace: WorkspaceRow) => {
|
|
126
|
+
if (initialWorkspaceId !== undefined) {
|
|
127
|
+
const url = new URL(window.location.href)
|
|
128
|
+
url.searchParams.delete('workspace')
|
|
129
|
+
window.history.replaceState(null, '', `${url.pathname}${url.search}${url.hash}`)
|
|
130
|
+
setInitialWorkspaceId(undefined)
|
|
131
|
+
}
|
|
132
|
+
setRoute({ kind: 'sessions', workspace })
|
|
133
|
+
}, [initialWorkspaceId])
|
|
134
|
+
|
|
116
135
|
return (
|
|
117
136
|
<div className="mobile">
|
|
118
137
|
{route.kind === 'workspaces'
|
|
119
|
-
? <WorkspaceRoster
|
|
138
|
+
? <WorkspaceRoster initialWorkspaceId={initialWorkspaceId} onPick={openWorkspace} />
|
|
120
139
|
: route.kind === 'sessions'
|
|
121
140
|
? (
|
|
122
141
|
<SessionListView
|
|
@@ -152,8 +171,8 @@ export function staleHostHint(message: string): string | undefined {
|
|
|
152
171
|
}
|
|
153
172
|
|
|
154
173
|
/** Fetch one history page (tail by default) — thin wrapper so views share the call shape. */
|
|
155
|
-
export function loadHistory(sessionId: string, beforeSeq?: number) {
|
|
156
|
-
return fetchHistory(sessionId, beforeSeq)
|
|
174
|
+
export function loadHistory(sessionId: string, beforeSeq?: number, signal?: AbortSignal) {
|
|
175
|
+
return fetchHistory(sessionId, beforeSeq, undefined, signal)
|
|
157
176
|
}
|
|
158
177
|
|
|
159
178
|
export { listSessions, listWorkspaces, prompt }
|