@mcoda/codali 0.1.108 → 0.1.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/agents/AgentInventory.d.ts +56 -0
  2. package/dist/agents/AgentInventory.d.ts.map +1 -0
  3. package/dist/agents/AgentInventory.js +131 -0
  4. package/dist/agents/RoleResolution.d.ts +49 -0
  5. package/dist/agents/RoleResolution.d.ts.map +1 -0
  6. package/dist/agents/RoleResolution.js +166 -0
  7. package/dist/api/ChatCompletionsAdapter.d.ts +76 -0
  8. package/dist/api/ChatCompletionsAdapter.d.ts.map +1 -0
  9. package/dist/api/ChatCompletionsAdapter.js +54 -0
  10. package/dist/api/CodaliApi.d.ts +78 -0
  11. package/dist/api/CodaliApi.d.ts.map +1 -0
  12. package/dist/api/CodaliApi.js +273 -0
  13. package/dist/api/CodaliServer.d.ts +42 -0
  14. package/dist/api/CodaliServer.d.ts.map +1 -0
  15. package/dist/api/CodaliServer.js +118 -0
  16. package/dist/cli/AskCommand.d.ts +66 -0
  17. package/dist/cli/AskCommand.d.ts.map +1 -0
  18. package/dist/cli/AskCommand.js +536 -0
  19. package/dist/cli/AuthCommand.d.ts +19 -0
  20. package/dist/cli/AuthCommand.d.ts.map +1 -0
  21. package/dist/cli/AuthCommand.js +161 -0
  22. package/dist/cli/ServeCommand.d.ts +5 -0
  23. package/dist/cli/ServeCommand.d.ts.map +1 -0
  24. package/dist/cli/ServeCommand.js +87 -0
  25. package/dist/cli/ToolsCommand.d.ts +22 -0
  26. package/dist/cli/ToolsCommand.d.ts.map +1 -0
  27. package/dist/cli/ToolsCommand.js +208 -0
  28. package/dist/cli.d.ts.map +1 -1
  29. package/dist/cli.js +29 -1
  30. package/dist/connectors/http/HttpToolDefinition.d.ts +74 -0
  31. package/dist/connectors/http/HttpToolDefinition.d.ts.map +1 -0
  32. package/dist/connectors/http/HttpToolDefinition.js +190 -0
  33. package/dist/connectors/http/HttpToolSource.d.ts +18 -0
  34. package/dist/connectors/http/HttpToolSource.d.ts.map +1 -0
  35. package/dist/connectors/http/HttpToolSource.js +29 -0
  36. package/dist/connectors/mcp/McpClient.d.ts +132 -0
  37. package/dist/connectors/mcp/McpClient.d.ts.map +1 -0
  38. package/dist/connectors/mcp/McpClient.js +215 -0
  39. package/dist/connectors/mcp/McpServerRegistry.d.ts +75 -0
  40. package/dist/connectors/mcp/McpServerRegistry.d.ts.map +1 -0
  41. package/dist/connectors/mcp/McpServerRegistry.js +180 -0
  42. package/dist/connectors/mcp/McpToolAdapter.d.ts +48 -0
  43. package/dist/connectors/mcp/McpToolAdapter.d.ts.map +1 -0
  44. package/dist/connectors/mcp/McpToolAdapter.js +101 -0
  45. package/dist/connectors/mcp/McpToolSource.d.ts +27 -0
  46. package/dist/connectors/mcp/McpToolSource.d.ts.map +1 -0
  47. package/dist/connectors/mcp/McpToolSource.js +33 -0
  48. package/dist/connectors/media/ImageGenerationTool.d.ts +43 -0
  49. package/dist/connectors/media/ImageGenerationTool.d.ts.map +1 -0
  50. package/dist/connectors/media/ImageGenerationTool.js +122 -0
  51. package/dist/connectors/oauth/DeviceCodeAuth.d.ts +74 -0
  52. package/dist/connectors/oauth/DeviceCodeAuth.d.ts.map +1 -0
  53. package/dist/connectors/oauth/DeviceCodeAuth.js +153 -0
  54. package/dist/docdex/DocdexClient.d.ts +2 -0
  55. package/dist/docdex/DocdexClient.d.ts.map +1 -1
  56. package/dist/docdex/DocdexClient.js +4 -0
  57. package/dist/gateway/CodaliGateway.d.ts +51 -3
  58. package/dist/gateway/CodaliGateway.d.ts.map +1 -1
  59. package/dist/gateway/CodaliGateway.js +226 -2
  60. package/dist/gateway/CodaliGatewayTypes.d.ts +34 -0
  61. package/dist/gateway/CodaliGatewayTypes.d.ts.map +1 -1
  62. package/dist/gateway/EvidenceNormalizer.d.ts.map +1 -1
  63. package/dist/gateway/EvidenceNormalizer.js +90 -1
  64. package/dist/gateway/Finalizer.d.ts +50 -0
  65. package/dist/gateway/Finalizer.d.ts.map +1 -0
  66. package/dist/gateway/Finalizer.js +79 -0
  67. package/dist/gateway/GatewayPlanner.d.ts +13 -0
  68. package/dist/gateway/GatewayPlanner.d.ts.map +1 -1
  69. package/dist/gateway/GatewayPlanner.js +81 -16
  70. package/dist/gateway/GatewayStateMachine.d.ts.map +1 -1
  71. package/dist/gateway/GatewayStateMachine.js +6 -0
  72. package/dist/gateway/GatewayTracer.d.ts +93 -0
  73. package/dist/gateway/GatewayTracer.d.ts.map +1 -0
  74. package/dist/gateway/GatewayTracer.js +214 -0
  75. package/dist/gateway/LocalGatewayProvider.d.ts +46 -0
  76. package/dist/gateway/LocalGatewayProvider.d.ts.map +1 -0
  77. package/dist/gateway/LocalGatewayProvider.js +161 -0
  78. package/dist/gateway/LocalGatewayTaskRunner.d.ts +79 -0
  79. package/dist/gateway/LocalGatewayTaskRunner.d.ts.map +1 -0
  80. package/dist/gateway/LocalGatewayTaskRunner.js +354 -0
  81. package/dist/gateway/RelevanceJudge.d.ts +14 -0
  82. package/dist/gateway/RelevanceJudge.d.ts.map +1 -0
  83. package/dist/gateway/RelevanceJudge.js +87 -0
  84. package/dist/gateway/ResponseSchema.d.ts +32 -0
  85. package/dist/gateway/ResponseSchema.d.ts.map +1 -0
  86. package/dist/gateway/ResponseSchema.js +120 -0
  87. package/dist/gateway/TemporalContext.d.ts +36 -0
  88. package/dist/gateway/TemporalContext.d.ts.map +1 -0
  89. package/dist/gateway/TemporalContext.js +115 -0
  90. package/dist/gateway/ToolCapabilityCompiler.d.ts +5 -0
  91. package/dist/gateway/ToolCapabilityCompiler.d.ts.map +1 -1
  92. package/dist/gateway/ToolCapabilityCompiler.js +27 -2
  93. package/dist/gateway/ToolDescriptorSource.d.ts +6 -0
  94. package/dist/gateway/ToolDescriptorSource.d.ts.map +1 -0
  95. package/dist/gateway/ToolDescriptorSource.js +30 -0
  96. package/dist/gateway/ToolExposure.d.ts +36 -0
  97. package/dist/gateway/ToolExposure.d.ts.map +1 -0
  98. package/dist/gateway/ToolExposure.js +154 -0
  99. package/dist/index.d.ts +57 -0
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +45 -0
  102. package/dist/providers/ClaudeCliProvider.d.ts +2 -0
  103. package/dist/providers/ClaudeCliProvider.d.ts.map +1 -1
  104. package/dist/providers/ClaudeCliProvider.js +2 -0
  105. package/dist/providers/CodexCliProvider.d.ts +2 -0
  106. package/dist/providers/CodexCliProvider.d.ts.map +1 -1
  107. package/dist/providers/CodexCliProvider.js +2 -0
  108. package/dist/providers/MswarmWorkerProvider.d.ts +2 -0
  109. package/dist/providers/MswarmWorkerProvider.d.ts.map +1 -1
  110. package/dist/providers/MswarmWorkerProvider.js +2 -0
  111. package/dist/providers/OllamaRemoteProvider.d.ts +2 -0
  112. package/dist/providers/OllamaRemoteProvider.d.ts.map +1 -1
  113. package/dist/providers/OllamaRemoteProvider.js +2 -0
  114. package/dist/providers/OpenAiCompatibleProvider.d.ts +2 -0
  115. package/dist/providers/OpenAiCompatibleProvider.d.ts.map +1 -1
  116. package/dist/providers/OpenAiCompatibleProvider.js +2 -0
  117. package/dist/providers/ProviderTypes.d.ts +13 -0
  118. package/dist/providers/ProviderTypes.d.ts.map +1 -1
  119. package/dist/providers/ProviderTypes.js +1 -1
  120. package/dist/runcontext/CredentialFile.d.ts +12 -0
  121. package/dist/runcontext/CredentialFile.d.ts.map +1 -0
  122. package/dist/runcontext/CredentialFile.js +81 -0
  123. package/dist/runcontext/RunContextResolver.d.ts +126 -0
  124. package/dist/runcontext/RunContextResolver.d.ts.map +1 -0
  125. package/dist/runcontext/RunContextResolver.js +389 -0
  126. package/dist/storage/CodaliDatasetPrivacyEngine.d.ts +1 -1
  127. package/dist/storage/CodaliDatasetPrivacyEngine.d.ts.map +1 -1
  128. package/dist/storage/CodaliDatasetPrivacyEngine.js +12 -0
  129. package/dist/tools/ToolRegistry.d.ts +14 -1
  130. package/dist/tools/ToolRegistry.d.ts.map +1 -1
  131. package/dist/tools/ToolRegistry.js +38 -1
  132. package/dist/tools/ToolTypes.d.ts +35 -0
  133. package/dist/tools/ToolTypes.d.ts.map +1 -1
  134. package/dist/tools/ToolTypes.js +21 -0
  135. package/dist/tools/TruncateResult.d.ts +29 -0
  136. package/dist/tools/TruncateResult.d.ts.map +1 -0
  137. package/dist/tools/TruncateResult.js +91 -0
  138. package/dist/tools/diff/DiffTool.d.ts.map +1 -1
  139. package/dist/tools/diff/DiffTool.js +2 -0
  140. package/dist/tools/docdex/DocdexTools.d.ts +21 -1
  141. package/dist/tools/docdex/DocdexTools.d.ts.map +1 -1
  142. package/dist/tools/docdex/DocdexTools.js +108 -16
  143. package/dist/tools/filesystem/FileTools.d.ts.map +1 -1
  144. package/dist/tools/filesystem/FileTools.js +8 -0
  145. package/dist/tools/search/SearchTool.d.ts.map +1 -1
  146. package/dist/tools/search/SearchTool.js +3 -1
  147. package/dist/tools/shell/ShellTool.d.ts.map +1 -1
  148. package/dist/tools/shell/ShellTool.js +2 -0
  149. package/package.json +4 -3
