dsh-quick-actions 0.1.0 → 0.2.0-rc.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.en.md +40 -17
- package/README.md +40 -17
- package/lib/client.js +257 -123
- package/lib/client.js.map +1 -1
- package/lib/index.js +192 -140
- package/lib/types/client/controller.d.ts +61 -39
- package/lib/types/client/dsh.d.ts +8 -4
- package/lib/types/client/index.d.ts +4 -4
- package/lib/types/host/index.d.ts +19 -22
- package/lib/types/host/settings.d.ts +63 -48
- package/lib/types/index.d.ts +16 -14
- package/lib/types/model/catalog.d.ts +3 -18
- package/lib/types/model/index.d.ts +1 -0
- package/lib/types/{host → model}/presets.d.ts +5 -0
- package/lib/types/model/settings.d.ts +11 -7
- package/package.json +18 -13
package/lib/client.js
CHANGED
|
@@ -182,6 +182,29 @@ window.__ModuleLoader__.load({
|
|
|
182
182
|
}
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
|
+
const FNV_OFFSET_BASIS = 144066263297769815596495629667062367629n;
|
|
186
|
+
const FNV_PRIME = 309485009821345068724781371n;
|
|
187
|
+
const FNV_MASK = (1n << 128n) - 1n;
|
|
188
|
+
function fnv1a128(value) {
|
|
189
|
+
let hash = FNV_OFFSET_BASIS;
|
|
190
|
+
for (const byte of new TextEncoder().encode(value)) hash = (hash ^ BigInt(byte)) * FNV_PRIME & FNV_MASK;
|
|
191
|
+
return hash.toString(16).padStart(32, "0");
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Hash the whole catalog projection, field by field and in order, so that any
|
|
195
|
+
* change a client could observe — including a pure reordering — moves the
|
|
196
|
+
* revision, and an unchanged catalog never does (spec 6.2).
|
|
197
|
+
*/
|
|
198
|
+
function revisionOf(presets) {
|
|
199
|
+
return fnv1a128(JSON.stringify([1, presets.map((preset) => [
|
|
200
|
+
preset.id,
|
|
201
|
+
preset.kind,
|
|
202
|
+
preset.label,
|
|
203
|
+
preset.text,
|
|
204
|
+
preset.icon ?? null,
|
|
205
|
+
preset.confirm
|
|
206
|
+
])]));
|
|
207
|
+
}
|
|
185
208
|
function fieldIssue(field, reason) {
|
|
186
209
|
return {
|
|
187
210
|
field,
|
|
@@ -221,55 +244,135 @@ window.__ModuleLoader__.load({
|
|
|
221
244
|
} };
|
|
222
245
|
}
|
|
223
246
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* cast: a snapshot this release cannot read in full yields no catalog at all.
|
|
228
|
-
*
|
|
229
|
-
* All-or-nothing on purpose. Dropping the entries it cannot read would show the
|
|
230
|
-
* user a silently truncated action list, which is exactly what spec 5.1 forbids
|
|
231
|
-
* the Host to do; the consumer reports a catalog error instead (spec 10).
|
|
232
|
-
*
|
|
233
|
-
* The Host's `revision` is carried through rather than recomputed. It is the
|
|
234
|
-
* catalog's published identity, and a Client that recomputed it would be
|
|
235
|
-
* asserting an authority it does not have.
|
|
236
|
-
*/
|
|
237
|
-
function decodeCatalogSnapshot(raw) {
|
|
238
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
239
|
-
const { schemaVersion, revision, presets } = raw;
|
|
240
|
-
if (schemaVersion !== 1) return void 0;
|
|
241
|
-
if (typeof revision !== "string" || revision === "") return void 0;
|
|
242
|
-
if (!Array.isArray(presets)) return void 0;
|
|
243
|
-
const read = readPublishedPresets(presets);
|
|
244
|
-
if (read === void 0) return void 0;
|
|
245
|
-
return {
|
|
246
|
-
schemaVersion: 1,
|
|
247
|
-
revision,
|
|
248
|
-
presets: read
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Validate an already-published preset list against the same field rules the
|
|
253
|
-
* Host applied. Nothing is reported field by field: the Host is the authority
|
|
254
|
-
* that names an author's mistake, and a Client can only refuse the snapshot.
|
|
247
|
+
* Merge and validate the built-in manifest with the Host composition's presets.
|
|
248
|
+
* Nothing is repaired or dropped: the first release refuses to load a catalog an
|
|
249
|
+
* author got wrong, so the mistake surfaces at startup rather than at send time.
|
|
255
250
|
*/
|
|
256
|
-
function
|
|
257
|
-
|
|
251
|
+
function buildPresetCatalog(input) {
|
|
252
|
+
const issues = [];
|
|
258
253
|
const presets = [];
|
|
259
254
|
const seen = /* @__PURE__ */ new Set();
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
const sources = [["builtin", input.builtins], ["config", input.configured]];
|
|
256
|
+
for (const [source, entries] of sources) for (const [index, entry] of entries.entries()) {
|
|
257
|
+
const at = (issue, id) => ({
|
|
258
|
+
scope: "preset",
|
|
259
|
+
source,
|
|
260
|
+
index,
|
|
261
|
+
id,
|
|
262
|
+
field: issue.field,
|
|
263
|
+
reason: issue.reason
|
|
264
|
+
});
|
|
265
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
266
|
+
issues.push(at(fieldIssue("id", "invalid-type"), void 0));
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
262
269
|
const record = entry;
|
|
263
270
|
const identity = readId(record);
|
|
264
|
-
if ("issue" in identity
|
|
271
|
+
if ("issue" in identity) {
|
|
272
|
+
issues.push(at(identity.issue, void 0));
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (seen.has(identity.id)) {
|
|
276
|
+
issues.push(at(fieldIssue("id", "duplicate"), identity.id));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
265
279
|
const read = readPreset(record, identity.id);
|
|
266
|
-
if ("issue" in read)
|
|
280
|
+
if ("issue" in read) {
|
|
281
|
+
issues.push(at(read.issue, identity.id));
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
267
284
|
seen.add(identity.id);
|
|
268
285
|
presets.push(read.preset);
|
|
269
286
|
}
|
|
270
|
-
|
|
287
|
+
const count = input.builtins.length + input.configured.length;
|
|
288
|
+
if (count > 50) issues.push({
|
|
289
|
+
scope: "catalog",
|
|
290
|
+
reason: "too-many",
|
|
291
|
+
count,
|
|
292
|
+
limit: 50
|
|
293
|
+
});
|
|
294
|
+
if (issues.length > 0) return {
|
|
295
|
+
ok: false,
|
|
296
|
+
issues
|
|
297
|
+
};
|
|
298
|
+
return {
|
|
299
|
+
ok: true,
|
|
300
|
+
catalog: {
|
|
301
|
+
schemaVersion: 1,
|
|
302
|
+
revision: revisionOf(presets),
|
|
303
|
+
presets
|
|
304
|
+
}
|
|
305
|
+
};
|
|
271
306
|
}
|
|
272
307
|
//#endregion
|
|
308
|
+
//#region src/model/presets.ts
|
|
309
|
+
/**
|
|
310
|
+
* The package's built-in Preset Quick Action manifest — component 1 of the
|
|
311
|
+
* Preset Catalog (spec 5.1), declared ahead of the Host composition's
|
|
312
|
+
* `Config.presets`.
|
|
313
|
+
*
|
|
314
|
+
* It lives in the shared model because both faces merge it: the Host validates
|
|
315
|
+
* the catalog at load, and the Client rebuilds the same catalog from this list
|
|
316
|
+
* plus the `presets` form field it reads (spec 22.3). One list, so the two can
|
|
317
|
+
* never disagree about what the built-ins are.
|
|
318
|
+
*
|
|
319
|
+
* These are author-facing product copy in one language; changing a label or a
|
|
320
|
+
* text is a product decision, not an implementation one.
|
|
321
|
+
*
|
|
322
|
+
* Two rules bind any edit here:
|
|
323
|
+
*
|
|
324
|
+
* - A Preset Action ID is permanent. Label, icon and text may change under the
|
|
325
|
+
* same id, but `kind` and `confirm` are the immutable safety behaviour
|
|
326
|
+
* signature — changing either needs a new id (spec 5.1). Editing a text so it
|
|
327
|
+
* crosses the Command Send Action boundary counts as crossing that signature.
|
|
328
|
+
* - Adding an entry is additive: a new id appends to the end of every existing
|
|
329
|
+
* user's order and rewrites no stored data (spec 5.3).
|
|
330
|
+
*
|
|
331
|
+
* `summarize` and `explain` carry new ids rather than reusing
|
|
332
|
+
* `summarize-thread` and `explain-last-change`: those two shipped on the
|
|
333
|
+
* default `confirm: true`, and turning confirmation off is exactly the
|
|
334
|
+
* signature change the first rule reserves a new id for. The old ids simply
|
|
335
|
+
* leave the catalog, which the model already handles — a stored preference for
|
|
336
|
+
* an id no longer in the catalog is kept as a tombstone, neither shown nor
|
|
337
|
+
* counted (spec 5.3).
|
|
338
|
+
*/
|
|
339
|
+
const BUILT_IN_PRESETS = Object.freeze([
|
|
340
|
+
Object.freeze({
|
|
341
|
+
id: "approve",
|
|
342
|
+
label: "确认",
|
|
343
|
+
text: "确认,按你刚才说的做。",
|
|
344
|
+
icon: "✅",
|
|
345
|
+
confirm: false
|
|
346
|
+
}),
|
|
347
|
+
Object.freeze({
|
|
348
|
+
id: "continue",
|
|
349
|
+
label: "继续",
|
|
350
|
+
text: "继续。",
|
|
351
|
+
icon: "▶️",
|
|
352
|
+
confirm: false
|
|
353
|
+
}),
|
|
354
|
+
Object.freeze({
|
|
355
|
+
id: "summarize",
|
|
356
|
+
label: "总结",
|
|
357
|
+
text: "总结当前对话:已确定的结论、仍未决的问题、下一步要做的事。",
|
|
358
|
+
icon: "📝",
|
|
359
|
+
confirm: false
|
|
360
|
+
}),
|
|
361
|
+
Object.freeze({
|
|
362
|
+
id: "explain",
|
|
363
|
+
label: "解释",
|
|
364
|
+
text: "解释你刚才的改动:为什么这样做,考虑过哪些替代方案,取舍是什么。",
|
|
365
|
+
icon: "🔍",
|
|
366
|
+
confirm: false
|
|
367
|
+
}),
|
|
368
|
+
Object.freeze({
|
|
369
|
+
id: "compact-context",
|
|
370
|
+
label: "压缩",
|
|
371
|
+
text: "/compact",
|
|
372
|
+
icon: "🧹"
|
|
373
|
+
})
|
|
374
|
+
]);
|
|
375
|
+
//#endregion
|
|
273
376
|
//#region src/model/settings.ts
|
|
274
377
|
/**
|
|
275
378
|
* Settings V1 decoding (spec 4.2, 6.3). Decoding is defensive and lossless: it
|
|
@@ -278,17 +381,27 @@ window.__ModuleLoader__.load({
|
|
|
278
381
|
* Catalog-aware canonicalization lives in `normalize.ts`.
|
|
279
382
|
*/
|
|
280
383
|
/**
|
|
281
|
-
* The one namespace holding user data (spec 4.2); renaming it orphans every
|
|
282
|
-
* stored section.
|
|
283
|
-
* the
|
|
384
|
+
* The one namespace holding user data (spec 4.2, 22); renaming it orphans every
|
|
385
|
+
* stored section. Since DSH 0.1.7 a Settings namespace *is* a profile entry id,
|
|
386
|
+
* so this is also the Cordis loader entry id the bundle patch inserts — the two
|
|
387
|
+
* can no longer drift apart. It lives in the shared model because both faces
|
|
388
|
+
* address it: the Host describes and fences its canonical rewrite against it,
|
|
389
|
+
* the Client reads and writes the same form.
|
|
284
390
|
*/
|
|
285
391
|
const QUICK_ACTIONS_SETTINGS_NAMESPACE = "composer-quick-actions";
|
|
286
392
|
/**
|
|
287
|
-
* The
|
|
288
|
-
*
|
|
289
|
-
*
|
|
393
|
+
* The top-level fields of the stored section, in canonical order (spec 4.2).
|
|
394
|
+
* Each is a separate volatile Config field (spec 22.2): the DSH legacy import
|
|
395
|
+
* addresses a section by these exact top-level keys, and every write sets them
|
|
396
|
+
* field by field so a write never reaches the author's `presets`.
|
|
290
397
|
*/
|
|
291
|
-
const
|
|
398
|
+
const QUICK_ACTION_STATE_FIELDS = Object.freeze([
|
|
399
|
+
"schemaVersion",
|
|
400
|
+
"layout",
|
|
401
|
+
"userActionsById",
|
|
402
|
+
"actionOrder",
|
|
403
|
+
"presetStateById"
|
|
404
|
+
]);
|
|
292
405
|
/** The state a fresh install starts from (spec 4.2). */
|
|
293
406
|
const DEFAULT_QUICK_ACTION_SETTINGS = Object.freeze({
|
|
294
407
|
schemaVersion: 1,
|
|
@@ -764,10 +877,10 @@ window.__ModuleLoader__.load({
|
|
|
764
877
|
/**
|
|
765
878
|
* The Client's single owner of authoritative Quick Action state (spec 7.1).
|
|
766
879
|
*
|
|
767
|
-
* It
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
* revision-fenced writes back through the same
|
|
880
|
+
* It reads the plugin entry's one Settings form (spec 22) — the user-state
|
|
881
|
+
* fields and the author's `presets`, from which it rebuilds the Preset Catalog
|
|
882
|
+
* with the shared model — derives the projection every surface renders, and
|
|
883
|
+
* serializes revision-fenced writes back through the same form.
|
|
771
884
|
*
|
|
772
885
|
* What it deliberately does not own:
|
|
773
886
|
*
|
|
@@ -779,10 +892,21 @@ window.__ModuleLoader__.load({
|
|
|
779
892
|
* `InputActions` object reaches this module — long-lived global state must not
|
|
780
893
|
* pin a session's runtime objects (spec 7.1). Draft occupancy, confirmation and
|
|
781
894
|
* send single-flight belong to the per-session execution layer (spec 7.2).
|
|
782
|
-
* - **Its own copy of the settings document.**
|
|
895
|
+
* - **Its own copy of the settings document.** The form derives from the one
|
|
783
896
|
* browser-side describe mirror, so the catalog costs no read of its own and
|
|
784
|
-
* refreshes with that mirror after a reconnect (spec 17.3).
|
|
897
|
+
* refreshes with that mirror after a reconnect (spec 17.3, 22.3).
|
|
898
|
+
*/
|
|
899
|
+
/**
|
|
900
|
+
* Whether a user retry can clear this catalog error (spec 10).
|
|
901
|
+
*
|
|
902
|
+
* Only a failed first read can: the mirror's public face re-reads only while it
|
|
903
|
+
* holds no document (`ensure()`, spec 22.4). The other two arrive with a held
|
|
904
|
+
* document and clear on their own when the Host's `settings/document-updated`
|
|
905
|
+
* broadcast or a reconnect refreshes it, so a retry there would do nothing.
|
|
785
906
|
*/
|
|
907
|
+
function isRetryableCatalogError(reason) {
|
|
908
|
+
return reason === "unreadable";
|
|
909
|
+
}
|
|
786
910
|
/**
|
|
787
911
|
* How many Custom Action IDs a create or clone tries before giving up.
|
|
788
912
|
* Normalization must never renumber a stored id (spec 4.1), so the planner
|
|
@@ -790,44 +914,27 @@ window.__ModuleLoader__.load({
|
|
|
790
914
|
* anything; exhausting the attempts surfaces the refusal instead of hiding it.
|
|
791
915
|
*/
|
|
792
916
|
const ID_MINT_ATTEMPTS = 4;
|
|
793
|
-
/**
|
|
917
|
+
/**
|
|
918
|
+
* One write per user-state field (spec 4.2), never touching `presets`: the
|
|
919
|
+
* author's list shares the entry's config, and this face never writes it.
|
|
920
|
+
*/
|
|
794
921
|
function sectionOps(next) {
|
|
795
|
-
return
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
},
|
|
801
|
-
{
|
|
802
|
-
op: "set",
|
|
803
|
-
path: ["layout"],
|
|
804
|
-
value: next.layout
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
op: "set",
|
|
808
|
-
path: ["userActionsById"],
|
|
809
|
-
value: next.userActionsById
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
op: "set",
|
|
813
|
-
path: ["actionOrder"],
|
|
814
|
-
value: next.actionOrder
|
|
815
|
-
},
|
|
816
|
-
{
|
|
817
|
-
op: "set",
|
|
818
|
-
path: ["presetStateById"],
|
|
819
|
-
value: next.presetStateById
|
|
820
|
-
}
|
|
821
|
-
];
|
|
922
|
+
return QUICK_ACTION_STATE_FIELDS.map((field) => ({
|
|
923
|
+
op: "set",
|
|
924
|
+
path: [field],
|
|
925
|
+
value: next[field]
|
|
926
|
+
}));
|
|
822
927
|
}
|
|
823
928
|
/**
|
|
824
|
-
*
|
|
929
|
+
* Rebuild the catalog from the form (spec 22.3): the package's built-ins, then
|
|
930
|
+
* the author's `presets` field, merged by the same shared model the Host
|
|
931
|
+
* validated them with at load.
|
|
825
932
|
*
|
|
826
|
-
* `
|
|
827
|
-
*
|
|
828
|
-
*
|
|
933
|
+
* The resolved `value` is read, not `base`: author presets are declared in the
|
|
934
|
+
* profile's own patch layer, which is exactly the layer `base` leaves out. A
|
|
935
|
+
* list the model refuses is an error, never a partial catalog (spec 5.1).
|
|
829
936
|
*/
|
|
830
|
-
function readCatalog(snapshot, document) {
|
|
937
|
+
function readCatalog(snapshot, document, builtins) {
|
|
831
938
|
if (snapshot.status === "loading") {
|
|
832
939
|
if (document.view !== void 0) return {
|
|
833
940
|
status: "error",
|
|
@@ -838,18 +945,25 @@ window.__ModuleLoader__.load({
|
|
|
838
945
|
reason: "unreadable"
|
|
839
946
|
};
|
|
840
947
|
}
|
|
841
|
-
if (snapshot.status === "unavailable"
|
|
948
|
+
if (snapshot.status === "unavailable") return {
|
|
842
949
|
status: "error",
|
|
843
950
|
reason: "unavailable"
|
|
844
951
|
};
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
952
|
+
const configured = snapshot.value?.presets ?? [];
|
|
953
|
+
if (!Array.isArray(configured)) return {
|
|
847
954
|
status: "error",
|
|
848
955
|
reason: "undecodable"
|
|
849
956
|
};
|
|
850
|
-
|
|
957
|
+
const result = buildPresetCatalog({
|
|
958
|
+
builtins,
|
|
959
|
+
configured
|
|
960
|
+
});
|
|
961
|
+
return result.ok ? {
|
|
851
962
|
status: "ready",
|
|
852
|
-
catalog
|
|
963
|
+
catalog: result.catalog
|
|
964
|
+
} : {
|
|
965
|
+
status: "error",
|
|
966
|
+
reason: "undecodable"
|
|
853
967
|
};
|
|
854
968
|
}
|
|
855
969
|
/**
|
|
@@ -891,21 +1005,19 @@ window.__ModuleLoader__.load({
|
|
|
891
1005
|
};
|
|
892
1006
|
}
|
|
893
1007
|
/**
|
|
894
|
-
* Create the controller.
|
|
895
|
-
*
|
|
896
|
-
*
|
|
1008
|
+
* Create the controller. The form is owned by the settings provider and shared
|
|
1009
|
+
* by every reader of this entry, so there is nothing to release for it;
|
|
1010
|
+
* `dispose()` withdraws this module's own subscriptions.
|
|
1011
|
+
*
|
|
1012
|
+
* Both the catalog and the user state come off the same form. It is decoded
|
|
1013
|
+
* here, by the shared model, rather than trusted as the transport validated
|
|
1014
|
+
* it: the Client's reading of a confirmed snapshot is the model's.
|
|
897
1015
|
*/
|
|
898
1016
|
function createQuickActionsController(options) {
|
|
899
1017
|
const mintId = options.mintCustomActionId ?? (() => crypto.randomUUID());
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
});
|
|
904
|
-
const settingsScope = options.settingsScope.bind({
|
|
905
|
-
namespace: QUICK_ACTIONS_SETTINGS_NAMESPACE,
|
|
906
|
-
decode: (value) => decodeQuickActionSettings(value)
|
|
907
|
-
});
|
|
908
|
-
const mirror = options.settingsScope.describe();
|
|
1018
|
+
const builtins = options.builtinPresets ?? BUILT_IN_PRESETS;
|
|
1019
|
+
const form = options.configForms.get(QUICK_ACTIONS_SETTINGS_NAMESPACE);
|
|
1020
|
+
const mirror = options.configForms.describe();
|
|
909
1021
|
const listeners = /* @__PURE__ */ new Set();
|
|
910
1022
|
let disposed = false;
|
|
911
1023
|
let manager = { open: false };
|
|
@@ -916,8 +1028,9 @@ window.__ModuleLoader__.load({
|
|
|
916
1028
|
/** Serializes this controller's own writes so each plan reads the previous one back. */
|
|
917
1029
|
let tail = Promise.resolve();
|
|
918
1030
|
function derive() {
|
|
919
|
-
const
|
|
920
|
-
const
|
|
1031
|
+
const snapshot = form.getSnapshot();
|
|
1032
|
+
const catalog = readCatalog(snapshot, mirror.getSnapshot(), builtins);
|
|
1033
|
+
const settings = readSettings(snapshot);
|
|
921
1034
|
const projection = catalog.status === "ready" ? projectQuickActions(settings.status === "ready" ? settings.settings : DEFAULT_QUICK_ACTION_SETTINGS, catalog.catalog) : void 0;
|
|
922
1035
|
const stale = connectionState !== void 0 && connectionState !== "connected";
|
|
923
1036
|
return {
|
|
@@ -939,11 +1052,7 @@ window.__ModuleLoader__.load({
|
|
|
939
1052
|
state = next;
|
|
940
1053
|
for (const listener of Array.from(listeners)) listener();
|
|
941
1054
|
}
|
|
942
|
-
const stopSources = [
|
|
943
|
-
mirror.subscribe(publish),
|
|
944
|
-
catalogScope.subscribe(publish),
|
|
945
|
-
settingsScope.subscribe(publish)
|
|
946
|
-
];
|
|
1055
|
+
const stopSources = [mirror.subscribe(publish), form.subscribe(publish)];
|
|
947
1056
|
const stopConnection = options.connection.state.subscribe(() => {
|
|
948
1057
|
connectionState = options.connection.state.getSnapshot();
|
|
949
1058
|
publish();
|
|
@@ -973,7 +1082,7 @@ window.__ModuleLoader__.load({
|
|
|
973
1082
|
* the writer that won.
|
|
974
1083
|
*/
|
|
975
1084
|
function classify(catalog, next, fence) {
|
|
976
|
-
const snapshot =
|
|
1085
|
+
const snapshot = form.getSnapshot();
|
|
977
1086
|
const settings = readSettings(snapshot);
|
|
978
1087
|
if (settings.status === "ready" && deepEqualJson(normalizeQuickActionSettings(settings.settings, catalog), next)) return {
|
|
979
1088
|
ok: true,
|
|
@@ -1019,10 +1128,10 @@ window.__ModuleLoader__.load({
|
|
|
1019
1128
|
ok: true,
|
|
1020
1129
|
changed: false
|
|
1021
1130
|
});
|
|
1022
|
-
await
|
|
1131
|
+
await form.mutate(sectionOps(outcome.plan.next), gate.revision);
|
|
1023
1132
|
return settle(classify(gate.catalog, outcome.plan.next, gate.revision));
|
|
1024
1133
|
} catch (error) {
|
|
1025
|
-
await mirror.
|
|
1134
|
+
await mirror.ensure().catch(() => void 0);
|
|
1026
1135
|
return settle({
|
|
1027
1136
|
ok: false,
|
|
1028
1137
|
failure: {
|
|
@@ -1048,7 +1157,7 @@ window.__ModuleLoader__.load({
|
|
|
1048
1157
|
},
|
|
1049
1158
|
refresh: async () => {
|
|
1050
1159
|
if (disposed) return;
|
|
1051
|
-
await mirror.
|
|
1160
|
+
await mirror.ensure();
|
|
1052
1161
|
},
|
|
1053
1162
|
openManager() {
|
|
1054
1163
|
manager = { open: true };
|
|
@@ -1781,6 +1890,31 @@ window.__ModuleLoader__.load({
|
|
|
1781
1890
|
* per-Session engine, which owns the single flight and the final re-verification
|
|
1782
1891
|
* (spec 9.3, 9.5). The panel never mints a second lock.
|
|
1783
1892
|
*/
|
|
1893
|
+
/**
|
|
1894
|
+
* The search field's glyph: the official `IconSearchOutlineRegular` artwork,
|
|
1895
|
+
* drawn here rather than imported. DSH renamed its icon exports between lines
|
|
1896
|
+
* (`IconSearchOutline16` is gone as of 0.1.7) and the shell's seed table serves
|
|
1897
|
+
* only the running line's names, so an imported icon breaks the whole panel the
|
|
1898
|
+
* next time a name moves (spec 22.5). Stroke follows `currentColor`, so it
|
|
1899
|
+
* themes with the field like the official one.
|
|
1900
|
+
*/
|
|
1901
|
+
function SearchIcon() {
|
|
1902
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1903
|
+
width: 16,
|
|
1904
|
+
height: 16,
|
|
1905
|
+
viewBox: "0 0 16 16",
|
|
1906
|
+
fill: "none",
|
|
1907
|
+
"aria-hidden": "true",
|
|
1908
|
+
strokeWidth: 1,
|
|
1909
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1910
|
+
d: "M6.58727 11.8586C9.55061 11.8586 11.9529 9.45637 11.9529 6.49304C11.9529 3.5297 9.55061 1.12744 6.58727 1.12744C3.62394 1.12744 1.22168 3.5297 1.22168 6.49304C1.22168 9.45637 3.62394 11.8586 6.58727 11.8586Z",
|
|
1911
|
+
stroke: "currentColor"
|
|
1912
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1913
|
+
d: "M10.2991 10.3933L14.7783 14.8725",
|
|
1914
|
+
stroke: "currentColor"
|
|
1915
|
+
})]
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1784
1918
|
function ActionPanel(props) {
|
|
1785
1919
|
const { actions, session, t, labelledBy, onActivate, onClose } = props;
|
|
1786
1920
|
const [query, setQuery] = (0, react.useState)("");
|
|
@@ -1822,7 +1956,7 @@ window.__ModuleLoader__.load({
|
|
|
1822
1956
|
className: "dsh-cqa-field-label",
|
|
1823
1957
|
children: t("panel.search")
|
|
1824
1958
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1825
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1959
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchIcon, {}),
|
|
1826
1960
|
type: "search",
|
|
1827
1961
|
value: query,
|
|
1828
1962
|
"data-quick-actions-search": "",
|
|
@@ -2190,8 +2324,8 @@ window.__ModuleLoader__.load({
|
|
|
2190
2324
|
"feedback.dismiss": "知道了",
|
|
2191
2325
|
"catalog.loading": "正在读取快捷动作…",
|
|
2192
2326
|
"catalog.unreadable": "无法读取设置,快捷动作暂不可用",
|
|
2193
|
-
"catalog.unavailable": "
|
|
2194
|
-
"catalog.undecodable": "
|
|
2327
|
+
"catalog.unavailable": "DSH 未向本页面提供快捷动作设置,快捷动作暂不可用",
|
|
2328
|
+
"catalog.undecodable": "预置目录配置无效,快捷动作暂不可用",
|
|
2195
2329
|
"catalog.retry": "重试",
|
|
2196
2330
|
"crash.title": "快捷动作出错了",
|
|
2197
2331
|
"crash.retry": "重新加载",
|
|
@@ -2290,8 +2424,8 @@ window.__ModuleLoader__.load({
|
|
|
2290
2424
|
"feedback.dismiss": "Dismiss",
|
|
2291
2425
|
"catalog.loading": "Loading Quick Actions…",
|
|
2292
2426
|
"catalog.unreadable": "Settings could not be read, so Quick Actions are unavailable",
|
|
2293
|
-
"catalog.unavailable": "
|
|
2294
|
-
"catalog.undecodable": "The Preset Catalog is
|
|
2427
|
+
"catalog.unavailable": "DSH is not serving Quick Actions settings to this page, so Quick Actions are unavailable",
|
|
2428
|
+
"catalog.undecodable": "The Preset Catalog configuration is invalid, so Quick Actions are unavailable",
|
|
2295
2429
|
"catalog.retry": "Retry",
|
|
2296
2430
|
"crash.title": "Quick Actions hit an error",
|
|
2297
2431
|
"crash.retry": "Reload",
|
|
@@ -2931,14 +3065,14 @@ window.__ModuleLoader__.load({
|
|
|
2931
3065
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2932
3066
|
className: "dsh-cqa-note-text",
|
|
2933
3067
|
children: t(client.catalog.status === "error" ? `catalog.${client.catalog.reason}` : "catalog.loading")
|
|
2934
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3068
|
+
}), client.catalog.status === "error" && isRetryableCatalogError(client.catalog.reason) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2935
3069
|
type: "button",
|
|
2936
3070
|
className: "dsh-cqa-link",
|
|
2937
3071
|
onClick: () => {
|
|
2938
3072
|
controller.refresh().catch(() => void 0);
|
|
2939
3073
|
},
|
|
2940
3074
|
children: t("catalog.retry")
|
|
2941
|
-
})]
|
|
3075
|
+
}) : null]
|
|
2942
3076
|
}) : null,
|
|
2943
3077
|
readOnlyReason === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2944
3078
|
className: "dsh-cqa-note",
|
|
@@ -3234,12 +3368,12 @@ window.__ModuleLoader__.load({
|
|
|
3234
3368
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3235
3369
|
className: "dsh-cqa-note-text",
|
|
3236
3370
|
children: t(`catalog.${client.catalog.reason}`)
|
|
3237
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3371
|
+
}), isRetryableCatalogError(client.catalog.reason) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3238
3372
|
type: "button",
|
|
3239
3373
|
className: "dsh-cqa-link",
|
|
3240
3374
|
onClick: onRetry,
|
|
3241
3375
|
children: t("catalog.retry")
|
|
3242
|
-
})]
|
|
3376
|
+
}) : null]
|
|
3243
3377
|
});
|
|
3244
3378
|
}
|
|
3245
3379
|
/** Build both Slot entry components over one set of fiber-owned dependencies. */
|
|
@@ -3839,7 +3973,7 @@ window.__ModuleLoader__.load({
|
|
|
3839
3973
|
*/
|
|
3840
3974
|
const inject = [
|
|
3841
3975
|
"slots",
|
|
3842
|
-
"
|
|
3976
|
+
"configForms",
|
|
3843
3977
|
"connection",
|
|
3844
3978
|
"locale"
|
|
3845
3979
|
];
|
|
@@ -3855,7 +3989,7 @@ window.__ModuleLoader__.load({
|
|
|
3855
3989
|
*/
|
|
3856
3990
|
function apply(ctx) {
|
|
3857
3991
|
const controller = createQuickActionsController({
|
|
3858
|
-
|
|
3992
|
+
configForms: ctx.configForms,
|
|
3859
3993
|
connection: ctx.connection
|
|
3860
3994
|
});
|
|
3861
3995
|
const sessions = createQuickActionSessionRegistry();
|