@tea-agent/loop-agent 0.44.0-next.2 → 0.44.0-next.4

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 (98) hide show
  1. package/CHANGELOG.md +5 -3
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/executors/dag-pi-executor.js +2 -2
  4. package/dist/task/config-types.js +2 -1
  5. package/dist/task/contract/schema.js +1 -0
  6. package/dist/worker/console/chat/chat-event-store.js +32 -0
  7. package/dist/worker/console/chat/conversation-nodes.js +383 -0
  8. package/dist/worker/console/chat/db-connections.js +48 -16
  9. package/dist/worker/console/chat/pi-runtime.js +131 -15
  10. package/dist/worker/console/chat/provider-error.js +0 -12
  11. package/dist/worker/console/chat/routes.js +136 -3
  12. package/dist/worker/console/chat/turn-process.js +128 -7
  13. package/dist/worker/console/prd-intake-bridge.js +3 -0
  14. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B8EdIjdl.js → abnfDiagram-N423BO3Z-B_lmJjOm.js} +1 -1
  15. package/dist/worker/console/static/assets/{arc-CUaduV17.js → arc-pymM4Qeh.js} +1 -1
  16. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-bYAcxRaf.js → architectureDiagram-T3A2C74G-Bw9EPAhi.js} +1 -1
  17. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-FfhFUEMJ.js → blockDiagram-VBNYF7ZC-C7_oXJp2.js} +1 -1
  18. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-0Rqwn2_U.js → c4Diagram-5PPSVZJV-LnLWDbh8.js} +1 -1
  19. package/dist/worker/console/static/assets/channel-Ym13M3Lb.js +1 -0
  20. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D4FbDmsM.js → chunk-2GRJ4B5K-BBiu3sCW.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C2nu32ml.js → chunk-2Q5K7J3B-DiXqDQml.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-G2tHSClK.js → chunk-5RXB4S5H-lmNq9nYk.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-181nyzuH.js → chunk-5VM5RSS4-boBu74dU.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-D091VbiQ.js → chunk-6Q2QTUOP-CjxqoJLQ.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-BjrMCKTH.js → chunk-GF5L2VYU-DL0vQ9MG.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-T3zFHTpb.js → chunk-JWPE2WC7-DGPqE-O0.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CIB7wrYq.js → chunk-KBJHAD2P-v4BbT_r2.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-HwnAsgDw.js → chunk-RYQCIY6F-Ci0ynvyL.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CQSdQAqo.js → chunk-XXDRQBXY-CGsbJoTV.js} +1 -1
  30. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeFR1wGo.js +1 -0
  31. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeFR1wGo.js +1 -0
  32. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CyBVLpjP.js → cose-bilkent-JH36ORCC-CzoRDEI7.js} +1 -1
  33. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-D2Pf3U7m.js → cynefin-VYW2F7L2-BOwFwbX4.js} +1 -1
  34. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CzXuQ1a2.js → cynefinDiagram-MW4NZA55-2Cfg451l.js} +1 -1
  35. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-9cMmcOsQ.js → dagre-VZM6K2ZE-DbT0D6Y5.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DTOr8GLs.js → diagram-7IWD3JNH-Dfd_JoMs.js} +1 -1
  37. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8saqFHU.js → diagram-B4RE2ZJO-DaGm82kh.js} +1 -1
  38. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bvygp--w.js → diagram-LBJQPF4R-DMoI8Ibc.js} +1 -1
  39. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CJyLwBhZ.js → diagram-Q27KOJAE-BtIFA3aY.js} +1 -1
  40. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CkR1FDRN.js → diagram-UB23O5K3-D_2X1DTZ.js} +1 -1
  41. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BsbNtwvi.js → ebnfDiagram-BXEA7PRR-yFW5ZYlZ.js} +1 -1
  42. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-EVZo9AKy.js → erDiagram-JOGREHBK-DBXh5Svj.js} +1 -1
  43. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C1x91tWN.js → flowDiagram-UKHOOZJN-C4ycm8KM.js} +1 -1
  44. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CiNBUsrJ.js → ganttDiagram-PKOTCBZU-qr-_Pekr.js} +1 -1
  45. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CSd50p3d.js → gitGraphDiagram-DS77QQ5N-DzOSLv_A.js} +1 -1
  46. package/dist/worker/console/static/assets/index-PFjB6g6l.js +482 -0
  47. package/dist/worker/console/static/assets/index-ZtyB6Ol-.css +1 -0
  48. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-1JDiYCuA.js → infoDiagram-6WML65LV-C7Hl8D7l.js} +1 -1
  49. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BZLE6zpm.js → ishikawaDiagram-WSZJBQD7-fBxVlr6t.js} +1 -1
  50. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cg0nDmbe.js → journeyDiagram-NVQOT4AX-CoiJEVCl.js} +1 -1
  51. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DcwEdKjq.js → kanban-definition-27J2QSJJ-TJXpxNuV.js} +1 -1
  52. package/dist/worker/console/static/assets/{linear-CXvVge5x.js → linear-CjQWz1pG.js} +1 -1
  53. package/dist/worker/console/static/assets/{mermaid.core-5R4WTYSN.js → mermaid.core-DY9_apIE.js} +5 -5
  54. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-By9pSeeq.js → mindmap-definition-FAOFIHXS-HB0Id8gM.js} +1 -1
  55. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DzbVZTqW.js → pegDiagram-VL7TDLO6-0OSWgr6v.js} +1 -1
  56. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B9nbfnAq.js → pieDiagram-7S7Q4E2Y-Cei1GGC2.js} +1 -1
  57. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-D268jfWH.js → quadrantDiagram-CIZ2JOQS-CDy0Adfu.js} +1 -1
  58. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-oNOXPRNn.js → railroadDiagram-AXF67PYL-BwqzUWwN.js} +1 -1
  59. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DXdr8K-4.js → requirementDiagram-LRYGKXZP-Bif2IBRL.js} +1 -1
  60. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Dtm4uOJ0.js → sankeyDiagram-W5VNT64P-CYIoNMLg.js} +1 -1
  61. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CeeFiof4.js → sequenceDiagram-SI44F4Z6-BDS7Y0aQ.js} +1 -1
  62. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CK1gARll.js → sizeCapture-X5ZJPWSS-lZic-_Bp.js} +1 -1
  63. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-3jMoAcZw.js → stateDiagram-OKZ733FA-WCVlCqSd.js} +1 -1
  64. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-cj5UUB6m.js +1 -0
  65. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Div6kwoD.js → swimlanes-SLNWSIFB-pGccxTw_.js} +2 -2
  66. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-C_Qp6bnh.js +8 -0
  67. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-B7P_6Up9.js → timeline-definition-Z64GVDOM-D29RlrQA.js} +1 -1
  68. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-eOPaGUOX.js → vennDiagram-T6HMQDX7-01fEM1NU.js} +1 -1
  69. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-tAcDnb31.js → wardleyDiagram-T6FBY63Y-C4GeB6dq.js} +1 -1
  70. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Dg_r_785.js → xychartDiagram-ELKLHX3M-BXHrXHps.js} +1 -1
  71. package/dist/worker/console/static/index.html +2 -2
  72. package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
  73. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +97 -3
  74. package/dist/worker/console/static-src/operator-chat/process-label.js +11 -2
  75. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +2 -1
  76. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  77. package/dist/worker/console/static-src/operator-chat/useChatThread.js +9 -0
  78. package/dist/worker/console/workspace-context.js +1 -0
  79. package/dist/worker/observe/static/console-theme.css +174 -0
  80. package/dist/worker/observe/static/console-theme.js +36 -0
  81. package/dist/worker/observe/static/operator-chrome.css +85 -0
  82. package/dist/worker/observe/static/operator-chrome.js +51 -0
  83. package/dist/workflows/dag/frontend-implementation-contract.js +3 -1
  84. package/dist/workflows/dag/frontend-plan-completeness.js +13 -3
  85. package/dist/workflows/dag/frontend-recovery-plan.js +10 -6
  86. package/dist/workflows/dag/frontend-typed-event-store.js +5 -0
  87. package/dist/workflows/dag/frontend-writer-admission.js +1 -0
  88. package/dist/workflows/dag/output-protocol.js +1 -0
  89. package/dist/workflows/dag/rerun-task.js +1 -0
  90. package/docs/governance/harness-methodology-verification.md +11 -0
  91. package/package.json +1 -1
  92. package/dist/worker/console/static/assets/channel-ICokO4I1.js +0 -1
  93. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0c0PBcA.js +0 -1
  94. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0c0PBcA.js +0 -1
  95. package/dist/worker/console/static/assets/index-DHBWEwz_.css +0 -1
  96. package/dist/worker/console/static/assets/index-RFWQLUE-.js +0 -478
  97. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cr_kaxrX.js +0 -1
  98. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bl4fQmZh.js +0 -8
