@mcp-abap-adt/llm-agent-server-libs 20.0.0 → 20.4.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 (166) hide show
  1. package/dist/builders/controller-skill-pipeline-builder.d.ts +57 -0
  2. package/dist/builders/controller-skill-pipeline-builder.d.ts.map +1 -0
  3. package/dist/builders/controller-skill-pipeline-builder.js +175 -0
  4. package/dist/builders/controller-skill-pipeline-builder.js.map +1 -0
  5. package/dist/generated/version.d.ts +1 -1
  6. package/dist/generated/version.js +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/pipelines/controller.d.ts.map +1 -1
  12. package/dist/pipelines/controller.js +19 -3
  13. package/dist/pipelines/controller.js.map +1 -1
  14. package/dist/pipelines/coordinator-resolvers.d.ts +68 -0
  15. package/dist/pipelines/coordinator-resolvers.d.ts.map +1 -0
  16. package/dist/pipelines/coordinator-resolvers.js +97 -0
  17. package/dist/pipelines/coordinator-resolvers.js.map +1 -0
  18. package/dist/pipelines/parsers.d.ts +1 -1
  19. package/dist/pipelines/parsers.d.ts.map +1 -1
  20. package/dist/pipelines/parsers.js +5 -4
  21. package/dist/pipelines/parsers.js.map +1 -1
  22. package/dist/pipelines/register-skill-sources.d.ts.map +1 -1
  23. package/dist/pipelines/register-skill-sources.js +5 -0
  24. package/dist/pipelines/register-skill-sources.js.map +1 -1
  25. package/dist/smart-agent/build-stepper-root.d.ts +3 -2
  26. package/dist/smart-agent/build-stepper-root.d.ts.map +1 -1
  27. package/dist/smart-agent/build-stepper-root.js +2 -1
  28. package/dist/smart-agent/build-stepper-root.js.map +1 -1
  29. package/dist/smart-agent/config-reload-watcher.d.ts +30 -0
  30. package/dist/smart-agent/config-reload-watcher.d.ts.map +1 -0
  31. package/dist/smart-agent/config-reload-watcher.js +101 -0
  32. package/dist/smart-agent/config-reload-watcher.js.map +1 -0
  33. package/dist/smart-agent/config-validator.d.ts +21 -0
  34. package/dist/smart-agent/config-validator.d.ts.map +1 -0
  35. package/dist/smart-agent/config-validator.js +196 -0
  36. package/dist/smart-agent/config-validator.js.map +1 -0
  37. package/dist/smart-agent/config.d.ts +16 -254
  38. package/dist/smart-agent/config.d.ts.map +1 -1
  39. package/dist/smart-agent/config.js +17 -904
  40. package/dist/smart-agent/config.js.map +1 -1
  41. package/dist/smart-agent/controller/board.d.ts +6 -3
  42. package/dist/smart-agent/controller/board.d.ts.map +1 -1
  43. package/dist/smart-agent/controller/board.js +21 -0
  44. package/dist/smart-agent/controller/board.js.map +1 -1
  45. package/dist/smart-agent/controller/controller-coordinator-handler.d.ts +6 -48
  46. package/dist/smart-agent/controller/controller-coordinator-handler.d.ts.map +1 -1
  47. package/dist/smart-agent/controller/controller-coordinator-handler.js +68 -357
  48. package/dist/smart-agent/controller/controller-coordinator-handler.js.map +1 -1
  49. package/dist/smart-agent/controller/finalizer.d.ts +5 -0
  50. package/dist/smart-agent/controller/finalizer.d.ts.map +1 -1
  51. package/dist/smart-agent/controller/finalizer.js +12 -5
  52. package/dist/smart-agent/controller/finalizer.js.map +1 -1
  53. package/dist/smart-agent/controller/parser.d.ts +11 -0
  54. package/dist/smart-agent/controller/parser.d.ts.map +1 -0
  55. package/dist/smart-agent/controller/parser.js +77 -0
  56. package/dist/smart-agent/controller/parser.js.map +1 -0
  57. package/dist/smart-agent/controller/planner.js +1 -1
  58. package/dist/smart-agent/controller/planner.js.map +1 -1
  59. package/dist/smart-agent/controller/recall.d.ts +48 -0
  60. package/dist/smart-agent/controller/recall.d.ts.map +1 -0
  61. package/dist/smart-agent/controller/recall.js +196 -0
  62. package/dist/smart-agent/controller/recall.js.map +1 -0
  63. package/dist/smart-agent/controller/reviewer.d.ts.map +1 -1
  64. package/dist/smart-agent/controller/reviewer.js +1 -1
  65. package/dist/smart-agent/controller/reviewer.js.map +1 -1
  66. package/dist/smart-agent/controller/usage-logging.d.ts +16 -0
  67. package/dist/smart-agent/controller/usage-logging.d.ts.map +1 -0
  68. package/dist/smart-agent/controller/usage-logging.js +39 -0
  69. package/dist/smart-agent/controller/usage-logging.js.map +1 -0
  70. package/dist/smart-agent/http/adapter-route-handler.d.ts +13 -0
  71. package/dist/smart-agent/http/adapter-route-handler.d.ts.map +1 -0
  72. package/dist/smart-agent/http/adapter-route-handler.js +76 -0
  73. package/dist/smart-agent/http/adapter-route-handler.js.map +1 -0
  74. package/dist/smart-agent/http/chat-route-handler.d.ts +15 -0
  75. package/dist/smart-agent/http/chat-route-handler.d.ts.map +1 -0
  76. package/dist/smart-agent/http/chat-route-handler.js +327 -0
  77. package/dist/smart-agent/http/chat-route-handler.js.map +1 -0
  78. package/dist/smart-agent/http/config-route-handler.d.ts +21 -0
  79. package/dist/smart-agent/http/config-route-handler.d.ts.map +1 -0
  80. package/dist/smart-agent/http/config-route-handler.js +149 -0
  81. package/dist/smart-agent/http/config-route-handler.js.map +1 -0
  82. package/dist/smart-agent/http/health-route-handler.d.ts +12 -0
  83. package/dist/smart-agent/http/health-route-handler.d.ts.map +1 -0
  84. package/dist/smart-agent/http/health-route-handler.js +19 -0
  85. package/dist/smart-agent/http/health-route-handler.js.map +1 -0
  86. package/dist/smart-agent/http/models-route-handler.d.ts +17 -0
  87. package/dist/smart-agent/http/models-route-handler.d.ts.map +1 -0
  88. package/dist/smart-agent/http/models-route-handler.js +65 -0
  89. package/dist/smart-agent/http/models-route-handler.js.map +1 -0
  90. package/dist/smart-agent/http/response-helpers.d.ts +31 -0
  91. package/dist/smart-agent/http/response-helpers.d.ts.map +1 -0
  92. package/dist/smart-agent/http/response-helpers.js +59 -0
  93. package/dist/smart-agent/http/response-helpers.js.map +1 -0
  94. package/dist/smart-agent/http/route-table.d.ts +44 -0
  95. package/dist/smart-agent/http/route-table.d.ts.map +1 -0
  96. package/dist/smart-agent/http/route-table.js +35 -0
  97. package/dist/smart-agent/http/route-table.js.map +1 -0
  98. package/dist/smart-agent/http/session-cookie.d.ts +10 -0
  99. package/dist/smart-agent/http/session-cookie.d.ts.map +1 -0
  100. package/dist/smart-agent/http/session-cookie.js +16 -0
  101. package/dist/smart-agent/http/session-cookie.js.map +1 -0
  102. package/dist/smart-agent/http/sessions-route-handler.d.ts +8 -0
  103. package/dist/smart-agent/http/sessions-route-handler.d.ts.map +1 -0
  104. package/dist/smart-agent/http/sessions-route-handler.js +54 -0
  105. package/dist/smart-agent/http/sessions-route-handler.js.map +1 -0
  106. package/dist/smart-agent/http/usage-route-handler.d.ts +12 -0
  107. package/dist/smart-agent/http/usage-route-handler.d.ts.map +1 -0
  108. package/dist/smart-agent/http/usage-route-handler.js +28 -0
  109. package/dist/smart-agent/http/usage-route-handler.js.map +1 -0
  110. package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts +13 -0
  111. package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts.map +1 -0
  112. package/dist/smart-agent/knowledge/make-knowledge-backend.js +18 -0
  113. package/dist/smart-agent/knowledge/make-knowledge-backend.js.map +1 -0
  114. package/dist/smart-agent/llm/role-llm-resolver.d.ts +30 -0
  115. package/dist/smart-agent/llm/role-llm-resolver.d.ts.map +1 -0
  116. package/dist/smart-agent/llm/role-llm-resolver.js +44 -0
  117. package/dist/smart-agent/llm/role-llm-resolver.js.map +1 -0
  118. package/dist/smart-agent/llm-config-map.d.ts +38 -0
  119. package/dist/smart-agent/llm-config-map.d.ts.map +1 -0
  120. package/dist/smart-agent/llm-config-map.js +75 -0
  121. package/dist/smart-agent/llm-config-map.js.map +1 -0
  122. package/dist/smart-agent/mcp-readiness-monitor.d.ts +38 -0
  123. package/dist/smart-agent/mcp-readiness-monitor.d.ts.map +1 -0
  124. package/dist/smart-agent/mcp-readiness-monitor.js +81 -0
  125. package/dist/smart-agent/mcp-readiness-monitor.js.map +1 -0
  126. package/dist/smart-agent/mcp-readiness-registry.d.ts +37 -0
  127. package/dist/smart-agent/mcp-readiness-registry.d.ts.map +1 -0
  128. package/dist/smart-agent/mcp-readiness-registry.js +55 -0
  129. package/dist/smart-agent/mcp-readiness-registry.js.map +1 -0
  130. package/dist/smart-agent/resolve-config-sections.d.ts +15 -0
  131. package/dist/smart-agent/resolve-config-sections.d.ts.map +1 -0
  132. package/dist/smart-agent/resolve-config-sections.js +212 -0
  133. package/dist/smart-agent/resolve-config-sections.js.map +1 -0
  134. package/dist/smart-agent/session-lifecycle/index.d.ts +117 -0
  135. package/dist/smart-agent/session-lifecycle/index.d.ts.map +1 -0
  136. package/dist/smart-agent/session-lifecycle/index.js +152 -0
  137. package/dist/smart-agent/session-lifecycle/index.js.map +1 -0
  138. package/dist/smart-agent/skill-plugins-config.d.ts +9 -1
  139. package/dist/smart-agent/skill-plugins-config.d.ts.map +1 -1
  140. package/dist/smart-agent/skill-plugins-config.js +27 -1
  141. package/dist/smart-agent/skill-plugins-config.js.map +1 -1
  142. package/dist/smart-agent/skill-plugins-host-factory.d.ts +14 -2
  143. package/dist/smart-agent/skill-plugins-host-factory.d.ts.map +1 -1
  144. package/dist/smart-agent/skill-plugins-host-factory.js +16 -4
  145. package/dist/smart-agent/skill-plugins-host-factory.js.map +1 -1
  146. package/dist/smart-agent/smart-server.d.ts +151 -222
  147. package/dist/smart-agent/smart-server.d.ts.map +1 -1
  148. package/dist/smart-agent/smart-server.js +527 -1384
  149. package/dist/smart-agent/smart-server.js.map +1 -1
  150. package/dist/smart-agent/stepper-config.d.ts +138 -0
  151. package/dist/smart-agent/stepper-config.d.ts.map +1 -0
  152. package/dist/smart-agent/stepper-config.js +216 -0
  153. package/dist/smart-agent/stepper-config.js.map +1 -0
  154. package/dist/smart-agent/tools-rag-handle.d.ts +10 -0
  155. package/dist/smart-agent/tools-rag-handle.d.ts.map +1 -0
  156. package/dist/smart-agent/tools-rag-handle.js +76 -0
  157. package/dist/smart-agent/tools-rag-handle.js.map +1 -0
  158. package/dist/smart-agent/workers/worker-registry.d.ts +159 -0
  159. package/dist/smart-agent/workers/worker-registry.d.ts.map +1 -0
  160. package/dist/smart-agent/workers/worker-registry.js +203 -0
  161. package/dist/smart-agent/workers/worker-registry.js.map +1 -0
  162. package/dist/smart-agent/yaml-loader.d.ts +7 -0
  163. package/dist/smart-agent/yaml-loader.d.ts.map +1 -0
  164. package/dist/smart-agent/yaml-loader.js +146 -0
  165. package/dist/smart-agent/yaml-loader.js.map +1 -0
  166. package/package.json +7 -7
