@mcoda/core 0.1.8 → 0.1.11
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/CHANGELOG.md +3 -0
- package/README.md +2 -2
- package/dist/api/AgentsApi.d.ts +9 -1
- package/dist/api/AgentsApi.d.ts.map +1 -1
- package/dist/api/AgentsApi.js +201 -6
- package/dist/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +6 -0
- package/dist/api/TasksApi.d.ts.map +1 -1
- package/dist/api/TasksApi.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -1
- package/dist/prompts/PdrPrompts.js +9 -1
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +9 -0
- package/dist/services/agents/AgentRatingFormula.d.ts +27 -0
- package/dist/services/agents/AgentRatingFormula.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingFormula.js +45 -0
- package/dist/services/agents/AgentRatingService.d.ts +60 -0
- package/dist/services/agents/AgentRatingService.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingService.js +363 -0
- package/dist/services/agents/GatewayAgentService.d.ts +11 -0
- package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
- package/dist/services/agents/GatewayAgentService.js +525 -84
- package/dist/services/agents/GatewayHandoff.d.ts +11 -0
- package/dist/services/agents/GatewayHandoff.d.ts.map +1 -0
- package/dist/services/agents/GatewayHandoff.js +141 -0
- package/dist/services/agents/RoutingService.d.ts +1 -0
- package/dist/services/agents/RoutingService.d.ts.map +1 -1
- package/dist/services/agents/RoutingService.js +4 -4
- package/dist/services/backlog/BacklogService.d.ts +23 -0
- package/dist/services/backlog/BacklogService.d.ts.map +1 -1
- package/dist/services/backlog/BacklogService.js +62 -7
- package/dist/services/backlog/TaskOrderingHeuristics.d.ts +12 -0
- package/dist/services/backlog/TaskOrderingHeuristics.d.ts.map +1 -0
- package/dist/services/backlog/TaskOrderingHeuristics.js +56 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +17 -4
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +538 -79
- package/dist/services/docs/DocInventory.d.ts +11 -0
- package/dist/services/docs/DocInventory.d.ts.map +1 -0
- package/dist/services/docs/DocInventory.js +230 -0
- package/dist/services/docs/DocgenRunContext.d.ts +59 -0
- package/dist/services/docs/DocgenRunContext.d.ts.map +1 -0
- package/dist/services/docs/DocgenRunContext.js +4 -0
- package/dist/services/docs/DocsService.d.ts +70 -3
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +1930 -89
- package/dist/services/docs/alignment/DocAlignmentGraph.d.ts +23 -0
- package/dist/services/docs/alignment/DocAlignmentGraph.d.ts.map +1 -0
- package/dist/services/docs/alignment/DocAlignmentGraph.js +78 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts +19 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts.map +1 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.js +222 -0
- package/dist/services/docs/patch/DocPatchEngine.d.ts +57 -0
- package/dist/services/docs/patch/DocPatchEngine.d.ts.map +1 -0
- package/dist/services/docs/patch/DocPatchEngine.js +331 -0
- package/dist/services/docs/review/Glossary.d.ts +16 -0
- package/dist/services/docs/review/Glossary.d.ts.map +1 -0
- package/dist/services/docs/review/Glossary.js +47 -0
- package/dist/services/docs/review/ReviewReportRenderer.d.ts +3 -0
- package/dist/services/docs/review/ReviewReportRenderer.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewReportRenderer.js +133 -0
- package/dist/services/docs/review/ReviewReportSchema.d.ts +39 -0
- package/dist/services/docs/review/ReviewReportSchema.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewReportSchema.js +47 -0
- package/dist/services/docs/review/ReviewTypes.d.ts +76 -0
- package/dist/services/docs/review/ReviewTypes.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewTypes.js +94 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts +7 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.js +93 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts +7 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.js +308 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts +8 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.js +278 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts +8 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.js +487 -0
- package/dist/services/docs/review/gates/NoMaybesGate.d.ts +8 -0
- package/dist/services/docs/review/gates/NoMaybesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/NoMaybesGate.js +145 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.js +266 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.js +59 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.js +200 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.js +159 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts +8 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.js +129 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.js +169 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts +10 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.js +261 -0
- package/dist/services/docs/review/gates/RfpConsentGate.d.ts +6 -0
- package/dist/services/docs/review/gates/RfpConsentGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/RfpConsentGate.js +127 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts +7 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.js +173 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.js +196 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.js +89 -0
- package/dist/services/docs/review/gates/SdsOpsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsOpsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsOpsGate.js +162 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.js +166 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.js +273 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.js +203 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts +9 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.js +217 -0
- package/dist/services/docs/review/glossary.json +47 -0
- package/dist/services/estimate/EstimateService.d.ts +2 -0
- package/dist/services/estimate/EstimateService.d.ts.map +1 -1
- package/dist/services/estimate/EstimateService.js +66 -18
- package/dist/services/estimate/VelocityService.d.ts +4 -0
- package/dist/services/estimate/VelocityService.d.ts.map +1 -1
- package/dist/services/estimate/VelocityService.js +179 -36
- package/dist/services/estimate/types.d.ts +1 -0
- package/dist/services/estimate/types.d.ts.map +1 -1
- package/dist/services/execution/GatewayTrioService.d.ts +200 -0
- package/dist/services/execution/GatewayTrioService.d.ts.map +1 -0
- package/dist/services/execution/GatewayTrioService.js +2492 -0
- package/dist/services/execution/QaApiRunner.d.ts +30 -0
- package/dist/services/execution/QaApiRunner.d.ts.map +1 -0
- package/dist/services/execution/QaApiRunner.js +881 -0
- package/dist/services/execution/QaFollowupService.d.ts +2 -0
- package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
- package/dist/services/execution/QaFollowupService.js +9 -2
- package/dist/services/execution/QaPlanValidator.d.ts +10 -0
- package/dist/services/execution/QaPlanValidator.d.ts.map +1 -0
- package/dist/services/execution/QaPlanValidator.js +128 -0
- package/dist/services/execution/QaProfileService.d.ts +27 -1
- package/dist/services/execution/QaProfileService.d.ts.map +1 -1
- package/dist/services/execution/QaProfileService.js +354 -7
- package/dist/services/execution/QaTasksService.d.ts +59 -1
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +3347 -318
- package/dist/services/execution/QaTestCommandBuilder.d.ts +51 -0
- package/dist/services/execution/QaTestCommandBuilder.d.ts.map +1 -0
- package/dist/services/execution/QaTestCommandBuilder.js +495 -0
- package/dist/services/execution/TaskSelectionService.d.ts +4 -2
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
- package/dist/services/execution/TaskSelectionService.js +144 -28
- package/dist/services/execution/TaskStateService.d.ts +19 -6
- package/dist/services/execution/TaskStateService.d.ts.map +1 -1
- package/dist/services/execution/TaskStateService.js +128 -13
- package/dist/services/execution/WorkOnTasksService.d.ts +32 -1
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +4667 -722
- package/dist/services/jobs/JobInsightsService.d.ts +4 -0
- package/dist/services/jobs/JobInsightsService.d.ts.map +1 -1
- package/dist/services/jobs/JobInsightsService.js +51 -5
- package/dist/services/jobs/JobResumeService.d.ts.map +1 -1
- package/dist/services/jobs/JobResumeService.js +23 -10
- package/dist/services/jobs/JobService.d.ts +56 -4
- package/dist/services/jobs/JobService.d.ts.map +1 -1
- package/dist/services/jobs/JobService.js +232 -1
- package/dist/services/openapi/OpenApiService.d.ts +51 -0
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +953 -106
- package/dist/services/planning/CreateTasksService.d.ts +21 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +569 -31
- package/dist/services/planning/RefineTasksService.d.ts +9 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +409 -59
- package/dist/services/review/CodeReviewService.d.ts +18 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +1309 -167
- package/dist/services/review/ReviewNormalizer.d.ts +9 -0
- package/dist/services/review/ReviewNormalizer.d.ts.map +1 -0
- package/dist/services/review/ReviewNormalizer.js +147 -0
- package/dist/services/shared/AuthErrors.d.ts +3 -0
- package/dist/services/shared/AuthErrors.d.ts.map +1 -0
- package/dist/services/shared/AuthErrors.js +17 -0
- package/dist/services/shared/DocdexGuidance.d.ts +7 -0
- package/dist/services/shared/DocdexGuidance.d.ts.map +1 -0
- package/dist/services/shared/DocdexGuidance.js +12 -0
- package/dist/services/shared/ProjectGuidance.d.ts +17 -0
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -0
- package/dist/services/shared/ProjectGuidance.js +78 -0
- package/dist/services/system/ToolDenylist.d.ts +13 -0
- package/dist/services/system/ToolDenylist.d.ts.map +1 -0
- package/dist/services/system/ToolDenylist.js +85 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts +20 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts.map +1 -0
- package/dist/services/tasks/TaskCommentFormatter.js +54 -0
- package/dist/services/telemetry/TelemetryService.d.ts.map +1 -1
- package/dist/services/telemetry/TelemetryService.js +39 -7
- package/dist/workspace/WorkspaceManager.d.ts +26 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
- package/dist/workspace/WorkspaceManager.js +206 -32
- package/package.json +6 -5
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { AgentService } from "@mcoda/agents";
|
|
4
5
|
import { DocdexClient } from "@mcoda/integrations";
|
|
5
6
|
import { GlobalRepository, WorkspaceRepository } from "@mcoda/db";
|
|
6
|
-
import { canonicalizeCommandName, getCommandRequiredCapabilities } from "@mcoda/shared";
|
|
7
|
+
import { READY_TO_CODE_REVIEW, canonicalizeCommandName, getCommandRequiredCapabilities } from "@mcoda/shared";
|
|
7
8
|
import { JobService } from "../jobs/JobService.js";
|
|
8
9
|
import { TaskSelectionService } from "../execution/TaskSelectionService.js";
|
|
9
10
|
import { RoutingService } from "./RoutingService.js";
|
|
11
|
+
import { isDocContextExcluded, normalizeDocType } from "../shared/ProjectGuidance.js";
|
|
10
12
|
const DEFAULT_GATEWAY_PROMPT = [
|
|
11
13
|
"You are the gateway agent. Read the task context and docdex snippets, digest the task, decide what is done vs. remaining, and plan the work.",
|
|
12
14
|
"You must identify concrete file paths to modify or create before offloading.",
|
|
15
|
+
"If new directories are required for planned files, list them explicitly in dirsToCreate.",
|
|
13
16
|
"Do not use placeholders like (unknown), TBD, or glob patterns in file paths.",
|
|
17
|
+
"Do not assume repository structure; only name paths grounded in provided file content or docdex context.",
|
|
18
|
+
"If you add or modify tests, ensure tests/all.js is updated (or state that it already covers the new tests).",
|
|
14
19
|
"If docdex returns no results, say so in docdexNotes.",
|
|
15
20
|
"Do not leave currentState, todo, or understanding blank.",
|
|
16
21
|
"Put reasoningSummary near the top of the JSON object so it appears early in the stream.",
|
|
@@ -28,12 +33,15 @@ const DEFAULT_GATEWAY_PROMPT = [
|
|
|
28
33
|
' "discipline": "backend|frontend|uiux|docs|architecture|qa|planning|ops|other",',
|
|
29
34
|
' "filesLikelyTouched": ["path/to/file.ext"],',
|
|
30
35
|
' "filesToCreate": ["path/to/new_file.ext"],',
|
|
36
|
+
' "dirsToCreate": ["path/to/new_dir"],',
|
|
31
37
|
' "assumptions": ["assumption 1"],',
|
|
32
38
|
' "risks": ["risk 1"],',
|
|
33
39
|
' "docdexNotes": ["notes about docdex coverage/gaps"]',
|
|
34
40
|
"}",
|
|
35
41
|
"If information is missing, keep arrays empty and mention the gap in assumptions or docdexNotes.",
|
|
36
42
|
].join("\n");
|
|
43
|
+
const REPO_PROMPTS_DIR = fileURLToPath(new URL("../../../../../prompts/", import.meta.url));
|
|
44
|
+
const resolveRepoPromptPath = (filename) => path.join(REPO_PROMPTS_DIR, filename);
|
|
37
45
|
const REQUIRED_PROMPT_MARKERS = [
|
|
38
46
|
'"summary"',
|
|
39
47
|
'"reasoningSummary"',
|
|
@@ -42,27 +50,63 @@ const REQUIRED_PROMPT_MARKERS = [
|
|
|
42
50
|
'"understanding"',
|
|
43
51
|
'"filesLikelyTouched"',
|
|
44
52
|
'"filesToCreate"',
|
|
53
|
+
'"dirsToCreate"',
|
|
45
54
|
];
|
|
46
55
|
const hasRequiredPromptMarkers = (content) => REQUIRED_PROMPT_MARKERS.every((marker) => content.includes(marker));
|
|
47
56
|
const DEFAULT_JOB_PROMPT = "You are an mcoda agent that follows workspace runbooks and responds with actionable, concise output.";
|
|
48
57
|
const DEFAULT_CHARACTER_PROMPT = "Write clearly, avoid hallucinations, cite assumptions, and prioritize risk mitigation for the user.";
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const ROUTING_PROMPT_MARKERS = [
|
|
59
|
+
"routing gateway",
|
|
60
|
+
"choose a route",
|
|
61
|
+
"devstral-local",
|
|
62
|
+
"glm-worker",
|
|
63
|
+
"codex-architect",
|
|
64
|
+
"complexity from 1 to 5",
|
|
65
|
+
];
|
|
66
|
+
const sanitizeAgentOutput = (output) => {
|
|
67
|
+
if (!output.includes("[agent-io]"))
|
|
68
|
+
return output;
|
|
69
|
+
const lines = output.split(/\r?\n/);
|
|
70
|
+
const cleaned = [];
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
if (!line.includes("[agent-io]")) {
|
|
73
|
+
cleaned.push(line);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (/^\s*\[agent-io\]\s*(?:begin|input|meta)/i.test(line)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const stripped = line.replace(/^\s*\[agent-io\]\s*(?:output\s*)?/i, "");
|
|
80
|
+
if (stripped.trim())
|
|
81
|
+
cleaned.push(stripped);
|
|
82
|
+
}
|
|
83
|
+
return cleaned.join("\n");
|
|
84
|
+
};
|
|
85
|
+
const extractJsonOnly = (raw) => {
|
|
86
|
+
const trimmed = raw.trim();
|
|
87
|
+
if (!trimmed)
|
|
88
|
+
return { jsonOnly: false };
|
|
89
|
+
let candidate = trimmed;
|
|
90
|
+
if (trimmed.startsWith("```")) {
|
|
91
|
+
const match = trimmed.match(/^```(?:json)?\s*([\s\S]*?)```$/i);
|
|
92
|
+
if (!match)
|
|
93
|
+
return { jsonOnly: false };
|
|
94
|
+
candidate = match[1].trim();
|
|
95
|
+
}
|
|
96
|
+
else if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
97
|
+
return { jsonOnly: false };
|
|
98
|
+
}
|
|
57
99
|
try {
|
|
58
|
-
return JSON.parse(
|
|
100
|
+
return { payload: JSON.parse(candidate), jsonOnly: true };
|
|
59
101
|
}
|
|
60
102
|
catch {
|
|
61
|
-
return
|
|
103
|
+
return { jsonOnly: true };
|
|
62
104
|
}
|
|
63
105
|
};
|
|
64
106
|
const estimateTokens = (text) => Math.max(1, Math.ceil((text ?? "").length / 4));
|
|
65
107
|
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
108
|
+
const STRONG_TIER_MIN_COMPLEXITY = 5;
|
|
109
|
+
const SPECIALIST_TIER_MIN_COMPLEXITY = 8;
|
|
66
110
|
const normalizeList = (value) => {
|
|
67
111
|
if (Array.isArray(value))
|
|
68
112
|
return value.map((item) => String(item)).filter(Boolean);
|
|
@@ -81,6 +125,20 @@ const normalizeTextField = (value) => {
|
|
|
81
125
|
}
|
|
82
126
|
return undefined;
|
|
83
127
|
};
|
|
128
|
+
const isRoutingPrompt = (value) => {
|
|
129
|
+
const lower = value.toLowerCase();
|
|
130
|
+
return ROUTING_PROMPT_MARKERS.some((marker) => lower.includes(marker));
|
|
131
|
+
};
|
|
132
|
+
const sanitizeGatewayPrompt = (value) => {
|
|
133
|
+
if (!value)
|
|
134
|
+
return undefined;
|
|
135
|
+
const trimmed = value.trim();
|
|
136
|
+
if (!trimmed)
|
|
137
|
+
return undefined;
|
|
138
|
+
if (isRoutingPrompt(trimmed))
|
|
139
|
+
return undefined;
|
|
140
|
+
return trimmed;
|
|
141
|
+
};
|
|
84
142
|
const isPlaceholderPath = (value) => {
|
|
85
143
|
const lower = value.trim().toLowerCase();
|
|
86
144
|
if (!lower)
|
|
@@ -93,7 +151,77 @@ const isPlaceholderPath = (value) => {
|
|
|
93
151
|
return false;
|
|
94
152
|
};
|
|
95
153
|
const normalizeFileList = (value) => normalizeList(value).map((item) => item.trim()).filter((item) => item.length > 0 && !isPlaceholderPath(item));
|
|
96
|
-
const
|
|
154
|
+
const normalizeDirList = (value) => normalizeList(value)
|
|
155
|
+
.map((item) => item.trim().replace(/[/\\]+$/, ""))
|
|
156
|
+
.filter((item) => item.length > 0 && !isPlaceholderPath(item));
|
|
157
|
+
const isDirMarker = (value) => {
|
|
158
|
+
const trimmed = value.trim();
|
|
159
|
+
if (!trimmed)
|
|
160
|
+
return false;
|
|
161
|
+
if (/[/\\]$/.test(trimmed))
|
|
162
|
+
return true;
|
|
163
|
+
if (/^\s*(?:dir|directory)\s*:/i.test(trimmed))
|
|
164
|
+
return true;
|
|
165
|
+
if (/\(\s*dir\s*\)\s*$/i.test(trimmed))
|
|
166
|
+
return true;
|
|
167
|
+
return false;
|
|
168
|
+
};
|
|
169
|
+
const stripDirMarker = (value) => {
|
|
170
|
+
let trimmed = value.trim();
|
|
171
|
+
trimmed = trimmed.replace(/^\s*(?:dir|directory)\s*:/i, "");
|
|
172
|
+
trimmed = trimmed.replace(/\(\s*dir\s*\)\s*$/i, "");
|
|
173
|
+
trimmed = trimmed.replace(/[/\\]+$/, "");
|
|
174
|
+
return trimmed.trim();
|
|
175
|
+
};
|
|
176
|
+
const splitFileAndDirEntries = (values) => {
|
|
177
|
+
const files = [];
|
|
178
|
+
const dirs = [];
|
|
179
|
+
for (const value of values) {
|
|
180
|
+
const trimmed = value.trim();
|
|
181
|
+
if (!trimmed)
|
|
182
|
+
continue;
|
|
183
|
+
if (isDirMarker(trimmed)) {
|
|
184
|
+
const cleaned = stripDirMarker(trimmed);
|
|
185
|
+
if (cleaned)
|
|
186
|
+
dirs.push(cleaned);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
files.push(trimmed);
|
|
190
|
+
}
|
|
191
|
+
return { files, dirs };
|
|
192
|
+
};
|
|
193
|
+
const hasFileContextJustification = (raw) => {
|
|
194
|
+
const notes = [...normalizeList(raw?.assumptions), ...normalizeList(raw?.docdexNotes)]
|
|
195
|
+
.map((item) => item.toLowerCase())
|
|
196
|
+
.join(" ");
|
|
197
|
+
if (!notes.trim())
|
|
198
|
+
return false;
|
|
199
|
+
const patterns = [
|
|
200
|
+
/\bno file(?:s)?\b/,
|
|
201
|
+
/\bmissing file(?:s)?\b/,
|
|
202
|
+
/\bunknown file(?:s)?\b/,
|
|
203
|
+
/\bfile context\b/,
|
|
204
|
+
/\binsufficient context\b/,
|
|
205
|
+
/\bnot enough context\b/,
|
|
206
|
+
/\bnot provided\b/,
|
|
207
|
+
/\bdocdex unavailable\b/,
|
|
208
|
+
/\bdocdex missing\b/,
|
|
209
|
+
/\bno matching docs?\b/,
|
|
210
|
+
/\bno matching documents?\b/,
|
|
211
|
+
/\bno results\b/,
|
|
212
|
+
];
|
|
213
|
+
return patterns.some((pattern) => pattern.test(notes));
|
|
214
|
+
};
|
|
215
|
+
const assessFileListCoverage = (raw) => {
|
|
216
|
+
const filesLikelyTouched = normalizeFileList(raw?.filesLikelyTouched);
|
|
217
|
+
const rawCreate = normalizeList(raw?.filesToCreate);
|
|
218
|
+
const splitCreate = splitFileAndDirEntries(rawCreate);
|
|
219
|
+
const filesToCreate = normalizeFileList(splitCreate.files);
|
|
220
|
+
const dirsToCreate = [...normalizeDirList(raw?.dirsToCreate), ...normalizeDirList(splitCreate.dirs)];
|
|
221
|
+
const empty = filesLikelyTouched.length === 0 && filesToCreate.length === 0 && dirsToCreate.length === 0;
|
|
222
|
+
return { empty, justified: empty && hasFileContextJustification(raw) };
|
|
223
|
+
};
|
|
224
|
+
const listMissingFields = (raw, options) => {
|
|
97
225
|
const missing = [];
|
|
98
226
|
const summary = normalizeTextField(raw?.summary);
|
|
99
227
|
const reasoningSummary = normalizeTextField(raw?.reasoningSummary);
|
|
@@ -102,7 +230,11 @@ const listMissingFields = (raw) => {
|
|
|
102
230
|
const understanding = normalizeTextField(raw?.understanding);
|
|
103
231
|
const plan = normalizeList(raw?.plan);
|
|
104
232
|
const filesLikelyTouched = normalizeFileList(raw?.filesLikelyTouched);
|
|
105
|
-
const
|
|
233
|
+
const rawCreate = normalizeList(raw?.filesToCreate);
|
|
234
|
+
const splitCreate = splitFileAndDirEntries(rawCreate);
|
|
235
|
+
const filesToCreate = normalizeFileList(splitCreate.files);
|
|
236
|
+
const dirsToCreate = [...normalizeDirList(raw?.dirsToCreate), ...normalizeDirList(splitCreate.dirs)];
|
|
237
|
+
const allowEmptyFiles = options?.allowEmptyFiles ?? false;
|
|
106
238
|
if (!summary)
|
|
107
239
|
missing.push("summary");
|
|
108
240
|
if (!reasoningSummary)
|
|
@@ -115,8 +247,9 @@ const listMissingFields = (raw) => {
|
|
|
115
247
|
missing.push("understanding");
|
|
116
248
|
if (plan.length === 0)
|
|
117
249
|
missing.push("plan");
|
|
118
|
-
if (filesLikelyTouched.length === 0 && filesToCreate.length === 0)
|
|
119
|
-
missing.push("
|
|
250
|
+
if (!allowEmptyFiles && filesLikelyTouched.length === 0 && filesToCreate.length === 0 && dirsToCreate.length === 0) {
|
|
251
|
+
missing.push("filesLikelyTouched", "filesToCreate", "dirsToCreate");
|
|
252
|
+
}
|
|
120
253
|
return missing;
|
|
121
254
|
};
|
|
122
255
|
const normalizeDiscipline = (value) => {
|
|
@@ -174,24 +307,33 @@ const scoreUsage = (discipline, bestUsage, capabilities) => {
|
|
|
174
307
|
score += 0.5;
|
|
175
308
|
return score;
|
|
176
309
|
};
|
|
310
|
+
const EXPLORATION_RATE = 0.1;
|
|
177
311
|
const DEFAULT_STATUS_FILTER = [
|
|
178
312
|
"not_started",
|
|
179
313
|
"in_progress",
|
|
180
|
-
|
|
181
|
-
"ready_to_review",
|
|
314
|
+
READY_TO_CODE_REVIEW,
|
|
182
315
|
"ready_to_qa",
|
|
183
316
|
"completed",
|
|
184
317
|
"cancelled",
|
|
185
318
|
"failed",
|
|
186
319
|
"skipped",
|
|
187
320
|
];
|
|
188
|
-
const summarizeDoc = (doc, index) => {
|
|
321
|
+
const summarizeDoc = (doc, index, warnings) => {
|
|
189
322
|
const title = doc.title ?? doc.path ?? doc.id ?? `doc-${index + 1}`;
|
|
190
323
|
const excerptSource = doc.segments?.[0]?.content ?? doc.content ?? "";
|
|
191
324
|
const excerpt = excerptSource ? (excerptSource.length > 480 ? `${excerptSource.slice(0, 480)}...` : excerptSource) : undefined;
|
|
325
|
+
const normalized = normalizeDocType({
|
|
326
|
+
docType: doc.docType,
|
|
327
|
+
path: doc.path,
|
|
328
|
+
title: doc.title,
|
|
329
|
+
content: excerptSource,
|
|
330
|
+
});
|
|
331
|
+
if (normalized.downgraded && warnings) {
|
|
332
|
+
warnings.push(`Docdex docType downgraded from SDS to DOC for ${doc.path ?? doc.title ?? doc.id}: ${normalized.reason ?? "not_sds"}`);
|
|
333
|
+
}
|
|
192
334
|
return {
|
|
193
335
|
id: doc.id ?? `doc-${index + 1}`,
|
|
194
|
-
docType:
|
|
336
|
+
docType: normalized.docType,
|
|
195
337
|
title,
|
|
196
338
|
path: doc.path,
|
|
197
339
|
excerpt,
|
|
@@ -210,6 +352,25 @@ const buildDocContext = (docs) => {
|
|
|
210
352
|
}),
|
|
211
353
|
].join("\n");
|
|
212
354
|
};
|
|
355
|
+
const PLACEHOLDER_DEPENDENCY_PATTERN = /^(?:t|task-?)\d+$/i;
|
|
356
|
+
const normalizeDependencies = (deps) => {
|
|
357
|
+
if (!deps?.length)
|
|
358
|
+
return undefined;
|
|
359
|
+
const seen = new Set();
|
|
360
|
+
const filtered = [];
|
|
361
|
+
for (const dep of deps) {
|
|
362
|
+
const trimmed = dep.trim();
|
|
363
|
+
if (!trimmed)
|
|
364
|
+
continue;
|
|
365
|
+
if (PLACEHOLDER_DEPENDENCY_PATTERN.test(trimmed))
|
|
366
|
+
continue;
|
|
367
|
+
if (seen.has(trimmed))
|
|
368
|
+
continue;
|
|
369
|
+
seen.add(trimmed);
|
|
370
|
+
filtered.push(trimmed);
|
|
371
|
+
}
|
|
372
|
+
return filtered.length ? filtered : undefined;
|
|
373
|
+
};
|
|
213
374
|
const buildTaskContext = (tasks) => {
|
|
214
375
|
if (tasks.length === 0)
|
|
215
376
|
return "Task context: (no task records found)";
|
|
@@ -240,9 +401,11 @@ export class GatewayAgentService {
|
|
|
240
401
|
const globalRepo = await GlobalRepository.create();
|
|
241
402
|
const agentService = new AgentService(globalRepo);
|
|
242
403
|
const routingService = await RoutingService.create();
|
|
404
|
+
const docdexRepoId = workspace.config?.docdexRepoId ?? process.env.MCODA_DOCDEX_REPO_ID ?? process.env.DOCDEX_REPO_ID;
|
|
243
405
|
const docdex = new DocdexClient({
|
|
244
406
|
workspaceRoot: workspace.workspaceRoot,
|
|
245
407
|
baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
|
|
408
|
+
repoId: docdexRepoId,
|
|
246
409
|
});
|
|
247
410
|
const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
|
|
248
411
|
const jobService = new JobService(workspace, workspaceRepo);
|
|
@@ -291,10 +454,31 @@ export class GatewayAgentService {
|
|
|
291
454
|
await maybeClose(this.deps.workspaceRepo);
|
|
292
455
|
await maybeClose(this.deps.routingService);
|
|
293
456
|
}
|
|
457
|
+
setDocdexAvailability(available, reason) {
|
|
458
|
+
if (available)
|
|
459
|
+
return;
|
|
460
|
+
const docdex = this.deps.docdex;
|
|
461
|
+
if (docdex && typeof docdex.disable === "function") {
|
|
462
|
+
docdex.disable(reason);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async saveRepoMemory(text) {
|
|
466
|
+
const docdex = this.deps.docdex;
|
|
467
|
+
if (!docdex || typeof docdex.memorySave !== "function")
|
|
468
|
+
return;
|
|
469
|
+
await docdex.memorySave(text);
|
|
470
|
+
}
|
|
471
|
+
async savePreference(category, content, agentId = "default") {
|
|
472
|
+
const docdex = this.deps.docdex;
|
|
473
|
+
if (!docdex || typeof docdex.savePreference !== "function")
|
|
474
|
+
return;
|
|
475
|
+
await docdex.savePreference(agentId, category, content);
|
|
476
|
+
}
|
|
294
477
|
async loadGatewayPrompts(agentId) {
|
|
295
478
|
const agentPrompts = "getPrompts" in this.deps.agentService ? await this.deps.agentService.getPrompts(agentId) : undefined;
|
|
296
|
-
const mcodaPromptPath = path.join(this.workspace.
|
|
479
|
+
const mcodaPromptPath = path.join(this.workspace.mcodaDir, "prompts", "gateway-agent.md");
|
|
297
480
|
const workspacePromptPath = path.join(this.workspace.workspaceRoot, "prompts", "gateway-agent.md");
|
|
481
|
+
const repoPromptPath = resolveRepoPromptPath("gateway-agent.md");
|
|
298
482
|
try {
|
|
299
483
|
await fs.promises.mkdir(path.dirname(mcodaPromptPath), { recursive: true });
|
|
300
484
|
await fs.promises.access(mcodaPromptPath);
|
|
@@ -305,7 +489,13 @@ export class GatewayAgentService {
|
|
|
305
489
|
await fs.promises.copyFile(workspacePromptPath, mcodaPromptPath);
|
|
306
490
|
}
|
|
307
491
|
catch {
|
|
308
|
-
|
|
492
|
+
try {
|
|
493
|
+
await fs.promises.access(repoPromptPath);
|
|
494
|
+
await fs.promises.copyFile(repoPromptPath, mcodaPromptPath);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
await fs.promises.writeFile(mcodaPromptPath, DEFAULT_GATEWAY_PROMPT, "utf8");
|
|
498
|
+
}
|
|
309
499
|
}
|
|
310
500
|
}
|
|
311
501
|
try {
|
|
@@ -319,7 +509,15 @@ export class GatewayAgentService {
|
|
|
319
509
|
}
|
|
320
510
|
}
|
|
321
511
|
catch {
|
|
322
|
-
|
|
512
|
+
try {
|
|
513
|
+
const repoPrompt = await fs.promises.readFile(repoPromptPath, "utf8");
|
|
514
|
+
if (hasRequiredPromptMarkers(repoPrompt)) {
|
|
515
|
+
nextPrompt = repoPrompt.trim();
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
/* ignore */
|
|
520
|
+
}
|
|
323
521
|
}
|
|
324
522
|
await fs.promises.writeFile(mcodaPromptPath, nextPrompt, "utf8");
|
|
325
523
|
}
|
|
@@ -327,7 +525,7 @@ export class GatewayAgentService {
|
|
|
327
525
|
catch {
|
|
328
526
|
/* ignore */
|
|
329
527
|
}
|
|
330
|
-
const commandPromptFiles = (await this.readPromptFiles([mcodaPromptPath, workspacePromptPath])).filter(hasRequiredPromptMarkers);
|
|
528
|
+
const commandPromptFiles = (await this.readPromptFiles([mcodaPromptPath, workspacePromptPath, repoPromptPath])).filter(hasRequiredPromptMarkers);
|
|
331
529
|
const mergedCommandPrompt = (() => {
|
|
332
530
|
const parts = [...commandPromptFiles];
|
|
333
531
|
const agentCommandPrompt = agentPrompts?.commandPrompts?.["gateway-agent"];
|
|
@@ -338,9 +536,11 @@ export class GatewayAgentService {
|
|
|
338
536
|
parts.push(DEFAULT_GATEWAY_PROMPT);
|
|
339
537
|
return parts.filter(Boolean).join("\n\n");
|
|
340
538
|
})();
|
|
539
|
+
const sanitizedJobPrompt = sanitizeGatewayPrompt(agentPrompts?.jobPrompt);
|
|
540
|
+
const sanitizedCharacterPrompt = sanitizeGatewayPrompt(agentPrompts?.characterPrompt);
|
|
341
541
|
return {
|
|
342
|
-
jobPrompt:
|
|
343
|
-
characterPrompt:
|
|
542
|
+
jobPrompt: sanitizedJobPrompt ?? DEFAULT_JOB_PROMPT,
|
|
543
|
+
characterPrompt: sanitizedCharacterPrompt ?? DEFAULT_CHARACTER_PROMPT,
|
|
344
544
|
commandPrompt: mergedCommandPrompt,
|
|
345
545
|
};
|
|
346
546
|
}
|
|
@@ -362,14 +562,15 @@ export class GatewayAgentService {
|
|
|
362
562
|
const caps = await this.deps.globalRepo.getAgentCapabilities(overrideAgent.id);
|
|
363
563
|
const missing = requiredCaps.filter((cap) => !caps.includes(cap));
|
|
364
564
|
const health = await this.deps.globalRepo.getAgentHealth(overrideAgent.id);
|
|
365
|
-
if (
|
|
366
|
-
|
|
565
|
+
if (health?.status === "unreachable") {
|
|
566
|
+
warnings.push(`Override agent ${overrideAgent.slug} is unreachable; ignoring override.`);
|
|
367
567
|
}
|
|
368
|
-
if (missing.length) {
|
|
369
|
-
|
|
568
|
+
else if (missing.length === 0) {
|
|
569
|
+
return overrideAgent;
|
|
370
570
|
}
|
|
371
|
-
else
|
|
372
|
-
warnings.push(`Override agent ${overrideAgent.slug} is
|
|
571
|
+
else {
|
|
572
|
+
warnings.push(`Override agent ${overrideAgent.slug} is missing gateway capabilities (${missing.join(", ")}); proceeding with override as requested.`);
|
|
573
|
+
return overrideAgent;
|
|
373
574
|
}
|
|
374
575
|
}
|
|
375
576
|
catch (overrideError) {
|
|
@@ -411,7 +612,7 @@ export class GatewayAgentService {
|
|
|
411
612
|
if (text && onChunk)
|
|
412
613
|
onChunk(text);
|
|
413
614
|
}
|
|
414
|
-
return { output, durationSeconds: (Date.now() - startedAt) / 1000 };
|
|
615
|
+
return { output: sanitizeAgentOutput(output), durationSeconds: (Date.now() - startedAt) / 1000 };
|
|
415
616
|
}
|
|
416
617
|
}
|
|
417
618
|
catch (error) {
|
|
@@ -424,7 +625,7 @@ export class GatewayAgentService {
|
|
|
424
625
|
input: prompt,
|
|
425
626
|
metadata: { command: "gateway-agent", job },
|
|
426
627
|
});
|
|
427
|
-
const output = response.output ?? "";
|
|
628
|
+
const output = sanitizeAgentOutput(response.output ?? "");
|
|
428
629
|
if (output && onChunk)
|
|
429
630
|
onChunk(output);
|
|
430
631
|
return { output, durationSeconds: (Date.now() - startedAt) / 1000 };
|
|
@@ -445,27 +646,81 @@ export class GatewayAgentService {
|
|
|
445
646
|
taskKeys: request.taskKeys,
|
|
446
647
|
statusFilter: request.statusFilter?.length ? request.statusFilter : DEFAULT_STATUS_FILTER,
|
|
447
648
|
limit,
|
|
649
|
+
ignoreDependencies: request.taskKeys && request.taskKeys.length > 0 ? true : request.ignoreDependencies,
|
|
448
650
|
};
|
|
449
651
|
const selection = await this.taskSelectionService.selectTasks(filters);
|
|
450
652
|
if (selection.warnings.length)
|
|
451
653
|
warnings.push(...selection.warnings);
|
|
452
|
-
const combined = [...selection.ordered
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
654
|
+
const combined = [...selection.ordered];
|
|
655
|
+
if (combined.length) {
|
|
656
|
+
return combined.slice(0, limit).map((entry) => ({
|
|
657
|
+
key: entry.task.key,
|
|
658
|
+
title: entry.task.title,
|
|
659
|
+
description: entry.task.description ?? undefined,
|
|
660
|
+
status: entry.task.status,
|
|
661
|
+
storyPoints: entry.task.storyPoints ?? undefined,
|
|
662
|
+
storyKey: entry.task.storyKey,
|
|
663
|
+
storyTitle: entry.task.storyTitle,
|
|
664
|
+
epicKey: entry.task.epicKey,
|
|
665
|
+
epicTitle: entry.task.epicTitle,
|
|
666
|
+
acceptanceCriteria: entry.task.acceptanceCriteria,
|
|
667
|
+
dependencies: normalizeDependencies(entry.dependencies.keys),
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
670
|
+
if (!request.taskKeys?.length)
|
|
671
|
+
return [];
|
|
672
|
+
warnings.push("Gateway task selection returned no tasks; falling back to direct task lookup.");
|
|
673
|
+
const tasks = await Promise.all(request.taskKeys.map((key) => this.deps.workspaceRepo.getTaskByKey(key)));
|
|
674
|
+
const found = tasks.filter((task) => Boolean(task));
|
|
675
|
+
const missing = request.taskKeys.filter((key, index) => !tasks[index]);
|
|
676
|
+
if (missing.length) {
|
|
677
|
+
warnings.push(`Fallback task lookup missing keys: ${missing.join(", ")}`);
|
|
678
|
+
}
|
|
679
|
+
if (!found.length)
|
|
680
|
+
return [];
|
|
681
|
+
let withRelations = [];
|
|
682
|
+
try {
|
|
683
|
+
const related = await this.deps.workspaceRepo.getTasksWithRelations(found.map((task) => task.id));
|
|
684
|
+
const relatedById = new Map(related.map((task) => [task.id, task]));
|
|
685
|
+
withRelations = found.map((task) => relatedById.get(task.id) ?? task);
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
withRelations = found;
|
|
689
|
+
}
|
|
690
|
+
return withRelations.slice(0, limit).map((task) => ({
|
|
691
|
+
key: task.key,
|
|
692
|
+
title: task.title,
|
|
693
|
+
description: task.description ?? undefined,
|
|
694
|
+
status: task.status,
|
|
695
|
+
storyPoints: task.storyPoints ?? undefined,
|
|
696
|
+
storyKey: task.storyKey,
|
|
697
|
+
storyTitle: task.storyTitle,
|
|
698
|
+
epicKey: task.epicKey,
|
|
699
|
+
epicTitle: task.epicTitle,
|
|
700
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
701
|
+
dependencies: undefined,
|
|
465
702
|
}));
|
|
466
703
|
}
|
|
467
704
|
catch (error) {
|
|
468
705
|
warnings.push(`Task lookup failed: ${error.message}`);
|
|
706
|
+
if (request.taskKeys?.length) {
|
|
707
|
+
warnings.push("Task lookup failed; attempting direct task fallback.");
|
|
708
|
+
try {
|
|
709
|
+
const tasks = await Promise.all(request.taskKeys.map((key) => this.deps.workspaceRepo.getTaskByKey(key)));
|
|
710
|
+
const found = tasks.filter((task) => Boolean(task));
|
|
711
|
+
return found.map((task) => ({
|
|
712
|
+
key: task.key,
|
|
713
|
+
title: task.title,
|
|
714
|
+
description: task.description ?? undefined,
|
|
715
|
+
status: task.status,
|
|
716
|
+
storyPoints: task.storyPoints ?? undefined,
|
|
717
|
+
dependencies: undefined,
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
723
|
+
}
|
|
469
724
|
return [];
|
|
470
725
|
}
|
|
471
726
|
}
|
|
@@ -509,22 +764,59 @@ export class GatewayAgentService {
|
|
|
509
764
|
const maxDocs = request.maxDocs ?? 4;
|
|
510
765
|
if (maxDocs <= 0)
|
|
511
766
|
return [];
|
|
767
|
+
if (typeof this.deps.docdex?.ensureRepoScope === "function") {
|
|
768
|
+
try {
|
|
769
|
+
await this.deps.docdex.ensureRepoScope();
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
warnings.push(`Docdex scope missing: ${error.message}`);
|
|
773
|
+
return [];
|
|
774
|
+
}
|
|
775
|
+
}
|
|
512
776
|
const docTypes = this.pickDocTypes(request.job, request.inputText);
|
|
513
777
|
const query = this.buildQuerySeed(tasks, request.inputText);
|
|
514
778
|
const summaries = [];
|
|
779
|
+
let openApiIncluded = false;
|
|
780
|
+
let reindexed = false;
|
|
515
781
|
for (const docType of docTypes) {
|
|
516
782
|
if (summaries.length >= maxDocs)
|
|
517
783
|
break;
|
|
518
784
|
try {
|
|
519
|
-
|
|
785
|
+
let docs = await this.deps.docdex.search({
|
|
520
786
|
projectKey: request.projectKey,
|
|
521
787
|
docType,
|
|
522
788
|
query,
|
|
523
789
|
});
|
|
790
|
+
if (!docs.length && !reindexed && typeof this.deps.docdex.reindex === "function") {
|
|
791
|
+
reindexed = true;
|
|
792
|
+
try {
|
|
793
|
+
warnings.push("Docdex search returned no results; reindexing and retrying.");
|
|
794
|
+
await this.deps.docdex.reindex();
|
|
795
|
+
docs = await this.deps.docdex.search({
|
|
796
|
+
projectKey: request.projectKey,
|
|
797
|
+
docType,
|
|
798
|
+
query,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
warnings.push(`Docdex reindex failed: ${error.message}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
524
805
|
for (const doc of docs) {
|
|
525
806
|
if (summaries.length >= maxDocs)
|
|
526
807
|
break;
|
|
527
|
-
|
|
808
|
+
const ref = doc.path ?? doc.title ?? doc.id;
|
|
809
|
+
if (isDocContextExcluded(ref, false)) {
|
|
810
|
+
warnings.push(`Docdex doc filtered from gateway context: ${ref}`);
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
const summary = summarizeDoc(doc, summaries.length, warnings);
|
|
814
|
+
if (summary.docType === "OPENAPI") {
|
|
815
|
+
if (openApiIncluded)
|
|
816
|
+
continue;
|
|
817
|
+
openApiIncluded = true;
|
|
818
|
+
}
|
|
819
|
+
summaries.push(summary);
|
|
528
820
|
}
|
|
529
821
|
}
|
|
530
822
|
catch (error) {
|
|
@@ -547,7 +839,10 @@ export class GatewayAgentService {
|
|
|
547
839
|
const understanding = normalizeTextField(raw?.understanding) ?? "";
|
|
548
840
|
const plan = normalizeList(raw?.plan);
|
|
549
841
|
const filesLikelyTouched = normalizeFileList(raw?.filesLikelyTouched);
|
|
550
|
-
const
|
|
842
|
+
const rawCreate = normalizeList(raw?.filesToCreate);
|
|
843
|
+
const splitCreate = splitFileAndDirEntries(rawCreate);
|
|
844
|
+
const filesToCreate = normalizeFileList(splitCreate.files);
|
|
845
|
+
const dirsToCreate = [...normalizeDirList(raw?.dirsToCreate), ...normalizeDirList(splitCreate.dirs)];
|
|
551
846
|
const complexityRaw = Number(raw?.complexity);
|
|
552
847
|
const complexity = Number.isFinite(complexityRaw) ? clamp(Math.round(complexityRaw), 1, 10) : 5;
|
|
553
848
|
const discipline = normalizeDiscipline(typeof raw?.discipline === "string" ? raw.discipline : undefined) ??
|
|
@@ -577,6 +872,7 @@ export class GatewayAgentService {
|
|
|
577
872
|
discipline,
|
|
578
873
|
filesLikelyTouched,
|
|
579
874
|
filesToCreate,
|
|
875
|
+
dirsToCreate,
|
|
580
876
|
assumptions: normalizeList(raw?.assumptions),
|
|
581
877
|
risks: normalizeList(raw?.risks),
|
|
582
878
|
docdexNotes: normalizeList(raw?.docdexNotes),
|
|
@@ -592,6 +888,36 @@ export class GatewayAgentService {
|
|
|
592
888
|
const isInside = (relative) => !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
593
889
|
const touched = [];
|
|
594
890
|
const created = [];
|
|
891
|
+
const dirs = [];
|
|
892
|
+
const dirSet = new Set();
|
|
893
|
+
for (const dir of analysis.dirsToCreate ?? []) {
|
|
894
|
+
const { relative, resolved } = normalize(dir);
|
|
895
|
+
if (!isInside(relative)) {
|
|
896
|
+
warnings.push(`Gateway directory path outside workspace ignored: ${dir}`);
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
try {
|
|
900
|
+
const stat = await fs.promises.stat(resolved);
|
|
901
|
+
if (stat.isDirectory()) {
|
|
902
|
+
const normalized = relative.replace(/\\/g, "/");
|
|
903
|
+
if (!dirSet.has(normalized)) {
|
|
904
|
+
dirSet.add(normalized);
|
|
905
|
+
dirs.push(normalized);
|
|
906
|
+
}
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
warnings.push(`Gateway directory path is not a directory: ${dir}`);
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
const normalized = relative.replace(/\\/g, "/");
|
|
914
|
+
if (!dirSet.has(normalized)) {
|
|
915
|
+
dirSet.add(normalized);
|
|
916
|
+
dirs.push(normalized);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const dirPrefixes = Array.from(dirSet).map((dir) => (dir.endsWith("/") ? dir : `${dir}/`));
|
|
595
921
|
for (const file of analysis.filesLikelyTouched) {
|
|
596
922
|
const { relative, resolved } = normalize(file);
|
|
597
923
|
if (!isInside(relative)) {
|
|
@@ -626,8 +952,14 @@ export class GatewayAgentService {
|
|
|
626
952
|
}
|
|
627
953
|
}
|
|
628
954
|
catch {
|
|
629
|
-
|
|
630
|
-
|
|
955
|
+
const parentRelative = path.relative(root, parent).replace(/\\/g, "/");
|
|
956
|
+
const parentAllowed = parentRelative === "" ||
|
|
957
|
+
dirSet.has(parentRelative) ||
|
|
958
|
+
dirPrefixes.some((prefix) => parentRelative.startsWith(prefix));
|
|
959
|
+
if (!parentAllowed) {
|
|
960
|
+
warnings.push(`Gateway create path parent does not exist: ${file}`);
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
631
963
|
}
|
|
632
964
|
try {
|
|
633
965
|
const stat = await fs.promises.stat(resolved);
|
|
@@ -646,17 +978,22 @@ export class GatewayAgentService {
|
|
|
646
978
|
...analysis,
|
|
647
979
|
filesLikelyTouched: touched,
|
|
648
980
|
filesToCreate: created,
|
|
981
|
+
dirsToCreate: dirs,
|
|
649
982
|
};
|
|
650
983
|
}
|
|
651
|
-
async listCandidates(requiredCaps, discipline) {
|
|
984
|
+
async listCandidates(requiredCaps, discipline, avoidAgents = []) {
|
|
652
985
|
const agents = await this.deps.globalRepo.listAgents();
|
|
653
986
|
if (agents.length === 0) {
|
|
654
987
|
throw new Error("No agents available; register one with mcoda agent add");
|
|
655
988
|
}
|
|
989
|
+
const avoidSet = new Set(avoidAgents.map((value) => value.toLowerCase()));
|
|
656
990
|
const health = await this.deps.globalRepo.listAgentHealthSummary();
|
|
657
991
|
const healthById = new Map(health.map((row) => [row.agentId, row]));
|
|
658
992
|
const candidates = [];
|
|
659
993
|
for (const agent of agents) {
|
|
994
|
+
const slug = agent.slug ?? agent.id;
|
|
995
|
+
if (avoidSet.has(agent.id.toLowerCase()) || avoidSet.has(slug.toLowerCase()))
|
|
996
|
+
continue;
|
|
660
997
|
const capabilities = await this.deps.globalRepo.getAgentCapabilities(agent.id);
|
|
661
998
|
const missing = requiredCaps.filter((cap) => !capabilities.includes(cap));
|
|
662
999
|
if (missing.length)
|
|
@@ -672,6 +1009,9 @@ export class GatewayAgentService {
|
|
|
672
1009
|
const usageScore = scoreUsage(discipline, agent.bestUsage, capabilities);
|
|
673
1010
|
const cost = agent.costPerMillion ?? Number.POSITIVE_INFINITY;
|
|
674
1011
|
const adjustedQuality = healthEntry?.status === "degraded" ? quality - 0.5 : quality;
|
|
1012
|
+
const maxComplexity = typeof agent.maxComplexity === "number" && Number.isFinite(agent.maxComplexity)
|
|
1013
|
+
? clamp(Math.round(agent.maxComplexity), 1, 10)
|
|
1014
|
+
: 5;
|
|
675
1015
|
candidates.push({
|
|
676
1016
|
agent,
|
|
677
1017
|
capabilities,
|
|
@@ -680,6 +1020,7 @@ export class GatewayAgentService {
|
|
|
680
1020
|
reasoning,
|
|
681
1021
|
usageScore,
|
|
682
1022
|
cost,
|
|
1023
|
+
maxComplexity,
|
|
683
1024
|
});
|
|
684
1025
|
}
|
|
685
1026
|
return candidates;
|
|
@@ -688,10 +1029,42 @@ export class GatewayAgentService {
|
|
|
688
1029
|
if (candidates.length === 0) {
|
|
689
1030
|
throw new Error("No eligible agents available for this job");
|
|
690
1031
|
}
|
|
691
|
-
const
|
|
1032
|
+
const normalizedComplexity = clamp(Math.round(complexity), 1, 10);
|
|
1033
|
+
const eligible = candidates.filter((c) => c.maxComplexity >= normalizedComplexity);
|
|
1034
|
+
let pool = eligible;
|
|
1035
|
+
let gatingNote = "";
|
|
1036
|
+
if (!eligible.length) {
|
|
1037
|
+
const fallback = normalizedComplexity > 1
|
|
1038
|
+
? candidates.filter((c) => c.maxComplexity >= normalizedComplexity - 1)
|
|
1039
|
+
: candidates;
|
|
1040
|
+
pool = fallback.length ? fallback : candidates;
|
|
1041
|
+
gatingNote = fallback.length
|
|
1042
|
+
? ` No agents meet max complexity ${normalizedComplexity}; allowing ${normalizedComplexity - 1} fallback.`
|
|
1043
|
+
: ` No agents meet max complexity ${normalizedComplexity}; using best available.`;
|
|
1044
|
+
}
|
|
1045
|
+
if (Math.random() < EXPLORATION_RATE) {
|
|
1046
|
+
const stretchPool = normalizedComplexity > 1
|
|
1047
|
+
? candidates.filter((c) => c.maxComplexity < normalizedComplexity && c.maxComplexity >= normalizedComplexity - 1)
|
|
1048
|
+
: [];
|
|
1049
|
+
const allowRedemption = normalizedComplexity <= 4;
|
|
1050
|
+
const sortedByQuality = pool.slice().sort((a, b) => a.quality - b.quality);
|
|
1051
|
+
const redemptionPool = allowRedemption ? sortedByQuality.slice(0, Math.max(1, Math.ceil(pool.length * 0.2))) : [];
|
|
1052
|
+
const canUseStretch = stretchPool.length > 0;
|
|
1053
|
+
const canUseRedemption = redemptionPool.length > 0;
|
|
1054
|
+
if (canUseStretch || canUseRedemption) {
|
|
1055
|
+
const useStretch = canUseStretch && (!canUseRedemption || Math.random() < 0.5);
|
|
1056
|
+
const explorePool = useStretch ? stretchPool : redemptionPool;
|
|
1057
|
+
const pick = explorePool[Math.floor(Math.random() * explorePool.length)];
|
|
1058
|
+
const rationale = useStretch
|
|
1059
|
+
? `Exploration: stretching an agent (max complexity ${pick.maxComplexity}) for task complexity ${normalizedComplexity}/10.${gatingNote}`
|
|
1060
|
+
: `Exploration: redemption run for a lower-rated agent to reassess performance.${gatingNote}`;
|
|
1061
|
+
return { pick, rationale };
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const sortedQuality = pool.map((c) => c.quality);
|
|
692
1065
|
const maxQuality = Math.max(...sortedQuality);
|
|
693
|
-
if (
|
|
694
|
-
const pick =
|
|
1066
|
+
if (normalizedComplexity >= 9) {
|
|
1067
|
+
const pick = pool
|
|
695
1068
|
.slice()
|
|
696
1069
|
.sort((a, b) => {
|
|
697
1070
|
if (b.quality !== a.quality)
|
|
@@ -704,12 +1077,12 @@ export class GatewayAgentService {
|
|
|
704
1077
|
})[0];
|
|
705
1078
|
return {
|
|
706
1079
|
pick,
|
|
707
|
-
rationale: `Complexity ${
|
|
1080
|
+
rationale: `Complexity ${normalizedComplexity}/10 requires the highest capability; selected top-rated agent with best fit for ${discipline}.${gatingNote}`,
|
|
708
1081
|
};
|
|
709
1082
|
}
|
|
710
|
-
if (
|
|
711
|
-
const
|
|
712
|
-
const pick = (
|
|
1083
|
+
if (normalizedComplexity >= 8) {
|
|
1084
|
+
const qualityPool = pool.filter((c) => c.quality >= maxQuality - 1);
|
|
1085
|
+
const pick = (qualityPool.length ? qualityPool : pool)
|
|
713
1086
|
.slice()
|
|
714
1087
|
.sort((a, b) => {
|
|
715
1088
|
if (b.usageScore !== a.usageScore)
|
|
@@ -720,12 +1093,12 @@ export class GatewayAgentService {
|
|
|
720
1093
|
})[0];
|
|
721
1094
|
return {
|
|
722
1095
|
pick,
|
|
723
|
-
rationale: `Complexity ${
|
|
1096
|
+
rationale: `Complexity ${normalizedComplexity}/10 favors strong agents with good cost/fit balance; selected best-fit candidate.${gatingNote}`,
|
|
724
1097
|
};
|
|
725
1098
|
}
|
|
726
|
-
const target =
|
|
727
|
-
const
|
|
728
|
-
const base =
|
|
1099
|
+
const target = normalizedComplexity;
|
|
1100
|
+
const qualityPool = pool.filter((c) => c.quality >= target);
|
|
1101
|
+
const base = qualityPool.length ? qualityPool : pool;
|
|
729
1102
|
const pick = base
|
|
730
1103
|
.slice()
|
|
731
1104
|
.sort((a, b) => {
|
|
@@ -741,14 +1114,49 @@ export class GatewayAgentService {
|
|
|
741
1114
|
})[0];
|
|
742
1115
|
return {
|
|
743
1116
|
pick,
|
|
744
|
-
rationale: `Complexity ${
|
|
1117
|
+
rationale: `Complexity ${normalizedComplexity}/10 targets a comparable tier agent; selected closest match with discipline fit and cost awareness.${gatingNote}`,
|
|
745
1118
|
};
|
|
746
1119
|
}
|
|
747
|
-
async selectAgentForJob(job, analysis) {
|
|
1120
|
+
async selectAgentForJob(job, analysis, avoidAgents = [], forceStronger = false, forceTier, warnings = []) {
|
|
748
1121
|
const normalizedJob = canonicalizeCommandName(job);
|
|
749
1122
|
const requiredCaps = getCommandRequiredCapabilities(normalizedJob);
|
|
750
|
-
|
|
751
|
-
|
|
1123
|
+
let candidates = await this.listCandidates(requiredCaps, analysis.discipline, avoidAgents);
|
|
1124
|
+
if (!candidates.length && avoidAgents.length) {
|
|
1125
|
+
const fallback = await this.listCandidates(requiredCaps, analysis.discipline, []);
|
|
1126
|
+
if (fallback.length) {
|
|
1127
|
+
candidates = fallback;
|
|
1128
|
+
warnings.push("Avoid list removed all eligible agents; reusing available agent.");
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const baseComplexity = Number.isFinite(analysis.complexity)
|
|
1132
|
+
? clamp(Math.round(analysis.complexity), 1, 10)
|
|
1133
|
+
: 5;
|
|
1134
|
+
const forcedMin = forceTier === "specialist"
|
|
1135
|
+
? SPECIALIST_TIER_MIN_COMPLEXITY
|
|
1136
|
+
: forceTier === "strong"
|
|
1137
|
+
? STRONG_TIER_MIN_COMPLEXITY
|
|
1138
|
+
: undefined;
|
|
1139
|
+
let boostedComplexity = baseComplexity;
|
|
1140
|
+
if (typeof forcedMin === "number") {
|
|
1141
|
+
boostedComplexity = Math.max(baseComplexity, forcedMin);
|
|
1142
|
+
}
|
|
1143
|
+
else if (forceStronger) {
|
|
1144
|
+
if (baseComplexity < STRONG_TIER_MIN_COMPLEXITY) {
|
|
1145
|
+
boostedComplexity = STRONG_TIER_MIN_COMPLEXITY;
|
|
1146
|
+
}
|
|
1147
|
+
else if (baseComplexity < SPECIALIST_TIER_MIN_COMPLEXITY) {
|
|
1148
|
+
boostedComplexity = SPECIALIST_TIER_MIN_COMPLEXITY;
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
boostedComplexity = clamp(baseComplexity + 1, 1, 10);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const { pick, rationale } = this.chooseCandidate(candidates, boostedComplexity, analysis.discipline);
|
|
1155
|
+
const finalRationale = forceTier
|
|
1156
|
+
? `${rationale} (force_tier:${forceTier})`
|
|
1157
|
+
: forceStronger
|
|
1158
|
+
? `${rationale} (force_stronger applied)`
|
|
1159
|
+
: rationale;
|
|
752
1160
|
return {
|
|
753
1161
|
agentId: pick.agent.id,
|
|
754
1162
|
agentSlug: pick.agent.slug ?? pick.agent.id,
|
|
@@ -756,9 +1164,26 @@ export class GatewayAgentService {
|
|
|
756
1164
|
reasoningRating: pick.agent.reasoningRating ?? undefined,
|
|
757
1165
|
bestUsage: pick.agent.bestUsage ?? undefined,
|
|
758
1166
|
costPerMillion: Number.isFinite(pick.cost) ? pick.cost : undefined,
|
|
759
|
-
rationale,
|
|
1167
|
+
rationale: finalRationale,
|
|
760
1168
|
};
|
|
761
1169
|
}
|
|
1170
|
+
async preflightExecutionAgents(job, overrideAgent) {
|
|
1171
|
+
const normalizedJob = canonicalizeCommandName(job);
|
|
1172
|
+
const requiredCaps = getCommandRequiredCapabilities(normalizedJob);
|
|
1173
|
+
if (overrideAgent) {
|
|
1174
|
+
const resolved = await this.deps.agentService.resolveAgent(overrideAgent);
|
|
1175
|
+
const capabilities = await this.deps.globalRepo.getAgentCapabilities(resolved.id);
|
|
1176
|
+
const missing = requiredCaps.filter((cap) => !capabilities.includes(cap));
|
|
1177
|
+
if (missing.length) {
|
|
1178
|
+
throw new Error(`Agent ${overrideAgent} is missing required capabilities for ${normalizedJob}: ${missing.join(", ")}`);
|
|
1179
|
+
}
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const candidates = await this.listCandidates(requiredCaps, "other");
|
|
1183
|
+
if (!candidates.length) {
|
|
1184
|
+
throw new Error(`No eligible execution agents available for ${normalizedJob}.`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
762
1187
|
async run(request) {
|
|
763
1188
|
const warnings = [];
|
|
764
1189
|
const normalizedJob = canonicalizeCommandName(request.job);
|
|
@@ -776,7 +1201,7 @@ export class GatewayAgentService {
|
|
|
776
1201
|
]
|
|
777
1202
|
.filter(Boolean)
|
|
778
1203
|
.join("\n\n");
|
|
779
|
-
const recordUsage = async (promptText, outputText, durationSeconds, action) => {
|
|
1204
|
+
const recordUsage = async (promptText, outputText, durationSeconds, action, attempt) => {
|
|
780
1205
|
const promptTokens = estimateTokens(promptText);
|
|
781
1206
|
const completionTokens = estimateTokens(outputText ?? "");
|
|
782
1207
|
await this.deps.jobService.recordTokenUsage({
|
|
@@ -792,20 +1217,24 @@ export class GatewayAgentService {
|
|
|
792
1217
|
tokensCompletion: completionTokens,
|
|
793
1218
|
tokensTotal: promptTokens + completionTokens,
|
|
794
1219
|
durationSeconds,
|
|
795
|
-
metadata: { action, job: normalizedJob },
|
|
1220
|
+
metadata: { action, job: normalizedJob, phase: action, attempt },
|
|
796
1221
|
});
|
|
797
1222
|
};
|
|
798
1223
|
const response = await this.invokeGatewayAgent(gatewayAgent, prompt, normalizedJob, {
|
|
799
1224
|
stream: request.agentStream !== false,
|
|
800
1225
|
onChunk: request.onStreamChunk,
|
|
801
1226
|
});
|
|
802
|
-
await recordUsage(prompt, response.output ?? "", response.durationSeconds, "gateway_summary");
|
|
803
|
-
|
|
1227
|
+
await recordUsage(prompt, response.output ?? "", response.durationSeconds, "gateway_summary", 1);
|
|
1228
|
+
const jsonResult = extractJsonOnly(response.output);
|
|
1229
|
+
let parsed = jsonResult.payload;
|
|
1230
|
+
let fileListCoverage = parsed ? assessFileListCoverage(parsed) : { empty: false, justified: false };
|
|
804
1231
|
let missingFields = parsed
|
|
805
|
-
? listMissingFields(parsed)
|
|
806
|
-
: ["summary", "reasoningSummary", "currentState", "todo", "understanding", "plan", "
|
|
1232
|
+
? listMissingFields(parsed, { allowEmptyFiles: true })
|
|
1233
|
+
: ["summary", "reasoningSummary", "currentState", "todo", "understanding", "plan", "filesLikelyTouched", "filesToCreate", "dirsToCreate"];
|
|
807
1234
|
if (!parsed) {
|
|
808
|
-
warnings.push(
|
|
1235
|
+
warnings.push(jsonResult.jsonOnly
|
|
1236
|
+
? "Gateway analysis response was invalid JSON."
|
|
1237
|
+
: "Gateway analysis response was not JSON-only.");
|
|
809
1238
|
}
|
|
810
1239
|
if (missingFields.length) {
|
|
811
1240
|
const repairPrompt = [
|
|
@@ -813,7 +1242,8 @@ export class GatewayAgentService {
|
|
|
813
1242
|
"",
|
|
814
1243
|
"Your previous response was incomplete or invalid. Return JSON only with the exact schema.",
|
|
815
1244
|
`Missing fields: ${missingFields.join(", ")}.`,
|
|
816
|
-
"Ensure reasoningSummary, currentState, todo, understanding,
|
|
1245
|
+
"Ensure reasoningSummary, currentState, todo, understanding, and plan are populated.",
|
|
1246
|
+
"If file paths are unknown, leave filesLikelyTouched/filesToCreate/dirsToCreate empty and explain the gap in assumptions or docdexNotes.",
|
|
817
1247
|
"Use real file paths only (no placeholders like (unknown), TBD, or glob patterns).",
|
|
818
1248
|
"If docdex returned no results, say so in docdexNotes.",
|
|
819
1249
|
].join("\n");
|
|
@@ -824,18 +1254,29 @@ export class GatewayAgentService {
|
|
|
824
1254
|
stream: request.agentStream !== false,
|
|
825
1255
|
onChunk: request.onStreamChunk,
|
|
826
1256
|
});
|
|
827
|
-
await recordUsage(repairPrompt, repairResponse.output ?? "", repairResponse.durationSeconds, "gateway_summary_repair");
|
|
828
|
-
const repaired =
|
|
829
|
-
if (repaired) {
|
|
830
|
-
parsed = repaired;
|
|
831
|
-
|
|
1257
|
+
await recordUsage(repairPrompt, repairResponse.output ?? "", repairResponse.durationSeconds, "gateway_summary_repair", 2);
|
|
1258
|
+
const repaired = extractJsonOnly(repairResponse.output);
|
|
1259
|
+
if (repaired.payload) {
|
|
1260
|
+
parsed = repaired.payload;
|
|
1261
|
+
fileListCoverage = assessFileListCoverage(parsed);
|
|
1262
|
+
missingFields = listMissingFields(parsed, { allowEmptyFiles: true });
|
|
832
1263
|
}
|
|
833
1264
|
else {
|
|
834
|
-
warnings.push(
|
|
1265
|
+
warnings.push(repaired.jsonOnly
|
|
1266
|
+
? "Gateway repair response was invalid JSON."
|
|
1267
|
+
: "Gateway repair response was not JSON-only.");
|
|
835
1268
|
}
|
|
836
1269
|
}
|
|
837
1270
|
if (missingFields.length) {
|
|
838
|
-
|
|
1271
|
+
throw new Error(`Gateway analysis missing required fields: ${missingFields.join(", ")}.`);
|
|
1272
|
+
}
|
|
1273
|
+
if (!parsed) {
|
|
1274
|
+
throw new Error(jsonResult.jsonOnly
|
|
1275
|
+
? "Gateway analysis response was invalid JSON."
|
|
1276
|
+
: "Gateway analysis response was not JSON-only.");
|
|
1277
|
+
}
|
|
1278
|
+
if (fileListCoverage.empty && !fileListCoverage.justified) {
|
|
1279
|
+
warnings.push("Gateway analysis returned no file paths; proceeding without file context.");
|
|
839
1280
|
}
|
|
840
1281
|
let analysis = this.normalizeAnalysis(parsed ?? {}, normalizedJob, tasks, request.inputText);
|
|
841
1282
|
if (analysis.docdexNotes.length === 0) {
|
|
@@ -849,7 +1290,7 @@ export class GatewayAgentService {
|
|
|
849
1290
|
analysis.docdexNotes.push(...docdexWarnings);
|
|
850
1291
|
}
|
|
851
1292
|
analysis = await this.validateFilePlan(analysis, warnings);
|
|
852
|
-
const chosenAgent = await this.selectAgentForJob(normalizedJob, analysis);
|
|
1293
|
+
const chosenAgent = await this.selectAgentForJob(normalizedJob, analysis, request.avoidAgents ?? [], request.forceStronger ?? false, request.forceTier, warnings);
|
|
853
1294
|
await this.deps.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
854
1295
|
return {
|
|
855
1296
|
commandRunId: commandRun.id,
|