@xenosystem/acp-agent 0.1.1
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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +22 -0
- package/dist/acpAgentServer.d.ts +43 -0
- package/dist/acpAgentServer.d.ts.map +1 -0
- package/dist/acpAgentServer.js +450 -0
- package/dist/acpAgentServer.js.map +1 -0
- package/dist/adapters/adapterContractVersion.d.ts +24 -0
- package/dist/adapters/adapterContractVersion.d.ts.map +1 -0
- package/dist/adapters/adapterContractVersion.js +29 -0
- package/dist/adapters/adapterContractVersion.js.map +1 -0
- package/dist/adapters/adapterDiagnostics.d.ts +19 -0
- package/dist/adapters/adapterDiagnostics.d.ts.map +1 -0
- package/dist/adapters/adapterDiagnostics.js +18 -0
- package/dist/adapters/adapterDiagnostics.js.map +1 -0
- package/dist/adapters/adapterErrors.d.ts +19 -0
- package/dist/adapters/adapterErrors.d.ts.map +1 -0
- package/dist/adapters/adapterErrors.js +23 -0
- package/dist/adapters/adapterErrors.js.map +1 -0
- package/dist/adapters/adapterEventMapper.d.ts +22 -0
- package/dist/adapters/adapterEventMapper.d.ts.map +1 -0
- package/dist/adapters/adapterEventMapper.js +70 -0
- package/dist/adapters/adapterEventMapper.js.map +1 -0
- package/dist/adapters/adapterEventParser.d.ts +16 -0
- package/dist/adapters/adapterEventParser.d.ts.map +1 -0
- package/dist/adapters/adapterEventParser.js +95 -0
- package/dist/adapters/adapterEventParser.js.map +1 -0
- package/dist/adapters/adapterEventProtocol.d.ts +85 -0
- package/dist/adapters/adapterEventProtocol.d.ts.map +1 -0
- package/dist/adapters/adapterEventProtocol.js +74 -0
- package/dist/adapters/adapterEventProtocol.js.map +1 -0
- package/dist/adapters/adapterFailureTaxonomy.d.ts +19 -0
- package/dist/adapters/adapterFailureTaxonomy.d.ts.map +1 -0
- package/dist/adapters/adapterFailureTaxonomy.js +62 -0
- package/dist/adapters/adapterFailureTaxonomy.js.map +1 -0
- package/dist/adapters/adapterHarnessTypes.d.ts +103 -0
- package/dist/adapters/adapterHarnessTypes.d.ts.map +1 -0
- package/dist/adapters/adapterHarnessTypes.js +15 -0
- package/dist/adapters/adapterHarnessTypes.js.map +1 -0
- package/dist/adapters/adapterProcessHarness.d.ts +23 -0
- package/dist/adapters/adapterProcessHarness.d.ts.map +1 -0
- package/dist/adapters/adapterProcessHarness.js +235 -0
- package/dist/adapters/adapterProcessHarness.js.map +1 -0
- package/dist/adapters/providerContractTypes.d.ts +64 -0
- package/dist/adapters/providerContractTypes.d.ts.map +1 -0
- package/dist/adapters/providerContractTypes.js +79 -0
- package/dist/adapters/providerContractTypes.js.map +1 -0
- package/dist/adapters/testAdapterProvider.d.ts +19 -0
- package/dist/adapters/testAdapterProvider.d.ts.map +1 -0
- package/dist/adapters/testAdapterProvider.js +95 -0
- package/dist/adapters/testAdapterProvider.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1059 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/providerApprovalStore.d.ts +39 -0
- package/dist/config/providerApprovalStore.d.ts.map +1 -0
- package/dist/config/providerApprovalStore.js +87 -0
- package/dist/config/providerApprovalStore.js.map +1 -0
- package/dist/config/providerApprovalTypes.d.ts +30 -0
- package/dist/config/providerApprovalTypes.d.ts.map +1 -0
- package/dist/config/providerApprovalTypes.js +11 -0
- package/dist/config/providerApprovalTypes.js.map +1 -0
- package/dist/config/providerPolicyConfig.d.ts +31 -0
- package/dist/config/providerPolicyConfig.d.ts.map +1 -0
- package/dist/config/providerPolicyConfig.js +111 -0
- package/dist/config/providerPolicyConfig.js.map +1 -0
- package/dist/config/providerPolicyDiagnostics.d.ts +49 -0
- package/dist/config/providerPolicyDiagnostics.d.ts.map +1 -0
- package/dist/config/providerPolicyDiagnostics.js +76 -0
- package/dist/config/providerPolicyDiagnostics.js.map +1 -0
- package/dist/config/providerPolicyHash.d.ts +14 -0
- package/dist/config/providerPolicyHash.d.ts.map +1 -0
- package/dist/config/providerPolicyHash.js +65 -0
- package/dist/config/providerPolicyHash.js.map +1 -0
- package/dist/config/providerPolicySchema.d.ts +32 -0
- package/dist/config/providerPolicySchema.d.ts.map +1 -0
- package/dist/config/providerPolicySchema.js +130 -0
- package/dist/config/providerPolicySchema.js.map +1 -0
- package/dist/config/providerRunnability.d.ts +36 -0
- package/dist/config/providerRunnability.d.ts.map +1 -0
- package/dist/config/providerRunnability.js +72 -0
- package/dist/config/providerRunnability.js.map +1 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +45 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/dist/process/providerProcessAudit.d.ts +14 -0
- package/dist/process/providerProcessAudit.d.ts.map +1 -0
- package/dist/process/providerProcessAudit.js +27 -0
- package/dist/process/providerProcessAudit.js.map +1 -0
- package/dist/process/providerProcessDiagnostics.d.ts +30 -0
- package/dist/process/providerProcessDiagnostics.d.ts.map +1 -0
- package/dist/process/providerProcessDiagnostics.js +51 -0
- package/dist/process/providerProcessDiagnostics.js.map +1 -0
- package/dist/process/providerProcessEnvironment.d.ts +29 -0
- package/dist/process/providerProcessEnvironment.d.ts.map +1 -0
- package/dist/process/providerProcessEnvironment.js +37 -0
- package/dist/process/providerProcessEnvironment.js.map +1 -0
- package/dist/process/providerProcessErrors.d.ts +21 -0
- package/dist/process/providerProcessErrors.d.ts.map +1 -0
- package/dist/process/providerProcessErrors.js +25 -0
- package/dist/process/providerProcessErrors.js.map +1 -0
- package/dist/process/providerProcessOutput.d.ts +24 -0
- package/dist/process/providerProcessOutput.d.ts.map +1 -0
- package/dist/process/providerProcessOutput.js +52 -0
- package/dist/process/providerProcessOutput.js.map +1 -0
- package/dist/process/providerProcessSupervisor.d.ts +40 -0
- package/dist/process/providerProcessSupervisor.d.ts.map +1 -0
- package/dist/process/providerProcessSupervisor.js +305 -0
- package/dist/process/providerProcessSupervisor.js.map +1 -0
- package/dist/process/providerProcessTypes.d.ts +114 -0
- package/dist/process/providerProcessTypes.d.ts.map +1 -0
- package/dist/process/providerProcessTypes.js +30 -0
- package/dist/process/providerProcessTypes.js.map +1 -0
- package/dist/process/providerProcessValidation.d.ts +26 -0
- package/dist/process/providerProcessValidation.d.ts.map +1 -0
- package/dist/process/providerProcessValidation.js +112 -0
- package/dist/process/providerProcessValidation.js.map +1 -0
- package/dist/process/providerProcessWorkspace.d.ts +38 -0
- package/dist/process/providerProcessWorkspace.d.ts.map +1 -0
- package/dist/process/providerProcessWorkspace.js +86 -0
- package/dist/process/providerProcessWorkspace.js.map +1 -0
- package/dist/providers/claudeLocalProvider.d.ts +68 -0
- package/dist/providers/claudeLocalProvider.d.ts.map +1 -0
- package/dist/providers/claudeLocalProvider.js +217 -0
- package/dist/providers/claudeLocalProvider.js.map +1 -0
- package/dist/providers/claudeStreamJsonParser.d.ts +39 -0
- package/dist/providers/claudeStreamJsonParser.d.ts.map +1 -0
- package/dist/providers/claudeStreamJsonParser.js +180 -0
- package/dist/providers/claudeStreamJsonParser.js.map +1 -0
- package/dist/providers/codexExecJsonParser.d.ts +38 -0
- package/dist/providers/codexExecJsonParser.d.ts.map +1 -0
- package/dist/providers/codexExecJsonParser.js +245 -0
- package/dist/providers/codexExecJsonParser.js.map +1 -0
- package/dist/providers/codexLocalProvider.d.ts +46 -0
- package/dist/providers/codexLocalProvider.d.ts.map +1 -0
- package/dist/providers/codexLocalProvider.js +430 -0
- package/dist/providers/codexLocalProvider.js.map +1 -0
- package/dist/providers/codexModelCatalog.d.ts +13 -0
- package/dist/providers/codexModelCatalog.d.ts.map +1 -0
- package/dist/providers/codexModelCatalog.js +133 -0
- package/dist/providers/codexModelCatalog.js.map +1 -0
- package/dist/providers/fixtureProvider.d.ts +14 -0
- package/dist/providers/fixtureProvider.d.ts.map +1 -0
- package/dist/providers/fixtureProvider.js +160 -0
- package/dist/providers/fixtureProvider.js.map +1 -0
- package/dist/providers/providerDiscovery.d.ts +24 -0
- package/dist/providers/providerDiscovery.d.ts.map +1 -0
- package/dist/providers/providerDiscovery.js +128 -0
- package/dist/providers/providerDiscovery.js.map +1 -0
- package/dist/providers/providerErrors.d.ts +64 -0
- package/dist/providers/providerErrors.d.ts.map +1 -0
- package/dist/providers/providerErrors.js +101 -0
- package/dist/providers/providerErrors.js.map +1 -0
- package/dist/providers/providerEvents.d.ts +63 -0
- package/dist/providers/providerEvents.d.ts.map +1 -0
- package/dist/providers/providerEvents.js +86 -0
- package/dist/providers/providerEvents.js.map +1 -0
- package/dist/providers/providerRegistry.d.ts +31 -0
- package/dist/providers/providerRegistry.d.ts.map +1 -0
- package/dist/providers/providerRegistry.js +234 -0
- package/dist/providers/providerRegistry.js.map +1 -0
- package/dist/providers/providerSafety.d.ts +17 -0
- package/dist/providers/providerSafety.d.ts.map +1 -0
- package/dist/providers/providerSafety.js +36 -0
- package/dist/providers/providerSafety.js.map +1 -0
- package/dist/providers/providerTypes.d.ts +212 -0
- package/dist/providers/providerTypes.d.ts.map +1 -0
- package/dist/providers/providerTypes.js +15 -0
- package/dist/providers/providerTypes.js.map +1 -0
- package/dist/providers/xenoLocalProvider.d.ts +35 -0
- package/dist/providers/xenoLocalProvider.d.ts.map +1 -0
- package/dist/providers/xenoLocalProvider.js +159 -0
- package/dist/providers/xenoLocalProvider.js.map +1 -0
- package/dist/sandbox/providerSandbox.d.ts +35 -0
- package/dist/sandbox/providerSandbox.d.ts.map +1 -0
- package/dist/sandbox/providerSandbox.js +93 -0
- package/dist/sandbox/providerSandbox.js.map +1 -0
- package/dist/sandbox/providerSandboxAudit.d.ts +12 -0
- package/dist/sandbox/providerSandboxAudit.d.ts.map +1 -0
- package/dist/sandbox/providerSandboxAudit.js +25 -0
- package/dist/sandbox/providerSandboxAudit.js.map +1 -0
- package/dist/sandbox/providerSandboxDiagnostics.d.ts +13 -0
- package/dist/sandbox/providerSandboxDiagnostics.d.ts.map +1 -0
- package/dist/sandbox/providerSandboxDiagnostics.js +28 -0
- package/dist/sandbox/providerSandboxDiagnostics.js.map +1 -0
- package/dist/sandbox/providerSandboxTypes.d.ts +138 -0
- package/dist/sandbox/providerSandboxTypes.d.ts.map +1 -0
- package/dist/sandbox/providerSandboxTypes.js +17 -0
- package/dist/sandbox/providerSandboxTypes.js.map +1 -0
- package/dist/sandbox/sandboxCancellation.d.ts +6 -0
- package/dist/sandbox/sandboxCancellation.d.ts.map +1 -0
- package/dist/sandbox/sandboxCancellation.js +24 -0
- package/dist/sandbox/sandboxCancellation.js.map +1 -0
- package/dist/sandbox/sandboxErrors.d.ts +17 -0
- package/dist/sandbox/sandboxErrors.d.ts.map +1 -0
- package/dist/sandbox/sandboxErrors.js +21 -0
- package/dist/sandbox/sandboxErrors.js.map +1 -0
- package/dist/sandbox/sandboxFilesystem.d.ts +14 -0
- package/dist/sandbox/sandboxFilesystem.d.ts.map +1 -0
- package/dist/sandbox/sandboxFilesystem.js +51 -0
- package/dist/sandbox/sandboxFilesystem.js.map +1 -0
- package/dist/sandbox/sandboxNetwork.d.ts +14 -0
- package/dist/sandbox/sandboxNetwork.d.ts.map +1 -0
- package/dist/sandbox/sandboxNetwork.js +60 -0
- package/dist/sandbox/sandboxNetwork.js.map +1 -0
- package/dist/sandbox/sandboxSecrets.d.ts +17 -0
- package/dist/sandbox/sandboxSecrets.d.ts.map +1 -0
- package/dist/sandbox/sandboxSecrets.js +61 -0
- package/dist/sandbox/sandboxSecrets.js.map +1 -0
- package/dist/sandbox/sandboxTerminal.d.ts +14 -0
- package/dist/sandbox/sandboxTerminal.d.ts.map +1 -0
- package/dist/sandbox/sandboxTerminal.js +32 -0
- package/dist/sandbox/sandboxTerminal.js.map +1 -0
- package/dist/security/authPolicy.d.ts +31 -0
- package/dist/security/authPolicy.d.ts.map +1 -0
- package/dist/security/authPolicy.js +72 -0
- package/dist/security/authPolicy.js.map +1 -0
- package/dist/security/capabilityGate.d.ts +16 -0
- package/dist/security/capabilityGate.d.ts.map +1 -0
- package/dist/security/capabilityGate.js +101 -0
- package/dist/security/capabilityGate.js.map +1 -0
- package/dist/security/egressPolicy.d.ts +31 -0
- package/dist/security/egressPolicy.d.ts.map +1 -0
- package/dist/security/egressPolicy.js +120 -0
- package/dist/security/egressPolicy.js.map +1 -0
- package/dist/security/redaction.d.ts +16 -0
- package/dist/security/redaction.d.ts.map +1 -0
- package/dist/security/redaction.js +65 -0
- package/dist/security/redaction.js.map +1 -0
- package/dist/security/secretBinding.d.ts +34 -0
- package/dist/security/secretBinding.d.ts.map +1 -0
- package/dist/security/secretBinding.js +66 -0
- package/dist/security/secretBinding.js.map +1 -0
- package/dist/security/secretStore.d.ts +37 -0
- package/dist/security/secretStore.d.ts.map +1 -0
- package/dist/security/secretStore.js +55 -0
- package/dist/security/secretStore.js.map +1 -0
- package/dist/security/securityDiagnostics.d.ts +39 -0
- package/dist/security/securityDiagnostics.d.ts.map +1 -0
- package/dist/security/securityDiagnostics.js +36 -0
- package/dist/security/securityDiagnostics.js.map +1 -0
- package/dist/security/securityTypes.d.ts +77 -0
- package/dist/security/securityTypes.d.ts.map +1 -0
- package/dist/security/securityTypes.js +17 -0
- package/dist/security/securityTypes.js.map +1 -0
- package/dist/sessionStore.d.ts +33 -0
- package/dist/sessionStore.d.ts.map +1 -0
- package/dist/sessionStore.js +42 -0
- package/dist/sessionStore.js.map +1 -0
- package/dist/stdioJsonRpc.d.ts +43 -0
- package/dist/stdioJsonRpc.d.ts.map +1 -0
- package/dist/stdioJsonRpc.js +219 -0
- package/dist/stdioJsonRpc.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +55 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* xeno-acp-agent CLI.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* xeno-acp-agent --version
|
|
7
|
+
* xeno-acp-agent check # lists providers + safety (human-readable)
|
|
8
|
+
* xeno-acp-agent agent --provider fixture [--delay-ms N] [--fail] [--stderr-test]
|
|
9
|
+
*
|
|
10
|
+
* stdout contract: `--version`/`check`/`--help` are NON-ACP and print human text. `agent` is
|
|
11
|
+
* ACP mode → stdout is JSON-RPC ONLY; all diagnostics go to stderr. Provider selection goes
|
|
12
|
+
* through the registry; an unknown or not-implemented provider fails safely (never falls back
|
|
13
|
+
* to fixture).
|
|
14
|
+
*/
|
|
15
|
+
import process from "node:process";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { createInterface } from "node:readline/promises";
|
|
18
|
+
import { startAcpAgent } from "./acpAgentServer.js";
|
|
19
|
+
import { createDefaultRegistry } from "./providers/providerRegistry.js";
|
|
20
|
+
import { ProviderError } from "./providers/providerErrors.js";
|
|
21
|
+
import { buildProviderDiagnostic } from "./security/securityDiagnostics.js";
|
|
22
|
+
import { loadProviderPolicyConfig, getPolicyEntry } from "./config/providerPolicyConfig.js";
|
|
23
|
+
import { ProviderApprovalStore, createApprovalRecord } from "./config/providerApprovalStore.js";
|
|
24
|
+
import { resolveProviderStatus, approvalDecisionsFor } from "./config/providerRunnability.js";
|
|
25
|
+
import { buildPolicyReview } from "./config/providerPolicyDiagnostics.js";
|
|
26
|
+
import { buildSecretBindingReview } from "./security/secretBinding.js";
|
|
27
|
+
import { buildProviderDiscoverySummary } from "./providers/providerDiscovery.js";
|
|
28
|
+
import { discoverCodexCliModels } from "./providers/codexModelCatalog.js";
|
|
29
|
+
import { enforcementSummaryLines, minimalSupervisorEnforcement } from "./process/providerProcessDiagnostics.js";
|
|
30
|
+
import { emitXenoLocalStream, XENO_LOCAL_ID } from "./providers/xenoLocalProvider.js";
|
|
31
|
+
import { CLAUDE_LOCAL_ENV_ALLOWLIST_KEYS } from "./providers/claudeLocalProvider.js";
|
|
32
|
+
import { CODEX_LOCAL_DEFAULT_EXECUTABLE, CODEX_LOCAL_ENV_ALLOWLIST_KEYS, CODEX_LOCAL_ID, } from "./providers/codexLocalProvider.js";
|
|
33
|
+
import { AGENT_NAME, VERSION } from "./version.js";
|
|
34
|
+
import { logStderr } from "./stdioJsonRpc.js";
|
|
35
|
+
function approvalStorePath(flagStore) {
|
|
36
|
+
return flagStore ?? process.env.XENO_ACP_APPROVALS_FILE ?? join(process.cwd(), ".xeno-acp", "approvals.json");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Minimal, NON-SECRET env a real local-adapter CLI needs for ITS OWN local auth + execution
|
|
40
|
+
* (XACP-021/022). The supervisor uses a clean allowlist (no ambient inheritance); this forwards
|
|
41
|
+
* only these documented keys so e.g. the Claude CLI can find its own local session. It NEVER
|
|
42
|
+
* includes `CLAUDECODE` (nested-session guard) or any `*_API_KEY`/`*_TOKEN`/`*_SECRET`.
|
|
43
|
+
*/
|
|
44
|
+
function minimalNonSecretProviderEnv(keys) {
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const k of keys) {
|
|
47
|
+
const v = process.env[k];
|
|
48
|
+
if (v)
|
|
49
|
+
out[k] = v;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function providerEnvKeysFor(providerId) {
|
|
54
|
+
if (providerId === "claude-local")
|
|
55
|
+
return CLAUDE_LOCAL_ENV_ALLOWLIST_KEYS;
|
|
56
|
+
if (providerId === "codex-local")
|
|
57
|
+
return CODEX_LOCAL_ENV_ALLOWLIST_KEYS;
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
function payloadString(payload, key) {
|
|
61
|
+
if (typeof payload !== "object" || payload === null)
|
|
62
|
+
return "";
|
|
63
|
+
const value = payload[key];
|
|
64
|
+
return typeof value === "string" ? value : "";
|
|
65
|
+
}
|
|
66
|
+
function messageTextFrom(events) {
|
|
67
|
+
return events
|
|
68
|
+
.filter((event) => event.kind === "message_delta")
|
|
69
|
+
.map((event) => payloadString(event.payload, "text"))
|
|
70
|
+
.join("");
|
|
71
|
+
}
|
|
72
|
+
function formatUsage(usage) {
|
|
73
|
+
if (!usage)
|
|
74
|
+
return undefined;
|
|
75
|
+
return `input=${usage.inputTokens ?? "?"} output=${usage.outputTokens ?? "?"} total=${usage.totalTokens ?? "?"}`;
|
|
76
|
+
}
|
|
77
|
+
function flagValue(args, name) {
|
|
78
|
+
const i = args.indexOf(name);
|
|
79
|
+
if (i !== -1 && i + 1 < args.length)
|
|
80
|
+
return args[i + 1];
|
|
81
|
+
const eq = args.find((a) => a.startsWith(`${name}=`));
|
|
82
|
+
return eq ? eq.slice(name.length + 1) : undefined;
|
|
83
|
+
}
|
|
84
|
+
function flagValues(args, name) {
|
|
85
|
+
const out = [];
|
|
86
|
+
for (let i = 0; i < args.length; i++) {
|
|
87
|
+
const a = args[i];
|
|
88
|
+
if (a === name && i + 1 < args.length)
|
|
89
|
+
out.push(args[++i]);
|
|
90
|
+
else if (a.startsWith(`${name}=`))
|
|
91
|
+
out.push(a.slice(name.length + 1));
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
function stripColorFlags(args) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (let i = 0; i < args.length; i++) {
|
|
98
|
+
const a = args[i];
|
|
99
|
+
if (a === "--no-color" || a.startsWith("--color="))
|
|
100
|
+
continue;
|
|
101
|
+
if (a === "--color") {
|
|
102
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("-"))
|
|
103
|
+
i++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
out.push(a);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
function usage() {
|
|
111
|
+
return [
|
|
112
|
+
`${AGENT_NAME} ${VERSION} — XENO-owned ACP agent-side endpoint`,
|
|
113
|
+
"",
|
|
114
|
+
"Usage:",
|
|
115
|
+
" xeno-acp-agent --version",
|
|
116
|
+
" xeno-acp-agent check [--config <p>] [--store <p>] [--json]",
|
|
117
|
+
" xeno-acp-agent doctor [--config <p>] [--store <p>] [--json]",
|
|
118
|
+
" xeno-acp-agent support-bundle [--config <p>] [--store <p>] [--provider <id>]",
|
|
119
|
+
" xeno-acp-agent certify --provider <id> [--config <p>] [--store <p>] [--json]",
|
|
120
|
+
" xeno-acp-agent process check",
|
|
121
|
+
" xeno-acp-agent providers list [--config <p>] [--store <p>] [--json]",
|
|
122
|
+
" xeno-acp-agent providers setup --provider <id> [--config <p>] [--store <p>] [--json]",
|
|
123
|
+
" xeno-acp-agent providers review --provider <id> [--config <p>] [--store <p>]",
|
|
124
|
+
" xeno-acp-agent providers models --provider <id> [--config <p>] [--store <p>] --json",
|
|
125
|
+
" xeno-acp-agent providers approve --provider <id> (--dry-run|--yes|--interactive) [--config <p>] [--store <p>] [--json]",
|
|
126
|
+
" xeno-acp-agent providers approvals [--config <p>] [--store <p>] [--json]",
|
|
127
|
+
" xeno-acp-agent providers revoke --provider <id> --yes [--config <p>] [--store <p>] [--json]",
|
|
128
|
+
" xeno-acp-agent providers test --provider <id> [--config <p>] [--store <p>] [--prompt <text>] [--json]",
|
|
129
|
+
" xeno-acp-agent providers diagnose --provider <id> [--config <p>] [--store <p>] [--bind REF=kind:alias] [--json]",
|
|
130
|
+
" xeno-acp-agent providers secrets --provider <id> [--bind REF=kind:alias]",
|
|
131
|
+
" xeno-acp-agent adapter --provider xeno-local [--mode normal] [--request-id ID]",
|
|
132
|
+
" xeno-acp-agent agent --provider fixture [--delay-ms N] [--fail] [--stderr-test]",
|
|
133
|
+
" xeno-acp-agent agent --provider xeno-local --provider-policy <p> --provider-approvals <p>",
|
|
134
|
+
"",
|
|
135
|
+
"Notes:",
|
|
136
|
+
" 'agent' speaks ACP v1 (JSON-RPC 2.0) over stdio. In that mode stdout is JSON-RPC ONLY.",
|
|
137
|
+
" Output is plain text by default; --no-color and --color=never are accepted as no-ops.",
|
|
138
|
+
" Runnable providers: 'fixture'; 'xeno-local', 'claude-local', and 'codex-local' after policy + approval.",
|
|
139
|
+
" Gemini/OpenCode/Kimi/GLM/Hermes remain blocked placeholders until scoped provider milestones.",
|
|
140
|
+
"",
|
|
141
|
+
].join("\n");
|
|
142
|
+
}
|
|
143
|
+
function parseAgentArgs(rest) {
|
|
144
|
+
const out = { fail: false, stderrTest: false };
|
|
145
|
+
for (let i = 0; i < rest.length; i++) {
|
|
146
|
+
const a = rest[i];
|
|
147
|
+
if (a === "--provider")
|
|
148
|
+
out.provider = rest[++i];
|
|
149
|
+
else if (a.startsWith("--provider="))
|
|
150
|
+
out.provider = a.slice("--provider=".length);
|
|
151
|
+
else if (a === "--delay-ms")
|
|
152
|
+
out.delayMs = Number(rest[++i]);
|
|
153
|
+
else if (a.startsWith("--delay-ms="))
|
|
154
|
+
out.delayMs = Number(a.slice("--delay-ms=".length));
|
|
155
|
+
else if (a === "--fail")
|
|
156
|
+
out.fail = true;
|
|
157
|
+
else if (a === "--stderr-test")
|
|
158
|
+
out.stderrTest = true;
|
|
159
|
+
else if (a === "--provider-policy")
|
|
160
|
+
out.providerPolicy = rest[++i];
|
|
161
|
+
else if (a.startsWith("--provider-policy="))
|
|
162
|
+
out.providerPolicy = a.slice("--provider-policy=".length);
|
|
163
|
+
else if (a === "--provider-approvals")
|
|
164
|
+
out.providerApprovals = rest[++i];
|
|
165
|
+
else if (a.startsWith("--provider-approvals="))
|
|
166
|
+
out.providerApprovals = a.slice("--provider-approvals=".length);
|
|
167
|
+
else if (a === "--mode")
|
|
168
|
+
out.mode = rest[++i];
|
|
169
|
+
else if (a.startsWith("--mode="))
|
|
170
|
+
out.mode = a.slice("--mode=".length);
|
|
171
|
+
else if (a === "--malformed-output-test") {
|
|
172
|
+
/* reserved self-test flag; no-op */
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
logStderr(`ignoring unknown agent flag: ${a}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
function nodeMajor() {
|
|
181
|
+
return Number(process.versions.node.split(".")[0] ?? "0");
|
|
182
|
+
}
|
|
183
|
+
async function confirmProviderApproval(providerId) {
|
|
184
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
185
|
+
try {
|
|
186
|
+
const answer = await rl.question(`Approve provider '${providerId}'? Type '${providerId}' to confirm: `);
|
|
187
|
+
return answer.trim() === providerId;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
rl.close();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function runCheck(rest = []) {
|
|
194
|
+
const configAware = rest.includes("--json")
|
|
195
|
+
|| rest.some((a) => a === "--config" || a.startsWith("--config=") || a === "--store" || a.startsWith("--store="));
|
|
196
|
+
if (configAware) {
|
|
197
|
+
const ctx = buildDoctorContext(flagValue(rest, "--config"), flagValue(rest, "--store"));
|
|
198
|
+
const out = { command: "check", ...ctx.report };
|
|
199
|
+
if (rest.includes("--json")) {
|
|
200
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
process.stdout.write([
|
|
204
|
+
`check: ${ctx.ready ? "ready" : "attention"}`,
|
|
205
|
+
`node: ${ctx.report.node.version} ${ctx.report.node.ok ? "ok" : "requires Node >= 20"}`,
|
|
206
|
+
`config: ${ctx.report.config.source ?? "none"} ${ctx.report.config.ok ? "ok" : "INVALID"}`,
|
|
207
|
+
`providers: runnable=${ctx.report.providers.filter((p) => p.runnable).length} total=${ctx.report.providers.length}`,
|
|
208
|
+
...ctx.report.providers.map((p) => ` - ${p.id} status=${p.status} runnable=${p.runnable} approval=${p.approval}`),
|
|
209
|
+
`process: platform=${ctx.report.process.platform} network=${ctx.report.process.enforcement.network} filesystem=${ctx.report.process.enforcement.filesystem}`,
|
|
210
|
+
].join("\n") + "\n");
|
|
211
|
+
}
|
|
212
|
+
if (!ctx.ready)
|
|
213
|
+
process.exitCode = 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const registry = createDefaultRegistry();
|
|
217
|
+
const ok = nodeMajor() >= 20;
|
|
218
|
+
const lines = [
|
|
219
|
+
`${AGENT_NAME} ${VERSION}`,
|
|
220
|
+
`node: ${process.version}`,
|
|
221
|
+
`acp protocol: v1`,
|
|
222
|
+
`transport: JSON-RPC 2.0 over stdio`,
|
|
223
|
+
`status: ${ok ? "ready" : "requires Node >= 20"}`,
|
|
224
|
+
``,
|
|
225
|
+
`providers:`,
|
|
226
|
+
];
|
|
227
|
+
for (const def of registry.list()) {
|
|
228
|
+
const diag = buildProviderDiagnostic(def);
|
|
229
|
+
const tag = registry.isRunnable(def.id) ? "[runnable]" : "[blocked] ";
|
|
230
|
+
lines.push(` - ${def.id.padEnd(10)} ${tag} trust=${diag.trustClass} safety=${diag.safety.overall} (${diag.safety.reason})`);
|
|
231
|
+
// Secret REFERENCE ids only (never values); allowed egress hosts. No credentials printed.
|
|
232
|
+
if (diag.secretRefs.length) {
|
|
233
|
+
lines.push(` secret-refs: ${diag.secretRefs.map((r) => r.id).join(", ")}`);
|
|
234
|
+
}
|
|
235
|
+
if (diag.allowedHosts.length) {
|
|
236
|
+
lines.push(` egress-allowlist: ${diag.allowedHosts.join(", ")}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
240
|
+
if (!ok)
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
}
|
|
243
|
+
function buildDoctorContext(configPath, storePathArg) {
|
|
244
|
+
const cfg = loadProviderPolicyConfig(configPath ? { path: configPath } : {});
|
|
245
|
+
const store = new ProviderApprovalStore(approvalStorePath(storePathArg));
|
|
246
|
+
const registry = createDefaultRegistry();
|
|
247
|
+
const nodeOk = nodeMajor() >= 20;
|
|
248
|
+
const providers = registry.list().map((def) => {
|
|
249
|
+
const policyEntry = getPolicyEntry(cfg.config, def.id);
|
|
250
|
+
const approval = store.get(def.id);
|
|
251
|
+
const s = resolveProviderStatus(def, {
|
|
252
|
+
...(policyEntry ? { policyEntry } : {}),
|
|
253
|
+
...(approval ? { approval } : {}),
|
|
254
|
+
policyInvalid: !cfg.ok,
|
|
255
|
+
});
|
|
256
|
+
const discovery = buildProviderDiscoverySummary(def, s);
|
|
257
|
+
return {
|
|
258
|
+
id: def.id,
|
|
259
|
+
displayName: discovery.displayName,
|
|
260
|
+
implemented: def.enabled,
|
|
261
|
+
status: s.status,
|
|
262
|
+
runnable: s.runnable,
|
|
263
|
+
approval: approval ? "present" : "missing",
|
|
264
|
+
modelDiscovery: {
|
|
265
|
+
status: discovery.modelDiscovery.status,
|
|
266
|
+
defaultModelId: discovery.modelDiscovery.defaultModelId,
|
|
267
|
+
modelCount: discovery.modelDiscovery.models.length,
|
|
268
|
+
allowCustomModel: discovery.modelDiscovery.allowCustomModel,
|
|
269
|
+
},
|
|
270
|
+
versionReadiness: discovery.versionReadiness,
|
|
271
|
+
streaming: discovery.streaming,
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
const ready = nodeOk && cfg.ok;
|
|
275
|
+
const processInfo = { platform: process.platform, enforcement: minimalSupervisorEnforcement() };
|
|
276
|
+
const report = {
|
|
277
|
+
status: ready ? "ready" : "attention",
|
|
278
|
+
node: { version: process.version, ok: nodeOk },
|
|
279
|
+
config: { source: cfg.source ?? null, ok: cfg.ok, errors: cfg.errors },
|
|
280
|
+
approvalStore: { repaired: store.repaired, count: store.list().length },
|
|
281
|
+
providers,
|
|
282
|
+
process: processInfo,
|
|
283
|
+
};
|
|
284
|
+
return { cfg, store, registry, ready, report };
|
|
285
|
+
}
|
|
286
|
+
function runDoctor(rest) {
|
|
287
|
+
const ctx = buildDoctorContext(flagValue(rest, "--config"), flagValue(rest, "--store"));
|
|
288
|
+
if (rest.includes("--json")) {
|
|
289
|
+
process.stdout.write(JSON.stringify(ctx.report, null, 2) + "\n");
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
process.stdout.write([
|
|
293
|
+
`doctor: ${ctx.ready ? "ready" : "attention"}`,
|
|
294
|
+
`node: ${ctx.report.node.version} ${ctx.report.node.ok ? "ok" : "requires Node >= 20"}`,
|
|
295
|
+
`config: ${ctx.report.config.source ?? "none"} ${ctx.report.config.ok ? "ok" : "INVALID"}`,
|
|
296
|
+
`providers: runnable=${ctx.report.providers.filter((p) => p.runnable).length} total=${ctx.report.providers.length}`,
|
|
297
|
+
...ctx.report.providers.map((p) => ` - ${p.id} status=${p.status} runnable=${p.runnable} approval=${p.approval}`),
|
|
298
|
+
`process: platform=${ctx.report.process.platform} network=${ctx.report.process.enforcement.network} filesystem=${ctx.report.process.enforcement.filesystem}`,
|
|
299
|
+
].join("\n") + "\n");
|
|
300
|
+
}
|
|
301
|
+
if (!ctx.ready)
|
|
302
|
+
process.exitCode = 1;
|
|
303
|
+
}
|
|
304
|
+
function runSupportBundle(rest) {
|
|
305
|
+
const ctx = buildDoctorContext(flagValue(rest, "--config"), flagValue(rest, "--store"));
|
|
306
|
+
const provider = flagValue(rest, "--provider");
|
|
307
|
+
const defs = provider ? [ctx.registry.get(provider)] : ctx.registry.list();
|
|
308
|
+
if (provider && !defs[0])
|
|
309
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
310
|
+
const generatedAt = new Date().toISOString();
|
|
311
|
+
const providers = defs.map((def) => {
|
|
312
|
+
const policyEntry = getPolicyEntry(ctx.cfg.config, def.id);
|
|
313
|
+
const approval = ctx.store.get(def.id);
|
|
314
|
+
const input = {
|
|
315
|
+
...(policyEntry ? { policyEntry } : {}),
|
|
316
|
+
...(approval ? { approval } : {}),
|
|
317
|
+
policyInvalid: !ctx.cfg.ok,
|
|
318
|
+
};
|
|
319
|
+
const status = resolveProviderStatus(def, input);
|
|
320
|
+
const discovery = buildProviderDiscoverySummary(def, status);
|
|
321
|
+
const refs = policyEntry?.auth.allowedSecretRefs ?? def.authPolicy?.allowedSecretRefs ?? [];
|
|
322
|
+
const binding = buildSecretBindingReview(def.id, refs, []);
|
|
323
|
+
return {
|
|
324
|
+
providerId: def.id,
|
|
325
|
+
displayName: discovery.displayName,
|
|
326
|
+
runnability: {
|
|
327
|
+
status: status.status,
|
|
328
|
+
runnable: status.runnable,
|
|
329
|
+
reason: status.reason,
|
|
330
|
+
safeMessage: status.safeMessage,
|
|
331
|
+
},
|
|
332
|
+
modelDiscovery: discovery.modelDiscovery,
|
|
333
|
+
versionReadiness: discovery.versionReadiness,
|
|
334
|
+
streaming: discovery.streaming,
|
|
335
|
+
setupHints: discovery.setupHints,
|
|
336
|
+
policyReview: buildPolicyReview(def, input),
|
|
337
|
+
secretBinding: {
|
|
338
|
+
requiredRefs: binding.requiredRefs,
|
|
339
|
+
optionalRefs: binding.optionalRefs,
|
|
340
|
+
missingRequiredRefs: binding.missingRequiredRefs,
|
|
341
|
+
errors: binding.errors,
|
|
342
|
+
complete: binding.complete,
|
|
343
|
+
bindingCount: binding.bindings.length,
|
|
344
|
+
valuePolicy: binding.valuePolicy,
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
const auditTimeline = [
|
|
349
|
+
{
|
|
350
|
+
sequence: 1,
|
|
351
|
+
at: generatedAt,
|
|
352
|
+
event: "support_bundle.generated",
|
|
353
|
+
status: "ok",
|
|
354
|
+
safeMessage: "redacted support bundle generated",
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
sequence: 2,
|
|
358
|
+
at: generatedAt,
|
|
359
|
+
event: "config.evaluated",
|
|
360
|
+
status: ctx.cfg.ok ? "ok" : "invalid",
|
|
361
|
+
safeMessage: ctx.cfg.ok ? "provider policy config loaded" : "provider policy config invalid",
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
sequence: 3,
|
|
365
|
+
at: generatedAt,
|
|
366
|
+
event: "doctor.evaluated",
|
|
367
|
+
status: ctx.ready ? "ready" : "attention",
|
|
368
|
+
safeMessage: ctx.ready ? "local doctor checks passed" : "local doctor checks need attention",
|
|
369
|
+
},
|
|
370
|
+
...providers.map((p, i) => ({
|
|
371
|
+
sequence: i + 4,
|
|
372
|
+
at: generatedAt,
|
|
373
|
+
event: "provider.runnability.evaluated",
|
|
374
|
+
providerId: p.providerId,
|
|
375
|
+
status: p.runnability.status,
|
|
376
|
+
safeMessage: p.runnability.safeMessage,
|
|
377
|
+
})),
|
|
378
|
+
];
|
|
379
|
+
process.stdout.write(JSON.stringify({
|
|
380
|
+
bundleVersion: 1,
|
|
381
|
+
generatedAt,
|
|
382
|
+
command: "support-bundle",
|
|
383
|
+
redaction: {
|
|
384
|
+
policy: "metadata_only_no_secret_values_no_env_values_no_logs",
|
|
385
|
+
note: "Secret refs and env key names may appear; values, credential paths, and recent logs are not included.",
|
|
386
|
+
},
|
|
387
|
+
doctor: ctx.report,
|
|
388
|
+
providers,
|
|
389
|
+
auditTimeline,
|
|
390
|
+
}, null, 2) + "\n");
|
|
391
|
+
if (!ctx.ready)
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
}
|
|
394
|
+
const HUB_REPO_NAME = "xeno" + "-hub";
|
|
395
|
+
const HUB_CONFIG_PREFIX = `docs/${HUB_REPO_NAME}`;
|
|
396
|
+
const HUB_CONFIG_PLACEHOLDER = `<${HUB_REPO_NAME}-config.json>`;
|
|
397
|
+
const HUB_CONFIG_EXAMPLES = {
|
|
398
|
+
fixture: `${HUB_CONFIG_PREFIX}-agent-endpoint-config.example.json`,
|
|
399
|
+
"xeno-local": `${HUB_CONFIG_PREFIX}-xeno-local-provider-config.example.json`,
|
|
400
|
+
"claude-local": `${HUB_CONFIG_PREFIX}-claude-local-provider-config.example.json`,
|
|
401
|
+
"codex-local": `${HUB_CONFIG_PREFIX}-codex-local-provider-config.example.json`,
|
|
402
|
+
};
|
|
403
|
+
function runCertify(rest) {
|
|
404
|
+
const provider = flagValue(rest, "--provider");
|
|
405
|
+
const configPath = flagValue(rest, "--config");
|
|
406
|
+
const storePath = flagValue(rest, "--store");
|
|
407
|
+
const json = rest.includes("--json");
|
|
408
|
+
const ctx = buildDoctorContext(configPath, storePath);
|
|
409
|
+
if (!provider)
|
|
410
|
+
return failProviders("certify requires --provider <id>");
|
|
411
|
+
const def = ctx.registry.get(provider);
|
|
412
|
+
if (!def)
|
|
413
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
414
|
+
const policyEntry = getPolicyEntry(ctx.cfg.config, def.id);
|
|
415
|
+
const approval = ctx.store.get(def.id);
|
|
416
|
+
const runnability = {
|
|
417
|
+
...(policyEntry ? { policyEntry } : {}),
|
|
418
|
+
...(approval ? { approval } : {}),
|
|
419
|
+
policyInvalid: !ctx.cfg.ok,
|
|
420
|
+
};
|
|
421
|
+
const status = resolveProviderStatus(def, runnability);
|
|
422
|
+
const discovery = buildProviderDiscoverySummary(def, status);
|
|
423
|
+
const preflightStatus = ctx.ready && status.runnable ? "ready_for_hub_handoff" : "blocked";
|
|
424
|
+
const agentCommand = [
|
|
425
|
+
"node",
|
|
426
|
+
"packages/agent/dist/cli.js",
|
|
427
|
+
"agent",
|
|
428
|
+
"--provider",
|
|
429
|
+
def.id,
|
|
430
|
+
...(configPath ? ["--provider-policy", configPath] : []),
|
|
431
|
+
...(storePath ? ["--provider-approvals", storePath] : []),
|
|
432
|
+
];
|
|
433
|
+
const out = {
|
|
434
|
+
command: "certify",
|
|
435
|
+
mode: "local_preflight_only",
|
|
436
|
+
providerId: def.id,
|
|
437
|
+
displayName: discovery.displayName,
|
|
438
|
+
preflightStatus,
|
|
439
|
+
hubCertification: "not_run_by_this_command",
|
|
440
|
+
config: { source: ctx.cfg.source ?? null, ok: ctx.cfg.ok, errors: ctx.cfg.errors },
|
|
441
|
+
runnability: {
|
|
442
|
+
status: status.status,
|
|
443
|
+
runnable: status.runnable,
|
|
444
|
+
reason: status.reason,
|
|
445
|
+
safeMessage: status.safeMessage,
|
|
446
|
+
},
|
|
447
|
+
modelDiscovery: discovery.modelDiscovery,
|
|
448
|
+
versionReadiness: discovery.versionReadiness,
|
|
449
|
+
streaming: discovery.streaming,
|
|
450
|
+
setupHints: discovery.setupHints,
|
|
451
|
+
approval: approval ? "present" : "missing",
|
|
452
|
+
agentCommand,
|
|
453
|
+
hubHandoff: {
|
|
454
|
+
runFrom: HUB_REPO_NAME,
|
|
455
|
+
script: `node scripts/agent-acp-external-certify.mjs --config ${HUB_CONFIG_PLACEHOLDER}`,
|
|
456
|
+
configExample: HUB_CONFIG_EXAMPLES[def.id] ?? null,
|
|
457
|
+
note: "This command does not certify; the XENO Hub certification harness must run separately.",
|
|
458
|
+
},
|
|
459
|
+
claimPolicy: {
|
|
460
|
+
notOfficialProviderAcp: true,
|
|
461
|
+
notProductionReady: true,
|
|
462
|
+
noBroadThirdPartyCertificationClaim: true,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
if (json) {
|
|
466
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
process.stdout.write([
|
|
470
|
+
`certify preflight: ${def.id}`,
|
|
471
|
+
`status: ${preflightStatus}`,
|
|
472
|
+
`hub certification: not run by this command`,
|
|
473
|
+
`config: ${ctx.cfg.source ?? "none"} ${ctx.cfg.ok ? "ok" : "INVALID"}`,
|
|
474
|
+
`provider: ${status.status} runnable=${status.runnable}`,
|
|
475
|
+
`agent command: ${agentCommand.join(" ")}`,
|
|
476
|
+
`hub handoff: run from ${out.hubHandoff.runFrom}: ${out.hubHandoff.script}`,
|
|
477
|
+
...(out.hubHandoff.configExample ? [`hub config example: ${out.hubHandoff.configExample}`] : []),
|
|
478
|
+
`claim policy: no official-provider, broad-third-party, or production-ready claim`,
|
|
479
|
+
...(status.runnable ? [] : [`next: ${status.safeMessage}`]),
|
|
480
|
+
].join("\n") + "\n");
|
|
481
|
+
}
|
|
482
|
+
if (preflightStatus !== "ready_for_hub_handoff")
|
|
483
|
+
process.exitCode = 1;
|
|
484
|
+
}
|
|
485
|
+
function runAgent(rest) {
|
|
486
|
+
const args = parseAgentArgs(rest);
|
|
487
|
+
if (!args.provider) {
|
|
488
|
+
failCli("AGENT_CONFIG_ERROR", "missing required --provider (e.g. --provider fixture)");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const registry = createDefaultRegistry();
|
|
492
|
+
const def = registry.get(args.provider);
|
|
493
|
+
if (!def) {
|
|
494
|
+
failCli("AGENT_UNKNOWN_PROVIDER", `unknown provider: ${args.provider}. Run 'providers list' to see providers.`);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
// Gate via definition + policy config + approvals (read-only here). Local-only store paths may
|
|
498
|
+
// be supplied for test/dev isolation (never a default user-home write).
|
|
499
|
+
const cfg = loadProviderPolicyConfig(args.providerPolicy ? { path: args.providerPolicy } : {});
|
|
500
|
+
const store = new ProviderApprovalStore(approvalStorePath(args.providerApprovals));
|
|
501
|
+
const policyEntry = getPolicyEntry(cfg.config, def.id);
|
|
502
|
+
const approval = store.get(def.id);
|
|
503
|
+
const sandboxInputs = {
|
|
504
|
+
...(policyEntry ? { policyEntry } : {}),
|
|
505
|
+
...(approval ? { approval } : {}),
|
|
506
|
+
policyInvalid: !cfg.ok,
|
|
507
|
+
};
|
|
508
|
+
const status = resolveProviderStatus(def, sandboxInputs);
|
|
509
|
+
if (!status.runnable) {
|
|
510
|
+
failCli("AGENT_PROVIDER_NOT_RUNNABLE", `provider '${args.provider}' is not runnable (${status.status}). Runnable providers today: 'fixture'; local adapters with current policy + approval.`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
// A real local-adapter CLI that needs auth/network (e.g. claude-local) gets the minimal
|
|
514
|
+
// non-secret env so it can use its OWN local auth; the supervisor forwards nothing else.
|
|
515
|
+
const providerEnvKeys = providerEnvKeysFor(def.id);
|
|
516
|
+
const needsProviderEnv = providerEnvKeys.length > 0;
|
|
517
|
+
let runtime;
|
|
518
|
+
try {
|
|
519
|
+
runtime = registry.createRuntime(args.provider, {
|
|
520
|
+
options: {
|
|
521
|
+
...(args.delayMs !== undefined ? { delayMs: args.delayMs } : {}),
|
|
522
|
+
fail: args.fail,
|
|
523
|
+
stderrTest: args.stderrTest,
|
|
524
|
+
// xeno-local / claude-local re-verify runnability inside the harness and select their mode.
|
|
525
|
+
runnability: sandboxInputs,
|
|
526
|
+
...(needsProviderEnv ? { envAllowlist: minimalNonSecretProviderEnv(providerEnvKeys) } : {}),
|
|
527
|
+
cwd: process.cwd(),
|
|
528
|
+
...(args.mode ? { mode: args.mode } : {}),
|
|
529
|
+
},
|
|
530
|
+
log: (m) => logStderr(m),
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
failCli("AGENT_RUNTIME_ERROR", err instanceof ProviderError ? err.safeMessage : String(err?.message ?? err));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// ACP MODE — stdout is JSON-RPC only from here on.
|
|
538
|
+
let handle;
|
|
539
|
+
handle = startAcpAgent({
|
|
540
|
+
runtime,
|
|
541
|
+
sandboxInputs,
|
|
542
|
+
onClose: () => {
|
|
543
|
+
handle?.close();
|
|
544
|
+
process.exitCode = 0;
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
logStderr(`agent ready (provider=${args.provider})`);
|
|
548
|
+
}
|
|
549
|
+
async function runProviders(rest) {
|
|
550
|
+
const sub = rest[0];
|
|
551
|
+
const args = rest.slice(1);
|
|
552
|
+
if (sub === undefined || sub === "help" || sub === "--help" || sub === "-h" || args[0] === "help" || args[0] === "--help" || args[0] === "-h") {
|
|
553
|
+
process.stdout.write(usage());
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const configPath = flagValue(args, "--config");
|
|
557
|
+
const storePathArg = flagValue(args, "--store");
|
|
558
|
+
const provider = flagValue(args, "--provider");
|
|
559
|
+
const yes = args.includes("--yes");
|
|
560
|
+
const dryRun = args.includes("--dry-run");
|
|
561
|
+
const interactive = args.includes("--interactive");
|
|
562
|
+
const cfg = loadProviderPolicyConfig(configPath ? { path: configPath } : {});
|
|
563
|
+
const store = new ProviderApprovalStore(approvalStorePath(storePathArg));
|
|
564
|
+
const registry = createDefaultRegistry();
|
|
565
|
+
const ctxFor = (def) => {
|
|
566
|
+
const policyEntry = getPolicyEntry(cfg.config, def.id);
|
|
567
|
+
const approval = store.get(def.id);
|
|
568
|
+
return {
|
|
569
|
+
...(policyEntry ? { policyEntry } : {}),
|
|
570
|
+
...(approval ? { approval } : {}),
|
|
571
|
+
policyInvalid: !cfg.ok,
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
const runProviderTest = async () => {
|
|
575
|
+
if (!provider)
|
|
576
|
+
return failProviders("test requires --provider <id>");
|
|
577
|
+
if (!cfg.ok)
|
|
578
|
+
return failProviders("provider policy config is invalid; fix it before testing");
|
|
579
|
+
const def = registry.get(provider);
|
|
580
|
+
if (!def)
|
|
581
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
582
|
+
const runnability = ctxFor(def);
|
|
583
|
+
const status = resolveProviderStatus(def, runnability);
|
|
584
|
+
if (!status.runnable) {
|
|
585
|
+
return failProviders(`${status.safeMessage}. Run 'providers review --provider ${provider}' and approve it if required before testing.`);
|
|
586
|
+
}
|
|
587
|
+
const options = { runnability };
|
|
588
|
+
const providerEnvKeys = providerEnvKeysFor(def.id);
|
|
589
|
+
if (providerEnvKeys.length)
|
|
590
|
+
options.envAllowlist = minimalNonSecretProviderEnv(providerEnvKeys);
|
|
591
|
+
options.cwd = process.cwd();
|
|
592
|
+
let runtime;
|
|
593
|
+
const events = [];
|
|
594
|
+
try {
|
|
595
|
+
runtime = registry.createRuntime(def.id, { options, log: (m) => logStderr(m) });
|
|
596
|
+
await runtime.initialize();
|
|
597
|
+
const session = runtime.createSession();
|
|
598
|
+
const controller = new AbortController();
|
|
599
|
+
const result = await runtime.prompt({
|
|
600
|
+
sessionId: session.sessionId,
|
|
601
|
+
requestId: `provider-test-${def.id}-${Date.now()}`,
|
|
602
|
+
prompt: [{ type: "text", text: flagValue(args, "--prompt") ?? "xeno-acp provider test" }],
|
|
603
|
+
}, { emit: (event) => events.push(event), signal: controller.signal, log: (m) => logStderr(m) });
|
|
604
|
+
const message = messageTextFrom(events);
|
|
605
|
+
const eventKinds = events.map((event) => event.kind);
|
|
606
|
+
const json = args.includes("--json");
|
|
607
|
+
if (json) {
|
|
608
|
+
process.stdout.write(JSON.stringify({
|
|
609
|
+
providerId: def.id,
|
|
610
|
+
displayName: def.displayName,
|
|
611
|
+
status: result.disposition,
|
|
612
|
+
eventCount: events.length,
|
|
613
|
+
eventKinds,
|
|
614
|
+
message,
|
|
615
|
+
...(result.usage ? { usage: result.usage } : {}),
|
|
616
|
+
...(result.error ? { error: result.error.safeMessage } : {}),
|
|
617
|
+
}, null, 2) + "\n");
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
const usage = formatUsage(result.usage);
|
|
621
|
+
const lines = [
|
|
622
|
+
`provider test: ${def.id}`,
|
|
623
|
+
`status: ${result.disposition}`,
|
|
624
|
+
`events: ${events.length}`,
|
|
625
|
+
`event kinds: ${eventKinds.join(", ")}`,
|
|
626
|
+
...(message ? [`message: ${message}`] : []),
|
|
627
|
+
...(usage ? [`usage: ${usage}`] : []),
|
|
628
|
+
...(result.error ? [`error: ${result.error.safeMessage}`] : []),
|
|
629
|
+
];
|
|
630
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
631
|
+
}
|
|
632
|
+
if (result.disposition !== "completed")
|
|
633
|
+
process.exitCode = 1;
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
failCli("PROVIDER_TEST_FAILED", err instanceof ProviderError ? err.safeMessage : String(err?.message ?? err));
|
|
637
|
+
}
|
|
638
|
+
finally {
|
|
639
|
+
if (runtime)
|
|
640
|
+
await runtime.dispose();
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
switch (sub) {
|
|
644
|
+
case "list":
|
|
645
|
+
case "check": {
|
|
646
|
+
const providers = registry.list().map((def) => {
|
|
647
|
+
const s = resolveProviderStatus(def, ctxFor(def));
|
|
648
|
+
const discovery = buildProviderDiscoverySummary(def, s);
|
|
649
|
+
return {
|
|
650
|
+
providerId: def.id,
|
|
651
|
+
displayName: discovery.displayName,
|
|
652
|
+
implemented: def.enabled,
|
|
653
|
+
status: s.status,
|
|
654
|
+
runnable: s.runnable,
|
|
655
|
+
approval: store.get(def.id) ? "present" : "missing",
|
|
656
|
+
modelDiscovery: {
|
|
657
|
+
status: discovery.modelDiscovery.status,
|
|
658
|
+
defaultModelId: discovery.modelDiscovery.defaultModelId,
|
|
659
|
+
modelCount: discovery.modelDiscovery.models.length,
|
|
660
|
+
allowCustomModel: discovery.modelDiscovery.allowCustomModel,
|
|
661
|
+
},
|
|
662
|
+
versionReadiness: discovery.versionReadiness,
|
|
663
|
+
streaming: discovery.streaming,
|
|
664
|
+
};
|
|
665
|
+
});
|
|
666
|
+
if (args.includes("--json")) {
|
|
667
|
+
process.stdout.write(JSON.stringify({
|
|
668
|
+
config: { source: cfg.source ?? null, ok: cfg.ok, errors: cfg.errors },
|
|
669
|
+
providers,
|
|
670
|
+
}, null, 2) + "\n");
|
|
671
|
+
if (!cfg.ok)
|
|
672
|
+
process.exitCode = 1;
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const lines = [`providers (config: ${cfg.source ?? "none (fixture only)"}${cfg.ok ? "" : " — INVALID"}):`];
|
|
676
|
+
for (const p of providers) {
|
|
677
|
+
const appr = p.approval === "present" ? " approval=present" : "";
|
|
678
|
+
lines.push(` - ${p.providerId.padEnd(10)} impl=${p.implemented ? "yes" : "no "} status=${p.status} runnable=${p.runnable}${appr}`);
|
|
679
|
+
}
|
|
680
|
+
if (!cfg.ok)
|
|
681
|
+
lines.push("", "config errors:", ...cfg.errors.map((e) => " ! " + e));
|
|
682
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
683
|
+
if (!cfg.ok)
|
|
684
|
+
process.exitCode = 1;
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
case "models": {
|
|
688
|
+
if (!provider)
|
|
689
|
+
return failProviders("models requires --provider <id>");
|
|
690
|
+
const def = registry.get(provider);
|
|
691
|
+
if (!def)
|
|
692
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
693
|
+
const status = resolveProviderStatus(def, ctxFor(def));
|
|
694
|
+
const discovery = buildProviderDiscoverySummary(def, status);
|
|
695
|
+
if (status.runnable && def.id === CODEX_LOCAL_ID) {
|
|
696
|
+
const env = minimalNonSecretProviderEnv(CODEX_LOCAL_ENV_ALLOWLIST_KEYS);
|
|
697
|
+
discovery.modelDiscovery = await discoverCodexCliModels({
|
|
698
|
+
executable: CODEX_LOCAL_DEFAULT_EXECUTABLE,
|
|
699
|
+
cwd: process.cwd(),
|
|
700
|
+
envAllowlist: env,
|
|
701
|
+
platformBasics: env,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
process.stdout.write(JSON.stringify({
|
|
705
|
+
...discovery,
|
|
706
|
+
config: { source: cfg.source ?? null, ok: cfg.ok, errors: cfg.errors },
|
|
707
|
+
}, null, 2) + "\n");
|
|
708
|
+
if (!cfg.ok || !status.runnable)
|
|
709
|
+
process.exitCode = 1;
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
case "review": {
|
|
713
|
+
if (!provider)
|
|
714
|
+
return failProviders("review requires --provider <id>");
|
|
715
|
+
const def = registry.get(provider);
|
|
716
|
+
if (!def)
|
|
717
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
718
|
+
process.stdout.write(JSON.stringify(buildPolicyReview(def, ctxFor(def)), null, 2) + "\n");
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
case "setup": {
|
|
722
|
+
if (!provider)
|
|
723
|
+
return failProviders("setup requires --provider <id>");
|
|
724
|
+
const def = registry.get(provider);
|
|
725
|
+
if (!def)
|
|
726
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
727
|
+
const input = ctxFor(def);
|
|
728
|
+
const status = resolveProviderStatus(def, input);
|
|
729
|
+
const policyEntry = getPolicyEntry(cfg.config, def.id);
|
|
730
|
+
const refs = policyEntry?.auth.allowedSecretRefs ?? def.authPolicy?.allowedSecretRefs ?? [];
|
|
731
|
+
const needsApproval = status.status === "requires_approval" || status.status === "stale_approval";
|
|
732
|
+
const canApprove = needsApproval && Boolean(policyEntry);
|
|
733
|
+
const canRunAfterSetup = status.runnable || canApprove;
|
|
734
|
+
const setupStatus = !cfg.ok ? "blocked" : status.runnable ? "ready" : canApprove ? "needs_approval" : "blocked";
|
|
735
|
+
const base = ["node", "packages/agent/dist/cli.js"];
|
|
736
|
+
const cfgFlags = [...(configPath ? ["--config", configPath] : []), ...(storePathArg ? ["--store", storePathArg] : [])];
|
|
737
|
+
const agentFlags = [...(configPath ? ["--provider-policy", configPath] : []), ...(storePathArg ? ["--provider-approvals", storePathArg] : [])];
|
|
738
|
+
const steps = [
|
|
739
|
+
{ name: "review", command: [...base, "providers", "review", "--provider", def.id, ...cfgFlags] },
|
|
740
|
+
...(refs.length ? [{ name: "secrets", command: [...base, "providers", "secrets", "--provider", def.id, ...cfgFlags] }] : []),
|
|
741
|
+
...(canApprove ? [{ name: "approve", command: [...base, "providers", "approve", "--provider", def.id, "--yes", ...cfgFlags] }] : []),
|
|
742
|
+
...(canRunAfterSetup ? [
|
|
743
|
+
{ name: "test", command: [...base, "providers", "test", "--provider", def.id, ...cfgFlags] },
|
|
744
|
+
{ name: "agent", command: [...base, "agent", "--provider", def.id, ...agentFlags] },
|
|
745
|
+
{ name: "certify", command: [...base, "certify", "--provider", def.id, ...cfgFlags] },
|
|
746
|
+
] : []),
|
|
747
|
+
];
|
|
748
|
+
const discovery = buildProviderDiscoverySummary(def, status);
|
|
749
|
+
const out = {
|
|
750
|
+
providerId: def.id,
|
|
751
|
+
displayName: discovery.displayName,
|
|
752
|
+
setupStatus,
|
|
753
|
+
config: { source: cfg.source ?? null, ok: cfg.ok, errors: cfg.errors },
|
|
754
|
+
runnability: {
|
|
755
|
+
status: status.status,
|
|
756
|
+
runnable: status.runnable,
|
|
757
|
+
safeMessage: status.safeMessage,
|
|
758
|
+
},
|
|
759
|
+
modelDiscovery: discovery.modelDiscovery,
|
|
760
|
+
versionReadiness: discovery.versionReadiness,
|
|
761
|
+
streaming: discovery.streaming,
|
|
762
|
+
setupHints: discovery.setupHints,
|
|
763
|
+
next: setupStatus === "blocked" ? status.safeMessage : steps[0]?.command.join(" "),
|
|
764
|
+
steps,
|
|
765
|
+
};
|
|
766
|
+
if (args.includes("--json")) {
|
|
767
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
process.stdout.write([
|
|
771
|
+
`provider setup: ${def.id}`,
|
|
772
|
+
`status: ${setupStatus}`,
|
|
773
|
+
`runnability: ${status.status} runnable=${status.runnable}`,
|
|
774
|
+
...steps.map((s, i) => `${i + 1}. ${s.name}: ${s.command.join(" ")}`),
|
|
775
|
+
...(setupStatus === "blocked" ? [`next: ${status.safeMessage}`] : []),
|
|
776
|
+
].join("\n") + "\n");
|
|
777
|
+
}
|
|
778
|
+
if (setupStatus === "blocked")
|
|
779
|
+
process.exitCode = 1;
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
case "approvals": {
|
|
783
|
+
const recs = store.list();
|
|
784
|
+
const approvals = recs.map((r) => {
|
|
785
|
+
const def = registry.get(r.providerId);
|
|
786
|
+
const s = def ? resolveProviderStatus(def, ctxFor(def)) : undefined;
|
|
787
|
+
const current = Boolean(s?.policyHash && r.policyHash === s.policyHash && r.providerDefinitionHash === s.defHash);
|
|
788
|
+
return {
|
|
789
|
+
providerId: r.providerId,
|
|
790
|
+
approvalId: r.approvalId,
|
|
791
|
+
approvedAt: r.approvedAt,
|
|
792
|
+
current,
|
|
793
|
+
status: s?.status ?? "unknown_provider",
|
|
794
|
+
};
|
|
795
|
+
});
|
|
796
|
+
if (args.includes("--json")) {
|
|
797
|
+
process.stdout.write(JSON.stringify({
|
|
798
|
+
store: approvalStorePath(storePathArg),
|
|
799
|
+
repaired: store.repaired,
|
|
800
|
+
approvals,
|
|
801
|
+
}, null, 2) + "\n");
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const lines = [`approvals (${approvalStorePath(storePathArg)}):`];
|
|
805
|
+
if (store.repaired)
|
|
806
|
+
lines.push(" (note: prior store was missing/corrupt; starting empty)");
|
|
807
|
+
for (const r of approvals) {
|
|
808
|
+
lines.push(` - ${r.providerId.padEnd(10)} ${r.approvalId} approvedAt=${r.approvedAt} ${r.current ? "[current]" : "[stale]"}`);
|
|
809
|
+
}
|
|
810
|
+
if (!approvals.length)
|
|
811
|
+
lines.push(" (none)");
|
|
812
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
case "secrets": {
|
|
816
|
+
if (!provider)
|
|
817
|
+
return failProviders("secrets requires --provider <id>");
|
|
818
|
+
if (!cfg.ok)
|
|
819
|
+
return failProviders("provider policy config is invalid; fix it before reviewing secrets");
|
|
820
|
+
const def = registry.get(provider);
|
|
821
|
+
if (!def)
|
|
822
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
823
|
+
const policyEntry = getPolicyEntry(cfg.config, def.id);
|
|
824
|
+
const refs = policyEntry?.auth.allowedSecretRefs ?? def.authPolicy?.allowedSecretRefs ?? [];
|
|
825
|
+
const review = buildSecretBindingReview(def.id, refs, flagValues(args, "--bind"));
|
|
826
|
+
process.stdout.write(JSON.stringify(review, null, 2) + "\n");
|
|
827
|
+
if (review.errors.length)
|
|
828
|
+
process.exitCode = 1;
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
case "diagnose": {
|
|
832
|
+
if (!provider)
|
|
833
|
+
return failProviders("diagnose requires --provider <id>");
|
|
834
|
+
const def = registry.get(provider);
|
|
835
|
+
if (!def)
|
|
836
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
837
|
+
const runnability = ctxFor(def);
|
|
838
|
+
const status = resolveProviderStatus(def, runnability);
|
|
839
|
+
const policyReview = buildPolicyReview(def, runnability);
|
|
840
|
+
const policyEntry = getPolicyEntry(cfg.config, def.id);
|
|
841
|
+
const refs = policyEntry?.auth.allowedSecretRefs ?? def.authPolicy?.allowedSecretRefs ?? [];
|
|
842
|
+
const secretBinding = buildSecretBindingReview(def.id, refs, flagValues(args, "--bind"));
|
|
843
|
+
const processInfo = { platform: process.platform, enforcement: minimalSupervisorEnforcement() };
|
|
844
|
+
if (args.includes("--json")) {
|
|
845
|
+
process.stdout.write(JSON.stringify({
|
|
846
|
+
providerId: def.id,
|
|
847
|
+
displayName: def.displayName,
|
|
848
|
+
config: { source: cfg.source ?? null, ok: cfg.ok, errors: cfg.errors },
|
|
849
|
+
runnability: {
|
|
850
|
+
status: status.status,
|
|
851
|
+
runnable: status.runnable,
|
|
852
|
+
reason: status.reason,
|
|
853
|
+
safeMessage: status.safeMessage,
|
|
854
|
+
},
|
|
855
|
+
approval: policyReview.approval ?? null,
|
|
856
|
+
policyReview,
|
|
857
|
+
secretBinding,
|
|
858
|
+
process: processInfo,
|
|
859
|
+
}, null, 2) + "\n");
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
const missing = secretBinding.missingRequiredRefs.length ? secretBinding.missingRequiredRefs.join(", ") : "none";
|
|
863
|
+
const approval = policyReview.approval ? (policyReview.approval.current ? "current" : "stale") : "missing";
|
|
864
|
+
process.stdout.write([
|
|
865
|
+
`provider diagnose: ${def.id}`,
|
|
866
|
+
`config: ${cfg.source ?? "none"} ${cfg.ok ? "ok" : "INVALID"}`,
|
|
867
|
+
`status: ${status.status} runnable=${status.runnable}`,
|
|
868
|
+
`approval: ${approval}`,
|
|
869
|
+
`secrets: ${secretBinding.complete ? "complete" : `incomplete missing=${missing}`}`,
|
|
870
|
+
`process: platform=${process.platform} network=${processInfo.enforcement.network} filesystem=${processInfo.enforcement.filesystem}`,
|
|
871
|
+
`next: ${status.runnable && secretBinding.complete ? `providers test --provider ${def.id}` : status.safeMessage}`,
|
|
872
|
+
].join("\n") + "\n");
|
|
873
|
+
}
|
|
874
|
+
if (!cfg.ok || !status.runnable || !secretBinding.complete || secretBinding.errors.length)
|
|
875
|
+
process.exitCode = 1;
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
case "test": {
|
|
879
|
+
await runProviderTest();
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
case "approve": {
|
|
883
|
+
if (!provider)
|
|
884
|
+
return failProviders("approve requires --provider <id>");
|
|
885
|
+
if (!yes && !dryRun && !interactive)
|
|
886
|
+
return failProviders("approve requires --yes, --dry-run, or --interactive");
|
|
887
|
+
const def = registry.get(provider);
|
|
888
|
+
if (!def)
|
|
889
|
+
return failProviders(`unknown provider: ${provider}`);
|
|
890
|
+
if (!cfg.ok)
|
|
891
|
+
return failProviders("provider policy config is invalid; fix it before approving");
|
|
892
|
+
const entry = getPolicyEntry(cfg.config, def.id);
|
|
893
|
+
const needsPolicy = def.authRequirement === "required" || def.networkRequirement === "required" ||
|
|
894
|
+
def.capabilities.requiresAuth || def.capabilities.usesNetwork;
|
|
895
|
+
if (needsPolicy && !entry)
|
|
896
|
+
return failProviders(`provider '${provider}' requires a policy; declare it in the providers config first`);
|
|
897
|
+
const s = resolveProviderStatus(def, ctxFor(def));
|
|
898
|
+
if (s.status === "blocked_io_authority" || s.status === "blocked_unknown_trust") {
|
|
899
|
+
return failProviders(`cannot approve '${provider}': ${s.status}`);
|
|
900
|
+
}
|
|
901
|
+
const interactiveApprove = interactive && !yes && !dryRun;
|
|
902
|
+
if (interactiveApprove && !(await confirmProviderApproval(def.id))) {
|
|
903
|
+
return failProviders(`approval cancelled for '${provider}'`);
|
|
904
|
+
}
|
|
905
|
+
const record = createApprovalRecord({
|
|
906
|
+
providerId: def.id,
|
|
907
|
+
providerDefinitionHash: s.defHash,
|
|
908
|
+
policyHash: s.policyHash ?? "",
|
|
909
|
+
decisions: approvalDecisionsFor(s.gate),
|
|
910
|
+
approvedAt: new Date().toISOString(),
|
|
911
|
+
});
|
|
912
|
+
const after = dryRun
|
|
913
|
+
? resolveProviderStatus(def, {
|
|
914
|
+
...(entry ? { policyEntry: entry } : {}),
|
|
915
|
+
approval: record,
|
|
916
|
+
policyInvalid: !cfg.ok,
|
|
917
|
+
})
|
|
918
|
+
: (store.approve(record), resolveProviderStatus(def, ctxFor(def)));
|
|
919
|
+
const result = {
|
|
920
|
+
providerId: def.id,
|
|
921
|
+
dryRun,
|
|
922
|
+
interactive: interactiveApprove,
|
|
923
|
+
written: !dryRun,
|
|
924
|
+
approvalId: record.approvalId,
|
|
925
|
+
statusBefore: s.status,
|
|
926
|
+
statusAfter: after.status,
|
|
927
|
+
runnableAfter: after.runnable,
|
|
928
|
+
notImplemented: !def.enabled,
|
|
929
|
+
};
|
|
930
|
+
if (args.includes("--json")) {
|
|
931
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
process.stdout.write(`${dryRun ? "approval dry-run" : "approved"} '${provider}' (approvalId=${record.approvalId}). status=${after.status} runnable=${after.runnable}\n`);
|
|
935
|
+
if (!def.enabled) {
|
|
936
|
+
process.stdout.write(`note: '${provider}' is not implemented; the approval is ${dryRun ? "not written and would not make it" : "recorded for the future but the provider is NOT"} runnable.\n`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
case "revoke": {
|
|
942
|
+
if (!provider)
|
|
943
|
+
return failProviders("revoke requires --provider <id>");
|
|
944
|
+
if (!yes)
|
|
945
|
+
return failProviders("revoke requires --yes to confirm");
|
|
946
|
+
const removed = store.revoke(provider);
|
|
947
|
+
if (args.includes("--json")) {
|
|
948
|
+
process.stdout.write(JSON.stringify({ providerId: provider, revoked: removed, written: removed }, null, 2) + "\n");
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
process.stdout.write(removed ? `revoked approval for '${provider}'\n` : `no approval to revoke for '${provider}'\n`);
|
|
952
|
+
}
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
default:
|
|
956
|
+
return failProviders(`unknown providers subcommand: ${sub ?? "(none)"}. Use list|check|setup|review|models|secrets|diagnose|test|approve|approvals|revoke`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function failProviders(message) {
|
|
960
|
+
failCli("CLI_OPERATOR_ERROR", message);
|
|
961
|
+
}
|
|
962
|
+
function failCli(code, message) {
|
|
963
|
+
logStderr(`${code}: ${message}`);
|
|
964
|
+
process.exitCode = 1;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* `adapter` — run the XENO-owned adapter PROCESS for a provider. This is NOT ACP mode: stdout is
|
|
968
|
+
* the adapter event protocol (v1 JSON lines), which the harness/supervisor reads. Only the
|
|
969
|
+
* XENO-owned `xeno-local` adapter is provided; no third-party provider CLI is launched.
|
|
970
|
+
*/
|
|
971
|
+
function runAdapterProcess(rest) {
|
|
972
|
+
const provider = flagValue(rest, "--provider");
|
|
973
|
+
const mode = (flagValue(rest, "--mode") ?? "normal");
|
|
974
|
+
const requestId = flagValue(rest, "--request-id") ?? "xeno-local-req";
|
|
975
|
+
if (provider !== XENO_LOCAL_ID) {
|
|
976
|
+
failCli("ADAPTER_PROVIDER_UNSUPPORTED", `unsupported provider '${provider ?? "(none)"}'. Only '${XENO_LOCAL_ID}' is a XENO-owned local adapter.`);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
emitXenoLocalStream((line) => void process.stdout.write(line), { mode, requestId });
|
|
980
|
+
if (mode === "delayed") {
|
|
981
|
+
// Keep the process alive (cancellable) so the supervisor can exercise timeout/cancel. The
|
|
982
|
+
// supervisor force-kills (SIGKILL / taskkill /F) on cancel/timeout; otherwise we exit at ~2s.
|
|
983
|
+
setTimeout(() => {
|
|
984
|
+
process.exitCode = 0;
|
|
985
|
+
}, 2000);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* `process check` — print the minimal supervisor's platform + HONEST enforcement classification.
|
|
990
|
+
* It runs NOTHING (no spawn, no provider) — it only reports what the supervisor would and would
|
|
991
|
+
* NOT enforce. This is a non-ACP, human-readable command.
|
|
992
|
+
*/
|
|
993
|
+
function runProcessCheck(rest) {
|
|
994
|
+
const sub = rest[0];
|
|
995
|
+
if (sub !== undefined && sub !== "check") {
|
|
996
|
+
return failProviders(`unknown process subcommand: ${sub}. Use: process check`);
|
|
997
|
+
}
|
|
998
|
+
const lines = [
|
|
999
|
+
`${AGENT_NAME} ${VERSION} — minimal provider process supervisor (XACP-010)`,
|
|
1000
|
+
`arch: ${process.arch} node: ${process.version}`,
|
|
1001
|
+
"",
|
|
1002
|
+
...enforcementSummaryLines(process.platform),
|
|
1003
|
+
"",
|
|
1004
|
+
"NOTE: this is a minimal supervisor / process boundary, NOT a sandbox. It runs no real",
|
|
1005
|
+
"provider CLI. network and filesystem are NOT enforced against a hostile process.",
|
|
1006
|
+
];
|
|
1007
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1008
|
+
}
|
|
1009
|
+
async function main() {
|
|
1010
|
+
process.on("unhandledRejection", (reason) => logStderr("unhandledRejection", reason?.message ?? String(reason)));
|
|
1011
|
+
process.on("uncaughtException", (err) => logStderr("uncaughtException", err?.message ?? String(err)));
|
|
1012
|
+
const argv = stripColorFlags(process.argv.slice(2));
|
|
1013
|
+
if (argv.includes("--version") || argv[0] === "version") {
|
|
1014
|
+
process.stdout.write(`${AGENT_NAME} ${VERSION}\n`);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (argv[0] === "check") {
|
|
1018
|
+
runCheck(argv.slice(1));
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (argv[0] === "doctor") {
|
|
1022
|
+
runDoctor(argv.slice(1));
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (argv[0] === "support-bundle") {
|
|
1026
|
+
runSupportBundle(argv.slice(1));
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (argv[0] === "certify") {
|
|
1030
|
+
runCertify(argv.slice(1));
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (argv[0] === "providers") {
|
|
1034
|
+
await runProviders(argv.slice(1));
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (argv[0] === "process") {
|
|
1038
|
+
runProcessCheck(argv.slice(1));
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (argv[0] === "adapter") {
|
|
1042
|
+
runAdapterProcess(argv.slice(1));
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
if (argv[0] === "agent") {
|
|
1046
|
+
runAgent(argv.slice(1));
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (argv[0] === "--help" || argv[0] === "-h" || argv[0] === "help" || argv.length === 0) {
|
|
1050
|
+
process.stdout.write(usage());
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
failCli("CLI_UNKNOWN_COMMAND", `unknown command: ${argv.join(" ")}`);
|
|
1054
|
+
process.stderr.write(usage());
|
|
1055
|
+
}
|
|
1056
|
+
void main().catch((err) => {
|
|
1057
|
+
failCli("CLI_FATAL", err instanceof Error ? err.message : String(err));
|
|
1058
|
+
});
|
|
1059
|
+
//# sourceMappingURL=cli.js.map
|