@qilin-agent/client-ui-agent-preset 0.0.0-stage → 3.1.4
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/LICENSE +21 -0
- package/README.i18n.yaml +43 -0
- package/README.md +91 -2
- package/README.zh.md +92 -0
- package/lib/client.js +2528 -0
- package/lib/index.js +11 -0
- package/lib/types/client/AgentPresetLabel.d.ts +30 -0
- package/lib/types/client/AgentPresetSeat.d.ts +40 -0
- package/lib/types/client/AgentPresetSection.d.ts +63 -0
- package/lib/types/client/CreatePluginMenuItem.d.ts +21 -0
- package/lib/types/client/index.d.ts +36 -0
- package/lib/types/client/locales.d.ts +10 -0
- package/lib/types/client/seat-store.d.ts +121 -0
- package/lib/types/client/section-store.d.ts +212 -0
- package/lib/types/client/settings-store.d.ts +119 -0
- package/lib/types/client/shipped-preset.d.ts +7 -0
- package/lib/types/index.d.ts +9 -0
- package/package.json +78 -3
package/lib/client.js
ADDED
|
@@ -0,0 +1,2528 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@qilin-agent/client-ui-agent-preset",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
|
|
10
|
+
let _qilin_agent_client_store = require("@qilin-agent/client-store");
|
|
11
|
+
//#region ../../util/values/src/partial-json.ts
|
|
12
|
+
/**
|
|
13
|
+
* Lazily scanned view of one JSON object's top-level fields, built from text
|
|
14
|
+
* that may still be streaming or from an already parsed object. Nothing is
|
|
15
|
+
* scanned until a reader asks; the view remembers every question it answered
|
|
16
|
+
* and reports changed answers when the owner refreshes for publication.
|
|
17
|
+
* Used for model tool-call arguments: a row reads the fields it
|
|
18
|
+
* cares about at whatever granularity it displays, at every stage of the call.
|
|
19
|
+
* @module @qilin-agent/util-values/src/partial-json
|
|
20
|
+
*/
|
|
21
|
+
const SIMPLE_ESCAPES = {
|
|
22
|
+
"\"": "\"",
|
|
23
|
+
"\\": "\\",
|
|
24
|
+
"/": "/",
|
|
25
|
+
b: "\b",
|
|
26
|
+
f: "\f",
|
|
27
|
+
n: "\n",
|
|
28
|
+
r: "\r",
|
|
29
|
+
t: " "
|
|
30
|
+
};
|
|
31
|
+
const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
|
|
32
|
+
function isWhitespace(c) {
|
|
33
|
+
return c === " " || c === "\n" || c === "\r" || c === " ";
|
|
34
|
+
}
|
|
35
|
+
function isHex(c) {
|
|
36
|
+
return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
|
|
37
|
+
}
|
|
38
|
+
(class PartialArguments {
|
|
39
|
+
/** The view of a call with no arguments available. */
|
|
40
|
+
static EMPTY = PartialArguments.fromObject({});
|
|
41
|
+
/**
|
|
42
|
+
* View finished argument text without scanning it until a reader asks.
|
|
43
|
+
* @param text - the complete argument JSON text.
|
|
44
|
+
* @returns a sealed view.
|
|
45
|
+
*/
|
|
46
|
+
static fromText(text) {
|
|
47
|
+
const view = new PartialArguments();
|
|
48
|
+
view.append(text);
|
|
49
|
+
view.sealed = true;
|
|
50
|
+
return view;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* View an already parsed argument payload, such as a PTC dispatch object.
|
|
54
|
+
* @param value - the parsed argument value.
|
|
55
|
+
* @returns a sealed view; a non-object payload has no fields.
|
|
56
|
+
*/
|
|
57
|
+
static fromObject(value) {
|
|
58
|
+
const view = new PartialArguments();
|
|
59
|
+
view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
60
|
+
view.sealed = true;
|
|
61
|
+
return view;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The source: text so far or a parsed object, plus whether it can still grow.
|
|
65
|
+
* These are the only enumerable fields, so two views over the same source
|
|
66
|
+
* compare equal structurally however far each has been read.
|
|
67
|
+
*/
|
|
68
|
+
chunks = [];
|
|
69
|
+
object;
|
|
70
|
+
sealed = false;
|
|
71
|
+
#ends = [];
|
|
72
|
+
#size = 0;
|
|
73
|
+
#consumed = 0;
|
|
74
|
+
#mode = "root";
|
|
75
|
+
#escape = false;
|
|
76
|
+
#keyStart = 0;
|
|
77
|
+
#keyEscaped = false;
|
|
78
|
+
#key = "";
|
|
79
|
+
#current = null;
|
|
80
|
+
#nestedEnds = [];
|
|
81
|
+
#nestedInString = false;
|
|
82
|
+
#invalidAt;
|
|
83
|
+
#invalidValue = false;
|
|
84
|
+
#entries = /* @__PURE__ */ new Map();
|
|
85
|
+
#order = [];
|
|
86
|
+
#reads = /* @__PURE__ */ new Map();
|
|
87
|
+
/** Whether this view rejects further appends; does not scan text or register reads. */
|
|
88
|
+
get isSealed() {
|
|
89
|
+
return this.sealed;
|
|
90
|
+
}
|
|
91
|
+
/** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
|
|
92
|
+
get invalid() {
|
|
93
|
+
this.scan();
|
|
94
|
+
return this.#mode === "invalid" || this.#invalidValue;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Retain streamed argument text without scanning or comparing observed answers.
|
|
98
|
+
* @param fragment - the text following every fragment appended before.
|
|
99
|
+
*/
|
|
100
|
+
append(fragment) {
|
|
101
|
+
if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
|
|
102
|
+
if (fragment.length === 0) return;
|
|
103
|
+
this.chunks.push(fragment);
|
|
104
|
+
this.#size += fragment.length;
|
|
105
|
+
this.#ends.push(this.#size);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Reconcile a streamed prefix with authoritative complete text without joining the fragments.
|
|
109
|
+
* @param text - the final argument text, which replaces missing or conflicting deltas.
|
|
110
|
+
* @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
|
|
111
|
+
*/
|
|
112
|
+
settle(text) {
|
|
113
|
+
if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
|
|
114
|
+
let offset = 0;
|
|
115
|
+
for (const chunk of this.chunks) {
|
|
116
|
+
if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
|
|
117
|
+
offset += chunk.length;
|
|
118
|
+
}
|
|
119
|
+
this.chunks = text.length === 0 ? [] : [text];
|
|
120
|
+
this.#ends = text.length === 0 ? [] : [text.length];
|
|
121
|
+
this.sealed = true;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Compare observed answers and advance their publication baseline. Unread views remain unscanned.
|
|
126
|
+
* @returns whether any observed answer changed since its first read or the preceding refresh.
|
|
127
|
+
*/
|
|
128
|
+
refresh() {
|
|
129
|
+
if (this.#reads.size === 0) return false;
|
|
130
|
+
this.scan();
|
|
131
|
+
let changed = false;
|
|
132
|
+
let completions = false;
|
|
133
|
+
for (const read of this.#reads.values()) {
|
|
134
|
+
if (read.completion) {
|
|
135
|
+
completions = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
changed = this.refreshRead(read) || changed;
|
|
139
|
+
}
|
|
140
|
+
if (completions) {
|
|
141
|
+
for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
|
|
142
|
+
}
|
|
143
|
+
if (this.sealed) this.#reads.clear();
|
|
144
|
+
return changed;
|
|
145
|
+
}
|
|
146
|
+
refreshRead(read) {
|
|
147
|
+
const now = read.answer();
|
|
148
|
+
if (Object.is(now, read.last)) return false;
|
|
149
|
+
read.last = now;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Check whether no further fields can arrive.
|
|
154
|
+
* @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
|
|
155
|
+
*/
|
|
156
|
+
closed() {
|
|
157
|
+
return this.remember("closed", "", () => this.closedNow());
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* List discovered fields in first-appearance order.
|
|
161
|
+
* @returns top-level keys seen so far, in first-appearance order.
|
|
162
|
+
*/
|
|
163
|
+
keys() {
|
|
164
|
+
return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check whether a top-level field has appeared.
|
|
168
|
+
* @param key - argument name.
|
|
169
|
+
* @returns whether the field has appeared (a string opened or another value began).
|
|
170
|
+
*/
|
|
171
|
+
has(key) {
|
|
172
|
+
return this.remember("has", key, () => this.hasNow(key));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Check whether a field's closing delimiter has arrived, without validating its contents.
|
|
176
|
+
* @param key - argument name.
|
|
177
|
+
* @returns whether its delimiter arrived and no content reader has reported an error for this value.
|
|
178
|
+
*/
|
|
179
|
+
complete(key) {
|
|
180
|
+
return this.remember("complete", key, () => this.completeNow(key));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Read string length without materializing its text.
|
|
184
|
+
* @param key - argument name.
|
|
185
|
+
* @param options - change granularity for a streaming string.
|
|
186
|
+
* @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
|
|
187
|
+
*/
|
|
188
|
+
stringLength(key, options) {
|
|
189
|
+
const step = Math.max(1, Math.floor(options?.step ?? 1));
|
|
190
|
+
const offset = options?.offset ?? 0;
|
|
191
|
+
return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Check a string against a decoded UTF-16 length limit without materializing it.
|
|
195
|
+
* @param key - argument name.
|
|
196
|
+
* @param maxLength - decoded UTF-16 limit, floored to at least zero.
|
|
197
|
+
* @returns whether the string is longer than the limit; false when absent or not a string.
|
|
198
|
+
*/
|
|
199
|
+
stringExceeds(key, maxLength) {
|
|
200
|
+
const limit = Math.max(0, Math.floor(maxLength));
|
|
201
|
+
return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Read a decoded string, including a streaming prefix.
|
|
205
|
+
* @param key - argument name.
|
|
206
|
+
* @returns the string field's decoded text so far; undefined when absent or not a string.
|
|
207
|
+
*/
|
|
208
|
+
text(key) {
|
|
209
|
+
return this.remember("text", key, () => this.textNow(key));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Read at most the first decoded UTF-16 units of a string.
|
|
213
|
+
* @param key - argument name.
|
|
214
|
+
* @param maxLength - maximum decoded UTF-16 length, floored to at least one.
|
|
215
|
+
* @returns the bounded string prefix; undefined when absent or not a string.
|
|
216
|
+
*/
|
|
217
|
+
textPrefix(key, maxLength) {
|
|
218
|
+
const limit = Math.max(1, Math.floor(maxLength));
|
|
219
|
+
return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Read a completed non-string argument.
|
|
223
|
+
* @param key - argument name.
|
|
224
|
+
* @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
|
|
225
|
+
*/
|
|
226
|
+
value(key) {
|
|
227
|
+
return this.remember("value", key, () => this.valueNow(key));
|
|
228
|
+
}
|
|
229
|
+
/** Answer a question and, on a streaming view, remember it for change detection. */
|
|
230
|
+
remember(kind, key, read, comparison) {
|
|
231
|
+
this.scan();
|
|
232
|
+
const result = read();
|
|
233
|
+
if (!this.sealed) {
|
|
234
|
+
const id = `${kind}/${key}`;
|
|
235
|
+
if (!this.#reads.has(id)) this.#reads.set(id, {
|
|
236
|
+
completion: kind === "complete",
|
|
237
|
+
answer: comparison === void 0 ? read : () => comparison(read()),
|
|
238
|
+
last: comparison === void 0 ? result : comparison(result)
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
closedNow() {
|
|
244
|
+
return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
|
|
245
|
+
}
|
|
246
|
+
keysNow() {
|
|
247
|
+
return this.object === void 0 ? this.#order : Object.keys(this.object);
|
|
248
|
+
}
|
|
249
|
+
hasNow(key) {
|
|
250
|
+
return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
|
|
251
|
+
}
|
|
252
|
+
completeNow(key) {
|
|
253
|
+
if (this.object !== void 0) return Object.hasOwn(this.object, key);
|
|
254
|
+
const entry = this.#entries.get(key);
|
|
255
|
+
return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
|
|
256
|
+
}
|
|
257
|
+
lengthNow(key, limit = Number.POSITIVE_INFINITY) {
|
|
258
|
+
if (this.object !== void 0) {
|
|
259
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
260
|
+
return typeof field === "string" ? field.length : void 0;
|
|
261
|
+
}
|
|
262
|
+
const entry = this.#entries.get(key);
|
|
263
|
+
if (entry?.kind !== "string") return void 0;
|
|
264
|
+
if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
|
|
265
|
+
const read = entry.length ??= {
|
|
266
|
+
at: entry.start,
|
|
267
|
+
length: 0,
|
|
268
|
+
text: ""
|
|
269
|
+
};
|
|
270
|
+
this.readString(entry, read, limit, false);
|
|
271
|
+
return read.length;
|
|
272
|
+
}
|
|
273
|
+
textNow(key) {
|
|
274
|
+
if (this.object !== void 0) {
|
|
275
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
276
|
+
return typeof field === "string" ? field : void 0;
|
|
277
|
+
}
|
|
278
|
+
const entry = this.#entries.get(key);
|
|
279
|
+
if (entry?.kind !== "string") return void 0;
|
|
280
|
+
if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
|
|
281
|
+
let text;
|
|
282
|
+
try {
|
|
283
|
+
text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
|
|
284
|
+
} catch (_error) {}
|
|
285
|
+
if (text !== void 0) entry.text = {
|
|
286
|
+
at: entry.end,
|
|
287
|
+
length: text.length,
|
|
288
|
+
text
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const read = entry.text ??= {
|
|
292
|
+
at: entry.start,
|
|
293
|
+
length: 0,
|
|
294
|
+
text: ""
|
|
295
|
+
};
|
|
296
|
+
this.readString(entry, read, Number.POSITIVE_INFINITY, true);
|
|
297
|
+
return read.text;
|
|
298
|
+
}
|
|
299
|
+
textPrefixNow(key, maxLength) {
|
|
300
|
+
if (this.object !== void 0) {
|
|
301
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
302
|
+
return typeof field === "string" ? field.slice(0, maxLength) : void 0;
|
|
303
|
+
}
|
|
304
|
+
const entry = this.#entries.get(key);
|
|
305
|
+
if (entry?.kind !== "string") return void 0;
|
|
306
|
+
const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
|
|
307
|
+
let read = prefixes.get(maxLength);
|
|
308
|
+
if (read === void 0) {
|
|
309
|
+
read = {
|
|
310
|
+
at: entry.start,
|
|
311
|
+
length: 0,
|
|
312
|
+
text: ""
|
|
313
|
+
};
|
|
314
|
+
prefixes.set(maxLength, read);
|
|
315
|
+
}
|
|
316
|
+
this.readString(entry, read, maxLength, true);
|
|
317
|
+
return read.text;
|
|
318
|
+
}
|
|
319
|
+
valueNow(key) {
|
|
320
|
+
if (this.object !== void 0) {
|
|
321
|
+
if (!Object.hasOwn(this.object, key)) return void 0;
|
|
322
|
+
const field = this.object[key];
|
|
323
|
+
return typeof field === "string" ? void 0 : field;
|
|
324
|
+
}
|
|
325
|
+
const entry = this.#entries.get(key);
|
|
326
|
+
if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
|
|
327
|
+
if (entry.parsed === void 0) try {
|
|
328
|
+
entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
|
|
329
|
+
} catch (_error) {
|
|
330
|
+
entry.invalid = true;
|
|
331
|
+
this.#invalidValue = true;
|
|
332
|
+
}
|
|
333
|
+
return entry.parsed;
|
|
334
|
+
}
|
|
335
|
+
chunkAt(at) {
|
|
336
|
+
let low = 0;
|
|
337
|
+
let high = this.#ends.length;
|
|
338
|
+
while (low < high) {
|
|
339
|
+
const mid = low + high >>> 1;
|
|
340
|
+
if (this.#ends[mid] <= at) low = mid + 1;
|
|
341
|
+
else high = mid;
|
|
342
|
+
}
|
|
343
|
+
return low;
|
|
344
|
+
}
|
|
345
|
+
/** Materialize only a requested range, never the cumulative source. */
|
|
346
|
+
slice(start, end) {
|
|
347
|
+
if (start >= end) return "";
|
|
348
|
+
const first = this.chunkAt(start);
|
|
349
|
+
const last = this.chunkAt(end - 1);
|
|
350
|
+
const base = first === 0 ? 0 : this.#ends[first - 1];
|
|
351
|
+
if (first === last) return this.chunks[first].slice(start - base, end - base);
|
|
352
|
+
const parts = [this.chunks[first].slice(start - base)];
|
|
353
|
+
for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
|
|
354
|
+
parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
|
|
355
|
+
return parts.join("");
|
|
356
|
+
}
|
|
357
|
+
readString(entry, read, limit, materialize) {
|
|
358
|
+
const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
|
|
359
|
+
if (!entry.needsDecoding) {
|
|
360
|
+
const length = Math.min(end - read.at, limit - read.length);
|
|
361
|
+
if (length <= 0) return;
|
|
362
|
+
if (materialize) read.text += this.slice(read.at, read.at + length);
|
|
363
|
+
read.at += length;
|
|
364
|
+
read.length += length;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
let chunkIndex = this.chunkAt(read.at);
|
|
368
|
+
while (read.at < end && read.length < limit) {
|
|
369
|
+
const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
|
|
370
|
+
const chunk = this.chunks[chunkIndex];
|
|
371
|
+
const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
|
|
372
|
+
const boundary = remaining.search(CONTENT_ESCAPE);
|
|
373
|
+
const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
|
|
374
|
+
if (length > 0) {
|
|
375
|
+
if (materialize) read.text += remaining.slice(0, length);
|
|
376
|
+
read.at += length;
|
|
377
|
+
read.length += length;
|
|
378
|
+
if (read.at === base + chunk.length) chunkIndex++;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
|
|
382
|
+
let decoded;
|
|
383
|
+
let width = 2;
|
|
384
|
+
if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
|
|
385
|
+
if (remaining[0] === "\\" && type === "u") {
|
|
386
|
+
const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
|
|
387
|
+
let valid = true;
|
|
388
|
+
for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
|
|
389
|
+
if (valid) {
|
|
390
|
+
if (hex.length < 4 && entry.end < 0) return;
|
|
391
|
+
if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
|
|
392
|
+
}
|
|
393
|
+
width = 6;
|
|
394
|
+
} else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
|
|
395
|
+
if (decoded === void 0) {
|
|
396
|
+
entry.invalidAt = read.at;
|
|
397
|
+
this.#invalidValue = true;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (materialize) read.text += decoded;
|
|
401
|
+
read.length++;
|
|
402
|
+
read.at += width;
|
|
403
|
+
while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/** Locate new field ranges without decoding or parsing their contents. */
|
|
407
|
+
scan() {
|
|
408
|
+
if (this.object !== void 0 || this.#consumed === this.#size) return;
|
|
409
|
+
for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
|
|
410
|
+
const pending = this.chunks[i];
|
|
411
|
+
const base = i === 0 ? 0 : this.#ends[i - 1];
|
|
412
|
+
for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
|
|
413
|
+
if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
|
|
414
|
+
const end = this.stringBoundary(pending, index);
|
|
415
|
+
this.#consumed += end - index;
|
|
416
|
+
index = end;
|
|
417
|
+
if (index === pending.length) break;
|
|
418
|
+
}
|
|
419
|
+
this.step(pending[index], this.#consumed);
|
|
420
|
+
this.#consumed++;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/** Only raw quotes and their preceding backslash runs can terminate a string. */
|
|
425
|
+
stringBoundary(fragment, start) {
|
|
426
|
+
let at = start;
|
|
427
|
+
while (true) {
|
|
428
|
+
const quote = fragment.indexOf("\"", at);
|
|
429
|
+
const end = quote < 0 ? fragment.length : quote;
|
|
430
|
+
if (this.#mode === "string") {
|
|
431
|
+
const entry = this.#current;
|
|
432
|
+
if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
|
|
433
|
+
}
|
|
434
|
+
let slashStart = end;
|
|
435
|
+
while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
|
|
436
|
+
const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
|
|
437
|
+
this.#escape = quote < 0 && escaped;
|
|
438
|
+
if (quote < 0 || !escaped) return end;
|
|
439
|
+
at = quote + 1;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
step(c, at) {
|
|
443
|
+
switch (this.#mode) {
|
|
444
|
+
case "root":
|
|
445
|
+
if (isWhitespace(c)) return;
|
|
446
|
+
if (c === "{") {
|
|
447
|
+
this.#mode = "key-or-end";
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
this.fail();
|
|
451
|
+
return;
|
|
452
|
+
case "key-or-end":
|
|
453
|
+
if (isWhitespace(c)) return;
|
|
454
|
+
if (c === "}") {
|
|
455
|
+
this.#mode = "closed";
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (c === "\"") {
|
|
459
|
+
this.beginKey(at);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
this.fail();
|
|
463
|
+
return;
|
|
464
|
+
case "key-only":
|
|
465
|
+
if (isWhitespace(c)) return;
|
|
466
|
+
if (c === "\"") {
|
|
467
|
+
this.beginKey(at);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
this.fail();
|
|
471
|
+
return;
|
|
472
|
+
case "key":
|
|
473
|
+
this.stepKey(c, at);
|
|
474
|
+
return;
|
|
475
|
+
case "colon":
|
|
476
|
+
if (isWhitespace(c)) return;
|
|
477
|
+
if (c === ":") {
|
|
478
|
+
this.#mode = "value";
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
this.fail();
|
|
482
|
+
return;
|
|
483
|
+
case "value":
|
|
484
|
+
this.beginValue(c, at);
|
|
485
|
+
return;
|
|
486
|
+
case "string": {
|
|
487
|
+
const entry = this.#current;
|
|
488
|
+
entry.end = at;
|
|
489
|
+
this.#current = null;
|
|
490
|
+
this.#mode = "comma-or-end";
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
case "scalar":
|
|
494
|
+
this.stepScalar(c, at);
|
|
495
|
+
return;
|
|
496
|
+
case "nested":
|
|
497
|
+
this.stepNested(c, at);
|
|
498
|
+
return;
|
|
499
|
+
case "comma-or-end":
|
|
500
|
+
if (isWhitespace(c)) return;
|
|
501
|
+
if (c === ",") {
|
|
502
|
+
this.#mode = "key-only";
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (c === "}") {
|
|
506
|
+
this.#mode = "closed";
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
this.fail();
|
|
510
|
+
return;
|
|
511
|
+
case "closed":
|
|
512
|
+
if (isWhitespace(c)) return;
|
|
513
|
+
this.fail();
|
|
514
|
+
return;
|
|
515
|
+
/* v8 ignore next 2 -- scan() stops stepping once the view is invalid. */
|
|
516
|
+
case "invalid": return;
|
|
517
|
+
/* v8 ignore next 2 -- Every scanner mode has a handler above. */
|
|
518
|
+
default: assertNever(this.#mode);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
fail() {
|
|
522
|
+
this.#invalidAt = this.#consumed;
|
|
523
|
+
this.#mode = "invalid";
|
|
524
|
+
this.#current = null;
|
|
525
|
+
}
|
|
526
|
+
beginKey(at) {
|
|
527
|
+
this.#mode = "key";
|
|
528
|
+
this.#keyStart = at + 1;
|
|
529
|
+
this.#keyEscaped = false;
|
|
530
|
+
this.#escape = false;
|
|
531
|
+
}
|
|
532
|
+
stepKey(c, at) {
|
|
533
|
+
if (c < " ") {
|
|
534
|
+
this.fail();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (this.#escape) {
|
|
538
|
+
this.#escape = false;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (c === "\\") {
|
|
542
|
+
this.#escape = true;
|
|
543
|
+
this.#keyEscaped = true;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (c !== "\"") return;
|
|
547
|
+
const raw = this.slice(this.#keyStart, at);
|
|
548
|
+
if (this.#keyEscaped) try {
|
|
549
|
+
this.#key = JSON.parse(`"${raw}"`);
|
|
550
|
+
} catch (_error) {
|
|
551
|
+
this.fail();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
else this.#key = raw;
|
|
555
|
+
this.#mode = "colon";
|
|
556
|
+
}
|
|
557
|
+
open(entry) {
|
|
558
|
+
if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
|
|
559
|
+
this.#entries.set(this.#key, entry);
|
|
560
|
+
this.#current = entry;
|
|
561
|
+
}
|
|
562
|
+
beginValue(c, at) {
|
|
563
|
+
if (isWhitespace(c)) return;
|
|
564
|
+
if (c === "\"") {
|
|
565
|
+
this.open({
|
|
566
|
+
kind: "string",
|
|
567
|
+
start: at + 1,
|
|
568
|
+
end: -1,
|
|
569
|
+
needsDecoding: false,
|
|
570
|
+
invalidAt: void 0,
|
|
571
|
+
length: void 0,
|
|
572
|
+
text: void 0,
|
|
573
|
+
prefixes: void 0
|
|
574
|
+
});
|
|
575
|
+
this.#escape = false;
|
|
576
|
+
this.#mode = "string";
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (c === "}" || c === "," || c === ":" || c === "]") {
|
|
580
|
+
this.fail();
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
this.open({
|
|
584
|
+
kind: "value",
|
|
585
|
+
start: at,
|
|
586
|
+
end: -1,
|
|
587
|
+
parsed: void 0,
|
|
588
|
+
invalid: false
|
|
589
|
+
});
|
|
590
|
+
if (c === "{" || c === "[") {
|
|
591
|
+
this.#mode = "nested";
|
|
592
|
+
this.#nestedEnds = [c === "{" ? "}" : "]"];
|
|
593
|
+
this.#nestedInString = false;
|
|
594
|
+
this.#escape = false;
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
this.#mode = "scalar";
|
|
598
|
+
}
|
|
599
|
+
stepScalar(c, at) {
|
|
600
|
+
if (c !== "," && c !== "}" && !isWhitespace(c)) return;
|
|
601
|
+
this.closeValue(at);
|
|
602
|
+
this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
|
|
603
|
+
}
|
|
604
|
+
stepNested(c, at) {
|
|
605
|
+
if (this.#nestedInString) {
|
|
606
|
+
this.#nestedInString = false;
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (c === "\"") {
|
|
610
|
+
this.#nestedInString = true;
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (c === "{" || c === "[") {
|
|
614
|
+
this.#nestedEnds.push(c === "{" ? "}" : "]");
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (c === "}" || c === "]") {
|
|
618
|
+
if (this.#nestedEnds.pop() !== c) {
|
|
619
|
+
this.fail();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (this.#nestedEnds.length === 0) {
|
|
623
|
+
this.closeValue(at + 1);
|
|
624
|
+
this.#mode = "comma-or-end";
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
closeValue(end) {
|
|
629
|
+
const entry = this.#current;
|
|
630
|
+
entry.end = end;
|
|
631
|
+
this.#current = null;
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region ../../util/values/src/index.ts
|
|
636
|
+
/**
|
|
637
|
+
* Mark an unreachable closed-union branch.
|
|
638
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
639
|
+
* @param context - optional switch-site label included in the failure message.
|
|
640
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
641
|
+
*/
|
|
642
|
+
function assertNever(value, context) {
|
|
643
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
644
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Weak-key lookup with a strongly retained iterable set of associated values.
|
|
648
|
+
*
|
|
649
|
+
* Each value must belong to only one key. The container performs no automatic
|
|
650
|
+
* cleanup; owners delete associations or clear the container at lifecycle end.
|
|
651
|
+
*/
|
|
652
|
+
var WeakMapWithValues = class {
|
|
653
|
+
keys = /* @__PURE__ */ new WeakMap();
|
|
654
|
+
valueSet = /* @__PURE__ */ new Set();
|
|
655
|
+
/** Live strongly retained values in insertion order. */
|
|
656
|
+
values = this.valueSet;
|
|
657
|
+
/**
|
|
658
|
+
* Read the value associated with a key.
|
|
659
|
+
* @param key - weakly held lookup key.
|
|
660
|
+
* @returns the associated value, or absence.
|
|
661
|
+
*/
|
|
662
|
+
get(key) {
|
|
663
|
+
return this.keys.get(key);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Test whether a key has an association.
|
|
667
|
+
* @param key - weakly held lookup key.
|
|
668
|
+
* @returns whether the key is present.
|
|
669
|
+
*/
|
|
670
|
+
has(key) {
|
|
671
|
+
return this.keys.has(key);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Associate one key with one caller-unique value.
|
|
675
|
+
* @param key - weakly held lookup key.
|
|
676
|
+
* @param value - strongly retained value that belongs to no other key.
|
|
677
|
+
* @returns this container.
|
|
678
|
+
*/
|
|
679
|
+
set(key, value) {
|
|
680
|
+
if (this.keys.has(key)) {
|
|
681
|
+
const previous = this.keys.get(key);
|
|
682
|
+
if (previous === value) return this;
|
|
683
|
+
this.valueSet.delete(previous);
|
|
684
|
+
}
|
|
685
|
+
this.keys.set(key, value);
|
|
686
|
+
this.valueSet.add(value);
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Remove one association and its strongly retained value.
|
|
691
|
+
* @param key - weakly held lookup key.
|
|
692
|
+
* @returns whether an association was removed.
|
|
693
|
+
*/
|
|
694
|
+
delete(key) {
|
|
695
|
+
if (!this.keys.has(key)) return false;
|
|
696
|
+
const value = this.keys.get(key);
|
|
697
|
+
const deleted = this.keys.delete(key);
|
|
698
|
+
this.valueSet.delete(value);
|
|
699
|
+
return deleted;
|
|
700
|
+
}
|
|
701
|
+
/** Remove every association and strongly retained value. */
|
|
702
|
+
clear() {
|
|
703
|
+
this.keys = /* @__PURE__ */ new WeakMap();
|
|
704
|
+
this.valueSet.clear();
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
//#endregion
|
|
708
|
+
//#region ../../preset/agent-presets/src/display.ts
|
|
709
|
+
const BUILT_IN_PRESET_KEYS = { ptc: {
|
|
710
|
+
name: "presetPtcName",
|
|
711
|
+
description: "presetPtcDescription"
|
|
712
|
+
} };
|
|
713
|
+
/**
|
|
714
|
+
* Resolve preset display copy without making user-authored metadata translatable.
|
|
715
|
+
* @param preset - roster row whose copy is being rendered.
|
|
716
|
+
* @param t - active locale lookup covering {@link BuiltInPresetCopyKey}.
|
|
717
|
+
* @returns localized copy for a known shipped preset, otherwise file metadata.
|
|
718
|
+
*/
|
|
719
|
+
function presetDisplayText(preset, t) {
|
|
720
|
+
const keys = preset.trust === "system" ? BUILT_IN_PRESET_KEYS[preset.id] : void 0;
|
|
721
|
+
if (keys !== void 0) return {
|
|
722
|
+
name: t(keys.name),
|
|
723
|
+
description: t(keys.description)
|
|
724
|
+
};
|
|
725
|
+
return {
|
|
726
|
+
name: preset.name ?? preset.id,
|
|
727
|
+
...preset.description === void 0 ? {} : { description: preset.description }
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
//#endregion
|
|
731
|
+
//#region lib/types/client/locales.js
|
|
732
|
+
/** Locale bundles for the agent-preset hero chip, header label, and management section. */
|
|
733
|
+
/** English copy. */
|
|
734
|
+
const en = {
|
|
735
|
+
error: "Could not load agent presets.",
|
|
736
|
+
userTrust: "Custom",
|
|
737
|
+
seatHint: "Agent preset for the session you are about to start",
|
|
738
|
+
headerHint: "The agent preset this session runs, fixed when it started",
|
|
739
|
+
nav: "Agent presets",
|
|
740
|
+
sectionIntro: "A preset is the plugin composition one session's agent runs — its tools, prompt, and capabilities. Duplicate an existing one and make it yours, or let the agent draft one for you.",
|
|
741
|
+
builtIn: "Built-in",
|
|
742
|
+
setDefault: "Set as default",
|
|
743
|
+
view: "View",
|
|
744
|
+
presetPtcName: "Coding mode",
|
|
745
|
+
presetPtcDescription: "Full coding agent without the workflow tool; other tools are exposed through a generated SDK so the model can combine multi-step operations in one TypeScript program.",
|
|
746
|
+
duplicate: "Duplicate",
|
|
747
|
+
duplicateUnavailable: "This deployment has no writable preset directory",
|
|
748
|
+
delete: "Delete",
|
|
749
|
+
presetId: "Identifier",
|
|
750
|
+
presetIdPlaceholder: "my-agent",
|
|
751
|
+
displayName: "Name",
|
|
752
|
+
displayNamePlaceholder: "Shown in the picker; defaults to the identifier",
|
|
753
|
+
inUse: "New task default",
|
|
754
|
+
selectionOffDefault: "Default",
|
|
755
|
+
builtInGroup: "Built-in",
|
|
756
|
+
customGroup: "Custom",
|
|
757
|
+
noDescription: "No description.",
|
|
758
|
+
brokenBadge: "Failed to load",
|
|
759
|
+
brokenNoCopy: "A preset that failed to load cannot be duplicated",
|
|
760
|
+
switchRefused: "Could not switch to {name}: {reason}",
|
|
761
|
+
copyOf: "Copied from",
|
|
762
|
+
composition: "Composition (agent.cordis.yml)",
|
|
763
|
+
cancel: "Cancel",
|
|
764
|
+
close: "Close",
|
|
765
|
+
retry: "Retry",
|
|
766
|
+
copyTitle: "Duplicate preset",
|
|
767
|
+
copyIntro: "The whole preset is copied on this machine. The identifier becomes its directory name and cannot be changed later; everything else is edited in the preset's own files.",
|
|
768
|
+
create: "Create",
|
|
769
|
+
creating: "Creating…",
|
|
770
|
+
creatorDraft: "Draft a custom preset with the agent",
|
|
771
|
+
createPlugin: "Let the agent create a plugin",
|
|
772
|
+
createPluginDescription: "Ask the agent to make your own plugin",
|
|
773
|
+
createPluginChecking: "Checking whether plugin creation is available",
|
|
774
|
+
createPluginUnavailable: "Temporarily unavailable. Reopen this menu to retry",
|
|
775
|
+
createPluginMissing: "This configuration cannot create plugins",
|
|
776
|
+
openLocation: "Open folder",
|
|
777
|
+
showLocation: "Show location",
|
|
778
|
+
revealedPathLabel: "Preset files:",
|
|
779
|
+
idRequired: "Give the preset an identifier.",
|
|
780
|
+
idInvalid: "Use lowercase letters, digits, and hyphens, starting with a letter or digit.",
|
|
781
|
+
idTaken: "A preset with this identifier already exists.",
|
|
782
|
+
deleteTitle: "Delete this preset?",
|
|
783
|
+
deleteDescription: "The preset directory is deleted. Sessions already running on it keep working; new sessions cannot select it.",
|
|
784
|
+
deleteConfirm: "Delete",
|
|
785
|
+
deleting: "Deleting…",
|
|
786
|
+
showPicker: "Allow switching Agent modes",
|
|
787
|
+
showPickerBeta: "Beta",
|
|
788
|
+
showPickerDescription: "When enabled, new tasks can choose among the available modes. When disabled, all new tasks use the default mode (configurable). Only affects new tasks.",
|
|
789
|
+
enablePickerToSetDefault: "Turn on Agent mode selection to choose a default",
|
|
790
|
+
enablePickerToCreate: "Turn on Agent mode selection to draft a preset with the agent"
|
|
791
|
+
};
|
|
792
|
+
/** Simplified Chinese copy. */
|
|
793
|
+
const zh = {
|
|
794
|
+
error: "无法加载 Agent 预设。",
|
|
795
|
+
userTrust: "自定义",
|
|
796
|
+
seatHint: "即将开始的这个会话所用的 Agent 预设",
|
|
797
|
+
headerHint: "本会话运行的 Agent 预设,开始时即固定",
|
|
798
|
+
nav: "Agent 预设",
|
|
799
|
+
sectionIntro: "预设即一个会话的 Agent 所运行的插件组装 —— 它的工具、提示词与能力。复制一份既有预设改成自己的,或让 Agent 帮你创建。",
|
|
800
|
+
builtIn: "内置",
|
|
801
|
+
setDefault: "设为默认",
|
|
802
|
+
view: "查看",
|
|
803
|
+
presetPtcName: "编码模式",
|
|
804
|
+
presetPtcDescription: "功能完整的编码 Agent,但默认不提供 workflow 工具;其他工具经生成的 SDK 呈现,让模型用一个 TypeScript 程序组合多步操作。",
|
|
805
|
+
duplicate: "复制",
|
|
806
|
+
duplicateUnavailable: "此部署未配置可写的预设目录",
|
|
807
|
+
delete: "删除",
|
|
808
|
+
presetId: "标识符",
|
|
809
|
+
presetIdPlaceholder: "my-agent",
|
|
810
|
+
displayName: "名称",
|
|
811
|
+
displayNamePlaceholder: "选择器中显示的名字,缺省用标识符",
|
|
812
|
+
inUse: "新任务默认",
|
|
813
|
+
selectionOffDefault: "默认",
|
|
814
|
+
builtInGroup: "内置",
|
|
815
|
+
customGroup: "自定义",
|
|
816
|
+
noDescription: "暂无描述。",
|
|
817
|
+
brokenBadge: "加载失败",
|
|
818
|
+
brokenNoCopy: "预设加载失败,不能复制",
|
|
819
|
+
switchRefused: "无法切换到「{name}」:{reason}",
|
|
820
|
+
copyOf: "复制自",
|
|
821
|
+
composition: "组装(agent.cordis.yml)",
|
|
822
|
+
cancel: "取消",
|
|
823
|
+
close: "关闭",
|
|
824
|
+
retry: "重试",
|
|
825
|
+
copyTitle: "复制预设",
|
|
826
|
+
copyIntro: "整个预设会在本机复制一份。标识符将成为目录名,事后无法更改;其余内容之后直接在预设自己的文件里编辑。",
|
|
827
|
+
create: "创建",
|
|
828
|
+
creating: "正在创建…",
|
|
829
|
+
creatorDraft: "让 Agent 起草自定义预设",
|
|
830
|
+
createPlugin: "让 Agent 创建插件",
|
|
831
|
+
createPluginDescription: "让 Agent 制作属于你的插件",
|
|
832
|
+
createPluginChecking: "正在确认是否可以创建插件",
|
|
833
|
+
createPluginUnavailable: "暂时不可用,请重新打开菜单重试",
|
|
834
|
+
createPluginMissing: "当前配置无法创建插件",
|
|
835
|
+
openLocation: "打开目录",
|
|
836
|
+
showLocation: "查看路径",
|
|
837
|
+
revealedPathLabel: "预设文件:",
|
|
838
|
+
idRequired: "请填写标识符。",
|
|
839
|
+
idInvalid: "只能使用小写字母、数字与连字符,且以字母或数字开头。",
|
|
840
|
+
idTaken: "该标识符已被占用。",
|
|
841
|
+
deleteTitle: "删除该预设?",
|
|
842
|
+
deleteDescription: "预设目录将被删除。已在其上运行的会话不受影响;新会话将无法再选择它。",
|
|
843
|
+
deleteConfirm: "删除",
|
|
844
|
+
deleting: "正在删除…",
|
|
845
|
+
showPicker: "允许切换agent模式",
|
|
846
|
+
showPickerBeta: "beta",
|
|
847
|
+
showPickerDescription: "开启后,新任务可在可选模式中选择;关闭后统一使用默认模式(可自定义)。仅影响新任务。",
|
|
848
|
+
enablePickerToSetDefault: "请先开启 Agent 模式选择,再设置默认模式",
|
|
849
|
+
enablePickerToCreate: "请先开启 Agent 模式选择,再让 Agent 起草预设"
|
|
850
|
+
};
|
|
851
|
+
//#endregion
|
|
852
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-agent-preset/src/client/AgentPresetLabel.module.css.mjs
|
|
853
|
+
const css$3 = "._5HW_OW_label{background:var(--qilin-alias-fill-tsp-secondary);max-width:180px;height:22px;color:var(--qilin-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;border-radius:6px;align-items:center;gap:4px;padding:0 2px 0 0;font-size:12px;line-height:22px;display:inline-flex;overflow:hidden}._5HW_OW_icon{opacity:.7;flex:none}";
|
|
854
|
+
const tagId$3 = "@qilin-agent/client-ui-agent-preset/AgentPresetLabel.module.css";
|
|
855
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
856
|
+
const tag = document.createElement("style");
|
|
857
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-agent-preset";
|
|
858
|
+
tag.dataset.pluginCss = tagId$3;
|
|
859
|
+
tag.textContent = css$3;
|
|
860
|
+
document.head.appendChild(tag);
|
|
861
|
+
}
|
|
862
|
+
var AgentPresetLabel_module_css_default = {
|
|
863
|
+
"icon": "_5HW_OW_icon",
|
|
864
|
+
"label": "_5HW_OW_label"
|
|
865
|
+
};
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region lib/types/client/AgentPresetLabel.js
|
|
868
|
+
/**
|
|
869
|
+
* The session header's agent-preset label.
|
|
870
|
+
*
|
|
871
|
+
* Read-only by construction: a session's composition is fixed once its
|
|
872
|
+
* conversation starts, and a header is only worth reading after that. Offering
|
|
873
|
+
* a control here would promise a switch the host refuses; naming what the
|
|
874
|
+
* session runs is the honest affordance, and the choice itself lives on the
|
|
875
|
+
* new-session screen ({@link AgentPresetSeat}).
|
|
876
|
+
*/
|
|
877
|
+
/**
|
|
878
|
+
* Render this session's agent-preset name beside its title.
|
|
879
|
+
* @param props - composed slot props.
|
|
880
|
+
* @returns the label, or null when the session records no preset.
|
|
881
|
+
*/
|
|
882
|
+
function AgentPresetLabel({ sessionId, useSessions, useAgentPresets, load, t }) {
|
|
883
|
+
const preset = useSessions((state) => {
|
|
884
|
+
const value = state.byId[sessionId]?.projectionValues?.agentPreset;
|
|
885
|
+
return typeof value === "string" ? value : void 0;
|
|
886
|
+
});
|
|
887
|
+
const options = useAgentPresets((state) => state.options);
|
|
888
|
+
(0, react.useEffect)(() => {
|
|
889
|
+
if (preset !== void 0) load();
|
|
890
|
+
}, [preset, load]);
|
|
891
|
+
if (preset === void 0) return null;
|
|
892
|
+
const option = options.find((entry) => entry.id === preset);
|
|
893
|
+
const text = option === void 0 ? void 0 : presetDisplayText(option, t);
|
|
894
|
+
return (0, react_jsx_runtime.jsxs)("span", {
|
|
895
|
+
className: AgentPresetLabel_module_css_default.label,
|
|
896
|
+
title: text?.description ?? t("headerHint"),
|
|
897
|
+
children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconAgentPresetOutline16, {
|
|
898
|
+
size: 14,
|
|
899
|
+
className: AgentPresetLabel_module_css_default.icon
|
|
900
|
+
}), text?.name ?? preset]
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
//#endregion
|
|
904
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-agent-preset/src/client/AgentPresetSeat.module.css.mjs
|
|
905
|
+
const css$2 = ".yNtIwG_menuAnchor{min-width:54px;max-width:100%}.yNtIwG_seat{min-width:0;max-width:min(100%,240px);min-height:28px;color:var(--qilin-alias-label-primary);white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:16px;align-items:center;gap:4px;padding:0 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.yNtIwG_seat:not(:disabled):hover,.yNtIwG_seat[aria-expanded=true]{background:var(--qilin-alias-interactive-bg-hover)}.yNtIwG_seat:disabled{cursor:default;color:var(--qilin-alias-label-quaternary)}.yNtIwG_seatIcon{color:var(--qilin-alias-label-primary);flex:none}.yNtIwG_seatLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.yNtIwG_introIcon{animation:.15s cubic-bezier(.16,1,.3,1) both yNtIwG_seat-icon-in}@keyframes yNtIwG_seat-icon-in{0%{opacity:0;transform:scale(.5)}to{opacity:1;transform:scale(1)}}.yNtIwG_introText{white-space:pre;display:inline-block}.yNtIwG_introChar{white-space:pre;opacity:0;animation:.4s ease-out forwards yNtIwG_seat-char-in;display:inline-block}@keyframes yNtIwG_seat-char-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.yNtIwG_introIcon,.yNtIwG_introChar{opacity:1;animation:none}}.yNtIwG_chevron{color:var(--qilin-alias-label-caption);flex:none}.yNtIwG_item{flex-direction:column;gap:2px;max-width:280px;display:flex}.yNtIwG_itemName{color:var(--qilin-alias-label-primary);font-size:13px;line-height:20px}.yNtIwG_itemDesc{color:var(--qilin-alias-label-caption);white-space:normal;font-size:12px;line-height:16px}";
|
|
906
|
+
const tagId$2 = "@qilin-agent/client-ui-agent-preset/AgentPresetSeat.module.css";
|
|
907
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
908
|
+
const tag = document.createElement("style");
|
|
909
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-agent-preset";
|
|
910
|
+
tag.dataset.pluginCss = tagId$2;
|
|
911
|
+
tag.textContent = css$2;
|
|
912
|
+
document.head.appendChild(tag);
|
|
913
|
+
}
|
|
914
|
+
var AgentPresetSeat_module_css_default = {
|
|
915
|
+
"chevron": "yNtIwG_chevron",
|
|
916
|
+
"introChar": "yNtIwG_introChar",
|
|
917
|
+
"introIcon": "yNtIwG_introIcon",
|
|
918
|
+
"introText": "yNtIwG_introText",
|
|
919
|
+
"item": "yNtIwG_item",
|
|
920
|
+
"itemDesc": "yNtIwG_itemDesc",
|
|
921
|
+
"itemName": "yNtIwG_itemName",
|
|
922
|
+
"menuAnchor": "yNtIwG_menuAnchor",
|
|
923
|
+
"seat": "yNtIwG_seat",
|
|
924
|
+
"seat-char-in": "yNtIwG_seat-char-in",
|
|
925
|
+
"seat-icon-in": "yNtIwG_seat-icon-in",
|
|
926
|
+
"seatIcon": "yNtIwG_seatIcon",
|
|
927
|
+
"seatLabel": "yNtIwG_seatLabel"
|
|
928
|
+
};
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region lib/types/client/AgentPresetSeat.js
|
|
931
|
+
/**
|
|
932
|
+
* The agent-preset chip on the new-session screen, beside the workspace
|
|
933
|
+
* picker.
|
|
934
|
+
*
|
|
935
|
+
* It lives here rather than in the composer because the choice is only
|
|
936
|
+
* available before a conversation starts: once a turn has run, the session's
|
|
937
|
+
* history was produced under that preset's tools and the host refuses to swap
|
|
938
|
+
* them. A control that spends most of its life disabled belongs on the screen
|
|
939
|
+
* where it still works.
|
|
940
|
+
*
|
|
941
|
+
* The menu opens on the staged choice, which starts as the deployment default.
|
|
942
|
+
* Picking stages; the choice reaches a session when one becomes current.
|
|
943
|
+
*/
|
|
944
|
+
const INTRO_TEXT_DELAY_MS = 150;
|
|
945
|
+
const INTRO_CHAR_STAGGER_MS = 40;
|
|
946
|
+
const INTRO_TEXT_REVEAL_MS = 200;
|
|
947
|
+
const INTRO_CHAR_FADE_MS = 400;
|
|
948
|
+
/**
|
|
949
|
+
* How long a refused switch holds before fading.
|
|
950
|
+
*
|
|
951
|
+
* Longer than the primitive's default because this banner is the only place
|
|
952
|
+
* the refusal appears. The chip's label has already snapped back to the
|
|
953
|
+
* preset the session still runs, and a preset the host refuses to MOUNT is
|
|
954
|
+
* one discovery reported healthy — its row on the settings page carries no
|
|
955
|
+
* reason to go back and read, because there was nothing to see until the
|
|
956
|
+
* rows actually ran.
|
|
957
|
+
*/
|
|
958
|
+
const REFUSAL_HOLD_MS = 8e3;
|
|
959
|
+
/**
|
|
960
|
+
* Per-character start offset for the introduce reveal.
|
|
961
|
+
* @param count - character count of the shown preset name.
|
|
962
|
+
* @returns milliseconds between successive character starts.
|
|
963
|
+
*/
|
|
964
|
+
function introStaggerMs(count) {
|
|
965
|
+
if (count <= 1) return 0;
|
|
966
|
+
return Math.min(INTRO_CHAR_STAGGER_MS, INTRO_TEXT_REVEAL_MS / (count - 1));
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Render the new-session agent-preset chip.
|
|
970
|
+
* @param props - composed slot props.
|
|
971
|
+
* @returns the chip and any pending selection refusal, or null outside the main view.
|
|
972
|
+
*/
|
|
973
|
+
function AgentPresetSeat({ sessionId, useSessionRetainInfo, load, select, dismissRefusal, introduced, useAgentPresetSeat, t }) {
|
|
974
|
+
const state = useAgentPresetSeat((snapshot) => snapshot);
|
|
975
|
+
const main = useSessionRetainInfo((info) => sessionId === void 0 || (info?.retainedBy.mainView ?? 0) > 0);
|
|
976
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
977
|
+
const toastSeq = (0, react.useRef)(0);
|
|
978
|
+
const [toast, setToast] = (0, react.useState)(null);
|
|
979
|
+
(0, react.useEffect)(() => {
|
|
980
|
+
load();
|
|
981
|
+
}, [load]);
|
|
982
|
+
(0, react.useEffect)(() => {
|
|
983
|
+
if (state.error !== null && typeof state.error === "object") {
|
|
984
|
+
toastSeq.current += 1;
|
|
985
|
+
setToast({
|
|
986
|
+
seq: toastSeq.current,
|
|
987
|
+
error: state.error
|
|
988
|
+
});
|
|
989
|
+
} else setToast(null);
|
|
990
|
+
}, [state.error]);
|
|
991
|
+
(0, react.useEffect)(() => {
|
|
992
|
+
if (state.showPicker) return;
|
|
993
|
+
setOpen(false);
|
|
994
|
+
setToast(null);
|
|
995
|
+
}, [state.showPicker]);
|
|
996
|
+
const chosen = state.options.find((option) => option.id === state.current);
|
|
997
|
+
const label = (chosen === void 0 ? void 0 : presetDisplayText(chosen, t))?.name ?? state.current;
|
|
998
|
+
const ready = state.options.length > 0 && state.current !== "";
|
|
999
|
+
const [introducing, setIntroducing] = (0, react.useState)(false);
|
|
1000
|
+
(0, react.useEffect)(() => {
|
|
1001
|
+
if (!state.introduce || !ready) return;
|
|
1002
|
+
const characters = Array.from(label);
|
|
1003
|
+
if (characters.length === 0 || window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
1004
|
+
introduced();
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
setIntroducing(true);
|
|
1008
|
+
const done = window.setTimeout(() => {
|
|
1009
|
+
setIntroducing(false);
|
|
1010
|
+
introduced();
|
|
1011
|
+
}, INTRO_TEXT_DELAY_MS + (characters.length - 1) * introStaggerMs(characters.length) + INTRO_CHAR_FADE_MS);
|
|
1012
|
+
return () => {
|
|
1013
|
+
window.clearTimeout(done);
|
|
1014
|
+
};
|
|
1015
|
+
}, [
|
|
1016
|
+
state.introduce,
|
|
1017
|
+
ready,
|
|
1018
|
+
label,
|
|
1019
|
+
introduced
|
|
1020
|
+
]);
|
|
1021
|
+
if (!main) return null;
|
|
1022
|
+
const characters = Array.from(label);
|
|
1023
|
+
const stagger = introStaggerMs(characters.length);
|
|
1024
|
+
const shownLabel = introducing ? (0, react_jsx_runtime.jsx)("span", {
|
|
1025
|
+
className: AgentPresetSeat_module_css_default.introText,
|
|
1026
|
+
children: characters.map((character, index) => (0, react_jsx_runtime.jsx)("span", {
|
|
1027
|
+
className: AgentPresetSeat_module_css_default.introChar,
|
|
1028
|
+
style: { animationDelay: `${INTRO_TEXT_DELAY_MS + index * stagger}ms` },
|
|
1029
|
+
children: character
|
|
1030
|
+
}, index))
|
|
1031
|
+
}) : label;
|
|
1032
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state.showPicker && ready && (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Menu, {
|
|
1033
|
+
open,
|
|
1034
|
+
onClose: () => {
|
|
1035
|
+
setOpen(false);
|
|
1036
|
+
},
|
|
1037
|
+
items: state.options.map((option) => {
|
|
1038
|
+
const text = presetDisplayText(option, t);
|
|
1039
|
+
return {
|
|
1040
|
+
id: option.id,
|
|
1041
|
+
label: (0, react_jsx_runtime.jsxs)("span", {
|
|
1042
|
+
className: AgentPresetSeat_module_css_default.item,
|
|
1043
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1044
|
+
className: AgentPresetSeat_module_css_default.itemName,
|
|
1045
|
+
children: text.name
|
|
1046
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1047
|
+
className: AgentPresetSeat_module_css_default.itemDesc,
|
|
1048
|
+
children: text.description ?? t("noDescription")
|
|
1049
|
+
})]
|
|
1050
|
+
})
|
|
1051
|
+
};
|
|
1052
|
+
}),
|
|
1053
|
+
selectedId: state.current,
|
|
1054
|
+
onSelect: (id) => {
|
|
1055
|
+
setOpen(false);
|
|
1056
|
+
select(id);
|
|
1057
|
+
},
|
|
1058
|
+
align: "start",
|
|
1059
|
+
portal: true,
|
|
1060
|
+
className: AgentPresetSeat_module_css_default.menuAnchor,
|
|
1061
|
+
anchor: (0, react_jsx_runtime.jsxs)("button", {
|
|
1062
|
+
type: "button",
|
|
1063
|
+
className: AgentPresetSeat_module_css_default.seat,
|
|
1064
|
+
"aria-haspopup": "menu",
|
|
1065
|
+
"aria-expanded": open,
|
|
1066
|
+
title: (typeof state.error === "object" ? state.error?.reason : state.error) ?? t("seatHint"),
|
|
1067
|
+
disabled: state.busy,
|
|
1068
|
+
onClick: () => {
|
|
1069
|
+
setOpen((value) => !value);
|
|
1070
|
+
},
|
|
1071
|
+
children: [
|
|
1072
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconAgentPresetOutline16, { className: introducing ? `${AgentPresetSeat_module_css_default.seatIcon} ${AgentPresetSeat_module_css_default.introIcon}` : AgentPresetSeat_module_css_default.seatIcon }),
|
|
1073
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1074
|
+
className: AgentPresetSeat_module_css_default.seatLabel,
|
|
1075
|
+
children: shownLabel
|
|
1076
|
+
}),
|
|
1077
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronDownOutline14, { className: AgentPresetSeat_module_css_default.chevron })
|
|
1078
|
+
]
|
|
1079
|
+
})
|
|
1080
|
+
}), toast !== null && (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Toast, {
|
|
1081
|
+
text: t("switchRefused", {
|
|
1082
|
+
name: presetDisplayText(toast.error.preset, t).name,
|
|
1083
|
+
reason: toast.error.reason
|
|
1084
|
+
}),
|
|
1085
|
+
icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconWarningOutline16, {}),
|
|
1086
|
+
holdMs: REFUSAL_HOLD_MS,
|
|
1087
|
+
anchor: document.querySelector("[data-composer-card]"),
|
|
1088
|
+
onDone: () => {
|
|
1089
|
+
dismissRefusal(toast.error);
|
|
1090
|
+
}
|
|
1091
|
+
}, toast.seq)] });
|
|
1092
|
+
}
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-agent-preset/src/client/CreatePluginMenuItem.module.css.mjs
|
|
1095
|
+
const css$1 = ".B_pwMW_copy{white-space:normal;flex-direction:column;gap:2px;min-width:0;display:flex}.B_pwMW_description{overflow-wrap:anywhere;max-height:36px;color:var(--qilin-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}";
|
|
1096
|
+
const tagId$1 = "@qilin-agent/client-ui-agent-preset/CreatePluginMenuItem.module.css";
|
|
1097
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
1098
|
+
const tag = document.createElement("style");
|
|
1099
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-agent-preset";
|
|
1100
|
+
tag.dataset.pluginCss = tagId$1;
|
|
1101
|
+
tag.textContent = css$1;
|
|
1102
|
+
document.head.appendChild(tag);
|
|
1103
|
+
}
|
|
1104
|
+
var CreatePluginMenuItem_module_css_default = {
|
|
1105
|
+
"copy": "B_pwMW_copy",
|
|
1106
|
+
"description": "B_pwMW_description"
|
|
1107
|
+
};
|
|
1108
|
+
//#endregion
|
|
1109
|
+
//#region lib/types/client/CreatePluginMenuItem.js
|
|
1110
|
+
/** Create a plugin through the existing Creator flow from the Add plugin menu. */
|
|
1111
|
+
/**
|
|
1112
|
+
* Close the Add plugin menu before opening Creator without sending a message;
|
|
1113
|
+
* the draft the visitor typed stays until they send it.
|
|
1114
|
+
* @param props - locale, roster, dismissal and navigation callbacks.
|
|
1115
|
+
* @returns a stable menu item that explains why Creator is unavailable.
|
|
1116
|
+
*/
|
|
1117
|
+
function CreatePluginMenuItem({ t, useAgentPresets, load, onDismiss, startCreatorDraft }) {
|
|
1118
|
+
const roster = useAgentPresets((state) => state);
|
|
1119
|
+
const enabled = roster.status === "ready" && roster.options.some((option) => option.id === "ptc");
|
|
1120
|
+
(0, react.useEffect)(() => {
|
|
1121
|
+
load();
|
|
1122
|
+
}, [load]);
|
|
1123
|
+
let description = t("createPluginDescription");
|
|
1124
|
+
if (roster.status === "idle" || roster.status === "loading") description = t("createPluginChecking");
|
|
1125
|
+
else if (roster.status === "error" || roster.status === "unavailable") description = t("createPluginUnavailable");
|
|
1126
|
+
else if (!enabled) description = t("createPluginMissing");
|
|
1127
|
+
return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.MenuItemButton, {
|
|
1128
|
+
icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconAgentPresetOutline16, { size: 14 }),
|
|
1129
|
+
disabled: !enabled,
|
|
1130
|
+
onSelect: () => {
|
|
1131
|
+
onDismiss();
|
|
1132
|
+
startCreatorDraft();
|
|
1133
|
+
},
|
|
1134
|
+
children: (0, react_jsx_runtime.jsxs)("span", {
|
|
1135
|
+
className: CreatePluginMenuItem_module_css_default.copy,
|
|
1136
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("createPlugin") }), (0, react_jsx_runtime.jsx)("span", {
|
|
1137
|
+
className: CreatePluginMenuItem_module_css_default.description,
|
|
1138
|
+
title: description,
|
|
1139
|
+
children: description
|
|
1140
|
+
})]
|
|
1141
|
+
})
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
//#endregion
|
|
1145
|
+
//#region lib/types/client/settings-store.js
|
|
1146
|
+
/**
|
|
1147
|
+
* Agent-preset roster store shared by the display surfaces.
|
|
1148
|
+
*
|
|
1149
|
+
* Options come from one `agentPresets.list` call. Writes target the settings
|
|
1150
|
+
* namespace fields the host resolves at creation; the management section is
|
|
1151
|
+
* the surface that writes them.
|
|
1152
|
+
*/
|
|
1153
|
+
/** The agent-preset settings namespace on the host wire. */
|
|
1154
|
+
const AGENT_PRESET_SETTINGS_NS = "agent-presets";
|
|
1155
|
+
/** Write only the named agent-preset settings fields. */
|
|
1156
|
+
async function writeAgentPresetSettings(ctx, patch) {
|
|
1157
|
+
const response = await ctx.remote.settings.update(AGENT_PRESET_SETTINGS_NS, patch, void 0);
|
|
1158
|
+
return response.ok ? void 0 : response.error.message;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Persist one preset as the default for sessions created later.
|
|
1162
|
+
*
|
|
1163
|
+
* The default is a settings field rather than a preset property; the
|
|
1164
|
+
* management section writes it here — one home for which namespace and field
|
|
1165
|
+
* the host resolves at session creation.
|
|
1166
|
+
* @param ctx - the browser plugin context carrying the Remote namespaces.
|
|
1167
|
+
* @param id - the preset to make default.
|
|
1168
|
+
* @returns the failure message, or undefined once the write landed.
|
|
1169
|
+
*/
|
|
1170
|
+
function writeDefaultPreset(ctx, id) {
|
|
1171
|
+
return writeAgentPresetSettings(ctx, { default: id });
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Persist whether new-session surfaces expose preset selection.
|
|
1175
|
+
* @param ctx - the browser plugin context carrying the Remote namespaces.
|
|
1176
|
+
* @param enabled - whether the picker should be exposed.
|
|
1177
|
+
* @returns the failure message, or undefined once the write landed.
|
|
1178
|
+
*/
|
|
1179
|
+
function writeModeSelectionEnabled(ctx, enabled) {
|
|
1180
|
+
return writeAgentPresetSettings(ctx, { modeSelectionEnabled: enabled });
|
|
1181
|
+
}
|
|
1182
|
+
const EMPTY_ROSTER = {
|
|
1183
|
+
presets: [],
|
|
1184
|
+
authorable: false,
|
|
1185
|
+
modeSelectionEnabled: false
|
|
1186
|
+
};
|
|
1187
|
+
/**
|
|
1188
|
+
* Read the roster, turning a refusal into the message every surface shows.
|
|
1189
|
+
* @param ctx - the browser plugin context carrying the Remote namespaces.
|
|
1190
|
+
* @returns the roster, or the message to show in its place.
|
|
1191
|
+
*/
|
|
1192
|
+
async function readRoster(ctx) {
|
|
1193
|
+
const result = await ctx.remote.agentPresets.list();
|
|
1194
|
+
if (result.ok) return {
|
|
1195
|
+
ok: true,
|
|
1196
|
+
value: result.value
|
|
1197
|
+
};
|
|
1198
|
+
if (result.error.code === "gateway/invocation-unavailable") return {
|
|
1199
|
+
ok: true,
|
|
1200
|
+
value: EMPTY_ROSTER
|
|
1201
|
+
};
|
|
1202
|
+
return {
|
|
1203
|
+
ok: false,
|
|
1204
|
+
error: result.error.message
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* The opening move every roster-backed surface makes: refuse a read that is
|
|
1209
|
+
* already in flight, mark the store loading, then read.
|
|
1210
|
+
*
|
|
1211
|
+
* A surface that gets `undefined` returns without touching its snapshot
|
|
1212
|
+
* further — either another read owns it, or this one already wrote the
|
|
1213
|
+
* failure. What differs between surfaces starts after this.
|
|
1214
|
+
* @param ctx - the browser plugin context carrying the Remote namespaces.
|
|
1215
|
+
* @param store - the surface's own snapshot store.
|
|
1216
|
+
* @returns the roster, or undefined when the caller should return.
|
|
1217
|
+
*/
|
|
1218
|
+
async function beginRosterRead(ctx, store) {
|
|
1219
|
+
const before = store.getSnapshot();
|
|
1220
|
+
if (before.status === "loading") return void 0;
|
|
1221
|
+
store.set({
|
|
1222
|
+
...before,
|
|
1223
|
+
status: "loading",
|
|
1224
|
+
error: null
|
|
1225
|
+
});
|
|
1226
|
+
const roster = await readRoster(ctx);
|
|
1227
|
+
if (roster.ok) return roster.value;
|
|
1228
|
+
store.set({
|
|
1229
|
+
...store.getSnapshot(),
|
|
1230
|
+
status: "error",
|
|
1231
|
+
error: roster.error
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* The roster entries as the pickers render them: healthy presets only.
|
|
1236
|
+
*
|
|
1237
|
+
* The chip exists to choose the NEXT session's composition, and a broken
|
|
1238
|
+
* preset cannot compose one — offering it would defer the discovery of that
|
|
1239
|
+
* fact to a failed session start. The management section renders the full
|
|
1240
|
+
* roster (broken rows included) from its own store instead.
|
|
1241
|
+
*
|
|
1242
|
+
* The chip, the header label, and the management section all show the same
|
|
1243
|
+
* facts, and `exactOptionalPropertyTypes` makes "absent" and "present as
|
|
1244
|
+
* undefined" different shapes — so the spread dance belongs in one place rather than
|
|
1245
|
+
* once per store.
|
|
1246
|
+
* @param presets - the roster the host answered with.
|
|
1247
|
+
* @returns one option per selectable preset, in roster order.
|
|
1248
|
+
*/
|
|
1249
|
+
function presetOptions(presets) {
|
|
1250
|
+
return presets.filter((preset) => preset.broken === void 0).map((preset) => ({
|
|
1251
|
+
id: preset.id,
|
|
1252
|
+
trust: preset.trust,
|
|
1253
|
+
...preset.name === void 0 ? {} : { name: preset.name },
|
|
1254
|
+
...preset.description === void 0 ? {} : { description: preset.description }
|
|
1255
|
+
}));
|
|
1256
|
+
}
|
|
1257
|
+
const INITIAL$2 = {
|
|
1258
|
+
status: "idle",
|
|
1259
|
+
error: null,
|
|
1260
|
+
options: []
|
|
1261
|
+
};
|
|
1262
|
+
/** Reads the roster for the surfaces that only display it. */
|
|
1263
|
+
var AgentPresetSettingsController = class {
|
|
1264
|
+
ctx;
|
|
1265
|
+
/** Roster snapshot the renderer subscribes to. */
|
|
1266
|
+
store = (0, _qilin_agent_client_store.createSnapshotStore)(INITIAL$2);
|
|
1267
|
+
/**
|
|
1268
|
+
* @param ctx - the browser plugin context (the roster read).
|
|
1269
|
+
*/
|
|
1270
|
+
constructor(ctx) {
|
|
1271
|
+
this.ctx = ctx;
|
|
1272
|
+
}
|
|
1273
|
+
set(patch) {
|
|
1274
|
+
this.store.set({
|
|
1275
|
+
...this.store.getSnapshot(),
|
|
1276
|
+
...patch
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Load the roster. An empty roster means the deployment composes no
|
|
1281
|
+
* presets, which is a valid deployment rather than a failure — the
|
|
1282
|
+
* surfaces report `unavailable` and render nothing.
|
|
1283
|
+
* @returns once the snapshot reflects the host.
|
|
1284
|
+
*/
|
|
1285
|
+
async load() {
|
|
1286
|
+
const roster = await beginRosterRead(this.ctx, this.store);
|
|
1287
|
+
if (roster === void 0) return;
|
|
1288
|
+
const { presets } = roster;
|
|
1289
|
+
if (presets.length === 0) {
|
|
1290
|
+
this.set({
|
|
1291
|
+
status: "unavailable",
|
|
1292
|
+
options: []
|
|
1293
|
+
});
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
this.set({
|
|
1297
|
+
status: "ready",
|
|
1298
|
+
error: null,
|
|
1299
|
+
options: presetOptions(presets)
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
//#endregion
|
|
1304
|
+
//#region lib/types/client/section-store.js
|
|
1305
|
+
/**
|
|
1306
|
+
* Agent-preset management controller: the roster as a list, a copy dialog as
|
|
1307
|
+
* the only way a preset is created, and a read-only viewer over the shipped
|
|
1308
|
+
* compositions.
|
|
1309
|
+
*
|
|
1310
|
+
* The browser edits no composition text. A new preset is a host-side copy of
|
|
1311
|
+
* an existing one (`{ from, id, name? }` is all that crosses the wire), and
|
|
1312
|
+
* everything after creation happens in the preset's own files — which is why
|
|
1313
|
+
* the page's other job is getting the user TO those files: open the directory
|
|
1314
|
+
* where the host has a desktop, show its path where it does not.
|
|
1315
|
+
*
|
|
1316
|
+
* The host stays the single fact source. Every mutation writes through the
|
|
1317
|
+
* wire and the page re-reads the roster afterwards, because a copy changes
|
|
1318
|
+
* more than the row it targeted.
|
|
1319
|
+
*/
|
|
1320
|
+
/** Ids a preset directory may be named, mirroring the host's own rule. */
|
|
1321
|
+
const PRESET_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
1322
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
1323
|
+
const INITIAL$1 = {
|
|
1324
|
+
status: "idle",
|
|
1325
|
+
error: null,
|
|
1326
|
+
authorable: false,
|
|
1327
|
+
hasDocument: false,
|
|
1328
|
+
showPicker: false,
|
|
1329
|
+
policySaving: false,
|
|
1330
|
+
rows: [],
|
|
1331
|
+
copy: null,
|
|
1332
|
+
view: null,
|
|
1333
|
+
pendingDelete: null,
|
|
1334
|
+
deleting: false,
|
|
1335
|
+
revealedPaths: {}
|
|
1336
|
+
};
|
|
1337
|
+
/**
|
|
1338
|
+
* Why this copy cannot be submitted yet, as a locale key, or undefined when
|
|
1339
|
+
* it can. Client-side only: the host re-checks the id and its answer is what
|
|
1340
|
+
* the dialog reports on failure.
|
|
1341
|
+
* @param draft - the open copy dialog.
|
|
1342
|
+
* @param rows - the roster, for the collision check.
|
|
1343
|
+
* @returns the blocking reason's locale key, or undefined when submittable.
|
|
1344
|
+
*/
|
|
1345
|
+
function draftBlocker(draft, rows) {
|
|
1346
|
+
if (draft.id === "") return "idRequired";
|
|
1347
|
+
if (!PRESET_ID.test(draft.id)) return "idInvalid";
|
|
1348
|
+
if (rows.some((row) => row.id === draft.id)) return "idTaken";
|
|
1349
|
+
}
|
|
1350
|
+
/** Reads the roster and drives the copy dialog, viewer, and location reveals. */
|
|
1351
|
+
var AgentPresetSectionController = class {
|
|
1352
|
+
ctx;
|
|
1353
|
+
rosterChanged;
|
|
1354
|
+
/** Page snapshot the renderer subscribes to. */
|
|
1355
|
+
store = (0, _qilin_agent_client_store.createSnapshotStore)(INITIAL$1);
|
|
1356
|
+
/** The one roster load whose completion current callers await. */
|
|
1357
|
+
loadFlight;
|
|
1358
|
+
reloadRequested = false;
|
|
1359
|
+
constructor(ctx, rosterChanged = () => {}) {
|
|
1360
|
+
this.ctx = ctx;
|
|
1361
|
+
this.rosterChanged = rosterChanged;
|
|
1362
|
+
}
|
|
1363
|
+
set(patch) {
|
|
1364
|
+
this.store.set({
|
|
1365
|
+
...this.store.getSnapshot(),
|
|
1366
|
+
...patch
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
/** Read back and reflect the Host-effective default after a policy write. */
|
|
1370
|
+
async confirmEffectiveDefault(showPicker) {
|
|
1371
|
+
await this.load();
|
|
1372
|
+
if (this.store.getSnapshot().status === "error") await this.load();
|
|
1373
|
+
const state = this.store.getSnapshot();
|
|
1374
|
+
if (state.status !== "ready" || state.showPicker !== showPicker) return void 0;
|
|
1375
|
+
return state.rows.find((row) => row.isDefault)?.id;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Show or hide new-session preset selection without changing the saved
|
|
1379
|
+
* default. The Host roster resolves that saved default while selection is
|
|
1380
|
+
* shown and the deployment default while it is hidden.
|
|
1381
|
+
* @param showPicker - whether the new-session picker should be exposed.
|
|
1382
|
+
* @param syncBlankSession - optional current-blank-task sync kept inside the saving state.
|
|
1383
|
+
* @returns once the Host state and optional blank-task sync settle.
|
|
1384
|
+
*/
|
|
1385
|
+
async setPickerVisible(showPicker, syncBlankSession) {
|
|
1386
|
+
const state = this.store.getSnapshot();
|
|
1387
|
+
if (state.status !== "ready" || state.policySaving || state.showPicker === showPicker) return;
|
|
1388
|
+
this.set({
|
|
1389
|
+
policySaving: true,
|
|
1390
|
+
error: null
|
|
1391
|
+
});
|
|
1392
|
+
try {
|
|
1393
|
+
const failure = await writeModeSelectionEnabled(this.ctx, showPicker);
|
|
1394
|
+
if (failure !== void 0) {
|
|
1395
|
+
await this.load();
|
|
1396
|
+
this.set({ error: failure });
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const effectiveDefault = await this.confirmEffectiveDefault(showPicker);
|
|
1400
|
+
if (effectiveDefault === void 0) return;
|
|
1401
|
+
const syncFailure = await syncBlankSession?.(effectiveDefault);
|
|
1402
|
+
if (syncFailure !== void 0) this.set({ error: syncFailure });
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
await this.load();
|
|
1405
|
+
this.set({ error: errorMessage(error) });
|
|
1406
|
+
} finally {
|
|
1407
|
+
this.set({ policySaving: false });
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
patchCopy(patch) {
|
|
1411
|
+
const { copy } = this.store.getSnapshot();
|
|
1412
|
+
if (copy === null) return;
|
|
1413
|
+
this.set({ copy: {
|
|
1414
|
+
...copy,
|
|
1415
|
+
...patch
|
|
1416
|
+
} });
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Load the roster. An empty roster means the deployment composes no
|
|
1420
|
+
* presets, which is a valid deployment rather than a failure — the section
|
|
1421
|
+
* reports `unavailable` and renders nothing.
|
|
1422
|
+
* @returns once the snapshot reflects the host.
|
|
1423
|
+
*/
|
|
1424
|
+
async load() {
|
|
1425
|
+
this.reloadRequested = true;
|
|
1426
|
+
this.loadFlight ??= this.drainLoads();
|
|
1427
|
+
await this.loadFlight;
|
|
1428
|
+
}
|
|
1429
|
+
/** Coalesce invalidations without losing changes received during a read. */
|
|
1430
|
+
async drainLoads() {
|
|
1431
|
+
try {
|
|
1432
|
+
do
|
|
1433
|
+
await this.loadOnce();
|
|
1434
|
+
while (this.reloadRequested);
|
|
1435
|
+
} finally {
|
|
1436
|
+
this.loadFlight = void 0;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
/** Perform the section's one owned roster read. */
|
|
1440
|
+
async loadOnce() {
|
|
1441
|
+
this.reloadRequested = false;
|
|
1442
|
+
const opener = this.ctx.remote.settings.canOpenAgentPresetDirectory();
|
|
1443
|
+
const roster = await beginRosterRead(this.ctx, this.store);
|
|
1444
|
+
const described = await opener;
|
|
1445
|
+
if (roster === void 0) return;
|
|
1446
|
+
const { presets, authorable, modeSelectionEnabled: showPicker } = roster;
|
|
1447
|
+
const hasDocument = described.ok && described.value;
|
|
1448
|
+
if (presets.length === 0) {
|
|
1449
|
+
this.set({
|
|
1450
|
+
status: "unavailable",
|
|
1451
|
+
rows: [],
|
|
1452
|
+
authorable,
|
|
1453
|
+
hasDocument,
|
|
1454
|
+
showPicker,
|
|
1455
|
+
copy: null,
|
|
1456
|
+
view: null
|
|
1457
|
+
});
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
const revealed = this.store.getSnapshot().revealedPaths;
|
|
1461
|
+
const kept = Object.fromEntries(Object.entries(revealed).filter(([id]) => presets.some((preset) => preset.id === id)));
|
|
1462
|
+
this.set({
|
|
1463
|
+
status: "ready",
|
|
1464
|
+
error: null,
|
|
1465
|
+
authorable,
|
|
1466
|
+
hasDocument,
|
|
1467
|
+
showPicker,
|
|
1468
|
+
rows: presets.map((preset) => ({ ...preset })),
|
|
1469
|
+
revealedPaths: kept
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Open one shipped preset's composition in the read-only viewer.
|
|
1474
|
+
* @param id - the preset to view.
|
|
1475
|
+
* @returns once the composition loaded or the failure is on the page.
|
|
1476
|
+
*/
|
|
1477
|
+
async view(id) {
|
|
1478
|
+
this.set({ error: null });
|
|
1479
|
+
const result = await this.ctx.remote.agentPresets.read(id);
|
|
1480
|
+
if (!result.ok) {
|
|
1481
|
+
this.set({ error: result.error.message });
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const { name, content } = result.value;
|
|
1485
|
+
this.set({ view: {
|
|
1486
|
+
id,
|
|
1487
|
+
title: name ?? id,
|
|
1488
|
+
content
|
|
1489
|
+
} });
|
|
1490
|
+
}
|
|
1491
|
+
/** Close the read-only viewer. */
|
|
1492
|
+
closeView() {
|
|
1493
|
+
this.set({ view: null });
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Open the copy dialog over one preset.
|
|
1497
|
+
* @param from - the preset the copy will start from.
|
|
1498
|
+
*/
|
|
1499
|
+
beginCopy(from) {
|
|
1500
|
+
const row = this.store.getSnapshot().rows.find((candidate) => candidate.id === from);
|
|
1501
|
+
this.set({
|
|
1502
|
+
error: null,
|
|
1503
|
+
copy: {
|
|
1504
|
+
from,
|
|
1505
|
+
fromTitle: row?.name ?? from,
|
|
1506
|
+
id: "",
|
|
1507
|
+
name: "",
|
|
1508
|
+
saving: false,
|
|
1509
|
+
error: null
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
/** Close the copy dialog, discarding whatever was typed. */
|
|
1514
|
+
cancelCopy() {
|
|
1515
|
+
this.set({ copy: null });
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Name the preset the copy creates.
|
|
1519
|
+
* @param id - the id typed into the dialog.
|
|
1520
|
+
*/
|
|
1521
|
+
setCopyId(id) {
|
|
1522
|
+
this.patchCopy({
|
|
1523
|
+
id,
|
|
1524
|
+
error: null
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Name the copy's display name.
|
|
1529
|
+
* @param name - the display name typed into the dialog.
|
|
1530
|
+
*/
|
|
1531
|
+
setCopyName(name) {
|
|
1532
|
+
this.patchCopy({
|
|
1533
|
+
name,
|
|
1534
|
+
error: null
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Submit the copy, re-read the roster, then take the user to the new
|
|
1539
|
+
* preset's files — the directory opens where the host has a desktop, and
|
|
1540
|
+
* its path appears on the new row where it does not.
|
|
1541
|
+
* @returns once the copy settled and the page reflects it.
|
|
1542
|
+
*/
|
|
1543
|
+
async confirmCopy() {
|
|
1544
|
+
const draft = this.store.getSnapshot().copy;
|
|
1545
|
+
if (draft === null || draft.saving) return;
|
|
1546
|
+
if (draftBlocker(draft, this.store.getSnapshot().rows) !== void 0) return;
|
|
1547
|
+
this.patchCopy({
|
|
1548
|
+
saving: true,
|
|
1549
|
+
error: null
|
|
1550
|
+
});
|
|
1551
|
+
const name = draft.name.trim();
|
|
1552
|
+
const result = await this.ctx.remote.agentPresets.copy(draft.from, draft.id, name === "" ? void 0 : name);
|
|
1553
|
+
if (!result.ok) {
|
|
1554
|
+
this.patchCopy({
|
|
1555
|
+
saving: false,
|
|
1556
|
+
error: result.error.message
|
|
1557
|
+
});
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
this.set({ copy: null });
|
|
1561
|
+
await this.load();
|
|
1562
|
+
this.rosterChanged();
|
|
1563
|
+
await this.openLocation(draft.id);
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Open one preset's directory on the host desktop, or reveal its path on
|
|
1567
|
+
* the row where the deployment has no opener to hand it to.
|
|
1568
|
+
* @param id - the preset whose files the user wants.
|
|
1569
|
+
* @returns once the host answered and the page reflects it.
|
|
1570
|
+
*/
|
|
1571
|
+
async openLocation(id) {
|
|
1572
|
+
const result = await this.ctx.remote.settings.openAgentPresetDirectory(id);
|
|
1573
|
+
if (!result.ok) {
|
|
1574
|
+
this.set({ error: result.error.message });
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
if (result.value.opened) return;
|
|
1578
|
+
const { path } = result.value;
|
|
1579
|
+
this.set({ revealedPaths: {
|
|
1580
|
+
...this.store.getSnapshot().revealedPaths,
|
|
1581
|
+
[id]: path
|
|
1582
|
+
} });
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Ask for confirmation before deleting one preset.
|
|
1586
|
+
* @param id - the preset to delete, or null to dismiss the confirmation.
|
|
1587
|
+
*/
|
|
1588
|
+
confirmDelete(id) {
|
|
1589
|
+
if (this.store.getSnapshot().deleting) return;
|
|
1590
|
+
this.set({ pendingDelete: id });
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Delete the preset awaiting confirmation, then re-read the roster.
|
|
1594
|
+
*
|
|
1595
|
+
* A session already composed from it keeps running: its composition was
|
|
1596
|
+
* mounted at creation and nothing re-reads the file.
|
|
1597
|
+
* @returns once the delete settled and the page reflects it.
|
|
1598
|
+
*/
|
|
1599
|
+
async remove() {
|
|
1600
|
+
const { pendingDelete, deleting } = this.store.getSnapshot();
|
|
1601
|
+
if (pendingDelete === null || deleting) return;
|
|
1602
|
+
this.set({
|
|
1603
|
+
deleting: true,
|
|
1604
|
+
error: null
|
|
1605
|
+
});
|
|
1606
|
+
const result = await this.ctx.remote.agentPresets.deletePreset(pendingDelete);
|
|
1607
|
+
if (!result.ok) {
|
|
1608
|
+
this.set({
|
|
1609
|
+
deleting: false,
|
|
1610
|
+
pendingDelete: null,
|
|
1611
|
+
error: result.error.message
|
|
1612
|
+
});
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
this.set({
|
|
1616
|
+
deleting: false,
|
|
1617
|
+
pendingDelete: null
|
|
1618
|
+
});
|
|
1619
|
+
await this.load();
|
|
1620
|
+
this.rosterChanged();
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Make one preset the default for sessions created later. Running sessions
|
|
1624
|
+
* keep the composition they began with, so this never disturbs work.
|
|
1625
|
+
* @param id - the preset to make default.
|
|
1626
|
+
* @param syncBlankSession - optional current-blank-task sync kept inside the policy lock.
|
|
1627
|
+
* @returns once the write and optional blank-task sync settle.
|
|
1628
|
+
*/
|
|
1629
|
+
async makeDefault(id, syncBlankSession) {
|
|
1630
|
+
const state = this.store.getSnapshot();
|
|
1631
|
+
if (!state.showPicker || state.policySaving) return;
|
|
1632
|
+
this.set({
|
|
1633
|
+
policySaving: true,
|
|
1634
|
+
error: null
|
|
1635
|
+
});
|
|
1636
|
+
try {
|
|
1637
|
+
const failure = await writeDefaultPreset(this.ctx, id);
|
|
1638
|
+
if (failure !== void 0) {
|
|
1639
|
+
this.set({ error: failure });
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
const effectiveDefault = await this.confirmEffectiveDefault(true);
|
|
1643
|
+
if (effectiveDefault === void 0) return;
|
|
1644
|
+
const syncFailure = await syncBlankSession?.(effectiveDefault);
|
|
1645
|
+
if (syncFailure !== void 0) this.set({ error: syncFailure });
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
this.set({ error: errorMessage(error) });
|
|
1648
|
+
} finally {
|
|
1649
|
+
this.set({ policySaving: false });
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
//#endregion
|
|
1654
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css.mjs
|
|
1655
|
+
const css = ".hbsEeq_section{color:var(--qilin-alias-label-primary);flex-direction:column;gap:12px;display:flex}.hbsEeq_title{margin:0;font-size:18px;font-weight:600}.hbsEeq_intro{color:var(--qilin-alias-label-tertiary);margin:0;font-size:13px}.hbsEeq_pickerPreference{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-bg-module-platform);border-radius:14px;justify-content:space-between;align-items:flex-start;gap:20px;padding:14px 16px;display:flex}.hbsEeq_pickerPreferenceCopy{gap:4px;min-width:0;display:grid}.hbsEeq_pickerPreferenceTitleRow{align-items:center;gap:6px;min-width:0;display:flex}.hbsEeq_pickerPreferenceTitle{font-size:14px;font-weight:600;line-height:1.5}.hbsEeq_pickerPreferenceDescription{color:var(--qilin-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.hbsEeq_group{flex-direction:column;gap:10px;display:flex}.hbsEeq_group+.hbsEeq_group{margin-top:20px}.hbsEeq_groupHead{letter-spacing:.06em;text-transform:uppercase;color:var(--qilin-alias-label-tertiary);margin:0;font-size:12px;font-weight:600}.hbsEeq_cards{grid-template-columns:repeat(auto-fill,minmax(268px,1fr));grid-auto-rows:1fr;gap:12px;margin:0;padding:0;list-style:none;display:grid}.hbsEeq_card{border:.5px solid var(--qilin-alias-border-l4);background:0 0;border-radius:20px;flex-direction:column;transition:border-color .16s,background .16s;display:flex}.hbsEeq_card:hover:not(.hbsEeq_cardActive):not(.hbsEeq_cardSelectionDisabled){background:var(--qilin-alias-interactive-bg-hover)}.hbsEeq_cardActive{background:var(--qilin-alias-bg-module-platform);border-color:var(--qilin-static-neutral-bluish-400)}.hbsEeq_cardSelectionDisabled{background:var(--qilin-alias-bg-module-platform)}.hbsEeq_cardSelectionDisabled .hbsEeq_cardMain{opacity:.48}.hbsEeq_cardBroken,.hbsEeq_cardBroken:hover{border-color:var(--qilin-alias-state-error-primary)}.hbsEeq_brokenBadge{corner-shape:round;white-space:nowrap;background:var(--qilin-alias-state-error-primary);color:var(--qilin-alias-bg-layer-3);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.hbsEeq_brokenTip{z-index:1;background:var(--qilin-alias-label-primary);width:max-content;max-width:100%;color:var(--qilin-alias-bg-layer-3);text-align:left;white-space:pre-line;overflow-wrap:anywhere;opacity:0;pointer-events:none;border-radius:6px;padding:6px 8px;font-size:11px;font-weight:400;line-height:1.5;transition:opacity .12s;position:absolute;top:calc(100% + 6px);left:0}.hbsEeq_brokenBadge:hover .hbsEeq_brokenTip,.hbsEeq_cardMain:focus-visible .hbsEeq_brokenTip{opacity:1}.hbsEeq_cardMain[aria-disabled=true]{cursor:default}.hbsEeq_cardBrokenReason{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.hbsEeq_cardMain{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px 12px 0 0;flex-direction:column;flex:1;gap:8px;padding:14px 16px 12px;display:flex}.hbsEeq_cardMain:disabled{cursor:default}.hbsEeq_cardMain:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:-2px}.hbsEeq_cardHead{align-items:center;gap:8px;display:flex;position:relative}.hbsEeq_cardName{font-size:15px;font-weight:600;line-height:1.4}.hbsEeq_inUse{margin-left:auto}.hbsEeq_cardDesc{color:var(--qilin-alias-label-secondary);-webkit-line-clamp:4;overflow-wrap:anywhere;-webkit-box-orient:vertical;min-height:42px;font-size:13px;line-height:1.55;display:-webkit-box;overflow:hidden}.hbsEeq_cardId{font-family:var(--qilin-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--qilin-alias-label-tertiary);margin-top:auto;font-size:11px}.hbsEeq_cardFoot{border-top:.5px solid var(--qilin-alias-border-l2);justify-content:flex-end;gap:2px;padding:6px 10px;display:flex}.hbsEeq_iconButton{appearance:none;color:var(--qilin-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:7px;align-items:center;padding:6px;display:inline-flex;position:relative}.hbsEeq_iconButton:disabled{opacity:.4;cursor:default}.hbsEeq_iconButton:hover:not(:disabled){background:var(--qilin-alias-bg-layer-1);color:var(--qilin-alias-label-primary)}.hbsEeq_iconButton:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:-1px}.hbsEeq_iconButton:after{content:attr(data-tip);background:var(--qilin-alias-label-primary);color:var(--qilin-alias-bg-layer-3);white-space:nowrap;opacity:0;pointer-events:none;border-radius:6px;padding:3px 8px;font-size:11px;line-height:17px;transition:opacity .12s;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%)}.hbsEeq_iconButton:hover:after,.hbsEeq_iconButton:focus-visible:after{opacity:1}.hbsEeq_iconDanger:hover:not(:disabled){background:var(--qilin-alias-interactive-bg-hover-danger);color:var(--qilin-alias-state-error-primary)}.hbsEeq_revealedPath{color:var(--qilin-alias-label-tertiary);align-items:baseline;gap:6px;margin:0;padding:6px 16px 10px;font-size:11px;display:flex}.hbsEeq_revealedPath code{font-family:var(--qilin-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--qilin-alias-label-secondary);user-select:all;overflow-wrap:anywhere}.hbsEeq_revealedPathLabel{white-space:nowrap}.hbsEeq_secondaryButton{color:var(--qilin-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 8px;font-size:12.5px}.hbsEeq_secondaryButton:hover:not(:disabled){background:var(--qilin-alias-bg-layer-1)}.hbsEeq_secondaryButton:disabled{opacity:.5;cursor:default}.hbsEeq_field{flex-direction:column;gap:6px;display:flex}.hbsEeq_fieldLabel{color:var(--qilin-alias-label-secondary);font-size:12px;font-weight:500}.hbsEeq_input{box-sizing:border-box;border:.5px solid var(--qilin-alias-border-l4);font:inherit;background:var(--qilin-alias-bg-layer-1);color:var(--qilin-alias-label-primary);border-radius:10px;padding:9px 12px;font-size:13px}.hbsEeq_input:focus{border-color:var(--qilin-alias-brand-primary);outline:none}.hbsEeq_input::placeholder{color:var(--qilin-alias-label-dimmed)}.hbsEeq_dialog{width:min(560px,100%)}.hbsEeq_dialogFields{flex-direction:column;gap:12px;display:flex}.hbsEeq_viewerCode{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-bg-layer-2);max-height:min(52vh,480px);color:var(--qilin-alias-label-secondary);font-family:var(--qilin-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);white-space:pre;tab-size:2;--qilin-scrollbar-thumb:var(--qilin-alias-scrollbar-bg-l2);--qilin-scrollbar-thumb-hover:var(--qilin-alias-scrollbar-hover-l2);border-radius:10px;margin:0;padding:12px;font-size:12.5px;line-height:1.5;overflow:auto}.hbsEeq_error{color:var(--qilin-alias-state-error-primary);margin:0;font-size:12px}.hbsEeq_deleteDialog{width:min(480px,100%)}.hbsEeq_deleteConfirm:not(:disabled){border-color:var(--qilin-alias-state-error-primary);color:var(--qilin-alias-state-error-primary)}.hbsEeq_deleteConfirm:hover:not(:disabled){background:var(--qilin-alias-interactive-bg-hover-danger)}.hbsEeq_creatorButton{box-sizing:border-box;border:1px dashed var(--qilin-alias-border-l3);height:44px;font:inherit;color:var(--qilin-alias-label-primary);cursor:pointer;background:0 0;border-radius:20px;justify-content:center;align-self:stretch;align-items:center;gap:6px;font-size:14px;line-height:22px;display:flex}.hbsEeq_creatorButton:hover:not(:disabled){background:var(--qilin-alias-interactive-bg-hover)}.hbsEeq_creatorButton:disabled{opacity:.4;cursor:default}";
|
|
1656
|
+
const tagId = "@qilin-agent/client-ui-agent-preset/AgentPresetSection.module.css";
|
|
1657
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1658
|
+
const tag = document.createElement("style");
|
|
1659
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-agent-preset";
|
|
1660
|
+
tag.dataset.pluginCss = tagId;
|
|
1661
|
+
tag.textContent = css;
|
|
1662
|
+
document.head.appendChild(tag);
|
|
1663
|
+
}
|
|
1664
|
+
var AgentPresetSection_module_css_default = {
|
|
1665
|
+
"brokenBadge": "hbsEeq_brokenBadge",
|
|
1666
|
+
"brokenTip": "hbsEeq_brokenTip",
|
|
1667
|
+
"card": "hbsEeq_card",
|
|
1668
|
+
"cardActive": "hbsEeq_cardActive",
|
|
1669
|
+
"cardBroken": "hbsEeq_cardBroken",
|
|
1670
|
+
"cardBrokenReason": "hbsEeq_cardBrokenReason",
|
|
1671
|
+
"cardDesc": "hbsEeq_cardDesc",
|
|
1672
|
+
"cardFoot": "hbsEeq_cardFoot",
|
|
1673
|
+
"cardHead": "hbsEeq_cardHead",
|
|
1674
|
+
"cardId": "hbsEeq_cardId",
|
|
1675
|
+
"cardMain": "hbsEeq_cardMain",
|
|
1676
|
+
"cardName": "hbsEeq_cardName",
|
|
1677
|
+
"cardSelectionDisabled": "hbsEeq_cardSelectionDisabled",
|
|
1678
|
+
"cards": "hbsEeq_cards",
|
|
1679
|
+
"creatorButton": "hbsEeq_creatorButton",
|
|
1680
|
+
"deleteConfirm": "hbsEeq_deleteConfirm",
|
|
1681
|
+
"deleteDialog": "hbsEeq_deleteDialog",
|
|
1682
|
+
"dialog": "hbsEeq_dialog",
|
|
1683
|
+
"dialogFields": "hbsEeq_dialogFields",
|
|
1684
|
+
"error": "hbsEeq_error",
|
|
1685
|
+
"field": "hbsEeq_field",
|
|
1686
|
+
"fieldLabel": "hbsEeq_fieldLabel",
|
|
1687
|
+
"group": "hbsEeq_group",
|
|
1688
|
+
"groupHead": "hbsEeq_groupHead",
|
|
1689
|
+
"iconButton": "hbsEeq_iconButton",
|
|
1690
|
+
"iconDanger": "hbsEeq_iconDanger",
|
|
1691
|
+
"inUse": "hbsEeq_inUse",
|
|
1692
|
+
"input": "hbsEeq_input",
|
|
1693
|
+
"intro": "hbsEeq_intro",
|
|
1694
|
+
"pickerPreference": "hbsEeq_pickerPreference",
|
|
1695
|
+
"pickerPreferenceCopy": "hbsEeq_pickerPreferenceCopy",
|
|
1696
|
+
"pickerPreferenceDescription": "hbsEeq_pickerPreferenceDescription",
|
|
1697
|
+
"pickerPreferenceTitle": "hbsEeq_pickerPreferenceTitle",
|
|
1698
|
+
"pickerPreferenceTitleRow": "hbsEeq_pickerPreferenceTitleRow",
|
|
1699
|
+
"revealedPath": "hbsEeq_revealedPath",
|
|
1700
|
+
"revealedPathLabel": "hbsEeq_revealedPathLabel",
|
|
1701
|
+
"secondaryButton": "hbsEeq_secondaryButton",
|
|
1702
|
+
"section": "hbsEeq_section",
|
|
1703
|
+
"title": "hbsEeq_title",
|
|
1704
|
+
"viewerCode": "hbsEeq_viewerCode"
|
|
1705
|
+
};
|
|
1706
|
+
//#endregion
|
|
1707
|
+
//#region lib/types/client/AgentPresetSection.js
|
|
1708
|
+
/**
|
|
1709
|
+
* Agent-presets settings section: the roster as cards, a copy dialog as the
|
|
1710
|
+
* only way a preset is created, and a read-only viewer over the shipped
|
|
1711
|
+
* compositions.
|
|
1712
|
+
*
|
|
1713
|
+
* The browser edits no composition text — a shipped preset opens read-only to
|
|
1714
|
+
* be READ (it is the known-good composition a copy starts from), and a custom
|
|
1715
|
+
* preset is edited in its own files, which is what the location action leads
|
|
1716
|
+
* to. Deleting a preset leaves running sessions alone: a composition is
|
|
1717
|
+
* mounted once at session creation and nothing re-reads the file.
|
|
1718
|
+
*/
|
|
1719
|
+
function CopyDialog({ state, t, actions }) {
|
|
1720
|
+
const draft = state.copy;
|
|
1721
|
+
const blocker = draft === null ? void 0 : draftBlocker(draft, state.rows);
|
|
1722
|
+
const message = draft === null ? null : draft.error ?? (blocker === void 0 ? null : t(blocker));
|
|
1723
|
+
const source = draft === null ? void 0 : state.rows.find((row) => row.id === draft.from);
|
|
1724
|
+
const sourceTitle = source === void 0 ? draft?.fromTitle : presetDisplayText(source, t).name;
|
|
1725
|
+
return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Modal, {
|
|
1726
|
+
open: draft !== null,
|
|
1727
|
+
onClose: () => {
|
|
1728
|
+
actions.cancelCopy();
|
|
1729
|
+
},
|
|
1730
|
+
title: draft === null ? t("copyTitle") : `${t("copyTitle")} · ${t("copyOf")} ${sourceTitle}`,
|
|
1731
|
+
closeLabel: t("close"),
|
|
1732
|
+
description: t("copyIntro"),
|
|
1733
|
+
className: AgentPresetSection_module_css_default.dialog,
|
|
1734
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Button, {
|
|
1735
|
+
variant: "outline",
|
|
1736
|
+
disabled: draft?.saving === true,
|
|
1737
|
+
onClick: () => {
|
|
1738
|
+
actions.cancelCopy();
|
|
1739
|
+
},
|
|
1740
|
+
children: t("cancel")
|
|
1741
|
+
}), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Button, {
|
|
1742
|
+
disabled: draft === null || draft.saving || blocker !== void 0,
|
|
1743
|
+
onClick: () => {
|
|
1744
|
+
actions.confirmCopy();
|
|
1745
|
+
},
|
|
1746
|
+
children: draft?.saving === true ? t("creating") : t("create")
|
|
1747
|
+
})] }),
|
|
1748
|
+
children: draft === null ? null : (0, react_jsx_runtime.jsxs)("div", {
|
|
1749
|
+
className: AgentPresetSection_module_css_default.dialogFields,
|
|
1750
|
+
children: [
|
|
1751
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
1752
|
+
className: AgentPresetSection_module_css_default.field,
|
|
1753
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1754
|
+
className: AgentPresetSection_module_css_default.fieldLabel,
|
|
1755
|
+
children: t("presetId")
|
|
1756
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
1757
|
+
className: AgentPresetSection_module_css_default.input,
|
|
1758
|
+
value: draft.id,
|
|
1759
|
+
autoFocus: true,
|
|
1760
|
+
spellCheck: false,
|
|
1761
|
+
placeholder: t("presetIdPlaceholder"),
|
|
1762
|
+
onChange: (event) => {
|
|
1763
|
+
actions.setCopyId(event.target.value);
|
|
1764
|
+
}
|
|
1765
|
+
})]
|
|
1766
|
+
}),
|
|
1767
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
1768
|
+
className: AgentPresetSection_module_css_default.field,
|
|
1769
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1770
|
+
className: AgentPresetSection_module_css_default.fieldLabel,
|
|
1771
|
+
children: t("displayName")
|
|
1772
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
1773
|
+
className: AgentPresetSection_module_css_default.input,
|
|
1774
|
+
value: draft.name,
|
|
1775
|
+
spellCheck: false,
|
|
1776
|
+
placeholder: t("displayNamePlaceholder"),
|
|
1777
|
+
onChange: (event) => {
|
|
1778
|
+
actions.setCopyName(event.target.value);
|
|
1779
|
+
}
|
|
1780
|
+
})]
|
|
1781
|
+
}),
|
|
1782
|
+
message === null ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1783
|
+
className: AgentPresetSection_module_css_default.error,
|
|
1784
|
+
role: "alert",
|
|
1785
|
+
children: message
|
|
1786
|
+
})
|
|
1787
|
+
]
|
|
1788
|
+
})
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Render one card's description, clamped by CSS and offered in full on hover.
|
|
1793
|
+
* The tooltip is attached only while the text is actually cut off, so a short
|
|
1794
|
+
* description does not answer a hover with a bubble repeating the card.
|
|
1795
|
+
* @param props.text - the description as rendered, already localized.
|
|
1796
|
+
* @returns the description element, tooltip-anchored while it overflows.
|
|
1797
|
+
*/
|
|
1798
|
+
function CardDescription({ text }) {
|
|
1799
|
+
const ref = (0, react.useRef)(null);
|
|
1800
|
+
const [truncated, setTruncated] = (0, react.useState)(false);
|
|
1801
|
+
(0, react.useLayoutEffect)(() => {
|
|
1802
|
+
const el = ref.current;
|
|
1803
|
+
/* v8 ignore next -- the ref is attached before layout effects run. */
|
|
1804
|
+
if (el === null) return;
|
|
1805
|
+
const measure = () => {
|
|
1806
|
+
setTruncated(el.scrollHeight > el.clientHeight);
|
|
1807
|
+
};
|
|
1808
|
+
measure();
|
|
1809
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
1810
|
+
const observer = new ResizeObserver(measure);
|
|
1811
|
+
observer.observe(el);
|
|
1812
|
+
return () => {
|
|
1813
|
+
observer.disconnect();
|
|
1814
|
+
};
|
|
1815
|
+
}, [text]);
|
|
1816
|
+
return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
|
|
1817
|
+
label: text,
|
|
1818
|
+
side: "bottom",
|
|
1819
|
+
delayMs: 400,
|
|
1820
|
+
disabled: !truncated,
|
|
1821
|
+
maxWidth: 360,
|
|
1822
|
+
children: (0, react_jsx_runtime.jsx)("span", {
|
|
1823
|
+
ref,
|
|
1824
|
+
className: AgentPresetSection_module_css_default.cardDesc,
|
|
1825
|
+
title: "",
|
|
1826
|
+
children: text
|
|
1827
|
+
})
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Render the Agent presets section content column.
|
|
1832
|
+
* @param props - composed slot props.
|
|
1833
|
+
* @returns the section, or null when the deployment composes no presets.
|
|
1834
|
+
*/
|
|
1835
|
+
function AgentPresetSection(props) {
|
|
1836
|
+
const { useAgentPresetSection, t, load } = props;
|
|
1837
|
+
const state = useAgentPresetSection((snapshot) => snapshot);
|
|
1838
|
+
const viewedId = state.view?.id;
|
|
1839
|
+
const viewedRow = viewedId === void 0 ? void 0 : state.rows.find((row) => row.id === viewedId);
|
|
1840
|
+
const viewedTitle = state.view === null ? "" : viewedRow === void 0 ? state.view.title : presetDisplayText(viewedRow, t).name;
|
|
1841
|
+
(0, react.useEffect)(() => {
|
|
1842
|
+
load();
|
|
1843
|
+
}, [load]);
|
|
1844
|
+
if (state.status === "unavailable") return null;
|
|
1845
|
+
if (state.status === "error") {
|
|
1846
|
+
/* v8 ignore next -- an error status always carries text; the fallback satisfies the nullable type */
|
|
1847
|
+
const detail = state.error ?? "";
|
|
1848
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1849
|
+
className: AgentPresetSection_module_css_default.section,
|
|
1850
|
+
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1851
|
+
className: AgentPresetSection_module_css_default.error,
|
|
1852
|
+
role: "alert",
|
|
1853
|
+
children: `${t("error")} ${detail}`
|
|
1854
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1855
|
+
type: "button",
|
|
1856
|
+
className: AgentPresetSection_module_css_default.secondaryButton,
|
|
1857
|
+
onClick: () => {
|
|
1858
|
+
load();
|
|
1859
|
+
},
|
|
1860
|
+
children: t("retry")
|
|
1861
|
+
})]
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
const creatorButton = props.startCreatorDraft !== void 0 && state.rows.some((row) => row.id === "ptc") ? (0, react_jsx_runtime.jsxs)("button", {
|
|
1865
|
+
type: "button",
|
|
1866
|
+
className: AgentPresetSection_module_css_default.creatorButton,
|
|
1867
|
+
disabled: !state.authorable || !state.showPicker || state.policySaving,
|
|
1868
|
+
title: !state.showPicker ? t("enablePickerToCreate") : state.authorable ? void 0 : t("duplicateUnavailable"),
|
|
1869
|
+
onClick: () => {
|
|
1870
|
+
props.startCreatorDraft?.();
|
|
1871
|
+
props.close();
|
|
1872
|
+
},
|
|
1873
|
+
children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlusOutline16, { size: 14 }), t("creatorDraft")]
|
|
1874
|
+
}) : null;
|
|
1875
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1876
|
+
className: AgentPresetSection_module_css_default.section,
|
|
1877
|
+
children: [
|
|
1878
|
+
(0, react_jsx_runtime.jsx)("h2", {
|
|
1879
|
+
className: AgentPresetSection_module_css_default.title,
|
|
1880
|
+
children: t("nav")
|
|
1881
|
+
}),
|
|
1882
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
1883
|
+
className: AgentPresetSection_module_css_default.intro,
|
|
1884
|
+
children: t("sectionIntro")
|
|
1885
|
+
}),
|
|
1886
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1887
|
+
className: AgentPresetSection_module_css_default.pickerPreference,
|
|
1888
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1889
|
+
className: AgentPresetSection_module_css_default.pickerPreferenceCopy,
|
|
1890
|
+
children: [(0, react_jsx_runtime.jsxs)("span", {
|
|
1891
|
+
className: AgentPresetSection_module_css_default.pickerPreferenceTitleRow,
|
|
1892
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1893
|
+
className: AgentPresetSection_module_css_default.pickerPreferenceTitle,
|
|
1894
|
+
children: t("showPicker")
|
|
1895
|
+
}), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tag, { children: t("showPickerBeta") })]
|
|
1896
|
+
}), (0, react_jsx_runtime.jsx)("p", {
|
|
1897
|
+
className: AgentPresetSection_module_css_default.pickerPreferenceDescription,
|
|
1898
|
+
children: t("showPickerDescription")
|
|
1899
|
+
})]
|
|
1900
|
+
}), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Switch, {
|
|
1901
|
+
checked: state.showPicker,
|
|
1902
|
+
label: t("showPicker"),
|
|
1903
|
+
disabled: state.status !== "ready" || state.policySaving,
|
|
1904
|
+
onChange: (next) => {
|
|
1905
|
+
props.setPickerVisible(next);
|
|
1906
|
+
}
|
|
1907
|
+
})]
|
|
1908
|
+
}),
|
|
1909
|
+
state.error === null ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1910
|
+
className: AgentPresetSection_module_css_default.error,
|
|
1911
|
+
role: "alert",
|
|
1912
|
+
children: state.error
|
|
1913
|
+
}),
|
|
1914
|
+
[["system", t("builtInGroup")], ["user", t("customGroup")]].map(([trust, heading]) => {
|
|
1915
|
+
const group = state.rows.filter((row) => row.trust === trust).map((row) => ({
|
|
1916
|
+
row,
|
|
1917
|
+
text: presetDisplayText(row, t),
|
|
1918
|
+
selectionAction: row.broken !== void 0 ? t("brokenBadge") : row.isDefault ? t(state.showPicker ? "inUse" : "selectionOffDefault") : t(state.showPicker ? "setDefault" : "enablePickerToSetDefault")
|
|
1919
|
+
}));
|
|
1920
|
+
const tail = trust === "user" ? creatorButton : null;
|
|
1921
|
+
if (group.length === 0 && tail === null) return null;
|
|
1922
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
1923
|
+
className: AgentPresetSection_module_css_default.group,
|
|
1924
|
+
children: [
|
|
1925
|
+
(0, react_jsx_runtime.jsx)("h3", {
|
|
1926
|
+
className: AgentPresetSection_module_css_default.groupHead,
|
|
1927
|
+
children: heading
|
|
1928
|
+
}),
|
|
1929
|
+
group.length === 0 ? null : (0, react_jsx_runtime.jsx)("ul", {
|
|
1930
|
+
className: AgentPresetSection_module_css_default.cards,
|
|
1931
|
+
children: group.map(({ row, text, selectionAction }) => (0, react_jsx_runtime.jsxs)("li", {
|
|
1932
|
+
className: [
|
|
1933
|
+
AgentPresetSection_module_css_default.card,
|
|
1934
|
+
row.broken !== void 0 ? AgentPresetSection_module_css_default.cardBroken : void 0,
|
|
1935
|
+
row.isDefault ? AgentPresetSection_module_css_default.cardActive : void 0,
|
|
1936
|
+
!state.showPicker && row.broken === void 0 && !row.isDefault ? AgentPresetSection_module_css_default.cardSelectionDisabled : void 0
|
|
1937
|
+
].filter(Boolean).join(" "),
|
|
1938
|
+
children: [
|
|
1939
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1940
|
+
type: "button",
|
|
1941
|
+
className: AgentPresetSection_module_css_default.cardMain,
|
|
1942
|
+
"aria-pressed": row.isDefault,
|
|
1943
|
+
disabled: row.isDefault || row.broken === void 0 && (!state.showPicker || state.policySaving),
|
|
1944
|
+
"aria-disabled": row.broken !== void 0,
|
|
1945
|
+
"aria-label": `${selectionAction}: ${text.name}`,
|
|
1946
|
+
title: selectionAction,
|
|
1947
|
+
onClick: () => {
|
|
1948
|
+
if (row.broken !== void 0) return;
|
|
1949
|
+
props.makeDefault(row.id);
|
|
1950
|
+
},
|
|
1951
|
+
children: [
|
|
1952
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
1953
|
+
className: AgentPresetSection_module_css_default.cardHead,
|
|
1954
|
+
children: [
|
|
1955
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1956
|
+
className: AgentPresetSection_module_css_default.cardName,
|
|
1957
|
+
children: text.name
|
|
1958
|
+
}),
|
|
1959
|
+
row.broken !== void 0 ? (0, react_jsx_runtime.jsxs)("span", {
|
|
1960
|
+
className: AgentPresetSection_module_css_default.brokenBadge,
|
|
1961
|
+
children: [t("brokenBadge"), (0, react_jsx_runtime.jsx)("span", {
|
|
1962
|
+
className: AgentPresetSection_module_css_default.brokenTip,
|
|
1963
|
+
"aria-hidden": "true",
|
|
1964
|
+
children: row.broken
|
|
1965
|
+
})]
|
|
1966
|
+
}) : null,
|
|
1967
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tag, { children: row.trust === "user" ? t("userTrust") : t("builtIn") }),
|
|
1968
|
+
row.isDefault ? (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tag, {
|
|
1969
|
+
tone: "solid",
|
|
1970
|
+
className: AgentPresetSection_module_css_default.inUse,
|
|
1971
|
+
children: state.showPicker ? t("inUse") : t("selectionOffDefault")
|
|
1972
|
+
}) : null
|
|
1973
|
+
]
|
|
1974
|
+
}),
|
|
1975
|
+
(0, react_jsx_runtime.jsx)(CardDescription, { text: text.description ?? t("noDescription") }),
|
|
1976
|
+
row.broken === void 0 ? null : (0, react_jsx_runtime.jsx)("span", {
|
|
1977
|
+
className: AgentPresetSection_module_css_default.cardBrokenReason,
|
|
1978
|
+
role: "alert",
|
|
1979
|
+
children: row.broken
|
|
1980
|
+
}),
|
|
1981
|
+
(0, react_jsx_runtime.jsx)("code", {
|
|
1982
|
+
className: AgentPresetSection_module_css_default.cardId,
|
|
1983
|
+
children: row.id
|
|
1984
|
+
})
|
|
1985
|
+
]
|
|
1986
|
+
}),
|
|
1987
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1988
|
+
className: AgentPresetSection_module_css_default.cardFoot,
|
|
1989
|
+
children: [
|
|
1990
|
+
row.trust === "system" ? row.broken === void 0 ? (0, react_jsx_runtime.jsx)("button", {
|
|
1991
|
+
type: "button",
|
|
1992
|
+
className: AgentPresetSection_module_css_default.iconButton,
|
|
1993
|
+
"data-tip": t("view"),
|
|
1994
|
+
"aria-label": `${t("view")}: ${text.name}`,
|
|
1995
|
+
onClick: () => {
|
|
1996
|
+
props.view(row.id);
|
|
1997
|
+
},
|
|
1998
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconBrowseOutline16, {})
|
|
1999
|
+
}) : null : (0, react_jsx_runtime.jsx)("button", {
|
|
2000
|
+
type: "button",
|
|
2001
|
+
className: AgentPresetSection_module_css_default.iconButton,
|
|
2002
|
+
"data-tip": state.hasDocument ? t("openLocation") : t("showLocation"),
|
|
2003
|
+
"aria-label": `${state.hasDocument ? t("openLocation") : t("showLocation")}: ${text.name}`,
|
|
2004
|
+
onClick: () => {
|
|
2005
|
+
props.openLocation(row.id);
|
|
2006
|
+
},
|
|
2007
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconFolderOpenOutline16, {})
|
|
2008
|
+
}),
|
|
2009
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
2010
|
+
type: "button",
|
|
2011
|
+
className: AgentPresetSection_module_css_default.iconButton,
|
|
2012
|
+
disabled: !state.authorable || row.broken !== void 0,
|
|
2013
|
+
"data-tip": row.broken !== void 0 ? t("brokenNoCopy") : state.authorable ? t("duplicate") : t("duplicateUnavailable"),
|
|
2014
|
+
"aria-label": `${t("duplicate")}: ${text.name}`,
|
|
2015
|
+
onClick: () => {
|
|
2016
|
+
props.beginCopy(row.id);
|
|
2017
|
+
},
|
|
2018
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconCopyOutline16, {})
|
|
2019
|
+
}),
|
|
2020
|
+
row.trust === "user" ? (0, react_jsx_runtime.jsx)("button", {
|
|
2021
|
+
type: "button",
|
|
2022
|
+
className: `${AgentPresetSection_module_css_default.iconButton} ${AgentPresetSection_module_css_default.iconDanger}`,
|
|
2023
|
+
"data-tip": t("delete"),
|
|
2024
|
+
"aria-label": `${t("delete")}: ${text.name}`,
|
|
2025
|
+
onClick: () => {
|
|
2026
|
+
props.confirmDelete(row.id);
|
|
2027
|
+
},
|
|
2028
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconTrashOutline16, {})
|
|
2029
|
+
}) : null
|
|
2030
|
+
]
|
|
2031
|
+
}),
|
|
2032
|
+
state.revealedPaths[row.id] === void 0 ? null : (0, react_jsx_runtime.jsxs)("p", {
|
|
2033
|
+
className: AgentPresetSection_module_css_default.revealedPath,
|
|
2034
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
2035
|
+
className: AgentPresetSection_module_css_default.revealedPathLabel,
|
|
2036
|
+
children: t("revealedPathLabel")
|
|
2037
|
+
}), (0, react_jsx_runtime.jsx)("code", { children: state.revealedPaths[row.id] })]
|
|
2038
|
+
})
|
|
2039
|
+
]
|
|
2040
|
+
}, row.id))
|
|
2041
|
+
}),
|
|
2042
|
+
tail
|
|
2043
|
+
]
|
|
2044
|
+
}, trust);
|
|
2045
|
+
}),
|
|
2046
|
+
(0, react_jsx_runtime.jsx)(CopyDialog, {
|
|
2047
|
+
state,
|
|
2048
|
+
t,
|
|
2049
|
+
actions: {
|
|
2050
|
+
cancelCopy: props.cancelCopy,
|
|
2051
|
+
confirmCopy: props.confirmCopy,
|
|
2052
|
+
setCopyId: props.setCopyId,
|
|
2053
|
+
setCopyName: props.setCopyName
|
|
2054
|
+
}
|
|
2055
|
+
}),
|
|
2056
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Modal, {
|
|
2057
|
+
open: state.view !== null,
|
|
2058
|
+
onClose: () => {
|
|
2059
|
+
props.closeView();
|
|
2060
|
+
},
|
|
2061
|
+
title: state.view === null ? "" : `${t("view")} · ${viewedTitle}`,
|
|
2062
|
+
closeLabel: t("close"),
|
|
2063
|
+
description: t("composition"),
|
|
2064
|
+
className: AgentPresetSection_module_css_default.dialog,
|
|
2065
|
+
footer: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Button, {
|
|
2066
|
+
variant: "outline",
|
|
2067
|
+
autoFocus: true,
|
|
2068
|
+
onClick: () => {
|
|
2069
|
+
props.closeView();
|
|
2070
|
+
},
|
|
2071
|
+
children: t("close")
|
|
2072
|
+
}),
|
|
2073
|
+
children: state.view === null ? null : (0, react_jsx_runtime.jsx)("pre", {
|
|
2074
|
+
className: AgentPresetSection_module_css_default.viewerCode,
|
|
2075
|
+
children: state.view.content
|
|
2076
|
+
})
|
|
2077
|
+
}),
|
|
2078
|
+
(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Modal, {
|
|
2079
|
+
open: state.pendingDelete !== null,
|
|
2080
|
+
onClose: () => {
|
|
2081
|
+
props.confirmDelete(null);
|
|
2082
|
+
},
|
|
2083
|
+
title: t("deleteTitle"),
|
|
2084
|
+
closeLabel: t("close"),
|
|
2085
|
+
description: t("deleteDescription"),
|
|
2086
|
+
className: AgentPresetSection_module_css_default.deleteDialog,
|
|
2087
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Button, {
|
|
2088
|
+
variant: "outline",
|
|
2089
|
+
autoFocus: true,
|
|
2090
|
+
disabled: state.deleting,
|
|
2091
|
+
onClick: () => {
|
|
2092
|
+
props.confirmDelete(null);
|
|
2093
|
+
},
|
|
2094
|
+
children: t("cancel")
|
|
2095
|
+
}), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Button, {
|
|
2096
|
+
variant: "outline",
|
|
2097
|
+
className: AgentPresetSection_module_css_default.deleteConfirm,
|
|
2098
|
+
disabled: state.deleting,
|
|
2099
|
+
onClick: () => {
|
|
2100
|
+
props.remove();
|
|
2101
|
+
},
|
|
2102
|
+
children: state.deleting ? t("deleting") : t("deleteConfirm")
|
|
2103
|
+
})] })
|
|
2104
|
+
})
|
|
2105
|
+
]
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
//#endregion
|
|
2109
|
+
//#region lib/types/client/seat-store.js
|
|
2110
|
+
/**
|
|
2111
|
+
* Hero-chip controller: which preset the NEXT session gets.
|
|
2112
|
+
*
|
|
2113
|
+
* The new-session screen has no session, so a pick is staged rather than
|
|
2114
|
+
* applied. It reaches a session when one becomes current and is still blank —
|
|
2115
|
+
* whether the workspace connect created it or reused an existing blank one,
|
|
2116
|
+
* which is why staging cannot simply ride along on `sessions.create`.
|
|
2117
|
+
*
|
|
2118
|
+
* The stage is forgotten once applied. The next new session starts from the
|
|
2119
|
+
* Host-effective default again.
|
|
2120
|
+
*/
|
|
2121
|
+
const INITIAL = {
|
|
2122
|
+
showPicker: false,
|
|
2123
|
+
options: [],
|
|
2124
|
+
current: "",
|
|
2125
|
+
error: null,
|
|
2126
|
+
busy: false,
|
|
2127
|
+
introduce: false
|
|
2128
|
+
};
|
|
2129
|
+
/** Stages the next session's preset and applies it when one appears. */
|
|
2130
|
+
var AgentPresetSeatController = class {
|
|
2131
|
+
ctx;
|
|
2132
|
+
currentSession;
|
|
2133
|
+
staged;
|
|
2134
|
+
/** Chip snapshot the renderer subscribes to. */
|
|
2135
|
+
store = (0, _qilin_agent_client_store.createSnapshotStore)(INITIAL);
|
|
2136
|
+
/**
|
|
2137
|
+
* The Host-effective default, so a consumed stage can fall back to it without
|
|
2138
|
+
* re-reading the roster.
|
|
2139
|
+
*/
|
|
2140
|
+
fallback = "";
|
|
2141
|
+
/** Only the newest roster read may publish after overlapping refreshes. */
|
|
2142
|
+
loadGeneration = 0;
|
|
2143
|
+
constructor(ctx, currentSession, staged = {
|
|
2144
|
+
id: void 0,
|
|
2145
|
+
introduce: false
|
|
2146
|
+
}) {
|
|
2147
|
+
this.ctx = ctx;
|
|
2148
|
+
this.currentSession = currentSession;
|
|
2149
|
+
this.staged = staged;
|
|
2150
|
+
}
|
|
2151
|
+
set(patch) {
|
|
2152
|
+
this.store.set({
|
|
2153
|
+
...this.store.getSnapshot(),
|
|
2154
|
+
...patch
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Read the roster and open the chip on the Host-effective default.
|
|
2159
|
+
* @returns once the snapshot reflects the host.
|
|
2160
|
+
*/
|
|
2161
|
+
async load() {
|
|
2162
|
+
const generation = ++this.loadGeneration;
|
|
2163
|
+
const roster = await readRoster(this.ctx);
|
|
2164
|
+
if (generation !== this.loadGeneration) return;
|
|
2165
|
+
const error = this.store.getSnapshot().error;
|
|
2166
|
+
if (!roster.ok) {
|
|
2167
|
+
if (typeof error !== "object" || error === null) this.set({ error: roster.error });
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
const { presets, modeSelectionEnabled } = roster.value;
|
|
2171
|
+
if (!modeSelectionEnabled) {
|
|
2172
|
+
this.staged.id = void 0;
|
|
2173
|
+
this.staged.introduce = false;
|
|
2174
|
+
}
|
|
2175
|
+
this.fallback = presets.find((preset) => preset.isDefault)?.id ?? presets[0]?.id ?? "";
|
|
2176
|
+
const session = this.currentSession();
|
|
2177
|
+
this.set({
|
|
2178
|
+
showPicker: modeSelectionEnabled,
|
|
2179
|
+
options: presetOptions(presets),
|
|
2180
|
+
current: this.staged.id ?? (session === void 0 ? this.fallback : presetOf(session) ?? ""),
|
|
2181
|
+
error: typeof error === "object" ? error : null,
|
|
2182
|
+
introduce: modeSelectionEnabled && this.staged.introduce
|
|
2183
|
+
});
|
|
2184
|
+
await this.apply();
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* Stage one preset for the next session, applying it immediately when a
|
|
2188
|
+
* blank session is already current.
|
|
2189
|
+
*
|
|
2190
|
+
* The refusal is returned as well as stored, because the two readers need
|
|
2191
|
+
* different things from it: the chip's own label carries the standing state,
|
|
2192
|
+
* while the caller that made this pick is the one that has to say why the
|
|
2193
|
+
* label came back — and only it knows the pick was a person's, not the
|
|
2194
|
+
* applier catching up with a session that just became current.
|
|
2195
|
+
* @param id - the preset to stage.
|
|
2196
|
+
* @returns the refusal text, or undefined once the pick settled.
|
|
2197
|
+
*/
|
|
2198
|
+
async select(id) {
|
|
2199
|
+
if (this.store.getSnapshot().busy) return void 0;
|
|
2200
|
+
this.stage(id);
|
|
2201
|
+
await this.apply();
|
|
2202
|
+
const { error } = this.store.getSnapshot();
|
|
2203
|
+
return error !== null && typeof error === "object" ? error.reason : error ?? void 0;
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Stage a pick WITHOUT the immediate apply, for a flow that starts the
|
|
2207
|
+
* receiving session after the pick (the settings section's creator entry).
|
|
2208
|
+
* `select()`'s immediate apply would meet the still-current running session
|
|
2209
|
+
* and drop the stage as unservable; staging alone leaves it for the
|
|
2210
|
+
* list-change applier, which fires when the started session becomes
|
|
2211
|
+
* current.
|
|
2212
|
+
* @param id - the preset to stage.
|
|
2213
|
+
* @param introduce - true when the stage came from another screen and the
|
|
2214
|
+
* chip should announce itself on the session it lands on.
|
|
2215
|
+
*/
|
|
2216
|
+
stage(id, introduce = false) {
|
|
2217
|
+
this.staged.id = id;
|
|
2218
|
+
this.staged.introduce = introduce;
|
|
2219
|
+
this.set({
|
|
2220
|
+
current: id,
|
|
2221
|
+
error: null,
|
|
2222
|
+
introduce
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Capture the exact blank Session a Settings action may bring along.
|
|
2227
|
+
* @returns its id, or undefined outside a blank Session.
|
|
2228
|
+
*/
|
|
2229
|
+
blankSessionId() {
|
|
2230
|
+
const session = this.currentSession();
|
|
2231
|
+
return session?.blank === true ? session.id : void 0;
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Apply a Settings choice only if its captured Session is still current and
|
|
2235
|
+
* blank. The selection uses the existing stage/apply path.
|
|
2236
|
+
* @param expectedSessionId - blank Session captured before the Settings write.
|
|
2237
|
+
* @param id - the effective default that the write persisted.
|
|
2238
|
+
* @returns the Host refusal text, or undefined when applied or no longer relevant.
|
|
2239
|
+
*/
|
|
2240
|
+
async syncBlankSession(expectedSessionId, id) {
|
|
2241
|
+
const session = this.currentSession();
|
|
2242
|
+
if (session === void 0 || !session.blank || session.id !== expectedSessionId) return void 0;
|
|
2243
|
+
this.stage(id);
|
|
2244
|
+
await this.apply();
|
|
2245
|
+
const { error } = this.store.getSnapshot();
|
|
2246
|
+
return error !== null && typeof error === "object" ? error.reason : error ?? void 0;
|
|
2247
|
+
}
|
|
2248
|
+
/** Acknowledge the introduction cue once the chip has played it. */
|
|
2249
|
+
introduced() {
|
|
2250
|
+
if (!this.store.getSnapshot().introduce) return;
|
|
2251
|
+
this.staged.introduce = false;
|
|
2252
|
+
this.set({ introduce: false });
|
|
2253
|
+
}
|
|
2254
|
+
/** Acknowledge a displayed refusal without dismissing a newer attempt.
|
|
2255
|
+
* @param refusal - the selection error whose Toast finished.
|
|
2256
|
+
*/
|
|
2257
|
+
dismissRefusal(refusal) {
|
|
2258
|
+
if (refusal !== null && typeof refusal === "object" && this.store.getSnapshot().error === refusal) this.set({ error: refusal.reason });
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Hand the staged choice to the current session, if there is one to take it.
|
|
2262
|
+
*
|
|
2263
|
+
* Called both by `select()` and by whoever observes the current session
|
|
2264
|
+
* changing, because the session may appear either before or after the pick.
|
|
2265
|
+
* @returns once the switch settled, or immediately when there is nothing to do.
|
|
2266
|
+
*/
|
|
2267
|
+
async apply() {
|
|
2268
|
+
const staged = this.staged.id;
|
|
2269
|
+
const session = this.currentSession();
|
|
2270
|
+
if (staged === void 0) {
|
|
2271
|
+
const current = session === void 0 ? this.fallback : presetOf(session) ?? "";
|
|
2272
|
+
if (current !== this.store.getSnapshot().current) this.set({ current });
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (session === void 0) return;
|
|
2276
|
+
if (!session.blank || presetOf(session) === staged) {
|
|
2277
|
+
this.staged.id = void 0;
|
|
2278
|
+
this.staged.introduce = false;
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
this.set({
|
|
2282
|
+
busy: true,
|
|
2283
|
+
error: null
|
|
2284
|
+
});
|
|
2285
|
+
const refuse = (reason) => {
|
|
2286
|
+
this.set({
|
|
2287
|
+
error: {
|
|
2288
|
+
reason,
|
|
2289
|
+
preset: this.store.getSnapshot().options.find((option) => option.id === staged) ?? {
|
|
2290
|
+
id: staged,
|
|
2291
|
+
trust: "user"
|
|
2292
|
+
}
|
|
2293
|
+
},
|
|
2294
|
+
current: presetOf(session) ?? ""
|
|
2295
|
+
});
|
|
2296
|
+
};
|
|
2297
|
+
try {
|
|
2298
|
+
const result = await this.ctx.remote.agentPresets.select(session.id, staged);
|
|
2299
|
+
this.staged.id = void 0;
|
|
2300
|
+
this.staged.introduce = false;
|
|
2301
|
+
if (!result.ok) {
|
|
2302
|
+
const { error } = result;
|
|
2303
|
+
refuse("reason" in error.details && typeof error.details.reason === "string" ? error.details.reason : error.message);
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
this.set({ current: result.value });
|
|
2307
|
+
} catch (error) {
|
|
2308
|
+
this.staged.id = void 0;
|
|
2309
|
+
this.staged.introduce = false;
|
|
2310
|
+
refuse(error instanceof Error ? error.message : String(error));
|
|
2311
|
+
} finally {
|
|
2312
|
+
this.set({ busy: false });
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
function presetOf(session) {
|
|
2317
|
+
const value = session?.projectionValues?.agentPreset;
|
|
2318
|
+
return typeof value === "string" ? value : void 0;
|
|
2319
|
+
}
|
|
2320
|
+
//#endregion
|
|
2321
|
+
//#region lib/types/client/index.js
|
|
2322
|
+
/**
|
|
2323
|
+
* Agent-preset surface plugin, browser half — three surfaces over one roster:
|
|
2324
|
+
* a chip on the new-session screen for the session about to start, a
|
|
2325
|
+
* read-only label in the session header, and a settings section that manages
|
|
2326
|
+
* the roster (copy, delete, default, and the way into a preset's own files).
|
|
2327
|
+
*
|
|
2328
|
+
* A running session keeps the composition it began with (the host refuses to
|
|
2329
|
+
* adopt an existing session under a different preset). That is what splits
|
|
2330
|
+
* the choice from the display: the hero chip is before-the-fact, while the
|
|
2331
|
+
* header only reports what a session already runs. The default preset is
|
|
2332
|
+
* edited where the roster is visible — the settings section's "make default"
|
|
2333
|
+
* — so General settings carries no duplicate control for the same field.
|
|
2334
|
+
*/
|
|
2335
|
+
/** Required services (cordis fiber inject). */
|
|
2336
|
+
const inject = [
|
|
2337
|
+
"slots",
|
|
2338
|
+
"sessions",
|
|
2339
|
+
"locale",
|
|
2340
|
+
"remote",
|
|
2341
|
+
"remote.agentPresets",
|
|
2342
|
+
"remote.settings"
|
|
2343
|
+
];
|
|
2344
|
+
/**
|
|
2345
|
+
* Mount the roster surfaces: hero chip, session-header label, settings section.
|
|
2346
|
+
* @param ctx - the browser plugin context.
|
|
2347
|
+
*/
|
|
2348
|
+
function apply(ctx) {
|
|
2349
|
+
const controller = new AgentPresetSettingsController(ctx);
|
|
2350
|
+
const staged = {
|
|
2351
|
+
id: void 0,
|
|
2352
|
+
introduce: false
|
|
2353
|
+
};
|
|
2354
|
+
const seats = new WeakMapWithValues();
|
|
2355
|
+
const unboundSeat = new AgentPresetSeatController(ctx, () => void 0, staged);
|
|
2356
|
+
const seatFor = (scope, binding) => {
|
|
2357
|
+
let seat = seats.get(binding);
|
|
2358
|
+
if (seat !== void 0) return seat;
|
|
2359
|
+
seat = new AgentPresetSeatController(scope, () => {
|
|
2360
|
+
if (scope.sessions.binding(binding.sessionId) !== binding) return void 0;
|
|
2361
|
+
const summary = scope.sessions.list.getSnapshot().byId[binding.sessionId];
|
|
2362
|
+
return summary !== void 0 && (scope.sessions.retainInfo(binding.sessionId).getSnapshot().retainedBy.mainView ?? 0) > 0 ? summary : void 0;
|
|
2363
|
+
}, staged);
|
|
2364
|
+
seats.set(binding, seat);
|
|
2365
|
+
binding.ctx.effect(() => () => {
|
|
2366
|
+
seats.delete(binding);
|
|
2367
|
+
}, "ui-agent-preset: Provider binding");
|
|
2368
|
+
return seat;
|
|
2369
|
+
};
|
|
2370
|
+
const section = new AgentPresetSectionController(ctx, () => {
|
|
2371
|
+
controller.load();
|
|
2372
|
+
unboundSeat.load();
|
|
2373
|
+
for (const seat of seats.values) seat.load();
|
|
2374
|
+
});
|
|
2375
|
+
const mainBlankSeat = (scope) => {
|
|
2376
|
+
const summary = Object.values(scope.sessions.list.getSnapshot().byId).find((session) => {
|
|
2377
|
+
/* v8 ignore next -- retained source counts omit zero-valued entries. */
|
|
2378
|
+
return session.blank && (session.retainedBy.mainView ?? 0) > 0;
|
|
2379
|
+
});
|
|
2380
|
+
const binding = summary === void 0 ? void 0 : scope.sessions.binding(summary.id);
|
|
2381
|
+
return binding === void 0 ? void 0 : seatFor(scope, binding);
|
|
2382
|
+
};
|
|
2383
|
+
ctx.effect(() => ctx.locale.register("settings.agentPreset", {
|
|
2384
|
+
zh,
|
|
2385
|
+
en
|
|
2386
|
+
}), "ui-agent-preset: settings row dictionaries");
|
|
2387
|
+
ctx.effect(() => {
|
|
2388
|
+
const refresh = () => {
|
|
2389
|
+
controller.load();
|
|
2390
|
+
if (section.store.getSnapshot().status !== "idle") section.load();
|
|
2391
|
+
unboundSeat.load();
|
|
2392
|
+
for (const seat of seats.values) seat.load();
|
|
2393
|
+
};
|
|
2394
|
+
const disposers = [ctx.remote.$on("settings/document-updated", (ns) => {
|
|
2395
|
+
if (ns !== "agent-presets") return;
|
|
2396
|
+
refresh();
|
|
2397
|
+
}), ctx.on("connection/reset", () => {
|
|
2398
|
+
refresh();
|
|
2399
|
+
})];
|
|
2400
|
+
return () => {
|
|
2401
|
+
for (const dispose of disposers) dispose();
|
|
2402
|
+
};
|
|
2403
|
+
}, "ui-agent-preset: settings refresh");
|
|
2404
|
+
let creatorDraft;
|
|
2405
|
+
ctx.inject([
|
|
2406
|
+
"slots",
|
|
2407
|
+
"conversation",
|
|
2408
|
+
"sessions",
|
|
2409
|
+
"uiWorkspace"
|
|
2410
|
+
], (scope) => {
|
|
2411
|
+
const seatInjected = (sessionId) => {
|
|
2412
|
+
const binding = sessionId === void 0 ? void 0 : scope.sessions.binding(sessionId);
|
|
2413
|
+
const seat = binding === void 0 ? unboundSeat : seatFor(scope, binding);
|
|
2414
|
+
return {
|
|
2415
|
+
hooks: { agentPresetSeat: seat.store },
|
|
2416
|
+
load: () => seat.load(),
|
|
2417
|
+
select: (id) => seat.select(id),
|
|
2418
|
+
dismissRefusal: (error) => {
|
|
2419
|
+
seat.dismissRefusal(error);
|
|
2420
|
+
},
|
|
2421
|
+
introduced: () => {
|
|
2422
|
+
seat.introduced();
|
|
2423
|
+
}
|
|
2424
|
+
};
|
|
2425
|
+
};
|
|
2426
|
+
const labelInjected = () => ({
|
|
2427
|
+
hooks: { agentPresets: controller.store },
|
|
2428
|
+
load: () => controller.load()
|
|
2429
|
+
});
|
|
2430
|
+
const startCreatorDraft = () => {
|
|
2431
|
+
if (!section.store.getSnapshot().showPicker) return;
|
|
2432
|
+
const seat = mainBlankSeat(scope) ?? unboundSeat;
|
|
2433
|
+
seat.stage("ptc", true);
|
|
2434
|
+
scope.uiWorkspace.startSession();
|
|
2435
|
+
seat.apply();
|
|
2436
|
+
};
|
|
2437
|
+
scope.effect(() => {
|
|
2438
|
+
creatorDraft = startCreatorDraft;
|
|
2439
|
+
const chip = scope.slots.register({
|
|
2440
|
+
name: "conversation.hero.agentPreset",
|
|
2441
|
+
locale: "settings.agentPreset",
|
|
2442
|
+
inject: seatInjected
|
|
2443
|
+
}, AgentPresetSeat);
|
|
2444
|
+
const label = scope.slots.register({
|
|
2445
|
+
name: "conversation.session.header.actions",
|
|
2446
|
+
id: "agent-preset",
|
|
2447
|
+
order: -10,
|
|
2448
|
+
locale: "settings.agentPreset",
|
|
2449
|
+
inject: labelInjected
|
|
2450
|
+
}, AgentPresetLabel);
|
|
2451
|
+
const createPlugin = scope.slots.inject("plugins.add.actions", () => scope.slots.register({
|
|
2452
|
+
name: "plugins.add.actions",
|
|
2453
|
+
id: "create-plugin",
|
|
2454
|
+
locale: "settings.agentPreset",
|
|
2455
|
+
inject: () => ({
|
|
2456
|
+
hooks: { agentPresets: controller.store },
|
|
2457
|
+
load: () => controller.load(),
|
|
2458
|
+
startCreatorDraft
|
|
2459
|
+
})
|
|
2460
|
+
}, CreatePluginMenuItem));
|
|
2461
|
+
return () => {
|
|
2462
|
+
creatorDraft = void 0;
|
|
2463
|
+
chip();
|
|
2464
|
+
label();
|
|
2465
|
+
createPlugin();
|
|
2466
|
+
};
|
|
2467
|
+
}, "ui-agent-preset: new-session chip and header label");
|
|
2468
|
+
});
|
|
2469
|
+
/** Capture the exact blank Session one Settings action may update. */
|
|
2470
|
+
const captureBlankSessionSync = () => {
|
|
2471
|
+
const summary = Object.values(ctx.sessions.list.getSnapshot().byId).find((session) => session.blank && (session.retainedBy.mainView ?? 0) > 0);
|
|
2472
|
+
const binding = summary === void 0 ? void 0 : ctx.sessions.binding(summary.id);
|
|
2473
|
+
const seat = binding === void 0 ? void 0 : seats.get(binding);
|
|
2474
|
+
const sessionId = seat?.blankSessionId();
|
|
2475
|
+
return async (id) => {
|
|
2476
|
+
if (seat === void 0 || sessionId === void 0 || binding === void 0 || seats.get(binding) !== seat) return void 0;
|
|
2477
|
+
return await seat.syncBlankSession(sessionId, id);
|
|
2478
|
+
};
|
|
2479
|
+
};
|
|
2480
|
+
const sectionInjected = () => ({
|
|
2481
|
+
hooks: { agentPresetSection: section.store },
|
|
2482
|
+
load: () => section.load(),
|
|
2483
|
+
view: (id) => section.view(id),
|
|
2484
|
+
closeView: () => {
|
|
2485
|
+
section.closeView();
|
|
2486
|
+
},
|
|
2487
|
+
beginCopy: (from) => {
|
|
2488
|
+
section.beginCopy(from);
|
|
2489
|
+
},
|
|
2490
|
+
cancelCopy: () => {
|
|
2491
|
+
section.cancelCopy();
|
|
2492
|
+
},
|
|
2493
|
+
setCopyId: (id) => {
|
|
2494
|
+
section.setCopyId(id);
|
|
2495
|
+
},
|
|
2496
|
+
setCopyName: (name) => {
|
|
2497
|
+
section.setCopyName(name);
|
|
2498
|
+
},
|
|
2499
|
+
confirmCopy: () => section.confirmCopy(),
|
|
2500
|
+
openLocation: (id) => section.openLocation(id),
|
|
2501
|
+
...creatorDraft === void 0 ? {} : { startCreatorDraft: creatorDraft },
|
|
2502
|
+
confirmDelete: (id) => {
|
|
2503
|
+
section.confirmDelete(id);
|
|
2504
|
+
},
|
|
2505
|
+
remove: () => section.remove(),
|
|
2506
|
+
makeDefault: (id) => section.makeDefault(id, captureBlankSessionSync()),
|
|
2507
|
+
setPickerVisible: (showPicker) => section.setPickerVisible(showPicker, captureBlankSessionSync())
|
|
2508
|
+
});
|
|
2509
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
2510
|
+
name: "settings.section",
|
|
2511
|
+
id: "agent-presets",
|
|
2512
|
+
order: 20,
|
|
2513
|
+
label: () => ctx.locale.bind("settings.agentPreset")("nav"),
|
|
2514
|
+
locale: "settings.agentPreset",
|
|
2515
|
+
inject: sectionInjected
|
|
2516
|
+
}, AgentPresetSection));
|
|
2517
|
+
}
|
|
2518
|
+
//#endregion
|
|
2519
|
+
exports.AGENT_PRESET_SETTINGS_NS = AGENT_PRESET_SETTINGS_NS;
|
|
2520
|
+
exports.apply = apply;
|
|
2521
|
+
exports.draftBlocker = draftBlocker;
|
|
2522
|
+
exports.inject = inject;
|
|
2523
|
+
exports.writeDefaultPreset = writeDefaultPreset;
|
|
2524
|
+
return module.exports;
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2528
|
+
//# sourceMappingURL=client.js.map
|