@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.3

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 (99) hide show
  1. package/CHANGELOG.md +178 -175
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/executors/pi-sdk-executor.js +29 -1
  4. package/dist/shared/wizard-local-telemetry.js +50 -0
  5. package/dist/worker/console/chat/chat-event-store.js +3 -0
  6. package/dist/worker/console/chat/db-connections.js +790 -0
  7. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
  8. package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +627 -0
  9. package/dist/worker/console/chat/pi-runtime.js +66 -6
  10. package/dist/worker/console/chat/routes.js +154 -3
  11. package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
  12. package/dist/worker/console/chat/timeline-message-order.js +22 -0
  13. package/dist/worker/console/chat/tools.js +34 -0
  14. package/dist/worker/console/chat/turn-process.js +43 -39
  15. package/dist/worker/console/dag-confirmation.js +3 -0
  16. package/dist/worker/console/dag-execution-receipt.js +3 -0
  17. package/dist/worker/console/operator-actions.js +32 -2
  18. package/dist/worker/console/server.js +5 -1
  19. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-COQlLTI7.js} +1 -1
  20. package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-zN3QkNaV.js} +1 -1
  21. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-Pyoyj5f2.js} +1 -1
  22. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-BNppA5WW.js} +1 -1
  23. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-lyxL8nTx.js} +1 -1
  24. package/dist/worker/console/static/assets/channel-CgLPkTw2.js +1 -0
  25. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-0hgjcdK4.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-D63N9d4i.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-CUw2AyB5.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-Q_kwZT30.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-C7vMiKcw.js} +1 -1
  30. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-T9GbV5Hz.js} +1 -1
  31. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-D8nUSUJI.js} +1 -1
  32. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-BCH2ewdp.js} +1 -1
  33. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-BTdDuDBN.js} +1 -1
  34. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-Cqm2RcKF.js} +1 -1
  35. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuSD1Mhb.js +1 -0
  36. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuSD1Mhb.js +1 -0
  37. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-pjwfpVu4.js} +1 -1
  38. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-BvalptKA.js} +1 -1
  39. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BU4qJ6d7.js} +1 -1
  40. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-DVTpq8J9.js} +1 -1
  41. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-B8s1W6Fc.js} +1 -1
  42. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DPrFk9yH.js} +1 -1
  43. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-CnQIQsHC.js} +1 -1
  44. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-op0DEpCv.js} +1 -1
  45. package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-DXQamm4T.js} +1 -1
  46. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-b1ad2C0r.js} +1 -1
  47. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-B4cD-ZFJ.js} +1 -1
  48. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-CrgGjlQ1.js} +1 -1
  49. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-DovmXQNV.js} +1 -1
  50. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-DpFyu2Jn.js} +1 -1
  51. package/dist/worker/console/static/assets/index-DHBWEwz_.css +1 -0
  52. package/dist/worker/console/static/assets/index-JRfTUgP7.js +478 -0
  53. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-D3MoQZIl.js} +1 -1
  54. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-Ci6C1yjS.js} +1 -1
  55. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-BWMo2lIP.js} +1 -1
  56. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdthIAg3.js} +1 -1
  57. package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DuFAlSOj.js} +1 -1
  58. package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-Dqz3Slj9.js} +5 -5
  59. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-DNVTrD09.js} +1 -1
  60. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-CgfZPfBh.js} +1 -1
  61. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-ClM9P7yo.js} +1 -1
  62. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-DwnEak2J.js} +1 -1
  63. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-rK4hxt9P.js} +1 -1
  64. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-Cjqa6vo3.js} +1 -1
  65. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-D3Fa250Z.js} +1 -1
  66. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-2PdrNajj.js} +1 -1
  67. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-BBcuGeZn.js} +1 -1
  68. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CSJX9X6s.js} +1 -1
  69. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cqskj0zt.js +1 -0
  70. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-pOUU1bic.js} +2 -2
  71. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CM0ZIhNP.js +8 -0
  72. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-CAeqfQn0.js} +1 -1
  73. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-DYPLOMVO.js} +1 -1
  74. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-Dy2V0Cha.js} +1 -1
  75. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-Dj9o7jrn.js} +1 -1
  76. package/dist/worker/console/static/index.html +2 -2
  77. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +33 -1
  78. package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
  79. package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
  80. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
  81. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
  82. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +15 -7
  83. package/dist/worker/console/static-src/operator-chat/useChatStream.js +47 -5
  84. package/dist/worker/console/static-src/operator-chat/useChatThread.js +24 -2
  85. package/dist/worker/console/workspace-context.js +2 -0
  86. package/dist/worker/observe/static/console-theme.css +174 -0
  87. package/dist/worker/observe/static/console-theme.js +36 -0
  88. package/dist/worker/observe/static/operator-chrome.css +85 -0
  89. package/dist/worker/observe/static/operator-chrome.js +51 -0
  90. package/dist/worker/scheduler/scheduled-goal-evidence.js +60 -2
  91. package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
  92. package/package.json +3 -1
  93. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
  94. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
  95. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
  96. package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
  97. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
  98. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
  99. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
