@kienmatu/inkos-studio 1.0.1 → 4.0.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.
Files changed (69) hide show
  1. package/dist/api/server.d.ts.map +1 -1
  2. package/dist/api/server.js +156 -23
  3. package/dist/api/server.js.map +1 -1
  4. package/dist/api/task-store.d.ts +1 -0
  5. package/dist/api/task-store.d.ts.map +1 -1
  6. package/dist/api/task-store.js +3 -0
  7. package/dist/api/task-store.js.map +1 -1
  8. package/dist/assets/{FilmWizard-CxoOJ7MQ.js → FilmWizard-BmrPbkFz.js} +1 -1
  9. package/dist/assets/{FlowView-Bt0EtmEr.js → FlowView-SbRviH5b.js} +1 -1
  10. package/dist/assets/{_baseUniq-ZL9pmMqM.js → _baseUniq-BkEZO23S.js} +1 -1
  11. package/dist/assets/{arc-B8qI0bEW.js → arc-DzsKsGBX.js} +1 -1
  12. package/dist/assets/{architectureDiagram-Q4EWVU46-BMqx63uW.js → architectureDiagram-Q4EWVU46-7-keOyLG.js} +1 -1
  13. package/dist/assets/{blockDiagram-DXYQGD6D-CpTeW9T5.js → blockDiagram-DXYQGD6D-CkWKO82x.js} +1 -1
  14. package/dist/assets/{c4Diagram-AHTNJAMY-DEMZhRny.js → c4Diagram-AHTNJAMY-D6DRnA_K.js} +1 -1
  15. package/dist/assets/channel-vMyuwkSI.js +1 -0
  16. package/dist/assets/{chunk-4BX2VUAB-CATTVIYo.js → chunk-4BX2VUAB-Cmg88BNH.js} +1 -1
  17. package/dist/assets/{chunk-4TB4RGXK-BVSOdPwi.js → chunk-4TB4RGXK-CMZP43JX.js} +1 -1
  18. package/dist/assets/{chunk-55IACEB6-roBKSgqh.js → chunk-55IACEB6-CDFZTSkf.js} +1 -1
  19. package/dist/assets/{chunk-EDXVE4YY-DcL-JJvQ.js → chunk-EDXVE4YY-f2fO2QMj.js} +1 -1
  20. package/dist/assets/{chunk-FMBD7UC4-CTt3nqty.js → chunk-FMBD7UC4-C8gUvwB8.js} +1 -1
  21. package/dist/assets/{chunk-OYMX7WX6-BKloZua_.js → chunk-OYMX7WX6-DaqAIa4a.js} +1 -1
  22. package/dist/assets/{chunk-QZHKN3VN-CksGJW6L.js → chunk-QZHKN3VN-BdHnVE44.js} +1 -1
  23. package/dist/assets/{chunk-YZCP3GAM-DFUs85Mo.js → chunk-YZCP3GAM-CTNEwMvq.js} +1 -1
  24. package/dist/assets/classDiagram-6PBFFD2Q-DlUnDT-R.js +1 -0
  25. package/dist/assets/classDiagram-v2-HSJHXN6E-DlUnDT-R.js +1 -0
  26. package/dist/assets/clone-CgzdGWcq.js +1 -0
  27. package/dist/assets/{cose-bilkent-S5V4N54A-BhjR9QKW.js → cose-bilkent-S5V4N54A-KpZHVU8t.js} +1 -1
  28. package/dist/assets/{dagre-KV5264BT-S0WgqeWF.js → dagre-KV5264BT-aiy2OT89.js} +1 -1
  29. package/dist/assets/{diagram-5BDNPKRD-iziIar_1.js → diagram-5BDNPKRD-pNT8yMDm.js} +1 -1
  30. package/dist/assets/{diagram-G4DWMVQ6-DGiQ4b3V.js → diagram-G4DWMVQ6-oxZhaIi5.js} +1 -1
  31. package/dist/assets/{diagram-MMDJMWI5-Ctfv9R4Z.js → diagram-MMDJMWI5-kwn_dxKS.js} +1 -1
  32. package/dist/assets/{diagram-TYMM5635-DpzPFDFI.js → diagram-TYMM5635-DXa2AxN_.js} +1 -1
  33. package/dist/assets/{erDiagram-SMLLAGMA-e51DloYW.js → erDiagram-SMLLAGMA-CAgK4N7D.js} +1 -1
  34. package/dist/assets/{flowDiagram-DWJPFMVM-TyuMrie7.js → flowDiagram-DWJPFMVM-BeYsHcsf.js} +1 -1
  35. package/dist/assets/{ganttDiagram-T4ZO3ILL-sewHvhyH.js → ganttDiagram-T4ZO3ILL-BU8wq8NL.js} +1 -1
  36. package/dist/assets/{gitGraphDiagram-UUTBAWPF-DI7YNaAi.js → gitGraphDiagram-UUTBAWPF-BgivHLNi.js} +1 -1
  37. package/dist/assets/{graph-iCANBjLp.js → graph-CSwQtVwF.js} +1 -1
  38. package/dist/assets/{highlighted-body-OFNGDK62-DTkXdU8M.js → highlighted-body-OFNGDK62-DqKI0DVm.js} +1 -1
  39. package/dist/assets/{index-3FC9cBAw.js → index-Bz4G_Cuu.js} +308 -308
  40. package/dist/assets/index-CTu6WYGu.css +1 -0
  41. package/dist/assets/{infoDiagram-42DDH7IO-KcOGgxDZ.js → infoDiagram-42DDH7IO-B4Aw1ReL.js} +1 -1
  42. package/dist/assets/{ishikawaDiagram-UXIWVN3A-DN46kos1.js → ishikawaDiagram-UXIWVN3A-ZVIYN2tB.js} +1 -1
  43. package/dist/assets/{journeyDiagram-VCZTEJTY-CSnW-uiU.js → journeyDiagram-VCZTEJTY-UaC8xj7j.js} +1 -1
  44. package/dist/assets/{kanban-definition-6JOO6SKY-DgeKhYzm.js → kanban-definition-6JOO6SKY-B4pC4qyO.js} +1 -1
  45. package/dist/assets/{layout-C3k8b-wj.js → layout-BYNOhtZE.js} +1 -1
  46. package/dist/assets/{linear-CSj-zsdP.js → linear-DcH0KvpB.js} +1 -1
  47. package/dist/assets/{min-BkU8UOgQ.js → min-B7b9i4d3.js} +1 -1
  48. package/dist/assets/{mindmap-definition-QFDTVHPH-jaWIxQM2.js → mindmap-definition-QFDTVHPH-LYMoC9qk.js} +1 -1
  49. package/dist/assets/{pieDiagram-DEJITSTG-DSCNVEy0.js → pieDiagram-DEJITSTG-CmdIR1nD.js} +1 -1
  50. package/dist/assets/{quadrantDiagram-34T5L4WZ-6Aki-Mea.js → quadrantDiagram-34T5L4WZ-Dy5E2ZXi.js} +1 -1
  51. package/dist/assets/{requirementDiagram-MS252O5E-CIA-stPo.js → requirementDiagram-MS252O5E-CvO01yCF.js} +1 -1
  52. package/dist/assets/{sankeyDiagram-XADWPNL6-Dm92baLv.js → sankeyDiagram-XADWPNL6-lV90jm6N.js} +1 -1
  53. package/dist/assets/{sequenceDiagram-FGHM5R23-CijKhpBV.js → sequenceDiagram-FGHM5R23-BXYteJo4.js} +1 -1
  54. package/dist/assets/{stateDiagram-FHFEXIEX-BoRVlJdY.js → stateDiagram-FHFEXIEX-DwuM_25x.js} +1 -1
  55. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CVwGByU6.js +1 -0
  56. package/dist/assets/{timeline-definition-GMOUNBTQ-Dgq0GzH4.js → timeline-definition-GMOUNBTQ-D5FpweXf.js} +1 -1
  57. package/dist/assets/{value-BU9Hdjc2.js → value-DKBwu4y3.js} +1 -1
  58. package/dist/assets/{vennDiagram-DHZGUBPP-DrIu0Q4b.js → vennDiagram-DHZGUBPP-S83B5bJw.js} +1 -1
  59. package/dist/assets/{wardley-RL74JXVD-BumycEok.js → wardley-RL74JXVD-DiagH8qG.js} +1 -1
  60. package/dist/assets/{wardleyDiagram-NUSXRM2D-3fn4THet.js → wardleyDiagram-NUSXRM2D-C_njDDS0.js} +1 -1
  61. package/dist/assets/{xychartDiagram-5P7HB3ND-BMcrjrBB.js → xychartDiagram-5P7HB3ND-DDLHFTQ4.js} +1 -1
  62. package/dist/index.html +2 -2
  63. package/package.json +2 -2
  64. package/dist/assets/channel-DxRmlnS_.js +0 -1
  65. package/dist/assets/classDiagram-6PBFFD2Q-Bkac267_.js +0 -1
  66. package/dist/assets/classDiagram-v2-HSJHXN6E-Bkac267_.js +0 -1
  67. package/dist/assets/clone-CmiP6enp.js +0 -1
  68. package/dist/assets/index-8wiyHNEA.css +0 -1
  69. package/dist/assets/stateDiagram-v2-QKLJ7IA2-B2UHePPb.js +0 -1
