@kienmatu/inkos-studio 1.0.1 → 3.3.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 +106 -6
  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-DsSZPo5A.js} +1 -1
  9. package/dist/assets/{FlowView-Bt0EtmEr.js → FlowView-DtborsGc.js} +1 -1
  10. package/dist/assets/{_baseUniq-ZL9pmMqM.js → _baseUniq-BEIj9tcE.js} +1 -1
  11. package/dist/assets/{arc-B8qI0bEW.js → arc-C7Rm_CAk.js} +1 -1
  12. package/dist/assets/{architectureDiagram-Q4EWVU46-BMqx63uW.js → architectureDiagram-Q4EWVU46-DlmfKm7b.js} +1 -1
  13. package/dist/assets/{blockDiagram-DXYQGD6D-CpTeW9T5.js → blockDiagram-DXYQGD6D-DwWcyB5e.js} +1 -1
  14. package/dist/assets/{c4Diagram-AHTNJAMY-DEMZhRny.js → c4Diagram-AHTNJAMY-DJr7ig4J.js} +1 -1
  15. package/dist/assets/channel-D3RJhFng.js +1 -0
  16. package/dist/assets/{chunk-4BX2VUAB-CATTVIYo.js → chunk-4BX2VUAB-BEbkhxPj.js} +1 -1
  17. package/dist/assets/{chunk-4TB4RGXK-BVSOdPwi.js → chunk-4TB4RGXK-CKlayWkT.js} +1 -1
  18. package/dist/assets/{chunk-55IACEB6-roBKSgqh.js → chunk-55IACEB6-Do_NAv20.js} +1 -1
  19. package/dist/assets/{chunk-EDXVE4YY-DcL-JJvQ.js → chunk-EDXVE4YY-CIaC2_XQ.js} +1 -1
  20. package/dist/assets/{chunk-FMBD7UC4-CTt3nqty.js → chunk-FMBD7UC4-DK2fFb-S.js} +1 -1
  21. package/dist/assets/{chunk-OYMX7WX6-BKloZua_.js → chunk-OYMX7WX6-DOk8ZO7Q.js} +1 -1
  22. package/dist/assets/{chunk-QZHKN3VN-CksGJW6L.js → chunk-QZHKN3VN-CWQlxTIX.js} +1 -1
  23. package/dist/assets/{chunk-YZCP3GAM-DFUs85Mo.js → chunk-YZCP3GAM-BKMKNDs_.js} +1 -1
  24. package/dist/assets/classDiagram-6PBFFD2Q-Ctnvqnpw.js +1 -0
  25. package/dist/assets/classDiagram-v2-HSJHXN6E-Ctnvqnpw.js +1 -0
  26. package/dist/assets/clone-BdOaHqQL.js +1 -0
  27. package/dist/assets/{cose-bilkent-S5V4N54A-BhjR9QKW.js → cose-bilkent-S5V4N54A-CtsSt-S-.js} +1 -1
  28. package/dist/assets/{dagre-KV5264BT-S0WgqeWF.js → dagre-KV5264BT-zROnUgJY.js} +1 -1
  29. package/dist/assets/{diagram-5BDNPKRD-iziIar_1.js → diagram-5BDNPKRD-Ct6aFDWr.js} +1 -1
  30. package/dist/assets/{diagram-G4DWMVQ6-DGiQ4b3V.js → diagram-G4DWMVQ6-B2bwAps6.js} +1 -1
  31. package/dist/assets/{diagram-MMDJMWI5-Ctfv9R4Z.js → diagram-MMDJMWI5-CbwZreHT.js} +1 -1
  32. package/dist/assets/{diagram-TYMM5635-DpzPFDFI.js → diagram-TYMM5635-gKzWLxCO.js} +1 -1
  33. package/dist/assets/{erDiagram-SMLLAGMA-e51DloYW.js → erDiagram-SMLLAGMA-DipB25lI.js} +1 -1
  34. package/dist/assets/{flowDiagram-DWJPFMVM-TyuMrie7.js → flowDiagram-DWJPFMVM-C9VuPnc4.js} +1 -1
  35. package/dist/assets/{ganttDiagram-T4ZO3ILL-sewHvhyH.js → ganttDiagram-T4ZO3ILL-B2dTjD_8.js} +1 -1
  36. package/dist/assets/{gitGraphDiagram-UUTBAWPF-DI7YNaAi.js → gitGraphDiagram-UUTBAWPF-BZXYA0Ec.js} +1 -1
  37. package/dist/assets/{graph-iCANBjLp.js → graph-iDCrfqrI.js} +1 -1
  38. package/dist/assets/{highlighted-body-OFNGDK62-DTkXdU8M.js → highlighted-body-OFNGDK62-CP2BM2l8.js} +1 -1
  39. package/dist/assets/index-CTu6WYGu.css +1 -0
  40. package/dist/assets/{index-3FC9cBAw.js → index-PEL5ijWf.js} +179 -179
  41. package/dist/assets/{infoDiagram-42DDH7IO-KcOGgxDZ.js → infoDiagram-42DDH7IO-Bd7gtY2E.js} +1 -1
  42. package/dist/assets/{ishikawaDiagram-UXIWVN3A-DN46kos1.js → ishikawaDiagram-UXIWVN3A-HMp96hG2.js} +1 -1
  43. package/dist/assets/{journeyDiagram-VCZTEJTY-CSnW-uiU.js → journeyDiagram-VCZTEJTY-D36jcF3p.js} +1 -1
  44. package/dist/assets/{kanban-definition-6JOO6SKY-DgeKhYzm.js → kanban-definition-6JOO6SKY-C4wSbzoj.js} +1 -1
  45. package/dist/assets/{layout-C3k8b-wj.js → layout-DqXRWJEx.js} +1 -1
  46. package/dist/assets/{linear-CSj-zsdP.js → linear-QlXzAe7A.js} +1 -1
  47. package/dist/assets/{min-BkU8UOgQ.js → min-iaA6vGbj.js} +1 -1
  48. package/dist/assets/{mindmap-definition-QFDTVHPH-jaWIxQM2.js → mindmap-definition-QFDTVHPH-B-PCe4LP.js} +1 -1
  49. package/dist/assets/{pieDiagram-DEJITSTG-DSCNVEy0.js → pieDiagram-DEJITSTG-DRSPzSKd.js} +1 -1
  50. package/dist/assets/{quadrantDiagram-34T5L4WZ-6Aki-Mea.js → quadrantDiagram-34T5L4WZ-D-c8JbLY.js} +1 -1
  51. package/dist/assets/{requirementDiagram-MS252O5E-CIA-stPo.js → requirementDiagram-MS252O5E-CdOoLbvR.js} +1 -1
  52. package/dist/assets/{sankeyDiagram-XADWPNL6-Dm92baLv.js → sankeyDiagram-XADWPNL6-CQDxnTGB.js} +1 -1
  53. package/dist/assets/{sequenceDiagram-FGHM5R23-CijKhpBV.js → sequenceDiagram-FGHM5R23-SZbPsgFb.js} +1 -1
  54. package/dist/assets/{stateDiagram-FHFEXIEX-BoRVlJdY.js → stateDiagram-FHFEXIEX-uFY2njzT.js} +1 -1
  55. package/dist/assets/stateDiagram-v2-QKLJ7IA2-BJCQ3uMo.js +1 -0
  56. package/dist/assets/{timeline-definition-GMOUNBTQ-Dgq0GzH4.js → timeline-definition-GMOUNBTQ-n_oY30Kg.js} +1 -1
  57. package/dist/assets/{value-BU9Hdjc2.js → value-hJ3k-Vla.js} +1 -1
  58. package/dist/assets/{vennDiagram-DHZGUBPP-DrIu0Q4b.js → vennDiagram-DHZGUBPP-B9VAsWw3.js} +1 -1
  59. package/dist/assets/{wardley-RL74JXVD-BumycEok.js → wardley-RL74JXVD-DPynk1O-.js} +1 -1
  60. package/dist/assets/{wardleyDiagram-NUSXRM2D-3fn4THet.js → wardleyDiagram-NUSXRM2D-BxHtrGEZ.js} +1 -1
  61. package/dist/assets/{xychartDiagram-5P7HB3ND-BMcrjrBB.js → xychartDiagram-5P7HB3ND-B3Hi8GO_.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,EAuHL,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;AAwgFD,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, 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;
1274
1300
  if (record.version !== 1
1275
1301
  || record.kind !== "short-fiction"
1276
1302
  || record.id !== expectedStoryId
1277
- || record.status !== "complete"
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;
1335
+ if (record.version !== 1
1336
+ || record.kind !== "short-fiction"
1337
+ || record.id !== expectedStoryId
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
  });
@@ -2067,6 +2129,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
2067
2129
  const state = new StateManager(root);
2068
2130
  let cachedConfig = initialConfig;
2069
2131
  const activeConfirmedTasks = new Map();
2132
+ const confirmedTaskRevisions = new Map();
2070
2133
  // 确认式生产任务的单任务名额(sessionId → taskId)。原来的检查是"await 读快照
2071
2134
  // → 之后才 set controller"的 check-then-act:两个并发确认请求都能通过检查,
2072
2135
  // 双任务同时启动、快照互相覆盖。这里在任何 await 之前同步占位,占位失败的
@@ -2090,8 +2153,11 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
2090
2153
  const persistConfirmedTask = async (sessionId, requestedIntent, exec, sourceRequestId) => {
2091
2154
  if (deletedSessionIds.has(sessionId))
2092
2155
  return;
2156
+ const revision = (confirmedTaskRevisions.get(exec.id) ?? 0) + 1;
2157
+ confirmedTaskRevisions.set(exec.id, revision);
2093
2158
  const snapshot = {
2094
2159
  version: 1,
2160
+ revision,
2095
2161
  sessionId,
2096
2162
  ...(sourceRequestId ? { sourceRequestId } : {}),
2097
2163
  requestedIntent,
@@ -2102,7 +2168,10 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
2102
2168
  ...(exec.logs ? { logs: [...exec.logs] } : {}),
2103
2169
  },
2104
2170
  };
2171
+ // Publish the same durable snapshot that refresh recovery reads, so an
2172
+ // already-open task card sees milestone logs without requiring a reload.
2105
2173
  await saveStudioTaskSnapshot(root, snapshot);
2174
+ broadcast("task:snapshot", snapshot);
2106
2175
  };