@@ -1,55 +1,33 @@
1
- import { externalToolCallId, } from '@mcp-abap-adt/llm-agent';
1
+ import { externalToolCallId, McpError, } from '@mcp-abap-adt/llm-agent';
2
2
  import { summaryToUsage, } from '@mcp-abap-adt/llm-agent-libs';
3
- import { cosine } from '../embedder-knowledge-index.js';
4
- import { readClaims, readPlanDecisions, writePlanDecision, } from './artifacts.js';
5
- import { BoardOverBudgetError, reconstructBoard, renderBoard, } from './board.js';
3
+ import { writePlanDecision } from './artifacts.js';
4
+ import { BoardOverBudgetError, renderLiveBoard, } from './board.js';
6
5
  import { writeArtifact } from './memorizer.js';
7
6
  import { resolveByPrecedence } from './outcome.js';
8
7
  import { makeControllerPlanner } from './planner.js';
9
8
  import { appendHint } from './prompts.js';
9
+ import { buildRecallBlock, collectApproved, RECALL_ARTIFACT_TYPES, RECALL_EVIDENCE_CHARS, RECALL_K_MCP, RECALL_K_STEP, RECALL_MAX_CHARS_MCP, RECALL_MAX_CHARS_STEP, relevantExtract, runScopedRecall, } from './recall.js';
10
10
  import { classifyRequest, readTerminal, writeTerminal } from './run-scope.js';
