@mcoda/codali 0.1.133 → 0.1.135-codali.2
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 +37 -0
- package/ORCHESTRATION.md +387 -0
- package/README.md +103 -1
- package/config/alternate-local.example.json +49 -0
- package/config/docdex-runtime.json +21 -0
- package/config/orchestration.schema.json +548 -0
- package/config/raijin-suku.example.json +172 -0
- package/config/terminal-long-scenarios.v1.json +70 -0
- package/dist/agents/RoleResolution.d.ts.map +1 -1
- package/dist/agents/RoleResolution.js +4 -4
- package/dist/api/CodaliApi.d.ts +16 -0
- package/dist/api/CodaliApi.d.ts.map +1 -1
- package/dist/api/CodaliApi.js +39 -10
- package/dist/cli/AskCommand.d.ts +3 -0
- package/dist/cli/AskCommand.d.ts.map +1 -1
- package/dist/cli/AskCommand.js +76 -13
- package/dist/cli/RunCommand.d.ts +4 -0
- package/dist/cli/RunCommand.d.ts.map +1 -1
- package/dist/cli/RunCommand.js +110 -24
- package/dist/cli/TerminalCommand.d.ts +35 -0
- package/dist/cli/TerminalCommand.d.ts.map +1 -0
- package/dist/cli/TerminalCommand.js +616 -0
- package/dist/cli/TerminalCredentials.d.ts +3 -0
- package/dist/cli/TerminalCredentials.d.ts.map +1 -0
- package/dist/cli/TerminalCredentials.js +89 -0
- package/dist/cli/TerminalInput.d.ts +24 -0
- package/dist/cli/TerminalInput.d.ts.map +1 -0
- package/dist/cli/TerminalInput.js +98 -0
- package/dist/cli/TerminalText.d.ts +10 -0
- package/dist/cli/TerminalText.d.ts.map +1 -0
- package/dist/cli/TerminalText.js +53 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +33 -4
- package/dist/cognitive/ConfiguredPipeline.d.ts +34 -0
- package/dist/cognitive/ConfiguredPipeline.d.ts.map +1 -0
- package/dist/cognitive/ConfiguredPipeline.js +50 -0
- package/dist/cognitive/ValidationRunner.d.ts +4 -0
- package/dist/cognitive/ValidationRunner.d.ts.map +1 -1
- package/dist/cognitive/ValidationRunner.js +18 -41
- package/dist/config/NodeOrchestration.d.ts +21 -0
- package/dist/config/NodeOrchestration.d.ts.map +1 -0
- package/dist/config/NodeOrchestration.js +54 -0
- package/dist/config/OrchestrationConfig.d.ts +100 -0
- package/dist/config/OrchestrationConfig.d.ts.map +1 -0
- package/dist/config/OrchestrationConfig.js +245 -0
- package/dist/connectors/mcp/McpClient.d.ts +3 -1
- package/dist/connectors/mcp/McpClient.d.ts.map +1 -1
- package/dist/connectors/mcp/McpClient.js +3 -2
- package/dist/connectors/mcp/McpServerRegistry.d.ts +2 -2
- package/dist/connectors/mcp/McpServerRegistry.d.ts.map +1 -1
- package/dist/connectors/mcp/McpServerRegistry.js +39 -16
- package/dist/connectors/mcp/McpToolAdapter.js +2 -2
- package/dist/connectors/media/ConfiguredMediaTools.d.ts +6 -0
- package/dist/connectors/media/ConfiguredMediaTools.d.ts.map +1 -0
- package/dist/connectors/media/ConfiguredMediaTools.js +41 -0
- package/dist/connectors/media/ImageGenerationTool.js +3 -3
- package/dist/connectors/media/MediaArtifactReceipt.d.ts +5 -0
- package/dist/connectors/media/MediaArtifactReceipt.d.ts.map +1 -0
- package/dist/connectors/media/MediaArtifactReceipt.js +65 -0
- package/dist/connectors/media/MediaJobStore.d.ts +68 -0
- package/dist/connectors/media/MediaJobStore.d.ts.map +1 -0
- package/dist/connectors/media/MediaJobStore.js +199 -0
- package/dist/connectors/media/MediaJobWorker.d.ts +2 -0
- package/dist/connectors/media/MediaJobWorker.d.ts.map +1 -0
- package/dist/connectors/media/MediaJobWorker.js +65 -0
- package/dist/connectors/media/RemoteMediaJob.d.ts +6 -0
- package/dist/connectors/media/RemoteMediaJob.d.ts.map +1 -0
- package/dist/connectors/media/RemoteMediaJob.js +80 -0
- package/dist/docdex/DocdexClient.d.ts +18 -1
- package/dist/docdex/DocdexClient.d.ts.map +1 -1
- package/dist/docdex/DocdexClient.js +135 -19
- package/dist/docdex/ManagedDocdex.d.ts +29 -0
- package/dist/docdex/ManagedDocdex.d.ts.map +1 -0
- package/dist/docdex/ManagedDocdex.js +215 -0
- package/dist/docdex/ManagedRuntime.d.ts +28 -0
- package/dist/docdex/ManagedRuntime.d.ts.map +1 -0
- package/dist/docdex/ManagedRuntime.js +212 -0
- package/dist/eval/EvaluationProcess.d.ts +13 -0
- package/dist/eval/EvaluationProcess.d.ts.map +1 -0
- package/dist/eval/EvaluationProcess.js +76 -0
- package/dist/eval/LongWorkflowJourney.d.ts +34 -0
- package/dist/eval/LongWorkflowJourney.d.ts.map +1 -0
- package/dist/eval/LongWorkflowJourney.js +243 -0
- package/dist/eval/PlanningCorpus.d.ts +45 -0
- package/dist/eval/PlanningCorpus.d.ts.map +1 -0
- package/dist/eval/PlanningCorpus.js +189 -0
- package/dist/eval/PlanningCorpusRunner.d.ts +13 -0
- package/dist/eval/PlanningCorpusRunner.d.ts.map +1 -0
- package/dist/eval/PlanningCorpusRunner.js +89 -0
- package/dist/eval/TerminalCorpusRunner.d.ts +36 -0
- package/dist/eval/TerminalCorpusRunner.d.ts.map +1 -0
- package/dist/eval/TerminalCorpusRunner.js +222 -0
- package/dist/eval/TerminalTaskCorpus.d.ts +26 -0
- package/dist/eval/TerminalTaskCorpus.d.ts.map +1 -0
- package/dist/eval/TerminalTaskCorpus.js +105 -0
- package/dist/eval/TerminalWorkflowCorpus.d.ts +11 -0
- package/dist/eval/TerminalWorkflowCorpus.d.ts.map +1 -0
- package/dist/eval/TerminalWorkflowCorpus.js +116 -0
- package/dist/eval/fixtures/TerminalRecoveryJourney.d.ts +4 -0
- package/dist/eval/fixtures/TerminalRecoveryJourney.d.ts.map +1 -0
- package/dist/eval/fixtures/TerminalRecoveryJourney.js +132 -0
- package/dist/gateway/CodaliGateway.d.ts +2 -0
- package/dist/gateway/CodaliGateway.d.ts.map +1 -1
- package/dist/gateway/CodaliGateway.js +16 -4
- package/dist/gateway/ConfiguredGateway.d.ts +23 -0
- package/dist/gateway/ConfiguredGateway.d.ts.map +1 -0
- package/dist/gateway/ConfiguredGateway.js +44 -0
- package/dist/gateway/GatewayTracer.d.ts +1 -1
- package/dist/gateway/GatewayTracer.d.ts.map +1 -1
- package/dist/gateway/GatewayTracer.js +2 -27
- package/dist/gateway/LocalGatewayTaskRunner.d.ts.map +1 -1
- package/dist/gateway/LocalGatewayTaskRunner.js +13 -1
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/permissions/PermissionGate.d.ts +44 -0
- package/dist/permissions/PermissionGate.d.ts.map +1 -0
- package/dist/permissions/PermissionGate.js +97 -0
- package/dist/providers/ConfiguredEmbeddings.d.ts +43 -0
- package/dist/providers/ConfiguredEmbeddings.d.ts.map +1 -0
- package/dist/providers/ConfiguredEmbeddings.js +87 -0
- package/dist/providers/ConfiguredModelScope.d.ts +69 -0
- package/dist/providers/ConfiguredModelScope.d.ts.map +1 -0
- package/dist/providers/ConfiguredModelScope.js +113 -0
- package/dist/providers/ConfiguredProvider.d.ts +79 -0
- package/dist/providers/ConfiguredProvider.d.ts.map +1 -0
- package/dist/providers/ConfiguredProvider.js +238 -0
- package/dist/providers/ModelServiceError.d.ts +10 -0
- package/dist/providers/ModelServiceError.d.ts.map +1 -0
- package/dist/providers/ModelServiceError.js +45 -0
- package/dist/providers/MswarmWorkerProvider.d.ts.map +1 -1
- package/dist/providers/MswarmWorkerProvider.js +4 -0
- package/dist/providers/OpenAiCompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAiCompatibleProvider.js +134 -52
- package/dist/providers/ProviderTypes.d.ts +14 -0
- package/dist/providers/ProviderTypes.d.ts.map +1 -1
- package/dist/runtime/CodaliJobRuntime.d.ts +6 -0
- package/dist/runtime/CodaliJobRuntime.d.ts.map +1 -1
- package/dist/runtime/CodaliJobRuntime.js +36 -1
- package/dist/runtime/CodaliRuntime.d.ts +30 -0
- package/dist/runtime/CodaliRuntime.d.ts.map +1 -1
- package/dist/runtime/CodaliRuntime.js +71 -1
- package/dist/runtime/CompletionReport.d.ts +5 -0
- package/dist/runtime/CompletionReport.d.ts.map +1 -0
- package/dist/runtime/CompletionReport.js +12 -0
- package/dist/runtime/ProgressTracker.d.ts +17 -0
- package/dist/runtime/ProgressTracker.d.ts.map +1 -0
- package/dist/runtime/ProgressTracker.js +27 -0
- package/dist/runtime/RequestedTestEvidence.d.ts +12 -0
- package/dist/runtime/RequestedTestEvidence.d.ts.map +1 -0
- package/dist/runtime/RequestedTestEvidence.js +16 -0
- package/dist/runtime/RequiredRetrieval.d.ts +13 -0
- package/dist/runtime/RequiredRetrieval.d.ts.map +1 -0
- package/dist/runtime/RequiredRetrieval.js +25 -0
- package/dist/runtime/ReviewSource.d.ts +6 -0
- package/dist/runtime/ReviewSource.d.ts.map +1 -0
- package/dist/runtime/ReviewSource.js +38 -0
- package/dist/runtime/Runner.d.ts +13 -0
- package/dist/runtime/Runner.d.ts.map +1 -1
- package/dist/runtime/Runner.js +220 -158
- package/dist/runtime/SecretRedaction.d.ts +11 -0
- package/dist/runtime/SecretRedaction.d.ts.map +1 -0
- package/dist/runtime/SecretRedaction.js +85 -0
- package/dist/runtime/SessionController.d.ts +148 -0
- package/dist/runtime/SessionController.d.ts.map +1 -0
- package/dist/runtime/SessionController.js +1356 -0
- package/dist/runtime/StructuralEvidence.d.ts +22 -0
- package/dist/runtime/StructuralEvidence.d.ts.map +1 -0
- package/dist/runtime/StructuralEvidence.js +87 -0
- package/dist/runtime/TaskReviewContext.d.ts +34 -0
- package/dist/runtime/TaskReviewContext.d.ts.map +1 -0
- package/dist/runtime/TaskReviewContext.js +34 -0
- package/dist/runtime/ValidationOutput.d.ts +5 -0
- package/dist/runtime/ValidationOutput.d.ts.map +1 -0
- package/dist/runtime/ValidationOutput.js +32 -0
- package/dist/runtime/VerificationCommand.d.ts +3 -0
- package/dist/runtime/VerificationCommand.d.ts.map +1 -0
- package/dist/runtime/VerificationCommand.js +48 -0
- package/dist/runtime/WorkStatusPage.d.ts +15 -0
- package/dist/runtime/WorkStatusPage.d.ts.map +1 -0
- package/dist/runtime/WorkStatusPage.js +58 -0
- package/dist/runtime/WorkspaceLedger.d.ts +13 -0
- package/dist/runtime/WorkspaceLedger.d.ts.map +1 -0
- package/dist/runtime/WorkspaceLedger.js +145 -0
- package/dist/runtime/WorkspaceLock.d.ts.map +1 -1
- package/dist/runtime/WorkspaceLock.js +20 -17
- package/dist/runtime/WorkspaceSnapshot.d.ts +13 -0
- package/dist/runtime/WorkspaceSnapshot.d.ts.map +1 -0
- package/dist/runtime/WorkspaceSnapshot.js +88 -0
- package/dist/session/AtomicState.d.ts +13 -0
- package/dist/session/AtomicState.d.ts.map +1 -0
- package/dist/session/AtomicState.js +109 -0
- package/dist/session/SessionStoragePath.d.ts +15 -0
- package/dist/session/SessionStoragePath.d.ts.map +1 -0
- package/dist/session/SessionStoragePath.js +89 -0
- package/dist/session/SessionStore.d.ts +9 -0
- package/dist/session/SessionStore.d.ts.map +1 -1
- package/dist/session/SessionStore.js +181 -47
- package/dist/session/WorkState.d.ts +23 -0
- package/dist/session/WorkState.d.ts.map +1 -0
- package/dist/session/WorkState.js +367 -0
- package/dist/session/WorkTypes.d.ts +94 -0
- package/dist/session/WorkTypes.d.ts.map +1 -0
- package/dist/session/WorkTypes.js +1 -0
- package/dist/tools/ToolRegistry.d.ts +2 -0
- package/dist/tools/ToolRegistry.d.ts.map +1 -1
- package/dist/tools/ToolRegistry.js +12 -2
- package/dist/tools/ToolTypes.d.ts +16 -0
- package/dist/tools/ToolTypes.d.ts.map +1 -1
- package/dist/tools/docdex/DocdexTools.d.ts +2 -0
- package/dist/tools/docdex/DocdexTools.d.ts.map +1 -1
- package/dist/tools/docdex/DocdexTools.js +17 -2
- package/dist/tools/filesystem/FileTools.d.ts.map +1 -1
- package/dist/tools/filesystem/FileTools.js +30 -10
- package/dist/tools/shell/AsyncProcess.d.ts +16 -0
- package/dist/tools/shell/AsyncProcess.d.ts.map +1 -0
- package/dist/tools/shell/AsyncProcess.js +94 -0
- package/dist/tools/shell/DarwinRuntimeLibraries.d.ts +13 -0
- package/dist/tools/shell/DarwinRuntimeLibraries.d.ts.map +1 -0
- package/dist/tools/shell/DarwinRuntimeLibraries.js +130 -0
- package/dist/tools/shell/HostToolchain.d.ts +19 -0
- package/dist/tools/shell/HostToolchain.d.ts.map +1 -0
- package/dist/tools/shell/HostToolchain.js +264 -0
- package/dist/tools/shell/IsolatedExecutor.d.ts +15 -0
- package/dist/tools/shell/IsolatedExecutor.d.ts.map +1 -0
- package/dist/tools/shell/IsolatedExecutor.js +134 -0
- package/dist/tools/shell/ShellTool.d.ts.map +1 -1
- package/dist/tools/shell/ShellTool.js +9 -29
- package/dist/tools/shell/ShellWorkingDirectory.d.ts +3 -0
- package/dist/tools/shell/ShellWorkingDirectory.d.ts.map +1 -0
- package/dist/tools/shell/ShellWorkingDirectory.js +30 -0
- package/package.json +27 -24
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { SessionController } from "../runtime/SessionController.js";
|
|
6
|
+
import { loadOrchestrationConfig, orchestrationHash, validateOrchestrationConfig } from "../config/OrchestrationConfig.js";
|
|
7
|
+
import { ConfiguredProvider, ModelServiceClient, providerCredential } from "../providers/ConfiguredProvider.js";
|
|
8
|
+
import { executorStatus } from "../tools/shell/IsolatedExecutor.js";
|
|
9
|
+
import { atomicWriteJson } from "../session/AtomicState.js";
|
|
10
|
+
import { readTerminalCredential, setTerminalCredential } from "./TerminalCredentials.js";
|
|
11
|
+
import { redactSecretValues } from "../gateway/GatewayTracer.js";
|
|
12
|
+
import { terminalText, TerminalTextStream } from "./TerminalText.js";
|
|
13
|
+
import { validateEmbeddingPreprocessing } from "@mcoda/shared";
|
|
14
|
+
import { TerminalInput, insertTerminalPaste } from "./TerminalInput.js";
|
|
15
|
+
import { ModelServiceError } from "../providers/ModelServiceError.js";
|
|
16
|
+
import { discoverHostToolchain } from "../tools/shell/HostToolchain.js";
|
|
17
|
+
import { ensureManagedDocdexRuntime, inspectManagedDocdexRuntime } from "../docdex/ManagedRuntime.js";
|
|
18
|
+
const DEFAULT_PROVIDER = { adapter: "mswarm", baseUrl: "https://api.mswarm.org/v1/swarm/self-hosted/openai", credentialRef: "env:MCODA_MSWARM_API_KEY" };
|
|
19
|
+
const CONFIG_PATH = path.join(os.homedir(), ".codali", "orchestration.json");
|
|
20
|
+
const COMMANDS = ["/help", "/usage", "/status", "/permissions", "/effort", "/models", "/config", "/goal", "/plan", "/tasks", "/decisions", "/context", "/diff", "/review", "/compact", "/checkpoint", "/sessions", "/resume", "/fork", "/retention", "/delete-session", "/new", "/steer", "/continue", "/cancel", "/reconcile", "/reconcile-media", "/export", "/artifacts", "/quit"];
|
|
21
|
+
const HELP = `Codali terminal\n codali [chat] [prompt] [--workspace-root PATH] [--config PATH]\n codali exec PROMPT [--json] [--permissions plan|ask|workspace] [--effort low|medium|high|max]\n codali setup [--config PATH] [--docdex-binary PATH]\n codali login [--stdin] | logout | auth status\n codali sessions | resume SESSION_ID | status SESSION_ID\n\n${COMMANDS.join(" ")}\n\nCtrl-C cancels the active turn. A second Ctrl-C exits after cleanup.\nIn noninteractive mode approvals are denied; choose an explicit permission profile.\nProject tasks: mcoda codali chat --project-task TASK_KEY (existing workspace task database).\nExit codes: 0 verified completion, 1 error, 2 open/blocked goal, 130 cancelled.`;
|
|
22
|
+
export const parseTerminalArguments = (argv) => {
|
|
23
|
+
const [command = "chat", ...input] = argv;
|
|
24
|
+
const result = { command, prompt: "", workspaceRoot: process.cwd(), effort: "medium", permissions: "ask", json: false, plain: false, stdin: false };
|
|
25
|
+
const positional = [];
|
|
26
|
+
for (let i = 0; i < input.length; i++) {
|
|
27
|
+
const flag = input[i];
|
|
28
|
+
if (["--allow-web", "--allow-memory", "--allow-media", "--allow-network"].includes(flag)) {
|
|
29
|
+
const key = { "--allow-web": "allowWeb", "--allow-memory": "allowMemory", "--allow-media": "allowMedia", "--allow-network": "allowNetwork" }[flag];
|
|
30
|
+
result[key] = true;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (["--json", "--plain", "--stdin"].includes(flag)) {
|
|
34
|
+
result[flag.slice(2)] = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (["--workspace-root", "--config", "--docdex-binary", "--effort", "--permissions", "--project-task"].includes(flag)) {
|
|
38
|
+
const value = input[++i];
|
|
39
|
+
if (!value || value.startsWith("--"))
|
|
40
|
+
throw new Error(`Missing value for ${flag}`);
|
|
41
|
+
if (flag === "--workspace-root")
|
|
42
|
+
result.workspaceRoot = path.resolve(value);
|
|
43
|
+
if (flag === "--config")
|
|
44
|
+
result.config = path.resolve(value);
|
|
45
|
+
if (flag === "--docdex-binary") {
|
|
46
|
+
if (command !== "setup")
|
|
47
|
+
throw new Error("--docdex-binary is a setup option");
|
|
48
|
+
result.docdexBinary = value.includes(path.sep) ? path.resolve(value) : value;
|
|
49
|
+
}
|
|
50
|
+
if (flag === "--project-task")
|
|
51
|
+
result.projectTask = value;
|
|
52
|
+
if (flag === "--effort") {
|
|
53
|
+
if (!["low", "medium", "high", "max"].includes(value))
|
|
54
|
+
throw new Error("Invalid effort level");
|
|
55
|
+
result.effort = value;
|
|
56
|
+
}
|
|
57
|
+
if (flag === "--permissions") {
|
|
58
|
+
if (!["plan", "ask", "workspace"].includes(value))
|
|
59
|
+
throw new Error("Invalid permission profile");
|
|
60
|
+
result.permissions = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (flag.startsWith("--"))
|
|
64
|
+
throw new Error(`Unknown terminal option: ${flag}`);
|
|
65
|
+
else
|
|
66
|
+
positional.push(flag);
|
|
67
|
+
}
|
|
68
|
+
result.prompt = positional.join(" ");
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
export const setupTerminalConfig = async (destination, options = {}) => {
|
|
72
|
+
try {
|
|
73
|
+
await fs.access(destination);
|
|
74
|
+
throw new Error(`Configuration already exists: ${destination}`);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code !== "ENOENT")
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
const client = new ModelServiceClient(DEFAULT_PROVIDER);
|
|
81
|
+
const catalog = await client.catalog(undefined, true);
|
|
82
|
+
const config = { schemaVersion: 1, profile: "mswarm-strata", providers: { mswarm: { ...DEFAULT_PROVIDER } }, models: {}, bindings: {}, routing: { strategy: "capability-and-capacity", requiredBackends: { lead: ["strata"], architect: ["strata"], worker: ["strata"], reviewer: ["strata"] } } };
|
|
83
|
+
const mediaJobsUrl = new URL("/v1/media/jobs", DEFAULT_PROVIDER.baseUrl).toString();
|
|
84
|
+
try {
|
|
85
|
+
const capability = await client.json(mediaJobsUrl);
|
|
86
|
+
if (capability.protocol === "mswarm-media-jobs-v1")
|
|
87
|
+
config.providers.mswarm.mediaJobsUrl = mediaJobsUrl;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (!(error instanceof ModelServiceError) || ![404, 405].includes(error.status))
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
const lead = catalog.filter(entry => entry.backend === "strata" && entry.supportsTools && entry.health === "healthy");
|
|
94
|
+
if (!lead.length)
|
|
95
|
+
throw new Error("No entitled healthy Strata tool route is advertised; setup cannot invent a fallback");
|
|
96
|
+
for (const entry of lead)
|
|
97
|
+
config.models[entry.id] = { provider: "mswarm", catalogId: entry.id, revision: String(entry.raw.digest ?? entry.raw.revision ?? "catalog-unversioned"), operations: ["chat.completions"], backend: "strata", contextWindow: entry.contextWindow ?? 32768, maxOutputTokens: 4096, supportsTools: true, supportsStreaming: false };
|
|
98
|
+
config.bindings.lead = { candidates: lead.map(entry => entry.id) };
|
|
99
|
+
for (const role of ["architect", "worker", "reviewer", "planner", "compactor", "router", "adjudicator", "synthesizer", "verifier", "repair"])
|
|
100
|
+
config.bindings[role] = { useBinding: "lead" };
|
|
101
|
+
config.bindings.helper = { enabled: false };
|
|
102
|
+
config.bindings.embedding = { enabled: false };
|
|
103
|
+
// Only explicit operator contracts can initialize an index. Incompatible
|
|
104
|
+
// embedding spaces never become replicas merely because dimensions match.
|
|
105
|
+
let embeddingIdentity;
|
|
106
|
+
for (const entry of [...catalog].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
107
|
+
if (entry.health !== "healthy" || !entry.operations.includes("embeddings") || entry.raw.load_balanced === true)
|
|
108
|
+
continue;
|
|
109
|
+
const digest = entry.raw.digest;
|
|
110
|
+
const descriptor = Array.isArray(entry.raw.operations) ? entry.raw.operations.find(item => item && typeof item === "object" && item.type === "embeddings") : undefined;
|
|
111
|
+
const dimensions = descriptor?.limits?.embedding_dimensions;
|
|
112
|
+
if (typeof digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(digest) || !Number.isSafeInteger(dimensions) || dimensions < 1 || dimensions > 4096)
|
|
113
|
+
continue;
|
|
114
|
+
let embedding;
|
|
115
|
+
try {
|
|
116
|
+
embedding = validateEmbeddingPreprocessing(entry.raw.embedding);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const identity = JSON.stringify({ digest, dimensions, ...embedding });
|
|
122
|
+
if (embeddingIdentity !== undefined && embeddingIdentity !== identity)
|
|
123
|
+
continue;
|
|
124
|
+
embeddingIdentity = identity;
|
|
125
|
+
const previous = config.models[entry.id];
|
|
126
|
+
config.models[entry.id] = { ...previous, provider: "mswarm", catalogId: entry.id, revision: digest,
|
|
127
|
+
operations: [...new Set([...(previous?.operations ?? []), "embeddings"])],
|
|
128
|
+
contextWindow: previous?.contextWindow ?? entry.contextWindow ?? 8192, maxOutputTokens: previous?.maxOutputTokens ?? 1,
|
|
129
|
+
embeddingDimensions: dimensions, embedding };
|
|
130
|
+
const candidates = config.bindings.embedding.candidates ?? [];
|
|
131
|
+
config.bindings.embedding = { candidates: [...candidates, entry.id], embeddingSpace: "catalog-pinned-v1" };
|
|
132
|
+
}
|
|
133
|
+
for (const [binding, operation] of [["image", "images.generations"], ["audio", "audio.generations"], ["video", "videos.generations"]]) {
|
|
134
|
+
const entries = catalog.filter(entry => entry.health === "healthy" && entry.operations.includes(operation));
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const previous = config.models[entry.id];
|
|
137
|
+
config.models[entry.id] = previous ? { ...previous, operations: [...new Set([...previous.operations, operation])] }
|
|
138
|
+
: { provider: "mswarm", catalogId: entry.id, revision: String(entry.raw.revision ?? "catalog-unversioned"), operations: [operation], contextWindow: 8192, maxOutputTokens: 1 };
|
|
139
|
+
}
|
|
140
|
+
config.bindings[binding] = entries.length ? { candidates: entries.map(entry => entry.id) } : { enabled: false };
|
|
141
|
+
}
|
|
142
|
+
if (options.prepareRuntime && config.bindings.embedding.enabled !== false) {
|
|
143
|
+
const runtime = await options.prepareRuntime();
|
|
144
|
+
config.docdex = { binary: runtime.binary, binarySha256: runtime.binarySha256 };
|
|
145
|
+
}
|
|
146
|
+
validateOrchestrationConfig(config);
|
|
147
|
+
await atomicWriteJson(destination, config);
|
|
148
|
+
return { config, source: destination, hash: orchestrationHash(config) };
|
|
149
|
+
};
|
|
150
|
+
const usageFor = async (resolved) => {
|
|
151
|
+
const entries = await Promise.all(Object.entries(resolved.config.providers).map(async ([id, provider]) => { try {
|
|
152
|
+
return [id, await new ModelServiceClient(provider).usage()];
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
return [id, { available: false, error: error instanceof Error ? error.message : String(error) }];
|
|
156
|
+
} }));
|
|
157
|
+
return Object.fromEntries(entries);
|
|
158
|
+
};
|
|
159
|
+
export const terminalDoctor = async (configPath) => {
|
|
160
|
+
const report = { node: process.version, runtimeSupported: Number(process.versions.node.split(".")[0]) >= 20, isolation: executorStatus() };
|
|
161
|
+
report.nodeToolchain = discoverHostToolchain(process.cwd()).map(runtime => ({ name: runtime.name, executable: runtime.executable, readOnlyRoot: runtime.readOnlyRoot }));
|
|
162
|
+
try {
|
|
163
|
+
const resolved = await loadOrchestrationConfig(configPath);
|
|
164
|
+
report.configuration = { source: resolved.source, hash: resolved.hash, profile: resolved.config.profile };
|
|
165
|
+
const routes = {};
|
|
166
|
+
for (const binding of ["lead", "reviewer", "helper", "embedding", "image", "audio", "video"]) {
|
|
167
|
+
if (resolved.config.bindings[binding]?.enabled === false) {
|
|
168
|
+
routes[binding] = { enabled: false };
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const selected = await new ConfiguredProvider(resolved, binding).select(binding === "embedding" ? "embeddings" : binding === "image" ? "images.generations" : binding === "audio" ? "audio.generations" : binding === "video" ? "videos.generations" : "chat.completions", undefined, binding === "lead");
|
|
173
|
+
routes[binding] = { model: selected.id, backend: selected.actual.backend, health: selected.actual.health };
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
routes[binding] = { error: error instanceof Error ? error.message : String(error) };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
report.routes = routes;
|
|
180
|
+
report.mediaRecovery = Object.fromEntries(Object.entries(resolved.config.providers).map(([id, provider]) => [id, { protocol: provider.mediaJobsUrl ? "mswarm-media-jobs-v1" : "synchronous", reconnect: provider.mediaJobsUrl ? "Recover by durable job id" : "Only complete local receipts can be recovered; ambiguous remote jobs require reconciliation" }]));
|
|
181
|
+
if (resolved.config.bindings.embedding?.enabled !== false && resolved.config.bindings.embedding) {
|
|
182
|
+
try {
|
|
183
|
+
if (!resolved.config.docdex?.binary)
|
|
184
|
+
throw new Error("Managed Docdex binary is not configured; run setup with a qualified release");
|
|
185
|
+
report.docdex = { mode: "managed", available: true, ...await inspectManagedDocdexRuntime(resolved.config.docdex.binary, resolved.config.docdex.binarySha256) };
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
report.docdex = { mode: "managed", available: false, error: error instanceof Error ? error.message : String(error), action: "Install a qualified runtime and update docdex.binary and binarySha256" };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
report.configuration = { error: error instanceof Error ? error.message : String(error) };
|
|
194
|
+
}
|
|
195
|
+
if (!report.docdex) {
|
|
196
|
+
try {
|
|
197
|
+
const response = await fetch("http://127.0.0.1:28491/healthz", { signal: AbortSignal.timeout(3000) });
|
|
198
|
+
report.docdex = { mode: "external", available: response.ok, status: response.status, configuredEmbeddings: false };
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
report.docdex = { mode: "external", available: false, action: "Configure a qualified embedding binding and managed Docdex runtime" };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return report;
|
|
205
|
+
};
|
|
206
|
+
export class TerminalCommand {
|
|
207
|
+
static async run(argv, host = {}) {
|
|
208
|
+
try {
|
|
209
|
+
await this.execute(argv, host);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (!argv.includes("--json"))
|
|
213
|
+
throw error;
|
|
214
|
+
const message = redactSecretValues(error instanceof Error ? error.message : String(error));
|
|
215
|
+
process.stdout.write(`${JSON.stringify({ schemaVersion: 1, type: "command_failed", data: { message, completed: false } })}\n`);
|
|
216
|
+
process.exitCode = /cancel/i.test(message) ? 130 : 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
static async execute(argv, host) {
|
|
220
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
221
|
+
process.stdout.write(`${HELP}\n`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const args = parseTerminalArguments(argv);
|
|
225
|
+
if (args.projectTask && !host.projectTaskAuthority)
|
|
226
|
+
throw new Error("Use mcoda codali with --project-task to attach to the existing mcoda task repository");
|
|
227
|
+
if (args.stdin && ["chat", "exec"].includes(args.command)) {
|
|
228
|
+
let text = "";
|
|
229
|
+
for await (const chunk of process.stdin) {
|
|
230
|
+
text += chunk.toString();
|
|
231
|
+
if (Buffer.byteLength(text) > 262144)
|
|
232
|
+
throw new Error("Input prompt exceeds 256 KiB");
|
|
233
|
+
}
|
|
234
|
+
args.prompt = [args.prompt, text.trim()].filter(Boolean).join("\n\n");
|
|
235
|
+
}
|
|
236
|
+
const output = (value) => { const text = redactSecretValues(typeof value === "string" ? value : JSON.stringify(value, null, args.json ? 0 : 2)); process.stdout.write(`${args.json ? text : terminalText(text)}\n`); };
|
|
237
|
+
if (args.command === "login") {
|
|
238
|
+
const key = await readTerminalCredential(args.stdin);
|
|
239
|
+
if (!key)
|
|
240
|
+
throw new Error("API key is required");
|
|
241
|
+
await new ModelServiceClient(DEFAULT_PROVIDER, key).catalog(undefined, true);
|
|
242
|
+
await setTerminalCredential(key);
|
|
243
|
+
output("mSwarm credential saved and validated.");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (args.command === "logout") {
|
|
247
|
+
await setTerminalCredential();
|
|
248
|
+
output("Stored Codali mSwarm credential removed. Environment credentials, if set, take precedence.");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (args.command === "auth-status") {
|
|
252
|
+
try {
|
|
253
|
+
await providerCredential(DEFAULT_PROVIDER);
|
|
254
|
+
const catalog = await new ModelServiceClient(DEFAULT_PROVIDER).catalog();
|
|
255
|
+
output({ authenticated: true, entitledModels: catalog.length });
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
output({ authenticated: false, error: error instanceof Error ? error.message : String(error) });
|
|
259
|
+
process.exitCode = 1;
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (args.command === "setup") {
|
|
264
|
+
const resolved = await setupTerminalConfig(args.config ?? process.env.CODALI_ORCHESTRATION_CONFIG ?? CONFIG_PATH, { prepareRuntime: () => ensureManagedDocdexRuntime({ binary: args.docdexBinary }) });
|
|
265
|
+
const docdex = resolved.config.docdex ? { mode: "managed", ...resolved.config.docdex, initialization: "Private repository indexing starts with the first session" }
|
|
266
|
+
: { mode: "unavailable", reason: "No entitled embedding route advertises a pinned model and complete preprocessing contract" };
|
|
267
|
+
output({ configuration: resolved.source, hash: resolved.hash, bindings: resolved.config.bindings, docdex });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (args.command === "doctor") {
|
|
271
|
+
output(await terminalDoctor(args.config));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
let resolved = await loadOrchestrationConfig(args.config).catch(error => { throw new Error(`Cannot load orchestration configuration; run codali setup. ${error instanceof Error ? error.message : String(error)}`); });
|
|
275
|
+
const interactive = args.command === "chat" && !!process.stdin.isTTY && !args.json;
|
|
276
|
+
let rl;
|
|
277
|
+
let pendingApproval;
|
|
278
|
+
let closed = false;
|
|
279
|
+
let lastCtrlC = 0;
|
|
280
|
+
let activeRun;
|
|
281
|
+
const queued = [];
|
|
282
|
+
const approve = async (request, signal) => {
|
|
283
|
+
if (!interactive || closed)
|
|
284
|
+
return { allow: false };
|
|
285
|
+
const reviewableArgs = JSON.stringify(request.args, (key, value) => /authorization|api[_-]?key|password|credential|secret|signature|token/i.test(key) ? "[redacted]" : value, 2);
|
|
286
|
+
process.stderr.write(terminalText(`\nPermission: ${request.tool} (${request.kind})\n${reviewableArgs}\nAllow once, for this session, or deny? [once/session/deny]\n`));
|
|
287
|
+
return new Promise(resolve => {
|
|
288
|
+
const abort = () => { pendingApproval = undefined; resolve({ allow: false }); };
|
|
289
|
+
pendingApproval = decision => { signal?.removeEventListener("abort", abort); resolve({ allow: decision !== "deny", scope: decision === "session" ? "session" : "once" }); };
|
|
290
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
291
|
+
if (signal?.aborted)
|
|
292
|
+
abort();
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const textStream = new TerminalTextStream();
|
|
296
|
+
const controller = new SessionController({ workspaceRoot: await fs.realpath(args.workspaceRoot), resolved, effort: args.effort, permissions: args.permissions, approve, allowWeb: args.allowWeb, allowMemory: args.allowMemory, allowMedia: args.allowMedia, allowNetwork: args.allowNetwork,
|
|
297
|
+
projectTaskKey: args.projectTask, projectTaskAuthority: host.projectTaskAuthority,
|
|
298
|
+
onEvent: (event) => {
|
|
299
|
+
if (args.json) {
|
|
300
|
+
output(event);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (event.type === "turn_started")
|
|
304
|
+
textStream.reset();
|
|
305
|
+
if (["tool_intent", "turn_result", "turn_failed"].includes(event.type) && !args.plain)
|
|
306
|
+
process.stdout.write(textStream.finish());
|
|
307
|
+
if (event.type === "token" && typeof event.data.content === "string" && !args.plain)
|
|
308
|
+
process.stdout.write(textStream.write(event.data.content));
|
|
309
|
+
else if (event.type === "model_route")
|
|
310
|
+
process.stderr.write(terminalText(`\n[${event.data.binding}: ${event.data.modelId}${event.data.host ? ` on ${event.data.host}` : ""}]\n`));
|
|
311
|
+
else if (event.type === "tool_intent") {
|
|
312
|
+
const receipt = event.data.receipt;
|
|
313
|
+
process.stderr.write(terminalText(`[${receipt.tool}]\n`));
|
|
314
|
+
}
|
|
315
|
+
else if (event.type === "process_output" && typeof event.data.content === "string")
|
|
316
|
+
process.stderr.write(terminalText(event.data.content));
|
|
317
|
+
else if (event.type === "index_progress")
|
|
318
|
+
process.stderr.write(`[Docdex: ${terminalText(String(event.data.status))}]\n`);
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
const showPrompt = () => { if (!closed && !controller.busy && !pendingApproval) {
|
|
322
|
+
rl?.setPrompt("codali> ");
|
|
323
|
+
rl?.prompt();
|
|
324
|
+
} };
|
|
325
|
+
const run = (prompt) => {
|
|
326
|
+
activeRun = (async () => {
|
|
327
|
+
try {
|
|
328
|
+
if (controller.state?.status === "completed" && prompt)
|
|
329
|
+
await controller.start(prompt);
|
|
330
|
+
const result = await controller.run(prompt);
|
|
331
|
+
if (!args.json)
|
|
332
|
+
output(`\n${result.content}`);
|
|
333
|
+
if (!interactive)
|
|
334
|
+
process.exitCode = result.completed || args.permissions === "plan" ? 0 : 2;
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
const message = redactSecretValues(error instanceof Error ? error.message : String(error));
|
|
338
|
+
if (args.json)
|
|
339
|
+
output({ schemaVersion: 1, type: "command_failed", sessionId: controller.sessionId, data: { message, completed: false } });
|
|
340
|
+
else
|
|
341
|
+
process.stderr.write(`${terminalText(message)}\n`);
|
|
342
|
+
if (!interactive)
|
|
343
|
+
process.exitCode = /cancel/i.test(String(error)) ? 130 : 1;
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
pendingApproval?.("deny");
|
|
347
|
+
pendingApproval = undefined;
|
|
348
|
+
activeRun = undefined;
|
|
349
|
+
if (!closed && queued.length) {
|
|
350
|
+
const next = queued.shift();
|
|
351
|
+
await controller.steer(next);
|
|
352
|
+
run();
|
|
353
|
+
}
|
|
354
|
+
else
|
|
355
|
+
showPrompt();
|
|
356
|
+
}
|
|
357
|
+
})();
|
|
358
|
+
};
|
|
359
|
+
const command = async (line) => {
|
|
360
|
+
const [name, ...parts] = line.trim().split(/\s+/);
|
|
361
|
+
const value = parts.join(" ");
|
|
362
|
+
if (name === "/cancel") {
|
|
363
|
+
controller.cancel();
|
|
364
|
+
pendingApproval?.("deny");
|
|
365
|
+
pendingApproval = undefined;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (name === "/quit") {
|
|
369
|
+
closed = true;
|
|
370
|
+
queued.length = 0;
|
|
371
|
+
controller.cancel();
|
|
372
|
+
pendingApproval?.("deny");
|
|
373
|
+
pendingApproval = undefined;
|
|
374
|
+
rl?.close();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (name === "/help")
|
|
378
|
+
output(HELP);
|
|
379
|
+
else if (name === "/status")
|
|
380
|
+
output(controller.status());
|
|
381
|
+
else if (name === "/usage")
|
|
382
|
+
output({ session: controller.usageSnapshot(), server: await usageFor(resolved) });
|
|
383
|
+
else if (name === "/permissions") {
|
|
384
|
+
if (value === "clear")
|
|
385
|
+
await controller.clearPermissionGrants();
|
|
386
|
+
else if (value) {
|
|
387
|
+
if (!["plan", "ask", "workspace"].includes(value))
|
|
388
|
+
throw new Error("Expected plan, ask, workspace or clear");
|
|
389
|
+
controller.gate.setProfile(value);
|
|
390
|
+
}
|
|
391
|
+
output(controller.gate.inspect());
|
|
392
|
+
}
|
|
393
|
+
else if (name === "/effort") {
|
|
394
|
+
if (value) {
|
|
395
|
+
if (controller.busy)
|
|
396
|
+
throw new Error("Change effort between turns");
|
|
397
|
+
if (!["low", "medium", "high", "max"].includes(value))
|
|
398
|
+
throw new Error("Expected low, medium, high or max");
|
|
399
|
+
controller.effort = value;
|
|
400
|
+
}
|
|
401
|
+
output({ effort: controller.effort });
|
|
402
|
+
}
|
|
403
|
+
else if (name === "/config") {
|
|
404
|
+
if (value === "reload") {
|
|
405
|
+
const next = await loadOrchestrationConfig(resolved.source);
|
|
406
|
+
await controller.reloadConfiguration(next);
|
|
407
|
+
resolved = next;
|
|
408
|
+
}
|
|
409
|
+
else if (value)
|
|
410
|
+
throw new Error("Usage: /config [reload]");
|
|
411
|
+
output({ source: resolved.source, hash: resolved.hash, configuration: resolved.config });
|
|
412
|
+
}
|
|
413
|
+
else if (name === "/models")
|
|
414
|
+
output(resolved.config.bindings);
|
|
415
|
+
else if (name === "/goal")
|
|
416
|
+
output(controller.state ? { original: controller.state.originalObjective, objective: controller.state.objective, requirements: controller.state.requirements, amendments: controller.state.amendments } : {});
|
|
417
|
+
else if (name === "/tasks")
|
|
418
|
+
output({ tasks: controller.state?.tasks ?? [], media: await controller.artifacts() });
|
|
419
|
+
else if (name === "/plan")
|
|
420
|
+
output({ revision: controller.state?.planRevision ?? 1, phases: controller.state?.phases ?? [], tasks: controller.state?.tasks ?? [], history: controller.state?.planHistory?.map(p => ({ revision: p.revision, goalRevision: p.goalRevision, at: p.at, reason: p.reason })) ?? [] });
|
|
421
|
+
else if (name === "/decisions")
|
|
422
|
+
output(controller.state?.decisions ?? []);
|
|
423
|
+
else if (name === "/context")
|
|
424
|
+
output({ goal: controller.state, configHash: resolved.hash });
|
|
425
|
+
else if (name === "/diff")
|
|
426
|
+
output(await controller.diff());
|
|
427
|
+
else if (name === "/sessions")
|
|
428
|
+
output(await controller.store.listSessions());
|
|
429
|
+
else if (name === "/artifacts")
|
|
430
|
+
output(await controller.artifacts());
|
|
431
|
+
else if (name === "/retention") {
|
|
432
|
+
const days = value ? Number(value) : 30;
|
|
433
|
+
if (!Number.isFinite(days) || days < 0)
|
|
434
|
+
throw new Error("Retention days must be nonnegative");
|
|
435
|
+
output({ dryRun: true, days, candidates: (await controller.store.listSessions()).filter(s => s.status === "completed" && Date.parse(s.updatedAt) < Date.now() - days * 86400000).map(s => ({ id: s.sessionId, updatedAt: s.updatedAt })), action: "Use /delete-session ID to remove a reviewed session and its local artifacts" });
|
|
436
|
+
}
|
|
437
|
+
else if (controller.busy)
|
|
438
|
+
throw new Error("This command requires an idle turn; use /cancel or queue an instruction");
|
|
439
|
+
else if (name === "/resume") {
|
|
440
|
+
await controller.resume(value);
|
|
441
|
+
output(controller.status());
|
|
442
|
+
}
|
|
443
|
+
else if (name === "/fork") {
|
|
444
|
+
output({ sessionId: await controller.fork(), verification: "Fresh verification required; permissions were reset" });
|
|
445
|
+
}
|
|
446
|
+
else if (name === "/delete-session") {
|
|
447
|
+
await controller.deleteSession(value);
|
|
448
|
+
output({ deleted: value });
|
|
449
|
+
}
|
|
450
|
+
else if (name === "/new") {
|
|
451
|
+
if (!value)
|
|
452
|
+
throw new Error("Provide the new objective");
|
|
453
|
+
await controller.start(value);
|
|
454
|
+
run();
|
|
455
|
+
}
|
|
456
|
+
else if (name === "/steer") {
|
|
457
|
+
if (!value)
|
|
458
|
+
throw new Error("Provide the correction");
|
|
459
|
+
await controller.steer(value);
|
|
460
|
+
output(controller.state);
|
|
461
|
+
}
|
|
462
|
+
else if (name === "/continue")
|
|
463
|
+
run();
|
|
464
|
+
else if (name === "/checkpoint") {
|
|
465
|
+
await controller.checkpoint(value || "User checkpoint");
|
|
466
|
+
output("Checkpoint saved.");
|
|
467
|
+
}
|
|
468
|
+
else if (name === "/compact") {
|
|
469
|
+
await controller.compact();
|
|
470
|
+
output("Context compacted.");
|
|
471
|
+
}
|
|
472
|
+
else if (name === "/review") {
|
|
473
|
+
if (!controller.state)
|
|
474
|
+
throw new Error("No goal to review");
|
|
475
|
+
await controller.steer("Review the current changes against the original goal and architecture decisions; fix concrete findings and reverify.");
|
|
476
|
+
run();
|
|
477
|
+
}
|
|
478
|
+
else if (name === "/reconcile") {
|
|
479
|
+
const [id, outcome, ...note] = parts;
|
|
480
|
+
if (!["success", "failed"].includes(outcome))
|
|
481
|
+
throw new Error("Usage: /reconcile ID success|failed observed outcome");
|
|
482
|
+
await controller.reconcile(id, outcome, note.join(" "));
|
|
483
|
+
output("Reconciliation recorded.");
|
|
484
|
+
}
|
|
485
|
+
else if (name === "/reconcile-media") {
|
|
486
|
+
const [id, outcome, ...note] = parts;
|
|
487
|
+
if (outcome !== "failed")
|
|
488
|
+
throw new Error("Usage: /reconcile-media ID failed confirmed remote failure or cancellation; a lost response alone is not confirmation");
|
|
489
|
+
output(await controller.mediaStore().reconcileFailure(id, note.join(" ")).then(job => ({ id: job.id, status: job.status, reconciliation: job.reconciliation })));
|
|
490
|
+
}
|
|
491
|
+
else if (name === "/export") {
|
|
492
|
+
if (!controller.sessionId)
|
|
493
|
+
throw new Error("No active session");
|
|
494
|
+
output({ schemaVersion: 1, state: controller.state, events: await controller.store.readTranscript(controller.sessionId) });
|
|
495
|
+
}
|
|
496
|
+
else
|
|
497
|
+
throw new Error(`Unknown command ${name}; use /help`);
|
|
498
|
+
};
|
|
499
|
+
const onSignal = () => { const now = Date.now(); if (!controller.busy || now - lastCtrlC < 1000) {
|
|
500
|
+
closed = true;
|
|
501
|
+
rl?.close();
|
|
502
|
+
} lastCtrlC = now; controller.cancel(); pendingApproval?.("deny"); pendingApproval = undefined; };
|
|
503
|
+
const onTerminate = () => { closed = true; queued.length = 0; controller.cancel(); pendingApproval?.("deny"); pendingApproval = undefined; rl?.close(); };
|
|
504
|
+
process.on("SIGINT", onSignal);
|
|
505
|
+
process.on("SIGTERM", onTerminate);
|
|
506
|
+
try {
|
|
507
|
+
if (["sessions", "status", "resume", "inspect", "checkpoint", "amend"].includes(args.command)) {
|
|
508
|
+
if (args.command === "sessions") {
|
|
509
|
+
output(await controller.store.listSessions());
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const [id, ...rest] = args.prompt.split(" ");
|
|
513
|
+
await controller.resume(id);
|
|
514
|
+
if (args.command === "checkpoint")
|
|
515
|
+
await controller.checkpoint(rest.join(" "));
|
|
516
|
+
else if (args.command === "amend")
|
|
517
|
+
await controller.steer(rest.join(" "));
|
|
518
|
+
else if (args.command === "resume") {
|
|
519
|
+
if (controller.state?.status === "completed")
|
|
520
|
+
output(controller.status());
|
|
521
|
+
else {
|
|
522
|
+
run();
|
|
523
|
+
await activeRun;
|
|
524
|
+
}
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
output(controller.status());
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (!interactive) {
|
|
531
|
+
if (!args.prompt)
|
|
532
|
+
throw new Error("Provide a prompt for noninteractive execution");
|
|
533
|
+
run(args.prompt);
|
|
534
|
+
await activeRun;
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
output(`Codali — ${resolved.config.profile}\nWorkspace: ${controller.workspaceRoot}\nUse /help for commands. Permissions: ${args.permissions}; effort: ${args.effort}.`);
|
|
538
|
+
const historyFile = path.join(path.dirname(controller.store.storageDir), "terminal-history.json");
|
|
539
|
+
let history = [];
|
|
540
|
+
try {
|
|
541
|
+
const value = JSON.parse(await fs.readFile(historyFile, "utf8"));
|
|
542
|
+
if (Array.isArray(value))
|
|
543
|
+
history = value.filter(v => typeof v === "string").slice(0, 500);
|
|
544
|
+
}
|
|
545
|
+
catch { /* First terminal session has no history. */ }
|
|
546
|
+
const pasteInput = args.plain ? undefined : new TerminalInput({ onPaste: text => { if (rl)
|
|
547
|
+
insertTerminalPaste(rl, text); }, onRejected: message => { process.stderr.write(`${message}\n`); showPrompt(); } });
|
|
548
|
+
const input = pasteInput ? Object.assign(pasteInput, { isTTY: process.stdin.isTTY, setRawMode: (mode) => process.stdin.setRawMode(mode) }) : process.stdin;
|
|
549
|
+
rl = createInterface({ input, output: process.stdout, terminal: !args.plain, history, historySize: 500, removeHistoryDuplicates: true, completer: (line) => { const matches = COMMANDS.filter(c => c.startsWith(line)); return [matches.length ? matches : COMMANDS, line]; } });
|
|
550
|
+
if (pasteInput) {
|
|
551
|
+
process.stdout.write("\u001b[?2004h");
|
|
552
|
+
process.stdin.pipe(pasteInput);
|
|
553
|
+
}
|
|
554
|
+
rl.on("history", values => { history = values.map(value => redactSecretValues(value)); });
|
|
555
|
+
rl.on("SIGINT", onSignal);
|
|
556
|
+
let chain = Promise.resolve();
|
|
557
|
+
rl.on("line", line => {
|
|
558
|
+
chain = chain.then(async () => {
|
|
559
|
+
if (pendingApproval) {
|
|
560
|
+
const response = line.trim().toLowerCase();
|
|
561
|
+
if (!["once", "session", "deny"].includes(response)) {
|
|
562
|
+
process.stderr.write("Enter once, session or deny.\n");
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const resolve = pendingApproval;
|
|
566
|
+
pendingApproval = undefined;
|
|
567
|
+
resolve(response);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (!line.trim()) {
|
|
571
|
+
showPrompt();
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (line.startsWith("/") && !/[\r\n]/.test(line)) {
|
|
575
|
+
await command(line);
|
|
576
|
+
showPrompt();
|
|
577
|
+
}
|
|
578
|
+
else if (controller.busy) {
|
|
579
|
+
queued.push(line);
|
|
580
|
+
output("Instruction queued for the next turn. Use /cancel to interrupt first.");
|
|
581
|
+
}
|
|
582
|
+
else if (controller.state && controller.state.status !== "completed") {
|
|
583
|
+
await controller.steer(line);
|
|
584
|
+
run();
|
|
585
|
+
}
|
|
586
|
+
else
|
|
587
|
+
run(line);
|
|
588
|
+
}).catch(error => { process.stderr.write(`${terminalText(error instanceof Error ? error.message : String(error))}\n`); showPrompt(); });
|
|
589
|
+
});
|
|
590
|
+
if (args.prompt)
|
|
591
|
+
run(args.prompt);
|
|
592
|
+
else
|
|
593
|
+
showPrompt();
|
|
594
|
+
await new Promise(resolve => rl.once("close", () => {
|
|
595
|
+
closed = true;
|
|
596
|
+
controller.cancel();
|
|
597
|
+
pendingApproval?.("deny");
|
|
598
|
+
pendingApproval = undefined;
|
|
599
|
+
if (pasteInput) {
|
|
600
|
+
process.stdin.unpipe(pasteInput);
|
|
601
|
+
pasteInput.destroy();
|
|
602
|
+
process.stdout.write("\u001b[?2004l");
|
|
603
|
+
}
|
|
604
|
+
resolve();
|
|
605
|
+
}));
|
|
606
|
+
await chain;
|
|
607
|
+
await activeRun;
|
|
608
|
+
await atomicWriteJson(historyFile, history);
|
|
609
|
+
}
|
|
610
|
+
finally {
|
|
611
|
+
process.off("SIGINT", onSignal);
|
|
612
|
+
process.off("SIGTERM", onTerminate);
|
|
613
|
+
rl?.close();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TerminalCredentials.d.ts","sourceRoot":"","sources":["../../src/cli/TerminalCredentials.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,qBAAqB,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAgBtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAU,WAAW,OAAO,KAAG,OAAO,CAAC,MAAM,CAqB/E,CAAC"}
|