@@ -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;AAM5B,OAAO,EAsHL,KAAK,aAAa,EAIlB,KAAK,aAAa,EAMnB,MAAM,sBAAsB,CAAC;AAkB9B,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAItE;AAGD,wBAAgB,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAGtF;AAk8ED,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE;IAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAA;CAAO,8EA6gI7I;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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM5B,OAAO,EAwHL,KAAK,aAAa,EAMlB,KAAK,aAAa,EAOnB,MAAM,sBAAsB,CAAC;AAkB9B,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAItE;AAGD,wBAAgB,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAGtF;AA0jFD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE;IAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAA;CAAO,8EAsjI7I;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"}
@@ -4,7 +4,7 @@ import { streamSSE } from "hono/streaming";
4
4
  import { serve } from "@hono/node-server";
5
5
  import { gzipSync } from "node:zlib";
6
6
  import { randomUUID } from "node:crypto";
7
- import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, abortAgentSession, runAgentSession, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, guessServiceFromBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, fetchWithProxy, chatCompletion, runWorkerAgent, buildExportArtifact, evaluateBookQuality, ConsolidatorAgent, DetectionConfigSchema, ResearchSearchConfigSchema, GLOBAL_ENV_PATH, COVER_PROVIDER_PRESETS, createPlayDB, PlayStore, buildPlayEntityImagePrompt, buildPlaySceneImagePrompt, generatePlayImage, readPlayImageManifest, readPlayImageSettings, writePlayImageSettings, Scheduler, coverSecretKey, normalizeCoverBaseUrl, resolveCoverProviderPreset, SessionKindSchema, normalizeActionSource as normalizeCoreActionSource, normalizeActionPayload as normalizeCoreActionPayload, normalizePlayMode as normalizeCorePlayMode, normalizeRequestedIntent as normalizeCoreRequestedIntent, normalizeSkillIdList as normalizeCoreSkillIdList, inferLanguage, toWritingLanguage, ingestMaterial, createSkillRegistry, loadAvailableAgentSkills, activatedSkillIds, mergeActivatedSkillGuidance, resolveProductionSkillActivations, parseAgentSkillDocument, getBuiltinPrompt, listBuiltinPromptPacks, listBuiltinPrompts, loadPromptPackPrompt, promptOverridePath, toPosixPath, createGenerateCoverTool, createInteractiveFilmCreationTool, createPlayStartTool, createScriptCreationTool, createShortFictionRunTool, createStoryboardCreationTool, createTranslationCreateTool, createFanficBookTool, createContinuationImportTool, createSpinoffBookTool, createImitationBookTool, createSubAgentTool, createDraftStructureTool, createConnectChoiceTool, createRemoveNodeTool, createLLMTranslationModel, deleteLatestChapter, executeEditTransaction, listChapterVersions, readChapterPlanDocument, readChapterUserBrief, readChapterVersion, saveChapterUserBrief, createTranslationProjectFromFile, loadTranslationChapter, loadTranslationManifest, runTranslationProject, writeTranslationExport, filmLLMDepsFromClient, applyGraphDelta, loadStoryGraph, reviewStoryGraph, exportInk, buildPlayableHtml, analyzeEmotionalArcs, analyzePathDistribution, generateNodeImage, defaultNodeImageDeps, } from "@kienmatu/inkos-core";
7
+ import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, abortAgentSession, runAgentSession, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, guessServiceFromBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, normalizeProbedModels, fetchWithProxy, chatCompletion, runWorkerAgent, buildExportArtifact, createProductionRunSnapshot, evaluateBookQuality, ConsolidatorAgent, DetectionConfigSchema, ResearchSearchConfigSchema, GLOBAL_ENV_PATH, COVER_PROVIDER_PRESETS, createPlayDB, PlayStore, buildPlayEntityImagePrompt, buildPlaySceneImagePrompt, generatePlayImage, readPlayImageManifest, readPlayImageSettings, writePlayImageSettings, Scheduler, coverSecretKey, normalizeCoverBaseUrl, resolveCoverProviderPreset, SessionKindSchema, normalizeActionSource as normalizeCoreActionSource, normalizeActionPayload as normalizeCoreActionPayload, normalizePlayMode as normalizeCorePlayMode, normalizeRequestedIntent as normalizeCoreRequestedIntent, normalizeSkillIdList as normalizeCoreSkillIdList, inferLanguage, toWritingLanguage, ingestMaterial, createSkillRegistry, loadAvailableAgentSkills, activatedSkillIds, mergeActivatedSkillGuidance, resolveProductionSkillActivations, parseAgentSkillDocument, getBuiltinPrompt, listBuiltinPromptPacks, listBuiltinPrompts, loadPromptPackPrompt, promptOverridePath, toPosixPath, createGenerateCoverTool, createInteractiveFilmCreationTool, createPlayStartTool, createScriptCreationTool, createShortFictionRunTool, createStoryboardCreationTool, createTranslationCreateTool, createFanficBookTool, createContinuationImportTool, createSpinoffBookTool, createImitationBookTool, createSubAgentTool, createDraftStructureTool, createConnectChoiceTool, createRemoveNodeTool, createLLMTranslationModel, deleteLatestChapter, executeEditTransaction, listChapterVersions, readChapterPlanDocument, readChapterUserBrief, readChapterVersion, saveChapterUserBrief, createTranslationProjectFromFile, loadTranslationChapter, loadTranslationManifest, runTranslationProject, writeTranslationExport, filmLLMDepsFromClient, applyGraphDelta, loadStoryGraph, reviewStoryGraph, exportInk, buildPlayableHtml, analyzeEmotionalArcs, analyzePathDistribution, generateNodeImage, defaultNodeImageDeps, writeProductionRunSnapshot, } from "@kienmatu/inkos-core";
8
8
  import { isConfirmedProductionAction } from "../shared/confirmed-production.js";