11
11
  import { hydrateBundle, persistBundle, resetRun } from './session-bundle.js';
12
12
  import { establishTargetState } from './target-state.js';
13
- import { validateRequires, } from './types.js';
13
+ import { makeLogUsage } from './usage-logging.js';
14
14
  // ---------------------------------------------------------------------------
15
15
  // Debug logging — gated behind DEBUG_CONTROLLER (e.g. DEBUG_CONTROLLER=1).
16
16
  // Surfaces the steps the planner delegates and per-role/total token usage to
17
17
  // stderr, for tuning step granularity and watching token spend. Off by default.
18
+ // (Also in usage-logging.ts — intentional small duplication; no 3rd copy exists.)
18
19
  // ---------------------------------------------------------------------------
19
20
  function dlog(msg) {
20
21
  if (process.env.DEBUG_CONTROLLER)
21
22
  console.error(`[controller] ${msg}`);
22
23
  }
23
- /**
24
- * Build a request-time `logUsage(role, usage)` that writes each subagent call
25
- * into the per-request `IRequestLogger` (the single aggregator), attributing the
26
- * role's configured model. The role is explicit at the call site, so the shared
27
- * planner/finalizer client is attributed correctly. `durationMs: 0` — the seam
28
- * carries no timing (matches the rag-query precedent).
29
- */
30
- export function makeLogUsage(requestLogger, requestId, models) {
31
- return (role, u) => {
32
- if (!u)
33
- return;
34
- const model = role === 'finalizer'
35
- ? (models.finalizer ?? models.planner)
36
- : role === 'reviewer'
37
- ? (models.reviewer ?? models.planner)
38
- : role === 'embedding'
39
- ? 'embedder'
40
- : (models[role] ?? 'unknown');
41
- requestLogger.logLlmCall({
42
- component: role,
43
- model,
44
- promptTokens: u.promptTokens ?? 0,
45
- completionTokens: u.completionTokens ?? 0,
46
- totalTokens: u.totalTokens ?? 0,
47
- durationMs: 0,
48
- requestId,
49
- });
50
- dlog(`tokens ${role}: prompt=${u.promptTokens} completion=${u.completionTokens} total=${u.totalTokens}`);
51
- };
52
- }
24
+ // ---------------------------------------------------------------------------
25
+ // Re-exported for import-path stability (helpers moved to sibling modules).
26
+ // ---------------------------------------------------------------------------
27
+ export { renderLiveBoard } from './board.js';
28
+ export { parseNextStep } from './parser.js';
29
+ export { relevantExtract, runScopedRecall } from './recall.js';
30
+ export { makeLogUsage } from './usage-logging.js';
53
31
  // ---------------------------------------------------------------------------