@@ -297,6 +297,35 @@ export function createWizardLocalHeadersExtension(options) {
297
297
  export { THINKING_LEVELS, RuntimeSnapshotUnavailableError, projectRuntimeSnapshot, };
298
298
  export { createDefaultPiSdkBindings } from "./pi-runtime/sdk-bindings.js";
299
299
  export { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage };
300
+ export function buildRequestContextEvent(input) {
301
+ const systemPrompt = input.session.state?.systemPrompt ?? input.session.systemPrompt ?? "";
302
+ const activeTools = input.session.getActiveToolNames()
303
+ .filter((name) => typeof name === "string" && name.trim())
304
+ .slice(0, 256);
305
+ const provider = input.session.model?.provider;
306
+ const modelId = input.session.model?.modelId ?? input.session.model?.id;
307
+ const sources = ["system", "conversation"];
308
+ if (activeTools.length > 0)
309
+ sources.push("tools");
310
+ if (input.imageCount > 0)
311
+ sources.push("images");
312
+ return {
313
+ type: "request_context",
314
+ sessionId: input.sessionId,
315
+ requestId: `${input.turnId}:request:1`,
316
+ step: 1,
317
+ ...(provider && modelId ? { model: { provider, modelId } } : {}),
318
+ ...(input.session.thinkingLevel
319
+ ? { thinkingLevel: input.session.thinkingLevel }
320
+ : {}),
321
+ systemPromptHash: createHash("sha256").update(systemPrompt).digest("hex"),
322
+ systemPromptChars: systemPrompt.length,
323
+ activeTools,
324
+ inputChars: input.text.length,
325
+ imageCount: input.imageCount,
326
+ sources,
327
+ };
328
+ }
300
329
  function readSessionManagerId(manager) {
301
330
  if (!manager || typeof manager !== "object")
302
331
  return undefined;
@@ -438,12 +467,12 @@ export class ConsolePiRuntime {
438
467
  downloadsDir: path.join(this.options.sessionDir, "..", "browser-downloads"),
439
468
  screenshotsDir: path.join(this.options.sessionDir, "..", "browser-screenshots"),
440
469
  });
441
- // Governed DB connections persist under the repo app-data namespace
442
- // (`<repoNamespace>/db/connections.json`) — never inside the repo.
470
+ // Governed DB connections are Console-global, not repository-scoped.
471
+ // This keeps named connections available when the user switches folders.
443
472
  this.ownsDbConnections = options.dbConnections === undefined;
444
473
  this.dbConnections =
445
474
  options.dbConnections ??
446
- new DbConnectionRegistry(path.join(this.options.sessionDir, "..", "db"));
475
+ new DbConnectionRegistry(path.join(options.globalAppDataRoot ?? path.resolve(this.options.sessionDir, ".."), "db"), undefined, undefined, path.join(this.options.sessionDir, "..", "db"));
447
476
  this.exploreOrchestrator = new ExploreOrchestrator({
448
477
  cwd: this.options.cwd,
449
478
  bindings: this.bindings,
@@ -1977,6 +2006,14 @@ export class ConsolePiRuntime {
1977
2006
  };
1978
2007
  emitContextUsage();
1979
2008
  await this.runPrePromptCompaction(sessionId, session, text, options?.images?.length ?? 0, onEvent);
2009
+ onEvent(buildRequestContextEvent({
2010
+ sessionId,
2011
+ turnId: options?.turnId ?? `${sessionId}:active`,
2012
+ session,
2013
+ text,
2014
+ imageCount: options?.images?.length ?? 0,
2015
+ }));
2016
+ let step = 0;
1980
2017
  let stepStartedAt;
1981
2018
  let firstTokenAt;
1982
2019
  let pendingProviderError;
@@ -1985,12 +2022,24 @@ export class ConsolePiRuntime {
1985
2022
  const emitPendingProviderError = () => {
1986
2023
  if (!pendingProviderError)
1987
2024
  return;
1988
- onEvent(pendingProviderError);
2025
+ const error = pendingProviderError;
2026
+ onEvent(error);
2027
+ onEvent({
2028
+ type: "turn_error",
2029
+ sessionId,
2030
+ step: Math.max(1, step),
2031
+ ...(error.providerError.code === undefined
2032
+ ? {}
2033
+ : { code: String(error.providerError.code) }),
2034
+ message: error.message,
2035
+ });
1989
2036
  pendingProviderError = undefined;
1990
2037
  providerErrorEmitted = true;
1991
2038
  };
1992
2039
  unsub = session.subscribe((event) => {
1993
2040
  const observedAt = Date.now();
2041
+ if (event.type === "turn_start")
2042
+ step += 1;
1994
2043
  if (event.type === "message_start" && isUserSdkMessage(event.message)) {
1995
2044
  if (!initialUserMessageStarted) {
1996
2045
  // The first user message_start belongs to session.prompt(text), not
@@ -2021,7 +2070,7 @@ export class ConsolePiRuntime {
2021
2070
  const compactionId = event.type === "compaction_start" || event.type === "compaction_end"
2022
2071
  ? activeCompaction?.compactionId ?? newCompactionId(sessionId)
2023
2072
  : undefined;
2024
- const mapped = mapSdkEvent(sessionId, event, compactionId);
2073
+ const mapped = mapSdkEvent(sessionId, event, compactionId, Math.max(1, step));
2025
2074
  if (event.type === "compaction_end")
2026
2075
  activeCompaction = undefined;
2027
2076
  if (mapped?.type === "error") {
@@ -2046,7 +2095,7 @@ export class ConsolePiRuntime {
2046
2095
  if (mapped?.type === "agent_settled") {
2047
2096
  emitPendingProviderError();
2048
2097
  }
2049
- if (mapped?.type === "agent_start") {
2098
+ if (mapped?.type === "step_start" || mapped?.type === "agent_start") {
2050
2099
  stepStartedAt = observedAt;
2051
2100
  firstTokenAt = undefined;
2052
2101
  }
@@ -2074,6 +2123,13 @@ export class ConsolePiRuntime {
2074
2123
  : { outputTokens: usage.outputTokens }),
2075
2124
  };
2076
2125
  onEvent({ ...mapped, ...(timing ? { timing } : {}) });
2126
+ if (mapped.stopReason === "length") {
2127
+ onEvent({
2128
+ type: "turn_max_tokens",
2129
+ sessionId,
2130
+ step: mapped.step ?? Math.max(1, step),
2131
+ });
2132
+ }
2077
2133
  if (usage)
2078
2134
  onEvent({ type: "usage", sessionId, usage });
2079
2135
  emitContextUsage();
@@ -2331,7 +2387,7 @@ export class ConsolePiRuntime {
2331
2387
  this.dbConnections.dispose();
2332
2388
  }
2333
2389
  }
2334
- function mapAssistantMessageEvent(type, sessionId, message) {
2390
+ function mapAssistantMessageEvent(type, sessionId, message, step) {
2335
2391
  const record = message !== null && typeof message === "object"
2336
2392
  ? message
2337
2393
  : undefined;
@@ -2355,6 +2411,10 @@ function mapAssistantMessageEvent(type, sessionId, message) {
2355
2411
  sessionId,
2356
2412
  text: extractAssistantText(message),
2357
2413
  ...(content.length > 0 ? { content } : {}),
2414
+ step,
2415
+ ...(type === "message_end" && typeof record?.stopReason === "string"
2416
+ ? { stopReason: record.stopReason }
2417
+ : {}),
2358
2418
  };
2359
2419
  }
2360
2420
  function isUserSdkMessage(message) {
@@ -2362,10 +2422,12 @@ function isUserSdkMessage(message) {
2362
2422
  typeof message === "object" &&
2363
2423
  message.role === "user");
2364
2424
  }
2365
- function mapSdkEvent(sessionId, event, compactionId) {
2425
+ export function mapSdkEvent(sessionId, event, compactionId, step = 1) {
2366
2426
  switch (event.type) {
2367
2427
  case "agent_start":
2368
2428
  return { type: "agent_start", sessionId };
2429
+ case "turn_start":
2430
+ return { type: "step_start", sessionId, step };
2369
2431
  case "agent_end":
2370
2432
  return {
2371
2433
  type: "agent_end",
@@ -2375,15 +2437,50 @@ function mapSdkEvent(sessionId, event, compactionId) {
2375
2437
  case "message_update":
2376
2438
  if (!isAssistantSdkMessage(event.message))
2377
2439
  return undefined;
2378
- return mapAssistantMessageEvent("message_update", sessionId, event.message);
2440
+ return mapAssistantMessageEvent("message_update", sessionId, event.message, step);
2379
2441
  case "message_end":
2380
2442
  if (!isAssistantSdkMessage(event.message))
2381
2443
  return undefined;
2382
- return mapAssistantMessageEvent("message_end", sessionId, event.message);
2383
- case "turn_end":
2384
- // Usage is emitted from message_end (see prompt subscribe). turn_end
2385
- // carries the same final assistant message and would double-count.
2386
- return undefined;
2444
+ return mapAssistantMessageEvent("message_end", sessionId, event.message, step);
2445
+ case "turn_end": {
2446
+ const record = event.message && typeof event.message === "object"
2447
+ ? event.message
2448
+ : undefined;
2449
+ const reason = record?.stopReason;
2450
+ return {
2451
+ type: "step_end",
2452
+ sessionId,
2453
+ step,
2454
+ outcome: reason === "error"
2455
+ ? "failed"
2456
+ : reason === "aborted"
2457
+ ? "interrupted"
2458
+ : "completed",
2459
+ };
2460
+ }
2461
+ case "auto_retry_start":
2462
+ return {
2463
+ type: "model_retry",
2464
+ sessionId,
2465
+ step,
2466
+ attempt: event.attempt,
2467
+ maxAttempts: event.maxAttempts,
2468
+ delayMs: event.delayMs,
2469
+ errorMessage: redactRuntimeText(event.errorMessage, 600),
2470
+ state: "scheduled",
2471
+ };
2472
+ case "auto_retry_end":
2473
+ return {
2474
+ type: "model_retry",
2475
+ sessionId,
2476
+ step,
2477
+ attempt: event.attempt,
2478
+ state: event.success ? "completed" : "failed",
2479
+ success: event.success,
2480
+ ...(event.finalError
2481
+ ? { errorMessage: redactRuntimeText(event.finalError, 600) }
2482
+ : {}),
2483
+ };
2387
2484
  case "tool_execution_start":
2388
2485
  return {
2389
2486
  type: "tool_call",
@@ -2391,6 +2488,10 @@ function mapSdkEvent(sessionId, event, compactionId) {
2391
2488
  toolCallId: event.toolCallId,
2392
2489
  toolName: event.toolName,
2393
2490
  args: event.args,
2491
+ step,
2492
+ ...(event.parentToolCallId
2493
+ ? { parentToolCallId: event.parentToolCallId }
2494
+ : {}),
2394
2495
  };
2395
2496
  case "tool_execution_end":
2396
2497
  return {
@@ -2400,6 +2501,10 @@ function mapSdkEvent(sessionId, event, compactionId) {
2400
2501
  toolName: event.toolName,
2401
2502
  result: event.result,
2402
2503
  isError: event.isError,
2504
+ step,
2505
+ ...(event.parentToolCallId
2506
+ ? { parentToolCallId: event.parentToolCallId }
2507
+ : {}),
2403
2508
  };
2404
2509
  case "agent_settled":
2405
2510
  return { type: "agent_settled", sessionId };
@@ -2446,8 +2551,19 @@ function mapSdkEvent(sessionId, event, compactionId) {
2446
2551
  : {}),
2447
2552
  };
2448
2553
  }
2449
- default:
2554
+ case "message_start":
2555
+ case "queue_update":
2450
2556
  return undefined;
2557
+ default:
2558
+ {
2559
+ const unknownEvent = event;
2560
+ return {
2561
+ type: "unknown",
2562
+ sessionId,
2563
+ eventType: String(unknownEvent.type ?? "unknown"),
2564
+ value: unknownEvent,
2565
+ };
2566
+ }
2451
2567
  }
2452
2568
  }
2453
2569
  function newCompactionId(sessionId) {
@@ -85,15 +85,3 @@ export function parseProviderError(value) {
85
85
  category: classify({ message, raw, code }),
86
86
  };
87
87
  }
88
- export function providerErrorTitle(category) {
89
- switch (category) {
90
- case "authentication":
91
- return "认证失败";
92
- case "context-overflow":
93
- return "上下文过大";
94
- case "rate-limit":
95
- return "请求过于频繁";
96
- default:
97
- return "Provider 请求失败";
98
- }
99
- }
@@ -19,7 +19,7 @@ import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_
19
19
  import { InvalidChatSessionModeError, SESSION_MODE_ACTION_DENIED_CODE, SESSION_MODE_ACTION_DENIED_MESSAGE, SESSION_MODE_IMMUTABLE_CODE, UnknownChatSessionModeError, isWorkflowSessionMode, parseCreateSessionMode, readStoredSessionMode, shouldAcceptOperatorSnapshot, } from "./session-mode.js";
20
20
  import { buildModelCallableToolSchemas } from "./tool-adapter.js";
21
21
  import { cleanChatTitle, acceptedOperationIdFromResult, isBlankChatSession } from "./session-store.js";
22
- import { hashChatTurnPayload, projectChatToolEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
22
+ import { hashChatTurnPayload, projectChatToolEventPayload, projectUnknownChatEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
23
23
  import { projectDeferredTurnSummary, projectDeferredTurnSummaries, } from "./deferred-turn.js";
24
24
  import { THINKING_LEVELS, RuntimeSnapshotUnavailableError, } from "./pi-runtime.js";
25
25
  import { ResourcePreferencesError, setSkillAutoInvocationPreference, } from "./resource-preferences-store.js";
@@ -197,6 +197,16 @@ export function projectStoredMessagesWithEventOrder(messages, events) {
197
197
  ...(finish
198
198
  ? { finishedSeq: finish.seq, finishedAt: finish.at }
199
199
  : {}),
200
+ ...(start && typeof start.data.step === "number"
201
+ ? { step: start.data.step }
202
+ : {}),
203
+ ...(start &&
204
+ typeof start.data.parentToolCallId ===
205
+ "string"
206
+ ? {
207
+ parentToolCallId: start.data.parentToolCallId,
208
+ }
209
+ : {}),
200
210
  };
201
211
  });
202
212
  }
@@ -243,10 +253,99 @@ function writeChatSse(res, event) {
243
253
  /** Map a runtime ChatTurnEvent to the persisted event-store partial.
244
254
  *
245
255
  * Returns undefined for heartbeat (not persisted — keep-alive only). */
246
- function turnEventToStorePartial(event) {
256
+ export function turnEventToStorePartial(event) {
247
257
  switch (event.type) {
248
258
  case "agent_start":
249
259
  return { kind: "agent_start", data: {} };
260
+ case "step_start":
261
+ case "step_end":
262
+ return {
263
+ kind: event.type,
264
+ data: {
265
+ step: Number(event.step ?? 1),
266
+ ...(event.type === "step_end"
267
+ ? { outcome: String(event.outcome ?? "completed") }
268
+ : {}),
269
+ },
270
+ };
271
+ case "model_retry":
272
+ {
273
+ const retryState = event.state === "completed" || event.state === "failed"
274
+ ? event.state
275
+ : "scheduled";
276
+ return {
277
+ kind: "model_retry",
278
+ data: {
279
+ step: Number(event.step ?? 1),
280
+ attempt: Number(event.attempt ?? 1),
281
+ ...(typeof event.maxAttempts === "number"
282
+ ? { maxAttempts: event.maxAttempts }
283
+ : {}),
284
+ ...(typeof event.delayMs === "number" ? { delayMs: event.delayMs } : {}),
285
+ ...(typeof event.errorMessage === "string"
286
+ ? { errorMessage: event.errorMessage }
287
+ : {}),
288
+ state: retryState,
289
+ ...(typeof event.success === "boolean" ? { success: event.success } : {}),
290
+ },
291
+ };
292
+ }
293
+ case "turn_max_tokens":
294
+ return {
295
+ kind: "turn_max_tokens",
296
+ data: { step: Number(event.step ?? 1) },
297
+ };
298
+ case "turn_error":
299
+ return {
300
+ kind: "turn_error",
301
+ data: {
302
+ ...(typeof event.step === "number" ? { step: event.step } : {}),
303
+ ...(typeof event.code === "string" ? { code: event.code } : {}),
304
+ message: String(event.message ?? "模型请求失败"),
305
+ },
306
+ };
307
+ case "request_context":
308
+ return {
309
+ kind: "request_context",
310
+ data: {
311
+ requestId: String(event.requestId ?? ""),
312
+ step: Number(event.step ?? 1),
313
+ ...(event.model && typeof event.model === "object"
314
+ ? { model: event.model }
315
+ : {}),
316
+ ...(typeof event.thinkingLevel === "string"
317
+ ? { thinkingLevel: event.thinkingLevel }
318
+ : {}),
319
+ systemPromptHash: String(event.systemPromptHash ?? ""),
320
+ systemPromptChars: Number(event.systemPromptChars ?? 0),
321
+ activeTools: Array.isArray(event.activeTools) ? event.activeTools : [],
322
+ inputChars: Number(event.inputChars ?? 0),
323
+ imageCount: Number(event.imageCount ?? 0),
324
+ sources: Array.isArray(event.sources) ? event.sources : [],
325
+ },
326
+ };
327
+ case "command":
328
+ return {
329
+ kind: "command",
330
+ data: {
331
+ commandId: String(event.commandId ?? ""),
332
+ name: String(event.name ?? "unknown"),
333
+ ...(typeof event.argsPreview === "string"
334
+ ? { argsPreview: event.argsPreview }
335
+ : {}),
336
+ ...(typeof event.action === "string" ? { action: event.action } : {}),
337
+ outcome: event.outcome === "failed" ? "failed" : "completed",
338
+ ...(typeof event.message === "string" ? { message: event.message } : {}),
339
+ },
340
+ };
341
+ case "unknown":
342
+ return {
343
+ kind: "unknown",
344
+ data: projectUnknownChatEventPayload({
345
+ eventType: String(event.eventType ?? "unknown"),
346
+ value: event.value,
347
+ }),
348
+ };
250
349
  case "message_update":
251
350
  case "message_end": {
252
351
  const content = parseAssistantContent(event.content);
@@ -291,6 +390,10 @@ function turnEventToStorePartial(event) {
291
390
  event.messageId
292
391
  ? { messageId: event.messageId }
293
392
  : {}),
393
+ ...(typeof event.step === "number" ? { step: event.step } : {}),
394
+ ...(typeof event.stopReason === "string"
395
+ ? { stopReason: event.stopReason }
396
+ : {}),
294
397
  },
295
398
  };
296
399
  }
@@ -304,6 +407,10 @@ function turnEventToStorePartial(event) {
304
407
  toolCallId: String(event.toolCallId ?? ""),
305
408
  toolName: String(event.toolName ?? ""),
306
409
  value: event.args,
410
+ ...(typeof event.step === "number" ? { step: event.step } : {}),
411
+ ...(typeof event.parentToolCallId === "string"
412
+ ? { parentToolCallId: event.parentToolCallId }
413
+ : {}),
307
414
  }),
308
415
  };
309
416
  case "tool_result": {
@@ -318,6 +425,10 @@ function turnEventToStorePartial(event) {
318
425
  toolName: String(event.toolName ?? ""),
319
426
  value: event.result,
320
427
  isError: Boolean(event.isError),
428
+ ...(typeof event.step === "number" ? { step: event.step } : {}),
429
+ ...(typeof event.parentToolCallId === "string"
430
+ ? { parentToolCallId: event.parentToolCallId }
431
+ : {}),
321
432
  ...(resultOperationId ? { operationId: resultOperationId } : {}),
322
433
  }),
323
434
  };
@@ -1029,6 +1140,16 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
1029
1140
  turnId: event.turnId,
1030
1141
  data: event.data,
1031
1142
  })),
1143
+ conversationEvents: snapshot.filter((event) => [
1144
+ "step_start",
1145
+ "step_end",
1146
+ "model_retry",
1147
+ "turn_error",
1148
+ "turn_max_tokens",
1149
+ "request_context",
1150
+ "command",
1151
+ "unknown",
1152
+ ].includes(event.kind)),
1032
1153
  lastEventId: lastEvent?.eventId ?? null,
1033
1154
  lastEventSeq: lastEvent?.seq ?? 0,
1034
1155
  });
@@ -2982,7 +3103,8 @@ async function handleSddBingo(req, res, deps, sessionId) {
2982
3103
  throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "请求体只允许 skillName 字段", 400);
2983
3104
  }
2984
3105
  const result = await deps.sddDataAlignment.report(sessionId, body.skillName);
2985
- deps.events.append(sessionId, deps.events.latestTurnId(sessionId) ?? `${sessionId}:operator-action`, {
3106
+ const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:operator-action`;
3107
+ deps.events.append(sessionId, turnId, {
2986
3108
  kind: "operator-action",
2987
3109
  data: {
2988
3110
  schemaVersion: 1,
@@ -2991,6 +3113,17 @@ async function handleSddBingo(req, res, deps, sessionId) {
2991
3113
  telemetrySessionId: result.telemetrySessionId,
2992
3114
  },
2993
3115
  });
3116
+ deps.events.append(sessionId, `${sessionId}:command:${result.telemetrySessionId}`, {
3117
+ kind: "command",
3118
+ data: {
3119
+ commandId: `sdd-bingo:${result.telemetrySessionId}`,
3120
+ name: "sdd-bingo",
3121
+ ...(result.skillName ? { argsPreview: result.skillName } : {}),
3122
+ action: "sdd-bingo",
3123
+ outcome: "completed",
3124
+ message: `Session: ${result.telemetrySessionId}`,
3125
+ },
3126
+ });
2994
3127
  sendJson(res, 200, {
2995
3128
  ok: true,
2996
3129
  skillName: result.skillName,
@@ -3,7 +3,89 @@
3
3
  */
4
4
  import { getDisplayableAssistantBlocks, joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
5
5
  import { dagProgressActivityForTool, semanticActivityForTool, } from "./semantic-activity.js";
6
+ import { compareChatOrder } from "./turn-order.js";
6
7
  import { ensureLinkedSubmissionOrder } from "./timeline-message-order.js";
8
+ function conversationNodeOrder(node) {
9
+ return { eventSeq: node.anchorSeq, createdAt: node.at };
10
+ }
11
+ function processItemOrder(item, group) {
12
+ const createdAt = item.createdAt ??
13
+ (item.kind === "tool" ? item.tool.createdAt : undefined) ??
14
+ group.assistants[0]?.createdAt ??
15
+ 0;
16
+ const eventSeq = item.eventSeq ?? (item.kind === "tool" ? item.tool.eventSeq : undefined);
17
+ return eventSeq === undefined ? { createdAt } : { eventSeq, createdAt };
18
+ }
19
+ function compareFlowEntries(a, b) {
20
+ return compareChatOrder(a.order, b.order);
21
+ }
22
+ /** Base identity for a rendered process row. Content is deliberately excluded
23
+ * so streaming text updates keep the same React instance. */
24
+ function processItemKey(item, order) {
25
+ if (item.kind === "tool")
26
+ return `tool:${item.tool.id}`;
27
+ if (item.kind === "activity")
28
+ return `activity:${item.activity.id}`;
29
+ if (item.kind === "dag-progress")
30
+ return `dag-progress:${item.progress.id}`;
31
+ const anchor = order.eventSeq ?? order.createdAt;
32
+ return `${item.kind}:${anchor}`;
33
+ }
34
+ export function turnProcessItemKey(item, occurrence = 0) {
35
+ const order = {
36
+ ...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
37
+ createdAt: item.createdAt ?? (item.kind === "tool" ? item.tool.createdAt : 0),
38
+ };
39
+ const base = processItemKey(item, order);
40
+ return occurrence === 0 ? base : `${base}:${occurrence}`;
41
+ }
42
+ /**
43
+ * Project one turn into the same ordered buckets used by the main timeline and
44
+ * the trajectory view. Only existing request/running-step nodes are prelude;
45
+ * retry and other lifecycle nodes stay in the collapsible process window.
46
+ */
47
+ export function projectTurnFlow(group) {
48
+ const conversationItems = group.conversationItems ?? [];
49
+ const prelude = conversationItems
50
+ .filter((item) => item.kind === "request" ||
51
+ (item.kind === "step" && item.node.status === "running"))
52
+ .sort((a, b) => compareChatOrder(conversationNodeOrder(a.node), conversationNodeOrder(b.node)));
53
+ const postAnswer = conversationItems
54
+ .filter((item) => item.kind === "max-tokens")
55
+ .sort((a, b) => compareChatOrder(conversationNodeOrder(a.node), conversationNodeOrder(b.node)));
56
+ const preludeKeys = new Set(prelude.map((item) => item.node.key));
57
+ const postAnswerKeys = new Set(postAnswer.map((item) => item.node.key));
58
+ const process = [];
59
+ for (const item of group.processItems) {
60
+ process.push({
61
+ source: "process",
62
+ key: "",
63
+ item,
64
+ order: processItemOrder(item, group),
65
+ });
66
+ }
67
+ for (const item of conversationItems) {
68
+ if (preludeKeys.has(item.node.key) || postAnswerKeys.has(item.node.key))
69
+ continue;
70
+ process.push({
71
+ source: "conversation",
72
+ key: `conversation:${item.node.key}`,
73
+ item,
74
+ order: conversationNodeOrder(item.node),
75
+ });
76
+ }
77
+ process.sort(compareFlowEntries);
78
+ const occurrences = new Map();
79
+ for (const entry of process) {
80
+ const base = entry.source === "conversation"
81
+ ? entry.key
82
+ : processItemKey(entry.item, entry.order);
83
+ const occurrence = occurrences.get(base) ?? 0;
84
+ occurrences.set(base, occurrence + 1);
85
+ entry.key = occurrence === 0 ? base : `${base}:${occurrence}`;
86
+ }
87
+ return { prelude, process, postAnswer };
88
+ }
7
89
  function emptyGroup(key, turnId) {
8
90
  return {
9
91
  key,
@@ -11,6 +93,7 @@ function emptyGroup(key, turnId) {
11
93
  assistants: [],
12
94
  tools: [],
13
95
  processItems: [],
96
+ conversationItems: [],
14
97
  live: false,
15
98
  };
16
99
  }
@@ -92,6 +175,10 @@ export function hydrateSessionThread(raw) {
92
175
  ? finishedAtMs
93
176
  : createdAt,
94
177
  turnId: item.turnId ?? turnId,
178
+ ...(item.step === undefined ? {} : { step: item.step }),
179
+ ...(item.parentToolCallId
180
+ ? { parentToolCallId: item.parentToolCallId }
181
+ : {}),
95
182
  ...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
96
183
  ...(item.finishedSeq === undefined
97
184
  ? {}
@@ -132,17 +219,21 @@ function flushToolBuffer(processItems, buffer) {
132
219
  return;
133
220
  const sorted = sortToolsStable(buffer);
134
221
  for (const tool of sorted) {
222
+ const order = {
223
+ ...(tool.eventSeq === undefined ? {} : { eventSeq: tool.eventSeq }),
224
+ createdAt: tool.createdAt,
225
+ };
135
226
  const dagProgress = dagProgressActivityForTool(tool);
136
227
  if (dagProgress) {
137
- processItems.push({ kind: "dag-progress", progress: dagProgress });
228
+ processItems.push({ kind: "dag-progress", progress: dagProgress, ...order });
138
229
  continue;
139
230
  }
140
231
  const activity = semanticActivityForTool(tool);
141
232
  if (activity) {
142
- processItems.push({ kind: "activity", activity });
233
+ processItems.push({ kind: "activity", activity, ...order });
143
234
  }
144
235
  else {
145
- processItems.push({ kind: "tool", tool });
236
+ processItems.push({ kind: "tool", tool, ...order });
146
237
  }
147
238
  }
148
239
  }
@@ -185,12 +276,23 @@ function fillProcessDetails(group) {
185
276
  };
186
277
  for (const block of blocks) {
187
278
  if (block.type === "thinking") {
188
- items.push({ kind: "thinking", thinking: block.thinking });
279
+ items.push({
280
+ kind: "thinking",
281
+ thinking: block.thinking,
282
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
283
+ createdAt: anchorAt,
284
+ });
189
285
  continue;
190
286
  }
191
287
  if (block.type === "text") {
192
- if (block.text.trim())
193
- items.push({ kind: "text", text: block.text });
288
+ if (block.text.trim()) {
289
+ items.push({
290
+ kind: "text",
291
+ text: block.text,
292
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
293
+ createdAt: anchorAt,
294
+ });
295
+ }
194
296
  continue;
195
297
  }
196
298
  const tool = group.tools.find((entry) => entry.id === block.toolCallId);
@@ -213,7 +315,12 @@ function fillProcessDetails(group) {
213
315
  anchorAt = tool.createdAt;
214
316
  }
215
317
  if (includeProviderError && assistant.providerError) {
216
- items.push({ kind: "provider-error", error: assistant.providerError });
318
+ items.push({
319
+ kind: "provider-error",
320
+ error: assistant.providerError,
321
+ ...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
322
+ createdAt: anchorAt,
323
+ });
217
324
  }
218
325
  flushItems();
219
326
  // Preserve the old-data fallback after this stage. Sequenced unmatched
@@ -401,6 +508,14 @@ export function buildTurnProcessGroups(input) {
401
508
  assigned.add(tool.id);
402
509
  }
403
510
  }
511
+ for (const node of input.conversationNodes ?? []) {
512
+ if (node.location.kind === "session")
513
+ continue;
514
+ const turnId = node.location.turnId;
515
+ if (!groups.some((group) => group.turnId === turnId)) {
516
+ startGroup(`turn-${turnId}`, turnId);
517
+ }
518
+ }
404
519
  for (const group of groups) {
405
520
  group.tools.sort((a, b) => {
406
521
  if (a.eventSeq !== undefined && b.eventSeq !== undefined) {
@@ -413,6 +528,12 @@ export function buildTurnProcessGroups(input) {
413
528
  input.activeAssistantId !== null &&
414
529
  group.assistants.some((a) => a.id === input.activeAssistantId);
415
530
  fillProcessDetails(group);
531
+ group.conversationItems = (input.conversationNodes ?? [])
532
+ .filter((node) => node.location.kind !== "session" && node.location.turnId === group.turnId)
533
+ .map((node) => ({ kind: node.kind, node }));
534
+ if (group.conversationItems.some((item) => item.kind === "turn-error")) {
535
+ group.processItems = group.processItems.filter((item) => item.kind !== "provider-error");
536
+ }
416
537
  }
417
538
  return groups;
418
539
  }