@musnows/scriverse 1.0.11 → 1.1.0
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.en.md +6 -3
- package/README.md +6 -3
- package/dist/ai-stream-steer.js +96 -0
- package/dist/ai-stream-steer.js.map +1 -0
- package/dist/ai-write-plans.js +44 -38
- package/dist/ai-write-plans.js.map +1 -1
- package/dist/ai.js +241 -128
- package/dist/ai.js.map +1 -1
- package/dist/app.js +134 -14
- package/dist/app.js.map +1 -1
- package/dist/attachment-storage.js +3 -0
- package/dist/attachment-storage.js.map +1 -1
- package/dist/chapter-annotation-note.js +14 -0
- package/dist/chapter-annotation-note.js.map +1 -0
- package/dist/character-structured-fields.js +7 -0
- package/dist/character-structured-fields.js.map +1 -0
- package/dist/cli-contract.js +1 -1
- package/dist/cli-contract.js.map +1 -1
- package/dist/database.js +29 -2
- package/dist/database.js.map +1 -1
- package/dist/im.js +2 -1
- package/dist/im.js.map +1 -1
- package/dist/public/ai-prompt-keyboard.js +5 -1
- package/dist/public/ai-prompt-queue.js +226 -0
- package/dist/public/ai-send-mode.js +31 -0
- package/dist/public/app.js +689 -74
- package/dist/public/chapter-editor-scroll.d.ts +37 -0
- package/dist/public/chapter-editor-scroll.js +52 -0
- package/dist/public/character-profile.d.ts +7 -0
- package/dist/public/character-profile.js +123 -5
- package/dist/public/index.html +31 -10
- package/dist/public/styles.css +75 -8
- package/dist/security.js +22 -3
- package/dist/security.js.map +1 -1
- package/dist/server-runtime.js +2 -0
- package/dist/server-runtime.js.map +1 -1
- package/dist/store.js +7 -6
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +125 -15
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -14,6 +14,7 @@ import { AI_PROVIDER_PROTOCOL_OPTIONS, AI_PROVIDER_PROTOCOLS, AI_THINKING_TYPES,
|
|
|
14
14
|
import { MAX_AI_ANALYSIS_TIMEOUT_SECONDS, MIN_AI_ANALYSIS_TIMEOUT_SECONDS } from "./ai-analysis-timeout.js";
|
|
15
15
|
import { aiConversationExportContentDisposition, exportAiConversationMarkdown } from "./ai-conversation-export.js";
|
|
16
16
|
import { DEFAULT_AI_CHAT_TAB_LIMIT } from "./ai-chat-tab-limit.js";
|
|
17
|
+
import { DEFAULT_CHAPTER_ANNOTATION_NOTE_MAX_LENGTH } from "./chapter-annotation-note.js";
|
|
17
18
|
import { MAX_AI_STREAM_IDLE_TIMEOUT_SECONDS, MIN_AI_STREAM_IDLE_TIMEOUT_SECONDS, normalizeAiStreamIdleTimeoutSeconds } from "./ai-stream-timeout.js";
|
|
18
19
|
import { AttachmentStorage } from "./attachment-storage.js";
|
|
19
20
|
import { attachmentDownloadFileName, inlineContentDisposition } from "./attachment-download.js";
|
|
@@ -22,6 +23,7 @@ import { LiteLlmPriceCache } from "./ai-model-pricing.js";
|
|
|
22
23
|
import { MIN_AGENT_TOOL_CALL_LIMIT, resolveMaxAgentToolCallLimit } from "./ai-tool-results.js";
|
|
23
24
|
import { SEMANTIC_SOURCE_TYPES } from "./semantic-search.js";
|
|
24
25
|
import { CHARACTER_EXTRACTION_MAX_ALIASES, CHARACTER_EXTRACTION_MAX_CANDIDATES, CHARACTER_EXTRACTION_MAX_IDENTITY_LENGTH, CHARACTER_EXTRACTION_MAX_NAME_LENGTH, CHARACTER_EXTRACTION_MAX_SPECIES_LENGTH } from "./character-extraction.js";
|
|
26
|
+
import { characterAttributesInputSchema, characterStateInputSchema } from "./character-structured-fields.js";
|
|
25
27
|
import { AiWritePlanManager, AI_USER_QUESTION_STATUSES, AI_WRITE_PLAN_STATUSES, aiWriteToolDescriptions, aiWriteToolLabels, aiWriteToolsUpdateSchema, answerAiUserQuestionSchema, resolveAiWritePlanMaxOperations } from "./ai-write-plans.js";
|
|
26
28
|
import { CredentialVault } from "./credential-vault.js";
|
|
27
29
|
import { Database } from "./database.js";
|
|
@@ -39,7 +41,7 @@ import { composeRoleplayStoredUserContent } from "./roleplay-turn.js";
|
|
|
39
41
|
import { ROLEPLAY_MEMORY_CATEGORIES, ROLEPLAY_MEMORY_CERTAINTY, ROLEPLAY_MEMORY_IMPORTANCE, ROLEPLAY_MEMORY_STATUSES } from "./roleplay-memory.js";
|
|
40
42
|
import { paginated, parsePagination } from "./pagination.js";
|
|
41
43
|
import { normalizeUploadFileName } from "./utils.js";
|
|
42
|
-
import { assertSafeAiEndpoint, assertSafeS3Endpoint, createApiRateLimitMiddleware, createAuthenticationRateLimitMiddleware, createBasicAuthMiddleware, createCaptchaRateLimitMiddleware, createExpensiveApiRateLimitMiddleware, createSameOriginMiddleware, createSecurityHeadersMiddleware, createUploadRateLimitMiddleware, enforceCaseInsensitiveRouting, normalizeApiPath, resolveTrustProxySetting, verifySetupToken } from "./security.js";
|
|
44
|
+
import { assertSafeAiEndpoint, assertSafeS3Endpoint, createApiRateLimitMiddleware, createAuthenticationRateLimitMiddleware, createBasicAuthMiddleware, createCaptchaRateLimitMiddleware, createExpensiveApiRateLimitMiddleware, createSameOriginMiddleware, createSecurityHeadersMiddleware, createUploadRateLimitMiddleware, enforceCaseInsensitiveRouting, isRegistrationEnabled, normalizeApiPath, resolveRegistrationMode, resolveTrustProxySetting, verifySetupToken } from "./security.js";
|
|
43
45
|
import { ImageCaptchaService } from "./image-captcha.js";
|
|
44
46
|
import { ImService } from "./im.js";
|
|
45
47
|
import { ImOrchestrator } from "./im-orchestrator.js";
|
|
@@ -146,6 +148,7 @@ const registrationSchema = z.object({
|
|
|
146
148
|
password: passwordSchema,
|
|
147
149
|
passwordConfirmation: passwordSchema,
|
|
148
150
|
setupToken: z.string().max(500).optional(),
|
|
151
|
+
inviteCode: z.string().trim().max(32).optional(),
|
|
149
152
|
...captchaFields
|
|
150
153
|
}).strict().refine((input) => input.password === input.passwordConfirmation, {
|
|
151
154
|
path: ["passwordConfirmation"],
|
|
@@ -164,6 +167,20 @@ const desktopLoginSchema = z.object({
|
|
|
164
167
|
clientVersion: z.string().trim().min(1).max(80),
|
|
165
168
|
...captchaFields
|
|
166
169
|
}).strict();
|
|
170
|
+
const desktopRegistrationSchema = z.object({
|
|
171
|
+
username: usernameSchema,
|
|
172
|
+
password: passwordSchema,
|
|
173
|
+
passwordConfirmation: passwordSchema,
|
|
174
|
+
setupToken: z.string().max(500).optional(),
|
|
175
|
+
inviteCode: z.string().trim().max(32).optional(),
|
|
176
|
+
desktopId: z.string().uuid(),
|
|
177
|
+
profileId: z.string().uuid(),
|
|
178
|
+
clientVersion: z.string().trim().min(1).max(80),
|
|
179
|
+
...captchaFields
|
|
180
|
+
}).strict().refine((input) => input.password === input.passwordConfirmation, {
|
|
181
|
+
path: ["passwordConfirmation"],
|
|
182
|
+
message: "两次输入的密码不一致"
|
|
183
|
+
});
|
|
167
184
|
const userUpdateSchema = z.object({ role: z.enum(["admin", "user"]).optional(), status: z.enum(["active", "disabled"]).optional() }).strict();
|
|
168
185
|
const memberRoleValueSchema = z.enum(["editor", "settings-editor", "viewer"]);
|
|
169
186
|
const moduleAccessSchema = z.enum(["none", "read", "write"]);
|
|
@@ -299,9 +316,9 @@ const characterSchema = z.object({
|
|
|
299
316
|
aliases: z.array(z.string().trim().min(1).max(200)).max(100).optional(),
|
|
300
317
|
raceId: identifier.nullable().optional(),
|
|
301
318
|
organizationIds: z.array(identifier).max(100).optional(),
|
|
302
|
-
attributes:
|
|
319
|
+
attributes: characterAttributesInputSchema.optional(),
|
|
303
320
|
profile: jsonObject.optional(),
|
|
304
|
-
currentState:
|
|
321
|
+
currentState: characterStateInputSchema.optional(),
|
|
305
322
|
lockedFields: optionalStrings,
|
|
306
323
|
firstChapterId: identifier.nullable().optional()
|
|
307
324
|
}).strict();
|
|
@@ -1266,6 +1283,7 @@ function redactVersionSnapshots(value, mapper) {
|
|
|
1266
1283
|
export function createRuntime(options) {
|
|
1267
1284
|
const uploadLimits = options.uploadLimits ?? DEFAULT_IMAGE_UPLOAD_LIMITS;
|
|
1268
1285
|
const aiChatTabLimit = options.aiChatTabLimit ?? DEFAULT_AI_CHAT_TAB_LIMIT;
|
|
1286
|
+
const chapterAnnotationNoteMaxLength = options.chapterAnnotationNoteMaxLength ?? DEFAULT_CHAPTER_ANNOTATION_NOTE_MAX_LENGTH;
|
|
1269
1287
|
const requestedAiStreamHeartbeatIntervalMs = Number(options.aiStreamHeartbeatIntervalMs);
|
|
1270
1288
|
const aiStreamHeartbeatIntervalMs = Number.isFinite(requestedAiStreamHeartbeatIntervalMs)
|
|
1271
1289
|
? Math.max(1, Math.trunc(requestedAiStreamHeartbeatIntervalMs))
|
|
@@ -1437,7 +1455,7 @@ export function createRuntime(options) {
|
|
|
1437
1455
|
auth,
|
|
1438
1456
|
resolveAnalysisTask: (workId, input) => ai.resolveTaskInput(workId, input),
|
|
1439
1457
|
startAnalysisTask: (workId, input) => store.createTask(workId, input)
|
|
1440
|
-
});
|
|
1458
|
+
}, { chapterAnnotationNoteMaxLength });
|
|
1441
1459
|
ai.attachWritePlanManager(aiWritePlanManager);
|
|
1442
1460
|
const app = express();
|
|
1443
1461
|
enforceCaseInsensitiveRouting(app);
|
|
@@ -1487,6 +1505,7 @@ export function createRuntime(options) {
|
|
|
1487
1505
|
protocols: [...AI_PROVIDER_PROTOCOLS],
|
|
1488
1506
|
development: options.developmentServer === true,
|
|
1489
1507
|
aiChatTabLimit,
|
|
1508
|
+
chapterAnnotationNoteMaxLength,
|
|
1490
1509
|
uploadLimits
|
|
1491
1510
|
});
|
|
1492
1511
|
});
|
|
@@ -1502,15 +1521,38 @@ export function createRuntime(options) {
|
|
|
1502
1521
|
app.use(createSameOriginMiddleware());
|
|
1503
1522
|
app.use("/api/sync/works", express.json({ limit: "3mb" }));
|
|
1504
1523
|
app.use(express.json({ limit: "2mb" }));
|
|
1524
|
+
const registrationMode = resolveRegistrationMode(options.security);
|
|
1525
|
+
const registrationEnabled = isRegistrationEnabled(registrationMode);
|
|
1526
|
+
const registrationPolicy = (setupRequired) => ({
|
|
1527
|
+
registrationMode,
|
|
1528
|
+
registrationOpen: registrationEnabled,
|
|
1529
|
+
inviteRequired: registrationMode === "invite" && !setupRequired
|
|
1530
|
+
});
|
|
1531
|
+
const resolvedInviteCode = (inviteCode, setupRequired) => {
|
|
1532
|
+
if (registrationMode !== "invite" || setupRequired)
|
|
1533
|
+
return undefined;
|
|
1534
|
+
const trimmed = inviteCode?.trim() ?? "";
|
|
1535
|
+
if (!trimmed)
|
|
1536
|
+
throw new AppError(400, "INVITE_CODE_REQUIRED", "当前部署需要邀请码才能注册");
|
|
1537
|
+
return trimmed;
|
|
1538
|
+
};
|
|
1505
1539
|
app.get("/api/auth/session", (request, response) => {
|
|
1506
1540
|
const desktopSession = auth.authenticateDesktop(request);
|
|
1507
1541
|
const session = desktopSession ? null : auth.authenticate(request);
|
|
1508
|
-
const registrationOpen = options.security?.allowRegistration === true;
|
|
1509
1542
|
const setupRequired = !auth.hasUsers();
|
|
1510
1543
|
const setupTokenRequired = setupRequired && Boolean(options.security?.setupToken);
|
|
1544
|
+
const policy = registrationPolicy(setupRequired);
|
|
1511
1545
|
const developmentUser = getDevelopmentUser();
|
|
1512
1546
|
if (!session && developmentUser) {
|
|
1513
|
-
data(response, {
|
|
1547
|
+
data(response, {
|
|
1548
|
+
authenticated: true,
|
|
1549
|
+
user: developmentUser,
|
|
1550
|
+
csrfToken: null,
|
|
1551
|
+
bootId,
|
|
1552
|
+
setupRequired: false,
|
|
1553
|
+
setupTokenRequired: false,
|
|
1554
|
+
...registrationPolicy(false)
|
|
1555
|
+
});
|
|
1514
1556
|
return;
|
|
1515
1557
|
}
|
|
1516
1558
|
const interactiveSession = desktopSession ?? session;
|
|
@@ -1522,25 +1564,33 @@ export function createRuntime(options) {
|
|
|
1522
1564
|
bootId,
|
|
1523
1565
|
setupRequired: false,
|
|
1524
1566
|
setupTokenRequired: false,
|
|
1525
|
-
|
|
1567
|
+
...registrationPolicy(false)
|
|
1526
1568
|
}
|
|
1527
|
-
: { authenticated: false, user: null, csrfToken: null, bootId, setupRequired, setupTokenRequired,
|
|
1569
|
+
: { authenticated: false, user: null, csrfToken: null, bootId, setupRequired, setupTokenRequired, ...policy });
|
|
1528
1570
|
});
|
|
1529
1571
|
app.get("/api/auth/captcha", (_request, response) => {
|
|
1530
1572
|
data(response, captcha.create());
|
|
1531
1573
|
});
|
|
1532
1574
|
app.post("/api/auth/register", (request, response) => {
|
|
1533
|
-
if (
|
|
1575
|
+
if (!registrationEnabled) {
|
|
1534
1576
|
throw new AppError(403, "REGISTRATION_DISABLED", "当前部署已关闭新用户注册");
|
|
1535
1577
|
}
|
|
1536
1578
|
const input = parse(registrationSchema, request.body);
|
|
1537
1579
|
captcha.consume(input.captchaId, input.captchaAnswer);
|
|
1538
|
-
|
|
1580
|
+
const setupRequired = !auth.hasUsers();
|
|
1581
|
+
if (setupRequired && !verifySetupToken(options.security?.setupToken, input.setupToken)) {
|
|
1539
1582
|
throw new AppError(403, "SETUP_TOKEN_INVALID", "初始化令牌无效或未配置");
|
|
1540
1583
|
}
|
|
1541
|
-
const result = auth.register({
|
|
1584
|
+
const result = auth.register({
|
|
1585
|
+
username: input.username,
|
|
1586
|
+
password: input.password,
|
|
1587
|
+
inviteCode: resolvedInviteCode(input.inviteCode, setupRequired)
|
|
1588
|
+
});
|
|
1542
1589
|
setSessionCookie(response, result.token, request.secure);
|
|
1543
|
-
runWithRequestActor(result.session.user, () => store.audit(null, "user.registered", "user", result.session.user.userId, {
|
|
1590
|
+
runWithRequestActor(result.session.user, () => store.audit(null, "user.registered", "user", result.session.user.userId, {
|
|
1591
|
+
role: result.session.user.role,
|
|
1592
|
+
...(result.inviteId ? { inviteId: result.inviteId } : {})
|
|
1593
|
+
}));
|
|
1544
1594
|
logger.info("auth.registration.succeeded", { actorRef: accountReference(result.session.user.userId) });
|
|
1545
1595
|
data(response, { user: result.session.user, csrfToken: result.session.csrfToken }, 201);
|
|
1546
1596
|
});
|
|
@@ -1567,6 +1617,34 @@ export function createRuntime(options) {
|
|
|
1567
1617
|
logger.info("auth.desktop_login.succeeded", { actorRef: accountReference(result.session.user.userId) });
|
|
1568
1618
|
data(response, { token: result.token, expiresAt: result.session.expiresAt, user: result.session.user });
|
|
1569
1619
|
});
|
|
1620
|
+
app.post("/api/desktop/auth/register", (request, response) => {
|
|
1621
|
+
if (!registrationEnabled) {
|
|
1622
|
+
throw new AppError(403, "REGISTRATION_DISABLED", "当前部署已关闭新用户注册");
|
|
1623
|
+
}
|
|
1624
|
+
const input = parse(desktopRegistrationSchema, request.body);
|
|
1625
|
+
captcha.consume(input.captchaId, input.captchaAnswer);
|
|
1626
|
+
const setupRequired = !auth.hasUsers();
|
|
1627
|
+
if (setupRequired && !verifySetupToken(options.security?.setupToken, input.setupToken)) {
|
|
1628
|
+
throw new AppError(403, "SETUP_TOKEN_INVALID", "初始化令牌无效或未配置");
|
|
1629
|
+
}
|
|
1630
|
+
const result = auth.registerDesktop({
|
|
1631
|
+
username: input.username,
|
|
1632
|
+
password: input.password,
|
|
1633
|
+
inviteCode: resolvedInviteCode(input.inviteCode, setupRequired),
|
|
1634
|
+
desktopId: input.desktopId,
|
|
1635
|
+
profileId: input.profileId,
|
|
1636
|
+
clientVersion: input.clientVersion
|
|
1637
|
+
});
|
|
1638
|
+
imOrchestrator.disconnectUser(result.session.user.userId);
|
|
1639
|
+
response.setHeader("Cache-Control", "no-store");
|
|
1640
|
+
runWithRequestActor(result.session.user, () => store.audit(null, "user.registered", "user", result.session.user.userId, {
|
|
1641
|
+
role: result.session.user.role,
|
|
1642
|
+
source: "desktop",
|
|
1643
|
+
...(result.inviteId ? { inviteId: result.inviteId } : {})
|
|
1644
|
+
}));
|
|
1645
|
+
logger.info("auth.desktop_registration.succeeded", { actorRef: accountReference(result.session.user.userId) });
|
|
1646
|
+
data(response, { token: result.token, expiresAt: result.session.expiresAt, user: result.session.user }, 201);
|
|
1647
|
+
});
|
|
1570
1648
|
app.use(createUserSessionMiddleware(auth, {
|
|
1571
1649
|
disabled: options.disableUserAuth === true,
|
|
1572
1650
|
resolveBypassUser: getDevelopmentUser
|
|
@@ -1714,6 +1792,28 @@ export function createRuntime(options) {
|
|
|
1714
1792
|
store.audit(null, "user.updated", "user", updated.userId, { role: updated.role, status: updated.status });
|
|
1715
1793
|
data(response, updated);
|
|
1716
1794
|
});
|
|
1795
|
+
app.get("/api/registration-invites", (request, response) => {
|
|
1796
|
+
if (!request.authUser)
|
|
1797
|
+
throw new AppError(401, "AUTH_REQUIRED", "请先登录");
|
|
1798
|
+
if (request.authUser.role !== "admin")
|
|
1799
|
+
throw new AppError(403, "ADMIN_REQUIRED", "该操作仅限系统管理员");
|
|
1800
|
+
data(response, {
|
|
1801
|
+
registrationMode,
|
|
1802
|
+
registrationOpen: registrationEnabled,
|
|
1803
|
+
items: auth.listRegistrationInvites()
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
app.post("/api/registration-invites", (request, response) => {
|
|
1807
|
+
if (!request.authUser)
|
|
1808
|
+
throw new AppError(401, "AUTH_REQUIRED", "请先登录");
|
|
1809
|
+
if (request.authUser.role !== "admin")
|
|
1810
|
+
throw new AppError(403, "ADMIN_REQUIRED", "该操作仅限系统管理员");
|
|
1811
|
+
parse(z.object({}).strict(), request.body ?? {});
|
|
1812
|
+
const created = auth.createRegistrationInvite(request.authUser);
|
|
1813
|
+
store.audit(null, "registration-invite.created", "registration-invite", created.id, {});
|
|
1814
|
+
logger.info("auth.registration_invite.created", { actorRef: accountReference(request.authUser.userId) });
|
|
1815
|
+
data(response, created, 201);
|
|
1816
|
+
});
|
|
1717
1817
|
const requireImUser = (request) => {
|
|
1718
1818
|
if (!request.authUser || !hasInteractiveSession(request)) {
|
|
1719
1819
|
throw new AppError(401, "SESSION_REQUIRED", "请使用交互式会话访问 IM");
|
|
@@ -2404,12 +2504,12 @@ export function createRuntime(options) {
|
|
|
2404
2504
|
kind: z.enum(["note", "todo"]),
|
|
2405
2505
|
startLine: z.number().int().positive(),
|
|
2406
2506
|
endLine: z.number().int().positive(),
|
|
2407
|
-
note: z.string().trim().min(1).max(
|
|
2507
|
+
note: z.string().trim().min(1).max(chapterAnnotationNoteMaxLength)
|
|
2408
2508
|
}).strict().refine((value) => value.endLine >= value.startLine, { message: "结束行不能早于开始行", path: ["endLine"] }), request.body);
|
|
2409
2509
|
data(response, store.createChapterAnnotation(request.params.chapterId, input), 201);
|
|
2410
2510
|
});
|
|
2411
2511
|
app.patch("/api/chapter-annotations/:annotationId", (request, response) => {
|
|
2412
|
-
const input = parse(z.object({ note: z.string().trim().min(1).max(
|
|
2512
|
+
const input = parse(z.object({ note: z.string().trim().min(1).max(chapterAnnotationNoteMaxLength).optional(), status: z.enum(["open", "resolved"]).optional(), expectedVersionNo: expectedVersionNoSchema }).strict().refine((value) => value.note !== undefined || value.status !== undefined, { message: "至少需要修改一项" }), request.body);
|
|
2413
2513
|
const { expectedVersionNo, ...update } = input;
|
|
2414
2514
|
data(response, store.updateChapterAnnotation(request.params.annotationId, update, expectedVersionNo));
|
|
2415
2515
|
});
|
|
@@ -3475,6 +3575,19 @@ export function createRuntime(options) {
|
|
|
3475
3575
|
const permissions = requestPermissions(request, String(conversation.workId));
|
|
3476
3576
|
data(response, redactAiConversationMessage(message, permissions), 201);
|
|
3477
3577
|
});
|
|
3578
|
+
app.post("/api/ai-conversations/:conversationId/steer", (request, response) => {
|
|
3579
|
+
const input = parse(z.object({
|
|
3580
|
+
content: nonEmpty.max(100_000)
|
|
3581
|
+
}).strict(), request.body ?? {});
|
|
3582
|
+
const conversation = store.getAiConversationSummary(request.params.conversationId);
|
|
3583
|
+
const workId = String(conversation.workId);
|
|
3584
|
+
const permissions = requestPermissions(request, workId);
|
|
3585
|
+
if (!canWriteWorkModule(permissions, "ai-chat")) {
|
|
3586
|
+
throw new AppError(403, "WORK_MODULE_WRITE_DENIED", "你没有使用创作助手的权限");
|
|
3587
|
+
}
|
|
3588
|
+
const entry = ai.enqueueSteer(workId, request.params.conversationId, input.content);
|
|
3589
|
+
data(response, { ...entry, status: "pending" }, 202);
|
|
3590
|
+
});
|
|
3478
3591
|
app.post("/api/ai-conversations/:conversationId/context/prepare", async (request, response) => {
|
|
3479
3592
|
const input = parse(z.object({
|
|
3480
3593
|
modelId: identifier.optional(),
|
|
@@ -3972,6 +4085,7 @@ export function createRuntime(options) {
|
|
|
3972
4085
|
if (!response.writableEnded && !response.destroyed)
|
|
3973
4086
|
response.write(`event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`);
|
|
3974
4087
|
};
|
|
4088
|
+
let releaseSteer = () => undefined;
|
|
3975
4089
|
try {
|
|
3976
4090
|
if (!existingRequest) {
|
|
3977
4091
|
preparedChatImageAttachments = await ai.prepareChatImageAttachments(request.params.workId, modelId, input.imageAttachmentIds ?? [], permissions);
|
|
@@ -4092,6 +4206,7 @@ export function createRuntime(options) {
|
|
|
4092
4206
|
questionId: pendingQuestion.id
|
|
4093
4207
|
});
|
|
4094
4208
|
}
|
|
4209
|
+
releaseSteer = ai.steerMailbox.begin(conversationId);
|
|
4095
4210
|
const result = await ai.createStreamingChat({
|
|
4096
4211
|
workId: request.params.workId,
|
|
4097
4212
|
instruction: resolvedInstruction,
|
|
@@ -4103,6 +4218,10 @@ export function createRuntime(options) {
|
|
|
4103
4218
|
onToolCall: (toolCall, round) => sendEvent("tool_call", { ...toolCall, round }),
|
|
4104
4219
|
onProcessStep: (step) => sendEvent("process_step", step),
|
|
4105
4220
|
onContextCompacted: (event) => sendEvent("context_compacted", event),
|
|
4221
|
+
onSteer: (event) => sendEvent("steer", {
|
|
4222
|
+
message: redactAiConversationMessage(event.message, permissions),
|
|
4223
|
+
applied: true
|
|
4224
|
+
}),
|
|
4106
4225
|
conversationId,
|
|
4107
4226
|
excludeConversationMessageId: currentMessageId,
|
|
4108
4227
|
...(currentMessageId ? { assistantMessageRequestId: `assistant:${currentMessageId}` } : {}),
|
|
@@ -4159,6 +4278,7 @@ export function createRuntime(options) {
|
|
|
4159
4278
|
}
|
|
4160
4279
|
}
|
|
4161
4280
|
finally {
|
|
4281
|
+
releaseSteer();
|
|
4162
4282
|
stopStreamHeartbeat();
|
|
4163
4283
|
if (streamRequestId && !streamRequestFinished && !stopping) {
|
|
4164
4284
|
store.finishAiConversationStreamRequest(streamRequestId, "cancelled", "stream_closed");
|