54
32
  // Handler
55
33
  // ---------------------------------------------------------------------------
@@ -824,7 +802,32 @@ export class ControllerCoordinatorHandler {
824
802
  bundle.plannerPrivate += `\n[step ${step.name} failed] empty tool call`;
825
803
  return settle('failed');
826
804
  }
827
- const call = toLlmToolCall(firstCall);
805
+ // Normalize the StreamToolCall (full or delta) into an LlmToolCall inline.
806
+ const call = 'arguments' in firstCall &&
807
+ typeof firstCall.arguments === 'object' &&
808
+ firstCall.arguments !== null
809
+ ? {
810
+ id: ('id' in firstCall && firstCall.id) || 'call',
811
+ name: ('name' in firstCall && firstCall.name) || '',
812
+ arguments: firstCall.arguments,
813
+ }
814
+ : (() => {
815
+ let iArgs = {};
816
+ const raw = 'arguments' in firstCall ? firstCall.arguments : undefined;
817
+ if (typeof raw === 'string' && raw.length > 0) {
818
+ try {
819
+ iArgs = JSON.parse(raw);
820
+ }
821
+ catch {
822
+ iArgs = {};
823
+ }
824
+ }
825
+ return {
826
+ id: ('id' in firstCall && firstCall.id) || 'call',
827
+ name: ('name' in firstCall && firstCall.name) || '',
828
+ arguments: iArgs,
829
+ };
830
+ })();
828
831
  const name = call.name;
829
832
  const args = call.arguments;