2107
2176
  const loadReconciledTaskSnapshot = async (sessionId) => {
2108
2177
  const task = await loadStudioTaskSnapshot(root, sessionId);
@@ -4186,6 +4255,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
4186
4255
  }
4187
4256
  finally {
4188
4257
  activeConfirmedTasks.delete(taskId);
4258
+ confirmedTaskRevisions.delete(taskId);
4189
4259
  reservedProductionSessions.delete(reservedSessionId);
4190
4260
  }
4191
4261
  }
@@ -5395,6 +5465,36 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
5395
5465
  const shorts = await loadStudioShortList(root);
5396
5466
  return c.json({ shorts });
5397
5467
  });
5468
+ app.post("/api/v1/shorts/:id/complete", async (c) => {
5469
+ const storyId = c.req.param("id");
5470
+ if (!isSafeBookId(storyId)) {
5471
+ return c.json({ error: "Invalid short id" }, 400);
5472
+ }
5473
+ const runPath = join("shorts", storyId, "status.json");
5474
+ let raw;
5475
+ try {
5476
+ raw = JSON.parse(await readFile(join(root, runPath), "utf-8"));
5477
+ }
5478
+ catch {
5479
+ return c.json({ error: "Short not found" }, 404);
5480
+ }
5481
+ const run = parseStudioShortRun(raw, storyId);
5482
+ if (!run) {
5483
+ return c.json({ error: "Short status is invalid" }, 409);
5484
+ }
5485
+ if (run.status === "needs-review") {
5486
+ await writeProductionRunSnapshot({
5487
+ rootDir: root,
5488
+ runPath,
5489
+ run: createProductionRunSnapshot({
5490
+ ...run,
5491
+ status: "complete",
5492
+ updatedAt: new Date().toISOString(),
5493
+ }),
5494
+ });
5495
+ }
5496
+ return c.json({ ok: true, storyId, status: "complete" });
5497
+ });
5398
5498
  app.get("/api/v1/translations", async (c) => {
5399
5499
  const translationsDir = join(root, "translations");
5400
5500
  let entries = [];