@kirkchinese/dsh-citeciter 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -34
- package/README.zh.md +52 -34
- package/lib/client.js +646 -151
- package/lib/index.js +442 -107
- package/lib/{topic-DByv84H6.js → topic-BVNCaVbJ.js} +1 -0
- package/lib/{typert-common-Bvx1k2cq.js → typert-common-4_cbghYz.js} +3 -2
- package/lib/typert.host.js +2 -2
- package/lib/typert.remote-client.js +1 -1
- package/lib/types/client/companion-controller.d.ts +6 -4
- package/lib/types/client/components/CiteCiterSettings.d.ts +3 -1
- package/lib/types/client/components/CitePanel.d.ts +1 -1
- package/lib/types/client/conversation-dom.d.ts +72 -0
- package/lib/types/client/prompt.d.ts +7 -2
- package/lib/types/client/request-guard.d.ts +26 -0
- package/lib/types/client/selection.d.ts +8 -0
- package/lib/types/client/settings-document.d.ts +21 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/topic-runtime.d.ts +39 -2
- package/lib/types/topic.d.ts +1 -0
- package/lib/types/typert-common.d.ts +5 -1
- package/lib/types/typert.host.d.ts +6 -1
- package/lib/types/typert.remote-client.d.ts +1 -1
- package/package.json +12 -10
- package/lib/types/client/citation.d.ts +0 -10
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as canonicalCitationIdentity, c as citationSelectionClaimSchema, d as citeCiterSettingsSchema, f as renderCitationContext, i as TUTOR_SECTION_NAME, l as citeCiterRequestSchema, n as CITECITER_SETTINGS_NAMESPACE, o as citationDraftSchema, p as topicMetadataSchema, r as DEFAULT_CITECITER_SETTINGS, t as CITATION_CONTEXT_NAME } from "./topic-
|
|
1
|
+
import { a as canonicalCitationIdentity, c as citationSelectionClaimSchema, d as citeCiterSettingsSchema, f as renderCitationContext, i as TUTOR_SECTION_NAME, l as citeCiterRequestSchema, n as CITECITER_SETTINGS_NAMESPACE, o as citationDraftSchema, p as topicMetadataSchema, r as DEFAULT_CITECITER_SETTINGS, t as CITATION_CONTEXT_NAME } from "./topic-BVNCaVbJ.js";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4
4
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
@@ -9,7 +9,7 @@ import { isAbsolute, matchesGlob, relative, resolve } from "node:path";
|
|
|
9
9
|
import AgentRegistry, { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
10
10
|
import AgentLoop from "@deepseek-ai/dsh-agent-loop";
|
|
11
11
|
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
12
|
-
import { BlockAssembler, ReasoningEffortId, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
12
|
+
import { BlockAssembler, MessageId, ReasoningEffortId, createUserMessage, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
13
13
|
import { effectiveSandboxMode, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
|
|
14
14
|
import SessionStore, { SessionId, foldRequestHeader, snapshotJsonValue } from "@deepseek-ai/dsh-session";
|
|
15
15
|
import JsonlSessionPersistence from "@deepseek-ai/dsh-session-persistence-jsonl";
|
|
@@ -14641,12 +14641,18 @@ function formatSourceSessionRead(source, options) {
|
|
|
14641
14641
|
if (!Number.isSafeInteger(fromSeq) || fromSeq < 0) throw new Error("fromSeq must be a non-negative safe integer");
|
|
14642
14642
|
if (options.throughSeq !== void 0 && (!Number.isSafeInteger(options.throughSeq) || options.throughSeq < fromSeq)) throw new Error("throughSeq must be a safe integer greater than or equal to fromSeq");
|
|
14643
14643
|
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 2) throw new Error("maxBytes must be a safe integer of at least 2");
|
|
14644
|
-
|
|
14644
|
+
let availableThroughSeq = null;
|
|
14645
|
+
for (const event of source.events) {
|
|
14646
|
+
if (options.throughSeq !== void 0 && event.seq > options.throughSeq) break;
|
|
14647
|
+
availableThroughSeq = event.seq;
|
|
14648
|
+
}
|
|
14645
14649
|
const events = [];
|
|
14646
14650
|
let bytesUsed = 2;
|
|
14647
14651
|
let capturedThroughSeq = null;
|
|
14648
14652
|
let truncated = false;
|
|
14649
|
-
for (
|
|
14653
|
+
for (let index = 0; index < source.events.length; index += 1) {
|
|
14654
|
+
const event = source.events[index];
|
|
14655
|
+
if (event === void 0) continue;
|
|
14650
14656
|
if (event.seq < fromSeq) continue;
|
|
14651
14657
|
if (options.throughSeq !== void 0 && event.seq > options.throughSeq) break;
|
|
14652
14658
|
const formatted = formatEvidenceEvent(event, options.includeReasoning);
|
|
@@ -14654,10 +14660,33 @@ function formatSourceSessionRead(source, options) {
|
|
|
14654
14660
|
capturedThroughSeq = event.seq;
|
|
14655
14661
|
continue;
|
|
14656
14662
|
}
|
|
14657
|
-
const
|
|
14663
|
+
const serializedBytes = Buffer.byteLength(JSON.stringify(formatted), "utf8");
|
|
14664
|
+
const eventBytes = serializedBytes + (events.length === 0 ? 0 : 1);
|
|
14658
14665
|
if (bytesUsed + eventBytes > options.maxBytes) {
|
|
14659
|
-
|
|
14660
|
-
|
|
14666
|
+
if (serializedBytes <= options.maxBytes - 2) {
|
|
14667
|
+
truncated = true;
|
|
14668
|
+
break;
|
|
14669
|
+
}
|
|
14670
|
+
const placeholder = evidence({
|
|
14671
|
+
type: event.type,
|
|
14672
|
+
seq: event.seq,
|
|
14673
|
+
oversized: true
|
|
14674
|
+
});
|
|
14675
|
+
const serializedPlaceholderBytes = Buffer.byteLength(JSON.stringify(placeholder), "utf8");
|
|
14676
|
+
const placeholderBytes = serializedPlaceholderBytes + (events.length === 0 ? 0 : 1);
|
|
14677
|
+
if (bytesUsed + placeholderBytes > options.maxBytes) {
|
|
14678
|
+
if (serializedPlaceholderBytes <= options.maxBytes - 2) {
|
|
14679
|
+
truncated = true;
|
|
14680
|
+
break;
|
|
14681
|
+
}
|
|
14682
|
+
capturedThroughSeq = event.seq;
|
|
14683
|
+
truncated = true;
|
|
14684
|
+
break;
|
|
14685
|
+
}
|
|
14686
|
+
events.push(placeholder);
|
|
14687
|
+
bytesUsed += placeholderBytes;
|
|
14688
|
+
capturedThroughSeq = event.seq;
|
|
14689
|
+
continue;
|
|
14661
14690
|
}
|
|
14662
14691
|
events.push(formatted);
|
|
14663
14692
|
bytesUsed += eventBytes;
|
|
@@ -14694,6 +14723,10 @@ const TOPIC_TITLE_CONFIG = resolveSessionTitleLlmConfig({
|
|
|
14694
14723
|
maxOutputTokens: 64,
|
|
14695
14724
|
timeoutMs: 6e4
|
|
14696
14725
|
});
|
|
14726
|
+
const CITECITER_SHUTTING_DOWN = "CiteCiter is shutting down";
|
|
14727
|
+
function citeCiterShuttingDownError() {
|
|
14728
|
+
return /* @__PURE__ */ new Error(CITECITER_SHUTTING_DOWN);
|
|
14729
|
+
}
|
|
14697
14730
|
/** Decide both model visibility and execution access for one private Topic tool. */
|
|
14698
14731
|
function citeCiterToolAvailable(name, allowSourceFiles) {
|
|
14699
14732
|
return ALWAYS_AVAILABLE_TOOLS.has(name) || allowSourceFiles && SOURCE_FILE_TOOLS.has(name);
|
|
@@ -15049,6 +15082,54 @@ function topicMessages(log) {
|
|
|
15049
15082
|
error
|
|
15050
15083
|
};
|
|
15051
15084
|
}
|
|
15085
|
+
/**
|
|
15086
|
+
* Return the first genuine Topic question after any Exact Fork seed.
|
|
15087
|
+
* @param log - private Topic Session contents.
|
|
15088
|
+
* @returns the first post-seed question, or `null` when it has not been committed.
|
|
15089
|
+
*/
|
|
15090
|
+
function firstPostSeedUserQuestion(log) {
|
|
15091
|
+
for (const event of log.events.slice(log.header.seedLength ?? 0)) {
|
|
15092
|
+
if (event.type !== "user/message" || event.data.source.kind !== "user") continue;
|
|
15093
|
+
const text = textBlocks(event.data.content, "text");
|
|
15094
|
+
if (text !== "") return text;
|
|
15095
|
+
}
|
|
15096
|
+
for (const message of pendingPostSeedUserMessages(log)) {
|
|
15097
|
+
if (message.source.kind !== "user") continue;
|
|
15098
|
+
const text = textBlocks(message.content, "text");
|
|
15099
|
+
if (text !== "") return text;
|
|
15100
|
+
}
|
|
15101
|
+
return null;
|
|
15102
|
+
}
|
|
15103
|
+
function pendingPostSeedUserMessages(log) {
|
|
15104
|
+
const pending = {
|
|
15105
|
+
"next-turn": [],
|
|
15106
|
+
"next-step": []
|
|
15107
|
+
};
|
|
15108
|
+
for (const event of log.events.slice(log.header.seedLength ?? 0)) {
|
|
15109
|
+
if (event.type !== "agent/inbox/spliced") continue;
|
|
15110
|
+
pending[event.data.target].splice(event.data.start, event.data.removedCount ?? 0, ...event.data.inserted);
|
|
15111
|
+
}
|
|
15112
|
+
return [...pending["next-step"], ...pending["next-turn"]];
|
|
15113
|
+
}
|
|
15114
|
+
/**
|
|
15115
|
+
* Find a post-seed user question by its durable message identifier.
|
|
15116
|
+
* @param log - private Topic Session contents.
|
|
15117
|
+
* @param messageId - request identity stored as the user-message identity.
|
|
15118
|
+
* @returns the matching question, or `null` when the request is not committed.
|
|
15119
|
+
*/
|
|
15120
|
+
function postSeedUserQuestionById(log, messageId) {
|
|
15121
|
+
const committed = committedPostSeedUserQuestionById(log, messageId);
|
|
15122
|
+
if (committed !== null) return committed;
|
|
15123
|
+
const pending = pendingPostSeedUserMessages(log).find((message) => message.source.kind === "user" && String(message.id) === messageId);
|
|
15124
|
+
return pending === void 0 ? null : textBlocks(pending.content, "text");
|
|
15125
|
+
}
|
|
15126
|
+
function committedPostSeedUserQuestionById(log, messageId) {
|
|
15127
|
+
for (const event of log.events.slice(log.header.seedLength ?? 0)) {
|
|
15128
|
+
if (event.type !== "user/message" || event.data.source.kind !== "user" || String(event.data.id) !== messageId) continue;
|
|
15129
|
+
return textBlocks(event.data.content, "text");
|
|
15130
|
+
}
|
|
15131
|
+
return null;
|
|
15132
|
+
}
|
|
15052
15133
|
function titleSourceKind(value) {
|
|
15053
15134
|
if (value === void 0) return null;
|
|
15054
15135
|
return value.source.kind === "fallback" || value.source.kind === "provider" || value.source.kind === "user" ? value.source.kind : null;
|
|
@@ -15110,18 +15191,34 @@ function resolveTopicModeAndSeed(requested, source, anchorSeq) {
|
|
|
15110
15191
|
function createSourceSessionId(request) {
|
|
15111
15192
|
return "selectionClaim" in request ? request.selectionClaim.sourceSessionId : request.citation.sourceSessionId;
|
|
15112
15193
|
}
|
|
15194
|
+
function identifiedQuestion(requestId, question) {
|
|
15195
|
+
return freezeMessage({
|
|
15196
|
+
id: MessageId(requestId),
|
|
15197
|
+
role: "user",
|
|
15198
|
+
content: [{
|
|
15199
|
+
type: "text",
|
|
15200
|
+
text: question
|
|
15201
|
+
}],
|
|
15202
|
+
source: { kind: "user" }
|
|
15203
|
+
});
|
|
15204
|
+
}
|
|
15113
15205
|
/** One process-local private DSH tree with standard Session logs and Agent loop. */
|
|
15114
15206
|
var TopicRuntime = class {
|
|
15115
15207
|
host;
|
|
15116
15208
|
settings;
|
|
15117
15209
|
runtime = new Context();
|
|
15118
15210
|
index = new TopicIndex();
|
|
15211
|
+
lifecycleAbort = new AbortController();
|
|
15119
15212
|
fibers = [];
|
|
15120
15213
|
handles = /* @__PURE__ */ new Map();
|
|
15121
15214
|
selections = /* @__PURE__ */ new Map();
|
|
15122
15215
|
opening = /* @__PURE__ */ new Map();
|
|
15216
|
+
requests = /* @__PURE__ */ new Set();
|
|
15217
|
+
cleanupFailures = [];
|
|
15123
15218
|
pendingQuestions = /* @__PURE__ */ new Map();
|
|
15124
15219
|
creations = /* @__PURE__ */ new Map();
|
|
15220
|
+
asks = /* @__PURE__ */ new Map();
|
|
15221
|
+
topicAdmissions = /* @__PURE__ */ new Map();
|
|
15125
15222
|
modelChanges = /* @__PURE__ */ new Map();
|
|
15126
15223
|
titleRefreshes = /* @__PURE__ */ new Map();
|
|
15127
15224
|
titleRefreshAttempted = /* @__PURE__ */ new Set();
|
|
@@ -15150,66 +15247,74 @@ var TopicRuntime = class {
|
|
|
15150
15247
|
return this.ready;
|
|
15151
15248
|
}
|
|
15152
15249
|
/** Execute one validated browser command against private Topics. */
|
|
15153
|
-
async request(rawRequest) {
|
|
15250
|
+
async request(rawRequest, callerSignal) {
|
|
15154
15251
|
const request = citeCiterRequestSchema.parse(rawRequest);
|
|
15155
15252
|
await this.ready;
|
|
15156
|
-
|
|
15253
|
+
const signal = AbortSignal.any([this.lifecycleAbort.signal, callerSignal]);
|
|
15254
|
+
this.assertOpen(signal);
|
|
15255
|
+
const operation = this.executeRequest(request, signal);
|
|
15256
|
+
this.requests.add(operation);
|
|
15257
|
+
operation.then(() => this.requests.delete(operation), () => this.requests.delete(operation));
|
|
15258
|
+
return operation;
|
|
15259
|
+
}
|
|
15260
|
+
async executeRequest(request, signal) {
|
|
15261
|
+
this.assertOpen(signal);
|
|
15157
15262
|
switch (request.action) {
|
|
15158
15263
|
case "create": return {
|
|
15159
15264
|
kind: "topic",
|
|
15160
|
-
topic: await this.createIdempotent(request)
|
|
15265
|
+
topic: await this.createIdempotent(request, signal)
|
|
15161
15266
|
};
|
|
15162
15267
|
case "list": return {
|
|
15163
15268
|
kind: "topics",
|
|
15164
|
-
topics: await this.list(request.sourceSessionId, request.includeArchived ?? false)
|
|
15269
|
+
topics: await this.list(request.sourceSessionId, request.includeArchived ?? false, signal)
|
|
15165
15270
|
};
|
|
15166
15271
|
case "get": return {
|
|
15167
15272
|
kind: "topic",
|
|
15168
|
-
topic: await this.
|
|
15273
|
+
topic: await this.get(request.topicSessionId, signal)
|
|
15169
15274
|
};
|
|
15170
15275
|
case "ask": return {
|
|
15171
15276
|
kind: "topic",
|
|
15172
|
-
topic: await this.
|
|
15277
|
+
topic: await this.askIdempotent(request, signal)
|
|
15173
15278
|
};
|
|
15174
15279
|
case "stop": return {
|
|
15175
15280
|
kind: "topic",
|
|
15176
|
-
topic: await this.stop(request.topicSessionId)
|
|
15281
|
+
topic: await this.stop(request.topicSessionId, signal)
|
|
15177
15282
|
};
|
|
15178
15283
|
case "answer-question": return {
|
|
15179
15284
|
kind: "topic",
|
|
15180
|
-
topic: await this.answerQuestion(request)
|
|
15285
|
+
topic: await this.answerQuestion(request, signal)
|
|
15181
15286
|
};
|
|
15182
15287
|
case "cancel-question": return {
|
|
15183
15288
|
kind: "topic",
|
|
15184
|
-
topic: await this.cancelQuestion(request.topicSessionId, request.key)
|
|
15289
|
+
topic: await this.cancelQuestion(request.topicSessionId, request.key, signal)
|
|
15185
15290
|
};
|
|
15186
15291
|
case "rename": return {
|
|
15187
15292
|
kind: "topic",
|
|
15188
|
-
topic: await this.rename(request.topicSessionId, request.title)
|
|
15293
|
+
topic: await this.rename(request.topicSessionId, request.title, signal)
|
|
15189
15294
|
};
|
|
15190
15295
|
case "archive": return {
|
|
15191
15296
|
kind: "topic",
|
|
15192
|
-
topic: await this.archive(request.topicSessionId, request.archived)
|
|
15297
|
+
topic: await this.archive(request.topicSessionId, request.archived, signal)
|
|
15193
15298
|
};
|
|
15194
15299
|
case "delete": return {
|
|
15195
15300
|
kind: "deleted",
|
|
15196
|
-
sessionId: await this.delete(request.topicSessionId, request.confirmSessionId)
|
|
15301
|
+
sessionId: await this.delete(request.topicSessionId, request.confirmSessionId, signal)
|
|
15197
15302
|
};
|
|
15198
15303
|
case "models": return {
|
|
15199
15304
|
kind: "models",
|
|
15200
|
-
providers: await this.models()
|
|
15305
|
+
providers: await this.models(signal)
|
|
15201
15306
|
};
|
|
15202
15307
|
case "set-model-route": return {
|
|
15203
15308
|
kind: "topic",
|
|
15204
|
-
topic: await this.setModelRoute(request)
|
|
15309
|
+
topic: await this.setModelRoute(request, signal)
|
|
15205
15310
|
};
|
|
15206
15311
|
case "set-reasoning-effort": return {
|
|
15207
15312
|
kind: "topic",
|
|
15208
|
-
topic: await this.setReasoningEffort(request)
|
|
15313
|
+
topic: await this.setReasoningEffort(request, signal)
|
|
15209
15314
|
};
|
|
15210
15315
|
case "select-model": return {
|
|
15211
15316
|
kind: "topic",
|
|
15212
|
-
topic: await this.selectModel(request)
|
|
15317
|
+
topic: await this.selectModel(request, signal)
|
|
15213
15318
|
};
|
|
15214
15319
|
default: return request;
|
|
15215
15320
|
}
|
|
@@ -15220,10 +15325,19 @@ var TopicRuntime = class {
|
|
|
15220
15325
|
return this.disposal;
|
|
15221
15326
|
}
|
|
15222
15327
|
async disposeOwned() {
|
|
15223
|
-
this.
|
|
15328
|
+
this.beginClosing();
|
|
15224
15329
|
await this.ready.catch(() => void 0);
|
|
15225
15330
|
await this.releaseRuntime();
|
|
15226
15331
|
}
|
|
15332
|
+
beginClosing() {
|
|
15333
|
+
if (this.closed) return;
|
|
15334
|
+
this.closed = true;
|
|
15335
|
+
this.lifecycleAbort.abort(citeCiterShuttingDownError());
|
|
15336
|
+
}
|
|
15337
|
+
assertOpen(signal) {
|
|
15338
|
+
if (this.closed) throw citeCiterShuttingDownError();
|
|
15339
|
+
signal?.throwIfAborted();
|
|
15340
|
+
}
|
|
15227
15341
|
async start() {
|
|
15228
15342
|
try {
|
|
15229
15343
|
this.releaseLlm = this.runtime.provide("llm", this.host.llm);
|
|
@@ -15275,6 +15389,7 @@ var TopicRuntime = class {
|
|
|
15275
15389
|
this.fibers.push(await this.runtime.plugin(TopicTitleProvider));
|
|
15276
15390
|
this.fibers.push(await this.runtime.plugin(AgentLoop, { agents: [] }));
|
|
15277
15391
|
} catch (error) {
|
|
15392
|
+
this.beginClosing();
|
|
15278
15393
|
try {
|
|
15279
15394
|
await this.releaseRuntime();
|
|
15280
15395
|
} catch (cleanupError) {
|
|
@@ -15288,37 +15403,47 @@ var TopicRuntime = class {
|
|
|
15288
15403
|
return this.releasing;
|
|
15289
15404
|
}
|
|
15290
15405
|
async releaseOwnedRuntime() {
|
|
15291
|
-
await Promise.allSettled([...this.opening.values()]);
|
|
15292
|
-
this.opening.clear();
|
|
15293
15406
|
const failures = [];
|
|
15294
|
-
for (const pending of this.pendingQuestions.values()) {
|
|
15295
|
-
pending.signal?.removeEventListener("abort", pending.onAbort);
|
|
15296
|
-
pending.reject(new UserQuestionError("CiteCiter is shutting down", "ASK_ABORTED"));
|
|
15297
|
-
}
|
|
15298
|
-
this.pendingQuestions.clear();
|
|
15299
15407
|
try {
|
|
15300
15408
|
this.releaseQuestionProvider?.();
|
|
15301
15409
|
} catch (error) {
|
|
15302
15410
|
failures.push(error);
|
|
15303
15411
|
}
|
|
15304
15412
|
this.releaseQuestionProvider = void 0;
|
|
15413
|
+
for (const pending of this.pendingQuestions.values()) {
|
|
15414
|
+
pending.signal?.removeEventListener("abort", pending.onAbort);
|
|
15415
|
+
pending.reject(new UserQuestionError(CITECITER_SHUTTING_DOWN, "ASK_ABORTED"));
|
|
15416
|
+
}
|
|
15417
|
+
this.pendingQuestions.clear();
|
|
15418
|
+
const handleDisposals = [];
|
|
15305
15419
|
for (const handle of [...this.handles.values()]) try {
|
|
15306
|
-
|
|
15420
|
+
handleDisposals.push(handle.dispose().catch((error) => {
|
|
15421
|
+
failures.push(error);
|
|
15422
|
+
}));
|
|
15307
15423
|
} catch (error) {
|
|
15308
15424
|
failures.push(error);
|
|
15309
15425
|
}
|
|
15310
15426
|
this.handles.clear();
|
|
15427
|
+
await this.settleOwnedOperations();
|
|
15428
|
+
await Promise.all(handleDisposals);
|
|
15429
|
+
failures.push(...this.cleanupFailures.splice(0));
|
|
15311
15430
|
for (const fiber of this.fibers.splice(0).reverse()) try {
|
|
15312
15431
|
await fiber.dispose();
|
|
15313
15432
|
} catch (error) {
|
|
15314
15433
|
failures.push(error);
|
|
15315
15434
|
}
|
|
15316
|
-
|
|
15435
|
+
this.requests.clear();
|
|
15436
|
+
this.creations.clear();
|
|
15437
|
+
this.asks.clear();
|
|
15438
|
+
this.topicAdmissions.clear();
|
|
15439
|
+
this.modelChanges.clear();
|
|
15440
|
+
this.sourceAvailabilityChecks.clear();
|
|
15317
15441
|
this.titleRefreshes.clear();
|
|
15442
|
+
this.opening.clear();
|
|
15318
15443
|
for (const release of [
|
|
15319
15444
|
this.releaseSandboxPolicy,
|
|
15320
|
-
this.releaseFs,
|
|
15321
15445
|
this.releaseSubprocess,
|
|
15446
|
+
this.releaseFs,
|
|
15322
15447
|
this.releaseLlm
|
|
15323
15448
|
]) try {
|
|
15324
15449
|
await release?.();
|
|
@@ -15331,9 +15456,26 @@ var TopicRuntime = class {
|
|
|
15331
15456
|
this.releaseLlm = void 0;
|
|
15332
15457
|
if (failures.length > 0) throw new AggregateError(failures, "CiteCiter Topic runtime cleanup failed");
|
|
15333
15458
|
}
|
|
15334
|
-
async
|
|
15459
|
+
async settleOwnedOperations() {
|
|
15460
|
+
while (true) {
|
|
15461
|
+
const operations = /* @__PURE__ */ new Set([
|
|
15462
|
+
...this.requests,
|
|
15463
|
+
...[...this.creations.values()].map(({ result }) => result),
|
|
15464
|
+
...[...this.asks.values()].map(({ result }) => result),
|
|
15465
|
+
...this.topicAdmissions.values(),
|
|
15466
|
+
...this.modelChanges.values(),
|
|
15467
|
+
...this.sourceAvailabilityChecks.values(),
|
|
15468
|
+
...this.titleRefreshes.values(),
|
|
15469
|
+
...this.opening.values()
|
|
15470
|
+
]);
|
|
15471
|
+
if (operations.size === 0) return;
|
|
15472
|
+
await Promise.allSettled(operations);
|
|
15473
|
+
}
|
|
15474
|
+
}
|
|
15475
|
+
async create(request, signal) {
|
|
15335
15476
|
const sourceSessionId = createSourceSessionId(request);
|
|
15336
15477
|
const source = await this.host.sessionQuery.readSession(SessionId(sourceSessionId));
|
|
15478
|
+
this.assertOpen(signal);
|
|
15337
15479
|
this.sourceAvailability.set(sourceSessionId, true);
|
|
15338
15480
|
const validated = "selectionClaim" in request ? resolveObserverCitation(source, request.selectionClaim) : validateObserverCitation(source, request.citation);
|
|
15339
15481
|
const { topicId, directory } = await this.index.reserve(sourceSessionId);
|
|
@@ -15377,17 +15519,11 @@ var TopicRuntime = class {
|
|
|
15377
15519
|
};
|
|
15378
15520
|
let handle;
|
|
15379
15521
|
try {
|
|
15380
|
-
handle = await this.createHandle(metadata, mode.seed);
|
|
15522
|
+
handle = await this.createHandle(metadata, mode.seed, signal);
|
|
15381
15523
|
await this.runtime.sessions.flush(handle.agent.session);
|
|
15524
|
+
this.assertOpen(signal);
|
|
15382
15525
|
await this.index.save(metadata);
|
|
15383
|
-
handle.
|
|
15384
|
-
content: [{
|
|
15385
|
-
type: "text",
|
|
15386
|
-
text: request.question
|
|
15387
|
-
}],
|
|
15388
|
-
source: { kind: "user" }
|
|
15389
|
-
}));
|
|
15390
|
-
await this.runtime.sessions.flush(handle.agent.session);
|
|
15526
|
+
await this.commitFollowup(handle, identifiedQuestion(request.requestId, request.question), signal);
|
|
15391
15527
|
return this.snapshot(metadata);
|
|
15392
15528
|
} catch (error) {
|
|
15393
15529
|
try {
|
|
@@ -15405,16 +15541,66 @@ var TopicRuntime = class {
|
|
|
15405
15541
|
throw error;
|
|
15406
15542
|
}
|
|
15407
15543
|
}
|
|
15408
|
-
|
|
15544
|
+
/** Let a caller stop waiting without cancelling an accepted idempotent mutation. */
|
|
15545
|
+
waitForCaller(operation, signal) {
|
|
15546
|
+
if (signal === void 0) return operation;
|
|
15547
|
+
return new Promise((resolve, reject) => {
|
|
15548
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
15549
|
+
const onAbort = () => {
|
|
15550
|
+
cleanup();
|
|
15551
|
+
reject(signal.reason);
|
|
15552
|
+
};
|
|
15553
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
15554
|
+
if (signal.aborted) onAbort();
|
|
15555
|
+
operation.then((value) => {
|
|
15556
|
+
cleanup();
|
|
15557
|
+
resolve(value);
|
|
15558
|
+
}, (error) => {
|
|
15559
|
+
cleanup();
|
|
15560
|
+
reject(error);
|
|
15561
|
+
});
|
|
15562
|
+
});
|
|
15563
|
+
}
|
|
15564
|
+
createIdempotent(request, signal) {
|
|
15565
|
+
const key = `${createSourceSessionId(request)}\0${request.requestId}`;
|
|
15566
|
+
const pending = this.creations.get(key);
|
|
15567
|
+
const intent = JSON.stringify(request);
|
|
15568
|
+
if (pending !== void 0) {
|
|
15569
|
+
if (pending.intent !== intent) throw new Error("CiteCiter create requestId was reused for a different request");
|
|
15570
|
+
return this.waitForCaller(pending.result, signal);
|
|
15571
|
+
}
|
|
15572
|
+
const creation = Promise.resolve().then(() => {
|
|
15573
|
+
this.assertOpen(signal);
|
|
15574
|
+
return this.resumeOrCreate(request, signal);
|
|
15575
|
+
}).finally(() => this.creations.delete(key));
|
|
15576
|
+
this.creations.set(key, {
|
|
15577
|
+
intent,
|
|
15578
|
+
result: creation
|
|
15579
|
+
});
|
|
15580
|
+
return this.waitForCaller(creation, signal);
|
|
15581
|
+
}
|
|
15582
|
+
async resumeOrCreate(request, signal) {
|
|
15409
15583
|
const committed = (await this.index.list(createSourceSessionId(request))).find((topic) => topic.createRequestId === request.requestId);
|
|
15410
|
-
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
|
|
15414
|
-
|
|
15415
|
-
|
|
15584
|
+
this.assertOpen(signal);
|
|
15585
|
+
if (committed !== void 0) return this.queueTopicAdmission(committed.sessionId, async () => {
|
|
15586
|
+
const log = await this.readLog(committed, signal);
|
|
15587
|
+
const identified = postSeedUserQuestionById(log, request.requestId);
|
|
15588
|
+
const existingQuestion = identified ?? firstPostSeedUserQuestion(log);
|
|
15589
|
+
if (existingQuestion !== null && existingQuestion !== request.question) throw new Error("CiteCiter create requestId was reused for a different question");
|
|
15590
|
+
if (existingQuestion === null || identified !== null && committedPostSeedUserQuestionById(log, request.requestId) === null) {
|
|
15591
|
+
const handle = await this.ensureHandle(committed, signal);
|
|
15592
|
+
handle.agent.inbox.remove(MessageId(request.requestId));
|
|
15593
|
+
await this.commitFollowup(handle, identifiedQuestion(request.requestId, request.question), signal);
|
|
15594
|
+
} else {
|
|
15595
|
+
const live = this.handles.get(committed.sessionId)?.agent.session;
|
|
15596
|
+
if (live !== void 0) await this.runtime.sessions.flush(live);
|
|
15597
|
+
}
|
|
15598
|
+
return this.snapshot(committed);
|
|
15599
|
+
}, signal);
|
|
15600
|
+
return this.create(request, signal);
|
|
15416
15601
|
}
|
|
15417
|
-
async createHandle(metadata, seed) {
|
|
15602
|
+
async createHandle(metadata, seed, signal) {
|
|
15603
|
+
this.assertOpen(signal);
|
|
15418
15604
|
const handle = await this.runtime.agents.create({
|
|
15419
15605
|
sessionId: SessionId(metadata.sessionId),
|
|
15420
15606
|
...metadata.mode === "exact-fork" ? {
|
|
@@ -15430,8 +15616,13 @@ var TopicRuntime = class {
|
|
|
15430
15616
|
model: metadata.modelConfig.model,
|
|
15431
15617
|
...metadata.modelConfig.maxTokens === void 0 ? {} : { maxTokens: metadata.modelConfig.maxTokens }
|
|
15432
15618
|
},
|
|
15433
|
-
setup: (agentCtx) => this.setupAgent(agentCtx, metadata)
|
|
15619
|
+
setup: (agentCtx) => this.setupAgent(agentCtx, metadata),
|
|
15620
|
+
...signal === void 0 ? {} : { signal }
|
|
15434
15621
|
});
|
|
15622
|
+
if (this.closed || signal?.aborted === true) {
|
|
15623
|
+
await this.disposeLateHandle(handle);
|
|
15624
|
+
this.assertOpen(signal);
|
|
15625
|
+
}
|
|
15435
15626
|
this.handles.set(metadata.sessionId, handle);
|
|
15436
15627
|
return handle;
|
|
15437
15628
|
}
|
|
@@ -15644,12 +15835,13 @@ var TopicRuntime = class {
|
|
|
15644
15835
|
}],
|
|
15645
15836
|
presentationMeta: (_args, value) => ({ capturedThroughSeq: value.capturedThroughSeq })
|
|
15646
15837
|
},
|
|
15647
|
-
execute: async (args) => {
|
|
15838
|
+
execute: async (args, exec) => {
|
|
15648
15839
|
let source;
|
|
15649
15840
|
let sourceAvailable = true;
|
|
15650
15841
|
try {
|
|
15651
15842
|
source = await this.host.sessionQuery.readSession(SessionId(metadata.sourceSessionId));
|
|
15652
15843
|
} catch (error) {
|
|
15844
|
+
exec.signal.throwIfAborted();
|
|
15653
15845
|
sourceAvailable = false;
|
|
15654
15846
|
const agent = agentCtx.agent;
|
|
15655
15847
|
if (metadata.mode !== "exact-fork" || agent === void 0 || agent.session.header.seedLength === void 0) {
|
|
@@ -15661,12 +15853,14 @@ var TopicRuntime = class {
|
|
|
15661
15853
|
events: agent.session.events.slice(0, agent.session.header.seedLength)
|
|
15662
15854
|
};
|
|
15663
15855
|
}
|
|
15856
|
+
exec.signal.throwIfAborted();
|
|
15664
15857
|
await this.rememberSourceAvailability(metadata, sourceAvailable);
|
|
15665
|
-
const
|
|
15666
|
-
|
|
15667
|
-
|
|
15858
|
+
const result = formatSourceSessionRead(metadata.forkThroughSeq === null ? source : {
|
|
15859
|
+
...source,
|
|
15860
|
+
events: source.events.filter((event) => event.seq <= metadata.forkThroughSeq)
|
|
15861
|
+
}, {
|
|
15668
15862
|
...args.fromSeq === void 0 ? {} : { fromSeq: args.fromSeq },
|
|
15669
|
-
...throughSeq === void 0 ? {} : { throughSeq },
|
|
15863
|
+
...args.throughSeq === void 0 ? {} : { throughSeq: args.throughSeq },
|
|
15670
15864
|
includeReasoning: this.settings().includeSourceReasoning,
|
|
15671
15865
|
maxBytes: SOURCE_READ_MAX_BYTES
|
|
15672
15866
|
});
|
|
@@ -15685,7 +15879,8 @@ var TopicRuntime = class {
|
|
|
15685
15879
|
})
|
|
15686
15880
|
});
|
|
15687
15881
|
}
|
|
15688
|
-
async ensureHandle(metadata) {
|
|
15882
|
+
async ensureHandle(metadata, signal) {
|
|
15883
|
+
this.assertOpen(signal);
|
|
15689
15884
|
const existing = this.handles.get(metadata.sessionId);
|
|
15690
15885
|
if (existing !== void 0) return existing;
|
|
15691
15886
|
const pending = this.opening.get(metadata.sessionId);
|
|
@@ -15697,8 +15892,13 @@ var TopicRuntime = class {
|
|
|
15697
15892
|
model: metadata.modelConfig.model,
|
|
15698
15893
|
...metadata.modelConfig.maxTokens === void 0 ? {} : { maxTokens: metadata.modelConfig.maxTokens }
|
|
15699
15894
|
},
|
|
15700
|
-
setup: (agentCtx) => this.setupAgent(agentCtx, metadata)
|
|
15701
|
-
|
|
15895
|
+
setup: (agentCtx) => this.setupAgent(agentCtx, metadata),
|
|
15896
|
+
...signal === void 0 ? {} : { signal }
|
|
15897
|
+
}).then(async (handle) => {
|
|
15898
|
+
if (this.closed || signal?.aborted === true) {
|
|
15899
|
+
await this.disposeLateHandle(handle);
|
|
15900
|
+
this.assertOpen(signal);
|
|
15901
|
+
}
|
|
15702
15902
|
this.handles.set(metadata.sessionId, handle);
|
|
15703
15903
|
return handle;
|
|
15704
15904
|
}).finally(() => {
|
|
@@ -15707,15 +15907,86 @@ var TopicRuntime = class {
|
|
|
15707
15907
|
this.opening.set(metadata.sessionId, opening);
|
|
15708
15908
|
return opening;
|
|
15709
15909
|
}
|
|
15710
|
-
async
|
|
15910
|
+
async disposeLateHandle(handle) {
|
|
15911
|
+
try {
|
|
15912
|
+
await handle.dispose();
|
|
15913
|
+
} catch (error) {
|
|
15914
|
+
this.cleanupFailures.push(error);
|
|
15915
|
+
throw error;
|
|
15916
|
+
}
|
|
15917
|
+
}
|
|
15918
|
+
/** Resolve only after the accepted question is present in the durable model-input log. */
|
|
15919
|
+
async commitFollowup(handle, message, admissionSignal) {
|
|
15920
|
+
this.assertOpen(admissionSignal);
|
|
15921
|
+
const signal = this.lifecycleAbort.signal;
|
|
15922
|
+
await new Promise((resolveCommitted, rejectCommitted) => {
|
|
15923
|
+
let claimedTurn;
|
|
15924
|
+
let settled = false;
|
|
15925
|
+
let disposeClaim = () => {};
|
|
15926
|
+
let disposeDiscard = () => {};
|
|
15927
|
+
let disposeEvent = () => {};
|
|
15928
|
+
const onAbort = () => finish(signal?.reason ?? citeCiterShuttingDownError());
|
|
15929
|
+
const finish = (error) => {
|
|
15930
|
+
if (settled) return;
|
|
15931
|
+
settled = true;
|
|
15932
|
+
signal?.removeEventListener("abort", onAbort);
|
|
15933
|
+
disposeEvent();
|
|
15934
|
+
disposeDiscard();
|
|
15935
|
+
disposeClaim();
|
|
15936
|
+
if (error === void 0) resolveCommitted();
|
|
15937
|
+
else rejectCommitted(error);
|
|
15938
|
+
};
|
|
15939
|
+
disposeClaim = handle.agent.ctx.on("agent/inbox/claimed", ({ message: claimed, turn }) => {
|
|
15940
|
+
if (claimed.id === message.id) claimedTurn = turn;
|
|
15941
|
+
});
|
|
15942
|
+
disposeDiscard = handle.agent.ctx.on("agent/inbox/discarded", ({ message: discarded }) => {
|
|
15943
|
+
if (discarded.id === message.id) finish(/* @__PURE__ */ new Error("CiteCiter question was discarded before it became model input"));
|
|
15944
|
+
});
|
|
15945
|
+
disposeEvent = handle.agent.ctx.on("session/event", (session, event) => {
|
|
15946
|
+
if (session !== handle.agent.session) return;
|
|
15947
|
+
if (event.type === "user/message" && event.data.source.kind === "user" && event.data.id === message.id) {
|
|
15948
|
+
finish();
|
|
15949
|
+
return;
|
|
15950
|
+
}
|
|
15951
|
+
if (event.type === "turn/end" && event.data.turn === claimedTurn) finish(/* @__PURE__ */ new Error("CiteCiter question was not committed before its turn ended"));
|
|
15952
|
+
});
|
|
15953
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15954
|
+
if (signal?.aborted === true) {
|
|
15955
|
+
onAbort();
|
|
15956
|
+
return;
|
|
15957
|
+
}
|
|
15958
|
+
try {
|
|
15959
|
+
handle.agent.followup(message);
|
|
15960
|
+
} catch (error) {
|
|
15961
|
+
finish(error);
|
|
15962
|
+
}
|
|
15963
|
+
});
|
|
15964
|
+
await this.runtime.sessions.flush(handle.agent.session);
|
|
15965
|
+
}
|
|
15966
|
+
async ask(sessionId, question, requestId, signal) {
|
|
15711
15967
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
15712
|
-
|
|
15968
|
+
this.assertOpen(signal);
|
|
15969
|
+
if (requestId !== void 0) {
|
|
15970
|
+
const log = await this.readLog(metadata, signal);
|
|
15971
|
+
const existingQuestion = postSeedUserQuestionById(log, requestId);
|
|
15972
|
+
if (existingQuestion !== null) {
|
|
15973
|
+
if (existingQuestion !== question) throw new Error("CiteCiter ask requestId was reused for a different question");
|
|
15974
|
+
if (committedPostSeedUserQuestionById(log, requestId) !== null) {
|
|
15975
|
+
const live = this.handles.get(sessionId)?.agent.session;
|
|
15976
|
+
if (live !== void 0) await this.runtime.sessions.flush(live);
|
|
15977
|
+
return this.snapshot(metadata);
|
|
15978
|
+
}
|
|
15979
|
+
}
|
|
15980
|
+
}
|
|
15981
|
+
const handle = await this.ensureHandle(metadata, signal);
|
|
15982
|
+
if (requestId !== void 0) handle.agent.inbox.remove(MessageId(requestId));
|
|
15983
|
+
await this.commitFollowup(handle, requestId === void 0 ? createUserMessage({
|
|
15713
15984
|
content: [{
|
|
15714
15985
|
type: "text",
|
|
15715
15986
|
text: question
|
|
15716
15987
|
}],
|
|
15717
15988
|
source: { kind: "user" }
|
|
15718
|
-
}));
|
|
15989
|
+
}) : identifiedQuestion(requestId, question), signal);
|
|
15719
15990
|
const updated = {
|
|
15720
15991
|
...metadata,
|
|
15721
15992
|
updatedAt: Date.now()
|
|
@@ -15723,7 +15994,38 @@ var TopicRuntime = class {
|
|
|
15723
15994
|
await this.index.save(updated);
|
|
15724
15995
|
return this.snapshot(updated);
|
|
15725
15996
|
}
|
|
15997
|
+
async askIdempotent(request, signal) {
|
|
15998
|
+
if (request.requestId === void 0) return this.queueAsk(request, signal);
|
|
15999
|
+
const key = `${request.topicSessionId}\0${request.requestId}`;
|
|
16000
|
+
const existing = this.asks.get(key);
|
|
16001
|
+
if (existing !== void 0) {
|
|
16002
|
+
if (existing.question !== request.question) throw new Error("CiteCiter ask requestId was reused for a different question");
|
|
16003
|
+
return this.waitForCaller(existing.result, signal);
|
|
16004
|
+
}
|
|
16005
|
+
const result = this.queueAsk(request, signal).finally(() => this.asks.delete(key));
|
|
16006
|
+
this.asks.set(key, {
|
|
16007
|
+
question: request.question,
|
|
16008
|
+
result
|
|
16009
|
+
});
|
|
16010
|
+
return this.waitForCaller(result, signal);
|
|
16011
|
+
}
|
|
16012
|
+
queueAsk(request, signal) {
|
|
16013
|
+
return this.queueTopicAdmission(request.topicSessionId, () => this.ask(request.topicSessionId, request.question, request.requestId, signal), signal);
|
|
16014
|
+
}
|
|
16015
|
+
queueTopicAdmission(sessionId, operation, signal) {
|
|
16016
|
+
const result = (this.topicAdmissions.get(sessionId) ?? Promise.resolve()).then(() => {
|
|
16017
|
+
this.assertOpen(signal);
|
|
16018
|
+
return operation();
|
|
16019
|
+
});
|
|
16020
|
+
const settled = result.then(() => void 0, () => void 0);
|
|
16021
|
+
this.topicAdmissions.set(sessionId, settled);
|
|
16022
|
+
settled.then(() => {
|
|
16023
|
+
if (this.topicAdmissions.get(sessionId) === settled) this.topicAdmissions.delete(sessionId);
|
|
16024
|
+
});
|
|
16025
|
+
return result;
|
|
16026
|
+
}
|
|
15726
16027
|
askUser(request) {
|
|
16028
|
+
if (this.closed) throw new UserQuestionError(CITECITER_SHUTTING_DOWN, "ASK_ABORTED");
|
|
15727
16029
|
const sessionId = request.agent === void 0 ? void 0 : String(request.agent.session.header.id);
|
|
15728
16030
|
if (sessionId === void 0 || !this.handles.has(sessionId)) throw new UserQuestionError("CiteCiter cannot identify the asking Topic", "CALLER_NOT_LIVE");
|
|
15729
16031
|
if (this.pendingQuestions.has(sessionId)) throw new UserQuestionError("this Topic already has a pending question", "DUPLICATE_QUESTION");
|
|
@@ -15756,31 +16058,36 @@ var TopicRuntime = class {
|
|
|
15756
16058
|
if (request.signal?.aborted === true) onAbort();
|
|
15757
16059
|
});
|
|
15758
16060
|
}
|
|
15759
|
-
async answerQuestion(request) {
|
|
16061
|
+
async answerQuestion(request, signal) {
|
|
15760
16062
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
16063
|
+
this.assertOpen(signal);
|
|
15761
16064
|
const pending = this.pendingQuestions.get(request.topicSessionId);
|
|
15762
16065
|
if (pending === void 0 || pending.key !== request.key) throw new Error("这个提问已结束或已被替换");
|
|
15763
16066
|
pending.resolve(validatedQuestionAnswer(pending.questions, request.answer));
|
|
15764
16067
|
return this.snapshot(metadata);
|
|
15765
16068
|
}
|
|
15766
|
-
async cancelQuestion(sessionId, key) {
|
|
16069
|
+
async cancelQuestion(sessionId, key, signal) {
|
|
15767
16070
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16071
|
+
this.assertOpen(signal);
|
|
15768
16072
|
const pending = this.pendingQuestions.get(sessionId);
|
|
15769
16073
|
if (pending === void 0 || pending.key !== key) throw new Error("这个提问已结束或已被替换");
|
|
15770
16074
|
pending.reject(new UserQuestionError("the user cancelled ask_user_question", "ASK_CANCELLED"));
|
|
15771
16075
|
return this.snapshot(metadata);
|
|
15772
16076
|
}
|
|
15773
|
-
async stop(sessionId) {
|
|
16077
|
+
async stop(sessionId, signal) {
|
|
15774
16078
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16079
|
+
this.assertOpen(signal);
|
|
15775
16080
|
const agent = this.handles.get(sessionId)?.agent;
|
|
15776
16081
|
agent?.cancel({ kind: "user" });
|
|
15777
16082
|
await agent?.whenIdle();
|
|
15778
16083
|
if (agent !== void 0) await this.runtime.sessions.flush(agent.session);
|
|
15779
16084
|
return this.snapshot(metadata);
|
|
15780
16085
|
}
|
|
15781
|
-
async rename(sessionId, title) {
|
|
16086
|
+
async rename(sessionId, title, signal) {
|
|
15782
16087
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
15783
|
-
|
|
16088
|
+
this.assertOpen(signal);
|
|
16089
|
+
const handle = await this.ensureHandle(metadata, signal);
|
|
16090
|
+
this.assertOpen(signal);
|
|
15784
16091
|
const renamed = this.runtime.sessionTitle.rename(handle.agent.session, title);
|
|
15785
16092
|
await this.runtime.sessions.flush(handle.agent.session);
|
|
15786
16093
|
const updated = {
|
|
@@ -15793,25 +16100,29 @@ var TopicRuntime = class {
|
|
|
15793
16100
|
await this.index.save(updated);
|
|
15794
16101
|
return this.snapshot(updated);
|
|
15795
16102
|
}
|
|
15796
|
-
async archive(sessionId, archived) {
|
|
16103
|
+
async archive(sessionId, archived, signal) {
|
|
16104
|
+
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16105
|
+
this.assertOpen(signal);
|
|
15797
16106
|
const updated = {
|
|
15798
|
-
...
|
|
16107
|
+
...metadata,
|
|
15799
16108
|
archivedAt: archived ? Date.now() : null,
|
|
15800
16109
|
updatedAt: Date.now()
|
|
15801
16110
|
};
|
|
15802
16111
|
await this.index.save(updated);
|
|
15803
16112
|
return this.snapshot(updated);
|
|
15804
16113
|
}
|
|
15805
|
-
async delete(sessionId, confirmSessionId) {
|
|
16114
|
+
async delete(sessionId, confirmSessionId, signal) {
|
|
15806
16115
|
if (sessionId !== confirmSessionId) throw new Error("Topic deletion confirmation does not match the target Session");
|
|
15807
16116
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16117
|
+
this.assertOpen(signal);
|
|
15808
16118
|
const pending = this.opening.get(sessionId);
|
|
15809
16119
|
const handle = this.handles.get(sessionId) ?? (pending === void 0 ? void 0 : await pending);
|
|
16120
|
+
this.assertOpen(signal);
|
|
15810
16121
|
if (handle !== void 0) {
|
|
15811
16122
|
await handle.dispose();
|
|
15812
16123
|
this.handles.delete(sessionId);
|
|
15813
16124
|
}
|
|
15814
|
-
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(sessionId));
|
|
16125
|
+
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(sessionId), signal);
|
|
15815
16126
|
await this.removeSessionArtifact(inspection.meta);
|
|
15816
16127
|
await this.index.remove(metadata);
|
|
15817
16128
|
return sessionId;
|
|
@@ -15826,20 +16137,25 @@ var TopicRuntime = class {
|
|
|
15826
16137
|
}) !== void 0) await unlink(artifact.path);
|
|
15827
16138
|
await rmdirIfEmpty(resolve(artifact.path, ".."));
|
|
15828
16139
|
}
|
|
15829
|
-
enqueueModelChange(sessionId, apply) {
|
|
16140
|
+
enqueueModelChange(sessionId, apply, signal) {
|
|
15830
16141
|
const previous = this.modelChanges.get(sessionId);
|
|
15831
16142
|
let change;
|
|
15832
|
-
change = (previous === void 0 ? Promise.resolve() : previous.catch(() => void 0)).then(
|
|
16143
|
+
change = (previous === void 0 ? Promise.resolve() : previous.catch(() => void 0)).then(() => {
|
|
16144
|
+
this.assertOpen(signal);
|
|
16145
|
+
return apply();
|
|
16146
|
+
}).finally(() => {
|
|
15833
16147
|
if (this.modelChanges.get(sessionId) === change) this.modelChanges.delete(sessionId);
|
|
15834
16148
|
});
|
|
15835
16149
|
this.modelChanges.set(sessionId, change);
|
|
15836
16150
|
return change;
|
|
15837
16151
|
}
|
|
15838
|
-
setModelRoute(request) {
|
|
16152
|
+
setModelRoute(request, signal) {
|
|
15839
16153
|
return this.enqueueModelChange(request.topicSessionId, async () => {
|
|
15840
16154
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
15841
|
-
|
|
15842
|
-
await this.
|
|
16155
|
+
this.assertOpen(signal);
|
|
16156
|
+
await this.host.llm.resolveModelInfo(request.provider, request.model, signal);
|
|
16157
|
+
await this.ensureHandle(metadata, signal);
|
|
16158
|
+
this.assertOpen(signal);
|
|
15843
16159
|
const selection = this.selections.get(metadata.sessionId);
|
|
15844
16160
|
if (selection === void 0) throw new Error("Topic model selector is unavailable");
|
|
15845
16161
|
const modelConfig = {
|
|
@@ -15859,14 +16175,16 @@ var TopicRuntime = class {
|
|
|
15859
16175
|
model: request.model
|
|
15860
16176
|
};
|
|
15861
16177
|
return this.snapshot(updated);
|
|
15862
|
-
});
|
|
16178
|
+
}, signal);
|
|
15863
16179
|
}
|
|
15864
|
-
setReasoningEffort(request) {
|
|
16180
|
+
setReasoningEffort(request, signal) {
|
|
15865
16181
|
return this.enqueueModelChange(request.topicSessionId, async () => {
|
|
15866
16182
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
15867
|
-
|
|
16183
|
+
this.assertOpen(signal);
|
|
16184
|
+
const model = await this.host.llm.resolveModelInfo(metadata.modelConfig.provider, metadata.modelConfig.model, signal);
|
|
15868
16185
|
if (request.reasoningEffort !== null && model.reasoning?.efforts.some((effort) => String(effort.id) === request.reasoningEffort) !== true) throw new Error(`模型不支持思考强度 ${request.reasoningEffort}`);
|
|
15869
|
-
await this.ensureHandle(metadata);
|
|
16186
|
+
await this.ensureHandle(metadata, signal);
|
|
16187
|
+
this.assertOpen(signal);
|
|
15870
16188
|
const selection = this.selections.get(metadata.sessionId);
|
|
15871
16189
|
if (selection === void 0) throw new Error("Topic model selector is unavailable");
|
|
15872
16190
|
const modelConfig = { ...metadata.modelConfig };
|
|
@@ -15884,16 +16202,18 @@ var TopicRuntime = class {
|
|
|
15884
16202
|
...request.reasoningEffort === null ? {} : { reasoningEffort: ReasoningEffortId(request.reasoningEffort) }
|
|
15885
16203
|
};
|
|
15886
16204
|
return this.snapshot(updated);
|
|
15887
|
-
});
|
|
16205
|
+
}, signal);
|
|
15888
16206
|
}
|
|
15889
|
-
selectModel(request) {
|
|
15890
|
-
return this.enqueueModelChange(request.topicSessionId, () => this.applyModelSelection(request));
|
|
16207
|
+
selectModel(request, signal) {
|
|
16208
|
+
return this.enqueueModelChange(request.topicSessionId, () => this.applyModelSelection(request, signal), signal);
|
|
15891
16209
|
}
|
|
15892
|
-
async applyModelSelection(request) {
|
|
16210
|
+
async applyModelSelection(request, signal) {
|
|
15893
16211
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
15894
|
-
|
|
16212
|
+
this.assertOpen(signal);
|
|
16213
|
+
const model = await this.host.llm.resolveModelInfo(request.provider, request.model, signal);
|
|
15895
16214
|
if (request.reasoningEffort !== null && model.reasoning?.efforts.some((effort) => String(effort.id) === request.reasoningEffort) !== true) throw new Error(`模型不支持思考强度 ${request.reasoningEffort}`);
|
|
15896
|
-
await this.ensureHandle(metadata);
|
|
16215
|
+
await this.ensureHandle(metadata, signal);
|
|
16216
|
+
this.assertOpen(signal);
|
|
15897
16217
|
const selection = this.selections.get(metadata.sessionId);
|
|
15898
16218
|
if (selection === void 0) throw new Error("Topic model selector is unavailable");
|
|
15899
16219
|
const previousModelConfig = { ...metadata.modelConfig };
|
|
@@ -15916,13 +16236,16 @@ var TopicRuntime = class {
|
|
|
15916
16236
|
};
|
|
15917
16237
|
return this.snapshot(updated);
|
|
15918
16238
|
}
|
|
15919
|
-
async models() {
|
|
16239
|
+
async models(signal) {
|
|
15920
16240
|
const providers = [];
|
|
15921
16241
|
for (const provider of this.host.llm.listProviders()) {
|
|
16242
|
+
this.assertOpen(signal);
|
|
15922
16243
|
let catalog;
|
|
15923
16244
|
try {
|
|
15924
16245
|
catalog = await this.host.llm.listModels(provider.id);
|
|
16246
|
+
this.assertOpen(signal);
|
|
15925
16247
|
} catch (error) {
|
|
16248
|
+
signal?.throwIfAborted();
|
|
15926
16249
|
this.host.logger.warn(`CiteCiter could not list models for ${provider.id}`, error);
|
|
15927
16250
|
catalog = [];
|
|
15928
16251
|
}
|
|
@@ -15930,8 +16253,9 @@ var TopicRuntime = class {
|
|
|
15930
16253
|
for (const model of catalog) {
|
|
15931
16254
|
let resolved;
|
|
15932
16255
|
try {
|
|
15933
|
-
resolved = await this.host.llm.resolveModelInfo(provider.id, model.id);
|
|
16256
|
+
resolved = await this.host.llm.resolveModelInfo(provider.id, model.id, signal);
|
|
15934
16257
|
} catch (error) {
|
|
16258
|
+
signal?.throwIfAborted();
|
|
15935
16259
|
this.host.logger.warn(`CiteCiter could not resolve ${provider.id}/${model.id}`, error);
|
|
15936
16260
|
}
|
|
15937
16261
|
models.push({
|
|
@@ -15952,21 +16276,22 @@ var TopicRuntime = class {
|
|
|
15952
16276
|
}
|
|
15953
16277
|
return providers;
|
|
15954
16278
|
}
|
|
15955
|
-
async list(sourceSessionId, includeArchived) {
|
|
16279
|
+
async list(sourceSessionId, includeArchived, signal) {
|
|
15956
16280
|
const metadata = await this.index.list(sourceSessionId);
|
|
15957
|
-
|
|
16281
|
+
this.assertOpen(signal);
|
|
16282
|
+
return (await Promise.all(metadata.filter((topic) => includeArchived ? topic.archivedAt !== null : topic.archivedAt === null).map((topic) => this.summary(topic, signal)))).sort((left, right) => right.updatedAt - left.updatedAt);
|
|
15958
16283
|
}
|
|
15959
|
-
async summary(metadata) {
|
|
16284
|
+
async summary(metadata, signal) {
|
|
15960
16285
|
let current = metadata;
|
|
15961
16286
|
if (cachedTopicTitle(current) === null && !this.titleHydrated.has(current.sessionId)) {
|
|
16287
|
+
const log = await this.readLog(current, signal);
|
|
15962
16288
|
this.titleHydrated.add(current.sessionId);
|
|
15963
|
-
const log = await this.readLog(current);
|
|
15964
16289
|
const title = foldTopicTitle(current, log.events);
|
|
15965
16290
|
if (title !== void 0) current = await this.patchMetadata(current, {
|
|
15966
16291
|
cachedTitle: title.title,
|
|
15967
16292
|
cachedTitleSource: titleSourceKind(title),
|
|
15968
16293
|
cachedTitleEventSeq: title.eventSeq
|
|
15969
|
-
});
|
|
16294
|
+
}, signal);
|
|
15970
16295
|
}
|
|
15971
16296
|
return this.summaryFromMetadata(current);
|
|
15972
16297
|
}
|
|
@@ -15989,20 +16314,27 @@ var TopicRuntime = class {
|
|
|
15989
16314
|
modelConfig: metadata.modelConfig
|
|
15990
16315
|
};
|
|
15991
16316
|
}
|
|
15992
|
-
async
|
|
16317
|
+
async get(sessionId, signal) {
|
|
16318
|
+
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16319
|
+
this.assertOpen(signal);
|
|
16320
|
+
return this.snapshot(metadata, signal);
|
|
16321
|
+
}
|
|
16322
|
+
async readLog(metadata, signal) {
|
|
16323
|
+
if (signal !== void 0) this.assertOpen(signal);
|
|
15993
16324
|
const live = this.handles.get(metadata.sessionId)?.agent.session;
|
|
15994
16325
|
if (live !== void 0) return {
|
|
15995
16326
|
header: live.header,
|
|
15996
16327
|
events: live.events
|
|
15997
16328
|
};
|
|
15998
|
-
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(metadata.sessionId));
|
|
16329
|
+
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(metadata.sessionId), signal);
|
|
16330
|
+
if (signal !== void 0) this.assertOpen(signal);
|
|
15999
16331
|
return {
|
|
16000
16332
|
header: inspection.meta,
|
|
16001
16333
|
events: inspection.events
|
|
16002
16334
|
};
|
|
16003
16335
|
}
|
|
16004
16336
|
scheduleSourceAvailabilityCheck(metadata) {
|
|
16005
|
-
if (this.sourceAvailability.has(metadata.sourceSessionId) || this.sourceAvailabilityChecks.has(metadata.sourceSessionId)) return;
|
|
16337
|
+
if (this.closed || this.sourceAvailability.has(metadata.sourceSessionId) || this.sourceAvailabilityChecks.has(metadata.sourceSessionId)) return;
|
|
16006
16338
|
const check = (async () => {
|
|
16007
16339
|
let available = true;
|
|
16008
16340
|
try {
|
|
@@ -16026,10 +16358,10 @@ var TopicRuntime = class {
|
|
|
16026
16358
|
const latest = await this.index.loadBySessionId(metadata.sessionId);
|
|
16027
16359
|
if (latest.sourceAvailable !== available) await this.patchMetadata(latest, { sourceAvailable: available });
|
|
16028
16360
|
}
|
|
16029
|
-
async snapshot(metadata) {
|
|
16361
|
+
async snapshot(metadata, signal) {
|
|
16030
16362
|
let current = metadata;
|
|
16031
16363
|
this.scheduleSourceAvailabilityCheck(current);
|
|
16032
|
-
const log = await this.readLog(current);
|
|
16364
|
+
const log = await this.readLog(current, signal);
|
|
16033
16365
|
const title = foldTopicTitle(current, log.events);
|
|
16034
16366
|
const latest = log.events.at(-1)?.time ?? metadata.updatedAt;
|
|
16035
16367
|
const observedThroughSeq = latestObservedSeq(log.events);
|
|
@@ -16042,7 +16374,7 @@ var TopicRuntime = class {
|
|
|
16042
16374
|
cachedTitleSource,
|
|
16043
16375
|
cachedTitleEventSeq: title.eventSeq
|
|
16044
16376
|
}
|
|
16045
|
-
});
|
|
16377
|
+
}, signal);
|
|
16046
16378
|
if (title === void 0) this.scheduleExactTitleRefresh(current, log);
|
|
16047
16379
|
const pending = this.pendingQuestions.get(current.sessionId);
|
|
16048
16380
|
return {
|
|
@@ -16060,8 +16392,9 @@ var TopicRuntime = class {
|
|
|
16060
16392
|
}
|
|
16061
16393
|
};
|
|
16062
16394
|
}
|
|
16063
|
-
async patchMetadata(metadata, patch) {
|
|
16395
|
+
async patchMetadata(metadata, patch, signal) {
|
|
16064
16396
|
const latest = await this.index.loadBySessionId(metadata.sessionId);
|
|
16397
|
+
if (signal !== void 0) this.assertOpen(signal);
|
|
16065
16398
|
const updated = topicMetadataSchema.parse({
|
|
16066
16399
|
...latest,
|
|
16067
16400
|
...patch
|
|
@@ -16070,20 +16403,22 @@ var TopicRuntime = class {
|
|
|
16070
16403
|
return updated;
|
|
16071
16404
|
}
|
|
16072
16405
|
scheduleExactTitleRefresh(metadata, log) {
|
|
16073
|
-
if (metadata.mode !== "exact-fork" || this.titleRefreshAttempted.has(metadata.sessionId) || this.handles.get(metadata.sessionId)?.agent.status === "running") return;
|
|
16406
|
+
if (this.closed || metadata.mode !== "exact-fork" || this.titleRefreshAttempted.has(metadata.sessionId) || this.handles.get(metadata.sessionId)?.agent.status === "running") return;
|
|
16074
16407
|
const postSeed = log.events.slice(log.header.seedLength ?? 0);
|
|
16075
16408
|
if (!postSeed.some((event) => event.type === "request/header") || !postSeed.some((event) => event.type === "assistant/message")) return;
|
|
16076
16409
|
const handle = this.handles.get(metadata.sessionId);
|
|
16077
16410
|
if (handle === void 0) return;
|
|
16078
16411
|
this.titleRefreshAttempted.add(metadata.sessionId);
|
|
16079
|
-
const refresh = this.runtime.sessionTitle.refresh(handle.agent.session).then(async (title) => {
|
|
16412
|
+
const refresh = this.runtime.sessionTitle.refresh(handle.agent.session, this.lifecycleAbort.signal).then(async (title) => {
|
|
16413
|
+
this.assertOpen(this.lifecycleAbort.signal);
|
|
16080
16414
|
await this.runtime.sessions.flush(handle.agent.session);
|
|
16415
|
+
this.assertOpen(this.lifecycleAbort.signal);
|
|
16081
16416
|
if (title === void 0 || title.eventSeq <= (metadata.forkThroughSeq ?? -1)) return;
|
|
16082
16417
|
await this.patchMetadata(metadata, {
|
|
16083
16418
|
cachedTitle: title.title,
|
|
16084
16419
|
cachedTitleSource: titleSourceKind(title),
|
|
16085
16420
|
cachedTitleEventSeq: title.eventSeq
|
|
16086
|
-
});
|
|
16421
|
+
}, this.lifecycleAbort.signal);
|
|
16087
16422
|
}).catch((error) => {
|
|
16088
16423
|
if (!this.closed) this.host.logger.warn(`CiteCiter could not title Topic ${metadata.sessionId}`, error);
|
|
16089
16424
|
}).finally(() => {
|
|
@@ -16200,8 +16535,8 @@ let CiteCiterHost = (() => {
|
|
|
16200
16535
|
await this.topics.initialize();
|
|
16201
16536
|
}
|
|
16202
16537
|
/** Validate and execute one strict Topic command. */
|
|
16203
|
-
async request(rawRequest) {
|
|
16204
|
-
return this.topics.request(citeCiterRequestSchema.parse(rawRequest));
|
|
16538
|
+
async request(rawRequest, signal) {
|
|
16539
|
+
return this.topics.request(citeCiterRequestSchema.parse(rawRequest), signal);
|
|
16205
16540
|
}
|
|
16206
16541
|
};
|
|
16207
16542
|
})();
|