830
833
  if (isExternalTool(name)) {
@@ -901,7 +904,27 @@ export class ControllerCoordinatorHandler {
901
904
  return settle('failed');
902
905
  }
903
906
  // Execute locally, memorize, re-send to the executor.
904
- const result = await deps.callMcp(name, args);
907
+ // FAIL LOUD: surface an MCP-unavailable failure as a terminal abort (not a
908
+ // silent empty response). The bridge (buildMcpBridge) throws an McpError
909
+ // IFF the injected classifier deemed it 'unavailable'; a tool-level error
910
+ // is returned as TEXT, never thrown. So ANY McpError reaching this catch is
911
+ // already a classifier-unavailable verdict — trust that throw-contract
912
+ // rather than re-checking the code (which would drop a CUSTOM classifier's
913
+ // decision → rethrow → outer catch swallow → (no response)). A non-McpError
914
+ // is a genuine unexpected error and is re-thrown for the outer handler.
915
+ let result;
916
+ try {
917
+ result = await deps.callMcp(name, args);
918
+ }
919
+ catch (mcpErr) {
920
+ if (mcpErr instanceof McpError) {
921
+ const now = deps.now ?? (() => new Date().toISOString());
922
+ const terminalTtlMs = deps.terminalTtlMs ?? 24 * 60 * 60 * 1000;
923
+ await this.abortTerminal(ctx, sessionId, bundle, `MCP server unavailable: ${mcpErr.message}`, now, terminalTtlMs, usageNow?.());
924
+ return 'aborted';
925
+ }
926
+ throw mcpErr;
927
+ }
905
928
  bundle.writeOrdinal = (bundle.writeOrdinal ?? 0) + 1;
906
929
  await writeArtifact(rag, {
907
930
  ...meta,
@@ -1004,12 +1027,18 @@ export class ControllerCoordinatorHandler {
1004
1027
  await persistBundle(deps.backend, sessionId, bundle);
1005
1028
  let answer;
1006
1029
  if (deps.finalizer && bundle.runId) {
1030
+ // Recall the skills block ONCE (not per finalize retry — re-embedding on
1031
+ // every attempt is wasteful; the recall is invariant across retries).
1032
+ const skillsBlock = deps.skillsRecall
1033
+ ? await deps.skillsRecall(bundle.goal, ctx.options)
1034
+ : undefined;
1007
1035
  while (answer === undefined) {
1008
1036
  try {
1009
1037
  const composed = await deps.finalizer.finalize(bundle.goal, request, approved, {
1010
1038
  hint: deps.config.subagents.finalizer?.hint,
1011
1039
  logUsage,
1012
1040
  log: (m) => dlog(m),
1041
+ skillsBlock,
1013
1042
  });
1014
1043
  // Empty-but-ok finalizer output is a JUDGE failure (spec), not a valid
1015
1044
  // answer → throw so it retries within maxFinalizeRetries.
@@ -1080,7 +1109,7 @@ export class ControllerCoordinatorHandler {
1080
1109
  }
1081
1110
  }
1082
1111
  // ---------------------------------------------------------------------------
1083
- // Episodic recall tuning
1112
+ // Goal-clarification helpers
1084
1113
  // ---------------------------------------------------------------------------
1085
1114
  /** Bare confirmations that, on a goal clarify, commit the evaluator's proposed
1086
1115
  * target rather than the literal answer. Anything else = a refinement. */
@@ -1111,7 +1140,6 @@ function isAffirmation(answer) {
1111
1140
  .replace(/[.!]+$/, '');
1112
1141
  return AFFIRMATIONS.has(t);
1113
1142
  }
1114
- /** Top-K tools surfaced from toolsRag per planner/step query. */
1115
1143
  /** Agnostic executor system prompt. Domain specifics (e.g. SAP/ABAP fact kinds)
1116
1144
  * are layered on via `subagents.executor.hint` (see {@link appendHint}). */
1117
1145
  const EXECUTOR_SYSTEM = 'You are the executor. You have tools that read the live target system. ' +
@@ -1124,44 +1152,11 @@ const EXECUTOR_SYSTEM = 'You are the executor. You have tools that read the live
1124
1152
  'if the step asks for a LIST or overview, return the list; do NOT then go and ' +
1125
1153
  'fetch the full details of every listed item unless the step explicitly asks ' +
1126
1154
  'for per-item details.';
1155
+ /** Top-K tools surfaced from toolsRag per planner/step query. */
1127
1156
  const TOOL_SELECT_K = 20;
1128
- /** Top-k recalled artifacts injected into the executor context per step. */
1129
- /** Artifact types eligible for recall (excludes the 'controller-bundle' record
1130
- * that shares the same backend). */
1131
- const RECALL_ARTIFACT_TYPES = ['step-result', 'mcp-result'];
1132
- /** Per-kind recall counts (distinct artifacts kept after dedup + cap). */
1133
- const RECALL_K_STEP = 4;
1134
- const RECALL_K_MCP = 4;
1135
- /** SEPARATE char budgets per kind, so a huge step-result cannot starve MCP context. */
1136
- const RECALL_MAX_CHARS_STEP = 2000;
1137
- const RECALL_MAX_CHARS_MCP = 2000;
1138
- /** Char budget for a single per-`requires` evidence extract handed to the reviewer. */
1139
- const RECALL_EVIDENCE_CHARS = 800;
1140
1157
  // ---------------------------------------------------------------------------
1141
1158
  // Pure helpers
1142
1159
  // ---------------------------------------------------------------------------
1143
- /** Build a bounded "Relevant prior context" block from recalled artifacts under
1144
- * the given char budget, or undefined when there is nothing to inject. */
1145
- function buildRecallBlock(hits, maxChars) {
1146
- if (hits.length === 0)
1147
- return undefined;
1148
- const parts = [];
1149
- let used = 0;
1150
- for (const h of hits) {
1151
- const c = h.content ?? '';
1152
- if (c.length === 0)
1153
- continue;
1154
- if (used + c.length > maxChars) {
1155
- parts.push(c.slice(0, maxChars - used));
1156
- break;
1157
- }
1158
- parts.push(c);
1159
- used += c.length;
1160
- }
1161
- if (parts.length === 0)
1162
- return undefined;
1163
- return `Relevant prior context:\n${parts.join('\n')}`;
1164
- }
1165
1160
  /** Extract the user prompt from the request's textOrMessages. */
1166
1161
  function extractPrompt(textOrMessages) {
1167
1162
  if (typeof textOrMessages === 'string')
@@ -1172,131 +1167,6 @@ function extractPrompt(textOrMessages) {
1172
1167
  }
1173
1168
  return '';
1174
1169
  }
1175
- /** Parse a planner content string into a NextStep, defensively. */
1176
- /** Parse the planner's reply into a NextStep, tolerating ```json fences and
1177
- * surrounding prose. Returns null when no valid decision can be extracted — the
1178
- * caller treats that as a FORMAT error (re-ask the planner), NOT a rewind, so a
1179
- * badly-formatted reply never silently burns the rewind budget. */
1180
- export function parseNextStep(content) {
1181
- const json = extractJsonObject(content);
1182
- if (json === null)
1183
- return null;
1184
- try {
1185
- const obj = JSON.parse(json);
1186
- if (obj.kind === 'done' && typeof obj.result === 'string')
1187
- return { kind: 'done', result: obj.result };
1188
- if (obj.kind === 'rewind' && typeof obj.reason === 'string')
1189
- return { kind: 'rewind', reason: obj.reason };
1190
- if (obj.kind === 'next' &&
1191
- obj.step &&
1192
- typeof obj.step.name === 'string' &&
1193
- typeof obj.step.instructions === 'string') {
1194
- // Validate requires[] so a non-string / empty / oversized reference never
1195
- // reaches the semantic query / embedder; a malformed value is a parse
1196
- // failure that drives the existing parse-retry.
1197
- const req = validateRequires(obj.step.requires);
1198
- if (req === false)
1199
- return null;
1200
- return {
1201
- kind: 'next',
1202
- step: {
1203
- name: obj.step.name,
1204
- instructions: obj.step.instructions,
1205
- ...(obj.step.type ? { type: obj.step.type } : {}),
1206
- ...(req ? { requires: req } : {}),
1207
- },
1208
- };
1209
- }
1210
- }
1211
- catch {
1212
- // fall through
1213
- }
1214
- return null;
1215
- }
1216
- /** Extract the first balanced JSON object from a planner reply, ignoring ```json
1217
- * fences and prose around it. String-aware (braces inside strings don't count).
1218
- * Returns null if no balanced object is present. */
1219
- export function extractJsonObject(raw) {
1220
- const fence = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
1221
- const body = fence ? fence[1] : raw;
1222
- const start = body.indexOf('{');
1223
- if (start < 0)
1224
- return null;
1225
- let depth = 0;
1226
- let inStr = false;
1227
- let esc = false;
1228
- for (let i = start; i < body.length; i++) {
1229
- const ch = body[i];
1230
- if (inStr) {
1231
- if (esc)
1232
- esc = false;
1233
- else if (ch === '\\')
1234
- esc = true;
1235
- else if (ch === '"')
1236
- inStr = false;
1237
- continue;
1238
- }
1239
- if (ch === '"')
1240
- inStr = true;
1241
- else if (ch === '{')
1242
- depth++;
1243
- else if (ch === '}') {
1244
- depth--;
1245
- if (depth === 0)
1246
- return body.slice(start, i + 1);
1247
- }
1248
- }
1249
- return null;
1250
- }
1251
- /** Normalize a StreamToolCall (full or delta) into an LlmToolCall. */
1252
- function toLlmToolCall(c) {
1253
- if ('arguments' in c &&
1254
- typeof c.arguments === 'object' &&
1255
- c.arguments !== null) {
1256
- // Full LlmToolCall: arguments is already a parsed object.
1257
- return {
1258
- id: ('id' in c && c.id) || 'call',
1259
- name: ('name' in c && c.name) || '',
1260
- arguments: c.arguments,
1261
- };
1262
- }
1263
- // Delta: arguments is a (possibly partial) JSON string.
1264
- let args = {};
1265
- const raw = 'arguments' in c ? c.arguments : undefined;
1266
- if (typeof raw === 'string' && raw.length > 0) {
1267
- try {
1268
- args = JSON.parse(raw);
1269
- }
1270
- catch {
1271
- args = {};
1272
- }
1273
- }
1274
- return {
1275
- id: ('id' in c && c.id) || 'call',
1276
- name: ('name' in c && c.name) || '',
1277
- arguments: args,
1278
- };
1279
- }
1280
- /** Reconstruct and render the live step-state board from artifacts.
1281
- * Returns '' when there is no runId (the board has nothing to show yet). */
1282
- async function renderLiveBoard(rag, bundle, budget) {
1283
- const runId = bundle.runId;
1284
- if (!runId)
1285
- return '';
1286
- const [structure, claims] = await Promise.all([
1287
- readPlanDecisions(rag, runId),
1288
- readClaims(rag, runId),
1289
- ]);
1290
- const stepResults = await rag.list({ runId, artifactType: 'step-result' });
1291
- const board = reconstructBoard({
1292
- structure,
1293
- stepResults,
1294
- claims,
1295
- inFlight: bundle.inFlightStep,
1296
- pending: bundle.pending,
1297
- });
1298
- return renderBoard(board, budget);
1299
- }
1300
1170
  /** Synthesize the strict KnowledgeEntryMetadata for controller artifacts. */
1301
1171
  function synthMeta(ctx, sessionId) {
1302
1172
  const traceId = ctx.options?.trace?.traceId ?? sessionId;
@@ -1328,163 +1198,4 @@ function recordStepControl(bundle, rec) {
1328
1198
  (rec.note ? ` ${rec.note}` : '') +
1329
1199
  (rec.remainder ? ` remainder: ${rec.remainder}` : '');
1330
1200
  }
1331
- /** Gather the run's approved results, one per seq, resolved by outcome precedence
1332
- * (ok/exists > partial > failed), ordered by seq. Reconstructs the complete
1333
- * Outcome from artifact metadata (status/note/remainder) + content. */
1334
- async function collectApproved(rag, runId) {
1335
- const all = await rag.list({ runId, artifactType: 'step-result' });
1336
- const bySeq = new Map();
1337
- for (const e of all) {
1338
- const seq = e.metadata.seq ?? 0;
1339
- const o = {
1340
- status: (e.metadata.status ?? 'failed'),
1341
- approved: e.content,
1342
- remainder: e.metadata.remainder ?? '',
1343
- note: e.metadata.note ?? '',
1344
- };
1345
- const arr = bySeq.get(seq);
1346
- if (arr)
1347
- arr.push(o);
1348
- else
1349
- bySeq.set(seq, [o]);
1350
- }
1351
- const out = [];
1352
- for (const [seq, outcomes] of [...bySeq.entries()].sort((a, b) => a[0] - b[0])) {
1353
- const resolved = resolveByPrecedence(outcomes);
1354
- if (resolved && resolved.status !== 'failed')
1355
- out.push({ seq, content: resolved.approved });
1356
- }
1357
- return out;
1358
- }
1359
- /** The ONE run-scoped results-RAG recall primitive — used by BOTH the whole-step
1360
- * recall AND the per-`requires` evidence. EMBEDDING-based similarity via the
1361
- * backend's semantic query (NO homemade lexical scoring): the backend embeds the
1362
- * query + ranks by vector similarity, with the `runId` filter applied PRE-cap.
1363
- * Over-fetch `kPrime` (caller-supplied so the duplication bound is justified PER
1364
- * KIND), then dedup and cap to `k`. Dedup: step-results (have `seq`) →
1365
- * precedence-winner per seq; mcp-results → by `identityKey`. Embedding rank order
1366
- * is preserved through the dedup.
1367
- * `options` is forwarded into the embedder so recall-time embeds are metered. */
1368
- export async function runScopedRecall(rag, text, k, runId, kPrime, artifactType, options) {
1369
- const hits = await rag.query(text, {
1370
- k: kPrime,
1371
- filter: { runId, artifactType },
1372
- options,
1373
- });
1374
- const bestStep = new Map();
1375
- const bestMcp = new Map();
1376
- for (const e of hits) {
1377
- if (e.metadata.seq !== undefined && e.metadata.status !== undefined) {
1378
- const prev = bestStep.get(e.metadata.seq);
1379
- if (!prev || isBetterStep(e, prev))
1380
- bestStep.set(e.metadata.seq, e);
1381
- }
1382
- else if (e.metadata.identityKey) {
1383
- const prev = bestMcp.get(e.metadata.identityKey);
1384
- if (!prev || isBetterMcp(e, prev))
1385
- bestMcp.set(e.metadata.identityKey, e);
1386
- }
1387
- }
1388
- // Walk hits in embedding-rank order; emit each (runId,seq) / identityKey once.
1389
- const out = [];
1390
- const seenSeq = new Set();
1391
- const seenMcp = new Set();
1392
- for (const e of hits) {
1393
- if (e.metadata.seq !== undefined && e.metadata.status !== undefined) {
1394
- if (seenSeq.has(e.metadata.seq))
1395
- continue;
1396
- seenSeq.add(e.metadata.seq);
1397
- // biome-ignore lint/style/noNonNullAssertion: bestStep has this seq (set above).
1398
- out.push(bestStep.get(e.metadata.seq));
1399
- }
1400
- else if (e.metadata.identityKey) {
1401
- if (seenMcp.has(e.metadata.identityKey))
1402
- continue;
1403
- seenMcp.add(e.metadata.identityKey);
1404
- // biome-ignore lint/style/noNonNullAssertion: bestMcp has this key (set above).
1405
- out.push(bestMcp.get(e.metadata.identityKey));
1406
- }
1407
- else {
1408
- out.push(e);
1409
- }
1410
- if (out.length >= k)
1411
- break;
1412
- }
1413
- return out.slice(0, k);
1414
- }
1415
- /** Outcome-precedence rank for step-result dedup (ok/exists > partial > failed). */
1416
- function rankStatus(s) {
1417
- return s === 'ok' || s === 'exists'
1418
- ? 3
1419
- : s === 'partial'
1420
- ? 2
1421
- : s === 'failed'
1422
- ? 1
1423
- : 0;
1424
- }
1425
- /** True when candidate `a` is a better winner than current `b` for step-result
1426
- * dedup. Latest-wins by EXECUTION IDENTITY, not by semantic-rank position:
1427
- * 1. Higher status rank wins; on tie →
1428
- * 2. Higher attempt wins; on further tie →
1429
- * 3. Higher writeOrdinal wins (tie-breaks same-timestamp artifacts from one run); on tie →
1430
- * 4. Later createdAt wins (missing = older: compare with '' as sentinel). */
1431
- function isBetterStep(a, b) {
1432
- const ra = rankStatus(a.metadata.status);
1433
- const rb = rankStatus(b.metadata.status);
1434
- if (ra !== rb)
1435
- return ra > rb;
1436
- const aa = a.metadata.attempt ?? 0;
1437
- const ba = b.metadata.attempt ?? 0;
1438
- if (aa !== ba)
1439
- return aa > ba;
1440
- const ao = a.metadata.writeOrdinal ?? -1;
1441
- const bo = b.metadata.writeOrdinal ?? -1;
1442
- if (ao !== bo)
1443
- return ao > bo;
1444
- return (a.metadata.createdAt ?? '') > (b.metadata.createdAt ?? '');
1445
- }
1446
- /** True when candidate `a` is a better winner than current `b` for mcp-result
1447
- * dedup. Latest-fetch wins by writeOrdinal first (handles same-timestamp), then
1448
- * falls back to createdAt (missing = older). */
1449
- function isBetterMcp(a, b) {
1450
- const ao = a.metadata.writeOrdinal ?? -1;
1451
- const bo = b.metadata.writeOrdinal ?? -1;
1452
- if (ao !== bo)
1453
- return ao > bo;
1454
- return (a.metadata.createdAt ?? '') > (b.metadata.createdAt ?? '');
1455
- }
1456
- const MAX_EXTRACT_WINDOWS = 64;
1457
- /** Return the ≤`maxChars` fragment of `content` most similar to `ref` by EMBEDDING
1458
- * (NOT ASCII lexical overlap). DIRECT single-pass ranking: every candidate is
1459
- * scored on its own. The SCORED window IS the RETURNED body: candidates are
1460
- * `body = maxChars - 2` chars (head+tail '…' reserved up front), so the
1461
- * highest-scoring fragment is never truncated by the markers. Stride is 50%
1462
- * overlap, widened to span the whole content within MAX_EXTRACT_WINDOWS windows
1463
- * (point coverage for content ≤ MAX_EXTRACT_WINDOWS×maxChars; larger thins to
1464
- * non-overlapping, best-effort). Embeds are SEQUENTIAL and BOUNDED to ≤
1465
- * MAX_EXTRACT_WINDOWS + 1 — touches NO public embedder API (batch is a deferred
1466
- * optimization). Result STRICTLY ≤ maxChars; tiny maxChars (< 3) → bare slice.
1467
- * The `requires` ref is English (planner invariant) → a normal embedder suffices. */
1468
- export async function relevantExtract(content, ref, maxChars, embedder, options) {
1469
- if (content.length <= maxChars)
1470
- return content;
1471
- if (maxChars < 3)
1472
- return content.slice(0, Math.max(0, maxChars));
1473
- const body = maxChars - 2;
1474
- const stride = Math.max(Math.floor(body / 2), Math.ceil(content.length / MAX_EXTRACT_WINDOWS));
1475
- const { vector: q } = await embedder.embed(ref, options);
1476
- let bestStart = 0;
1477
- let bestScore = Number.NEGATIVE_INFINITY;
1478
- for (let s = 0; s < content.length; s += stride) {
1479
- const { vector } = await embedder.embed(content.slice(s, s + body), options);
1480
- const score = cosine(q, vector);
1481
- if (score > bestScore) {
1482
- bestScore = score;
1483
- bestStart = s;
1484
- }
1485
- }
1486
- const head = bestStart > 0 ? '…' : '';
1487
- const tail = bestStart + body < content.length ? '…' : '';
1488
- return head + content.slice(bestStart, bestStart + body) + tail;
1489
- }
1490
1201
  //# sourceMappingURL=controller-coordinator-handler.js.map