koishi-plugin-chatluna-studio 1.1.8 → 1.1.9
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,9 @@
|
|
|
1
|
+
import type { Context } from 'koishi';
|
|
2
|
+
/**
|
|
3
|
+
* 观察 chatluna-character 一轮响应真正退出的边界。
|
|
4
|
+
*
|
|
5
|
+
* Character 的 `after-chat` 只覆盖成功路径;它自己的 collect 回调则无论成功、提前返回还是抛错,都会在
|
|
6
|
+
* 最外层 finally 调用公开的 `releaseResponseLock(session)`。这里在每次 before-chat 时懒解析当前服务并
|
|
7
|
+
* 透明包装该方法,因此不需要声明注入,也能跟随 Character 后装或重载。
|
|
8
|
+
*/
|
|
9
|
+
export declare function observeCharacterTurnCompletion(ctx: Context, notify: (session: unknown) => void): () => void;
|
|
@@ -72,6 +72,8 @@ export declare class ChatLunaSessionTracker {
|
|
|
72
72
|
* 都没有,只有这张映射能把它还原成具体的(机器人,会话)。
|
|
73
73
|
*/
|
|
74
74
|
private readonly coreKeys;
|
|
75
|
+
/** Character 的 session 对应到精确开始代次;失败终态只能收掉自己,不能按目标误删后继轮。 */
|
|
76
|
+
private readonly characterTurns;
|
|
75
77
|
private readonly disposers;
|
|
76
78
|
constructor(ctx: Context, options?: ChatLunaSessionTrackerOptions);
|
|
77
79
|
/** 内部会话标识唯一对应一轮时才认;映射到多轮时这次错误归属不明。 */
|
|
@@ -91,7 +93,14 @@ export declare class ChatLunaSessionTracker {
|
|
|
91
93
|
resolveActiveTurn(): ChatLunaTurnResolution;
|
|
92
94
|
/** 当前正在进行的对话轮数;诊断与测试用。 */
|
|
93
95
|
get activeTurnCount(): number;
|
|
96
|
+
/**
|
|
97
|
+
* 从 Character 的真实 finally 边界精确收掉对应开始代次。
|
|
98
|
+
*
|
|
99
|
+
* 同一目标的新一轮可能已覆盖活动表;此时过期 release 只消费自己的 token,不删除后继轮。
|
|
100
|
+
*/
|
|
101
|
+
finishCharacterTurn(session: unknown): boolean;
|
|
94
102
|
dispose(): void;
|
|
103
|
+
private beginCharacter;
|
|
95
104
|
private begin;
|
|
96
105
|
private finish;
|
|
97
106
|
private finishCore;
|
package/lib/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Context, Schema } from 'koishi';
|
|
|
2
2
|
import type { StudioAppearance, StudioPersistenceMode } from './types';
|
|
3
3
|
export * from './console';
|
|
4
4
|
export * from './console-contract';
|
|
5
|
+
export * from './chatluna/character-turn-completion';
|
|
5
6
|
export * from './chatluna/error';
|
|
6
7
|
export * from './chatluna/session-tracker';
|
|
7
8
|
export * from './chatluna/usage';
|
package/lib/index.js
CHANGED
|
@@ -77,6 +77,7 @@ __export(index_exports, {
|
|
|
77
77
|
matchesModelRequestQuery: () => matchesModelRequestQuery,
|
|
78
78
|
modelRequestVariableStatusLabel: () => modelRequestVariableStatusLabel,
|
|
79
79
|
name: () => name,
|
|
80
|
+
observeCharacterTurnCompletion: () => observeCharacterTurnCompletion,
|
|
80
81
|
parsePresetSourceDocument: () => parsePresetSourceDocument,
|
|
81
82
|
presentModelRequestDetail: () => presentModelRequestDetail,
|
|
82
83
|
presentModelRequestListItem: () => presentModelRequestListItem,
|
|
@@ -100,6 +101,137 @@ __export(index_exports, {
|
|
|
100
101
|
module.exports = __toCommonJS(index_exports);
|
|
101
102
|
var import_koishi3 = require("koishi");
|
|
102
103
|
|
|
104
|
+
// src/chatluna/runtime.ts
|
|
105
|
+
function readRecord(value) {
|
|
106
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
107
|
+
}
|
|
108
|
+
function readServiceFrom(host, serviceName, accept) {
|
|
109
|
+
const get = readRecord(host)?.get;
|
|
110
|
+
if (typeof get !== "function") return void 0;
|
|
111
|
+
const service = readRecord(get.call(host, serviceName));
|
|
112
|
+
return service ? accept(service) : void 0;
|
|
113
|
+
}
|
|
114
|
+
function listRegistryContexts(value) {
|
|
115
|
+
const registry = readRecord(readRecord(value)?.registry);
|
|
116
|
+
const values = registry?.values;
|
|
117
|
+
if (typeof values !== "function") return [];
|
|
118
|
+
return [...values.call(registry)].flatMap((runtime) => {
|
|
119
|
+
const ctx = readRecord(runtime)?.ctx;
|
|
120
|
+
return ctx ? [ctx] : [];
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function findChatLunaRuntime(host, serviceName, accept) {
|
|
124
|
+
const direct = readServiceFrom(host, serviceName, accept);
|
|
125
|
+
if (direct) return direct;
|
|
126
|
+
for (const ctx of listRegistryContexts(host)) {
|
|
127
|
+
const resolved = readServiceFrom(ctx, serviceName, accept);
|
|
128
|
+
if (resolved) return resolved;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/chatluna/character-turn-completion.ts
|
|
133
|
+
var runtimeStates = /* @__PURE__ */ new WeakMap();
|
|
134
|
+
function observeCharacterTurnCompletion(ctx, notify) {
|
|
135
|
+
const observer = {
|
|
136
|
+
notify,
|
|
137
|
+
bindings: /* @__PURE__ */ new Set(),
|
|
138
|
+
disposed: false
|
|
139
|
+
};
|
|
140
|
+
const on = ctx.on.bind(ctx);
|
|
141
|
+
const disposeListener = on("chatluna_character/before-chat", (payload) => {
|
|
142
|
+
if (observer.disposed) return;
|
|
143
|
+
const session = readRecord(payload)?.session;
|
|
144
|
+
if (!session || typeof session !== "object") return;
|
|
145
|
+
const service = findChatLunaRuntime(ctx, "chatluna_character", acceptCharacterRuntime);
|
|
146
|
+
if (!service) return;
|
|
147
|
+
const binding = selectBinding(observer, ensureRuntimeState(service));
|
|
148
|
+
trackSession(binding, session);
|
|
149
|
+
});
|
|
150
|
+
return () => {
|
|
151
|
+
if (observer.disposed) return;
|
|
152
|
+
observer.disposed = true;
|
|
153
|
+
disposeListener();
|
|
154
|
+
for (const binding of [...observer.bindings]) detachBinding(binding);
|
|
155
|
+
observer.current = void 0;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function acceptCharacterRuntime(service) {
|
|
159
|
+
return typeof service.releaseResponseLock === "function" ? service : void 0;
|
|
160
|
+
}
|
|
161
|
+
function ensureRuntimeState(service) {
|
|
162
|
+
const existing = runtimeStates.get(service);
|
|
163
|
+
if (existing && service.releaseResponseLock === existing.wrapper) return existing;
|
|
164
|
+
const original = service.releaseResponseLock;
|
|
165
|
+
const state = {
|
|
166
|
+
service,
|
|
167
|
+
original,
|
|
168
|
+
bindings: /* @__PURE__ */ new Set()
|
|
169
|
+
};
|
|
170
|
+
state.wrapper = function(session, ...args) {
|
|
171
|
+
for (const binding of [...state.bindings]) {
|
|
172
|
+
if (!consumeSession(binding, session)) continue;
|
|
173
|
+
try {
|
|
174
|
+
binding.observer.notify(session);
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
pruneInactiveBinding(binding);
|
|
178
|
+
}
|
|
179
|
+
return original.apply(this, [session, ...args]);
|
|
180
|
+
};
|
|
181
|
+
service.releaseResponseLock = state.wrapper;
|
|
182
|
+
runtimeStates.set(service, state);
|
|
183
|
+
return state;
|
|
184
|
+
}
|
|
185
|
+
function selectBinding(observer, state) {
|
|
186
|
+
if (observer.current?.state === state) return observer.current;
|
|
187
|
+
if (observer.current) {
|
|
188
|
+
observer.current.current = false;
|
|
189
|
+
pruneInactiveBinding(observer.current);
|
|
190
|
+
}
|
|
191
|
+
let binding = [...observer.bindings].find((candidate) => candidate.state === state);
|
|
192
|
+
if (!binding) {
|
|
193
|
+
binding = {
|
|
194
|
+
observer,
|
|
195
|
+
state,
|
|
196
|
+
pending: /* @__PURE__ */ new WeakMap(),
|
|
197
|
+
pendingCount: 0,
|
|
198
|
+
current: true
|
|
199
|
+
};
|
|
200
|
+
observer.bindings.add(binding);
|
|
201
|
+
state.bindings.add(binding);
|
|
202
|
+
} else {
|
|
203
|
+
binding.current = true;
|
|
204
|
+
}
|
|
205
|
+
observer.current = binding;
|
|
206
|
+
return binding;
|
|
207
|
+
}
|
|
208
|
+
function trackSession(binding, session) {
|
|
209
|
+
binding.pending.set(session, (binding.pending.get(session) ?? 0) + 1);
|
|
210
|
+
binding.pendingCount += 1;
|
|
211
|
+
}
|
|
212
|
+
function consumeSession(binding, session) {
|
|
213
|
+
if (!session || typeof session !== "object") return false;
|
|
214
|
+
const count = binding.pending.get(session);
|
|
215
|
+
if (!count) return false;
|
|
216
|
+
if (count === 1) binding.pending.delete(session);
|
|
217
|
+
else binding.pending.set(session, count - 1);
|
|
218
|
+
binding.pendingCount -= 1;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
function pruneInactiveBinding(binding) {
|
|
222
|
+
if (binding.current || binding.pendingCount > 0) return;
|
|
223
|
+
detachBinding(binding);
|
|
224
|
+
}
|
|
225
|
+
function detachBinding(binding) {
|
|
226
|
+
binding.state.bindings.delete(binding);
|
|
227
|
+
binding.observer.bindings.delete(binding);
|
|
228
|
+
if (binding.observer.current === binding) binding.observer.current = void 0;
|
|
229
|
+
if (binding.state.bindings.size > 0) return;
|
|
230
|
+
const { service, wrapper, original } = binding.state;
|
|
231
|
+
if (service.releaseResponseLock === wrapper) service.releaseResponseLock = original;
|
|
232
|
+
if (runtimeStates.get(service) === binding.state) runtimeStates.delete(service);
|
|
233
|
+
}
|
|
234
|
+
|
|
103
235
|
// src/chatluna/error.ts
|
|
104
236
|
var CHATLUNA_ERROR_CODE_DOCUMENTATION_URL = "https://chatluna.chat/guide/faq/error_code.html";
|
|
105
237
|
var CHATLUNA_ERROR_CAUSES = {
|
|
@@ -116,7 +248,7 @@ var CHATLUNA_ERROR_CAUSES = {
|
|
|
116
248
|
309: ["\u6A21\u578B\u8FD4\u56DE\u7A7A\u54CD\u5E94\uFF1B\u8BF7\u68C0\u67E5 API\u3001\u7F51\u7EDC\u548C\u5BC6\u94A5\uFF0C\u5E76\u786E\u8BA4\u5BF9\u8BDD\u6216\u9884\u8BBE\u5185\u5BB9\u672A\u88AB\u6A21\u578B\u62D2\u7EDD\u3002"]
|
|
117
249
|
};
|
|
118
250
|
function readChatLunaRequestError(error) {
|
|
119
|
-
const value =
|
|
251
|
+
const value = readRecord2(error);
|
|
120
252
|
if (!value) return;
|
|
121
253
|
const code = readFiniteNumber(value.errorCode);
|
|
122
254
|
const message = readNonEmptyString(value.message);
|
|
@@ -152,7 +284,7 @@ function archiveChatLunaModelRequestError(store, error, target) {
|
|
|
152
284
|
}).catch(() => void 0);
|
|
153
285
|
store.trackUpdate(task);
|
|
154
286
|
}
|
|
155
|
-
function
|
|
287
|
+
function readRecord2(value) {
|
|
156
288
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
157
289
|
}
|
|
158
290
|
function readNonEmptyString(value) {
|
|
@@ -162,13 +294,13 @@ function readFiniteNumber(value) {
|
|
|
162
294
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
163
295
|
}
|
|
164
296
|
function readOriginMessage(origin) {
|
|
165
|
-
const record =
|
|
297
|
+
const record = readRecord2(origin);
|
|
166
298
|
return readNonEmptyString(record?.message) ?? readNonEmptyString(origin);
|
|
167
299
|
}
|
|
168
300
|
|
|
169
301
|
// src/chatluna/session-tracker.ts
|
|
170
302
|
var import_koishi = require("koishi");
|
|
171
|
-
function
|
|
303
|
+
function readRecord3(value) {
|
|
172
304
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
173
305
|
}
|
|
174
306
|
function readString(value) {
|
|
@@ -176,7 +308,7 @@ function readString(value) {
|
|
|
176
308
|
}
|
|
177
309
|
function readNested(value, path) {
|
|
178
310
|
let current = value;
|
|
179
|
-
for (const key of path) current =
|
|
311
|
+
for (const key of path) current = readRecord3(current)?.[key];
|
|
180
312
|
return current;
|
|
181
313
|
}
|
|
182
314
|
function readFirstString(value, paths) {
|
|
@@ -233,10 +365,10 @@ var ChatLunaSessionTracker = class {
|
|
|
233
365
|
this.options = options;
|
|
234
366
|
const on = ctx.on.bind(ctx);
|
|
235
367
|
this.disposers.push(on("chatluna/before-chat", (conversationId, _message, _variables, _chatInterface, session) => {
|
|
236
|
-
const
|
|
237
|
-
if (!
|
|
368
|
+
const started = this.begin(session);
|
|
369
|
+
if (!started) return;
|
|
238
370
|
const keys = this.coreKeys.get(conversationId) ?? /* @__PURE__ */ new Set();
|
|
239
|
-
keys.add(key);
|
|
371
|
+
keys.add(started.key);
|
|
240
372
|
this.coreKeys.set(conversationId, keys);
|
|
241
373
|
}));
|
|
242
374
|
this.disposers.push(on("chatluna/after-chat", (conversationId, _source, _response, _variables, _chatInterface, session) => {
|
|
@@ -248,10 +380,11 @@ var ChatLunaSessionTracker = class {
|
|
|
248
380
|
this.finishCore(conversationId);
|
|
249
381
|
}));
|
|
250
382
|
this.disposers.push(on("chatluna_character/before-chat", (payload) => {
|
|
251
|
-
this.
|
|
383
|
+
this.beginCharacter(readRecord3(payload)?.session);
|
|
252
384
|
}));
|
|
253
385
|
this.disposers.push(on("chatluna_character/after-chat", (payload) => {
|
|
254
|
-
|
|
386
|
+
const session = readRecord3(payload)?.session;
|
|
387
|
+
if (!this.finishCharacterTurn(session)) this.finish(session);
|
|
255
388
|
}));
|
|
256
389
|
}
|
|
257
390
|
options;
|
|
@@ -264,6 +397,8 @@ var ChatLunaSessionTracker = class {
|
|
|
264
397
|
* 都没有,只有这张映射能把它还原成具体的(机器人,会话)。
|
|
265
398
|
*/
|
|
266
399
|
coreKeys = /* @__PURE__ */ new Map();
|
|
400
|
+
/** Character 的 session 对应到精确开始代次;失败终态只能收掉自己,不能按目标误删后继轮。 */
|
|
401
|
+
characterTurns = /* @__PURE__ */ new WeakMap();
|
|
267
402
|
disposers = [];
|
|
268
403
|
/** 内部会话标识唯一对应一轮时才认;映射到多轮时这次错误归属不明。 */
|
|
269
404
|
resolveCoreTurn(conversationId) {
|
|
@@ -296,22 +431,44 @@ var ChatLunaSessionTracker = class {
|
|
|
296
431
|
get activeTurnCount() {
|
|
297
432
|
return this.turns.size;
|
|
298
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* 从 Character 的真实 finally 边界精确收掉对应开始代次。
|
|
436
|
+
*
|
|
437
|
+
* 同一目标的新一轮可能已覆盖活动表;此时过期 release 只消费自己的 token,不删除后继轮。
|
|
438
|
+
*/
|
|
439
|
+
finishCharacterTurn(session) {
|
|
440
|
+
if (!session || typeof session !== "object") return false;
|
|
441
|
+
const tokens = this.characterTurns.get(session);
|
|
442
|
+
const token = tokens?.shift();
|
|
443
|
+
if (!token) return false;
|
|
444
|
+
if (!tokens?.length) this.characterTurns.delete(session);
|
|
445
|
+
if (this.turns.get(token.key) === token.turn) this.turns.delete(token.key);
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
299
448
|
dispose() {
|
|
300
449
|
for (const dispose of this.disposers.splice(0)) dispose();
|
|
301
450
|
this.turns.clear();
|
|
302
451
|
this.coreKeys.clear();
|
|
303
452
|
}
|
|
453
|
+
beginCharacter(session) {
|
|
454
|
+
const started = this.begin(session);
|
|
455
|
+
if (!started || !session || typeof session !== "object") return;
|
|
456
|
+
const tokens = this.characterTurns.get(session) ?? [];
|
|
457
|
+
tokens.push(started);
|
|
458
|
+
this.characterTurns.set(session, tokens);
|
|
459
|
+
}
|
|
304
460
|
begin(session) {
|
|
305
461
|
const entities = readStudioSessionEntities(session);
|
|
306
462
|
if (!entities) return;
|
|
307
463
|
const key = turnKey(entities);
|
|
308
464
|
const existing = this.turns.get(key);
|
|
309
|
-
|
|
465
|
+
const turn = {
|
|
310
466
|
entities,
|
|
311
467
|
interactionId: existing?.interactionId ?? import_koishi.Random.id(),
|
|
312
468
|
virtual: isVirtualOneBotSession(session)
|
|
313
|
-
}
|
|
314
|
-
|
|
469
|
+
};
|
|
470
|
+
this.turns.set(key, turn);
|
|
471
|
+
return { key, turn };
|
|
315
472
|
}
|
|
316
473
|
finish(session) {
|
|
317
474
|
const entities = readStudioSessionEntities(session);
|
|
@@ -4166,34 +4323,6 @@ function normalizeFileSystemError(error, fileName) {
|
|
|
4166
4323
|
return new PresetRepositoryError("unsafe-file", `\u65E0\u6CD5\u5B89\u5168\u8BBF\u95EE\u9884\u8BBE\uFF1A${fileName}`, error);
|
|
4167
4324
|
}
|
|
4168
4325
|
|
|
4169
|
-
// src/chatluna/runtime.ts
|
|
4170
|
-
function readRecord3(value) {
|
|
4171
|
-
return value !== null && typeof value === "object" ? value : void 0;
|
|
4172
|
-
}
|
|
4173
|
-
function readServiceFrom(host, serviceName, accept) {
|
|
4174
|
-
const get = readRecord3(host)?.get;
|
|
4175
|
-
if (typeof get !== "function") return void 0;
|
|
4176
|
-
const service = readRecord3(get.call(host, serviceName));
|
|
4177
|
-
return service ? accept(service) : void 0;
|
|
4178
|
-
}
|
|
4179
|
-
function listRegistryContexts(value) {
|
|
4180
|
-
const registry = readRecord3(readRecord3(value)?.registry);
|
|
4181
|
-
const values = registry?.values;
|
|
4182
|
-
if (typeof values !== "function") return [];
|
|
4183
|
-
return [...values.call(registry)].flatMap((runtime) => {
|
|
4184
|
-
const ctx = readRecord3(runtime)?.ctx;
|
|
4185
|
-
return ctx ? [ctx] : [];
|
|
4186
|
-
});
|
|
4187
|
-
}
|
|
4188
|
-
function findChatLunaRuntime(host, serviceName, accept) {
|
|
4189
|
-
const direct = readServiceFrom(host, serviceName, accept);
|
|
4190
|
-
if (direct) return direct;
|
|
4191
|
-
for (const ctx of listRegistryContexts(host)) {
|
|
4192
|
-
const resolved = readServiceFrom(ctx, serviceName, accept);
|
|
4193
|
-
if (resolved) return resolved;
|
|
4194
|
-
}
|
|
4195
|
-
}
|
|
4196
|
-
|
|
4197
4326
|
// src/presets/runtime-correlation.ts
|
|
4198
4327
|
var PresetRuntimeSnapshotTracker = class {
|
|
4199
4328
|
constructor(ctx) {
|
|
@@ -4231,23 +4360,17 @@ var PresetRuntimeSnapshotTracker = class {
|
|
|
4231
4360
|
this.active.set(key, { target, turnId, snapshot });
|
|
4232
4361
|
const session = record?.session;
|
|
4233
4362
|
if (session && typeof session === "object") {
|
|
4234
|
-
const keys = this.characterSessionTurnKeys.get(session) ??
|
|
4235
|
-
keys.
|
|
4363
|
+
const keys = this.characterSessionTurnKeys.get(session) ?? [];
|
|
4364
|
+
keys.push(key);
|
|
4236
4365
|
this.characterSessionTurnKeys.set(session, keys);
|
|
4237
4366
|
}
|
|
4238
4367
|
}));
|
|
4239
4368
|
this.disposers.push(on("chatluna_character/after-chat", (payload) => {
|
|
4240
4369
|
const record = readRecord4(payload);
|
|
4241
|
-
const target = this.resolveSession(record?.session);
|
|
4242
|
-
if (!target) return;
|
|
4243
4370
|
const session = record?.session;
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
if (directKey) this.active.delete(directKey);
|
|
4248
|
-
this.characterSessionTurnKeys.delete(session);
|
|
4249
|
-
return;
|
|
4250
|
-
}
|
|
4371
|
+
if (this.finishCharacterTurn(session)) return;
|
|
4372
|
+
const target = this.resolveSession(session);
|
|
4373
|
+
if (!target) return;
|
|
4251
4374
|
const matches = [...this.active].filter(([, turn]) => turn.snapshot.kind === "character" && sameTarget(turn.target, target));
|
|
4252
4375
|
if (matches.length === 1) this.active.delete(matches[0][0]);
|
|
4253
4376
|
}));
|
|
@@ -4255,10 +4378,21 @@ var PresetRuntimeSnapshotTracker = class {
|
|
|
4255
4378
|
ctx;
|
|
4256
4379
|
active = /* @__PURE__ */ new Map();
|
|
4257
4380
|
coreTurnKeys = /* @__PURE__ */ new Map();
|
|
4381
|
+
/** Character 同一 session 上的开始代次按顺序保存,终态只能删除与自己对应的快照。 */
|
|
4258
4382
|
characterSessionTurnKeys = /* @__PURE__ */ new WeakMap();
|
|
4259
4383
|
disposers = [];
|
|
4260
4384
|
nextCoreTurn = 1;
|
|
4261
4385
|
nextCharacterTurn = 1;
|
|
4386
|
+
/** Character 的 release 终态只删除该 session 最早尚未完成的快照代次。 */
|
|
4387
|
+
finishCharacterTurn(session) {
|
|
4388
|
+
if (!session || typeof session !== "object") return false;
|
|
4389
|
+
const keys = this.characterSessionTurnKeys.get(session);
|
|
4390
|
+
const key = keys?.shift();
|
|
4391
|
+
if (!key) return false;
|
|
4392
|
+
if (!keys?.length) this.characterSessionTurnKeys.delete(session);
|
|
4393
|
+
this.active.delete(key);
|
|
4394
|
+
return true;
|
|
4395
|
+
}
|
|
4262
4396
|
getActiveSnapshots(target) {
|
|
4263
4397
|
const turns = [...this.active.values()].filter((turn) => sameTarget(turn.target, target));
|
|
4264
4398
|
const snapshots = ["core", "character"].flatMap((kind) => {
|
|
@@ -4609,6 +4743,10 @@ function apply(ctx, config) {
|
|
|
4609
4743
|
if (conversationId) archiveChatLunaModelRequestError(modelRequests, error, { conversationId });
|
|
4610
4744
|
}
|
|
4611
4745
|
});
|
|
4746
|
+
const disposeCharacterTurnCompletion = observeCharacterTurnCompletion(inner, (session) => {
|
|
4747
|
+
sessions.finishCharacterTurn(session);
|
|
4748
|
+
presetSnapshots.finishCharacterTurn(session);
|
|
4749
|
+
});
|
|
4612
4750
|
const presets = new StudioPresetService({ baseDir: inner.baseDir, modelRequests });
|
|
4613
4751
|
const getPersistenceStatus = () => {
|
|
4614
4752
|
if (config.persistenceMode !== "database") {
|
|
@@ -4653,6 +4791,7 @@ function apply(ctx, config) {
|
|
|
4653
4791
|
});
|
|
4654
4792
|
inner.on("dispose", () => {
|
|
4655
4793
|
disposeCollector();
|
|
4794
|
+
disposeCharacterTurnCompletion();
|
|
4656
4795
|
presetSnapshots.dispose();
|
|
4657
4796
|
sessions.dispose();
|
|
4658
4797
|
void modelRequests.waitForPersistence().catch((error) => {
|
|
@@ -4710,6 +4849,7 @@ function apply(ctx, config) {
|
|
|
4710
4849
|
matchesModelRequestQuery,
|
|
4711
4850
|
modelRequestVariableStatusLabel,
|
|
4712
4851
|
name,
|
|
4852
|
+
observeCharacterTurnCompletion,
|
|
4713
4853
|
parsePresetSourceDocument,
|
|
4714
4854
|
presentModelRequestDetail,
|
|
4715
4855
|
presentModelRequestListItem,
|
|
@@ -4,11 +4,14 @@ export declare class PresetRuntimeSnapshotTracker {
|
|
|
4
4
|
private ctx;
|
|
5
5
|
private readonly active;
|
|
6
6
|
private readonly coreTurnKeys;
|
|
7
|
+
/** Character 同一 session 上的开始代次按顺序保存,终态只能删除与自己对应的快照。 */
|
|
7
8
|
private readonly characterSessionTurnKeys;
|
|
8
9
|
private readonly disposers;
|
|
9
10
|
private nextCoreTurn;
|
|
10
11
|
private nextCharacterTurn;
|
|
11
12
|
constructor(ctx: Context);
|
|
13
|
+
/** Character 的 release 终态只删除该 session 最早尚未完成的快照代次。 */
|
|
14
|
+
finishCharacterTurn(session: unknown): boolean;
|
|
12
15
|
getActiveSnapshots(target: PresetRuntimeSessionTarget): PresetRuntimeSnapshot[];
|
|
13
16
|
dispose(): void;
|
|
14
17
|
private resolveSession;
|