@mcp-abap-adt/llm-agent-server-libs 19.2.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.
- package/dist/builders/controller-skill-pipeline-builder.d.ts +57 -0
- package/dist/builders/controller-skill-pipeline-builder.d.ts.map +1 -0
- package/dist/builders/controller-skill-pipeline-builder.js +175 -0
- package/dist/builders/controller-skill-pipeline-builder.js.map +1 -0
- package/dist/factories/controller-factory.d.ts +2 -2
- package/dist/factories/controller-factory.d.ts.map +1 -1
- package/dist/factories/controller-factory.js +2 -1
- package/dist/factories/controller-factory.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pipelines/controller.d.ts +4 -2
- package/dist/pipelines/controller.d.ts.map +1 -1
- package/dist/pipelines/controller.js +33 -8
- package/dist/pipelines/controller.js.map +1 -1
- package/dist/pipelines/coordinator-resolvers.d.ts +68 -0
- package/dist/pipelines/coordinator-resolvers.d.ts.map +1 -0
- package/dist/pipelines/coordinator-resolvers.js +97 -0
- package/dist/pipelines/coordinator-resolvers.js.map +1 -0
- package/dist/pipelines/dag.d.ts +2 -2
- package/dist/pipelines/dag.js +2 -2
- package/dist/pipelines/parsers.d.ts +1 -1
- package/dist/pipelines/parsers.d.ts.map +1 -1
- package/dist/pipelines/parsers.js +5 -4
- package/dist/pipelines/parsers.js.map +1 -1
- package/dist/pipelines/register-skill-sources.d.ts.map +1 -1
- package/dist/pipelines/register-skill-sources.js +5 -0
- package/dist/pipelines/register-skill-sources.js.map +1 -1
- package/dist/pipelines/stepper.d.ts +2 -2
- package/dist/pipelines/stepper.js +2 -2
- package/dist/smart-agent/build-stepper-root.d.ts +3 -2
- package/dist/smart-agent/build-stepper-root.d.ts.map +1 -1
- package/dist/smart-agent/build-stepper-root.js +2 -1
- package/dist/smart-agent/build-stepper-root.js.map +1 -1
- package/dist/smart-agent/config-reload-watcher.d.ts +30 -0
- package/dist/smart-agent/config-reload-watcher.d.ts.map +1 -0
- package/dist/smart-agent/config-reload-watcher.js +101 -0
- package/dist/smart-agent/config-reload-watcher.js.map +1 -0
- package/dist/smart-agent/config-validator.d.ts +21 -0
- package/dist/smart-agent/config-validator.d.ts.map +1 -0
- package/dist/smart-agent/config-validator.js +196 -0
- package/dist/smart-agent/config-validator.js.map +1 -0
- package/dist/smart-agent/config.d.ts +16 -254
- package/dist/smart-agent/config.d.ts.map +1 -1
- package/dist/smart-agent/config.js +17 -904
- package/dist/smart-agent/config.js.map +1 -1
- package/dist/smart-agent/controller/board.d.ts +6 -3
- package/dist/smart-agent/controller/board.d.ts.map +1 -1
- package/dist/smart-agent/controller/board.js +21 -0
- package/dist/smart-agent/controller/board.js.map +1 -1
- package/dist/smart-agent/controller/controller-coordinator-handler.d.ts +16 -50
- package/dist/smart-agent/controller/controller-coordinator-handler.d.ts.map +1 -1
- package/dist/smart-agent/controller/controller-coordinator-handler.js +83 -371
- package/dist/smart-agent/controller/controller-coordinator-handler.js.map +1 -1
- package/dist/smart-agent/controller/finalizer.d.ts +5 -0
- package/dist/smart-agent/controller/finalizer.d.ts.map +1 -1
- package/dist/smart-agent/controller/finalizer.js +12 -5
- package/dist/smart-agent/controller/finalizer.js.map +1 -1
- package/dist/smart-agent/controller/parser.d.ts +11 -0
- package/dist/smart-agent/controller/parser.d.ts.map +1 -0
- package/dist/smart-agent/controller/parser.js +77 -0
- package/dist/smart-agent/controller/parser.js.map +1 -0
- package/dist/smart-agent/controller/planner.d.ts +20 -16
- package/dist/smart-agent/controller/planner.d.ts.map +1 -1
- package/dist/smart-agent/controller/planner.js +58 -64
- package/dist/smart-agent/controller/planner.js.map +1 -1
- package/dist/smart-agent/controller/recall.d.ts +48 -0
- package/dist/smart-agent/controller/recall.d.ts.map +1 -0
- package/dist/smart-agent/controller/recall.js +196 -0
- package/dist/smart-agent/controller/recall.js.map +1 -0
- package/dist/smart-agent/controller/reviewer.d.ts.map +1 -1
- package/dist/smart-agent/controller/reviewer.js +1 -1
- package/dist/smart-agent/controller/reviewer.js.map +1 -1
- package/dist/smart-agent/controller/types.d.ts +8 -9
- package/dist/smart-agent/controller/types.d.ts.map +1 -1
- package/dist/smart-agent/controller/usage-logging.d.ts +16 -0
- package/dist/smart-agent/controller/usage-logging.d.ts.map +1 -0
- package/dist/smart-agent/controller/usage-logging.js +39 -0
- package/dist/smart-agent/controller/usage-logging.js.map +1 -0
- package/dist/smart-agent/http/adapter-route-handler.d.ts +13 -0
- package/dist/smart-agent/http/adapter-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/adapter-route-handler.js +76 -0
- package/dist/smart-agent/http/adapter-route-handler.js.map +1 -0
- package/dist/smart-agent/http/chat-route-handler.d.ts +15 -0
- package/dist/smart-agent/http/chat-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/chat-route-handler.js +327 -0
- package/dist/smart-agent/http/chat-route-handler.js.map +1 -0
- package/dist/smart-agent/http/config-route-handler.d.ts +21 -0
- package/dist/smart-agent/http/config-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/config-route-handler.js +149 -0
- package/dist/smart-agent/http/config-route-handler.js.map +1 -0
- package/dist/smart-agent/http/health-route-handler.d.ts +12 -0
- package/dist/smart-agent/http/health-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/health-route-handler.js +19 -0
- package/dist/smart-agent/http/health-route-handler.js.map +1 -0
- package/dist/smart-agent/http/models-route-handler.d.ts +17 -0
- package/dist/smart-agent/http/models-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/models-route-handler.js +65 -0
- package/dist/smart-agent/http/models-route-handler.js.map +1 -0
- package/dist/smart-agent/http/response-helpers.d.ts +31 -0
- package/dist/smart-agent/http/response-helpers.d.ts.map +1 -0
- package/dist/smart-agent/http/response-helpers.js +59 -0
- package/dist/smart-agent/http/response-helpers.js.map +1 -0
- package/dist/smart-agent/http/route-table.d.ts +44 -0
- package/dist/smart-agent/http/route-table.d.ts.map +1 -0
- package/dist/smart-agent/http/route-table.js +35 -0
- package/dist/smart-agent/http/route-table.js.map +1 -0
- package/dist/smart-agent/http/session-cookie.d.ts +10 -0
- package/dist/smart-agent/http/session-cookie.d.ts.map +1 -0
- package/dist/smart-agent/http/session-cookie.js +16 -0
- package/dist/smart-agent/http/session-cookie.js.map +1 -0
- package/dist/smart-agent/http/sessions-route-handler.d.ts +8 -0
- package/dist/smart-agent/http/sessions-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/sessions-route-handler.js +54 -0
- package/dist/smart-agent/http/sessions-route-handler.js.map +1 -0
- package/dist/smart-agent/http/usage-route-handler.d.ts +12 -0
- package/dist/smart-agent/http/usage-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/usage-route-handler.js +28 -0
- package/dist/smart-agent/http/usage-route-handler.js.map +1 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts +13 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts.map +1 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.js +18 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.js.map +1 -0
- package/dist/smart-agent/llm/role-llm-resolver.d.ts +30 -0
- package/dist/smart-agent/llm/role-llm-resolver.d.ts.map +1 -0
- package/dist/smart-agent/llm/role-llm-resolver.js +44 -0
- package/dist/smart-agent/llm/role-llm-resolver.js.map +1 -0
- package/dist/smart-agent/llm-config-map.d.ts +38 -0
- package/dist/smart-agent/llm-config-map.d.ts.map +1 -0
- package/dist/smart-agent/llm-config-map.js +75 -0
- package/dist/smart-agent/llm-config-map.js.map +1 -0
- package/dist/smart-agent/mcp-readiness-monitor.d.ts +38 -0
- package/dist/smart-agent/mcp-readiness-monitor.d.ts.map +1 -0
- package/dist/smart-agent/mcp-readiness-monitor.js +81 -0
- package/dist/smart-agent/mcp-readiness-monitor.js.map +1 -0
- package/dist/smart-agent/mcp-readiness-registry.d.ts +37 -0
- package/dist/smart-agent/mcp-readiness-registry.d.ts.map +1 -0
- package/dist/smart-agent/mcp-readiness-registry.js +55 -0
- package/dist/smart-agent/mcp-readiness-registry.js.map +1 -0
- package/dist/smart-agent/resolve-config-sections.d.ts +15 -0
- package/dist/smart-agent/resolve-config-sections.d.ts.map +1 -0
- package/dist/smart-agent/resolve-config-sections.js +212 -0
- package/dist/smart-agent/resolve-config-sections.js.map +1 -0
- package/dist/smart-agent/session-lifecycle/index.d.ts +117 -0
- package/dist/smart-agent/session-lifecycle/index.d.ts.map +1 -0
- package/dist/smart-agent/session-lifecycle/index.js +152 -0
- package/dist/smart-agent/session-lifecycle/index.js.map +1 -0
- package/dist/smart-agent/skill-plugins-config.d.ts +9 -1
- package/dist/smart-agent/skill-plugins-config.d.ts.map +1 -1
- package/dist/smart-agent/skill-plugins-config.js +27 -1
- package/dist/smart-agent/skill-plugins-config.js.map +1 -1
- package/dist/smart-agent/skill-plugins-host-factory.d.ts +14 -2
- package/dist/smart-agent/skill-plugins-host-factory.d.ts.map +1 -1
- package/dist/smart-agent/skill-plugins-host-factory.js +16 -4
- package/dist/smart-agent/skill-plugins-host-factory.js.map +1 -1
- package/dist/smart-agent/smart-server.d.ts +151 -222
- package/dist/smart-agent/smart-server.d.ts.map +1 -1
- package/dist/smart-agent/smart-server.js +529 -1385
- package/dist/smart-agent/smart-server.js.map +1 -1
- package/dist/smart-agent/stepper-config.d.ts +138 -0
- package/dist/smart-agent/stepper-config.d.ts.map +1 -0
- package/dist/smart-agent/stepper-config.js +216 -0
- package/dist/smart-agent/stepper-config.js.map +1 -0
- package/dist/smart-agent/tools-rag-handle.d.ts +10 -0
- package/dist/smart-agent/tools-rag-handle.d.ts.map +1 -0
- package/dist/smart-agent/tools-rag-handle.js +76 -0
- package/dist/smart-agent/tools-rag-handle.js.map +1 -0
- package/dist/smart-agent/workers/worker-registry.d.ts +159 -0
- package/dist/smart-agent/workers/worker-registry.d.ts.map +1 -0
- package/dist/smart-agent/workers/worker-registry.js +203 -0
- package/dist/smart-agent/workers/worker-registry.js.map +1 -0
- package/dist/smart-agent/yaml-loader.d.ts +7 -0
- package/dist/smart-agent/yaml-loader.d.ts.map +1 -0
- package/dist/smart-agent/yaml-loader.js +146 -0
- package/dist/smart-agent/yaml-loader.js.map +1 -0
- 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 {
|
|
4
|
-
import {
|
|
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
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -106,7 +84,7 @@ export class ControllerCoordinatorHandler {
|
|
|
106
84
|
const externalNames = new Set((ctx.externalTools ?? []).map((t) => t.name));
|
|
107
85
|
const isExternalTool = (name) => externalNames.has(name) || (deps.isExternalTool?.(name) ?? false);
|
|
108
86
|
// True for the first planner.next of a turn that resumed an external-tool
|
|
109
|
-
// result (the result is now in plannerPrivate) → the
|
|
87
|
+
// result (the result is now in plannerPrivate) → the plan-first planner replans
|
|
110
88
|
// with it rather than blindly re-running the suspended step. Set in the
|
|
111
89
|
// external-tool resume branch below.
|
|
112
90
|
let resumedExternal = false;
|
|
@@ -187,10 +165,11 @@ export class ControllerCoordinatorHandler {
|
|
|
187
165
|
}
|
|
188
166
|
// -- Resume from a persisted pending marker -----------------------------
|
|
189
167
|
// Planner is constructed BEFORE the resume preamble: the artifact-first
|
|
190
|
-
// external-resume adopt below calls planner.commit() to keep the
|
|
168
|
+
// external-resume adopt below calls planner.commit() to keep the plan-first
|
|
191
169
|
// planCursor in lockstep with nextSeq. Stateless construction; the main loop
|
|
192
170
|
// reuses this same instance.
|
|
193
|
-
const planner =
|
|
171
|
+
const planner = deps.controllerPlanner ??
|
|
172
|
+
makeControllerPlanner(deps.plannerKind ?? 'smart-executor', deps.planner, deps.config.subagents.planner?.hint, deps.skillsRecall);
|
|
194
173
|
if (bundle.pending?.kind === 'external-tool') {
|
|
195
174
|
const { extId, toolName } = bundle.pending;
|
|
196
175
|
const seq = bundle.inFlightStep?.seq;
|
|
@@ -217,7 +196,7 @@ export class ControllerCoordinatorHandler {
|
|
|
217
196
|
bundle.pending = undefined;
|
|
218
197
|
bundle.runState = 'active';
|
|
219
198
|
// Same commit side effects as settle(), incl. planner.commit() so the
|
|
220
|
-
//
|
|
199
|
+
// plan-first planCursor advances with nextSeq.
|
|
221
200
|
const mapped = mapOutcome(resolved.status);
|
|
222
201
|
bundle.lastOutcome = mapped;
|
|
223
202
|
planner.commit?.(bundle, mapped);
|
|
@@ -292,7 +271,7 @@ export class ControllerCoordinatorHandler {
|
|
|
292
271
|
externalContinuation = true;
|
|
293
272
|
}
|
|
294
273
|
else {
|
|
295
|
-
// Legacy path (no inFlightStep — e.g. a seeded
|
|
274
|
+
// Legacy path (no inFlightStep — e.g. a seeded plan-first bundle): feed the
|
|
296
275
|
// result via plannerPrivate and let the planner replan.
|
|
297
276
|
bundle.plannerPrivate += `\n[external tool ${toolName} result] ${result}`;
|
|
298
277
|
bundle.pending = undefined;
|
|
@@ -383,7 +362,7 @@ export class ControllerCoordinatorHandler {
|
|
|
383
362
|
let planParseRetries = 0;
|
|
384
363
|
// bundle.lastOutcome is the SINGLE source of truth for the last step's
|
|
385
364
|
// outcome — durable, so a resume after a FAILED step replans instead of
|
|
386
|
-
// repeating it. runStep.settle() sets it; the
|
|
365
|
+
// repeating it. runStep.settle() sets it; the plan-first replan branch clears it
|
|
387
366
|
// once the failure has been consumed into a new plan (so a crash after the
|
|
388
367
|
// replan, or a finalizer retry after an empty replan, does NOT replan again).
|
|
389
368
|
while (bundle.budgets.stepsUsed < cfg.maxSteps) {
|
|
@@ -404,7 +383,7 @@ export class ControllerCoordinatorHandler {
|
|
|
404
383
|
})));
|
|
405
384
|
if (resolved) {
|
|
406
385
|
// Already committed → adopt, do NOT re-run. Same commit side effects as
|
|
407
|
-
// settle(), including planner.commit() so the
|
|
386
|
+
// settle(), including planner.commit() so the plan-first planCursor advances
|
|
408
387
|
// in lockstep with nextSeq.
|
|
409
388
|
const mapped = mapOutcome(resolved.status);
|
|
410
389
|
bundle.lastOutcome = mapped;
|
|
@@ -450,7 +429,7 @@ export class ControllerCoordinatorHandler {
|
|
|
450
429
|
}
|
|
451
430
|
// Planner crash-guard: a prior crash mid-call left plannerCallInFlight set →
|
|
452
431
|
// charge plannerResumeCount; exhausting maxPlannerResumes is a TERMINAL abort
|
|
453
|
-
// (store-first). The
|
|
432
|
+
// (store-first). The plan-first replan runs through planner.next too, so this one
|
|
454
433
|
// guard covers the awaiting-replan replan with no separate site.
|
|
455
434
|
if (bundle.plannerCallInFlight) {
|
|
456
435
|
bundle.plannerResumeCount = (bundle.plannerResumeCount ?? 0) + 1;
|
|
@@ -505,8 +484,8 @@ export class ControllerCoordinatorHandler {
|
|
|
505
484
|
// NB: do NOT reset resumedExternal here — if this replan reply was malformed
|
|
506
485
|
// (next === null), the parse-retry below must keep replanning. It is reset
|
|
507
486
|
// only after a VALID decision (beside planParseRetries = 0;).
|
|
508
|
-
// The
|
|
509
|
-
// a stateless resume continues from the same point. (No-op for
|
|
487
|
+
// The plan-first planner mutates bundle.plan/planCursor in next(); persist so
|
|
488
|
+
// a stateless resume continues from the same point. (No-op for a planner without plan-state.)
|
|
510
489
|
await persistBundle(deps.backend, sessionId, bundle);
|
|
511
490
|
// Format failure (not valid NextStep JSON) → re-ask the planner with a
|
|
512
491
|
// stern reminder, bounded by maxRetries. This does NOT touch rewindsUsed:
|
|
@@ -522,7 +501,7 @@ export class ControllerCoordinatorHandler {
|
|
|
522
501
|
resumedExternal = false; // a valid decision consumed any external-resume replan
|
|
523
502
|
if (next.kind === 'done') {
|
|
524
503
|
// Pass next.result as the legacy answer: used only when no finalizer is
|
|
525
|
-
// injected (3-role config) — the
|
|
504
|
+
// injected (3-role config) — the plan-first planner already composed it.
|
|
526
505
|
return this.finalize(ctx, sessionId, bundle, rag, prompt, logUsage, usageNow, now, terminalTtlMs, next.result);
|
|
527
506
|
}
|
|
528
507
|
if (next.kind === 'rewind') {
|
|
@@ -823,7 +802,32 @@ export class ControllerCoordinatorHandler {
|
|
|
823
802
|
bundle.plannerPrivate += `\n[step ${step.name} failed] empty tool call`;
|
|
824
803
|
return settle('failed');
|
|
825
804
|
}
|
|
826
|
-
|
|
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
|
+
})();
|
|
827
831
|
const name = call.name;
|
|
828
832
|
const args = call.arguments;
|
|
829
833
|
if (isExternalTool(name)) {
|
|
@@ -900,7 +904,27 @@ export class ControllerCoordinatorHandler {
|
|
|
900
904
|
return settle('failed');
|
|
901
905
|
}
|
|
902
906
|
// Execute locally, memorize, re-send to the executor.
|
|
903
|
-
|
|
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
|
+
}
|
|
904
928
|
bundle.writeOrdinal = (bundle.writeOrdinal ?? 0) + 1;
|
|
905
929
|
await writeArtifact(rag, {
|
|
906
930
|
...meta,
|
|
@@ -959,7 +983,7 @@ export class ControllerCoordinatorHandler {
|
|
|
959
983
|
this.surfaceFinal(ctx, `Error: ${error}`, usage);
|
|
960
984
|
}
|
|
961
985
|
async finalize(ctx, sessionId, bundle, rag, prompt, logUsage, usageNow, now, terminalTtlMs,
|
|
962
|
-
/** Used ONLY when no finalizer is injected (3-role config): the
|
|
986
|
+
/** Used ONLY when no finalizer is injected (3-role config): the plan-first
|
|
963
987
|
* planner's already-composed done.result. */
|
|
964
988
|
legacyAnswer) {
|
|
965
989
|
const deps = this.deps;
|
|
@@ -1003,12 +1027,18 @@ export class ControllerCoordinatorHandler {
|
|
|
1003
1027
|
await persistBundle(deps.backend, sessionId, bundle);
|
|
1004
1028
|
let answer;
|
|
1005
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;
|
|
1006
1035
|
while (answer === undefined) {
|
|
1007
1036
|
try {
|
|
1008
1037
|
const composed = await deps.finalizer.finalize(bundle.goal, request, approved, {
|
|
1009
1038
|
hint: deps.config.subagents.finalizer?.hint,
|
|
1010
1039
|
logUsage,
|
|
1011
1040
|
log: (m) => dlog(m),
|
|
1041
|
+
skillsBlock,
|
|
1012
1042
|
});
|
|
1013
1043
|
// Empty-but-ok finalizer output is a JUDGE failure (spec), not a valid
|
|
1014
1044
|
// answer → throw so it retries within maxFinalizeRetries.
|
|
@@ -1032,7 +1062,7 @@ export class ControllerCoordinatorHandler {
|
|
|
1032
1062
|
}
|
|
1033
1063
|
}
|
|
1034
1064
|
else {
|
|
1035
|
-
// Legacy: the
|
|
1065
|
+
// Legacy: the plan-first planner already composed the answer in done.result.
|
|
1036
1066
|
// Prefer the live param, else the durable copy persisted on finalizing-entry.
|
|
1037
1067
|
answer = legacyAnswer ?? bundle.legacyFinalAnswer ?? '';
|
|
1038
1068
|
}
|
|
@@ -1079,7 +1109,7 @@ export class ControllerCoordinatorHandler {
|
|
|
1079
1109
|
}
|
|
1080
1110
|
}
|
|
1081
1111
|
// ---------------------------------------------------------------------------
|
|
1082
|
-
//
|
|
1112
|
+
// Goal-clarification helpers
|
|
1083
1113
|
// ---------------------------------------------------------------------------
|
|
1084
1114
|
/** Bare confirmations that, on a goal clarify, commit the evaluator's proposed
|
|
1085
1115
|
* target rather than the literal answer. Anything else = a refinement. */
|
|
@@ -1110,7 +1140,6 @@ function isAffirmation(answer) {
|
|
|
1110
1140
|
.replace(/[.!]+$/, '');
|
|
1111
1141
|
return AFFIRMATIONS.has(t);
|
|
1112
1142
|
}
|
|
1113
|
-
/** Top-K tools surfaced from toolsRag per planner/step query. */
|
|
1114
1143
|
/** Agnostic executor system prompt. Domain specifics (e.g. SAP/ABAP fact kinds)
|
|
1115
1144
|
* are layered on via `subagents.executor.hint` (see {@link appendHint}). */
|
|
1116
1145
|
const EXECUTOR_SYSTEM = 'You are the executor. You have tools that read the live target system. ' +
|
|
@@ -1123,44 +1152,11 @@ const EXECUTOR_SYSTEM = 'You are the executor. You have tools that read the live
|
|
|
1123
1152
|
'if the step asks for a LIST or overview, return the list; do NOT then go and ' +
|
|
1124
1153
|
'fetch the full details of every listed item unless the step explicitly asks ' +
|
|
1125
1154
|
'for per-item details.';
|
|
1155
|
+
/** Top-K tools surfaced from toolsRag per planner/step query. */
|
|
1126
1156
|
const TOOL_SELECT_K = 20;
|
|
1127
|
-
/** Top-k recalled artifacts injected into the executor context per step. */
|
|
1128
|
-
/** Artifact types eligible for recall (excludes the 'controller-bundle' record
|
|
1129
|
-
* that shares the same backend). */
|
|
1130
|
-
const RECALL_ARTIFACT_TYPES = ['step-result', 'mcp-result'];
|
|
1131
|
-
/** Per-kind recall counts (distinct artifacts kept after dedup + cap). */
|
|
1132
|
-
const RECALL_K_STEP = 4;
|
|
1133
|
-
const RECALL_K_MCP = 4;
|
|
1134
|
-
/** SEPARATE char budgets per kind, so a huge step-result cannot starve MCP context. */
|
|
1135
|
-
const RECALL_MAX_CHARS_STEP = 2000;
|
|
1136
|
-
const RECALL_MAX_CHARS_MCP = 2000;
|
|
1137
|
-
/** Char budget for a single per-`requires` evidence extract handed to the reviewer. */
|
|
1138
|
-
const RECALL_EVIDENCE_CHARS = 800;
|
|
1139
1157
|
// ---------------------------------------------------------------------------
|
|
1140
1158
|
// Pure helpers
|
|
1141
1159
|
// ---------------------------------------------------------------------------
|
|
1142
|
-
/** Build a bounded "Relevant prior context" block from recalled artifacts under
|
|
1143
|
-
* the given char budget, or undefined when there is nothing to inject. */
|
|
1144
|
-
function buildRecallBlock(hits, maxChars) {
|
|
1145
|
-
if (hits.length === 0)
|
|
1146
|
-
return undefined;
|
|
1147
|
-
const parts = [];
|
|
1148
|
-
let used = 0;
|
|
1149
|
-
for (const h of hits) {
|
|
1150
|
-
const c = h.content ?? '';
|
|
1151
|
-
if (c.length === 0)
|
|
1152
|
-
continue;
|
|
1153
|
-
if (used + c.length > maxChars) {
|
|
1154
|
-
parts.push(c.slice(0, maxChars - used));
|
|
1155
|
-
break;
|
|
1156
|
-
}
|
|
1157
|
-
parts.push(c);
|
|
1158
|
-
used += c.length;
|
|
1159
|
-
}
|
|
1160
|
-
if (parts.length === 0)
|
|
1161
|
-
return undefined;
|
|
1162
|
-
return `Relevant prior context:\n${parts.join('\n')}`;
|
|
1163
|
-
}
|
|
1164
1160
|
/** Extract the user prompt from the request's textOrMessages. */
|
|
1165
1161
|
function extractPrompt(textOrMessages) {
|
|
1166
1162
|
if (typeof textOrMessages === 'string')
|
|
@@ -1171,131 +1167,6 @@ function extractPrompt(textOrMessages) {
|
|
|
1171
1167
|
}
|
|
1172
1168
|
return '';
|
|
1173
1169
|
}
|
|
1174
|
-
/** Parse a planner content string into a NextStep, defensively. */
|
|
1175
|
-
/** Parse the planner's reply into a NextStep, tolerating ```json fences and
|
|
1176
|
-
* surrounding prose. Returns null when no valid decision can be extracted — the
|
|
1177
|
-
* caller treats that as a FORMAT error (re-ask the planner), NOT a rewind, so a
|
|
1178
|
-
* badly-formatted reply never silently burns the rewind budget. */
|
|
1179
|
-
export function parseNextStep(content) {
|
|
1180
|
-
const json = extractJsonObject(content);
|
|
1181
|
-
if (json === null)
|
|
1182
|
-
return null;
|
|
1183
|
-
try {
|
|
1184
|
-
const obj = JSON.parse(json);
|
|
1185
|
-
if (obj.kind === 'done' && typeof obj.result === 'string')
|
|
1186
|
-
return { kind: 'done', result: obj.result };
|
|
1187
|
-
if (obj.kind === 'rewind' && typeof obj.reason === 'string')
|
|
1188
|
-
return { kind: 'rewind', reason: obj.reason };
|
|
1189
|
-
if (obj.kind === 'next' &&
|
|
1190
|
-
obj.step &&
|
|
1191
|
-
typeof obj.step.name === 'string' &&
|
|
1192
|
-
typeof obj.step.instructions === 'string') {
|
|
1193
|
-
// Validate requires[] so a non-string / empty / oversized reference never
|
|
1194
|
-
// reaches the semantic query / embedder; a malformed value is a parse
|
|
1195
|
-
// failure that drives the existing parse-retry.
|
|
1196
|
-
const req = validateRequires(obj.step.requires);
|
|
1197
|
-
if (req === false)
|
|
1198
|
-
return null;
|
|
1199
|
-
return {
|
|
1200
|
-
kind: 'next',
|
|
1201
|
-
step: {
|
|
1202
|
-
name: obj.step.name,
|
|
1203
|
-
instructions: obj.step.instructions,
|
|
1204
|
-
...(obj.step.type ? { type: obj.step.type } : {}),
|
|
1205
|
-
...(req ? { requires: req } : {}),
|
|
1206
|
-
},
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
catch {
|
|
1211
|
-
// fall through
|
|
1212
|
-
}
|
|
1213
|
-
return null;
|
|
1214
|
-
}
|
|
1215
|
-
/** Extract the first balanced JSON object from a planner reply, ignoring ```json
|
|
1216
|
-
* fences and prose around it. String-aware (braces inside strings don't count).
|
|
1217
|
-
* Returns null if no balanced object is present. */
|
|
1218
|
-
export function extractJsonObject(raw) {
|
|
1219
|
-
const fence = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
1220
|
-
const body = fence ? fence[1] : raw;
|
|
1221
|
-
const start = body.indexOf('{');
|
|
1222
|
-
if (start < 0)
|
|
1223
|
-
return null;
|
|
1224
|
-
let depth = 0;
|
|
1225
|
-
let inStr = false;
|
|
1226
|
-
let esc = false;
|
|
1227
|
-
for (let i = start; i < body.length; i++) {
|
|
1228
|
-
const ch = body[i];
|
|
1229
|
-
if (inStr) {
|
|
1230
|
-
if (esc)
|
|
1231
|
-
esc = false;
|
|
1232
|
-
else if (ch === '\\')
|
|
1233
|
-
esc = true;
|
|
1234
|
-
else if (ch === '"')
|
|
1235
|
-
inStr = false;
|
|
1236
|
-
continue;
|
|
1237
|
-
}
|
|
1238
|
-
if (ch === '"')
|
|
1239
|
-
inStr = true;
|
|
1240
|
-
else if (ch === '{')
|
|
1241
|
-
depth++;
|
|
1242
|
-
else if (ch === '}') {
|
|
1243
|
-
depth--;
|
|
1244
|
-
if (depth === 0)
|
|
1245
|
-
return body.slice(start, i + 1);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return null;
|
|
1249
|
-
}
|
|
1250
|
-
/** Normalize a StreamToolCall (full or delta) into an LlmToolCall. */
|
|
1251
|
-
function toLlmToolCall(c) {
|
|
1252
|
-
if ('arguments' in c &&
|
|
1253
|
-
typeof c.arguments === 'object' &&
|
|
1254
|
-
c.arguments !== null) {
|
|
1255
|
-
// Full LlmToolCall: arguments is already a parsed object.
|
|
1256
|
-
return {
|
|
1257
|
-
id: ('id' in c && c.id) || 'call',
|
|
1258
|
-
name: ('name' in c && c.name) || '',
|
|
1259
|
-
arguments: c.arguments,
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
// Delta: arguments is a (possibly partial) JSON string.
|
|
1263
|
-
let args = {};
|
|
1264
|
-
const raw = 'arguments' in c ? c.arguments : undefined;
|
|
1265
|
-
if (typeof raw === 'string' && raw.length > 0) {
|
|
1266
|
-
try {
|
|
1267
|
-
args = JSON.parse(raw);
|
|
1268
|
-
}
|
|
1269
|
-
catch {
|
|
1270
|
-
args = {};
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
return {
|
|
1274
|
-
id: ('id' in c && c.id) || 'call',
|
|
1275
|
-
name: ('name' in c && c.name) || '',
|
|
1276
|
-
arguments: args,
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
/** Reconstruct and render the live step-state board from artifacts.
|
|
1280
|
-
* Returns '' when there is no runId (the board has nothing to show yet). */
|
|
1281
|
-
async function renderLiveBoard(rag, bundle, budget) {
|
|
1282
|
-
const runId = bundle.runId;
|
|
1283
|
-
if (!runId)
|
|
1284
|
-
return '';
|
|
1285
|
-
const [structure, claims] = await Promise.all([
|
|
1286
|
-
readPlanDecisions(rag, runId),
|
|
1287
|
-
readClaims(rag, runId),
|
|
1288
|
-
]);
|
|
1289
|
-
const stepResults = await rag.list({ runId, artifactType: 'step-result' });
|
|
1290
|
-
const board = reconstructBoard({
|
|
1291
|
-
structure,
|
|
1292
|
-
stepResults,
|
|
1293
|
-
claims,
|
|
1294
|
-
inFlight: bundle.inFlightStep,
|
|
1295
|
-
pending: bundle.pending,
|
|
1296
|
-
});
|
|
1297
|
-
return renderBoard(board, budget);
|
|
1298
|
-
}
|
|
1299
1170
|
/** Synthesize the strict KnowledgeEntryMetadata for controller artifacts. */
|
|
1300
1171
|
function synthMeta(ctx, sessionId) {
|
|
1301
1172
|
const traceId = ctx.options?.trace?.traceId ?? sessionId;
|
|
@@ -1327,163 +1198,4 @@ function recordStepControl(bundle, rec) {
|
|
|
1327
1198
|
(rec.note ? ` ${rec.note}` : '') +
|
|
1328
1199
|
(rec.remainder ? ` remainder: ${rec.remainder}` : '');
|
|
1329
1200
|
}
|
|
1330
|
-
/** Gather the run's approved results, one per seq, resolved by outcome precedence
|
|
1331
|
-
* (ok/exists > partial > failed), ordered by seq. Reconstructs the complete
|
|
1332
|
-
* Outcome from artifact metadata (status/note/remainder) + content. */
|
|
1333
|
-
async function collectApproved(rag, runId) {
|
|
1334
|
-
const all = await rag.list({ runId, artifactType: 'step-result' });
|
|
1335
|
-
const bySeq = new Map();
|
|
1336
|
-
for (const e of all) {
|
|
1337
|
-
const seq = e.metadata.seq ?? 0;
|
|
1338
|
-
const o = {
|
|
1339
|
-
status: (e.metadata.status ?? 'failed'),
|
|
1340
|
-
approved: e.content,
|
|
1341
|
-
remainder: e.metadata.remainder ?? '',
|
|
1342
|
-
note: e.metadata.note ?? '',
|
|
1343
|
-
};
|
|
1344
|
-
const arr = bySeq.get(seq);
|
|
1345
|
-
if (arr)
|
|
1346
|
-
arr.push(o);
|
|
1347
|
-
else
|
|
1348
|
-
bySeq.set(seq, [o]);
|
|
1349
|
-
}
|
|
1350
|
-
const out = [];
|
|
1351
|
-
for (const [seq, outcomes] of [...bySeq.entries()].sort((a, b) => a[0] - b[0])) {
|
|
1352
|
-
const resolved = resolveByPrecedence(outcomes);
|
|
1353
|
-
if (resolved && resolved.status !== 'failed')
|
|
1354
|
-
out.push({ seq, content: resolved.approved });
|
|
1355
|
-
}
|
|
1356
|
-
return out;
|
|
1357
|
-
}
|
|
1358
|
-
/** The ONE run-scoped results-RAG recall primitive — used by BOTH the whole-step
|
|
1359
|
-
* recall AND the per-`requires` evidence. EMBEDDING-based similarity via the
|
|
1360
|
-
* backend's semantic query (NO homemade lexical scoring): the backend embeds the
|
|
1361
|
-
* query + ranks by vector similarity, with the `runId` filter applied PRE-cap.
|
|
1362
|
-
* Over-fetch `kPrime` (caller-supplied so the duplication bound is justified PER
|
|
1363
|
-
* KIND), then dedup and cap to `k`. Dedup: step-results (have `seq`) →
|
|
1364
|
-
* precedence-winner per seq; mcp-results → by `identityKey`. Embedding rank order
|
|
1365
|
-
* is preserved through the dedup.
|
|
1366
|
-
* `options` is forwarded into the embedder so recall-time embeds are metered. */
|
|
1367
|
-
export async function runScopedRecall(rag, text, k, runId, kPrime, artifactType, options) {
|
|
1368
|
-
const hits = await rag.query(text, {
|
|
1369
|
-
k: kPrime,
|
|
1370
|
-
filter: { runId, artifactType },
|
|
1371
|
-
options,
|
|
1372
|
-
});
|
|
1373
|
-
const bestStep = new Map();
|
|
1374
|
-
const bestMcp = new Map();
|
|
1375
|
-
for (const e of hits) {
|
|
1376
|
-
if (e.metadata.seq !== undefined && e.metadata.status !== undefined) {
|
|
1377
|
-
const prev = bestStep.get(e.metadata.seq);
|
|
1378
|
-
if (!prev || isBetterStep(e, prev))
|
|
1379
|
-
bestStep.set(e.metadata.seq, e);
|
|
1380
|
-
}
|
|
1381
|
-
else if (e.metadata.identityKey) {
|
|
1382
|
-
const prev = bestMcp.get(e.metadata.identityKey);
|
|
1383
|
-
if (!prev || isBetterMcp(e, prev))
|
|
1384
|
-
bestMcp.set(e.metadata.identityKey, e);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
// Walk hits in embedding-rank order; emit each (runId,seq) / identityKey once.
|
|
1388
|
-
const out = [];
|
|
1389
|
-
const seenSeq = new Set();
|
|
1390
|
-
const seenMcp = new Set();
|
|
1391
|
-
for (const e of hits) {
|
|
1392
|
-
if (e.metadata.seq !== undefined && e.metadata.status !== undefined) {
|
|
1393
|
-
if (seenSeq.has(e.metadata.seq))
|
|
1394
|
-
continue;
|
|
1395
|
-
seenSeq.add(e.metadata.seq);
|
|
1396
|
-
// biome-ignore lint/style/noNonNullAssertion: bestStep has this seq (set above).
|
|
1397
|
-
out.push(bestStep.get(e.metadata.seq));
|
|
1398
|
-
}
|
|
1399
|
-
else if (e.metadata.identityKey) {
|
|
1400
|
-
if (seenMcp.has(e.metadata.identityKey))
|
|
1401
|
-
continue;
|
|
1402
|
-
seenMcp.add(e.metadata.identityKey);
|
|
1403
|
-
// biome-ignore lint/style/noNonNullAssertion: bestMcp has this key (set above).
|
|
1404
|
-
out.push(bestMcp.get(e.metadata.identityKey));
|
|
1405
|
-
}
|
|
1406
|
-
else {
|
|
1407
|
-
out.push(e);
|
|
1408
|
-
}
|
|
1409
|
-
if (out.length >= k)
|
|
1410
|
-
break;
|
|
1411
|
-
}
|
|
1412
|
-
return out.slice(0, k);
|
|
1413
|
-
}
|
|
1414
|
-
/** Outcome-precedence rank for step-result dedup (ok/exists > partial > failed). */
|
|
1415
|
-
function rankStatus(s) {
|
|
1416
|
-
return s === 'ok' || s === 'exists'
|
|
1417
|
-
? 3
|
|
1418
|
-
: s === 'partial'
|
|
1419
|
-
? 2
|
|
1420
|
-
: s === 'failed'
|
|
1421
|
-
? 1
|
|
1422
|
-
: 0;
|
|
1423
|
-
}
|
|
1424
|
-
/** True when candidate `a` is a better winner than current `b` for step-result
|
|
1425
|
-
* dedup. Latest-wins by EXECUTION IDENTITY, not by semantic-rank position:
|
|
1426
|
-
* 1. Higher status rank wins; on tie →
|
|
1427
|
-
* 2. Higher attempt wins; on further tie →
|
|
1428
|
-
* 3. Higher writeOrdinal wins (tie-breaks same-timestamp artifacts from one run); on tie →
|
|
1429
|
-
* 4. Later createdAt wins (missing = older: compare with '' as sentinel). */
|
|
1430
|
-
function isBetterStep(a, b) {
|
|
1431
|
-
const ra = rankStatus(a.metadata.status);
|
|
1432
|
-
const rb = rankStatus(b.metadata.status);
|
|
1433
|
-
if (ra !== rb)
|
|
1434
|
-
return ra > rb;
|
|
1435
|
-
const aa = a.metadata.attempt ?? 0;
|
|
1436
|
-
const ba = b.metadata.attempt ?? 0;
|
|
1437
|
-
if (aa !== ba)
|
|
1438
|
-
return aa > ba;
|
|
1439
|
-
const ao = a.metadata.writeOrdinal ?? -1;
|
|
1440
|
-
const bo = b.metadata.writeOrdinal ?? -1;
|
|
1441
|
-
if (ao !== bo)
|
|
1442
|
-
return ao > bo;
|
|
1443
|
-
return (a.metadata.createdAt ?? '') > (b.metadata.createdAt ?? '');
|
|
1444
|
-
}
|
|
1445
|
-
/** True when candidate `a` is a better winner than current `b` for mcp-result
|
|
1446
|
-
* dedup. Latest-fetch wins by writeOrdinal first (handles same-timestamp), then
|
|
1447
|
-
* falls back to createdAt (missing = older). */
|
|
1448
|
-
function isBetterMcp(a, b) {
|
|
1449
|
-
const ao = a.metadata.writeOrdinal ?? -1;
|
|
1450
|
-
const bo = b.metadata.writeOrdinal ?? -1;
|
|
1451
|
-
if (ao !== bo)
|
|
1452
|
-
return ao > bo;
|
|
1453
|
-
return (a.metadata.createdAt ?? '') > (b.metadata.createdAt ?? '');
|
|
1454
|
-
}
|
|
1455
|
-
const MAX_EXTRACT_WINDOWS = 64;
|
|
1456
|
-
/** Return the ≤`maxChars` fragment of `content` most similar to `ref` by EMBEDDING
|
|
1457
|
-
* (NOT ASCII lexical overlap). DIRECT single-pass ranking: every candidate is
|
|
1458
|
-
* scored on its own. The SCORED window IS the RETURNED body: candidates are
|
|
1459
|
-
* `body = maxChars - 2` chars (head+tail '…' reserved up front), so the
|
|
1460
|
-
* highest-scoring fragment is never truncated by the markers. Stride is 50%
|
|
1461
|
-
* overlap, widened to span the whole content within MAX_EXTRACT_WINDOWS windows
|
|
1462
|
-
* (point coverage for content ≤ MAX_EXTRACT_WINDOWS×maxChars; larger thins to
|
|
1463
|
-
* non-overlapping, best-effort). Embeds are SEQUENTIAL and BOUNDED to ≤
|
|
1464
|
-
* MAX_EXTRACT_WINDOWS + 1 — touches NO public embedder API (batch is a deferred
|
|
1465
|
-
* optimization). Result STRICTLY ≤ maxChars; tiny maxChars (< 3) → bare slice.
|
|
1466
|
-
* The `requires` ref is English (planner invariant) → a normal embedder suffices. */
|
|
1467
|
-
export async function relevantExtract(content, ref, maxChars, embedder, options) {
|
|
1468
|
-
if (content.length <= maxChars)
|
|
1469
|
-
return content;
|
|
1470
|
-
if (maxChars < 3)
|
|
1471
|
-
return content.slice(0, Math.max(0, maxChars));
|
|
1472
|
-
const body = maxChars - 2;
|
|
1473
|
-
const stride = Math.max(Math.floor(body / 2), Math.ceil(content.length / MAX_EXTRACT_WINDOWS));
|
|
1474
|
-
const { vector: q } = await embedder.embed(ref, options);
|
|
1475
|
-
let bestStart = 0;
|
|
1476
|
-
let bestScore = Number.NEGATIVE_INFINITY;
|
|
1477
|
-
for (let s = 0; s < content.length; s += stride) {
|
|
1478
|
-
const { vector } = await embedder.embed(content.slice(s, s + body), options);
|
|
1479
|
-
const score = cosine(q, vector);
|
|
1480
|
-
if (score > bestScore) {
|
|
1481
|
-
bestScore = score;
|
|
1482
|
-
bestStart = s;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
const head = bestStart > 0 ? '…' : '';
|
|
1486
|
-
const tail = bestStart + body < content.length ? '…' : '';
|
|
1487
|
-
return head + content.slice(bestStart, bestStart + body) + tail;
|
|
1488
|
-
}
|
|
1489
1201
|
//# sourceMappingURL=controller-coordinator-handler.js.map
|