@riemannre3/dsh-roleplay 0.1.3

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.
@@ -0,0 +1,618 @@
1
+ export class CommittedReplyVariableGate {
2
+ #pending = new Map();
3
+ capture(sessionId, body) {
4
+ this.#pending.set(sessionId, body);
5
+ }
6
+ async commit(sessionId, apply) {
7
+ const body = this.#pending.get(sessionId);
8
+ if (body === undefined)
9
+ return false;
10
+ await apply(body);
11
+ this.#pending.delete(sessionId);
12
+ return true;
13
+ }
14
+ discard(sessionId) {
15
+ this.#pending.delete(sessionId);
16
+ }
17
+ }
18
+ class VariableRuntimeError extends Error {
19
+ code;
20
+ operation;
21
+ path;
22
+ constructor(code, message, operation, path) {
23
+ super(message);
24
+ this.code = code;
25
+ this.operation = operation;
26
+ this.path = path;
27
+ }
28
+ }
29
+ function isObject(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+ function isVariableValue(value) {
33
+ if (value === null || typeof value === "string" || typeof value === "boolean")
34
+ return true;
35
+ if (typeof value === "number")
36
+ return Number.isFinite(value);
37
+ if (Array.isArray(value))
38
+ return value.every(isVariableValue);
39
+ if (!isObject(value))
40
+ return false;
41
+ return Object.entries(value).every(([key, child]) => !forbiddenSegment(key) && isVariableValue(child));
42
+ }
43
+ function cloneValue(value) {
44
+ if (Array.isArray(value))
45
+ return value.map((item) => cloneValue(item));
46
+ if (isObject(value))
47
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, cloneValue(child)]));
48
+ return value;
49
+ }
50
+ function mergeObject(target, source) {
51
+ const result = cloneValue(target);
52
+ for (const [key, value] of Object.entries(source)) {
53
+ if (forbiddenSegment(key))
54
+ throw new VariableRuntimeError("INVALID_PATH", `变量对象包含不安全的键:${key}`);
55
+ const current = result[key];
56
+ result[key] = isObject(current) && isObject(value) ? mergeObject(current, value) : cloneValue(value);
57
+ }
58
+ return result;
59
+ }
60
+ export function mergeVariableScopes(scopes) {
61
+ return [scopes.global, scopes.character, scopes.script, scopes.chat, scopes.messageSelectedVariant]
62
+ .reduce((state, scope) => scope === undefined ? state : mergeObject(state, scope), {});
63
+ }
64
+ function stripFence(value) {
65
+ const trimmed = value.trim();
66
+ const match = /^```(?:json|yaml|yml|xml)?\s*\n([\s\S]*?)\n```$/iu.exec(trimmed);
67
+ return match?.[1].trim() ?? trimmed;
68
+ }
69
+ function initvarPayloads(content) {
70
+ const matches = Array.from(content.matchAll(/<initvar\b[^>]*>([\s\S]*?)<\/initvar>/giu), (match) => match[1]);
71
+ return matches.length > 0 ? matches : [content];
72
+ }
73
+ export function classifyInitvarSyntax(content) {
74
+ const payload = stripFence(initvarPayloads(content)[0] ?? "");
75
+ if (/^[{[]/u.test(payload))
76
+ return "json";
77
+ if (/^<[/a-z_][\s\S]*>$/iu.test(payload))
78
+ return "xml";
79
+ if (/^[^\n:#]+:\s*.+(?:\n|$)/u.test(payload))
80
+ return "yaml";
81
+ return "unknown";
82
+ }
83
+ function parseJsonObject(payload) {
84
+ let value;
85
+ try {
86
+ value = JSON.parse(withoutTrailingJsonCommas(payload));
87
+ }
88
+ catch {
89
+ throw new VariableRuntimeError("INVALID_INITVAR", "initvar JSON 无法解析");
90
+ }
91
+ if (!isObject(value) || !isVariableValue(value))
92
+ throw new VariableRuntimeError("INVALID_INITVAR", "initvar 必须是安全的 JSON 对象");
93
+ return cloneValue(value);
94
+ }
95
+ function withoutTrailingJsonCommas(payload) {
96
+ let result = "";
97
+ let quote = "";
98
+ let escaped = false;
99
+ for (let index = 0; index < payload.length; index += 1) {
100
+ const character = payload[index];
101
+ if (quote.length > 0) {
102
+ result += character;
103
+ if (escaped)
104
+ escaped = false;
105
+ else if (character === "\\")
106
+ escaped = true;
107
+ else if (character === quote)
108
+ quote = "";
109
+ continue;
110
+ }
111
+ if (character === '"') {
112
+ quote = character;
113
+ result += character;
114
+ continue;
115
+ }
116
+ if (character !== ",") {
117
+ result += character;
118
+ continue;
119
+ }
120
+ let cursor = index + 1;
121
+ while (cursor < payload.length && /\s/u.test(payload[cursor]))
122
+ cursor += 1;
123
+ if (payload[cursor] !== "}" && payload[cursor] !== "]")
124
+ result += character;
125
+ }
126
+ return result;
127
+ }
128
+ function scalarFromYaml(value) {
129
+ const trimmed = value.trim();
130
+ if (trimmed === "null" || trimmed === "~")
131
+ return null;
132
+ if (/^(?:true|false)$/iu.test(trimmed))
133
+ return trimmed.toLocaleLowerCase() === "true";
134
+ if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(trimmed))
135
+ return Number(trimmed);
136
+ if (/^["']/.test(trimmed))
137
+ return parseLiteral(trimmed);
138
+ if (/^[{[]/u.test(trimmed))
139
+ return parseLiteral(trimmed);
140
+ return trimmed;
141
+ }
142
+ function parseSimpleYamlObject(payload) {
143
+ const root = {};
144
+ const lines = payload.split(/\r?\n/u).flatMap((rawLine) => {
145
+ if (rawLine.trim().length === 0 || rawLine.trimStart().startsWith("#"))
146
+ return [];
147
+ return [{ indent: rawLine.length - rawLine.trimStart().length, text: rawLine.trim() }];
148
+ });
149
+ const stack = [{ indent: -1, value: root }];
150
+ for (const [lineIndex, line] of lines.entries()) {
151
+ const { indent } = line;
152
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent)
153
+ stack.pop();
154
+ const parent = stack[stack.length - 1].value;
155
+ if (line.text.startsWith("- ")) {
156
+ if (!Array.isArray(parent))
157
+ throw new VariableRuntimeError("INVALID_INITVAR", "initvar YAML 列表缺少父级键");
158
+ parent.push(scalarFromYaml(line.text.slice(2)));
159
+ continue;
160
+ }
161
+ if (Array.isArray(parent))
162
+ throw new VariableRuntimeError("INVALID_INITVAR", "initvar YAML 暂不支持对象列表项");
163
+ const match = /^([^:#][^:]*):(?:\s*(.*))?$/u.exec(line.text);
164
+ if (match === null)
165
+ throw new VariableRuntimeError("INVALID_INITVAR", "initvar YAML 只支持确定性的对象映射");
166
+ const key = match[1].trim();
167
+ if (key.length === 0 || forbiddenSegment(key))
168
+ throw new VariableRuntimeError("INVALID_INITVAR", "initvar YAML 包含不安全的键");
169
+ const rawValue = match[2] ?? "";
170
+ if (rawValue.length === 0) {
171
+ const next = lines[lineIndex + 1];
172
+ const child = next !== undefined && next.indent > indent && next.text.startsWith("- ") ? [] : {};
173
+ parent[key] = child;
174
+ stack.push({ indent, value: child });
175
+ }
176
+ else
177
+ parent[key] = scalarFromYaml(rawValue);
178
+ }
179
+ return root;
180
+ }
181
+ function parseInitvarObject(content) {
182
+ let combined = {};
183
+ let syntax = "unknown";
184
+ for (const rawPayload of initvarPayloads(content)) {
185
+ const payload = stripFence(rawPayload);
186
+ const current = classifyInitvarSyntax(payload);
187
+ if (current === "json")
188
+ combined = mergeObject(combined, parseJsonObject(payload));
189
+ else if (current === "yaml")
190
+ combined = mergeObject(combined, parseSimpleYamlObject(payload));
191
+ else
192
+ throw new VariableRuntimeError("INVALID_INITVAR", `暂不支持 ${current === "xml" ? "XML" : "未知"} initvar 格式`);
193
+ syntax = current;
194
+ }
195
+ return { value: combined, syntax };
196
+ }
197
+ export function initializeVariableRuntime(scopes, sources) {
198
+ const before = mergeVariableScopes(scopes);
199
+ let next = cloneValue(before);
200
+ const events = [{ type: "VARIABLE_INITIALIZATION_STARTED" }];
201
+ const diagnostics = [];
202
+ try {
203
+ for (const source of sources) {
204
+ const parsed = parseInitvarObject(source.content);
205
+ next = mergeObject(next, parsed.value);
206
+ events.push({ type: "COMMAND_PARSED", sourceId: source.id, syntax: parsed.syntax, operation: "merge-initvar" });
207
+ }
208
+ events.push({ type: "VARIABLE_INITIALIZED" });
209
+ return { status: "initialized", state: next, events, diagnostics };
210
+ }
211
+ catch (error) {
212
+ const diagnostic = toDiagnostic(error);
213
+ diagnostics.push(diagnostic);
214
+ events.push({ type: "VARIABLE_INITIALIZATION_FAILED", diagnostic });
215
+ return { status: "failed", state: before, events, diagnostics };
216
+ }
217
+ }
218
+ function forbiddenSegment(value) {
219
+ return value === "__proto__" || value === "prototype" || value === "constructor";
220
+ }
221
+ function validateSegments(segments, path) {
222
+ if (segments.length === 0 || segments.some((segment) => typeof segment === "string" && (segment.length === 0 || forbiddenSegment(segment)))) {
223
+ throw new VariableRuntimeError("INVALID_PATH", `变量路径不安全:${path}`, undefined, path);
224
+ }
225
+ return [...segments];
226
+ }
227
+ function legacyPath(path) {
228
+ const segments = [];
229
+ const normalized = path.replace(/\[(\d+)\]/gu, ".$1");
230
+ for (const item of normalized.split("."))
231
+ segments.push(/^\d+$/u.test(item) ? Number(item) : item);
232
+ return validateSegments(segments, path);
233
+ }
234
+ function pointerPath(path) {
235
+ if (!path.startsWith("/"))
236
+ throw new VariableRuntimeError("INVALID_PATH", `JSON Pointer 必须以 / 开头:${path}`, undefined, path);
237
+ return validateSegments(path.slice(1).split("/").map((item) => item.replace(/~1/gu, "/").replace(/~0/gu, "~")).map((item) => /^\d+$/u.test(item) ? Number(item) : item), path);
238
+ }
239
+ function parentAt(root, segments, path, create) {
240
+ let cursor = root;
241
+ for (const [index, segment] of segments.slice(0, -1).entries()) {
242
+ const child = cursor[segment];
243
+ if (child === undefined && create) {
244
+ const created = typeof segments[index + 1] === "number" ? [] : {};
245
+ cursor[segment] = created;
246
+ cursor = created;
247
+ continue;
248
+ }
249
+ if (!isObject(child) && !Array.isArray(child))
250
+ throw new VariableRuntimeError(child === undefined ? "PATH_NOT_FOUND" : "TYPE_MISMATCH", `变量路径不存在或不是容器:${path}`, undefined, path);
251
+ cursor = child;
252
+ }
253
+ return { parent: cursor, key: segments[segments.length - 1] };
254
+ }
255
+ function valueAt(root, segments, path) {
256
+ let cursor = root;
257
+ for (const segment of segments) {
258
+ if ((!isObject(cursor) && !Array.isArray(cursor)) || !(segment in cursor))
259
+ throw new VariableRuntimeError("PATH_NOT_FOUND", `变量路径不存在:${path}`, undefined, path);
260
+ cursor = cursor[segment];
261
+ }
262
+ return cursor;
263
+ }
264
+ function hasAt(root, segments) {
265
+ try {
266
+ valueAt(root, segments, segments.join("."));
267
+ return true;
268
+ }
269
+ catch {
270
+ return false;
271
+ }
272
+ }
273
+ function setAt(root, segments, path, value, create) {
274
+ const { parent, key } = parentAt(root, segments, path, create);
275
+ if (!create && !(key in parent))
276
+ throw new VariableRuntimeError("PATH_NOT_FOUND", `变量路径不存在:${path}`, undefined, path);
277
+ if (Array.isArray(parent) && typeof key !== "number")
278
+ throw new VariableRuntimeError("TYPE_MISMATCH", `数组路径必须使用数字下标:${path}`, undefined, path);
279
+ parent[key] = cloneValue(value);
280
+ }
281
+ function replaceLeafAt(root, segments, path, value) {
282
+ const { parent, key } = parentAt(root, segments, path, false);
283
+ if (Array.isArray(parent)) {
284
+ if (typeof key !== "number")
285
+ throw new VariableRuntimeError("TYPE_MISMATCH", `数组路径必须使用数字下标:${path}`, undefined, path);
286
+ if (!(key in parent))
287
+ throw new VariableRuntimeError("PATH_NOT_FOUND", `变量路径不存在:${path}`, undefined, path);
288
+ }
289
+ else if (typeof key !== "string")
290
+ throw new VariableRuntimeError("TYPE_MISMATCH", `对象路径不能使用数字键:${path}`, undefined, path);
291
+ parent[key] = cloneValue(value);
292
+ }
293
+ function deleteAt(root, segments, path) {
294
+ const { parent, key } = parentAt(root, segments, path, false);
295
+ if (!(key in parent))
296
+ throw new VariableRuntimeError("PATH_NOT_FOUND", `变量路径不存在:${path}`, undefined, path);
297
+ if (Array.isArray(parent)) {
298
+ if (typeof key !== "number")
299
+ throw new VariableRuntimeError("TYPE_MISMATCH", `数组路径必须使用数字下标:${path}`, undefined, path);
300
+ parent.splice(key, 1);
301
+ }
302
+ else
303
+ delete parent[key];
304
+ }
305
+ function parseQuotedString(value) {
306
+ const quote = value[0];
307
+ if ((quote !== "'" && quote !== '"') || value.at(-1) !== quote)
308
+ throw new VariableRuntimeError("INVALID_ARGUMENT", "字符串参数缺少配对引号");
309
+ if (quote === '"') {
310
+ try {
311
+ return JSON.parse(value);
312
+ }
313
+ catch {
314
+ throw new VariableRuntimeError("INVALID_ARGUMENT", "字符串参数无法解析");
315
+ }
316
+ }
317
+ let result = "";
318
+ for (let index = 1; index < value.length - 1; index += 1) {
319
+ const character = value[index];
320
+ if (character !== "\\") {
321
+ result += character;
322
+ continue;
323
+ }
324
+ index += 1;
325
+ const escaped = value[index];
326
+ if (escaped === "n")
327
+ result += "\n";
328
+ else if (escaped === "r")
329
+ result += "\r";
330
+ else if (escaped === "t")
331
+ result += "\t";
332
+ else if (escaped === "\\" || escaped === "'")
333
+ result += escaped;
334
+ else
335
+ throw new VariableRuntimeError("INVALID_ARGUMENT", `不支持的字符串转义:\\${escaped}`);
336
+ }
337
+ return result;
338
+ }
339
+ function parseLiteral(value) {
340
+ const trimmed = value.trim();
341
+ let parsed;
342
+ if (trimmed.startsWith("'") || trimmed.startsWith('"'))
343
+ parsed = parseQuotedString(trimmed);
344
+ else {
345
+ try {
346
+ parsed = JSON.parse(trimmed);
347
+ }
348
+ catch {
349
+ throw new VariableRuntimeError("INVALID_ARGUMENT", `命令参数不是安全的 JSON 字面量:${trimmed.slice(0, 48)}`);
350
+ }
351
+ }
352
+ if (!isVariableValue(parsed))
353
+ throw new VariableRuntimeError("INVALID_ARGUMENT", "命令参数不是安全的变量值");
354
+ return cloneValue(parsed);
355
+ }
356
+ function splitArguments(value) {
357
+ const parts = [];
358
+ let start = 0;
359
+ let quote = "";
360
+ let escaped = false;
361
+ let depth = 0;
362
+ for (let index = 0; index < value.length; index += 1) {
363
+ const character = value[index];
364
+ if (quote.length > 0) {
365
+ if (escaped)
366
+ escaped = false;
367
+ else if (character === "\\")
368
+ escaped = true;
369
+ else if (character === quote)
370
+ quote = "";
371
+ continue;
372
+ }
373
+ if (character === "'" || character === '"')
374
+ quote = character;
375
+ else if (character === "{" || character === "[")
376
+ depth += 1;
377
+ else if (character === "}" || character === "]")
378
+ depth -= 1;
379
+ else if (character === "," && depth === 0) {
380
+ parts.push(value.slice(start, index).trim());
381
+ start = index + 1;
382
+ }
383
+ }
384
+ if (quote.length > 0 || depth !== 0)
385
+ throw new VariableRuntimeError("INVALID_COMMAND", "命令参数括号或引号不完整");
386
+ const last = value.slice(start).trim();
387
+ if (last.length > 0)
388
+ parts.push(last);
389
+ return parts;
390
+ }
391
+ function scanLegacyCommands(payload) {
392
+ const operations = [];
393
+ const consumed = [];
394
+ const head = /_\.(set|add|insert|assign|remove|delete|unset|move|replace|delta)\s*\(/giu;
395
+ let match;
396
+ while ((match = head.exec(payload)) !== null) {
397
+ const open = head.lastIndex - 1;
398
+ let quote = "";
399
+ let escaped = false;
400
+ let depth = 1;
401
+ let close = -1;
402
+ for (let index = open + 1; index < payload.length; index += 1) {
403
+ const character = payload[index];
404
+ if (quote.length > 0) {
405
+ if (escaped)
406
+ escaped = false;
407
+ else if (character === "\\")
408
+ escaped = true;
409
+ else if (character === quote)
410
+ quote = "";
411
+ }
412
+ else if (character === "'" || character === '"')
413
+ quote = character;
414
+ else if (character === "(")
415
+ depth += 1;
416
+ else if (character === ")" && --depth === 0) {
417
+ close = index;
418
+ break;
419
+ }
420
+ }
421
+ if (close < 0)
422
+ throw new VariableRuntimeError("INVALID_COMMAND", "更新命令缺少右括号", match[1]);
423
+ const args = splitArguments(payload.slice(open + 1, close));
424
+ const operation = match[1].toLocaleLowerCase();
425
+ if (operation === "move") {
426
+ if (args.length !== 2)
427
+ throw new VariableRuntimeError("INVALID_ARGUMENT", "move 需要来源和目标两个路径", operation);
428
+ operations.push({ operation, from: parseQuotedString(args[0]), path: parseQuotedString(args[1]) });
429
+ }
430
+ else {
431
+ if (args.length < 1 || args.length > 3)
432
+ throw new VariableRuntimeError("INVALID_ARGUMENT", `${operation} 参数数量不正确`, operation);
433
+ const path = parseQuotedString(args[0]);
434
+ operations.push({ operation, path, ...(args[1] === undefined ? {} : { value: parseLiteral(args[1]) }), ...(args[2] === undefined ? {} : { index: Number(parseLiteral(args[2])) }) });
435
+ }
436
+ consumed.push({ start: match.index, end: close + 1 });
437
+ head.lastIndex = close + 1;
438
+ }
439
+ if (operations.length > 0) {
440
+ let cursor = 0;
441
+ let residue = "";
442
+ for (const range of consumed) {
443
+ residue += payload.slice(cursor, range.start);
444
+ cursor = range.end;
445
+ }
446
+ residue += payload.slice(cursor);
447
+ if (residue.replace(/[\s;]+/gu, "").length > 0)
448
+ throw new VariableRuntimeError("UNKNOWN_FORMAT", "UpdateVariable 混入了不可识别的命令文本");
449
+ }
450
+ return operations;
451
+ }
452
+ function jsonPatchOperations(payload) {
453
+ let parsed;
454
+ try {
455
+ parsed = JSON.parse(payload);
456
+ }
457
+ catch {
458
+ throw new VariableRuntimeError("INVALID_JSON_PATCH", "JSON Patch 无法解析");
459
+ }
460
+ if (!Array.isArray(parsed) || parsed.length === 0)
461
+ throw new VariableRuntimeError("INVALID_JSON_PATCH", "JSON Patch 必须是非空数组");
462
+ return parsed.map((item) => {
463
+ if (!isObject(item) || typeof item.op !== "string" || typeof item.path !== "string")
464
+ throw new VariableRuntimeError("INVALID_JSON_PATCH", "JSON Patch 项缺少 op 或 path");
465
+ const operation = item.op.toLocaleLowerCase();
466
+ if (!isVariableValue(item.value) && item.value !== undefined)
467
+ throw new VariableRuntimeError("INVALID_JSON_PATCH", "JSON Patch value 不是安全变量值", operation, item.path);
468
+ return { operation, path: item.path, ...(typeof item.from === "string" ? { from: item.from } : {}), ...(item.value === undefined ? {} : { value: cloneValue(item.value) }) };
469
+ });
470
+ }
471
+ function parseUpdateOperations(body) {
472
+ const blocks = Array.from(body.matchAll(/<UpdateVariable\b[^>]*>([\s\S]*?)<\/UpdateVariable>/giu), (match) => match[1]);
473
+ if (blocks.length === 0)
474
+ return undefined;
475
+ const operations = [];
476
+ let syntax = "unknown";
477
+ for (const rawBlock of blocks) {
478
+ const patch = /<JSON_?Patch\b[^>]*>([\s\S]*?)<\/JSON_?Patch>/iu.exec(rawBlock);
479
+ if (patch !== null) {
480
+ operations.push(...jsonPatchOperations(stripFence(patch[1])));
481
+ syntax = "json-patch";
482
+ continue;
483
+ }
484
+ const commands = scanLegacyCommands(rawBlock);
485
+ if (commands.length === 0)
486
+ throw new VariableRuntimeError("UNKNOWN_FORMAT", "UpdateVariable 中没有可识别的 pinned MVU 更新格式");
487
+ operations.push(...commands);
488
+ syntax = syntax === "json-patch" ? syntax : "legacy";
489
+ }
490
+ return { operations, syntax };
491
+ }
492
+ function operationPath(operation) {
493
+ const path = operation.path ?? "";
494
+ return { segments: path.startsWith("/") ? pointerPath(path) : legacyPath(path), label: path };
495
+ }
496
+ function requireValue(operation) {
497
+ if (operation.value === undefined)
498
+ throw new VariableRuntimeError("INVALID_ARGUMENT", `${operation.operation} 缺少 value`, operation.operation, operation.path);
499
+ return operation.value;
500
+ }
501
+ function applyOperation(state, operation) {
502
+ const name = operation.operation;
503
+ const { segments, label } = operationPath(operation);
504
+ try {
505
+ if ((name === "add" || name === "insert") && label.startsWith("/")) {
506
+ const value = requireValue(operation);
507
+ const { parent, key } = parentAt(state, segments, label, name === "insert");
508
+ if (Array.isArray(parent)) {
509
+ const index = key === "-" ? parent.length : key;
510
+ if (typeof index !== "number" || index < 0 || index > parent.length)
511
+ throw new VariableRuntimeError("INVALID_ARGUMENT", `${name} 数组下标越界`, name, label);
512
+ parent.splice(index, 0, cloneValue(value));
513
+ }
514
+ else {
515
+ if (typeof key !== "string")
516
+ throw new VariableRuntimeError("TYPE_MISMATCH", `${name} 对象路径不能使用数字键`, name, label);
517
+ parent[key] = cloneValue(value);
518
+ }
519
+ }
520
+ else if (name === "set")
521
+ setAt(state, segments, label, requireValue(operation), true);
522
+ else if (name === "replace")
523
+ replaceLeafAt(state, segments, label, requireValue(operation));
524
+ else if (name === "remove" || name === "delete" || name === "unset")
525
+ deleteAt(state, segments, label);
526
+ else if (name === "assign") {
527
+ const source = requireValue(operation);
528
+ if (!isObject(source))
529
+ throw new VariableRuntimeError("TYPE_MISMATCH", "assign value 必须是对象", name, label);
530
+ const current = valueAt(state, segments, label);
531
+ if (!isObject(current))
532
+ throw new VariableRuntimeError("TYPE_MISMATCH", "assign 目标必须是对象", name, label);
533
+ setAt(state, segments, label, { ...current, ...cloneValue(source) }, false);
534
+ }
535
+ else if (name === "add" || name === "delta") {
536
+ const delta = requireValue(operation);
537
+ const current = hasAt(state, segments) ? valueAt(state, segments, label) : undefined;
538
+ if (current === undefined && name === "add")
539
+ setAt(state, segments, label, delta, true);
540
+ else if (typeof current === "number" && typeof delta === "number")
541
+ setAt(state, segments, label, current + delta, false);
542
+ else if (name === "add" && Array.isArray(current)) {
543
+ current.push(cloneValue(delta));
544
+ }
545
+ else
546
+ throw new VariableRuntimeError("TYPE_MISMATCH", `${name} 需要数字目标${name === "add" ? "或数组目标" : ""}`, name, label);
547
+ }
548
+ else if (name === "insert") {
549
+ const target = valueAt(state, segments, label);
550
+ if (!Array.isArray(target))
551
+ throw new VariableRuntimeError("TYPE_MISMATCH", "insert 目标必须是数组", name, label);
552
+ const index = operation.index ?? target.length;
553
+ if (!Number.isInteger(index) || index < 0 || index > target.length)
554
+ throw new VariableRuntimeError("INVALID_ARGUMENT", "insert 下标越界", name, label);
555
+ target.splice(index, 0, cloneValue(requireValue(operation)));
556
+ }
557
+ else if (name === "move") {
558
+ if (operation.from === undefined)
559
+ throw new VariableRuntimeError("INVALID_ARGUMENT", "move 缺少来源路径", name, label);
560
+ const from = operation.from.startsWith("/") ? pointerPath(operation.from) : legacyPath(operation.from);
561
+ const value = cloneValue(valueAt(state, from, operation.from));
562
+ deleteAt(state, from, operation.from);
563
+ setAt(state, segments, label, value, true);
564
+ }
565
+ else
566
+ throw new VariableRuntimeError("UNSUPPORTED_OPERATION", `不支持的更新操作:${name}`, name, label);
567
+ }
568
+ catch (error) {
569
+ if (error instanceof VariableRuntimeError && error.operation === undefined)
570
+ throw new VariableRuntimeError(error.code, error.message, name, error.path ?? label);
571
+ throw error;
572
+ }
573
+ }
574
+ function toDiagnostic(error) {
575
+ if (error instanceof VariableRuntimeError)
576
+ return { code: error.code, message: error.message, ...(error.operation === undefined ? {} : { operation: error.operation }), ...(error.path === undefined ? {} : { path: error.path }) };
577
+ return { code: "INVALID_COMMAND", message: error instanceof Error ? error.message : "变量更新失败" };
578
+ }
579
+ export function applyVariableUpdate(before, body) {
580
+ const original = cloneValue(before);
581
+ let parsed;
582
+ try {
583
+ parsed = parseUpdateOperations(body);
584
+ }
585
+ catch (error) {
586
+ const diagnostic = toDiagnostic(error);
587
+ return { status: "failed", state: original, operationCount: 0, diagnostics: [diagnostic], events: [{ type: "VARIABLE_UPDATE_STARTED" }, { type: "VARIABLE_UPDATE_FAILED", diagnostic }] };
588
+ }
589
+ if (parsed === undefined)
590
+ return { status: "ignored", state: original, operationCount: 0, diagnostics: [], events: [] };
591
+ const events = [{ type: "VARIABLE_UPDATE_STARTED", syntax: parsed.syntax }];
592
+ const next = cloneValue(original);
593
+ try {
594
+ for (const operation of parsed.operations) {
595
+ applyOperation(next, operation);
596
+ events.push({ type: "COMMAND_PARSED", syntax: parsed.syntax, operation: operation.operation, path: operation.path });
597
+ }
598
+ events.push({ type: "VARIABLE_UPDATE_ENDED", syntax: parsed.syntax });
599
+ return { status: "committed", state: next, operationCount: parsed.operations.length, diagnostics: [], events };
600
+ }
601
+ catch (error) {
602
+ const diagnostic = toDiagnostic(error);
603
+ events.push({ type: "VARIABLE_UPDATE_FAILED", syntax: parsed.syntax, diagnostic });
604
+ return { status: "failed", state: original, operationCount: parsed.operations.length, diagnostics: [diagnostic], events };
605
+ }
606
+ }
607
+ function canonical(value) {
608
+ if (value === null || typeof value !== "object")
609
+ return JSON.stringify(value);
610
+ if (Array.isArray(value))
611
+ return `[${value.map(canonical).join(",")}]`;
612
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
613
+ }
614
+ export async function variableStateDigest(state) {
615
+ const bytes = new TextEncoder().encode(canonical(state));
616
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
617
+ return Array.from(digest, (value) => value.toString(16).padStart(2, "0")).join("");
618
+ }