@actalk/inkos-studio 1.3.4 → 1.3.5-canary.33.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/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +110 -34
- package/dist/api/server.js.map +1 -1
- package/dist/assets/{_baseUniq-CUXaSiLD.js → _baseUniq-CpTphU9M.js} +1 -1
- package/dist/assets/{arc-FvWBPXrI.js → arc-BmdlE7tJ.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-B8UpD9eK.js → architectureDiagram-Q4EWVU46-B08ry5zQ.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-CPxSckyQ.js → blockDiagram-DXYQGD6D-BNxUKAO_.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-BHFUy4R7.js → c4Diagram-AHTNJAMY-DoV0FOWJ.js} +1 -1
- package/dist/assets/channel-Q-ZfuLB-.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-wWYh9dQR.js → chunk-4BX2VUAB-CvSWEgQl.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-B_UhnwUd.js → chunk-4TB4RGXK-BQAIQHv6.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DJ2soGkg.js → chunk-55IACEB6-lFr-e-c6.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-C4gQb1PT.js → chunk-EDXVE4YY-5yQ0WRsM.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-3bX5pTEy.js → chunk-FMBD7UC4-C-hhtNlL.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-ypA0Yl6Q.js → chunk-OYMX7WX6-CjZNxR6R.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-DMNDYw8g.js → chunk-QZHKN3VN-eWWqsAhu.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-CGOQPqQ5.js → chunk-YZCP3GAM-nJ78mfvK.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-D2TnKGt8.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-D2TnKGt8.js +1 -0
- package/dist/assets/clone-pNCFH49W.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-DBNsFf7M.js → cose-bilkent-S5V4N54A-CmEnoxru.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-uxOmz6yQ.js → dagre-KV5264BT-CA8eB0J-.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-B-7RdZ-8.js → diagram-5BDNPKRD-_vtscRZj.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-Bhteb-vj.js → diagram-G4DWMVQ6-DGxxw7hL.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-maGfd5tL.js → diagram-MMDJMWI5-D9un5UaS.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-BovUtJbC.js → diagram-TYMM5635-BG_AbiPQ.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-6ffWXGPL.js → erDiagram-SMLLAGMA-PXwO0FVu.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-4fV85_dF.js → flowDiagram-DWJPFMVM-BAL1S6ll.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-bLy7-lid.js → ganttDiagram-T4ZO3ILL-CjfyXTlO.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-hCqDpD-N.js → gitGraphDiagram-UUTBAWPF-GdUdCj3c.js} +1 -1
- package/dist/assets/{graph-Bz91hyDc.js → graph-DG9IRQPc.js} +1 -1
- package/dist/assets/{highlighted-body-OFNGDK62-BNyd72qC.js → highlighted-body-OFNGDK62-DyFe3hzx.js} +1 -1
- package/dist/assets/index-BbfGoXqD.js +1277 -0
- package/dist/assets/index-D12V4C17.css +1 -0
- package/dist/assets/{infoDiagram-42DDH7IO-D-Xu56Hx.js → infoDiagram-42DDH7IO-QI2F2hJ3.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-DhACBOSM.js → ishikawaDiagram-UXIWVN3A-rixFkGkz.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-0X971G0m.js → journeyDiagram-VCZTEJTY-Btze11PL.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-DvOLUhmo.js → kanban-definition-6JOO6SKY-D8ERKCyh.js} +1 -1
- package/dist/assets/{layout-DF8ziATB.js → layout-CsMnAU1N.js} +1 -1
- package/dist/assets/{linear-D_W5Fxl0.js → linear-vWd9anT-.js} +1 -1
- package/dist/assets/{min-Smhc5oQZ.js → min-D0IR8kco.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-B1985XGN.js → mindmap-definition-QFDTVHPH-B9QfdTTi.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-bAWD-keH.js → pieDiagram-DEJITSTG-B_04JnhY.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-C-gvS6yv.js → quadrantDiagram-34T5L4WZ-lLuRcyTx.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-BUQl_0Ji.js → requirementDiagram-MS252O5E-BQgrDamx.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-C4SiZWPm.js → sankeyDiagram-XADWPNL6-DvhmUNk5.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-Dq797rMU.js → sequenceDiagram-FGHM5R23-BWwpEzOy.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-C5Es6t2q.js → stateDiagram-FHFEXIEX-DYCX0F3e.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-DDAseEQj.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-BlUkWWqo.js → timeline-definition-GMOUNBTQ-CN3nhCvg.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-D_rpvcv7.js → vennDiagram-DHZGUBPP-BsUxiq2N.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-8oOVQEtX.js → wardley-RL74JXVD-Kbj_jXoM.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-C9J1BpO8.js → wardleyDiagram-NUSXRM2D-Ct795uew.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-DqS5X4cE.js → xychartDiagram-5P7HB3ND-B6Vf25Ms.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/assets/channel-CTJbcQ5k.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CM2qd_FL.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CM2qd_FL.js +0 -1
- package/dist/assets/clone-Ft-RfRjl.js +0 -1
- package/dist/assets/index-CFZHr4vH.css +0 -1
- package/dist/assets/index-CnJ4cmEe.js +0 -1267
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Dc3H6t-6.js +0 -1
package/dist/api/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAmCL,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAC;AAyd5B,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,8EA8zD5E;AAID,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,SAAO,EACX,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
|
package/dist/api/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { cors } from "hono/cors";
|
|
3
3
|
import { streamSSE } from "hono/streaming";
|
|
4
4
|
import { serve } from "@hono/node-server";
|
|
5
|
-
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook,
|
|
5
|
+
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, persistBookSession, appendBookSessionMessage, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, getServiceApiKey, listModelsForService, chatCompletion, buildExportArtifact, GLOBAL_ENV_PATH, } from "@actalk/inkos-core";
|
|
6
6
|
import { access, readFile, readdir, writeFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { isSafeBookId } from "./safety.js";
|
|
@@ -66,6 +66,8 @@ function extractToolError(result) {
|
|
|
66
66
|
}
|
|
67
67
|
const subscribers = new Set();
|
|
68
68
|
const bookCreateStatus = new Map();
|
|
69
|
+
// 内存缓存:service -> 模型列表 + 更新时间戳;避免每次 sidebar 挂载时都打真实 LLM /models
|
|
70
|
+
const modelListCache = new Map();
|
|
69
71
|
function broadcast(event, data) {
|
|
70
72
|
for (const handler of subscribers) {
|
|
71
73
|
handler(event, data);
|
|
@@ -426,7 +428,19 @@ export function createStudioServer(initialConfig, root) {
|
|
|
426
428
|
}
|
|
427
429
|
async function buildPipelineConfig(overrides) {
|
|
428
430
|
const currentConfig = overrides?.currentConfig ?? await loadCurrentProjectConfig();
|
|
429
|
-
const
|
|
431
|
+
const scopedSseSink = overrides?.sessionIdForSSE
|
|
432
|
+
? {
|
|
433
|
+
write(entry) {
|
|
434
|
+
broadcast("log", {
|
|
435
|
+
sessionId: overrides.sessionIdForSSE,
|
|
436
|
+
level: entry.level,
|
|
437
|
+
tag: entry.tag,
|
|
438
|
+
message: entry.message,
|
|
439
|
+
});
|
|
440
|
+
},
|
|
441
|
+
}
|
|
442
|
+
: sseSink;
|
|
443
|
+
const logger = createLogger({ tag: "studio", sinks: [scopedSseSink, consoleSink] });
|
|
430
444
|
return {
|
|
431
445
|
client: overrides?.client ?? createLLMClient(currentConfig.llm),
|
|
432
446
|
model: overrides?.model ?? currentConfig.llm.model,
|
|
@@ -437,6 +451,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
437
451
|
logger,
|
|
438
452
|
onStreamProgress: (progress) => {
|
|
439
453
|
broadcast("llm:progress", {
|
|
454
|
+
...(overrides?.sessionIdForSSE ? { sessionId: overrides.sessionIdForSSE } : {}),
|
|
440
455
|
status: progress.status,
|
|
441
456
|
elapsedMs: progress.elapsedMs,
|
|
442
457
|
totalChars: progress.totalChars,
|
|
@@ -811,19 +826,28 @@ export function createStudioServer(initialConfig, root) {
|
|
|
811
826
|
});
|
|
812
827
|
app.get("/api/v1/services/:service/models", async (c) => {
|
|
813
828
|
const service = c.req.param("service");
|
|
829
|
+
const refresh = c.req.query("refresh") === "1";
|
|
814
830
|
const apiKey = c.req.query("apiKey") || await getServiceApiKey(root, service);
|
|
815
831
|
// No key = no models
|
|
816
832
|
if (!apiKey)
|
|
817
833
|
return c.json({ models: [] });
|
|
818
|
-
// Fast path: services with knownModels return immediately
|
|
819
834
|
const preset = resolveServicePreset(isCustomServiceId(service) ? "custom" : service);
|
|
835
|
+
const resolvedBaseUrl = await resolveConfiguredServiceBaseUrl(root, service);
|
|
836
|
+
// Cache by service + resolved baseUrl + apiKey fingerprint; valid for 10 min unless ?refresh=1
|
|
837
|
+
const cacheKey = `${service}::${resolvedBaseUrl ?? ""}::${apiKey.slice(-8)}`;
|
|
838
|
+
if (!refresh) {
|
|
839
|
+
const cached = modelListCache.get(cacheKey);
|
|
840
|
+
if (cached && Date.now() - cached.at < 10 * 60 * 1000) {
|
|
841
|
+
return c.json({ models: cached.models });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// Fast path: services with knownModels return immediately
|
|
820
845
|
if (preset?.knownModels && preset.knownModels.length > 0) {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
});
|
|
846
|
+
const models = preset.knownModels.map((id) => ({ id, name: id }));
|
|
847
|
+
modelListCache.set(cacheKey, { models, at: Date.now() });
|
|
848
|
+
return c.json({ models });
|
|
824
849
|
}
|
|
825
850
|
// Simple /models API call + fallback to pi-ai built-in list (no slow probe)
|
|
826
|
-
const resolvedBaseUrl = await resolveConfiguredServiceBaseUrl(root, service);
|
|
827
851
|
if (!resolvedBaseUrl)
|
|
828
852
|
return c.json({ models: [] });
|
|
829
853
|
const modelsBase = preset?.modelsBaseUrl ?? resolvedBaseUrl;
|
|
@@ -844,6 +868,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
844
868
|
const builtIn = await listModelsForService(service, apiKey);
|
|
845
869
|
models = builtIn.map((m) => ({ id: m.id, name: m.name }));
|
|
846
870
|
}
|
|
871
|
+
modelListCache.set(cacheKey, { models, at: Date.now() });
|
|
847
872
|
return c.json({ models });
|
|
848
873
|
});
|
|
849
874
|
// --- Project info ---
|
|
@@ -1001,13 +1026,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1001
1026
|
app.get("/api/v1/sessions", async (c) => {
|
|
1002
1027
|
const bookId = c.req.query("bookId");
|
|
1003
1028
|
const sessions = await listBookSessions(root, bookId === undefined ? null : bookId === "null" ? null : bookId);
|
|
1004
|
-
return c.json({ sessions
|
|
1005
|
-
sessionId: s.sessionId,
|
|
1006
|
-
bookId: s.bookId,
|
|
1007
|
-
messageCount: s.messages.length,
|
|
1008
|
-
createdAt: s.createdAt,
|
|
1009
|
-
updatedAt: s.updatedAt,
|
|
1010
|
-
})) });
|
|
1029
|
+
return c.json({ sessions });
|
|
1011
1030
|
});
|
|
1012
1031
|
app.get("/api/v1/sessions/:sessionId", async (c) => {
|
|
1013
1032
|
const session = await loadBookSession(root, c.req.param("sessionId"));
|
|
@@ -1018,30 +1037,49 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1018
1037
|
app.post("/api/v1/sessions", async (c) => {
|
|
1019
1038
|
const body = await c.req.json().catch(() => ({}));
|
|
1020
1039
|
const bookId = body.bookId ?? null;
|
|
1021
|
-
const
|
|
1040
|
+
const sessionId = body.sessionId;
|
|
1041
|
+
// sessionId 只允许 timestamp-random 格式;防止注入任意文件名
|
|
1042
|
+
const safeSessionId = sessionId && /^[0-9]+-[a-z0-9]+$/.test(sessionId) ? sessionId : undefined;
|
|
1043
|
+
const session = await createAndPersistBookSession(root, bookId, safeSessionId);
|
|
1044
|
+
return c.json({ session });
|
|
1045
|
+
});
|
|
1046
|
+
app.put("/api/v1/sessions/:sessionId", async (c) => {
|
|
1047
|
+
const sessionId = c.req.param("sessionId");
|
|
1048
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1049
|
+
const title = body.title?.trim();
|
|
1050
|
+
if (!title) {
|
|
1051
|
+
throw new ApiError(400, "INVALID_SESSION_TITLE", "Session title is required");
|
|
1052
|
+
}
|
|
1053
|
+
const session = await renameBookSession(root, sessionId, title);
|
|
1054
|
+
if (!session) {
|
|
1055
|
+
return c.json({ error: "Session not found" }, 404);
|
|
1056
|
+
}
|
|
1022
1057
|
return c.json({ session });
|
|
1023
1058
|
});
|
|
1059
|
+
app.delete("/api/v1/sessions/:sessionId", async (c) => {
|
|
1060
|
+
await deleteBookSession(root, c.req.param("sessionId"));
|
|
1061
|
+
return c.json({ ok: true });
|
|
1062
|
+
});
|
|
1024
1063
|
app.post("/api/v1/agent", async (c) => {
|
|
1025
1064
|
const { instruction, activeBookId, sessionId: reqSessionId, model: reqModel, service: reqService } = await c.req.json();
|
|
1026
1065
|
const sessionId = reqSessionId;
|
|
1027
1066
|
if (!instruction?.trim()) {
|
|
1028
1067
|
return c.json({ error: "No instruction provided" }, 400);
|
|
1029
1068
|
}
|
|
1030
|
-
|
|
1069
|
+
if (!sessionId?.trim()) {
|
|
1070
|
+
throw new ApiError(400, "SESSION_ID_REQUIRED", "sessionId is required");
|
|
1071
|
+
}
|
|
1072
|
+
broadcast("agent:start", { instruction, activeBookId, sessionId });
|
|
1031
1073
|
try {
|
|
1032
1074
|
// Load config + create LLM client (pipeline created after model resolution)
|
|
1033
1075
|
const config = await loadCurrentProjectConfig({ requireApiKey: false });
|
|
1034
1076
|
const client = createLLMClient(config.llm);
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
bookSession =
|
|
1039
|
-
(await loadBookSession(root, sessionId)) ??
|
|
1040
|
-
(await findOrCreateBookSession(root, activeBookId ?? null));
|
|
1041
|
-
}
|
|
1042
|
-
else {
|
|
1043
|
-
bookSession = await findOrCreateBookSession(root, activeBookId ?? null);
|
|
1077
|
+
const loadedBookSession = await loadBookSession(root, sessionId);
|
|
1078
|
+
if (!loadedBookSession) {
|
|
1079
|
+
throw new ApiError(404, "SESSION_NOT_FOUND", `Session not found: ${sessionId}`);
|
|
1044
1080
|
}
|
|
1081
|
+
let bookSession = loadedBookSession;
|
|
1082
|
+
const streamSessionId = loadedBookSession.sessionId;
|
|
1045
1083
|
// Build initial message context from persisted session
|
|
1046
1084
|
const initialMessages = bookSession.messages
|
|
1047
1085
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
@@ -1130,6 +1168,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1130
1168
|
client: pipelineClient,
|
|
1131
1169
|
model: reqModel ?? config.llm.model,
|
|
1132
1170
|
currentConfig: config,
|
|
1171
|
+
sessionIdForSSE: bookSession.sessionId,
|
|
1133
1172
|
}));
|
|
1134
1173
|
// Run pi-agent session
|
|
1135
1174
|
const collectedToolExecs = [];
|
|
@@ -1145,16 +1184,16 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1145
1184
|
if (event.type === "message_update") {
|
|
1146
1185
|
const ame = event.assistantMessageEvent;
|
|
1147
1186
|
if (ame.type === "text_delta") {
|
|
1148
|
-
broadcast("draft:delta", { text: ame.delta });
|
|
1187
|
+
broadcast("draft:delta", { sessionId: streamSessionId, text: ame.delta });
|
|
1149
1188
|
}
|
|
1150
1189
|
else if (ame.type === "thinking_delta") {
|
|
1151
|
-
broadcast("thinking:delta", { text: ame.delta });
|
|
1190
|
+
broadcast("thinking:delta", { sessionId: streamSessionId, text: ame.delta });
|
|
1152
1191
|
}
|
|
1153
1192
|
else if (ame.type === "thinking_start") {
|
|
1154
|
-
broadcast("thinking:start", {});
|
|
1193
|
+
broadcast("thinking:start", { sessionId: streamSessionId });
|
|
1155
1194
|
}
|
|
1156
1195
|
else if (ame.type === "thinking_end") {
|
|
1157
|
-
broadcast("thinking:end", {});
|
|
1196
|
+
broadcast("thinking:end", { sessionId: streamSessionId });
|
|
1158
1197
|
}
|
|
1159
1198
|
}
|
|
1160
1199
|
if (event.type === "tool_execution_start") {
|
|
@@ -1174,6 +1213,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1174
1213
|
startedAt: Date.now(),
|
|
1175
1214
|
});
|
|
1176
1215
|
broadcast("tool:start", {
|
|
1216
|
+
sessionId: streamSessionId,
|
|
1177
1217
|
id: event.toolCallId,
|
|
1178
1218
|
tool: event.toolName,
|
|
1179
1219
|
args,
|
|
@@ -1181,7 +1221,11 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1181
1221
|
});
|
|
1182
1222
|
}
|
|
1183
1223
|
if (event.type === "tool_execution_update") {
|
|
1184
|
-
broadcast("tool:update", {
|
|
1224
|
+
broadcast("tool:update", {
|
|
1225
|
+
sessionId: streamSessionId,
|
|
1226
|
+
tool: event.toolName,
|
|
1227
|
+
partialResult: event.partialResult,
|
|
1228
|
+
});
|
|
1185
1229
|
}
|
|
1186
1230
|
if (event.type === "tool_execution_end") {
|
|
1187
1231
|
const exec = collectedToolExecs.find(t => t.id === event.toolCallId);
|
|
@@ -1195,6 +1239,7 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1195
1239
|
exec.result = summarizeResult(event.result);
|
|
1196
1240
|
}
|
|
1197
1241
|
broadcast("tool:end", {
|
|
1242
|
+
sessionId: streamSessionId,
|
|
1198
1243
|
id: event.toolCallId,
|
|
1199
1244
|
tool: event.toolName,
|
|
1200
1245
|
result: event.result,
|
|
@@ -1209,6 +1254,16 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1209
1254
|
content: instruction,
|
|
1210
1255
|
timestamp: Date.now(),
|
|
1211
1256
|
});
|
|
1257
|
+
// 第一条用户消息就是 session 的标题:如果 title 还是 null,用消息内容(单行、≤20字)写入。
|
|
1258
|
+
// 后续消息不覆盖;用户手动改名通过 renameBookSession 覆盖。
|
|
1259
|
+
if (bookSession.title === null) {
|
|
1260
|
+
const oneLine = instruction.trim().replace(/\s+/g, " ");
|
|
1261
|
+
const title = oneLine.length > 20 ? `${oneLine.slice(0, 20)}…` : oneLine;
|
|
1262
|
+
if (title) {
|
|
1263
|
+
bookSession = { ...bookSession, title };
|
|
1264
|
+
broadcast("session:title", { sessionId: bookSession.sessionId, title });
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1212
1267
|
if (result.responseText) {
|
|
1213
1268
|
const lastAssistant = result.messages?.filter((m) => m.role === "assistant").pop();
|
|
1214
1269
|
const thinking = lastAssistant?.thinking;
|
|
@@ -1277,24 +1332,45 @@ export function createStudioServer(initialConfig, root) {
|
|
|
1277
1332
|
}, 502);
|
|
1278
1333
|
}
|
|
1279
1334
|
await persistBookSession(root, bookSession);
|
|
1280
|
-
broadcast("agent:complete", { instruction, activeBookId });
|
|
1335
|
+
broadcast("agent:complete", { instruction, activeBookId, sessionId: bookSession.sessionId });
|
|
1281
1336
|
// If a sub_agent created a new book during this session, broadcast book:created
|
|
1282
1337
|
// so the sidebar refreshes.
|
|
1283
1338
|
if (!activeBookId && collectedToolExecs.some((t) => t.agent === "architect" && t.status === "completed")) {
|
|
1284
1339
|
const books = await state.listBooks();
|
|
1285
1340
|
const latestBook = books.at(-1);
|
|
1286
1341
|
if (latestBook) {
|
|
1287
|
-
|
|
1342
|
+
try {
|
|
1343
|
+
const migratedSession = await migrateBookSession(root, bookSession.sessionId, latestBook);
|
|
1344
|
+
if (migratedSession) {
|
|
1345
|
+
bookSession = migratedSession;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
catch (e) {
|
|
1349
|
+
if (!(e instanceof SessionAlreadyMigratedError)) {
|
|
1350
|
+
throw e;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
broadcast("book:created", { bookId: latestBook, sessionId: bookSession.sessionId });
|
|
1288
1354
|
}
|
|
1289
1355
|
}
|
|
1290
1356
|
return c.json({
|
|
1291
1357
|
response: result.responseText,
|
|
1292
|
-
session: {
|
|
1358
|
+
session: {
|
|
1359
|
+
sessionId: bookSession.sessionId,
|
|
1360
|
+
...(bookSession.bookId ? { activeBookId: bookSession.bookId } : {}),
|
|
1361
|
+
},
|
|
1293
1362
|
});
|
|
1294
1363
|
}
|
|
1295
1364
|
catch (e) {
|
|
1365
|
+
if (e instanceof ApiError) {
|
|
1366
|
+
throw e;
|
|
1367
|
+
}
|
|
1368
|
+
if (e instanceof SessionAlreadyMigratedError) {
|
|
1369
|
+
const migratedMessage = e instanceof Error ? e.message : String(e);
|
|
1370
|
+
throw new ApiError(409, "SESSION_ALREADY_MIGRATED", migratedMessage);
|
|
1371
|
+
}
|
|
1296
1372
|
const msg = e instanceof Error ? e.message : String(e);
|
|
1297
|
-
broadcast("agent:error", { instruction, activeBookId, error: msg });
|
|
1373
|
+
broadcast("agent:error", { instruction, activeBookId, sessionId, error: msg });
|
|
1298
1374
|
// Agent busy — return 429 with user-friendly message
|
|
1299
1375
|
if (/already processing|prompt.*queue/i.test(msg)) {
|
|
1300
1376
|
return c.json({
|