@kirkchinese/dsh-citeciter 0.4.0 → 0.4.2
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 +492 -110
- 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 +27 -24
- 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";
|
|
@@ -14369,7 +14369,8 @@ function compactMapped(units) {
|
|
|
14369
14369
|
});
|
|
14370
14370
|
else compact[compact.length - 1] = {
|
|
14371
14371
|
...previous,
|
|
14372
|
-
endOffset: unit.endOffset
|
|
14372
|
+
endOffset: unit.endOffset,
|
|
14373
|
+
...previous.synthetic === true || unit.synthetic === true ? { synthetic: true } : {}
|
|
14373
14374
|
};
|
|
14374
14375
|
}
|
|
14375
14376
|
return compact;
|
|
@@ -14384,7 +14385,8 @@ function joinMapped(parts, separator) {
|
|
|
14384
14385
|
for (const text of separator) joined.push({
|
|
14385
14386
|
text,
|
|
14386
14387
|
startOffset: before,
|
|
14387
|
-
endOffset: after
|
|
14388
|
+
endOffset: after,
|
|
14389
|
+
synthetic: true
|
|
14388
14390
|
});
|
|
14389
14391
|
}
|
|
14390
14392
|
joined.push(...part);
|
|
@@ -14442,6 +14444,47 @@ function candidatesFromProjection(markdown, projection, needle) {
|
|
|
14442
14444
|
}
|
|
14443
14445
|
return candidates;
|
|
14444
14446
|
}
|
|
14447
|
+
function candidatesIgnoringSyntheticWhitespace(markdown, projection, needle) {
|
|
14448
|
+
const rendered = visibleText(projection);
|
|
14449
|
+
const visibleOffsets = [0];
|
|
14450
|
+
for (const unit of projection) visibleOffsets.push(visibleOffsets.at(-1) + unit.text.length);
|
|
14451
|
+
const candidates = [];
|
|
14452
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14453
|
+
for (let start = 0; start < projection.length; start++) {
|
|
14454
|
+
const first = projection[start];
|
|
14455
|
+
if (first.synthetic === true) continue;
|
|
14456
|
+
let unitIndex = start;
|
|
14457
|
+
let needleOffset = 0;
|
|
14458
|
+
let lastIndex = -1;
|
|
14459
|
+
while (needleOffset < needle.length && unitIndex < projection.length) {
|
|
14460
|
+
const unit = projection[unitIndex];
|
|
14461
|
+
if (unit.synthetic === true) {
|
|
14462
|
+
while (projection[unitIndex]?.synthetic === true) unitIndex++;
|
|
14463
|
+
needleOffset += /^\s+/u.exec(needle.slice(needleOffset))?.[0].length ?? 0;
|
|
14464
|
+
continue;
|
|
14465
|
+
}
|
|
14466
|
+
if (!needle.startsWith(unit.text, needleOffset)) break;
|
|
14467
|
+
needleOffset += unit.text.length;
|
|
14468
|
+
lastIndex = unitIndex;
|
|
14469
|
+
unitIndex++;
|
|
14470
|
+
}
|
|
14471
|
+
if (needleOffset !== needle.length || lastIndex < start) continue;
|
|
14472
|
+
const last = projection[lastIndex];
|
|
14473
|
+
const key = `${first.startOffset}:${last.endOffset}`;
|
|
14474
|
+
if (seen.has(key)) continue;
|
|
14475
|
+
seen.add(key);
|
|
14476
|
+
const visibleStart = visibleOffsets[start];
|
|
14477
|
+
const visibleEnd = visibleOffsets[lastIndex + 1];
|
|
14478
|
+
candidates.push({
|
|
14479
|
+
startOffset: first.startOffset,
|
|
14480
|
+
endOffset: last.endOffset,
|
|
14481
|
+
sourceText: markdown.slice(first.startOffset, last.endOffset),
|
|
14482
|
+
displayPrefix: rendered.slice(Math.max(0, visibleStart - 240), visibleStart),
|
|
14483
|
+
displaySuffix: rendered.slice(visibleEnd, visibleEnd + 240)
|
|
14484
|
+
});
|
|
14485
|
+
}
|
|
14486
|
+
return candidates;
|
|
14487
|
+
}
|
|
14445
14488
|
/**
|
|
14446
14489
|
* Locate every GFM source range that renders as one browser-visible selection.
|
|
14447
14490
|
*
|
|
@@ -14458,7 +14501,11 @@ function markdownSourceCandidates(markdown, displayText) {
|
|
|
14458
14501
|
if (needle === "") return [];
|
|
14459
14502
|
const exact = candidatesFromProjection(markdown, projection, needle);
|
|
14460
14503
|
if (exact.length > 0) return exact;
|
|
14461
|
-
|
|
14504
|
+
const compactProjection = compactMapped(projection);
|
|
14505
|
+
const compactNeedle = needle.replace(/\s+/gu, " ");
|
|
14506
|
+
const compact = candidatesFromProjection(markdown, compactProjection, compactNeedle);
|
|
14507
|
+
if (compact.length > 0) return compact;
|
|
14508
|
+
return candidatesIgnoringSyntheticWhitespace(markdown, compactProjection, compactNeedle);
|
|
14462
14509
|
}
|
|
14463
14510
|
//#endregion
|
|
14464
14511
|
//#region lib/types/citation-mapping.js
|
|
@@ -14641,12 +14688,18 @@ function formatSourceSessionRead(source, options) {
|
|
|
14641
14688
|
if (!Number.isSafeInteger(fromSeq) || fromSeq < 0) throw new Error("fromSeq must be a non-negative safe integer");
|
|
14642
14689
|
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
14690
|
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 2) throw new Error("maxBytes must be a safe integer of at least 2");
|
|
14644
|
-
|
|
14691
|
+
let availableThroughSeq = null;
|
|
14692
|
+
for (const event of source.events) {
|
|
14693
|
+
if (options.throughSeq !== void 0 && event.seq > options.throughSeq) break;
|
|
14694
|
+
availableThroughSeq = event.seq;
|
|
14695
|
+
}
|
|
14645
14696
|
const events = [];
|
|
14646
14697
|
let bytesUsed = 2;
|
|
14647
14698
|
let capturedThroughSeq = null;
|
|
14648
14699
|
let truncated = false;
|
|
14649
|
-
for (
|
|
14700
|
+
for (let index = 0; index < source.events.length; index += 1) {
|
|
14701
|
+
const event = source.events[index];
|
|
14702
|
+
if (event === void 0) continue;
|
|
14650
14703
|
if (event.seq < fromSeq) continue;
|
|
14651
14704
|
if (options.throughSeq !== void 0 && event.seq > options.throughSeq) break;
|
|
14652
14705
|
const formatted = formatEvidenceEvent(event, options.includeReasoning);
|
|
@@ -14654,10 +14707,33 @@ function formatSourceSessionRead(source, options) {
|
|
|
14654
14707
|
capturedThroughSeq = event.seq;
|
|
14655
14708
|
continue;
|
|
14656
14709
|
}
|
|
14657
|
-
const
|
|
14710
|
+
const serializedBytes = Buffer.byteLength(JSON.stringify(formatted), "utf8");
|
|
14711
|
+
const eventBytes = serializedBytes + (events.length === 0 ? 0 : 1);
|
|
14658
14712
|
if (bytesUsed + eventBytes > options.maxBytes) {
|
|
14659
|
-
|
|
14660
|
-
|
|
14713
|
+
if (serializedBytes <= options.maxBytes - 2) {
|
|
14714
|
+
truncated = true;
|
|
14715
|
+
break;
|
|
14716
|
+
}
|
|
14717
|
+
const placeholder = evidence({
|
|
14718
|
+
type: event.type,
|
|
14719
|
+
seq: event.seq,
|
|
14720
|
+
oversized: true
|
|
14721
|
+
});
|
|
14722
|
+
const serializedPlaceholderBytes = Buffer.byteLength(JSON.stringify(placeholder), "utf8");
|
|
14723
|
+
const placeholderBytes = serializedPlaceholderBytes + (events.length === 0 ? 0 : 1);
|
|
14724
|
+
if (bytesUsed + placeholderBytes > options.maxBytes) {
|
|
14725
|
+
if (serializedPlaceholderBytes <= options.maxBytes - 2) {
|
|
14726
|
+
truncated = true;
|
|
14727
|
+
break;
|
|
14728
|
+
}
|
|
14729
|
+
capturedThroughSeq = event.seq;
|
|
14730
|
+
truncated = true;
|
|
14731
|
+
break;
|
|
14732
|
+
}
|
|
14733
|
+
events.push(placeholder);
|
|
14734
|
+
bytesUsed += placeholderBytes;
|
|
14735
|
+
capturedThroughSeq = event.seq;
|
|
14736
|
+
continue;
|
|
14661
14737
|
}
|
|
14662
14738
|
events.push(formatted);
|
|
14663
14739
|
bytesUsed += eventBytes;
|
|
@@ -14694,6 +14770,10 @@ const TOPIC_TITLE_CONFIG = resolveSessionTitleLlmConfig({
|
|
|
14694
14770
|
maxOutputTokens: 64,
|
|
14695
14771
|
timeoutMs: 6e4
|
|
14696
14772
|
});
|
|
14773
|
+
const CITECITER_SHUTTING_DOWN = "CiteCiter is shutting down";
|
|
14774
|
+
function citeCiterShuttingDownError() {
|
|
14775
|
+
return /* @__PURE__ */ new Error(CITECITER_SHUTTING_DOWN);
|
|
14776
|
+
}
|
|
14697
14777
|
/** Decide both model visibility and execution access for one private Topic tool. */
|
|
14698
14778
|
function citeCiterToolAvailable(name, allowSourceFiles) {
|
|
14699
14779
|
return ALWAYS_AVAILABLE_TOOLS.has(name) || allowSourceFiles && SOURCE_FILE_TOOLS.has(name);
|
|
@@ -15049,6 +15129,54 @@ function topicMessages(log) {
|
|
|
15049
15129
|
error
|
|
15050
15130
|
};
|
|
15051
15131
|
}
|
|
15132
|
+
/**
|
|
15133
|
+
* Return the first genuine Topic question after any Exact Fork seed.
|
|
15134
|
+
* @param log - private Topic Session contents.
|
|
15135
|
+
* @returns the first post-seed question, or `null` when it has not been committed.
|
|
15136
|
+
*/
|
|
15137
|
+
function firstPostSeedUserQuestion(log) {
|
|
15138
|
+
for (const event of log.events.slice(log.header.seedLength ?? 0)) {
|
|
15139
|
+
if (event.type !== "user/message" || event.data.source.kind !== "user") continue;
|
|
15140
|
+
const text = textBlocks(event.data.content, "text");
|
|
15141
|
+
if (text !== "") return text;
|
|
15142
|
+
}
|
|
15143
|
+
for (const message of pendingPostSeedUserMessages(log)) {
|
|
15144
|
+
if (message.source.kind !== "user") continue;
|
|
15145
|
+
const text = textBlocks(message.content, "text");
|
|
15146
|
+
if (text !== "") return text;
|
|
15147
|
+
}
|
|
15148
|
+
return null;
|
|
15149
|
+
}
|
|
15150
|
+
function pendingPostSeedUserMessages(log) {
|
|
15151
|
+
const pending = {
|
|
15152
|
+
"next-turn": [],
|
|
15153
|
+
"next-step": []
|
|
15154
|
+
};
|
|
15155
|
+
for (const event of log.events.slice(log.header.seedLength ?? 0)) {
|
|
15156
|
+
if (event.type !== "agent/inbox/spliced") continue;
|
|
15157
|
+
pending[event.data.target].splice(event.data.start, event.data.removedCount ?? 0, ...event.data.inserted);
|
|
15158
|
+
}
|
|
15159
|
+
return [...pending["next-step"], ...pending["next-turn"]];
|
|
15160
|
+
}
|
|
15161
|
+
/**
|
|
15162
|
+
* Find a post-seed user question by its durable message identifier.
|
|
15163
|
+
* @param log - private Topic Session contents.
|
|
15164
|
+
* @param messageId - request identity stored as the user-message identity.
|
|
15165
|
+
* @returns the matching question, or `null` when the request is not committed.
|
|
15166
|
+
*/
|
|
15167
|
+
function postSeedUserQuestionById(log, messageId) {
|
|
15168
|
+
const committed = committedPostSeedUserQuestionById(log, messageId);
|
|
15169
|
+
if (committed !== null) return committed;
|
|
15170
|
+
const pending = pendingPostSeedUserMessages(log).find((message) => message.source.kind === "user" && String(message.id) === messageId);
|
|
15171
|
+
return pending === void 0 ? null : textBlocks(pending.content, "text");
|
|
15172
|
+
}
|
|
15173
|
+
function committedPostSeedUserQuestionById(log, messageId) {
|
|
15174
|
+
for (const event of log.events.slice(log.header.seedLength ?? 0)) {
|
|
15175
|
+
if (event.type !== "user/message" || event.data.source.kind !== "user" || String(event.data.id) !== messageId) continue;
|
|
15176
|
+
return textBlocks(event.data.content, "text");
|
|
15177
|
+
}
|
|
15178
|
+
return null;
|
|
15179
|
+
}
|
|
15052
15180
|
function titleSourceKind(value) {
|
|
15053
15181
|
if (value === void 0) return null;
|
|
15054
15182
|
return value.source.kind === "fallback" || value.source.kind === "provider" || value.source.kind === "user" ? value.source.kind : null;
|
|
@@ -15110,18 +15238,34 @@ function resolveTopicModeAndSeed(requested, source, anchorSeq) {
|
|
|
15110
15238
|
function createSourceSessionId(request) {
|
|
15111
15239
|
return "selectionClaim" in request ? request.selectionClaim.sourceSessionId : request.citation.sourceSessionId;
|
|
15112
15240
|
}
|
|
15241
|
+
function identifiedQuestion(requestId, question) {
|
|
15242
|
+
return freezeMessage({
|
|
15243
|
+
id: MessageId(requestId),
|
|
15244
|
+
role: "user",
|
|
15245
|
+
content: [{
|
|
15246
|
+
type: "text",
|
|
15247
|
+
text: question
|
|
15248
|
+
}],
|
|
15249
|
+
source: { kind: "user" }
|
|
15250
|
+
});
|
|
15251
|
+
}
|
|
15113
15252
|
/** One process-local private DSH tree with standard Session logs and Agent loop. */
|
|
15114
15253
|
var TopicRuntime = class {
|
|
15115
15254
|
host;
|
|
15116
15255
|
settings;
|
|
15117
15256
|
runtime = new Context();
|
|
15118
15257
|
index = new TopicIndex();
|
|
15258
|
+
lifecycleAbort = new AbortController();
|
|
15119
15259
|
fibers = [];
|
|
15120
15260
|
handles = /* @__PURE__ */ new Map();
|
|
15121
15261
|
selections = /* @__PURE__ */ new Map();
|
|
15122
15262
|
opening = /* @__PURE__ */ new Map();
|
|
15263
|
+
requests = /* @__PURE__ */ new Set();
|
|
15264
|
+
cleanupFailures = [];
|
|
15123
15265
|
pendingQuestions = /* @__PURE__ */ new Map();
|
|
15124
15266
|
creations = /* @__PURE__ */ new Map();
|
|
15267
|
+
asks = /* @__PURE__ */ new Map();
|
|
15268
|
+
topicAdmissions = /* @__PURE__ */ new Map();
|
|
15125
15269
|
modelChanges = /* @__PURE__ */ new Map();
|
|
15126
15270
|
titleRefreshes = /* @__PURE__ */ new Map();
|
|
15127
15271
|
titleRefreshAttempted = /* @__PURE__ */ new Set();
|
|
@@ -15150,66 +15294,74 @@ var TopicRuntime = class {
|
|
|
15150
15294
|
return this.ready;
|
|
15151
15295
|
}
|
|
15152
15296
|
/** Execute one validated browser command against private Topics. */
|
|
15153
|
-
async request(rawRequest) {
|
|
15297
|
+
async request(rawRequest, callerSignal) {
|
|
15154
15298
|
const request = citeCiterRequestSchema.parse(rawRequest);
|
|
15155
15299
|
await this.ready;
|
|
15156
|
-
|
|
15300
|
+
const signal = AbortSignal.any([this.lifecycleAbort.signal, callerSignal]);
|
|
15301
|
+
this.assertOpen(signal);
|
|
15302
|
+
const operation = this.executeRequest(request, signal);
|
|
15303
|
+
this.requests.add(operation);
|
|
15304
|
+
operation.then(() => this.requests.delete(operation), () => this.requests.delete(operation));
|
|
15305
|
+
return operation;
|
|
15306
|
+
}
|
|
15307
|
+
async executeRequest(request, signal) {
|
|
15308
|
+
this.assertOpen(signal);
|
|
15157
15309
|
switch (request.action) {
|
|
15158
15310
|
case "create": return {
|
|
15159
15311
|
kind: "topic",
|
|
15160
|
-
topic: await this.createIdempotent(request)
|
|
15312
|
+
topic: await this.createIdempotent(request, signal)
|
|
15161
15313
|
};
|
|
15162
15314
|
case "list": return {
|
|
15163
15315
|
kind: "topics",
|
|
15164
|
-
topics: await this.list(request.sourceSessionId, request.includeArchived ?? false)
|
|
15316
|
+
topics: await this.list(request.sourceSessionId, request.includeArchived ?? false, signal)
|
|
15165
15317
|
};
|
|
15166
15318
|
case "get": return {
|
|
15167
15319
|
kind: "topic",
|
|
15168
|
-
topic: await this.
|
|
15320
|
+
topic: await this.get(request.topicSessionId, signal)
|
|
15169
15321
|
};
|
|
15170
15322
|
case "ask": return {
|
|
15171
15323
|
kind: "topic",
|
|
15172
|
-
topic: await this.
|
|
15324
|
+
topic: await this.askIdempotent(request, signal)
|
|
15173
15325
|
};
|
|
15174
15326
|
case "stop": return {
|
|
15175
15327
|
kind: "topic",
|
|
15176
|
-
topic: await this.stop(request.topicSessionId)
|
|
15328
|
+
topic: await this.stop(request.topicSessionId, signal)
|
|
15177
15329
|
};
|
|
15178
15330
|
case "answer-question": return {
|
|
15179
15331
|
kind: "topic",
|
|
15180
|
-
topic: await this.answerQuestion(request)
|
|
15332
|
+
topic: await this.answerQuestion(request, signal)
|
|
15181
15333
|
};
|
|
15182
15334
|
case "cancel-question": return {
|
|
15183
15335
|
kind: "topic",
|
|
15184
|
-
topic: await this.cancelQuestion(request.topicSessionId, request.key)
|
|
15336
|
+
topic: await this.cancelQuestion(request.topicSessionId, request.key, signal)
|
|
15185
15337
|
};
|
|
15186
15338
|
case "rename": return {
|
|
15187
15339
|
kind: "topic",
|
|
15188
|
-
topic: await this.rename(request.topicSessionId, request.title)
|
|
15340
|
+
topic: await this.rename(request.topicSessionId, request.title, signal)
|
|
15189
15341
|
};
|
|
15190
15342
|
case "archive": return {
|
|
15191
15343
|
kind: "topic",
|
|
15192
|
-
topic: await this.archive(request.topicSessionId, request.archived)
|
|
15344
|
+
topic: await this.archive(request.topicSessionId, request.archived, signal)
|
|
15193
15345
|
};
|
|
15194
15346
|
case "delete": return {
|
|
15195
15347
|
kind: "deleted",
|
|
15196
|
-
sessionId: await this.delete(request.topicSessionId, request.confirmSessionId)
|
|
15348
|
+
sessionId: await this.delete(request.topicSessionId, request.confirmSessionId, signal)
|
|
15197
15349
|
};
|
|
15198
15350
|
case "models": return {
|
|
15199
15351
|
kind: "models",
|
|
15200
|
-
providers: await this.models()
|
|
15352
|
+
providers: await this.models(signal)
|
|
15201
15353
|
};
|
|
15202
15354
|
case "set-model-route": return {
|
|
15203
15355
|
kind: "topic",
|
|
15204
|
-
topic: await this.setModelRoute(request)
|
|
15356
|
+
topic: await this.setModelRoute(request, signal)
|
|
15205
15357
|
};
|
|
15206
15358
|
case "set-reasoning-effort": return {
|
|
15207
15359
|
kind: "topic",
|
|
15208
|
-
topic: await this.setReasoningEffort(request)
|
|
15360
|
+
topic: await this.setReasoningEffort(request, signal)
|
|
15209
15361
|
};
|
|
15210
15362
|
case "select-model": return {
|
|
15211
15363
|
kind: "topic",
|
|
15212
|
-
topic: await this.selectModel(request)
|
|
15364
|
+
topic: await this.selectModel(request, signal)
|
|
15213
15365
|
};
|
|
15214
15366
|
default: return request;
|
|
15215
15367
|
}
|
|
@@ -15220,10 +15372,19 @@ var TopicRuntime = class {
|
|
|
15220
15372
|
return this.disposal;
|
|
15221
15373
|
}
|
|
15222
15374
|
async disposeOwned() {
|
|
15223
|
-
this.
|
|
15375
|
+
this.beginClosing();
|
|
15224
15376
|
await this.ready.catch(() => void 0);
|
|
15225
15377
|
await this.releaseRuntime();
|
|
15226
15378
|
}
|
|
15379
|
+
beginClosing() {
|
|
15380
|
+
if (this.closed) return;
|
|
15381
|
+
this.closed = true;
|
|
15382
|
+
this.lifecycleAbort.abort(citeCiterShuttingDownError());
|
|
15383
|
+
}
|
|
15384
|
+
assertOpen(signal) {
|
|
15385
|
+
if (this.closed) throw citeCiterShuttingDownError();
|
|
15386
|
+
signal?.throwIfAborted();
|
|
15387
|
+
}
|
|
15227
15388
|
async start() {
|
|
15228
15389
|
try {
|
|
15229
15390
|
this.releaseLlm = this.runtime.provide("llm", this.host.llm);
|
|
@@ -15275,6 +15436,7 @@ var TopicRuntime = class {
|
|
|
15275
15436
|
this.fibers.push(await this.runtime.plugin(TopicTitleProvider));
|
|
15276
15437
|
this.fibers.push(await this.runtime.plugin(AgentLoop, { agents: [] }));
|
|
15277
15438
|
} catch (error) {
|
|
15439
|
+
this.beginClosing();
|
|
15278
15440
|
try {
|
|
15279
15441
|
await this.releaseRuntime();
|
|
15280
15442
|
} catch (cleanupError) {
|
|
@@ -15288,37 +15450,47 @@ var TopicRuntime = class {
|
|
|
15288
15450
|
return this.releasing;
|
|
15289
15451
|
}
|
|
15290
15452
|
async releaseOwnedRuntime() {
|
|
15291
|
-
await Promise.allSettled([...this.opening.values()]);
|
|
15292
|
-
this.opening.clear();
|
|
15293
15453
|
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
15454
|
try {
|
|
15300
15455
|
this.releaseQuestionProvider?.();
|
|
15301
15456
|
} catch (error) {
|
|
15302
15457
|
failures.push(error);
|
|
15303
15458
|
}
|
|
15304
15459
|
this.releaseQuestionProvider = void 0;
|
|
15460
|
+
for (const pending of this.pendingQuestions.values()) {
|
|
15461
|
+
pending.signal?.removeEventListener("abort", pending.onAbort);
|
|
15462
|
+
pending.reject(new UserQuestionError(CITECITER_SHUTTING_DOWN, "ASK_ABORTED"));
|
|
15463
|
+
}
|
|
15464
|
+
this.pendingQuestions.clear();
|
|
15465
|
+
const handleDisposals = [];
|
|
15305
15466
|
for (const handle of [...this.handles.values()]) try {
|
|
15306
|
-
|
|
15467
|
+
handleDisposals.push(handle.dispose().catch((error) => {
|
|
15468
|
+
failures.push(error);
|
|
15469
|
+
}));
|
|
15307
15470
|
} catch (error) {
|
|
15308
15471
|
failures.push(error);
|
|
15309
15472
|
}
|
|
15310
15473
|
this.handles.clear();
|
|
15474
|
+
await this.settleOwnedOperations();
|
|
15475
|
+
await Promise.all(handleDisposals);
|
|
15476
|
+
failures.push(...this.cleanupFailures.splice(0));
|
|
15311
15477
|
for (const fiber of this.fibers.splice(0).reverse()) try {
|
|
15312
15478
|
await fiber.dispose();
|
|
15313
15479
|
} catch (error) {
|
|
15314
15480
|
failures.push(error);
|
|
15315
15481
|
}
|
|
15316
|
-
|
|
15482
|
+
this.requests.clear();
|
|
15483
|
+
this.creations.clear();
|
|
15484
|
+
this.asks.clear();
|
|
15485
|
+
this.topicAdmissions.clear();
|
|
15486
|
+
this.modelChanges.clear();
|
|
15487
|
+
this.sourceAvailabilityChecks.clear();
|
|
15317
15488
|
this.titleRefreshes.clear();
|
|
15489
|
+
this.opening.clear();
|
|
15318
15490
|
for (const release of [
|
|
15319
15491
|
this.releaseSandboxPolicy,
|
|
15320
|
-
this.releaseFs,
|
|
15321
15492
|
this.releaseSubprocess,
|
|
15493
|
+
this.releaseFs,
|
|
15322
15494
|
this.releaseLlm
|
|
15323
15495
|
]) try {
|
|
15324
15496
|
await release?.();
|
|
@@ -15331,9 +15503,26 @@ var TopicRuntime = class {
|
|
|
15331
15503
|
this.releaseLlm = void 0;
|
|
15332
15504
|
if (failures.length > 0) throw new AggregateError(failures, "CiteCiter Topic runtime cleanup failed");
|
|
15333
15505
|
}
|
|
15334
|
-
async
|
|
15506
|
+
async settleOwnedOperations() {
|
|
15507
|
+
while (true) {
|
|
15508
|
+
const operations = /* @__PURE__ */ new Set([
|
|
15509
|
+
...this.requests,
|
|
15510
|
+
...[...this.creations.values()].map(({ result }) => result),
|
|
15511
|
+
...[...this.asks.values()].map(({ result }) => result),
|
|
15512
|
+
...this.topicAdmissions.values(),
|
|
15513
|
+
...this.modelChanges.values(),
|
|
15514
|
+
...this.sourceAvailabilityChecks.values(),
|
|
15515
|
+
...this.titleRefreshes.values(),
|
|
15516
|
+
...this.opening.values()
|
|
15517
|
+
]);
|
|
15518
|
+
if (operations.size === 0) return;
|
|
15519
|
+
await Promise.allSettled(operations);
|
|
15520
|
+
}
|
|
15521
|
+
}
|
|
15522
|
+
async create(request, signal) {
|
|
15335
15523
|
const sourceSessionId = createSourceSessionId(request);
|
|
15336
15524
|
const source = await this.host.sessionQuery.readSession(SessionId(sourceSessionId));
|
|
15525
|
+
this.assertOpen(signal);
|
|
15337
15526
|
this.sourceAvailability.set(sourceSessionId, true);
|
|
15338
15527
|
const validated = "selectionClaim" in request ? resolveObserverCitation(source, request.selectionClaim) : validateObserverCitation(source, request.citation);
|
|
15339
15528
|
const { topicId, directory } = await this.index.reserve(sourceSessionId);
|
|
@@ -15377,17 +15566,11 @@ var TopicRuntime = class {
|
|
|
15377
15566
|
};
|
|
15378
15567
|
let handle;
|
|
15379
15568
|
try {
|
|
15380
|
-
handle = await this.createHandle(metadata, mode.seed);
|
|
15569
|
+
handle = await this.createHandle(metadata, mode.seed, signal);
|
|
15381
15570
|
await this.runtime.sessions.flush(handle.agent.session);
|
|
15571
|
+
this.assertOpen(signal);
|
|
15382
15572
|
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);
|
|
15573
|
+
await this.commitFollowup(handle, identifiedQuestion(request.requestId, request.question), signal);
|
|
15391
15574
|
return this.snapshot(metadata);
|
|
15392
15575
|
} catch (error) {
|
|
15393
15576
|
try {
|
|
@@ -15405,16 +15588,66 @@ var TopicRuntime = class {
|
|
|
15405
15588
|
throw error;
|
|
15406
15589
|
}
|
|
15407
15590
|
}
|
|
15408
|
-
|
|
15591
|
+
/** Let a caller stop waiting without cancelling an accepted idempotent mutation. */
|
|
15592
|
+
waitForCaller(operation, signal) {
|
|
15593
|
+
if (signal === void 0) return operation;
|
|
15594
|
+
return new Promise((resolve, reject) => {
|
|
15595
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
15596
|
+
const onAbort = () => {
|
|
15597
|
+
cleanup();
|
|
15598
|
+
reject(signal.reason);
|
|
15599
|
+
};
|
|
15600
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
15601
|
+
if (signal.aborted) onAbort();
|
|
15602
|
+
operation.then((value) => {
|
|
15603
|
+
cleanup();
|
|
15604
|
+
resolve(value);
|
|
15605
|
+
}, (error) => {
|
|
15606
|
+
cleanup();
|
|
15607
|
+
reject(error);
|
|
15608
|
+
});
|
|
15609
|
+
});
|
|
15610
|
+
}
|
|
15611
|
+
createIdempotent(request, signal) {
|
|
15612
|
+
const key = `${createSourceSessionId(request)}\0${request.requestId}`;
|
|
15613
|
+
const pending = this.creations.get(key);
|
|
15614
|
+
const intent = JSON.stringify(request);
|
|
15615
|
+
if (pending !== void 0) {
|
|
15616
|
+
if (pending.intent !== intent) throw new Error("CiteCiter create requestId was reused for a different request");
|
|
15617
|
+
return this.waitForCaller(pending.result, signal);
|
|
15618
|
+
}
|
|
15619
|
+
const creation = Promise.resolve().then(() => {
|
|
15620
|
+
this.assertOpen(signal);
|
|
15621
|
+
return this.resumeOrCreate(request, signal);
|
|
15622
|
+
}).finally(() => this.creations.delete(key));
|
|
15623
|
+
this.creations.set(key, {
|
|
15624
|
+
intent,
|
|
15625
|
+
result: creation
|
|
15626
|
+
});
|
|
15627
|
+
return this.waitForCaller(creation, signal);
|
|
15628
|
+
}
|
|
15629
|
+
async resumeOrCreate(request, signal) {
|
|
15409
15630
|
const committed = (await this.index.list(createSourceSessionId(request))).find((topic) => topic.createRequestId === request.requestId);
|
|
15410
|
-
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
|
|
15414
|
-
|
|
15415
|
-
|
|
15631
|
+
this.assertOpen(signal);
|
|
15632
|
+
if (committed !== void 0) return this.queueTopicAdmission(committed.sessionId, async () => {
|
|
15633
|
+
const log = await this.readLog(committed, signal);
|
|
15634
|
+
const identified = postSeedUserQuestionById(log, request.requestId);
|
|
15635
|
+
const existingQuestion = identified ?? firstPostSeedUserQuestion(log);
|
|
15636
|
+
if (existingQuestion !== null && existingQuestion !== request.question) throw new Error("CiteCiter create requestId was reused for a different question");
|
|
15637
|
+
if (existingQuestion === null || identified !== null && committedPostSeedUserQuestionById(log, request.requestId) === null) {
|
|
15638
|
+
const handle = await this.ensureHandle(committed, signal);
|
|
15639
|
+
handle.agent.inbox.remove(MessageId(request.requestId));
|
|
15640
|
+
await this.commitFollowup(handle, identifiedQuestion(request.requestId, request.question), signal);
|
|
15641
|
+
} else {
|
|
15642
|
+
const live = this.handles.get(committed.sessionId)?.agent.session;
|
|
15643
|
+
if (live !== void 0) await this.runtime.sessions.flush(live);
|
|
15644
|
+
}
|
|
15645
|
+
return this.snapshot(committed);
|
|
15646
|
+
}, signal);
|
|
15647
|
+
return this.create(request, signal);
|
|
15416
15648
|
}
|
|
15417
|
-
async createHandle(metadata, seed) {
|
|
15649
|
+
async createHandle(metadata, seed, signal) {
|
|
15650
|
+
this.assertOpen(signal);
|
|
15418
15651
|
const handle = await this.runtime.agents.create({
|
|
15419
15652
|
sessionId: SessionId(metadata.sessionId),
|
|
15420
15653
|
...metadata.mode === "exact-fork" ? {
|
|
@@ -15430,8 +15663,13 @@ var TopicRuntime = class {
|
|
|
15430
15663
|
model: metadata.modelConfig.model,
|
|
15431
15664
|
...metadata.modelConfig.maxTokens === void 0 ? {} : { maxTokens: metadata.modelConfig.maxTokens }
|
|
15432
15665
|
},
|
|
15433
|
-
setup: (agentCtx) => this.setupAgent(agentCtx, metadata)
|
|
15666
|
+
setup: (agentCtx) => this.setupAgent(agentCtx, metadata),
|
|
15667
|
+
...signal === void 0 ? {} : { signal }
|
|
15434
15668
|
});
|
|
15669
|
+
if (this.closed || signal?.aborted === true) {
|
|
15670
|
+
await this.disposeLateHandle(handle);
|
|
15671
|
+
this.assertOpen(signal);
|
|
15672
|
+
}
|
|
15435
15673
|
this.handles.set(metadata.sessionId, handle);
|
|
15436
15674
|
return handle;
|
|
15437
15675
|
}
|
|
@@ -15644,12 +15882,13 @@ var TopicRuntime = class {
|
|
|
15644
15882
|
}],
|
|
15645
15883
|
presentationMeta: (_args, value) => ({ capturedThroughSeq: value.capturedThroughSeq })
|
|
15646
15884
|
},
|
|
15647
|
-
execute: async (args) => {
|
|
15885
|
+
execute: async (args, exec) => {
|
|
15648
15886
|
let source;
|
|
15649
15887
|
let sourceAvailable = true;
|
|
15650
15888
|
try {
|
|
15651
15889
|
source = await this.host.sessionQuery.readSession(SessionId(metadata.sourceSessionId));
|
|
15652
15890
|
} catch (error) {
|
|
15891
|
+
exec.signal.throwIfAborted();
|
|
15653
15892
|
sourceAvailable = false;
|
|
15654
15893
|
const agent = agentCtx.agent;
|
|
15655
15894
|
if (metadata.mode !== "exact-fork" || agent === void 0 || agent.session.header.seedLength === void 0) {
|
|
@@ -15661,12 +15900,14 @@ var TopicRuntime = class {
|
|
|
15661
15900
|
events: agent.session.events.slice(0, agent.session.header.seedLength)
|
|
15662
15901
|
};
|
|
15663
15902
|
}
|
|
15903
|
+
exec.signal.throwIfAborted();
|
|
15664
15904
|
await this.rememberSourceAvailability(metadata, sourceAvailable);
|
|
15665
|
-
const
|
|
15666
|
-
|
|
15667
|
-
|
|
15905
|
+
const result = formatSourceSessionRead(metadata.forkThroughSeq === null ? source : {
|
|
15906
|
+
...source,
|
|
15907
|
+
events: source.events.filter((event) => event.seq <= metadata.forkThroughSeq)
|
|
15908
|
+
}, {
|
|
15668
15909
|
...args.fromSeq === void 0 ? {} : { fromSeq: args.fromSeq },
|
|
15669
|
-
...throughSeq === void 0 ? {} : { throughSeq },
|
|
15910
|
+
...args.throughSeq === void 0 ? {} : { throughSeq: args.throughSeq },
|
|
15670
15911
|
includeReasoning: this.settings().includeSourceReasoning,
|
|
15671
15912
|
maxBytes: SOURCE_READ_MAX_BYTES
|
|
15672
15913
|
});
|
|
@@ -15685,7 +15926,8 @@ var TopicRuntime = class {
|
|
|
15685
15926
|
})
|
|
15686
15927
|
});
|
|
15687
15928
|
}
|
|
15688
|
-
async ensureHandle(metadata) {
|
|
15929
|
+
async ensureHandle(metadata, signal) {
|
|
15930
|
+
this.assertOpen(signal);
|
|
15689
15931
|
const existing = this.handles.get(metadata.sessionId);
|
|
15690
15932
|
if (existing !== void 0) return existing;
|
|
15691
15933
|
const pending = this.opening.get(metadata.sessionId);
|
|
@@ -15697,8 +15939,13 @@ var TopicRuntime = class {
|
|
|
15697
15939
|
model: metadata.modelConfig.model,
|
|
15698
15940
|
...metadata.modelConfig.maxTokens === void 0 ? {} : { maxTokens: metadata.modelConfig.maxTokens }
|
|
15699
15941
|
},
|
|
15700
|
-
setup: (agentCtx) => this.setupAgent(agentCtx, metadata)
|
|
15701
|
-
|
|
15942
|
+
setup: (agentCtx) => this.setupAgent(agentCtx, metadata),
|
|
15943
|
+
...signal === void 0 ? {} : { signal }
|
|
15944
|
+
}).then(async (handle) => {
|
|
15945
|
+
if (this.closed || signal?.aborted === true) {
|
|
15946
|
+
await this.disposeLateHandle(handle);
|
|
15947
|
+
this.assertOpen(signal);
|
|
15948
|
+
}
|
|
15702
15949
|
this.handles.set(metadata.sessionId, handle);
|
|
15703
15950
|
return handle;
|
|
15704
15951
|
}).finally(() => {
|
|
@@ -15707,15 +15954,86 @@ var TopicRuntime = class {
|
|
|
15707
15954
|
this.opening.set(metadata.sessionId, opening);
|
|
15708
15955
|
return opening;
|
|
15709
15956
|
}
|
|
15710
|
-
async
|
|
15957
|
+
async disposeLateHandle(handle) {
|
|
15958
|
+
try {
|
|
15959
|
+
await handle.dispose();
|
|
15960
|
+
} catch (error) {
|
|
15961
|
+
this.cleanupFailures.push(error);
|
|
15962
|
+
throw error;
|
|
15963
|
+
}
|
|
15964
|
+
}
|
|
15965
|
+
/** Resolve only after the accepted question is present in the durable model-input log. */
|
|
15966
|
+
async commitFollowup(handle, message, admissionSignal) {
|
|
15967
|
+
this.assertOpen(admissionSignal);
|
|
15968
|
+
const signal = this.lifecycleAbort.signal;
|
|
15969
|
+
await new Promise((resolveCommitted, rejectCommitted) => {
|
|
15970
|
+
let claimedTurn;
|
|
15971
|
+
let settled = false;
|
|
15972
|
+
let disposeClaim = () => {};
|
|
15973
|
+
let disposeDiscard = () => {};
|
|
15974
|
+
let disposeEvent = () => {};
|
|
15975
|
+
const onAbort = () => finish(signal?.reason ?? citeCiterShuttingDownError());
|
|
15976
|
+
const finish = (error) => {
|
|
15977
|
+
if (settled) return;
|
|
15978
|
+
settled = true;
|
|
15979
|
+
signal?.removeEventListener("abort", onAbort);
|
|
15980
|
+
disposeEvent();
|
|
15981
|
+
disposeDiscard();
|
|
15982
|
+
disposeClaim();
|
|
15983
|
+
if (error === void 0) resolveCommitted();
|
|
15984
|
+
else rejectCommitted(error);
|
|
15985
|
+
};
|
|
15986
|
+
disposeClaim = handle.agent.ctx.on("agent/inbox/claimed", ({ message: claimed, turn }) => {
|
|
15987
|
+
if (claimed.id === message.id) claimedTurn = turn;
|
|
15988
|
+
});
|
|
15989
|
+
disposeDiscard = handle.agent.ctx.on("agent/inbox/discarded", ({ message: discarded }) => {
|
|
15990
|
+
if (discarded.id === message.id) finish(/* @__PURE__ */ new Error("CiteCiter question was discarded before it became model input"));
|
|
15991
|
+
});
|
|
15992
|
+
disposeEvent = handle.agent.ctx.on("session/event", (session, event) => {
|
|
15993
|
+
if (session !== handle.agent.session) return;
|
|
15994
|
+
if (event.type === "user/message" && event.data.source.kind === "user" && event.data.id === message.id) {
|
|
15995
|
+
finish();
|
|
15996
|
+
return;
|
|
15997
|
+
}
|
|
15998
|
+
if (event.type === "turn/end" && event.data.turn === claimedTurn) finish(/* @__PURE__ */ new Error("CiteCiter question was not committed before its turn ended"));
|
|
15999
|
+
});
|
|
16000
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
16001
|
+
if (signal?.aborted === true) {
|
|
16002
|
+
onAbort();
|
|
16003
|
+
return;
|
|
16004
|
+
}
|
|
16005
|
+
try {
|
|
16006
|
+
handle.agent.followup(message);
|
|
16007
|
+
} catch (error) {
|
|
16008
|
+
finish(error);
|
|
16009
|
+
}
|
|
16010
|
+
});
|
|
16011
|
+
await this.runtime.sessions.flush(handle.agent.session);
|
|
16012
|
+
}
|
|
16013
|
+
async ask(sessionId, question, requestId, signal) {
|
|
15711
16014
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
15712
|
-
|
|
16015
|
+
this.assertOpen(signal);
|
|
16016
|
+
if (requestId !== void 0) {
|
|
16017
|
+
const log = await this.readLog(metadata, signal);
|
|
16018
|
+
const existingQuestion = postSeedUserQuestionById(log, requestId);
|
|
16019
|
+
if (existingQuestion !== null) {
|
|
16020
|
+
if (existingQuestion !== question) throw new Error("CiteCiter ask requestId was reused for a different question");
|
|
16021
|
+
if (committedPostSeedUserQuestionById(log, requestId) !== null) {
|
|
16022
|
+
const live = this.handles.get(sessionId)?.agent.session;
|
|
16023
|
+
if (live !== void 0) await this.runtime.sessions.flush(live);
|
|
16024
|
+
return this.snapshot(metadata);
|
|
16025
|
+
}
|
|
16026
|
+
}
|
|
16027
|
+
}
|
|
16028
|
+
const handle = await this.ensureHandle(metadata, signal);
|
|
16029
|
+
if (requestId !== void 0) handle.agent.inbox.remove(MessageId(requestId));
|
|
16030
|
+
await this.commitFollowup(handle, requestId === void 0 ? createUserMessage({
|
|
15713
16031
|
content: [{
|
|
15714
16032
|
type: "text",
|
|
15715
16033
|
text: question
|
|
15716
16034
|
}],
|
|
15717
16035
|
source: { kind: "user" }
|
|
15718
|
-
}));
|
|
16036
|
+
}) : identifiedQuestion(requestId, question), signal);
|
|
15719
16037
|
const updated = {
|
|
15720
16038
|
...metadata,
|
|
15721
16039
|
updatedAt: Date.now()
|
|
@@ -15723,7 +16041,38 @@ var TopicRuntime = class {
|
|
|
15723
16041
|
await this.index.save(updated);
|
|
15724
16042
|
return this.snapshot(updated);
|
|
15725
16043
|
}
|
|
16044
|
+
async askIdempotent(request, signal) {
|
|
16045
|
+
if (request.requestId === void 0) return this.queueAsk(request, signal);
|
|
16046
|
+
const key = `${request.topicSessionId}\0${request.requestId}`;
|
|
16047
|
+
const existing = this.asks.get(key);
|
|
16048
|
+
if (existing !== void 0) {
|
|
16049
|
+
if (existing.question !== request.question) throw new Error("CiteCiter ask requestId was reused for a different question");
|
|
16050
|
+
return this.waitForCaller(existing.result, signal);
|
|
16051
|
+
}
|
|
16052
|
+
const result = this.queueAsk(request, signal).finally(() => this.asks.delete(key));
|
|
16053
|
+
this.asks.set(key, {
|
|
16054
|
+
question: request.question,
|
|
16055
|
+
result
|
|
16056
|
+
});
|
|
16057
|
+
return this.waitForCaller(result, signal);
|
|
16058
|
+
}
|
|
16059
|
+
queueAsk(request, signal) {
|
|
16060
|
+
return this.queueTopicAdmission(request.topicSessionId, () => this.ask(request.topicSessionId, request.question, request.requestId, signal), signal);
|
|
16061
|
+
}
|
|
16062
|
+
queueTopicAdmission(sessionId, operation, signal) {
|
|
16063
|
+
const result = (this.topicAdmissions.get(sessionId) ?? Promise.resolve()).then(() => {
|
|
16064
|
+
this.assertOpen(signal);
|
|
16065
|
+
return operation();
|
|
16066
|
+
});
|
|
16067
|
+
const settled = result.then(() => void 0, () => void 0);
|
|
16068
|
+
this.topicAdmissions.set(sessionId, settled);
|
|
16069
|
+
settled.then(() => {
|
|
16070
|
+
if (this.topicAdmissions.get(sessionId) === settled) this.topicAdmissions.delete(sessionId);
|
|
16071
|
+
});
|
|
16072
|
+
return result;
|
|
16073
|
+
}
|
|
15726
16074
|
askUser(request) {
|
|
16075
|
+
if (this.closed) throw new UserQuestionError(CITECITER_SHUTTING_DOWN, "ASK_ABORTED");
|
|
15727
16076
|
const sessionId = request.agent === void 0 ? void 0 : String(request.agent.session.header.id);
|
|
15728
16077
|
if (sessionId === void 0 || !this.handles.has(sessionId)) throw new UserQuestionError("CiteCiter cannot identify the asking Topic", "CALLER_NOT_LIVE");
|
|
15729
16078
|
if (this.pendingQuestions.has(sessionId)) throw new UserQuestionError("this Topic already has a pending question", "DUPLICATE_QUESTION");
|
|
@@ -15756,31 +16105,36 @@ var TopicRuntime = class {
|
|
|
15756
16105
|
if (request.signal?.aborted === true) onAbort();
|
|
15757
16106
|
});
|
|
15758
16107
|
}
|
|
15759
|
-
async answerQuestion(request) {
|
|
16108
|
+
async answerQuestion(request, signal) {
|
|
15760
16109
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
16110
|
+
this.assertOpen(signal);
|
|
15761
16111
|
const pending = this.pendingQuestions.get(request.topicSessionId);
|
|
15762
16112
|
if (pending === void 0 || pending.key !== request.key) throw new Error("这个提问已结束或已被替换");
|
|
15763
16113
|
pending.resolve(validatedQuestionAnswer(pending.questions, request.answer));
|
|
15764
16114
|
return this.snapshot(metadata);
|
|
15765
16115
|
}
|
|
15766
|
-
async cancelQuestion(sessionId, key) {
|
|
16116
|
+
async cancelQuestion(sessionId, key, signal) {
|
|
15767
16117
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16118
|
+
this.assertOpen(signal);
|
|
15768
16119
|
const pending = this.pendingQuestions.get(sessionId);
|
|
15769
16120
|
if (pending === void 0 || pending.key !== key) throw new Error("这个提问已结束或已被替换");
|
|
15770
16121
|
pending.reject(new UserQuestionError("the user cancelled ask_user_question", "ASK_CANCELLED"));
|
|
15771
16122
|
return this.snapshot(metadata);
|
|
15772
16123
|
}
|
|
15773
|
-
async stop(sessionId) {
|
|
16124
|
+
async stop(sessionId, signal) {
|
|
15774
16125
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16126
|
+
this.assertOpen(signal);
|
|
15775
16127
|
const agent = this.handles.get(sessionId)?.agent;
|
|
15776
16128
|
agent?.cancel({ kind: "user" });
|
|
15777
16129
|
await agent?.whenIdle();
|
|
15778
16130
|
if (agent !== void 0) await this.runtime.sessions.flush(agent.session);
|
|
15779
16131
|
return this.snapshot(metadata);
|
|
15780
16132
|
}
|
|
15781
|
-
async rename(sessionId, title) {
|
|
16133
|
+
async rename(sessionId, title, signal) {
|
|
15782
16134
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
15783
|
-
|
|
16135
|
+
this.assertOpen(signal);
|
|
16136
|
+
const handle = await this.ensureHandle(metadata, signal);
|
|
16137
|
+
this.assertOpen(signal);
|
|
15784
16138
|
const renamed = this.runtime.sessionTitle.rename(handle.agent.session, title);
|
|
15785
16139
|
await this.runtime.sessions.flush(handle.agent.session);
|
|
15786
16140
|
const updated = {
|
|
@@ -15793,25 +16147,29 @@ var TopicRuntime = class {
|
|
|
15793
16147
|
await this.index.save(updated);
|
|
15794
16148
|
return this.snapshot(updated);
|
|
15795
16149
|
}
|
|
15796
|
-
async archive(sessionId, archived) {
|
|
16150
|
+
async archive(sessionId, archived, signal) {
|
|
16151
|
+
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16152
|
+
this.assertOpen(signal);
|
|
15797
16153
|
const updated = {
|
|
15798
|
-
...
|
|
16154
|
+
...metadata,
|
|
15799
16155
|
archivedAt: archived ? Date.now() : null,
|
|
15800
16156
|
updatedAt: Date.now()
|
|
15801
16157
|
};
|
|
15802
16158
|
await this.index.save(updated);
|
|
15803
16159
|
return this.snapshot(updated);
|
|
15804
16160
|
}
|
|
15805
|
-
async delete(sessionId, confirmSessionId) {
|
|
16161
|
+
async delete(sessionId, confirmSessionId, signal) {
|
|
15806
16162
|
if (sessionId !== confirmSessionId) throw new Error("Topic deletion confirmation does not match the target Session");
|
|
15807
16163
|
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16164
|
+
this.assertOpen(signal);
|
|
15808
16165
|
const pending = this.opening.get(sessionId);
|
|
15809
16166
|
const handle = this.handles.get(sessionId) ?? (pending === void 0 ? void 0 : await pending);
|
|
16167
|
+
this.assertOpen(signal);
|
|
15810
16168
|
if (handle !== void 0) {
|
|
15811
16169
|
await handle.dispose();
|
|
15812
16170
|
this.handles.delete(sessionId);
|
|
15813
16171
|
}
|
|
15814
|
-
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(sessionId));
|
|
16172
|
+
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(sessionId), signal);
|
|
15815
16173
|
await this.removeSessionArtifact(inspection.meta);
|
|
15816
16174
|
await this.index.remove(metadata);
|
|
15817
16175
|
return sessionId;
|
|
@@ -15826,20 +16184,25 @@ var TopicRuntime = class {
|
|
|
15826
16184
|
}) !== void 0) await unlink(artifact.path);
|
|
15827
16185
|
await rmdirIfEmpty(resolve(artifact.path, ".."));
|
|
15828
16186
|
}
|
|
15829
|
-
enqueueModelChange(sessionId, apply) {
|
|
16187
|
+
enqueueModelChange(sessionId, apply, signal) {
|
|
15830
16188
|
const previous = this.modelChanges.get(sessionId);
|
|
15831
16189
|
let change;
|
|
15832
|
-
change = (previous === void 0 ? Promise.resolve() : previous.catch(() => void 0)).then(
|
|
16190
|
+
change = (previous === void 0 ? Promise.resolve() : previous.catch(() => void 0)).then(() => {
|
|
16191
|
+
this.assertOpen(signal);
|
|
16192
|
+
return apply();
|
|
16193
|
+
}).finally(() => {
|
|
15833
16194
|
if (this.modelChanges.get(sessionId) === change) this.modelChanges.delete(sessionId);
|
|
15834
16195
|
});
|
|
15835
16196
|
this.modelChanges.set(sessionId, change);
|
|
15836
16197
|
return change;
|
|
15837
16198
|
}
|
|
15838
|
-
setModelRoute(request) {
|
|
16199
|
+
setModelRoute(request, signal) {
|
|
15839
16200
|
return this.enqueueModelChange(request.topicSessionId, async () => {
|
|
15840
16201
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
15841
|
-
|
|
15842
|
-
await this.
|
|
16202
|
+
this.assertOpen(signal);
|
|
16203
|
+
await this.host.llm.resolveModelInfo(request.provider, request.model, signal);
|
|
16204
|
+
await this.ensureHandle(metadata, signal);
|
|
16205
|
+
this.assertOpen(signal);
|
|
15843
16206
|
const selection = this.selections.get(metadata.sessionId);
|
|
15844
16207
|
if (selection === void 0) throw new Error("Topic model selector is unavailable");
|
|
15845
16208
|
const modelConfig = {
|
|
@@ -15859,14 +16222,16 @@ var TopicRuntime = class {
|
|
|
15859
16222
|
model: request.model
|
|
15860
16223
|
};
|
|
15861
16224
|
return this.snapshot(updated);
|
|
15862
|
-
});
|
|
16225
|
+
}, signal);
|
|
15863
16226
|
}
|
|
15864
|
-
setReasoningEffort(request) {
|
|
16227
|
+
setReasoningEffort(request, signal) {
|
|
15865
16228
|
return this.enqueueModelChange(request.topicSessionId, async () => {
|
|
15866
16229
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
15867
|
-
|
|
16230
|
+
this.assertOpen(signal);
|
|
16231
|
+
const model = await this.host.llm.resolveModelInfo(metadata.modelConfig.provider, metadata.modelConfig.model, signal);
|
|
15868
16232
|
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);
|
|
16233
|
+
await this.ensureHandle(metadata, signal);
|
|
16234
|
+
this.assertOpen(signal);
|
|
15870
16235
|
const selection = this.selections.get(metadata.sessionId);
|
|
15871
16236
|
if (selection === void 0) throw new Error("Topic model selector is unavailable");
|
|
15872
16237
|
const modelConfig = { ...metadata.modelConfig };
|
|
@@ -15884,16 +16249,18 @@ var TopicRuntime = class {
|
|
|
15884
16249
|
...request.reasoningEffort === null ? {} : { reasoningEffort: ReasoningEffortId(request.reasoningEffort) }
|
|
15885
16250
|
};
|
|
15886
16251
|
return this.snapshot(updated);
|
|
15887
|
-
});
|
|
16252
|
+
}, signal);
|
|
15888
16253
|
}
|
|
15889
|
-
selectModel(request) {
|
|
15890
|
-
return this.enqueueModelChange(request.topicSessionId, () => this.applyModelSelection(request));
|
|
16254
|
+
selectModel(request, signal) {
|
|
16255
|
+
return this.enqueueModelChange(request.topicSessionId, () => this.applyModelSelection(request, signal), signal);
|
|
15891
16256
|
}
|
|
15892
|
-
async applyModelSelection(request) {
|
|
16257
|
+
async applyModelSelection(request, signal) {
|
|
15893
16258
|
const metadata = await this.index.loadBySessionId(request.topicSessionId);
|
|
15894
|
-
|
|
16259
|
+
this.assertOpen(signal);
|
|
16260
|
+
const model = await this.host.llm.resolveModelInfo(request.provider, request.model, signal);
|
|
15895
16261
|
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);
|
|
16262
|
+
await this.ensureHandle(metadata, signal);
|
|
16263
|
+
this.assertOpen(signal);
|
|
15897
16264
|
const selection = this.selections.get(metadata.sessionId);
|
|
15898
16265
|
if (selection === void 0) throw new Error("Topic model selector is unavailable");
|
|
15899
16266
|
const previousModelConfig = { ...metadata.modelConfig };
|
|
@@ -15916,13 +16283,16 @@ var TopicRuntime = class {
|
|
|
15916
16283
|
};
|
|
15917
16284
|
return this.snapshot(updated);
|
|
15918
16285
|
}
|
|
15919
|
-
async models() {
|
|
16286
|
+
async models(signal) {
|
|
15920
16287
|
const providers = [];
|
|
15921
16288
|
for (const provider of this.host.llm.listProviders()) {
|
|
16289
|
+
this.assertOpen(signal);
|
|
15922
16290
|
let catalog;
|
|
15923
16291
|
try {
|
|
15924
16292
|
catalog = await this.host.llm.listModels(provider.id);
|
|
16293
|
+
this.assertOpen(signal);
|
|
15925
16294
|
} catch (error) {
|
|
16295
|
+
signal?.throwIfAborted();
|
|
15926
16296
|
this.host.logger.warn(`CiteCiter could not list models for ${provider.id}`, error);
|
|
15927
16297
|
catalog = [];
|
|
15928
16298
|
}
|
|
@@ -15930,8 +16300,9 @@ var TopicRuntime = class {
|
|
|
15930
16300
|
for (const model of catalog) {
|
|
15931
16301
|
let resolved;
|
|
15932
16302
|
try {
|
|
15933
|
-
resolved = await this.host.llm.resolveModelInfo(provider.id, model.id);
|
|
16303
|
+
resolved = await this.host.llm.resolveModelInfo(provider.id, model.id, signal);
|
|
15934
16304
|
} catch (error) {
|
|
16305
|
+
signal?.throwIfAborted();
|
|
15935
16306
|
this.host.logger.warn(`CiteCiter could not resolve ${provider.id}/${model.id}`, error);
|
|
15936
16307
|
}
|
|
15937
16308
|
models.push({
|
|
@@ -15952,21 +16323,22 @@ var TopicRuntime = class {
|
|
|
15952
16323
|
}
|
|
15953
16324
|
return providers;
|
|
15954
16325
|
}
|
|
15955
|
-
async list(sourceSessionId, includeArchived) {
|
|
16326
|
+
async list(sourceSessionId, includeArchived, signal) {
|
|
15956
16327
|
const metadata = await this.index.list(sourceSessionId);
|
|
15957
|
-
|
|
16328
|
+
this.assertOpen(signal);
|
|
16329
|
+
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
16330
|
}
|
|
15959
|
-
async summary(metadata) {
|
|
16331
|
+
async summary(metadata, signal) {
|
|
15960
16332
|
let current = metadata;
|
|
15961
16333
|
if (cachedTopicTitle(current) === null && !this.titleHydrated.has(current.sessionId)) {
|
|
16334
|
+
const log = await this.readLog(current, signal);
|
|
15962
16335
|
this.titleHydrated.add(current.sessionId);
|
|
15963
|
-
const log = await this.readLog(current);
|
|
15964
16336
|
const title = foldTopicTitle(current, log.events);
|
|
15965
16337
|
if (title !== void 0) current = await this.patchMetadata(current, {
|
|
15966
16338
|
cachedTitle: title.title,
|
|
15967
16339
|
cachedTitleSource: titleSourceKind(title),
|
|
15968
16340
|
cachedTitleEventSeq: title.eventSeq
|
|
15969
|
-
});
|
|
16341
|
+
}, signal);
|
|
15970
16342
|
}
|
|
15971
16343
|
return this.summaryFromMetadata(current);
|
|
15972
16344
|
}
|
|
@@ -15989,20 +16361,27 @@ var TopicRuntime = class {
|
|
|
15989
16361
|
modelConfig: metadata.modelConfig
|
|
15990
16362
|
};
|
|
15991
16363
|
}
|
|
15992
|
-
async
|
|
16364
|
+
async get(sessionId, signal) {
|
|
16365
|
+
const metadata = await this.index.loadBySessionId(sessionId);
|
|
16366
|
+
this.assertOpen(signal);
|
|
16367
|
+
return this.snapshot(metadata, signal);
|
|
16368
|
+
}
|
|
16369
|
+
async readLog(metadata, signal) {
|
|
16370
|
+
if (signal !== void 0) this.assertOpen(signal);
|
|
15993
16371
|
const live = this.handles.get(metadata.sessionId)?.agent.session;
|
|
15994
16372
|
if (live !== void 0) return {
|
|
15995
16373
|
header: live.header,
|
|
15996
16374
|
events: live.events
|
|
15997
16375
|
};
|
|
15998
|
-
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(metadata.sessionId));
|
|
16376
|
+
const inspection = await this.runtime.sessionPersistence.inspect(SessionId(metadata.sessionId), signal);
|
|
16377
|
+
if (signal !== void 0) this.assertOpen(signal);
|
|
15999
16378
|
return {
|
|
16000
16379
|
header: inspection.meta,
|
|
16001
16380
|
events: inspection.events
|
|
16002
16381
|
};
|
|
16003
16382
|
}
|
|
16004
16383
|
scheduleSourceAvailabilityCheck(metadata) {
|
|
16005
|
-
if (this.sourceAvailability.has(metadata.sourceSessionId) || this.sourceAvailabilityChecks.has(metadata.sourceSessionId)) return;
|
|
16384
|
+
if (this.closed || this.sourceAvailability.has(metadata.sourceSessionId) || this.sourceAvailabilityChecks.has(metadata.sourceSessionId)) return;
|
|
16006
16385
|
const check = (async () => {
|
|
16007
16386
|
let available = true;
|
|
16008
16387
|
try {
|
|
@@ -16026,10 +16405,10 @@ var TopicRuntime = class {
|
|
|
16026
16405
|
const latest = await this.index.loadBySessionId(metadata.sessionId);
|
|
16027
16406
|
if (latest.sourceAvailable !== available) await this.patchMetadata(latest, { sourceAvailable: available });
|
|
16028
16407
|
}
|
|
16029
|
-
async snapshot(metadata) {
|
|
16408
|
+
async snapshot(metadata, signal) {
|
|
16030
16409
|
let current = metadata;
|
|
16031
16410
|
this.scheduleSourceAvailabilityCheck(current);
|
|
16032
|
-
const log = await this.readLog(current);
|
|
16411
|
+
const log = await this.readLog(current, signal);
|
|
16033
16412
|
const title = foldTopicTitle(current, log.events);
|
|
16034
16413
|
const latest = log.events.at(-1)?.time ?? metadata.updatedAt;
|
|
16035
16414
|
const observedThroughSeq = latestObservedSeq(log.events);
|
|
@@ -16042,7 +16421,7 @@ var TopicRuntime = class {
|
|
|
16042
16421
|
cachedTitleSource,
|
|
16043
16422
|
cachedTitleEventSeq: title.eventSeq
|
|
16044
16423
|
}
|
|
16045
|
-
});
|
|
16424
|
+
}, signal);
|
|
16046
16425
|
if (title === void 0) this.scheduleExactTitleRefresh(current, log);
|
|
16047
16426
|
const pending = this.pendingQuestions.get(current.sessionId);
|
|
16048
16427
|
return {
|
|
@@ -16060,8 +16439,9 @@ var TopicRuntime = class {
|
|
|
16060
16439
|
}
|
|
16061
16440
|
};
|
|
16062
16441
|
}
|
|
16063
|
-
async patchMetadata(metadata, patch) {
|
|
16442
|
+
async patchMetadata(metadata, patch, signal) {
|
|
16064
16443
|
const latest = await this.index.loadBySessionId(metadata.sessionId);
|
|
16444
|
+
if (signal !== void 0) this.assertOpen(signal);
|
|
16065
16445
|
const updated = topicMetadataSchema.parse({
|
|
16066
16446
|
...latest,
|
|
16067
16447
|
...patch
|
|
@@ -16070,20 +16450,22 @@ var TopicRuntime = class {
|
|
|
16070
16450
|
return updated;
|
|
16071
16451
|
}
|
|
16072
16452
|
scheduleExactTitleRefresh(metadata, log) {
|
|
16073
|
-
if (metadata.mode !== "exact-fork" || this.titleRefreshAttempted.has(metadata.sessionId) || this.handles.get(metadata.sessionId)?.agent.status === "running") return;
|
|
16453
|
+
if (this.closed || metadata.mode !== "exact-fork" || this.titleRefreshAttempted.has(metadata.sessionId) || this.handles.get(metadata.sessionId)?.agent.status === "running") return;
|
|
16074
16454
|
const postSeed = log.events.slice(log.header.seedLength ?? 0);
|
|
16075
16455
|
if (!postSeed.some((event) => event.type === "request/header") || !postSeed.some((event) => event.type === "assistant/message")) return;
|
|
16076
16456
|
const handle = this.handles.get(metadata.sessionId);
|
|
16077
16457
|
if (handle === void 0) return;
|
|
16078
16458
|
this.titleRefreshAttempted.add(metadata.sessionId);
|
|
16079
|
-
const refresh = this.runtime.sessionTitle.refresh(handle.agent.session).then(async (title) => {
|
|
16459
|
+
const refresh = this.runtime.sessionTitle.refresh(handle.agent.session, this.lifecycleAbort.signal).then(async (title) => {
|
|
16460
|
+
this.assertOpen(this.lifecycleAbort.signal);
|
|
16080
16461
|
await this.runtime.sessions.flush(handle.agent.session);
|
|
16462
|
+
this.assertOpen(this.lifecycleAbort.signal);
|
|
16081
16463
|
if (title === void 0 || title.eventSeq <= (metadata.forkThroughSeq ?? -1)) return;
|
|
16082
16464
|
await this.patchMetadata(metadata, {
|
|
16083
16465
|
cachedTitle: title.title,
|
|
16084
16466
|
cachedTitleSource: titleSourceKind(title),
|
|
16085
16467
|
cachedTitleEventSeq: title.eventSeq
|
|
16086
|
-
});
|
|
16468
|
+
}, this.lifecycleAbort.signal);
|
|
16087
16469
|
}).catch((error) => {
|
|
16088
16470
|
if (!this.closed) this.host.logger.warn(`CiteCiter could not title Topic ${metadata.sessionId}`, error);
|
|
16089
16471
|
}).finally(() => {
|
|
@@ -16200,8 +16582,8 @@ let CiteCiterHost = (() => {
|
|
|
16200
16582
|
await this.topics.initialize();
|
|
16201
16583
|
}
|
|
16202
16584
|
/** Validate and execute one strict Topic command. */
|
|
16203
|
-
async request(rawRequest) {
|
|
16204
|
-
return this.topics.request(citeCiterRequestSchema.parse(rawRequest));
|
|
16585
|
+
async request(rawRequest, signal) {
|
|
16586
|
+
return this.topics.request(citeCiterRequestSchema.parse(rawRequest), signal);
|
|
16205
16587
|
}
|
|
16206
16588
|
};
|
|
16207
16589
|
})();
|