@@ -48,6 +48,8 @@ import { BrowserAutomationService } from "./browser-automation.js";
48
48
  import { buildTodoContinuationContext, } from "./todos.js";
49
49
  import { buildPersistentTerminalTools } from "./terminal-tools.js";
50
50
  import { buildScmTools } from "./scm-tools.js";
51
+ import { DbConnectionRegistry } from "./db-connections.js";
52
+ import { buildSqlTools } from "./pi-runtime/custom-tools/sql-tools.js";
51
53
  import { buildGoalContinuationContext, } from "./goals.js";
52
54
  import { injectWizardLocalHeaders, } from "./sdd-data-alignment.js";
53
55
  import { buildScheduledGoalTools } from "./pi-runtime/custom-tools/scheduled-goal-tools.js";
@@ -418,12 +420,14 @@ export class ConsolePiRuntime {
418
420
  compactionControllers = new Map();
419
421
  siftStates = new Map();
420
422
  /** Session-less tool definition caches (no AgentSession state). */
421
- operatorCustomToolsByMode = new Map();
423
+ operatorCustomToolsBySession = new Map();
422
424
  exploreCustomToolsPromise;
423
425
  exploreOrchestrator;
424
426
  bindings;
425
427
  browserAutomation;
426
428
  ownsBrowserAutomation;
429
+ dbConnections;
430
+ ownsDbConnections;
427
431
  constructor(options) {
428
432
  this.options = options;
429
433
  this.loader = createOperatorChatResourceLoader();
@@ -434,6 +438,12 @@ export class ConsolePiRuntime {
434
438
  downloadsDir: path.join(this.options.sessionDir, "..", "browser-downloads"),
435
439
  screenshotsDir: path.join(this.options.sessionDir, "..", "browser-screenshots"),
436
440
  });
441
+ // Governed DB connections are Console-global, not repository-scoped.
442
+ // This keeps named connections available when the user switches folders.
443
+ this.ownsDbConnections = options.dbConnections === undefined;
444
+ this.dbConnections =
445
+ options.dbConnections ??
446
+ new DbConnectionRegistry(path.join(options.globalAppDataRoot ?? path.resolve(this.options.sessionDir, ".."), "db"), undefined, undefined, path.join(this.options.sessionDir, "..", "db"));
437
447
  this.exploreOrchestrator = new ExploreOrchestrator({
438
448
  cwd: this.options.cwd,
439
449
  bindings: this.bindings,
@@ -1018,7 +1028,7 @@ export class ConsolePiRuntime {
1018
1028
  const loadWorkflowTools = shouldLoadWorkflowBusinessTools(sessionMode);
1019
1029
  const [operatorCustomTools, exploreCustomTools] = await Promise.all([
1020
1030
  loadWorkflowTools
1021
- ? this.loadOperatorCustomTools(sessionMode)
1031
+ ? this.loadOperatorCustomTools(sessionId, sessionMode)
1022
1032
  : Promise.resolve([]),
1023
1033
  this.loadExploreCustomTools(),
1024
1034
  ]);
@@ -1059,6 +1069,16 @@ export class ConsolePiRuntime {
1059
1069
  return [];
1060
1070
  })
1061
1071
  : [];
1072
+ const sqlTools = await buildSqlTools({
1073
+ sessionId,
1074
+ registry: this.dbConnections,
1075
+ ...(this.options.questionRegistry
1076
+ ? { questionRegistry: this.options.questionRegistry }
1077
+ : {}),
1078
+ }).catch((error) => {
1079
+ process.stderr.write(`[console] chat sql-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
1080
+ return [];
1081
+ });
1062
1082
  const agentTool = await buildAgentCustomTool({
1063
1083
  parentSessionId: sessionId,
1064
1084
  orchestrator: this.exploreOrchestrator,
@@ -1092,25 +1112,28 @@ export class ConsolePiRuntime {
1092
1112
  ...terminalTools,
1093
1113
  ...scmTools,
1094
1114
  ...browserTools,
1115
+ ...sqlTools,
1095
1116
  ...(agentTool ? [agentTool] : []),
1096
1117
  ];
1097
1118
  }
1098
- loadOperatorCustomTools(sessionMode) {
1119
+ loadOperatorCustomTools(sessionId, sessionMode) {
1099
1120
  if (!this.options.actionContext)
1100
1121
  return Promise.resolve([]);
1101
- const cached = this.operatorCustomToolsByMode.get(sessionMode);
1122
+ const cacheKey = `${sessionId}:${sessionMode}`;
1123
+ const cached = this.operatorCustomToolsBySession.get(cacheKey);
1102
1124
  if (cached)
1103
1125
  return cached;
1104
1126
  const promise = buildCustomOperatorTools({
1105
1127
  actionContext: this.options.actionContext,
1128
+ sessionId,
1106
1129
  sessionMode,
1107
1130
  }).catch((error) => {
1108
1131
  process.stderr.write(`[console] chat custom-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
1109
1132
  // Allow a later create/reopen to retry after a transient failure.
1110
- this.operatorCustomToolsByMode.delete(sessionMode);
1133
+ this.operatorCustomToolsBySession.delete(cacheKey);
1111
1134
  return [];
1112
1135
  });
1113
- this.operatorCustomToolsByMode.set(sessionMode, promise);
1136
+ this.operatorCustomToolsBySession.set(cacheKey, promise);
1114
1137
  return promise;
1115
1138
  }
1116
1139
  loadExploreCustomTools() {
@@ -2232,6 +2255,36 @@ export class ConsolePiRuntime {
2232
2255
  const result = await session.compact(customInstructions);
2233
2256
  return projectCompactSnapshot(result ?? {});
2234
2257
  }
2258
+ listDatabaseConnections() {
2259
+ return this.dbConnections.list();
2260
+ }
2261
+ addDatabaseConnection(input) {
2262
+ return this.dbConnections.add(input);
2263
+ }
2264
+ updateDatabaseConnection(currentName, input) {
2265
+ return this.dbConnections.update(currentName, input);
2266
+ }
2267
+ removeDatabaseConnection(name) {
2268
+ return this.dbConnections.remove(name);
2269
+ }
2270
+ exportDatabaseConnections() {
2271
+ return this.dbConnections.exportConfig();
2272
+ }
2273
+ importDatabaseConnections(input, conflict) {
2274
+ return this.dbConnections.importConfig(input, conflict);
2275
+ }
2276
+ testDatabaseConnection(name) {
2277
+ return this.dbConnections.test(name);
2278
+ }
2279
+ listDatabaseDatabases(name) {
2280
+ return this.dbConnections.listDatabases(name);
2281
+ }
2282
+ listDatabaseTables(name, database) {
2283
+ return this.dbConnections.listTables(name, database);
2284
+ }
2285
+ browseDatabaseTable(name, table, page, pageSize, database) {
2286
+ return this.dbConnections.browseTable(name, table, page, pageSize, database);
2287
+ }
2235
2288
  dispose(sessionId) {
2236
2289
  // Mark disposed first so a late materialization never registers ready.
2237
2290
  this.disposedSessions.add(sessionId);
@@ -2239,6 +2292,11 @@ export class ConsolePiRuntime {
2239
2292
  this.sessionInit.delete(sessionId);
2240
2293
  this.compactionControllers.delete(sessionId);
2241
2294
  this.siftStates.delete(sessionId);
2295
+ for (const key of this.operatorCustomToolsBySession.keys()) {
2296
+ if (key.startsWith(`${sessionId}:`)) {
2297
+ this.operatorCustomToolsBySession.delete(key);
2298
+ }
2299
+ }
2242
2300
  const session = this.sessions.get(sessionId);
2243
2301
  if (session) {
2244
2302
  try {
@@ -2269,6 +2327,8 @@ export class ConsolePiRuntime {
2269
2327
  void this.exploreOrchestrator.abortAll();
2270
2328
  if (this.ownsBrowserAutomation)
2271
2329
  void this.browserAutomation.dispose();
2330
+ if (this.ownsDbConnections)
2331
+ this.dbConnections.dispose();
2272
2332
  }
2273
2333
  }
2274
2334
  function mapAssistantMessageEvent(type, sessionId, message) {
@@ -15,7 +15,7 @@
15
15
  import { randomUUID } from "node:crypto";
16
16
  import { access } from "node:fs/promises";
17
17
  import { parseAssistantContent } from "./assistant-content.js";
18
- import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
18
+ import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, OPERATOR_CHAT_SQL_TOOL_CAPABILITIES, } from "./tools.js";
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";
@@ -49,6 +49,7 @@ import { TerminalSessionRegistry } from "./terminal-sessions.js";
49
49
  import { handleTerminalRequest } from "./terminal-routes.js";
50
50
  import { handleScmRequest } from "./scm-routes.js";
51
51
  import { handleBrowserRequest } from "./browser-routes.js";
52
+ import { DbConnectionError } from "./db-connections.js";
52
53
  function projectBrowserChatSession(record) {
53
54
  const { telemetrySessionId, ...projected } = record;
54
55
  void telemetrySessionId;
@@ -606,6 +607,7 @@ export async function handleChatCapabilities(_req, res, deps) {
606
607
  deniedTools: OPERATOR_CHAT_DENIED_TOOLS,
607
608
  deniedOperatorActions: OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS,
608
609
  tools: toolSchemas,
610
+ builtinCustomTools: OPERATOR_CHAT_SQL_TOOL_CAPABILITIES,
609
611
  builtinSubagents: [
610
612
  {
611
613
  toolId: "Agent",
@@ -981,6 +983,9 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
981
983
  const lastEvent = snapshot.at(-1);
982
984
  const activeTurn = deps.events.getActiveTurn(sessionId);
983
985
  const pendingQuestion = await deps.questionRegistry?.get(sessionId);
986
+ const sddSessionIdentity = deps.sddDataAlignment && (await deps.sddDataAlignment.isEligible())
987
+ ? { telemetrySessionId: record.telemetrySessionId ?? null }
988
+ : undefined;
984
989
  sendJson(res, 200, {
985
990
  ok: true,
986
991
  eventEpoch: deps.events.epoch(sessionId),
@@ -1006,6 +1011,9 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
1006
1011
  sessionStats: telemetry.stats,
1007
1012
  usage: telemetry.usage,
1008
1013
  turnTelemetry: deriveChatTurnTelemetry(snapshot),
1014
+ ...(sddSessionIdentity
1015
+ ? { sddDataAlignment: sddSessionIdentity }
1016
+ : {}),
1009
1017
  contextPressure: typeof deps.runtime.getContextPressureTelemetry === "function"
1010
1018
  ? deps.runtime.getContextPressureTelemetry(sessionId)
1011
1019
  : undefined,
@@ -2669,6 +2677,7 @@ export async function handleChatEventsStream(req, res, deps, sessionId) {
2669
2677
  kind: "stream_ready",
2670
2678
  sessionId,
2671
2679
  epoch: deps.events.epoch(sessionId),
2680
+ lastSeq: deps.events.latestSeq(sessionId),
2672
2681
  latestEventId: retainedTail?.eventId ?? null,
2673
2682
  });
2674
2683
  let closed = false;
@@ -2973,7 +2982,20 @@ async function handleSddBingo(req, res, deps, sessionId) {
2973
2982
  throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "请求体只允许 skillName 字段", 400);
2974
2983
  }
2975
2984
  const result = await deps.sddDataAlignment.report(sessionId, body.skillName);
2976
- sendJson(res, 200, { ok: true, skillName: result.skillName });
2985
+ deps.events.append(sessionId, deps.events.latestTurnId(sessionId) ?? `${sessionId}:operator-action`, {
2986
+ kind: "operator-action",
2987
+ data: {
2988
+ schemaVersion: 1,
2989
+ action: "sdd-bingo",
2990
+ skillName: result.skillName,
2991
+ telemetrySessionId: result.telemetrySessionId,
2992
+ },
2993
+ });
2994
+ sendJson(res, 200, {
2995
+ ok: true,
2996
+ skillName: result.skillName,
2997
+ telemetrySessionId: result.telemetrySessionId,
2998
+ });
2977
2999
  }
2978
3000
  catch (error) {
2979
3001
  if (error instanceof SddDataAlignmentError) {
@@ -3901,7 +3923,7 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
3901
3923
  : step === "confirm"
3902
3924
  ? "confirmDagConfirmation"
3903
3925
  : "runDag";
3904
- const result = await dispatchOperatorAction(ctx, {
3926
+ const result = await dispatchOperatorAction({ ...ctx, chatSessionRef: sessionId }, {
3905
3927
  action,
3906
3928
  actionParams: body,
3907
3929
  clientRequestId: typeof body.clientRequestId === "string"
@@ -4279,6 +4301,24 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
4279
4301
  throw error;
4280
4302
  }
4281
4303
  }
4304
+ function databaseConnectionInput(body, engine) {
4305
+ return {
4306
+ name: typeof body.name === "string" ? body.name : "",
4307
+ engine,
4308
+ ...(typeof body.path === "string" ? { path: body.path } : {}),
4309
+ ...(body.mode === "parameters" || body.mode === "url-env" ? { mode: body.mode } : {}),
4310
+ ...(typeof body.urlEnv === "string" ? { urlEnv: body.urlEnv } : {}),
4311
+ ...(typeof body.host === "string" ? { host: body.host } : {}),
4312
+ ...(typeof body.port === "number" ? { port: body.port } : {}),
4313
+ ...(typeof body.database === "string" ? { database: body.database } : {}),
4314
+ ...(typeof body.username === "string" ? { username: body.username } : {}),
4315
+ ...(body.passwordMode === "runtime" || body.passwordMode === "environment" || body.passwordMode === "encrypted" ? { passwordMode: body.passwordMode } : {}),
4316
+ ...(typeof body.passwordRef === "string" ? { passwordRef: body.passwordRef } : {}),
4317
+ ...(typeof body.passwordEnv === "string" ? { passwordEnv: body.passwordEnv } : {}),
4318
+ ...(typeof body.password === "string" ? { password: body.password } : {}),
4319
+ ...(typeof body.ssl === "boolean" ? { ssl: body.ssl } : {}),
4320
+ };
4321
+ }
4282
4322
  async function dispatchChatRequest(req, res, deps, pathname, workspaceScope) {
4283
4323
  // Terminal management API (model + Console UI shared PTY registry).
4284
4324
  if (await handleTerminalRequest(req, res, {
@@ -4358,6 +4398,117 @@ async function dispatchChatRequest(req, res, deps, pathname, workspaceScope) {
4358
4398
  await handleChatCapabilities(req, res, deps);
4359
4399
  return true;
4360
4400
  }
4401
+ if (method === "GET" && pathname === "/api/operator/v1/chat/database-connections/export") {
4402
+ sendJson(res, 200, { ok: true, document: deps.runtime.exportDatabaseConnections() });
4403
+ return true;
4404
+ }
4405
+ if (method === "POST" && pathname === "/api/operator/v1/chat/database-connections/import") {
4406
+ const gate = gateMutation(req, deps);
4407
+ if (!gate.ok) {
4408
+ sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
4409
+ return true;
4410
+ }
4411
+ try {
4412
+ const body = await readJsonBody(req);
4413
+ const conflict = body.conflict === "overwrite" ? "overwrite" : "skip";
4414
+ const result = await deps.runtime.importDatabaseConnections(body.document, conflict);
4415
+ sendJson(res, 200, { ok: true, result });
4416
+ }
4417
+ catch (error) {
4418
+ const code = error instanceof DbConnectionError ? error.code : "DB_PERSISTENCE_ERROR";
4419
+ const status = code === "DB_PERSISTENCE_ERROR" ? 500 : 400;
4420
+ sendJson(res, status, { ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } });
4421
+ }
4422
+ return true;
4423
+ }
4424
+ if (pathname === "/api/operator/v1/chat/database-connections" &&
4425
+ (method === "GET" || method === "POST")) {
4426
+ if (method === "GET") {
4427
+ sendJson(res, 200, { ok: true, connections: deps.runtime.listDatabaseConnections() });
4428
+ return true;
4429
+ }
4430
+ const gate = gateMutation(req, deps);
4431
+ if (!gate.ok) {
4432
+ sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
4433
+ return true;
4434
+ }
4435
+ try {
4436
+ const body = await readJsonBody(req);
4437
+ const engine = body.engine;
4438
+ if (engine !== "sqlite" && engine !== "postgres" && engine !== "mysql") {
4439
+ throw new DbConnectionError("DB_INVALID_CONFIG", "engine must be sqlite, postgres, or mysql");
4440
+ }
4441
+ const connection = await deps.runtime.addDatabaseConnection(databaseConnectionInput(body, engine));
4442
+ sendJson(res, 201, { ok: true, connection });
4443
+ }
4444
+ catch (error) {
4445
+ const code = error instanceof DbConnectionError ? error.code : "DB_PERSISTENCE_ERROR";
4446
+ const status = code === "DB_PERSISTENCE_ERROR" ? 500 : 400;
4447
+ sendJson(res, status, { ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } });
4448
+ }
4449
+ return true;
4450
+ }
4451
+ const databaseBrowseRoute = pathname.match(/^\/api\/operator\/v1\/chat\/database-connections\/([^/]+)\/browse$/);
4452
+ if (databaseBrowseRoute && method === "GET") {
4453
+ const name = decodeURIComponent(databaseBrowseRoute[1]);
4454
+ try {
4455
+ const table = url.searchParams.get("table")?.trim();
4456
+ const database = url.searchParams.get("database")?.trim() || undefined;
4457
+ if (!table) {
4458
+ const databases = await deps.runtime.listDatabaseDatabases(name);
4459
+ const connection = deps.runtime.listDatabaseConnections().find((item) => item.name === name);
4460
+ const shouldLoadTables = connection?.engine === "sqlite" || Boolean(database || connection?.database);
4461
+ sendJson(res, 200, { ok: true, connection: name, database, databases, tables: shouldLoadTables ? await deps.runtime.listDatabaseTables(name, database) : [] });
4462
+ }
4463
+ else {
4464
+ const page = Number(url.searchParams.get("page") ?? "1");
4465
+ const pageSize = Number(url.searchParams.get("pageSize") ?? "50");
4466
+ sendJson(res, 200, { ok: true, result: await deps.runtime.browseDatabaseTable(name, table, page, pageSize, database) });
4467
+ }
4468
+ }
4469
+ catch (error) {
4470
+ const code = error instanceof DbConnectionError ? error.code : "DB_ENGINE_ERROR";
4471
+ const status = code === "DB_CONNECTION_NOT_FOUND" ? 404 : 400;
4472
+ sendJson(res, status, { ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } });
4473
+ }
4474
+ return true;
4475
+ }
4476
+ const databaseConnectionRoute = pathname.match(/^\/api\/operator\/v1\/chat\/database-connections\/([^/]+)(?:\/(test))?$/);
4477
+ if (databaseConnectionRoute && (method === "DELETE" || method === "POST" || method === "PATCH")) {
4478
+ const gate = gateMutation(req, deps);
4479
+ if (!gate.ok) {
4480
+ sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
4481
+ return true;
4482
+ }
4483
+ const name = decodeURIComponent(databaseConnectionRoute[1]);
4484
+ try {
4485
+ if (method === "POST" && databaseConnectionRoute[2] === "test") {
4486
+ sendJson(res, 200, { ok: true, result: await deps.runtime.testDatabaseConnection(name) });
4487
+ }
4488
+ else if (method === "PATCH" && !databaseConnectionRoute[2]) {
4489
+ const body = await readJsonBody(req);
4490
+ const engine = body.engine;
4491
+ if (engine !== "sqlite" && engine !== "postgres" && engine !== "mysql") {
4492
+ throw new DbConnectionError("DB_INVALID_CONFIG", "engine must be sqlite, postgres, or mysql");
4493
+ }
4494
+ const connection = await deps.runtime.updateDatabaseConnection(name, databaseConnectionInput(body, engine));
4495
+ sendJson(res, 200, { ok: true, connection });
4496
+ }
4497
+ else if (method === "DELETE" && !databaseConnectionRoute[2]) {
4498
+ await deps.runtime.removeDatabaseConnection(name);
4499
+ sendJson(res, 200, { ok: true });
4500
+ }
4501
+ else {
4502
+ sendJson(res, 405, { ok: false, error: { code: "METHOD_NOT_ALLOWED", message: "method not allowed" } });
4503
+ }
4504
+ }
4505
+ catch (error) {
4506
+ const code = error instanceof DbConnectionError ? error.code : "DB_ENGINE_ERROR";
4507
+ const status = code === "DB_CONNECTION_NOT_FOUND" ? 404 : code === "DB_PERSISTENCE_ERROR" ? 500 : 400;
4508
+ sendJson(res, status, { ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } });
4509
+ }
4510
+ return true;
4511
+ }
4361
4512
  const htmlPreviewRoute = matchRepoHtmlPreviewRoute(pathname);
4362
4513
  if (method === "GET" && htmlPreviewRoute) {
4363
4514
  if (!htmlPreviewRoute.rawPath)
@@ -2,6 +2,8 @@ import { createDecipheriv, randomBytes } from "node:crypto";
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ import { injectWizardLocalTelemetryHeaders, } from "../../../shared/wizard-local-telemetry.js";
6
+ export { WIZARD_LOCAL_DEFAULT_USER_AGENT, } from "../../../shared/wizard-local-telemetry.js";
5
7
  export const SDD_SKILL_NAMES = [
6
8
  "SDD-design-analysis",
7
9
  "SDD-implementation-test-review",
@@ -14,7 +16,6 @@ export const SDD_TELEMETRY_URL = "https://wizard-ai.paic.com.cn/code_pilot/api/s
14
16
  export const DEFAULT_AI_MA_PUB_SALT_KEY = "52aad0c09e6b46818a11ee702d6fe0ba";
15
17
  export const SDD_TELEMETRY_SIGN = DEFAULT_AI_MA_PUB_SALT_KEY;
16
18
  export const AIMAX_AUTH_TOKEN_ENV = "AIMAX_AUTH_TOKEN";
17
- export const WIZARD_LOCAL_DEFAULT_USER_AGENT = "Builder/OpenCode ai-sdk/provider-utils/4.0.21 runtime/bun/1.3.11";
18
19
  const SM4_V1_PREFIX = "sm4:v1:";
19
20
  /** Decode a plaintext ticket or `sm4:v1:` ciphertext. Unknown prefixes and crypto errors fail closed. */
20
21
  export function decodeAimaxAuthToken(raw, keyHex = DEFAULT_AI_MA_PUB_SALT_KEY) {
@@ -215,7 +216,7 @@ export class SddDataAlignmentService {
215
216
  if (!response.ok || body?.ret !== 0 || body.succ !== true) {
216
217
  throw new SddDataAlignmentError("SDD_UPSTREAM_FAILED", "SDD 数据对齐上报失败", 502);
217
218
  }
218
- return { skillName };
219
+ return { skillName, telemetrySessionId };
219
220
  }
220
221
  }
221
222
  async function readBoundedTelemetryJson(response) {
@@ -251,39 +252,15 @@ async function readBoundedTelemetryJson(response) {
251
252
  reader.releaseLock();
252
253
  }
253
254
  }
254
- function deleteHeaderCaseInsensitive(headers, name) {
255
- for (const key of Object.keys(headers))
256
- if (key.toLowerCase() === name.toLowerCase())
257
- delete headers[key];
258
- }
259
- function configuredHeader(headers, name) {
260
- const key = Object.keys(headers ?? {}).find((entry) => entry.toLowerCase() === name.toLowerCase());
261
- return key ? headers?.[key] : undefined;
262
- }
263
- function setStaticHeader(headers, configured, name, defaultValue) {
264
- deleteHeaderCaseInsensitive(headers, name);
265
- headers[name] = configuredHeader(configured, name) ?? defaultValue;
266
- }
267
255
  export async function injectWizardLocalHeaders(options) {
268
256
  const model = options.context.model;
269
257
  if (model?.provider !== "wizard-local" || !model.id)
270
258
  return;
271
259
  if (!(await options.service.isEligible()))
272
260
  return;
273
- const modelRegistry = options.context.modelRegistry;
274
- if (!modelRegistry?.getApiKeyAndHeaders)
275
- return;
276
- // Keep the method call attached to ModelRegistry. Its implementation reads
277
- // the private runtime through `this`; extracting the function would make the
278
- // SDK path fail closed and silently drop all custom headers.
279
- const auth = await modelRegistry.getApiKeyAndHeaders(model);
280
- if (!auth.ok || typeof auth.apiKey !== "string" || !auth.apiKey)
281
- return;
282
- setStaticHeader(options.headers, auth.headers, "client-code", "OpenCode");
283
- setStaticHeader(options.headers, auth.headers, "User-Agent", WIZARD_LOCAL_DEFAULT_USER_AGENT);
284
- for (const name of ["x-api-key", "X-Session-Id", "X-Model-Id"])
285
- deleteHeaderCaseInsensitive(options.headers, name);
286
- options.headers["x-api-key"] = auth.apiKey;
287
- options.headers["X-Session-Id"] = await options.service.ensureTelemetrySessionId(options.sessionId);
288
- options.headers["X-Model-Id"] = model.id;
261
+ await injectWizardLocalTelemetryHeaders({
262
+ headers: options.headers,
263
+ context: options.context,
264
+ telemetrySessionId: await options.service.ensureTelemetrySessionId(options.sessionId),
265
+ });
289
266
  }
@@ -0,0 +1,22 @@
1
+ /** Repair only a local submission pair after the ordinary stable sort.
2
+ * A pair-specific comparator would be non-transitive when a third row falls
3
+ * between the user and placeholder, so this bounded pass moves the user back
4
+ * to the placeholder's established timeline seat instead. */
5
+ export function ensureLinkedSubmissionOrder(messages) {
6
+ const next = [...messages];
7
+ for (let assistantIndex = 0; assistantIndex < next.length; assistantIndex += 1) {
8
+ const assistant = next[assistantIndex];
9
+ if (assistant?.role !== "assistant" || !assistant.clientRequestId)
10
+ continue;
11
+ const userIndex = next.findIndex((message, index) => index > assistantIndex &&
12
+ message.role === "user" &&
13
+ message.clientRequestId === assistant.clientRequestId);
14
+ if (userIndex < 0)
15
+ continue;
16
+ const [user] = next.splice(userIndex, 1);
17
+ if (user)
18
+ next.splice(assistantIndex, 0, user);
19
+ assistantIndex += 1;
20
+ }
21
+ return next;
22
+ }
@@ -20,6 +20,39 @@ import { OFFICIAL_DENIED_TOOL_IDS } from "../resource-loader.js";
20
20
  import { EXECUTABLE_GATE_ONLY_ACTIONS } from "./tool-adapter.js";
21
21
  import { OPERATOR_CHAT_AGENT_TOOL_NAME } from "./subagents/types.js";
22
22
  const EXECUTABLE_GATE_ONLY = new Set(EXECUTABLE_GATE_ONLY_ACTIONS);
23
+ /** Server-owned metadata for SQL custom tools advertised to the Console UI. */
24
+ export const OPERATOR_CHAT_SQL_TOOL_CAPABILITIES = Object.freeze([
25
+ {
26
+ toolId: "sql_query",
27
+ description: "对命名数据库连接执行受限只读 SQL 查询。",
28
+ kind: "read",
29
+ },
30
+ {
31
+ toolId: "sql_exec",
32
+ description: "已禁用:数据库连接强制只读,拒绝 DELETE、DROP 及所有写入或 DDL。",
33
+ kind: "read",
34
+ },
35
+ {
36
+ toolId: "sql_schema",
37
+ description: "查看数据库表及字段结构。",
38
+ kind: "read",
39
+ },
40
+ {
41
+ toolId: "sql_stats",
42
+ description: "查看数据库表行数和存储规模统计。",
43
+ kind: "read",
44
+ },
45
+ {
46
+ toolId: "sql_health",
47
+ description: "检查数据库连接健康状态与安全配置。",
48
+ kind: "read",
49
+ },
50
+ {
51
+ toolId: "sql_connections",
52
+ description: "列出、新增或移除受治理的命名数据库连接。",
53
+ kind: "mutation",
54
+ },
55
+ ]);
23
56
  /** Built-in Chat custom tools that are not operator registry actions. */
24
57
  export const OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS = Object.freeze([
25
58
  OPERATOR_CHAT_AGENT_TOOL_NAME,
@@ -35,6 +68,7 @@ export const OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS = Object.freeze([
35
68
  "browser_dialog",
36
69
  "browser_wait",
37
70
  "browser_screenshot",
71
+ ...OPERATOR_CHAT_SQL_TOOL_CAPABILITIES.map((tool) => tool.toolId),
38
72
  ]);
39
73
  /**
40
74
  * Actions the model MAY invoke. Derived from the registry but filtered by the