@@ -0,0 +1,536 @@
1
+ import path from "node:path";
2
+ import { filterLocallyDrivable, getAgentInventory } from "../agents/AgentInventory.js";
3
+ import { resolveConfigurableRoles } from "../agents/RoleResolution.js";
4
+ import { attachHttpTools } from "../connectors/http/HttpToolSource.js";
5
+ import { attachMcpTools } from "../connectors/mcp/McpToolSource.js";
6
+ import { DocdexClient } from "../docdex/DocdexClient.js";
7
+ import { createCodaliGateway } from "../gateway/CodaliGateway.js";
8
+ import { createLocalGatewayTaskRunner } from "../gateway/LocalGatewayTaskRunner.js";
9
+ import { createProviderForAssignment, } from "../gateway/LocalGatewayProvider.js";
10
+ import { gatewayToolDescriptorsFromRegistry } from "../gateway/ToolDescriptorSource.js";
11
+ import { GatewayTracer } from "../gateway/GatewayTracer.js";
12
+ import { createRelevanceJudge } from "../gateway/RelevanceJudge.js";
13
+ import { ToolRegistry } from "../tools/ToolRegistry.js";
14
+ import { createDocdexTools } from "../tools/docdex/DocdexTools.js";
15
+ import { LocalConfigRunContextResolver } from "../runcontext/RunContextResolver.js";
16
+ import { providerSupportsToolCalls } from "../providers/ProviderTypes.js";
17
+ /**
18
+ * `codali ask` — the terminal entry point to the orchestration gateway.
19
+ *
20
+ * Until this existed the gateway had exactly one caller (mswarm's cloud job
21
+ * path) and could not be exercised locally at all, which made every other
22
+ * improvement unverifiable.
23
+ */
24
+ /**
25
+ * Default budgets. Deliberately small: an orchestrator that can quietly spend
26
+ * fifty tool calls on one question is not one you can put in front of users.
27
+ */
28
+ export const DEFAULT_MAX_ROUNDS = 3;
29
+ export const DEFAULT_MAX_TOOL_CALLS = 20;
30
+ export const DEFAULT_MAX_MODEL_CALLS = 12;
31
+ /**
32
+ * Wall-clock budget. Generous because CLI-backed agents (codex, claude,
33
+ * gemini) commonly take 30-90s per call, and several tasks run per question.
34
+ */
35
+ export const DEFAULT_DEADLINE_MS = 600000;
36
+ /**
37
+ * Independent worker tasks executed concurrently. Bounded rather than
38
+ * unlimited: each task holds a model call, and a tenant's connectors have their
39
+ * own rate limits.
40
+ */
41
+ export const DEFAULT_MAX_PARALLEL_WORKERS = 3;
42
+ /** Read-only docdex tools offered by default. */
43
+ const DEFAULT_ALLOWED_TOOLS = [
44
+ "docdex_search",
45
+ "docdex_open",
46
+ "docdex_symbols",
47
+ "docdex_tree",
48
+ "docdex_files",
49
+ "docdex_impact_graph",
50
+ "docdex_batch_search",
51
+ "docdex_web_research",
52
+ "docdex_stats",
53
+ ];
54
+ const HELP = `Usage: codali ask "<question>" [options]
55
+
56
+ Ask Codali a question. The orchestrator routes the request, gathers evidence
57
+ with the tools available to it, and returns a cited answer.
58
+
59
+ Options:
60
+ --workspace-root <path> Repository to run against (default: cwd)
61
+ --mode <mode> fast | balanced | deep | cheap (default: balanced)
62
+ --trace Stream progress and print the run trace
63
+ --json Emit the full result as JSON
64
+ --max-rounds <n> Orchestration rounds (default: ${DEFAULT_MAX_ROUNDS})
65
+ --max-tool-calls <n> Tool calls for the whole run (default: ${DEFAULT_MAX_TOOL_CALLS})
66
+ --deadline-ms <n> Wall-clock budget (default: ${DEFAULT_DEADLINE_MS})
67
+ --docdex-base-url <url> Docdex endpoint (default: http://127.0.0.1:28491)
68
+ --response-schema <path> JSON Schema the answer must satisfy
69
+ --format <text|json> Response format (default: text)
70
+ --no-tools Answer without tools, for a pure generation task
71
+ --help, -h Show this help
72
+ `;
73
+ const parseIntArg = (value, flag, fallback) => {
74
+ if (value === undefined)
75
+ return fallback;
76
+ const parsed = Number.parseInt(value, 10);
77
+ if (!Number.isFinite(parsed) || parsed <= 0) {
78
+ throw new Error(`${flag} expects a positive integer`);
79
+ }
80
+ return parsed;
81
+ };
82
+ export const parseAskArgs = (argv) => {
83
+ const queryParts = [];
84
+ let workspaceRoot = process.cwd();
85
+ let mode = "balanced";
86
+ let trace = false;
87
+ let json = false;
88
+ let maxRounds = DEFAULT_MAX_ROUNDS;
89
+ let maxToolCalls = DEFAULT_MAX_TOOL_CALLS;
90
+ let deadlineMs = DEFAULT_DEADLINE_MS;
91
+ let docdexBaseUrl;
92
+ let responseSchemaPath;
93
+ let responseFormat;
94
+ let noTools = false;
95
+ for (let index = 0; index < argv.length; index += 1) {
96
+ const arg = argv[index];
97
+ const next = argv[index + 1];
98
+ if (arg === "--workspace-root" && next) {
99
+ workspaceRoot = path.resolve(next);
100
+ index += 1;
101
+ }
102
+ else if (arg === "--mode" && next) {
103
+ mode = next;
104
+ index += 1;
105
+ }
106
+ else if (arg === "--trace") {
107
+ trace = true;
108
+ }
109
+ else if (arg === "--json") {
110
+ json = true;
111
+ }
112
+ else if (arg === "--no-tools") {
113
+ noTools = true;
114
+ }
115
+ else if (arg === "--max-rounds" && next) {
116
+ maxRounds = parseIntArg(next, "--max-rounds", DEFAULT_MAX_ROUNDS);
117
+ index += 1;
118
+ }
119
+ else if (arg === "--max-tool-calls" && next) {
120
+ maxToolCalls = parseIntArg(next, "--max-tool-calls", DEFAULT_MAX_TOOL_CALLS);
121
+ index += 1;
122
+ }
123
+ else if (arg === "--deadline-ms" && next) {
124
+ deadlineMs = parseIntArg(next, "--deadline-ms", DEFAULT_DEADLINE_MS);
125
+ index += 1;
126
+ }
127
+ else if (arg === "--docdex-base-url" && next) {
128
+ docdexBaseUrl = next;
129
+ index += 1;
130
+ }
131
+ else if (arg === "--response-schema" && next) {
132
+ responseSchemaPath = next;
133
+ index += 1;
134
+ }
135
+ else if (arg === "--format" && next) {
136
+ responseFormat = next === "json" ? "json" : "text";
137
+ index += 1;
138
+ }
139
+ else if (arg && !arg.startsWith("--")) {
140
+ queryParts.push(arg);
141
+ }
142
+ }
143
+ const query = queryParts.join(" ").trim();
144
+ if (!query) {
145
+ throw new Error("codali ask requires a question.\n\n" + HELP);
146
+ }
147
+ return {
148
+ query,
149
+ workspaceRoot,
150
+ mode,
151
+ trace,
152
+ json,
153
+ maxRounds,
154
+ maxToolCalls,
155
+ deadlineMs,
156
+ docdexBaseUrl,
157
+ responseSchemaPath,
158
+ responseFormat,
159
+ noTools,
160
+ };
161
+ };
162
+ const buildDefaultRegistry = (options, context, judgeProvider) => {
163
+ const registry = new ToolRegistry();
164
+ if (options.noTools)
165
+ return registry;
166
+ const client = new DocdexClient({
167
+ baseUrl: options.docdexBaseUrl ?? context.docdex?.baseUrl ?? "http://127.0.0.1:28491",
168
+ repoRoot: context.repo?.root ?? options.workspaceRoot,
169
+ repoId: context.docdex?.repoId,
170
+ apiKey: context.docdex?.apiKey,
171
+ allowedOperations: context.docdex?.allowedOperations,
172
+ });
173
+ for (const tool of createDocdexTools(client, {
174
+ // Judged by the orchestrator model: small, fast, and already loaded.
175
+ judgeRelevance: judgeProvider
176
+ ? createRelevanceJudge({ provider: judgeProvider })
177
+ : undefined,
178
+ })) {
179
+ registry.register(tool);
180
+ }
181
+ return registry;
182
+ };
183
+ export const runAsk = async (argv, deps = {}) => {
184
+ if (argv.includes("--help") || argv.includes("-h")) {
185
+ (deps.write ?? console.log)(HELP);
186
+ return { tracer: new GatewayTracer(""), exitCode: 0 };
187
+ }
188
+ const options = parseAskArgs(argv);
189
+ const write = deps.write ?? ((line) => process.stdout.write(`${line}\n`));
190
+ const tracer = new GatewayTracer(options.query, { verbose: options.trace });
191
+ // ---- Resolve what this run may touch --------------------------------
192
+ const context = deps.resolveRunContext
193
+ ? await deps.resolveRunContext(options.workspaceRoot)
194
+ : await new LocalConfigRunContextResolver().resolve({
195
+ workspaceRoot: options.workspaceRoot,
196
+ });
197
+ for (const warning of context.warnings ?? [])
198
+ tracer.addWarning(warning);
199
+ // ---- Resolve models (inventory is loaded once per process) -----------
200
+ const inventory = await (deps.loadInventory ?? getAgentInventory)();
201
+ for (const warning of inventory.warnings)
202
+ tracer.addWarning(warning);
203
+ // Only consider agents this process can actually call. Selecting one it
204
+ // cannot drive produces a plausible plan and then a wall of provider errors.
205
+ const drivable = filterLocallyDrivable(inventory.agents);
206
+ if (drivable.length < inventory.agents.length) {
207
+ tracer.addWarning(`agents_not_locally_drivable:${inventory.agents.length - drivable.length}`);
208
+ }
209
+ const roles = resolveConfigurableRoles({
210
+ inventory: drivable,
211
+ bindings: context.agentRoles,
212
+ });
213
+ for (const warning of roles.warnings)
214
+ tracer.addWarning(warning);
215
+ tracer.setRoleBindings(roles.bindings);
216
+ const orchestrator = roles.assignments.orchestrator;
217
+ const synthesizer = roles.assignments.synthesizer;
218
+ if (!orchestrator && !synthesizer) {
219
+ write("No usable mcoda agent was found for the orchestrator or synthesizer role.\n" +
220
+ "Check `mcoda agent list --json` and bind roles in ~/.codali/config.json.");
221
+ tracer.finish({ status: "failed", completionReason: "no_agents_resolved" });
222
+ if (options.trace)
223
+ write(tracer.render());
224
+ return { tracer, exitCode: 1 };
225
+ }
226
+ const makeProvider = deps.createProvider ?? createProviderForAssignment;
227
+ const orchestratorProvider = orchestrator ? makeProvider(orchestrator) : undefined;
228
+ const synthesizerProvider = synthesizer ? makeProvider(synthesizer) : undefined;
229
+ // Either role can stand in for the other if only one resolved; the trace
230
+ // records which, so a degraded run is visible rather than silent.
231
+ const primary = (synthesizerProvider ?? orchestratorProvider);
232
+ if (!orchestrator || !synthesizer) {
233
+ tracer.addWarning("agent_role_substituted");
234
+ }
235
+ // Worker tasks need a provider that can emit structured tool calls. The
236
+ // agent's advertised `supportsTools` is not sufficient — a CLI-backed agent
237
+ // reports true because the CLI has tools, while this adapter drives it
238
+ // through text and can never surface a call. So probe the built provider.
239
+ const workerCandidates = [roles.toolCapable, roles.assignments.worker, orchestrator, synthesizer].filter((assignment) => Boolean(assignment));
240
+ let workerAssignment = workerCandidates[0];
241
+ let worker = workerAssignment ? makeProvider(workerAssignment) : primary;
242
+ for (const candidate of workerCandidates) {
243
+ const provider = makeProvider(candidate);
244
+ if (providerSupportsToolCalls(provider)) {
245
+ workerAssignment = candidate;
246
+ worker = provider;
247
+ break;
248
+ }
249
+ }
250
+ if (workerAssignment) {
251
+ tracer.setRoleBindings({ worker: workerAssignment.candidate.slug });
252
+ }
253
+ // If nothing can call a tool, do not offer tools at all. Planning four
254
+ // retrieval tasks that physically cannot execute is worse than planning
255
+ // none: the run burns its budget and answers "I could not determine",
256
+ // with the real cause buried.
257
+ const toolsExecutable = providerSupportsToolCalls(worker);
258
+ if (!toolsExecutable && !options.noTools) {
259
+ tracer.addWarning(`tools_unavailable:no_tool_calling_provider:${workerAssignment?.candidate.slug ?? "none"}`);
260
+ }
261
+ // ---- Tools -----------------------------------------------------------
262
+ const registry = deps.buildRegistry
263
+ ? deps.buildRegistry(options, context)
264
+ : buildDefaultRegistry(options, context, orchestratorProvider ?? worker);
265
+ // Connect the run's MCP servers and register what they expose into the same
266
+ // registry the built-in and docdex tools live in. A failing server degrades
267
+ // its own tools to unavailable and is reported; it never fails the run.
268
+ const mcp = options.noTools
269
+ ? { registry: undefined, health: [], registered: [], warnings: [] }
270
+ : await (deps.attachMcp ?? attachMcpTools)({ context, toolRegistry: registry });
271
+ for (const warning of mcp.warnings)
272
+ tracer.addWarning(warning);
273
+ if (mcp.registered.length > 0) {
274
+ tracer.addWarning(`mcp_tools_registered:${mcp.registered.length}`);
275
+ }
276
+ // Hand-declared HTTP connectors register the same way MCP tools do.
277
+ const http = options.noTools
278
+ ? { registered: [], warnings: [] }
279
+ : attachHttpTools({ context, toolRegistry: registry });
280
+ for (const warning of http.warnings)
281
+ tracer.addWarning(warning);
282
+ const registeredTools = registry.list().map((tool) => tool.name);
283
+ // Only read-only MCP tools enter the default set. Phase 2 keeps every
284
+ // external connector read-only, and since a server's self-description is not
285
+ // trusted, "read-only" means an operator declared it so — either with
286
+ // `readOnly: true` on the server or by naming tools in `allowTools`. Write
287
+ // tools stay registered and visible to `codali tools list`, but are not
288
+ // offered to a model unless the operator allow-lists them explicitly.
289
+ const readOnlyMcpTools = registry
290
+ .catalog(mcp.registered)
291
+ .filter((descriptor) => descriptor.readOnly)
292
+ .map((descriptor) => descriptor.name);
293
+ const withheldWriteTools = mcp.registered.length - readOnlyMcpTools.length;
294
+ if (withheldWriteTools > 0) {
295
+ tracer.addWarning(`mcp_write_tools_withheld:${withheldWriteTools}`);
296
+ }
297
+ // HTTP connector tools are GET/HEAD by construction, so they are read-only
298
+ // without needing an operator declaration.
299
+ const defaultAllowed = [...DEFAULT_ALLOWED_TOOLS, ...readOnlyMcpTools, ...http.registered];
300
+ const allowedTools = toolsExecutable
301
+ ? (context.allowedTools ?? defaultAllowed).filter((tool) => registeredTools.includes(tool))
302
+ : [];
303
+ const toolDescriptors = gatewayToolDescriptorsFromRegistry(registry, allowedTools);
304
+ // ---- Build the request ------------------------------------------------
305
+ const limits = context.limits ?? {};
306
+ const maxToolCalls = Math.min(options.maxToolCalls, limits.maxToolCalls ?? Infinity);
307
+ const maxRounds = Math.min(options.maxRounds, limits.maxRounds ?? Infinity);
308
+ const deadlineMs = Math.min(options.deadlineMs, limits.deadlineMs ?? Infinity);
309
+ // Every tool must be declared to the capability compiler, which otherwise
310
+ // discards it as `not_declared`. That includes docdex tools outside the
311
+ // compiler's hardcoded backing-tool set — `docdex_web_research` among them,
312
+ // which meant the "web" capability was never offered to the classifier and
313
+ // no question could ever reach the public web.
314
+ const connectorTools = allowedTools.filter((tool) => registeredTools.includes(tool));
315
+ let responseSchema;
316
+ if (options.responseSchemaPath) {
317
+ const { readFile } = await import("node:fs/promises");
318
+ responseSchema = JSON.parse(await readFile(path.resolve(options.responseSchemaPath), "utf8"));
319
+ }
320
+ const request = {
321
+ query: options.query,
322
+ mode: options.mode,
323
+ product: { name: "codali-cli" },
324
+ tenant: context.tenant,
325
+ // Declare discovered MCP tools to the capability compiler. Without this it
326
+ // treats them as `not_declared` and silently drops every one: the compiler
327
+ // predates connectors and otherwise only recognizes built-ins, app
328
+ // contracts, and manifest entries. Discovery is the declaration — these
329
+ // names came from a live `tools/list` against a configured server.
330
+ tools: connectorTools.length > 0
331
+ ? { actualTools: connectorTools, readOnlyTools: connectorTools }
332
+ : undefined,
333
+ docdex: {
334
+ enabled: !options.noTools,
335
+ baseUrl: options.docdexBaseUrl ?? context.docdex?.baseUrl,
336
+ repoRoot: context.repo?.root ?? options.workspaceRoot,
337
+ repoId: context.docdex?.repoId,
338
+ },
339
+ policy: {
340
+ allowedTools,
341
+ deniedTools: context.deniedTools,
342
+ maxIterations: maxRounds,
343
+ maxRuntimeMs: deadlineMs,
344
+ maxToolCalls,
345
+ maxModelCalls: DEFAULT_MAX_MODEL_CALLS,
346
+ maxEvidenceItems: 40,
347
+ maxContextPackTokens: 16000,
348
+ allowWrites: false,
349
+ allowShell: false,
350
+ allowDestructiveOperations: false,
351
+ allowOutsideWorkspace: false,
352
+ // Phase 1 resolves two roles; requiring a strictly "large" final model
353
+ // would fail on setups that only expose medium agents.
354
+ requireFinalLargeModel: false,
355
+ allowDegradedFinalAnswer: true,
356
+ },
357
+ response: {
358
+ format: responseSchema ? "json" : (options.responseFormat ?? "text"),
359
+ schema: responseSchema,
360
+ },
361
+ };
362
+ // ---- Run --------------------------------------------------------------
363
+ const gateway = createCodaliGateway({
364
+ provider: primary,
365
+ // Classify and plan on the orchestrator model; only the final answer pays
366
+ // for the large one.
367
+ plannerProvider: orchestratorProvider ?? primary,
368
+ // So the trace names the model that actually ran.
369
+ finalAgent: synthesizer ?? orchestrator,
370
+ agentInventory: inventory.agents,
371
+ toolDescriptors,
372
+ taskRunner: createLocalGatewayTaskRunner({
373
+ provider: worker,
374
+ registry,
375
+ toolContext: {
376
+ workspaceRoot: options.workspaceRoot,
377
+ allowShell: false,
378
+ allowDestructiveOperations: false,
379
+ allowOutsideWorkspace: false,
380
+ },
381
+ onEvent: (event) => {
382
+ if (event.type === "tool_call") {
383
+ tracer.recordToolCall({ taskId: event.taskId, tool: event.tool, args: event.args });
384
+ }
385
+ else if (event.type === "tool_result") {
386
+ tracer.recordToolResult({
387
+ taskId: event.taskId,
388
+ tool: event.tool,
389
+ ok: event.ok,
390
+ latencyMs: event.latencyMs,
391
+ errorCode: event.errorCode,
392
+ });
393
+ }
394
+ },
395
+ }),
396
+ workerOptions: {
397
+ maxRuntimeMs: deadlineMs,
398
+ maxToolCalls,
399
+ // The state machine defaults to 30s per task, which is fine for a hosted
400
+ // API and far too short for a CLI-backed agent — those routinely take a
401
+ // minute per call, and every task times out before it can call a tool.
402
+ // Derive it from the run deadline instead of guessing a constant.
403
+ perTaskTimeoutMs: Math.max(60000, Math.floor(deadlineMs / 2)),
404
+ // Independent read-only tasks run concurrently. The state machine
405
+ // defaults to serial, which turns a four-source question into four
406
+ // sequential model round trips.
407
+ maxParallelWorkers: DEFAULT_MAX_PARALLEL_WORKERS,
408
+ },
409
+ });
410
+ try {
411
+ const planning = await gateway.plan(request);
412
+ tracer.setRunId(planning.runId);
413
+ tracer.recordPlan({
414
+ capabilities: planning.classifier.capabilities,
415
+ tasks: planning.planner.workerTasks,
416
+ });
417
+ // An ambiguous request stops before any research budget is spent. Guessing
418
+ // which "Bekir" was meant yields a confident answer about the wrong person.
419
+ const clarification = planning.classifier.needsClarification?.trim();
420
+ if (clarification) {
421
+ const clarified = await gateway.buildClarificationResult(request, planning, clarification);
422
+ tracer.finish({
423
+ status: clarified.status,
424
+ completionReason: "needs_clarification",
425
+ });
426
+ if (options.json) {
427
+ write(JSON.stringify(clarified, null, 2));
428
+ }
429
+ else {
430
+ write(`[needs clarification] ${clarified.answer}`);
431
+ }
432
+ if (options.trace) {
433
+ if (options.json)
434
+ process.stderr.write(`\n${tracer.render()}\n`);
435
+ else {
436
+ write("");
437
+ write(tracer.render());
438
+ }
439
+ }
440
+ return { result: clarified, tracer, exitCode: 0 };
441
+ }
442
+ const workers = await gateway.executePlannedWorkerTasks(request, planning);
443
+ tracer.recordRound(1);
444
+ // Surface worker failures. Without this a run whose every task failed still
445
+ // reaches the synthesizer, which correctly reports it cannot answer — and
446
+ // the actual cause (a 400 from a provider, say) never appears anywhere.
447
+ for (const task of workers.workers.taskResults) {
448
+ if (task.status === "failed") {
449
+ tracer.addWarning(`worker_task_failed:${task.taskId}:${task.errorCode ?? "unknown"}:${(task.errorMessage ?? "").slice(0, 300)}`);
450
+ }
451
+ }
452
+ for (const error of workers.workers.errors)
453
+ tracer.addWarning(`worker_error:${error}`);
454
+ const result = await gateway.synthesizeFinalAnswer({
455
+ runId: planning.runId,
456
+ request,
457
+ planning,
458
+ workers: workers.workers,
459
+ });
460
+ tracer.finish({
461
+ status: result.status,
462
+ finalizerMode: String(result.telemetry.finalizerMode ?? "synthesizer"),
463
+ completionReason: result.status === "succeeded" ? "complete" : result.status,
464
+ warnings: result.warnings,
465
+ });
466
+ if (options.json) {
467
+ write(JSON.stringify(result, null, 2));
468
+ }
469
+ else {
470
+ // A partial answer must never read like a complete one.
471
+ if (result.status !== "succeeded") {
472
+ write(`[${result.status}] This answer is incomplete. See warnings below.`);
473
+ write("");
474
+ }
475
+ write(result.answer);
476
+ if (result.sources.length > 0) {
477
+ write("");
478
+ write("Sources:");
479
+ for (const source of result.sources) {
480
+ write(` [${source.evidenceId}] ${source.title ?? source.sourceType}${source.uri ? ` — ${source.uri}` : ""}`);
481
+ }
482
+ }
483
+ if (result.artifacts.length > 0) {
484
+ write("");
485
+ write("Artifacts:");
486
+ for (const artifact of result.artifacts) {
487
+ write(` ${artifact.type}: ${artifact.path ?? artifact.uri ?? artifact.id}`);
488
+ }
489
+ }
490
+ if (result.status !== "succeeded" && result.warnings.length > 0 && !options.trace) {
491
+ write("");
492
+ write("Warnings:");
493
+ for (const warning of result.warnings)
494
+ write(` ${warning}`);
495
+ }
496
+ }
497
+ // With --json, stdout is a machine-readable document. Appending the
498
+ // rendered trace after it makes the output unparseable, so the trace goes
499
+ // to stderr instead of being interleaved.
500
+ if (options.trace) {
501
+ if (options.json) {
502
+ process.stderr.write(`\n${tracer.render()}\n`);
503
+ }
504
+ else {
505
+ write("");
506
+ write(tracer.render());
507
+ }
508
+ }
509
+ return {
510
+ result,
511
+ tracer,
512
+ exitCode: result.status === "failed" ? 1 : 0,
513
+ };
514
+ }
515
+ catch (error) {
516
+ const message = error instanceof Error ? error.message : String(error);
517
+ tracer.finish({ status: "failed", completionReason: message });
518
+ write(`Codali run failed: ${message}`);
519
+ if (options.trace)
520
+ write(tracer.render());
521
+ return { tracer, exitCode: 1 };
522
+ }
523
+ finally {
524
+ // stdio MCP servers are child processes. They must be shut down on the
525
+ // failure path too, or a failed run leaves orphans behind.
526
+ await mcp.registry?.close();
527
+ }
528
+ };
529
+ export const AskCommand = {
530
+ async run(argv) {
531
+ const outcome = await runAsk(argv);
532
+ if (outcome.exitCode !== 0) {
533
+ process.exitCode = outcome.exitCode;
534
+ }
535
+ },
536
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Writes a key into the credentials file, replacing any existing entry.
3
+ * Rewrites in place rather than appending, so re-authenticating does not leave
4
+ * a stale token above the new one — the parser takes the last occurrence, and
5
+ * relying on that is a trap.
6
+ */
7
+ export declare const upsertCredential: (key: string, value: string, file?: string) => void;
8
+ export interface AuthDependencies {
9
+ write?: (line: string) => void;
10
+ fetchImpl?: typeof fetch;
11
+ /** Injected in tests so polling does not actually sleep. */
12
+ sleep?: (ms: number) => Promise<void>;
13
+ credsFile?: string;
14
+ }
15
+ export declare const runAuth: (argv: string[], deps?: AuthDependencies) => Promise<number>;
16
+ export declare const AuthCommand: {
17
+ run(argv: string[]): Promise<void>;
18
+ };
19
+ //# sourceMappingURL=AuthCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthCommand.d.ts","sourceRoot":"","sources":["../../src/cli/AuthCommand.ts"],"names":[],"mappings":"AAmCA;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,MAAM,EACX,OAAO,MAAM,EACb,OAAM,MAAc,KACnB,IAmBF,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+FD,eAAO,MAAM,OAAO,GAClB,MAAM,MAAM,EAAE,EACd,OAAM,gBAAqB,KAC1B,OAAO,CAAC,MAAM,CAahB,CAAC;AAEF,eAAO,MAAM,WAAW;cACN,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzC,CAAC"}