9
9
  import { summarizeToolResult } from "../shared/tool-result.js";
10
10
  import { access, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -1267,18 +1267,79 @@ async function executeConfirmedProductionAction(args) {
1267
1267
  throw new ConfirmedActionExecutionError(message, exec, error);
1268
1268
  }
1269
1269
  }
1270
- function parseCompleteShortRunIndex(value, expectedStoryId) {
1270
+ function parseProductionObservation(value) {
1271
1271
  if (!value || typeof value !== "object" || Array.isArray(value))
1272
1272
  return null;
1273
1273
  const record = value;
1274
+ if (typeof record.metric !== "string"
1275
+ || !["info", "warning", "blocking"].includes(String(record.severity))
1276
+ || typeof record.repairable !== "boolean"
1277
+ || (record.evidence !== undefined && typeof record.evidence !== "string")) {
1278
+ return null;
1279
+ }
1280
+ return {
1281
+ metric: record.metric,
1282
+ expected: record.expected,
1283
+ actual: record.actual,
1284
+ severity: record.severity,
1285
+ ...(typeof record.evidence === "string" ? { evidence: record.evidence } : {}),
1286
+ repairable: record.repairable,
1287
+ };
1288
+ }
1289
+ function parseStudioShortRun(value, expectedStoryId) {
1290
+ if (!value || typeof value !== "object" || Array.isArray(value))
1291
+ return null;
1292
+ const record = value;
1293
+ const status = record.status;
1294
+ const artifacts = Array.isArray(record.artifacts) && record.artifacts.every((item) => typeof item === "string")
1295
+ ? record.artifacts
1296
+ : null;
1297
+ const observations = Array.isArray(record.observations)
1298
+ ? record.observations.map(parseProductionObservation)
1299
+ : null;
1300
+ if (record.version !== 1
1301
+ || record.kind !== "short-fiction"
1302
+ || record.id !== expectedStoryId
1303
+ || (status !== "complete" && status !== "needs-review")
1304
+ || typeof record.stage !== "string"
1305
+ || !artifacts
1306
+ || !observations
1307
+ || observations.some((observation) => observation === null)
1308
+ || typeof record.updatedAt !== "string"
1309
+ || (record.model !== undefined && typeof record.model !== "string")
1310
+ || (record.skillIds !== undefined && (!Array.isArray(record.skillIds) || !record.skillIds.every((item) => typeof item === "string")))
1311
+ || (record.resumeCursor !== undefined && typeof record.resumeCursor !== "string")
1312
+ || (record.error !== undefined && typeof record.error !== "string")) {
1313
+ return null;
1314
+ }
1315
+ return {
1316
+ version: 1,
1317
+ kind: "short-fiction",
1318
+ id: expectedStoryId,
1319
+ status,
1320
+ stage: record.stage,
1321
+ artifacts,
1322
+ observations: observations,
1323
+ ...(typeof record.model === "string" ? { model: record.model } : {}),
1324
+ ...(Array.isArray(record.skillIds) ? { skillIds: record.skillIds } : {}),
1325
+ ...(typeof record.resumeCursor === "string" ? { resumeCursor: record.resumeCursor } : {}),
1326
+ ...(typeof record.error === "string" ? { error: record.error } : {}),
1327
+ updatedAt: record.updatedAt,
1328
+ };
1329
+ }
1330
+ function parseVisibleShortRunIndex(value, expectedStoryId) {
1331
+ if (!value || typeof value !== "object" || Array.isArray(value))
1332
+ return null;
1333
+ const record = value;
1334
+ const status = record.status;
1274
1335
  if (record.version !== 1
1275
1336
  || record.kind !== "short-fiction"
1276
1337
  || record.id !== expectedStoryId
1277
- || record.status !== "complete"
1338
+ || (status !== "complete" && status !== "needs-review")
1278
1339
  || typeof record.updatedAt !== "string") {
1279
1340
  return null;
1280
1341
  }
1281
- return { storyId: expectedStoryId, updatedAt: record.updatedAt };
1342
+ return { storyId: expectedStoryId, status, updatedAt: record.updatedAt };
1282
1343
  }
1283
1344
  function parseShortStoryTitle(value) {
1284
1345
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -1304,15 +1365,16 @@ async function loadStudioShortList(root) {
1304
1365
  const shortDir = join(shortsDir, storyId);
1305
1366
  try {
1306
1367
  const status = JSON.parse(await readFile(join(shortDir, "status.json"), "utf-8"));
1307
- const run = parseCompleteShortRunIndex(status, storyId);
1368
+ const run = parseVisibleShortRunIndex(status, storyId);
1308
1369
  if (!run)
1309
1370
  continue;
1310
1371
  const story = JSON.parse(await readFile(join(shortDir, "final", "short-story.json"), "utf-8"));
1311
1372
  const title = parseShortStoryTitle(story) ?? storyId;
1312
- await access(join(shortDir, "final", "full.md"));
1373
+ await readFile(join(shortDir, "final", "full.md"), "utf-8");
1313
1374
  shorts.push({
1314
1375
  storyId,
1315
1376
  title,
1377
+ status: run.status,
1316
1378
  finalMarkdownPath: toPosixPath(join("shorts", storyId, "final", "full.md")),
1317
1379
  updatedAt: run.updatedAt,
1318
1380
  });
@@ -1325,7 +1387,6 @@ async function loadStudioShortList(root) {
1325
1387
  }
1326
1388
  const subscribers = new Set();
1327
1389
  const bookCreateStatus = new Map();
1328
- // 内存缓存:service -> 模型列表 + 更新时间戳;避免每次 sidebar 挂载时都打真实 LLM /models
1329
1390
  const modelListCache = new Map();
1330
1391
  function broadcast(event, data) {
1331
1392
  for (const handler of subscribers) {
@@ -1407,16 +1468,50 @@ function normalizeServiceModelIds(value) {
1407
1468
  }
1408
1469
  return models;
1409
1470
  }
1471
+ function normalizeServiceModelCapabilities(value, models) {
1472
+ if (!value || typeof value !== "object" || Array.isArray(value))
1473
+ return {};
1474
+ const allowed = models ? new Set(models.map((model) => model.toLowerCase())) : undefined;
1475
+ const result = {};
1476
+ for (const [model, raw] of Object.entries(value)) {
1477
+ if (!model.trim() || (allowed && !allowed.has(model.toLowerCase())))
1478
+ continue;
1479
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1480
+ continue;
1481
+ const record = raw;
1482
+ const contextWindow = positiveInteger(record.contextWindow);
1483
+ const candidateMax = positiveInteger(record.maxOutput);
1484
+ const maxOutput = candidateMax !== undefined
1485
+ && (contextWindow === undefined || candidateMax <= contextWindow)
1486
+ ? candidateMax
1487
+ : undefined;
1488
+ if (maxOutput === undefined && contextWindow === undefined)
1489
+ continue;
1490
+ result[model] = {
1491
+ ...(maxOutput !== undefined ? { maxOutput } : {}),
1492
+ ...(contextWindow !== undefined ? { contextWindow } : {}),
1493
+ };
1494
+ }
1495
+ return result;
1496
+ }
1497
+ function positiveInteger(value) {
1498
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0
1499
+ ? value
1500
+ : undefined;
1501
+ }
1410
1502
  function mergeServiceModelIds(...groups) {
1411
1503
  return normalizeServiceModelIds(groups.flatMap((group) => group ?? []));
1412
1504
  }
1413
1505
  function normalizeServiceEntry(serviceId, value) {
1506
+ const models = Array.isArray(value.models) ? normalizeServiceModelIds(value.models) : undefined;
1507
+ const modelCapabilities = normalizeServiceModelCapabilities(value.modelCapabilities, models);
1414
1508
  if (serviceId.startsWith("custom:")) {
1415
1509
  return {
1416
1510
  service: "custom",
1417
1511
  name: decodeURIComponent(serviceId.slice("custom:".length)),
1418
1512
  ...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
1419
- ...(Array.isArray(value.models) ? { models: normalizeServiceModelIds(value.models) } : {}),
1513
+ ...(models ? { models } : {}),
1514
+ ...(Object.keys(modelCapabilities).length > 0 ? { modelCapabilities } : {}),
1420
1515
  ...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
1421
1516
  ...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
1422
1517
  ...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
@@ -1427,7 +1522,8 @@ function normalizeServiceEntry(serviceId, value) {
1427
1522
  service: "custom",
1428
1523
  ...(typeof value.name === "string" && value.name.length > 0 ? { name: value.name } : {}),
1429
1524
  ...(typeof value.baseUrl === "string" && value.baseUrl.length > 0 ? { baseUrl: value.baseUrl } : {}),
1430
- ...(Array.isArray(value.models) ? { models: normalizeServiceModelIds(value.models) } : {}),
1525
+ ...(models ? { models } : {}),
1526
+ ...(Object.keys(modelCapabilities).length > 0 ? { modelCapabilities } : {}),
1431
1527
  ...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
1432
1528
  ...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
1433
1529
  ...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
@@ -1435,7 +1531,8 @@ function normalizeServiceEntry(serviceId, value) {
1435
1531
  }
1436
1532
  return {
1437
1533
  service: serviceId,
1438
- ...(Array.isArray(value.models) ? { models: normalizeServiceModelIds(value.models) } : {}),
1534
+ ...(models ? { models } : {}),
1535
+ ...(Object.keys(modelCapabilities).length > 0 ? { modelCapabilities } : {}),
1439
1536
  ...(typeof value.temperature === "number" ? { temperature: value.temperature } : {}),
1440
1537
  ...(value.apiFormat === "chat" || value.apiFormat === "responses" ? { apiFormat: value.apiFormat } : {}),
1441
1538
  ...(typeof value.stream === "boolean" ? { stream: value.stream } : {}),
@@ -1448,15 +1545,7 @@ function normalizeServiceConfig(raw) {
1448
1545
  if (Array.isArray(raw)) {
1449
1546
  return raw
1450
1547
  .filter((entry) => Boolean(entry) && typeof entry === "object")
1451
- .map((entry) => ({
1452
- service: typeof entry.service === "string" && entry.service.length > 0 ? entry.service : "custom",
1453
- ...(typeof entry.name === "string" && entry.name.length > 0 ? { name: entry.name } : {}),
1454
- ...(typeof entry.baseUrl === "string" && entry.baseUrl.length > 0 ? { baseUrl: entry.baseUrl } : {}),
1455
- ...(Array.isArray(entry.models) ? { models: normalizeServiceModelIds(entry.models) } : {}),
1456
- ...(typeof entry.temperature === "number" ? { temperature: entry.temperature } : {}),
1457
- ...(entry.apiFormat === "chat" || entry.apiFormat === "responses" ? { apiFormat: entry.apiFormat } : {}),
1458
- ...(typeof entry.stream === "boolean" ? { stream: entry.stream } : {}),
1459
- }));
1548
+ .map((entry) => normalizeServiceEntry(typeof entry.service === "string" && entry.service.length > 0 ? entry.service : "custom", entry));
1460
1549
  }
1461
1550
  if (raw && typeof raw === "object") {
1462
1551
  return Object.entries(raw)
@@ -1474,6 +1563,9 @@ function mergeServiceConfig(existing, updates) {
1474
1563
  ...previous,
1475
1564
  ...update,
1476
1565
  ...(update.models === undefined && previous?.models ? { models: previous.models } : {}),
1566
+ ...(update.modelCapabilities === undefined && previous?.modelCapabilities
1567
+ ? { modelCapabilities: previous.modelCapabilities }
1568
+ : {}),
1477
1569
  });
1478
1570
  }
1479
1571
  return [...merged.values()];
@@ -1518,6 +1610,12 @@ function syncTopLevelLlmMirror(llm) {
1518
1610
  llm.apiFormat = selectedEntry.apiFormat;
1519
1611
  if (selectedEntry.stream !== undefined)
1520
1612
  llm.stream = selectedEntry.stream;
1613
+ if (selectedEntry.modelCapabilities && Object.keys(selectedEntry.modelCapabilities).length > 0) {
1614
+ llm.modelCapabilities = selectedEntry.modelCapabilities;
1615
+ }
1616
+ else {
1617
+ delete llm.modelCapabilities;
1618
+ }
1521
1619
  }
1522
1620
  async function loadRawConfig(root) {
1523
1621
  const configPath = join(root, "inkos.json");
@@ -1893,10 +1991,7 @@ async function fetchModelsFromServiceBaseUrl(serviceId, baseUrl, apiKey, proxyUr
1893
1991
  authFailed: res.status === 401 || res.status === 403,
1894
1992
  };
1895
1993
  }
1896
- const json = await res.json();
1897
- return {
1898
- models: (json.data ?? []).map((m) => ({ id: m.id, name: m.id })),
1899
- };
1994
+ return { models: normalizeProbedModels(await res.json()) };
1900
1995
  }
1901
1996
  catch (error) {
1902
1997
  return {
@@ -2067,6 +2162,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
2067
2162
  const state = new StateManager(root);
2068
2163
  let cachedConfig = initialConfig;
2069
2164
  const activeConfirmedTasks = new Map();
2165
+ const confirmedTaskRevisions = new Map();
2070
2166
  // 确认式生产任务的单任务名额(sessionId → taskId)。原来的检查是"await 读快照
2071
2167
  // → 之后才 set controller"的 check-then-act:两个并发确认请求都能通过检查,
2072
2168
  // 双任务同时启动、快照互相覆盖。这里在任何 await 之前同步占位,占位失败的
@@ -2090,8 +2186,11 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
2090
2186
  const persistConfirmedTask = async (sessionId, requestedIntent, exec, sourceRequestId) => {
2091
2187
  if (deletedSessionIds.has(sessionId))
2092
2188
  return;
2189
+ const revision = (confirmedTaskRevisions.get(exec.id) ?? 0) + 1;
2190
+ confirmedTaskRevisions.set(exec.id, revision);
2093
2191
  const snapshot = {
2094
2192
  version: 1,
2193
+ revision,
2095
2194
  sessionId,
2096
2195
  ...(sourceRequestId ? { sourceRequestId } : {}),
2097
2196
  requestedIntent,
@@ -2102,7 +2201,10 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
2102
2201
  ...(exec.logs ? { logs: [...exec.logs] } : {}),
2103
2202
  },
2104
2203
  };
2204
+ // Publish the same durable snapshot that refresh recovery reads, so an
2205
+ // already-open task card sees milestone logs without requiring a reload.
2105
2206
  await saveStudioTaskSnapshot(root, snapshot);
2207
+ broadcast("task:snapshot", snapshot);
2106
2208
  };
2107
2209
  const loadReconciledTaskSnapshot = async (sessionId) => {
2108
2210
  const task = await loadStudioTaskSnapshot(root, sessionId);
@@ -4186,6 +4288,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
4186
4288
  }
4187
4289
  finally {
4188
4290
  activeConfirmedTasks.delete(taskId);
4291
+ confirmedTaskRevisions.delete(taskId);
4189
4292
  reservedProductionSessions.delete(reservedSessionId);
4190
4293
  }
4191
4294
  }
@@ -5395,6 +5498,36 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
5395
5498
  const shorts = await loadStudioShortList(root);
5396
5499
  return c.json({ shorts });
5397
5500
  });
5501
+ app.post("/api/v1/shorts/:id/complete", async (c) => {
5502
+ const storyId = c.req.param("id");
5503
+ if (!isSafeBookId(storyId)) {
5504
+ return c.json({ error: "Invalid short id" }, 400);
5505
+ }
5506
+ const runPath = join("shorts", storyId, "status.json");
5507
+ let raw;
5508
+ try {
5509
+ raw = JSON.parse(await readFile(join(root, runPath), "utf-8"));
5510
+ }
5511
+ catch {
5512
+ return c.json({ error: "Short not found" }, 404);
5513
+ }
5514
+ const run = parseStudioShortRun(raw, storyId);
5515
+ if (!run) {
5516
+ return c.json({ error: "Short status is invalid" }, 409);
5517
+ }
5518
+ if (run.status === "needs-review") {
5519
+ await writeProductionRunSnapshot({
5520
+ rootDir: root,
5521
+ runPath,
5522
+ run: createProductionRunSnapshot({
5523
+ ...run,
5524
+ status: "complete",
5525
+ updatedAt: new Date().toISOString(),
5526
+ }),
5527
+ });
5528
+ }
5529
+ return c.json({ ok: true, storyId, status: "complete" });
5530
+ });
5398
5531
  app.get("/api/v1/translations", async (c) => {
5399
5532
  const translationsDir = join(root, "translations");
5400
5533
  let entries = [];