agencyx-cli 0.0.0-stage → 0.1.0-dev.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/agent.js +73 -0
- package/dist/adapters/ambient.js +35 -0
- package/dist/adapters/instructions.js +102 -0
- package/dist/adapters/mcp.js +237 -0
- package/dist/adapters/permissions.js +42 -0
- package/dist/adapters/plugins.js +484 -0
- package/dist/agency/invocation.js +97 -0
- package/dist/agency/prepared-input.js +124 -0
- package/dist/bridge/execution.js +119 -0
- package/dist/bridge/main.js +26 -0
- package/dist/bridge/plan.js +268 -0
- package/dist/bridge/version.js +2 -0
- package/dist/cli-options.js +96 -0
- package/dist/cli.js +41 -0
- package/dist/codex/agents.js +72 -0
- package/dist/codex/app-server.js +116 -0
- package/dist/codex/inspection.js +217 -0
- package/dist/codex/mcp-readiness.js +45 -0
- package/dist/codex/native-plugins.js +109 -0
- package/dist/codex/options.js +170 -0
- package/dist/codex/plugin-hooks.js +93 -0
- package/dist/codex/rpc.js +170 -0
- package/dist/codex/session-proxy.js +258 -0
- package/dist/codex/skill-policy.js +69 -0
- package/dist/codex/skill-views.js +104 -0
- package/dist/codex/toml.js +46 -0
- package/dist/commands/codex.js +118 -0
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/passthrough.js +14 -0
- package/dist/diagnostics/plan.js +87 -0
- package/dist/diagnostics.js +39 -0
- package/dist/model.js +2 -0
- package/dist/runtime/ipc.js +316 -0
- package/dist/runtime/process.js +286 -0
- package/dist/runtime/resolve-command.js +38 -0
- package/dist/runtime/session.js +107 -0
- package/dist/validation.js +31 -0
- package/package.json +41 -4
- package/README.md +0 -3
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fail } from "../diagnostics.js";
|
|
4
|
+
const DISPLAY_OPTIONS = new Set([
|
|
5
|
+
"--log-dir",
|
|
6
|
+
"--log-level",
|
|
7
|
+
"--session-id",
|
|
8
|
+
"--new-session-id",
|
|
9
|
+
]);
|
|
10
|
+
const IGNORED_SWITCHES = new Set([
|
|
11
|
+
"--no-auto-update",
|
|
12
|
+
"--disable-builtin-mcps",
|
|
13
|
+
]);
|
|
14
|
+
export function isVersionProbe(args) {
|
|
15
|
+
return (args.length === 1 &&
|
|
16
|
+
(args[0] === "--version" ||
|
|
17
|
+
args[0] === "--binary-version" ||
|
|
18
|
+
args[0] === "-V"));
|
|
19
|
+
}
|
|
20
|
+
export function parsePreparedInvocation(args, cwd, env) {
|
|
21
|
+
const prepared = {
|
|
22
|
+
cwd,
|
|
23
|
+
pluginRoots: [],
|
|
24
|
+
mcpConfigPaths: [],
|
|
25
|
+
inlineMcpConfigs: [],
|
|
26
|
+
extraInstructionRoots: [],
|
|
27
|
+
readGrants: [],
|
|
28
|
+
disabledMcpNames: [],
|
|
29
|
+
findings: [],
|
|
30
|
+
};
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
const raw = args[i];
|
|
33
|
+
const equals = raw.indexOf("=");
|
|
34
|
+
const flag = equals < 0 ? raw : raw.slice(0, equals);
|
|
35
|
+
if (IGNORED_SWITCHES.has(flag) && equals < 0)
|
|
36
|
+
continue;
|
|
37
|
+
const takesValue = DISPLAY_OPTIONS.has(flag) ||
|
|
38
|
+
[
|
|
39
|
+
"--additional-mcp-config",
|
|
40
|
+
"--plugin-dir",
|
|
41
|
+
"--add-dir",
|
|
42
|
+
"--agent",
|
|
43
|
+
"--disable-mcp-server",
|
|
44
|
+
"-C",
|
|
45
|
+
"--cwd",
|
|
46
|
+
].includes(flag);
|
|
47
|
+
if (!takesValue)
|
|
48
|
+
fail("AGENCY_ARGUMENT_UNSUPPORTED", "compatibility", "The prepared Copilot argument has no verified Codex equivalent. Put native Codex options after --.", { field: flag.startsWith("-") ? flag : "(positional argument)" });
|
|
49
|
+
const value = equals < 0 ? args[++i] : raw.slice(equals + 1);
|
|
50
|
+
if (value === undefined || value.includes("\0"))
|
|
51
|
+
fail("HANDOFF_ARGUMENT_INVALID", "handoff", "A prepared option is missing a valid value.", { field: flag }, 6);
|
|
52
|
+
if (DISPLAY_OPTIONS.has(flag)) {
|
|
53
|
+
prepared.findings.push({
|
|
54
|
+
component: flag,
|
|
55
|
+
state: "adapted",
|
|
56
|
+
reason: "Agency session metadata remains Agency-owned; Codex uses its own logs and session identity.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (flag === "--additional-mcp-config") {
|
|
60
|
+
if (value.startsWith("@"))
|
|
61
|
+
prepared.mcpConfigPaths.push(path.resolve(cwd, value.slice(1)));
|
|
62
|
+
else
|
|
63
|
+
prepared.inlineMcpConfigs.push(value);
|
|
64
|
+
}
|
|
65
|
+
else if (flag === "--plugin-dir")
|
|
66
|
+
prepared.pluginRoots.push(path.resolve(cwd, value));
|
|
67
|
+
else if (flag === "--add-dir")
|
|
68
|
+
prepared.readGrants.push(path.resolve(cwd, value));
|
|
69
|
+
else if (flag === "--agent")
|
|
70
|
+
prepared.selectedAgent = value;
|
|
71
|
+
else if (flag === "--disable-mcp-server") {
|
|
72
|
+
prepared.disabledMcpNames.push(value);
|
|
73
|
+
prepared.findings.push({
|
|
74
|
+
component: "profile-isolation",
|
|
75
|
+
state: "adapted",
|
|
76
|
+
reason: "Copilot discovery suppression is replaced by isolation of the actual Codex sources.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else if (path.resolve(cwd, value) !== path.resolve(cwd)) {
|
|
80
|
+
fail("CWD_MISMATCH", "handoff", "Agency prepared a different working directory. Launch agencyx from the intended directory.", { field: flag }, 6);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
prepared.pluginRoots = [...new Set(prepared.pluginRoots)];
|
|
84
|
+
if (env.AGENCY_REPO_DIR)
|
|
85
|
+
prepared.repositoryRoot = path.resolve(cwd, env.AGENCY_REPO_DIR);
|
|
86
|
+
prepared.readGrants = [...new Set(prepared.readGrants)];
|
|
87
|
+
prepared.extraInstructionRoots = [
|
|
88
|
+
...new Set((env.COPILOT_CUSTOM_INSTRUCTIONS_DIRS ?? "")
|
|
89
|
+
.split(",")
|
|
90
|
+
.map((part) => part.trim())
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.map((part) => path.resolve(cwd, part))),
|
|
93
|
+
];
|
|
94
|
+
if (env.AGENCY_SESSION_CATEGORIZATION_DISABLED === "1")
|
|
95
|
+
prepared.findings.push({
|
|
96
|
+
component: "copilot-session-telemetry",
|
|
97
|
+
state: "adapted",
|
|
98
|
+
reason: "Copilot-specific conversation tracking, categorization and analysis are disabled for the Codex branch. Native Codex sessions are not represented as Copilot transcripts.",
|
|
99
|
+
});
|
|
100
|
+
return prepared;
|
|
101
|
+
}
|
|
102
|
+
export async function readTextFile(file, maxBytes = 2 * 1024 * 1024) {
|
|
103
|
+
try {
|
|
104
|
+
const metadata = await stat(file);
|
|
105
|
+
if (!metadata.isFile() || metadata.size > maxBytes)
|
|
106
|
+
fail("CONFIG_FILE_INVALID", "configuration", "Expected a regular file within the supported size limit.", { path: file }, 5);
|
|
107
|
+
return await readFile(file, "utf8");
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error instanceof Error && error.name === "AgencyxError")
|
|
111
|
+
throw error;
|
|
112
|
+
fail("CONFIG_FILE_UNREADABLE", "configuration", "A prepared configuration or resource file cannot be read.", { path: file }, 5);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export async function readJsonFile(file) {
|
|
116
|
+
const text = await readTextFile(file);
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(text);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
fail("CONFIG_JSON_INVALID", "configuration", "The prepared file is not valid JSON.", { path: file }, 5);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=prepared-input.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { parsePreparedInvocation } from "../agency/prepared-input.js";
|
|
4
|
+
import { AgencyxError, asDiagnostic } from "../diagnostics.js";
|
|
5
|
+
import { engineEnvironment } from "../runtime/session.js";
|
|
6
|
+
import { exitCode, observeSignals, runInteractive, } from "../runtime/process.js";
|
|
7
|
+
import { mergedOverrides, parseCodexOptions } from "../codex/options.js";
|
|
8
|
+
import { configArguments } from "../codex/toml.js";
|
|
9
|
+
import { inspectCodex } from "../codex/inspection.js";
|
|
10
|
+
import { verifyNativePlugins } from "../codex/native-plugins.js";
|
|
11
|
+
import { buildLaunchPlan, verifyMcpOverrides } from "./plan.js";
|
|
12
|
+
import { diagnosticPlan } from "../diagnostics/plan.js";
|
|
13
|
+
import { runAppServer } from "../codex/app-server.js";
|
|
14
|
+
import { verifyNativeAgents } from "../codex/agents.js";
|
|
15
|
+
import { hookTrustOverrides, verifySessionHooks, } from "../codex/plugin-hooks.js";
|
|
16
|
+
export async function executeBridge(args, env, control) {
|
|
17
|
+
const localAbort = new AbortController();
|
|
18
|
+
let engineStarted = false;
|
|
19
|
+
const removeSignals = observeSignals((signal) => localAbort.abort(signal), Boolean(process.stdin.isTTY), () => !engineStarted);
|
|
20
|
+
const signal = AbortSignal.any([control.signal, localAbort.signal]);
|
|
21
|
+
try {
|
|
22
|
+
const { context } = control;
|
|
23
|
+
const prepared = parsePreparedInvocation(args, context.cwd, env);
|
|
24
|
+
const nativeOptions = parseCodexOptions(context.options.codexArgs, context.cwd);
|
|
25
|
+
const codexEnv = engineEnvironment(env);
|
|
26
|
+
const inspection = await inspectCodex(context.codexPath, nativeOptions, context.cwd, codexEnv, { signal });
|
|
27
|
+
const { plan, bindings } = await buildLaunchPlan(context, prepared, inspection, nativeOptions);
|
|
28
|
+
if (signal.aborted)
|
|
29
|
+
throw new AgencyxError({
|
|
30
|
+
code: "LAUNCH_CANCELLED",
|
|
31
|
+
stage: "codex",
|
|
32
|
+
reason: "The session was cancelled during preparation.",
|
|
33
|
+
}, 130);
|
|
34
|
+
if (!plan.errors.length) {
|
|
35
|
+
try {
|
|
36
|
+
const inspectPlan = (checkMcps) => inspectCodex(context.codexPath, {
|
|
37
|
+
...nativeOptions,
|
|
38
|
+
configArgs: [
|
|
39
|
+
...nativeOptions.configArgs,
|
|
40
|
+
...configArguments(plan.overrides),
|
|
41
|
+
],
|
|
42
|
+
}, context.cwd, codexEnv, {
|
|
43
|
+
...(checkMcps ? { selectedMcps: plan.expectedMcps ?? {} } : {}),
|
|
44
|
+
signal,
|
|
45
|
+
skillViews: plan.skillViews,
|
|
46
|
+
});
|
|
47
|
+
let effective = await inspectPlan(true);
|
|
48
|
+
if (nativeOptions.bypassHookTrust) {
|
|
49
|
+
// The frontend's CLI switch does not activate trust in a separate
|
|
50
|
+
// app-server. Apply its explicit intent through a native session
|
|
51
|
+
// layer, then require native discovery to confirm trusted status.
|
|
52
|
+
const trust = hookTrustOverrides(effective);
|
|
53
|
+
if (Object.keys(trust).length) {
|
|
54
|
+
plan.overrides = mergedOverrides(plan.overrides, trust);
|
|
55
|
+
effective = await inspectPlan(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
verifyMcpOverrides(plan, effective);
|
|
59
|
+
verifyNativePlugins(bindings, effective, context.profileMode);
|
|
60
|
+
verifyNativeAgents(plan.agents ?? [], effective);
|
|
61
|
+
verifySessionHooks(plan.sessionHooks ?? [], effective);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
plan.errors.push(asDiagnostic(error));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (signal.aborted)
|
|
68
|
+
throw new AgencyxError({
|
|
69
|
+
code: "LAUNCH_CANCELLED",
|
|
70
|
+
stage: "codex",
|
|
71
|
+
reason: "The session was cancelled during preparation.",
|
|
72
|
+
}, 130);
|
|
73
|
+
const publicPlan = diagnosticPlan(plan);
|
|
74
|
+
await writeFile(path.join(context.sessionRoot, "plan.json"), `${JSON.stringify(publicPlan, null, 2)}\n`, { mode: 0o600 });
|
|
75
|
+
await control.reportPlan(publicPlan);
|
|
76
|
+
if (context.options.dryRun) {
|
|
77
|
+
const code = plan.errors.length ? 4 : 0;
|
|
78
|
+
await control.reportExit({ code, signal: null });
|
|
79
|
+
return code;
|
|
80
|
+
}
|
|
81
|
+
if (plan.errors.length)
|
|
82
|
+
throw new AgencyxError(plan.errors[0], 4);
|
|
83
|
+
if (context.options.agentToolsPolicy === "inherit" &&
|
|
84
|
+
((plan.agents?.length ?? 0) > 0 || prepared.selectedAgent))
|
|
85
|
+
process.stderr.write("Agencyx: source agent tools allow-lists are not applied (--agent-tools=inherit). Native Codex tool access and permissions remain in force.\n");
|
|
86
|
+
if (signal.aborted) {
|
|
87
|
+
await control.reportFailure({
|
|
88
|
+
code: "LAUNCH_CANCELLED",
|
|
89
|
+
stage: "codex",
|
|
90
|
+
reason: "The session was cancelled before Codex started.",
|
|
91
|
+
});
|
|
92
|
+
return 130;
|
|
93
|
+
}
|
|
94
|
+
control.engineStarted();
|
|
95
|
+
engineStarted = true;
|
|
96
|
+
const result = plan.strategy === "app-server"
|
|
97
|
+
? await runAppServer(context, plan, nativeOptions, codexEnv, signal)
|
|
98
|
+
: await runInteractive(context.codexPath, [
|
|
99
|
+
"--no-daemon",
|
|
100
|
+
...nativeOptions.original,
|
|
101
|
+
...configArguments(plan.overrides),
|
|
102
|
+
], { cwd: context.cwd, env: codexEnv, signal });
|
|
103
|
+
await control.reportExit(result);
|
|
104
|
+
return exitCode(result);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (signal.aborted)
|
|
108
|
+
throw new AgencyxError({
|
|
109
|
+
code: "LAUNCH_CANCELLED",
|
|
110
|
+
stage: "codex",
|
|
111
|
+
reason: "The owned session was cancelled.",
|
|
112
|
+
}, signal.reason === "SIGINT" ? 130 : 143);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
removeSignals();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=execution.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isVersionProbe } from "../agency/prepared-input.js";
|
|
2
|
+
import { asDiagnostic, errorExitCode } from "../diagnostics.js";
|
|
3
|
+
import { connectBridge } from "../runtime/ipc.js";
|
|
4
|
+
// This is an Agency launch-protocol compatibility version, not a Codex version.
|
|
5
|
+
import { COPILOT_PROTOCOL_VERSION } from "./version.js";
|
|
6
|
+
export { COPILOT_PROTOCOL_VERSION } from "./version.js";
|
|
7
|
+
export async function bridgeMain(args, env = process.env) {
|
|
8
|
+
const probe = isVersionProbe(args);
|
|
9
|
+
const control = await connectBridge(env, probe ? "probe" : "execution");
|
|
10
|
+
try {
|
|
11
|
+
if (probe) {
|
|
12
|
+
process.stdout.write(`GitHub Copilot CLI ${COPILOT_PROTOCOL_VERSION}\n`);
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
// Keep Agency's separate five-second probe free of heavy engine adapters.
|
|
16
|
+
return await (await import("./execution.js")).executeBridge(args, env, control);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
await control.reportFailure(asDiagnostic(error), errorExitCode(error));
|
|
20
|
+
return errorExitCode(error);
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
control.close();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readJsonFile } from "../agency/prepared-input.js";
|
|
3
|
+
import { AgencyxError, asDiagnostic, fail } from "../diagnostics.js";
|
|
4
|
+
import { isRecord, record } from "../validation.js";
|
|
5
|
+
import { equivalentMcp, parseMcpDocument, planMcps } from "../adapters/mcp.js";
|
|
6
|
+
import { collectInstructions, combineInstructions, } from "../adapters/instructions.js";
|
|
7
|
+
import { adaptPrimaryAgent } from "../adapters/agent.js";
|
|
8
|
+
import { validateReadGrants } from "../adapters/permissions.js";
|
|
9
|
+
import { inspectPlugin } from "../adapters/plugins.js";
|
|
10
|
+
import { bindNativePlugin, pluginOverrides } from "../codex/native-plugins.js";
|
|
11
|
+
import { mergedOverrides } from "../codex/options.js";
|
|
12
|
+
import { validateAmbientCopilotContracts } from "../adapters/ambient.js";
|
|
13
|
+
import { createSkillView } from "../codex/skill-views.js";
|
|
14
|
+
import { prepareAgents } from "../codex/agents.js";
|
|
15
|
+
import { prepareSessionHooks } from "../codex/plugin-hooks.js";
|
|
16
|
+
export async function buildLaunchPlan(context, prepared, inspection, options) {
|
|
17
|
+
const plan = {
|
|
18
|
+
strategy: "direct-cli",
|
|
19
|
+
reason: "All selected components can use the native CLI with session-only overrides.",
|
|
20
|
+
cwd: context.cwd,
|
|
21
|
+
profileMode: context.profileMode,
|
|
22
|
+
mcpNames: [],
|
|
23
|
+
reusedMcpNames: [],
|
|
24
|
+
disabledMcpNames: [],
|
|
25
|
+
serverAliases: {},
|
|
26
|
+
pluginIds: [],
|
|
27
|
+
skillViews: [],
|
|
28
|
+
findings: [...prepared.findings],
|
|
29
|
+
errors: [],
|
|
30
|
+
overrides: {},
|
|
31
|
+
agentToolsPolicy: context.options.agentToolsPolicy ?? "strict",
|
|
32
|
+
};
|
|
33
|
+
const incoming = Object.create(null);
|
|
34
|
+
const bindings = [];
|
|
35
|
+
const specs = [];
|
|
36
|
+
const additions = [];
|
|
37
|
+
const sessionPackages = [];
|
|
38
|
+
const attempt = async (operation) => {
|
|
39
|
+
try {
|
|
40
|
+
await operation();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const diagnostic = asDiagnostic(error);
|
|
44
|
+
plan.errors.push(diagnostic);
|
|
45
|
+
plan.findings.push({
|
|
46
|
+
component: diagnostic.component ?? diagnostic.code,
|
|
47
|
+
state: "unsupported",
|
|
48
|
+
reason: diagnostic.reason,
|
|
49
|
+
...(diagnostic.path ? { path: diagnostic.path } : {}),
|
|
50
|
+
...(diagnostic.field ? { field: diagnostic.field } : {}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
await attempt(() => validateAmbientCopilotContracts(prepared));
|
|
55
|
+
await attempt(() => {
|
|
56
|
+
if (context.profileMode === "profile-only" &&
|
|
57
|
+
inspection.requirementsKnown === false)
|
|
58
|
+
fail("PROFILE_REQUIREMENTS_UNVERIFIED", "compatibility", "The installed Codex cannot report the managed requirements needed to prove profile isolation.");
|
|
59
|
+
});
|
|
60
|
+
await attempt(async () => {
|
|
61
|
+
const documents = [
|
|
62
|
+
...(await Promise.all(prepared.mcpConfigPaths.map(readJsonFile))),
|
|
63
|
+
...prepared.inlineMcpConfigs.map((text) => {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(text);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
fail("CONFIG_JSON_INVALID", "configuration", "An inline Agency MCP configuration is not valid JSON.", {}, 5);
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
71
|
+
];
|
|
72
|
+
for (const document of documents) {
|
|
73
|
+
for (const [name, value] of Object.entries(parseMcpDocument(document))) {
|
|
74
|
+
if (Object.hasOwn(incoming, name) &&
|
|
75
|
+
JSON.stringify(incoming[name]) !== JSON.stringify(value))
|
|
76
|
+
fail("MCP_MERGE_UNVERIFIED", "compatibility", "Multiple prepared sources define the same MCP differently. Agency must deliver one resolved result.", { component: name });
|
|
77
|
+
incoming[name] = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
plan.mcpNames = Object.keys(incoming);
|
|
81
|
+
if (prepared.disabledMcpNames.length &&
|
|
82
|
+
(context.profileMode !== "profile-only" ||
|
|
83
|
+
prepared.disabledMcpNames.some((name) => Object.hasOwn(incoming, name))))
|
|
84
|
+
fail("MCP_DISABLE_UNVERIFIED", "compatibility", "The prepared Copilot server-disable policy cannot be silently applied to different Codex discovery sources.");
|
|
85
|
+
const mcp = planMcps(incoming, inspection.rootMcps, context.profileMode, context.options.conflictPolicy, context.cwd);
|
|
86
|
+
if (mcp.disabled.some((name) => isRecord(inspection.rootMcps[name]) &&
|
|
87
|
+
inspection.rootMcps[name].required === true))
|
|
88
|
+
fail("MCP_REQUIRED_CONFLICT", "compatibility", "A required Codex MCP server cannot be disabled by this session plan.");
|
|
89
|
+
if (mcp.disabled.length &&
|
|
90
|
+
(inspection.requirementsKnown === false ||
|
|
91
|
+
(inspection.requirements &&
|
|
92
|
+
(inspection.requirements.mcp_servers !== undefined ||
|
|
93
|
+
inspection.requirements.mcpServers !== undefined))))
|
|
94
|
+
fail("MCP_MANAGED_OVERRIDE_UNVERIFIED", "compatibility", "The requested replacement or isolation intersects managed MCP requirements.");
|
|
95
|
+
plan.overrides = mcp.overrides;
|
|
96
|
+
plan.expectedMcps = mcp.expected;
|
|
97
|
+
plan.mcpNames = mcp.names;
|
|
98
|
+
plan.reusedMcpNames = mcp.reused;
|
|
99
|
+
plan.disabledMcpNames = mcp.disabled;
|
|
100
|
+
plan.serverAliases = mcp.aliases;
|
|
101
|
+
plan.findings.push(...mcp.findings);
|
|
102
|
+
});
|
|
103
|
+
for (const root of prepared.pluginRoots) {
|
|
104
|
+
await attempt(async () => {
|
|
105
|
+
const spec = await inspectPlugin(root, prepared.selectedAgent, context.options.agentToolsPolicy);
|
|
106
|
+
specs.push(spec);
|
|
107
|
+
for (const name of spec.mcpNames)
|
|
108
|
+
if (!Object.hasOwn(incoming, name))
|
|
109
|
+
fail("PLUGIN_MCP_FINAL_MISSING", "compatibility", "A required plugin MCP is absent from the Agency final result.", { component: spec.name, field: name });
|
|
110
|
+
for (const name of spec.requiredMcpNames)
|
|
111
|
+
if (!plan.mcpNames.includes(name) ||
|
|
112
|
+
plan.serverAliases[name] !== undefined)
|
|
113
|
+
fail("SKILL_DEPENDENCY_UNAVAILABLE", "compatibility", "A native skill dependency is absent or renamed; its structured reference requires a compatible resource view.", { component: spec.name, field: name });
|
|
114
|
+
try {
|
|
115
|
+
if (spec.agents.length)
|
|
116
|
+
throw new AgencyxError({
|
|
117
|
+
code: "PLUGIN_NOT_READY",
|
|
118
|
+
stage: "compatibility",
|
|
119
|
+
reason: "Plugin Markdown agents require native session role registration.",
|
|
120
|
+
});
|
|
121
|
+
const binding = await bindNativePlugin(spec, inspection);
|
|
122
|
+
if (spec.manualInvocationSkills.length ||
|
|
123
|
+
spec.nonUserInvocableSkills.length ||
|
|
124
|
+
(spec.mcpNeedsAdapter &&
|
|
125
|
+
(spec.skills.length > 0 ||
|
|
126
|
+
spec.hooks.some((hook) => hook.count > 0))))
|
|
127
|
+
throw new AgencyxError({
|
|
128
|
+
code: "PLUGIN_NOT_READY",
|
|
129
|
+
stage: "compatibility",
|
|
130
|
+
component: spec.name,
|
|
131
|
+
reason: "Skill invocation restrictions require a session compatibility view.",
|
|
132
|
+
});
|
|
133
|
+
if (binding) {
|
|
134
|
+
bindings.push(binding);
|
|
135
|
+
plan.pluginIds.push(binding.native.id);
|
|
136
|
+
}
|
|
137
|
+
plan.findings.push(...spec.findings);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (!(error instanceof AgencyxError) ||
|
|
141
|
+
error.diagnostic.code !== "PLUGIN_NOT_READY")
|
|
142
|
+
throw error;
|
|
143
|
+
if (inspection.requirementsKnown === false ||
|
|
144
|
+
(isRecord(inspection.config.features) &&
|
|
145
|
+
inspection.config.features.plugins === false) ||
|
|
146
|
+
inspection.requirements?.plugins !== undefined)
|
|
147
|
+
fail("PLUGIN_POLICY_UNVERIFIED", "compatibility", "Plugin restrictions require a verified policy mapping before a skill-only resource view can be used.", { component: spec.name });
|
|
148
|
+
const view = await createSkillView(spec, context.sessionRoot, {
|
|
149
|
+
allowHooks: true,
|
|
150
|
+
});
|
|
151
|
+
for (const skill of view.skills) {
|
|
152
|
+
const collision = inspection.skills.some((existing) => existing.enabled &&
|
|
153
|
+
existing.name === skill.name &&
|
|
154
|
+
!(context.profileMode === "profile-only" &&
|
|
155
|
+
existing.pluginId !== null));
|
|
156
|
+
if (collision ||
|
|
157
|
+
plan.skillViews.some((existing) => existing.skills.some((candidate) => candidate.name === skill.name)))
|
|
158
|
+
fail("SKILL_CONFLICT", "compatibility", "An existing skill has the selected package's qualified name.", { component: skill.name });
|
|
159
|
+
}
|
|
160
|
+
plan.skillViews.push(view);
|
|
161
|
+
sessionPackages.push({
|
|
162
|
+
spec,
|
|
163
|
+
resourceRoot: path.join(view.root, "package"),
|
|
164
|
+
});
|
|
165
|
+
plan.findings.push(...spec.findings.filter((finding) => finding.field !== undefined));
|
|
166
|
+
plan.findings.push({
|
|
167
|
+
component: `plugin:${spec.name}`,
|
|
168
|
+
state: "adapted",
|
|
169
|
+
path: spec.root,
|
|
170
|
+
reason: "Required skills, supported agents and SessionStart hooks use complete session resources and native registration. MCP connections remain Agency-owned. Native discovery and hook trust are checked before launch.",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
await attempt(async () => {
|
|
176
|
+
if (prepared.selectedAgent) {
|
|
177
|
+
const agents = specs.flatMap((spec) => spec.primaryAgentPath ? [spec.primaryAgentPath] : []);
|
|
178
|
+
if (agents.length !== 1)
|
|
179
|
+
fail("AGENT_RENDERED_FILE_MISSING", "compatibility", "The selected primary agent could not be matched to exactly one Agency-rendered file.");
|
|
180
|
+
additions.push(await adaptPrimaryAgent(agents[0], inspection.config, context.options.agentToolsPolicy));
|
|
181
|
+
plan.findings.push({
|
|
182
|
+
component: "primary-agent",
|
|
183
|
+
state: "adapted",
|
|
184
|
+
reason: "Agency-rendered instructions enter the developer instruction layer; all declared execution metadata was checked.",
|
|
185
|
+
});
|
|
186
|
+
if (context.options.agentToolsPolicy === "inherit")
|
|
187
|
+
plan.findings.push({
|
|
188
|
+
component: "primary-agent",
|
|
189
|
+
state: "adapted",
|
|
190
|
+
field: "tools",
|
|
191
|
+
reason: "Explicit inherited-tool mode does not apply the source tools allow-list. The primary agent uses native Codex permissions and tool access.",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
additions.push(...(await collectInstructions(prepared.extraInstructionRoots)));
|
|
195
|
+
for (const spec of specs) {
|
|
196
|
+
if (spec.persona &&
|
|
197
|
+
!additions.some((text) => text.includes(spec.persona)) &&
|
|
198
|
+
!(typeof inspection.config.developer_instructions === "string" &&
|
|
199
|
+
inspection.config.developer_instructions.includes(spec.persona))) {
|
|
200
|
+
additions.push(`Plugin ${JSON.stringify(spec.name)} instructions:\n${spec.persona}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
plan.findings.push(...validateReadGrants(prepared.readGrants, inspection.config, inspection.requirements));
|
|
204
|
+
plan.overrides = mergedOverrides(plan.overrides, pluginOverrides(bindings, inspection, context.profileMode));
|
|
205
|
+
if (additions.length || Object.keys(plan.serverAliases).length) {
|
|
206
|
+
const combined = combineInstructions(inspection.config.developer_instructions, additions, plan.serverAliases);
|
|
207
|
+
if (combined !== undefined)
|
|
208
|
+
plan.developerInstructions = combined;
|
|
209
|
+
const additional = combineInstructions(undefined, additions, plan.serverAliases);
|
|
210
|
+
if (additional !== undefined)
|
|
211
|
+
plan.additionalDeveloperInstructions = additional;
|
|
212
|
+
plan.strategy = "app-server";
|
|
213
|
+
plan.reason =
|
|
214
|
+
"Additional Agency instructions require the authenticated thread configuration interface.";
|
|
215
|
+
}
|
|
216
|
+
if (plan.skillViews.length) {
|
|
217
|
+
plan.strategy = "app-server";
|
|
218
|
+
plan.reason =
|
|
219
|
+
"A validated cold skill bundle requires native skill roots in a private app-server.";
|
|
220
|
+
}
|
|
221
|
+
const agents = await prepareAgents(sessionPackages, context.sessionRoot, inspection, plan.developerInstructions);
|
|
222
|
+
plan.agents = agents.bindings;
|
|
223
|
+
plan.overrides = mergedOverrides(plan.overrides, agents.overrides);
|
|
224
|
+
const hooks = await prepareSessionHooks(sessionPackages, inspection);
|
|
225
|
+
plan.sessionHooks = hooks.bindings;
|
|
226
|
+
plan.overrides = mergedOverrides(plan.overrides, hooks.overrides);
|
|
227
|
+
if (agents.bindings.length || hooks.bindings.length) {
|
|
228
|
+
plan.strategy = "app-server";
|
|
229
|
+
plan.reason =
|
|
230
|
+
"Plugin agents and hooks require native session registration with retained resources.";
|
|
231
|
+
}
|
|
232
|
+
if (hooks.bindings.length && options.bypassHookTrust)
|
|
233
|
+
plan.findings.push({
|
|
234
|
+
component: "session-hooks",
|
|
235
|
+
state: "adapted",
|
|
236
|
+
field: "trust",
|
|
237
|
+
reason: "The user explicitly supplied native --dangerously-bypass-hook-trust for this invocation. Persistent trust records are not created by the wrapper.",
|
|
238
|
+
});
|
|
239
|
+
if (plan.strategy === "app-server") {
|
|
240
|
+
if (options.entry !== "interactive" ||
|
|
241
|
+
options.addDirs.length ||
|
|
242
|
+
options.nativeProfile)
|
|
243
|
+
fail("CODEX_SESSION_MODE_UNSUPPORTED", "compatibility", "This instruction-bearing plan requires a verified new interactive app-server session. Native exec, add-dir, and native profiles are not silently downgraded.");
|
|
244
|
+
if (!inspection.capabilities.remoteTui ||
|
|
245
|
+
!inspection.capabilities.websocketAuth)
|
|
246
|
+
fail("CODEX_REMOTE_UNSUPPORTED", "compatibility", "The installed Codex lacks the authenticated original-TUI handoff required by this plan.");
|
|
247
|
+
}
|
|
248
|
+
else if (!inspection.capabilities.noDaemon) {
|
|
249
|
+
fail("CODEX_ISOLATION_UNSUPPORTED", "compatibility", "The installed CLI lacks --no-daemon; private direct-session isolation is not verified for this version.");
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return { plan, bindings };
|
|
253
|
+
}
|
|
254
|
+
export function verifyMcpOverrides(plan, inspection) {
|
|
255
|
+
for (const name of plan.mcpNames) {
|
|
256
|
+
if (!Object.hasOwn(inspection.rootMcps, name))
|
|
257
|
+
fail("MCP_OVERRIDE_REJECTED", "configuration", "Codex did not retain a planned MCP definition.", { component: name }, 5);
|
|
258
|
+
const expected = plan.expectedMcps?.[name];
|
|
259
|
+
if (expected !== undefined &&
|
|
260
|
+
!equivalentMcp(record(inspection.rootMcps[name], name), record(expected, name), plan.cwd))
|
|
261
|
+
fail("MCP_OVERRIDE_REJECTED", "configuration", "Codex changed the planned MCP connection or tool policy.", { component: name }, 5);
|
|
262
|
+
}
|
|
263
|
+
for (const name of plan.disabledMcpNames) {
|
|
264
|
+
if (record(inspection.rootMcps[name], name).enabled !== false)
|
|
265
|
+
fail("MCP_OVERRIDE_REJECTED", "configuration", "Codex did not disable an old MCP definition.", { component: name }, 5);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { fail } from "./diagnostics.js";
|
|
2
|
+
export function parseCommand(args) {
|
|
3
|
+
const first = args[0];
|
|
4
|
+
if (first === "--help" || first === "-h")
|
|
5
|
+
return { kind: "help" };
|
|
6
|
+
if (first === "--version" || first === "-V")
|
|
7
|
+
return { kind: "version" };
|
|
8
|
+
if (first === "__bridge")
|
|
9
|
+
return { kind: "bridge" };
|
|
10
|
+
if (first === "doctor") {
|
|
11
|
+
if (args.slice(1).some((arg) => arg !== "--json"))
|
|
12
|
+
fail("ARGUMENT_INVALID", "arguments", "Usage: agencyx doctor [--json]", {}, 2);
|
|
13
|
+
return { kind: "doctor", json: args.includes("--json") };
|
|
14
|
+
}
|
|
15
|
+
if (first !== "codex")
|
|
16
|
+
return {
|
|
17
|
+
kind: "passthrough",
|
|
18
|
+
args: args.slice(first === "agency" ? 1 : 0),
|
|
19
|
+
};
|
|
20
|
+
const rest = args.slice(1);
|
|
21
|
+
if (rest.length === 1 && (rest[0] === "--help" || rest[0] === "-h"))
|
|
22
|
+
return { kind: "help" };
|
|
23
|
+
const separator = rest.indexOf("--");
|
|
24
|
+
const before = separator < 0 ? rest : rest.slice(0, separator);
|
|
25
|
+
const codexArgs = separator < 0 ? [] : rest.slice(separator + 1);
|
|
26
|
+
const agencyArgs = [];
|
|
27
|
+
let dryRun = false;
|
|
28
|
+
let json = false;
|
|
29
|
+
let conflictPolicy = "error";
|
|
30
|
+
let agentToolsPolicy = "strict";
|
|
31
|
+
for (let i = 0; i < before.length; i++) {
|
|
32
|
+
const arg = before[i];
|
|
33
|
+
if (arg === "--dry-run")
|
|
34
|
+
dryRun = true;
|
|
35
|
+
else if (arg === "--json")
|
|
36
|
+
json = true;
|
|
37
|
+
else if (arg === "--mcp-conflict" || arg.startsWith("--mcp-conflict=")) {
|
|
38
|
+
const value = arg.includes("=")
|
|
39
|
+
? arg.slice(arg.indexOf("=") + 1)
|
|
40
|
+
: before[++i];
|
|
41
|
+
if (value !== "error" && value !== "prefer-agency")
|
|
42
|
+
fail("ARGUMENT_INVALID", "arguments", "--mcp-conflict must be error or prefer-agency.", {}, 2);
|
|
43
|
+
conflictPolicy = value;
|
|
44
|
+
}
|
|
45
|
+
else if (arg === "--agent-tools" || arg.startsWith("--agent-tools=")) {
|
|
46
|
+
const value = arg.includes("=")
|
|
47
|
+
? arg.slice(arg.indexOf("=") + 1)
|
|
48
|
+
: before[++i];
|
|
49
|
+
if (value !== "strict" && value !== "inherit")
|
|
50
|
+
fail("ARGUMENT_INVALID", "arguments", "--agent-tools must be strict or inherit.", {}, 2);
|
|
51
|
+
agentToolsPolicy = value;
|
|
52
|
+
}
|
|
53
|
+
else if (arg.startsWith("--dry-run=") || arg.startsWith("--json=")) {
|
|
54
|
+
fail("ARGUMENT_INVALID", "arguments", "--dry-run and --json do not take values.", {}, 2);
|
|
55
|
+
}
|
|
56
|
+
else if (arg === "--copilot-path" || arg.startsWith("--copilot-path=")) {
|
|
57
|
+
fail("ARGUMENT_INVALID", "arguments", "The wrapper owns the engine handoff path.", { field: "--copilot-path" }, 2);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
agencyArgs.push(arg);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (json && !dryRun)
|
|
64
|
+
fail("ARGUMENT_INVALID", "arguments", "--json requires --dry-run for agencyx codex.", {}, 2);
|
|
65
|
+
return {
|
|
66
|
+
kind: "codex",
|
|
67
|
+
agencyArgs,
|
|
68
|
+
codexArgs,
|
|
69
|
+
dryRun,
|
|
70
|
+
json,
|
|
71
|
+
conflictPolicy,
|
|
72
|
+
agentToolsPolicy,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export const HELP = `agencyx: Agency configuration, Codex execution
|
|
76
|
+
|
|
77
|
+
Usage:
|
|
78
|
+
agencyx codex [Agency options] [--dry-run [--json]] [-- Codex options]
|
|
79
|
+
agencyx doctor [--json]
|
|
80
|
+
agencyx agency <original Agency arguments>
|
|
81
|
+
agencyx <Agency command> [arguments]
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
agencyx config set --global --mcp kusto
|
|
85
|
+
agencyx codex --profile oncall
|
|
86
|
+
agencyx codex --mcp-conflict=prefer-agency -- --model MODEL
|
|
87
|
+
agencyx codex --agent-tools=inherit --mcp-conflict=prefer-agency
|
|
88
|
+
agencyx copilot
|
|
89
|
+
|
|
90
|
+
Agency profiles go before --. Codex options go after --.
|
|
91
|
+
Selected unsupported components stop the launch. No global Codex injection
|
|
92
|
+
configuration is rewritten. Plugin cold installation is not automatic.
|
|
93
|
+
--agent-tools=inherit registers supported plugin agents with native Codex tool
|
|
94
|
+
permissions; source tools allow-lists are not applied in this explicit mode.
|
|
95
|
+
`;
|
|
96
|
+
//# sourceMappingURL=cli-options.js.map
|